Skip to content

Commit 5acc4dc

Browse files
committed
Fix HEVC codec string encoding
1 parent f5f5f6c commit 5acc4dc

2 files changed

Lines changed: 90 additions & 18 deletions

File tree

src/mp4_box.cpp

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,32 @@ std::string hex_byte(std::uint8_t value) {
115115
return out.str();
116116
}
117117

118+
std::string trim_trailing_zero_nibbles(std::uint32_t value) {
119+
std::ostringstream out;
120+
out << std::uppercase << std::hex << value;
121+
std::string text = out.str();
122+
while (text.size() > 1 && text.back() == '0') {
123+
text.pop_back();
124+
}
125+
return text;
126+
}
127+
128+
std::string hevc_constraint_string(std::span<const std::uint8_t, 6> constraint_bytes) {
129+
std::size_t last_non_zero = constraint_bytes.size();
130+
while (last_non_zero > 0 && constraint_bytes[last_non_zero - 1] == 0) {
131+
--last_non_zero;
132+
}
133+
if (last_non_zero == 0) {
134+
return {};
135+
}
136+
137+
std::ostringstream out;
138+
for (std::size_t index = 0; index < last_non_zero; ++index) {
139+
out << hex_byte(constraint_bytes[index]);
140+
}
141+
return out.str();
142+
}
143+
118144
std::size_t find_child_box_offset(const Mp4Box& sample_entry,
119145
std::span<const std::uint8_t> bytes,
120146
std::size_t child_offset,
@@ -172,32 +198,27 @@ std::string hevc_codec_string(const Mp4Box& sample_entry, std::span<const std::u
172198
const char profile_space = (profile_byte >> 6U) == 1 ? 'A' : (profile_byte >> 6U) == 2 ? 'B' : (profile_byte >> 6U) == 3 ? 'C' : '\0';
173199
const std::uint8_t profile_idc = profile_byte & 0x1FU;
174200
const std::uint32_t compatibility_flags = read_be32(bytes, hvcc_offset + 10);
175-
const std::uint64_t constraint_indicator =
176-
(static_cast<std::uint64_t>(bytes[hvcc_offset + 14]) << 40U) |
177-
(static_cast<std::uint64_t>(bytes[hvcc_offset + 15]) << 32U) |
178-
(static_cast<std::uint64_t>(bytes[hvcc_offset + 16]) << 24U) |
179-
(static_cast<std::uint64_t>(bytes[hvcc_offset + 17]) << 16U) |
180-
(static_cast<std::uint64_t>(bytes[hvcc_offset + 18]) << 8U) |
181-
static_cast<std::uint64_t>(bytes[hvcc_offset + 19]);
182201
const std::uint8_t level_idc = bytes[hvcc_offset + 20];
202+
const std::array<std::uint8_t, 6> constraint_bytes = {
203+
bytes[hvcc_offset + 14],
204+
bytes[hvcc_offset + 15],
205+
bytes[hvcc_offset + 16],
206+
bytes[hvcc_offset + 17],
207+
bytes[hvcc_offset + 18],
208+
bytes[hvcc_offset + 19],
209+
};
183210

184211
std::ostringstream out;
185212
out << sample_entry.type << '.';
186213
if (profile_space != '\0') {
187214
out << profile_space;
188215
}
189216
out << static_cast<unsigned int>(profile_idc) << '.'
190-
<< std::uppercase << std::hex << compatibility_flags << '.'
191-
<< ((bytes[hvcc_offset + 13] & 0x20U) != 0 ? 'H' : 'L') << static_cast<unsigned int>(level_idc);
192-
if (constraint_indicator != 0) {
193-
out << '.';
194-
for (int shift = 40; shift >= 0; shift -= 8) {
195-
const auto component = static_cast<std::uint8_t>((constraint_indicator >> shift) & 0xFFU);
196-
if (component == 0 && shift != 0) {
197-
continue;
198-
}
199-
out << hex_byte(component);
200-
}
217+
<< trim_trailing_zero_nibbles(compatibility_flags) << '.'
218+
<< ((profile_byte & 0x20U) != 0 ? 'H' : 'L') << static_cast<unsigned int>(level_idc);
219+
const std::string constraint_string = hevc_constraint_string(constraint_bytes);
220+
if (!constraint_string.empty()) {
221+
out << '.' << constraint_string;
201222
}
202223
return out.str();
203224
}

tests/cmaf_segmenter_test.cpp

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <cstdint>
66
#include <iostream>
77
#include <map>
8+
#include <array>
89
#include <sstream>
910
#include <string>
1011
#include <vector>
@@ -236,6 +237,45 @@ std::vector<std::uint8_t> make_multitrack_init_mp4() {
236237
return concat({ftyp, moov});
237238
}
238239

240+
std::vector<std::uint8_t> make_hevc_init_mp4(std::uint8_t general_profile_byte,
241+
std::uint32_t compatibility_flags,
242+
std::array<std::uint8_t, 6> constraint_bytes,
243+
std::uint8_t level_idc) {
244+
const auto ftyp = make_box("ftyp", {'i', 's', 'o', '6', 0, 0, 0, 1, 'i', 's', 'o', '6', 'c', 'm', 'f', 'c'});
245+
const auto tkhd = make_full_box("tkhd",
246+
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0});
247+
const auto hdlr = make_full_box("hdlr", {0, 0, 0, 0, 'v', 'i', 'd', 'e', 0, 0, 0, 0});
248+
auto video_header = std::vector<std::uint8_t>(70, 0);
249+
video_header[24] = 0x01;
250+
video_header[25] = 0x40;
251+
video_header[26] = 0x00;
252+
video_header[27] = 0xf0;
253+
254+
std::vector<std::uint8_t> hvcc_payload = {
255+
0x01,
256+
general_profile_byte,
257+
static_cast<std::uint8_t>((compatibility_flags >> 24U) & 0xFFU),
258+
static_cast<std::uint8_t>((compatibility_flags >> 16U) & 0xFFU),
259+
static_cast<std::uint8_t>((compatibility_flags >> 8U) & 0xFFU),
260+
static_cast<std::uint8_t>(compatibility_flags & 0xFFU),
261+
constraint_bytes[0],
262+
constraint_bytes[1],
263+
constraint_bytes[2],
264+
constraint_bytes[3],
265+
constraint_bytes[4],
266+
constraint_bytes[5],
267+
level_idc,
268+
};
269+
const auto sample_entry = make_box("hev1", concat({video_header, make_box("hvcC", hvcc_payload)}));
270+
const auto stsd = make_full_box("stsd", concat({std::vector<std::uint8_t>{0, 0, 0, 1}, sample_entry}));
271+
const auto stbl = make_box("stbl", stsd);
272+
const auto minf = make_box("minf", stbl);
273+
const auto mdia = make_box("mdia", concat({hdlr, minf}));
274+
const auto trak = make_box("trak", concat({tkhd, mdia}));
275+
const auto moov = make_box("moov", trak);
276+
return concat({ftyp, moov});
277+
}
278+
239279
std::size_t find_after(std::string_view haystack, std::string_view needle, std::size_t start = 0) {
240280
const std::size_t pos = haystack.find(needle, start);
241281
return pos == std::string_view::npos ? pos : pos + needle.size();
@@ -485,5 +525,16 @@ int main() {
485525
ok &= expect(audio_init_boxes[0].type == "ftyp" && audio_init_boxes[1].type == "moov",
486526
"expected audio initData to contain ftyp and moov boxes");
487527

528+
const auto hevc_main_bytes = make_hevc_init_mp4(0x01, 0x60000000, {0xB0, 0x00, 0x00, 0x00, 0x00, 0x00}, 90);
529+
const auto hevc_high_bytes = make_hevc_init_mp4(0x22, 0x40000000, {0xB0, 0x00, 0x00, 0x00, 0x00, 0x00}, 150);
530+
const auto hevc_main_tracks = extract_tracks(parse_mp4_boxes(hevc_main_bytes), hevc_main_bytes);
531+
const auto hevc_high_tracks = extract_tracks(parse_mp4_boxes(hevc_high_bytes), hevc_high_bytes);
532+
ok &= expect(hevc_main_tracks.size() == 1, "expected one HEVC main-profile track");
533+
ok &= expect(hevc_high_tracks.size() == 1, "expected one HEVC high-tier track");
534+
ok &= expect(hevc_main_tracks.front().codec == "hev1.1.6.L90.B0",
535+
"expected compact RFC 6381 HEVC main-profile codec string");
536+
ok &= expect(hevc_high_tracks.front().codec == "hev1.2.4.H150.B0",
537+
"expected compact RFC 6381 HEVC high-tier codec string");
538+
488539
return ok ? 0 : 1;
489540
}

0 commit comments

Comments
 (0)