/* -*- 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 "MediaCodecProxy.h" #include #include #include #include "GonkVideoDecoderManager.h" #include "GrallocImages.h" #include "MediaDecoderReader.h" #include "ImageContainer.h" #include "VideoUtils.h" #include "nsThreadUtils.h" #include "Layers.h" #include "mozilla/Logging.h" #include #include #include #include #include "GonkNativeWindow.h" #include "mozilla/layers/GrallocTextureClient.h" #include "mozilla/layers/ImageBridgeChild.h" #include "mozilla/layers/TextureClient.h" #include "mozilla/layers/TextureClientRecycleAllocator.h" #include #define CODECCONFIG_TIMEOUT_US 10000LL #define READ_OUTPUT_BUFFER_TIMEOUT_US 0LL #include #define GVDM_LOG(...) __android_log_print(ANDROID_LOG_DEBUG, "GonkVideoDecoderManager", __VA_ARGS__) #define LOG(...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, (__VA_ARGS__)) using namespace mozilla::layers; using namespace android; typedef android::MediaCodecProxy MediaCodecProxy; namespace mozilla { class GonkTextureClientAllocationHelper : public layers::ITextureClientAllocationHelper { public: GonkTextureClientAllocationHelper(uint32_t aGrallocFormat, gfx::IntSize aSize) : ITextureClientAllocationHelper(gfx::SurfaceFormat::UNKNOWN, aSize, BackendSelector::Content, TextureFlags::DEALLOCATE_CLIENT, ALLOC_DISALLOW_BUFFERTEXTURECLIENT) , mGrallocFormat(aGrallocFormat) {} already_AddRefed Allocate(KnowsCompositor* aAllocator) override { uint32_t usage = android::GraphicBuffer::USAGE_SW_READ_OFTEN | android::GraphicBuffer::USAGE_SW_WRITE_OFTEN | android::GraphicBuffer::USAGE_HW_TEXTURE; GrallocTextureData* texData = GrallocTextureData::Create(mSize, mGrallocFormat, gfx::BackendType::NONE, usage, aAllocator->GetTextureForwarder()); if (!texData) { return nullptr; } sp graphicBuffer = texData->GetGraphicBuffer(); if (!graphicBuffer.get()) { return nullptr; } RefPtr textureClient = TextureClient::CreateWithData(texData, TextureFlags::DEALLOCATE_CLIENT, aAllocator->GetTextureForwarder()); return textureClient.forget(); } bool IsCompatible(TextureClient* aTextureClient) override { if (!aTextureClient) { return false; } sp graphicBuffer = static_cast(aTextureClient->GetInternalData())->GetGraphicBuffer(); if (!graphicBuffer.get() || static_cast(graphicBuffer->getPixelFormat()) != mGrallocFormat || aTextureClient->GetSize() != mSize) { return false; } return true; } private: uint32_t mGrallocFormat; }; GonkVideoDecoderManager::GonkVideoDecoderManager( mozilla::layers::ImageContainer* aImageContainer, const VideoInfo& aConfig) : mConfig(aConfig) , mImageContainer(aImageContainer) , mColorConverterBufferSize(0) , mPendingReleaseItemsLock("GonkVideoDecoderManager::mPendingReleaseItemsLock") , mNeedsCopyBuffer(false) { MOZ_COUNT_CTOR(GonkVideoDecoderManager); } GonkVideoDecoderManager::~GonkVideoDecoderManager() { MOZ_COUNT_DTOR(GonkVideoDecoderManager); } nsresult GonkVideoDecoderManager::Shutdown() { mVideoCodecRequest.DisconnectIfExists(); return GonkDecoderManager::Shutdown(); } RefPtr GonkVideoDecoderManager::Init() { mNeedsCopyBuffer = false; uint32_t maxWidth, maxHeight; char propValue[PROPERTY_VALUE_MAX]; property_get("ro.moz.omx.hw.max_width", propValue, "-1"); maxWidth = -1 == atoi(propValue) ? MAX_VIDEO_WIDTH : atoi(propValue); property_get("ro.moz.omx.hw.max_height", propValue, "-1"); maxHeight = -1 == atoi(propValue) ? MAX_VIDEO_HEIGHT : atoi(propValue) ; if (uint32_t(mConfig.mImage.width * mConfig.mImage.height) > maxWidth * maxHeight) { GVDM_LOG("Video resolution exceeds hw codec capability"); return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } // Validate the container-reported frame and pictureRect sizes. This ensures // that our video frame creation code doesn't overflow. if (!IsValidVideoRegion(mConfig.mImage, mConfig.ImageRect(), mConfig.mDisplay)) { GVDM_LOG("It is not a valid region"); return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } mReaderTaskQueue = AbstractThread::GetCurrent()->AsTaskQueue(); MOZ_ASSERT(mReaderTaskQueue); if (mDecodeLooper.get() != nullptr) { return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } if (!InitLoopers(MediaData::VIDEO_DATA)) { return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } RefPtr p = mInitPromise.Ensure(__func__); android::sp self = this; mDecoder = MediaCodecProxy::CreateByType(mDecodeLooper, mConfig.mMimeType.get(), false); uint32_t capability = MediaCodecProxy::kEmptyCapability; if (mDecoder->getCapability(&capability) == OK && (capability & MediaCodecProxy::kCanExposeGraphicBuffer)) { #if ANDROID_VERSION >= 21 sp consumer; GonkBufferQueue::createBufferQueue(&mGraphicBufferProducer, &consumer); mNativeWindow = new GonkNativeWindow(consumer); #else mNativeWindow = new GonkNativeWindow(); #endif } mVideoCodecRequest.Begin(mDecoder->AsyncAllocateVideoMediaCodec() ->Then(mReaderTaskQueue, __func__, [self] (bool) -> void { self->mVideoCodecRequest.Complete(); self->codecReserved(); }, [self] (bool) -> void { self->mVideoCodecRequest.Complete(); self->codecCanceled(); })); return p; } nsresult GonkVideoDecoderManager::CreateVideoData(MediaBuffer* aBuffer, int64_t aStreamOffset, VideoData **v) { *v = nullptr; RefPtr data; int64_t timeUs; int32_t keyFrame; if (aBuffer == nullptr) { GVDM_LOG("Video Buffer is not valid!"); return NS_ERROR_UNEXPECTED; } AutoReleaseMediaBuffer autoRelease(aBuffer, mDecoder.get()); if (!aBuffer->meta_data()->findInt64(kKeyTime, &timeUs)) { GVDM_LOG("Decoder did not return frame time"); return NS_ERROR_UNEXPECTED; } if (mLastTime > timeUs) { GVDM_LOG("Output decoded sample time is revert. time=%lld", timeUs); return NS_ERROR_NOT_AVAILABLE; } mLastTime = timeUs; if (aBuffer->range_length() == 0) { // Some decoders may return spurious empty buffers that we just want to ignore // quoted from Android's AwesomePlayer.cpp return NS_ERROR_NOT_AVAILABLE; } if (!aBuffer->meta_data()->findInt32(kKeyIsSyncFrame, &keyFrame)) { keyFrame = 0; } gfx::IntRect picture = mConfig.ScaledImageRect(mFrameInfo.mWidth, mFrameInfo.mHeight); if (aBuffer->graphicBuffer().get()) { data = CreateVideoDataFromGraphicBuffer(aBuffer, picture); if (data && !mNeedsCopyBuffer) { // RecycleCallback() will be responsible for release the buffer. autoRelease.forget(); } mNeedsCopyBuffer = false; } else { data = CreateVideoDataFromDataBuffer(aBuffer, picture); } if (!data) { return NS_ERROR_UNEXPECTED; } // Fill necessary info. data->mOffset = aStreamOffset; data->mTime = timeUs; data->mKeyframe = keyFrame; data.forget(v); return NS_OK; } // Copy pixels from one planar YUV to another. static void CopyYUV(PlanarYCbCrData& aSource, PlanarYCbCrData& aDestination) { // Fill Y plane. uint8_t* srcY = aSource.mYChannel; gfx::IntSize ySize = aSource.mYSize; uint8_t* destY = aDestination.mYChannel; // Y plane. for (int i = 0; i < ySize.height; i++) { memcpy(destY, srcY, ySize.width); srcY += aSource.mYStride; destY += aDestination.mYStride; } // Fill UV plane. // Line start uint8_t* srcU = aSource.mCbChannel; uint8_t* srcV = aSource.mCrChannel; uint8_t* destU = aDestination.mCbChannel; uint8_t* destV = aDestination.mCrChannel; gfx::IntSize uvSize = aSource.mCbCrSize; for (int i = 0; i < uvSize.height; i++) { uint8_t* su = srcU; uint8_t* sv = srcV; uint8_t* du = destU; uint8_t* dv =destV; for (int j = 0; j < uvSize.width; j++) { *du++ = *su++; *dv++ = *sv++; // Move to next pixel. su += aSource.mCbSkip; sv += aSource.mCrSkip; du += aDestination.mCbSkip; dv += aDestination.mCrSkip; } // Move to next line. srcU += aSource.mCbCrStride; srcV += aSource.mCbCrStride; destU += aDestination.mCbCrStride; destV += aDestination.mCbCrStride; } } inline static int Align(int aX, int aAlign) { return (aX + aAlign - 1) & ~(aAlign - 1); } // Venus formats are doucmented in kernel/include/media/msm_media_info.h: // * Y_Stride : Width aligned to 128 // * UV_Stride : Width aligned to 128 // * Y_Scanlines: Height aligned to 32 // * UV_Scanlines: Height/2 aligned to 16 // * Total size = align((Y_Stride * Y_Scanlines // * + UV_Stride * UV_Scanlines + 4096), 4096) static void CopyVenus(uint8_t* aSrc, uint8_t* aDest, uint32_t aWidth, uint32_t aHeight) { size_t yStride = Align(aWidth, 128); uint8_t* s = aSrc; uint8_t* d = aDest; for (size_t i = 0; i < aHeight; i++) { memcpy(d, s, aWidth); s += yStride; d += yStride; } size_t uvStride = yStride; size_t uvLines = (aHeight + 1) / 2; size_t ySize = yStride * Align(aHeight, 32); s = aSrc + ySize; d = aDest + ySize; for (size_t i = 0; i < uvLines; i++) { memcpy(d, s, aWidth); s += uvStride; d += uvStride; } } static void CopyGraphicBuffer(sp& aSource, sp& aDestination) { void* srcPtr = nullptr; aSource->lock(GraphicBuffer::USAGE_SW_READ_OFTEN, &srcPtr); void* destPtr = nullptr; aDestination->lock(GraphicBuffer::USAGE_SW_WRITE_OFTEN, &destPtr); MOZ_ASSERT(srcPtr && destPtr); // Build PlanarYCbCrData for source buffer. PlanarYCbCrData srcData; switch (aSource->getPixelFormat()) { case HAL_PIXEL_FORMAT_YV12: { // Android YV12 format is defined in system/core/include/system/graphics.h srcData.mYChannel = static_cast(srcPtr); srcData.mYSkip = 0; srcData.mYSize.width = aSource->getWidth(); srcData.mYSize.height = aSource->getHeight(); srcData.mYStride = aSource->getStride(); // 4:2:0. srcData.mCbCrSize.width = srcData.mYSize.width / 2; srcData.mCbCrSize.height = srcData.mYSize.height / 2; srcData.mCrChannel = srcData.mYChannel + (srcData.mYStride * srcData.mYSize.height); // Aligned to 16 bytes boundary. srcData.mCbCrStride = Align(srcData.mYStride / 2, 16); srcData.mCrSkip = 0; srcData.mCbChannel = srcData.mCrChannel + (srcData.mCbCrStride * srcData.mCbCrSize.height); srcData.mCbSkip = 0; // Build PlanarYCbCrData for destination buffer. PlanarYCbCrData destData; destData.mYChannel = static_cast(destPtr); destData.mYSkip = 0; destData.mYSize.width = aDestination->getWidth(); destData.mYSize.height = aDestination->getHeight(); destData.mYStride = aDestination->getStride(); // 4:2:0. destData.mCbCrSize.width = destData.mYSize.width / 2; destData.mCbCrSize.height = destData.mYSize.height / 2; destData.mCrChannel = destData.mYChannel + (destData.mYStride * destData.mYSize.height); // Aligned to 16 bytes boundary. destData.mCbCrStride = Align(destData.mYStride / 2, 16); destData.mCrSkip = 0; destData.mCbChannel = destData.mCrChannel + (destData.mCbCrStride * destData.mCbCrSize.height); destData.mCbSkip = 0; CopyYUV(srcData, destData); break; } case GrallocImage::HAL_PIXEL_FORMAT_YCbCr_420_SP_VENUS: CopyVenus(static_cast(srcPtr), static_cast(destPtr), aSource->getWidth(), aSource->getHeight()); break; default: NS_ERROR("Unsupported input gralloc image type. Should never be here."); } aSource->unlock(); aDestination->unlock(); } already_AddRefed GonkVideoDecoderManager::CreateVideoDataFromGraphicBuffer(MediaBuffer* aSource, gfx::IntRect& aPicture) { sp srcBuffer(aSource->graphicBuffer()); RefPtr textureClient; if (mNeedsCopyBuffer) { // Copy buffer contents for bug 1199809. if (!mCopyAllocator) { RefPtr bridge = layers::ImageBridgeChild::GetSingleton(); mCopyAllocator = new TextureClientRecycleAllocator(bridge); } if (!mCopyAllocator) { GVDM_LOG("Create buffer allocator failed!"); return nullptr; } gfx::IntSize size(srcBuffer->getWidth(), srcBuffer->getHeight()); GonkTextureClientAllocationHelper helper(srcBuffer->getPixelFormat(), size); textureClient = mCopyAllocator->CreateOrRecycle(helper); if (!textureClient) { GVDM_LOG("Copy buffer allocation failed!"); return nullptr; } sp destBuffer = static_cast(textureClient->GetInternalData())->GetGraphicBuffer(); CopyGraphicBuffer(srcBuffer, destBuffer); } else { textureClient = mNativeWindow->getTextureClientFromBuffer(srcBuffer.get()); textureClient->SetRecycleCallback(GonkVideoDecoderManager::RecycleCallback, this); static_cast(textureClient->GetInternalData())->SetMediaBuffer(aSource); } RefPtr data = VideoData::CreateAndCopyIntoTextureClient(mConfig, 0, // Filled later by caller. 0, // Filled later by caller. 1, // No way to pass sample duration from muxer to // OMX codec, so we hardcode the duration here. textureClient, false, // Filled later by caller. -1, aPicture); return data.forget(); } already_AddRefed GonkVideoDecoderManager::CreateVideoDataFromDataBuffer(MediaBuffer* aSource, gfx::IntRect& aPicture) { if (!aSource->data()) { GVDM_LOG("No data in Video Buffer!"); return nullptr; } uint8_t *yuv420p_buffer = (uint8_t *)aSource->data(); int32_t stride = mFrameInfo.mStride; int32_t slice_height = mFrameInfo.mSliceHeight; // Converts to OMX_COLOR_FormatYUV420Planar if (mFrameInfo.mColorFormat != OMX_COLOR_FormatYUV420Planar) { ARect crop; crop.top = 0; crop.bottom = mFrameInfo.mHeight; crop.left = 0; crop.right = mFrameInfo.mWidth; yuv420p_buffer = GetColorConverterBuffer(mFrameInfo.mWidth, mFrameInfo.mHeight); if (mColorConverter.convertDecoderOutputToI420(aSource->data(), mFrameInfo.mWidth, mFrameInfo.mHeight, crop, yuv420p_buffer) != OK) { GVDM_LOG("Color conversion failed!"); return nullptr; } stride = mFrameInfo.mWidth; slice_height = mFrameInfo.mHeight; } size_t yuv420p_y_size = stride * slice_height; size_t yuv420p_u_size = ((stride + 1) / 2) * ((slice_height + 1) / 2); uint8_t *yuv420p_y = yuv420p_buffer; uint8_t *yuv420p_u = yuv420p_y + yuv420p_y_size; uint8_t *yuv420p_v = yuv420p_u + yuv420p_u_size; VideoData::YCbCrBuffer b; b.mPlanes[0].mData = yuv420p_y; b.mPlanes[0].mWidth = mFrameInfo.mWidth; b.mPlanes[0].mHeight = mFrameInfo.mHeight; b.mPlanes[0].mStride = stride; b.mPlanes[0].mOffset = 0; b.mPlanes[0].mSkip = 0; b.mPlanes[1].mData = yuv420p_u; b.mPlanes[1].mWidth = (mFrameInfo.mWidth + 1) / 2; b.mPlanes[1].mHeight = (mFrameInfo.mHeight + 1) / 2; b.mPlanes[1].mStride = (stride + 1) / 2; b.mPlanes[1].mOffset = 0; b.mPlanes[1].mSkip = 0; b.mPlanes[2].mData = yuv420p_v; b.mPlanes[2].mWidth =(mFrameInfo.mWidth + 1) / 2; b.mPlanes[2].mHeight = (mFrameInfo.mHeight + 1) / 2; b.mPlanes[2].mStride = (stride + 1) / 2; b.mPlanes[2].mOffset = 0; b.mPlanes[2].mSkip = 0; RefPtr data = VideoData::CreateAndCopyData(mConfig, mImageContainer, 0, // Filled later by caller. 0, // Filled later by caller. 1, // We don't know the duration. b, 0, // Filled later by caller. -1, aPicture); return data.forget(); } bool GonkVideoDecoderManager::SetVideoFormat() { // read video metadata from MediaCodec sp codecFormat; if (mDecoder->getOutputFormat(&codecFormat) == OK) { AString mime; int32_t width = 0; int32_t height = 0; int32_t stride = 0; int32_t slice_height = 0; int32_t color_format = 0; int32_t crop_left = 0; int32_t crop_top = 0; int32_t crop_right = 0; int32_t crop_bottom = 0; if (!codecFormat->findString("mime", &mime) || !codecFormat->findInt32("width", &width) || !codecFormat->findInt32("height", &height) || !codecFormat->findInt32("stride", &stride) || !codecFormat->findInt32("slice-height", &slice_height) || !codecFormat->findInt32("color-format", &color_format) || !codecFormat->findRect("crop", &crop_left, &crop_top, &crop_right, &crop_bottom)) { GVDM_LOG("Failed to find values"); return false; } mFrameInfo.mWidth = width; mFrameInfo.mHeight = height; mFrameInfo.mStride = stride; mFrameInfo.mSliceHeight = slice_height; mFrameInfo.mColorFormat = color_format; nsIntSize displaySize(width, height); if (!IsValidVideoRegion(mConfig.mDisplay, mConfig.ScaledImageRect(width, height), displaySize)) { GVDM_LOG("It is not a valid region"); return false; } return true; } GVDM_LOG("Fail to get output format"); return false; } // Blocks until decoded sample is produced by the deoder. nsresult GonkVideoDecoderManager::Output(int64_t aStreamOffset, RefPtr& aOutData) { aOutData = nullptr; status_t err; if (mDecoder == nullptr) { GVDM_LOG("Decoder is not inited"); return NS_ERROR_UNEXPECTED; } MediaBuffer* outputBuffer = nullptr; err = mDecoder->Output(&outputBuffer, READ_OUTPUT_BUFFER_TIMEOUT_US); switch (err) { case OK: { RefPtr data; nsresult rv = CreateVideoData(outputBuffer, aStreamOffset, getter_AddRefs(data)); if (rv == NS_ERROR_NOT_AVAILABLE) { // Decoder outputs a empty video buffer, try again return NS_ERROR_NOT_AVAILABLE; } else if (rv != NS_OK || data == nullptr) { GVDM_LOG("Failed to create VideoData"); return NS_ERROR_UNEXPECTED; } aOutData = data; return NS_OK; } case android::INFO_FORMAT_CHANGED: { // If the format changed, update our cached info. GVDM_LOG("Decoder format changed"); if (!SetVideoFormat()) { return NS_ERROR_UNEXPECTED; } return Output(aStreamOffset, aOutData); } case android::INFO_OUTPUT_BUFFERS_CHANGED: { if (mDecoder->UpdateOutputBuffers()) { return Output(aStreamOffset, aOutData); } GVDM_LOG("Fails to update output buffers!"); return NS_ERROR_FAILURE; } case -EAGAIN: { // GVDM_LOG("Need to try again!"); return NS_ERROR_NOT_AVAILABLE; } case android::ERROR_END_OF_STREAM: { GVDM_LOG("Got the EOS frame!"); RefPtr data; nsresult rv = CreateVideoData(outputBuffer, aStreamOffset, getter_AddRefs(data)); if (rv == NS_ERROR_NOT_AVAILABLE) { // For EOS, no need to do any thing. return NS_ERROR_ABORT; } if (rv != NS_OK || data == nullptr) { GVDM_LOG("Failed to create video data"); return NS_ERROR_UNEXPECTED; } aOutData = data; return NS_ERROR_ABORT; } case -ETIMEDOUT: { GVDM_LOG("Timeout. can try again next time"); return NS_ERROR_UNEXPECTED; } default: { GVDM_LOG("Decoder failed, err=%d", err); return NS_ERROR_UNEXPECTED; } } return NS_OK; } void GonkVideoDecoderManager::codecReserved() { if (mInitPromise.IsEmpty()) { return; } GVDM_LOG("codecReserved"); sp format = new AMessage; sp surface; status_t rv = OK; // Fixed values GVDM_LOG("Configure video mime type: %s, width:%d, height:%d", mConfig.mMimeType.get(), mConfig.mImage.width, mConfig.mImage.height); format->setString("mime", mConfig.mMimeType.get()); format->setInt32("width", mConfig.mImage.width); format->setInt32("height", mConfig.mImage.height); // Set the "moz-use-undequeued-bufs" to use the undeque buffers to accelerate // the video decoding. format->setInt32("moz-use-undequeued-bufs", 1); if (mNativeWindow != nullptr) { #if ANDROID_VERSION >= 21 surface = new Surface(mGraphicBufferProducer); #else surface = new Surface(mNativeWindow->getBufferQueue()); #endif } mDecoder->configure(format, surface, nullptr, 0); mDecoder->Prepare(); if (mConfig.mMimeType.EqualsLiteral("video/mp4v-es")) { rv = mDecoder->Input(mConfig.mCodecSpecificConfig->Elements(), mConfig.mCodecSpecificConfig->Length(), 0, android::MediaCodec::BUFFER_FLAG_CODECCONFIG, CODECCONFIG_TIMEOUT_US); } if (rv != OK) { GVDM_LOG("Failed to configure codec!!!!"); mInitPromise.Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); return; } mInitPromise.Resolve(TrackType::kVideoTrack, __func__); } void GonkVideoDecoderManager::codecCanceled() { GVDM_LOG("codecCanceled"); mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__); } // Called on GonkDecoderManager::mTaskLooper thread. void GonkVideoDecoderManager::onMessageReceived(const sp &aMessage) { switch (aMessage->what()) { case kNotifyPostReleaseBuffer: { ReleaseAllPendingVideoBuffers(); break; } default: { GonkDecoderManager::onMessageReceived(aMessage); break; } } } uint8_t * GonkVideoDecoderManager::GetColorConverterBuffer(int32_t aWidth, int32_t aHeight) { // Allocate a temporary YUV420Planer buffer. size_t yuv420p_y_size = aWidth * aHeight; size_t yuv420p_u_size = ((aWidth + 1) / 2) * ((aHeight + 1) / 2); size_t yuv420p_v_size = yuv420p_u_size; size_t yuv420p_size = yuv420p_y_size + yuv420p_u_size + yuv420p_v_size; if (mColorConverterBufferSize != yuv420p_size) { mColorConverterBuffer = MakeUnique(yuv420p_size); mColorConverterBufferSize = yuv420p_size; } return mColorConverterBuffer.get(); } /* static */ void GonkVideoDecoderManager::RecycleCallback(TextureClient* aClient, void* aClosure) { MOZ_ASSERT(aClient && !aClient->IsDead()); GonkVideoDecoderManager* videoManager = static_cast(aClosure); GrallocTextureData* client = static_cast(aClient->GetInternalData()); aClient->ClearRecycleCallback(); FenceHandle handle = aClient->GetAndResetReleaseFenceHandle(); videoManager->PostReleaseVideoBuffer(client->GetMediaBuffer(), handle); } void GonkVideoDecoderManager::PostReleaseVideoBuffer( android::MediaBuffer *aBuffer, FenceHandle aReleaseFence) { { MutexAutoLock autoLock(mPendingReleaseItemsLock); if (aBuffer) { mPendingReleaseItems.AppendElement(ReleaseItem(aBuffer, aReleaseFence)); } } sp notify = new AMessage(kNotifyPostReleaseBuffer, id()); notify->post(); } void GonkVideoDecoderManager::ReleaseAllPendingVideoBuffers() { nsTArray releasingItems; { MutexAutoLock autoLock(mPendingReleaseItemsLock); releasingItems.AppendElements(mPendingReleaseItems); mPendingReleaseItems.Clear(); } // Free all pending video buffers without holding mPendingReleaseItemsLock. size_t size = releasingItems.Length(); for (size_t i = 0; i < size; i++) { RefPtr fdObj = releasingItems[i].mReleaseFence.GetAndResetFdObj(); sp fence = new Fence(fdObj->GetAndResetFd()); fence->waitForever("GonkVideoDecoderManager"); mDecoder->ReleaseMediaBuffer(releasingItems[i].mBuffer); } releasingItems.Clear(); } } // namespace mozilla