DXR is a code search and navigation tool aimed at making sense of large projects. It supports full-text and regex searches as well as structural queries.

Header

Mercurial (b6d82b1a6b02)

VCS Links

Line Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240
/* -*- 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() {}

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;
      mInfo.mCodecSpecificConfig->AppendElements(blockDataStart, blockDataSize);
      auto duration = FramesToTimeUnit(mNumFrames, sampleRate);
      mInfo.mDuration = duration.IsValid() ? duration : media::TimeUnit::Zero();
      mParser = new 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