trisquel-icecat/icecat/dom/media/platforms/agnostic/bytestreams/H264.cpp
2025-10-06 02:35:48 -06:00

1525 lines
49 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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 "H264.h"
#include "AnnexB.h"
#include "BitReader.h"
#include "BitWriter.h"
#include "BufferReader.h"
#include "ByteStreamsUtils.h"
#include "ByteWriter.h"
#include "MediaInfo.h"
#include "mozilla/PodOperations.h"
#include "mozilla/Result.h"
#include "mozilla/ResultExtensions.h"
#include "mozilla/Try.h"
#include <limits>
#define READSE(var, min, max) \
{ \
int32_t val = br.ReadSE(); \
if (val < min || val > max) { \
return false; \
} \
aDest.var = val; \
}
#define READUE(var, max) \
{ \
uint32_t uval = br.ReadUE(); \
if (uval > max) { \
return false; \
} \
aDest.var = uval; \
}
mozilla::LazyLogModule gH264("H264");
#define LOG(msg, ...) MOZ_LOG(gH264, LogLevel::Debug, (msg, ##__VA_ARGS__))
namespace mozilla {
// Default scaling lists (per spec).
// ITU H264:
// Table 7-2 Assignment of mnemonic names to scaling list indices and
// specification of fall-back rule
static const uint8_t Default_4x4_Intra[16] = {6, 13, 13, 20, 20, 20, 28, 28,
28, 28, 32, 32, 32, 37, 37, 42};
static const uint8_t Default_4x4_Inter[16] = {10, 14, 14, 20, 20, 20, 24, 24,
24, 24, 27, 27, 27, 30, 30, 34};
static const uint8_t Default_8x8_Intra[64] = {
6, 10, 10, 13, 11, 13, 16, 16, 16, 16, 18, 18, 18, 18, 18, 23,
23, 23, 23, 23, 23, 25, 25, 25, 25, 25, 25, 25, 27, 27, 27, 27,
27, 27, 27, 27, 29, 29, 29, 29, 29, 29, 29, 31, 31, 31, 31, 31,
31, 33, 33, 33, 33, 33, 36, 36, 36, 36, 38, 38, 38, 40, 40, 42};
static const uint8_t Default_8x8_Inter[64] = {
9, 13, 13, 15, 13, 15, 17, 17, 17, 17, 19, 19, 19, 19, 19, 21,
21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 24, 24, 24, 24,
24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 27, 27, 27, 27, 27,
27, 28, 28, 28, 28, 28, 30, 30, 30, 30, 32, 32, 32, 33, 33, 35};
namespace detail {
static void scaling_list(BitReader& aBr, uint8_t* aScalingList,
int aSizeOfScalingList, const uint8_t* aDefaultList,
const uint8_t* aFallbackList) {
int32_t lastScale = 8;
int32_t nextScale = 8;
int32_t deltaScale;
// (pic|seq)_scaling_list_present_flag[i]
if (!aBr.ReadBit()) {
if (aFallbackList) {
memcpy(aScalingList, aFallbackList, aSizeOfScalingList);
}
return;
}
for (int i = 0; i < aSizeOfScalingList; i++) {
if (nextScale != 0) {
deltaScale = aBr.ReadSE();
nextScale = (lastScale + deltaScale + 256) % 256;
if (!i && !nextScale) {
memcpy(aScalingList, aDefaultList, aSizeOfScalingList);
return;
}
}
aScalingList[i] = (nextScale == 0) ? lastScale : nextScale;
lastScale = aScalingList[i];
}
}
} // namespace detail.
template <size_t N>
static void scaling_list(BitReader& aBr, uint8_t (&aScalingList)[N],
const uint8_t (&aDefaultList)[N],
const uint8_t (&aFallbackList)[N]) {
detail::scaling_list(aBr, aScalingList, N, aDefaultList, aFallbackList);
}
template <size_t N>
static void scaling_list(BitReader& aBr, uint8_t (&aScalingList)[N],
const uint8_t (&aDefaultList)[N]) {
detail::scaling_list(aBr, aScalingList, N, aDefaultList, nullptr);
}
SPSData::SPSData() {
PodZero(this);
// Default values when they aren't defined as per ITU-T H.264 (2014/02).
chroma_format_idc = 1;
video_format = 5;
colour_primaries = 2;
transfer_characteristics = 2;
sample_ratio = 1.0;
memset(scaling_matrix4x4, 16, sizeof(scaling_matrix4x4));
memset(scaling_matrix8x8, 16, sizeof(scaling_matrix8x8));
}
bool SPSData::operator==(const SPSData& aOther) const {
return this->valid && aOther.valid && !memcmp(this, &aOther, sizeof(SPSData));
}
bool SPSData::operator!=(const SPSData& aOther) const {
return !(operator==(aOther));
}
static PrimaryID GetPrimaryID(int aPrimary) {
if (aPrimary < 1 || aPrimary > 22 || aPrimary == 3) {
return PrimaryID::INVALID;
}
if (aPrimary > 12 && aPrimary < 22) {
return PrimaryID::INVALID;
}
return static_cast<PrimaryID>(aPrimary);
}
static TransferID GetTransferID(int aTransfer) {
if (aTransfer < 1 || aTransfer > 18 || aTransfer == 3) {
return TransferID::INVALID;
}
return static_cast<TransferID>(aTransfer);
}
static MatrixID GetMatrixID(int aMatrix) {
if (aMatrix < 0 || aMatrix > 11 || aMatrix == 3) {
return MatrixID::INVALID;
}
return static_cast<MatrixID>(aMatrix);
}
gfx::YUVColorSpace SPSData::ColorSpace() const {
// Bitfield, note that guesses with higher values take precedence over
// guesses with lower values.
enum Guess {
GUESS_BT601 = 1 << 0,
GUESS_BT709 = 1 << 1,
GUESS_BT2020 = 1 << 2,
};
uint32_t guess = 0;
switch (GetPrimaryID(colour_primaries)) {
case PrimaryID::BT709:
guess |= GUESS_BT709;
break;
case PrimaryID::BT470M:
case PrimaryID::BT470BG:
case PrimaryID::SMPTE170M:
case PrimaryID::SMPTE240M:
guess |= GUESS_BT601;
break;
case PrimaryID::BT2020:
guess |= GUESS_BT2020;
break;
case PrimaryID::FILM:
case PrimaryID::SMPTEST428_1:
case PrimaryID::SMPTEST431_2:
case PrimaryID::SMPTEST432_1:
case PrimaryID::EBU_3213_E:
case PrimaryID::INVALID:
case PrimaryID::UNSPECIFIED:
break;
}
switch (GetTransferID(transfer_characteristics)) {
case TransferID::BT709:
guess |= GUESS_BT709;
break;
case TransferID::GAMMA22:
case TransferID::GAMMA28:
case TransferID::SMPTE170M:
case TransferID::SMPTE240M:
guess |= GUESS_BT601;
break;
case TransferID::BT2020_10:
case TransferID::BT2020_12:
guess |= GUESS_BT2020;
break;
case TransferID::LINEAR:
case TransferID::LOG:
case TransferID::LOG_SQRT:
case TransferID::IEC61966_2_4:
case TransferID::BT1361_ECG:
case TransferID::IEC61966_2_1:
case TransferID::SMPTEST2084:
case TransferID::SMPTEST428_1:
case TransferID::ARIB_STD_B67:
case TransferID::INVALID:
case TransferID::UNSPECIFIED:
break;
}
switch (GetMatrixID(matrix_coefficients)) {
case MatrixID::BT709:
guess |= GUESS_BT709;
break;
case MatrixID::BT470BG:
case MatrixID::SMPTE170M:
case MatrixID::SMPTE240M:
guess |= GUESS_BT601;
break;
case MatrixID::BT2020_NCL:
case MatrixID::BT2020_CL:
guess |= GUESS_BT2020;
break;
case MatrixID::RGB:
case MatrixID::FCC:
case MatrixID::YCOCG:
case MatrixID::YDZDX:
case MatrixID::INVALID:
case MatrixID::UNSPECIFIED:
break;
}
// Removes lowest bit until only a single bit remains.
while (guess & (guess - 1)) {
guess &= guess - 1;
}
if (!guess) {
// A better default to BT601 which should die a slow death.
guess = GUESS_BT709;
}
switch (guess) {
case GUESS_BT601:
return gfx::YUVColorSpace::BT601;
case GUESS_BT709:
return gfx::YUVColorSpace::BT709;
case GUESS_BT2020:
return gfx::YUVColorSpace::BT2020;
default:
MOZ_CRASH("not possible to get here but makes compiler happy");
}
}
gfx::ColorDepth SPSData::ColorDepth() const {
if (bit_depth_luma_minus8 != 0 && bit_depth_luma_minus8 != 2 &&
bit_depth_luma_minus8 != 4) {
// We don't know what that is, just assume 8 bits to prevent decoding
// regressions if we ever encounter those.
return gfx::ColorDepth::COLOR_8;
}
return gfx::ColorDepthForBitDepth(bit_depth_luma_minus8 + 8);
}
// SPSNAL and SPSNALIterator do not own their data.
class SPSNAL {
public:
SPSNAL(const uint8_t* aPtr, size_t aLength) {
MOZ_ASSERT(aPtr);
if (aLength == 0 || (*aPtr & 0x1f) != H264_NAL_SPS) {
return;
}
mDecodedNAL = H264::DecodeNALUnit(aPtr, aLength);
if (mDecodedNAL) {
mLength = BitReader::GetBitLength(mDecodedNAL);
}
}
SPSNAL() = default;
bool IsValid() const { return mDecodedNAL; }
bool operator==(const SPSNAL& aOther) const {
if (!mDecodedNAL || !aOther.mDecodedNAL) {
return false;
}
SPSData decodedSPS1;
SPSData decodedSPS2;
if (!GetSPSData(decodedSPS1) || !aOther.GetSPSData(decodedSPS2)) {
// Couldn't decode one SPS, perform a binary comparison
if (mLength != aOther.mLength) {
return false;
}
MOZ_ASSERT(mLength / 8 <= mDecodedNAL->Length());
if (memcmp(mDecodedNAL->Elements(), aOther.mDecodedNAL->Elements(),
mLength / 8) != 0) {
return false;
}
uint32_t remaining = mLength - (mLength & ~7);
BitReader b1(mDecodedNAL->Elements() + mLength / 8, remaining);
BitReader b2(aOther.mDecodedNAL->Elements() + mLength / 8, remaining);
for (uint32_t i = 0; i < remaining; i++) {
if (b1.ReadBit() != b2.ReadBit()) {
return false;
}
}
return true;
}
return decodedSPS1 == decodedSPS2;
}
bool operator!=(const SPSNAL& aOther) const { return !(operator==(aOther)); }
bool GetSPSData(SPSData& aDest) const {
return H264::DecodeSPS(mDecodedNAL, aDest);
}
private:
RefPtr<mozilla::MediaByteBuffer> mDecodedNAL;
uint32_t mLength = 0;
};
class SPSNALIterator {
public:
explicit SPSNALIterator(const mozilla::MediaByteBuffer* aExtraData)
: mExtraDataPtr(aExtraData->Elements()), mReader(aExtraData) {
if (!mReader.Read(5)) {
return;
}
auto res = mReader.ReadU8();
mNumSPS = res.isOk() ? res.unwrap() & 0x1f : 0;
if (mNumSPS == 0) {
return;
}
mValid = true;
}
SPSNALIterator& operator++() {
if (mEOS || !mValid) {
return *this;
}
if (--mNumSPS == 0) {
mEOS = true;
}
auto res = mReader.ReadU16();
uint16_t length = res.isOk() ? res.unwrap() : 0;
if (length == 0 || !mReader.Read(length)) {
mEOS = true;
}
return *this;
}
explicit operator bool() const { return mValid && !mEOS; }
SPSNAL operator*() const {
MOZ_ASSERT(bool(*this));
BufferReader reader(mExtraDataPtr + mReader.Offset(), mReader.Remaining());
auto res = reader.ReadU16();
uint16_t length = res.isOk() ? res.unwrap() : 0;
const uint8_t* ptr = reader.Read(length);
if (!ptr || !length) {
return SPSNAL();
}
return SPSNAL(ptr, length);
}
private:
const uint8_t* mExtraDataPtr;
BufferReader mReader;
bool mValid = false;
bool mEOS = false;
uint8_t mNumSPS = 0;
};
/* static */ Result<int, nsresult> H264::ExtractSVCTemporalId(
const uint8_t* aData, size_t aLength) {
nsTArray<AnnexB::NALEntry> paramSets;
AnnexB::ParseNALEntries(Span<const uint8_t>(aData, aLength), paramSets);
BufferReader reader(aData, aLength);
// Discard what's needed to find the correct NAL.
int i = 0;
while (paramSets[i].mSize < 4) {
i++;
}
reader.Read(paramSets[i].mOffset);
uint8_t byte;
MOZ_TRY_VAR(byte, reader.ReadU8());
uint8_t nalUnitType = byte & 0x1f;
if (nalUnitType == H264_NAL_PREFIX || nalUnitType == H264_NAL_SLICE_EXT) {
bool svcExtensionFlag = false;
MOZ_TRY_VAR(byte, reader.ReadU8());
svcExtensionFlag = byte & 0x80;
if (svcExtensionFlag) {
// Discard the first byte, and find the temporal id in the second byte
MOZ_TRY(reader.ReadU8());
MOZ_TRY_VAR(byte, reader.ReadU8());
int temporalId = (byte & 0xE0) >> 5;
return temporalId;
}
}
return 0;
}
/* static */ already_AddRefed<mozilla::MediaByteBuffer> H264::DecodeNALUnit(
const uint8_t* aNAL, size_t aLength) {
MOZ_ASSERT(aNAL);
if (aLength < 4) {
return nullptr;
}
RefPtr<mozilla::MediaByteBuffer> rbsp = new mozilla::MediaByteBuffer;
BufferReader reader(aNAL, aLength);
auto res = reader.ReadU8();
if (res.isErr()) {
return nullptr;
}
uint8_t nal_unit_type = res.unwrap() & 0x1f;
uint32_t nalUnitHeaderBytes = 1;
if (nal_unit_type == H264_NAL_PREFIX || nal_unit_type == H264_NAL_SLICE_EXT ||
nal_unit_type == H264_NAL_SLICE_EXT_DVC) {
bool svc_extension_flag = false;
bool avc_3d_extension_flag = false;
if (nal_unit_type != H264_NAL_SLICE_EXT_DVC) {
res = reader.PeekU8();
if (res.isErr()) {
return nullptr;
}
svc_extension_flag = res.unwrap() & 0x80;
} else {
res = reader.PeekU8();
if (res.isErr()) {
return nullptr;
}
avc_3d_extension_flag = res.unwrap() & 0x80;
}
if (svc_extension_flag) {
nalUnitHeaderBytes += 3;
} else if (avc_3d_extension_flag) {
nalUnitHeaderBytes += 2;
} else {
nalUnitHeaderBytes += 3;
}
}
if (!reader.Read(nalUnitHeaderBytes - 1)) {
return nullptr;
}
uint32_t lastbytes = 0xffff;
while (reader.Remaining()) {
auto res = reader.ReadU8();
if (res.isErr()) {
return nullptr;
}
uint8_t byte = res.unwrap();
if ((lastbytes & 0xffff) == 0 && byte == 0x03) {
// reset last two bytes, to detect the 0x000003 sequence again.
lastbytes = 0xffff;
} else {
rbsp->AppendElement(byte);
}
lastbytes = (lastbytes << 8) | byte;
}
return rbsp.forget();
}
// The reverse of DecodeNALUnit. To allow the distinction between Annex B (that
// uses 0x000001 as marker) and AVCC, the pattern 0x00 0x00 0x0n (where n is
// between 0 and 3) can't be found in the bytestream. A 0x03 byte is inserted
// after the second 0. Eg. 0x00 0x00 0x00 becomes 0x00 0x00 0x03 0x00
/* static */ already_AddRefed<mozilla::MediaByteBuffer> H264::EncodeNALUnit(
const uint8_t* aNAL, size_t aLength) {
MOZ_ASSERT(aNAL);
RefPtr<MediaByteBuffer> rbsp = new MediaByteBuffer();
BufferReader reader(aNAL, aLength);
auto res = reader.ReadU8();
if (res.isErr()) {
return rbsp.forget();
}
rbsp->AppendElement(res.unwrap());
res = reader.ReadU8();
if (res.isErr()) {
return rbsp.forget();
}
rbsp->AppendElement(res.unwrap());
while ((res = reader.ReadU8()).isOk()) {
uint8_t val = res.unwrap();
if (val <= 0x03 && rbsp->ElementAt(rbsp->Length() - 2) == 0 &&
rbsp->ElementAt(rbsp->Length() - 1) == 0) {
rbsp->AppendElement(0x03);
}
rbsp->AppendElement(val);
}
return rbsp.forget();
}
static int32_t ConditionDimension(double aValue) {
// This will exclude NaNs and too-big values.
if (aValue > 1.0 && aValue <= double(INT32_MAX) / 2) {
return int32_t(aValue);
}
return 0;
}
/* static */
bool H264::DecodeSPS(const mozilla::MediaByteBuffer* aSPS, SPSData& aDest) {
if (!aSPS) {
return false;
}
BitReader br(aSPS, BitReader::GetBitLength(aSPS));
aDest.profile_idc = br.ReadBits(8);
aDest.constraint_set0_flag = br.ReadBit();
aDest.constraint_set1_flag = br.ReadBit();
aDest.constraint_set2_flag = br.ReadBit();
aDest.constraint_set3_flag = br.ReadBit();
aDest.constraint_set4_flag = br.ReadBit();
aDest.constraint_set5_flag = br.ReadBit();
br.ReadBits(2); // reserved_zero_2bits
aDest.level_idc = br.ReadBits(8);
READUE(seq_parameter_set_id, MAX_SPS_COUNT - 1);
if (aDest.profile_idc == 100 || aDest.profile_idc == 110 ||
aDest.profile_idc == 122 || aDest.profile_idc == 244 ||
aDest.profile_idc == 44 || aDest.profile_idc == 83 ||
aDest.profile_idc == 86 || aDest.profile_idc == 118 ||
aDest.profile_idc == 128 || aDest.profile_idc == 138 ||
aDest.profile_idc == 139 || aDest.profile_idc == 134) {
READUE(chroma_format_idc, 3);
if (aDest.chroma_format_idc == 3) {
aDest.separate_colour_plane_flag = br.ReadBit();
}
READUE(bit_depth_luma_minus8, 6);
READUE(bit_depth_chroma_minus8, 6);
br.ReadBit(); // qpprime_y_zero_transform_bypass_flag
aDest.seq_scaling_matrix_present_flag = br.ReadBit();
if (aDest.seq_scaling_matrix_present_flag) {
scaling_list(br, aDest.scaling_matrix4x4[0], Default_4x4_Intra,
Default_4x4_Intra);
scaling_list(br, aDest.scaling_matrix4x4[1], Default_4x4_Intra,
aDest.scaling_matrix4x4[0]);
scaling_list(br, aDest.scaling_matrix4x4[2], Default_4x4_Intra,
aDest.scaling_matrix4x4[1]);
scaling_list(br, aDest.scaling_matrix4x4[3], Default_4x4_Inter,
Default_4x4_Inter);
scaling_list(br, aDest.scaling_matrix4x4[4], Default_4x4_Inter,
aDest.scaling_matrix4x4[3]);
scaling_list(br, aDest.scaling_matrix4x4[5], Default_4x4_Inter,
aDest.scaling_matrix4x4[4]);
scaling_list(br, aDest.scaling_matrix8x8[0], Default_8x8_Intra,
Default_8x8_Intra);
scaling_list(br, aDest.scaling_matrix8x8[1], Default_8x8_Inter,
Default_8x8_Inter);
if (aDest.chroma_format_idc == 3) {
scaling_list(br, aDest.scaling_matrix8x8[2], Default_8x8_Intra,
aDest.scaling_matrix8x8[0]);
scaling_list(br, aDest.scaling_matrix8x8[3], Default_8x8_Inter,
aDest.scaling_matrix8x8[1]);
scaling_list(br, aDest.scaling_matrix8x8[4], Default_8x8_Intra,
aDest.scaling_matrix8x8[2]);
scaling_list(br, aDest.scaling_matrix8x8[5], Default_8x8_Inter,
aDest.scaling_matrix8x8[3]);
}
}
} else if (aDest.profile_idc == 183) {
aDest.chroma_format_idc = 0;
} else {
// default value if chroma_format_idc isn't set.
aDest.chroma_format_idc = 1;
}
READUE(log2_max_frame_num, 12);
aDest.log2_max_frame_num += 4;
READUE(pic_order_cnt_type, 2);
if (aDest.pic_order_cnt_type == 0) {
READUE(log2_max_pic_order_cnt_lsb, 12);
aDest.log2_max_pic_order_cnt_lsb += 4;
} else if (aDest.pic_order_cnt_type == 1) {
aDest.delta_pic_order_always_zero_flag = br.ReadBit();
READSE(offset_for_non_ref_pic, -231, 230);
READSE(offset_for_top_to_bottom_field, -231, 230);
uint32_t num_ref_frames_in_pic_order_cnt_cycle = br.ReadUE();
for (uint32_t i = 0; i < num_ref_frames_in_pic_order_cnt_cycle; i++) {
br.ReadSE(); // offset_for_ref_frame[i]
}
}
aDest.max_num_ref_frames = br.ReadUE();
aDest.gaps_in_frame_num_allowed_flag = br.ReadBit();
aDest.pic_width_in_mbs = br.ReadUE() + 1;
aDest.pic_height_in_map_units = br.ReadUE() + 1;
aDest.frame_mbs_only_flag = br.ReadBit();
if (!aDest.frame_mbs_only_flag) {
aDest.pic_height_in_map_units *= 2;
aDest.mb_adaptive_frame_field_flag = br.ReadBit();
}
aDest.direct_8x8_inference_flag = br.ReadBit();
aDest.frame_cropping_flag = br.ReadBit();
if (aDest.frame_cropping_flag) {
aDest.frame_crop_left_offset = br.ReadUE();
aDest.frame_crop_right_offset = br.ReadUE();
aDest.frame_crop_top_offset = br.ReadUE();
aDest.frame_crop_bottom_offset = br.ReadUE();
}
aDest.sample_ratio = 1.0f;
aDest.vui_parameters_present_flag = br.ReadBit();
if (aDest.vui_parameters_present_flag) {
if (!vui_parameters(br, aDest)) {
return false;
}
}
// Calculate common values.
uint8_t ChromaArrayType =
aDest.separate_colour_plane_flag ? 0 : aDest.chroma_format_idc;
// Calculate width.
uint32_t CropUnitX = 1;
uint32_t SubWidthC = aDest.chroma_format_idc == 3 ? 1 : 2;
if (ChromaArrayType != 0) {
CropUnitX = SubWidthC;
}
// Calculate Height
uint32_t CropUnitY = 2 - aDest.frame_mbs_only_flag;
uint32_t SubHeightC = aDest.chroma_format_idc <= 1 ? 2 : 1;
if (ChromaArrayType != 0) {
CropUnitY *= SubHeightC;
}
uint32_t width = aDest.pic_width_in_mbs * 16;
uint32_t height = aDest.pic_height_in_map_units * 16;
if (aDest.frame_crop_left_offset <=
std::numeric_limits<int32_t>::max() / 4 / CropUnitX &&
aDest.frame_crop_right_offset <=
std::numeric_limits<int32_t>::max() / 4 / CropUnitX &&
aDest.frame_crop_top_offset <=
std::numeric_limits<int32_t>::max() / 4 / CropUnitY &&
aDest.frame_crop_bottom_offset <=
std::numeric_limits<int32_t>::max() / 4 / CropUnitY &&
(aDest.frame_crop_left_offset + aDest.frame_crop_right_offset) *
CropUnitX <
width &&
(aDest.frame_crop_top_offset + aDest.frame_crop_bottom_offset) *
CropUnitY <
height) {
aDest.crop_left = aDest.frame_crop_left_offset * CropUnitX;
aDest.crop_right = aDest.frame_crop_right_offset * CropUnitX;
aDest.crop_top = aDest.frame_crop_top_offset * CropUnitY;
aDest.crop_bottom = aDest.frame_crop_bottom_offset * CropUnitY;
} else {
// Nonsensical value, ignore them.
aDest.crop_left = aDest.crop_right = aDest.crop_top = aDest.crop_bottom = 0;
}
aDest.pic_width = width - aDest.crop_left - aDest.crop_right;
aDest.pic_height = height - aDest.crop_top - aDest.crop_bottom;
aDest.interlaced = !aDest.frame_mbs_only_flag;
// Determine display size.
if (aDest.sample_ratio > 1.0) {
// Increase the intrinsic width
aDest.display_width = ConditionDimension(
AssertedCast<double>(aDest.pic_width) * aDest.sample_ratio);
aDest.display_height = aDest.pic_height;
} else {
// Increase the intrinsic height
aDest.display_width = aDest.pic_width;
aDest.display_height = ConditionDimension(
AssertedCast<double>(aDest.pic_height) / aDest.sample_ratio);
}
aDest.valid = true;
return true;
}
/* static */
bool H264::vui_parameters(BitReader& aBr, SPSData& aDest) {
aDest.aspect_ratio_info_present_flag = aBr.ReadBit();
if (aDest.aspect_ratio_info_present_flag) {
aDest.aspect_ratio_idc = aBr.ReadBits(8);
aDest.sar_width = aDest.sar_height = 0;
// From E.2.1 VUI parameters semantics (ITU-T H.264 02/2014)
switch (aDest.aspect_ratio_idc) {
case 0:
// Unspecified
break;
case 1:
/*
1:1
7680x4320 16:9 frame without horizontal overscan
3840x2160 16:9 frame without horizontal overscan
1280x720 16:9 frame without horizontal overscan
1920x1080 16:9 frame without horizontal overscan (cropped from
1920x1088) 640x480 4:3 frame without horizontal overscan
*/
aDest.sample_ratio = 1.0f;
break;
case 2:
/*
12:11
720x576 4:3 frame with horizontal overscan
352x288 4:3 frame without horizontal overscan
*/
aDest.sample_ratio = 12.0 / 11.0;
break;
case 3:
/*
10:11
720x480 4:3 frame with horizontal overscan
352x240 4:3 frame without horizontal overscan
*/
aDest.sample_ratio = 10.0 / 11.0;
break;
case 4:
/*
16:11
720x576 16:9 frame with horizontal overscan
528x576 4:3 frame without horizontal overscan
*/
aDest.sample_ratio = 16.0 / 11.0;
break;
case 5:
/*
40:33
720x480 16:9 frame with horizontal overscan
528x480 4:3 frame without horizontal overscan
*/
aDest.sample_ratio = 40.0 / 33.0;
break;
case 6:
/*
24:11
352x576 4:3 frame without horizontal overscan
480x576 16:9 frame with horizontal overscan
*/
aDest.sample_ratio = 24.0 / 11.0;
break;
case 7:
/*
20:11
352x480 4:3 frame without horizontal overscan
480x480 16:9 frame with horizontal overscan
*/
aDest.sample_ratio = 20.0 / 11.0;
break;
case 8:
/*
32:11
352x576 16:9 frame without horizontal overscan
*/
aDest.sample_ratio = 32.0 / 11.0;
break;
case 9:
/*
80:33
352x480 16:9 frame without horizontal overscan
*/
aDest.sample_ratio = 80.0 / 33.0;
break;
case 10:
/*
18:11
480x576 4:3 frame with horizontal overscan
*/
aDest.sample_ratio = 18.0 / 11.0;
break;
case 11:
/*
15:11
480x480 4:3 frame with horizontal overscan
*/
aDest.sample_ratio = 15.0 / 11.0;
break;
case 12:
/*
64:33
528x576 16:9 frame with horizontal overscan
*/
aDest.sample_ratio = 64.0 / 33.0;
break;
case 13:
/*
160:99
528x480 16:9 frame without horizontal overscan
*/
aDest.sample_ratio = 160.0 / 99.0;
break;
case 14:
/*
4:3
1440x1080 16:9 frame without horizontal overscan
*/
aDest.sample_ratio = 4.0 / 3.0;
break;
case 15:
/*
3:2
1280x1080 16:9 frame without horizontal overscan
*/
aDest.sample_ratio = 3.2 / 2.0;
break;
case 16:
/*
2:1
960x1080 16:9 frame without horizontal overscan
*/
aDest.sample_ratio = 2.0 / 1.0;
break;
case 255:
/* Extended_SAR */
aDest.sar_width = aBr.ReadBits(16);
aDest.sar_height = aBr.ReadBits(16);
if (aDest.sar_width && aDest.sar_height) {
aDest.sample_ratio = float(aDest.sar_width) / float(aDest.sar_height);
}
break;
default:
break;
}
}
if (aBr.ReadBit()) { // overscan_info_present_flag
aDest.overscan_appropriate_flag = aBr.ReadBit();
}
if (aBr.ReadBit()) { // video_signal_type_present_flag
aDest.video_format = aBr.ReadBits(3);
aDest.video_full_range_flag = aBr.ReadBit();
aDest.colour_description_present_flag = aBr.ReadBit();
if (aDest.colour_description_present_flag) {
aDest.colour_primaries = aBr.ReadBits(8);
aDest.transfer_characteristics = aBr.ReadBits(8);
aDest.matrix_coefficients = aBr.ReadBits(8);
}
}
aDest.chroma_loc_info_present_flag = aBr.ReadBit();
if (aDest.chroma_loc_info_present_flag) {
BitReader& br = aBr; // so that macro READUE works
READUE(chroma_sample_loc_type_top_field, 5);
READUE(chroma_sample_loc_type_bottom_field, 5);
}
bool timing_info_present_flag = aBr.ReadBit();
if (timing_info_present_flag) {
aBr.ReadBits(32); // num_units_in_tick
aBr.ReadBits(32); // time_scale
aBr.ReadBit(); // fixed_frame_rate_flag
}
return true;
}
/* static */
bool H264::DecodeSPSFromExtraData(const mozilla::MediaByteBuffer* aExtraData,
SPSData& aDest) {
SPSNALIterator it(aExtraData);
if (!it) {
return false;
}
return (*it).GetSPSData(aDest);
}
/* static */
bool H264::EnsureSPSIsSane(SPSData& aSPS) {
bool valid = true;
static const float default_aspect = 4.0f / 3.0f;
if (aSPS.sample_ratio <= 0.0f || aSPS.sample_ratio > 6.0f) {
if (aSPS.pic_width && aSPS.pic_height) {
aSPS.sample_ratio = (float)aSPS.pic_width / (float)aSPS.pic_height;
} else {
aSPS.sample_ratio = default_aspect;
}
aSPS.display_width = aSPS.pic_width;
aSPS.display_height = aSPS.pic_height;
valid = false;
}
if (aSPS.max_num_ref_frames > 16) {
aSPS.max_num_ref_frames = 16;
valid = false;
}
return valid;
}
/* static */
uint32_t H264::ComputeMaxRefFrames(const mozilla::MediaByteBuffer* aExtraData) {
uint32_t maxRefFrames = 4;
// Retrieve video dimensions from H264 SPS NAL.
SPSData spsdata;
if (DecodeSPSFromExtraData(aExtraData, spsdata)) {
// max_num_ref_frames determines the size of the sliding window
// we need to queue that many frames in order to guarantee proper
// pts frames ordering. Use a minimum of 4 to ensure proper playback of
// non compliant videos.
maxRefFrames =
std::min(std::max(maxRefFrames, spsdata.max_num_ref_frames + 1), 16u);
}
return maxRefFrames;
}
/* static */ H264::FrameType H264::GetFrameType(
const mozilla::MediaRawData* aSample) {
auto avcc = AVCCConfig::Parse(aSample);
if (avcc.isErr()) {
// We must have a valid AVCC frame with extradata.
return FrameType::INVALID;
}
MOZ_ASSERT(aSample->Data());
int nalLenSize = avcc.unwrap().NALUSize();
BufferReader reader(aSample->Data(), aSample->Size());
while (reader.Remaining() >= nalLenSize) {
uint32_t nalLen = 0;
switch (nalLenSize) {
case 1:
nalLen = reader.ReadU8().unwrapOr(0);
break;
case 2:
nalLen = reader.ReadU16().unwrapOr(0);
break;
case 3:
nalLen = reader.ReadU24().unwrapOr(0);
break;
case 4:
nalLen = reader.ReadU32().unwrapOr(0);
break;
default:
MOZ_ASSERT_UNREACHABLE("NAL length is up to 4 bytes");
}
if (!nalLen) {
continue;
}
const uint8_t* p = reader.Read(nalLen);
if (!p) {
return FrameType::INVALID;
}
int8_t nalType = AssertedCast<int8_t>(*p & 0x1f);
if (nalType == H264_NAL_IDR_SLICE) {
// IDR NAL.
return FrameType::I_FRAME_IDR;
}
if (nalType == H264_NAL_SEI) {
RefPtr<mozilla::MediaByteBuffer> decodedNAL = DecodeNALUnit(p, nalLen);
SEIRecoveryData data;
if (DecodeRecoverySEI(decodedNAL, data)) {
return FrameType::I_FRAME_OTHER;
}
} else if (nalType == H264_NAL_SLICE) {
RefPtr<mozilla::MediaByteBuffer> decodedNAL = DecodeNALUnit(p, nalLen);
if (DecodeISlice(decodedNAL)) {
return FrameType::I_FRAME_OTHER;
}
}
}
return FrameType::OTHER;
}
/* static */ already_AddRefed<mozilla::MediaByteBuffer> H264::ExtractExtraData(
const mozilla::MediaRawData* aSample) {
auto avcc = AVCCConfig::Parse(aSample);
MOZ_ASSERT(avcc.isOk());
RefPtr<mozilla::MediaByteBuffer> extradata = new mozilla::MediaByteBuffer;
// SPS content
nsTArray<uint8_t> sps;
ByteWriter<BigEndian> spsw(sps);
int numSps = 0;
// PPS content
nsTArray<uint8_t> pps;
ByteWriter<BigEndian> ppsw(pps);
int numPps = 0;
int nalLenSize = avcc.unwrap().NALUSize();
size_t sampleSize = aSample->Size();
if (aSample->mCrypto.IsEncrypted()) {
// The content is encrypted, we can only parse the non-encrypted data.
MOZ_ASSERT(aSample->mCrypto.mPlainSizes.Length() > 0);
if (aSample->mCrypto.mPlainSizes.Length() == 0 ||
aSample->mCrypto.mPlainSizes[0] > sampleSize) {
// This is invalid content.
return nullptr;
}
sampleSize = aSample->mCrypto.mPlainSizes[0];
}
BufferReader reader(aSample->Data(), sampleSize);
nsTArray<SPSData> SPSTable;
// If we encounter SPS with the same id but different content, we will stop
// attempting to detect duplicates.
bool checkDuplicate = true;
// Find SPS and PPS NALUs in AVCC data
while (reader.Remaining() > nalLenSize) {
uint32_t nalLen = 0;
switch (nalLenSize) {
case 1:
Unused << reader.ReadU8().map(
[&](uint8_t x) mutable { return nalLen = x; });
break;
case 2:
Unused << reader.ReadU16().map(
[&](uint16_t x) mutable { return nalLen = x; });
break;
case 3:
Unused << reader.ReadU24().map(
[&](uint32_t x) mutable { return nalLen = x; });
break;
case 4:
Unused << reader.ReadU32().map(
[&](uint32_t x) mutable { return nalLen = x; });
break;
default:
MOZ_ASSERT_UNREACHABLE("NAL length size is at most 4 bytes");
}
const uint8_t* p = reader.Read(nalLen);
if (!p) {
// The read failed, but we may already have some SPS + PPS data so
// break out of reading and process what we have, if any.
break;
}
uint8_t nalType = *p & 0x1f;
if (nalType == H264_NAL_SPS) {
RefPtr<mozilla::MediaByteBuffer> sps = DecodeNALUnit(p, nalLen);
SPSData data;
if (!DecodeSPS(sps, data)) {
// Invalid SPS, ignore.
continue;
}
uint8_t spsId = data.seq_parameter_set_id;
if (spsId >= SPSTable.Length()) {
if (!SPSTable.SetLength(spsId + 1, fallible)) {
// OOM.
return nullptr;
}
}
if (checkDuplicate && SPSTable[spsId].valid && SPSTable[spsId] == data) {
// Duplicate ignore.
continue;
}
if (SPSTable[spsId].valid) {
// We already have detected a SPS with this Id. Just to be safe we
// disable SPS duplicate detection.
checkDuplicate = false;
} else {
SPSTable[spsId] = data;
}
numSps++;
if (!spsw.WriteU16(nalLen) || !spsw.Write(p, nalLen)) {
return extradata.forget();
}
} else if (nalType == H264_NAL_PPS) {
numPps++;
if (!ppsw.WriteU16(nalLen) || !ppsw.Write(p, nalLen)) {
return extradata.forget();
}
}
}
// We ignore PPS data if we didn't find a SPS as we would be unable to
// decode it anyway.
numPps = numSps ? numPps : 0;
if (numSps && sps.Length() > 5) {
extradata->AppendElement(1); // version
extradata->AppendElement(sps[3]); // profile
extradata->AppendElement(sps[4]); // profile compat
extradata->AppendElement(sps[5]); // level
extradata->AppendElement(0xfc | 3); // nal size - 1
extradata->AppendElement(0xe0 | numSps);
extradata->AppendElements(sps.Elements(), sps.Length());
extradata->AppendElement(numPps);
if (numPps) {
extradata->AppendElements(pps.Elements(), pps.Length());
}
}
return extradata.forget();
}
/* static */
uint8_t H264::NumSPS(const mozilla::MediaByteBuffer* aExtraData) {
auto avcc = AVCCConfig::Parse(aExtraData);
return avcc.isErr() ? 0 : avcc.unwrap().NumSPS();
}
/* static */
bool H264::HasSPS(const mozilla::MediaByteBuffer* aExtraData) {
return H264::NumSPS(aExtraData) > 0;
}
/* static */
bool H264::CompareExtraData(const mozilla::MediaByteBuffer* aExtraData1,
const mozilla::MediaByteBuffer* aExtraData2) {
if (aExtraData1 == aExtraData2) {
return true;
}
uint8_t numSPS = NumSPS(aExtraData1);
if (numSPS == 0 || numSPS != NumSPS(aExtraData2)) {
return false;
}
// We only compare if the SPS are the same as the various H264 decoders can
// deal with in-band change of PPS.
SPSNALIterator it1(aExtraData1);
SPSNALIterator it2(aExtraData2);
while (it1 && it2) {
if (*it1 != *it2) {
return false;
}
++it1;
++it2;
}
return true;
}
static inline Result<Ok, nsresult> ReadSEIInt(BufferReader& aBr,
uint32_t& aOutput) {
uint8_t tmpByte;
aOutput = 0;
MOZ_TRY_VAR(tmpByte, aBr.ReadU8());
while (tmpByte == 0xFF) {
aOutput += 255;
MOZ_TRY_VAR(tmpByte, aBr.ReadU8());
}
aOutput += tmpByte; // this is the last byte
return Ok();
}
/* static */
bool H264::DecodeISlice(const mozilla::MediaByteBuffer* aSlice) {
if (!aSlice) {
return false;
}
// According to ITU-T Rec H.264 Table 7.3.3, read the slice type from
// slice_header, and the slice type 2 and 7 are representing I slice.
BitReader br(aSlice);
// Skip `first_mb_in_slice`
br.ReadUE();
// The value of slice type can go from 0 to 9, but the value between 5 to
// 9 are actually equal to 0 to 4.
const uint32_t sliceType = br.ReadUE() % 5;
return sliceType == SLICE_TYPES::I_SLICE || sliceType == SI_SLICE;
}
/* static */
bool H264::DecodeRecoverySEI(const mozilla::MediaByteBuffer* aSEI,
SEIRecoveryData& aDest) {
if (!aSEI) {
return false;
}
// sei_rbsp() as per 7.3.2.3 Supplemental enhancement information RBSP syntax
BufferReader br(aSEI);
do {
// sei_message() as per
// 7.3.2.3.1 Supplemental enhancement information message syntax
uint32_t payloadType = 0;
if (ReadSEIInt(br, payloadType).isErr()) {
return false;
}
uint32_t payloadSize = 0;
if (ReadSEIInt(br, payloadSize).isErr()) {
return false;
}
// sei_payload(payloadType, payloadSize) as per
// D.1 SEI payload syntax.
const uint8_t* p = br.Read(payloadSize);
if (!p) {
return false;
}
if (payloadType == 6) { // SEI_RECOVERY_POINT
if (payloadSize == 0) {
// Invalid content, ignore.
continue;
}
// D.1.7 Recovery point SEI message syntax
BitReader br(p, payloadSize * 8);
aDest.recovery_frame_cnt = br.ReadUE();
aDest.exact_match_flag = br.ReadBit();
aDest.broken_link_flag = br.ReadBit();
aDest.changing_slice_group_idc = br.ReadBits(2);
return true;
}
} while (br.PeekU8().isOk() &&
br.PeekU8().unwrap() !=
0x80); // more_rbsp_data() msg[offset] != 0x80
// ignore the trailing bits rbsp_trailing_bits();
return false;
}
/*static */ already_AddRefed<mozilla::MediaByteBuffer> H264::CreateExtraData(
uint8_t aProfile, uint8_t aConstraints, H264_LEVEL aLevel,
const gfx::IntSize& aSize) {
// SPS of a 144p video.
const uint8_t originSPS[] = {0x4d, 0x40, 0x0c, 0xe8, 0x80, 0x80, 0x9d,
0x80, 0xb5, 0x01, 0x01, 0x01, 0x40, 0x00,
0x00, 0x00, 0x40, 0x00, 0x00, 0x0f, 0x03,
0xc5, 0x0a, 0x44, 0x80};
RefPtr<MediaByteBuffer> extraData = new MediaByteBuffer();
extraData->AppendElements(originSPS, sizeof(originSPS));
BitReader br(extraData, BitReader::GetBitLength(extraData));
RefPtr<MediaByteBuffer> sps = new MediaByteBuffer();
BitWriter bw(sps);
br.ReadBits(8); // Skip original profile_idc
bw.WriteU8(aProfile);
br.ReadBits(8); // Skip original constraint flags && reserved_zero_2bits
aConstraints =
aConstraints & ~0x3; // Ensure reserved_zero_2bits are set to 0
bw.WriteBits(aConstraints, 8);
br.ReadBits(8); // Skip original level_idc
bw.WriteU8(static_cast<uint8_t>(aLevel));
bw.WriteUE(br.ReadUE()); // seq_parameter_set_id (0 stored on 1 bit)
if (aProfile == 100 || aProfile == 110 || aProfile == 122 ||
aProfile == 244 || aProfile == 44 || aProfile == 83 || aProfile == 86 ||
aProfile == 118 || aProfile == 128 || aProfile == 138 ||
aProfile == 139 || aProfile == 134) {
bw.WriteUE(1); // chroma_format_idc -> always set to 4:2:0 chroma format
bw.WriteUE(0); // bit_depth_luma_minus8 -> always 8 bits here
bw.WriteUE(0); // bit_depth_chroma_minus8 -> always 8 bits here
bw.WriteBit(false); // qpprime_y_zero_transform_bypass_flag
bw.WriteBit(false); // seq_scaling_matrix_present_flag
}
bw.WriteBits(br.ReadBits(11),
11); // log2_max_frame_num to gaps_in_frame_num_allowed_flag
// skip over original exp-golomb encoded width/height
br.ReadUE(); // skip width
br.ReadUE(); // skip height
uint32_t width = aSize.width;
uint32_t widthNeeded = width % 16 != 0 ? (width / 16 + 1) * 16 : width;
uint32_t height = aSize.height;
uint32_t heightNeeded = height % 16 != 0 ? (height / 16 + 1) * 16 : height;
bw.WriteUE(widthNeeded / 16 - 1);
bw.WriteUE(heightNeeded / 16 - 1);
bw.WriteBit(br.ReadBit()); // write frame_mbs_only_flag
bw.WriteBit(br.ReadBit()); // write direct_8x8_inference_flag;
if (widthNeeded != width || heightNeeded != height) {
// Write cropping value
bw.WriteBit(true); // skip frame_cropping_flag
bw.WriteUE(0); // frame_crop_left_offset
bw.WriteUE((widthNeeded - width) / 2); // frame_crop_right_offset
bw.WriteUE(0); // frame_crop_top_offset
bw.WriteUE((heightNeeded - height) / 2); // frame_crop_bottom_offset
} else {
bw.WriteBit(false); // skip frame_cropping_flag
}
br.ReadBit(); // skip frame_cropping_flag;
// Write the remainings of the original sps (vui_parameters which sets an
// aspect ration of 1.0)
while (br.BitsLeft()) {
bw.WriteBit(br.ReadBit());
}
bw.CloseWithRbspTrailing();
RefPtr<MediaByteBuffer> encodedSPS =
EncodeNALUnit(sps->Elements(), sps->Length());
extraData->Clear();
const uint8_t PPS[] = {0xeb, 0xef, 0x20};
WriteExtraData(
extraData, aProfile, aConstraints, static_cast<uint8_t>(aLevel),
Span<const uint8_t>(encodedSPS->Elements(), encodedSPS->Length()),
Span<const uint8_t>(PPS, sizeof(PPS)));
return extraData.forget();
}
void H264::WriteExtraData(MediaByteBuffer* aDestExtraData,
const uint8_t aProfile, const uint8_t aConstraints,
const uint8_t aLevel, const Span<const uint8_t> aSPS,
const Span<const uint8_t> aPPS) {
aDestExtraData->AppendElement(1);
aDestExtraData->AppendElement(aProfile);
aDestExtraData->AppendElement(aConstraints);
aDestExtraData->AppendElement(aLevel);
aDestExtraData->AppendElement(3); // nalLENSize-1
aDestExtraData->AppendElement(1); // numPPS
uint8_t c[2];
mozilla::BigEndian::writeUint16(&c[0], aSPS.Length() + 1);
aDestExtraData->AppendElements(c, 2);
aDestExtraData->AppendElement((0x00 << 7) | (0x3 << 5) | H264_NAL_SPS);
aDestExtraData->AppendElements(aSPS.Elements(), aSPS.Length());
aDestExtraData->AppendElement(1); // numPPS
mozilla::BigEndian::writeUint16(&c[0], aPPS.Length() + 1);
aDestExtraData->AppendElements(c, 2);
aDestExtraData->AppendElement((0x00 << 7) | (0x3 << 5) | H264_NAL_PPS);
aDestExtraData->AppendElements(aPPS.Elements(), aPPS.Length());
}
/* static */ Result<AVCCConfig, nsresult> AVCCConfig::Parse(
const mozilla::MediaRawData* aSample) {
if (!aSample || aSample->Size() < 3) {
return mozilla::Err(NS_ERROR_FAILURE);
}
if (aSample->mTrackInfo &&
!aSample->mTrackInfo->mMimeType.EqualsLiteral("video/avc")) {
LOG("Only allow 'video/avc' (mimeType=%s)",
aSample->mTrackInfo->mMimeType.get());
return mozilla::Err(NS_ERROR_FAILURE);
}
return AVCCConfig::Parse(aSample->mExtraData);
}
/* static */ Result<AVCCConfig, nsresult> AVCCConfig::Parse(
const mozilla::MediaByteBuffer* aExtraData) {
if (!aExtraData || aExtraData->Length() < 7) {
return mozilla::Err(NS_ERROR_FAILURE);
}
AVCCConfig avcc{};
BitReader reader(aExtraData);
avcc.mConfigurationVersion = reader.ReadBits(8);
if (avcc.mConfigurationVersion != 1) {
LOG("Invalid configuration version %u", avcc.mConfigurationVersion);
return mozilla::Err(NS_ERROR_FAILURE);
}
avcc.mAVCProfileIndication = reader.ReadBits(8);
avcc.mProfileCompatibility = reader.ReadBits(8);
avcc.mAVCLevelIndication = reader.ReadBits(8);
Unused << reader.ReadBits(6); // reserved
avcc.mLengthSizeMinusOne = reader.ReadBits(2);
Unused << reader.ReadBits(3); // reserved
const uint8_t numSPS = reader.ReadBits(5);
for (uint8_t idx = 0; idx < numSPS; idx++) {
if (reader.BitsLeft() < 16) {
LOG("Aborting parsing, not enough bits (16) for SPS length!");
return mozilla::Err(NS_ERROR_FAILURE);
}
uint16_t sequenceParameterSetLength = reader.ReadBits(16);
uint32_t spsBitsLength = sequenceParameterSetLength * 8;
const uint8_t* spsPtr = aExtraData->Elements() + reader.BitCount() / 8;
if (reader.AdvanceBits(spsBitsLength) < spsBitsLength) {
LOG("Aborting parsing, SPS NALU size (%u bits) is larger than remaining!",
spsBitsLength);
return mozilla::Err(NS_ERROR_FAILURE);
}
H264NALU nalu(spsPtr, sequenceParameterSetLength);
if (nalu.mNalUnitType != H264_NAL_SPS) {
LOG("Aborting parsing, expect SPS but got incorrect NALU type (%d)!",
nalu.mNalUnitType);
return mozilla::Err(NS_ERROR_FAILURE);
}
avcc.mSPSs.AppendElement(nalu);
}
// TODO : make PPS parsing failure become hard fail in 1974040.
if (reader.BitsLeft() < 8) {
LOG("Failed to parse numPPS, and soft fail.");
return avcc;
}
const uint8_t numPPS = reader.ReadBits(8);
for (uint8_t idx = 0; idx < numPPS; idx++) {
if (reader.BitsLeft() < 16) {
LOG("Aborting parsing, not enough bits (16) for PPS length!");
break;
}
uint16_t pictureParameterSetLength = reader.ReadBits(16);
uint32_t ppsBitsLength = pictureParameterSetLength * 8;
const uint8_t* ppsPtr = aExtraData->Elements() + reader.BitCount() / 8;
if (reader.AdvanceBits(ppsBitsLength) < ppsBitsLength) {
LOG("Aborting parsing, PPS NALU size (%u bits) is larger than remaining!",
ppsBitsLength);
break;
}
H264NALU nalu(ppsPtr, pictureParameterSetLength);
if (nalu.mNalUnitType != H264_NAL_PPS) {
LOG("Aborting parsing, expect PPS but got incorrect NALU type (%d)!",
nalu.mNalUnitType);
break;
}
avcc.mPPSs.AppendElement(nalu);
}
// We can't guarantee the following bit contents are still correct, skip them.
// This should be removed in bug 1974040 as well.
if (avcc.mPPSs.Length() != numPPS) {
LOG("Failed to parse all PPS, and soft fail.");
return avcc;
}
// The AVCDecoderConfigurationRecord syntax requires that the SPSExt must be
// present if AVCProfileIndication is not 66 (Baseline), 77 (Main), or 88
// (Extended). However, in practice, many H.264 streams in the wild omit the
// SPSExt fields, especially when default values are used (e.g.,
// chroma_format_idc = 1 for 4:2:0 chroma format, bit_depth_luma_minus8 = 0,
// and bit_depth_chroma_minus8 = 0) Therefore, parsing this part is not
// mandatory and fail to parse this part will not cause an actual error.
// Instead, we will simply clear the incorrect result.
if (avcc.mAVCProfileIndication != 66 && avcc.mAVCProfileIndication != 77 &&
avcc.mAVCProfileIndication != 88 && reader.BitsLeft() >= 32) {
Unused << reader.ReadBits(6); // reserved
avcc.mChromaFormat = Some(reader.ReadBits(2));
Unused << reader.ReadBits(5); // reserved
avcc.mBitDepthLumaMinus8 = Some(reader.ReadBits(3));
Unused << reader.ReadBits(5); // reserved
avcc.mBitDepthChromaMinus8 = Some(reader.ReadBits(3));
const uint8_t numOfSequenceParameterSetExt = reader.ReadBits(8);
for (uint8_t idx = 0; idx < numOfSequenceParameterSetExt; idx++) {
if (reader.BitsLeft() < 16) {
LOG("Aborting parsing, not enough bits (16) for SPSExt length!");
break;
}
uint16_t sequenceParameterSetExtLength = reader.ReadBits(16);
uint32_t spsExtBitsLength = sequenceParameterSetExtLength * 8;
const uint8_t* spsExtPtr = aExtraData->Elements() + reader.BitCount() / 8;
if (reader.AdvanceBits(spsExtBitsLength) < spsExtBitsLength) {
LOG("Aborting parsing, SPS Ext NALU size (%u bits) is larger than "
"remaining!",
spsExtBitsLength);
break;
}
H264NALU nalu(spsExtPtr, sequenceParameterSetExtLength);
if (nalu.mNalUnitType != H264_NAL_SPS_EXT) {
LOG("Aborting parsing, expect SPSExt but got incorrect NALU type "
"(%d)!",
nalu.mNalUnitType);
break;
}
avcc.mSPSExts.AppendElement(nalu);
}
if (avcc.mSPSExts.Length() != numOfSequenceParameterSetExt) {
LOG("Failed to parse all SPSExt, and soft fail.");
}
}
return avcc;
}
already_AddRefed<mozilla::MediaByteBuffer> AVCCConfig::CreateNewExtraData()
const {
auto extradata = MakeRefPtr<mozilla::MediaByteBuffer>();
BitWriter writer(extradata);
writer.WriteBits(mConfigurationVersion, 8);
writer.WriteBits(mAVCProfileIndication, 8);
writer.WriteBits(mProfileCompatibility, 8);
writer.WriteBits(mAVCLevelIndication, 8);
writer.WriteBits(0x111111, 6); // reserved
writer.WriteBits(mLengthSizeMinusOne, 2);
writer.WriteBits(0x111, 3); // reserved
writer.WriteBits(mSPSs.Length(), 5);
for (const auto& nalu : mSPSs) {
writer.WriteBits(nalu.mNALU.Length(), 16);
MOZ_DIAGNOSTIC_ASSERT(writer.BitCount() % 8 == 0);
extradata->AppendElements(nalu.mNALU.Elements(), nalu.mNALU.Length());
writer.AdvanceBytes(nalu.mNALU.Length());
}
writer.WriteBits(mPPSs.Length(), 8);
for (const auto& nalu : mPPSs) {
writer.WriteBits(nalu.mNALU.Length(), 16);
MOZ_DIAGNOSTIC_ASSERT(writer.BitCount() % 8 == 0);
extradata->AppendElements(nalu.mNALU.Elements(), nalu.mNALU.Length());
writer.AdvanceBytes(nalu.mNALU.Length());
}
if (mAVCProfileIndication != 66 && mAVCProfileIndication != 77 &&
mAVCProfileIndication != 88 && mChromaFormat.isSome() &&
mBitDepthLumaMinus8.isSome() && mBitDepthChromaMinus8.isSome()) {
writer.WriteBits(0x111111, 6); // reserved
writer.WriteBits(*mChromaFormat, 2);
writer.WriteBits(0x11111, 5); // reserved
writer.WriteBits(*mBitDepthLumaMinus8, 3);
writer.WriteBits(0x11111, 5); // reserved
writer.WriteBits(*mBitDepthChromaMinus8, 3);
writer.WriteBits(mSPSExts.Length(), 8);
for (const auto& nalu : mSPSExts) {
writer.WriteBits(nalu.mNALU.Length(), 16);
MOZ_DIAGNOSTIC_ASSERT(writer.BitCount() % 8 == 0);
extradata->AppendElements(nalu.mNALU.Elements(), nalu.mNALU.Length());
writer.AdvanceBytes(nalu.mNALU.Length());
}
}
return AVCCConfig::Parse(extradata).isOk() ? extradata.forget() : nullptr;
}
H264NALU::H264NALU(const uint8_t* aData, uint32_t aByteCount)
: mNALU(aData, aByteCount) {
// Per 7.3.1 NAL unit syntax
BitReader reader(aData, aByteCount * 8);
Unused << reader.ReadBit(); // forbidden_zero_bit
Unused << reader.ReadBits(2); // nal_ref_idc
mNalUnitType = reader.ReadBits(5);
}
#undef READUE
#undef READSE
} // namespace mozilla
#undef LOG