From eb6fdca18bc8858bd5e85a0c2543985d01cf78e6 Mon Sep 17 00:00:00 2001 From: trav90 Date: Sat, 8 Dec 2018 16:28:34 -0600 Subject: Remove the old MP3FrameParser All former users of the old MP3 parsing code are gone, so we can now just remove the parser itself as well. --- dom/media/MP3FrameParser.cpp | 591 ------------------------------------------- dom/media/MP3FrameParser.h | 219 ---------------- dom/media/moz.build | 2 - 3 files changed, 812 deletions(-) delete mode 100644 dom/media/MP3FrameParser.cpp delete mode 100644 dom/media/MP3FrameParser.h diff --git a/dom/media/MP3FrameParser.cpp b/dom/media/MP3FrameParser.cpp deleted file mode 100644 index 242e3df00..000000000 --- a/dom/media/MP3FrameParser.cpp +++ /dev/null @@ -1,591 +0,0 @@ -/* -*- 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 - -#include "nsMemory.h" -#include "MP3FrameParser.h" -#include "VideoUtils.h" - - -#define FROM_BIG_ENDIAN(X) ((uint32_t)((uint8_t)(X)[0] << 24 | (uint8_t)(X)[1] << 16 | \ - (uint8_t)(X)[2] << 8 | (uint8_t)(X)[3])) - - -namespace mozilla { - -/* - * Following code taken from http://www.hydrogenaudio.org/forums/index.php?showtopic=85125 - * with permission from the author, Nick Wallette . - */ - -/* BEGIN shameless copy and paste */ - -// Bitrates - use [version][layer][bitrate] -const uint16_t mpeg_bitrates[4][4][16] = { - { // Version 2.5 - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Reserved - { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }, // Layer 3 - { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }, // Layer 2 - { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0 } // Layer 1 - }, - { // Reserved - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Invalid - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Invalid - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Invalid - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } // Invalid - }, - { // Version 2 - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Reserved - { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }, // Layer 3 - { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }, // Layer 2 - { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0 } // Layer 1 - }, - { // Version 1 - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Reserved - { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0 }, // Layer 3 - { 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0 }, // Layer 2 - { 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0 }, // Layer 1 - } -}; - -// Sample rates - use [version][srate] -const uint16_t mpeg_srates[4][4] = { - { 11025, 12000, 8000, 0 }, // MPEG 2.5 - { 0, 0, 0, 0 }, // Reserved - { 22050, 24000, 16000, 0 }, // MPEG 2 - { 44100, 48000, 32000, 0 } // MPEG 1 -}; - -// Samples per frame - use [version][layer] -const uint16_t mpeg_frame_samples[4][4] = { -// Rsvd 3 2 1 < Layer v Version - { 0, 576, 1152, 384 }, // 2.5 - { 0, 0, 0, 0 }, // Reserved - { 0, 576, 1152, 384 }, // 2 - { 0, 1152, 1152, 384 } // 1 -}; - -// Slot size (MPEG unit of measurement) - use [layer] -const uint8_t mpeg_slot_size[4] = { 0, 1, 1, 4 }; // Rsvd, 3, 2, 1 - -uint16_t -MP3Frame::CalculateLength() -{ - // Lookup real values of these fields - uint32_t bitrate = mpeg_bitrates[mVersion][mLayer][mBitrate] * 1000; - uint32_t samprate = mpeg_srates[mVersion][mSampleRate]; - uint16_t samples = mpeg_frame_samples[mVersion][mLayer]; - uint8_t slot_size = mpeg_slot_size[mLayer]; - - // In-between calculations - float bps = (float)samples / 8.0; - float fsize = ( (bps * (float)bitrate) / (float)samprate ) - + ( (mPad) ? slot_size : 0 ); - - // Frame sizes are truncated integers - return (uint16_t)fsize; -} - -/* END shameless copy and paste */ - - -/** MP3Parser methods **/ - -MP3Parser::MP3Parser() - : mCurrentChar(0) -{ } - -void -MP3Parser::Reset() -{ - mCurrentChar = 0; -} - -uint16_t -MP3Parser::ParseFrameLength(uint8_t ch) -{ - mData.mRaw[mCurrentChar] = ch; - - MP3Frame &frame = mData.mFrame; - - // Validate MP3 header as we read. We can't mistake the start of an MP3 frame - // for the middle of another frame due to the sync byte at the beginning - // of the frame. - - // The only valid position for an all-high byte is the sync byte at the - // beginning of the frame. - if (ch == 0xff) { - mCurrentChar = 0; - } - - // Make sure the current byte is valid in context. If not, reset the parser. - if (mCurrentChar == 2) { - if (frame.mBitrate == 0x0f) { - goto fail; - } - } else if (mCurrentChar == 1) { - if (frame.mSync2 != 0x07 - || frame.mVersion == 0x01 - || frame.mLayer == 0x00) { - goto fail; - } - } - - // The only valid character at the beginning of the header is 0xff. Fail if - // it's different. - if (mCurrentChar == 0 && frame.mSync1 != 0xff) { - // Couldn't find the sync byte. Fail. - return 0; - } - - mCurrentChar++; - MOZ_ASSERT(mCurrentChar <= sizeof(MP3Frame)); - - // Don't have a full header yet. - if (mCurrentChar < sizeof(MP3Frame)) { - return 0; - } - - // Woo, valid header. Return the length. - mCurrentChar = 0; - return frame.CalculateLength(); - -fail: - Reset(); - return 0; -} - -uint32_t -MP3Parser::GetSampleRate() -{ - MP3Frame &frame = mData.mFrame; - return mpeg_srates[frame.mVersion][frame.mSampleRate]; -} - -uint32_t -MP3Parser::GetSamplesPerFrame() -{ - MP3Frame &frame = mData.mFrame; - return mpeg_frame_samples[frame.mVersion][frame.mLayer]; -} - - -/** ID3Parser methods **/ - -const char sID3Head[3] = { 'I', 'D', '3' }; -const uint32_t ID3_HEADER_LENGTH = 10; -const uint32_t ID3_FOOTER_LENGTH = 10; -const uint8_t ID3_FOOTER_PRESENT = 0x10; - -ID3Parser::ID3Parser() - : mCurrentChar(0) - , mVersion(0) - , mFlags(0) - , mHeaderLength(0) -{ } - -void -ID3Parser::Reset() -{ - mCurrentChar = mVersion = mFlags = mHeaderLength = 0; -} - -bool -ID3Parser::ParseChar(char ch) -{ - switch (mCurrentChar) { - // The first three bytes of an ID3v2 header must match the string "ID3". - case 0: case 1: case 2: - if (ch != sID3Head[mCurrentChar]) { - goto fail; - } - break; - // The fourth and fifth bytes give the version, between 2 and 4. - case 3: - if (ch < '\2' || ch > '\4') { - goto fail; - } - mVersion = uint8_t(ch); - break; - case 4: - if (ch != '\0') { - goto fail; - } - break; - // The sixth byte gives the flags; valid flags depend on the version. - case 5: - if ((ch & (0xff >> mVersion)) != '\0') { - goto fail; - } - mFlags = uint8_t(ch); - break; - // Bytes seven through ten give the sum of the byte length of the extended - // header, the padding and the frames after unsynchronisation. - // These bytes form a 28-bit integer, with the high bit of each byte unset. - case 6: case 7: case 8: case 9: - if (ch & 0x80) { - goto fail; - } - mHeaderLength <<= 7; - mHeaderLength |= ch; - if (mCurrentChar == 9) { - mHeaderLength += ID3_HEADER_LENGTH; - mHeaderLength += (mFlags & ID3_FOOTER_PRESENT) ? ID3_FOOTER_LENGTH : 0; - } - break; - default: - MOZ_CRASH("Header already fully parsed!"); - } - - mCurrentChar++; - - return IsParsed(); - -fail: - if (mCurrentChar) { - Reset(); - return ParseChar(ch); - } - Reset(); - return false; -} - -bool -ID3Parser::IsParsed() const -{ - return mCurrentChar >= ID3_HEADER_LENGTH; -} - -uint32_t -ID3Parser::GetHeaderLength() const -{ - MOZ_ASSERT(IsParsed(), - "Queried length of ID3 header before parsing finished."); - return mHeaderLength; -} - - -/** VBR header helper stuff **/ - -// Helper function to find a VBR header in an MP3 frame. -// Based on information from -// http://www.codeproject.com/Articles/8295/MPEG-Audio-Frame-Header - -const uint32_t VBRI_TAG = FROM_BIG_ENDIAN("VBRI"); -const uint32_t VBRI_OFFSET = 32 - sizeof(MP3Frame); -const uint32_t VBRI_FRAME_COUNT_OFFSET = VBRI_OFFSET + 14; -const uint32_t VBRI_MIN_FRAME_SIZE = VBRI_OFFSET + 26; - -const uint32_t XING_TAG = FROM_BIG_ENDIAN("Xing"); -enum XingFlags { - XING_HAS_NUM_FRAMES = 0x01, - XING_HAS_NUM_BYTES = 0x02, - XING_HAS_TOC = 0x04, - XING_HAS_VBR_SCALE = 0x08 -}; - -static int64_t -ParseXing(const char *aBuffer) -{ - uint32_t flags = FROM_BIG_ENDIAN(aBuffer + 4); - - if (!(flags & XING_HAS_NUM_FRAMES)) { - NS_WARNING("VBR file without frame count. Duration estimation likely to " - "be totally wrong."); - return -1; - } - - int64_t numFrames = -1; - if (flags & XING_HAS_NUM_FRAMES) { - numFrames = FROM_BIG_ENDIAN(aBuffer + 8); - } - - return numFrames; -} - -static int64_t -FindNumVBRFrames(const nsCString& aFrame) -{ - const char *buffer = aFrame.get(); - const char *bufferEnd = aFrame.get() + aFrame.Length(); - - // VBRI header is nice and well-defined; let's try to find that first. - if (aFrame.Length() > VBRI_MIN_FRAME_SIZE && - FROM_BIG_ENDIAN(buffer + VBRI_OFFSET) == VBRI_TAG) { - return FROM_BIG_ENDIAN(buffer + VBRI_FRAME_COUNT_OFFSET); - } - - // We have to search for the Xing header as its position can change. - for (; buffer + sizeof(XING_TAG) < bufferEnd; buffer++) { - if (FROM_BIG_ENDIAN(buffer) == XING_TAG) { - return ParseXing(buffer); - } - } - - return -1; -} - - -/** MP3FrameParser methods **/ - -// Some MP3's have large ID3v2 tags, up to 150KB, so we allow lots of -// skipped bytes to be read, just in case, before we give up and assume -// we're not parsing an MP3 stream. -static const uint32_t MAX_SKIPPED_BYTES = 4096; - -enum { - MP3_HEADER_LENGTH = 4, -}; - -MP3FrameParser::MP3FrameParser(int64_t aLength) -: mLock("MP3FrameParser.mLock"), - mTotalID3Size(0), - mTotalFrameSize(0), - mFrameCount(0), - mOffset(0), - mLength(aLength), - mMP3Offset(-1), - mSamplesPerSecond(0), - mFirstFrameEnd(-1), - mIsMP3(MAYBE_MP3) -{ } - -nsresult MP3FrameParser::ParseBuffer(const uint8_t* aBuffer, - uint32_t aLength, - int64_t aStreamOffset, - uint32_t* aOutBytesRead) -{ - // Iterate forwards over the buffer, looking for ID3 tag, or MP3 - // Frame headers. - const uint8_t *buffer = aBuffer; - const uint8_t *bufferEnd = aBuffer + aLength; - - // If we haven't found any MP3 frame data yet, there might be ID3 headers - // we can skip over. - if (mMP3Offset < 0) { - for (const uint8_t *ch = buffer; ch < bufferEnd; ch++) { - if (mID3Parser.ParseChar(*ch)) { - // Found an ID3 header. We don't care about the body of the header, so - // just skip past. - buffer = ch + mID3Parser.GetHeaderLength() - (ID3_HEADER_LENGTH - 1); - - if (buffer <= ch) { - return NS_ERROR_FAILURE; - } - - ch = buffer; - - mTotalID3Size += mID3Parser.GetHeaderLength(); - - // Yes, this is an MP3! - mIsMP3 = DEFINITELY_MP3; - - mID3Parser.Reset(); - } - } - } - - // The first MP3 frame in a variable bitrate stream can contain metadata - // for duration estimation and seeking, so we buffer that first frame here. - if (aStreamOffset < mFirstFrameEnd) { - uint64_t copyLen = std::min((int64_t)aLength, mFirstFrameEnd - aStreamOffset); - mFirstFrame.Append((const char *)buffer, copyLen); - buffer += copyLen; - } - - while (buffer < bufferEnd) { - uint16_t frameLen = mMP3Parser.ParseFrameLength(*buffer); - - if (frameLen) { - // We've found an MP3 frame! - // This is the first frame (and the only one we'll bother parsing), so: - // * Mark this stream as MP3; - // * Store the offset at which the MP3 data started; and - // * Start buffering the frame, as it might contain handy metadata. - - // We're now sure this is an MP3 stream. - mIsMP3 = DEFINITELY_MP3; - - // We need to know these to convert the number of frames in the stream - // to the length of the stream in seconds. - mSamplesPerSecond = mMP3Parser.GetSampleRate(); - mSamplesPerFrame = mMP3Parser.GetSamplesPerFrame(); - - // If the stream has a constant bitrate, we should only need the length - // of the first frame and the length (in bytes) of the stream to - // estimate the length (in seconds). - mTotalFrameSize += frameLen; - mFrameCount++; - - // If |mMP3Offset| isn't set then this is the first MP3 frame we have - // seen in the stream, which is useful for duration estimation. - if (mMP3Offset > -1) { - uint16_t skip = frameLen - sizeof(MP3Frame); - buffer += skip ? skip : 1; - continue; - } - - // Remember the offset of the MP3 stream. - // We're at the last byte of an MP3Frame, so MP3 data started - // sizeof(MP3Frame) - 1 bytes ago. - mMP3Offset = aStreamOffset - + (buffer - aBuffer) - - (sizeof(MP3Frame) - 1); - - buffer++; - - // If the stream has a variable bitrate, the first frame has metadata - // we need for duration estimation and seeking. Start buffering it so we - // can parse it later. - mFirstFrameEnd = mMP3Offset + frameLen; - uint64_t currOffset = buffer - aBuffer + aStreamOffset; - uint64_t copyLen = std::min(mFirstFrameEnd - currOffset, - (uint64_t)(bufferEnd - buffer)); - mFirstFrame.Append((const char *)buffer, copyLen); - - buffer += copyLen; - - } else { - // Nothing to see here. Move along. - buffer++; - } - } - - *aOutBytesRead = buffer - aBuffer; - - if (mFirstFrameEnd > -1 && mFirstFrameEnd <= aStreamOffset + buffer - aBuffer) { - // We have our whole first frame. Try to find a VBR header. - mNumFrames = FindNumVBRFrames(mFirstFrame); - mFirstFrameEnd = -1; - } - - return NS_OK; -} - -void MP3FrameParser::Parse(const uint8_t* aBuffer, uint32_t aLength, uint64_t aOffset) -{ - MutexAutoLock mon(mLock); - - if (HasExactDuration()) { - // We know the duration; nothing to do here. - return; - } - - const uint8_t* buffer = aBuffer; - int32_t length = aLength; - uint64_t offset = aOffset; - - // Got some data we have seen already. Skip forward to what we need. - if (aOffset < mOffset) { - buffer += mOffset - aOffset; - length -= mOffset - aOffset; - offset = mOffset; - - if (length <= 0) { - return; - } - } - - // If there is a discontinuity in the input stream, reset the state of the - // parsers so we don't get any partial headers. - if (mOffset < aOffset) { - if (!mID3Parser.IsParsed()) { - // Only reset this if it hasn't finished yet. - mID3Parser.Reset(); - } - - if (mFirstFrameEnd > -1) { - NS_WARNING("Discontinuity in input while buffering first frame."); - mFirstFrameEnd = -1; - } - - mMP3Parser.Reset(); - } - - uint32_t bytesRead = 0; - if (NS_FAILED(ParseBuffer(buffer, - length, - offset, - &bytesRead))) { - return; - } - - MOZ_ASSERT(length <= (int)bytesRead, "All bytes should have been consumed"); - - // Update next data offset - mOffset = offset + bytesRead; - - // If we've parsed lots of data and we still have nothing, just give up. - // We don't count ID3 headers towards the skipped bytes count, as MP3 files - // can have massive ID3 sections. - if (!mID3Parser.IsParsed() && mMP3Offset < 0 && - mOffset - mTotalID3Size > MAX_SKIPPED_BYTES) { - mIsMP3 = NOT_MP3; - } -} - -int64_t MP3FrameParser::GetDuration() -{ - MutexAutoLock mon(mLock); - - if (!ParsedHeaders() || !mSamplesPerSecond) { - // Not a single frame decoded yet. - return -1; - } - - MOZ_ASSERT(mFrameCount > 0 && mTotalFrameSize > 0, - "Frame parser should have seen at least one MP3 frame of positive length."); - - if (!mFrameCount || !mTotalFrameSize) { - // This should never happen. - return -1; - } - - double frames; - if (mNumFrames < 0) { - // Estimate the number of frames in the stream based on the average frame - // size and the length of the MP3 file. - double frameSize = (double)mTotalFrameSize / mFrameCount; - frames = (double)(mLength - mMP3Offset) / frameSize; - } else { - // We know the exact number of frames from the VBR header. - frames = mNumFrames; - } - - // The duration of each frame is constant over a given stream. - double usPerFrame = USECS_PER_S * mSamplesPerFrame / mSamplesPerSecond; - - return frames * usPerFrame; -} - -int64_t MP3FrameParser::GetMP3Offset() -{ - MutexAutoLock mon(mLock); - return mMP3Offset; -} - -bool MP3FrameParser::ParsedHeaders() -{ - // We have seen both the beginning and the end of the first MP3 frame in the - // stream. - return mMP3Offset > -1 && mFirstFrameEnd < 0; -} - -bool MP3FrameParser::HasExactDuration() -{ - return ParsedHeaders() && mNumFrames > -1; -} - -bool MP3FrameParser::NeedsData() -{ - // If we don't know the duration exactly then either: - // - we're still waiting for a VBR header; or - // - we look at all frames to constantly update our duration estimate. - return IsMP3() && !HasExactDuration(); -} - -} // namespace mozilla diff --git a/dom/media/MP3FrameParser.h b/dom/media/MP3FrameParser.h deleted file mode 100644 index d2ba791fd..000000000 --- a/dom/media/MP3FrameParser.h +++ /dev/null @@ -1,219 +0,0 @@ -/* -*- 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 MP3FrameParser_h -#define MP3FrameParser_h - -#include - -#include "mozilla/Mutex.h" -#include "nsString.h" -#include "Intervals.h" - -namespace mozilla { - -// Simple parser to tell whether we've found an ID3 header and how long it is, -// so that we can skip it. -// XXX maybe actually parse this stuff? -class ID3Parser -{ -public: - ID3Parser(); - - void Reset(); - bool ParseChar(char ch); - bool IsParsed() const; - uint32_t GetHeaderLength() const; - -private: - uint32_t mCurrentChar; - uint8_t mVersion; - uint8_t mFlags; - uint32_t mHeaderLength; -}; - -struct MP3Frame { - uint16_t mSync1 : 8; // Always all set - uint16_t mProtected : 1; // Ignored - uint16_t mLayer : 2; - uint16_t mVersion : 2; - uint16_t mSync2 : 3; // Always all set - uint16_t mPrivate : 1; // Ignored - uint16_t mPad : 1; - uint16_t mSampleRate : 2; // Index into mpeg_srates above - uint16_t mBitrate : 4; // Index into mpeg_bitrates above - - uint16_t CalculateLength(); -}; - -// Buffering parser for MP3 frames. -class MP3Parser -{ -public: - MP3Parser(); - - // Forget all data the parser has seen so far. - void Reset(); - - // Parse the given byte. If we have found a frame header, return the length of - // the frame. - uint16_t ParseFrameLength(uint8_t ch); - - // Get the sample rate from the current header. - uint32_t GetSampleRate(); - - // Get the number of samples per frame. - uint32_t GetSamplesPerFrame(); - -private: - uint32_t mCurrentChar; - union { - uint8_t mRaw[3]; - MP3Frame mFrame; - } mData; -}; - - -// A description of the MP3 format and its extensions is available at -// -// http://www.codeproject.com/Articles/8295/MPEG-Audio-Frame-Header -// -// The data in MP3 streams is split into small frames, with each frame -// containing a fixed number of samples. The duration of a frame depends -// on the frame's bit rate and sample rate. Both values can vary among -// frames, so it is necessary to examine each individual frame of an MP3 -// stream to calculate the stream's overall duration. -// -// The MP3 frame parser extracts information from an MP3 data stream. It -// accepts a range of frames of an MP3 stream as input, and parses all -// frames for their duration. Callers can query the stream's overall -// duration from the parser. -// -// Call the methods NotifyDataArrived or Parse to add new data. If you added -// information for a certain stream position, you cannot go back to previous -// positions. The parser will simply ignore the input. If you skip stream -// positions, the duration of the related MP3 frames will be estimated from -// the stream's average. -// -// The method GetDuration returns calculated duration of the stream, including -// estimates for skipped ranges. -// -// All public methods are thread-safe. - -class MP3FrameParser -{ -public: - explicit MP3FrameParser(int64_t aLength=-1); - - bool IsMP3() { - MutexAutoLock mon(mLock); - return mIsMP3 != NOT_MP3; - } - - void Parse(const uint8_t* aBuffer, uint32_t aLength, uint64_t aStreamOffset); - - // Returns the duration, in microseconds. If the entire stream has not - // been parsed yet, this is an estimate based on the bitrate of the - // frames parsed so far. - int64_t GetDuration(); - - // Returns the offset of the first MP3 frame in the stream, or -1 of - // no MP3 frame has been detected yet. - int64_t GetMP3Offset(); - - // Returns true if we've seen the whole first frame of the MP3 stream, and - // therefore can make an estimate on the stream duration. - // Otherwise, returns false. - bool ParsedHeaders(); - - // Returns true if we know the exact duration of the MP3 stream; - // false otherwise. - bool HasExactDuration(); - - // Returns true if the parser needs more data for duration estimation. - bool NeedsData(); - // Assign the total lenght of this mp3 stream - void SetLength(int64_t aLength) { - MutexAutoLock mon(mLock); - mLength = aLength; - } -private: - - // Parses aBuffer, starting at offset 0. Returns the number of bytes - // parsed, relative to the start of the buffer. Note this may be - // greater than aLength if the headers in the buffer indicate that - // the frame or ID3 tag extends outside of aBuffer. Returns failure - // if too many non-MP3 bytes are parsed. - nsresult ParseBuffer(const uint8_t* aBuffer, - uint32_t aLength, - int64_t aStreamOffset, - uint32_t* aOutBytesRead); - - // A low-contention lock for protecting the parser results - Mutex mLock; - - // ID3 header parser. Keeps state between reads in case the header falls - // in between. - ID3Parser mID3Parser; - - // MP3 frame header parser. - MP3Parser mMP3Parser; - - // If we read |MAX_SKIPPED_BYTES| from the stream without finding any MP3 - // frames, we give up and report |NOT_MP3|. Here we track the cumulative size - // of any ID3 headers we've seen so big ID3 sections aren't counted towards - // skipped bytes. - uint32_t mTotalID3Size; - - // All fields below are protected by mLock - - // We keep stats on the size of all the frames we've seen, as well as how many - // so that we can estimate the duration of the rest of the stream. - uint64_t mTotalFrameSize; - uint64_t mFrameCount; - - // Offset of the last data parsed. This is the end offset of the last data - // block parsed, so it's the start offset we expect to get on the next - // call to Parse(). - uint64_t mOffset; - - // Total length of the stream in bytes. - int64_t mLength; - - // Offset of first MP3 frame in the bitstream. Has value -1 until the - // first MP3 frame is found. - int64_t mMP3Offset; - - // The exact number of frames in this stream, if we know it. -1 otherwise. - int64_t mNumFrames; - - // Number of audio samples per second and per frame. Fixed through the whole - // file. If we know these variables as well as the number of frames in the - // file, we can get an exact duration for the stream. - uint16_t mSamplesPerSecond; - uint16_t mSamplesPerFrame; - - // If the MP3 has a variable bitrate, then there *should* be metadata about - // the encoding in the first frame. We buffer the first frame here. - nsCString mFirstFrame; - - // While we are reading the first frame, this is the stream offset of the - // last byte of that frame. -1 at all other times. - int64_t mFirstFrameEnd; - - enum eIsMP3 { - MAYBE_MP3, // We're giving the stream the benefit of the doubt... - DEFINITELY_MP3, // We've hit at least one ID3 tag or MP3 frame. - NOT_MP3 // Not found any evidence of the stream being MP3. - }; - - eIsMP3 mIsMP3; - -}; - -} // namespace mozilla - -#endif diff --git a/dom/media/moz.build b/dom/media/moz.build index b3db05af1..772f27b35 100644 --- a/dom/media/moz.build +++ b/dom/media/moz.build @@ -127,7 +127,6 @@ EXPORTS += [ 'MediaTrackList.h', 'MP3Decoder.h', 'MP3Demuxer.h', - 'MP3FrameParser.h', 'NextFrameSeekTask.h', 'nsIDocumentActivity.h', 'PrincipalChangeObserver.h', @@ -236,7 +235,6 @@ UNIFIED_SOURCES += [ 'MediaTrackList.cpp', 'MP3Decoder.cpp', 'MP3Demuxer.cpp', - 'MP3FrameParser.cpp', 'NextFrameSeekTask.cpp', 'QueueObject.cpp', 'SeekJob.cpp', -- cgit v1.2.3 From 6b6a28ddb0d42a93e66578d598c08020b7e45985 Mon Sep 17 00:00:00 2001 From: trav90 Date: Sat, 8 Dec 2018 16:29:04 -0600 Subject: Remove separate MP3 name space It is no longer needed. --- dom/media/DecoderTraits.cpp | 2 +- dom/media/MP3Decoder.cpp | 2 +- dom/media/MP3Demuxer.cpp | 2 -- dom/media/MP3Demuxer.h | 2 -- dom/media/gtest/TestMP3Demuxer.cpp | 1 - 5 files changed, 2 insertions(+), 7 deletions(-) diff --git a/dom/media/DecoderTraits.cpp b/dom/media/DecoderTraits.cpp index 473ca9533..477ecf358 100644 --- a/dom/media/DecoderTraits.cpp +++ b/dom/media/DecoderTraits.cpp @@ -432,7 +432,7 @@ MediaDecoderReader* DecoderTraits::CreateReader(const nsACString& aType, Abstrac } else #endif if (IsMP3SupportedType(aType)) { - decoderReader = new MediaFormatReader(aDecoder, new mp3::MP3Demuxer(aDecoder->GetResource())); + decoderReader = new MediaFormatReader(aDecoder, new MP3Demuxer(aDecoder->GetResource())); } else if (IsAACSupportedType(aType)) { decoderReader = new MediaFormatReader(aDecoder, new ADTSDemuxer(aDecoder->GetResource())); diff --git a/dom/media/MP3Decoder.cpp b/dom/media/MP3Decoder.cpp index b71111e79..074a0866d 100644 --- a/dom/media/MP3Decoder.cpp +++ b/dom/media/MP3Decoder.cpp @@ -24,7 +24,7 @@ MP3Decoder::Clone(MediaDecoderOwner* aOwner) { MediaDecoderStateMachine* MP3Decoder::CreateStateMachine() { RefPtr reader = - new MediaFormatReader(this, new mp3::MP3Demuxer(GetResource())); + new MediaFormatReader(this, new MP3Demuxer(GetResource())); return new MediaDecoderStateMachine(this, reader); } diff --git a/dom/media/MP3Demuxer.cpp b/dom/media/MP3Demuxer.cpp index 7d478a41b..5a98cabfe 100644 --- a/dom/media/MP3Demuxer.cpp +++ b/dom/media/MP3Demuxer.cpp @@ -33,7 +33,6 @@ using mozilla::media::TimeIntervals; using mp4_demuxer::ByteReader; namespace mozilla { -namespace mp3 { // MP3Demuxer @@ -1338,5 +1337,4 @@ ID3Parser::ID3Header::Update(uint8_t c) { return IsValid(mPos++); } -} // namespace mp3 } // namespace mozilla diff --git a/dom/media/MP3Demuxer.h b/dom/media/MP3Demuxer.h index 03e67b0d9..5331c4d54 100644 --- a/dom/media/MP3Demuxer.h +++ b/dom/media/MP3Demuxer.h @@ -13,7 +13,6 @@ #include namespace mozilla { -namespace mp3 { class MP3TrackDemuxer; @@ -468,7 +467,6 @@ private: UniquePtr mInfo; }; -} // namespace mp3 } // namespace mozilla #endif diff --git a/dom/media/gtest/TestMP3Demuxer.cpp b/dom/media/gtest/TestMP3Demuxer.cpp index 8d2109f00..934acb60e 100644 --- a/dom/media/gtest/TestMP3Demuxer.cpp +++ b/dom/media/gtest/TestMP3Demuxer.cpp @@ -11,7 +11,6 @@ #include "MockMediaResource.h" using namespace mozilla; -using namespace mozilla::mp3; using media::TimeUnit; -- cgit v1.2.3 From 8a9362ff04fbdb35c41f5b0fe969b5ab45fb00d8 Mon Sep 17 00:00:00 2001 From: trav90 Date: Sat, 8 Dec 2018 16:30:00 -0600 Subject: Move the MP3 code to it's own directory --- dom/media/MP3Decoder.cpp | 50 -- dom/media/MP3Decoder.h | 29 - dom/media/MP3Demuxer.cpp | 1340 ------------------------------------------ dom/media/MP3Demuxer.h | 472 --------------- dom/media/moz.build | 5 +- dom/media/mp3/MP3Decoder.cpp | 50 ++ dom/media/mp3/MP3Decoder.h | 29 + dom/media/mp3/MP3Demuxer.cpp | 1340 ++++++++++++++++++++++++++++++++++++++++++ dom/media/mp3/MP3Demuxer.h | 472 +++++++++++++++ dom/media/mp3/moz.build | 17 + 10 files changed, 1909 insertions(+), 1895 deletions(-) delete mode 100644 dom/media/MP3Decoder.cpp delete mode 100644 dom/media/MP3Decoder.h delete mode 100644 dom/media/MP3Demuxer.cpp delete mode 100644 dom/media/MP3Demuxer.h create mode 100644 dom/media/mp3/MP3Decoder.cpp create mode 100644 dom/media/mp3/MP3Decoder.h create mode 100644 dom/media/mp3/MP3Demuxer.cpp create mode 100644 dom/media/mp3/MP3Demuxer.h create mode 100644 dom/media/mp3/moz.build diff --git a/dom/media/MP3Decoder.cpp b/dom/media/MP3Decoder.cpp deleted file mode 100644 index 074a0866d..000000000 --- a/dom/media/MP3Decoder.cpp +++ /dev/null @@ -1,50 +0,0 @@ - -/* -*- 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 "MP3Decoder.h" -#include "MediaDecoderStateMachine.h" -#include "MediaFormatReader.h" -#include "MP3Demuxer.h" -#include "PDMFactory.h" - -namespace mozilla { - -MediaDecoder* -MP3Decoder::Clone(MediaDecoderOwner* aOwner) { - if (!IsEnabled()) { - return nullptr; - } - return new MP3Decoder(aOwner); -} - -MediaDecoderStateMachine* -MP3Decoder::CreateStateMachine() { - RefPtr reader = - new MediaFormatReader(this, new MP3Demuxer(GetResource())); - return new MediaDecoderStateMachine(this, reader); -} - -/* static */ -bool -MP3Decoder::IsEnabled() { - RefPtr platform = new PDMFactory(); - return platform->SupportsMimeType(NS_LITERAL_CSTRING("audio/mpeg"), - /* DecoderDoctorDiagnostics* */ nullptr); -} - -/* static */ -bool MP3Decoder::CanHandleMediaType(const nsACString& aType, - const nsAString& aCodecs) -{ - if (aType.EqualsASCII("audio/mp3") || aType.EqualsASCII("audio/mpeg")) { - return IsEnabled() && - (aCodecs.IsEmpty() || aCodecs.EqualsASCII("mp3")); - } - return false; -} - -} // namespace mozilla diff --git a/dom/media/MP3Decoder.h b/dom/media/MP3Decoder.h deleted file mode 100644 index 887251065..000000000 --- a/dom/media/MP3Decoder.h +++ /dev/null @@ -1,29 +0,0 @@ -/* -*- 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 MP3Decoder_h_ -#define MP3Decoder_h_ - -#include "MediaDecoder.h" - -namespace mozilla { - -class MP3Decoder : public MediaDecoder { -public: - // MediaDecoder interface. - explicit MP3Decoder(MediaDecoderOwner* aOwner) : MediaDecoder(aOwner) {} - MediaDecoder* Clone(MediaDecoderOwner* aOwner) override; - MediaDecoderStateMachine* CreateStateMachine() override; - - // Returns true if the MP3 backend is preffed on, and we're running on a - // platform that is likely to have decoders for the format. - static bool IsEnabled(); - static bool CanHandleMediaType(const nsACString& aType, - const nsAString& aCodecs); -}; - -} // namespace mozilla - -#endif diff --git a/dom/media/MP3Demuxer.cpp b/dom/media/MP3Demuxer.cpp deleted file mode 100644 index 5a98cabfe..000000000 --- a/dom/media/MP3Demuxer.cpp +++ /dev/null @@ -1,1340 +0,0 @@ -/* -*- 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 "MP3Demuxer.h" - -#include -#include - -#include "mozilla/Assertions.h" -#include "mozilla/EndianUtils.h" -#include "nsAutoPtr.h" -#include "VideoUtils.h" -#include "TimeUnits.h" -#include "prenv.h" - -#ifdef PR_LOGGING -extern mozilla::LazyLogModule gMediaDemuxerLog; -#define MP3LOG(msg, ...) \ - MOZ_LOG(gMediaDemuxerLog, LogLevel::Debug, ("MP3Demuxer " msg, ##__VA_ARGS__)) -#define MP3LOGV(msg, ...) \ - MOZ_LOG(gMediaDemuxerLog, LogLevel::Verbose, ("MP3Demuxer " msg, ##__VA_ARGS__)) -#else -#define MP3LOG(msg, ...) -#define MP3LOGV(msg, ...) -#endif - -using mozilla::media::TimeUnit; -using mozilla::media::TimeInterval; -using mozilla::media::TimeIntervals; -using mp4_demuxer::ByteReader; - -namespace mozilla { - -// MP3Demuxer - -MP3Demuxer::MP3Demuxer(MediaResource* aSource) - : mSource(aSource) -{} - -bool -MP3Demuxer::InitInternal() { - if (!mTrackDemuxer) { - mTrackDemuxer = new MP3TrackDemuxer(mSource); - } - return mTrackDemuxer->Init(); -} - -RefPtr -MP3Demuxer::Init() { - if (!InitInternal()) { - MP3LOG("MP3Demuxer::Init() failure: waiting for data"); - - return InitPromise::CreateAndReject( - NS_ERROR_DOM_MEDIA_METADATA_ERR, __func__); - } - - MP3LOG("MP3Demuxer::Init() successful"); - return InitPromise::CreateAndResolve(NS_OK, __func__); -} - -bool -MP3Demuxer::HasTrackType(TrackInfo::TrackType aType) const { - return aType == TrackInfo::kAudioTrack; -} - -uint32_t -MP3Demuxer::GetNumberTracks(TrackInfo::TrackType aType) const { - return aType == TrackInfo::kAudioTrack ? 1u : 0u; -} - -already_AddRefed -MP3Demuxer::GetTrackDemuxer(TrackInfo::TrackType aType, uint32_t aTrackNumber) { - if (!mTrackDemuxer) { - return nullptr; - } - return RefPtr(mTrackDemuxer).forget(); -} - -bool -MP3Demuxer::IsSeekable() const { - return true; -} - -void -MP3Demuxer::NotifyDataArrived() { - // TODO: bug 1169485. - NS_WARNING("Unimplemented function NotifyDataArrived"); - MP3LOGV("NotifyDataArrived()"); -} - -void -MP3Demuxer::NotifyDataRemoved() { - // TODO: bug 1169485. - NS_WARNING("Unimplemented function NotifyDataRemoved"); - MP3LOGV("NotifyDataRemoved()"); -} - - -// MP3TrackDemuxer - -MP3TrackDemuxer::MP3TrackDemuxer(MediaResource* aSource) - : mSource(aSource) - , mOffset(0) - , mFirstFrameOffset(0) - , mNumParsedFrames(0) - , mFrameIndex(0) - , mTotalFrameLen(0) - , mSamplesPerFrame(0) - , mSamplesPerSecond(0) - , mChannels(0) -{ - Reset(); -} - -bool -MP3TrackDemuxer::Init() { - Reset(); - FastSeek(TimeUnit()); - // Read the first frame to fetch sample rate and other meta data. - RefPtr frame(GetNextFrame(FindFirstFrame())); - - MP3LOG("Init StreamLength()=%" PRId64 " first-frame-found=%d", - StreamLength(), !!frame); - - if (!frame) { - return false; - } - - // Rewind back to the stream begin to avoid dropping the first frame. - FastSeek(TimeUnit()); - - if (!mInfo) { - mInfo = MakeUnique(); - } - - mInfo->mRate = mSamplesPerSecond; - mInfo->mChannels = mChannels; - mInfo->mBitDepth = 16; - mInfo->mMimeType = "audio/mpeg"; - mInfo->mDuration = Duration().ToMicroseconds(); - - MP3LOG("Init mInfo={mRate=%d mChannels=%d mBitDepth=%d mDuration=%" PRId64 "}", - mInfo->mRate, mInfo->mChannels, mInfo->mBitDepth, - mInfo->mDuration); - - return mSamplesPerSecond && mChannels; -} - -media::TimeUnit -MP3TrackDemuxer::SeekPosition() const { - TimeUnit pos = Duration(mFrameIndex); - if (Duration() > TimeUnit()) { - pos = std::min(Duration(), pos); - } - return pos; -} - -const FrameParser::Frame& -MP3TrackDemuxer::LastFrame() const { - return mParser.PrevFrame(); -} - -RefPtr -MP3TrackDemuxer::DemuxSample() { - return GetNextFrame(FindNextFrame()); -} - -const ID3Parser::ID3Header& -MP3TrackDemuxer::ID3Header() const { - return mParser.ID3Header(); -} - -const FrameParser::VBRHeader& -MP3TrackDemuxer::VBRInfo() const { - return mParser.VBRInfo(); -} - -UniquePtr -MP3TrackDemuxer::GetInfo() const { - return mInfo->Clone(); -} - -RefPtr -MP3TrackDemuxer::Seek(TimeUnit aTime) { - // Efficiently seek to the position. - FastSeek(aTime); - // Correct seek position by scanning the next frames. - const TimeUnit seekTime = ScanUntil(aTime); - - return SeekPromise::CreateAndResolve(seekTime, __func__); -} - -TimeUnit -MP3TrackDemuxer::FastSeek(const TimeUnit& aTime) { - MP3LOG("FastSeek(%" PRId64 ") avgFrameLen=%f mNumParsedFrames=%" PRIu64 - " mFrameIndex=%" PRId64 " mOffset=%" PRIu64, - aTime.ToMicroseconds(), AverageFrameLength(), mNumParsedFrames, - mFrameIndex, mOffset); - - const auto& vbr = mParser.VBRInfo(); - if (!aTime.ToMicroseconds()) { - // Quick seek to the beginning of the stream. - mFrameIndex = 0; - } else if (vbr.IsTOCPresent() && Duration().ToMicroseconds() > 0) { - // Use TOC for more precise seeking. - const float durationFrac = static_cast(aTime.ToMicroseconds()) / - Duration().ToMicroseconds(); - mFrameIndex = FrameIndexFromOffset(vbr.Offset(durationFrac)); - } else if (AverageFrameLength() > 0) { - mFrameIndex = FrameIndexFromTime(aTime); - } - - mOffset = OffsetFromFrameIndex(mFrameIndex); - - if (mOffset > mFirstFrameOffset && StreamLength() > 0) { - mOffset = std::min(StreamLength() - 1, mOffset); - } - - mParser.EndFrameSession(); - - MP3LOG("FastSeek End TOC=%d avgFrameLen=%f mNumParsedFrames=%" PRIu64 - " mFrameIndex=%" PRId64 " mFirstFrameOffset=%llu mOffset=%" PRIu64 - " SL=%llu NumBytes=%u", - vbr.IsTOCPresent(), AverageFrameLength(), mNumParsedFrames, mFrameIndex, - mFirstFrameOffset, mOffset, StreamLength(), vbr.NumBytes().valueOr(0)); - - return Duration(mFrameIndex); -} - -TimeUnit -MP3TrackDemuxer::ScanUntil(const TimeUnit& aTime) { - MP3LOG("ScanUntil(%" PRId64 ") avgFrameLen=%f mNumParsedFrames=%" PRIu64 - " mFrameIndex=%" PRId64 " mOffset=%" PRIu64, - aTime.ToMicroseconds(), AverageFrameLength(), mNumParsedFrames, - mFrameIndex, mOffset); - - if (!aTime.ToMicroseconds()) { - return FastSeek(aTime); - } - - if (Duration(mFrameIndex) > aTime) { - FastSeek(aTime); - } - - if (Duration(mFrameIndex + 1) > aTime) { - return SeekPosition(); - } - - MediaByteRange nextRange = FindNextFrame(); - while (SkipNextFrame(nextRange) && Duration(mFrameIndex + 1) < aTime) { - nextRange = FindNextFrame(); - MP3LOGV("ScanUntil* avgFrameLen=%f mNumParsedFrames=%" PRIu64 - " mFrameIndex=%" PRId64 " mOffset=%" PRIu64 " Duration=%" PRId64, - AverageFrameLength(), mNumParsedFrames, - mFrameIndex, mOffset, Duration(mFrameIndex + 1).ToMicroseconds()); - } - - MP3LOG("ScanUntil End avgFrameLen=%f mNumParsedFrames=%" PRIu64 - " mFrameIndex=%" PRId64 " mOffset=%" PRIu64, - AverageFrameLength(), mNumParsedFrames, mFrameIndex, mOffset); - - return SeekPosition(); -} - -RefPtr -MP3TrackDemuxer::GetSamples(int32_t aNumSamples) { - MP3LOGV("GetSamples(%d) Begin mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64 - " mFrameIndex=%" PRId64 " mTotalFrameLen=%" PRIu64 " mSamplesPerFrame=%d " - "mSamplesPerSecond=%d mChannels=%d", - aNumSamples, mOffset, mNumParsedFrames, mFrameIndex, mTotalFrameLen, - mSamplesPerFrame, mSamplesPerSecond, mChannels); - - if (!aNumSamples) { - return SamplesPromise::CreateAndReject( - NS_ERROR_DOM_MEDIA_DEMUXER_ERR, __func__); - } - - RefPtr frames = new SamplesHolder(); - - while (aNumSamples--) { - RefPtr frame(GetNextFrame(FindNextFrame())); - if (!frame) { - break; - } - - frames->mSamples.AppendElement(frame); - } - - MP3LOGV("GetSamples() End mSamples.Size()=%d aNumSamples=%d mOffset=%" PRIu64 - " mNumParsedFrames=%" PRIu64 " mFrameIndex=%" PRId64 - " mTotalFrameLen=%" PRIu64 " mSamplesPerFrame=%d mSamplesPerSecond=%d " - "mChannels=%d", - frames->mSamples.Length(), aNumSamples, mOffset, mNumParsedFrames, - mFrameIndex, mTotalFrameLen, mSamplesPerFrame, mSamplesPerSecond, - mChannels); - - if (frames->mSamples.IsEmpty()) { - return SamplesPromise::CreateAndReject( - NS_ERROR_DOM_MEDIA_END_OF_STREAM, __func__); - } - return SamplesPromise::CreateAndResolve(frames, __func__); -} - -void -MP3TrackDemuxer::Reset() { - MP3LOG("Reset()"); - - FastSeek(TimeUnit()); - mParser.Reset(); -} - -RefPtr -MP3TrackDemuxer::SkipToNextRandomAccessPoint(TimeUnit aTimeThreshold) { - // Will not be called for audio-only resources. - return SkipAccessPointPromise::CreateAndReject( - SkipFailureHolder(NS_ERROR_DOM_MEDIA_DEMUXER_ERR, 0), __func__); -} - -int64_t -MP3TrackDemuxer::GetResourceOffset() const { - return mOffset; -} - -TimeIntervals -MP3TrackDemuxer::GetBuffered() { - AutoPinned stream(mSource.GetResource()); - TimeIntervals buffered; - - if (Duration() > TimeUnit() && stream->IsDataCachedToEndOfResource(0)) { - // Special case completely cached files. This also handles local files. - buffered += TimeInterval(TimeUnit(), Duration()); - MP3LOGV("buffered = [[%" PRId64 ", %" PRId64 "]]", - TimeUnit().ToMicroseconds(), Duration().ToMicroseconds()); - return buffered; - } - - MediaByteRangeSet ranges; - nsresult rv = stream->GetCachedRanges(ranges); - NS_ENSURE_SUCCESS(rv, buffered); - - for (const auto& range: ranges) { - if (range.IsEmpty()) { - continue; - } - TimeUnit start = Duration(FrameIndexFromOffset(range.mStart)); - TimeUnit end = Duration(FrameIndexFromOffset(range.mEnd)); - MP3LOGV("buffered += [%" PRId64 ", %" PRId64 "]", - start.ToMicroseconds(), end.ToMicroseconds()); - buffered += TimeInterval(start, end); - } - - return buffered; -} - -int64_t -MP3TrackDemuxer::StreamLength() const { - return mSource.GetLength(); -} - -TimeUnit -MP3TrackDemuxer::Duration() const { - if (!mNumParsedFrames) { - return TimeUnit::FromMicroseconds(-1); - } - - int64_t numFrames = 0; - const auto numAudioFrames = mParser.VBRInfo().NumAudioFrames(); - if (mParser.VBRInfo().IsValid() && numAudioFrames.valueOr(0) + 1 > 1) { - // VBR headers don't include the VBR header frame. - numFrames = numAudioFrames.value() + 1; - } else { - const int64_t streamLen = StreamLength(); - if (streamLen < 0) { - // Unknown length, we can't estimate duration. - return TimeUnit::FromMicroseconds(-1); - } - if (AverageFrameLength() > 0) { - numFrames = (streamLen - mFirstFrameOffset) / AverageFrameLength(); - } - } - return Duration(numFrames); -} - -TimeUnit -MP3TrackDemuxer::Duration(int64_t aNumFrames) const { - if (!mSamplesPerSecond) { - return TimeUnit::FromMicroseconds(-1); - } - - const double usPerFrame = USECS_PER_S * mSamplesPerFrame / mSamplesPerSecond; - return TimeUnit::FromMicroseconds(aNumFrames * usPerFrame); -} - -MediaByteRange -MP3TrackDemuxer::FindFirstFrame() { - static const int MIN_SUCCESSIVE_FRAMES = 4; - - MediaByteRange candidateFrame = FindNextFrame(); - int numSuccFrames = candidateFrame.Length() > 0; - MediaByteRange currentFrame = candidateFrame; - MP3LOGV("FindFirst() first candidate frame: mOffset=%" PRIu64 " Length()=%" PRIu64, - candidateFrame.mStart, candidateFrame.Length()); - - while (candidateFrame.Length() && numSuccFrames < MIN_SUCCESSIVE_FRAMES) { - mParser.EndFrameSession(); - mOffset = currentFrame.mEnd; - const MediaByteRange prevFrame = currentFrame; - - // FindNextFrame() here will only return frames consistent with our candidate frame. - currentFrame = FindNextFrame(); - numSuccFrames += currentFrame.Length() > 0; - // Multiple successive false positives, which wouldn't be caught by the consistency - // checks alone, can be detected by wrong alignment (non-zero gap between frames). - const int64_t frameSeparation = currentFrame.mStart - prevFrame.mEnd; - - if (!currentFrame.Length() || frameSeparation != 0) { - MP3LOGV("FindFirst() not enough successive frames detected, " - "rejecting candidate frame: successiveFrames=%d, last Length()=%" PRIu64 - ", last frameSeparation=%" PRId64, numSuccFrames, currentFrame.Length(), - frameSeparation); - - mParser.ResetFrameData(); - mOffset = candidateFrame.mStart + 1; - candidateFrame = FindNextFrame(); - numSuccFrames = candidateFrame.Length() > 0; - currentFrame = candidateFrame; - MP3LOGV("FindFirst() new candidate frame: mOffset=%" PRIu64 " Length()=%" PRIu64, - candidateFrame.mStart, candidateFrame.Length()); - } - } - - if (numSuccFrames >= MIN_SUCCESSIVE_FRAMES) { - MP3LOG("FindFirst() accepting candidate frame: " - "successiveFrames=%d", numSuccFrames); - } else { - MP3LOG("FindFirst() no suitable first frame found"); - } - return candidateFrame; -} - -static bool -VerifyFrameConsistency( - const FrameParser::Frame& aFrame1, const FrameParser::Frame& aFrame2) { - const auto& h1 = aFrame1.Header(); - const auto& h2 = aFrame2.Header(); - - return h1.IsValid() && h2.IsValid() && - h1.Layer() == h2.Layer() && - h1.SlotSize() == h2.SlotSize() && - h1.SamplesPerFrame() == h2.SamplesPerFrame() && - h1.Channels() == h2.Channels() && - h1.SampleRate() == h2.SampleRate() && - h1.RawVersion() == h2.RawVersion() && - h1.RawProtection() == h2.RawProtection(); -} - -MediaByteRange -MP3TrackDemuxer::FindNextFrame() { - static const int BUFFER_SIZE = 64; - static const int MAX_SKIPPED_BYTES = 1024 * BUFFER_SIZE; - - MP3LOGV("FindNext() Begin mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64 - " mFrameIndex=%" PRId64 " mTotalFrameLen=%" PRIu64 - " mSamplesPerFrame=%d mSamplesPerSecond=%d mChannels=%d", - mOffset, mNumParsedFrames, mFrameIndex, mTotalFrameLen, - mSamplesPerFrame, mSamplesPerSecond, mChannels); - - uint8_t buffer[BUFFER_SIZE]; - int32_t read = 0; - - bool foundFrame = false; - int64_t frameHeaderOffset = 0; - - // Check whether we've found a valid MPEG frame. - while (!foundFrame) { - if ((!mParser.FirstFrame().Length() && - mOffset - mParser.ID3Header().Size() > MAX_SKIPPED_BYTES) || - (read = Read(buffer, mOffset, BUFFER_SIZE)) == 0) { - MP3LOG("FindNext() EOS or exceeded MAX_SKIPPED_BYTES without a frame"); - // This is not a valid MPEG audio stream or we've reached EOS, give up. - break; - } - - ByteReader reader(buffer, read); - uint32_t bytesToSkip = 0; - foundFrame = mParser.Parse(&reader, &bytesToSkip); - frameHeaderOffset = mOffset + reader.Offset() - FrameParser::FrameHeader::SIZE; - - // If we've found neither an MPEG frame header nor an ID3v2 tag, - // the reader shouldn't have any bytes remaining. - MOZ_ASSERT(foundFrame || bytesToSkip || !reader.Remaining()); - - if (foundFrame && mParser.FirstFrame().Length() && - !VerifyFrameConsistency(mParser.FirstFrame(), mParser.CurrentFrame())) { - // We've likely hit a false-positive, ignore it and proceed with the - // search for the next valid frame. - foundFrame = false; - mOffset = frameHeaderOffset + 1; - mParser.EndFrameSession(); - } else { - // Advance mOffset by the amount of bytes read and if necessary, - // skip an ID3v2 tag which stretches beyond the current buffer. - NS_ENSURE_TRUE(mOffset + read + bytesToSkip > mOffset, - MediaByteRange(0, 0)); - mOffset += read + bytesToSkip; - } - } - - if (!foundFrame || !mParser.CurrentFrame().Length()) { - MP3LOG("FindNext() Exit foundFrame=%d mParser.CurrentFrame().Length()=%d ", - foundFrame, mParser.CurrentFrame().Length()); - return { 0, 0 }; - } - - MP3LOGV("FindNext() End mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64 - " mFrameIndex=%" PRId64 " frameHeaderOffset=%d" - " mTotalFrameLen=%" PRIu64 " mSamplesPerFrame=%d mSamplesPerSecond=%d" - " mChannels=%d", - mOffset, mNumParsedFrames, mFrameIndex, frameHeaderOffset, - mTotalFrameLen, mSamplesPerFrame, mSamplesPerSecond, mChannels); - - return { frameHeaderOffset, frameHeaderOffset + mParser.CurrentFrame().Length() }; -} - -bool -MP3TrackDemuxer::SkipNextFrame(const MediaByteRange& aRange) { - if (!mNumParsedFrames || !aRange.Length()) { - // We can't skip the first frame, since it could contain VBR headers. - RefPtr frame(GetNextFrame(aRange)); - return frame; - } - - UpdateState(aRange); - - MP3LOGV("SkipNext() End mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64 - " mFrameIndex=%" PRId64 " mTotalFrameLen=%" PRIu64 - " mSamplesPerFrame=%d mSamplesPerSecond=%d mChannels=%d", - mOffset, mNumParsedFrames, mFrameIndex, mTotalFrameLen, - mSamplesPerFrame, mSamplesPerSecond, mChannels); - - return true; -} - -already_AddRefed -MP3TrackDemuxer::GetNextFrame(const MediaByteRange& aRange) { - MP3LOG("GetNext() Begin({mStart=%" PRId64 " Length()=%" PRId64 "})", - aRange.mStart, aRange.Length()); - if (!aRange.Length()) { - return nullptr; - } - - RefPtr frame = new MediaRawData(); - frame->mOffset = aRange.mStart; - - nsAutoPtr frameWriter(frame->CreateWriter()); - if (!frameWriter->SetSize(aRange.Length())) { - MP3LOG("GetNext() Exit failed to allocated media buffer"); - return nullptr; - } - - const uint32_t read = Read(frameWriter->Data(), frame->mOffset, frame->Size()); - - if (read != aRange.Length()) { - MP3LOG("GetNext() Exit read=%u frame->Size()=%u", read, frame->Size()); - return nullptr; - } - - UpdateState(aRange); - - frame->mTime = Duration(mFrameIndex - 1).ToMicroseconds(); - frame->mDuration = Duration(1).ToMicroseconds(); - frame->mTimecode = frame->mTime; - frame->mKeyframe = true; - - MOZ_ASSERT(frame->mTime >= 0); - MOZ_ASSERT(frame->mDuration > 0); - - if (mNumParsedFrames == 1) { - // First frame parsed, let's read VBR info if available. - ByteReader reader(frame->Data(), frame->Size()); - mParser.ParseVBRHeader(&reader); - mFirstFrameOffset = frame->mOffset; - } - - MP3LOGV("GetNext() End mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64 - " mFrameIndex=%" PRId64 " mTotalFrameLen=%" PRIu64 - " mSamplesPerFrame=%d mSamplesPerSecond=%d mChannels=%d", - mOffset, mNumParsedFrames, mFrameIndex, mTotalFrameLen, - mSamplesPerFrame, mSamplesPerSecond, mChannels); - - return frame.forget(); -} - -int64_t -MP3TrackDemuxer::OffsetFromFrameIndex(int64_t aFrameIndex) const { - int64_t offset = 0; - const auto& vbr = mParser.VBRInfo(); - - if (vbr.IsComplete()) { - offset = mFirstFrameOffset + aFrameIndex * vbr.NumBytes().value() / - vbr.NumAudioFrames().value(); - } else if (AverageFrameLength() > 0) { - offset = mFirstFrameOffset + aFrameIndex * AverageFrameLength(); - } - - MP3LOGV("OffsetFromFrameIndex(%" PRId64 ") -> %" PRId64, aFrameIndex, offset); - return std::max(mFirstFrameOffset, offset); -} - -int64_t -MP3TrackDemuxer::FrameIndexFromOffset(int64_t aOffset) const { - int64_t frameIndex = 0; - const auto& vbr = mParser.VBRInfo(); - - if (vbr.IsComplete()) { - frameIndex = static_cast(aOffset - mFirstFrameOffset) / - vbr.NumBytes().value() * vbr.NumAudioFrames().value(); - frameIndex = std::min(vbr.NumAudioFrames().value(), frameIndex); - } else if (AverageFrameLength() > 0) { - frameIndex = (aOffset - mFirstFrameOffset) / AverageFrameLength(); - } - - MP3LOGV("FrameIndexFromOffset(%" PRId64 ") -> %" PRId64, aOffset, frameIndex); - return std::max(0, frameIndex); -} - -int64_t -MP3TrackDemuxer::FrameIndexFromTime(const media::TimeUnit& aTime) const { - int64_t frameIndex = 0; - if (mSamplesPerSecond > 0 && mSamplesPerFrame > 0) { - frameIndex = aTime.ToSeconds() * mSamplesPerSecond / mSamplesPerFrame - 1; - } - - MP3LOGV("FrameIndexFromOffset(%fs) -> %" PRId64, aTime.ToSeconds(), frameIndex); - return std::max(0, frameIndex); -} - -void -MP3TrackDemuxer::UpdateState(const MediaByteRange& aRange) { - // Prevent overflow. - if (mTotalFrameLen + aRange.Length() < mTotalFrameLen) { - // These variables have a linear dependency and are only used to derive the - // average frame length. - mTotalFrameLen /= 2; - mNumParsedFrames /= 2; - } - - // Full frame parsed, move offset to its end. - mOffset = aRange.mEnd; - - mTotalFrameLen += aRange.Length(); - - if (!mSamplesPerFrame) { - mSamplesPerFrame = mParser.CurrentFrame().Header().SamplesPerFrame(); - mSamplesPerSecond = mParser.CurrentFrame().Header().SampleRate(); - mChannels = mParser.CurrentFrame().Header().Channels(); - } - - ++mNumParsedFrames; - ++mFrameIndex; - MOZ_ASSERT(mFrameIndex > 0); - - // Prepare the parser for the next frame parsing session. - mParser.EndFrameSession(); -} - -int32_t -MP3TrackDemuxer::Read(uint8_t* aBuffer, int64_t aOffset, int32_t aSize) { - MP3LOGV("MP3TrackDemuxer::Read(%p %" PRId64 " %d)", aBuffer, aOffset, aSize); - - const int64_t streamLen = StreamLength(); - if (mInfo && streamLen > 0) { - // Prevent blocking reads after successful initialization. - aSize = std::min(aSize, streamLen - aOffset); - } - - uint32_t read = 0; - MP3LOGV("MP3TrackDemuxer::Read -> ReadAt(%d)", aSize); - const nsresult rv = mSource.ReadAt(aOffset, reinterpret_cast(aBuffer), - static_cast(aSize), &read); - NS_ENSURE_SUCCESS(rv, 0); - return static_cast(read); -} - -double -MP3TrackDemuxer::AverageFrameLength() const { - if (mNumParsedFrames) { - return static_cast(mTotalFrameLen) / mNumParsedFrames; - } - const auto& vbr = mParser.VBRInfo(); - if (vbr.IsComplete() && vbr.NumAudioFrames().value() + 1) { - return static_cast(vbr.NumBytes().value()) / - (vbr.NumAudioFrames().value() + 1); - } - return 0.0; -} - -// FrameParser - -namespace frame_header { -// FrameHeader mRaw byte offsets. -static const int SYNC1 = 0; -static const int SYNC2_VERSION_LAYER_PROTECTION = 1; -static const int BITRATE_SAMPLERATE_PADDING_PRIVATE = 2; -static const int CHANNELMODE_MODEEXT_COPY_ORIG_EMPH = 3; -} // namespace frame_header - -FrameParser::FrameParser() -{ -} - -void -FrameParser::Reset() { - mID3Parser.Reset(); - mFrame.Reset(); -} - -void -FrameParser::ResetFrameData() { - mFrame.Reset(); - mFirstFrame.Reset(); - mPrevFrame.Reset(); -} - -void -FrameParser::EndFrameSession() { - if (!mID3Parser.Header().IsValid()) { - // Reset ID3 tags only if we have not parsed a valid ID3 header yet. - mID3Parser.Reset(); - } - mPrevFrame = mFrame; - mFrame.Reset(); -} - -const FrameParser::Frame& -FrameParser::CurrentFrame() const { - return mFrame; -} - -const FrameParser::Frame& -FrameParser::PrevFrame() const { - return mPrevFrame; -} - -const FrameParser::Frame& -FrameParser::FirstFrame() const { - return mFirstFrame; -} - -const ID3Parser::ID3Header& -FrameParser::ID3Header() const { - return mID3Parser.Header(); -} - -const FrameParser::VBRHeader& -FrameParser::VBRInfo() const { - return mVBRHeader; -} - -bool -FrameParser::Parse(ByteReader* aReader, uint32_t* aBytesToSkip) { - MOZ_ASSERT(aReader && aBytesToSkip); - *aBytesToSkip = 0; - - if (!mID3Parser.Header().Size() && !mFirstFrame.Length()) { - // No MP3 frames have been parsed yet, look for ID3v2 headers at file begin. - // ID3v1 tags may only be at file end. - // TODO: should we try to read ID3 tags at end of file/mid-stream, too? - const size_t prevReaderOffset = aReader->Offset(); - const uint32_t tagSize = mID3Parser.Parse(aReader); - if (tagSize) { - // ID3 tag found, skip past it. - const uint32_t skipSize = tagSize - ID3Parser::ID3Header::SIZE; - - if (skipSize > aReader->Remaining()) { - // Skipping across the ID3v2 tag would take us past the end of the buffer, therefore we - // return immediately and let the calling function handle skipping the rest of the tag. - MP3LOGV("ID3v2 tag detected, size=%d," - " needing to skip %d bytes past the current buffer", - tagSize, skipSize - aReader->Remaining()); - *aBytesToSkip = skipSize - aReader->Remaining(); - return false; - } - MP3LOGV("ID3v2 tag detected, size=%d", tagSize); - aReader->Read(skipSize); - } else { - // No ID3v2 tag found, rewinding reader in order to search for a MPEG frame header. - aReader->Seek(prevReaderOffset); - } - } - - while (aReader->CanRead8() && !mFrame.ParseNext(aReader->ReadU8())) { } - - if (mFrame.Length()) { - // MP3 frame found. - if (!mFirstFrame.Length()) { - mFirstFrame = mFrame; - } - // Indicate success. - return true; - } - return false; -} - -// FrameParser::Header - -FrameParser::FrameHeader::FrameHeader() -{ - Reset(); -} - -uint8_t -FrameParser::FrameHeader::Sync1() const { - return mRaw[frame_header::SYNC1]; -} - -uint8_t -FrameParser::FrameHeader::Sync2() const { - return 0x7 & mRaw[frame_header::SYNC2_VERSION_LAYER_PROTECTION] >> 5; -} - -uint8_t -FrameParser::FrameHeader::RawVersion() const { - return 0x3 & mRaw[frame_header::SYNC2_VERSION_LAYER_PROTECTION] >> 3; -} - -uint8_t -FrameParser::FrameHeader::RawLayer() const { - return 0x3 & mRaw[frame_header::SYNC2_VERSION_LAYER_PROTECTION] >> 1; -} - -uint8_t -FrameParser::FrameHeader::RawProtection() const { - return 0x1 & mRaw[frame_header::SYNC2_VERSION_LAYER_PROTECTION] >> 6; -} - -uint8_t -FrameParser::FrameHeader::RawBitrate() const { - return 0xF & mRaw[frame_header::BITRATE_SAMPLERATE_PADDING_PRIVATE] >> 4; -} - -uint8_t -FrameParser::FrameHeader::RawSampleRate() const { - return 0x3 & mRaw[frame_header::BITRATE_SAMPLERATE_PADDING_PRIVATE] >> 2; -} - -uint8_t -FrameParser::FrameHeader::Padding() const { - return 0x1 & mRaw[frame_header::BITRATE_SAMPLERATE_PADDING_PRIVATE] >> 1; -} - -uint8_t -FrameParser::FrameHeader::Private() const { - return 0x1 & mRaw[frame_header::BITRATE_SAMPLERATE_PADDING_PRIVATE]; -} - -uint8_t -FrameParser::FrameHeader::RawChannelMode() const { - return 0x3 & mRaw[frame_header::CHANNELMODE_MODEEXT_COPY_ORIG_EMPH] >> 6; -} - -int32_t -FrameParser::FrameHeader::Layer() const { - static const uint8_t LAYERS[4] = { 0, 3, 2, 1 }; - - return LAYERS[RawLayer()]; -} - -int32_t -FrameParser::FrameHeader::SampleRate() const { - // Sample rates - use [version][srate] - static const uint16_t SAMPLE_RATE[4][4] = { - { 11025, 12000, 8000, 0 }, // MPEG 2.5 - { 0, 0, 0, 0 }, // Reserved - { 22050, 24000, 16000, 0 }, // MPEG 2 - { 44100, 48000, 32000, 0 } // MPEG 1 - }; - - return SAMPLE_RATE[RawVersion()][RawSampleRate()]; -} - -int32_t -FrameParser::FrameHeader::Channels() const { - // 3 is single channel (mono), any other value is some variant of dual - // channel. - return RawChannelMode() == 3 ? 1 : 2; -} - -int32_t -FrameParser::FrameHeader::SamplesPerFrame() const { - // Samples per frame - use [version][layer] - static const uint16_t FRAME_SAMPLE[4][4] = { - // Layer 3 2 1 Version - { 0, 576, 1152, 384 }, // 2.5 - { 0, 0, 0, 0 }, // Reserved - { 0, 576, 1152, 384 }, // 2 - { 0, 1152, 1152, 384 } // 1 - }; - - return FRAME_SAMPLE[RawVersion()][RawLayer()]; -} - -int32_t -FrameParser::FrameHeader::Bitrate() const { - // Bitrates - use [version][layer][bitrate] - static const uint16_t BITRATE[4][4][16] = { - { // Version 2.5 - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Reserved - { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }, // Layer 3 - { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }, // Layer 2 - { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0 } // Layer 1 - }, - { // Reserved - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Invalid - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Invalid - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Invalid - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } // Invalid - }, - { // Version 2 - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Reserved - { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }, // Layer 3 - { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }, // Layer 2 - { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0 } // Layer 1 - }, - { // Version 1 - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Reserved - { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0 }, // Layer 3 - { 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0 }, // Layer 2 - { 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0 }, // Layer 1 - } - }; - - return 1000 * BITRATE[RawVersion()][RawLayer()][RawBitrate()]; -} - -int32_t -FrameParser::FrameHeader::SlotSize() const { - // Slot size (MPEG unit of measurement) - use [layer] - static const uint8_t SLOT_SIZE[4] = { 0, 1, 1, 4 }; // Rsvd, 3, 2, 1 - - return SLOT_SIZE[RawLayer()]; -} - -bool -FrameParser::FrameHeader::ParseNext(uint8_t c) { - if (!Update(c)) { - Reset(); - if (!Update(c)) { - Reset(); - } - } - return IsValid(); -} - -bool -FrameParser::FrameHeader::IsValid(int aPos) const { - if (aPos >= SIZE) { - return true; - } - if (aPos == frame_header::SYNC1) { - return Sync1() == 0xFF; - } - if (aPos == frame_header::SYNC2_VERSION_LAYER_PROTECTION) { - return Sync2() == 7 && - RawVersion() != 1 && - Layer() == 3; - } - if (aPos == frame_header::BITRATE_SAMPLERATE_PADDING_PRIVATE) { - return RawBitrate() != 0xF && RawBitrate() != 0 && - RawSampleRate() != 3; - } - return true; -} - -bool -FrameParser::FrameHeader::IsValid() const { - return mPos >= SIZE; -} - -void -FrameParser::FrameHeader::Reset() { - mPos = 0; -} - -bool -FrameParser::FrameHeader::Update(uint8_t c) { - if (mPos < SIZE) { - mRaw[mPos] = c; - } - return IsValid(mPos++); -} - -// FrameParser::VBRHeader - -namespace vbr_header { -static const char* TYPE_STR[3] = {"NONE", "XING", "VBRI"}; -static const uint32_t TOC_SIZE = 100; -} // namespace vbr_header - -FrameParser::VBRHeader::VBRHeader() - : mType(NONE) -{ -} - -FrameParser::VBRHeader::VBRHeaderType -FrameParser::VBRHeader::Type() const { - return mType; -} - -const Maybe& -FrameParser::VBRHeader::NumAudioFrames() const { - return mNumAudioFrames; -} - -const Maybe& -FrameParser::VBRHeader::NumBytes() const { - return mNumBytes; -} - -const Maybe& -FrameParser::VBRHeader::Scale() const { - return mScale; -} - -bool -FrameParser::VBRHeader::IsTOCPresent() const { - return mTOC.size() == vbr_header::TOC_SIZE; -} - -bool -FrameParser::VBRHeader::IsValid() const { - return mType != NONE; -} - -bool -FrameParser::VBRHeader::IsComplete() const { - return IsValid() && - mNumAudioFrames.valueOr(0) > 0 && - mNumBytes.valueOr(0) > 0 && - // We don't care about the scale for any computations here. - // mScale < 101 && - true; -} - -int64_t -FrameParser::VBRHeader::Offset(float aDurationFac) const { - if (!IsTOCPresent()) { - return -1; - } - - // Constrain the duration percentage to [0, 99]. - const float durationPer = 100.0f * std::min(0.99f, std::max(0.0f, aDurationFac)); - const size_t fullPer = durationPer; - const float rest = durationPer - fullPer; - - MOZ_ASSERT(fullPer < mTOC.size()); - int64_t offset = mTOC.at(fullPer); - - if (rest > 0.0 && fullPer + 1 < mTOC.size()) { - offset += rest * (mTOC.at(fullPer + 1) - offset); - } - - return offset; -} - -bool -FrameParser::VBRHeader::ParseXing(ByteReader* aReader) { - static const uint32_t XING_TAG = BigEndian::readUint32("Xing"); - static const uint32_t INFO_TAG = BigEndian::readUint32("Info"); - - enum Flags { - NUM_FRAMES = 0x01, - NUM_BYTES = 0x02, - TOC = 0x04, - VBR_SCALE = 0x08 - }; - - MOZ_ASSERT(aReader); - const size_t prevReaderOffset = aReader->Offset(); - - // We have to search for the Xing header as its position can change. - while (aReader->CanRead32() && - aReader->PeekU32() != XING_TAG && aReader->PeekU32() != INFO_TAG) { - aReader->Read(1); - } - - if (aReader->CanRead32()) { - // Skip across the VBR header ID tag. - aReader->ReadU32(); - mType = XING; - } - uint32_t flags = 0; - if (aReader->CanRead32()) { - flags = aReader->ReadU32(); - } - if (flags & NUM_FRAMES && aReader->CanRead32()) { - mNumAudioFrames = Some(aReader->ReadU32()); - } - if (flags & NUM_BYTES && aReader->CanRead32()) { - mNumBytes = Some(aReader->ReadU32()); - } - if (flags & TOC && aReader->Remaining() >= vbr_header::TOC_SIZE) { - if (!mNumBytes) { - // We don't have the stream size to calculate offsets, skip the TOC. - aReader->Read(vbr_header::TOC_SIZE); - } else { - mTOC.clear(); - mTOC.reserve(vbr_header::TOC_SIZE); - for (size_t i = 0; i < vbr_header::TOC_SIZE; ++i) { - mTOC.push_back(1.0f / 256.0f * aReader->ReadU8() * mNumBytes.value()); - } - } - } - if (flags & VBR_SCALE && aReader->CanRead32()) { - mScale = Some(aReader->ReadU32()); - } - - aReader->Seek(prevReaderOffset); - return mType == XING; -} - -bool -FrameParser::VBRHeader::ParseVBRI(ByteReader* aReader) { - static const uint32_t TAG = BigEndian::readUint32("VBRI"); - static const uint32_t OFFSET = 32 + FrameParser::FrameHeader::SIZE; - static const uint32_t FRAME_COUNT_OFFSET = OFFSET + 14; - static const uint32_t MIN_FRAME_SIZE = OFFSET + 26; - - MOZ_ASSERT(aReader); - // ParseVBRI assumes that the ByteReader offset points to the beginning of a frame, - // therefore as a simple check, we look for the presence of a frame sync at that position. - MOZ_ASSERT((aReader->PeekU16() & 0xFFE0) == 0xFFE0); - const size_t prevReaderOffset = aReader->Offset(); - - // VBRI have a fixed relative position, so let's check for it there. - if (aReader->Remaining() > MIN_FRAME_SIZE) { - aReader->Seek(prevReaderOffset + OFFSET); - if (aReader->ReadU32() == TAG) { - aReader->Seek(prevReaderOffset + FRAME_COUNT_OFFSET); - mNumAudioFrames = Some(aReader->ReadU32()); - mType = VBRI; - aReader->Seek(prevReaderOffset); - return true; - } - } - aReader->Seek(prevReaderOffset); - return false; -} - -bool -FrameParser::VBRHeader::Parse(ByteReader* aReader) { - const bool rv = ParseVBRI(aReader) || ParseXing(aReader); - if (rv) { - MP3LOG("VBRHeader::Parse found valid VBR/CBR header: type=%s" - " NumAudioFrames=%u NumBytes=%u Scale=%u TOC-size=%u", - vbr_header::TYPE_STR[Type()], NumAudioFrames().valueOr(0), - NumBytes().valueOr(0), Scale().valueOr(0), mTOC.size()); - } - return rv; -} - -// FrameParser::Frame - -void -FrameParser::Frame::Reset() { - mHeader.Reset(); -} - -int32_t -FrameParser::Frame::Length() const { - if (!mHeader.IsValid() || !mHeader.SampleRate()) { - return 0; - } - - const float bitsPerSample = mHeader.SamplesPerFrame() / 8.0f; - const int32_t frameLen = bitsPerSample * mHeader.Bitrate() / - mHeader.SampleRate() + - mHeader.Padding() * mHeader.SlotSize(); - return frameLen; -} - -bool -FrameParser::Frame::ParseNext(uint8_t c) { - return mHeader.ParseNext(c); -} - -const FrameParser::FrameHeader& -FrameParser::Frame::Header() const { - return mHeader; -} - -bool -FrameParser::ParseVBRHeader(ByteReader* aReader) { - return mVBRHeader.Parse(aReader); -} - -// ID3Parser - -// Constants -namespace id3_header { -static const int ID_LEN = 3; -static const int VERSION_LEN = 2; -static const int FLAGS_LEN = 1; -static const int SIZE_LEN = 4; - -static const int ID_END = ID_LEN; -static const int VERSION_END = ID_END + VERSION_LEN; -static const int FLAGS_END = VERSION_END + FLAGS_LEN; -static const int SIZE_END = FLAGS_END + SIZE_LEN; - -static const uint8_t ID[ID_LEN] = {'I', 'D', '3'}; - -static const uint8_t MIN_MAJOR_VER = 2; -static const uint8_t MAX_MAJOR_VER = 4; -} // namespace id3_header - -uint32_t -ID3Parser::Parse(ByteReader* aReader) { - MOZ_ASSERT(aReader); - - while (aReader->CanRead8() && !mHeader.ParseNext(aReader->ReadU8())) { } - - if (mHeader.IsValid()) { - // Header found, return total tag size. - return ID3Header::SIZE + Header().Size() + Header().FooterSize(); - } - return 0; -} - -void -ID3Parser::Reset() { - mHeader.Reset(); -} - -const ID3Parser::ID3Header& -ID3Parser::Header() const { - return mHeader; -} - -// ID3Parser::Header - -ID3Parser::ID3Header::ID3Header() -{ - Reset(); -} - -void -ID3Parser::ID3Header::Reset() { - mSize = 0; - mPos = 0; -} - -uint8_t -ID3Parser::ID3Header::MajorVersion() const { - return mRaw[id3_header::ID_END]; -} - -uint8_t -ID3Parser::ID3Header::MinorVersion() const { - return mRaw[id3_header::ID_END + 1]; -} - -uint8_t -ID3Parser::ID3Header::Flags() const { - return mRaw[id3_header::FLAGS_END - id3_header::FLAGS_LEN]; -} - -uint32_t -ID3Parser::ID3Header::Size() const { - if (!IsValid()) { - return 0; - } - return mSize; -} - -uint8_t -ID3Parser::ID3Header::FooterSize() const { - if (Flags() & (1 << 4)) { - return SIZE; - } - return 0; -} - -bool -ID3Parser::ID3Header::ParseNext(uint8_t c) { - if (!Update(c)) { - Reset(); - if (!Update(c)) { - Reset(); - } - } - return IsValid(); -} - -bool -ID3Parser::ID3Header::IsValid(int aPos) const { - if (aPos >= SIZE) { - return true; - } - const uint8_t c = mRaw[aPos]; - switch (aPos) { - case 0: case 1: case 2: - // Expecting "ID3". - return id3_header::ID[aPos] == c; - case 3: - return MajorVersion() >= id3_header::MIN_MAJOR_VER && - MajorVersion() <= id3_header::MAX_MAJOR_VER; - case 4: - return MinorVersion() < 0xFF; - case 5: - // Validate flags for supported versions, see bug 949036. - return ((0xFF >> MajorVersion()) & c) == 0; - case 6: case 7: case 8: case 9: - return c < 0x80; - } - return true; -} - -bool -ID3Parser::ID3Header::IsValid() const { - return mPos >= SIZE; -} - -bool -ID3Parser::ID3Header::Update(uint8_t c) { - if (mPos >= id3_header::SIZE_END - id3_header::SIZE_LEN && - mPos < id3_header::SIZE_END) { - mSize <<= 7; - mSize |= c; - } - if (mPos < SIZE) { - mRaw[mPos] = c; - } - return IsValid(mPos++); -} - -} // namespace mozilla diff --git a/dom/media/MP3Demuxer.h b/dom/media/MP3Demuxer.h deleted file mode 100644 index 5331c4d54..000000000 --- a/dom/media/MP3Demuxer.h +++ /dev/null @@ -1,472 +0,0 @@ -/* 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 MP3_DEMUXER_H_ -#define MP3_DEMUXER_H_ - -#include "mozilla/Attributes.h" -#include "mozilla/Maybe.h" -#include "MediaDataDemuxer.h" -#include "MediaResource.h" -#include "mp4_demuxer/ByteReader.h" -#include - -namespace mozilla { - -class MP3TrackDemuxer; - -class MP3Demuxer : public MediaDataDemuxer { -public: - // MediaDataDemuxer interface. - explicit MP3Demuxer(MediaResource* aSource); - RefPtr Init() override; - bool HasTrackType(TrackInfo::TrackType aType) const override; - uint32_t GetNumberTracks(TrackInfo::TrackType aType) const override; - already_AddRefed GetTrackDemuxer( - TrackInfo::TrackType aType, uint32_t aTrackNumber) override; - bool IsSeekable() const override; - void NotifyDataArrived() override; - void NotifyDataRemoved() override; - -private: - // Synchronous initialization. - bool InitInternal(); - - RefPtr mSource; - RefPtr mTrackDemuxer; -}; - -// ID3 header parser state machine used by FrameParser. -// The header contains the following format (one byte per term): -// 'I' 'D' '3' MajorVersion MinorVersion Flags Size1 Size2 Size3 Size4 -// For more details see http://id3.org/id3v2.3.0. -class ID3Parser { -public: - // Holds the ID3 header and its parsing state. - class ID3Header { - public: - // The header size is static, see class comment. - static const int SIZE = 10; - - // Constructor. - ID3Header(); - - // Resets the state to allow for a new parsing session. - void Reset(); - - // The ID3 tags are versioned like this: ID3vMajorVersion.MinorVersion. - uint8_t MajorVersion() const; - uint8_t MinorVersion() const; - - // The ID3 flags field. - uint8_t Flags() const; - - // The derived size based on the provided size fields. - uint32_t Size() const; - - // Returns the size of an ID3v2.4 footer if present and zero otherwise. - uint8_t FooterSize() const; - - // Returns whether the parsed data is a valid ID3 header up to the given - // byte position. - bool IsValid(int aPos) const; - - // Returns whether the parsed data is a complete and valid ID3 header. - bool IsValid() const; - - // Parses the next provided byte. - // Returns whether the byte creates a valid sequence up to this point. - bool ParseNext(uint8_t c); - - private: - // Updates the parser state machine with the provided next byte. - // Returns whether the provided byte is a valid next byte in the sequence. - bool Update(uint8_t c); - - // The currently parsed byte sequence. - uint8_t mRaw[SIZE]; - - // The derived size as provided by the size fields. - // The header size fields holds a 4 byte sequence with each MSB set to 0, - // this bits need to be ignored when deriving the actual size. - uint32_t mSize; - - // The current byte position in the parsed sequence. Reset via Reset and - // incremented via Update. - int mPos; - }; - - // Returns the parsed ID3 header. Note: check for validity. - const ID3Header& Header() const; - - // Parses contents of given ByteReader for a valid ID3v2 header. - // Returns the total ID3v2 tag size if successful and zero otherwise. - uint32_t Parse(mp4_demuxer::ByteReader* aReader); - - // Resets the state to allow for a new parsing session. - void Reset(); - -private: - // The currently parsed ID3 header. Reset via Reset, updated via Parse. - ID3Header mHeader; -}; - -// MPEG audio frame parser. -// The MPEG frame header has the following format (one bit per character): -// 11111111 111VVLLC BBBBSSPR MMEETOHH -// { sync } - 11 sync bits -// VV - MPEG audio version ID (0->2.5, 1->reserved, 2->2, 3->1) -// LL - Layer description (0->reserved, 1->III, 2->II, 3->I) -// C - CRC protection bit (0->protected, 1->not protected) -// BBBB - Bitrate index (see table in implementation) -// SS - Sampling rate index (see table in implementation) -// P - Padding bit (0->not padded, 1->padded by 1 slot size) -// R - Private bit (ignored) -// MM - Channel mode (0->stereo, 1->joint stereo, 2->dual channel, -// 3->single channel) -// EE - Mode extension for joint stereo (ignored) -// T - Copyright (0->disabled, 1->enabled) -// O - Original (0->copy, 1->original) -// HH - Emphasis (0->none, 1->50/15 ms, 2->reserved, 3->CCIT J.17) -class FrameParser { -public: - // Holds the frame header and its parsing state. - class FrameHeader { - public: - // The header size is static, see class comments. - static const int SIZE = 4; - - // Constructor. - FrameHeader(); - - // Raw field access, see class comments for details. - uint8_t Sync1() const; - uint8_t Sync2() const; - uint8_t RawVersion() const; - uint8_t RawLayer() const; - uint8_t RawProtection() const; - uint8_t RawBitrate() const; - uint8_t RawSampleRate() const; - uint8_t Padding() const; - uint8_t Private() const; - uint8_t RawChannelMode() const; - - // Sampling rate frequency in Hz. - int32_t SampleRate() const; - - // Number of audio channels. - int32_t Channels() const; - - // Samples per frames, static depending on MPEG version and layer. - int32_t SamplesPerFrame() const; - - // Slot size used for padding, static depending on MPEG layer. - int32_t SlotSize() const; - - // Bitrate in kbps, can vary between frames. - int32_t Bitrate() const; - - // MPEG layer (0->invalid, 1->I, 2->II, 3->III). - int32_t Layer() const; - - // Returns whether the parsed data is a valid frame header up to the given - // byte position. - bool IsValid(const int aPos) const; - - // Returns whether the parsed data is a complete and valid frame header. - bool IsValid() const; - - // Resets the state to allow for a new parsing session. - void Reset(); - - // Parses the next provided byte. - // Returns whether the byte creates a valid sequence up to this point. - bool ParseNext(const uint8_t c); - - private: - // Updates the parser state machine with the provided next byte. - // Returns whether the provided byte is a valid next byte in the sequence. - bool Update(const uint8_t c); - - // The currently parsed byte sequence. - uint8_t mRaw[SIZE]; - - // The current byte position in the parsed sequence. Reset via Reset and - // incremented via Update. - int mPos; - }; - - // VBR frames may contain Xing or VBRI headers for additional info, we use - // this class to parse them and access this info. - class VBRHeader { - public: - // Synchronize with vbr_header TYPE_STR on change. - enum VBRHeaderType { - NONE = 0, - XING, - VBRI - }; - - // Constructor. - VBRHeader(); - - // Returns the parsed VBR header type, or NONE if no valid header found. - VBRHeaderType Type() const; - - // Returns the total number of audio frames (excluding the VBR header frame) - // expected in the stream/file. - const Maybe& NumAudioFrames() const; - - // Returns the expected size of the stream. - const Maybe& NumBytes() const; - - // Returns the VBR scale factor (0: best quality, 100: lowest quality). - const Maybe& Scale() const; - - // Returns true iff Xing/Info TOC (table of contents) is present. - bool IsTOCPresent() const; - - // Returns whether the header is valid (type XING or VBRI). - bool IsValid() const; - - // Returns whether the header is valid and contains reasonable non-zero field values. - bool IsComplete() const; - - // Returns the byte offset for the given duration percentage as a factor - // (0: begin, 1.0: end). - int64_t Offset(float aDurationFac) const; - - // Parses contents of given ByteReader for a valid VBR header. - // The offset of the passed ByteReader needs to point to an MPEG frame begin, - // as a VBRI-style header is searched at a fixed offset relative to frame begin. - // Returns whether a valid VBR header was found in the range. - bool Parse(mp4_demuxer::ByteReader* aReader); - - private: - // Parses contents of given ByteReader for a valid Xing header. - // The initial ByteReader offset will be preserved. - // Returns whether a valid Xing header was found in the range. - bool ParseXing(mp4_demuxer::ByteReader* aReader); - - // Parses contents of given ByteReader for a valid VBRI header. - // The initial ByteReader offset will be preserved. It also needs to point - // to the beginning of a valid MPEG frame, as VBRI headers are searched - // at a fixed offset relative to frame begin. - // Returns whether a valid VBRI header was found in the range. - bool ParseVBRI(mp4_demuxer::ByteReader* aReader); - - // The total number of frames expected as parsed from a VBR header. - Maybe mNumAudioFrames; - - // The total number of bytes expected in the stream. - Maybe mNumBytes; - - // The VBR scale factor. - Maybe mScale; - - // The TOC table mapping duration percentage to byte offset. - std::vector mTOC; - - // The detected VBR header type. - VBRHeaderType mType; - }; - - // Frame meta container used to parse and hold a frame header and side info. - class Frame { - public: - // Returns the length of the frame excluding the header in bytes. - int32_t Length() const; - - // Returns the parsed frame header. - const FrameHeader& Header() const; - - // Resets the frame header and data. - void Reset(); - - // Parses the next provided byte. - // Returns whether the byte creates a valid sequence up to this point. - bool ParseNext(uint8_t c); - - private: - // The currently parsed frame header. - FrameHeader mHeader; - }; - - // Constructor. - FrameParser(); - - // Returns the currently parsed frame. Reset via Reset or EndFrameSession. - const Frame& CurrentFrame() const; - - // Returns the previously parsed frame. Reset via Reset. - const Frame& PrevFrame() const; - - // Returns the first parsed frame. Reset via Reset. - const Frame& FirstFrame() const; - - // Returns the parsed ID3 header. Note: check for validity. - const ID3Parser::ID3Header& ID3Header() const; - - // Returns the parsed VBR header info. Note: check for validity by type. - const VBRHeader& VBRInfo() const; - - // Resets the parser. - void Reset(); - - // Resets all frame data, but not the ID3Header. - // Don't use between frames as first frame data is reset. - void ResetFrameData(); - - // Clear the last parsed frame to allow for next frame parsing, i.e.: - // - sets PrevFrame to CurrentFrame - // - resets the CurrentFrame - // - resets ID3Header if no valid header was parsed yet - void EndFrameSession(); - - // Parses contents of given ByteReader for a valid frame header and returns true - // if one was found. After returning, the variable passed to 'aBytesToSkip' holds - // the amount of bytes to be skipped (if any) in order to jump across a large - // ID3v2 tag spanning multiple buffers. - bool Parse(mp4_demuxer::ByteReader* aReader, uint32_t* aBytesToSkip); - - // Parses contents of given ByteReader for a valid VBR header. - // The offset of the passed ByteReader needs to point to an MPEG frame begin, - // as a VBRI-style header is searched at a fixed offset relative to frame begin. - // Returns whether a valid VBR header was found. - bool ParseVBRHeader(mp4_demuxer::ByteReader* aReader); - -private: - // ID3 header parser. - ID3Parser mID3Parser; - - // VBR header parser. - VBRHeader mVBRHeader; - - // We keep the first parsed frame around for static info access, the - // previously parsed frame for debugging and the currently parsed frame. - Frame mFirstFrame; - Frame mFrame; - Frame mPrevFrame; -}; - -// The MP3 demuxer used to extract MPEG frames and side information out of -// MPEG streams. -class MP3TrackDemuxer : public MediaTrackDemuxer { -public: - // Constructor, expecting a valid media resource. - explicit MP3TrackDemuxer(MediaResource* aSource); - - // Initializes the track demuxer by reading the first frame for meta data. - // Returns initialization success state. - bool Init(); - - // Returns the total stream length if known, -1 otherwise. - int64_t StreamLength() const; - - // Returns the estimated stream duration, or a 0-duration if unknown. - media::TimeUnit Duration() const; - - // Returns the estimated duration up to the given frame number, - // or a 0-duration if unknown. - media::TimeUnit Duration(int64_t aNumFrames) const; - - // Returns the estimated current seek position time. - media::TimeUnit SeekPosition() const; - - const FrameParser::Frame& LastFrame() const; - RefPtr DemuxSample(); - - const ID3Parser::ID3Header& ID3Header() const; - const FrameParser::VBRHeader& VBRInfo() const; - - // MediaTrackDemuxer interface. - UniquePtr GetInfo() const override; - RefPtr Seek(media::TimeUnit aTime) override; - RefPtr GetSamples(int32_t aNumSamples = 1) override; - void Reset() override; - RefPtr SkipToNextRandomAccessPoint( - media::TimeUnit aTimeThreshold) override; - int64_t GetResourceOffset() const override; - media::TimeIntervals GetBuffered() override; - -private: - // Destructor. - ~MP3TrackDemuxer() {} - - // Fast approximate seeking to given time. - media::TimeUnit FastSeek(const media::TimeUnit& aTime); - - // Seeks by scanning the stream up to the given time for more accurate results. - media::TimeUnit ScanUntil(const media::TimeUnit& aTime); - - // Finds the first valid frame and returns its byte range if found - // or a null-byte range otherwise. - MediaByteRange FindFirstFrame(); - - // Finds the next valid frame and returns its byte range if found - // or a null-byte range otherwise. - MediaByteRange FindNextFrame(); - - // Skips the next frame given the provided byte range. - bool SkipNextFrame(const MediaByteRange& aRange); - - // Returns the next MPEG frame, if available. - already_AddRefed GetNextFrame(const MediaByteRange& aRange); - - // Updates post-read meta data. - void UpdateState(const MediaByteRange& aRange); - - // Returns the estimated offset for the given frame index. - int64_t OffsetFromFrameIndex(int64_t aFrameIndex) const; - - // Returns the estimated frame index for the given offset. - int64_t FrameIndexFromOffset(int64_t aOffset) const; - - // Returns the estimated frame index for the given time. - int64_t FrameIndexFromTime(const media::TimeUnit& aTime) const; - - // Reads aSize bytes into aBuffer from the source starting at aOffset. - // Returns the actual size read. - int32_t Read(uint8_t* aBuffer, int64_t aOffset, int32_t aSize); - - // Returns the average frame length derived from the previously parsed frames. - double AverageFrameLength() const; - - // The (hopefully) MPEG resource. - MediaResourceIndex mSource; - - // MPEG frame parser used to detect frames and extract side info. - FrameParser mParser; - - // Current byte offset in the source stream. - int64_t mOffset; - - // Byte offset of the begin of the first frame, or 0 if none parsed yet. - int64_t mFirstFrameOffset; - - // Total parsed frames. - uint64_t mNumParsedFrames; - - // Current frame index. - int64_t mFrameIndex; - - // Sum of parsed frames' lengths in bytes. - uint64_t mTotalFrameLen; - - // Samples per frame metric derived from frame headers or 0 if none available. - int32_t mSamplesPerFrame; - - // Samples per second metric derived from frame headers or 0 if none available. - int32_t mSamplesPerSecond; - - // Channel count derived from frame headers or 0 if none available. - int32_t mChannels; - - // Audio track config info. - UniquePtr mInfo; -}; - -} // namespace mozilla - -#endif diff --git a/dom/media/moz.build b/dom/media/moz.build index 772f27b35..6d2c71f2d 100644 --- a/dom/media/moz.build +++ b/dom/media/moz.build @@ -30,6 +30,7 @@ DIRS += [ 'ipc', 'mediasink', 'mediasource', + 'mp3', 'ogg', 'platforms', 'systemservices', @@ -125,8 +126,6 @@ EXPORTS += [ 'MediaTimer.h', 'MediaTrack.h', 'MediaTrackList.h', - 'MP3Decoder.h', - 'MP3Demuxer.h', 'NextFrameSeekTask.h', 'nsIDocumentActivity.h', 'PrincipalChangeObserver.h', @@ -233,8 +232,6 @@ UNIFIED_SOURCES += [ 'MediaTimer.cpp', 'MediaTrack.cpp', 'MediaTrackList.cpp', - 'MP3Decoder.cpp', - 'MP3Demuxer.cpp', 'NextFrameSeekTask.cpp', 'QueueObject.cpp', 'SeekJob.cpp', diff --git a/dom/media/mp3/MP3Decoder.cpp b/dom/media/mp3/MP3Decoder.cpp new file mode 100644 index 000000000..074a0866d --- /dev/null +++ b/dom/media/mp3/MP3Decoder.cpp @@ -0,0 +1,50 @@ + +/* -*- 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 "MP3Decoder.h" +#include "MediaDecoderStateMachine.h" +#include "MediaFormatReader.h" +#include "MP3Demuxer.h" +#include "PDMFactory.h" + +namespace mozilla { + +MediaDecoder* +MP3Decoder::Clone(MediaDecoderOwner* aOwner) { + if (!IsEnabled()) { + return nullptr; + } + return new MP3Decoder(aOwner); +} + +MediaDecoderStateMachine* +MP3Decoder::CreateStateMachine() { + RefPtr reader = + new MediaFormatReader(this, new MP3Demuxer(GetResource())); + return new MediaDecoderStateMachine(this, reader); +} + +/* static */ +bool +MP3Decoder::IsEnabled() { + RefPtr platform = new PDMFactory(); + return platform->SupportsMimeType(NS_LITERAL_CSTRING("audio/mpeg"), + /* DecoderDoctorDiagnostics* */ nullptr); +} + +/* static */ +bool MP3Decoder::CanHandleMediaType(const nsACString& aType, + const nsAString& aCodecs) +{ + if (aType.EqualsASCII("audio/mp3") || aType.EqualsASCII("audio/mpeg")) { + return IsEnabled() && + (aCodecs.IsEmpty() || aCodecs.EqualsASCII("mp3")); + } + return false; +} + +} // namespace mozilla diff --git a/dom/media/mp3/MP3Decoder.h b/dom/media/mp3/MP3Decoder.h new file mode 100644 index 000000000..887251065 --- /dev/null +++ b/dom/media/mp3/MP3Decoder.h @@ -0,0 +1,29 @@ +/* -*- 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 MP3Decoder_h_ +#define MP3Decoder_h_ + +#include "MediaDecoder.h" + +namespace mozilla { + +class MP3Decoder : public MediaDecoder { +public: + // MediaDecoder interface. + explicit MP3Decoder(MediaDecoderOwner* aOwner) : MediaDecoder(aOwner) {} + MediaDecoder* Clone(MediaDecoderOwner* aOwner) override; + MediaDecoderStateMachine* CreateStateMachine() override; + + // Returns true if the MP3 backend is preffed on, and we're running on a + // platform that is likely to have decoders for the format. + static bool IsEnabled(); + static bool CanHandleMediaType(const nsACString& aType, + const nsAString& aCodecs); +}; + +} // namespace mozilla + +#endif diff --git a/dom/media/mp3/MP3Demuxer.cpp b/dom/media/mp3/MP3Demuxer.cpp new file mode 100644 index 000000000..5a98cabfe --- /dev/null +++ b/dom/media/mp3/MP3Demuxer.cpp @@ -0,0 +1,1340 @@ +/* -*- 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 "MP3Demuxer.h" + +#include +#include + +#include "mozilla/Assertions.h" +#include "mozilla/EndianUtils.h" +#include "nsAutoPtr.h" +#include "VideoUtils.h" +#include "TimeUnits.h" +#include "prenv.h" + +#ifdef PR_LOGGING +extern mozilla::LazyLogModule gMediaDemuxerLog; +#define MP3LOG(msg, ...) \ + MOZ_LOG(gMediaDemuxerLog, LogLevel::Debug, ("MP3Demuxer " msg, ##__VA_ARGS__)) +#define MP3LOGV(msg, ...) \ + MOZ_LOG(gMediaDemuxerLog, LogLevel::Verbose, ("MP3Demuxer " msg, ##__VA_ARGS__)) +#else +#define MP3LOG(msg, ...) +#define MP3LOGV(msg, ...) +#endif + +using mozilla::media::TimeUnit; +using mozilla::media::TimeInterval; +using mozilla::media::TimeIntervals; +using mp4_demuxer::ByteReader; + +namespace mozilla { + +// MP3Demuxer + +MP3Demuxer::MP3Demuxer(MediaResource* aSource) + : mSource(aSource) +{} + +bool +MP3Demuxer::InitInternal() { + if (!mTrackDemuxer) { + mTrackDemuxer = new MP3TrackDemuxer(mSource); + } + return mTrackDemuxer->Init(); +} + +RefPtr +MP3Demuxer::Init() { + if (!InitInternal()) { + MP3LOG("MP3Demuxer::Init() failure: waiting for data"); + + return InitPromise::CreateAndReject( + NS_ERROR_DOM_MEDIA_METADATA_ERR, __func__); + } + + MP3LOG("MP3Demuxer::Init() successful"); + return InitPromise::CreateAndResolve(NS_OK, __func__); +} + +bool +MP3Demuxer::HasTrackType(TrackInfo::TrackType aType) const { + return aType == TrackInfo::kAudioTrack; +} + +uint32_t +MP3Demuxer::GetNumberTracks(TrackInfo::TrackType aType) const { + return aType == TrackInfo::kAudioTrack ? 1u : 0u; +} + +already_AddRefed +MP3Demuxer::GetTrackDemuxer(TrackInfo::TrackType aType, uint32_t aTrackNumber) { + if (!mTrackDemuxer) { + return nullptr; + } + return RefPtr(mTrackDemuxer).forget(); +} + +bool +MP3Demuxer::IsSeekable() const { + return true; +} + +void +MP3Demuxer::NotifyDataArrived() { + // TODO: bug 1169485. + NS_WARNING("Unimplemented function NotifyDataArrived"); + MP3LOGV("NotifyDataArrived()"); +} + +void +MP3Demuxer::NotifyDataRemoved() { + // TODO: bug 1169485. + NS_WARNING("Unimplemented function NotifyDataRemoved"); + MP3LOGV("NotifyDataRemoved()"); +} + + +// MP3TrackDemuxer + +MP3TrackDemuxer::MP3TrackDemuxer(MediaResource* aSource) + : mSource(aSource) + , mOffset(0) + , mFirstFrameOffset(0) + , mNumParsedFrames(0) + , mFrameIndex(0) + , mTotalFrameLen(0) + , mSamplesPerFrame(0) + , mSamplesPerSecond(0) + , mChannels(0) +{ + Reset(); +} + +bool +MP3TrackDemuxer::Init() { + Reset(); + FastSeek(TimeUnit()); + // Read the first frame to fetch sample rate and other meta data. + RefPtr frame(GetNextFrame(FindFirstFrame())); + + MP3LOG("Init StreamLength()=%" PRId64 " first-frame-found=%d", + StreamLength(), !!frame); + + if (!frame) { + return false; + } + + // Rewind back to the stream begin to avoid dropping the first frame. + FastSeek(TimeUnit()); + + if (!mInfo) { + mInfo = MakeUnique(); + } + + mInfo->mRate = mSamplesPerSecond; + mInfo->mChannels = mChannels; + mInfo->mBitDepth = 16; + mInfo->mMimeType = "audio/mpeg"; + mInfo->mDuration = Duration().ToMicroseconds(); + + MP3LOG("Init mInfo={mRate=%d mChannels=%d mBitDepth=%d mDuration=%" PRId64 "}", + mInfo->mRate, mInfo->mChannels, mInfo->mBitDepth, + mInfo->mDuration); + + return mSamplesPerSecond && mChannels; +} + +media::TimeUnit +MP3TrackDemuxer::SeekPosition() const { + TimeUnit pos = Duration(mFrameIndex); + if (Duration() > TimeUnit()) { + pos = std::min(Duration(), pos); + } + return pos; +} + +const FrameParser::Frame& +MP3TrackDemuxer::LastFrame() const { + return mParser.PrevFrame(); +} + +RefPtr +MP3TrackDemuxer::DemuxSample() { + return GetNextFrame(FindNextFrame()); +} + +const ID3Parser::ID3Header& +MP3TrackDemuxer::ID3Header() const { + return mParser.ID3Header(); +} + +const FrameParser::VBRHeader& +MP3TrackDemuxer::VBRInfo() const { + return mParser.VBRInfo(); +} + +UniquePtr +MP3TrackDemuxer::GetInfo() const { + return mInfo->Clone(); +} + +RefPtr +MP3TrackDemuxer::Seek(TimeUnit aTime) { + // Efficiently seek to the position. + FastSeek(aTime); + // Correct seek position by scanning the next frames. + const TimeUnit seekTime = ScanUntil(aTime); + + return SeekPromise::CreateAndResolve(seekTime, __func__); +} + +TimeUnit +MP3TrackDemuxer::FastSeek(const TimeUnit& aTime) { + MP3LOG("FastSeek(%" PRId64 ") avgFrameLen=%f mNumParsedFrames=%" PRIu64 + " mFrameIndex=%" PRId64 " mOffset=%" PRIu64, + aTime.ToMicroseconds(), AverageFrameLength(), mNumParsedFrames, + mFrameIndex, mOffset); + + const auto& vbr = mParser.VBRInfo(); + if (!aTime.ToMicroseconds()) { + // Quick seek to the beginning of the stream. + mFrameIndex = 0; + } else if (vbr.IsTOCPresent() && Duration().ToMicroseconds() > 0) { + // Use TOC for more precise seeking. + const float durationFrac = static_cast(aTime.ToMicroseconds()) / + Duration().ToMicroseconds(); + mFrameIndex = FrameIndexFromOffset(vbr.Offset(durationFrac)); + } else if (AverageFrameLength() > 0) { + mFrameIndex = FrameIndexFromTime(aTime); + } + + mOffset = OffsetFromFrameIndex(mFrameIndex); + + if (mOffset > mFirstFrameOffset && StreamLength() > 0) { + mOffset = std::min(StreamLength() - 1, mOffset); + } + + mParser.EndFrameSession(); + + MP3LOG("FastSeek End TOC=%d avgFrameLen=%f mNumParsedFrames=%" PRIu64 + " mFrameIndex=%" PRId64 " mFirstFrameOffset=%llu mOffset=%" PRIu64 + " SL=%llu NumBytes=%u", + vbr.IsTOCPresent(), AverageFrameLength(), mNumParsedFrames, mFrameIndex, + mFirstFrameOffset, mOffset, StreamLength(), vbr.NumBytes().valueOr(0)); + + return Duration(mFrameIndex); +} + +TimeUnit +MP3TrackDemuxer::ScanUntil(const TimeUnit& aTime) { + MP3LOG("ScanUntil(%" PRId64 ") avgFrameLen=%f mNumParsedFrames=%" PRIu64 + " mFrameIndex=%" PRId64 " mOffset=%" PRIu64, + aTime.ToMicroseconds(), AverageFrameLength(), mNumParsedFrames, + mFrameIndex, mOffset); + + if (!aTime.ToMicroseconds()) { + return FastSeek(aTime); + } + + if (Duration(mFrameIndex) > aTime) { + FastSeek(aTime); + } + + if (Duration(mFrameIndex + 1) > aTime) { + return SeekPosition(); + } + + MediaByteRange nextRange = FindNextFrame(); + while (SkipNextFrame(nextRange) && Duration(mFrameIndex + 1) < aTime) { + nextRange = FindNextFrame(); + MP3LOGV("ScanUntil* avgFrameLen=%f mNumParsedFrames=%" PRIu64 + " mFrameIndex=%" PRId64 " mOffset=%" PRIu64 " Duration=%" PRId64, + AverageFrameLength(), mNumParsedFrames, + mFrameIndex, mOffset, Duration(mFrameIndex + 1).ToMicroseconds()); + } + + MP3LOG("ScanUntil End avgFrameLen=%f mNumParsedFrames=%" PRIu64 + " mFrameIndex=%" PRId64 " mOffset=%" PRIu64, + AverageFrameLength(), mNumParsedFrames, mFrameIndex, mOffset); + + return SeekPosition(); +} + +RefPtr +MP3TrackDemuxer::GetSamples(int32_t aNumSamples) { + MP3LOGV("GetSamples(%d) Begin mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64 + " mFrameIndex=%" PRId64 " mTotalFrameLen=%" PRIu64 " mSamplesPerFrame=%d " + "mSamplesPerSecond=%d mChannels=%d", + aNumSamples, mOffset, mNumParsedFrames, mFrameIndex, mTotalFrameLen, + mSamplesPerFrame, mSamplesPerSecond, mChannels); + + if (!aNumSamples) { + return SamplesPromise::CreateAndReject( + NS_ERROR_DOM_MEDIA_DEMUXER_ERR, __func__); + } + + RefPtr frames = new SamplesHolder(); + + while (aNumSamples--) { + RefPtr frame(GetNextFrame(FindNextFrame())); + if (!frame) { + break; + } + + frames->mSamples.AppendElement(frame); + } + + MP3LOGV("GetSamples() End mSamples.Size()=%d aNumSamples=%d mOffset=%" PRIu64 + " mNumParsedFrames=%" PRIu64 " mFrameIndex=%" PRId64 + " mTotalFrameLen=%" PRIu64 " mSamplesPerFrame=%d mSamplesPerSecond=%d " + "mChannels=%d", + frames->mSamples.Length(), aNumSamples, mOffset, mNumParsedFrames, + mFrameIndex, mTotalFrameLen, mSamplesPerFrame, mSamplesPerSecond, + mChannels); + + if (frames->mSamples.IsEmpty()) { + return SamplesPromise::CreateAndReject( + NS_ERROR_DOM_MEDIA_END_OF_STREAM, __func__); + } + return SamplesPromise::CreateAndResolve(frames, __func__); +} + +void +MP3TrackDemuxer::Reset() { + MP3LOG("Reset()"); + + FastSeek(TimeUnit()); + mParser.Reset(); +} + +RefPtr +MP3TrackDemuxer::SkipToNextRandomAccessPoint(TimeUnit aTimeThreshold) { + // Will not be called for audio-only resources. + return SkipAccessPointPromise::CreateAndReject( + SkipFailureHolder(NS_ERROR_DOM_MEDIA_DEMUXER_ERR, 0), __func__); +} + +int64_t +MP3TrackDemuxer::GetResourceOffset() const { + return mOffset; +} + +TimeIntervals +MP3TrackDemuxer::GetBuffered() { + AutoPinned stream(mSource.GetResource()); + TimeIntervals buffered; + + if (Duration() > TimeUnit() && stream->IsDataCachedToEndOfResource(0)) { + // Special case completely cached files. This also handles local files. + buffered += TimeInterval(TimeUnit(), Duration()); + MP3LOGV("buffered = [[%" PRId64 ", %" PRId64 "]]", + TimeUnit().ToMicroseconds(), Duration().ToMicroseconds()); + return buffered; + } + + MediaByteRangeSet ranges; + nsresult rv = stream->GetCachedRanges(ranges); + NS_ENSURE_SUCCESS(rv, buffered); + + for (const auto& range: ranges) { + if (range.IsEmpty()) { + continue; + } + TimeUnit start = Duration(FrameIndexFromOffset(range.mStart)); + TimeUnit end = Duration(FrameIndexFromOffset(range.mEnd)); + MP3LOGV("buffered += [%" PRId64 ", %" PRId64 "]", + start.ToMicroseconds(), end.ToMicroseconds()); + buffered += TimeInterval(start, end); + } + + return buffered; +} + +int64_t +MP3TrackDemuxer::StreamLength() const { + return mSource.GetLength(); +} + +TimeUnit +MP3TrackDemuxer::Duration() const { + if (!mNumParsedFrames) { + return TimeUnit::FromMicroseconds(-1); + } + + int64_t numFrames = 0; + const auto numAudioFrames = mParser.VBRInfo().NumAudioFrames(); + if (mParser.VBRInfo().IsValid() && numAudioFrames.valueOr(0) + 1 > 1) { + // VBR headers don't include the VBR header frame. + numFrames = numAudioFrames.value() + 1; + } else { + const int64_t streamLen = StreamLength(); + if (streamLen < 0) { + // Unknown length, we can't estimate duration. + return TimeUnit::FromMicroseconds(-1); + } + if (AverageFrameLength() > 0) { + numFrames = (streamLen - mFirstFrameOffset) / AverageFrameLength(); + } + } + return Duration(numFrames); +} + +TimeUnit +MP3TrackDemuxer::Duration(int64_t aNumFrames) const { + if (!mSamplesPerSecond) { + return TimeUnit::FromMicroseconds(-1); + } + + const double usPerFrame = USECS_PER_S * mSamplesPerFrame / mSamplesPerSecond; + return TimeUnit::FromMicroseconds(aNumFrames * usPerFrame); +} + +MediaByteRange +MP3TrackDemuxer::FindFirstFrame() { + static const int MIN_SUCCESSIVE_FRAMES = 4; + + MediaByteRange candidateFrame = FindNextFrame(); + int numSuccFrames = candidateFrame.Length() > 0; + MediaByteRange currentFrame = candidateFrame; + MP3LOGV("FindFirst() first candidate frame: mOffset=%" PRIu64 " Length()=%" PRIu64, + candidateFrame.mStart, candidateFrame.Length()); + + while (candidateFrame.Length() && numSuccFrames < MIN_SUCCESSIVE_FRAMES) { + mParser.EndFrameSession(); + mOffset = currentFrame.mEnd; + const MediaByteRange prevFrame = currentFrame; + + // FindNextFrame() here will only return frames consistent with our candidate frame. + currentFrame = FindNextFrame(); + numSuccFrames += currentFrame.Length() > 0; + // Multiple successive false positives, which wouldn't be caught by the consistency + // checks alone, can be detected by wrong alignment (non-zero gap between frames). + const int64_t frameSeparation = currentFrame.mStart - prevFrame.mEnd; + + if (!currentFrame.Length() || frameSeparation != 0) { + MP3LOGV("FindFirst() not enough successive frames detected, " + "rejecting candidate frame: successiveFrames=%d, last Length()=%" PRIu64 + ", last frameSeparation=%" PRId64, numSuccFrames, currentFrame.Length(), + frameSeparation); + + mParser.ResetFrameData(); + mOffset = candidateFrame.mStart + 1; + candidateFrame = FindNextFrame(); + numSuccFrames = candidateFrame.Length() > 0; + currentFrame = candidateFrame; + MP3LOGV("FindFirst() new candidate frame: mOffset=%" PRIu64 " Length()=%" PRIu64, + candidateFrame.mStart, candidateFrame.Length()); + } + } + + if (numSuccFrames >= MIN_SUCCESSIVE_FRAMES) { + MP3LOG("FindFirst() accepting candidate frame: " + "successiveFrames=%d", numSuccFrames); + } else { + MP3LOG("FindFirst() no suitable first frame found"); + } + return candidateFrame; +} + +static bool +VerifyFrameConsistency( + const FrameParser::Frame& aFrame1, const FrameParser::Frame& aFrame2) { + const auto& h1 = aFrame1.Header(); + const auto& h2 = aFrame2.Header(); + + return h1.IsValid() && h2.IsValid() && + h1.Layer() == h2.Layer() && + h1.SlotSize() == h2.SlotSize() && + h1.SamplesPerFrame() == h2.SamplesPerFrame() && + h1.Channels() == h2.Channels() && + h1.SampleRate() == h2.SampleRate() && + h1.RawVersion() == h2.RawVersion() && + h1.RawProtection() == h2.RawProtection(); +} + +MediaByteRange +MP3TrackDemuxer::FindNextFrame() { + static const int BUFFER_SIZE = 64; + static const int MAX_SKIPPED_BYTES = 1024 * BUFFER_SIZE; + + MP3LOGV("FindNext() Begin mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64 + " mFrameIndex=%" PRId64 " mTotalFrameLen=%" PRIu64 + " mSamplesPerFrame=%d mSamplesPerSecond=%d mChannels=%d", + mOffset, mNumParsedFrames, mFrameIndex, mTotalFrameLen, + mSamplesPerFrame, mSamplesPerSecond, mChannels); + + uint8_t buffer[BUFFER_SIZE]; + int32_t read = 0; + + bool foundFrame = false; + int64_t frameHeaderOffset = 0; + + // Check whether we've found a valid MPEG frame. + while (!foundFrame) { + if ((!mParser.FirstFrame().Length() && + mOffset - mParser.ID3Header().Size() > MAX_SKIPPED_BYTES) || + (read = Read(buffer, mOffset, BUFFER_SIZE)) == 0) { + MP3LOG("FindNext() EOS or exceeded MAX_SKIPPED_BYTES without a frame"); + // This is not a valid MPEG audio stream or we've reached EOS, give up. + break; + } + + ByteReader reader(buffer, read); + uint32_t bytesToSkip = 0; + foundFrame = mParser.Parse(&reader, &bytesToSkip); + frameHeaderOffset = mOffset + reader.Offset() - FrameParser::FrameHeader::SIZE; + + // If we've found neither an MPEG frame header nor an ID3v2 tag, + // the reader shouldn't have any bytes remaining. + MOZ_ASSERT(foundFrame || bytesToSkip || !reader.Remaining()); + + if (foundFrame && mParser.FirstFrame().Length() && + !VerifyFrameConsistency(mParser.FirstFrame(), mParser.CurrentFrame())) { + // We've likely hit a false-positive, ignore it and proceed with the + // search for the next valid frame. + foundFrame = false; + mOffset = frameHeaderOffset + 1; + mParser.EndFrameSession(); + } else { + // Advance mOffset by the amount of bytes read and if necessary, + // skip an ID3v2 tag which stretches beyond the current buffer. + NS_ENSURE_TRUE(mOffset + read + bytesToSkip > mOffset, + MediaByteRange(0, 0)); + mOffset += read + bytesToSkip; + } + } + + if (!foundFrame || !mParser.CurrentFrame().Length()) { + MP3LOG("FindNext() Exit foundFrame=%d mParser.CurrentFrame().Length()=%d ", + foundFrame, mParser.CurrentFrame().Length()); + return { 0, 0 }; + } + + MP3LOGV("FindNext() End mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64 + " mFrameIndex=%" PRId64 " frameHeaderOffset=%d" + " mTotalFrameLen=%" PRIu64 " mSamplesPerFrame=%d mSamplesPerSecond=%d" + " mChannels=%d", + mOffset, mNumParsedFrames, mFrameIndex, frameHeaderOffset, + mTotalFrameLen, mSamplesPerFrame, mSamplesPerSecond, mChannels); + + return { frameHeaderOffset, frameHeaderOffset + mParser.CurrentFrame().Length() }; +} + +bool +MP3TrackDemuxer::SkipNextFrame(const MediaByteRange& aRange) { + if (!mNumParsedFrames || !aRange.Length()) { + // We can't skip the first frame, since it could contain VBR headers. + RefPtr frame(GetNextFrame(aRange)); + return frame; + } + + UpdateState(aRange); + + MP3LOGV("SkipNext() End mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64 + " mFrameIndex=%" PRId64 " mTotalFrameLen=%" PRIu64 + " mSamplesPerFrame=%d mSamplesPerSecond=%d mChannels=%d", + mOffset, mNumParsedFrames, mFrameIndex, mTotalFrameLen, + mSamplesPerFrame, mSamplesPerSecond, mChannels); + + return true; +} + +already_AddRefed +MP3TrackDemuxer::GetNextFrame(const MediaByteRange& aRange) { + MP3LOG("GetNext() Begin({mStart=%" PRId64 " Length()=%" PRId64 "})", + aRange.mStart, aRange.Length()); + if (!aRange.Length()) { + return nullptr; + } + + RefPtr frame = new MediaRawData(); + frame->mOffset = aRange.mStart; + + nsAutoPtr frameWriter(frame->CreateWriter()); + if (!frameWriter->SetSize(aRange.Length())) { + MP3LOG("GetNext() Exit failed to allocated media buffer"); + return nullptr; + } + + const uint32_t read = Read(frameWriter->Data(), frame->mOffset, frame->Size()); + + if (read != aRange.Length()) { + MP3LOG("GetNext() Exit read=%u frame->Size()=%u", read, frame->Size()); + return nullptr; + } + + UpdateState(aRange); + + frame->mTime = Duration(mFrameIndex - 1).ToMicroseconds(); + frame->mDuration = Duration(1).ToMicroseconds(); + frame->mTimecode = frame->mTime; + frame->mKeyframe = true; + + MOZ_ASSERT(frame->mTime >= 0); + MOZ_ASSERT(frame->mDuration > 0); + + if (mNumParsedFrames == 1) { + // First frame parsed, let's read VBR info if available. + ByteReader reader(frame->Data(), frame->Size()); + mParser.ParseVBRHeader(&reader); + mFirstFrameOffset = frame->mOffset; + } + + MP3LOGV("GetNext() End mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64 + " mFrameIndex=%" PRId64 " mTotalFrameLen=%" PRIu64 + " mSamplesPerFrame=%d mSamplesPerSecond=%d mChannels=%d", + mOffset, mNumParsedFrames, mFrameIndex, mTotalFrameLen, + mSamplesPerFrame, mSamplesPerSecond, mChannels); + + return frame.forget(); +} + +int64_t +MP3TrackDemuxer::OffsetFromFrameIndex(int64_t aFrameIndex) const { + int64_t offset = 0; + const auto& vbr = mParser.VBRInfo(); + + if (vbr.IsComplete()) { + offset = mFirstFrameOffset + aFrameIndex * vbr.NumBytes().value() / + vbr.NumAudioFrames().value(); + } else if (AverageFrameLength() > 0) { + offset = mFirstFrameOffset + aFrameIndex * AverageFrameLength(); + } + + MP3LOGV("OffsetFromFrameIndex(%" PRId64 ") -> %" PRId64, aFrameIndex, offset); + return std::max(mFirstFrameOffset, offset); +} + +int64_t +MP3TrackDemuxer::FrameIndexFromOffset(int64_t aOffset) const { + int64_t frameIndex = 0; + const auto& vbr = mParser.VBRInfo(); + + if (vbr.IsComplete()) { + frameIndex = static_cast(aOffset - mFirstFrameOffset) / + vbr.NumBytes().value() * vbr.NumAudioFrames().value(); + frameIndex = std::min(vbr.NumAudioFrames().value(), frameIndex); + } else if (AverageFrameLength() > 0) { + frameIndex = (aOffset - mFirstFrameOffset) / AverageFrameLength(); + } + + MP3LOGV("FrameIndexFromOffset(%" PRId64 ") -> %" PRId64, aOffset, frameIndex); + return std::max(0, frameIndex); +} + +int64_t +MP3TrackDemuxer::FrameIndexFromTime(const media::TimeUnit& aTime) const { + int64_t frameIndex = 0; + if (mSamplesPerSecond > 0 && mSamplesPerFrame > 0) { + frameIndex = aTime.ToSeconds() * mSamplesPerSecond / mSamplesPerFrame - 1; + } + + MP3LOGV("FrameIndexFromOffset(%fs) -> %" PRId64, aTime.ToSeconds(), frameIndex); + return std::max(0, frameIndex); +} + +void +MP3TrackDemuxer::UpdateState(const MediaByteRange& aRange) { + // Prevent overflow. + if (mTotalFrameLen + aRange.Length() < mTotalFrameLen) { + // These variables have a linear dependency and are only used to derive the + // average frame length. + mTotalFrameLen /= 2; + mNumParsedFrames /= 2; + } + + // Full frame parsed, move offset to its end. + mOffset = aRange.mEnd; + + mTotalFrameLen += aRange.Length(); + + if (!mSamplesPerFrame) { + mSamplesPerFrame = mParser.CurrentFrame().Header().SamplesPerFrame(); + mSamplesPerSecond = mParser.CurrentFrame().Header().SampleRate(); + mChannels = mParser.CurrentFrame().Header().Channels(); + } + + ++mNumParsedFrames; + ++mFrameIndex; + MOZ_ASSERT(mFrameIndex > 0); + + // Prepare the parser for the next frame parsing session. + mParser.EndFrameSession(); +} + +int32_t +MP3TrackDemuxer::Read(uint8_t* aBuffer, int64_t aOffset, int32_t aSize) { + MP3LOGV("MP3TrackDemuxer::Read(%p %" PRId64 " %d)", aBuffer, aOffset, aSize); + + const int64_t streamLen = StreamLength(); + if (mInfo && streamLen > 0) { + // Prevent blocking reads after successful initialization. + aSize = std::min(aSize, streamLen - aOffset); + } + + uint32_t read = 0; + MP3LOGV("MP3TrackDemuxer::Read -> ReadAt(%d)", aSize); + const nsresult rv = mSource.ReadAt(aOffset, reinterpret_cast(aBuffer), + static_cast(aSize), &read); + NS_ENSURE_SUCCESS(rv, 0); + return static_cast(read); +} + +double +MP3TrackDemuxer::AverageFrameLength() const { + if (mNumParsedFrames) { + return static_cast(mTotalFrameLen) / mNumParsedFrames; + } + const auto& vbr = mParser.VBRInfo(); + if (vbr.IsComplete() && vbr.NumAudioFrames().value() + 1) { + return static_cast(vbr.NumBytes().value()) / + (vbr.NumAudioFrames().value() + 1); + } + return 0.0; +} + +// FrameParser + +namespace frame_header { +// FrameHeader mRaw byte offsets. +static const int SYNC1 = 0; +static const int SYNC2_VERSION_LAYER_PROTECTION = 1; +static const int BITRATE_SAMPLERATE_PADDING_PRIVATE = 2; +static const int CHANNELMODE_MODEEXT_COPY_ORIG_EMPH = 3; +} // namespace frame_header + +FrameParser::FrameParser() +{ +} + +void +FrameParser::Reset() { + mID3Parser.Reset(); + mFrame.Reset(); +} + +void +FrameParser::ResetFrameData() { + mFrame.Reset(); + mFirstFrame.Reset(); + mPrevFrame.Reset(); +} + +void +FrameParser::EndFrameSession() { + if (!mID3Parser.Header().IsValid()) { + // Reset ID3 tags only if we have not parsed a valid ID3 header yet. + mID3Parser.Reset(); + } + mPrevFrame = mFrame; + mFrame.Reset(); +} + +const FrameParser::Frame& +FrameParser::CurrentFrame() const { + return mFrame; +} + +const FrameParser::Frame& +FrameParser::PrevFrame() const { + return mPrevFrame; +} + +const FrameParser::Frame& +FrameParser::FirstFrame() const { + return mFirstFrame; +} + +const ID3Parser::ID3Header& +FrameParser::ID3Header() const { + return mID3Parser.Header(); +} + +const FrameParser::VBRHeader& +FrameParser::VBRInfo() const { + return mVBRHeader; +} + +bool +FrameParser::Parse(ByteReader* aReader, uint32_t* aBytesToSkip) { + MOZ_ASSERT(aReader && aBytesToSkip); + *aBytesToSkip = 0; + + if (!mID3Parser.Header().Size() && !mFirstFrame.Length()) { + // No MP3 frames have been parsed yet, look for ID3v2 headers at file begin. + // ID3v1 tags may only be at file end. + // TODO: should we try to read ID3 tags at end of file/mid-stream, too? + const size_t prevReaderOffset = aReader->Offset(); + const uint32_t tagSize = mID3Parser.Parse(aReader); + if (tagSize) { + // ID3 tag found, skip past it. + const uint32_t skipSize = tagSize - ID3Parser::ID3Header::SIZE; + + if (skipSize > aReader->Remaining()) { + // Skipping across the ID3v2 tag would take us past the end of the buffer, therefore we + // return immediately and let the calling function handle skipping the rest of the tag. + MP3LOGV("ID3v2 tag detected, size=%d," + " needing to skip %d bytes past the current buffer", + tagSize, skipSize - aReader->Remaining()); + *aBytesToSkip = skipSize - aReader->Remaining(); + return false; + } + MP3LOGV("ID3v2 tag detected, size=%d", tagSize); + aReader->Read(skipSize); + } else { + // No ID3v2 tag found, rewinding reader in order to search for a MPEG frame header. + aReader->Seek(prevReaderOffset); + } + } + + while (aReader->CanRead8() && !mFrame.ParseNext(aReader->ReadU8())) { } + + if (mFrame.Length()) { + // MP3 frame found. + if (!mFirstFrame.Length()) { + mFirstFrame = mFrame; + } + // Indicate success. + return true; + } + return false; +} + +// FrameParser::Header + +FrameParser::FrameHeader::FrameHeader() +{ + Reset(); +} + +uint8_t +FrameParser::FrameHeader::Sync1() const { + return mRaw[frame_header::SYNC1]; +} + +uint8_t +FrameParser::FrameHeader::Sync2() const { + return 0x7 & mRaw[frame_header::SYNC2_VERSION_LAYER_PROTECTION] >> 5; +} + +uint8_t +FrameParser::FrameHeader::RawVersion() const { + return 0x3 & mRaw[frame_header::SYNC2_VERSION_LAYER_PROTECTION] >> 3; +} + +uint8_t +FrameParser::FrameHeader::RawLayer() const { + return 0x3 & mRaw[frame_header::SYNC2_VERSION_LAYER_PROTECTION] >> 1; +} + +uint8_t +FrameParser::FrameHeader::RawProtection() const { + return 0x1 & mRaw[frame_header::SYNC2_VERSION_LAYER_PROTECTION] >> 6; +} + +uint8_t +FrameParser::FrameHeader::RawBitrate() const { + return 0xF & mRaw[frame_header::BITRATE_SAMPLERATE_PADDING_PRIVATE] >> 4; +} + +uint8_t +FrameParser::FrameHeader::RawSampleRate() const { + return 0x3 & mRaw[frame_header::BITRATE_SAMPLERATE_PADDING_PRIVATE] >> 2; +} + +uint8_t +FrameParser::FrameHeader::Padding() const { + return 0x1 & mRaw[frame_header::BITRATE_SAMPLERATE_PADDING_PRIVATE] >> 1; +} + +uint8_t +FrameParser::FrameHeader::Private() const { + return 0x1 & mRaw[frame_header::BITRATE_SAMPLERATE_PADDING_PRIVATE]; +} + +uint8_t +FrameParser::FrameHeader::RawChannelMode() const { + return 0x3 & mRaw[frame_header::CHANNELMODE_MODEEXT_COPY_ORIG_EMPH] >> 6; +} + +int32_t +FrameParser::FrameHeader::Layer() const { + static const uint8_t LAYERS[4] = { 0, 3, 2, 1 }; + + return LAYERS[RawLayer()]; +} + +int32_t +FrameParser::FrameHeader::SampleRate() const { + // Sample rates - use [version][srate] + static const uint16_t SAMPLE_RATE[4][4] = { + { 11025, 12000, 8000, 0 }, // MPEG 2.5 + { 0, 0, 0, 0 }, // Reserved + { 22050, 24000, 16000, 0 }, // MPEG 2 + { 44100, 48000, 32000, 0 } // MPEG 1 + }; + + return SAMPLE_RATE[RawVersion()][RawSampleRate()]; +} + +int32_t +FrameParser::FrameHeader::Channels() const { + // 3 is single channel (mono), any other value is some variant of dual + // channel. + return RawChannelMode() == 3 ? 1 : 2; +} + +int32_t +FrameParser::FrameHeader::SamplesPerFrame() const { + // Samples per frame - use [version][layer] + static const uint16_t FRAME_SAMPLE[4][4] = { + // Layer 3 2 1 Version + { 0, 576, 1152, 384 }, // 2.5 + { 0, 0, 0, 0 }, // Reserved + { 0, 576, 1152, 384 }, // 2 + { 0, 1152, 1152, 384 } // 1 + }; + + return FRAME_SAMPLE[RawVersion()][RawLayer()]; +} + +int32_t +FrameParser::FrameHeader::Bitrate() const { + // Bitrates - use [version][layer][bitrate] + static const uint16_t BITRATE[4][4][16] = { + { // Version 2.5 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Reserved + { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }, // Layer 3 + { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }, // Layer 2 + { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0 } // Layer 1 + }, + { // Reserved + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Invalid + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Invalid + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Invalid + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } // Invalid + }, + { // Version 2 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Reserved + { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }, // Layer 3 + { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }, // Layer 2 + { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0 } // Layer 1 + }, + { // Version 1 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Reserved + { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0 }, // Layer 3 + { 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0 }, // Layer 2 + { 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0 }, // Layer 1 + } + }; + + return 1000 * BITRATE[RawVersion()][RawLayer()][RawBitrate()]; +} + +int32_t +FrameParser::FrameHeader::SlotSize() const { + // Slot size (MPEG unit of measurement) - use [layer] + static const uint8_t SLOT_SIZE[4] = { 0, 1, 1, 4 }; // Rsvd, 3, 2, 1 + + return SLOT_SIZE[RawLayer()]; +} + +bool +FrameParser::FrameHeader::ParseNext(uint8_t c) { + if (!Update(c)) { + Reset(); + if (!Update(c)) { + Reset(); + } + } + return IsValid(); +} + +bool +FrameParser::FrameHeader::IsValid(int aPos) const { + if (aPos >= SIZE) { + return true; + } + if (aPos == frame_header::SYNC1) { + return Sync1() == 0xFF; + } + if (aPos == frame_header::SYNC2_VERSION_LAYER_PROTECTION) { + return Sync2() == 7 && + RawVersion() != 1 && + Layer() == 3; + } + if (aPos == frame_header::BITRATE_SAMPLERATE_PADDING_PRIVATE) { + return RawBitrate() != 0xF && RawBitrate() != 0 && + RawSampleRate() != 3; + } + return true; +} + +bool +FrameParser::FrameHeader::IsValid() const { + return mPos >= SIZE; +} + +void +FrameParser::FrameHeader::Reset() { + mPos = 0; +} + +bool +FrameParser::FrameHeader::Update(uint8_t c) { + if (mPos < SIZE) { + mRaw[mPos] = c; + } + return IsValid(mPos++); +} + +// FrameParser::VBRHeader + +namespace vbr_header { +static const char* TYPE_STR[3] = {"NONE", "XING", "VBRI"}; +static const uint32_t TOC_SIZE = 100; +} // namespace vbr_header + +FrameParser::VBRHeader::VBRHeader() + : mType(NONE) +{ +} + +FrameParser::VBRHeader::VBRHeaderType +FrameParser::VBRHeader::Type() const { + return mType; +} + +const Maybe& +FrameParser::VBRHeader::NumAudioFrames() const { + return mNumAudioFrames; +} + +const Maybe& +FrameParser::VBRHeader::NumBytes() const { + return mNumBytes; +} + +const Maybe& +FrameParser::VBRHeader::Scale() const { + return mScale; +} + +bool +FrameParser::VBRHeader::IsTOCPresent() const { + return mTOC.size() == vbr_header::TOC_SIZE; +} + +bool +FrameParser::VBRHeader::IsValid() const { + return mType != NONE; +} + +bool +FrameParser::VBRHeader::IsComplete() const { + return IsValid() && + mNumAudioFrames.valueOr(0) > 0 && + mNumBytes.valueOr(0) > 0 && + // We don't care about the scale for any computations here. + // mScale < 101 && + true; +} + +int64_t +FrameParser::VBRHeader::Offset(float aDurationFac) const { + if (!IsTOCPresent()) { + return -1; + } + + // Constrain the duration percentage to [0, 99]. + const float durationPer = 100.0f * std::min(0.99f, std::max(0.0f, aDurationFac)); + const size_t fullPer = durationPer; + const float rest = durationPer - fullPer; + + MOZ_ASSERT(fullPer < mTOC.size()); + int64_t offset = mTOC.at(fullPer); + + if (rest > 0.0 && fullPer + 1 < mTOC.size()) { + offset += rest * (mTOC.at(fullPer + 1) - offset); + } + + return offset; +} + +bool +FrameParser::VBRHeader::ParseXing(ByteReader* aReader) { + static const uint32_t XING_TAG = BigEndian::readUint32("Xing"); + static const uint32_t INFO_TAG = BigEndian::readUint32("Info"); + + enum Flags { + NUM_FRAMES = 0x01, + NUM_BYTES = 0x02, + TOC = 0x04, + VBR_SCALE = 0x08 + }; + + MOZ_ASSERT(aReader); + const size_t prevReaderOffset = aReader->Offset(); + + // We have to search for the Xing header as its position can change. + while (aReader->CanRead32() && + aReader->PeekU32() != XING_TAG && aReader->PeekU32() != INFO_TAG) { + aReader->Read(1); + } + + if (aReader->CanRead32()) { + // Skip across the VBR header ID tag. + aReader->ReadU32(); + mType = XING; + } + uint32_t flags = 0; + if (aReader->CanRead32()) { + flags = aReader->ReadU32(); + } + if (flags & NUM_FRAMES && aReader->CanRead32()) { + mNumAudioFrames = Some(aReader->ReadU32()); + } + if (flags & NUM_BYTES && aReader->CanRead32()) { + mNumBytes = Some(aReader->ReadU32()); + } + if (flags & TOC && aReader->Remaining() >= vbr_header::TOC_SIZE) { + if (!mNumBytes) { + // We don't have the stream size to calculate offsets, skip the TOC. + aReader->Read(vbr_header::TOC_SIZE); + } else { + mTOC.clear(); + mTOC.reserve(vbr_header::TOC_SIZE); + for (size_t i = 0; i < vbr_header::TOC_SIZE; ++i) { + mTOC.push_back(1.0f / 256.0f * aReader->ReadU8() * mNumBytes.value()); + } + } + } + if (flags & VBR_SCALE && aReader->CanRead32()) { + mScale = Some(aReader->ReadU32()); + } + + aReader->Seek(prevReaderOffset); + return mType == XING; +} + +bool +FrameParser::VBRHeader::ParseVBRI(ByteReader* aReader) { + static const uint32_t TAG = BigEndian::readUint32("VBRI"); + static const uint32_t OFFSET = 32 + FrameParser::FrameHeader::SIZE; + static const uint32_t FRAME_COUNT_OFFSET = OFFSET + 14; + static const uint32_t MIN_FRAME_SIZE = OFFSET + 26; + + MOZ_ASSERT(aReader); + // ParseVBRI assumes that the ByteReader offset points to the beginning of a frame, + // therefore as a simple check, we look for the presence of a frame sync at that position. + MOZ_ASSERT((aReader->PeekU16() & 0xFFE0) == 0xFFE0); + const size_t prevReaderOffset = aReader->Offset(); + + // VBRI have a fixed relative position, so let's check for it there. + if (aReader->Remaining() > MIN_FRAME_SIZE) { + aReader->Seek(prevReaderOffset + OFFSET); + if (aReader->ReadU32() == TAG) { + aReader->Seek(prevReaderOffset + FRAME_COUNT_OFFSET); + mNumAudioFrames = Some(aReader->ReadU32()); + mType = VBRI; + aReader->Seek(prevReaderOffset); + return true; + } + } + aReader->Seek(prevReaderOffset); + return false; +} + +bool +FrameParser::VBRHeader::Parse(ByteReader* aReader) { + const bool rv = ParseVBRI(aReader) || ParseXing(aReader); + if (rv) { + MP3LOG("VBRHeader::Parse found valid VBR/CBR header: type=%s" + " NumAudioFrames=%u NumBytes=%u Scale=%u TOC-size=%u", + vbr_header::TYPE_STR[Type()], NumAudioFrames().valueOr(0), + NumBytes().valueOr(0), Scale().valueOr(0), mTOC.size()); + } + return rv; +} + +// FrameParser::Frame + +void +FrameParser::Frame::Reset() { + mHeader.Reset(); +} + +int32_t +FrameParser::Frame::Length() const { + if (!mHeader.IsValid() || !mHeader.SampleRate()) { + return 0; + } + + const float bitsPerSample = mHeader.SamplesPerFrame() / 8.0f; + const int32_t frameLen = bitsPerSample * mHeader.Bitrate() / + mHeader.SampleRate() + + mHeader.Padding() * mHeader.SlotSize(); + return frameLen; +} + +bool +FrameParser::Frame::ParseNext(uint8_t c) { + return mHeader.ParseNext(c); +} + +const FrameParser::FrameHeader& +FrameParser::Frame::Header() const { + return mHeader; +} + +bool +FrameParser::ParseVBRHeader(ByteReader* aReader) { + return mVBRHeader.Parse(aReader); +} + +// ID3Parser + +// Constants +namespace id3_header { +static const int ID_LEN = 3; +static const int VERSION_LEN = 2; +static const int FLAGS_LEN = 1; +static const int SIZE_LEN = 4; + +static const int ID_END = ID_LEN; +static const int VERSION_END = ID_END + VERSION_LEN; +static const int FLAGS_END = VERSION_END + FLAGS_LEN; +static const int SIZE_END = FLAGS_END + SIZE_LEN; + +static const uint8_t ID[ID_LEN] = {'I', 'D', '3'}; + +static const uint8_t MIN_MAJOR_VER = 2; +static const uint8_t MAX_MAJOR_VER = 4; +} // namespace id3_header + +uint32_t +ID3Parser::Parse(ByteReader* aReader) { + MOZ_ASSERT(aReader); + + while (aReader->CanRead8() && !mHeader.ParseNext(aReader->ReadU8())) { } + + if (mHeader.IsValid()) { + // Header found, return total tag size. + return ID3Header::SIZE + Header().Size() + Header().FooterSize(); + } + return 0; +} + +void +ID3Parser::Reset() { + mHeader.Reset(); +} + +const ID3Parser::ID3Header& +ID3Parser::Header() const { + return mHeader; +} + +// ID3Parser::Header + +ID3Parser::ID3Header::ID3Header() +{ + Reset(); +} + +void +ID3Parser::ID3Header::Reset() { + mSize = 0; + mPos = 0; +} + +uint8_t +ID3Parser::ID3Header::MajorVersion() const { + return mRaw[id3_header::ID_END]; +} + +uint8_t +ID3Parser::ID3Header::MinorVersion() const { + return mRaw[id3_header::ID_END + 1]; +} + +uint8_t +ID3Parser::ID3Header::Flags() const { + return mRaw[id3_header::FLAGS_END - id3_header::FLAGS_LEN]; +} + +uint32_t +ID3Parser::ID3Header::Size() const { + if (!IsValid()) { + return 0; + } + return mSize; +} + +uint8_t +ID3Parser::ID3Header::FooterSize() const { + if (Flags() & (1 << 4)) { + return SIZE; + } + return 0; +} + +bool +ID3Parser::ID3Header::ParseNext(uint8_t c) { + if (!Update(c)) { + Reset(); + if (!Update(c)) { + Reset(); + } + } + return IsValid(); +} + +bool +ID3Parser::ID3Header::IsValid(int aPos) const { + if (aPos >= SIZE) { + return true; + } + const uint8_t c = mRaw[aPos]; + switch (aPos) { + case 0: case 1: case 2: + // Expecting "ID3". + return id3_header::ID[aPos] == c; + case 3: + return MajorVersion() >= id3_header::MIN_MAJOR_VER && + MajorVersion() <= id3_header::MAX_MAJOR_VER; + case 4: + return MinorVersion() < 0xFF; + case 5: + // Validate flags for supported versions, see bug 949036. + return ((0xFF >> MajorVersion()) & c) == 0; + case 6: case 7: case 8: case 9: + return c < 0x80; + } + return true; +} + +bool +ID3Parser::ID3Header::IsValid() const { + return mPos >= SIZE; +} + +bool +ID3Parser::ID3Header::Update(uint8_t c) { + if (mPos >= id3_header::SIZE_END - id3_header::SIZE_LEN && + mPos < id3_header::SIZE_END) { + mSize <<= 7; + mSize |= c; + } + if (mPos < SIZE) { + mRaw[mPos] = c; + } + return IsValid(mPos++); +} + +} // namespace mozilla diff --git a/dom/media/mp3/MP3Demuxer.h b/dom/media/mp3/MP3Demuxer.h new file mode 100644 index 000000000..5331c4d54 --- /dev/null +++ b/dom/media/mp3/MP3Demuxer.h @@ -0,0 +1,472 @@ +/* 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 MP3_DEMUXER_H_ +#define MP3_DEMUXER_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" +#include "MediaDataDemuxer.h" +#include "MediaResource.h" +#include "mp4_demuxer/ByteReader.h" +#include + +namespace mozilla { + +class MP3TrackDemuxer; + +class MP3Demuxer : public MediaDataDemuxer { +public: + // MediaDataDemuxer interface. + explicit MP3Demuxer(MediaResource* aSource); + RefPtr Init() override; + bool HasTrackType(TrackInfo::TrackType aType) const override; + uint32_t GetNumberTracks(TrackInfo::TrackType aType) const override; + already_AddRefed GetTrackDemuxer( + TrackInfo::TrackType aType, uint32_t aTrackNumber) override; + bool IsSeekable() const override; + void NotifyDataArrived() override; + void NotifyDataRemoved() override; + +private: + // Synchronous initialization. + bool InitInternal(); + + RefPtr mSource; + RefPtr mTrackDemuxer; +}; + +// ID3 header parser state machine used by FrameParser. +// The header contains the following format (one byte per term): +// 'I' 'D' '3' MajorVersion MinorVersion Flags Size1 Size2 Size3 Size4 +// For more details see http://id3.org/id3v2.3.0. +class ID3Parser { +public: + // Holds the ID3 header and its parsing state. + class ID3Header { + public: + // The header size is static, see class comment. + static const int SIZE = 10; + + // Constructor. + ID3Header(); + + // Resets the state to allow for a new parsing session. + void Reset(); + + // The ID3 tags are versioned like this: ID3vMajorVersion.MinorVersion. + uint8_t MajorVersion() const; + uint8_t MinorVersion() const; + + // The ID3 flags field. + uint8_t Flags() const; + + // The derived size based on the provided size fields. + uint32_t Size() const; + + // Returns the size of an ID3v2.4 footer if present and zero otherwise. + uint8_t FooterSize() const; + + // Returns whether the parsed data is a valid ID3 header up to the given + // byte position. + bool IsValid(int aPos) const; + + // Returns whether the parsed data is a complete and valid ID3 header. + bool IsValid() const; + + // Parses the next provided byte. + // Returns whether the byte creates a valid sequence up to this point. + bool ParseNext(uint8_t c); + + private: + // Updates the parser state machine with the provided next byte. + // Returns whether the provided byte is a valid next byte in the sequence. + bool Update(uint8_t c); + + // The currently parsed byte sequence. + uint8_t mRaw[SIZE]; + + // The derived size as provided by the size fields. + // The header size fields holds a 4 byte sequence with each MSB set to 0, + // this bits need to be ignored when deriving the actual size. + uint32_t mSize; + + // The current byte position in the parsed sequence. Reset via Reset and + // incremented via Update. + int mPos; + }; + + // Returns the parsed ID3 header. Note: check for validity. + const ID3Header& Header() const; + + // Parses contents of given ByteReader for a valid ID3v2 header. + // Returns the total ID3v2 tag size if successful and zero otherwise. + uint32_t Parse(mp4_demuxer::ByteReader* aReader); + + // Resets the state to allow for a new parsing session. + void Reset(); + +private: + // The currently parsed ID3 header. Reset via Reset, updated via Parse. + ID3Header mHeader; +}; + +// MPEG audio frame parser. +// The MPEG frame header has the following format (one bit per character): +// 11111111 111VVLLC BBBBSSPR MMEETOHH +// { sync } - 11 sync bits +// VV - MPEG audio version ID (0->2.5, 1->reserved, 2->2, 3->1) +// LL - Layer description (0->reserved, 1->III, 2->II, 3->I) +// C - CRC protection bit (0->protected, 1->not protected) +// BBBB - Bitrate index (see table in implementation) +// SS - Sampling rate index (see table in implementation) +// P - Padding bit (0->not padded, 1->padded by 1 slot size) +// R - Private bit (ignored) +// MM - Channel mode (0->stereo, 1->joint stereo, 2->dual channel, +// 3->single channel) +// EE - Mode extension for joint stereo (ignored) +// T - Copyright (0->disabled, 1->enabled) +// O - Original (0->copy, 1->original) +// HH - Emphasis (0->none, 1->50/15 ms, 2->reserved, 3->CCIT J.17) +class FrameParser { +public: + // Holds the frame header and its parsing state. + class FrameHeader { + public: + // The header size is static, see class comments. + static const int SIZE = 4; + + // Constructor. + FrameHeader(); + + // Raw field access, see class comments for details. + uint8_t Sync1() const; + uint8_t Sync2() const; + uint8_t RawVersion() const; + uint8_t RawLayer() const; + uint8_t RawProtection() const; + uint8_t RawBitrate() const; + uint8_t RawSampleRate() const; + uint8_t Padding() const; + uint8_t Private() const; + uint8_t RawChannelMode() const; + + // Sampling rate frequency in Hz. + int32_t SampleRate() const; + + // Number of audio channels. + int32_t Channels() const; + + // Samples per frames, static depending on MPEG version and layer. + int32_t SamplesPerFrame() const; + + // Slot size used for padding, static depending on MPEG layer. + int32_t SlotSize() const; + + // Bitrate in kbps, can vary between frames. + int32_t Bitrate() const; + + // MPEG layer (0->invalid, 1->I, 2->II, 3->III). + int32_t Layer() const; + + // Returns whether the parsed data is a valid frame header up to the given + // byte position. + bool IsValid(const int aPos) const; + + // Returns whether the parsed data is a complete and valid frame header. + bool IsValid() const; + + // Resets the state to allow for a new parsing session. + void Reset(); + + // Parses the next provided byte. + // Returns whether the byte creates a valid sequence up to this point. + bool ParseNext(const uint8_t c); + + private: + // Updates the parser state machine with the provided next byte. + // Returns whether the provided byte is a valid next byte in the sequence. + bool Update(const uint8_t c); + + // The currently parsed byte sequence. + uint8_t mRaw[SIZE]; + + // The current byte position in the parsed sequence. Reset via Reset and + // incremented via Update. + int mPos; + }; + + // VBR frames may contain Xing or VBRI headers for additional info, we use + // this class to parse them and access this info. + class VBRHeader { + public: + // Synchronize with vbr_header TYPE_STR on change. + enum VBRHeaderType { + NONE = 0, + XING, + VBRI + }; + + // Constructor. + VBRHeader(); + + // Returns the parsed VBR header type, or NONE if no valid header found. + VBRHeaderType Type() const; + + // Returns the total number of audio frames (excluding the VBR header frame) + // expected in the stream/file. + const Maybe& NumAudioFrames() const; + + // Returns the expected size of the stream. + const Maybe& NumBytes() const; + + // Returns the VBR scale factor (0: best quality, 100: lowest quality). + const Maybe& Scale() const; + + // Returns true iff Xing/Info TOC (table of contents) is present. + bool IsTOCPresent() const; + + // Returns whether the header is valid (type XING or VBRI). + bool IsValid() const; + + // Returns whether the header is valid and contains reasonable non-zero field values. + bool IsComplete() const; + + // Returns the byte offset for the given duration percentage as a factor + // (0: begin, 1.0: end). + int64_t Offset(float aDurationFac) const; + + // Parses contents of given ByteReader for a valid VBR header. + // The offset of the passed ByteReader needs to point to an MPEG frame begin, + // as a VBRI-style header is searched at a fixed offset relative to frame begin. + // Returns whether a valid VBR header was found in the range. + bool Parse(mp4_demuxer::ByteReader* aReader); + + private: + // Parses contents of given ByteReader for a valid Xing header. + // The initial ByteReader offset will be preserved. + // Returns whether a valid Xing header was found in the range. + bool ParseXing(mp4_demuxer::ByteReader* aReader); + + // Parses contents of given ByteReader for a valid VBRI header. + // The initial ByteReader offset will be preserved. It also needs to point + // to the beginning of a valid MPEG frame, as VBRI headers are searched + // at a fixed offset relative to frame begin. + // Returns whether a valid VBRI header was found in the range. + bool ParseVBRI(mp4_demuxer::ByteReader* aReader); + + // The total number of frames expected as parsed from a VBR header. + Maybe mNumAudioFrames; + + // The total number of bytes expected in the stream. + Maybe mNumBytes; + + // The VBR scale factor. + Maybe mScale; + + // The TOC table mapping duration percentage to byte offset. + std::vector mTOC; + + // The detected VBR header type. + VBRHeaderType mType; + }; + + // Frame meta container used to parse and hold a frame header and side info. + class Frame { + public: + // Returns the length of the frame excluding the header in bytes. + int32_t Length() const; + + // Returns the parsed frame header. + const FrameHeader& Header() const; + + // Resets the frame header and data. + void Reset(); + + // Parses the next provided byte. + // Returns whether the byte creates a valid sequence up to this point. + bool ParseNext(uint8_t c); + + private: + // The currently parsed frame header. + FrameHeader mHeader; + }; + + // Constructor. + FrameParser(); + + // Returns the currently parsed frame. Reset via Reset or EndFrameSession. + const Frame& CurrentFrame() const; + + // Returns the previously parsed frame. Reset via Reset. + const Frame& PrevFrame() const; + + // Returns the first parsed frame. Reset via Reset. + const Frame& FirstFrame() const; + + // Returns the parsed ID3 header. Note: check for validity. + const ID3Parser::ID3Header& ID3Header() const; + + // Returns the parsed VBR header info. Note: check for validity by type. + const VBRHeader& VBRInfo() const; + + // Resets the parser. + void Reset(); + + // Resets all frame data, but not the ID3Header. + // Don't use between frames as first frame data is reset. + void ResetFrameData(); + + // Clear the last parsed frame to allow for next frame parsing, i.e.: + // - sets PrevFrame to CurrentFrame + // - resets the CurrentFrame + // - resets ID3Header if no valid header was parsed yet + void EndFrameSession(); + + // Parses contents of given ByteReader for a valid frame header and returns true + // if one was found. After returning, the variable passed to 'aBytesToSkip' holds + // the amount of bytes to be skipped (if any) in order to jump across a large + // ID3v2 tag spanning multiple buffers. + bool Parse(mp4_demuxer::ByteReader* aReader, uint32_t* aBytesToSkip); + + // Parses contents of given ByteReader for a valid VBR header. + // The offset of the passed ByteReader needs to point to an MPEG frame begin, + // as a VBRI-style header is searched at a fixed offset relative to frame begin. + // Returns whether a valid VBR header was found. + bool ParseVBRHeader(mp4_demuxer::ByteReader* aReader); + +private: + // ID3 header parser. + ID3Parser mID3Parser; + + // VBR header parser. + VBRHeader mVBRHeader; + + // We keep the first parsed frame around for static info access, the + // previously parsed frame for debugging and the currently parsed frame. + Frame mFirstFrame; + Frame mFrame; + Frame mPrevFrame; +}; + +// The MP3 demuxer used to extract MPEG frames and side information out of +// MPEG streams. +class MP3TrackDemuxer : public MediaTrackDemuxer { +public: + // Constructor, expecting a valid media resource. + explicit MP3TrackDemuxer(MediaResource* aSource); + + // Initializes the track demuxer by reading the first frame for meta data. + // Returns initialization success state. + bool Init(); + + // Returns the total stream length if known, -1 otherwise. + int64_t StreamLength() const; + + // Returns the estimated stream duration, or a 0-duration if unknown. + media::TimeUnit Duration() const; + + // Returns the estimated duration up to the given frame number, + // or a 0-duration if unknown. + media::TimeUnit Duration(int64_t aNumFrames) const; + + // Returns the estimated current seek position time. + media::TimeUnit SeekPosition() const; + + const FrameParser::Frame& LastFrame() const; + RefPtr DemuxSample(); + + const ID3Parser::ID3Header& ID3Header() const; + const FrameParser::VBRHeader& VBRInfo() const; + + // MediaTrackDemuxer interface. + UniquePtr GetInfo() const override; + RefPtr Seek(media::TimeUnit aTime) override; + RefPtr GetSamples(int32_t aNumSamples = 1) override; + void Reset() override; + RefPtr SkipToNextRandomAccessPoint( + media::TimeUnit aTimeThreshold) override; + int64_t GetResourceOffset() const override; + media::TimeIntervals GetBuffered() override; + +private: + // Destructor. + ~MP3TrackDemuxer() {} + + // Fast approximate seeking to given time. + media::TimeUnit FastSeek(const media::TimeUnit& aTime); + + // Seeks by scanning the stream up to the given time for more accurate results. + media::TimeUnit ScanUntil(const media::TimeUnit& aTime); + + // Finds the first valid frame and returns its byte range if found + // or a null-byte range otherwise. + MediaByteRange FindFirstFrame(); + + // Finds the next valid frame and returns its byte range if found + // or a null-byte range otherwise. + MediaByteRange FindNextFrame(); + + // Skips the next frame given the provided byte range. + bool SkipNextFrame(const MediaByteRange& aRange); + + // Returns the next MPEG frame, if available. + already_AddRefed GetNextFrame(const MediaByteRange& aRange); + + // Updates post-read meta data. + void UpdateState(const MediaByteRange& aRange); + + // Returns the estimated offset for the given frame index. + int64_t OffsetFromFrameIndex(int64_t aFrameIndex) const; + + // Returns the estimated frame index for the given offset. + int64_t FrameIndexFromOffset(int64_t aOffset) const; + + // Returns the estimated frame index for the given time. + int64_t FrameIndexFromTime(const media::TimeUnit& aTime) const; + + // Reads aSize bytes into aBuffer from the source starting at aOffset. + // Returns the actual size read. + int32_t Read(uint8_t* aBuffer, int64_t aOffset, int32_t aSize); + + // Returns the average frame length derived from the previously parsed frames. + double AverageFrameLength() const; + + // The (hopefully) MPEG resource. + MediaResourceIndex mSource; + + // MPEG frame parser used to detect frames and extract side info. + FrameParser mParser; + + // Current byte offset in the source stream. + int64_t mOffset; + + // Byte offset of the begin of the first frame, or 0 if none parsed yet. + int64_t mFirstFrameOffset; + + // Total parsed frames. + uint64_t mNumParsedFrames; + + // Current frame index. + int64_t mFrameIndex; + + // Sum of parsed frames' lengths in bytes. + uint64_t mTotalFrameLen; + + // Samples per frame metric derived from frame headers or 0 if none available. + int32_t mSamplesPerFrame; + + // Samples per second metric derived from frame headers or 0 if none available. + int32_t mSamplesPerSecond; + + // Channel count derived from frame headers or 0 if none available. + int32_t mChannels; + + // Audio track config info. + UniquePtr mInfo; +}; + +} // namespace mozilla + +#endif diff --git a/dom/media/mp3/moz.build b/dom/media/mp3/moz.build new file mode 100644 index 000000000..596d061f8 --- /dev/null +++ b/dom/media/mp3/moz.build @@ -0,0 +1,17 @@ +# -*- 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 += [ + 'MP3Decoder.h', + 'MP3Demuxer.h', +] + +UNIFIED_SOURCES += [ + 'MP3Decoder.cpp', + 'MP3Demuxer.cpp', +] + +FINAL_LIBRARY = 'xul' -- cgit v1.2.3 From 45f442a2a04bfc299c994e2712c3553278d46e6c Mon Sep 17 00:00:00 2001 From: trav90 Date: Sat, 8 Dec 2018 23:13:55 -0600 Subject: Add missing #include --- dom/media/WebVTTListener.h | 1 + 1 file changed, 1 insertion(+) diff --git a/dom/media/WebVTTListener.h b/dom/media/WebVTTListener.h index 67271664a..461d7f00d 100644 --- a/dom/media/WebVTTListener.h +++ b/dom/media/WebVTTListener.h @@ -10,6 +10,7 @@ #include "nsIStreamListener.h" #include "nsIChannelEventSink.h" #include "nsIInterfaceRequestor.h" +#include "nsCOMPtr.h" #include "nsCycleCollectionParticipant.h" class nsIWebVTTParserWrapper; -- cgit v1.2.3