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