From 34f16e0567721b3741ee6e44c07dae8539491715 Mon Sep 17 00:00:00 2001 From: Brad Hards Date: Mon, 26 May 2025 18:00:47 +0930 Subject: [PATCH] mux/mp4: migrate to mp4-atom to check muxing This avoids implementing parsing logic for tests. Part-of: --- Cargo.lock | 50 +++++++++++- mux/mp4/Cargo.toml | 1 + mux/mp4/tests/tests.rs | 167 ++++++++++++++++++++--------------------- 3 files changed, 131 insertions(+), 87 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9297ae570..b7c98d72a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1721,6 +1721,26 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "diff" version = "0.1.13" @@ -2958,6 +2978,7 @@ dependencies = [ "gstreamer-pbutils", "gstreamer-tag", "gstreamer-video", + "mp4-atom", "num-integer", "tempfile", "url", @@ -5292,6 +5313,19 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fafa6961cabd9c63bcd77a45d7e3b7f3b552b70417831fb0f56db717e72407e" +[[package]] +name = "mp4-atom" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a59fd1da0f5d0864ba19bd9d45cefc3dc19835f6f3bbf9016a86e4b4480042" +dependencies = [ + "derive_more", + "num", + "paste", + "thiserror 1.0.69", + "tracing", +] + [[package]] name = "muldiv" version = "1.0.1" @@ -5441,6 +5475,20 @@ dependencies = [ "winapi", ] +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -6123,7 +6171,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" dependencies = [ "bytes", - "heck 0.4.1", + "heck 0.5.0", "itertools 0.12.1", "log", "multimap", diff --git a/mux/mp4/Cargo.toml b/mux/mp4/Cargo.toml index 44840718a..483c94670 100644 --- a/mux/mp4/Cargo.toml +++ b/mux/mp4/Cargo.toml @@ -25,6 +25,7 @@ crate-type = ["cdylib", "rlib"] path = "src/lib.rs" [dev-dependencies] +mp4-atom = "0.8.1" tempfile = "3" url = "2" diff --git a/mux/mp4/tests/tests.rs b/mux/mp4/tests/tests.rs index f182d1d3b..99e0cada7 100644 --- a/mux/mp4/tests/tests.rs +++ b/mux/mp4/tests/tests.rs @@ -7,13 +7,10 @@ // SPDX-License-Identifier: MPL-2.0 // -use std::{ - fs::{self}, - io::{Cursor, Read}, - path::Path, -}; +use std::{fs::File, path::Path}; use gst_pbutils::prelude::*; +use mp4_atom::{Atom as _, ReadAtom as _, ReadFrom as _}; use tempfile::tempdir; fn init() { @@ -67,40 +64,6 @@ impl Pipeline { } } -struct FileTypeBox { - major_brand: [u8; 4], - minor_version: u32, - compatible_brands: Vec<[u8; 4]>, -} - -impl FileTypeBox { - fn read(mut reader: Cursor<&[u8]>) -> std::io::Result { - let mut box_size_buf = [0u8; 4]; - reader.read_exact(&mut box_size_buf)?; - let box_size = u32::from_be_bytes(box_size_buf); - let mut four_cc = [0u8; 4]; - reader.read_exact(&mut four_cc)?; - assert_eq!(four_cc, *b"ftyp"); - let mut major_brand = [0u8; 4]; - reader.read_exact(&mut major_brand)?; - let mut minor_version_buf = [0u8; 4]; - reader.read_exact(&mut minor_version_buf)?; - let minor_version = u32::from_be_bytes(minor_version_buf); - let num_brands = (box_size - 16) / 4; - let mut compatible_brands = Vec::with_capacity(num_brands.try_into().unwrap()); - for _ in 0..num_brands { - let mut compatible_brand = [0u8; 4]; - reader.read_exact(&mut compatible_brand)?; - compatible_brands.push(compatible_brand); - } - Ok(FileTypeBox { - major_brand, - minor_version, - compatible_brands, - }) - } -} - fn test_basic_with(video_enc: &str, audio_enc: &str, cb: impl FnOnce(&Path)) { let Ok(pipeline) = gst::parse::launch(&format!( "videotestsrc num-buffers=99 ! {video_enc} ! mux. \ @@ -302,26 +265,20 @@ fn test_encode_uncompressed(video_format: &str, width: u32, height: u32) { } fn test_expected_uncompressed_output(location: &Path) { - let check_data: Vec = fs::read(location).unwrap(); - let cursor = Cursor::new(check_data.as_ref()); - test_default_mpeg_file_type_box(cursor); -} + let required_top_level_boxes: Vec = vec![ + b"ftyp".into(), + b"free".into(), + b"mdat".into(), + b"moov".into(), + ]; -fn test_expected_file_type_box( - expected_major_brand: &[u8; 4], - expected_minor_version: u32, - expected_compatible_brands: Vec<[u8; 4]>, - cursor: Cursor<&[u8]>, -) { - let ftyp = FileTypeBox::read(cursor).unwrap(); - - assert_eq!(ftyp.major_brand, *expected_major_brand); - assert_eq!(ftyp.minor_version, expected_minor_version); - let mut sorted_compatible_brands = ftyp.compatible_brands.clone(); - sorted_compatible_brands.sort(); - let mut sorted_expected_compatible_brands = expected_compatible_brands.clone(); - sorted_expected_compatible_brands.sort(); - assert_eq!(sorted_compatible_brands, sorted_expected_compatible_brands); + check_file_boxes( + location, + required_top_level_boxes, + b"iso4".into(), + 0, + vec![b"isom".into(), b"mp41".into(), b"mp42".into()], + ); } #[test] @@ -582,21 +539,20 @@ fn test_encode_uncompressed_image_sequence(video_format: &str, width: u32, heigh test_expected_image_sequence_output(location); } -fn test_expected_image_sequence_output(filename: &Path) { - let check_data: Vec = fs::read(filename).unwrap(); - let cursor = Cursor::new(check_data.as_ref()); - test_expected_image_sequence_file_type_box_content(cursor); -} +fn test_expected_image_sequence_output(location: &Path) { + let required_top_level_boxes: Vec = vec![ + b"ftyp".into(), + b"free".into(), + b"mdat".into(), + b"moov".into(), + ]; -fn test_expected_image_sequence_file_type_box_content(cursor: Cursor<&[u8]>) { - let expected_major_brand = b"msf1"; - let expected_minor_version = 0; - let expected_compatible_brands: Vec<[u8; 4]> = vec![*b"iso8", *b"msf1", *b"unif"]; - test_expected_file_type_box( - expected_major_brand, - expected_minor_version, - expected_compatible_brands, - cursor, + check_file_boxes( + location, + required_top_level_boxes, + b"msf1".into(), + 0, + vec![b"iso8".into(), b"msf1".into(), b"unif".into()], ); } @@ -650,20 +606,59 @@ fn test_encode_audio_trak() { test_audio_only_output(location); } -fn test_audio_only_output(filename: &Path) { - let check_data: Vec = fs::read(filename).unwrap(); - let cursor = Cursor::new(check_data.as_ref()); - test_default_mpeg_file_type_box(cursor); -} +fn test_audio_only_output(location: &Path) { + let required_top_level_boxes: Vec = vec![ + b"ftyp".into(), + b"free".into(), + b"mdat".into(), + b"moov".into(), + ]; -fn test_default_mpeg_file_type_box(cursor: Cursor<&[u8]>) { - let expected_major_brand = b"iso4"; - let expected_minor_version = 0; - let expected_compatible_brands: Vec<[u8; 4]> = vec![*b"isom", *b"mp41", *b"mp42"]; - test_expected_file_type_box( - expected_major_brand, - expected_minor_version, - expected_compatible_brands, - cursor, + check_file_boxes( + location, + required_top_level_boxes, + b"iso4".into(), + 0, + vec![b"isom".into(), b"mp41".into(), b"mp42".into()], + ); +} + +fn check_file_boxes( + location: &Path, + mut required_top_level_boxes: Vec, + expected_major_brand: mp4_atom::FourCC, + expected_minor_version: u32, + expected_compatible_brands: Vec, +) { + let mut input = File::open(location).unwrap(); + while let Ok(header) = mp4_atom::Header::read_from(&mut input) { + assert!(required_top_level_boxes.contains(&header.kind)); + let pos = required_top_level_boxes + .iter() + .position(|&fourcc| fourcc == header.kind) + .unwrap_or_else(|| panic!("expected to find a matching fourcc {:?}", header.kind)); + required_top_level_boxes.remove(pos); + match header.kind { + mp4_atom::Ftyp::KIND => { + let ftyp = mp4_atom::Ftyp::read_atom(&header, &mut input).unwrap(); + assert_eq!(ftyp.major_brand, expected_major_brand); + assert_eq!(ftyp.minor_version, expected_minor_version); + assert_eq!( + ftyp.compatible_brands.len(), + expected_compatible_brands.len() + ); + for fourcc in &expected_compatible_brands { + assert!(ftyp.compatible_brands.contains(fourcc)); + } + } + _ => { + let _ = mp4_atom::Any::read_atom(&header, &mut input).unwrap(); + } + } + } + assert!( + required_top_level_boxes.is_empty(), + "expected all top level boxes to be found, but these were missed: {:?}", + required_top_level_boxes ); }