Skip to content

Commit 6083ab4

Browse files
committed
Validate live MP4 codec initialization data
1 parent 537f846 commit 6083ab4

4 files changed

Lines changed: 200 additions & 1 deletion

File tree

CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,12 @@ if(OPENMOQ_BUILD_TESTS)
489489
target_link_libraries(openmoq-publisher-packaging-tests PRIVATE openmoq_publisher_lib)
490490
add_test(NAME openmoq-publisher-packaging-tests COMMAND openmoq-publisher-packaging-tests)
491491

492+
add_executable(openmoq-publisher-mp4-init-tests
493+
tests/mp4_init_test.cpp
494+
)
495+
target_link_libraries(openmoq-publisher-mp4-init-tests PRIVATE openmoq_publisher_lib)
496+
add_test(NAME openmoq-publisher-mp4-init-tests COMMAND openmoq-publisher-mp4-init-tests)
497+
492498
add_executable(openmoq-publisher-msf-catalog-tests
493499
tests/msf_catalog_test.cpp
494500
)

src/cmsf_packager.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -734,11 +734,13 @@ LiveCatalog build_live_catalog(const std::vector<TrackDescription>& tracks,
734734
std::map<std::string, std::string> init_data_by_track;
735735
for (std::size_t index = 0; index < tracks.size(); ++index) {
736736
const auto& track = tracks[index];
737+
std::vector<std::uint8_t> codec_init_data =
738+
build_track_codec_init_data(init_segment, track, index);
737739
std::vector<std::uint8_t> track_init = build_track_specific_init_segment(init_segment, track, index);
738740
init_data_by_track.emplace(track.track_name, base64_encode(track_init));
739741
result.track_initializations.push_back({
740742
.track_name = track.track_name,
741-
.codec_payload = {},
743+
.codec_payload = std::move(codec_init_data),
742744
.init_segment = std::move(track_init),
743745
});
744746
}

tests/ffmpeg_avc_init_fixture.inc

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// FFmpeg/Lavf 62.6.103 fragmented-MP4 initialization segment for a 320x180 H.264
2+
// stream. Keeping independently muxed bytes here prevents the test fixture
3+
// from sharing moqxr's sample-entry layout assumptions.
4+
constexpr unsigned char kFfmpegAvcInitData[] = {
5+
0x00, 0x00, 0x00, 0x1c, 0x66, 0x74, 0x79, 0x70, 0x69, 0x73, 0x6f, 0x35,
6+
0x00, 0x00, 0x02, 0x00, 0x69, 0x73, 0x6f, 0x35, 0x69, 0x73, 0x6f, 0x36,
7+
0x6d, 0x70, 0x34, 0x31, 0x00, 0x00, 0x02, 0xed, 0x6d, 0x6f, 0x6f, 0x76,
8+
0x00, 0x00, 0x00, 0x6c, 0x6d, 0x76, 0x68, 0x64, 0x00, 0x00, 0x00, 0x00,
9+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe8,
10+
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
11+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
12+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
13+
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
14+
0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
15+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
16+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
17+
0x00, 0x00, 0x01, 0xf0, 0x74, 0x72, 0x61, 0x6b, 0x00, 0x00, 0x00, 0x5c,
18+
0x74, 0x6b, 0x68, 0x64, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00,
19+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
20+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
21+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
22+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
23+
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
24+
0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00,
25+
0x00, 0xb4, 0x00, 0x00, 0x00, 0x00, 0x01, 0x8c, 0x6d, 0x64, 0x69, 0x61,
26+
0x00, 0x00, 0x00, 0x20, 0x6d, 0x64, 0x68, 0x64, 0x00, 0x00, 0x00, 0x00,
27+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00,
28+
0x00, 0x00, 0x00, 0x00, 0x55, 0xc4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d,
29+
0x68, 0x64, 0x6c, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
30+
0x76, 0x69, 0x64, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
31+
0x00, 0x00, 0x00, 0x00, 0x56, 0x69, 0x64, 0x65, 0x6f, 0x48, 0x61, 0x6e,
32+
0x64, 0x6c, 0x65, 0x72, 0x00, 0x00, 0x00, 0x01, 0x37, 0x6d, 0x69, 0x6e,
33+
0x66, 0x00, 0x00, 0x00, 0x14, 0x76, 0x6d, 0x68, 0x64, 0x00, 0x00, 0x00,
34+
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
35+
0x24, 0x64, 0x69, 0x6e, 0x66, 0x00, 0x00, 0x00, 0x1c, 0x64, 0x72, 0x65,
36+
0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
37+
0x0c, 0x75, 0x72, 0x6c, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
38+
0xf7, 0x73, 0x74, 0x62, 0x6c, 0x00, 0x00, 0x00, 0xab, 0x73, 0x74, 0x73,
39+
0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
40+
0x9b, 0x61, 0x76, 0x63, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
41+
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
42+
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0xb4, 0x00, 0x48, 0x00,
43+
0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x15,
44+
0x4c, 0x61, 0x76, 0x63, 0x36, 0x32, 0x2e, 0x32, 0x31, 0x2e, 0x31, 0x30,
45+
0x30, 0x20, 0x6c, 0x69, 0x62, 0x78, 0x32, 0x36, 0x34, 0x00, 0x00, 0x00,
46+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0xff, 0xff, 0x00,
47+
0x00, 0x00, 0x35, 0x61, 0x76, 0x63, 0x43, 0x01, 0x64, 0x00, 0x0c, 0xff,
48+
0xe1, 0x00, 0x1a, 0x67, 0x64, 0x00, 0x0c, 0xac, 0xd9, 0x41, 0x41, 0x9f,
49+
0x9f, 0x01, 0x10, 0x00, 0x00, 0x03, 0x00, 0x10, 0x00, 0x00, 0x03, 0x03,
50+
0x00, 0xf1, 0x42, 0x99, 0x60, 0x01, 0x00, 0x04, 0x68, 0xef, 0x8f, 0xcb,
51+
0xfd, 0xf8, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x10, 0x70, 0x61, 0x73, 0x70,
52+
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10,
53+
0x73, 0x74, 0x74, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
54+
0x00, 0x00, 0x00, 0x10, 0x73, 0x74, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00,
55+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x73, 0x74, 0x73, 0x7a,
56+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
57+
0x00, 0x00, 0x00, 0x10, 0x73, 0x74, 0x63, 0x6f, 0x00, 0x00, 0x00, 0x00,
58+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x6d, 0x76, 0x65, 0x78,
59+
0x00, 0x00, 0x00, 0x20, 0x74, 0x72, 0x65, 0x78, 0x00, 0x00, 0x00, 0x00,
60+
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
61+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61,
62+
0x75, 0x64, 0x74, 0x61, 0x00, 0x00, 0x00, 0x59, 0x6d, 0x65, 0x74, 0x61,
63+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x68, 0x64, 0x6c, 0x72,
64+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6d, 0x64, 0x69, 0x72,
65+
0x61, 0x70, 0x70, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
66+
0x00, 0x00, 0x00, 0x00, 0x2c, 0x69, 0x6c, 0x73, 0x74, 0x00, 0x00, 0x00,
67+
0x24, 0xa9, 0x74, 0x6f, 0x6f, 0x00, 0x00, 0x00, 0x1c, 0x64, 0x61, 0x74,
68+
0x61, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x61, 0x76,
69+
0x66, 0x36, 0x32, 0x2e, 0x36, 0x2e, 0x31, 0x30, 0x33
70+
};
71+
constexpr std::size_t kFfmpegAvcInitDataSize = 777;

tests/mp4_init_test.cpp

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
#include "openmoq/publisher/cmsf_packager.h"
2+
#include "openmoq/publisher/mp4_box.h"
3+
4+
#include <cstddef>
5+
#include <cstdint>
6+
#include <iostream>
7+
#include <span>
8+
#include <stdexcept>
9+
#include <string>
10+
#include <vector>
11+
12+
namespace {
13+
14+
#include "ffmpeg_avc_init_fixture.inc"
15+
16+
using namespace openmoq::publisher;
17+
18+
static_assert(sizeof(kFfmpegAvcInitData) == kFfmpegAvcInitDataSize);
19+
20+
bool expect(bool condition, const std::string& message) {
21+
if (!condition) {
22+
std::cerr << "FAIL: " << message << '\n';
23+
return false;
24+
}
25+
return true;
26+
}
27+
28+
bool expect_contains(const std::string& text, const std::string& expected, const std::string& message) {
29+
return expect(text.find(expected) != std::string::npos, message);
30+
}
31+
32+
std::uint32_t read_be32(std::span<const std::uint8_t> bytes, std::size_t offset) {
33+
return (static_cast<std::uint32_t>(bytes[offset]) << 24U) | (static_cast<std::uint32_t>(bytes[offset + 1]) << 16U) |
34+
(static_cast<std::uint32_t>(bytes[offset + 2]) << 8U) | static_cast<std::uint32_t>(bytes[offset + 3]);
35+
}
36+
37+
void add_to_box_size(std::vector<std::uint8_t>& bytes, std::size_t offset, std::uint32_t amount) {
38+
const std::uint32_t value = read_be32(bytes, offset) + amount;
39+
bytes[offset] = static_cast<std::uint8_t>((value >> 24U) & 0xFFU);
40+
bytes[offset + 1] = static_cast<std::uint8_t>((value >> 16U) & 0xFFU);
41+
bytes[offset + 2] = static_cast<std::uint8_t>((value >> 8U) & 0xFFU);
42+
bytes[offset + 3] = static_cast<std::uint8_t>(value & 0xFFU);
43+
}
44+
45+
std::vector<std::uint8_t> make_nonstandard_visual_preamble(std::span<const std::uint8_t> init_segment) {
46+
std::vector<std::uint8_t> bytes(init_segment.begin(), init_segment.end());
47+
const auto boxes = parse_mp4_boxes(bytes);
48+
const Mp4Box* moov = find_first_box(boxes, "moov");
49+
const Mp4Box* trak = moov == nullptr ? nullptr : find_child_box(*moov, "trak");
50+
const Mp4Box* mdia = trak == nullptr ? nullptr : find_child_box(*trak, "mdia");
51+
const Mp4Box* minf = mdia == nullptr ? nullptr : find_child_box(*mdia, "minf");
52+
const Mp4Box* stbl = minf == nullptr ? nullptr : find_child_box(*minf, "stbl");
53+
const Mp4Box* stsd = stbl == nullptr ? nullptr : find_child_box(*stbl, "stsd");
54+
if (stsd == nullptr) {
55+
throw std::runtime_error("fixture has no stsd");
56+
}
57+
58+
const std::size_t sample_entry_offset = stsd->payload.offset + 8;
59+
const std::size_t child_offset = sample_entry_offset + kVisualSampleEntryChildOffset;
60+
bytes.insert(bytes.begin() + static_cast<std::ptrdiff_t>(child_offset), 8, 0);
61+
62+
constexpr std::uint32_t kInsertedPreambleBytes = 8;
63+
for (const Mp4Box* box : {moov, trak, mdia, minf, stbl, stsd}) {
64+
add_to_box_size(bytes, box->span.offset, kInsertedPreambleBytes);
65+
}
66+
add_to_box_size(bytes, sample_entry_offset, kInsertedPreambleBytes);
67+
return bytes;
68+
}
69+
70+
} // namespace
71+
72+
int main() {
73+
bool ok = true;
74+
const std::span<const std::uint8_t> init_segment(kFfmpegAvcInitData, kFfmpegAvcInitDataSize);
75+
76+
const auto boxes = parse_mp4_boxes(init_segment);
77+
const auto tracks = extract_tracks(boxes, init_segment);
78+
79+
ok &= expect(tracks.size() == 1, "expected one track in the independently muxed init segment");
80+
if (tracks.size() != 1) {
81+
return 1;
82+
}
83+
84+
const auto& track = tracks.front();
85+
ok &= expect(track.codec == "avc1.64000C", "expected the complete RFC 6381 AVC codec string, not a bare fourcc");
86+
ok &= expect(track.sample_entry_type == "avc1", "expected the avc1 sample-entry type");
87+
ok &= expect(track.width == 320 && track.height == 180, "expected 320x180 geometry from the VisualSampleEntry");
88+
ok &= expect(track.timescale == 12288, "expected the independently muxed track timescale");
89+
90+
const auto live_catalog = build_live_catalog(tracks, init_segment, true);
91+
const std::string catalog_text(live_catalog.catalog_payload.begin(), live_catalog.catalog_payload.end());
92+
ok &= expect_contains(catalog_text, "\"codec\":\"avc1.64000C\"",
93+
"expected the live catalog to advertise the complete codec string");
94+
ok &= expect_contains(catalog_text, "\"initDataList\"", "expected the live catalog to include initialization data");
95+
ok &= expect(live_catalog.track_initializations.size() == 1 &&
96+
!live_catalog.track_initializations.front().codec_payload.empty() &&
97+
!live_catalog.track_initializations.front().init_segment.empty(),
98+
"expected one track initialization with codec and CMAF init payloads");
99+
100+
const auto malformed_init = make_nonstandard_visual_preamble(init_segment);
101+
const auto malformed_tracks = extract_tracks(parse_mp4_boxes(malformed_init), malformed_init);
102+
ok &= expect(malformed_tracks.size() == 1 && malformed_tracks.front().codec == "avc1",
103+
"expected the shifted decoder configuration to expose the "
104+
"bare-fourcc failure mode");
105+
106+
bool malformed_rejected = false;
107+
std::string malformed_error;
108+
try {
109+
(void)build_live_catalog(malformed_tracks, malformed_init, true);
110+
} catch (const std::runtime_error& error) {
111+
malformed_rejected = true;
112+
malformed_error = error.what();
113+
}
114+
ok &= expect(malformed_rejected, "expected live catalog generation to reject "
115+
"a nonstandard visual preamble");
116+
ok &= expect_contains(malformed_error, "codec initData box",
117+
"expected rejection to identify the missing codec initialization box");
118+
119+
return ok ? 0 : 1;
120+
}

0 commit comments

Comments
 (0)