// Copyright 2020 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/h264_annex_b_to_avc_bitstream_converter.h"
#include "base/containers/flat_set.h"
#include "base/containers/span.h"
#include "base/containers/span_writer.h"
#include "base/numerics/safe_conversions.h"
namespace media {
H264AnnexBToAvcBitstreamConverter::H264AnnexBToAvcBitstreamConverter() {
// These parts of configuration never change.
config_.version = 1;
config_.length_size = 4;
}
H264AnnexBToAvcBitstreamConverter::~H264AnnexBToAvcBitstreamConverter() =
default;
const mp4::AVCDecoderConfigurationRecord&
H264AnnexBToAvcBitstreamConverter::GetCurrentConfig() {
return config_;
}
MP4Status H264AnnexBToAvcBitstreamConverter::ConvertChunk(
const base::span<const uint8_t> input,
base::span<uint8_t> output,
bool* config_changed_out,
size_t* size_out) {
std::vector<H264NALU> slice_units;
size_t data_size = 0;
bool config_changed = false;
H264NALU nalu;
H264Parser::Result result;
int new_active_sps_id = -1;
int new_active_pps_id = -1;
// Sets of SPS and PPS ids to be included into the decoder config.
// They contain
// - all SPS and PPS units encountered in the input chunk;
// - any SPS referenced by PPS units encountered in the input;
// - The active SPS/PPS pair.
base::flat_set<int> sps_to_include;
base::flat_set<int> pps_to_include;
// Scan input buffer looking for two main types of NALUs
// 1. SPS and PPS. They'll be added to the AVC 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.
parser_.SetStream(input.data(), input.size());
while ((result = parser_.AdvanceToNextNALU(&nalu)) != H264Parser::kEOStream) {
if (result == H264Parser::kUnsupportedStream)
return MP4Status::Codes::kUnsupportedStream;
if (result != H264Parser::kOk)
return MP4Status::Codes::kFailedToParse;
switch (nalu.nal_unit_type) {
case H264NALU::kAUD: {
break;
}
case H264NALU::kSPS: {
int sps_id = -1;
result = parser_.ParseSPS(&sps_id);
if (result != H264Parser::kOk)
return MP4Status::Codes::kInvalidSPS;
id2sps_.insert_or_assign(
sps_id,
blob(nalu.data.get(),
(nalu.data + base::checked_cast<size_t>(nalu.size)).get()));
id2sps_ext_.erase(sps_id);
sps_to_include.insert(sps_id);
config_changed = true;
break;
}
case H264NALU::kSPSExt: {
int sps_id = -1;
result = parser_.ParseSPSExt(&sps_id);
if (result != H264Parser::kOk) {
return MP4Status::Codes::kFailedToParse;
}
id2sps_ext_.insert_or_assign(
sps_id,
blob(nalu.data.get(),
(nalu.data + base::checked_cast<size_t>(nalu.size)).get()));
config_changed = true;
break;
}
case H264NALU::kPPS: {
int pps_id = -1;
result = parser_.ParsePPS(&pps_id);
if (result != H264Parser::kOk)
return MP4Status::Codes::kInvalidPPS;
id2pps_.insert_or_assign(
pps_id,
blob(nalu.data.get(),
(nalu.data + base::checked_cast<size_t>(nalu.size)).get()));
pps_to_include.insert(pps_id);
if (auto* pps = parser_.GetPPS(pps_id))
sps_to_include.insert(pps->seq_parameter_set_id);
config_changed = true;
break;
}
case H264NALU::kSliceDataA:
case H264NALU::kSliceDataB:
case H264NALU::kSliceDataC:
case H264NALU::kNonIDRSlice:
case H264NALU::kIDRSlice: {
H264SliceHeader slice_hdr;
result = parser_.ParseSliceHeader(nalu, &slice_hdr);
if (result != H264Parser::kOk) {
return MP4Status::Codes::kInvalidSliceHeader;
}
const H264PPS* pps = parser_.GetPPS(slice_hdr.pic_parameter_set_id);
if (!pps) {
return MP4Status::Codes::kFailedToLookupPPS;
}
const H264SPS* sps = parser_.GetSPS(pps->seq_parameter_set_id);
if (!sps) {
return MP4Status::Codes::kFailedToLookupSPS;
}
new_active_pps_id = pps->pic_parameter_set_id;
new_active_sps_id = sps->seq_parameter_set_id;
pps_to_include.insert(new_active_pps_id);
sps_to_include.insert(new_active_sps_id);
if (new_active_sps_id != active_sps_id_) {
if (!config_changed) {
DCHECK(nalu.nal_unit_type == H264NALU::kIDRSlice)
<< "SPS shouldn't change in non-IDR slice";
}
config_changed = true;
}
}
[[fallthrough]];
default:
slice_units.push_back(nalu);
data_size += config_.length_size + 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::SpanWriter writer(output);
for (auto& unit : slice_units) {
bool written_ok =
writer.WriteU32BigEndian(unit.size) &&
writer.Write(
// SAFETY: `unit` is constructed with a size that is the number of
// elements at the data pointer.
//
// TODO(crbug.com/40284755): The `unit` should hold a span instead
// of a pointer.
UNSAFE_TODO(base::span(unit.data.get(),
base::checked_cast<size_t>(unit.size))));
if (!written_ok) {
return MP4Status::Codes::kBufferTooSmall;
}
}
DCHECK_EQ(writer.num_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_;
const H264SPS* active_sps = parser_.GetSPS(new_active_sps_id);
if (!active_sps) {
return MP4Status::Codes::kFailedToLookupSPS;
}
active_pps_id_ = new_active_pps_id;
active_sps_id_ = new_active_sps_id;
config_.sps_list.clear();
config_.pps_list.clear();
config_.sps_ext_list.clear();
// flat_set is iterated in key-order
for (int id : sps_to_include) {
config_.sps_list.push_back(id2sps_[id]);
if (id2sps_ext_.contains(id)) {
config_.sps_ext_list.push_back(id2sps_ext_[id]);
}
}
for (int id : pps_to_include)
config_.pps_list.push_back(id2pps_[id]);
config_.profile_indication = active_sps->profile_idc;
// Bits 0 and 1 are reserved and must always be zero.
config_.profile_compatibility =
((active_sps->constraint_set0_flag ? 1 : 0) << 7) |
((active_sps->constraint_set1_flag ? 1 : 0) << 6) |
((active_sps->constraint_set2_flag ? 1 : 0) << 5) |
((active_sps->constraint_set3_flag ? 1 : 0) << 4) |
((active_sps->constraint_set4_flag ? 1 : 0) << 3) |
((active_sps->constraint_set5_flag ? 1 : 0) << 2);
config_.avc_level = active_sps->level_idc;
config_.chroma_format = active_sps->chroma_format_idc;
config_.bit_depth_luma_minus8 = active_sps->bit_depth_luma_minus8;
config_.bit_depth_chroma_minus8 = active_sps->bit_depth_chroma_minus8;
}
if (config_changed_out)
*config_changed_out = config_changed;
return OkStatus();
}
} // namespace media