diff --git a/content/media/DecoderTraits.cpp b/content/media/DecoderTraits.cpp index eafed0b1524..952d433555e 100644 --- a/content/media/DecoderTraits.cpp +++ b/content/media/DecoderTraits.cpp @@ -56,6 +56,10 @@ #include "DirectShowDecoder.h" #include "DirectShowReader.h" #endif +#ifdef MOZ_APPLEMEDIA +#include "AppleDecoder.h" +#include "AppleMP3Reader.h" +#endif namespace mozilla { @@ -288,6 +292,38 @@ IsDirectShowSupportedType(const nsACString& aType) } #endif +#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) { @@ -371,6 +407,11 @@ DecoderTraits::CanHandleMediaType(const char* aMIMEType, result = CANPLAY_MAYBE; } #endif +#ifdef MOZ_APPLEMEDIA + if (IsAppleMediaSupportedType(nsDependentCString(aMIMEType), &codecList)) { + result = CANPLAY_MAYBE; + } +#endif #ifdef MOZ_MEDIA_PLUGINS if (MediaDecoder::IsMediaPluginsEnabled() && GetMediaPluginHost()->FindDecoder(nsDependentCString(aMIMEType), &codecList)) @@ -473,6 +514,11 @@ DecoderTraits::CreateDecoder(const nsACString& aType, MediaDecoderOwner* aOwner) decoder = new WMFDecoder(); } #endif +#ifdef MOZ_APPLEMEDIA + if (IsAppleMediaSupportedType(aType)) { + decoder = new AppleDecoder(); + } +#endif NS_ENSURE_TRUE(decoder != nullptr, nullptr); NS_ENSURE_TRUE(decoder->Init(aOwner), nullptr); @@ -533,6 +579,11 @@ MediaDecoderReader* DecoderTraits::CreateReader(const nsACString& aType, Abstrac decoderReader = new WMFReader(aDecoder); } else #endif +#ifdef MOZ_APPLEMEDIA + if (IsAppleMediaSupportedType(aType)) { + decoderReader = new AppleMP3Reader(aDecoder); + } else +#endif #ifdef MOZ_DASH // The DASH decoder is not supported. #endif @@ -571,6 +622,9 @@ bool DecoderTraits::IsSupportedInVideoDocument(const nsACString& aType) #endif #ifdef MOZ_DIRECTSHOW IsDirectShowSupportedType(aType) || +#endif +#ifdef MOZ_APPLEMEDIA + IsAppleMediaSupportedType(aType) || #endif false; } diff --git a/content/media/MediaDecoder.cpp b/content/media/MediaDecoder.cpp index d56967e2b9f..6b2a072b4fd 100644 --- a/content/media/MediaDecoder.cpp +++ b/content/media/MediaDecoder.cpp @@ -1274,7 +1274,6 @@ void MediaDecoder::SetMediaDuration(int64_t aDuration) void MediaDecoder::UpdateEstimatedMediaDuration(int64_t aDuration) { - MOZ_ASSERT(NS_IsMainThread()); if (mPlayState <= PLAY_STATE_LOADING) { return; } @@ -1728,6 +1727,14 @@ MediaDecoder::IsWMFEnabled() } #endif +#ifdef MOZ_APPLEMEDIA +bool +MediaDecoder::IsAppleMP3Enabled() +{ + return Preferences::GetBool("media.apple.mp3.enabled"); +} +#endif + class MediaReporter MOZ_FINAL : public nsIMemoryReporter { public: diff --git a/content/media/MediaDecoder.h b/content/media/MediaDecoder.h index d5aff6fb4a6..ee433efc770 100644 --- a/content/media/MediaDecoder.h +++ b/content/media/MediaDecoder.h @@ -786,6 +786,10 @@ public: static bool IsWMFEnabled(); #endif +#ifdef MOZ_APPLEMEDIA + static bool IsAppleMP3Enabled(); +#endif + // Schedules the state machine to run one cycle on the shared state // machine thread. Main thread only. nsresult ScheduleStateMachineThread(); diff --git a/content/media/apple/AppleDecoder.cpp b/content/media/apple/AppleDecoder.cpp new file mode 100644 index 00000000000..e496b7d2743 --- /dev/null +++ b/content/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/content/media/apple/AppleDecoder.h b/content/media/apple/AppleDecoder.h new file mode 100644 index 00000000000..b0e7c6dd884 --- /dev/null +++ b/content/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() MOZ_OVERRIDE; + virtual MediaDecoderStateMachine* CreateStateMachine() MOZ_OVERRIDE; + +}; + +} + +#endif // __AppleDecoder_h__ diff --git a/content/media/apple/AppleMP3Reader.cpp b/content/media/apple/AppleMP3Reader.cpp new file mode 100644 index 00000000000..4fea66ee91c --- /dev/null +++ b/content/media/apple/AppleMP3Reader.cpp @@ -0,0 +1,533 @@ +/* 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. +#define MAX_AUDIO_FRAMES 4096 + +namespace mozilla { + +#ifdef PR_LOGGING +extern PRLogModuleInfo* gMediaDecoderLog; +#define LOGE(...) PR_LOG(gMediaDecoderLog, PR_LOG_ERROR, (__VA_ARGS__)) +#define LOGW(...) PR_LOG(gMediaDecoderLog, PR_LOG_WARNING, (__VA_ARGS__)) +#define LOGD(...) PR_LOG(gMediaDecoderLog, PR_LOG_DEBUG, (__VA_ARGS__)) +#else +#define LOGE(...) +#define LOGW(...) +#define LOGD(...) +#endif + +#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()) +{ + 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. + * + * This function also calls NotifyBytesConsumed() on the media resource and + * passes the read data on to the MP3 frame parser for stream duration + * estimation. + */ +nsresult +AppleMP3Reader::ReadAndNotify(uint32_t *aNumBytes, char *aData) +{ + MediaResource *resource = mDecoder->GetResource(); + + uint64_t offset = resource->Tell(); + + // Loop until we have all the data asked for, or we've reached EOS + uint32_t totalBytes = 0; + uint32_t numBytes; + do { + uint32_t bytesWanted = *aNumBytes - totalBytes; + nsresult rv = resource->Read(aData + totalBytes, bytesWanted, &numBytes); + totalBytes += numBytes; + + if (NS_FAILED(rv)) { + *aNumBytes = 0; + return NS_ERROR_FAILURE; + } + } while(totalBytes < *aNumBytes && numBytes); + + mDecoder->NotifyBytesConsumed(totalBytes); + + // Pass the buffer to the MP3 frame parser to improve our duration estimate. + if (mMP3FrameParser.IsMP3()) { + mMP3FrameParser.Parse(aData, totalBytes, offset); + uint64_t duration = mMP3FrameParser.GetDuration(); + if (duration != mDuration) { + LOGD("Updating media duration to %lluus\n", duration); + mDuration = duration; + ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); + mDecoder->UpdateEstimatedMediaDuration(duration); + } + } + + *aNumBytes = totalBytes; + + // We will have read some data in the last iteration iff we filled the buffer. + // XXX Maybe return a better value than NS_ERROR_FAILURE? + return numBytes ? 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) +{ + 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 * 4; + + // 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; + } + + 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(mDecoder->GetResource()->Tell(), + time, duration, numFrames, + reinterpret_cast(decoded.forget()), + mAudioChannels); + 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(mDecoder->OnDecodeThread(), "Should be on decode thread"); + + // Read AUDIO_READ_BYTES if we can + char bytes[AUDIO_READ_BYTES]; + uint32_t numBytes = AUDIO_READ_BYTES; + + nsresult readrv = ReadAndNotify(&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(mDecoder->OnDecodeThread(), "Should be on decode thread"); + return false; +} + + +bool +AppleMP3Reader::HasAudio() +{ + MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread"); + return mStreamReady; +} + +bool +AppleMP3Reader::HasVideo() +{ + MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread"); + return false; +} + + +/* + * 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(VideoInfo* aInfo, + MetadataTags** aTags) +{ + MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread"); + + *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; + do { + char bytes[AUDIO_READ_BYTES]; + uint32_t numBytes = AUDIO_READ_BYTES; + readrv = ReadAndNotify(&numBytes, bytes); + + rv = AudioFileStreamParseBytes(mAudioFileStream, + numBytes, + bytes, + 0 /* flags */); + + // 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; + } + + aInfo->mAudioRate = mAudioSampleRate; + aInfo->mAudioChannels = mAudioChannels; + aInfo->mHasAudio = mStreamReady; + + { + ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); + mDecoder->SetMediaDuration(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); + + outputFormat = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + + // 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; + } +} + + +nsresult +AppleMP3Reader::Seek(int64_t aTime, + int64_t aStartTime, + int64_t aEndTime, + int64_t aCurrentTime) +{ + MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread"); + NS_ASSERTION(aStartTime < aEndTime, + "Seeking should happen over a positive range"); + + // 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 NS_ERROR_FAILURE; + } + + LOGD("computed byte offset = %lld; estimated = %s\n", + byteOffset, + (flags & kAudioFileStreamSeekFlag_OffsetIsEstimated) ? "YES" : "NO"); + + mDecoder->GetResource()->Seek(nsISeekableStream::NS_SEEK_SET, byteOffset); + + ResetDecode(); + + return NS_OK; +} + + +nsresult +AppleMP3Reader::GetBuffered(dom::TimeRanges* aBuffered, + int64_t aStartTime) +{ + ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); + GetEstimatedBufferedTimeRanges(mDecoder->GetResource(), + mDecoder->GetMediaDuration(), + aBuffered); + return NS_OK; +} + +} // namespace mozilla diff --git a/content/media/apple/AppleMP3Reader.h b/content/media/apple/AppleMP3Reader.h new file mode 100644 index 00000000000..30da7a81c77 --- /dev/null +++ b/content/media/apple/AppleMP3Reader.h @@ -0,0 +1,84 @@ +/* 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 "MP3FrameParser.h" +#include "VideoUtils.h" + +#include + +namespace mozilla { + +class AppleMP3Reader : public MediaDecoderReader +{ +public: + AppleMP3Reader(AbstractMediaDecoder *aDecoder); + virtual ~AppleMP3Reader() MOZ_OVERRIDE; + + virtual nsresult Init(MediaDecoderReader* aCloneDonor) MOZ_OVERRIDE; + + nsresult PushDataToDemuxer(); + + virtual bool DecodeAudioData() MOZ_OVERRIDE; + virtual bool DecodeVideoFrame(bool &aKeyframeSkip, + int64_t aTimeThreshold) MOZ_OVERRIDE; + + virtual bool HasAudio() MOZ_OVERRIDE; + virtual bool HasVideo() MOZ_OVERRIDE; + + virtual nsresult ReadMetadata(VideoInfo* aInfo, + MetadataTags** aTags) MOZ_OVERRIDE; + + virtual nsresult Seek(int64_t aTime, + int64_t aStartTime, + int64_t aEndTime, + int64_t aCurrentTime) MOZ_OVERRIDE; + + virtual nsresult GetBuffered(dom::TimeRanges* aBuffered, + int64_t aStartTime) MOZ_OVERRIDE; + + void AudioSampleCallback(UInt32 aNumBytes, + UInt32 aNumPackets, + const void *aData, + AudioStreamPacketDescription *aPackets); + + void AudioMetadataCallback(AudioFileStreamID aFileStream, + AudioFileStreamPropertyID aPropertyID, + UInt32 *aFlags); + +private: + void SetupDecoder(); + nsresult ReadAndNotify(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; +}; + +} // namespace mozilla + +#endif // __AppleMP3Reader_h__ diff --git a/content/media/test/manifest.js b/content/media/test/manifest.js index 359c7cd43dc..a453b2daef8 100644 --- a/content/media/test/manifest.js +++ b/content/media/test/manifest.js @@ -695,9 +695,11 @@ function mediaTestCleanup() { var branch = prefService.getBranch("media."); var oldDefault = 2; var oldAuto = 3; + var oldAppleMedia = undefined; var oldGStreamer = undefined; var oldOpus = undefined; + try { oldAppleMedia = SpecialPowers.getBoolPref("media.apple.mp3.enabled"); } catch(ex) { } try { oldGStreamer = SpecialPowers.getBoolPref("media.gstreamer.enabled"); } catch(ex) { } try { oldDefault = SpecialPowers.getIntPref("media.preload.default"); } catch(ex) { } try { oldAuto = SpecialPowers.getIntPref("media.preload.auto"); } catch(ex) { } @@ -710,10 +712,14 @@ function mediaTestCleanup() { SpecialPowers.setBoolPref("media.opus.enabled", true); if (oldGStreamer !== undefined) SpecialPowers.setBoolPref("media.gstreamer.enabled", true); + if (oldAppleMedia !== undefined) + SpecialPowers.setBoolPref("media.apple.mp3.enabled", true); window.addEventListener("unload", function() { if (oldGStreamer !== undefined) SpecialPowers.setBoolPref("media.gstreamer.enabled", oldGStreamer); + if (oldAppleMedia !== undefined) + SpecialPowers.setBoolPref("media.apple.mp3.enabled", oldAppleMedia); SpecialPowers.setIntPref("media.preload.default", oldDefault); SpecialPowers.setIntPref("media.preload.auto", oldAuto); if (oldOpus !== undefined) diff --git a/content/media/test/test_can_play_type_mpeg.html b/content/media/test/test_can_play_type_mpeg.html index 0fd25e4dbb0..eeaf2b2f10d 100644 --- a/content/media/test/test_can_play_type_mpeg.html +++ b/content/media/test/test_can_play_type_mpeg.html @@ -48,7 +48,8 @@ check_mp4(document.getElementById('v'), haveMp4); var haveMp3 = getPref("media.directshow.enabled") || (getPref("media.windows-media-foundation.enabled") && IsWindowsVistaOrLater()) || getPref("media.omx.enabled") || - getPref("media.gstreamer.enabled"); + getPref("media.gstreamer.enabled") || + getPref("media.apple.mp3.enabled"); check_mp3(document.getElementById('v'), haveMp3); mediaTestCleanup(); diff --git a/modules/libpref/src/init/all.js b/modules/libpref/src/init/all.js index f2ff469aa24..32ec38f4dd0 100644 --- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -208,6 +208,9 @@ pref("media.dash.enabled", false); #ifdef MOZ_GSTREAMER pref("media.gstreamer.enabled", true); #endif +#ifdef MOZ_APPLEMEDIA +pref("media.apple.mp3.enabled", false); +#endif #ifdef MOZ_WEBRTC pref("media.navigator.enabled", true); pref("media.navigator.video.default_width",640);