diff options
author | Android (Google) Code Review <android-gerrit@google.com> | 2009-12-17 10:21:31 -0800 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2009-12-17 10:21:31 -0800 |
commit | 082e51712ab9c9ce94eaee88797bbdbc80b2004d (patch) | |
tree | d1828eede455f12ac24bc74fb048b23c46a37a43 /media | |
parent | 31a69fdbe1edd8d686043e8ca7d278289f65808e (diff) | |
parent | 2ea14e231945afb6581fa8f54015b33bc74a19e5 (diff) | |
download | frameworks_base-082e51712ab9c9ce94eaee88797bbdbc80b2004d.zip frameworks_base-082e51712ab9c9ce94eaee88797bbdbc80b2004d.tar.gz frameworks_base-082e51712ab9c9ce94eaee88797bbdbc80b2004d.tar.bz2 |
Merge change I895fb7d7 into eclair-mr2
* changes:
Squashed commit of the following:
Diffstat (limited to 'media')
-rw-r--r-- | media/libmedia/IOMX.cpp | 24 | ||||
-rw-r--r-- | media/libstagefright/MPEG4Extractor.cpp | 8 | ||||
-rw-r--r-- | media/libstagefright/OMXCodec.cpp | 51 | ||||
-rw-r--r-- | media/libstagefright/include/OMX.h | 2 | ||||
-rw-r--r-- | media/libstagefright/include/OMXNodeInstance.h | 1 | ||||
-rw-r--r-- | media/libstagefright/omx/Android.mk | 3 | ||||
-rw-r--r-- | media/libstagefright/omx/OMX.cpp | 40 | ||||
-rw-r--r-- | media/libstagefright/omx/OMXNodeInstance.cpp | 17 | ||||
-rw-r--r-- | media/libstagefright/omx/tests/Android.mk | 23 | ||||
-rw-r--r-- | media/libstagefright/omx/tests/OMXHarness.cpp | 815 | ||||
-rw-r--r-- | media/libstagefright/omx/tests/OMXHarness.h | 105 |
11 files changed, 1027 insertions, 62 deletions
diff --git a/media/libmedia/IOMX.cpp b/media/libmedia/IOMX.cpp index 76a9e7d..b43e48f 100644 --- a/media/libmedia/IOMX.cpp +++ b/media/libmedia/IOMX.cpp @@ -75,7 +75,7 @@ public: : BpInterface<IOMX>(impl) { } - virtual status_t listNodes(List<String8> *list) { + virtual status_t listNodes(List<ComponentInfo> *list) { list->clear(); Parcel data, reply; @@ -84,9 +84,14 @@ public: int32_t n = reply.readInt32(); for (int32_t i = 0; i < n; ++i) { - String8 s = reply.readString8(); + list->push_back(ComponentInfo()); + ComponentInfo &info = *--list->end(); - list->push_back(s); + info.mName = reply.readString8(); + int32_t numRoles = reply.readInt32(); + for (int32_t j = 0; j < numRoles; ++j) { + info.mRoles.push_back(reply.readString8()); + } } return OK; @@ -368,13 +373,20 @@ status_t BnOMX::onTransact( { CHECK_INTERFACE(IOMX, data, reply); - List<String8> list; + List<ComponentInfo> list; listNodes(&list); reply->writeInt32(list.size()); - for (List<String8>::iterator it = list.begin(); + for (List<ComponentInfo>::iterator it = list.begin(); it != list.end(); ++it) { - reply->writeString8(*it); + ComponentInfo &cur = *it; + + reply->writeString8(cur.mName); + reply->writeInt32(cur.mRoles.size()); + for (List<String8>::iterator role_it = cur.mRoles.begin(); + role_it != cur.mRoles.end(); ++role_it) { + reply->writeString8(*role_it); + } } return NO_ERROR; diff --git a/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp index 7969710..143e8ee 100644 --- a/media/libstagefright/MPEG4Extractor.cpp +++ b/media/libstagefright/MPEG4Extractor.cpp @@ -534,8 +534,8 @@ status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) { uint16_t sample_size = U16_AT(&buffer[18]); uint32_t sample_rate = U32_AT(&buffer[24]) >> 16; - printf("*** coding='%s' %d channels, size %d, rate %d\n", - chunk, num_channels, sample_size, sample_rate); + // printf("*** coding='%s' %d channels, size %d, rate %d\n", + // chunk, num_channels, sample_size, sample_rate); mLastTrack->meta->setCString(kKeyMIMEType, FourCC2MIME(chunk_type)); mLastTrack->meta->setInt32(kKeyChannelCount, num_channels); @@ -576,8 +576,8 @@ status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) { uint16_t width = U16_AT(&buffer[6 + 18]); uint16_t height = U16_AT(&buffer[6 + 20]); - printf("*** coding='%s' width=%d height=%d\n", - chunk, width, height); + // printf("*** coding='%s' width=%d height=%d\n", + // chunk, width, height); mLastTrack->meta->setCString(kKeyMIMEType, FourCC2MIME(chunk_type)); mLastTrack->meta->setInt32(kKeyWidth, width); diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp index 99c39f8..47bbda3 100644 --- a/media/libstagefright/OMXCodec.cpp +++ b/media/libstagefright/OMXCodec.cpp @@ -425,14 +425,9 @@ sp<MediaSource> OMXCodec::Create( esds.getCodecSpecificInfo( &codec_specific_data, &codec_specific_data_size); - printf("found codec-specific data of size %d\n", - codec_specific_data_size); - codec->addCodecSpecificData( codec_specific_data, codec_specific_data_size); } else if (meta->findData(kKeyAVCC, &type, &data, &size)) { - printf("found avcc of size %d\n", size); - // Parse the AVCDecoderConfigurationRecord const uint8_t *ptr = (const uint8_t *)data; @@ -1223,7 +1218,7 @@ status_t OMXCodec::allocateBuffersOnPort(OMX_U32 portIndex) { portIndex == kPortIndexInput ? "input" : "output"); } - dumpPortStatus(portIndex); + // dumpPortStatus(portIndex); return OK; } @@ -1273,7 +1268,6 @@ void OMXCodec::on_message(const omx_message &msg) { CHECK_EQ(mPortStatus[kPortIndexInput], ENABLED); drainInputBuffer(&buffers->editItemAt(i)); } - break; } @@ -1282,12 +1276,10 @@ void OMXCodec::on_message(const omx_message &msg) { IOMX::buffer_id buffer = msg.u.extended_buffer_data.buffer; OMX_U32 flags = msg.u.extended_buffer_data.flags; - CODEC_LOGV("FILL_BUFFER_DONE(buffer: %p, size: %ld, flags: 0x%08lx)", + CODEC_LOGV("FILL_BUFFER_DONE(buffer: %p, size: %ld, flags: 0x%08lx, timestamp: %lld us (%.2f secs))", buffer, msg.u.extended_buffer_data.range_length, - flags); - - CODEC_LOGV("FILL_BUFFER_DONE(timestamp: %lld us (%.2f secs))", + flags, msg.u.extended_buffer_data.timestamp, msg.u.extended_buffer_data.timestamp / 1E6); @@ -1315,11 +1307,13 @@ void OMXCodec::on_message(const omx_message &msg) { CHECK_EQ(err, OK); buffers->removeAt(i); +#if 0 } else if (mPortStatus[kPortIndexOutput] == ENABLED && (flags & OMX_BUFFERFLAG_EOS)) { CODEC_LOGV("No more output data."); mNoMoreOutputData = true; mBufferFilled.signal(); +#endif } else if (mPortStatus[kPortIndexOutput] != SHUTTING_DOWN) { CHECK_EQ(mPortStatus[kPortIndexOutput], ENABLED); @@ -1351,6 +1345,11 @@ void OMXCodec::on_message(const omx_message &msg) { mFilledBuffers.push_back(i); mBufferFilled.signal(); + + if (msg.u.extended_buffer_data.flags & OMX_BUFFERFLAG_EOS) { + CODEC_LOGV("No more output data."); + mNoMoreOutputData = true; + } } break; @@ -1374,7 +1373,7 @@ void OMXCodec::onEvent(OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2) { case OMX_EventError: { - LOGE("ERROR(%ld, %ld)", data1, data2); + LOGE("ERROR(0x%08lx, %ld)", data1, data2); setState(ERROR); break; @@ -1386,6 +1385,7 @@ void OMXCodec::onEvent(OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2) { break; } +#if 0 case OMX_EventBufferFlag: { CODEC_LOGV("EVENT_BUFFER_FLAG(%ld)", data1); @@ -1395,6 +1395,7 @@ void OMXCodec::onEvent(OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2) { } break; } +#endif default: { @@ -1565,13 +1566,6 @@ void OMXCodec::onCmdComplete(OMX_COMMANDTYPE cmd, OMX_U32 data) { CODEC_LOGV("Finished flushing both ports, now continuing from" " seek-time."); - // Clear this flag in case the decoder sent us either - // the EVENT_BUFFER_FLAG(1) or an output buffer with - // the EOS flag set _while_ flushing. Since we're going - // to submit "fresh" input data now, this flag no longer - // applies to our future. - mNoMoreOutputData = false; - drainInputBuffers(); fillOutputBuffers(); } @@ -1832,6 +1826,8 @@ void OMXCodec::drainInputBuffer(BufferInfo *info) { memcpy(info->mMem->pointer(), specific->mData, specific->mSize); } + mNoMoreOutputData = false; + status_t err = mOMX->emptyBuffer( mNode, info->mBuffer, 0, size, OMX_BUFFERFLAG_ENDOFFRAME | OMX_BUFFERFLAG_CODECCONFIG, @@ -1849,7 +1845,9 @@ void OMXCodec::drainInputBuffer(BufferInfo *info) { if (mSeekTimeUs >= 0) { MediaSource::ReadOptions options; options.setSeekTo(mSeekTimeUs); + mSeekTimeUs = -1; + mBufferFilled.signal(); err = mSource->read(&srcBuffer, &options); } else { @@ -1866,6 +1864,8 @@ void OMXCodec::drainInputBuffer(BufferInfo *info) { mSignalledEOS = true; } else { + mNoMoreOutputData = false; + srcLength = srcBuffer->range_length(); if (info->mMem->size() < srcLength) { @@ -1878,10 +1878,10 @@ void OMXCodec::drainInputBuffer(BufferInfo *info) { srcLength); if (srcBuffer->meta_data()->findInt64(kKeyTime, ×tampUs)) { - CODEC_LOGV("Calling emptyBuffer on buffer %p (length %d)", - info->mBuffer, srcLength); - CODEC_LOGV("Calling emptyBuffer with timestamp %lld us (%.2f secs)", - timestampUs, timestampUs / 1E6); + CODEC_LOGV("Calling emptyBuffer on buffer %p (length %d), " + "timestamp %lld us (%.2f secs)", + info->mBuffer, srcLength, + timestampUs, timestampUs / 1E6); } } @@ -2298,7 +2298,6 @@ status_t OMXCodec::read( CODEC_LOGV("seeking to %lld us (%.2f secs)", seekTimeUs, seekTimeUs / 1E6); mSignalledEOS = false; - mNoMoreOutputData = false; CHECK(seekTimeUs >= 0); mSeekTimeUs = seekTimeUs; @@ -2317,6 +2316,10 @@ status_t OMXCodec::read( if (emulateOutputFlushCompletion) { onCmdComplete(OMX_CommandFlush, kPortIndexOutput); } + + while (mSeekTimeUs >= 0) { + mBufferFilled.wait(mLock); + } } while (mState != ERROR && !mNoMoreOutputData && mFilledBuffers.empty()) { diff --git a/media/libstagefright/include/OMX.h b/media/libstagefright/include/OMX.h index 01b8e7a..ce0b0d5 100644 --- a/media/libstagefright/include/OMX.h +++ b/media/libstagefright/include/OMX.h @@ -31,7 +31,7 @@ class OMX : public BnOMX, public: OMX(); - virtual status_t listNodes(List<String8> *list); + virtual status_t listNodes(List<ComponentInfo> *list); virtual status_t allocateNode( const char *name, const sp<IOMXObserver> &observer, node_id *node); diff --git a/media/libstagefright/include/OMXNodeInstance.h b/media/libstagefright/include/OMXNodeInstance.h index 19d3940..923b801 100644 --- a/media/libstagefright/include/OMXNodeInstance.h +++ b/media/libstagefright/include/OMXNodeInstance.h @@ -85,6 +85,7 @@ private: OMX::node_id mNodeID; OMX_HANDLETYPE mHandle; sp<IOMXObserver> mObserver; + bool mDying; struct ActiveBuffer { OMX_U32 mPortIndex; diff --git a/media/libstagefright/omx/Android.mk b/media/libstagefright/omx/Android.mk index 4f88d99..2473731 100644 --- a/media/libstagefright/omx/Android.mk +++ b/media/libstagefright/omx/Android.mk @@ -47,3 +47,6 @@ LOCAL_PRELINK_MODULE:= false LOCAL_MODULE:= libstagefright_omx include $(BUILD_SHARED_LIBRARY) + +include $(call all-makefiles-under,$(LOCAL_PATH)) + diff --git a/media/libstagefright/omx/OMX.cpp b/media/libstagefright/omx/OMX.cpp index 94dbb6d..8c3f252 100644 --- a/media/libstagefright/omx/OMX.cpp +++ b/media/libstagefright/omx/OMX.cpp @@ -208,39 +208,27 @@ void OMX::binderDied(const wp<IBinder> &the_late_who) { instance->onObserverDied(mMaster); } -#if 0 -static void dumpRoles(OMXMaster *master, const char *name) { - Vector<String8> roles; - OMX_ERRORTYPE err = master->getRolesOfComponent(name, &roles); - - if (err != OMX_ErrorNone) { - LOGE("Could not get roles for component '%s'.", name); - return; - } - - if (roles.isEmpty()) { - LOGE("Component '%s' has NO roles!", name); - return; - } - - LOGI("Component '%s' has the following roles:", name); - - for (size_t i = 0; i < roles.size(); ++i) { - LOGI("%d) %s", i + 1, roles[i].string()); - } -} -#endif - -status_t OMX::listNodes(List<String8> *list) { +status_t OMX::listNodes(List<ComponentInfo> *list) { list->clear(); OMX_U32 index = 0; char componentName[256]; while (mMaster->enumerateComponents( componentName, sizeof(componentName), index) == OMX_ErrorNone) { - list->push_back(String8(componentName)); + list->push_back(ComponentInfo()); + ComponentInfo &info = *--list->end(); - // dumpRoles(mMaster, componentName); + info.mName = componentName; + + Vector<String8> roles; + OMX_ERRORTYPE err = + mMaster->getRolesOfComponent(componentName, &roles); + + if (err == OMX_ErrorNone) { + for (OMX_U32 i = 0; i < roles.size(); ++i) { + info.mRoles.push_back(roles[i]); + } + } ++index; } diff --git a/media/libstagefright/omx/OMXNodeInstance.cpp b/media/libstagefright/omx/OMXNodeInstance.cpp index 288710e..4eb6417 100644 --- a/media/libstagefright/omx/OMXNodeInstance.cpp +++ b/media/libstagefright/omx/OMXNodeInstance.cpp @@ -78,7 +78,8 @@ OMXNodeInstance::OMXNodeInstance( : mOwner(owner), mNodeID(NULL), mHandle(NULL), - mObserver(observer) { + mObserver(observer), + mDying(false) { } OMXNodeInstance::~OMXNodeInstance() { @@ -114,6 +115,11 @@ status_t OMXNodeInstance::freeNode(OMXMaster *master) { // for components that don't do this themselves on a call to // "FreeHandle". + // The code below may trigger some more events to be dispatched + // by the OMX component - we want to ignore them as our client + // does not expect them. + mDying = true; + OMX_STATETYPE state; CHECK_EQ(OMX_GetState(mHandle, &state), OMX_ErrorNone); switch (state) { @@ -406,6 +412,9 @@ OMX_ERRORTYPE OMXNodeInstance::OnEvent( OMX_IN OMX_U32 nData2, OMX_IN OMX_PTR pEventData) { OMXNodeInstance *instance = static_cast<OMXNodeInstance *>(pAppData); + if (instance->mDying) { + return OMX_ErrorNone; + } return instance->owner()->OnEvent( instance->nodeID(), eEvent, nData1, nData2, pEventData); } @@ -416,6 +425,9 @@ OMX_ERRORTYPE OMXNodeInstance::OnEmptyBufferDone( OMX_IN OMX_PTR pAppData, OMX_IN OMX_BUFFERHEADERTYPE* pBuffer) { OMXNodeInstance *instance = static_cast<OMXNodeInstance *>(pAppData); + if (instance->mDying) { + return OMX_ErrorNone; + } return instance->owner()->OnEmptyBufferDone(instance->nodeID(), pBuffer); } @@ -425,6 +437,9 @@ OMX_ERRORTYPE OMXNodeInstance::OnFillBufferDone( OMX_IN OMX_PTR pAppData, OMX_IN OMX_BUFFERHEADERTYPE* pBuffer) { OMXNodeInstance *instance = static_cast<OMXNodeInstance *>(pAppData); + if (instance->mDying) { + return OMX_ErrorNone; + } return instance->owner()->OnFillBufferDone(instance->nodeID(), pBuffer); } diff --git a/media/libstagefright/omx/tests/Android.mk b/media/libstagefright/omx/tests/Android.mk new file mode 100644 index 0000000..cd654510 --- /dev/null +++ b/media/libstagefright/omx/tests/Android.mk @@ -0,0 +1,23 @@ +ifeq ($(BUILD_WITH_FULL_STAGEFRIGHT),true) + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES = \ + OMXHarness.cpp \ + +LOCAL_SHARED_LIBRARIES := \ + libstagefright + +LOCAL_C_INCLUDES:= \ + $(JNI_H_INCLUDE) \ + frameworks/base/media/libstagefright \ + $(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include + +LOCAL_MODULE:= omx_tests + +include $(BUILD_EXECUTABLE) + +endif + + diff --git a/media/libstagefright/omx/tests/OMXHarness.cpp b/media/libstagefright/omx/tests/OMXHarness.cpp new file mode 100644 index 0000000..2e23899 --- /dev/null +++ b/media/libstagefright/omx/tests/OMXHarness.cpp @@ -0,0 +1,815 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "OMXHarness" +#include <utils/Log.h> + +#include "OMXHarness.h" + +#include <sys/time.h> + +#include <binder/ProcessState.h> +#include <binder/IServiceManager.h> +#include <binder/MemoryDealer.h> +#include <media/IMediaPlayerService.h> +#include <media/stagefright/DataSource.h> +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/MediaDebug.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MediaExtractor.h> +#include <media/stagefright/MediaSource.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/OMXCodec.h> + +#define DEFAULT_TIMEOUT 500000 + +namespace android { + +static int64_t getNowUs() { + struct timeval tv; + gettimeofday(&tv, NULL); + + return (int64_t)tv.tv_usec + tv.tv_sec * 1000000; +} + +Harness::Harness() + : mInitCheck(NO_INIT) { + mInitCheck = initOMX(); +} + +Harness::~Harness() { +} + +status_t Harness::initCheck() const { + return mInitCheck; +} + +status_t Harness::initOMX() { + sp<IServiceManager> sm = defaultServiceManager(); + sp<IBinder> binder = sm->getService(String16("media.player")); + sp<IMediaPlayerService> service = interface_cast<IMediaPlayerService>(binder); + mOMX = service->getOMX(); + + return mOMX != 0 ? OK : NO_INIT; +} + +void Harness::onMessage(const omx_message &msg) { + Mutex::Autolock autoLock(mLock); + mMessageQueue.push_back(msg); + mMessageAddedCondition.signal(); +} + +status_t Harness::dequeueMessageForNode( + IOMX::node_id node, omx_message *msg, int64_t timeoutUs) { + return dequeueMessageForNodeIgnoringBuffers( + node, NULL, NULL, msg, timeoutUs); +} + +// static +bool Harness::handleBufferMessage( + const omx_message &msg, + Vector<Buffer> *inputBuffers, + Vector<Buffer> *outputBuffers) { + switch (msg.type) { + case omx_message::EMPTY_BUFFER_DONE: + { + if (inputBuffers) { + for (size_t i = 0; i < inputBuffers->size(); ++i) { + if ((*inputBuffers)[i].mID == msg.u.buffer_data.buffer) { + inputBuffers->editItemAt(i).mFlags &= ~kBufferBusy; + return true; + } + } + CHECK(!"should not be here"); + } + break; + } + + case omx_message::FILL_BUFFER_DONE: + { + if (outputBuffers) { + for (size_t i = 0; i < outputBuffers->size(); ++i) { + if ((*outputBuffers)[i].mID == msg.u.buffer_data.buffer) { + outputBuffers->editItemAt(i).mFlags &= ~kBufferBusy; + return true; + } + } + CHECK(!"should not be here"); + } + break; + } + + default: + break; + } + + return false; +} + +status_t Harness::dequeueMessageForNodeIgnoringBuffers( + IOMX::node_id node, + Vector<Buffer> *inputBuffers, + Vector<Buffer> *outputBuffers, + omx_message *msg, int64_t timeoutUs) { + int64_t finishBy = getNowUs() + timeoutUs; + + for (;;) { + Mutex::Autolock autoLock(mLock); + List<omx_message>::iterator it = mMessageQueue.begin(); + while (it != mMessageQueue.end()) { + if ((*it).node == node) { + if (handleBufferMessage(*it, inputBuffers, outputBuffers)) { + it = mMessageQueue.erase(it); + continue; + } + + *msg = *it; + mMessageQueue.erase(it); + + return OK; + } + + ++it; + } + + status_t err = (timeoutUs < 0) + ? mMessageAddedCondition.wait(mLock) + : mMessageAddedCondition.waitRelative( + mLock, (finishBy - getNowUs()) * 1000); + + if (err == TIMED_OUT) { + return err; + } + CHECK_EQ(err, OK); + } +} + +status_t Harness::getPortDefinition( + IOMX::node_id node, OMX_U32 portIndex, + OMX_PARAM_PORTDEFINITIONTYPE *def) { + def->nSize = sizeof(*def); + def->nVersion.s.nVersionMajor = 1; + def->nVersion.s.nVersionMinor = 0; + def->nVersion.s.nRevision = 0; + def->nVersion.s.nStep = 0; + def->nPortIndex = portIndex; + return mOMX->getParameter( + node, OMX_IndexParamPortDefinition, def, sizeof(*def)); +} + +#define EXPECT(condition, info) \ + if (!(condition)) { \ + LOGE(info); printf("\n * " info "\n"); return UNKNOWN_ERROR; \ + } + +#define EXPECT_SUCCESS(err, info) \ + EXPECT((err) == OK, info " failed") + +status_t Harness::allocatePortBuffers( + const sp<MemoryDealer> &dealer, + IOMX::node_id node, OMX_U32 portIndex, + Vector<Buffer> *buffers) { + buffers->clear(); + + OMX_PARAM_PORTDEFINITIONTYPE def; + status_t err = getPortDefinition(node, portIndex, &def); + EXPECT_SUCCESS(err, "getPortDefinition"); + + for (OMX_U32 i = 0; i < def.nBufferCountActual; ++i) { + Buffer buffer; + buffer.mMemory = dealer->allocate(def.nBufferSize); + buffer.mFlags = 0; + CHECK(buffer.mMemory != NULL); + + err = mOMX->allocateBufferWithBackup( + node, portIndex, buffer.mMemory, &buffer.mID); + EXPECT_SUCCESS(err, "allocateBuffer"); + + buffers->push(buffer); + } + + return OK; +} + +status_t Harness::setRole(IOMX::node_id node, const char *role) { + OMX_PARAM_COMPONENTROLETYPE params; + params.nSize = sizeof(params); + params.nVersion.s.nVersionMajor = 1; + params.nVersion.s.nVersionMinor = 0; + params.nVersion.s.nRevision = 0; + params.nVersion.s.nStep = 0; + strncpy((char *)params.cRole, role, OMX_MAX_STRINGNAME_SIZE - 1); + params.cRole[OMX_MAX_STRINGNAME_SIZE - 1] = '\0'; + + return mOMX->setParameter( + node, OMX_IndexParamStandardComponentRole, + ¶ms, sizeof(params)); +} + +struct NodeReaper { + NodeReaper(const sp<Harness> &harness, IOMX::node_id node) + : mHarness(harness), + mNode(node) { + } + + ~NodeReaper() { + if (mNode != 0) { + mHarness->mOMX->freeNode(mNode); + mNode = 0; + } + } + + void disarm() { + mNode = 0; + } + +private: + sp<Harness> mHarness; + IOMX::node_id mNode; + + NodeReaper(const NodeReaper &); + NodeReaper &operator=(const NodeReaper &); +}; + +static sp<MediaSource> MakeSource( + const char *uri, + const char *mimeType) { + sp<MediaExtractor> extractor = MediaExtractor::CreateFromURI(uri); + + if (extractor == NULL) { + return NULL; + } + + for (size_t i = 0; i < extractor->countTracks(); ++i) { + sp<MetaData> meta = extractor->getTrackMetaData(i); + + const char *trackMIME; + CHECK(meta->findCString(kKeyMIMEType, &trackMIME)); + + if (!strcasecmp(trackMIME, mimeType)) { + return extractor->getTrack(i); + } + } + + return NULL; +} + +status_t Harness::testStateTransitions( + const char *componentName, const char *componentRole) { + if (strncmp(componentName, "OMX.", 4)) { + // Non-OMX components, i.e. software decoders won't execute this + // test. + return OK; + } + + sp<MemoryDealer> dealer = new MemoryDealer(8 * 1024 * 1024); + IOMX::node_id node; + + status_t err = + mOMX->allocateNode(componentName, this, &node); + EXPECT_SUCCESS(err, "allocateNode"); + + NodeReaper reaper(this, node); + + err = setRole(node, componentRole); + EXPECT_SUCCESS(err, "setRole"); + + // Initiate transition Loaded->Idle + err = mOMX->sendCommand(node, OMX_CommandStateSet, OMX_StateIdle); + EXPECT_SUCCESS(err, "sendCommand(go-to-Idle)"); + + omx_message msg; + err = dequeueMessageForNode(node, &msg, DEFAULT_TIMEOUT); + // Make sure node doesn't just transition to idle before we are done + // allocating all input and output buffers. + EXPECT(err == TIMED_OUT, + "Component must not transition from loaded to idle before " + "all input and output buffers are allocated."); + + // Now allocate buffers. + Vector<Buffer> inputBuffers; + err = allocatePortBuffers(dealer, node, 0, &inputBuffers); + EXPECT_SUCCESS(err, "allocatePortBuffers(input)"); + + err = dequeueMessageForNode(node, &msg, DEFAULT_TIMEOUT); + CHECK_EQ(err, TIMED_OUT); + + Vector<Buffer> outputBuffers; + err = allocatePortBuffers(dealer, node, 1, &outputBuffers); + EXPECT_SUCCESS(err, "allocatePortBuffers(output)"); + + err = dequeueMessageForNode(node, &msg, DEFAULT_TIMEOUT); + EXPECT(err == OK + && msg.type == omx_message::EVENT + && msg.u.event_data.event == OMX_EventCmdComplete + && msg.u.event_data.data1 == OMX_CommandStateSet + && msg.u.event_data.data2 == OMX_StateIdle, + "Component did not properly transition to idle state " + "after all input and output buffers were allocated."); + + // Initiate transition Idle->Executing + err = mOMX->sendCommand(node, OMX_CommandStateSet, OMX_StateExecuting); + EXPECT_SUCCESS(err, "sendCommand(go-to-Executing)"); + + err = dequeueMessageForNode(node, &msg, DEFAULT_TIMEOUT); + EXPECT(err == OK + && msg.type == omx_message::EVENT + && msg.u.event_data.event == OMX_EventCmdComplete + && msg.u.event_data.data1 == OMX_CommandStateSet + && msg.u.event_data.data2 == OMX_StateExecuting, + "Component did not properly transition from idle to " + "executing state."); + + for (size_t i = 0; i < outputBuffers.size(); ++i) { + err = mOMX->fillBuffer(node, outputBuffers[i].mID); + EXPECT_SUCCESS(err, "fillBuffer"); + + outputBuffers.editItemAt(i).mFlags |= kBufferBusy; + } + + err = mOMX->sendCommand(node, OMX_CommandFlush, 1); + EXPECT_SUCCESS(err, "sendCommand(flush-output-port)"); + + err = dequeueMessageForNodeIgnoringBuffers( + node, &inputBuffers, &outputBuffers, &msg, DEFAULT_TIMEOUT); + EXPECT(err == OK + && msg.type == omx_message::EVENT + && msg.u.event_data.event == OMX_EventCmdComplete + && msg.u.event_data.data1 == OMX_CommandFlush + && msg.u.event_data.data2 == 1, + "Component did not properly acknowledge flushing the output port."); + + for (size_t i = 0; i < outputBuffers.size(); ++i) { + EXPECT((outputBuffers[i].mFlags & kBufferBusy) == 0, + "Not all output buffers have been returned to us by the time " + "we received the flush-complete notification."); + } + + for (size_t i = 0; i < outputBuffers.size(); ++i) { + err = mOMX->fillBuffer(node, outputBuffers[i].mID); + EXPECT_SUCCESS(err, "fillBuffer"); + + outputBuffers.editItemAt(i).mFlags |= kBufferBusy; + } + + // Initiate transition Executing->Idle + err = mOMX->sendCommand(node, OMX_CommandStateSet, OMX_StateIdle); + EXPECT_SUCCESS(err, "sendCommand(go-to-Idle)"); + + err = dequeueMessageForNodeIgnoringBuffers( + node, &inputBuffers, &outputBuffers, &msg, DEFAULT_TIMEOUT); + EXPECT(err == OK + && msg.type == omx_message::EVENT + && msg.u.event_data.event == OMX_EventCmdComplete + && msg.u.event_data.data1 == OMX_CommandStateSet + && msg.u.event_data.data2 == OMX_StateIdle, + "Component did not properly transition to from executing to " + "idle state."); + + for (size_t i = 0; i < inputBuffers.size(); ++i) { + EXPECT((inputBuffers[i].mFlags & kBufferBusy) == 0, + "Not all input buffers have been returned to us by the " + "time we received the transition-to-idle complete " + "notification."); + } + + for (size_t i = 0; i < outputBuffers.size(); ++i) { + EXPECT((outputBuffers[i].mFlags & kBufferBusy) == 0, + "Not all output buffers have been returned to us by the " + "time we received the transition-to-idle complete " + "notification."); + } + + // Initiate transition Idle->Loaded + err = mOMX->sendCommand(node, OMX_CommandStateSet, OMX_StateLoaded); + EXPECT_SUCCESS(err, "sendCommand(go-to-Loaded)"); + + // Make sure node doesn't just transition to loaded before we are done + // freeing all input and output buffers. + err = dequeueMessageForNode(node, &msg, DEFAULT_TIMEOUT); + CHECK_EQ(err, TIMED_OUT); + + for (size_t i = 0; i < inputBuffers.size(); ++i) { + err = mOMX->freeBuffer(node, 0, inputBuffers[i].mID); + EXPECT_SUCCESS(err, "freeBuffer"); + } + + err = dequeueMessageForNode(node, &msg, DEFAULT_TIMEOUT); + CHECK_EQ(err, TIMED_OUT); + + for (size_t i = 0; i < outputBuffers.size(); ++i) { + err = mOMX->freeBuffer(node, 1, outputBuffers[i].mID); + EXPECT_SUCCESS(err, "freeBuffer"); + } + + err = dequeueMessageForNode(node, &msg, DEFAULT_TIMEOUT); + EXPECT(err == OK + && msg.type == omx_message::EVENT + && msg.u.event_data.event == OMX_EventCmdComplete + && msg.u.event_data.data1 == OMX_CommandStateSet + && msg.u.event_data.data2 == OMX_StateLoaded, + "Component did not properly transition to from idle to " + "loaded state after freeing all input and output buffers."); + + err = mOMX->freeNode(node); + EXPECT_SUCCESS(err, "freeNode"); + + reaper.disarm(); + + node = 0; + + return OK; +} + +static const char *GetMimeFromComponentRole(const char *componentRole) { + struct RoleToMime { + const char *mRole; + const char *mMime; + }; + const RoleToMime kRoleToMime[] = { + { "video_decoder.avc", "video/avc" }, + { "video_decoder.mpeg4", "video/mp4v-es" }, + { "video_decoder.h263", "video/3gpp" }, + + // we appear to use this as a synonym to amrnb. + { "audio_decoder.amr", "audio/3gpp" }, + + { "audio_decoder.amrnb", "audio/3gpp" }, + { "audio_decoder.amrwb", "audio/amr-wb" }, + { "audio_decoder.aac", "audio/mp4a-latm" }, + { "audio_decoder.mp3", "audio/mpeg" } + }; + + for (size_t i = 0; i < sizeof(kRoleToMime) / sizeof(kRoleToMime[0]); ++i) { + if (!strcmp(componentRole, kRoleToMime[i].mRole)) { + return kRoleToMime[i].mMime; + } + } + + return NULL; +} + +static const char *GetURLForMime(const char *mime) { + struct MimeToURL { + const char *mMime; + const char *mURL; + }; + static const MimeToURL kMimeToURL[] = { + { "video/avc", + "file:///sdcard/media_api/video/H264_AAC.3gp" }, + { "video/mp4v-es", "file:///sdcard/media_api/video/gingerkids.MP4" }, + { "video/3gpp", + "file:///sdcard/media_api/video/H263_500_AMRNB_12.3gp" }, + { "audio/3gpp", + "file:///sdcard/media_api/video/H263_500_AMRNB_12.3gp" }, + { "audio/amr-wb", + "file:///sdcard/media_api/music_perf/AMRWB/" + "NIN_AMR-WB_15.85kbps_16kbps.amr" }, + { "audio/mp4a-latm", + "file:///sdcard/media_api/music_perf/AAC/" + "WC_AAC_80kbps_32khz_Stereo_1pCBR_SSE.mp4" }, + { "audio/mpeg", + "file:///sdcard/media_api/music_perf/MP3/" + "WC_256kbps_44.1khz_mono_CBR_DPA.mp3" } + }; + + for (size_t i = 0; i < sizeof(kMimeToURL) / sizeof(kMimeToURL[0]); ++i) { + if (!strcasecmp(kMimeToURL[i].mMime, mime)) { + return kMimeToURL[i].mURL; + } + } + + return NULL; +} + +static sp<MediaSource> CreateSourceForMime(const char *mime) { + const char *url = GetURLForMime(mime); + CHECK(url != NULL); + + sp<MediaExtractor> extractor = MediaExtractor::CreateFromURI(url); + + CHECK(extractor != NULL); + + for (size_t i = 0; i < extractor->countTracks(); ++i) { + sp<MetaData> meta = extractor->getTrackMetaData(i); + CHECK(meta != NULL); + + const char *trackMime; + CHECK(meta->findCString(kKeyMIMEType, &trackMime)); + + if (!strcasecmp(mime, trackMime)) { + return extractor->getTrack(i); + } + } + + return NULL; +} + +static double uniform_rand() { + return (double)rand() / RAND_MAX; +} + +static bool CloseEnough(int64_t time1Us, int64_t time2Us) { +#if 0 + int64_t diff = time1Us - time2Us; + if (diff < 0) { + diff = -diff; + } + + return diff <= 50000; +#else + return time1Us == time2Us; +#endif +} + +status_t Harness::testSeek( + const char *componentName, const char *componentRole) { + bool isEncoder = + !strncmp(componentRole, "audio_encoder.", 14) + || !strncmp(componentRole, "video_encoder.", 14); + + if (isEncoder) { + // Not testing seek behaviour for encoders. + + printf(" * Not testing seek functionality for encoders.\n"); + return OK; + } + + const char *mime = GetMimeFromComponentRole(componentRole); + + if (!mime) { + LOGI("Cannot perform seek test with this componentRole (%s)", + componentRole); + + return OK; + } + + sp<MediaSource> source = CreateSourceForMime(mime); + + sp<MediaSource> seekSource = CreateSourceForMime(mime); + CHECK_EQ(seekSource->start(), OK); + + sp<MediaSource> codec = OMXCodec::Create( + mOMX, source->getFormat(), false /* createEncoder */, + source, componentName); + + CHECK(codec != NULL); + + CHECK_EQ(codec->start(), OK); + + int64_t durationUs; + CHECK(source->getFormat()->findInt64(kKeyDuration, &durationUs)); + + LOGI("stream duration is %lld us (%.2f secs)", + durationUs, durationUs / 1E6); + + static const int32_t kNumIterations = 5000; + + for (int32_t i = 0; i < kNumIterations; ++i) { + int64_t requestedSeekTimeUs; + int64_t actualSeekTimeUs; + MediaSource::ReadOptions options; + + double r = uniform_rand(); + + if (r < 0.5) { + // 50% chance of just continuing to decode from last position. + + requestedSeekTimeUs = -1; + + LOGI("requesting linear read"); + } else { + if (r < 0.55) { + // 5% chance of seeking beyond end of stream. + + requestedSeekTimeUs = durationUs; + + LOGI("requesting seek beyond EOF"); + } else { + requestedSeekTimeUs = + (int64_t)(uniform_rand() * durationUs); + + LOGI("requesting seek to %lld us (%.2f secs)", + requestedSeekTimeUs, requestedSeekTimeUs / 1E6); + } + + MediaBuffer *buffer; + options.setSeekTo(requestedSeekTimeUs); + if (seekSource->read(&buffer, &options) != OK) { + CHECK_EQ(buffer, NULL); + actualSeekTimeUs = -1; + } else { + CHECK(buffer != NULL); + CHECK(buffer->meta_data()->findInt64(kKeyTime, &actualSeekTimeUs)); + CHECK(actualSeekTimeUs >= 0); + + buffer->release(); + buffer = NULL; + } + + LOGI("nearest keyframe is at %lld us (%.2f secs)", + actualSeekTimeUs, actualSeekTimeUs / 1E6); + } + + status_t err; + MediaBuffer *buffer; + for (;;) { + err = codec->read(&buffer, &options); + options.clearSeekTo(); + if (err == INFO_FORMAT_CHANGED) { + CHECK_EQ(buffer, NULL); + continue; + } + if (err == OK) { + CHECK(buffer != NULL); + if (buffer->range_length() == 0) { + buffer->release(); + buffer = NULL; + continue; + } + } else { + CHECK_EQ(buffer, NULL); + } + + break; + } + + if (requestedSeekTimeUs < 0) { + // Linear read. + if (err != OK) { + CHECK_EQ(buffer, NULL); + } else { + CHECK(buffer != NULL); + buffer->release(); + buffer = NULL; + } + } else if (actualSeekTimeUs < 0) { + CHECK(err != OK); + CHECK_EQ(buffer, NULL); + } else { + EXPECT(err == OK, + "Expected a valid buffer to be returned from " + "OMXCodec::read."); + CHECK(buffer != NULL); + + int64_t bufferTimeUs; + CHECK(buffer->meta_data()->findInt64(kKeyTime, &bufferTimeUs)); + if (!CloseEnough(bufferTimeUs, actualSeekTimeUs)) { + printf("\n * Attempted seeking to %lld us (%.2f secs)", + requestedSeekTimeUs, requestedSeekTimeUs / 1E6); + printf("\n * Nearest keyframe is at %lld us (%.2f secs)", + actualSeekTimeUs, actualSeekTimeUs / 1E6); + printf("\n * Returned buffer was at %lld us (%.2f secs)\n\n", + bufferTimeUs, bufferTimeUs / 1E6); + + buffer->release(); + buffer = NULL; + + CHECK_EQ(codec->stop(), OK); + + return UNKNOWN_ERROR; + } + + buffer->release(); + buffer = NULL; + } + } + + CHECK_EQ(codec->stop(), OK); + + return OK; +} + +status_t Harness::test( + const char *componentName, const char *componentRole) { + printf("testing %s [%s] ... ", componentName, componentRole); + LOGI("testing %s [%s].", componentName, componentRole); + + status_t err1 = testStateTransitions(componentName, componentRole); + status_t err2 = testSeek(componentName, componentRole); + + if (err1 != OK) { + return err1; + } + + return err2; +} + +status_t Harness::testAll() { + List<IOMX::ComponentInfo> componentInfos; + status_t err = mOMX->listNodes(&componentInfos); + EXPECT_SUCCESS(err, "listNodes"); + + for (List<IOMX::ComponentInfo>::iterator it = componentInfos.begin(); + it != componentInfos.end(); ++it) { + const IOMX::ComponentInfo &info = *it; + const char *componentName = info.mName.string(); + + for (List<String8>::const_iterator role_it = info.mRoles.begin(); + role_it != info.mRoles.end(); ++role_it) { + const char *componentRole = (*role_it).string(); + + err = test(componentName, componentRole); + + if (err == OK) { + printf("OK\n"); + } + } + } + + return OK; +} + +} // namespace android + +static void usage(const char *me) { + fprintf(stderr, "usage: %s\n" + " -h(elp) Show this information\n" + " -s(eed) Set the random seed\n" + " [ component role ]\n\n" + "When launched without specifying a specific component " + "and role, tool will test all available OMX components " + "in all their supported roles. To determine available " + "component names, use \"stagefright -l\"\n" + "It's also a good idea to run a separate \"adb logcat\"" + " for additional debug and progress information.", me); + + exit(0); +} + +int main(int argc, char **argv) { + using namespace android; + + android::ProcessState::self()->startThreadPool(); + DataSource::RegisterDefaultSniffers(); + + const char *me = argv[0]; + + unsigned long seed = 0xdeadbeef; + + int res; + while ((res = getopt(argc, argv, "hs:")) >= 0) { + switch (res) { + case 's': + { + char *end; + unsigned long x = strtoul(optarg, &end, 10); + + if (*end != '\0' || end == optarg) { + fprintf(stderr, "Malformed seed.\n"); + return 1; + } + + seed = x; + break; + } + + case '?': + fprintf(stderr, "\n"); + // fall through + + case 'h': + default: + { + usage(me); + exit(1); + break; + } + } + } + + argc -= optind; + argv += optind; + + printf("To reproduce the conditions for this test, launch " + "with \"%s -s %lu\"\n", me, seed); + + srand(seed); + + sp<Harness> h = new Harness; + CHECK_EQ(h->initCheck(), OK); + + if (argc == 0) { + h->testAll(); + } else if (argc == 2) { + if (h->test(argv[0], argv[1]) == OK) { + printf("OK\n"); + } + } + + return 0; +} diff --git a/media/libstagefright/omx/tests/OMXHarness.h b/media/libstagefright/omx/tests/OMXHarness.h new file mode 100644 index 0000000..bb8fd0c --- /dev/null +++ b/media/libstagefright/omx/tests/OMXHarness.h @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OMX_HARNESS_H_ + +#define OMX_HARNESS_H_ + +#include <media/IOMX.h> +#include <utils/List.h> +#include <utils/Vector.h> +#include <utils/threads.h> + +#include <OMX_Component.h> + +namespace android { + +class MemoryDealer; + +struct Harness : public BnOMXObserver { + enum BufferFlags { + kBufferBusy = 1 + }; + struct Buffer { + IOMX::buffer_id mID; + sp<IMemory> mMemory; + uint32_t mFlags; + }; + + Harness(); + + status_t initCheck() const; + + status_t dequeueMessageForNode( + IOMX::node_id node, omx_message *msg, int64_t timeoutUs = -1); + + status_t dequeueMessageForNodeIgnoringBuffers( + IOMX::node_id node, + Vector<Buffer> *inputBuffers, + Vector<Buffer> *outputBuffers, + omx_message *msg, int64_t timeoutUs = -1); + + status_t getPortDefinition( + IOMX::node_id node, OMX_U32 portIndex, + OMX_PARAM_PORTDEFINITIONTYPE *def); + + status_t allocatePortBuffers( + const sp<MemoryDealer> &dealer, + IOMX::node_id node, OMX_U32 portIndex, + Vector<Buffer> *buffers); + + status_t setRole(IOMX::node_id node, const char *role); + + status_t testStateTransitions( + const char *componentName, const char *componentRole); + + status_t testSeek( + const char *componentName, const char *componentRole); + + status_t test( + const char *componentName, const char *componentRole); + + status_t testAll(); + + virtual void onMessage(const omx_message &msg); + +protected: + virtual ~Harness(); + +private: + friend struct NodeReaper; + + Mutex mLock; + + status_t mInitCheck; + sp<IOMX> mOMX; + List<omx_message> mMessageQueue; + Condition mMessageAddedCondition; + + status_t initOMX(); + + bool handleBufferMessage( + const omx_message &msg, + Vector<Buffer> *inputBuffers, + Vector<Buffer> *outputBuffers); + + Harness(const Harness &); + Harness &operator=(const Harness &); +}; + +} // namespace android + +#endif // OMX_HARNESS_H_ |