diff options
Diffstat (limited to 'dom/media/platforms/omx/GonkOmxPlatformLayer.cpp')
-rw-r--r-- | dom/media/platforms/omx/GonkOmxPlatformLayer.cpp | 667 |
1 files changed, 667 insertions, 0 deletions
diff --git a/dom/media/platforms/omx/GonkOmxPlatformLayer.cpp b/dom/media/platforms/omx/GonkOmxPlatformLayer.cpp new file mode 100644 index 000000000..870566cf5 --- /dev/null +++ b/dom/media/platforms/omx/GonkOmxPlatformLayer.cpp @@ -0,0 +1,667 @@ +/* -*- 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 "GonkOmxPlatformLayer.h" + +#include <binder/MemoryDealer.h> +#include <cutils/properties.h> +#include <media/IOMX.h> +#include <media/stagefright/MediaCodecList.h> +#include <utils/List.h> + +#include "mozilla/Monitor.h" +#include "mozilla/layers/TextureClient.h" +#include "mozilla/layers/GrallocTextureClient.h" +#include "mozilla/layers/ImageBridgeChild.h" +#include "mozilla/layers/TextureClientRecycleAllocator.h" + +#include "ImageContainer.h" +#include "MediaInfo.h" +#include "OmxDataDecoder.h" + + +#ifdef LOG +#undef LOG +#endif + +#define LOG(arg, ...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, ("GonkOmxPlatformLayer(%p)::%s: " arg, this, __func__, ##__VA_ARGS__)) + +#define CHECK_ERR(err) \ + if (err != OK) { \ + LOG("error %d at %s", err, __func__); \ + return NS_ERROR_FAILURE; \ + } \ + +// Android proprietary value. +#define ANDROID_OMX_VIDEO_CodingVP8 (static_cast<OMX_VIDEO_CODINGTYPE>(9)) + +using namespace android; + +namespace mozilla { + +// In Gonk, the software component name has prefix "OMX.google". It needs to +// have a way to use hardware codec first. +bool IsSoftwareCodec(const char* aComponentName) +{ + nsAutoCString str(aComponentName); + return (str.Find(NS_LITERAL_CSTRING("OMX.google.")) == -1 ? false : true); +} + +bool IsInEmulator() +{ + char propQemu[PROPERTY_VALUE_MAX]; + property_get("ro.kernel.qemu", propQemu, ""); + return !strncmp(propQemu, "1", 1); +} + +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 combine both event handlers into one. And we should provide + // an unified way for event handling in OmxPlatformLayer class. + RefPtr<OmxPromiseLayer> mPromiseLayer; + RefPtr<OmxDataDecoder> mClient; +}; + +// This class allocates Gralloc buffer and manages TextureClient's recycle. +class GonkTextureClientRecycleHandler : public layers::ITextureClientRecycleAllocator +{ + typedef MozPromise<layers::TextureClient*, nsresult, /* IsExclusive = */ true> TextureClientRecyclePromise; + +public: + GonkTextureClientRecycleHandler(OMX_VIDEO_PORTDEFINITIONTYPE& aDef) + : ITextureClientRecycleAllocator() + , mMonitor("GonkTextureClientRecycleHandler") + { + RefPtr<layers::ImageBridgeChild> bridge = layers::ImageBridgeChild::GetSingleton(); + + // Allocate Gralloc texture memory. + layers::GrallocTextureData* textureData = + layers::GrallocTextureData::Create(gfx::IntSize(aDef.nFrameWidth, aDef.nFrameHeight), + aDef.eColorFormat, + gfx::BackendType::NONE, + GraphicBuffer::USAGE_HW_TEXTURE | GraphicBuffer::USAGE_SW_READ_OFTEN, + bridge); + + mGraphBuffer = textureData->GetGraphicBuffer(); + MOZ_ASSERT(mGraphBuffer.get()); + + mTextureClient = + layers::TextureClient::CreateWithData(textureData, + layers::TextureFlags::DEALLOCATE_CLIENT | layers::TextureFlags::RECYCLE, + bridge); + MOZ_ASSERT(mTextureClient); + + mPromise.SetMonitor(&mMonitor); + } + + RefPtr<TextureClientRecyclePromise> WaitforRecycle() + { + MonitorAutoLock lock(mMonitor); + MOZ_ASSERT(!!mGraphBuffer.get()); + + mTextureClient->SetRecycleAllocator(this); + return mPromise.Ensure(__func__); + } + + // DO NOT use smart pointer to receive TextureClient; otherwise it will + // distrupt the reference count. + layers::TextureClient* GetTextureClient() + { + return mTextureClient; + } + + GraphicBuffer* GetGraphicBuffer() + { + MonitorAutoLock lock(mMonitor); + return mGraphBuffer.get(); + } + + // This function is called from layers thread. + void RecycleTextureClient(layers::TextureClient* aClient) override + { + MOZ_ASSERT(mTextureClient == aClient); + + // Clearing the recycle allocator drops a reference, so make sure we stay alive + // for the duration of this function. + RefPtr<GonkTextureClientRecycleHandler> kungFuDeathGrip(this); + aClient->SetRecycleAllocator(nullptr); + + { + MonitorAutoLock lock(mMonitor); + mPromise.ResolveIfExists(mTextureClient, __func__); + } + } + + void Shutdown() + { + MonitorAutoLock lock(mMonitor); + + mPromise.RejectIfExists(NS_ERROR_FAILURE, __func__); + + // DO NOT clear TextureClient here. + // The ref count could be 1 and RecycleCallback will be called if we clear + // the ref count here. That breaks the whole mechanism. (RecycleCallback + // should be called from layers) + mGraphBuffer = nullptr; + } + +private: + // Because TextureClient calls RecycleCallbackl when ref count is 1, so we + // should hold only one reference here and use raw pointer when out of this + // class. + RefPtr<layers::TextureClient> mTextureClient; + + // It is protected by mMonitor. + sp<android::GraphicBuffer> mGraphBuffer; + + // It is protected by mMonitor. + MozPromiseHolder<TextureClientRecyclePromise> mPromise; + + Monitor mMonitor; +}; + +GonkBufferData::GonkBufferData(bool aLiveInLocal, + GonkOmxPlatformLayer* aGonkPlatformLayer) + : BufferData(nullptr) + , mId(0) + , mGonkPlatformLayer(aGonkPlatformLayer) +{ + if (!aLiveInLocal) { + mMirrorBuffer = new OMX_BUFFERHEADERTYPE; + PodZero(mMirrorBuffer.get()); + mBuffer = mMirrorBuffer.get(); + } +} + +void +GonkBufferData::ReleaseBuffer() +{ + if (mTextureClientRecycleHandler) { + mTextureClientRecycleHandler->Shutdown(); + mTextureClientRecycleHandler = nullptr; + } +} + +nsresult +GonkBufferData::InitSharedMemory(android::IMemory* aMemory) +{ + MOZ_RELEASE_ASSERT(mMirrorBuffer.get()); + + // aMemory is a IPC memory, it is safe to use it here. + mBuffer->pBuffer = (OMX_U8*)aMemory->pointer(); + mBuffer->nAllocLen = aMemory->size(); + return NS_OK; +} + +nsresult +GonkBufferData::InitLocalBuffer(IOMX::buffer_id aId) +{ + MOZ_RELEASE_ASSERT(!mMirrorBuffer.get()); + + mBuffer = (OMX_BUFFERHEADERTYPE*)aId; + return NS_OK; +} + +nsresult +GonkBufferData::InitGraphicBuffer(OMX_VIDEO_PORTDEFINITIONTYPE& aDef) +{ + mTextureClientRecycleHandler = new GonkTextureClientRecycleHandler(aDef); + + if (!mTextureClientRecycleHandler->GetGraphicBuffer()) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +already_AddRefed<MediaData> +GonkBufferData::GetPlatformMediaData() +{ + if (mGonkPlatformLayer->GetTrackInfo()->GetAsAudioInfo()) { + // This is audio decoding. + return nullptr; + } + + if (!mTextureClientRecycleHandler) { + // There is no GraphicBuffer, it should fallback to normal YUV420 VideoData. + return nullptr; + } + + VideoInfo info(*mGonkPlatformLayer->GetTrackInfo()->GetAsVideoInfo()); + RefPtr<VideoData> data = + VideoData::CreateAndCopyIntoTextureClient(info, + 0, + mBuffer->nTimeStamp, + 1, + mTextureClientRecycleHandler->GetTextureClient(), + false, + 0, + info.ImageRect()); + LOG("%p, disp width %d, height %d, pic width %d, height %d, time %ld", + this, info.mDisplay.width, info.mDisplay.height, + info.mImage.width, info.mImage.height, mBuffer->nTimeStamp); + + // Get TextureClient Promise here to wait for resolved. + RefPtr<GonkBufferData> self(this); + mTextureClientRecycleHandler->WaitforRecycle() + ->Then(mGonkPlatformLayer->GetTaskQueue(), __func__, + [self] () { + self->mPromise.ResolveIfExists(self, __func__); + }, + [self] () { + OmxBufferFailureHolder failure(OMX_ErrorUndefined, self); + self->mPromise.RejectIfExists(failure, __func__); + }); + + return data.forget(); +} + +GonkOmxPlatformLayer::GonkOmxPlatformLayer(OmxDataDecoder* aDataDecoder, + OmxPromiseLayer* aPromiseLayer, + TaskQueue* aTaskQueue, + layers::ImageContainer* aImageContainer) + : mTaskQueue(aTaskQueue) + , mImageContainer(aImageContainer) + , mNode(0) +{ + 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; + GetPortIndices(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 = 0; + + // Configure video output GraphicBuffer for video decoding acceleration. + bool useGralloc = false; + if (aType == OMX_DirOutput && mQuirks.test(kRequiresAllocateBufferOnOutputPorts) && + (def.eDomain == OMX_PortDomainVideo)) { + if (NS_FAILED(EnableOmxGraphicBufferPort(def))) { + return NS_ERROR_FAILURE; + } + + LOG("Enable OMX GraphicBuffer port, number %d, width %d, height %d", def.nBufferCountActual, + def.format.video.nFrameWidth, def.format.video.nFrameHeight); + + useGralloc = true; + + t = 1024; // MemoryDealer doesn't like 0, it's just for MemoryDealer happy. + } else { + 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) { + RefPtr<GonkBufferData> buffer; + IOMX::buffer_id bufferID; + status_t st; + nsresult rv; + + buffer = new GonkBufferData(liveinlocal, this); + if (useGralloc) { + // Buffer is lived remotely. Use GraphicBuffer for decoded video frame display. + rv = buffer->InitGraphicBuffer(def.format.video); + NS_ENSURE_SUCCESS(rv, rv); + st = mOmx->useGraphicBuffer(mNode, + def.nPortIndex, + buffer->mTextureClientRecycleHandler->GetGraphicBuffer(), + &bufferID); + CHECK_ERR(st); + } else { + sp<IMemory> mem = mMemoryDealer[aType]->allocate(def.nBufferSize); + MOZ_ASSERT(mem.get()); + + if ((mQuirks.test(kRequiresAllocateBufferOnInputPorts) && aType == OMX_DirInput) || + (mQuirks.test(kRequiresAllocateBufferOnOutputPorts) && aType == OMX_DirOutput)) { + // Buffer is lived remotely. We allocate a local OMX_BUFFERHEADERTYPE + // as the mirror of the remote OMX_BUFFERHEADERTYPE. + st = mOmx->allocateBufferWithBackup(mNode, aType, mem, &bufferID); + CHECK_ERR(st); + rv = buffer->InitSharedMemory(mem.get()); + NS_ENSURE_SUCCESS(rv, rv); + } else { + // Buffer is lived locally, bufferID is the actually OMX_BUFFERHEADERTYPE + // pointer. + st = mOmx->useBuffer(mNode, aType, mem, &bufferID); + CHECK_ERR(st); + rv = buffer->InitLocalBuffer(bufferID); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + rv = buffer->SetBufferId(bufferID); + NS_ENSURE_SUCCESS(rv, rv); + + aBufferList->AppendElement(buffer); + } + + return NS_OK; +} + +nsresult +GonkOmxPlatformLayer::ReleaseOmxBuffer(OMX_DIRTYPE aType, + BUFFERLIST* aBufferList) +{ + status_t st; + uint32_t len = aBufferList->Length(); + for (uint32_t i = 0; i < len; i++) { + GonkBufferData* buffer = static_cast<GonkBufferData*>(aBufferList->ElementAt(i).get()); + IOMX::buffer_id id = (OMX_BUFFERHEADERTYPE*) buffer->ID(); + st = mOmx->freeBuffer(mNode, aType, id); + if (st != OK) { + return NS_ERROR_FAILURE; + } + buffer->ReleaseBuffer(); + } + aBufferList->Clear(); + mMemoryDealer[aType].clear(); + + return NS_OK; +} + +nsresult +GonkOmxPlatformLayer::EnableOmxGraphicBufferPort(OMX_PARAM_PORTDEFINITIONTYPE& aDef) +{ + status_t st; + + st = mOmx->enableGraphicBuffers(mNode, aDef.nPortIndex, OMX_TRUE); + CHECK_ERR(st); + + 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) +{ + mInfo = aInfo; + status_t err = mOmxClient.connect(); + if (err != OK) { + return OMX_ErrorUndefined; + } + mOmx = mOmxClient.interface(); + if (!mOmx.get()) { + return OMX_ErrorUndefined; + } + + LOG("find componenet for mime type %s", mInfo->mMimeType.Data()); + + nsTArray<ComponentInfo> components; + if (FindComponents(mInfo->mMimeType, &components)) { + for (auto comp : components) { + if (LoadComponent(comp)) { + 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->ID()); +} + +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 ComponentInfo& aComponent) +{ + status_t err = mOmx->allocateNode(aComponent.mName, mOmxObserver, &mNode); + if (err == OK) { + mQuirks = aComponent.mQuirks; + LOG("Load OpenMax component %s, alloc input %d, alloc output %d, live locally %d", + aComponent.mName, mQuirks.test(kRequiresAllocateBufferOnInputPorts), + mQuirks.test(kRequiresAllocateBufferOnOutputPorts), + mOmx->livesLocally(mNode, getpid())); + return true; + } + return false; +} + +layers::ImageContainer* +GonkOmxPlatformLayer::GetImageContainer() +{ + return mImageContainer; +} + +const TrackInfo* +GonkOmxPlatformLayer::GetTrackInfo() +{ + return mInfo; +} + +bool +GonkOmxPlatformLayer::FindComponents(const nsACString& aMimeType, + nsTArray<ComponentInfo>* aComponents) +{ + static const MediaCodecList* codecs = MediaCodecList::getInstance(); + + bool useHardwareCodecOnly = false; + + // H264 and H263 has different profiles, software codec doesn't support high profile. + // So we use hardware codec only. + if (!IsInEmulator() && + (aMimeType.EqualsLiteral("video/avc") || + aMimeType.EqualsLiteral("video/mp4") || + aMimeType.EqualsLiteral("video/mp4v-es") || + aMimeType.EqualsLiteral("video/3gp"))) { + useHardwareCodecOnly = true; + } + + const char* mime = aMimeType.Data(); + // Translate VP8 MIME type to Android format. + if (aMimeType.EqualsLiteral("video/webm; codecs=vp8")) { + mime = "video/x-vnd.on2.vp8"; + } + + size_t start = 0; + bool found = false; + while (true) { + ssize_t index = codecs->findCodecByType(mime, false /* encoder */, start); + if (index < 0) { + break; + } + start = index + 1; + + const char* name = codecs->getCodecName(index); + if (IsSoftwareCodec(name) && useHardwareCodecOnly) { + continue; + } + + found = true; + + if (!aComponents) { + continue; + } + ComponentInfo* comp = aComponents->AppendElement(); + comp->mName = name; + if (codecs->codecHasQuirk(index, "requires-allocate-on-input-ports")) { + comp->mQuirks.set(kRequiresAllocateBufferOnInputPorts); + } + if (codecs->codecHasQuirk(index, "requires-allocate-on-output-ports")) { + comp->mQuirks.set(kRequiresAllocateBufferOnOutputPorts); + } + } + + return found; +} + +OMX_VIDEO_CODINGTYPE +GonkOmxPlatformLayer::CompressionFormat() +{ + MOZ_ASSERT(mInfo); + + return mInfo->mMimeType.EqualsLiteral("video/webm; codecs=vp8") ? + ANDROID_OMX_VIDEO_CodingVP8 : OmxPlatformLayer::CompressionFormat(); +} + +} // mozilla |