diff options
Diffstat (limited to 'dom/media/platforms/android')
-rw-r--r-- | dom/media/platforms/android/AndroidDecoderModule.cpp | 227 | ||||
-rw-r--r-- | dom/media/platforms/android/AndroidDecoderModule.h | 34 | ||||
-rw-r--r-- | dom/media/platforms/android/MediaCodecDataDecoder.cpp | 698 | ||||
-rw-r--r-- | dom/media/platforms/android/MediaCodecDataDecoder.h | 126 | ||||
-rw-r--r-- | dom/media/platforms/android/RemoteDataDecoder.cpp | 489 | ||||
-rw-r--r-- | dom/media/platforms/android/RemoteDataDecoder.h | 62 |
6 files changed, 1636 insertions, 0 deletions
diff --git a/dom/media/platforms/android/AndroidDecoderModule.cpp b/dom/media/platforms/android/AndroidDecoderModule.cpp new file mode 100644 index 000000000..3bf5cbf09 --- /dev/null +++ b/dom/media/platforms/android/AndroidDecoderModule.cpp @@ -0,0 +1,227 @@ +/* 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 "AndroidDecoderModule.h" +#include "AndroidBridge.h" + +#include "MediaCodecDataDecoder.h" +#include "RemoteDataDecoder.h" + +#include "MediaInfo.h" +#include "VPXDecoder.h" + +#include "MediaPrefs.h" +#include "OpusDecoder.h" +#include "VorbisDecoder.h" + +#include "nsPromiseFlatString.h" +#include "nsIGfxInfo.h" + +#include "prlog.h" + +#include <jni.h> + +#undef LOG +#define LOG(arg, ...) MOZ_LOG(sAndroidDecoderModuleLog, \ + mozilla::LogLevel::Debug, ("AndroidDecoderModule(%p)::%s: " arg, \ + this, __func__, ##__VA_ARGS__)) + +using namespace mozilla; +using namespace mozilla::gl; +using namespace mozilla::java::sdk; +using media::TimeUnit; + +namespace mozilla { + +mozilla::LazyLogModule sAndroidDecoderModuleLog("AndroidDecoderModule"); + +static const char* +TranslateMimeType(const nsACString& aMimeType) +{ + if (VPXDecoder::IsVPX(aMimeType, VPXDecoder::VP8)) { + return "video/x-vnd.on2.vp8"; + } else if (VPXDecoder::IsVPX(aMimeType, VPXDecoder::VP9)) { + return "video/x-vnd.on2.vp9"; + } + return PromiseFlatCString(aMimeType).get(); +} + +static bool +GetFeatureStatus(int32_t aFeature) +{ + nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo(); + int32_t status = nsIGfxInfo::FEATURE_STATUS_UNKNOWN; + nsCString discardFailureId; + if (!gfxInfo || NS_FAILED(gfxInfo->GetFeatureStatus(aFeature, discardFailureId, &status))) { + return false; + } + return status == nsIGfxInfo::FEATURE_STATUS_OK; +}; + +CryptoInfo::LocalRef +GetCryptoInfoFromSample(const MediaRawData* aSample) +{ + auto& cryptoObj = aSample->mCrypto; + + if (!cryptoObj.mValid) { + return nullptr; + } + + CryptoInfo::LocalRef cryptoInfo; + nsresult rv = CryptoInfo::New(&cryptoInfo); + NS_ENSURE_SUCCESS(rv, nullptr); + + uint32_t numSubSamples = + std::min<uint32_t>(cryptoObj.mPlainSizes.Length(), cryptoObj.mEncryptedSizes.Length()); + + uint32_t totalSubSamplesSize = 0; + for (auto& size : cryptoObj.mEncryptedSizes) { + totalSubSamplesSize += size; + } + + // mPlainSizes is uint16_t, need to transform to uint32_t first. + nsTArray<uint32_t> plainSizes; + for (auto& size : cryptoObj.mPlainSizes) { + totalSubSamplesSize += size; + plainSizes.AppendElement(size); + } + + uint32_t codecSpecificDataSize = aSample->Size() - totalSubSamplesSize; + // Size of codec specific data("CSD") for Android MediaCodec usage should be + // included in the 1st plain size. + plainSizes[0] += codecSpecificDataSize; + + 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<int32_t*>(&plainSizes[0]), + plainSizes.Length()); + + auto numBytesOfEncryptedData = + mozilla::jni::IntArray::New(reinterpret_cast<const int32_t*>(&cryptoObj.mEncryptedSizes[0]), + cryptoObj.mEncryptedSizes.Length()); + auto iv = mozilla::jni::ByteArray::New(reinterpret_cast<int8_t*>(&tempIV[0]), + tempIV.Length()); + auto keyId = mozilla::jni::ByteArray::New(reinterpret_cast<const int8_t*>(&cryptoObj.mKeyId[0]), + cryptoObj.mKeyId.Length()); + cryptoInfo->Set(numSubSamples, + numBytesOfPlainData, + numBytesOfEncryptedData, + keyId, + iv, + MediaCodec::CRYPTO_MODE_AES_CTR); + + return cryptoInfo; +} + +bool +AndroidDecoderModule::SupportsMimeType(const nsACString& aMimeType, + DecoderDoctorDiagnostics* aDiagnostics) const +{ + if (!AndroidBridge::Bridge() || + AndroidBridge::Bridge()->GetAPIVersion() < 16) { + return false; + } + + if (aMimeType.EqualsLiteral("video/mp4") || + aMimeType.EqualsLiteral("video/avc")) { + return true; + } + + // When checking "audio/x-wav", CreateDecoder can cause a JNI ERROR by + // Accessing a stale local reference leading to a SIGSEGV crash. + // To avoid this we check for wav types here. + if (aMimeType.EqualsLiteral("audio/x-wav") || + aMimeType.EqualsLiteral("audio/wave; codecs=1") || + aMimeType.EqualsLiteral("audio/wave; codecs=6") || + aMimeType.EqualsLiteral("audio/wave; codecs=7") || + aMimeType.EqualsLiteral("audio/wave; codecs=65534")) { + return false; + } + + if ((VPXDecoder::IsVPX(aMimeType, VPXDecoder::VP8) && + !GetFeatureStatus(nsIGfxInfo::FEATURE_VP8_HW_DECODE)) || + (VPXDecoder::IsVPX(aMimeType, VPXDecoder::VP9) && + !GetFeatureStatus(nsIGfxInfo::FEATURE_VP9_HW_DECODE))) { + return false; + } + + // Prefer the gecko decoder for opus and vorbis; stagefright crashes + // on content demuxed from mp4. + if (OpusDataDecoder::IsOpus(aMimeType) || + VorbisDataDecoder::IsVorbis(aMimeType)) { + LOG("Rejecting audio of type %s", aMimeType.Data()); + return false; + } + + return java::HardwareCodecCapabilityUtils::FindDecoderCodecInfoForMimeType( + nsCString(TranslateMimeType(aMimeType))); +} + +already_AddRefed<MediaDataDecoder> +AndroidDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams) +{ + MediaFormat::LocalRef format; + + const VideoInfo& config = aParams.VideoConfig(); + NS_ENSURE_SUCCESS(MediaFormat::CreateVideoFormat( + TranslateMimeType(config.mMimeType), + config.mDisplay.width, + config.mDisplay.height, + &format), nullptr); + + RefPtr<MediaDataDecoder> decoder = MediaPrefs::PDMAndroidRemoteCodecEnabled() ? + RemoteDataDecoder::CreateVideoDecoder(config, + format, + aParams.mCallback, + aParams.mImageContainer) : + MediaCodecDataDecoder::CreateVideoDecoder(config, + format, + aParams.mCallback, + aParams.mImageContainer); + + return decoder.forget(); +} + +already_AddRefed<MediaDataDecoder> +AndroidDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams) +{ + const AudioInfo& config = aParams.AudioConfig(); + MOZ_ASSERT(config.mBitDepth == 16, "We only handle 16-bit audio!"); + + MediaFormat::LocalRef format; + + LOG("CreateAudioFormat with mimeType=%s, mRate=%d, channels=%d", + config.mMimeType.Data(), config.mRate, config.mChannels); + + NS_ENSURE_SUCCESS(MediaFormat::CreateAudioFormat( + config.mMimeType, + config.mRate, + config.mChannels, + &format), nullptr); + + RefPtr<MediaDataDecoder> decoder = MediaPrefs::PDMAndroidRemoteCodecEnabled() ? + RemoteDataDecoder::CreateAudioDecoder(config, format, aParams.mCallback) : + MediaCodecDataDecoder::CreateAudioDecoder(config, format, aParams.mCallback); + + return decoder.forget(); +} + +PlatformDecoderModule::ConversionRequired +AndroidDecoderModule::DecoderNeedsConversion(const TrackInfo& aConfig) const +{ + if (aConfig.IsVideo()) { + return ConversionRequired::kNeedAnnexB; + } + return ConversionRequired::kNeedNone; +} + +} // mozilla diff --git a/dom/media/platforms/android/AndroidDecoderModule.h b/dom/media/platforms/android/AndroidDecoderModule.h new file mode 100644 index 000000000..339cbb311 --- /dev/null +++ b/dom/media/platforms/android/AndroidDecoderModule.h @@ -0,0 +1,34 @@ +/* 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/. */ + +#ifndef AndroidDecoderModule_h_ +#define AndroidDecoderModule_h_ + +#include "PlatformDecoderModule.h" + +namespace mozilla { + +class AndroidDecoderModule : public PlatformDecoderModule { +public: + already_AddRefed<MediaDataDecoder> + CreateVideoDecoder(const CreateDecoderParams& aParams) override; + + already_AddRefed<MediaDataDecoder> + CreateAudioDecoder(const CreateDecoderParams& aParams) override; + + AndroidDecoderModule() {} + virtual ~AndroidDecoderModule() {} + + bool SupportsMimeType(const nsACString& aMimeType, + DecoderDoctorDiagnostics* aDiagnostics) const override; + + ConversionRequired + DecoderNeedsConversion(const TrackInfo& aConfig) const override; +}; + +extern LazyLogModule sAndroidDecoderModuleLog; + +} // namespace mozilla + +#endif diff --git a/dom/media/platforms/android/MediaCodecDataDecoder.cpp b/dom/media/platforms/android/MediaCodecDataDecoder.cpp new file mode 100644 index 000000000..3c5df54ee --- /dev/null +++ b/dom/media/platforms/android/MediaCodecDataDecoder.cpp @@ -0,0 +1,698 @@ +/* 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 "MediaCodecDataDecoder.h" + +#include "AndroidBridge.h" +#include "AndroidSurfaceTexture.h" +#include "GeneratedJNINatives.h" +#include "GLImages.h" + +#include "MediaData.h" +#include "MediaInfo.h" +#include "VPXDecoder.h" + +#include "nsThreadUtils.h" +#include "nsPromiseFlatString.h" +#include "nsIGfxInfo.h" + +#include "prlog.h" + +#include <jni.h> + +#undef LOG +#define LOG(arg, ...) MOZ_LOG(sAndroidDecoderModuleLog, \ + mozilla::LogLevel::Debug, ("MediaCodecDataDecoder(%p)::%s: " arg, \ + this, __func__, ##__VA_ARGS__)) + +using namespace mozilla; +using namespace mozilla::gl; +using namespace mozilla::java; +using namespace mozilla::java::sdk; +using media::TimeUnit; + +namespace mozilla { + +#define INVOKE_CALLBACK(Func, ...) \ + if (mCallback) { \ + mCallback->Func(__VA_ARGS__); \ + } else { \ + NS_WARNING("Callback not set"); \ + } + +static MediaCodec::LocalRef +CreateDecoder(const nsACString& aMimeType) +{ + MediaCodec::LocalRef codec; + NS_ENSURE_SUCCESS(MediaCodec::CreateDecoderByType(TranslateMimeType(aMimeType), + &codec), nullptr); + return codec; +} + +class VideoDataDecoder : public MediaCodecDataDecoder +{ +public: + VideoDataDecoder(const VideoInfo& aConfig, + MediaFormat::Param aFormat, + MediaDataDecoderCallback* aCallback, + layers::ImageContainer* aImageContainer) + : MediaCodecDataDecoder(MediaData::Type::VIDEO_DATA, aConfig.mMimeType, + aFormat, aCallback) + , mImageContainer(aImageContainer) + , mConfig(aConfig) + { + + } + + const char* GetDescriptionName() const override + { + return "Android MediaCodec video decoder"; + } + + RefPtr<InitPromise> Init() override + { + mSurfaceTexture = AndroidSurfaceTexture::Create(); + if (!mSurfaceTexture) { + NS_WARNING("Failed to create SurfaceTexture for video decode\n"); + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); + } + + if (NS_FAILED(InitDecoder(mSurfaceTexture->JavaSurface()))) { + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); + } + + return InitPromise::CreateAndResolve(TrackInfo::kVideoTrack, __func__); + } + + void Cleanup() override + { + } + + nsresult PostOutput(BufferInfo::Param aInfo, MediaFormat::Param aFormat, + const TimeUnit& aDuration) override + { + RefPtr<layers::Image> img = + new SurfaceTextureImage(mSurfaceTexture.get(), mConfig.mDisplay, + gl::OriginPos::BottomLeft); + + nsresult rv; + int32_t flags; + NS_ENSURE_SUCCESS(rv = aInfo->Flags(&flags), rv); + + bool isSync = !!(flags & MediaCodec::BUFFER_FLAG_SYNC_FRAME); + + int32_t offset; + NS_ENSURE_SUCCESS(rv = aInfo->Offset(&offset), rv); + + int64_t presentationTimeUs; + NS_ENSURE_SUCCESS(rv = aInfo->PresentationTimeUs(&presentationTimeUs), rv); + + RefPtr<VideoData> v = + VideoData::CreateFromImage(mConfig, + offset, + presentationTimeUs, + aDuration.ToMicroseconds(), + img, + isSync, + presentationTimeUs, + gfx::IntRect(0, 0, + mConfig.mDisplay.width, + mConfig.mDisplay.height)); + INVOKE_CALLBACK(Output, v); + return NS_OK; + } + +protected: + layers::ImageContainer* mImageContainer; + const VideoInfo& mConfig; + RefPtr<AndroidSurfaceTexture> mSurfaceTexture; +}; + +class AudioDataDecoder : public MediaCodecDataDecoder +{ +public: + AudioDataDecoder(const AudioInfo& aConfig, MediaFormat::Param aFormat, + MediaDataDecoderCallback* aCallback) + : MediaCodecDataDecoder(MediaData::Type::AUDIO_DATA, aConfig.mMimeType, + aFormat, aCallback) + { + JNIEnv* const env = jni::GetEnvForThread(); + + jni::ByteBuffer::LocalRef buffer(env); + NS_ENSURE_SUCCESS_VOID(aFormat->GetByteBuffer(NS_LITERAL_STRING("csd-0"), + &buffer)); + + if (!buffer && aConfig.mCodecSpecificConfig->Length() >= 2) { + buffer = jni::ByteBuffer::New( + aConfig.mCodecSpecificConfig->Elements(), + aConfig.mCodecSpecificConfig->Length()); + NS_ENSURE_SUCCESS_VOID(aFormat->SetByteBuffer(NS_LITERAL_STRING("csd-0"), + buffer)); + } + } + + const char* GetDescriptionName() const override + { + return "android audio decoder"; + } + + nsresult Output(BufferInfo::Param aInfo, void* aBuffer, + MediaFormat::Param aFormat, const TimeUnit& aDuration) + { + // The output on Android is always 16-bit signed + nsresult rv; + int32_t numChannels; + NS_ENSURE_SUCCESS(rv = + aFormat->GetInteger(NS_LITERAL_STRING("channel-count"), &numChannels), rv); + AudioConfig::ChannelLayout layout(numChannels); + if (!layout.IsValid()) { + return NS_ERROR_FAILURE; + } + + int32_t sampleRate; + NS_ENSURE_SUCCESS(rv = + aFormat->GetInteger(NS_LITERAL_STRING("sample-rate"), &sampleRate), rv); + + int32_t size; + NS_ENSURE_SUCCESS(rv = aInfo->Size(&size), rv); + + int32_t offset; + NS_ENSURE_SUCCESS(rv = aInfo->Offset(&offset), rv); + +#ifdef MOZ_SAMPLE_TYPE_S16 + const int32_t numSamples = size / 2; +#else +#error We only support 16-bit integer PCM +#endif + + const int32_t numFrames = numSamples / numChannels; + AlignedAudioBuffer audio(numSamples); + if (!audio) { + return NS_ERROR_OUT_OF_MEMORY; + } + + const uint8_t* bufferStart = static_cast<uint8_t*>(aBuffer) + offset; + PodCopy(audio.get(), reinterpret_cast<const AudioDataValue*>(bufferStart), + numSamples); + + int64_t presentationTimeUs; + NS_ENSURE_SUCCESS(rv = aInfo->PresentationTimeUs(&presentationTimeUs), rv); + + RefPtr<AudioData> data = new AudioData(0, presentationTimeUs, + aDuration.ToMicroseconds(), + numFrames, + Move(audio), + numChannels, + sampleRate); + INVOKE_CALLBACK(Output, data); + return NS_OK; + } +}; + +MediaDataDecoder* +MediaCodecDataDecoder::CreateAudioDecoder(const AudioInfo& aConfig, + MediaFormat::Param aFormat, + MediaDataDecoderCallback* aCallback) +{ + return new AudioDataDecoder(aConfig, aFormat, aCallback); +} + +MediaDataDecoder* +MediaCodecDataDecoder::CreateVideoDecoder(const VideoInfo& aConfig, + MediaFormat::Param aFormat, + MediaDataDecoderCallback* aCallback, + layers::ImageContainer* aImageContainer) +{ + return new VideoDataDecoder(aConfig, aFormat, aCallback, aImageContainer); +} + +MediaCodecDataDecoder::MediaCodecDataDecoder(MediaData::Type aType, + const nsACString& aMimeType, + MediaFormat::Param aFormat, + MediaDataDecoderCallback* aCallback) + : mType(aType) + , mMimeType(aMimeType) + , mFormat(aFormat) + , mCallback(aCallback) + , mInputBuffers(nullptr) + , mOutputBuffers(nullptr) + , mMonitor("MediaCodecDataDecoder::mMonitor") + , mState(ModuleState::kDecoding) +{ + +} + +MediaCodecDataDecoder::~MediaCodecDataDecoder() +{ + Shutdown(); +} + +RefPtr<MediaDataDecoder::InitPromise> +MediaCodecDataDecoder::Init() +{ + nsresult rv = InitDecoder(nullptr); + + TrackInfo::TrackType type = + (mType == MediaData::AUDIO_DATA ? TrackInfo::TrackType::kAudioTrack + : TrackInfo::TrackType::kVideoTrack); + + return NS_SUCCEEDED(rv) ? + InitPromise::CreateAndResolve(type, __func__) : + InitPromise::CreateAndReject( + NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); +} + +nsresult +MediaCodecDataDecoder::InitDecoder(Surface::Param aSurface) +{ + mDecoder = CreateDecoder(mMimeType); + if (!mDecoder) { + INVOKE_CALLBACK(Error, + MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__)); + return NS_ERROR_FAILURE; + } + + nsresult rv; + NS_ENSURE_SUCCESS(rv = mDecoder->Configure(mFormat, aSurface, nullptr, 0), rv); + NS_ENSURE_SUCCESS(rv = mDecoder->Start(), rv); + + NS_ENSURE_SUCCESS(rv = ResetInputBuffers(), rv); + NS_ENSURE_SUCCESS(rv = ResetOutputBuffers(), rv); + + nsCOMPtr<nsIRunnable> r = NewRunnableMethod(this, &MediaCodecDataDecoder::DecoderLoop); + rv = NS_NewNamedThread("MC Decoder", getter_AddRefs(mThread), r); + + return rv; +} + +// This is in usec, so that's 10ms. +static const int64_t kDecoderTimeout = 10000; + +#define BREAK_ON_DECODER_ERROR() \ + if (NS_FAILED(res)) { \ + NS_WARNING("Exiting decoder loop due to exception"); \ + if (mState == ModuleState::kDrainDecoder) { \ + INVOKE_CALLBACK(DrainComplete); \ + SetState(ModuleState::kDecoding); \ + } \ + INVOKE_CALLBACK(Error, MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__)); \ + break; \ + } + +nsresult +MediaCodecDataDecoder::GetInputBuffer( + JNIEnv* aEnv, int aIndex, jni::Object::LocalRef* aBuffer) +{ + MOZ_ASSERT(aEnv); + MOZ_ASSERT(!*aBuffer); + + int numTries = 2; + + while (numTries--) { + *aBuffer = jni::Object::LocalRef::Adopt( + aEnv->GetObjectArrayElement(mInputBuffers.Get(), aIndex)); + if (*aBuffer) { + return NS_OK; + } + nsresult res = ResetInputBuffers(); + if (NS_FAILED(res)) { + return res; + } + } + return NS_ERROR_FAILURE; +} + +bool +MediaCodecDataDecoder::WaitForInput() +{ + MonitorAutoLock lock(mMonitor); + + while (mState == ModuleState::kDecoding && mQueue.empty()) { + // Signal that we require more input. + INVOKE_CALLBACK(InputExhausted); + lock.Wait(); + } + + return mState != ModuleState::kStopping; +} + + +already_AddRefed<MediaRawData> +MediaCodecDataDecoder::PeekNextSample() +{ + MonitorAutoLock lock(mMonitor); + + if (mState == ModuleState::kFlushing) { + mDecoder->Flush(); + ClearQueue(); + SetState(ModuleState::kDecoding); + lock.Notify(); + return nullptr; + } + + if (mQueue.empty()) { + if (mState == ModuleState::kDrainQueue) { + SetState(ModuleState::kDrainDecoder); + } + return nullptr; + } + + // We're not stopping or flushing, so try to get a sample. + return RefPtr<MediaRawData>(mQueue.front()).forget(); +} + +nsresult +MediaCodecDataDecoder::QueueSample(const MediaRawData* aSample) +{ + MOZ_ASSERT(aSample); + AutoLocalJNIFrame frame(jni::GetEnvForThread(), 1); + + // We have a sample, try to feed it to the decoder. + int32_t inputIndex = -1; + nsresult res = mDecoder->DequeueInputBuffer(kDecoderTimeout, &inputIndex); + if (NS_FAILED(res)) { + return res; + } + + if (inputIndex < 0) { + // There is no valid input buffer available. + return NS_ERROR_FAILURE; + } + + jni::Object::LocalRef buffer(frame.GetEnv()); + res = GetInputBuffer(frame.GetEnv(), inputIndex, &buffer); + if (NS_FAILED(res)) { + return res; + } + + void* directBuffer = frame.GetEnv()->GetDirectBufferAddress(buffer.Get()); + + MOZ_ASSERT(frame.GetEnv()->GetDirectBufferCapacity(buffer.Get()) >= + aSample->Size(), + "Decoder buffer is not large enough for sample"); + + PodCopy(static_cast<uint8_t*>(directBuffer), aSample->Data(), aSample->Size()); + + CryptoInfo::LocalRef cryptoInfo = GetCryptoInfoFromSample(aSample); + if (cryptoInfo) { + res = mDecoder->QueueSecureInputBuffer(inputIndex, 0, cryptoInfo, + aSample->mTime, 0); + } else { + res = mDecoder->QueueInputBuffer(inputIndex, 0, aSample->Size(), + aSample->mTime, 0); + } + + if (NS_FAILED(res)) { + return res; + } + + mDurations.push_back(TimeUnit::FromMicroseconds(aSample->mDuration)); + return NS_OK; +} + +nsresult +MediaCodecDataDecoder::QueueEOS() +{ + mMonitor.AssertCurrentThreadOwns(); + + nsresult res = NS_OK; + int32_t inputIndex = -1; + res = mDecoder->DequeueInputBuffer(kDecoderTimeout, &inputIndex); + if (NS_FAILED(res) || inputIndex < 0) { + return res; + } + + res = mDecoder->QueueInputBuffer(inputIndex, 0, 0, 0, + MediaCodec::BUFFER_FLAG_END_OF_STREAM); + if (NS_SUCCEEDED(res)) { + SetState(ModuleState::kDrainWaitEOS); + mMonitor.Notify(); + } + return res; +} + +void +MediaCodecDataDecoder::HandleEOS(int32_t aOutputStatus) +{ + MonitorAutoLock lock(mMonitor); + + if (mState == ModuleState::kDrainWaitEOS) { + SetState(ModuleState::kDecoding); + mMonitor.Notify(); + + INVOKE_CALLBACK(DrainComplete); + } + + mDecoder->ReleaseOutputBuffer(aOutputStatus, false); +} + +Maybe<TimeUnit> +MediaCodecDataDecoder::GetOutputDuration() +{ + if (mDurations.empty()) { + return Nothing(); + } + const Maybe<TimeUnit> duration = Some(mDurations.front()); + mDurations.pop_front(); + return duration; +} + +nsresult +MediaCodecDataDecoder::ProcessOutput( + BufferInfo::Param aInfo, MediaFormat::Param aFormat, int32_t aStatus) +{ + AutoLocalJNIFrame frame(jni::GetEnvForThread(), 1); + + const Maybe<TimeUnit> duration = GetOutputDuration(); + if (!duration) { + // Some devices report failure in QueueSample while actually succeeding at + // it, in which case we get an output buffer without having a cached duration + // (bug 1273523). + return NS_OK; + } + + const auto buffer = jni::Object::LocalRef::Adopt( + frame.GetEnv()->GetObjectArrayElement(mOutputBuffers.Get(), aStatus)); + + if (buffer) { + // The buffer will be null on Android L if we are decoding to a Surface. + void* directBuffer = frame.GetEnv()->GetDirectBufferAddress(buffer.Get()); + Output(aInfo, directBuffer, aFormat, duration.value()); + } + + // The Surface will be updated at this point (for video). + mDecoder->ReleaseOutputBuffer(aStatus, true); + PostOutput(aInfo, aFormat, duration.value()); + + return NS_OK; +} + +void +MediaCodecDataDecoder::DecoderLoop() +{ + bool isOutputDone = false; + AutoLocalJNIFrame frame(jni::GetEnvForThread(), 1); + MediaFormat::LocalRef outputFormat(frame.GetEnv()); + nsresult res = NS_OK; + + while (WaitForInput()) { + RefPtr<MediaRawData> sample = PeekNextSample(); + + { + MonitorAutoLock lock(mMonitor); + if (mState == ModuleState::kDrainDecoder) { + MOZ_ASSERT(!sample, "Shouldn't have a sample when pushing EOF frame"); + res = QueueEOS(); + BREAK_ON_DECODER_ERROR(); + } + } + + if (sample) { + res = QueueSample(sample); + if (NS_SUCCEEDED(res)) { + // We've fed this into the decoder, so remove it from the queue. + MonitorAutoLock lock(mMonitor); + MOZ_RELEASE_ASSERT(mQueue.size(), "Queue may not be empty"); + mQueue.pop_front(); + isOutputDone = false; + } + } + + if (isOutputDone) { + continue; + } + + BufferInfo::LocalRef bufferInfo; + nsresult res = BufferInfo::New(&bufferInfo); + BREAK_ON_DECODER_ERROR(); + + int32_t outputStatus = -1; + res = mDecoder->DequeueOutputBuffer(bufferInfo, kDecoderTimeout, + &outputStatus); + BREAK_ON_DECODER_ERROR(); + + if (outputStatus == MediaCodec::INFO_TRY_AGAIN_LATER) { + // We might want to call mCallback->InputExhausted() here, but there seems + // to be some possible bad interactions here with the threading. + } else if (outputStatus == MediaCodec::INFO_OUTPUT_BUFFERS_CHANGED) { + res = ResetOutputBuffers(); + BREAK_ON_DECODER_ERROR(); + } else if (outputStatus == MediaCodec::INFO_OUTPUT_FORMAT_CHANGED) { + res = mDecoder->GetOutputFormat(ReturnTo(&outputFormat)); + BREAK_ON_DECODER_ERROR(); + } else if (outputStatus < 0) { + NS_WARNING("Unknown error from decoder!"); + INVOKE_CALLBACK(Error, + MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, + __func__)); + // Don't break here just in case it's recoverable. If it's not, other + // stuff will fail later and we'll bail out. + } else { + // We have a valid buffer index >= 0 here. + int32_t flags; + nsresult res = bufferInfo->Flags(&flags); + BREAK_ON_DECODER_ERROR(); + + if (flags & MediaCodec::BUFFER_FLAG_END_OF_STREAM) { + HandleEOS(outputStatus); + isOutputDone = true; + // We only queue empty EOF frames, so we're done for now. + continue; + } + + res = ProcessOutput(bufferInfo, outputFormat, outputStatus); + BREAK_ON_DECODER_ERROR(); + } + } + + Cleanup(); + + // We're done. + MonitorAutoLock lock(mMonitor); + SetState(ModuleState::kShutdown); + mMonitor.Notify(); +} + +const char* +MediaCodecDataDecoder::ModuleStateStr(ModuleState aState) { + switch (aState) { + case ModuleState::kDecoding: return "Decoding"; + case ModuleState::kFlushing: return "Flushing"; + case ModuleState::kDrainQueue: return "DrainQueue"; + case ModuleState::kDrainDecoder: return "DrainDecoder"; + case ModuleState::kDrainWaitEOS: return "DrainWaitEOS"; + case ModuleState::kStopping: return "Stopping"; + case ModuleState::kShutdown: return "Shutdown"; + default: MOZ_ASSERT_UNREACHABLE("Invalid state."); + } + return "Unknown"; +} + +bool +MediaCodecDataDecoder::SetState(ModuleState aState) +{ + bool ok = true; + + if (mState == ModuleState::kShutdown) { + ok = false; + } else if (mState == ModuleState::kStopping) { + ok = aState == ModuleState::kShutdown; + } else if (aState == ModuleState::kDrainDecoder) { + ok = mState == ModuleState::kDrainQueue; + } else if (aState == ModuleState::kDrainWaitEOS) { + ok = mState == ModuleState::kDrainDecoder; + } + + if (ok) { + LOG("%s -> %s", ModuleStateStr(mState), ModuleStateStr(aState)); + mState = aState; + } else { + LOG("Fail to transit from %s to %s state", ModuleStateStr(mState), ModuleStateStr(aState)); + } + + return ok; +} + +void +MediaCodecDataDecoder::ClearQueue() +{ + mMonitor.AssertCurrentThreadOwns(); + + mQueue.clear(); + mDurations.clear(); +} + +void +MediaCodecDataDecoder::Input(MediaRawData* aSample) +{ + MonitorAutoLock lock(mMonitor); + mQueue.push_back(aSample); + lock.NotifyAll(); +} + +nsresult +MediaCodecDataDecoder::ResetInputBuffers() +{ + return mDecoder->GetInputBuffers(ReturnTo(&mInputBuffers)); +} + +nsresult +MediaCodecDataDecoder::ResetOutputBuffers() +{ + return mDecoder->GetOutputBuffers(ReturnTo(&mOutputBuffers)); +} + +void +MediaCodecDataDecoder::Flush() +{ + MonitorAutoLock lock(mMonitor); + if (!SetState(ModuleState::kFlushing)) { + return; + } + lock.Notify(); + + while (mState == ModuleState::kFlushing) { + lock.Wait(); + } +} + +void +MediaCodecDataDecoder::Drain() +{ + MonitorAutoLock lock(mMonitor); + if (mState == ModuleState::kDrainDecoder || + mState == ModuleState::kDrainQueue) { + return; + } + + SetState(ModuleState::kDrainQueue); + lock.Notify(); +} + + +void +MediaCodecDataDecoder::Shutdown() +{ + MonitorAutoLock lock(mMonitor); + + SetState(ModuleState::kStopping); + lock.Notify(); + + while (mThread && mState != ModuleState::kShutdown) { + lock.Wait(); + } + + if (mThread) { + mThread->Shutdown(); + mThread = nullptr; + } + + if (mDecoder) { + mDecoder->Stop(); + mDecoder->Release(); + mDecoder = nullptr; + } +} + +} // mozilla diff --git a/dom/media/platforms/android/MediaCodecDataDecoder.h b/dom/media/platforms/android/MediaCodecDataDecoder.h new file mode 100644 index 000000000..0db6407bf --- /dev/null +++ b/dom/media/platforms/android/MediaCodecDataDecoder.h @@ -0,0 +1,126 @@ +/* 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/. */ + +#ifndef MediaCodecDataDecoder_h_ +#define MediaCodecDataDecoder_h_ + +#include "AndroidDecoderModule.h" + +#include "MediaCodec.h" +#include "SurfaceTexture.h" +#include "TimeUnits.h" +#include "mozilla/Monitor.h" +#include "mozilla/Maybe.h" + +#include <deque> + +namespace mozilla { + +typedef std::deque<RefPtr<MediaRawData>> SampleQueue; + +class MediaCodecDataDecoder : public MediaDataDecoder { +public: + static MediaDataDecoder* CreateAudioDecoder(const AudioInfo& aConfig, + java::sdk::MediaFormat::Param aFormat, + MediaDataDecoderCallback* aCallback); + + static MediaDataDecoder* CreateVideoDecoder(const VideoInfo& aConfig, + java::sdk::MediaFormat::Param aFormat, + MediaDataDecoderCallback* aCallback, + layers::ImageContainer* aImageContainer); + + virtual ~MediaCodecDataDecoder(); + + RefPtr<MediaDataDecoder::InitPromise> Init() override; + void Flush() override; + void Drain() override; + void Shutdown() override; + void Input(MediaRawData* aSample) override; + const char* GetDescriptionName() const override + { + return "Android MediaCodec decoder"; + } + +protected: + enum class ModuleState : uint8_t { + kDecoding = 0, + kFlushing, + kDrainQueue, + kDrainDecoder, + kDrainWaitEOS, + kStopping, + kShutdown + }; + + friend class AndroidDecoderModule; + + MediaCodecDataDecoder(MediaData::Type aType, + const nsACString& aMimeType, + java::sdk::MediaFormat::Param aFormat, + MediaDataDecoderCallback* aCallback); + + static const char* ModuleStateStr(ModuleState aState); + + virtual nsresult InitDecoder(java::sdk::Surface::Param aSurface); + + virtual nsresult Output(java::sdk::BufferInfo::Param aInfo, void* aBuffer, + java::sdk::MediaFormat::Param aFormat, const media::TimeUnit& aDuration) + { + return NS_OK; + } + + virtual nsresult PostOutput(java::sdk::BufferInfo::Param aInfo, + java::sdk::MediaFormat::Param aFormat, const media::TimeUnit& aDuration) + { + return NS_OK; + } + + virtual void Cleanup() {}; + + nsresult ResetInputBuffers(); + nsresult ResetOutputBuffers(); + + nsresult GetInputBuffer(JNIEnv* env, int index, jni::Object::LocalRef* buffer); + bool WaitForInput(); + already_AddRefed<MediaRawData> PeekNextSample(); + nsresult QueueSample(const MediaRawData* aSample); + nsresult QueueEOS(); + void HandleEOS(int32_t aOutputStatus); + Maybe<media::TimeUnit> GetOutputDuration(); + nsresult ProcessOutput(java::sdk::BufferInfo::Param aInfo, + java::sdk::MediaFormat::Param aFormat, + int32_t aStatus); + // Sets decoder state and returns whether the new state has become effective. + bool SetState(ModuleState aState); + void DecoderLoop(); + + virtual void ClearQueue(); + + MediaData::Type mType; + + nsAutoCString mMimeType; + java::sdk::MediaFormat::GlobalRef mFormat; + + MediaDataDecoderCallback* mCallback; + + java::sdk::MediaCodec::GlobalRef mDecoder; + + jni::ObjectArray::GlobalRef mInputBuffers; + jni::ObjectArray::GlobalRef mOutputBuffers; + + nsCOMPtr<nsIThread> mThread; + + // Only these members are protected by mMonitor. + Monitor mMonitor; + + ModuleState mState; + + SampleQueue mQueue; + // Durations are stored in microseconds. + std::deque<media::TimeUnit> mDurations; +}; + +} // namespace mozilla + +#endif diff --git a/dom/media/platforms/android/RemoteDataDecoder.cpp b/dom/media/platforms/android/RemoteDataDecoder.cpp new file mode 100644 index 000000000..56af2601f --- /dev/null +++ b/dom/media/platforms/android/RemoteDataDecoder.cpp @@ -0,0 +1,489 @@ +/* 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 "AndroidDecoderModule.h" +#include "AndroidBridge.h" +#include "AndroidSurfaceTexture.h" +#include "FennecJNINatives.h" +#include "GLImages.h" + +#include "MediaData.h" +#include "MediaInfo.h" +#include "VideoUtils.h" +#include "VPXDecoder.h" + +#include "nsThreadUtils.h" +#include "nsPromiseFlatString.h" +#include "nsIGfxInfo.h" + +#include "prlog.h" + +#include <jni.h> + +#include <deque> + +#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 namespace mozilla::java; +using namespace mozilla::java::sdk; +using media::TimeUnit; + +namespace mozilla { + +class JavaCallbacksSupport + : public CodecProxy::NativeCallbacks::Natives<JavaCallbacksSupport> +{ +public: + typedef CodecProxy::NativeCallbacks::Natives<JavaCallbacksSupport> Base; + using Base::AttachNative; + + JavaCallbacksSupport(MediaDataDecoderCallback* aDecoderCallback) + : mDecoderCallback(aDecoderCallback) + { + MOZ_ASSERT(aDecoderCallback); + } + + virtual ~JavaCallbacksSupport() {} + + void OnInputExhausted() + { + if (mDecoderCallback) { + mDecoderCallback->InputExhausted(); + } + } + + virtual void HandleOutput(Sample::Param aSample) = 0; + + void OnOutput(jni::Object::Param aSample) + { + if (mDecoderCallback) { + HandleOutput(Sample::Ref::From(aSample)); + } + } + + virtual void HandleOutputFormatChanged(MediaFormat::Param aFormat) {}; + + void OnOutputFormatChanged(jni::Object::Param aFormat) + { + if (mDecoderCallback) { + HandleOutputFormatChanged(MediaFormat::Ref::From(aFormat)); + } + } + + void OnError(bool aIsFatal) + { + if (mDecoderCallback) { + mDecoderCallback->Error(aIsFatal ? + MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__) : + MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, __func__)); + } + } + + void DisposeNative() + { + // TODO + } + + void Cancel() + { + mDecoderCallback = nullptr; + } + +protected: + MediaDataDecoderCallback* mDecoderCallback; +}; + +struct SampleTime final +{ + SampleTime(int64_t aStart, int64_t aDuration) + : mStart(aStart) + , mDuration(aDuration) + {} + + int64_t mStart; + int64_t mDuration; +}; + + +class RemoteVideoDecoder final : public RemoteDataDecoder +{ +public: + class CallbacksSupport final : public JavaCallbacksSupport + { + public: + CallbacksSupport(RemoteVideoDecoder* aDecoder, MediaDataDecoderCallback* aCallback) + : JavaCallbacksSupport(aCallback) + , mDecoder(aDecoder) + {} + + virtual ~CallbacksSupport() {} + + void HandleOutput(Sample::Param aSample) override + { + Maybe<int64_t> durationUs = mDecoder->mInputDurations.Get(); + if (!durationUs) { + return; + } + + BufferInfo::LocalRef info = aSample->Info(); + + int32_t flags; + bool ok = NS_SUCCEEDED(info->Flags(&flags)); + MOZ_ASSERT(ok); + + int32_t offset; + ok |= NS_SUCCEEDED(info->Offset(&offset)); + MOZ_ASSERT(ok); + + int64_t presentationTimeUs; + ok |= NS_SUCCEEDED(info->PresentationTimeUs(&presentationTimeUs)); + MOZ_ASSERT(ok); + + int32_t size; + ok |= NS_SUCCEEDED(info->Size(&size)); + MOZ_ASSERT(ok); + + NS_ENSURE_TRUE_VOID(ok); + + if (size > 0) { + RefPtr<layers::Image> img = + new SurfaceTextureImage(mDecoder->mSurfaceTexture.get(), mDecoder->mConfig.mDisplay, + gl::OriginPos::BottomLeft); + + RefPtr<VideoData> v = + VideoData::CreateFromImage(mDecoder->mConfig, + offset, + presentationTimeUs, + durationUs.value(), + img, + !!(flags & MediaCodec::BUFFER_FLAG_SYNC_FRAME), + presentationTimeUs, + gfx::IntRect(0, 0, + mDecoder->mConfig.mDisplay.width, + mDecoder->mConfig.mDisplay.height)); + + mDecoderCallback->Output(v); + } + + if ((flags & MediaCodec::BUFFER_FLAG_END_OF_STREAM) != 0) { + mDecoderCallback->DrainComplete(); + } + } + + friend class RemoteDataDecoder; + + private: + RemoteVideoDecoder* mDecoder; + }; + + RemoteVideoDecoder(const VideoInfo& aConfig, + MediaFormat::Param aFormat, + MediaDataDecoderCallback* aCallback, + layers::ImageContainer* aImageContainer) + : RemoteDataDecoder(MediaData::Type::VIDEO_DATA, aConfig.mMimeType, + aFormat, aCallback) + , mImageContainer(aImageContainer) + , mConfig(aConfig) + { + } + + RefPtr<InitPromise> Init() override + { + mSurfaceTexture = AndroidSurfaceTexture::Create(); + if (!mSurfaceTexture) { + NS_WARNING("Failed to create SurfaceTexture for video decode\n"); + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); + } + + if (!jni::IsFennec()) { + NS_WARNING("Remote decoding not supported in non-Fennec environment\n"); + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); + } + + // Register native methods. + JavaCallbacksSupport::Init(); + + mJavaCallbacks = CodecProxy::NativeCallbacks::New(); + JavaCallbacksSupport::AttachNative(mJavaCallbacks, + mozilla::MakeUnique<CallbacksSupport>(this, mCallback)); + + mJavaDecoder = CodecProxy::Create(mFormat, mSurfaceTexture->JavaSurface(), mJavaCallbacks); + if (mJavaDecoder == nullptr) { + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); + } + + mInputDurations.Clear(); + + return InitPromise::CreateAndResolve(TrackInfo::kVideoTrack, __func__); + } + + void Flush() override + { + mInputDurations.Clear(); + RemoteDataDecoder::Flush(); + } + + void Drain() override + { + RemoteDataDecoder::Drain(); + mInputDurations.Put(0); + } + + void Input(MediaRawData* aSample) override + { + RemoteDataDecoder::Input(aSample); + mInputDurations.Put(aSample->mDuration); + } + +private: + class DurationQueue { + public: + + void Clear() + { + mValues.clear(); + } + + void Put(int64_t aDurationUs) + { + mValues.emplace_back(aDurationUs); + } + + Maybe<int64_t> Get() + { + if (mValues.empty()) { + return Nothing(); + } + + auto value = Some(mValues.front()); + mValues.pop_front(); + + return value; + } + + private: + std::deque<int64_t> mValues; + }; + + layers::ImageContainer* mImageContainer; + const VideoInfo& mConfig; + RefPtr<AndroidSurfaceTexture> mSurfaceTexture; + DurationQueue mInputDurations; +}; + +class RemoteAudioDecoder final : public RemoteDataDecoder +{ +public: + RemoteAudioDecoder(const AudioInfo& aConfig, + MediaFormat::Param aFormat, + MediaDataDecoderCallback* aCallback) + : RemoteDataDecoder(MediaData::Type::AUDIO_DATA, aConfig.mMimeType, + aFormat, aCallback) + , mConfig(aConfig) + { + JNIEnv* const env = jni::GetEnvForThread(); + + bool formatHasCSD = false; + NS_ENSURE_SUCCESS_VOID(aFormat->ContainsKey(NS_LITERAL_STRING("csd-0"), &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(NS_LITERAL_STRING("csd-0"), + buffer)); + } + } + + RefPtr<InitPromise> Init() override + { + // Register native methods. + JavaCallbacksSupport::Init(); + + mJavaCallbacks = CodecProxy::NativeCallbacks::New(); + JavaCallbacksSupport::AttachNative(mJavaCallbacks, + mozilla::MakeUnique<CallbacksSupport>(this, mCallback)); + + mJavaDecoder = CodecProxy::Create(mFormat, nullptr, mJavaCallbacks); + if (mJavaDecoder == nullptr) { + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); + } + + return InitPromise::CreateAndResolve(TrackInfo::kAudioTrack, __func__); + } + +private: + class CallbacksSupport final : public JavaCallbacksSupport + { + public: + CallbacksSupport(RemoteAudioDecoder* aDecoder, MediaDataDecoderCallback* aCallback) + : JavaCallbacksSupport(aCallback) + , mDecoder(aDecoder) + {} + + virtual ~CallbacksSupport() {} + + void HandleOutput(Sample::Param aSample) override + { + BufferInfo::LocalRef info = aSample->Info(); + + int32_t flags; + bool ok = NS_SUCCEEDED(info->Flags(&flags)); + MOZ_ASSERT(ok); + + int32_t offset; + ok |= NS_SUCCEEDED(info->Offset(&offset)); + MOZ_ASSERT(ok); + + int64_t presentationTimeUs; + ok |= NS_SUCCEEDED(info->PresentationTimeUs(&presentationTimeUs)); + MOZ_ASSERT(ok); + + int32_t size; + ok |= NS_SUCCEEDED(info->Size(&size)); + MOZ_ASSERT(ok); + + NS_ENSURE_TRUE_VOID(ok); + + if (size > 0) { +#ifdef MOZ_SAMPLE_TYPE_S16 + const int32_t numSamples = size / 2; +#else +#error We only support 16-bit integer PCM +#endif + + const int32_t numFrames = numSamples / mOutputChannels; + AlignedAudioBuffer audio(numSamples); + if (!audio) { + return; + } + + jni::ByteBuffer::LocalRef dest = jni::ByteBuffer::New(audio.get(), size); + aSample->WriteToByteBuffer(dest); + + RefPtr<AudioData> data = new AudioData(0, presentationTimeUs, + FramesToUsecs(numFrames, mOutputSampleRate).value(), + numFrames, + Move(audio), + mOutputChannels, + mOutputSampleRate); + + mDecoderCallback->Output(data); + } + + if ((flags & MediaCodec::BUFFER_FLAG_END_OF_STREAM) != 0) { + mDecoderCallback->DrainComplete(); + return; + } + } + + void HandleOutputFormatChanged(MediaFormat::Param aFormat) override + { + aFormat->GetInteger(NS_LITERAL_STRING("channel-count"), &mOutputChannels); + AudioConfig::ChannelLayout layout(mOutputChannels); + if (!layout.IsValid()) { + mDecoderCallback->Error(MediaResult( + NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL("Invalid channel layout:%d", mOutputChannels))); + return; + } + aFormat->GetInteger(NS_LITERAL_STRING("sample-rate"), &mOutputSampleRate); + LOG("Audio output format changed: channels:%d sample rate:%d", mOutputChannels, mOutputSampleRate); + } + + private: + RemoteAudioDecoder* mDecoder; + int32_t mOutputChannels; + int32_t mOutputSampleRate; + }; + + const AudioInfo& mConfig; +}; + +MediaDataDecoder* +RemoteDataDecoder::CreateAudioDecoder(const AudioInfo& aConfig, + MediaFormat::Param aFormat, + MediaDataDecoderCallback* aCallback) +{ + return new RemoteAudioDecoder(aConfig, aFormat, aCallback); +} + +MediaDataDecoder* +RemoteDataDecoder::CreateVideoDecoder(const VideoInfo& aConfig, + MediaFormat::Param aFormat, + MediaDataDecoderCallback* aCallback, + layers::ImageContainer* aImageContainer) +{ + return new RemoteVideoDecoder(aConfig, aFormat, aCallback, aImageContainer); +} + +RemoteDataDecoder::RemoteDataDecoder(MediaData::Type aType, + const nsACString& aMimeType, + MediaFormat::Param aFormat, + MediaDataDecoderCallback* aCallback) + : mType(aType) + , mMimeType(aMimeType) + , mFormat(aFormat) + , mCallback(aCallback) +{ +} + +void +RemoteDataDecoder::Flush() +{ + mJavaDecoder->Flush(); +} + +void +RemoteDataDecoder::Drain() +{ + BufferInfo::LocalRef bufferInfo; + nsresult rv = BufferInfo::New(&bufferInfo); + NS_ENSURE_SUCCESS_VOID(rv); + bufferInfo->Set(0, 0, -1, MediaCodec::BUFFER_FLAG_END_OF_STREAM); + + mJavaDecoder->Input(nullptr, bufferInfo, nullptr); +} + +void +RemoteDataDecoder::Shutdown() +{ + LOG(""); + MOZ_ASSERT(mJavaDecoder && mJavaCallbacks); + + mJavaDecoder->Release(); + mJavaDecoder = nullptr; + + JavaCallbacksSupport::GetNative(mJavaCallbacks)->Cancel(); + mJavaCallbacks = nullptr; + + mFormat = nullptr; +} + +void +RemoteDataDecoder::Input(MediaRawData* aSample) +{ + MOZ_ASSERT(aSample != nullptr); + + jni::ByteBuffer::LocalRef bytes = jni::ByteBuffer::New(const_cast<uint8_t*>(aSample->Data()), + aSample->Size()); + + BufferInfo::LocalRef bufferInfo; + nsresult rv = BufferInfo::New(&bufferInfo); + if (NS_FAILED(rv)) { + mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__)); + return; + } + bufferInfo->Set(0, aSample->Size(), aSample->mTime, 0); + + mJavaDecoder->Input(bytes, bufferInfo, GetCryptoInfoFromSample(aSample)); +} + +} // mozilla diff --git a/dom/media/platforms/android/RemoteDataDecoder.h b/dom/media/platforms/android/RemoteDataDecoder.h new file mode 100644 index 000000000..219539a0a --- /dev/null +++ b/dom/media/platforms/android/RemoteDataDecoder.h @@ -0,0 +1,62 @@ +/* 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/. */ + +#ifndef RemoteDataDecoder_h_ +#define RemoteDataDecoder_h_ + +#include "AndroidDecoderModule.h" + +#include "FennecJNIWrappers.h" + +#include "SurfaceTexture.h" +#include "TimeUnits.h" +#include "mozilla/Monitor.h" +#include "mozilla/Maybe.h" + +#include <deque> + +namespace mozilla { + +class RemoteDataDecoder : public MediaDataDecoder { +public: + static MediaDataDecoder* CreateAudioDecoder(const AudioInfo& aConfig, + java::sdk::MediaFormat::Param aFormat, + MediaDataDecoderCallback* aCallback); + + static MediaDataDecoder* CreateVideoDecoder(const VideoInfo& aConfig, + java::sdk::MediaFormat::Param aFormat, + MediaDataDecoderCallback* aCallback, + layers::ImageContainer* aImageContainer); + + virtual ~RemoteDataDecoder() {} + + void Flush() override; + void Drain() override; + void Shutdown() override; + void Input(MediaRawData* aSample) override; + const char* GetDescriptionName() const override + { + return "android remote decoder"; + } + +protected: + RemoteDataDecoder(MediaData::Type aType, + const nsACString& aMimeType, + java::sdk::MediaFormat::Param aFormat, + MediaDataDecoderCallback* aCallback); + + MediaData::Type mType; + + nsAutoCString mMimeType; + java::sdk::MediaFormat::GlobalRef mFormat; + + MediaDataDecoderCallback* mCallback; + + java::CodecProxy::GlobalRef mJavaDecoder; + java::CodecProxy::NativeCallbacks::GlobalRef mJavaCallbacks; +}; + +} // namespace mozilla + +#endif |