diff -r 000000000000 -r 6474c204b198 content/media/omx/OmxDecoder.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/content/media/omx/OmxDecoder.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,1128 @@ +/* -*- 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 +#include + +#include "base/basictypes.h" +#include +#include +#include +#include +#include +#include +#include +#include +#if MOZ_WIDGET_GONK && ANDROID_VERSION >= 17 +#include +#endif + +#include "mozilla/layers/GrallocTextureClient.h" +#include "mozilla/layers/TextureClient.h" +#include "mozilla/Preferences.h" +#include "mozilla/Types.h" +#include "mozilla/Monitor.h" +#include "nsMimeTypes.h" +#include "MPAPI.h" +#include "prlog.h" + +#include "GonkNativeWindow.h" +#include "GonkNativeWindowClient.h" +#include "OMXCodecProxy.h" +#include "OmxDecoder.h" +#include "nsISeekableStream.h" + +#ifdef PR_LOGGING +PRLogModuleInfo *gOmxDecoderLog; +#define LOG(type, msg...) PR_LOG(gOmxDecoderLog, type, (msg)) +#else +#define LOG(x...) +#endif + +using namespace MPAPI; +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::layers; + +namespace mozilla { + +class ReleaseOmxDecoderRunnable : public nsRunnable +{ +public: + ReleaseOmxDecoderRunnable(const android::sp& aOmxDecoder) + : mOmxDecoder(aOmxDecoder) + { + } + + NS_METHOD Run() MOZ_OVERRIDE + { + MOZ_ASSERT(NS_IsMainThread()); + mOmxDecoder = nullptr; // release OmxDecoder + return NS_OK; + } + +private: + android::sp mOmxDecoder; +}; + +class OmxDecoderProcessCachedDataTask : public Task +{ +public: + OmxDecoderProcessCachedDataTask(android::OmxDecoder* aOmxDecoder, int64_t aOffset) + : mOmxDecoder(aOmxDecoder), + mOffset(aOffset) + { } + + void Run() + { + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ASSERT(mOmxDecoder.get()); + int64_t rem = mOmxDecoder->ProcessCachedData(mOffset, false); + + if (rem <= 0) { + ReleaseOmxDecoderRunnable* r = new ReleaseOmxDecoderRunnable(mOmxDecoder); + mOmxDecoder.clear(); + NS_DispatchToMainThread(r); + } + } + +private: + android::sp mOmxDecoder; + int64_t mOffset; +}; + +// When loading an MP3 stream from a file, we need to parse the file's +// content to find its duration. Reading files of 100 MiB or more can +// delay the player app noticably, so the file is read and decoded in +// smaller chunks. +// +// We first read on the decode thread, but parsing must be done on the +// main thread. After we read the file's initial MiBs in the decode +// thread, an instance of this class is scheduled to the main thread for +// parsing the MP3 stream. The decode thread waits until it has finished. +// +// If there is more data available from the file, the runnable dispatches +// a task to the IO thread for retrieving the next chunk of data, and +// the IO task dispatches a runnable to the main thread for parsing the +// data. This goes on until all of the MP3 file has been parsed. + +class OmxDecoderNotifyDataArrivedRunnable : public nsRunnable +{ +public: + OmxDecoderNotifyDataArrivedRunnable(android::OmxDecoder* aOmxDecoder, + const char* aBuffer, uint64_t aLength, + int64_t aOffset, uint64_t aFullLength) + : mOmxDecoder(aOmxDecoder), + mBuffer(aBuffer), + mLength(aLength), + mOffset(aOffset), + mFullLength(aFullLength), + mCompletedMonitor("OmxDecoderNotifyDataArrived.mCompleted"), + mCompleted(false) + { + MOZ_ASSERT(mOmxDecoder.get()); + MOZ_ASSERT(mBuffer.get() || !mLength); + } + + NS_IMETHOD Run() + { + NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); + + NotifyDataArrived(); + Completed(); + + return NS_OK; + } + + void WaitForCompletion() + { + MOZ_ASSERT(!NS_IsMainThread()); + + MonitorAutoLock mon(mCompletedMonitor); + if (!mCompleted) { + mCompletedMonitor.Wait(); + } + } + +private: + void NotifyDataArrived() + { + const char* buffer = mBuffer.get(); + + while (mLength) { + uint32_t length = std::min(mLength, UINT32_MAX); + bool success = mOmxDecoder->NotifyDataArrived(buffer, mLength, + mOffset); + if (!success) { + return; + } + + buffer += length; + mLength -= length; + mOffset += length; + } + + if (mOffset < mFullLength) { + // We cannot read data in the main thread because it + // might block for too long. Instead we post an IO task + // to the IO thread if there is more data available. + XRE_GetIOMessageLoop()->PostTask(FROM_HERE, + new OmxDecoderProcessCachedDataTask(mOmxDecoder.get(), mOffset)); + } + } + + // Call this function at the end of Run() to notify waiting + // threads. + void Completed() + { + MonitorAutoLock mon(mCompletedMonitor); + MOZ_ASSERT(!mCompleted); + mCompleted = true; + mCompletedMonitor.Notify(); + } + + android::sp mOmxDecoder; + nsAutoArrayPtr mBuffer; + uint64_t mLength; + int64_t mOffset; + uint64_t mFullLength; + + Monitor mCompletedMonitor; + bool mCompleted; +}; + +} + +namespace android { + +MediaStreamSource::MediaStreamSource(MediaResource *aResource, + AbstractMediaDecoder *aDecoder) : + mResource(aResource), mDecoder(aDecoder) +{ +} + +MediaStreamSource::~MediaStreamSource() +{ +} + +status_t MediaStreamSource::initCheck() const +{ + return OK; +} + +ssize_t MediaStreamSource::readAt(off64_t offset, void *data, size_t size) +{ + char *ptr = static_cast(data); + size_t todo = size; + while (todo > 0) { + Mutex::Autolock autoLock(mLock); + uint32_t bytesRead; + if ((offset != mResource->Tell() && + NS_FAILED(mResource->Seek(nsISeekableStream::NS_SEEK_SET, offset))) || + NS_FAILED(mResource->Read(ptr, todo, &bytesRead))) { + return ERROR_IO; + } + + if (bytesRead == 0) { + return size - todo; + } + + offset += bytesRead; + todo -= bytesRead; + ptr += bytesRead; + } + return size; +} + +status_t MediaStreamSource::getSize(off64_t *size) +{ + uint64_t length = mResource->GetLength(); + if (length == static_cast(-1)) + return ERROR_UNSUPPORTED; + + *size = length; + + return OK; +} + +} // namespace android + +using namespace android; + +OmxDecoder::OmxDecoder(MediaResource *aResource, + AbstractMediaDecoder *aDecoder) : + mDecoder(aDecoder), + mResource(aResource), + mDisplayWidth(0), + mDisplayHeight(0), + mVideoWidth(0), + mVideoHeight(0), + mVideoColorFormat(0), + mVideoStride(0), + mVideoSliceHeight(0), + mVideoRotation(0), + mAudioChannels(-1), + mAudioSampleRate(-1), + mDurationUs(-1), + mMP3FrameParser(aResource->GetLength()), + mIsMp3(false), + mVideoBuffer(nullptr), + mAudioBuffer(nullptr), + mIsVideoSeeking(false), + mAudioMetadataRead(false), + mAudioPaused(false), + mVideoPaused(false) +{ + mLooper = new ALooper; + mLooper->setName("OmxDecoder"); + + mReflector = new AHandlerReflector(this); + // Register AMessage handler to ALooper. + mLooper->registerHandler(mReflector); + // Start ALooper thread. + mLooper->start(); +} + +OmxDecoder::~OmxDecoder() +{ + MOZ_ASSERT(NS_IsMainThread()); + + ReleaseMediaResources(); + + // unregister AMessage handler from ALooper. + mLooper->unregisterHandler(mReflector->id()); + // Stop ALooper thread. + mLooper->stop(); +} + +void OmxDecoder::statusChanged() +{ + sp notify = + new AMessage(kNotifyStatusChanged, mReflector->id()); + // post AMessage to OmxDecoder via ALooper. + notify->post(); +} + +static sp sOMX = nullptr; +static sp GetOMX() +{ + if(sOMX.get() == nullptr) { + sOMX = new OMX; + } + return sOMX; +} + +bool OmxDecoder::Init(sp& extractor) { +#ifdef PR_LOGGING + if (!gOmxDecoderLog) { + gOmxDecoderLog = PR_NewLogModule("OmxDecoder"); + } +#endif + + const char* extractorMime; + sp meta = extractor->getMetaData(); + if (meta->findCString(kKeyMIMEType, &extractorMime) && !strcasecmp(extractorMime, AUDIO_MP3)) { + mIsMp3 = true; + } + + ssize_t audioTrackIndex = -1; + ssize_t videoTrackIndex = -1; + + for (size_t i = 0; i < extractor->countTracks(); ++i) { + sp meta = extractor->getTrackMetaData(i); + + int32_t bitRate; + if (!meta->findInt32(kKeyBitRate, &bitRate)) + bitRate = 0; + + const char *mime; + if (!meta->findCString(kKeyMIMEType, &mime)) { + continue; + } + + if (videoTrackIndex == -1 && !strncasecmp(mime, "video/", 6)) { + videoTrackIndex = i; + } else if (audioTrackIndex == -1 && !strncasecmp(mime, "audio/", 6)) { + audioTrackIndex = i; + } + } + + if (videoTrackIndex == -1 && audioTrackIndex == -1) { + NS_WARNING("OMX decoder could not find video or audio tracks"); + return false; + } + + mResource->SetReadMode(MediaCacheStream::MODE_PLAYBACK); + + if (videoTrackIndex != -1) { + mVideoTrack = extractor->getTrack(videoTrackIndex); + } + + if (audioTrackIndex != -1) { + mAudioTrack = extractor->getTrack(audioTrackIndex); + +#ifdef MOZ_AUDIO_OFFLOAD + // mAudioTrack is be used by OMXCodec. For offloaded audio track, using same + // object gives undetermined behavior. So get a new track + mAudioOffloadTrack = extractor->getTrack(audioTrackIndex); +#endif + } + return true; +} + +bool OmxDecoder::TryLoad() { + + if (!AllocateMediaResources()) { + return false; + } + + //check if video is waiting resources + if (mVideoSource.get()) { + if (mVideoSource->IsWaitingResources()) { + return true; + } + } + + // calculate duration + int64_t totalDurationUs = 0; + int64_t durationUs = 0; + if (mVideoTrack.get() && mVideoTrack->getFormat()->findInt64(kKeyDuration, &durationUs)) { + if (durationUs > totalDurationUs) + totalDurationUs = durationUs; + } + if (mAudioTrack.get()) { + durationUs = -1; + const char* audioMime; + sp meta = mAudioTrack->getFormat(); + + if (mIsMp3) { + // Feed MP3 parser with cached data. Local files will be fully + // cached already, network streams will update with sucessive + // calls to NotifyDataArrived. + if (ProcessCachedData(0, true) >= 0) { + durationUs = mMP3FrameParser.GetDuration(); + if (durationUs > totalDurationUs) { + totalDurationUs = durationUs; + } + } + } + if ((durationUs == -1) && meta->findInt64(kKeyDuration, &durationUs)) { + if (durationUs > totalDurationUs) { + totalDurationUs = durationUs; + } + } + } + mDurationUs = totalDurationUs; + + // read video metadata + if (mVideoSource.get() && !SetVideoFormat()) { + NS_WARNING("Couldn't set OMX video format"); + return false; + } + + // read audio metadata + if (mAudioSource.get()) { + // To reliably get the channel and sample rate data we need to read from the + // audio source until we get a INFO_FORMAT_CHANGE status + status_t err = mAudioSource->read(&mAudioBuffer); + if (err != INFO_FORMAT_CHANGED) { + if (err != OK) { + NS_WARNING("Couldn't read audio buffer from OMX decoder"); + return false; + } + sp meta = mAudioSource->getFormat(); + if (!meta->findInt32(kKeyChannelCount, &mAudioChannels) || + !meta->findInt32(kKeySampleRate, &mAudioSampleRate)) { + NS_WARNING("Couldn't get audio metadata from OMX decoder"); + return false; + } + mAudioMetadataRead = true; + } + else if (!SetAudioFormat()) { + NS_WARNING("Couldn't set audio format"); + return false; + } + } + + return true; +} + +bool OmxDecoder::IsDormantNeeded() +{ + if (mVideoTrack.get()) { + return true; + } + return false; +} + +bool OmxDecoder::IsWaitingMediaResources() +{ + if (mVideoSource.get()) { + return mVideoSource->IsWaitingResources(); + } + return false; +} + +static bool isInEmulator() +{ + char propQemu[PROPERTY_VALUE_MAX]; + property_get("ro.kernel.qemu", propQemu, ""); + return !strncmp(propQemu, "1", 1); +} + +bool OmxDecoder::AllocateMediaResources() +{ + // OMXClient::connect() always returns OK and abort's fatally if + // it can't connect. + OMXClient client; + DebugOnly err = client.connect(); + NS_ASSERTION(err == OK, "Failed to connect to OMX in mediaserver."); + sp omx = client.interface(); + + if ((mVideoTrack != nullptr) && (mVideoSource == nullptr)) { + mNativeWindow = new GonkNativeWindow(); +#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17 + mNativeWindowClient = new GonkNativeWindowClient(mNativeWindow->getBufferQueue()); +#else + mNativeWindowClient = new GonkNativeWindowClient(mNativeWindow); +#endif + + // Experience with OMX codecs is that only the HW decoders are + // worth bothering with, at least on the platforms where this code + // is currently used, and for formats this code is currently used + // for (h.264). So if we don't get a hardware decoder, just give + // up. + int flags = kHardwareCodecsOnly; + + if (isInEmulator()) { + // If we are in emulator, allow to fall back to software. + flags = 0; + } + mVideoSource = + OMXCodecProxy::Create(omx, + mVideoTrack->getFormat(), + false, // decoder + mVideoTrack, + nullptr, + flags, + mNativeWindowClient); + if (mVideoSource == nullptr) { + NS_WARNING("Couldn't create OMX video source"); + return false; + } else { + sp listener = this; + mVideoSource->setEventListener(listener); + mVideoSource->requestResource(); + } + } + + if ((mAudioTrack != nullptr) && (mAudioSource == nullptr)) { + const char *audioMime = nullptr; + sp meta = mAudioTrack->getFormat(); + if (!meta->findCString(kKeyMIMEType, &audioMime)) { + return false; + } + if (!strcasecmp(audioMime, "audio/raw")) { + mAudioSource = mAudioTrack; + } else { + // try to load hardware codec in mediaserver process. + int flags = kHardwareCodecsOnly; + mAudioSource = OMXCodec::Create(omx, + mAudioTrack->getFormat(), + false, // decoder + mAudioTrack, + nullptr, + flags); + } + + if (mAudioSource == nullptr) { + // try to load software codec in this process. + int flags = kSoftwareCodecsOnly; + mAudioSource = OMXCodec::Create(GetOMX(), + mAudioTrack->getFormat(), + false, // decoder + mAudioTrack, + nullptr, + flags); + if (mAudioSource == nullptr) { + NS_WARNING("Couldn't create OMX audio source"); + return false; + } + } + if (mAudioSource->start() != OK) { + NS_WARNING("Couldn't start OMX audio source"); + mAudioSource.clear(); + return false; + } + } + return true; +} + + +void OmxDecoder::ReleaseMediaResources() { + { + // Free all pending video buffers. + Mutex::Autolock autoLock(mSeekLock); + ReleaseAllPendingVideoBuffersLocked(); + } + + ReleaseVideoBuffer(); + ReleaseAudioBuffer(); + + if (mVideoSource.get()) { + mVideoSource->stop(); + mVideoSource.clear(); + } + + if (mAudioSource.get()) { + mAudioSource->stop(); + mAudioSource.clear(); + } + + mNativeWindowClient.clear(); + mNativeWindow.clear(); +} + +bool OmxDecoder::SetVideoFormat() { + const char *componentName; + + if (!mVideoSource->getFormat()->findInt32(kKeyWidth, &mVideoWidth) || + !mVideoSource->getFormat()->findInt32(kKeyHeight, &mVideoHeight) || + !mVideoSource->getFormat()->findCString(kKeyDecoderComponent, &componentName) || + !mVideoSource->getFormat()->findInt32(kKeyColorFormat, &mVideoColorFormat) ) { + return false; + } + + if (!mVideoTrack.get() || !mVideoTrack->getFormat()->findInt32(kKeyDisplayWidth, &mDisplayWidth)) { + mDisplayWidth = mVideoWidth; + NS_WARNING("display width not available, assuming width"); + } + + if (!mVideoTrack.get() || !mVideoTrack->getFormat()->findInt32(kKeyDisplayHeight, &mDisplayHeight)) { + mDisplayHeight = mVideoHeight; + NS_WARNING("display height not available, assuming height"); + } + + if (!mVideoSource->getFormat()->findInt32(kKeyStride, &mVideoStride)) { + mVideoStride = mVideoWidth; + NS_WARNING("stride not available, assuming width"); + } + + if (!mVideoSource->getFormat()->findInt32(kKeySliceHeight, &mVideoSliceHeight)) { + mVideoSliceHeight = mVideoHeight; + NS_WARNING("slice height not available, assuming height"); + } + + // Since ICS, valid video side is caluculated from kKeyCropRect. + // kKeyWidth means decoded video buffer width. + // kKeyHeight means decoded video buffer height. + // On some hardwares, decoded video buffer and valid video size are different. + int32_t crop_left, crop_top, crop_right, crop_bottom; + if (mVideoSource->getFormat()->findRect(kKeyCropRect, + &crop_left, + &crop_top, + &crop_right, + &crop_bottom)) { + mVideoWidth = crop_right - crop_left + 1; + mVideoHeight = crop_bottom - crop_top + 1; + } + + if (!mVideoSource->getFormat()->findInt32(kKeyRotation, &mVideoRotation)) { + mVideoRotation = 0; + NS_WARNING("rotation not available, assuming 0"); + } + + LOG(PR_LOG_DEBUG, "display width: %d display height %d width: %d height: %d component: %s format: %d stride: %d sliceHeight: %d rotation: %d", + mDisplayWidth, mDisplayHeight, mVideoWidth, mVideoHeight, componentName, + mVideoColorFormat, mVideoStride, mVideoSliceHeight, mVideoRotation); + + return true; +} + +bool OmxDecoder::SetAudioFormat() { + // If the format changed, update our cached info. + if (!mAudioSource->getFormat()->findInt32(kKeyChannelCount, &mAudioChannels) || + !mAudioSource->getFormat()->findInt32(kKeySampleRate, &mAudioSampleRate)) { + return false; + } + + LOG(PR_LOG_DEBUG, "channelCount: %d sampleRate: %d", + mAudioChannels, mAudioSampleRate); + + return true; +} + +void OmxDecoder::ReleaseDecoder() +{ + mDecoder = nullptr; +} + +bool OmxDecoder::NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset) +{ + if (!mAudioTrack.get() || !mIsMp3 || !mMP3FrameParser.IsMP3() || !mDecoder) { + return false; + } + + mMP3FrameParser.Parse(aBuffer, aLength, aOffset); + + int64_t durationUs = mMP3FrameParser.GetDuration(); + + if (durationUs != mDurationUs) { + mDurationUs = durationUs; + + MOZ_ASSERT(mDecoder); + ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); + mDecoder->UpdateEstimatedMediaDuration(mDurationUs); + } + + return true; +} + +void OmxDecoder::ReleaseVideoBuffer() { + if (mVideoBuffer) { + mVideoBuffer->release(); + mVideoBuffer = nullptr; + } +} + +void OmxDecoder::ReleaseAudioBuffer() { + if (mAudioBuffer) { + mAudioBuffer->release(); + mAudioBuffer = nullptr; + } +} + +void OmxDecoder::PlanarYUV420Frame(VideoFrame *aFrame, int64_t aTimeUs, void *aData, size_t aSize, bool aKeyFrame) { + void *y = aData; + void *u = static_cast(y) + mVideoStride * mVideoSliceHeight; + void *v = static_cast(u) + mVideoStride/2 * mVideoSliceHeight/2; + + aFrame->Set(aTimeUs, aKeyFrame, + aData, aSize, mVideoStride, mVideoSliceHeight, mVideoRotation, + y, mVideoStride, mVideoWidth, mVideoHeight, 0, 0, + u, mVideoStride/2, mVideoWidth/2, mVideoHeight/2, 0, 0, + v, mVideoStride/2, mVideoWidth/2, mVideoHeight/2, 0, 0); +} + +void OmxDecoder::CbYCrYFrame(VideoFrame *aFrame, int64_t aTimeUs, void *aData, size_t aSize, bool aKeyFrame) { + aFrame->Set(aTimeUs, aKeyFrame, + aData, aSize, mVideoStride, mVideoSliceHeight, mVideoRotation, + aData, mVideoStride, mVideoWidth, mVideoHeight, 1, 1, + aData, mVideoStride, mVideoWidth/2, mVideoHeight/2, 0, 3, + aData, mVideoStride, mVideoWidth/2, mVideoHeight/2, 2, 3); +} + +void OmxDecoder::SemiPlanarYUV420Frame(VideoFrame *aFrame, int64_t aTimeUs, void *aData, size_t aSize, bool aKeyFrame) { + void *y = aData; + void *uv = static_cast(y) + (mVideoStride * mVideoSliceHeight); + + aFrame->Set(aTimeUs, aKeyFrame, + aData, aSize, mVideoStride, mVideoSliceHeight, mVideoRotation, + y, mVideoStride, mVideoWidth, mVideoHeight, 0, 0, + uv, mVideoStride, mVideoWidth/2, mVideoHeight/2, 0, 1, + uv, mVideoStride, mVideoWidth/2, mVideoHeight/2, 1, 1); +} + +void OmxDecoder::SemiPlanarYVU420Frame(VideoFrame *aFrame, int64_t aTimeUs, void *aData, size_t aSize, bool aKeyFrame) { + SemiPlanarYUV420Frame(aFrame, aTimeUs, aData, aSize, aKeyFrame); + aFrame->Cb.mOffset = 1; + aFrame->Cr.mOffset = 0; +} + +bool OmxDecoder::ToVideoFrame(VideoFrame *aFrame, int64_t aTimeUs, void *aData, size_t aSize, bool aKeyFrame) { + const int OMX_QCOM_COLOR_FormatYVU420SemiPlanar = 0x7FA30C00; + + aFrame->mGraphicBuffer = nullptr; + + switch (mVideoColorFormat) { + case OMX_COLOR_FormatYUV420Planar: + PlanarYUV420Frame(aFrame, aTimeUs, aData, aSize, aKeyFrame); + break; + case OMX_COLOR_FormatCbYCrY: + CbYCrYFrame(aFrame, aTimeUs, aData, aSize, aKeyFrame); + break; + case OMX_COLOR_FormatYUV420SemiPlanar: + SemiPlanarYUV420Frame(aFrame, aTimeUs, aData, aSize, aKeyFrame); + break; + case OMX_QCOM_COLOR_FormatYVU420SemiPlanar: + SemiPlanarYVU420Frame(aFrame, aTimeUs, aData, aSize, aKeyFrame); + break; + default: + LOG(PR_LOG_DEBUG, "Unknown video color format %08x", mVideoColorFormat); + return false; + } + return true; +} + +bool OmxDecoder::ToAudioFrame(AudioFrame *aFrame, int64_t aTimeUs, void *aData, size_t aDataOffset, size_t aSize, int32_t aAudioChannels, int32_t aAudioSampleRate) +{ + aFrame->Set(aTimeUs, static_cast(aData) + aDataOffset, aSize, aAudioChannels, aAudioSampleRate); + return true; +} + +bool OmxDecoder::ReadVideo(VideoFrame *aFrame, int64_t aTimeUs, + bool aKeyframeSkip, bool aDoSeek) +{ + if (!mVideoSource.get()) + return false; + + ReleaseVideoBuffer(); + + status_t err; + + if (aDoSeek) { + { + Mutex::Autolock autoLock(mSeekLock); + mIsVideoSeeking = true; + } + MediaSource::ReadOptions options; + options.setSeekTo(aTimeUs, MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC); + err = mVideoSource->read(&mVideoBuffer, &options); + { + Mutex::Autolock autoLock(mSeekLock); + mIsVideoSeeking = false; + PostReleaseVideoBuffer(nullptr, FenceHandle()); + } + + aDoSeek = false; + } else { + err = mVideoSource->read(&mVideoBuffer); + } + + aFrame->mSize = 0; + + if (err == OK) { + int64_t timeUs; + int32_t unreadable; + int32_t keyFrame; + + if (!mVideoBuffer->meta_data()->findInt64(kKeyTime, &timeUs) ) { + NS_WARNING("OMX decoder did not return frame time"); + return false; + } + + if (!mVideoBuffer->meta_data()->findInt32(kKeyIsSyncFrame, &keyFrame)) { + keyFrame = 0; + } + + if (!mVideoBuffer->meta_data()->findInt32(kKeyIsUnreadable, &unreadable)) { + unreadable = 0; + } + + RefPtr textureClient; + if ((mVideoBuffer->graphicBuffer().get())) { + textureClient = mNativeWindow->getTextureClientFromBuffer(mVideoBuffer->graphicBuffer().get()); + } + + if (textureClient) { + // Manually increment reference count to keep MediaBuffer alive + // during TextureClient is in use. + mVideoBuffer->add_ref(); + GrallocTextureClientOGL* grallocClient = static_cast(textureClient.get()); + grallocClient->SetMediaBuffer(mVideoBuffer); + // Set recycle callback for TextureClient + textureClient->SetRecycleCallback(OmxDecoder::RecycleCallback, this); + + aFrame->mGraphicBuffer = textureClient; + aFrame->mRotation = mVideoRotation; + aFrame->mTimeUs = timeUs; + aFrame->mKeyFrame = keyFrame; + aFrame->Y.mWidth = mVideoWidth; + aFrame->Y.mHeight = mVideoHeight; + // Release to hold video buffer in OmxDecoder more. + // MediaBuffer's ref count is changed from 2 to 1. + ReleaseVideoBuffer(); + } else if (mVideoBuffer->range_length() > 0) { + char *data = static_cast(mVideoBuffer->data()) + mVideoBuffer->range_offset(); + size_t length = mVideoBuffer->range_length(); + + if (unreadable) { + LOG(PR_LOG_DEBUG, "video frame is unreadable"); + } + + if (!ToVideoFrame(aFrame, timeUs, data, length, keyFrame)) { + return false; + } + } + + if (aKeyframeSkip && timeUs < aTimeUs) { + aFrame->mShouldSkip = true; + } + } + else if (err == INFO_FORMAT_CHANGED) { + // If the format changed, update our cached info. + if (!SetVideoFormat()) { + return false; + } else { + return ReadVideo(aFrame, aTimeUs, aKeyframeSkip, aDoSeek); + } + } + else if (err == ERROR_END_OF_STREAM) { + return false; + } + else if (err == -ETIMEDOUT) { + LOG(PR_LOG_DEBUG, "OmxDecoder::ReadVideo timed out, will retry"); + return true; + } + else { + // UNKNOWN_ERROR is sometimes is used to mean "out of memory", but + // regardless, don't keep trying to decode if the decoder doesn't want to. + LOG(PR_LOG_DEBUG, "OmxDecoder::ReadVideo failed, err=%d", err); + return false; + } + + return true; +} + +bool OmxDecoder::ReadAudio(AudioFrame *aFrame, int64_t aSeekTimeUs) +{ + status_t err; + + if (mAudioMetadataRead && aSeekTimeUs == -1) { + // Use the data read into the buffer during metadata time + err = OK; + } + else { + ReleaseAudioBuffer(); + if (aSeekTimeUs != -1) { + MediaSource::ReadOptions options; + options.setSeekTo(aSeekTimeUs); + err = mAudioSource->read(&mAudioBuffer, &options); + } else { + err = mAudioSource->read(&mAudioBuffer); + } + } + mAudioMetadataRead = false; + + aSeekTimeUs = -1; + aFrame->mSize = 0; + + if (err == OK && mAudioBuffer && mAudioBuffer->range_length() != 0) { + int64_t timeUs; + if (!mAudioBuffer->meta_data()->findInt64(kKeyTime, &timeUs)) + return false; + + return ToAudioFrame(aFrame, timeUs, + mAudioBuffer->data(), + mAudioBuffer->range_offset(), + mAudioBuffer->range_length(), + mAudioChannels, mAudioSampleRate); + } + else if (err == INFO_FORMAT_CHANGED) { + // If the format changed, update our cached info. + if (!SetAudioFormat()) { + return false; + } else { + return ReadAudio(aFrame, aSeekTimeUs); + } + } + else if (err == ERROR_END_OF_STREAM) { + if (aFrame->mSize == 0) { + return false; + } + } + else if (err == -ETIMEDOUT) { + LOG(PR_LOG_DEBUG, "OmxDecoder::ReadAudio timed out, will retry"); + return true; + } + else if (err != OK) { + LOG(PR_LOG_DEBUG, "OmxDecoder::ReadAudio failed, err=%d", err); + return false; + } + + return true; +} + +nsresult OmxDecoder::Play() +{ + if (!mVideoPaused && !mAudioPaused) { + return NS_OK; + } + + if (mVideoPaused && mVideoSource.get() && mVideoSource->start() != OK) { + return NS_ERROR_UNEXPECTED; + } + mVideoPaused = false; + + if (mAudioPaused && mAudioSource.get() && mAudioSource->start() != OK) { + return NS_ERROR_UNEXPECTED; + } + mAudioPaused = false; + + return NS_OK; +} + +// AOSP didn't give implementation on OMXCodec::Pause() and not define +// OMXCodec::Start() should be called for resuming the decoding. Currently +// it is customized by a specific open source repository only. +// ToDo The one not supported OMXCodec::Pause() should return error code here, +// so OMXCodec::Start() doesn't be called again for resuming. But if someone +// implement the OMXCodec::Pause() and need a following OMXCodec::Read() with +// seek option (define in MediaSource.h) then it is still not supported here. +// We need to fix it until it is really happened. +void OmxDecoder::Pause() +{ + /* The implementation of OMXCodec::pause is flawed. + * OMXCodec::start will not restore from the paused state and result in + * buffer timeout which causes timeouts in mochitests. + * Since there is not power consumption problem in emulator, we will just + * return when running in emulator to fix timeouts in mochitests. + */ + if (isInEmulator()) { + return; + } + + if (mVideoPaused || mAudioPaused) { + return; + } + + if (mVideoSource.get() && mVideoSource->pause() == OK) { + mVideoPaused = true; + } + + if (mAudioSource.get() && mAudioSource->pause() == OK) { + mAudioPaused = true; + } +} + +// Called on ALooper thread. +void OmxDecoder::onMessageReceived(const sp &msg) +{ + switch (msg->what()) { + case kNotifyPostReleaseVideoBuffer: + { + Mutex::Autolock autoLock(mSeekLock); + // Free pending video buffers when OmxDecoder is not seeking video. + // If OmxDecoder is seeking video, the buffers are freed on seek exit. + if (!mIsVideoSeeking) { + ReleaseAllPendingVideoBuffersLocked(); + } + break; + } + + case kNotifyStatusChanged: + { + // Our decode may have acquired the hardware resource that it needs + // to start. Notify the state machine to resume loading metadata. + mDecoder->NotifyWaitingForResourcesStatusChanged(); + break; + } + + default: + TRESPASS(); + break; + } +} + +void OmxDecoder::PostReleaseVideoBuffer(MediaBuffer *aBuffer, const FenceHandle& aReleaseFenceHandle) +{ + { + Mutex::Autolock autoLock(mPendingVideoBuffersLock); + if (aBuffer) { + mPendingVideoBuffers.push(BufferItem(aBuffer, aReleaseFenceHandle)); + } + } + + sp notify = + new AMessage(kNotifyPostReleaseVideoBuffer, mReflector->id()); + // post AMessage to OmxDecoder via ALooper. + notify->post(); +} + +void OmxDecoder::ReleaseAllPendingVideoBuffersLocked() +{ + Vector releasingVideoBuffers; + { + Mutex::Autolock autoLock(mPendingVideoBuffersLock); + + int size = mPendingVideoBuffers.size(); + for (int i = 0; i < size; i++) { + releasingVideoBuffers.push(mPendingVideoBuffers[i]); + } + mPendingVideoBuffers.clear(); + } + // Free all pending video buffers without holding mPendingVideoBuffersLock. + int size = releasingVideoBuffers.size(); + for (int i = 0; i < size; i++) { + MediaBuffer *buffer; + buffer = releasingVideoBuffers[i].mMediaBuffer; +#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17 + android::sp fence; + int fenceFd = -1; + fence = releasingVideoBuffers[i].mReleaseFenceHandle.mFence; + if (fence.get() && fence->isValid()) { + fenceFd = fence->dup(); + } + MOZ_ASSERT(buffer->refcount() == 1); + // This code expect MediaBuffer's ref count is 1. + // Return gralloc buffer to ANativeWindow + ANativeWindow* window = static_cast(mNativeWindowClient.get()); + window->cancelBuffer(window, + buffer->graphicBuffer().get(), + fenceFd); + // Mark MediaBuffer as rendered. + // When gralloc buffer is directly returned to ANativeWindow, + // this mark is necesary. + sp metaData = buffer->meta_data(); + metaData->setInt32(kKeyRendered, 1); +#endif + // Return MediaBuffer to OMXCodec. + buffer->release(); + } + releasingVideoBuffers.clear(); +} + +/* static */ void +OmxDecoder::RecycleCallback(TextureClient* aClient, void* aClosure) +{ + OmxDecoder* decoder = static_cast(aClosure); + GrallocTextureClientOGL* client = static_cast(aClient); + + aClient->ClearRecycleCallback(); + decoder->PostReleaseVideoBuffer(client->GetMediaBuffer(), client->GetReleaseFenceHandle()); +} + +int64_t OmxDecoder::ProcessCachedData(int64_t aOffset, bool aWaitForCompletion) +{ + // We read data in chunks of 32 KiB. We can reduce this + // value if media, such as sdcards, is too slow. + // Because of SD card's slowness, need to keep sReadSize to small size. + // See Bug 914870. + static const int64_t sReadSize = 32 * 1024; + + NS_ASSERTION(!NS_IsMainThread(), "Should not be on main thread."); + + MOZ_ASSERT(mResource); + + int64_t resourceLength = mResource->GetCachedDataEnd(0); + NS_ENSURE_TRUE(resourceLength >= 0, -1); + + if (aOffset >= resourceLength) { + return 0; // Cache is empty, nothing to do + } + + int64_t bufferLength = std::min(resourceLength-aOffset, sReadSize); + + nsAutoArrayPtr buffer(new char[bufferLength]); + + nsresult rv = mResource->ReadFromCache(buffer.get(), aOffset, bufferLength); + NS_ENSURE_SUCCESS(rv, -1); + + nsRefPtr runnable( + new OmxDecoderNotifyDataArrivedRunnable(this, + buffer.forget(), + bufferLength, + aOffset, + resourceLength)); + + rv = NS_DispatchToMainThread(runnable.get()); + NS_ENSURE_SUCCESS(rv, -1); + + if (aWaitForCompletion) { + runnable->WaitForCompletion(); + } + + return resourceLength - aOffset - bufferLength; +}