diff --git a/dom/media/DecoderTraits.cpp b/dom/media/DecoderTraits.cpp index ced6479ad957..26fe5bbb6ae2 100644 --- a/dom/media/DecoderTraits.cpp +++ b/dom/media/DecoderTraits.cpp @@ -56,6 +56,10 @@ #include "DirectShowDecoder.h" #include "DirectShowReader.h" #endif +#ifdef MOZ_APPLEMEDIA +#include "AppleDecoder.h" +#include "AppleMP3Reader.h" +#endif #ifdef MOZ_FMP4 #include "MP4Decoder.h" #include "MP4Demuxer.h" @@ -365,6 +369,38 @@ IsMP3SupportedType(const nsACString& aType, return MP3Decoder::CanHandleMediaType(aType, aCodecs); } +#ifdef MOZ_APPLEMEDIA +static const char * const gAppleMP3Types[] = { + "audio/mp3", + "audio/mpeg", + nullptr, +}; + +static const char * const gAppleMP3Codecs[] = { + "mp3", + nullptr +}; + +static bool +IsAppleMediaSupportedType(const nsACString& aType, + const char * const ** aCodecs = nullptr) +{ + if (MediaDecoder::IsAppleMP3Enabled() + && CodecListContains(gAppleMP3Types, aType)) { + + if (aCodecs) { + *aCodecs = gAppleMP3Codecs; + } + + return true; + } + + // TODO MP4 + + return false; +} +#endif + /* static */ bool DecoderTraits::ShouldHandleMediaType(const char* aMIMEType) { @@ -436,6 +472,9 @@ DecoderTraits::CanHandleCodecsType(const char* aMIMEType, #ifdef MOZ_DIRECTSHOW DirectShowDecoder::GetSupportedCodecs(nsDependentCString(aMIMEType), &codecList); #endif +#ifdef MOZ_APPLEMEDIA + IsAppleMediaSupportedType(nsDependentCString(aMIMEType), &codecList); +#endif #ifdef MOZ_ANDROID_OMX if (MediaDecoder::IsAndroidMediaEnabled()) { EnsureAndroidMediaPluginHost()->FindDecoder(nsDependentCString(aMIMEType), &codecList); @@ -520,6 +559,11 @@ DecoderTraits::CanHandleMediaType(const char* aMIMEType, return CANPLAY_MAYBE; } #endif +#ifdef MOZ_APPLEMEDIA + if (IsAppleMediaSupportedType(nsDependentCString(aMIMEType), nullptr)) { + return CANPLAY_MAYBE; + } +#endif #ifdef MOZ_ANDROID_OMX if (MediaDecoder::IsAndroidMediaEnabled() && EnsureAndroidMediaPluginHost()->FindDecoder(nsDependentCString(aMIMEType), nullptr)) { @@ -632,6 +676,12 @@ InstantiateDecoder(const nsACString& aType, MediaDecoderOwner* aOwner) return decoder.forget(); } #endif +#ifdef MOZ_APPLEMEDIA + if (IsAppleMediaSupportedType(aType)) { + decoder = new AppleDecoder(); + return decoder.forget(); + } +#endif return nullptr; } @@ -709,6 +759,11 @@ MediaDecoderReader* DecoderTraits::CreateReader(const nsACString& aType, Abstrac if (IsDirectShowSupportedType(aType)) { decoderReader = new DirectShowReader(aDecoder); } else +#endif +#ifdef MOZ_APPLEMEDIA + if (IsAppleMediaSupportedType(aType)) { + decoderReader = new AppleMP3Reader(aDecoder); + } else #endif if (false) {} // dummy if to take care of the dangling else @@ -749,6 +804,9 @@ bool DecoderTraits::IsSupportedInVideoDocument(const nsACString& aType) #ifdef MOZ_DIRECTSHOW IsDirectShowSupportedType(aType) || #endif +#ifdef MOZ_APPLEMEDIA + IsAppleMediaSupportedType(aType) || +#endif #ifdef NECKO_PROTOCOL_rtsp IsRtspSupportedType(aType) || #endif diff --git a/dom/media/MediaDecoder.cpp b/dom/media/MediaDecoder.cpp index 6ce66420ffa1..b6bf368fad00 100644 --- a/dom/media/MediaDecoder.cpp +++ b/dom/media/MediaDecoder.cpp @@ -1505,6 +1505,14 @@ MediaDecoder::IsAndroidMediaEnabled() } #endif +#ifdef MOZ_APPLEMEDIA +bool +MediaDecoder::IsAppleMP3Enabled() +{ + return Preferences::GetBool("media.apple.mp3.enabled"); +} +#endif + NS_IMETHODIMP MediaMemoryTracker::CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData, bool aAnonymize) diff --git a/dom/media/MediaDecoder.h b/dom/media/MediaDecoder.h index 1a7990598447..32e110e1d3db 100644 --- a/dom/media/MediaDecoder.h +++ b/dom/media/MediaDecoder.h @@ -706,6 +706,10 @@ public: static bool IsWMFEnabled(); #endif +#ifdef MOZ_APPLEMEDIA + static bool IsAppleMP3Enabled(); +#endif + // Return statistics. This is used for progress events and other things. // This can be called from any thread. It's only a snapshot of the // current state, since other threads might be changing the state diff --git a/dom/media/apple/AppleDecoder.cpp b/dom/media/apple/AppleDecoder.cpp new file mode 100644 index 000000000000..e496b7d2743c --- /dev/null +++ b/dom/media/apple/AppleDecoder.cpp @@ -0,0 +1,30 @@ +/* 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 "AppleDecoder.h" +#include "AppleMP3Reader.h" + +#include "MediaDecoderStateMachine.h" + +namespace mozilla { + +AppleDecoder::AppleDecoder() + : MediaDecoder() +{ +} + +MediaDecoder * +AppleDecoder::Clone() +{ + return new AppleDecoder(); +} + +MediaDecoderStateMachine * +AppleDecoder::CreateStateMachine() +{ + // TODO MP4 + return new MediaDecoderStateMachine(this, new AppleMP3Reader(this)); +} + +} // namespace mozilla diff --git a/dom/media/apple/AppleDecoder.h b/dom/media/apple/AppleDecoder.h new file mode 100644 index 000000000000..53cf749ad017 --- /dev/null +++ b/dom/media/apple/AppleDecoder.h @@ -0,0 +1,24 @@ +/* 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 __AppleDecoder_h__ +#define __AppleDecoder_h__ + +#include "MediaDecoder.h" + +namespace mozilla { + +class AppleDecoder : public MediaDecoder +{ +public: + AppleDecoder(); + + virtual MediaDecoder* Clone() override; + virtual MediaDecoderStateMachine* CreateStateMachine() override; + +}; + +} // namespace mozilla + +#endif // __AppleDecoder_h__ diff --git a/dom/media/apple/AppleMP3Reader.cpp b/dom/media/apple/AppleMP3Reader.cpp new file mode 100644 index 000000000000..1b27b6ac3806 --- /dev/null +++ b/dom/media/apple/AppleMP3Reader.cpp @@ -0,0 +1,544 @@ +/* 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 "AppleMP3Reader.h" + +#include "nsISeekableStream.h" +#include "MediaDecoder.h" + +// Number of bytes we will read and pass to the audio parser in each +// |DecodeAudioData| call. +#define AUDIO_READ_BYTES 4096 + +// Maximum number of audio frames we will accept from the audio decoder in one +// go. Carefully select this to work well with both the mp3 1152 max frames +// per block and power-of-2 allocation sizes. Since we must pre-allocate the +// buffer we cannot use AudioCompactor without paying for an additional +// allocation and copy. Therefore, choosing a value that divides exactly into +// 1152 is most memory efficient. +#define MAX_AUDIO_FRAMES 128 + +using namespace mozilla::media; + +namespace mozilla { + +extern PRLogModuleInfo* gMediaDecoderLog; +#define LOGE(...) MOZ_LOG(gMediaDecoderLog, mozilla::LogLevel::Error, (__VA_ARGS__)) +#define LOGW(...) MOZ_LOG(gMediaDecoderLog, mozilla::LogLevel::Warning, (__VA_ARGS__)) +#define LOGD(...) MOZ_LOG(gMediaDecoderLog, mozilla::LogLevel::Debug, (__VA_ARGS__)) + +#define PROPERTY_ID_FORMAT "%c%c%c%c" +#define PROPERTY_ID_PRINT(x) ((x) >> 24), \ + ((x) >> 16) & 0xff, \ + ((x) >> 8) & 0xff, \ + (x) & 0xff + +AppleMP3Reader::AppleMP3Reader(AbstractMediaDecoder *aDecoder) + : MediaDecoderReader(aDecoder) + , mStreamReady(false) + , mAudioFramesPerCompressedPacket(0) + , mCurrentAudioFrame(0) + , mAudioChannels(0) + , mAudioSampleRate(0) + , mAudioFileStream(nullptr) + , mAudioConverter(nullptr) + , mMP3FrameParser(mDecoder->GetResource()->GetLength()) + , mResource(mDecoder->GetResource()) +{ + MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread"); +} + +AppleMP3Reader::~AppleMP3Reader() +{ + MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread"); +} + + +/* + * The Apple audio decoding APIs are very callback-happy. When the parser has + * some metadata, it will call back to here. + */ +static void _AudioMetadataCallback(void *aThis, + AudioFileStreamID aFileStream, + AudioFileStreamPropertyID aPropertyID, + UInt32 *aFlags) +{ + ((AppleMP3Reader*)aThis)->AudioMetadataCallback(aFileStream, aPropertyID, + aFlags); +} + +/* + * Similar to above, this is called when the parser has enough data to parse + * one or more samples. + */ +static void _AudioSampleCallback(void *aThis, + UInt32 aNumBytes, UInt32 aNumPackets, + const void *aData, + AudioStreamPacketDescription *aPackets) +{ + ((AppleMP3Reader*)aThis)->AudioSampleCallback(aNumBytes, aNumPackets, + aData, aPackets); +} + + +/* + * If we're not at end of stream, read |aNumBytes| from the media resource, + * put it in |aData|, and return true. + * Otherwise, put as much data as is left into |aData|, set |aNumBytes| to the + * amount of data we have left, and return false. + */ +nsresult +AppleMP3Reader::Read(uint32_t *aNumBytes, char *aData) +{ + nsresult rv = mResource.Read(aData, *aNumBytes, aNumBytes); + + if (NS_FAILED(rv)) { + *aNumBytes = 0; + return NS_ERROR_FAILURE; + } + + // XXX Maybe return a better value than NS_ERROR_FAILURE? + return *aNumBytes ? NS_OK : NS_ERROR_FAILURE; +} + +nsresult +AppleMP3Reader::Init(MediaDecoderReader* aCloneDonor) +{ + AudioFileTypeID fileType = kAudioFileMP3Type; + + OSStatus rv = AudioFileStreamOpen(this, + _AudioMetadataCallback, + _AudioSampleCallback, + fileType, + &mAudioFileStream); + + if (rv) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + + +struct PassthroughUserData { + AppleMP3Reader *mReader; + UInt32 mNumPackets; + UInt32 mDataSize; + const void *mData; + AudioStreamPacketDescription *mPacketDesc; + bool mDone; +}; + +// Error value we pass through the decoder to signal that nothing has gone wrong +// during decoding, but more data is needed. +const UInt32 kNeedMoreData = 'MOAR'; + +/* + * This function is called from |AudioConverterFillComplexBuffer|, which is + * called from |AudioSampleCallback| below, which in turn is called by + * |AudioFileStreamParseBytes|, which is called by |DecodeAudioData|. + * + * Mercifully, this is all synchronous. + * + * This callback is run when the AudioConverter (decoder) wants more MP3 packets + * to decode. + */ +/* static */ OSStatus +AppleMP3Reader::PassthroughInputDataCallback(AudioConverterRef aAudioConverter, + UInt32 *aNumDataPackets /* in/out */, + AudioBufferList *aData /* in/out */, + AudioStreamPacketDescription **aPacketDesc, + void *aUserData) +{ + if (!aPacketDesc) { + return kAudioFileStreamError_UnspecifiedError; + } + + PassthroughUserData *userData = (PassthroughUserData *)aUserData; + if (userData->mDone) { + // We make sure this callback is run _once_, with all the data we received + // from |AudioFileStreamParseBytes|. When we return an error, the decoder + // simply passes the return value on to the calling method, + // |AudioSampleCallback|; and flushes all of the audio frames it had + // buffered. It does not change the decoder's state. + LOGD("requested too much data; returning\n"); + *aNumDataPackets = 0; + return kNeedMoreData; + } + + userData->mDone = true; + + LOGD("AudioConverter wants %u packets of audio data\n", *aNumDataPackets); + + *aNumDataPackets = userData->mNumPackets; + *aPacketDesc = userData->mPacketDesc; + + aData->mBuffers[0].mNumberChannels = userData->mReader->mAudioChannels; + aData->mBuffers[0].mDataByteSize = userData->mDataSize; + aData->mBuffers[0].mData = const_cast(userData->mData); + + return 0; +} + +/* + * This callback is called when |AudioFileStreamParseBytes| has enough data to + * extract one or more MP3 packets. + */ +void +AppleMP3Reader::AudioSampleCallback(UInt32 aNumBytes, + UInt32 aNumPackets, + const void *aData, + AudioStreamPacketDescription *aPackets) +{ + LOGD("got %u bytes, %u packets\n", aNumBytes, aNumPackets); + + // 1 frame per packet * num channels * 32-bit float + uint32_t decodedSize = MAX_AUDIO_FRAMES * mAudioChannels * + sizeof(AudioDataValue); + + // descriptions for _decompressed_ audio packets. ignored. + nsAutoArrayPtr + packets(new AudioStreamPacketDescription[MAX_AUDIO_FRAMES]); + + // This API insists on having MP3 packets spoon-fed to it from a callback. + // This structure exists only to pass our state and the result of the parser + // on to the callback above. + PassthroughUserData userData = { this, aNumPackets, aNumBytes, aData, aPackets, false }; + + do { + // Decompressed audio buffer + nsAutoArrayPtr decoded(new uint8_t[decodedSize]); + + AudioBufferList decBuffer; + decBuffer.mNumberBuffers = 1; + decBuffer.mBuffers[0].mNumberChannels = mAudioChannels; + decBuffer.mBuffers[0].mDataByteSize = decodedSize; + decBuffer.mBuffers[0].mData = decoded.get(); + + // in: the max number of packets we can handle from the decoder. + // out: the number of packets the decoder is actually returning. + UInt32 numFrames = MAX_AUDIO_FRAMES; + + OSStatus rv = AudioConverterFillComplexBuffer(mAudioConverter, + PassthroughInputDataCallback, + &userData, + &numFrames /* in/out */, + &decBuffer, + packets.get()); + + if (rv && rv != kNeedMoreData) { + LOGE("Error decoding audio stream: %x\n", rv); + break; + } + + // If we decoded zero frames then AudiOConverterFillComplexBuffer is out + // of data to provide. We drained its internal buffer completely on the + // last pass. + if (numFrames == 0 && rv == kNeedMoreData) { + LOGD("FillComplexBuffer out of data exactly\n"); + break; + } + + int64_t time = FramesToUsecs(mCurrentAudioFrame, mAudioSampleRate).value(); + int64_t duration = FramesToUsecs(numFrames, mAudioSampleRate).value(); + + LOGD("pushed audio at time %lfs; duration %lfs\n", + (double)time / USECS_PER_S, (double)duration / USECS_PER_S); + + AudioData *audio = new AudioData(mResource.Tell(), + time, duration, numFrames, + reinterpret_cast(decoded.forget()), + mAudioChannels, mAudioSampleRate); + mAudioQueue.Push(audio); + + mCurrentAudioFrame += numFrames; + + if (rv == kNeedMoreData) { + // No error; we just need more data. + LOGD("FillComplexBuffer out of data\n"); + break; + } + } while (true); +} + +bool +AppleMP3Reader::DecodeAudioData() +{ + MOZ_ASSERT(OnTaskQueue()); + + // Read AUDIO_READ_BYTES if we can + char bytes[AUDIO_READ_BYTES]; + uint32_t numBytes = AUDIO_READ_BYTES; + + nsresult readrv = Read(&numBytes, bytes); + + // This function calls |AudioSampleCallback| above, synchronously, when it + // finds compressed MP3 frame. + OSStatus rv = AudioFileStreamParseBytes(mAudioFileStream, + numBytes, + bytes, + 0 /* flags */); + + if (NS_FAILED(readrv)) { + mAudioQueue.Finish(); + return false; + } + + // DataUnavailable just means there wasn't enough data to demux anything. + // We should have more to push into the demuxer next time we're called. + if (rv && rv != kAudioFileStreamError_DataUnavailable) { + LOGE("AudioFileStreamParseBytes returned unknown error %x", rv); + return false; + } + + return true; +} + +bool +AppleMP3Reader::DecodeVideoFrame(bool &aKeyframeSkip, + int64_t aTimeThreshold) +{ + MOZ_ASSERT(OnTaskQueue()); + return false; +} + + +bool +AppleMP3Reader::HasAudio() +{ + MOZ_ASSERT(OnTaskQueue()); + return mStreamReady; +} + +bool +AppleMP3Reader::HasVideo() +{ + MOZ_ASSERT(OnTaskQueue()); + return false; +} + +bool +AppleMP3Reader::IsMediaSeekable() +{ + // not used + return true; +} + +/* + * Query the MP3 parser for a piece of metadata. + */ +static nsresult +GetProperty(AudioFileStreamID aAudioFileStream, + AudioFileStreamPropertyID aPropertyID, void *aData) +{ + UInt32 size; + Boolean writeable; + OSStatus rv = AudioFileStreamGetPropertyInfo(aAudioFileStream, aPropertyID, + &size, &writeable); + + if (rv) { + LOGW("Couldn't get property " PROPERTY_ID_FORMAT "\n", + PROPERTY_ID_PRINT(aPropertyID)); + return NS_ERROR_FAILURE; + } + + rv = AudioFileStreamGetProperty(aAudioFileStream, aPropertyID, + &size, aData); + + return NS_OK; +} + + +nsresult +AppleMP3Reader::ReadMetadata(MediaInfo* aInfo, + MetadataTags** aTags) +{ + MOZ_ASSERT(OnTaskQueue()); + + *aTags = nullptr; + + /* + * Feed bytes into the parser until we have all the metadata we need to + * set up the decoder. When the parser has enough data, it will + * synchronously call back to |AudioMetadataCallback| below. + */ + OSStatus rv; + nsresult readrv; + uint32_t offset = 0; + do { + char bytes[AUDIO_READ_BYTES]; + uint32_t numBytes = AUDIO_READ_BYTES; + readrv = Read(&numBytes, bytes); + + rv = AudioFileStreamParseBytes(mAudioFileStream, + numBytes, + bytes, + 0 /* flags */); + + mMP3FrameParser.Parse(reinterpret_cast(bytes), numBytes, offset); + + offset += numBytes; + + // We have to do our decoder setup from the callback. When it's done it will + // set mStreamReady. + } while (!mStreamReady && !rv && NS_SUCCEEDED(readrv)); + + if (rv) { + LOGE("Error decoding audio stream metadata\n"); + return NS_ERROR_FAILURE; + } + + if (!mAudioConverter) { + LOGE("Failed to setup the AudioToolbox audio decoder\n"); + return NS_ERROR_FAILURE; + } + + if (!mMP3FrameParser.IsMP3()) { + LOGE("Frame parser failed to parse MP3 stream\n"); + return NS_ERROR_FAILURE; + } + + if (mStreamReady) { + aInfo->mAudio.mRate = mAudioSampleRate; + aInfo->mAudio.mChannels = mAudioChannels; + } + + // This special snowflake reader doesn't seem to set *aInfo = mInfo like all + // the others. Yuck. + mDuration = mMP3FrameParser.GetDuration(); + mInfo.mMetadataDuration.emplace(TimeUnit::FromMicroseconds(mDuration)); + aInfo->mMetadataDuration.emplace(TimeUnit::FromMicroseconds(mDuration)); + + return NS_OK; +} + + +void +AppleMP3Reader::AudioMetadataCallback(AudioFileStreamID aFileStream, + AudioFileStreamPropertyID aPropertyID, + UInt32 *aFlags) +{ + if (aPropertyID == kAudioFileStreamProperty_ReadyToProducePackets) { + /* + * The parser is ready to send us packets of MP3 audio. + * + * We need to set the decoder up here, because if + * |AudioFileStreamParseBytes| has enough audio data, then it will call + * |AudioSampleCallback| before we get back to |ReadMetadata|. + */ + SetupDecoder(); + mStreamReady = true; + } +} + + +void +AppleMP3Reader::SetupDecoder() +{ + // Get input format description from demuxer + AudioStreamBasicDescription inputFormat, outputFormat; + GetProperty(mAudioFileStream, kAudioFileStreamProperty_DataFormat, &inputFormat); + + memset(&outputFormat, 0, sizeof(outputFormat)); + + // Set output format +#if defined(MOZ_SAMPLE_TYPE_FLOAT32) + outputFormat.mBitsPerChannel = 32; + outputFormat.mFormatFlags = + kLinearPCMFormatFlagIsFloat | + 0; +#else +#error Unknown audio sample type +#endif + + mAudioSampleRate = outputFormat.mSampleRate = inputFormat.mSampleRate; + mAudioChannels + = outputFormat.mChannelsPerFrame = inputFormat.mChannelsPerFrame; + mAudioFramesPerCompressedPacket = inputFormat.mFramesPerPacket; + + outputFormat.mFormatID = kAudioFormatLinearPCM; + + // Set up the decoder so it gives us one sample per frame; this way, it will + // pass us all the samples it has in one go. Also makes it much easier to + // deinterlace. + outputFormat.mFramesPerPacket = 1; + outputFormat.mBytesPerPacket = outputFormat.mBytesPerFrame + = outputFormat.mChannelsPerFrame * outputFormat.mBitsPerChannel / 8; + + OSStatus rv = AudioConverterNew(&inputFormat, + &outputFormat, + &mAudioConverter); + + if (rv) { + LOGE("Error constructing audio format converter: %x\n", rv); + mAudioConverter = nullptr; + return; + } +} + + +nsRefPtr +AppleMP3Reader::Seek(int64_t aTime, int64_t aEndTime) +{ + MOZ_ASSERT(OnTaskQueue()); + + // Find the exact frame/packet that contains |aTime|. + mCurrentAudioFrame = aTime * mAudioSampleRate / USECS_PER_S; + SInt64 packet = mCurrentAudioFrame / mAudioFramesPerCompressedPacket; + + // |AudioFileStreamSeek| will pass back through |byteOffset| the byte offset + // into the stream it expects next time it reads. + SInt64 byteOffset; + UInt32 flags = 0; + + OSStatus rv = AudioFileStreamSeek(mAudioFileStream, + packet, + &byteOffset, + &flags); + + if (rv) { + LOGE("Couldn't seek demuxer. Error code %x\n", rv); + return SeekPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } + + LOGD("computed byte offset = %lld; estimated = %s\n", + byteOffset, + (flags & kAudioFileStreamSeekFlag_OffsetIsEstimated) ? "YES" : "NO"); + + mResource.Seek(nsISeekableStream::NS_SEEK_SET, byteOffset); + + ResetDecode(); + + return SeekPromise::CreateAndResolve(aTime, __func__); +} + +void +AppleMP3Reader::NotifyDataArrivedInternal(uint32_t aLength, int64_t aOffset) +{ + MOZ_ASSERT(OnTaskQueue()); + if (!mMP3FrameParser.NeedsData()) { + return; + } + + IntervalSet intervals = mFilter.NotifyDataArrived(aLength, aOffset); + for (const auto& interval : intervals) { + nsRefPtr bytes = + mResource.MediaReadAt(interval.mStart, interval.Length()); + NS_ENSURE_TRUE_VOID(bytes); + mMP3FrameParser.Parse(bytes->Elements(), interval.Length(), interval.mStart); + if (!mMP3FrameParser.IsMP3()) { + return; + } + + uint64_t duration = mMP3FrameParser.GetDuration(); + if (duration != mDuration) { + LOGD("Updating media duration to %lluus\n", duration); + MOZ_ASSERT(mDecoder); + mDuration = duration; + mDecoder->DispatchUpdateEstimatedMediaDuration(duration); + } + } +} + +} // namespace mozilla diff --git a/dom/media/apple/AppleMP3Reader.h b/dom/media/apple/AppleMP3Reader.h new file mode 100644 index 000000000000..c5e704ac9c4a --- /dev/null +++ b/dom/media/apple/AppleMP3Reader.h @@ -0,0 +1,90 @@ +/* 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 __AppleMP3Reader_h__ +#define __AppleMP3Reader_h__ + +#include "MediaDecoderReader.h" +#include "MediaResource.h" +#include "MP3FrameParser.h" +#include "VideoUtils.h" + +#include + +namespace mozilla { + +class AppleMP3Reader : public MediaDecoderReader +{ +public: + explicit AppleMP3Reader(AbstractMediaDecoder *aDecoder); + virtual ~AppleMP3Reader() override; + + virtual nsresult Init(MediaDecoderReader* aCloneDonor) override; + + nsresult PushDataToDemuxer(); + + virtual bool DecodeAudioData() override; + virtual bool DecodeVideoFrame(bool &aKeyframeSkip, + int64_t aTimeThreshold) override; + + virtual bool HasAudio() override; + virtual bool HasVideo() override; + + virtual nsresult ReadMetadata(MediaInfo* aInfo, + MetadataTags** aTags) override; + + virtual nsRefPtr + Seek(int64_t aTime, int64_t aEndTime) override; + + void AudioSampleCallback(UInt32 aNumBytes, + UInt32 aNumPackets, + const void *aData, + AudioStreamPacketDescription *aPackets); + + void AudioMetadataCallback(AudioFileStreamID aFileStream, + AudioFileStreamPropertyID aPropertyID, + UInt32 *aFlags); + +protected: + virtual void NotifyDataArrivedInternal(uint32_t aLength, + int64_t aOffset) override; +public: + + virtual bool IsMediaSeekable() override; + +private: + void SetupDecoder(); + nsresult Read(uint32_t *aNumBytes, char *aData); + + static OSStatus PassthroughInputDataCallback(AudioConverterRef aAudioConverter, + UInt32 *aNumDataPackets, + AudioBufferList *aData, + AudioStreamPacketDescription **aPacketDesc, + void *aUserData); + + // Initialisation has to be done in a callback, so we store the result here. + bool mStreamReady; + + // Number of audio samples in an audio packet. Constant over all packets in a + // stream. + UInt32 mAudioFramesPerCompressedPacket; + // Store the next audio frame to be played; so we can keep time when seeking. + UInt64 mCurrentAudioFrame; + UInt32 mAudioChannels; + UInt32 mAudioSampleRate; + + uint64_t mDuration; + + AudioFileStreamID mAudioFileStream; + AudioConverterRef mAudioConverter; + + MP3FrameParser mMP3FrameParser; + + MediaResourceIndex mResource; + NotifyDataArrivedFilter mFilter; +}; + +} // namespace mozilla + +#endif // __AppleMP3Reader_h__ diff --git a/dom/media/apple/moz.build b/dom/media/apple/moz.build new file mode 100644 index 000000000000..f3cd00117768 --- /dev/null +++ b/dom/media/apple/moz.build @@ -0,0 +1,19 @@ +# -*- Mode: python; c-basic-offset: 4; 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 += [ + 'AppleDecoder.h', + 'AppleMP3Reader.h', +] + +UNIFIED_SOURCES += [ + 'AppleDecoder.cpp', + 'AppleMP3Reader.cpp', +] + +FINAL_LIBRARY = 'xul' + +OS_LIBS += ['-framework AudioToolbox'] diff --git a/dom/media/moz.build b/dom/media/moz.build index 5395ce7a9c56..bdacd29ad4b6 100644 --- a/dom/media/moz.build +++ b/dom/media/moz.build @@ -58,6 +58,9 @@ if CONFIG['MOZ_ANDROID_OMX']: if CONFIG['MOZ_FMP4']: DIRS += ['fmp4'] +if CONFIG['MOZ_APPLEMEDIA']: + DIRS += ['apple'] + if CONFIG['MOZ_WEBRTC']: DIRS += ['bridge']