/* -*- 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 "MediaCodecReader.h" #include #include #include #include "GonkNativeWindow.h" #include #include #include #include #include #include #include #include #include #include #include #include "mozilla/TaskQueue.h" #include "mozilla/TimeStamp.h" #include "mozilla/layers/GrallocTextureClient.h" #include "gfx2DGlue.h" #include "MediaStreamSource.h" #include "MP3FrameParser.h" #include "nsMimeTypes.h" #include "nsThreadUtils.h" #include "ImageContainer.h" #include "mozilla/SharedThreadPool.h" #include "VideoFrameContainer.h" #include "VideoUtils.h" using namespace android; using namespace mozilla::layers; using namespace mozilla::media; namespace mozilla { static const int64_t sInvalidDurationUs = INT64_C(-1); static const int64_t sInvalidTimestampUs = INT64_C(-1); // Try not to spend more than this much time (in seconds) in a single call // to GetCodecOutputData. static const double sMaxAudioDecodeDurationS = 0.1; static const double sMaxVideoDecodeDurationS = 0.1; static CheckedUint32 sInvalidInputIndex = INT32_C(-1); inline bool IsValidDurationUs(int64_t aDuration) { return aDuration >= INT64_C(0); } inline bool IsValidTimestampUs(int64_t aTimestamp) { return aTimestamp >= INT64_C(0); } MediaCodecReader::VideoResourceListener::VideoResourceListener( MediaCodecReader* aReader) : mReader(aReader) { } MediaCodecReader::VideoResourceListener::~VideoResourceListener() { mReader = nullptr; } void MediaCodecReader::VideoResourceListener::codecReserved() { if (mReader) { mReader->VideoCodecReserved(); } } void MediaCodecReader::VideoResourceListener::codecCanceled() { if (mReader) { mReader->VideoCodecCanceled(); } } MediaCodecReader::TrackInputCopier::~TrackInputCopier() { } bool MediaCodecReader::TrackInputCopier::Copy(MediaBuffer* aSourceBuffer, sp aCodecBuffer) { if (aSourceBuffer == nullptr || aCodecBuffer == nullptr || aSourceBuffer->range_length() > aCodecBuffer->capacity()) { return false; } aCodecBuffer->setRange(0, aSourceBuffer->range_length()); memcpy(aCodecBuffer->data(), (uint8_t*)aSourceBuffer->data() + aSourceBuffer->range_offset(), aSourceBuffer->range_length()); return true; } MediaCodecReader::Track::Track(Type type) : mType(type) , mSourceIsStopped(true) , mDurationUs(INT64_C(0)) , mInputIndex(sInvalidInputIndex) , mInputEndOfStream(false) , mOutputEndOfStream(false) , mSeekTimeUs(sInvalidTimestampUs) , mFlushed(false) , mDiscontinuity(false) , mTaskQueue(nullptr) , mTrackMonitor("MediaCodecReader::mTrackMonitor") { MOZ_ASSERT(mType != kUnknown, "Should have a valid Track::Type"); } // Append the value of |kKeyValidSamples| to the end of each vorbis buffer. // https://github.com/mozilla-b2g/platform_frameworks_av/blob/master/media/libstagefright/OMXCodec.cpp#L3128 // https://github.com/mozilla-b2g/platform_frameworks_av/blob/master/media/libstagefright/NuMediaExtractor.cpp#L472 bool MediaCodecReader::VorbisInputCopier::Copy(MediaBuffer* aSourceBuffer, sp aCodecBuffer) { if (aSourceBuffer == nullptr || aCodecBuffer == nullptr || aSourceBuffer->range_length() + sizeof(int32_t) > aCodecBuffer->capacity()) { return false; } int32_t numPageSamples = 0; if (!aSourceBuffer->meta_data()->findInt32(kKeyValidSamples, &numPageSamples)) { numPageSamples = -1; } aCodecBuffer->setRange(0, aSourceBuffer->range_length() + sizeof(int32_t)); memcpy(aCodecBuffer->data(), (uint8_t*)aSourceBuffer->data() + aSourceBuffer->range_offset(), aSourceBuffer->range_length()); memcpy(aCodecBuffer->data() + aSourceBuffer->range_length(), &numPageSamples, sizeof(numPageSamples)); return true; } MediaCodecReader::AudioTrack::AudioTrack() : Track(kAudio) { mAudioPromise.SetMonitor(&mTrackMonitor); } MediaCodecReader::VideoTrack::VideoTrack() : Track(kVideo) , mWidth(0) , mHeight(0) , mStride(0) , mSliceHeight(0) , mColorFormat(0) , mRotation(0) { mVideoPromise.SetMonitor(&mTrackMonitor); } MediaCodecReader::CodecBufferInfo::CodecBufferInfo() : mIndex(0) , mOffset(0) , mSize(0) , mTimeUs(0) , mFlags(0) { } MediaCodecReader::SignalObject::SignalObject(const char* aName) : mMonitor(aName) , mSignaled(false) { } MediaCodecReader::SignalObject::~SignalObject() { } void MediaCodecReader::SignalObject::Wait() { MonitorAutoLock al(mMonitor); if (!mSignaled) { mMonitor.Wait(); } } void MediaCodecReader::SignalObject::Signal() { MonitorAutoLock al(mMonitor); mSignaled = true; mMonitor.Notify(); } MediaCodecReader::ParseCachedDataRunnable::ParseCachedDataRunnable(nsRefPtr aReader, const char* aBuffer, uint32_t aLength, int64_t aOffset, nsRefPtr aSignal) : mReader(aReader) , mBuffer(aBuffer) , mLength(aLength) , mOffset(aOffset) , mSignal(aSignal) { MOZ_ASSERT(mReader, "Should have a valid MediaCodecReader."); MOZ_ASSERT(mBuffer, "Should have a valid buffer."); MOZ_ASSERT(mOffset >= INT64_C(0), "Should have a valid offset."); } NS_IMETHODIMP MediaCodecReader::ParseCachedDataRunnable::Run() { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); if (mReader->ParseDataSegment(mBuffer, mLength, mOffset)) { MonitorAutoLock monLock(mReader->mParserMonitor); if (mReader->mNextParserPosition >= mOffset + mLength && mReader->mParsedDataLength < mOffset + mLength) { mReader->mParsedDataLength = mOffset + mLength; } } if (mSignal != nullptr) { mSignal->Signal(); } return NS_OK; } MediaCodecReader::ProcessCachedDataTask::ProcessCachedDataTask(nsRefPtr aReader, int64_t aOffset) : mReader(aReader) , mOffset(aOffset) { MOZ_ASSERT(mReader, "Should have a valid MediaCodecReader."); MOZ_ASSERT(mOffset >= INT64_C(0), "Should have a valid offset."); } void MediaCodecReader::ProcessCachedDataTask::Run() { mReader->ProcessCachedData(mOffset, nullptr); } MediaCodecReader::MediaCodecReader(AbstractMediaDecoder* aDecoder) : MediaOmxCommonReader(aDecoder) , mExtractor(nullptr) , mTextureClientIndexesLock("MediaCodecReader::mTextureClientIndexesLock") , mColorConverterBufferSize(0) , mParserMonitor("MediaCodecReader::mParserMonitor") , mParseDataFromCache(true) , mNextParserPosition(INT64_C(0)) , mParsedDataLength(INT64_C(0)) { mVideoListener = new VideoResourceListener(this); } MediaCodecReader::~MediaCodecReader() { } nsresult MediaCodecReader::Init(MediaDecoderReader* aCloneDonor) { return NS_OK; } void MediaCodecReader::ReleaseMediaResources() { // Stop the mSource because we are in the dormant state and the stop function // will rewind the mSource to the beginning of the stream. if (mVideoTrack.mSource != nullptr && !mVideoTrack.mSourceIsStopped) { mVideoTrack.mSource->stop(); mVideoTrack.mSourceIsStopped = true; } if (mAudioTrack.mSource != nullptr && !mAudioTrack.mSourceIsStopped) { mAudioTrack.mSource->stop(); mAudioTrack.mSourceIsStopped = true; } ReleaseCriticalResources(); } nsRefPtr MediaCodecReader::Shutdown() { MOZ_ASSERT(mAudioTrack.mAudioPromise.IsEmpty()); MOZ_ASSERT(mVideoTrack.mVideoPromise.IsEmpty()); ReleaseResources(); return MediaDecoderReader::Shutdown(); } void MediaCodecReader::DispatchAudioTask() { if (mAudioTrack.mTaskQueue) { RefPtr task = NS_NewRunnableMethod(this, &MediaCodecReader::DecodeAudioDataTask); mAudioTrack.mTaskQueue->Dispatch(task.forget()); } } void MediaCodecReader::DispatchVideoTask(int64_t aTimeThreshold) { if (mVideoTrack.mTaskQueue) { RefPtr task = NS_NewRunnableMethodWithArg(this, &MediaCodecReader::DecodeVideoFrameTask, aTimeThreshold); mVideoTrack.mTaskQueue->Dispatch(task.forget()); } } nsRefPtr MediaCodecReader::RequestAudioData() { MOZ_ASSERT(OnTaskQueue()); MOZ_ASSERT(HasAudio()); MonitorAutoLock al(mAudioTrack.mTrackMonitor); if (CheckAudioResources()) { DispatchAudioTask(); } MOZ_ASSERT(mAudioTrack.mAudioPromise.IsEmpty()); return mAudioTrack.mAudioPromise.Ensure(__func__); } nsRefPtr MediaCodecReader::RequestVideoData(bool aSkipToNextKeyframe, int64_t aTimeThreshold) { MOZ_ASSERT(OnTaskQueue()); MOZ_ASSERT(HasVideo()); int64_t threshold = sInvalidTimestampUs; if (aSkipToNextKeyframe && IsValidTimestampUs(aTimeThreshold)) { threshold = aTimeThreshold; } MonitorAutoLock al(mVideoTrack.mTrackMonitor); if (CheckVideoResources()) { DispatchVideoTask(threshold); } MOZ_ASSERT(mVideoTrack.mVideoPromise.IsEmpty()); return mVideoTrack.mVideoPromise.Ensure(__func__); } void MediaCodecReader::DecodeAudioDataSync() { if (mAudioTrack.mCodec == nullptr || !mAudioTrack.mCodec->allocated() || mAudioTrack.mOutputEndOfStream) { return; } // Get one audio output data from MediaCodec CodecBufferInfo bufferInfo; status_t status; TimeStamp timeout = TimeStamp::Now() + TimeDuration::FromSeconds(sMaxAudioDecodeDurationS); while (true) { // Try to fill more input buffers and then get one output buffer. // FIXME: use callback from MediaCodec FillCodecInputData(mAudioTrack); status = GetCodecOutputData(mAudioTrack, bufferInfo, sInvalidTimestampUs, timeout); if (status == OK || status == ERROR_END_OF_STREAM) { break; } else if (status == -EAGAIN) { if (TimeStamp::Now() > timeout) { // Don't let this loop run for too long. Try it again later. return; } continue; // Try it again now. } else if (status == INFO_FORMAT_CHANGED) { if (UpdateAudioInfo()) { continue; // Try it again now. } else { return; } } else { return; } } if ((bufferInfo.mFlags & MediaCodec::BUFFER_FLAG_EOS) || (status == ERROR_END_OF_STREAM)) { AudioQueue().Finish(); } else if (bufferInfo.mBuffer != nullptr && bufferInfo.mSize > 0 && bufferInfo.mBuffer->data() != nullptr) { MOZ_ASSERT(mStreamSource); // This is the approximate byte position in the stream. int64_t pos = mStreamSource->Tell(); uint32_t frames = bufferInfo.mSize / (mInfo.mAudio.mChannels * sizeof(AudioDataValue)); mAudioCompactor.Push( pos, bufferInfo.mTimeUs, mInfo.mAudio.mRate, frames, mInfo.mAudio.mChannels, AudioCompactor::NativeCopy( bufferInfo.mBuffer->data() + bufferInfo.mOffset, bufferInfo.mSize, mInfo.mAudio.mChannels)); } mAudioTrack.mCodec->releaseOutputBuffer(bufferInfo.mIndex); } void MediaCodecReader::DecodeAudioDataTask() { MOZ_ASSERT(mAudioTrack.mTaskQueue->IsCurrentThreadIn()); MonitorAutoLock al(mAudioTrack.mTrackMonitor); if (mAudioTrack.mAudioPromise.IsEmpty()) { // Clear the data in queue because the promise might be canceled by // ResetDecode(). AudioQueue().Reset(); return; } if (AudioQueue().GetSize() == 0 && !AudioQueue().IsFinished()) { MonitorAutoUnlock ul(mAudioTrack.mTrackMonitor); DecodeAudioDataSync(); } // Since we unlock the monitor above, we should check the promise again // because the promise might be canceled by ResetDecode(). if (mAudioTrack.mAudioPromise.IsEmpty()) { AudioQueue().Reset(); return; } if (AudioQueue().GetSize() > 0) { nsRefPtr a = AudioQueue().PopFront(); if (a) { if (mAudioTrack.mDiscontinuity) { a->mDiscontinuity = true; mAudioTrack.mDiscontinuity = false; } mAudioTrack.mAudioPromise.Resolve(a, __func__); } } else if (AudioQueue().AtEndOfStream()) { mAudioTrack.mAudioPromise.Reject(END_OF_STREAM, __func__); } else if (AudioQueue().GetSize() == 0) { DispatchAudioTask(); } } void MediaCodecReader::DecodeVideoFrameTask(int64_t aTimeThreshold) { MOZ_ASSERT(mVideoTrack.mTaskQueue->IsCurrentThreadIn()); MonitorAutoLock al(mVideoTrack.mTrackMonitor); if (mVideoTrack.mVideoPromise.IsEmpty()) { // Clear the data in queue because the promise might be canceled by // ResetDecode(). VideoQueue().Reset(); return; } { MonitorAutoUnlock ul(mVideoTrack.mTrackMonitor); DecodeVideoFrameSync(aTimeThreshold); } // Since we unlock the monitor above, we should check the promise again // because the promise might be canceled by ResetDecode(). if (mVideoTrack.mVideoPromise.IsEmpty()) { VideoQueue().Reset(); return; } if (VideoQueue().GetSize() > 0) { nsRefPtr v = VideoQueue().PopFront(); if (v) { if (mVideoTrack.mDiscontinuity) { v->mDiscontinuity = true; mVideoTrack.mDiscontinuity = false; } mVideoTrack.mVideoPromise.Resolve(v, __func__); } } else if (VideoQueue().AtEndOfStream()) { mVideoTrack.mVideoPromise.Reject(END_OF_STREAM, __func__); } else if (VideoQueue().GetSize() == 0) { DispatchVideoTask(aTimeThreshold); } } bool MediaCodecReader::HasAudio() { return mInfo.HasAudio(); } bool MediaCodecReader::HasVideo() { return mInfo.HasVideo(); } void MediaCodecReader::NotifyDataArrivedInternal(uint32_t aLength, int64_t aOffset) { nsRefPtr bytes = mDecoder->GetResource()->MediaReadAt(aOffset, aLength); NS_ENSURE_TRUE_VOID(bytes); MonitorAutoLock monLock(mParserMonitor); if (mNextParserPosition == mParsedDataLength && mNextParserPosition >= aOffset && mNextParserPosition <= aOffset + aLength) { // No pending parsing runnable currently. And available data are adjacent to // parsed data. int64_t shift = mNextParserPosition - aOffset; const char* buffer = reinterpret_cast(bytes->Elements()) + shift; uint32_t length = aLength - shift; int64_t offset = mNextParserPosition; if (length > 0) { MonitorAutoUnlock monUnlock(mParserMonitor); ParseDataSegment(buffer, length, offset); } mParseDataFromCache = false; mParsedDataLength = offset + length; mNextParserPosition = mParsedDataLength; } } int64_t MediaCodecReader::ProcessCachedData(int64_t aOffset, nsRefPtr aSignal) { // 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; MOZ_ASSERT(!NS_IsMainThread(), "Should not be on main thread."); { MonitorAutoLock monLock(mParserMonitor); if (!mParseDataFromCache) { // Skip cache processing since data can be continuously be parsed by // ParseDataSegment() from NotifyDataArrived() directly. return INT64_C(0); } } MediaResource *resource = mDecoder->GetResource(); MOZ_ASSERT(resource); int64_t resourceLength = resource->GetCachedDataEnd(0); NS_ENSURE_TRUE(resourceLength >= 0, INT64_C(-1)); if (aOffset >= resourceLength) { return INT64_C(0); // Cache is empty, nothing to do } int64_t bufferLength = std::min(resourceLength - aOffset, sReadSize); nsAutoArrayPtr buffer(new char[bufferLength]); nsresult rv = resource->ReadFromCache(buffer.get(), aOffset, bufferLength); NS_ENSURE_SUCCESS(rv, INT64_C(-1)); MonitorAutoLock monLock(mParserMonitor); if (mParseDataFromCache) { nsRefPtr runnable( new ParseCachedDataRunnable(this, buffer.forget(), bufferLength, aOffset, aSignal)); rv = NS_DispatchToMainThread(runnable.get()); NS_ENSURE_SUCCESS(rv, INT64_C(-1)); mNextParserPosition = aOffset + bufferLength; if (mNextParserPosition < resource->GetCachedDataEnd(0)) { // 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 ProcessCachedDataTask(this, mNextParserPosition)); } } return bufferLength; } bool MediaCodecReader::ParseDataSegment(const char* aBuffer, uint32_t aLength, int64_t aOffset) { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); int64_t duration = INT64_C(-1); { MonitorAutoLock monLock(mParserMonitor); // currently only mp3 files are supported for incremental parsing if (mMP3FrameParser == nullptr) { return false; } if (!mMP3FrameParser->IsMP3()) { return true; // NO-OP } mMP3FrameParser->Parse(reinterpret_cast(aBuffer), aLength, aOffset); duration = mMP3FrameParser->GetDuration(); } bool durationUpdateRequired = false; { MonitorAutoLock al(mAudioTrack.mTrackMonitor); if (duration > mAudioTrack.mDurationUs) { mAudioTrack.mDurationUs = duration; durationUpdateRequired = true; } } if (durationUpdateRequired && HasVideo()) { MonitorAutoLock al(mVideoTrack.mTrackMonitor); durationUpdateRequired = duration > mVideoTrack.mDurationUs; } if (durationUpdateRequired) { MOZ_ASSERT(mDecoder); mDecoder->DispatchUpdateEstimatedMediaDuration(duration); } return true; } nsRefPtr MediaCodecReader::AsyncReadMetadata() { MOZ_ASSERT(OnTaskQueue()); if (!ReallocateExtractorResources()) { return MediaDecoderReader::MetadataPromise::CreateAndReject( ReadMetadataFailureReason::METADATA_ERROR, __func__); } bool incrementalParserNeeded = mDecoder->GetResource()->GetContentType().EqualsASCII(AUDIO_MP3); if (incrementalParserNeeded && !TriggerIncrementalParser()) { return MediaDecoderReader::MetadataPromise::CreateAndReject( ReadMetadataFailureReason::METADATA_ERROR, __func__); } nsRefPtr p = mMetadataPromise.Ensure(__func__); nsRefPtr self = this; mMediaResourceRequest.Begin(CreateMediaCodecs() ->Then(OwnerThread(), __func__, [self] (bool) -> void { self->mMediaResourceRequest.Complete(); self->HandleResourceAllocated(); }, [self] (bool) -> void { self->mMediaResourceRequest.Complete(); self->mMetadataPromise.Reject(ReadMetadataFailureReason::METADATA_ERROR, __func__); })); return p; } void MediaCodecReader::HandleResourceAllocated() { // Configure video codec after the codecReserved. if (mVideoTrack.mSource != nullptr) { if (!ConfigureMediaCodec(mVideoTrack)) { DestroyMediaCodec(mVideoTrack); mMetadataPromise.Reject(ReadMetadataFailureReason::METADATA_ERROR, __func__); return; } } // TODO: start streaming if (!UpdateDuration()) { mMetadataPromise.Reject(ReadMetadataFailureReason::METADATA_ERROR, __func__); return; } if (!UpdateAudioInfo()) { mMetadataPromise.Reject(ReadMetadataFailureReason::METADATA_ERROR, __func__); return; } if (!UpdateVideoInfo()) { mMetadataPromise.Reject(ReadMetadataFailureReason::METADATA_ERROR, __func__); return; } // Set the total duration (the max of the audio and video track). int64_t audioDuration = INT64_C(-1); { MonitorAutoLock al(mAudioTrack.mTrackMonitor); audioDuration = mAudioTrack.mDurationUs; } int64_t videoDuration = INT64_C(-1); { MonitorAutoLock al(mVideoTrack.mTrackMonitor); videoDuration = mVideoTrack.mDurationUs; } int64_t duration = audioDuration > videoDuration ? audioDuration : videoDuration; if (duration >= INT64_C(0)) { mInfo.mMetadataDuration = Some(TimeUnit::FromMicroseconds(duration)); } // Video track's frame sizes will not overflow. Activate the video track. VideoFrameContainer* container = mDecoder->GetVideoFrameContainer(); if (container) { container->ClearCurrentFrame( gfxIntSize(mInfo.mVideo.mDisplay.width, mInfo.mVideo.mDisplay.height)); } nsRefPtr metadata = new MetadataHolder(); metadata->mInfo = mInfo; metadata->mTags = nullptr; #ifdef MOZ_AUDIO_OFFLOAD CheckAudioOffload(); #endif mMetadataPromise.Resolve(metadata, __func__); } nsresult MediaCodecReader::ResetDecode() { if (CheckAudioResources()) { MonitorAutoLock al(mAudioTrack.mTrackMonitor); if (!mAudioTrack.mAudioPromise.IsEmpty()) { mAudioTrack.mAudioPromise.Reject(CANCELED, __func__); } FlushCodecData(mAudioTrack); mAudioTrack.mDiscontinuity = true; } if (CheckVideoResources()) { MonitorAutoLock al(mVideoTrack.mTrackMonitor); if (!mVideoTrack.mVideoPromise.IsEmpty()) { mVideoTrack.mVideoPromise.Reject(CANCELED, __func__); } FlushCodecData(mVideoTrack); mVideoTrack.mDiscontinuity = true; } return MediaDecoderReader::ResetDecode(); } void MediaCodecReader::TextureClientRecycleCallback(TextureClient* aClient, void* aClosure) { nsRefPtr reader = static_cast(aClosure); MOZ_ASSERT(reader, "reader should not be nullptr in TextureClientRecycleCallback()"); reader->TextureClientRecycleCallback(aClient); } void MediaCodecReader::TextureClientRecycleCallback(TextureClient* aClient) { MOZ_ASSERT(aClient, "aClient should not be nullptr in RecycleCallback()"); MOZ_ASSERT(!aClient->IsDead()); size_t index = 0; { MutexAutoLock al(mTextureClientIndexesLock); aClient->ClearRecycleCallback(); // aClient has been removed from mTextureClientIndexes by // ReleaseAllTextureClients() on another thread. if (!mTextureClientIndexes.Get(aClient, &index)) { return; } FenceHandle handle = aClient->GetAndResetReleaseFenceHandle(); mPendingReleaseItems.AppendElement(ReleaseItem(index, handle)); mTextureClientIndexes.Remove(aClient); } if (mVideoTrack.mReleaseBufferTaskQueue->IsEmpty()) { RefPtr task = NS_NewRunnableMethod(this, &MediaCodecReader::WaitFenceAndReleaseOutputBuffer); mVideoTrack.mReleaseBufferTaskQueue->Dispatch(task.forget()); } } void MediaCodecReader::WaitFenceAndReleaseOutputBuffer() { nsTArray releasingItems; { MutexAutoLock autoLock(mTextureClientIndexesLock); releasingItems.AppendElements(mPendingReleaseItems); mPendingReleaseItems.Clear(); } for (size_t i = 0; i < releasingItems.Length(); i++) { if (releasingItems[i].mReleaseFence.IsValid()) { #if MOZ_WIDGET_GONK && ANDROID_VERSION >= 17 nsRefPtr fdObj = releasingItems[i].mReleaseFence.GetAndResetFdObj(); sp fence = new Fence(fdObj->GetAndResetFd()); fence->waitForever("MediaCodecReader"); #endif } if (mVideoTrack.mCodec != nullptr) { mVideoTrack.mCodec->releaseOutputBuffer(releasingItems[i].mReleaseIndex); } } } PLDHashOperator MediaCodecReader::ReleaseTextureClient(TextureClient* aClient, size_t& aIndex, void* aUserArg) { nsRefPtr reader = static_cast(aUserArg); MOZ_ASSERT(reader, "reader should not be nullptr in ReleaseTextureClient()"); return reader->ReleaseTextureClient(aClient, aIndex); } PLDHashOperator MediaCodecReader::ReleaseTextureClient(TextureClient* aClient, size_t& aIndex) { MOZ_ASSERT(aClient, "TextureClient should be a valid pointer"); aClient->ClearRecycleCallback(); if (mVideoTrack.mCodec != nullptr) { mVideoTrack.mCodec->releaseOutputBuffer(aIndex); } return PL_DHASH_REMOVE; } void MediaCodecReader::ReleaseAllTextureClients() { MutexAutoLock al(mTextureClientIndexesLock); MOZ_ASSERT(mTextureClientIndexes.Count(), "All TextureClients should be released already"); if (mTextureClientIndexes.Count() == 0) { return; } printf_stderr("All TextureClients should be released already"); mTextureClientIndexes.Enumerate(MediaCodecReader::ReleaseTextureClient, this); mTextureClientIndexes.Clear(); } void MediaCodecReader::DecodeVideoFrameSync(int64_t aTimeThreshold) { if (mVideoTrack.mCodec == nullptr || !mVideoTrack.mCodec->allocated() || mVideoTrack.mOutputEndOfStream) { return; } // Get one video output data from MediaCodec CodecBufferInfo bufferInfo; status_t status; TimeStamp timeout = TimeStamp::Now() + TimeDuration::FromSeconds(sMaxVideoDecodeDurationS); while (true) { // Try to fill more input buffers and then get one output buffer. // FIXME: use callback from MediaCodec FillCodecInputData(mVideoTrack); status = GetCodecOutputData(mVideoTrack, bufferInfo, aTimeThreshold, timeout); if (status == OK || status == ERROR_END_OF_STREAM) { break; } else if (status == -EAGAIN) { if (TimeStamp::Now() > timeout) { // Don't let this loop run for too long. Try it again later. return; } continue; // Try it again now. } else if (status == INFO_FORMAT_CHANGED) { if (UpdateVideoInfo()) { continue; // Try it again now. } else { return; } } else { return; } } if ((bufferInfo.mFlags & MediaCodec::BUFFER_FLAG_EOS) || (status == ERROR_END_OF_STREAM)) { VideoQueue().Finish(); mVideoTrack.mCodec->releaseOutputBuffer(bufferInfo.mIndex); return; } nsRefPtr v; RefPtr textureClient; sp graphicBuffer; if (bufferInfo.mBuffer != nullptr) { MOZ_ASSERT(mStreamSource); // This is the approximate byte position in the stream. int64_t pos = mStreamSource->Tell(); if (mVideoTrack.mNativeWindow != nullptr && mVideoTrack.mCodec->getOutputGraphicBufferFromIndex(bufferInfo.mIndex, &graphicBuffer) == OK && graphicBuffer != nullptr) { textureClient = mVideoTrack.mNativeWindow->getTextureClientFromBuffer(graphicBuffer.get()); v = VideoData::Create(mInfo.mVideo, mDecoder->GetImageContainer(), pos, bufferInfo.mTimeUs, 1, // We don't know the duration. textureClient, bufferInfo.mFlags & MediaCodec::BUFFER_FLAG_SYNCFRAME, -1, mVideoTrack.mRelativePictureRect); } else if (bufferInfo.mSize > 0 && bufferInfo.mBuffer->data() != nullptr) { uint8_t* yuv420p_buffer = bufferInfo.mBuffer->data(); int32_t stride = mVideoTrack.mStride; int32_t slice_height = mVideoTrack.mSliceHeight; // Converts to OMX_COLOR_FormatYUV420Planar if (mVideoTrack.mColorFormat != OMX_COLOR_FormatYUV420Planar) { ARect crop; crop.top = 0; crop.bottom = mVideoTrack.mHeight; crop.left = 0; crop.right = mVideoTrack.mWidth; yuv420p_buffer = GetColorConverterBuffer(mVideoTrack.mWidth, mVideoTrack.mHeight); if (mColorConverter.convertDecoderOutputToI420( bufferInfo.mBuffer->data(), mVideoTrack.mWidth, mVideoTrack.mHeight, crop, yuv420p_buffer) != OK) { mVideoTrack.mCodec->releaseOutputBuffer(bufferInfo.mIndex); NS_WARNING("Unable to convert color format"); return; } stride = mVideoTrack.mWidth; slice_height = mVideoTrack.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 = mVideoTrack.mWidth; b.mPlanes[0].mHeight = mVideoTrack.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 = (mVideoTrack.mWidth + 1) / 2; b.mPlanes[1].mHeight = (mVideoTrack.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 =(mVideoTrack.mWidth + 1) / 2; b.mPlanes[2].mHeight = (mVideoTrack.mHeight + 1) / 2; b.mPlanes[2].mStride = (stride + 1) / 2; b.mPlanes[2].mOffset = 0; b.mPlanes[2].mSkip = 0; v = VideoData::Create(mInfo.mVideo, mDecoder->GetImageContainer(), pos, bufferInfo.mTimeUs, 1, // We don't know the duration. b, bufferInfo.mFlags & MediaCodec::BUFFER_FLAG_SYNCFRAME, -1, mVideoTrack.mRelativePictureRect); } if (v) { // Notify mDecoder that we have decoded a video frame. mDecoder->NotifyDecodedFrames(0, 1, 0); VideoQueue().Push(v); } else { NS_WARNING("Unable to create VideoData"); } } if (v != nullptr && textureClient != nullptr && graphicBuffer != nullptr) { MutexAutoLock al(mTextureClientIndexesLock); mTextureClientIndexes.Put(textureClient.get(), bufferInfo.mIndex); textureClient->SetRecycleCallback(MediaCodecReader::TextureClientRecycleCallback, this); } else { mVideoTrack.mCodec->releaseOutputBuffer(bufferInfo.mIndex); } } nsRefPtr MediaCodecReader::Seek(int64_t aTime, int64_t aEndTime) { MOZ_ASSERT(OnTaskQueue()); int64_t timestamp = sInvalidTimestampUs; if (CheckVideoResources()) { MonitorAutoLock al(mVideoTrack.mTrackMonitor); mVideoTrack.mSeekTimeUs = aTime; mVideoTrack.mInputEndOfStream = false; mVideoTrack.mOutputEndOfStream = false; mVideoTrack.mFlushed = false; MediaBuffer* source_buffer = nullptr; MediaSource::ReadOptions options; options.setSeekTo(aTime, MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC); if (mVideoTrack.mSource->read(&source_buffer, &options) != OK || source_buffer == nullptr) { return SeekPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); } sp format = source_buffer->meta_data(); if (format != nullptr) { if (format->findInt64(kKeyTime, ×tamp) && IsValidTimestampUs(timestamp)) { mVideoTrack.mSeekTimeUs = timestamp; } format = nullptr; } source_buffer->release(); } { MonitorAutoLock al(mAudioTrack.mTrackMonitor); mAudioTrack.mInputEndOfStream = false; mAudioTrack.mOutputEndOfStream = false; mAudioTrack.mFlushed = false; if (IsValidTimestampUs(timestamp)) { mAudioTrack.mSeekTimeUs = timestamp; } else { mAudioTrack.mSeekTimeUs = aTime; } } return SeekPromise::CreateAndResolve(aTime, __func__); } bool MediaCodecReader::IsMediaSeekable() { // Check the MediaExtract flag if the source is seekable. return (mExtractor != nullptr) && (mExtractor->flags() & MediaExtractor::CAN_SEEK); } sp MediaCodecReader::GetAudioOffloadTrack() { return mAudioOffloadTrack.mSource; } bool MediaCodecReader::ReallocateExtractorResources() { if (CreateLooper() && CreateExtractor() && CreateMediaSources() && CreateTaskQueues()) { return true; } ReleaseResources(); return false; } void MediaCodecReader::ReleaseCriticalResources() { mMediaResourceRequest.DisconnectIfExists(); mMediaResourcePromise.RejectIfExists(true, __func__); mMetadataPromise.RejectIfExists(ReadMetadataFailureReason::METADATA_ERROR, __func__); ResetDecode(); // Before freeing a video codec, all video buffers needed to be released // even from graphics pipeline. VideoFrameContainer* videoframe = mDecoder->GetVideoFrameContainer(); if (videoframe) { videoframe->ClearCurrentFrame(); } ReleaseAllTextureClients(); DestroyMediaCodecs(); ClearColorConverterBuffer(); } void MediaCodecReader::ReleaseResources() { ReleaseCriticalResources(); DestroyMediaSources(); DestroyExtractor(); DestroyLooper(); ShutdownTaskQueues(); } bool MediaCodecReader::CreateLooper() { if (mLooper != nullptr) { return true; } // Create ALooper sp looper = new ALooper; looper->setName("MediaCodecReader::mLooper"); // Start ALooper thread. if (looper->start() != OK) { return false; } mLooper = looper; return true; } void MediaCodecReader::DestroyLooper() { if (mLooper == nullptr) { return; } // Stop ALooper thread. mLooper->stop(); // Clear ALooper mLooper = nullptr; } bool MediaCodecReader::CreateExtractor() { if (mExtractor != nullptr) { return true; } //register sniffers, if they are not registered in this process. DataSource::RegisterDefaultSniffers(); if (mExtractor == nullptr) { sp dataSource = new MediaStreamSource(mDecoder->GetResource()); if (dataSource->initCheck() != OK) { return false; } mStreamSource = static_cast(dataSource.get()); mExtractor = MediaExtractor::Create(dataSource); } return mExtractor != nullptr; } void MediaCodecReader::DestroyExtractor() { mExtractor = nullptr; } bool MediaCodecReader::CreateMediaSources() { if (mExtractor == nullptr) { return false; } mMetaData = mExtractor->getMetaData(); const ssize_t invalidTrackIndex = -1; ssize_t audioTrackIndex = invalidTrackIndex; ssize_t videoTrackIndex = invalidTrackIndex; for (size_t i = 0; i < mExtractor->countTracks(); ++i) { sp trackFormat = mExtractor->getTrackMetaData(i); const char* mime; if (!trackFormat->findCString(kKeyMIMEType, &mime)) { continue; } if (audioTrackIndex == invalidTrackIndex && !strncasecmp(mime, "audio/", 6)) { audioTrackIndex = i; } else if (videoTrackIndex == invalidTrackIndex && !strncasecmp(mime, "video/", 6)) { videoTrackIndex = i; } } if (audioTrackIndex == invalidTrackIndex && videoTrackIndex == invalidTrackIndex) { NS_WARNING("OMX decoder could not find audio or video tracks"); return false; } if (audioTrackIndex != invalidTrackIndex && mAudioTrack.mSource == nullptr) { sp audioSource = mExtractor->getTrack(audioTrackIndex); if (audioSource != nullptr && audioSource->start() == OK) { mAudioTrack.mSource = audioSource; mAudioTrack.mSourceIsStopped = false; } // Get one another track instance for audio offload playback. mAudioOffloadTrack.mSource = mExtractor->getTrack(audioTrackIndex); } if (videoTrackIndex != invalidTrackIndex && mVideoTrack.mSource == nullptr && mDecoder->GetImageContainer()) { sp videoSource = mExtractor->getTrack(videoTrackIndex); if (videoSource != nullptr && videoSource->start() == OK) { mVideoTrack.mSource = videoSource; mVideoTrack.mSourceIsStopped = false; } } return (audioTrackIndex == invalidTrackIndex || mAudioTrack.mSource != nullptr) && (videoTrackIndex == invalidTrackIndex || mVideoTrack.mSource != nullptr); } void MediaCodecReader::DestroyMediaSources() { mAudioTrack.mSource = nullptr; mVideoTrack.mSource = nullptr; #if ANDROID_VERSION >= 21 mAudioOffloadTrack.mSource = nullptr; #endif } void MediaCodecReader::ShutdownTaskQueues() { if (mAudioTrack.mTaskQueue) { mAudioTrack.mTaskQueue->BeginShutdown(); mAudioTrack.mTaskQueue->AwaitShutdownAndIdle(); mAudioTrack.mTaskQueue = nullptr; } if (mVideoTrack.mTaskQueue) { mVideoTrack.mTaskQueue->BeginShutdown(); mVideoTrack.mTaskQueue->AwaitShutdownAndIdle(); mVideoTrack.mTaskQueue = nullptr; } if (mVideoTrack.mReleaseBufferTaskQueue) { mVideoTrack.mReleaseBufferTaskQueue->BeginShutdown(); mVideoTrack.mReleaseBufferTaskQueue->AwaitShutdownAndIdle(); mVideoTrack.mReleaseBufferTaskQueue = nullptr; } } bool MediaCodecReader::CreateTaskQueues() { if (mAudioTrack.mSource != nullptr && !mAudioTrack.mTaskQueue) { mAudioTrack.mTaskQueue = CreateMediaDecodeTaskQueue(); NS_ENSURE_TRUE(mAudioTrack.mTaskQueue, false); } if (mVideoTrack.mSource != nullptr && !mVideoTrack.mTaskQueue) { mVideoTrack.mTaskQueue = CreateMediaDecodeTaskQueue(); NS_ENSURE_TRUE(mVideoTrack.mTaskQueue, false); mVideoTrack.mReleaseBufferTaskQueue = CreateMediaDecodeTaskQueue(); NS_ENSURE_TRUE(mVideoTrack.mReleaseBufferTaskQueue, false); } return true; } nsRefPtr MediaCodecReader::CreateMediaCodecs() { bool isWaiting = false; nsRefPtr p = mMediaResourcePromise.Ensure(__func__); if (!CreateMediaCodec(mLooper, mAudioTrack, false, isWaiting, nullptr)) { mMediaResourcePromise.Reject(true, __func__); return p; } if (!CreateMediaCodec(mLooper, mVideoTrack, true, isWaiting, mVideoListener)) { mMediaResourcePromise.Reject(true, __func__); return p; } if (!isWaiting) { // No MediaCodec allocation wait. mMediaResourcePromise.Resolve(true, __func__); } return p; } bool MediaCodecReader::CreateMediaCodec(sp& aLooper, Track& aTrack, bool aAsync, bool& aIsWaiting, wp aListener) { if (aTrack.mSource != nullptr && aTrack.mCodec == nullptr) { sp sourceFormat = aTrack.mSource->getFormat(); const char* mime; if (sourceFormat->findCString(kKeyMIMEType, &mime)) { aTrack.mCodec = MediaCodecProxy::CreateByType(aLooper, mime, false, aListener); } if (aTrack.mCodec == nullptr) { NS_WARNING("Couldn't create MediaCodecProxy"); return false; } if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_VORBIS)) { aTrack.mInputCopier = new VorbisInputCopier; } else { aTrack.mInputCopier = new TrackInputCopier; } uint32_t capability = MediaCodecProxy::kEmptyCapability; if (aTrack.mType == Track::kVideo && aTrack.mCodec->getCapability(&capability) == OK && (capability & MediaCodecProxy::kCanExposeGraphicBuffer) == MediaCodecProxy::kCanExposeGraphicBuffer) { #if ANDROID_VERSION >= 21 android::sp producer; android::sp consumer; GonkBufferQueue::createBufferQueue(&producer, &consumer); aTrack.mNativeWindow = new GonkNativeWindow(consumer); aTrack.mGraphicBufferProducer = producer; #else aTrack.mNativeWindow = new GonkNativeWindow(); #endif } if (!aAsync && aTrack.mCodec->AskMediaCodecAndWait()) { // Pending configure() and start() to codecReserved() if the creation // should be asynchronous. if (!aTrack.mCodec->allocated() || !ConfigureMediaCodec(aTrack)){ NS_WARNING("Couldn't create and configure MediaCodec synchronously"); DestroyMediaCodec(aTrack); return false; } } else if (aAsync) { if (aTrack.mCodec->AsyncAskMediaCodec()) { aIsWaiting = true; } else { NS_WARNING("Couldn't request MediaCodec asynchronously"); DestroyMediaCodec(aTrack); return false; } } } return true; } bool MediaCodecReader::ConfigureMediaCodec(Track& aTrack) { if (aTrack.mSource != nullptr && aTrack.mCodec != nullptr) { if (!aTrack.mCodec->allocated()) { return false; } sp surface; if (aTrack.mNativeWindow != nullptr) { #if ANDROID_VERSION >= 21 surface = new Surface(aTrack.mGraphicBufferProducer); #else surface = new Surface(aTrack.mNativeWindow->getBufferQueue()); #endif } sp sourceFormat = aTrack.mSource->getFormat(); sp codecFormat; convertMetaDataToMessage(sourceFormat, &codecFormat); bool allpass = true; if (allpass && aTrack.mCodec->configure(codecFormat, surface, nullptr, 0) != OK) { NS_WARNING("Couldn't configure MediaCodec"); allpass = false; } if (allpass && aTrack.mCodec->start() != OK) { NS_WARNING("Couldn't start MediaCodec"); allpass = false; } if (allpass && aTrack.mCodec->getInputBuffers(&aTrack.mInputBuffers) != OK) { NS_WARNING("Couldn't get input buffers from MediaCodec"); allpass = false; } if (allpass && aTrack.mCodec->getOutputBuffers(&aTrack.mOutputBuffers) != OK) { NS_WARNING("Couldn't get output buffers from MediaCodec"); allpass = false; } if (!allpass) { DestroyMediaCodec(aTrack); return false; } } return true; } void MediaCodecReader::DestroyMediaCodecs() { DestroyMediaCodec(mAudioTrack); DestroyMediaCodec(mVideoTrack); } void MediaCodecReader::DestroyMediaCodec(Track& aTrack) { aTrack.mCodec = nullptr; aTrack.mNativeWindow = nullptr; #if ANDROID_VERSION >= 21 aTrack.mGraphicBufferProducer = nullptr; #endif } bool MediaCodecReader::TriggerIncrementalParser() { if (mMetaData == nullptr) { return false; } int64_t duration = INT64_C(-1); { MonitorAutoLock monLock(mParserMonitor); // only support incremental parsing for mp3 currently. if (mMP3FrameParser != nullptr) { return true; } mParseDataFromCache = true; mNextParserPosition = INT64_C(0); mParsedDataLength = INT64_C(0); // MP3 file duration const char* mime = nullptr; if (mMetaData->findCString(kKeyMIMEType, &mime) && !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_MPEG)) { mMP3FrameParser = new MP3FrameParser(mDecoder->GetResource()->GetLength()); { MonitorAutoUnlock monUnlock(mParserMonitor); // trigger parsing logic and wait for finishing parsing data in the beginning. nsRefPtr signalObject = new SignalObject("MediaCodecReader::UpdateDuration()"); if (ProcessCachedData(INT64_C(0), signalObject) > INT64_C(0)) { signalObject->Wait(); } } duration = mMP3FrameParser->GetDuration(); } } { MonitorAutoLock al(mAudioTrack.mTrackMonitor); if (duration > mAudioTrack.mDurationUs) { mAudioTrack.mDurationUs = duration; } } return true; } bool MediaCodecReader::UpdateDuration() { // read audio duration if (mAudioTrack.mSource != nullptr) { sp audioFormat = mAudioTrack.mSource->getFormat(); if (audioFormat != nullptr) { int64_t duration = INT64_C(0); if (audioFormat->findInt64(kKeyDuration, &duration)) { MonitorAutoLock al(mAudioTrack.mTrackMonitor); if (duration > mAudioTrack.mDurationUs) { mAudioTrack.mDurationUs = duration; } } } } // read video duration if (mVideoTrack.mSource != nullptr) { sp videoFormat = mVideoTrack.mSource->getFormat(); if (videoFormat != nullptr) { int64_t duration = INT64_C(0); if (videoFormat->findInt64(kKeyDuration, &duration)) { MonitorAutoLock al(mVideoTrack.mTrackMonitor); if (duration > mVideoTrack.mDurationUs) { mVideoTrack.mDurationUs = duration; } } } } return true; } bool MediaCodecReader::UpdateAudioInfo() { if (mAudioTrack.mSource == nullptr && mAudioTrack.mCodec == nullptr) { // No needs to update AudioInfo if no audio streams. return true; } if (mAudioTrack.mSource == nullptr || mAudioTrack.mCodec == nullptr || !mAudioTrack.mCodec->allocated()) { // Something wrong. MOZ_ASSERT(mAudioTrack.mSource != nullptr, "mAudioTrack.mSource should not be nullptr"); MOZ_ASSERT(mAudioTrack.mCodec != nullptr, "mAudioTrack.mCodec should not be nullptr"); MOZ_ASSERT(mAudioTrack.mCodec->allocated(), "mAudioTrack.mCodec->allocated() should not be false"); return false; } // read audio metadata from MediaSource sp audioSourceFormat = mAudioTrack.mSource->getFormat(); if (audioSourceFormat == nullptr) { return false; } // ensure audio metadata from MediaCodec has been parsed if (!EnsureCodecFormatParsed(mAudioTrack)){ return false; } // read audio metadata from MediaCodec sp audioCodecFormat; if (mAudioTrack.mCodec->getOutputFormat(&audioCodecFormat) != OK || audioCodecFormat == nullptr) { return false; } AString codec_mime; int32_t codec_channel_count = 0; int32_t codec_sample_rate = 0; if (!audioCodecFormat->findString("mime", &codec_mime) || !audioCodecFormat->findInt32("channel-count", &codec_channel_count) || !audioCodecFormat->findInt32("sample-rate", &codec_sample_rate)) { return false; } // Update AudioInfo mInfo.mAudio.mChannels = codec_channel_count; mInfo.mAudio.mRate = codec_sample_rate; return true; } bool MediaCodecReader::UpdateVideoInfo() { if (mVideoTrack.mSource == nullptr && mVideoTrack.mCodec == nullptr) { // No needs to update VideoInfo if no video streams. return true; } if (mVideoTrack.mSource == nullptr || mVideoTrack.mCodec == nullptr || !mVideoTrack.mCodec->allocated()) { // Something wrong. MOZ_ASSERT(mVideoTrack.mSource != nullptr, "mVideoTrack.mSource should not be nullptr"); MOZ_ASSERT(mVideoTrack.mCodec != nullptr, "mVideoTrack.mCodec should not be nullptr"); MOZ_ASSERT(mVideoTrack.mCodec->allocated(), "mVideoTrack.mCodec->allocated() should not be false"); return false; } // read video metadata from MediaSource sp videoSourceFormat = mVideoTrack.mSource->getFormat(); if (videoSourceFormat == nullptr) { return false; } int32_t container_width = 0; int32_t container_height = 0; int32_t container_rotation = 0; if (!videoSourceFormat->findInt32(kKeyWidth, &container_width) || !videoSourceFormat->findInt32(kKeyHeight, &container_height)) { return false; } mVideoTrack.mFrameSize = nsIntSize(container_width, container_height); if (videoSourceFormat->findInt32(kKeyRotation, &container_rotation)) { mVideoTrack.mRotation = container_rotation; } // ensure video metadata from MediaCodec has been parsed if (!EnsureCodecFormatParsed(mVideoTrack)){ return false; } // read video metadata from MediaCodec sp videoCodecFormat; if (mVideoTrack.mCodec->getOutputFormat(&videoCodecFormat) != OK || videoCodecFormat == nullptr) { return false; } AString codec_mime; int32_t codec_width = 0; int32_t codec_height = 0; int32_t codec_stride = 0; int32_t codec_slice_height = 0; int32_t codec_color_format = 0; int32_t codec_crop_left = 0; int32_t codec_crop_top = 0; int32_t codec_crop_right = 0; int32_t codec_crop_bottom = 0; if (!videoCodecFormat->findString("mime", &codec_mime) || !videoCodecFormat->findInt32("width", &codec_width) || !videoCodecFormat->findInt32("height", &codec_height) || !videoCodecFormat->findInt32("stride", &codec_stride) || !videoCodecFormat->findInt32("slice-height", &codec_slice_height) || !videoCodecFormat->findInt32("color-format", &codec_color_format) || !videoCodecFormat->findRect("crop", &codec_crop_left, &codec_crop_top, &codec_crop_right, &codec_crop_bottom)) { return false; } mVideoTrack.mWidth = codec_width; mVideoTrack.mHeight = codec_height; mVideoTrack.mStride = codec_stride; mVideoTrack.mSliceHeight = codec_slice_height; mVideoTrack.mColorFormat = codec_color_format; // Validate the container-reported frame and pictureRect sizes. This ensures // that our video frame creation code doesn't overflow. int32_t display_width = codec_crop_right - codec_crop_left + 1; int32_t display_height = codec_crop_bottom - codec_crop_top + 1; nsIntRect picture_rect(0, 0, mVideoTrack.mWidth, mVideoTrack.mHeight); nsIntSize display_size(display_width, display_height); if (!IsValidVideoRegion(mVideoTrack.mFrameSize, picture_rect, display_size)) { return false; } // Relative picture size gfx::IntRect relative_picture_rect = picture_rect; if (mVideoTrack.mWidth != mVideoTrack.mFrameSize.width || mVideoTrack.mHeight != mVideoTrack.mFrameSize.height) { // Frame size is different from what the container reports. This is legal, // and we will preserve the ratio of the crop rectangle as it // was reported relative to the picture size reported by the container. relative_picture_rect.x = (picture_rect.x * mVideoTrack.mWidth) / mVideoTrack.mFrameSize.width; relative_picture_rect.y = (picture_rect.y * mVideoTrack.mHeight) / mVideoTrack.mFrameSize.height; relative_picture_rect.width = (picture_rect.width * mVideoTrack.mWidth) / mVideoTrack.mFrameSize.width; relative_picture_rect.height = (picture_rect.height * mVideoTrack.mHeight) / mVideoTrack.mFrameSize.height; } // Update VideoInfo mVideoTrack.mPictureRect = picture_rect; mInfo.mVideo.mDisplay = display_size; mVideoTrack.mRelativePictureRect = relative_picture_rect; return true; } status_t MediaCodecReader::FlushCodecData(Track& aTrack) { if (aTrack.mType == Track::kVideo) { // TODO: if we do release TextureClient on a separate thread in the future, // we will have to explicitly cleanup TextureClients which have been // recycled through TextureClient::mRecycleCallback. // Just NO-OP for now. } if (aTrack.mSource == nullptr || aTrack.mCodec == nullptr || !aTrack.mCodec->allocated()) { return UNKNOWN_ERROR; } status_t status = aTrack.mCodec->flush(); aTrack.mFlushed = (status == OK); if (aTrack.mFlushed) { aTrack.mInputIndex = sInvalidInputIndex; } return status; } // Keep filling data if there are available buffers. // FIXME: change to non-blocking read status_t MediaCodecReader::FillCodecInputData(Track& aTrack) { if (aTrack.mSource == nullptr || aTrack.mCodec == nullptr || !aTrack.mCodec->allocated()) { return UNKNOWN_ERROR; } if (aTrack.mInputEndOfStream) { return ERROR_END_OF_STREAM; } if (IsValidTimestampUs(aTrack.mSeekTimeUs) && !aTrack.mFlushed) { FlushCodecData(aTrack); } size_t index = 0; while (aTrack.mInputIndex.isValid() || aTrack.mCodec->dequeueInputBuffer(&index) == OK) { if (!aTrack.mInputIndex.isValid()) { aTrack.mInputIndex = index; } MOZ_ASSERT(aTrack.mInputIndex.isValid(), "aElement.mInputIndex should be valid"); // Start the mSource before we read it. if (aTrack.mSourceIsStopped) { if (aTrack.mSource->start() == OK) { aTrack.mSourceIsStopped = false; } else { return UNKNOWN_ERROR; } } MediaBuffer* source_buffer = nullptr; status_t status = OK; if (IsValidTimestampUs(aTrack.mSeekTimeUs)) { MediaSource::ReadOptions options; options.setSeekTo(aTrack.mSeekTimeUs); status = aTrack.mSource->read(&source_buffer, &options); } else { status = aTrack.mSource->read(&source_buffer); } // read() fails if (status == INFO_FORMAT_CHANGED) { return INFO_FORMAT_CHANGED; } else if (status == ERROR_END_OF_STREAM) { aTrack.mInputEndOfStream = true; aTrack.mCodec->queueInputBuffer(aTrack.mInputIndex.value(), 0, 0, 0, MediaCodec::BUFFER_FLAG_EOS); return ERROR_END_OF_STREAM; } else if (status == -ETIMEDOUT) { return OK; // try it later } else if (status != OK) { return status; } else if (source_buffer == nullptr) { return UNKNOWN_ERROR; } // read() successes aTrack.mInputEndOfStream = false; aTrack.mSeekTimeUs = sInvalidTimestampUs; sp input_buffer = nullptr; if (aTrack.mInputIndex.value() < aTrack.mInputBuffers.size()) { input_buffer = aTrack.mInputBuffers[aTrack.mInputIndex.value()]; } if (input_buffer != nullptr && aTrack.mInputCopier != nullptr && aTrack.mInputCopier->Copy(source_buffer, input_buffer)) { int64_t timestamp = sInvalidTimestampUs; sp codec_format = source_buffer->meta_data(); if (codec_format != nullptr) { codec_format->findInt64(kKeyTime, ×tamp); } status = aTrack.mCodec->queueInputBuffer( aTrack.mInputIndex.value(), input_buffer->offset(), input_buffer->size(), timestamp, 0); if (status == OK) { aTrack.mInputIndex = sInvalidInputIndex; } } source_buffer->release(); if (status != OK) { return status; } } return OK; } status_t MediaCodecReader::GetCodecOutputData(Track& aTrack, CodecBufferInfo& aBuffer, int64_t aThreshold, const TimeStamp& aTimeout) { // Read next frame. CodecBufferInfo info; status_t status = OK; while (status == OK || status == INFO_OUTPUT_BUFFERS_CHANGED || status == -EAGAIN) { int64_t duration = (int64_t)(aTimeout - TimeStamp::Now()).ToMicroseconds(); if (!IsValidDurationUs(duration)) { return -EAGAIN; } status = aTrack.mCodec->dequeueOutputBuffer(&info.mIndex, &info.mOffset, &info.mSize, &info.mTimeUs, &info.mFlags, duration); // Check EOS first. if (status == ERROR_END_OF_STREAM || (info.mFlags & MediaCodec::BUFFER_FLAG_EOS)) { aBuffer = info; aBuffer.mBuffer = aTrack.mOutputBuffers[info.mIndex]; aTrack.mOutputEndOfStream = true; return ERROR_END_OF_STREAM; } if (status == OK) { // Notify mDecoder that we have parsed a video frame. if (aTrack.mType == Track::kVideo) { mDecoder->NotifyDecodedFrames(1, 0, 0); } if (!IsValidTimestampUs(aThreshold) || info.mTimeUs >= aThreshold) { // Get a valid output buffer. break; } else { aTrack.mCodec->releaseOutputBuffer(info.mIndex); } } else if (status == INFO_OUTPUT_BUFFERS_CHANGED) { // Update output buffers of MediaCodec. if (aTrack.mCodec->getOutputBuffers(&aTrack.mOutputBuffers) != OK) { NS_WARNING("Couldn't get output buffers from MediaCodec"); return UNKNOWN_ERROR; } } if (TimeStamp::Now() > aTimeout) { // Don't let this loop run for too long. Try it again later. return -EAGAIN; } } if (status != OK) { // Something wrong. return status; } if (info.mIndex >= aTrack.mOutputBuffers.size()) { NS_WARNING("Couldn't get proper index of output buffers from MediaCodec"); aTrack.mCodec->releaseOutputBuffer(info.mIndex); return UNKNOWN_ERROR; } aBuffer = info; aBuffer.mBuffer = aTrack.mOutputBuffers[info.mIndex]; return OK; } bool MediaCodecReader::EnsureCodecFormatParsed(Track& aTrack) { if (aTrack.mSource == nullptr || aTrack.mCodec == nullptr || !aTrack.mCodec->allocated()) { return false; } sp format; if (aTrack.mCodec->getOutputFormat(&format) == OK) { return true; } status_t status = OK; size_t index = 0; size_t offset = 0; size_t size = 0; int64_t timeUs = INT64_C(0); uint32_t flags = 0; FillCodecInputData(aTrack); while ((status = aTrack.mCodec->dequeueOutputBuffer(&index, &offset, &size, &timeUs, &flags)) != INFO_FORMAT_CHANGED) { if (status == OK) { aTrack.mCodec->releaseOutputBuffer(index); } else if (status == INFO_OUTPUT_BUFFERS_CHANGED) { // Update output buffers of MediaCodec. if (aTrack.mCodec->getOutputBuffers(&aTrack.mOutputBuffers) != OK) { NS_WARNING("Couldn't get output buffers from MediaCodec"); return false; } } else if (status != -EAGAIN) { return false; // something wrong!!! } FillCodecInputData(aTrack); } aTrack.mCodec->releaseOutputBuffer(index); return aTrack.mCodec->getOutputFormat(&format) == OK; } uint8_t* MediaCodecReader::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 = nullptr; // release the previous buffer first mColorConverterBuffer = new uint8_t[yuv420p_size]; mColorConverterBufferSize = yuv420p_size; } return mColorConverterBuffer.get(); } void MediaCodecReader::ClearColorConverterBuffer() { mColorConverterBuffer = nullptr; mColorConverterBufferSize = 0; } // Called on Binder thread. void MediaCodecReader::VideoCodecReserved() { mMediaResourcePromise.ResolveIfExists(true, __func__); } // Called on Binder thread. void MediaCodecReader::VideoCodecCanceled() { mMediaResourcePromise.RejectIfExists(true, __func__); } } // namespace mozilla