summaryrefslogtreecommitdiffstats
path: root/dom/media/platforms/android
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /dom/media/platforms/android
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/media/platforms/android')
-rw-r--r--dom/media/platforms/android/AndroidDecoderModule.cpp227
-rw-r--r--dom/media/platforms/android/AndroidDecoderModule.h34
-rw-r--r--dom/media/platforms/android/MediaCodecDataDecoder.cpp698
-rw-r--r--dom/media/platforms/android/MediaCodecDataDecoder.h126
-rw-r--r--dom/media/platforms/android/RemoteDataDecoder.cpp489
-rw-r--r--dom/media/platforms/android/RemoteDataDecoder.h62
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