--- a/dom/media/platforms/moz.build
+++ b/dom/media/platforms/moz.build
@@ -22,17 +22,20 @@ UNIFIED_SOURCES += [
'agnostic/VorbisDecoder.cpp',
'agnostic/VPXDecoder.cpp',
'PDMFactory.cpp',
'PlatformDecoderModule.cpp',
'wrappers/FuzzingWrapper.cpp',
'wrappers/H264Converter.cpp'
]
-DIRS += ['agnostic/gmp']
+DIRS += [
+ 'agnostic/gmp',
+ 'omx'
+]
if CONFIG['MOZ_WMF']:
DIRS += [ 'wmf' ];
if CONFIG['MOZ_EME']:
DIRS += ['agnostic/eme']
if CONFIG['MOZ_FFMPEG']:
new file mode 100644
--- /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 <binder/MemoryDealer.h>
+#include <media/IOMX.h>
+#include <utils/List.h>
+#include <media/stagefright/OMXCodec.h>
+
+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<uint32_t>& 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<GonkOmxObserver> self = this;
+ nsCOMPtr<nsIRunnable> 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<GonkOmxObserver> self = this;
+ nsCOMPtr<nsIRunnable> 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<GonkOmxObserver> self = this;
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([self, aMsg] () {
+ if (!self->mPromiseLayer) {
+ return;
+ }
+
+ // TODO: these codes look a little ugly, it'd be better to improve them.
+ RefPtr<BufferData> 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<GonkBufferData*>(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<TaskQueue> mTaskQueue;
+ // TODO:
+ // we should combination both event handlers into one. And we should provide
+ // an unified way for event handling in OmxPlatforLayer class.
+ RefPtr<OmxPromiseLayer> mPromiseLayer;
+ RefPtr<OmxDataDecoder> 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<uint32_t> 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<IMemory> 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<OMXCodec::CodecNameAndQuirks> 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<class T> void
+GonkOmxPlatformLayer::InitOmxParameter(T* aParam)
+{
+ PodZero(aParam);
+ aParam->nSize = sizeof(T);
+ aParam->nVersion.s.nVersionMajor = 1;
+}
+
+} // mozilla
new file mode 100644
--- /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 <utils/RefBase.h>
+#include <media/stagefright/OMXClient.h>
+
+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<OMX_BUFFERHEADERTYPE> 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<class T> void InitOmxParameter(T* aParam);
+
+protected:
+ bool LoadComponent(const char* aName);
+
+ friend class GonkOmxObserver;
+
+ RefPtr<TaskQueue> mTaskQueue;
+
+ // OMX_DirInput is 0, OMX_DirOutput is 1.
+ android::sp<android::MemoryDealer> mMemoryDealer[2];
+
+ android::sp<GonkOmxObserver> mOmxObserver;
+
+ android::sp<android::IOMX> mOmx;
+
+ android::IOMX::node_id mNode;
+
+ android::OMXClient mOmxClient;
+
+ uint32_t mQuirks;
+
+ bool mUsingHardwareCodec;
+};
+
+}
+
+#pragma GCC visibility pop
+
+#endif // GonkOmxPlatformLayer_h_
new file mode 100644
--- /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<uint32_t>& 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<nsIRunnable> 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<OmxDataDecoder> self = this;
+ nsCOMPtr<nsIRunnable> r =
+ NS_NewRunnableFunction([self] () {
+ self->mCallback->DrainComplete();
+ });
+ mReaderTaskQueue->Dispatch(r.forget());
+}
+
+RefPtr<MediaDataDecoder::InitPromise>
+OmxDataDecoder::Init()
+{
+ LOG("(%p)", this);
+ mReaderTaskQueue = AbstractThread::GetCurrent()->AsTaskQueue();
+ MOZ_ASSERT(mReaderTaskQueue);
+
+ RefPtr<InitPromise> p = mInitPromise.Ensure(__func__);
+ RefPtr<OmxDataDecoder> 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<nsIRunnable> 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<OmxDataDecoder> self = this;
+ RefPtr<MediaRawData> sample = aSample;
+
+ nsCOMPtr<nsIRunnable> 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<nsIRunnable> 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<nsIRunnable> r =
+ NS_NewRunnableMethod(this, &OmxDataDecoder::SendEosBuffer);
+ mOmxTaskQueue->Dispatch(r.forget());
+
+ return NS_OK;
+}
+
+nsresult
+OmxDataDecoder::Shutdown()
+{
+ LOG("(%p)", this);
+
+ mShutdown = true;
+
+ nsCOMPtr<nsIRunnable> 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<OmxDataDecoder> self = this;
+ mOmxLayer->SendCommand(OMX_CommandFlush, OMX_ALL, nullptr)
+ ->Then(mOmxTaskQueue, __func__,
+ [self] () -> RefPtr<OmxCommandPromise> {
+ 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<OmxCommandPromise> {
+ 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<AudioData> 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<nsIRunnable> 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<BufferData> inbuf = FindAvailableBuffer(OMX_DirInput);
+ if (!inbuf) {
+ LOG("no input buffer!");
+ break;
+ }
+
+ RefPtr<MediaRawData> 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<BufferData> 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<RefPtr<OmxPromiseLayer::BufferData>>*
+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<OmxDataDecoder> self = this;
+ nsCOMPtr<nsIRunnable> 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<OmxDataDecoder> self = this;
+ nsCOMPtr<nsIRunnable> 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<OmxDataDecoder> 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<OmxDataDecoder> 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<BufferData> 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<class T> 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<OmxPromiseLayer::OmxBufferPromise::AllPromiseType>
+OmxDataDecoder::CollectBufferPromises(OMX_DIRTYPE aType)
+{
+ MOZ_ASSERT(mOmxTaskQueue->IsCurrentThreadIn());
+
+ nsTArray<RefPtr<OmxBufferPromise>> 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<BufferData*> 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<OmxDataDecoder> 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<OmxCommandPromise> {
+ // 3. enable port.
+ // Send enable port command.
+ RefPtr<OmxCommandPromise> 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<MediaRawData> 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<OmxDataDecoder> 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();
+}
+
+}
new file mode 100644
--- /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<InitPromise> 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<class T> void InitOmxParameter(T* aParam);
+
+ // aType could be OMX_DirMax for all types.
+ RefPtr<OmxPromiseLayer::OmxBufferPromise::AllPromiseType>
+ CollectBufferPromises(OMX_DIRTYPE aType);
+
+ Monitor mMonitor;
+
+ // The Omx TaskQueue.
+ RefPtr<TaskQueue> mOmxTaskQueue;
+
+ RefPtr<TaskQueue> mReaderTaskQueue;
+
+ WatchManager<OmxDataDecoder> mWatchManager;
+
+ // It is accessed in omx TaskQueue.
+ Watchable<OMX_STATETYPE> mOmxState;
+
+ RefPtr<OmxPromiseLayer> mOmxLayer;
+
+ UniquePtr<TrackInfo> mTrackInfo;
+
+ // It is accessed in both omx and reader TaskQueue.
+ Atomic<bool> mFlushing;
+
+ // It is accessed in Omx/reader TaskQeueu.
+ Atomic<bool> mShutdown;
+
+ // It is accessed in Omx TaskQeueu.
+ bool mCheckingInputExhausted;
+
+ // It is accessed in reader TaskQueue.
+ MozPromiseHolder<InitPromise> 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<int32_t> mPortSettingsChanged;
+
+ // It is access in Omx TaskQueue.
+ nsTArray<RefPtr<MediaRawData>> mMediaRawDatas;
+
+ // It is access in Omx TaskQueue. The latest input MediaRawData.
+ RefPtr<MediaRawData> 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<AudioData> mAudioQueue;
+
+ AudioCompactor mAudioCompactor;
+
+ MediaDataDecoderCallback* mCallback;
+};
+
+}
+
+#endif /* OmxDataDecoder_h_ */
new file mode 100644
--- /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<MediaDataDecoder>
+OmxDecoderModule::CreateVideoDecoder(const VideoInfo& aConfig,
+ mozilla::layers::LayersBackend aLayersBackend,
+ mozilla::layers::ImageContainer* aImageContainer,
+ FlushableTaskQueue* aVideoTaskQueue,
+ MediaDataDecoderCallback* aCallback)
+{
+ return nullptr;
+}
+
+already_AddRefed<MediaDataDecoder>
+OmxDecoderModule::CreateAudioDecoder(const AudioInfo& aConfig,
+ FlushableTaskQueue* aAudioTaskQueue,
+ MediaDataDecoderCallback* aCallback)
+{
+ RefPtr<OmxDataDecoder> 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");
+}
+
+}
new file mode 100644
--- /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<MediaDataDecoder>
+ CreateVideoDecoder(const VideoInfo& aConfig,
+ mozilla::layers::LayersBackend aLayersBackend,
+ mozilla::layers::ImageContainer* aImageContainer,
+ FlushableTaskQueue* aVideoTaskQueue,
+ MediaDataDecoderCallback* aCallback) override;
+
+ already_AddRefed<MediaDataDecoder>
+ 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_
new file mode 100644
--- /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_
new file mode 100644
--- /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"
+
+#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION < 21
+#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)
+{
+#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION < 21
+ mPlatformLayer = new GonkOmxPlatformLayer(aDataDecoder, this, aTaskQueue);
+#endif
+ MOZ_ASSERT(!!mPlatformLayer);
+}
+
+RefPtr<OmxPromiseLayer::OmxCommandPromise>
+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::OmxBufferPromise>
+OmxPromiseLayer::FillBuffer(BufferData* aData)
+{
+ MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+ LOG("FillBuffer: buffer %p", aData->mBuffer);
+
+ RefPtr<OmxBufferPromise> 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::OmxBufferPromise>
+OmxPromiseLayer::EmptyBuffer(BufferData* aData)
+{
+ MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+ LOG("EmptyBuffer: buffer %p, size %d", aData->mBuffer, aData->mBuffer->nFilledLen);
+
+ RefPtr<OmxBufferPromise> 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<MediaRawData>
+OmxPromiseLayer::FindAndRemoveRawData(OMX_TICKS aTimecode)
+{
+ for (auto raw : mRawDatas) {
+ if (raw->mTimecode == aTimecode) {
+ mRawDatas.RemoveElement(raw);
+ return raw.forget();
+ }
+ }
+ return nullptr;
+}
+
+already_AddRefed<BufferData>
+OmxPromiseLayer::FindAndRemoveBufferHolder(OMX_DIRTYPE aType,
+ BufferData::BufferID aId)
+{
+ MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+
+ RefPtr<BufferData> 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<BufferData>
+OmxPromiseLayer::FindBufferById(OMX_DIRTYPE aType, BufferData::BufferID aId)
+{
+ MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+
+ RefPtr<BufferData> 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<BufferData> 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::OmxCommandPromise>
+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<OmxCommandPromise> 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();
+}
+
+}
new file mode 100644
--- /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<RefPtr<BufferData>> BUFFERLIST;
+
+ class OmxBufferFailureHolder {
+ public:
+ OmxBufferFailureHolder(OMX_ERRORTYPE aError, BufferData* aBuffer)
+ : mError(aError)
+ , mBuffer(aBuffer)
+ {}
+
+ OMX_ERRORTYPE mError;
+ BufferData* mBuffer;
+ };
+
+ typedef MozPromise<BufferData*, OmxBufferFailureHolder, /* IsExclusive = */ false> OmxBufferPromise;
+
+ class OmxCommandFailureHolder {
+ public:
+ OmxCommandFailureHolder(OMX_ERRORTYPE aErrorType,
+ OMX_COMMANDTYPE aCommandType)
+ : mErrorType(aErrorType)
+ , mCommandType(aCommandType)
+ {}
+
+ OMX_ERRORTYPE mErrorType;
+ OMX_COMMANDTYPE mCommandType;
+ };
+
+ typedef MozPromise<OMX_COMMANDTYPE, OmxCommandFailureHolder, /* IsExclusive = */ true> OmxCommandPromise;
+
+ typedef MozPromise<uint32_t, bool, /* IsExclusive = */ true> OmxPortConfigPromise;
+
+ // TODO: maybe a generic promise is good enough for this case?
+ RefPtr<OmxCommandPromise> Init(TaskQueue* aQueue, const TrackInfo* aInfo);
+
+ RefPtr<OmxBufferPromise> FillBuffer(BufferData* aData);
+
+ RefPtr<OmxBufferPromise> EmptyBuffer(BufferData* aData);
+
+ RefPtr<OmxCommandPromise> 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<MediaRawData> mRawData;
+
+ // Because OMX buffer works acorssing threads, so it uses a promise
+ // for each buffer when the buffer is used by Omx component.
+ MozPromiseHolder<OmxBufferPromise> mPromise;
+ BufferStatus mStatus;
+ OMX_BUFFERHEADERTYPE* mBuffer;
+ };
+
+ void EmptyFillBufferDone(OMX_DIRTYPE aType, BufferData::BufferID aID);
+
+ void EmptyFillBufferDone(OMX_DIRTYPE aType, BufferData* aData);
+
+ already_AddRefed<BufferData>
+ FindBufferById(OMX_DIRTYPE aType, BufferData::BufferID aId);
+
+ already_AddRefed<BufferData>
+ 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<MediaRawData> FindAndRemoveRawData(OMX_TICKS aTimecode);
+
+ RefPtr<TaskQueue> mTaskQueue;
+
+ MozPromiseHolder<OmxCommandPromise> mCommandStatePromise;
+
+ MozPromiseHolder<OmxCommandPromise> mPortDisablePromise;
+
+ MozPromiseHolder<OmxCommandPromise> mPortEnablePromise;
+
+ MozPromiseHolder<OmxCommandPromise> mFlushPromise;
+
+ OMX_U32 mFlushPortIndex;
+
+ nsAutoPtr<OmxPlatformLayer> 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<RefPtr<MediaRawData>> mRawDatas;
+};
+
+}
+
+#endif /* OmxPromiseLayer_h_ */
new file mode 100644
--- /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' and (CONFIG['ANDROID_VERSION'] == '19' or CONFIG['ANDROID_VERSION'] == '20'):
+ # 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'