Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 38 additions & 5 deletions media/codec_signaling/src/codec_signaling.c
Original file line number Diff line number Diff line change
Expand Up @@ -548,8 +548,12 @@ static moq_result_t build_avcc(const moq_codec_init_data_cfg_t *cfg,
/*
* Parse the fields an hvcC header needs from an HEVC SPS NAL (payload after
* the 2-byte NAL header), per ISO/IEC 23008-2 7.3.2.2.1. The 12-byte general
* profile_tier_level maps directly onto hvcC bytes 1..12. Sub-layer PTL
* (sps_max_sub_layers_minus1 > 0) is not parsed.
* profile_tier_level maps directly onto hvcC bytes 1..12.
*
* When sps_max_sub_layers_minus1 > 0 the SPS carries sub-layer PTL syntax
* between the general PTL and sps_seq_parameter_set_id. hvcC needs none of it,
* but it is variable-length and has to be stepped over to reach
* chroma_format_idc and the bit depths.
*/
static moq_result_t parse_hevc_sps(const uint8_t *sps, size_t sps_len,
uint8_t ptl[12],
Expand All @@ -562,7 +566,9 @@ static moq_result_t parse_hevc_sps(const uint8_t *sps, size_t sps_len,
if (sps_len < 3) {
return MOQ_ERR_PROTO;
}
uint8_t rbsp[64];
/* Large enough for the general PTL plus a full set of sub-layer PTLs
* (7 x 96 bits) and the fields we read after them. */
uint8_t rbsp[256];
size_t n = deemulate(sps + 2, sps_len - 2, rbsp, sizeof(rbsp));
if (n < 13) {
return MOQ_ERR_PROTO;
Expand All @@ -574,10 +580,37 @@ static moq_result_t parse_hevc_sps(const uint8_t *sps, size_t sps_len,
uint32_t tid_nest = br_u(&br, 1); /* sps_temporal_id_nesting */

memcpy(ptl, rbsp + 1, 12); /* general profile_tier_level */
br.bitpos = 8 + 12 * 8;

/* profile_tier_level() sub-layer part, 23008-2 7.3.3. The general half is
* the fixed 12 bytes already copied above; what follows is present only
* when there is more than one temporal sub-layer. max_sub is 3 bits, so
* it never exceeds 7 and the arrays below are always in range. */
if (max_sub > 0) {
return MOQ_ERR_UNSUPPORTED; /* sub-layer PTL not handled */
bool profile_present[7] = { false };
bool level_present[7] = { false };
for (uint32_t i = 0; i < max_sub; i++) {
profile_present[i] = br_u(&br, 1) != 0;
level_present[i] = br_u(&br, 1) != 0;
}
for (uint32_t i = max_sub; i < 8; i++) {
br_u(&br, 2); /* reserved_zero_2bits */
}
for (uint32_t i = 0; i < max_sub; i++) {
if (profile_present[i]) {
/* Same layout as the general PTL minus its 8-bit level. */
br_u(&br, 32);
br_u(&br, 32);
br_u(&br, 24);
}
if (level_present[i]) {
br_u(&br, 8); /* sub_layer_level_idc */
}
}
if (br.overrun) {
return MOQ_ERR_PROTO;
}
}
br.bitpos = 8 + 12 * 8;

br_ue(&br); /* sps_seq_parameter_set_id */
uint32_t chroma = br_ue(&br);
Expand Down
40 changes: 40 additions & 0 deletions media/codec_signaling/tests/test_codec_signaling.c
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,46 @@ int main(void)
CHECK(out_len == sizeof(expect) && memcmp(out, expect, sizeof(expect)) == 0);
}

/* -- HEVC Annex B with temporal sub-layers -> hvcC -------------- */
{
/* VPS + SPS + PPS captured from Apple's low-latency HEVC encoder
* (com.apple.videotoolbox.videoencoder.hevc.rtvc) at 1280x720. Its SPS
* carries sps_max_sub_layers_minus1 = 1, which the parser used to
* reject outright with MOQ_ERR_UNSUPPORTED -- no hvcC meant no video
* track was ever announced, so a stream encoded this way came out
* audio-only. The sub-layer PTL is skipped, not interpreted. */
static const uint8_t annexb[] = {
0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x0c, 0x03, 0xff, 0xff, 0x01, 0x60,
0x00, 0x00, 0x03, 0x00, 0xb0, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00,
0x5d, 0x00, 0x00, 0x1b, 0x02, 0x40, 0x00, 0x00, 0x00, 0x01, 0x42, 0x01,
0x03, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0xb0, 0x00, 0x00, 0x03, 0x00,
0x00, 0x03, 0x00, 0x5d, 0x00, 0x00, 0xa0, 0x02, 0x80, 0x80, 0x2d, 0x16,
0x20, 0x6e, 0xe4, 0x52, 0x32, 0xe7, 0xe1, 0x3d, 0x0b, 0xea, 0x1b, 0xd5,
0x29, 0xa8, 0x10, 0x10, 0x10, 0x1f, 0xc2, 0x01, 0x04, 0x00, 0x00, 0x00,
0x01, 0x44, 0x01, 0xc0, 0x72, 0xf0, 0x5b, 0x24,
};
/* general profile_tier_level, hvcC bytes 1..12: Main profile, Main
* tier, general_level_idc 0x5d (level 4.0). */
static const uint8_t ptl[] = {
0x01, 0x60, 0x00, 0x00, 0x00, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5d,
};
moq_codec_init_data_cfg_t cfg;
moq_codec_init_data_cfg_init(&cfg);
cfg.source_format = MOQ_CODEC_SOURCE_HEVC_ANNEXB;
cfg.source = bytes(annexb, sizeof(annexb));

uint8_t out[256];
size_t out_len = 0;
moq_result_t rc = moq_codec_init_data_build(&cfg, out, sizeof(out), &out_len);
CHECK(rc == MOQ_OK);
CHECK(out_len >= 23);
CHECK(out[0] == 0x01); /* configurationVersion */
CHECK(memcmp(out + 1, ptl, sizeof(ptl)) == 0);
/* byte 21: constantFrameRate(2)=0 | numTemporalLayers(3)=2 |
* temporalIdNested(1)=1 | lengthSizeMinusOne(2)=3 */
CHECK(out[21] == 0x17);
}

/* -- AV1 OBU -> av1C (header vs ffmpeg; seq header verbatim) ---- */
{
/* Temporal delimiter + sequence header OBU from SVT-AV1. */
Expand Down
Loading