diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /dom/media/flac | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | uxp-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz |
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/media/flac')
-rw-r--r-- | dom/media/flac/FlacDecoder.cpp | 52 | ||||
-rw-r--r-- | dom/media/flac/FlacDecoder.h | 30 | ||||
-rw-r--r-- | dom/media/flac/FlacDemuxer.cpp | 1074 | ||||
-rw-r--r-- | dom/media/flac/FlacDemuxer.h | 108 | ||||
-rw-r--r-- | dom/media/flac/FlacFrameParser.cpp | 251 | ||||
-rw-r--r-- | dom/media/flac/FlacFrameParser.h | 64 | ||||
-rw-r--r-- | dom/media/flac/moz.build | 24 |
7 files changed, 1603 insertions, 0 deletions
diff --git a/dom/media/flac/FlacDecoder.cpp b/dom/media/flac/FlacDecoder.cpp new file mode 100644 index 0000000000..3109713dbe --- /dev/null +++ b/dom/media/flac/FlacDecoder.cpp @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FlacDecoder.h" +#include "FlacDemuxer.h" +#include "MediaDecoderStateMachine.h" +#include "MediaFormatReader.h" +#include "MediaPrefs.h" + +namespace mozilla { + +MediaDecoder* +FlacDecoder::Clone(MediaDecoderOwner* aOwner) +{ + if (!IsEnabled()) + return nullptr; + + return new FlacDecoder(aOwner); +} + +MediaDecoderStateMachine* +FlacDecoder::CreateStateMachine() +{ + RefPtr<MediaDecoderReader> reader = + new MediaFormatReader(this, new FlacDemuxer(GetResource())); + return new MediaDecoderStateMachine(this, reader); +} + +/* static */ bool +FlacDecoder::IsEnabled() +{ +#ifdef MOZ_FFVPX + return MediaPrefs::FlacEnabled(); +#else + // Until bug 1295886 is fixed. + return false; +#endif +} + +/* static */ bool +FlacDecoder::CanHandleMediaType(const nsACString& aType, + const nsAString& aCodecs) +{ + return IsEnabled() && + (aType.EqualsASCII("audio/flac") || aType.EqualsASCII("audio/x-flac") || + aType.EqualsASCII("application/x-flac")); +} + +} // namespace mozilla diff --git a/dom/media/flac/FlacDecoder.h b/dom/media/flac/FlacDecoder.h new file mode 100644 index 0000000000..304ee62f8b --- /dev/null +++ b/dom/media/flac/FlacDecoder.h @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef FLAC_DECODER_H_ +#define FLAC_DECODER_H_ + +#include "MediaDecoder.h" + +namespace mozilla { + +class FlacDecoder : public MediaDecoder { +public: + // MediaDecoder interface. + explicit FlacDecoder(MediaDecoderOwner* aOwner) : MediaDecoder(aOwner) {} + MediaDecoder* Clone(MediaDecoderOwner* aOwner) override; + MediaDecoderStateMachine* CreateStateMachine() override; + + // Returns true if the Flac backend is pref'ed 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 // !FLAC_DECODER_H_ diff --git a/dom/media/flac/FlacDemuxer.cpp b/dom/media/flac/FlacDemuxer.cpp new file mode 100644 index 0000000000..7a98edbee6 --- /dev/null +++ b/dom/media/flac/FlacDemuxer.cpp @@ -0,0 +1,1074 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FlacDemuxer.h" + +#include "mozilla/Maybe.h" +#include "mp4_demuxer/BitReader.h" +#include "nsAutoPtr.h" +#include "prenv.h" +#include "FlacFrameParser.h" +#include "VideoUtils.h" +#include "TimeUnits.h" + +#ifdef PR_LOGGING +extern mozilla::LazyLogModule gMediaDemuxerLog; +#define LOG(msg, ...) \ + MOZ_LOG(gMediaDemuxerLog, LogLevel::Debug, ("FlacDemuxer " msg, ##__VA_ARGS__)) +#define LOGV(msg, ...) \ + MOZ_LOG(gMediaDemuxerLog, LogLevel::Verbose, ("FlacDemuxer " msg, ##__VA_ARGS__)) +#else +#define LOG(msg, ...) do {} while (false) +#define LOGV(msg, ...) do {} while (false) +#endif + +using namespace mozilla::media; + +namespace mozilla { +namespace flac { + +// flac::FrameHeader - Holds the flac frame header and its parsing +// state. + +#define FLAC_MAX_CHANNELS 8 +#define FLAC_MIN_BLOCKSIZE 16 +#define FLAC_MAX_BLOCKSIZE 65535 +#define FLAC_MIN_FRAME_SIZE 11 +#define FLAC_MAX_FRAME_HEADER_SIZE 16 +#define FLAC_MAX_FRAME_SIZE (FLAC_MAX_FRAME_HEADER_SIZE \ + +FLAC_MAX_BLOCKSIZE*FLAC_MAX_CHANNELS*3) + +class FrameHeader { +public: + const AudioInfo& Info() const { return mInfo; } + + uint32_t Size() const { return mSize; } + + bool IsValid() const { return mValid; } + + // Return the index (in samples) from the beginning of the track. + int64_t Index() const { return mIndex; } + + // Parse the current packet and check that it made a valid flac frame header. + // From https://xiph.org/flac/format.html#frame_header + // A valid header is one that can be decoded without error and that has a + // valid CRC. + // aPacket must points to a buffer that is at least FLAC_MAX_FRAME_HEADER_SIZE + // bytes. + bool Parse(const uint8_t* aPacket) + { + mp4_demuxer::BitReader br(aPacket, FLAC_MAX_FRAME_HEADER_SIZE); + + // Frame sync code. + if ((br.ReadBits(15) & 0x7fff) != 0x7ffc) { + return false; + } + + // Variable block size stream code. + mVariableBlockSize = br.ReadBit(); + + // Block size and sample rate codes. + int bs_code = br.ReadBits(4); + int sr_code = br.ReadBits(4); + + // Channels and decorrelation. + int ch_mode = br.ReadBits(4); + if (ch_mode < FLAC_MAX_CHANNELS) { + mInfo.mChannels = ch_mode + 1; + } else if (ch_mode < FLAC_MAX_CHANNELS + FLAC_CHMODE_MID_SIDE) { + // This is a special flac channels, we can't handle those yet. Treat it + // as stereo. + mInfo.mChannels = 2; + } else { + // invalid channel mode + return false; + } + + // Bits per sample. + int bps_code = br.ReadBits(3); + if (bps_code == 3 || bps_code == 7) { + // Invalid sample size code. + return false; + } + mInfo.mBitDepth = FlacSampleSizeTable[bps_code]; + + // Reserved bit, most be 1. + if (br.ReadBit()) { + // Broken stream, invalid padding. + return false; + } + + // Sample or frame count. + int64_t frame_or_sample_num = br.ReadUTF8(); + if (frame_or_sample_num < 0) { + // Sample/frame number invalid. + return false; + } + + // Blocksize + if (bs_code == 0) { + // reserved blocksize code + return false; + } else if (bs_code == 6) { + mBlocksize = br.ReadBits(8) + 1; + } else if (bs_code == 7) { + mBlocksize = br.ReadBits(16) + 1; + } else { + mBlocksize = FlacBlocksizeTable[bs_code]; + } + + // The sample index is either: + // 1- coded sample number if blocksize is variable or + // 2- coded frame number if blocksize is known. + // A frame is made of Blocksize sample. + mIndex = mVariableBlockSize + ? frame_or_sample_num + : frame_or_sample_num * mBlocksize; + + // Sample rate. + if (sr_code < 12) { + mInfo.mRate = FlacSampleRateTable[sr_code]; + } else if (sr_code == 12) { + mInfo.mRate = br.ReadBits(8) * 1000; + } else if (sr_code == 13) { + mInfo.mRate = br.ReadBits(16); + } else if (sr_code == 14) { + mInfo.mRate = br.ReadBits(16) * 10; + } else { + // Illegal sample rate code. + return false; + } + + // Header CRC-8 check. + uint8_t crc = 0; + for (uint32_t i = 0; i < br.BitCount() / 8; i++) { + crc = CRC8Table[crc ^ aPacket[i]]; + } + mValid = crc == br.ReadBits(8); + mSize = br.BitCount() / 8; + + if (mValid) { + // Set the mimetype to make it a valid AudioInfo. + mInfo.mMimeType = "audio/flac"; + } + + return mValid; + } + +private: + friend class Frame; + enum { + FLAC_CHMODE_INDEPENDENT = 0, + FLAC_CHMODE_LEFT_SIDE, + FLAC_CHMODE_RIGHT_SIDE, + FLAC_CHMODE_MID_SIDE, + }; + AudioInfo mInfo; + // Index in samples from start; + int64_t mIndex = 0; + bool mVariableBlockSize = false; + uint32_t mBlocksize = 0;; + uint32_t mSize = 0; + bool mValid = false; + + static const int FlacSampleRateTable[16]; + static const int32_t FlacBlocksizeTable[16]; + static const uint8_t FlacSampleSizeTable[8]; + static const uint8_t CRC8Table[256]; +}; + +const int FrameHeader::FlacSampleRateTable[16] = +{ 0, + 88200, 176400, 192000, + 8000, 16000, 22050, 24000, 32000, 44100, 48000, 96000, + 0, 0, 0, 0 +}; + +const int32_t FrameHeader::FlacBlocksizeTable[16] = +{ + 0 , 192 , 576<<0, 576<<1, 576<<2, 576<<3, 0, 0, + 256<<0, 256<<1, 256<<2, 256<<3, 256<<4, 256<<5, 256<<6, 256<<7 +}; + +const uint8_t FrameHeader::FlacSampleSizeTable[8] = { 0, 8, 12, 0, 16, 20, 24, 0 }; + +const uint8_t FrameHeader::CRC8Table[256] = +{ + 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, + 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D, + 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, + 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D, + 0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, + 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD, + 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, + 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD, + 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, + 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA, + 0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, + 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A, + 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, + 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A, + 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, + 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A, + 0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, + 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4, + 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, + 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4, + 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, + 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44, + 0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, + 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34, + 0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, + 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63, + 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, + 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13, + 0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, + 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83, + 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, + 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3 +}; + +// flac::Frame - Frame meta container used to parse and hold a frame +// header and side info. +class Frame { +public: + + // The FLAC signature is made of 14 bits set to 1; however the 15th bit is + // mandatorily set to 0, so we need to find either of 0xfffc or 0xfffd 2-bytes + // signature. We first use a bitmask to see if 0xfc or 0xfd is present. And if + // so we check for the whole signature. + // aData must be pointing to a buffer at least + // aLength + FLAC_MAX_FRAME_HEADER_SIZE bytes. + int64_t FindNext(const uint8_t* aData, const uint32_t aLength) + { + uint32_t modOffset = aLength % 4; + uint32_t i, j; + + for (i = 0; i < modOffset; i++) { + if ((BigEndian::readUint16(aData + i) & 0xfffe) == 0xfff8) { + if (mHeader.Parse(aData + i)) { + return i; + } + } + } + + for (; i < aLength; i += 4) { + uint32_t x = BigEndian::readUint32(aData + i); + if (((x & ~(x + 0x01010101)) & 0x80808080)) { + for (j = 0; j < 4; j++) { + if ((BigEndian::readUint16(aData + i + j) & 0xfffe) == 0xfff8) { + if (mHeader.Parse(aData + i + j)) { + return i + j; + } + } + } + } + } + return -1; + } + + // Find the next frame start in the current resource. + // On exit return true, offset is set and resource points to the frame found. + bool FindNext(MediaResourceIndex& aResource) + { + static const int BUFFER_SIZE = 4096; + + Reset(); + + nsTArray<char> buffer; + int64_t originalOffset = aResource.Tell(); + int64_t offset = originalOffset; + uint32_t innerOffset = 0; + + do { + uint32_t read = 0; + buffer.SetLength(BUFFER_SIZE + innerOffset); + nsresult rv = + aResource.Read(buffer.Elements() + innerOffset, BUFFER_SIZE, &read); + if (NS_FAILED(rv)) { + return false; + } + + if (read < FLAC_MAX_FRAME_HEADER_SIZE) { + // Assume that we can't have a valid frame in such small content, we + // must have reached EOS. + // So we're done. + mEOS = true; + return false; + } + + const size_t bufSize = read + innerOffset - FLAC_MAX_FRAME_HEADER_SIZE; + int64_t foundOffset = + FindNext(reinterpret_cast<uint8_t*>(buffer.Elements()), bufSize); + + if (foundOffset >= 0) { + SetOffset(aResource, foundOffset + offset); + return true; + } + + // Scan the next block; + offset += bufSize; + buffer.RemoveElementsAt(0, bufSize); + innerOffset = buffer.Length(); + } while (offset - originalOffset < FLAC_MAX_FRAME_SIZE); + + return false; + } + + int64_t Offset() const { return mOffset; } + + const AudioInfo& Info() const { return Header().Info(); } + + void SetEndOffset(int64_t aOffset) { mSize = aOffset - mOffset; } + + void SetEndTime(int64_t aIndex) + { + if (aIndex > Header().mIndex) { + mDuration = aIndex - Header().mIndex; + } + } + + uint32_t Size() const { return mSize; } + + TimeUnit Time() const + { + if (!IsValid()) { + return TimeUnit::Invalid(); + } + MOZ_ASSERT(Header().Info().mRate, "Invalid Frame. Need Header"); + return FramesToTimeUnit(Header().mIndex, Header().Info().mRate); + } + + TimeUnit Duration() const + { + if (!IsValid()) { + return TimeUnit(); + } + MOZ_ASSERT(Header().Info().mRate, "Invalid Frame. Need Header"); + return FramesToTimeUnit(mDuration, Header().Info().mRate); + } + + // Returns the parsed frame header. + const FrameHeader& Header() const { return mHeader; } + + bool IsValid() const { return mHeader.IsValid(); } + + bool EOS() const { return mEOS; } + + void SetRate(uint32_t aRate) { mHeader.mInfo.mRate = aRate; }; + + void SetBitDepth(uint32_t aBitDepth) { mHeader.mInfo.mBitDepth = aBitDepth; } + + void SetInvalid() { mHeader.mValid = false; } + + // Resets the frame header and data. + void Reset() { *this = Frame(); } + +private: + void SetOffset(MediaResourceIndex& aResource, int64_t aOffset) + { + mOffset = aOffset; + aResource.Seek(SEEK_SET, mOffset); + } + + // The offset to the start of the header. + int64_t mOffset = 0; + uint32_t mSize = 0; + uint32_t mDuration = 0; + bool mEOS = false; + + // The currently parsed frame header. + FrameHeader mHeader; + +}; + +class FrameParser { +public: + + // Returns the currently parsed frame. Reset via EndFrameSession. + const Frame& CurrentFrame() const { return mFrame; } + + // Returns the first parsed frame. + const Frame& FirstFrame() const { return mFirstFrame; } + + // Clear the last parsed frame to allow for next frame parsing + void EndFrameSession() + { + mNextFrame.Reset(); + mFrame.Reset(); + } + + // Attempt to find the next frame. + bool FindNextFrame(MediaResourceIndex& aResource) + { + mFrame = mNextFrame; + if (GetNextFrame(aResource)) { + if (!mFrame.IsValid()) { + mFrame = mNextFrame; + // We need two frames to be able to start playing (or have reached EOS). + GetNextFrame(aResource); + } + } + + if (mFrame.IsValid()) { + if (mNextFrame.EOS()) { + mFrame.SetEndOffset(aResource.Tell()); + } else if (mNextFrame.IsValid()) { + mFrame.SetEndOffset(mNextFrame.Offset()); + mFrame.SetEndTime(mNextFrame.Header().Index()); + } + } + + if (!mFirstFrame.IsValid()) { + mFirstFrame = mFrame; + } + return mFrame.IsValid(); + } + + // Convenience methods to external FlacFrameParser ones. + bool IsHeaderBlock(const uint8_t* aPacket, size_t aLength) const + { + return mParser.IsHeaderBlock(aPacket, aLength); + } + + uint32_t HeaderBlockLength(const uint8_t* aPacket) const + { + return mParser.HeaderBlockLength(aPacket); + } + + bool DecodeHeaderBlock(const uint8_t* aPacket, size_t aLength) + { + return mParser.DecodeHeaderBlock(aPacket, aLength); + } + + bool HasFullMetadata() const { return mParser.HasFullMetadata(); } + + AudioInfo Info() const { return mParser.mInfo; } + + // Return a hash table with tag metadata. + MetadataTags* GetTags() const { return mParser.GetTags(); } + +private: + bool GetNextFrame(MediaResourceIndex& aResource) + { + while (mNextFrame.FindNext(aResource)) { + // Move our offset slightly, so that we don't find the same frame at the + // next FindNext call. + aResource.Seek(SEEK_CUR, mNextFrame.Header().Size()); + if (mFrame.IsValid() + && mNextFrame.Offset() - mFrame.Offset() < FLAC_MAX_FRAME_SIZE + && !CheckCRC16AtOffset(mFrame.Offset(), + mNextFrame.Offset(), + aResource)) { + // The frame doesn't match its CRC or would be too far, skip it.. + continue; + } + CheckFrameData(); + break; + } + return mNextFrame.IsValid(); + } + + bool CheckFrameData() + { + if (mNextFrame.Header().Info().mRate == 0 + || mNextFrame.Header().Info().mBitDepth == 0) { + if (!Info().IsValid()) { + // We can only use the STREAMINFO data if we have one. + mNextFrame.SetInvalid(); + } else { + if (mNextFrame.Header().Info().mRate == 0) { + mNextFrame.SetRate(Info().mRate); + } + if (mNextFrame.Header().Info().mBitDepth == 0) { + mNextFrame.SetBitDepth(Info().mBitDepth); + } + } + } + return mNextFrame.IsValid(); + } + + bool CheckCRC16AtOffset(int64_t aStart, int64_t aEnd, + MediaResourceIndex& aResource) const + { + int64_t size = aEnd - aStart; + if (size <= 0) { + return false; + } + UniquePtr<char[]> buffer(new char[size]); + uint32_t read = 0; + if (NS_FAILED(aResource.ReadAt(aStart, buffer.get(), + size, &read)) + || read != size) { + NS_WARNING("Couldn't read frame content"); + return false; + } + + uint16_t crc = 0; + uint8_t* buf = reinterpret_cast<uint8_t*>(buffer.get()); + const uint8_t *end = buf + size; + while (buf < end) { + crc = CRC16Table[((uint8_t)crc) ^ *buf++] ^ (crc >> 8); + } + return !crc; + } + + const uint16_t CRC16Table[256] = + { + 0x0000, 0x0580, 0x0F80, 0x0A00, 0x1B80, 0x1E00, 0x1400, 0x1180, + 0x3380, 0x3600, 0x3C00, 0x3980, 0x2800, 0x2D80, 0x2780, 0x2200, + 0x6380, 0x6600, 0x6C00, 0x6980, 0x7800, 0x7D80, 0x7780, 0x7200, + 0x5000, 0x5580, 0x5F80, 0x5A00, 0x4B80, 0x4E00, 0x4400, 0x4180, + 0xC380, 0xC600, 0xCC00, 0xC980, 0xD800, 0xDD80, 0xD780, 0xD200, + 0xF000, 0xF580, 0xFF80, 0xFA00, 0xEB80, 0xEE00, 0xE400, 0xE180, + 0xA000, 0xA580, 0xAF80, 0xAA00, 0xBB80, 0xBE00, 0xB400, 0xB180, + 0x9380, 0x9600, 0x9C00, 0x9980, 0x8800, 0x8D80, 0x8780, 0x8200, + 0x8381, 0x8601, 0x8C01, 0x8981, 0x9801, 0x9D81, 0x9781, 0x9201, + 0xB001, 0xB581, 0xBF81, 0xBA01, 0xAB81, 0xAE01, 0xA401, 0xA181, + 0xE001, 0xE581, 0xEF81, 0xEA01, 0xFB81, 0xFE01, 0xF401, 0xF181, + 0xD381, 0xD601, 0xDC01, 0xD981, 0xC801, 0xCD81, 0xC781, 0xC201, + 0x4001, 0x4581, 0x4F81, 0x4A01, 0x5B81, 0x5E01, 0x5401, 0x5181, + 0x7381, 0x7601, 0x7C01, 0x7981, 0x6801, 0x6D81, 0x6781, 0x6201, + 0x2381, 0x2601, 0x2C01, 0x2981, 0x3801, 0x3D81, 0x3781, 0x3201, + 0x1001, 0x1581, 0x1F81, 0x1A01, 0x0B81, 0x0E01, 0x0401, 0x0181, + 0x0383, 0x0603, 0x0C03, 0x0983, 0x1803, 0x1D83, 0x1783, 0x1203, + 0x3003, 0x3583, 0x3F83, 0x3A03, 0x2B83, 0x2E03, 0x2403, 0x2183, + 0x6003, 0x6583, 0x6F83, 0x6A03, 0x7B83, 0x7E03, 0x7403, 0x7183, + 0x5383, 0x5603, 0x5C03, 0x5983, 0x4803, 0x4D83, 0x4783, 0x4203, + 0xC003, 0xC583, 0xCF83, 0xCA03, 0xDB83, 0xDE03, 0xD403, 0xD183, + 0xF383, 0xF603, 0xFC03, 0xF983, 0xE803, 0xED83, 0xE783, 0xE203, + 0xA383, 0xA603, 0xAC03, 0xA983, 0xB803, 0xBD83, 0xB783, 0xB203, + 0x9003, 0x9583, 0x9F83, 0x9A03, 0x8B83, 0x8E03, 0x8403, 0x8183, + 0x8002, 0x8582, 0x8F82, 0x8A02, 0x9B82, 0x9E02, 0x9402, 0x9182, + 0xB382, 0xB602, 0xBC02, 0xB982, 0xA802, 0xAD82, 0xA782, 0xA202, + 0xE382, 0xE602, 0xEC02, 0xE982, 0xF802, 0xFD82, 0xF782, 0xF202, + 0xD002, 0xD582, 0xDF82, 0xDA02, 0xCB82, 0xCE02, 0xC402, 0xC182, + 0x4382, 0x4602, 0x4C02, 0x4982, 0x5802, 0x5D82, 0x5782, 0x5202, + 0x7002, 0x7582, 0x7F82, 0x7A02, 0x6B82, 0x6E02, 0x6402, 0x6182, + 0x2002, 0x2582, 0x2F82, 0x2A02, 0x3B82, 0x3E02, 0x3402, 0x3182, + 0x1382, 0x1602, 0x1C02, 0x1982, 0x0802, 0x0D82, 0x0782, 0x0202, + }; + + FlacFrameParser mParser; + // We keep the first parsed frame around for static info access + // and the currently parsed frame. + Frame mFirstFrame; + Frame mNextFrame; + Frame mFrame; +}; + +} // namespace flac + +// FlacDemuxer + +FlacDemuxer::FlacDemuxer(MediaResource* aSource) + : mSource(aSource) +{} + +bool +FlacDemuxer::InitInternal() +{ + if (!mTrackDemuxer) { + mTrackDemuxer = new FlacTrackDemuxer(mSource); + } + return mTrackDemuxer->Init(); +} + +RefPtr<FlacDemuxer::InitPromise> +FlacDemuxer::Init() +{ + if (!InitInternal()) { + LOG("Init() failure: waiting for data"); + + return InitPromise::CreateAndReject( + NS_ERROR_DOM_MEDIA_DEMUXER_ERR, __func__); + } + + LOG("Init() successful"); + return InitPromise::CreateAndResolve(NS_OK, __func__); +} + +bool +FlacDemuxer::HasTrackType(TrackInfo::TrackType aType) const +{ + return aType == TrackInfo::kAudioTrack; +} + +uint32_t +FlacDemuxer::GetNumberTracks(TrackInfo::TrackType aType) const +{ + return (aType == TrackInfo::kAudioTrack) ? 1 : 0; +} + +already_AddRefed<MediaTrackDemuxer> +FlacDemuxer::GetTrackDemuxer(TrackInfo::TrackType aType, uint32_t aTrackNumber) +{ + if (!mTrackDemuxer) { + return nullptr; + } + + return RefPtr<FlacTrackDemuxer>(mTrackDemuxer).forget(); +} + +bool +FlacDemuxer::IsSeekable() const +{ + return mTrackDemuxer && mTrackDemuxer->IsSeekable(); +} + +// FlacTrackDemuxer +FlacTrackDemuxer::FlacTrackDemuxer(MediaResource* aSource) + : mSource(aSource) + , mParser(new flac::FrameParser()) + , mTotalFrameLen(0) +{ + Reset(); +} + +FlacTrackDemuxer::~FlacTrackDemuxer() +{ +} + +bool +FlacTrackDemuxer::Init() +{ + static const int BUFFER_SIZE = 4096; + + // First check if we have a valid Flac start. + char buffer[BUFFER_SIZE]; + const uint8_t* ubuffer = reinterpret_cast<uint8_t*>(buffer); // only needed due to type constraints of ReadAt. + int64_t offset = 0; + + do { + uint32_t read = 0; + nsresult ret = mSource.ReadAt(offset, buffer, BUFFER_SIZE, &read); + if (NS_FAILED(ret) || read < BUFFER_SIZE) { + // Assume that if we can't read that many bytes while parsing the header, + // that something is wrong. + return false; + } + if (!mParser->IsHeaderBlock(ubuffer, BUFFER_SIZE)) { + // Not a header and we haven't reached the end of the metadata blocks. + // Will fall back to using the frames header instead. + break; + } + uint32_t sizeHeader = mParser->HeaderBlockLength(ubuffer); + RefPtr<MediaByteBuffer> block = + mSource.MediaReadAt(offset, sizeHeader); + if (!block || block->Length() != sizeHeader) { + break; + } + if (!mParser->DecodeHeaderBlock(block->Elements(), sizeHeader)) { + break; + } + offset += sizeHeader; + } while (!mParser->HasFullMetadata()); + + // First flac frame is found after the metadata. + // Can seek there immediately to avoid reparsing it all. + mSource.Seek(SEEK_SET, offset); + + // Find the first frame to fully initialise our parser. + if (mParser->FindNextFrame(mSource)) { + // Ensure that the next frame returned will be the first. + mSource.Seek(SEEK_SET, mParser->FirstFrame().Offset()); + mParser->EndFrameSession(); + } else if (!mParser->Info().IsValid() || !mParser->FirstFrame().IsValid()) { + // We must find at least a frame to determine the metadata. + // We can't play this stream. + return false; + } + + if (!mParser->Info().IsValid() || !mParser->Info().mDuration) { + // Check if we can look at the last frame for the end time to determine the + // duration when we don't have any. + TimeAtEnd(); + } + + return true; +} + +UniquePtr<TrackInfo> +FlacTrackDemuxer::GetInfo() const +{ + if (mParser->Info().IsValid()) { + // We have a proper metadata header. + UniquePtr<TrackInfo> info = mParser->Info().Clone(); + nsAutoPtr<MetadataTags> tags(mParser->GetTags()); + if (tags) { + for (auto iter = tags->Iter(); !iter.Done(); iter.Next()) { + info->mTags.AppendElement(MetadataTag(iter.Key(), iter.Data())); + } + } + return info; + } else if (mParser->FirstFrame().Info().IsValid()) { + // Use the first frame header. + UniquePtr<TrackInfo> info = mParser->FirstFrame().Info().Clone(); + info->mDuration = Duration().ToMicroseconds(); + return info; + } + return nullptr; +} + +bool +FlacTrackDemuxer::IsSeekable() const +{ + // For now we only allow seeking if a STREAMINFO block was found and with + // a known number of samples (duration is set). + return mParser->Info().IsValid() && mParser->Info().mDuration; +} + +RefPtr<FlacTrackDemuxer::SeekPromise> +FlacTrackDemuxer::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 +FlacTrackDemuxer::FastSeek(const TimeUnit& aTime) +{ + LOG("FastSeek(%f) avgFrameLen=%f mParsedFramesDuration=%f offset=%lld", + aTime.ToSeconds(), AverageFrameLength(), + mParsedFramesDuration.ToSeconds(), GetResourceOffset()); + + // Invalidate current frames in the parser. + mParser->EndFrameSession(); + + if (!mParser->FirstFrame().IsValid()) { + // Something wrong, and there's nothing to seek to anyway, so we can + // do whatever here. + mSource.Seek(SEEK_SET, 0); + return TimeUnit(); + } + + if (aTime <= mParser->FirstFrame().Time()) { + // We're attempting to seek prior the first frame, return the first frame. + mSource.Seek(SEEK_SET, mParser->FirstFrame().Offset()); + return mParser->FirstFrame().Time(); + } + + // We look for the seek position using a bisection search, starting where the + // estimated position might be using the average frame length. + // Typically, with flac such approximation is typically useless. + + // Estimate where the position might be. + int64_t pivot = + aTime.ToSeconds() * AverageFrameLength() + mParser->FirstFrame().Offset(); + + // Time in seconds where we can stop seeking and will continue using ScanUntil. + static const int GAP_THRESHOLD = 5; + int64_t first = mParser->FirstFrame().Offset(); + int64_t last = mSource.GetLength(); + Maybe<int64_t> lastFoundOffset; + uint32_t iterations = 0; + TimeUnit timeSeekedTo; + + do { + iterations++; + mSource.Seek(SEEK_SET, pivot); + flac::Frame frame; + if (!frame.FindNext(mSource)) { + NS_WARNING("We should have found a point"); + break; + } + timeSeekedTo = frame.Time(); + + LOGV("FastSeek: interation:%u found:%f @ %lld", + iterations, timeSeekedTo.ToSeconds(), frame.Offset()); + + if (lastFoundOffset && lastFoundOffset.ref() == frame.Offset()) { + // Same frame found twice. We're done. + break; + } + lastFoundOffset = Some(frame.Offset()); + + if (frame.Time() == aTime) { + break; + } + if (aTime > frame.Time() + && aTime - frame.Time() <= TimeUnit::FromSeconds(GAP_THRESHOLD)) { + // We're close enough to the target, experimentation shows that bisection + // search doesn't help much after that. + break; + } + if (frame.Time() > aTime) { + last = pivot; + pivot -= (pivot - first) / 2; + } else { + first = pivot; + pivot += (last - pivot) / 2; + } + } while (true); + + if (lastFoundOffset) { + mSource.Seek(SEEK_SET, lastFoundOffset.ref()); + } + + return timeSeekedTo; +} + +TimeUnit +FlacTrackDemuxer::ScanUntil(const TimeUnit& aTime) +{ + LOG("ScanUntil(%f avgFrameLen=%f mParsedFramesDuration=%f offset=%lld", + aTime.ToSeconds(), AverageFrameLength(), + mParsedFramesDuration.ToSeconds(), mParser->CurrentFrame().Offset()); + + if (!mParser->FirstFrame().IsValid() + || aTime <= mParser->FirstFrame().Time()) { + return FastSeek(aTime); + } + + int64_t previousOffset = 0; + TimeUnit previousTime; + while (FindNextFrame().IsValid() && mParser->CurrentFrame().Time() < aTime) { + previousOffset = mParser->CurrentFrame().Offset(); + previousTime = mParser->CurrentFrame().Time(); + } + + if (!mParser->CurrentFrame().IsValid()) { + // We reached EOS. + return Duration(); + } + + // Seek back to the last frame found prior the target. + mParser->EndFrameSession(); + mSource.Seek(SEEK_SET, previousOffset); + return previousTime; +} + +RefPtr<FlacTrackDemuxer::SamplesPromise> +FlacTrackDemuxer::GetSamples(int32_t aNumSamples) +{ + LOGV("GetSamples(%d) Begin offset=%lld mParsedFramesDuration=%f" + " mTotalFrameLen=%llu", + aNumSamples, GetResourceOffset(), mParsedFramesDuration.ToSeconds(), + mTotalFrameLen); + + if (!aNumSamples) { + return SamplesPromise::CreateAndReject( + NS_ERROR_DOM_MEDIA_DEMUXER_ERR, __func__); + } + + RefPtr<SamplesHolder> frames = new SamplesHolder(); + + while (aNumSamples--) { + RefPtr<MediaRawData> frame(GetNextFrame(FindNextFrame())); + if (!frame) + break; + + frames->mSamples.AppendElement(frame); + } + + LOGV("GetSamples() End mSamples.Length=%u aNumSamples=%d offset=%lld" + " mParsedFramesDuration=%f mTotalFrameLen=%llu", + frames->mSamples.Length(), aNumSamples, GetResourceOffset(), + mParsedFramesDuration.ToSeconds(), mTotalFrameLen); + + if (frames->mSamples.IsEmpty()) { + return SamplesPromise::CreateAndReject( + NS_ERROR_DOM_MEDIA_END_OF_STREAM, __func__); + } + + return SamplesPromise::CreateAndResolve(frames, __func__); +} + +void +FlacTrackDemuxer::Reset() +{ + LOG("Reset()"); + MOZ_ASSERT(mParser); + if (mParser->FirstFrame().IsValid()) { + mSource.Seek(SEEK_SET, mParser->FirstFrame().Offset()); + } else { + mSource.Seek(SEEK_SET, 0); + } + mParser->EndFrameSession(); +} + +RefPtr<FlacTrackDemuxer::SkipAccessPointPromise> +FlacTrackDemuxer::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 +FlacTrackDemuxer::GetResourceOffset() const +{ + return mSource.Tell(); +} + +TimeIntervals +FlacTrackDemuxer::GetBuffered() +{ + TimeUnit duration = Duration(); + + if (duration <= TimeUnit()) { + return TimeIntervals(); + } + + // We could simply parse the cached data instead and read the timestamps. + // However, for now this will do. + AutoPinned<MediaResource> stream(mSource.GetResource()); + return GetEstimatedBufferedTimeRanges(stream, duration.ToMicroseconds()); +} + +const flac::Frame& +FlacTrackDemuxer::FindNextFrame() +{ + LOGV("FindNext() Begin offset=%lld mParsedFramesDuration=%f" + " mTotalFrameLen=%llu", + GetResourceOffset(), mParsedFramesDuration.ToSeconds(), mTotalFrameLen); + + if (mParser->FindNextFrame(mSource)) { + // Update our current progress stats. + mParsedFramesDuration = + std::max(mParsedFramesDuration, + mParser->CurrentFrame().Time() - mParser->FirstFrame().Time() + + mParser->CurrentFrame().Duration()); + mTotalFrameLen = + std::max<uint64_t>(mTotalFrameLen, + mParser->CurrentFrame().Offset() + - mParser->FirstFrame().Offset() + + mParser->CurrentFrame().Size()); + + LOGV("FindNext() End time=%f offset=%lld mParsedFramesDuration=%f" + " mTotalFrameLen=%llu", + mParser->CurrentFrame().Time().ToSeconds(), GetResourceOffset(), + mParsedFramesDuration.ToSeconds(), mTotalFrameLen); + } + + return mParser->CurrentFrame(); +} + +already_AddRefed<MediaRawData> +FlacTrackDemuxer::GetNextFrame(const flac::Frame& aFrame) +{ + if (!aFrame.IsValid()) { + LOG("GetNextFrame() EOS"); + return nullptr; + } + + LOG("GetNextFrame() Begin(time=%f offset=%lld size=%u)", + aFrame.Time().ToSeconds(), aFrame.Offset(), aFrame.Size()); + + const int64_t offset = aFrame.Offset(); + const uint32_t size = aFrame.Size(); + + RefPtr<MediaRawData> frame = new MediaRawData(); + frame->mOffset = offset; + + nsAutoPtr<MediaRawDataWriter> frameWriter(frame->CreateWriter()); + if (!frameWriter->SetSize(size)) { + LOG("GetNext() Exit failed to allocated media buffer"); + return nullptr; + } + + const uint32_t read = Read(frameWriter->Data(), offset, size); + if (read != size) { + LOG("GetNextFrame() Exit read=%u frame->Size=%u", read, frame->Size()); + return nullptr; + } + + frame->mTime = aFrame.Time().ToMicroseconds(); + frame->mDuration = aFrame.Duration().ToMicroseconds(); + frame->mTimecode = frame->mTime; + frame->mOffset = aFrame.Offset(); + frame->mKeyframe = true; + + MOZ_ASSERT(frame->mTime >= 0); + MOZ_ASSERT(frame->mDuration >= 0); + + return frame.forget(); +} + +int32_t +FlacTrackDemuxer::Read(uint8_t* aBuffer, int64_t aOffset, int32_t aSize) +{ + uint32_t read = 0; + const nsresult rv = mSource.ReadAt(aOffset, reinterpret_cast<char*>(aBuffer), + static_cast<uint32_t>(aSize), &read); + NS_ENSURE_SUCCESS(rv, 0); + return static_cast<int32_t>(read); +} + +double +FlacTrackDemuxer::AverageFrameLength() const +{ + if (mParsedFramesDuration.ToMicroseconds()) { + return mTotalFrameLen / mParsedFramesDuration.ToSeconds(); + } + + return 0.0; +} + +TimeUnit +FlacTrackDemuxer::Duration() const +{ + return std::max(mParsedFramesDuration, + TimeUnit::FromMicroseconds(mParser->Info().mDuration)); +} + +TimeUnit +FlacTrackDemuxer::TimeAtEnd() +{ + // Scan the last 128kB if available to determine the last frame. + static const int OFFSET_FROM_END = 128 * 1024; + + // Seek to the end of the file and attempt to find the last frame. + MediaResourceIndex source(mSource.GetResource()); + TimeUnit previousDuration; + TimeUnit previousTime; + + const int64_t streamLen = mSource.GetLength(); + if (streamLen < 0) { + return TimeUnit::FromInfinity(); + } + + flac::FrameParser parser; + + source.Seek(SEEK_SET, std::max<int64_t>(0LL, streamLen - OFFSET_FROM_END)); + while (parser.FindNextFrame(source)) { + // FFmpeg flac muxer can generate a last frame with earlier than the others. + previousTime = std::max(previousTime, parser.CurrentFrame().Time()); + if (parser.CurrentFrame().Duration() > TimeUnit()) { + // The last frame doesn't have a duration, so only update our duration + // if we do have one. + previousDuration = parser.CurrentFrame().Duration(); + } + if (source.Tell() >= streamLen) { + // Limit the read, in case the length change half-way. + break; + } + } + + // Update our current progress stats. + mParsedFramesDuration = + previousTime + previousDuration - mParser->FirstFrame().Time(); + mTotalFrameLen = streamLen - mParser->FirstFrame().Offset(); + + return mParsedFramesDuration; +} + +/* static */ bool +FlacDemuxer::FlacSniffer(const uint8_t* aData, const uint32_t aLength) +{ + if (aLength < FLAC_MAX_FRAME_HEADER_SIZE) { + return false; + } + + flac::Frame frame; + return frame.FindNext(aData, aLength - FLAC_MAX_FRAME_HEADER_SIZE) >= 0; +} + +} // namespace mozilla diff --git a/dom/media/flac/FlacDemuxer.h b/dom/media/flac/FlacDemuxer.h new file mode 100644 index 0000000000..fd0e35c94e --- /dev/null +++ b/dom/media/flac/FlacDemuxer.h @@ -0,0 +1,108 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef FLAC_DEMUXER_H_ +#define FLAC_DEMUXER_H_ + +#include "mozilla/Attributes.h" +#include "MediaDataDemuxer.h" +#include "MediaResource.h" +namespace mozilla { + +namespace flac { +class Frame; +class FrameParser; +} +class FlacTrackDemuxer; + + +class FlacDemuxer : public MediaDataDemuxer { +public: + // MediaDataDemuxer interface. + explicit FlacDemuxer(MediaResource* aSource); + RefPtr<InitPromise> Init() override; + bool HasTrackType(TrackInfo::TrackType aType) const override; + uint32_t GetNumberTracks(TrackInfo::TrackType aType) const override; + already_AddRefed<MediaTrackDemuxer> GetTrackDemuxer( + TrackInfo::TrackType aType, uint32_t aTrackNumber) override; + bool IsSeekable() const override; + + // Return true if a valid flac frame header could be found. + static bool FlacSniffer(const uint8_t* aData, const uint32_t aLength); + +private: + bool InitInternal(); + + RefPtr<MediaResource> mSource; + RefPtr<FlacTrackDemuxer> mTrackDemuxer; +}; + +class FlacTrackDemuxer : public MediaTrackDemuxer { +public: + explicit FlacTrackDemuxer(MediaResource* aSource); + + // Initializes the track demuxer by reading the first frame for meta data. + // Returns initialization success state. + bool Init(); + + // MediaTrackDemuxer interface. + UniquePtr<TrackInfo> GetInfo() const override; + RefPtr<SeekPromise> Seek(media::TimeUnit aTime) override; + RefPtr<SamplesPromise> GetSamples(int32_t aNumSamples = 1) override; + void Reset() override; + int64_t GetResourceOffset() const override; + media::TimeIntervals GetBuffered() override; + RefPtr<SkipAccessPointPromise> SkipToNextRandomAccessPoint( + media::TimeUnit aTimeThreshold) override; + + bool IsSeekable() const; + +private: + // Destructor. + ~FlacTrackDemuxer(); + + // Returns the estimated stream duration, or a 0-duration if unknown. + media::TimeUnit Duration() const; + media::TimeUnit TimeAtEnd(); + + // 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 next valid frame and return it. + const flac::Frame& FindNextFrame(); + + // Returns the next ADTS frame, if available. + already_AddRefed<MediaRawData> GetNextFrame(const flac::Frame& aFrame); + + // 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) Flac resource. + MediaResourceIndex mSource; + + // Flac frame parser used to detect frames and extract side info. + nsAutoPtr<flac::FrameParser> mParser; + + // Total duration of parsed frames. + media::TimeUnit mParsedFramesDuration; + + // Sum of parsed frames' lengths in bytes. + uint64_t mTotalFrameLen; + + // Audio track config info. + UniquePtr<AudioInfo> mInfo; +}; + +} // mozilla + +#endif // !FLAC_DEMUXER_H_ diff --git a/dom/media/flac/FlacFrameParser.cpp b/dom/media/flac/FlacFrameParser.cpp new file mode 100644 index 0000000000..f2d6c1367e --- /dev/null +++ b/dom/media/flac/FlacFrameParser.cpp @@ -0,0 +1,251 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FlacFrameParser.h" +#include "mp4_demuxer/ByteReader.h" +#include "nsTArray.h" +#include "OggCodecState.h" +#include "OpusParser.h" +#include "VideoUtils.h" + +using mp4_demuxer::ByteReader; + +namespace mozilla +{ + +#define OGG_FLAC_METADATA_TYPE_STREAMINFO 0x7F +#define FLAC_STREAMINFO_SIZE 34 + +#define BITMASK(x) ((1ULL << x)-1) + +enum +{ + FLAC_METADATA_TYPE_STREAMINFO = 0, + FLAC_METADATA_TYPE_PADDING, + FLAC_METADATA_TYPE_APPLICATION, + FLAC_METADATA_TYPE_SEEKTABLE, + FLAC_METADATA_TYPE_VORBIS_COMMENT, + FLAC_METADATA_TYPE_CUESHEET, + FLAC_METADATA_TYPE_PICTURE, + FLAC_METADATA_TYPE_INVALID = 127 +}; + +FlacFrameParser::FlacFrameParser() + : mMinBlockSize(0) + , mMaxBlockSize(0) + , mMinFrameSize(0) + , mMaxFrameSize(0) + , mNumFrames(0) + , mFullMetadata(false) + , mPacketCount(0) +{ +} + +FlacFrameParser::~FlacFrameParser() +{ +} + +uint32_t +FlacFrameParser::HeaderBlockLength(const uint8_t* aPacket) const +{ + uint32_t extra = 4; + if (aPacket[0] == 'f') { + // This must be the first block read, which contains the fLaC signature. + aPacket += 4; + extra += 4; + } + return (BigEndian::readUint32(aPacket) & BITMASK(24)) + extra; +} + +bool +FlacFrameParser::DecodeHeaderBlock(const uint8_t* aPacket, size_t aLength) +{ + if (aLength < 4 || aPacket[0] == 0xff) { + // Not a header block. + return false; + } + ByteReader br(aPacket, aLength); + + mPacketCount++; + + if (aPacket[0] == 'f') { + if (mPacketCount != 1 || memcmp(br.Read(4), "fLaC", 4) || + br.Remaining() != FLAC_STREAMINFO_SIZE + 4) { + return false; + } + aPacket += 4; + aLength -= 4; + } + uint8_t blockHeader = br.ReadU8(); + // blockType is a misnomer as it could indicate here either a packet type + // should it points to the start of a Flac in Ogg metadata, or an actual + // block type as per the flac specification. + uint32_t blockType = blockHeader & 0x7f; + bool lastBlock = blockHeader & 0x80; + + if (blockType == OGG_FLAC_METADATA_TYPE_STREAMINFO) { + if (mPacketCount != 1 || memcmp(br.Read(4), "FLAC", 4) || + br.Remaining() != FLAC_STREAMINFO_SIZE + 12) { + return false; + } + uint32_t major = br.ReadU8(); + if (major != 1) { + // unsupported version; + return false; + } + br.ReadU8(); // minor version + mNumHeaders = Some(uint32_t(br.ReadU16())); + br.Read(4); // fLaC + blockType = br.ReadU8() & BITMASK(7); + // First METADATA_BLOCK_STREAMINFO + if (blockType != FLAC_METADATA_TYPE_STREAMINFO) { + // First block must be a stream info. + return false; + } + } + + uint32_t blockDataSize = br.ReadU24(); + const uint8_t* blockDataStart = br.Peek(blockDataSize); + if (!blockDataStart) { + // Incomplete block. + return false; + } + + switch (blockType) { + case FLAC_METADATA_TYPE_STREAMINFO: + { + if (mPacketCount != 1 || blockDataSize != FLAC_STREAMINFO_SIZE) { + // STREAMINFO must be the first metadata block found, and its size + // is constant. + return false; + } + + mMinBlockSize = br.ReadU16(); + mMaxBlockSize = br.ReadU16(); + mMinFrameSize = br.ReadU24(); + mMaxFrameSize = br.ReadU24(); + + uint64_t blob = br.ReadU64(); + uint32_t sampleRate = (blob >> 44) & BITMASK(20); + if (!sampleRate) { + return false; + } + uint32_t numChannels = ((blob >> 41) & BITMASK(3)) + 1; + if (numChannels > FLAC_MAX_CHANNELS) { + return false; + } + uint32_t bps = ((blob >> 36) & BITMASK(5)) + 1; + if (bps > 24) { + return false; + } + mNumFrames = blob & BITMASK(36); + + mInfo.mMimeType = "audio/flac"; + mInfo.mRate = sampleRate; + mInfo.mChannels = numChannels; + mInfo.mBitDepth = bps; + mInfo.mCodecSpecificConfig->AppendElements(blockDataStart, blockDataSize); + CheckedInt64 duration = + SaferMultDiv(mNumFrames, USECS_PER_S, sampleRate); + mInfo.mDuration = duration.isValid() ? duration.value() : 0; + mParser = new OpusParser; + break; + } + case FLAC_METADATA_TYPE_VORBIS_COMMENT: + { + if (!mParser) { + // We must have seen a valid streaminfo first. + return false; + } + nsTArray<uint8_t> comments(blockDataSize + 8); + comments.AppendElements("OpusTags", 8); + comments.AppendElements(blockDataStart, blockDataSize); + if (!mParser->DecodeTags(comments.Elements(), comments.Length())) { + return false; + } + break; + } + default: + break; + } + + if (mNumHeaders && mPacketCount > mNumHeaders.ref() + 1) { + // Received too many header block. assuming invalid. + return false; + } + + if (lastBlock || (mNumHeaders && mNumHeaders.ref() + 1 == mPacketCount)) { + mFullMetadata = true; + } + + return true; +} + +int64_t +FlacFrameParser::BlockDuration(const uint8_t* aPacket, size_t aLength) const +{ + if (!mInfo.IsValid()) { + return -1; + } + if (mMinBlockSize == mMaxBlockSize) { + // block size is fixed, use this instead of looking at the frame header. + return mMinBlockSize; + } + // TODO + return 0; +} + +bool +FlacFrameParser::IsHeaderBlock(const uint8_t* aPacket, size_t aLength) const +{ + // Ogg Flac header + // The one-byte packet type 0x7F + // The four-byte ASCII signature "FLAC", i.e. 0x46, 0x4C, 0x41, 0x43 + + // Flac header: + // "fLaC", the FLAC stream marker in ASCII, meaning byte 0 of the stream is 0x66, followed by 0x4C 0x61 0x43 + + // If we detect either a ogg or plain flac header, then it must be valid. + if (aLength < 4 || aPacket[0] == 0xff) { + // A header is at least 4 bytes. + return false; + } + if (aPacket[0] == 0x7f) { + // Ogg packet + ByteReader br(aPacket + 1, aLength - 1); + const uint8_t* signature = br.Read(4); + return signature && !memcmp(signature, "FLAC", 4); + } + ByteReader br(aPacket, aLength - 1); + const uint8_t* signature = br.Read(4); + if (signature && !memcmp(signature, "fLaC", 4)) { + // Flac start header, must have STREAMINFO as first metadata block; + if (!br.CanRead8()) { + return false; + } + uint32_t blockType = br.ReadU8() & 0x7f; + return blockType == FLAC_METADATA_TYPE_STREAMINFO; + } + char type = aPacket[0] & 0x7f; + return type >= 1 && type <= 6; +} + +MetadataTags* +FlacFrameParser::GetTags() const +{ + MetadataTags* tags; + + tags = new MetadataTags; + for (uint32_t i = 0; i < mParser->mTags.Length(); i++) { + OggCodecState::AddVorbisComment(tags, + mParser->mTags[i].Data(), + mParser->mTags[i].Length()); + } + + return tags; +} + +} // namespace mozilla diff --git a/dom/media/flac/FlacFrameParser.h b/dom/media/flac/FlacFrameParser.h new file mode 100644 index 0000000000..aa1a6e15b8 --- /dev/null +++ b/dom/media/flac/FlacFrameParser.h @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef FLAC_FRAME_PARSER_H_ +#define FLAC_FRAME_PARSER_H_ + +#include "mozilla/Maybe.h" +#include "nsAutoPtr.h" +#include "MediaDecoder.h" // For MetadataTags +#include "MediaInfo.h" +#include "MediaResource.h" + +namespace mozilla +{ + +class OpusParser; + +// Decode a Flac Metadata block contained in either a ogg packet +// (https://xiph.org/flac/ogg_mapping.html) or in flac container +// (https://xiph.org/flac/format.html#frame_header) + +class FlacFrameParser +{ +public: + FlacFrameParser(); + ~FlacFrameParser(); + + bool IsHeaderBlock(const uint8_t* aPacket, size_t aLength) const; + // Return the length of the block header (METADATA_BLOCK_HEADER+ + // METADATA_BLOCK_DATA), aPacket must point to at least 4 + // bytes and to a valid block header start (as determined by IsHeaderBlock). + uint32_t HeaderBlockLength(const uint8_t* aPacket) const; + bool DecodeHeaderBlock(const uint8_t* aPacket, size_t aLength); + bool HasFullMetadata() const { return mFullMetadata; } + // Return the duration in frames found in the block. -1 if error + // such as invalid packet. + int64_t BlockDuration(const uint8_t* aPacket, size_t aLength) const; + + // Return a hash table with tag metadata. + MetadataTags* GetTags() const; + + AudioInfo mInfo; + +private: + bool ReconstructFlacGranulepos(void); + Maybe<uint32_t> mNumHeaders; + uint32_t mMinBlockSize; + uint32_t mMaxBlockSize; + uint32_t mMinFrameSize; + uint32_t mMaxFrameSize; + uint64_t mNumFrames; + bool mFullMetadata; + uint32_t mPacketCount; + + // Used to decode the vorbis comment metadata. + nsAutoPtr<OpusParser> mParser; +}; + +} + +#endif // FLAC_FRAME_PARSER_H_ diff --git a/dom/media/flac/moz.build b/dom/media/flac/moz.build new file mode 100644 index 0000000000..6933ded1f9 --- /dev/null +++ b/dom/media/flac/moz.build @@ -0,0 +1,24 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXPORTS += [ + 'FlacDecoder.h', + 'FlacDemuxer.h', + 'FlacFrameParser.h', +] + +UNIFIED_SOURCES += [ + 'FlacDecoder.cpp', + 'FlacDemuxer.cpp', + 'FlacFrameParser.cpp', +] + +CXXFLAGS += CONFIG['MOZ_LIBVPX_CFLAGS'] + +FINAL_LIBRARY = 'xul' + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] |