blob: 866bb515e852d5354cc35060859a4a93a487cef4 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/formats/mp4/h265_annex_b_to_hevc_bitstream_converter.h"
#include "base/big_endian.h"
#include "base/containers/flat_set.h"
namespace media {
H265AnnexBToHevcBitstreamConverter::H265AnnexBToHevcBitstreamConverter() {
// These configuration items never change.
config_.configurationVersion = 1;
config_.lengthSizeMinusOne = 3;
}
H265AnnexBToHevcBitstreamConverter::~H265AnnexBToHevcBitstreamConverter() =
default;
const mp4::HEVCDecoderConfigurationRecord&
H265AnnexBToHevcBitstreamConverter::GetCurrentConfig() {
return config_;
}
MP4Status H265AnnexBToHevcBitstreamConverter::ConvertChunk(
const base::span<const uint8_t> input,
base::span<uint8_t> output,
bool* config_changed_out,
size_t* size_out) {
std::vector<H265NALU> slice_units;
size_t data_size = 0;
bool config_changed = false;
H265NALU nalu;
H265Parser::Result result;
int new_active_sps_id = -1;
int new_active_pps_id = -1;
int new_active_vps_id = -1;
// Sets of SPS, PPS & VPS ids to be included into the decoder config.
// They contain
// - all VPS, SPS and PPS units encountered in the input chunk;
// - any SPS referenced by PPS units encountered in the input;
// - The active VPS/SPS/PPS pair.
base::flat_set<int> sps_to_include;
base::flat_set<int> pps_to_include;
base::flat_set<int> vps_to_include;
// Scan input buffer looking for two main types of NALUs
// 1. VPS, SPS and PPS. They'll be added to the HEVC configuration |config_|
// and will *not* be copied to |output|.
// 2. Slices. They'll being copied into the output buffer, but also affect
// what configuration (profile and level) is active now.
// A configure change will only happen on IDR frame. It is expected the
// encoder output stream repeats VPS/SPS/PPS on IDR frames.
parser_.SetStream(input.data(), input.size());
while ((result = parser_.AdvanceToNextNALU(&nalu)) != H265Parser::kEOStream) {
if (result == H265Parser::kUnsupportedStream)
return MP4Status::Codes::kUnsupportedStream;
if (result != H265Parser::kOk)
return MP4Status::Codes::kFailedToParse;
switch (nalu.nal_unit_type) {
case H265NALU::AUD_NUT: {
break;
}
case H265NALU::SPS_NUT: {
int sps_id = -1;
result = parser_.ParseSPS(&sps_id);
if (result == H265Parser::kUnsupportedStream)
return MP4Status::Codes::kInvalidSPS;
if (result != H265Parser::kOk)
return MP4Status::Codes::kInvalidSPS;
id2sps_.insert_or_assign(sps_id,
blob(nalu.data, nalu.data + nalu.size));
sps_to_include.insert(sps_id);
if (auto* sps = parser_.GetSPS(sps_id)) {
vps_to_include.insert(sps->sps_video_parameter_set_id);
}
config_changed = true;
break;
}
case H265NALU::VPS_NUT: {
int vps_id = -1;
result = parser_.ParseVPS(&vps_id);
if (result == H265Parser::kUnsupportedStream)
return MP4Status::Codes::kInvalidVPS;
if (result != H265Parser::kOk)
return MP4Status::Codes::kInvalidVPS;
id2vps_.insert_or_assign(vps_id,
blob(nalu.data, nalu.data + nalu.size));
vps_to_include.insert(vps_id);
config_changed = true;
break;
}
case H265NALU::PPS_NUT: {
int pps_id = -1;
result = parser_.ParsePPS(nalu, &pps_id);
if (result == H265Parser::kUnsupportedStream)
return MP4Status::Codes::kInvalidPPS;
if (result != H265Parser::kOk)
return MP4Status::Codes::kInvalidPPS;
id2pps_.insert_or_assign(pps_id,
blob(nalu.data, nalu.data + nalu.size));
pps_to_include.insert(pps_id);
if (auto* pps = parser_.GetPPS(pps_id))
sps_to_include.insert(pps->pps_seq_parameter_set_id);
config_changed = true;
break;
}
// TODO: when HDR encoding is supported, we need to also move the prefix
// SEI out of slice data and put it into the hvccBox.
// VCL, Non-IRAP
case H265NALU::TRAIL_N:
case H265NALU::TRAIL_R:
case H265NALU::TSA_N:
case H265NALU::TSA_R:
case H265NALU::STSA_N:
case H265NALU::STSA_R:
case H265NALU::RADL_N:
case H265NALU::RADL_R:
case H265NALU::RASL_N:
case H265NALU::RASL_R:
// VCL, IRAP
case H265NALU::BLA_W_LP:
case H265NALU::BLA_W_RADL:
case H265NALU::BLA_N_LP:
case H265NALU::IDR_W_RADL:
case H265NALU::IDR_N_LP:
case H265NALU::CRA_NUT: {
int pps_id = -1;
result = parser_.ParseSliceHeaderForPictureParameterSets(nalu, &pps_id);
if (result != H265Parser::kOk) {
return MP4Status::Codes::kInvalidSliceHeader;
}
const H265PPS* pps = parser_.GetPPS(pps_id);
if (!pps) {
return MP4Status::Codes::kFailedToLookupPPS;
}
const H265SPS* sps = parser_.GetSPS(pps->pps_seq_parameter_set_id);
if (!sps) {
return MP4Status::Codes::kFailedToLookupSPS;
}
const H265VPS* vps = parser_.GetVPS(sps->sps_video_parameter_set_id);
if (!vps) {
return MP4Status::Codes::kFailedToLookupVPS;
}
new_active_pps_id = pps->pps_pic_parameter_set_id;
new_active_sps_id = sps->sps_seq_parameter_set_id;
new_active_vps_id = vps->vps_video_parameter_set_id;
pps_to_include.insert(new_active_pps_id);
sps_to_include.insert(new_active_sps_id);
vps_to_include.insert(new_active_vps_id);
if (new_active_sps_id != active_sps_id_ ||
new_active_vps_id != active_vps_id_) {
if (!config_changed) {
DCHECK(nalu.nal_unit_type == H265NALU::IDR_W_RADL ||
nalu.nal_unit_type == H265NALU::IDR_N_LP)
<< "SPS/VPS shouldn't change in non-IDR slice";
}
config_changed = true;
}
}
[[fallthrough]];
default:
slice_units.push_back(nalu);
data_size += config_.lengthSizeMinusOne + 1 + nalu.size;
break;
}
}
if (size_out)
*size_out = data_size;
if (data_size > output.size()) {
return MP4Status::Codes::kBufferTooSmall;
}
// Write slice NALUs from the input buffer to the output buffer
// prefixing them with size.
base::BigEndianWriter writer(reinterpret_cast<char*>(output.data()),
output.size());
for (auto& unit : slice_units) {
bool written_ok =
writer.WriteU32(unit.size) && writer.WriteBytes(unit.data, unit.size);
if (!written_ok) {
return MP4Status::Codes::kBufferTooSmall;
}
}
DCHECK_LE(writer.remaining(), output.size());
size_t bytes_written = output.size() - writer.remaining();
DCHECK_EQ(bytes_written, data_size);
// Now when we are sure that everything is written and fits nicely,
// we can update parts of the |config_| that were changed by this data chunk.
if (config_changed) {
if (new_active_sps_id < 0)
new_active_sps_id = active_sps_id_;
if (new_active_pps_id < 0)
new_active_pps_id = active_pps_id_;
if (new_active_vps_id < 0)
new_active_vps_id = active_vps_id_;
const H265SPS* active_sps = parser_.GetSPS(new_active_sps_id);
if (!active_sps) {
return MP4Status::Codes::kFailedToLookupSPS;
}
const H265PPS* active_pps = parser_.GetPPS(new_active_pps_id);
if (!active_pps) {
return MP4Status::Codes::kFailedToLookupPPS;
}
active_pps_id_ = new_active_pps_id;
active_sps_id_ = new_active_sps_id;
active_vps_id_ = new_active_vps_id;
// General profile space and tier level is not provided by the parser and it
// must always be 0.
config_.general_profile_space = 0;
config_.general_tier_flag = 0;
auto ptl = active_sps->profile_tier_level;
config_.general_profile_idc = ptl.general_profile_idc;
config_.general_profile_compatibility_flags =
ptl.general_profile_compatibility_flags;
config_.general_level_idc = ptl.general_level_idc;
if (ptl.general_progressive_source_flag) {
config_.general_constraint_indicator_flags |= 1ull << 47;
}
if (ptl.general_interlaced_source_flag) {
config_.general_constraint_indicator_flags |= 1ull << 46;
}
if (ptl.general_non_packed_constraint_flag) {
config_.general_constraint_indicator_flags |= 1ull << 45;
}
if (ptl.general_frame_only_constraint_flag) {
config_.general_constraint_indicator_flags |= 1ull << 44;
}
if (ptl.general_one_picture_only_constraint_flag) {
config_.general_constraint_indicator_flags |= 1ull << 35;
}
config_.min_spatial_segmentation_idc =
active_sps->vui_parameters.min_spatial_segmentation_idc;
if (active_sps->vui_parameters.min_spatial_segmentation_idc == 0) {
config_.parallelismType =
mp4::HEVCDecoderConfigurationRecord::kMixedParallel;
} else if (active_pps->entropy_coding_sync_enabled_flag &&
active_pps->tiles_enabled_flag) {
config_.parallelismType =
mp4::HEVCDecoderConfigurationRecord::kMixedParallel;
} else if (active_pps->entropy_coding_sync_enabled_flag) {
config_.parallelismType =
mp4::HEVCDecoderConfigurationRecord::kWaveFrontParallel;
} else if (active_pps->tiles_enabled_flag) {
config_.parallelismType =
mp4::HEVCDecoderConfigurationRecord::kTileParallel;
} else {
config_.parallelismType =
mp4::HEVCDecoderConfigurationRecord::kSliceParallel;
}
config_.chromaFormat = active_sps->chroma_format_idc;
config_.bitDepthLumaMinus8 = active_sps->bit_depth_luma_minus8;
config_.bitDepthChromaMinus8 = active_sps->bit_depth_chroma_minus8;
// Gives the average frame rate in units of frames per 256 seconds.
// A value of 0 indicates an unspecified average frame rate.
config_.avgFrameRate = 0;
// Set to 0 to indicate it may or may not be of constant frame rate.
config_.constantFrameRate = 0;
config_.numTemporalLayers = active_sps->sps_max_sub_layers_minus1 + 1;
config_.temporalIdNested = active_sps->sps_temporal_id_nesting_flag;
// We write 3 arrays, in the order of VPS array, SPS array and PPS array.
auto hvcc_array_idx = 0;
mp4::HEVCDecoderConfigurationRecord::HVCCNALUnit nal_unit;
mp4::HEVCDecoderConfigurationRecord::HVCCNALArray nalu_array;
// bit 7: array_completeness. When set to 1, corresponding type of
// NAL unit will be in the array only and none are in the stream; otherwise
// they may additionally be in the stream.
uint8_t first_byte = (1 << 7) | (H265NALU::VPS_NUT & 0x3F);
if (id2vps_.size() > 0) {
nalu_array.first_byte = first_byte;
for (auto& vps : id2vps_) {
nal_unit.assign(vps.second.begin(), vps.second.end());
nalu_array.units.push_back(nal_unit);
}
config_.arrays.push_back(nalu_array);
hvcc_array_idx++;
}
first_byte = (1 << 7) | (H265NALU::SPS_NUT & 0x3F);
nalu_array.units.clear();
if (id2sps_.size() > 0) {
nalu_array.first_byte = first_byte;
for (auto& sps : id2sps_) {
nal_unit.assign(sps.second.begin(), sps.second.end());
nalu_array.units.push_back(nal_unit);
}
config_.arrays.push_back(nalu_array);
hvcc_array_idx++;
}
first_byte = (1 << 7) | (H265NALU::PPS_NUT & 0x3F);
nalu_array.units.clear();
if (id2sps_.size() > 0) {
nalu_array.first_byte = first_byte;
for (auto& pps : id2pps_) {
nal_unit.assign(pps.second.begin(), pps.second.end());
nalu_array.units.push_back(nal_unit);
}
config_.arrays.push_back(nalu_array);
hvcc_array_idx++;
}
config_.numOfArrays = hvcc_array_idx;
}
if (config_changed_out) {
*config_changed_out = config_changed;
}
return OkStatus();
}
} // namespace media