summaryrefslogtreecommitdiffstats
path: root/dom/media/flac/FlacFrameParser.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/flac/FlacFrameParser.cpp')
-rw-r--r--dom/media/flac/FlacFrameParser.cpp244
1 files changed, 244 insertions, 0 deletions
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