use std::io::Write;
use std::time::Duration;
use media_pipeline::capture::{self, CaptureConfig};
use media_pipeline::encoder::mf_h264::{iter_annex_b_nals, MfH264Encoder};
use media_pipeline::encoder::EncodedSample;
use media_pipeline::{CaptureTarget, VideoConfig};
const FRAMES_TO_ENCODE: usize = 60;
fn main() {
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| "info".into()),
)
.init();
let cfg = VideoConfig {
width: 1920,
height: 1080,
fps: 30,
bitrate: 6_000_000,
keyframe_interval: 30, };
let (session, rx) = capture::start(
CaptureConfig {
target: CaptureTarget::Monitor(0),
capture_cursor: true,
},
4,
)
.expect("start capture");
let mut encoder =
MfH264Encoder::new(session.device(), cfg).expect("create encoder");
let mut samples: Vec<EncodedSample> = Vec::new();
let mut encoded = 0usize;
while encoded < FRAMES_TO_ENCODE {
let frame = match rx.recv_timeout(Duration::from_secs(5)) {
Ok(f) => f,
Err(_) => {
eprintln!("timed out waiting for frames");
break;
}
};
let mut out = Vec::new();
if let Err(e) = encoder.encode(&frame.texture, frame.timestamp, &mut out) {
eprintln!("encode error: {e:?}");
break;
}
encoded += 1;
samples.append(&mut out);
}
let mut tail = Vec::new();
let _ = encoder.drain(&mut tail);
samples.append(&mut tail);
drop(session);
let mut file = std::fs::File::create("test.h264").expect("create test.h264");
let mut total_bytes = 0usize;
for s in &samples {
file.write_all(&s.data).expect("write");
total_bytes += s.data.len();
}
file.flush().unwrap();
tracing::info!(
frames_fed = encoded,
output_samples = samples.len(),
bytes = total_bytes,
"wrote test.h264"
);
validate(&samples, &cfg);
}
fn validate(samples: &[EncodedSample], cfg: &VideoConfig) {
let mut n_sps = 0;
let mut n_pps = 0;
let mut n_idr = 0;
let mut n_slice = 0;
let mut sps_bytes: Option<Vec<u8>> = None;
for s in samples {
for nal in iter_annex_b_nals(&s.data) {
if nal.is_empty() {
continue;
}
match nal[0] & 0x1f {
7 => {
n_sps += 1;
if sps_bytes.is_none() {
sps_bytes = Some(nal.to_vec());
}
}
8 => n_pps += 1,
5 => {
n_idr += 1;
n_slice += 1;
}
1 => n_slice += 1,
_ => {}
}
}
}
let keyframes = samples.iter().filter(|s| s.is_keyframe).count();
println!("--- H.264 elementary stream validation ---");
println!(" SPS NALs: {n_sps}");
println!(" PPS NALs: {n_pps}");
println!(" IDR slices: {n_idr}");
println!(" total slices: {n_slice}");
println!(" keyframe samples:{keyframes}");
let mut ok = true;
if n_sps == 0 {
println!(" FAIL: no SPS found");
ok = false;
}
if n_pps == 0 {
println!(" FAIL: no PPS found");
ok = false;
}
if n_idr == 0 {
println!(" FAIL: no IDR keyframe found");
ok = false;
}
if let Some(sps) = sps_bytes {
match parse_sps_resolution(&sps) {
Some((w, h)) => {
println!(" SPS resolution: {w}x{h}");
if w != cfg.width || h != cfg.height {
println!(
" WARN: SPS resolution {w}x{h} != requested {}x{}",
cfg.width, cfg.height
);
}
}
None => println!(" WARN: could not parse SPS resolution"),
}
}
if ok && n_slice > 0 {
println!(" RESULT: VALID H.264 elementary stream");
} else {
println!(" RESULT: INVALID");
std::process::exit(1);
}
}
fn parse_sps_resolution(sps: &[u8]) -> Option<(u32, u32)> {
let rbsp = strip_emulation(&sps[1..]);
let mut r = BitReader::new(&rbsp);
let profile_idc = r.u(8)?;
let _constraints = r.u(8)?;
let _level_idc = r.u(8)?;
let _sps_id = r.ue()?;
if matches!(
profile_idc,
100 | 110 | 122 | 244 | 44 | 83 | 86 | 118 | 128 | 138 | 139 | 134 | 135
) {
let chroma_format_idc = r.ue()?;
if chroma_format_idc == 3 {
r.u(1)?; }
r.ue()?; r.ue()?; r.u(1)?; let seq_scaling_matrix = r.u(1)?;
if seq_scaling_matrix == 1 {
return None;
}
}
r.ue()?; let pic_order_cnt_type = r.ue()?;
if pic_order_cnt_type == 0 {
r.ue()?; } else if pic_order_cnt_type == 1 {
r.u(1)?;
r.se()?;
r.se()?;
let n = r.ue()?;
for _ in 0..n {
r.se()?;
}
}
r.ue()?; r.u(1)?;
let pic_width_in_mbs_minus1 = r.ue()?;
let pic_height_in_map_units_minus1 = r.ue()?;
let frame_mbs_only_flag = r.u(1)?;
if frame_mbs_only_flag == 0 {
r.u(1)?; }
r.u(1)?;
let mut crop_left = 0;
let mut crop_right = 0;
let mut crop_top = 0;
let mut crop_bottom = 0;
let frame_cropping_flag = r.u(1)?;
if frame_cropping_flag == 1 {
crop_left = r.ue()?;
crop_right = r.ue()?;
crop_top = r.ue()?;
crop_bottom = r.ue()?;
}
let width = (pic_width_in_mbs_minus1 + 1) * 16;
let height = (pic_height_in_map_units_minus1 + 1) * 16 * (2 - frame_mbs_only_flag);
let crop_unit_x = 2;
let crop_unit_y = 2 * (2 - frame_mbs_only_flag);
let w = width.saturating_sub((crop_left + crop_right) * crop_unit_x);
let h = height.saturating_sub((crop_top + crop_bottom) * crop_unit_y);
Some((w, h))
}
fn strip_emulation(data: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(data.len());
let mut zeros = 0;
for &b in data {
if zeros >= 2 && b == 0x03 {
zeros = 0;
continue; }
if b == 0 {
zeros += 1;
} else {
zeros = 0;
}
out.push(b);
}
out
}
struct BitReader<'a> {
data: &'a [u8],
bit_pos: usize,
}
impl<'a> BitReader<'a> {
fn new(data: &'a [u8]) -> Self {
Self { data, bit_pos: 0 }
}
fn u(&mut self, n: u32) -> Option<u32> {
let mut val = 0u32;
for _ in 0..n {
let byte = self.data.get(self.bit_pos / 8)?;
let bit = (byte >> (7 - (self.bit_pos % 8))) & 1;
val = (val << 1) | bit as u32;
self.bit_pos += 1;
}
Some(val)
}
fn ue(&mut self) -> Option<u32> {
let mut leading = 0;
while self.u(1)? == 0 {
leading += 1;
if leading > 31 {
return None;
}
}
if leading == 0 {
return Some(0);
}
let rest = self.u(leading)?;
Some((1 << leading) - 1 + rest)
}
fn se(&mut self) -> Option<i32> {
let k = self.ue()? as i64;
let val = if k % 2 == 1 {
(k + 1) / 2
} else {
-(k / 2)
};
Some(val as i32)
}
}