use crate::ndisys; use crate::ndisys::*; use std::ffi; use std::mem; use std::ptr; use byte_slice_cast::*; pub fn initialize() -> bool { unsafe { NDIlib_initialize() } } #[derive(Debug)] pub struct FindBuilder<'a> { show_local_sources: bool, groups: Option<&'a str>, extra_ips: Option<&'a str>, } impl<'a> Default for FindBuilder<'a> { fn default() -> Self { Self { show_local_sources: true, groups: None, extra_ips: None, } } } impl<'a> FindBuilder<'a> { pub fn show_local_sources(self, show_local_sources: bool) -> Self { Self { show_local_sources, ..self } } pub fn groups(self, groups: &'a str) -> Self { Self { groups: Some(groups), ..self } } pub fn extra_ips(self, extra_ips: &'a str) -> Self { Self { extra_ips: Some(extra_ips), ..self } } pub fn build(self) -> Option { let groups = self.groups.map(|s| ffi::CString::new(s).unwrap()); let extra_ips = self.extra_ips.map(|s| ffi::CString::new(s).unwrap()); unsafe { let ptr = NDIlib_find_create_v2(&NDIlib_find_create_t { show_local_sources: self.show_local_sources, p_groups: groups.as_ref().map(|s| s.as_ptr()).unwrap_or(ptr::null()), p_extra_ips: extra_ips .as_ref() .map(|s| s.as_ptr()) .unwrap_or(ptr::null()), }); if ptr.is_null() { None } else { Some(FindInstance(ptr::NonNull::new_unchecked(ptr))) } } } } #[derive(Debug)] pub struct FindInstance(ptr::NonNull<::std::os::raw::c_void>); unsafe impl Send for FindInstance {} impl FindInstance { pub fn builder<'a>() -> FindBuilder<'a> { FindBuilder::default() } pub fn wait_for_sources(&mut self, timeout_in_ms: u32) -> bool { unsafe { NDIlib_find_wait_for_sources(self.0.as_ptr(), timeout_in_ms) } } pub fn get_current_sources(&mut self) -> Vec { unsafe { let mut no_sources = mem::MaybeUninit::uninit(); let sources_ptr = NDIlib_find_get_current_sources(self.0.as_ptr(), no_sources.as_mut_ptr()); let no_sources = no_sources.assume_init(); if sources_ptr.is_null() || no_sources == 0 { return vec![]; } let mut sources = vec![]; for i in 0..no_sources { sources.push(Source::Borrowed( ptr::NonNull::new_unchecked(sources_ptr.add(i as usize) as *mut _), self, )); } sources } } } impl Drop for FindInstance { fn drop(&mut self) { unsafe { NDIlib_find_destroy(self.0.as_mut()); } } } #[derive(Debug)] pub enum Source<'a> { Borrowed(ptr::NonNull, &'a FindInstance), Owned(NDIlib_source_t, ffi::CString, ffi::CString), } unsafe impl<'a> Send for Source<'a> {} unsafe impl<'a> Sync for Source<'a> {} impl<'a> Source<'a> { pub fn ndi_name(&self) -> &str { unsafe { let ptr = match *self { Source::Borrowed(ptr, _) => &*ptr.as_ptr(), Source::Owned(ref source, _, _) => source, }; assert!(!ptr.p_ndi_name.is_null()); ffi::CStr::from_ptr(ptr.p_ndi_name).to_str().unwrap() } } pub fn url_address(&self) -> &str { unsafe { let ptr = match *self { Source::Borrowed(ptr, _) => &*ptr.as_ptr(), Source::Owned(ref source, _, _) => source, }; assert!(!ptr.p_url_address.is_null()); ffi::CStr::from_ptr(ptr.p_url_address).to_str().unwrap() } } pub fn to_owned<'b>(&self) -> Source<'b> { unsafe { let (ndi_name, url_address) = match *self { Source::Borrowed(ptr, _) => (ptr.as_ref().p_ndi_name, ptr.as_ref().p_url_address), Source::Owned(_, ref ndi_name, ref url_address) => { (ndi_name.as_ptr(), url_address.as_ptr()) } }; let ndi_name = ffi::CString::new(ffi::CStr::from_ptr(ndi_name).to_bytes()).unwrap(); let url_address = ffi::CString::new(ffi::CStr::from_ptr(url_address).to_bytes()).unwrap(); Source::Owned( NDIlib_source_t { p_ndi_name: ndi_name.as_ptr(), p_url_address: url_address.as_ptr(), }, ndi_name, url_address, ) } } } impl<'a> PartialEq for Source<'a> { fn eq(&self, other: &Source<'a>) -> bool { self.ndi_name() == other.ndi_name() && self.url_address() == other.url_address() } } #[derive(Debug)] pub struct RecvBuilder<'a> { ndi_name: Option<&'a str>, url_address: Option<&'a str>, allow_video_fields: bool, bandwidth: NDIlib_recv_bandwidth_e, color_format: NDIlib_recv_color_format_e, ndi_recv_name: &'a str, } impl<'a> RecvBuilder<'a> { pub fn allow_video_fields(self, allow_video_fields: bool) -> Self { Self { allow_video_fields, ..self } } pub fn bandwidth(self, bandwidth: NDIlib_recv_bandwidth_e) -> Self { Self { bandwidth, ..self } } pub fn color_format(self, color_format: NDIlib_recv_color_format_e) -> Self { Self { color_format, ..self } } pub fn build(self) -> Option { unsafe { let ndi_recv_name = ffi::CString::new(self.ndi_recv_name).unwrap(); let ndi_name = self .ndi_name .as_ref() .map(|s| ffi::CString::new(*s).unwrap()); let url_address = self .url_address .as_ref() .map(|s| ffi::CString::new(*s).unwrap()); let ptr = NDIlib_recv_create_v3(&NDIlib_recv_create_v3_t { source_to_connect_to: NDIlib_source_t { p_ndi_name: ndi_name .as_ref() .map(|s| s.as_ptr()) .unwrap_or_else(|| ptr::null_mut()), p_url_address: url_address .as_ref() .map(|s| s.as_ptr()) .unwrap_or_else(|| ptr::null_mut()), }, allow_video_fields: self.allow_video_fields, bandwidth: self.bandwidth, color_format: self.color_format, p_ndi_recv_name: ndi_recv_name.as_ptr(), }); if ptr.is_null() { None } else { Some(RecvInstance(ptr::NonNull::new_unchecked(ptr))) } } } } #[derive(Debug, Clone)] pub struct RecvInstance(ptr::NonNull<::std::os::raw::c_void>); unsafe impl Send for RecvInstance {} impl RecvInstance { pub fn builder<'a>( ndi_name: Option<&'a str>, url_address: Option<&'a str>, ndi_recv_name: &'a str, ) -> RecvBuilder<'a> { RecvBuilder { ndi_name, url_address, allow_video_fields: true, bandwidth: NDIlib_recv_bandwidth_highest, color_format: NDIlib_recv_color_format_UYVY_BGRA, ndi_recv_name, } } pub fn set_tally(&self, tally: &Tally) -> bool { unsafe { NDIlib_recv_set_tally(self.0.as_ptr(), &tally.0) } } pub fn send_metadata(&self, metadata: &MetadataFrame) -> bool { unsafe { NDIlib_recv_send_metadata(self.0.as_ptr(), metadata.as_ptr()) } } pub fn get_queue(&self) -> Queue { unsafe { let mut queue = mem::MaybeUninit::uninit(); NDIlib_recv_get_queue(self.0.as_ptr(), queue.as_mut_ptr()); Queue(queue.assume_init()) } } pub fn capture(&self, timeout_in_ms: u32) -> Result, ()> { unsafe { let ptr = self.0.as_ptr(); let mut video_frame = mem::zeroed(); let mut audio_frame = mem::zeroed(); let mut metadata_frame = mem::zeroed(); let res = NDIlib_recv_capture_v3( ptr, &mut video_frame, &mut audio_frame, &mut metadata_frame, timeout_in_ms, ); match res { NDIlib_frame_type_e::NDIlib_frame_type_audio => Ok(Some(Frame::Audio( AudioFrame::BorrowedRecv(audio_frame, self), ))), NDIlib_frame_type_e::NDIlib_frame_type_video => Ok(Some(Frame::Video( VideoFrame::BorrowedRecv(video_frame, self), ))), NDIlib_frame_type_e::NDIlib_frame_type_metadata => Ok(Some(Frame::Metadata( MetadataFrame::Borrowed(metadata_frame, self), ))), NDIlib_frame_type_e::NDIlib_frame_type_error => Err(()), _ => Ok(None), } } } } impl Drop for RecvInstance { fn drop(&mut self) { unsafe { NDIlib_recv_destroy(self.0.as_ptr() as *mut _) } } } #[derive(Debug)] pub struct SendBuilder<'a> { ndi_name: &'a str, clock_audio: bool, clock_video: bool, } impl<'a> SendBuilder<'a> { pub fn clock_audio(self) -> Self { Self { clock_audio: true, ..self } } pub fn clock_video(self) -> Self { Self { clock_video: true, ..self } } pub fn build(self) -> Option { unsafe { let ndi_name = ffi::CString::new(self.ndi_name).unwrap(); let ptr = NDIlib_send_create(&NDIlib_send_create_t { p_ndi_name: ndi_name.as_ptr(), clock_video: self.clock_video, clock_audio: self.clock_audio, p_groups: ptr::null(), }); if ptr.is_null() { None } else { Some(SendInstance(ptr::NonNull::new_unchecked(ptr))) } } } } #[derive(Debug)] pub struct SendInstance(ptr::NonNull<::std::os::raw::c_void>); unsafe impl Send for SendInstance {} impl SendInstance { pub fn builder(ndi_name: &str) -> SendBuilder { SendBuilder { ndi_name, clock_video: false, clock_audio: false, } } pub fn send_video(&mut self, frame: &VideoFrame) { unsafe { NDIlib_send_send_video_v2(self.0.as_ptr(), frame.as_ptr()); } } pub fn send_audio(&mut self, frame: &AudioFrame) { unsafe { NDIlib_send_send_audio_v3(self.0.as_ptr(), frame.as_ptr()); } } } impl Drop for SendInstance { fn drop(&mut self) { unsafe { NDIlib_send_destroy(self.0.as_ptr() as *mut _) } } } #[derive(Debug)] pub struct Tally(NDIlib_tally_t); unsafe impl Send for Tally {} impl Default for Tally { fn default() -> Self { Self(NDIlib_tally_t { on_program: true, on_preview: false, }) } } impl Tally { pub fn new(on_program: bool, on_preview: bool) -> Self { Self(NDIlib_tally_t { on_program, on_preview, }) } pub fn on_program(&self) -> bool { self.0.on_program } pub fn on_preview(&self) -> bool { self.0.on_preview } } #[derive(Debug)] pub enum Frame<'a> { Video(VideoFrame<'a>), Audio(AudioFrame<'a>), Metadata(MetadataFrame<'a>), } #[derive(Debug)] pub enum VideoFrame<'a> { //Owned(NDIlib_video_frame_v2_t, Option, Option>), BorrowedRecv(NDIlib_video_frame_v2_t, &'a RecvInstance), BorrowedGst( NDIlib_video_frame_v2_t, &'a gst_video::VideoFrameRef<&'a gst::BufferRef>, ), } impl<'a> VideoFrame<'a> { pub fn xres(&self) -> i32 { match self { VideoFrame::BorrowedRecv(ref frame, _) | VideoFrame::BorrowedGst(ref frame, _) => { frame.xres } } } pub fn yres(&self) -> i32 { match self { VideoFrame::BorrowedRecv(ref frame, _) | VideoFrame::BorrowedGst(ref frame, _) => { frame.yres } } } pub fn fourcc(&self) -> NDIlib_FourCC_video_type_e { match self { VideoFrame::BorrowedRecv(ref frame, _) | VideoFrame::BorrowedGst(ref frame, _) => { frame.FourCC } } } pub fn frame_rate(&self) -> (i32, i32) { match self { VideoFrame::BorrowedRecv(ref frame, _) | VideoFrame::BorrowedGst(ref frame, _) => { (frame.frame_rate_N, frame.frame_rate_D) } } } pub fn picture_aspect_ratio(&self) -> f32 { match self { VideoFrame::BorrowedRecv(ref frame, _) | VideoFrame::BorrowedGst(ref frame, _) => { frame.picture_aspect_ratio } } } pub fn frame_format_type(&self) -> NDIlib_frame_format_type_e { match self { VideoFrame::BorrowedRecv(ref frame, _) | VideoFrame::BorrowedGst(ref frame, _) => { frame.frame_format_type } } } pub fn timecode(&self) -> i64 { match self { VideoFrame::BorrowedRecv(ref frame, _) | VideoFrame::BorrowedGst(ref frame, _) => { frame.timecode } } } pub fn data(&self) -> Option<&[u8]> { let fourcc = self.fourcc(); if [ NDIlib_FourCC_video_type_UYVY, NDIlib_FourCC_video_type_UYVA, NDIlib_FourCC_video_type_P216, NDIlib_FourCC_video_type_PA16, NDIlib_FourCC_video_type_YV12, NDIlib_FourCC_video_type_I420, NDIlib_FourCC_video_type_NV12, NDIlib_FourCC_video_type_BGRA, NDIlib_FourCC_video_type_BGRX, NDIlib_FourCC_video_type_RGBA, NDIlib_FourCC_video_type_RGBX, ] .contains(&fourcc) { // FIXME: Unclear if this is correct. Needs to be validated against an actual // interlaced stream let frame_size = if self.frame_format_type() == NDIlib_frame_format_type_e::NDIlib_frame_format_type_field_0 || self.frame_format_type() == NDIlib_frame_format_type_e::NDIlib_frame_format_type_field_1 { self.yres() * self.line_stride_or_data_size_in_bytes() / 2 } else { self.yres() * self.line_stride_or_data_size_in_bytes() }; return unsafe { use std::slice; match self { VideoFrame::BorrowedRecv(ref frame, _) | VideoFrame::BorrowedGst(ref frame, _) => Some(slice::from_raw_parts( frame.p_data as *const u8, frame_size as usize, )), } }; } #[cfg(feature = "advanced-sdk")] if [ NDIlib_FourCC_video_type_ex_SHQ0_highest_bandwidth, NDIlib_FourCC_video_type_ex_SHQ2_highest_bandwidth, NDIlib_FourCC_video_type_ex_SHQ7_highest_bandwidth, NDIlib_FourCC_video_type_ex_SHQ0_lowest_bandwidth, NDIlib_FourCC_video_type_ex_SHQ2_lowest_bandwidth, NDIlib_FourCC_video_type_ex_SHQ7_lowest_bandwidth, ] .contains(&fourcc) { return unsafe { use std::slice; match self { VideoFrame::BorrowedRecv(ref frame, _) | VideoFrame::BorrowedGst(ref frame, _) => Some(slice::from_raw_parts( frame.p_data as *const u8, frame.line_stride_or_data_size_in_bytes as usize, )), } }; } None } #[cfg(feature = "advanced-sdk")] pub fn compressed_packet(&self) -> Option { use byteorder::{LittleEndian, ReadBytesExt}; use std::io::Cursor; use std::slice; unsafe { let fourcc = self.fourcc(); if ![ NDIlib_FourCC_video_type_ex_H264_highest_bandwidth, NDIlib_FourCC_video_type_ex_H264_lowest_bandwidth, NDIlib_FourCC_video_type_ex_HEVC_highest_bandwidth, NDIlib_FourCC_video_type_ex_HEVC_lowest_bandwidth, NDIlib_FourCC_video_type_ex_H264_alpha_highest_bandwidth, NDIlib_FourCC_video_type_ex_H264_alpha_lowest_bandwidth, NDIlib_FourCC_video_type_ex_HEVC_alpha_highest_bandwidth, NDIlib_FourCC_video_type_ex_HEVC_alpha_lowest_bandwidth, ] .contains(&fourcc) { return None; } let data = match self { VideoFrame::BorrowedRecv(ref frame, _) | VideoFrame::BorrowedGst(ref frame, _) => { slice::from_raw_parts( frame.p_data as *const u8, frame.line_stride_or_data_size_in_bytes as usize, ) } }; let mut cursor = Cursor::new(data); let version = cursor.read_u32::().ok()?; if version != ndisys::NDIlib_compressed_packet_version_0 { return None; } let fourcc = cursor.read_u32::().ok()?; let pts = cursor.read_i64::().ok()?; let dts = cursor.read_i64::().ok()?; let _reserved = cursor.read_u64::().ok()?; let flags = cursor.read_u32::().ok()?; let data_size = cursor.read_u32::().ok()?; let extra_data_size = cursor.read_u32::().ok()?; let expected_size = (ndisys::NDIlib_compressed_packet_version_0 as usize) .checked_add(data_size as usize)? .checked_add(extra_data_size as usize)?; if data.len() < expected_size { return None; } Some(CompressedPacket { fourcc, pts, dts, key_frame: flags & ndisys::NDIlib_compressed_packet_flags_keyframe != 0, data: &data[ndisys::NDIlib_compressed_packet_version_0 as usize..] [..data_size as usize], extra_data: if extra_data_size > 0 { Some( &data[ndisys::NDIlib_compressed_packet_version_0 as usize + data_size as usize..][..extra_data_size as usize], ) } else { None }, }) } } pub fn line_stride_or_data_size_in_bytes(&self) -> i32 { match self { VideoFrame::BorrowedRecv(ref frame, _) | VideoFrame::BorrowedGst(ref frame, _) => { let stride = frame.line_stride_or_data_size_in_bytes; if stride != 0 { return stride; } let xres = frame.xres; match frame.FourCC { ndisys::NDIlib_FourCC_video_type_UYVY | ndisys::NDIlib_FourCC_video_type_UYVA | ndisys::NDIlib_FourCC_video_type_YV12 | ndisys::NDIlib_FourCC_video_type_NV12 | ndisys::NDIlib_FourCC_video_type_I420 | ndisys::NDIlib_FourCC_video_type_BGRA | ndisys::NDIlib_FourCC_video_type_BGRX | ndisys::NDIlib_FourCC_video_type_RGBA | ndisys::NDIlib_FourCC_video_type_RGBX => xres, ndisys::NDIlib_FourCC_video_type_P216 | ndisys::NDIlib_FourCC_video_type_PA16 => 2 * xres, _ => 0, } } } } pub fn metadata(&self) -> Option<&str> { unsafe { match self { VideoFrame::BorrowedRecv(ref frame, _) | VideoFrame::BorrowedGst(ref frame, _) => { if frame.p_metadata.is_null() { None } else { Some(ffi::CStr::from_ptr(frame.p_metadata).to_str().unwrap()) } } } } } pub fn timestamp(&self) -> i64 { match self { VideoFrame::BorrowedRecv(ref frame, _) | VideoFrame::BorrowedGst(ref frame, _) => { frame.timestamp } } } pub fn as_ptr(&self) -> *const NDIlib_video_frame_v2_t { match self { VideoFrame::BorrowedRecv(ref frame, _) | VideoFrame::BorrowedGst(ref frame, _) => frame, } } pub fn try_from_video_frame( frame: &'a gst_video::VideoFrameRef<&'a gst::BufferRef>, timecode: i64, ) -> Result { // Planar formats must be in contiguous memory let format = match frame.format() { gst_video::VideoFormat::Uyvy => ndisys::NDIlib_FourCC_video_type_UYVY, gst_video::VideoFormat::I420 => { if (frame.plane_data(1).unwrap().as_ptr() as usize) .checked_sub(frame.plane_data(0).unwrap().as_ptr() as usize) != Some(frame.height() as usize * frame.plane_stride()[0] as usize) { return Err(()); } if (frame.plane_data(2).unwrap().as_ptr() as usize) .checked_sub(frame.plane_data(1).unwrap().as_ptr() as usize) != Some((frame.height() as usize + 1) / 2 * frame.plane_stride()[1] as usize) { return Err(()); } ndisys::NDIlib_FourCC_video_type_I420 } gst_video::VideoFormat::Nv12 => { if (frame.plane_data(1).unwrap().as_ptr() as usize) .checked_sub(frame.plane_data(0).unwrap().as_ptr() as usize) != Some(frame.height() as usize * frame.plane_stride()[0] as usize) { return Err(()); } ndisys::NDIlib_FourCC_video_type_NV12 } gst_video::VideoFormat::Nv21 => { if (frame.plane_data(1).unwrap().as_ptr() as usize) .checked_sub(frame.plane_data(0).unwrap().as_ptr() as usize) != Some(frame.height() as usize * frame.plane_stride()[0] as usize) { return Err(()); } ndisys::NDIlib_FourCC_video_type_NV12 } gst_video::VideoFormat::Yv12 => { if (frame.plane_data(1).unwrap().as_ptr() as usize) .checked_sub(frame.plane_data(0).unwrap().as_ptr() as usize) != Some(frame.height() as usize * frame.plane_stride()[0] as usize) { return Err(()); } if (frame.plane_data(2).unwrap().as_ptr() as usize) .checked_sub(frame.plane_data(1).unwrap().as_ptr() as usize) != Some((frame.height() as usize + 1) / 2 * frame.plane_stride()[1] as usize) { return Err(()); } ndisys::NDIlib_FourCC_video_type_YV12 } gst_video::VideoFormat::Bgra => ndisys::NDIlib_FourCC_video_type_BGRA, gst_video::VideoFormat::Bgrx => ndisys::NDIlib_FourCC_video_type_BGRX, gst_video::VideoFormat::Rgba => ndisys::NDIlib_FourCC_video_type_RGBA, gst_video::VideoFormat::Rgbx => ndisys::NDIlib_FourCC_video_type_RGBX, _ => return Err(()), }; let frame_format_type = match frame.info().interlace_mode() { gst_video::VideoInterlaceMode::Progressive => { NDIlib_frame_format_type_e::NDIlib_frame_format_type_progressive } gst_video::VideoInterlaceMode::Interleaved => { NDIlib_frame_format_type_e::NDIlib_frame_format_type_interleaved } // FIXME: Is this correct? #[cfg(feature = "interlaced-fields")] gst_video::VideoInterlaceMode::Alternate if frame.flags().contains(gst_video::VideoFrameFlags::TFF) => { NDIlib_frame_format_type_e::NDIlib_frame_format_type_field_0 } #[cfg(feature = "interlaced-fields")] gst_video::VideoInterlaceMode::Alternate if !frame.flags().contains(gst_video::VideoFrameFlags::TFF) => { NDIlib_frame_format_type_e::NDIlib_frame_format_type_field_1 } _ => return Err(()), }; let picture_aspect_ratio = frame.info().par() * gst::Fraction::new(frame.width() as i32, frame.height() as i32); let picture_aspect_ratio = picture_aspect_ratio.numer() as f32 / picture_aspect_ratio.denom() as f32; let ndi_frame = NDIlib_video_frame_v2_t { xres: frame.width() as i32, yres: frame.height() as i32, FourCC: format, frame_rate_N: frame.info().fps().numer(), frame_rate_D: frame.info().fps().denom(), picture_aspect_ratio, frame_format_type, timecode, p_data: frame.plane_data(0).unwrap().as_ptr() as *const ::std::os::raw::c_char, line_stride_or_data_size_in_bytes: frame.plane_stride()[0], p_metadata: ptr::null(), timestamp: 0, }; Ok(VideoFrame::BorrowedGst(ndi_frame, frame)) } } impl<'a> Drop for VideoFrame<'a> { #[allow(irrefutable_let_patterns)] fn drop(&mut self) { if let VideoFrame::BorrowedRecv(ref mut frame, recv) = *self { unsafe { NDIlib_recv_free_video_v2(recv.0.as_ptr() as *mut _, frame); } } } } #[derive(Debug)] pub enum AudioFrame<'a> { Owned( NDIlib_audio_frame_v3_t, Option, Option>, ), BorrowedRecv(NDIlib_audio_frame_v3_t, &'a RecvInstance), } impl<'a> AudioFrame<'a> { pub fn sample_rate(&self) -> i32 { match self { AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => { frame.sample_rate } } } pub fn no_channels(&self) -> i32 { match self { AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => { frame.no_channels } } } pub fn no_samples(&self) -> i32 { match self { AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => { frame.no_samples } } } pub fn timecode(&self) -> i64 { match self { AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => { frame.timecode } } } pub fn fourcc(&self) -> NDIlib_FourCC_audio_type_e { match self { AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => { frame.FourCC } } } pub fn data(&self) -> Option<&[u8]> { unsafe { use std::slice; let fourcc = self.fourcc(); if [NDIlib_FourCC_audio_type_FLTp].contains(&fourcc) { return match self { AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => { Some(slice::from_raw_parts( frame.p_data as *const u8, (frame.no_channels * frame.channel_stride_or_data_size_in_bytes) as usize, )) } }; } #[cfg(feature = "advanced-sdk")] if [NDIlib_FourCC_audio_type_Opus].contains(&fourcc) { return match self { AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => { Some(slice::from_raw_parts( frame.p_data as *const u8, frame.channel_stride_or_data_size_in_bytes as usize, )) } }; } None } } #[cfg(feature = "advanced-sdk")] pub fn compressed_packet(&self) -> Option { use byteorder::{LittleEndian, ReadBytesExt}; use std::io::Cursor; use std::slice; unsafe { let fourcc = self.fourcc(); if ![NDIlib_FourCC_audio_type_AAC].contains(&fourcc) { return None; } let data = match self { AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => { slice::from_raw_parts( frame.p_data as *const u8, frame.channel_stride_or_data_size_in_bytes as usize, ) } }; let mut cursor = Cursor::new(data); let version = cursor.read_u32::().ok()?; if version != ndisys::NDIlib_compressed_packet_version_0 { return None; } let fourcc = cursor.read_u32::().ok()?; let pts = cursor.read_i64::().ok()?; let dts = cursor.read_i64::().ok()?; let _reserved = cursor.read_u64::().ok()?; let flags = cursor.read_u32::().ok()?; let data_size = cursor.read_u32::().ok()?; let extra_data_size = cursor.read_u32::().ok()?; let expected_size = (ndisys::NDIlib_compressed_packet_version_0 as usize) .checked_add(data_size as usize)? .checked_add(extra_data_size as usize)?; if data.len() < expected_size { return None; } Some(CompressedPacket { fourcc, pts, dts, key_frame: flags & ndisys::NDIlib_compressed_packet_flags_keyframe != 0, data: &data[ndisys::NDIlib_compressed_packet_version_0 as usize..] [..data_size as usize], extra_data: if extra_data_size > 0 { Some( &data[ndisys::NDIlib_compressed_packet_version_0 as usize + data_size as usize..][..extra_data_size as usize], ) } else { None }, }) } } pub fn channel_stride_or_data_size_in_bytes(&self) -> i32 { match self { AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => { frame.channel_stride_or_data_size_in_bytes } } } pub fn metadata(&self) -> Option<&str> { unsafe { match self { AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => { if frame.p_metadata.is_null() { None } else { Some(ffi::CStr::from_ptr(frame.p_metadata).to_str().unwrap()) } } } } } pub fn timestamp(&self) -> i64 { match self { AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => { frame.timestamp } } } pub fn as_ptr(&self) -> *const NDIlib_audio_frame_v3_t { match self { AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => frame, } } pub fn try_from_buffer( info: &gst_audio::AudioInfo, buffer: &gst::BufferRef, timecode: i64, ) -> Result { if info.format() != gst_audio::AUDIO_FORMAT_F32 { return Err(()); } let map = buffer.map_readable().map_err(|_| ())?; let src_data = map.as_slice_of::().map_err(|_| ())?; let no_samples = src_data.len() as i32 / info.channels() as i32; let channel_stride_or_data_size_in_bytes = no_samples * mem::size_of::() as i32; let mut dest_data = Vec::::with_capacity(no_samples as usize * info.channels() as usize); assert_eq!(dest_data.capacity(), src_data.len()); unsafe { let dest_ptr = dest_data.as_mut_ptr(); for (i, samples) in src_data.chunks_exact(info.channels() as usize).enumerate() { for (c, sample) in samples.iter().enumerate() { ptr::write(dest_ptr.add(c * no_samples as usize + i), *sample); } } dest_data.set_len(no_samples as usize * info.channels() as usize); } let dest = NDIlib_audio_frame_v3_t { sample_rate: info.rate() as i32, no_channels: info.channels() as i32, no_samples, timecode, FourCC: NDIlib_FourCC_audio_type_FLTp, p_data: dest_data.as_mut_ptr(), channel_stride_or_data_size_in_bytes, p_metadata: ptr::null(), timestamp: 0, }; Ok(AudioFrame::Owned(dest, None, Some(dest_data))) } } impl<'a> Drop for AudioFrame<'a> { #[allow(irrefutable_let_patterns)] fn drop(&mut self) { if let AudioFrame::BorrowedRecv(ref mut frame, recv) = *self { unsafe { NDIlib_recv_free_audio_v3(recv.0.as_ptr() as *mut _, frame); } } } } #[cfg(feature = "advanced-sdk")] pub struct CompressedPacket<'a> { pub fourcc: ndisys::NDIlib_compressed_FourCC_type_e, pub pts: i64, pub dts: i64, pub key_frame: bool, pub data: &'a [u8], pub extra_data: Option<&'a [u8]>, } #[derive(Debug)] pub enum MetadataFrame<'a> { Owned(NDIlib_metadata_frame_t, Option), Borrowed(NDIlib_metadata_frame_t, &'a RecvInstance), } impl<'a> MetadataFrame<'a> { pub fn new(timecode: i64, data: Option<&str>) -> Self { let data = data.map(|s| ffi::CString::new(s).unwrap()); MetadataFrame::Owned( NDIlib_metadata_frame_t { length: data .as_ref() .map(|s| s.to_str().unwrap().len()) .unwrap_or(0) as i32, timecode, p_data: data .as_ref() .map(|s| s.as_ptr() as *mut _) .unwrap_or(ptr::null_mut()), }, data, ) } pub fn timecode(&self) -> i64 { match self { MetadataFrame::Owned(ref frame, _) => frame.timecode, MetadataFrame::Borrowed(ref frame, _) => frame.timecode, } } pub fn metadata(&self) -> Option<&str> { unsafe { match self { MetadataFrame::Owned(_, ref metadata) => { metadata.as_ref().map(|s| s.to_str().unwrap()) } MetadataFrame::Borrowed(ref frame, _) => { if frame.p_data.is_null() || frame.length == 0 { None } else if frame.length != 0 { use std::slice; Some( ffi::CStr::from_bytes_with_nul_unchecked(slice::from_raw_parts( frame.p_data as *const u8, frame.length as usize, )) .to_str() .unwrap(), ) } else { Some(ffi::CStr::from_ptr(frame.p_data).to_str().unwrap()) } } } } } pub fn as_ptr(&self) -> *const NDIlib_metadata_frame_t { match self { MetadataFrame::Owned(ref frame, _) => frame, MetadataFrame::Borrowed(ref frame, _) => frame, } } } impl<'a> Default for MetadataFrame<'a> { fn default() -> Self { MetadataFrame::Owned( NDIlib_metadata_frame_t { length: 0, timecode: 0, //NDIlib_send_timecode_synthesize, p_data: ptr::null(), }, None, ) } } impl<'a> Drop for MetadataFrame<'a> { fn drop(&mut self) { if let MetadataFrame::Borrowed(ref mut frame, recv) = *self { unsafe { NDIlib_recv_free_metadata(recv.0.as_ptr() as *mut _, frame); } } } } #[derive(Debug, Clone)] pub struct Queue(NDIlib_recv_queue_t); impl Queue { pub fn audio_frames(&self) -> i32 { self.0.audio_frames } pub fn video_frames(&self) -> i32 { self.0.video_frames } pub fn metadata_frames(&self) -> i32 { self.0.metadata_frames } }