diff --git a/content/media/MediaDecoderReader.h b/content/media/MediaDecoderReader.h index 243eb64e7431..f6636c9612d8 100644 --- a/content/media/MediaDecoderReader.h +++ b/content/media/MediaDecoderReader.h @@ -519,8 +519,8 @@ public: return functor.mResult; } - // Only used by WebMReader for now, so stub here rather than in every - // reader than inherits from MediaDecoderReader. + // Only used by WebMReader and MediaOmxReader for now, so stub here rather + // than in every reader than inherits from MediaDecoderReader. virtual void NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset) {} virtual MediaQueue& AudioQueue() { return mAudioQueue; } diff --git a/content/media/omx/MediaOmxReader.cpp b/content/media/omx/MediaOmxReader.cpp index cf27940e75c6..5fcb456a50ff 100644 --- a/content/media/omx/MediaOmxReader.cpp +++ b/content/media/omx/MediaOmxReader.cpp @@ -270,6 +270,15 @@ bool MediaOmxReader::DecodeVideoFrame(bool &aKeyframeSkip, return true; } +void MediaOmxReader::NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset) +{ + android::OmxDecoder *omxDecoder = mOmxDecoder.get(); + + if (omxDecoder) { + omxDecoder->NotifyDataArrived(aBuffer, aLength, aOffset); + } +} + bool MediaOmxReader::DecodeAudioData() { NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); diff --git a/content/media/omx/MediaOmxReader.h b/content/media/omx/MediaOmxReader.h index 13a36c595c62..db55f2d7fd67 100644 --- a/content/media/omx/MediaOmxReader.h +++ b/content/media/omx/MediaOmxReader.h @@ -40,6 +40,8 @@ public: virtual nsresult Init(MediaDecoderReader* aCloneDonor); virtual nsresult ResetDecode(); + virtual void NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset); + virtual bool DecodeAudioData(); virtual bool DecodeVideoFrame(bool &aKeyframeSkip, int64_t aTimeThreshold); diff --git a/content/media/omx/OmxDecoder.cpp b/content/media/omx/OmxDecoder.cpp index 011ef252119b..3899e0f92f5f 100644 --- a/content/media/omx/OmxDecoder.cpp +++ b/content/media/omx/OmxDecoder.cpp @@ -18,6 +18,8 @@ #include "mozilla/Preferences.h" #include "mozilla/Types.h" +#include "mozilla/Monitor.h" +#include "nsMimeTypes.h" #include "MPAPI.h" #include "prlog.h" @@ -37,6 +39,114 @@ using namespace MPAPI; using namespace mozilla; namespace mozilla { + +// When loading an MP3 stream from a file, we need to parse the file's +// content to find its duration. We must do this from within the decode +// thread, but parsing itself must be done in the main thread. +// +// After we read the file's content in the decode thread, an instance +// of this class is scheduled to the main thread for parsing the MP3 +// stream. We then wait until it has returned. + +class OmxDecoderNotifyDataArrivedRunnable : public nsRunnable +{ +public: + OmxDecoderNotifyDataArrivedRunnable(android::OmxDecoder* aOmxDecoder, const char* aBuffer, uint64_t aLength, int64_t aOffset) + : mOmxDecoder(aOmxDecoder), + mBuffer(aBuffer), + mLength(aLength), + mOffset(aOffset), + 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."); + + const char* buffer = mBuffer.get(); + + while (mLength) { + uint32_t length = std::min(mLength, UINT32_MAX); + mOmxDecoder->NotifyDataArrived(mBuffer.get(), mLength, mOffset); + + buffer += length; + mLength -= length; + mOffset += length; + } + + Completed(); + + return NS_OK; + } + + void WaitForCompletion() + { + MOZ_ASSERT(!NS_IsMainThread()); + + MonitorAutoLock mon(mCompletedMonitor); + if (!mCompleted) { + mCompletedMonitor.Wait(); + } + } + + static nsresult ProcessCachedData(android::OmxDecoder* aOmxDecoder); + +private: + // 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; + + Monitor mCompletedMonitor; + bool mCompleted; +}; + +nsresult OmxDecoderNotifyDataArrivedRunnable::ProcessCachedData(android::OmxDecoder* aOmxDecoder) +{ + MOZ_ASSERT(aOmxDecoder); + + NS_ASSERTION(!NS_IsMainThread(), "Should not be on main thread."); + + MediaResource* resource = aOmxDecoder->GetResource(); + MOZ_ASSERT(resource); + + int64_t length = resource->GetCachedDataEnd(0); + NS_ENSURE_TRUE(length >= 0, NS_ERROR_UNEXPECTED); + + if (!length) { + return NS_OK; // Cache is empty, nothing to do + } + + nsAutoArrayPtr buffer(new char[length]); + + nsresult rv = resource->ReadFromCache(buffer.get(), 0, length); + NS_ENSURE_SUCCESS(rv, rv); + + nsRefPtr runnable( + new OmxDecoderNotifyDataArrivedRunnable(aOmxDecoder, buffer.forget(), length, 0)); + + rv = NS_DispatchToMainThread(runnable.get()); + NS_ENSURE_SUCCESS(rv, rv); + + runnable->WaitForCompletion(); + + return NS_OK; +} + namespace layers { VideoGraphicBuffer::VideoGraphicBuffer(const android::wp aOmxDecoder, @@ -147,6 +257,7 @@ OmxDecoder::OmxDecoder(MediaResource *aResource, mAudioChannels(-1), mAudioSampleRate(-1), mDurationUs(-1), + mMP3FrameParser(aResource->GetLength()), mVideoBuffer(nullptr), mAudioBuffer(nullptr), mIsVideoSeeking(false), @@ -274,9 +385,29 @@ bool OmxDecoder::TryLoad() { if (durationUs > totalDurationUs) totalDurationUs = durationUs; } - if (mAudioTrack.get() && mAudioTrack->getFormat()->findInt64(kKeyDuration, &durationUs)) { - if (durationUs > totalDurationUs) - totalDurationUs = durationUs; + if (mAudioTrack.get()) { + durationUs = -1; + const char* audioMime; + sp meta = mAudioTrack->getFormat(); + + if (meta->findCString(kKeyMIMEType, &audioMime) && !strcasecmp(audioMime, AUDIO_MP3)) { + // Feed MP3 parser with cached data. Local files will be fully + // cached already, network streams will update with sucessive + // calls to NotifyDataArrived. + nsresult rv = OmxDecoderNotifyDataArrivedRunnable::ProcessCachedData(this); + + if (rv == NS_OK) { + durationUs = mMP3FrameParser.GetDuration(); + if (durationUs > totalDurationUs) { + totalDurationUs = durationUs; + } + } + } + if ((durationUs == -1) && meta->findInt64(kKeyDuration, &durationUs)) { + if (durationUs > totalDurationUs) { + totalDurationUs = durationUs; + } + } } mDurationUs = totalDurationUs; @@ -485,6 +616,25 @@ bool OmxDecoder::SetAudioFormat() { return true; } +void OmxDecoder::NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset) +{ + if (!mMP3FrameParser.IsMP3()) { + return; + } + + mMP3FrameParser.NotifyDataArrived(aBuffer, aLength, aOffset); + + int64_t durationUs = mMP3FrameParser.GetDuration(); + + if (durationUs != mDurationUs) { + mDurationUs = durationUs; + + MOZ_ASSERT(mDecoder); + ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); + mDecoder->UpdateMediaDuration(mDurationUs); + } +} + void OmxDecoder::ReleaseVideoBuffer() { if (mVideoBuffer) { mVideoBuffer->release(); diff --git a/content/media/omx/OmxDecoder.h b/content/media/omx/OmxDecoder.h index 84e7e467358d..82d3135aa6dc 100644 --- a/content/media/omx/OmxDecoder.h +++ b/content/media/omx/OmxDecoder.h @@ -9,6 +9,7 @@ #include "GonkNativeWindow.h" #include "GonkNativeWindowClient.h" #include "GrallocImages.h" +#include "MP3FrameParser.h" #include "MPAPI.h" #include "MediaResource.h" #include "AbstractMediaDecoder.h" @@ -76,6 +77,7 @@ private: class OmxDecoder : public OMXCodecProxy::EventListener { typedef MPAPI::AudioFrame AudioFrame; typedef MPAPI::VideoFrame VideoFrame; + typedef mozilla::MP3FrameParser MP3FrameParser; typedef mozilla::MediaResource MediaResource; typedef mozilla::AbstractMediaDecoder AbstractMediaDecoder; @@ -109,6 +111,7 @@ class OmxDecoder : public OMXCodecProxy::EventListener { int64_t mDurationUs; VideoFrame mVideoFrame; AudioFrame mAudioFrame; + MP3FrameParser mMP3FrameParser; // Lifetime of these should be handled by OMXCodec, as long as we release // them after use: see ReleaseVideoBuffer(), ReleaseAudioBuffer() @@ -177,6 +180,8 @@ public: bool SetVideoFormat(); bool SetAudioFormat(); + void NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset); + void GetDuration(int64_t *durationUs) { *durationUs = mDurationUs; }