зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1224887 - new PlatformDecoderModule based on OpenMax IL. r=jya, sotaro
This commit is contained in:
Родитель
d2f4121c27
Коммит
d0c13c6363
|
@ -27,7 +27,10 @@ UNIFIED_SOURCES += [
|
|||
'wrappers/H264Converter.cpp'
|
||||
]
|
||||
|
||||
DIRS += ['agnostic/gmp']
|
||||
DIRS += [
|
||||
'agnostic/gmp',
|
||||
'omx'
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_WMF']:
|
||||
DIRS += [ 'wmf' ];
|
||||
|
|
|
@ -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
|
|
@ -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_
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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_ */
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
|
@ -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_
|
|
@ -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_
|
|
@ -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::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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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_ */
|
|
@ -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'
|
Загрузка…
Ссылка в новой задаче