summaryrefslogtreecommitdiffstats
path: root/dom/media/flac
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/media/flac/FlacDecoder.cpp45
-rw-r--r--dom/media/flac/FlacDecoder.h30
-rw-r--r--dom/media/flac/FlacDemuxer.cpp1027
-rw-r--r--dom/media/flac/FlacDemuxer.h112
-rw-r--r--dom/media/flac/FlacFrameParser.cpp244
-rw-r--r--dom/media/flac/FlacFrameParser.h72
-rw-r--r--dom/media/flac/moz.build24
7 files changed, 1554 insertions, 0 deletions
diff --git a/dom/media/flac/FlacDecoder.cpp b/dom/media/flac/FlacDecoder.cpp
new file mode 100644
index 0000000000..2f205c9aae
--- /dev/null
+++ b/dom/media/flac/FlacDecoder.cpp
@@ -0,0 +1,45 @@
+/* -*- 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 "MediaContainerType.h"
+#include "mozilla/StaticPrefs_media.h"
+
+namespace mozilla {
+
+/* static */
+bool FlacDecoder::IsEnabled() {
+#ifdef MOZ_FFVPX
+ return StaticPrefs::media_flac_enabled();
+#else
+ return false;
+#endif
+}
+
+/* static */
+bool FlacDecoder::IsSupportedType(const MediaContainerType& aContainerType) {
+ return IsEnabled() &&
+ (aContainerType.Type() == MEDIAMIMETYPE("audio/flac") ||
+ aContainerType.Type() == MEDIAMIMETYPE("audio/x-flac") ||
+ aContainerType.Type() == MEDIAMIMETYPE("application/x-flac"));
+}
+
+/* static */
+nsTArray<UniquePtr<TrackInfo>> FlacDecoder::GetTracksInfo(
+ const MediaContainerType& aType) {
+ nsTArray<UniquePtr<TrackInfo>> tracks;
+ if (!IsSupportedType(aType)) {
+ return tracks;
+ }
+
+ tracks.AppendElement(
+ CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters(
+ "audio/flac"_ns, aType));
+
+ return tracks;
+}
+
+} // namespace mozilla
diff --git a/dom/media/flac/FlacDecoder.h b/dom/media/flac/FlacDecoder.h
new file mode 100644
index 0000000000..f40ae6c31a
--- /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 "mozilla/UniquePtr.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+
+class MediaContainerType;
+class TrackInfo;
+
+class FlacDecoder {
+ public:
+ // 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 IsSupportedType(const MediaContainerType& aContainerType);
+ static nsTArray<UniquePtr<TrackInfo>> GetTracksInfo(
+ const MediaContainerType& aType);
+};
+
+} // 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..fd6eea13c2
--- /dev/null
+++ b/dom/media/flac/FlacDemuxer.cpp
@@ -0,0 +1,1027 @@
+/* -*- 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 "BitReader.h"
+#include "prenv.h"
+#include "FlacFrameParser.h"
+#include "VideoUtils.h"
+#include "TimeUnits.h"
+
+extern mozilla::LazyLogModule gMediaDemuxerLog;
+#define LOG(msg, ...) \
+ DDMOZ_LOG(gMediaDemuxerLog, LogLevel::Debug, msg, ##__VA_ARGS__)
+#define LOGV(msg, ...) \
+ DDMOZ_LOG(gMediaDemuxerLog, LogLevel::Verbose, msg, ##__VA_ARGS__)
+
+using namespace mozilla::media;
+
+namespace mozilla {
+namespace flac {
+
+// flac::FrameHeader - Holds the flac frame header and its parsing
+// state.
+
+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.
+ bool Parse(const uint8_t* aPacket, size_t aBytes) {
+ BitReader br(aPacket, aBytes * 8);
+
+ // 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.
+ uint32_t 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, must be 0.
+ if (br.ReadBit()) {
+ // Broken stream, invalid padding.
+ return false;
+ }
+
+ // Sample or frame count.
+ uint64_t frame_or_sample_num = br.ReadUTF8();
+ if (frame_or_sample_num == UINT64_MAX) {
+ // 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;
+ mFrameOrSampleNum = static_cast<uint64_t>(frame_or_sample_num);
+
+ // 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 =
+#ifdef FUZZING
+ true;
+#else
+ crc == br.ReadBits(8);
+#endif
+ mSize = br.BitCount() / 8;
+
+ if (mValid) {
+ // Set the mimetype to make it a valid AudioInfo.
+ mInfo.mMimeType = "audio/flac";
+ // Set the codec specific data to flac, but leave it empty since we don't
+ // have METADATA_BLOCK_STREAMINFO in the frame.
+ mInfo.mCodecSpecificConfig =
+ AudioCodecSpecificVariant{FlacCodecSpecificData{}};
+ }
+
+ 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;
+ // mFrameOrSampleNum is either:
+ // 1- coded sample number if blocksize is variable or
+ // 2- coded frame number if blocksize is fixed.
+ // A frame is made of Blocksize sample.
+ uint64_t mFrameOrSampleNum = 0;
+ // 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 uint32_t FlacSampleRateTable[16];
+ static const uint32_t FlacBlocksizeTable[16];
+ static const uint8_t FlacSampleSizeTable[8];
+ static const uint8_t CRC8Table[256];
+};
+
+const uint32_t FrameHeader::FlacSampleRateTable[16] = {
+ 0, 88200, 176400, 192000, 8000, 16000, 22050, 24000,
+ 32000, 44100, 48000, 96000, 0, 0, 0, 0};
+
+const uint32_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.
+ int64_t FindNext(const uint8_t* aData, const uint32_t aLength) {
+ // The non-variable size of a FLAC header is 32 bits followed by variable
+ // size data and a 8 bits CRC.
+ // There's no need to read the last 4 bytes, it can never make a complete
+ // header.
+ if (aLength < 4) {
+ return -1;
+ }
+ 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, aLength - i)) {
+ return i;
+ }
+ }
+ }
+
+ for (; i < aLength - 4; 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, aLength - 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;
+ uint64_t originalOffset = static_cast<uint64_t>(aResource.Tell());
+ uint64_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;
+ }
+
+ const size_t bufSize = read + innerOffset;
+ int64_t foundOffset =
+ FindNext(reinterpret_cast<uint8_t*>(buffer.Elements()), bufSize);
+
+ if (foundOffset >= 0) {
+ SetOffset(aResource, static_cast<uint64_t>(foundOffset) + offset);
+ return true;
+ }
+
+ if (read < BUFFER_SIZE) {
+ // Nothing more to try on as we had reached EOS during the previous
+ // read.
+ mEOS = true;
+ return false;
+ }
+
+ // Scan the next block;
+ // We rewind a bit to re-try what could have been an incomplete packet.
+ // The maximum size of a FLAC header being FLAC_MAX_FRAME_HEADER_SIZE so
+ // we need to retry just after that amount.
+ offset += bufSize - (FLAC_MAX_FRAME_HEADER_SIZE + 1);
+ buffer.RemoveElementsAt(0, bufSize - (FLAC_MAX_FRAME_HEADER_SIZE + 1));
+ innerOffset = buffer.Length();
+ } while (offset - originalOffset < FLAC_MAX_FRAME_SIZE);
+
+ return false;
+ }
+
+ uint64_t Offset() const { return mOffset; }
+
+ const AudioInfo& Info() const { return Header().Info(); }
+
+ void SetEndOffset(uint64_t aOffset) { mSize = aOffset - mOffset; }
+
+ void SetEndTime(int64_t aIndex) {
+ if (aIndex > Header().mIndex) {
+ mDuration = aIndex - Header().mIndex;
+ }
+ }
+
+ void ResetStartTimeIfNeeded(const Frame& aReferenceFrame) {
+ if (Header().mVariableBlockSize ||
+ aReferenceFrame.Header().mVariableBlockSize ||
+ aReferenceFrame.Header().mBlocksize <= Header().mBlocksize) {
+ // Not a fixed size frame, or nothing to adjust.
+ return;
+ }
+ mHeader.mIndex =
+ Header().mFrameOrSampleNum * aReferenceFrame.Header().mBlocksize;
+ }
+
+ uint32_t Size() const { return mSize; }
+
+ TimeUnit Time() const {
+ if (!IsValid()) {
+ return TimeUnit::Invalid();
+ }
+ MOZ_ASSERT(Header().Info().mRate, "Invalid Frame. Need Header");
+ return media::TimeUnit(Header().mIndex, Header().Info().mRate);
+ }
+
+ TimeUnit Duration() const {
+ if (!IsValid()) {
+ return TimeUnit();
+ }
+ MOZ_ASSERT(Header().Info().mRate, "Invalid Frame. Need Header");
+ return media::TimeUnit(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, uint64_t aOffset) {
+ mOffset = aOffset;
+ aResource.Seek(SEEK_SET, mOffset);
+ }
+
+ // The offset to the start of the header.
+ uint64_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(static_cast<uint64_t>(aResource.Tell()));
+ // If the blocksize is fixed, the frame's starting sample number will be
+ // the frame number times the blocksize. However, the last block may
+ // have been incorrectly set as shorter than the stream blocksize.
+ // We recalculate the start time of this last sample using the first
+ // frame blocksize.
+ // TODO: should we use an overall counter of frames instead?
+ mFrame.ResetStartTimeIfNeeded(mFirstFrame);
+ } 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 {
+ auto res = mParser.IsHeaderBlock(aPacket, aLength);
+ return res.isOk() ? res.unwrap() : false;
+ }
+
+ 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).isOk();
+ }
+
+ bool HasFullMetadata() const { return mParser.HasFullMetadata(); }
+
+ AudioInfo Info() const { return mParser.mInfo; }
+
+ // Return a hash table with tag metadata.
+ UniquePtr<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[static_cast<size_t>(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);
+ }
+#ifdef FUZZING
+ return true;
+#else
+ return !crc;
+#endif
+ }
+
+ 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) {
+ DDLINKCHILD("source", aSource);
+}
+
+bool FlacDemuxer::InitInternal() {
+ if (!mTrackDemuxer) {
+ mTrackDemuxer = new FlacTrackDemuxer(mSource);
+ DDLINKCHILD("track demuxer", mTrackDemuxer.get());
+ }
+ 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__);
+}
+
+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) {
+ DDLINKCHILD("source", aSource);
+ Reset();
+}
+
+FlacTrackDemuxer::~FlacTrackDemuxer() = default;
+
+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 = // only needed due to type constraints of ReadAt.
+ reinterpret_cast<uint8_t*>(buffer);
+ 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.IsPositive()) {
+ // 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();
+ UniquePtr<MetadataTags> tags(mParser->GetTags());
+ if (tags) {
+ for (const auto& entry : *tags) {
+ info->mTags.AppendElement(MetadataTag(entry.GetKey(), entry.GetData()));
+ }
+ }
+ MOZ_ASSERT(info->IsAudio() &&
+ info->GetAsAudioInfo()
+ ->mCodecSpecificConfig.is<FlacCodecSpecificData>(),
+ "Should get flac specific data from parser");
+ return info;
+ } else if (mParser->FirstFrame().Info().IsValid()) {
+ // Use the first frame header.
+ UniquePtr<TrackInfo> info = mParser->FirstFrame().Info().Clone();
+ info->mDuration = Duration();
+ MOZ_ASSERT(info->IsAudio() &&
+ info->GetAsAudioInfo()
+ ->mCodecSpecificConfig.is<FlacCodecSpecificData>(),
+ "Should get flac specific data from parser");
+ 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.IsPositive();
+}
+
+RefPtr<FlacTrackDemuxer::SeekPromise> FlacTrackDemuxer::Seek(
+ const 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=%" PRId64,
+ 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<uint64_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 @ %" PRIu64, 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=%" PRId64,
+ 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=%" PRId64
+ " mParsedFramesDuration=%f"
+ " mTotalFrameLen=%" PRIu64,
+ 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;
+ if (!frame->HasValidTime()) {
+ return SamplesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_DEMUXER_ERR,
+ __func__);
+ }
+ frames->AppendSample(frame);
+ }
+
+ LOGV("GetSamples() End mSamples.Length=%zu aNumSamples=%d offset=%" PRId64
+ " mParsedFramesDuration=%f mTotalFrameLen=%" PRIu64,
+ frames->GetSamples().Length(), aNumSamples, GetResourceOffset(),
+ mParsedFramesDuration.ToSeconds(), mTotalFrameLen);
+
+ if (frames->GetSamples().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(const 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=%" PRId64
+ " mParsedFramesDuration=%f"
+ " mTotalFrameLen=%" PRIu64,
+ 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=%" PRId64
+ " mParsedFramesDuration=%f"
+ " mTotalFrameLen=%" PRIu64,
+ 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=%" PRId64 " size=%u)",
+ aFrame.Time().ToSeconds(), aFrame.Offset(), aFrame.Size());
+
+ const uint64_t offset = aFrame.Offset();
+ const uint32_t size = aFrame.Size();
+
+ RefPtr<MediaRawData> frame = new MediaRawData();
+ frame->mOffset = offset;
+
+ UniquePtr<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=%zu", read, frame->Size());
+ return nullptr;
+ }
+
+ frame->mTime = aFrame.Time();
+ frame->mDuration = aFrame.Duration();
+ frame->mTimecode = frame->mTime;
+ frame->mOffset = aFrame.Offset();
+ frame->mKeyframe = true;
+
+ MOZ_ASSERT(!frame->mTime.IsNegative());
+ MOZ_ASSERT(!frame->mDuration.IsNegative());
+
+ return frame.forget();
+}
+
+uint32_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 read;
+}
+
+double FlacTrackDemuxer::AverageFrameLength() const {
+ if (mParsedFramesDuration.ToMicroseconds()) {
+ return mTotalFrameLen / mParsedFramesDuration.ToSeconds();
+ }
+
+ return 0.0;
+}
+
+TimeUnit FlacTrackDemuxer::Duration() const {
+ return std::max(mParsedFramesDuration, 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 =
+ static_cast<uint64_t>(streamLen) - mParser->FirstFrame().Offset();
+
+ return mParsedFramesDuration;
+}
+
+/* static */
+bool FlacDemuxer::FlacSniffer(const uint8_t* aData, const uint32_t aLength) {
+ if (aLength < FLAC_MIN_FRAME_SIZE) {
+ return false;
+ }
+
+ flac::Frame frame;
+ return frame.FindNext(aData, aLength) >= 0;
+}
+
+} // namespace mozilla
diff --git a/dom/media/flac/FlacDemuxer.h b/dom/media/flac/FlacDemuxer.h
new file mode 100644
index 0000000000..cfbbfb7c57
--- /dev/null
+++ b/dom/media/flac/FlacDemuxer.h
@@ -0,0 +1,112 @@
+/* -*- 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;
+} // namespace flac
+class FlacTrackDemuxer;
+
+DDLoggedTypeDeclNameAndBase(FlacDemuxer, MediaDataDemuxer);
+DDLoggedTypeNameAndBase(FlacTrackDemuxer, MediaTrackDemuxer);
+
+class FlacDemuxer : public MediaDataDemuxer,
+ public DecoderDoctorLifeLogger<FlacDemuxer> {
+ public:
+ // MediaDataDemuxer interface.
+ explicit FlacDemuxer(MediaResource* aSource);
+ RefPtr<InitPromise> Init() 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 DecoderDoctorLifeLogger<FlacTrackDemuxer> {
+ 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(const 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(
+ const 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.
+ uint32_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.
+ UniquePtr<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;
+};
+
+} // namespace 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..c042a7d334
--- /dev/null
+++ b/dom/media/flac/FlacFrameParser.cpp
@@ -0,0 +1,244 @@
+/* -*- 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 "nsTArray.h"
+#include "OggCodecState.h"
+#include "OpusParser.h"
+#include "VideoUtils.h"
+#include "BufferReader.h"
+#include "mozilla/ResultExtensions.h"
+
+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() = default;
+
+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;
+}
+
+Result<Ok, nsresult> FlacFrameParser::DecodeHeaderBlock(const uint8_t* aPacket,
+ size_t aLength) {
+ if (aLength < 4 || aPacket[0] == 0xff) {
+ // Not a header block.
+ return Err(NS_ERROR_FAILURE);
+ }
+ BufferReader br(aPacket, aLength);
+
+ mPacketCount++;
+
+ if (aPacket[0] == 'f') {
+ if (mPacketCount != 1 || memcmp(br.Read(4), "fLaC", 4) ||
+ br.Remaining() != FLAC_STREAMINFO_SIZE + 4) {
+ return Err(NS_ERROR_FAILURE);
+ }
+ }
+ uint8_t blockHeader;
+ MOZ_TRY_VAR(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 Err(NS_ERROR_FAILURE);
+ }
+ uint32_t major;
+ MOZ_TRY_VAR(major, br.ReadU8());
+ if (major != 1) {
+ // unsupported version;
+ return Err(NS_ERROR_FAILURE);
+ }
+ MOZ_TRY(br.ReadU8()); // minor version
+ uint32_t header;
+ MOZ_TRY_VAR(header, br.ReadU16());
+ mNumHeaders = Some(header);
+ br.Read(4); // fLaC
+ MOZ_TRY_VAR(blockType, br.ReadU8());
+ blockType &= BITMASK(7);
+ // First METADATA_BLOCK_STREAMINFO
+ if (blockType != FLAC_METADATA_TYPE_STREAMINFO) {
+ // First block must be a stream info.
+ return Err(NS_ERROR_FAILURE);
+ }
+ }
+
+ uint32_t blockDataSize;
+ MOZ_TRY_VAR(blockDataSize, br.ReadU24());
+ const uint8_t* blockDataStart = br.Peek(blockDataSize);
+ if (!blockDataStart) {
+ // Incomplete block.
+ return Err(NS_ERROR_FAILURE);
+ }
+
+ 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 Err(NS_ERROR_FAILURE);
+ }
+
+ MOZ_TRY_VAR(mMinBlockSize, br.ReadU16());
+ MOZ_TRY_VAR(mMaxBlockSize, br.ReadU16());
+ MOZ_TRY_VAR(mMinFrameSize, br.ReadU24());
+ MOZ_TRY_VAR(mMaxFrameSize, br.ReadU24());
+
+ uint64_t blob;
+ MOZ_TRY_VAR(blob, br.ReadU64());
+ uint32_t sampleRate = (blob >> 44) & BITMASK(20);
+ if (!sampleRate) {
+ return Err(NS_ERROR_FAILURE);
+ }
+ uint32_t numChannels = ((blob >> 41) & BITMASK(3)) + 1;
+ if (numChannels > FLAC_MAX_CHANNELS) {
+ return Err(NS_ERROR_FAILURE);
+ }
+ uint32_t bps = ((blob >> 36) & BITMASK(5)) + 1;
+ if (bps > 24) {
+ return Err(NS_ERROR_FAILURE);
+ }
+ mNumFrames = blob & BITMASK(36);
+
+ mInfo.mMimeType = "audio/flac";
+ mInfo.mRate = sampleRate;
+ mInfo.mChannels = numChannels;
+ mInfo.mBitDepth = bps;
+ FlacCodecSpecificData flacCodecSpecificData;
+ flacCodecSpecificData.mStreamInfoBinaryBlob->AppendElements(
+ blockDataStart, blockDataSize);
+ mInfo.mCodecSpecificConfig =
+ AudioCodecSpecificVariant{std::move(flacCodecSpecificData)};
+ auto duration = media::TimeUnit(mNumFrames, sampleRate);
+ mInfo.mDuration = duration.IsValid() ? duration : media::TimeUnit::Zero();
+ mParser = MakeUnique<OpusParser>();
+ break;
+ }
+ case FLAC_METADATA_TYPE_VORBIS_COMMENT: {
+ if (!mParser) {
+ // We must have seen a valid streaminfo first.
+ return Err(NS_ERROR_FAILURE);
+ }
+ nsTArray<uint8_t> comments(blockDataSize + 8);
+ comments.AppendElements("OpusTags", 8);
+ comments.AppendElements(blockDataStart, blockDataSize);
+ if (!mParser->DecodeTags(comments.Elements(), comments.Length())) {
+ return Err(NS_ERROR_FAILURE);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ if (mNumHeaders && mPacketCount > mNumHeaders.ref() + 1) {
+ // Received too many header block. assuming invalid.
+ return Err(NS_ERROR_FAILURE);
+ }
+
+ if (lastBlock || (mNumHeaders && mNumHeaders.ref() + 1 == mPacketCount)) {
+ mFullMetadata = true;
+ }
+
+ return Ok();
+}
+
+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;
+}
+
+Result<bool, nsresult> 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
+ BufferReader br(aPacket + 1, aLength - 1);
+ const uint8_t* signature = br.Read(4);
+ return signature && !memcmp(signature, "FLAC", 4);
+ }
+ BufferReader 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;
+ uint32_t blockType;
+ MOZ_TRY_VAR(blockType, br.ReadU8());
+ blockType &= 0x7f;
+ return blockType == FLAC_METADATA_TYPE_STREAMINFO;
+ }
+ char type = aPacket[0] & 0x7f;
+ return type >= 1 && type <= 6;
+}
+
+UniquePtr<MetadataTags> FlacFrameParser::GetTags() const {
+ if (!mParser) {
+ return nullptr;
+ }
+
+ auto tags = MakeUnique<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..2bd457ea03
--- /dev/null
+++ b/dom/media/flac/FlacFrameParser.h
@@ -0,0 +1,72 @@
+/* -*- 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 "mozilla/Result.h"
+#include "MediaDecoder.h" // For MetadataTags
+#include "MediaInfo.h"
+#include "MediaResource.h"
+
+namespace mozilla {
+
+#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 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();
+
+ Result<bool, nsresult> 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;
+ Result<Ok, nsresult> 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.
+ UniquePtr<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.
+ UniquePtr<OpusParser> mParser;
+};
+
+} // namespace mozilla
+
+#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..0e3c8eae61
--- /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"
+
+# Add libFuzzer configuration directives
+include("/tools/fuzzing/libfuzzer-config.mozbuild")