diff options
Diffstat (limited to 'dom/media/platforms/agnostic')
31 files changed, 4218 insertions, 0 deletions
diff --git a/dom/media/platforms/agnostic/AgnosticDecoderModule.cpp b/dom/media/platforms/agnostic/AgnosticDecoderModule.cpp new file mode 100644 index 000000000..7bd75b7fe --- /dev/null +++ b/dom/media/platforms/agnostic/AgnosticDecoderModule.cpp @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AgnosticDecoderModule.h" +#include "mozilla/Logging.h" +#include "OpusDecoder.h" +#include "VorbisDecoder.h" +#include "VPXDecoder.h" +#include "WAVDecoder.h" +#include "TheoraDecoder.h" + +namespace mozilla { + +bool +AgnosticDecoderModule::SupportsMimeType(const nsACString& aMimeType, + DecoderDoctorDiagnostics* aDiagnostics) const +{ + bool supports = + VPXDecoder::IsVPX(aMimeType) || + OpusDataDecoder::IsOpus(aMimeType) || + VorbisDataDecoder::IsVorbis(aMimeType) || + WaveDataDecoder::IsWave(aMimeType) || + TheoraDecoder::IsTheora(aMimeType); + MOZ_LOG(sPDMLog, LogLevel::Debug, ("Agnostic decoder %s requested type", + supports ? "supports" : "rejects")); + return supports; +} + +already_AddRefed<MediaDataDecoder> +AgnosticDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams) +{ + RefPtr<MediaDataDecoder> m; + + if (VPXDecoder::IsVPX(aParams.mConfig.mMimeType)) { + m = new VPXDecoder(aParams); + } else if (TheoraDecoder::IsTheora(aParams.mConfig.mMimeType)) { + m = new TheoraDecoder(aParams); + } + + return m.forget(); +} + +already_AddRefed<MediaDataDecoder> +AgnosticDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams) +{ + RefPtr<MediaDataDecoder> m; + + const TrackInfo& config = aParams.mConfig; + if (VorbisDataDecoder::IsVorbis(config.mMimeType)) { + m = new VorbisDataDecoder(aParams); + } else if (OpusDataDecoder::IsOpus(config.mMimeType)) { + m = new OpusDataDecoder(aParams); + } else if (WaveDataDecoder::IsWave(config.mMimeType)) { + m = new WaveDataDecoder(aParams); + } + + return m.forget(); +} + +} // namespace mozilla diff --git a/dom/media/platforms/agnostic/AgnosticDecoderModule.h b/dom/media/platforms/agnostic/AgnosticDecoderModule.h new file mode 100644 index 000000000..d944b84ab --- /dev/null +++ b/dom/media/platforms/agnostic/AgnosticDecoderModule.h @@ -0,0 +1,34 @@ +#if !defined(AgnosticDecoderModule_h_) +#define AgnosticDecoderModule_h_ + +#include "PlatformDecoderModule.h" + +namespace mozilla { + +class AgnosticDecoderModule : public PlatformDecoderModule { +public: + AgnosticDecoderModule() = default; + virtual ~AgnosticDecoderModule() = default; + + bool SupportsMimeType(const nsACString& aMimeType, + DecoderDoctorDiagnostics* aDiagnostics) const override; + + ConversionRequired + DecoderNeedsConversion(const TrackInfo& aConfig) const override + { + return ConversionRequired::kNeedNone; + } + +protected: + // Decode thread. + already_AddRefed<MediaDataDecoder> + CreateVideoDecoder(const CreateDecoderParams& aParams) override; + + // Decode thread. + already_AddRefed<MediaDataDecoder> + CreateAudioDecoder(const CreateDecoderParams& aParams) override; +}; + +} // namespace mozilla + +#endif /* AgnosticDecoderModule_h_ */ diff --git a/dom/media/platforms/agnostic/BlankDecoderModule.cpp b/dom/media/platforms/agnostic/BlankDecoderModule.cpp new file mode 100644 index 000000000..7d7bae721 --- /dev/null +++ b/dom/media/platforms/agnostic/BlankDecoderModule.cpp @@ -0,0 +1,278 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ImageContainer.h" +#include "MediaDecoderReader.h" +#include "MediaInfo.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/mozalloc.h" // for operator new, and new (fallible) +#include "mozilla/RefPtr.h" +#include "mozilla/TaskQueue.h" +#include "mp4_demuxer/AnnexB.h" +#include "mp4_demuxer/H264.h" +#include "MP4Decoder.h" +#include "nsAutoPtr.h" +#include "nsRect.h" +#include "PlatformDecoderModule.h" +#include "ReorderQueue.h" +#include "TimeUnits.h" +#include "VideoUtils.h" + +namespace mozilla { + +// Decoder that uses a passed in object's Create function to create blank +// MediaData objects. +template<class BlankMediaDataCreator> +class BlankMediaDataDecoder : public MediaDataDecoder { +public: + + BlankMediaDataDecoder(BlankMediaDataCreator* aCreator, + const CreateDecoderParams& aParams) + : mCreator(aCreator) + , mCallback(aParams.mCallback) + , mMaxRefFrames(aParams.mConfig.GetType() == TrackInfo::kVideoTrack && + MP4Decoder::IsH264(aParams.mConfig.mMimeType) + ? mp4_demuxer::AnnexB::HasSPS(aParams.VideoConfig().mExtraData) + ? mp4_demuxer::H264::ComputeMaxRefFrames(aParams.VideoConfig().mExtraData) + : 16 + : 0) + , mType(aParams.mConfig.GetType()) + { + } + + RefPtr<InitPromise> Init() override { + return InitPromise::CreateAndResolve(mType, __func__); + } + + void Shutdown() override {} + + void Input(MediaRawData* aSample) override + { + RefPtr<MediaData> data = + mCreator->Create(media::TimeUnit::FromMicroseconds(aSample->mTime), + media::TimeUnit::FromMicroseconds(aSample->mDuration), + aSample->mOffset); + + OutputFrame(data); + } + + void Flush() override + { + mReorderQueue.Clear(); + } + + void Drain() override + { + while (!mReorderQueue.IsEmpty()) { + mCallback->Output(mReorderQueue.Pop().get()); + } + + mCallback->DrainComplete(); + } + + const char* GetDescriptionName() const override + { + return "blank media data decoder"; + } + +private: + void OutputFrame(MediaData* aData) + { + if (!aData) { + mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__)); + return; + } + + // Frames come out in DTS order but we need to output them in PTS order. + mReorderQueue.Push(aData); + + while (mReorderQueue.Length() > mMaxRefFrames) { + mCallback->Output(mReorderQueue.Pop().get()); + } + mCallback->InputExhausted(); + } + +private: + nsAutoPtr<BlankMediaDataCreator> mCreator; + MediaDataDecoderCallback* mCallback; + const uint32_t mMaxRefFrames; + ReorderQueue mReorderQueue; + TrackInfo::TrackType mType; +}; + +class BlankVideoDataCreator { +public: + BlankVideoDataCreator(uint32_t aFrameWidth, + uint32_t aFrameHeight, + layers::ImageContainer* aImageContainer) + : mFrameWidth(aFrameWidth) + , mFrameHeight(aFrameHeight) + , mImageContainer(aImageContainer) + { + mInfo.mDisplay = nsIntSize(mFrameWidth, mFrameHeight); + mPicture = gfx::IntRect(0, 0, mFrameWidth, mFrameHeight); + } + + already_AddRefed<MediaData> + Create(const media::TimeUnit& aDTS, const media::TimeUnit& aDuration, int64_t aOffsetInStream) + { + // Create a fake YUV buffer in a 420 format. That is, an 8bpp Y plane, + // with a U and V plane that are half the size of the Y plane, i.e 8 bit, + // 2x2 subsampled. + const int sizeY = mFrameWidth * mFrameHeight; + const int sizeCbCr = ((mFrameWidth + 1) / 2) * ((mFrameHeight + 1) / 2); + auto frame = MakeUnique<uint8_t[]>(sizeY + sizeCbCr); + VideoData::YCbCrBuffer buffer; + + // Y plane. + buffer.mPlanes[0].mData = frame.get(); + buffer.mPlanes[0].mStride = mFrameWidth; + buffer.mPlanes[0].mHeight = mFrameHeight; + buffer.mPlanes[0].mWidth = mFrameWidth; + buffer.mPlanes[0].mOffset = 0; + buffer.mPlanes[0].mSkip = 0; + + // Cb plane. + buffer.mPlanes[1].mData = frame.get() + sizeY; + buffer.mPlanes[1].mStride = mFrameWidth / 2; + buffer.mPlanes[1].mHeight = mFrameHeight / 2; + buffer.mPlanes[1].mWidth = mFrameWidth / 2; + buffer.mPlanes[1].mOffset = 0; + buffer.mPlanes[1].mSkip = 0; + + // Cr plane. + buffer.mPlanes[2].mData = frame.get() + sizeY; + buffer.mPlanes[2].mStride = mFrameWidth / 2; + buffer.mPlanes[2].mHeight = mFrameHeight / 2; + buffer.mPlanes[2].mWidth = mFrameWidth / 2; + buffer.mPlanes[2].mOffset = 0; + buffer.mPlanes[2].mSkip = 0; + + // Set to color white. + memset(buffer.mPlanes[0].mData, 255, sizeY); + memset(buffer.mPlanes[1].mData, 128, sizeCbCr); + + return VideoData::CreateAndCopyData(mInfo, + mImageContainer, + aOffsetInStream, + aDTS.ToMicroseconds(), + aDuration.ToMicroseconds(), + buffer, + true, + aDTS.ToMicroseconds(), + mPicture); + } + +private: + VideoInfo mInfo; + gfx::IntRect mPicture; + uint32_t mFrameWidth; + uint32_t mFrameHeight; + RefPtr<layers::ImageContainer> mImageContainer; +}; + +class BlankAudioDataCreator { +public: + BlankAudioDataCreator(uint32_t aChannelCount, uint32_t aSampleRate) + : mFrameSum(0), mChannelCount(aChannelCount), mSampleRate(aSampleRate) + { + } + + MediaData* Create(const media::TimeUnit& aDTS, + const media::TimeUnit& aDuration, + int64_t aOffsetInStream) + { + // Convert duration to frames. We add 1 to duration to account for + // rounding errors, so we get a consistent tone. + CheckedInt64 frames = + UsecsToFrames(aDuration.ToMicroseconds()+1, mSampleRate); + if (!frames.isValid() || + !mChannelCount || + !mSampleRate || + frames.value() > (UINT32_MAX / mChannelCount)) { + return nullptr; + } + AlignedAudioBuffer samples(frames.value() * mChannelCount); + if (!samples) { + return nullptr; + } + // Fill the sound buffer with an A4 tone. + static const float pi = 3.14159265f; + static const float noteHz = 440.0f; + for (int i = 0; i < frames.value(); i++) { + float f = sin(2 * pi * noteHz * mFrameSum / mSampleRate); + for (unsigned c = 0; c < mChannelCount; c++) { + samples[i * mChannelCount + c] = AudioDataValue(f); + } + mFrameSum++; + } + return new AudioData(aOffsetInStream, + aDTS.ToMicroseconds(), + aDuration.ToMicroseconds(), + uint32_t(frames.value()), + Move(samples), + mChannelCount, + mSampleRate); + } + +private: + int64_t mFrameSum; + uint32_t mChannelCount; + uint32_t mSampleRate; +}; + +class BlankDecoderModule : public PlatformDecoderModule { +public: + + // Decode thread. + already_AddRefed<MediaDataDecoder> + CreateVideoDecoder(const CreateDecoderParams& aParams) override { + const VideoInfo& config = aParams.VideoConfig(); + BlankVideoDataCreator* creator = new BlankVideoDataCreator( + config.mDisplay.width, config.mDisplay.height, aParams.mImageContainer); + RefPtr<MediaDataDecoder> decoder = + new BlankMediaDataDecoder<BlankVideoDataCreator>(creator, aParams); + return decoder.forget(); + } + + // Decode thread. + already_AddRefed<MediaDataDecoder> + CreateAudioDecoder(const CreateDecoderParams& aParams) override { + const AudioInfo& config = aParams.AudioConfig(); + BlankAudioDataCreator* creator = new BlankAudioDataCreator( + config.mChannels, config.mRate); + + RefPtr<MediaDataDecoder> decoder = + new BlankMediaDataDecoder<BlankAudioDataCreator>(creator, aParams); + return decoder.forget(); + } + + bool + SupportsMimeType(const nsACString& aMimeType, + DecoderDoctorDiagnostics* aDiagnostics) const override + { + return true; + } + + ConversionRequired + DecoderNeedsConversion(const TrackInfo& aConfig) const override + { + if (aConfig.IsVideo() && MP4Decoder::IsH264(aConfig.mMimeType)) { + return ConversionRequired::kNeedAVCC; + } else { + return ConversionRequired::kNeedNone; + } + } + +}; + +already_AddRefed<PlatformDecoderModule> CreateBlankDecoderModule() +{ + RefPtr<PlatformDecoderModule> pdm = new BlankDecoderModule(); + return pdm.forget(); +} + +} // namespace mozilla diff --git a/dom/media/platforms/agnostic/OpusDecoder.cpp b/dom/media/platforms/agnostic/OpusDecoder.cpp new file mode 100644 index 000000000..9163ed058 --- /dev/null +++ b/dom/media/platforms/agnostic/OpusDecoder.cpp @@ -0,0 +1,356 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "OpusDecoder.h" +#include "OpusParser.h" +#include "TimeUnits.h" +#include "VorbisUtils.h" +#include "VorbisDecoder.h" // For VorbisLayout +#include "mozilla/EndianUtils.h" +#include "mozilla/PodOperations.h" +#include "mozilla/SyncRunnable.h" + +#include <inttypes.h> // For PRId64 + +#include "opus/opus.h" +extern "C" { +#include "opus/opus_multistream.h" +} + +#define OPUS_DEBUG(arg, ...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, \ + ("OpusDataDecoder(%p)::%s: " arg, this, __func__, ##__VA_ARGS__)) + +namespace mozilla { + +OpusDataDecoder::OpusDataDecoder(const CreateDecoderParams& aParams) + : mInfo(aParams.AudioConfig()) + , mTaskQueue(aParams.mTaskQueue) + , mCallback(aParams.mCallback) + , mOpusDecoder(nullptr) + , mSkip(0) + , mDecodedHeader(false) + , mPaddingDiscarded(false) + , mFrames(0) + , mIsFlushing(false) +{ +} + +OpusDataDecoder::~OpusDataDecoder() +{ + if (mOpusDecoder) { + opus_multistream_decoder_destroy(mOpusDecoder); + mOpusDecoder = nullptr; + } +} + +void +OpusDataDecoder::Shutdown() +{ +} + +void +OpusDataDecoder::AppendCodecDelay(MediaByteBuffer* config, uint64_t codecDelayUS) +{ + uint8_t buffer[sizeof(uint64_t)]; + BigEndian::writeUint64(buffer, codecDelayUS); + config->AppendElements(buffer, sizeof(uint64_t)); +} + +RefPtr<MediaDataDecoder::InitPromise> +OpusDataDecoder::Init() +{ + size_t length = mInfo.mCodecSpecificConfig->Length(); + uint8_t *p = mInfo.mCodecSpecificConfig->Elements(); + if (length < sizeof(uint64_t)) { + OPUS_DEBUG("CodecSpecificConfig too short to read codecDelay!"); + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); + } + int64_t codecDelay = BigEndian::readUint64(p); + length -= sizeof(uint64_t); + p += sizeof(uint64_t); + if (NS_FAILED(DecodeHeader(p, length))) { + OPUS_DEBUG("Error decoding header!"); + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); + } + + int r; + mOpusDecoder = opus_multistream_decoder_create(mOpusParser->mRate, + mOpusParser->mChannels, + mOpusParser->mStreams, + mOpusParser->mCoupledStreams, + mMappingTable, + &r); + mSkip = mOpusParser->mPreSkip; + mPaddingDiscarded = false; + + if (codecDelay != FramesToUsecs(mOpusParser->mPreSkip, + mOpusParser->mRate).value()) { + NS_WARNING("Invalid Opus header: CodecDelay and pre-skip do not match!"); + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); + } + + if (mInfo.mRate != (uint32_t)mOpusParser->mRate) { + NS_WARNING("Invalid Opus header: container and codec rate do not match!"); + } + if (mInfo.mChannels != (uint32_t)mOpusParser->mChannels) { + NS_WARNING("Invalid Opus header: container and codec channels do not match!"); + } + + return r == OPUS_OK ? InitPromise::CreateAndResolve(TrackInfo::kAudioTrack, __func__) + : InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); +} + +nsresult +OpusDataDecoder::DecodeHeader(const unsigned char* aData, size_t aLength) +{ + MOZ_ASSERT(!mOpusParser); + MOZ_ASSERT(!mOpusDecoder); + MOZ_ASSERT(!mDecodedHeader); + mDecodedHeader = true; + + mOpusParser = new OpusParser; + if (!mOpusParser->DecodeHeader(const_cast<unsigned char*>(aData), aLength)) { + return NS_ERROR_FAILURE; + } + int channels = mOpusParser->mChannels; + + AudioConfig::ChannelLayout layout(channels); + if (!layout.IsValid()) { + OPUS_DEBUG("Invalid channel mapping. Source is %d channels", channels); + return NS_ERROR_FAILURE; + } + + AudioConfig::ChannelLayout vorbisLayout( + channels, VorbisDataDecoder::VorbisLayout(channels)); + AudioConfig::ChannelLayout smpteLayout(channels); + static_assert(sizeof(mOpusParser->mMappingTable) / sizeof(mOpusParser->mMappingTable[0]) >= MAX_AUDIO_CHANNELS, + "Invalid size set"); + uint8_t map[sizeof(mOpusParser->mMappingTable) / sizeof(mOpusParser->mMappingTable[0])]; + if (vorbisLayout.MappingTable(smpteLayout, map)) { + for (int i = 0; i < channels; i++) { + mMappingTable[i] = mOpusParser->mMappingTable[map[i]]; + } + } else { + // Should never get here as vorbis layout is always convertible to SMPTE + // default layout. + PodCopy(mMappingTable, mOpusParser->mMappingTable, MAX_AUDIO_CHANNELS); + } + + return NS_OK; +} + +void +OpusDataDecoder::Input(MediaRawData* aSample) +{ + mTaskQueue->Dispatch(NewRunnableMethod<RefPtr<MediaRawData>>( + this, &OpusDataDecoder::ProcessDecode, aSample)); +} + +void +OpusDataDecoder::ProcessDecode(MediaRawData* aSample) +{ + if (mIsFlushing) { + return; + } + + MediaResult rv = DoDecode(aSample); + if (NS_FAILED(rv)) { + mCallback->Error(rv); + return; + } + mCallback->InputExhausted(); +} + +MediaResult +OpusDataDecoder::DoDecode(MediaRawData* aSample) +{ + uint32_t channels = mOpusParser->mChannels; + + if (mPaddingDiscarded) { + // Discard padding should be used only on the final packet, so + // decoding after a padding discard is invalid. + OPUS_DEBUG("Opus error, discard padding on interstitial packet"); + return MediaResult( + NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL("Discard padding on interstitial packet")); + } + + if (!mLastFrameTime || mLastFrameTime.ref() != aSample->mTime) { + // We are starting a new block. + mFrames = 0; + mLastFrameTime = Some(aSample->mTime); + } + + // Maximum value is 63*2880, so there's no chance of overflow. + int frames_number = + opus_packet_get_nb_frames(aSample->Data(), aSample->Size()); + if (frames_number <= 0) { + OPUS_DEBUG("Invalid packet header: r=%d length=%u", + frames_number, uint32_t(aSample->Size())); + return MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, + RESULT_DETAIL("Invalid packet header: r=%d length=%u", + frames_number, uint32_t(aSample->Size()))); + } + + int samples = opus_packet_get_samples_per_frame( + aSample->Data(), opus_int32(mOpusParser->mRate)); + + + // A valid Opus packet must be between 2.5 and 120 ms long (48kHz). + CheckedInt32 totalFrames = + CheckedInt32(frames_number) * CheckedInt32(samples); + if (!totalFrames.isValid()) { + return MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, + RESULT_DETAIL("Frames count overflow")); + } + + int frames = totalFrames.value(); + if (frames < 120 || frames > 5760) { + OPUS_DEBUG("Invalid packet frames: %d", frames); + return MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, + RESULT_DETAIL("Invalid packet frames:%d", frames)); + } + + AlignedAudioBuffer buffer(frames * channels); + if (!buffer) { + return MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__); + } + + // Decode to the appropriate sample type. +#ifdef MOZ_SAMPLE_TYPE_FLOAT32 + int ret = opus_multistream_decode_float(mOpusDecoder, + aSample->Data(), aSample->Size(), + buffer.get(), frames, false); +#else + int ret = opus_multistream_decode(mOpusDecoder, + aSample->Data(), aSample->Size(), + buffer.get(), frames, false); +#endif + if (ret < 0) { + return MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, + RESULT_DETAIL("Opus decoding error:%d", ret)); + } + NS_ASSERTION(ret == frames, "Opus decoded too few audio samples"); + CheckedInt64 startTime = aSample->mTime; + + // Trim the initial frames while the decoder is settling. + if (mSkip > 0) { + int32_t skipFrames = std::min<int32_t>(mSkip, frames); + int32_t keepFrames = frames - skipFrames; + OPUS_DEBUG( + "Opus decoder skipping %d of %d frames", skipFrames, frames); + PodMove(buffer.get(), + buffer.get() + skipFrames * channels, + keepFrames * channels); + startTime = startTime + FramesToUsecs(skipFrames, mOpusParser->mRate); + frames = keepFrames; + mSkip -= skipFrames; + } + + if (aSample->mDiscardPadding > 0) { + OPUS_DEBUG("Opus decoder discarding %u of %d frames", + aSample->mDiscardPadding, frames); + // Padding discard is only supposed to happen on the final packet. + // Record the discard so we can return an error if another packet is + // decoded. + if (aSample->mDiscardPadding > uint32_t(frames)) { + // Discarding more than the entire packet is invalid. + OPUS_DEBUG("Opus error, discard padding larger than packet"); + return MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL("Discard padding larger than packet")); + } + + mPaddingDiscarded = true; + frames = frames - aSample->mDiscardPadding; + } + + // Apply the header gain if one was specified. +#ifdef MOZ_SAMPLE_TYPE_FLOAT32 + if (mOpusParser->mGain != 1.0f) { + float gain = mOpusParser->mGain; + uint32_t samples = frames * channels; + for (uint32_t i = 0; i < samples; i++) { + buffer[i] *= gain; + } + } +#else + if (mOpusParser->mGain_Q16 != 65536) { + int64_t gain_Q16 = mOpusParser->mGain_Q16; + uint32_t samples = frames * channels; + for (uint32_t i = 0; i < samples; i++) { + int32_t val = static_cast<int32_t>((gain_Q16*buffer[i] + 32768)>>16); + buffer[i] = static_cast<AudioDataValue>(MOZ_CLIP_TO_15(val)); + } + } +#endif + + CheckedInt64 duration = FramesToUsecs(frames, mOpusParser->mRate); + if (!duration.isValid()) { + return MediaResult( + NS_ERROR_DOM_MEDIA_OVERFLOW_ERR, + RESULT_DETAIL("Overflow converting WebM audio duration")); + } + CheckedInt64 time = + startTime - FramesToUsecs(mOpusParser->mPreSkip, mOpusParser->mRate) + + FramesToUsecs(mFrames, mOpusParser->mRate); + if (!time.isValid()) { + return MediaResult( + NS_ERROR_DOM_MEDIA_OVERFLOW_ERR, + RESULT_DETAIL("Overflow shifting tstamp by codec delay")); + }; + + mCallback->Output(new AudioData(aSample->mOffset, + time.value(), + duration.value(), + frames, + Move(buffer), + mOpusParser->mChannels, + mOpusParser->mRate)); + mFrames += frames; + return NS_OK; +} + +void +OpusDataDecoder::ProcessDrain() +{ + mCallback->DrainComplete(); +} + +void +OpusDataDecoder::Drain() +{ + mTaskQueue->Dispatch(NewRunnableMethod(this, &OpusDataDecoder::ProcessDrain)); +} + +void +OpusDataDecoder::Flush() +{ + if (!mOpusDecoder) { + return; + } + mIsFlushing = true; + RefPtr<OpusDataDecoder> self = this; + nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction([self] () { + MOZ_ASSERT(self->mOpusDecoder); + // Reset the decoder. + opus_multistream_decoder_ctl(self->mOpusDecoder, OPUS_RESET_STATE); + self->mSkip = self->mOpusParser->mPreSkip; + self->mPaddingDiscarded = false; + self->mLastFrameTime.reset(); + }); + SyncRunnable::DispatchToThread(mTaskQueue, runnable); + mIsFlushing = false; +} + +/* static */ +bool +OpusDataDecoder::IsOpus(const nsACString& aMimeType) +{ + return aMimeType.EqualsLiteral("audio/opus"); +} + +} // namespace mozilla +#undef OPUS_DEBUG diff --git a/dom/media/platforms/agnostic/OpusDecoder.h b/dom/media/platforms/agnostic/OpusDecoder.h new file mode 100644 index 000000000..6d82ae502 --- /dev/null +++ b/dom/media/platforms/agnostic/OpusDecoder.h @@ -0,0 +1,76 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#if !defined(OpusDecoder_h_) +#define OpusDecoder_h_ + +#include "PlatformDecoderModule.h" + +#include "mozilla/Maybe.h" +#include "nsAutoPtr.h" + +struct OpusMSDecoder; + +namespace mozilla { + +class OpusParser; + +class OpusDataDecoder : public MediaDataDecoder +{ +public: + explicit OpusDataDecoder(const CreateDecoderParams& aParams); + ~OpusDataDecoder(); + + RefPtr<InitPromise> Init() override; + void Input(MediaRawData* aSample) override; + void Flush() override; + void Drain() override; + void Shutdown() override; + const char* GetDescriptionName() const override + { + return "opus audio decoder"; + } + + // Return true if mimetype is Opus + static bool IsOpus(const nsACString& aMimeType); + + // Pack pre-skip/CodecDelay, given in microseconds, into a + // MediaByteBuffer. The decoder expects this value to come + // from the container (if any) and to precede the OpusHead + // block in the CodecSpecificConfig buffer to verify the + // values match. + static void AppendCodecDelay(MediaByteBuffer* config, uint64_t codecDelayUS); + +private: + nsresult DecodeHeader(const unsigned char* aData, size_t aLength); + + void ProcessDecode(MediaRawData* aSample); + MediaResult DoDecode(MediaRawData* aSample); + void ProcessDrain(); + + const AudioInfo& mInfo; + const RefPtr<TaskQueue> mTaskQueue; + MediaDataDecoderCallback* mCallback; + + // Opus decoder state + nsAutoPtr<OpusParser> mOpusParser; + OpusMSDecoder* mOpusDecoder; + + uint16_t mSkip; // Samples left to trim before playback. + bool mDecodedHeader; + + // Opus padding should only be discarded on the final packet. Once this + // is set to true, if the reader attempts to decode any further packets it + // will raise an error so we can indicate that the file is invalid. + bool mPaddingDiscarded; + int64_t mFrames; + Maybe<int64_t> mLastFrameTime; + uint8_t mMappingTable[MAX_AUDIO_CHANNELS]; // Channel mapping table. + + Atomic<bool> mIsFlushing; +}; + +} // namespace mozilla +#endif diff --git a/dom/media/platforms/agnostic/TheoraDecoder.cpp b/dom/media/platforms/agnostic/TheoraDecoder.cpp new file mode 100644 index 000000000..b216791e4 --- /dev/null +++ b/dom/media/platforms/agnostic/TheoraDecoder.cpp @@ -0,0 +1,240 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TheoraDecoder.h" +#include "XiphExtradata.h" +#include "gfx2DGlue.h" +#include "nsError.h" +#include "TimeUnits.h" +#include "mozilla/PodOperations.h" + +#include <algorithm> + +#undef LOG +#define LOG(arg, ...) MOZ_LOG(gMediaDecoderLog, mozilla::LogLevel::Debug, ("TheoraDecoder(%p)::%s: " arg, this, __func__, ##__VA_ARGS__)) + +namespace mozilla { + +using namespace gfx; +using namespace layers; + +extern LazyLogModule gMediaDecoderLog; + +ogg_packet InitTheoraPacket(const unsigned char* aData, size_t aLength, + bool aBOS, bool aEOS, + int64_t aGranulepos, int64_t aPacketNo) +{ + ogg_packet packet; + packet.packet = const_cast<unsigned char*>(aData); + packet.bytes = aLength; + packet.b_o_s = aBOS; + packet.e_o_s = aEOS; + packet.granulepos = aGranulepos; + packet.packetno = aPacketNo; + return packet; +} + +TheoraDecoder::TheoraDecoder(const CreateDecoderParams& aParams) + : mImageContainer(aParams.mImageContainer) + , mTaskQueue(aParams.mTaskQueue) + , mCallback(aParams.mCallback) + , mIsFlushing(false) + , mTheoraSetupInfo(nullptr) + , mTheoraDecoderContext(nullptr) + , mPacketCount(0) + , mInfo(aParams.VideoConfig()) +{ + MOZ_COUNT_CTOR(TheoraDecoder); +} + +TheoraDecoder::~TheoraDecoder() +{ + MOZ_COUNT_DTOR(TheoraDecoder); + th_setup_free(mTheoraSetupInfo); + th_comment_clear(&mTheoraComment); + th_info_clear(&mTheoraInfo); +} + +void +TheoraDecoder::Shutdown() +{ + if (mTheoraDecoderContext) { + th_decode_free(mTheoraDecoderContext); + mTheoraDecoderContext = nullptr; + } +} + +RefPtr<MediaDataDecoder::InitPromise> +TheoraDecoder::Init() +{ + th_comment_init(&mTheoraComment); + th_info_init(&mTheoraInfo); + + nsTArray<unsigned char*> headers; + nsTArray<size_t> headerLens; + if (!XiphExtradataToHeaders(headers, headerLens, + mInfo.mCodecSpecificConfig->Elements(), + mInfo.mCodecSpecificConfig->Length())) { + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); + } + for (size_t i = 0; i < headers.Length(); i++) { + if (NS_FAILED(DoDecodeHeader(headers[i], headerLens[i]))) { + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); + } + } + if (mPacketCount != 3) { + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); + } + + mTheoraDecoderContext = th_decode_alloc(&mTheoraInfo, mTheoraSetupInfo); + if (mTheoraDecoderContext) { + return InitPromise::CreateAndResolve(TrackInfo::kVideoTrack, __func__); + } else { + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); + } + +} + +void +TheoraDecoder::Flush() +{ + MOZ_ASSERT(mCallback->OnReaderTaskQueue()); + mIsFlushing = true; + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([this] () { + // nothing to do for now. + }); + SyncRunnable::DispatchToThread(mTaskQueue, r); + mIsFlushing = false; +} + +nsresult +TheoraDecoder::DoDecodeHeader(const unsigned char* aData, size_t aLength) +{ + bool bos = mPacketCount == 0; + ogg_packet pkt = InitTheoraPacket(aData, aLength, bos, false, 0, mPacketCount++); + + int r = th_decode_headerin(&mTheoraInfo, + &mTheoraComment, + &mTheoraSetupInfo, + &pkt); + return r > 0 ? NS_OK : NS_ERROR_FAILURE; +} + +MediaResult +TheoraDecoder::DoDecode(MediaRawData* aSample) +{ + MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); + + const unsigned char* aData = aSample->Data(); + size_t aLength = aSample->Size(); + + bool bos = mPacketCount == 0; + ogg_packet pkt = InitTheoraPacket(aData, aLength, bos, false, aSample->mTimecode, mPacketCount++); + + int ret = th_decode_packetin(mTheoraDecoderContext, &pkt, nullptr); + if (ret == 0 || ret == TH_DUPFRAME) { + th_ycbcr_buffer ycbcr; + th_decode_ycbcr_out(mTheoraDecoderContext, ycbcr); + + int hdec = !(mTheoraInfo.pixel_fmt & 1); + int vdec = !(mTheoraInfo.pixel_fmt & 2); + + VideoData::YCbCrBuffer b; + b.mPlanes[0].mData = ycbcr[0].data; + b.mPlanes[0].mStride = ycbcr[0].stride; + b.mPlanes[0].mHeight = mTheoraInfo.frame_height; + b.mPlanes[0].mWidth = mTheoraInfo.frame_width; + b.mPlanes[0].mOffset = b.mPlanes[0].mSkip = 0; + + b.mPlanes[1].mData = ycbcr[1].data; + b.mPlanes[1].mStride = ycbcr[1].stride; + b.mPlanes[1].mHeight = mTheoraInfo.frame_height >> vdec; + b.mPlanes[1].mWidth = mTheoraInfo.frame_width >> hdec; + b.mPlanes[1].mOffset = b.mPlanes[1].mSkip = 0; + + b.mPlanes[2].mData = ycbcr[2].data; + b.mPlanes[2].mStride = ycbcr[2].stride; + b.mPlanes[2].mHeight = mTheoraInfo.frame_height >> vdec; + b.mPlanes[2].mWidth = mTheoraInfo.frame_width >> hdec; + b.mPlanes[2].mOffset = b.mPlanes[2].mSkip = 0; + + IntRect pictureArea(mTheoraInfo.pic_x, mTheoraInfo.pic_y, + mTheoraInfo.pic_width, mTheoraInfo.pic_height); + + VideoInfo info; + info.mDisplay = mInfo.mDisplay; + RefPtr<VideoData> v = + VideoData::CreateAndCopyData(info, + mImageContainer, + aSample->mOffset, + aSample->mTime, + aSample->mDuration, + b, + aSample->mKeyframe, + aSample->mTimecode, + mInfo.ScaledImageRect(mTheoraInfo.frame_width, + mTheoraInfo.frame_height)); + if (!v) { + LOG("Image allocation error source %ldx%ld display %ldx%ld picture %ldx%ld", + mTheoraInfo.frame_width, mTheoraInfo.frame_height, mInfo.mDisplay.width, mInfo.mDisplay.height, + mInfo.mImage.width, mInfo.mImage.height); + return MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__); + } + mCallback->Output(v); + return NS_OK; + } else { + LOG("Theora Decode error: %d", ret); + return MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, + RESULT_DETAIL("Theora decode error:%d", ret)); + } +} + +void +TheoraDecoder::ProcessDecode(MediaRawData* aSample) +{ + MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); + if (mIsFlushing) { + return; + } + MediaResult rv = DoDecode(aSample); + if (NS_FAILED(rv)) { + mCallback->Error(rv); + } else { + mCallback->InputExhausted(); + } +} + +void +TheoraDecoder::Input(MediaRawData* aSample) +{ + MOZ_ASSERT(mCallback->OnReaderTaskQueue()); + mTaskQueue->Dispatch(NewRunnableMethod<RefPtr<MediaRawData>>( + this, &TheoraDecoder::ProcessDecode, aSample)); +} + +void +TheoraDecoder::ProcessDrain() +{ + MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); + mCallback->DrainComplete(); +} + +void +TheoraDecoder::Drain() +{ + MOZ_ASSERT(mCallback->OnReaderTaskQueue()); + mTaskQueue->Dispatch(NewRunnableMethod(this, &TheoraDecoder::ProcessDrain)); +} + +/* static */ +bool +TheoraDecoder::IsTheora(const nsACString& aMimeType) +{ + return aMimeType.EqualsLiteral("video/theora"); +} + +} // namespace mozilla +#undef LOG diff --git a/dom/media/platforms/agnostic/TheoraDecoder.h b/dom/media/platforms/agnostic/TheoraDecoder.h new file mode 100644 index 000000000..4a5daebb9 --- /dev/null +++ b/dom/media/platforms/agnostic/TheoraDecoder.h @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#if !defined(TheoraDecoder_h_) +#define TheoraDecoder_h_ + +#include "PlatformDecoderModule.h" + +#include <stdint.h> +#include "ogg/ogg.h" +#include "theora/theoradec.h" + +namespace mozilla { + + using namespace layers; + +class TheoraDecoder : public MediaDataDecoder +{ +public: + explicit TheoraDecoder(const CreateDecoderParams& aParams); + + ~TheoraDecoder(); + + RefPtr<InitPromise> Init() override; + void Input(MediaRawData* aSample) override; + void Flush() override; + void Drain() override; + void Shutdown() override; + + // Return true if mimetype is a Theora codec + static bool IsTheora(const nsACString& aMimeType); + + const char* GetDescriptionName() const override + { + return "theora video decoder"; + } + +private: + nsresult DoDecodeHeader(const unsigned char* aData, size_t aLength); + + void ProcessDecode(MediaRawData* aSample); + MediaResult DoDecode(MediaRawData* aSample); + void ProcessDrain(); + + RefPtr<ImageContainer> mImageContainer; + RefPtr<TaskQueue> mTaskQueue; + MediaDataDecoderCallback* mCallback; + Atomic<bool> mIsFlushing; + + // Theora header & decoder state + th_info mTheoraInfo; + th_comment mTheoraComment; + th_setup_info *mTheoraSetupInfo; + th_dec_ctx *mTheoraDecoderContext; + int mPacketCount; + + const VideoInfo& mInfo; +}; + +} // namespace mozilla + +#endif diff --git a/dom/media/platforms/agnostic/VPXDecoder.cpp b/dom/media/platforms/agnostic/VPXDecoder.cpp new file mode 100644 index 000000000..77c81b51b --- /dev/null +++ b/dom/media/platforms/agnostic/VPXDecoder.cpp @@ -0,0 +1,253 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "VPXDecoder.h" +#include "gfx2DGlue.h" +#include "nsError.h" +#include "TimeUnits.h" +#include "mozilla/PodOperations.h" +#include "mozilla/SyncRunnable.h" +#include "prsystem.h" + +#include <algorithm> + +#undef LOG +#define LOG(arg, ...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, ("VPXDecoder(%p)::%s: " arg, this, __func__, ##__VA_ARGS__)) + +namespace mozilla { + +using namespace gfx; +using namespace layers; + +static int MimeTypeToCodec(const nsACString& aMimeType) +{ + if (aMimeType.EqualsLiteral("video/webm; codecs=vp8")) { + return VPXDecoder::Codec::VP8; + } else if (aMimeType.EqualsLiteral("video/webm; codecs=vp9")) { + return VPXDecoder::Codec::VP9; + } else if (aMimeType.EqualsLiteral("video/vp9")) { + return VPXDecoder::Codec::VP9; + } + return -1; +} + +VPXDecoder::VPXDecoder(const CreateDecoderParams& aParams) + : mImageContainer(aParams.mImageContainer) + , mTaskQueue(aParams.mTaskQueue) + , mCallback(aParams.mCallback) + , mIsFlushing(false) + , mInfo(aParams.VideoConfig()) + , mCodec(MimeTypeToCodec(aParams.VideoConfig().mMimeType)) +{ + MOZ_COUNT_CTOR(VPXDecoder); + PodZero(&mVPX); +} + +VPXDecoder::~VPXDecoder() +{ + MOZ_COUNT_DTOR(VPXDecoder); +} + +void +VPXDecoder::Shutdown() +{ + vpx_codec_destroy(&mVPX); +} + +RefPtr<MediaDataDecoder::InitPromise> +VPXDecoder::Init() +{ + int decode_threads = 2; + + vpx_codec_iface_t* dx = nullptr; + if (mCodec == Codec::VP8) { + dx = vpx_codec_vp8_dx(); + } else if (mCodec == Codec::VP9) { + dx = vpx_codec_vp9_dx(); + if (mInfo.mDisplay.width >= 2048) { + decode_threads = 8; + } else if (mInfo.mDisplay.width >= 1024) { + decode_threads = 4; + } + } + decode_threads = std::min(decode_threads, PR_GetNumberOfProcessors()); + + vpx_codec_dec_cfg_t config; + config.threads = decode_threads; + config.w = config.h = 0; // set after decode + + if (!dx || vpx_codec_dec_init(&mVPX, dx, &config, 0)) { + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); + } + return InitPromise::CreateAndResolve(TrackInfo::kVideoTrack, __func__); +} + +void +VPXDecoder::Flush() +{ + MOZ_ASSERT(mCallback->OnReaderTaskQueue()); + mIsFlushing = true; + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([this] () { + // nothing to do for now. + }); + SyncRunnable::DispatchToThread(mTaskQueue, r); + mIsFlushing = false; +} + +MediaResult +VPXDecoder::DoDecode(MediaRawData* aSample) +{ + MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); +#if defined(DEBUG) + vpx_codec_stream_info_t si; + PodZero(&si); + si.sz = sizeof(si); + if (mCodec == Codec::VP8) { + vpx_codec_peek_stream_info(vpx_codec_vp8_dx(), aSample->Data(), aSample->Size(), &si); + } else if (mCodec == Codec::VP9) { + vpx_codec_peek_stream_info(vpx_codec_vp9_dx(), aSample->Data(), aSample->Size(), &si); + } + NS_ASSERTION(bool(si.is_kf) == aSample->mKeyframe, + "VPX Decode Keyframe error sample->mKeyframe and si.si_kf out of sync"); +#endif + + if (vpx_codec_err_t r = vpx_codec_decode(&mVPX, aSample->Data(), aSample->Size(), nullptr, 0)) { + LOG("VPX Decode error: %s", vpx_codec_err_to_string(r)); + return MediaResult( + NS_ERROR_DOM_MEDIA_DECODE_ERR, + RESULT_DETAIL("VPX error: %s", vpx_codec_err_to_string(r))); + } + + vpx_codec_iter_t iter = nullptr; + vpx_image_t *img; + + while ((img = vpx_codec_get_frame(&mVPX, &iter))) { + NS_ASSERTION(img->fmt == VPX_IMG_FMT_I420 || + img->fmt == VPX_IMG_FMT_I444, + "WebM image format not I420 or I444"); + + // Chroma shifts are rounded down as per the decoding examples in the SDK + VideoData::YCbCrBuffer b; + b.mPlanes[0].mData = img->planes[0]; + b.mPlanes[0].mStride = img->stride[0]; + b.mPlanes[0].mHeight = img->d_h; + b.mPlanes[0].mWidth = img->d_w; + b.mPlanes[0].mOffset = b.mPlanes[0].mSkip = 0; + + b.mPlanes[1].mData = img->planes[1]; + b.mPlanes[1].mStride = img->stride[1]; + b.mPlanes[1].mOffset = b.mPlanes[1].mSkip = 0; + + b.mPlanes[2].mData = img->planes[2]; + b.mPlanes[2].mStride = img->stride[2]; + b.mPlanes[2].mOffset = b.mPlanes[2].mSkip = 0; + + if (img->fmt == VPX_IMG_FMT_I420) { + b.mPlanes[1].mHeight = (img->d_h + 1) >> img->y_chroma_shift; + b.mPlanes[1].mWidth = (img->d_w + 1) >> img->x_chroma_shift; + + b.mPlanes[2].mHeight = (img->d_h + 1) >> img->y_chroma_shift; + b.mPlanes[2].mWidth = (img->d_w + 1) >> img->x_chroma_shift; + } else if (img->fmt == VPX_IMG_FMT_I444) { + b.mPlanes[1].mHeight = img->d_h; + b.mPlanes[1].mWidth = img->d_w; + + b.mPlanes[2].mHeight = img->d_h; + b.mPlanes[2].mWidth = img->d_w; + } else { + LOG("VPX Unknown image format"); + return MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, + RESULT_DETAIL("VPX Unknown image format")); + } + + RefPtr<VideoData> v = + VideoData::CreateAndCopyData(mInfo, + mImageContainer, + aSample->mOffset, + aSample->mTime, + aSample->mDuration, + b, + aSample->mKeyframe, + aSample->mTimecode, + mInfo.ScaledImageRect(img->d_w, + img->d_h)); + + if (!v) { + LOG("Image allocation error source %ldx%ld display %ldx%ld picture %ldx%ld", + img->d_w, img->d_h, mInfo.mDisplay.width, mInfo.mDisplay.height, + mInfo.mImage.width, mInfo.mImage.height); + return MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__); + } + mCallback->Output(v); + } + return NS_OK; +} + +void +VPXDecoder::ProcessDecode(MediaRawData* aSample) +{ + MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); + if (mIsFlushing) { + return; + } + MediaResult rv = DoDecode(aSample); + if (NS_FAILED(rv)) { + mCallback->Error(rv); + } else { + mCallback->InputExhausted(); + } +} + +void +VPXDecoder::Input(MediaRawData* aSample) +{ + MOZ_ASSERT(mCallback->OnReaderTaskQueue()); + mTaskQueue->Dispatch(NewRunnableMethod<RefPtr<MediaRawData>>( + this, &VPXDecoder::ProcessDecode, aSample)); +} + +void +VPXDecoder::ProcessDrain() +{ + MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); + mCallback->DrainComplete(); +} + +void +VPXDecoder::Drain() +{ + MOZ_ASSERT(mCallback->OnReaderTaskQueue()); + mTaskQueue->Dispatch(NewRunnableMethod(this, &VPXDecoder::ProcessDrain)); +} + +/* static */ +bool +VPXDecoder::IsVPX(const nsACString& aMimeType, uint8_t aCodecMask) +{ + return ((aCodecMask & VPXDecoder::VP8) && + aMimeType.EqualsLiteral("video/webm; codecs=vp8")) || + ((aCodecMask & VPXDecoder::VP9) && + aMimeType.EqualsLiteral("video/webm; codecs=vp9")) || + ((aCodecMask & VPXDecoder::VP9) && + aMimeType.EqualsLiteral("video/vp9")); +} + +/* static */ +bool +VPXDecoder::IsVP8(const nsACString& aMimeType) +{ + return IsVPX(aMimeType, VPXDecoder::VP8); +} + +/* static */ +bool +VPXDecoder::IsVP9(const nsACString& aMimeType) +{ + return IsVPX(aMimeType, VPXDecoder::VP9); +} + +} // namespace mozilla +#undef LOG diff --git a/dom/media/platforms/agnostic/VPXDecoder.h b/dom/media/platforms/agnostic/VPXDecoder.h new file mode 100644 index 000000000..d420ec069 --- /dev/null +++ b/dom/media/platforms/agnostic/VPXDecoder.h @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#if !defined(VPXDecoder_h_) +#define VPXDecoder_h_ + +#include "PlatformDecoderModule.h" + +#include <stdint.h> +#define VPX_DONT_DEFINE_STDINT_TYPES +#include "vpx/vp8dx.h" +#include "vpx/vpx_codec.h" +#include "vpx/vpx_decoder.h" + +namespace mozilla { + +using namespace layers; + +class VPXDecoder : public MediaDataDecoder +{ +public: + explicit VPXDecoder(const CreateDecoderParams& aParams); + ~VPXDecoder(); + + RefPtr<InitPromise> Init() override; + void Input(MediaRawData* aSample) override; + void Flush() override; + void Drain() override; + void Shutdown() override; + const char* GetDescriptionName() const override + { + return "libvpx video decoder"; + } + + enum Codec: uint8_t { + VP8 = 1 << 0, + VP9 = 1 << 1 + }; + + // Return true if aMimeType is a one of the strings used by our demuxers to + // identify VPX of the specified type. Does not parse general content type + // strings, i.e. white space matters. + static bool IsVPX(const nsACString& aMimeType, uint8_t aCodecMask=VP8|VP9); + static bool IsVP8(const nsACString& aMimeType); + static bool IsVP9(const nsACString& aMimeType); + +private: + void ProcessDecode(MediaRawData* aSample); + MediaResult DoDecode(MediaRawData* aSample); + void ProcessDrain(); + + const RefPtr<ImageContainer> mImageContainer; + const RefPtr<TaskQueue> mTaskQueue; + MediaDataDecoderCallback* mCallback; + Atomic<bool> mIsFlushing; + + // VPx decoder state + vpx_codec_ctx_t mVPX; + + const VideoInfo& mInfo; + + const int mCodec; +}; + +} // namespace mozilla + +#endif diff --git a/dom/media/platforms/agnostic/VorbisDecoder.cpp b/dom/media/platforms/agnostic/VorbisDecoder.cpp new file mode 100644 index 000000000..ed8b90dbd --- /dev/null +++ b/dom/media/platforms/agnostic/VorbisDecoder.cpp @@ -0,0 +1,349 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "VorbisDecoder.h" +#include "VorbisUtils.h" +#include "XiphExtradata.h" + +#include "mozilla/PodOperations.h" +#include "mozilla/SyncRunnable.h" + +#undef LOG +#define LOG(type, msg) MOZ_LOG(sPDMLog, type, msg) + +namespace mozilla { + +ogg_packet InitVorbisPacket(const unsigned char* aData, size_t aLength, + bool aBOS, bool aEOS, + int64_t aGranulepos, int64_t aPacketNo) +{ + ogg_packet packet; + packet.packet = const_cast<unsigned char*>(aData); + packet.bytes = aLength; + packet.b_o_s = aBOS; + packet.e_o_s = aEOS; + packet.granulepos = aGranulepos; + packet.packetno = aPacketNo; + return packet; +} + +VorbisDataDecoder::VorbisDataDecoder(const CreateDecoderParams& aParams) + : mInfo(aParams.AudioConfig()) + , mTaskQueue(aParams.mTaskQueue) + , mCallback(aParams.mCallback) + , mPacketCount(0) + , mFrames(0) + , mIsFlushing(false) +{ + // Zero these member vars to avoid crashes in Vorbis clear functions when + // destructor is called before |Init|. + PodZero(&mVorbisBlock); + PodZero(&mVorbisDsp); + PodZero(&mVorbisInfo); + PodZero(&mVorbisComment); +} + +VorbisDataDecoder::~VorbisDataDecoder() +{ + vorbis_block_clear(&mVorbisBlock); + vorbis_dsp_clear(&mVorbisDsp); + vorbis_info_clear(&mVorbisInfo); + vorbis_comment_clear(&mVorbisComment); +} + +void +VorbisDataDecoder::Shutdown() +{ +} + +RefPtr<MediaDataDecoder::InitPromise> +VorbisDataDecoder::Init() +{ + vorbis_info_init(&mVorbisInfo); + vorbis_comment_init(&mVorbisComment); + PodZero(&mVorbisDsp); + PodZero(&mVorbisBlock); + + AutoTArray<unsigned char*,4> headers; + AutoTArray<size_t,4> headerLens; + if (!XiphExtradataToHeaders(headers, headerLens, + mInfo.mCodecSpecificConfig->Elements(), + mInfo.mCodecSpecificConfig->Length())) { + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); + } + for (size_t i = 0; i < headers.Length(); i++) { + if (NS_FAILED(DecodeHeader(headers[i], headerLens[i]))) { + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); + } + } + + MOZ_ASSERT(mPacketCount == 3); + + int r = vorbis_synthesis_init(&mVorbisDsp, &mVorbisInfo); + if (r) { + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); + } + + r = vorbis_block_init(&mVorbisDsp, &mVorbisBlock); + if (r) { + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); + } + + if (mInfo.mRate != (uint32_t)mVorbisDsp.vi->rate) { + LOG(LogLevel::Warning, + ("Invalid Vorbis header: container and codec rate do not match!")); + } + if (mInfo.mChannels != (uint32_t)mVorbisDsp.vi->channels) { + LOG(LogLevel::Warning, + ("Invalid Vorbis header: container and codec channels do not match!")); + } + + AudioConfig::ChannelLayout layout(mVorbisDsp.vi->channels); + if (!layout.IsValid()) { + return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); + } + + return InitPromise::CreateAndResolve(TrackInfo::kAudioTrack, __func__); +} + +nsresult +VorbisDataDecoder::DecodeHeader(const unsigned char* aData, size_t aLength) +{ + bool bos = mPacketCount == 0; + ogg_packet pkt = InitVorbisPacket(aData, aLength, bos, false, 0, mPacketCount++); + MOZ_ASSERT(mPacketCount <= 3); + + int r = vorbis_synthesis_headerin(&mVorbisInfo, + &mVorbisComment, + &pkt); + return r == 0 ? NS_OK : NS_ERROR_FAILURE; +} + +void +VorbisDataDecoder::Input(MediaRawData* aSample) +{ + MOZ_ASSERT(mCallback->OnReaderTaskQueue()); + mTaskQueue->Dispatch(NewRunnableMethod<RefPtr<MediaRawData>>( + this, &VorbisDataDecoder::ProcessDecode, aSample)); +} + +void +VorbisDataDecoder::ProcessDecode(MediaRawData* aSample) +{ + MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); + if (mIsFlushing) { + return; + } + + MediaResult rv = DoDecode(aSample); + if (NS_FAILED(rv)) { + mCallback->Error(rv); + } else { + mCallback->InputExhausted(); + } +} + +MediaResult +VorbisDataDecoder::DoDecode(MediaRawData* aSample) +{ + MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); + + const unsigned char* aData = aSample->Data(); + size_t aLength = aSample->Size(); + int64_t aOffset = aSample->mOffset; + int64_t aTstampUsecs = aSample->mTime; + int64_t aTotalFrames = 0; + + MOZ_ASSERT(mPacketCount >= 3); + + if (!mLastFrameTime || mLastFrameTime.ref() != aSample->mTime) { + // We are starting a new block. + mFrames = 0; + mLastFrameTime = Some(aSample->mTime); + } + + ogg_packet pkt = InitVorbisPacket(aData, aLength, false, aSample->mEOS, + aSample->mTimecode, mPacketCount++); + + int err = vorbis_synthesis(&mVorbisBlock, &pkt); + if (err) { + return MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, + RESULT_DETAIL("vorbis_synthesis:%d", err)); + } + + err = vorbis_synthesis_blockin(&mVorbisDsp, &mVorbisBlock); + if (err) { + return MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, + RESULT_DETAIL("vorbis_synthesis_blockin:%d", err)); + } + + VorbisPCMValue** pcm = 0; + int32_t frames = vorbis_synthesis_pcmout(&mVorbisDsp, &pcm); + if (frames == 0) { + return NS_OK; + } + while (frames > 0) { + uint32_t channels = mVorbisDsp.vi->channels; + uint32_t rate = mVorbisDsp.vi->rate; + AlignedAudioBuffer buffer(frames*channels); + if (!buffer) { + return MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__); + } + for (uint32_t j = 0; j < channels; ++j) { + VorbisPCMValue* channel = pcm[j]; + for (uint32_t i = 0; i < uint32_t(frames); ++i) { + buffer[i*channels + j] = MOZ_CONVERT_VORBIS_SAMPLE(channel[i]); + } + } + + CheckedInt64 duration = FramesToUsecs(frames, rate); + if (!duration.isValid()) { + return MediaResult(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR, + RESULT_DETAIL("Overflow converting audio duration")); + } + CheckedInt64 total_duration = FramesToUsecs(mFrames, rate); + if (!total_duration.isValid()) { + return MediaResult( + NS_ERROR_DOM_MEDIA_OVERFLOW_ERR, + RESULT_DETAIL("Overflow converting audio total_duration")); + } + + CheckedInt64 time = total_duration + aTstampUsecs; + if (!time.isValid()) { + return MediaResult( + NS_ERROR_DOM_MEDIA_OVERFLOW_ERR, + RESULT_DETAIL("Overflow adding total_duration and aTstampUsecs")); + }; + + if (!mAudioConverter) { + AudioConfig in(AudioConfig::ChannelLayout(channels, VorbisLayout(channels)), + rate); + AudioConfig out(channels, rate); + if (!in.IsValid() || !out.IsValid()) { + return MediaResult( + NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL("Invalid channel layout:%u", channels)); + } + mAudioConverter = MakeUnique<AudioConverter>(in, out); + } + MOZ_ASSERT(mAudioConverter->CanWorkInPlace()); + AudioSampleBuffer data(Move(buffer)); + data = mAudioConverter->Process(Move(data)); + + aTotalFrames += frames; + mCallback->Output(new AudioData(aOffset, + time.value(), + duration.value(), + frames, + data.Forget(), + channels, + rate)); + mFrames += frames; + err = vorbis_synthesis_read(&mVorbisDsp, frames); + if (err) { + return MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, + RESULT_DETAIL("vorbis_synthesis_read:%d", err)); + } + + frames = vorbis_synthesis_pcmout(&mVorbisDsp, &pcm); + } + + return NS_OK; +} + +void +VorbisDataDecoder::ProcessDrain() +{ + MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); + mCallback->DrainComplete(); +} + +void +VorbisDataDecoder::Drain() +{ + MOZ_ASSERT(mCallback->OnReaderTaskQueue()); + mTaskQueue->Dispatch(NewRunnableMethod(this, &VorbisDataDecoder::ProcessDrain)); +} + +void +VorbisDataDecoder::Flush() +{ + MOZ_ASSERT(mCallback->OnReaderTaskQueue()); + mIsFlushing = true; + RefPtr<VorbisDataDecoder> self = this; + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([self] () { + // Ignore failed results from vorbis_synthesis_restart. They + // aren't fatal and it fails when ResetDecode is called at a + // time when no vorbis data has been read. + vorbis_synthesis_restart(&self->mVorbisDsp); + self->mLastFrameTime.reset(); + }); + SyncRunnable::DispatchToThread(mTaskQueue, r); + mIsFlushing = false; +} + +/* static */ +bool +VorbisDataDecoder::IsVorbis(const nsACString& aMimeType) +{ + return aMimeType.EqualsLiteral("audio/vorbis"); +} + +/* static */ const AudioConfig::Channel* +VorbisDataDecoder::VorbisLayout(uint32_t aChannels) +{ + // From https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html + // Section 4.3.9. + typedef AudioConfig::Channel Channel; + + switch (aChannels) { + case 1: // the stream is monophonic + { + static const Channel config[] = { AudioConfig::CHANNEL_MONO }; + return config; + } + case 2: // the stream is stereo. channel order: left, right + { + static const Channel config[] = { AudioConfig::CHANNEL_LEFT, AudioConfig::CHANNEL_RIGHT }; + return config; + } + case 3: // the stream is a 1d-surround encoding. channel order: left, center, right + { + static const Channel config[] = { AudioConfig::CHANNEL_LEFT, AudioConfig::CHANNEL_CENTER, AudioConfig::CHANNEL_RIGHT }; + return config; + } + case 4: // the stream is quadraphonic surround. channel order: front left, front right, rear left, rear right + { + static const Channel config[] = { AudioConfig::CHANNEL_LEFT, AudioConfig::CHANNEL_RIGHT, AudioConfig::CHANNEL_LS, AudioConfig::CHANNEL_RS }; + return config; + } + case 5: // the stream is five-channel surround. channel order: front left, center, front right, rear left, rear right + { + static const Channel config[] = { AudioConfig::CHANNEL_LEFT, AudioConfig::CHANNEL_CENTER, AudioConfig::CHANNEL_RIGHT, AudioConfig::CHANNEL_LS, AudioConfig::CHANNEL_RS }; + return config; + } + case 6: // the stream is 5.1 surround. channel order: front left, center, front right, rear left, rear right, LFE + { + static const Channel config[] = { AudioConfig::CHANNEL_LEFT, AudioConfig::CHANNEL_CENTER, AudioConfig::CHANNEL_RIGHT, AudioConfig::CHANNEL_LS, AudioConfig::CHANNEL_RS, AudioConfig::CHANNEL_LFE }; + return config; + } + case 7: // surround. channel order: front left, center, front right, side left, side right, rear center, LFE + { + static const Channel config[] = { AudioConfig::CHANNEL_LEFT, AudioConfig::CHANNEL_CENTER, AudioConfig::CHANNEL_RIGHT, AudioConfig::CHANNEL_LS, AudioConfig::CHANNEL_RS, AudioConfig::CHANNEL_RCENTER, AudioConfig::CHANNEL_LFE }; + return config; + } + case 8: // the stream is 7.1 surround. channel order: front left, center, front right, side left, side right, rear left, rear right, LFE + { + static const Channel config[] = { AudioConfig::CHANNEL_LEFT, AudioConfig::CHANNEL_CENTER, AudioConfig::CHANNEL_RIGHT, AudioConfig::CHANNEL_LS, AudioConfig::CHANNEL_RS, AudioConfig::CHANNEL_RLS, AudioConfig::CHANNEL_RRS, AudioConfig::CHANNEL_LFE }; + return config; + } + default: + return nullptr; + } +} + +} // namespace mozilla +#undef LOG diff --git a/dom/media/platforms/agnostic/VorbisDecoder.h b/dom/media/platforms/agnostic/VorbisDecoder.h new file mode 100644 index 000000000..0ed7bb645 --- /dev/null +++ b/dom/media/platforms/agnostic/VorbisDecoder.h @@ -0,0 +1,66 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#if !defined(VorbisDecoder_h_) +#define VorbisDecoder_h_ + +#include "PlatformDecoderModule.h" +#include "mozilla/Maybe.h" +#include "AudioConverter.h" + +#ifdef MOZ_TREMOR +#include "tremor/ivorbiscodec.h" +#else +#include "vorbis/codec.h" +#endif + +namespace mozilla { + +class VorbisDataDecoder : public MediaDataDecoder +{ +public: + explicit VorbisDataDecoder(const CreateDecoderParams& aParams); + ~VorbisDataDecoder(); + + RefPtr<InitPromise> Init() override; + void Input(MediaRawData* aSample) override; + void Flush() override; + void Drain() override; + void Shutdown() override; + const char* GetDescriptionName() const override + { + return "vorbis audio decoder"; + } + + // Return true if mimetype is Vorbis + static bool IsVorbis(const nsACString& aMimeType); + static const AudioConfig::Channel* VorbisLayout(uint32_t aChannels); + +private: + nsresult DecodeHeader(const unsigned char* aData, size_t aLength); + + void ProcessDecode(MediaRawData* aSample); + MediaResult DoDecode(MediaRawData* aSample); + void ProcessDrain(); + + const AudioInfo& mInfo; + const RefPtr<TaskQueue> mTaskQueue; + MediaDataDecoderCallback* mCallback; + + // Vorbis decoder state + vorbis_info mVorbisInfo; + vorbis_comment mVorbisComment; + vorbis_dsp_state mVorbisDsp; + vorbis_block mVorbisBlock; + + int64_t mPacketCount; + int64_t mFrames; + Maybe<int64_t> mLastFrameTime; + UniquePtr<AudioConverter> mAudioConverter; + Atomic<bool> mIsFlushing; +}; + +} // namespace mozilla +#endif diff --git a/dom/media/platforms/agnostic/WAVDecoder.cpp b/dom/media/platforms/agnostic/WAVDecoder.cpp new file mode 100644 index 000000000..67a045287 --- /dev/null +++ b/dom/media/platforms/agnostic/WAVDecoder.cpp @@ -0,0 +1,158 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WAVDecoder.h" +#include "AudioSampleFormat.h" +#include "mozilla/SyncRunnable.h" + +using mp4_demuxer::ByteReader; + +namespace mozilla { + +int16_t +DecodeALawSample(uint8_t aValue) +{ + aValue = aValue ^ 0x55; + int8_t sign = (aValue & 0x80) ? -1 : 1; + uint8_t exponent = (aValue & 0x70) >> 4; + uint8_t mantissa = aValue & 0x0F; + int16_t sample = mantissa << 4; + switch (exponent) { + case 0: + sample += 8; + break; + case 1: + sample += 0x108; + break; + default: + sample += 0x108; + sample <<= exponent - 1; + } + return sign * sample; +} + +int16_t +DecodeULawSample(uint8_t aValue) +{ + aValue = aValue ^ 0xFF; + int8_t sign = (aValue & 0x80) ? -1 : 1; + uint8_t exponent = (aValue & 0x70) >> 4; + uint8_t mantissa = aValue & 0x0F; + int16_t sample = (33 + 2 * mantissa) * (2 << (exponent + 1)) - 33; + return sign * sample; +} + +WaveDataDecoder::WaveDataDecoder(const CreateDecoderParams& aParams) + : mInfo(aParams.AudioConfig()) + , mCallback(aParams.mCallback) +{ +} + +void +WaveDataDecoder::Shutdown() +{ +} + +RefPtr<MediaDataDecoder::InitPromise> +WaveDataDecoder::Init() +{ + return InitPromise::CreateAndResolve(TrackInfo::kAudioTrack, __func__); +} + +void +WaveDataDecoder::Input(MediaRawData* aSample) +{ + MediaResult rv = DoDecode(aSample); + if (NS_FAILED(rv)) { + mCallback->Error(rv); + } else { + mCallback->InputExhausted(); + } +} + +MediaResult +WaveDataDecoder::DoDecode(MediaRawData* aSample) +{ + size_t aLength = aSample->Size(); + ByteReader aReader(aSample->Data(), aLength); + int64_t aOffset = aSample->mOffset; + uint64_t aTstampUsecs = aSample->mTime; + + int32_t frames = aLength * 8 / mInfo.mBitDepth / mInfo.mChannels; + + AlignedAudioBuffer buffer(frames * mInfo.mChannels); + if (!buffer) { + return MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__); + } + for (int i = 0; i < frames; ++i) { + for (unsigned int j = 0; j < mInfo.mChannels; ++j) { + if (mInfo.mProfile == 6) { //ALAW Data + uint8_t v = aReader.ReadU8(); + int16_t decoded = DecodeALawSample(v); + buffer[i * mInfo.mChannels + j] = + IntegerToAudioSample<AudioDataValue>(decoded); + } else if (mInfo.mProfile == 7) { //ULAW Data + uint8_t v = aReader.ReadU8(); + int16_t decoded = DecodeULawSample(v); + buffer[i * mInfo.mChannels + j] = + IntegerToAudioSample<AudioDataValue>(decoded); + } else { //PCM Data + if (mInfo.mBitDepth == 8) { + uint8_t v = aReader.ReadU8(); + buffer[i * mInfo.mChannels + j] = + UInt8bitToAudioSample<AudioDataValue>(v); + } else if (mInfo.mBitDepth == 16) { + int16_t v = aReader.ReadLE16(); + buffer[i * mInfo.mChannels + j] = + IntegerToAudioSample<AudioDataValue>(v); + } else if (mInfo.mBitDepth == 24) { + int32_t v = aReader.ReadLE24(); + buffer[i * mInfo.mChannels + j] = + Int24bitToAudioSample<AudioDataValue>(v); + } + } + } + } + + int64_t duration = frames / mInfo.mRate; + + mCallback->Output(new AudioData(aOffset, + aTstampUsecs, + duration, + frames, + Move(buffer), + mInfo.mChannels, + mInfo.mRate)); + + return NS_OK; +} + +void +WaveDataDecoder::Drain() +{ + mCallback->DrainComplete(); +} + +void +WaveDataDecoder::Flush() +{ +} + +/* static */ +bool +WaveDataDecoder::IsWave(const nsACString& aMimeType) +{ + // Some WebAudio uses "audio/x-wav", + // WAVdemuxer uses "audio/wave; codecs=aNum". + return 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"); +} + +} // namespace mozilla +#undef LOG diff --git a/dom/media/platforms/agnostic/WAVDecoder.h b/dom/media/platforms/agnostic/WAVDecoder.h new file mode 100644 index 000000000..dea980168 --- /dev/null +++ b/dom/media/platforms/agnostic/WAVDecoder.h @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if !defined(WaveDecoder_h_) +#define WaveDecoder_h_ + +#include "PlatformDecoderModule.h" +#include "mp4_demuxer/ByteReader.h" + +namespace mozilla { + +class WaveDataDecoder : public MediaDataDecoder +{ +public: + explicit WaveDataDecoder(const CreateDecoderParams& aParams); + + // Return true if mimetype is Wave + static bool IsWave(const nsACString& aMimeType); + + RefPtr<InitPromise> Init() override; + void Input(MediaRawData* aSample) override; + void Flush() override; + void Drain() override; + void Shutdown() override; + const char* GetDescriptionName() const override + { + return "wave audio decoder"; + } + +private: + MediaResult DoDecode(MediaRawData* aSample); + + const AudioInfo& mInfo; + MediaDataDecoderCallback* mCallback; +}; + +} // namespace mozilla +#endif diff --git a/dom/media/platforms/agnostic/eme/EMEAudioDecoder.cpp b/dom/media/platforms/agnostic/eme/EMEAudioDecoder.cpp new file mode 100644 index 000000000..25e502025 --- /dev/null +++ b/dom/media/platforms/agnostic/eme/EMEAudioDecoder.cpp @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "EMEAudioDecoder.h" +#include "mozilla/CDMProxy.h" + +namespace mozilla { + +void +EMEAudioCallbackAdapter::Error(GMPErr aErr) +{ + if (aErr == GMPNoKeyErr) { + // The GMP failed to decrypt a frame due to not having a key. This can + // happen if a key expires or a session is closed during playback. + NS_WARNING("GMP failed to decrypt due to lack of key"); + return; + } + AudioCallbackAdapter::Error(aErr); +} + +EMEAudioDecoder::EMEAudioDecoder(CDMProxy* aProxy, + const GMPAudioDecoderParams& aParams) + : GMPAudioDecoder(GMPAudioDecoderParams(aParams).WithAdapter( + new EMEAudioCallbackAdapter(aParams.mCallback))) + , mProxy(aProxy) +{} + +void +EMEAudioDecoder::InitTags(nsTArray<nsCString>& aTags) +{ + aTags.AppendElement(NS_LITERAL_CSTRING("aac")); + aTags.AppendElement(NS_ConvertUTF16toUTF8(mProxy->KeySystem())); +} + +nsCString +EMEAudioDecoder::GetNodeId() +{ + return mProxy->GetNodeId(); +} + +} // namespace mozilla diff --git a/dom/media/platforms/agnostic/eme/EMEAudioDecoder.h b/dom/media/platforms/agnostic/eme/EMEAudioDecoder.h new file mode 100644 index 000000000..c5944cf8c --- /dev/null +++ b/dom/media/platforms/agnostic/eme/EMEAudioDecoder.h @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef EMEAudioDecoder_h_ +#define EMEAudioDecoder_h_ + +#include "GMPAudioDecoder.h" +#include "PlatformDecoderModule.h" + +namespace mozilla { + +class EMEAudioCallbackAdapter : public AudioCallbackAdapter { +public: + explicit EMEAudioCallbackAdapter(MediaDataDecoderCallbackProxy* aCallback) + : AudioCallbackAdapter(aCallback) + {} + + void Error(GMPErr aErr) override; +}; + +class EMEAudioDecoder : public GMPAudioDecoder { +public: + EMEAudioDecoder(CDMProxy* aProxy, const GMPAudioDecoderParams& aParams); + +private: + void InitTags(nsTArray<nsCString>& aTags) override; + nsCString GetNodeId() override; + + RefPtr<CDMProxy> mProxy; +}; + +} // namespace mozilla + +#endif diff --git a/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp b/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp new file mode 100644 index 000000000..7d523d926 --- /dev/null +++ b/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp @@ -0,0 +1,307 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "EMEDecoderModule.h" +#include "EMEAudioDecoder.h" +#include "EMEVideoDecoder.h" +#include "MediaDataDecoderProxy.h" +#include "mozIGeckoMediaPluginService.h" +#include "mozilla/CDMProxy.h" +#include "mozilla/Unused.h" +#include "nsAutoPtr.h" +#include "nsServiceManagerUtils.h" +#include "MediaInfo.h" +#include "nsClassHashtable.h" +#include "GMPDecoderModule.h" +#include "MP4Decoder.h" + +namespace mozilla { + +typedef MozPromiseRequestHolder<CDMProxy::DecryptPromise> DecryptPromiseRequestHolder; + +class EMEDecryptor : public MediaDataDecoder { + +public: + + EMEDecryptor(MediaDataDecoder* aDecoder, + MediaDataDecoderCallback* aCallback, + CDMProxy* aProxy, + TaskQueue* aDecodeTaskQueue) + : mDecoder(aDecoder) + , mCallback(aCallback) + , mTaskQueue(aDecodeTaskQueue) + , mProxy(aProxy) + , mSamplesWaitingForKey(new SamplesWaitingForKey(this, this->mCallback, + mTaskQueue, mProxy)) + , mIsShutdown(false) + { + } + + RefPtr<InitPromise> Init() override { + MOZ_ASSERT(!mIsShutdown); + return mDecoder->Init(); + } + + void Input(MediaRawData* aSample) override { + MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); + if (mIsShutdown) { + NS_WARNING("EME encrypted sample arrived after shutdown"); + return; + } + if (mSamplesWaitingForKey->WaitIfKeyNotUsable(aSample)) { + return; + } + + nsAutoPtr<MediaRawDataWriter> writer(aSample->CreateWriter()); + mProxy->GetSessionIdsForKeyId(aSample->mCrypto.mKeyId, + writer->mCrypto.mSessionIds); + + mDecrypts.Put(aSample, new DecryptPromiseRequestHolder()); + mDecrypts.Get(aSample)->Begin(mProxy->Decrypt(aSample)->Then( + mTaskQueue, __func__, this, + &EMEDecryptor::Decrypted, + &EMEDecryptor::Decrypted)); + return; + } + + void Decrypted(const DecryptResult& aDecrypted) { + MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); + MOZ_ASSERT(aDecrypted.mSample); + + nsAutoPtr<DecryptPromiseRequestHolder> holder; + mDecrypts.RemoveAndForget(aDecrypted.mSample, holder); + if (holder) { + holder->Complete(); + } else { + // Decryption is not in the list of decrypt operations waiting + // for a result. It must have been flushed or drained. Ignore result. + return; + } + + if (mIsShutdown) { + NS_WARNING("EME decrypted sample arrived after shutdown"); + return; + } + + if (aDecrypted.mStatus == NoKeyErr) { + // Key became unusable after we sent the sample to CDM to decrypt. + // Call Input() again, so that the sample is enqueued for decryption + // if the key becomes usable again. + Input(aDecrypted.mSample); + } else if (aDecrypted.mStatus != Ok) { + if (mCallback) { + mCallback->Error(MediaResult( + NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL("decrypted.mStatus=%u", uint32_t(aDecrypted.mStatus)))); + } + } else { + MOZ_ASSERT(!mIsShutdown); + // The Adobe GMP AAC decoder gets confused if we pass it non-encrypted + // samples with valid crypto data. So clear the crypto data, since the + // sample should be decrypted now anyway. If we don't do this and we're + // using the Adobe GMP for unencrypted decoding of data that is decrypted + // by gmp-clearkey, decoding will fail. + UniquePtr<MediaRawDataWriter> writer(aDecrypted.mSample->CreateWriter()); + writer->mCrypto = CryptoSample(); + mDecoder->Input(aDecrypted.mSample); + } + } + + void Flush() override { + MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); + MOZ_ASSERT(!mIsShutdown); + for (auto iter = mDecrypts.Iter(); !iter.Done(); iter.Next()) { + nsAutoPtr<DecryptPromiseRequestHolder>& holder = iter.Data(); + holder->DisconnectIfExists(); + iter.Remove(); + } + mDecoder->Flush(); + mSamplesWaitingForKey->Flush(); + } + + void Drain() override { + MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); + MOZ_ASSERT(!mIsShutdown); + for (auto iter = mDecrypts.Iter(); !iter.Done(); iter.Next()) { + nsAutoPtr<DecryptPromiseRequestHolder>& holder = iter.Data(); + holder->DisconnectIfExists(); + iter.Remove(); + } + mDecoder->Drain(); + } + + void Shutdown() override { + MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); + MOZ_ASSERT(!mIsShutdown); + mIsShutdown = true; + mDecoder->Shutdown(); + mSamplesWaitingForKey->BreakCycles(); + mSamplesWaitingForKey = nullptr; + mDecoder = nullptr; + mProxy = nullptr; + mCallback = nullptr; + } + + const char* GetDescriptionName() const override { + return mDecoder->GetDescriptionName(); + } + +private: + + RefPtr<MediaDataDecoder> mDecoder; + MediaDataDecoderCallback* mCallback; + RefPtr<TaskQueue> mTaskQueue; + RefPtr<CDMProxy> mProxy; + nsClassHashtable<nsRefPtrHashKey<MediaRawData>, DecryptPromiseRequestHolder> mDecrypts; + RefPtr<SamplesWaitingForKey> mSamplesWaitingForKey; + bool mIsShutdown; +}; + +class EMEMediaDataDecoderProxy : public MediaDataDecoderProxy { +public: + EMEMediaDataDecoderProxy(already_AddRefed<AbstractThread> aProxyThread, + MediaDataDecoderCallback* aCallback, + CDMProxy* aProxy, + TaskQueue* aTaskQueue) + : MediaDataDecoderProxy(Move(aProxyThread), aCallback) + , mSamplesWaitingForKey(new SamplesWaitingForKey(this, aCallback, + aTaskQueue, aProxy)) + , mProxy(aProxy) + { + } + + void Input(MediaRawData* aSample) override; + void Shutdown() override; + +private: + RefPtr<SamplesWaitingForKey> mSamplesWaitingForKey; + RefPtr<CDMProxy> mProxy; +}; + +void +EMEMediaDataDecoderProxy::Input(MediaRawData* aSample) +{ + if (mSamplesWaitingForKey->WaitIfKeyNotUsable(aSample)) { + return; + } + + nsAutoPtr<MediaRawDataWriter> writer(aSample->CreateWriter()); + mProxy->GetSessionIdsForKeyId(aSample->mCrypto.mKeyId, + writer->mCrypto.mSessionIds); + + MediaDataDecoderProxy::Input(aSample); +} + +void +EMEMediaDataDecoderProxy::Shutdown() +{ + MediaDataDecoderProxy::Shutdown(); + + mSamplesWaitingForKey->BreakCycles(); + mSamplesWaitingForKey = nullptr; + mProxy = nullptr; +} + +EMEDecoderModule::EMEDecoderModule(CDMProxy* aProxy, PDMFactory* aPDM) + : mProxy(aProxy) + , mPDM(aPDM) +{ +} + +EMEDecoderModule::~EMEDecoderModule() +{ +} + +static already_AddRefed<MediaDataDecoderProxy> +CreateDecoderWrapper(MediaDataDecoderCallback* aCallback, CDMProxy* aProxy, TaskQueue* aTaskQueue) +{ + RefPtr<gmp::GeckoMediaPluginService> s(gmp::GeckoMediaPluginService::GetGeckoMediaPluginService()); + if (!s) { + return nullptr; + } + RefPtr<AbstractThread> thread(s->GetAbstractGMPThread()); + if (!thread) { + return nullptr; + } + RefPtr<MediaDataDecoderProxy> decoder( + new EMEMediaDataDecoderProxy(thread.forget(), aCallback, aProxy, aTaskQueue)); + return decoder.forget(); +} + +already_AddRefed<MediaDataDecoder> +EMEDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams) +{ + MOZ_ASSERT(aParams.mConfig.mCrypto.mValid); + + if (SupportsMimeType(aParams.mConfig.mMimeType, nullptr)) { + // GMP decodes. Assume that means it can decrypt too. + RefPtr<MediaDataDecoderProxy> wrapper = + CreateDecoderWrapper(aParams.mCallback, mProxy, aParams.mTaskQueue); + auto params = GMPVideoDecoderParams(aParams).WithCallback(wrapper); + wrapper->SetProxyTarget(new EMEVideoDecoder(mProxy, params)); + return wrapper.forget(); + } + + MOZ_ASSERT(mPDM); + RefPtr<MediaDataDecoder> decoder(mPDM->CreateDecoder(aParams)); + if (!decoder) { + return nullptr; + } + + RefPtr<MediaDataDecoder> emeDecoder(new EMEDecryptor(decoder, + aParams.mCallback, + mProxy, + AbstractThread::GetCurrent()->AsTaskQueue())); + return emeDecoder.forget(); +} + +already_AddRefed<MediaDataDecoder> +EMEDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams) +{ + MOZ_ASSERT(aParams.mConfig.mCrypto.mValid); + + if (SupportsMimeType(aParams.mConfig.mMimeType, nullptr)) { + // GMP decodes. Assume that means it can decrypt too. + RefPtr<MediaDataDecoderProxy> wrapper = + CreateDecoderWrapper(aParams.mCallback, mProxy, aParams.mTaskQueue); + auto gmpParams = GMPAudioDecoderParams(aParams).WithCallback(wrapper); + wrapper->SetProxyTarget(new EMEAudioDecoder(mProxy, gmpParams)); + return wrapper.forget(); + } + + MOZ_ASSERT(mPDM); + RefPtr<MediaDataDecoder> decoder(mPDM->CreateDecoder(aParams)); + if (!decoder) { + return nullptr; + } + + RefPtr<MediaDataDecoder> emeDecoder(new EMEDecryptor(decoder, + aParams.mCallback, + mProxy, + AbstractThread::GetCurrent()->AsTaskQueue())); + return emeDecoder.forget(); +} + +PlatformDecoderModule::ConversionRequired +EMEDecoderModule::DecoderNeedsConversion(const TrackInfo& aConfig) const +{ + if (aConfig.IsVideo() && MP4Decoder::IsH264(aConfig.mMimeType)) { + return ConversionRequired::kNeedAVCC; + } else { + return ConversionRequired::kNeedNone; + } +} + +bool +EMEDecoderModule::SupportsMimeType(const nsACString& aMimeType, + DecoderDoctorDiagnostics* aDiagnostics) const +{ + Maybe<nsCString> gmp; + gmp.emplace(NS_ConvertUTF16toUTF8(mProxy->KeySystem())); + return GMPDecoderModule::SupportsMimeType(aMimeType, gmp); +} + +} // namespace mozilla diff --git a/dom/media/platforms/agnostic/eme/EMEDecoderModule.h b/dom/media/platforms/agnostic/eme/EMEDecoderModule.h new file mode 100644 index 000000000..32074ae8c --- /dev/null +++ b/dom/media/platforms/agnostic/eme/EMEDecoderModule.h @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if !defined(EMEDecoderModule_h_) +#define EMEDecoderModule_h_ + +#include "PlatformDecoderModule.h" +#include "PDMFactory.h" +#include "gmp-decryption.h" + +namespace mozilla { + +class CDMProxy; + +class EMEDecoderModule : public PlatformDecoderModule { +private: + +public: + EMEDecoderModule(CDMProxy* aProxy, PDMFactory* aPDM); + + virtual ~EMEDecoderModule(); + +protected: + // Decode thread. + already_AddRefed<MediaDataDecoder> + CreateVideoDecoder(const CreateDecoderParams& aParams) override; + + // Decode thread. + already_AddRefed<MediaDataDecoder> + CreateAudioDecoder(const CreateDecoderParams& aParams) override; + + ConversionRequired + DecoderNeedsConversion(const TrackInfo& aConfig) const override; + + bool + SupportsMimeType(const nsACString& aMimeType, + DecoderDoctorDiagnostics* aDiagnostics) const override; + +private: + RefPtr<CDMProxy> mProxy; + // Will be null if CDM has decoding capability. + RefPtr<PDMFactory> mPDM; + // We run the PDM on its own task queue. + RefPtr<TaskQueue> mTaskQueue; +}; + +} // namespace mozilla + +#endif // EMEDecoderModule_h_ diff --git a/dom/media/platforms/agnostic/eme/EMEVideoDecoder.cpp b/dom/media/platforms/agnostic/eme/EMEVideoDecoder.cpp new file mode 100644 index 000000000..bddb89660 --- /dev/null +++ b/dom/media/platforms/agnostic/eme/EMEVideoDecoder.cpp @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "EMEVideoDecoder.h" +#include "GMPVideoEncodedFrameImpl.h" +#include "mozilla/CDMProxy.h" +#include "MediaData.h" +#include "MP4Decoder.h" +#include "VPXDecoder.h" + +namespace mozilla { + +void +EMEVideoCallbackAdapter::Error(GMPErr aErr) +{ + if (aErr == GMPNoKeyErr) { + // The GMP failed to decrypt a frame due to not having a key. This can + // happen if a key expires or a session is closed during playback. + NS_WARNING("GMP failed to decrypt due to lack of key"); + return; + } + VideoCallbackAdapter::Error(aErr); +} + +EMEVideoDecoder::EMEVideoDecoder(CDMProxy* aProxy, + const GMPVideoDecoderParams& aParams) + : GMPVideoDecoder(GMPVideoDecoderParams(aParams).WithAdapter( + new EMEVideoCallbackAdapter(aParams.mCallback, + VideoInfo(aParams.mConfig.mDisplay), + aParams.mImageContainer))) + , mProxy(aProxy) + , mDecryptorId(aProxy->GetDecryptorId()) +{} + +void +EMEVideoDecoder::InitTags(nsTArray<nsCString>& aTags) +{ + VideoInfo config = GetConfig(); + if (MP4Decoder::IsH264(config.mMimeType)) { + aTags.AppendElement(NS_LITERAL_CSTRING("h264")); + } else if (VPXDecoder::IsVP8(config.mMimeType)) { + aTags.AppendElement(NS_LITERAL_CSTRING("vp8")); + } else if (VPXDecoder::IsVP9(config.mMimeType)) { + aTags.AppendElement(NS_LITERAL_CSTRING("vp9")); + } + aTags.AppendElement(NS_ConvertUTF16toUTF8(mProxy->KeySystem())); +} + +nsCString +EMEVideoDecoder::GetNodeId() +{ + return mProxy->GetNodeId(); +} + +GMPUniquePtr<GMPVideoEncodedFrame> +EMEVideoDecoder::CreateFrame(MediaRawData* aSample) +{ + GMPUniquePtr<GMPVideoEncodedFrame> frame = GMPVideoDecoder::CreateFrame(aSample); + if (frame && aSample->mCrypto.mValid) { + static_cast<gmp::GMPVideoEncodedFrameImpl*>(frame.get())->InitCrypto(aSample->mCrypto); + } + return frame; +} + +} // namespace mozilla diff --git a/dom/media/platforms/agnostic/eme/EMEVideoDecoder.h b/dom/media/platforms/agnostic/eme/EMEVideoDecoder.h new file mode 100644 index 000000000..a0f23f867 --- /dev/null +++ b/dom/media/platforms/agnostic/eme/EMEVideoDecoder.h @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef EMEVideoDecoder_h_ +#define EMEVideoDecoder_h_ + +#include "GMPVideoDecoder.h" +#include "PlatformDecoderModule.h" + +namespace mozilla { + +class CDMProxy; +class TaskQueue; + +class EMEVideoCallbackAdapter : public VideoCallbackAdapter { +public: + EMEVideoCallbackAdapter(MediaDataDecoderCallbackProxy* aCallback, + VideoInfo aVideoInfo, + layers::ImageContainer* aImageContainer) + : VideoCallbackAdapter(aCallback, aVideoInfo, aImageContainer) + {} + + void Error(GMPErr aErr) override; +}; + +class EMEVideoDecoder : public GMPVideoDecoder { +public: + EMEVideoDecoder(CDMProxy* aProxy, const GMPVideoDecoderParams& aParams); + +private: + void InitTags(nsTArray<nsCString>& aTags) override; + nsCString GetNodeId() override; + uint32_t DecryptorId() const override { return mDecryptorId; } + GMPUniquePtr<GMPVideoEncodedFrame> CreateFrame(MediaRawData* aSample) override; + + RefPtr<CDMProxy> mProxy; + uint32_t mDecryptorId; +}; + +} // namespace mozilla + +#endif // EMEVideoDecoder_h_ diff --git a/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.cpp b/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.cpp new file mode 100644 index 000000000..58098c2b3 --- /dev/null +++ b/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.cpp @@ -0,0 +1,86 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "SamplesWaitingForKey.h" +#include "mozilla/CDMProxy.h" +#include "mozilla/CDMCaps.h" +#include "MediaData.h" + +namespace mozilla { + +SamplesWaitingForKey::SamplesWaitingForKey(MediaDataDecoder* aDecoder, + MediaDataDecoderCallback* aCallback, + TaskQueue* aTaskQueue, + CDMProxy* aProxy) + : mMutex("SamplesWaitingForKey") + , mDecoder(aDecoder) + , mDecoderCallback(aCallback) + , mTaskQueue(aTaskQueue) + , mProxy(aProxy) +{ +} + +SamplesWaitingForKey::~SamplesWaitingForKey() +{ +} + +bool +SamplesWaitingForKey::WaitIfKeyNotUsable(MediaRawData* aSample) +{ + if (!aSample || !aSample->mCrypto.mValid || !mProxy) { + return false; + } + CDMCaps::AutoLock caps(mProxy->Capabilites()); + const auto& keyid = aSample->mCrypto.mKeyId; + if (!caps.IsKeyUsable(keyid)) { + { + MutexAutoLock lock(mMutex); + mSamples.AppendElement(aSample); + } + mDecoderCallback->WaitingForKey(); + caps.NotifyWhenKeyIdUsable(aSample->mCrypto.mKeyId, this); + return true; + } + return false; +} + +void +SamplesWaitingForKey::NotifyUsable(const CencKeyId& aKeyId) +{ + MutexAutoLock lock(mMutex); + size_t i = 0; + while (i < mSamples.Length()) { + if (aKeyId == mSamples[i]->mCrypto.mKeyId) { + RefPtr<nsIRunnable> task; + task = NewRunnableMethod<RefPtr<MediaRawData>>(mDecoder, + &MediaDataDecoder::Input, + RefPtr<MediaRawData>(mSamples[i])); + mSamples.RemoveElementAt(i); + mTaskQueue->Dispatch(task.forget()); + } else { + i++; + } + } +} + +void +SamplesWaitingForKey::Flush() +{ + MutexAutoLock lock(mMutex); + mSamples.Clear(); +} + +void +SamplesWaitingForKey::BreakCycles() +{ + MutexAutoLock lock(mMutex); + mDecoder = nullptr; + mTaskQueue = nullptr; + mProxy = nullptr; + mSamples.Clear(); +} + +} // namespace mozilla diff --git a/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.h b/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.h new file mode 100644 index 000000000..65bb14403 --- /dev/null +++ b/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.h @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef SamplesWaitingForKey_h_ +#define SamplesWaitingForKey_h_ + +#include "mozilla/TaskQueue.h" + +#include "PlatformDecoderModule.h" + +namespace mozilla { + +typedef nsTArray<uint8_t> CencKeyId; + +class CDMProxy; + +// Encapsulates the task of waiting for the CDMProxy to have the necessary +// keys to decrypt a given sample. +class SamplesWaitingForKey { +public: + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SamplesWaitingForKey) + + explicit SamplesWaitingForKey(MediaDataDecoder* aDecoder, + MediaDataDecoderCallback* aCallback, + TaskQueue* aTaskQueue, + CDMProxy* aProxy); + + // Returns true if we need to wait for a key to become usable. + // Will callback MediaDataDecoder::Input(aSample) on mDecoder once the + // sample is ready to be decrypted. The order of input samples is + // preserved. + bool WaitIfKeyNotUsable(MediaRawData* aSample); + + void NotifyUsable(const CencKeyId& aKeyId); + + void Flush(); + + void BreakCycles(); + +protected: + ~SamplesWaitingForKey(); + +private: + Mutex mMutex; + RefPtr<MediaDataDecoder> mDecoder; + MediaDataDecoderCallback* mDecoderCallback; + RefPtr<TaskQueue> mTaskQueue; + RefPtr<CDMProxy> mProxy; + nsTArray<RefPtr<MediaRawData>> mSamples; +}; + +} // namespace mozilla + +#endif // SamplesWaitingForKey_h_ diff --git a/dom/media/platforms/agnostic/eme/moz.build b/dom/media/platforms/agnostic/eme/moz.build new file mode 100644 index 000000000..78b76d4d4 --- /dev/null +++ b/dom/media/platforms/agnostic/eme/moz.build @@ -0,0 +1,23 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +EXPORTS += [ + 'EMEAudioDecoder.h', + 'EMEDecoderModule.h', + 'EMEVideoDecoder.h', + 'SamplesWaitingForKey.h', +] + +UNIFIED_SOURCES += [ + 'EMEAudioDecoder.cpp', + 'EMEDecoderModule.cpp', + 'EMEVideoDecoder.cpp', + 'SamplesWaitingForKey.cpp', +] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' diff --git a/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.cpp b/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.cpp new file mode 100644 index 000000000..d863d44d4 --- /dev/null +++ b/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.cpp @@ -0,0 +1,306 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GMPAudioDecoder.h" +#include "nsServiceManagerUtils.h" +#include "MediaInfo.h" +#include "GMPDecoderModule.h" +#include "nsPrintfCString.h" + +namespace mozilla { + +#if defined(DEBUG) +bool IsOnGMPThread() +{ + nsCOMPtr<mozIGeckoMediaPluginService> mps = do_GetService("@mozilla.org/gecko-media-plugin-service;1"); + MOZ_ASSERT(mps); + + nsCOMPtr<nsIThread> gmpThread; + nsresult rv = mps->GetThread(getter_AddRefs(gmpThread)); + MOZ_ASSERT(NS_SUCCEEDED(rv) && gmpThread); + return NS_GetCurrentThread() == gmpThread; +} +#endif + +void +AudioCallbackAdapter::Decoded(const nsTArray<int16_t>& aPCM, uint64_t aTimeStamp, uint32_t aChannels, uint32_t aRate) +{ + MOZ_ASSERT(IsOnGMPThread()); + + if (aRate == 0 || aChannels == 0) { + mCallback->Error(MediaResult( + NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL( + "Invalid rate or num channels returned on GMP audio samples"))); + return; + } + + size_t numFrames = aPCM.Length() / aChannels; + MOZ_ASSERT((aPCM.Length() % aChannels) == 0); + AlignedAudioBuffer audioData(aPCM.Length()); + if (!audioData) { + mCallback->Error( + MediaResult(NS_ERROR_OUT_OF_MEMORY, + RESULT_DETAIL("Unable to allocate audio buffer"))); + return; + } + + for (size_t i = 0; i < aPCM.Length(); ++i) { + audioData[i] = AudioSampleToFloat(aPCM[i]); + } + + if (mMustRecaptureAudioPosition) { + mAudioFrameSum = 0; + auto timestamp = UsecsToFrames(aTimeStamp, aRate); + if (!timestamp.isValid()) { + mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR, + RESULT_DETAIL("Invalid timestamp"))); + return; + } + mAudioFrameOffset = timestamp.value(); + mMustRecaptureAudioPosition = false; + } + + auto timestamp = FramesToUsecs(mAudioFrameOffset + mAudioFrameSum, aRate); + if (!timestamp.isValid()) { + mCallback->Error( + MediaResult(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR, + RESULT_DETAIL("Invalid timestamp on audio samples"))); + return; + } + mAudioFrameSum += numFrames; + + auto duration = FramesToUsecs(numFrames, aRate); + if (!duration.isValid()) { + mCallback->Error( + MediaResult(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR, + RESULT_DETAIL("Invalid duration on audio samples"))); + return; + } + + RefPtr<AudioData> audio(new AudioData(mLastStreamOffset, + timestamp.value(), + duration.value(), + numFrames, + Move(audioData), + aChannels, + aRate)); + +#ifdef LOG_SAMPLE_DECODE + LOG("Decoded audio sample! timestamp=%lld duration=%lld currentLength=%u", + timestamp, duration, currentLength); +#endif + + mCallback->Output(audio); +} + +void +AudioCallbackAdapter::InputDataExhausted() +{ + MOZ_ASSERT(IsOnGMPThread()); + mCallback->InputExhausted(); +} + +void +AudioCallbackAdapter::DrainComplete() +{ + MOZ_ASSERT(IsOnGMPThread()); + mCallback->DrainComplete(); +} + +void +AudioCallbackAdapter::ResetComplete() +{ + MOZ_ASSERT(IsOnGMPThread()); + mMustRecaptureAudioPosition = true; + mCallback->FlushComplete(); +} + +void +AudioCallbackAdapter::Error(GMPErr aErr) +{ + MOZ_ASSERT(IsOnGMPThread()); + mCallback->Error(MediaResult(aErr == GMPDecodeErr + ? NS_ERROR_DOM_MEDIA_DECODE_ERR + : NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL("GMPErr:%x", aErr))); +} + +void +AudioCallbackAdapter::Terminated() +{ + mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL("Audio GMP decoder terminated."))); +} + +GMPAudioDecoderParams::GMPAudioDecoderParams(const CreateDecoderParams& aParams) + : mConfig(aParams.AudioConfig()) + , mTaskQueue(aParams.mTaskQueue) + , mCallback(nullptr) + , mAdapter(nullptr) + , mCrashHelper(aParams.mCrashHelper) +{} + +GMPAudioDecoderParams& +GMPAudioDecoderParams::WithCallback(MediaDataDecoderProxy* aWrapper) +{ + MOZ_ASSERT(aWrapper); + MOZ_ASSERT(!mCallback); // Should only be called once per instance. + mCallback = aWrapper->Callback(); + mAdapter = nullptr; + return *this; +} + +GMPAudioDecoderParams& +GMPAudioDecoderParams::WithAdapter(AudioCallbackAdapter* aAdapter) +{ + MOZ_ASSERT(aAdapter); + MOZ_ASSERT(!mAdapter); // Should only be called once per instance. + mCallback = aAdapter->Callback(); + mAdapter = aAdapter; + return *this; +} + +GMPAudioDecoder::GMPAudioDecoder(const GMPAudioDecoderParams& aParams) + : mConfig(aParams.mConfig) + , mCallback(aParams.mCallback) + , mGMP(nullptr) + , mAdapter(aParams.mAdapter) + , mCrashHelper(aParams.mCrashHelper) +{ + MOZ_ASSERT(!mAdapter || mCallback == mAdapter->Callback()); + if (!mAdapter) { + mAdapter = new AudioCallbackAdapter(mCallback); + } +} + +void +GMPAudioDecoder::InitTags(nsTArray<nsCString>& aTags) +{ + aTags.AppendElement(NS_LITERAL_CSTRING("aac")); + const Maybe<nsCString> gmp( + GMPDecoderModule::PreferredGMP(NS_LITERAL_CSTRING("audio/mp4a-latm"))); + if (gmp.isSome()) { + aTags.AppendElement(gmp.value()); + } +} + +nsCString +GMPAudioDecoder::GetNodeId() +{ + return SHARED_GMP_DECODING_NODE_ID; +} + +void +GMPAudioDecoder::GMPInitDone(GMPAudioDecoderProxy* aGMP) +{ + MOZ_ASSERT(IsOnGMPThread()); + + if (!aGMP) { + mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); + return; + } + if (mInitPromise.IsEmpty()) { + // GMP must have been shutdown while we were waiting for Init operation + // to complete. + aGMP->Close(); + return; + } + nsTArray<uint8_t> codecSpecific; + codecSpecific.AppendElements(mConfig.mCodecSpecificConfig->Elements(), + mConfig.mCodecSpecificConfig->Length()); + + nsresult rv = aGMP->InitDecode(kGMPAudioCodecAAC, + mConfig.mChannels, + mConfig.mBitDepth, + mConfig.mRate, + codecSpecific, + mAdapter); + if (NS_FAILED(rv)) { + aGMP->Close(); + mInitPromise.Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); + return; + } + + mGMP = aGMP; + mInitPromise.Resolve(TrackInfo::kAudioTrack, __func__); +} + +RefPtr<MediaDataDecoder::InitPromise> +GMPAudioDecoder::Init() +{ + MOZ_ASSERT(IsOnGMPThread()); + + mMPS = do_GetService("@mozilla.org/gecko-media-plugin-service;1"); + MOZ_ASSERT(mMPS); + + RefPtr<InitPromise> promise(mInitPromise.Ensure(__func__)); + + nsTArray<nsCString> tags; + InitTags(tags); + UniquePtr<GetGMPAudioDecoderCallback> callback(new GMPInitDoneCallback(this)); + if (NS_FAILED(mMPS->GetGMPAudioDecoder(mCrashHelper, &tags, GetNodeId(), Move(callback)))) { + mInitPromise.Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); + } + + return promise; +} + +void +GMPAudioDecoder::Input(MediaRawData* aSample) +{ + MOZ_ASSERT(IsOnGMPThread()); + + RefPtr<MediaRawData> sample(aSample); + if (!mGMP) { + mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL("mGMP not initialized"))); + return; + } + + mAdapter->SetLastStreamOffset(sample->mOffset); + + gmp::GMPAudioSamplesImpl samples(sample, mConfig.mChannels, mConfig.mRate); + nsresult rv = mGMP->Decode(samples); + if (NS_FAILED(rv)) { + mCallback->Error(MediaResult(rv, __func__)); + } +} + +void +GMPAudioDecoder::Flush() +{ + MOZ_ASSERT(IsOnGMPThread()); + + if (!mGMP || NS_FAILED(mGMP->Reset())) { + // Abort the flush. + mCallback->FlushComplete(); + } +} + +void +GMPAudioDecoder::Drain() +{ + MOZ_ASSERT(IsOnGMPThread()); + + if (!mGMP || NS_FAILED(mGMP->Drain())) { + mCallback->DrainComplete(); + } +} + +void +GMPAudioDecoder::Shutdown() +{ + mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__); + if (!mGMP) { + return; + } + // Note this unblocks flush and drain operations waiting for callbacks. + mGMP->Close(); + mGMP = nullptr; +} + +} // namespace mozilla diff --git a/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.h b/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.h new file mode 100644 index 000000000..90e3ebdb6 --- /dev/null +++ b/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.h @@ -0,0 +1,112 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if !defined(GMPAudioDecoder_h_) +#define GMPAudioDecoder_h_ + +#include "GMPAudioDecoderProxy.h" +#include "MediaDataDecoderProxy.h" +#include "PlatformDecoderModule.h" +#include "mozIGeckoMediaPluginService.h" +#include "nsAutoPtr.h" + +namespace mozilla { + +class AudioCallbackAdapter : public GMPAudioDecoderCallbackProxy { +public: + explicit AudioCallbackAdapter(MediaDataDecoderCallbackProxy* aCallback) + : mCallback(aCallback) + , mLastStreamOffset(0) + , mAudioFrameSum(0) + , mAudioFrameOffset(0) + , mMustRecaptureAudioPosition(true) + {} + + MediaDataDecoderCallbackProxy* Callback() const { return mCallback; } + + // GMPAudioDecoderCallbackProxy + void Decoded(const nsTArray<int16_t>& aPCM, uint64_t aTimeStamp, uint32_t aChannels, uint32_t aRate) override; + void InputDataExhausted() override; + void DrainComplete() override; + void ResetComplete() override; + void Error(GMPErr aErr) override; + void Terminated() override; + + void SetLastStreamOffset(int64_t aStreamOffset) { + mLastStreamOffset = aStreamOffset; + } + +private: + MediaDataDecoderCallbackProxy* mCallback; + int64_t mLastStreamOffset; + + int64_t mAudioFrameSum; + int64_t mAudioFrameOffset; + bool mMustRecaptureAudioPosition; +}; + +struct GMPAudioDecoderParams { + explicit GMPAudioDecoderParams(const CreateDecoderParams& aParams); + GMPAudioDecoderParams& WithCallback(MediaDataDecoderProxy* aWrapper); + GMPAudioDecoderParams& WithAdapter(AudioCallbackAdapter* aAdapter); + + const AudioInfo& mConfig; + TaskQueue* mTaskQueue; + MediaDataDecoderCallbackProxy* mCallback; + AudioCallbackAdapter* mAdapter; + RefPtr<GMPCrashHelper> mCrashHelper; +}; + +class GMPAudioDecoder : public MediaDataDecoder { +public: + explicit GMPAudioDecoder(const GMPAudioDecoderParams& aParams); + + RefPtr<InitPromise> Init() override; + void Input(MediaRawData* aSample) override; + void Flush() override; + void Drain() override; + void Shutdown() override; + const char* GetDescriptionName() const override + { + return "GMP audio decoder"; + } + +protected: + virtual void InitTags(nsTArray<nsCString>& aTags); + virtual nsCString GetNodeId(); + +private: + + class GMPInitDoneCallback : public GetGMPAudioDecoderCallback + { + public: + explicit GMPInitDoneCallback(GMPAudioDecoder* aDecoder) + : mDecoder(aDecoder) + { + } + + void Done(GMPAudioDecoderProxy* aGMP) override + { + mDecoder->GMPInitDone(aGMP); + } + + private: + RefPtr<GMPAudioDecoder> mDecoder; + }; + void GMPInitDone(GMPAudioDecoderProxy* aGMP); + + const AudioInfo mConfig; + MediaDataDecoderCallbackProxy* mCallback; + nsCOMPtr<mozIGeckoMediaPluginService> mMPS; + GMPAudioDecoderProxy* mGMP; + nsAutoPtr<AudioCallbackAdapter> mAdapter; + MozPromiseHolder<InitPromise> mInitPromise; + RefPtr<GMPCrashHelper> mCrashHelper; +}; + +} // namespace mozilla + +#endif // GMPAudioDecoder_h_ diff --git a/dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp b/dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp new file mode 100644 index 000000000..cc53d2c93 --- /dev/null +++ b/dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp @@ -0,0 +1,172 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GMPDecoderModule.h" +#include "DecoderDoctorDiagnostics.h" +#include "GMPAudioDecoder.h" +#include "GMPVideoDecoder.h" +#include "GMPUtils.h" +#include "MediaDataDecoderProxy.h" +#include "MediaPrefs.h" +#include "VideoUtils.h" +#include "mozIGeckoMediaPluginService.h" +#include "nsServiceManagerUtils.h" +#include "mozilla/StaticMutex.h" +#include "gmp-audio-decode.h" +#include "gmp-video-decode.h" +#include "MP4Decoder.h" +#include "VPXDecoder.h" +#ifdef XP_WIN +#include "WMFDecoderModule.h" +#endif + +namespace mozilla { + +GMPDecoderModule::GMPDecoderModule() +{ +} + +GMPDecoderModule::~GMPDecoderModule() +{ +} + +static already_AddRefed<MediaDataDecoderProxy> +CreateDecoderWrapper(MediaDataDecoderCallback* aCallback) +{ + RefPtr<gmp::GeckoMediaPluginService> s(gmp::GeckoMediaPluginService::GetGeckoMediaPluginService()); + if (!s) { + return nullptr; + } + RefPtr<AbstractThread> thread(s->GetAbstractGMPThread()); + if (!thread) { + return nullptr; + } + RefPtr<MediaDataDecoderProxy> decoder(new MediaDataDecoderProxy(thread.forget(), aCallback)); + return decoder.forget(); +} + +already_AddRefed<MediaDataDecoder> +GMPDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams) +{ + if (!MP4Decoder::IsH264(aParams.mConfig.mMimeType) && + !VPXDecoder::IsVP8(aParams.mConfig.mMimeType) && + !VPXDecoder::IsVP9(aParams.mConfig.mMimeType)) { + return nullptr; + } + + if (aParams.mDiagnostics) { + const Maybe<nsCString> preferredGMP = PreferredGMP(aParams.mConfig.mMimeType); + if (preferredGMP.isSome()) { + aParams.mDiagnostics->SetGMP(preferredGMP.value()); + } + } + + RefPtr<MediaDataDecoderProxy> wrapper = CreateDecoderWrapper(aParams.mCallback); + auto params = GMPVideoDecoderParams(aParams).WithCallback(wrapper); + wrapper->SetProxyTarget(new GMPVideoDecoder(params)); + return wrapper.forget(); +} + +already_AddRefed<MediaDataDecoder> +GMPDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams) +{ + if (!aParams.mConfig.mMimeType.EqualsLiteral("audio/mp4a-latm")) { + return nullptr; + } + + if (aParams.mDiagnostics) { + const Maybe<nsCString> preferredGMP = PreferredGMP(aParams.mConfig.mMimeType); + if (preferredGMP.isSome()) { + aParams.mDiagnostics->SetGMP(preferredGMP.value()); + } + } + + RefPtr<MediaDataDecoderProxy> wrapper = CreateDecoderWrapper(aParams.mCallback); + auto params = GMPAudioDecoderParams(aParams).WithCallback(wrapper); + wrapper->SetProxyTarget(new GMPAudioDecoder(params)); + return wrapper.forget(); +} + +PlatformDecoderModule::ConversionRequired +GMPDecoderModule::DecoderNeedsConversion(const TrackInfo& aConfig) const +{ + // GMPVideoCodecType::kGMPVideoCodecH264 specifies that encoded frames must be in AVCC format. + if (aConfig.IsVideo() && MP4Decoder::IsH264(aConfig.mMimeType)) { + return ConversionRequired::kNeedAVCC; + } else { + return ConversionRequired::kNeedNone; + } +} + +/* static */ +const Maybe<nsCString> +GMPDecoderModule::PreferredGMP(const nsACString& aMimeType) +{ + Maybe<nsCString> rv; + if (aMimeType.EqualsLiteral("audio/mp4a-latm")) { + switch (MediaPrefs::GMPAACPreferred()) { + case 1: rv.emplace(kEMEKeySystemClearkey); break; + case 2: rv.emplace(kEMEKeySystemPrimetime); break; + default: break; + } + } + + if (MP4Decoder::IsH264(aMimeType)) { + switch (MediaPrefs::GMPH264Preferred()) { + case 1: rv.emplace(kEMEKeySystemClearkey); break; + case 2: rv.emplace(kEMEKeySystemPrimetime); break; + default: break; + } + } + + return rv; +} + +/* static */ +bool +GMPDecoderModule::SupportsMimeType(const nsACString& aMimeType, + const Maybe<nsCString>& aGMP) +{ + if (aGMP.isNothing()) { + return false; + } + + if (MP4Decoder::IsH264(aMimeType)) { + return HaveGMPFor(NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER), + { NS_LITERAL_CSTRING("h264"), aGMP.value()}); + } + + if (VPXDecoder::IsVP9(aMimeType)) { + return HaveGMPFor(NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER), + { NS_LITERAL_CSTRING("vp9"), aGMP.value()}); + } + + if (VPXDecoder::IsVP8(aMimeType)) { + return HaveGMPFor(NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER), + { NS_LITERAL_CSTRING("vp8"), aGMP.value()}); + } + + if (MP4Decoder::IsAAC(aMimeType)) { + return HaveGMPFor(NS_LITERAL_CSTRING(GMP_API_AUDIO_DECODER), + { NS_LITERAL_CSTRING("aac"), aGMP.value()}); + } + + return false; +} + +bool +GMPDecoderModule::SupportsMimeType(const nsACString& aMimeType, + DecoderDoctorDiagnostics* aDiagnostics) const +{ + const Maybe<nsCString> preferredGMP = PreferredGMP(aMimeType); + bool rv = SupportsMimeType(aMimeType, preferredGMP); + if (rv && aDiagnostics && preferredGMP.isSome()) { + aDiagnostics->SetGMP(preferredGMP.value()); + } + return rv; +} + +} // namespace mozilla diff --git a/dom/media/platforms/agnostic/gmp/GMPDecoderModule.h b/dom/media/platforms/agnostic/gmp/GMPDecoderModule.h new file mode 100644 index 000000000..b501ecb54 --- /dev/null +++ b/dom/media/platforms/agnostic/gmp/GMPDecoderModule.h @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if !defined(GMPDecoderModule_h_) +#define GMPDecoderModule_h_ + +#include "PlatformDecoderModule.h" +#include "mozilla/Maybe.h" + +// The special NodeId we use when doing unencrypted decoding using the GMP's +// decoder. This ensures that each GMP MediaDataDecoder we create doesn't +// require spinning up a new process, but instead we run all instances of +// GMP decoders in the one process, to reduce overhead. +// +// Note: GMP storage is isolated by NodeId, and non persistent for this +// special NodeId, and the only way a GMP can communicate with the outside +// world is through the EME GMP APIs, and we never run EME with this NodeID +// (because NodeIds are random strings which can't contain the '-' character), +// so there's no way a malicious GMP can harvest, store, and then report any +// privacy sensitive data about what users are watching. +#define SHARED_GMP_DECODING_NODE_ID NS_LITERAL_CSTRING("gmp-shared-decoding") + +namespace mozilla { + +class GMPDecoderModule : public PlatformDecoderModule { +public: + GMPDecoderModule(); + + virtual ~GMPDecoderModule(); + + // Decode thread. + already_AddRefed<MediaDataDecoder> + CreateVideoDecoder(const CreateDecoderParams& aParams) override; + + // Decode thread. + already_AddRefed<MediaDataDecoder> + CreateAudioDecoder(const CreateDecoderParams& aParams) override; + + ConversionRequired + DecoderNeedsConversion(const TrackInfo& aConfig) const override; + + bool + SupportsMimeType(const nsACString& aMimeType, + DecoderDoctorDiagnostics* aDiagnostics) const override; + + static const Maybe<nsCString> PreferredGMP(const nsACString& aMimeType); + + static bool SupportsMimeType(const nsACString& aMimeType, + const Maybe<nsCString>& aGMP); +}; + +} // namespace mozilla + +#endif // GMPDecoderModule_h_ diff --git a/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp new file mode 100644 index 000000000..912b88ce1 --- /dev/null +++ b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp @@ -0,0 +1,387 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GMPVideoDecoder.h" +#include "GMPVideoHost.h" +#include "mozilla/EndianUtils.h" +#include "prsystem.h" +#include "MediaData.h" +#include "GMPDecoderModule.h" +#include "VPXDecoder.h" + +namespace mozilla { + +#if defined(DEBUG) +extern bool IsOnGMPThread(); +#endif + +void +VideoCallbackAdapter::Decoded(GMPVideoi420Frame* aDecodedFrame) +{ + GMPUniquePtr<GMPVideoi420Frame> decodedFrame(aDecodedFrame); + + MOZ_ASSERT(IsOnGMPThread()); + + VideoData::YCbCrBuffer b; + for (int i = 0; i < kGMPNumOfPlanes; ++i) { + b.mPlanes[i].mData = decodedFrame->Buffer(GMPPlaneType(i)); + b.mPlanes[i].mStride = decodedFrame->Stride(GMPPlaneType(i)); + if (i == kGMPYPlane) { + b.mPlanes[i].mWidth = decodedFrame->Width(); + b.mPlanes[i].mHeight = decodedFrame->Height(); + } else { + b.mPlanes[i].mWidth = (decodedFrame->Width() + 1) / 2; + b.mPlanes[i].mHeight = (decodedFrame->Height() + 1) / 2; + } + b.mPlanes[i].mOffset = 0; + b.mPlanes[i].mSkip = 0; + } + + gfx::IntRect pictureRegion(0, 0, decodedFrame->Width(), decodedFrame->Height()); + RefPtr<VideoData> v = + VideoData::CreateAndCopyData(mVideoInfo, + mImageContainer, + mLastStreamOffset, + decodedFrame->Timestamp(), + decodedFrame->Duration(), + b, + false, + -1, + pictureRegion); + if (v) { + mCallback->Output(v); + } else { + mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__)); + } +} + +void +VideoCallbackAdapter::ReceivedDecodedReferenceFrame(const uint64_t aPictureId) +{ + MOZ_ASSERT(IsOnGMPThread()); +} + +void +VideoCallbackAdapter::ReceivedDecodedFrame(const uint64_t aPictureId) +{ + MOZ_ASSERT(IsOnGMPThread()); +} + +void +VideoCallbackAdapter::InputDataExhausted() +{ + MOZ_ASSERT(IsOnGMPThread()); + mCallback->InputExhausted(); +} + +void +VideoCallbackAdapter::DrainComplete() +{ + MOZ_ASSERT(IsOnGMPThread()); + mCallback->DrainComplete(); +} + +void +VideoCallbackAdapter::ResetComplete() +{ + MOZ_ASSERT(IsOnGMPThread()); + mCallback->FlushComplete(); +} + +void +VideoCallbackAdapter::Error(GMPErr aErr) +{ + MOZ_ASSERT(IsOnGMPThread()); + mCallback->Error(MediaResult(aErr == GMPDecodeErr + ? NS_ERROR_DOM_MEDIA_DECODE_ERR + : NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL("GMPErr:%x", aErr))); +} + +void +VideoCallbackAdapter::Terminated() +{ + // Note that this *may* be called from the proxy thread also. + mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL("Video GMP decoder terminated."))); +} + +GMPVideoDecoderParams::GMPVideoDecoderParams(const CreateDecoderParams& aParams) + : mConfig(aParams.VideoConfig()) + , mTaskQueue(aParams.mTaskQueue) + , mCallback(nullptr) + , mAdapter(nullptr) + , mImageContainer(aParams.mImageContainer) + , mLayersBackend(aParams.GetLayersBackend()) + , mCrashHelper(aParams.mCrashHelper) +{} + +GMPVideoDecoderParams& +GMPVideoDecoderParams::WithCallback(MediaDataDecoderProxy* aWrapper) +{ + MOZ_ASSERT(aWrapper); + MOZ_ASSERT(!mCallback); // Should only be called once per instance. + mCallback = aWrapper->Callback(); + mAdapter = nullptr; + return *this; +} + +GMPVideoDecoderParams& +GMPVideoDecoderParams::WithAdapter(VideoCallbackAdapter* aAdapter) +{ + MOZ_ASSERT(aAdapter); + MOZ_ASSERT(!mAdapter); // Should only be called once per instance. + mCallback = aAdapter->Callback(); + mAdapter = aAdapter; + return *this; +} + +GMPVideoDecoder::GMPVideoDecoder(const GMPVideoDecoderParams& aParams) + : mConfig(aParams.mConfig) + , mCallback(aParams.mCallback) + , mGMP(nullptr) + , mHost(nullptr) + , mAdapter(aParams.mAdapter) + , mConvertNALUnitLengths(false) + , mCrashHelper(aParams.mCrashHelper) +{ + MOZ_ASSERT(!mAdapter || mCallback == mAdapter->Callback()); + if (!mAdapter) { + mAdapter = new VideoCallbackAdapter(mCallback, + VideoInfo(mConfig.mDisplay.width, + mConfig.mDisplay.height), + aParams.mImageContainer); + } +} + +void +GMPVideoDecoder::InitTags(nsTArray<nsCString>& aTags) +{ + if (MP4Decoder::IsH264(mConfig.mMimeType)) { + aTags.AppendElement(NS_LITERAL_CSTRING("h264")); + const Maybe<nsCString> gmp( + GMPDecoderModule::PreferredGMP(NS_LITERAL_CSTRING("video/avc"))); + if (gmp.isSome()) { + aTags.AppendElement(gmp.value()); + } + } else if (VPXDecoder::IsVP8(mConfig.mMimeType)) { + aTags.AppendElement(NS_LITERAL_CSTRING("vp8")); + } else if (VPXDecoder::IsVP9(mConfig.mMimeType)) { + aTags.AppendElement(NS_LITERAL_CSTRING("vp9")); + } +} + +nsCString +GMPVideoDecoder::GetNodeId() +{ + return SHARED_GMP_DECODING_NODE_ID; +} + +GMPUniquePtr<GMPVideoEncodedFrame> +GMPVideoDecoder::CreateFrame(MediaRawData* aSample) +{ + GMPVideoFrame* ftmp = nullptr; + GMPErr err = mHost->CreateFrame(kGMPEncodedVideoFrame, &ftmp); + if (GMP_FAILED(err)) { + mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, + RESULT_DETAIL("Host::CreateFrame:%x", err))); + return nullptr; + } + + GMPUniquePtr<GMPVideoEncodedFrame> frame(static_cast<GMPVideoEncodedFrame*>(ftmp)); + err = frame->CreateEmptyFrame(aSample->Size()); + if (GMP_FAILED(err)) { + mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, + RESULT_DETAIL("GMPVideoEncodedFrame::CreateEmptyFrame:%x", err))); + return nullptr; + } + + memcpy(frame->Buffer(), aSample->Data(), frame->Size()); + + // Convert 4-byte NAL unit lengths to host-endian 4-byte buffer lengths to + // suit the GMP API. + if (mConvertNALUnitLengths) { + const int kNALLengthSize = 4; + uint8_t* buf = frame->Buffer(); + while (buf < frame->Buffer() + frame->Size() - kNALLengthSize) { + uint32_t length = BigEndian::readUint32(buf) + kNALLengthSize; + *reinterpret_cast<uint32_t *>(buf) = length; + buf += length; + } + } + + frame->SetBufferType(GMP_BufferLength32); + + frame->SetEncodedWidth(mConfig.mDisplay.width); + frame->SetEncodedHeight(mConfig.mDisplay.height); + frame->SetTimeStamp(aSample->mTime); + frame->SetCompleteFrame(true); + frame->SetDuration(aSample->mDuration); + frame->SetFrameType(aSample->mKeyframe ? kGMPKeyFrame : kGMPDeltaFrame); + + return frame; +} + +const VideoInfo& +GMPVideoDecoder::GetConfig() const +{ + return mConfig; +} + +void +GMPVideoDecoder::GMPInitDone(GMPVideoDecoderProxy* aGMP, GMPVideoHost* aHost) +{ + MOZ_ASSERT(IsOnGMPThread()); + + if (!aGMP) { + mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); + return; + } + MOZ_ASSERT(aHost); + + if (mInitPromise.IsEmpty()) { + // GMP must have been shutdown while we were waiting for Init operation + // to complete. + aGMP->Close(); + return; + } + + GMPVideoCodec codec; + memset(&codec, 0, sizeof(codec)); + + codec.mGMPApiVersion = kGMPVersion33; + nsTArray<uint8_t> codecSpecific; + if (MP4Decoder::IsH264(mConfig.mMimeType)) { + codec.mCodecType = kGMPVideoCodecH264; + codecSpecific.AppendElement(0); // mPacketizationMode. + codecSpecific.AppendElements(mConfig.mExtraData->Elements(), + mConfig.mExtraData->Length()); + } else if (VPXDecoder::IsVP8(mConfig.mMimeType)) { + codec.mCodecType = kGMPVideoCodecVP8; + } else if (VPXDecoder::IsVP9(mConfig.mMimeType)) { + codec.mCodecType = kGMPVideoCodecVP9; + } else { + // Unrecognized mime type + aGMP->Close(); + mInitPromise.Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); + return; + } + codec.mWidth = mConfig.mImage.width; + codec.mHeight = mConfig.mImage.height; + + nsresult rv = aGMP->InitDecode(codec, + codecSpecific, + mAdapter, + PR_GetNumberOfProcessors()); + if (NS_FAILED(rv)) { + aGMP->Close(); + mInitPromise.Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); + return; + } + + mGMP = aGMP; + mHost = aHost; + + // GMP implementations have interpreted the meaning of GMP_BufferLength32 + // differently. The OpenH264 GMP expects GMP_BufferLength32 to behave as + // specified in the GMP API, where each buffer is prefixed by a 32-bit + // host-endian buffer length that includes the size of the buffer length + // field. Other existing GMPs currently expect GMP_BufferLength32 (when + // combined with kGMPVideoCodecH264) to mean "like AVCC but restricted to + // 4-byte NAL lengths" (i.e. buffer lengths are specified in big-endian + // and do not include the length of the buffer length field. + mConvertNALUnitLengths = mGMP->GetDisplayName().EqualsLiteral("gmpopenh264"); + + mInitPromise.Resolve(TrackInfo::kVideoTrack, __func__); +} + +RefPtr<MediaDataDecoder::InitPromise> +GMPVideoDecoder::Init() +{ + MOZ_ASSERT(IsOnGMPThread()); + + mMPS = do_GetService("@mozilla.org/gecko-media-plugin-service;1"); + MOZ_ASSERT(mMPS); + + RefPtr<InitPromise> promise(mInitPromise.Ensure(__func__)); + + nsTArray<nsCString> tags; + InitTags(tags); + UniquePtr<GetGMPVideoDecoderCallback> callback(new GMPInitDoneCallback(this)); + if (NS_FAILED(mMPS->GetDecryptingGMPVideoDecoder(mCrashHelper, + &tags, + GetNodeId(), + Move(callback), + DecryptorId()))) { + mInitPromise.Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); + } + + return promise; +} + +void +GMPVideoDecoder::Input(MediaRawData* aSample) +{ + MOZ_ASSERT(IsOnGMPThread()); + + RefPtr<MediaRawData> sample(aSample); + if (!mGMP) { + mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL("mGMP not initialized"))); + return; + } + + mAdapter->SetLastStreamOffset(sample->mOffset); + + GMPUniquePtr<GMPVideoEncodedFrame> frame = CreateFrame(sample); + if (!frame) { + mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, + RESULT_DETAIL("CreateFrame returned null"))); + return; + } + nsTArray<uint8_t> info; // No codec specific per-frame info to pass. + nsresult rv = mGMP->Decode(Move(frame), false, info, 0); + if (NS_FAILED(rv)) { + mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, + RESULT_DETAIL("mGMP->Decode:%x", rv))); + } +} + +void +GMPVideoDecoder::Flush() +{ + MOZ_ASSERT(IsOnGMPThread()); + + if (!mGMP || NS_FAILED(mGMP->Reset())) { + // Abort the flush. + mCallback->FlushComplete(); + } +} + +void +GMPVideoDecoder::Drain() +{ + MOZ_ASSERT(IsOnGMPThread()); + + if (!mGMP || NS_FAILED(mGMP->Drain())) { + mCallback->DrainComplete(); + } +} + +void +GMPVideoDecoder::Shutdown() +{ + mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__); + // Note that this *may* be called from the proxy thread also. + if (!mGMP) { + return; + } + // Note this unblocks flush and drain operations waiting for callbacks. + mGMP->Close(); + mGMP = nullptr; +} + +} // namespace mozilla diff --git a/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.h b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.h new file mode 100644 index 000000000..900ef4553 --- /dev/null +++ b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.h @@ -0,0 +1,122 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if !defined(GMPVideoDecoder_h_) +#define GMPVideoDecoder_h_ + +#include "GMPVideoDecoderProxy.h" +#include "ImageContainer.h" +#include "MediaDataDecoderProxy.h" +#include "PlatformDecoderModule.h" +#include "mozIGeckoMediaPluginService.h" +#include "MediaInfo.h" + +namespace mozilla { + +class VideoCallbackAdapter : public GMPVideoDecoderCallbackProxy { +public: + VideoCallbackAdapter(MediaDataDecoderCallbackProxy* aCallback, + VideoInfo aVideoInfo, + layers::ImageContainer* aImageContainer) + : mCallback(aCallback) + , mLastStreamOffset(0) + , mVideoInfo(aVideoInfo) + , mImageContainer(aImageContainer) + {} + + MediaDataDecoderCallbackProxy* Callback() const { return mCallback; } + + // GMPVideoDecoderCallbackProxy + void Decoded(GMPVideoi420Frame* aDecodedFrame) override; + void ReceivedDecodedReferenceFrame(const uint64_t aPictureId) override; + void ReceivedDecodedFrame(const uint64_t aPictureId) override; + void InputDataExhausted() override; + void DrainComplete() override; + void ResetComplete() override; + void Error(GMPErr aErr) override; + void Terminated() override; + + void SetLastStreamOffset(int64_t aStreamOffset) { + mLastStreamOffset = aStreamOffset; + } + +private: + MediaDataDecoderCallbackProxy* mCallback; + int64_t mLastStreamOffset; + + VideoInfo mVideoInfo; + RefPtr<layers::ImageContainer> mImageContainer; +}; + +struct GMPVideoDecoderParams { + explicit GMPVideoDecoderParams(const CreateDecoderParams& aParams); + GMPVideoDecoderParams& WithCallback(MediaDataDecoderProxy* aWrapper); + GMPVideoDecoderParams& WithAdapter(VideoCallbackAdapter* aAdapter); + + const VideoInfo& mConfig; + TaskQueue* mTaskQueue; + MediaDataDecoderCallbackProxy* mCallback; + VideoCallbackAdapter* mAdapter; + layers::ImageContainer* mImageContainer; + layers::LayersBackend mLayersBackend; + RefPtr<GMPCrashHelper> mCrashHelper; +}; + +class GMPVideoDecoder : public MediaDataDecoder { +public: + explicit GMPVideoDecoder(const GMPVideoDecoderParams& aParams); + + RefPtr<InitPromise> Init() override; + void Input(MediaRawData* aSample) override; + void Flush() override; + void Drain() override; + void Shutdown() override; + const char* GetDescriptionName() const override + { + return "GMP video decoder"; + } + +protected: + virtual void InitTags(nsTArray<nsCString>& aTags); + virtual nsCString GetNodeId(); + virtual uint32_t DecryptorId() const { return 0; } + virtual GMPUniquePtr<GMPVideoEncodedFrame> CreateFrame(MediaRawData* aSample); + virtual const VideoInfo& GetConfig() const; + +private: + + class GMPInitDoneCallback : public GetGMPVideoDecoderCallback + { + public: + explicit GMPInitDoneCallback(GMPVideoDecoder* aDecoder) + : mDecoder(aDecoder) + { + } + + void Done(GMPVideoDecoderProxy* aGMP, GMPVideoHost* aHost) override + { + mDecoder->GMPInitDone(aGMP, aHost); + } + + private: + RefPtr<GMPVideoDecoder> mDecoder; + }; + void GMPInitDone(GMPVideoDecoderProxy* aGMP, GMPVideoHost* aHost); + + const VideoInfo mConfig; + MediaDataDecoderCallbackProxy* mCallback; + nsCOMPtr<mozIGeckoMediaPluginService> mMPS; + GMPVideoDecoderProxy* mGMP; + GMPVideoHost* mHost; + nsAutoPtr<VideoCallbackAdapter> mAdapter; + bool mConvertNALUnitLengths; + MozPromiseHolder<InitPromise> mInitPromise; + RefPtr<GMPCrashHelper> mCrashHelper; +}; + +} // namespace mozilla + +#endif // GMPVideoDecoder_h_ diff --git a/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.cpp b/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.cpp new file mode 100644 index 000000000..5a196f8e6 --- /dev/null +++ b/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.cpp @@ -0,0 +1,90 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MediaDataDecoderProxy.h" +#include "MediaData.h" + +namespace mozilla { + +void +MediaDataDecoderCallbackProxy::Error(const MediaResult& aError) +{ + mProxyCallback->Error(aError); +} + +void +MediaDataDecoderCallbackProxy::FlushComplete() +{ + mProxyDecoder->FlushComplete(); +} + +RefPtr<MediaDataDecoder::InitPromise> +MediaDataDecoderProxy::InternalInit() +{ + return mProxyDecoder->Init(); +} + +RefPtr<MediaDataDecoder::InitPromise> +MediaDataDecoderProxy::Init() +{ + MOZ_ASSERT(!mIsShutdown); + + return InvokeAsync(mProxyThread, this, __func__, + &MediaDataDecoderProxy::InternalInit); +} + +void +MediaDataDecoderProxy::Input(MediaRawData* aSample) +{ + MOZ_ASSERT(!IsOnProxyThread()); + MOZ_ASSERT(!mIsShutdown); + + nsCOMPtr<nsIRunnable> task(new InputTask(mProxyDecoder, aSample)); + mProxyThread->Dispatch(task.forget()); +} + +void +MediaDataDecoderProxy::Flush() +{ + MOZ_ASSERT(!IsOnProxyThread()); + MOZ_ASSERT(!mIsShutdown); + + mFlushComplete.Set(false); + + mProxyThread->Dispatch(NewRunnableMethod(mProxyDecoder, &MediaDataDecoder::Flush)); + + mFlushComplete.WaitUntil(true); +} + +void +MediaDataDecoderProxy::Drain() +{ + MOZ_ASSERT(!IsOnProxyThread()); + MOZ_ASSERT(!mIsShutdown); + + mProxyThread->Dispatch(NewRunnableMethod(mProxyDecoder, &MediaDataDecoder::Drain)); +} + +void +MediaDataDecoderProxy::Shutdown() +{ + // Note that this *may* be called from the proxy thread also. + MOZ_ASSERT(!mIsShutdown); +#if defined(DEBUG) + mIsShutdown = true; +#endif + mProxyThread->AsXPCOMThread()->Dispatch(NewRunnableMethod(mProxyDecoder, + &MediaDataDecoder::Shutdown), + NS_DISPATCH_SYNC); +} + +void +MediaDataDecoderProxy::FlushComplete() +{ + mFlushComplete.Set(true); +} + +} // namespace mozilla diff --git a/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.h b/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.h new file mode 100644 index 000000000..735b6126e --- /dev/null +++ b/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.h @@ -0,0 +1,181 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#if !defined(MediaDataDecoderProxy_h_) +#define MediaDataDecoderProxy_h_ + +#include "PlatformDecoderModule.h" +#include "mozilla/RefPtr.h" +#include "nsThreadUtils.h" +#include "nscore.h" +#include "GMPService.h" + +namespace mozilla { + +class InputTask : public Runnable { +public: + InputTask(MediaDataDecoder* aDecoder, + MediaRawData* aSample) + : mDecoder(aDecoder) + , mSample(aSample) + {} + + NS_IMETHOD Run() override { + mDecoder->Input(mSample); + return NS_OK; + } + +private: + RefPtr<MediaDataDecoder> mDecoder; + RefPtr<MediaRawData> mSample; +}; + +template<typename T> +class Condition { +public: + explicit Condition(T aValue) + : mMonitor("Condition") + , mCondition(aValue) + {} + + void Set(T aValue) { + MonitorAutoLock mon(mMonitor); + mCondition = aValue; + mon.NotifyAll(); + } + + void WaitUntil(T aValue) { + MonitorAutoLock mon(mMonitor); + while (mCondition != aValue) { + mon.Wait(); + } + } + +private: + Monitor mMonitor; + T mCondition; +}; + +class MediaDataDecoderProxy; + +class MediaDataDecoderCallbackProxy : public MediaDataDecoderCallback { +public: + MediaDataDecoderCallbackProxy(MediaDataDecoderProxy* aProxyDecoder, + MediaDataDecoderCallback* aCallback) + : mProxyDecoder(aProxyDecoder) + , mProxyCallback(aCallback) + { + } + + void Output(MediaData* aData) override { + mProxyCallback->Output(aData); + } + + void Error(const MediaResult& aError) override; + + void InputExhausted() override { + mProxyCallback->InputExhausted(); + } + + void DrainComplete() override { + mProxyCallback->DrainComplete(); + } + + void ReleaseMediaResources() override { + mProxyCallback->ReleaseMediaResources(); + } + + void FlushComplete(); + + bool OnReaderTaskQueue() override + { + return mProxyCallback->OnReaderTaskQueue(); + } + + void WaitingForKey() override + { + mProxyCallback->WaitingForKey(); + } + +private: + MediaDataDecoderProxy* mProxyDecoder; + MediaDataDecoderCallback* mProxyCallback; +}; + +class MediaDataDecoderProxy : public MediaDataDecoder { +public: + MediaDataDecoderProxy(already_AddRefed<AbstractThread> aProxyThread, + MediaDataDecoderCallback* aCallback) + : mProxyThread(aProxyThread) + , mProxyCallback(this, aCallback) + , mFlushComplete(false) +#if defined(DEBUG) + , mIsShutdown(false) +#endif + { + } + + // Ideally, this would return a regular MediaDataDecoderCallback pointer + // to retain the clean abstraction, but until MediaDataDecoderCallback + // supports the FlushComplete interface, this will have to do. When MDDC + // supports FlushComplete, this, the GMP*Decoders, and the + // *CallbackAdapters can be reverted to accepting a regular + // MediaDataDecoderCallback pointer. + MediaDataDecoderCallbackProxy* Callback() + { + return &mProxyCallback; + } + + void SetProxyTarget(MediaDataDecoder* aProxyDecoder) + { + MOZ_ASSERT(aProxyDecoder); + mProxyDecoder = aProxyDecoder; + } + + // These are called from the decoder thread pool. + // Init and Shutdown run synchronously on the proxy thread, all others are + // asynchronously and responded to via the MediaDataDecoderCallback. + // Note: the nsresults returned by the proxied decoder are lost. + RefPtr<InitPromise> Init() override; + void Input(MediaRawData* aSample) override; + void Flush() override; + void Drain() override; + void Shutdown() override; + + const char* GetDescriptionName() const override + { + return "GMP proxy data decoder"; + } + + // Called by MediaDataDecoderCallbackProxy. + void FlushComplete(); + +private: + RefPtr<InitPromise> InternalInit(); + +#ifdef DEBUG + bool IsOnProxyThread() { + return mProxyThread && mProxyThread->IsCurrentThreadIn(); + } +#endif + + friend class InputTask; + friend class InitTask; + + RefPtr<MediaDataDecoder> mProxyDecoder; + RefPtr<AbstractThread> mProxyThread; + + MediaDataDecoderCallbackProxy mProxyCallback; + + Condition<bool> mFlushComplete; +#if defined(DEBUG) + bool mIsShutdown; +#endif +}; + +} // namespace mozilla + +#endif // MediaDataDecoderProxy_h_ diff --git a/dom/media/platforms/agnostic/gmp/moz.build b/dom/media/platforms/agnostic/gmp/moz.build new file mode 100644 index 000000000..eb2738e26 --- /dev/null +++ b/dom/media/platforms/agnostic/gmp/moz.build @@ -0,0 +1,24 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +EXPORTS += [ + 'GMPAudioDecoder.h', + 'GMPDecoderModule.h', + 'GMPVideoDecoder.h', + 'MediaDataDecoderProxy.h', +] + +UNIFIED_SOURCES += [ + 'GMPAudioDecoder.cpp', + 'GMPDecoderModule.cpp', + 'GMPVideoDecoder.cpp', + 'MediaDataDecoderProxy.cpp', +] + +# GMPVideoEncodedFrameImpl.h needs IPC +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' |