/* 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 "RemoteDataDecoder.h" #include #include "AndroidBridge.h" #include "AndroidDecoderModule.h" #include "EMEDecoderModule.h" #include "GLImages.h" #include "JavaCallbacksSupport.h" #include "MediaData.h" #include "MediaInfo.h" #include "SimpleMap.h" #include "VPXDecoder.h" #include "VideoUtils.h" #include "mozilla/java/CodecProxyWrappers.h" #include "mozilla/java/GeckoSurfaceWrappers.h" #include "mozilla/java/SampleBufferWrappers.h" #include "mozilla/java/SampleWrappers.h" #include "mozilla/java/SurfaceAllocatorWrappers.h" #include "nsPromiseFlatString.h" #include "nsThreadUtils.h" #include "prlog.h" #undef LOG #define LOG(arg, ...) \ MOZ_LOG(sAndroidDecoderModuleLog, mozilla::LogLevel::Debug, \ ("RemoteDataDecoder(%p)::%s: " arg, this, __func__, ##__VA_ARGS__)) using namespace mozilla; using namespace mozilla::gl; using media::TimeUnit; namespace mozilla { // Hold a reference to the output buffer until we're ready to release it back to // the Java codec (for rendering or not). class RenderOrReleaseOutput { public: RenderOrReleaseOutput(java::CodecProxy::Param aCodec, java::Sample::Param aSample) : mCodec(aCodec), mSample(aSample) {} virtual ~RenderOrReleaseOutput() { ReleaseOutput(false); } protected: void ReleaseOutput(bool aToRender) { if (mCodec && mSample) { mCodec->ReleaseOutput(mSample, aToRender); mCodec = nullptr; mSample = nullptr; } } private: java::CodecProxy::GlobalRef mCodec; java::Sample::GlobalRef mSample; }; class RemoteVideoDecoder : public RemoteDataDecoder { public: // Render the output to the surface when the frame is sent // to compositor, or release it if not presented. class CompositeListener : private RenderOrReleaseOutput, public layers::SurfaceTextureImage::SetCurrentCallback { public: CompositeListener(java::CodecProxy::Param aCodec, java::Sample::Param aSample) : RenderOrReleaseOutput(aCodec, aSample) {} void operator()(void) override { ReleaseOutput(true); } }; class InputInfo { public: InputInfo() {} InputInfo(const int64_t aDurationUs, const gfx::IntSize& aImageSize, const gfx::IntSize& aDisplaySize) : mDurationUs(aDurationUs), mImageSize(aImageSize), mDisplaySize(aDisplaySize) {} int64_t mDurationUs; gfx::IntSize mImageSize; gfx::IntSize mDisplaySize; }; class CallbacksSupport final : public JavaCallbacksSupport { public: explicit CallbacksSupport(RemoteVideoDecoder* aDecoder) : mDecoder(aDecoder) {} void HandleInput(int64_t aTimestamp, bool aProcessed) override { mDecoder->UpdateInputStatus(aTimestamp, aProcessed); } void HandleOutput(java::Sample::Param aSample, java::SampleBuffer::Param aBuffer) override { MOZ_ASSERT(!aBuffer, "Video sample should be bufferless"); // aSample will be implicitly converted into a GlobalRef. mDecoder->ProcessOutput(std::move(aSample)); } void HandleError(const MediaResult& aError) override { mDecoder->Error(aError); } friend class RemoteDataDecoder; private: RemoteVideoDecoder* mDecoder; }; RemoteVideoDecoder(const VideoInfo& aConfig, java::sdk::MediaFormat::Param aFormat, const nsString& aDrmStubId) : RemoteDataDecoder(MediaData::Type::VIDEO_DATA, aConfig.mMimeType, aFormat, aDrmStubId), mConfig(aConfig) {} ~RemoteVideoDecoder() { if (mSurface) { java::SurfaceAllocator::DisposeSurface(mSurface); } } RefPtr Init() override { mThread = GetCurrentSerialEventTarget(); java::sdk::BufferInfo::LocalRef bufferInfo; if (NS_FAILED(java::sdk::BufferInfo::New(&bufferInfo)) || !bufferInfo) { return InitPromise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY, __func__); } mInputBufferInfo = bufferInfo; mSurface = java::GeckoSurface::LocalRef(java::SurfaceAllocator::AcquireSurface( mConfig.mImage.width, mConfig.mImage.height, false)); if (!mSurface) { return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } mSurfaceHandle = mSurface->GetHandle(); // Register native methods. JavaCallbacksSupport::Init(); mJavaCallbacks = java::CodecProxy::NativeCallbacks::New(); if (!mJavaCallbacks) { return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } JavaCallbacksSupport::AttachNative( mJavaCallbacks, mozilla::MakeUnique(this)); mJavaDecoder = java::CodecProxy::Create( false, // false indicates to create a decoder and true denotes encoder mFormat, mSurface, mJavaCallbacks, mDrmStubId); if (mJavaDecoder == nullptr) { return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } mIsCodecSupportAdaptivePlayback = mJavaDecoder->IsAdaptivePlaybackSupported(); mIsHardwareAccelerated = mJavaDecoder->IsHardwareAccelerated(); return InitPromise::CreateAndResolve(TrackInfo::kVideoTrack, __func__); } RefPtr Flush() override { AssertOnThread(); mInputInfos.Clear(); mSeekTarget.reset(); mLatestOutputTime.reset(); return RemoteDataDecoder::Flush(); } RefPtr Decode( MediaRawData* aSample) override { AssertOnThread(); const VideoInfo* config = aSample->mTrackInfo ? aSample->mTrackInfo->GetAsVideoInfo() : &mConfig; MOZ_ASSERT(config); InputInfo info(aSample->mDuration.ToMicroseconds(), config->mImage, config->mDisplay); mInputInfos.Insert(aSample->mTime.ToMicroseconds(), info); return RemoteDataDecoder::Decode(aSample); } bool SupportDecoderRecycling() const override { return mIsCodecSupportAdaptivePlayback; } void SetSeekThreshold(const TimeUnit& aTime) override { auto setter = [self = RefPtr{this}, aTime] { if (aTime.IsValid()) { self->mSeekTarget = Some(aTime); } else { self->mSeekTarget.reset(); } }; if (mThread->IsOnCurrentThread()) { setter(); } else { nsCOMPtr runnable = NS_NewRunnableFunction( "RemoteVideoDecoder::SetSeekThreshold", std::move(setter)); nsresult rv = mThread->Dispatch(runnable.forget()); MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); Unused << rv; } } bool IsUsefulData(const RefPtr& aSample) override { AssertOnThread(); if (mLatestOutputTime && aSample->mTime < mLatestOutputTime.value()) { return false; } const TimeUnit endTime = aSample->GetEndTime(); if (mSeekTarget && endTime <= mSeekTarget.value()) { return false; } mSeekTarget.reset(); mLatestOutputTime = Some(endTime); return true; } bool IsHardwareAccelerated(nsACString& aFailureReason) const override { return mIsHardwareAccelerated; } ConversionRequired NeedsConversion() const override { return ConversionRequired::kNeedAnnexB; } private: // Param and LocalRef are only valid for the duration of a JNI method call. // Use GlobalRef as the parameter type to keep the Java object referenced // until running. void ProcessOutput(java::Sample::GlobalRef&& aSample) { if (!mThread->IsOnCurrentThread()) { nsresult rv = mThread->Dispatch(NewRunnableMethod( "RemoteVideoDecoder::ProcessOutput", this, &RemoteVideoDecoder::ProcessOutput, std::move(aSample))); MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); Unused << rv; return; } AssertOnThread(); if (GetState() == State::SHUTDOWN) { aSample->Dispose(); return; } UniquePtr releaseSample( new CompositeListener(mJavaDecoder, aSample)); java::sdk::BufferInfo::LocalRef info = aSample->Info(); MOZ_ASSERT(info); int32_t flags; bool ok = NS_SUCCEEDED(info->Flags(&flags)); int32_t offset; ok &= NS_SUCCEEDED(info->Offset(&offset)); int32_t size; ok &= NS_SUCCEEDED(info->Size(&size)); int64_t presentationTimeUs; ok &= NS_SUCCEEDED(info->PresentationTimeUs(&presentationTimeUs)); if (!ok) { Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, RESULT_DETAIL("VideoCallBack::HandleOutput"))); return; } InputInfo inputInfo; ok = mInputInfos.Find(presentationTimeUs, inputInfo); bool isEOS = !!(flags & java::sdk::MediaCodec::BUFFER_FLAG_END_OF_STREAM); if (!ok && !isEOS) { // Ignore output with no corresponding input. return; } if (ok && (size > 0 || presentationTimeUs >= 0)) { RefPtr img = new layers::SurfaceTextureImage( mSurfaceHandle, inputInfo.mImageSize, false /* NOT continuous */, gl::OriginPos::BottomLeft, mConfig.HasAlpha()); img->AsSurfaceTextureImage()->RegisterSetCurrentCallback( std::move(releaseSample)); RefPtr v = VideoData::CreateFromImage( inputInfo.mDisplaySize, offset, TimeUnit::FromMicroseconds(presentationTimeUs), TimeUnit::FromMicroseconds(inputInfo.mDurationUs), img.forget(), !!(flags & java::sdk::MediaCodec::BUFFER_FLAG_SYNC_FRAME), TimeUnit::FromMicroseconds(presentationTimeUs)); RemoteDataDecoder::UpdateOutputStatus(std::move(v)); } if (isEOS) { DrainComplete(); } } const VideoInfo mConfig; java::GeckoSurface::GlobalRef mSurface; AndroidSurfaceTextureHandle mSurfaceHandle; // Only accessed on reader's task queue. bool mIsCodecSupportAdaptivePlayback = false; // Can be accessed on any thread, but only written on during init. bool mIsHardwareAccelerated = false; // Accessed on mThread and reader's thread. SimpleMap however is // thread-safe, so it's okay to do so. SimpleMap mInputInfos; // Only accessed on mThread. Maybe mSeekTarget; Maybe mLatestOutputTime; }; class RemoteAudioDecoder : public RemoteDataDecoder { public: RemoteAudioDecoder(const AudioInfo& aConfig, java::sdk::MediaFormat::Param aFormat, const nsString& aDrmStubId) : RemoteDataDecoder(MediaData::Type::AUDIO_DATA, aConfig.mMimeType, aFormat, aDrmStubId) { JNIEnv* const env = jni::GetEnvForThread(); bool formatHasCSD = false; NS_ENSURE_SUCCESS_VOID(aFormat->ContainsKey(u"csd-0"_ns, &formatHasCSD)); if (!formatHasCSD && aConfig.mCodecSpecificConfig->Length() >= 2) { jni::ByteBuffer::LocalRef buffer(env); buffer = jni::ByteBuffer::New(aConfig.mCodecSpecificConfig->Elements(), aConfig.mCodecSpecificConfig->Length()); NS_ENSURE_SUCCESS_VOID(aFormat->SetByteBuffer(u"csd-0"_ns, buffer)); } } RefPtr Init() override { mThread = GetCurrentSerialEventTarget(); java::sdk::BufferInfo::LocalRef bufferInfo; if (NS_FAILED(java::sdk::BufferInfo::New(&bufferInfo)) || !bufferInfo) { return InitPromise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY, __func__); } mInputBufferInfo = bufferInfo; // Register native methods. JavaCallbacksSupport::Init(); mJavaCallbacks = java::CodecProxy::NativeCallbacks::New(); if (!mJavaCallbacks) { return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } JavaCallbacksSupport::AttachNative( mJavaCallbacks, mozilla::MakeUnique(this)); mJavaDecoder = java::CodecProxy::Create(false, mFormat, nullptr, mJavaCallbacks, mDrmStubId); if (mJavaDecoder == nullptr) { return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); } return InitPromise::CreateAndResolve(TrackInfo::kAudioTrack, __func__); } RefPtr Flush() override { AssertOnThread(); mFirstDemuxedSampleTime.reset(); return RemoteDataDecoder::Flush(); } RefPtr Decode(MediaRawData* aSample) override { AssertOnThread(); if (!mFirstDemuxedSampleTime) { MOZ_ASSERT(aSample->mTime.IsValid()); mFirstDemuxedSampleTime.emplace(aSample->mTime); } return RemoteDataDecoder::Decode(aSample); } private: class CallbacksSupport final : public JavaCallbacksSupport { public: explicit CallbacksSupport(RemoteAudioDecoder* aDecoder) : mDecoder(aDecoder) {} void HandleInput(int64_t aTimestamp, bool aProcessed) override { mDecoder->UpdateInputStatus(aTimestamp, aProcessed); } void HandleOutput(java::Sample::Param aSample, java::SampleBuffer::Param aBuffer) override { MOZ_ASSERT(aBuffer, "Audio sample should have buffer"); // aSample will be implicitly converted into a GlobalRef. mDecoder->ProcessOutput(std::move(aSample), std::move(aBuffer)); } void HandleOutputFormatChanged( java::sdk::MediaFormat::Param aFormat) override { int32_t outputChannels = 0; aFormat->GetInteger(u"channel-count"_ns, &outputChannels); AudioConfig::ChannelLayout layout(outputChannels); if (!layout.IsValid()) { mDecoder->Error(MediaResult( NS_ERROR_DOM_MEDIA_FATAL_ERR, RESULT_DETAIL("Invalid channel layout:%d", outputChannels))); return; } int32_t sampleRate = 0; aFormat->GetInteger(u"sample-rate"_ns, &sampleRate); LOG("Audio output format changed: channels:%d sample rate:%d", outputChannels, sampleRate); mDecoder->ProcessOutputFormatChange(outputChannels, sampleRate); } void HandleError(const MediaResult& aError) override { mDecoder->Error(aError); } private: RemoteAudioDecoder* mDecoder; }; bool IsSampleTimeSmallerThanFirstDemuxedSampleTime(int64_t aTime) const { return mFirstDemuxedSampleTime->ToMicroseconds() > aTime; } bool ShouldDiscardSample(int64_t aSession) const { AssertOnThread(); // HandleOutput() runs on Android binder thread pool and could be preempted // by RemoteDateDecoder task queue. That means ProcessOutput() could be // scheduled after Shutdown() or Flush(). We won't need the // sample which is returned after calling Shutdown() and Flush(). We can // check mFirstDemuxedSampleTime to know whether the Flush() has been // called, becasue it would be reset in Flush(). return GetState() == State::SHUTDOWN || !mFirstDemuxedSampleTime || mSession != aSession; } // Param and LocalRef are only valid for the duration of a JNI method call. // Use GlobalRef as the parameter type to keep the Java object referenced // until running. void ProcessOutput(java::Sample::GlobalRef&& aSample, java::SampleBuffer::GlobalRef&& aBuffer) { if (!mThread->IsOnCurrentThread()) { nsresult rv = mThread->Dispatch(NewRunnableMethod( "RemoteAudioDecoder::ProcessOutput", this, &RemoteAudioDecoder::ProcessOutput, std::move(aSample), std::move(aBuffer))); MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); Unused << rv; return; } AssertOnThread(); if (ShouldDiscardSample(aSample->Session()) || !aBuffer->IsValid()) { aSample->Dispose(); return; } RenderOrReleaseOutput autoRelease(mJavaDecoder, aSample); java::sdk::BufferInfo::LocalRef info = aSample->Info(); MOZ_ASSERT(info); int32_t flags = 0; bool ok = NS_SUCCEEDED(info->Flags(&flags)); bool isEOS = !!(flags & java::sdk::MediaCodec::BUFFER_FLAG_END_OF_STREAM); int32_t offset; ok &= NS_SUCCEEDED(info->Offset(&offset)); int64_t presentationTimeUs; ok &= NS_SUCCEEDED(info->PresentationTimeUs(&presentationTimeUs)); int32_t size; ok &= NS_SUCCEEDED(info->Size(&size)); if (!ok || (IsSampleTimeSmallerThanFirstDemuxedSampleTime(presentationTimeUs) && !isEOS)) { Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__)); return; } if (size > 0) { #ifdef MOZ_SAMPLE_TYPE_S16 const int32_t numSamples = size / 2; #else # error We only support 16-bit integer PCM #endif AlignedAudioBuffer audio(numSamples); if (!audio) { Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__)); return; } jni::ByteBuffer::LocalRef dest = jni::ByteBuffer::New(audio.get(), size); aBuffer->WriteToByteBuffer(dest, offset, size); RefPtr data = new AudioData(0, TimeUnit::FromMicroseconds(presentationTimeUs), std::move(audio), mOutputChannels, mOutputSampleRate); UpdateOutputStatus(std::move(data)); } if (isEOS) { DrainComplete(); } } void ProcessOutputFormatChange(int32_t aChannels, int32_t aSampleRate) { if (!mThread->IsOnCurrentThread()) { nsresult rv = mThread->Dispatch(NewRunnableMethod( "RemoteAudioDecoder::ProcessOutputFormatChange", this, &RemoteAudioDecoder::ProcessOutputFormatChange, aChannels, aSampleRate)); MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); Unused << rv; return; } AssertOnThread(); mOutputChannels = aChannels; mOutputSampleRate = aSampleRate; } int32_t mOutputChannels; int32_t mOutputSampleRate; Maybe mFirstDemuxedSampleTime; }; already_AddRefed RemoteDataDecoder::CreateAudioDecoder( const CreateDecoderParams& aParams, const nsString& aDrmStubId, CDMProxy* aProxy) { const AudioInfo& config = aParams.AudioConfig(); java::sdk::MediaFormat::LocalRef format; NS_ENSURE_SUCCESS( java::sdk::MediaFormat::CreateAudioFormat(config.mMimeType, config.mRate, config.mChannels, &format), nullptr); RefPtr decoder = new RemoteAudioDecoder(config, format, aDrmStubId); if (aProxy) { decoder = new EMEMediaDataDecoderProxy(aParams, decoder.forget(), aProxy); } return decoder.forget(); } already_AddRefed RemoteDataDecoder::CreateVideoDecoder( const CreateDecoderParams& aParams, const nsString& aDrmStubId, CDMProxy* aProxy) { const VideoInfo& config = aParams.VideoConfig(); java::sdk::MediaFormat::LocalRef format; NS_ENSURE_SUCCESS(java::sdk::MediaFormat::CreateVideoFormat( TranslateMimeType(config.mMimeType), config.mImage.width, config.mImage.height, &format), nullptr); RefPtr decoder = new RemoteVideoDecoder(config, format, aDrmStubId); if (aProxy) { decoder = new EMEMediaDataDecoderProxy(aParams, decoder.forget(), aProxy); } return decoder.forget(); } RemoteDataDecoder::RemoteDataDecoder(MediaData::Type aType, const nsACString& aMimeType, java::sdk::MediaFormat::Param aFormat, const nsString& aDrmStubId) : mType(aType), mMimeType(aMimeType), mFormat(aFormat), mDrmStubId(aDrmStubId), mSession(0), mNumPendingInputs(0) {} RefPtr RemoteDataDecoder::Flush() { AssertOnThread(); MOZ_ASSERT(GetState() != State::SHUTDOWN); mDecodedData = DecodedData(); UpdatePendingInputStatus(PendingOp::CLEAR); mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__); mDrainPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__); SetState(State::DRAINED); mJavaDecoder->Flush(); return FlushPromise::CreateAndResolve(true, __func__); } RefPtr RemoteDataDecoder::Drain() { AssertOnThread(); if (GetState() == State::SHUTDOWN) { return DecodePromise::CreateAndReject(NS_ERROR_DOM_MEDIA_CANCELED, __func__); } RefPtr p = mDrainPromise.Ensure(__func__); if (GetState() == State::DRAINED) { // There's no operation to perform other than returning any already // decoded data. ReturnDecodedData(); return p; } if (GetState() == State::DRAINING) { // Draining operation already pending, let it complete its course. return p; } SetState(State::DRAINING); mInputBufferInfo->Set(0, 0, -1, java::sdk::MediaCodec::BUFFER_FLAG_END_OF_STREAM); mSession = mJavaDecoder->Input(nullptr, mInputBufferInfo, nullptr); return p; } RefPtr RemoteDataDecoder::Shutdown() { LOG(""); AssertOnThread(); SetState(State::SHUTDOWN); if (mJavaDecoder) { mJavaDecoder->Release(); mJavaDecoder = nullptr; } if (mJavaCallbacks) { JavaCallbacksSupport::GetNative(mJavaCallbacks)->Cancel(); JavaCallbacksSupport::DisposeNative(mJavaCallbacks); mJavaCallbacks = nullptr; } mFormat = nullptr; return ShutdownPromise::CreateAndResolve(true, __func__); } static java::sdk::CryptoInfo::LocalRef GetCryptoInfoFromSample( const MediaRawData* aSample) { auto& cryptoObj = aSample->mCrypto; if (!cryptoObj.IsEncrypted()) { return nullptr; } java::sdk::CryptoInfo::LocalRef cryptoInfo; nsresult rv = java::sdk::CryptoInfo::New(&cryptoInfo); NS_ENSURE_SUCCESS(rv, nullptr); uint32_t numSubSamples = std::min( cryptoObj.mPlainSizes.Length(), cryptoObj.mEncryptedSizes.Length()); uint32_t totalSubSamplesSize = 0; for (auto& size : cryptoObj.mPlainSizes) { totalSubSamplesSize += size; } for (auto& size : cryptoObj.mEncryptedSizes) { totalSubSamplesSize += size; } // Deep copy the plain sizes so we can modify them. nsTArray plainSizes = cryptoObj.mPlainSizes.Clone(); uint32_t codecSpecificDataSize = aSample->Size() - totalSubSamplesSize; // Size of codec specific data("CSD") for Android java::sdk::MediaCodec usage // should be included in the 1st plain size if it exists. if (!plainSizes.IsEmpty()) { // This shouldn't overflow as the the plain size should be UINT16_MAX at // most, and the CSD should never be that large. Checked int acts like a // diagnostic assert here to help catch if we ever have insane inputs. CheckedUint32 newLeadingPlainSize{plainSizes[0]}; newLeadingPlainSize += codecSpecificDataSize; plainSizes[0] = newLeadingPlainSize.value(); } static const int kExpectedIVLength = 16; auto tempIV(cryptoObj.mIV); auto tempIVLength = tempIV.Length(); MOZ_ASSERT(tempIVLength <= kExpectedIVLength); for (size_t i = tempIVLength; i < kExpectedIVLength; i++) { // Padding with 0 tempIV.AppendElement(0); } auto numBytesOfPlainData = mozilla::jni::IntArray::New( reinterpret_cast(&plainSizes[0]), plainSizes.Length()); auto numBytesOfEncryptedData = mozilla::jni::IntArray::New( reinterpret_cast(&cryptoObj.mEncryptedSizes[0]), cryptoObj.mEncryptedSizes.Length()); auto iv = mozilla::jni::ByteArray::New(reinterpret_cast(&tempIV[0]), tempIV.Length()); auto keyId = mozilla::jni::ByteArray::New( reinterpret_cast(&cryptoObj.mKeyId[0]), cryptoObj.mKeyId.Length()); cryptoInfo->Set(numSubSamples, numBytesOfPlainData, numBytesOfEncryptedData, keyId, iv, java::sdk::MediaCodec::CRYPTO_MODE_AES_CTR); return cryptoInfo; } RefPtr RemoteDataDecoder::Decode( MediaRawData* aSample) { AssertOnThread(); MOZ_ASSERT(GetState() != State::SHUTDOWN); MOZ_ASSERT(aSample != nullptr); jni::ByteBuffer::LocalRef bytes = jni::ByteBuffer::New( const_cast(aSample->Data()), aSample->Size()); SetState(State::DRAINABLE); mInputBufferInfo->Set(0, aSample->Size(), aSample->mTime.ToMicroseconds(), 0); int64_t session = mJavaDecoder->Input(bytes, mInputBufferInfo, GetCryptoInfoFromSample(aSample)); if (session == java::CodecProxy::INVALID_SESSION) { return DecodePromise::CreateAndReject( MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__), __func__); } mSession = session; return mDecodePromise.Ensure(__func__); } void RemoteDataDecoder::UpdatePendingInputStatus(PendingOp aOp) { AssertOnThread(); switch (aOp) { case PendingOp::INCREASE: mNumPendingInputs++; break; case PendingOp::DECREASE: mNumPendingInputs--; break; case PendingOp::CLEAR: mNumPendingInputs = 0; break; } } void RemoteDataDecoder::UpdateInputStatus(int64_t aTimestamp, bool aProcessed) { if (!mThread->IsOnCurrentThread()) { nsresult rv = mThread->Dispatch(NewRunnableMethod( "RemoteDataDecoder::UpdateInputStatus", this, &RemoteDataDecoder::UpdateInputStatus, aTimestamp, aProcessed)); MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); Unused << rv; return; } AssertOnThread(); if (GetState() == State::SHUTDOWN) { return; } if (!aProcessed) { UpdatePendingInputStatus(PendingOp::INCREASE); } else if (HasPendingInputs()) { UpdatePendingInputStatus(PendingOp::DECREASE); } if (!HasPendingInputs() || // Input has been processed, request the next one. !mDecodedData.IsEmpty()) { // Previous output arrived before Decode(). ReturnDecodedData(); } } void RemoteDataDecoder::UpdateOutputStatus(RefPtr&& aSample) { AssertOnThread(); if (GetState() == State::SHUTDOWN) { return; } if (IsUsefulData(aSample)) { mDecodedData.AppendElement(std::move(aSample)); } ReturnDecodedData(); } void RemoteDataDecoder::ReturnDecodedData() { AssertOnThread(); MOZ_ASSERT(GetState() != State::SHUTDOWN); // We only want to clear mDecodedData when we have resolved the promises. if (!mDecodePromise.IsEmpty()) { mDecodePromise.Resolve(std::move(mDecodedData), __func__); mDecodedData = DecodedData(); } else if (!mDrainPromise.IsEmpty() && (!mDecodedData.IsEmpty() || GetState() == State::DRAINED)) { mDrainPromise.Resolve(std::move(mDecodedData), __func__); mDecodedData = DecodedData(); } } void RemoteDataDecoder::DrainComplete() { if (!mThread->IsOnCurrentThread()) { nsresult rv = mThread->Dispatch( NewRunnableMethod("RemoteDataDecoder::DrainComplete", this, &RemoteDataDecoder::DrainComplete)); MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); Unused << rv; return; } AssertOnThread(); if (GetState() == State::SHUTDOWN) { return; } SetState(State::DRAINED); ReturnDecodedData(); } void RemoteDataDecoder::Error(const MediaResult& aError) { if (!mThread->IsOnCurrentThread()) { nsresult rv = mThread->Dispatch(NewRunnableMethod( "RemoteDataDecoder::Error", this, &RemoteDataDecoder::Error, aError)); MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); Unused << rv; return; } AssertOnThread(); if (GetState() == State::SHUTDOWN) { return; } mDecodePromise.RejectIfExists(aError, __func__); mDrainPromise.RejectIfExists(aError, __func__); } } // namespace mozilla #undef LOG