Bug 1943650 - Command-line --help output misformatted after --dbus-service. r=emilio
[gecko.git] / dom / media / flac / FlacFrameParser.cpp
blobfb8ebcb846133497f7a47825b133975ae4f4387f
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "FlacFrameParser.h"
8 #include "nsTArray.h"
9 #include "OggCodecState.h"
10 #include "OpusParser.h"
11 #include "VideoUtils.h"
12 #include "BufferReader.h"
13 #include "mozilla/ResultExtensions.h"
14 #include "mozilla/Try.h"
16 namespace mozilla {
18 #define OGG_FLAC_METADATA_TYPE_STREAMINFO 0x7F
19 #define FLAC_STREAMINFO_SIZE 34
21 #define BITMASK(x) ((1ULL << x) - 1)
23 enum {
24 FLAC_METADATA_TYPE_STREAMINFO = 0,
25 FLAC_METADATA_TYPE_PADDING,
26 FLAC_METADATA_TYPE_APPLICATION,
27 FLAC_METADATA_TYPE_SEEKTABLE,
28 FLAC_METADATA_TYPE_VORBIS_COMMENT,
29 FLAC_METADATA_TYPE_CUESHEET,
30 FLAC_METADATA_TYPE_PICTURE,
31 FLAC_METADATA_TYPE_INVALID = 127
34 FlacFrameParser::FlacFrameParser()
35 : mMinBlockSize(0),
36 mMaxBlockSize(0),
37 mMinFrameSize(0),
38 mMaxFrameSize(0),
39 mNumFrames(0),
40 mFullMetadata(false),
41 mPacketCount(0) {};
43 FlacFrameParser::~FlacFrameParser() = default;
45 uint32_t FlacFrameParser::HeaderBlockLength(const uint8_t* aPacket) const {
46 uint32_t extra = 4;
47 if (aPacket[0] == 'f') {
48 // This must be the first block read, which contains the fLaC signature.
49 aPacket += 4;
50 extra += 4;
52 return (BigEndian::readUint32(aPacket) & BITMASK(24)) + extra;
55 Result<Ok, nsresult> FlacFrameParser::DecodeHeaderBlock(const uint8_t* aPacket,
56 size_t aLength) {
57 if (aLength < 4 || aPacket[0] == 0xff) {
58 // Not a header block.
59 return Err(NS_ERROR_FAILURE);
61 BufferReader br(aPacket, aLength);
63 mPacketCount++;
65 if (aPacket[0] == 'f') {
66 if (mPacketCount != 1 || memcmp(br.Read(4), "fLaC", 4) ||
67 br.Remaining() != FLAC_STREAMINFO_SIZE + 4) {
68 return Err(NS_ERROR_FAILURE);
71 uint8_t blockHeader;
72 MOZ_TRY_VAR(blockHeader, br.ReadU8());
73 // blockType is a misnomer as it could indicate here either a packet type
74 // should it points to the start of a Flac in Ogg metadata, or an actual
75 // block type as per the flac specification.
76 uint32_t blockType = blockHeader & 0x7f;
77 bool lastBlock = blockHeader & 0x80;
79 if (blockType == OGG_FLAC_METADATA_TYPE_STREAMINFO) {
80 if (mPacketCount != 1 || memcmp(br.Read(4), "FLAC", 4) ||
81 br.Remaining() != FLAC_STREAMINFO_SIZE + 12) {
82 return Err(NS_ERROR_FAILURE);
84 uint32_t major;
85 MOZ_TRY_VAR(major, br.ReadU8());
86 if (major != 1) {
87 // unsupported version;
88 return Err(NS_ERROR_FAILURE);
90 MOZ_TRY(br.ReadU8()); // minor version
91 uint32_t header;
92 MOZ_TRY_VAR(header, br.ReadU16());
93 mNumHeaders = Some(header);
94 br.Read(4); // fLaC
95 MOZ_TRY_VAR(blockType, br.ReadU8());
96 blockType &= BITMASK(7);
97 // First METADATA_BLOCK_STREAMINFO
98 if (blockType != FLAC_METADATA_TYPE_STREAMINFO) {
99 // First block must be a stream info.
100 return Err(NS_ERROR_FAILURE);
104 uint32_t blockDataSize;
105 MOZ_TRY_VAR(blockDataSize, br.ReadU24());
106 const uint8_t* blockDataStart = br.Peek(blockDataSize);
107 if (!blockDataStart) {
108 // Incomplete block.
109 return Err(NS_ERROR_FAILURE);
112 switch (blockType) {
113 case FLAC_METADATA_TYPE_STREAMINFO: {
114 if (mPacketCount != 1 || blockDataSize != FLAC_STREAMINFO_SIZE) {
115 // STREAMINFO must be the first metadata block found, and its size
116 // is constant.
117 return Err(NS_ERROR_FAILURE);
120 MOZ_TRY_VAR(mMinBlockSize, br.ReadU16());
121 MOZ_TRY_VAR(mMaxBlockSize, br.ReadU16());
122 MOZ_TRY_VAR(mMinFrameSize, br.ReadU24());
123 MOZ_TRY_VAR(mMaxFrameSize, br.ReadU24());
125 uint64_t blob;
126 MOZ_TRY_VAR(blob, br.ReadU64());
127 uint32_t sampleRate = (blob >> 44) & BITMASK(20);
128 if (!sampleRate) {
129 return Err(NS_ERROR_FAILURE);
131 uint32_t numChannels = ((blob >> 41) & BITMASK(3)) + 1;
132 if (numChannels > FLAC_MAX_CHANNELS) {
133 return Err(NS_ERROR_FAILURE);
135 uint32_t bps = ((blob >> 36) & BITMASK(5)) + 1;
136 if (bps > 24) {
137 return Err(NS_ERROR_FAILURE);
139 mNumFrames = blob & BITMASK(36);
141 mInfo.mMimeType = "audio/flac";
142 mInfo.mRate = sampleRate;
143 mInfo.mChannels = numChannels;
144 mInfo.mBitDepth = bps;
145 FlacCodecSpecificData flacCodecSpecificData;
146 flacCodecSpecificData.mStreamInfoBinaryBlob->AppendElements(
147 blockDataStart, blockDataSize);
148 mInfo.mCodecSpecificConfig =
149 AudioCodecSpecificVariant{std::move(flacCodecSpecificData)};
150 auto duration = media::TimeUnit(mNumFrames, sampleRate);
151 mInfo.mDuration = duration.IsValid() ? duration : media::TimeUnit::Zero();
152 mParser = MakeUnique<OpusParser>();
153 break;
155 case FLAC_METADATA_TYPE_VORBIS_COMMENT: {
156 if (!mParser) {
157 // We must have seen a valid streaminfo first.
158 return Err(NS_ERROR_FAILURE);
160 nsTArray<uint8_t> comments(blockDataSize + 8);
161 comments.AppendElements("OpusTags", 8);
162 comments.AppendElements(blockDataStart, blockDataSize);
163 if (!mParser->DecodeTags(comments.Elements(), comments.Length())) {
164 return Err(NS_ERROR_FAILURE);
166 break;
168 default:
169 break;
172 if (mNumHeaders && mPacketCount > mNumHeaders.ref() + 1) {
173 // Received too many header block. assuming invalid.
174 return Err(NS_ERROR_FAILURE);
177 if (lastBlock || (mNumHeaders && mNumHeaders.ref() + 1 == mPacketCount)) {
178 mFullMetadata = true;
181 return Ok();
184 int64_t FlacFrameParser::BlockDuration(const uint8_t* aPacket,
185 size_t aLength) const {
186 if (!mInfo.IsValid()) {
187 return -1;
189 if (mMinBlockSize == mMaxBlockSize) {
190 // block size is fixed, use this instead of looking at the frame header.
191 return mMinBlockSize;
193 // TODO
194 return 0;
197 Result<bool, nsresult> FlacFrameParser::IsHeaderBlock(const uint8_t* aPacket,
198 size_t aLength) const {
199 // Ogg Flac header
200 // The one-byte packet type 0x7F
201 // The four-byte ASCII signature "FLAC", i.e. 0x46, 0x4C, 0x41, 0x43
203 // Flac header:
204 // "fLaC", the FLAC stream marker in ASCII, meaning byte 0 of the stream is
205 // 0x66, followed by 0x4C 0x61 0x43
207 // If we detect either a ogg or plain flac header, then it must be valid.
208 if (aLength < 4 || aPacket[0] == 0xff) {
209 // A header is at least 4 bytes.
210 return false;
212 if (aPacket[0] == 0x7f) {
213 // Ogg packet
214 BufferReader br(aPacket + 1, aLength - 1);
215 const uint8_t* signature = br.Read(4);
216 return signature && !memcmp(signature, "FLAC", 4);
218 BufferReader br(aPacket, aLength - 1);
219 const uint8_t* signature = br.Read(4);
220 if (signature && !memcmp(signature, "fLaC", 4)) {
221 // Flac start header, must have STREAMINFO as first metadata block;
222 uint32_t blockType;
223 MOZ_TRY_VAR(blockType, br.ReadU8());
224 blockType &= 0x7f;
225 return blockType == FLAC_METADATA_TYPE_STREAMINFO;
227 char type = aPacket[0] & 0x7f;
228 return type >= 1 && type <= 6;
231 UniquePtr<MetadataTags> FlacFrameParser::GetTags() const {
232 if (!mParser) {
233 return nullptr;
236 auto tags = MakeUnique<MetadataTags>();
237 for (uint32_t i = 0; i < mParser->mTags.Length(); i++) {
238 OggCodecState::AddVorbisComment(tags, mParser->mTags[i].Data(),
239 mParser->mTags[i].Length());
242 return tags;
245 } // namespace mozilla