From db51136ff0bdf3b497b1fcb600567cc3c0759bb7 Mon Sep 17 00:00:00 2001 From: Ric Li Date: Fri, 26 Jan 2024 11:25:59 +0800 Subject: [PATCH] rust: add pipeline video (#714) Signed-off-by: Ric Li --- .github/workflows/ecosystem.yml | 5 +- include/st20_api.h | 1 + rust/Cargo.toml | 6 +- rust/README.md | 9 +- rust/examples/video-rx.rs | 41 +- rust/examples/video-tx.rs | 41 +- rust/imtl-sys/Cargo.toml | 4 - rust/imtl-sys/build.rs | 24 -- rust/imtl-sys/src/convert.rs | 47 --- rust/imtl-sys/src/lib.rs | 6 - rust/imtl-sys/src/pipeline.rs | 1 - rust/imtl-sys/wrapper.h | 4 +- rust/imtl-sys/wrapper_convert.h | 1 - rust/imtl-sys/wrapper_pipeline.h | 1 - rust/src/imtl/convert/mod.rs | 30 -- rust/src/imtl/lib.rs | 6 - rust/src/imtl/mtl.rs | 13 + rust/src/imtl/pipeline/mod.rs | 2 - rust/src/imtl/session.rs | 13 +- rust/src/imtl/video.rs | 680 ++++++++++++++++++++++--------- 20 files changed, 574 insertions(+), 361 deletions(-) delete mode 100644 rust/imtl-sys/src/convert.rs delete mode 100644 rust/imtl-sys/src/pipeline.rs delete mode 100644 rust/imtl-sys/wrapper_convert.h delete mode 100644 rust/imtl-sys/wrapper_pipeline.h delete mode 100644 rust/src/imtl/convert/mod.rs delete mode 100644 rust/src/imtl/pipeline/mod.rs diff --git a/.github/workflows/ecosystem.yml b/.github/workflows/ecosystem.yml index 27f329e76..81323adf1 100644 --- a/.github/workflows/ecosystem.yml +++ b/.github/workflows/ecosystem.yml @@ -86,8 +86,7 @@ jobs: - name: Build Rust binding run: | cd rust/ - cargo build --verbose - cargo clippy + cargo clippy --all-targets + cargo build --all-targets --verbose cd imtl-sys - cargo build --verbose cargo test --verbose \ No newline at end of file diff --git a/include/st20_api.h b/include/st20_api.h index 7c72223f7..1f208a960 100644 --- a/include/st20_api.h +++ b/include/st20_api.h @@ -274,6 +274,7 @@ enum st20_fmt { ST20_FMT_YUV_420_8BIT, /**< 8-bit YUV 4:2:0 */ ST20_FMT_YUV_420_10BIT, /**< 10-bit YUV 4:2:0 */ ST20_FMT_YUV_420_12BIT, /**< 12-bit YUV 4:2:0 */ + ST20_FMT_YUV_420_16BIT, /**< 16-bit YUV 4:2:0 */ ST20_FMT_RGB_8BIT, /**< 8-bit RGB */ ST20_FMT_RGB_10BIT, /**< 10-bit RGB */ ST20_FMT_RGB_12BIT, /**< 12-bit RGB */ diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 537f27100..1feeee2a7 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "imtl-rs" -version = "0.1.2" +version = "0.1.3" edition = "2021" [lib] @@ -22,7 +22,3 @@ ctrlc = "3.4.2" memmap2 = "0.9.3" clap = { version = "4.4.13", features = ["derive"] } sdl2 = "0.36.0" - -[features] -convert = ["imtl-sys/convert"] -pipeline = ["imtl-sys/pipeline"] diff --git a/rust/README.md b/rust/README.md index 813451832..5655cf488 100644 --- a/rust/README.md +++ b/rust/README.md @@ -8,7 +8,7 @@ Add this to your `Cargo.toml`: ```toml [dependencies] -imtl = "0.1.2" +imtl = "0.1.3" ``` ## Example @@ -28,3 +28,10 @@ cargo run --example video-rx -- --display --width 1920 --height 1080 --fps 30 -- # Check more options with --help cargo run --example video-rx -- --help ``` + +USe pipeline API for internal color format conversion by adding '--input_format' for Tx and '--output_format' for Rx. + +```bash +cargo run --example video-tx -- --yuv 422p10le.yuv --width 1920 --height 1080 --fps 30 --format yuv_422_10bit --input-format YUV422PLANAR10LE +cargo run --example video-rx -- --display --width 1920 --height 1080 --fps 30 --format yuv_422_10bit --output-format UYVY +``` diff --git a/rust/examples/video-rx.rs b/rust/examples/video-rx.rs index 6ab5bb6f8..4653790a6 100644 --- a/rust/examples/video-rx.rs +++ b/rust/examples/video-rx.rs @@ -5,14 +5,13 @@ use sdl2::render::{Canvas, Texture}; use sdl2::video::Window; use std::io::Write; use std::net::Ipv4Addr; -use std::str::FromStr; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use imtl::mtl::{Flags, LogLevel, MtlBuilder}; use imtl::netdev::*; use imtl::session::RtpSessionBuilder; -use imtl::video::{Fps, TransportFmt, VideoRxBuilder}; +use imtl::video::{Fps, FrameFmt, TransportFmt, VideoRxBuilder}; /// Simple program to use IMTL to receive raw YUV frame and save the latest one to file #[derive(Parser, Debug)] @@ -43,24 +42,28 @@ struct Args { height: u32, /// Framerate - #[arg(long, default_value_t = String::from("60"))] - fps: String, + #[arg(long, default_value_t = Fps::P60)] + fps: Fps, + + /// Pipeline output format + #[arg(long)] + output_format: Option, /// Transport format - #[arg(long, default_value_t = String::from("yuv_422_10bit"))] - format: String, + #[arg(long, default_value_t = TransportFmt::Yuv422_10bit)] + format: TransportFmt, /// Name of the YUV file #[arg(long)] yuv: Option, - /// Enable display window, only for 'yuv_422_8bit' format + /// Enable display window, only support for 'UYVY' output/transport format #[arg(long, default_value_t = false)] display: bool, /// Log level - #[arg(short, long, default_value_t = String::from("info"))] - log_level: String, + #[arg(short, long, default_value_t = LogLevel::Info)] + log_level: LogLevel, } fn main() -> Result<()> { @@ -98,16 +101,13 @@ fn main() -> Result<()> { let mtl = MtlBuilder::default() .net_devs(net_devs) .flags(flags) - .log_level(LogLevel::from_str(&args.log_level)?) + .log_level(args.log_level) .build() .unwrap() .init() .context("Failed to init mtl")?; - let net_dev0 = mtl.net_devs()[0].clone(); - let session = RtpSessionBuilder::default() - .net_dev(net_dev0) .ip(args.ip) .port(args.port) .payload_type(112u8) @@ -116,11 +116,13 @@ fn main() -> Result<()> { .context("Failed to add rtp session")?; let mut video_rx = VideoRxBuilder::default() + .netdev_id(0) .rtp_session(session) .width(args.width) .height(args.height) - .fps(Fps::from_str(&args.fps)?) - .t_fmt(TransportFmt::from_str(&args.format)?) + .fps(args.fps) + .output_fmt(args.output_format) + .t_fmt(args.format) .build() .unwrap() .create(&mtl) @@ -149,7 +151,12 @@ fn main() -> Result<()> { )?); } - let frame = vec![0u8; video_rx.frame_size()]; + let frame_size = if let Some(output_format) = args.output_format { + output_format.frame_size(args.width, args.height)? + } else { + video_rx.frame_size() + }; + let frame = vec![0u8; frame_size]; while running.load(Ordering::SeqCst) { match video_rx.fill_new_frame(&frame) { @@ -157,7 +164,7 @@ fn main() -> Result<()> { if let (Some(ref mut texture), Some(ref mut canvas)) = (&mut texture, &mut canvas) { texture.update(None, &frame, args.width as usize * 2)?; canvas.clear(); - canvas.copy(&texture, None, None).unwrap(); + canvas.copy(texture, None, None).unwrap(); canvas.present(); } } diff --git a/rust/examples/video-tx.rs b/rust/examples/video-tx.rs index a47dbf7a3..d4fee99b9 100644 --- a/rust/examples/video-tx.rs +++ b/rust/examples/video-tx.rs @@ -4,14 +4,13 @@ use sdl2::pixels::PixelFormatEnum; use sdl2::render::{Canvas, Texture}; use sdl2::video::Window; use std::net::Ipv4Addr; -use std::str::FromStr; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use imtl::mtl::{Flags, LogLevel, MtlBuilder}; use imtl::netdev::*; use imtl::session::RtpSessionBuilder; -use imtl::video::{Fps, TransportFmt, VideoTxBuilder}; +use imtl::video::{Fps, FrameFmt, TransportFmt, VideoTxBuilder}; /// Simple program to use IMTL to send raw YUV frame from file #[derive(Parser, Debug)] @@ -42,12 +41,16 @@ struct Args { height: u32, /// Framerate - #[arg(long, default_value_t = String::from("60"))] - fps: String, + #[arg(long, default_value_t = Fps::P60)] + fps: Fps, + + /// Pipeline input format + #[arg(long)] + input_format: Option, /// Transport format - #[arg(long, default_value_t = String::from("yuv_422_10bit"))] - format: String, + #[arg(long, default_value_t = TransportFmt::Yuv422_10bit)] + format: TransportFmt, /// Name of the YUV file #[arg(long)] @@ -58,8 +61,8 @@ struct Args { display: bool, /// Log level - #[arg(short, long, default_value_t = String::from("info"))] - log_level: String, + #[arg(short, long, default_value_t = LogLevel::Info)] + log_level: LogLevel, } fn main() -> Result<()> { @@ -96,16 +99,13 @@ fn main() -> Result<()> { let mtl = MtlBuilder::default() .net_devs(net_devs) .flags(flags) - .log_level(LogLevel::from_str(&args.log_level)?) + .log_level(args.log_level) .build() .unwrap() .init() .context("Failed to init mtl")?; - let net_dev0 = mtl.net_devs()[0].clone(); - let session = RtpSessionBuilder::default() - .net_dev(net_dev0) .ip(args.ip) .port(args.port) .payload_type(112u8) @@ -114,11 +114,13 @@ fn main() -> Result<()> { .context("Failed to add rtp session")?; let mut video_tx = VideoTxBuilder::default() + .netdev_id(0) .rtp_session(session) .width(args.width) .height(args.height) - .fps(Fps::from_str(&args.fps)?) - .t_fmt(TransportFmt::from_str(&args.format)?) + .fps(args.fps) + .input_fmt(args.input_format) + .t_fmt(args.format) .build() .unwrap() .create(&mtl) @@ -148,7 +150,12 @@ fn main() -> Result<()> { )?); } - let frames = yuv_file.chunks_exact(video_tx.frame_size()); + let frame_size = if let Some(input_format) = args.input_format { + input_format.frame_size(args.width, args.height)? + } else { + video_tx.frame_size() + }; + let frames = yuv_file.chunks_exact(frame_size); if frames.len() == 0 { bail!("No frames in file"); } @@ -159,9 +166,9 @@ fn main() -> Result<()> { match video_tx.fill_next_frame(frame) { Ok(_) => { if let (Some(ref mut texture), Some(ref mut canvas)) = (&mut texture, &mut canvas) { - texture.update(None, &frame, args.width as usize * 2)?; + texture.update(None, frame, args.width as usize * 2)?; canvas.clear(); - canvas.copy(&texture, None, None).unwrap(); + canvas.copy(texture, None, None).unwrap(); canvas.present(); } frame = frames.next().unwrap(); diff --git a/rust/imtl-sys/Cargo.toml b/rust/imtl-sys/Cargo.toml index 8a756d072..06727add5 100644 --- a/rust/imtl-sys/Cargo.toml +++ b/rust/imtl-sys/Cargo.toml @@ -15,7 +15,3 @@ rand = "0.8.5" [build-dependencies] bindgen = "0.69.1" pkg-config = "0.3.27" - -[features] -convert = [] -pipeline = [] diff --git a/rust/imtl-sys/build.rs b/rust/imtl-sys/build.rs index 006272779..2232cc90b 100644 --- a/rust/imtl-sys/build.rs +++ b/rust/imtl-sys/build.rs @@ -17,28 +17,4 @@ fn main() { bindings .write_to_file(out_path.join("mtl_bindings.rs")) .expect("Couldn't write bindings!"); - - if cfg!(feature = "convert") { - let bindings = bindgen::Builder::default() - .header("wrapper_convert.h") - .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) - .generate() - .expect("Unable to generate bindings"); - - bindings - .write_to_file(out_path.join("mtl_convert_bindings.rs")) - .expect("Couldn't write bindings!"); - } - - if cfg!(feature = "pipeline") { - let bindings = bindgen::Builder::default() - .header("wrapper_pipeline.h") - .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) - .generate() - .expect("Unable to generate bindings"); - - bindings - .write_to_file(out_path.join("mtl_pipeline_bindings.rs")) - .expect("Couldn't write bindings!"); - } } diff --git a/rust/imtl-sys/src/convert.rs b/rust/imtl-sys/src/convert.rs deleted file mode 100644 index 4148f51d2..000000000 --- a/rust/imtl-sys/src/convert.rs +++ /dev/null @@ -1,47 +0,0 @@ -include!(concat!(env!("OUT_DIR"), "/mtl_convert_bindings.rs")); - -#[cfg(test)] -mod tests { - use super::*; - - fn cvt_rfc4175_422be10_to_v210(w: u32, h: u32, cvt_level: mtl_simd_level) { - let mut ret; - let src_fb_size = w * h * 5 / 2; - let dst_fb_size = w * h * 8 / 3; - - let mut src_buf: Vec = vec![0; src_fb_size as usize]; - let mut dst_buf: Vec = vec![0; dst_fb_size as usize]; - let mut rev_buf: Vec = vec![0; src_fb_size as usize]; - - use rand::{thread_rng, Rng}; - thread_rng().fill(&mut src_buf[..]); - unsafe { - ret = st20_rfc4175_422be10_to_v210_simd( - src_buf.as_mut_ptr() as *mut st20_rfc4175_422_10_pg2_be, - dst_buf.as_mut_ptr(), - w, - h, - cvt_level, - ); - assert_eq!(0, ret); - - ret = st20_v210_to_rfc4175_422be10_simd( - dst_buf.as_mut_ptr(), - rev_buf.as_mut_ptr() as *mut st20_rfc4175_422_10_pg2_be, - w, - h, - cvt_level, - ); - assert_eq!(0, ret); - } - assert_eq!(src_buf, rev_buf); - println!("src 100:{}, rev 100: {}", src_buf[100], rev_buf[100]); - } - - #[test] - fn rfc_422be_to_v210() { - cvt_rfc4175_422be10_to_v210(1920, 1080, mtl_simd_level_MTL_SIMD_LEVEL_NONE); - cvt_rfc4175_422be10_to_v210(1920, 1080, mtl_simd_level_MTL_SIMD_LEVEL_AVX512); - cvt_rfc4175_422be10_to_v210(1920, 1080, mtl_simd_level_MTL_SIMD_LEVEL_AVX512_VBMI2); - } -} diff --git a/rust/imtl-sys/src/lib.rs b/rust/imtl-sys/src/lib.rs index 2463e0903..273a8a0ef 100644 --- a/rust/imtl-sys/src/lib.rs +++ b/rust/imtl-sys/src/lib.rs @@ -4,12 +4,6 @@ include!(concat!(env!("OUT_DIR"), "/mtl_bindings.rs")); -#[cfg(feature = "convert")] -pub mod convert; - -#[cfg(feature = "pipeline")] -pub mod pipeline; - #[cfg(test)] mod tests { use super::*; diff --git a/rust/imtl-sys/src/pipeline.rs b/rust/imtl-sys/src/pipeline.rs deleted file mode 100644 index baec4f474..000000000 --- a/rust/imtl-sys/src/pipeline.rs +++ /dev/null @@ -1 +0,0 @@ -include!(concat!(env!("OUT_DIR"), "/mtl_pipeline_bindings.rs")); \ No newline at end of file diff --git a/rust/imtl-sys/wrapper.h b/rust/imtl-sys/wrapper.h index 872c0137e..faebe6fc9 100644 --- a/rust/imtl-sys/wrapper.h +++ b/rust/imtl-sys/wrapper.h @@ -1,3 +1,5 @@ #include #include -#include \ No newline at end of file +#include +#include +#include \ No newline at end of file diff --git a/rust/imtl-sys/wrapper_convert.h b/rust/imtl-sys/wrapper_convert.h deleted file mode 100644 index e5378bdcb..000000000 --- a/rust/imtl-sys/wrapper_convert.h +++ /dev/null @@ -1 +0,0 @@ -#include \ No newline at end of file diff --git a/rust/imtl-sys/wrapper_pipeline.h b/rust/imtl-sys/wrapper_pipeline.h deleted file mode 100644 index e5fd0eb87..000000000 --- a/rust/imtl-sys/wrapper_pipeline.h +++ /dev/null @@ -1 +0,0 @@ -#include \ No newline at end of file diff --git a/rust/src/imtl/convert/mod.rs b/rust/src/imtl/convert/mod.rs deleted file mode 100644 index a4fc8c5e9..000000000 --- a/rust/src/imtl/convert/mod.rs +++ /dev/null @@ -1,30 +0,0 @@ -//! -//! A binding for `st_convert_api` -//! -//! -//! Note that you need to build with the -//! feature `convert` for this module to be enabled, -//! like so: -//! -//! ```bash -//! $ cargo build --features "convert" -//! ``` -//! -//! If you want to use this with from inside your own -//! crate, you will need to add this in your Cargo.toml -//! -//! ```toml -//! [dependencies.imtl] -//! version = ... -//! default-features = false -//! features = ["convert"] -//! ``` - -use format; -use sys; -use sys::convert; - -pub trait ToRFC { - fn to_rfc4175() -> () {} - fn from_rfc4175() -> () {} -} diff --git a/rust/src/imtl/lib.rs b/rust/src/imtl/lib.rs index 92d9cf57a..0f8ebcb04 100644 --- a/rust/src/imtl/lib.rs +++ b/rust/src/imtl/lib.rs @@ -8,9 +8,3 @@ pub mod netdev; pub mod session; pub mod version; pub mod video; - -// modules -#[cfg(feature = "convert")] -pub mod convert; -#[cfg(feature = "pipeline")] -pub mod pipeline; diff --git a/rust/src/imtl/mtl.rs b/rust/src/imtl/mtl.rs index 26521725b..dc15471e7 100644 --- a/rust/src/imtl/mtl.rs +++ b/rust/src/imtl/mtl.rs @@ -25,6 +25,7 @@ use anyhow::{bail, Result}; use bitflags::bitflags; use derive_builder::Builder; +use std::fmt::Display; use std::mem::MaybeUninit; use std::str::FromStr; @@ -43,6 +44,18 @@ pub enum LogLevel { Error, } +impl Display for LogLevel { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + LogLevel::Debug => write!(f, "debug"), + LogLevel::Info => write!(f, "info"), + LogLevel::Notice => write!(f, "notice"), + LogLevel::Warning => write!(f, "warning"), + LogLevel::Error => write!(f, "error"), + } + } +} + impl FromStr for LogLevel { type Err = anyhow::Error; diff --git a/rust/src/imtl/pipeline/mod.rs b/rust/src/imtl/pipeline/mod.rs deleted file mode 100644 index d7a15800f..000000000 --- a/rust/src/imtl/pipeline/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -use sys; -use sys::pipeline; diff --git a/rust/src/imtl/session.rs b/rust/src/imtl/session.rs index 9570823d3..ad76cea4c 100644 --- a/rust/src/imtl/session.rs +++ b/rust/src/imtl/session.rs @@ -7,14 +7,11 @@ */ use derive_builder::Builder; - -use crate::netdev::NetDev; -use std::{net::Ipv4Addr, rc::Rc}; +use std::net::Ipv4Addr; #[derive(Clone, Builder, Debug)] #[builder(setter(into))] pub struct RtpSession { - net_dev: Rc, ip: Ipv4Addr, port: u16, payload_type: u8, @@ -28,11 +25,6 @@ pub struct RtpSession { } impl RtpSession { - /// Retrieves a reference to the associated `NetDev` object. - pub fn net_dev(&self) -> &NetDev { - &self.net_dev - } - /// Retrieves the IP address used by this RTP session. pub fn ip(&self) -> Ipv4Addr { self.ip @@ -74,8 +66,7 @@ impl RtpSession { impl Default for RtpSession { fn default() -> Self { Self { - ip: Ipv4Addr::new(127, 0, 0, 1), - net_dev: Rc::new(NetDev::default()), + ip: Ipv4Addr::new(239, 0, 0, 1), port: 0, payload_type: 0, enable_rtcp: false, diff --git a/rust/src/imtl/video.rs b/rust/src/imtl/video.rs index 9988d6227..9eade9acc 100644 --- a/rust/src/imtl/video.rs +++ b/rust/src/imtl/video.rs @@ -15,30 +15,77 @@ use crossbeam_utils::sync::Parker; use derive_builder::Builder; use std::{ffi::c_void, fmt::Display, mem::MaybeUninit, str::FromStr}; +#[derive(Clone, Debug)] +enum VideoHandle { + Tx(sys::st20_tx_handle), + Rx(sys::st20_rx_handle), + PipelineTx(sys::st20p_tx_handle), + PipelineRx(sys::st20p_rx_handle), + /* TODO + CompressedTx(sys::st22_tx_handle), + CompressedRx(sys::st22_rx_handle), + PipelineCompressedTx(sys::st22p_tx_handle), + PipelineCompressedRx(sys::st22p_rx_handle), + */ +} + +impl Drop for VideoHandle { + fn drop(&mut self) { + match self { + VideoHandle::Tx(h) => unsafe { + sys::st20_tx_free(*h); + }, + VideoHandle::Rx(h) => unsafe { + sys::st20_rx_free(*h); + }, + VideoHandle::PipelineTx(h) => unsafe { + sys::st20p_tx_free(*h); + }, + VideoHandle::PipelineRx(h) => unsafe { + sys::st20p_rx_free(*h); + }, + /* TODO + VideoHandle::CompressedTx(h) => unsafe { + sys::st22_tx_free(*h); + }, + VideoHandle::CompressedRx(h) => unsafe { + sys::st22_rx_free(*h); + }, + VideoHandle::PipelineCompressedTx(h) => unsafe { + sys::st22p_tx_free(*h); + }, + VideoHandle::PipelineCompressedRx(h) => unsafe { + sys::st22p_rx_free(*h); + }, + */ + } + } +} + /// Different packing formats for uncompressed video. #[derive(Copy, Clone, Debug, Default)] pub enum Packing { #[default] - Bpm = 0, // Block Packing Mode - Gpm, // General Packing Mode - GpmSl, // General Packing Mode with Single Line + Bpm = sys::st20_packing_ST20_PACKING_BPM as _, // Block Packing Mode + Gpm = sys::st20_packing_ST20_PACKING_GPM as _, // General Packing Mode + GpmSl = sys::st20_packing_ST20_PACKING_GPM_SL as _, // General Packing Mode with Single Line } /// Different frame rates (frames per second) supported. #[derive(Copy, Clone, Debug, Default)] pub enum Fps { #[default] - P59_94 = 0, - P50, - P29_97, - P25, - P119_88, - P120, - P100, - P60, - P30, - P24, - P23_98, + P59_94 = sys::st_fps_ST_FPS_P59_94 as _, + P50 = sys::st_fps_ST_FPS_P50 as _, + P29_97 = sys::st_fps_ST_FPS_P29_97 as _, + P25 = sys::st_fps_ST_FPS_P25 as _, + P119_88 = sys::st_fps_ST_FPS_P119_88 as _, + P120 = sys::st_fps_ST_FPS_P120 as _, + P100 = sys::st_fps_ST_FPS_P100 as _, + P60 = sys::st_fps_ST_FPS_P60 as _, + P30 = sys::st_fps_ST_FPS_P30 as _, + P24 = sys::st_fps_ST_FPS_P24 as _, + P23_98 = sys::st_fps_ST_FPS_P23_98 as _, } impl Display for Fps { @@ -107,33 +154,35 @@ impl Fps { #[derive(Copy, Clone, Debug, Default)] pub enum TransportFmt { #[default] - Yuv422_10bit = 0, - Yuv422_8bit, - Yuv422_12bit, - Yuv422_16bit, - Yuv420_8bit, - Yuv420_10bit, - Yuv420_12bit, - Rgb8bit, - Rgb10bit, - Rgb12bit, - Rgb16bit, - Yuv444_8bit, - Yuv444_10bit, - Yuv444_12bit, - Yuv444_16bit, + Yuv422_8bit = sys::st20_fmt_ST20_FMT_YUV_422_8BIT as _, + Yuv422_10bit = sys::st20_fmt_ST20_FMT_YUV_422_10BIT as _, + Yuv422_12bit = sys::st20_fmt_ST20_FMT_YUV_422_12BIT as _, + Yuv422_16bit = sys::st20_fmt_ST20_FMT_YUV_422_16BIT as _, + Yuv420_8bit = sys::st20_fmt_ST20_FMT_YUV_420_8BIT as _, + Yuv420_10bit = sys::st20_fmt_ST20_FMT_YUV_420_10BIT as _, + Yuv420_12bit = sys::st20_fmt_ST20_FMT_YUV_420_12BIT as _, + Yuv420_16bit = sys::st20_fmt_ST20_FMT_YUV_420_16BIT as _, + Rgb8bit = sys::st20_fmt_ST20_FMT_RGB_8BIT as _, + Rgb10bit = sys::st20_fmt_ST20_FMT_RGB_10BIT as _, + Rgb12bit = sys::st20_fmt_ST20_FMT_RGB_12BIT as _, + Rgb16bit = sys::st20_fmt_ST20_FMT_RGB_16BIT as _, + Yuv444_8bit = sys::st20_fmt_ST20_FMT_YUV_444_8BIT as _, + Yuv444_10bit = sys::st20_fmt_ST20_FMT_YUV_444_10BIT as _, + Yuv444_12bit = sys::st20_fmt_ST20_FMT_YUV_444_12BIT as _, + Yuv444_16bit = sys::st20_fmt_ST20_FMT_YUV_444_16BIT as _, } impl Display for TransportFmt { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - TransportFmt::Yuv422_10bit => write!(f, "YUV 4:2:2 10bit"), TransportFmt::Yuv422_8bit => write!(f, "YUV 4:2:2 8bit"), + TransportFmt::Yuv422_10bit => write!(f, "YUV 4:2:2 10bit"), TransportFmt::Yuv422_12bit => write!(f, "YUV 4:2:2 12bit"), TransportFmt::Yuv422_16bit => write!(f, "YUV 4:2:2 16bit"), TransportFmt::Yuv420_8bit => write!(f, "YUV 4:2:0 8bit"), TransportFmt::Yuv420_10bit => write!(f, "YUV 4:2:0 10bit"), TransportFmt::Yuv420_12bit => write!(f, "YUV 4:2:0 12bit"), + TransportFmt::Yuv420_16bit => write!(f, "YUV 4:2:0 16bit"), TransportFmt::Rgb8bit => write!(f, "RGB 8bit"), TransportFmt::Rgb10bit => write!(f, "RGB 10bit"), TransportFmt::Rgb12bit => write!(f, "RGB 12bit"), @@ -151,13 +200,14 @@ impl FromStr for TransportFmt { fn from_str(s: &str) -> Result { match s { - "yuv_422_10bit" => Ok(TransportFmt::Yuv422_10bit), "yuv_422_8bit" => Ok(TransportFmt::Yuv422_8bit), + "yuv_422_10bit" => Ok(TransportFmt::Yuv422_10bit), "yuv_422_12bit" => Ok(TransportFmt::Yuv422_12bit), "yuv_422_16bit" => Ok(TransportFmt::Yuv422_16bit), "yuv_420_8bit" => Ok(TransportFmt::Yuv420_8bit), "yuv_420_10bit" => Ok(TransportFmt::Yuv420_10bit), "yuv_420_12bit" => Ok(TransportFmt::Yuv420_12bit), + "yuv_420_16bit" => Ok(TransportFmt::Yuv420_16bit), "rgb_8bit" => Ok(TransportFmt::Rgb8bit), "rgb_10bit" => Ok(TransportFmt::Rgb10bit), "rgb_12bit" => Ok(TransportFmt::Rgb12bit), @@ -180,14 +230,108 @@ enum FrameStatus { Ready, } +#[derive(Copy, Clone, Debug, Default)] +pub enum FrameFmt { + Yuv422Planar10Le = sys::st_frame_fmt_ST_FRAME_FMT_YUV422PLANAR10LE as _, + V210 = sys::st_frame_fmt_ST_FRAME_FMT_V210 as _, + Y210 = sys::st_frame_fmt_ST_FRAME_FMT_Y210 as _, + Yuv422Planar8 = sys::st_frame_fmt_ST_FRAME_FMT_YUV422PLANAR8 as _, + Uyvy = sys::st_frame_fmt_ST_FRAME_FMT_UYVY as _, + #[default] + Yuv422Rfc4175Pg2Be10 = sys::st_frame_fmt_ST_FRAME_FMT_YUV422RFC4175PG2BE10 as _, + Yuv422Planar12Le = sys::st_frame_fmt_ST_FRAME_FMT_YUV422PLANAR12LE as _, + Yuv422Rfc4175Pg2Be12 = sys::st_frame_fmt_ST_FRAME_FMT_YUV422RFC4175PG2BE12 as _, + Yuv444Planar10Le = sys::st_frame_fmt_ST_FRAME_FMT_YUV444PLANAR10LE as _, + Yuv444Rfc4175Pg4Be10 = sys::st_frame_fmt_ST_FRAME_FMT_YUV444RFC4175PG4BE10 as _, + Yuv444Planar12Le = sys::st_frame_fmt_ST_FRAME_FMT_YUV444PLANAR12LE as _, + Yuv444Rfc4175Pg2Be12 = sys::st_frame_fmt_ST_FRAME_FMT_YUV444RFC4175PG2BE12 as _, + Yuv420Custom8 = sys::st_frame_fmt_ST_FRAME_FMT_YUV420CUSTOM8 as _, + Yuv422Custom8 = sys::st_frame_fmt_ST_FRAME_FMT_YUV422CUSTOM8 as _, + Argb = sys::st_frame_fmt_ST_FRAME_FMT_ARGB as _, + Bgra = sys::st_frame_fmt_ST_FRAME_FMT_BGRA as _, + Rgb8 = sys::st_frame_fmt_ST_FRAME_FMT_RGB8 as _, + Gbrplanar10Le = sys::st_frame_fmt_ST_FRAME_FMT_GBRPLANAR10LE as _, + RgbRfc4175Pg4Be10 = sys::st_frame_fmt_ST_FRAME_FMT_RGBRFC4175PG4BE10 as _, + Gbrplanar12Le = sys::st_frame_fmt_ST_FRAME_FMT_GBRPLANAR12LE as _, + RgbRfc4175Pg2Be12 = sys::st_frame_fmt_ST_FRAME_FMT_RGBRFC4175PG2BE12 as _, + JpegxsCodestream = sys::st_frame_fmt_ST_FRAME_FMT_JPEGXS_CODESTREAM as _, + H264CbrCodestream = sys::st_frame_fmt_ST_FRAME_FMT_H264_CBR_CODESTREAM as _, +} + +impl FromStr for FrameFmt { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + use FrameFmt::*; + match s { + "YUV422PLANAR10LE" => Ok(Yuv422Planar10Le), + "V210" => Ok(V210), + "Y210" => Ok(Y210), + "YUV422PLANAR8" => Ok(Yuv422Planar8), + "UYVY" => Ok(Uyvy), + "YUV422RFC4175PG2BE10" => Ok(Yuv422Rfc4175Pg2Be10), + "YUV422PLANAR12LE" => Ok(Yuv422Planar12Le), + "YUV422RFC4175PG2BE12" => Ok(Yuv422Rfc4175Pg2Be12), + "YUV444PLANAR10LE" => Ok(Yuv444Planar10Le), + "YUV444RFC4175PG4BE10" => Ok(Yuv444Rfc4175Pg4Be10), + "YUV444PLANAR12LE" => Ok(Yuv444Planar12Le), + "YUV444RFC4175PG2BE12" => Ok(Yuv444Rfc4175Pg2Be12), + "YUV420CUSTOM8" => Ok(Yuv420Custom8), + "YUV422CUSTOM8" => Ok(Yuv422Custom8), + "ARGB" => Ok(Argb), + "BGRA" => Ok(Bgra), + "RGB8" => Ok(Rgb8), + "GBRPLANAR10LE" => Ok(Gbrplanar10Le), + "RGBRFC4175PG4BE10" => Ok(RgbRfc4175Pg4Be10), + "GBRPLANAR12LE" => Ok(Gbrplanar12Le), + "RGBRFC4175PG2BE12" => Ok(RgbRfc4175Pg2Be12), + "JPEGXS_CODESTREAM" => Ok(JpegxsCodestream), + "H264_CBR_CODESTREAM" => Ok(H264CbrCodestream), + _ => bail!(format!("Unknown format: {}", s)), + } + } +} + +impl Display for FrameFmt { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +impl FrameFmt { + pub fn frame_size(&self, width: u32, height: u32) -> Result { + use FrameFmt::*; + match self { + Yuv422Planar10Le | Y210 | Yuv422Planar12Le => Ok(width as usize * height as usize * 4), + V210 => Ok(width as usize * height as usize * 16 / 6), + Yuv422Planar8 | Uyvy | Yuv422Custom8 => Ok(width as usize * height as usize * 2), + Yuv422Rfc4175Pg2Be10 => Ok(width as usize * height as usize * 5 / 2), + Yuv422Rfc4175Pg2Be12 => Ok(width as usize * height as usize * 3), + Yuv444Planar10Le | Yuv444Planar12Le | Gbrplanar10Le | Gbrplanar12Le => { + Ok(width as usize * height as usize * 6) + } + Yuv444Rfc4175Pg4Be10 | RgbRfc4175Pg4Be10 => { + Ok(width as usize * height as usize * 15 / 4) + } + Yuv444Rfc4175Pg2Be12 | RgbRfc4175Pg2Be12 => { + Ok(width as usize * height as usize * 9 / 2) + } + Yuv420Custom8 => Ok(width as usize * height as usize * 6 / 4), + Argb | Bgra => Ok(width as usize * height as usize * 4), + Rgb8 => Ok(width as usize * height as usize * 3), + _ => bail!("Unknown frame size"), + } + } +} + /// VideoTx structure for handling transmission of uncompressed video. #[derive(Default, Builder, Debug)] #[builder(setter(into))] pub struct VideoTx { #[builder(default)] - rtp_session: RtpSession, + netdev_id: u8, #[builder(default)] - handle: Option, + rtp_session: RtpSession, #[builder(default = "1920")] width: u32, #[builder(default = "1080")] @@ -203,6 +347,12 @@ pub struct VideoTx { #[builder(default = "false")] interlaced: bool, + // For pipeline API + #[builder(default)] + input_fmt: Option, + + #[builder(setter(skip))] + handle: Option, #[builder(setter(skip))] consumer_idx: u8, #[builder(setter(skip))] @@ -253,6 +403,15 @@ unsafe extern "C" fn video_tx_notify_frame_done( } } +unsafe extern "C" fn st20p_tx_notify_frame_done(p_void: *mut c_void, _: *mut sys::st_frame) -> i32 { + unsafe { + let s: &mut VideoTx = &mut *(p_void as *mut VideoTx); + let u = s.parker.unparker().clone(); + u.unpark(); + 0 + } +} + impl VideoTx { /// Initializes a new VideoTx session with Media Transport Library (MTL) handle. pub fn create(mut self, mtl: &Mtl) -> Result { @@ -260,61 +419,116 @@ impl VideoTx { bail!("VideoTx Session is already created"); } - for _ in 0..self.fb_cnt { - self.frame_status.push(FrameStatus::Free); - } self.parker = Parker::new(); - self.consumer_idx = 0; - self.producer_idx = 0; - - let mut ops: MaybeUninit = MaybeUninit::uninit(); - - // Fill the ops - unsafe { - std::ptr::write_bytes(ops.as_mut_ptr(), 0, 1); - let ops = &mut *ops.as_mut_ptr(); - ops.num_port = 1; - ops.name = self.rtp_session.name().unwrap().as_ptr() as *const i8; - - let net_dev = self.rtp_session.net_dev(); - let port_bytes: Vec = net_dev - .get_port() - .as_bytes() - .iter() - .cloned() - .map(|b| b as i8) // Convert u8 to i8 - .chain(std::iter::repeat(0)) // Pad with zeros if needed - .take(64) // Take only up to 64 elements - .collect(); - ops.port[0].copy_from_slice(&port_bytes); - ops.dip_addr[0] = self.rtp_session.ip().octets(); - ops.udp_port[0] = self.rtp_session.port() as _; - ops.payload_type = self.rtp_session.payload_type() as _; - ops.packing = self.packing as _; - ops.width = self.width as _; - ops.height = self.height as _; - ops.fps = self.fps as _; - ops.fmt = self.t_fmt as _; - ops.interlaced = self.interlaced as _; - ops.framebuff_cnt = self.fb_cnt as _; - - let pointer_to_void: *mut c_void = &self as *const VideoTx as *mut c_void; - ops.priv_ = pointer_to_void; - ops.get_next_frame = Some(video_tx_get_next_frame); - ops.notify_frame_done = Some(video_tx_notify_frame_done); - } - let mut ops = unsafe { ops.assume_init() }; + if let Some(input_fmt) = self.input_fmt { + // Use pipeline API - let handle = unsafe { sys::st20_tx_create(mtl.handle().unwrap(), &mut ops as *mut _) }; - if handle.is_null() { - bail!("Failed to initialize MTL") + let mut ops: MaybeUninit = MaybeUninit::uninit(); + + // Fill the ops + unsafe { + std::ptr::write_bytes(ops.as_mut_ptr(), 0, 1); + let ops = &mut *ops.as_mut_ptr(); + ops.port.num_port = 1; + ops.name = self.rtp_session.name().unwrap().as_ptr() as *const i8; + + let id = self.netdev_id as usize; + let net_dev = mtl.net_devs().get(id).unwrap(); + let port_bytes: Vec = net_dev + .get_port() + .as_bytes() + .iter() + .cloned() + .map(|b| b as i8) // Convert u8 to i8 + .chain(std::iter::repeat(0)) // Pad with zeros if needed + .take(64) // Take only up to 64 elements + .collect(); + ops.port.port[0].copy_from_slice(&port_bytes); + ops.port.dip_addr[0] = self.rtp_session.ip().octets(); + ops.port.udp_port[0] = self.rtp_session.port() as _; + ops.port.payload_type = self.rtp_session.payload_type() as _; + ops.transport_packing = self.packing as _; + ops.width = self.width as _; + ops.height = self.height as _; + ops.fps = self.fps as _; + ops.input_fmt = input_fmt as _; + ops.transport_fmt = self.t_fmt as _; + ops.interlaced = self.interlaced as _; + ops.framebuff_cnt = self.fb_cnt as _; + ops.device = sys::st_plugin_device_ST_PLUGIN_DEVICE_AUTO; // only set auto for now + + let pointer_to_void: *mut c_void = &self as *const VideoTx as *mut c_void; + ops.priv_ = pointer_to_void; + ops.notify_frame_done = Some(st20p_tx_notify_frame_done); + } + + let mut ops = unsafe { ops.assume_init() }; + + let handle = unsafe { sys::st20p_tx_create(mtl.handle().unwrap(), &mut ops as *mut _) }; + if handle.is_null() { + bail!("Failed to initialize MTL") + } else { + self.handle = Some(VideoHandle::PipelineTx(handle)); + Ok(self) + } } else { - self.handle = Some(handle); + for _ in 0..self.fb_cnt { + self.frame_status.push(FrameStatus::Free); + } + self.consumer_idx = 0; + self.producer_idx = 0; + + let mut ops: MaybeUninit = MaybeUninit::uninit(); + + // Fill the ops unsafe { - self.frame_size = sys::st20_tx_get_framebuffer_size(handle); + std::ptr::write_bytes(ops.as_mut_ptr(), 0, 1); + let ops = &mut *ops.as_mut_ptr(); + ops.num_port = 1; + ops.name = self.rtp_session.name().unwrap().as_ptr() as *const i8; + + let id = self.netdev_id as usize; + let net_dev = mtl.net_devs().get(id).unwrap(); + let port_bytes: Vec = net_dev + .get_port() + .as_bytes() + .iter() + .cloned() + .map(|b| b as i8) // Convert u8 to i8 + .chain(std::iter::repeat(0)) // Pad with zeros if needed + .take(64) // Take only up to 64 elements + .collect(); + ops.port[0].copy_from_slice(&port_bytes); + ops.dip_addr[0] = self.rtp_session.ip().octets(); + ops.udp_port[0] = self.rtp_session.port() as _; + ops.payload_type = self.rtp_session.payload_type() as _; + ops.packing = self.packing as _; + ops.width = self.width as _; + ops.height = self.height as _; + ops.fps = self.fps as _; + ops.fmt = self.t_fmt as _; + ops.interlaced = self.interlaced as _; + ops.framebuff_cnt = self.fb_cnt as _; + + let pointer_to_void: *mut c_void = &self as *const VideoTx as *mut c_void; + ops.priv_ = pointer_to_void; + ops.get_next_frame = Some(video_tx_get_next_frame); + ops.notify_frame_done = Some(video_tx_notify_frame_done); + } + + let mut ops = unsafe { ops.assume_init() }; + + let handle = unsafe { sys::st20_tx_create(mtl.handle().unwrap(), &mut ops as *mut _) }; + if handle.is_null() { + bail!("Failed to initialize MTL") + } else { + self.handle = Some(VideoHandle::Tx(handle)); + unsafe { + self.frame_size = sys::st20_tx_get_framebuffer_size(handle); + } + Ok(self) } - Ok(self) } } @@ -325,31 +539,58 @@ impl VideoTx { /// Wait until free frame available, default timeout is 1 frame interval pub fn wait_free_frame(&mut self) { - let frame_idx = self.producer_idx; - if !self.is_frame_free(frame_idx) { - self.parker.park_timeout(self.fps.duration(1)); + match self.handle { + Some(VideoHandle::Tx(_)) => { + let frame_idx = self.producer_idx; + if !self.is_frame_free(frame_idx) { + self.parker.park_timeout(self.fps.duration(1)); + } + } + Some(VideoHandle::PipelineTx(_)) => self.parker.park_timeout(self.fps.duration(1)), + _ => (), } } /// Fill the frame buffer to be transmitted pub fn fill_next_frame(&mut self, frame: &[u8]) -> Result<()> { - let mut frame_idx = self.producer_idx; - while !self.is_frame_free(frame_idx) { - frame_idx = self.next_frame_idx(frame_idx); - if frame_idx == self.producer_idx { - bail!("No free frames"); + match self.handle { + Some(VideoHandle::Tx(handle)) => { + let mut frame_idx = self.producer_idx; + while !self.is_frame_free(frame_idx) { + frame_idx = self.next_frame_idx(frame_idx); + if frame_idx == self.producer_idx { + bail!("No free frames"); + } + } + + unsafe { + let frame_dst = sys::st20_tx_get_framebuffer(handle, frame_idx as _); + // memcpy frame to frame_dst with size + sys::mtl_memcpy(frame_dst, frame.as_ptr() as _, self.frame_size); + } + + self.set_frame_ready(frame_idx); + self.producer_idx = self.next_frame_idx(frame_idx); + Ok(()) } + Some(VideoHandle::PipelineTx(handle)) => { + unsafe { + let inner_frame = sys::st20p_tx_get_frame(handle); + if inner_frame.is_null() { + bail!("No free frames"); + } + // memcpy frame to frame_dst with size, assume lines no padding + sys::mtl_memcpy( + (*inner_frame).addr[0], + frame.as_ptr() as _, + (*inner_frame).data_size, + ); + sys::st20p_tx_put_frame(handle, inner_frame); + } + Ok(()) + } + _ => bail!("Invalid handle"), } - - unsafe { - let frame_dst = sys::st20_tx_get_framebuffer(self.handle.unwrap(), frame_idx as _); - // memcpy frame to frame_dst with size - sys::mtl_memcpy(frame_dst, frame.as_ptr() as _, self.frame_size); - } - - self.set_frame_ready(frame_idx); - self.producer_idx = self.next_frame_idx(frame_idx); - Ok(()) } fn set_frame_in_use(&mut self, frame_idx: u8) { @@ -381,25 +622,14 @@ impl VideoTx { } } -// Drop trait implementation to automatically clean up resources when the `VideoTx` instance goes out of scope. -impl Drop for VideoTx { - fn drop(&mut self) { - if let Some(handle) = self.handle { - unsafe { - sys::st20_tx_free(handle); - } - } - } -} - /// VideoRx structure for handling receiving of uncompressed video. #[derive(Default, Builder, Debug)] #[builder(setter(into))] pub struct VideoRx { #[builder(default)] - rtp_session: RtpSession, + netdev_id: u8, #[builder(default)] - handle: Option, + rtp_session: RtpSession, #[builder(default = "1920")] width: u32, #[builder(default = "1080")] @@ -413,6 +643,12 @@ pub struct VideoRx { #[builder(default = "false")] interlaced: bool, + // For pipeline API + #[builder(default)] + output_fmt: Option, + + #[builder(setter(skip))] + handle: Option, #[builder(setter(skip))] consumer_idx: u8, #[builder(setter(skip))] @@ -443,66 +679,128 @@ unsafe extern "C" fn video_rx_notify_frame_ready( } } +unsafe extern "C" fn st20p_rx_notify_frame_available(p_void: *mut c_void) -> i32 { + unsafe { + let s: &mut VideoRx = &mut *(p_void as *mut VideoRx); + let u = s.parker.unparker().clone(); + u.unpark(); + 0 + } +} + impl VideoRx { - /// Initializes a new VideoRx session with Media Transport Library (MTL) handle. + /// Initializes a new VideoRx session with Media Transport Library (MTL) handle and Netdev ID. pub fn create(mut self, mtl: &Mtl) -> Result { if self.handle.is_some() { bail!("VideoRx Session is already created"); } - for _ in 0..self.fb_cnt { - self.frames.push(std::ptr::null_mut()); - } self.parker = Parker::new(); - self.consumer_idx = 0; - self.producer_idx = 0; - - let mut ops: MaybeUninit = MaybeUninit::uninit(); - - // Fill the ops - unsafe { - std::ptr::write_bytes(ops.as_mut_ptr(), 0, 1); - let ops = &mut *ops.as_mut_ptr(); - ops.num_port = 1; - ops.name = self.rtp_session.name().unwrap().as_ptr() as *const i8; - - let net_dev = self.rtp_session.net_dev(); - let port_bytes: Vec = net_dev - .get_port() - .as_bytes() - .iter() - .cloned() - .map(|b| b as i8) // Convert u8 to i8 - .chain(std::iter::repeat(0)) // Pad with zeros if needed - .take(64) // Take only up to 64 elements - .collect(); - ops.port[0].copy_from_slice(&port_bytes); - ops.__bindgen_anon_1.ip_addr[0] = self.rtp_session.ip().octets(); - ops.udp_port[0] = self.rtp_session.port() as _; - ops.payload_type = self.rtp_session.payload_type() as _; - ops.width = self.width as _; - ops.height = self.height as _; - ops.fps = self.fps as _; - ops.fmt = self.t_fmt as _; - ops.interlaced = self.interlaced as _; - ops.framebuff_cnt = self.fb_cnt as _; - - let pointer_to_void: *mut c_void = &self as *const VideoRx as *mut c_void; - ops.priv_ = pointer_to_void; - ops.notify_frame_ready = Some(video_rx_notify_frame_ready); - } - let mut ops = unsafe { ops.assume_init() }; + if let Some(output_fmt) = self.output_fmt { + let mut ops: MaybeUninit = MaybeUninit::uninit(); + + // Fill the ops + unsafe { + std::ptr::write_bytes(ops.as_mut_ptr(), 0, 1); + let ops = &mut *ops.as_mut_ptr(); + ops.port.num_port = 1; + ops.name = self.rtp_session.name().unwrap().as_ptr() as *const i8; + + let id = self.netdev_id as usize; + let net_dev = mtl.net_devs().get(id).unwrap(); + let port_bytes: Vec = net_dev + .get_port() + .as_bytes() + .iter() + .cloned() + .map(|b| b as i8) // Convert u8 to i8 + .chain(std::iter::repeat(0)) // Pad with zeros if needed + .take(64) // Take only up to 64 elements + .collect(); + ops.port.port[0].copy_from_slice(&port_bytes); + ops.port.__bindgen_anon_1.ip_addr[0] = self.rtp_session.ip().octets(); + ops.port.udp_port[0] = self.rtp_session.port() as _; + ops.port.payload_type = self.rtp_session.payload_type() as _; + ops.width = self.width as _; + ops.height = self.height as _; + ops.fps = self.fps as _; + ops.output_fmt = output_fmt as _; + ops.transport_fmt = self.t_fmt as _; + ops.interlaced = self.interlaced as _; + ops.framebuff_cnt = self.fb_cnt as _; + ops.device = sys::st_plugin_device_ST_PLUGIN_DEVICE_AUTO; // only set auto for now + + let pointer_to_void: *mut c_void = &self as *const VideoRx as *mut c_void; + ops.priv_ = pointer_to_void; + ops.notify_frame_available = Some(st20p_rx_notify_frame_available); + } + + let mut ops = unsafe { ops.assume_init() }; - let handle = unsafe { sys::st20_rx_create(mtl.handle().unwrap(), &mut ops as *mut _) }; - if handle.is_null() { - bail!("Failed to initialize MTL") + let handle = unsafe { sys::st20p_rx_create(mtl.handle().unwrap(), &mut ops as *mut _) }; + if handle.is_null() { + bail!("Failed to initialize MTL") + } else { + self.handle = Some(VideoHandle::PipelineRx(handle)); + Ok(self) + } } else { - self.handle = Some(handle); + for _ in 0..self.fb_cnt { + self.frames.push(std::ptr::null_mut()); + } + + self.consumer_idx = 0; + self.producer_idx = 0; + + let mut ops: MaybeUninit = MaybeUninit::uninit(); + + // Fill the ops unsafe { - self.frame_size = sys::st20_rx_get_framebuffer_size(handle); + std::ptr::write_bytes(ops.as_mut_ptr(), 0, 1); + let ops = &mut *ops.as_mut_ptr(); + ops.num_port = 1; + ops.name = self.rtp_session.name().unwrap().as_ptr() as *const i8; + + let id = self.netdev_id as usize; + let net_dev = mtl.net_devs().get(id).unwrap(); + let port_bytes: Vec = net_dev + .get_port() + .as_bytes() + .iter() + .cloned() + .map(|b| b as i8) // Convert u8 to i8 + .chain(std::iter::repeat(0)) // Pad with zeros if needed + .take(64) // Take only up to 64 elements + .collect(); + ops.port[0].copy_from_slice(&port_bytes); + ops.__bindgen_anon_1.ip_addr[0] = self.rtp_session.ip().octets(); + ops.udp_port[0] = self.rtp_session.port() as _; + ops.payload_type = self.rtp_session.payload_type() as _; + ops.width = self.width as _; + ops.height = self.height as _; + ops.fps = self.fps as _; + ops.fmt = self.t_fmt as _; + ops.interlaced = self.interlaced as _; + ops.framebuff_cnt = self.fb_cnt as _; + + let pointer_to_void: *mut c_void = &self as *const VideoRx as *mut c_void; + ops.priv_ = pointer_to_void; + ops.notify_frame_ready = Some(video_rx_notify_frame_ready); + } + + let mut ops = unsafe { ops.assume_init() }; + + let handle = unsafe { sys::st20_rx_create(mtl.handle().unwrap(), &mut ops as *mut _) }; + if handle.is_null() { + bail!("Failed to initialize MTL") + } else { + self.handle = Some(VideoHandle::Rx(handle)); + unsafe { + self.frame_size = sys::st20_rx_get_framebuffer_size(handle); + } + Ok(self) } - Ok(self) } } @@ -513,24 +811,49 @@ impl VideoRx { /// Wait until new frame available, default timeout is 1 frame interval pub fn wait_new_frame(&mut self) { - if self.frames[self.consumer_idx as usize].is_null() { - self.parker.park_timeout(self.fps.duration(1)); + match self.handle { + Some(VideoHandle::Rx(_)) => { + if self.frames[self.consumer_idx as usize].is_null() { + self.parker.park_timeout(self.fps.duration(1)); + } + } + Some(VideoHandle::PipelineRx(_)) => { + self.parker.park_timeout(self.fps.duration(1)); + } + _ => (), } } /// Copy the new frame to user provided memory pub fn fill_new_frame(&mut self, data: &[u8]) -> Result<()> { - let frame = self.frames[self.consumer_idx as usize]; - if frame.is_null() { - bail!("No frame available"); - } else { - unsafe { - sys::mtl_memcpy(data.as_ptr() as _, frame as _, self.frame_size); - sys::st20_rx_put_framebuff(self.handle.unwrap(), frame as _); + match self.handle { + Some(VideoHandle::Rx(handle)) => { + let frame = self.frames[self.consumer_idx as usize]; + if frame.is_null() { + bail!("No frame available"); + } else { + unsafe { + sys::mtl_memcpy(data.as_ptr() as _, frame as _, self.frame_size); + sys::st20_rx_put_framebuff(handle, frame as _); + } + self.frames[self.consumer_idx as usize] = std::ptr::null_mut(); + self.consumer_idx = self.next_frame_idx(self.consumer_idx); + Ok(()) + } } - self.frames[self.consumer_idx as usize] = std::ptr::null_mut(); - self.consumer_idx = self.next_frame_idx(self.consumer_idx); - Ok(()) + Some(VideoHandle::PipelineRx(handle)) => { + unsafe { + let frame = sys::st20p_rx_get_frame(handle); + if frame.is_null() { + bail!("No frame available"); + } + // assume lines no padding + sys::mtl_memcpy(data.as_ptr() as _, (*frame).addr[0], (*frame).data_size); + sys::st20p_rx_put_frame(handle, frame); + } + Ok(()) + } + _ => bail!("Invalid handle"), } } @@ -551,17 +874,6 @@ impl VideoRx { } } -// Drop trait implementation to automatically clean up resources when the `VideoRx` instance goes out of scope. -impl Drop for VideoRx { - fn drop(&mut self) { - if let Some(handle) = self.handle { - unsafe { - sys::st20_rx_free(handle); - } - } - } -} - /* TODO #[derive(Default, Builder, Debug)] #[builder(setter(into))]