diff options
| author | s-ol <s+removethis@s-ol.nu> | 2026-05-04 21:45:39 +0000 |
|---|---|---|
| committer | s-ol <s+removethis@s-ol.nu> | 2026-05-14 15:00:13 +0000 |
| commit | 0bdd9ae4e181283ac1d21b52cce7b28c565d936f (patch) | |
| tree | 141632d39cda63d9a605b182041006ada7031ec2 /src/bin | |
| parent | switch to WESL shaders (diff) | |
| download | wgsl-view-0bdd9ae4e181283ac1d21b52cce7b28c565d936f.tar.gz wgsl-view-0bdd9ae4e181283ac1d21b52cce7b28c565d936f.zip | |
tsv-video-*: share code
Diffstat (limited to 'src/bin')
| -rw-r--r-- | src/bin/tsv_video_buffer.rs | 150 | ||||
| -rw-r--r-- | src/bin/tsv_video_stream.rs | 89 |
2 files changed, 59 insertions, 180 deletions
diff --git a/src/bin/tsv_video_buffer.rs b/src/bin/tsv_video_buffer.rs index 9c8ee99..3a244a8 100644 --- a/src/bin/tsv_video_buffer.rs +++ b/src/bin/tsv_video_buffer.rs @@ -2,77 +2,7 @@ use std::io::Read; use std::process::{Command, Stdio}; use ash::vk; -use wgsl_view::{gpu, hap}; - -/// Options that ffmpeg supports but ffprobe does not. -const FFMPEG_ONLY_OPTIONS: &[&str] = &["-stream_loop", "-t"]; - -fn filter_probe_args(ff_args: &[String]) -> Vec<String> { - let mut out = Vec::new(); - let mut skip_next = false; - for arg in ff_args { - if skip_next { - skip_next = false; - continue; - } - if FFMPEG_ONLY_OPTIONS.contains(&arg.as_str()) { - skip_next = true; - continue; - } - out.push(arg.clone()); - } - out -} - -struct VideoInfo { - width: u32, - height: u32, - num_frames: u32, - codec_tag: String, -} - -fn probe_video(ff_args: &[String], max_frames: Option<u32>) -> VideoInfo { - let probe_args = filter_probe_args(ff_args); - let output = Command::new("ffprobe") - .args(["-v", "error", "-select_streams", "v:0"]) - .args([ - "-show_entries", - "stream=width,height,nb_frames,codec_tag_string", - ]) - .args(["-of", "csv=p=0"]) - .args(&probe_args) - .output() - .expect("failed to run ffprobe"); - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - panic!("ffprobe failed: {stderr}"); - } - - let stdout = String::from_utf8_lossy(&output.stdout); - let line = stdout.trim(); - let parts: Vec<&str> = line.split(',').collect(); - if parts.len() < 4 { - panic!("unexpected ffprobe output: {line}"); - } - - let num_frames = parts[3].parse().unwrap_or_else(|_| { - max_frames.unwrap_or_else(|| { - panic!( - "could not determine frame count (nb_frames={}).\n\ - Use --frames N or use tsv-video-stream instead.", - parts[3] - ) - }) - }); - - VideoInfo { - codec_tag: parts[0].to_string(), - width: parts[1].parse().expect("parse width"), - height: parts[2].parse().expect("parse height"), - num_frames, - } -} +use wgsl_view::{ffmpeg, gpu, hap}; fn is_hap_codec(codec_tag: &str) -> Option<hap::HapFormat> { match codec_tag { @@ -119,28 +49,29 @@ fn main() { } } - let mut info = probe_video(ff_args, max_frames); - if let Some(max) = max_frames { - info.num_frames = info.num_frames.min(max); - } + let mut info = ffmpeg::probe_video(ff_args, max_frames); + let num_frames = match (info.num_frames, max_frames) { + (Some(a), Some(b)) => u32::min(a, b), + (Some(a), None) => a, + (None, Some(b)) => b, + _ => panic!("failed to detect duration, please specify --frames"), + }; + info.num_frames = Some(num_frames); let hap_format = is_hap_codec(&info.codec_tag); - let (texture_format, tsv_img_format) = match hap_format { - Some(hap::HapFormat::Bc1) => (wgpu::TextureFormat::Bc1RgbaUnorm, gpu::ImgFormat::BC1_RGBA), - Some(hap::HapFormat::Bc3) => (wgpu::TextureFormat::Bc3RgbaUnorm, gpu::ImgFormat::BC3_RGBA), - None => ( - wgpu::TextureFormat::Rgba8UnormSrgb, - gpu::ImgFormat::R8G8B8A8, - ), + let format = match hap_format { + Some(hap::HapFormat::Bc1) => gpu::ImgFormat::BC1_RGBA, + Some(hap::HapFormat::Bc3) => gpu::ImgFormat::BC3_RGBA, + None => gpu::ImgFormat::R8G8B8A8, }; log::info!( - "{}x{}, {} frames, codec_tag={}, format={:?}, tsv image: {}", + "{}x{}, {} frames, codec_tag={}, format={:?}, tsv image: {:?}", info.width, info.height, - info.num_frames, + num_frames, info.codec_tag, - texture_format, + format, name ); @@ -156,10 +87,10 @@ fn main() { wgpu::Features::empty() }; - let (tsv_img_type, wgpu_dimension) = if use_array { - (gpu::ImgType::D2, wgpu::TextureDimension::D2) + let img_type = if use_array { + gpu::ImgType::D2 } else { - (gpu::ImgType::D3, wgpu::TextureDimension::D3) + gpu::ImgType::D3 }; let instance = gpu::create_instance(); @@ -172,9 +103,9 @@ fn main() { &name, info.width, info.height, - info.num_frames, - tsv_img_format, - tsv_img_type, + num_frames, + format, + img_type, true, ) .expect("init tsv image"); @@ -186,12 +117,12 @@ fn main() { size: wgpu::Extent3d { width: info.width, height: info.height, - depth_or_array_layers: info.num_frames, + depth_or_array_layers: num_frames, }, mip_level_count: 1, sample_count: 1, - dimension: wgpu_dimension, - format: texture_format, + dimension: gpu::img_type_to_wgpu(img_type, num_frames), + format: gpu::img_format_to_wgpu(format), usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::COPY_SRC, view_formats: &[], }); @@ -219,10 +150,7 @@ fn main() { std::process::exit(1); } - log::info!( - "all {} frames uploaded and shared, waiting...", - info.num_frames - ); + log::info!("all {} frames uploaded and shared, waiting...", num_frames); // Keep the process alive so the shared image remains available loop { @@ -235,7 +163,7 @@ fn upload_hap_frames( queue: &wgpu::Queue, texture: &wgpu::Texture, ff_args: &[String], - info: &VideoInfo, + info: &ffmpeg::VideoInfo, hap_fmt: hap::HapFormat, ) { // For HAP, we need raw packet data (not decoded pixels). @@ -254,6 +182,7 @@ fn upload_hap_frames( let compressed_frame_size = hap_fmt.compressed_size(info.width, info.height); let mut decode_buf = vec![0u8; compressed_frame_size + 1024 * 1024]; // extra margin let mut frame_index: u32 = 0; + let num_frames = info.num_frames.unwrap(); let block_bytes = hap_fmt.block_bytes(); let blocks_x = info.width.div_ceil(4); @@ -293,18 +222,18 @@ fn upload_hap_frames( ); frame_index += 1; - if frame_index.is_multiple_of(10) || frame_index == info.num_frames { - log::info!("uploaded frame {}/{}", frame_index, info.num_frames); + if frame_index.is_multiple_of(10) || frame_index == num_frames { + log::info!("uploaded frame {}/{}", frame_index, num_frames); } - if frame_index >= info.num_frames { + if frame_index >= num_frames { break; } } - if frame_index < info.num_frames { + if frame_index < num_frames { log::warn!( "expected {} frames but only decoded {frame_index}", - info.num_frames + num_frames ); } } @@ -314,9 +243,10 @@ fn upload_raw_frames( queue: &wgpu::Queue, texture: &wgpu::Texture, ff_args: &[String], - info: &VideoInfo, + info: &ffmpeg::VideoInfo, ) { let frame_size = (info.width * info.height * 4) as usize; + let num_frames = info.num_frames.unwrap(); let mut ffmpeg = Command::new("ffmpeg") .args(["-v", "quiet"]) @@ -358,10 +288,10 @@ fn upload_raw_frames( ); frame_index += 1; - if frame_index.is_multiple_of(10) || frame_index == info.num_frames { - log::info!("uploaded frame {}/{}", frame_index, info.num_frames); + if frame_index.is_multiple_of(10) || frame_index == num_frames { + log::info!("uploaded frame {}/{}", frame_index, num_frames); } - if frame_index >= info.num_frames { + if frame_index >= num_frames { break; } } @@ -371,10 +301,10 @@ fn upload_raw_frames( drop(reader); ffmpeg.wait().ok(); - if frame_index < info.num_frames { + if frame_index < num_frames { log::warn!( "expected {} frames but only decoded {frame_index}", - info.num_frames + num_frames ); } } diff --git a/src/bin/tsv_video_stream.rs b/src/bin/tsv_video_stream.rs index 3e12f6d..c2db35c 100644 --- a/src/bin/tsv_video_stream.rs +++ b/src/bin/tsv_video_stream.rs @@ -4,63 +4,7 @@ use std::thread; use std::time::{Duration, Instant}; use ash::vk; -use wgsl_view::gpu; - -/// Options that ffmpeg supports but ffprobe does not. -const FFMPEG_ONLY_OPTIONS: &[&str] = &["-stream_loop"]; - -fn filter_probe_args(ff_args: &[String]) -> Vec<String> { - let mut out = Vec::new(); - let mut skip_next = false; - for arg in ff_args { - if skip_next { - skip_next = false; - continue; - } - if FFMPEG_ONLY_OPTIONS.contains(&arg.as_str()) { - skip_next = true; - continue; - } - out.push(arg.clone()); - } - out -} - -fn probe_video(ff_args: &[String]) -> (u32, u32, f64) { - let probe_args = filter_probe_args(ff_args); - let output = Command::new("ffprobe") - .args(["-v", "error", "-select_streams", "v:0"]) - .args(["-show_entries", "stream=width,height,r_frame_rate"]) - .args(["-of", "csv=p=0"]) - .args(&probe_args) - .output() - .expect("failed to run ffprobe"); - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - panic!("ffprobe failed: {stderr}"); - } - - let stdout = String::from_utf8_lossy(&output.stdout); - let line = stdout.trim(); - let parts: Vec<&str> = line.split(',').collect(); - if parts.len() < 3 { - panic!("unexpected ffprobe output: {line}"); - } - - let width: u32 = parts[0].parse().expect("parse width"); - let height: u32 = parts[1].parse().expect("parse height"); - - let fps: f64 = if let Some((num, den)) = parts[2].split_once('/') { - let n: f64 = num.parse().expect("parse fps numerator"); - let d: f64 = den.parse().expect("parse fps denominator"); - n / d - } else { - parts[2].parse().expect("parse fps") - }; - - (width, height, fps) -} +use wgsl_view::{ffmpeg, gpu}; fn main() { env_logger::init(); @@ -89,8 +33,13 @@ fn main() { } } - let (width, height, fps) = probe_video(ff_args); - log::info!("{width}x{height} @ {fps:.2}fps, tsv image: {name}"); + let info = ffmpeg::probe_video(ff_args, None); + log::info!( + "{}x{} @ {:.2}fps, tsv image: {name}", + info.width, + info.height, + info.fps + ); let instance = gpu::create_instance(); let adapter = gpu::create_adapter(&instance, None); @@ -100,10 +49,10 @@ fn main() { client .init_image( &name, - width, - height, + info.width, + info.height, 1, - gpu::TSV_FORMAT, + gpu::ImgFormat::R8G8B8A8, gpu::ImgType::D2, true, ) @@ -114,20 +63,20 @@ fn main() { let texture = device.create_texture(&wgpu::TextureDescriptor { label: Some("video_frame"), size: wgpu::Extent3d { - width, - height, + width: info.width, + height: info.height, depth_or_array_layers: 1, }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Rgba8UnormSrgb, + format: wgpu::TextureFormat::Rgba8Unorm, usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::COPY_SRC, view_formats: &[], }); - let frame_size = (width * height * 4) as usize; - let frame_time = Duration::from_secs_f64(1.0 / fps); + let frame_size = (info.width * info.height * 4) as usize; + let frame_time = Duration::from_secs_f64(1.0 / info.fps); let mut ffmpeg = Command::new("ffmpeg") .args(["-v", "quiet"]) @@ -159,12 +108,12 @@ fn main() { &frame_buf, wgpu::TexelCopyBufferLayout { offset: 0, - bytes_per_row: Some(width * 4), + bytes_per_row: Some(info.width * 4), rows_per_image: None, }, wgpu::Extent3d { - width, - height, + width: info.width, + height: info.height, depth_or_array_layers: 1, }, ); |
