use super::*;
use crate::extras::TrackExtra;
fn parameters_with(extradata: usize, icc_profile: usize) -> Parameters {
let mut out = Parameters::new();
unsafe {
let par = out.as_mut_ptr();
if extradata > 0 {
let buffer = av_mallocz(extradata).cast::<u8>();
assert!(!buffer.is_null(), "av_mallocz extradata");
core::ptr::write_bytes(buffer, 0xAB, extradata);
(*par).extradata = buffer;
(*par).extradata_size = extradata as i32;
}
if icc_profile > 0 {
let array = av_mallocz(core::mem::size_of::<AVPacketSideData>()).cast::<AVPacketSideData>();
assert!(!array.is_null(), "av_mallocz side-data array");
let payload = av_mallocz(icc_profile).cast::<u8>();
assert!(!payload.is_null(), "av_mallocz icc profile");
core::ptr::write_bytes(payload, 0xCD, icc_profile);
(*array).data = payload;
(*array).size = icc_profile;
(*array).type_ = ffmpeg_next::ffi::AVPacketSideDataType::AV_PKT_DATA_ICC_PROFILE;
(*par).coded_side_data = array;
(*par).nb_coded_side_data = 1;
}
}
out
}
fn parameters_with_side_data_entries(count: usize) -> Parameters {
let mut out = Parameters::new();
unsafe {
let par = out.as_mut_ptr();
let bytes = count * core::mem::size_of::<AVPacketSideData>();
let array = av_mallocz(bytes).cast::<AVPacketSideData>();
assert!(!array.is_null(), "av_mallocz side-data array");
for index in 0..count {
core::ptr::write_unaligned(
core::ptr::addr_of_mut!((*array.add(index)).type_).cast::<i32>(),
index as i32,
);
}
(*par).coded_side_data = array;
(*par).nb_coded_side_data = count as i32;
}
out
}
fn parameters_with_custom_layout(channels: usize) -> Parameters {
let mut out = Parameters::new();
unsafe {
let par = out.as_mut_ptr();
let bytes = channels * core::mem::size_of::<AVChannelCustom>();
let map = av_mallocz(bytes).cast::<AVChannelCustom>();
assert!(!map.is_null(), "av_mallocz channel map");
for index in 0..channels {
core::ptr::write_unaligned(
core::ptr::addr_of_mut!((*map.add(index)).id).cast::<i32>(),
index as i32,
);
}
core::ptr::write_unaligned(
core::ptr::addr_of_mut!((*par).ch_layout.order).cast::<i32>(),
AVChannelOrder::AV_CHANNEL_ORDER_CUSTOM as i32,
);
(*par).ch_layout.nb_channels = channels as i32;
(*par).ch_layout.u.map = map;
}
out
}
#[test]
fn every_rebuild_allocation_that_fails_is_named() {
crate::fault_subprocess::in_subprocess(
"ticket::tests::every_rebuild_allocation_that_fails_is_named",
|| {
const HUGE: usize = 8 * 1024 * 1024;
let extradata =
CodecTicket::mirror(¶meters_with(HUGE, 0), 2, usize::MAX).expect("an uncapped mirror");
let payload =
CodecTicket::mirror(¶meters_with(0, HUGE), 3, usize::MAX).expect("an uncapped mirror");
let array = CodecTicket::mirror(¶meters_with_side_data_entries(64), 4, usize::MAX)
.expect("an uncapped mirror");
let layout = CodecTicket::mirror(¶meters_with_custom_layout(64), 5, usize::MAX)
.expect("an uncapped mirror");
for (ticket, cap, seat, index) in [
(&extradata, 64 * 1024, "extradata", 2usize),
(&payload, 64 * 1024, "a side-data payload", 3),
(&array, 1024, "the side-data descriptor array", 4),
(&layout, 1024, "the custom channel map", 5),
] {
crate::fault_subprocess::cap_ffmpeg_allocations(cap);
let refused = ticket.rebuild().map(|_| ());
crate::fault_subprocess::uncap_ffmpeg_allocations();
match refused {
Err(DemuxError::ParametersCopy(ref p)) => assert_eq!(
p.stream_index(),
index,
"{seat}: the refusal named the wrong stream",
),
Err(other) => panic!("{seat}: expected ParametersCopy, got {other:?}"),
Ok(()) => panic!("{seat}: an allocation that could not happen was reported as success"),
}
let rebuilt = ticket.rebuild().expect("an uncapped rebuild");
let measured = unsafe { measure_parameters(rebuilt.as_ptr()) }
.and_then(|f| f.total())
.expect("measurable");
assert_eq!(measured, ticket.footprint_bytes(), "{seat}");
}
},
);
}
#[test]
fn the_ticket_is_send_and_sync() {
const fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<CodecTicket>();
assert_send_sync::<ChannelLayoutTicket>();
assert_send_sync::<CustomChannel>();
assert_send_sync::<TrackExtra>();
assert_send_sync::<mediadecode::demuxer::TrackInfo<crate::Ffmpeg>>();
assert_send_sync::<std::sync::Arc<mediadecode::demuxer::TrackInfo<crate::Ffmpeg>>>();
}
#[test]
fn the_omit_policy_leaves_extradata_behind_and_charges_nothing_for_it() {
let parameters = parameters_with(4_096, 128);
let copied = CodecTicket::mirror_with(¶meters, 0, usize::MAX, ExtradataPolicy::Copy)
.expect("mirror with extradata");
let omitted = CodecTicket::mirror_with(¶meters, 0, usize::MAX, ExtradataPolicy::Omit)
.expect("mirror without extradata");
assert_eq!(copied.extradata().len(), 4_096);
assert!(omitted.extradata().is_empty(), "the payload stayed behind");
assert_eq!(omitted.coded_side_data().len(), 1);
const PAD: usize = AV_INPUT_BUFFER_PADDING_SIZE as usize;
assert_eq!(
copied.footprint_bytes() - omitted.footprint_bytes(),
4_096 + PAD,
);
let rebuilt = omitted.rebuild().expect("rebuild");
unsafe {
let par = rebuilt.as_ptr();
assert!((*par).extradata.is_null());
assert_eq!((*par).extradata_size, 0);
}
}
#[test]
fn the_footprint_is_exactly_what_the_rebuild_allocates() {
for (extradata, icc) in [(0, 0), (32, 0), (0, 256), (4_096, 64 * 1024)] {
let parameters = parameters_with(extradata, icc);
let ticket = CodecTicket::mirror(¶meters, 0, usize::MAX).expect("mirror");
let rebuilt = ticket.rebuild().expect("rebuild");
let measured = unsafe { measure_parameters(rebuilt.as_ptr()) }
.and_then(|f| f.total())
.expect("measurable");
assert_eq!(
ticket.footprint_bytes(),
measured,
"extradata {extradata}, icc {icc}",
);
}
}
#[test]
fn an_oversized_mirror_is_refused_before_the_copy() {
let parameters = parameters_with(0, 8 * 1024 * 1024);
let declared = unsafe { measure_parameters(parameters.as_ptr()) }
.and_then(|f| f.total())
.expect("measurable");
match CodecTicket::mirror(¶meters, 3, 64 * 1024) {
Err(DemuxError::ParametersTooLarge(p)) => {
assert_eq!(p.stream_index(), 3);
assert_eq!(p.bytes(), declared);
assert_eq!(p.limit(), 64 * 1024);
}
Err(other) => panic!("expected ParametersTooLarge, got {other:?}"),
Ok(_) => panic!("an 8 MiB ICC profile passed a 64 KiB ceiling"),
}
CodecTicket::mirror(¶meters, 3, declared).expect("at the cap is not over it");
}
#[test]
fn null_backed_parameters_are_refused_at_the_mirror() {
let parameters = Parameters::default();
if unsafe { parameters.as_ptr() }.is_null() {
assert!(matches!(
CodecTicket::mirror(¶meters, 9, usize::MAX),
Err(DemuxError::ParametersMissing(_)),
));
}
}
#[test]
fn the_row_reports_the_tickets_footprint_and_clones_by_refcount() {
let parameters = parameters_with(64, 128);
let ticket = CodecTicket::mirror(¶meters, 7, usize::MAX).expect("mirror");
let footprint = ticket.footprint_bytes();
let extra = TrackExtra::new(7, ticket)
.with_disposition(0x11)
.with_start_time(Some(-3))
.with_frame_count(Some(42));
assert_eq!(extra.parameter_bytes(), footprint);
assert_eq!(extra.ticket().extradata().len(), 64);
let copy = extra.clone();
assert_eq!(copy.stream_index(), 7);
assert_eq!(copy.disposition(), 0x11);
assert_eq!(copy.start_time(), Some(-3));
assert_eq!(copy.frame_count(), Some(42));
assert_eq!(copy.parameter_bytes(), footprint);
assert!(
copy
.ticket()
.extradata_ref()
.ptr_eq(extra.ticket().extradata_ref()),
"cloning a row copied its extradata bytes",
);
}
#[test]
fn the_debug_prints_sizes_rather_than_payloads() {
let parameters = parameters_with(64, 128);
let ticket = CodecTicket::mirror(¶meters, 0, usize::MAX).expect("mirror");
let text = format!("{ticket:?}");
assert!(text.contains("extradata_len: 64"), "{text}");
assert!(text.contains("coded_side_data: 1"), "{text}");
assert!(!text.contains("171"), "the bytes themselves leaked: {text}");
}