#![cfg(feature = "cenc")]
use std::alloc::{GlobalAlloc, Layout, System};
use std::cell::Cell;
use std::path::PathBuf;
use broadcast_common::{Encrypt, Unpackage};
use transmux::{
CencEncryptor, CencScheme, CodecConfig, ConstantIvSenc, EncryptConfig, IvGen, Media,
RtpPacketiser, Sample, SubsamplePolicy, TsDemux,
};
struct CountingAlloc;
thread_local! {
static ALLOC_COUNT: Cell<usize> = const { Cell::new(0) };
static ALLOC_BYTES: Cell<usize> = const { Cell::new(0) };
static DEALLOC_COUNT: Cell<usize> = const { Cell::new(0) };
}
unsafe impl GlobalAlloc for CountingAlloc {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
ALLOC_COUNT.with(|c| c.set(c.get() + 1));
ALLOC_BYTES.with(|c| c.set(c.get() + layout.size()));
unsafe { System.alloc(layout) }
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
DEALLOC_COUNT.with(|c| c.set(c.get() + 1));
unsafe { System.dealloc(ptr, layout) }
}
}
#[global_allocator]
static GLOBAL: CountingAlloc = CountingAlloc;
fn reset_counters() {
ALLOC_COUNT.with(|c| c.set(0));
ALLOC_BYTES.with(|c| c.set(0));
DEALLOC_COUNT.with(|c| c.set(0));
}
fn snapshot_counters() -> (usize, usize, usize) {
(
ALLOC_COUNT.with(Cell::get),
ALLOC_BYTES.with(Cell::get),
DEALLOC_COUNT.with(Cell::get),
)
}
fn fixture_path() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../fixtures/ts/h264/main.ts")
}
fn clear_video_media() -> Option<Media> {
let path = fixture_path();
if !path.exists() {
eprintln!("alloc_measurement: SKIPPED — {path:?} not found.");
return None;
}
let bytes = std::fs::read(&path).unwrap_or_else(|e| panic!("read {path:?}: {e}"));
let mut demux = TsDemux::new();
let media = demux.unpackage(bytes.as_slice()).expect("demux main.ts");
Some(
media
.select_tracks_by(|t| matches!(t.spec.config, CodecConfig::Avc { .. }))
.expect("AVC video track present"),
)
}
const KID: [u8; 16] = [
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00,
];
const KEY: [u8; 16] = [
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10,
];
const CENC_MEASURED_ALLOCS: usize = 68;
const CENC_MEASURED_ALLOC_BYTES: usize = 3_392;
const CENC_MEASURED_DEALLOCS: usize = 51;
#[test]
fn cenc_encrypt_allocation_count_over_real_fixture() {
let Some(mut media) = clear_video_media() else {
return;
};
let sample_count = media.tracks[0].samples.len();
assert!(sample_count > 1, "fixture must carry more than one sample");
let total_payload: usize = media.tracks[0].samples.iter().map(|s| s.data.len()).sum();
let cfg = EncryptConfig {
scheme: CencScheme::Cenc,
kid: KID,
iv: IvGen::Counter,
pattern: None,
subsample: SubsamplePolicy::Video,
constant_iv_senc: ConstantIvSenc::default(),
};
reset_counters();
CencEncryptor::new(KEY)
.encrypt(&mut media, &cfg)
.expect("encrypt");
let (allocs, alloc_bytes, deallocs) = snapshot_counters();
eprintln!(
"MEASUREMENT cenc_encrypt: samples={sample_count} allocs={allocs} \
alloc_bytes={alloc_bytes} deallocs={deallocs} total_payload={total_payload} \
allocs_per_sample={:.2}",
allocs as f64 / sample_count as f64
);
assert!(
alloc_bytes < total_payload / 4,
"encrypt allocated {alloc_bytes} bytes over a {total_payload}-byte payload \
(>25%) — looks like a per-sample or per-subsample payload copy regression"
);
assert_eq!(
alloc_bytes, CENC_MEASURED_ALLOC_BYTES,
"encrypt allocated {alloc_bytes} bytes — the pinned measurement is \
{CENC_MEASURED_ALLOC_BYTES}; re-measure and update the const (with a fresh date) if \
this is a legitimate change, otherwise this is a regression (a no-op encrypt would \
allocate 0, not the pinned amount)"
);
assert_eq!(
allocs, CENC_MEASURED_ALLOCS,
"encrypt allocated {allocs} times over {sample_count} samples — the pinned measurement \
is {CENC_MEASURED_ALLOCS}; likely a per-subsample or per-NAL copy regression (or, if \
lower, encrypt did less work than it should have)"
);
assert_eq!(
deallocs, CENC_MEASURED_DEALLOCS,
"dealloc count {deallocs} drifted from the pinned measurement of \
{CENC_MEASURED_DEALLOCS} — re-measure and update the const if this is a legitimate \
change"
);
}
const CBCS_MEASURED_ALLOCS: usize = 68;
const CBCS_MEASURED_ALLOC_BYTES: usize = 3_392;
const CBCS_MEASURED_DEALLOCS: usize = 51;
#[test]
fn cbcs_encrypt_allocation_count_over_real_fixture() {
let Some(mut media) = clear_video_media() else {
return;
};
let sample_count = media.tracks[0].samples.len();
let total_payload: usize = media.tracks[0].samples.iter().map(|s| s.data.len()).sum();
let cfg = EncryptConfig {
scheme: CencScheme::Cbcs,
kid: KID,
iv: IvGen::Counter,
pattern: Some((1, 9)),
subsample: SubsamplePolicy::Video,
constant_iv_senc: ConstantIvSenc::default(),
};
reset_counters();
CencEncryptor::new(KEY)
.encrypt(&mut media, &cfg)
.expect("encrypt");
let (allocs, alloc_bytes, deallocs) = snapshot_counters();
eprintln!(
"MEASUREMENT cbcs_encrypt: samples={sample_count} allocs={allocs} \
alloc_bytes={alloc_bytes} deallocs={deallocs} total_payload={total_payload} \
allocs_per_sample={:.2}",
allocs as f64 / sample_count as f64
);
assert!(
alloc_bytes < total_payload / 4,
"cbcs encrypt allocated {alloc_bytes} bytes over a {total_payload}-byte payload \
(>25%) — looks like a per-sample or per-subsample payload copy regression"
);
assert_eq!(
alloc_bytes, CBCS_MEASURED_ALLOC_BYTES,
"cbcs encrypt allocated {alloc_bytes} bytes — the pinned measurement is \
{CBCS_MEASURED_ALLOC_BYTES}; re-measure and update the const (with a fresh date) if \
this is a legitimate change, otherwise this is a regression (a no-op encrypt would \
allocate 0, not the pinned amount)"
);
assert_eq!(
allocs, CBCS_MEASURED_ALLOCS,
"cbcs encrypt allocated {allocs} times over {sample_count} samples — the pinned \
measurement is {CBCS_MEASURED_ALLOCS}; likely a per-subsample or per-NAL copy \
regression (or, if lower, encrypt did less work than it should have)"
);
assert_eq!(
deallocs, CBCS_MEASURED_DEALLOCS,
"cbcs dealloc count {deallocs} drifted from the pinned measurement of \
{CBCS_MEASURED_DEALLOCS} — re-measure and update the const if this is a legitimate \
change"
);
}
#[test]
fn bytes_clone_shares_buffer_enabling_zero_copy_fan_out() {
let Some(media) = clear_video_media() else {
return;
};
let sample = &media.tracks[0].samples[0];
assert!(!sample.data.is_empty(), "sample must carry payload bytes");
let original_ptr = sample.data.as_ptr();
const N_CONSUMERS: usize = 6;
let mut fanned_out: Vec<bytes::Bytes> = Vec::with_capacity(N_CONSUMERS);
reset_counters();
for _ in 0..N_CONSUMERS {
fanned_out.push(sample.data.clone());
}
let (allocs, alloc_bytes, _deallocs) = snapshot_counters();
eprintln!(
"MEASUREMENT fan_out: consumers={N_CONSUMERS} allocs={allocs} alloc_bytes={alloc_bytes}"
);
for (i, clone) in fanned_out.iter().enumerate() {
assert_eq!(
clone.as_ptr(),
original_ptr,
"consumer {i}'s clone must share the original buffer, not copy it"
);
assert_eq!(clone.len(), sample.data.len());
}
assert_eq!(
allocs, 0,
"fanning a sample out to {N_CONSUMERS} consumers must allocate zero payload bytes \
(got {allocs} allocations, {alloc_bytes} bytes)"
);
}
const RTP_MEASURED_ALLOCS: usize = 70;
const RTP_MEASURED_ALLOC_BYTES: usize = 9_632;
#[test]
fn rtp_packetiser_allocates_zero_payload_bytes() {
let Some(media) = clear_video_media() else {
return;
};
let sample_count = media.tracks[0].samples.len();
assert!(sample_count > 1, "fixture must carry more than one sample");
let total_payload: usize = media.tracks[0].samples.iter().map(|s| s.data.len()).sum();
assert!(
total_payload > 10_000,
"fixture payload must be non-trivial"
);
let pkt = RtpPacketiser {
stap_a_parameter_sets: false,
..RtpPacketiser::default()
};
reset_counters();
let result = pkt.packetise_video(&media.tracks[0], 96);
let (allocs, alloc_bytes, _deallocs) = snapshot_counters();
let packets = result.expect("packetise video");
let packet_count = packets.len();
eprintln!(
"MEASUREMENT rtp_packetise: packets={packet_count} allocs={allocs} \
alloc_bytes={alloc_bytes} total_payload={total_payload}"
);
assert_eq!(
allocs, RTP_MEASURED_ALLOCS,
"RTP packetiser allocation count {allocs} drifted from the pinned \
measurement of {RTP_MEASURED_ALLOCS} — re-measure and update the const \
if this is a legitimate change; a copy-based packetiser would allocate \
many more (one per NAL + one per packet body)."
);
assert_eq!(
alloc_bytes,
RTP_MEASURED_ALLOC_BYTES,
"RTP packetiser allocated {alloc_bytes} bytes — the pinned measurement \
is {RTP_MEASURED_ALLOC_BYTES}. A copy-based packetiser would add \
{total_payload} bytes (the sum of all NAL payloads) on top, moving \
this to ~{}. Re-measure and update the const if this is a legitimate \
change; otherwise this is a payload-copy regression (issue #777).",
RTP_MEASURED_ALLOC_BYTES + total_payload
);
}
#[test]
fn rtp_packetiser_per_packet_cost_is_independent_of_payload_size() {
let Some(media) = clear_video_media() else {
return;
};
use transmux::media::Track;
let orig_track = &media.tracks[0];
let spec = orig_track.spec.clone();
let scaled_samples: Vec<Sample> = orig_track
.samples
.iter()
.map(|s| {
let mut big = Vec::with_capacity(s.data.len() * 10);
for _ in 0..10 {
big.extend_from_slice(&s.data);
}
Sample::new(big, s.dts, s.pts, s.duration, s.flags.is_sync)
})
.collect();
let scaled_track = Track::new(spec, scaled_samples);
let total_payload_10x: usize = scaled_track.samples.iter().map(|s| s.data.len()).sum();
eprintln!("PROOF 10× payload: {total_payload_10x} bytes");
let pkt = RtpPacketiser {
stap_a_parameter_sets: false,
..RtpPacketiser::default()
};
reset_counters();
let result = pkt.packetise_video(&scaled_track, 96);
let (allocs, alloc_bytes, _deallocs) = snapshot_counters();
let packets = result.expect("packetise 10x video");
let packet_count = packets.len();
eprintln!(
"MEASUREMENT rtp_packetise_10x: packets={packet_count} allocs={allocs} \
alloc_bytes={alloc_bytes} total_payload={total_payload_10x}"
);
let per_packet_10x = alloc_bytes as f64 / packet_count as f64;
let per_packet_baseline = RTP_MEASURED_ALLOC_BYTES as f64 / 39.0;
eprintln!("10× per-packet: {per_packet_10x:.1} bytes (baseline: {per_packet_baseline:.1})");
assert!(
per_packet_10x < per_packet_baseline * 2.0,
"RTP packetiser 10× payload: per-packet allocation {per_packet_10x:.1} bytes \
vs baseline {per_packet_baseline:.1} bytes. A copy-based packetiser would \
add payload bytes to every packet, pushing this well past 2×."
);
}