diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index 99b5096be..87464da9a 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -31,7 +31,9 @@ extern "C" { #include } +#include #include +#include VideoStore::VideoStore( const char *filename_in, @@ -1731,35 +1733,18 @@ void VideoStore::finalize() { oc->pb = nullptr; // The MOV muxer writes an mfra (Movie Fragment Random Access) box at the end - // of the file when fragmentation is on. Its trailing mfro box is exactly 16 - // bytes and contains the mfra size, so we can subtract that to find where - // the final fragment's mdat actually ends. + // of the file when fragmentation is on, so the final fragment's mdat ends + // where the mfra begins. int64_t fragment_n_end = file_size; - if (!filename.empty() && file_size >= 16) { - FILE *fp = fopen(filename.c_str(), "rb"); - if (fp) { - if (fseeko(fp, file_size - 16, SEEK_SET) == 0) { - uint8_t mfro[16]; - if (fread(mfro, 1, 16, fp) == 16) { - uint32_t box_size = (static_cast(mfro[0]) << 24) - | (static_cast(mfro[1]) << 16) - | (static_cast(mfro[2]) << 8) - | static_cast(mfro[3]); - if (box_size == 16 - && mfro[4] == 'm' && mfro[5] == 'f' && mfro[6] == 'r' && mfro[7] == 'o') { - uint32_t mfra_size = (static_cast(mfro[12]) << 24) - | (static_cast(mfro[13]) << 16) - | (static_cast(mfro[14]) << 8) - | static_cast(mfro[15]); - if (mfra_size > 0 && static_cast(mfra_size) <= file_size) { - fragment_n_end = file_size - mfra_size; - Debug(1, "mfra trailer is %u bytes; final fragment ends at %" PRId64, - mfra_size, fragment_n_end); - } - } - } + if (!filename.empty()) { + const int fd = ::open(filename.c_str(), O_RDONLY | O_CLOEXEC); + if (fd >= 0) { + fragment_n_end = zm_mp4::media_end(fd, file_size); + close(fd); + if (fragment_n_end != file_size) { + Debug(1, "mfra trailer is %" PRId64 " bytes; final fragment ends at %" PRId64, + file_size - fragment_n_end, fragment_n_end); } - fclose(fp); } } diff --git a/tests/zm_mp4_sidx.cpp b/tests/zm_mp4_sidx.cpp index 5ca481133..0f135eb3d 100644 --- a/tests/zm_mp4_sidx.cpp +++ b/tests/zm_mp4_sidx.cpp @@ -229,6 +229,42 @@ TEST_CASE("Mp4SidxIndexesAFileWithNoMfra") { REQUIRE(first_difference(read_file(subject.path), subject.expected) == kSame); } +TEST_CASE("Mp4SidxMediaEndTrustsOnlyAnMfraThatIsThere") { + // finalize() sizes the last HLS fragment from this, so an mfro whose size + // does not land on an mfra of that size must not cut the media short. + std::vector file = read_file(fixture("sidx-abs.mp4")); + const int64_t size = static_cast(file.size()); + const int64_t mfra = static_cast(offset_of(top_level(file), "mfra")); + const std::filesystem::path path = scratch("mfro.mp4"); + + auto end_of = [&path](const std::vector &bytes) { + write_file(path, bytes); + const int fd = open(path.c_str(), O_RDONLY); + REQUIRE(fd >= 0); + const int64_t end = zm_mp4::media_end(fd, static_cast(bytes.size())); + close(fd); + return end; + }; + + REQUIRE(end_of(file) == mfra); + + SECTION("an mfro size off by four") { + file[size - 1] = uint8_t(file[size - 1] + 4); + REQUIRE(end_of(file) == size); + } + SECTION("an mfro size larger than the file") { + file[size - 4] = 0xFF; + REQUIRE(end_of(file) == size); + } + SECTION("no mfro at all") { + memcpy(&file[size - 12], "xxxx", 4); + REQUIRE(end_of(file) == size); + } + + std::error_code ignored; + std::filesystem::remove(path, ignored); +} + TEST_CASE("Mp4SidxMergesWhenTheRegionIsTight") { std::vector fragments; for (int i = 0; i < 8; i++) {