mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2026-10-01 23:15:28 -04:00
refactor: find the mfra in finalize() with zm_mp4::media_end
finalize() located the mfra by reading the trailing mfro with fopen and fseeko and decoding it by hand, the same job zm_mp4::media_end() does for the sidx scan. Use media_end() for both, so the last HLS fragment and the index agree on where the media ends. media_end() is also stricter: it requires an mfra box of the stated size at the offset the mfro points to, where the old code accepted any size up to the file length. A trailer that does not check out now leaves the final fragment running to EOF, as a missing trailer already did. A new test covers media_end() against the fixture's real mfra and three damaged trailers: an mfro size off by four, one larger than the file, and no mfro at all. refs #5144 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
26e35beed9
commit
3d858e2ccb
2 files changed
+48
-27
No files matched your search
+12
-27
@@ -31,7 +31,9 @@ extern "C" {
|
||||
#include <libavutil/display.h>
|
||||
}
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <string>
|
||||
#include <unistd.h>
|
||||
|
||||
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<uint32_t>(mfro[0]) << 24)
|
||||
| (static_cast<uint32_t>(mfro[1]) << 16)
|
||||
| (static_cast<uint32_t>(mfro[2]) << 8)
|
||||
| static_cast<uint32_t>(mfro[3]);
|
||||
if (box_size == 16
|
||||
&& mfro[4] == 'm' && mfro[5] == 'f' && mfro[6] == 'r' && mfro[7] == 'o') {
|
||||
uint32_t mfra_size = (static_cast<uint32_t>(mfro[12]) << 24)
|
||||
| (static_cast<uint32_t>(mfro[13]) << 16)
|
||||
| (static_cast<uint32_t>(mfro[14]) << 8)
|
||||
| static_cast<uint32_t>(mfro[15]);
|
||||
if (mfra_size > 0 && static_cast<int64_t>(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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<uint8_t> file = read_file(fixture("sidx-abs.mp4"));
|
||||
const int64_t size = static_cast<int64_t>(file.size());
|
||||
const int64_t mfra = static_cast<int64_t>(offset_of(top_level(file), "mfra"));
|
||||
const std::filesystem::path path = scratch("mfro.mp4");
|
||||
|
||||
auto end_of = [&path](const std::vector<uint8_t> &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<int64_t>(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<zm_mp4::Fragment> fragments;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
|
||||
Reference in new issue
Block a user