From d0c13c6363bbb601d3bae78a30e97eab3ec2164e Mon Sep 17 00:00:00 2001 From: Alfredo Yang Date: Tue, 1 Dec 2015 06:12:00 +0100 Subject: [PATCH] Bug 1224887 - new PlatformDecoderModule based on OpenMax IL. r=jya, sotaro --- dom/media/platforms/moz.build | 5 +- .../platforms/omx/GonkOmxPlatformLayer.cpp | 353 +++++++ .../platforms/omx/GonkOmxPlatformLayer.h | 131 +++ dom/media/platforms/omx/OmxDataDecoder.cpp | 861 ++++++++++++++++++ dom/media/platforms/omx/OmxDataDecoder.h | 204 +++++ dom/media/platforms/omx/OmxDecoderModule.cpp | 49 + dom/media/platforms/omx/OmxDecoderModule.h | 37 + dom/media/platforms/omx/OmxPlatformLayer.h | 67 ++ dom/media/platforms/omx/OmxPromiseLayer.cpp | 335 +++++++ dom/media/platforms/omx/OmxPromiseLayer.h | 221 +++++ dom/media/platforms/omx/moz.build | 46 + 11 files changed, 2308 insertions(+), 1 deletion(-) create mode 100644 dom/media/platforms/omx/GonkOmxPlatformLayer.cpp create mode 100644 dom/media/platforms/omx/GonkOmxPlatformLayer.h create mode 100644 dom/media/platforms/omx/OmxDataDecoder.cpp create mode 100644 dom/media/platforms/omx/OmxDataDecoder.h create mode 100644 dom/media/platforms/omx/OmxDecoderModule.cpp create mode 100644 dom/media/platforms/omx/OmxDecoderModule.h create mode 100644 dom/media/platforms/omx/OmxPlatformLayer.h create mode 100644 dom/media/platforms/omx/OmxPromiseLayer.cpp create mode 100644 dom/media/platforms/omx/OmxPromiseLayer.h create mode 100644 dom/media/platforms/omx/moz.build diff --git a/dom/media/platforms/moz.build b/dom/media/platforms/moz.build index 58f24494fd04..12d3a8643a62 100644 --- a/dom/media/platforms/moz.build +++ b/dom/media/platforms/moz.build @@ -27,7 +27,10 @@ UNIFIED_SOURCES += [ 'wrappers/H264Converter.cpp' ] -DIRS += ['agnostic/gmp'] +DIRS += [ + 'agnostic/gmp', + 'omx' +] if CONFIG['MOZ_WMF']: DIRS += [ 'wmf' ]; diff --git a/dom/media/platforms/omx/GonkOmxPlatformLayer.cpp b/dom/media/platforms/omx/GonkOmxPlatformLayer.cpp new file mode 100644 index 000000000000..c6b9e43d0a93 --- /dev/null +++ b/dom/media/platforms/omx/GonkOmxPlatformLayer.cpp @@ -0,0 +1,353 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "OmxDataDecoder.h" +#include "OmxPromiseLayer.h" +#include "GonkOmxPlatformLayer.h" +#include "MediaInfo.h" +#include +#include +#include +#include + +extern mozilla::LogModule* GetPDMLog(); + +#ifdef LOG +#undef LOG +#endif + +#define LOG(arg, ...) MOZ_LOG(GetPDMLog(), mozilla::LogLevel::Debug, ("GonkOmxPlatformLayer:: " arg, ##__VA_ARGS__)) + +using namespace android; + +namespace mozilla { + +extern void GetPortIndex(nsTArray& aPortIndex); + +bool IsSoftwareCodec(const char* aComponentName) { + nsAutoCString str(aComponentName); + return (str.Find(NS_LITERAL_CSTRING("OMX.google.")) == -1 ? false : true); +} + +class GonkOmxObserver : public BnOMXObserver { +public: + void onMessage(const omx_message& aMsg) + { + switch (aMsg.type) { + case omx_message::EVENT: + { + sp self = this; + nsCOMPtr r = NS_NewRunnableFunction([self, aMsg] () { + if (self->mClient && self->mClient->Event(aMsg.u.event_data.event, + aMsg.u.event_data.data1, + aMsg.u.event_data.data2)) + { + return; + } + }); + mTaskQueue->Dispatch(r.forget()); + break; + } + case omx_message::EMPTY_BUFFER_DONE: + { + sp self = this; + nsCOMPtr r = NS_NewRunnableFunction([self, aMsg] () { + if (!self->mPromiseLayer) { + return; + } + BufferData::BufferID id = (BufferData::BufferID)aMsg.u.buffer_data.buffer; + self->mPromiseLayer->EmptyFillBufferDone(OMX_DirInput, id); + }); + mTaskQueue->Dispatch(r.forget()); + break; + } + case omx_message::FILL_BUFFER_DONE: + { + sp self = this; + nsCOMPtr r = NS_NewRunnableFunction([self, aMsg] () { + if (!self->mPromiseLayer) { + return; + } + + // TODO: these codes look a little ugly, it'd be better to improve them. + RefPtr buf; + BufferData::BufferID id = (BufferData::BufferID)aMsg.u.extended_buffer_data.buffer; + buf = self->mPromiseLayer->FindAndRemoveBufferHolder(OMX_DirOutput, id); + MOZ_RELEASE_ASSERT(buf); + GonkBufferData* gonkBuffer = static_cast(buf.get()); + + // Copy the critical information to local buffer. + if (gonkBuffer->IsLocalBuffer()) { + gonkBuffer->mBuffer->nOffset = aMsg.u.extended_buffer_data.range_offset; + gonkBuffer->mBuffer->nFilledLen = aMsg.u.extended_buffer_data.range_length; + gonkBuffer->mBuffer->nFlags = aMsg.u.extended_buffer_data.flags; + gonkBuffer->mBuffer->nTimeStamp = aMsg.u.extended_buffer_data.timestamp; + } + self->mPromiseLayer->EmptyFillBufferDone(OMX_DirOutput, buf); + }); + mTaskQueue->Dispatch(r.forget()); + break; + } + default: + { + LOG("Unhandle event %d", aMsg.type); + } + } + } + + void Shutdown() + { + MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); + mPromiseLayer = nullptr; + mClient = nullptr; + } + + GonkOmxObserver(TaskQueue* aTaskQueue, OmxPromiseLayer* aPromiseLayer, OmxDataDecoder* aDataDecoder) + : mTaskQueue(aTaskQueue) + , mPromiseLayer(aPromiseLayer) + , mClient(aDataDecoder) + {} + +protected: + RefPtr mTaskQueue; + // TODO: + // we should combination both event handlers into one. And we should provide + // an unified way for event handling in OmxPlatforLayer class. + RefPtr mPromiseLayer; + RefPtr mClient; +}; + +GonkBufferData::GonkBufferData(android::IOMX::buffer_id aId, bool aLiveInLocal, android::IMemory* aMemory) + : BufferData((OMX_BUFFERHEADERTYPE*)aId) + , mId(aId) +{ + if (!aLiveInLocal) { + mLocalBuffer = new OMX_BUFFERHEADERTYPE; + PodZero(mLocalBuffer.get()); + // aMemory is a IPC memory, it is safe to use it here. + mLocalBuffer->pBuffer = (OMX_U8*)aMemory->pointer(); + mBuffer = mLocalBuffer.get(); + } +} + +GonkOmxPlatformLayer::GonkOmxPlatformLayer(OmxDataDecoder* aDataDecoder, + OmxPromiseLayer* aPromiseLayer, + TaskQueue* aTaskQueue) + : mTaskQueue(aTaskQueue) + , mNode(0) + , mQuirks(0) + , mUsingHardwareCodec(false) +{ + mOmxObserver = new GonkOmxObserver(mTaskQueue, aPromiseLayer, aDataDecoder); +} + +nsresult +GonkOmxPlatformLayer::AllocateOmxBuffer(OMX_DIRTYPE aType, + BUFFERLIST* aBufferList) +{ + MOZ_ASSERT(!mMemoryDealer[aType].get()); + + // Get port definition. + OMX_PARAM_PORTDEFINITIONTYPE def; + nsTArray portindex; + GetPortIndex(portindex); + for (auto idx : portindex) { + InitOmxParameter(&def); + def.nPortIndex = idx; + + OMX_ERRORTYPE err = GetParameter(OMX_IndexParamPortDefinition, + &def, + sizeof(OMX_PARAM_PORTDEFINITIONTYPE)); + if (err != OMX_ErrorNone) { + return NS_ERROR_FAILURE; + } else if (def.eDir == aType) { + LOG("Get OMX_IndexParamPortDefinition: port: %d, type: %d", def.nPortIndex, def.eDir); + break; + } + } + + size_t t = def.nBufferCountActual * def.nBufferSize; + LOG("Buffer count %d, buffer size %d", def.nBufferCountActual, def.nBufferSize); + + bool liveinlocal = mOmx->livesLocally(mNode, getpid()); + + // MemoryDealer is a IPC buffer allocator in Gonk because IOMX is actually + // lives in mediaserver. + mMemoryDealer[aType] = new MemoryDealer(t, "Gecko-OMX"); + for (OMX_U32 i = 0; i < def.nBufferCountActual; ++i) { + sp mem = mMemoryDealer[aType]->allocate(def.nBufferSize); + MOZ_ASSERT(mem.get()); + + IOMX::buffer_id bufferID; + status_t st; + + if ((mQuirks & OMXCodec::kRequiresAllocateBufferOnInputPorts && aType == OMX_DirInput) || + (mQuirks & OMXCodec::kRequiresAllocateBufferOnOutputPorts && aType == OMX_DirOutput)) { + st = mOmx->allocateBufferWithBackup(mNode, aType, mem, &bufferID); + } else { + st = mOmx->useBuffer(mNode, aType, mem, &bufferID); + } + + if (st != OK) { + return NS_ERROR_FAILURE; + } + + aBufferList->AppendElement(new GonkBufferData(bufferID, liveinlocal, mem.get())); + } + + return NS_OK; +} + +nsresult +GonkOmxPlatformLayer::ReleaseOmxBuffer(OMX_DIRTYPE aType, + BUFFERLIST* aBufferList) +{ + status_t st; + for (uint32_t i = 0; i < aBufferList->Length(); i++) { + IOMX::buffer_id id = (OMX_BUFFERHEADERTYPE*) aBufferList->ElementAt(i)->ID(); + st = mOmx->freeBuffer(mNode, aType, id); + if (st != OK) { + return NS_ERROR_FAILURE; + } + } + aBufferList->Clear(); + mMemoryDealer[aType].clear(); + + return NS_OK; +} + +OMX_ERRORTYPE +GonkOmxPlatformLayer::GetState(OMX_STATETYPE* aType) +{ + return (OMX_ERRORTYPE)mOmx->getState(mNode, aType); +} + +OMX_ERRORTYPE +GonkOmxPlatformLayer::GetParameter(OMX_INDEXTYPE aParamIndex, + OMX_PTR aComponentParameterStructure, + OMX_U32 aComponentParameterSize) +{ + return (OMX_ERRORTYPE)mOmx->getParameter(mNode, + aParamIndex, + aComponentParameterStructure, + aComponentParameterSize); +} + +OMX_ERRORTYPE +GonkOmxPlatformLayer::SetParameter(OMX_INDEXTYPE aParamIndex, + OMX_PTR aComponentParameterStructure, + OMX_U32 aComponentParameterSize) +{ + return (OMX_ERRORTYPE)mOmx->setParameter(mNode, + aParamIndex, + aComponentParameterStructure, + aComponentParameterSize); +} + +nsresult +GonkOmxPlatformLayer::Shutdown() +{ + mOmx->freeNode(mNode); + mOmxObserver->Shutdown(); + mOmxObserver = nullptr; + mOmxClient.disconnect(); + + return NS_OK; +} + +OMX_ERRORTYPE +GonkOmxPlatformLayer::InitOmxToStateLoaded(const TrackInfo* aInfo) +{ + status_t err = mOmxClient.connect(); + if (err != OK) { + return OMX_ErrorUndefined; + } + mOmx = mOmxClient.interface(); + if (!mOmx.get()) { + return OMX_ErrorUndefined; + } + + // In Gonk, the software compoment name has prefix "OMX.google". It needs to + // have a way to use hardware codec first. + android::Vector matchingCodecs; + const char* swcomponent = nullptr; + OMXCodec::findMatchingCodecs(aInfo->mMimeType.Data(), + 0, + nullptr, + 0, + &matchingCodecs); + for (uint32_t i = 0; i < matchingCodecs.size(); i++) { + const char* componentName = matchingCodecs.itemAt(i).mName.string(); + if (IsSoftwareCodec(componentName)) { + swcomponent = componentName; + } else { + // Try to use hardware codec first. + if (LoadComponent(componentName)) { + mUsingHardwareCodec = true; + return OMX_ErrorNone; + } + } + } + + // TODO: in android ICS, the software codec is allocated in mediaserver by + // default, it may be necessay to allocate it in local process. + // + // fallback to sw codec + if (LoadComponent(swcomponent)) { + return OMX_ErrorNone; + } + + LOG("no component is loaded"); + return OMX_ErrorUndefined; +} + +OMX_ERRORTYPE +GonkOmxPlatformLayer::EmptyThisBuffer(BufferData* aData) +{ + return (OMX_ERRORTYPE)mOmx->emptyBuffer(mNode, + (IOMX::buffer_id)aData->ID(), + aData->mBuffer->nOffset, + aData->mBuffer->nFilledLen, + aData->mBuffer->nFlags, + aData->mBuffer->nTimeStamp); +} + +OMX_ERRORTYPE +GonkOmxPlatformLayer::FillThisBuffer(BufferData* aData) +{ + return (OMX_ERRORTYPE)mOmx->fillBuffer(mNode, (IOMX::buffer_id)aData->mBuffer); +} + +OMX_ERRORTYPE +GonkOmxPlatformLayer::SendCommand(OMX_COMMANDTYPE aCmd, + OMX_U32 aParam1, + OMX_PTR aCmdData) +{ + return (OMX_ERRORTYPE)mOmx->sendCommand(mNode, aCmd, aParam1); +} + +bool +GonkOmxPlatformLayer::LoadComponent(const char* aName) +{ + status_t err = mOmx->allocateNode(aName, mOmxObserver, &mNode); + if (err == OK) { + OMXCodec::findCodecQuirks(aName, &mQuirks); + LOG("Load OpenMax component %s, quirks %x, live locally %d", + aName, mQuirks, mOmx->livesLocally(mNode, getpid())); + return true; + } + return false; +} + +template void +GonkOmxPlatformLayer::InitOmxParameter(T* aParam) +{ + PodZero(aParam); + aParam->nSize = sizeof(T); + aParam->nVersion.s.nVersionMajor = 1; +} + +} // mozilla diff --git a/dom/media/platforms/omx/GonkOmxPlatformLayer.h b/dom/media/platforms/omx/GonkOmxPlatformLayer.h new file mode 100644 index 000000000000..c13a5c6a1942 --- /dev/null +++ b/dom/media/platforms/omx/GonkOmxPlatformLayer.h @@ -0,0 +1,131 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if !defined(GonkOmxPlatformLayer_h_) +#define GonkOmxPlatformLayer_h_ + +#pragma GCC visibility push(default) + +#include "OmxPlatformLayer.h" +#include "OMX_Component.h" +#include +#include + +namespace android { +class MemoryDealer; +class IMemory; +} + +namespace mozilla { + +class GonkOmxObserver; + +/* + * Due to Android's omx node could live in local process (client) or remote + * process (mediaserver). + * + * When it is in local process, the IOMX::buffer_id is OMX_BUFFERHEADERTYPE + * pointer actually, it is safe to use it directly. + * + * When it is in remote process, the OMX_BUFFERHEADERTYPE pointer is 'IN' the + * remote process. It can't be used in local process, so here it allocates a + * local OMX_BUFFERHEADERTYPE. + */ +class GonkBufferData : public OmxPromiseLayer::BufferData { +protected: + virtual ~GonkBufferData() {} + +public: + // aMemory is an IPC based memory which will be used as the pBuffer in + // mLocalBuffer. + GonkBufferData(android::IOMX::buffer_id aId, bool aLiveInLocal, android::IMemory* aMemory); + + BufferID ID() override + { + return mId; + } + + bool IsLocalBuffer() + { + return !!mLocalBuffer.get(); + } + + // Android OMX uses this id to pass the buffer between OMX component and + // client. + android::IOMX::buffer_id mId; + + // mLocalBuffer are used only when the omx node is in mediaserver. + // Due to IPC problem, the mId is the OMX_BUFFERHEADERTYPE address in mediaserver. + // It can't mapping to client process, so we need a local OMX_BUFFERHEADERTYPE + // here. + nsAutoPtr mLocalBuffer; +}; + +class GonkOmxPlatformLayer : public OmxPlatformLayer { +public: + GonkOmxPlatformLayer(OmxDataDecoder* aDataDecoder, + OmxPromiseLayer* aPromiseLayer, + TaskQueue* aTaskQueue); + + nsresult AllocateOmxBuffer(OMX_DIRTYPE aType, BUFFERLIST* aBufferList) override; + + nsresult ReleaseOmxBuffer(OMX_DIRTYPE aType, BUFFERLIST* aBufferList) override; + + OMX_ERRORTYPE GetState(OMX_STATETYPE* aType) override; + + OMX_ERRORTYPE GetParameter(OMX_INDEXTYPE aParamIndex, + OMX_PTR aComponentParameterStructure, + OMX_U32 aComponentParameterSize) override; + + OMX_ERRORTYPE SetParameter(OMX_INDEXTYPE nIndex, + OMX_PTR aComponentParameterStructure, + OMX_U32 aComponentParameterSize) override; + + OMX_ERRORTYPE InitOmxToStateLoaded(const TrackInfo* aInfo) override; + + OMX_ERRORTYPE EmptyThisBuffer(BufferData* aData) override; + + OMX_ERRORTYPE FillThisBuffer(BufferData* aData) override; + + OMX_ERRORTYPE SendCommand(OMX_COMMANDTYPE aCmd, + OMX_U32 aParam1, + OMX_PTR aCmdData) override; + + nsresult Shutdown() override; + + // TODO: + // There is another InitOmxParameter in OmxDataDecoder. They need to combinate + // to one function. + template void InitOmxParameter(T* aParam); + +protected: + bool LoadComponent(const char* aName); + + friend class GonkOmxObserver; + + RefPtr mTaskQueue; + + // OMX_DirInput is 0, OMX_DirOutput is 1. + android::sp mMemoryDealer[2]; + + android::sp mOmxObserver; + + android::sp mOmx; + + android::IOMX::node_id mNode; + + android::OMXClient mOmxClient; + + uint32_t mQuirks; + + bool mUsingHardwareCodec; +}; + +} + +#pragma GCC visibility pop + +#endif // GonkOmxPlatformLayer_h_ diff --git a/dom/media/platforms/omx/OmxDataDecoder.cpp b/dom/media/platforms/omx/OmxDataDecoder.cpp new file mode 100644 index 000000000000..2a9bee7c0557 --- /dev/null +++ b/dom/media/platforms/omx/OmxDataDecoder.cpp @@ -0,0 +1,861 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "OmxDataDecoder.h" +#include "OMX_Types.h" +#include "OMX_Component.h" +#include "OMX_Audio.h" + +extern mozilla::LogModule* GetPDMLog(); + +#ifdef LOG +#undef LOG +#endif + +#define LOG(arg, ...) MOZ_LOG(GetPDMLog(), mozilla::LogLevel::Debug, ("OmxDataDecoder::%s: " arg, __func__, ##__VA_ARGS__)) + +#define CHECK_OMX_ERR(err) \ + if (err != OMX_ErrorNone) { \ + NotifyError(err, __func__);\ + return; \ + } \ + + +namespace mozilla { + +static const char* +StateTypeToStr(OMX_STATETYPE aType) +{ + MOZ_ASSERT(aType == OMX_StateLoaded || + aType == OMX_StateIdle || + aType == OMX_StateExecuting || + aType == OMX_StatePause || + aType == OMX_StateWaitForResources || + aType == OMX_StateInvalid); + + switch (aType) { + case OMX_StateLoaded: + return "OMX_StateLoaded"; + case OMX_StateIdle: + return "OMX_StateIdle"; + case OMX_StateExecuting: + return "OMX_StateExecuting"; + case OMX_StatePause: + return "OMX_StatePause"; + case OMX_StateWaitForResources: + return "OMX_StateWaitForResources"; + case OMX_StateInvalid: + return "OMX_StateInvalid"; + default: + return "Unknown"; + } +} + +// There should be 2 ports and port number start from 0. +void GetPortIndex(nsTArray& aPortIndex) { + aPortIndex.AppendElement(0); + aPortIndex.AppendElement(1); +} + +OmxDataDecoder::OmxDataDecoder(const TrackInfo& aTrackInfo, + MediaDataDecoderCallback* aCallback) + : mMonitor("OmxDataDecoder") + , mOmxTaskQueue(CreateMediaDecodeTaskQueue()) + , mWatchManager(this, mOmxTaskQueue) + , mOmxState(OMX_STATETYPE::OMX_StateInvalid, "OmxDataDecoder::mOmxState") + , mTrackInfo(aTrackInfo.Clone()) + , mFlushing(false) + , mShutdown(false) + , mCheckingInputExhausted(false) + , mPortSettingsChanged(-1, "OmxDataDecoder::mPortSettingsChanged") + , mAudioCompactor(mAudioQueue) + , mCallback(aCallback) +{ + LOG("(%p)", this); + mOmxLayer = new OmxPromiseLayer(mOmxTaskQueue, this); + + nsCOMPtr r = + NS_NewRunnableMethod(this, &OmxDataDecoder::InitializationTask); + mOmxTaskQueue->Dispatch(r.forget()); +} + +OmxDataDecoder::~OmxDataDecoder() +{ + LOG("(%p)", this); + mWatchManager.Shutdown(); + mOmxTaskQueue->AwaitShutdownAndIdle(); +} + +void +OmxDataDecoder::InitializationTask() +{ + mWatchManager.Watch(mOmxState, &OmxDataDecoder::OmxStateRunner); + mWatchManager.Watch(mPortSettingsChanged, &OmxDataDecoder::PortSettingsChanged); +} + +void +OmxDataDecoder::EndOfStream() +{ + LOG("(%p)", this); + MOZ_ASSERT(mOmxTaskQueue->IsCurrentThreadIn()); + + RefPtr self = this; + nsCOMPtr r = + NS_NewRunnableFunction([self] () { + self->mCallback->DrainComplete(); + }); + mReaderTaskQueue->Dispatch(r.forget()); +} + +RefPtr +OmxDataDecoder::Init() +{ + LOG("(%p)", this); + mReaderTaskQueue = AbstractThread::GetCurrent()->AsTaskQueue(); + MOZ_ASSERT(mReaderTaskQueue); + + RefPtr p = mInitPromise.Ensure(__func__); + RefPtr self = this; + + // TODO: it needs to get permission from resource manager before allocating + // Omx component. + InvokeAsync(mOmxTaskQueue, mOmxLayer.get(), __func__, &OmxPromiseLayer::Init, + mOmxTaskQueue, mTrackInfo.get()) + ->Then(mReaderTaskQueue, __func__, + [self] () { + // Omx state should be OMX_StateIdle. + nsCOMPtr r = + NS_NewRunnableFunction([self] () { + self->mOmxState = self->mOmxLayer->GetState(); + MOZ_ASSERT(self->mOmxState != OMX_StateIdle); + }); + self->mOmxTaskQueue->Dispatch(r.forget()); + }, + [self] () { + self->RejectInitPromise(DecoderFailureReason::INIT_ERROR, __func__); + }); + + return p; +} + +nsresult +OmxDataDecoder::Input(MediaRawData* aSample) +{ + LOG("(%p) sample %p", this, aSample); + MOZ_ASSERT(mInitPromise.IsEmpty()); + + RefPtr self = this; + RefPtr sample = aSample; + + nsCOMPtr r = + NS_NewRunnableFunction([self, sample] () { + self->mMediaRawDatas.AppendElement(sample); + + // Start to fill/empty buffers. + if (self->mOmxState == OMX_StateIdle || + self->mOmxState == OMX_StateExecuting) { + self->FillAndEmptyBuffers(); + } + }); + mOmxTaskQueue->Dispatch(r.forget()); + + return NS_OK; +} + +nsresult +OmxDataDecoder::Flush() +{ + LOG("(%p)", this); + + mFlushing = true; + + nsCOMPtr r = + NS_NewRunnableMethod(this, &OmxDataDecoder::DoFlush); + mOmxTaskQueue->Dispatch(r.forget()); + + // According to the definition of Flush() in PDM: + // "the decoder must be ready to accept new input for decoding". + // So it needs to wait for the Omx to complete the flush command. + MonitorAutoLock lock(mMonitor); + while (mFlushing) { + lock.Wait(); + } + + return NS_OK; +} + +nsresult +OmxDataDecoder::Drain() +{ + LOG("(%p)", this); + + // TODO: For video decoding, it needs to copy the latest video frame to yuv + // and output to layer again, because all video buffers will be released + // later. + nsCOMPtr r = + NS_NewRunnableMethod(this, &OmxDataDecoder::SendEosBuffer); + mOmxTaskQueue->Dispatch(r.forget()); + + return NS_OK; +} + +nsresult +OmxDataDecoder::Shutdown() +{ + LOG("(%p)", this); + + mShutdown = true; + + nsCOMPtr r = + NS_NewRunnableMethod(this, &OmxDataDecoder::DoAsyncShutdown); + mOmxTaskQueue->Dispatch(r.forget()); + + return NS_OK; +} + +void +OmxDataDecoder::DoAsyncShutdown() +{ + LOG("(%p)", this); + MOZ_ASSERT(mOmxTaskQueue->IsCurrentThreadIn()); + MOZ_ASSERT(mFlushing); + + mWatchManager.Unwatch(mOmxState, &OmxDataDecoder::OmxStateRunner); + mWatchManager.Unwatch(mPortSettingsChanged, &OmxDataDecoder::PortSettingsChanged); + + // Do flush so all port can be returned to client. + RefPtr self = this; + mOmxLayer->SendCommand(OMX_CommandFlush, OMX_ALL, nullptr) + ->Then(mOmxTaskQueue, __func__, + [self] () -> RefPtr { + LOG("DoAsyncShutdown: flush complete, collecting buffers..."); + self->CollectBufferPromises(OMX_DirMax) + ->Then(self->mOmxTaskQueue, __func__, + [self] () { + LOG("DoAsyncShutdown: releasing all buffers."); + self->ReleaseBuffers(OMX_DirInput); + self->ReleaseBuffers(OMX_DirOutput); + }, + [self] () { + self->mOmxLayer->Shutdown(); + }); + + return self->mOmxLayer->SendCommand(OMX_CommandStateSet, OMX_StateIdle, nullptr); + }, + [self] () { + self->mOmxLayer->Shutdown(); + }) + ->CompletionPromise() + ->Then(mOmxTaskQueue, __func__, + [self] () -> RefPtr { + LOG("DoAsyncShutdown: OMX_StateIdle"); + return self->mOmxLayer->SendCommand(OMX_CommandStateSet, OMX_StateLoaded, nullptr); + }, + [self] () { + self->mOmxLayer->Shutdown(); + }) + ->CompletionPromise() + ->Then(mOmxTaskQueue, __func__, + [self] () { + LOG("DoAsyncShutdown: OMX_StateLoaded, it is safe to shutdown omx"); + self->mOmxLayer->Shutdown(); + }, + [self] () { + self->mOmxLayer->Shutdown(); + }); +} + +void +OmxDataDecoder::CheckIfInputExhausted() +{ + MOZ_ASSERT(mOmxTaskQueue->IsCurrentThreadIn()); + MOZ_ASSERT(!mCheckingInputExhausted); + + mCheckingInputExhausted = false; + + if (mMediaRawDatas.Length()) { + return; + } + + // When all input buffers are not in omx component, it means all samples have + // been fed into OMX component. + for (auto buf : mInPortBuffers) { + if (buf->mStatus == BufferData::BufferStatus::OMX_COMPONENT) { + return; + } + } + + // When all output buffers are held by component, it means client is waiting for output. + for (auto buf : mOutPortBuffers) { + if (buf->mStatus != BufferData::BufferStatus::OMX_COMPONENT) { + return; + } + } + + LOG("Call InputExhausted()"); + mCallback->InputExhausted(); +} + +void +OmxDataDecoder::OutputAudio(BufferData* aBufferData) +{ + MOZ_ASSERT(mOmxTaskQueue->IsCurrentThreadIn()); + OMX_BUFFERHEADERTYPE* buf = aBufferData->mBuffer; + AudioInfo* info = mTrackInfo->GetAsAudioInfo(); + if (buf->nFilledLen) { + uint64_t offset = 0; + uint32_t frames = buf->nFilledLen / (2 * info->mChannels); + if (aBufferData->mRawData) { + offset = aBufferData->mRawData->mOffset; + } + typedef AudioCompactor::NativeCopy OmxCopy; + mAudioCompactor.Push(offset, + buf->nTimeStamp, + info->mRate, + frames, + info->mChannels, + OmxCopy(buf->pBuffer + buf->nOffset, + buf->nFilledLen, + info->mChannels)); + RefPtr audio = mAudioQueue.PopFront(); + mCallback->Output(audio); + } + aBufferData->mStatus = BufferData::BufferStatus::FREE; +} + +void +OmxDataDecoder::FillBufferDone(BufferData* aData) +{ + MOZ_ASSERT(!aData || aData->mStatus == BufferData::BufferStatus::OMX_CLIENT); + + if (mTrackInfo->IsAudio()) { + OutputAudio(aData); + } else { + MOZ_ASSERT(0); + } + + if (aData->mBuffer->nFlags & OMX_BUFFERFLAG_EOS) { + EndOfStream(); + } else { + FillAndEmptyBuffers(); + + // If the latest decoded sample's MediaRawData is also the latest input + // sample, it means there is no input data in queue and component, calling + // CheckIfInputExhausted(). + if (aData->mRawData == mLatestInputRawData && !mCheckingInputExhausted) { + mCheckingInputExhausted = true; + nsCOMPtr r = + NS_NewRunnableMethod(this, &OmxDataDecoder::CheckIfInputExhausted); + mOmxTaskQueue->Dispatch(r.forget()); + } + } +} + +void +OmxDataDecoder::FillBufferFailure(OmxBufferFailureHolder aFailureHolder) +{ + NotifyError(aFailureHolder.mError, __func__); +} + +void +OmxDataDecoder::EmptyBufferDone(BufferData* aData) +{ + MOZ_ASSERT(!aData || aData->mStatus == BufferData::BufferStatus::OMX_CLIENT); + + // Nothing to do when status of input buffer is OMX_CLIENT. + aData->mStatus = BufferData::BufferStatus::FREE; + FillAndEmptyBuffers(); +} + +void +OmxDataDecoder::EmptyBufferFailure(OmxBufferFailureHolder aFailureHolder) +{ + NotifyError(aFailureHolder.mError, __func__); +} + +void +OmxDataDecoder::NotifyError(OMX_ERRORTYPE aError, const char* aLine) +{ + LOG("NotifyError %d at %s", aError, aLine); + mCallback->Error(); +} + +void +OmxDataDecoder::FillAndEmptyBuffers() +{ + MOZ_ASSERT(mOmxTaskQueue->IsCurrentThreadIn()); + MOZ_ASSERT(mOmxState == OMX_StateExecuting); + + // During the port setting changed, it is forbided to do any buffer operations. + if (mPortSettingsChanged != -1 || mShutdown) { + return; + } + + if (mFlushing) { + return; + } + + // Trigger input port. + while (!!mMediaRawDatas.Length()) { + // input buffer must be usedi by component if there is data available. + RefPtr inbuf = FindAvailableBuffer(OMX_DirInput); + if (!inbuf) { + LOG("no input buffer!"); + break; + } + + RefPtr data = mMediaRawDatas[0]; + memcpy(inbuf->mBuffer->pBuffer, data->Data(), data->Size()); + inbuf->mBuffer->nFilledLen = data->Size(); + inbuf->mBuffer->nOffset = 0; + // TODO: the frame size could larger than buffer size in video case. + inbuf->mBuffer->nFlags = inbuf->mBuffer->nAllocLen > data->Size() ? + OMX_BUFFERFLAG_ENDOFFRAME : 0; + inbuf->mBuffer->nTimeStamp = data->mTime; + if (data->Size()) { + inbuf->mRawData = mMediaRawDatas[0]; + } else { + LOG("send EOS buffer"); + inbuf->mBuffer->nFlags |= OMX_BUFFERFLAG_EOS; + } + + LOG("feed sample %p to omx component, len %d, flag %X", data.get(), + inbuf->mBuffer->nFilledLen, inbuf->mBuffer->nFlags); + mOmxLayer->EmptyBuffer(inbuf)->Then(mOmxTaskQueue, __func__, this, + &OmxDataDecoder::EmptyBufferDone, + &OmxDataDecoder::EmptyBufferFailure); + mLatestInputRawData.swap(mMediaRawDatas[0]); + mMediaRawDatas.RemoveElementAt(0); + } + + // Trigger output port. + while (true) { + RefPtr outbuf = FindAvailableBuffer(OMX_DirOutput); + if (!outbuf) { + break; + } + + mOmxLayer->FillBuffer(outbuf)->Then(mOmxTaskQueue, __func__, this, + &OmxDataDecoder::FillBufferDone, + &OmxDataDecoder::FillBufferFailure); + } +} + +OmxPromiseLayer::BufferData* +OmxDataDecoder::FindAvailableBuffer(OMX_DIRTYPE aType) +{ + BUFFERLIST* buffers = GetBuffers(aType); + + for (uint32_t i = 0; i < buffers->Length(); i++) { + BufferData* buf = buffers->ElementAt(i); + if (buf->mStatus == BufferData::BufferStatus::FREE) { + return buf; + } + } + + return nullptr; +} + +nsresult +OmxDataDecoder::AllocateBuffers(OMX_DIRTYPE aType) +{ + MOZ_ASSERT(mOmxTaskQueue->IsCurrentThreadIn()); + + return mOmxLayer->AllocateOmxBuffer(aType, GetBuffers(aType)); +} + +nsresult +OmxDataDecoder::ReleaseBuffers(OMX_DIRTYPE aType) +{ + MOZ_ASSERT(mOmxTaskQueue->IsCurrentThreadIn()); + + return mOmxLayer->ReleaseOmxBuffer(aType, GetBuffers(aType)); +} + +nsTArray>* +OmxDataDecoder::GetBuffers(OMX_DIRTYPE aType) +{ + MOZ_ASSERT(aType == OMX_DIRTYPE::OMX_DirInput || + aType == OMX_DIRTYPE::OMX_DirOutput); + + if (aType == OMX_DIRTYPE::OMX_DirInput) { + return &mInPortBuffers; + } + return &mOutPortBuffers; +} + +void +OmxDataDecoder::ResolveInitPromise(const char* aMethodName) +{ + LOG("Resolved InitPromise"); + RefPtr self = this; + nsCOMPtr r = + NS_NewRunnableFunction([self, aMethodName] () { + MOZ_ASSERT(self->mReaderTaskQueue->IsCurrentThreadIn()); + self->mInitPromise.ResolveIfExists(self->mTrackInfo->GetType(), aMethodName); + }); + mReaderTaskQueue->Dispatch(r.forget()); +} + +void +OmxDataDecoder::RejectInitPromise(DecoderFailureReason aReason, const char* aMethodName) +{ + RefPtr self = this; + nsCOMPtr r = + NS_NewRunnableFunction([self, aReason, aMethodName] () { + MOZ_ASSERT(self->mReaderTaskQueue->IsCurrentThreadIn()); + self->mInitPromise.RejectIfExists(aReason, aMethodName); + }); + mReaderTaskQueue->Dispatch(r.forget()); +} + +void +OmxDataDecoder::OmxStateRunner() +{ + MOZ_ASSERT(mOmxTaskQueue->IsCurrentThreadIn()); + LOG("OMX state: %s", StateTypeToStr(mOmxState)); + + // TODO: maybe it'd be better to use promise CompletionPromise() to replace + // this state machine. + if (mOmxState == OMX_StateLoaded) { + // Config codec parameters by minetype. + if (mTrackInfo->IsAudio()) { + ConfigAudioCodec(); + } + + // Send OpenMax state commane to OMX_StateIdle. + RefPtr self = this; + mOmxLayer->SendCommand(OMX_CommandStateSet, OMX_StateIdle, nullptr) + ->Then(mOmxTaskQueue, __func__, + [self] () { + // Current state should be OMX_StateIdle. + self->mOmxState = self->mOmxLayer->GetState(); + MOZ_ASSERT(self->mOmxState == OMX_StateIdle); + }, + [self] () { + self->RejectInitPromise(DecoderFailureReason::INIT_ERROR, __func__); + }); + + // Allocate input and output buffers. + OMX_DIRTYPE types[] = {OMX_DIRTYPE::OMX_DirInput, OMX_DIRTYPE::OMX_DirOutput}; + for(const auto id : types) { + if (NS_FAILED(AllocateBuffers(id))) { + LOG("Failed to allocate buffer on port %d", id); + RejectInitPromise(DecoderFailureReason::INIT_ERROR, __func__); + break; + } + } + } else if (mOmxState == OMX_StateIdle) { + RefPtr self = this; + mOmxLayer->SendCommand(OMX_CommandStateSet, OMX_StateExecuting, nullptr) + ->Then(mOmxTaskQueue, __func__, + [self] () { + self->mOmxState = self->mOmxLayer->GetState(); + MOZ_ASSERT(self->mOmxState == OMX_StateExecuting); + + self->ResolveInitPromise(__func__); + }, + [self] () { + self->RejectInitPromise(DecoderFailureReason::INIT_ERROR, __func__); + }); + } else if (mOmxState == OMX_StateExecuting) { + // Config codec once it gets OMX_StateExecuting state. + FillCodecConfigDataToOmx(); + } else { + MOZ_ASSERT(0); + } +} + +void +OmxDataDecoder::ConfigAudioCodec() +{ + const AudioInfo* audioInfo = mTrackInfo->GetAsAudioInfo(); + OMX_ERRORTYPE err; + + // TODO: it needs to handle other formats like mp3, amr-nb...etc. + if (audioInfo->mMimeType.EqualsLiteral("audio/mp4a-latm")) { + OMX_AUDIO_PARAM_AACPROFILETYPE aac_profile; + InitOmxParameter(&aac_profile); + err = mOmxLayer->GetParameter(OMX_IndexParamAudioAac, &aac_profile, sizeof(aac_profile)); + CHECK_OMX_ERR(err); + aac_profile.nSampleRate = audioInfo->mRate; + aac_profile.nChannels = audioInfo->mChannels; + aac_profile.eAACProfile = (OMX_AUDIO_AACPROFILETYPE)audioInfo->mProfile; + err = mOmxLayer->SetParameter(OMX_IndexParamAudioAac, &aac_profile, sizeof(aac_profile)); + CHECK_OMX_ERR(err); + LOG("Config OMX_IndexParamAudioAac, channel %d, sample rate %d, profile %d", + audioInfo->mChannels, audioInfo->mRate, audioInfo->mProfile); + } +} + +void +OmxDataDecoder::FillCodecConfigDataToOmx() +{ + // Codec config data should be the first sample running on Omx TaskQueue. + MOZ_ASSERT(mOmxTaskQueue->IsCurrentThreadIn()); + MOZ_ASSERT(!mMediaRawDatas.Length()); + MOZ_ASSERT(mOmxState == OMX_StateIdle || mOmxState == OMX_StateExecuting); + + + RefPtr inbuf = FindAvailableBuffer(OMX_DirInput); + if (mTrackInfo->IsAudio()) { + AudioInfo* audio_info = mTrackInfo->GetAsAudioInfo(); + memcpy(inbuf->mBuffer->pBuffer, + audio_info->mCodecSpecificConfig->Elements(), + audio_info->mCodecSpecificConfig->Length()); + inbuf->mBuffer->nFilledLen = audio_info->mCodecSpecificConfig->Length(); + inbuf->mBuffer->nOffset = 0; + inbuf->mBuffer->nFlags = (OMX_BUFFERFLAG_ENDOFFRAME | OMX_BUFFERFLAG_CODECCONFIG); + } else { + MOZ_ASSERT(0); + } + + LOG("Feed codec configure data to OMX component"); + mOmxLayer->EmptyBuffer(inbuf)->Then(mOmxTaskQueue, __func__, this, + &OmxDataDecoder::EmptyBufferDone, + &OmxDataDecoder::EmptyBufferFailure); +} + +bool +OmxDataDecoder::Event(OMX_EVENTTYPE aEvent, OMX_U32 aData1, OMX_U32 aData2) +{ + MOZ_ASSERT(mOmxTaskQueue->IsCurrentThreadIn()); + + if (mOmxLayer->Event(aEvent, aData1, aData2)) { + return true; + } + + switch (aEvent) { + case OMX_EventPortSettingsChanged: + { + // According to spec: "To prevent the loss of any input data, the + // component issuing the OMX_EventPortSettingsChanged event on its input + // port should buffer all input port data that arrives between the + // emission of the OMX_EventPortSettingsChanged event and the arrival of + // the command to disable the input port." + // + // So client needs to disable port and reallocate buffers. + MOZ_ASSERT(mPortSettingsChanged == -1); + mPortSettingsChanged = aData1; + LOG("Got OMX_EventPortSettingsChanged event"); + break; + } + default: + { + LOG("WARNING: got none handle event: %d, aData1: %d, aData2: %d", + aEvent, aData1, aData2); + return false; + } + } + + return true; +} + +template void +OmxDataDecoder::InitOmxParameter(T* aParam) +{ + PodZero(aParam); + aParam->nSize = sizeof(T); + aParam->nVersion.s.nVersionMajor = 1; +} + +bool +OmxDataDecoder::BuffersCanBeReleased(OMX_DIRTYPE aType) +{ + BUFFERLIST* buffers = GetBuffers(aType); + uint32_t len = buffers->Length(); + for (uint32_t i = 0; i < len; i++) { + BufferData::BufferStatus buf_status = buffers->ElementAt(i)->mStatus; + if (buf_status == BufferData::BufferStatus::OMX_COMPONENT || + buf_status == BufferData::BufferStatus::OMX_CLIENT_OUTPUT) { + return false; + } + } + return true; +} + +OMX_DIRTYPE +OmxDataDecoder::GetPortDirection(uint32_t aPortIndex) +{ + OMX_PARAM_PORTDEFINITIONTYPE def; + InitOmxParameter(&def); + def.nPortIndex = mPortSettingsChanged; + + OMX_ERRORTYPE err = mOmxLayer->GetParameter(OMX_IndexParamPortDefinition, + &def, + sizeof(def)); + if (err != OMX_ErrorNone) { + return OMX_DirMax; + } + return def.eDir; +} + +RefPtr +OmxDataDecoder::CollectBufferPromises(OMX_DIRTYPE aType) +{ + MOZ_ASSERT(mOmxTaskQueue->IsCurrentThreadIn()); + + nsTArray> promises; + OMX_DIRTYPE types[] = {OMX_DIRTYPE::OMX_DirInput, OMX_DIRTYPE::OMX_DirOutput}; + for (const auto type : types) { + if ((aType == type) || (aType == OMX_DirMax)) { + // find the buffer which has promise. + BUFFERLIST* buffers = GetBuffers(type); + + for (uint32_t i = 0; i < buffers->Length(); i++) { + BufferData* buf = buffers->ElementAt(i); + if (!buf->mPromise.IsEmpty()) { + // OmxBufferPromise is not exclusive, it can be multiple "Then"s, so it + // is safe to call "Ensure" here. + promises.AppendElement(buf->mPromise.Ensure(__func__)); + } + } + } + } + + LOG("CollectBufferPromises: type %d, total %d promiese", aType, promises.Length()); + if (promises.Length()) { + return OmxBufferPromise::All(mOmxTaskQueue, promises); + } + + nsTArray headers; + return OmxBufferPromise::AllPromiseType::CreateAndResolve(headers, __func__); +} + +void +OmxDataDecoder::PortSettingsChanged() +{ + MOZ_ASSERT(mOmxTaskQueue->IsCurrentThreadIn()); + + if (mPortSettingsChanged == -1 || mOmxState == OMX_STATETYPE::OMX_StateInvalid) { + return; + } + + // The PortSettingsChanged algorithm: + // + // 1. disable port. + // 2. wait for port buffers return to client and then release these buffers. + // 3. enable port. + // 4. allocate port buffers. + // + + // Disable port. Get port definition if the target port is enable. + OMX_PARAM_PORTDEFINITIONTYPE def; + InitOmxParameter(&def); + def.nPortIndex = mPortSettingsChanged; + + OMX_ERRORTYPE err = mOmxLayer->GetParameter(OMX_IndexParamPortDefinition, + &def, + sizeof(def)); + CHECK_OMX_ERR(err); + + RefPtr self = this; + if (def.bEnabled) { + // 1. disable port. + LOG("PortSettingsChanged: disable port %d", def.nPortIndex); + mOmxLayer->SendCommand(OMX_CommandPortDisable, mPortSettingsChanged, nullptr) + ->Then(mOmxTaskQueue, __func__, + [self, def] () -> RefPtr { + // 3. enable port. + // Send enable port command. + RefPtr p = + self->mOmxLayer->SendCommand(OMX_CommandPortEnable, + self->mPortSettingsChanged, + nullptr); + + // 4. allocate port buffers. + // Allocate new port buffers. + nsresult rv = self->AllocateBuffers(def.eDir); + if (NS_FAILED(rv)) { + self->NotifyError(OMX_ErrorUndefined, __func__); + } + + return p; + }, + [self] () { + self->NotifyError(OMX_ErrorUndefined, __func__); + }) + ->CompletionPromise() + ->Then(mOmxTaskQueue, __func__, + [self] () { + LOG("PortSettingsChanged: port settings changed complete"); + // finish port setting changed. + self->mPortSettingsChanged = -1; + self->FillAndEmptyBuffers(); + }, + [self] () { + self->NotifyError(OMX_ErrorUndefined, __func__); + }); + + // 2. wait for port buffers return to client and then release these buffers. + // + // Port buffers will be returned to client soon once OMX_CommandPortDisable + // command is sent. Then releasing these buffers. + CollectBufferPromises(def.eDir) + ->Then(mOmxTaskQueue, __func__, + [self, def] () { + MOZ_ASSERT(self->BuffersCanBeReleased(def.eDir)); + nsresult rv = self->ReleaseBuffers(def.eDir); + if (NS_FAILED(rv)) { + MOZ_RELEASE_ASSERT(0); + self->NotifyError(OMX_ErrorUndefined, __func__); + } + }, + [self] () { + self->NotifyError(OMX_ErrorUndefined, __func__); + }); + } +} + +void +OmxDataDecoder::SendEosBuffer() +{ + MOZ_ASSERT(mOmxTaskQueue->IsCurrentThreadIn()); + + // There is no 'Drain' API in OpenMax, so it needs to wait for output sample + // with EOS flag. However, MediaRawData doesn't provide EOS information, + // so here it generates an empty BufferData with eos OMX_BUFFERFLAG_EOS in queue. + // This behaviour should be compliant with spec, I think... + RefPtr eos_data = new MediaRawData(); + mMediaRawDatas.AppendElement(eos_data); + FillAndEmptyBuffers(); +} + +void +OmxDataDecoder::DoFlush() +{ + MOZ_ASSERT(mOmxTaskQueue->IsCurrentThreadIn()); + + // 1. Call OMX command OMX_CommandFlush in Omx TaskQueue. + // 2. Remove all elements in mMediaRawDatas when flush is completed. + RefPtr self = this; + mOmxLayer->SendCommand(OMX_CommandFlush, OMX_ALL, nullptr) + ->Then(mOmxTaskQueue, __func__, this, + &OmxDataDecoder::FlushComplete, + &OmxDataDecoder::FlushFailure); +} + +void +OmxDataDecoder::FlushComplete(OMX_COMMANDTYPE aCommandType) +{ + mMediaRawDatas.Clear(); + mFlushing = false; + + MonitorAutoLock lock(mMonitor); + mMonitor.Notify(); + LOG("Flush complete"); +} + +void OmxDataDecoder::FlushFailure(OmxCommandFailureHolder aFailureHolder) +{ + NotifyError(OMX_ErrorUndefined, __func__); + mFlushing = false; + + MonitorAutoLock lock(mMonitor); + mMonitor.Notify(); +} + +} diff --git a/dom/media/platforms/omx/OmxDataDecoder.h b/dom/media/platforms/omx/OmxDataDecoder.h new file mode 100644 index 000000000000..d5fd5527f67c --- /dev/null +++ b/dom/media/platforms/omx/OmxDataDecoder.h @@ -0,0 +1,204 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if !defined(OmxDataDecoder_h_) +#define OmxDataDecoder_h_ + +#include "mozilla/Monitor.h" +#include "PlatformDecoderModule.h" +#include "OmxPromiseLayer.h" +#include "MediaInfo.h" +#include "AudioCompactor.h" + +namespace mozilla { + +typedef OmxPromiseLayer::OmxCommandPromise OmxCommandPromise; +typedef OmxPromiseLayer::OmxBufferPromise OmxBufferPromise; +typedef OmxPromiseLayer::OmxBufferFailureHolder OmxBufferFailureHolder; +typedef OmxPromiseLayer::OmxCommandFailureHolder OmxCommandFailureHolder; +typedef OmxPromiseLayer::BufferData BufferData; +typedef OmxPromiseLayer::BUFFERLIST BUFFERLIST; + +/* OmxDataDecoder is the major class which performs followings: + * 1. Translate PDM function into OMX commands. + * 2. Keeping the buffers between client and component. + * 3. Manage the OMX state. + * + * From the definiton in OpenMax spec. "2.2.1", there are 3 major roles in + * OpenMax IL. + * + * IL client: + * "The IL client may be a layer below the GUI application, such as GStreamer, + * or may be several layers below the GUI layer." + * + * OmxDataDecoder acts as the IL client. + * + * OpenMAX IL component: + * "A component that is intended to wrap functionality that is required in the + * target system." + * + * OmxPromiseLayer acts as the OpenMAX IL component. + * + * OpenMAX IL core: + * "Platform-specific code that has the functionality necessary to locate and + * then load an OpenMAX IL component into main memory." + * + * OmxPlatformLayer acts as the OpenMAX IL core. + */ +class OmxDataDecoder : public MediaDataDecoder { +protected: + virtual ~OmxDataDecoder(); + +public: + OmxDataDecoder(const TrackInfo& aTrackInfo, + MediaDataDecoderCallback* aCallback); + + RefPtr Init() override; + + nsresult Input(MediaRawData* aSample) override; + + nsresult Flush() override; + + nsresult Drain() override; + + nsresult Shutdown() override; + + // Return true if event is handled. + bool Event(OMX_EVENTTYPE aEvent, OMX_U32 aData1, OMX_U32 aData2); + +protected: + void InitializationTask(); + + void ResolveInitPromise(const char* aMethodName); + + void RejectInitPromise(DecoderFailureReason aReason, const char* aMethodName); + + void OmxStateRunner(); + + void FillAndEmptyBuffers(); + + void FillBufferDone(BufferData* aData); + + void FillBufferFailure(OmxBufferFailureHolder aFailureHolder); + + void EmptyBufferDone(BufferData* aData); + + void EmptyBufferFailure(OmxBufferFailureHolder aFailureHolder); + + void NotifyError(OMX_ERRORTYPE aError, const char* aLine); + + // Config audio codec. + // Some codec may just ignore this and rely on codec specific data in + // FillCodecConfigDataToOmx(). + void ConfigAudioCodec(); + + // Sending codec specific data to OMX component. OMX component could send a + // OMX_EventPortSettingsChanged back to client. And then client needs to + // disable port and reallocate buffer. + void FillCodecConfigDataToOmx(); + + void SendEosBuffer(); + + void EndOfStream(); + + // It could be called after codec specific data is sent and component found + // the port format is changed due to different codec specific. + void PortSettingsChanged(); + + void OutputAudio(BufferData* aBufferData); + + // Notify InputExhausted when: + // 1. all input buffers are not held by component. + // 2. all output buffers are waiting for filling complete. + void CheckIfInputExhausted(); + + // Buffer can be released if its status is not OMX_COMPONENT or + // OMX_CLIENT_OUTPUT. + bool BuffersCanBeReleased(OMX_DIRTYPE aType); + + OMX_DIRTYPE GetPortDirection(uint32_t aPortIndex); + + void DoAsyncShutdown(); + + void DoFlush(); + + void FlushComplete(OMX_COMMANDTYPE aCommandType); + + void FlushFailure(OmxCommandFailureHolder aFailureHolder); + + BUFFERLIST* GetBuffers(OMX_DIRTYPE aType); + + nsresult AllocateBuffers(OMX_DIRTYPE aType); + + nsresult ReleaseBuffers(OMX_DIRTYPE aType); + + BufferData* FindAvailableBuffer(OMX_DIRTYPE aType); + + template void InitOmxParameter(T* aParam); + + // aType could be OMX_DirMax for all types. + RefPtr + CollectBufferPromises(OMX_DIRTYPE aType); + + Monitor mMonitor; + + // The Omx TaskQueue. + RefPtr mOmxTaskQueue; + + RefPtr mReaderTaskQueue; + + WatchManager mWatchManager; + + // It is accessed in omx TaskQueue. + Watchable mOmxState; + + RefPtr mOmxLayer; + + UniquePtr mTrackInfo; + + // It is accessed in both omx and reader TaskQueue. + Atomic mFlushing; + + // It is accessed in Omx/reader TaskQeueu. + Atomic mShutdown; + + // It is accessed in Omx TaskQeueu. + bool mCheckingInputExhausted; + + // It is accessed in reader TaskQueue. + MozPromiseHolder mInitPromise; + + // It is written in Omx TaskQeueu. Read in Omx TaskQueue. + // It value means the port index which port settings is changed. + // -1 means no port setting changed. + // + // Note: when port setting changed, there should be no buffer operations + // via EmptyBuffer or FillBuffer. + Watchable mPortSettingsChanged; + + // It is access in Omx TaskQueue. + nsTArray> mMediaRawDatas; + + // It is access in Omx TaskQueue. The latest input MediaRawData. + RefPtr mLatestInputRawData; + + BUFFERLIST mInPortBuffers; + + BUFFERLIST mOutPortBuffers; + + // For audio output. + // TODO: because this class is for both video and audio decoding, so there + // should be some kind of abstract things to these members. + MediaQueue mAudioQueue; + + AudioCompactor mAudioCompactor; + + MediaDataDecoderCallback* mCallback; +}; + +} + +#endif /* OmxDataDecoder_h_ */ diff --git a/dom/media/platforms/omx/OmxDecoderModule.cpp b/dom/media/platforms/omx/OmxDecoderModule.cpp new file mode 100644 index 000000000000..a0456b763453 --- /dev/null +++ b/dom/media/platforms/omx/OmxDecoderModule.cpp @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "OmxDecoderModule.h" +#include "OmxDataDecoder.h" + +namespace mozilla { + +already_AddRefed +OmxDecoderModule::CreateVideoDecoder(const VideoInfo& aConfig, + mozilla::layers::LayersBackend aLayersBackend, + mozilla::layers::ImageContainer* aImageContainer, + FlushableTaskQueue* aVideoTaskQueue, + MediaDataDecoderCallback* aCallback) +{ + return nullptr; +} + +already_AddRefed +OmxDecoderModule::CreateAudioDecoder(const AudioInfo& aConfig, + FlushableTaskQueue* aAudioTaskQueue, + MediaDataDecoderCallback* aCallback) +{ + RefPtr decoder = new OmxDataDecoder(aConfig, aCallback); + return decoder.forget(); +} + +void +OmxDecoderModule::Init() +{ + MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread."); +} + +PlatformDecoderModule::ConversionRequired +OmxDecoderModule::DecoderNeedsConversion(const TrackInfo& aConfig) const +{ + return kNeedNone; +} + +bool +OmxDecoderModule::SupportsMimeType(const nsACString& aMimeType) const +{ + return aMimeType.EqualsLiteral("audio/mp4a-latm"); +} + +} diff --git a/dom/media/platforms/omx/OmxDecoderModule.h b/dom/media/platforms/omx/OmxDecoderModule.h new file mode 100644 index 000000000000..2a029865cd0b --- /dev/null +++ b/dom/media/platforms/omx/OmxDecoderModule.h @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if !defined(OmxDecoderModule_h_) +#define OmxDecoderModule_h_ + +#include "PlatformDecoderModule.h" + +namespace mozilla { + +class OmxDecoderModule : public PlatformDecoderModule { +public: + already_AddRefed + CreateVideoDecoder(const VideoInfo& aConfig, + mozilla::layers::LayersBackend aLayersBackend, + mozilla::layers::ImageContainer* aImageContainer, + FlushableTaskQueue* aVideoTaskQueue, + MediaDataDecoderCallback* aCallback) override; + + already_AddRefed + CreateAudioDecoder(const AudioInfo& aConfig, + FlushableTaskQueue* aAudioTaskQueue, + MediaDataDecoderCallback* aCallback) override; + + static void Init(); + + bool SupportsMimeType(const nsACString& aMimeType) const override; + + ConversionRequired DecoderNeedsConversion(const TrackInfo& aConfig) const override; +}; + +} // namespace mozilla + +#endif // OmxDecoderModule_h_ diff --git a/dom/media/platforms/omx/OmxPlatformLayer.h b/dom/media/platforms/omx/OmxPlatformLayer.h new file mode 100644 index 000000000000..cefdb245ee9a --- /dev/null +++ b/dom/media/platforms/omx/OmxPlatformLayer.h @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if !defined(OmxPlatformLayer_h_) +#define OmxPlatformLayer_h_ + +#include "OMX_Core.h" +#include "OMX_Types.h" +#include "mozilla/MozPromise.h" +#include "mozilla/TaskQueue.h" +#include "OmxPromiseLayer.h" + +namespace mozilla { + +class TrackInfo; + +/* + * This class the the abstract layer of the platform OpenMax IL implementation. + * + * For some platform like andoird, it exposures its OpenMax IL via IOMX which + * is definitions are different comparing to standard. + * For other platforms like Raspberry Pi, it will be easy to implement this layer + * with the standard OpenMax IL api. + */ +class OmxPlatformLayer { +public: + typedef OmxPromiseLayer::BUFFERLIST BUFFERLIST; + typedef OmxPromiseLayer::BufferData BufferData; + + virtual OMX_ERRORTYPE InitOmxToStateLoaded(const TrackInfo* aInfo) = 0; + + virtual OMX_ERRORTYPE EmptyThisBuffer(BufferData* aData) = 0; + + virtual OMX_ERRORTYPE FillThisBuffer(BufferData* aData) = 0; + + virtual OMX_ERRORTYPE SendCommand(OMX_COMMANDTYPE aCmd, + OMX_U32 aParam1, + OMX_PTR aCmdData) = 0; + + // Buffer could be platform dependent; for example, video decoding needs gralloc + // on Gonk. Therefore, derived class needs to implement its owned buffer + // allocate/release API according to its platform type. + virtual nsresult AllocateOmxBuffer(OMX_DIRTYPE aType, BUFFERLIST* aBufferList) = 0; + + virtual nsresult ReleaseOmxBuffer(OMX_DIRTYPE aType, BUFFERLIST* aBufferList) = 0; + + virtual OMX_ERRORTYPE GetState(OMX_STATETYPE* aType) = 0; + + virtual OMX_ERRORTYPE GetParameter(OMX_INDEXTYPE aParamIndex, + OMX_PTR aComponentParameterStructure, + OMX_U32 aComponentParameterSize) = 0; + + virtual OMX_ERRORTYPE SetParameter(OMX_INDEXTYPE nIndex, + OMX_PTR aComponentParameterStructure, + OMX_U32 aComponentParameterSize) = 0; + + virtual nsresult Shutdown() = 0; + + virtual ~OmxPlatformLayer() {} +}; + +} + +#endif // OmxPlatformLayer_h_ diff --git a/dom/media/platforms/omx/OmxPromiseLayer.cpp b/dom/media/platforms/omx/OmxPromiseLayer.cpp new file mode 100644 index 000000000000..9b55db9e88f5 --- /dev/null +++ b/dom/media/platforms/omx/OmxPromiseLayer.cpp @@ -0,0 +1,335 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "OmxPromiseLayer.h" +#include "OmxPlatformLayer.h" +#include "OmxDataDecoder.h" + +#ifdef MOZ_WIDGET_GONK +#include "GonkOmxPlatformLayer.h" +#endif + +extern mozilla::LogModule* GetPDMLog(); + +#ifdef LOG +#undef LOG +#endif + +#define LOG(arg, ...) MOZ_LOG(GetPDMLog(), mozilla::LogLevel::Debug, ("OmxPromiseLayer:: " arg, ##__VA_ARGS__)) + +namespace mozilla { + +OmxPromiseLayer::OmxPromiseLayer(TaskQueue* aTaskQueue, OmxDataDecoder* aDataDecoder) + : mTaskQueue(aTaskQueue) + , mFlushPortIndex(0) +{ +#ifdef MOZ_WIDGET_GONK + mPlatformLayer = new GonkOmxPlatformLayer(aDataDecoder, this, aTaskQueue); +#endif + MOZ_ASSERT(!!mPlatformLayer); +} + +RefPtr +OmxPromiseLayer::Init(TaskQueue* aTaskQueue, const TrackInfo* aInfo) +{ + mTaskQueue = aTaskQueue; + MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); + + OMX_ERRORTYPE err = mPlatformLayer->InitOmxToStateLoaded(aInfo); + if (err != OMX_ErrorNone) { + OmxCommandFailureHolder failure(OMX_ErrorUndefined, OMX_CommandStateSet); + return OmxCommandPromise::CreateAndReject(failure, __func__); + } + + OMX_STATETYPE state = GetState(); + if (state == OMX_StateLoaded) { + return OmxCommandPromise::CreateAndResolve(OMX_CommandStateSet, __func__); + } if (state == OMX_StateIdle) { + return SendCommand(OMX_CommandStateSet, OMX_StateIdle, nullptr); + } + + OmxCommandFailureHolder failure(OMX_ErrorUndefined, OMX_CommandStateSet); + return OmxCommandPromise::CreateAndReject(failure, __func__); +} + +RefPtr +OmxPromiseLayer::FillBuffer(BufferData* aData) +{ + MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); + LOG("FillBuffer: buffer %p", aData->mBuffer); + + RefPtr p = aData->mPromise.Ensure(__func__); + + OMX_ERRORTYPE err = mPlatformLayer->FillThisBuffer(aData); + + if (err != OMX_ErrorNone) { + OmxBufferFailureHolder failure(err, aData); + aData->mPromise.Reject(Move(failure), __func__); + } else { + aData->mStatus = BufferData::BufferStatus::OMX_COMPONENT; + GetBufferHolders(OMX_DirOutput)->AppendElement(aData); + } + + return p; +} + +RefPtr +OmxPromiseLayer::EmptyBuffer(BufferData* aData) +{ + MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); + LOG("EmptyBuffer: buffer %p, size %d", aData->mBuffer, aData->mBuffer->nFilledLen); + + RefPtr p = aData->mPromise.Ensure(__func__); + + OMX_ERRORTYPE err = mPlatformLayer->EmptyThisBuffer(aData); + + if (err != OMX_ErrorNone) { + OmxBufferFailureHolder failure(err, aData); + aData->mPromise.Reject(Move(failure), __func__); + } else { + if (aData->mRawData) { + mRawDatas.AppendElement(Move(aData->mRawData)); + } + aData->mStatus = BufferData::BufferStatus::OMX_COMPONENT; + GetBufferHolders(OMX_DirInput)->AppendElement(aData); + } + + return p; +} + +OmxPromiseLayer::BUFFERLIST* +OmxPromiseLayer::GetBufferHolders(OMX_DIRTYPE aType) +{ + MOZ_ASSERT(aType == OMX_DirInput || aType == OMX_DirOutput); + + if (aType == OMX_DirInput) { + return &mInbufferHolders; + } + + return &mOutbufferHolders; +} + +already_AddRefed +OmxPromiseLayer::FindAndRemoveRawData(OMX_TICKS aTimecode) +{ + for (auto raw : mRawDatas) { + if (raw->mTimecode == aTimecode) { + mRawDatas.RemoveElement(raw); + return raw.forget(); + } + } + return nullptr; +} + +already_AddRefed +OmxPromiseLayer::FindAndRemoveBufferHolder(OMX_DIRTYPE aType, + BufferData::BufferID aId) +{ + MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); + + RefPtr holder; + BUFFERLIST* holders = GetBufferHolders(aType); + + for (uint32_t i = 0; i < holders->Length(); i++) { + if (holders->ElementAt(i)->ID() == aId) { + holder = holders->ElementAt(i); + holders->RemoveElementAt(i); + return holder.forget(); + } + } + + return nullptr; +} + +already_AddRefed +OmxPromiseLayer::FindBufferById(OMX_DIRTYPE aType, BufferData::BufferID aId) +{ + MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); + + RefPtr holder; + BUFFERLIST* holders = GetBufferHolders(aType); + + for (uint32_t i = 0; i < holders->Length(); i++) { + if (holders->ElementAt(i)->ID() == aId) { + holder = holders->ElementAt(i); + return holder.forget(); + } + } + + return nullptr; +} + +void +OmxPromiseLayer::EmptyFillBufferDone(OMX_DIRTYPE aType, BufferData* aData) +{ + MOZ_ASSERT(!!aData); + LOG("EmptyFillBufferDone: type %d, buffer %p", aType, aData->mBuffer); + if (aData) { + if (aType == OMX_DirOutput) { + aData->mRawData = nullptr; + aData->mRawData = FindAndRemoveRawData(aData->mBuffer->nTimeStamp); + } + aData->mStatus = BufferData::BufferStatus::OMX_CLIENT; + aData->mPromise.Resolve(aData, __func__); + } +} + +void +OmxPromiseLayer::EmptyFillBufferDone(OMX_DIRTYPE aType, BufferData::BufferID aID) +{ + RefPtr holder = FindAndRemoveBufferHolder(aType, aID); + MOZ_ASSERT(!!holder); + LOG("EmptyFillBufferDone: type %d, buffer %p", aType, holder->mBuffer); + if (holder) { + if (aType == OMX_DirOutput) { + holder->mRawData = nullptr; + holder->mRawData = FindAndRemoveRawData(holder->mBuffer->nTimeStamp); + } + holder->mStatus = BufferData::BufferStatus::OMX_CLIENT; + holder->mPromise.Resolve(holder, __func__); + } +} + +RefPtr +OmxPromiseLayer::SendCommand(OMX_COMMANDTYPE aCmd, OMX_U32 aParam1, OMX_PTR aCmdData) +{ + // No need to issue flush because of buffers are in client already. + // + // Some components fail to respond flush event when all of buffers are in + // client. + if (aCmd == OMX_CommandFlush) { + bool needFlush = false; + if ((aParam1 & OMX_DirInput && mInbufferHolders.Length()) || + (aParam1 & OMX_DirOutput && mOutbufferHolders.Length())) { + needFlush = true; + } + if (!needFlush) { + LOG("SendCommand: buffers are in client already, no need to flush"); + mRawDatas.Clear(); + return OmxCommandPromise::CreateAndResolve(OMX_CommandFlush, __func__); + } + } + + OMX_ERRORTYPE err = mPlatformLayer->SendCommand(aCmd, aParam1, aCmdData); + if (err != OMX_ErrorNone) { + OmxCommandFailureHolder failure(OMX_ErrorNotReady, aCmd); + return OmxCommandPromise::CreateAndReject(failure, __func__); + } + + RefPtr p; + if (aCmd == OMX_CommandStateSet) { + p = mCommandStatePromise.Ensure(__func__); + } else if (aCmd == OMX_CommandFlush) { + p = mFlushPromise.Ensure(__func__); + mFlushPortIndex = aParam1; + // Clear all buffered raw data. + mRawDatas.Clear(); + } else if (aCmd == OMX_CommandPortEnable) { + p = mPortEnablePromise.Ensure(__func__); + } else if (aCmd == OMX_CommandPortDisable) { + p = mPortDisablePromise.Ensure(__func__); + } else { + LOG("SendCommand: error unsupport command"); + MOZ_ASSERT(0); + } + + return p; +} + +bool +OmxPromiseLayer::Event(OMX_EVENTTYPE aEvent, OMX_U32 aData1, OMX_U32 aData2) +{ + OMX_COMMANDTYPE cmd = (OMX_COMMANDTYPE) aData1; + switch (aEvent) { + case OMX_EventCmdComplete: + { + if (cmd == OMX_CommandStateSet) { + mCommandStatePromise.Resolve(OMX_CommandStateSet, __func__); + } else if (cmd == OMX_CommandFlush && mFlushPortIndex == aData2) { + mFlushPromise.Resolve(OMX_CommandFlush, __func__); + } else if (cmd == OMX_CommandPortDisable) { + mPortDisablePromise.Resolve(OMX_CommandPortDisable, __func__); + } else if (cmd == OMX_CommandPortEnable) { + mPortEnablePromise.Resolve(OMX_CommandPortEnable, __func__); + } + break; + } + case OMX_EventError: + { + if (cmd == OMX_CommandStateSet) { + OmxCommandFailureHolder failure(OMX_ErrorUndefined, OMX_CommandStateSet); + mCommandStatePromise.Reject(failure, __func__); + } else if (cmd == OMX_CommandFlush && mFlushPortIndex == aData2) { + OmxCommandFailureHolder failure(OMX_ErrorUndefined, OMX_CommandFlush); + mFlushPromise.Reject(failure, __func__); + } else if (cmd == OMX_CommandPortDisable) { + OmxCommandFailureHolder failure(OMX_ErrorUndefined, OMX_CommandPortDisable); + mPortDisablePromise.Reject(failure, __func__); + } else if (cmd == OMX_CommandPortEnable) { + OmxCommandFailureHolder failure(OMX_ErrorUndefined, OMX_CommandPortEnable); + mPortEnablePromise.Reject(failure, __func__); + } + break; + } + default: + { + return false; + } + } + return true; +} + +nsresult +OmxPromiseLayer::AllocateOmxBuffer(OMX_DIRTYPE aType, BUFFERLIST* aBuffers) +{ + return mPlatformLayer->AllocateOmxBuffer(aType, aBuffers); +} + +nsresult +OmxPromiseLayer::ReleaseOmxBuffer(OMX_DIRTYPE aType, BUFFERLIST* aBuffers) +{ + return mPlatformLayer->ReleaseOmxBuffer(aType, aBuffers); +} + +OMX_STATETYPE +OmxPromiseLayer::GetState() +{ + OMX_STATETYPE state; + OMX_ERRORTYPE err = mPlatformLayer->GetState(&state); + return err == OMX_ErrorNone ? state : OMX_StateInvalid; +} + +OMX_ERRORTYPE +OmxPromiseLayer::GetParameter(OMX_INDEXTYPE aParamIndex, + OMX_PTR aComponentParameterStructure, + OMX_U32 aComponentParameterSize) +{ + return mPlatformLayer->GetParameter(aParamIndex, + aComponentParameterStructure, + aComponentParameterSize); +} + +OMX_ERRORTYPE +OmxPromiseLayer::SetParameter(OMX_INDEXTYPE aParamIndex, + OMX_PTR aComponentParameterStructure, + OMX_U32 aComponentParameterSize) +{ + return mPlatformLayer->SetParameter(aParamIndex, + aComponentParameterStructure, + aComponentParameterSize); +} + +nsresult +OmxPromiseLayer::Shutdown() +{ + LOG("Shutdown"); + MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); + MOZ_ASSERT(!GetBufferHolders(OMX_DirInput)->Length()); + MOZ_ASSERT(!GetBufferHolders(OMX_DirOutput)->Length()); + return mPlatformLayer->Shutdown(); +} + +} diff --git a/dom/media/platforms/omx/OmxPromiseLayer.h b/dom/media/platforms/omx/OmxPromiseLayer.h new file mode 100644 index 000000000000..aa130d340dd9 --- /dev/null +++ b/dom/media/platforms/omx/OmxPromiseLayer.h @@ -0,0 +1,221 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if !defined(OmxPromiseLayer_h_) +#define OmxPromiseLayer_h_ + +#include "OMX_Core.h" +#include "OMX_Types.h" +#include "mozilla/MozPromise.h" +#include "mozilla/TaskQueue.h" + +namespace mozilla { + +class TrackInfo; +class OmxPlatformLayer; +class OmxDataDecoder; + +/* This class acts as a middle layer between OmxDataDecoder and the underlying + * OmxPlatformLayer. + * + * This class has two purposes: + * 1. Using promise instead of OpenMax async callback function. + * For example, OmxCommandPromise is used for OpenMax IL SendCommand. + * 2. Manage the buffer exchanged between client and component. + * Because omx buffer works crossing threads, so each omx buffer has its own + * promise, it is defined in BufferData. + * + * All of functions and members in this class should be run in the same + * TaskQueue. + */ +class OmxPromiseLayer { +protected: + virtual ~OmxPromiseLayer() {} + +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(OmxPromiseLayer) + + OmxPromiseLayer(TaskQueue* aTaskQueue, OmxDataDecoder* aDataDecoder); + + class BufferData; + + typedef nsTArray> BUFFERLIST; + + class OmxBufferFailureHolder { + public: + OmxBufferFailureHolder(OMX_ERRORTYPE aError, BufferData* aBuffer) + : mError(aError) + , mBuffer(aBuffer) + {} + + OMX_ERRORTYPE mError; + BufferData* mBuffer; + }; + + typedef MozPromise OmxBufferPromise; + + class OmxCommandFailureHolder { + public: + OmxCommandFailureHolder(OMX_ERRORTYPE aErrorType, + OMX_COMMANDTYPE aCommandType) + : mErrorType(aErrorType) + , mCommandType(aCommandType) + {} + + OMX_ERRORTYPE mErrorType; + OMX_COMMANDTYPE mCommandType; + }; + + typedef MozPromise OmxCommandPromise; + + typedef MozPromise OmxPortConfigPromise; + + // TODO: maybe a generic promise is good enough for this case? + RefPtr Init(TaskQueue* aQueue, const TrackInfo* aInfo); + + RefPtr FillBuffer(BufferData* aData); + + RefPtr EmptyBuffer(BufferData* aData); + + RefPtr SendCommand(OMX_COMMANDTYPE aCmd, + OMX_U32 aParam1, + OMX_PTR aCmdData); + + nsresult AllocateOmxBuffer(OMX_DIRTYPE aType, BUFFERLIST* aBuffers); + + nsresult ReleaseOmxBuffer(OMX_DIRTYPE aType, BUFFERLIST* aBuffers); + + OMX_STATETYPE GetState(); + + OMX_ERRORTYPE GetParameter(OMX_INDEXTYPE aParamIndex, + OMX_PTR aComponentParameterStructure, + OMX_U32 aComponentParameterSize); + + OMX_ERRORTYPE SetParameter(OMX_INDEXTYPE nIndex, + OMX_PTR aComponentParameterStructure, + OMX_U32 aComponentParameterSize); + + nsresult Shutdown(); + + // BufferData maintains the status of OMX buffer (OMX_BUFFERHEADERTYPE). + // mStatus tracks the buffer owner. + // And a promise because OMX buffer working among different threads. + class BufferData { + protected: + virtual ~BufferData() {} + + public: + explicit BufferData(OMX_BUFFERHEADERTYPE* aBuffer) + : mEos(false) + , mStatus(BufferStatus::FREE) + , mBuffer(aBuffer) + {} + + typedef void* BufferID; + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BufferData) + + // In most cases, the ID of this buffer is the pointer address of mBuffer. + // However, in platform like gonk, it is another value. + virtual BufferID ID() + { + return mBuffer; + } + + // The buffer could be used by several objects. And only one object owns the + // buffer the same time. + // FREE: + // nobody uses it. + // + // OMX_COMPONENT: + // buffer is used by OMX component (OmxPlatformLayer). + // + // OMX_CLIENT: + // buffer is used by client which is wait for audio/video playing + // (OmxDataDecoder) + // + // OMX_CLIENT_OUTPUT: + // used by client to output decoded data (for example, Gecko layer in + // this case) + // + // For output port buffer, the status transition is: + // FREE -> OMX_COMPONENT -> OMX_CLIENT -> OMX_CLIENT_OUTPUT -> FREE + // + // For input port buffer, the status transition is: + // FREE -> OMX_COMPONENT -> OMX_CLIENT -> FREE + // + enum BufferStatus { + FREE, + OMX_COMPONENT, + OMX_CLIENT, + OMX_CLIENT_OUTPUT, + INVALID + }; + + bool mEos; + + // The raw keeps in OmxPromiseLayer after EmptyBuffer and then passing to + // output decoded buffer in EmptyFillBufferDone. It is used to keep the + // records of the original data from demuxer, like duration, stream offset...etc. + RefPtr mRawData; + + // Because OMX buffer works acorssing threads, so it uses a promise + // for each buffer when the buffer is used by Omx component. + MozPromiseHolder mPromise; + BufferStatus mStatus; + OMX_BUFFERHEADERTYPE* mBuffer; + }; + + void EmptyFillBufferDone(OMX_DIRTYPE aType, BufferData::BufferID aID); + + void EmptyFillBufferDone(OMX_DIRTYPE aType, BufferData* aData); + + already_AddRefed + FindBufferById(OMX_DIRTYPE aType, BufferData::BufferID aId); + + already_AddRefed + FindAndRemoveBufferHolder(OMX_DIRTYPE aType, BufferData::BufferID aId); + + // Return truen if event is handled. + bool Event(OMX_EVENTTYPE aEvent, OMX_U32 aData1, OMX_U32 aData2); + +protected: + BUFFERLIST* GetBufferHolders(OMX_DIRTYPE aType); + + already_AddRefed FindAndRemoveRawData(OMX_TICKS aTimecode); + + RefPtr mTaskQueue; + + MozPromiseHolder mCommandStatePromise; + + MozPromiseHolder mPortDisablePromise; + + MozPromiseHolder mPortEnablePromise; + + MozPromiseHolder mFlushPromise; + + OMX_U32 mFlushPortIndex; + + nsAutoPtr mPlatformLayer; + +private: + // Elements are added to holders when FillBuffer() or FillBuffer(). And + // removing elelments when the promise is resolved. Buffers in these lists + // should NOT be used by other component; for example, output it to audio + // output. These list should be empty when engine is about to shutdown. + // + // Note: + // There bufferlist should not be used by other class directly. + BUFFERLIST mInbufferHolders; + + BUFFERLIST mOutbufferHolders; + + nsTArray> mRawDatas; +}; + +} + +#endif /* OmxPromiseLayer_h_ */ diff --git a/dom/media/platforms/omx/moz.build b/dom/media/platforms/omx/moz.build new file mode 100644 index 000000000000..e98c2ca9a370 --- /dev/null +++ b/dom/media/platforms/omx/moz.build @@ -0,0 +1,46 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXPORTS += [ + 'OmxDecoderModule.h', +] + +UNIFIED_SOURCES += [ + 'OmxDataDecoder.cpp', + 'OmxDecoderModule.cpp', + 'OmxPromiseLayer.cpp', +] + +LOCAL_INCLUDES += [ + '/media/openmax_il/il112', +] + +include('/ipc/chromium/chromium-config.mozbuild') + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk': + # Suppress some GCC/clang warnings being treated as errors: + # - about attributes on forward declarations for types that are already + # defined, which complains about an important MOZ_EXPORT for android::AString + # - about multi-character constants which are used in codec-related code + if CONFIG['GNU_CC'] or CONFIG['CLANG_CL']: + CXXFLAGS += [ + '-Wno-error=attributes', + '-Wno-error=multichar' + ] + CXXFLAGS += [ + '-I%s/%s' % (CONFIG['ANDROID_SOURCE'], d) for d in [ + 'frameworks/base/include/binder', + 'frameworks/base/include/utils', + ] + ] + UNIFIED_SOURCES += [ + 'GonkOmxPlatformLayer.cpp', + ] + EXTRA_DSO_LDOPTS += [ + '-libbinder', + ] + +FINAL_LIBRARY = 'xul'