From 864e9b4520fdc5993492503f0ef692a03f1e845b Mon Sep 17 00:00:00 2001 From: rubenrua Date: Wed, 4 Apr 2018 22:22:53 +0200 Subject: [PATCH 001/199] 1st commit --- Cargo.lock | 4 ++ Cargo.toml | 6 ++ src/main.rs | 178 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 188 insertions(+) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/main.rs diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..201bb94c --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,4 @@ +[[package]] +name = "ndi" +version = "0.1.0" + diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..51ef523e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "ndi" +version = "0.1.0" +authors = ["rubenrua "] + +[dependencies] diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 00000000..efc9db89 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,178 @@ +use std::ptr; +use std::ffi::{CString, CStr}; + + + +#[link(name = "ndi")] +extern "C" { + pub fn NDIlib_initialize() -> bool; + pub fn NDIlib_find_create_v2( + p_create_settings: *const NDIlib_find_create_t, + ) -> NDIlib_find_instance_t; + pub fn NDIlib_find_get_current_sources( + p_instance: NDIlib_find_instance_t, + p_no_sources: *mut u32, + ) -> *const NDIlib_source_t; + pub fn NDIlib_recv_create_v3( + p_create_settings: *const NDIlib_recv_create_v3_t, + ) -> NDIlib_recv_instance_t; + pub fn NDIlib_find_destroy( + p_instance: NDIlib_find_instance_t, + ); + pub fn NDIlib_recv_set_tally( + p_instance: NDIlib_recv_instance_t, + p_tally: *const NDIlib_tally_t, + ) -> bool; +} + +pub type NDIlib_find_instance_t = *mut ::std::os::raw::c_void; + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct NDIlib_find_create_t { + pub show_local_sources: bool, + pub p_groups: *const ::std::os::raw::c_char, + pub p_extra_ips: *const ::std::os::raw::c_char, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct NDIlib_source_t { + pub p_ndi_name: *const ::std::os::raw::c_char, + pub p_ip_address: *const ::std::os::raw::c_char, +} + + +//TODO review enum +pub type NDIlib_recv_bandwidth_e = i32; +pub const NDIlib_recv_bandwidth_e_NDIlib_recv_bandwidth_metadata_only : NDIlib_recv_bandwidth_e = -10; +pub const NDIlib_recv_bandwidth_e_NDIlib_recv_bandwidth_audio_only : NDIlib_recv_bandwidth_e = 10; +pub const NDIlib_recv_bandwidth_e_NDIlib_recv_bandwidth_lowest : NDIlib_recv_bandwidth_e = 0; +pub const NDIlib_recv_bandwidth_e_NDIlib_recv_bandwidth_highest : NDIlib_recv_bandwidth_e = 100; + + +pub type NDIlib_recv_color_format_e = u32; +pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_BGRX_BGRA : NDIlib_recv_color_format_e = 0; +pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_UYVY_BGRA : NDIlib_recv_color_format_e = 1; +pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_RGBX_RGBA : NDIlib_recv_color_format_e = 2; +pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_UYVY_RGBA : NDIlib_recv_color_format_e = 3; +pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_fastest : NDIlib_recv_color_format_e = 100; +pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_e_BGRX_BGRA : NDIlib_recv_color_format_e = 0; +pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_e_UYVY_BGRA : NDIlib_recv_color_format_e = 1; +pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_e_RGBX_RGBA : NDIlib_recv_color_format_e = 2; +pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_e_UYVY_RGBA : NDIlib_recv_color_format_e = 3; + +#[repr(C)] +#[derive(Debug,Copy,Clone)] +pub struct NDIlib_recv_create_v3_t { + pub source_to_connect_to: NDIlib_source_t, + pub color_format: NDIlib_recv_color_format_e, + pub bandwidth: NDIlib_recv_bandwidth_e, + pub allow_video_fields: bool, + pub p_ndi_name: *const ::std::os::raw::c_char, +} + +pub type NDIlib_recv_instance_t = *mut ::std::os::raw::c_void; + + +#[repr(C)] +#[derive(Debug,Copy,Clone)] +pub struct NDIlib_tally_t { + pub on_program: bool, + pub on_preview: bool, +} + +#[repr(C)] +#[derive(Debug,Copy,Clone)] +pub struct NDIlib_metadata_frame_t { + pub length: ::std::os::raw::c_int, + pub timecode: i64, + pub p_data: *mut ::std::os::raw::c_char, +} + + +fn main() { + unsafe { + if !NDIlib_initialize() { + //TODO delete exits + println!("Cannot run NDI: NDIlib_initialize error."); + ::std::process::exit(1); + } + + //TODO valores por defecto + let mut NDI_find_create_desc = NDIlib_find_create_t { + show_local_sources: false, + p_groups: ptr::null(), + p_extra_ips: ptr::null() + }; + let pNDI_find = NDIlib_find_create_v2(&NDI_find_create_desc); + if pNDI_find.is_null() { + println!("Cannot run NDI: NDIlib_find_create_v2 error."); + ::std::process::exit(1); + } + + let mut no_sources: u32 = 0; + let mut p_sources = ptr::null(); + while no_sources == 0 { + p_sources = NDIlib_find_get_current_sources(pNDI_find, &mut no_sources as *mut u32); + } + + + // We need at least one source + if p_sources.is_null() { + println!("Error getting NDIlib_find_get_current_sources."); + ::std::process::exit(1); + } + + println!("no_source {}: Name '{}' Address '{}'", + no_sources, + CStr::from_ptr((*p_sources).p_ndi_name).to_string_lossy().into_owned(), + CStr::from_ptr((*p_sources).p_ip_address).to_string_lossy().into_owned() + ); + + // We now have at least one source, so we create a receiver to look at it. + // We tell it that we prefer YCbCr video since it is more efficient for us. If the source has an alpha channel + // it will still be provided in BGRA + let p_ndi_name = CString::new("Galicaster NDI Receiver").unwrap(); + let mut NDI_recv_create_desc = NDIlib_recv_create_v3_t { + source_to_connect_to: *p_sources, + allow_video_fields: false, + bandwidth: NDIlib_recv_bandwidth_e_NDIlib_recv_bandwidth_lowest, + color_format: NDIlib_recv_color_format_e_NDIlib_recv_color_format_BGRX_BGRA, + p_ndi_name: p_ndi_name.as_ptr(), //ptr::null(), + }; + + + let pNDI_recv = NDIlib_recv_create_v3(&NDI_recv_create_desc); + if pNDI_recv.is_null() { + println!("Cannot run NDI: NDIlib_recv_create_v3 error."); + ::std::process::exit(1); + } + + // Destroy the NDI finder. We needed to have access to the pointers to p_sources[0] + NDIlib_find_destroy(pNDI_find); + + // We are now going to mark this source as being on program output for tally purposes (but not on preview) + let tally_state = NDIlib_tally_t { + on_program: true, + on_preview: true, + }; + NDIlib_recv_set_tally(pNDI_recv, &tally_state); + + + // Enable Hardwqre Decompression support if this support has it. Please read the caveats in the documentation + // regarding this. There are times in which it might reduce the performance although on small stream numbers + // it almost always yields the same or better performance. +/* + NDIlib_metadata_frame_t enable_hw_accel; + enable_hw_accel.p_data = ""; + NDIlib_recv_send_metadata(pNDI_recv, &enable_hw_accel); +*/ + + + + + + } + println!("Hello, world!"); +} \ No newline at end of file From ef508c39d13067f8a0b8ecccd55c815cbfe56ed8 Mon Sep 17 00:00:00 2001 From: rubenrua Date: Thu, 5 Apr 2018 09:56:38 +0200 Subject: [PATCH 002/199] Print metadata recv --- src/main.rs | 136 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 118 insertions(+), 18 deletions(-) diff --git a/src/main.rs b/src/main.rs index efc9db89..41e5faa0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,6 +23,17 @@ extern "C" { p_instance: NDIlib_recv_instance_t, p_tally: *const NDIlib_tally_t, ) -> bool; + pub fn NDIlib_recv_send_metadata( + p_instance: NDIlib_recv_instance_t, + p_metadata: *const NDIlib_metadata_frame_t, + ) -> bool; + pub fn NDIlib_recv_capture_v2( + p_instance: NDIlib_recv_instance_t, + p_video_data: *const NDIlib_video_frame_v2_t, + p_audio_data: *const NDIlib_audio_frame_v2_t, + p_metadata: *const NDIlib_metadata_frame_t, + timeout_in_ms: u32, + ) -> NDIlib_frame_type_e; } pub type NDIlib_find_instance_t = *mut ::std::os::raw::c_void; @@ -44,23 +55,32 @@ pub struct NDIlib_source_t { //TODO review enum +pub type NDIlib_frame_type_e = i32; +pub const NDIlib_frame_type_none: NDIlib_frame_type_e = 0; +pub const NDIlib_frame_type_video: NDIlib_frame_type_e = 1; +pub const NDIlib_frame_type_audio: NDIlib_frame_type_e = 2; +pub const NDIlib_frame_type_metadata: NDIlib_frame_type_e = 3; +pub const NDIlib_frame_type_error: NDIlib_frame_type_e = 4; +pub const NDIlib_frame_type_status_change: NDIlib_frame_type_e = 100; + + pub type NDIlib_recv_bandwidth_e = i32; -pub const NDIlib_recv_bandwidth_e_NDIlib_recv_bandwidth_metadata_only : NDIlib_recv_bandwidth_e = -10; -pub const NDIlib_recv_bandwidth_e_NDIlib_recv_bandwidth_audio_only : NDIlib_recv_bandwidth_e = 10; -pub const NDIlib_recv_bandwidth_e_NDIlib_recv_bandwidth_lowest : NDIlib_recv_bandwidth_e = 0; -pub const NDIlib_recv_bandwidth_e_NDIlib_recv_bandwidth_highest : NDIlib_recv_bandwidth_e = 100; +pub const NDIlib_recv_bandwidth_e_NDIlib_recv_bandwidth_metadata_only: NDIlib_recv_bandwidth_e = -10; +pub const NDIlib_recv_bandwidth_e_NDIlib_recv_bandwidth_audio_only: NDIlib_recv_bandwidth_e = 10; +pub const NDIlib_recv_bandwidth_e_NDIlib_recv_bandwidth_lowest: NDIlib_recv_bandwidth_e = 0; +pub const NDIlib_recv_bandwidth_e_NDIlib_recv_bandwidth_highest: NDIlib_recv_bandwidth_e = 100; pub type NDIlib_recv_color_format_e = u32; -pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_BGRX_BGRA : NDIlib_recv_color_format_e = 0; -pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_UYVY_BGRA : NDIlib_recv_color_format_e = 1; -pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_RGBX_RGBA : NDIlib_recv_color_format_e = 2; -pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_UYVY_RGBA : NDIlib_recv_color_format_e = 3; -pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_fastest : NDIlib_recv_color_format_e = 100; -pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_e_BGRX_BGRA : NDIlib_recv_color_format_e = 0; -pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_e_UYVY_BGRA : NDIlib_recv_color_format_e = 1; -pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_e_RGBX_RGBA : NDIlib_recv_color_format_e = 2; -pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_e_UYVY_RGBA : NDIlib_recv_color_format_e = 3; +pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_BGRX_BGRA: NDIlib_recv_color_format_e = 0; +pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_UYVY_BGRA: NDIlib_recv_color_format_e = 1; +pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_RGBX_RGBA: NDIlib_recv_color_format_e = 2; +pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_UYVY_RGBA: NDIlib_recv_color_format_e = 3; +pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_fastest: NDIlib_recv_color_format_e = 100; +pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_e_BGRX_BGRA: NDIlib_recv_color_format_e = 0; +pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_e_UYVY_BGRA: NDIlib_recv_color_format_e = 1; +pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_e_RGBX_RGBA: NDIlib_recv_color_format_e = 2; +pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_e_UYVY_RGBA: NDIlib_recv_color_format_e = 3; #[repr(C)] #[derive(Debug,Copy,Clone)] @@ -87,7 +107,38 @@ pub struct NDIlib_tally_t { pub struct NDIlib_metadata_frame_t { pub length: ::std::os::raw::c_int, pub timecode: i64, - pub p_data: *mut ::std::os::raw::c_char, + pub p_data: *const ::std::os::raw::c_char, +} + +#[repr(C)] +#[derive(Debug,Copy,Clone)] +pub struct NDIlib_video_frame_v2_t { + pub xres: ::std::os::raw::c_int, + pub yres: ::std::os::raw::c_int, + pub FourCC: u32, //TODO enum + pub frame_rate_N: ::std::os::raw::c_int, + pub frame_rate_D: ::std::os::raw::c_int, + pub picture_aspect_ratio: ::std::os::raw::c_float, + pub frame_format_type: u32, //TODO enum + pub timecode: i64, + pub p_data: *const ::std::os::raw::c_char, + pub line_stride_in_bytes: ::std::os::raw::c_int, + pub p_metadata: *const ::std::os::raw::c_char, + pub timestamp: i64, +} + + +#[repr(C)] +#[derive(Debug,Copy,Clone)] +pub struct NDIlib_audio_frame_v2_t { + pub sample_rate: ::std::os::raw::c_int, + pub no_channels: ::std::os::raw::c_int, + pub no_samples: ::std::os::raw::c_int, + pub timecode: i64, + pub p_data: *const ::std::os::raw::c_float, + pub channel_stride_in_bytes: ::std::os::raw::c_int, + pub p_metadata: *const ::std::os::raw::c_char, + pub timestamp: i64, } @@ -163,15 +214,64 @@ fn main() { // Enable Hardwqre Decompression support if this support has it. Please read the caveats in the documentation // regarding this. There are times in which it might reduce the performance although on small stream numbers // it almost always yields the same or better performance. -/* - NDIlib_metadata_frame_t enable_hw_accel; - enable_hw_accel.p_data = ""; + let data = CString::new("").unwrap(); + let enable_hw_accel = NDIlib_metadata_frame_t { + length: data.to_bytes().len() as i32, + timecode: 0, + p_data: data.as_ptr(), + }; + NDIlib_recv_send_metadata(pNDI_recv, &enable_hw_accel); -*/ + + loop { + + let video_frame = NDIlib_video_frame_v2_t { + xres: 0, + yres: 0, + FourCC: 0, + frame_rate_N: 0, + frame_rate_D: 0, + picture_aspect_ratio: 0.0, + frame_format_type: 0, + timecode: 0, + p_data: ptr::null(), + line_stride_in_bytes: 0, + p_metadata: ptr::null(), + timestamp: 0, + }; + let audio_frame = NDIlib_audio_frame_v2_t { + sample_rate: 0, + no_channels: 0, + no_samples: 0, + timecode: 0, + p_data: ptr::null(), + channel_stride_in_bytes: 0, + p_metadata: ptr::null(), + timestamp: 0, + }; + + let metadata_frame = NDIlib_metadata_frame_t { + length: 0, + timecode: 0, + p_data: ptr::null(), + }; + + let frame_type = NDIlib_recv_capture_v2(pNDI_recv, &video_frame, &audio_frame, &metadata_frame, 1000); + match frame_type { + NDIlib_frame_type_metadata => { + println!("Tengo 3 {} '{}'", + metadata_frame.length, + CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned(), + ); + }, + _ => println!("Tengo {}", frame_type), + } + + } } println!("Hello, world!"); From 941a500c5facaa445d09cac7fe81f68e8daf5b1f Mon Sep 17 00:00:00 2001 From: rubenrua Date: Thu, 5 Apr 2018 09:56:53 +0200 Subject: [PATCH 003/199] Add gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..327c7006 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target/ +**/*.rs.bk +*~ From 683253e8e7f3557ed152f212d9ef698946feb99f Mon Sep 17 00:00:00 2001 From: rubenrua Date: Thu, 5 Apr 2018 11:10:13 +0200 Subject: [PATCH 004/199] Create external ndilib --- src/main.rs | 160 ++++++-------------------------------------------- src/ndilib.rs | 139 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+), 143 deletions(-) create mode 100644 src/ndilib.rs diff --git a/src/main.rs b/src/main.rs index 41e5faa0..bd844d1f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,145 +1,12 @@ +#![allow(non_upper_case_globals, non_snake_case)] + + +pub mod ndilib; + use std::ptr; use std::ffi::{CString, CStr}; - - -#[link(name = "ndi")] -extern "C" { - pub fn NDIlib_initialize() -> bool; - pub fn NDIlib_find_create_v2( - p_create_settings: *const NDIlib_find_create_t, - ) -> NDIlib_find_instance_t; - pub fn NDIlib_find_get_current_sources( - p_instance: NDIlib_find_instance_t, - p_no_sources: *mut u32, - ) -> *const NDIlib_source_t; - pub fn NDIlib_recv_create_v3( - p_create_settings: *const NDIlib_recv_create_v3_t, - ) -> NDIlib_recv_instance_t; - pub fn NDIlib_find_destroy( - p_instance: NDIlib_find_instance_t, - ); - pub fn NDIlib_recv_set_tally( - p_instance: NDIlib_recv_instance_t, - p_tally: *const NDIlib_tally_t, - ) -> bool; - pub fn NDIlib_recv_send_metadata( - p_instance: NDIlib_recv_instance_t, - p_metadata: *const NDIlib_metadata_frame_t, - ) -> bool; - pub fn NDIlib_recv_capture_v2( - p_instance: NDIlib_recv_instance_t, - p_video_data: *const NDIlib_video_frame_v2_t, - p_audio_data: *const NDIlib_audio_frame_v2_t, - p_metadata: *const NDIlib_metadata_frame_t, - timeout_in_ms: u32, - ) -> NDIlib_frame_type_e; -} - -pub type NDIlib_find_instance_t = *mut ::std::os::raw::c_void; - -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct NDIlib_find_create_t { - pub show_local_sources: bool, - pub p_groups: *const ::std::os::raw::c_char, - pub p_extra_ips: *const ::std::os::raw::c_char, -} - -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct NDIlib_source_t { - pub p_ndi_name: *const ::std::os::raw::c_char, - pub p_ip_address: *const ::std::os::raw::c_char, -} - - -//TODO review enum -pub type NDIlib_frame_type_e = i32; -pub const NDIlib_frame_type_none: NDIlib_frame_type_e = 0; -pub const NDIlib_frame_type_video: NDIlib_frame_type_e = 1; -pub const NDIlib_frame_type_audio: NDIlib_frame_type_e = 2; -pub const NDIlib_frame_type_metadata: NDIlib_frame_type_e = 3; -pub const NDIlib_frame_type_error: NDIlib_frame_type_e = 4; -pub const NDIlib_frame_type_status_change: NDIlib_frame_type_e = 100; - - -pub type NDIlib_recv_bandwidth_e = i32; -pub const NDIlib_recv_bandwidth_e_NDIlib_recv_bandwidth_metadata_only: NDIlib_recv_bandwidth_e = -10; -pub const NDIlib_recv_bandwidth_e_NDIlib_recv_bandwidth_audio_only: NDIlib_recv_bandwidth_e = 10; -pub const NDIlib_recv_bandwidth_e_NDIlib_recv_bandwidth_lowest: NDIlib_recv_bandwidth_e = 0; -pub const NDIlib_recv_bandwidth_e_NDIlib_recv_bandwidth_highest: NDIlib_recv_bandwidth_e = 100; - - -pub type NDIlib_recv_color_format_e = u32; -pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_BGRX_BGRA: NDIlib_recv_color_format_e = 0; -pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_UYVY_BGRA: NDIlib_recv_color_format_e = 1; -pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_RGBX_RGBA: NDIlib_recv_color_format_e = 2; -pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_UYVY_RGBA: NDIlib_recv_color_format_e = 3; -pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_fastest: NDIlib_recv_color_format_e = 100; -pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_e_BGRX_BGRA: NDIlib_recv_color_format_e = 0; -pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_e_UYVY_BGRA: NDIlib_recv_color_format_e = 1; -pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_e_RGBX_RGBA: NDIlib_recv_color_format_e = 2; -pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_e_UYVY_RGBA: NDIlib_recv_color_format_e = 3; - -#[repr(C)] -#[derive(Debug,Copy,Clone)] -pub struct NDIlib_recv_create_v3_t { - pub source_to_connect_to: NDIlib_source_t, - pub color_format: NDIlib_recv_color_format_e, - pub bandwidth: NDIlib_recv_bandwidth_e, - pub allow_video_fields: bool, - pub p_ndi_name: *const ::std::os::raw::c_char, -} - -pub type NDIlib_recv_instance_t = *mut ::std::os::raw::c_void; - - -#[repr(C)] -#[derive(Debug,Copy,Clone)] -pub struct NDIlib_tally_t { - pub on_program: bool, - pub on_preview: bool, -} - -#[repr(C)] -#[derive(Debug,Copy,Clone)] -pub struct NDIlib_metadata_frame_t { - pub length: ::std::os::raw::c_int, - pub timecode: i64, - pub p_data: *const ::std::os::raw::c_char, -} - -#[repr(C)] -#[derive(Debug,Copy,Clone)] -pub struct NDIlib_video_frame_v2_t { - pub xres: ::std::os::raw::c_int, - pub yres: ::std::os::raw::c_int, - pub FourCC: u32, //TODO enum - pub frame_rate_N: ::std::os::raw::c_int, - pub frame_rate_D: ::std::os::raw::c_int, - pub picture_aspect_ratio: ::std::os::raw::c_float, - pub frame_format_type: u32, //TODO enum - pub timecode: i64, - pub p_data: *const ::std::os::raw::c_char, - pub line_stride_in_bytes: ::std::os::raw::c_int, - pub p_metadata: *const ::std::os::raw::c_char, - pub timestamp: i64, -} - - -#[repr(C)] -#[derive(Debug,Copy,Clone)] -pub struct NDIlib_audio_frame_v2_t { - pub sample_rate: ::std::os::raw::c_int, - pub no_channels: ::std::os::raw::c_int, - pub no_samples: ::std::os::raw::c_int, - pub timecode: i64, - pub p_data: *const ::std::os::raw::c_float, - pub channel_stride_in_bytes: ::std::os::raw::c_int, - pub p_metadata: *const ::std::os::raw::c_char, - pub timestamp: i64, -} +use ndilib::*; fn main() { @@ -151,7 +18,7 @@ fn main() { } //TODO valores por defecto - let mut NDI_find_create_desc = NDIlib_find_create_t { + let NDI_find_create_desc = NDIlib_find_create_t { show_local_sources: false, p_groups: ptr::null(), p_extra_ips: ptr::null() @@ -185,7 +52,7 @@ fn main() { // We tell it that we prefer YCbCr video since it is more efficient for us. If the source has an alpha channel // it will still be provided in BGRA let p_ndi_name = CString::new("Galicaster NDI Receiver").unwrap(); - let mut NDI_recv_create_desc = NDIlib_recv_create_v3_t { + let NDI_recv_create_desc = NDIlib_recv_create_v3_t { source_to_connect_to: *p_sources, allow_video_fields: false, bandwidth: NDIlib_recv_bandwidth_e_NDIlib_recv_bandwidth_lowest, @@ -263,16 +130,23 @@ fn main() { match frame_type { NDIlib_frame_type_metadata => { - println!("Tengo 3 {} '{}'", + println!("Tengo metadata {} '{}'", metadata_frame.length, CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned(), ); }, + NDIlib_frame_type_error => { + println!("Tengo error {} '{}'", + metadata_frame.length, + CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned(), + ); + break; + }, _ => println!("Tengo {}", frame_type), } } } - println!("Hello, world!"); + println!("Exit"); } \ No newline at end of file diff --git a/src/ndilib.rs b/src/ndilib.rs new file mode 100644 index 00000000..49a32c0a --- /dev/null +++ b/src/ndilib.rs @@ -0,0 +1,139 @@ +#![allow(non_camel_case_types, non_upper_case_globals)] + +#[link(name = "ndi")] +extern "C" { + pub fn NDIlib_initialize() -> bool; + pub fn NDIlib_find_create_v2( + p_create_settings: *const NDIlib_find_create_t, + ) -> NDIlib_find_instance_t; + pub fn NDIlib_find_get_current_sources( + p_instance: NDIlib_find_instance_t, + p_no_sources: *mut u32, + ) -> *const NDIlib_source_t; + pub fn NDIlib_recv_create_v3( + p_create_settings: *const NDIlib_recv_create_v3_t, + ) -> NDIlib_recv_instance_t; + pub fn NDIlib_find_destroy( + p_instance: NDIlib_find_instance_t, + ); + pub fn NDIlib_recv_set_tally( + p_instance: NDIlib_recv_instance_t, + p_tally: *const NDIlib_tally_t, + ) -> bool; + pub fn NDIlib_recv_send_metadata( + p_instance: NDIlib_recv_instance_t, + p_metadata: *const NDIlib_metadata_frame_t, + ) -> bool; + pub fn NDIlib_recv_capture_v2( + p_instance: NDIlib_recv_instance_t, + p_video_data: *const NDIlib_video_frame_v2_t, + p_audio_data: *const NDIlib_audio_frame_v2_t, + p_metadata: *const NDIlib_metadata_frame_t, + timeout_in_ms: u32, + ) -> NDIlib_frame_type_e; +} + +pub type NDIlib_find_instance_t = *mut ::std::os::raw::c_void; + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct NDIlib_find_create_t { + pub show_local_sources: bool, + pub p_groups: *const ::std::os::raw::c_char, + pub p_extra_ips: *const ::std::os::raw::c_char, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct NDIlib_source_t { + pub p_ndi_name: *const ::std::os::raw::c_char, + pub p_ip_address: *const ::std::os::raw::c_char, +} + + +//TODO review enum +pub type NDIlib_frame_type_e = i32; +pub const NDIlib_frame_type_none: NDIlib_frame_type_e = 0; +pub const NDIlib_frame_type_video: NDIlib_frame_type_e = 1; +pub const NDIlib_frame_type_audio: NDIlib_frame_type_e = 2; +pub const NDIlib_frame_type_metadata: NDIlib_frame_type_e = 3; +pub const NDIlib_frame_type_error: NDIlib_frame_type_e = 4; +pub const NDIlib_frame_type_status_change: NDIlib_frame_type_e = 100; + + +pub type NDIlib_recv_bandwidth_e = i32; +pub const NDIlib_recv_bandwidth_e_NDIlib_recv_bandwidth_metadata_only: NDIlib_recv_bandwidth_e = -10; +pub const NDIlib_recv_bandwidth_e_NDIlib_recv_bandwidth_audio_only: NDIlib_recv_bandwidth_e = 10; +pub const NDIlib_recv_bandwidth_e_NDIlib_recv_bandwidth_lowest: NDIlib_recv_bandwidth_e = 0; +pub const NDIlib_recv_bandwidth_e_NDIlib_recv_bandwidth_highest: NDIlib_recv_bandwidth_e = 100; + + +pub type NDIlib_recv_color_format_e = u32; +pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_BGRX_BGRA: NDIlib_recv_color_format_e = 0; +pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_UYVY_BGRA: NDIlib_recv_color_format_e = 1; +pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_RGBX_RGBA: NDIlib_recv_color_format_e = 2; +pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_UYVY_RGBA: NDIlib_recv_color_format_e = 3; +pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_fastest: NDIlib_recv_color_format_e = 100; +pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_e_BGRX_BGRA: NDIlib_recv_color_format_e = 0; +pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_e_UYVY_BGRA: NDIlib_recv_color_format_e = 1; +pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_e_RGBX_RGBA: NDIlib_recv_color_format_e = 2; +pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_e_UYVY_RGBA: NDIlib_recv_color_format_e = 3; + +#[repr(C)] +#[derive(Debug,Copy,Clone)] +pub struct NDIlib_recv_create_v3_t { + pub source_to_connect_to: NDIlib_source_t, + pub color_format: NDIlib_recv_color_format_e, + pub bandwidth: NDIlib_recv_bandwidth_e, + pub allow_video_fields: bool, + pub p_ndi_name: *const ::std::os::raw::c_char, +} + +pub type NDIlib_recv_instance_t = *mut ::std::os::raw::c_void; + + +#[repr(C)] +#[derive(Debug,Copy,Clone)] +pub struct NDIlib_tally_t { + pub on_program: bool, + pub on_preview: bool, +} + +#[repr(C)] +#[derive(Debug,Copy,Clone)] +pub struct NDIlib_metadata_frame_t { + pub length: ::std::os::raw::c_int, + pub timecode: i64, + pub p_data: *const ::std::os::raw::c_char, +} + +#[repr(C)] +#[derive(Debug,Copy,Clone)] +pub struct NDIlib_video_frame_v2_t { + pub xres: ::std::os::raw::c_int, + pub yres: ::std::os::raw::c_int, + pub FourCC: u32, //TODO enum + pub frame_rate_N: ::std::os::raw::c_int, + pub frame_rate_D: ::std::os::raw::c_int, + pub picture_aspect_ratio: ::std::os::raw::c_float, + pub frame_format_type: u32, //TODO enum + pub timecode: i64, + pub p_data: *const ::std::os::raw::c_char, + pub line_stride_in_bytes: ::std::os::raw::c_int, + pub p_metadata: *const ::std::os::raw::c_char, + pub timestamp: i64, +} + + +#[repr(C)] +#[derive(Debug,Copy,Clone)] +pub struct NDIlib_audio_frame_v2_t { + pub sample_rate: ::std::os::raw::c_int, + pub no_channels: ::std::os::raw::c_int, + pub no_samples: ::std::os::raw::c_int, + pub timecode: i64, + pub p_data: *const ::std::os::raw::c_float, + pub channel_stride_in_bytes: ::std::os::raw::c_int, + pub p_metadata: *const ::std::os::raw::c_char, + pub timestamp: i64, +} From 80dcf3c90caae78b96db9e1d5f218f06766e1205 Mon Sep 17 00:00:00 2001 From: rubenrua Date: Thu, 5 Apr 2018 11:13:51 +0200 Subject: [PATCH 005/199] Finish example --- src/main.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main.rs b/src/main.rs index bd844d1f..5350c194 100644 --- a/src/main.rs +++ b/src/main.rs @@ -129,6 +129,16 @@ fn main() { match frame_type { + NDIlib_frame_type_video => { + println!("Tengo video {:?}", + video_frame + ); + }, + NDIlib_frame_type_audio => { + println!("Tengo audio {:?}", + audio_frame + ); + }, NDIlib_frame_type_metadata => { println!("Tengo metadata {} '{}'", metadata_frame.length, From 6c19cc8d9feee8313002ecf35a9226ea31d9d364 Mon Sep 17 00:00:00 2001 From: rubenrua Date: Mon, 9 Apr 2018 05:32:57 +0000 Subject: [PATCH 006/199] Move example to a workspace --- Cargo.lock => example/Cargo.lock | 0 Cargo.toml => example/Cargo.toml | 0 {src => example/src}/main.rs | 0 {src => example/src}/ndilib.rs | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename Cargo.lock => example/Cargo.lock (100%) rename Cargo.toml => example/Cargo.toml (100%) rename {src => example/src}/main.rs (100%) rename {src => example/src}/ndilib.rs (100%) diff --git a/Cargo.lock b/example/Cargo.lock similarity index 100% rename from Cargo.lock rename to example/Cargo.lock diff --git a/Cargo.toml b/example/Cargo.toml similarity index 100% rename from Cargo.toml rename to example/Cargo.toml diff --git a/src/main.rs b/example/src/main.rs similarity index 100% rename from src/main.rs rename to example/src/main.rs diff --git a/src/ndilib.rs b/example/src/ndilib.rs similarity index 100% rename from src/ndilib.rs rename to example/src/ndilib.rs From 337337854b4def47fd6e7ae82b9549d7e9391a09 Mon Sep 17 00:00:00 2001 From: rubenrua Date: Mon, 9 Apr 2018 05:53:04 +0000 Subject: [PATCH 007/199] Add gst-plugin-ndi --- gst-plugin-ndi/Cargo.lock | 275 ++++++++++++ gst-plugin-ndi/Cargo.toml | 21 + gst-plugin-ndi/README.md | 11 + gst-plugin-ndi/src/lib.rs | 45 ++ gst-plugin-ndi/src/ndisrc.rs | 832 +++++++++++++++++++++++++++++++++++ 5 files changed, 1184 insertions(+) create mode 100644 gst-plugin-ndi/Cargo.lock create mode 100644 gst-plugin-ndi/Cargo.toml create mode 100644 gst-plugin-ndi/README.md create mode 100644 gst-plugin-ndi/src/lib.rs create mode 100644 gst-plugin-ndi/src/ndisrc.rs diff --git a/gst-plugin-ndi/Cargo.lock b/gst-plugin-ndi/Cargo.lock new file mode 100644 index 00000000..740d507d --- /dev/null +++ b/gst-plugin-ndi/Cargo.lock @@ -0,0 +1,275 @@ +[[package]] +name = "array-init" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bitflags" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "byte-slice-cast" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "byteorder" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cfg-if" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "glib" +version = "0.5.0" +source = "git+https://github.com/gtk-rs/glib#cae39ff7a72073a553f5b60321bd9389db00ac3b" +dependencies = [ + "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "glib-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", + "gobject-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", + "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "glib-sys" +version = "0.6.0" +source = "git+https://github.com/gtk-rs/sys#ce1fffe51a6498ac278502bf1afb18d711ad0250" +dependencies = [ + "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gobject-sys" +version = "0.6.0" +source = "git+https://github.com/gtk-rs/sys#ce1fffe51a6498ac278502bf1afb18d711ad0250" +dependencies = [ + "glib-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", + "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gst-plugin" +version = "0.1.0" +source = "git+https://github.com/sdroege/gst-plugin-rs#f2f18ebb278f66f09995148cf9790ed469f6357f" +dependencies = [ + "byteorder 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "glib 0.5.0 (git+https://github.com/gtk-rs/glib)", + "glib-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", + "gobject-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", + "gstreamer 0.12.0 (git+https://github.com/sdroege/gstreamer-rs)", + "gstreamer-base 0.12.0 (git+https://github.com/sdroege/gstreamer-rs)", + "gstreamer-base-sys 0.6.0 (git+https://github.com/sdroege/gstreamer-sys)", + "gstreamer-sys 0.6.0 (git+https://github.com/sdroege/gstreamer-sys)", + "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gst-plugin-ndi" +version = "0.1.0" +dependencies = [ + "byte-slice-cast 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "glib 0.5.0 (git+https://github.com/gtk-rs/glib)", + "gst-plugin 0.1.0 (git+https://github.com/sdroege/gst-plugin-rs)", + "gstreamer 0.12.0 (git+https://github.com/sdroege/gstreamer-rs)", + "gstreamer-audio 0.12.0 (git+https://github.com/sdroege/gstreamer-rs)", + "gstreamer-base 0.12.0 (git+https://github.com/sdroege/gstreamer-rs)", + "gstreamer-video 0.12.0 (git+https://github.com/sdroege/gstreamer-rs)", + "num-traits 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gstreamer" +version = "0.12.0" +source = "git+https://github.com/sdroege/gstreamer-rs#5dd17d6248b45fa2536f1864962908fb21b041e8" +dependencies = [ + "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "glib 0.5.0 (git+https://github.com/gtk-rs/glib)", + "glib-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", + "gobject-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", + "gstreamer-sys 0.6.0 (git+https://github.com/sdroege/gstreamer-sys)", + "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", + "muldiv 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "num-rational 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gstreamer-audio" +version = "0.12.0" +source = "git+https://github.com/sdroege/gstreamer-rs#5dd17d6248b45fa2536f1864962908fb21b041e8" +dependencies = [ + "array-init 0.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "glib 0.5.0 (git+https://github.com/gtk-rs/glib)", + "glib-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", + "gobject-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", + "gstreamer 0.12.0 (git+https://github.com/sdroege/gstreamer-rs)", + "gstreamer-audio-sys 0.6.0 (git+https://github.com/sdroege/gstreamer-sys)", + "gstreamer-sys 0.6.0 (git+https://github.com/sdroege/gstreamer-sys)", +] + +[[package]] +name = "gstreamer-audio-sys" +version = "0.6.0" +source = "git+https://github.com/sdroege/gstreamer-sys#a9a4608562bcd377f116d5d4806bff439eca5168" +dependencies = [ + "glib-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", + "gobject-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", + "gstreamer-base-sys 0.6.0 (git+https://github.com/sdroege/gstreamer-sys)", + "gstreamer-sys 0.6.0 (git+https://github.com/sdroege/gstreamer-sys)", + "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gstreamer-base" +version = "0.12.0" +source = "git+https://github.com/sdroege/gstreamer-rs#5dd17d6248b45fa2536f1864962908fb21b041e8" +dependencies = [ + "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "glib 0.5.0 (git+https://github.com/gtk-rs/glib)", + "glib-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", + "gobject-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", + "gstreamer 0.12.0 (git+https://github.com/sdroege/gstreamer-rs)", + "gstreamer-base-sys 0.6.0 (git+https://github.com/sdroege/gstreamer-sys)", + "gstreamer-sys 0.6.0 (git+https://github.com/sdroege/gstreamer-sys)", +] + +[[package]] +name = "gstreamer-base-sys" +version = "0.6.0" +source = "git+https://github.com/sdroege/gstreamer-sys#a9a4608562bcd377f116d5d4806bff439eca5168" +dependencies = [ + "glib-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", + "gobject-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", + "gstreamer-sys 0.6.0 (git+https://github.com/sdroege/gstreamer-sys)", + "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gstreamer-sys" +version = "0.6.0" +source = "git+https://github.com/sdroege/gstreamer-sys#a9a4608562bcd377f116d5d4806bff439eca5168" +dependencies = [ + "glib-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", + "gobject-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", + "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gstreamer-video" +version = "0.12.0" +source = "git+https://github.com/sdroege/gstreamer-rs#5dd17d6248b45fa2536f1864962908fb21b041e8" +dependencies = [ + "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "glib 0.5.0 (git+https://github.com/gtk-rs/glib)", + "glib-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", + "gobject-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", + "gstreamer 0.12.0 (git+https://github.com/sdroege/gstreamer-rs)", + "gstreamer-base 0.12.0 (git+https://github.com/sdroege/gstreamer-rs)", + "gstreamer-base-sys 0.6.0 (git+https://github.com/sdroege/gstreamer-sys)", + "gstreamer-sys 0.6.0 (git+https://github.com/sdroege/gstreamer-sys)", + "gstreamer-video-sys 0.6.0 (git+https://github.com/sdroege/gstreamer-sys)", + "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gstreamer-video-sys" +version = "0.6.0" +source = "git+https://github.com/sdroege/gstreamer-sys#a9a4608562bcd377f116d5d4806bff439eca5168" +dependencies = [ + "glib-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", + "gobject-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", + "gstreamer-base-sys 0.6.0 (git+https://github.com/sdroege/gstreamer-sys)", + "gstreamer-sys 0.6.0 (git+https://github.com/sdroege/gstreamer-sys)", + "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "lazy_static" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.40" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "muldiv" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "nodrop" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "num-integer" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-traits 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-rational" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-integer 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-traits" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "pkg-config" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum array-init 0.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4d3b508d35216892b50a135fb52c9bb90f04a97b7782230805dff1a156ad5469" +"checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf" +"checksum byte-slice-cast 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5a865e7bfa6c3b79216ccba767d4dc66e4f9f65f1ed4639e73faff3c4a2485d7" +"checksum byteorder 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "73b5bdfe7ee3ad0b99c9801d58807a9dbc9e09196365b0203853b99889ab3c87" +"checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" +"checksum glib 0.5.0 (git+https://github.com/gtk-rs/glib)" = "" +"checksum glib-sys 0.6.0 (git+https://github.com/gtk-rs/sys)" = "" +"checksum gobject-sys 0.6.0 (git+https://github.com/gtk-rs/sys)" = "" +"checksum gst-plugin 0.1.0 (git+https://github.com/sdroege/gst-plugin-rs)" = "" +"checksum gstreamer 0.12.0 (git+https://github.com/sdroege/gstreamer-rs)" = "" +"checksum gstreamer-audio 0.12.0 (git+https://github.com/sdroege/gstreamer-rs)" = "" +"checksum gstreamer-audio-sys 0.6.0 (git+https://github.com/sdroege/gstreamer-sys)" = "" +"checksum gstreamer-base 0.12.0 (git+https://github.com/sdroege/gstreamer-rs)" = "" +"checksum gstreamer-base-sys 0.6.0 (git+https://github.com/sdroege/gstreamer-sys)" = "" +"checksum gstreamer-sys 0.6.0 (git+https://github.com/sdroege/gstreamer-sys)" = "" +"checksum gstreamer-video 0.12.0 (git+https://github.com/sdroege/gstreamer-rs)" = "" +"checksum gstreamer-video-sys 0.6.0 (git+https://github.com/sdroege/gstreamer-sys)" = "" +"checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d" +"checksum libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "6fd41f331ac7c5b8ac259b8bf82c75c0fb2e469bbf37d2becbba9a6a2221965b" +"checksum muldiv 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1cbef5aa2e8cd82a18cc20e26434cc9843e1ef46e55bfabe5bddb022236c5b3e" +"checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2" +"checksum num-integer 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f8d26da319fb45674985c78f1d1caf99aa4941f785d384a2ae36d0740bc3e2fe" +"checksum num-rational 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "ee314c74bd753fc86b4780aa9475da469155f3848473a261d2d18e35245a784e" +"checksum num-traits 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dee092fcdf725aee04dd7da1d21debff559237d49ef1cb3e69bcb8ece44c7364" +"checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903" diff --git a/gst-plugin-ndi/Cargo.toml b/gst-plugin-ndi/Cargo.toml new file mode 100644 index 00000000..892ab255 --- /dev/null +++ b/gst-plugin-ndi/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "gst-plugin-ndi" +version = "0.1.0" +authors = ["Ruben Gonzalez "] +repository = "https://gitlab.teltek.es/rubenrua/ndi-rs.git" +license = "unknow" # TODO MIT/Apache-2.0 + +[dependencies] +gst-plugin = { git = "https://github.com/sdroege/gst-plugin-rs" } +glib = { git = "https://github.com/gtk-rs/glib" } +gstreamer = { git = "https://github.com/sdroege/gstreamer-rs" } +gstreamer-base = { git = "https://github.com/sdroege/gstreamer-rs" } +gstreamer-video = { git = "https://github.com/sdroege/gstreamer-rs" } +gstreamer-audio = { git = "https://github.com/sdroege/gstreamer-rs" } +byte-slice-cast = "0.1" # TODO delete +num-traits = "0.2" # TODO delete + +[lib] +name = "gstndi" +crate-type = ["cdylib"] +path = "src/lib.rs" diff --git a/gst-plugin-ndi/README.md b/gst-plugin-ndi/README.md new file mode 100644 index 00000000..7737d60a --- /dev/null +++ b/gst-plugin-ndi/README.md @@ -0,0 +1,11 @@ +TODO +==== + +Test +------- + +``` +cargo build +export GST_PLUGIN_PATH=`pwd`/target/debug +gst-inspect-1.0 ndisrc +``` diff --git a/gst-plugin-ndi/src/lib.rs b/gst-plugin-ndi/src/lib.rs new file mode 100644 index 00000000..4c620360 --- /dev/null +++ b/gst-plugin-ndi/src/lib.rs @@ -0,0 +1,45 @@ +// Copyright (C) 2017 Sebastian Dröge +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +extern crate glib; +#[macro_use] +extern crate gst_plugin; +#[macro_use] +extern crate gstreamer as gst; +extern crate gstreamer_audio as gst_audio; +extern crate gstreamer_base as gst_base; +extern crate gstreamer_video as gst_video; + +extern crate byte_slice_cast; +extern crate num_traits; + +mod ndisrc; + +// Plugin entry point that should register all elements provided by this plugin, +// and everything else that this plugin might provide (e.g. typefinders or device providers). +fn plugin_init(plugin: &gst::Plugin) -> bool { + ndisrc::register(plugin); + true +} + +// Static plugin metdata that is directly stored in the plugin shared object and read by GStreamer +// upon loading. +// Plugin name, plugin description, plugin entry point function, version number of this plugin, +// license of the plugin, source package name, binary package name, origin where it comes from +// and the date/time of release. +plugin_define!( + b"ndi\0", + b"NewTek NDI Plugin\0", + plugin_init, + b"1.0\0", + b"MIT/X11\0", + b"ndi\0", + b"ndi\0", + b"https://gitlab.teltek.es/rubenrua/ndi-rs.git\0", + b"2018-04-09\0" +); diff --git a/gst-plugin-ndi/src/ndisrc.rs b/gst-plugin-ndi/src/ndisrc.rs new file mode 100644 index 00000000..e4bc72ee --- /dev/null +++ b/gst-plugin-ndi/src/ndisrc.rs @@ -0,0 +1,832 @@ +// Copyright (C) 2018 Sebastian Dröge +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use glib; +use gst; +use gst::prelude::*; +use gst_audio; +use gst_base::prelude::*; + +use byte_slice_cast::*; + +use gst_plugin::base_src::*; +use gst_plugin::element::*; +use gst_plugin::object::*; +use gst_plugin::properties::*; + +use std::ops::Rem; +use std::sync::Mutex; +use std::{i32, u32}; + +use num_traits::cast::NumCast; +use num_traits::float::Float; + +// Default values of properties +const DEFAULT_SAMPLES_PER_BUFFER: u32 = 1024; +const DEFAULT_FREQ: u32 = 440; +const DEFAULT_VOLUME: f64 = 0.8; +const DEFAULT_MUTE: bool = false; +const DEFAULT_IS_LIVE: bool = false; + +// Property value storage +#[derive(Debug, Clone, Copy)] +struct Settings { + samples_per_buffer: u32, + freq: u32, + volume: f64, + mute: bool, + is_live: bool, +} + +impl Default for Settings { + fn default() -> Self { + Settings { + samples_per_buffer: DEFAULT_SAMPLES_PER_BUFFER, + freq: DEFAULT_FREQ, + volume: DEFAULT_VOLUME, + mute: DEFAULT_MUTE, + is_live: DEFAULT_IS_LIVE, + } + } +} + +// Metadata for the properties +static PROPERTIES: [Property; 5] = [ + Property::UInt( + "samples-per-buffer", + "Samples Per Buffer", + "Number of samples per output buffer", + (1, u32::MAX), + DEFAULT_SAMPLES_PER_BUFFER, + PropertyMutability::ReadWrite, + ), + Property::UInt( + "freq", + "Frequency", + "Frequency", + (1, u32::MAX), + DEFAULT_FREQ, + PropertyMutability::ReadWrite, + ), + Property::Double( + "volume", + "Volume", + "Output volume", + (0.0, 10.0), + DEFAULT_VOLUME, + PropertyMutability::ReadWrite, + ), + Property::Boolean( + "mute", + "Mute", + "Mute", + DEFAULT_MUTE, + PropertyMutability::ReadWrite, + ), + Property::Boolean( + "is-live", + "Is Live", + "(Pseudo) live output", + DEFAULT_IS_LIVE, + PropertyMutability::ReadWrite, + ), +]; + +// Stream-specific state, i.e. audio format configuration +// and sample offset +struct State { + info: Option, + sample_offset: u64, + sample_stop: Option, + accumulator: f64, +} + +impl Default for State { + fn default() -> State { + State { + info: None, + sample_offset: 0, + sample_stop: None, + accumulator: 0.0, + } + } +} + +struct ClockWait { + clock_id: Option, + flushing: bool, +} + +// Struct containing all the element data +struct NdiSrc { + cat: gst::DebugCategory, + settings: Mutex, + state: Mutex, + clock_wait: Mutex, +} + +impl NdiSrc { + // Called when a new instance is to be created + fn new(element: &BaseSrc) -> Box> { + // Initialize live-ness and notify the base class that + // we'd like to operate in Time format + element.set_live(DEFAULT_IS_LIVE); + element.set_format(gst::Format::Time); + + Box::new(Self { + cat: gst::DebugCategory::new( + "ndisrc", + gst::DebugColorFlags::empty(), + "NewTek NDI Source", + ), + settings: Mutex::new(Default::default()), + state: Mutex::new(Default::default()), + clock_wait: Mutex::new(ClockWait { + clock_id: None, + flushing: true, + }), + }) + } + + // Called exactly once when registering the type. Used for + // setting up metadata for all instances, e.g. the name and + // classification and the pad templates with their caps. + // + // Actual instances can create pads based on those pad templates + // with a subset of the caps given here. In case of basesrc, + // a "src" and "sink" pad template are required here and the base class + // will automatically instantiate pads for them. + // + // Our element here can output f32 and f64 + fn class_init(klass: &mut BaseSrcClass) { + klass.set_metadata( + "NewTek NDI Source", + "Source", + "NewTek NDI video/audio source", + "Ruben Gonzalez ", + ); + + // On the src pad, we can produce F32/F64 with any sample rate + // and any number of channels + let caps = gst::Caps::new_simple( + "audio/x-raw", + &[ + ( + "format", + &gst::List::new(&[ + &gst_audio::AUDIO_FORMAT_F32.to_string(), + &gst_audio::AUDIO_FORMAT_F64.to_string(), + ]), + ), + ("layout", &"interleaved"), + ("rate", &gst::IntRange::::new(1, i32::MAX)), + ("channels", &gst::IntRange::::new(1, i32::MAX)), + ], + ); + // The src pad template must be named "src" for basesrc + // and specific a pad that is always there + let src_pad_template = gst::PadTemplate::new( + "src", + gst::PadDirection::Src, + gst::PadPresence::Always, + &caps, + ); + klass.add_pad_template(src_pad_template); + + // Install all our properties + klass.install_properties(&PROPERTIES); + } + + fn process( + data: &mut [u8], + accumulator_ref: &mut f64, + freq: u32, + rate: u32, + channels: u32, + vol: f64, + ) { + use std::f64::consts::PI; + + // Reinterpret our byte-slice as a slice containing elements of the type + // we're interested in. GStreamer requires for raw audio that the alignment + // of memory is correct, so this will never ever fail unless there is an + // actual bug elsewhere. + let data = data.as_mut_slice_of::().unwrap(); + + // Convert all our parameters to the target type for calculations + let vol: F = NumCast::from(vol).unwrap(); + let freq = freq as f64; + let rate = rate as f64; + let two_pi = 2.0 * PI; + + // We're carrying a accumulator with up to 2pi around instead of working + // on the sample offset. High sample offsets cause too much inaccuracy when + // converted to floating point numbers and then iterated over in 1-steps + let mut accumulator = *accumulator_ref; + let step = two_pi * freq / rate; + + for chunk in data.chunks_mut(channels as usize) { + let value = vol * F::sin(NumCast::from(accumulator).unwrap()); + for sample in chunk { + *sample = value; + } + + accumulator += step; + if accumulator >= two_pi { + accumulator -= two_pi; + } + } + + *accumulator_ref = accumulator; + } +} + +// Virtual methods of GObject itself +impl ObjectImpl for NdiSrc { + // Called whenever a value of a property is changed. It can be called + // at any time from any thread. + fn set_property(&self, obj: &glib::Object, id: u32, value: &glib::Value) { + let prop = &PROPERTIES[id as usize]; + let element = obj.clone().downcast::().unwrap(); + + match *prop { + Property::UInt("samples-per-buffer", ..) => { + let mut settings = self.settings.lock().unwrap(); + let samples_per_buffer = value.get().unwrap(); + gst_info!( + self.cat, + obj: &element, + "Changing samples-per-buffer from {} to {}", + settings.samples_per_buffer, + samples_per_buffer + ); + settings.samples_per_buffer = samples_per_buffer; + drop(settings); + + let _ = + element.post_message(&gst::Message::new_latency().src(Some(&element)).build()); + } + Property::UInt("freq", ..) => { + let mut settings = self.settings.lock().unwrap(); + let freq = value.get().unwrap(); + gst_info!( + self.cat, + obj: &element, + "Changing freq from {} to {}", + settings.freq, + freq + ); + settings.freq = freq; + } + Property::Double("volume", ..) => { + let mut settings = self.settings.lock().unwrap(); + let volume = value.get().unwrap(); + gst_info!( + self.cat, + obj: &element, + "Changing volume from {} to {}", + settings.volume, + volume + ); + settings.volume = volume; + } + Property::Boolean("mute", ..) => { + let mut settings = self.settings.lock().unwrap(); + let mute = value.get().unwrap(); + gst_info!( + self.cat, + obj: &element, + "Changing mute from {} to {}", + settings.mute, + mute + ); + settings.mute = mute; + } + Property::Boolean("is-live", ..) => { + let mut settings = self.settings.lock().unwrap(); + let is_live = value.get().unwrap(); + gst_info!( + self.cat, + obj: &element, + "Changing is-live from {} to {}", + settings.is_live, + is_live + ); + settings.is_live = is_live; + } + _ => unimplemented!(), + } + } + + // Called whenever a value of a property is read. It can be called + // at any time from any thread. + fn get_property(&self, _obj: &glib::Object, id: u32) -> Result { + let prop = &PROPERTIES[id as usize]; + + match *prop { + Property::UInt("samples-per-buffer", ..) => { + let settings = self.settings.lock().unwrap(); + Ok(settings.samples_per_buffer.to_value()) + } + Property::UInt("freq", ..) => { + let settings = self.settings.lock().unwrap(); + Ok(settings.freq.to_value()) + } + Property::Double("volume", ..) => { + let settings = self.settings.lock().unwrap(); + Ok(settings.volume.to_value()) + } + Property::Boolean("mute", ..) => { + let settings = self.settings.lock().unwrap(); + Ok(settings.mute.to_value()) + } + Property::Boolean("is-live", ..) => { + let settings = self.settings.lock().unwrap(); + Ok(settings.is_live.to_value()) + } + _ => unimplemented!(), + } + } +} + +// Virtual methods of gst::Element. We override none +impl ElementImpl for NdiSrc { + fn change_state( + &self, + element: &BaseSrc, + transition: gst::StateChange, + ) -> gst::StateChangeReturn { + // Configure live'ness once here just before starting the source + match transition { + gst::StateChange::ReadyToPaused => { + element.set_live(self.settings.lock().unwrap().is_live); + } + _ => (), + } + + element.parent_change_state(transition) + } +} + +// Virtual methods of gst_base::BaseSrc +impl BaseSrcImpl for NdiSrc { + // Called whenever the input/output caps are changing, i.e. in the very beginning before data + // flow happens and whenever the situation in the pipeline is changing. All buffers after this + // call have the caps given here. + // + // We simply remember the resulting AudioInfo from the caps to be able to use this for knowing + // the sample rate, etc. when creating buffers + fn set_caps(&self, element: &BaseSrc, caps: &gst::CapsRef) -> bool { + use std::f64::consts::PI; + + let info = match gst_audio::AudioInfo::from_caps(caps) { + None => return false, + Some(info) => info, + }; + + gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); + + element.set_blocksize(info.bpf() * (*self.settings.lock().unwrap()).samples_per_buffer); + + let settings = *self.settings.lock().unwrap(); + let mut state = self.state.lock().unwrap(); + + // If we have no caps yet, any old sample_offset and sample_stop will be + // in nanoseconds + let old_rate = match state.info { + Some(ref info) => info.rate() as u64, + None => gst::SECOND_VAL, + }; + + // Update sample offset and accumulator based on the previous values and the + // sample rate change, if any + let old_sample_offset = state.sample_offset; + let sample_offset = old_sample_offset + .mul_div_floor(info.rate() as u64, old_rate) + .unwrap(); + + let old_sample_stop = state.sample_stop; + let sample_stop = + old_sample_stop.map(|v| v.mul_div_floor(info.rate() as u64, old_rate).unwrap()); + + let accumulator = + (sample_offset as f64).rem(2.0 * PI * (settings.freq as f64) / (info.rate() as f64)); + + *state = State { + info: Some(info), + sample_offset: sample_offset, + sample_stop: sample_stop, + accumulator: accumulator, + }; + + drop(state); + + let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); + + true + } + + // Called when starting, so we can initialize all stream-related state to its defaults + fn start(&self, element: &BaseSrc) -> bool { + // Reset state + *self.state.lock().unwrap() = Default::default(); + self.unlock_stop(element); + + gst_info!(self.cat, obj: element, "Started"); + + true + } + + // Called when shutting down the element so we can release all stream-related state + fn stop(&self, element: &BaseSrc) -> bool { + // Reset state + *self.state.lock().unwrap() = Default::default(); + self.unlock(element); + + gst_info!(self.cat, obj: element, "Stopped"); + + true + } + + fn query(&self, element: &BaseSrc, query: &mut gst::QueryRef) -> bool { + use gst::QueryView; + + match query.view_mut() { + // We only work in Push mode. In Pull mode, create() could be called with + // arbitrary offsets and we would have to produce for that specific offset + QueryView::Scheduling(ref mut q) => { + q.set(gst::SchedulingFlags::SEQUENTIAL, 1, -1, 0); + q.add_scheduling_modes(&[gst::PadMode::Push]); + return true; + } + // In Live mode we will have a latency equal to the number of samples in each buffer. + // We can't output samples before they were produced, and the last sample of a buffer + // is produced that much after the beginning, leading to this latency calculation + QueryView::Latency(ref mut q) => { + let settings = *self.settings.lock().unwrap(); + let state = self.state.lock().unwrap(); + + if let Some(ref info) = state.info { + let latency = gst::SECOND + .mul_div_floor(settings.samples_per_buffer as u64, info.rate() as u64) + .unwrap(); + gst_debug!(self.cat, obj: element, "Returning latency {}", latency); + q.set(settings.is_live, latency, gst::CLOCK_TIME_NONE); + return true; + } else { + return false; + } + } + _ => (), + } + BaseSrcBase::parent_query(element, query) + } + + // Creates the audio buffers + fn create( + &self, + element: &BaseSrc, + _offset: u64, + _length: u32, + ) -> Result { + // Keep a local copy of the values of all our properties at this very moment. This + // ensures that the mutex is never locked for long and the application wouldn't + // have to block until this function returns when getting/setting property values + let settings = *self.settings.lock().unwrap(); + + // Get a locked reference to our state, i.e. the input and output AudioInfo + let mut state = self.state.lock().unwrap(); + let info = match state.info { + None => { + gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); + return Err(gst::FlowReturn::NotNegotiated); + } + Some(ref info) => info.clone(), + }; + + // If a stop position is set (from a seek), only produce samples up to that + // point but at most samples_per_buffer samples per buffer + let n_samples = if let Some(sample_stop) = state.sample_stop { + if sample_stop <= state.sample_offset { + gst_log!(self.cat, obj: element, "At EOS"); + return Err(gst::FlowReturn::Eos); + } + + sample_stop - state.sample_offset + } else { + settings.samples_per_buffer as u64 + }; + + // Allocate a new buffer of the required size, update the metadata with the + // current timestamp and duration and then fill it according to the current + // caps + let mut buffer = + gst::Buffer::with_size((n_samples as usize) * (info.bpf() as usize)).unwrap(); + { + let buffer = buffer.get_mut().unwrap(); + + // Calculate the current timestamp (PTS) and the next one, + // and calculate the duration from the difference instead of + // simply the number of samples to prevent rounding errors + let pts = state + .sample_offset + .mul_div_floor(gst::SECOND_VAL, info.rate() as u64) + .unwrap() + .into(); + let next_pts: gst::ClockTime = (state.sample_offset + n_samples) + .mul_div_floor(gst::SECOND_VAL, info.rate() as u64) + .unwrap() + .into(); + buffer.set_pts(pts); + buffer.set_duration(next_pts - pts); + + // Map the buffer writable and create the actual samples + let mut map = buffer.map_writable().unwrap(); + let data = map.as_mut_slice(); + + if info.format() == gst_audio::AUDIO_FORMAT_F32 { + Self::process::( + data, + &mut state.accumulator, + settings.freq, + info.rate(), + info.channels(), + settings.volume, + ); + } else { + Self::process::( + data, + &mut state.accumulator, + settings.freq, + info.rate(), + info.channels(), + settings.volume, + ); + } + } + state.sample_offset += n_samples; + drop(state); + + // If we're live, we are waiting until the time of the last sample in our buffer has + // arrived. This is the very reason why we have to report that much latency. + // A real live-source would of course only allow us to have the data available after + // that latency, e.g. when capturing from a microphone, and no waiting from our side + // would be necessary.. + // + // Waiting happens based on the pipeline clock, which means that a real live source + // with its own clock would require various translations between the two clocks. + // This is out of scope for the tutorial though. + if element.is_live() { + let clock = match element.get_clock() { + None => return Ok(buffer), + Some(clock) => clock, + }; + + let segment = element + .get_segment() + .downcast::() + .unwrap(); + let base_time = element.get_base_time(); + let running_time = segment.to_running_time(buffer.get_pts() + buffer.get_duration()); + + // The last sample's clock time is the base time of the element plus the + // running time of the last sample + let wait_until = running_time + base_time; + if wait_until.is_none() { + return Ok(buffer); + } + + // Store the clock ID in our struct unless we're flushing anyway. + // This allows to asynchronously cancel the waiting from unlock() + // so that we immediately stop waiting on e.g. shutdown. + let mut clock_wait = self.clock_wait.lock().unwrap(); + if clock_wait.flushing { + gst_debug!(self.cat, obj: element, "Flushing"); + return Err(gst::FlowReturn::Flushing); + } + + let id = clock.new_single_shot_id(wait_until).unwrap(); + clock_wait.clock_id = Some(id.clone()); + drop(clock_wait); + + gst_log!( + self.cat, + obj: element, + "Waiting until {}, now {}", + wait_until, + clock.get_time() + ); + let (res, jitter) = id.wait(); + gst_log!( + self.cat, + obj: element, + "Waited res {:?} jitter {}", + res, + jitter + ); + self.clock_wait.lock().unwrap().clock_id.take(); + + // If the clock ID was unscheduled, unlock() was called + // and we should return Flushing immediately. + if res == gst::ClockReturn::Unscheduled { + gst_debug!(self.cat, obj: element, "Flushing"); + return Err(gst::FlowReturn::Flushing); + } + } + + gst_debug!(self.cat, obj: element, "Produced buffer {:?}", buffer); + + Ok(buffer) + } + + fn fixate(&self, element: &BaseSrc, caps: gst::Caps) -> gst::Caps { + // Fixate the caps. BaseSrc will do some fixation for us, but + // as we allow any rate between 1 and MAX it would fixate to 1. 1Hz + // is generally not a useful sample rate. + // + // We fixate to the closest integer value to 48kHz that is possible + // here, and for good measure also decide that the closest value to 1 + // channel is good. + let mut caps = gst::Caps::truncate(caps); + { + let caps = caps.make_mut(); + let s = caps.get_mut_structure(0).unwrap(); + s.fixate_field_nearest_int("rate", 48_000); + s.fixate_field_nearest_int("channels", 1); + } + + // Let BaseSrc fixate anything else for us. We could've alternatively have + // called Caps::fixate() here + element.parent_fixate(caps) + } + + fn is_seekable(&self, _element: &BaseSrc) -> bool { + true + } + + fn do_seek(&self, element: &BaseSrc, segment: &mut gst::Segment) -> bool { + // Handle seeking here. For Time and Default (sample offset) seeks we can + // do something and have to update our sample offset and accumulator accordingly. + // + // Also we should remember the stop time (so we can stop at that point), and if + // reverse playback is requested. These values will all be used during buffer creation + // and for calculating the timestamps, etc. + + if segment.get_rate() < 0.0 { + gst_error!(self.cat, obj: element, "Reverse playback not supported"); + return false; + } + + let settings = *self.settings.lock().unwrap(); + let mut state = self.state.lock().unwrap(); + + // We store sample_offset and sample_stop in nanoseconds if we + // don't know any sample rate yet. It will be converted correctly + // once a sample rate is known. + let rate = match state.info { + None => gst::SECOND_VAL, + Some(ref info) => info.rate() as u64, + }; + + if let Some(segment) = segment.downcast_ref::() { + use std::f64::consts::PI; + + let sample_offset = segment + .get_start() + .unwrap() + .mul_div_floor(rate, gst::SECOND_VAL) + .unwrap(); + + let sample_stop = segment + .get_stop() + .map(|v| v.mul_div_floor(rate, gst::SECOND_VAL).unwrap()); + + let accumulator = + (sample_offset as f64).rem(2.0 * PI * (settings.freq as f64) / (rate as f64)); + + gst_debug!( + self.cat, + obj: element, + "Seeked to {}-{:?} (accum: {}) for segment {:?}", + sample_offset, + sample_stop, + accumulator, + segment + ); + + *state = State { + info: state.info.clone(), + sample_offset: sample_offset, + sample_stop: sample_stop, + accumulator: accumulator, + }; + + true + } else if let Some(segment) = segment.downcast_ref::() { + use std::f64::consts::PI; + + if state.info.is_none() { + gst_error!( + self.cat, + obj: element, + "Can only seek in Default format if sample rate is known" + ); + return false; + } + + let sample_offset = segment.get_start().unwrap(); + let sample_stop = segment.get_stop().0; + + let accumulator = + (sample_offset as f64).rem(2.0 * PI * (settings.freq as f64) / (rate as f64)); + + gst_debug!( + self.cat, + obj: element, + "Seeked to {}-{:?} (accum: {}) for segment {:?}", + sample_offset, + sample_stop, + accumulator, + segment + ); + + *state = State { + info: state.info.clone(), + sample_offset: sample_offset, + sample_stop: sample_stop, + accumulator: accumulator, + }; + + true + } else { + gst_error!( + self.cat, + obj: element, + "Can't seek in format {:?}", + segment.get_format() + ); + + false + } + } + + fn unlock(&self, element: &BaseSrc) -> bool { + // This should unblock the create() function ASAP, so we + // just unschedule the clock it here, if any. + gst_debug!(self.cat, obj: element, "Unlocking"); + let mut clock_wait = self.clock_wait.lock().unwrap(); + if let Some(clock_id) = clock_wait.clock_id.take() { + clock_id.unschedule(); + } + clock_wait.flushing = true; + + true + } + + fn unlock_stop(&self, element: &BaseSrc) -> bool { + // This signals that unlocking is done, so we can reset + // all values again. + gst_debug!(self.cat, obj: element, "Unlock stop"); + let mut clock_wait = self.clock_wait.lock().unwrap(); + clock_wait.flushing = false; + + true + } +} + +// This zero-sized struct is containing the static metadata of our element. It is only necessary to +// be able to implement traits on it, but e.g. a plugin that registers multiple elements with the +// same code would use this struct to store information about the concrete element. An example of +// this would be a plugin that wraps around a library that has multiple decoders with the same API, +// but wants (as it should) a separate element registered for each decoder. +struct NdiSrcStatic; + +// The basic trait for registering the type: This returns a name for the type and registers the +// instance and class initializations functions with the type system, thus hooking everything +// together. +impl ImplTypeStatic for NdiSrcStatic { + fn get_name(&self) -> &str { + "NdiSrc" + } + + fn new(&self, element: &BaseSrc) -> Box> { + NdiSrc::new(element) + } + + fn class_init(&self, klass: &mut BaseSrcClass) { + NdiSrc::class_init(klass); + } +} + +// Registers the type for our element, and then registers in GStreamer under +// the name "ndisrc" for being able to instantiate it via e.g. +// gst::ElementFactory::make(). +pub fn register(plugin: &gst::Plugin) { + let type_ = register_type(NdiSrcStatic); + gst::Element::register(plugin, "ndisrc", 0, type_); +} From 3a28e7a9fb60959f19f389cd1a2f31fef62f5672 Mon Sep 17 00:00:00 2001 From: rubenrua Date: Mon, 9 Apr 2018 05:55:03 +0000 Subject: [PATCH 008/199] Add .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 327c7006..bbc567f8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ -/target/ +target **/*.rs.bk *~ From 6239c2b6b991a0ffa8643a4f5dad34b581d773c0 Mon Sep 17 00:00:00 2001 From: rubenrua Date: Mon, 9 Apr 2018 05:56:38 +0000 Subject: [PATCH 009/199] Add links to tutorial based in --- gst-plugin-ndi/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/gst-plugin-ndi/README.md b/gst-plugin-ndi/README.md index 7737d60a..b8a862fb 100644 --- a/gst-plugin-ndi/README.md +++ b/gst-plugin-ndi/README.md @@ -1,6 +1,12 @@ TODO ==== +See: + +https://coaxion.net/blog/2018/01/how-to-write-gstreamer-elements-in-rust-part-1-a-video-filter-for-converting-rgb-to-grayscale/ +https://coaxion.net/blog/2018/02/how-to-write-gstreamer-elements-in-rust-part-2-a-raw-audio-sine-wave-source/ + + Test ------- From 8d99d47f8115bbf122da8915e418daea1bdc22e8 Mon Sep 17 00:00:00 2001 From: rubenrua Date: Tue, 10 Apr 2018 15:25:01 +0200 Subject: [PATCH 010/199] Using correnct default values --- example/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example/src/main.rs b/example/src/main.rs index 5350c194..c1f1978f 100644 --- a/example/src/main.rs +++ b/example/src/main.rs @@ -54,8 +54,8 @@ fn main() { let p_ndi_name = CString::new("Galicaster NDI Receiver").unwrap(); let NDI_recv_create_desc = NDIlib_recv_create_v3_t { source_to_connect_to: *p_sources, - allow_video_fields: false, - bandwidth: NDIlib_recv_bandwidth_e_NDIlib_recv_bandwidth_lowest, + allow_video_fields: true, + bandwidth: NDIlib_recv_bandwidth_e_NDIlib_recv_bandwidth_highest, color_format: NDIlib_recv_color_format_e_NDIlib_recv_color_format_BGRX_BGRA, p_ndi_name: p_ndi_name.as_ptr(), //ptr::null(), }; From 20b6ae5ff679df425edb854a33348ab5a067cc01 Mon Sep 17 00:00:00 2001 From: rubenrua Date: Tue, 10 Apr 2018 17:49:21 +0200 Subject: [PATCH 011/199] Use Default Trait The default values must be reviewed --- example/src/main.rs | 51 ++++-------------------- example/src/ndilib.rs | 92 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 98 insertions(+), 45 deletions(-) diff --git a/example/src/main.rs b/example/src/main.rs index c1f1978f..a299687f 100644 --- a/example/src/main.rs +++ b/example/src/main.rs @@ -18,11 +18,7 @@ fn main() { } //TODO valores por defecto - let NDI_find_create_desc = NDIlib_find_create_t { - show_local_sources: false, - p_groups: ptr::null(), - p_extra_ips: ptr::null() - }; + let NDI_find_create_desc: NDIlib_find_create_t = Default::default(); let pNDI_find = NDIlib_find_create_v2(&NDI_find_create_desc); if pNDI_find.is_null() { println!("Cannot run NDI: NDIlib_find_create_v2 error."); @@ -54,10 +50,8 @@ fn main() { let p_ndi_name = CString::new("Galicaster NDI Receiver").unwrap(); let NDI_recv_create_desc = NDIlib_recv_create_v3_t { source_to_connect_to: *p_sources, - allow_video_fields: true, - bandwidth: NDIlib_recv_bandwidth_e_NDIlib_recv_bandwidth_highest, - color_format: NDIlib_recv_color_format_e_NDIlib_recv_color_format_BGRX_BGRA, - p_ndi_name: p_ndi_name.as_ptr(), //ptr::null(), + p_ndi_name: p_ndi_name.as_ptr(), + ..Default::default() }; @@ -71,10 +65,7 @@ fn main() { NDIlib_find_destroy(pNDI_find); // We are now going to mark this source as being on program output for tally purposes (but not on preview) - let tally_state = NDIlib_tally_t { - on_program: true, - on_preview: true, - }; + let tally_state: NDIlib_tally_t = Default::default(); NDIlib_recv_set_tally(pNDI_recv, &tally_state); @@ -92,39 +83,11 @@ fn main() { loop { - let video_frame = NDIlib_video_frame_v2_t { - xres: 0, - yres: 0, - FourCC: 0, - frame_rate_N: 0, - frame_rate_D: 0, - picture_aspect_ratio: 0.0, - frame_format_type: 0, - timecode: 0, - p_data: ptr::null(), - line_stride_in_bytes: 0, - p_metadata: ptr::null(), - timestamp: 0, - }; + let video_frame: NDIlib_video_frame_v2_t = Default::default(); + let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); + let metadata_frame: NDIlib_metadata_frame_t = Default::default(); - let audio_frame = NDIlib_audio_frame_v2_t { - sample_rate: 0, - no_channels: 0, - no_samples: 0, - timecode: 0, - p_data: ptr::null(), - channel_stride_in_bytes: 0, - p_metadata: ptr::null(), - timestamp: 0, - }; - - let metadata_frame = NDIlib_metadata_frame_t { - length: 0, - timecode: 0, - p_data: ptr::null(), - }; - let frame_type = NDIlib_recv_capture_v2(pNDI_recv, &video_frame, &audio_frame, &metadata_frame, 1000); diff --git a/example/src/ndilib.rs b/example/src/ndilib.rs index 49a32c0a..dbc2f140 100644 --- a/example/src/ndilib.rs +++ b/example/src/ndilib.rs @@ -1,5 +1,7 @@ #![allow(non_camel_case_types, non_upper_case_globals)] +use std::ptr; + #[link(name = "ndi")] extern "C" { pub fn NDIlib_initialize() -> bool; @@ -43,6 +45,17 @@ pub struct NDIlib_find_create_t { pub p_extra_ips: *const ::std::os::raw::c_char, } +impl Default for NDIlib_find_create_t { + fn default() -> Self { + NDIlib_find_create_t { + show_local_sources: true, + p_groups: ptr::null(), + p_extra_ips: ptr::null() + } + } +} + + #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct NDIlib_source_t { @@ -50,6 +63,15 @@ pub struct NDIlib_source_t { pub p_ip_address: *const ::std::os::raw::c_char, } +impl Default for NDIlib_source_t { + fn default() -> Self { + NDIlib_source_t { + p_ndi_name: ptr::null(), + p_ip_address: ptr::null() + } + } +} + //TODO review enum pub type NDIlib_frame_type_e = i32; @@ -89,6 +111,18 @@ pub struct NDIlib_recv_create_v3_t { pub p_ndi_name: *const ::std::os::raw::c_char, } +impl Default for NDIlib_recv_create_v3_t { + fn default() -> Self { + NDIlib_recv_create_v3_t { + source_to_connect_to: Default::default(), + allow_video_fields: true, + bandwidth: NDIlib_recv_bandwidth_e_NDIlib_recv_bandwidth_highest, + color_format: NDIlib_recv_color_format_e_NDIlib_recv_color_format_UYVY_BGRA, + p_ndi_name: ptr::null() + } + } +} + pub type NDIlib_recv_instance_t = *mut ::std::os::raw::c_void; @@ -99,14 +133,36 @@ pub struct NDIlib_tally_t { pub on_preview: bool, } +impl Default for NDIlib_tally_t { + fn default() -> Self { + NDIlib_tally_t { + on_program: false, + on_preview: false + } + } +} + #[repr(C)] #[derive(Debug,Copy,Clone)] pub struct NDIlib_metadata_frame_t { pub length: ::std::os::raw::c_int, - pub timecode: i64, + pub timecode: i64, pub p_data: *const ::std::os::raw::c_char, } + +impl Default for NDIlib_metadata_frame_t { + fn default() -> Self { + NDIlib_metadata_frame_t { + length: 0, + timecode: 0, //NDIlib_send_timecode_synthesize, + p_data: ptr::null() + } + } +} + + + #[repr(C)] #[derive(Debug,Copy,Clone)] pub struct NDIlib_video_frame_v2_t { @@ -124,6 +180,25 @@ pub struct NDIlib_video_frame_v2_t { pub timestamp: i64, } +impl Default for NDIlib_video_frame_v2_t { + fn default() -> Self { + NDIlib_video_frame_v2_t { + xres: 0, + yres: 0, + FourCC: 0, + frame_rate_N: 0, + frame_rate_D: 0, + picture_aspect_ratio: 0.0, + frame_format_type: 0, + timecode: 0, //NDIlib_send_timecode_synthesize, + p_data: ptr::null(), + line_stride_in_bytes: 0, + p_metadata: ptr::null(), + timestamp: 0 + } + } +} + #[repr(C)] #[derive(Debug,Copy,Clone)] @@ -137,3 +212,18 @@ pub struct NDIlib_audio_frame_v2_t { pub p_metadata: *const ::std::os::raw::c_char, pub timestamp: i64, } + +impl Default for NDIlib_audio_frame_v2_t { + fn default() -> Self { + NDIlib_audio_frame_v2_t { + sample_rate: 48000, + no_channels: 2, + no_samples: 0, + timecode: 0, //NDIlib_send_timecode_synthesize, + p_data: ptr::null(), + channel_stride_in_bytes: 0, + p_metadata: ptr::null(), + timestamp: 0 + } + } +} From 98db99924e277cc947a999157fffbc960a3f1e77 Mon Sep 17 00:00:00 2001 From: rubenrua Date: Tue, 10 Apr 2018 17:53:49 +0200 Subject: [PATCH 012/199] Execute rustfmt --- example/src/main.rs | 203 +++++++++++++++++++++--------------------- example/src/ndilib.rs | 79 ++++++++-------- 2 files changed, 142 insertions(+), 140 deletions(-) diff --git a/example/src/main.rs b/example/src/main.rs index a299687f..36257b9a 100644 --- a/example/src/main.rs +++ b/example/src/main.rs @@ -1,125 +1,128 @@ #![allow(non_upper_case_globals, non_snake_case)] - pub mod ndilib; use std::ptr; -use std::ffi::{CString, CStr}; +use std::ffi::{CStr, CString}; use ndilib::*; - fn main() { unsafe { - if !NDIlib_initialize() { - //TODO delete exits - println!("Cannot run NDI: NDIlib_initialize error."); - ::std::process::exit(1); - } + if !NDIlib_initialize() { + //TODO delete exits + println!("Cannot run NDI: NDIlib_initialize error."); + ::std::process::exit(1); + } - //TODO valores por defecto - let NDI_find_create_desc: NDIlib_find_create_t = Default::default(); - let pNDI_find = NDIlib_find_create_v2(&NDI_find_create_desc); - if pNDI_find.is_null() { - println!("Cannot run NDI: NDIlib_find_create_v2 error."); - ::std::process::exit(1); - } + //TODO valores por defecto + let NDI_find_create_desc: NDIlib_find_create_t = Default::default(); + let pNDI_find = NDIlib_find_create_v2(&NDI_find_create_desc); + if pNDI_find.is_null() { + println!("Cannot run NDI: NDIlib_find_create_v2 error."); + ::std::process::exit(1); + } - let mut no_sources: u32 = 0; - let mut p_sources = ptr::null(); - while no_sources == 0 { - p_sources = NDIlib_find_get_current_sources(pNDI_find, &mut no_sources as *mut u32); - } + let mut no_sources: u32 = 0; + let mut p_sources = ptr::null(); + while no_sources == 0 { + p_sources = NDIlib_find_get_current_sources(pNDI_find, &mut no_sources as *mut u32); + } + // We need at least one source + if p_sources.is_null() { + println!("Error getting NDIlib_find_get_current_sources."); + ::std::process::exit(1); + } - // We need at least one source - if p_sources.is_null() { - println!("Error getting NDIlib_find_get_current_sources."); - ::std::process::exit(1); - } + println!( + "no_source {}: Name '{}' Address '{}'", + no_sources, + CStr::from_ptr((*p_sources).p_ndi_name) + .to_string_lossy() + .into_owned(), + CStr::from_ptr((*p_sources).p_ip_address) + .to_string_lossy() + .into_owned() + ); - println!("no_source {}: Name '{}' Address '{}'", - no_sources, - CStr::from_ptr((*p_sources).p_ndi_name).to_string_lossy().into_owned(), - CStr::from_ptr((*p_sources).p_ip_address).to_string_lossy().into_owned() - ); + // We now have at least one source, so we create a receiver to look at it. + // We tell it that we prefer YCbCr video since it is more efficient for us. If the source has an alpha channel + // it will still be provided in BGRA + let p_ndi_name = CString::new("Galicaster NDI Receiver").unwrap(); + let NDI_recv_create_desc = NDIlib_recv_create_v3_t { + source_to_connect_to: *p_sources, + p_ndi_name: p_ndi_name.as_ptr(), + ..Default::default() + }; - // We now have at least one source, so we create a receiver to look at it. - // We tell it that we prefer YCbCr video since it is more efficient for us. If the source has an alpha channel - // it will still be provided in BGRA - let p_ndi_name = CString::new("Galicaster NDI Receiver").unwrap(); - let NDI_recv_create_desc = NDIlib_recv_create_v3_t { - source_to_connect_to: *p_sources, - p_ndi_name: p_ndi_name.as_ptr(), - ..Default::default() - }; + let pNDI_recv = NDIlib_recv_create_v3(&NDI_recv_create_desc); + if pNDI_recv.is_null() { + println!("Cannot run NDI: NDIlib_recv_create_v3 error."); + ::std::process::exit(1); + } + // Destroy the NDI finder. We needed to have access to the pointers to p_sources[0] + NDIlib_find_destroy(pNDI_find); - let pNDI_recv = NDIlib_recv_create_v3(&NDI_recv_create_desc); - if pNDI_recv.is_null() { - println!("Cannot run NDI: NDIlib_recv_create_v3 error."); - ::std::process::exit(1); - } + // We are now going to mark this source as being on program output for tally purposes (but not on preview) + let tally_state: NDIlib_tally_t = Default::default(); + NDIlib_recv_set_tally(pNDI_recv, &tally_state); - // Destroy the NDI finder. We needed to have access to the pointers to p_sources[0] - NDIlib_find_destroy(pNDI_find); + // Enable Hardwqre Decompression support if this support has it. Please read the caveats in the documentation + // regarding this. There are times in which it might reduce the performance although on small stream numbers + // it almost always yields the same or better performance. + let data = CString::new("").unwrap(); + let enable_hw_accel = NDIlib_metadata_frame_t { + length: data.to_bytes().len() as i32, + timecode: 0, + p_data: data.as_ptr(), + }; - // We are now going to mark this source as being on program output for tally purposes (but not on preview) - let tally_state: NDIlib_tally_t = Default::default(); - NDIlib_recv_set_tally(pNDI_recv, &tally_state); + NDIlib_recv_send_metadata(pNDI_recv, &enable_hw_accel); + loop { + let video_frame: NDIlib_video_frame_v2_t = Default::default(); + let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); + let metadata_frame: NDIlib_metadata_frame_t = Default::default(); - // Enable Hardwqre Decompression support if this support has it. Please read the caveats in the documentation - // regarding this. There are times in which it might reduce the performance although on small stream numbers - // it almost always yields the same or better performance. - let data = CString::new("").unwrap(); - let enable_hw_accel = NDIlib_metadata_frame_t { - length: data.to_bytes().len() as i32, - timecode: 0, - p_data: data.as_ptr(), - }; - - NDIlib_recv_send_metadata(pNDI_recv, &enable_hw_accel); - - loop { - - let video_frame: NDIlib_video_frame_v2_t = Default::default(); - let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); - let metadata_frame: NDIlib_metadata_frame_t = Default::default(); - - - let frame_type = NDIlib_recv_capture_v2(pNDI_recv, &video_frame, &audio_frame, &metadata_frame, 1000); - - - match frame_type { - NDIlib_frame_type_video => { - println!("Tengo video {:?}", - video_frame - ); - }, - NDIlib_frame_type_audio => { - println!("Tengo audio {:?}", - audio_frame - ); - }, - NDIlib_frame_type_metadata => { - println!("Tengo metadata {} '{}'", - metadata_frame.length, - CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned(), - ); - }, - NDIlib_frame_type_error => { - println!("Tengo error {} '{}'", - metadata_frame.length, - CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned(), - ); - break; - }, - _ => println!("Tengo {}", frame_type), - } - - } + let frame_type = NDIlib_recv_capture_v2( + pNDI_recv, + &video_frame, + &audio_frame, + &metadata_frame, + 1000, + ); + match frame_type { + NDIlib_frame_type_video => { + println!("Tengo video {:?}", video_frame); + } + NDIlib_frame_type_audio => { + println!("Tengo audio {:?}", audio_frame); + } + NDIlib_frame_type_metadata => { + println!( + "Tengo metadata {} '{}'", + metadata_frame.length, + CStr::from_ptr(metadata_frame.p_data) + .to_string_lossy() + .into_owned(), + ); + } + NDIlib_frame_type_error => { + println!( + "Tengo error {} '{}'", + metadata_frame.length, + CStr::from_ptr(metadata_frame.p_data) + .to_string_lossy() + .into_owned(), + ); + break; + } + _ => println!("Tengo {}", frame_type), + } + } } println!("Exit"); -} \ No newline at end of file +} diff --git a/example/src/ndilib.rs b/example/src/ndilib.rs index dbc2f140..0b2b022a 100644 --- a/example/src/ndilib.rs +++ b/example/src/ndilib.rs @@ -15,9 +15,7 @@ extern "C" { pub fn NDIlib_recv_create_v3( p_create_settings: *const NDIlib_recv_create_v3_t, ) -> NDIlib_recv_instance_t; - pub fn NDIlib_find_destroy( - p_instance: NDIlib_find_instance_t, - ); + pub fn NDIlib_find_destroy(p_instance: NDIlib_find_instance_t); pub fn NDIlib_recv_set_tally( p_instance: NDIlib_recv_instance_t, p_tally: *const NDIlib_tally_t, @@ -47,15 +45,14 @@ pub struct NDIlib_find_create_t { impl Default for NDIlib_find_create_t { fn default() -> Self { - NDIlib_find_create_t { + NDIlib_find_create_t { show_local_sources: true, p_groups: ptr::null(), - p_extra_ips: ptr::null() + p_extra_ips: ptr::null(), } } } - #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct NDIlib_source_t { @@ -64,15 +61,14 @@ pub struct NDIlib_source_t { } impl Default for NDIlib_source_t { - fn default() -> Self { - NDIlib_source_t { + fn default() -> Self { + NDIlib_source_t { p_ndi_name: ptr::null(), - p_ip_address: ptr::null() + p_ip_address: ptr::null(), } } } - //TODO review enum pub type NDIlib_frame_type_e = i32; pub const NDIlib_frame_type_none: NDIlib_frame_type_e = 0; @@ -82,27 +78,35 @@ pub const NDIlib_frame_type_metadata: NDIlib_frame_type_e = 3; pub const NDIlib_frame_type_error: NDIlib_frame_type_e = 4; pub const NDIlib_frame_type_status_change: NDIlib_frame_type_e = 100; - pub type NDIlib_recv_bandwidth_e = i32; -pub const NDIlib_recv_bandwidth_e_NDIlib_recv_bandwidth_metadata_only: NDIlib_recv_bandwidth_e = -10; +pub const NDIlib_recv_bandwidth_e_NDIlib_recv_bandwidth_metadata_only: NDIlib_recv_bandwidth_e = + -10; pub const NDIlib_recv_bandwidth_e_NDIlib_recv_bandwidth_audio_only: NDIlib_recv_bandwidth_e = 10; pub const NDIlib_recv_bandwidth_e_NDIlib_recv_bandwidth_lowest: NDIlib_recv_bandwidth_e = 0; pub const NDIlib_recv_bandwidth_e_NDIlib_recv_bandwidth_highest: NDIlib_recv_bandwidth_e = 100; - pub type NDIlib_recv_color_format_e = u32; -pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_BGRX_BGRA: NDIlib_recv_color_format_e = 0; -pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_UYVY_BGRA: NDIlib_recv_color_format_e = 1; -pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_RGBX_RGBA: NDIlib_recv_color_format_e = 2; -pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_UYVY_RGBA: NDIlib_recv_color_format_e = 3; -pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_fastest: NDIlib_recv_color_format_e = 100; -pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_e_BGRX_BGRA: NDIlib_recv_color_format_e = 0; -pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_e_UYVY_BGRA: NDIlib_recv_color_format_e = 1; -pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_e_RGBX_RGBA: NDIlib_recv_color_format_e = 2; -pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_e_UYVY_RGBA: NDIlib_recv_color_format_e = 3; +pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_BGRX_BGRA: + NDIlib_recv_color_format_e = 0; +pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_UYVY_BGRA: + NDIlib_recv_color_format_e = 1; +pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_RGBX_RGBA: + NDIlib_recv_color_format_e = 2; +pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_UYVY_RGBA: + NDIlib_recv_color_format_e = 3; +pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_fastest: NDIlib_recv_color_format_e = + 100; +pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_e_BGRX_BGRA: + NDIlib_recv_color_format_e = 0; +pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_e_UYVY_BGRA: + NDIlib_recv_color_format_e = 1; +pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_e_RGBX_RGBA: + NDIlib_recv_color_format_e = 2; +pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_e_UYVY_RGBA: + NDIlib_recv_color_format_e = 3; #[repr(C)] -#[derive(Debug,Copy,Clone)] +#[derive(Debug, Copy, Clone)] pub struct NDIlib_recv_create_v3_t { pub source_to_connect_to: NDIlib_source_t, pub color_format: NDIlib_recv_color_format_e, @@ -112,22 +116,21 @@ pub struct NDIlib_recv_create_v3_t { } impl Default for NDIlib_recv_create_v3_t { - fn default() -> Self { + fn default() -> Self { NDIlib_recv_create_v3_t { source_to_connect_to: Default::default(), allow_video_fields: true, bandwidth: NDIlib_recv_bandwidth_e_NDIlib_recv_bandwidth_highest, color_format: NDIlib_recv_color_format_e_NDIlib_recv_color_format_UYVY_BGRA, - p_ndi_name: ptr::null() + p_ndi_name: ptr::null(), } } } pub type NDIlib_recv_instance_t = *mut ::std::os::raw::c_void; - #[repr(C)] -#[derive(Debug,Copy,Clone)] +#[derive(Debug, Copy, Clone)] pub struct NDIlib_tally_t { pub on_program: bool, pub on_preview: bool, @@ -137,34 +140,31 @@ impl Default for NDIlib_tally_t { fn default() -> Self { NDIlib_tally_t { on_program: false, - on_preview: false + on_preview: false, } } } #[repr(C)] -#[derive(Debug,Copy,Clone)] +#[derive(Debug, Copy, Clone)] pub struct NDIlib_metadata_frame_t { pub length: ::std::os::raw::c_int, - pub timecode: i64, + pub timecode: i64, pub p_data: *const ::std::os::raw::c_char, } - impl Default for NDIlib_metadata_frame_t { fn default() -> Self { NDIlib_metadata_frame_t { length: 0, timecode: 0, //NDIlib_send_timecode_synthesize, - p_data: ptr::null() + p_data: ptr::null(), } } } - - #[repr(C)] -#[derive(Debug,Copy,Clone)] +#[derive(Debug, Copy, Clone)] pub struct NDIlib_video_frame_v2_t { pub xres: ::std::os::raw::c_int, pub yres: ::std::os::raw::c_int, @@ -190,18 +190,17 @@ impl Default for NDIlib_video_frame_v2_t { frame_rate_D: 0, picture_aspect_ratio: 0.0, frame_format_type: 0, - timecode: 0, //NDIlib_send_timecode_synthesize, + timecode: 0, //NDIlib_send_timecode_synthesize, p_data: ptr::null(), line_stride_in_bytes: 0, p_metadata: ptr::null(), - timestamp: 0 + timestamp: 0, } } } - #[repr(C)] -#[derive(Debug,Copy,Clone)] +#[derive(Debug, Copy, Clone)] pub struct NDIlib_audio_frame_v2_t { pub sample_rate: ::std::os::raw::c_int, pub no_channels: ::std::os::raw::c_int, @@ -223,7 +222,7 @@ impl Default for NDIlib_audio_frame_v2_t { p_data: ptr::null(), channel_stride_in_bytes: 0, p_metadata: ptr::null(), - timestamp: 0 + timestamp: 0, } } } From 755fda63d3861768d5e0f65536ee0fd86dfb4d8e Mon Sep 17 00:00:00 2001 From: rubenrua Date: Tue, 10 Apr 2018 18:35:39 +0200 Subject: [PATCH 013/199] Define remaining enums --- example/src/ndilib.rs | 74 +++++++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 35 deletions(-) diff --git a/example/src/ndilib.rs b/example/src/ndilib.rs index 0b2b022a..398e4e79 100644 --- a/example/src/ndilib.rs +++ b/example/src/ndilib.rs @@ -79,31 +79,35 @@ pub const NDIlib_frame_type_error: NDIlib_frame_type_e = 4; pub const NDIlib_frame_type_status_change: NDIlib_frame_type_e = 100; pub type NDIlib_recv_bandwidth_e = i32; -pub const NDIlib_recv_bandwidth_e_NDIlib_recv_bandwidth_metadata_only: NDIlib_recv_bandwidth_e = - -10; -pub const NDIlib_recv_bandwidth_e_NDIlib_recv_bandwidth_audio_only: NDIlib_recv_bandwidth_e = 10; -pub const NDIlib_recv_bandwidth_e_NDIlib_recv_bandwidth_lowest: NDIlib_recv_bandwidth_e = 0; -pub const NDIlib_recv_bandwidth_e_NDIlib_recv_bandwidth_highest: NDIlib_recv_bandwidth_e = 100; +pub const NDIlib_recv_bandwidth_metadata_only: NDIlib_recv_bandwidth_e = -10; +pub const NDIlib_recv_bandwidth_audio_only: NDIlib_recv_bandwidth_e = 10; +pub const NDIlib_recv_bandwidth_lowest: NDIlib_recv_bandwidth_e = 0; +pub const NDIlib_recv_bandwidth_highest: NDIlib_recv_bandwidth_e = 100; pub type NDIlib_recv_color_format_e = u32; -pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_BGRX_BGRA: - NDIlib_recv_color_format_e = 0; -pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_UYVY_BGRA: - NDIlib_recv_color_format_e = 1; -pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_RGBX_RGBA: - NDIlib_recv_color_format_e = 2; -pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_UYVY_RGBA: - NDIlib_recv_color_format_e = 3; -pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_fastest: NDIlib_recv_color_format_e = - 100; -pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_e_BGRX_BGRA: - NDIlib_recv_color_format_e = 0; -pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_e_UYVY_BGRA: - NDIlib_recv_color_format_e = 1; -pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_e_RGBX_RGBA: - NDIlib_recv_color_format_e = 2; -pub const NDIlib_recv_color_format_e_NDIlib_recv_color_format_e_UYVY_RGBA: - NDIlib_recv_color_format_e = 3; +pub const NDIlib_recv_color_format_BGRX_BGRA: NDIlib_recv_color_format_e = 0; +pub const NDIlib_recv_color_format_UYVY_BGRA: NDIlib_recv_color_format_e = 1; +pub const NDIlib_recv_color_format_RGBX_RGBA: NDIlib_recv_color_format_e = 2; +pub const NDIlib_recv_color_format_UYVY_RGBA: NDIlib_recv_color_format_e = 3; +pub const NDIlib_recv_color_format_fastest: NDIlib_recv_color_format_e = 100; + +pub type NDIlib_FourCC_type_e = u32; +pub const NDIlib_FourCC_type_UYVY: NDIlib_FourCC_type_e = 1498831189; +pub const NDIlib_FourCC_type_BGRA: NDIlib_FourCC_type_e = 1095911234; +pub const NDIlib_FourCC_type_BGRX: NDIlib_FourCC_type_e = 1481787202; +pub const NDIlib_FourCC_type_RGBA: NDIlib_FourCC_type_e = 1094862674; +pub const NDIlib_FourCC_type_RGBX: NDIlib_FourCC_type_e = 1480738642; +pub const NDIlib_FourCC_type_UYVA: NDIlib_FourCC_type_e = 1096178005; + +pub type NDIlib_frame_format_type_e = u32; +pub const NDIlib_frame_format_type_progressive: NDIlib_frame_format_type_e = 1; +pub const NDIlib_frame_format_type_interleaved: NDIlib_frame_format_type_e = 0; +pub const NDIlib_frame_format_type_field_0: NDIlib_frame_format_type_e = 2; +pub const NDIlib_frame_format_type_field_1: NDIlib_frame_format_type_e = 3; + +pub const NDIlib_send_timecode_synthesize: i64 = ::std::i64::MAX; +pub const NDIlib_send_timecode_empty: i64 = 0; +pub const NDIlib_recv_timestamp_undefined: i64 = ::std::i64::MAX; #[repr(C)] #[derive(Debug, Copy, Clone)] @@ -120,8 +124,8 @@ impl Default for NDIlib_recv_create_v3_t { NDIlib_recv_create_v3_t { source_to_connect_to: Default::default(), allow_video_fields: true, - bandwidth: NDIlib_recv_bandwidth_e_NDIlib_recv_bandwidth_highest, - color_format: NDIlib_recv_color_format_e_NDIlib_recv_color_format_UYVY_BGRA, + bandwidth: NDIlib_recv_bandwidth_highest, + color_format: NDIlib_recv_color_format_UYVY_BGRA, p_ndi_name: ptr::null(), } } @@ -168,11 +172,11 @@ impl Default for NDIlib_metadata_frame_t { pub struct NDIlib_video_frame_v2_t { pub xres: ::std::os::raw::c_int, pub yres: ::std::os::raw::c_int, - pub FourCC: u32, //TODO enum + pub FourCC: NDIlib_FourCC_type_e, pub frame_rate_N: ::std::os::raw::c_int, pub frame_rate_D: ::std::os::raw::c_int, pub picture_aspect_ratio: ::std::os::raw::c_float, - pub frame_format_type: u32, //TODO enum + pub frame_format_type: NDIlib_frame_format_type_e, pub timecode: i64, pub p_data: *const ::std::os::raw::c_char, pub line_stride_in_bytes: ::std::os::raw::c_int, @@ -185,16 +189,16 @@ impl Default for NDIlib_video_frame_v2_t { NDIlib_video_frame_v2_t { xres: 0, yres: 0, - FourCC: 0, - frame_rate_N: 0, - frame_rate_D: 0, + FourCC: NDIlib_FourCC_type_UYVY, + frame_rate_N: 30000, + frame_rate_D: 1001, picture_aspect_ratio: 0.0, - frame_format_type: 0, - timecode: 0, //NDIlib_send_timecode_synthesize, + frame_format_type: NDIlib_frame_format_type_progressive, + timecode: NDIlib_send_timecode_synthesize, p_data: ptr::null(), line_stride_in_bytes: 0, p_metadata: ptr::null(), - timestamp: 0, + timestamp: NDIlib_send_timecode_empty, } } } @@ -218,11 +222,11 @@ impl Default for NDIlib_audio_frame_v2_t { sample_rate: 48000, no_channels: 2, no_samples: 0, - timecode: 0, //NDIlib_send_timecode_synthesize, + timecode: NDIlib_send_timecode_synthesize, p_data: ptr::null(), channel_stride_in_bytes: 0, p_metadata: ptr::null(), - timestamp: 0, + timestamp: NDIlib_send_timecode_empty, } } } From 1d3371974491866686d5a77f93848697cb5d6e1f Mon Sep 17 00:00:00 2001 From: rubenrua Date: Tue, 10 Apr 2018 18:49:34 +0200 Subject: [PATCH 014/199] Using RUST enums With repr --- example/src/main.rs | 10 +++--- example/src/ndilib.rs | 84 +++++++++++++++++++++++++------------------ 2 files changed, 54 insertions(+), 40 deletions(-) diff --git a/example/src/main.rs b/example/src/main.rs index 36257b9a..a2f07a45 100644 --- a/example/src/main.rs +++ b/example/src/main.rs @@ -95,13 +95,13 @@ fn main() { ); match frame_type { - NDIlib_frame_type_video => { + NDIlib_frame_type_e::NDIlib_frame_type_video => { println!("Tengo video {:?}", video_frame); } - NDIlib_frame_type_audio => { + NDIlib_frame_type_e::NDIlib_frame_type_audio => { println!("Tengo audio {:?}", audio_frame); } - NDIlib_frame_type_metadata => { + NDIlib_frame_type_e::NDIlib_frame_type_metadata => { println!( "Tengo metadata {} '{}'", metadata_frame.length, @@ -110,7 +110,7 @@ fn main() { .into_owned(), ); } - NDIlib_frame_type_error => { + NDIlib_frame_type_e::NDIlib_frame_type_error => { println!( "Tengo error {} '{}'", metadata_frame.length, @@ -120,7 +120,7 @@ fn main() { ); break; } - _ => println!("Tengo {}", frame_type), + _ => println!("Tengo {:?}", frame_type), } } } diff --git a/example/src/ndilib.rs b/example/src/ndilib.rs index 398e4e79..6474e153 100644 --- a/example/src/ndilib.rs +++ b/example/src/ndilib.rs @@ -69,41 +69,55 @@ impl Default for NDIlib_source_t { } } -//TODO review enum -pub type NDIlib_frame_type_e = i32; -pub const NDIlib_frame_type_none: NDIlib_frame_type_e = 0; -pub const NDIlib_frame_type_video: NDIlib_frame_type_e = 1; -pub const NDIlib_frame_type_audio: NDIlib_frame_type_e = 2; -pub const NDIlib_frame_type_metadata: NDIlib_frame_type_e = 3; -pub const NDIlib_frame_type_error: NDIlib_frame_type_e = 4; -pub const NDIlib_frame_type_status_change: NDIlib_frame_type_e = 100; +#[repr(i32)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum NDIlib_frame_type_e { + NDIlib_frame_type_none = 0, + NDIlib_frame_type_video = 1, + NDIlib_frame_type_audio = 2, + NDIlib_frame_type_metadata = 3, + NDIlib_frame_type_error = 4, + NDIlib_frame_type_status_change = 100, +} -pub type NDIlib_recv_bandwidth_e = i32; -pub const NDIlib_recv_bandwidth_metadata_only: NDIlib_recv_bandwidth_e = -10; -pub const NDIlib_recv_bandwidth_audio_only: NDIlib_recv_bandwidth_e = 10; -pub const NDIlib_recv_bandwidth_lowest: NDIlib_recv_bandwidth_e = 0; -pub const NDIlib_recv_bandwidth_highest: NDIlib_recv_bandwidth_e = 100; +#[repr(i32)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum NDIlib_recv_bandwidth_e { + NDIlib_recv_bandwidth_metadata_only = -10, + NDIlib_recv_bandwidth_audio_only = 10, + NDIlib_recv_bandwidth_lowest = 0, + NDIlib_recv_bandwidth_highest = 100, +} -pub type NDIlib_recv_color_format_e = u32; -pub const NDIlib_recv_color_format_BGRX_BGRA: NDIlib_recv_color_format_e = 0; -pub const NDIlib_recv_color_format_UYVY_BGRA: NDIlib_recv_color_format_e = 1; -pub const NDIlib_recv_color_format_RGBX_RGBA: NDIlib_recv_color_format_e = 2; -pub const NDIlib_recv_color_format_UYVY_RGBA: NDIlib_recv_color_format_e = 3; -pub const NDIlib_recv_color_format_fastest: NDIlib_recv_color_format_e = 100; +#[repr(u32)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum NDIlib_recv_color_format_e { + NDIlib_recv_color_format_BGRX_BGRA = 0, + NDIlib_recv_color_format_UYVY_BGRA = 1, + NDIlib_recv_color_format_RGBX_RGBA = 2, + NDIlib_recv_color_format_UYVY_RGBA = 3, + NDIlib_recv_color_format_fastest = 100, +} -pub type NDIlib_FourCC_type_e = u32; -pub const NDIlib_FourCC_type_UYVY: NDIlib_FourCC_type_e = 1498831189; -pub const NDIlib_FourCC_type_BGRA: NDIlib_FourCC_type_e = 1095911234; -pub const NDIlib_FourCC_type_BGRX: NDIlib_FourCC_type_e = 1481787202; -pub const NDIlib_FourCC_type_RGBA: NDIlib_FourCC_type_e = 1094862674; -pub const NDIlib_FourCC_type_RGBX: NDIlib_FourCC_type_e = 1480738642; -pub const NDIlib_FourCC_type_UYVA: NDIlib_FourCC_type_e = 1096178005; +#[repr(u32)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum NDIlib_FourCC_type_e { + NDIlib_FourCC_type_UYVY = 1498831189, + NDIlib_FourCC_type_BGRA = 1095911234, + NDIlib_FourCC_type_BGRX = 1481787202, + NDIlib_FourCC_type_RGBA = 1094862674, + NDIlib_FourCC_type_RGBX = 1480738642, + NDIlib_FourCC_type_UYVA = 1096178005, +} -pub type NDIlib_frame_format_type_e = u32; -pub const NDIlib_frame_format_type_progressive: NDIlib_frame_format_type_e = 1; -pub const NDIlib_frame_format_type_interleaved: NDIlib_frame_format_type_e = 0; -pub const NDIlib_frame_format_type_field_0: NDIlib_frame_format_type_e = 2; -pub const NDIlib_frame_format_type_field_1: NDIlib_frame_format_type_e = 3; +#[repr(u32)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum NDIlib_frame_format_type_e { + NDIlib_frame_format_type_progressive = 1, + NDIlib_frame_format_type_interleaved = 0, + NDIlib_frame_format_type_field_0 = 2, + NDIlib_frame_format_type_field_1 = 3, +} pub const NDIlib_send_timecode_synthesize: i64 = ::std::i64::MAX; pub const NDIlib_send_timecode_empty: i64 = 0; @@ -124,8 +138,8 @@ impl Default for NDIlib_recv_create_v3_t { NDIlib_recv_create_v3_t { source_to_connect_to: Default::default(), allow_video_fields: true, - bandwidth: NDIlib_recv_bandwidth_highest, - color_format: NDIlib_recv_color_format_UYVY_BGRA, + bandwidth: NDIlib_recv_bandwidth_e::NDIlib_recv_bandwidth_highest, + color_format: NDIlib_recv_color_format_e::NDIlib_recv_color_format_UYVY_BGRA, p_ndi_name: ptr::null(), } } @@ -189,11 +203,11 @@ impl Default for NDIlib_video_frame_v2_t { NDIlib_video_frame_v2_t { xres: 0, yres: 0, - FourCC: NDIlib_FourCC_type_UYVY, + FourCC: NDIlib_FourCC_type_e::NDIlib_FourCC_type_UYVY, frame_rate_N: 30000, frame_rate_D: 1001, picture_aspect_ratio: 0.0, - frame_format_type: NDIlib_frame_format_type_progressive, + frame_format_type: NDIlib_frame_format_type_e::NDIlib_frame_format_type_progressive, timecode: NDIlib_send_timecode_synthesize, p_data: ptr::null(), line_stride_in_bytes: 0, From 8229082cc18b989140be8d48a92317fbe3b18b3f Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Tue, 10 Apr 2018 18:50:44 +0200 Subject: [PATCH 015/199] Added license --- gst-plugin-ndi/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gst-plugin-ndi/Cargo.toml b/gst-plugin-ndi/Cargo.toml index 892ab255..df0c515c 100644 --- a/gst-plugin-ndi/Cargo.toml +++ b/gst-plugin-ndi/Cargo.toml @@ -3,7 +3,7 @@ name = "gst-plugin-ndi" version = "0.1.0" authors = ["Ruben Gonzalez "] repository = "https://gitlab.teltek.es/rubenrua/ndi-rs.git" -license = "unknow" # TODO MIT/Apache-2.0 +license = "LGPL" [dependencies] gst-plugin = { git = "https://github.com/sdroege/gst-plugin-rs" } From a3067f0dd5fd2d96ddc19a28e572ae86bfb4b3bf Mon Sep 17 00:00:00 2001 From: rubenrua Date: Thu, 12 Apr 2018 15:14:59 +0200 Subject: [PATCH 016/199] Add prerequisites. See https://github.com/sdroege/gstreamer-rs#installation-linux --- gst-plugin-ndi/README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/gst-plugin-ndi/README.md b/gst-plugin-ndi/README.md index b8a862fb..6462fc1f 100644 --- a/gst-plugin-ndi/README.md +++ b/gst-plugin-ndi/README.md @@ -6,6 +6,16 @@ See: https://coaxion.net/blog/2018/01/how-to-write-gstreamer-elements-in-rust-part-1-a-video-filter-for-converting-rgb-to-grayscale/ https://coaxion.net/blog/2018/02/how-to-write-gstreamer-elements-in-rust-part-2-a-raw-audio-sine-wave-source/ +Before cargo build install: + +``` +$ apt-get install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \ + gstreamer1.0-plugins-base gstreamer1.0-plugins-good \ + gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly \ + gstreamer1.0-libav libgstrtspserver-1.0-dev +``` + + Test ------- From 2aed66ba0f95bb61720fec9e65352c4c3fed780e Mon Sep 17 00:00:00 2001 From: rubenrua Date: Thu, 12 Apr 2018 15:17:02 +0200 Subject: [PATCH 017/199] Using deps cte version --- gst-plugin-ndi/Cargo.toml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gst-plugin-ndi/Cargo.toml b/gst-plugin-ndi/Cargo.toml index 892ab255..fa56acce 100644 --- a/gst-plugin-ndi/Cargo.toml +++ b/gst-plugin-ndi/Cargo.toml @@ -6,12 +6,12 @@ repository = "https://gitlab.teltek.es/rubenrua/ndi-rs.git" license = "unknow" # TODO MIT/Apache-2.0 [dependencies] -gst-plugin = { git = "https://github.com/sdroege/gst-plugin-rs" } -glib = { git = "https://github.com/gtk-rs/glib" } -gstreamer = { git = "https://github.com/sdroege/gstreamer-rs" } -gstreamer-base = { git = "https://github.com/sdroege/gstreamer-rs" } -gstreamer-video = { git = "https://github.com/sdroege/gstreamer-rs" } -gstreamer-audio = { git = "https://github.com/sdroege/gstreamer-rs" } +gst-plugin = "0.2" +glib = "0.5" +gstreamer = "0.11" +gstreamer-base = "0.11" +gstreamer-video = "0.11" +gstreamer-audio = "0.11" byte-slice-cast = "0.1" # TODO delete num-traits = "0.2" # TODO delete From b0f9b7fae960f870c3bc1bcdc978bf23328138b0 Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Thu, 12 Apr 2018 16:54:36 +0200 Subject: [PATCH 018/199] Commented unused seek --- gst-plugin-ndi/src/ndisrc.rs | 212 +++++++++++++++++------------------ 1 file changed, 106 insertions(+), 106 deletions(-) diff --git a/gst-plugin-ndi/src/ndisrc.rs b/gst-plugin-ndi/src/ndisrc.rs index e4bc72ee..a643cb84 100644 --- a/gst-plugin-ndi/src/ndisrc.rs +++ b/gst-plugin-ndi/src/ndisrc.rs @@ -666,114 +666,114 @@ impl BaseSrcImpl for NdiSrc { } fn is_seekable(&self, _element: &BaseSrc) -> bool { - true + false } - fn do_seek(&self, element: &BaseSrc, segment: &mut gst::Segment) -> bool { - // Handle seeking here. For Time and Default (sample offset) seeks we can - // do something and have to update our sample offset and accumulator accordingly. - // - // Also we should remember the stop time (so we can stop at that point), and if - // reverse playback is requested. These values will all be used during buffer creation - // and for calculating the timestamps, etc. - - if segment.get_rate() < 0.0 { - gst_error!(self.cat, obj: element, "Reverse playback not supported"); - return false; - } - - let settings = *self.settings.lock().unwrap(); - let mut state = self.state.lock().unwrap(); - - // We store sample_offset and sample_stop in nanoseconds if we - // don't know any sample rate yet. It will be converted correctly - // once a sample rate is known. - let rate = match state.info { - None => gst::SECOND_VAL, - Some(ref info) => info.rate() as u64, - }; - - if let Some(segment) = segment.downcast_ref::() { - use std::f64::consts::PI; - - let sample_offset = segment - .get_start() - .unwrap() - .mul_div_floor(rate, gst::SECOND_VAL) - .unwrap(); - - let sample_stop = segment - .get_stop() - .map(|v| v.mul_div_floor(rate, gst::SECOND_VAL).unwrap()); - - let accumulator = - (sample_offset as f64).rem(2.0 * PI * (settings.freq as f64) / (rate as f64)); - - gst_debug!( - self.cat, - obj: element, - "Seeked to {}-{:?} (accum: {}) for segment {:?}", - sample_offset, - sample_stop, - accumulator, - segment - ); - - *state = State { - info: state.info.clone(), - sample_offset: sample_offset, - sample_stop: sample_stop, - accumulator: accumulator, - }; - - true - } else if let Some(segment) = segment.downcast_ref::() { - use std::f64::consts::PI; - - if state.info.is_none() { - gst_error!( - self.cat, - obj: element, - "Can only seek in Default format if sample rate is known" - ); - return false; - } - - let sample_offset = segment.get_start().unwrap(); - let sample_stop = segment.get_stop().0; - - let accumulator = - (sample_offset as f64).rem(2.0 * PI * (settings.freq as f64) / (rate as f64)); - - gst_debug!( - self.cat, - obj: element, - "Seeked to {}-{:?} (accum: {}) for segment {:?}", - sample_offset, - sample_stop, - accumulator, - segment - ); - - *state = State { - info: state.info.clone(), - sample_offset: sample_offset, - sample_stop: sample_stop, - accumulator: accumulator, - }; - - true - } else { - gst_error!( - self.cat, - obj: element, - "Can't seek in format {:?}", - segment.get_format() - ); - - false - } - } + // fn do_seek(&self, element: &BaseSrc, segment: &mut gst::Segment) -> bool { + // // Handle seeking here. For Time and Default (sample offset) seeks we can + // // do something and have to update our sample offset and accumulator accordingly. + // // + // // Also we should remember the stop time (so we can stop at that point), and if + // // reverse playback is requested. These values will all be used during buffer creation + // // and for calculating the timestamps, etc. + // + // if segment.get_rate() < 0.0 { + // gst_error!(self.cat, obj: element, "Reverse playback not supported"); + // return false; + // } + // + // let settings = *self.settings.lock().unwrap(); + // let mut state = self.state.lock().unwrap(); + // + // // We store sample_offset and sample_stop in nanoseconds if we + // // don't know any sample rate yet. It will be converted correctly + // // once a sample rate is known. + // let rate = match state.info { + // None => gst::SECOND_VAL, + // Some(ref info) => info.rate() as u64, + // }; + // + // if let Some(segment) = segment.downcast_ref::() { + // use std::f64::consts::PI; + // + // let sample_offset = segment + // .get_start() + // .unwrap() + // .mul_div_floor(rate, gst::SECOND_VAL) + // .unwrap(); + // + // let sample_stop = segment + // .get_stop() + // .map(|v| v.mul_div_floor(rate, gst::SECOND_VAL).unwrap()); + // + // let accumulator = + // (sample_offset as f64).rem(2.0 * PI * (settings.freq as f64) / (rate as f64)); + // + // gst_debug!( + // self.cat, + // obj: element, + // "Seeked to {}-{:?} (accum: {}) for segment {:?}", + // sample_offset, + // sample_stop, + // accumulator, + // segment + // ); + // + // *state = State { + // info: state.info.clone(), + // sample_offset: sample_offset, + // sample_stop: sample_stop, + // accumulator: accumulator, + // }; + // + // true + // } else if let Some(segment) = segment.downcast_ref::() { + // use std::f64::consts::PI; + // + // if state.info.is_none() { + // gst_error!( + // self.cat, + // obj: element, + // "Can only seek in Default format if sample rate is known" + // ); + // return false; + // } + // + // let sample_offset = segment.get_start().unwrap(); + // let sample_stop = segment.get_stop().0; + // + // let accumulator = + // (sample_offset as f64).rem(2.0 * PI * (settings.freq as f64) / (rate as f64)); + // + // gst_debug!( + // self.cat, + // obj: element, + // "Seeked to {}-{:?} (accum: {}) for segment {:?}", + // sample_offset, + // sample_stop, + // accumulator, + // segment + // ); + // + // *state = State { + // info: state.info.clone(), + // sample_offset: sample_offset, + // sample_stop: sample_stop, + // accumulator: accumulator, + // }; + // + // true + // } else { + // gst_error!( + // self.cat, + // obj: element, + // "Can't seek in format {:?}", + // segment.get_format() + // ); + // + // false + // } + // } fn unlock(&self, element: &BaseSrc) -> bool { // This should unblock the create() function ASAP, so we From 227d750305e8d82eca56637fc9fe8d9e540db454 Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Thu, 12 Apr 2018 17:17:59 +0200 Subject: [PATCH 019/199] Added property stream-name --- gst-plugin-ndi/src/ndisrc.rs | 40 +++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/gst-plugin-ndi/src/ndisrc.rs b/gst-plugin-ndi/src/ndisrc.rs index a643cb84..9323b0d2 100644 --- a/gst-plugin-ndi/src/ndisrc.rs +++ b/gst-plugin-ndi/src/ndisrc.rs @@ -34,8 +34,9 @@ const DEFAULT_MUTE: bool = false; const DEFAULT_IS_LIVE: bool = false; // Property value storage -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] struct Settings { + stream_name: String, samples_per_buffer: u32, freq: u32, volume: f64, @@ -46,6 +47,7 @@ struct Settings { impl Default for Settings { fn default() -> Self { Settings { + stream_name: String::from("Fixed ndi stream name"), samples_per_buffer: DEFAULT_SAMPLES_PER_BUFFER, freq: DEFAULT_FREQ, volume: DEFAULT_VOLUME, @@ -56,7 +58,14 @@ impl Default for Settings { } // Metadata for the properties -static PROPERTIES: [Property; 5] = [ +static PROPERTIES: [Property; 6] = [ + Property::String( + "stream-name", + "Sream Name", + "Name of the streaming device", + None, + PropertyMutability::ReadWrite, + ), Property::UInt( "samples-per-buffer", "Samples Per Buffer", @@ -255,6 +264,22 @@ impl ObjectImpl for NdiSrc { let element = obj.clone().downcast::().unwrap(); match *prop { + Property::String("stream-name", ..) => { + let mut settings = self.settings.lock().unwrap(); + let stream_name = value.get().unwrap(); + gst_info!( + self.cat, + obj: &element, + "Changing stream-name from {} to {}", + settings.stream_name, + stream_name + ); + settings.stream_name = stream_name; + drop(settings); + + let _ = + element.post_message(&gst::Message::new_latency().src(Some(&element)).build()); + } Property::UInt("samples-per-buffer", ..) => { let mut settings = self.settings.lock().unwrap(); let samples_per_buffer = value.get().unwrap(); @@ -329,6 +354,11 @@ impl ObjectImpl for NdiSrc { let prop = &PROPERTIES[id as usize]; match *prop { + Property::UInt("stream-name", ..) => { + let settings = self.settings.lock().unwrap(); + //TODO to_value supongo que solo funciona con numeros + Ok(settings.stream_name.to_value()) + } Property::UInt("samples-per-buffer", ..) => { let settings = self.settings.lock().unwrap(); Ok(settings.samples_per_buffer.to_value()) @@ -393,7 +423,7 @@ impl BaseSrcImpl for NdiSrc { element.set_blocksize(info.bpf() * (*self.settings.lock().unwrap()).samples_per_buffer); - let settings = *self.settings.lock().unwrap(); + let settings = &*self.settings.lock().unwrap(); let mut state = self.state.lock().unwrap(); // If we have no caps yet, any old sample_offset and sample_stop will be @@ -468,7 +498,7 @@ impl BaseSrcImpl for NdiSrc { // We can't output samples before they were produced, and the last sample of a buffer // is produced that much after the beginning, leading to this latency calculation QueryView::Latency(ref mut q) => { - let settings = *self.settings.lock().unwrap(); + let settings = &*self.settings.lock().unwrap(); let state = self.state.lock().unwrap(); if let Some(ref info) = state.info { @@ -497,7 +527,7 @@ impl BaseSrcImpl for NdiSrc { // Keep a local copy of the values of all our properties at this very moment. This // ensures that the mutex is never locked for long and the application wouldn't // have to block until this function returns when getting/setting property values - let settings = *self.settings.lock().unwrap(); + let settings = &*self.settings.lock().unwrap(); // Get a locked reference to our state, i.e. the input and output AudioInfo let mut state = self.state.lock().unwrap(); From 2b09a48a5cc43c7566ac186893c85d9e5bab0fb9 Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Thu, 12 Apr 2018 17:52:21 +0200 Subject: [PATCH 020/199] Added videocaps and commented almost audiosrc related code --- gst-plugin-ndi/src/ndisrc.rs | 445 ++++++++++++++++++----------------- 1 file changed, 231 insertions(+), 214 deletions(-) diff --git a/gst-plugin-ndi/src/ndisrc.rs b/gst-plugin-ndi/src/ndisrc.rs index 9323b0d2..1a52ef95 100644 --- a/gst-plugin-ndi/src/ndisrc.rs +++ b/gst-plugin-ndi/src/ndisrc.rs @@ -9,7 +9,8 @@ use glib; use gst; use gst::prelude::*; -use gst_audio; +//use gst_audio; +use gst_video; use gst_base::prelude::*; use byte_slice_cast::*; @@ -109,19 +110,19 @@ static PROPERTIES: [Property; 6] = [ // Stream-specific state, i.e. audio format configuration // and sample offset struct State { - info: Option, - sample_offset: u64, - sample_stop: Option, - accumulator: f64, + info: Option, + // sample_offset: u64, + // sample_stop: Option, + // accumulator: f64, } impl Default for State { fn default() -> State { State { info: None, - sample_offset: 0, - sample_stop: None, - accumulator: 0.0, + // sample_offset: 0, + // sample_stop: None, + // accumulator: 0.0, } } } @@ -183,32 +184,41 @@ impl NdiSrc { // On the src pad, we can produce F32/F64 with any sample rate // and any number of channels let caps = gst::Caps::new_simple( - "audio/x-raw", + "video/x-raw", &[ ( "format", &gst::List::new(&[ - &gst_audio::AUDIO_FORMAT_F32.to_string(), - &gst_audio::AUDIO_FORMAT_F64.to_string(), - ]), + &gst_video::VideoFormat::Rgb.to_string(), + &gst_video::VideoFormat::Gray8.to_string(), + ]), + ), + // ("layout", &"interleaved"), + // ("rate", &gst::IntRange::::new(1, i32::MAX)), + // ("channels", &gst::IntRange::::new(1, i32::MAX)), + ("width", &gst::IntRange::::new(0, i32::MAX)), + ("height", &gst::IntRange::::new(0, i32::MAX)), + ( + "framerate", + &gst::FractionRange::new( + gst::Fraction::new(0, 1), + gst::Fraction::new(i32::MAX, 1), ), - ("layout", &"interleaved"), - ("rate", &gst::IntRange::::new(1, i32::MAX)), - ("channels", &gst::IntRange::::new(1, i32::MAX)), - ], - ); - // The src pad template must be named "src" for basesrc - // and specific a pad that is always there - let src_pad_template = gst::PadTemplate::new( - "src", - gst::PadDirection::Src, - gst::PadPresence::Always, - &caps, - ); - klass.add_pad_template(src_pad_template); + ), + ], + ); + // The src pad template must be named "src" for basesrc + // and specific a pad that is always there + let src_pad_template = gst::PadTemplate::new( + "src", + gst::PadDirection::Src, + gst::PadPresence::Always, + &caps, + ); + klass.add_pad_template(src_pad_template); - // Install all our properties - klass.install_properties(&PROPERTIES); + // Install all our properties + klass.install_properties(&PROPERTIES); } fn process( @@ -414,49 +424,54 @@ impl BaseSrcImpl for NdiSrc { fn set_caps(&self, element: &BaseSrc, caps: &gst::CapsRef) -> bool { use std::f64::consts::PI; - let info = match gst_audio::AudioInfo::from_caps(caps) { + let info = match gst_video::VideoInfo::from_caps(caps) { None => return false, Some(info) => info, }; gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); - element.set_blocksize(info.bpf() * (*self.settings.lock().unwrap()).samples_per_buffer); - - let settings = &*self.settings.lock().unwrap(); let mut state = self.state.lock().unwrap(); - - // If we have no caps yet, any old sample_offset and sample_stop will be - // in nanoseconds - let old_rate = match state.info { - Some(ref info) => info.rate() as u64, - None => gst::SECOND_VAL, - }; - - // Update sample offset and accumulator based on the previous values and the - // sample rate change, if any - let old_sample_offset = state.sample_offset; - let sample_offset = old_sample_offset - .mul_div_floor(info.rate() as u64, old_rate) - .unwrap(); - - let old_sample_stop = state.sample_stop; - let sample_stop = - old_sample_stop.map(|v| v.mul_div_floor(info.rate() as u64, old_rate).unwrap()); - - let accumulator = - (sample_offset as f64).rem(2.0 * PI * (settings.freq as f64) / (info.rate() as f64)); - - *state = State { + *state = State { info: Some(info), - sample_offset: sample_offset, - sample_stop: sample_stop, - accumulator: accumulator, - }; + }; - drop(state); - - let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); + // element.set_blocksize(info.bpf() * (*self.settings.lock().unwrap()).samples_per_buffer); + // + // let settings = &*self.settings.lock().unwrap(); + // let mut state = self.state.lock().unwrap(); + // + // // If we have no caps yet, any old sample_offset and sample_stop will be + // // in nanoseconds + // let old_rate = match state.info { + // Some(ref info) => info.rate() as u64, + // None => gst::SECOND_VAL, + // }; + // + // // Update sample offset and accumulator based on the previous values and the + // // sample rate change, if any + // let old_sample_offset = state.sample_offset; + // let sample_offset = old_sample_offset + // .mul_div_floor(info.rate() as u64, old_rate) + // .unwrap(); + // + // let old_sample_stop = state.sample_stop; + // let sample_stop = + // old_sample_stop.map(|v| v.mul_div_floor(info.rate() as u64, old_rate).unwrap()); + // + // let accumulator = + // (sample_offset as f64).rem(2.0 * PI * (settings.freq as f64) / (info.rate() as f64)); + // + // *state = State { + // info: Some(info), + // sample_offset: sample_offset, + // sample_stop: sample_stop, + // accumulator: accumulator, + // }; + // + // drop(state); + // + // let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); true } @@ -483,39 +498,39 @@ impl BaseSrcImpl for NdiSrc { true } - fn query(&self, element: &BaseSrc, query: &mut gst::QueryRef) -> bool { - use gst::QueryView; - - match query.view_mut() { - // We only work in Push mode. In Pull mode, create() could be called with - // arbitrary offsets and we would have to produce for that specific offset - QueryView::Scheduling(ref mut q) => { - q.set(gst::SchedulingFlags::SEQUENTIAL, 1, -1, 0); - q.add_scheduling_modes(&[gst::PadMode::Push]); - return true; - } - // In Live mode we will have a latency equal to the number of samples in each buffer. - // We can't output samples before they were produced, and the last sample of a buffer - // is produced that much after the beginning, leading to this latency calculation - QueryView::Latency(ref mut q) => { - let settings = &*self.settings.lock().unwrap(); - let state = self.state.lock().unwrap(); - - if let Some(ref info) = state.info { - let latency = gst::SECOND - .mul_div_floor(settings.samples_per_buffer as u64, info.rate() as u64) - .unwrap(); - gst_debug!(self.cat, obj: element, "Returning latency {}", latency); - q.set(settings.is_live, latency, gst::CLOCK_TIME_NONE); - return true; - } else { - return false; - } - } - _ => (), - } - BaseSrcBase::parent_query(element, query) - } + // fn query(&self, element: &BaseSrc, query: &mut gst::QueryRef) -> bool { + // use gst::QueryView; + // + // match query.view_mut() { + // // We only work in Push mode. In Pull mode, create() could be called with + // // arbitrary offsets and we would have to produce for that specific offset + // QueryView::Scheduling(ref mut q) => { + // q.set(gst::SchedulingFlags::SEQUENTIAL, 1, -1, 0); + // q.add_scheduling_modes(&[gst::PadMode::Push]); + // return true; + // } + // // In Live mode we will have a latency equal to the number of samples in each buffer. + // // We can't output samples before they were produced, and the last sample of a buffer + // // is produced that much after the beginning, leading to this latency calculation + // QueryView::Latency(ref mut q) => { + // let settings = &*self.settings.lock().unwrap(); + // let state = self.state.lock().unwrap(); + // + // if let Some(ref info) = state.info { + // let latency = gst::SECOND + // .mul_div_floor(settings.samples_per_buffer as u64, info.rate() as u64) + // .unwrap(); + // gst_debug!(self.cat, obj: element, "Returning latency {}", latency); + // q.set(settings.is_live, latency, gst::CLOCK_TIME_NONE); + // return true; + // } else { + // return false; + // } + // } + // _ => (), + // } + // BaseSrcBase::parent_query(element, query) + // } // Creates the audio buffers fn create( @@ -539,134 +554,136 @@ impl BaseSrcImpl for NdiSrc { Some(ref info) => info.clone(), }; - // If a stop position is set (from a seek), only produce samples up to that - // point but at most samples_per_buffer samples per buffer - let n_samples = if let Some(sample_stop) = state.sample_stop { - if sample_stop <= state.sample_offset { - gst_log!(self.cat, obj: element, "At EOS"); - return Err(gst::FlowReturn::Eos); - } - - sample_stop - state.sample_offset - } else { - settings.samples_per_buffer as u64 - }; + // // If a stop position is set (from a seek), only produce samples up to that + // // point but at most samples_per_buffer samples per buffer + // let n_samples = if let Some(sample_stop) = state.sample_stop { + // if sample_stop <= state.sample_offset { + // gst_log!(self.cat, obj: element, "At EOS"); + // return Err(gst::FlowReturn::Eos); + // } + // + // sample_stop - state.sample_offset + // } else { + // settings.samples_per_buffer as u64 + // }; // Allocate a new buffer of the required size, update the metadata with the // current timestamp and duration and then fill it according to the current // caps + //TODO Set buffer size from data received from NDI let mut buffer = - gst::Buffer::with_size((n_samples as usize) * (info.bpf() as usize)).unwrap(); + // gst::Buffer::with_size((n_samples as usize) * (info.bpf() as usize)).unwrap(); + gst::Buffer::with_size(200 * 400).unwrap(); { let buffer = buffer.get_mut().unwrap(); - // Calculate the current timestamp (PTS) and the next one, - // and calculate the duration from the difference instead of - // simply the number of samples to prevent rounding errors - let pts = state - .sample_offset - .mul_div_floor(gst::SECOND_VAL, info.rate() as u64) - .unwrap() - .into(); - let next_pts: gst::ClockTime = (state.sample_offset + n_samples) - .mul_div_floor(gst::SECOND_VAL, info.rate() as u64) - .unwrap() - .into(); - buffer.set_pts(pts); - buffer.set_duration(next_pts - pts); - - // Map the buffer writable and create the actual samples - let mut map = buffer.map_writable().unwrap(); - let data = map.as_mut_slice(); - - if info.format() == gst_audio::AUDIO_FORMAT_F32 { - Self::process::( - data, - &mut state.accumulator, - settings.freq, - info.rate(), - info.channels(), - settings.volume, - ); - } else { - Self::process::( - data, - &mut state.accumulator, - settings.freq, - info.rate(), - info.channels(), - settings.volume, - ); - } - } - state.sample_offset += n_samples; - drop(state); - - // If we're live, we are waiting until the time of the last sample in our buffer has - // arrived. This is the very reason why we have to report that much latency. - // A real live-source would of course only allow us to have the data available after - // that latency, e.g. when capturing from a microphone, and no waiting from our side - // would be necessary.. + // // Calculate the current timestamp (PTS) and the next one, + // // and calculate the duration from the difference instead of + // // simply the number of samples to prevent rounding errors + // let pts = state + // .sample_offset + // .mul_div_floor(gst::SECOND_VAL, info.rate() as u64) + // .unwrap() + // .into(); + // let next_pts: gst::ClockTime = (state.sample_offset + n_samples) + // .mul_div_floor(gst::SECOND_VAL, info.rate() as u64) + // .unwrap() + // .into(); + // buffer.set_pts(pts); + // buffer.set_duration(next_pts - pts); // - // Waiting happens based on the pipeline clock, which means that a real live source - // with its own clock would require various translations between the two clocks. - // This is out of scope for the tutorial though. - if element.is_live() { - let clock = match element.get_clock() { - None => return Ok(buffer), - Some(clock) => clock, - }; - - let segment = element - .get_segment() - .downcast::() - .unwrap(); - let base_time = element.get_base_time(); - let running_time = segment.to_running_time(buffer.get_pts() + buffer.get_duration()); - - // The last sample's clock time is the base time of the element plus the - // running time of the last sample - let wait_until = running_time + base_time; - if wait_until.is_none() { - return Ok(buffer); - } - - // Store the clock ID in our struct unless we're flushing anyway. - // This allows to asynchronously cancel the waiting from unlock() - // so that we immediately stop waiting on e.g. shutdown. - let mut clock_wait = self.clock_wait.lock().unwrap(); - if clock_wait.flushing { - gst_debug!(self.cat, obj: element, "Flushing"); - return Err(gst::FlowReturn::Flushing); - } - - let id = clock.new_single_shot_id(wait_until).unwrap(); - clock_wait.clock_id = Some(id.clone()); - drop(clock_wait); - - gst_log!( - self.cat, - obj: element, - "Waiting until {}, now {}", - wait_until, - clock.get_time() - ); - let (res, jitter) = id.wait(); - gst_log!( - self.cat, - obj: element, - "Waited res {:?} jitter {}", - res, - jitter - ); - self.clock_wait.lock().unwrap().clock_id.take(); - - // If the clock ID was unscheduled, unlock() was called - // and we should return Flushing immediately. - if res == gst::ClockReturn::Unscheduled { - gst_debug!(self.cat, obj: element, "Flushing"); - return Err(gst::FlowReturn::Flushing); - } + // // Map the buffer writable and create the actual samples + // let mut map = buffer.map_writable().unwrap(); + // let data = map.as_mut_slice(); + // + // if info.format() == gst_audio::AUDIO_FORMAT_F32 { + // Self::process::( + // data, + // &mut state.accumulator, + // settings.freq, + // info.rate(), + // info.channels(), + // settings.volume, + // ); + // } else { + // Self::process::( + // data, + // &mut state.accumulator, + // settings.freq, + // info.rate(), + // info.channels(), + // settings.volume, + // ); + // } + // } + // state.sample_offset += n_samples; + // drop(state); + // + // // If we're live, we are waiting until the time of the last sample in our buffer has + // // arrived. This is the very reason why we have to report that much latency. + // // A real live-source would of course only allow us to have the data available after + // // that latency, e.g. when capturing from a microphone, and no waiting from our side + // // would be necessary.. + // // + // // Waiting happens based on the pipeline clock, which means that a real live source + // // with its own clock would require various translations between the two clocks. + // // This is out of scope for the tutorial though. + // if element.is_live() { + // let clock = match element.get_clock() { + // None => return Ok(buffer), + // Some(clock) => clock, + // }; + // + // let segment = element + // .get_segment() + // .downcast::() + // .unwrap(); + // let base_time = element.get_base_time(); + // let running_time = segment.to_running_time(buffer.get_pts() + buffer.get_duration()); + // + // // The last sample's clock time is the base time of the element plus the + // // running time of the last sample + // let wait_until = running_time + base_time; + // if wait_until.is_none() { + // return Ok(buffer); + // } + // + // // Store the clock ID in our struct unless we're flushing anyway. + // // This allows to asynchronously cancel the waiting from unlock() + // // so that we immediately stop waiting on e.g. shutdown. + // let mut clock_wait = self.clock_wait.lock().unwrap(); + // if clock_wait.flushing { + // gst_debug!(self.cat, obj: element, "Flushing"); + // return Err(gst::FlowReturn::Flushing); + // } + // + // let id = clock.new_single_shot_id(wait_until).unwrap(); + // clock_wait.clock_id = Some(id.clone()); + // drop(clock_wait); + // + // gst_log!( + // self.cat, + // obj: element, + // "Waiting until {}, now {}", + // wait_until, + // clock.get_time() + // ); + // let (res, jitter) = id.wait(); + // gst_log!( + // self.cat, + // obj: element, + // "Waited res {:?} jitter {}", + // res, + // jitter + // ); + // self.clock_wait.lock().unwrap().clock_id.take(); + // + // // If the clock ID was unscheduled, unlock() was called + // // and we should return Flushing immediately. + // if res == gst::ClockReturn::Unscheduled { + // gst_debug!(self.cat, obj: element, "Flushing"); + // return Err(gst::FlowReturn::Flushing); + // } } gst_debug!(self.cat, obj: element, "Produced buffer {:?}", buffer); From c25ad01561595835eeb29dc726851301ccfd20e6 Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Fri, 13 Apr 2018 12:06:15 +0200 Subject: [PATCH 021/199] First try to insert NDI libraries --- gst-plugin-ndi/src/lib.rs | 1 + gst-plugin-ndi/src/ndisrc.rs | 11 +++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/gst-plugin-ndi/src/lib.rs b/gst-plugin-ndi/src/lib.rs index 4c620360..26602e2e 100644 --- a/gst-plugin-ndi/src/lib.rs +++ b/gst-plugin-ndi/src/lib.rs @@ -19,6 +19,7 @@ extern crate byte_slice_cast; extern crate num_traits; mod ndisrc; +pub mod ndilib; // Plugin entry point that should register all elements provided by this plugin, // and everything else that this plugin might provide (e.g. typefinders or device providers). diff --git a/gst-plugin-ndi/src/ndisrc.rs b/gst-plugin-ndi/src/ndisrc.rs index 1a52ef95..706b6ce8 100644 --- a/gst-plugin-ndi/src/ndisrc.rs +++ b/gst-plugin-ndi/src/ndisrc.rs @@ -27,6 +27,11 @@ use std::{i32, u32}; use num_traits::cast::NumCast; use num_traits::float::Float; +use std::ptr; +use std::ffi::{CStr, CString}; + +use ndilib::*; + // Default values of properties const DEFAULT_SAMPLES_PER_BUFFER: u32 = 1024; const DEFAULT_FREQ: u32 = 440; @@ -111,6 +116,7 @@ static PROPERTIES: [Property; 6] = [ // and sample offset struct State { info: Option, + recv: Option, // sample_offset: u64, // sample_stop: Option, // accumulator: f64, @@ -120,6 +126,7 @@ impl Default for State { fn default() -> State { State { info: None, + //recv: None, // sample_offset: 0, // sample_stop: None, // accumulator: 0.0, @@ -277,7 +284,7 @@ impl ObjectImpl for NdiSrc { Property::String("stream-name", ..) => { let mut settings = self.settings.lock().unwrap(); let stream_name = value.get().unwrap(); - gst_info!( + gst_warning!( self.cat, obj: &element, "Changing stream-name from {} to {}", @@ -482,7 +489,7 @@ impl BaseSrcImpl for NdiSrc { *self.state.lock().unwrap() = Default::default(); self.unlock_stop(element); - gst_info!(self.cat, obj: element, "Started"); + gst_warning!(self.cat, obj: element, "Starting"); true } From 892eb29c3ba83163b0da4857000bbb9edfde776c Mon Sep 17 00:00:00 2001 From: rubenrua Date: Mon, 16 Apr 2018 16:42:33 +0000 Subject: [PATCH 022/199] Add ndilib.rs --- gst-plugin-ndi/src/ndilib.rs | 246 +++++++++++++++++++++++++++++++++++ 1 file changed, 246 insertions(+) create mode 100644 gst-plugin-ndi/src/ndilib.rs diff --git a/gst-plugin-ndi/src/ndilib.rs b/gst-plugin-ndi/src/ndilib.rs new file mode 100644 index 00000000..6474e153 --- /dev/null +++ b/gst-plugin-ndi/src/ndilib.rs @@ -0,0 +1,246 @@ +#![allow(non_camel_case_types, non_upper_case_globals)] + +use std::ptr; + +#[link(name = "ndi")] +extern "C" { + pub fn NDIlib_initialize() -> bool; + pub fn NDIlib_find_create_v2( + p_create_settings: *const NDIlib_find_create_t, + ) -> NDIlib_find_instance_t; + pub fn NDIlib_find_get_current_sources( + p_instance: NDIlib_find_instance_t, + p_no_sources: *mut u32, + ) -> *const NDIlib_source_t; + pub fn NDIlib_recv_create_v3( + p_create_settings: *const NDIlib_recv_create_v3_t, + ) -> NDIlib_recv_instance_t; + pub fn NDIlib_find_destroy(p_instance: NDIlib_find_instance_t); + pub fn NDIlib_recv_set_tally( + p_instance: NDIlib_recv_instance_t, + p_tally: *const NDIlib_tally_t, + ) -> bool; + pub fn NDIlib_recv_send_metadata( + p_instance: NDIlib_recv_instance_t, + p_metadata: *const NDIlib_metadata_frame_t, + ) -> bool; + pub fn NDIlib_recv_capture_v2( + p_instance: NDIlib_recv_instance_t, + p_video_data: *const NDIlib_video_frame_v2_t, + p_audio_data: *const NDIlib_audio_frame_v2_t, + p_metadata: *const NDIlib_metadata_frame_t, + timeout_in_ms: u32, + ) -> NDIlib_frame_type_e; +} + +pub type NDIlib_find_instance_t = *mut ::std::os::raw::c_void; + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct NDIlib_find_create_t { + pub show_local_sources: bool, + pub p_groups: *const ::std::os::raw::c_char, + pub p_extra_ips: *const ::std::os::raw::c_char, +} + +impl Default for NDIlib_find_create_t { + fn default() -> Self { + NDIlib_find_create_t { + show_local_sources: true, + p_groups: ptr::null(), + p_extra_ips: ptr::null(), + } + } +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct NDIlib_source_t { + pub p_ndi_name: *const ::std::os::raw::c_char, + pub p_ip_address: *const ::std::os::raw::c_char, +} + +impl Default for NDIlib_source_t { + fn default() -> Self { + NDIlib_source_t { + p_ndi_name: ptr::null(), + p_ip_address: ptr::null(), + } + } +} + +#[repr(i32)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum NDIlib_frame_type_e { + NDIlib_frame_type_none = 0, + NDIlib_frame_type_video = 1, + NDIlib_frame_type_audio = 2, + NDIlib_frame_type_metadata = 3, + NDIlib_frame_type_error = 4, + NDIlib_frame_type_status_change = 100, +} + +#[repr(i32)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum NDIlib_recv_bandwidth_e { + NDIlib_recv_bandwidth_metadata_only = -10, + NDIlib_recv_bandwidth_audio_only = 10, + NDIlib_recv_bandwidth_lowest = 0, + NDIlib_recv_bandwidth_highest = 100, +} + +#[repr(u32)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum NDIlib_recv_color_format_e { + NDIlib_recv_color_format_BGRX_BGRA = 0, + NDIlib_recv_color_format_UYVY_BGRA = 1, + NDIlib_recv_color_format_RGBX_RGBA = 2, + NDIlib_recv_color_format_UYVY_RGBA = 3, + NDIlib_recv_color_format_fastest = 100, +} + +#[repr(u32)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum NDIlib_FourCC_type_e { + NDIlib_FourCC_type_UYVY = 1498831189, + NDIlib_FourCC_type_BGRA = 1095911234, + NDIlib_FourCC_type_BGRX = 1481787202, + NDIlib_FourCC_type_RGBA = 1094862674, + NDIlib_FourCC_type_RGBX = 1480738642, + NDIlib_FourCC_type_UYVA = 1096178005, +} + +#[repr(u32)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum NDIlib_frame_format_type_e { + NDIlib_frame_format_type_progressive = 1, + NDIlib_frame_format_type_interleaved = 0, + NDIlib_frame_format_type_field_0 = 2, + NDIlib_frame_format_type_field_1 = 3, +} + +pub const NDIlib_send_timecode_synthesize: i64 = ::std::i64::MAX; +pub const NDIlib_send_timecode_empty: i64 = 0; +pub const NDIlib_recv_timestamp_undefined: i64 = ::std::i64::MAX; + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct NDIlib_recv_create_v3_t { + pub source_to_connect_to: NDIlib_source_t, + pub color_format: NDIlib_recv_color_format_e, + pub bandwidth: NDIlib_recv_bandwidth_e, + pub allow_video_fields: bool, + pub p_ndi_name: *const ::std::os::raw::c_char, +} + +impl Default for NDIlib_recv_create_v3_t { + fn default() -> Self { + NDIlib_recv_create_v3_t { + source_to_connect_to: Default::default(), + allow_video_fields: true, + bandwidth: NDIlib_recv_bandwidth_e::NDIlib_recv_bandwidth_highest, + color_format: NDIlib_recv_color_format_e::NDIlib_recv_color_format_UYVY_BGRA, + p_ndi_name: ptr::null(), + } + } +} + +pub type NDIlib_recv_instance_t = *mut ::std::os::raw::c_void; + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct NDIlib_tally_t { + pub on_program: bool, + pub on_preview: bool, +} + +impl Default for NDIlib_tally_t { + fn default() -> Self { + NDIlib_tally_t { + on_program: false, + on_preview: false, + } + } +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct NDIlib_metadata_frame_t { + pub length: ::std::os::raw::c_int, + pub timecode: i64, + pub p_data: *const ::std::os::raw::c_char, +} + +impl Default for NDIlib_metadata_frame_t { + fn default() -> Self { + NDIlib_metadata_frame_t { + length: 0, + timecode: 0, //NDIlib_send_timecode_synthesize, + p_data: ptr::null(), + } + } +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct NDIlib_video_frame_v2_t { + pub xres: ::std::os::raw::c_int, + pub yres: ::std::os::raw::c_int, + pub FourCC: NDIlib_FourCC_type_e, + pub frame_rate_N: ::std::os::raw::c_int, + pub frame_rate_D: ::std::os::raw::c_int, + pub picture_aspect_ratio: ::std::os::raw::c_float, + pub frame_format_type: NDIlib_frame_format_type_e, + pub timecode: i64, + pub p_data: *const ::std::os::raw::c_char, + pub line_stride_in_bytes: ::std::os::raw::c_int, + pub p_metadata: *const ::std::os::raw::c_char, + pub timestamp: i64, +} + +impl Default for NDIlib_video_frame_v2_t { + fn default() -> Self { + NDIlib_video_frame_v2_t { + xres: 0, + yres: 0, + FourCC: NDIlib_FourCC_type_e::NDIlib_FourCC_type_UYVY, + frame_rate_N: 30000, + frame_rate_D: 1001, + picture_aspect_ratio: 0.0, + frame_format_type: NDIlib_frame_format_type_e::NDIlib_frame_format_type_progressive, + timecode: NDIlib_send_timecode_synthesize, + p_data: ptr::null(), + line_stride_in_bytes: 0, + p_metadata: ptr::null(), + timestamp: NDIlib_send_timecode_empty, + } + } +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct NDIlib_audio_frame_v2_t { + pub sample_rate: ::std::os::raw::c_int, + pub no_channels: ::std::os::raw::c_int, + pub no_samples: ::std::os::raw::c_int, + pub timecode: i64, + pub p_data: *const ::std::os::raw::c_float, + pub channel_stride_in_bytes: ::std::os::raw::c_int, + pub p_metadata: *const ::std::os::raw::c_char, + pub timestamp: i64, +} + +impl Default for NDIlib_audio_frame_v2_t { + fn default() -> Self { + NDIlib_audio_frame_v2_t { + sample_rate: 48000, + no_channels: 2, + no_samples: 0, + timecode: NDIlib_send_timecode_synthesize, + p_data: ptr::null(), + channel_stride_in_bytes: 0, + p_metadata: ptr::null(), + timestamp: NDIlib_send_timecode_empty, + } + } +} From 1377916b3bdd86129ffb911ad70daec2912520ba Mon Sep 17 00:00:00 2001 From: rubenrua Date: Mon, 16 Apr 2018 16:44:00 +0000 Subject: [PATCH 023/199] Add NdiInstance struct that implement Send https://doc.rust-lang.org/book/second-edition/ch19-03-advanced-traits.html#the-newtype-pattern-to-implement-external-traits-on-external-types error[E0277]: the trait bound `*mut std::os::raw::c_void: std::marker::Send` is not satisfied in `ndisrc::State` --> src/ndisrc.rs:276:6 | 276 | impl ObjectImpl for NdiSrc { | ^^^^^^^^^^^^^^^^^^^ `*mut std::os::raw::c_void` cannot be sent between threads safely | = help: within `ndisrc::State`, the trait `std::marker::Send` is not implemented for `*mut std::os::raw::c_void` = note: required because it appears within the type `std::option::Option<*mut std::os::raw::c_void>` = note: required because it appears within the type `ndisrc::State` = note: required because of the requirements on the impl of `std::marker::Sync` for `std::sync::Mutex` = note: required because it appears within the type `ndisrc::NdiSrc` error[E0277]: the trait bound `*mut std::os::raw::c_void: std::marker::Send` is not satisfied in `ndisrc::State` --> src/ndisrc.rs:405:6 | 405 | impl ElementImpl for NdiSrc { | ^^^^^^^^^^^^^^^^^^^^ `*mut std::os::raw::c_void` cannot be sent between threads safely | = help: within `ndisrc::State`, the trait `std::marker::Send` is not implemented for `*mut std::os::raw::c_void` = note: required because it appears within the type `std::option::Option<*mut std::os::raw::c_void>` = note: required because it appears within the type `ndisrc::State` = note: required because of the requirements on the impl of `std::marker::Sync` for `std::sync::Mutex` = note: required because it appears within the type `ndisrc::NdiSrc` error[E0277]: the trait bound `*mut std::os::raw::c_void: std::marker::Send` is not satisfied in `ndisrc::State` --> src/ndisrc.rs:424:6 | 424 | impl BaseSrcImpl for NdiSrc { | ^^^^^^^^^^^^^^^^^^^^ `*mut std::os::raw::c_void` cannot be sent between threads safely | = help: within `ndisrc::State`, the trait `std::marker::Send` is not implemented for `*mut std::os::raw::c_void` = note: required because it appears within the type `std::option::Option<*mut std::os::raw::c_void>` = note: required because it appears within the type `ndisrc::State` = note: required because of the requirements on the impl of `std::marker::Sync` for `std::sync::Mutex` = note: required because it appears within the type `ndisrc::NdiSrc` error: aborting due to 3 previous errors --- gst-plugin-ndi/src/ndilib.rs | 6 ++++++ gst-plugin-ndi/src/ndisrc.rs | 7 ++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/gst-plugin-ndi/src/ndilib.rs b/gst-plugin-ndi/src/ndilib.rs index 6474e153..231186b7 100644 --- a/gst-plugin-ndi/src/ndilib.rs +++ b/gst-plugin-ndi/src/ndilib.rs @@ -147,6 +147,12 @@ impl Default for NDIlib_recv_create_v3_t { pub type NDIlib_recv_instance_t = *mut ::std::os::raw::c_void; +//Rust wrapper around *mut ::std::os::raw::c_void +pub struct NdiInstance (NDIlib_recv_instance_t); + +unsafe impl ::std::marker::Send for NdiInstance {} + + #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct NDIlib_tally_t { diff --git a/gst-plugin-ndi/src/ndisrc.rs b/gst-plugin-ndi/src/ndisrc.rs index 706b6ce8..41bb32ee 100644 --- a/gst-plugin-ndi/src/ndisrc.rs +++ b/gst-plugin-ndi/src/ndisrc.rs @@ -116,7 +116,7 @@ static PROPERTIES: [Property; 6] = [ // and sample offset struct State { info: Option, - recv: Option, + recv: Option, // sample_offset: u64, // sample_stop: Option, // accumulator: f64, @@ -126,7 +126,7 @@ impl Default for State { fn default() -> State { State { info: None, - //recv: None, + recv: None, // sample_offset: 0, // sample_stop: None, // accumulator: 0.0, @@ -440,7 +440,8 @@ impl BaseSrcImpl for NdiSrc { let mut state = self.state.lock().unwrap(); *state = State { - info: Some(info), + info: Some(info), + recv: None }; // element.set_blocksize(info.bpf() * (*self.settings.lock().unwrap()).samples_per_buffer); From ca4e498a0bbedecdc8fc7018ec3a54296d23bf66 Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Mon, 23 Apr 2018 13:42:38 +0200 Subject: [PATCH 024/199] Added NDI code --- gst-plugin-ndi/src/ndilib.rs | 4 +- gst-plugin-ndi/src/ndisrc.rs | 152 ++++++++++++++++++++++++++++++++++- 2 files changed, 151 insertions(+), 5 deletions(-) diff --git a/gst-plugin-ndi/src/ndilib.rs b/gst-plugin-ndi/src/ndilib.rs index 231186b7..c851eabc 100644 --- a/gst-plugin-ndi/src/ndilib.rs +++ b/gst-plugin-ndi/src/ndilib.rs @@ -148,7 +148,9 @@ impl Default for NDIlib_recv_create_v3_t { pub type NDIlib_recv_instance_t = *mut ::std::os::raw::c_void; //Rust wrapper around *mut ::std::os::raw::c_void -pub struct NdiInstance (NDIlib_recv_instance_t); +pub struct NdiInstance { + pub recv: NDIlib_recv_instance_t, +} unsafe impl ::std::marker::Send for NdiInstance {} diff --git a/gst-plugin-ndi/src/ndisrc.rs b/gst-plugin-ndi/src/ndisrc.rs index 41bb32ee..a7358576 100644 --- a/gst-plugin-ndi/src/ndisrc.rs +++ b/gst-plugin-ndi/src/ndisrc.rs @@ -438,11 +438,13 @@ impl BaseSrcImpl for NdiSrc { gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); + // TODO Puede que falle si no creamos la estructura de cero, pero si lo hacemos no podemos poner recv a none let mut state = self.state.lock().unwrap(); - *state = State { - info: Some(info), - recv: None - }; + state.info = Some(info); + // *state = State { + // info: Some(info), + // recv: Some(NdiInstance{recv: pNDI_recv}), + // }; // element.set_blocksize(info.bpf() * (*self.settings.lock().unwrap()).samples_per_buffer); // @@ -491,6 +493,89 @@ impl BaseSrcImpl for NdiSrc { self.unlock_stop(element); gst_warning!(self.cat, obj: element, "Starting"); + let mut state = self.state.lock().unwrap(); + //let mut pNDI_recv = state.recv; + unsafe { + //TODO Al inicializar el plugin + if !NDIlib_initialize() { + //TODO delete exits + println!("Cannot run NDI: NDIlib_initialize error."); + ::std::process::exit(1); + } + + //TODO valores por defecto + let NDI_find_create_desc: NDIlib_find_create_t = Default::default(); + let pNDI_find = NDIlib_find_create_v2(&NDI_find_create_desc); + if pNDI_find.is_null() { + println!("Cannot run NDI: NDIlib_find_create_v2 error."); + ::std::process::exit(1); + } + + let mut no_sources: u32 = 0; + let mut p_sources = ptr::null(); + //TODO Evitar bloqueo + while no_sources == 0 { + p_sources = NDIlib_find_get_current_sources(pNDI_find, &mut no_sources as *mut u32); + } + + // We need at least one source + if p_sources.is_null() { + println!("Error getting NDIlib_find_get_current_sources."); + ::std::process::exit(1); + } + + println!( + "no_source {}: Name '{}' Address '{}'", + no_sources, + CStr::from_ptr((*p_sources).p_ndi_name) + .to_string_lossy() + .into_owned(), + CStr::from_ptr((*p_sources).p_ip_address) + .to_string_lossy() + .into_owned() + ); + + // We now have at least one source, so we create a receiver to look at it. + // We tell it that we prefer YCbCr video since it is more efficient for us. If the source has an alpha channel + // it will still be provided in BGRA + let p_ndi_name = CString::new("Galicaster NDI Receiver").unwrap(); + let NDI_recv_create_desc = NDIlib_recv_create_v3_t { + source_to_connect_to: *p_sources, + p_ndi_name: p_ndi_name.as_ptr(), + ..Default::default() + }; + + let pNDI_recv = NDIlib_recv_create_v3(&NDI_recv_create_desc); + if pNDI_recv.is_null() { + println!("Cannot run NDI: NDIlib_recv_create_v3 error."); + ::std::process::exit(1); + } + + // Destroy the NDI finder. We needed to have access to the pointers to p_sources[0] + NDIlib_find_destroy(pNDI_find); + + // We are now going to mark this source as being on program output for tally purposes (but not on preview) + let tally_state: NDIlib_tally_t = Default::default(); + NDIlib_recv_set_tally(pNDI_recv, &tally_state); + + // Enable Hardwqre Decompression support if this support has it. Please read the caveats in the documentation + // regarding this. There are times in which it might reduce the performance although on small stream numbers + // it almost always yields the same or better performance. + let data = CString::new("").unwrap(); + let enable_hw_accel = NDIlib_metadata_frame_t { + length: data.to_bytes().len() as i32, + timecode: 0, + p_data: data.as_ptr(), + }; + + NDIlib_recv_send_metadata(pNDI_recv, &enable_hw_accel); + state.recv = Some(NdiInstance{recv: pNDI_recv}); + //TODO Otra opcion para guardar pNDI_recv es esta: + // *state = State{ + // info: state.info.clone(), + // recv: Some(NdiInstance{recv: pNDI_recv}), + // }; + } true } @@ -561,6 +646,64 @@ impl BaseSrcImpl for NdiSrc { } Some(ref info) => info.clone(), }; + let recv = match state.recv{ + None => { + //TODO Cambiar gst_element_error por uno mas descriptivo + println!("pNDI_recv no encontrado"); + gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); + return Err(gst::FlowReturn::NotNegotiated); + } + Some(ref recv) => recv.clone(), + }; + let pNDI_recv = recv.recv; + + unsafe{ + // loop { + let video_frame: NDIlib_video_frame_v2_t = Default::default(); + let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); + let metadata_frame: NDIlib_metadata_frame_t = Default::default(); + + let frame_type = NDIlib_recv_capture_v2( + pNDI_recv, + &video_frame, + &audio_frame, + &metadata_frame, + 1000, + ); + + match frame_type { + NDIlib_frame_type_e::NDIlib_frame_type_video => { + println!("Tengo video {:?}", video_frame); + + } + NDIlib_frame_type_e::NDIlib_frame_type_audio => { + println!("Tengo audio {:?}", audio_frame); + } + NDIlib_frame_type_e::NDIlib_frame_type_metadata => { + println!( + "Tengo metadata {} '{}'", + metadata_frame.length, + CStr::from_ptr(metadata_frame.p_data) + .to_string_lossy() + .into_owned(), + ); + } + NDIlib_frame_type_e::NDIlib_frame_type_error => { + println!( + "Tengo error {} '{}'", + metadata_frame.length, + CStr::from_ptr(metadata_frame.p_data) + .to_string_lossy() + .into_owned(), + ); + // break; + } + _ => println!("Tengo {:?}", frame_type), + } + // } + + + // // If a stop position is set (from a seek), only produce samples up to that // // point but at most samples_per_buffer samples per buffer @@ -698,6 +841,7 @@ impl BaseSrcImpl for NdiSrc { Ok(buffer) } + } fn fixate(&self, element: &BaseSrc, caps: gst::Caps) -> gst::Caps { // Fixate the caps. BaseSrc will do some fixation for us, but From d813fa568151341f11a1f9238d69b5da5dfd4045 Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Mon, 23 Apr 2018 14:12:41 +0200 Subject: [PATCH 025/199] Try to add video data to a buffer --- gst-plugin-ndi/src/ndisrc.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/gst-plugin-ndi/src/ndisrc.rs b/gst-plugin-ndi/src/ndisrc.rs index a7358576..e8b38621 100644 --- a/gst-plugin-ndi/src/ndisrc.rs +++ b/gst-plugin-ndi/src/ndisrc.rs @@ -663,6 +663,9 @@ impl BaseSrcImpl for NdiSrc { let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); let metadata_frame: NDIlib_metadata_frame_t = Default::default(); + //TODO Solo hacemos el buffer cuando tengamos un frame de video + let mut frame = false; + while !frame{ let frame_type = NDIlib_recv_capture_v2( pNDI_recv, &video_frame, @@ -674,7 +677,7 @@ impl BaseSrcImpl for NdiSrc { match frame_type { NDIlib_frame_type_e::NDIlib_frame_type_video => { println!("Tengo video {:?}", video_frame); - + frame = true; } NDIlib_frame_type_e::NDIlib_frame_type_audio => { println!("Tengo audio {:?}", audio_frame); @@ -700,6 +703,7 @@ impl BaseSrcImpl for NdiSrc { } _ => println!("Tengo {:?}", frame_type), } + } // } @@ -727,6 +731,16 @@ impl BaseSrcImpl for NdiSrc { gst::Buffer::with_size(200 * 400).unwrap(); { let buffer = buffer.get_mut().unwrap(); + let pts: gst::ClockTime = (video_frame.timestamp as u64).into(); + let duration: gst::ClockTime = (334624).into(); + buffer.set_pts(pts); + buffer.set_duration(duration); + // Map the buffer writable and create the actual samples + let mut map = buffer.map_writable().unwrap(); + let mut data = map.as_slice(); + //data = &mut video_frame.p_data; + let a = CStr::from_ptr(video_frame.p_data); + data = a.to_bytes(); // // Calculate the current timestamp (PTS) and the next one, // // and calculate the duration from the difference instead of From 2d9feaa462338eec34eea34c69d4600523ecf75f Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Tue, 24 Apr 2018 14:08:06 +0200 Subject: [PATCH 026/199] Martes --- gst-plugin-ndi/README.md | 1 + gst-plugin-ndi/src/ndisrc.rs | 15 ++++++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/gst-plugin-ndi/README.md b/gst-plugin-ndi/README.md index 6462fc1f..62e42fef 100644 --- a/gst-plugin-ndi/README.md +++ b/gst-plugin-ndi/README.md @@ -24,4 +24,5 @@ Test cargo build export GST_PLUGIN_PATH=`pwd`/target/debug gst-inspect-1.0 ndisrc +GST_DEBUG=3 gst-launch-1.0 ndisrc ! video/x-raw, format=UYVY, width=720, height=576, framerate=1/25 ! videoconvert ! autovideosink ``` diff --git a/gst-plugin-ndi/src/ndisrc.rs b/gst-plugin-ndi/src/ndisrc.rs index e8b38621..12d9c14a 100644 --- a/gst-plugin-ndi/src/ndisrc.rs +++ b/gst-plugin-ndi/src/ndisrc.rs @@ -196,6 +196,7 @@ impl NdiSrc { ( "format", &gst::List::new(&[ + &gst_video::VideoFormat::Uyvy.to_string(), &gst_video::VideoFormat::Rgb.to_string(), &gst_video::VideoFormat::Gray8.to_string(), ]), @@ -625,13 +626,16 @@ impl BaseSrcImpl for NdiSrc { // BaseSrcBase::parent_query(element, query) // } - // Creates the audio buffers + + + //Creates the audio buffers fn create( &self, element: &BaseSrc, _offset: u64, _length: u32, ) -> Result { + println!("Principio create"); // Keep a local copy of the values of all our properties at this very moment. This // ensures that the mutex is never locked for long and the application wouldn't // have to block until this function returns when getting/setting property values @@ -728,13 +732,14 @@ impl BaseSrcImpl for NdiSrc { //TODO Set buffer size from data received from NDI let mut buffer = // gst::Buffer::with_size((n_samples as usize) * (info.bpf() as usize)).unwrap(); - gst::Buffer::with_size(200 * 400).unwrap(); + gst::Buffer::with_size(720 * 576 * 2).unwrap(); { let buffer = buffer.get_mut().unwrap(); let pts: gst::ClockTime = (video_frame.timestamp as u64).into(); let duration: gst::ClockTime = (334624).into(); - buffer.set_pts(pts); - buffer.set_duration(duration); + // buffer.set_pts(pts); + //buffer.set_pts(pts); + // buffer.set_duration(duration); // Map the buffer writable and create the actual samples let mut map = buffer.map_writable().unwrap(); let mut data = map.as_slice(); @@ -852,7 +857,7 @@ impl BaseSrcImpl for NdiSrc { } gst_debug!(self.cat, obj: element, "Produced buffer {:?}", buffer); - + println!("Final create"); Ok(buffer) } } From db5493d110740184a402e434637813d68cf73f49 Mon Sep 17 00:00:00 2001 From: galicaster Date: Tue, 24 Apr 2018 19:41:27 +0200 Subject: [PATCH 027/199] Clean code --- gst-plugin-ndi/src/ndilib.rs | 2 +- gst-plugin-ndi/src/ndisrc.rs | 748 +++++------------------------------ 2 files changed, 102 insertions(+), 648 deletions(-) diff --git a/gst-plugin-ndi/src/ndilib.rs b/gst-plugin-ndi/src/ndilib.rs index c851eabc..428c23a1 100644 --- a/gst-plugin-ndi/src/ndilib.rs +++ b/gst-plugin-ndi/src/ndilib.rs @@ -1,4 +1,4 @@ -#![allow(non_camel_case_types, non_upper_case_globals)] +#![allow(non_camel_case_types, non_upper_case_globals, non_snake_case)] use std::ptr; diff --git a/gst-plugin-ndi/src/ndisrc.rs b/gst-plugin-ndi/src/ndisrc.rs index 12d9c14a..6bdb7562 100644 --- a/gst-plugin-ndi/src/ndisrc.rs +++ b/gst-plugin-ndi/src/ndisrc.rs @@ -1,70 +1,40 @@ -// Copyright (C) 2018 Sebastian Dröge -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. +#![allow(non_camel_case_types, non_upper_case_globals, non_snake_case)] use glib; use gst; use gst::prelude::*; -//use gst_audio; use gst_video; use gst_base::prelude::*; -use byte_slice_cast::*; - use gst_plugin::base_src::*; use gst_plugin::element::*; use gst_plugin::object::*; use gst_plugin::properties::*; -use std::ops::Rem; use std::sync::Mutex; use std::{i32, u32}; -use num_traits::cast::NumCast; -use num_traits::float::Float; - use std::ptr; use std::ffi::{CStr, CString}; use ndilib::*; -// Default values of properties -const DEFAULT_SAMPLES_PER_BUFFER: u32 = 1024; -const DEFAULT_FREQ: u32 = 440; -const DEFAULT_VOLUME: f64 = 0.8; -const DEFAULT_MUTE: bool = false; -const DEFAULT_IS_LIVE: bool = false; - // Property value storage #[derive(Debug, Clone)] struct Settings { stream_name: String, - samples_per_buffer: u32, - freq: u32, - volume: f64, - mute: bool, - is_live: bool, } impl Default for Settings { fn default() -> Self { Settings { stream_name: String::from("Fixed ndi stream name"), - samples_per_buffer: DEFAULT_SAMPLES_PER_BUFFER, - freq: DEFAULT_FREQ, - volume: DEFAULT_VOLUME, - mute: DEFAULT_MUTE, - is_live: DEFAULT_IS_LIVE, } } } // Metadata for the properties -static PROPERTIES: [Property; 6] = [ +static PROPERTIES: [Property; 1] = [ Property::String( "stream-name", "Sream Name", @@ -72,44 +42,6 @@ static PROPERTIES: [Property; 6] = [ None, PropertyMutability::ReadWrite, ), - Property::UInt( - "samples-per-buffer", - "Samples Per Buffer", - "Number of samples per output buffer", - (1, u32::MAX), - DEFAULT_SAMPLES_PER_BUFFER, - PropertyMutability::ReadWrite, - ), - Property::UInt( - "freq", - "Frequency", - "Frequency", - (1, u32::MAX), - DEFAULT_FREQ, - PropertyMutability::ReadWrite, - ), - Property::Double( - "volume", - "Volume", - "Output volume", - (0.0, 10.0), - DEFAULT_VOLUME, - PropertyMutability::ReadWrite, - ), - Property::Boolean( - "mute", - "Mute", - "Mute", - DEFAULT_MUTE, - PropertyMutability::ReadWrite, - ), - Property::Boolean( - "is-live", - "Is Live", - "(Pseudo) live output", - DEFAULT_IS_LIVE, - PropertyMutability::ReadWrite, - ), ]; // Stream-specific state, i.e. audio format configuration @@ -117,9 +49,6 @@ static PROPERTIES: [Property; 6] = [ struct State { info: Option, recv: Option, - // sample_offset: u64, - // sample_stop: Option, - // accumulator: f64, } impl Default for State { @@ -127,9 +56,6 @@ impl Default for State { State { info: None, recv: None, - // sample_offset: 0, - // sample_stop: None, - // accumulator: 0.0, } } } @@ -152,7 +78,7 @@ impl NdiSrc { fn new(element: &BaseSrc) -> Box> { // Initialize live-ness and notify the base class that // we'd like to operate in Time format - element.set_live(DEFAULT_IS_LIVE); + element.set_live(true); element.set_format(gst::Format::Time); Box::new(Self { @@ -196,83 +122,39 @@ impl NdiSrc { ( "format", &gst::List::new(&[ - &gst_video::VideoFormat::Uyvy.to_string(), - &gst_video::VideoFormat::Rgb.to_string(), - &gst_video::VideoFormat::Gray8.to_string(), - ]), - ), - // ("layout", &"interleaved"), - // ("rate", &gst::IntRange::::new(1, i32::MAX)), - // ("channels", &gst::IntRange::::new(1, i32::MAX)), - ("width", &gst::IntRange::::new(0, i32::MAX)), - ("height", &gst::IntRange::::new(0, i32::MAX)), - ( - "framerate", - &gst::FractionRange::new( - gst::Fraction::new(0, 1), - gst::Fraction::new(i32::MAX, 1), + &gst_video::VideoFormat::Uyvy.to_string(), + //&gst_video::VideoFormat::Rgb.to_string(), + //&gst_video::VideoFormat::Gray8.to_string(), + ]), ), - ), - ], - ); - // The src pad template must be named "src" for basesrc - // and specific a pad that is always there - let src_pad_template = gst::PadTemplate::new( - "src", - gst::PadDirection::Src, - gst::PadPresence::Always, - &caps, - ); - klass.add_pad_template(src_pad_template); - - // Install all our properties - klass.install_properties(&PROPERTIES); - } - - fn process( - data: &mut [u8], - accumulator_ref: &mut f64, - freq: u32, - rate: u32, - channels: u32, - vol: f64, - ) { - use std::f64::consts::PI; - - // Reinterpret our byte-slice as a slice containing elements of the type - // we're interested in. GStreamer requires for raw audio that the alignment - // of memory is correct, so this will never ever fail unless there is an - // actual bug elsewhere. - let data = data.as_mut_slice_of::().unwrap(); - - // Convert all our parameters to the target type for calculations - let vol: F = NumCast::from(vol).unwrap(); - let freq = freq as f64; - let rate = rate as f64; - let two_pi = 2.0 * PI; - - // We're carrying a accumulator with up to 2pi around instead of working - // on the sample offset. High sample offsets cause too much inaccuracy when - // converted to floating point numbers and then iterated over in 1-steps - let mut accumulator = *accumulator_ref; - let step = two_pi * freq / rate; - - for chunk in data.chunks_mut(channels as usize) { - let value = vol * F::sin(NumCast::from(accumulator).unwrap()); - for sample in chunk { - *sample = value; - } - - accumulator += step; - if accumulator >= two_pi { - accumulator -= two_pi; - } - } - - *accumulator_ref = accumulator; + ("width", &gst::IntRange::::new(0, i32::MAX)), + ("height", &gst::IntRange::::new(0, i32::MAX)), + ( + "framerate", + &gst::FractionRange::new( + gst::Fraction::new(0, 1), + gst::Fraction::new(i32::MAX, 1), + ), + ), + ], + ); + // The src pad template must be named "src" for basesrc + // and specific a pad that is always there + let src_pad_template = gst::PadTemplate::new( + "src", + gst::PadDirection::Src, + gst::PadPresence::Always, + &caps, + ); + klass.add_pad_template(src_pad_template); + + // Install all our properties + klass.install_properties(&PROPERTIES); } } + + // Virtual methods of GObject itself impl ObjectImpl for NdiSrc { // Called whenever a value of a property is changed. It can be called @@ -298,70 +180,6 @@ impl ObjectImpl for NdiSrc { let _ = element.post_message(&gst::Message::new_latency().src(Some(&element)).build()); } - Property::UInt("samples-per-buffer", ..) => { - let mut settings = self.settings.lock().unwrap(); - let samples_per_buffer = value.get().unwrap(); - gst_info!( - self.cat, - obj: &element, - "Changing samples-per-buffer from {} to {}", - settings.samples_per_buffer, - samples_per_buffer - ); - settings.samples_per_buffer = samples_per_buffer; - drop(settings); - - let _ = - element.post_message(&gst::Message::new_latency().src(Some(&element)).build()); - } - Property::UInt("freq", ..) => { - let mut settings = self.settings.lock().unwrap(); - let freq = value.get().unwrap(); - gst_info!( - self.cat, - obj: &element, - "Changing freq from {} to {}", - settings.freq, - freq - ); - settings.freq = freq; - } - Property::Double("volume", ..) => { - let mut settings = self.settings.lock().unwrap(); - let volume = value.get().unwrap(); - gst_info!( - self.cat, - obj: &element, - "Changing volume from {} to {}", - settings.volume, - volume - ); - settings.volume = volume; - } - Property::Boolean("mute", ..) => { - let mut settings = self.settings.lock().unwrap(); - let mute = value.get().unwrap(); - gst_info!( - self.cat, - obj: &element, - "Changing mute from {} to {}", - settings.mute, - mute - ); - settings.mute = mute; - } - Property::Boolean("is-live", ..) => { - let mut settings = self.settings.lock().unwrap(); - let is_live = value.get().unwrap(); - gst_info!( - self.cat, - obj: &element, - "Changing is-live from {} to {}", - settings.is_live, - is_live - ); - settings.is_live = is_live; - } _ => unimplemented!(), } } @@ -377,26 +195,6 @@ impl ObjectImpl for NdiSrc { //TODO to_value supongo que solo funciona con numeros Ok(settings.stream_name.to_value()) } - Property::UInt("samples-per-buffer", ..) => { - let settings = self.settings.lock().unwrap(); - Ok(settings.samples_per_buffer.to_value()) - } - Property::UInt("freq", ..) => { - let settings = self.settings.lock().unwrap(); - Ok(settings.freq.to_value()) - } - Property::Double("volume", ..) => { - let settings = self.settings.lock().unwrap(); - Ok(settings.volume.to_value()) - } - Property::Boolean("mute", ..) => { - let settings = self.settings.lock().unwrap(); - Ok(settings.mute.to_value()) - } - Property::Boolean("is-live", ..) => { - let settings = self.settings.lock().unwrap(); - Ok(settings.is_live.to_value()) - } _ => unimplemented!(), } } @@ -404,21 +202,6 @@ impl ObjectImpl for NdiSrc { // Virtual methods of gst::Element. We override none impl ElementImpl for NdiSrc { - fn change_state( - &self, - element: &BaseSrc, - transition: gst::StateChange, - ) -> gst::StateChangeReturn { - // Configure live'ness once here just before starting the source - match transition { - gst::StateChange::ReadyToPaused => { - element.set_live(self.settings.lock().unwrap().is_live); - } - _ => (), - } - - element.parent_change_state(transition) - } } // Virtual methods of gst_base::BaseSrc @@ -430,7 +213,6 @@ impl BaseSrcImpl for NdiSrc { // We simply remember the resulting AudioInfo from the caps to be able to use this for knowing // the sample rate, etc. when creating buffers fn set_caps(&self, element: &BaseSrc, caps: &gst::CapsRef) -> bool { - use std::f64::consts::PI; let info = match gst_video::VideoInfo::from_caps(caps) { None => return false, @@ -442,47 +224,6 @@ impl BaseSrcImpl for NdiSrc { // TODO Puede que falle si no creamos la estructura de cero, pero si lo hacemos no podemos poner recv a none let mut state = self.state.lock().unwrap(); state.info = Some(info); - // *state = State { - // info: Some(info), - // recv: Some(NdiInstance{recv: pNDI_recv}), - // }; - - // element.set_blocksize(info.bpf() * (*self.settings.lock().unwrap()).samples_per_buffer); - // - // let settings = &*self.settings.lock().unwrap(); - // let mut state = self.state.lock().unwrap(); - // - // // If we have no caps yet, any old sample_offset and sample_stop will be - // // in nanoseconds - // let old_rate = match state.info { - // Some(ref info) => info.rate() as u64, - // None => gst::SECOND_VAL, - // }; - // - // // Update sample offset and accumulator based on the previous values and the - // // sample rate change, if any - // let old_sample_offset = state.sample_offset; - // let sample_offset = old_sample_offset - // .mul_div_floor(info.rate() as u64, old_rate) - // .unwrap(); - // - // let old_sample_stop = state.sample_stop; - // let sample_stop = - // old_sample_stop.map(|v| v.mul_div_floor(info.rate() as u64, old_rate).unwrap()); - // - // let accumulator = - // (sample_offset as f64).rem(2.0 * PI * (settings.freq as f64) / (info.rate() as f64)); - // - // *state = State { - // info: Some(info), - // sample_offset: sample_offset, - // sample_stop: sample_stop, - // accumulator: accumulator, - // }; - // - // drop(state); - // - // let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); true } @@ -592,41 +333,6 @@ impl BaseSrcImpl for NdiSrc { true } - // fn query(&self, element: &BaseSrc, query: &mut gst::QueryRef) -> bool { - // use gst::QueryView; - // - // match query.view_mut() { - // // We only work in Push mode. In Pull mode, create() could be called with - // // arbitrary offsets and we would have to produce for that specific offset - // QueryView::Scheduling(ref mut q) => { - // q.set(gst::SchedulingFlags::SEQUENTIAL, 1, -1, 0); - // q.add_scheduling_modes(&[gst::PadMode::Push]); - // return true; - // } - // // In Live mode we will have a latency equal to the number of samples in each buffer. - // // We can't output samples before they were produced, and the last sample of a buffer - // // is produced that much after the beginning, leading to this latency calculation - // QueryView::Latency(ref mut q) => { - // let settings = &*self.settings.lock().unwrap(); - // let state = self.state.lock().unwrap(); - // - // if let Some(ref info) = state.info { - // let latency = gst::SECOND - // .mul_div_floor(settings.samples_per_buffer as u64, info.rate() as u64) - // .unwrap(); - // gst_debug!(self.cat, obj: element, "Returning latency {}", latency); - // q.set(settings.is_live, latency, gst::CLOCK_TIME_NONE); - // return true; - // } else { - // return false; - // } - // } - // _ => (), - // } - // BaseSrcBase::parent_query(element, query) - // } - - //Creates the audio buffers fn create( @@ -639,11 +345,11 @@ impl BaseSrcImpl for NdiSrc { // Keep a local copy of the values of all our properties at this very moment. This // ensures that the mutex is never locked for long and the application wouldn't // have to block until this function returns when getting/setting property values - let settings = &*self.settings.lock().unwrap(); + let _settings = &*self.settings.lock().unwrap(); // Get a locked reference to our state, i.e. the input and output AudioInfo - let mut state = self.state.lock().unwrap(); - let info = match state.info { + let state = self.state.lock().unwrap(); + let _info = match state.info { None => { gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); return Err(gst::FlowReturn::NotNegotiated); @@ -656,342 +362,90 @@ impl BaseSrcImpl for NdiSrc { println!("pNDI_recv no encontrado"); gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); return Err(gst::FlowReturn::NotNegotiated); - } + } Some(ref recv) => recv.clone(), }; let pNDI_recv = recv.recv; unsafe{ - // loop { + // loop { let video_frame: NDIlib_video_frame_v2_t = Default::default(); let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); let metadata_frame: NDIlib_metadata_frame_t = Default::default(); - + //TODO Solo hacemos el buffer cuando tengamos un frame de video let mut frame = false; while !frame{ - let frame_type = NDIlib_recv_capture_v2( - pNDI_recv, - &video_frame, - &audio_frame, - &metadata_frame, - 1000, - ); - - match frame_type { - NDIlib_frame_type_e::NDIlib_frame_type_video => { - println!("Tengo video {:?}", video_frame); - frame = true; + let frame_type = NDIlib_recv_capture_v2( + pNDI_recv, + &video_frame, + &audio_frame, + &metadata_frame, + 1000, + ); + + match frame_type { + NDIlib_frame_type_e::NDIlib_frame_type_video => { + println!("Tengo video {:?}", video_frame); + frame = true; + } + NDIlib_frame_type_e::NDIlib_frame_type_audio => { + println!("Tengo audio {:?}", audio_frame); + } + NDIlib_frame_type_e::NDIlib_frame_type_metadata => { + println!( + "Tengo metadata {} '{}'", + metadata_frame.length, + CStr::from_ptr(metadata_frame.p_data) + .to_string_lossy() + .into_owned(), + ); + } + NDIlib_frame_type_e::NDIlib_frame_type_error => { + println!( + "Tengo error {} '{}'", + metadata_frame.length, + CStr::from_ptr(metadata_frame.p_data) + .to_string_lossy() + .into_owned(), + ); + // break; + } + _ => println!("Tengo {:?}", frame_type), } - NDIlib_frame_type_e::NDIlib_frame_type_audio => { - println!("Tengo audio {:?}", audio_frame); - } - NDIlib_frame_type_e::NDIlib_frame_type_metadata => { - println!( - "Tengo metadata {} '{}'", - metadata_frame.length, - CStr::from_ptr(metadata_frame.p_data) - .to_string_lossy() - .into_owned(), - ); - } - NDIlib_frame_type_e::NDIlib_frame_type_error => { - println!( - "Tengo error {} '{}'", - metadata_frame.length, - CStr::from_ptr(metadata_frame.p_data) - .to_string_lossy() - .into_owned(), - ); - // break; - } - _ => println!("Tengo {:?}", frame_type), } + // } + + + + let mut buffer = gst::Buffer::with_size(720 * 576 * 2).unwrap(); + //let mut buffer = gst::Buffer::from_slice(video_frame.p_data).unwrap(); + + + { + //rr let buffer = buffer.get_mut().unwrap(); + //rr let pts: gst::ClockTime = (video_frame.timestamp as u64).into(); + //rr let duration: gst::ClockTime = (334624).into(); + //rr // buffer.set_pts(pts); + //rr //buffer.set_pts(pts); + //rr // buffer.set_duration(duration); + //rr // Map the buffer writable and create the actual samples + //rr let mut map = buffer.map_writable().unwrap(); + //rr let mut data = map.as_slice(); + //rr //data = &mut video_frame.p_data; + //rr let a = CStr::from_ptr(video_frame.p_data); + //rr data = a.to_bytes(); + + } + + gst_debug!(self.cat, obj: element, "Produced buffer {:?}", buffer); + println!("Final create"); + Ok(buffer) } - // } - - - - - // // If a stop position is set (from a seek), only produce samples up to that - // // point but at most samples_per_buffer samples per buffer - // let n_samples = if let Some(sample_stop) = state.sample_stop { - // if sample_stop <= state.sample_offset { - // gst_log!(self.cat, obj: element, "At EOS"); - // return Err(gst::FlowReturn::Eos); - // } - // - // sample_stop - state.sample_offset - // } else { - // settings.samples_per_buffer as u64 - // }; - - // Allocate a new buffer of the required size, update the metadata with the - // current timestamp and duration and then fill it according to the current - // caps - //TODO Set buffer size from data received from NDI - let mut buffer = - // gst::Buffer::with_size((n_samples as usize) * (info.bpf() as usize)).unwrap(); - gst::Buffer::with_size(720 * 576 * 2).unwrap(); - { - let buffer = buffer.get_mut().unwrap(); - let pts: gst::ClockTime = (video_frame.timestamp as u64).into(); - let duration: gst::ClockTime = (334624).into(); - // buffer.set_pts(pts); - //buffer.set_pts(pts); - // buffer.set_duration(duration); - // Map the buffer writable and create the actual samples - let mut map = buffer.map_writable().unwrap(); - let mut data = map.as_slice(); - //data = &mut video_frame.p_data; - let a = CStr::from_ptr(video_frame.p_data); - data = a.to_bytes(); - - // // Calculate the current timestamp (PTS) and the next one, - // // and calculate the duration from the difference instead of - // // simply the number of samples to prevent rounding errors - // let pts = state - // .sample_offset - // .mul_div_floor(gst::SECOND_VAL, info.rate() as u64) - // .unwrap() - // .into(); - // let next_pts: gst::ClockTime = (state.sample_offset + n_samples) - // .mul_div_floor(gst::SECOND_VAL, info.rate() as u64) - // .unwrap() - // .into(); - // buffer.set_pts(pts); - // buffer.set_duration(next_pts - pts); - // - // // Map the buffer writable and create the actual samples - // let mut map = buffer.map_writable().unwrap(); - // let data = map.as_mut_slice(); - // - // if info.format() == gst_audio::AUDIO_FORMAT_F32 { - // Self::process::( - // data, - // &mut state.accumulator, - // settings.freq, - // info.rate(), - // info.channels(), - // settings.volume, - // ); - // } else { - // Self::process::( - // data, - // &mut state.accumulator, - // settings.freq, - // info.rate(), - // info.channels(), - // settings.volume, - // ); - // } - // } - // state.sample_offset += n_samples; - // drop(state); - // - // // If we're live, we are waiting until the time of the last sample in our buffer has - // // arrived. This is the very reason why we have to report that much latency. - // // A real live-source would of course only allow us to have the data available after - // // that latency, e.g. when capturing from a microphone, and no waiting from our side - // // would be necessary.. - // // - // // Waiting happens based on the pipeline clock, which means that a real live source - // // with its own clock would require various translations between the two clocks. - // // This is out of scope for the tutorial though. - // if element.is_live() { - // let clock = match element.get_clock() { - // None => return Ok(buffer), - // Some(clock) => clock, - // }; - // - // let segment = element - // .get_segment() - // .downcast::() - // .unwrap(); - // let base_time = element.get_base_time(); - // let running_time = segment.to_running_time(buffer.get_pts() + buffer.get_duration()); - // - // // The last sample's clock time is the base time of the element plus the - // // running time of the last sample - // let wait_until = running_time + base_time; - // if wait_until.is_none() { - // return Ok(buffer); - // } - // - // // Store the clock ID in our struct unless we're flushing anyway. - // // This allows to asynchronously cancel the waiting from unlock() - // // so that we immediately stop waiting on e.g. shutdown. - // let mut clock_wait = self.clock_wait.lock().unwrap(); - // if clock_wait.flushing { - // gst_debug!(self.cat, obj: element, "Flushing"); - // return Err(gst::FlowReturn::Flushing); - // } - // - // let id = clock.new_single_shot_id(wait_until).unwrap(); - // clock_wait.clock_id = Some(id.clone()); - // drop(clock_wait); - // - // gst_log!( - // self.cat, - // obj: element, - // "Waiting until {}, now {}", - // wait_until, - // clock.get_time() - // ); - // let (res, jitter) = id.wait(); - // gst_log!( - // self.cat, - // obj: element, - // "Waited res {:?} jitter {}", - // res, - // jitter - // ); - // self.clock_wait.lock().unwrap().clock_id.take(); - // - // // If the clock ID was unscheduled, unlock() was called - // // and we should return Flushing immediately. - // if res == gst::ClockReturn::Unscheduled { - // gst_debug!(self.cat, obj: element, "Flushing"); - // return Err(gst::FlowReturn::Flushing); - // } - } - - gst_debug!(self.cat, obj: element, "Produced buffer {:?}", buffer); - println!("Final create"); - Ok(buffer) - } } - fn fixate(&self, element: &BaseSrc, caps: gst::Caps) -> gst::Caps { - // Fixate the caps. BaseSrc will do some fixation for us, but - // as we allow any rate between 1 and MAX it would fixate to 1. 1Hz - // is generally not a useful sample rate. - // - // We fixate to the closest integer value to 48kHz that is possible - // here, and for good measure also decide that the closest value to 1 - // channel is good. - let mut caps = gst::Caps::truncate(caps); - { - let caps = caps.make_mut(); - let s = caps.get_mut_structure(0).unwrap(); - s.fixate_field_nearest_int("rate", 48_000); - s.fixate_field_nearest_int("channels", 1); - } - // Let BaseSrc fixate anything else for us. We could've alternatively have - // called Caps::fixate() here - element.parent_fixate(caps) - } - fn is_seekable(&self, _element: &BaseSrc) -> bool { - false - } - - // fn do_seek(&self, element: &BaseSrc, segment: &mut gst::Segment) -> bool { - // // Handle seeking here. For Time and Default (sample offset) seeks we can - // // do something and have to update our sample offset and accumulator accordingly. - // // - // // Also we should remember the stop time (so we can stop at that point), and if - // // reverse playback is requested. These values will all be used during buffer creation - // // and for calculating the timestamps, etc. - // - // if segment.get_rate() < 0.0 { - // gst_error!(self.cat, obj: element, "Reverse playback not supported"); - // return false; - // } - // - // let settings = *self.settings.lock().unwrap(); - // let mut state = self.state.lock().unwrap(); - // - // // We store sample_offset and sample_stop in nanoseconds if we - // // don't know any sample rate yet. It will be converted correctly - // // once a sample rate is known. - // let rate = match state.info { - // None => gst::SECOND_VAL, - // Some(ref info) => info.rate() as u64, - // }; - // - // if let Some(segment) = segment.downcast_ref::() { - // use std::f64::consts::PI; - // - // let sample_offset = segment - // .get_start() - // .unwrap() - // .mul_div_floor(rate, gst::SECOND_VAL) - // .unwrap(); - // - // let sample_stop = segment - // .get_stop() - // .map(|v| v.mul_div_floor(rate, gst::SECOND_VAL).unwrap()); - // - // let accumulator = - // (sample_offset as f64).rem(2.0 * PI * (settings.freq as f64) / (rate as f64)); - // - // gst_debug!( - // self.cat, - // obj: element, - // "Seeked to {}-{:?} (accum: {}) for segment {:?}", - // sample_offset, - // sample_stop, - // accumulator, - // segment - // ); - // - // *state = State { - // info: state.info.clone(), - // sample_offset: sample_offset, - // sample_stop: sample_stop, - // accumulator: accumulator, - // }; - // - // true - // } else if let Some(segment) = segment.downcast_ref::() { - // use std::f64::consts::PI; - // - // if state.info.is_none() { - // gst_error!( - // self.cat, - // obj: element, - // "Can only seek in Default format if sample rate is known" - // ); - // return false; - // } - // - // let sample_offset = segment.get_start().unwrap(); - // let sample_stop = segment.get_stop().0; - // - // let accumulator = - // (sample_offset as f64).rem(2.0 * PI * (settings.freq as f64) / (rate as f64)); - // - // gst_debug!( - // self.cat, - // obj: element, - // "Seeked to {}-{:?} (accum: {}) for segment {:?}", - // sample_offset, - // sample_stop, - // accumulator, - // segment - // ); - // - // *state = State { - // info: state.info.clone(), - // sample_offset: sample_offset, - // sample_stop: sample_stop, - // accumulator: accumulator, - // }; - // - // true - // } else { - // gst_error!( - // self.cat, - // obj: element, - // "Can't seek in format {:?}", - // segment.get_format() - // ); - // - // false - // } - // } fn unlock(&self, element: &BaseSrc) -> bool { // This should unblock the create() function ASAP, so we From 45fcccbd31777553464d31206a2aaba2bcfbca3e Mon Sep 17 00:00:00 2001 From: galicaster Date: Wed, 25 Apr 2018 10:32:20 +0200 Subject: [PATCH 028/199] Eurekagit statusgit status First iteration copying memory. --- gst-plugin-ndi/src/ndisrc.rs | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/gst-plugin-ndi/src/ndisrc.rs b/gst-plugin-ndi/src/ndisrc.rs index 6bdb7562..fd0f6ce9 100644 --- a/gst-plugin-ndi/src/ndisrc.rs +++ b/gst-plugin-ndi/src/ndisrc.rs @@ -418,23 +418,34 @@ impl BaseSrcImpl for NdiSrc { - let mut buffer = gst::Buffer::with_size(720 * 576 * 2).unwrap(); //let mut buffer = gst::Buffer::from_slice(video_frame.p_data).unwrap(); + let mut buffer = gst::Buffer::with_size(720 * 576 * 2).unwrap(); + + { + let mut len = 720 * 576 * 2; + let mut vec = unsafe { Vec::from_raw_parts(video_frame.p_data as *mut u8, len, len) }; + let mutbuffer = buffer.get_mut().unwrap(); + mutbuffer.copy_from_slice(0, &vec).unwrap(); + + } + { - //rr let buffer = buffer.get_mut().unwrap(); + /* + let buffer = buffer.get_mut().unwrap(); //rr let pts: gst::ClockTime = (video_frame.timestamp as u64).into(); //rr let duration: gst::ClockTime = (334624).into(); //rr // buffer.set_pts(pts); //rr //buffer.set_pts(pts); //rr // buffer.set_duration(duration); //rr // Map the buffer writable and create the actual samples - //rr let mut map = buffer.map_writable().unwrap(); - //rr let mut data = map.as_slice(); - //rr //data = &mut video_frame.p_data; - //rr let a = CStr::from_ptr(video_frame.p_data); - //rr data = a.to_bytes(); + let mut map = buffer.map_writable().unwrap(); + let mut data = map.as_slice(); + //data = &mut video_frame.p_data; + let a = CStr::from_ptr(video_frame.p_data); + data = a.to_bytes(); + */ } From bf25388216874aee472d6b4414f1a436eb923f3e Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Wed, 25 Apr 2018 12:42:41 +0200 Subject: [PATCH 029/199] Improved logs --- gst-plugin-ndi/src/ndisrc.rs | 90 ++++++++++++++++-------------------- 1 file changed, 41 insertions(+), 49 deletions(-) diff --git a/gst-plugin-ndi/src/ndisrc.rs b/gst-plugin-ndi/src/ndisrc.rs index fd0f6ce9..2fc89ad9 100644 --- a/gst-plugin-ndi/src/ndisrc.rs +++ b/gst-plugin-ndi/src/ndisrc.rs @@ -147,7 +147,7 @@ impl NdiSrc { &caps, ); klass.add_pad_template(src_pad_template); - + // Install all our properties klass.install_properties(&PROPERTIES); } @@ -238,32 +238,34 @@ impl BaseSrcImpl for NdiSrc { let mut state = self.state.lock().unwrap(); //let mut pNDI_recv = state.recv; unsafe { - //TODO Al inicializar el plugin if !NDIlib_initialize() { - //TODO delete exits - println!("Cannot run NDI: NDIlib_initialize error."); - ::std::process::exit(1); + //println!("Cannot run NDI: NDIlib_initialize error."); + gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_initialize error"]); + return false; } - //TODO valores por defecto + //TODO default values let NDI_find_create_desc: NDIlib_find_create_t = Default::default(); let pNDI_find = NDIlib_find_create_v2(&NDI_find_create_desc); if pNDI_find.is_null() { - println!("Cannot run NDI: NDIlib_find_create_v2 error."); - ::std::process::exit(1); + //println!("Cannot run NDI: NDIlib_find_create_v2 error."); + gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_find_create_v2 error"]); + return false; } let mut no_sources: u32 = 0; let mut p_sources = ptr::null(); - //TODO Evitar bloqueo + //TODO Delete while. If not, will loop until a source it's available while no_sources == 0 { p_sources = NDIlib_find_get_current_sources(pNDI_find, &mut no_sources as *mut u32); } // We need at least one source if p_sources.is_null() { - println!("Error getting NDIlib_find_get_current_sources."); - ::std::process::exit(1); + //println!("Error getting NDIlib_find_get_current_sources."); + gst_element_error!(element, gst::CoreError::Negotiation, ["Error getting NDIlib_find_get_current_sources"]); + return false; + //::std::process::exit(1); } println!( @@ -289,8 +291,10 @@ impl BaseSrcImpl for NdiSrc { let pNDI_recv = NDIlib_recv_create_v3(&NDI_recv_create_desc); if pNDI_recv.is_null() { - println!("Cannot run NDI: NDIlib_recv_create_v3 error."); - ::std::process::exit(1); + //println!("Cannot run NDI: NDIlib_recv_create_v3 error."); + gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_recv_create_v3 error"]); + return false; + //::std::process::exit(1); } // Destroy the NDI finder. We needed to have access to the pointers to p_sources[0] @@ -312,7 +316,7 @@ impl BaseSrcImpl for NdiSrc { NDIlib_recv_send_metadata(pNDI_recv, &enable_hw_accel); state.recv = Some(NdiInstance{recv: pNDI_recv}); - //TODO Otra opcion para guardar pNDI_recv es esta: + //TODO Another way to save NDI_recv variable // *state = State{ // info: state.info.clone(), // recv: Some(NdiInstance{recv: pNDI_recv}), @@ -341,7 +345,6 @@ impl BaseSrcImpl for NdiSrc { _offset: u64, _length: u32, ) -> Result { - println!("Principio create"); // Keep a local copy of the values of all our properties at this very moment. This // ensures that the mutex is never locked for long and the application wouldn't // have to block until this function returns when getting/setting property values @@ -358,8 +361,8 @@ impl BaseSrcImpl for NdiSrc { }; let recv = match state.recv{ None => { - //TODO Cambiar gst_element_error por uno mas descriptivo - println!("pNDI_recv no encontrado"); + //TODO Update gst_element_error with one more descriptive + //println!("pNDI_recv no encontrado"); gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); return Err(gst::FlowReturn::NotNegotiated); } @@ -372,8 +375,8 @@ impl BaseSrcImpl for NdiSrc { let video_frame: NDIlib_video_frame_v2_t = Default::default(); let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); let metadata_frame: NDIlib_metadata_frame_t = Default::default(); - - //TODO Solo hacemos el buffer cuando tengamos un frame de video + + //TODO Only create buffer when we got a video frame let mut frame = false; while !frame{ let frame_type = NDIlib_recv_capture_v2( @@ -383,14 +386,18 @@ impl BaseSrcImpl for NdiSrc { &metadata_frame, 1000, ); - + match frame_type { NDIlib_frame_type_e::NDIlib_frame_type_video => { - println!("Tengo video {:?}", video_frame); + //println!("Tengo video {:?}", video_frame); + //TODO Change gst_warning to gst_debug + gst_warning!(self.cat, obj: element, "Received video frame: {:?}", video_frame); frame = true; } NDIlib_frame_type_e::NDIlib_frame_type_audio => { println!("Tengo audio {:?}", audio_frame); + //TODO Change gst_warning to gst_debug + gst_debug!(self.cat, obj: element, "Received audio frame: {:?}", video_frame); } NDIlib_frame_type_e::NDIlib_frame_type_metadata => { println!( @@ -400,6 +407,8 @@ impl BaseSrcImpl for NdiSrc { .to_string_lossy() .into_owned(), ); + //TODO Change gst_warning to gst_debug + gst_debug!(self.cat, obj: element, "Received metadata frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned(),); } NDIlib_frame_type_e::NDIlib_frame_type_error => { println!( @@ -409,48 +418,31 @@ impl BaseSrcImpl for NdiSrc { .to_string_lossy() .into_owned(), ); + //TODO Change gst_warning to gst_debug + gst_debug!(self.cat, obj: element, "Received error frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned()); // break; } _ => println!("Tengo {:?}", frame_type), } } // } - - //let mut buffer = gst::Buffer::from_slice(video_frame.p_data).unwrap(); - let mut buffer = gst::Buffer::with_size(720 * 576 * 2).unwrap(); - - { - let mut len = 720 * 576 * 2; - let mut vec = unsafe { Vec::from_raw_parts(video_frame.p_data as *mut u8, len, len) }; - let mutbuffer = buffer.get_mut().unwrap(); - mutbuffer.copy_from_slice(0, &vec).unwrap(); - - } - - - + let buff_size = (video_frame.yres * video_frame.line_stride_in_bytes) as usize; + let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); { - /* + let vec = Vec::from_raw_parts(video_frame.p_data as *mut u8, buff_size, buff_size); + //TODO Set pts, duration and other info about the buffer + // let pts: gst::ClockTime = (video_frame.timestamp as u64).into(); + // let duration: gst::ClockTime = (334624).into(); let buffer = buffer.get_mut().unwrap(); - //rr let pts: gst::ClockTime = (video_frame.timestamp as u64).into(); - //rr let duration: gst::ClockTime = (334624).into(); - //rr // buffer.set_pts(pts); - //rr //buffer.set_pts(pts); - //rr // buffer.set_duration(duration); - //rr // Map the buffer writable and create the actual samples - let mut map = buffer.map_writable().unwrap(); - let mut data = map.as_slice(); - //data = &mut video_frame.p_data; - let a = CStr::from_ptr(video_frame.p_data); - data = a.to_bytes(); - */ + // buffer.set_pts(pts); + // buffer.set_duration(duration); + buffer.copy_from_slice(0, &vec).unwrap(); } gst_debug!(self.cat, obj: element, "Produced buffer {:?}", buffer); - println!("Final create"); Ok(buffer) } } From 53c692fac5c6636a6c5e9468fd9b07d41748a158 Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Thu, 26 Apr 2018 12:30:13 +0200 Subject: [PATCH 030/199] Connect to the stream inidicated in pipeline stream-name property --- gst-plugin-ndi/README.md | 2 ++ gst-plugin-ndi/src/ndisrc.rs | 31 +++++++++++++++++++++++++++---- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/gst-plugin-ndi/README.md b/gst-plugin-ndi/README.md index 62e42fef..a2d632e5 100644 --- a/gst-plugin-ndi/README.md +++ b/gst-plugin-ndi/README.md @@ -25,4 +25,6 @@ cargo build export GST_PLUGIN_PATH=`pwd`/target/debug gst-inspect-1.0 ndisrc GST_DEBUG=3 gst-launch-1.0 ndisrc ! video/x-raw, format=UYVY, width=720, height=576, framerate=1/25 ! videoconvert ! autovideosink + +GST_DEBUG=3 gst-launch-1.0 -v ndisrc stream-name="GC-DEV2 (Nombre_del_stream)" ! video/x-raw, format=UYVY, width=720, height=576, framerate=1/25 ! xvimagesink sync=false ``` diff --git a/gst-plugin-ndi/src/ndisrc.rs b/gst-plugin-ndi/src/ndisrc.rs index 2fc89ad9..487c5e77 100644 --- a/gst-plugin-ndi/src/ndisrc.rs +++ b/gst-plugin-ndi/src/ndisrc.rs @@ -15,6 +15,7 @@ use std::sync::Mutex; use std::{i32, u32}; use std::ptr; +use std::{thread, time}; use std::ffi::{CStr, CString}; use ndilib::*; @@ -236,6 +237,9 @@ impl BaseSrcImpl for NdiSrc { gst_warning!(self.cat, obj: element, "Starting"); let mut state = self.state.lock().unwrap(); + //let mut settings = self.settings.lock().unwrap(); + let settings = self.settings.lock().unwrap(); + //let mut pNDI_recv = state.recv; unsafe { if !NDIlib_initialize() { @@ -257,6 +261,8 @@ impl BaseSrcImpl for NdiSrc { let mut p_sources = ptr::null(); //TODO Delete while. If not, will loop until a source it's available while no_sources == 0 { + // TODO Sleep 1s to wait for all sources + thread::sleep(time::Duration::from_millis(1000)); p_sources = NDIlib_find_get_current_sources(pNDI_find, &mut no_sources as *mut u32); } @@ -268,13 +274,30 @@ impl BaseSrcImpl for NdiSrc { //::std::process::exit(1); } + let mut source: isize = -1; + for i in 0..no_sources as isize{ + if CStr::from_ptr((*p_sources.offset(i)).p_ndi_name) + .to_string_lossy() + .into_owned() == settings.stream_name{ + println!("coincide" ); + source = i; + break; + } + else{ + println!("No coincide" ); + } + } + if source == -1 { + gst_element_error!(element, gst::CoreError::Negotiation, ["Stream name not found"]); + return false; + } println!( "no_source {}: Name '{}' Address '{}'", no_sources, - CStr::from_ptr((*p_sources).p_ndi_name) + CStr::from_ptr((*p_sources.offset(source)).p_ndi_name) .to_string_lossy() .into_owned(), - CStr::from_ptr((*p_sources).p_ip_address) + CStr::from_ptr((*p_sources.offset(source)).p_ip_address) .to_string_lossy() .into_owned() ); @@ -395,9 +418,9 @@ impl BaseSrcImpl for NdiSrc { frame = true; } NDIlib_frame_type_e::NDIlib_frame_type_audio => { - println!("Tengo audio {:?}", audio_frame); + //println!("Tengo audio {:?}", audio_frame); //TODO Change gst_warning to gst_debug - gst_debug!(self.cat, obj: element, "Received audio frame: {:?}", video_frame); + gst_warning!(self.cat, obj: element, "Received audio frame: {:?}", video_frame); } NDIlib_frame_type_e::NDIlib_frame_type_metadata => { println!( From bc8950303cd20c28a2ecf0320e528eeb910f4208 Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Thu, 26 Apr 2018 13:53:50 +0200 Subject: [PATCH 031/199] Added field pts to buffer --- gst-plugin-ndi/src/ndisrc.rs | 37 +++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/gst-plugin-ndi/src/ndisrc.rs b/gst-plugin-ndi/src/ndisrc.rs index 487c5e77..c712c63f 100644 --- a/gst-plugin-ndi/src/ndisrc.rs +++ b/gst-plugin-ndi/src/ndisrc.rs @@ -16,6 +16,7 @@ use std::{i32, u32}; use std::ptr; use std::{thread, time}; +use std::time::{SystemTime, UNIX_EPOCH}; use std::ffi::{CStr, CString}; use ndilib::*; @@ -50,6 +51,7 @@ static PROPERTIES: [Property; 1] = [ struct State { info: Option, recv: Option, + start_pts: Option, } impl Default for State { @@ -57,6 +59,7 @@ impl Default for State { State { info: None, recv: None, + start_pts: None, } } } @@ -279,14 +282,10 @@ impl BaseSrcImpl for NdiSrc { if CStr::from_ptr((*p_sources.offset(i)).p_ndi_name) .to_string_lossy() .into_owned() == settings.stream_name{ - println!("coincide" ); source = i; break; } - else{ - println!("No coincide" ); - } - } + } if source == -1 { gst_element_error!(element, gst::CoreError::Negotiation, ["Stream name not found"]); return false; @@ -339,6 +338,12 @@ impl BaseSrcImpl for NdiSrc { NDIlib_recv_send_metadata(pNDI_recv, &enable_hw_accel); state.recv = Some(NdiInstance{recv: pNDI_recv}); + let start = SystemTime::now(); + let since_the_epoch = start.duration_since(UNIX_EPOCH) + .expect("Time went backwards"); + println!("{:?}", since_the_epoch); + state.start_pts = Some(since_the_epoch.as_secs() * 1000000000 + + since_the_epoch.subsec_nanos() as u64); //TODO Another way to save NDI_recv variable // *state = State{ // info: state.info.clone(), @@ -393,6 +398,14 @@ impl BaseSrcImpl for NdiSrc { }; let pNDI_recv = recv.recv; + // let start_pts = match state.start_pts { + // None => { + // gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); + // return Err(gst::FlowReturn::NotNegotiated); + // } + // Some(ref start_pts) => start_pts.clone(), + // }; + unsafe{ // loop { let video_frame: NDIlib_video_frame_v2_t = Default::default(); @@ -401,6 +414,7 @@ impl BaseSrcImpl for NdiSrc { //TODO Only create buffer when we got a video frame let mut frame = false; + let mut pts: u64 = 0; while !frame{ let frame_type = NDIlib_recv_capture_v2( pNDI_recv, @@ -414,13 +428,14 @@ impl BaseSrcImpl for NdiSrc { NDIlib_frame_type_e::NDIlib_frame_type_video => { //println!("Tengo video {:?}", video_frame); //TODO Change gst_warning to gst_debug - gst_warning!(self.cat, obj: element, "Received video frame: {:?}", video_frame); + gst_debug!(self.cat, obj: element, "Received video frame: {:?}", video_frame); frame = true; + pts = ((video_frame.timestamp as u64) * 100) - state.start_pts.unwrap(); } NDIlib_frame_type_e::NDIlib_frame_type_audio => { //println!("Tengo audio {:?}", audio_frame); //TODO Change gst_warning to gst_debug - gst_warning!(self.cat, obj: element, "Received audio frame: {:?}", video_frame); + gst_debug!(self.cat, obj: element, "Received audio frame: {:?}", video_frame); } NDIlib_frame_type_e::NDIlib_frame_type_metadata => { println!( @@ -456,11 +471,11 @@ impl BaseSrcImpl for NdiSrc { { let vec = Vec::from_raw_parts(video_frame.p_data as *mut u8, buff_size, buff_size); //TODO Set pts, duration and other info about the buffer - // let pts: gst::ClockTime = (video_frame.timestamp as u64).into(); - // let duration: gst::ClockTime = (334624).into(); + let pts: gst::ClockTime = (pts).into(); + //let duration: gst::ClockTime = (334624).into(); let buffer = buffer.get_mut().unwrap(); - // buffer.set_pts(pts); - // buffer.set_duration(duration); + buffer.set_pts(pts); + //buffer.set_duration(duration); buffer.copy_from_slice(0, &vec).unwrap(); } From d667536ec6df312198ebdaf32d40ce1929e9ff12 Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Mon, 30 Apr 2018 10:18:17 +0200 Subject: [PATCH 032/199] Added pts, and offsets to gstreamer buffer --- gst-plugin-ndi/src/ndisrc.rs | 61 ++++++++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 17 deletions(-) diff --git a/gst-plugin-ndi/src/ndisrc.rs b/gst-plugin-ndi/src/ndisrc.rs index c712c63f..013161fe 100644 --- a/gst-plugin-ndi/src/ndisrc.rs +++ b/gst-plugin-ndi/src/ndisrc.rs @@ -68,6 +68,10 @@ struct ClockWait { clock_id: Option, flushing: bool, } +struct Pts{ + pts: u64, + offset: u64, +} // Struct containing all the element data struct NdiSrc { @@ -75,6 +79,7 @@ struct NdiSrc { settings: Mutex, state: Mutex, clock_wait: Mutex, + pts: Mutex, } impl NdiSrc { @@ -97,6 +102,10 @@ impl NdiSrc { clock_id: None, flushing: true, }), + pts: Mutex::new(Pts{ + pts: 0, + offset: 0, + }), }) } @@ -263,11 +272,11 @@ impl BaseSrcImpl for NdiSrc { let mut no_sources: u32 = 0; let mut p_sources = ptr::null(); //TODO Delete while. If not, will loop until a source it's available - while no_sources == 0 { + //while no_sources == 0 { // TODO Sleep 1s to wait for all sources - thread::sleep(time::Duration::from_millis(1000)); + thread::sleep(time::Duration::from_millis(2000)); p_sources = NDIlib_find_get_current_sources(pNDI_find, &mut no_sources as *mut u32); - } + //} // We need at least one source if p_sources.is_null() { @@ -306,7 +315,7 @@ impl BaseSrcImpl for NdiSrc { // it will still be provided in BGRA let p_ndi_name = CString::new("Galicaster NDI Receiver").unwrap(); let NDI_recv_create_desc = NDIlib_recv_create_v3_t { - source_to_connect_to: *p_sources, + source_to_connect_to: *p_sources.offset(source), p_ndi_name: p_ndi_name.as_ptr(), ..Default::default() }; @@ -378,6 +387,7 @@ impl BaseSrcImpl for NdiSrc { // have to block until this function returns when getting/setting property values let _settings = &*self.settings.lock().unwrap(); + let mut pts2 = self.pts.lock().unwrap(); // Get a locked reference to our state, i.e. the input and output AudioInfo let state = self.state.lock().unwrap(); let _info = match state.info { @@ -387,6 +397,8 @@ impl BaseSrcImpl for NdiSrc { } Some(ref info) => info.clone(), }; + //let mut pNDI_recva = ptr::null(); + // { let recv = match state.recv{ None => { //TODO Update gst_element_error with one more descriptive @@ -397,14 +409,15 @@ impl BaseSrcImpl for NdiSrc { Some(ref recv) => recv.clone(), }; let pNDI_recv = recv.recv; + // } - // let start_pts = match state.start_pts { - // None => { - // gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); - // return Err(gst::FlowReturn::NotNegotiated); - // } - // Some(ref start_pts) => start_pts.clone(), - // }; + let start_pts = match state.start_pts { + None => { + gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); + return Err(gst::FlowReturn::NotNegotiated); + } + Some(ref start_pts) => start_pts.clone(), + }; unsafe{ // loop { @@ -426,15 +439,26 @@ impl BaseSrcImpl for NdiSrc { match frame_type { NDIlib_frame_type_e::NDIlib_frame_type_video => { - //println!("Tengo video {:?}", video_frame); - //TODO Change gst_warning to gst_debug gst_debug!(self.cat, obj: element, "Received video frame: {:?}", video_frame); frame = true; pts = ((video_frame.timestamp as u64) * 100) - state.start_pts.unwrap(); + // println!("{:?}", pts/1000000); + // pts = (video_frame.timecode as u64) * 100; + // if pts2.pts == 0{ + // pts2.pts = (video_frame.timecode as u64) * 100; + // pts = 0; + // } + // else{ + // // println!("{:?}", video_frame.timecode * 100); + // // println!("{:?}", pts2.pts); + // pts = (((video_frame.timecode as u64) * 100) - pts2.pts); + // println!("{:?}", pts/1000000); + // println!("heuhuehue"); + // // thread::sleep(time::Duration::from_millis(1000)); + // } + } NDIlib_frame_type_e::NDIlib_frame_type_audio => { - //println!("Tengo audio {:?}", audio_frame); - //TODO Change gst_warning to gst_debug gst_debug!(self.cat, obj: element, "Received audio frame: {:?}", video_frame); } NDIlib_frame_type_e::NDIlib_frame_type_metadata => { @@ -472,10 +496,13 @@ impl BaseSrcImpl for NdiSrc { let vec = Vec::from_raw_parts(video_frame.p_data as *mut u8, buff_size, buff_size); //TODO Set pts, duration and other info about the buffer let pts: gst::ClockTime = (pts).into(); - //let duration: gst::ClockTime = (334624).into(); + let duration: gst::ClockTime = (40000000).into(); let buffer = buffer.get_mut().unwrap(); buffer.set_pts(pts); - //buffer.set_duration(duration); + buffer.set_duration(duration); + buffer.set_offset(pts2.offset); + buffer.set_offset_end(pts2.offset + 1); + pts2.offset = pts2.offset +1; buffer.copy_from_slice(0, &vec).unwrap(); } From 385351e5adc6d926f1c2c96a9cd12143fd52be61 Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Wed, 30 May 2018 12:55:54 +0200 Subject: [PATCH 033/199] Refactor start_pts --- gst-plugin-ndi/src/ndisrc.rs | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/gst-plugin-ndi/src/ndisrc.rs b/gst-plugin-ndi/src/ndisrc.rs index 013161fe..6d2a2e08 100644 --- a/gst-plugin-ndi/src/ndisrc.rs +++ b/gst-plugin-ndi/src/ndisrc.rs @@ -441,21 +441,19 @@ impl BaseSrcImpl for NdiSrc { NDIlib_frame_type_e::NDIlib_frame_type_video => { gst_debug!(self.cat, obj: element, "Received video frame: {:?}", video_frame); frame = true; - pts = ((video_frame.timestamp as u64) * 100) - state.start_pts.unwrap(); - // println!("{:?}", pts/1000000); - // pts = (video_frame.timecode as u64) * 100; - // if pts2.pts == 0{ - // pts2.pts = (video_frame.timecode as u64) * 100; - // pts = 0; - // } - // else{ - // // println!("{:?}", video_frame.timecode * 100); - // // println!("{:?}", pts2.pts); - // pts = (((video_frame.timecode as u64) * 100) - pts2.pts); - // println!("{:?}", pts/1000000); - // println!("heuhuehue"); - // // thread::sleep(time::Duration::from_millis(1000)); - // } + //pts = ((video_frame.timestamp as u64) * 100) - state.start_pts.unwrap(); + // println!("{:?}", pts/1000000); + pts = (video_frame.timecode as u64) * 100; + if pts2.pts == 0{ + pts2.pts = (video_frame.timecode as u64) * 100; + pts = 0; + } + else{ + // println!("{:?}", video_frame.timecode * 100); + // println!("{:?}", pts2.pts); + pts = (((video_frame.timecode as u64) * 100) - pts2.pts); + //println!("{:?}", pts/1000000); + } } NDIlib_frame_type_e::NDIlib_frame_type_audio => { From c744353583b194f0bafbb0834ce0bb9a22a9439c Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Thu, 31 May 2018 11:14:11 +0200 Subject: [PATCH 034/199] Added ip parameter to connect to the stream --- gst-plugin-ndi/src/ndisrc.rs | 75 +++++++++++++++++++++++++++++------- 1 file changed, 62 insertions(+), 13 deletions(-) diff --git a/gst-plugin-ndi/src/ndisrc.rs b/gst-plugin-ndi/src/ndisrc.rs index 6d2a2e08..ce0fcb7f 100644 --- a/gst-plugin-ndi/src/ndisrc.rs +++ b/gst-plugin-ndi/src/ndisrc.rs @@ -25,18 +25,20 @@ use ndilib::*; #[derive(Debug, Clone)] struct Settings { stream_name: String, + ip: String, } impl Default for Settings { fn default() -> Self { Settings { stream_name: String::from("Fixed ndi stream name"), + ip: String::from(""), } } } // Metadata for the properties -static PROPERTIES: [Property; 1] = [ +static PROPERTIES: [Property; 2] = [ Property::String( "stream-name", "Sream Name", @@ -44,6 +46,13 @@ static PROPERTIES: [Property; 1] = [ None, PropertyMutability::ReadWrite, ), + Property::String( + "ip", + "Stream IP", + "Stream IP", + None, + PropertyMutability::ReadWrite, + ), ]; // Stream-specific state, i.e. audio format configuration @@ -190,6 +199,22 @@ impl ObjectImpl for NdiSrc { settings.stream_name = stream_name; drop(settings); + let _ = + element.post_message(&gst::Message::new_latency().src(Some(&element)).build()); + }, + Property::String("ip", ..) => { + let mut settings = self.settings.lock().unwrap(); + let ip = value.get().unwrap(); + gst_warning!( + self.cat, + obj: &element, + "Changing ip from {} to {}", + settings.ip, + ip + ); + settings.ip = ip; + drop(settings); + let _ = element.post_message(&gst::Message::new_latency().src(Some(&element)).build()); } @@ -207,6 +232,11 @@ impl ObjectImpl for NdiSrc { let settings = self.settings.lock().unwrap(); //TODO to_value supongo que solo funciona con numeros Ok(settings.stream_name.to_value()) + }, + Property::UInt("ip", ..) => { + let settings = self.settings.lock().unwrap(); + //TODO to_value supongo que solo funciona con numeros + Ok(settings.ip.to_value()) } _ => unimplemented!(), } @@ -260,22 +290,30 @@ impl BaseSrcImpl for NdiSrc { return false; } + let mut source: NDIlib_source_t = NDIlib_source_t{p_ndi_name: ptr::null(), + p_ip_address: ptr::null()}; + + // print!("{:?}", settings.stream_name); + // print!("{:?}", settings.ip); + //TODO default values let NDI_find_create_desc: NDIlib_find_create_t = Default::default(); let pNDI_find = NDIlib_find_create_v2(&NDI_find_create_desc); + let ip_ptr = CString::new(settings.ip.clone()).unwrap(); + if (ip_ptr == CString::new("").unwrap()){ if pNDI_find.is_null() { //println!("Cannot run NDI: NDIlib_find_create_v2 error."); gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_find_create_v2 error"]); return false; } - let mut no_sources: u32 = 0; + let mut total_sources: u32 = 0; let mut p_sources = ptr::null(); //TODO Delete while. If not, will loop until a source it's available - //while no_sources == 0 { + //while total_sources == 0 { // TODO Sleep 1s to wait for all sources thread::sleep(time::Duration::from_millis(2000)); - p_sources = NDIlib_find_get_current_sources(pNDI_find, &mut no_sources as *mut u32); + p_sources = NDIlib_find_get_current_sources(pNDI_find, &mut total_sources as *mut u32); //} // We need at least one source @@ -286,36 +324,47 @@ impl BaseSrcImpl for NdiSrc { //::std::process::exit(1); } - let mut source: isize = -1; - for i in 0..no_sources as isize{ + let mut no_source: isize = -1; + for i in 0..total_sources as isize{ if CStr::from_ptr((*p_sources.offset(i)).p_ndi_name) .to_string_lossy() .into_owned() == settings.stream_name{ - source = i; + no_source = i; break; } } - if source == -1 { + if no_source == -1 { gst_element_error!(element, gst::CoreError::Negotiation, ["Stream name not found"]); return false; } println!( - "no_source {}: Name '{}' Address '{}'", - no_sources, - CStr::from_ptr((*p_sources.offset(source)).p_ndi_name) + "Total_sources {}: Name '{}' Address '{}'", + total_sources, + CStr::from_ptr((*p_sources.offset(no_source)).p_ndi_name) .to_string_lossy() .into_owned(), - CStr::from_ptr((*p_sources.offset(source)).p_ip_address) + CStr::from_ptr((*p_sources.offset(no_source)).p_ip_address) .to_string_lossy() .into_owned() ); + source = *p_sources.offset(no_source).clone(); + } + else{ + source.p_ip_address = ip_ptr.as_ptr(); + println!( + "Address '{}'", + CStr::from_ptr(source.p_ip_address) + .to_string_lossy() + .into_owned() + ); + } // We now have at least one source, so we create a receiver to look at it. // We tell it that we prefer YCbCr video since it is more efficient for us. If the source has an alpha channel // it will still be provided in BGRA let p_ndi_name = CString::new("Galicaster NDI Receiver").unwrap(); let NDI_recv_create_desc = NDIlib_recv_create_v3_t { - source_to_connect_to: *p_sources.offset(source), + source_to_connect_to: source, p_ndi_name: p_ndi_name.as_ptr(), ..Default::default() }; From a1fc0efe3d6b1d6baa09398603e10aed491f010f Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Thu, 31 May 2018 11:16:29 +0200 Subject: [PATCH 035/199] Indent code correctly --- gst-plugin-ndi/src/ndisrc.rs | 890 +++++++++++++++++------------------ 1 file changed, 445 insertions(+), 445 deletions(-) diff --git a/gst-plugin-ndi/src/ndisrc.rs b/gst-plugin-ndi/src/ndisrc.rs index ce0fcb7f..d400eb68 100644 --- a/gst-plugin-ndi/src/ndisrc.rs +++ b/gst-plugin-ndi/src/ndisrc.rs @@ -39,20 +39,20 @@ impl Default for Settings { // Metadata for the properties static PROPERTIES: [Property; 2] = [ - Property::String( - "stream-name", - "Sream Name", - "Name of the streaming device", - None, - PropertyMutability::ReadWrite, - ), - Property::String( - "ip", - "Stream IP", - "Stream IP", - None, - PropertyMutability::ReadWrite, - ), +Property::String( + "stream-name", + "Sream Name", + "Name of the streaming device", + None, + PropertyMutability::ReadWrite, +), +Property::String( + "ip", + "Stream IP", + "Stream IP", + None, + PropertyMutability::ReadWrite, +), ]; // Stream-specific state, i.e. audio format configuration @@ -141,12 +141,12 @@ impl NdiSrc { let caps = gst::Caps::new_simple( "video/x-raw", &[ - ( - "format", - &gst::List::new(&[ - &gst_video::VideoFormat::Uyvy.to_string(), - //&gst_video::VideoFormat::Rgb.to_string(), - //&gst_video::VideoFormat::Gray8.to_string(), + ( + "format", + &gst::List::new(&[ + &gst_video::VideoFormat::Uyvy.to_string(), + //&gst_video::VideoFormat::Rgb.to_string(), + //&gst_video::VideoFormat::Gray8.to_string(), ]), ), ("width", &gst::IntRange::::new(0, i32::MAX)), @@ -158,462 +158,462 @@ impl NdiSrc { gst::Fraction::new(i32::MAX, 1), ), ), - ], - ); - // The src pad template must be named "src" for basesrc - // and specific a pad that is always there - let src_pad_template = gst::PadTemplate::new( - "src", - gst::PadDirection::Src, - gst::PadPresence::Always, - &caps, - ); - klass.add_pad_template(src_pad_template); - - // Install all our properties - klass.install_properties(&PROPERTIES); - } -} - - - -// Virtual methods of GObject itself -impl ObjectImpl for NdiSrc { - // Called whenever a value of a property is changed. It can be called - // at any time from any thread. - fn set_property(&self, obj: &glib::Object, id: u32, value: &glib::Value) { - let prop = &PROPERTIES[id as usize]; - let element = obj.clone().downcast::().unwrap(); - - match *prop { - Property::String("stream-name", ..) => { - let mut settings = self.settings.lock().unwrap(); - let stream_name = value.get().unwrap(); - gst_warning!( - self.cat, - obj: &element, - "Changing stream-name from {} to {}", - settings.stream_name, - stream_name - ); - settings.stream_name = stream_name; - drop(settings); - - let _ = - element.post_message(&gst::Message::new_latency().src(Some(&element)).build()); - }, - Property::String("ip", ..) => { - let mut settings = self.settings.lock().unwrap(); - let ip = value.get().unwrap(); - gst_warning!( - self.cat, - obj: &element, - "Changing ip from {} to {}", - settings.ip, - ip - ); - settings.ip = ip; - drop(settings); - - let _ = - element.post_message(&gst::Message::new_latency().src(Some(&element)).build()); - } - _ => unimplemented!(), - } - } - - // Called whenever a value of a property is read. It can be called - // at any time from any thread. - fn get_property(&self, _obj: &glib::Object, id: u32) -> Result { - let prop = &PROPERTIES[id as usize]; - - match *prop { - Property::UInt("stream-name", ..) => { - let settings = self.settings.lock().unwrap(); - //TODO to_value supongo que solo funciona con numeros - Ok(settings.stream_name.to_value()) - }, - Property::UInt("ip", ..) => { - let settings = self.settings.lock().unwrap(); - //TODO to_value supongo que solo funciona con numeros - Ok(settings.ip.to_value()) - } - _ => unimplemented!(), - } - } -} - -// Virtual methods of gst::Element. We override none -impl ElementImpl for NdiSrc { -} - -// Virtual methods of gst_base::BaseSrc -impl BaseSrcImpl for NdiSrc { - // Called whenever the input/output caps are changing, i.e. in the very beginning before data - // flow happens and whenever the situation in the pipeline is changing. All buffers after this - // call have the caps given here. - // - // We simply remember the resulting AudioInfo from the caps to be able to use this for knowing - // the sample rate, etc. when creating buffers - fn set_caps(&self, element: &BaseSrc, caps: &gst::CapsRef) -> bool { - - let info = match gst_video::VideoInfo::from_caps(caps) { - None => return false, - Some(info) => info, - }; - - gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); - - // TODO Puede que falle si no creamos la estructura de cero, pero si lo hacemos no podemos poner recv a none - let mut state = self.state.lock().unwrap(); - state.info = Some(info); - - true - } - - // Called when starting, so we can initialize all stream-related state to its defaults - fn start(&self, element: &BaseSrc) -> bool { - // Reset state - *self.state.lock().unwrap() = Default::default(); - self.unlock_stop(element); - - gst_warning!(self.cat, obj: element, "Starting"); - let mut state = self.state.lock().unwrap(); - //let mut settings = self.settings.lock().unwrap(); - let settings = self.settings.lock().unwrap(); - - //let mut pNDI_recv = state.recv; - unsafe { - if !NDIlib_initialize() { - //println!("Cannot run NDI: NDIlib_initialize error."); - gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_initialize error"]); - return false; - } - - let mut source: NDIlib_source_t = NDIlib_source_t{p_ndi_name: ptr::null(), - p_ip_address: ptr::null()}; - - // print!("{:?}", settings.stream_name); - // print!("{:?}", settings.ip); - - //TODO default values - let NDI_find_create_desc: NDIlib_find_create_t = Default::default(); - let pNDI_find = NDIlib_find_create_v2(&NDI_find_create_desc); - let ip_ptr = CString::new(settings.ip.clone()).unwrap(); - if (ip_ptr == CString::new("").unwrap()){ - if pNDI_find.is_null() { - //println!("Cannot run NDI: NDIlib_find_create_v2 error."); - gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_find_create_v2 error"]); - return false; - } - - let mut total_sources: u32 = 0; - let mut p_sources = ptr::null(); - //TODO Delete while. If not, will loop until a source it's available - //while total_sources == 0 { - // TODO Sleep 1s to wait for all sources - thread::sleep(time::Duration::from_millis(2000)); - p_sources = NDIlib_find_get_current_sources(pNDI_find, &mut total_sources as *mut u32); - //} - - // We need at least one source - if p_sources.is_null() { - //println!("Error getting NDIlib_find_get_current_sources."); - gst_element_error!(element, gst::CoreError::Negotiation, ["Error getting NDIlib_find_get_current_sources"]); - return false; - //::std::process::exit(1); - } - - let mut no_source: isize = -1; - for i in 0..total_sources as isize{ - if CStr::from_ptr((*p_sources.offset(i)).p_ndi_name) - .to_string_lossy() - .into_owned() == settings.stream_name{ - no_source = i; - break; - } - } - if no_source == -1 { - gst_element_error!(element, gst::CoreError::Negotiation, ["Stream name not found"]); - return false; - } - println!( - "Total_sources {}: Name '{}' Address '{}'", - total_sources, - CStr::from_ptr((*p_sources.offset(no_source)).p_ndi_name) - .to_string_lossy() - .into_owned(), - CStr::from_ptr((*p_sources.offset(no_source)).p_ip_address) - .to_string_lossy() - .into_owned() + ], ); - source = *p_sources.offset(no_source).clone(); + // The src pad template must be named "src" for basesrc + // and specific a pad that is always there + let src_pad_template = gst::PadTemplate::new( + "src", + gst::PadDirection::Src, + gst::PadPresence::Always, + &caps, + ); + klass.add_pad_template(src_pad_template); + + // Install all our properties + klass.install_properties(&PROPERTIES); } - else{ - source.p_ip_address = ip_ptr.as_ptr(); - println!( - "Address '{}'", - CStr::from_ptr(source.p_ip_address) - .to_string_lossy() - .into_owned() - ); - } - - // We now have at least one source, so we create a receiver to look at it. - // We tell it that we prefer YCbCr video since it is more efficient for us. If the source has an alpha channel - // it will still be provided in BGRA - let p_ndi_name = CString::new("Galicaster NDI Receiver").unwrap(); - let NDI_recv_create_desc = NDIlib_recv_create_v3_t { - source_to_connect_to: source, - p_ndi_name: p_ndi_name.as_ptr(), - ..Default::default() - }; - - let pNDI_recv = NDIlib_recv_create_v3(&NDI_recv_create_desc); - if pNDI_recv.is_null() { - //println!("Cannot run NDI: NDIlib_recv_create_v3 error."); - gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_recv_create_v3 error"]); - return false; - //::std::process::exit(1); - } - - // Destroy the NDI finder. We needed to have access to the pointers to p_sources[0] - NDIlib_find_destroy(pNDI_find); - - // We are now going to mark this source as being on program output for tally purposes (but not on preview) - let tally_state: NDIlib_tally_t = Default::default(); - NDIlib_recv_set_tally(pNDI_recv, &tally_state); - - // Enable Hardwqre Decompression support if this support has it. Please read the caveats in the documentation - // regarding this. There are times in which it might reduce the performance although on small stream numbers - // it almost always yields the same or better performance. - let data = CString::new("").unwrap(); - let enable_hw_accel = NDIlib_metadata_frame_t { - length: data.to_bytes().len() as i32, - timecode: 0, - p_data: data.as_ptr(), - }; - - NDIlib_recv_send_metadata(pNDI_recv, &enable_hw_accel); - state.recv = Some(NdiInstance{recv: pNDI_recv}); - let start = SystemTime::now(); - let since_the_epoch = start.duration_since(UNIX_EPOCH) - .expect("Time went backwards"); - println!("{:?}", since_the_epoch); - state.start_pts = Some(since_the_epoch.as_secs() * 1000000000 + - since_the_epoch.subsec_nanos() as u64); - //TODO Another way to save NDI_recv variable - // *state = State{ - // info: state.info.clone(), - // recv: Some(NdiInstance{recv: pNDI_recv}), - // }; - } - - true - } - - // Called when shutting down the element so we can release all stream-related state - fn stop(&self, element: &BaseSrc) -> bool { - // Reset state - *self.state.lock().unwrap() = Default::default(); - self.unlock(element); - - gst_info!(self.cat, obj: element, "Stopped"); - - true } - //Creates the audio buffers - fn create( - &self, - element: &BaseSrc, - _offset: u64, - _length: u32, - ) -> Result { - // Keep a local copy of the values of all our properties at this very moment. This - // ensures that the mutex is never locked for long and the application wouldn't - // have to block until this function returns when getting/setting property values - let _settings = &*self.settings.lock().unwrap(); - let mut pts2 = self.pts.lock().unwrap(); - // Get a locked reference to our state, i.e. the input and output AudioInfo - let state = self.state.lock().unwrap(); - let _info = match state.info { - None => { - gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); - return Err(gst::FlowReturn::NotNegotiated); + // Virtual methods of GObject itself + impl ObjectImpl for NdiSrc { + // Called whenever a value of a property is changed. It can be called + // at any time from any thread. + fn set_property(&self, obj: &glib::Object, id: u32, value: &glib::Value) { + let prop = &PROPERTIES[id as usize]; + let element = obj.clone().downcast::().unwrap(); + + match *prop { + Property::String("stream-name", ..) => { + let mut settings = self.settings.lock().unwrap(); + let stream_name = value.get().unwrap(); + gst_warning!( + self.cat, + obj: &element, + "Changing stream-name from {} to {}", + settings.stream_name, + stream_name + ); + settings.stream_name = stream_name; + drop(settings); + + let _ = + element.post_message(&gst::Message::new_latency().src(Some(&element)).build()); + }, + Property::String("ip", ..) => { + let mut settings = self.settings.lock().unwrap(); + let ip = value.get().unwrap(); + gst_warning!( + self.cat, + obj: &element, + "Changing ip from {} to {}", + settings.ip, + ip + ); + settings.ip = ip; + drop(settings); + + let _ = + element.post_message(&gst::Message::new_latency().src(Some(&element)).build()); + } + _ => unimplemented!(), } - Some(ref info) => info.clone(), - }; - //let mut pNDI_recva = ptr::null(); - // { - let recv = match state.recv{ - None => { - //TODO Update gst_element_error with one more descriptive - //println!("pNDI_recv no encontrado"); - gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); - return Err(gst::FlowReturn::NotNegotiated); + } + + // Called whenever a value of a property is read. It can be called + // at any time from any thread. + fn get_property(&self, _obj: &glib::Object, id: u32) -> Result { + let prop = &PROPERTIES[id as usize]; + + match *prop { + Property::UInt("stream-name", ..) => { + let settings = self.settings.lock().unwrap(); + //TODO to_value supongo que solo funciona con numeros + Ok(settings.stream_name.to_value()) + }, + Property::UInt("ip", ..) => { + let settings = self.settings.lock().unwrap(); + //TODO to_value supongo que solo funciona con numeros + Ok(settings.ip.to_value()) + } + _ => unimplemented!(), } - Some(ref recv) => recv.clone(), - }; - let pNDI_recv = recv.recv; - // } + } + } - let start_pts = match state.start_pts { - None => { - gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); - return Err(gst::FlowReturn::NotNegotiated); - } - Some(ref start_pts) => start_pts.clone(), - }; + // Virtual methods of gst::Element. We override none + impl ElementImpl for NdiSrc { + } - unsafe{ - // loop { - let video_frame: NDIlib_video_frame_v2_t = Default::default(); - let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); - let metadata_frame: NDIlib_metadata_frame_t = Default::default(); + // Virtual methods of gst_base::BaseSrc + impl BaseSrcImpl for NdiSrc { + // Called whenever the input/output caps are changing, i.e. in the very beginning before data + // flow happens and whenever the situation in the pipeline is changing. All buffers after this + // call have the caps given here. + // + // We simply remember the resulting AudioInfo from the caps to be able to use this for knowing + // the sample rate, etc. when creating buffers + fn set_caps(&self, element: &BaseSrc, caps: &gst::CapsRef) -> bool { - //TODO Only create buffer when we got a video frame - let mut frame = false; - let mut pts: u64 = 0; - while !frame{ - let frame_type = NDIlib_recv_capture_v2( - pNDI_recv, - &video_frame, - &audio_frame, - &metadata_frame, - 1000, - ); + let info = match gst_video::VideoInfo::from_caps(caps) { + None => return false, + Some(info) => info, + }; - match frame_type { - NDIlib_frame_type_e::NDIlib_frame_type_video => { - gst_debug!(self.cat, obj: element, "Received video frame: {:?}", video_frame); - frame = true; - //pts = ((video_frame.timestamp as u64) * 100) - state.start_pts.unwrap(); - // println!("{:?}", pts/1000000); - pts = (video_frame.timecode as u64) * 100; - if pts2.pts == 0{ - pts2.pts = (video_frame.timecode as u64) * 100; - pts = 0; + gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); + + // TODO Puede que falle si no creamos la estructura de cero, pero si lo hacemos no podemos poner recv a none + let mut state = self.state.lock().unwrap(); + state.info = Some(info); + + true + } + + // Called when starting, so we can initialize all stream-related state to its defaults + fn start(&self, element: &BaseSrc) -> bool { + // Reset state + *self.state.lock().unwrap() = Default::default(); + self.unlock_stop(element); + + gst_warning!(self.cat, obj: element, "Starting"); + let mut state = self.state.lock().unwrap(); + //let mut settings = self.settings.lock().unwrap(); + let settings = self.settings.lock().unwrap(); + + //let mut pNDI_recv = state.recv; + unsafe { + if !NDIlib_initialize() { + //println!("Cannot run NDI: NDIlib_initialize error."); + gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_initialize error"]); + return false; + } + + let mut source: NDIlib_source_t = NDIlib_source_t{p_ndi_name: ptr::null(), + p_ip_address: ptr::null()}; + + // print!("{:?}", settings.stream_name); + // print!("{:?}", settings.ip); + + //TODO default values + let NDI_find_create_desc: NDIlib_find_create_t = Default::default(); + let pNDI_find = NDIlib_find_create_v2(&NDI_find_create_desc); + let ip_ptr = CString::new(settings.ip.clone()).unwrap(); + if (ip_ptr == CString::new("").unwrap()){ + if pNDI_find.is_null() { + //println!("Cannot run NDI: NDIlib_find_create_v2 error."); + gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_find_create_v2 error"]); + return false; } - else{ - // println!("{:?}", video_frame.timecode * 100); - // println!("{:?}", pts2.pts); - pts = (((video_frame.timecode as u64) * 100) - pts2.pts); - //println!("{:?}", pts/1000000); + + let mut total_sources: u32 = 0; + let mut p_sources = ptr::null(); + //TODO Delete while. If not, will loop until a source it's available + //while total_sources == 0 { + // TODO Sleep 1s to wait for all sources + thread::sleep(time::Duration::from_millis(2000)); + p_sources = NDIlib_find_get_current_sources(pNDI_find, &mut total_sources as *mut u32); + //} + + // We need at least one source + if p_sources.is_null() { + //println!("Error getting NDIlib_find_get_current_sources."); + gst_element_error!(element, gst::CoreError::Negotiation, ["Error getting NDIlib_find_get_current_sources"]); + return false; + //::std::process::exit(1); + } + + let mut no_source: isize = -1; + for i in 0..total_sources as isize{ + if CStr::from_ptr((*p_sources.offset(i)).p_ndi_name) + .to_string_lossy() + .into_owned() == settings.stream_name{ + no_source = i; + break; + } + } + if no_source == -1 { + gst_element_error!(element, gst::CoreError::Negotiation, ["Stream name not found"]); + return false; + } + println!( + "Total_sources {}: Name '{}' Address '{}'", + total_sources, + CStr::from_ptr((*p_sources.offset(no_source)).p_ndi_name) + .to_string_lossy() + .into_owned(), + CStr::from_ptr((*p_sources.offset(no_source)).p_ip_address) + .to_string_lossy() + .into_owned() + ); + source = *p_sources.offset(no_source).clone(); + } + else{ + source.p_ip_address = ip_ptr.as_ptr(); + println!( + "Address '{}'", + CStr::from_ptr(source.p_ip_address) + .to_string_lossy() + .into_owned() + ); } + // We now have at least one source, so we create a receiver to look at it. + // We tell it that we prefer YCbCr video since it is more efficient for us. If the source has an alpha channel + // it will still be provided in BGRA + let p_ndi_name = CString::new("Galicaster NDI Receiver").unwrap(); + let NDI_recv_create_desc = NDIlib_recv_create_v3_t { + source_to_connect_to: source, + p_ndi_name: p_ndi_name.as_ptr(), + ..Default::default() + }; + + let pNDI_recv = NDIlib_recv_create_v3(&NDI_recv_create_desc); + if pNDI_recv.is_null() { + //println!("Cannot run NDI: NDIlib_recv_create_v3 error."); + gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_recv_create_v3 error"]); + return false; + //::std::process::exit(1); } - NDIlib_frame_type_e::NDIlib_frame_type_audio => { - gst_debug!(self.cat, obj: element, "Received audio frame: {:?}", video_frame); + + // Destroy the NDI finder. We needed to have access to the pointers to p_sources[0] + NDIlib_find_destroy(pNDI_find); + + // We are now going to mark this source as being on program output for tally purposes (but not on preview) + let tally_state: NDIlib_tally_t = Default::default(); + NDIlib_recv_set_tally(pNDI_recv, &tally_state); + + // Enable Hardwqre Decompression support if this support has it. Please read the caveats in the documentation + // regarding this. There are times in which it might reduce the performance although on small stream numbers + // it almost always yields the same or better performance. + let data = CString::new("").unwrap(); + let enable_hw_accel = NDIlib_metadata_frame_t { + length: data.to_bytes().len() as i32, + timecode: 0, + p_data: data.as_ptr(), + }; + + NDIlib_recv_send_metadata(pNDI_recv, &enable_hw_accel); + state.recv = Some(NdiInstance{recv: pNDI_recv}); + let start = SystemTime::now(); + let since_the_epoch = start.duration_since(UNIX_EPOCH) + .expect("Time went backwards"); + println!("{:?}", since_the_epoch); + state.start_pts = Some(since_the_epoch.as_secs() * 1000000000 + + since_the_epoch.subsec_nanos() as u64); + //TODO Another way to save NDI_recv variable + // *state = State{ + // info: state.info.clone(), + // recv: Some(NdiInstance{recv: pNDI_recv}), + // }; + } + + true + } + + // Called when shutting down the element so we can release all stream-related state + fn stop(&self, element: &BaseSrc) -> bool { + // Reset state + *self.state.lock().unwrap() = Default::default(); + self.unlock(element); + + gst_info!(self.cat, obj: element, "Stopped"); + + true + } + + + //Creates the audio buffers + fn create( + &self, + element: &BaseSrc, + _offset: u64, + _length: u32, + ) -> Result { + // Keep a local copy of the values of all our properties at this very moment. This + // ensures that the mutex is never locked for long and the application wouldn't + // have to block until this function returns when getting/setting property values + let _settings = &*self.settings.lock().unwrap(); + + let mut pts2 = self.pts.lock().unwrap(); + // Get a locked reference to our state, i.e. the input and output AudioInfo + let state = self.state.lock().unwrap(); + let _info = match state.info { + None => { + gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); + return Err(gst::FlowReturn::NotNegotiated); } - NDIlib_frame_type_e::NDIlib_frame_type_metadata => { - println!( - "Tengo metadata {} '{}'", - metadata_frame.length, - CStr::from_ptr(metadata_frame.p_data) - .to_string_lossy() - .into_owned(), + Some(ref info) => info.clone(), + }; + //let mut pNDI_recva = ptr::null(); + // { + let recv = match state.recv{ + None => { + //TODO Update gst_element_error with one more descriptive + //println!("pNDI_recv no encontrado"); + gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); + return Err(gst::FlowReturn::NotNegotiated); + } + Some(ref recv) => recv.clone(), + }; + let pNDI_recv = recv.recv; + // } + + let start_pts = match state.start_pts { + None => { + gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); + return Err(gst::FlowReturn::NotNegotiated); + } + Some(ref start_pts) => start_pts.clone(), + }; + + unsafe{ + // loop { + let video_frame: NDIlib_video_frame_v2_t = Default::default(); + let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); + let metadata_frame: NDIlib_metadata_frame_t = Default::default(); + + //TODO Only create buffer when we got a video frame + let mut frame = false; + let mut pts: u64 = 0; + while !frame{ + let frame_type = NDIlib_recv_capture_v2( + pNDI_recv, + &video_frame, + &audio_frame, + &metadata_frame, + 1000, ); - //TODO Change gst_warning to gst_debug - gst_debug!(self.cat, obj: element, "Received metadata frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned(),); + + match frame_type { + NDIlib_frame_type_e::NDIlib_frame_type_video => { + gst_debug!(self.cat, obj: element, "Received video frame: {:?}", video_frame); + frame = true; + //pts = ((video_frame.timestamp as u64) * 100) - state.start_pts.unwrap(); + // println!("{:?}", pts/1000000); + pts = (video_frame.timecode as u64) * 100; + if pts2.pts == 0{ + pts2.pts = (video_frame.timecode as u64) * 100; + pts = 0; + } + else{ + // println!("{:?}", video_frame.timecode * 100); + // println!("{:?}", pts2.pts); + pts = (((video_frame.timecode as u64) * 100) - pts2.pts); + //println!("{:?}", pts/1000000); + } + + } + NDIlib_frame_type_e::NDIlib_frame_type_audio => { + gst_debug!(self.cat, obj: element, "Received audio frame: {:?}", video_frame); + } + NDIlib_frame_type_e::NDIlib_frame_type_metadata => { + println!( + "Tengo metadata {} '{}'", + metadata_frame.length, + CStr::from_ptr(metadata_frame.p_data) + .to_string_lossy() + .into_owned(), + ); + //TODO Change gst_warning to gst_debug + gst_debug!(self.cat, obj: element, "Received metadata frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned(),); + } + NDIlib_frame_type_e::NDIlib_frame_type_error => { + println!( + "Tengo error {} '{}'", + metadata_frame.length, + CStr::from_ptr(metadata_frame.p_data) + .to_string_lossy() + .into_owned(), + ); + //TODO Change gst_warning to gst_debug + gst_debug!(self.cat, obj: element, "Received error frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned()); + // break; + } + _ => println!("Tengo {:?}", frame_type), + } } - NDIlib_frame_type_e::NDIlib_frame_type_error => { - println!( - "Tengo error {} '{}'", - metadata_frame.length, - CStr::from_ptr(metadata_frame.p_data) - .to_string_lossy() - .into_owned(), - ); - //TODO Change gst_warning to gst_debug - gst_debug!(self.cat, obj: element, "Received error frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned()); - // break; + // } + + + let buff_size = (video_frame.yres * video_frame.line_stride_in_bytes) as usize; + let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); + { + let vec = Vec::from_raw_parts(video_frame.p_data as *mut u8, buff_size, buff_size); + //TODO Set pts, duration and other info about the buffer + let pts: gst::ClockTime = (pts).into(); + let duration: gst::ClockTime = (40000000).into(); + let buffer = buffer.get_mut().unwrap(); + buffer.set_pts(pts); + buffer.set_duration(duration); + buffer.set_offset(pts2.offset); + buffer.set_offset_end(pts2.offset + 1); + pts2.offset = pts2.offset +1; + buffer.copy_from_slice(0, &vec).unwrap(); + } - _ => println!("Tengo {:?}", frame_type), + + gst_debug!(self.cat, obj: element, "Produced buffer {:?}", buffer); + Ok(buffer) } } - // } - let buff_size = (video_frame.yres * video_frame.line_stride_in_bytes) as usize; - let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); - { - let vec = Vec::from_raw_parts(video_frame.p_data as *mut u8, buff_size, buff_size); - //TODO Set pts, duration and other info about the buffer - let pts: gst::ClockTime = (pts).into(); - let duration: gst::ClockTime = (40000000).into(); - let buffer = buffer.get_mut().unwrap(); - buffer.set_pts(pts); - buffer.set_duration(duration); - buffer.set_offset(pts2.offset); - buffer.set_offset_end(pts2.offset + 1); - pts2.offset = pts2.offset +1; - buffer.copy_from_slice(0, &vec).unwrap(); + + fn unlock(&self, element: &BaseSrc) -> bool { + // This should unblock the create() function ASAP, so we + // just unschedule the clock it here, if any. + gst_debug!(self.cat, obj: element, "Unlocking"); + let mut clock_wait = self.clock_wait.lock().unwrap(); + if let Some(clock_id) = clock_wait.clock_id.take() { + clock_id.unschedule(); + } + clock_wait.flushing = true; + + true } - gst_debug!(self.cat, obj: element, "Produced buffer {:?}", buffer); - Ok(buffer) + fn unlock_stop(&self, element: &BaseSrc) -> bool { + // This signals that unlocking is done, so we can reset + // all values again. + gst_debug!(self.cat, obj: element, "Unlock stop"); + let mut clock_wait = self.clock_wait.lock().unwrap(); + clock_wait.flushing = false; + + true + } } - } + // This zero-sized struct is containing the static metadata of our element. It is only necessary to + // be able to implement traits on it, but e.g. a plugin that registers multiple elements with the + // same code would use this struct to store information about the concrete element. An example of + // this would be a plugin that wraps around a library that has multiple decoders with the same API, + // but wants (as it should) a separate element registered for each decoder. + struct NdiSrcStatic; + // The basic trait for registering the type: This returns a name for the type and registers the + // instance and class initializations functions with the type system, thus hooking everything + // together. + impl ImplTypeStatic for NdiSrcStatic { + fn get_name(&self) -> &str { + "NdiSrc" + } + fn new(&self, element: &BaseSrc) -> Box> { + NdiSrc::new(element) + } - fn unlock(&self, element: &BaseSrc) -> bool { - // This should unblock the create() function ASAP, so we - // just unschedule the clock it here, if any. - gst_debug!(self.cat, obj: element, "Unlocking"); - let mut clock_wait = self.clock_wait.lock().unwrap(); - if let Some(clock_id) = clock_wait.clock_id.take() { - clock_id.unschedule(); + fn class_init(&self, klass: &mut BaseSrcClass) { + NdiSrc::class_init(klass); + } } - clock_wait.flushing = true; - true - } - - fn unlock_stop(&self, element: &BaseSrc) -> bool { - // This signals that unlocking is done, so we can reset - // all values again. - gst_debug!(self.cat, obj: element, "Unlock stop"); - let mut clock_wait = self.clock_wait.lock().unwrap(); - clock_wait.flushing = false; - - true - } -} - -// This zero-sized struct is containing the static metadata of our element. It is only necessary to -// be able to implement traits on it, but e.g. a plugin that registers multiple elements with the -// same code would use this struct to store information about the concrete element. An example of -// this would be a plugin that wraps around a library that has multiple decoders with the same API, -// but wants (as it should) a separate element registered for each decoder. -struct NdiSrcStatic; - -// The basic trait for registering the type: This returns a name for the type and registers the -// instance and class initializations functions with the type system, thus hooking everything -// together. -impl ImplTypeStatic for NdiSrcStatic { - fn get_name(&self) -> &str { - "NdiSrc" - } - - fn new(&self, element: &BaseSrc) -> Box> { - NdiSrc::new(element) - } - - fn class_init(&self, klass: &mut BaseSrcClass) { - NdiSrc::class_init(klass); - } -} - -// Registers the type for our element, and then registers in GStreamer under -// the name "ndisrc" for being able to instantiate it via e.g. -// gst::ElementFactory::make(). -pub fn register(plugin: &gst::Plugin) { - let type_ = register_type(NdiSrcStatic); - gst::Element::register(plugin, "ndisrc", 0, type_); -} + // Registers the type for our element, and then registers in GStreamer under + // the name "ndisrc" for being able to instantiate it via e.g. + // gst::ElementFactory::make(). + pub fn register(plugin: &gst::Plugin) { + let type_ = register_type(NdiSrcStatic); + gst::Element::register(plugin, "ndisrc", 0, type_); + } From a89334a500dffcd248eda1d23ba2a97ca93d5aea Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Thu, 31 May 2018 11:19:01 +0200 Subject: [PATCH 036/199] Updated README --- gst-plugin-ndi/README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/gst-plugin-ndi/README.md b/gst-plugin-ndi/README.md index a2d632e5..35898a34 100644 --- a/gst-plugin-ndi/README.md +++ b/gst-plugin-ndi/README.md @@ -24,7 +24,14 @@ Test cargo build export GST_PLUGIN_PATH=`pwd`/target/debug gst-inspect-1.0 ndisrc -GST_DEBUG=3 gst-launch-1.0 ndisrc ! video/x-raw, format=UYVY, width=720, height=576, framerate=1/25 ! videoconvert ! autovideosink +GST_DEBUG=3 gst-launch-1.0 ndisrc ! video/x-raw, format=UYVY, width=720, height=576, framerate=25/1 ! videoconvert ! autovideosink -GST_DEBUG=3 gst-launch-1.0 -v ndisrc stream-name="GC-DEV2 (Nombre_del_stream)" ! video/x-raw, format=UYVY, width=720, height=576, framerate=1/25 ! xvimagesink sync=false +GST_DEBUG=3 gst-launch-1.0 -v ndisrc stream-name="GC-DEV2 (Nombre_del_stream)" ! video/x-raw, format=UYVY, width=720, height=576, framerate=25/1 ! xvimagesink sync=false +``` +``` +GST_DEBUG=3 gst-launch-1.0 -v ndisrc ip=10.21.10.103:5961 ! video/x-raw, format=UYVY, width=1920, height=1080, framerate=25/1 ! autovideosink sync=false + +or + +GST_DEBUG=3 gst-launch-1.0 -v ndisrc stream-name="MINI-DE-TELTEK.OFICINA.TELTEK.ES (NDI Signal Generator)" ! video/x-raw, format=UYVY, width=1920, height=1080, framerate=25/1 ! autovideosink sync=false ``` From e8ee6fd806c4460f5bed13bae12240f7cace937c Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Mon, 4 Jun 2018 13:44:27 +0200 Subject: [PATCH 037/199] Close NDI stream when closing gstreamer --- gst-plugin-ndi/src/ndilib.rs | 3 ++- gst-plugin-ndi/src/ndisrc.rs | 18 ++++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/gst-plugin-ndi/src/ndilib.rs b/gst-plugin-ndi/src/ndilib.rs index 428c23a1..09e581d3 100644 --- a/gst-plugin-ndi/src/ndilib.rs +++ b/gst-plugin-ndi/src/ndilib.rs @@ -15,7 +15,8 @@ extern "C" { pub fn NDIlib_recv_create_v3( p_create_settings: *const NDIlib_recv_create_v3_t, ) -> NDIlib_recv_instance_t; - pub fn NDIlib_find_destroy(p_instance: NDIlib_find_instance_t); + pub fn NDIlib_find_destroy(p_instance: NDIlib_recv_instance_t); + pub fn NDIlib_recv_destroy(p_instance: NDIlib_recv_instance_t); pub fn NDIlib_recv_set_tally( p_instance: NDIlib_recv_instance_t, p_tally: *const NDIlib_tally_t, diff --git a/gst-plugin-ndi/src/ndisrc.rs b/gst-plugin-ndi/src/ndisrc.rs index d400eb68..a3221115 100644 --- a/gst-plugin-ndi/src/ndisrc.rs +++ b/gst-plugin-ndi/src/ndisrc.rs @@ -415,9 +415,23 @@ impl NdiSrc { // Called when shutting down the element so we can release all stream-related state fn stop(&self, element: &BaseSrc) -> bool { // Reset state - *self.state.lock().unwrap() = Default::default(); + let state = self.state.lock().unwrap(); + let recv = match state.recv{ + None => { + //println!("pNDI_recv no encontrado"); + gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); + return true; + } + Some(ref recv) => recv.clone(), + }; + let pNDI_recv = recv.recv; + unsafe{ + NDIlib_recv_destroy(pNDI_recv); + //NDIlib_destroy(); + } + // Commented because when adding ndi destroy stopped in this line + //*self.state.lock().unwrap() = Default::default(); self.unlock(element); - gst_info!(self.cat, obj: element, "Stopped"); true From 3e9b1a85caa52a8de4c992e502605674e7a74d30 Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Mon, 4 Jun 2018 13:45:29 +0200 Subject: [PATCH 038/199] Use timestamp instead of timecode, some videos timecode is 0 --- gst-plugin-ndi/src/ndisrc.rs | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/gst-plugin-ndi/src/ndisrc.rs b/gst-plugin-ndi/src/ndisrc.rs index a3221115..0c1a7b53 100644 --- a/gst-plugin-ndi/src/ndisrc.rs +++ b/gst-plugin-ndi/src/ndisrc.rs @@ -506,15 +506,15 @@ impl NdiSrc { frame = true; //pts = ((video_frame.timestamp as u64) * 100) - state.start_pts.unwrap(); // println!("{:?}", pts/1000000); - pts = (video_frame.timecode as u64) * 100; + pts = (video_frame.timestamp as u64) * 100; if pts2.pts == 0{ - pts2.pts = (video_frame.timecode as u64) * 100; + pts2.pts = (video_frame.timestamp as u64) * 100; pts = 0; } else{ // println!("{:?}", video_frame.timecode * 100); // println!("{:?}", pts2.pts); - pts = (((video_frame.timecode as u64) * 100) - pts2.pts); + pts = (((video_frame.timestamp as u64) * 100) - pts2.pts); //println!("{:?}", pts/1000000); } @@ -523,24 +523,24 @@ impl NdiSrc { gst_debug!(self.cat, obj: element, "Received audio frame: {:?}", video_frame); } NDIlib_frame_type_e::NDIlib_frame_type_metadata => { - println!( - "Tengo metadata {} '{}'", - metadata_frame.length, - CStr::from_ptr(metadata_frame.p_data) - .to_string_lossy() - .into_owned(), - ); + // println!( + // "Tengo metadata {} '{}'", + // metadata_frame.length, + // CStr::from_ptr(metadata_frame.p_data) + // .to_string_lossy() + // .into_owned(), + // ); //TODO Change gst_warning to gst_debug gst_debug!(self.cat, obj: element, "Received metadata frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned(),); } NDIlib_frame_type_e::NDIlib_frame_type_error => { - println!( - "Tengo error {} '{}'", - metadata_frame.length, - CStr::from_ptr(metadata_frame.p_data) - .to_string_lossy() - .into_owned(), - ); + // println!( + // "Tengo error {} '{}'", + // metadata_frame.length, + // CStr::from_ptr(metadata_frame.p_data) + // .to_string_lossy() + // .into_owned(), + // ); //TODO Change gst_warning to gst_debug gst_debug!(self.cat, obj: element, "Received error frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned()); // break; From 24f2cd57ad99041a11b4df33c23dfb6a6bdd54f7 Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Tue, 12 Jun 2018 13:39:49 +0200 Subject: [PATCH 039/199] Refactor NDI receiver logic --- gst-plugin-ndi/src/ndisrc.rs | 221 +++++++++++++++++++++++++---------- 1 file changed, 159 insertions(+), 62 deletions(-) diff --git a/gst-plugin-ndi/src/ndisrc.rs b/gst-plugin-ndi/src/ndisrc.rs index 0c1a7b53..7408ebf5 100644 --- a/gst-plugin-ndi/src/ndisrc.rs +++ b/gst-plugin-ndi/src/ndisrc.rs @@ -5,6 +5,7 @@ use gst; use gst::prelude::*; use gst_video; use gst_base::prelude::*; +use gst::Fraction; use gst_plugin::base_src::*; use gst_plugin::element::*; @@ -144,6 +145,7 @@ impl NdiSrc { ( "format", &gst::List::new(&[ + //TODO add all formats? &gst_video::VideoFormat::Uyvy.to_string(), //&gst_video::VideoFormat::Rgb.to_string(), //&gst_video::VideoFormat::Gray8.to_string(), @@ -167,6 +169,7 @@ impl NdiSrc { gst::PadDirection::Src, gst::PadPresence::Always, &caps, + //&gst::Caps::new_any(), ); klass.add_pad_template(src_pad_template); @@ -247,6 +250,75 @@ impl NdiSrc { impl ElementImpl for NdiSrc { } + fn get_frame(ndisrc_struct: &NdiSrc, element: &BaseSrc, pNDI_recv : NDIlib_recv_instance_t, pts2 : &mut u64, pts : &mut u64) -> NDIlib_video_frame_v2_t{ + unsafe{ + let video_frame: NDIlib_video_frame_v2_t = Default::default(); + let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); + let metadata_frame: NDIlib_metadata_frame_t = Default::default(); + + //TODO Only create buffer when we got a video frame + let mut frame = false; + while !frame{ + let frame_type = NDIlib_recv_capture_v2( + pNDI_recv, + &video_frame, + ptr::null(), + ptr::null(), + 1000, + ); + + match frame_type { + NDIlib_frame_type_e::NDIlib_frame_type_video => { + gst_debug!(ndisrc_struct.cat, obj: element, "Received video frame: {:?}", video_frame); + frame = true; + //pts = ((video_frame.timestamp as u64) * 100) - state.start_pts.unwrap(); + // println!("{:?}", pts/1000000); + *pts = ((video_frame.timestamp as u64) * 100); + if *pts2 == 0{ + *pts2 = (video_frame.timestamp as u64) * 100; + *pts = 0; + } + else{ + // println!("{:?}", video_frame.timecode * 100); + // println!("{:?}", pts2.pts); + *pts = (((video_frame.timestamp as u64) * 100) - *pts2); + //println!("{:?}", pts/1000000); + } + + } + NDIlib_frame_type_e::NDIlib_frame_type_audio => { + gst_debug!(ndisrc_struct.cat, obj: element, "Received audio frame: {:?}", video_frame); + } + NDIlib_frame_type_e::NDIlib_frame_type_metadata => { + // println!( + // "Tengo metadata {} '{}'", + // metadata_frame.length, + // CStr::from_ptr(metadata_frame.p_data) + // .to_string_lossy() + // .into_owned(), + // ); + //TODO Change gst_warning to gst_debug + gst_debug!(ndisrc_struct.cat, obj: element, "Received metadata frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned(),); + } + NDIlib_frame_type_e::NDIlib_frame_type_error => { + // println!( + // "Tengo error {} '{}'", + // metadata_frame.length, + // CStr::from_ptr(metadata_frame.p_data) + // .to_string_lossy() + // .into_owned(), + // ); + //TODO Change gst_warning to gst_debug + gst_debug!(ndisrc_struct.cat, obj: element, "Received error frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned()); + // break; + } + _ => println!("Tengo {:?}", frame_type), + } + } + return video_frame; + } + } + // Virtual methods of gst_base::BaseSrc impl BaseSrcImpl for NdiSrc { // Called whenever the input/output caps are changing, i.e. in the very beginning before data @@ -483,72 +555,97 @@ impl NdiSrc { }; unsafe{ - // loop { - let video_frame: NDIlib_video_frame_v2_t = Default::default(); + // // loop { + let mut pts: u64 = 0; + let video_frame: NDIlib_video_frame_v2_t = get_frame(self, element, pNDI_recv, &mut pts2.pts, &mut pts); let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); let metadata_frame: NDIlib_metadata_frame_t = Default::default(); + //video_frame = get_frame(self, element, pNDI_recv, pts2.pts); - //TODO Only create buffer when we got a video frame - let mut frame = false; - let mut pts: u64 = 0; - while !frame{ - let frame_type = NDIlib_recv_capture_v2( - pNDI_recv, - &video_frame, - &audio_frame, - &metadata_frame, - 1000, - ); - - match frame_type { - NDIlib_frame_type_e::NDIlib_frame_type_video => { - gst_debug!(self.cat, obj: element, "Received video frame: {:?}", video_frame); - frame = true; - //pts = ((video_frame.timestamp as u64) * 100) - state.start_pts.unwrap(); - // println!("{:?}", pts/1000000); - pts = (video_frame.timestamp as u64) * 100; - if pts2.pts == 0{ - pts2.pts = (video_frame.timestamp as u64) * 100; - pts = 0; - } - else{ - // println!("{:?}", video_frame.timecode * 100); - // println!("{:?}", pts2.pts); - pts = (((video_frame.timestamp as u64) * 100) - pts2.pts); - //println!("{:?}", pts/1000000); - } - - } - NDIlib_frame_type_e::NDIlib_frame_type_audio => { - gst_debug!(self.cat, obj: element, "Received audio frame: {:?}", video_frame); - } - NDIlib_frame_type_e::NDIlib_frame_type_metadata => { - // println!( - // "Tengo metadata {} '{}'", - // metadata_frame.length, - // CStr::from_ptr(metadata_frame.p_data) - // .to_string_lossy() - // .into_owned(), - // ); - //TODO Change gst_warning to gst_debug - gst_debug!(self.cat, obj: element, "Received metadata frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned(),); - } - NDIlib_frame_type_e::NDIlib_frame_type_error => { - // println!( - // "Tengo error {} '{}'", - // metadata_frame.length, - // CStr::from_ptr(metadata_frame.p_data) - // .to_string_lossy() - // .into_owned(), - // ); - //TODO Change gst_warning to gst_debug - gst_debug!(self.cat, obj: element, "Received error frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned()); - // break; - } - _ => println!("Tengo {:?}", frame_type), - } - } + // //TODO Only create buffer when we got a video frame + // let mut frame = false; + // let mut pts: u64 = 0; + // while !frame{ + // let frame_type = NDIlib_recv_capture_v2( + // pNDI_recv, + // &video_frame, + // &audio_frame, + // &metadata_frame, + // 1000, + // ); + // + // match frame_type { + // NDIlib_frame_type_e::NDIlib_frame_type_video => { + // gst_debug!(self.cat, obj: element, "Received video frame: {:?}", video_frame); + // frame = true; + // //pts = ((video_frame.timestamp as u64) * 100) - state.start_pts.unwrap(); + // // println!("{:?}", pts/1000000); + // pts = (video_frame.timestamp as u64) * 100; + // if pts2.pts == 0{ + // pts2.pts = (video_frame.timestamp as u64) * 100; + // pts = 0; + // //let mut caps = _info.to_caps(); + // // let s = caps.get_mut_structure(0).unwrap(); + // // s.fixate_field_nearest_int("width", 1500); + // // s.fixate_field_nearest_int("framerate", 25/1); + // // self.set_caps(&self, s); + // // let mut caps = Some(gst::Caps::new_simple( + // // "video/x-raw", + // // &[("format", + // // &gst_video::VideoFormat::Uyvy.to_string() + // // )], + // // )); + // // caps.as_mut().map(|c| { + // // c.get_mut() + // // .unwrap() + // // .set_simple(&[("framerate", &(25/1 as i32)), ("width", &(1600 as i32)), ("height", &(1200 as i32))]) + // // }); + // //caps.unwrap().set_simple(&[("framerate", &(20/1 as i32))]); + // //let mut caps2 = _info.to_caps().unwrap(); + // // println!("{:?}", caps); + // //element.parent_set_caps(&caps.unwrap()); + // //element.parent_fixate(caps.unwrap()); + // //gst_video::VideoInfo::from_caps(&caps.unwrap()); + // //self.set_caps(element, &caps.unwrap()); + // } + // else{ + // // println!("{:?}", video_frame.timecode * 100); + // // println!("{:?}", pts2.pts); + // pts = (((video_frame.timestamp as u64) * 100) - pts2.pts); + // //println!("{:?}", pts/1000000); + // } + // + // } + // NDIlib_frame_type_e::NDIlib_frame_type_audio => { + // gst_debug!(self.cat, obj: element, "Received audio frame: {:?}", video_frame); + // } + // NDIlib_frame_type_e::NDIlib_frame_type_metadata => { + // // println!( + // // "Tengo metadata {} '{}'", + // // metadata_frame.length, + // // CStr::from_ptr(metadata_frame.p_data) + // // .to_string_lossy() + // // .into_owned(), + // // ); + // //TODO Change gst_warning to gst_debug + // gst_debug!(self.cat, obj: element, "Received metadata frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned(),); + // } + // NDIlib_frame_type_e::NDIlib_frame_type_error => { + // // println!( + // // "Tengo error {} '{}'", + // // metadata_frame.length, + // // CStr::from_ptr(metadata_frame.p_data) + // // .to_string_lossy() + // // .into_owned(), + // // ); + // //TODO Change gst_warning to gst_debug + // gst_debug!(self.cat, obj: element, "Received error frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned()); + // // break; + // } + // _ => println!("Tengo {:?}", frame_type), + // } // } + // // } let buff_size = (video_frame.yres * video_frame.line_stride_in_bytes) as usize; From 1ea558641ed36f7f45224c42e5e68d3a3cb06699 Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Tue, 12 Jun 2018 13:41:48 +0200 Subject: [PATCH 040/199] Get video caps from NDI stream --- gst-plugin-ndi/src/ndisrc.rs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/gst-plugin-ndi/src/ndisrc.rs b/gst-plugin-ndi/src/ndisrc.rs index 7408ebf5..04907f94 100644 --- a/gst-plugin-ndi/src/ndisrc.rs +++ b/gst-plugin-ndi/src/ndisrc.rs @@ -509,6 +509,41 @@ impl NdiSrc { true } + fn fixate(&self, element: &BaseSrc, caps: gst::Caps) -> gst::Caps { + //We need to set the correct caps resolution and framerate + let state = self.state.lock().unwrap(); + let recv = match state.recv{ + None => { + //TODO Update gst_element_error with one more descriptive + //println!("pNDI_recv no encontrado"); + gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); + //TODO if none not return anything + return caps; + } + Some(ref recv) => recv.clone(), + }; + + let pNDI_recv = recv.recv; + let mut pts2 = self.pts.lock().unwrap(); + let mut pts: u64 = 0; + + let video_frame: NDIlib_video_frame_v2_t = get_frame(self, element, pNDI_recv, &mut pts2.pts, &mut pts); + let mut caps = gst::Caps::truncate(caps); + { + let caps = caps.make_mut(); + let s = caps.get_mut_structure(0).unwrap(); + s.fixate_field_nearest_int("width", video_frame.xres); + s.fixate_field_nearest_int("height", video_frame.yres); + s.fixate_field_nearest_fraction("framerate", Fraction::new(video_frame.frame_rate_N, video_frame.frame_rate_D)); + //s.fixate_field_str("format", &gst_video::VideoFormat::Rgb.to_string()); + //caps.set_simple(&[("width", &(1600 as i32))]); + //s.set_value("width", &(1600 as i32)); + } + + // Let BaseSrc fixate anything else for us. We could've alternatively have + // called Caps::fixate() here + element.parent_fixate(caps) + } //Creates the audio buffers fn create( From 5302c67f973e1c56d8111d0c9cffdb33121ef104 Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Tue, 12 Jun 2018 14:40:17 +0200 Subject: [PATCH 041/199] First step to implement audiosrc --- gst-plugin-ndi/src/lib.rs | 6 +- .../src/{ndisrc.rs => ndiaudiosrc.rs} | 38 +- gst-plugin-ndi/src/ndivideosrc.rs | 765 ++++++++++++++++++ 3 files changed, 788 insertions(+), 21 deletions(-) rename gst-plugin-ndi/src/{ndisrc.rs => ndiaudiosrc.rs} (97%) create mode 100644 gst-plugin-ndi/src/ndivideosrc.rs diff --git a/gst-plugin-ndi/src/lib.rs b/gst-plugin-ndi/src/lib.rs index 26602e2e..4f7cd6b4 100644 --- a/gst-plugin-ndi/src/lib.rs +++ b/gst-plugin-ndi/src/lib.rs @@ -18,13 +18,15 @@ extern crate gstreamer_video as gst_video; extern crate byte_slice_cast; extern crate num_traits; -mod ndisrc; +mod ndivideosrc; +mod ndiaudiosrc; pub mod ndilib; // Plugin entry point that should register all elements provided by this plugin, // and everything else that this plugin might provide (e.g. typefinders or device providers). fn plugin_init(plugin: &gst::Plugin) -> bool { - ndisrc::register(plugin); + ndivideosrc::register(plugin); + ndiaudiosrc::register(plugin); true } diff --git a/gst-plugin-ndi/src/ndisrc.rs b/gst-plugin-ndi/src/ndiaudiosrc.rs similarity index 97% rename from gst-plugin-ndi/src/ndisrc.rs rename to gst-plugin-ndi/src/ndiaudiosrc.rs index 04907f94..c99ec80e 100644 --- a/gst-plugin-ndi/src/ndisrc.rs +++ b/gst-plugin-ndi/src/ndiaudiosrc.rs @@ -84,7 +84,7 @@ struct Pts{ } // Struct containing all the element data -struct NdiSrc { +struct NdiAudioSrc { cat: gst::DebugCategory, settings: Mutex, state: Mutex, @@ -92,7 +92,7 @@ struct NdiSrc { pts: Mutex, } -impl NdiSrc { +impl NdiAudioSrc { // Called when a new instance is to be created fn new(element: &BaseSrc) -> Box> { // Initialize live-ness and notify the base class that @@ -102,9 +102,9 @@ impl NdiSrc { Box::new(Self { cat: gst::DebugCategory::new( - "ndisrc", + "ndiaudiosrc", gst::DebugColorFlags::empty(), - "NewTek NDI Source", + "NewTek NDI Video Source", ), settings: Mutex::new(Default::default()), state: Mutex::new(Default::default()), @@ -131,7 +131,7 @@ impl NdiSrc { // Our element here can output f32 and f64 fn class_init(klass: &mut BaseSrcClass) { klass.set_metadata( - "NewTek NDI Source", + "NewTek NDI Audio Source", "Source", "NewTek NDI video/audio source", "Ruben Gonzalez ", @@ -181,7 +181,7 @@ impl NdiSrc { // Virtual methods of GObject itself - impl ObjectImpl for NdiSrc { + impl ObjectImpl for NdiAudioSrc { // Called whenever a value of a property is changed. It can be called // at any time from any thread. fn set_property(&self, obj: &glib::Object, id: u32, value: &glib::Value) { @@ -231,12 +231,12 @@ impl NdiSrc { let prop = &PROPERTIES[id as usize]; match *prop { - Property::UInt("stream-name", ..) => { + Property::String("stream-name", ..) => { let settings = self.settings.lock().unwrap(); //TODO to_value supongo que solo funciona con numeros Ok(settings.stream_name.to_value()) }, - Property::UInt("ip", ..) => { + Property::String("ip", ..) => { let settings = self.settings.lock().unwrap(); //TODO to_value supongo que solo funciona con numeros Ok(settings.ip.to_value()) @@ -247,10 +247,10 @@ impl NdiSrc { } // Virtual methods of gst::Element. We override none - impl ElementImpl for NdiSrc { + impl ElementImpl for NdiAudioSrc { } - fn get_frame(ndisrc_struct: &NdiSrc, element: &BaseSrc, pNDI_recv : NDIlib_recv_instance_t, pts2 : &mut u64, pts : &mut u64) -> NDIlib_video_frame_v2_t{ + fn get_frame(ndisrc_struct: &NdiAudioSrc, element: &BaseSrc, pNDI_recv : NDIlib_recv_instance_t, pts2 : &mut u64, pts : &mut u64) -> NDIlib_video_frame_v2_t{ unsafe{ let video_frame: NDIlib_video_frame_v2_t = Default::default(); let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); @@ -320,7 +320,7 @@ impl NdiSrc { } // Virtual methods of gst_base::BaseSrc - impl BaseSrcImpl for NdiSrc { + impl BaseSrcImpl for NdiAudioSrc { // Called whenever the input/output caps are changing, i.e. in the very beginning before data // flow happens and whenever the situation in the pipeline is changing. All buffers after this // call have the caps given here. @@ -737,29 +737,29 @@ impl NdiSrc { // same code would use this struct to store information about the concrete element. An example of // this would be a plugin that wraps around a library that has multiple decoders with the same API, // but wants (as it should) a separate element registered for each decoder. - struct NdiSrcStatic; + struct NdiAudioSrcStatic; // The basic trait for registering the type: This returns a name for the type and registers the // instance and class initializations functions with the type system, thus hooking everything // together. - impl ImplTypeStatic for NdiSrcStatic { + impl ImplTypeStatic for NdiAudioSrcStatic { fn get_name(&self) -> &str { - "NdiSrc" + "NdiAudioSrc" } fn new(&self, element: &BaseSrc) -> Box> { - NdiSrc::new(element) + NdiAudioSrc::new(element) } fn class_init(&self, klass: &mut BaseSrcClass) { - NdiSrc::class_init(klass); + NdiAudioSrc::class_init(klass); } } // Registers the type for our element, and then registers in GStreamer under - // the name "ndisrc" for being able to instantiate it via e.g. + // the name NdiAudioSrc for being able to instantiate it via e.g. // gst::ElementFactory::make(). pub fn register(plugin: &gst::Plugin) { - let type_ = register_type(NdiSrcStatic); - gst::Element::register(plugin, "ndisrc", 0, type_); + let type_ = register_type(NdiAudioSrcStatic); + gst::Element::register(plugin, "ndiaudiosrc", 0, type_); } diff --git a/gst-plugin-ndi/src/ndivideosrc.rs b/gst-plugin-ndi/src/ndivideosrc.rs new file mode 100644 index 00000000..8bf4d65e --- /dev/null +++ b/gst-plugin-ndi/src/ndivideosrc.rs @@ -0,0 +1,765 @@ +#![allow(non_camel_case_types, non_upper_case_globals, non_snake_case)] + +use glib; +use gst; +use gst::prelude::*; +use gst_video; +use gst_base::prelude::*; +use gst::Fraction; + +use gst_plugin::base_src::*; +use gst_plugin::element::*; +use gst_plugin::object::*; +use gst_plugin::properties::*; + +use std::sync::Mutex; +use std::{i32, u32}; + +use std::ptr; +use std::{thread, time}; +use std::time::{SystemTime, UNIX_EPOCH}; +use std::ffi::{CStr, CString}; + +use ndilib::*; + +// Property value storage +#[derive(Debug, Clone)] +struct Settings { + stream_name: String, + ip: String, +} + +impl Default for Settings { + fn default() -> Self { + Settings { + stream_name: String::from("Fixed ndi stream name"), + ip: String::from(""), + } + } +} + +// Metadata for the properties +static PROPERTIES: [Property; 2] = [ +Property::String( + "stream-name", + "Sream Name", + "Name of the streaming device", + None, + PropertyMutability::ReadWrite, +), +Property::String( + "ip", + "Stream IP", + "Stream IP", + None, + PropertyMutability::ReadWrite, +), +]; + +// Stream-specific state, i.e. audio format configuration +// and sample offset +struct State { + info: Option, + recv: Option, + start_pts: Option, +} + +impl Default for State { + fn default() -> State { + State { + info: None, + recv: None, + start_pts: None, + } + } +} + +struct ClockWait { + clock_id: Option, + flushing: bool, +} +struct Pts{ + pts: u64, + offset: u64, +} + +// Struct containing all the element data +struct NdiVideoSrc { + cat: gst::DebugCategory, + settings: Mutex, + state: Mutex, + clock_wait: Mutex, + pts: Mutex, +} + +impl NdiVideoSrc { + // Called when a new instance is to be created + fn new(element: &BaseSrc) -> Box> { + // Initialize live-ness and notify the base class that + // we'd like to operate in Time format + element.set_live(true); + element.set_format(gst::Format::Time); + + Box::new(Self { + cat: gst::DebugCategory::new( + "ndivideosrc", + gst::DebugColorFlags::empty(), + "NewTek NDI Video Source", + ), + settings: Mutex::new(Default::default()), + state: Mutex::new(Default::default()), + clock_wait: Mutex::new(ClockWait { + clock_id: None, + flushing: true, + }), + pts: Mutex::new(Pts{ + pts: 0, + offset: 0, + }), + }) + } + + // Called exactly once when registering the type. Used for + // setting up metadata for all instances, e.g. the name and + // classification and the pad templates with their caps. + // + // Actual instances can create pads based on those pad templates + // with a subset of the caps given here. In case of basesrc, + // a "src" and "sink" pad template are required here and the base class + // will automatically instantiate pads for them. + // + // Our element here can output f32 and f64 + fn class_init(klass: &mut BaseSrcClass) { + klass.set_metadata( + "NewTek NDI Video Source", + "Source", + "NewTek NDI video/audio source", + "Ruben Gonzalez ", + ); + + // On the src pad, we can produce F32/F64 with any sample rate + // and any number of channels + let caps = gst::Caps::new_simple( + "video/x-raw", + &[ + ( + "format", + &gst::List::new(&[ + //TODO add all formats? + &gst_video::VideoFormat::Uyvy.to_string(), + //&gst_video::VideoFormat::Rgb.to_string(), + //&gst_video::VideoFormat::Gray8.to_string(), + ]), + ), + ("width", &gst::IntRange::::new(0, i32::MAX)), + ("height", &gst::IntRange::::new(0, i32::MAX)), + ( + "framerate", + &gst::FractionRange::new( + gst::Fraction::new(0, 1), + gst::Fraction::new(i32::MAX, 1), + ), + ), + ], + ); + // The src pad template must be named "src" for basesrc + // and specific a pad that is always there + let src_pad_template = gst::PadTemplate::new( + "src", + gst::PadDirection::Src, + gst::PadPresence::Always, + &caps, + //&gst::Caps::new_any(), + ); + klass.add_pad_template(src_pad_template); + + // Install all our properties + klass.install_properties(&PROPERTIES); + } + } + + + + // Virtual methods of GObject itself + impl ObjectImpl for NdiVideoSrc { + // Called whenever a value of a property is changed. It can be called + // at any time from any thread. + fn set_property(&self, obj: &glib::Object, id: u32, value: &glib::Value) { + let prop = &PROPERTIES[id as usize]; + let element = obj.clone().downcast::().unwrap(); + + match *prop { + Property::String("stream-name", ..) => { + let mut settings = self.settings.lock().unwrap(); + let stream_name = value.get().unwrap(); + gst_warning!( + self.cat, + obj: &element, + "Changing stream-name from {} to {}", + settings.stream_name, + stream_name + ); + settings.stream_name = stream_name; + drop(settings); + + let _ = + element.post_message(&gst::Message::new_latency().src(Some(&element)).build()); + }, + Property::String("ip", ..) => { + let mut settings = self.settings.lock().unwrap(); + let ip = value.get().unwrap(); + gst_warning!( + self.cat, + obj: &element, + "Changing ip from {} to {}", + settings.ip, + ip + ); + settings.ip = ip; + drop(settings); + + let _ = + element.post_message(&gst::Message::new_latency().src(Some(&element)).build()); + } + _ => unimplemented!(), + } + } + + // Called whenever a value of a property is read. It can be called + // at any time from any thread. + fn get_property(&self, _obj: &glib::Object, id: u32) -> Result { + let prop = &PROPERTIES[id as usize]; + + match *prop { + Property::String("stream-name", ..) => { + let settings = self.settings.lock().unwrap(); + //TODO to_value supongo que solo funciona con numeros + Ok(settings.stream_name.to_value()) + }, + Property::String("ip", ..) => { + let settings = self.settings.lock().unwrap(); + //TODO to_value supongo que solo funciona con numeros + Ok(settings.ip.to_value()) + } + _ => unimplemented!(), + } + } + } + + // Virtual methods of gst::Element. We override none + impl ElementImpl for NdiVideoSrc { + } + + fn get_frame(ndisrc_struct: &NdiVideoSrc, element: &BaseSrc, pNDI_recv : NDIlib_recv_instance_t, pts2 : &mut u64, pts : &mut u64) -> NDIlib_video_frame_v2_t{ + unsafe{ + let video_frame: NDIlib_video_frame_v2_t = Default::default(); + let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); + let metadata_frame: NDIlib_metadata_frame_t = Default::default(); + + //TODO Only create buffer when we got a video frame + let mut frame = false; + while !frame{ + let frame_type = NDIlib_recv_capture_v2( + pNDI_recv, + &video_frame, + ptr::null(), + ptr::null(), + 1000, + ); + + match frame_type { + NDIlib_frame_type_e::NDIlib_frame_type_video => { + gst_debug!(ndisrc_struct.cat, obj: element, "Received video frame: {:?}", video_frame); + frame = true; + //pts = ((video_frame.timestamp as u64) * 100) - state.start_pts.unwrap(); + // println!("{:?}", pts/1000000); + *pts = ((video_frame.timestamp as u64) * 100); + if *pts2 == 0{ + *pts2 = (video_frame.timestamp as u64) * 100; + *pts = 0; + } + else{ + // println!("{:?}", video_frame.timecode * 100); + // println!("{:?}", pts2.pts); + *pts = (((video_frame.timestamp as u64) * 100) - *pts2); + //println!("{:?}", pts/1000000); + } + + } + NDIlib_frame_type_e::NDIlib_frame_type_audio => { + gst_debug!(ndisrc_struct.cat, obj: element, "Received audio frame: {:?}", video_frame); + } + NDIlib_frame_type_e::NDIlib_frame_type_metadata => { + // println!( + // "Tengo metadata {} '{}'", + // metadata_frame.length, + // CStr::from_ptr(metadata_frame.p_data) + // .to_string_lossy() + // .into_owned(), + // ); + //TODO Change gst_warning to gst_debug + gst_debug!(ndisrc_struct.cat, obj: element, "Received metadata frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned(),); + } + NDIlib_frame_type_e::NDIlib_frame_type_error => { + // println!( + // "Tengo error {} '{}'", + // metadata_frame.length, + // CStr::from_ptr(metadata_frame.p_data) + // .to_string_lossy() + // .into_owned(), + // ); + //TODO Change gst_warning to gst_debug + gst_debug!(ndisrc_struct.cat, obj: element, "Received error frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned()); + // break; + } + _ => println!("Tengo {:?}", frame_type), + } + } + return video_frame; + } + } + + // Virtual methods of gst_base::BaseSrc + impl BaseSrcImpl for NdiVideoSrc { + // Called whenever the input/output caps are changing, i.e. in the very beginning before data + // flow happens and whenever the situation in the pipeline is changing. All buffers after this + // call have the caps given here. + // + // We simply remember the resulting AudioInfo from the caps to be able to use this for knowing + // the sample rate, etc. when creating buffers + fn set_caps(&self, element: &BaseSrc, caps: &gst::CapsRef) -> bool { + + let info = match gst_video::VideoInfo::from_caps(caps) { + None => return false, + Some(info) => info, + }; + + gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); + + // TODO Puede que falle si no creamos la estructura de cero, pero si lo hacemos no podemos poner recv a none + let mut state = self.state.lock().unwrap(); + state.info = Some(info); + + true + } + + // Called when starting, so we can initialize all stream-related state to its defaults + fn start(&self, element: &BaseSrc) -> bool { + // Reset state + *self.state.lock().unwrap() = Default::default(); + self.unlock_stop(element); + + gst_warning!(self.cat, obj: element, "Starting"); + let mut state = self.state.lock().unwrap(); + //let mut settings = self.settings.lock().unwrap(); + let settings = self.settings.lock().unwrap(); + + //let mut pNDI_recv = state.recv; + unsafe { + if !NDIlib_initialize() { + //println!("Cannot run NDI: NDIlib_initialize error."); + gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_initialize error"]); + return false; + } + + let mut source: NDIlib_source_t = NDIlib_source_t{p_ndi_name: ptr::null(), + p_ip_address: ptr::null()}; + + // print!("{:?}", settings.stream_name); + // print!("{:?}", settings.ip); + + //TODO default values + let NDI_find_create_desc: NDIlib_find_create_t = Default::default(); + let pNDI_find = NDIlib_find_create_v2(&NDI_find_create_desc); + let ip_ptr = CString::new(settings.ip.clone()).unwrap(); + if (ip_ptr == CString::new("").unwrap()){ + if pNDI_find.is_null() { + //println!("Cannot run NDI: NDIlib_find_create_v2 error."); + gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_find_create_v2 error"]); + return false; + } + + let mut total_sources: u32 = 0; + let mut p_sources = ptr::null(); + //TODO Delete while. If not, will loop until a source it's available + //while total_sources == 0 { + // TODO Sleep 1s to wait for all sources + thread::sleep(time::Duration::from_millis(2000)); + p_sources = NDIlib_find_get_current_sources(pNDI_find, &mut total_sources as *mut u32); + //} + + // We need at least one source + if p_sources.is_null() { + //println!("Error getting NDIlib_find_get_current_sources."); + gst_element_error!(element, gst::CoreError::Negotiation, ["Error getting NDIlib_find_get_current_sources"]); + return false; + //::std::process::exit(1); + } + + let mut no_source: isize = -1; + for i in 0..total_sources as isize{ + if CStr::from_ptr((*p_sources.offset(i)).p_ndi_name) + .to_string_lossy() + .into_owned() == settings.stream_name{ + no_source = i; + break; + } + } + if no_source == -1 { + gst_element_error!(element, gst::CoreError::Negotiation, ["Stream name not found"]); + return false; + } + println!( + "Total_sources {}: Name '{}' Address '{}'", + total_sources, + CStr::from_ptr((*p_sources.offset(no_source)).p_ndi_name) + .to_string_lossy() + .into_owned(), + CStr::from_ptr((*p_sources.offset(no_source)).p_ip_address) + .to_string_lossy() + .into_owned() + ); + source = *p_sources.offset(no_source).clone(); + } + else{ + source.p_ip_address = ip_ptr.as_ptr(); + println!( + "Address '{}'", + CStr::from_ptr(source.p_ip_address) + .to_string_lossy() + .into_owned() + ); + } + + // We now have at least one source, so we create a receiver to look at it. + // We tell it that we prefer YCbCr video since it is more efficient for us. If the source has an alpha channel + // it will still be provided in BGRA + let p_ndi_name = CString::new("Galicaster NDI Receiver").unwrap(); + let NDI_recv_create_desc = NDIlib_recv_create_v3_t { + source_to_connect_to: source, + p_ndi_name: p_ndi_name.as_ptr(), + ..Default::default() + }; + + let pNDI_recv = NDIlib_recv_create_v3(&NDI_recv_create_desc); + if pNDI_recv.is_null() { + //println!("Cannot run NDI: NDIlib_recv_create_v3 error."); + gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_recv_create_v3 error"]); + return false; + //::std::process::exit(1); + } + + // Destroy the NDI finder. We needed to have access to the pointers to p_sources[0] + NDIlib_find_destroy(pNDI_find); + + // We are now going to mark this source as being on program output for tally purposes (but not on preview) + let tally_state: NDIlib_tally_t = Default::default(); + NDIlib_recv_set_tally(pNDI_recv, &tally_state); + + // Enable Hardwqre Decompression support if this support has it. Please read the caveats in the documentation + // regarding this. There are times in which it might reduce the performance although on small stream numbers + // it almost always yields the same or better performance. + let data = CString::new("").unwrap(); + let enable_hw_accel = NDIlib_metadata_frame_t { + length: data.to_bytes().len() as i32, + timecode: 0, + p_data: data.as_ptr(), + }; + + NDIlib_recv_send_metadata(pNDI_recv, &enable_hw_accel); + state.recv = Some(NdiInstance{recv: pNDI_recv}); + let start = SystemTime::now(); + let since_the_epoch = start.duration_since(UNIX_EPOCH) + .expect("Time went backwards"); + println!("{:?}", since_the_epoch); + state.start_pts = Some(since_the_epoch.as_secs() * 1000000000 + + since_the_epoch.subsec_nanos() as u64); + //TODO Another way to save NDI_recv variable + // *state = State{ + // info: state.info.clone(), + // recv: Some(NdiInstance{recv: pNDI_recv}), + // }; + } + + true + } + + // Called when shutting down the element so we can release all stream-related state + fn stop(&self, element: &BaseSrc) -> bool { + // Reset state + let state = self.state.lock().unwrap(); + let recv = match state.recv{ + None => { + //println!("pNDI_recv no encontrado"); + gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); + return true; + } + Some(ref recv) => recv.clone(), + }; + let pNDI_recv = recv.recv; + unsafe{ + NDIlib_recv_destroy(pNDI_recv); + //NDIlib_destroy(); + } + // Commented because when adding ndi destroy stopped in this line + //*self.state.lock().unwrap() = Default::default(); + self.unlock(element); + gst_info!(self.cat, obj: element, "Stopped"); + + true + } + + fn fixate(&self, element: &BaseSrc, caps: gst::Caps) -> gst::Caps { + //We need to set the correct caps resolution and framerate + let state = self.state.lock().unwrap(); + let recv = match state.recv{ + None => { + //TODO Update gst_element_error with one more descriptive + //println!("pNDI_recv no encontrado"); + gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); + //TODO if none not return anything + return caps; + } + Some(ref recv) => recv.clone(), + }; + + let pNDI_recv = recv.recv; + let mut pts2 = self.pts.lock().unwrap(); + let mut pts: u64 = 0; + + let video_frame: NDIlib_video_frame_v2_t = get_frame(self, element, pNDI_recv, &mut pts2.pts, &mut pts); + let mut caps = gst::Caps::truncate(caps); + { + let caps = caps.make_mut(); + let s = caps.get_mut_structure(0).unwrap(); + s.fixate_field_nearest_int("width", video_frame.xres); + s.fixate_field_nearest_int("height", video_frame.yres); + s.fixate_field_nearest_fraction("framerate", Fraction::new(video_frame.frame_rate_N, video_frame.frame_rate_D)); + //s.fixate_field_str("format", &gst_video::VideoFormat::Rgb.to_string()); + //caps.set_simple(&[("width", &(1600 as i32))]); + //s.set_value("width", &(1600 as i32)); + } + + // Let BaseSrc fixate anything else for us. We could've alternatively have + // called Caps::fixate() here + element.parent_fixate(caps) + } + + //Creates the audio buffers + fn create( + &self, + element: &BaseSrc, + _offset: u64, + _length: u32, + ) -> Result { + // Keep a local copy of the values of all our properties at this very moment. This + // ensures that the mutex is never locked for long and the application wouldn't + // have to block until this function returns when getting/setting property values + let _settings = &*self.settings.lock().unwrap(); + + let mut pts2 = self.pts.lock().unwrap(); + // Get a locked reference to our state, i.e. the input and output AudioInfo + let state = self.state.lock().unwrap(); + let _info = match state.info { + None => { + gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); + return Err(gst::FlowReturn::NotNegotiated); + } + Some(ref info) => info.clone(), + }; + //let mut pNDI_recva = ptr::null(); + // { + let recv = match state.recv{ + None => { + //TODO Update gst_element_error with one more descriptive + //println!("pNDI_recv no encontrado"); + gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); + return Err(gst::FlowReturn::NotNegotiated); + } + Some(ref recv) => recv.clone(), + }; + let pNDI_recv = recv.recv; + // } + + let start_pts = match state.start_pts { + None => { + gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); + return Err(gst::FlowReturn::NotNegotiated); + } + Some(ref start_pts) => start_pts.clone(), + }; + + unsafe{ + // // loop { + let mut pts: u64 = 0; + let video_frame: NDIlib_video_frame_v2_t = get_frame(self, element, pNDI_recv, &mut pts2.pts, &mut pts); + let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); + let metadata_frame: NDIlib_metadata_frame_t = Default::default(); + //video_frame = get_frame(self, element, pNDI_recv, pts2.pts); + + // //TODO Only create buffer when we got a video frame + // let mut frame = false; + // let mut pts: u64 = 0; + // while !frame{ + // let frame_type = NDIlib_recv_capture_v2( + // pNDI_recv, + // &video_frame, + // &audio_frame, + // &metadata_frame, + // 1000, + // ); + // + // match frame_type { + // NDIlib_frame_type_e::NDIlib_frame_type_video => { + // gst_debug!(self.cat, obj: element, "Received video frame: {:?}", video_frame); + // frame = true; + // //pts = ((video_frame.timestamp as u64) * 100) - state.start_pts.unwrap(); + // // println!("{:?}", pts/1000000); + // pts = (video_frame.timestamp as u64) * 100; + // if pts2.pts == 0{ + // pts2.pts = (video_frame.timestamp as u64) * 100; + // pts = 0; + // //let mut caps = _info.to_caps(); + // // let s = caps.get_mut_structure(0).unwrap(); + // // s.fixate_field_nearest_int("width", 1500); + // // s.fixate_field_nearest_int("framerate", 25/1); + // // self.set_caps(&self, s); + // // let mut caps = Some(gst::Caps::new_simple( + // // "video/x-raw", + // // &[("format", + // // &gst_video::VideoFormat::Uyvy.to_string() + // // )], + // // )); + // // caps.as_mut().map(|c| { + // // c.get_mut() + // // .unwrap() + // // .set_simple(&[("framerate", &(25/1 as i32)), ("width", &(1600 as i32)), ("height", &(1200 as i32))]) + // // }); + // //caps.unwrap().set_simple(&[("framerate", &(20/1 as i32))]); + // //let mut caps2 = _info.to_caps().unwrap(); + // // println!("{:?}", caps); + // //element.parent_set_caps(&caps.unwrap()); + // //element.parent_fixate(caps.unwrap()); + // //gst_video::VideoInfo::from_caps(&caps.unwrap()); + // //self.set_caps(element, &caps.unwrap()); + // } + // else{ + // // println!("{:?}", video_frame.timecode * 100); + // // println!("{:?}", pts2.pts); + // pts = (((video_frame.timestamp as u64) * 100) - pts2.pts); + // //println!("{:?}", pts/1000000); + // } + // + // } + // NDIlib_frame_type_e::NDIlib_frame_type_audio => { + // gst_debug!(self.cat, obj: element, "Received audio frame: {:?}", video_frame); + // } + // NDIlib_frame_type_e::NDIlib_frame_type_metadata => { + // // println!( + // // "Tengo metadata {} '{}'", + // // metadata_frame.length, + // // CStr::from_ptr(metadata_frame.p_data) + // // .to_string_lossy() + // // .into_owned(), + // // ); + // //TODO Change gst_warning to gst_debug + // gst_debug!(self.cat, obj: element, "Received metadata frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned(),); + // } + // NDIlib_frame_type_e::NDIlib_frame_type_error => { + // // println!( + // // "Tengo error {} '{}'", + // // metadata_frame.length, + // // CStr::from_ptr(metadata_frame.p_data) + // // .to_string_lossy() + // // .into_owned(), + // // ); + // //TODO Change gst_warning to gst_debug + // gst_debug!(self.cat, obj: element, "Received error frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned()); + // // break; + // } + // _ => println!("Tengo {:?}", frame_type), + // } + // } + // // } + + + let buff_size = (video_frame.yres * video_frame.line_stride_in_bytes) as usize; + let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); + { + let vec = Vec::from_raw_parts(video_frame.p_data as *mut u8, buff_size, buff_size); + //TODO Set pts, duration and other info about the buffer + let pts: gst::ClockTime = (pts).into(); + let duration: gst::ClockTime = (40000000).into(); + let buffer = buffer.get_mut().unwrap(); + buffer.set_pts(pts); + buffer.set_duration(duration); + buffer.set_offset(pts2.offset); + buffer.set_offset_end(pts2.offset + 1); + pts2.offset = pts2.offset +1; + buffer.copy_from_slice(0, &vec).unwrap(); + + } + + gst_debug!(self.cat, obj: element, "Produced buffer {:?}", buffer); + Ok(buffer) + } + } + + + + + fn unlock(&self, element: &BaseSrc) -> bool { + // This should unblock the create() function ASAP, so we + // just unschedule the clock it here, if any. + gst_debug!(self.cat, obj: element, "Unlocking"); + let mut clock_wait = self.clock_wait.lock().unwrap(); + if let Some(clock_id) = clock_wait.clock_id.take() { + clock_id.unschedule(); + } + clock_wait.flushing = true; + + true + } + + fn unlock_stop(&self, element: &BaseSrc) -> bool { + // This signals that unlocking is done, so we can reset + // all values again. + gst_debug!(self.cat, obj: element, "Unlock stop"); + let mut clock_wait = self.clock_wait.lock().unwrap(); + clock_wait.flushing = false; + + true + } + } + + // This zero-sized struct is containing the static metadata of our element. It is only necessary to + // be able to implement traits on it, but e.g. a plugin that registers multiple elements with the + // same code would use this struct to store information about the concrete element. An example of + // this would be a plugin that wraps around a library that has multiple decoders with the same API, + // but wants (as it should) a separate element registered for each decoder. + struct NdiVideoSrcStatic; + + // The basic trait for registering the type: This returns a name for the type and registers the + // instance and class initializations functions with the type system, thus hooking everything + // together. + impl ImplTypeStatic for NdiVideoSrcStatic { + fn get_name(&self) -> &str { + "NdiVideoSrc" + } + + fn new(&self, element: &BaseSrc) -> Box> { + NdiVideoSrc::new(element) + } + + fn class_init(&self, klass: &mut BaseSrcClass) { + NdiVideoSrc::class_init(klass); + } + } + + // Registers the type for our element, and then registers in GStreamer under + // the name NdiVideoSrc for being able to instantiate it via e.g. + // gst::ElementFactory::make(). + pub fn register(plugin: &gst::Plugin) { + let type_ = register_type(NdiVideoSrcStatic); + gst::Element::register(plugin, "ndivideosrc", 0, type_); + } From e0a3fb393e75871f30a1c2a3b413f2f8f9429dab Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Fri, 15 Jun 2018 15:16:25 +0200 Subject: [PATCH 042/199] Work in progress --- gst-plugin-ndi/src/ndiaudiosrc.rs | 248 +++++++++++++++--------------- gst-plugin-ndi/src/ndilib.rs | 28 ++++ gst-plugin-ndi/src/ndivideosrc.rs | 3 +- 3 files changed, 157 insertions(+), 122 deletions(-) diff --git a/gst-plugin-ndi/src/ndiaudiosrc.rs b/gst-plugin-ndi/src/ndiaudiosrc.rs index c99ec80e..27ac84d1 100644 --- a/gst-plugin-ndi/src/ndiaudiosrc.rs +++ b/gst-plugin-ndi/src/ndiaudiosrc.rs @@ -3,7 +3,7 @@ use glib; use gst; use gst::prelude::*; -use gst_video; +use gst_audio; use gst_base::prelude::*; use gst::Fraction; @@ -20,6 +20,11 @@ use std::{thread, time}; use std::time::{SystemTime, UNIX_EPOCH}; use std::ffi::{CStr, CString}; +use num_traits::float::Float; +use num_traits::cast::NumCast; +use byte_slice_cast::FromByteSlice; +use byte_slice_cast::AsSliceOf; + use ndilib::*; // Property value storage @@ -59,7 +64,7 @@ Property::String( // Stream-specific state, i.e. audio format configuration // and sample offset struct State { - info: Option, + info: Option, recv: Option, start_pts: Option, } @@ -104,7 +109,7 @@ impl NdiAudioSrc { cat: gst::DebugCategory::new( "ndiaudiosrc", gst::DebugColorFlags::empty(), - "NewTek NDI Video Source", + "NewTek NDI Audio Source", ), settings: Mutex::new(Default::default()), state: Mutex::new(Default::default()), @@ -140,26 +145,20 @@ impl NdiAudioSrc { // On the src pad, we can produce F32/F64 with any sample rate // and any number of channels let caps = gst::Caps::new_simple( - "video/x-raw", + "audio/x-raw", &[ ( "format", &gst::List::new(&[ //TODO add all formats? - &gst_video::VideoFormat::Uyvy.to_string(), - //&gst_video::VideoFormat::Rgb.to_string(), - //&gst_video::VideoFormat::Gray8.to_string(), + &gst_audio::AUDIO_FORMAT_F32.to_string(), + &gst_audio::AUDIO_FORMAT_F64.to_string(), + &gst_audio::AUDIO_FORMAT_S16.to_string(), ]), ), - ("width", &gst::IntRange::::new(0, i32::MAX)), - ("height", &gst::IntRange::::new(0, i32::MAX)), - ( - "framerate", - &gst::FractionRange::new( - gst::Fraction::new(0, 1), - gst::Fraction::new(i32::MAX, 1), - ), - ), + ("rate", &gst::IntRange::::new(1, i32::MAX)), + ("channels", &gst::IntRange::::new(1, i32::MAX)), + ("layout", &"interleaved"), ], ); // The src pad template must be named "src" for basesrc @@ -176,9 +175,64 @@ impl NdiAudioSrc { // Install all our properties klass.install_properties(&PROPERTIES); } - } + fn process( + data: &mut [u8], + p_data: *const ::std::os::raw::c_float + ){ + let data = data.as_mut_slice_of::().unwrap(); + // data = p_data; + println!("asdf"); + unsafe{ + let v: Vec = Vec::from_raw_parts(p_data as *mut f64, 7372800, 7372800); + // //let vec: &mut [F] = &v; + // let a = v.as_slice(); + // *data = a.to_vec().as_slice(); + } +// ////////////********************* +// use std::f64::consts::PI; +// +// // Reinterpret our byte-slice as a slice containing elements of the type +// // we're interested in. GStreamer requires for raw audio that the alignment +// // of memory is correct, so this will never ever fail unless there is an +// // actual bug elsewhere. +// let data = data.as_mut_slice_of::().unwrap(); +// +// // Convert all our parameters to the target type for calculations +// //let vol: F = NumCast::from(vol).unwrap(); +// let freq = 440 as f64; +// let rate = 48000 as f64; +// let two_pi = 2.0 * PI; +// let channels = 1; +// +// // We're carrying a accumulator with up to 2pi around instead of working +// // on the sample offset. High sample offsets cause too much inaccuracy when +// // converted to floating point numbers and then iterated over in 1-steps +// let mut accumulator = 0 as f64; +// //let mut accumulator = *accumulator_ref; +// let step = two_pi * freq / rate; +// +// let mut vec: Vec = Vec::from_raw_parts(p_data as *mut f64, 7372800, 7372800); +// data = vec.as_slice(); +// // for chunk in data.chunks_mut(channels as usize) { +// // // let value = F::sin(NumCast::from(accumulator).unwrap()); +// // // for sample in chunk { +// // // *sample = value; +// // // } +// // // +// // // accumulator += step; +// // // if accumulator >= two_pi { +// // // accumulator -= two_pi; +// // // } +// // chunk = p_data; +// // } +// +// //*accumulator_ref = accumulator; +// //////////////////********************* + } + } + // Virtual methods of GObject itself impl ObjectImpl for NdiAudioSrc { @@ -250,7 +304,7 @@ impl NdiAudioSrc { impl ElementImpl for NdiAudioSrc { } - fn get_frame(ndisrc_struct: &NdiAudioSrc, element: &BaseSrc, pNDI_recv : NDIlib_recv_instance_t, pts2 : &mut u64, pts : &mut u64) -> NDIlib_video_frame_v2_t{ + fn get_frame(ndisrc_struct: &NdiAudioSrc, element: &BaseSrc, pNDI_recv : NDIlib_recv_instance_t, pts2 : &mut u64, pts : &mut u64) -> NDIlib_audio_frame_v2_t{ unsafe{ let video_frame: NDIlib_video_frame_v2_t = Default::default(); let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); @@ -261,16 +315,16 @@ impl NdiAudioSrc { while !frame{ let frame_type = NDIlib_recv_capture_v2( pNDI_recv, - &video_frame, ptr::null(), + &audio_frame, ptr::null(), 1000, ); - match frame_type { NDIlib_frame_type_e::NDIlib_frame_type_video => { + println!("Videeeeeeo frrrame"); gst_debug!(ndisrc_struct.cat, obj: element, "Received video frame: {:?}", video_frame); - frame = true; + //frame = true; //pts = ((video_frame.timestamp as u64) * 100) - state.start_pts.unwrap(); // println!("{:?}", pts/1000000); *pts = ((video_frame.timestamp as u64) * 100); @@ -288,6 +342,20 @@ impl NdiAudioSrc { } NDIlib_frame_type_e::NDIlib_frame_type_audio => { gst_debug!(ndisrc_struct.cat, obj: element, "Received audio frame: {:?}", video_frame); + frame = true; + //pts = ((video_frame.timestamp as u64) * 100) - state.start_pts.unwrap(); + // println!("{:?}", pts/1000000); + *pts = ((audio_frame.timestamp as u64) * 100); + if *pts2 == 0{ + *pts2 = (audio_frame.timestamp as u64) * 100; + *pts = 0; + } + else{ + // println!("{:?}", video_frame.timecode * 100); + // println!("{:?}", pts2.pts); + *pts = (((audio_frame.timestamp as u64) * 100) - *pts2); + //println!("{:?}", pts/1000000); + } } NDIlib_frame_type_e::NDIlib_frame_type_metadata => { // println!( @@ -315,7 +383,7 @@ impl NdiAudioSrc { _ => println!("Tengo {:?}", frame_type), } } - return video_frame; + return audio_frame; } } @@ -329,7 +397,7 @@ impl NdiAudioSrc { // the sample rate, etc. when creating buffers fn set_caps(&self, element: &BaseSrc, caps: &gst::CapsRef) -> bool { - let info = match gst_video::VideoInfo::from_caps(caps) { + let info = match gst_audio::AudioInfo::from_caps(caps) { None => return false, Some(info) => info, }; @@ -527,14 +595,14 @@ impl NdiAudioSrc { let mut pts2 = self.pts.lock().unwrap(); let mut pts: u64 = 0; - let video_frame: NDIlib_video_frame_v2_t = get_frame(self, element, pNDI_recv, &mut pts2.pts, &mut pts); + let audio_frame: NDIlib_audio_frame_v2_t = get_frame(self, element, pNDI_recv, &mut pts2.pts, &mut pts); let mut caps = gst::Caps::truncate(caps); { let caps = caps.make_mut(); let s = caps.get_mut_structure(0).unwrap(); - s.fixate_field_nearest_int("width", video_frame.xres); - s.fixate_field_nearest_int("height", video_frame.yres); - s.fixate_field_nearest_fraction("framerate", Fraction::new(video_frame.frame_rate_N, video_frame.frame_rate_D)); + s.fixate_field_nearest_int("rate", audio_frame.sample_rate); + s.fixate_field_nearest_int("channels", audio_frame.no_channels); + //s.fixate_field_nearest_fraction("framerate", Fraction::new(video_frame.frame_rate_N, video_frame.frame_rate_D)); //s.fixate_field_str("format", &gst_video::VideoFormat::Rgb.to_string()); //caps.set_simple(&[("width", &(1600 as i32))]); //s.set_value("width", &(1600 as i32)); @@ -592,101 +660,24 @@ impl NdiAudioSrc { unsafe{ // // loop { let mut pts: u64 = 0; - let video_frame: NDIlib_video_frame_v2_t = get_frame(self, element, pNDI_recv, &mut pts2.pts, &mut pts); - let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); + let video_frame: NDIlib_video_frame_v2_t = Default::default(); + let audio_frame: NDIlib_audio_frame_v2_t = get_frame(self, element, pNDI_recv, &mut pts2.pts, &mut pts); let metadata_frame: NDIlib_metadata_frame_t = Default::default(); - //video_frame = get_frame(self, element, pNDI_recv, pts2.pts); - // //TODO Only create buffer when we got a video frame - // let mut frame = false; - // let mut pts: u64 = 0; - // while !frame{ - // let frame_type = NDIlib_recv_capture_v2( - // pNDI_recv, - // &video_frame, - // &audio_frame, - // &metadata_frame, - // 1000, - // ); - // - // match frame_type { - // NDIlib_frame_type_e::NDIlib_frame_type_video => { - // gst_debug!(self.cat, obj: element, "Received video frame: {:?}", video_frame); - // frame = true; - // //pts = ((video_frame.timestamp as u64) * 100) - state.start_pts.unwrap(); - // // println!("{:?}", pts/1000000); - // pts = (video_frame.timestamp as u64) * 100; - // if pts2.pts == 0{ - // pts2.pts = (video_frame.timestamp as u64) * 100; - // pts = 0; - // //let mut caps = _info.to_caps(); - // // let s = caps.get_mut_structure(0).unwrap(); - // // s.fixate_field_nearest_int("width", 1500); - // // s.fixate_field_nearest_int("framerate", 25/1); - // // self.set_caps(&self, s); - // // let mut caps = Some(gst::Caps::new_simple( - // // "video/x-raw", - // // &[("format", - // // &gst_video::VideoFormat::Uyvy.to_string() - // // )], - // // )); - // // caps.as_mut().map(|c| { - // // c.get_mut() - // // .unwrap() - // // .set_simple(&[("framerate", &(25/1 as i32)), ("width", &(1600 as i32)), ("height", &(1200 as i32))]) - // // }); - // //caps.unwrap().set_simple(&[("framerate", &(20/1 as i32))]); - // //let mut caps2 = _info.to_caps().unwrap(); - // // println!("{:?}", caps); - // //element.parent_set_caps(&caps.unwrap()); - // //element.parent_fixate(caps.unwrap()); - // //gst_video::VideoInfo::from_caps(&caps.unwrap()); - // //self.set_caps(element, &caps.unwrap()); - // } - // else{ - // // println!("{:?}", video_frame.timecode * 100); - // // println!("{:?}", pts2.pts); - // pts = (((video_frame.timestamp as u64) * 100) - pts2.pts); - // //println!("{:?}", pts/1000000); - // } - // - // } - // NDIlib_frame_type_e::NDIlib_frame_type_audio => { - // gst_debug!(self.cat, obj: element, "Received audio frame: {:?}", video_frame); - // } - // NDIlib_frame_type_e::NDIlib_frame_type_metadata => { - // // println!( - // // "Tengo metadata {} '{}'", - // // metadata_frame.length, - // // CStr::from_ptr(metadata_frame.p_data) - // // .to_string_lossy() - // // .into_owned(), - // // ); - // //TODO Change gst_warning to gst_debug - // gst_debug!(self.cat, obj: element, "Received metadata frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned(),); - // } - // NDIlib_frame_type_e::NDIlib_frame_type_error => { - // // println!( - // // "Tengo error {} '{}'", - // // metadata_frame.length, - // // CStr::from_ptr(metadata_frame.p_data) - // // .to_string_lossy() - // // .into_owned(), - // // ); - // //TODO Change gst_warning to gst_debug - // gst_debug!(self.cat, obj: element, "Received error frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned()); - // // break; - // } - // _ => println!("Tengo {:?}", frame_type), - // } - // } - // // } - - - let buff_size = (video_frame.yres * video_frame.line_stride_in_bytes) as usize; + let buff_size = (audio_frame.no_channels * audio_frame.no_samples) as usize; + //let buff_size = 126864 as usize; + //let buff_size = 7372800 as usize; + println!("1"); + let mut audio_frame_16s: NDIlib_audio_frame_interleaved_16s_t = Default::default(); + let thing: [::std::os::raw::c_short; 0] = []; + let a : *const i16 = &thing; + audio_frame_16s.p_data = a; + NDIlib_util_audio_to_interleaved_16s_v2(&audio_frame, &audio_frame_16s); + println!("2"); + println!("{:?}", audio_frame_16s); let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); { - let vec = Vec::from_raw_parts(video_frame.p_data as *mut u8, buff_size, buff_size); + let vec = Vec::from_raw_parts(audio_frame_16s.p_data as *mut u8, buff_size, buff_size); //TODO Set pts, duration and other info about the buffer let pts: gst::ClockTime = (pts).into(); let duration: gst::ClockTime = (40000000).into(); @@ -696,8 +687,25 @@ impl NdiAudioSrc { buffer.set_offset(pts2.offset); buffer.set_offset_end(pts2.offset + 1); pts2.offset = pts2.offset +1; - buffer.copy_from_slice(0, &vec).unwrap(); + println!("{:?}", buff_size); + //println!("{:?}", vec); + // let mut vec: Vec = Vec::from_raw_parts(audio_frame.p_data as *mut f64, 7372800, 7372800); + // + // println!("aasdfasdf"); + // print + buffer.copy_from_slice(0, &vec).unwrap(); + // let mut map = buffer.map_writable().unwrap(); + // let data = map.as_mut_slice(); + // + // let mut data = data.as_mut_slice_of::().unwrap(); + // data = vec.as_mut_slice(); + + + // Self::process::( + // data, + // audio_frame.p_data, + // ); } gst_debug!(self.cat, obj: element, "Produced buffer {:?}", buffer); @@ -706,8 +714,6 @@ impl NdiAudioSrc { } - - fn unlock(&self, element: &BaseSrc) -> bool { // This should unblock the create() function ASAP, so we // just unschedule the clock it here, if any. diff --git a/gst-plugin-ndi/src/ndilib.rs b/gst-plugin-ndi/src/ndilib.rs index 09e581d3..75357675 100644 --- a/gst-plugin-ndi/src/ndilib.rs +++ b/gst-plugin-ndi/src/ndilib.rs @@ -32,6 +32,10 @@ extern "C" { p_metadata: *const NDIlib_metadata_frame_t, timeout_in_ms: u32, ) -> NDIlib_frame_type_e; + pub fn NDIlib_util_audio_to_interleaved_16s_v2( + p_src: *const NDIlib_audio_frame_v2_t, + p_dst: *const NDIlib_audio_frame_interleaved_16s_t, + ) -> NDIlib_audio_frame_interleaved_16s_t; } pub type NDIlib_find_instance_t = *mut ::std::os::raw::c_void; @@ -253,3 +257,27 @@ impl Default for NDIlib_audio_frame_v2_t { } } } + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct NDIlib_audio_frame_interleaved_16s_t { + pub sample_rate: ::std::os::raw::c_int, + pub no_channels: ::std::os::raw::c_int, + pub no_samples: ::std::os::raw::c_int, + pub timecode: i64, + pub p_data: *const ::std::os::raw::c_short, + pub reference_level: ::std::os::raw::c_int, +} + +impl Default for NDIlib_audio_frame_interleaved_16s_t { + fn default() -> Self { + NDIlib_audio_frame_interleaved_16s_t { + sample_rate: 48000, + no_channels: 2, + no_samples: 0, + timecode: NDIlib_send_timecode_synthesize, + p_data: ptr::null(), + reference_level: 20, + } + } +} diff --git a/gst-plugin-ndi/src/ndivideosrc.rs b/gst-plugin-ndi/src/ndivideosrc.rs index 8bf4d65e..55ec553e 100644 --- a/gst-plugin-ndi/src/ndivideosrc.rs +++ b/gst-plugin-ndi/src/ndivideosrc.rs @@ -684,11 +684,12 @@ impl NdiVideoSrc { let buff_size = (video_frame.yres * video_frame.line_stride_in_bytes) as usize; + println!("{:?}", buff_size); let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); { let vec = Vec::from_raw_parts(video_frame.p_data as *mut u8, buff_size, buff_size); - //TODO Set pts, duration and other info about the buffer let pts: gst::ClockTime = (pts).into(); + //TODO get duration let duration: gst::ClockTime = (40000000).into(); let buffer = buffer.get_mut().unwrap(); buffer.set_pts(pts); From 2e9a0242afb9d39f467a58c819bce5f434e0a11c Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Wed, 20 Jun 2018 10:23:19 +0200 Subject: [PATCH 043/199] Working audio src --- gst-plugin-ndi/src/ndiaudiosrc.rs | 231 ++++++++++-------------------- gst-plugin-ndi/src/ndilib.rs | 28 ---- gst-plugin-ndi/src/ndivideosrc.rs | 106 +++++++------- 3 files changed, 125 insertions(+), 240 deletions(-) diff --git a/gst-plugin-ndi/src/ndiaudiosrc.rs b/gst-plugin-ndi/src/ndiaudiosrc.rs index 27ac84d1..07698fdd 100644 --- a/gst-plugin-ndi/src/ndiaudiosrc.rs +++ b/gst-plugin-ndi/src/ndiaudiosrc.rs @@ -175,63 +175,7 @@ impl NdiAudioSrc { // Install all our properties klass.install_properties(&PROPERTIES); } - - - fn process( - data: &mut [u8], - p_data: *const ::std::os::raw::c_float - ){ - let data = data.as_mut_slice_of::().unwrap(); - // data = p_data; - println!("asdf"); - unsafe{ - let v: Vec = Vec::from_raw_parts(p_data as *mut f64, 7372800, 7372800); - // //let vec: &mut [F] = &v; - // let a = v.as_slice(); - // *data = a.to_vec().as_slice(); - } -// ////////////********************* -// use std::f64::consts::PI; -// -// // Reinterpret our byte-slice as a slice containing elements of the type -// // we're interested in. GStreamer requires for raw audio that the alignment -// // of memory is correct, so this will never ever fail unless there is an -// // actual bug elsewhere. -// let data = data.as_mut_slice_of::().unwrap(); -// -// // Convert all our parameters to the target type for calculations -// //let vol: F = NumCast::from(vol).unwrap(); -// let freq = 440 as f64; -// let rate = 48000 as f64; -// let two_pi = 2.0 * PI; -// let channels = 1; -// -// // We're carrying a accumulator with up to 2pi around instead of working -// // on the sample offset. High sample offsets cause too much inaccuracy when -// // converted to floating point numbers and then iterated over in 1-steps -// let mut accumulator = 0 as f64; -// //let mut accumulator = *accumulator_ref; -// let step = two_pi * freq / rate; -// -// let mut vec: Vec = Vec::from_raw_parts(p_data as *mut f64, 7372800, 7372800); -// data = vec.as_slice(); -// // for chunk in data.chunks_mut(channels as usize) { -// // // let value = F::sin(NumCast::from(accumulator).unwrap()); -// // // for sample in chunk { -// // // *sample = value; -// // // } -// // // -// // // accumulator += step; -// // // if accumulator >= two_pi { -// // // accumulator -= two_pi; -// // // } -// // chunk = p_data; -// // } -// -// //*accumulator_ref = accumulator; -// //////////////////********************* - } - } + } // Virtual methods of GObject itself @@ -313,76 +257,76 @@ impl NdiAudioSrc { //TODO Only create buffer when we got a video frame let mut frame = false; while !frame{ - let frame_type = NDIlib_recv_capture_v2( - pNDI_recv, - ptr::null(), - &audio_frame, - ptr::null(), - 1000, - ); - match frame_type { - NDIlib_frame_type_e::NDIlib_frame_type_video => { - println!("Videeeeeeo frrrame"); - gst_debug!(ndisrc_struct.cat, obj: element, "Received video frame: {:?}", video_frame); - //frame = true; - //pts = ((video_frame.timestamp as u64) * 100) - state.start_pts.unwrap(); - // println!("{:?}", pts/1000000); - *pts = ((video_frame.timestamp as u64) * 100); - if *pts2 == 0{ - *pts2 = (video_frame.timestamp as u64) * 100; - *pts = 0; - } - else{ - // println!("{:?}", video_frame.timecode * 100); - // println!("{:?}", pts2.pts); - *pts = (((video_frame.timestamp as u64) * 100) - *pts2); - //println!("{:?}", pts/1000000); - } + let frame_type = NDIlib_recv_capture_v2( + pNDI_recv, + ptr::null(), + &audio_frame, + ptr::null(), + 1000, + ); + match frame_type { + NDIlib_frame_type_e::NDIlib_frame_type_video => { + println!("Videeeeeeo frrrame"); + gst_debug!(ndisrc_struct.cat, obj: element, "Received video frame: {:?}", video_frame); + //frame = true; + //pts = ((video_frame.timestamp as u64) * 100) - state.start_pts.unwrap(); + // println!("{:?}", pts/1000000); + *pts = ((video_frame.timestamp as u64) * 100); + if *pts2 == 0{ + *pts2 = (video_frame.timestamp as u64) * 100; + *pts = 0; + } + else{ + // println!("{:?}", video_frame.timecode * 100); + // println!("{:?}", pts2.pts); + *pts = (((video_frame.timestamp as u64) * 100) - *pts2); + //println!("{:?}", pts/1000000); + } - } - NDIlib_frame_type_e::NDIlib_frame_type_audio => { - gst_debug!(ndisrc_struct.cat, obj: element, "Received audio frame: {:?}", video_frame); - frame = true; - //pts = ((video_frame.timestamp as u64) * 100) - state.start_pts.unwrap(); - // println!("{:?}", pts/1000000); - *pts = ((audio_frame.timestamp as u64) * 100); - if *pts2 == 0{ - *pts2 = (audio_frame.timestamp as u64) * 100; - *pts = 0; } - else{ - // println!("{:?}", video_frame.timecode * 100); - // println!("{:?}", pts2.pts); - *pts = (((audio_frame.timestamp as u64) * 100) - *pts2); - //println!("{:?}", pts/1000000); + NDIlib_frame_type_e::NDIlib_frame_type_audio => { + gst_debug!(ndisrc_struct.cat, obj: element, "Received audio frame: {:?}", video_frame); + frame = true; + //pts = ((video_frame.timestamp as u64) * 100) - state.start_pts.unwrap(); + // println!("{:?}", pts/1000000); + *pts = ((audio_frame.timestamp as u64) * 100); + if *pts2 == 0{ + *pts2 = (audio_frame.timestamp as u64) * 100; + *pts = 0; + } + else{ + // println!("{:?}", video_frame.timecode * 100); + // println!("{:?}", pts2.pts); + *pts = (((audio_frame.timestamp as u64) * 100) - *pts2); + //println!("{:?}", pts/1000000); + } } + NDIlib_frame_type_e::NDIlib_frame_type_metadata => { + // println!( + // "Tengo metadata {} '{}'", + // metadata_frame.length, + // CStr::from_ptr(metadata_frame.p_data) + // .to_string_lossy() + // .into_owned(), + // ); + //TODO Change gst_warning to gst_debug + gst_debug!(ndisrc_struct.cat, obj: element, "Received metadata frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned(),); + } + NDIlib_frame_type_e::NDIlib_frame_type_error => { + // println!( + // "Tengo error {} '{}'", + // metadata_frame.length, + // CStr::from_ptr(metadata_frame.p_data) + // .to_string_lossy() + // .into_owned(), + // ); + //TODO Change gst_warning to gst_debug + gst_debug!(ndisrc_struct.cat, obj: element, "Received error frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned()); + // break; + } + _ => println!("Tengo {:?}", frame_type), } - NDIlib_frame_type_e::NDIlib_frame_type_metadata => { - // println!( - // "Tengo metadata {} '{}'", - // metadata_frame.length, - // CStr::from_ptr(metadata_frame.p_data) - // .to_string_lossy() - // .into_owned(), - // ); - //TODO Change gst_warning to gst_debug - gst_debug!(ndisrc_struct.cat, obj: element, "Received metadata frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned(),); - } - NDIlib_frame_type_e::NDIlib_frame_type_error => { - // println!( - // "Tengo error {} '{}'", - // metadata_frame.length, - // CStr::from_ptr(metadata_frame.p_data) - // .to_string_lossy() - // .into_owned(), - // ); - //TODO Change gst_warning to gst_debug - gst_debug!(ndisrc_struct.cat, obj: element, "Received error frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned()); - // break; - } - _ => println!("Tengo {:?}", frame_type), } - } return audio_frame; } } @@ -600,12 +544,9 @@ impl NdiAudioSrc { { let caps = caps.make_mut(); let s = caps.get_mut_structure(0).unwrap(); - s.fixate_field_nearest_int("rate", audio_frame.sample_rate); + //s.fixate_field_nearest_int("rate", audio_frame.sample_rate); + s.fixate_field_nearest_int("rate", audio_frame.sample_rate / audio_frame.no_channels); s.fixate_field_nearest_int("channels", audio_frame.no_channels); - //s.fixate_field_nearest_fraction("framerate", Fraction::new(video_frame.frame_rate_N, video_frame.frame_rate_D)); - //s.fixate_field_str("format", &gst_video::VideoFormat::Rgb.to_string()); - //caps.set_simple(&[("width", &(1600 as i32))]); - //s.set_value("width", &(1600 as i32)); } // Let BaseSrc fixate anything else for us. We could've alternatively have @@ -664,48 +605,20 @@ impl NdiAudioSrc { let audio_frame: NDIlib_audio_frame_v2_t = get_frame(self, element, pNDI_recv, &mut pts2.pts, &mut pts); let metadata_frame: NDIlib_metadata_frame_t = Default::default(); - let buff_size = (audio_frame.no_channels * audio_frame.no_samples) as usize; - //let buff_size = 126864 as usize; - //let buff_size = 7372800 as usize; - println!("1"); - let mut audio_frame_16s: NDIlib_audio_frame_interleaved_16s_t = Default::default(); - let thing: [::std::os::raw::c_short; 0] = []; - let a : *const i16 = &thing; - audio_frame_16s.p_data = a; - NDIlib_util_audio_to_interleaved_16s_v2(&audio_frame, &audio_frame_16s); - println!("2"); - println!("{:?}", audio_frame_16s); + let buff_size = ((audio_frame.channel_stride_in_bytes)) as usize; let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); { - let vec = Vec::from_raw_parts(audio_frame_16s.p_data as *mut u8, buff_size, buff_size); + let vec = Vec::from_raw_parts(audio_frame.p_data as *mut u8, buff_size, buff_size); //TODO Set pts, duration and other info about the buffer let pts: gst::ClockTime = (pts).into(); - let duration: gst::ClockTime = (40000000).into(); + let duration: gst::ClockTime = (20154200).into(); let buffer = buffer.get_mut().unwrap(); buffer.set_pts(pts); - buffer.set_duration(duration); + //buffer.set_duration(duration); buffer.set_offset(pts2.offset); buffer.set_offset_end(pts2.offset + 1); pts2.offset = pts2.offset +1; - println!("{:?}", buff_size); - //println!("{:?}", vec); - - // let mut vec: Vec = Vec::from_raw_parts(audio_frame.p_data as *mut f64, 7372800, 7372800); - // - // println!("aasdfasdf"); - // print buffer.copy_from_slice(0, &vec).unwrap(); - // let mut map = buffer.map_writable().unwrap(); - // let data = map.as_mut_slice(); - // - // let mut data = data.as_mut_slice_of::().unwrap(); - // data = vec.as_mut_slice(); - - - // Self::process::( - // data, - // audio_frame.p_data, - // ); } gst_debug!(self.cat, obj: element, "Produced buffer {:?}", buffer); diff --git a/gst-plugin-ndi/src/ndilib.rs b/gst-plugin-ndi/src/ndilib.rs index 75357675..09e581d3 100644 --- a/gst-plugin-ndi/src/ndilib.rs +++ b/gst-plugin-ndi/src/ndilib.rs @@ -32,10 +32,6 @@ extern "C" { p_metadata: *const NDIlib_metadata_frame_t, timeout_in_ms: u32, ) -> NDIlib_frame_type_e; - pub fn NDIlib_util_audio_to_interleaved_16s_v2( - p_src: *const NDIlib_audio_frame_v2_t, - p_dst: *const NDIlib_audio_frame_interleaved_16s_t, - ) -> NDIlib_audio_frame_interleaved_16s_t; } pub type NDIlib_find_instance_t = *mut ::std::os::raw::c_void; @@ -257,27 +253,3 @@ impl Default for NDIlib_audio_frame_v2_t { } } } - -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct NDIlib_audio_frame_interleaved_16s_t { - pub sample_rate: ::std::os::raw::c_int, - pub no_channels: ::std::os::raw::c_int, - pub no_samples: ::std::os::raw::c_int, - pub timecode: i64, - pub p_data: *const ::std::os::raw::c_short, - pub reference_level: ::std::os::raw::c_int, -} - -impl Default for NDIlib_audio_frame_interleaved_16s_t { - fn default() -> Self { - NDIlib_audio_frame_interleaved_16s_t { - sample_rate: 48000, - no_channels: 2, - no_samples: 0, - timecode: NDIlib_send_timecode_synthesize, - p_data: ptr::null(), - reference_level: 20, - } - } -} diff --git a/gst-plugin-ndi/src/ndivideosrc.rs b/gst-plugin-ndi/src/ndivideosrc.rs index 55ec553e..f40d26f5 100644 --- a/gst-plugin-ndi/src/ndivideosrc.rs +++ b/gst-plugin-ndi/src/ndivideosrc.rs @@ -259,62 +259,62 @@ impl NdiVideoSrc { //TODO Only create buffer when we got a video frame let mut frame = false; while !frame{ - let frame_type = NDIlib_recv_capture_v2( - pNDI_recv, - &video_frame, - ptr::null(), - ptr::null(), - 1000, - ); + let frame_type = NDIlib_recv_capture_v2( + pNDI_recv, + &video_frame, + ptr::null(), + ptr::null(), + 1000, + ); - match frame_type { - NDIlib_frame_type_e::NDIlib_frame_type_video => { - gst_debug!(ndisrc_struct.cat, obj: element, "Received video frame: {:?}", video_frame); - frame = true; - //pts = ((video_frame.timestamp as u64) * 100) - state.start_pts.unwrap(); - // println!("{:?}", pts/1000000); - *pts = ((video_frame.timestamp as u64) * 100); - if *pts2 == 0{ - *pts2 = (video_frame.timestamp as u64) * 100; - *pts = 0; - } - else{ - // println!("{:?}", video_frame.timecode * 100); - // println!("{:?}", pts2.pts); - *pts = (((video_frame.timestamp as u64) * 100) - *pts2); - //println!("{:?}", pts/1000000); - } + match frame_type { + NDIlib_frame_type_e::NDIlib_frame_type_video => { + gst_debug!(ndisrc_struct.cat, obj: element, "Received video frame: {:?}", video_frame); + frame = true; + //pts = ((video_frame.timestamp as u64) * 100) - state.start_pts.unwrap(); + // println!("{:?}", pts/1000000); + *pts = ((video_frame.timestamp as u64) * 100); + if *pts2 == 0{ + *pts2 = (video_frame.timestamp as u64) * 100; + *pts = 0; + } + else{ + // println!("{:?}", video_frame.timecode * 100); + // println!("{:?}", pts2.pts); + *pts = (((video_frame.timestamp as u64) * 100) - *pts2); + //println!("{:?}", pts/1000000); + } + } + NDIlib_frame_type_e::NDIlib_frame_type_audio => { + gst_debug!(ndisrc_struct.cat, obj: element, "Received audio frame: {:?}", video_frame); + } + NDIlib_frame_type_e::NDIlib_frame_type_metadata => { + // println!( + // "Tengo metadata {} '{}'", + // metadata_frame.length, + // CStr::from_ptr(metadata_frame.p_data) + // .to_string_lossy() + // .into_owned(), + // ); + //TODO Change gst_warning to gst_debug + gst_debug!(ndisrc_struct.cat, obj: element, "Received metadata frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned(),); + } + NDIlib_frame_type_e::NDIlib_frame_type_error => { + // println!( + // "Tengo error {} '{}'", + // metadata_frame.length, + // CStr::from_ptr(metadata_frame.p_data) + // .to_string_lossy() + // .into_owned(), + // ); + //TODO Change gst_warning to gst_debug + gst_debug!(ndisrc_struct.cat, obj: element, "Received error frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned()); + // break; + } + _ => println!("Tengo {:?}", frame_type), } - NDIlib_frame_type_e::NDIlib_frame_type_audio => { - gst_debug!(ndisrc_struct.cat, obj: element, "Received audio frame: {:?}", video_frame); - } - NDIlib_frame_type_e::NDIlib_frame_type_metadata => { - // println!( - // "Tengo metadata {} '{}'", - // metadata_frame.length, - // CStr::from_ptr(metadata_frame.p_data) - // .to_string_lossy() - // .into_owned(), - // ); - //TODO Change gst_warning to gst_debug - gst_debug!(ndisrc_struct.cat, obj: element, "Received metadata frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned(),); - } - NDIlib_frame_type_e::NDIlib_frame_type_error => { - // println!( - // "Tengo error {} '{}'", - // metadata_frame.length, - // CStr::from_ptr(metadata_frame.p_data) - // .to_string_lossy() - // .into_owned(), - // ); - //TODO Change gst_warning to gst_debug - gst_debug!(ndisrc_struct.cat, obj: element, "Received error frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned()); - // break; - } - _ => println!("Tengo {:?}", frame_type), } - } return video_frame; } } @@ -684,7 +684,7 @@ impl NdiVideoSrc { let buff_size = (video_frame.yres * video_frame.line_stride_in_bytes) as usize; - println!("{:?}", buff_size); + //println!("{:?}", buff_size); let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); { let vec = Vec::from_raw_parts(video_frame.p_data as *mut u8, buff_size, buff_size); From e5405a66573e3933d42f3bdc10456a81c77ab9a7 Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Mon, 25 Jun 2018 10:38:45 +0200 Subject: [PATCH 044/199] Moved connection to ndi logic from sources to a common function --- gst-plugin-ndi/src/lib.rs | 216 +++++++++++++++++-- gst-plugin-ndi/src/ndiaudiosrc.rs | 348 +++++++++++++++++------------- gst-plugin-ndi/src/ndivideosrc.rs | 344 ++++++++++++++++------------- 3 files changed, 599 insertions(+), 309 deletions(-) diff --git a/gst-plugin-ndi/src/lib.rs b/gst-plugin-ndi/src/lib.rs index 4f7cd6b4..1909e03c 100644 --- a/gst-plugin-ndi/src/lib.rs +++ b/gst-plugin-ndi/src/lib.rs @@ -21,28 +21,212 @@ extern crate num_traits; mod ndivideosrc; mod ndiaudiosrc; pub mod ndilib; +pub mod a; + +use std::ptr; +use std::{thread, time}; +use std::time::{SystemTime, UNIX_EPOCH}; +use std::ffi::{CStr, CString}; +use ndilib::*; +use gst_plugin::base_src::*; + // Plugin entry point that should register all elements provided by this plugin, // and everything else that this plugin might provide (e.g. typefinders or device providers). fn plugin_init(plugin: &gst::Plugin) -> bool { ndivideosrc::register(plugin); ndiaudiosrc::register(plugin); + // a::test(2); + // println!("---------------------------------------"); + // let mut ndistruc = ndi1{ + // recv : 0 + // }; true } -// Static plugin metdata that is directly stored in the plugin shared object and read by GStreamer -// upon loading. -// Plugin name, plugin description, plugin entry point function, version number of this plugin, -// license of the plugin, source package name, binary package name, origin where it comes from -// and the date/time of release. -plugin_define!( - b"ndi\0", - b"NewTek NDI Plugin\0", - plugin_init, - b"1.0\0", - b"MIT/X11\0", - b"ndi\0", - b"ndi\0", - b"https://gitlab.teltek.es/rubenrua/ndi-rs.git\0", - b"2018-04-09\0" -); + +struct ndi1{ + recv: Option, + start_pts: Option, +} + +static mut ndi2: ndi1 = ndi1{ + recv: None, + start_pts: None, +}; + +fn hue(element: &BaseSrc, ip: String, stream_name: String) -> bool{ + println!("---------------------------------------"); + unsafe { + match ndi2.recv { + None => { + //gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); + //return true; + } + _ => return true, + }; + //println!("{:?}", ndi2.recv); + //ndi2.recv = i; + //println!("{:?}", ndi2.recv); + if !NDIlib_initialize() { + //println!("Cannot run NDI: NDIlib_initialize error."); + gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_initialize error"]); + return false; + } + + let mut source: NDIlib_source_t = NDIlib_source_t{p_ndi_name: ptr::null(), + p_ip_address: ptr::null()}; + + // print!("{:?}", settings.stream_name); + // print!("{:?}", settings.ip); + + //TODO default values + let NDI_find_create_desc: NDIlib_find_create_t = Default::default(); + let pNDI_find = NDIlib_find_create_v2(&NDI_find_create_desc); + let ip_ptr = CString::new(ip.clone()).unwrap(); + if (ip_ptr == CString::new("").unwrap()){ + if pNDI_find.is_null() { + //println!("Cannot run NDI: NDIlib_find_create_v2 error."); + gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_find_create_v2 error"]); + return false; + } + + let mut total_sources: u32 = 0; + let mut p_sources = ptr::null(); + //TODO Delete while. If not, will loop until a source it's available + //while total_sources == 0 { + // TODO Sleep 1s to wait for all sources + thread::sleep(time::Duration::from_millis(2000)); + p_sources = NDIlib_find_get_current_sources(pNDI_find, &mut total_sources as *mut u32); + //} + + // We need at least one source + if p_sources.is_null() { + //println!("Error getting NDIlib_find_get_current_sources."); + gst_element_error!(element, gst::CoreError::Negotiation, ["Error getting NDIlib_find_get_current_sources"]); + return false; + //::std::process::exit(1); + } + + let mut no_source: isize = -1; + for i in 0..total_sources as isize{ + if CStr::from_ptr((*p_sources.offset(i)).p_ndi_name) + .to_string_lossy() + .into_owned() == stream_name{ + no_source = i; + break; + } + } + if no_source == -1 { + gst_element_error!(element, gst::CoreError::Negotiation, ["Stream name not found"]); + return false; + } + println!( + "Total_sources {}: Name '{}' Address '{}'", + total_sources, + CStr::from_ptr((*p_sources.offset(no_source)).p_ndi_name) + .to_string_lossy() + .into_owned(), + CStr::from_ptr((*p_sources.offset(no_source)).p_ip_address) + .to_string_lossy() + .into_owned() + ); + source = *p_sources.offset(no_source).clone(); + } + else{ + source.p_ip_address = ip_ptr.as_ptr(); + println!( + "Address '{}'", + CStr::from_ptr(source.p_ip_address) + .to_string_lossy() + .into_owned() + ); + } + + // We now have at least one source, so we create a receiver to look at it. + // We tell it that we prefer YCbCr video since it is more efficient for us. If the source has an alpha channel + // it will still be provided in BGRA + let p_ndi_name = CString::new("Galicaster NDI Receiver").unwrap(); + let NDI_recv_create_desc = NDIlib_recv_create_v3_t { + source_to_connect_to: source, + p_ndi_name: p_ndi_name.as_ptr(), + ..Default::default() + }; + + let pNDI_recv = NDIlib_recv_create_v3(&NDI_recv_create_desc); + if pNDI_recv.is_null() { + //println!("Cannot run NDI: NDIlib_recv_create_v3 error."); + gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_recv_create_v3 error"]); + return false; + //::std::process::exit(1); + } + + // Destroy the NDI finder. We needed to have access to the pointers to p_sources[0] + NDIlib_find_destroy(pNDI_find); + + // We are now going to mark this source as being on program output for tally purposes (but not on preview) + let tally_state: NDIlib_tally_t = Default::default(); + NDIlib_recv_set_tally(pNDI_recv, &tally_state); + + // Enable Hardwqre Decompression support if this support has it. Please read the caveats in the documentation + // regarding this. There are times in which it might reduce the performance although on small stream numbers + // it almost always yields the same or better performance. + let data = CString::new("").unwrap(); + let enable_hw_accel = NDIlib_metadata_frame_t { + length: data.to_bytes().len() as i32, + timecode: 0, + p_data: data.as_ptr(), + }; + + NDIlib_recv_send_metadata(pNDI_recv, &enable_hw_accel); + ndi2.recv = Some(NdiInstance{recv: pNDI_recv}); + let start = SystemTime::now(); + let since_the_epoch = start.duration_since(UNIX_EPOCH) + .expect("Time went backwards"); + println!("{:?}", since_the_epoch); + ndi2.start_pts = Some(since_the_epoch.as_secs() * 1000000000 + + since_the_epoch.subsec_nanos() as u64); + //TODO Another way to save NDI_recv variable + // *state = State{ + // info: state.info.clone(), + // recv: Some(NdiInstance{recv: pNDI_recv}), + // }; + return true; + } + } + + fn stop_ndi() -> bool{ + unsafe{ + let recv = match ndi2.recv{ + None => { + //TODO Update gst_element_error with one more descriptive + //println!("pNDI_recv no encontrado"); + //gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); + return true; + } + Some(ref recv) => recv.clone(), + }; + let pNDI_recv = recv.recv; + NDIlib_recv_destroy(pNDI_recv); + ndi2.recv = None; + //NDIlib_destroy(); + return true; + } + } + + // Static plugin metdata that is directly stored in the plugin shared object and read by GStreamer + // upon loading. + // Plugin name, plugin description, plugin entry point function, version number of this plugin, + // license of the plugin, source package name, binary package name, origin where it comes from + // and the date/time of release. + plugin_define!( + b"ndi\0", + b"NewTek NDI Plugin\0", + plugin_init, + b"1.0\0", + b"MIT/X11\0", + b"ndi\0", + b"ndi\0", + b"https://gitlab.teltek.es/rubenrua/ndi-rs.git\0", + b"2018-04-09\0" + ); diff --git a/gst-plugin-ndi/src/ndiaudiosrc.rs b/gst-plugin-ndi/src/ndiaudiosrc.rs index 07698fdd..e0c3871f 100644 --- a/gst-plugin-ndi/src/ndiaudiosrc.rs +++ b/gst-plugin-ndi/src/ndiaudiosrc.rs @@ -26,6 +26,9 @@ use byte_slice_cast::FromByteSlice; use byte_slice_cast::AsSliceOf; use ndilib::*; +use hue; +use ndi2; +use stop_ndi; // Property value storage #[derive(Debug, Clone)] @@ -138,8 +141,8 @@ impl NdiAudioSrc { klass.set_metadata( "NewTek NDI Audio Source", "Source", - "NewTek NDI video/audio source", - "Ruben Gonzalez ", + "NewTek NDI audio source", + "Ruben Gonzalez , Daniel Vilar ", ); // On the src pad, we can produce F32/F64 with any sample rate @@ -288,16 +291,21 @@ impl NdiAudioSrc { gst_debug!(ndisrc_struct.cat, obj: element, "Received audio frame: {:?}", video_frame); frame = true; //pts = ((video_frame.timestamp as u64) * 100) - state.start_pts.unwrap(); - // println!("{:?}", pts/1000000); - *pts = ((audio_frame.timestamp as u64) * 100); + //println!("{:?}", *pts/1000000); + // println!("{:?}", audio_frame.timestamp); + // println!("{:?}", audio_frame.timecode); + // *pts = ((audio_frame.timestamp as u64) * 100); + *pts = ((audio_frame.timecode as u64) * 100); if *pts2 == 0{ - *pts2 = (audio_frame.timestamp as u64) * 100; + // *pts2 = (audio_frame.timestamp as u64) * 100; + *pts2 = (audio_frame.timecode as u64) * 100; *pts = 0; } else{ // println!("{:?}", video_frame.timecode * 100); // println!("{:?}", pts2.pts); - *pts = (((audio_frame.timestamp as u64) * 100) - *pts2); + // *pts = (((audio_frame.timestamp as u64) * 100) - *pts2); + *pts = (((audio_frame.timecode as u64) * 100) - *pts2); //println!("{:?}", pts/1000000); } } @@ -357,6 +365,7 @@ impl NdiAudioSrc { // Called when starting, so we can initialize all stream-related state to its defaults fn start(&self, element: &BaseSrc) -> bool { + println!("/*/*/*/*/*/*/*/*/*"); // Reset state *self.state.lock().unwrap() = Default::default(); self.unlock_stop(element); @@ -368,151 +377,163 @@ impl NdiAudioSrc { //let mut pNDI_recv = state.recv; unsafe { - if !NDIlib_initialize() { - //println!("Cannot run NDI: NDIlib_initialize error."); - gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_initialize error"]); - return false; + return hue(element, settings.ip.clone(), settings.stream_name.clone()); + // if !NDIlib_initialize() { + // //println!("Cannot run NDI: NDIlib_initialize error."); + // gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_initialize error"]); + // return false; + // } + // + // let mut source: NDIlib_source_t = NDIlib_source_t{p_ndi_name: ptr::null(), + // p_ip_address: ptr::null()}; + // + // // print!("{:?}", settings.stream_name); + // // print!("{:?}", settings.ip); + // + // //TODO default values + // let NDI_find_create_desc: NDIlib_find_create_t = Default::default(); + // let pNDI_find = NDIlib_find_create_v2(&NDI_find_create_desc); + // let ip_ptr = CString::new(settings.ip.clone()).unwrap(); + // if (ip_ptr == CString::new("").unwrap()){ + // if pNDI_find.is_null() { + // //println!("Cannot run NDI: NDIlib_find_create_v2 error."); + // gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_find_create_v2 error"]); + // return false; + // } + // + // let mut total_sources: u32 = 0; + // let mut p_sources = ptr::null(); + // //TODO Delete while. If not, will loop until a source it's available + // //while total_sources == 0 { + // // TODO Sleep 1s to wait for all sources + // thread::sleep(time::Duration::from_millis(2000)); + // p_sources = NDIlib_find_get_current_sources(pNDI_find, &mut total_sources as *mut u32); + // //} + // + // // We need at least one source + // if p_sources.is_null() { + // //println!("Error getting NDIlib_find_get_current_sources."); + // gst_element_error!(element, gst::CoreError::Negotiation, ["Error getting NDIlib_find_get_current_sources"]); + // return false; + // //::std::process::exit(1); + // } + // + // let mut no_source: isize = -1; + // for i in 0..total_sources as isize{ + // if CStr::from_ptr((*p_sources.offset(i)).p_ndi_name) + // .to_string_lossy() + // .into_owned() == settings.stream_name{ + // no_source = i; + // break; + // } + // } + // if no_source == -1 { + // gst_element_error!(element, gst::CoreError::Negotiation, ["Stream name not found"]); + // return false; + // } + // println!( + // "Total_sources {}: Name '{}' Address '{}'", + // total_sources, + // CStr::from_ptr((*p_sources.offset(no_source)).p_ndi_name) + // .to_string_lossy() + // .into_owned(), + // CStr::from_ptr((*p_sources.offset(no_source)).p_ip_address) + // .to_string_lossy() + // .into_owned() + // ); + // source = *p_sources.offset(no_source).clone(); + // } + // else{ + // source.p_ip_address = ip_ptr.as_ptr(); + // println!( + // "Address '{}'", + // CStr::from_ptr(source.p_ip_address) + // .to_string_lossy() + // .into_owned() + // ); + // } + // + // // We now have at least one source, so we create a receiver to look at it. + // // We tell it that we prefer YCbCr video since it is more efficient for us. If the source has an alpha channel + // // it will still be provided in BGRA + // let p_ndi_name = CString::new("Galicaster NDI Receiver").unwrap(); + // let NDI_recv_create_desc = NDIlib_recv_create_v3_t { + // source_to_connect_to: source, + // p_ndi_name: p_ndi_name.as_ptr(), + // ..Default::default() + // }; + // + // let pNDI_recv = NDIlib_recv_create_v3(&NDI_recv_create_desc); + // if pNDI_recv.is_null() { + // //println!("Cannot run NDI: NDIlib_recv_create_v3 error."); + // gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_recv_create_v3 error"]); + // return false; + // //::std::process::exit(1); + // } + // + // // Destroy the NDI finder. We needed to have access to the pointers to p_sources[0] + // NDIlib_find_destroy(pNDI_find); + // + // // We are now going to mark this source as being on program output for tally purposes (but not on preview) + // let tally_state: NDIlib_tally_t = Default::default(); + // NDIlib_recv_set_tally(pNDI_recv, &tally_state); + // + // // Enable Hardwqre Decompression support if this support has it. Please read the caveats in the documentation + // // regarding this. There are times in which it might reduce the performance although on small stream numbers + // // it almost always yields the same or better performance. + // let data = CString::new("").unwrap(); + // let enable_hw_accel = NDIlib_metadata_frame_t { + // length: data.to_bytes().len() as i32, + // timecode: 0, + // p_data: data.as_ptr(), + // }; + // + // NDIlib_recv_send_metadata(pNDI_recv, &enable_hw_accel); + // state.recv = Some(NdiInstance{recv: pNDI_recv}); + // let start = SystemTime::now(); + // let since_the_epoch = start.duration_since(UNIX_EPOCH) + // .expect("Time went backwards"); + // println!("{:?}", since_the_epoch); + // state.start_pts = Some(since_the_epoch.as_secs() * 1000000000 + + // since_the_epoch.subsec_nanos() as u64); + // //TODO Another way to save NDI_recv variable + // // *state = State{ + // // info: state.info.clone(), + // // recv: Some(NdiInstance{recv: pNDI_recv}), + // // }; } - let mut source: NDIlib_source_t = NDIlib_source_t{p_ndi_name: ptr::null(), - p_ip_address: ptr::null()}; - - // print!("{:?}", settings.stream_name); - // print!("{:?}", settings.ip); - - //TODO default values - let NDI_find_create_desc: NDIlib_find_create_t = Default::default(); - let pNDI_find = NDIlib_find_create_v2(&NDI_find_create_desc); - let ip_ptr = CString::new(settings.ip.clone()).unwrap(); - if (ip_ptr == CString::new("").unwrap()){ - if pNDI_find.is_null() { - //println!("Cannot run NDI: NDIlib_find_create_v2 error."); - gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_find_create_v2 error"]); - return false; - } - - let mut total_sources: u32 = 0; - let mut p_sources = ptr::null(); - //TODO Delete while. If not, will loop until a source it's available - //while total_sources == 0 { - // TODO Sleep 1s to wait for all sources - thread::sleep(time::Duration::from_millis(2000)); - p_sources = NDIlib_find_get_current_sources(pNDI_find, &mut total_sources as *mut u32); - //} - - // We need at least one source - if p_sources.is_null() { - //println!("Error getting NDIlib_find_get_current_sources."); - gst_element_error!(element, gst::CoreError::Negotiation, ["Error getting NDIlib_find_get_current_sources"]); - return false; - //::std::process::exit(1); - } - - let mut no_source: isize = -1; - for i in 0..total_sources as isize{ - if CStr::from_ptr((*p_sources.offset(i)).p_ndi_name) - .to_string_lossy() - .into_owned() == settings.stream_name{ - no_source = i; - break; - } - } - if no_source == -1 { - gst_element_error!(element, gst::CoreError::Negotiation, ["Stream name not found"]); - return false; - } - println!( - "Total_sources {}: Name '{}' Address '{}'", - total_sources, - CStr::from_ptr((*p_sources.offset(no_source)).p_ndi_name) - .to_string_lossy() - .into_owned(), - CStr::from_ptr((*p_sources.offset(no_source)).p_ip_address) - .to_string_lossy() - .into_owned() - ); - source = *p_sources.offset(no_source).clone(); - } - else{ - source.p_ip_address = ip_ptr.as_ptr(); - println!( - "Address '{}'", - CStr::from_ptr(source.p_ip_address) - .to_string_lossy() - .into_owned() - ); - } - - // We now have at least one source, so we create a receiver to look at it. - // We tell it that we prefer YCbCr video since it is more efficient for us. If the source has an alpha channel - // it will still be provided in BGRA - let p_ndi_name = CString::new("Galicaster NDI Receiver").unwrap(); - let NDI_recv_create_desc = NDIlib_recv_create_v3_t { - source_to_connect_to: source, - p_ndi_name: p_ndi_name.as_ptr(), - ..Default::default() - }; - - let pNDI_recv = NDIlib_recv_create_v3(&NDI_recv_create_desc); - if pNDI_recv.is_null() { - //println!("Cannot run NDI: NDIlib_recv_create_v3 error."); - gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_recv_create_v3 error"]); - return false; - //::std::process::exit(1); - } - - // Destroy the NDI finder. We needed to have access to the pointers to p_sources[0] - NDIlib_find_destroy(pNDI_find); - - // We are now going to mark this source as being on program output for tally purposes (but not on preview) - let tally_state: NDIlib_tally_t = Default::default(); - NDIlib_recv_set_tally(pNDI_recv, &tally_state); - - // Enable Hardwqre Decompression support if this support has it. Please read the caveats in the documentation - // regarding this. There are times in which it might reduce the performance although on small stream numbers - // it almost always yields the same or better performance. - let data = CString::new("").unwrap(); - let enable_hw_accel = NDIlib_metadata_frame_t { - length: data.to_bytes().len() as i32, - timecode: 0, - p_data: data.as_ptr(), - }; - - NDIlib_recv_send_metadata(pNDI_recv, &enable_hw_accel); - state.recv = Some(NdiInstance{recv: pNDI_recv}); - let start = SystemTime::now(); - let since_the_epoch = start.duration_since(UNIX_EPOCH) - .expect("Time went backwards"); - println!("{:?}", since_the_epoch); - state.start_pts = Some(since_the_epoch.as_secs() * 1000000000 + - since_the_epoch.subsec_nanos() as u64); - //TODO Another way to save NDI_recv variable - // *state = State{ - // info: state.info.clone(), - // recv: Some(NdiInstance{recv: pNDI_recv}), - // }; - } - - true + //true } // Called when shutting down the element so we can release all stream-related state fn stop(&self, element: &BaseSrc) -> bool { // Reset state let state = self.state.lock().unwrap(); - let recv = match state.recv{ - None => { - //println!("pNDI_recv no encontrado"); - gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); - return true; - } - Some(ref recv) => recv.clone(), - }; - let pNDI_recv = recv.recv; - unsafe{ - NDIlib_recv_destroy(pNDI_recv); - //NDIlib_destroy(); - } + // let recv = match state.recv{ + // None => { + // //println!("pNDI_recv no encontrado"); + // gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); + // return true; + // } + // Some(ref recv) => recv.clone(), + // }; + // let pNDI_recv = recv.recv; + // unsafe{ + // let recv = match ndi2.recv{ + // None => { + // //TODO Update gst_element_error with one more descriptive + // //println!("pNDI_recv no encontrado"); + // gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); + // return true; + // } + // Some(ref recv) => recv.clone(), + // }; + // let pNDI_recv = recv.recv; + // NDIlib_recv_destroy(pNDI_recv); + // //NDIlib_destroy(); + // } + stop_ndi(); // Commented because when adding ndi destroy stopped in this line //*self.state.lock().unwrap() = Default::default(); self.unlock(element); @@ -524,12 +545,23 @@ impl NdiAudioSrc { fn fixate(&self, element: &BaseSrc, caps: gst::Caps) -> gst::Caps { //We need to set the correct caps resolution and framerate let state = self.state.lock().unwrap(); - let recv = match state.recv{ + // let recv = match state.recv{ + // None => { + // //TODO Update gst_element_error with one more descriptive + // //println!("pNDI_recv no encontrado"); + // gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); + // //TODO if none not return anything + // return caps; + // } + // Some(ref recv) => recv.clone(), + // }; + // let pNDI_recv = recv.recv; + unsafe{ + let recv = match ndi2.recv{ None => { //TODO Update gst_element_error with one more descriptive //println!("pNDI_recv no encontrado"); gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); - //TODO if none not return anything return caps; } Some(ref recv) => recv.clone(), @@ -553,6 +585,7 @@ impl NdiAudioSrc { // called Caps::fixate() here element.parent_fixate(caps) } + } //Creates the audio buffers fn create( @@ -578,7 +611,18 @@ impl NdiAudioSrc { }; //let mut pNDI_recva = ptr::null(); // { - let recv = match state.recv{ + // let recv = match state.recv{ + // None => { + // //TODO Update gst_element_error with one more descriptive + // //println!("pNDI_recv no encontrado"); + // gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); + // return Err(gst::FlowReturn::NotNegotiated); + // } + // Some(ref recv) => recv.clone(), + // }; + // let pNDI_recv = recv.recv; + unsafe{ + let recv = match ndi2.recv{ None => { //TODO Update gst_element_error with one more descriptive //println!("pNDI_recv no encontrado"); @@ -588,9 +632,18 @@ impl NdiAudioSrc { Some(ref recv) => recv.clone(), }; let pNDI_recv = recv.recv; + + // } - let start_pts = match state.start_pts { + // let start_pts = match state.start_pts { + // None => { + // gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); + // return Err(gst::FlowReturn::NotNegotiated); + // } + // Some(ref start_pts) => start_pts.clone(), + // }; + let start_pts = match ndi2.start_pts { None => { gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); return Err(gst::FlowReturn::NotNegotiated); @@ -624,6 +677,7 @@ impl NdiAudioSrc { gst_debug!(self.cat, obj: element, "Produced buffer {:?}", buffer); Ok(buffer) } + } } diff --git a/gst-plugin-ndi/src/ndivideosrc.rs b/gst-plugin-ndi/src/ndivideosrc.rs index f40d26f5..eb2808e9 100644 --- a/gst-plugin-ndi/src/ndivideosrc.rs +++ b/gst-plugin-ndi/src/ndivideosrc.rs @@ -21,6 +21,9 @@ use std::time::{SystemTime, UNIX_EPOCH}; use std::ffi::{CStr, CString}; use ndilib::*; +use hue; +use ndi2; +use stop_ndi; // Property value storage #[derive(Debug, Clone)] @@ -133,8 +136,8 @@ impl NdiVideoSrc { klass.set_metadata( "NewTek NDI Video Source", "Source", - "NewTek NDI video/audio source", - "Ruben Gonzalez ", + "NewTek NDI video source", + "Ruben Gonzalez , Daniel Vilar ", ); // On the src pad, we can produce F32/F64 with any sample rate @@ -273,15 +276,20 @@ impl NdiVideoSrc { frame = true; //pts = ((video_frame.timestamp as u64) * 100) - state.start_pts.unwrap(); // println!("{:?}", pts/1000000); - *pts = ((video_frame.timestamp as u64) * 100); + // println!("{:?}", video_frame.timestamp); + // println!("{:?}", video_frame.timecode); + //*pts = ((video_frame.timestamp as u64) * 100); + *pts = ((video_frame.timecode as u64) * 100); if *pts2 == 0{ - *pts2 = (video_frame.timestamp as u64) * 100; + // *pts2 = (video_frame.timestamp as u64) * 100; + *pts2 = (video_frame.timecode as u64) * 100; *pts = 0; } else{ // println!("{:?}", video_frame.timecode * 100); // println!("{:?}", pts2.pts); - *pts = (((video_frame.timestamp as u64) * 100) - *pts2); + //*pts = (((video_frame.timestamp as u64) * 100) - *pts2); + *pts = (((video_frame.timecode as u64) * 100) - *pts2); //println!("{:?}", pts/1000000); } @@ -356,151 +364,162 @@ impl NdiVideoSrc { //let mut pNDI_recv = state.recv; unsafe { - if !NDIlib_initialize() { - //println!("Cannot run NDI: NDIlib_initialize error."); - gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_initialize error"]); - return false; + return hue(element, settings.ip.clone(), settings.stream_name.clone()); + // if !NDIlib_initialize() { + // //println!("Cannot run NDI: NDIlib_initialize error."); + // gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_initialize error"]); + // return false; + // } + // + // let mut source: NDIlib_source_t = NDIlib_source_t{p_ndi_name: ptr::null(), + // p_ip_address: ptr::null()}; + // + // // print!("{:?}", settings.stream_name); + // // print!("{:?}", settings.ip); + // + // //TODO default values + // let NDI_find_create_desc: NDIlib_find_create_t = Default::default(); + // let pNDI_find = NDIlib_find_create_v2(&NDI_find_create_desc); + // let ip_ptr = CString::new(settings.ip.clone()).unwrap(); + // if (ip_ptr == CString::new("").unwrap()){ + // if pNDI_find.is_null() { + // //println!("Cannot run NDI: NDIlib_find_create_v2 error."); + // gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_find_create_v2 error"]); + // return false; + // } + // + // let mut total_sources: u32 = 0; + // let mut p_sources = ptr::null(); + // //TODO Delete while. If not, will loop until a source it's available + // //while total_sources == 0 { + // // TODO Sleep 1s to wait for all sources + // thread::sleep(time::Duration::from_millis(2000)); + // p_sources = NDIlib_find_get_current_sources(pNDI_find, &mut total_sources as *mut u32); + // //} + // + // // We need at least one source + // if p_sources.is_null() { + // //println!("Error getting NDIlib_find_get_current_sources."); + // gst_element_error!(element, gst::CoreError::Negotiation, ["Error getting NDIlib_find_get_current_sources"]); + // return false; + // //::std::process::exit(1); + // } + // + // let mut no_source: isize = -1; + // for i in 0..total_sources as isize{ + // if CStr::from_ptr((*p_sources.offset(i)).p_ndi_name) + // .to_string_lossy() + // .into_owned() == settings.stream_name{ + // no_source = i; + // break; + // } + // } + // if no_source == -1 { + // gst_element_error!(element, gst::CoreError::Negotiation, ["Stream name not found"]); + // return false; + // } + // println!( + // "Total_sources {}: Name '{}' Address '{}'", + // total_sources, + // CStr::from_ptr((*p_sources.offset(no_source)).p_ndi_name) + // .to_string_lossy() + // .into_owned(), + // CStr::from_ptr((*p_sources.offset(no_source)).p_ip_address) + // .to_string_lossy() + // .into_owned() + // ); + // source = *p_sources.offset(no_source).clone(); + // } + // else{ + // source.p_ip_address = ip_ptr.as_ptr(); + // println!( + // "Address '{}'", + // CStr::from_ptr(source.p_ip_address) + // .to_string_lossy() + // .into_owned() + // ); + // } + // + // // We now have at least one source, so we create a receiver to look at it. + // // We tell it that we prefer YCbCr video since it is more efficient for us. If the source has an alpha channel + // // it will still be provided in BGRA + // let p_ndi_name = CString::new("Galicaster NDI Receiver").unwrap(); + // let NDI_recv_create_desc = NDIlib_recv_create_v3_t { + // source_to_connect_to: source, + // p_ndi_name: p_ndi_name.as_ptr(), + // ..Default::default() + // }; + // + // let pNDI_recv = NDIlib_recv_create_v3(&NDI_recv_create_desc); + // if pNDI_recv.is_null() { + // //println!("Cannot run NDI: NDIlib_recv_create_v3 error."); + // gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_recv_create_v3 error"]); + // return false; + // //::std::process::exit(1); + // } + // + // // Destroy the NDI finder. We needed to have access to the pointers to p_sources[0] + // NDIlib_find_destroy(pNDI_find); + // + // // We are now going to mark this source as being on program output for tally purposes (but not on preview) + // let tally_state: NDIlib_tally_t = Default::default(); + // NDIlib_recv_set_tally(pNDI_recv, &tally_state); + // + // // Enable Hardwqre Decompression support if this support has it. Please read the caveats in the documentation + // // regarding this. There are times in which it might reduce the performance although on small stream numbers + // // it almost always yields the same or better performance. + // let data = CString::new("").unwrap(); + // let enable_hw_accel = NDIlib_metadata_frame_t { + // length: data.to_bytes().len() as i32, + // timecode: 0, + // p_data: data.as_ptr(), + // }; + // + // NDIlib_recv_send_metadata(pNDI_recv, &enable_hw_accel); + // state.recv = Some(NdiInstance{recv: pNDI_recv}); + // let start = SystemTime::now(); + // let since_the_epoch = start.duration_since(UNIX_EPOCH) + // .expect("Time went backwards"); + // println!("{:?}", since_the_epoch); + // state.start_pts = Some(since_the_epoch.as_secs() * 1000000000 + + // since_the_epoch.subsec_nanos() as u64); + // //TODO Another way to save NDI_recv variable + // // *state = State{ + // // info: state.info.clone(), + // // recv: Some(NdiInstance{recv: pNDI_recv}), + // // }; } - let mut source: NDIlib_source_t = NDIlib_source_t{p_ndi_name: ptr::null(), - p_ip_address: ptr::null()}; - - // print!("{:?}", settings.stream_name); - // print!("{:?}", settings.ip); - - //TODO default values - let NDI_find_create_desc: NDIlib_find_create_t = Default::default(); - let pNDI_find = NDIlib_find_create_v2(&NDI_find_create_desc); - let ip_ptr = CString::new(settings.ip.clone()).unwrap(); - if (ip_ptr == CString::new("").unwrap()){ - if pNDI_find.is_null() { - //println!("Cannot run NDI: NDIlib_find_create_v2 error."); - gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_find_create_v2 error"]); - return false; - } - - let mut total_sources: u32 = 0; - let mut p_sources = ptr::null(); - //TODO Delete while. If not, will loop until a source it's available - //while total_sources == 0 { - // TODO Sleep 1s to wait for all sources - thread::sleep(time::Duration::from_millis(2000)); - p_sources = NDIlib_find_get_current_sources(pNDI_find, &mut total_sources as *mut u32); - //} - - // We need at least one source - if p_sources.is_null() { - //println!("Error getting NDIlib_find_get_current_sources."); - gst_element_error!(element, gst::CoreError::Negotiation, ["Error getting NDIlib_find_get_current_sources"]); - return false; - //::std::process::exit(1); - } - - let mut no_source: isize = -1; - for i in 0..total_sources as isize{ - if CStr::from_ptr((*p_sources.offset(i)).p_ndi_name) - .to_string_lossy() - .into_owned() == settings.stream_name{ - no_source = i; - break; - } - } - if no_source == -1 { - gst_element_error!(element, gst::CoreError::Negotiation, ["Stream name not found"]); - return false; - } - println!( - "Total_sources {}: Name '{}' Address '{}'", - total_sources, - CStr::from_ptr((*p_sources.offset(no_source)).p_ndi_name) - .to_string_lossy() - .into_owned(), - CStr::from_ptr((*p_sources.offset(no_source)).p_ip_address) - .to_string_lossy() - .into_owned() - ); - source = *p_sources.offset(no_source).clone(); - } - else{ - source.p_ip_address = ip_ptr.as_ptr(); - println!( - "Address '{}'", - CStr::from_ptr(source.p_ip_address) - .to_string_lossy() - .into_owned() - ); - } - - // We now have at least one source, so we create a receiver to look at it. - // We tell it that we prefer YCbCr video since it is more efficient for us. If the source has an alpha channel - // it will still be provided in BGRA - let p_ndi_name = CString::new("Galicaster NDI Receiver").unwrap(); - let NDI_recv_create_desc = NDIlib_recv_create_v3_t { - source_to_connect_to: source, - p_ndi_name: p_ndi_name.as_ptr(), - ..Default::default() - }; - - let pNDI_recv = NDIlib_recv_create_v3(&NDI_recv_create_desc); - if pNDI_recv.is_null() { - //println!("Cannot run NDI: NDIlib_recv_create_v3 error."); - gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_recv_create_v3 error"]); - return false; - //::std::process::exit(1); - } - - // Destroy the NDI finder. We needed to have access to the pointers to p_sources[0] - NDIlib_find_destroy(pNDI_find); - - // We are now going to mark this source as being on program output for tally purposes (but not on preview) - let tally_state: NDIlib_tally_t = Default::default(); - NDIlib_recv_set_tally(pNDI_recv, &tally_state); - - // Enable Hardwqre Decompression support if this support has it. Please read the caveats in the documentation - // regarding this. There are times in which it might reduce the performance although on small stream numbers - // it almost always yields the same or better performance. - let data = CString::new("").unwrap(); - let enable_hw_accel = NDIlib_metadata_frame_t { - length: data.to_bytes().len() as i32, - timecode: 0, - p_data: data.as_ptr(), - }; - - NDIlib_recv_send_metadata(pNDI_recv, &enable_hw_accel); - state.recv = Some(NdiInstance{recv: pNDI_recv}); - let start = SystemTime::now(); - let since_the_epoch = start.duration_since(UNIX_EPOCH) - .expect("Time went backwards"); - println!("{:?}", since_the_epoch); - state.start_pts = Some(since_the_epoch.as_secs() * 1000000000 + - since_the_epoch.subsec_nanos() as u64); - //TODO Another way to save NDI_recv variable - // *state = State{ - // info: state.info.clone(), - // recv: Some(NdiInstance{recv: pNDI_recv}), - // }; - } - - true + // true } // Called when shutting down the element so we can release all stream-related state fn stop(&self, element: &BaseSrc) -> bool { // Reset state let state = self.state.lock().unwrap(); - let recv = match state.recv{ - None => { - //println!("pNDI_recv no encontrado"); - gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); - return true; - } - Some(ref recv) => recv.clone(), - }; - let pNDI_recv = recv.recv; - unsafe{ - NDIlib_recv_destroy(pNDI_recv); - //NDIlib_destroy(); - } + // let recv = match state.recv{ + // None => { + // //println!("pNDI_recv no encontrado"); + // gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); + // return true; + // } + // Some(ref recv) => recv.clone(), + // }; + // let pNDI_recv = recv.recv; + // unsafe{ + // let recv = match ndi2.recv{ + // None => { + // //println!("pNDI_recv no encontrado"); + // gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); + // return true; + // } + // Some(ref recv) => recv.clone(), + // }; + // let pNDI_recv = recv.recv; + // NDIlib_recv_destroy(pNDI_recv); + // //NDIlib_destroy(); + // } + stop_ndi(); // Commented because when adding ndi destroy stopped in this line //*self.state.lock().unwrap() = Default::default(); self.unlock(element); @@ -512,7 +531,20 @@ impl NdiVideoSrc { fn fixate(&self, element: &BaseSrc, caps: gst::Caps) -> gst::Caps { //We need to set the correct caps resolution and framerate let state = self.state.lock().unwrap(); - let recv = match state.recv{ + // let recv = match state.recv{ + // None => { + // //TODO Update gst_element_error with one more descriptive + // //println!("pNDI_recv no encontrado"); + // gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); + // //TODO if none not return anything + // return caps; + // } + // Some(ref recv) => recv.clone(), + // }; + // + // let pNDI_recv = recv.recv; + unsafe{ + let recv = match ndi2.recv{ None => { //TODO Update gst_element_error with one more descriptive //println!("pNDI_recv no encontrado"); @@ -544,6 +576,7 @@ impl NdiVideoSrc { // called Caps::fixate() here element.parent_fixate(caps) } + } //Creates the audio buffers fn create( @@ -569,7 +602,18 @@ impl NdiVideoSrc { }; //let mut pNDI_recva = ptr::null(); // { - let recv = match state.recv{ + // let recv = match state.recv{ + // None => { + // //TODO Update gst_element_error with one more descriptive + // //println!("pNDI_recv no encontrado"); + // gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); + // return Err(gst::FlowReturn::NotNegotiated); + // } + // Some(ref recv) => recv.clone(), + // }; + // let pNDI_recv = recv.recv; + unsafe{ + let recv = match ndi2.recv{ None => { //TODO Update gst_element_error with one more descriptive //println!("pNDI_recv no encontrado"); @@ -581,7 +625,15 @@ impl NdiVideoSrc { let pNDI_recv = recv.recv; // } - let start_pts = match state.start_pts { + // let start_pts = match state.start_pts { + // None => { + // gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); + // return Err(gst::FlowReturn::NotNegotiated); + // } + // Some(ref start_pts) => start_pts.clone(), + // }; + + let start_pts = match ndi2.start_pts { None => { gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); return Err(gst::FlowReturn::NotNegotiated); @@ -705,7 +757,7 @@ impl NdiVideoSrc { Ok(buffer) } } - +} From 893c2172122d301d7e377a6e9203fab599d425ac Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Tue, 26 Jun 2018 13:06:55 +0200 Subject: [PATCH 045/199] Refactor get frame logic --- gst-plugin-ndi/src/ndiaudiosrc.rs | 207 ++++++++------ gst-plugin-ndi/src/ndivideosrc.rs | 449 ++++++++++++++++-------------- 2 files changed, 351 insertions(+), 305 deletions(-) diff --git a/gst-plugin-ndi/src/ndiaudiosrc.rs b/gst-plugin-ndi/src/ndiaudiosrc.rs index e0c3871f..f0e7d0fc 100644 --- a/gst-plugin-ndi/src/ndiaudiosrc.rs +++ b/gst-plugin-ndi/src/ndiaudiosrc.rs @@ -29,6 +29,7 @@ use ndilib::*; use hue; use ndi2; use stop_ndi; +use get_frame; // Property value storage #[derive(Debug, Clone)] @@ -251,93 +252,93 @@ impl NdiAudioSrc { impl ElementImpl for NdiAudioSrc { } - fn get_frame(ndisrc_struct: &NdiAudioSrc, element: &BaseSrc, pNDI_recv : NDIlib_recv_instance_t, pts2 : &mut u64, pts : &mut u64) -> NDIlib_audio_frame_v2_t{ - unsafe{ - let video_frame: NDIlib_video_frame_v2_t = Default::default(); - let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); - let metadata_frame: NDIlib_metadata_frame_t = Default::default(); - - //TODO Only create buffer when we got a video frame - let mut frame = false; - while !frame{ - let frame_type = NDIlib_recv_capture_v2( - pNDI_recv, - ptr::null(), - &audio_frame, - ptr::null(), - 1000, - ); - match frame_type { - NDIlib_frame_type_e::NDIlib_frame_type_video => { - println!("Videeeeeeo frrrame"); - gst_debug!(ndisrc_struct.cat, obj: element, "Received video frame: {:?}", video_frame); - //frame = true; - //pts = ((video_frame.timestamp as u64) * 100) - state.start_pts.unwrap(); - // println!("{:?}", pts/1000000); - *pts = ((video_frame.timestamp as u64) * 100); - if *pts2 == 0{ - *pts2 = (video_frame.timestamp as u64) * 100; - *pts = 0; - } - else{ - // println!("{:?}", video_frame.timecode * 100); - // println!("{:?}", pts2.pts); - *pts = (((video_frame.timestamp as u64) * 100) - *pts2); - //println!("{:?}", pts/1000000); - } - - } - NDIlib_frame_type_e::NDIlib_frame_type_audio => { - gst_debug!(ndisrc_struct.cat, obj: element, "Received audio frame: {:?}", video_frame); - frame = true; - //pts = ((video_frame.timestamp as u64) * 100) - state.start_pts.unwrap(); - //println!("{:?}", *pts/1000000); - // println!("{:?}", audio_frame.timestamp); - // println!("{:?}", audio_frame.timecode); - // *pts = ((audio_frame.timestamp as u64) * 100); - *pts = ((audio_frame.timecode as u64) * 100); - if *pts2 == 0{ - // *pts2 = (audio_frame.timestamp as u64) * 100; - *pts2 = (audio_frame.timecode as u64) * 100; - *pts = 0; - } - else{ - // println!("{:?}", video_frame.timecode * 100); - // println!("{:?}", pts2.pts); - // *pts = (((audio_frame.timestamp as u64) * 100) - *pts2); - *pts = (((audio_frame.timecode as u64) * 100) - *pts2); - //println!("{:?}", pts/1000000); - } - } - NDIlib_frame_type_e::NDIlib_frame_type_metadata => { - // println!( - // "Tengo metadata {} '{}'", - // metadata_frame.length, - // CStr::from_ptr(metadata_frame.p_data) - // .to_string_lossy() - // .into_owned(), - // ); - //TODO Change gst_warning to gst_debug - gst_debug!(ndisrc_struct.cat, obj: element, "Received metadata frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned(),); - } - NDIlib_frame_type_e::NDIlib_frame_type_error => { - // println!( - // "Tengo error {} '{}'", - // metadata_frame.length, - // CStr::from_ptr(metadata_frame.p_data) - // .to_string_lossy() - // .into_owned(), - // ); - //TODO Change gst_warning to gst_debug - gst_debug!(ndisrc_struct.cat, obj: element, "Received error frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned()); - // break; - } - _ => println!("Tengo {:?}", frame_type), - } - } - return audio_frame; - } - } + // fn get_frame(ndisrc_struct: &NdiAudioSrc, element: &BaseSrc, pNDI_recv : NDIlib_recv_instance_t, pts2 : &mut u64, pts : &mut u64) -> NDIlib_audio_frame_v2_t{ + // unsafe{ + // let video_frame: NDIlib_video_frame_v2_t = Default::default(); + // let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); + // let metadata_frame: NDIlib_metadata_frame_t = Default::default(); + // + // //TODO Only create buffer when we got a video frame + // let mut frame = false; + // while !frame{ + // let frame_type = NDIlib_recv_capture_v2( + // pNDI_recv, + // ptr::null(), + // &audio_frame, + // ptr::null(), + // 1000, + // ); + // match frame_type { + // NDIlib_frame_type_e::NDIlib_frame_type_video => { + // println!("Videeeeeeo frrrame"); + // gst_debug!(ndisrc_struct.cat, obj: element, "Received video frame: {:?}", video_frame); + // //frame = true; + // //pts = ((video_frame.timestamp as u64) * 100) - state.start_pts.unwrap(); + // // println!("{:?}", pts/1000000); + // *pts = ((video_frame.timestamp as u64) * 100); + // if *pts2 == 0{ + // *pts2 = (video_frame.timestamp as u64) * 100; + // *pts = 0; + // } + // else{ + // // println!("{:?}", video_frame.timecode * 100); + // // println!("{:?}", pts2.pts); + // *pts = (((video_frame.timestamp as u64) * 100) - *pts2); + // //println!("{:?}", pts/1000000); + // } + // + // } + // NDIlib_frame_type_e::NDIlib_frame_type_audio => { + // gst_debug!(ndisrc_struct.cat, obj: element, "Received audio frame: {:?}", video_frame); + // frame = true; + // //pts = ((video_frame.timestamp as u64) * 100) - state.start_pts.unwrap(); + // //println!("{:?}", *pts/1000000); + // // println!("{:?}", audio_frame.timestamp); + // // println!("{:?}", audio_frame.timecode); + // // *pts = ((audio_frame.timestamp as u64) * 100); + // *pts = ((audio_frame.timecode as u64) * 100); + // if *pts2 == 0{ + // // *pts2 = (audio_frame.timestamp as u64) * 100; + // *pts2 = (audio_frame.timecode as u64) * 100; + // *pts = 0; + // } + // else{ + // // println!("{:?}", video_frame.timecode * 100); + // // println!("{:?}", pts2.pts); + // // *pts = (((audio_frame.timestamp as u64) * 100) - *pts2); + // *pts = (((audio_frame.timecode as u64) * 100) - *pts2); + // //println!("{:?}", pts/1000000); + // } + // } + // NDIlib_frame_type_e::NDIlib_frame_type_metadata => { + // // println!( + // // "Tengo metadata {} '{}'", + // // metadata_frame.length, + // // CStr::from_ptr(metadata_frame.p_data) + // // .to_string_lossy() + // // .into_owned(), + // // ); + // //TODO Change gst_warning to gst_debug + // gst_debug!(ndisrc_struct.cat, obj: element, "Received metadata frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned(),); + // } + // NDIlib_frame_type_e::NDIlib_frame_type_error => { + // // println!( + // // "Tengo error {} '{}'", + // // metadata_frame.length, + // // CStr::from_ptr(metadata_frame.p_data) + // // .to_string_lossy() + // // .into_owned(), + // // ); + // //TODO Change gst_warning to gst_debug + // gst_debug!(ndisrc_struct.cat, obj: element, "Received error frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned()); + // // break; + // } + // _ => println!("Tengo {:?}", frame_type), + // } + // } + // return audio_frame; + // } + // } // Virtual methods of gst_base::BaseSrc impl BaseSrcImpl for NdiAudioSrc { @@ -571,7 +572,20 @@ impl NdiAudioSrc { let mut pts2 = self.pts.lock().unwrap(); let mut pts: u64 = 0; - let audio_frame: NDIlib_audio_frame_v2_t = get_frame(self, element, pNDI_recv, &mut pts2.pts, &mut pts); + // let video_frame: NDIlib_video_frame_v2_t = Default::default(); + let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); + // let metadata_frame: NDIlib_metadata_frame_t = Default::default(); + //let audio_frame: NDIlib_audio_frame_v2_t = get_frame(self, element, pNDI_recv, &mut pts2.pts, &mut pts); + //get_frame(element, pNDI_recv, &video_frame, &audio_frame, &metadata_frame, &mut pts2.pts, &mut pts); + + let mut frame_type: NDIlib_frame_type_e = NDIlib_frame_type_e::NDIlib_frame_type_none; + while (frame_type != NDIlib_frame_type_e::NDIlib_frame_type_audio){ + frame_type = NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000); + } + println!("{:?}", audio_frame.timecode); + pts2.pts = (audio_frame.timecode as u64) * 100; + pts = 0; + let mut caps = gst::Caps::truncate(caps); { let caps = caps.make_mut(); @@ -654,10 +668,19 @@ impl NdiAudioSrc { unsafe{ // // loop { let mut pts: u64 = 0; - let video_frame: NDIlib_video_frame_v2_t = Default::default(); - let audio_frame: NDIlib_audio_frame_v2_t = get_frame(self, element, pNDI_recv, &mut pts2.pts, &mut pts); - let metadata_frame: NDIlib_metadata_frame_t = Default::default(); - + // let video_frame: NDIlib_video_frame_v2_t = Default::default(); + //let audio_frame: NDIlib_audio_frame_v2_t = get_frame(self, element, pNDI_recv, &mut pts2.pts, &mut pts); + let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); + // let metadata_frame: NDIlib_metadata_frame_t = Default::default(); + //get_frame(element, pNDI_recv, &video_frame, &audio_frame, &metadata_frame, &mut pts2.pts, &mut pts); + NDIlib_recv_capture_v2( + pNDI_recv, + ptr::null(), + &audio_frame, + ptr::null(), + 1000, + ); + pts = (((audio_frame.timecode as u64) * 100) - pts2.pts); let buff_size = ((audio_frame.channel_stride_in_bytes)) as usize; let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); { diff --git a/gst-plugin-ndi/src/ndivideosrc.rs b/gst-plugin-ndi/src/ndivideosrc.rs index eb2808e9..7540de9a 100644 --- a/gst-plugin-ndi/src/ndivideosrc.rs +++ b/gst-plugin-ndi/src/ndivideosrc.rs @@ -24,6 +24,7 @@ use ndilib::*; use hue; use ndi2; use stop_ndi; +use get_frame; // Property value storage #[derive(Debug, Clone)] @@ -253,79 +254,79 @@ impl NdiVideoSrc { impl ElementImpl for NdiVideoSrc { } - fn get_frame(ndisrc_struct: &NdiVideoSrc, element: &BaseSrc, pNDI_recv : NDIlib_recv_instance_t, pts2 : &mut u64, pts : &mut u64) -> NDIlib_video_frame_v2_t{ - unsafe{ - let video_frame: NDIlib_video_frame_v2_t = Default::default(); - let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); - let metadata_frame: NDIlib_metadata_frame_t = Default::default(); - - //TODO Only create buffer when we got a video frame - let mut frame = false; - while !frame{ - let frame_type = NDIlib_recv_capture_v2( - pNDI_recv, - &video_frame, - ptr::null(), - ptr::null(), - 1000, - ); - - match frame_type { - NDIlib_frame_type_e::NDIlib_frame_type_video => { - gst_debug!(ndisrc_struct.cat, obj: element, "Received video frame: {:?}", video_frame); - frame = true; - //pts = ((video_frame.timestamp as u64) * 100) - state.start_pts.unwrap(); - // println!("{:?}", pts/1000000); - // println!("{:?}", video_frame.timestamp); - // println!("{:?}", video_frame.timecode); - //*pts = ((video_frame.timestamp as u64) * 100); - *pts = ((video_frame.timecode as u64) * 100); - if *pts2 == 0{ - // *pts2 = (video_frame.timestamp as u64) * 100; - *pts2 = (video_frame.timecode as u64) * 100; - *pts = 0; - } - else{ - // println!("{:?}", video_frame.timecode * 100); - // println!("{:?}", pts2.pts); - //*pts = (((video_frame.timestamp as u64) * 100) - *pts2); - *pts = (((video_frame.timecode as u64) * 100) - *pts2); - //println!("{:?}", pts/1000000); - } - - } - NDIlib_frame_type_e::NDIlib_frame_type_audio => { - gst_debug!(ndisrc_struct.cat, obj: element, "Received audio frame: {:?}", video_frame); - } - NDIlib_frame_type_e::NDIlib_frame_type_metadata => { - // println!( - // "Tengo metadata {} '{}'", - // metadata_frame.length, - // CStr::from_ptr(metadata_frame.p_data) - // .to_string_lossy() - // .into_owned(), - // ); - //TODO Change gst_warning to gst_debug - gst_debug!(ndisrc_struct.cat, obj: element, "Received metadata frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned(),); - } - NDIlib_frame_type_e::NDIlib_frame_type_error => { - // println!( - // "Tengo error {} '{}'", - // metadata_frame.length, - // CStr::from_ptr(metadata_frame.p_data) - // .to_string_lossy() - // .into_owned(), - // ); - //TODO Change gst_warning to gst_debug - gst_debug!(ndisrc_struct.cat, obj: element, "Received error frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned()); - // break; - } - _ => println!("Tengo {:?}", frame_type), - } - } - return video_frame; - } - } + // fn get_frame(ndisrc_struct: &NdiVideoSrc, element: &BaseSrc, pNDI_recv : NDIlib_recv_instance_t, pts2 : &mut u64, pts : &mut u64) -> NDIlib_video_frame_v2_t{ + // unsafe{ + // let video_frame: NDIlib_video_frame_v2_t = Default::default(); + // let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); + // let metadata_frame: NDIlib_metadata_frame_t = Default::default(); + // + // //TODO Only create buffer when we got a video frame + // let mut frame = false; + // while !frame{ + // let frame_type = NDIlib_recv_capture_v2( + // pNDI_recv, + // &video_frame, + // ptr::null(), + // ptr::null(), + // 1000, + // ); + // + // match frame_type { + // NDIlib_frame_type_e::NDIlib_frame_type_video => { + // gst_debug!(ndisrc_struct.cat, obj: element, "Received video frame: {:?}", video_frame); + // frame = true; + // //pts = ((video_frame.timestamp as u64) * 100) - state.start_pts.unwrap(); + // // println!("{:?}", pts/1000000); + // // println!("{:?}", video_frame.timestamp); + // // println!("{:?}", video_frame.timecode); + // //*pts = ((video_frame.timestamp as u64) * 100); + // *pts = ((video_frame.timecode as u64) * 100); + // if *pts2 == 0{ + // // *pts2 = (video_frame.timestamp as u64) * 100; + // *pts2 = (video_frame.timecode as u64) * 100; + // *pts = 0; + // } + // else{ + // // println!("{:?}", video_frame.timecode * 100); + // // println!("{:?}", pts2.pts); + // //*pts = (((video_frame.timestamp as u64) * 100) - *pts2); + // *pts = (((video_frame.timecode as u64) * 100) - *pts2); + // //println!("{:?}", pts/1000000); + // } + // + // } + // NDIlib_frame_type_e::NDIlib_frame_type_audio => { + // gst_debug!(ndisrc_struct.cat, obj: element, "Received audio frame: {:?}", video_frame); + // } + // NDIlib_frame_type_e::NDIlib_frame_type_metadata => { + // // println!( + // // "Tengo metadata {} '{}'", + // // metadata_frame.length, + // // CStr::from_ptr(metadata_frame.p_data) + // // .to_string_lossy() + // // .into_owned(), + // // ); + // //TODO Change gst_warning to gst_debug + // gst_debug!(ndisrc_struct.cat, obj: element, "Received metadata frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned(),); + // } + // NDIlib_frame_type_e::NDIlib_frame_type_error => { + // // println!( + // // "Tengo error {} '{}'", + // // metadata_frame.length, + // // CStr::from_ptr(metadata_frame.p_data) + // // .to_string_lossy() + // // .into_owned(), + // // ); + // //TODO Change gst_warning to gst_debug + // gst_debug!(ndisrc_struct.cat, obj: element, "Received error frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned()); + // // break; + // } + // _ => println!("Tengo {:?}", frame_type), + // } + // } + // return video_frame; + // } + // } // Virtual methods of gst_base::BaseSrc impl BaseSrcImpl for NdiVideoSrc { @@ -488,62 +489,62 @@ impl NdiVideoSrc { // // info: state.info.clone(), // // recv: Some(NdiInstance{recv: pNDI_recv}), // // }; - } - - // true } - // Called when shutting down the element so we can release all stream-related state - fn stop(&self, element: &BaseSrc) -> bool { - // Reset state - let state = self.state.lock().unwrap(); - // let recv = match state.recv{ - // None => { - // //println!("pNDI_recv no encontrado"); - // gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); - // return true; - // } - // Some(ref recv) => recv.clone(), - // }; - // let pNDI_recv = recv.recv; - // unsafe{ - // let recv = match ndi2.recv{ - // None => { - // //println!("pNDI_recv no encontrado"); - // gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); - // return true; - // } - // Some(ref recv) => recv.clone(), - // }; - // let pNDI_recv = recv.recv; - // NDIlib_recv_destroy(pNDI_recv); - // //NDIlib_destroy(); - // } - stop_ndi(); - // Commented because when adding ndi destroy stopped in this line - //*self.state.lock().unwrap() = Default::default(); - self.unlock(element); - gst_info!(self.cat, obj: element, "Stopped"); + // true + } - true - } + // Called when shutting down the element so we can release all stream-related state + fn stop(&self, element: &BaseSrc) -> bool { + // Reset state + let state = self.state.lock().unwrap(); + // let recv = match state.recv{ + // None => { + // //println!("pNDI_recv no encontrado"); + // gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); + // return true; + // } + // Some(ref recv) => recv.clone(), + // }; + // let pNDI_recv = recv.recv; + // unsafe{ + // let recv = match ndi2.recv{ + // None => { + // //println!("pNDI_recv no encontrado"); + // gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); + // return true; + // } + // Some(ref recv) => recv.clone(), + // }; + // let pNDI_recv = recv.recv; + // NDIlib_recv_destroy(pNDI_recv); + // //NDIlib_destroy(); + // } + stop_ndi(); + // Commented because when adding ndi destroy stopped in this line + //*self.state.lock().unwrap() = Default::default(); + self.unlock(element); + gst_info!(self.cat, obj: element, "Stopped"); - fn fixate(&self, element: &BaseSrc, caps: gst::Caps) -> gst::Caps { - //We need to set the correct caps resolution and framerate - let state = self.state.lock().unwrap(); - // let recv = match state.recv{ - // None => { - // //TODO Update gst_element_error with one more descriptive - // //println!("pNDI_recv no encontrado"); - // gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); - // //TODO if none not return anything - // return caps; - // } - // Some(ref recv) => recv.clone(), - // }; - // - // let pNDI_recv = recv.recv; - unsafe{ + true + } + + fn fixate(&self, element: &BaseSrc, caps: gst::Caps) -> gst::Caps { + //We need to set the correct caps resolution and framerate + let state = self.state.lock().unwrap(); + // let recv = match state.recv{ + // None => { + // //TODO Update gst_element_error with one more descriptive + // //println!("pNDI_recv no encontrado"); + // gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); + // //TODO if none not return anything + // return caps; + // } + // Some(ref recv) => recv.clone(), + // }; + // + // let pNDI_recv = recv.recv; + unsafe{ let recv = match ndi2.recv{ None => { //TODO Update gst_element_error with one more descriptive @@ -559,7 +560,18 @@ impl NdiVideoSrc { let mut pts2 = self.pts.lock().unwrap(); let mut pts: u64 = 0; - let video_frame: NDIlib_video_frame_v2_t = get_frame(self, element, pNDI_recv, &mut pts2.pts, &mut pts); + let video_frame: NDIlib_video_frame_v2_t = Default::default(); + // let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); + // let metadata_frame: NDIlib_metadata_frame_t = Default::default(); + //let video_frame: NDIlib_video_frame_v2_t = get_frame(self, element, pNDI_recv, &mut pts2.pts, &mut pts); + //get_frame(element, pNDI_recv, &video_frame, &audio_frame, &metadata_frame, &mut pts2.pts, &mut pts); + let mut frame_type: NDIlib_frame_type_e = NDIlib_frame_type_e::NDIlib_frame_type_none; + while (frame_type != NDIlib_frame_type_e::NDIlib_frame_type_video){ + frame_type = NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000); + } + println!("{:?}", video_frame.timecode); + pts2.pts = (video_frame.timecode as u64) * 100; + pts = 0; let mut caps = gst::Caps::truncate(caps); { let caps = caps.make_mut(); @@ -578,41 +590,41 @@ impl NdiVideoSrc { } } - //Creates the audio buffers - fn create( - &self, - element: &BaseSrc, - _offset: u64, - _length: u32, - ) -> Result { - // Keep a local copy of the values of all our properties at this very moment. This - // ensures that the mutex is never locked for long and the application wouldn't - // have to block until this function returns when getting/setting property values - let _settings = &*self.settings.lock().unwrap(); + //Creates the audio buffers + fn create( + &self, + element: &BaseSrc, + _offset: u64, + _length: u32, + ) -> Result { + // Keep a local copy of the values of all our properties at this very moment. This + // ensures that the mutex is never locked for long and the application wouldn't + // have to block until this function returns when getting/setting property values + let _settings = &*self.settings.lock().unwrap(); - let mut pts2 = self.pts.lock().unwrap(); - // Get a locked reference to our state, i.e. the input and output AudioInfo - let state = self.state.lock().unwrap(); - let _info = match state.info { - None => { - gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); - return Err(gst::FlowReturn::NotNegotiated); - } - Some(ref info) => info.clone(), - }; - //let mut pNDI_recva = ptr::null(); - // { - // let recv = match state.recv{ - // None => { - // //TODO Update gst_element_error with one more descriptive - // //println!("pNDI_recv no encontrado"); - // gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); - // return Err(gst::FlowReturn::NotNegotiated); - // } - // Some(ref recv) => recv.clone(), - // }; - // let pNDI_recv = recv.recv; - unsafe{ + let mut pts2 = self.pts.lock().unwrap(); + // Get a locked reference to our state, i.e. the input and output AudioInfo + let state = self.state.lock().unwrap(); + let _info = match state.info { + None => { + gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); + return Err(gst::FlowReturn::NotNegotiated); + } + Some(ref info) => info.clone(), + }; + //let mut pNDI_recva = ptr::null(); + // { + // let recv = match state.recv{ + // None => { + // //TODO Update gst_element_error with one more descriptive + // //println!("pNDI_recv no encontrado"); + // gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); + // return Err(gst::FlowReturn::NotNegotiated); + // } + // Some(ref recv) => recv.clone(), + // }; + // let pNDI_recv = recv.recv; + unsafe{ let recv = match ndi2.recv{ None => { //TODO Update gst_element_error with one more descriptive @@ -644,9 +656,20 @@ impl NdiVideoSrc { unsafe{ // // loop { let mut pts: u64 = 0; - let video_frame: NDIlib_video_frame_v2_t = get_frame(self, element, pNDI_recv, &mut pts2.pts, &mut pts); - let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); - let metadata_frame: NDIlib_metadata_frame_t = Default::default(); + //let video_frame: NDIlib_video_frame_v2_t = get_frame(self, element, pNDI_recv, &mut pts2.pts, &mut pts); + let video_frame: NDIlib_video_frame_v2_t = Default::default(); + // let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); + // let metadata_frame: NDIlib_metadata_frame_t = Default::default(); + //get_frame(element, pNDI_recv, &video_frame, &audio_frame, &metadata_frame, &mut pts2.pts, &mut pts); + + NDIlib_recv_capture_v2( + pNDI_recv, + &video_frame, + ptr::null(), + ptr::null(), + 1000, + ); + pts = (((video_frame.timecode as u64) * 100) - pts2.pts); //video_frame = get_frame(self, element, pNDI_recv, pts2.pts); // //TODO Only create buffer when we got a video frame @@ -757,62 +780,62 @@ impl NdiVideoSrc { Ok(buffer) } } -} - - - - fn unlock(&self, element: &BaseSrc) -> bool { - // This should unblock the create() function ASAP, so we - // just unschedule the clock it here, if any. - gst_debug!(self.cat, obj: element, "Unlocking"); - let mut clock_wait = self.clock_wait.lock().unwrap(); - if let Some(clock_id) = clock_wait.clock_id.take() { - clock_id.unschedule(); - } - clock_wait.flushing = true; - - true - } - - fn unlock_stop(&self, element: &BaseSrc) -> bool { - // This signals that unlocking is done, so we can reset - // all values again. - gst_debug!(self.cat, obj: element, "Unlock stop"); - let mut clock_wait = self.clock_wait.lock().unwrap(); - clock_wait.flushing = false; - - true - } } - // This zero-sized struct is containing the static metadata of our element. It is only necessary to - // be able to implement traits on it, but e.g. a plugin that registers multiple elements with the - // same code would use this struct to store information about the concrete element. An example of - // this would be a plugin that wraps around a library that has multiple decoders with the same API, - // but wants (as it should) a separate element registered for each decoder. - struct NdiVideoSrcStatic; - // The basic trait for registering the type: This returns a name for the type and registers the - // instance and class initializations functions with the type system, thus hooking everything - // together. - impl ImplTypeStatic for NdiVideoSrcStatic { - fn get_name(&self) -> &str { - "NdiVideoSrc" - } - fn new(&self, element: &BaseSrc) -> Box> { - NdiVideoSrc::new(element) + fn unlock(&self, element: &BaseSrc) -> bool { + // This should unblock the create() function ASAP, so we + // just unschedule the clock it here, if any. + gst_debug!(self.cat, obj: element, "Unlocking"); + let mut clock_wait = self.clock_wait.lock().unwrap(); + if let Some(clock_id) = clock_wait.clock_id.take() { + clock_id.unschedule(); } + clock_wait.flushing = true; - fn class_init(&self, klass: &mut BaseSrcClass) { - NdiVideoSrc::class_init(klass); - } + true } - // Registers the type for our element, and then registers in GStreamer under - // the name NdiVideoSrc for being able to instantiate it via e.g. - // gst::ElementFactory::make(). - pub fn register(plugin: &gst::Plugin) { - let type_ = register_type(NdiVideoSrcStatic); - gst::Element::register(plugin, "ndivideosrc", 0, type_); + fn unlock_stop(&self, element: &BaseSrc) -> bool { + // This signals that unlocking is done, so we can reset + // all values again. + gst_debug!(self.cat, obj: element, "Unlock stop"); + let mut clock_wait = self.clock_wait.lock().unwrap(); + clock_wait.flushing = false; + + true } + } + + // This zero-sized struct is containing the static metadata of our element. It is only necessary to + // be able to implement traits on it, but e.g. a plugin that registers multiple elements with the + // same code would use this struct to store information about the concrete element. An example of + // this would be a plugin that wraps around a library that has multiple decoders with the same API, + // but wants (as it should) a separate element registered for each decoder. + struct NdiVideoSrcStatic; + + // The basic trait for registering the type: This returns a name for the type and registers the + // instance and class initializations functions with the type system, thus hooking everything + // together. + impl ImplTypeStatic for NdiVideoSrcStatic { + fn get_name(&self) -> &str { + "NdiVideoSrc" + } + + fn new(&self, element: &BaseSrc) -> Box> { + NdiVideoSrc::new(element) + } + + fn class_init(&self, klass: &mut BaseSrcClass) { + NdiVideoSrc::class_init(klass); + } + } + + // Registers the type for our element, and then registers in GStreamer under + // the name NdiVideoSrc for being able to instantiate it via e.g. + // gst::ElementFactory::make(). + pub fn register(plugin: &gst::Plugin) { + let type_ = register_type(NdiVideoSrcStatic); + gst::Element::register(plugin, "ndivideosrc", 0, type_); + } From 43db3e4477b9b86088ba7ee0a9bba18177bf52a8 Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Tue, 26 Jun 2018 14:07:43 +0200 Subject: [PATCH 046/199] Clean code --- gst-plugin-ndi/src/lib.rs | 40 +-- gst-plugin-ndi/src/ndiaudiosrc.rs | 552 +++++++----------------------- gst-plugin-ndi/src/ndivideosrc.rs | 449 ++---------------------- 3 files changed, 168 insertions(+), 873 deletions(-) diff --git a/gst-plugin-ndi/src/lib.rs b/gst-plugin-ndi/src/lib.rs index 1909e03c..6420a9df 100644 --- a/gst-plugin-ndi/src/lib.rs +++ b/gst-plugin-ndi/src/lib.rs @@ -1,3 +1,5 @@ +#![allow(non_camel_case_types, non_upper_case_globals, non_snake_case)] + // Copyright (C) 2017 Sebastian Dröge // // Licensed under the Apache License, Version 2.0 bool { ndivideosrc::register(plugin); ndiaudiosrc::register(plugin); - // a::test(2); - // println!("---------------------------------------"); - // let mut ndistruc = ndi1{ - // recv : 0 - // }; true } -struct ndi1{ +struct ndi{ recv: Option, start_pts: Option, } -static mut ndi2: ndi1 = ndi1{ +static mut ndi_struct: ndi = ndi{ recv: None, start_pts: None, }; -fn hue(element: &BaseSrc, ip: String, stream_name: String) -> bool{ - println!("---------------------------------------"); +fn connect_ndi(element: &BaseSrc, ip: String, stream_name: String) -> bool{ unsafe { - match ndi2.recv { + match ndi_struct.recv { None => { //gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); //return true; } _ => return true, }; - //println!("{:?}", ndi2.recv); - //ndi2.recv = i; - //println!("{:?}", ndi2.recv); if !NDIlib_initialize() { //println!("Cannot run NDI: NDIlib_initialize error."); gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_initialize error"]); @@ -77,14 +69,11 @@ fn hue(element: &BaseSrc, ip: String, stream_name: String) -> bool{ let mut source: NDIlib_source_t = NDIlib_source_t{p_ndi_name: ptr::null(), p_ip_address: ptr::null()}; - // print!("{:?}", settings.stream_name); - // print!("{:?}", settings.ip); - //TODO default values let NDI_find_create_desc: NDIlib_find_create_t = Default::default(); let pNDI_find = NDIlib_find_create_v2(&NDI_find_create_desc); let ip_ptr = CString::new(ip.clone()).unwrap(); - if (ip_ptr == CString::new("").unwrap()){ + if ip_ptr == CString::new("").unwrap(){ if pNDI_find.is_null() { //println!("Cannot run NDI: NDIlib_find_create_v2 error."); gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_find_create_v2 error"]); @@ -92,7 +81,7 @@ fn hue(element: &BaseSrc, ip: String, stream_name: String) -> bool{ } let mut total_sources: u32 = 0; - let mut p_sources = ptr::null(); + let p_sources; //TODO Delete while. If not, will loop until a source it's available //while total_sources == 0 { // TODO Sleep 1s to wait for all sources @@ -179,25 +168,20 @@ fn hue(element: &BaseSrc, ip: String, stream_name: String) -> bool{ }; NDIlib_recv_send_metadata(pNDI_recv, &enable_hw_accel); - ndi2.recv = Some(NdiInstance{recv: pNDI_recv}); + ndi_struct.recv = Some(NdiInstance{recv: pNDI_recv}); let start = SystemTime::now(); let since_the_epoch = start.duration_since(UNIX_EPOCH) .expect("Time went backwards"); println!("{:?}", since_the_epoch); - ndi2.start_pts = Some(since_the_epoch.as_secs() * 1000000000 + + ndi_struct.start_pts = Some(since_the_epoch.as_secs() * 1000000000 + since_the_epoch.subsec_nanos() as u64); - //TODO Another way to save NDI_recv variable - // *state = State{ - // info: state.info.clone(), - // recv: Some(NdiInstance{recv: pNDI_recv}), - // }; return true; } } fn stop_ndi() -> bool{ unsafe{ - let recv = match ndi2.recv{ + let recv = match ndi_struct.recv{ None => { //TODO Update gst_element_error with one more descriptive //println!("pNDI_recv no encontrado"); @@ -208,7 +192,7 @@ fn hue(element: &BaseSrc, ip: String, stream_name: String) -> bool{ }; let pNDI_recv = recv.recv; NDIlib_recv_destroy(pNDI_recv); - ndi2.recv = None; + ndi_struct.recv = None; //NDIlib_destroy(); return true; } diff --git a/gst-plugin-ndi/src/ndiaudiosrc.rs b/gst-plugin-ndi/src/ndiaudiosrc.rs index f0e7d0fc..ae419ae4 100644 --- a/gst-plugin-ndi/src/ndiaudiosrc.rs +++ b/gst-plugin-ndi/src/ndiaudiosrc.rs @@ -5,7 +5,6 @@ use gst; use gst::prelude::*; use gst_audio; use gst_base::prelude::*; -use gst::Fraction; use gst_plugin::base_src::*; use gst_plugin::element::*; @@ -16,20 +15,11 @@ use std::sync::Mutex; use std::{i32, u32}; use std::ptr; -use std::{thread, time}; -use std::time::{SystemTime, UNIX_EPOCH}; -use std::ffi::{CStr, CString}; - -use num_traits::float::Float; -use num_traits::cast::NumCast; -use byte_slice_cast::FromByteSlice; -use byte_slice_cast::AsSliceOf; use ndilib::*; -use hue; -use ndi2; +use connect_ndi; +use ndi_struct; use stop_ndi; -use get_frame; // Property value storage #[derive(Debug, Clone)] @@ -69,16 +59,12 @@ Property::String( // and sample offset struct State { info: Option, - recv: Option, - start_pts: Option, } impl Default for State { fn default() -> State { State { info: None, - recv: None, - start_pts: None, } } } @@ -252,94 +238,6 @@ impl NdiAudioSrc { impl ElementImpl for NdiAudioSrc { } - // fn get_frame(ndisrc_struct: &NdiAudioSrc, element: &BaseSrc, pNDI_recv : NDIlib_recv_instance_t, pts2 : &mut u64, pts : &mut u64) -> NDIlib_audio_frame_v2_t{ - // unsafe{ - // let video_frame: NDIlib_video_frame_v2_t = Default::default(); - // let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); - // let metadata_frame: NDIlib_metadata_frame_t = Default::default(); - // - // //TODO Only create buffer when we got a video frame - // let mut frame = false; - // while !frame{ - // let frame_type = NDIlib_recv_capture_v2( - // pNDI_recv, - // ptr::null(), - // &audio_frame, - // ptr::null(), - // 1000, - // ); - // match frame_type { - // NDIlib_frame_type_e::NDIlib_frame_type_video => { - // println!("Videeeeeeo frrrame"); - // gst_debug!(ndisrc_struct.cat, obj: element, "Received video frame: {:?}", video_frame); - // //frame = true; - // //pts = ((video_frame.timestamp as u64) * 100) - state.start_pts.unwrap(); - // // println!("{:?}", pts/1000000); - // *pts = ((video_frame.timestamp as u64) * 100); - // if *pts2 == 0{ - // *pts2 = (video_frame.timestamp as u64) * 100; - // *pts = 0; - // } - // else{ - // // println!("{:?}", video_frame.timecode * 100); - // // println!("{:?}", pts2.pts); - // *pts = (((video_frame.timestamp as u64) * 100) - *pts2); - // //println!("{:?}", pts/1000000); - // } - // - // } - // NDIlib_frame_type_e::NDIlib_frame_type_audio => { - // gst_debug!(ndisrc_struct.cat, obj: element, "Received audio frame: {:?}", video_frame); - // frame = true; - // //pts = ((video_frame.timestamp as u64) * 100) - state.start_pts.unwrap(); - // //println!("{:?}", *pts/1000000); - // // println!("{:?}", audio_frame.timestamp); - // // println!("{:?}", audio_frame.timecode); - // // *pts = ((audio_frame.timestamp as u64) * 100); - // *pts = ((audio_frame.timecode as u64) * 100); - // if *pts2 == 0{ - // // *pts2 = (audio_frame.timestamp as u64) * 100; - // *pts2 = (audio_frame.timecode as u64) * 100; - // *pts = 0; - // } - // else{ - // // println!("{:?}", video_frame.timecode * 100); - // // println!("{:?}", pts2.pts); - // // *pts = (((audio_frame.timestamp as u64) * 100) - *pts2); - // *pts = (((audio_frame.timecode as u64) * 100) - *pts2); - // //println!("{:?}", pts/1000000); - // } - // } - // NDIlib_frame_type_e::NDIlib_frame_type_metadata => { - // // println!( - // // "Tengo metadata {} '{}'", - // // metadata_frame.length, - // // CStr::from_ptr(metadata_frame.p_data) - // // .to_string_lossy() - // // .into_owned(), - // // ); - // //TODO Change gst_warning to gst_debug - // gst_debug!(ndisrc_struct.cat, obj: element, "Received metadata frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned(),); - // } - // NDIlib_frame_type_e::NDIlib_frame_type_error => { - // // println!( - // // "Tengo error {} '{}'", - // // metadata_frame.length, - // // CStr::from_ptr(metadata_frame.p_data) - // // .to_string_lossy() - // // .into_owned(), - // // ); - // //TODO Change gst_warning to gst_debug - // gst_debug!(ndisrc_struct.cat, obj: element, "Received error frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned()); - // // break; - // } - // _ => println!("Tengo {:?}", frame_type), - // } - // } - // return audio_frame; - // } - // } - // Virtual methods of gst_base::BaseSrc impl BaseSrcImpl for NdiAudioSrc { // Called whenever the input/output caps are changing, i.e. in the very beginning before data @@ -366,199 +264,33 @@ impl NdiAudioSrc { // Called when starting, so we can initialize all stream-related state to its defaults fn start(&self, element: &BaseSrc) -> bool { - println!("/*/*/*/*/*/*/*/*/*"); // Reset state *self.state.lock().unwrap() = Default::default(); self.unlock_stop(element); gst_warning!(self.cat, obj: element, "Starting"); - let mut state = self.state.lock().unwrap(); - //let mut settings = self.settings.lock().unwrap(); let settings = self.settings.lock().unwrap(); + return connect_ndi(element, settings.ip.clone(), settings.stream_name.clone()); - //let mut pNDI_recv = state.recv; - unsafe { - return hue(element, settings.ip.clone(), settings.stream_name.clone()); - // if !NDIlib_initialize() { - // //println!("Cannot run NDI: NDIlib_initialize error."); - // gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_initialize error"]); - // return false; - // } - // - // let mut source: NDIlib_source_t = NDIlib_source_t{p_ndi_name: ptr::null(), - // p_ip_address: ptr::null()}; - // - // // print!("{:?}", settings.stream_name); - // // print!("{:?}", settings.ip); - // - // //TODO default values - // let NDI_find_create_desc: NDIlib_find_create_t = Default::default(); - // let pNDI_find = NDIlib_find_create_v2(&NDI_find_create_desc); - // let ip_ptr = CString::new(settings.ip.clone()).unwrap(); - // if (ip_ptr == CString::new("").unwrap()){ - // if pNDI_find.is_null() { - // //println!("Cannot run NDI: NDIlib_find_create_v2 error."); - // gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_find_create_v2 error"]); - // return false; - // } - // - // let mut total_sources: u32 = 0; - // let mut p_sources = ptr::null(); - // //TODO Delete while. If not, will loop until a source it's available - // //while total_sources == 0 { - // // TODO Sleep 1s to wait for all sources - // thread::sleep(time::Duration::from_millis(2000)); - // p_sources = NDIlib_find_get_current_sources(pNDI_find, &mut total_sources as *mut u32); - // //} - // - // // We need at least one source - // if p_sources.is_null() { - // //println!("Error getting NDIlib_find_get_current_sources."); - // gst_element_error!(element, gst::CoreError::Negotiation, ["Error getting NDIlib_find_get_current_sources"]); - // return false; - // //::std::process::exit(1); - // } - // - // let mut no_source: isize = -1; - // for i in 0..total_sources as isize{ - // if CStr::from_ptr((*p_sources.offset(i)).p_ndi_name) - // .to_string_lossy() - // .into_owned() == settings.stream_name{ - // no_source = i; - // break; - // } - // } - // if no_source == -1 { - // gst_element_error!(element, gst::CoreError::Negotiation, ["Stream name not found"]); - // return false; - // } - // println!( - // "Total_sources {}: Name '{}' Address '{}'", - // total_sources, - // CStr::from_ptr((*p_sources.offset(no_source)).p_ndi_name) - // .to_string_lossy() - // .into_owned(), - // CStr::from_ptr((*p_sources.offset(no_source)).p_ip_address) - // .to_string_lossy() - // .into_owned() - // ); - // source = *p_sources.offset(no_source).clone(); - // } - // else{ - // source.p_ip_address = ip_ptr.as_ptr(); - // println!( - // "Address '{}'", - // CStr::from_ptr(source.p_ip_address) - // .to_string_lossy() - // .into_owned() - // ); - // } - // - // // We now have at least one source, so we create a receiver to look at it. - // // We tell it that we prefer YCbCr video since it is more efficient for us. If the source has an alpha channel - // // it will still be provided in BGRA - // let p_ndi_name = CString::new("Galicaster NDI Receiver").unwrap(); - // let NDI_recv_create_desc = NDIlib_recv_create_v3_t { - // source_to_connect_to: source, - // p_ndi_name: p_ndi_name.as_ptr(), - // ..Default::default() - // }; - // - // let pNDI_recv = NDIlib_recv_create_v3(&NDI_recv_create_desc); - // if pNDI_recv.is_null() { - // //println!("Cannot run NDI: NDIlib_recv_create_v3 error."); - // gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_recv_create_v3 error"]); - // return false; - // //::std::process::exit(1); - // } - // - // // Destroy the NDI finder. We needed to have access to the pointers to p_sources[0] - // NDIlib_find_destroy(pNDI_find); - // - // // We are now going to mark this source as being on program output for tally purposes (but not on preview) - // let tally_state: NDIlib_tally_t = Default::default(); - // NDIlib_recv_set_tally(pNDI_recv, &tally_state); - // - // // Enable Hardwqre Decompression support if this support has it. Please read the caveats in the documentation - // // regarding this. There are times in which it might reduce the performance although on small stream numbers - // // it almost always yields the same or better performance. - // let data = CString::new("").unwrap(); - // let enable_hw_accel = NDIlib_metadata_frame_t { - // length: data.to_bytes().len() as i32, - // timecode: 0, - // p_data: data.as_ptr(), - // }; - // - // NDIlib_recv_send_metadata(pNDI_recv, &enable_hw_accel); - // state.recv = Some(NdiInstance{recv: pNDI_recv}); - // let start = SystemTime::now(); - // let since_the_epoch = start.duration_since(UNIX_EPOCH) - // .expect("Time went backwards"); - // println!("{:?}", since_the_epoch); - // state.start_pts = Some(since_the_epoch.as_secs() * 1000000000 + - // since_the_epoch.subsec_nanos() as u64); - // //TODO Another way to save NDI_recv variable - // // *state = State{ - // // info: state.info.clone(), - // // recv: Some(NdiInstance{recv: pNDI_recv}), - // // }; - } + } - //true - } + // Called when shutting down the element so we can release all stream-related state + fn stop(&self, element: &BaseSrc) -> bool { + // Reset state + *self.state.lock().unwrap() = Default::default(); + stop_ndi(); + // Commented because when adding ndi destroy stopped in this line + //*self.state.lock().unwrap() = Default::default(); + self.unlock(element); + gst_info!(self.cat, obj: element, "Stopped"); - // Called when shutting down the element so we can release all stream-related state - fn stop(&self, element: &BaseSrc) -> bool { - // Reset state - let state = self.state.lock().unwrap(); - // let recv = match state.recv{ - // None => { - // //println!("pNDI_recv no encontrado"); - // gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); - // return true; - // } - // Some(ref recv) => recv.clone(), - // }; - // let pNDI_recv = recv.recv; - // unsafe{ - // let recv = match ndi2.recv{ - // None => { - // //TODO Update gst_element_error with one more descriptive - // //println!("pNDI_recv no encontrado"); - // gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); - // return true; - // } - // Some(ref recv) => recv.clone(), - // }; - // let pNDI_recv = recv.recv; - // NDIlib_recv_destroy(pNDI_recv); - // //NDIlib_destroy(); - // } - stop_ndi(); - // Commented because when adding ndi destroy stopped in this line - //*self.state.lock().unwrap() = Default::default(); - self.unlock(element); - gst_info!(self.cat, obj: element, "Stopped"); + true + } - true - } - - fn fixate(&self, element: &BaseSrc, caps: gst::Caps) -> gst::Caps { - //We need to set the correct caps resolution and framerate - let state = self.state.lock().unwrap(); - // let recv = match state.recv{ - // None => { - // //TODO Update gst_element_error with one more descriptive - // //println!("pNDI_recv no encontrado"); - // gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); - // //TODO if none not return anything - // return caps; - // } - // Some(ref recv) => recv.clone(), - // }; - // let pNDI_recv = recv.recv; - unsafe{ - let recv = match ndi2.recv{ + fn fixate(&self, element: &BaseSrc, caps: gst::Caps) -> gst::Caps { + //We need to set the correct caps resolution and framerate + unsafe{ + let recv = match ndi_struct.recv{ None => { //TODO Update gst_element_error with one more descriptive //println!("pNDI_recv no encontrado"); @@ -570,21 +302,13 @@ impl NdiAudioSrc { let pNDI_recv = recv.recv; let mut pts2 = self.pts.lock().unwrap(); - let mut pts: u64 = 0; - // let video_frame: NDIlib_video_frame_v2_t = Default::default(); let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); - // let metadata_frame: NDIlib_metadata_frame_t = Default::default(); - //let audio_frame: NDIlib_audio_frame_v2_t = get_frame(self, element, pNDI_recv, &mut pts2.pts, &mut pts); - //get_frame(element, pNDI_recv, &video_frame, &audio_frame, &metadata_frame, &mut pts2.pts, &mut pts); - let mut frame_type: NDIlib_frame_type_e = NDIlib_frame_type_e::NDIlib_frame_type_none; - while (frame_type != NDIlib_frame_type_e::NDIlib_frame_type_audio){ + while frame_type != NDIlib_frame_type_e::NDIlib_frame_type_audio{ frame_type = NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000); } - println!("{:?}", audio_frame.timecode); pts2.pts = (audio_frame.timecode as u64) * 100; - pts = 0; let mut caps = gst::Caps::truncate(caps); { @@ -601,42 +325,30 @@ impl NdiAudioSrc { } } - //Creates the audio buffers - fn create( - &self, - element: &BaseSrc, - _offset: u64, - _length: u32, - ) -> Result { - // Keep a local copy of the values of all our properties at this very moment. This - // ensures that the mutex is never locked for long and the application wouldn't - // have to block until this function returns when getting/setting property values - let _settings = &*self.settings.lock().unwrap(); + //Creates the audio buffers + fn create( + &self, + element: &BaseSrc, + _offset: u64, + _length: u32, + ) -> Result { + // Keep a local copy of the values of all our properties at this very moment. This + // ensures that the mutex is never locked for long and the application wouldn't + // have to block until this function returns when getting/setting property values + let _settings = &*self.settings.lock().unwrap(); - let mut pts2 = self.pts.lock().unwrap(); - // Get a locked reference to our state, i.e. the input and output AudioInfo - let state = self.state.lock().unwrap(); - let _info = match state.info { - None => { - gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); - return Err(gst::FlowReturn::NotNegotiated); - } - Some(ref info) => info.clone(), - }; - //let mut pNDI_recva = ptr::null(); - // { - // let recv = match state.recv{ - // None => { - // //TODO Update gst_element_error with one more descriptive - // //println!("pNDI_recv no encontrado"); - // gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); - // return Err(gst::FlowReturn::NotNegotiated); - // } - // Some(ref recv) => recv.clone(), - // }; - // let pNDI_recv = recv.recv; - unsafe{ - let recv = match ndi2.recv{ + let mut pts2 = self.pts.lock().unwrap(); + // Get a locked reference to our state, i.e. the input and output AudioInfo + let state = self.state.lock().unwrap(); + let _info = match state.info { + None => { + gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); + return Err(gst::FlowReturn::NotNegotiated); + } + Some(ref info) => info.clone(), + }; + unsafe{ + let recv = match ndi_struct.recv{ None => { //TODO Update gst_element_error with one more descriptive //println!("pNDI_recv no encontrado"); @@ -646,116 +358,92 @@ impl NdiAudioSrc { Some(ref recv) => recv.clone(), }; let pNDI_recv = recv.recv; + let pts: u64; + let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); - - // } - - // let start_pts = match state.start_pts { - // None => { - // gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); - // return Err(gst::FlowReturn::NotNegotiated); - // } - // Some(ref start_pts) => start_pts.clone(), - // }; - let start_pts = match ndi2.start_pts { - None => { - gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); - return Err(gst::FlowReturn::NotNegotiated); - } - Some(ref start_pts) => start_pts.clone(), - }; - - unsafe{ - // // loop { - let mut pts: u64 = 0; - // let video_frame: NDIlib_video_frame_v2_t = Default::default(); - //let audio_frame: NDIlib_audio_frame_v2_t = get_frame(self, element, pNDI_recv, &mut pts2.pts, &mut pts); - let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); - // let metadata_frame: NDIlib_metadata_frame_t = Default::default(); - //get_frame(element, pNDI_recv, &video_frame, &audio_frame, &metadata_frame, &mut pts2.pts, &mut pts); - NDIlib_recv_capture_v2( - pNDI_recv, - ptr::null(), - &audio_frame, - ptr::null(), - 1000, - ); - pts = (((audio_frame.timecode as u64) * 100) - pts2.pts); - let buff_size = ((audio_frame.channel_stride_in_bytes)) as usize; - let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); - { - let vec = Vec::from_raw_parts(audio_frame.p_data as *mut u8, buff_size, buff_size); - //TODO Set pts, duration and other info about the buffer - let pts: gst::ClockTime = (pts).into(); - let duration: gst::ClockTime = (20154200).into(); - let buffer = buffer.get_mut().unwrap(); - buffer.set_pts(pts); - //buffer.set_duration(duration); - buffer.set_offset(pts2.offset); - buffer.set_offset_end(pts2.offset + 1); - pts2.offset = pts2.offset +1; - buffer.copy_from_slice(0, &vec).unwrap(); - } - - gst_debug!(self.cat, obj: element, "Produced buffer {:?}", buffer); - Ok(buffer) - } + NDIlib_recv_capture_v2( + pNDI_recv, + ptr::null(), + &audio_frame, + ptr::null(), + 1000, + ); + pts = ((audio_frame.timecode as u64) * 100) - pts2.pts; + let buff_size = ((audio_frame.channel_stride_in_bytes)) as usize; + let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); + { + let vec = Vec::from_raw_parts(audio_frame.p_data as *mut u8, buff_size, buff_size); + //TODO Set pts, duration and other info about the buffer + let pts: gst::ClockTime = (pts).into(); + let duration: gst::ClockTime = (20154200).into(); + let buffer = buffer.get_mut().unwrap(); + buffer.set_pts(pts); + buffer.set_duration(duration); + buffer.set_offset(pts2.offset); + buffer.set_offset_end(pts2.offset + 1); + pts2.offset = pts2.offset +1; + buffer.copy_from_slice(0, &vec).unwrap(); } + + gst_debug!(self.cat, obj: element, "Produced buffer {:?}", buffer); + Ok(buffer) } - - fn unlock(&self, element: &BaseSrc) -> bool { - // This should unblock the create() function ASAP, so we - // just unschedule the clock it here, if any. - gst_debug!(self.cat, obj: element, "Unlocking"); - let mut clock_wait = self.clock_wait.lock().unwrap(); - if let Some(clock_id) = clock_wait.clock_id.take() { - clock_id.unschedule(); - } - clock_wait.flushing = true; - - true - } - - fn unlock_stop(&self, element: &BaseSrc) -> bool { - // This signals that unlocking is done, so we can reset - // all values again. - gst_debug!(self.cat, obj: element, "Unlock stop"); - let mut clock_wait = self.clock_wait.lock().unwrap(); - clock_wait.flushing = false; - - true - } } - // This zero-sized struct is containing the static metadata of our element. It is only necessary to - // be able to implement traits on it, but e.g. a plugin that registers multiple elements with the - // same code would use this struct to store information about the concrete element. An example of - // this would be a plugin that wraps around a library that has multiple decoders with the same API, - // but wants (as it should) a separate element registered for each decoder. - struct NdiAudioSrcStatic; - // The basic trait for registering the type: This returns a name for the type and registers the - // instance and class initializations functions with the type system, thus hooking everything - // together. - impl ImplTypeStatic for NdiAudioSrcStatic { - fn get_name(&self) -> &str { - "NdiAudioSrc" + fn unlock(&self, element: &BaseSrc) -> bool { + // This should unblock the create() function ASAP, so we + // just unschedule the clock it here, if any. + gst_debug!(self.cat, obj: element, "Unlocking"); + let mut clock_wait = self.clock_wait.lock().unwrap(); + if let Some(clock_id) = clock_wait.clock_id.take() { + clock_id.unschedule(); } + clock_wait.flushing = true; - fn new(&self, element: &BaseSrc) -> Box> { - NdiAudioSrc::new(element) - } - - fn class_init(&self, klass: &mut BaseSrcClass) { - NdiAudioSrc::class_init(klass); - } + true } - // Registers the type for our element, and then registers in GStreamer under - // the name NdiAudioSrc for being able to instantiate it via e.g. - // gst::ElementFactory::make(). - pub fn register(plugin: &gst::Plugin) { - let type_ = register_type(NdiAudioSrcStatic); - gst::Element::register(plugin, "ndiaudiosrc", 0, type_); + fn unlock_stop(&self, element: &BaseSrc) -> bool { + // This signals that unlocking is done, so we can reset + // all values again. + gst_debug!(self.cat, obj: element, "Unlock stop"); + let mut clock_wait = self.clock_wait.lock().unwrap(); + clock_wait.flushing = false; + + true } + } + + // This zero-sized struct is containing the static metadata of our element. It is only necessary to + // be able to implement traits on it, but e.g. a plugin that registers multiple elements with the + // same code would use this struct to store information about the concrete element. An example of + // this would be a plugin that wraps around a library that has multiple decoders with the same API, + // but wants (as it should) a separate element registered for each decoder. + struct NdiAudioSrcStatic; + + // The basic trait for registering the type: This returns a name for the type and registers the + // instance and class initializations functions with the type system, thus hooking everything + // together. + impl ImplTypeStatic for NdiAudioSrcStatic { + fn get_name(&self) -> &str { + "NdiAudioSrc" + } + + fn new(&self, element: &BaseSrc) -> Box> { + NdiAudioSrc::new(element) + } + + fn class_init(&self, klass: &mut BaseSrcClass) { + NdiAudioSrc::class_init(klass); + } + } + + // Registers the type for our element, and then registers in GStreamer under + // the name NdiAudioSrc for being able to instantiate it via e.g. + // gst::ElementFactory::make(). + pub fn register(plugin: &gst::Plugin) { + let type_ = register_type(NdiAudioSrcStatic); + gst::Element::register(plugin, "ndiaudiosrc", 0, type_); + } diff --git a/gst-plugin-ndi/src/ndivideosrc.rs b/gst-plugin-ndi/src/ndivideosrc.rs index 7540de9a..c7d66002 100644 --- a/gst-plugin-ndi/src/ndivideosrc.rs +++ b/gst-plugin-ndi/src/ndivideosrc.rs @@ -16,15 +16,11 @@ use std::sync::Mutex; use std::{i32, u32}; use std::ptr; -use std::{thread, time}; -use std::time::{SystemTime, UNIX_EPOCH}; -use std::ffi::{CStr, CString}; use ndilib::*; -use hue; -use ndi2; +use connect_ndi; +use ndi_struct; use stop_ndi; -use get_frame; // Property value storage #[derive(Debug, Clone)] @@ -64,16 +60,12 @@ Property::String( // and sample offset struct State { info: Option, - recv: Option, - start_pts: Option, } impl Default for State { fn default() -> State { State { info: None, - recv: None, - start_pts: None, } } } @@ -254,80 +246,6 @@ impl NdiVideoSrc { impl ElementImpl for NdiVideoSrc { } - // fn get_frame(ndisrc_struct: &NdiVideoSrc, element: &BaseSrc, pNDI_recv : NDIlib_recv_instance_t, pts2 : &mut u64, pts : &mut u64) -> NDIlib_video_frame_v2_t{ - // unsafe{ - // let video_frame: NDIlib_video_frame_v2_t = Default::default(); - // let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); - // let metadata_frame: NDIlib_metadata_frame_t = Default::default(); - // - // //TODO Only create buffer when we got a video frame - // let mut frame = false; - // while !frame{ - // let frame_type = NDIlib_recv_capture_v2( - // pNDI_recv, - // &video_frame, - // ptr::null(), - // ptr::null(), - // 1000, - // ); - // - // match frame_type { - // NDIlib_frame_type_e::NDIlib_frame_type_video => { - // gst_debug!(ndisrc_struct.cat, obj: element, "Received video frame: {:?}", video_frame); - // frame = true; - // //pts = ((video_frame.timestamp as u64) * 100) - state.start_pts.unwrap(); - // // println!("{:?}", pts/1000000); - // // println!("{:?}", video_frame.timestamp); - // // println!("{:?}", video_frame.timecode); - // //*pts = ((video_frame.timestamp as u64) * 100); - // *pts = ((video_frame.timecode as u64) * 100); - // if *pts2 == 0{ - // // *pts2 = (video_frame.timestamp as u64) * 100; - // *pts2 = (video_frame.timecode as u64) * 100; - // *pts = 0; - // } - // else{ - // // println!("{:?}", video_frame.timecode * 100); - // // println!("{:?}", pts2.pts); - // //*pts = (((video_frame.timestamp as u64) * 100) - *pts2); - // *pts = (((video_frame.timecode as u64) * 100) - *pts2); - // //println!("{:?}", pts/1000000); - // } - // - // } - // NDIlib_frame_type_e::NDIlib_frame_type_audio => { - // gst_debug!(ndisrc_struct.cat, obj: element, "Received audio frame: {:?}", video_frame); - // } - // NDIlib_frame_type_e::NDIlib_frame_type_metadata => { - // // println!( - // // "Tengo metadata {} '{}'", - // // metadata_frame.length, - // // CStr::from_ptr(metadata_frame.p_data) - // // .to_string_lossy() - // // .into_owned(), - // // ); - // //TODO Change gst_warning to gst_debug - // gst_debug!(ndisrc_struct.cat, obj: element, "Received metadata frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned(),); - // } - // NDIlib_frame_type_e::NDIlib_frame_type_error => { - // // println!( - // // "Tengo error {} '{}'", - // // metadata_frame.length, - // // CStr::from_ptr(metadata_frame.p_data) - // // .to_string_lossy() - // // .into_owned(), - // // ); - // //TODO Change gst_warning to gst_debug - // gst_debug!(ndisrc_struct.cat, obj: element, "Received error frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned()); - // // break; - // } - // _ => println!("Tengo {:?}", frame_type), - // } - // } - // return video_frame; - // } - // } - // Virtual methods of gst_base::BaseSrc impl BaseSrcImpl for NdiVideoSrc { // Called whenever the input/output caps are changing, i.e. in the very beginning before data @@ -359,167 +277,14 @@ impl NdiVideoSrc { self.unlock_stop(element); gst_warning!(self.cat, obj: element, "Starting"); - let mut state = self.state.lock().unwrap(); - //let mut settings = self.settings.lock().unwrap(); let settings = self.settings.lock().unwrap(); - - //let mut pNDI_recv = state.recv; - unsafe { - return hue(element, settings.ip.clone(), settings.stream_name.clone()); - // if !NDIlib_initialize() { - // //println!("Cannot run NDI: NDIlib_initialize error."); - // gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_initialize error"]); - // return false; - // } - // - // let mut source: NDIlib_source_t = NDIlib_source_t{p_ndi_name: ptr::null(), - // p_ip_address: ptr::null()}; - // - // // print!("{:?}", settings.stream_name); - // // print!("{:?}", settings.ip); - // - // //TODO default values - // let NDI_find_create_desc: NDIlib_find_create_t = Default::default(); - // let pNDI_find = NDIlib_find_create_v2(&NDI_find_create_desc); - // let ip_ptr = CString::new(settings.ip.clone()).unwrap(); - // if (ip_ptr == CString::new("").unwrap()){ - // if pNDI_find.is_null() { - // //println!("Cannot run NDI: NDIlib_find_create_v2 error."); - // gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_find_create_v2 error"]); - // return false; - // } - // - // let mut total_sources: u32 = 0; - // let mut p_sources = ptr::null(); - // //TODO Delete while. If not, will loop until a source it's available - // //while total_sources == 0 { - // // TODO Sleep 1s to wait for all sources - // thread::sleep(time::Duration::from_millis(2000)); - // p_sources = NDIlib_find_get_current_sources(pNDI_find, &mut total_sources as *mut u32); - // //} - // - // // We need at least one source - // if p_sources.is_null() { - // //println!("Error getting NDIlib_find_get_current_sources."); - // gst_element_error!(element, gst::CoreError::Negotiation, ["Error getting NDIlib_find_get_current_sources"]); - // return false; - // //::std::process::exit(1); - // } - // - // let mut no_source: isize = -1; - // for i in 0..total_sources as isize{ - // if CStr::from_ptr((*p_sources.offset(i)).p_ndi_name) - // .to_string_lossy() - // .into_owned() == settings.stream_name{ - // no_source = i; - // break; - // } - // } - // if no_source == -1 { - // gst_element_error!(element, gst::CoreError::Negotiation, ["Stream name not found"]); - // return false; - // } - // println!( - // "Total_sources {}: Name '{}' Address '{}'", - // total_sources, - // CStr::from_ptr((*p_sources.offset(no_source)).p_ndi_name) - // .to_string_lossy() - // .into_owned(), - // CStr::from_ptr((*p_sources.offset(no_source)).p_ip_address) - // .to_string_lossy() - // .into_owned() - // ); - // source = *p_sources.offset(no_source).clone(); - // } - // else{ - // source.p_ip_address = ip_ptr.as_ptr(); - // println!( - // "Address '{}'", - // CStr::from_ptr(source.p_ip_address) - // .to_string_lossy() - // .into_owned() - // ); - // } - // - // // We now have at least one source, so we create a receiver to look at it. - // // We tell it that we prefer YCbCr video since it is more efficient for us. If the source has an alpha channel - // // it will still be provided in BGRA - // let p_ndi_name = CString::new("Galicaster NDI Receiver").unwrap(); - // let NDI_recv_create_desc = NDIlib_recv_create_v3_t { - // source_to_connect_to: source, - // p_ndi_name: p_ndi_name.as_ptr(), - // ..Default::default() - // }; - // - // let pNDI_recv = NDIlib_recv_create_v3(&NDI_recv_create_desc); - // if pNDI_recv.is_null() { - // //println!("Cannot run NDI: NDIlib_recv_create_v3 error."); - // gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_recv_create_v3 error"]); - // return false; - // //::std::process::exit(1); - // } - // - // // Destroy the NDI finder. We needed to have access to the pointers to p_sources[0] - // NDIlib_find_destroy(pNDI_find); - // - // // We are now going to mark this source as being on program output for tally purposes (but not on preview) - // let tally_state: NDIlib_tally_t = Default::default(); - // NDIlib_recv_set_tally(pNDI_recv, &tally_state); - // - // // Enable Hardwqre Decompression support if this support has it. Please read the caveats in the documentation - // // regarding this. There are times in which it might reduce the performance although on small stream numbers - // // it almost always yields the same or better performance. - // let data = CString::new("").unwrap(); - // let enable_hw_accel = NDIlib_metadata_frame_t { - // length: data.to_bytes().len() as i32, - // timecode: 0, - // p_data: data.as_ptr(), - // }; - // - // NDIlib_recv_send_metadata(pNDI_recv, &enable_hw_accel); - // state.recv = Some(NdiInstance{recv: pNDI_recv}); - // let start = SystemTime::now(); - // let since_the_epoch = start.duration_since(UNIX_EPOCH) - // .expect("Time went backwards"); - // println!("{:?}", since_the_epoch); - // state.start_pts = Some(since_the_epoch.as_secs() * 1000000000 + - // since_the_epoch.subsec_nanos() as u64); - // //TODO Another way to save NDI_recv variable - // // *state = State{ - // // info: state.info.clone(), - // // recv: Some(NdiInstance{recv: pNDI_recv}), - // // }; - } - - // true + return connect_ndi(element, settings.ip.clone(), settings.stream_name.clone()); } // Called when shutting down the element so we can release all stream-related state fn stop(&self, element: &BaseSrc) -> bool { // Reset state - let state = self.state.lock().unwrap(); - // let recv = match state.recv{ - // None => { - // //println!("pNDI_recv no encontrado"); - // gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); - // return true; - // } - // Some(ref recv) => recv.clone(), - // }; - // let pNDI_recv = recv.recv; - // unsafe{ - // let recv = match ndi2.recv{ - // None => { - // //println!("pNDI_recv no encontrado"); - // gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); - // return true; - // } - // Some(ref recv) => recv.clone(), - // }; - // let pNDI_recv = recv.recv; - // NDIlib_recv_destroy(pNDI_recv); - // //NDIlib_destroy(); - // } + *self.state.lock().unwrap() = Default::default(); stop_ndi(); // Commented because when adding ndi destroy stopped in this line //*self.state.lock().unwrap() = Default::default(); @@ -531,21 +296,8 @@ impl NdiVideoSrc { fn fixate(&self, element: &BaseSrc, caps: gst::Caps) -> gst::Caps { //We need to set the correct caps resolution and framerate - let state = self.state.lock().unwrap(); - // let recv = match state.recv{ - // None => { - // //TODO Update gst_element_error with one more descriptive - // //println!("pNDI_recv no encontrado"); - // gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); - // //TODO if none not return anything - // return caps; - // } - // Some(ref recv) => recv.clone(), - // }; - // - // let pNDI_recv = recv.recv; unsafe{ - let recv = match ndi2.recv{ + let recv = match ndi_struct.recv{ None => { //TODO Update gst_element_error with one more descriptive //println!("pNDI_recv no encontrado"); @@ -558,20 +310,14 @@ impl NdiVideoSrc { let pNDI_recv = recv.recv; let mut pts2 = self.pts.lock().unwrap(); - let mut pts: u64 = 0; let video_frame: NDIlib_video_frame_v2_t = Default::default(); - // let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); - // let metadata_frame: NDIlib_metadata_frame_t = Default::default(); - //let video_frame: NDIlib_video_frame_v2_t = get_frame(self, element, pNDI_recv, &mut pts2.pts, &mut pts); - //get_frame(element, pNDI_recv, &video_frame, &audio_frame, &metadata_frame, &mut pts2.pts, &mut pts); let mut frame_type: NDIlib_frame_type_e = NDIlib_frame_type_e::NDIlib_frame_type_none; - while (frame_type != NDIlib_frame_type_e::NDIlib_frame_type_video){ + while frame_type != NDIlib_frame_type_e::NDIlib_frame_type_video{ frame_type = NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000); } - println!("{:?}", video_frame.timecode); pts2.pts = (video_frame.timecode as u64) * 100; - pts = 0; + let mut caps = gst::Caps::truncate(caps); { let caps = caps.make_mut(); @@ -612,20 +358,8 @@ impl NdiVideoSrc { } Some(ref info) => info.clone(), }; - //let mut pNDI_recva = ptr::null(); - // { - // let recv = match state.recv{ - // None => { - // //TODO Update gst_element_error with one more descriptive - // //println!("pNDI_recv no encontrado"); - // gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); - // return Err(gst::FlowReturn::NotNegotiated); - // } - // Some(ref recv) => recv.clone(), - // }; - // let pNDI_recv = recv.recv; unsafe{ - let recv = match ndi2.recv{ + let recv = match ndi_struct.recv{ None => { //TODO Update gst_element_error with one more descriptive //println!("pNDI_recv no encontrado"); @@ -635,150 +369,39 @@ impl NdiVideoSrc { Some(ref recv) => recv.clone(), }; let pNDI_recv = recv.recv; - // } - // let start_pts = match state.start_pts { - // None => { - // gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); - // return Err(gst::FlowReturn::NotNegotiated); - // } - // Some(ref start_pts) => start_pts.clone(), - // }; + let pts: u64; + let video_frame: NDIlib_video_frame_v2_t = Default::default(); - let start_pts = match ndi2.start_pts { - None => { - gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); - return Err(gst::FlowReturn::NotNegotiated); - } - Some(ref start_pts) => start_pts.clone(), - }; + NDIlib_recv_capture_v2( + pNDI_recv, + &video_frame, + ptr::null(), + ptr::null(), + 1000, + ); + pts = ((video_frame.timecode as u64) * 100) - pts2.pts; - unsafe{ - // // loop { - let mut pts: u64 = 0; - //let video_frame: NDIlib_video_frame_v2_t = get_frame(self, element, pNDI_recv, &mut pts2.pts, &mut pts); - let video_frame: NDIlib_video_frame_v2_t = Default::default(); - // let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); - // let metadata_frame: NDIlib_metadata_frame_t = Default::default(); - //get_frame(element, pNDI_recv, &video_frame, &audio_frame, &metadata_frame, &mut pts2.pts, &mut pts); + let buff_size = (video_frame.yres * video_frame.line_stride_in_bytes) as usize; + //println!("{:?}", buff_size); + let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); + { + let vec = Vec::from_raw_parts(video_frame.p_data as *mut u8, buff_size, buff_size); + let pts: gst::ClockTime = (pts).into(); + //TODO get duration + let duration: gst::ClockTime = (40000000).into(); + let buffer = buffer.get_mut().unwrap(); + buffer.set_pts(pts); + buffer.set_duration(duration); + buffer.set_offset(pts2.offset); + buffer.set_offset_end(pts2.offset + 1); + pts2.offset = pts2.offset +1; + buffer.copy_from_slice(0, &vec).unwrap(); - NDIlib_recv_capture_v2( - pNDI_recv, - &video_frame, - ptr::null(), - ptr::null(), - 1000, - ); - pts = (((video_frame.timecode as u64) * 100) - pts2.pts); - //video_frame = get_frame(self, element, pNDI_recv, pts2.pts); - - // //TODO Only create buffer when we got a video frame - // let mut frame = false; - // let mut pts: u64 = 0; - // while !frame{ - // let frame_type = NDIlib_recv_capture_v2( - // pNDI_recv, - // &video_frame, - // &audio_frame, - // &metadata_frame, - // 1000, - // ); - // - // match frame_type { - // NDIlib_frame_type_e::NDIlib_frame_type_video => { - // gst_debug!(self.cat, obj: element, "Received video frame: {:?}", video_frame); - // frame = true; - // //pts = ((video_frame.timestamp as u64) * 100) - state.start_pts.unwrap(); - // // println!("{:?}", pts/1000000); - // pts = (video_frame.timestamp as u64) * 100; - // if pts2.pts == 0{ - // pts2.pts = (video_frame.timestamp as u64) * 100; - // pts = 0; - // //let mut caps = _info.to_caps(); - // // let s = caps.get_mut_structure(0).unwrap(); - // // s.fixate_field_nearest_int("width", 1500); - // // s.fixate_field_nearest_int("framerate", 25/1); - // // self.set_caps(&self, s); - // // let mut caps = Some(gst::Caps::new_simple( - // // "video/x-raw", - // // &[("format", - // // &gst_video::VideoFormat::Uyvy.to_string() - // // )], - // // )); - // // caps.as_mut().map(|c| { - // // c.get_mut() - // // .unwrap() - // // .set_simple(&[("framerate", &(25/1 as i32)), ("width", &(1600 as i32)), ("height", &(1200 as i32))]) - // // }); - // //caps.unwrap().set_simple(&[("framerate", &(20/1 as i32))]); - // //let mut caps2 = _info.to_caps().unwrap(); - // // println!("{:?}", caps); - // //element.parent_set_caps(&caps.unwrap()); - // //element.parent_fixate(caps.unwrap()); - // //gst_video::VideoInfo::from_caps(&caps.unwrap()); - // //self.set_caps(element, &caps.unwrap()); - // } - // else{ - // // println!("{:?}", video_frame.timecode * 100); - // // println!("{:?}", pts2.pts); - // pts = (((video_frame.timestamp as u64) * 100) - pts2.pts); - // //println!("{:?}", pts/1000000); - // } - // - // } - // NDIlib_frame_type_e::NDIlib_frame_type_audio => { - // gst_debug!(self.cat, obj: element, "Received audio frame: {:?}", video_frame); - // } - // NDIlib_frame_type_e::NDIlib_frame_type_metadata => { - // // println!( - // // "Tengo metadata {} '{}'", - // // metadata_frame.length, - // // CStr::from_ptr(metadata_frame.p_data) - // // .to_string_lossy() - // // .into_owned(), - // // ); - // //TODO Change gst_warning to gst_debug - // gst_debug!(self.cat, obj: element, "Received metadata frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned(),); - // } - // NDIlib_frame_type_e::NDIlib_frame_type_error => { - // // println!( - // // "Tengo error {} '{}'", - // // metadata_frame.length, - // // CStr::from_ptr(metadata_frame.p_data) - // // .to_string_lossy() - // // .into_owned(), - // // ); - // //TODO Change gst_warning to gst_debug - // gst_debug!(self.cat, obj: element, "Received error frame: {:?}", CStr::from_ptr(metadata_frame.p_data).to_string_lossy().into_owned()); - // // break; - // } - // _ => println!("Tengo {:?}", frame_type), - // } - // } - // // } - - - let buff_size = (video_frame.yres * video_frame.line_stride_in_bytes) as usize; - //println!("{:?}", buff_size); - let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); - { - let vec = Vec::from_raw_parts(video_frame.p_data as *mut u8, buff_size, buff_size); - let pts: gst::ClockTime = (pts).into(); - //TODO get duration - let duration: gst::ClockTime = (40000000).into(); - let buffer = buffer.get_mut().unwrap(); - buffer.set_pts(pts); - buffer.set_duration(duration); - buffer.set_offset(pts2.offset); - buffer.set_offset_end(pts2.offset + 1); - pts2.offset = pts2.offset +1; - buffer.copy_from_slice(0, &vec).unwrap(); - - } - - gst_debug!(self.cat, obj: element, "Produced buffer {:?}", buffer); - Ok(buffer) } + + gst_debug!(self.cat, obj: element, "Produced buffer {:?}", buffer); + Ok(buffer) } } From 570abeff49997fdfdcf4508632c7eb42924c05f6 Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Wed, 27 Jun 2018 11:56:11 +0200 Subject: [PATCH 047/199] Clean code --- gst-plugin-ndi/src/lib.rs | 229 +++++++++++++++--------------- gst-plugin-ndi/src/ndiaudiosrc.rs | 78 +++------- gst-plugin-ndi/src/ndilib.rs | 1 + gst-plugin-ndi/src/ndivideosrc.rs | 86 +++-------- 4 files changed, 149 insertions(+), 245 deletions(-) diff --git a/gst-plugin-ndi/src/lib.rs b/gst-plugin-ndi/src/lib.rs index 6420a9df..adc7c5e6 100644 --- a/gst-plugin-ndi/src/lib.rs +++ b/gst-plugin-ndi/src/lib.rs @@ -26,7 +26,7 @@ pub mod ndilib; use std::ptr; use std::{thread, time}; -use std::time::{SystemTime, UNIX_EPOCH}; +//use std::time::{SystemTime, UNIX_EPOCH}; use std::ffi::{CStr, CString}; use ndilib::*; use gst_plugin::base_src::*; @@ -41,27 +41,27 @@ fn plugin_init(plugin: &gst::Plugin) -> bool { } -struct ndi{ +struct Ndi{ recv: Option, - start_pts: Option, + //start_pts: Option, } -static mut ndi_struct: ndi = ndi{ +static mut ndi_struct: Ndi = Ndi{ recv: None, - start_pts: None, + //start_pts: None, }; -fn connect_ndi(element: &BaseSrc, ip: String, stream_name: String) -> bool{ - unsafe { - match ndi_struct.recv { - None => { - //gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); - //return true; - } - _ => return true, - }; +fn connect_ndi(cat: gst::DebugCategory , element: &BaseSrc, ip: String, stream_name: String) -> bool{ + unsafe { + gst_debug!(cat, obj: element, "Starting NDI connection..."); + match ndi_struct.recv { + None => { + //gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); + //return true; + } + _ => return true, + }; if !NDIlib_initialize() { - //println!("Cannot run NDI: NDIlib_initialize error."); gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_initialize error"]); return false; } @@ -75,26 +75,21 @@ fn connect_ndi(element: &BaseSrc, ip: String, stream_name: String) -> bool{ let ip_ptr = CString::new(ip.clone()).unwrap(); if ip_ptr == CString::new("").unwrap(){ if pNDI_find.is_null() { - //println!("Cannot run NDI: NDIlib_find_create_v2 error."); gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_find_create_v2 error"]); return false; } let mut total_sources: u32 = 0; let p_sources; - //TODO Delete while. If not, will loop until a source it's available - //while total_sources == 0 { + // TODO Sleep 1s to wait for all sources thread::sleep(time::Duration::from_millis(2000)); p_sources = NDIlib_find_get_current_sources(pNDI_find, &mut total_sources as *mut u32); - //} // We need at least one source if p_sources.is_null() { - //println!("Error getting NDIlib_find_get_current_sources."); gst_element_error!(element, gst::CoreError::Negotiation, ["Error getting NDIlib_find_get_current_sources"]); return false; - //::std::process::exit(1); } let mut no_source: isize = -1; @@ -110,107 +105,107 @@ fn connect_ndi(element: &BaseSrc, ip: String, stream_name: String) -> bool{ gst_element_error!(element, gst::CoreError::Negotiation, ["Stream name not found"]); return false; } - println!( - "Total_sources {}: Name '{}' Address '{}'", - total_sources, - CStr::from_ptr((*p_sources.offset(no_source)).p_ndi_name) - .to_string_lossy() - .into_owned(), - CStr::from_ptr((*p_sources.offset(no_source)).p_ip_address) - .to_string_lossy() - .into_owned() - ); + + gst_debug!(cat, obj: element, "Total sources in network {}: Connecting to NDI source with name '{}' and address '{}'", total_sources, + CStr::from_ptr((*p_sources.offset(no_source)).p_ndi_name) + .to_string_lossy() + .into_owned(), + CStr::from_ptr((*p_sources.offset(no_source)).p_ip_address) + .to_string_lossy() + .into_owned()); + source = *p_sources.offset(no_source).clone(); } else{ source.p_ip_address = ip_ptr.as_ptr(); - println!( - "Address '{}'", - CStr::from_ptr(source.p_ip_address) - .to_string_lossy() - .into_owned() - ); - } - - // We now have at least one source, so we create a receiver to look at it. - // We tell it that we prefer YCbCr video since it is more efficient for us. If the source has an alpha channel - // it will still be provided in BGRA - let p_ndi_name = CString::new("Galicaster NDI Receiver").unwrap(); - let NDI_recv_create_desc = NDIlib_recv_create_v3_t { - source_to_connect_to: source, - p_ndi_name: p_ndi_name.as_ptr(), - ..Default::default() - }; - - let pNDI_recv = NDIlib_recv_create_v3(&NDI_recv_create_desc); - if pNDI_recv.is_null() { - //println!("Cannot run NDI: NDIlib_recv_create_v3 error."); - gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_recv_create_v3 error"]); - return false; - //::std::process::exit(1); - } - - // Destroy the NDI finder. We needed to have access to the pointers to p_sources[0] - NDIlib_find_destroy(pNDI_find); - - // We are now going to mark this source as being on program output for tally purposes (but not on preview) - let tally_state: NDIlib_tally_t = Default::default(); - NDIlib_recv_set_tally(pNDI_recv, &tally_state); - - // Enable Hardwqre Decompression support if this support has it. Please read the caveats in the documentation - // regarding this. There are times in which it might reduce the performance although on small stream numbers - // it almost always yields the same or better performance. - let data = CString::new("").unwrap(); - let enable_hw_accel = NDIlib_metadata_frame_t { - length: data.to_bytes().len() as i32, - timecode: 0, - p_data: data.as_ptr(), - }; - - NDIlib_recv_send_metadata(pNDI_recv, &enable_hw_accel); - ndi_struct.recv = Some(NdiInstance{recv: pNDI_recv}); - let start = SystemTime::now(); - let since_the_epoch = start.duration_since(UNIX_EPOCH) - .expect("Time went backwards"); - println!("{:?}", since_the_epoch); - ndi_struct.start_pts = Some(since_the_epoch.as_secs() * 1000000000 + - since_the_epoch.subsec_nanos() as u64); - return true; + gst_debug!(cat, obj: element, "Connecting to NDI source with address '{}'", CStr::from_ptr(source.p_ip_address) + .to_string_lossy() + .into_owned() + ); } - } - fn stop_ndi() -> bool{ - unsafe{ - let recv = match ndi_struct.recv{ - None => { - //TODO Update gst_element_error with one more descriptive - //println!("pNDI_recv no encontrado"); - //gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); - return true; - } - Some(ref recv) => recv.clone(), - }; - let pNDI_recv = recv.recv; - NDIlib_recv_destroy(pNDI_recv); - ndi_struct.recv = None; - //NDIlib_destroy(); - return true; + // We now have at least one source, so we create a receiver to look at it. + // We tell it that we prefer YCbCr video since it is more efficient for us. If the source has an alpha channel + // it will still be provided in BGRA + let p_ndi_name = CString::new("Galicaster NDI Receiver").unwrap(); + let NDI_recv_create_desc = NDIlib_recv_create_v3_t { + source_to_connect_to: source, + p_ndi_name: p_ndi_name.as_ptr(), + ..Default::default() + }; + + let pNDI_recv = NDIlib_recv_create_v3(&NDI_recv_create_desc); + if pNDI_recv.is_null() { + //println!("Cannot run NDI: NDIlib_recv_create_v3 error."); + gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_recv_create_v3 error"]); + return false; } - } - // Static plugin metdata that is directly stored in the plugin shared object and read by GStreamer - // upon loading. - // Plugin name, plugin description, plugin entry point function, version number of this plugin, - // license of the plugin, source package name, binary package name, origin where it comes from - // and the date/time of release. - plugin_define!( - b"ndi\0", - b"NewTek NDI Plugin\0", - plugin_init, - b"1.0\0", - b"MIT/X11\0", - b"ndi\0", - b"ndi\0", - b"https://gitlab.teltek.es/rubenrua/ndi-rs.git\0", - b"2018-04-09\0" - ); + // Destroy the NDI finder. We needed to have access to the pointers to p_sources[0] + NDIlib_find_destroy(pNDI_find); + + // We are now going to mark this source as being on program output for tally purposes (but not on preview) + let tally_state: NDIlib_tally_t = Default::default(); + NDIlib_recv_set_tally(pNDI_recv, &tally_state); + + // Enable Hardwqre Decompression support if this support has it. Please read the caveats in the documentation + // regarding this. There are times in which it might reduce the performance although on small stream numbers + // it almost always yields the same or better performance. + let data = CString::new("").unwrap(); + let enable_hw_accel = NDIlib_metadata_frame_t { + length: data.to_bytes().len() as i32, + timecode: 0, + p_data: data.as_ptr(), + }; + + NDIlib_recv_send_metadata(pNDI_recv, &enable_hw_accel); + ndi_struct.recv = Some(NdiInstance{recv: pNDI_recv}); + + // let start = SystemTime::now(); + // let since_the_epoch = start.duration_since(UNIX_EPOCH) + // .expect("Time went backwards"); + // println!("{:?}", since_the_epoch); + // ndi_struct.start_pts = Some(since_the_epoch.as_secs() * 1000000000 + + // since_the_epoch.subsec_nanos() as u64); + gst_debug!(cat, obj: element, "Started NDI connection"); + return true; + } +} + +fn stop_ndi(cat: gst::DebugCategory , element: &BaseSrc) -> bool{ + gst_debug!(cat, obj: element, "Closing NDI connection..."); + unsafe{ + let recv = match ndi_struct.recv{ + None => { + //TODO Update gst_element_error with one more descriptive + //println!("pNDI_recv no encontrado"); + //gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); + return true; + } + Some(ref recv) => recv.clone(), + }; + let pNDI_recv = recv.recv; + NDIlib_recv_destroy(pNDI_recv); + ndi_struct.recv = None; + NDIlib_destroy(); + gst_debug!(cat, obj: element, "Closed NDI connection"); + return true; + } +} + +// Static plugin metdata that is directly stored in the plugin shared object and read by GStreamer +// upon loading. +// Plugin name, plugin description, plugin entry point function, version number of this plugin, +// license of the plugin, source package name, binary package name, origin where it comes from +// and the date/time of release. +plugin_define!( + b"ndi\0", + b"NewTek NDI Plugin\0", + plugin_init, + b"1.0\0", + b"MIT/X11\0", + b"ndi\0", + b"ndi\0", + b"https://gitlab.teltek.es/rubenrua/ndi-rs.git\0", + b"2018-04-09\0" +); diff --git a/gst-plugin-ndi/src/ndiaudiosrc.rs b/gst-plugin-ndi/src/ndiaudiosrc.rs index ae419ae4..eedef452 100644 --- a/gst-plugin-ndi/src/ndiaudiosrc.rs +++ b/gst-plugin-ndi/src/ndiaudiosrc.rs @@ -69,11 +69,7 @@ impl Default for State { } } -struct ClockWait { - clock_id: Option, - flushing: bool, -} -struct Pts{ +struct TimestampData{ pts: u64, offset: u64, } @@ -83,8 +79,7 @@ struct NdiAudioSrc { cat: gst::DebugCategory, settings: Mutex, state: Mutex, - clock_wait: Mutex, - pts: Mutex, + timestamp_data: Mutex, } impl NdiAudioSrc { @@ -103,11 +98,7 @@ impl NdiAudioSrc { ), settings: Mutex::new(Default::default()), state: Mutex::new(Default::default()), - clock_wait: Mutex::new(ClockWait { - clock_id: None, - flushing: true, - }), - pts: Mutex::new(Pts{ + timestamp_data: Mutex::new(TimestampData{ pts: 0, offset: 0, }), @@ -180,7 +171,7 @@ impl NdiAudioSrc { Property::String("stream-name", ..) => { let mut settings = self.settings.lock().unwrap(); let stream_name = value.get().unwrap(); - gst_warning!( + gst_debug!( self.cat, obj: &element, "Changing stream-name from {} to {}", @@ -196,7 +187,7 @@ impl NdiAudioSrc { Property::String("ip", ..) => { let mut settings = self.settings.lock().unwrap(); let ip = value.get().unwrap(); - gst_warning!( + gst_debug!( self.cat, obj: &element, "Changing ip from {} to {}", @@ -266,11 +257,9 @@ impl NdiAudioSrc { fn start(&self, element: &BaseSrc) -> bool { // Reset state *self.state.lock().unwrap() = Default::default(); - self.unlock_stop(element); - gst_warning!(self.cat, obj: element, "Starting"); let settings = self.settings.lock().unwrap(); - return connect_ndi(element, settings.ip.clone(), settings.stream_name.clone()); + return connect_ndi(self.cat, element, settings.ip.clone(), settings.stream_name.clone()); } @@ -278,12 +267,9 @@ impl NdiAudioSrc { fn stop(&self, element: &BaseSrc) -> bool { // Reset state *self.state.lock().unwrap() = Default::default(); - stop_ndi(); + stop_ndi(self.cat, element); // Commented because when adding ndi destroy stopped in this line //*self.state.lock().unwrap() = Default::default(); - self.unlock(element); - gst_info!(self.cat, obj: element, "Stopped"); - true } @@ -301,14 +287,14 @@ impl NdiAudioSrc { }; let pNDI_recv = recv.recv; - let mut pts2 = self.pts.lock().unwrap(); + let mut timestamp_data = self.timestamp_data.lock().unwrap(); let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); let mut frame_type: NDIlib_frame_type_e = NDIlib_frame_type_e::NDIlib_frame_type_none; while frame_type != NDIlib_frame_type_e::NDIlib_frame_type_audio{ frame_type = NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000); } - pts2.pts = (audio_frame.timecode as u64) * 100; + timestamp_data.pts = (audio_frame.timecode as u64) * 100; let mut caps = gst::Caps::truncate(caps); { @@ -337,7 +323,7 @@ impl NdiAudioSrc { // have to block until this function returns when getting/setting property values let _settings = &*self.settings.lock().unwrap(); - let mut pts2 = self.pts.lock().unwrap(); + let mut timestamp_data = self.timestamp_data.lock().unwrap(); // Get a locked reference to our state, i.e. the input and output AudioInfo let state = self.state.lock().unwrap(); let _info = match state.info { @@ -359,16 +345,11 @@ impl NdiAudioSrc { }; let pNDI_recv = recv.recv; let pts: u64; - let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); - NDIlib_recv_capture_v2( - pNDI_recv, - ptr::null(), - &audio_frame, - ptr::null(), - 1000, - ); - pts = ((audio_frame.timecode as u64) * 100) - pts2.pts; + let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); + NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000,); + pts = ((audio_frame.timecode as u64) * 100) - timestamp_data.pts; + let buff_size = ((audio_frame.channel_stride_in_bytes)) as usize; let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); { @@ -379,40 +360,15 @@ impl NdiAudioSrc { let buffer = buffer.get_mut().unwrap(); buffer.set_pts(pts); buffer.set_duration(duration); - buffer.set_offset(pts2.offset); - buffer.set_offset_end(pts2.offset + 1); - pts2.offset = pts2.offset +1; + buffer.set_offset(timestamp_data.offset); + buffer.set_offset_end(timestamp_data.offset + 1); + timestamp_data.offset = timestamp_data.offset +1; buffer.copy_from_slice(0, &vec).unwrap(); } gst_debug!(self.cat, obj: element, "Produced buffer {:?}", buffer); Ok(buffer) } - - } - - - fn unlock(&self, element: &BaseSrc) -> bool { - // This should unblock the create() function ASAP, so we - // just unschedule the clock it here, if any. - gst_debug!(self.cat, obj: element, "Unlocking"); - let mut clock_wait = self.clock_wait.lock().unwrap(); - if let Some(clock_id) = clock_wait.clock_id.take() { - clock_id.unschedule(); - } - clock_wait.flushing = true; - - true - } - - fn unlock_stop(&self, element: &BaseSrc) -> bool { - // This signals that unlocking is done, so we can reset - // all values again. - gst_debug!(self.cat, obj: element, "Unlock stop"); - let mut clock_wait = self.clock_wait.lock().unwrap(); - clock_wait.flushing = false; - - true } } diff --git a/gst-plugin-ndi/src/ndilib.rs b/gst-plugin-ndi/src/ndilib.rs index 09e581d3..2b345ac0 100644 --- a/gst-plugin-ndi/src/ndilib.rs +++ b/gst-plugin-ndi/src/ndilib.rs @@ -17,6 +17,7 @@ extern "C" { ) -> NDIlib_recv_instance_t; pub fn NDIlib_find_destroy(p_instance: NDIlib_recv_instance_t); pub fn NDIlib_recv_destroy(p_instance: NDIlib_recv_instance_t); + pub fn NDIlib_destroy(); pub fn NDIlib_recv_set_tally( p_instance: NDIlib_recv_instance_t, p_tally: *const NDIlib_tally_t, diff --git a/gst-plugin-ndi/src/ndivideosrc.rs b/gst-plugin-ndi/src/ndivideosrc.rs index c7d66002..54c2b5e5 100644 --- a/gst-plugin-ndi/src/ndivideosrc.rs +++ b/gst-plugin-ndi/src/ndivideosrc.rs @@ -70,11 +70,7 @@ impl Default for State { } } -struct ClockWait { - clock_id: Option, - flushing: bool, -} -struct Pts{ +struct TimestampData{ pts: u64, offset: u64, } @@ -84,8 +80,7 @@ struct NdiVideoSrc { cat: gst::DebugCategory, settings: Mutex, state: Mutex, - clock_wait: Mutex, - pts: Mutex, + timestamp_data: Mutex, } impl NdiVideoSrc { @@ -104,11 +99,7 @@ impl NdiVideoSrc { ), settings: Mutex::new(Default::default()), state: Mutex::new(Default::default()), - clock_wait: Mutex::new(ClockWait { - clock_id: None, - flushing: true, - }), - pts: Mutex::new(Pts{ + timestamp_data: Mutex::new(TimestampData{ pts: 0, offset: 0, }), @@ -188,7 +179,7 @@ impl NdiVideoSrc { Property::String("stream-name", ..) => { let mut settings = self.settings.lock().unwrap(); let stream_name = value.get().unwrap(); - gst_warning!( + gst_debug!( self.cat, obj: &element, "Changing stream-name from {} to {}", @@ -204,7 +195,7 @@ impl NdiVideoSrc { Property::String("ip", ..) => { let mut settings = self.settings.lock().unwrap(); let ip = value.get().unwrap(); - gst_warning!( + gst_debug!( self.cat, obj: &element, "Changing ip from {} to {}", @@ -229,12 +220,10 @@ impl NdiVideoSrc { match *prop { Property::String("stream-name", ..) => { let settings = self.settings.lock().unwrap(); - //TODO to_value supongo que solo funciona con numeros Ok(settings.stream_name.to_value()) }, Property::String("ip", ..) => { let settings = self.settings.lock().unwrap(); - //TODO to_value supongo que solo funciona con numeros Ok(settings.ip.to_value()) } _ => unimplemented!(), @@ -274,23 +263,18 @@ impl NdiVideoSrc { fn start(&self, element: &BaseSrc) -> bool { // Reset state *self.state.lock().unwrap() = Default::default(); - self.unlock_stop(element); - gst_warning!(self.cat, obj: element, "Starting"); let settings = self.settings.lock().unwrap(); - return connect_ndi(element, settings.ip.clone(), settings.stream_name.clone()); + return connect_ndi(self.cat, element, settings.ip.clone(), settings.stream_name.clone()); } // Called when shutting down the element so we can release all stream-related state fn stop(&self, element: &BaseSrc) -> bool { // Reset state *self.state.lock().unwrap() = Default::default(); - stop_ndi(); + stop_ndi(self.cat, element); // Commented because when adding ndi destroy stopped in this line //*self.state.lock().unwrap() = Default::default(); - self.unlock(element); - gst_info!(self.cat, obj: element, "Stopped"); - true } @@ -309,14 +293,16 @@ impl NdiVideoSrc { }; let pNDI_recv = recv.recv; - let mut pts2 = self.pts.lock().unwrap(); + let mut timestamp_data = self.timestamp_data.lock().unwrap(); let video_frame: NDIlib_video_frame_v2_t = Default::default(); + let mut frame_type: NDIlib_frame_type_e = NDIlib_frame_type_e::NDIlib_frame_type_none; while frame_type != NDIlib_frame_type_e::NDIlib_frame_type_video{ frame_type = NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000); } - pts2.pts = (video_frame.timecode as u64) * 100; + //FIXME It's possible than timecode not exist + timestamp_data.pts = (video_frame.timecode as u64) * 100; let mut caps = gst::Caps::truncate(caps); { @@ -325,9 +311,6 @@ impl NdiVideoSrc { s.fixate_field_nearest_int("width", video_frame.xres); s.fixate_field_nearest_int("height", video_frame.yres); s.fixate_field_nearest_fraction("framerate", Fraction::new(video_frame.frame_rate_N, video_frame.frame_rate_D)); - //s.fixate_field_str("format", &gst_video::VideoFormat::Rgb.to_string()); - //caps.set_simple(&[("width", &(1600 as i32))]); - //s.set_value("width", &(1600 as i32)); } // Let BaseSrc fixate anything else for us. We could've alternatively have @@ -336,7 +319,7 @@ impl NdiVideoSrc { } } - //Creates the audio buffers + //Creates the video buffers fn create( &self, element: &BaseSrc, @@ -348,7 +331,7 @@ impl NdiVideoSrc { // have to block until this function returns when getting/setting property values let _settings = &*self.settings.lock().unwrap(); - let mut pts2 = self.pts.lock().unwrap(); + let mut timestamp_data = self.timestamp_data.lock().unwrap(); // Get a locked reference to our state, i.e. the input and output AudioInfo let state = self.state.lock().unwrap(); let _info = match state.info { @@ -372,15 +355,9 @@ impl NdiVideoSrc { let pts: u64; let video_frame: NDIlib_video_frame_v2_t = Default::default(); - - NDIlib_recv_capture_v2( - pNDI_recv, - &video_frame, - ptr::null(), - ptr::null(), - 1000, - ); - pts = ((video_frame.timecode as u64) * 100) - pts2.pts; + NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000,); + //TODO It's possible than timecode not exist + pts = ((video_frame.timecode as u64) * 100) - timestamp_data.pts; let buff_size = (video_frame.yres * video_frame.line_stride_in_bytes) as usize; //println!("{:?}", buff_size); @@ -393,9 +370,9 @@ impl NdiVideoSrc { let buffer = buffer.get_mut().unwrap(); buffer.set_pts(pts); buffer.set_duration(duration); - buffer.set_offset(pts2.offset); - buffer.set_offset_end(pts2.offset + 1); - pts2.offset = pts2.offset +1; + buffer.set_offset(timestamp_data.offset); + buffer.set_offset_end(timestamp_data.offset + 1); + timestamp_data.offset = timestamp_data.offset +1; buffer.copy_from_slice(0, &vec).unwrap(); } @@ -404,31 +381,6 @@ impl NdiVideoSrc { Ok(buffer) } } - - - - fn unlock(&self, element: &BaseSrc) -> bool { - // This should unblock the create() function ASAP, so we - // just unschedule the clock it here, if any. - gst_debug!(self.cat, obj: element, "Unlocking"); - let mut clock_wait = self.clock_wait.lock().unwrap(); - if let Some(clock_id) = clock_wait.clock_id.take() { - clock_id.unschedule(); - } - clock_wait.flushing = true; - - true - } - - fn unlock_stop(&self, element: &BaseSrc) -> bool { - // This signals that unlocking is done, so we can reset - // all values again. - gst_debug!(self.cat, obj: element, "Unlock stop"); - let mut clock_wait = self.clock_wait.lock().unwrap(); - clock_wait.flushing = false; - - true - } } // This zero-sized struct is containing the static metadata of our element. It is only necessary to From 8399da4f7c943b6ee5ca76c1f5bfc3dbc57b0b0a Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Wed, 27 Jun 2018 13:53:09 +0200 Subject: [PATCH 048/199] Refactor timestamps and timecode logic --- gst-plugin-ndi/src/ndiaudiosrc.rs | 8 ++++---- gst-plugin-ndi/src/ndivideosrc.rs | 11 +++++------ 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/gst-plugin-ndi/src/ndiaudiosrc.rs b/gst-plugin-ndi/src/ndiaudiosrc.rs index eedef452..6bb8d91e 100644 --- a/gst-plugin-ndi/src/ndiaudiosrc.rs +++ b/gst-plugin-ndi/src/ndiaudiosrc.rs @@ -294,7 +294,7 @@ impl NdiAudioSrc { while frame_type != NDIlib_frame_type_e::NDIlib_frame_type_audio{ frame_type = NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000); } - timestamp_data.pts = (audio_frame.timecode as u64) * 100; + timestamp_data.pts = audio_frame.timecode as u64; let mut caps = gst::Caps::truncate(caps); { @@ -348,21 +348,21 @@ impl NdiAudioSrc { let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000,); - pts = ((audio_frame.timecode as u64) * 100) - timestamp_data.pts; + pts = (audio_frame.timecode as u64) - timestamp_data.pts; let buff_size = ((audio_frame.channel_stride_in_bytes)) as usize; let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); { let vec = Vec::from_raw_parts(audio_frame.p_data as *mut u8, buff_size, buff_size); //TODO Set pts, duration and other info about the buffer - let pts: gst::ClockTime = (pts).into(); + let pts: gst::ClockTime = (pts * 100).into(); let duration: gst::ClockTime = (20154200).into(); let buffer = buffer.get_mut().unwrap(); buffer.set_pts(pts); buffer.set_duration(duration); buffer.set_offset(timestamp_data.offset); buffer.set_offset_end(timestamp_data.offset + 1); - timestamp_data.offset = timestamp_data.offset +1; + timestamp_data.offset = timestamp_data.offset + 1; buffer.copy_from_slice(0, &vec).unwrap(); } diff --git a/gst-plugin-ndi/src/ndivideosrc.rs b/gst-plugin-ndi/src/ndivideosrc.rs index 54c2b5e5..239b4c33 100644 --- a/gst-plugin-ndi/src/ndivideosrc.rs +++ b/gst-plugin-ndi/src/ndivideosrc.rs @@ -301,8 +301,8 @@ impl NdiVideoSrc { while frame_type != NDIlib_frame_type_e::NDIlib_frame_type_video{ frame_type = NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000); } - //FIXME It's possible than timecode not exist - timestamp_data.pts = (video_frame.timecode as u64) * 100; + + timestamp_data.pts = video_frame.timecode as u64; let mut caps = gst::Caps::truncate(caps); { @@ -356,15 +356,14 @@ impl NdiVideoSrc { let pts: u64; let video_frame: NDIlib_video_frame_v2_t = Default::default(); NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000,); - //TODO It's possible than timecode not exist - pts = ((video_frame.timecode as u64) * 100) - timestamp_data.pts; + pts = (video_frame.timecode as u64) - timestamp_data.pts; let buff_size = (video_frame.yres * video_frame.line_stride_in_bytes) as usize; //println!("{:?}", buff_size); let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); { let vec = Vec::from_raw_parts(video_frame.p_data as *mut u8, buff_size, buff_size); - let pts: gst::ClockTime = (pts).into(); + let pts: gst::ClockTime = (pts * 100).into(); //TODO get duration let duration: gst::ClockTime = (40000000).into(); let buffer = buffer.get_mut().unwrap(); @@ -372,7 +371,7 @@ impl NdiVideoSrc { buffer.set_duration(duration); buffer.set_offset(timestamp_data.offset); buffer.set_offset_end(timestamp_data.offset + 1); - timestamp_data.offset = timestamp_data.offset +1; + timestamp_data.offset = timestamp_data.offset + 1; buffer.copy_from_slice(0, &vec).unwrap(); } From 6ba47cc085a74b31d56cd733b34e2314674fc8a7 Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Mon, 2 Jul 2018 14:06:44 +0200 Subject: [PATCH 049/199] Use common start timestamp --- gst-plugin-ndi/src/lib.rs | 4 ++-- gst-plugin-ndi/src/ndiaudiosrc.rs | 6 ++++-- gst-plugin-ndi/src/ndivideosrc.rs | 11 ++++++++--- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/gst-plugin-ndi/src/lib.rs b/gst-plugin-ndi/src/lib.rs index adc7c5e6..f9f68f0c 100644 --- a/gst-plugin-ndi/src/lib.rs +++ b/gst-plugin-ndi/src/lib.rs @@ -43,12 +43,12 @@ fn plugin_init(plugin: &gst::Plugin) -> bool { struct Ndi{ recv: Option, - //start_pts: Option, + start_pts: u64, } static mut ndi_struct: Ndi = Ndi{ recv: None, - //start_pts: None, + start_pts: 0, }; fn connect_ndi(cat: gst::DebugCategory , element: &BaseSrc, ip: String, stream_name: String) -> bool{ diff --git a/gst-plugin-ndi/src/ndiaudiosrc.rs b/gst-plugin-ndi/src/ndiaudiosrc.rs index 6bb8d91e..03387f4d 100644 --- a/gst-plugin-ndi/src/ndiaudiosrc.rs +++ b/gst-plugin-ndi/src/ndiaudiosrc.rs @@ -294,7 +294,8 @@ impl NdiAudioSrc { while frame_type != NDIlib_frame_type_e::NDIlib_frame_type_audio{ frame_type = NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000); } - timestamp_data.pts = audio_frame.timecode as u64; + ndi_struct.start_pts = audio_frame.timecode as u64; + //timestamp_data.pts = audio_frame.timecode as u64; let mut caps = gst::Caps::truncate(caps); { @@ -348,7 +349,8 @@ impl NdiAudioSrc { let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000,); - pts = (audio_frame.timecode as u64) - timestamp_data.pts; + //pts = (audio_frame.timecode as u64) - timestamp_data.pts; + pts = (audio_frame.timecode as u64) - ndi_struct.start_pts; let buff_size = ((audio_frame.channel_stride_in_bytes)) as usize; let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); diff --git a/gst-plugin-ndi/src/ndivideosrc.rs b/gst-plugin-ndi/src/ndivideosrc.rs index 239b4c33..ab58f188 100644 --- a/gst-plugin-ndi/src/ndivideosrc.rs +++ b/gst-plugin-ndi/src/ndivideosrc.rs @@ -302,7 +302,8 @@ impl NdiVideoSrc { frame_type = NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000); } - timestamp_data.pts = video_frame.timecode as u64; + //timestamp_data.pts = video_frame.timecode as u64; + ndi_struct.start_pts = video_frame.timecode as u64; let mut caps = gst::Caps::truncate(caps); { @@ -356,16 +357,20 @@ impl NdiVideoSrc { let pts: u64; let video_frame: NDIlib_video_frame_v2_t = Default::default(); NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000,); - pts = (video_frame.timecode as u64) - timestamp_data.pts; + //pts = (video_frame.timecode as u64) - timestamp_data.pts; + pts = (video_frame.timecode as u64) - ndi_struct.start_pts; let buff_size = (video_frame.yres * video_frame.line_stride_in_bytes) as usize; //println!("{:?}", buff_size); let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); { let vec = Vec::from_raw_parts(video_frame.p_data as *mut u8, buff_size, buff_size); let pts: gst::ClockTime = (pts * 100).into(); + + // println!("{:?}", video_frame.line_stride_in_bytes); + //println!("{:?}", video_frame); //TODO get duration - let duration: gst::ClockTime = (40000000).into(); + let duration: gst::ClockTime = (33333333).into(); let buffer = buffer.get_mut().unwrap(); buffer.set_pts(pts); buffer.set_duration(duration); From 7cbf8e57a8e6c6f31ab007111a87d879b97fe617 Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Mon, 2 Jul 2018 14:07:51 +0200 Subject: [PATCH 050/199] Added query function to return latency --- gst-plugin-ndi/src/ndiaudiosrc.rs | 42 ++++++++++++++++++++++++++++++ gst-plugin-ndi/src/ndivideosrc.rs | 43 +++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/gst-plugin-ndi/src/ndiaudiosrc.rs b/gst-plugin-ndi/src/ndiaudiosrc.rs index 03387f4d..fecb87b9 100644 --- a/gst-plugin-ndi/src/ndiaudiosrc.rs +++ b/gst-plugin-ndi/src/ndiaudiosrc.rs @@ -273,6 +273,48 @@ impl NdiAudioSrc { true } + fn query(&self, element: &BaseSrc, query: &mut gst::QueryRef) -> bool { + use gst::QueryView; + + match query.view_mut() { + // We only work in Push mode. In Pull mode, create() could be called with + // arbitrary offsets and we would have to produce for that specific offset + QueryView::Scheduling(ref mut q) => { + q.set(gst::SchedulingFlags::SEQUENTIAL, 1, -1, 0); + q.add_scheduling_modes(&[gst::PadMode::Push]); + return true; + } + // In Live mode we will have a latency equal to the number of samples in each buffer. + // We can't output samples before they were produced, and the last sample of a buffer + // is produced that much after the beginning, leading to this latency calculation + QueryView::Latency(ref mut q) => { + //let settings = *self.settings.lock().unwrap(); + let state = self.state.lock().unwrap(); + + if let Some(ref info) = state.info { + // let latency = gst::SECOND + // .mul_div_floor(settings.samples_per_buffer as u64, info.rate() as u64) + // .unwrap(); + let latency = gst::SECOND.mul_div_floor(3 as u64, 2 as u64).unwrap(); + // let latency = gst::SECOND + // .mul_div_floor(1 as u64, 30 as u64) + // .unwrap(); + // gst_debug!(self.cat, obj: element, "Returning latency {}", latency); + println!("/*/a*f/a*sd/f*ad/sf*ad/sf*ad/sf"); + let max = latency * 1843200; + println!("{:?}", latency); + println!("{:?}",max); + q.set(true, latency, max); + return true; + } else { + return false; + } + } + _ => (), + } + BaseSrcBase::parent_query(element, query) + } + fn fixate(&self, element: &BaseSrc, caps: gst::Caps) -> gst::Caps { //We need to set the correct caps resolution and framerate unsafe{ diff --git a/gst-plugin-ndi/src/ndivideosrc.rs b/gst-plugin-ndi/src/ndivideosrc.rs index ab58f188..e9667bd9 100644 --- a/gst-plugin-ndi/src/ndivideosrc.rs +++ b/gst-plugin-ndi/src/ndivideosrc.rs @@ -278,6 +278,49 @@ impl NdiVideoSrc { true } + + fn query(&self, element: &BaseSrc, query: &mut gst::QueryRef) -> bool { + use gst::QueryView; + + match query.view_mut() { + // We only work in Push mode. In Pull mode, create() could be called with + // arbitrary offsets and we would have to produce for that specific offset + QueryView::Scheduling(ref mut q) => { + q.set(gst::SchedulingFlags::SEQUENTIAL, 1, -1, 0); + q.add_scheduling_modes(&[gst::PadMode::Push]); + return true; + } + // In Live mode we will have a latency equal to the number of samples in each buffer. + // We can't output samples before they were produced, and the last sample of a buffer + // is produced that much after the beginning, leading to this latency calculation + QueryView::Latency(ref mut q) => { + //let settings = *self.settings.lock().unwrap(); + let state = self.state.lock().unwrap(); + + if let Some(ref info) = state.info { + // let latency = gst::SECOND + // .mul_div_floor(settings.samples_per_buffer as u64, info.rate() as u64) + // .unwrap(); + let latency = gst::SECOND.mul_div_floor(3 as u64, 2 as u64).unwrap(); + // let latency = gst::SECOND + // .mul_div_floor(1 as u64, 30 as u64) + // .unwrap(); + // gst_debug!(self.cat, obj: element, "Returning latency {}", latency); + println!("/*/a*f/a*sd/f*ad/sf*ad/sf*ad/sf"); + let max = latency * 1843200; + println!("{:?}", latency); + println!("{:?}",max); + q.set(true, latency, max); + return true; + } else { + return false; + } + } + _ => (), + } + BaseSrcBase::parent_query(element, query) + } + fn fixate(&self, element: &BaseSrc, caps: gst::Caps) -> gst::Caps { //We need to set the correct caps resolution and framerate unsafe{ From 98cd1bbc8ac401e2a7c825640a6fa53bbde9809b Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Mon, 2 Jul 2018 14:08:15 +0200 Subject: [PATCH 051/199] Added pipelines to README --- gst-plugin-ndi/README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/gst-plugin-ndi/README.md b/gst-plugin-ndi/README.md index 35898a34..24a8628b 100644 --- a/gst-plugin-ndi/README.md +++ b/gst-plugin-ndi/README.md @@ -34,4 +34,9 @@ GST_DEBUG=3 gst-launch-1.0 -v ndisrc ip=10.21.10.103:5961 ! video/x-raw, format= or GST_DEBUG=3 gst-launch-1.0 -v ndisrc stream-name="MINI-DE-TELTEK.OFICINA.TELTEK.ES (NDI Signal Generator)" ! video/x-raw, format=UYVY, width=1920, height=1080, framerate=25/1 ! autovideosink sync=false + +gst-launch-1.0 -v ndivideosrc name=gc-ndi-src stream-name="GC-DEV2 (OBS)" ! autovideosink ts-offset=1000000000 +gst-launch-1.0 -v ndivideosrc name=gc-ndi-src stream-name="GC-DEV2 (OBS)" ! fakesink silent=false +GST_DEBUG=*basesink*:5 gst-launch-1.0 -v ndivideosrc name=gc-ndi-src stream-name="GC-DEV2 (OBS)" ! autovideosink + ``` From 0baf7ee83927506407abdbbe306a2c37e8ea6098 Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Tue, 3 Jul 2018 10:02:15 +0200 Subject: [PATCH 052/199] Calculate buffer duration from frame info --- gst-plugin-ndi/src/ndiaudiosrc.rs | 2 +- gst-plugin-ndi/src/ndivideosrc.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gst-plugin-ndi/src/ndiaudiosrc.rs b/gst-plugin-ndi/src/ndiaudiosrc.rs index fecb87b9..92df9e60 100644 --- a/gst-plugin-ndi/src/ndiaudiosrc.rs +++ b/gst-plugin-ndi/src/ndiaudiosrc.rs @@ -400,7 +400,7 @@ impl NdiAudioSrc { let vec = Vec::from_raw_parts(audio_frame.p_data as *mut u8, buff_size, buff_size); //TODO Set pts, duration and other info about the buffer let pts: gst::ClockTime = (pts * 100).into(); - let duration: gst::ClockTime = (20154200).into(); + let duration: gst::ClockTime = (((audio_frame.no_samples as f64 / audio_frame.sample_rate as f64) * 10000000.0) as u64).into(); let buffer = buffer.get_mut().unwrap(); buffer.set_pts(pts); buffer.set_duration(duration); diff --git a/gst-plugin-ndi/src/ndivideosrc.rs b/gst-plugin-ndi/src/ndivideosrc.rs index e9667bd9..2e5c5391 100644 --- a/gst-plugin-ndi/src/ndivideosrc.rs +++ b/gst-plugin-ndi/src/ndivideosrc.rs @@ -413,7 +413,7 @@ impl NdiVideoSrc { // println!("{:?}", video_frame.line_stride_in_bytes); //println!("{:?}", video_frame); //TODO get duration - let duration: gst::ClockTime = (33333333).into(); + let duration: gst::ClockTime = (((video_frame.frame_rate_D as f64 / video_frame.frame_rate_N as f64) * 1000000000.0) as u64).into(); let buffer = buffer.get_mut().unwrap(); buffer.set_pts(pts); buffer.set_duration(duration); From 8bf4f8f935460a5ff243fcede831442eccdf4f0a Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Tue, 14 Aug 2018 15:45:13 +0200 Subject: [PATCH 053/199] Fix to use multiple NDI streams in the same pipeline It's not possible to connect to the same stream twice. For example to audio and video from the same stream. --- gst-plugin-ndi/src/lib.rs | 47 +++++++++++++++------------ gst-plugin-ndi/src/ndiaudiosrc.rs | 53 ++++++++++++++++--------------- gst-plugin-ndi/src/ndivideosrc.rs | 49 ++++++++++++++-------------- 3 files changed, 79 insertions(+), 70 deletions(-) diff --git a/gst-plugin-ndi/src/lib.rs b/gst-plugin-ndi/src/lib.rs index f9f68f0c..61853636 100644 --- a/gst-plugin-ndi/src/lib.rs +++ b/gst-plugin-ndi/src/lib.rs @@ -19,6 +19,8 @@ extern crate gstreamer_video as gst_video; extern crate byte_slice_cast; extern crate num_traits; +#[macro_use] +extern crate lazy_static; mod ndivideosrc; mod ndiaudiosrc; @@ -31,6 +33,8 @@ use std::ffi::{CStr, CString}; use ndilib::*; use gst_plugin::base_src::*; +use std::collections::HashMap; +use std::sync::Mutex; // Plugin entry point that should register all elements provided by this plugin, // and everything else that this plugin might provide (e.g. typefinders or device providers). @@ -40,7 +44,6 @@ fn plugin_init(plugin: &gst::Plugin) -> bool { true } - struct Ndi{ recv: Option, start_pts: u64, @@ -51,16 +54,23 @@ static mut ndi_struct: Ndi = Ndi{ start_pts: 0, }; +lazy_static! { + static ref hashmap_receivers: Mutex> = { + let mut m = HashMap::new(); + Mutex::new(m) + }; +} + fn connect_ndi(cat: gst::DebugCategory , element: &BaseSrc, ip: String, stream_name: String) -> bool{ unsafe { gst_debug!(cat, obj: element, "Starting NDI connection..."); - match ndi_struct.recv { - None => { - //gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); - //return true; - } - _ => return true, - }; + + let mut map = hashmap_receivers.lock().unwrap(); + if (map.contains_key(&stream_name) || map.contains_key(&ip)){ + println!("Already connected to {}{}", ip, stream_name); + return false; + } + if !NDIlib_initialize() { gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_initialize error"]); return false; @@ -73,7 +83,6 @@ fn connect_ndi(cat: gst::DebugCategory , element: &BaseSrc, ip: String, stream let NDI_find_create_desc: NDIlib_find_create_t = Default::default(); let pNDI_find = NDIlib_find_create_v2(&NDI_find_create_desc); let ip_ptr = CString::new(ip.clone()).unwrap(); - if ip_ptr == CString::new("").unwrap(){ if pNDI_find.is_null() { gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_find_create_v2 error"]); return false; @@ -94,9 +103,8 @@ fn connect_ndi(cat: gst::DebugCategory , element: &BaseSrc, ip: String, stream let mut no_source: isize = -1; for i in 0..total_sources as isize{ - if CStr::from_ptr((*p_sources.offset(i)).p_ndi_name) - .to_string_lossy() - .into_owned() == stream_name{ + if (CStr::from_ptr((*p_sources.offset(i)).p_ndi_name).to_string_lossy().into_owned() == stream_name || + CStr::from_ptr((*p_sources.offset(i)).p_ip_address).to_string_lossy().into_owned() == ip){ no_source = i; break; } @@ -115,14 +123,9 @@ fn connect_ndi(cat: gst::DebugCategory , element: &BaseSrc, ip: String, stream .into_owned()); source = *p_sources.offset(no_source).clone(); - } - else{ - source.p_ip_address = ip_ptr.as_ptr(); - gst_debug!(cat, obj: element, "Connecting to NDI source with address '{}'", CStr::from_ptr(source.p_ip_address) - .to_string_lossy() - .into_owned() - ); - } + + let source_ip = CStr::from_ptr(source.p_ip_address).to_string_lossy().into_owned(); + let source_name = CStr::from_ptr(source.p_ndi_name).to_string_lossy().into_owned(); // We now have at least one source, so we create a receiver to look at it. // We tell it that we prefer YCbCr video since it is more efficient for us. If the source has an alpha channel @@ -159,7 +162,9 @@ fn connect_ndi(cat: gst::DebugCategory , element: &BaseSrc, ip: String, stream }; NDIlib_recv_send_metadata(pNDI_recv, &enable_hw_accel); - ndi_struct.recv = Some(NdiInstance{recv: pNDI_recv}); + + map.insert(source_name.clone(), NdiInstance{recv: pNDI_recv}); + map.insert(source_ip.clone(), NdiInstance{recv: pNDI_recv}); // let start = SystemTime::now(); // let since_the_epoch = start.duration_since(UNIX_EPOCH) diff --git a/gst-plugin-ndi/src/ndiaudiosrc.rs b/gst-plugin-ndi/src/ndiaudiosrc.rs index 92df9e60..12287076 100644 --- a/gst-plugin-ndi/src/ndiaudiosrc.rs +++ b/gst-plugin-ndi/src/ndiaudiosrc.rs @@ -18,9 +18,11 @@ use std::ptr; use ndilib::*; use connect_ndi; -use ndi_struct; +// use ndi_struct; use stop_ndi; +use hashmap_receivers; + // Property value storage #[derive(Debug, Clone)] struct Settings { @@ -318,26 +320,27 @@ impl NdiAudioSrc { fn fixate(&self, element: &BaseSrc, caps: gst::Caps) -> gst::Caps { //We need to set the correct caps resolution and framerate unsafe{ - let recv = match ndi_struct.recv{ - None => { - //TODO Update gst_element_error with one more descriptive - //println!("pNDI_recv no encontrado"); - gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); - return caps; - } - Some(ref recv) => recv.clone(), - }; + let map = hashmap_receivers.lock().unwrap(); + let settings = self.settings.lock().unwrap(); + + let mut id = &settings.stream_name; + if (&settings.ip != ""){ + id = &settings.ip; + } + let recv = map.get(id).unwrap(); let pNDI_recv = recv.recv; let mut timestamp_data = self.timestamp_data.lock().unwrap(); let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); + let mut frame_type: NDIlib_frame_type_e = NDIlib_frame_type_e::NDIlib_frame_type_none; while frame_type != NDIlib_frame_type_e::NDIlib_frame_type_audio{ frame_type = NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000); } - ndi_struct.start_pts = audio_frame.timecode as u64; - //timestamp_data.pts = audio_frame.timecode as u64; + + //ndi_struct.start_pts = audio_frame.timecode as u64; + timestamp_data.pts = audio_frame.timecode as u64; let mut caps = gst::Caps::truncate(caps); { @@ -377,22 +380,22 @@ impl NdiAudioSrc { Some(ref info) => info.clone(), }; unsafe{ - let recv = match ndi_struct.recv{ - None => { - //TODO Update gst_element_error with one more descriptive - //println!("pNDI_recv no encontrado"); - gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); - return Err(gst::FlowReturn::NotNegotiated); - } - Some(ref recv) => recv.clone(), - }; - let pNDI_recv = recv.recv; - let pts: u64; + let map = hashmap_receivers.lock().unwrap(); + let mut id = &_settings.stream_name; + if (&_settings.ip != ""){ + id = &_settings.ip; + } + let recv = map.get(id).unwrap(); + + let pNDI_recv = recv.recv; + + let pts: u64; let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000,); - //pts = (audio_frame.timecode as u64) - timestamp_data.pts; - pts = (audio_frame.timecode as u64) - ndi_struct.start_pts; + + pts = (audio_frame.timecode as u64) - timestamp_data.pts; + //pts = (audio_frame.timecode as u64) - ndi_struct.start_pts; let buff_size = ((audio_frame.channel_stride_in_bytes)) as usize; let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); diff --git a/gst-plugin-ndi/src/ndivideosrc.rs b/gst-plugin-ndi/src/ndivideosrc.rs index 2e5c5391..1b6f99bb 100644 --- a/gst-plugin-ndi/src/ndivideosrc.rs +++ b/gst-plugin-ndi/src/ndivideosrc.rs @@ -19,9 +19,11 @@ use std::ptr; use ndilib::*; use connect_ndi; -use ndi_struct; +// use ndi_struct; use stop_ndi; +use hashmap_receivers; + // Property value storage #[derive(Debug, Clone)] struct Settings { @@ -324,16 +326,14 @@ impl NdiVideoSrc { fn fixate(&self, element: &BaseSrc, caps: gst::Caps) -> gst::Caps { //We need to set the correct caps resolution and framerate unsafe{ - let recv = match ndi_struct.recv{ - None => { - //TODO Update gst_element_error with one more descriptive - //println!("pNDI_recv no encontrado"); - gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); - //TODO if none not return anything - return caps; - } - Some(ref recv) => recv.clone(), - }; + let map = hashmap_receivers.lock().unwrap(); + let settings = self.settings.lock().unwrap(); + + let mut id = &settings.stream_name; + if (&settings.ip != ""){ + id = &settings.ip; + } + let recv = map.get(id).unwrap(); let pNDI_recv = recv.recv; let mut timestamp_data = self.timestamp_data.lock().unwrap(); @@ -345,8 +345,9 @@ impl NdiVideoSrc { frame_type = NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000); } - //timestamp_data.pts = video_frame.timecode as u64; - ndi_struct.start_pts = video_frame.timecode as u64; + //TODO Check that this is working + timestamp_data.pts = video_frame.timecode as u64; + //ndi_struct.start_pts = video_frame.timecode as u64; let mut caps = gst::Caps::truncate(caps); { @@ -386,23 +387,23 @@ impl NdiVideoSrc { Some(ref info) => info.clone(), }; unsafe{ - let recv = match ndi_struct.recv{ - None => { - //TODO Update gst_element_error with one more descriptive - //println!("pNDI_recv no encontrado"); - gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); - return Err(gst::FlowReturn::NotNegotiated); - } - Some(ref recv) => recv.clone(), - }; + let map = hashmap_receivers.lock().unwrap(); + let mut id = &_settings.stream_name; + + if (&_settings.ip != ""){ + id = &_settings.ip; + } + let recv = map.get(id).unwrap(); + let pNDI_recv = recv.recv; let pts: u64; let video_frame: NDIlib_video_frame_v2_t = Default::default(); NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000,); - //pts = (video_frame.timecode as u64) - timestamp_data.pts; - pts = (video_frame.timecode as u64) - ndi_struct.start_pts; + pts = (video_frame.timecode as u64) - timestamp_data.pts; + //pts = (video_frame.timecode as u64) - ndi_struct.start_pts; + let buff_size = (video_frame.yres * video_frame.line_stride_in_bytes) as usize; //println!("{:?}", buff_size); let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); From ba45931830620bf792e55d4fe616f3cd5d8e6d59 Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Tue, 14 Aug 2018 16:47:58 +0200 Subject: [PATCH 054/199] Add audio to the pipeline even if the stream source it's already in use for video FIXME: Whith this commit it's possible to add more streams for the same source if the pipeline has an audio source for that source. --- gst-plugin-ndi/src/lib.rs | 32 +++++++++++++++++++++++++++----- gst-plugin-ndi/src/ndilib.rs | 1 + 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/gst-plugin-ndi/src/lib.rs b/gst-plugin-ndi/src/lib.rs index 61853636..a62d2f9c 100644 --- a/gst-plugin-ndi/src/lib.rs +++ b/gst-plugin-ndi/src/lib.rs @@ -36,6 +36,8 @@ use gst_plugin::base_src::*; use std::collections::HashMap; use std::sync::Mutex; +use gst::GstObjectExt; + // Plugin entry point that should register all elements provided by this plugin, // and everything else that this plugin might provide (e.g. typefinders or device providers). fn plugin_init(plugin: &gst::Plugin) -> bool { @@ -64,11 +66,31 @@ lazy_static! { fn connect_ndi(cat: gst::DebugCategory , element: &BaseSrc, ip: String, stream_name: String) -> bool{ unsafe { gst_debug!(cat, obj: element, "Starting NDI connection..."); - let mut map = hashmap_receivers.lock().unwrap(); + + //FIXME It's possible to connect more than one source if an audiosrc is active + let mut audio = false; + if (element.get_name().contains("ndiaudiosrc")){ + audio = true; + } + if (map.contains_key(&stream_name) || map.contains_key(&ip)){ - println!("Already connected to {}{}", ip, stream_name); - return false; + if (map.contains_key(&stream_name)){ + let recv = map.get(&stream_name).unwrap(); + audio = recv.audio; + } + else if (map.contains_key(&ip)){ + let recv = map.get(&ip).unwrap(); + audio = recv.audio; + } + if (audio){ + println!("We already have an source, but we need to add an audio source to the pipeline. So we need to reutilize the source..."); + return true; + } + else{ + println!("Already connected to {}{}", ip, stream_name); + return false; + } } if !NDIlib_initialize() { @@ -163,8 +185,8 @@ fn connect_ndi(cat: gst::DebugCategory , element: &BaseSrc, ip: String, stream NDIlib_recv_send_metadata(pNDI_recv, &enable_hw_accel); - map.insert(source_name.clone(), NdiInstance{recv: pNDI_recv}); - map.insert(source_ip.clone(), NdiInstance{recv: pNDI_recv}); + map.insert(source_name.clone(), NdiInstance{recv: pNDI_recv, audio: audio}); + map.insert(source_ip.clone(), NdiInstance{recv: pNDI_recv, audio: audio}); // let start = SystemTime::now(); // let since_the_epoch = start.duration_since(UNIX_EPOCH) diff --git a/gst-plugin-ndi/src/ndilib.rs b/gst-plugin-ndi/src/ndilib.rs index 2b345ac0..dd48278f 100644 --- a/gst-plugin-ndi/src/ndilib.rs +++ b/gst-plugin-ndi/src/ndilib.rs @@ -152,6 +152,7 @@ pub type NDIlib_recv_instance_t = *mut ::std::os::raw::c_void; //Rust wrapper around *mut ::std::os::raw::c_void pub struct NdiInstance { pub recv: NDIlib_recv_instance_t, + pub audio: bool, } unsafe impl ::std::marker::Send for NdiInstance {} From 07a8b8a274951249882cc4c1e2693f0d93f28eba Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Mon, 20 Aug 2018 09:25:15 +0200 Subject: [PATCH 055/199] Refactor logic to use multiple streams in the same pipeline Now it's possible to connect to N streams in the same pipeline. Each new connection will create a new NDI receiver except if it's a empty slot in the receiver. Each receiver has two slots one to connect to audio and other to connect to video to the same stream. --- gst-plugin-ndi/Cargo.toml | 3 +- gst-plugin-ndi/src/lib.rs | 115 +++++++++++++++++++----------- gst-plugin-ndi/src/ndiaudiosrc.rs | 33 +++++---- gst-plugin-ndi/src/ndilib.rs | 2 +- gst-plugin-ndi/src/ndivideosrc.rs | 35 ++++----- 5 files changed, 113 insertions(+), 75 deletions(-) diff --git a/gst-plugin-ndi/Cargo.toml b/gst-plugin-ndi/Cargo.toml index fa56acce..e4d6caec 100644 --- a/gst-plugin-ndi/Cargo.toml +++ b/gst-plugin-ndi/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "gst-plugin-ndi" version = "0.1.0" -authors = ["Ruben Gonzalez "] +authors = ["Ruben Gonzalez ", "Daniel Vilar "] repository = "https://gitlab.teltek.es/rubenrua/ndi-rs.git" license = "unknow" # TODO MIT/Apache-2.0 @@ -12,6 +12,7 @@ gstreamer = "0.11" gstreamer-base = "0.11" gstreamer-video = "0.11" gstreamer-audio = "0.11" +lazy_static = "1.1.0" byte-slice-cast = "0.1" # TODO delete num-traits = "0.2" # TODO delete diff --git a/gst-plugin-ndi/src/lib.rs b/gst-plugin-ndi/src/lib.rs index a62d2f9c..69e921fb 100644 --- a/gst-plugin-ndi/src/lib.rs +++ b/gst-plugin-ndi/src/lib.rs @@ -56,46 +56,64 @@ static mut ndi_struct: Ndi = Ndi{ start_pts: 0, }; +struct ndi_receiver_info{ + stream_name: String, + ip: String, + video: bool, + audio: bool, + ndi_instance: NdiInstance, + id: i8, +} + lazy_static! { - static ref hashmap_receivers: Mutex> = { + static ref hashmap_receivers: Mutex> = { let mut m = HashMap::new(); Mutex::new(m) }; } -fn connect_ndi(cat: gst::DebugCategory , element: &BaseSrc, ip: String, stream_name: String) -> bool{ +static mut id_receiver: i8 = 0; + +fn connect_ndi(cat: gst::DebugCategory , element: &BaseSrc, ip: String, stream_name: String) -> i8{ unsafe { gst_debug!(cat, obj: element, "Starting NDI connection..."); - let mut map = hashmap_receivers.lock().unwrap(); - //FIXME It's possible to connect more than one source if an audiosrc is active + let mut receivers = hashmap_receivers.lock().unwrap(); let mut audio = false; - if (element.get_name().contains("ndiaudiosrc")){ + let mut video = false; + id_receiver += 1; + + //FIXME Search for another way to know if the source is an audio or a video source + if (element.get_name().contains("audiosrc")){ audio = true; } + else + { + video = true; + } - if (map.contains_key(&stream_name) || map.contains_key(&ip)){ - if (map.contains_key(&stream_name)){ - let recv = map.get(&stream_name).unwrap(); - audio = recv.audio; + for val in receivers.values_mut(){ + if (val.ip == ip || val.stream_name == stream_name){ + if ((val.audio && val.video) || (val.audio && audio) || (val.video && video)){ + break; + } + else { + if (video){ + val.video = video; + } + else{ + val.audio = audio; + } + return val.id; + } } - else if (map.contains_key(&ip)){ - let recv = map.get(&ip).unwrap(); - audio = recv.audio; - } - if (audio){ - println!("We already have an source, but we need to add an audio source to the pipeline. So we need to reutilize the source..."); - return true; - } - else{ - println!("Already connected to {}{}", ip, stream_name); - return false; - } - } + + } if !NDIlib_initialize() { gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_initialize error"]); - return false; + // return false; + return 0; } let mut source: NDIlib_source_t = NDIlib_source_t{p_ndi_name: ptr::null(), @@ -107,7 +125,8 @@ fn connect_ndi(cat: gst::DebugCategory , element: &BaseSrc, ip: String, stream let ip_ptr = CString::new(ip.clone()).unwrap(); if pNDI_find.is_null() { gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_find_create_v2 error"]); - return false; + // return false; + return 0; } let mut total_sources: u32 = 0; @@ -120,7 +139,8 @@ fn connect_ndi(cat: gst::DebugCategory , element: &BaseSrc, ip: String, stream // We need at least one source if p_sources.is_null() { gst_element_error!(element, gst::CoreError::Negotiation, ["Error getting NDIlib_find_get_current_sources"]); - return false; + // return false; + return 0; } let mut no_source: isize = -1; @@ -133,7 +153,8 @@ fn connect_ndi(cat: gst::DebugCategory , element: &BaseSrc, ip: String, stream } if no_source == -1 { gst_element_error!(element, gst::CoreError::Negotiation, ["Stream name not found"]); - return false; + // return false; + return 0; } gst_debug!(cat, obj: element, "Total sources in network {}: Connecting to NDI source with name '{}' and address '{}'", total_sources, @@ -163,7 +184,8 @@ fn connect_ndi(cat: gst::DebugCategory , element: &BaseSrc, ip: String, stream if pNDI_recv.is_null() { //println!("Cannot run NDI: NDIlib_recv_create_v3 error."); gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_recv_create_v3 error"]); - return false; + // return false; + return 0; } // Destroy the NDI finder. We needed to have access to the pointers to p_sources[0] @@ -173,7 +195,7 @@ fn connect_ndi(cat: gst::DebugCategory , element: &BaseSrc, ip: String, stream let tally_state: NDIlib_tally_t = Default::default(); NDIlib_recv_set_tally(pNDI_recv, &tally_state); - // Enable Hardwqre Decompression support if this support has it. Please read the caveats in the documentation + // Enable Hardware Decompression support if this support has it. Please read the caveats in the documentation // regarding this. There are times in which it might reduce the performance although on small stream numbers // it almost always yields the same or better performance. let data = CString::new("").unwrap(); @@ -185,8 +207,7 @@ fn connect_ndi(cat: gst::DebugCategory , element: &BaseSrc, ip: String, stream NDIlib_recv_send_metadata(pNDI_recv, &enable_hw_accel); - map.insert(source_name.clone(), NdiInstance{recv: pNDI_recv, audio: audio}); - map.insert(source_ip.clone(), NdiInstance{recv: pNDI_recv, audio: audio}); + receivers.insert(id_receiver, ndi_receiver_info{stream_name: source_name.clone(), ip: source_ip.clone(), video:video, audio: audio, ndi_instance: NdiInstance{recv: pNDI_recv}, id: id_receiver}); // let start = SystemTime::now(); // let since_the_epoch = start.duration_since(UNIX_EPOCH) @@ -195,26 +216,34 @@ fn connect_ndi(cat: gst::DebugCategory , element: &BaseSrc, ip: String, stream // ndi_struct.start_pts = Some(since_the_epoch.as_secs() * 1000000000 + // since_the_epoch.subsec_nanos() as u64); gst_debug!(cat, obj: element, "Started NDI connection"); - return true; + return id_receiver; } } -fn stop_ndi(cat: gst::DebugCategory , element: &BaseSrc) -> bool{ +fn stop_ndi(cat: gst::DebugCategory , element: &BaseSrc, id: i8) -> bool{ gst_debug!(cat, obj: element, "Closing NDI connection..."); unsafe{ - let recv = match ndi_struct.recv{ - None => { - //TODO Update gst_element_error with one more descriptive - //println!("pNDI_recv no encontrado"); - //gst_element_error!(element, gst::CoreError::Negotiation, ["No encontramos ndi recv"]); + let mut receivers = hashmap_receivers.lock().unwrap(); + + { + let val = receivers.get_mut(&id).unwrap(); + if val.video && val.audio{ + if (element.get_name().contains("audiosrc")){ + val.audio = false; + } + else{ + val.video = false; + } return true; } - Some(ref recv) => recv.clone(), - }; - let pNDI_recv = recv.recv; - NDIlib_recv_destroy(pNDI_recv); - ndi_struct.recv = None; - NDIlib_destroy(); + + let recv = &val.ndi_instance; + let pNDI_recv = recv.recv; + NDIlib_recv_destroy(pNDI_recv); + // ndi_struct.recv = None; + NDIlib_destroy(); + } + receivers.remove(&id); gst_debug!(cat, obj: element, "Closed NDI connection"); return true; } diff --git a/gst-plugin-ndi/src/ndiaudiosrc.rs b/gst-plugin-ndi/src/ndiaudiosrc.rs index 12287076..0dbb7b2c 100644 --- a/gst-plugin-ndi/src/ndiaudiosrc.rs +++ b/gst-plugin-ndi/src/ndiaudiosrc.rs @@ -28,6 +28,7 @@ use hashmap_receivers; struct Settings { stream_name: String, ip: String, + id_receiver: i8, } impl Default for Settings { @@ -35,6 +36,7 @@ impl Default for Settings { Settings { stream_name: String::from("Fixed ndi stream name"), ip: String::from(""), + id_receiver: 0, } } } @@ -260,16 +262,23 @@ impl NdiAudioSrc { // Reset state *self.state.lock().unwrap() = Default::default(); - let settings = self.settings.lock().unwrap(); - return connect_ndi(self.cat, element, settings.ip.clone(), settings.stream_name.clone()); - + let mut settings = self.settings.lock().unwrap(); + settings.id_receiver = connect_ndi(self.cat, element, settings.ip.clone(), settings.stream_name.clone()); + if settings.id_receiver == 0{ + return false; + } + else{ + return true; + } } // Called when shutting down the element so we can release all stream-related state fn stop(&self, element: &BaseSrc) -> bool { // Reset state *self.state.lock().unwrap() = Default::default(); - stop_ndi(self.cat, element); + + let settings = self.settings.lock().unwrap(); + stop_ndi(self.cat, element, settings.id_receiver.clone()); // Commented because when adding ndi destroy stopped in this line //*self.state.lock().unwrap() = Default::default(); true @@ -320,16 +329,12 @@ impl NdiAudioSrc { fn fixate(&self, element: &BaseSrc, caps: gst::Caps) -> gst::Caps { //We need to set the correct caps resolution and framerate unsafe{ - let map = hashmap_receivers.lock().unwrap(); + let receivers = hashmap_receivers.lock().unwrap(); let settings = self.settings.lock().unwrap(); - let mut id = &settings.stream_name; - if (&settings.ip != ""){ - id = &settings.ip; - } - let recv = map.get(id).unwrap(); - + let recv = &receivers.get(&settings.id_receiver).unwrap().ndi_instance; let pNDI_recv = recv.recv; + let mut timestamp_data = self.timestamp_data.lock().unwrap(); let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); @@ -380,13 +385,15 @@ impl NdiAudioSrc { Some(ref info) => info.clone(), }; unsafe{ - let map = hashmap_receivers.lock().unwrap(); + let receivers = hashmap_receivers.lock().unwrap(); + let mut id = &_settings.stream_name; if (&_settings.ip != ""){ id = &_settings.ip; } - let recv = map.get(id).unwrap(); + + let recv = &receivers.get(&_settings.id_receiver).unwrap().ndi_instance; let pNDI_recv = recv.recv; diff --git a/gst-plugin-ndi/src/ndilib.rs b/gst-plugin-ndi/src/ndilib.rs index dd48278f..ec2c8d38 100644 --- a/gst-plugin-ndi/src/ndilib.rs +++ b/gst-plugin-ndi/src/ndilib.rs @@ -152,7 +152,7 @@ pub type NDIlib_recv_instance_t = *mut ::std::os::raw::c_void; //Rust wrapper around *mut ::std::os::raw::c_void pub struct NdiInstance { pub recv: NDIlib_recv_instance_t, - pub audio: bool, + // pub audio: bool, } unsafe impl ::std::marker::Send for NdiInstance {} diff --git a/gst-plugin-ndi/src/ndivideosrc.rs b/gst-plugin-ndi/src/ndivideosrc.rs index 1b6f99bb..543b0efb 100644 --- a/gst-plugin-ndi/src/ndivideosrc.rs +++ b/gst-plugin-ndi/src/ndivideosrc.rs @@ -29,6 +29,7 @@ use hashmap_receivers; struct Settings { stream_name: String, ip: String, + id_receiver: i8, } impl Default for Settings { @@ -36,6 +37,7 @@ impl Default for Settings { Settings { stream_name: String::from("Fixed ndi stream name"), ip: String::from(""), + id_receiver: 0, } } } @@ -266,15 +268,23 @@ impl NdiVideoSrc { // Reset state *self.state.lock().unwrap() = Default::default(); - let settings = self.settings.lock().unwrap(); - return connect_ndi(self.cat, element, settings.ip.clone(), settings.stream_name.clone()); + let mut settings = self.settings.lock().unwrap(); + settings.id_receiver = connect_ndi(self.cat, element, settings.ip.clone(), settings.stream_name.clone()); + if settings.id_receiver == 0{ + return false; + } + else{ + return true; + } } // Called when shutting down the element so we can release all stream-related state fn stop(&self, element: &BaseSrc) -> bool { // Reset state *self.state.lock().unwrap() = Default::default(); - stop_ndi(self.cat, element); + + let settings = self.settings.lock().unwrap(); + stop_ndi(self.cat, element, settings.id_receiver.clone()); // Commented because when adding ndi destroy stopped in this line //*self.state.lock().unwrap() = Default::default(); true @@ -326,16 +336,12 @@ impl NdiVideoSrc { fn fixate(&self, element: &BaseSrc, caps: gst::Caps) -> gst::Caps { //We need to set the correct caps resolution and framerate unsafe{ - let map = hashmap_receivers.lock().unwrap(); + let receivers = hashmap_receivers.lock().unwrap(); let settings = self.settings.lock().unwrap(); - let mut id = &settings.stream_name; - if (&settings.ip != ""){ - id = &settings.ip; - } - let recv = map.get(id).unwrap(); - + let recv = &receivers.get(&settings.id_receiver).unwrap().ndi_instance; let pNDI_recv = recv.recv; + let mut timestamp_data = self.timestamp_data.lock().unwrap(); let video_frame: NDIlib_video_frame_v2_t = Default::default(); @@ -387,14 +393,9 @@ impl NdiVideoSrc { Some(ref info) => info.clone(), }; unsafe{ - let map = hashmap_receivers.lock().unwrap(); - let mut id = &_settings.stream_name; - - if (&_settings.ip != ""){ - id = &_settings.ip; - } - let recv = map.get(id).unwrap(); + let receivers = hashmap_receivers.lock().unwrap(); + let recv = &receivers.get(&_settings.id_receiver).unwrap().ndi_instance; let pNDI_recv = recv.recv; let pts: u64; From 3b4ae6c00e3025213dbd31e200db0aac883f549e Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Mon, 20 Aug 2018 12:14:54 +0200 Subject: [PATCH 056/199] Indent and clean warnings --- gst-plugin-ndi/src/lib.rs | 108 +++++++++++++----------------- gst-plugin-ndi/src/ndiaudiosrc.rs | 19 ++---- gst-plugin-ndi/src/ndivideosrc.rs | 10 +-- 3 files changed, 54 insertions(+), 83 deletions(-) diff --git a/gst-plugin-ndi/src/lib.rs b/gst-plugin-ndi/src/lib.rs index 69e921fb..4f7f1e1b 100644 --- a/gst-plugin-ndi/src/lib.rs +++ b/gst-plugin-ndi/src/lib.rs @@ -26,7 +26,6 @@ mod ndivideosrc; mod ndiaudiosrc; pub mod ndilib; -use std::ptr; use std::{thread, time}; //use std::time::{SystemTime, UNIX_EPOCH}; use std::ffi::{CStr, CString}; @@ -46,16 +45,6 @@ fn plugin_init(plugin: &gst::Plugin) -> bool { true } -struct Ndi{ - recv: Option, - start_pts: u64, -} - -static mut ndi_struct: Ndi = Ndi{ - recv: None, - start_pts: 0, -}; - struct ndi_receiver_info{ stream_name: String, ip: String, @@ -67,7 +56,7 @@ struct ndi_receiver_info{ lazy_static! { static ref hashmap_receivers: Mutex> = { - let mut m = HashMap::new(); + let m = HashMap::new(); Mutex::new(m) }; } @@ -84,7 +73,7 @@ fn connect_ndi(cat: gst::DebugCategory , element: &BaseSrc, ip: String, stream id_receiver += 1; //FIXME Search for another way to know if the source is an audio or a video source - if (element.get_name().contains("audiosrc")){ + if element.get_name().contains("audiosrc"){ audio = true; } else @@ -93,12 +82,12 @@ fn connect_ndi(cat: gst::DebugCategory , element: &BaseSrc, ip: String, stream } for val in receivers.values_mut(){ - if (val.ip == ip || val.stream_name == stream_name){ - if ((val.audio && val.video) || (val.audio && audio) || (val.video && video)){ + if val.ip == ip || val.stream_name == stream_name{ + if (val.audio && val.video) || (val.audio && audio) || (val.video && video){ break; } else { - if (video){ + if video { val.video = video; } else{ @@ -116,56 +105,53 @@ fn connect_ndi(cat: gst::DebugCategory , element: &BaseSrc, ip: String, stream return 0; } - let mut source: NDIlib_source_t = NDIlib_source_t{p_ndi_name: ptr::null(), - p_ip_address: ptr::null()}; + //TODO default values + let NDI_find_create_desc: NDIlib_find_create_t = Default::default(); + let pNDI_find = NDIlib_find_create_v2(&NDI_find_create_desc); + //let ip_ptr = CString::new(ip.clone()).unwrap(); + if pNDI_find.is_null() { + gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_find_create_v2 error"]); + // return false; + return 0; + } - //TODO default values - let NDI_find_create_desc: NDIlib_find_create_t = Default::default(); - let pNDI_find = NDIlib_find_create_v2(&NDI_find_create_desc); - let ip_ptr = CString::new(ip.clone()).unwrap(); - if pNDI_find.is_null() { - gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_find_create_v2 error"]); - // return false; - return 0; - } + let mut total_sources: u32 = 0; + let p_sources; - let mut total_sources: u32 = 0; - let p_sources; + // TODO Sleep 1s to wait for all sources + thread::sleep(time::Duration::from_millis(2000)); + p_sources = NDIlib_find_get_current_sources(pNDI_find, &mut total_sources as *mut u32); - // TODO Sleep 1s to wait for all sources - thread::sleep(time::Duration::from_millis(2000)); - p_sources = NDIlib_find_get_current_sources(pNDI_find, &mut total_sources as *mut u32); + // We need at least one source + if p_sources.is_null() { + gst_element_error!(element, gst::CoreError::Negotiation, ["Error getting NDIlib_find_get_current_sources"]); + // return false; + return 0; + } - // We need at least one source - if p_sources.is_null() { - gst_element_error!(element, gst::CoreError::Negotiation, ["Error getting NDIlib_find_get_current_sources"]); - // return false; - return 0; - } + let mut no_source: isize = -1; + for i in 0..total_sources as isize{ + if CStr::from_ptr((*p_sources.offset(i)).p_ndi_name).to_string_lossy().into_owned() == stream_name || + CStr::from_ptr((*p_sources.offset(i)).p_ip_address).to_string_lossy().into_owned() == ip{ + no_source = i; + break; + } + } + if no_source == -1 { + gst_element_error!(element, gst::CoreError::Negotiation, ["Stream name not found"]); + // return false; + return 0; + } - let mut no_source: isize = -1; - for i in 0..total_sources as isize{ - if (CStr::from_ptr((*p_sources.offset(i)).p_ndi_name).to_string_lossy().into_owned() == stream_name || - CStr::from_ptr((*p_sources.offset(i)).p_ip_address).to_string_lossy().into_owned() == ip){ - no_source = i; - break; - } - } - if no_source == -1 { - gst_element_error!(element, gst::CoreError::Negotiation, ["Stream name not found"]); - // return false; - return 0; - } + gst_debug!(cat, obj: element, "Total sources in network {}: Connecting to NDI source with name '{}' and address '{}'", total_sources, + CStr::from_ptr((*p_sources.offset(no_source)).p_ndi_name) + .to_string_lossy() + .into_owned(), + CStr::from_ptr((*p_sources.offset(no_source)).p_ip_address) + .to_string_lossy() + .into_owned()); - gst_debug!(cat, obj: element, "Total sources in network {}: Connecting to NDI source with name '{}' and address '{}'", total_sources, - CStr::from_ptr((*p_sources.offset(no_source)).p_ndi_name) - .to_string_lossy() - .into_owned(), - CStr::from_ptr((*p_sources.offset(no_source)).p_ip_address) - .to_string_lossy() - .into_owned()); - - source = *p_sources.offset(no_source).clone(); + let source = *p_sources.offset(no_source).clone(); let source_ip = CStr::from_ptr(source.p_ip_address).to_string_lossy().into_owned(); let source_name = CStr::from_ptr(source.p_ndi_name).to_string_lossy().into_owned(); @@ -228,7 +214,7 @@ fn stop_ndi(cat: gst::DebugCategory , element: &BaseSrc, id: i8) -> bool{ { let val = receivers.get_mut(&id).unwrap(); if val.video && val.audio{ - if (element.get_name().contains("audiosrc")){ + if element.get_name().contains("audiosrc"){ val.audio = false; } else{ diff --git a/gst-plugin-ndi/src/ndiaudiosrc.rs b/gst-plugin-ndi/src/ndiaudiosrc.rs index 0dbb7b2c..07f907b5 100644 --- a/gst-plugin-ndi/src/ndiaudiosrc.rs +++ b/gst-plugin-ndi/src/ndiaudiosrc.rs @@ -18,7 +18,6 @@ use std::ptr; use ndilib::*; use connect_ndi; -// use ndi_struct; use stop_ndi; use hashmap_receivers; @@ -302,7 +301,7 @@ impl NdiAudioSrc { //let settings = *self.settings.lock().unwrap(); let state = self.state.lock().unwrap(); - if let Some(ref info) = state.info { + if let Some(ref _info) = state.info { // let latency = gst::SECOND // .mul_div_floor(settings.samples_per_buffer as u64, info.rate() as u64) // .unwrap(); @@ -311,10 +310,9 @@ impl NdiAudioSrc { // .mul_div_floor(1 as u64, 30 as u64) // .unwrap(); // gst_debug!(self.cat, obj: element, "Returning latency {}", latency); - println!("/*/a*f/a*sd/f*ad/sf*ad/sf*ad/sf"); let max = latency * 1843200; - println!("{:?}", latency); - println!("{:?}",max); + // println!("{:?}", latency); + // println!("{:?}",max); q.set(true, latency, max); return true; } else { @@ -334,7 +332,7 @@ impl NdiAudioSrc { let recv = &receivers.get(&settings.id_receiver).unwrap().ndi_instance; let pNDI_recv = recv.recv; - + let mut timestamp_data = self.timestamp_data.lock().unwrap(); let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); @@ -344,7 +342,6 @@ impl NdiAudioSrc { frame_type = NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000); } - //ndi_struct.start_pts = audio_frame.timecode as u64; timestamp_data.pts = audio_frame.timecode as u64; let mut caps = gst::Caps::truncate(caps); @@ -387,14 +384,7 @@ impl NdiAudioSrc { unsafe{ let receivers = hashmap_receivers.lock().unwrap(); - let mut id = &_settings.stream_name; - - if (&_settings.ip != ""){ - id = &_settings.ip; - } - let recv = &receivers.get(&_settings.id_receiver).unwrap().ndi_instance; - let pNDI_recv = recv.recv; let pts: u64; @@ -402,7 +392,6 @@ impl NdiAudioSrc { NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000,); pts = (audio_frame.timecode as u64) - timestamp_data.pts; - //pts = (audio_frame.timecode as u64) - ndi_struct.start_pts; let buff_size = ((audio_frame.channel_stride_in_bytes)) as usize; let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); diff --git a/gst-plugin-ndi/src/ndivideosrc.rs b/gst-plugin-ndi/src/ndivideosrc.rs index 543b0efb..63d17092 100644 --- a/gst-plugin-ndi/src/ndivideosrc.rs +++ b/gst-plugin-ndi/src/ndivideosrc.rs @@ -19,7 +19,6 @@ use std::ptr; use ndilib::*; use connect_ndi; -// use ndi_struct; use stop_ndi; use hashmap_receivers; @@ -309,7 +308,7 @@ impl NdiVideoSrc { //let settings = *self.settings.lock().unwrap(); let state = self.state.lock().unwrap(); - if let Some(ref info) = state.info { + if let Some(ref _info) = state.info { // let latency = gst::SECOND // .mul_div_floor(settings.samples_per_buffer as u64, info.rate() as u64) // .unwrap(); @@ -318,10 +317,9 @@ impl NdiVideoSrc { // .mul_div_floor(1 as u64, 30 as u64) // .unwrap(); // gst_debug!(self.cat, obj: element, "Returning latency {}", latency); - println!("/*/a*f/a*sd/f*ad/sf*ad/sf*ad/sf"); let max = latency * 1843200; - println!("{:?}", latency); - println!("{:?}",max); + // println!("{:?}", latency); + // println!("{:?}",max); q.set(true, latency, max); return true; } else { @@ -353,7 +351,6 @@ impl NdiVideoSrc { //TODO Check that this is working timestamp_data.pts = video_frame.timecode as u64; - //ndi_struct.start_pts = video_frame.timecode as u64; let mut caps = gst::Caps::truncate(caps); { @@ -403,7 +400,6 @@ impl NdiVideoSrc { NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000,); pts = (video_frame.timecode as u64) - timestamp_data.pts; - //pts = (video_frame.timecode as u64) - ndi_struct.start_pts; let buff_size = (video_frame.yres * video_frame.line_stride_in_bytes) as usize; //println!("{:?}", buff_size); From 4338068b3c8fd41b0ed59bcae8ca9b5f7d2ca6bf Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Mon, 20 Aug 2018 16:59:12 +0200 Subject: [PATCH 057/199] Use same start_pts for audio and video in the same ndi receiver --- gst-plugin-ndi/src/lib.rs | 3 ++- gst-plugin-ndi/src/ndiaudiosrc.rs | 16 ++++++++-------- gst-plugin-ndi/src/ndivideosrc.rs | 16 ++++++++-------- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/gst-plugin-ndi/src/lib.rs b/gst-plugin-ndi/src/lib.rs index 4f7f1e1b..7b51c53a 100644 --- a/gst-plugin-ndi/src/lib.rs +++ b/gst-plugin-ndi/src/lib.rs @@ -52,6 +52,7 @@ struct ndi_receiver_info{ audio: bool, ndi_instance: NdiInstance, id: i8, + timestamp: u64, } lazy_static! { @@ -193,7 +194,7 @@ fn connect_ndi(cat: gst::DebugCategory , element: &BaseSrc, ip: String, stream NDIlib_recv_send_metadata(pNDI_recv, &enable_hw_accel); - receivers.insert(id_receiver, ndi_receiver_info{stream_name: source_name.clone(), ip: source_ip.clone(), video:video, audio: audio, ndi_instance: NdiInstance{recv: pNDI_recv}, id: id_receiver}); + receivers.insert(id_receiver, ndi_receiver_info{stream_name: source_name.clone(), ip: source_ip.clone(), video:video, audio: audio, ndi_instance: NdiInstance{recv: pNDI_recv}, id: id_receiver, timestamp: 0}); // let start = SystemTime::now(); // let since_the_epoch = start.duration_since(UNIX_EPOCH) diff --git a/gst-plugin-ndi/src/ndiaudiosrc.rs b/gst-plugin-ndi/src/ndiaudiosrc.rs index 07f907b5..897b4484 100644 --- a/gst-plugin-ndi/src/ndiaudiosrc.rs +++ b/gst-plugin-ndi/src/ndiaudiosrc.rs @@ -73,7 +73,6 @@ impl Default for State { } struct TimestampData{ - pts: u64, offset: u64, } @@ -102,7 +101,6 @@ impl NdiAudioSrc { settings: Mutex::new(Default::default()), state: Mutex::new(Default::default()), timestamp_data: Mutex::new(TimestampData{ - pts: 0, offset: 0, }), }) @@ -327,14 +325,13 @@ impl NdiAudioSrc { fn fixate(&self, element: &BaseSrc, caps: gst::Caps) -> gst::Caps { //We need to set the correct caps resolution and framerate unsafe{ - let receivers = hashmap_receivers.lock().unwrap(); + let mut receivers = hashmap_receivers.lock().unwrap(); let settings = self.settings.lock().unwrap(); - let recv = &receivers.get(&settings.id_receiver).unwrap().ndi_instance; + let receiver = receivers.get_mut(&settings.id_receiver).unwrap(); + let recv = &receiver.ndi_instance; let pNDI_recv = recv.recv; - let mut timestamp_data = self.timestamp_data.lock().unwrap(); - let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); let mut frame_type: NDIlib_frame_type_e = NDIlib_frame_type_e::NDIlib_frame_type_none; @@ -342,7 +339,9 @@ impl NdiAudioSrc { frame_type = NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000); } - timestamp_data.pts = audio_frame.timecode as u64; + if receiver.timestamp >= audio_frame.timecode as u64 || receiver.timestamp == 0{ + receiver.timestamp = audio_frame.timecode as u64; + } let mut caps = gst::Caps::truncate(caps); { @@ -391,7 +390,8 @@ impl NdiAudioSrc { let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000,); - pts = (audio_frame.timecode as u64) - timestamp_data.pts; + let time = &receivers.get(&_settings.id_receiver).unwrap().timestamp; + pts = audio_frame.timecode as u64- time; let buff_size = ((audio_frame.channel_stride_in_bytes)) as usize; let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); diff --git a/gst-plugin-ndi/src/ndivideosrc.rs b/gst-plugin-ndi/src/ndivideosrc.rs index 63d17092..49e5189e 100644 --- a/gst-plugin-ndi/src/ndivideosrc.rs +++ b/gst-plugin-ndi/src/ndivideosrc.rs @@ -74,7 +74,6 @@ impl Default for State { } struct TimestampData{ - pts: u64, offset: u64, } @@ -103,7 +102,6 @@ impl NdiVideoSrc { settings: Mutex::new(Default::default()), state: Mutex::new(Default::default()), timestamp_data: Mutex::new(TimestampData{ - pts: 0, offset: 0, }), }) @@ -334,14 +332,13 @@ impl NdiVideoSrc { fn fixate(&self, element: &BaseSrc, caps: gst::Caps) -> gst::Caps { //We need to set the correct caps resolution and framerate unsafe{ - let receivers = hashmap_receivers.lock().unwrap(); + let mut receivers = hashmap_receivers.lock().unwrap(); let settings = self.settings.lock().unwrap(); - let recv = &receivers.get(&settings.id_receiver).unwrap().ndi_instance; + let receiver = receivers.get_mut(&settings.id_receiver).unwrap(); + let recv = &receiver.ndi_instance; let pNDI_recv = recv.recv; - let mut timestamp_data = self.timestamp_data.lock().unwrap(); - let video_frame: NDIlib_video_frame_v2_t = Default::default(); let mut frame_type: NDIlib_frame_type_e = NDIlib_frame_type_e::NDIlib_frame_type_none; @@ -350,7 +347,9 @@ impl NdiVideoSrc { } //TODO Check that this is working - timestamp_data.pts = video_frame.timecode as u64; + if receiver.timestamp >= video_frame.timecode as u64 || receiver.timestamp == 0{ + receiver.timestamp = video_frame.timecode as u64; + } let mut caps = gst::Caps::truncate(caps); { @@ -399,7 +398,8 @@ impl NdiVideoSrc { let video_frame: NDIlib_video_frame_v2_t = Default::default(); NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000,); - pts = (video_frame.timecode as u64) - timestamp_data.pts; + let time = &receivers.get(&_settings.id_receiver).unwrap().timestamp; + pts = video_frame.timecode as u64 - time; let buff_size = (video_frame.yres * video_frame.line_stride_in_bytes) as usize; //println!("{:?}", buff_size); From aaaa593a7296c1eda6231b73d7a4a5644ca19036 Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Fri, 24 Aug 2018 18:00:02 +0200 Subject: [PATCH 058/199] Refactor timestamp and pts logic --- gst-plugin-ndi/src/lib.rs | 16 +++- gst-plugin-ndi/src/ndiaudiosrc.rs | 114 +++++++++++++---------- gst-plugin-ndi/src/ndivideosrc.rs | 144 +++++++++++++++++------------- 3 files changed, 162 insertions(+), 112 deletions(-) diff --git a/gst-plugin-ndi/src/lib.rs b/gst-plugin-ndi/src/lib.rs index 7b51c53a..7990466f 100644 --- a/gst-plugin-ndi/src/lib.rs +++ b/gst-plugin-ndi/src/lib.rs @@ -52,9 +52,18 @@ struct ndi_receiver_info{ audio: bool, ndi_instance: NdiInstance, id: i8, - timestamp: u64, } +struct Ndi{ + initial_timestamp: u64, + start_pts: gst::ClockTime, +} + +static mut ndi_struct: Ndi = Ndi{ + initial_timestamp: 0, + start_pts: gst::ClockTime(Some(0)), +}; + lazy_static! { static ref hashmap_receivers: Mutex> = { let m = HashMap::new(); @@ -139,7 +148,7 @@ fn connect_ndi(cat: gst::DebugCategory , element: &BaseSrc, ip: String, stream } } if no_source == -1 { - gst_element_error!(element, gst::CoreError::Negotiation, ["Stream name not found"]); + gst_element_error!(element, gst::ResourceError::OpenRead, ["Stream not found"]); // return false; return 0; } @@ -194,7 +203,7 @@ fn connect_ndi(cat: gst::DebugCategory , element: &BaseSrc, ip: String, stream NDIlib_recv_send_metadata(pNDI_recv, &enable_hw_accel); - receivers.insert(id_receiver, ndi_receiver_info{stream_name: source_name.clone(), ip: source_ip.clone(), video:video, audio: audio, ndi_instance: NdiInstance{recv: pNDI_recv}, id: id_receiver, timestamp: 0}); + receivers.insert(id_receiver, ndi_receiver_info{stream_name: source_name.clone(), ip: source_ip.clone(), video:video, audio: audio, ndi_instance: NdiInstance{recv: pNDI_recv}, id: id_receiver}); // let start = SystemTime::now(); // let since_the_epoch = start.duration_since(UNIX_EPOCH) @@ -211,7 +220,6 @@ fn stop_ndi(cat: gst::DebugCategory , element: &BaseSrc, id: i8) -> bool{ gst_debug!(cat, obj: element, "Closing NDI connection..."); unsafe{ let mut receivers = hashmap_receivers.lock().unwrap(); - { let val = receivers.get_mut(&id).unwrap(); if val.video && val.audio{ diff --git a/gst-plugin-ndi/src/ndiaudiosrc.rs b/gst-plugin-ndi/src/ndiaudiosrc.rs index 897b4484..cba58f6a 100644 --- a/gst-plugin-ndi/src/ndiaudiosrc.rs +++ b/gst-plugin-ndi/src/ndiaudiosrc.rs @@ -19,6 +19,7 @@ use std::ptr; use ndilib::*; use connect_ndi; use stop_ndi; +use ndi_struct; use hashmap_receivers; @@ -281,46 +282,46 @@ impl NdiAudioSrc { true } - fn query(&self, element: &BaseSrc, query: &mut gst::QueryRef) -> bool { - use gst::QueryView; - - match query.view_mut() { - // We only work in Push mode. In Pull mode, create() could be called with - // arbitrary offsets and we would have to produce for that specific offset - QueryView::Scheduling(ref mut q) => { - q.set(gst::SchedulingFlags::SEQUENTIAL, 1, -1, 0); - q.add_scheduling_modes(&[gst::PadMode::Push]); - return true; - } - // In Live mode we will have a latency equal to the number of samples in each buffer. - // We can't output samples before they were produced, and the last sample of a buffer - // is produced that much after the beginning, leading to this latency calculation - QueryView::Latency(ref mut q) => { - //let settings = *self.settings.lock().unwrap(); - let state = self.state.lock().unwrap(); - - if let Some(ref _info) = state.info { - // let latency = gst::SECOND - // .mul_div_floor(settings.samples_per_buffer as u64, info.rate() as u64) - // .unwrap(); - let latency = gst::SECOND.mul_div_floor(3 as u64, 2 as u64).unwrap(); - // let latency = gst::SECOND - // .mul_div_floor(1 as u64, 30 as u64) - // .unwrap(); - // gst_debug!(self.cat, obj: element, "Returning latency {}", latency); - let max = latency * 1843200; - // println!("{:?}", latency); - // println!("{:?}",max); - q.set(true, latency, max); - return true; - } else { - return false; - } - } - _ => (), - } - BaseSrcBase::parent_query(element, query) - } + // fn query(&self, element: &BaseSrc, query: &mut gst::QueryRef) -> bool { + // use gst::QueryView; + // + // match query.view_mut() { + // // We only work in Push mode. In Pull mode, create() could be called with + // // arbitrary offsets and we would have to produce for that specific offset + // QueryView::Scheduling(ref mut q) => { + // q.set(gst::SchedulingFlags::SEQUENTIAL, 1, -1, 0); + // q.add_scheduling_modes(&[gst::PadMode::Push]); + // return true; + // } + // // In Live mode we will have a latency equal to the number of samples in each buffer. + // // We can't output samples before they were produced, and the last sample of a buffer + // // is produced that much after the beginning, leading to this latency calculation + // QueryView::Latency(ref mut q) => { + // let settings = &*self.settings.lock().unwrap(); + // let state = self.state.lock().unwrap(); + // + // if let Some(ref _info) = state.info { + // // let latency = gst::SECOND + // // .mul_div_floor(1024 as u64, _info.rate() as u64) + // // .unwrap(); + // let latency = gst::SECOND.mul_div_floor(3 as u64, 2 as u64).unwrap(); + // // let latency = gst::SECOND + // // .mul_div_floor(1 as u64, 30 as u64) + // // .unwrap(); + // // gst_debug!(self.cat, obj: element, "Returning latency {}", latency); + // let max = latency * 1843200; + // // println!("{:?}", latency); + // // println!("{:?}",max); + // q.set(true, latency, max); + // return true; + // } else { + // return false; + // } + // } + // _ => (), + // } + // BaseSrcBase::parent_query(element, query) + // } fn fixate(&self, element: &BaseSrc, caps: gst::Caps) -> gst::Caps { //We need to set the correct caps resolution and framerate @@ -339,8 +340,8 @@ impl NdiAudioSrc { frame_type = NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000); } - if receiver.timestamp >= audio_frame.timecode as u64 || receiver.timestamp == 0{ - receiver.timestamp = audio_frame.timecode as u64; + if ndi_struct.initial_timestamp <= audio_frame.timestamp as u64 || ndi_struct.initial_timestamp == 0{ + ndi_struct.initial_timestamp = audio_frame.timestamp as u64; } let mut caps = gst::Caps::truncate(caps); @@ -388,20 +389,36 @@ impl NdiAudioSrc { let pts: u64; let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); - NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000,); - let time = &receivers.get(&_settings.id_receiver).unwrap().timestamp; - pts = audio_frame.timecode as u64- time; + let time = ndi_struct.initial_timestamp; + + let mut skip_frame = true; + while skip_frame { + NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000,); + if time >= (audio_frame.timestamp as u64){ + gst_debug!(self.cat, obj: element, "Frame timestamp ({:?}) is lower than received in the first frame from NDI ({:?}), so skiping...", (audio_frame.timestamp as u64), time); + } + else{ + skip_frame = false; + } + } + + pts = audio_frame.timestamp as u64 - time; let buff_size = ((audio_frame.channel_stride_in_bytes)) as usize; let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); { let vec = Vec::from_raw_parts(audio_frame.p_data as *mut u8, buff_size, buff_size); - //TODO Set pts, duration and other info about the buffer let pts: gst::ClockTime = (pts * 100).into(); - let duration: gst::ClockTime = (((audio_frame.no_samples as f64 / audio_frame.sample_rate as f64) * 10000000.0) as u64).into(); + + let duration: gst::ClockTime = (((audio_frame.no_samples as f64 / audio_frame.sample_rate as f64) * 1000000000.0) as u64).into(); let buffer = buffer.get_mut().unwrap(); - buffer.set_pts(pts); + + if ndi_struct.start_pts == gst::ClockTime(Some(0)){ + ndi_struct.start_pts = element.get_clock().unwrap().get_time() - element.get_base_time(); + } + + buffer.set_pts(pts + ndi_struct.start_pts); buffer.set_duration(duration); buffer.set_offset(timestamp_data.offset); buffer.set_offset_end(timestamp_data.offset + 1); @@ -410,6 +427,7 @@ impl NdiAudioSrc { } gst_debug!(self.cat, obj: element, "Produced buffer {:?}", buffer); + Ok(buffer) } } diff --git a/gst-plugin-ndi/src/ndivideosrc.rs b/gst-plugin-ndi/src/ndivideosrc.rs index 49e5189e..910b74a6 100644 --- a/gst-plugin-ndi/src/ndivideosrc.rs +++ b/gst-plugin-ndi/src/ndivideosrc.rs @@ -20,6 +20,7 @@ use std::ptr; use ndilib::*; use connect_ndi; use stop_ndi; +use ndi_struct; use hashmap_receivers; @@ -29,6 +30,7 @@ struct Settings { stream_name: String, ip: String, id_receiver: i8, + latency: u64, } impl Default for Settings { @@ -37,6 +39,7 @@ impl Default for Settings { stream_name: String::from("Fixed ndi stream name"), ip: String::from(""), id_receiver: 0, + latency: 0, } } } @@ -190,8 +193,8 @@ impl NdiVideoSrc { settings.stream_name = stream_name; drop(settings); - let _ = - element.post_message(&gst::Message::new_latency().src(Some(&element)).build()); + // let _ = + // element.post_message(&gst::Message::new_latency().src(Some(&element)).build()); }, Property::String("ip", ..) => { let mut settings = self.settings.lock().unwrap(); @@ -206,8 +209,8 @@ impl NdiVideoSrc { settings.ip = ip; drop(settings); - let _ = - element.post_message(&gst::Message::new_latency().src(Some(&element)).build()); + // let _ = + // element.post_message(&gst::Message::new_latency().src(Some(&element)).build()); } _ => unimplemented!(), } @@ -245,18 +248,16 @@ impl NdiVideoSrc { // We simply remember the resulting AudioInfo from the caps to be able to use this for knowing // the sample rate, etc. when creating buffers fn set_caps(&self, element: &BaseSrc, caps: &gst::CapsRef) -> bool { - let info = match gst_video::VideoInfo::from_caps(caps) { None => return false, Some(info) => info, }; - gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); // TODO Puede que falle si no creamos la estructura de cero, pero si lo hacemos no podemos poner recv a none let mut state = self.state.lock().unwrap(); state.info = Some(info); - + let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); true } @@ -264,13 +265,15 @@ impl NdiVideoSrc { fn start(&self, element: &BaseSrc) -> bool { // Reset state *self.state.lock().unwrap() = Default::default(); - let mut settings = self.settings.lock().unwrap(); settings.id_receiver = connect_ndi(self.cat, element, settings.ip.clone(), settings.stream_name.clone()); + if settings.id_receiver == 0{ + gst_error!(self.cat, obj: element, "Stream not found"); return false; } else{ + // let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); return true; } } @@ -288,46 +291,54 @@ impl NdiVideoSrc { } - fn query(&self, element: &BaseSrc, query: &mut gst::QueryRef) -> bool { - use gst::QueryView; - - match query.view_mut() { - // We only work in Push mode. In Pull mode, create() could be called with - // arbitrary offsets and we would have to produce for that specific offset - QueryView::Scheduling(ref mut q) => { - q.set(gst::SchedulingFlags::SEQUENTIAL, 1, -1, 0); - q.add_scheduling_modes(&[gst::PadMode::Push]); - return true; - } - // In Live mode we will have a latency equal to the number of samples in each buffer. - // We can't output samples before they were produced, and the last sample of a buffer - // is produced that much after the beginning, leading to this latency calculation - QueryView::Latency(ref mut q) => { - //let settings = *self.settings.lock().unwrap(); - let state = self.state.lock().unwrap(); - - if let Some(ref _info) = state.info { - // let latency = gst::SECOND - // .mul_div_floor(settings.samples_per_buffer as u64, info.rate() as u64) - // .unwrap(); - let latency = gst::SECOND.mul_div_floor(3 as u64, 2 as u64).unwrap(); - // let latency = gst::SECOND - // .mul_div_floor(1 as u64, 30 as u64) - // .unwrap(); - // gst_debug!(self.cat, obj: element, "Returning latency {}", latency); - let max = latency * 1843200; - // println!("{:?}", latency); - // println!("{:?}",max); - q.set(true, latency, max); - return true; - } else { - return false; - } - } - _ => (), - } - BaseSrcBase::parent_query(element, query) - } + // fn query(&self, element: &BaseSrc, query: &mut gst::QueryRef) -> bool { + // use gst::QueryView; + // println!("****************************************Dentro de query"); + // match query.view_mut() { + // // We only work in Push mode. In Pull mode, create() could be called with + // // arbitrary offsets and we would have to produce for that specific offset + // QueryView::Scheduling(ref mut q) => { + // q.set(gst::SchedulingFlags::SEQUENTIAL, 1, -1, 0); + // q.add_scheduling_modes(&[gst::PadMode::Push]); + // return true; + // } + // // In Live mode we will have a latency equal to the number of samples in each buffer. + // // We can't output samples before they were produced, and the last sample of a buffer + // // is produced that much after the beginning, leading to this latency calculation + // QueryView::Latency(ref mut q) => { + // let settings = self.settings.lock().unwrap(); + // let state = self.state.lock().unwrap(); + // println!("Dentro de query"); + // + // if let Some(ref _info) = state.info { + // // let latency = gst::SECOND + // // .mul_div_floor(settings.samples_per_buffer as u64, info.rate() as u64) + // // .unwrap(); + // let latency = gst::SECOND.mul_div_floor(3 as u64, 2 as u64).unwrap(); + // let mut latency = gst::SECOND.mul_div_floor(settings.latency, 1000).unwrap(); + // // if settings.latency > 2000{ + // // println!("{:?}", element.get_name()); + // // latency = gst::SECOND * 0; + // // } + // let latency = gst::SECOND * 0; + // // .mul_div_floor(1 as u64, 30 as u64) + // // .unwrap(); + // // gst_debug!(self.cat, obj: element, "Returning latency {}", latency); + // let max = gst::SECOND * 120 * 1843200; + // // println!("{:?}", latency2); + // println!("{:?}", latency); + // println!("{:?}", (settings.latency / 1000)); + // // println!("{:?}",max); + // q.set(true, latency, max); + // return true; + // } else { + // return false; + // } + // } + // _ => (), + // } + // BaseSrcBase::parent_query(element, query) + // } fn fixate(&self, element: &BaseSrc, caps: gst::Caps) -> gst::Caps { //We need to set the correct caps resolution and framerate @@ -346,9 +357,8 @@ impl NdiVideoSrc { frame_type = NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000); } - //TODO Check that this is working - if receiver.timestamp >= video_frame.timecode as u64 || receiver.timestamp == 0{ - receiver.timestamp = video_frame.timecode as u64; + if ndi_struct.initial_timestamp <= video_frame.timestamp as u64 || ndi_struct.initial_timestamp == 0{ + ndi_struct.initial_timestamp = video_frame.timestamp as u64; } let mut caps = gst::Caps::truncate(caps); @@ -362,6 +372,8 @@ impl NdiVideoSrc { // Let BaseSrc fixate anything else for us. We could've alternatively have // called Caps::fixate() here + + let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); element.parent_fixate(caps) } } @@ -396,33 +408,45 @@ impl NdiVideoSrc { let pts: u64; let video_frame: NDIlib_video_frame_v2_t = Default::default(); - NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000,); - let time = &receivers.get(&_settings.id_receiver).unwrap().timestamp; - pts = video_frame.timecode as u64 - time; + let time = ndi_struct.initial_timestamp; + + let mut skip_frame = true; + while skip_frame { + NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000,); + if time >= (video_frame.timestamp as u64){ + gst_debug!(self.cat, obj: element, "Frame timestamp ({:?}) is lower than received in the first frame from NDI ({:?}), so skiping...", (video_frame.timestamp as u64), time); + } + else{ + skip_frame = false; + } + } + + pts = video_frame.timestamp as u64 - time; let buff_size = (video_frame.yres * video_frame.line_stride_in_bytes) as usize; - //println!("{:?}", buff_size); let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); { let vec = Vec::from_raw_parts(video_frame.p_data as *mut u8, buff_size, buff_size); let pts: gst::ClockTime = (pts * 100).into(); - // println!("{:?}", video_frame.line_stride_in_bytes); - //println!("{:?}", video_frame); - //TODO get duration let duration: gst::ClockTime = (((video_frame.frame_rate_D as f64 / video_frame.frame_rate_N as f64) * 1000000000.0) as u64).into(); let buffer = buffer.get_mut().unwrap(); - buffer.set_pts(pts); + + if ndi_struct.start_pts == gst::ClockTime(Some(0)){ + ndi_struct.start_pts = element.get_clock().unwrap().get_time() - element.get_base_time(); + } + + buffer.set_pts(pts + ndi_struct.start_pts); buffer.set_duration(duration); buffer.set_offset(timestamp_data.offset); buffer.set_offset_end(timestamp_data.offset + 1); timestamp_data.offset = timestamp_data.offset + 1; buffer.copy_from_slice(0, &vec).unwrap(); - } gst_debug!(self.cat, obj: element, "Produced buffer {:?}", buffer); + Ok(buffer) } } From 85d0c75f9b9b1f15dee8c8dfc8f0882bab9a917a Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Mon, 3 Sep 2018 16:31:32 +0200 Subject: [PATCH 059/199] Fix error when creating new NDI receivers --- gst-plugin-ndi/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gst-plugin-ndi/src/lib.rs b/gst-plugin-ndi/src/lib.rs index 7990466f..58a7c271 100644 --- a/gst-plugin-ndi/src/lib.rs +++ b/gst-plugin-ndi/src/lib.rs @@ -80,7 +80,6 @@ fn connect_ndi(cat: gst::DebugCategory , element: &BaseSrc, ip: String, stream let mut receivers = hashmap_receivers.lock().unwrap(); let mut audio = false; let mut video = false; - id_receiver += 1; //FIXME Search for another way to know if the source is an audio or a video source if element.get_name().contains("audiosrc"){ @@ -94,7 +93,7 @@ fn connect_ndi(cat: gst::DebugCategory , element: &BaseSrc, ip: String, stream for val in receivers.values_mut(){ if val.ip == ip || val.stream_name == stream_name{ if (val.audio && val.video) || (val.audio && audio) || (val.video && video){ - break; + continue; } else { if video { @@ -203,6 +202,7 @@ fn connect_ndi(cat: gst::DebugCategory , element: &BaseSrc, ip: String, stream NDIlib_recv_send_metadata(pNDI_recv, &enable_hw_accel); + id_receiver += 1; receivers.insert(id_receiver, ndi_receiver_info{stream_name: source_name.clone(), ip: source_ip.clone(), video:video, audio: audio, ndi_instance: NdiInstance{recv: pNDI_recv}, id: id_receiver}); // let start = SystemTime::now(); From 2864ef4d9994a4876e1c4da45d9fa88927a3fd24 Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Tue, 4 Sep 2018 16:32:01 +0200 Subject: [PATCH 060/199] Updated README --- gst-plugin-ndi/README.md | 47 +++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/gst-plugin-ndi/README.md b/gst-plugin-ndi/README.md index 24a8628b..8d76182e 100644 --- a/gst-plugin-ndi/README.md +++ b/gst-plugin-ndi/README.md @@ -1,42 +1,49 @@ -TODO -==== +GStreamer NDI Plugin +==================== -See: +*Compiled and tested with Ubuntu 16.04.5 and GStreamer 1.8.3* -https://coaxion.net/blog/2018/01/how-to-write-gstreamer-elements-in-rust-part-1-a-video-filter-for-converting-rgb-to-grayscale/ -https://coaxion.net/blog/2018/02/how-to-write-gstreamer-elements-in-rust-part-2-a-raw-audio-sine-wave-source/ - -Before cargo build install: +Before compile the element it's necessary install Rust, NDI SDK and the following packages for gstreamer: ``` -$ apt-get install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \ +apt-get install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \ gstreamer1.0-plugins-base gstreamer1.0-plugins-good \ gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly \ gstreamer1.0-libav libgstrtspserver-1.0-dev ``` - -Test +Compile NDI element and basic pipelines ------- ``` cargo build export GST_PLUGIN_PATH=`pwd`/target/debug -gst-inspect-1.0 ndisrc -GST_DEBUG=3 gst-launch-1.0 ndisrc ! video/x-raw, format=UYVY, width=720, height=576, framerate=25/1 ! videoconvert ! autovideosink -GST_DEBUG=3 gst-launch-1.0 -v ndisrc stream-name="GC-DEV2 (Nombre_del_stream)" ! video/x-raw, format=UYVY, width=720, height=576, framerate=25/1 ! xvimagesink sync=false +gst-inspect-1.0 ndi +gst-inspect-1.0 ndivideosrc +gst-inspect-1.0 ndiaudiosrc + +gst-launch-1.0 ndivideosrc stream-name="GC-DEV2 (OBS)" ! autovideosink +gst-launch-1.0 ndiaudiosrc stream-name="GC-DEV2 (OBS)" ! autoaudiosink + +gst-launch-1.0 ndivideosrc stream-name="GC-DEV2 (OBS)" ! autovideosink ndiaudiosrc stream-name="GC-DEV2 (OBS)" ! autoaudiosink + ``` + +Debug pipelines: ``` -GST_DEBUG=3 gst-launch-1.0 -v ndisrc ip=10.21.10.103:5961 ! video/x-raw, format=UYVY, width=1920, height=1080, framerate=25/1 ! autovideosink sync=false - -or - -GST_DEBUG=3 gst-launch-1.0 -v ndisrc stream-name="MINI-DE-TELTEK.OFICINA.TELTEK.ES (NDI Signal Generator)" ! video/x-raw, format=UYVY, width=1920, height=1080, framerate=25/1 ! autovideosink sync=false - -gst-launch-1.0 -v ndivideosrc name=gc-ndi-src stream-name="GC-DEV2 (OBS)" ! autovideosink ts-offset=1000000000 +#Check if the timestamps are correct gst-launch-1.0 -v ndivideosrc name=gc-ndi-src stream-name="GC-DEV2 (OBS)" ! fakesink silent=false + +#Debug sink to check if jitter is correct GST_DEBUG=*basesink*:5 gst-launch-1.0 -v ndivideosrc name=gc-ndi-src stream-name="GC-DEV2 (OBS)" ! autovideosink +#Add latency when launching the pipeline +gst-launch-1.0 -v ndivideosrc name=gc-ndi-src stream-name="GC-DEV2 (OBS)" ! autovideosink ts-offset=1000000000 ``` + +More info about GStreamer plugins and Rust: +---------------------------------- +https://coaxion.net/blog/2018/01/how-to-write-gstreamer-elements-in-rust-part-1-a-video-filter-for-converting-rgb-to-grayscale/ +https://coaxion.net/blog/2018/02/how-to-write-gstreamer-elements-in-rust-part-2-a-raw-audio-sine-wave-source/ From 79fc47b41f7ec8c117748e79e38dbd0c04fec914 Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Tue, 11 Sep 2018 09:47:17 +0200 Subject: [PATCH 061/199] Updated gstreamer rust bindings to version 0.12 and gstreamer plugin to 0.3 --- gst-plugin-ndi/Cargo.toml | 15 +++++++-------- gst-plugin-ndi/src/lib.rs | 5 ++--- gst-plugin-ndi/src/ndiaudiosrc.rs | 3 +-- gst-plugin-ndi/src/ndivideosrc.rs | 3 +-- 4 files changed, 11 insertions(+), 15 deletions(-) diff --git a/gst-plugin-ndi/Cargo.toml b/gst-plugin-ndi/Cargo.toml index e4d6caec..e79d6760 100644 --- a/gst-plugin-ndi/Cargo.toml +++ b/gst-plugin-ndi/Cargo.toml @@ -6,15 +6,14 @@ repository = "https://gitlab.teltek.es/rubenrua/ndi-rs.git" license = "unknow" # TODO MIT/Apache-2.0 [dependencies] -gst-plugin = "0.2" -glib = "0.5" -gstreamer = "0.11" -gstreamer-base = "0.11" -gstreamer-video = "0.11" -gstreamer-audio = "0.11" +gobject-subclass = "0.2" +gst-plugin = "0.3" +glib = "0.6" +gstreamer = "0.12" +gstreamer-base = "0.12" +gstreamer-video = "0.12" +gstreamer-audio = "0.12" lazy_static = "1.1.0" -byte-slice-cast = "0.1" # TODO delete -num-traits = "0.2" # TODO delete [lib] name = "gstndi" diff --git a/gst-plugin-ndi/src/lib.rs b/gst-plugin-ndi/src/lib.rs index 58a7c271..c55e2726 100644 --- a/gst-plugin-ndi/src/lib.rs +++ b/gst-plugin-ndi/src/lib.rs @@ -9,6 +9,8 @@ // except according to those terms. extern crate glib; +extern crate gobject_subclass; + #[macro_use] extern crate gst_plugin; #[macro_use] @@ -17,8 +19,6 @@ extern crate gstreamer_audio as gst_audio; extern crate gstreamer_base as gst_base; extern crate gstreamer_video as gst_video; -extern crate byte_slice_cast; -extern crate num_traits; #[macro_use] extern crate lazy_static; @@ -27,7 +27,6 @@ mod ndiaudiosrc; pub mod ndilib; use std::{thread, time}; -//use std::time::{SystemTime, UNIX_EPOCH}; use std::ffi::{CStr, CString}; use ndilib::*; use gst_plugin::base_src::*; diff --git a/gst-plugin-ndi/src/ndiaudiosrc.rs b/gst-plugin-ndi/src/ndiaudiosrc.rs index cba58f6a..28356f4f 100644 --- a/gst-plugin-ndi/src/ndiaudiosrc.rs +++ b/gst-plugin-ndi/src/ndiaudiosrc.rs @@ -8,8 +8,7 @@ use gst_base::prelude::*; use gst_plugin::base_src::*; use gst_plugin::element::*; -use gst_plugin::object::*; -use gst_plugin::properties::*; +use gobject_subclass::object::*; use std::sync::Mutex; use std::{i32, u32}; diff --git a/gst-plugin-ndi/src/ndivideosrc.rs b/gst-plugin-ndi/src/ndivideosrc.rs index 910b74a6..a273eefb 100644 --- a/gst-plugin-ndi/src/ndivideosrc.rs +++ b/gst-plugin-ndi/src/ndivideosrc.rs @@ -9,8 +9,7 @@ use gst::Fraction; use gst_plugin::base_src::*; use gst_plugin::element::*; -use gst_plugin::object::*; -use gst_plugin::properties::*; +use gobject_subclass::object::*; use std::sync::Mutex; use std::{i32, u32}; From 1cabad00920ff7adf5f0f36ba948b1b3a881e48a Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Tue, 11 Sep 2018 15:20:47 +0200 Subject: [PATCH 062/199] Refactor logic to get initial timestamp --- gst-plugin-ndi/src/ndiaudiosrc.rs | 113 +++++++++++++++----------- gst-plugin-ndi/src/ndivideosrc.rs | 128 +++++++++++++++++------------- 2 files changed, 141 insertions(+), 100 deletions(-) diff --git a/gst-plugin-ndi/src/ndiaudiosrc.rs b/gst-plugin-ndi/src/ndiaudiosrc.rs index 28356f4f..03965bf9 100644 --- a/gst-plugin-ndi/src/ndiaudiosrc.rs +++ b/gst-plugin-ndi/src/ndiaudiosrc.rs @@ -228,6 +228,30 @@ impl NdiAudioSrc { // Virtual methods of gst::Element. We override none impl ElementImpl for NdiAudioSrc { + fn change_state(&self, element: &BaseSrc, transition: gst::StateChange) -> gst::StateChangeReturn { + if transition == gst::StateChange::PausedToPlaying{ + let receivers = hashmap_receivers.lock().unwrap(); + let settings = self.settings.lock().unwrap(); + + let receiver = receivers.get(&settings.id_receiver).unwrap(); + let recv = &receiver.ndi_instance; + let pNDI_recv = recv.recv; + + let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); + + let mut frame_type: NDIlib_frame_type_e = NDIlib_frame_type_e::NDIlib_frame_type_none; + unsafe{ + while frame_type != NDIlib_frame_type_e::NDIlib_frame_type_audio{ + frame_type = NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000); + } + + if ndi_struct.initial_timestamp <= audio_frame.timestamp as u64 || ndi_struct.initial_timestamp == 0{ + ndi_struct.initial_timestamp = audio_frame.timestamp as u64; + } + } + } + element.parent_change_state(transition) + } } // Virtual methods of gst_base::BaseSrc @@ -281,54 +305,55 @@ impl NdiAudioSrc { true } - // fn query(&self, element: &BaseSrc, query: &mut gst::QueryRef) -> bool { - // use gst::QueryView; - // - // match query.view_mut() { - // // We only work in Push mode. In Pull mode, create() could be called with - // // arbitrary offsets and we would have to produce for that specific offset - // QueryView::Scheduling(ref mut q) => { - // q.set(gst::SchedulingFlags::SEQUENTIAL, 1, -1, 0); - // q.add_scheduling_modes(&[gst::PadMode::Push]); - // return true; - // } - // // In Live mode we will have a latency equal to the number of samples in each buffer. - // // We can't output samples before they were produced, and the last sample of a buffer - // // is produced that much after the beginning, leading to this latency calculation - // QueryView::Latency(ref mut q) => { - // let settings = &*self.settings.lock().unwrap(); - // let state = self.state.lock().unwrap(); - // - // if let Some(ref _info) = state.info { - // // let latency = gst::SECOND - // // .mul_div_floor(1024 as u64, _info.rate() as u64) - // // .unwrap(); - // let latency = gst::SECOND.mul_div_floor(3 as u64, 2 as u64).unwrap(); - // // let latency = gst::SECOND - // // .mul_div_floor(1 as u64, 30 as u64) - // // .unwrap(); - // // gst_debug!(self.cat, obj: element, "Returning latency {}", latency); - // let max = latency * 1843200; - // // println!("{:?}", latency); - // // println!("{:?}",max); - // q.set(true, latency, max); - // return true; - // } else { - // return false; - // } - // } - // _ => (), - // } - // BaseSrcBase::parent_query(element, query) - // } + fn query(&self, element: &BaseSrc, query: &mut gst::QueryRef) -> bool { + use gst::QueryView; + + match query.view_mut() { + // We only work in Push mode. In Pull mode, create() could be called with + // arbitrary offsets and we would have to produce for that specific offset + QueryView::Scheduling(ref mut q) => { + q.set(gst::SchedulingFlags::SEQUENTIAL, 1, -1, 0); + q.add_scheduling_modes(&[gst::PadMode::Push]); + return true; + } + // In Live mode we will have a latency equal to the number of samples in each buffer. + // We can't output samples before they were produced, and the last sample of a buffer + // is produced that much after the beginning, leading to this latency calculation + // QueryView::Latency(ref mut q) => { + // let settings = &*self.settings.lock().unwrap(); + // let state = self.state.lock().unwrap(); + // + // if let Some(ref _info) = state.info { + // // let latency = gst::SECOND + // // .mul_div_floor(1024 as u64, _info.rate() as u64) + // // .unwrap(); + // let latency = gst::SECOND.mul_div_floor(3 as u64, 2 as u64).unwrap(); + // // let latency = gst::SECOND + // // .mul_div_floor(1 as u64, 30 as u64) + // // .unwrap(); + // // gst_debug!(self.cat, obj: element, "Returning latency {}", latency); + // let max = latency * 1843200; + // // println!("{:?}", latency); + // // println!("{:?}",max); + // q.set(true, latency, max); + // return true; + // } else { + // return false; + // } + // } + _ => (), + } + BaseSrcBase::parent_query(element, query) + } fn fixate(&self, element: &BaseSrc, caps: gst::Caps) -> gst::Caps { //We need to set the correct caps resolution and framerate unsafe{ - let mut receivers = hashmap_receivers.lock().unwrap(); + let receivers = hashmap_receivers.lock().unwrap(); let settings = self.settings.lock().unwrap(); - let receiver = receivers.get_mut(&settings.id_receiver).unwrap(); + let receiver = receivers.get(&settings.id_receiver).unwrap(); + let recv = &receiver.ndi_instance; let pNDI_recv = recv.recv; @@ -339,10 +364,6 @@ impl NdiAudioSrc { frame_type = NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000); } - if ndi_struct.initial_timestamp <= audio_frame.timestamp as u64 || ndi_struct.initial_timestamp == 0{ - ndi_struct.initial_timestamp = audio_frame.timestamp as u64; - } - let mut caps = gst::Caps::truncate(caps); { let caps = caps.make_mut(); diff --git a/gst-plugin-ndi/src/ndivideosrc.rs b/gst-plugin-ndi/src/ndivideosrc.rs index a273eefb..5136114e 100644 --- a/gst-plugin-ndi/src/ndivideosrc.rs +++ b/gst-plugin-ndi/src/ndivideosrc.rs @@ -236,8 +236,33 @@ impl NdiVideoSrc { // Virtual methods of gst::Element. We override none impl ElementImpl for NdiVideoSrc { + fn change_state(&self, element: &BaseSrc, transition: gst::StateChange) -> gst::StateChangeReturn { + if transition == gst::StateChange::PausedToPlaying{ + let receivers = hashmap_receivers.lock().unwrap(); + let settings = self.settings.lock().unwrap(); + + let receiver = receivers.get(&settings.id_receiver).unwrap(); + let recv = &receiver.ndi_instance; + let pNDI_recv = recv.recv; + + let video_frame: NDIlib_video_frame_v2_t = Default::default(); + + let mut frame_type: NDIlib_frame_type_e = NDIlib_frame_type_e::NDIlib_frame_type_none; + unsafe{ + while frame_type != NDIlib_frame_type_e::NDIlib_frame_type_video{ + frame_type = NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000); + } + + if ndi_struct.initial_timestamp <= video_frame.timestamp as u64 || ndi_struct.initial_timestamp == 0{ + ndi_struct.initial_timestamp = video_frame.timestamp as u64; + } + } + } + element.parent_change_state(transition) + } } + // Virtual methods of gst_base::BaseSrc impl BaseSrcImpl for NdiVideoSrc { // Called whenever the input/output caps are changing, i.e. in the very beginning before data @@ -290,62 +315,61 @@ impl NdiVideoSrc { } - // fn query(&self, element: &BaseSrc, query: &mut gst::QueryRef) -> bool { - // use gst::QueryView; - // println!("****************************************Dentro de query"); - // match query.view_mut() { - // // We only work in Push mode. In Pull mode, create() could be called with - // // arbitrary offsets and we would have to produce for that specific offset - // QueryView::Scheduling(ref mut q) => { - // q.set(gst::SchedulingFlags::SEQUENTIAL, 1, -1, 0); - // q.add_scheduling_modes(&[gst::PadMode::Push]); - // return true; - // } - // // In Live mode we will have a latency equal to the number of samples in each buffer. - // // We can't output samples before they were produced, and the last sample of a buffer - // // is produced that much after the beginning, leading to this latency calculation - // QueryView::Latency(ref mut q) => { - // let settings = self.settings.lock().unwrap(); - // let state = self.state.lock().unwrap(); - // println!("Dentro de query"); - // - // if let Some(ref _info) = state.info { - // // let latency = gst::SECOND - // // .mul_div_floor(settings.samples_per_buffer as u64, info.rate() as u64) - // // .unwrap(); - // let latency = gst::SECOND.mul_div_floor(3 as u64, 2 as u64).unwrap(); - // let mut latency = gst::SECOND.mul_div_floor(settings.latency, 1000).unwrap(); - // // if settings.latency > 2000{ - // // println!("{:?}", element.get_name()); - // // latency = gst::SECOND * 0; - // // } - // let latency = gst::SECOND * 0; - // // .mul_div_floor(1 as u64, 30 as u64) - // // .unwrap(); - // // gst_debug!(self.cat, obj: element, "Returning latency {}", latency); - // let max = gst::SECOND * 120 * 1843200; - // // println!("{:?}", latency2); - // println!("{:?}", latency); - // println!("{:?}", (settings.latency / 1000)); - // // println!("{:?}",max); - // q.set(true, latency, max); - // return true; - // } else { - // return false; - // } - // } - // _ => (), - // } - // BaseSrcBase::parent_query(element, query) - // } + fn query(&self, element: &BaseSrc, query: &mut gst::QueryRef) -> bool { + use gst::QueryView; + match query.view_mut() { + // We only work in Push mode. In Pull mode, create() could be called with + // arbitrary offsets and we would have to produce for that specific offset + QueryView::Scheduling(ref mut q) => { + q.set(gst::SchedulingFlags::SEQUENTIAL, 1, -1, 0); + q.add_scheduling_modes(&[gst::PadMode::Push]); + return true; + } + // In Live mode we will have a latency equal to the number of samples in each buffer. + // We can't output samples before they were produced, and the last sample of a buffer + // is produced that much after the beginning, leading to this latency calculation + // QueryView::Latency(ref mut q) => { + // let settings = self.settings.lock().unwrap(); + // let state = self.state.lock().unwrap(); + // println!("Dentro de query"); + // + // if let Some(ref _info) = state.info { + // // let latency = gst::SECOND + // // .mul_div_floor(settings.samples_per_buffer as u64, info.rate() as u64) + // // .unwrap(); + // let latency = gst::SECOND.mul_div_floor(3 as u64, 2 as u64).unwrap(); + // let mut latency = gst::SECOND.mul_div_floor(settings.latency, 1000).unwrap(); + // // if settings.latency > 2000{ + // // println!("{:?}", element.get_name()); + // // latency = gst::SECOND * 0; + // // } + // let latency = gst::SECOND * 0; + // // .mul_div_floor(1 as u64, 30 as u64) + // // .unwrap(); + // // gst_debug!(self.cat, obj: element, "Returning latency {}", latency); + // let max = gst::SECOND * 120 * 1843200; + // // println!("{:?}", latency2); + // println!("{:?}", latency); + // println!("{:?}", (settings.latency / 1000)); + // // println!("{:?}",max); + // q.set(true, latency, max); + // return true; + // } else { + // return false; + // } + // } + _ => (), + } + BaseSrcBase::parent_query(element, query) + } fn fixate(&self, element: &BaseSrc, caps: gst::Caps) -> gst::Caps { //We need to set the correct caps resolution and framerate unsafe{ - let mut receivers = hashmap_receivers.lock().unwrap(); + let receivers = hashmap_receivers.lock().unwrap(); let settings = self.settings.lock().unwrap(); - let receiver = receivers.get_mut(&settings.id_receiver).unwrap(); + let receiver = receivers.get(&settings.id_receiver).unwrap(); let recv = &receiver.ndi_instance; let pNDI_recv = recv.recv; @@ -356,10 +380,6 @@ impl NdiVideoSrc { frame_type = NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000); } - if ndi_struct.initial_timestamp <= video_frame.timestamp as u64 || ndi_struct.initial_timestamp == 0{ - ndi_struct.initial_timestamp = video_frame.timestamp as u64; - } - let mut caps = gst::Caps::truncate(caps); { let caps = caps.make_mut(); From 1aaf429f9ef3912489980acfed47446c36121b0e Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Tue, 11 Sep 2018 17:47:28 +0200 Subject: [PATCH 063/199] Detect if NDI source closed the stream --- gst-plugin-ndi/src/ndiaudiosrc.rs | 6 +++++- gst-plugin-ndi/src/ndivideosrc.rs | 7 +++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/gst-plugin-ndi/src/ndiaudiosrc.rs b/gst-plugin-ndi/src/ndiaudiosrc.rs index 03965bf9..99db9710 100644 --- a/gst-plugin-ndi/src/ndiaudiosrc.rs +++ b/gst-plugin-ndi/src/ndiaudiosrc.rs @@ -414,7 +414,11 @@ impl NdiAudioSrc { let mut skip_frame = true; while skip_frame { - NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000,); + let frame_type = NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000,); + if frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none || frame_type == NDIlib_frame_type_e::NDIlib_frame_type_error { + gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type none received, assuming that the source closed the stream...."]); + return Err(gst::FlowReturn::CustomError); + } if time >= (audio_frame.timestamp as u64){ gst_debug!(self.cat, obj: element, "Frame timestamp ({:?}) is lower than received in the first frame from NDI ({:?}), so skiping...", (audio_frame.timestamp as u64), time); } diff --git a/gst-plugin-ndi/src/ndivideosrc.rs b/gst-plugin-ndi/src/ndivideosrc.rs index 5136114e..176fda1d 100644 --- a/gst-plugin-ndi/src/ndivideosrc.rs +++ b/gst-plugin-ndi/src/ndivideosrc.rs @@ -293,7 +293,6 @@ impl NdiVideoSrc { settings.id_receiver = connect_ndi(self.cat, element, settings.ip.clone(), settings.stream_name.clone()); if settings.id_receiver == 0{ - gst_error!(self.cat, obj: element, "Stream not found"); return false; } else{ @@ -432,7 +431,11 @@ impl NdiVideoSrc { let mut skip_frame = true; while skip_frame { - NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000,); + let frame_type = NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000,); + if frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none || frame_type == NDIlib_frame_type_e::NDIlib_frame_type_error { + gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type none received, assuming that the source closed the stream...."]); + return Err(gst::FlowReturn::CustomError); + } if time >= (video_frame.timestamp as u64){ gst_debug!(self.cat, obj: element, "Frame timestamp ({:?}) is lower than received in the first frame from NDI ({:?}), so skiping...", (video_frame.timestamp as u64), time); } From c92d63ee88ab81e59522cb924c10c6f4212e9710 Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Wed, 12 Sep 2018 09:44:46 +0200 Subject: [PATCH 064/199] Refactor unsafe blocks --- gst-plugin-ndi/src/lib.rs | 90 +++++++++++++++---------------- gst-plugin-ndi/src/ndiaudiosrc.rs | 60 ++++++++++----------- gst-plugin-ndi/src/ndivideosrc.rs | 65 +++++++++++----------- 3 files changed, 108 insertions(+), 107 deletions(-) diff --git a/gst-plugin-ndi/src/lib.rs b/gst-plugin-ndi/src/lib.rs index c55e2726..e538203b 100644 --- a/gst-plugin-ndi/src/lib.rs +++ b/gst-plugin-ndi/src/lib.rs @@ -73,40 +73,40 @@ lazy_static! { static mut id_receiver: i8 = 0; fn connect_ndi(cat: gst::DebugCategory , element: &BaseSrc, ip: String, stream_name: String) -> i8{ - unsafe { - gst_debug!(cat, obj: element, "Starting NDI connection..."); + gst_debug!(cat, obj: element, "Starting NDI connection..."); - let mut receivers = hashmap_receivers.lock().unwrap(); - let mut audio = false; - let mut video = false; + let mut receivers = hashmap_receivers.lock().unwrap(); + let mut audio = false; + let mut video = false; - //FIXME Search for another way to know if the source is an audio or a video source - if element.get_name().contains("audiosrc"){ - audio = true; - } - else - { - video = true; - } + //FIXME Search for another way to know if the source is an audio or a video source + if element.get_name().contains("audiosrc"){ + audio = true; + } + else + { + video = true; + } - for val in receivers.values_mut(){ - if val.ip == ip || val.stream_name == stream_name{ - if (val.audio && val.video) || (val.audio && audio) || (val.video && video){ - continue; - } - else { - if video { - val.video = video; - } - else{ - val.audio = audio; - } - return val.id; - } + for val in receivers.values_mut(){ + if val.ip == ip || val.stream_name == stream_name{ + if (val.audio && val.video) || (val.audio && audio) || (val.video && video){ + continue; + } + else { + if video { + val.video = video; + } + else{ + val.audio = audio; + } + return val.id; } - } + } + unsafe { + if !NDIlib_initialize() { gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_initialize error"]); // return false; @@ -217,30 +217,30 @@ fn connect_ndi(cat: gst::DebugCategory , element: &BaseSrc, ip: String, stream fn stop_ndi(cat: gst::DebugCategory , element: &BaseSrc, id: i8) -> bool{ gst_debug!(cat, obj: element, "Closing NDI connection..."); - unsafe{ - let mut receivers = hashmap_receivers.lock().unwrap(); - { - let val = receivers.get_mut(&id).unwrap(); - if val.video && val.audio{ - if element.get_name().contains("audiosrc"){ - val.audio = false; - } - else{ - val.video = false; - } - return true; + let mut receivers = hashmap_receivers.lock().unwrap(); + { + let val = receivers.get_mut(&id).unwrap(); + if val.video && val.audio{ + if element.get_name().contains("audiosrc"){ + val.audio = false; } + else{ + val.video = false; + } + return true; + } - let recv = &val.ndi_instance; - let pNDI_recv = recv.recv; + let recv = &val.ndi_instance; + let pNDI_recv = recv.recv; + unsafe{ NDIlib_recv_destroy(pNDI_recv); // ndi_struct.recv = None; NDIlib_destroy(); } - receivers.remove(&id); - gst_debug!(cat, obj: element, "Closed NDI connection"); - return true; } + receivers.remove(&id); + gst_debug!(cat, obj: element, "Closed NDI connection"); + return true; } // Static plugin metdata that is directly stored in the plugin shared object and read by GStreamer diff --git a/gst-plugin-ndi/src/ndiaudiosrc.rs b/gst-plugin-ndi/src/ndiaudiosrc.rs index 99db9710..445807c4 100644 --- a/gst-plugin-ndi/src/ndiaudiosrc.rs +++ b/gst-plugin-ndi/src/ndiaudiosrc.rs @@ -348,35 +348,35 @@ impl NdiAudioSrc { fn fixate(&self, element: &BaseSrc, caps: gst::Caps) -> gst::Caps { //We need to set the correct caps resolution and framerate - unsafe{ - let receivers = hashmap_receivers.lock().unwrap(); - let settings = self.settings.lock().unwrap(); + let receivers = hashmap_receivers.lock().unwrap(); + let settings = self.settings.lock().unwrap(); - let receiver = receivers.get(&settings.id_receiver).unwrap(); + let receiver = receivers.get(&settings.id_receiver).unwrap(); - let recv = &receiver.ndi_instance; - let pNDI_recv = recv.recv; + let recv = &receiver.ndi_instance; + let pNDI_recv = recv.recv; - let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); + let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); - let mut frame_type: NDIlib_frame_type_e = NDIlib_frame_type_e::NDIlib_frame_type_none; - while frame_type != NDIlib_frame_type_e::NDIlib_frame_type_audio{ + let mut frame_type: NDIlib_frame_type_e = NDIlib_frame_type_e::NDIlib_frame_type_none; + while frame_type != NDIlib_frame_type_e::NDIlib_frame_type_audio{ + unsafe{ frame_type = NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000); } - - let mut caps = gst::Caps::truncate(caps); - { - let caps = caps.make_mut(); - let s = caps.get_mut_structure(0).unwrap(); - //s.fixate_field_nearest_int("rate", audio_frame.sample_rate); - s.fixate_field_nearest_int("rate", audio_frame.sample_rate / audio_frame.no_channels); - s.fixate_field_nearest_int("channels", audio_frame.no_channels); - } - - // Let BaseSrc fixate anything else for us. We could've alternatively have - // called Caps::fixate() here - element.parent_fixate(caps) } + let mut caps = gst::Caps::truncate(caps); + { + let caps = caps.make_mut(); + let s = caps.get_mut_structure(0).unwrap(); + //s.fixate_field_nearest_int("rate", audio_frame.sample_rate); + s.fixate_field_nearest_int("rate", audio_frame.sample_rate / audio_frame.no_channels); + s.fixate_field_nearest_int("channels", audio_frame.no_channels); + } + + // Let BaseSrc fixate anything else for us. We could've alternatively have + // called Caps::fixate() here + element.parent_fixate(caps) + // } } //Creates the audio buffers @@ -401,15 +401,15 @@ impl NdiAudioSrc { } Some(ref info) => info.clone(), }; + let receivers = hashmap_receivers.lock().unwrap(); + + let recv = &receivers.get(&_settings.id_receiver).unwrap().ndi_instance; + let pNDI_recv = recv.recv; + + let pts: u64; + let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); + unsafe{ - let receivers = hashmap_receivers.lock().unwrap(); - - let recv = &receivers.get(&_settings.id_receiver).unwrap().ndi_instance; - let pNDI_recv = recv.recv; - - let pts: u64; - let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); - let time = ndi_struct.initial_timestamp; let mut skip_frame = true; diff --git a/gst-plugin-ndi/src/ndivideosrc.rs b/gst-plugin-ndi/src/ndivideosrc.rs index 176fda1d..7e18770d 100644 --- a/gst-plugin-ndi/src/ndivideosrc.rs +++ b/gst-plugin-ndi/src/ndivideosrc.rs @@ -364,36 +364,36 @@ impl NdiVideoSrc { fn fixate(&self, element: &BaseSrc, caps: gst::Caps) -> gst::Caps { //We need to set the correct caps resolution and framerate - unsafe{ - let receivers = hashmap_receivers.lock().unwrap(); - let settings = self.settings.lock().unwrap(); + let receivers = hashmap_receivers.lock().unwrap(); + let settings = self.settings.lock().unwrap(); - let receiver = receivers.get(&settings.id_receiver).unwrap(); - let recv = &receiver.ndi_instance; - let pNDI_recv = recv.recv; + let receiver = receivers.get(&settings.id_receiver).unwrap(); + let recv = &receiver.ndi_instance; + let pNDI_recv = recv.recv; - let video_frame: NDIlib_video_frame_v2_t = Default::default(); + let video_frame: NDIlib_video_frame_v2_t = Default::default(); - let mut frame_type: NDIlib_frame_type_e = NDIlib_frame_type_e::NDIlib_frame_type_none; - while frame_type != NDIlib_frame_type_e::NDIlib_frame_type_video{ + let mut frame_type: NDIlib_frame_type_e = NDIlib_frame_type_e::NDIlib_frame_type_none; + while frame_type != NDIlib_frame_type_e::NDIlib_frame_type_video{ + unsafe{ frame_type = NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000); } - - let mut caps = gst::Caps::truncate(caps); - { - let caps = caps.make_mut(); - let s = caps.get_mut_structure(0).unwrap(); - s.fixate_field_nearest_int("width", video_frame.xres); - s.fixate_field_nearest_int("height", video_frame.yres); - s.fixate_field_nearest_fraction("framerate", Fraction::new(video_frame.frame_rate_N, video_frame.frame_rate_D)); - } - - // Let BaseSrc fixate anything else for us. We could've alternatively have - // called Caps::fixate() here - - let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); - element.parent_fixate(caps) } + + let mut caps = gst::Caps::truncate(caps); + { + let caps = caps.make_mut(); + let s = caps.get_mut_structure(0).unwrap(); + s.fixate_field_nearest_int("width", video_frame.xres); + s.fixate_field_nearest_int("height", video_frame.yres); + s.fixate_field_nearest_fraction("framerate", Fraction::new(video_frame.frame_rate_N, video_frame.frame_rate_D)); + } + + // Let BaseSrc fixate anything else for us. We could've alternatively have + // called Caps::fixate() here + + let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); + element.parent_fixate(caps) } //Creates the video buffers @@ -418,15 +418,16 @@ impl NdiVideoSrc { } Some(ref info) => info.clone(), }; + // unsafe{ + let receivers = hashmap_receivers.lock().unwrap(); + + let recv = &receivers.get(&_settings.id_receiver).unwrap().ndi_instance; + let pNDI_recv = recv.recv; + + let pts: u64; + let video_frame: NDIlib_video_frame_v2_t = Default::default(); + unsafe{ - let receivers = hashmap_receivers.lock().unwrap(); - - let recv = &receivers.get(&_settings.id_receiver).unwrap().ndi_instance; - let pNDI_recv = recv.recv; - - let pts: u64; - let video_frame: NDIlib_video_frame_v2_t = Default::default(); - let time = ndi_struct.initial_timestamp; let mut skip_frame = true; From 00992c182568a21bb66f07e4331abe64aa32d0b7 Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Wed, 12 Sep 2018 09:52:16 +0200 Subject: [PATCH 065/199] Deleted Cargo.lock --- gst-plugin-ndi/Cargo.lock | 275 -------------------------------------- 1 file changed, 275 deletions(-) delete mode 100644 gst-plugin-ndi/Cargo.lock diff --git a/gst-plugin-ndi/Cargo.lock b/gst-plugin-ndi/Cargo.lock deleted file mode 100644 index 740d507d..00000000 --- a/gst-plugin-ndi/Cargo.lock +++ /dev/null @@ -1,275 +0,0 @@ -[[package]] -name = "array-init" -version = "0.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "bitflags" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "byte-slice-cast" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "byteorder" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "cfg-if" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "glib" -version = "0.5.0" -source = "git+https://github.com/gtk-rs/glib#cae39ff7a72073a553f5b60321bd9389db00ac3b" -dependencies = [ - "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "glib-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", - "gobject-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", - "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "glib-sys" -version = "0.6.0" -source = "git+https://github.com/gtk-rs/sys#ce1fffe51a6498ac278502bf1afb18d711ad0250" -dependencies = [ - "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "gobject-sys" -version = "0.6.0" -source = "git+https://github.com/gtk-rs/sys#ce1fffe51a6498ac278502bf1afb18d711ad0250" -dependencies = [ - "glib-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", - "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "gst-plugin" -version = "0.1.0" -source = "git+https://github.com/sdroege/gst-plugin-rs#f2f18ebb278f66f09995148cf9790ed469f6357f" -dependencies = [ - "byteorder 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "glib 0.5.0 (git+https://github.com/gtk-rs/glib)", - "glib-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", - "gobject-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", - "gstreamer 0.12.0 (git+https://github.com/sdroege/gstreamer-rs)", - "gstreamer-base 0.12.0 (git+https://github.com/sdroege/gstreamer-rs)", - "gstreamer-base-sys 0.6.0 (git+https://github.com/sdroege/gstreamer-sys)", - "gstreamer-sys 0.6.0 (git+https://github.com/sdroege/gstreamer-sys)", - "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "gst-plugin-ndi" -version = "0.1.0" -dependencies = [ - "byte-slice-cast 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "glib 0.5.0 (git+https://github.com/gtk-rs/glib)", - "gst-plugin 0.1.0 (git+https://github.com/sdroege/gst-plugin-rs)", - "gstreamer 0.12.0 (git+https://github.com/sdroege/gstreamer-rs)", - "gstreamer-audio 0.12.0 (git+https://github.com/sdroege/gstreamer-rs)", - "gstreamer-base 0.12.0 (git+https://github.com/sdroege/gstreamer-rs)", - "gstreamer-video 0.12.0 (git+https://github.com/sdroege/gstreamer-rs)", - "num-traits 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "gstreamer" -version = "0.12.0" -source = "git+https://github.com/sdroege/gstreamer-rs#5dd17d6248b45fa2536f1864962908fb21b041e8" -dependencies = [ - "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "glib 0.5.0 (git+https://github.com/gtk-rs/glib)", - "glib-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", - "gobject-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", - "gstreamer-sys 0.6.0 (git+https://github.com/sdroege/gstreamer-sys)", - "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", - "muldiv 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "num-rational 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "gstreamer-audio" -version = "0.12.0" -source = "git+https://github.com/sdroege/gstreamer-rs#5dd17d6248b45fa2536f1864962908fb21b041e8" -dependencies = [ - "array-init 0.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "glib 0.5.0 (git+https://github.com/gtk-rs/glib)", - "glib-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", - "gobject-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", - "gstreamer 0.12.0 (git+https://github.com/sdroege/gstreamer-rs)", - "gstreamer-audio-sys 0.6.0 (git+https://github.com/sdroege/gstreamer-sys)", - "gstreamer-sys 0.6.0 (git+https://github.com/sdroege/gstreamer-sys)", -] - -[[package]] -name = "gstreamer-audio-sys" -version = "0.6.0" -source = "git+https://github.com/sdroege/gstreamer-sys#a9a4608562bcd377f116d5d4806bff439eca5168" -dependencies = [ - "glib-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", - "gobject-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", - "gstreamer-base-sys 0.6.0 (git+https://github.com/sdroege/gstreamer-sys)", - "gstreamer-sys 0.6.0 (git+https://github.com/sdroege/gstreamer-sys)", - "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "gstreamer-base" -version = "0.12.0" -source = "git+https://github.com/sdroege/gstreamer-rs#5dd17d6248b45fa2536f1864962908fb21b041e8" -dependencies = [ - "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "glib 0.5.0 (git+https://github.com/gtk-rs/glib)", - "glib-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", - "gobject-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", - "gstreamer 0.12.0 (git+https://github.com/sdroege/gstreamer-rs)", - "gstreamer-base-sys 0.6.0 (git+https://github.com/sdroege/gstreamer-sys)", - "gstreamer-sys 0.6.0 (git+https://github.com/sdroege/gstreamer-sys)", -] - -[[package]] -name = "gstreamer-base-sys" -version = "0.6.0" -source = "git+https://github.com/sdroege/gstreamer-sys#a9a4608562bcd377f116d5d4806bff439eca5168" -dependencies = [ - "glib-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", - "gobject-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", - "gstreamer-sys 0.6.0 (git+https://github.com/sdroege/gstreamer-sys)", - "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "gstreamer-sys" -version = "0.6.0" -source = "git+https://github.com/sdroege/gstreamer-sys#a9a4608562bcd377f116d5d4806bff439eca5168" -dependencies = [ - "glib-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", - "gobject-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", - "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "gstreamer-video" -version = "0.12.0" -source = "git+https://github.com/sdroege/gstreamer-rs#5dd17d6248b45fa2536f1864962908fb21b041e8" -dependencies = [ - "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "glib 0.5.0 (git+https://github.com/gtk-rs/glib)", - "glib-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", - "gobject-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", - "gstreamer 0.12.0 (git+https://github.com/sdroege/gstreamer-rs)", - "gstreamer-base 0.12.0 (git+https://github.com/sdroege/gstreamer-rs)", - "gstreamer-base-sys 0.6.0 (git+https://github.com/sdroege/gstreamer-sys)", - "gstreamer-sys 0.6.0 (git+https://github.com/sdroege/gstreamer-sys)", - "gstreamer-video-sys 0.6.0 (git+https://github.com/sdroege/gstreamer-sys)", - "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "gstreamer-video-sys" -version = "0.6.0" -source = "git+https://github.com/sdroege/gstreamer-sys#a9a4608562bcd377f116d5d4806bff439eca5168" -dependencies = [ - "glib-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", - "gobject-sys 0.6.0 (git+https://github.com/gtk-rs/sys)", - "gstreamer-base-sys 0.6.0 (git+https://github.com/sdroege/gstreamer-sys)", - "gstreamer-sys 0.6.0 (git+https://github.com/sdroege/gstreamer-sys)", - "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "lazy_static" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "libc" -version = "0.2.40" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "muldiv" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "nodrop" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "num-integer" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "num-traits 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "num-rational" -version = "0.1.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "num-integer 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "num-traits" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "pkg-config" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[metadata] -"checksum array-init 0.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4d3b508d35216892b50a135fb52c9bb90f04a97b7782230805dff1a156ad5469" -"checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf" -"checksum byte-slice-cast 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5a865e7bfa6c3b79216ccba767d4dc66e4f9f65f1ed4639e73faff3c4a2485d7" -"checksum byteorder 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "73b5bdfe7ee3ad0b99c9801d58807a9dbc9e09196365b0203853b99889ab3c87" -"checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" -"checksum glib 0.5.0 (git+https://github.com/gtk-rs/glib)" = "" -"checksum glib-sys 0.6.0 (git+https://github.com/gtk-rs/sys)" = "" -"checksum gobject-sys 0.6.0 (git+https://github.com/gtk-rs/sys)" = "" -"checksum gst-plugin 0.1.0 (git+https://github.com/sdroege/gst-plugin-rs)" = "" -"checksum gstreamer 0.12.0 (git+https://github.com/sdroege/gstreamer-rs)" = "" -"checksum gstreamer-audio 0.12.0 (git+https://github.com/sdroege/gstreamer-rs)" = "" -"checksum gstreamer-audio-sys 0.6.0 (git+https://github.com/sdroege/gstreamer-sys)" = "" -"checksum gstreamer-base 0.12.0 (git+https://github.com/sdroege/gstreamer-rs)" = "" -"checksum gstreamer-base-sys 0.6.0 (git+https://github.com/sdroege/gstreamer-sys)" = "" -"checksum gstreamer-sys 0.6.0 (git+https://github.com/sdroege/gstreamer-sys)" = "" -"checksum gstreamer-video 0.12.0 (git+https://github.com/sdroege/gstreamer-rs)" = "" -"checksum gstreamer-video-sys 0.6.0 (git+https://github.com/sdroege/gstreamer-sys)" = "" -"checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d" -"checksum libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "6fd41f331ac7c5b8ac259b8bf82c75c0fb2e469bbf37d2becbba9a6a2221965b" -"checksum muldiv 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1cbef5aa2e8cd82a18cc20e26434cc9843e1ef46e55bfabe5bddb022236c5b3e" -"checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2" -"checksum num-integer 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f8d26da319fb45674985c78f1d1caf99aa4941f785d384a2ae36d0740bc3e2fe" -"checksum num-rational 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "ee314c74bd753fc86b4780aa9475da469155f3848473a261d2d18e35245a784e" -"checksum num-traits 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dee092fcdf725aee04dd7da1d21debff559237d49ef1cb3e69bcb8ece44c7364" -"checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903" From 45e2ef54fb6236806d2775a82f39357ebe06aa43 Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Wed, 12 Sep 2018 09:52:53 +0200 Subject: [PATCH 066/199] Added .gitignore --- gst-plugin-ndi/.gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 gst-plugin-ndi/.gitignore diff --git a/gst-plugin-ndi/.gitignore b/gst-plugin-ndi/.gitignore new file mode 100644 index 00000000..ffa3bbd2 --- /dev/null +++ b/gst-plugin-ndi/.gitignore @@ -0,0 +1 @@ +Cargo.lock \ No newline at end of file From c669a99043a29575a8955a36bc4addbc210cb3fc Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Wed, 12 Sep 2018 12:10:03 +0200 Subject: [PATCH 067/199] Updated README --- gst-plugin-ndi/README.md | 62 ++++++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/gst-plugin-ndi/README.md b/gst-plugin-ndi/README.md index 8d76182e..7c603345 100644 --- a/gst-plugin-ndi/README.md +++ b/gst-plugin-ndi/README.md @@ -1,8 +1,32 @@ -GStreamer NDI Plugin +GStreamer NDI Plugin for Linux ==================== -*Compiled and tested with Ubuntu 16.04.5 and GStreamer 1.8.3* +*Compiled and tested with Ubuntu 16.04.5, GStreamer 1.8.3 and NDI SDK 3.0.9 and 3.5.1* +This is a plugin for the [GStreamer](https://gstreamer.freedesktop.org/) multimedia framework that allows to receive a stream from a [NDI](https://www.newtek.com/ndi/) source. This plugin is developed by [Teltek](http://teltek.es/) and funded by the [University of the Arts London](https://www.arts.ac.uk/) and [The University of Manchester](https://www.manchester.ac.uk/). + +Currently the plugin only has sources elements, `ndivideosrc` to get video from the stream and `ndiaudiosrc` for audio. Only it's necessary to provide the name or the ip of the stream, and automatically the element get all the information required from the stream, such resolution, framerate, audio channels,... + +Some examples of usage of these elements: +``` +#Information about the elements +gst-inspect-1.0 ndi +gst-inspect-1.0 ndivideosrc +gst-inspect-1.0 ndiaudiosrc + +#Video pipeline +gst-launch-1.0 ndivideosrc stream-name="GC-DEV2 (OBS)" ! autovideosink +#Audio pipeline +gst-launch-1.0 ndiaudiosrc stream-name="GC-DEV2 (OBS)" ! autoaudiosink + +#Video and audio pipeline +gst-launch-1.0 ndivideosrc stream-name="GC-DEV2 (OBS)" ! autovideosink ndiaudiosrc stream-name="GC-DEV2 (OBS)" ! autoaudiosink +``` + +Feel free to contribute to this project testing with more hardware and software, reporting bugs or with pull requests. + +Compile NDI element +------- Before compile the element it's necessary install Rust, NDI SDK and the following packages for gstreamer: ``` @@ -10,40 +34,30 @@ apt-get install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \ gstreamer1.0-plugins-base gstreamer1.0-plugins-good \ gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly \ gstreamer1.0-libav libgstrtspserver-1.0-dev + ``` +To install the necessary NDI library exists two options: +* Download NDI SDK from NDI website and move the library to the correct location. +* Use a [deb package](https://github.com/Palakis/obs-ndi/releases/download/4.5.2/libndi3_3.5.1-1_amd64.deb) made by the community. Thanks to [NDI plugin for OBS](https://github.com/Palakis/obs-ndi). - -Compile NDI element and basic pipelines -------- +To build the plugin execute these commands from the root of the repository folder ``` cargo build + export GST_PLUGIN_PATH=`pwd`/target/debug - gst-inspect-1.0 ndi -gst-inspect-1.0 ndivideosrc -gst-inspect-1.0 ndiaudiosrc - -gst-launch-1.0 ndivideosrc stream-name="GC-DEV2 (OBS)" ! autovideosink -gst-launch-1.0 ndiaudiosrc stream-name="GC-DEV2 (OBS)" ! autoaudiosink - -gst-launch-1.0 ndivideosrc stream-name="GC-DEV2 (OBS)" ! autovideosink ndiaudiosrc stream-name="GC-DEV2 (OBS)" ! autoaudiosink - ``` -Debug pipelines: +If all went ok, you should see info related to the NDI element. To make the plugin available without using `GST_PLUGIN_PATH` it's necessary copy the plugin to the gstreamer plugins folder. ``` -#Check if the timestamps are correct -gst-launch-1.0 -v ndivideosrc name=gc-ndi-src stream-name="GC-DEV2 (OBS)" ! fakesink silent=false - -#Debug sink to check if jitter is correct -GST_DEBUG=*basesink*:5 gst-launch-1.0 -v ndivideosrc name=gc-ndi-src stream-name="GC-DEV2 (OBS)" ! autovideosink - -#Add latency when launching the pipeline -gst-launch-1.0 -v ndivideosrc name=gc-ndi-src stream-name="GC-DEV2 (OBS)" ! autovideosink ts-offset=1000000000 +cp target/debug/libgstndi.so /usr/lib/x86_64-linux-gnu/gstreamer-1.0/ ``` -More info about GStreamer plugins and Rust: +More info about GStreamer plugins written in Rust: ---------------------------------- +https://github.com/sdroege/gstreamer-rs +https://github.com/sdroege/gst-plugin-rs + https://coaxion.net/blog/2018/01/how-to-write-gstreamer-elements-in-rust-part-1-a-video-filter-for-converting-rgb-to-grayscale/ https://coaxion.net/blog/2018/02/how-to-write-gstreamer-elements-in-rust-part-2-a-raw-audio-sine-wave-source/ From 6db4929e081ec72dd0e39ed4e4b0482abc805970 Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Tue, 18 Sep 2018 11:52:09 +0200 Subject: [PATCH 068/199] Rename ndilib to ndisys --- gst-plugin-ndi/src/lib.rs | 4 ++-- gst-plugin-ndi/src/ndiaudiosrc.rs | 2 +- gst-plugin-ndi/src/{ndilib.rs => ndisys.rs} | 0 gst-plugin-ndi/src/ndivideosrc.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename gst-plugin-ndi/src/{ndilib.rs => ndisys.rs} (100%) diff --git a/gst-plugin-ndi/src/lib.rs b/gst-plugin-ndi/src/lib.rs index e538203b..3b6c79bb 100644 --- a/gst-plugin-ndi/src/lib.rs +++ b/gst-plugin-ndi/src/lib.rs @@ -24,11 +24,11 @@ extern crate lazy_static; mod ndivideosrc; mod ndiaudiosrc; -pub mod ndilib; +pub mod ndisys; use std::{thread, time}; use std::ffi::{CStr, CString}; -use ndilib::*; +use ndisys::*; use gst_plugin::base_src::*; use std::collections::HashMap; diff --git a/gst-plugin-ndi/src/ndiaudiosrc.rs b/gst-plugin-ndi/src/ndiaudiosrc.rs index 445807c4..e56ffd97 100644 --- a/gst-plugin-ndi/src/ndiaudiosrc.rs +++ b/gst-plugin-ndi/src/ndiaudiosrc.rs @@ -15,7 +15,7 @@ use std::{i32, u32}; use std::ptr; -use ndilib::*; +use ndisys::*; use connect_ndi; use stop_ndi; use ndi_struct; diff --git a/gst-plugin-ndi/src/ndilib.rs b/gst-plugin-ndi/src/ndisys.rs similarity index 100% rename from gst-plugin-ndi/src/ndilib.rs rename to gst-plugin-ndi/src/ndisys.rs diff --git a/gst-plugin-ndi/src/ndivideosrc.rs b/gst-plugin-ndi/src/ndivideosrc.rs index 7e18770d..83bde839 100644 --- a/gst-plugin-ndi/src/ndivideosrc.rs +++ b/gst-plugin-ndi/src/ndivideosrc.rs @@ -16,7 +16,7 @@ use std::{i32, u32}; use std::ptr; -use ndilib::*; +use ndisys::*; use connect_ndi; use stop_ndi; use ndi_struct; From cf8b7db9bdb78472697554dc8ad5db7c3ce35ab6 Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Tue, 18 Sep 2018 11:53:12 +0200 Subject: [PATCH 069/199] Fix code style with cargo fmt --- gst-plugin-ndi/src/lib.rs | 125 +++-- gst-plugin-ndi/src/ndiaudiosrc.rs | 707 +++++++++++++++-------------- gst-plugin-ndi/src/ndisys.rs | 1 - gst-plugin-ndi/src/ndivideosrc.rs | 730 +++++++++++++++--------------- 4 files changed, 816 insertions(+), 747 deletions(-) diff --git a/gst-plugin-ndi/src/lib.rs b/gst-plugin-ndi/src/lib.rs index 3b6c79bb..f0686803 100644 --- a/gst-plugin-ndi/src/lib.rs +++ b/gst-plugin-ndi/src/lib.rs @@ -22,14 +22,14 @@ extern crate gstreamer_video as gst_video; #[macro_use] extern crate lazy_static; -mod ndivideosrc; mod ndiaudiosrc; pub mod ndisys; +mod ndivideosrc; -use std::{thread, time}; -use std::ffi::{CStr, CString}; -use ndisys::*; use gst_plugin::base_src::*; +use ndisys::*; +use std::ffi::{CStr, CString}; +use std::{thread, time}; use std::collections::HashMap; use std::sync::Mutex; @@ -44,7 +44,7 @@ fn plugin_init(plugin: &gst::Plugin) -> bool { true } -struct ndi_receiver_info{ +struct ndi_receiver_info { stream_name: String, ip: String, video: bool, @@ -53,12 +53,12 @@ struct ndi_receiver_info{ id: i8, } -struct Ndi{ +struct Ndi { initial_timestamp: u64, start_pts: gst::ClockTime, } -static mut ndi_struct: Ndi = Ndi{ +static mut ndi_struct: Ndi = Ndi { initial_timestamp: 0, start_pts: gst::ClockTime(Some(0)), }; @@ -72,7 +72,7 @@ lazy_static! { static mut id_receiver: i8 = 0; -fn connect_ndi(cat: gst::DebugCategory , element: &BaseSrc, ip: String, stream_name: String) -> i8{ +fn connect_ndi(cat: gst::DebugCategory, element: &BaseSrc, ip: String, stream_name: String) -> i8 { gst_debug!(cat, obj: element, "Starting NDI connection..."); let mut receivers = hashmap_receivers.lock().unwrap(); @@ -80,35 +80,33 @@ fn connect_ndi(cat: gst::DebugCategory , element: &BaseSrc, ip: String, stream let mut video = false; //FIXME Search for another way to know if the source is an audio or a video source - if element.get_name().contains("audiosrc"){ + if element.get_name().contains("audiosrc") { audio = true; - } - else - { + } else { video = true; } - for val in receivers.values_mut(){ - if val.ip == ip || val.stream_name == stream_name{ - if (val.audio && val.video) || (val.audio && audio) || (val.video && video){ + for val in receivers.values_mut() { + if val.ip == ip || val.stream_name == stream_name { + if (val.audio && val.video) || (val.audio && audio) || (val.video && video) { continue; - } - else { + } else { if video { val.video = video; - } - else{ + } else { val.audio = audio; } return val.id; } } - } unsafe { - if !NDIlib_initialize() { - gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_initialize error"]); + gst_element_error!( + element, + gst::CoreError::Negotiation, + ["Cannot run NDI: NDIlib_initialize error"] + ); // return false; return 0; } @@ -118,7 +116,11 @@ fn connect_ndi(cat: gst::DebugCategory , element: &BaseSrc, ip: String, stream let pNDI_find = NDIlib_find_create_v2(&NDI_find_create_desc); //let ip_ptr = CString::new(ip.clone()).unwrap(); if pNDI_find.is_null() { - gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_find_create_v2 error"]); + gst_element_error!( + element, + gst::CoreError::Negotiation, + ["Cannot run NDI: NDIlib_find_create_v2 error"] + ); // return false; return 0; } @@ -132,37 +134,57 @@ fn connect_ndi(cat: gst::DebugCategory , element: &BaseSrc, ip: String, stream // We need at least one source if p_sources.is_null() { - gst_element_error!(element, gst::CoreError::Negotiation, ["Error getting NDIlib_find_get_current_sources"]); + gst_element_error!( + element, + gst::CoreError::Negotiation, + ["Error getting NDIlib_find_get_current_sources"] + ); // return false; return 0; } let mut no_source: isize = -1; - for i in 0..total_sources as isize{ - if CStr::from_ptr((*p_sources.offset(i)).p_ndi_name).to_string_lossy().into_owned() == stream_name || - CStr::from_ptr((*p_sources.offset(i)).p_ip_address).to_string_lossy().into_owned() == ip{ + for i in 0..total_sources as isize { + if CStr::from_ptr((*p_sources.offset(i)).p_ndi_name) + .to_string_lossy() + .into_owned() + == stream_name + || CStr::from_ptr((*p_sources.offset(i)).p_ip_address) + .to_string_lossy() + .into_owned() + == ip + { no_source = i; break; } } - if no_source == -1 { + if no_source == -1 { gst_element_error!(element, gst::ResourceError::OpenRead, ["Stream not found"]); // return false; return 0; } - gst_debug!(cat, obj: element, "Total sources in network {}: Connecting to NDI source with name '{}' and address '{}'", total_sources, - CStr::from_ptr((*p_sources.offset(no_source)).p_ndi_name) - .to_string_lossy() - .into_owned(), - CStr::from_ptr((*p_sources.offset(no_source)).p_ip_address) - .to_string_lossy() - .into_owned()); + gst_debug!( + cat, + obj: element, + "Total sources in network {}: Connecting to NDI source with name '{}' and address '{}'", + total_sources, + CStr::from_ptr((*p_sources.offset(no_source)).p_ndi_name) + .to_string_lossy() + .into_owned(), + CStr::from_ptr((*p_sources.offset(no_source)).p_ip_address) + .to_string_lossy() + .into_owned() + ); let source = *p_sources.offset(no_source).clone(); - let source_ip = CStr::from_ptr(source.p_ip_address).to_string_lossy().into_owned(); - let source_name = CStr::from_ptr(source.p_ndi_name).to_string_lossy().into_owned(); + let source_ip = CStr::from_ptr(source.p_ip_address) + .to_string_lossy() + .into_owned(); + let source_name = CStr::from_ptr(source.p_ndi_name) + .to_string_lossy() + .into_owned(); // We now have at least one source, so we create a receiver to look at it. // We tell it that we prefer YCbCr video since it is more efficient for us. If the source has an alpha channel @@ -177,7 +199,11 @@ fn connect_ndi(cat: gst::DebugCategory , element: &BaseSrc, ip: String, stream let pNDI_recv = NDIlib_recv_create_v3(&NDI_recv_create_desc); if pNDI_recv.is_null() { //println!("Cannot run NDI: NDIlib_recv_create_v3 error."); - gst_element_error!(element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_recv_create_v3 error"]); + gst_element_error!( + element, + gst::CoreError::Negotiation, + ["Cannot run NDI: NDIlib_recv_create_v3 error"] + ); // return false; return 0; } @@ -202,7 +228,17 @@ fn connect_ndi(cat: gst::DebugCategory , element: &BaseSrc, ip: String, stream NDIlib_recv_send_metadata(pNDI_recv, &enable_hw_accel); id_receiver += 1; - receivers.insert(id_receiver, ndi_receiver_info{stream_name: source_name.clone(), ip: source_ip.clone(), video:video, audio: audio, ndi_instance: NdiInstance{recv: pNDI_recv}, id: id_receiver}); + receivers.insert( + id_receiver, + ndi_receiver_info { + stream_name: source_name.clone(), + ip: source_ip.clone(), + video: video, + audio: audio, + ndi_instance: NdiInstance { recv: pNDI_recv }, + id: id_receiver, + }, + ); // let start = SystemTime::now(); // let since_the_epoch = start.duration_since(UNIX_EPOCH) @@ -215,16 +251,15 @@ fn connect_ndi(cat: gst::DebugCategory , element: &BaseSrc, ip: String, stream } } -fn stop_ndi(cat: gst::DebugCategory , element: &BaseSrc, id: i8) -> bool{ +fn stop_ndi(cat: gst::DebugCategory, element: &BaseSrc, id: i8) -> bool { gst_debug!(cat, obj: element, "Closing NDI connection..."); let mut receivers = hashmap_receivers.lock().unwrap(); { let val = receivers.get_mut(&id).unwrap(); - if val.video && val.audio{ - if element.get_name().contains("audiosrc"){ + if val.video && val.audio { + if element.get_name().contains("audiosrc") { val.audio = false; - } - else{ + } else { val.video = false; } return true; @@ -232,7 +267,7 @@ fn stop_ndi(cat: gst::DebugCategory , element: &BaseSrc, id: i8) -> bool{ let recv = &val.ndi_instance; let pNDI_recv = recv.recv; - unsafe{ + unsafe { NDIlib_recv_destroy(pNDI_recv); // ndi_struct.recv = None; NDIlib_destroy(); diff --git a/gst-plugin-ndi/src/ndiaudiosrc.rs b/gst-plugin-ndi/src/ndiaudiosrc.rs index e56ffd97..57b28db5 100644 --- a/gst-plugin-ndi/src/ndiaudiosrc.rs +++ b/gst-plugin-ndi/src/ndiaudiosrc.rs @@ -6,19 +6,19 @@ use gst::prelude::*; use gst_audio; use gst_base::prelude::*; +use gobject_subclass::object::*; use gst_plugin::base_src::*; use gst_plugin::element::*; -use gobject_subclass::object::*; use std::sync::Mutex; use std::{i32, u32}; use std::ptr; -use ndisys::*; use connect_ndi; -use stop_ndi; use ndi_struct; +use ndisys::*; +use stop_ndi; use hashmap_receivers; @@ -42,20 +42,20 @@ impl Default for Settings { // Metadata for the properties static PROPERTIES: [Property; 2] = [ -Property::String( - "stream-name", - "Sream Name", - "Name of the streaming device", - None, - PropertyMutability::ReadWrite, -), -Property::String( - "ip", - "Stream IP", - "Stream IP", - None, - PropertyMutability::ReadWrite, -), + Property::String( + "stream-name", + "Sream Name", + "Name of the streaming device", + None, + PropertyMutability::ReadWrite, + ), + Property::String( + "ip", + "Stream IP", + "Stream IP", + None, + PropertyMutability::ReadWrite, + ), ]; // Stream-specific state, i.e. audio format configuration @@ -66,13 +66,11 @@ struct State { impl Default for State { fn default() -> State { - State { - info: None, - } + State { info: None } } } -struct TimestampData{ +struct TimestampData { offset: u64, } @@ -100,9 +98,7 @@ impl NdiAudioSrc { ), settings: Mutex::new(Default::default()), state: Mutex::new(Default::default()), - timestamp_data: Mutex::new(TimestampData{ - offset: 0, - }), + timestamp_data: Mutex::new(TimestampData { offset: 0 }), }) } @@ -129,362 +125,383 @@ impl NdiAudioSrc { let caps = gst::Caps::new_simple( "audio/x-raw", &[ - ( - "format", - &gst::List::new(&[ - //TODO add all formats? - &gst_audio::AUDIO_FORMAT_F32.to_string(), - &gst_audio::AUDIO_FORMAT_F64.to_string(), - &gst_audio::AUDIO_FORMAT_S16.to_string(), + ( + "format", + &gst::List::new(&[ + //TODO add all formats? + &gst_audio::AUDIO_FORMAT_F32.to_string(), + &gst_audio::AUDIO_FORMAT_F64.to_string(), + &gst_audio::AUDIO_FORMAT_S16.to_string(), ]), ), ("rate", &gst::IntRange::::new(1, i32::MAX)), ("channels", &gst::IntRange::::new(1, i32::MAX)), ("layout", &"interleaved"), - ], - ); - // The src pad template must be named "src" for basesrc - // and specific a pad that is always there - let src_pad_template = gst::PadTemplate::new( - "src", - gst::PadDirection::Src, - gst::PadPresence::Always, - &caps, - //&gst::Caps::new_any(), - ); - klass.add_pad_template(src_pad_template); + ], + ); + // The src pad template must be named "src" for basesrc + // and specific a pad that is always there + let src_pad_template = gst::PadTemplate::new( + "src", + gst::PadDirection::Src, + gst::PadPresence::Always, + &caps, + //&gst::Caps::new_any(), + ); + klass.add_pad_template(src_pad_template); - // Install all our properties - klass.install_properties(&PROPERTIES); + // Install all our properties + klass.install_properties(&PROPERTIES); + } +} + +// Virtual methods of GObject itself +impl ObjectImpl for NdiAudioSrc { + // Called whenever a value of a property is changed. It can be called + // at any time from any thread. + fn set_property(&self, obj: &glib::Object, id: u32, value: &glib::Value) { + let prop = &PROPERTIES[id as usize]; + let element = obj.clone().downcast::().unwrap(); + + match *prop { + Property::String("stream-name", ..) => { + let mut settings = self.settings.lock().unwrap(); + let stream_name = value.get().unwrap(); + gst_debug!( + self.cat, + obj: &element, + "Changing stream-name from {} to {}", + settings.stream_name, + stream_name + ); + settings.stream_name = stream_name; + drop(settings); + + let _ = + element.post_message(&gst::Message::new_latency().src(Some(&element)).build()); + } + Property::String("ip", ..) => { + let mut settings = self.settings.lock().unwrap(); + let ip = value.get().unwrap(); + gst_debug!( + self.cat, + obj: &element, + "Changing ip from {} to {}", + settings.ip, + ip + ); + settings.ip = ip; + drop(settings); + + let _ = + element.post_message(&gst::Message::new_latency().src(Some(&element)).build()); + } + _ => unimplemented!(), } } + // Called whenever a value of a property is read. It can be called + // at any time from any thread. + fn get_property(&self, _obj: &glib::Object, id: u32) -> Result { + let prop = &PROPERTIES[id as usize]; - // Virtual methods of GObject itself - impl ObjectImpl for NdiAudioSrc { - // Called whenever a value of a property is changed. It can be called - // at any time from any thread. - fn set_property(&self, obj: &glib::Object, id: u32, value: &glib::Value) { - let prop = &PROPERTIES[id as usize]; - let element = obj.clone().downcast::().unwrap(); - - match *prop { - Property::String("stream-name", ..) => { - let mut settings = self.settings.lock().unwrap(); - let stream_name = value.get().unwrap(); - gst_debug!( - self.cat, - obj: &element, - "Changing stream-name from {} to {}", - settings.stream_name, - stream_name - ); - settings.stream_name = stream_name; - drop(settings); - - let _ = - element.post_message(&gst::Message::new_latency().src(Some(&element)).build()); - }, - Property::String("ip", ..) => { - let mut settings = self.settings.lock().unwrap(); - let ip = value.get().unwrap(); - gst_debug!( - self.cat, - obj: &element, - "Changing ip from {} to {}", - settings.ip, - ip - ); - settings.ip = ip; - drop(settings); - - let _ = - element.post_message(&gst::Message::new_latency().src(Some(&element)).build()); - } - _ => unimplemented!(), - } - } - - // Called whenever a value of a property is read. It can be called - // at any time from any thread. - fn get_property(&self, _obj: &glib::Object, id: u32) -> Result { - let prop = &PROPERTIES[id as usize]; - - match *prop { - Property::String("stream-name", ..) => { - let settings = self.settings.lock().unwrap(); - //TODO to_value supongo que solo funciona con numeros - Ok(settings.stream_name.to_value()) - }, - Property::String("ip", ..) => { - let settings = self.settings.lock().unwrap(); - //TODO to_value supongo que solo funciona con numeros - Ok(settings.ip.to_value()) - } - _ => unimplemented!(), - } - } - } - - // Virtual methods of gst::Element. We override none - impl ElementImpl for NdiAudioSrc { - fn change_state(&self, element: &BaseSrc, transition: gst::StateChange) -> gst::StateChangeReturn { - if transition == gst::StateChange::PausedToPlaying{ - let receivers = hashmap_receivers.lock().unwrap(); + match *prop { + Property::String("stream-name", ..) => { let settings = self.settings.lock().unwrap(); - - let receiver = receivers.get(&settings.id_receiver).unwrap(); - let recv = &receiver.ndi_instance; - let pNDI_recv = recv.recv; - - let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); - - let mut frame_type: NDIlib_frame_type_e = NDIlib_frame_type_e::NDIlib_frame_type_none; - unsafe{ - while frame_type != NDIlib_frame_type_e::NDIlib_frame_type_audio{ - frame_type = NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000); - } - - if ndi_struct.initial_timestamp <= audio_frame.timestamp as u64 || ndi_struct.initial_timestamp == 0{ - ndi_struct.initial_timestamp = audio_frame.timestamp as u64; - } - } + //TODO to_value supongo que solo funciona con numeros + Ok(settings.stream_name.to_value()) } - element.parent_change_state(transition) + Property::String("ip", ..) => { + let settings = self.settings.lock().unwrap(); + //TODO to_value supongo que solo funciona con numeros + Ok(settings.ip.to_value()) + } + _ => unimplemented!(), } } +} - // Virtual methods of gst_base::BaseSrc - impl BaseSrcImpl for NdiAudioSrc { - // Called whenever the input/output caps are changing, i.e. in the very beginning before data - // flow happens and whenever the situation in the pipeline is changing. All buffers after this - // call have the caps given here. - // - // We simply remember the resulting AudioInfo from the caps to be able to use this for knowing - // the sample rate, etc. when creating buffers - fn set_caps(&self, element: &BaseSrc, caps: &gst::CapsRef) -> bool { - - let info = match gst_audio::AudioInfo::from_caps(caps) { - None => return false, - Some(info) => info, - }; - - gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); - - // TODO Puede que falle si no creamos la estructura de cero, pero si lo hacemos no podemos poner recv a none - let mut state = self.state.lock().unwrap(); - state.info = Some(info); - - true - } - - // Called when starting, so we can initialize all stream-related state to its defaults - fn start(&self, element: &BaseSrc) -> bool { - // Reset state - *self.state.lock().unwrap() = Default::default(); - - let mut settings = self.settings.lock().unwrap(); - settings.id_receiver = connect_ndi(self.cat, element, settings.ip.clone(), settings.stream_name.clone()); - if settings.id_receiver == 0{ - return false; - } - else{ - return true; - } - } - - // Called when shutting down the element so we can release all stream-related state - fn stop(&self, element: &BaseSrc) -> bool { - // Reset state - *self.state.lock().unwrap() = Default::default(); - - let settings = self.settings.lock().unwrap(); - stop_ndi(self.cat, element, settings.id_receiver.clone()); - // Commented because when adding ndi destroy stopped in this line - //*self.state.lock().unwrap() = Default::default(); - true - } - - fn query(&self, element: &BaseSrc, query: &mut gst::QueryRef) -> bool { - use gst::QueryView; - - match query.view_mut() { - // We only work in Push mode. In Pull mode, create() could be called with - // arbitrary offsets and we would have to produce for that specific offset - QueryView::Scheduling(ref mut q) => { - q.set(gst::SchedulingFlags::SEQUENTIAL, 1, -1, 0); - q.add_scheduling_modes(&[gst::PadMode::Push]); - return true; - } - // In Live mode we will have a latency equal to the number of samples in each buffer. - // We can't output samples before they were produced, and the last sample of a buffer - // is produced that much after the beginning, leading to this latency calculation - // QueryView::Latency(ref mut q) => { - // let settings = &*self.settings.lock().unwrap(); - // let state = self.state.lock().unwrap(); - // - // if let Some(ref _info) = state.info { - // // let latency = gst::SECOND - // // .mul_div_floor(1024 as u64, _info.rate() as u64) - // // .unwrap(); - // let latency = gst::SECOND.mul_div_floor(3 as u64, 2 as u64).unwrap(); - // // let latency = gst::SECOND - // // .mul_div_floor(1 as u64, 30 as u64) - // // .unwrap(); - // // gst_debug!(self.cat, obj: element, "Returning latency {}", latency); - // let max = latency * 1843200; - // // println!("{:?}", latency); - // // println!("{:?}",max); - // q.set(true, latency, max); - // return true; - // } else { - // return false; - // } - // } - _ => (), - } - BaseSrcBase::parent_query(element, query) - } - - fn fixate(&self, element: &BaseSrc, caps: gst::Caps) -> gst::Caps { - //We need to set the correct caps resolution and framerate +// Virtual methods of gst::Element. We override none +impl ElementImpl for NdiAudioSrc { + fn change_state( + &self, + element: &BaseSrc, + transition: gst::StateChange, + ) -> gst::StateChangeReturn { + if transition == gst::StateChange::PausedToPlaying { let receivers = hashmap_receivers.lock().unwrap(); let settings = self.settings.lock().unwrap(); let receiver = receivers.get(&settings.id_receiver).unwrap(); - let recv = &receiver.ndi_instance; let pNDI_recv = recv.recv; let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); let mut frame_type: NDIlib_frame_type_e = NDIlib_frame_type_e::NDIlib_frame_type_none; - while frame_type != NDIlib_frame_type_e::NDIlib_frame_type_audio{ - unsafe{ - frame_type = NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000); - } - } - let mut caps = gst::Caps::truncate(caps); - { - let caps = caps.make_mut(); - let s = caps.get_mut_structure(0).unwrap(); - //s.fixate_field_nearest_int("rate", audio_frame.sample_rate); - s.fixate_field_nearest_int("rate", audio_frame.sample_rate / audio_frame.no_channels); - s.fixate_field_nearest_int("channels", audio_frame.no_channels); - } - - // Let BaseSrc fixate anything else for us. We could've alternatively have - // called Caps::fixate() here - element.parent_fixate(caps) - // } - } - - //Creates the audio buffers - fn create( - &self, - element: &BaseSrc, - _offset: u64, - _length: u32, - ) -> Result { - // Keep a local copy of the values of all our properties at this very moment. This - // ensures that the mutex is never locked for long and the application wouldn't - // have to block until this function returns when getting/setting property values - let _settings = &*self.settings.lock().unwrap(); - - let mut timestamp_data = self.timestamp_data.lock().unwrap(); - // Get a locked reference to our state, i.e. the input and output AudioInfo - let state = self.state.lock().unwrap(); - let _info = match state.info { - None => { - gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); - return Err(gst::FlowReturn::NotNegotiated); - } - Some(ref info) => info.clone(), - }; - let receivers = hashmap_receivers.lock().unwrap(); - - let recv = &receivers.get(&_settings.id_receiver).unwrap().ndi_instance; - let pNDI_recv = recv.recv; - - let pts: u64; - let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); - - unsafe{ - let time = ndi_struct.initial_timestamp; - - let mut skip_frame = true; - while skip_frame { - let frame_type = NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000,); - if frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none || frame_type == NDIlib_frame_type_e::NDIlib_frame_type_error { - gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type none received, assuming that the source closed the stream...."]); - return Err(gst::FlowReturn::CustomError); - } - if time >= (audio_frame.timestamp as u64){ - gst_debug!(self.cat, obj: element, "Frame timestamp ({:?}) is lower than received in the first frame from NDI ({:?}), so skiping...", (audio_frame.timestamp as u64), time); - } - else{ - skip_frame = false; - } + unsafe { + while frame_type != NDIlib_frame_type_e::NDIlib_frame_type_audio { + frame_type = NDIlib_recv_capture_v2( + pNDI_recv, + ptr::null(), + &audio_frame, + ptr::null(), + 1000, + ); } - pts = audio_frame.timestamp as u64 - time; - - let buff_size = ((audio_frame.channel_stride_in_bytes)) as usize; - let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); + if ndi_struct.initial_timestamp <= audio_frame.timestamp as u64 + || ndi_struct.initial_timestamp == 0 { - let vec = Vec::from_raw_parts(audio_frame.p_data as *mut u8, buff_size, buff_size); - let pts: gst::ClockTime = (pts * 100).into(); - - let duration: gst::ClockTime = (((audio_frame.no_samples as f64 / audio_frame.sample_rate as f64) * 1000000000.0) as u64).into(); - let buffer = buffer.get_mut().unwrap(); - - if ndi_struct.start_pts == gst::ClockTime(Some(0)){ - ndi_struct.start_pts = element.get_clock().unwrap().get_time() - element.get_base_time(); - } - - buffer.set_pts(pts + ndi_struct.start_pts); - buffer.set_duration(duration); - buffer.set_offset(timestamp_data.offset); - buffer.set_offset_end(timestamp_data.offset + 1); - timestamp_data.offset = timestamp_data.offset + 1; - buffer.copy_from_slice(0, &vec).unwrap(); + ndi_struct.initial_timestamp = audio_frame.timestamp as u64; } - - gst_debug!(self.cat, obj: element, "Produced buffer {:?}", buffer); - - Ok(buffer) } } + element.parent_change_state(transition) + } +} + +// Virtual methods of gst_base::BaseSrc +impl BaseSrcImpl for NdiAudioSrc { + // Called whenever the input/output caps are changing, i.e. in the very beginning before data + // flow happens and whenever the situation in the pipeline is changing. All buffers after this + // call have the caps given here. + // + // We simply remember the resulting AudioInfo from the caps to be able to use this for knowing + // the sample rate, etc. when creating buffers + fn set_caps(&self, element: &BaseSrc, caps: &gst::CapsRef) -> bool { + let info = match gst_audio::AudioInfo::from_caps(caps) { + None => return false, + Some(info) => info, + }; + + gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); + + // TODO Puede que falle si no creamos la estructura de cero, pero si lo hacemos no podemos poner recv a none + let mut state = self.state.lock().unwrap(); + state.info = Some(info); + + true } - // This zero-sized struct is containing the static metadata of our element. It is only necessary to - // be able to implement traits on it, but e.g. a plugin that registers multiple elements with the - // same code would use this struct to store information about the concrete element. An example of - // this would be a plugin that wraps around a library that has multiple decoders with the same API, - // but wants (as it should) a separate element registered for each decoder. - struct NdiAudioSrcStatic; + // Called when starting, so we can initialize all stream-related state to its defaults + fn start(&self, element: &BaseSrc) -> bool { + // Reset state + *self.state.lock().unwrap() = Default::default(); - // The basic trait for registering the type: This returns a name for the type and registers the - // instance and class initializations functions with the type system, thus hooking everything - // together. - impl ImplTypeStatic for NdiAudioSrcStatic { - fn get_name(&self) -> &str { - "NdiAudioSrc" - } - - fn new(&self, element: &BaseSrc) -> Box> { - NdiAudioSrc::new(element) - } - - fn class_init(&self, klass: &mut BaseSrcClass) { - NdiAudioSrc::class_init(klass); + let mut settings = self.settings.lock().unwrap(); + settings.id_receiver = connect_ndi( + self.cat, + element, + settings.ip.clone(), + settings.stream_name.clone(), + ); + if settings.id_receiver == 0 { + return false; + } else { + return true; } } - // Registers the type for our element, and then registers in GStreamer under - // the name NdiAudioSrc for being able to instantiate it via e.g. - // gst::ElementFactory::make(). - pub fn register(plugin: &gst::Plugin) { - let type_ = register_type(NdiAudioSrcStatic); - gst::Element::register(plugin, "ndiaudiosrc", 0, type_); + // Called when shutting down the element so we can release all stream-related state + fn stop(&self, element: &BaseSrc) -> bool { + // Reset state + *self.state.lock().unwrap() = Default::default(); + + let settings = self.settings.lock().unwrap(); + stop_ndi(self.cat, element, settings.id_receiver.clone()); + // Commented because when adding ndi destroy stopped in this line + //*self.state.lock().unwrap() = Default::default(); + true } + + fn query(&self, element: &BaseSrc, query: &mut gst::QueryRef) -> bool { + use gst::QueryView; + + match query.view_mut() { + // We only work in Push mode. In Pull mode, create() could be called with + // arbitrary offsets and we would have to produce for that specific offset + QueryView::Scheduling(ref mut q) => { + q.set(gst::SchedulingFlags::SEQUENTIAL, 1, -1, 0); + q.add_scheduling_modes(&[gst::PadMode::Push]); + return true; + } + // In Live mode we will have a latency equal to the number of samples in each buffer. + // We can't output samples before they were produced, and the last sample of a buffer + // is produced that much after the beginning, leading to this latency calculation + // QueryView::Latency(ref mut q) => { + // let settings = &*self.settings.lock().unwrap(); + // let state = self.state.lock().unwrap(); + // + // if let Some(ref _info) = state.info { + // // let latency = gst::SECOND + // // .mul_div_floor(1024 as u64, _info.rate() as u64) + // // .unwrap(); + // let latency = gst::SECOND.mul_div_floor(3 as u64, 2 as u64).unwrap(); + // // let latency = gst::SECOND + // // .mul_div_floor(1 as u64, 30 as u64) + // // .unwrap(); + // // gst_debug!(self.cat, obj: element, "Returning latency {}", latency); + // let max = latency * 1843200; + // // println!("{:?}", latency); + // // println!("{:?}",max); + // q.set(true, latency, max); + // return true; + // } else { + // return false; + // } + // } + _ => (), + } + BaseSrcBase::parent_query(element, query) + } + + fn fixate(&self, element: &BaseSrc, caps: gst::Caps) -> gst::Caps { + //We need to set the correct caps resolution and framerate + let receivers = hashmap_receivers.lock().unwrap(); + let settings = self.settings.lock().unwrap(); + + let receiver = receivers.get(&settings.id_receiver).unwrap(); + + let recv = &receiver.ndi_instance; + let pNDI_recv = recv.recv; + + let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); + + let mut frame_type: NDIlib_frame_type_e = NDIlib_frame_type_e::NDIlib_frame_type_none; + while frame_type != NDIlib_frame_type_e::NDIlib_frame_type_audio { + unsafe { + frame_type = + NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000); + } + } + let mut caps = gst::Caps::truncate(caps); + { + let caps = caps.make_mut(); + let s = caps.get_mut_structure(0).unwrap(); + //s.fixate_field_nearest_int("rate", audio_frame.sample_rate); + s.fixate_field_nearest_int("rate", audio_frame.sample_rate / audio_frame.no_channels); + s.fixate_field_nearest_int("channels", audio_frame.no_channels); + } + + // Let BaseSrc fixate anything else for us. We could've alternatively have + // called Caps::fixate() here + element.parent_fixate(caps) + // } + } + + //Creates the audio buffers + fn create( + &self, + element: &BaseSrc, + _offset: u64, + _length: u32, + ) -> Result { + // Keep a local copy of the values of all our properties at this very moment. This + // ensures that the mutex is never locked for long and the application wouldn't + // have to block until this function returns when getting/setting property values + let _settings = &*self.settings.lock().unwrap(); + + let mut timestamp_data = self.timestamp_data.lock().unwrap(); + // Get a locked reference to our state, i.e. the input and output AudioInfo + let state = self.state.lock().unwrap(); + let _info = match state.info { + None => { + gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); + return Err(gst::FlowReturn::NotNegotiated); + } + Some(ref info) => info.clone(), + }; + let receivers = hashmap_receivers.lock().unwrap(); + + let recv = &receivers.get(&_settings.id_receiver).unwrap().ndi_instance; + let pNDI_recv = recv.recv; + + let pts: u64; + let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); + + unsafe { + let time = ndi_struct.initial_timestamp; + + let mut skip_frame = true; + while skip_frame { + let frame_type = + NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000); + if frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none + || frame_type == NDIlib_frame_type_e::NDIlib_frame_type_error + { + gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type none received, assuming that the source closed the stream...."]); + return Err(gst::FlowReturn::CustomError); + } + if time >= (audio_frame.timestamp as u64) { + gst_debug!(self.cat, obj: element, "Frame timestamp ({:?}) is lower than received in the first frame from NDI ({:?}), so skiping...", (audio_frame.timestamp as u64), time); + } else { + skip_frame = false; + } + } + + pts = audio_frame.timestamp as u64 - time; + + let buff_size = (audio_frame.channel_stride_in_bytes) as usize; + let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); + { + let vec = Vec::from_raw_parts(audio_frame.p_data as *mut u8, buff_size, buff_size); + let pts: gst::ClockTime = (pts * 100).into(); + + let duration: gst::ClockTime = (((audio_frame.no_samples as f64 + / audio_frame.sample_rate as f64) + * 1000000000.0) as u64) + .into(); + let buffer = buffer.get_mut().unwrap(); + + if ndi_struct.start_pts == gst::ClockTime(Some(0)) { + ndi_struct.start_pts = + element.get_clock().unwrap().get_time() - element.get_base_time(); + } + + buffer.set_pts(pts + ndi_struct.start_pts); + buffer.set_duration(duration); + buffer.set_offset(timestamp_data.offset); + buffer.set_offset_end(timestamp_data.offset + 1); + timestamp_data.offset = timestamp_data.offset + 1; + buffer.copy_from_slice(0, &vec).unwrap(); + } + + gst_debug!(self.cat, obj: element, "Produced buffer {:?}", buffer); + + Ok(buffer) + } + } +} + +// This zero-sized struct is containing the static metadata of our element. It is only necessary to +// be able to implement traits on it, but e.g. a plugin that registers multiple elements with the +// same code would use this struct to store information about the concrete element. An example of +// this would be a plugin that wraps around a library that has multiple decoders with the same API, +// but wants (as it should) a separate element registered for each decoder. +struct NdiAudioSrcStatic; + +// The basic trait for registering the type: This returns a name for the type and registers the +// instance and class initializations functions with the type system, thus hooking everything +// together. +impl ImplTypeStatic for NdiAudioSrcStatic { + fn get_name(&self) -> &str { + "NdiAudioSrc" + } + + fn new(&self, element: &BaseSrc) -> Box> { + NdiAudioSrc::new(element) + } + + fn class_init(&self, klass: &mut BaseSrcClass) { + NdiAudioSrc::class_init(klass); + } +} + +// Registers the type for our element, and then registers in GStreamer under +// the name NdiAudioSrc for being able to instantiate it via e.g. +// gst::ElementFactory::make(). +pub fn register(plugin: &gst::Plugin) { + let type_ = register_type(NdiAudioSrcStatic); + gst::Element::register(plugin, "ndiaudiosrc", 0, type_); +} diff --git a/gst-plugin-ndi/src/ndisys.rs b/gst-plugin-ndi/src/ndisys.rs index ec2c8d38..50298609 100644 --- a/gst-plugin-ndi/src/ndisys.rs +++ b/gst-plugin-ndi/src/ndisys.rs @@ -157,7 +157,6 @@ pub struct NdiInstance { unsafe impl ::std::marker::Send for NdiInstance {} - #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct NDIlib_tally_t { diff --git a/gst-plugin-ndi/src/ndivideosrc.rs b/gst-plugin-ndi/src/ndivideosrc.rs index 83bde839..871b2282 100644 --- a/gst-plugin-ndi/src/ndivideosrc.rs +++ b/gst-plugin-ndi/src/ndivideosrc.rs @@ -3,23 +3,23 @@ use glib; use gst; use gst::prelude::*; -use gst_video; -use gst_base::prelude::*; use gst::Fraction; +use gst_base::prelude::*; +use gst_video; +use gobject_subclass::object::*; use gst_plugin::base_src::*; use gst_plugin::element::*; -use gobject_subclass::object::*; use std::sync::Mutex; use std::{i32, u32}; use std::ptr; -use ndisys::*; use connect_ndi; -use stop_ndi; use ndi_struct; +use ndisys::*; +use stop_ndi; use hashmap_receivers; @@ -45,20 +45,20 @@ impl Default for Settings { // Metadata for the properties static PROPERTIES: [Property; 2] = [ -Property::String( - "stream-name", - "Sream Name", - "Name of the streaming device", - None, - PropertyMutability::ReadWrite, -), -Property::String( - "ip", - "Stream IP", - "Stream IP", - None, - PropertyMutability::ReadWrite, -), + Property::String( + "stream-name", + "Sream Name", + "Name of the streaming device", + None, + PropertyMutability::ReadWrite, + ), + Property::String( + "ip", + "Stream IP", + "Stream IP", + None, + PropertyMutability::ReadWrite, + ), ]; // Stream-specific state, i.e. audio format configuration @@ -69,13 +69,11 @@ struct State { impl Default for State { fn default() -> State { - State { - info: None, - } + State { info: None } } } -struct TimestampData{ +struct TimestampData { offset: u64, } @@ -103,9 +101,7 @@ impl NdiVideoSrc { ), settings: Mutex::new(Default::default()), state: Mutex::new(Default::default()), - timestamp_data: Mutex::new(TimestampData{ - offset: 0, - }), + timestamp_data: Mutex::new(TimestampData { offset: 0 }), }) } @@ -132,13 +128,13 @@ impl NdiVideoSrc { let caps = gst::Caps::new_simple( "video/x-raw", &[ - ( - "format", - &gst::List::new(&[ - //TODO add all formats? - &gst_video::VideoFormat::Uyvy.to_string(), - //&gst_video::VideoFormat::Rgb.to_string(), - //&gst_video::VideoFormat::Gray8.to_string(), + ( + "format", + &gst::List::new(&[ + //TODO add all formats? + &gst_video::VideoFormat::Uyvy.to_string(), + //&gst_video::VideoFormat::Rgb.to_string(), + //&gst_video::VideoFormat::Gray8.to_string(), ]), ), ("width", &gst::IntRange::::new(0, i32::MAX)), @@ -150,220 +146,96 @@ impl NdiVideoSrc { gst::Fraction::new(i32::MAX, 1), ), ), - ], - ); - // The src pad template must be named "src" for basesrc - // and specific a pad that is always there - let src_pad_template = gst::PadTemplate::new( - "src", - gst::PadDirection::Src, - gst::PadPresence::Always, - &caps, - //&gst::Caps::new_any(), - ); - klass.add_pad_template(src_pad_template); + ], + ); + // The src pad template must be named "src" for basesrc + // and specific a pad that is always there + let src_pad_template = gst::PadTemplate::new( + "src", + gst::PadDirection::Src, + gst::PadPresence::Always, + &caps, + //&gst::Caps::new_any(), + ); + klass.add_pad_template(src_pad_template); - // Install all our properties - klass.install_properties(&PROPERTIES); + // Install all our properties + klass.install_properties(&PROPERTIES); + } +} + +// Virtual methods of GObject itself +impl ObjectImpl for NdiVideoSrc { + // Called whenever a value of a property is changed. It can be called + // at any time from any thread. + fn set_property(&self, obj: &glib::Object, id: u32, value: &glib::Value) { + let prop = &PROPERTIES[id as usize]; + let element = obj.clone().downcast::().unwrap(); + + match *prop { + Property::String("stream-name", ..) => { + let mut settings = self.settings.lock().unwrap(); + let stream_name = value.get().unwrap(); + gst_debug!( + self.cat, + obj: &element, + "Changing stream-name from {} to {}", + settings.stream_name, + stream_name + ); + settings.stream_name = stream_name; + drop(settings); + + // let _ = + // element.post_message(&gst::Message::new_latency().src(Some(&element)).build()); + } + Property::String("ip", ..) => { + let mut settings = self.settings.lock().unwrap(); + let ip = value.get().unwrap(); + gst_debug!( + self.cat, + obj: &element, + "Changing ip from {} to {}", + settings.ip, + ip + ); + settings.ip = ip; + drop(settings); + + // let _ = + // element.post_message(&gst::Message::new_latency().src(Some(&element)).build()); + } + _ => unimplemented!(), } } + // Called whenever a value of a property is read. It can be called + // at any time from any thread. + fn get_property(&self, _obj: &glib::Object, id: u32) -> Result { + let prop = &PROPERTIES[id as usize]; - - // Virtual methods of GObject itself - impl ObjectImpl for NdiVideoSrc { - // Called whenever a value of a property is changed. It can be called - // at any time from any thread. - fn set_property(&self, obj: &glib::Object, id: u32, value: &glib::Value) { - let prop = &PROPERTIES[id as usize]; - let element = obj.clone().downcast::().unwrap(); - - match *prop { - Property::String("stream-name", ..) => { - let mut settings = self.settings.lock().unwrap(); - let stream_name = value.get().unwrap(); - gst_debug!( - self.cat, - obj: &element, - "Changing stream-name from {} to {}", - settings.stream_name, - stream_name - ); - settings.stream_name = stream_name; - drop(settings); - - // let _ = - // element.post_message(&gst::Message::new_latency().src(Some(&element)).build()); - }, - Property::String("ip", ..) => { - let mut settings = self.settings.lock().unwrap(); - let ip = value.get().unwrap(); - gst_debug!( - self.cat, - obj: &element, - "Changing ip from {} to {}", - settings.ip, - ip - ); - settings.ip = ip; - drop(settings); - - // let _ = - // element.post_message(&gst::Message::new_latency().src(Some(&element)).build()); - } - _ => unimplemented!(), - } - } - - // Called whenever a value of a property is read. It can be called - // at any time from any thread. - fn get_property(&self, _obj: &glib::Object, id: u32) -> Result { - let prop = &PROPERTIES[id as usize]; - - match *prop { - Property::String("stream-name", ..) => { - let settings = self.settings.lock().unwrap(); - Ok(settings.stream_name.to_value()) - }, - Property::String("ip", ..) => { - let settings = self.settings.lock().unwrap(); - Ok(settings.ip.to_value()) - } - _ => unimplemented!(), - } - } - } - - // Virtual methods of gst::Element. We override none - impl ElementImpl for NdiVideoSrc { - fn change_state(&self, element: &BaseSrc, transition: gst::StateChange) -> gst::StateChangeReturn { - if transition == gst::StateChange::PausedToPlaying{ - let receivers = hashmap_receivers.lock().unwrap(); + match *prop { + Property::String("stream-name", ..) => { let settings = self.settings.lock().unwrap(); - - let receiver = receivers.get(&settings.id_receiver).unwrap(); - let recv = &receiver.ndi_instance; - let pNDI_recv = recv.recv; - - let video_frame: NDIlib_video_frame_v2_t = Default::default(); - - let mut frame_type: NDIlib_frame_type_e = NDIlib_frame_type_e::NDIlib_frame_type_none; - unsafe{ - while frame_type != NDIlib_frame_type_e::NDIlib_frame_type_video{ - frame_type = NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000); - } - - if ndi_struct.initial_timestamp <= video_frame.timestamp as u64 || ndi_struct.initial_timestamp == 0{ - ndi_struct.initial_timestamp = video_frame.timestamp as u64; - } - } + Ok(settings.stream_name.to_value()) } - element.parent_change_state(transition) + Property::String("ip", ..) => { + let settings = self.settings.lock().unwrap(); + Ok(settings.ip.to_value()) + } + _ => unimplemented!(), } } +} - - // Virtual methods of gst_base::BaseSrc - impl BaseSrcImpl for NdiVideoSrc { - // Called whenever the input/output caps are changing, i.e. in the very beginning before data - // flow happens and whenever the situation in the pipeline is changing. All buffers after this - // call have the caps given here. - // - // We simply remember the resulting AudioInfo from the caps to be able to use this for knowing - // the sample rate, etc. when creating buffers - fn set_caps(&self, element: &BaseSrc, caps: &gst::CapsRef) -> bool { - let info = match gst_video::VideoInfo::from_caps(caps) { - None => return false, - Some(info) => info, - }; - gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); - - // TODO Puede que falle si no creamos la estructura de cero, pero si lo hacemos no podemos poner recv a none - let mut state = self.state.lock().unwrap(); - state.info = Some(info); - let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); - true - } - - // Called when starting, so we can initialize all stream-related state to its defaults - fn start(&self, element: &BaseSrc) -> bool { - // Reset state - *self.state.lock().unwrap() = Default::default(); - let mut settings = self.settings.lock().unwrap(); - settings.id_receiver = connect_ndi(self.cat, element, settings.ip.clone(), settings.stream_name.clone()); - - if settings.id_receiver == 0{ - return false; - } - else{ - // let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); - return true; - } - } - - // Called when shutting down the element so we can release all stream-related state - fn stop(&self, element: &BaseSrc) -> bool { - // Reset state - *self.state.lock().unwrap() = Default::default(); - - let settings = self.settings.lock().unwrap(); - stop_ndi(self.cat, element, settings.id_receiver.clone()); - // Commented because when adding ndi destroy stopped in this line - //*self.state.lock().unwrap() = Default::default(); - true - } - - - fn query(&self, element: &BaseSrc, query: &mut gst::QueryRef) -> bool { - use gst::QueryView; - match query.view_mut() { - // We only work in Push mode. In Pull mode, create() could be called with - // arbitrary offsets and we would have to produce for that specific offset - QueryView::Scheduling(ref mut q) => { - q.set(gst::SchedulingFlags::SEQUENTIAL, 1, -1, 0); - q.add_scheduling_modes(&[gst::PadMode::Push]); - return true; - } - // In Live mode we will have a latency equal to the number of samples in each buffer. - // We can't output samples before they were produced, and the last sample of a buffer - // is produced that much after the beginning, leading to this latency calculation - // QueryView::Latency(ref mut q) => { - // let settings = self.settings.lock().unwrap(); - // let state = self.state.lock().unwrap(); - // println!("Dentro de query"); - // - // if let Some(ref _info) = state.info { - // // let latency = gst::SECOND - // // .mul_div_floor(settings.samples_per_buffer as u64, info.rate() as u64) - // // .unwrap(); - // let latency = gst::SECOND.mul_div_floor(3 as u64, 2 as u64).unwrap(); - // let mut latency = gst::SECOND.mul_div_floor(settings.latency, 1000).unwrap(); - // // if settings.latency > 2000{ - // // println!("{:?}", element.get_name()); - // // latency = gst::SECOND * 0; - // // } - // let latency = gst::SECOND * 0; - // // .mul_div_floor(1 as u64, 30 as u64) - // // .unwrap(); - // // gst_debug!(self.cat, obj: element, "Returning latency {}", latency); - // let max = gst::SECOND * 120 * 1843200; - // // println!("{:?}", latency2); - // println!("{:?}", latency); - // println!("{:?}", (settings.latency / 1000)); - // // println!("{:?}",max); - // q.set(true, latency, max); - // return true; - // } else { - // return false; - // } - // } - _ => (), - } - BaseSrcBase::parent_query(element, query) - } - - fn fixate(&self, element: &BaseSrc, caps: gst::Caps) -> gst::Caps { - //We need to set the correct caps resolution and framerate +// Virtual methods of gst::Element. We override none +impl ElementImpl for NdiVideoSrc { + fn change_state( + &self, + element: &BaseSrc, + transition: gst::StateChange, + ) -> gst::StateChangeReturn { + if transition == gst::StateChange::PausedToPlaying { let receivers = hashmap_receivers.lock().unwrap(); let settings = self.settings.lock().unwrap(); @@ -374,135 +246,281 @@ impl NdiVideoSrc { let video_frame: NDIlib_video_frame_v2_t = Default::default(); let mut frame_type: NDIlib_frame_type_e = NDIlib_frame_type_e::NDIlib_frame_type_none; - while frame_type != NDIlib_frame_type_e::NDIlib_frame_type_video{ - unsafe{ - frame_type = NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000); - } - } - - let mut caps = gst::Caps::truncate(caps); - { - let caps = caps.make_mut(); - let s = caps.get_mut_structure(0).unwrap(); - s.fixate_field_nearest_int("width", video_frame.xres); - s.fixate_field_nearest_int("height", video_frame.yres); - s.fixate_field_nearest_fraction("framerate", Fraction::new(video_frame.frame_rate_N, video_frame.frame_rate_D)); - } - - // Let BaseSrc fixate anything else for us. We could've alternatively have - // called Caps::fixate() here - - let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); - element.parent_fixate(caps) - } - - //Creates the video buffers - fn create( - &self, - element: &BaseSrc, - _offset: u64, - _length: u32, - ) -> Result { - // Keep a local copy of the values of all our properties at this very moment. This - // ensures that the mutex is never locked for long and the application wouldn't - // have to block until this function returns when getting/setting property values - let _settings = &*self.settings.lock().unwrap(); - - let mut timestamp_data = self.timestamp_data.lock().unwrap(); - // Get a locked reference to our state, i.e. the input and output AudioInfo - let state = self.state.lock().unwrap(); - let _info = match state.info { - None => { - gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); - return Err(gst::FlowReturn::NotNegotiated); - } - Some(ref info) => info.clone(), - }; - // unsafe{ - let receivers = hashmap_receivers.lock().unwrap(); - - let recv = &receivers.get(&_settings.id_receiver).unwrap().ndi_instance; - let pNDI_recv = recv.recv; - - let pts: u64; - let video_frame: NDIlib_video_frame_v2_t = Default::default(); - - unsafe{ - let time = ndi_struct.initial_timestamp; - - let mut skip_frame = true; - while skip_frame { - let frame_type = NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000,); - if frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none || frame_type == NDIlib_frame_type_e::NDIlib_frame_type_error { - gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type none received, assuming that the source closed the stream...."]); - return Err(gst::FlowReturn::CustomError); - } - if time >= (video_frame.timestamp as u64){ - gst_debug!(self.cat, obj: element, "Frame timestamp ({:?}) is lower than received in the first frame from NDI ({:?}), so skiping...", (video_frame.timestamp as u64), time); - } - else{ - skip_frame = false; - } + unsafe { + while frame_type != NDIlib_frame_type_e::NDIlib_frame_type_video { + frame_type = NDIlib_recv_capture_v2( + pNDI_recv, + &video_frame, + ptr::null(), + ptr::null(), + 1000, + ); } - pts = video_frame.timestamp as u64 - time; - - let buff_size = (video_frame.yres * video_frame.line_stride_in_bytes) as usize; - let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); + if ndi_struct.initial_timestamp <= video_frame.timestamp as u64 + || ndi_struct.initial_timestamp == 0 { - let vec = Vec::from_raw_parts(video_frame.p_data as *mut u8, buff_size, buff_size); - let pts: gst::ClockTime = (pts * 100).into(); - - let duration: gst::ClockTime = (((video_frame.frame_rate_D as f64 / video_frame.frame_rate_N as f64) * 1000000000.0) as u64).into(); - let buffer = buffer.get_mut().unwrap(); - - if ndi_struct.start_pts == gst::ClockTime(Some(0)){ - ndi_struct.start_pts = element.get_clock().unwrap().get_time() - element.get_base_time(); - } - - buffer.set_pts(pts + ndi_struct.start_pts); - buffer.set_duration(duration); - buffer.set_offset(timestamp_data.offset); - buffer.set_offset_end(timestamp_data.offset + 1); - timestamp_data.offset = timestamp_data.offset + 1; - buffer.copy_from_slice(0, &vec).unwrap(); + ndi_struct.initial_timestamp = video_frame.timestamp as u64; } - - gst_debug!(self.cat, obj: element, "Produced buffer {:?}", buffer); - - Ok(buffer) } } + element.parent_change_state(transition) + } +} + +// Virtual methods of gst_base::BaseSrc +impl BaseSrcImpl for NdiVideoSrc { + // Called whenever the input/output caps are changing, i.e. in the very beginning before data + // flow happens and whenever the situation in the pipeline is changing. All buffers after this + // call have the caps given here. + // + // We simply remember the resulting AudioInfo from the caps to be able to use this for knowing + // the sample rate, etc. when creating buffers + fn set_caps(&self, element: &BaseSrc, caps: &gst::CapsRef) -> bool { + let info = match gst_video::VideoInfo::from_caps(caps) { + None => return false, + Some(info) => info, + }; + gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); + + // TODO Puede que falle si no creamos la estructura de cero, pero si lo hacemos no podemos poner recv a none + let mut state = self.state.lock().unwrap(); + state.info = Some(info); + let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); + true } - // This zero-sized struct is containing the static metadata of our element. It is only necessary to - // be able to implement traits on it, but e.g. a plugin that registers multiple elements with the - // same code would use this struct to store information about the concrete element. An example of - // this would be a plugin that wraps around a library that has multiple decoders with the same API, - // but wants (as it should) a separate element registered for each decoder. - struct NdiVideoSrcStatic; + // Called when starting, so we can initialize all stream-related state to its defaults + fn start(&self, element: &BaseSrc) -> bool { + // Reset state + *self.state.lock().unwrap() = Default::default(); + let mut settings = self.settings.lock().unwrap(); + settings.id_receiver = connect_ndi( + self.cat, + element, + settings.ip.clone(), + settings.stream_name.clone(), + ); - // The basic trait for registering the type: This returns a name for the type and registers the - // instance and class initializations functions with the type system, thus hooking everything - // together. - impl ImplTypeStatic for NdiVideoSrcStatic { - fn get_name(&self) -> &str { - "NdiVideoSrc" - } - - fn new(&self, element: &BaseSrc) -> Box> { - NdiVideoSrc::new(element) - } - - fn class_init(&self, klass: &mut BaseSrcClass) { - NdiVideoSrc::class_init(klass); + if settings.id_receiver == 0 { + return false; + } else { + // let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); + return true; } } - // Registers the type for our element, and then registers in GStreamer under - // the name NdiVideoSrc for being able to instantiate it via e.g. - // gst::ElementFactory::make(). - pub fn register(plugin: &gst::Plugin) { - let type_ = register_type(NdiVideoSrcStatic); - gst::Element::register(plugin, "ndivideosrc", 0, type_); + // Called when shutting down the element so we can release all stream-related state + fn stop(&self, element: &BaseSrc) -> bool { + // Reset state + *self.state.lock().unwrap() = Default::default(); + + let settings = self.settings.lock().unwrap(); + stop_ndi(self.cat, element, settings.id_receiver.clone()); + // Commented because when adding ndi destroy stopped in this line + //*self.state.lock().unwrap() = Default::default(); + true } + + fn query(&self, element: &BaseSrc, query: &mut gst::QueryRef) -> bool { + use gst::QueryView; + match query.view_mut() { + // We only work in Push mode. In Pull mode, create() could be called with + // arbitrary offsets and we would have to produce for that specific offset + QueryView::Scheduling(ref mut q) => { + q.set(gst::SchedulingFlags::SEQUENTIAL, 1, -1, 0); + q.add_scheduling_modes(&[gst::PadMode::Push]); + return true; + } + // In Live mode we will have a latency equal to the number of samples in each buffer. + // We can't output samples before they were produced, and the last sample of a buffer + // is produced that much after the beginning, leading to this latency calculation + // QueryView::Latency(ref mut q) => { + // let settings = self.settings.lock().unwrap(); + // let state = self.state.lock().unwrap(); + // println!("Dentro de query"); + // + // if let Some(ref _info) = state.info { + // // let latency = gst::SECOND + // // .mul_div_floor(settings.samples_per_buffer as u64, info.rate() as u64) + // // .unwrap(); + // let latency = gst::SECOND.mul_div_floor(3 as u64, 2 as u64).unwrap(); + // let mut latency = gst::SECOND.mul_div_floor(settings.latency, 1000).unwrap(); + // // if settings.latency > 2000{ + // // println!("{:?}", element.get_name()); + // // latency = gst::SECOND * 0; + // // } + // let latency = gst::SECOND * 0; + // // .mul_div_floor(1 as u64, 30 as u64) + // // .unwrap(); + // // gst_debug!(self.cat, obj: element, "Returning latency {}", latency); + // let max = gst::SECOND * 120 * 1843200; + // // println!("{:?}", latency2); + // println!("{:?}", latency); + // println!("{:?}", (settings.latency / 1000)); + // // println!("{:?}",max); + // q.set(true, latency, max); + // return true; + // } else { + // return false; + // } + // } + _ => (), + } + BaseSrcBase::parent_query(element, query) + } + + fn fixate(&self, element: &BaseSrc, caps: gst::Caps) -> gst::Caps { + //We need to set the correct caps resolution and framerate + let receivers = hashmap_receivers.lock().unwrap(); + let settings = self.settings.lock().unwrap(); + + let receiver = receivers.get(&settings.id_receiver).unwrap(); + let recv = &receiver.ndi_instance; + let pNDI_recv = recv.recv; + + let video_frame: NDIlib_video_frame_v2_t = Default::default(); + + let mut frame_type: NDIlib_frame_type_e = NDIlib_frame_type_e::NDIlib_frame_type_none; + while frame_type != NDIlib_frame_type_e::NDIlib_frame_type_video { + unsafe { + frame_type = + NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000); + } + } + + let mut caps = gst::Caps::truncate(caps); + { + let caps = caps.make_mut(); + let s = caps.get_mut_structure(0).unwrap(); + s.fixate_field_nearest_int("width", video_frame.xres); + s.fixate_field_nearest_int("height", video_frame.yres); + s.fixate_field_nearest_fraction( + "framerate", + Fraction::new(video_frame.frame_rate_N, video_frame.frame_rate_D), + ); + } + + // Let BaseSrc fixate anything else for us. We could've alternatively have + // called Caps::fixate() here + + let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); + element.parent_fixate(caps) + } + + //Creates the video buffers + fn create( + &self, + element: &BaseSrc, + _offset: u64, + _length: u32, + ) -> Result { + // Keep a local copy of the values of all our properties at this very moment. This + // ensures that the mutex is never locked for long and the application wouldn't + // have to block until this function returns when getting/setting property values + let _settings = &*self.settings.lock().unwrap(); + + let mut timestamp_data = self.timestamp_data.lock().unwrap(); + // Get a locked reference to our state, i.e. the input and output AudioInfo + let state = self.state.lock().unwrap(); + let _info = match state.info { + None => { + gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); + return Err(gst::FlowReturn::NotNegotiated); + } + Some(ref info) => info.clone(), + }; + // unsafe{ + let receivers = hashmap_receivers.lock().unwrap(); + + let recv = &receivers.get(&_settings.id_receiver).unwrap().ndi_instance; + let pNDI_recv = recv.recv; + + let pts: u64; + let video_frame: NDIlib_video_frame_v2_t = Default::default(); + + unsafe { + let time = ndi_struct.initial_timestamp; + + let mut skip_frame = true; + while skip_frame { + let frame_type = + NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000); + if frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none + || frame_type == NDIlib_frame_type_e::NDIlib_frame_type_error + { + gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type none received, assuming that the source closed the stream...."]); + return Err(gst::FlowReturn::CustomError); + } + if time >= (video_frame.timestamp as u64) { + gst_debug!(self.cat, obj: element, "Frame timestamp ({:?}) is lower than received in the first frame from NDI ({:?}), so skiping...", (video_frame.timestamp as u64), time); + } else { + skip_frame = false; + } + } + + pts = video_frame.timestamp as u64 - time; + + let buff_size = (video_frame.yres * video_frame.line_stride_in_bytes) as usize; + let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); + { + let vec = Vec::from_raw_parts(video_frame.p_data as *mut u8, buff_size, buff_size); + let pts: gst::ClockTime = (pts * 100).into(); + + let duration: gst::ClockTime = (((video_frame.frame_rate_D as f64 + / video_frame.frame_rate_N as f64) + * 1000000000.0) as u64) + .into(); + let buffer = buffer.get_mut().unwrap(); + + if ndi_struct.start_pts == gst::ClockTime(Some(0)) { + ndi_struct.start_pts = + element.get_clock().unwrap().get_time() - element.get_base_time(); + } + + buffer.set_pts(pts + ndi_struct.start_pts); + buffer.set_duration(duration); + buffer.set_offset(timestamp_data.offset); + buffer.set_offset_end(timestamp_data.offset + 1); + timestamp_data.offset = timestamp_data.offset + 1; + buffer.copy_from_slice(0, &vec).unwrap(); + } + + gst_debug!(self.cat, obj: element, "Produced buffer {:?}", buffer); + + Ok(buffer) + } + } +} + +// This zero-sized struct is containing the static metadata of our element. It is only necessary to +// be able to implement traits on it, but e.g. a plugin that registers multiple elements with the +// same code would use this struct to store information about the concrete element. An example of +// this would be a plugin that wraps around a library that has multiple decoders with the same API, +// but wants (as it should) a separate element registered for each decoder. +struct NdiVideoSrcStatic; + +// The basic trait for registering the type: This returns a name for the type and registers the +// instance and class initializations functions with the type system, thus hooking everything +// together. +impl ImplTypeStatic for NdiVideoSrcStatic { + fn get_name(&self) -> &str { + "NdiVideoSrc" + } + + fn new(&self, element: &BaseSrc) -> Box> { + NdiVideoSrc::new(element) + } + + fn class_init(&self, klass: &mut BaseSrcClass) { + NdiVideoSrc::class_init(klass); + } +} + +// Registers the type for our element, and then registers in GStreamer under +// the name NdiVideoSrc for being able to instantiate it via e.g. +// gst::ElementFactory::make(). +pub fn register(plugin: &gst::Plugin) { + let type_ = register_type(NdiVideoSrcStatic); + gst::Element::register(plugin, "ndivideosrc", 0, type_); +} From 8ce45f4f0d77f061967f32a2cccd44874089b5e5 Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Tue, 18 Sep 2018 13:12:04 +0200 Subject: [PATCH 070/199] Refactor cargo clippy warnings --- gst-plugin-ndi/src/lib.rs | 12 +++--- gst-plugin-ndi/src/ndiaudiosrc.rs | 60 ++++++--------------------- gst-plugin-ndi/src/ndisys.rs | 12 +++--- gst-plugin-ndi/src/ndivideosrc.rs | 67 ++++++------------------------- 4 files changed, 37 insertions(+), 114 deletions(-) diff --git a/gst-plugin-ndi/src/lib.rs b/gst-plugin-ndi/src/lib.rs index f0686803..3a062688 100644 --- a/gst-plugin-ndi/src/lib.rs +++ b/gst-plugin-ndi/src/lib.rs @@ -72,7 +72,7 @@ lazy_static! { static mut id_receiver: i8 = 0; -fn connect_ndi(cat: gst::DebugCategory, element: &BaseSrc, ip: String, stream_name: String) -> i8 { +fn connect_ndi(cat: gst::DebugCategory, element: &BaseSrc, ip: &str, stream_name: &str) -> i8 { gst_debug!(cat, obj: element, "Starting NDI connection..."); let mut receivers = hashmap_receivers.lock().unwrap(); @@ -177,7 +177,7 @@ fn connect_ndi(cat: gst::DebugCategory, element: &BaseSrc, ip: String, stream_na .into_owned() ); - let source = *p_sources.offset(no_source).clone(); + let source = *p_sources.offset(no_source); let source_ip = CStr::from_ptr(source.p_ip_address) .to_string_lossy() @@ -233,8 +233,8 @@ fn connect_ndi(cat: gst::DebugCategory, element: &BaseSrc, ip: String, stream_na ndi_receiver_info { stream_name: source_name.clone(), ip: source_ip.clone(), - video: video, - audio: audio, + video, + audio, ndi_instance: NdiInstance { recv: pNDI_recv }, id: id_receiver, }, @@ -247,7 +247,7 @@ fn connect_ndi(cat: gst::DebugCategory, element: &BaseSrc, ip: String, stream_na // ndi_struct.start_pts = Some(since_the_epoch.as_secs() * 1000000000 + // since_the_epoch.subsec_nanos() as u64); gst_debug!(cat, obj: element, "Started NDI connection"); - return id_receiver; + id_receiver } } @@ -275,7 +275,7 @@ fn stop_ndi(cat: gst::DebugCategory, element: &BaseSrc, id: i8) -> bool { } receivers.remove(&id); gst_debug!(cat, obj: element, "Closed NDI connection"); - return true; + true } // Static plugin metdata that is directly stored in the plugin shared object and read by GStreamer diff --git a/gst-plugin-ndi/src/ndiaudiosrc.rs b/gst-plugin-ndi/src/ndiaudiosrc.rs index 57b28db5..117cb1b3 100644 --- a/gst-plugin-ndi/src/ndiaudiosrc.rs +++ b/gst-plugin-ndi/src/ndiaudiosrc.rs @@ -293,14 +293,11 @@ impl BaseSrcImpl for NdiAudioSrc { settings.id_receiver = connect_ndi( self.cat, element, - settings.ip.clone(), - settings.stream_name.clone(), + &settings.ip.clone(), + &settings.stream_name.clone(), ); - if settings.id_receiver == 0 { - return false; - } else { - return true; - } + + settings.id_receiver != 0 } // Called when shutting down the element so we can release all stream-related state @@ -309,7 +306,7 @@ impl BaseSrcImpl for NdiAudioSrc { *self.state.lock().unwrap() = Default::default(); let settings = self.settings.lock().unwrap(); - stop_ndi(self.cat, element, settings.id_receiver.clone()); + stop_ndi(self.cat, element, settings.id_receiver); // Commented because when adding ndi destroy stopped in this line //*self.state.lock().unwrap() = Default::default(); true @@ -317,41 +314,10 @@ impl BaseSrcImpl for NdiAudioSrc { fn query(&self, element: &BaseSrc, query: &mut gst::QueryRef) -> bool { use gst::QueryView; - - match query.view_mut() { - // We only work in Push mode. In Pull mode, create() could be called with - // arbitrary offsets and we would have to produce for that specific offset - QueryView::Scheduling(ref mut q) => { - q.set(gst::SchedulingFlags::SEQUENTIAL, 1, -1, 0); - q.add_scheduling_modes(&[gst::PadMode::Push]); - return true; - } - // In Live mode we will have a latency equal to the number of samples in each buffer. - // We can't output samples before they were produced, and the last sample of a buffer - // is produced that much after the beginning, leading to this latency calculation - // QueryView::Latency(ref mut q) => { - // let settings = &*self.settings.lock().unwrap(); - // let state = self.state.lock().unwrap(); - // - // if let Some(ref _info) = state.info { - // // let latency = gst::SECOND - // // .mul_div_floor(1024 as u64, _info.rate() as u64) - // // .unwrap(); - // let latency = gst::SECOND.mul_div_floor(3 as u64, 2 as u64).unwrap(); - // // let latency = gst::SECOND - // // .mul_div_floor(1 as u64, 30 as u64) - // // .unwrap(); - // // gst_debug!(self.cat, obj: element, "Returning latency {}", latency); - // let max = latency * 1843200; - // // println!("{:?}", latency); - // // println!("{:?}",max); - // q.set(true, latency, max); - // return true; - // } else { - // return false; - // } - // } - _ => (), + if let QueryView::Scheduling(ref mut q) = query.view_mut() { + q.set(gst::SchedulingFlags::SEQUENTIAL, 1, -1, 0); + q.add_scheduling_modes(&[gst::PadMode::Push]); + return true; } BaseSrcBase::parent_query(element, query) } @@ -448,9 +414,9 @@ impl BaseSrcImpl for NdiAudioSrc { let vec = Vec::from_raw_parts(audio_frame.p_data as *mut u8, buff_size, buff_size); let pts: gst::ClockTime = (pts * 100).into(); - let duration: gst::ClockTime = (((audio_frame.no_samples as f64 - / audio_frame.sample_rate as f64) - * 1000000000.0) as u64) + let duration: gst::ClockTime = (((f64::from(audio_frame.no_samples) + / f64::from(audio_frame.sample_rate)) + * 1_000_000_000.0) as u64) .into(); let buffer = buffer.get_mut().unwrap(); @@ -463,7 +429,7 @@ impl BaseSrcImpl for NdiAudioSrc { buffer.set_duration(duration); buffer.set_offset(timestamp_data.offset); buffer.set_offset_end(timestamp_data.offset + 1); - timestamp_data.offset = timestamp_data.offset + 1; + timestamp_data.offset += timestamp_data.offset; buffer.copy_from_slice(0, &vec).unwrap(); } diff --git a/gst-plugin-ndi/src/ndisys.rs b/gst-plugin-ndi/src/ndisys.rs index 50298609..46c6fe62 100644 --- a/gst-plugin-ndi/src/ndisys.rs +++ b/gst-plugin-ndi/src/ndisys.rs @@ -104,12 +104,12 @@ pub enum NDIlib_recv_color_format_e { #[repr(u32)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum NDIlib_FourCC_type_e { - NDIlib_FourCC_type_UYVY = 1498831189, - NDIlib_FourCC_type_BGRA = 1095911234, - NDIlib_FourCC_type_BGRX = 1481787202, - NDIlib_FourCC_type_RGBA = 1094862674, - NDIlib_FourCC_type_RGBX = 1480738642, - NDIlib_FourCC_type_UYVA = 1096178005, + NDIlib_FourCC_type_UYVY = 1_498_831_189, + NDIlib_FourCC_type_BGRA = 1_095_911_234, + NDIlib_FourCC_type_BGRX = 1_481_787_202, + NDIlib_FourCC_type_RGBA = 1_094_862_674, + NDIlib_FourCC_type_RGBX = 1_480_738_642, + NDIlib_FourCC_type_UYVA = 1_096_178_005, } #[repr(u32)] diff --git a/gst-plugin-ndi/src/ndivideosrc.rs b/gst-plugin-ndi/src/ndivideosrc.rs index 871b2282..8d5fb2fc 100644 --- a/gst-plugin-ndi/src/ndivideosrc.rs +++ b/gst-plugin-ndi/src/ndivideosrc.rs @@ -298,16 +298,11 @@ impl BaseSrcImpl for NdiVideoSrc { settings.id_receiver = connect_ndi( self.cat, element, - settings.ip.clone(), - settings.stream_name.clone(), + &settings.ip.clone(), + &settings.stream_name.clone(), ); - if settings.id_receiver == 0 { - return false; - } else { - // let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); - return true; - } + settings.id_receiver != 0 } // Called when shutting down the element so we can release all stream-related state @@ -316,7 +311,7 @@ impl BaseSrcImpl for NdiVideoSrc { *self.state.lock().unwrap() = Default::default(); let settings = self.settings.lock().unwrap(); - stop_ndi(self.cat, element, settings.id_receiver.clone()); + stop_ndi(self.cat, element, settings.id_receiver); // Commented because when adding ndi destroy stopped in this line //*self.state.lock().unwrap() = Default::default(); true @@ -324,48 +319,10 @@ impl BaseSrcImpl for NdiVideoSrc { fn query(&self, element: &BaseSrc, query: &mut gst::QueryRef) -> bool { use gst::QueryView; - match query.view_mut() { - // We only work in Push mode. In Pull mode, create() could be called with - // arbitrary offsets and we would have to produce for that specific offset - QueryView::Scheduling(ref mut q) => { - q.set(gst::SchedulingFlags::SEQUENTIAL, 1, -1, 0); - q.add_scheduling_modes(&[gst::PadMode::Push]); - return true; - } - // In Live mode we will have a latency equal to the number of samples in each buffer. - // We can't output samples before they were produced, and the last sample of a buffer - // is produced that much after the beginning, leading to this latency calculation - // QueryView::Latency(ref mut q) => { - // let settings = self.settings.lock().unwrap(); - // let state = self.state.lock().unwrap(); - // println!("Dentro de query"); - // - // if let Some(ref _info) = state.info { - // // let latency = gst::SECOND - // // .mul_div_floor(settings.samples_per_buffer as u64, info.rate() as u64) - // // .unwrap(); - // let latency = gst::SECOND.mul_div_floor(3 as u64, 2 as u64).unwrap(); - // let mut latency = gst::SECOND.mul_div_floor(settings.latency, 1000).unwrap(); - // // if settings.latency > 2000{ - // // println!("{:?}", element.get_name()); - // // latency = gst::SECOND * 0; - // // } - // let latency = gst::SECOND * 0; - // // .mul_div_floor(1 as u64, 30 as u64) - // // .unwrap(); - // // gst_debug!(self.cat, obj: element, "Returning latency {}", latency); - // let max = gst::SECOND * 120 * 1843200; - // // println!("{:?}", latency2); - // println!("{:?}", latency); - // println!("{:?}", (settings.latency / 1000)); - // // println!("{:?}",max); - // q.set(true, latency, max); - // return true; - // } else { - // return false; - // } - // } - _ => (), + if let QueryView::Scheduling(ref mut q) = query.view_mut() { + q.set(gst::SchedulingFlags::SEQUENTIAL, 1, -1, 0); + q.add_scheduling_modes(&[gst::PadMode::Push]); + return true; } BaseSrcBase::parent_query(element, query) } @@ -467,9 +424,9 @@ impl BaseSrcImpl for NdiVideoSrc { let vec = Vec::from_raw_parts(video_frame.p_data as *mut u8, buff_size, buff_size); let pts: gst::ClockTime = (pts * 100).into(); - let duration: gst::ClockTime = (((video_frame.frame_rate_D as f64 - / video_frame.frame_rate_N as f64) - * 1000000000.0) as u64) + let duration: gst::ClockTime = (((f64::from(video_frame.frame_rate_D) + / f64::from(video_frame.frame_rate_N)) + * 1_000_000_000.0) as u64) .into(); let buffer = buffer.get_mut().unwrap(); @@ -482,7 +439,7 @@ impl BaseSrcImpl for NdiVideoSrc { buffer.set_duration(duration); buffer.set_offset(timestamp_data.offset); buffer.set_offset_end(timestamp_data.offset + 1); - timestamp_data.offset = timestamp_data.offset + 1; + timestamp_data.offset += timestamp_data.offset; buffer.copy_from_slice(0, &vec).unwrap(); } From a41f33ae08749205b08f401085fd0be1546a2ef5 Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Tue, 18 Sep 2018 13:25:24 +0200 Subject: [PATCH 071/199] Clean comments --- gst-plugin-ndi/Cargo.toml | 4 +- gst-plugin-ndi/src/lib.rs | 43 ++----------------- gst-plugin-ndi/src/ndiaudiosrc.rs | 69 ++----------------------------- gst-plugin-ndi/src/ndivideosrc.rs | 69 +------------------------------ 4 files changed, 10 insertions(+), 175 deletions(-) diff --git a/gst-plugin-ndi/Cargo.toml b/gst-plugin-ndi/Cargo.toml index ce88157c..89f43cc0 100644 --- a/gst-plugin-ndi/Cargo.toml +++ b/gst-plugin-ndi/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "gst-plugin-ndi" -version = "0.1.0" +version = "1.0.0" authors = ["Ruben Gonzalez ", "Daniel Vilar "] -repository = "https://gitlab.teltek.es/rubenrua/ndi-rs.git" +repository = "https://github.com/teltek/gst-plugin-ndi" license = "LGPL" [dependencies] diff --git a/gst-plugin-ndi/src/lib.rs b/gst-plugin-ndi/src/lib.rs index 3a062688..3e3be71e 100644 --- a/gst-plugin-ndi/src/lib.rs +++ b/gst-plugin-ndi/src/lib.rs @@ -1,13 +1,5 @@ #![allow(non_camel_case_types, non_upper_case_globals, non_snake_case)] -// Copyright (C) 2017 Sebastian Dröge -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - extern crate glib; extern crate gobject_subclass; @@ -36,8 +28,6 @@ use std::sync::Mutex; use gst::GstObjectExt; -// Plugin entry point that should register all elements provided by this plugin, -// and everything else that this plugin might provide (e.g. typefinders or device providers). fn plugin_init(plugin: &gst::Plugin) -> bool { ndivideosrc::register(plugin); ndiaudiosrc::register(plugin); @@ -107,21 +97,17 @@ fn connect_ndi(cat: gst::DebugCategory, element: &BaseSrc, ip: &str, stream_name gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_initialize error"] ); - // return false; return 0; } - //TODO default values let NDI_find_create_desc: NDIlib_find_create_t = Default::default(); let pNDI_find = NDIlib_find_create_v2(&NDI_find_create_desc); - //let ip_ptr = CString::new(ip.clone()).unwrap(); if pNDI_find.is_null() { gst_element_error!( element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_find_create_v2 error"] ); - // return false; return 0; } @@ -139,7 +125,6 @@ fn connect_ndi(cat: gst::DebugCategory, element: &BaseSrc, ip: &str, stream_name gst::CoreError::Negotiation, ["Error getting NDIlib_find_get_current_sources"] ); - // return false; return 0; } @@ -160,7 +145,6 @@ fn connect_ndi(cat: gst::DebugCategory, element: &BaseSrc, ip: &str, stream_name } if no_source == -1 { gst_element_error!(element, gst::ResourceError::OpenRead, ["Stream not found"]); - // return false; return 0; } @@ -186,9 +170,6 @@ fn connect_ndi(cat: gst::DebugCategory, element: &BaseSrc, ip: &str, stream_name .to_string_lossy() .into_owned(); - // We now have at least one source, so we create a receiver to look at it. - // We tell it that we prefer YCbCr video since it is more efficient for us. If the source has an alpha channel - // it will still be provided in BGRA let p_ndi_name = CString::new("Galicaster NDI Receiver").unwrap(); let NDI_recv_create_desc = NDIlib_recv_create_v3_t { source_to_connect_to: source, @@ -198,26 +179,19 @@ fn connect_ndi(cat: gst::DebugCategory, element: &BaseSrc, ip: &str, stream_name let pNDI_recv = NDIlib_recv_create_v3(&NDI_recv_create_desc); if pNDI_recv.is_null() { - //println!("Cannot run NDI: NDIlib_recv_create_v3 error."); gst_element_error!( element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_recv_create_v3 error"] ); - // return false; return 0; } - // Destroy the NDI finder. We needed to have access to the pointers to p_sources[0] NDIlib_find_destroy(pNDI_find); - // We are now going to mark this source as being on program output for tally purposes (but not on preview) let tally_state: NDIlib_tally_t = Default::default(); NDIlib_recv_set_tally(pNDI_recv, &tally_state); - // Enable Hardware Decompression support if this support has it. Please read the caveats in the documentation - // regarding this. There are times in which it might reduce the performance although on small stream numbers - // it almost always yields the same or better performance. let data = CString::new("").unwrap(); let enable_hw_accel = NDIlib_metadata_frame_t { length: data.to_bytes().len() as i32, @@ -240,12 +214,6 @@ fn connect_ndi(cat: gst::DebugCategory, element: &BaseSrc, ip: &str, stream_name }, ); - // let start = SystemTime::now(); - // let since_the_epoch = start.duration_since(UNIX_EPOCH) - // .expect("Time went backwards"); - // println!("{:?}", since_the_epoch); - // ndi_struct.start_pts = Some(since_the_epoch.as_secs() * 1000000000 + - // since_the_epoch.subsec_nanos() as u64); gst_debug!(cat, obj: element, "Started NDI connection"); id_receiver } @@ -278,19 +246,14 @@ fn stop_ndi(cat: gst::DebugCategory, element: &BaseSrc, id: i8) -> bool { true } -// Static plugin metdata that is directly stored in the plugin shared object and read by GStreamer -// upon loading. -// Plugin name, plugin description, plugin entry point function, version number of this plugin, -// license of the plugin, source package name, binary package name, origin where it comes from -// and the date/time of release. plugin_define!( b"ndi\0", b"NewTek NDI Plugin\0", plugin_init, - b"1.0\0", - b"MIT/X11\0", + b"1.0.0\0", + b"LGPL\0", b"ndi\0", b"ndi\0", - b"https://gitlab.teltek.es/rubenrua/ndi-rs.git\0", + b"https://github.com/teltek/gst-plugin-ndi\0", b"2018-04-09\0" ); diff --git a/gst-plugin-ndi/src/ndiaudiosrc.rs b/gst-plugin-ndi/src/ndiaudiosrc.rs index 117cb1b3..da8bf722 100644 --- a/gst-plugin-ndi/src/ndiaudiosrc.rs +++ b/gst-plugin-ndi/src/ndiaudiosrc.rs @@ -22,7 +22,6 @@ use stop_ndi; use hashmap_receivers; -// Property value storage #[derive(Debug, Clone)] struct Settings { stream_name: String, @@ -40,7 +39,6 @@ impl Default for Settings { } } -// Metadata for the properties static PROPERTIES: [Property; 2] = [ Property::String( "stream-name", @@ -58,8 +56,6 @@ static PROPERTIES: [Property; 2] = [ ), ]; -// Stream-specific state, i.e. audio format configuration -// and sample offset struct State { info: Option, } @@ -74,7 +70,6 @@ struct TimestampData { offset: u64, } -// Struct containing all the element data struct NdiAudioSrc { cat: gst::DebugCategory, settings: Mutex, @@ -83,10 +78,7 @@ struct NdiAudioSrc { } impl NdiAudioSrc { - // Called when a new instance is to be created fn new(element: &BaseSrc) -> Box> { - // Initialize live-ness and notify the base class that - // we'd like to operate in Time format element.set_live(true); element.set_format(gst::Format::Time); @@ -102,16 +94,6 @@ impl NdiAudioSrc { }) } - // Called exactly once when registering the type. Used for - // setting up metadata for all instances, e.g. the name and - // classification and the pad templates with their caps. - // - // Actual instances can create pads based on those pad templates - // with a subset of the caps given here. In case of basesrc, - // a "src" and "sink" pad template are required here and the base class - // will automatically instantiate pads for them. - // - // Our element here can output f32 and f64 fn class_init(klass: &mut BaseSrcClass) { klass.set_metadata( "NewTek NDI Audio Source", @@ -120,15 +102,13 @@ impl NdiAudioSrc { "Ruben Gonzalez , Daniel Vilar ", ); - // On the src pad, we can produce F32/F64 with any sample rate - // and any number of channels let caps = gst::Caps::new_simple( "audio/x-raw", &[ ( "format", &gst::List::new(&[ - //TODO add all formats? + //TODO add more formats? &gst_audio::AUDIO_FORMAT_F32.to_string(), &gst_audio::AUDIO_FORMAT_F64.to_string(), &gst_audio::AUDIO_FORMAT_S16.to_string(), @@ -139,26 +119,20 @@ impl NdiAudioSrc { ("layout", &"interleaved"), ], ); - // The src pad template must be named "src" for basesrc - // and specific a pad that is always there + let src_pad_template = gst::PadTemplate::new( "src", gst::PadDirection::Src, gst::PadPresence::Always, &caps, - //&gst::Caps::new_any(), ); klass.add_pad_template(src_pad_template); - // Install all our properties klass.install_properties(&PROPERTIES); } } -// Virtual methods of GObject itself impl ObjectImpl for NdiAudioSrc { - // Called whenever a value of a property is changed. It can be called - // at any time from any thread. fn set_property(&self, obj: &glib::Object, id: u32, value: &glib::Value) { let prop = &PROPERTIES[id as usize]; let element = obj.clone().downcast::().unwrap(); @@ -200,20 +174,16 @@ impl ObjectImpl for NdiAudioSrc { } } - // Called whenever a value of a property is read. It can be called - // at any time from any thread. fn get_property(&self, _obj: &glib::Object, id: u32) -> Result { let prop = &PROPERTIES[id as usize]; match *prop { Property::String("stream-name", ..) => { let settings = self.settings.lock().unwrap(); - //TODO to_value supongo que solo funciona con numeros Ok(settings.stream_name.to_value()) } Property::String("ip", ..) => { let settings = self.settings.lock().unwrap(); - //TODO to_value supongo que solo funciona con numeros Ok(settings.ip.to_value()) } _ => unimplemented!(), @@ -221,7 +191,6 @@ impl ObjectImpl for NdiAudioSrc { } } -// Virtual methods of gst::Element. We override none impl ElementImpl for NdiAudioSrc { fn change_state( &self, @@ -261,14 +230,7 @@ impl ElementImpl for NdiAudioSrc { } } -// Virtual methods of gst_base::BaseSrc impl BaseSrcImpl for NdiAudioSrc { - // Called whenever the input/output caps are changing, i.e. in the very beginning before data - // flow happens and whenever the situation in the pipeline is changing. All buffers after this - // call have the caps given here. - // - // We simply remember the resulting AudioInfo from the caps to be able to use this for knowing - // the sample rate, etc. when creating buffers fn set_caps(&self, element: &BaseSrc, caps: &gst::CapsRef) -> bool { let info = match gst_audio::AudioInfo::from_caps(caps) { None => return false, @@ -277,16 +239,13 @@ impl BaseSrcImpl for NdiAudioSrc { gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); - // TODO Puede que falle si no creamos la estructura de cero, pero si lo hacemos no podemos poner recv a none let mut state = self.state.lock().unwrap(); state.info = Some(info); true } - // Called when starting, so we can initialize all stream-related state to its defaults fn start(&self, element: &BaseSrc) -> bool { - // Reset state *self.state.lock().unwrap() = Default::default(); let mut settings = self.settings.lock().unwrap(); @@ -300,9 +259,7 @@ impl BaseSrcImpl for NdiAudioSrc { settings.id_receiver != 0 } - // Called when shutting down the element so we can release all stream-related state fn stop(&self, element: &BaseSrc) -> bool { - // Reset state *self.state.lock().unwrap() = Default::default(); let settings = self.settings.lock().unwrap(); @@ -323,7 +280,6 @@ impl BaseSrcImpl for NdiAudioSrc { } fn fixate(&self, element: &BaseSrc, caps: gst::Caps) -> gst::Caps { - //We need to set the correct caps resolution and framerate let receivers = hashmap_receivers.lock().unwrap(); let settings = self.settings.lock().unwrap(); @@ -345,31 +301,23 @@ impl BaseSrcImpl for NdiAudioSrc { { let caps = caps.make_mut(); let s = caps.get_mut_structure(0).unwrap(); - //s.fixate_field_nearest_int("rate", audio_frame.sample_rate); s.fixate_field_nearest_int("rate", audio_frame.sample_rate / audio_frame.no_channels); s.fixate_field_nearest_int("channels", audio_frame.no_channels); } - // Let BaseSrc fixate anything else for us. We could've alternatively have - // called Caps::fixate() here element.parent_fixate(caps) - // } } - //Creates the audio buffers fn create( &self, element: &BaseSrc, _offset: u64, _length: u32, ) -> Result { - // Keep a local copy of the values of all our properties at this very moment. This - // ensures that the mutex is never locked for long and the application wouldn't - // have to block until this function returns when getting/setting property values let _settings = &*self.settings.lock().unwrap(); let mut timestamp_data = self.timestamp_data.lock().unwrap(); - // Get a locked reference to our state, i.e. the input and output AudioInfo + let state = self.state.lock().unwrap(); let _info = match state.info { None => { @@ -440,16 +388,8 @@ impl BaseSrcImpl for NdiAudioSrc { } } -// This zero-sized struct is containing the static metadata of our element. It is only necessary to -// be able to implement traits on it, but e.g. a plugin that registers multiple elements with the -// same code would use this struct to store information about the concrete element. An example of -// this would be a plugin that wraps around a library that has multiple decoders with the same API, -// but wants (as it should) a separate element registered for each decoder. struct NdiAudioSrcStatic; -// The basic trait for registering the type: This returns a name for the type and registers the -// instance and class initializations functions with the type system, thus hooking everything -// together. impl ImplTypeStatic for NdiAudioSrcStatic { fn get_name(&self) -> &str { "NdiAudioSrc" @@ -464,9 +404,6 @@ impl ImplTypeStatic for NdiAudioSrcStatic { } } -// Registers the type for our element, and then registers in GStreamer under -// the name NdiAudioSrc for being able to instantiate it via e.g. -// gst::ElementFactory::make(). pub fn register(plugin: &gst::Plugin) { let type_ = register_type(NdiAudioSrcStatic); gst::Element::register(plugin, "ndiaudiosrc", 0, type_); diff --git a/gst-plugin-ndi/src/ndivideosrc.rs b/gst-plugin-ndi/src/ndivideosrc.rs index 8d5fb2fc..6e8920c4 100644 --- a/gst-plugin-ndi/src/ndivideosrc.rs +++ b/gst-plugin-ndi/src/ndivideosrc.rs @@ -23,7 +23,6 @@ use stop_ndi; use hashmap_receivers; -// Property value storage #[derive(Debug, Clone)] struct Settings { stream_name: String, @@ -43,7 +42,6 @@ impl Default for Settings { } } -// Metadata for the properties static PROPERTIES: [Property; 2] = [ Property::String( "stream-name", @@ -61,8 +59,6 @@ static PROPERTIES: [Property; 2] = [ ), ]; -// Stream-specific state, i.e. audio format configuration -// and sample offset struct State { info: Option, } @@ -77,7 +73,6 @@ struct TimestampData { offset: u64, } -// Struct containing all the element data struct NdiVideoSrc { cat: gst::DebugCategory, settings: Mutex, @@ -86,10 +81,7 @@ struct NdiVideoSrc { } impl NdiVideoSrc { - // Called when a new instance is to be created fn new(element: &BaseSrc) -> Box> { - // Initialize live-ness and notify the base class that - // we'd like to operate in Time format element.set_live(true); element.set_format(gst::Format::Time); @@ -105,16 +97,6 @@ impl NdiVideoSrc { }) } - // Called exactly once when registering the type. Used for - // setting up metadata for all instances, e.g. the name and - // classification and the pad templates with their caps. - // - // Actual instances can create pads based on those pad templates - // with a subset of the caps given here. In case of basesrc, - // a "src" and "sink" pad template are required here and the base class - // will automatically instantiate pads for them. - // - // Our element here can output f32 and f64 fn class_init(klass: &mut BaseSrcClass) { klass.set_metadata( "NewTek NDI Video Source", @@ -131,7 +113,7 @@ impl NdiVideoSrc { ( "format", &gst::List::new(&[ - //TODO add all formats? + //TODO add all formats &gst_video::VideoFormat::Uyvy.to_string(), //&gst_video::VideoFormat::Rgb.to_string(), //&gst_video::VideoFormat::Gray8.to_string(), @@ -148,26 +130,20 @@ impl NdiVideoSrc { ), ], ); - // The src pad template must be named "src" for basesrc - // and specific a pad that is always there + let src_pad_template = gst::PadTemplate::new( "src", gst::PadDirection::Src, gst::PadPresence::Always, &caps, - //&gst::Caps::new_any(), ); klass.add_pad_template(src_pad_template); - // Install all our properties klass.install_properties(&PROPERTIES); } } -// Virtual methods of GObject itself impl ObjectImpl for NdiVideoSrc { - // Called whenever a value of a property is changed. It can be called - // at any time from any thread. fn set_property(&self, obj: &glib::Object, id: u32, value: &glib::Value) { let prop = &PROPERTIES[id as usize]; let element = obj.clone().downcast::().unwrap(); @@ -185,9 +161,6 @@ impl ObjectImpl for NdiVideoSrc { ); settings.stream_name = stream_name; drop(settings); - - // let _ = - // element.post_message(&gst::Message::new_latency().src(Some(&element)).build()); } Property::String("ip", ..) => { let mut settings = self.settings.lock().unwrap(); @@ -201,16 +174,11 @@ impl ObjectImpl for NdiVideoSrc { ); settings.ip = ip; drop(settings); - - // let _ = - // element.post_message(&gst::Message::new_latency().src(Some(&element)).build()); } _ => unimplemented!(), } } - // Called whenever a value of a property is read. It can be called - // at any time from any thread. fn get_property(&self, _obj: &glib::Object, id: u32) -> Result { let prop = &PROPERTIES[id as usize]; @@ -228,7 +196,6 @@ impl ObjectImpl for NdiVideoSrc { } } -// Virtual methods of gst::Element. We override none impl ElementImpl for NdiVideoSrc { fn change_state( &self, @@ -268,14 +235,7 @@ impl ElementImpl for NdiVideoSrc { } } -// Virtual methods of gst_base::BaseSrc impl BaseSrcImpl for NdiVideoSrc { - // Called whenever the input/output caps are changing, i.e. in the very beginning before data - // flow happens and whenever the situation in the pipeline is changing. All buffers after this - // call have the caps given here. - // - // We simply remember the resulting AudioInfo from the caps to be able to use this for knowing - // the sample rate, etc. when creating buffers fn set_caps(&self, element: &BaseSrc, caps: &gst::CapsRef) -> bool { let info = match gst_video::VideoInfo::from_caps(caps) { None => return false, @@ -283,16 +243,13 @@ impl BaseSrcImpl for NdiVideoSrc { }; gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); - // TODO Puede que falle si no creamos la estructura de cero, pero si lo hacemos no podemos poner recv a none let mut state = self.state.lock().unwrap(); state.info = Some(info); let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); true } - // Called when starting, so we can initialize all stream-related state to its defaults fn start(&self, element: &BaseSrc) -> bool { - // Reset state *self.state.lock().unwrap() = Default::default(); let mut settings = self.settings.lock().unwrap(); settings.id_receiver = connect_ndi( @@ -305,9 +262,7 @@ impl BaseSrcImpl for NdiVideoSrc { settings.id_receiver != 0 } - // Called when shutting down the element so we can release all stream-related state fn stop(&self, element: &BaseSrc) -> bool { - // Reset state *self.state.lock().unwrap() = Default::default(); let settings = self.settings.lock().unwrap(); @@ -328,7 +283,6 @@ impl BaseSrcImpl for NdiVideoSrc { } fn fixate(&self, element: &BaseSrc, caps: gst::Caps) -> gst::Caps { - //We need to set the correct caps resolution and framerate let receivers = hashmap_receivers.lock().unwrap(); let settings = self.settings.lock().unwrap(); @@ -358,9 +312,6 @@ impl BaseSrcImpl for NdiVideoSrc { ); } - // Let BaseSrc fixate anything else for us. We could've alternatively have - // called Caps::fixate() here - let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); element.parent_fixate(caps) } @@ -372,13 +323,9 @@ impl BaseSrcImpl for NdiVideoSrc { _offset: u64, _length: u32, ) -> Result { - // Keep a local copy of the values of all our properties at this very moment. This - // ensures that the mutex is never locked for long and the application wouldn't - // have to block until this function returns when getting/setting property values let _settings = &*self.settings.lock().unwrap(); let mut timestamp_data = self.timestamp_data.lock().unwrap(); - // Get a locked reference to our state, i.e. the input and output AudioInfo let state = self.state.lock().unwrap(); let _info = match state.info { None => { @@ -387,7 +334,6 @@ impl BaseSrcImpl for NdiVideoSrc { } Some(ref info) => info.clone(), }; - // unsafe{ let receivers = hashmap_receivers.lock().unwrap(); let recv = &receivers.get(&_settings.id_receiver).unwrap().ndi_instance; @@ -450,16 +396,8 @@ impl BaseSrcImpl for NdiVideoSrc { } } -// This zero-sized struct is containing the static metadata of our element. It is only necessary to -// be able to implement traits on it, but e.g. a plugin that registers multiple elements with the -// same code would use this struct to store information about the concrete element. An example of -// this would be a plugin that wraps around a library that has multiple decoders with the same API, -// but wants (as it should) a separate element registered for each decoder. struct NdiVideoSrcStatic; -// The basic trait for registering the type: This returns a name for the type and registers the -// instance and class initializations functions with the type system, thus hooking everything -// together. impl ImplTypeStatic for NdiVideoSrcStatic { fn get_name(&self) -> &str { "NdiVideoSrc" @@ -474,9 +412,6 @@ impl ImplTypeStatic for NdiVideoSrcStatic { } } -// Registers the type for our element, and then registers in GStreamer under -// the name NdiVideoSrc for being able to instantiate it via e.g. -// gst::ElementFactory::make(). pub fn register(plugin: &gst::Plugin) { let type_ = register_type(NdiVideoSrcStatic); gst::Element::register(plugin, "ndivideosrc", 0, type_); From 440a353f931d127dd6ee5b5a4b59b32e9fcefeb4 Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Tue, 18 Sep 2018 13:39:34 +0200 Subject: [PATCH 072/199] Publish code in Github --- .gitignore | 5 +- gst-plugin-ndi/Cargo.toml => Cargo.toml | 0 README.md | 67 ++++++ example/Cargo.lock | 4 - example/Cargo.toml | 6 - example/src/main.rs | 128 ----------- example/src/ndilib.rs | 246 --------------------- gst-plugin-ndi/.gitignore | 1 - gst-plugin-ndi/README.md | 63 ------ {gst-plugin-ndi/src => src}/lib.rs | 0 {gst-plugin-ndi/src => src}/ndiaudiosrc.rs | 0 {gst-plugin-ndi/src => src}/ndisys.rs | 0 {gst-plugin-ndi/src => src}/ndivideosrc.rs | 0 13 files changed, 69 insertions(+), 451 deletions(-) rename gst-plugin-ndi/Cargo.toml => Cargo.toml (100%) create mode 100644 README.md delete mode 100644 example/Cargo.lock delete mode 100644 example/Cargo.toml delete mode 100644 example/src/main.rs delete mode 100644 example/src/ndilib.rs delete mode 100644 gst-plugin-ndi/.gitignore delete mode 100644 gst-plugin-ndi/README.md rename {gst-plugin-ndi/src => src}/lib.rs (100%) rename {gst-plugin-ndi/src => src}/ndiaudiosrc.rs (100%) rename {gst-plugin-ndi/src => src}/ndisys.rs (100%) rename {gst-plugin-ndi/src => src}/ndivideosrc.rs (100%) diff --git a/.gitignore b/.gitignore index bbc567f8..b354aec7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ -target -**/*.rs.bk -*~ +Cargo.lock +target/ \ No newline at end of file diff --git a/gst-plugin-ndi/Cargo.toml b/Cargo.toml similarity index 100% rename from gst-plugin-ndi/Cargo.toml rename to Cargo.toml diff --git a/README.md b/README.md new file mode 100644 index 00000000..e4304822 --- /dev/null +++ b/README.md @@ -0,0 +1,67 @@ +GStreamer NDI Plugin for Linux +==================== + +*Compiled and tested with Ubuntu 16.04.5, GStreamer 1.8.3 and NDI SDK 3.0.9 and 3.5.1* + +This is a plugin for the [GStreamer](https://gstreamer.freedesktop.org/) multimedia framework that allows GStreamer to receive a stream from a [NDI](https://www.newtek.com/ndi/) source. This plugin has been developed by [Teltek](http://teltek.es/) and was funded by the [University of the Arts London](https://www.arts.ac.uk/) and [The University of Manchester](https://www.manchester.ac.uk/). + +Currently the plugin has two source elements, `ndivideosrc` to get video from the stream and `ndiaudiosrc` for audio. By just providing the name or the ip of the stream, all the information required from the stream is picked up automatically, such as resolution, framerate, audio channels, ... + +Some examples of how to use these elements from the command line: + +``` +#Information about the elements +gst-inspect-1.0 ndi +gst-inspect-1.0 ndivideosrc +gst-inspect-1.0 ndiaudiosrc + +#Video pipeline +gst-launch-1.0 ndivideosrc stream-name="GC-DEV2 (OBS)" ! autovideosink +#Audio pipeline +gst-launch-1.0 ndiaudiosrc stream-name="GC-DEV2 (OBS)" ! autoaudiosink + +#Video and audio pipeline +gst-launch-1.0 ndivideosrc stream-name="GC-DEV2 (OBS)" ! autovideosink ndiaudiosrc stream-name="GC-DEV2 (OBS)" ! autoaudiosink +``` + +Feel free to contribute to this project. Some ways you can contribute are: +* Testing with more hardware and software and reporting bugs +* Doing pull requests. + +Compilation of the NDI element +------- +To compile the NDI element it's necessary to install Rust, the NDI SDK and the following packages for gstreamer: + +``` +apt-get install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \ + gstreamer1.0-plugins-base gstreamer1.0-plugins-good \ + gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly \ + gstreamer1.0-libav libgstrtspserver-1.0-dev + +``` +To install the required NDI library there are two options: +1. Download NDI SDK from NDI website and move the library to the correct location. +2. Use a [deb package](https://github.com/Palakis/obs-ndi/releases/download/4.5.2/libndi3_3.5.1-1_amd64.deb) made by the community. Thanks to [NDI plugin for OBS](https://github.com/Palakis/obs-ndi). + +To install Rust, you can follow their documentation: https://www.rust-lang.org/en-US/install.html + +Once all requirements are met, you can build the plugin by executing the following command from the project root folder: + +``` +cargo build +export GST_PLUGIN_PATH=`pwd`/target/debug +gst-inspect-1.0 ndi +``` + +If all went ok, you should see info related to the NDI element. To make the plugin available without using `GST_PLUGIN_PATH` it's necessary to copy the plugin to the gstreamer plugins folder. +``` +cp target/debug/libgstndi.so /usr/lib/x86_64-linux-gnu/gstreamer-1.0/ +``` + +More info about GStreamer plugins written in Rust: +---------------------------------- +https://github.com/sdroege/gstreamer-rs +https://github.com/sdroege/gst-plugin-rs + +https://coaxion.net/blog/2018/01/how-to-write-gstreamer-elements-in-rust-part-1-a-video-filter-for-converting-rgb-to-grayscale/ +https://coaxion.net/blog/2018/02/how-to-write-gstreamer-elements-in-rust-part-2-a-raw-audio-sine-wave-source/ diff --git a/example/Cargo.lock b/example/Cargo.lock deleted file mode 100644 index 201bb94c..00000000 --- a/example/Cargo.lock +++ /dev/null @@ -1,4 +0,0 @@ -[[package]] -name = "ndi" -version = "0.1.0" - diff --git a/example/Cargo.toml b/example/Cargo.toml deleted file mode 100644 index 51ef523e..00000000 --- a/example/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "ndi" -version = "0.1.0" -authors = ["rubenrua "] - -[dependencies] diff --git a/example/src/main.rs b/example/src/main.rs deleted file mode 100644 index a2f07a45..00000000 --- a/example/src/main.rs +++ /dev/null @@ -1,128 +0,0 @@ -#![allow(non_upper_case_globals, non_snake_case)] - -pub mod ndilib; - -use std::ptr; -use std::ffi::{CStr, CString}; - -use ndilib::*; - -fn main() { - unsafe { - if !NDIlib_initialize() { - //TODO delete exits - println!("Cannot run NDI: NDIlib_initialize error."); - ::std::process::exit(1); - } - - //TODO valores por defecto - let NDI_find_create_desc: NDIlib_find_create_t = Default::default(); - let pNDI_find = NDIlib_find_create_v2(&NDI_find_create_desc); - if pNDI_find.is_null() { - println!("Cannot run NDI: NDIlib_find_create_v2 error."); - ::std::process::exit(1); - } - - let mut no_sources: u32 = 0; - let mut p_sources = ptr::null(); - while no_sources == 0 { - p_sources = NDIlib_find_get_current_sources(pNDI_find, &mut no_sources as *mut u32); - } - - // We need at least one source - if p_sources.is_null() { - println!("Error getting NDIlib_find_get_current_sources."); - ::std::process::exit(1); - } - - println!( - "no_source {}: Name '{}' Address '{}'", - no_sources, - CStr::from_ptr((*p_sources).p_ndi_name) - .to_string_lossy() - .into_owned(), - CStr::from_ptr((*p_sources).p_ip_address) - .to_string_lossy() - .into_owned() - ); - - // We now have at least one source, so we create a receiver to look at it. - // We tell it that we prefer YCbCr video since it is more efficient for us. If the source has an alpha channel - // it will still be provided in BGRA - let p_ndi_name = CString::new("Galicaster NDI Receiver").unwrap(); - let NDI_recv_create_desc = NDIlib_recv_create_v3_t { - source_to_connect_to: *p_sources, - p_ndi_name: p_ndi_name.as_ptr(), - ..Default::default() - }; - - let pNDI_recv = NDIlib_recv_create_v3(&NDI_recv_create_desc); - if pNDI_recv.is_null() { - println!("Cannot run NDI: NDIlib_recv_create_v3 error."); - ::std::process::exit(1); - } - - // Destroy the NDI finder. We needed to have access to the pointers to p_sources[0] - NDIlib_find_destroy(pNDI_find); - - // We are now going to mark this source as being on program output for tally purposes (but not on preview) - let tally_state: NDIlib_tally_t = Default::default(); - NDIlib_recv_set_tally(pNDI_recv, &tally_state); - - // Enable Hardwqre Decompression support if this support has it. Please read the caveats in the documentation - // regarding this. There are times in which it might reduce the performance although on small stream numbers - // it almost always yields the same or better performance. - let data = CString::new("").unwrap(); - let enable_hw_accel = NDIlib_metadata_frame_t { - length: data.to_bytes().len() as i32, - timecode: 0, - p_data: data.as_ptr(), - }; - - NDIlib_recv_send_metadata(pNDI_recv, &enable_hw_accel); - - loop { - let video_frame: NDIlib_video_frame_v2_t = Default::default(); - let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); - let metadata_frame: NDIlib_metadata_frame_t = Default::default(); - - let frame_type = NDIlib_recv_capture_v2( - pNDI_recv, - &video_frame, - &audio_frame, - &metadata_frame, - 1000, - ); - - match frame_type { - NDIlib_frame_type_e::NDIlib_frame_type_video => { - println!("Tengo video {:?}", video_frame); - } - NDIlib_frame_type_e::NDIlib_frame_type_audio => { - println!("Tengo audio {:?}", audio_frame); - } - NDIlib_frame_type_e::NDIlib_frame_type_metadata => { - println!( - "Tengo metadata {} '{}'", - metadata_frame.length, - CStr::from_ptr(metadata_frame.p_data) - .to_string_lossy() - .into_owned(), - ); - } - NDIlib_frame_type_e::NDIlib_frame_type_error => { - println!( - "Tengo error {} '{}'", - metadata_frame.length, - CStr::from_ptr(metadata_frame.p_data) - .to_string_lossy() - .into_owned(), - ); - break; - } - _ => println!("Tengo {:?}", frame_type), - } - } - } - println!("Exit"); -} diff --git a/example/src/ndilib.rs b/example/src/ndilib.rs deleted file mode 100644 index 6474e153..00000000 --- a/example/src/ndilib.rs +++ /dev/null @@ -1,246 +0,0 @@ -#![allow(non_camel_case_types, non_upper_case_globals)] - -use std::ptr; - -#[link(name = "ndi")] -extern "C" { - pub fn NDIlib_initialize() -> bool; - pub fn NDIlib_find_create_v2( - p_create_settings: *const NDIlib_find_create_t, - ) -> NDIlib_find_instance_t; - pub fn NDIlib_find_get_current_sources( - p_instance: NDIlib_find_instance_t, - p_no_sources: *mut u32, - ) -> *const NDIlib_source_t; - pub fn NDIlib_recv_create_v3( - p_create_settings: *const NDIlib_recv_create_v3_t, - ) -> NDIlib_recv_instance_t; - pub fn NDIlib_find_destroy(p_instance: NDIlib_find_instance_t); - pub fn NDIlib_recv_set_tally( - p_instance: NDIlib_recv_instance_t, - p_tally: *const NDIlib_tally_t, - ) -> bool; - pub fn NDIlib_recv_send_metadata( - p_instance: NDIlib_recv_instance_t, - p_metadata: *const NDIlib_metadata_frame_t, - ) -> bool; - pub fn NDIlib_recv_capture_v2( - p_instance: NDIlib_recv_instance_t, - p_video_data: *const NDIlib_video_frame_v2_t, - p_audio_data: *const NDIlib_audio_frame_v2_t, - p_metadata: *const NDIlib_metadata_frame_t, - timeout_in_ms: u32, - ) -> NDIlib_frame_type_e; -} - -pub type NDIlib_find_instance_t = *mut ::std::os::raw::c_void; - -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct NDIlib_find_create_t { - pub show_local_sources: bool, - pub p_groups: *const ::std::os::raw::c_char, - pub p_extra_ips: *const ::std::os::raw::c_char, -} - -impl Default for NDIlib_find_create_t { - fn default() -> Self { - NDIlib_find_create_t { - show_local_sources: true, - p_groups: ptr::null(), - p_extra_ips: ptr::null(), - } - } -} - -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct NDIlib_source_t { - pub p_ndi_name: *const ::std::os::raw::c_char, - pub p_ip_address: *const ::std::os::raw::c_char, -} - -impl Default for NDIlib_source_t { - fn default() -> Self { - NDIlib_source_t { - p_ndi_name: ptr::null(), - p_ip_address: ptr::null(), - } - } -} - -#[repr(i32)] -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub enum NDIlib_frame_type_e { - NDIlib_frame_type_none = 0, - NDIlib_frame_type_video = 1, - NDIlib_frame_type_audio = 2, - NDIlib_frame_type_metadata = 3, - NDIlib_frame_type_error = 4, - NDIlib_frame_type_status_change = 100, -} - -#[repr(i32)] -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub enum NDIlib_recv_bandwidth_e { - NDIlib_recv_bandwidth_metadata_only = -10, - NDIlib_recv_bandwidth_audio_only = 10, - NDIlib_recv_bandwidth_lowest = 0, - NDIlib_recv_bandwidth_highest = 100, -} - -#[repr(u32)] -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub enum NDIlib_recv_color_format_e { - NDIlib_recv_color_format_BGRX_BGRA = 0, - NDIlib_recv_color_format_UYVY_BGRA = 1, - NDIlib_recv_color_format_RGBX_RGBA = 2, - NDIlib_recv_color_format_UYVY_RGBA = 3, - NDIlib_recv_color_format_fastest = 100, -} - -#[repr(u32)] -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub enum NDIlib_FourCC_type_e { - NDIlib_FourCC_type_UYVY = 1498831189, - NDIlib_FourCC_type_BGRA = 1095911234, - NDIlib_FourCC_type_BGRX = 1481787202, - NDIlib_FourCC_type_RGBA = 1094862674, - NDIlib_FourCC_type_RGBX = 1480738642, - NDIlib_FourCC_type_UYVA = 1096178005, -} - -#[repr(u32)] -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub enum NDIlib_frame_format_type_e { - NDIlib_frame_format_type_progressive = 1, - NDIlib_frame_format_type_interleaved = 0, - NDIlib_frame_format_type_field_0 = 2, - NDIlib_frame_format_type_field_1 = 3, -} - -pub const NDIlib_send_timecode_synthesize: i64 = ::std::i64::MAX; -pub const NDIlib_send_timecode_empty: i64 = 0; -pub const NDIlib_recv_timestamp_undefined: i64 = ::std::i64::MAX; - -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct NDIlib_recv_create_v3_t { - pub source_to_connect_to: NDIlib_source_t, - pub color_format: NDIlib_recv_color_format_e, - pub bandwidth: NDIlib_recv_bandwidth_e, - pub allow_video_fields: bool, - pub p_ndi_name: *const ::std::os::raw::c_char, -} - -impl Default for NDIlib_recv_create_v3_t { - fn default() -> Self { - NDIlib_recv_create_v3_t { - source_to_connect_to: Default::default(), - allow_video_fields: true, - bandwidth: NDIlib_recv_bandwidth_e::NDIlib_recv_bandwidth_highest, - color_format: NDIlib_recv_color_format_e::NDIlib_recv_color_format_UYVY_BGRA, - p_ndi_name: ptr::null(), - } - } -} - -pub type NDIlib_recv_instance_t = *mut ::std::os::raw::c_void; - -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct NDIlib_tally_t { - pub on_program: bool, - pub on_preview: bool, -} - -impl Default for NDIlib_tally_t { - fn default() -> Self { - NDIlib_tally_t { - on_program: false, - on_preview: false, - } - } -} - -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct NDIlib_metadata_frame_t { - pub length: ::std::os::raw::c_int, - pub timecode: i64, - pub p_data: *const ::std::os::raw::c_char, -} - -impl Default for NDIlib_metadata_frame_t { - fn default() -> Self { - NDIlib_metadata_frame_t { - length: 0, - timecode: 0, //NDIlib_send_timecode_synthesize, - p_data: ptr::null(), - } - } -} - -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct NDIlib_video_frame_v2_t { - pub xres: ::std::os::raw::c_int, - pub yres: ::std::os::raw::c_int, - pub FourCC: NDIlib_FourCC_type_e, - pub frame_rate_N: ::std::os::raw::c_int, - pub frame_rate_D: ::std::os::raw::c_int, - pub picture_aspect_ratio: ::std::os::raw::c_float, - pub frame_format_type: NDIlib_frame_format_type_e, - pub timecode: i64, - pub p_data: *const ::std::os::raw::c_char, - pub line_stride_in_bytes: ::std::os::raw::c_int, - pub p_metadata: *const ::std::os::raw::c_char, - pub timestamp: i64, -} - -impl Default for NDIlib_video_frame_v2_t { - fn default() -> Self { - NDIlib_video_frame_v2_t { - xres: 0, - yres: 0, - FourCC: NDIlib_FourCC_type_e::NDIlib_FourCC_type_UYVY, - frame_rate_N: 30000, - frame_rate_D: 1001, - picture_aspect_ratio: 0.0, - frame_format_type: NDIlib_frame_format_type_e::NDIlib_frame_format_type_progressive, - timecode: NDIlib_send_timecode_synthesize, - p_data: ptr::null(), - line_stride_in_bytes: 0, - p_metadata: ptr::null(), - timestamp: NDIlib_send_timecode_empty, - } - } -} - -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct NDIlib_audio_frame_v2_t { - pub sample_rate: ::std::os::raw::c_int, - pub no_channels: ::std::os::raw::c_int, - pub no_samples: ::std::os::raw::c_int, - pub timecode: i64, - pub p_data: *const ::std::os::raw::c_float, - pub channel_stride_in_bytes: ::std::os::raw::c_int, - pub p_metadata: *const ::std::os::raw::c_char, - pub timestamp: i64, -} - -impl Default for NDIlib_audio_frame_v2_t { - fn default() -> Self { - NDIlib_audio_frame_v2_t { - sample_rate: 48000, - no_channels: 2, - no_samples: 0, - timecode: NDIlib_send_timecode_synthesize, - p_data: ptr::null(), - channel_stride_in_bytes: 0, - p_metadata: ptr::null(), - timestamp: NDIlib_send_timecode_empty, - } - } -} diff --git a/gst-plugin-ndi/.gitignore b/gst-plugin-ndi/.gitignore deleted file mode 100644 index ffa3bbd2..00000000 --- a/gst-plugin-ndi/.gitignore +++ /dev/null @@ -1 +0,0 @@ -Cargo.lock \ No newline at end of file diff --git a/gst-plugin-ndi/README.md b/gst-plugin-ndi/README.md deleted file mode 100644 index 7c603345..00000000 --- a/gst-plugin-ndi/README.md +++ /dev/null @@ -1,63 +0,0 @@ -GStreamer NDI Plugin for Linux -==================== - -*Compiled and tested with Ubuntu 16.04.5, GStreamer 1.8.3 and NDI SDK 3.0.9 and 3.5.1* - -This is a plugin for the [GStreamer](https://gstreamer.freedesktop.org/) multimedia framework that allows to receive a stream from a [NDI](https://www.newtek.com/ndi/) source. This plugin is developed by [Teltek](http://teltek.es/) and funded by the [University of the Arts London](https://www.arts.ac.uk/) and [The University of Manchester](https://www.manchester.ac.uk/). - -Currently the plugin only has sources elements, `ndivideosrc` to get video from the stream and `ndiaudiosrc` for audio. Only it's necessary to provide the name or the ip of the stream, and automatically the element get all the information required from the stream, such resolution, framerate, audio channels,... - -Some examples of usage of these elements: -``` -#Information about the elements -gst-inspect-1.0 ndi -gst-inspect-1.0 ndivideosrc -gst-inspect-1.0 ndiaudiosrc - -#Video pipeline -gst-launch-1.0 ndivideosrc stream-name="GC-DEV2 (OBS)" ! autovideosink -#Audio pipeline -gst-launch-1.0 ndiaudiosrc stream-name="GC-DEV2 (OBS)" ! autoaudiosink - -#Video and audio pipeline -gst-launch-1.0 ndivideosrc stream-name="GC-DEV2 (OBS)" ! autovideosink ndiaudiosrc stream-name="GC-DEV2 (OBS)" ! autoaudiosink -``` - -Feel free to contribute to this project testing with more hardware and software, reporting bugs or with pull requests. - -Compile NDI element -------- -Before compile the element it's necessary install Rust, NDI SDK and the following packages for gstreamer: - -``` -apt-get install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \ - gstreamer1.0-plugins-base gstreamer1.0-plugins-good \ - gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly \ - gstreamer1.0-libav libgstrtspserver-1.0-dev - -``` -To install the necessary NDI library exists two options: -* Download NDI SDK from NDI website and move the library to the correct location. -* Use a [deb package](https://github.com/Palakis/obs-ndi/releases/download/4.5.2/libndi3_3.5.1-1_amd64.deb) made by the community. Thanks to [NDI plugin for OBS](https://github.com/Palakis/obs-ndi). - -To build the plugin execute these commands from the root of the repository folder - -``` -cargo build - -export GST_PLUGIN_PATH=`pwd`/target/debug -gst-inspect-1.0 ndi -``` - -If all went ok, you should see info related to the NDI element. To make the plugin available without using `GST_PLUGIN_PATH` it's necessary copy the plugin to the gstreamer plugins folder. -``` -cp target/debug/libgstndi.so /usr/lib/x86_64-linux-gnu/gstreamer-1.0/ -``` - -More info about GStreamer plugins written in Rust: ----------------------------------- -https://github.com/sdroege/gstreamer-rs -https://github.com/sdroege/gst-plugin-rs - -https://coaxion.net/blog/2018/01/how-to-write-gstreamer-elements-in-rust-part-1-a-video-filter-for-converting-rgb-to-grayscale/ -https://coaxion.net/blog/2018/02/how-to-write-gstreamer-elements-in-rust-part-2-a-raw-audio-sine-wave-source/ diff --git a/gst-plugin-ndi/src/lib.rs b/src/lib.rs similarity index 100% rename from gst-plugin-ndi/src/lib.rs rename to src/lib.rs diff --git a/gst-plugin-ndi/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs similarity index 100% rename from gst-plugin-ndi/src/ndiaudiosrc.rs rename to src/ndiaudiosrc.rs diff --git a/gst-plugin-ndi/src/ndisys.rs b/src/ndisys.rs similarity index 100% rename from gst-plugin-ndi/src/ndisys.rs rename to src/ndisys.rs diff --git a/gst-plugin-ndi/src/ndivideosrc.rs b/src/ndivideosrc.rs similarity index 100% rename from gst-plugin-ndi/src/ndivideosrc.rs rename to src/ndivideosrc.rs From 104509fb7931679192488ad43d98cd32e2aa7363 Mon Sep 17 00:00:00 2001 From: rubenrua Date: Fri, 21 Sep 2018 17:46:34 +0200 Subject: [PATCH 073/199] Add Travis CI Thank you @greyblake for https://youtu.be/SNpJ3h8R4ks --- .travis.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..e7b568c6 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,10 @@ +language: rust +rust: + - stable +install: + - rustup component add rustfmt-preview + - rustup component add clippy-preview +script: + - cargo fmt -- --check + - touch ./src/*.rs && cargo clippy -- -D warnings + - cargo build \ No newline at end of file From d1ded51a96f70ef20cf35bdbf4feacdac2b18b27 Mon Sep 17 00:00:00 2001 From: rubenrua Date: Fri, 21 Sep 2018 18:00:07 +0200 Subject: [PATCH 074/199] Add dependencies copy ndi lib from https://github.com/Palakis/obs-ndi/blob/master/CI/install-dependencies-xenial.sh --- .travis.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.travis.yml b/.travis.yml index e7b568c6..6ab6fe8d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,21 @@ language: rust rust: - stable +addons: + apt: + packages: + - libgstreamer1.0-dev + - libgstreamer-plugins-base1.0-dev + - gstreamer1.0-plugins-base + - gstreamer1.0-plugins-good + - gstreamer1.0-plugins-bad + - gstreamer1.0-plugins-ugly + - gstreamer1.0-libav + - libgstrtspserver-1.0-dev install: - rustup component add rustfmt-preview - rustup component add clippy-preview + - curl -kLO https://slepin.fr/obs-ndi/ci/ndisdk-slim-v3.5-linux.zip -f --retry 5 && unzip ./ndisdk-slim-v3.5-linux.zip script: - cargo fmt -- --check - touch ./src/*.rs && cargo clippy -- -D warnings From 02f3b84bb3317fabee528ba0fdc2d5a41bc63901 Mon Sep 17 00:00:00 2001 From: Ruben Gonzalez Date: Fri, 21 Sep 2018 20:11:54 +0200 Subject: [PATCH 075/199] Fix bug added with clippy refactor --- src/ndiaudiosrc.rs | 5 +++-- src/ndivideosrc.rs | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index da8bf722..fe8db62a 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -375,9 +375,10 @@ impl BaseSrcImpl for NdiAudioSrc { buffer.set_pts(pts + ndi_struct.start_pts); buffer.set_duration(duration); + //TODO fix audio offset buffer.set_offset(timestamp_data.offset); - buffer.set_offset_end(timestamp_data.offset + 1); - timestamp_data.offset += timestamp_data.offset; + timestamp_data.offset += 1; + buffer.set_offset_end(timestamp_data.offset); buffer.copy_from_slice(0, &vec).unwrap(); } diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index 6e8920c4..b18020fa 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -384,8 +384,8 @@ impl BaseSrcImpl for NdiVideoSrc { buffer.set_pts(pts + ndi_struct.start_pts); buffer.set_duration(duration); buffer.set_offset(timestamp_data.offset); - buffer.set_offset_end(timestamp_data.offset + 1); - timestamp_data.offset += timestamp_data.offset; + timestamp_data.offset += 1; + buffer.set_offset_end(timestamp_data.offset); buffer.copy_from_slice(0, &vec).unwrap(); } From 56befc7f0876ba76becd916bf4effe6db3dcf3e6 Mon Sep 17 00:00:00 2001 From: rubenrua Date: Sat, 22 Sep 2018 09:50:58 +0200 Subject: [PATCH 076/199] Delete packages not locale in trusty: libgstrtspserver-1.0-dev --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6ab6fe8d..7e977498 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,6 @@ addons: - gstreamer1.0-plugins-bad - gstreamer1.0-plugins-ugly - gstreamer1.0-libav - - libgstrtspserver-1.0-dev install: - rustup component add rustfmt-preview - rustup component add clippy-preview From 533cc1148b92011e87be105dc8e7fadbb9c878d0 Mon Sep 17 00:00:00 2001 From: rubenrua Date: Sat, 22 Sep 2018 09:53:36 +0200 Subject: [PATCH 077/199] Update .gitignore with gitignore.io --- .gitignore | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index b354aec7..bbec86db 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,64 @@ + +# Created by https://www.gitignore.io/api/rust,emacs + +### Emacs ### +# -*- mode: gitignore; -*- +*~ +\#*\# +/.emacs.desktop +/.emacs.desktop.lock +*.elc +auto-save-list +tramp +.\#* + +# Org-mode +.org-id-locations +*_archive + +# flymake-mode +*_flymake.* + +# eshell files +/eshell/history +/eshell/lastdir + +# elpa packages +/elpa/ + +# reftex files +*.rel + +# AUCTeX auto folder +/auto/ + +# cask packages +.cask/ +dist/ + +# Flycheck +flycheck_*.el + +# server auth directory +/server/ + +# projectiles files +.projectile + +# directory configuration +.dir-locals.el + +### Rust ### +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html Cargo.lock -target/ \ No newline at end of file + +# These are backup files generated by rustfmt +**/*.rs.bk + + +# End of https://www.gitignore.io/api/rust,emacs From c94727c8728963ef6b0e1bcc4e65fa1f736eda85 Mon Sep 17 00:00:00 2001 From: rubenrua Date: Mon, 24 Sep 2018 10:02:01 +0200 Subject: [PATCH 078/199] Fix ndiaudiosrc offset A buffer can also have one or both of a start and an end offset. These are media-type specific. For video buffers, the start offset will generally be the frame number. For audio buffers, it will be the number of samples produced so far. For compressed data, it could be the byte offset in a source or destination file. Likewise, the end offset will be the offset of the end of the buffer. These can only be meaningfully interpreted if you know the media type of the buffer (the preceding CAPS event). Either or both can be set to GST_BUFFER_OFFSET_NONE. https://gstreamer.freedesktop.org/data/doc/gstreamer/head/gstreamer/html/GstBuffer.html --- src/ndiaudiosrc.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index fe8db62a..34eb0934 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -375,9 +375,8 @@ impl BaseSrcImpl for NdiAudioSrc { buffer.set_pts(pts + ndi_struct.start_pts); buffer.set_duration(duration); - //TODO fix audio offset buffer.set_offset(timestamp_data.offset); - timestamp_data.offset += 1; + timestamp_data.offset += audio_frame.no_samples as u64/audio_frame.no_channels as u64; buffer.set_offset_end(timestamp_data.offset); buffer.copy_from_slice(0, &vec).unwrap(); } From 72bd822525b30dc44811442d79890583f81d59e3 Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Mon, 24 Sep 2018 15:46:36 +0200 Subject: [PATCH 079/199] Added latency query --- src/ndiaudiosrc.rs | 25 +++++++++++++++++++++++-- src/ndivideosrc.rs | 23 ++++++++++++++++++++--- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index 34eb0934..9b53a751 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -27,6 +27,7 @@ struct Settings { stream_name: String, ip: String, id_receiver: i8, + latency: Option, } impl Default for Settings { @@ -35,6 +36,7 @@ impl Default for Settings { stream_name: String::from("Fixed ndi stream name"), ip: String::from(""), id_receiver: 0, + latency: None, } } } @@ -276,12 +278,24 @@ impl BaseSrcImpl for NdiAudioSrc { q.add_scheduling_modes(&[gst::PadMode::Push]); return true; } + if let QueryView::Latency(ref mut q) = query.view_mut() { + let settings = &*self.settings.lock().unwrap(); + let state = self.state.lock().unwrap(); + + if let Some(ref _info) = state.info { + let latency = settings.latency.unwrap(); + q.set(true, latency, gst::CLOCK_TIME_NONE); + return true; + } else { + return false; + } + } BaseSrcBase::parent_query(element, query) } fn fixate(&self, element: &BaseSrc, caps: gst::Caps) -> gst::Caps { let receivers = hashmap_receivers.lock().unwrap(); - let settings = self.settings.lock().unwrap(); + let mut settings = self.settings.lock().unwrap(); let receiver = receivers.get(&settings.id_receiver).unwrap(); @@ -297,6 +311,11 @@ impl BaseSrcImpl for NdiAudioSrc { NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000); } } + + let no_samples = audio_frame.no_samples as u64 / audio_frame.no_channels as u64; + let audio_rate = audio_frame.sample_rate as u64 / audio_frame.no_channels as u64; + settings.latency = gst::SECOND.mul_div_floor(no_samples, audio_rate); + let mut caps = gst::Caps::truncate(caps); { let caps = caps.make_mut(); @@ -305,6 +324,7 @@ impl BaseSrcImpl for NdiAudioSrc { s.fixate_field_nearest_int("channels", audio_frame.no_channels); } + let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); element.parent_fixate(caps) } @@ -376,7 +396,8 @@ impl BaseSrcImpl for NdiAudioSrc { buffer.set_pts(pts + ndi_struct.start_pts); buffer.set_duration(duration); buffer.set_offset(timestamp_data.offset); - timestamp_data.offset += audio_frame.no_samples as u64/audio_frame.no_channels as u64; + timestamp_data.offset += + audio_frame.no_samples as u64 / audio_frame.no_channels as u64; buffer.set_offset_end(timestamp_data.offset); buffer.copy_from_slice(0, &vec).unwrap(); } diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index b18020fa..e2dfc7d5 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -28,7 +28,7 @@ struct Settings { stream_name: String, ip: String, id_receiver: i8, - latency: u64, + latency: Option, } impl Default for Settings { @@ -37,7 +37,7 @@ impl Default for Settings { stream_name: String::from("Fixed ndi stream name"), ip: String::from(""), id_receiver: 0, - latency: 0, + latency: None, } } } @@ -279,12 +279,24 @@ impl BaseSrcImpl for NdiVideoSrc { q.add_scheduling_modes(&[gst::PadMode::Push]); return true; } + if let QueryView::Latency(ref mut q) = query.view_mut() { + let settings = &*self.settings.lock().unwrap(); + let state = self.state.lock().unwrap(); + + if let Some(ref _info) = state.info { + let latency = settings.latency.unwrap(); + // q.set(true, latency, gst::CLOCK_TIME_NONE); + return true; + } else { + return false; + } + } BaseSrcBase::parent_query(element, query) } fn fixate(&self, element: &BaseSrc, caps: gst::Caps) -> gst::Caps { let receivers = hashmap_receivers.lock().unwrap(); - let settings = self.settings.lock().unwrap(); + let mut settings = self.settings.lock().unwrap(); let receiver = receivers.get(&settings.id_receiver).unwrap(); let recv = &receiver.ndi_instance; @@ -300,6 +312,11 @@ impl BaseSrcImpl for NdiVideoSrc { } } + settings.latency = gst::SECOND.mul_div_floor( + video_frame.frame_rate_N as u64, + video_frame.frame_rate_D as u64, + ); + let mut caps = gst::Caps::truncate(caps); { let caps = caps.make_mut(); From 90b5fa8937d3ae0df8480a172a7269a75e57fad0 Mon Sep 17 00:00:00 2001 From: rubenrua Date: Mon, 24 Sep 2018 17:11:15 +0200 Subject: [PATCH 080/199] Fix video latency --- src/ndivideosrc.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index e2dfc7d5..c65c57c5 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -285,7 +285,7 @@ impl BaseSrcImpl for NdiVideoSrc { if let Some(ref _info) = state.info { let latency = settings.latency.unwrap(); - // q.set(true, latency, gst::CLOCK_TIME_NONE); + q.set(true, latency, gst::CLOCK_TIME_NONE); return true; } else { return false; @@ -313,8 +313,8 @@ impl BaseSrcImpl for NdiVideoSrc { } settings.latency = gst::SECOND.mul_div_floor( - video_frame.frame_rate_N as u64, video_frame.frame_rate_D as u64, + video_frame.frame_rate_N as u64, ); let mut caps = gst::Caps::truncate(caps); From 22c7240bad88e45f2c165d3618ca03328d179b40 Mon Sep 17 00:00:00 2001 From: rubenrua Date: Mon, 24 Sep 2018 18:15:33 +0200 Subject: [PATCH 081/199] Add debug info about latency --- src/ndiaudiosrc.rs | 1 + src/ndivideosrc.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index 9b53a751..05c2d02a 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -284,6 +284,7 @@ impl BaseSrcImpl for NdiAudioSrc { if let Some(ref _info) = state.info { let latency = settings.latency.unwrap(); + gst_debug!(self.cat, obj: element, "Returning latency {}", latency); q.set(true, latency, gst::CLOCK_TIME_NONE); return true; } else { diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index c65c57c5..876b5a41 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -285,6 +285,7 @@ impl BaseSrcImpl for NdiVideoSrc { if let Some(ref _info) = state.info { let latency = settings.latency.unwrap(); + gst_debug!(self.cat, obj: element, "Returning latency {}", latency); q.set(true, latency, gst::CLOCK_TIME_NONE); return true; } else { From b2ec1da3459723fce50a8a122142460da4f15eb7 Mon Sep 17 00:00:00 2001 From: rubenrua Date: Tue, 25 Sep 2018 11:48:08 +0200 Subject: [PATCH 082/199] Fix audio interleave with NDIlib_util_audio_to_interleaved_16s_v2 --- src/ndiaudiosrc.rs | 38 +++++++++++++++++++++----------------- src/ndisys.rs | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 17 deletions(-) diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index 05c2d02a..ddc22a6c 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -111,8 +111,8 @@ impl NdiAudioSrc { "format", &gst::List::new(&[ //TODO add more formats? - &gst_audio::AUDIO_FORMAT_F32.to_string(), - &gst_audio::AUDIO_FORMAT_F64.to_string(), + //&gst_audio::AUDIO_FORMAT_F32.to_string(), + //&gst_audio::AUDIO_FORMAT_F64.to_string(), &gst_audio::AUDIO_FORMAT_S16.to_string(), ]), ), @@ -314,15 +314,16 @@ impl BaseSrcImpl for NdiAudioSrc { } let no_samples = audio_frame.no_samples as u64 / audio_frame.no_channels as u64; - let audio_rate = audio_frame.sample_rate as u64 / audio_frame.no_channels as u64; - settings.latency = gst::SECOND.mul_div_floor(no_samples, audio_rate); + let audio_rate = audio_frame.sample_rate; + settings.latency = gst::SECOND.mul_div_floor(no_samples, audio_rate as u64); let mut caps = gst::Caps::truncate(caps); { let caps = caps.make_mut(); let s = caps.get_mut_structure(0).unwrap(); - s.fixate_field_nearest_int("rate", audio_frame.sample_rate / audio_frame.no_channels); + s.fixate_field_nearest_int("rate", audio_rate); s.fixate_field_nearest_int("channels", audio_frame.no_channels); + s.fixate_field_str("layout", "interleaved"); } let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); @@ -380,27 +381,30 @@ impl BaseSrcImpl for NdiAudioSrc { let buff_size = (audio_frame.channel_stride_in_bytes) as usize; let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); { - let vec = Vec::from_raw_parts(audio_frame.p_data as *mut u8, buff_size, buff_size); - let pts: gst::ClockTime = (pts * 100).into(); - - let duration: gst::ClockTime = (((f64::from(audio_frame.no_samples) - / f64::from(audio_frame.sample_rate)) - * 1_000_000_000.0) as u64) - .into(); - let buffer = buffer.get_mut().unwrap(); - if ndi_struct.start_pts == gst::ClockTime(Some(0)) { ndi_struct.start_pts = element.get_clock().unwrap().get_time() - element.get_base_time(); } + let buffer = buffer.get_mut().unwrap(); + + let pts: gst::ClockTime = (pts * 100).into(); buffer.set_pts(pts + ndi_struct.start_pts); + + let duration: gst::ClockTime = (((f64::from(audio_frame.no_samples) + / f64::from(audio_frame.sample_rate)) + * 1_000_000_000.0) as u64) + .into(); buffer.set_duration(duration); + buffer.set_offset(timestamp_data.offset); - timestamp_data.offset += - audio_frame.no_samples as u64 / audio_frame.no_channels as u64; + timestamp_data.offset += audio_frame.no_samples as u64; buffer.set_offset_end(timestamp_data.offset); - buffer.copy_from_slice(0, &vec).unwrap(); + + let mut dst: NDIlib_audio_frame_interleaved_16s_t = Default::default(); + dst.reference_level = 0; + dst.p_data = buffer.map_writable().unwrap().as_mut_slice().as_mut_ptr() as *mut i16; + NDIlib_util_audio_to_interleaved_16s_v2(&audio_frame, &mut dst); } gst_debug!(self.cat, obj: element, "Produced buffer {:?}", buffer); diff --git a/src/ndisys.rs b/src/ndisys.rs index 46c6fe62..ad6915d9 100644 --- a/src/ndisys.rs +++ b/src/ndisys.rs @@ -254,3 +254,39 @@ impl Default for NDIlib_audio_frame_v2_t { } } } + +extern "C" { + pub fn NDIlib_util_audio_to_interleaved_16s_v2( + p_src: *const NDIlib_audio_frame_v2_t, + p_dst: *mut NDIlib_audio_frame_interleaved_16s_t, + ); + + pub fn NDIlib_util_audio_from_interleaved_16s_v2( + p_src: *const NDIlib_audio_frame_interleaved_16s_t, + p_dst: *mut NDIlib_audio_frame_v2_t, + ); +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct NDIlib_audio_frame_interleaved_16s_t { + pub sample_rate: ::std::os::raw::c_int, + pub no_channels: ::std::os::raw::c_int, + pub no_samples: ::std::os::raw::c_int, + pub timecode: i64, + pub reference_level: ::std::os::raw::c_int, + pub p_data: *mut ::std::os::raw::c_short, +} + +impl Default for NDIlib_audio_frame_interleaved_16s_t { + fn default() -> Self { + NDIlib_audio_frame_interleaved_16s_t { + sample_rate: 48000, + no_channels: 2, + no_samples: 0, + timecode: NDIlib_send_timecode_synthesize, + reference_level: 0, + p_data: ptr::null_mut(), + } + } +} From 1a4f146318324f36b964bed4718b13b83e5dc2a3 Mon Sep 17 00:00:00 2001 From: rubenrua Date: Tue, 25 Sep 2018 12:05:51 +0200 Subject: [PATCH 083/199] Add License and Acknowledgments License: https://choosealicense.com/licenses/lgpl-3.0/ Thank you @sdroege for your work with GStreamer and with Rust. --- LICENSE | 165 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 15 ++++- 2 files changed, 178 insertions(+), 2 deletions(-) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..153d416d --- /dev/null +++ b/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. \ No newline at end of file diff --git a/README.md b/README.md index e4304822..dad330e3 100644 --- a/README.md +++ b/README.md @@ -60,8 +60,19 @@ cp target/debug/libgstndi.so /usr/lib/x86_64-linux-gnu/gstreamer-1.0/ More info about GStreamer plugins written in Rust: ---------------------------------- -https://github.com/sdroege/gstreamer-rs +https://github.com/sdroege/gstreamer-rs https://github.com/sdroege/gst-plugin-rs -https://coaxion.net/blog/2018/01/how-to-write-gstreamer-elements-in-rust-part-1-a-video-filter-for-converting-rgb-to-grayscale/ +https://coaxion.net/blog/2018/01/how-to-write-gstreamer-elements-in-rust-part-1-a-video-filter-for-converting-rgb-to-grayscale/ https://coaxion.net/blog/2018/02/how-to-write-gstreamer-elements-in-rust-part-2-a-raw-audio-sine-wave-source/ + + +License +------- +This plugin is licensed under the LGPL - see the ([LICENSE](LICENSE) file for details + + +Acknowledgments +------- +* University of the Arts London and The University of Manchester. +* Sebastian Dröge (@sdroege). From a8c9d78bff33163714ab8e78226cf56c34a96fd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 25 Sep 2018 13:37:59 +0300 Subject: [PATCH 084/199] Compare element factory names to distinguish video source from audio source We could also go via the glib::Type but that requires more steps unless we also add a getter from the registered type to the audio/video source modules. --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3e3be71e..7faab13b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ extern crate gobject_subclass; extern crate gst_plugin; #[macro_use] extern crate gstreamer as gst; +use gst::prelude::*; extern crate gstreamer_audio as gst_audio; extern crate gstreamer_base as gst_base; extern crate gstreamer_video as gst_video; @@ -69,8 +70,7 @@ fn connect_ndi(cat: gst::DebugCategory, element: &BaseSrc, ip: &str, stream_name let mut audio = false; let mut video = false; - //FIXME Search for another way to know if the source is an audio or a video source - if element.get_name().contains("audiosrc") { + if element.get_factory().map(|f| f.get_name() == "ndiaudiosrc").unwrap_or(false) { audio = true; } else { video = true; From 00df152210d7c1d3cba062bb2b4aaf9d6b10594b Mon Sep 17 00:00:00 2001 From: rubenrua Date: Tue, 25 Sep 2018 13:28:38 +0200 Subject: [PATCH 085/199] Update travis with slomo GStreamer version Copy from https://github.com/sdroege/gst-plugin-rs/blob/master/.travis.yml --- .travis.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7e977498..2f892217 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,11 +11,20 @@ addons: - gstreamer1.0-plugins-bad - gstreamer1.0-plugins-ugly - gstreamer1.0-libav +before_install: + - curl -kLO https://slepin.fr/obs-ndi/ci/ndisdk-slim-v3.5-linux.zip -f --retry 5 && unzip ./ndisdk-slim-v3.5-linux.zip && rm ./ndisdk-slim-v3.5-linux.zip + - sudo cp ndisdk/lib/x86_64-linux-gnu/libndi.so.3.5.1 /usr/local/lib/libndi.so && sudo ldconfig + - curl -L https://people.freedesktop.org/~slomo/gstreamer-1.14.3.tar.gz | tar xz + - sed -i "s;prefix=/root/gstreamer;prefix=$PWD/gstreamer;g" $PWD/gstreamer/lib/x86_64-linux-gnu/pkgconfig/*.pc + - export PKG_CONFIG_PATH=$PWD/gstreamer/lib/x86_64-linux-gnu/pkgconfig + - export GST_PLUGIN_SYSTEM_PATH=$PWD/gstreamer/lib/x86_64-linux-gnu/gstreamer-1.0 + - export GST_PLUGIN_SCANNER=$PWD/gstreamer/libexec/gstreamer-1.0/gst-plugin-scanner + - export PATH=$PATH:$PWD/gstreamer/bin + - export LD_LIBRARY_PATH=$PWD/gstreamer/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH install: - rustup component add rustfmt-preview - rustup component add clippy-preview - - curl -kLO https://slepin.fr/obs-ndi/ci/ndisdk-slim-v3.5-linux.zip -f --retry 5 && unzip ./ndisdk-slim-v3.5-linux.zip script: - cargo fmt -- --check - - touch ./src/*.rs && cargo clippy -- -D warnings + - touch ./src/*.rs && cargo clippy -- -A cast_ptr_alignment -A new_ret_no_self - cargo build \ No newline at end of file From 8a81ca526b892aef023ff3600fe7ec6e88b12b04 Mon Sep 17 00:00:00 2001 From: rubenrua Date: Tue, 25 Sep 2018 15:55:39 +0200 Subject: [PATCH 086/199] Use "cargo build --release" in README. Fix #4 Waiting for use a distro package. --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dad330e3..d9694a9b 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,10 @@ gst-inspect-1.0 ndi If all went ok, you should see info related to the NDI element. To make the plugin available without using `GST_PLUGIN_PATH` it's necessary to copy the plugin to the gstreamer plugins folder. ``` -cp target/debug/libgstndi.so /usr/lib/x86_64-linux-gnu/gstreamer-1.0/ +cargo build --release +sudo install -o root -g root -m 644 target/release/libgstndi.so /usr/lib/x86_64-linux-gnu/gstreamer-1.0/ +sudo ldconfig +gst-inspect-1.0 ndi ``` More info about GStreamer plugins written in Rust: From cb1ea2195f7514b37e9ef239f93216fcb401d8eb Mon Sep 17 00:00:00 2001 From: rubenrua Date: Tue, 25 Sep 2018 16:04:00 +0200 Subject: [PATCH 087/199] Fix rust fmt --- src/lib.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 7faab13b..4dcc1320 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -70,7 +70,11 @@ fn connect_ndi(cat: gst::DebugCategory, element: &BaseSrc, ip: &str, stream_name let mut audio = false; let mut video = false; - if element.get_factory().map(|f| f.get_name() == "ndiaudiosrc").unwrap_or(false) { + if element + .get_factory() + .map(|f| f.get_name() == "ndiaudiosrc") + .unwrap_or(false) + { audio = true; } else { video = true; From 227080e32d573fa497290791691e91d72d853be9 Mon Sep 17 00:00:00 2001 From: rubenrua Date: Thu, 27 Sep 2018 10:18:54 +0200 Subject: [PATCH 088/199] Add comment about Newtek NDI times resolution See: https://github.com/FFmpeg/FFmpeg/blob/master/libavdevice/libndi_newtek_common.h#L27 From NDI SDK Documentation: This is the timecode of this frame in 100ns intervals. This is generally not used internally by the SDK, but is passed through to applications who may interpret it as they wish. When sending data, a value of NDIlib_send_timecode_synthesize can be specified (and should be the default), the operation of this value is documented in the sending section of this documentation. NDIlib_send_timecode_synthesize will yield UTC time in 100ns intervals since the Unix Time Epoch 1/1/1970 00:00. When interpreting this timecode a receiving application may choose to localise the time of day based on time zone offset which can optionally be communicated by the sender in connection metadata. Since timecode is stored in UTC within NDI, communicating timecode time of day for non UTC time zones requires a translation --- src/ndiaudiosrc.rs | 1 + src/ndivideosrc.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index ddc22a6c..a88f6e42 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -388,6 +388,7 @@ impl BaseSrcImpl for NdiAudioSrc { let buffer = buffer.get_mut().unwrap(); + // Newtek NDI yields times in 100ns intervals since the Unix Time let pts: gst::ClockTime = (pts * 100).into(); buffer.set_pts(pts + ndi_struct.start_pts); diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index 876b5a41..71f9118a 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -386,6 +386,7 @@ impl BaseSrcImpl for NdiVideoSrc { let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); { let vec = Vec::from_raw_parts(video_frame.p_data as *mut u8, buff_size, buff_size); + // Newtek NDI yields times in 100ns intervals since the Unix Time let pts: gst::ClockTime = (pts * 100).into(); let duration: gst::ClockTime = (((f64::from(video_frame.frame_rate_D) From 192253ab0561f7781af0d9476ee2782086619375 Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Wed, 5 Dec 2018 12:55:01 +0100 Subject: [PATCH 089/199] A new parameter was added to adjust the threshold when detecting that a stream was closed --- src/ndiaudiosrc.rs | 34 +++++++++++++++++++++++++++++++++- src/ndivideosrc.rs | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index a88f6e42..bd889c6b 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -26,6 +26,7 @@ use hashmap_receivers; struct Settings { stream_name: String, ip: String, + loss_threshold: u32, id_receiver: i8, latency: Option, } @@ -35,13 +36,14 @@ impl Default for Settings { Settings { stream_name: String::from("Fixed ndi stream name"), ip: String::from(""), + loss_threshold: 5, id_receiver: 0, latency: None, } } } -static PROPERTIES: [Property; 2] = [ +static PROPERTIES: [Property; 3] = [ Property::String( "stream-name", "Sream Name", @@ -56,6 +58,14 @@ static PROPERTIES: [Property; 2] = [ None, PropertyMutability::ReadWrite, ), + Property::UInt( + "loss-threshold", + "Loss threshold", + "Loss threshold", + (0, 60), + 5, + PropertyMutability::ReadWrite, + ), ]; struct State { @@ -172,6 +182,19 @@ impl ObjectImpl for NdiAudioSrc { let _ = element.post_message(&gst::Message::new_latency().src(Some(&element)).build()); } + Property::UInt("loss-threshold", ..) => { + let mut settings = self.settings.lock().unwrap(); + let loss_threshold = value.get().unwrap(); + gst_debug!( + self.cat, + obj: &element, + "Changing loss threshold from {} to {}", + settings.loss_threshold, + loss_threshold + ); + settings.loss_threshold = loss_threshold; + drop(settings); + } _ => unimplemented!(), } } @@ -188,6 +211,10 @@ impl ObjectImpl for NdiAudioSrc { let settings = self.settings.lock().unwrap(); Ok(settings.ip.to_value()) } + Property::UInt("loss-threshold", ..) => { + let settings = self.settings.lock().unwrap(); + Ok(settings.loss_threshold.to_value()) + } _ => unimplemented!(), } } @@ -360,12 +387,17 @@ impl BaseSrcImpl for NdiAudioSrc { let time = ndi_struct.initial_timestamp; let mut skip_frame = true; + let mut count_frame_none = 0; while skip_frame { let frame_type = NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000); if frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none || frame_type == NDIlib_frame_type_e::NDIlib_frame_type_error { + if count_frame_none < _settings.loss_threshold{ + count_frame_none += 1; + continue; + } gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type none received, assuming that the source closed the stream...."]); return Err(gst::FlowReturn::CustomError); } diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index 71f9118a..28b37674 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -27,6 +27,7 @@ use hashmap_receivers; struct Settings { stream_name: String, ip: String, + loss_threshold: u32, id_receiver: i8, latency: Option, } @@ -36,13 +37,14 @@ impl Default for Settings { Settings { stream_name: String::from("Fixed ndi stream name"), ip: String::from(""), + loss_threshold: 5, id_receiver: 0, latency: None, } } } -static PROPERTIES: [Property; 2] = [ +static PROPERTIES: [Property; 3] = [ Property::String( "stream-name", "Sream Name", @@ -57,6 +59,14 @@ static PROPERTIES: [Property; 2] = [ None, PropertyMutability::ReadWrite, ), + Property::UInt( + "loss-threshold", + "Loss threshold", + "Loss threshold", + (0, 60), + 5, + PropertyMutability::ReadWrite, + ), ]; struct State { @@ -175,6 +185,19 @@ impl ObjectImpl for NdiVideoSrc { settings.ip = ip; drop(settings); } + Property::UInt("loss-threshold", ..) => { + let mut settings = self.settings.lock().unwrap(); + let loss_threshold = value.get().unwrap(); + gst_debug!( + self.cat, + obj: &element, + "Changing loss threshold from {} to {}", + settings.loss_threshold, + loss_threshold + ); + settings.loss_threshold = loss_threshold; + drop(settings); + } _ => unimplemented!(), } } @@ -191,6 +214,10 @@ impl ObjectImpl for NdiVideoSrc { let settings = self.settings.lock().unwrap(); Ok(settings.ip.to_value()) } + Property::UInt("loss-threshold", ..) => { + let settings = self.settings.lock().unwrap(); + Ok(settings.loss_threshold.to_value()) + } _ => unimplemented!(), } } @@ -364,12 +391,17 @@ impl BaseSrcImpl for NdiVideoSrc { let time = ndi_struct.initial_timestamp; let mut skip_frame = true; + let mut count_frame_none = 0; while skip_frame { let frame_type = NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000); if frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none || frame_type == NDIlib_frame_type_e::NDIlib_frame_type_error { + if count_frame_none < _settings.loss_threshold{ + count_frame_none += 1; + continue; + } gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type none received, assuming that the source closed the stream...."]); return Err(gst::FlowReturn::CustomError); } From e68bb9b4aa63b3be854870983eec22b8255c3931 Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Wed, 5 Dec 2018 15:22:01 +0100 Subject: [PATCH 090/199] Fix issues with multichanel audio --- src/ndiaudiosrc.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index a88f6e42..1698fd85 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -119,6 +119,7 @@ impl NdiAudioSrc { ("rate", &gst::IntRange::::new(1, i32::MAX)), ("channels", &gst::IntRange::::new(1, i32::MAX)), ("layout", &"interleaved"), + ("channel-mask", &gst::Bitmask::new(0)), ], ); @@ -313,7 +314,7 @@ impl BaseSrcImpl for NdiAudioSrc { } } - let no_samples = audio_frame.no_samples as u64 / audio_frame.no_channels as u64; + let no_samples = audio_frame.no_samples as u64; let audio_rate = audio_frame.sample_rate; settings.latency = gst::SECOND.mul_div_floor(no_samples, audio_rate as u64); @@ -324,6 +325,7 @@ impl BaseSrcImpl for NdiAudioSrc { s.fixate_field_nearest_int("rate", audio_rate); s.fixate_field_nearest_int("channels", audio_frame.no_channels); s.fixate_field_str("layout", "interleaved"); + s.set_value("channel-mask", gst::Bitmask::new(gst_audio::AudioChannelPosition::get_fallback_mask(audio_frame.no_channels as u32)).to_send_value()); } let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); @@ -378,7 +380,8 @@ impl BaseSrcImpl for NdiAudioSrc { pts = audio_frame.timestamp as u64 - time; - let buff_size = (audio_frame.channel_stride_in_bytes) as usize; + // We multiply by 2 because is the size in bytes of an i16 variable + let buff_size = (audio_frame.no_samples * 2 * audio_frame.no_channels) as usize; let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); { if ndi_struct.start_pts == gst::ClockTime(Some(0)) { From 9df0a01a0c09ea868c26c3a5bb6d473bc6d50708 Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Wed, 5 Dec 2018 16:13:36 +0100 Subject: [PATCH 091/199] If param loss_threshold is 0, we don't close the stream connection when receiving a NDIlib_frame_type_none --- src/ndiaudiosrc.rs | 2 +- src/ndivideosrc.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index bd889c6b..296c7bcd 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -391,7 +391,7 @@ impl BaseSrcImpl for NdiAudioSrc { while skip_frame { let frame_type = NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000); - if frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none + if (frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none && _settings.loss_threshold != 0) || frame_type == NDIlib_frame_type_e::NDIlib_frame_type_error { if count_frame_none < _settings.loss_threshold{ diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index 28b37674..120a0e79 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -395,7 +395,7 @@ impl BaseSrcImpl for NdiVideoSrc { while skip_frame { let frame_type = NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000); - if frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none + if (frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none && _settings.loss_threshold != 0) || frame_type == NDIlib_frame_type_e::NDIlib_frame_type_error { if count_frame_none < _settings.loss_threshold{ From 678c5876de2858f5fd7558ecf62eecda1238e8b2 Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Mon, 10 Dec 2018 13:27:23 +0100 Subject: [PATCH 092/199] Improved timestamps calculation Due to the possibility to connect to two or more streams simultaneously with different clocks synchronization It's necessary to improve the timestamps calculation to detect this. Prior to this commit, we saved the first timestamp that arrive and use it to calculate the running time of the stream for the rest of frames (pts field in gstreamer buffer) in all of the streams. This lead to problems when connecting to multiple streams in multiple computers and the clocks were not correctly synchronized. To fix this, now we save a different initial timestamp for each stream. --- src/lib.rs | 4 ++-- src/ndiaudiosrc.rs | 24 +++++++++++++++++------- src/ndivideosrc.rs | 29 ++++++++++++++++++++++------- 3 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4dcc1320..fab45fa1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,16 +41,15 @@ struct ndi_receiver_info { video: bool, audio: bool, ndi_instance: NdiInstance, + initial_timestamp: u64, id: i8, } struct Ndi { - initial_timestamp: u64, start_pts: gst::ClockTime, } static mut ndi_struct: Ndi = Ndi { - initial_timestamp: 0, start_pts: gst::ClockTime(Some(0)), }; @@ -214,6 +213,7 @@ fn connect_ndi(cat: gst::DebugCategory, element: &BaseSrc, ip: &str, stream_name video, audio, ndi_instance: NdiInstance { recv: pNDI_recv }, + initial_timestamp: 0, id: id_receiver, }, ); diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index 16228144..d0355f98 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -80,6 +80,7 @@ impl Default for State { struct TimestampData { offset: u64, + initial_timestamp: u64, } struct NdiAudioSrc { @@ -102,7 +103,7 @@ impl NdiAudioSrc { ), settings: Mutex::new(Default::default()), state: Mutex::new(Default::default()), - timestamp_data: Mutex::new(TimestampData { offset: 0 }), + timestamp_data: Mutex::new(TimestampData { offset: 0 , initial_timestamp: 0}), }) } @@ -228,10 +229,10 @@ impl ElementImpl for NdiAudioSrc { transition: gst::StateChange, ) -> gst::StateChangeReturn { if transition == gst::StateChange::PausedToPlaying { - let receivers = hashmap_receivers.lock().unwrap(); + let mut receivers = hashmap_receivers.lock().unwrap(); let settings = self.settings.lock().unwrap(); - let receiver = receivers.get(&settings.id_receiver).unwrap(); + let receiver = receivers.get_mut(&settings.id_receiver).unwrap(); let recv = &receiver.ndi_instance; let pNDI_recv = recv.recv; @@ -249,10 +250,13 @@ impl ElementImpl for NdiAudioSrc { ); } - if ndi_struct.initial_timestamp <= audio_frame.timestamp as u64 - || ndi_struct.initial_timestamp == 0 + let mut timestamp_data = self.timestamp_data.lock().unwrap(); + timestamp_data.initial_timestamp = receiver.initial_timestamp; + if receiver.initial_timestamp <= audio_frame.timestamp as u64 + || receiver.initial_timestamp == 0 { - ndi_struct.initial_timestamp = audio_frame.timestamp as u64; + receiver.initial_timestamp = audio_frame.timestamp as u64; + timestamp_data.initial_timestamp = audio_frame.timestamp as u64; } } } @@ -386,7 +390,7 @@ impl BaseSrcImpl for NdiAudioSrc { let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); unsafe { - let time = ndi_struct.initial_timestamp; + let time = timestamp_data.initial_timestamp; let mut skip_frame = true; let mut count_frame_none = 0; @@ -403,6 +407,12 @@ impl BaseSrcImpl for NdiAudioSrc { gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type none received, assuming that the source closed the stream...."]); return Err(gst::FlowReturn::CustomError); } + else{ + if frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none && _settings.loss_threshold == 0{ + let buffer = gst::Buffer::with_size(0).unwrap(); + return Ok(buffer) + } + } if time >= (audio_frame.timestamp as u64) { gst_debug!(self.cat, obj: element, "Frame timestamp ({:?}) is lower than received in the first frame from NDI ({:?}), so skiping...", (audio_frame.timestamp as u64), time); } else { diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index 120a0e79..5e32020f 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -81,6 +81,7 @@ impl Default for State { struct TimestampData { offset: u64, + initial_timestamp: u64, } struct NdiVideoSrc { @@ -103,7 +104,7 @@ impl NdiVideoSrc { ), settings: Mutex::new(Default::default()), state: Mutex::new(Default::default()), - timestamp_data: Mutex::new(TimestampData { offset: 0 }), + timestamp_data: Mutex::new(TimestampData { offset: 0 , initial_timestamp: 0}), }) } @@ -230,10 +231,10 @@ impl ElementImpl for NdiVideoSrc { transition: gst::StateChange, ) -> gst::StateChangeReturn { if transition == gst::StateChange::PausedToPlaying { - let receivers = hashmap_receivers.lock().unwrap(); + let mut receivers = hashmap_receivers.lock().unwrap(); let settings = self.settings.lock().unwrap(); - let receiver = receivers.get(&settings.id_receiver).unwrap(); + let receiver = receivers.get_mut(&settings.id_receiver).unwrap(); let recv = &receiver.ndi_instance; let pNDI_recv = recv.recv; @@ -251,11 +252,15 @@ impl ElementImpl for NdiVideoSrc { ); } - if ndi_struct.initial_timestamp <= video_frame.timestamp as u64 - || ndi_struct.initial_timestamp == 0 + let mut timestamp_data = self.timestamp_data.lock().unwrap(); + timestamp_data.initial_timestamp = receiver.initial_timestamp; + if receiver.initial_timestamp <= video_frame.timestamp as u64 + || receiver.initial_timestamp == 0 { - ndi_struct.initial_timestamp = video_frame.timestamp as u64; + receiver.initial_timestamp = video_frame.timestamp as u64; + timestamp_data.initial_timestamp = video_frame.timestamp as u64; } + } } element.parent_change_state(transition) @@ -388,7 +393,7 @@ impl BaseSrcImpl for NdiVideoSrc { let video_frame: NDIlib_video_frame_v2_t = Default::default(); unsafe { - let time = ndi_struct.initial_timestamp; + let time = timestamp_data.initial_timestamp; let mut skip_frame = true; let mut count_frame_none = 0; @@ -405,6 +410,12 @@ impl BaseSrcImpl for NdiVideoSrc { gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type none received, assuming that the source closed the stream...."]); return Err(gst::FlowReturn::CustomError); } + else{ + if frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none && _settings.loss_threshold == 0{ + let buffer = gst::Buffer::with_size(0).unwrap(); + return Ok(buffer) + } + } if time >= (video_frame.timestamp as u64) { gst_debug!(self.cat, obj: element, "Frame timestamp ({:?}) is lower than received in the first frame from NDI ({:?}), so skiping...", (video_frame.timestamp as u64), time); } else { @@ -412,8 +423,12 @@ impl BaseSrcImpl for NdiVideoSrc { } } + gst_warning!(self.cat, obj: element, "NDI video frame received: {:?}", (video_frame)); + pts = video_frame.timestamp as u64 - time; + gst_warning!(self.cat, obj: element, "Calculated pts for video frame: {:?}", (pts)); + let buff_size = (video_frame.yres * video_frame.line_stride_in_bytes) as usize; let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); { From 9a2b64744332df2a985baf11d4a7cb1a1c0d8e91 Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Mon, 10 Dec 2018 17:29:30 +0100 Subject: [PATCH 093/199] Updated descriptions of element properties --- src/ndiaudiosrc.rs | 4 ++-- src/ndivideosrc.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index 16228144..df8ab421 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -54,14 +54,14 @@ static PROPERTIES: [Property; 3] = [ Property::String( "ip", "Stream IP", - "Stream IP", + "IP of the streaming device. Ex: 127.0.0.1:5961", None, PropertyMutability::ReadWrite, ), Property::UInt( "loss-threshold", "Loss threshold", - "Loss threshold", + "Loss threshold. If 0 the stream is never closed by the element", (0, 60), 5, PropertyMutability::ReadWrite, diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index 120a0e79..162e336c 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -47,7 +47,7 @@ impl Default for Settings { static PROPERTIES: [Property; 3] = [ Property::String( "stream-name", - "Sream Name", + "Stream Name", "Name of the streaming device", None, PropertyMutability::ReadWrite, @@ -55,14 +55,14 @@ static PROPERTIES: [Property; 3] = [ Property::String( "ip", "Stream IP", - "Stream IP", + "IP of the streaming device. Ex: 127.0.0.1:5961", None, PropertyMutability::ReadWrite, ), Property::UInt( "loss-threshold", "Loss threshold", - "Loss threshold", + "Loss threshold. If 0 the stream is never closed by the element", (0, 60), 5, PropertyMutability::ReadWrite, From 3e38042d7f549a2863699dad615cc73d9eb3f5ec Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Tue, 11 Dec 2018 12:26:50 +0100 Subject: [PATCH 094/199] Improve logging #14 --- src/ndiaudiosrc.rs | 12 ++++++++++-- src/ndivideosrc.rs | 13 ++++++++----- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index d0355f98..3a141afc 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -248,6 +248,7 @@ impl ElementImpl for NdiAudioSrc { ptr::null(), 1000, ); + gst_debug!(self.cat, obj: element, "NDI audio frame received: {:?}", audio_frame); } let mut timestamp_data = self.timestamp_data.lock().unwrap(); @@ -258,6 +259,7 @@ impl ElementImpl for NdiAudioSrc { receiver.initial_timestamp = audio_frame.timestamp as u64; timestamp_data.initial_timestamp = audio_frame.timestamp as u64; } + gst_debug!(self.cat, obj: element, "Setting initial timestamp to {}", timestamp_data.initial_timestamp); } } element.parent_change_state(transition) @@ -342,6 +344,7 @@ impl BaseSrcImpl for NdiAudioSrc { unsafe { frame_type = NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000); + gst_debug!(self.cat, obj: element, "NDI audio frame received: {:?}", audio_frame); } } @@ -404,11 +407,12 @@ impl BaseSrcImpl for NdiAudioSrc { count_frame_none += 1; continue; } - gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type none received, assuming that the source closed the stream...."]); + gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type none or error received, assuming that the source closed the stream...."]); return Err(gst::FlowReturn::CustomError); } else{ if frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none && _settings.loss_threshold == 0{ + gst_debug!(self.cat, obj: element, "No audio frame received, sending empty buffer"); let buffer = gst::Buffer::with_size(0).unwrap(); return Ok(buffer) } @@ -420,8 +424,12 @@ impl BaseSrcImpl for NdiAudioSrc { } } + gst_log!(self.cat, obj: element, "NDI audio frame received: {:?}", (audio_frame)); + pts = audio_frame.timestamp as u64 - time; + gst_log!(self.cat, obj: element, "Calculated pts for audio frame: {:?}", (pts)); + // We multiply by 2 because is the size in bytes of an i16 variable let buff_size = (audio_frame.no_samples * 2 * audio_frame.no_channels) as usize; let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); @@ -453,7 +461,7 @@ impl BaseSrcImpl for NdiAudioSrc { NDIlib_util_audio_to_interleaved_16s_v2(&audio_frame, &mut dst); } - gst_debug!(self.cat, obj: element, "Produced buffer {:?}", buffer); + gst_log!(self.cat, obj: element, "Produced buffer {:?}", buffer); Ok(buffer) } diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index 5e32020f..c2674c20 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -250,6 +250,7 @@ impl ElementImpl for NdiVideoSrc { ptr::null(), 1000, ); + gst_debug!(self.cat, obj: element, "NDI video frame received: {:?}", video_frame); } let mut timestamp_data = self.timestamp_data.lock().unwrap(); @@ -260,7 +261,7 @@ impl ElementImpl for NdiVideoSrc { receiver.initial_timestamp = video_frame.timestamp as u64; timestamp_data.initial_timestamp = video_frame.timestamp as u64; } - + gst_debug!(self.cat, obj: element, "Setting initial timestamp to {}", timestamp_data.initial_timestamp); } } element.parent_change_state(transition) @@ -342,6 +343,7 @@ impl BaseSrcImpl for NdiVideoSrc { unsafe { frame_type = NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000); + gst_debug!(self.cat, obj: element, "NDI video frame received: {:?}", video_frame); } } @@ -407,11 +409,12 @@ impl BaseSrcImpl for NdiVideoSrc { count_frame_none += 1; continue; } - gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type none received, assuming that the source closed the stream...."]); + gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type none or error received, assuming that the source closed the stream...."]); return Err(gst::FlowReturn::CustomError); } else{ if frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none && _settings.loss_threshold == 0{ + gst_debug!(self.cat, obj: element, "No video frame received, sending empty buffer"); let buffer = gst::Buffer::with_size(0).unwrap(); return Ok(buffer) } @@ -423,11 +426,11 @@ impl BaseSrcImpl for NdiVideoSrc { } } - gst_warning!(self.cat, obj: element, "NDI video frame received: {:?}", (video_frame)); + gst_log!(self.cat, obj: element, "NDI video frame received: {:?}", (video_frame)); pts = video_frame.timestamp as u64 - time; - gst_warning!(self.cat, obj: element, "Calculated pts for video frame: {:?}", (pts)); + gst_log!(self.cat, obj: element, "Calculated pts for video frame: {:?}", (pts)); let buff_size = (video_frame.yres * video_frame.line_stride_in_bytes) as usize; let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); @@ -455,7 +458,7 @@ impl BaseSrcImpl for NdiVideoSrc { buffer.copy_from_slice(0, &vec).unwrap(); } - gst_debug!(self.cat, obj: element, "Produced buffer {:?}", buffer); + gst_log!(self.cat, obj: element, "Produced buffer {:?}", buffer); Ok(buffer) } From 6116729c5d83a857e81d13ca7a0142ad25242f67 Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Tue, 11 Dec 2018 16:42:27 +0100 Subject: [PATCH 095/199] Fixed timestamps --- src/ndiaudiosrc.rs | 10 +++------- src/ndivideosrc.rs | 10 +++------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index 3a141afc..bb9eab8d 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -80,7 +80,6 @@ impl Default for State { struct TimestampData { offset: u64, - initial_timestamp: u64, } struct NdiAudioSrc { @@ -103,7 +102,7 @@ impl NdiAudioSrc { ), settings: Mutex::new(Default::default()), state: Mutex::new(Default::default()), - timestamp_data: Mutex::new(TimestampData { offset: 0 , initial_timestamp: 0}), + timestamp_data: Mutex::new(TimestampData { offset: 0}), }) } @@ -251,15 +250,12 @@ impl ElementImpl for NdiAudioSrc { gst_debug!(self.cat, obj: element, "NDI audio frame received: {:?}", audio_frame); } - let mut timestamp_data = self.timestamp_data.lock().unwrap(); - timestamp_data.initial_timestamp = receiver.initial_timestamp; if receiver.initial_timestamp <= audio_frame.timestamp as u64 || receiver.initial_timestamp == 0 { receiver.initial_timestamp = audio_frame.timestamp as u64; - timestamp_data.initial_timestamp = audio_frame.timestamp as u64; } - gst_debug!(self.cat, obj: element, "Setting initial timestamp to {}", timestamp_data.initial_timestamp); + gst_debug!(self.cat, obj: element, "Setting initial timestamp to {}", receiver.initial_timestamp); } } element.parent_change_state(transition) @@ -393,7 +389,7 @@ impl BaseSrcImpl for NdiAudioSrc { let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); unsafe { - let time = timestamp_data.initial_timestamp; + let time = receivers.get(&_settings.id_receiver).unwrap().initial_timestamp; let mut skip_frame = true; let mut count_frame_none = 0; diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index c2674c20..3df32973 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -81,7 +81,6 @@ impl Default for State { struct TimestampData { offset: u64, - initial_timestamp: u64, } struct NdiVideoSrc { @@ -104,7 +103,7 @@ impl NdiVideoSrc { ), settings: Mutex::new(Default::default()), state: Mutex::new(Default::default()), - timestamp_data: Mutex::new(TimestampData { offset: 0 , initial_timestamp: 0}), + timestamp_data: Mutex::new(TimestampData { offset: 0 }), }) } @@ -253,15 +252,12 @@ impl ElementImpl for NdiVideoSrc { gst_debug!(self.cat, obj: element, "NDI video frame received: {:?}", video_frame); } - let mut timestamp_data = self.timestamp_data.lock().unwrap(); - timestamp_data.initial_timestamp = receiver.initial_timestamp; if receiver.initial_timestamp <= video_frame.timestamp as u64 || receiver.initial_timestamp == 0 { receiver.initial_timestamp = video_frame.timestamp as u64; - timestamp_data.initial_timestamp = video_frame.timestamp as u64; } - gst_debug!(self.cat, obj: element, "Setting initial timestamp to {}", timestamp_data.initial_timestamp); + gst_debug!(self.cat, obj: element, "Setting initial timestamp to {}", receiver.initial_timestamp); } } element.parent_change_state(transition) @@ -395,7 +391,7 @@ impl BaseSrcImpl for NdiVideoSrc { let video_frame: NDIlib_video_frame_v2_t = Default::default(); unsafe { - let time = timestamp_data.initial_timestamp; + let time = receivers.get(&_settings.id_receiver).unwrap().initial_timestamp; let mut skip_frame = true; let mut count_frame_none = 0; From d3ba5efff87810c9839ff9df01e3d3ae1c6fc1b8 Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Tue, 11 Dec 2018 17:47:03 +0100 Subject: [PATCH 096/199] Port to new glib subclassing API #13 --- Cargo.toml | 12 +- src/lib.rs | 37 ++- src/ndiaudiosrc.rs | 691 +++++++++++++++++++++++---------------------- src/ndivideosrc.rs | 664 ++++++++++++++++++++++--------------------- 4 files changed, 705 insertions(+), 699 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 89f43cc0..f7342312 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,13 +6,11 @@ repository = "https://github.com/teltek/gst-plugin-ndi" license = "LGPL" [dependencies] -gobject-subclass = "0.2" -gst-plugin = "0.3" -glib = "0.6" -gstreamer = "0.12" -gstreamer-base = "0.12" -gstreamer-video = "0.12" -gstreamer-audio = "0.12" +glib = { git = "https://github.com/gtk-rs/glib", features = ["subclassing"] } +gstreamer = { git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["subclassing"] } +gstreamer-base = { git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["subclassing"] } +gstreamer-video = { git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" } +gstreamer-audio = { git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" } lazy_static = "1.1.0" [lib] diff --git a/src/lib.rs b/src/lib.rs index fab45fa1..d3b2dd27 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,7 @@ #![allow(non_camel_case_types, non_upper_case_globals, non_snake_case)] -extern crate glib; -extern crate gobject_subclass; - #[macro_use] -extern crate gst_plugin; +extern crate glib; #[macro_use] extern crate gstreamer as gst; use gst::prelude::*; @@ -19,7 +16,7 @@ mod ndiaudiosrc; pub mod ndisys; mod ndivideosrc; -use gst_plugin::base_src::*; +// use gst_plugin::base_src::*; use ndisys::*; use std::ffi::{CStr, CString}; use std::{thread, time}; @@ -29,10 +26,10 @@ use std::sync::Mutex; use gst::GstObjectExt; -fn plugin_init(plugin: &gst::Plugin) -> bool { - ndivideosrc::register(plugin); - ndiaudiosrc::register(plugin); - true +fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { + ndivideosrc::register(plugin)?; + ndiaudiosrc::register(plugin)?; + Ok(()) } struct ndi_receiver_info { @@ -62,7 +59,7 @@ lazy_static! { static mut id_receiver: i8 = 0; -fn connect_ndi(cat: gst::DebugCategory, element: &BaseSrc, ip: &str, stream_name: &str) -> i8 { +fn connect_ndi(cat: gst::DebugCategory, element: &gst_base::BaseSrc, ip: &str, stream_name: &str) -> i8 { gst_debug!(cat, obj: element, "Starting NDI connection..."); let mut receivers = hashmap_receivers.lock().unwrap(); @@ -223,7 +220,7 @@ fn connect_ndi(cat: gst::DebugCategory, element: &BaseSrc, ip: &str, stream_name } } -fn stop_ndi(cat: gst::DebugCategory, element: &BaseSrc, id: i8) -> bool { +fn stop_ndi(cat: gst::DebugCategory, element: &gst_base::BaseSrc, id: i8) -> bool { gst_debug!(cat, obj: element, "Closing NDI connection..."); let mut receivers = hashmap_receivers.lock().unwrap(); { @@ -250,14 +247,14 @@ fn stop_ndi(cat: gst::DebugCategory, element: &BaseSrc, id: i8) -> bool { true } -plugin_define!( - b"ndi\0", - b"NewTek NDI Plugin\0", +gst_plugin_define!( + "ndi", + "NewTek NDI Plugin", plugin_init, - b"1.0.0\0", - b"LGPL\0", - b"ndi\0", - b"ndi\0", - b"https://github.com/teltek/gst-plugin-ndi\0", - b"2018-04-09\0" + "1.0.0", + "LGPL", + "ndi", + "ndi", + "https://github.com/teltek/gst-plugin-ndi", + "2018-04-09" ); diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index fe57dd50..ea172d9e 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -1,14 +1,15 @@ -#![allow(non_camel_case_types, non_upper_case_globals, non_snake_case)] +#![allow(non_camel_case_types, non_upper_case_globals, non_snake_case, cast_ptr_alignment)] use glib; +use glib::subclass; +use glib::subclass::prelude::*; use gst; use gst::prelude::*; +use gst::subclass::prelude::*; use gst_audio; +use gst_base; use gst_base::prelude::*; - -use gobject_subclass::object::*; -use gst_plugin::base_src::*; -use gst_plugin::element::*; +use gst_base::subclass::prelude::*; use std::sync::Mutex; use std::{i32, u32}; @@ -43,29 +44,36 @@ impl Default for Settings { } } -static PROPERTIES: [Property; 3] = [ - Property::String( +static PROPERTIES: [subclass::Property; 3] = [ +subclass::Property("stream-name", || { + glib::ParamSpec::string( "stream-name", "Sream Name", "Name of the streaming device", None, - PropertyMutability::ReadWrite, - ), - Property::String( + glib::ParamFlags::READWRITE, + ) +}), +subclass::Property("ip", || { + glib::ParamSpec::string( "ip", "Stream IP", "IP of the streaming device. Ex: 127.0.0.1:5961", None, - PropertyMutability::ReadWrite, - ), - Property::UInt( + glib::ParamFlags::READWRITE, + ) +}), +subclass::Property("loss-threshold", || { + glib::ParamSpec::uint( "loss-threshold", "Loss threshold", - "Loss threshold. If 0 the stream is never closed by the element", - (0, 60), + "Loss threshold", + 0, + 60, 5, - PropertyMutability::ReadWrite, - ), + glib::ParamFlags::READWRITE, + ) +}), ]; struct State { @@ -89,12 +97,17 @@ struct NdiAudioSrc { timestamp_data: Mutex, } -impl NdiAudioSrc { - fn new(element: &BaseSrc) -> Box> { - element.set_live(true); - element.set_format(gst::Format::Time); +impl ObjectSubclass for NdiAudioSrc { - Box::new(Self { + const NAME: &'static str = "NdiAudioSrc"; + type ParentType = gst_base::BaseSrc; + type Instance = gst::subclass::ElementInstanceStruct; + type Class = subclass::simple::ClassStruct; + + glib_object_subclass!(); + + fn new() -> Self { + Self { cat: gst::DebugCategory::new( "ndiaudiosrc", gst::DebugColorFlags::empty(), @@ -103,10 +116,10 @@ impl NdiAudioSrc { settings: Mutex::new(Default::default()), state: Mutex::new(Default::default()), timestamp_data: Mutex::new(TimestampData { offset: 0}), - }) + } } - fn class_init(klass: &mut BaseSrcClass) { + fn class_init(klass: &mut subclass::simple::ClassStruct) { klass.set_metadata( "NewTek NDI Audio Source", "Source", @@ -117,370 +130,358 @@ impl NdiAudioSrc { let caps = gst::Caps::new_simple( "audio/x-raw", &[ - ( - "format", - &gst::List::new(&[ - //TODO add more formats? - //&gst_audio::AUDIO_FORMAT_F32.to_string(), - //&gst_audio::AUDIO_FORMAT_F64.to_string(), - &gst_audio::AUDIO_FORMAT_S16.to_string(), + ( + "format", + &gst::List::new(&[ + //TODO add more formats? + //&gst_audio::AUDIO_FORMAT_F32.to_string(), + //&gst_audio::AUDIO_FORMAT_F64.to_string(), + &gst_audio::AUDIO_FORMAT_S16.to_string(), ]), ), ("rate", &gst::IntRange::::new(1, i32::MAX)), ("channels", &gst::IntRange::::new(1, i32::MAX)), ("layout", &"interleaved"), ("channel-mask", &gst::Bitmask::new(0)), - ], - ); + ], + ); - let src_pad_template = gst::PadTemplate::new( - "src", - gst::PadDirection::Src, - gst::PadPresence::Always, - &caps, - ); - klass.add_pad_template(src_pad_template); + let src_pad_template = gst::PadTemplate::new( + "src", + gst::PadDirection::Src, + gst::PadPresence::Always, + &caps, + ); + klass.add_pad_template(src_pad_template); - klass.install_properties(&PROPERTIES); - } -} - -impl ObjectImpl for NdiAudioSrc { - fn set_property(&self, obj: &glib::Object, id: u32, value: &glib::Value) { - let prop = &PROPERTIES[id as usize]; - let element = obj.clone().downcast::().unwrap(); - - match *prop { - Property::String("stream-name", ..) => { - let mut settings = self.settings.lock().unwrap(); - let stream_name = value.get().unwrap(); - gst_debug!( - self.cat, - obj: &element, - "Changing stream-name from {} to {}", - settings.stream_name, - stream_name - ); - settings.stream_name = stream_name; - drop(settings); - - let _ = - element.post_message(&gst::Message::new_latency().src(Some(&element)).build()); - } - Property::String("ip", ..) => { - let mut settings = self.settings.lock().unwrap(); - let ip = value.get().unwrap(); - gst_debug!( - self.cat, - obj: &element, - "Changing ip from {} to {}", - settings.ip, - ip - ); - settings.ip = ip; - drop(settings); - - let _ = - element.post_message(&gst::Message::new_latency().src(Some(&element)).build()); - } - Property::UInt("loss-threshold", ..) => { - let mut settings = self.settings.lock().unwrap(); - let loss_threshold = value.get().unwrap(); - gst_debug!( - self.cat, - obj: &element, - "Changing loss threshold from {} to {}", - settings.loss_threshold, - loss_threshold - ); - settings.loss_threshold = loss_threshold; - drop(settings); - } - _ => unimplemented!(), + klass.install_properties(&PROPERTIES); } } - fn get_property(&self, _obj: &glib::Object, id: u32) -> Result { - let prop = &PROPERTIES[id as usize]; + impl ObjectImpl for NdiAudioSrc { + glib_object_impl!(); - match *prop { - Property::String("stream-name", ..) => { - let settings = self.settings.lock().unwrap(); - Ok(settings.stream_name.to_value()) + fn constructed(&self, obj: &glib::Object) { + self.parent_constructed(obj); + + let basesrc = obj.downcast_ref::().unwrap(); + // Initialize live-ness and notify the base class that + // we'd like to operate in Time format + basesrc.set_live(true); + basesrc.set_format(gst::Format::Time); + } + + fn set_property(&self, obj: &glib::Object, id: usize, value: &glib::Value) { + let prop = &PROPERTIES[id]; + let basesrc = obj.downcast_ref::().unwrap(); + + match *prop { + subclass::Property("stream-name", ..) => { + let mut settings = self.settings.lock().unwrap(); + let stream_name = value.get().unwrap(); + gst_debug!( + self.cat, + obj: basesrc, + "Changing stream-name from {} to {}", + settings.stream_name, + stream_name + ); + settings.stream_name = stream_name; + drop(settings); + } + subclass::Property("ip", ..) => { + let mut settings = self.settings.lock().unwrap(); + let ip = value.get().unwrap(); + gst_debug!( + self.cat, + obj: basesrc, + "Changing ip from {} to {}", + settings.ip, + ip + ); + settings.ip = ip; + drop(settings); + } + subclass::Property("loss-threshold", ..) => { + let mut settings = self.settings.lock().unwrap(); + let loss_threshold = value.get().unwrap(); + gst_debug!( + self.cat, + obj: basesrc, + "Changing loss threshold from {} to {}", + settings.loss_threshold, + loss_threshold + ); + settings.loss_threshold = loss_threshold; + drop(settings); + } + _ => unimplemented!(), } - Property::String("ip", ..) => { - let settings = self.settings.lock().unwrap(); - Ok(settings.ip.to_value()) + } + + fn get_property(&self, _obj: &glib::Object, id: usize) -> Result { + let prop = &PROPERTIES[id]; + + match *prop { + subclass::Property("stream-name", ..) => { + let settings = self.settings.lock().unwrap(); + Ok(settings.stream_name.to_value()) + } + subclass::Property("ip", ..) => { + let settings = self.settings.lock().unwrap(); + Ok(settings.ip.to_value()) + } + subclass::Property("loss-threshold", ..) => { + let settings = self.settings.lock().unwrap(); + Ok(settings.loss_threshold.to_value()) + } + _ => unimplemented!(), } - Property::UInt("loss-threshold", ..) => { - let settings = self.settings.lock().unwrap(); - Ok(settings.loss_threshold.to_value()) - } - _ => unimplemented!(), } } -} -impl ElementImpl for NdiAudioSrc { - fn change_state( - &self, - element: &BaseSrc, - transition: gst::StateChange, - ) -> gst::StateChangeReturn { - if transition == gst::StateChange::PausedToPlaying { - let mut receivers = hashmap_receivers.lock().unwrap(); + impl ElementImpl for NdiAudioSrc { + fn change_state( + &self, + element: &gst::Element, + transition: gst::StateChange, + ) -> gst::StateChangeReturn { + if transition == gst::StateChange::PausedToPlaying { + let mut receivers = hashmap_receivers.lock().unwrap(); + let settings = self.settings.lock().unwrap(); + + let receiver = receivers.get_mut(&settings.id_receiver).unwrap(); + let recv = &receiver.ndi_instance; + let pNDI_recv = recv.recv; + + let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); + + let mut frame_type: NDIlib_frame_type_e = NDIlib_frame_type_e::NDIlib_frame_type_none; + unsafe { + while frame_type != NDIlib_frame_type_e::NDIlib_frame_type_audio { + frame_type = NDIlib_recv_capture_v2( + pNDI_recv, + ptr::null(), + &audio_frame, + ptr::null(), + 1000, + ); + gst_debug!(self.cat, obj: element, "NDI audio frame received: {:?}", audio_frame); + } + + if receiver.initial_timestamp <= audio_frame.timestamp as u64 + || receiver.initial_timestamp == 0 + { + receiver.initial_timestamp = audio_frame.timestamp as u64; + } + gst_debug!(self.cat, obj: element, "Setting initial timestamp to {}", receiver.initial_timestamp); + } + } + self.parent_change_state(element, transition) + } + } + + impl BaseSrcImpl for NdiAudioSrc { + fn set_caps(&self, element: &gst_base::BaseSrc, caps: &gst::CapsRef) -> bool { + let info = match gst_audio::AudioInfo::from_caps(caps) { + None => return false, + Some(info) => info, + }; + + gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); + + let mut state = self.state.lock().unwrap(); + state.info = Some(info); + + true + } + + fn start(&self, element: &gst_base::BaseSrc) -> bool { + *self.state.lock().unwrap() = Default::default(); + + let mut settings = self.settings.lock().unwrap(); + settings.id_receiver = connect_ndi( + self.cat, + element, + &settings.ip.clone(), + &settings.stream_name.clone(), + ); + + settings.id_receiver != 0 + } + + fn stop(&self, element: &gst_base::BaseSrc) -> bool { + *self.state.lock().unwrap() = Default::default(); + let settings = self.settings.lock().unwrap(); + stop_ndi(self.cat, element, settings.id_receiver); + // Commented because when adding ndi destroy stopped in this line + //*self.state.lock().unwrap() = Default::default(); + true + } + + fn query(&self, element: &gst_base::BaseSrc, query: &mut gst::QueryRef) -> bool { + use gst::QueryView; + if let QueryView::Scheduling(ref mut q) = query.view_mut() { + q.set(gst::SchedulingFlags::SEQUENTIAL, 1, -1, 0); + q.add_scheduling_modes(&[gst::PadMode::Push]); + return true; + } + if let QueryView::Latency(ref mut q) = query.view_mut() { + let settings = &*self.settings.lock().unwrap(); + let state = self.state.lock().unwrap(); + + if let Some(ref _info) = state.info { + let latency = settings.latency.unwrap(); + gst_debug!(self.cat, obj: element, "Returning latency {}", latency); + q.set(true, latency, gst::CLOCK_TIME_NONE); + return true; + } else { + return false; + } + } + BaseSrcImpl::parent_query(self, element, query) + } + + fn fixate(&self, element: &gst_base::BaseSrc, caps: gst::Caps) -> gst::Caps { + let receivers = hashmap_receivers.lock().unwrap(); + let mut settings = self.settings.lock().unwrap(); + + let receiver = receivers.get(&settings.id_receiver).unwrap(); - let receiver = receivers.get_mut(&settings.id_receiver).unwrap(); let recv = &receiver.ndi_instance; let pNDI_recv = recv.recv; let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); let mut frame_type: NDIlib_frame_type_e = NDIlib_frame_type_e::NDIlib_frame_type_none; - unsafe { - while frame_type != NDIlib_frame_type_e::NDIlib_frame_type_audio { - frame_type = NDIlib_recv_capture_v2( - pNDI_recv, - ptr::null(), - &audio_frame, - ptr::null(), - 1000, - ); - gst_debug!(self.cat, obj: element, "NDI audio frame received: {:?}", audio_frame); - } - - if receiver.initial_timestamp <= audio_frame.timestamp as u64 - || receiver.initial_timestamp == 0 - { - receiver.initial_timestamp = audio_frame.timestamp as u64; - } - gst_debug!(self.cat, obj: element, "Setting initial timestamp to {}", receiver.initial_timestamp); - } - } - element.parent_change_state(transition) - } -} - -impl BaseSrcImpl for NdiAudioSrc { - fn set_caps(&self, element: &BaseSrc, caps: &gst::CapsRef) -> bool { - let info = match gst_audio::AudioInfo::from_caps(caps) { - None => return false, - Some(info) => info, - }; - - gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); - - let mut state = self.state.lock().unwrap(); - state.info = Some(info); - - true - } - - fn start(&self, element: &BaseSrc) -> bool { - *self.state.lock().unwrap() = Default::default(); - - let mut settings = self.settings.lock().unwrap(); - settings.id_receiver = connect_ndi( - self.cat, - element, - &settings.ip.clone(), - &settings.stream_name.clone(), - ); - - settings.id_receiver != 0 - } - - fn stop(&self, element: &BaseSrc) -> bool { - *self.state.lock().unwrap() = Default::default(); - - let settings = self.settings.lock().unwrap(); - stop_ndi(self.cat, element, settings.id_receiver); - // Commented because when adding ndi destroy stopped in this line - //*self.state.lock().unwrap() = Default::default(); - true - } - - fn query(&self, element: &BaseSrc, query: &mut gst::QueryRef) -> bool { - use gst::QueryView; - if let QueryView::Scheduling(ref mut q) = query.view_mut() { - q.set(gst::SchedulingFlags::SEQUENTIAL, 1, -1, 0); - q.add_scheduling_modes(&[gst::PadMode::Push]); - return true; - } - if let QueryView::Latency(ref mut q) = query.view_mut() { - let settings = &*self.settings.lock().unwrap(); - let state = self.state.lock().unwrap(); - - if let Some(ref _info) = state.info { - let latency = settings.latency.unwrap(); - gst_debug!(self.cat, obj: element, "Returning latency {}", latency); - q.set(true, latency, gst::CLOCK_TIME_NONE); - return true; - } else { - return false; - } - } - BaseSrcBase::parent_query(element, query) - } - - fn fixate(&self, element: &BaseSrc, caps: gst::Caps) -> gst::Caps { - let receivers = hashmap_receivers.lock().unwrap(); - let mut settings = self.settings.lock().unwrap(); - - let receiver = receivers.get(&settings.id_receiver).unwrap(); - - let recv = &receiver.ndi_instance; - let pNDI_recv = recv.recv; - - let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); - - let mut frame_type: NDIlib_frame_type_e = NDIlib_frame_type_e::NDIlib_frame_type_none; - while frame_type != NDIlib_frame_type_e::NDIlib_frame_type_audio { - unsafe { - frame_type = + while frame_type != NDIlib_frame_type_e::NDIlib_frame_type_audio { + unsafe { + frame_type = NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000); gst_debug!(self.cat, obj: element, "NDI audio frame received: {:?}", audio_frame); - } - } - - let no_samples = audio_frame.no_samples as u64; - let audio_rate = audio_frame.sample_rate; - settings.latency = gst::SECOND.mul_div_floor(no_samples, audio_rate as u64); - - let mut caps = gst::Caps::truncate(caps); - { - let caps = caps.make_mut(); - let s = caps.get_mut_structure(0).unwrap(); - s.fixate_field_nearest_int("rate", audio_rate); - s.fixate_field_nearest_int("channels", audio_frame.no_channels); - s.fixate_field_str("layout", "interleaved"); - s.set_value("channel-mask", gst::Bitmask::new(gst_audio::AudioChannelPosition::get_fallback_mask(audio_frame.no_channels as u32)).to_send_value()); - } - - let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); - element.parent_fixate(caps) - } - - fn create( - &self, - element: &BaseSrc, - _offset: u64, - _length: u32, - ) -> Result { - let _settings = &*self.settings.lock().unwrap(); - - let mut timestamp_data = self.timestamp_data.lock().unwrap(); - - let state = self.state.lock().unwrap(); - let _info = match state.info { - None => { - gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); - return Err(gst::FlowReturn::NotNegotiated); - } - Some(ref info) => info.clone(), - }; - let receivers = hashmap_receivers.lock().unwrap(); - - let recv = &receivers.get(&_settings.id_receiver).unwrap().ndi_instance; - let pNDI_recv = recv.recv; - - let pts: u64; - let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); - - unsafe { - let time = receivers.get(&_settings.id_receiver).unwrap().initial_timestamp; - - let mut skip_frame = true; - let mut count_frame_none = 0; - while skip_frame { - let frame_type = - NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000); - if (frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none && _settings.loss_threshold != 0) - || frame_type == NDIlib_frame_type_e::NDIlib_frame_type_error - { - if count_frame_none < _settings.loss_threshold{ - count_frame_none += 1; - continue; - } - gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type none or error received, assuming that the source closed the stream...."]); - return Err(gst::FlowReturn::CustomError); - } - else{ - if frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none && _settings.loss_threshold == 0{ - gst_debug!(self.cat, obj: element, "No audio frame received, sending empty buffer"); - let buffer = gst::Buffer::with_size(0).unwrap(); - return Ok(buffer) - } - } - if time >= (audio_frame.timestamp as u64) { - gst_debug!(self.cat, obj: element, "Frame timestamp ({:?}) is lower than received in the first frame from NDI ({:?}), so skiping...", (audio_frame.timestamp as u64), time); - } else { - skip_frame = false; } } - gst_log!(self.cat, obj: element, "NDI audio frame received: {:?}", (audio_frame)); + let no_samples = audio_frame.no_samples as u64; + let audio_rate = audio_frame.sample_rate; + settings.latency = gst::SECOND.mul_div_floor(no_samples, audio_rate as u64); - pts = audio_frame.timestamp as u64 - time; - - gst_log!(self.cat, obj: element, "Calculated pts for audio frame: {:?}", (pts)); - - // We multiply by 2 because is the size in bytes of an i16 variable - let buff_size = (audio_frame.no_samples * 2 * audio_frame.no_channels) as usize; - let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); + let mut caps = gst::Caps::truncate(caps); { - if ndi_struct.start_pts == gst::ClockTime(Some(0)) { - ndi_struct.start_pts = - element.get_clock().unwrap().get_time() - element.get_base_time(); + let caps = caps.make_mut(); + let s = caps.get_mut_structure(0).unwrap(); + s.fixate_field_nearest_int("rate", audio_rate); + s.fixate_field_nearest_int("channels", audio_frame.no_channels); + s.fixate_field_str("layout", "interleaved"); + s.set_value("channel-mask", gst::Bitmask::new(gst_audio::AudioChannelPosition::get_fallback_mask(audio_frame.no_channels as u32)).to_send_value()); + } + + let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); + self.parent_fixate(element, caps) + } + + fn create( + &self, + element: &gst_base::BaseSrc, + _offset: u64, + _length: u32, + ) -> Result { + let _settings = &*self.settings.lock().unwrap(); + + let mut timestamp_data = self.timestamp_data.lock().unwrap(); + + let state = self.state.lock().unwrap(); + let _info = match state.info { + None => { + gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); + return Err(gst::FlowError::NotNegotiated); + } + Some(ref info) => info.clone(), + }; + let receivers = hashmap_receivers.lock().unwrap(); + + let recv = &receivers.get(&_settings.id_receiver).unwrap().ndi_instance; + let pNDI_recv = recv.recv; + + let pts: u64; + let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); + + unsafe { + let time = receivers.get(&_settings.id_receiver).unwrap().initial_timestamp; + + let mut skip_frame = true; + let mut count_frame_none = 0; + while skip_frame { + let frame_type = + NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000); + if (frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none && _settings.loss_threshold != 0) + || frame_type == NDIlib_frame_type_e::NDIlib_frame_type_error + { + if count_frame_none < _settings.loss_threshold{ + count_frame_none += 1; + continue; + } + gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type none or error received, assuming that the source closed the stream...."]); + return Err(gst::FlowError::CustomError); + } + else if frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none && _settings.loss_threshold == 0{ + gst_debug!(self.cat, obj: element, "No audio frame received, sending empty buffer"); + let buffer = gst::Buffer::with_size(0).unwrap(); + return Ok(buffer) + } + + if time >= (audio_frame.timestamp as u64) { + gst_debug!(self.cat, obj: element, "Frame timestamp ({:?}) is lower than received in the first frame from NDI ({:?}), so skiping...", (audio_frame.timestamp as u64), time); + } else { + skip_frame = false; + } } - let buffer = buffer.get_mut().unwrap(); + gst_log!(self.cat, obj: element, "NDI audio frame received: {:?}", (audio_frame)); - // Newtek NDI yields times in 100ns intervals since the Unix Time - let pts: gst::ClockTime = (pts * 100).into(); - buffer.set_pts(pts + ndi_struct.start_pts); + pts = audio_frame.timestamp as u64 - time; - let duration: gst::ClockTime = (((f64::from(audio_frame.no_samples) + gst_log!(self.cat, obj: element, "Calculated pts for audio frame: {:?}", (pts)); + + // We multiply by 2 because is the size in bytes of an i16 variable + let buff_size = (audio_frame.no_samples * 2 * audio_frame.no_channels) as usize; + let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); + { + if ndi_struct.start_pts == gst::ClockTime(Some(0)) { + ndi_struct.start_pts = + element.get_clock().unwrap().get_time() - element.get_base_time(); + } + + let buffer = buffer.get_mut().unwrap(); + + // Newtek NDI yields times in 100ns intervals since the Unix Time + let pts: gst::ClockTime = (pts * 100).into(); + buffer.set_pts(pts + ndi_struct.start_pts); + + let duration: gst::ClockTime = (((f64::from(audio_frame.no_samples) / f64::from(audio_frame.sample_rate)) * 1_000_000_000.0) as u64) .into(); - buffer.set_duration(duration); + buffer.set_duration(duration); - buffer.set_offset(timestamp_data.offset); - timestamp_data.offset += audio_frame.no_samples as u64; - buffer.set_offset_end(timestamp_data.offset); + buffer.set_offset(timestamp_data.offset); + timestamp_data.offset += audio_frame.no_samples as u64; + buffer.set_offset_end(timestamp_data.offset); - let mut dst: NDIlib_audio_frame_interleaved_16s_t = Default::default(); - dst.reference_level = 0; - dst.p_data = buffer.map_writable().unwrap().as_mut_slice().as_mut_ptr() as *mut i16; - NDIlib_util_audio_to_interleaved_16s_v2(&audio_frame, &mut dst); + let mut dst: NDIlib_audio_frame_interleaved_16s_t = Default::default(); + dst.reference_level = 0; + dst.p_data = buffer.map_writable().unwrap().as_mut_slice().as_mut_ptr() as *mut i16; + NDIlib_util_audio_to_interleaved_16s_v2(&audio_frame, &mut dst); + } + + gst_log!(self.cat, obj: element, "Produced buffer {:?}", buffer); + + Ok(buffer) } - - gst_log!(self.cat, obj: element, "Produced buffer {:?}", buffer); - - Ok(buffer) } } -} -struct NdiAudioSrcStatic; - -impl ImplTypeStatic for NdiAudioSrcStatic { - fn get_name(&self) -> &str { - "NdiAudioSrc" + pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { + gst::Element::register(plugin, "ndiaudiosrc", 0, NdiAudioSrc::get_type()) } - - fn new(&self, element: &BaseSrc) -> Box> { - NdiAudioSrc::new(element) - } - - fn class_init(&self, klass: &mut BaseSrcClass) { - NdiAudioSrc::class_init(klass); - } -} - -pub fn register(plugin: &gst::Plugin) { - let type_ = register_type(NdiAudioSrcStatic); - gst::Element::register(plugin, "ndiaudiosrc", 0, type_); -} diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index 698ca60d..e641bb45 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -1,15 +1,17 @@ #![allow(non_camel_case_types, non_upper_case_globals, non_snake_case)] use glib; +use glib::subclass; +use glib::subclass::prelude::*; use gst; use gst::prelude::*; -use gst::Fraction; +use gst::subclass::prelude::*; +use gst_base; use gst_base::prelude::*; -use gst_video; +use gst_base::subclass::prelude::*; -use gobject_subclass::object::*; -use gst_plugin::base_src::*; -use gst_plugin::element::*; +use gst::Fraction; +use gst_video; use std::sync::Mutex; use std::{i32, u32}; @@ -44,29 +46,36 @@ impl Default for Settings { } } -static PROPERTIES: [Property; 3] = [ - Property::String( +static PROPERTIES: [subclass::Property; 3] = [ +subclass::Property("stream-name", || { + glib::ParamSpec::string( "stream-name", "Stream Name", "Name of the streaming device", None, - PropertyMutability::ReadWrite, - ), - Property::String( + glib::ParamFlags::READWRITE, + ) +}), +subclass::Property("ip", || { + glib::ParamSpec::string( "ip", "Stream IP", "IP of the streaming device. Ex: 127.0.0.1:5961", None, - PropertyMutability::ReadWrite, - ), - Property::UInt( + glib::ParamFlags::READWRITE, + ) +}), +subclass::Property("loss-threshold", || { + glib::ParamSpec::uint( "loss-threshold", "Loss threshold", - "Loss threshold. If 0 the stream is never closed by the element", - (0, 60), + "Loss threshold", + 0, + 60, 5, - PropertyMutability::ReadWrite, - ), + glib::ParamFlags::READWRITE, + ) +}), ]; struct State { @@ -90,12 +99,17 @@ struct NdiVideoSrc { timestamp_data: Mutex, } -impl NdiVideoSrc { - fn new(element: &BaseSrc) -> Box> { - element.set_live(true); - element.set_format(gst::Format::Time); +impl ObjectSubclass for NdiVideoSrc { - Box::new(Self { + const NAME: &'static str = "NdiVideoSrc"; + type ParentType = gst_base::BaseSrc; + type Instance = gst::subclass::ElementInstanceStruct; + type Class = subclass::simple::ClassStruct; + + glib_object_subclass!(); + + fn new() -> Self { + Self { cat: gst::DebugCategory::new( "ndivideosrc", gst::DebugColorFlags::empty(), @@ -104,10 +118,10 @@ impl NdiVideoSrc { settings: Mutex::new(Default::default()), state: Mutex::new(Default::default()), timestamp_data: Mutex::new(TimestampData { offset: 0 }), - }) + } } - fn class_init(klass: &mut BaseSrcClass) { + fn class_init(klass: &mut subclass::simple::ClassStruct) { klass.set_metadata( "NewTek NDI Video Source", "Source", @@ -120,13 +134,13 @@ impl NdiVideoSrc { let caps = gst::Caps::new_simple( "video/x-raw", &[ - ( - "format", - &gst::List::new(&[ - //TODO add all formats - &gst_video::VideoFormat::Uyvy.to_string(), - //&gst_video::VideoFormat::Rgb.to_string(), - //&gst_video::VideoFormat::Gray8.to_string(), + ( + "format", + &gst::List::new(&[ + //TODO add all formats + &gst_video::VideoFormat::Uyvy.to_string(), + //&gst_video::VideoFormat::Rgb.to_string(), + //&gst_video::VideoFormat::Gray8.to_string(), ]), ), ("width", &gst::IntRange::::new(0, i32::MAX)), @@ -138,346 +152,342 @@ impl NdiVideoSrc { gst::Fraction::new(i32::MAX, 1), ), ), - ], - ); + ], + ); - let src_pad_template = gst::PadTemplate::new( - "src", - gst::PadDirection::Src, - gst::PadPresence::Always, - &caps, - ); - klass.add_pad_template(src_pad_template); + let src_pad_template = gst::PadTemplate::new( + "src", + gst::PadDirection::Src, + gst::PadPresence::Always, + &caps, + ); + klass.add_pad_template(src_pad_template); - klass.install_properties(&PROPERTIES); - } -} - -impl ObjectImpl for NdiVideoSrc { - fn set_property(&self, obj: &glib::Object, id: u32, value: &glib::Value) { - let prop = &PROPERTIES[id as usize]; - let element = obj.clone().downcast::().unwrap(); - - match *prop { - Property::String("stream-name", ..) => { - let mut settings = self.settings.lock().unwrap(); - let stream_name = value.get().unwrap(); - gst_debug!( - self.cat, - obj: &element, - "Changing stream-name from {} to {}", - settings.stream_name, - stream_name - ); - settings.stream_name = stream_name; - drop(settings); - } - Property::String("ip", ..) => { - let mut settings = self.settings.lock().unwrap(); - let ip = value.get().unwrap(); - gst_debug!( - self.cat, - obj: &element, - "Changing ip from {} to {}", - settings.ip, - ip - ); - settings.ip = ip; - drop(settings); - } - Property::UInt("loss-threshold", ..) => { - let mut settings = self.settings.lock().unwrap(); - let loss_threshold = value.get().unwrap(); - gst_debug!( - self.cat, - obj: &element, - "Changing loss threshold from {} to {}", - settings.loss_threshold, - loss_threshold - ); - settings.loss_threshold = loss_threshold; - drop(settings); - } - _ => unimplemented!(), + klass.install_properties(&PROPERTIES); } } - fn get_property(&self, _obj: &glib::Object, id: u32) -> Result { - let prop = &PROPERTIES[id as usize]; - match *prop { - Property::String("stream-name", ..) => { - let settings = self.settings.lock().unwrap(); - Ok(settings.stream_name.to_value()) + impl ObjectImpl for NdiVideoSrc { + glib_object_impl!(); + + fn constructed(&self, obj: &glib::Object) { + self.parent_constructed(obj); + + let basesrc = obj.downcast_ref::().unwrap(); + // Initialize live-ness and notify the base class that + // we'd like to operate in Time format + basesrc.set_live(true); + basesrc.set_format(gst::Format::Time); + } + + + fn set_property(&self, obj: &glib::Object, id: usize, value: &glib::Value) { + let prop = &PROPERTIES[id]; + let basesrc = obj.downcast_ref::().unwrap(); + + match *prop { + subclass::Property("stream-name", ..) => { + let mut settings = self.settings.lock().unwrap(); + let stream_name = value.get().unwrap(); + gst_debug!( + self.cat, + obj: basesrc, + "Changing stream-name from {} to {}", + settings.stream_name, + stream_name + ); + settings.stream_name = stream_name; + drop(settings); + } + subclass::Property("ip", ..) => { + let mut settings = self.settings.lock().unwrap(); + let ip = value.get().unwrap(); + gst_debug!( + self.cat, + obj: basesrc, + "Changing ip from {} to {}", + settings.ip, + ip + ); + settings.ip = ip; + drop(settings); + } + subclass::Property("loss-threshold", ..) => { + let mut settings = self.settings.lock().unwrap(); + let loss_threshold = value.get().unwrap(); + gst_debug!( + self.cat, + obj: basesrc, + "Changing loss threshold from {} to {}", + settings.loss_threshold, + loss_threshold + ); + settings.loss_threshold = loss_threshold; + drop(settings); + } + _ => unimplemented!(), } - Property::String("ip", ..) => { - let settings = self.settings.lock().unwrap(); - Ok(settings.ip.to_value()) + } + + fn get_property(&self, _obj: &glib::Object, id: usize) -> Result { + let prop = &PROPERTIES[id]; + + match *prop { + subclass::Property("stream-name", ..) => { + let settings = self.settings.lock().unwrap(); + Ok(settings.stream_name.to_value()) + } + subclass::Property("ip", ..) => { + let settings = self.settings.lock().unwrap(); + Ok(settings.ip.to_value()) + } + subclass::Property("loss-threshold", ..) => { + let settings = self.settings.lock().unwrap(); + Ok(settings.loss_threshold.to_value()) + } + _ => unimplemented!(), } - Property::UInt("loss-threshold", ..) => { - let settings = self.settings.lock().unwrap(); - Ok(settings.loss_threshold.to_value()) - } - _ => unimplemented!(), } } -} -impl ElementImpl for NdiVideoSrc { - fn change_state( - &self, - element: &BaseSrc, - transition: gst::StateChange, - ) -> gst::StateChangeReturn { - if transition == gst::StateChange::PausedToPlaying { - let mut receivers = hashmap_receivers.lock().unwrap(); + impl ElementImpl for NdiVideoSrc { + fn change_state( + &self, + element: &gst::Element, + transition: gst::StateChange, + ) -> gst::StateChangeReturn { + if transition == gst::StateChange::PausedToPlaying { + let mut receivers = hashmap_receivers.lock().unwrap(); + let settings = self.settings.lock().unwrap(); + + let receiver = receivers.get_mut(&settings.id_receiver).unwrap(); + let recv = &receiver.ndi_instance; + let pNDI_recv = recv.recv; + + let video_frame: NDIlib_video_frame_v2_t = Default::default(); + + let mut frame_type: NDIlib_frame_type_e = NDIlib_frame_type_e::NDIlib_frame_type_none; + unsafe { + while frame_type != NDIlib_frame_type_e::NDIlib_frame_type_video { + frame_type = NDIlib_recv_capture_v2( + pNDI_recv, + &video_frame, + ptr::null(), + ptr::null(), + 1000, + ); + gst_debug!(self.cat, obj: element, "NDI video frame received: {:?}", video_frame); + } + + if receiver.initial_timestamp <= video_frame.timestamp as u64 + || receiver.initial_timestamp == 0 + { + receiver.initial_timestamp = video_frame.timestamp as u64; + } + gst_debug!(self.cat, obj: element, "Setting initial timestamp to {}", receiver.initial_timestamp); + } + } + self.parent_change_state(element, transition) + } + } + + impl BaseSrcImpl for NdiVideoSrc { + fn set_caps(&self, element: &gst_base::BaseSrc, caps: &gst::CapsRef) -> bool { + let info = match gst_video::VideoInfo::from_caps(caps) { + None => return false, + Some(info) => info, + }; + gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); + + let mut state = self.state.lock().unwrap(); + state.info = Some(info); + let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); + true + } + + fn start(&self, element: &gst_base::BaseSrc) -> bool { + *self.state.lock().unwrap() = Default::default(); + let mut settings = self.settings.lock().unwrap(); + settings.id_receiver = connect_ndi( + self.cat, + element, + &settings.ip.clone(), + &settings.stream_name.clone(), + ); + + settings.id_receiver != 0 + } + + fn stop(&self, element: &gst_base::BaseSrc) -> bool { + *self.state.lock().unwrap() = Default::default(); + let settings = self.settings.lock().unwrap(); + stop_ndi(self.cat, element, settings.id_receiver); + // Commented because when adding ndi destroy stopped in this line + //*self.state.lock().unwrap() = Default::default(); + true + } - let receiver = receivers.get_mut(&settings.id_receiver).unwrap(); + fn query(&self, element: &gst_base::BaseSrc, query: &mut gst::QueryRef) -> bool { + use gst::QueryView; + if let QueryView::Scheduling(ref mut q) = query.view_mut() { + q.set(gst::SchedulingFlags::SEQUENTIAL, 1, -1, 0); + q.add_scheduling_modes(&[gst::PadMode::Push]); + return true; + } + if let QueryView::Latency(ref mut q) = query.view_mut() { + let settings = &*self.settings.lock().unwrap(); + let state = self.state.lock().unwrap(); + + if let Some(ref _info) = state.info { + let latency = settings.latency.unwrap(); + gst_debug!(self.cat, obj: element, "Returning latency {}", latency); + q.set(true, latency, gst::CLOCK_TIME_NONE); + return true; + } else { + return false; + } + } + BaseSrcImpl::parent_query(self, element, query) + } + + fn fixate(&self, element: &gst_base::BaseSrc, caps: gst::Caps) -> gst::Caps { + let receivers = hashmap_receivers.lock().unwrap(); + let mut settings = self.settings.lock().unwrap(); + + let receiver = receivers.get(&settings.id_receiver).unwrap(); let recv = &receiver.ndi_instance; let pNDI_recv = recv.recv; let video_frame: NDIlib_video_frame_v2_t = Default::default(); let mut frame_type: NDIlib_frame_type_e = NDIlib_frame_type_e::NDIlib_frame_type_none; - unsafe { - while frame_type != NDIlib_frame_type_e::NDIlib_frame_type_video { - frame_type = NDIlib_recv_capture_v2( - pNDI_recv, - &video_frame, - ptr::null(), - ptr::null(), - 1000, - ); - gst_debug!(self.cat, obj: element, "NDI video frame received: {:?}", video_frame); - } - - if receiver.initial_timestamp <= video_frame.timestamp as u64 - || receiver.initial_timestamp == 0 - { - receiver.initial_timestamp = video_frame.timestamp as u64; - } - gst_debug!(self.cat, obj: element, "Setting initial timestamp to {}", receiver.initial_timestamp); - } - } - element.parent_change_state(transition) - } -} - -impl BaseSrcImpl for NdiVideoSrc { - fn set_caps(&self, element: &BaseSrc, caps: &gst::CapsRef) -> bool { - let info = match gst_video::VideoInfo::from_caps(caps) { - None => return false, - Some(info) => info, - }; - gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); - - let mut state = self.state.lock().unwrap(); - state.info = Some(info); - let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); - true - } - - fn start(&self, element: &BaseSrc) -> bool { - *self.state.lock().unwrap() = Default::default(); - let mut settings = self.settings.lock().unwrap(); - settings.id_receiver = connect_ndi( - self.cat, - element, - &settings.ip.clone(), - &settings.stream_name.clone(), - ); - - settings.id_receiver != 0 - } - - fn stop(&self, element: &BaseSrc) -> bool { - *self.state.lock().unwrap() = Default::default(); - - let settings = self.settings.lock().unwrap(); - stop_ndi(self.cat, element, settings.id_receiver); - // Commented because when adding ndi destroy stopped in this line - //*self.state.lock().unwrap() = Default::default(); - true - } - - fn query(&self, element: &BaseSrc, query: &mut gst::QueryRef) -> bool { - use gst::QueryView; - if let QueryView::Scheduling(ref mut q) = query.view_mut() { - q.set(gst::SchedulingFlags::SEQUENTIAL, 1, -1, 0); - q.add_scheduling_modes(&[gst::PadMode::Push]); - return true; - } - if let QueryView::Latency(ref mut q) = query.view_mut() { - let settings = &*self.settings.lock().unwrap(); - let state = self.state.lock().unwrap(); - - if let Some(ref _info) = state.info { - let latency = settings.latency.unwrap(); - gst_debug!(self.cat, obj: element, "Returning latency {}", latency); - q.set(true, latency, gst::CLOCK_TIME_NONE); - return true; - } else { - return false; - } - } - BaseSrcBase::parent_query(element, query) - } - - fn fixate(&self, element: &BaseSrc, caps: gst::Caps) -> gst::Caps { - let receivers = hashmap_receivers.lock().unwrap(); - let mut settings = self.settings.lock().unwrap(); - - let receiver = receivers.get(&settings.id_receiver).unwrap(); - let recv = &receiver.ndi_instance; - let pNDI_recv = recv.recv; - - let video_frame: NDIlib_video_frame_v2_t = Default::default(); - - let mut frame_type: NDIlib_frame_type_e = NDIlib_frame_type_e::NDIlib_frame_type_none; - while frame_type != NDIlib_frame_type_e::NDIlib_frame_type_video { - unsafe { - frame_type = + while frame_type != NDIlib_frame_type_e::NDIlib_frame_type_video { + unsafe { + frame_type = NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000); gst_debug!(self.cat, obj: element, "NDI video frame received: {:?}", video_frame); + } } - } - settings.latency = gst::SECOND.mul_div_floor( - video_frame.frame_rate_D as u64, - video_frame.frame_rate_N as u64, - ); - - let mut caps = gst::Caps::truncate(caps); - { - let caps = caps.make_mut(); - let s = caps.get_mut_structure(0).unwrap(); - s.fixate_field_nearest_int("width", video_frame.xres); - s.fixate_field_nearest_int("height", video_frame.yres); - s.fixate_field_nearest_fraction( - "framerate", - Fraction::new(video_frame.frame_rate_N, video_frame.frame_rate_D), + settings.latency = gst::SECOND.mul_div_floor( + video_frame.frame_rate_D as u64, + video_frame.frame_rate_N as u64, ); + + let mut caps = gst::Caps::truncate(caps); + { + let caps = caps.make_mut(); + let s = caps.get_mut_structure(0).unwrap(); + s.fixate_field_nearest_int("width", video_frame.xres); + s.fixate_field_nearest_int("height", video_frame.yres); + s.fixate_field_nearest_fraction( + "framerate", + Fraction::new(video_frame.frame_rate_N, video_frame.frame_rate_D), + ); + } + + let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); + self.parent_fixate(element, caps) } - let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); - element.parent_fixate(caps) - } + //Creates the video buffers + fn create( + &self, + element: &gst_base::BaseSrc, + _offset: u64, + _length: u32, + ) -> Result { + let _settings = &*self.settings.lock().unwrap(); - //Creates the video buffers - fn create( - &self, - element: &BaseSrc, - _offset: u64, - _length: u32, - ) -> Result { - let _settings = &*self.settings.lock().unwrap(); + let mut timestamp_data = self.timestamp_data.lock().unwrap(); + let state = self.state.lock().unwrap(); + let _info = match state.info { + None => { + gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); + return Err(gst::FlowError::NotNegotiated); + } + Some(ref info) => info.clone(), + }; + let receivers = hashmap_receivers.lock().unwrap(); - let mut timestamp_data = self.timestamp_data.lock().unwrap(); - let state = self.state.lock().unwrap(); - let _info = match state.info { - None => { - gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); - return Err(gst::FlowReturn::NotNegotiated); - } - Some(ref info) => info.clone(), - }; - let receivers = hashmap_receivers.lock().unwrap(); + let recv = &receivers.get(&_settings.id_receiver).unwrap().ndi_instance; + let pNDI_recv = recv.recv; - let recv = &receivers.get(&_settings.id_receiver).unwrap().ndi_instance; - let pNDI_recv = recv.recv; + let pts: u64; + let video_frame: NDIlib_video_frame_v2_t = Default::default(); - let pts: u64; - let video_frame: NDIlib_video_frame_v2_t = Default::default(); + unsafe { + let time = receivers.get(&_settings.id_receiver).unwrap().initial_timestamp; - unsafe { - let time = receivers.get(&_settings.id_receiver).unwrap().initial_timestamp; - - let mut skip_frame = true; - let mut count_frame_none = 0; - while skip_frame { - let frame_type = + let mut skip_frame = true; + let mut count_frame_none = 0; + while skip_frame { + let frame_type = NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000); - if (frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none && _settings.loss_threshold != 0) + if (frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none && _settings.loss_threshold != 0) || frame_type == NDIlib_frame_type_e::NDIlib_frame_type_error + { + if count_frame_none < _settings.loss_threshold{ + count_frame_none += 1; + continue; + } + gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type none or error received, assuming that the source closed the stream...."]); + return Err(gst::FlowError::CustomError); + } + else if frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none && _settings.loss_threshold == 0{ + gst_debug!(self.cat, obj: element, "No video frame received, sending empty buffer"); + let buffer = gst::Buffer::with_size(0).unwrap(); + return Ok(buffer) + } + + if time >= (video_frame.timestamp as u64) { + gst_debug!(self.cat, obj: element, "Frame timestamp ({:?}) is lower than received in the first frame from NDI ({:?}), so skiping...", (video_frame.timestamp as u64), time); + } else { + skip_frame = false; + } + } + + gst_log!(self.cat, obj: element, "NDI video frame received: {:?}", (video_frame)); + + pts = video_frame.timestamp as u64 - time; + + gst_log!(self.cat, obj: element, "Calculated pts for video frame: {:?}", (pts)); + + let buff_size = (video_frame.yres * video_frame.line_stride_in_bytes) as usize; + let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); { - if count_frame_none < _settings.loss_threshold{ - count_frame_none += 1; - continue; - } - gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type none or error received, assuming that the source closed the stream...."]); - return Err(gst::FlowReturn::CustomError); - } - else{ - if frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none && _settings.loss_threshold == 0{ - gst_debug!(self.cat, obj: element, "No video frame received, sending empty buffer"); - let buffer = gst::Buffer::with_size(0).unwrap(); - return Ok(buffer) - } - } - if time >= (video_frame.timestamp as u64) { - gst_debug!(self.cat, obj: element, "Frame timestamp ({:?}) is lower than received in the first frame from NDI ({:?}), so skiping...", (video_frame.timestamp as u64), time); - } else { - skip_frame = false; - } - } + let vec = Vec::from_raw_parts(video_frame.p_data as *mut u8, buff_size, buff_size); + // Newtek NDI yields times in 100ns intervals since the Unix Time + let pts: gst::ClockTime = (pts * 100).into(); - gst_log!(self.cat, obj: element, "NDI video frame received: {:?}", (video_frame)); - - pts = video_frame.timestamp as u64 - time; - - gst_log!(self.cat, obj: element, "Calculated pts for video frame: {:?}", (pts)); - - let buff_size = (video_frame.yres * video_frame.line_stride_in_bytes) as usize; - let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); - { - let vec = Vec::from_raw_parts(video_frame.p_data as *mut u8, buff_size, buff_size); - // Newtek NDI yields times in 100ns intervals since the Unix Time - let pts: gst::ClockTime = (pts * 100).into(); - - let duration: gst::ClockTime = (((f64::from(video_frame.frame_rate_D) + let duration: gst::ClockTime = (((f64::from(video_frame.frame_rate_D) / f64::from(video_frame.frame_rate_N)) * 1_000_000_000.0) as u64) .into(); - let buffer = buffer.get_mut().unwrap(); + let buffer = buffer.get_mut().unwrap(); - if ndi_struct.start_pts == gst::ClockTime(Some(0)) { - ndi_struct.start_pts = + if ndi_struct.start_pts == gst::ClockTime(Some(0)) { + ndi_struct.start_pts = element.get_clock().unwrap().get_time() - element.get_base_time(); + } + + buffer.set_pts(pts + ndi_struct.start_pts); + buffer.set_duration(duration); + buffer.set_offset(timestamp_data.offset); + timestamp_data.offset += 1; + buffer.set_offset_end(timestamp_data.offset); + buffer.copy_from_slice(0, &vec).unwrap(); } - buffer.set_pts(pts + ndi_struct.start_pts); - buffer.set_duration(duration); - buffer.set_offset(timestamp_data.offset); - timestamp_data.offset += 1; - buffer.set_offset_end(timestamp_data.offset); - buffer.copy_from_slice(0, &vec).unwrap(); + gst_log!(self.cat, obj: element, "Produced buffer {:?}", buffer); + + Ok(buffer) } - - gst_log!(self.cat, obj: element, "Produced buffer {:?}", buffer); - - Ok(buffer) } } -} -struct NdiVideoSrcStatic; - -impl ImplTypeStatic for NdiVideoSrcStatic { - fn get_name(&self) -> &str { - "NdiVideoSrc" + pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { + gst::Element::register(plugin, "ndivideosrc", 0, NdiVideoSrc::get_type()) } - - fn new(&self, element: &BaseSrc) -> Box> { - NdiVideoSrc::new(element) - } - - fn class_init(&self, klass: &mut BaseSrcClass) { - NdiVideoSrc::class_init(klass); - } -} - -pub fn register(plugin: &gst::Plugin) { - let type_ = register_type(NdiVideoSrcStatic); - gst::Element::register(plugin, "ndivideosrc", 0, type_); -} From b85fcdf649d255f9d96d59471e5b1eb696ca2b0c Mon Sep 17 00:00:00 2001 From: Daniel Vilar Date: Wed, 12 Dec 2018 12:00:33 +0100 Subject: [PATCH 097/199] Cast correctly audio data in buffer from *mut u8 to *mut i16 --- Cargo.toml | 1 + src/lib.rs | 1 + src/ndiaudiosrc.rs | 5 +++-- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f7342312..e8a4c85b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ gstreamer-base = { git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" gstreamer-video = { git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" } gstreamer-audio = { git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" } lazy_static = "1.1.0" +byte-slice-cast = "0.2.0" [lib] name = "gstndi" diff --git a/src/lib.rs b/src/lib.rs index d3b2dd27..739cda41 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,7 @@ extern crate gstreamer_video as gst_video; #[macro_use] extern crate lazy_static; +extern crate byte_slice_cast; mod ndiaudiosrc; pub mod ndisys; diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index ea172d9e..92d0d906 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -1,4 +1,4 @@ -#![allow(non_camel_case_types, non_upper_case_globals, non_snake_case, cast_ptr_alignment)] +#![allow(non_camel_case_types, non_upper_case_globals, non_snake_case)] use glib; use glib::subclass; @@ -22,6 +22,7 @@ use ndisys::*; use stop_ndi; use hashmap_receivers; +use byte_slice_cast::AsMutSliceOf; #[derive(Debug, Clone)] struct Settings { @@ -471,7 +472,7 @@ impl ObjectSubclass for NdiAudioSrc { let mut dst: NDIlib_audio_frame_interleaved_16s_t = Default::default(); dst.reference_level = 0; - dst.p_data = buffer.map_writable().unwrap().as_mut_slice().as_mut_ptr() as *mut i16; + dst.p_data = buffer.map_writable().unwrap().as_mut_slice_of::().unwrap().as_mut_ptr(); NDIlib_util_audio_to_interleaved_16s_v2(&audio_frame, &mut dst); } From c1f0b8e941f9a6957e9300a8ea549ecb02916fee Mon Sep 17 00:00:00 2001 From: o-reo Date: Thu, 28 Feb 2019 10:55:01 +0100 Subject: [PATCH 098/199] WIP: changed return type for status_change, start, stop, change_caps,... --- src/ndiaudiosrc.rs | 16 ++++++++-------- src/ndivideosrc.rs | 14 +++++++------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index 92d0d906..43f4cbb9 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -46,7 +46,7 @@ impl Default for Settings { } static PROPERTIES: [subclass::Property; 3] = [ -subclass::Property("stream-name", || { +subclass::Property("stream-name", |_| { glib::ParamSpec::string( "stream-name", "Sream Name", @@ -55,7 +55,7 @@ subclass::Property("stream-name", || { glib::ParamFlags::READWRITE, ) }), -subclass::Property("ip", || { +subclass::Property("ip", |_| { glib::ParamSpec::string( "ip", "Stream IP", @@ -64,7 +64,7 @@ subclass::Property("ip", || { glib::ParamFlags::READWRITE, ) }), -subclass::Property("loss-threshold", || { +subclass::Property("loss-threshold", |_| { glib::ParamSpec::uint( "loss-threshold", "Loss threshold", @@ -246,7 +246,7 @@ impl ObjectSubclass for NdiAudioSrc { &self, element: &gst::Element, transition: gst::StateChange, - ) -> gst::StateChangeReturn { + ) -> Result { if transition == gst::StateChange::PausedToPlaying { let mut receivers = hashmap_receivers.lock().unwrap(); let settings = self.settings.lock().unwrap(); @@ -283,7 +283,7 @@ impl ObjectSubclass for NdiAudioSrc { } impl BaseSrcImpl for NdiAudioSrc { - fn set_caps(&self, element: &gst_base::BaseSrc, caps: &gst::CapsRef) -> bool { + fn set_caps(&self, element: &gst_base::BaseSrc, caps: &gst::CapsRef) -> Result<(), gst::LoggableError> { let info = match gst_audio::AudioInfo::from_caps(caps) { None => return false, Some(info) => info, @@ -297,7 +297,7 @@ impl ObjectSubclass for NdiAudioSrc { true } - fn start(&self, element: &gst_base::BaseSrc) -> bool { + fn start(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { *self.state.lock().unwrap() = Default::default(); let mut settings = self.settings.lock().unwrap(); @@ -311,7 +311,7 @@ impl ObjectSubclass for NdiAudioSrc { settings.id_receiver != 0 } - fn stop(&self, element: &gst_base::BaseSrc) -> bool { + fn stop(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { *self.state.lock().unwrap() = Default::default(); let settings = self.settings.lock().unwrap(); @@ -431,7 +431,7 @@ impl ObjectSubclass for NdiAudioSrc { let buffer = gst::Buffer::with_size(0).unwrap(); return Ok(buffer) } - + if time >= (audio_frame.timestamp as u64) { gst_debug!(self.cat, obj: element, "Frame timestamp ({:?}) is lower than received in the first frame from NDI ({:?}), so skiping...", (audio_frame.timestamp as u64), time); } else { diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index e641bb45..f5281104 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -47,7 +47,7 @@ impl Default for Settings { } static PROPERTIES: [subclass::Property; 3] = [ -subclass::Property("stream-name", || { +subclass::Property("stream-name", |_| { glib::ParamSpec::string( "stream-name", "Stream Name", @@ -56,7 +56,7 @@ subclass::Property("stream-name", || { glib::ParamFlags::READWRITE, ) }), -subclass::Property("ip", || { +subclass::Property("ip", |_| { glib::ParamSpec::string( "ip", "Stream IP", @@ -65,7 +65,7 @@ subclass::Property("ip", || { glib::ParamFlags::READWRITE, ) }), -subclass::Property("loss-threshold", || { +subclass::Property("loss-threshold", |_| { glib::ParamSpec::uint( "loss-threshold", "Loss threshold", @@ -256,7 +256,7 @@ impl ObjectSubclass for NdiVideoSrc { &self, element: &gst::Element, transition: gst::StateChange, - ) -> gst::StateChangeReturn { + ) -> Result { if transition == gst::StateChange::PausedToPlaying { let mut receivers = hashmap_receivers.lock().unwrap(); let settings = self.settings.lock().unwrap(); @@ -293,7 +293,7 @@ impl ObjectSubclass for NdiVideoSrc { } impl BaseSrcImpl for NdiVideoSrc { - fn set_caps(&self, element: &gst_base::BaseSrc, caps: &gst::CapsRef) -> bool { + fn set_caps(&self, element: &gst_base::BaseSrc, caps: &gst::CapsRef) -> Result<(), gst::LoggableError> { let info = match gst_video::VideoInfo::from_caps(caps) { None => return false, Some(info) => info, @@ -306,7 +306,7 @@ impl ObjectSubclass for NdiVideoSrc { true } - fn start(&self, element: &gst_base::BaseSrc) -> bool { + fn start(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { *self.state.lock().unwrap() = Default::default(); let mut settings = self.settings.lock().unwrap(); settings.id_receiver = connect_ndi( @@ -319,7 +319,7 @@ impl ObjectSubclass for NdiVideoSrc { settings.id_receiver != 0 } - fn stop(&self, element: &gst_base::BaseSrc) -> bool { + fn stop(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { *self.state.lock().unwrap() = Default::default(); let settings = self.settings.lock().unwrap(); From f74b0b40883caad54d175ca8bf9b02a0f41ab015 Mon Sep 17 00:00:00 2001 From: o-reo Date: Thu, 28 Feb 2019 11:43:31 +0100 Subject: [PATCH 099/199] FIX: changed return bool to errorMessages --- src/ndiaudiosrc.rs | 13 +++++++------ src/ndivideosrc.rs | 15 ++++++++------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index 43f4cbb9..268c4b19 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -152,7 +152,8 @@ impl ObjectSubclass for NdiAudioSrc { gst::PadDirection::Src, gst::PadPresence::Always, &caps, - ); + ) + .unwrap(); klass.add_pad_template(src_pad_template); klass.install_properties(&PROPERTIES); @@ -285,7 +286,7 @@ impl ObjectSubclass for NdiAudioSrc { impl BaseSrcImpl for NdiAudioSrc { fn set_caps(&self, element: &gst_base::BaseSrc, caps: &gst::CapsRef) -> Result<(), gst::LoggableError> { let info = match gst_audio::AudioInfo::from_caps(caps) { - None => return false, + None => return Err(gst_loggable_error!(self.cat, "Failed to build `AudioInfo` from caps {}", caps)), Some(info) => info, }; @@ -294,7 +295,7 @@ impl ObjectSubclass for NdiAudioSrc { let mut state = self.state.lock().unwrap(); state.info = Some(info); - true + Ok(()) } fn start(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { @@ -308,7 +309,7 @@ impl ObjectSubclass for NdiAudioSrc { &settings.stream_name.clone(), ); - settings.id_receiver != 0 + Ok(()) } fn stop(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { @@ -318,7 +319,7 @@ impl ObjectSubclass for NdiAudioSrc { stop_ndi(self.cat, element, settings.id_receiver); // Commented because when adding ndi destroy stopped in this line //*self.state.lock().unwrap() = Default::default(); - true + Ok(()) } fn query(&self, element: &gst_base::BaseSrc, query: &mut gst::QueryRef) -> bool { @@ -341,7 +342,7 @@ impl ObjectSubclass for NdiAudioSrc { return false; } } - BaseSrcImpl::parent_query(self, element, query) + BaseSrcImplExt::parent_query(self, element, query) } fn fixate(&self, element: &gst_base::BaseSrc, caps: gst::Caps) -> gst::Caps { diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index f5281104..19b7f6a9 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -160,7 +160,7 @@ impl ObjectSubclass for NdiVideoSrc { gst::PadDirection::Src, gst::PadPresence::Always, &caps, - ); + ).unwrap(); klass.add_pad_template(src_pad_template); klass.install_properties(&PROPERTIES); @@ -295,7 +295,7 @@ impl ObjectSubclass for NdiVideoSrc { impl BaseSrcImpl for NdiVideoSrc { fn set_caps(&self, element: &gst_base::BaseSrc, caps: &gst::CapsRef) -> Result<(), gst::LoggableError> { let info = match gst_video::VideoInfo::from_caps(caps) { - None => return false, + None => return Err(gst_loggable_error!(self.cat, "Failed to build `VideoInfo` from caps {}", caps)), Some(info) => info, }; gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); @@ -303,7 +303,7 @@ impl ObjectSubclass for NdiVideoSrc { let mut state = self.state.lock().unwrap(); state.info = Some(info); let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); - true + Ok(()) } fn start(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { @@ -316,17 +316,18 @@ impl ObjectSubclass for NdiVideoSrc { &settings.stream_name.clone(), ); - settings.id_receiver != 0 + // settings.id_receiver != 0 + Ok(()) } fn stop(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { - *self.state.lock().unwrap() = Default::default(); + *self.state.lock().unwrap() = Default::default(); let settings = self.settings.lock().unwrap(); stop_ndi(self.cat, element, settings.id_receiver); // Commented because when adding ndi destroy stopped in this line //*self.state.lock().unwrap() = Default::default(); - true + Ok(()) } fn query(&self, element: &gst_base::BaseSrc, query: &mut gst::QueryRef) -> bool { @@ -349,7 +350,7 @@ impl ObjectSubclass for NdiVideoSrc { return false; } } - BaseSrcImpl::parent_query(self, element, query) + BaseSrcImplExt::parent_query(self, element, query) } fn fixate(&self, element: &gst_base::BaseSrc, caps: gst::Caps) -> gst::Caps { From 1bacf8237b19032b3e8feaf731d7e0a3da0c24fb Mon Sep 17 00:00:00 2001 From: o-reo Date: Thu, 28 Feb 2019 12:13:40 +0100 Subject: [PATCH 100/199] ENH: Error handling on fn start --- src/ndiaudiosrc.rs | 8 +++++++- src/ndivideosrc.rs | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index 268c4b19..6e603ffa 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -309,7 +309,13 @@ impl ObjectSubclass for NdiAudioSrc { &settings.stream_name.clone(), ); - Ok(()) + match settings.id_receiver { + 0 => Err(gst_error_msg!( + gst::ResourceError::Settings, + ["Could not connect to this source"] + )), + _ => Ok(()) + } } fn stop(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index 19b7f6a9..a99eb67b 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -317,7 +317,13 @@ impl ObjectSubclass for NdiVideoSrc { ); // settings.id_receiver != 0 - Ok(()) + match settings.id_receiver { + 0 => Err(gst_error_msg!( + gst::ResourceError::Settings, + ["Could not connect to this source"] + )), + _ => Ok(()) + } } fn stop(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { From a407346053fad2463e0a3a375c144e6c4189c9cc Mon Sep 17 00:00:00 2001 From: o-reo Date: Thu, 28 Feb 2019 13:27:38 +0100 Subject: [PATCH 101/199] ENH: Changed error message on start to notfound --- src/ndiaudiosrc.rs | 2 +- src/ndivideosrc.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index 6e603ffa..b35f4280 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -311,7 +311,7 @@ impl ObjectSubclass for NdiAudioSrc { match settings.id_receiver { 0 => Err(gst_error_msg!( - gst::ResourceError::Settings, + gst::ResourceError::NotFound, ["Could not connect to this source"] )), _ => Ok(()) diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index a99eb67b..b712d6b4 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -319,7 +319,7 @@ impl ObjectSubclass for NdiVideoSrc { // settings.id_receiver != 0 match settings.id_receiver { 0 => Err(gst_error_msg!( - gst::ResourceError::Settings, + gst::ResourceError::NotFound, ["Could not connect to this source"] )), _ => Ok(()) From 761d7ae7efae54c7d0aeeecd061afc49ed9c40ab Mon Sep 17 00:00:00 2001 From: o-reo Date: Fri, 1 Mar 2019 11:24:54 +0100 Subject: [PATCH 102/199] FIX: Fixed NDI frames memory leaks --- src/ndiaudiosrc.rs | 31 +++++++++++++++---------------- src/ndisys.rs | 8 ++++++++ src/ndivideosrc.rs | 32 ++++++++++++++------------------ 3 files changed, 37 insertions(+), 34 deletions(-) diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index b35f4280..584abbf4 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -258,24 +258,18 @@ impl ObjectSubclass for NdiAudioSrc { let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); - let mut frame_type: NDIlib_frame_type_e = NDIlib_frame_type_e::NDIlib_frame_type_none; unsafe { - while frame_type != NDIlib_frame_type_e::NDIlib_frame_type_audio { - frame_type = NDIlib_recv_capture_v2( - pNDI_recv, - ptr::null(), - &audio_frame, - ptr::null(), - 1000, - ); - gst_debug!(self.cat, obj: element, "NDI audio frame received: {:?}", audio_frame); + while NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000) != NDIlib_frame_type_e::NDIlib_frame_type_audio { + NDIlib_recv_free_audio_v2(pNDI_recv, &audio_frame); } + gst_debug!(self.cat, obj: element, "NDI audio frame received: {:?}", audio_frame); if receiver.initial_timestamp <= audio_frame.timestamp as u64 || receiver.initial_timestamp == 0 { receiver.initial_timestamp = audio_frame.timestamp as u64; } + NDIlib_recv_free_audio_v2(pNDI_recv, &audio_frame); gst_debug!(self.cat, obj: element, "Setting initial timestamp to {}", receiver.initial_timestamp); } } @@ -362,12 +356,9 @@ impl ObjectSubclass for NdiAudioSrc { let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); - let mut frame_type: NDIlib_frame_type_e = NDIlib_frame_type_e::NDIlib_frame_type_none; - while frame_type != NDIlib_frame_type_e::NDIlib_frame_type_audio { - unsafe { - frame_type = - NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000); - gst_debug!(self.cat, obj: element, "NDI audio frame received: {:?}", audio_frame); + unsafe { + while NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000) != NDIlib_frame_type_e::NDIlib_frame_type_audio { + NDIlib_recv_free_audio_v2(pNDI_recv, &audio_frame); } } @@ -386,6 +377,10 @@ impl ObjectSubclass for NdiAudioSrc { } let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); + unsafe { + NDIlib_recv_free_audio_v2(pNDI_recv, &audio_frame); + } + self.parent_fixate(element, caps) } @@ -426,6 +421,7 @@ impl ObjectSubclass for NdiAudioSrc { if (frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none && _settings.loss_threshold != 0) || frame_type == NDIlib_frame_type_e::NDIlib_frame_type_error { + NDIlib_recv_free_audio_v2(pNDI_recv, &audio_frame); if count_frame_none < _settings.loss_threshold{ count_frame_none += 1; continue; @@ -434,12 +430,14 @@ impl ObjectSubclass for NdiAudioSrc { return Err(gst::FlowError::CustomError); } else if frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none && _settings.loss_threshold == 0{ + NDIlib_recv_free_audio_v2(pNDI_recv, &audio_frame); gst_debug!(self.cat, obj: element, "No audio frame received, sending empty buffer"); let buffer = gst::Buffer::with_size(0).unwrap(); return Ok(buffer) } if time >= (audio_frame.timestamp as u64) { + NDIlib_recv_free_audio_v2(pNDI_recv, &audio_frame); gst_debug!(self.cat, obj: element, "Frame timestamp ({:?}) is lower than received in the first frame from NDI ({:?}), so skiping...", (audio_frame.timestamp as u64), time); } else { skip_frame = false; @@ -481,6 +479,7 @@ impl ObjectSubclass for NdiAudioSrc { dst.reference_level = 0; dst.p_data = buffer.map_writable().unwrap().as_mut_slice_of::().unwrap().as_mut_ptr(); NDIlib_util_audio_to_interleaved_16s_v2(&audio_frame, &mut dst); + NDIlib_recv_free_audio_v2(pNDI_recv, &audio_frame); } gst_log!(self.cat, obj: element, "Produced buffer {:?}", buffer); diff --git a/src/ndisys.rs b/src/ndisys.rs index ad6915d9..2c39777f 100644 --- a/src/ndisys.rs +++ b/src/ndisys.rs @@ -33,6 +33,14 @@ extern "C" { p_metadata: *const NDIlib_metadata_frame_t, timeout_in_ms: u32, ) -> NDIlib_frame_type_e; + pub fn NDIlib_recv_free_video_v2( + p_instance: NDIlib_recv_instance_t, + p_video_data: *const NDIlib_video_frame_v2_t + ); + pub fn NDIlib_recv_free_audio_v2( + p_instance: NDIlib_recv_instance_t, + p_audio_data: *const NDIlib_audio_frame_v2_t + ); } pub type NDIlib_find_instance_t = *mut ::std::os::raw::c_void; diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index b712d6b4..b36eb115 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -267,24 +267,18 @@ impl ObjectSubclass for NdiVideoSrc { let video_frame: NDIlib_video_frame_v2_t = Default::default(); - let mut frame_type: NDIlib_frame_type_e = NDIlib_frame_type_e::NDIlib_frame_type_none; unsafe { - while frame_type != NDIlib_frame_type_e::NDIlib_frame_type_video { - frame_type = NDIlib_recv_capture_v2( - pNDI_recv, - &video_frame, - ptr::null(), - ptr::null(), - 1000, - ); - gst_debug!(self.cat, obj: element, "NDI video frame received: {:?}", video_frame); + while NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000) != NDIlib_frame_type_e::NDIlib_frame_type_video { + NDIlib_recv_free_video_v2(pNDI_recv, &video_frame); } + gst_debug!(self.cat, obj: element, "NDI video frame received: {:?}", video_frame); if receiver.initial_timestamp <= video_frame.timestamp as u64 || receiver.initial_timestamp == 0 { receiver.initial_timestamp = video_frame.timestamp as u64; } + NDIlib_recv_free_video_v2(pNDI_recv, &video_frame); gst_debug!(self.cat, obj: element, "Setting initial timestamp to {}", receiver.initial_timestamp); } } @@ -369,15 +363,11 @@ impl ObjectSubclass for NdiVideoSrc { let video_frame: NDIlib_video_frame_v2_t = Default::default(); - let mut frame_type: NDIlib_frame_type_e = NDIlib_frame_type_e::NDIlib_frame_type_none; - while frame_type != NDIlib_frame_type_e::NDIlib_frame_type_video { - unsafe { - frame_type = - NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000); - gst_debug!(self.cat, obj: element, "NDI video frame received: {:?}", video_frame); + unsafe { + while NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000) != NDIlib_frame_type_e::NDIlib_frame_type_video { + NDIlib_recv_free_video_v2(pNDI_recv, &video_frame); } } - settings.latency = gst::SECOND.mul_div_floor( video_frame.frame_rate_D as u64, video_frame.frame_rate_N as u64, @@ -394,7 +384,9 @@ impl ObjectSubclass for NdiVideoSrc { Fraction::new(video_frame.frame_rate_N, video_frame.frame_rate_D), ); } - + unsafe { + NDIlib_recv_free_video_v2(pNDI_recv, &video_frame); + } let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); self.parent_fixate(element, caps) } @@ -436,6 +428,7 @@ impl ObjectSubclass for NdiVideoSrc { if (frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none && _settings.loss_threshold != 0) || frame_type == NDIlib_frame_type_e::NDIlib_frame_type_error { + NDIlib_recv_free_video_v2(pNDI_recv, &video_frame); if count_frame_none < _settings.loss_threshold{ count_frame_none += 1; continue; @@ -444,12 +437,14 @@ impl ObjectSubclass for NdiVideoSrc { return Err(gst::FlowError::CustomError); } else if frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none && _settings.loss_threshold == 0{ + NDIlib_recv_free_video_v2(pNDI_recv, &video_frame); gst_debug!(self.cat, obj: element, "No video frame received, sending empty buffer"); let buffer = gst::Buffer::with_size(0).unwrap(); return Ok(buffer) } if time >= (video_frame.timestamp as u64) { + NDIlib_recv_free_video_v2(pNDI_recv, &video_frame); gst_debug!(self.cat, obj: element, "Frame timestamp ({:?}) is lower than received in the first frame from NDI ({:?}), so skiping...", (video_frame.timestamp as u64), time); } else { skip_frame = false; @@ -486,6 +481,7 @@ impl ObjectSubclass for NdiVideoSrc { timestamp_data.offset += 1; buffer.set_offset_end(timestamp_data.offset); buffer.copy_from_slice(0, &vec).unwrap(); + // NDIlib_recv_free_video_v2(pNDI_recv, &video_frame); } gst_log!(self.cat, obj: element, "Produced buffer {:?}", buffer); From 7c98b2e8309115e98a8475c37976d4c891e7b4fa Mon Sep 17 00:00:00 2001 From: o-reo Date: Mon, 4 Mar 2019 15:21:01 +0100 Subject: [PATCH 103/199] STYLE: clean useless comment --- src/ndivideosrc.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index b36eb115..4b1a292e 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -481,7 +481,6 @@ impl ObjectSubclass for NdiVideoSrc { timestamp_data.offset += 1; buffer.set_offset_end(timestamp_data.offset); buffer.copy_from_slice(0, &vec).unwrap(); - // NDIlib_recv_free_video_v2(pNDI_recv, &video_frame); } gst_log!(self.cat, obj: element, "Produced buffer {:?}", buffer); From dc2591ab450aafedd553ed7dfc936b0abc66a102 Mon Sep 17 00:00:00 2001 From: o-reo Date: Tue, 5 Mar 2019 17:59:44 +0100 Subject: [PATCH 104/199] FIX: Removed unncessary NDI free methods - when looping until a specific frame typeis returned no free are needed --- src/ndiaudiosrc.rs | 15 +++++++++------ src/ndivideosrc.rs | 13 +++++++------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index 584abbf4..797c4228 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -259,9 +259,9 @@ impl ObjectSubclass for NdiAudioSrc { let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); unsafe { - while NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000) != NDIlib_frame_type_e::NDIlib_frame_type_audio { - NDIlib_recv_free_audio_v2(pNDI_recv, &audio_frame); - } + while NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000) + != NDIlib_frame_type_e::NDIlib_frame_type_audio {} + } gst_debug!(self.cat, obj: element, "NDI audio frame received: {:?}", audio_frame); if receiver.initial_timestamp <= audio_frame.timestamp as u64 @@ -269,7 +269,9 @@ impl ObjectSubclass for NdiAudioSrc { { receiver.initial_timestamp = audio_frame.timestamp as u64; } - NDIlib_recv_free_audio_v2(pNDI_recv, &audio_frame); + unsafe { + NDIlib_recv_free_audio_v2(pNDI_recv, &audio_frame); + } gst_debug!(self.cat, obj: element, "Setting initial timestamp to {}", receiver.initial_timestamp); } } @@ -357,8 +359,9 @@ impl ObjectSubclass for NdiAudioSrc { let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); unsafe { - while NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000) != NDIlib_frame_type_e::NDIlib_frame_type_audio { - NDIlib_recv_free_audio_v2(pNDI_recv, &audio_frame); + unsafe { + while NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000) + != NDIlib_frame_type_e::NDIlib_frame_type_audio {} } } diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index 4b1a292e..283a68a2 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -268,9 +268,9 @@ impl ObjectSubclass for NdiVideoSrc { let video_frame: NDIlib_video_frame_v2_t = Default::default(); unsafe { - while NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000) != NDIlib_frame_type_e::NDIlib_frame_type_video { - NDIlib_recv_free_video_v2(pNDI_recv, &video_frame); - } + while NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000) + != NDIlib_frame_type_e::NDIlib_frame_type_video {} + } gst_debug!(self.cat, obj: element, "NDI video frame received: {:?}", video_frame); if receiver.initial_timestamp <= video_frame.timestamp as u64 @@ -278,7 +278,9 @@ impl ObjectSubclass for NdiVideoSrc { { receiver.initial_timestamp = video_frame.timestamp as u64; } + unsafe { NDIlib_recv_free_video_v2(pNDI_recv, &video_frame); + } gst_debug!(self.cat, obj: element, "Setting initial timestamp to {}", receiver.initial_timestamp); } } @@ -364,9 +366,8 @@ impl ObjectSubclass for NdiVideoSrc { let video_frame: NDIlib_video_frame_v2_t = Default::default(); unsafe { - while NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000) != NDIlib_frame_type_e::NDIlib_frame_type_video { - NDIlib_recv_free_video_v2(pNDI_recv, &video_frame); - } + while NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000) + != NDIlib_frame_type_e::NDIlib_frame_type_video {} } settings.latency = gst::SECOND.mul_div_floor( video_frame.frame_rate_D as u64, From 547df0f042e827df92f096dbac94133dff2f0fc8 Mon Sep 17 00:00:00 2001 From: o-reo Date: Wed, 6 Mar 2019 16:16:47 +0100 Subject: [PATCH 105/199] FIX: left a bracked after unsafe block refactoring --- src/ndiaudiosrc.rs | 26 +++++++++++--------------- src/ndivideosrc.rs | 11 +++++------ 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index 797c4228..a6be9d36 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -262,19 +262,17 @@ impl ObjectSubclass for NdiAudioSrc { while NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000) != NDIlib_frame_type_e::NDIlib_frame_type_audio {} } - gst_debug!(self.cat, obj: element, "NDI audio frame received: {:?}", audio_frame); + gst_debug!(self.cat, obj: element, "NDI audio frame received: {:?}", audio_frame); - if receiver.initial_timestamp <= audio_frame.timestamp as u64 - || receiver.initial_timestamp == 0 - { - receiver.initial_timestamp = audio_frame.timestamp as u64; - } - unsafe { - NDIlib_recv_free_audio_v2(pNDI_recv, &audio_frame); - } - gst_debug!(self.cat, obj: element, "Setting initial timestamp to {}", receiver.initial_timestamp); + if receiver.initial_timestamp <= audio_frame.timestamp as u64 + || receiver.initial_timestamp == 0 { + receiver.initial_timestamp = audio_frame.timestamp as u64; + } + unsafe { + NDIlib_recv_free_audio_v2(pNDI_recv, &audio_frame); + } + gst_debug!(self.cat, obj: element, "Setting initial timestamp to {}", receiver.initial_timestamp); } - } self.parent_change_state(element, transition) } } @@ -359,10 +357,8 @@ impl ObjectSubclass for NdiAudioSrc { let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); unsafe { - unsafe { - while NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000) - != NDIlib_frame_type_e::NDIlib_frame_type_audio {} - } + while NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000) + != NDIlib_frame_type_e::NDIlib_frame_type_audio {} } let no_samples = audio_frame.no_samples as u64; diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index 283a68a2..487865c2 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -268,8 +268,8 @@ impl ObjectSubclass for NdiVideoSrc { let video_frame: NDIlib_video_frame_v2_t = Default::default(); unsafe { - while NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000) - != NDIlib_frame_type_e::NDIlib_frame_type_video {} + while NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000) + != NDIlib_frame_type_e::NDIlib_frame_type_video {} } gst_debug!(self.cat, obj: element, "NDI video frame received: {:?}", video_frame); @@ -278,12 +278,11 @@ impl ObjectSubclass for NdiVideoSrc { { receiver.initial_timestamp = video_frame.timestamp as u64; } - unsafe { - NDIlib_recv_free_video_v2(pNDI_recv, &video_frame); - } + unsafe { + NDIlib_recv_free_video_v2(pNDI_recv, &video_frame); + } gst_debug!(self.cat, obj: element, "Setting initial timestamp to {}", receiver.initial_timestamp); } - } self.parent_change_state(element, transition) } } From 16139c4565cf8277cd144b8a0e1ce09cfed1f74b Mon Sep 17 00:00:00 2001 From: o-reo Date: Wed, 6 Mar 2019 17:57:28 +0100 Subject: [PATCH 106/199] FIX: Removed NDI free fctns when receiving NDI frame type none --- src/ndiaudiosrc.rs | 2 -- src/ndivideosrc.rs | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index a6be9d36..67b897af 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -420,7 +420,6 @@ impl ObjectSubclass for NdiAudioSrc { if (frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none && _settings.loss_threshold != 0) || frame_type == NDIlib_frame_type_e::NDIlib_frame_type_error { - NDIlib_recv_free_audio_v2(pNDI_recv, &audio_frame); if count_frame_none < _settings.loss_threshold{ count_frame_none += 1; continue; @@ -429,7 +428,6 @@ impl ObjectSubclass for NdiAudioSrc { return Err(gst::FlowError::CustomError); } else if frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none && _settings.loss_threshold == 0{ - NDIlib_recv_free_audio_v2(pNDI_recv, &audio_frame); gst_debug!(self.cat, obj: element, "No audio frame received, sending empty buffer"); let buffer = gst::Buffer::with_size(0).unwrap(); return Ok(buffer) diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index 487865c2..4bf2fe51 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -428,7 +428,6 @@ impl ObjectSubclass for NdiVideoSrc { if (frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none && _settings.loss_threshold != 0) || frame_type == NDIlib_frame_type_e::NDIlib_frame_type_error { - NDIlib_recv_free_video_v2(pNDI_recv, &video_frame); if count_frame_none < _settings.loss_threshold{ count_frame_none += 1; continue; @@ -437,7 +436,6 @@ impl ObjectSubclass for NdiVideoSrc { return Err(gst::FlowError::CustomError); } else if frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none && _settings.loss_threshold == 0{ - NDIlib_recv_free_video_v2(pNDI_recv, &video_frame); gst_debug!(self.cat, obj: element, "No video frame received, sending empty buffer"); let buffer = gst::Buffer::with_size(0).unwrap(); return Ok(buffer) From 2794366186f62f4b2dcf8d14e3673a86bb543a79 Mon Sep 17 00:00:00 2001 From: Ruben Gonzalez Date: Mon, 18 Mar 2019 18:24:37 +0100 Subject: [PATCH 107/199] Update license (fix #22) Use the same GStreamer license --- LICENSE | 591 ++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 465 insertions(+), 126 deletions(-) diff --git a/LICENSE b/LICENSE index 153d416d..8000a6fa 100644 --- a/LICENSE +++ b/LICENSE @@ -1,165 +1,504 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 - Copyright (C) 2007 Free Software Foundation, Inc. + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. + Preamble - 0. Additional Definitions. + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. - 1. Exception to Section 3 of the GNU GPL. + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. - 2. Conveying Modified Versions. + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. - 3. Object Code Incorporating Material from Library Header Files. + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - b) Accompany the object code with a copy of the GNU GPL and this license - document. + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". - 4. Combined Works. + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. - d) Do one of the following: + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. + a) The modified work must itself be a software library. - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. - 5. Combined Libraries. + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. - 6. Revised Versions of the GNU Lesser General Public License. +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. \ No newline at end of file + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random + Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! From 6dc39b2deaf005f4d0a965c0a4d52fcd48264cf0 Mon Sep 17 00:00:00 2001 From: rubenrua Date: Tue, 26 Mar 2019 17:41:28 +0100 Subject: [PATCH 108/199] Fix rust fmt --- src/lib.rs | 7 +- src/ndiaudiosrc.rs | 715 ++++++++++++++++++++++++--------------------- src/ndisys.rs | 4 +- src/ndivideosrc.rs | 688 +++++++++++++++++++++++-------------------- 4 files changed, 760 insertions(+), 654 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 739cda41..272d2fb1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -60,7 +60,12 @@ lazy_static! { static mut id_receiver: i8 = 0; -fn connect_ndi(cat: gst::DebugCategory, element: &gst_base::BaseSrc, ip: &str, stream_name: &str) -> i8 { +fn connect_ndi( + cat: gst::DebugCategory, + element: &gst_base::BaseSrc, + ip: &str, + stream_name: &str, +) -> i8 { gst_debug!(cat, obj: element, "Starting NDI connection..."); let mut receivers = hashmap_receivers.lock().unwrap(); diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index 67b897af..9c2ebcfa 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -21,8 +21,8 @@ use ndi_struct; use ndisys::*; use stop_ndi; -use hashmap_receivers; use byte_slice_cast::AsMutSliceOf; +use hashmap_receivers; #[derive(Debug, Clone)] struct Settings { @@ -46,35 +46,35 @@ impl Default for Settings { } static PROPERTIES: [subclass::Property; 3] = [ -subclass::Property("stream-name", |_| { - glib::ParamSpec::string( - "stream-name", - "Sream Name", - "Name of the streaming device", - None, - glib::ParamFlags::READWRITE, - ) -}), -subclass::Property("ip", |_| { - glib::ParamSpec::string( - "ip", - "Stream IP", - "IP of the streaming device. Ex: 127.0.0.1:5961", - None, - glib::ParamFlags::READWRITE, - ) -}), -subclass::Property("loss-threshold", |_| { - glib::ParamSpec::uint( - "loss-threshold", - "Loss threshold", - "Loss threshold", - 0, - 60, - 5, - glib::ParamFlags::READWRITE, - ) -}), + subclass::Property("stream-name", |_| { + glib::ParamSpec::string( + "stream-name", + "Sream Name", + "Name of the streaming device", + None, + glib::ParamFlags::READWRITE, + ) + }), + subclass::Property("ip", |_| { + glib::ParamSpec::string( + "ip", + "Stream IP", + "IP of the streaming device. Ex: 127.0.0.1:5961", + None, + glib::ParamFlags::READWRITE, + ) + }), + subclass::Property("loss-threshold", |_| { + glib::ParamSpec::uint( + "loss-threshold", + "Loss threshold", + "Loss threshold", + 0, + 60, + 5, + glib::ParamFlags::READWRITE, + ) + }), ]; struct State { @@ -99,7 +99,6 @@ struct NdiAudioSrc { } impl ObjectSubclass for NdiAudioSrc { - const NAME: &'static str = "NdiAudioSrc"; type ParentType = gst_base::BaseSrc; type Instance = gst::subclass::ElementInstanceStruct; @@ -116,7 +115,7 @@ impl ObjectSubclass for NdiAudioSrc { ), settings: Mutex::new(Default::default()), state: Mutex::new(Default::default()), - timestamp_data: Mutex::new(TimestampData { offset: 0}), + timestamp_data: Mutex::new(TimestampData { offset: 0 }), } } @@ -131,361 +130,419 @@ impl ObjectSubclass for NdiAudioSrc { let caps = gst::Caps::new_simple( "audio/x-raw", &[ - ( - "format", - &gst::List::new(&[ - //TODO add more formats? - //&gst_audio::AUDIO_FORMAT_F32.to_string(), - //&gst_audio::AUDIO_FORMAT_F64.to_string(), - &gst_audio::AUDIO_FORMAT_S16.to_string(), + ( + "format", + &gst::List::new(&[ + //TODO add more formats? + //&gst_audio::AUDIO_FORMAT_F32.to_string(), + //&gst_audio::AUDIO_FORMAT_F64.to_string(), + &gst_audio::AUDIO_FORMAT_S16.to_string(), ]), ), ("rate", &gst::IntRange::::new(1, i32::MAX)), ("channels", &gst::IntRange::::new(1, i32::MAX)), ("layout", &"interleaved"), ("channel-mask", &gst::Bitmask::new(0)), - ], - ); + ], + ); - let src_pad_template = gst::PadTemplate::new( - "src", - gst::PadDirection::Src, - gst::PadPresence::Always, - &caps, - ) - .unwrap(); - klass.add_pad_template(src_pad_template); + let src_pad_template = gst::PadTemplate::new( + "src", + gst::PadDirection::Src, + gst::PadPresence::Always, + &caps, + ) + .unwrap(); + klass.add_pad_template(src_pad_template); - klass.install_properties(&PROPERTIES); + klass.install_properties(&PROPERTIES); + } +} + +impl ObjectImpl for NdiAudioSrc { + glib_object_impl!(); + + fn constructed(&self, obj: &glib::Object) { + self.parent_constructed(obj); + + let basesrc = obj.downcast_ref::().unwrap(); + // Initialize live-ness and notify the base class that + // we'd like to operate in Time format + basesrc.set_live(true); + basesrc.set_format(gst::Format::Time); + } + + fn set_property(&self, obj: &glib::Object, id: usize, value: &glib::Value) { + let prop = &PROPERTIES[id]; + let basesrc = obj.downcast_ref::().unwrap(); + + match *prop { + subclass::Property("stream-name", ..) => { + let mut settings = self.settings.lock().unwrap(); + let stream_name = value.get().unwrap(); + gst_debug!( + self.cat, + obj: basesrc, + "Changing stream-name from {} to {}", + settings.stream_name, + stream_name + ); + settings.stream_name = stream_name; + drop(settings); + } + subclass::Property("ip", ..) => { + let mut settings = self.settings.lock().unwrap(); + let ip = value.get().unwrap(); + gst_debug!( + self.cat, + obj: basesrc, + "Changing ip from {} to {}", + settings.ip, + ip + ); + settings.ip = ip; + drop(settings); + } + subclass::Property("loss-threshold", ..) => { + let mut settings = self.settings.lock().unwrap(); + let loss_threshold = value.get().unwrap(); + gst_debug!( + self.cat, + obj: basesrc, + "Changing loss threshold from {} to {}", + settings.loss_threshold, + loss_threshold + ); + settings.loss_threshold = loss_threshold; + drop(settings); + } + _ => unimplemented!(), } } - impl ObjectImpl for NdiAudioSrc { - glib_object_impl!(); + fn get_property(&self, _obj: &glib::Object, id: usize) -> Result { + let prop = &PROPERTIES[id]; - fn constructed(&self, obj: &glib::Object) { - self.parent_constructed(obj); - - let basesrc = obj.downcast_ref::().unwrap(); - // Initialize live-ness and notify the base class that - // we'd like to operate in Time format - basesrc.set_live(true); - basesrc.set_format(gst::Format::Time); - } - - fn set_property(&self, obj: &glib::Object, id: usize, value: &glib::Value) { - let prop = &PROPERTIES[id]; - let basesrc = obj.downcast_ref::().unwrap(); - - match *prop { - subclass::Property("stream-name", ..) => { - let mut settings = self.settings.lock().unwrap(); - let stream_name = value.get().unwrap(); - gst_debug!( - self.cat, - obj: basesrc, - "Changing stream-name from {} to {}", - settings.stream_name, - stream_name - ); - settings.stream_name = stream_name; - drop(settings); - } - subclass::Property("ip", ..) => { - let mut settings = self.settings.lock().unwrap(); - let ip = value.get().unwrap(); - gst_debug!( - self.cat, - obj: basesrc, - "Changing ip from {} to {}", - settings.ip, - ip - ); - settings.ip = ip; - drop(settings); - } - subclass::Property("loss-threshold", ..) => { - let mut settings = self.settings.lock().unwrap(); - let loss_threshold = value.get().unwrap(); - gst_debug!( - self.cat, - obj: basesrc, - "Changing loss threshold from {} to {}", - settings.loss_threshold, - loss_threshold - ); - settings.loss_threshold = loss_threshold; - drop(settings); - } - _ => unimplemented!(), - } - } - - fn get_property(&self, _obj: &glib::Object, id: usize) -> Result { - let prop = &PROPERTIES[id]; - - match *prop { - subclass::Property("stream-name", ..) => { - let settings = self.settings.lock().unwrap(); - Ok(settings.stream_name.to_value()) - } - subclass::Property("ip", ..) => { - let settings = self.settings.lock().unwrap(); - Ok(settings.ip.to_value()) - } - subclass::Property("loss-threshold", ..) => { - let settings = self.settings.lock().unwrap(); - Ok(settings.loss_threshold.to_value()) - } - _ => unimplemented!(), - } - } - } - - impl ElementImpl for NdiAudioSrc { - fn change_state( - &self, - element: &gst::Element, - transition: gst::StateChange, - ) -> Result { - if transition == gst::StateChange::PausedToPlaying { - let mut receivers = hashmap_receivers.lock().unwrap(); + match *prop { + subclass::Property("stream-name", ..) => { let settings = self.settings.lock().unwrap(); - - let receiver = receivers.get_mut(&settings.id_receiver).unwrap(); - let recv = &receiver.ndi_instance; - let pNDI_recv = recv.recv; - - let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); - - unsafe { - while NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000) - != NDIlib_frame_type_e::NDIlib_frame_type_audio {} - } - gst_debug!(self.cat, obj: element, "NDI audio frame received: {:?}", audio_frame); - - if receiver.initial_timestamp <= audio_frame.timestamp as u64 - || receiver.initial_timestamp == 0 { - receiver.initial_timestamp = audio_frame.timestamp as u64; - } - unsafe { - NDIlib_recv_free_audio_v2(pNDI_recv, &audio_frame); - } - gst_debug!(self.cat, obj: element, "Setting initial timestamp to {}", receiver.initial_timestamp); - } - self.parent_change_state(element, transition) + Ok(settings.stream_name.to_value()) + } + subclass::Property("ip", ..) => { + let settings = self.settings.lock().unwrap(); + Ok(settings.ip.to_value()) + } + subclass::Property("loss-threshold", ..) => { + let settings = self.settings.lock().unwrap(); + Ok(settings.loss_threshold.to_value()) + } + _ => unimplemented!(), } } +} - impl BaseSrcImpl for NdiAudioSrc { - fn set_caps(&self, element: &gst_base::BaseSrc, caps: &gst::CapsRef) -> Result<(), gst::LoggableError> { - let info = match gst_audio::AudioInfo::from_caps(caps) { - None => return Err(gst_loggable_error!(self.cat, "Failed to build `AudioInfo` from caps {}", caps)), - Some(info) => info, - }; - - gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); - - let mut state = self.state.lock().unwrap(); - state.info = Some(info); - - Ok(()) - } - - fn start(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { - *self.state.lock().unwrap() = Default::default(); - - let mut settings = self.settings.lock().unwrap(); - settings.id_receiver = connect_ndi( - self.cat, - element, - &settings.ip.clone(), - &settings.stream_name.clone(), - ); - - match settings.id_receiver { - 0 => Err(gst_error_msg!( - gst::ResourceError::NotFound, - ["Could not connect to this source"] - )), - _ => Ok(()) - } - } - - fn stop(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { - *self.state.lock().unwrap() = Default::default(); - +impl ElementImpl for NdiAudioSrc { + fn change_state( + &self, + element: &gst::Element, + transition: gst::StateChange, + ) -> Result { + if transition == gst::StateChange::PausedToPlaying { + let mut receivers = hashmap_receivers.lock().unwrap(); let settings = self.settings.lock().unwrap(); - stop_ndi(self.cat, element, settings.id_receiver); - // Commented because when adding ndi destroy stopped in this line - //*self.state.lock().unwrap() = Default::default(); - Ok(()) - } - - fn query(&self, element: &gst_base::BaseSrc, query: &mut gst::QueryRef) -> bool { - use gst::QueryView; - if let QueryView::Scheduling(ref mut q) = query.view_mut() { - q.set(gst::SchedulingFlags::SEQUENTIAL, 1, -1, 0); - q.add_scheduling_modes(&[gst::PadMode::Push]); - return true; - } - if let QueryView::Latency(ref mut q) = query.view_mut() { - let settings = &*self.settings.lock().unwrap(); - let state = self.state.lock().unwrap(); - - if let Some(ref _info) = state.info { - let latency = settings.latency.unwrap(); - gst_debug!(self.cat, obj: element, "Returning latency {}", latency); - q.set(true, latency, gst::CLOCK_TIME_NONE); - return true; - } else { - return false; - } - } - BaseSrcImplExt::parent_query(self, element, query) - } - - fn fixate(&self, element: &gst_base::BaseSrc, caps: gst::Caps) -> gst::Caps { - let receivers = hashmap_receivers.lock().unwrap(); - let mut settings = self.settings.lock().unwrap(); - - let receiver = receivers.get(&settings.id_receiver).unwrap(); + let receiver = receivers.get_mut(&settings.id_receiver).unwrap(); let recv = &receiver.ndi_instance; let pNDI_recv = recv.recv; let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); unsafe { - while NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000) - != NDIlib_frame_type_e::NDIlib_frame_type_audio {} + while NDIlib_recv_capture_v2( + pNDI_recv, + ptr::null(), + &audio_frame, + ptr::null(), + 1000, + ) != NDIlib_frame_type_e::NDIlib_frame_type_audio + {} } + gst_debug!( + self.cat, + obj: element, + "NDI audio frame received: {:?}", + audio_frame + ); - let no_samples = audio_frame.no_samples as u64; - let audio_rate = audio_frame.sample_rate; - settings.latency = gst::SECOND.mul_div_floor(no_samples, audio_rate as u64); - - let mut caps = gst::Caps::truncate(caps); + if receiver.initial_timestamp <= audio_frame.timestamp as u64 + || receiver.initial_timestamp == 0 { - let caps = caps.make_mut(); - let s = caps.get_mut_structure(0).unwrap(); - s.fixate_field_nearest_int("rate", audio_rate); - s.fixate_field_nearest_int("channels", audio_frame.no_channels); - s.fixate_field_str("layout", "interleaved"); - s.set_value("channel-mask", gst::Bitmask::new(gst_audio::AudioChannelPosition::get_fallback_mask(audio_frame.no_channels as u32)).to_send_value()); + receiver.initial_timestamp = audio_frame.timestamp as u64; } - - let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); unsafe { NDIlib_recv_free_audio_v2(pNDI_recv, &audio_frame); } + gst_debug!( + self.cat, + obj: element, + "Setting initial timestamp to {}", + receiver.initial_timestamp + ); + } + self.parent_change_state(element, transition) + } +} - self.parent_fixate(element, caps) +impl BaseSrcImpl for NdiAudioSrc { + fn set_caps( + &self, + element: &gst_base::BaseSrc, + caps: &gst::CapsRef, + ) -> Result<(), gst::LoggableError> { + let info = match gst_audio::AudioInfo::from_caps(caps) { + None => { + return Err(gst_loggable_error!( + self.cat, + "Failed to build `AudioInfo` from caps {}", + caps + )); + } + Some(info) => info, + }; + + gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); + + let mut state = self.state.lock().unwrap(); + state.info = Some(info); + + Ok(()) + } + + fn start(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { + *self.state.lock().unwrap() = Default::default(); + + let mut settings = self.settings.lock().unwrap(); + settings.id_receiver = connect_ndi( + self.cat, + element, + &settings.ip.clone(), + &settings.stream_name.clone(), + ); + + match settings.id_receiver { + 0 => Err(gst_error_msg!( + gst::ResourceError::NotFound, + ["Could not connect to this source"] + )), + _ => Ok(()), + } + } + + fn stop(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { + *self.state.lock().unwrap() = Default::default(); + + let settings = self.settings.lock().unwrap(); + stop_ndi(self.cat, element, settings.id_receiver); + // Commented because when adding ndi destroy stopped in this line + //*self.state.lock().unwrap() = Default::default(); + Ok(()) + } + + fn query(&self, element: &gst_base::BaseSrc, query: &mut gst::QueryRef) -> bool { + use gst::QueryView; + if let QueryView::Scheduling(ref mut q) = query.view_mut() { + q.set(gst::SchedulingFlags::SEQUENTIAL, 1, -1, 0); + q.add_scheduling_modes(&[gst::PadMode::Push]); + return true; + } + if let QueryView::Latency(ref mut q) = query.view_mut() { + let settings = &*self.settings.lock().unwrap(); + let state = self.state.lock().unwrap(); + + if let Some(ref _info) = state.info { + let latency = settings.latency.unwrap(); + gst_debug!(self.cat, obj: element, "Returning latency {}", latency); + q.set(true, latency, gst::CLOCK_TIME_NONE); + return true; + } else { + return false; + } + } + BaseSrcImplExt::parent_query(self, element, query) + } + + fn fixate(&self, element: &gst_base::BaseSrc, caps: gst::Caps) -> gst::Caps { + let receivers = hashmap_receivers.lock().unwrap(); + let mut settings = self.settings.lock().unwrap(); + + let receiver = receivers.get(&settings.id_receiver).unwrap(); + + let recv = &receiver.ndi_instance; + let pNDI_recv = recv.recv; + + let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); + + unsafe { + while NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000) + != NDIlib_frame_type_e::NDIlib_frame_type_audio + {} } - fn create( - &self, - element: &gst_base::BaseSrc, - _offset: u64, - _length: u32, - ) -> Result { - let _settings = &*self.settings.lock().unwrap(); + let no_samples = audio_frame.no_samples as u64; + let audio_rate = audio_frame.sample_rate; + settings.latency = gst::SECOND.mul_div_floor(no_samples, audio_rate as u64); - let mut timestamp_data = self.timestamp_data.lock().unwrap(); + let mut caps = gst::Caps::truncate(caps); + { + let caps = caps.make_mut(); + let s = caps.get_mut_structure(0).unwrap(); + s.fixate_field_nearest_int("rate", audio_rate); + s.fixate_field_nearest_int("channels", audio_frame.no_channels); + s.fixate_field_str("layout", "interleaved"); + s.set_value( + "channel-mask", + gst::Bitmask::new(gst_audio::AudioChannelPosition::get_fallback_mask( + audio_frame.no_channels as u32, + )) + .to_send_value(), + ); + } - let state = self.state.lock().unwrap(); - let _info = match state.info { - None => { - gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); - return Err(gst::FlowError::NotNegotiated); - } - Some(ref info) => info.clone(), - }; - let receivers = hashmap_receivers.lock().unwrap(); + let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); + unsafe { + NDIlib_recv_free_audio_v2(pNDI_recv, &audio_frame); + } - let recv = &receivers.get(&_settings.id_receiver).unwrap().ndi_instance; - let pNDI_recv = recv.recv; + self.parent_fixate(element, caps) + } - let pts: u64; - let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); + fn create( + &self, + element: &gst_base::BaseSrc, + _offset: u64, + _length: u32, + ) -> Result { + let _settings = &*self.settings.lock().unwrap(); - unsafe { - let time = receivers.get(&_settings.id_receiver).unwrap().initial_timestamp; + let mut timestamp_data = self.timestamp_data.lock().unwrap(); - let mut skip_frame = true; - let mut count_frame_none = 0; - while skip_frame { - let frame_type = + let state = self.state.lock().unwrap(); + let _info = match state.info { + None => { + gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); + return Err(gst::FlowError::NotNegotiated); + } + Some(ref info) => info.clone(), + }; + let receivers = hashmap_receivers.lock().unwrap(); + + let recv = &receivers.get(&_settings.id_receiver).unwrap().ndi_instance; + let pNDI_recv = recv.recv; + + let pts: u64; + let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); + + unsafe { + let time = receivers + .get(&_settings.id_receiver) + .unwrap() + .initial_timestamp; + + let mut skip_frame = true; + let mut count_frame_none = 0; + while skip_frame { + let frame_type = NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000); - if (frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none && _settings.loss_threshold != 0) + if (frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none + && _settings.loss_threshold != 0) || frame_type == NDIlib_frame_type_e::NDIlib_frame_type_error - { - if count_frame_none < _settings.loss_threshold{ - count_frame_none += 1; - continue; - } - gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type none or error received, assuming that the source closed the stream...."]); - return Err(gst::FlowError::CustomError); - } - else if frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none && _settings.loss_threshold == 0{ - gst_debug!(self.cat, obj: element, "No audio frame received, sending empty buffer"); - let buffer = gst::Buffer::with_size(0).unwrap(); - return Ok(buffer) - } - - if time >= (audio_frame.timestamp as u64) { - NDIlib_recv_free_audio_v2(pNDI_recv, &audio_frame); - gst_debug!(self.cat, obj: element, "Frame timestamp ({:?}) is lower than received in the first frame from NDI ({:?}), so skiping...", (audio_frame.timestamp as u64), time); - } else { - skip_frame = false; + { + if count_frame_none < _settings.loss_threshold { + count_frame_none += 1; + continue; } + gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type none or error received, assuming that the source closed the stream...."]); + return Err(gst::FlowError::CustomError); + } else if frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none + && _settings.loss_threshold == 0 + { + gst_debug!( + self.cat, + obj: element, + "No audio frame received, sending empty buffer" + ); + let buffer = gst::Buffer::with_size(0).unwrap(); + return Ok(buffer); } - gst_log!(self.cat, obj: element, "NDI audio frame received: {:?}", (audio_frame)); + if time >= (audio_frame.timestamp as u64) { + NDIlib_recv_free_audio_v2(pNDI_recv, &audio_frame); + gst_debug!(self.cat, obj: element, "Frame timestamp ({:?}) is lower than received in the first frame from NDI ({:?}), so skiping...", (audio_frame.timestamp as u64), time); + } else { + skip_frame = false; + } + } - pts = audio_frame.timestamp as u64 - time; + gst_log!( + self.cat, + obj: element, + "NDI audio frame received: {:?}", + (audio_frame) + ); - gst_log!(self.cat, obj: element, "Calculated pts for audio frame: {:?}", (pts)); + pts = audio_frame.timestamp as u64 - time; - // We multiply by 2 because is the size in bytes of an i16 variable - let buff_size = (audio_frame.no_samples * 2 * audio_frame.no_channels) as usize; - let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); - { - if ndi_struct.start_pts == gst::ClockTime(Some(0)) { - ndi_struct.start_pts = + gst_log!( + self.cat, + obj: element, + "Calculated pts for audio frame: {:?}", + (pts) + ); + + // We multiply by 2 because is the size in bytes of an i16 variable + let buff_size = (audio_frame.no_samples * 2 * audio_frame.no_channels) as usize; + let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); + { + if ndi_struct.start_pts == gst::ClockTime(Some(0)) { + ndi_struct.start_pts = element.get_clock().unwrap().get_time() - element.get_base_time(); - } + } - let buffer = buffer.get_mut().unwrap(); + let buffer = buffer.get_mut().unwrap(); - // Newtek NDI yields times in 100ns intervals since the Unix Time - let pts: gst::ClockTime = (pts * 100).into(); - buffer.set_pts(pts + ndi_struct.start_pts); + // Newtek NDI yields times in 100ns intervals since the Unix Time + let pts: gst::ClockTime = (pts * 100).into(); + buffer.set_pts(pts + ndi_struct.start_pts); - let duration: gst::ClockTime = (((f64::from(audio_frame.no_samples) + let duration: gst::ClockTime = (((f64::from(audio_frame.no_samples) / f64::from(audio_frame.sample_rate)) * 1_000_000_000.0) as u64) .into(); - buffer.set_duration(duration); + buffer.set_duration(duration); - buffer.set_offset(timestamp_data.offset); - timestamp_data.offset += audio_frame.no_samples as u64; - buffer.set_offset_end(timestamp_data.offset); + buffer.set_offset(timestamp_data.offset); + timestamp_data.offset += audio_frame.no_samples as u64; + buffer.set_offset_end(timestamp_data.offset); - let mut dst: NDIlib_audio_frame_interleaved_16s_t = Default::default(); - dst.reference_level = 0; - dst.p_data = buffer.map_writable().unwrap().as_mut_slice_of::().unwrap().as_mut_ptr(); - NDIlib_util_audio_to_interleaved_16s_v2(&audio_frame, &mut dst); - NDIlib_recv_free_audio_v2(pNDI_recv, &audio_frame); - } - - gst_log!(self.cat, obj: element, "Produced buffer {:?}", buffer); - - Ok(buffer) + let mut dst: NDIlib_audio_frame_interleaved_16s_t = Default::default(); + dst.reference_level = 0; + dst.p_data = buffer + .map_writable() + .unwrap() + .as_mut_slice_of::() + .unwrap() + .as_mut_ptr(); + NDIlib_util_audio_to_interleaved_16s_v2(&audio_frame, &mut dst); + NDIlib_recv_free_audio_v2(pNDI_recv, &audio_frame); } + + gst_log!(self.cat, obj: element, "Produced buffer {:?}", buffer); + + Ok(buffer) } } +} - pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { - gst::Element::register(plugin, "ndiaudiosrc", 0, NdiAudioSrc::get_type()) - } +pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { + gst::Element::register(plugin, "ndiaudiosrc", 0, NdiAudioSrc::get_type()) +} diff --git a/src/ndisys.rs b/src/ndisys.rs index 2c39777f..30e5ebf4 100644 --- a/src/ndisys.rs +++ b/src/ndisys.rs @@ -35,11 +35,11 @@ extern "C" { ) -> NDIlib_frame_type_e; pub fn NDIlib_recv_free_video_v2( p_instance: NDIlib_recv_instance_t, - p_video_data: *const NDIlib_video_frame_v2_t + p_video_data: *const NDIlib_video_frame_v2_t, ); pub fn NDIlib_recv_free_audio_v2( p_instance: NDIlib_recv_instance_t, - p_audio_data: *const NDIlib_audio_frame_v2_t + p_audio_data: *const NDIlib_audio_frame_v2_t, ); } diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index 4bf2fe51..50192083 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -47,35 +47,35 @@ impl Default for Settings { } static PROPERTIES: [subclass::Property; 3] = [ -subclass::Property("stream-name", |_| { - glib::ParamSpec::string( - "stream-name", - "Stream Name", - "Name of the streaming device", - None, - glib::ParamFlags::READWRITE, - ) -}), -subclass::Property("ip", |_| { - glib::ParamSpec::string( - "ip", - "Stream IP", - "IP of the streaming device. Ex: 127.0.0.1:5961", - None, - glib::ParamFlags::READWRITE, - ) -}), -subclass::Property("loss-threshold", |_| { - glib::ParamSpec::uint( - "loss-threshold", - "Loss threshold", - "Loss threshold", - 0, - 60, - 5, - glib::ParamFlags::READWRITE, - ) -}), + subclass::Property("stream-name", |_| { + glib::ParamSpec::string( + "stream-name", + "Stream Name", + "Name of the streaming device", + None, + glib::ParamFlags::READWRITE, + ) + }), + subclass::Property("ip", |_| { + glib::ParamSpec::string( + "ip", + "Stream IP", + "IP of the streaming device. Ex: 127.0.0.1:5961", + None, + glib::ParamFlags::READWRITE, + ) + }), + subclass::Property("loss-threshold", |_| { + glib::ParamSpec::uint( + "loss-threshold", + "Loss threshold", + "Loss threshold", + 0, + 60, + 5, + glib::ParamFlags::READWRITE, + ) + }), ]; struct State { @@ -100,7 +100,6 @@ struct NdiVideoSrc { } impl ObjectSubclass for NdiVideoSrc { - const NAME: &'static str = "NdiVideoSrc"; type ParentType = gst_base::BaseSrc; type Instance = gst::subclass::ElementInstanceStruct; @@ -134,13 +133,13 @@ impl ObjectSubclass for NdiVideoSrc { let caps = gst::Caps::new_simple( "video/x-raw", &[ - ( - "format", - &gst::List::new(&[ - //TODO add all formats - &gst_video::VideoFormat::Uyvy.to_string(), - //&gst_video::VideoFormat::Rgb.to_string(), - //&gst_video::VideoFormat::Gray8.to_string(), + ( + "format", + &gst::List::new(&[ + //TODO add all formats + &gst_video::VideoFormat::Uyvy.to_string(), + //&gst_video::VideoFormat::Rgb.to_string(), + //&gst_video::VideoFormat::Gray8.to_string(), ]), ), ("width", &gst::IntRange::::new(0, i32::MAX)), @@ -152,342 +151,387 @@ impl ObjectSubclass for NdiVideoSrc { gst::Fraction::new(i32::MAX, 1), ), ), - ], - ); + ], + ); - let src_pad_template = gst::PadTemplate::new( - "src", - gst::PadDirection::Src, - gst::PadPresence::Always, - &caps, - ).unwrap(); - klass.add_pad_template(src_pad_template); + let src_pad_template = gst::PadTemplate::new( + "src", + gst::PadDirection::Src, + gst::PadPresence::Always, + &caps, + ) + .unwrap(); + klass.add_pad_template(src_pad_template); - klass.install_properties(&PROPERTIES); + klass.install_properties(&PROPERTIES); + } +} + +impl ObjectImpl for NdiVideoSrc { + glib_object_impl!(); + + fn constructed(&self, obj: &glib::Object) { + self.parent_constructed(obj); + + let basesrc = obj.downcast_ref::().unwrap(); + // Initialize live-ness and notify the base class that + // we'd like to operate in Time format + basesrc.set_live(true); + basesrc.set_format(gst::Format::Time); + } + + fn set_property(&self, obj: &glib::Object, id: usize, value: &glib::Value) { + let prop = &PROPERTIES[id]; + let basesrc = obj.downcast_ref::().unwrap(); + + match *prop { + subclass::Property("stream-name", ..) => { + let mut settings = self.settings.lock().unwrap(); + let stream_name = value.get().unwrap(); + gst_debug!( + self.cat, + obj: basesrc, + "Changing stream-name from {} to {}", + settings.stream_name, + stream_name + ); + settings.stream_name = stream_name; + drop(settings); + } + subclass::Property("ip", ..) => { + let mut settings = self.settings.lock().unwrap(); + let ip = value.get().unwrap(); + gst_debug!( + self.cat, + obj: basesrc, + "Changing ip from {} to {}", + settings.ip, + ip + ); + settings.ip = ip; + drop(settings); + } + subclass::Property("loss-threshold", ..) => { + let mut settings = self.settings.lock().unwrap(); + let loss_threshold = value.get().unwrap(); + gst_debug!( + self.cat, + obj: basesrc, + "Changing loss threshold from {} to {}", + settings.loss_threshold, + loss_threshold + ); + settings.loss_threshold = loss_threshold; + drop(settings); + } + _ => unimplemented!(), } } + fn get_property(&self, _obj: &glib::Object, id: usize) -> Result { + let prop = &PROPERTIES[id]; - impl ObjectImpl for NdiVideoSrc { - glib_object_impl!(); - - fn constructed(&self, obj: &glib::Object) { - self.parent_constructed(obj); - - let basesrc = obj.downcast_ref::().unwrap(); - // Initialize live-ness and notify the base class that - // we'd like to operate in Time format - basesrc.set_live(true); - basesrc.set_format(gst::Format::Time); - } - - - fn set_property(&self, obj: &glib::Object, id: usize, value: &glib::Value) { - let prop = &PROPERTIES[id]; - let basesrc = obj.downcast_ref::().unwrap(); - - match *prop { - subclass::Property("stream-name", ..) => { - let mut settings = self.settings.lock().unwrap(); - let stream_name = value.get().unwrap(); - gst_debug!( - self.cat, - obj: basesrc, - "Changing stream-name from {} to {}", - settings.stream_name, - stream_name - ); - settings.stream_name = stream_name; - drop(settings); - } - subclass::Property("ip", ..) => { - let mut settings = self.settings.lock().unwrap(); - let ip = value.get().unwrap(); - gst_debug!( - self.cat, - obj: basesrc, - "Changing ip from {} to {}", - settings.ip, - ip - ); - settings.ip = ip; - drop(settings); - } - subclass::Property("loss-threshold", ..) => { - let mut settings = self.settings.lock().unwrap(); - let loss_threshold = value.get().unwrap(); - gst_debug!( - self.cat, - obj: basesrc, - "Changing loss threshold from {} to {}", - settings.loss_threshold, - loss_threshold - ); - settings.loss_threshold = loss_threshold; - drop(settings); - } - _ => unimplemented!(), - } - } - - fn get_property(&self, _obj: &glib::Object, id: usize) -> Result { - let prop = &PROPERTIES[id]; - - match *prop { - subclass::Property("stream-name", ..) => { - let settings = self.settings.lock().unwrap(); - Ok(settings.stream_name.to_value()) - } - subclass::Property("ip", ..) => { - let settings = self.settings.lock().unwrap(); - Ok(settings.ip.to_value()) - } - subclass::Property("loss-threshold", ..) => { - let settings = self.settings.lock().unwrap(); - Ok(settings.loss_threshold.to_value()) - } - _ => unimplemented!(), - } - } - } - - impl ElementImpl for NdiVideoSrc { - fn change_state( - &self, - element: &gst::Element, - transition: gst::StateChange, - ) -> Result { - if transition == gst::StateChange::PausedToPlaying { - let mut receivers = hashmap_receivers.lock().unwrap(); + match *prop { + subclass::Property("stream-name", ..) => { let settings = self.settings.lock().unwrap(); - - let receiver = receivers.get_mut(&settings.id_receiver).unwrap(); - let recv = &receiver.ndi_instance; - let pNDI_recv = recv.recv; - - let video_frame: NDIlib_video_frame_v2_t = Default::default(); - - unsafe { - while NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000) - != NDIlib_frame_type_e::NDIlib_frame_type_video {} - } - gst_debug!(self.cat, obj: element, "NDI video frame received: {:?}", video_frame); - - if receiver.initial_timestamp <= video_frame.timestamp as u64 - || receiver.initial_timestamp == 0 - { - receiver.initial_timestamp = video_frame.timestamp as u64; - } - unsafe { - NDIlib_recv_free_video_v2(pNDI_recv, &video_frame); - } - gst_debug!(self.cat, obj: element, "Setting initial timestamp to {}", receiver.initial_timestamp); - } - self.parent_change_state(element, transition) + Ok(settings.stream_name.to_value()) + } + subclass::Property("ip", ..) => { + let settings = self.settings.lock().unwrap(); + Ok(settings.ip.to_value()) + } + subclass::Property("loss-threshold", ..) => { + let settings = self.settings.lock().unwrap(); + Ok(settings.loss_threshold.to_value()) + } + _ => unimplemented!(), } } +} - impl BaseSrcImpl for NdiVideoSrc { - fn set_caps(&self, element: &gst_base::BaseSrc, caps: &gst::CapsRef) -> Result<(), gst::LoggableError> { - let info = match gst_video::VideoInfo::from_caps(caps) { - None => return Err(gst_loggable_error!(self.cat, "Failed to build `VideoInfo` from caps {}", caps)), - Some(info) => info, - }; - gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); - - let mut state = self.state.lock().unwrap(); - state.info = Some(info); - let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); - Ok(()) - } - - fn start(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { - *self.state.lock().unwrap() = Default::default(); - let mut settings = self.settings.lock().unwrap(); - settings.id_receiver = connect_ndi( - self.cat, - element, - &settings.ip.clone(), - &settings.stream_name.clone(), - ); - - // settings.id_receiver != 0 - match settings.id_receiver { - 0 => Err(gst_error_msg!( - gst::ResourceError::NotFound, - ["Could not connect to this source"] - )), - _ => Ok(()) - } - } - - fn stop(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { - *self.state.lock().unwrap() = Default::default(); - +impl ElementImpl for NdiVideoSrc { + fn change_state( + &self, + element: &gst::Element, + transition: gst::StateChange, + ) -> Result { + if transition == gst::StateChange::PausedToPlaying { + let mut receivers = hashmap_receivers.lock().unwrap(); let settings = self.settings.lock().unwrap(); - stop_ndi(self.cat, element, settings.id_receiver); - // Commented because when adding ndi destroy stopped in this line - //*self.state.lock().unwrap() = Default::default(); - Ok(()) - } - fn query(&self, element: &gst_base::BaseSrc, query: &mut gst::QueryRef) -> bool { - use gst::QueryView; - if let QueryView::Scheduling(ref mut q) = query.view_mut() { - q.set(gst::SchedulingFlags::SEQUENTIAL, 1, -1, 0); - q.add_scheduling_modes(&[gst::PadMode::Push]); - return true; - } - if let QueryView::Latency(ref mut q) = query.view_mut() { - let settings = &*self.settings.lock().unwrap(); - let state = self.state.lock().unwrap(); - - if let Some(ref _info) = state.info { - let latency = settings.latency.unwrap(); - gst_debug!(self.cat, obj: element, "Returning latency {}", latency); - q.set(true, latency, gst::CLOCK_TIME_NONE); - return true; - } else { - return false; - } - } - BaseSrcImplExt::parent_query(self, element, query) - } - - fn fixate(&self, element: &gst_base::BaseSrc, caps: gst::Caps) -> gst::Caps { - let receivers = hashmap_receivers.lock().unwrap(); - let mut settings = self.settings.lock().unwrap(); - - let receiver = receivers.get(&settings.id_receiver).unwrap(); + let receiver = receivers.get_mut(&settings.id_receiver).unwrap(); let recv = &receiver.ndi_instance; let pNDI_recv = recv.recv; let video_frame: NDIlib_video_frame_v2_t = Default::default(); unsafe { - while NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000) - != NDIlib_frame_type_e::NDIlib_frame_type_video {} + while NDIlib_recv_capture_v2( + pNDI_recv, + &video_frame, + ptr::null(), + ptr::null(), + 1000, + ) != NDIlib_frame_type_e::NDIlib_frame_type_video + {} } - settings.latency = gst::SECOND.mul_div_floor( - video_frame.frame_rate_D as u64, - video_frame.frame_rate_N as u64, + gst_debug!( + self.cat, + obj: element, + "NDI video frame received: {:?}", + video_frame ); - let mut caps = gst::Caps::truncate(caps); + if receiver.initial_timestamp <= video_frame.timestamp as u64 + || receiver.initial_timestamp == 0 { - let caps = caps.make_mut(); - let s = caps.get_mut_structure(0).unwrap(); - s.fixate_field_nearest_int("width", video_frame.xres); - s.fixate_field_nearest_int("height", video_frame.yres); - s.fixate_field_nearest_fraction( - "framerate", - Fraction::new(video_frame.frame_rate_N, video_frame.frame_rate_D), - ); + receiver.initial_timestamp = video_frame.timestamp as u64; } unsafe { NDIlib_recv_free_video_v2(pNDI_recv, &video_frame); } - let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); - self.parent_fixate(element, caps) + gst_debug!( + self.cat, + obj: element, + "Setting initial timestamp to {}", + receiver.initial_timestamp + ); } + self.parent_change_state(element, transition) + } +} - //Creates the video buffers - fn create( - &self, - element: &gst_base::BaseSrc, - _offset: u64, - _length: u32, - ) -> Result { - let _settings = &*self.settings.lock().unwrap(); +impl BaseSrcImpl for NdiVideoSrc { + fn set_caps( + &self, + element: &gst_base::BaseSrc, + caps: &gst::CapsRef, + ) -> Result<(), gst::LoggableError> { + let info = match gst_video::VideoInfo::from_caps(caps) { + None => { + return Err(gst_loggable_error!( + self.cat, + "Failed to build `VideoInfo` from caps {}", + caps + )); + } + Some(info) => info, + }; + gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); - let mut timestamp_data = self.timestamp_data.lock().unwrap(); + let mut state = self.state.lock().unwrap(); + state.info = Some(info); + let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); + Ok(()) + } + + fn start(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { + *self.state.lock().unwrap() = Default::default(); + let mut settings = self.settings.lock().unwrap(); + settings.id_receiver = connect_ndi( + self.cat, + element, + &settings.ip.clone(), + &settings.stream_name.clone(), + ); + + // settings.id_receiver != 0 + match settings.id_receiver { + 0 => Err(gst_error_msg!( + gst::ResourceError::NotFound, + ["Could not connect to this source"] + )), + _ => Ok(()), + } + } + + fn stop(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { + *self.state.lock().unwrap() = Default::default(); + + let settings = self.settings.lock().unwrap(); + stop_ndi(self.cat, element, settings.id_receiver); + // Commented because when adding ndi destroy stopped in this line + //*self.state.lock().unwrap() = Default::default(); + Ok(()) + } + + fn query(&self, element: &gst_base::BaseSrc, query: &mut gst::QueryRef) -> bool { + use gst::QueryView; + if let QueryView::Scheduling(ref mut q) = query.view_mut() { + q.set(gst::SchedulingFlags::SEQUENTIAL, 1, -1, 0); + q.add_scheduling_modes(&[gst::PadMode::Push]); + return true; + } + if let QueryView::Latency(ref mut q) = query.view_mut() { + let settings = &*self.settings.lock().unwrap(); let state = self.state.lock().unwrap(); - let _info = match state.info { - None => { - gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); - return Err(gst::FlowError::NotNegotiated); - } - Some(ref info) => info.clone(), - }; - let receivers = hashmap_receivers.lock().unwrap(); - let recv = &receivers.get(&_settings.id_receiver).unwrap().ndi_instance; - let pNDI_recv = recv.recv; + if let Some(ref _info) = state.info { + let latency = settings.latency.unwrap(); + gst_debug!(self.cat, obj: element, "Returning latency {}", latency); + q.set(true, latency, gst::CLOCK_TIME_NONE); + return true; + } else { + return false; + } + } + BaseSrcImplExt::parent_query(self, element, query) + } - let pts: u64; - let video_frame: NDIlib_video_frame_v2_t = Default::default(); + fn fixate(&self, element: &gst_base::BaseSrc, caps: gst::Caps) -> gst::Caps { + let receivers = hashmap_receivers.lock().unwrap(); + let mut settings = self.settings.lock().unwrap(); - unsafe { - let time = receivers.get(&_settings.id_receiver).unwrap().initial_timestamp; + let receiver = receivers.get(&settings.id_receiver).unwrap(); + let recv = &receiver.ndi_instance; + let pNDI_recv = recv.recv; - let mut skip_frame = true; - let mut count_frame_none = 0; - while skip_frame { - let frame_type = + let video_frame: NDIlib_video_frame_v2_t = Default::default(); + + unsafe { + while NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000) + != NDIlib_frame_type_e::NDIlib_frame_type_video + {} + } + settings.latency = gst::SECOND.mul_div_floor( + video_frame.frame_rate_D as u64, + video_frame.frame_rate_N as u64, + ); + + let mut caps = gst::Caps::truncate(caps); + { + let caps = caps.make_mut(); + let s = caps.get_mut_structure(0).unwrap(); + s.fixate_field_nearest_int("width", video_frame.xres); + s.fixate_field_nearest_int("height", video_frame.yres); + s.fixate_field_nearest_fraction( + "framerate", + Fraction::new(video_frame.frame_rate_N, video_frame.frame_rate_D), + ); + } + unsafe { + NDIlib_recv_free_video_v2(pNDI_recv, &video_frame); + } + let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); + self.parent_fixate(element, caps) + } + + //Creates the video buffers + fn create( + &self, + element: &gst_base::BaseSrc, + _offset: u64, + _length: u32, + ) -> Result { + let _settings = &*self.settings.lock().unwrap(); + + let mut timestamp_data = self.timestamp_data.lock().unwrap(); + let state = self.state.lock().unwrap(); + let _info = match state.info { + None => { + gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); + return Err(gst::FlowError::NotNegotiated); + } + Some(ref info) => info.clone(), + }; + let receivers = hashmap_receivers.lock().unwrap(); + + let recv = &receivers.get(&_settings.id_receiver).unwrap().ndi_instance; + let pNDI_recv = recv.recv; + + let pts: u64; + let video_frame: NDIlib_video_frame_v2_t = Default::default(); + + unsafe { + let time = receivers + .get(&_settings.id_receiver) + .unwrap() + .initial_timestamp; + + let mut skip_frame = true; + let mut count_frame_none = 0; + while skip_frame { + let frame_type = NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000); - if (frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none && _settings.loss_threshold != 0) + if (frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none + && _settings.loss_threshold != 0) || frame_type == NDIlib_frame_type_e::NDIlib_frame_type_error - { - if count_frame_none < _settings.loss_threshold{ - count_frame_none += 1; - continue; - } - gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type none or error received, assuming that the source closed the stream...."]); - return Err(gst::FlowError::CustomError); - } - else if frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none && _settings.loss_threshold == 0{ - gst_debug!(self.cat, obj: element, "No video frame received, sending empty buffer"); - let buffer = gst::Buffer::with_size(0).unwrap(); - return Ok(buffer) - } - - if time >= (video_frame.timestamp as u64) { - NDIlib_recv_free_video_v2(pNDI_recv, &video_frame); - gst_debug!(self.cat, obj: element, "Frame timestamp ({:?}) is lower than received in the first frame from NDI ({:?}), so skiping...", (video_frame.timestamp as u64), time); - } else { - skip_frame = false; + { + if count_frame_none < _settings.loss_threshold { + count_frame_none += 1; + continue; } + gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type none or error received, assuming that the source closed the stream...."]); + return Err(gst::FlowError::CustomError); + } else if frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none + && _settings.loss_threshold == 0 + { + gst_debug!( + self.cat, + obj: element, + "No video frame received, sending empty buffer" + ); + let buffer = gst::Buffer::with_size(0).unwrap(); + return Ok(buffer); } - gst_log!(self.cat, obj: element, "NDI video frame received: {:?}", (video_frame)); + if time >= (video_frame.timestamp as u64) { + NDIlib_recv_free_video_v2(pNDI_recv, &video_frame); + gst_debug!(self.cat, obj: element, "Frame timestamp ({:?}) is lower than received in the first frame from NDI ({:?}), so skiping...", (video_frame.timestamp as u64), time); + } else { + skip_frame = false; + } + } - pts = video_frame.timestamp as u64 - time; + gst_log!( + self.cat, + obj: element, + "NDI video frame received: {:?}", + (video_frame) + ); - gst_log!(self.cat, obj: element, "Calculated pts for video frame: {:?}", (pts)); + pts = video_frame.timestamp as u64 - time; - let buff_size = (video_frame.yres * video_frame.line_stride_in_bytes) as usize; - let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); - { - let vec = Vec::from_raw_parts(video_frame.p_data as *mut u8, buff_size, buff_size); - // Newtek NDI yields times in 100ns intervals since the Unix Time - let pts: gst::ClockTime = (pts * 100).into(); + gst_log!( + self.cat, + obj: element, + "Calculated pts for video frame: {:?}", + (pts) + ); - let duration: gst::ClockTime = (((f64::from(video_frame.frame_rate_D) + let buff_size = (video_frame.yres * video_frame.line_stride_in_bytes) as usize; + let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); + { + let vec = Vec::from_raw_parts(video_frame.p_data as *mut u8, buff_size, buff_size); + // Newtek NDI yields times in 100ns intervals since the Unix Time + let pts: gst::ClockTime = (pts * 100).into(); + + let duration: gst::ClockTime = (((f64::from(video_frame.frame_rate_D) / f64::from(video_frame.frame_rate_N)) * 1_000_000_000.0) as u64) .into(); - let buffer = buffer.get_mut().unwrap(); + let buffer = buffer.get_mut().unwrap(); - if ndi_struct.start_pts == gst::ClockTime(Some(0)) { - ndi_struct.start_pts = + if ndi_struct.start_pts == gst::ClockTime(Some(0)) { + ndi_struct.start_pts = element.get_clock().unwrap().get_time() - element.get_base_time(); - } - - buffer.set_pts(pts + ndi_struct.start_pts); - buffer.set_duration(duration); - buffer.set_offset(timestamp_data.offset); - timestamp_data.offset += 1; - buffer.set_offset_end(timestamp_data.offset); - buffer.copy_from_slice(0, &vec).unwrap(); } - gst_log!(self.cat, obj: element, "Produced buffer {:?}", buffer); - - Ok(buffer) + buffer.set_pts(pts + ndi_struct.start_pts); + buffer.set_duration(duration); + buffer.set_offset(timestamp_data.offset); + timestamp_data.offset += 1; + buffer.set_offset_end(timestamp_data.offset); + buffer.copy_from_slice(0, &vec).unwrap(); } + + gst_log!(self.cat, obj: element, "Produced buffer {:?}", buffer); + + Ok(buffer) } } +} - pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { - gst::Element::register(plugin, "ndivideosrc", 0, NdiVideoSrc::get_type()) - } +pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { + gst::Element::register(plugin, "ndivideosrc", 0, NdiVideoSrc::get_type()) +} From c1eecbc5c481d5f06be28ecf723ae5f077a646b9 Mon Sep 17 00:00:00 2001 From: o-reo Date: Tue, 23 Apr 2019 16:17:29 +0200 Subject: [PATCH 109/199] fix element register due to gstreamer rs update --- src/ndiaudiosrc.rs | 2 +- src/ndivideosrc.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index 9c2ebcfa..1a3342a3 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -544,5 +544,5 @@ impl BaseSrcImpl for NdiAudioSrc { } pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { - gst::Element::register(plugin, "ndiaudiosrc", 0, NdiAudioSrc::get_type()) + gst::Element::register(Some(plugin), "ndiaudiosrc", 0, NdiAudioSrc::get_type()) } diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index 50192083..378fab5b 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -533,5 +533,5 @@ impl BaseSrcImpl for NdiVideoSrc { } pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { - gst::Element::register(plugin, "ndivideosrc", 0, NdiVideoSrc::get_type()) + gst::Element::register(Some(plugin), "ndivideosrc", 0, NdiVideoSrc::get_type()) } From 81cde09641fe0b4858a4cdde1b2c06a63ea6d3b3 Mon Sep 17 00:00:00 2001 From: o-reo Date: Thu, 16 May 2019 15:08:10 +0200 Subject: [PATCH 110/199] compile: freeze gstreamer-rs dependencies --- Cargo.toml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e8a4c85b..3262fa13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,11 +6,11 @@ repository = "https://github.com/teltek/gst-plugin-ndi" license = "LGPL" [dependencies] -glib = { git = "https://github.com/gtk-rs/glib", features = ["subclassing"] } -gstreamer = { git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["subclassing"] } -gstreamer-base = { git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["subclassing"] } -gstreamer-video = { git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" } -gstreamer-audio = { git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" } +glib = { version = "0.7.1", features = ["subclassing"] } +gstreamer = { version = "0.13.0", features = ["subclassing"] } +gstreamer-base = { version = "0.13.0", features = ["subclassing"] } +gstreamer-audio = "0.13.0" +gstreamer-video = "0.13.0" lazy_static = "1.1.0" byte-slice-cast = "0.2.0" From 28265f1151908d5f1716001fc3e3b96e50b747aa Mon Sep 17 00:00:00 2001 From: rubenrua Date: Tue, 25 Jun 2019 18:20:50 +0200 Subject: [PATCH 111/199] Update to the new version of the GStreamer Rust bindings, 0.14.0 https://gstreamer.freedesktop.org/news/#2019-06-24T20:00:00Z --- .travis.yml | 2 +- Cargo.toml | 10 +++++----- src/lib.rs | 2 +- src/ndiaudiosrc.rs | 11 ++++++++--- src/ndivideosrc.rs | 11 ++++++++--- 5 files changed, 23 insertions(+), 13 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2f892217..691885e9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,5 +26,5 @@ install: - rustup component add clippy-preview script: - cargo fmt -- --check - - touch ./src/*.rs && cargo clippy -- -A cast_ptr_alignment -A new_ret_no_self + - touch ./src/*.rs && cargo clippy -- -A clippy::cast_ptr_alignment -A clippy::new_ret_no_self - cargo build \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 3262fa13..aa2239cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,11 +6,11 @@ repository = "https://github.com/teltek/gst-plugin-ndi" license = "LGPL" [dependencies] -glib = { version = "0.7.1", features = ["subclassing"] } -gstreamer = { version = "0.13.0", features = ["subclassing"] } -gstreamer-base = { version = "0.13.0", features = ["subclassing"] } -gstreamer-audio = "0.13.0" -gstreamer-video = "0.13.0" +glib = { version = "0.8.0", features = ["subclassing"] } +gstreamer = { version = "0.14.0", features = ["subclassing"] } +gstreamer-base = { version = "0.14.0", features = ["subclassing"] } +gstreamer-audio = "0.14.0" +gstreamer-video = "0.14.0" lazy_static = "1.1.0" byte-slice-cast = "0.2.0" diff --git a/src/lib.rs b/src/lib.rs index 272d2fb1..7037fe2d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -254,7 +254,7 @@ fn stop_ndi(cat: gst::DebugCategory, element: &gst_base::BaseSrc, id: i8) -> boo } gst_plugin_define!( - "ndi", + ndi, "NewTek NDI Plugin", plugin_init, "1.0.0", diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index 1a3342a3..d52348c5 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -111,7 +111,7 @@ impl ObjectSubclass for NdiAudioSrc { cat: gst::DebugCategory::new( "ndiaudiosrc", gst::DebugColorFlags::empty(), - "NewTek NDI Audio Source", + Some("NewTek NDI Audio Source"), ), settings: Mutex::new(Default::default()), state: Mutex::new(Default::default()), @@ -297,7 +297,7 @@ impl BaseSrcImpl for NdiAudioSrc { fn set_caps( &self, element: &gst_base::BaseSrc, - caps: &gst::CapsRef, + caps: &gst::Caps, ) -> Result<(), gst::LoggableError> { let info = match gst_audio::AudioInfo::from_caps(caps) { None => { @@ -544,5 +544,10 @@ impl BaseSrcImpl for NdiAudioSrc { } pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { - gst::Element::register(Some(plugin), "ndiaudiosrc", 0, NdiAudioSrc::get_type()) + gst::Element::register( + Some(plugin), + "ndiaudiosrc", + gst::Rank::None, + NdiAudioSrc::get_type(), + ) } diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index 378fab5b..0b31b2f2 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -112,7 +112,7 @@ impl ObjectSubclass for NdiVideoSrc { cat: gst::DebugCategory::new( "ndivideosrc", gst::DebugColorFlags::empty(), - "NewTek NDI Video Source", + Some("NewTek NDI Video Source"), ), settings: Mutex::new(Default::default()), state: Mutex::new(Default::default()), @@ -305,7 +305,7 @@ impl BaseSrcImpl for NdiVideoSrc { fn set_caps( &self, element: &gst_base::BaseSrc, - caps: &gst::CapsRef, + caps: &gst::Caps, ) -> Result<(), gst::LoggableError> { let info = match gst_video::VideoInfo::from_caps(caps) { None => { @@ -533,5 +533,10 @@ impl BaseSrcImpl for NdiVideoSrc { } pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { - gst::Element::register(Some(plugin), "ndivideosrc", 0, NdiVideoSrc::get_type()) + gst::Element::register( + Some(plugin), + "ndivideosrc", + gst::Rank::None, + NdiVideoSrc::get_type(), + ) } From 3acd848b5f6c232c4e97cee89eb362956aa326be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 9 Jul 2019 17:51:57 +0300 Subject: [PATCH 112/199] Use gst-plugin-version-helper crate to populate the plugin metadata --- Cargo.toml | 4 ++++ build.rs | 5 +++++ src/lib.rs | 12 ++++++------ 3 files changed, 15 insertions(+), 6 deletions(-) create mode 100644 build.rs diff --git a/Cargo.toml b/Cargo.toml index aa2239cf..b774dde2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ version = "1.0.0" authors = ["Ruben Gonzalez ", "Daniel Vilar "] repository = "https://github.com/teltek/gst-plugin-ndi" license = "LGPL" +description = "NewTek NDI Plugin" [dependencies] glib = { version = "0.8.0", features = ["subclassing"] } @@ -14,6 +15,9 @@ gstreamer-video = "0.14.0" lazy_static = "1.1.0" byte-slice-cast = "0.2.0" +[build-dependencies] +gst-plugin-version-helper = "0.1" + [lib] name = "gstndi" crate-type = ["cdylib"] diff --git a/build.rs b/build.rs new file mode 100644 index 00000000..0d1ddb61 --- /dev/null +++ b/build.rs @@ -0,0 +1,5 @@ +extern crate gst_plugin_version_helper; + +fn main() { + gst_plugin_version_helper::get_info() +} diff --git a/src/lib.rs b/src/lib.rs index 7037fe2d..db9080c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -255,12 +255,12 @@ fn stop_ndi(cat: gst::DebugCategory, element: &gst_base::BaseSrc, id: i8) -> boo gst_plugin_define!( ndi, - "NewTek NDI Plugin", + env!("CARGO_PKG_DESCRIPTION"), plugin_init, - "1.0.0", + concat!(env!("CARGO_PKG_VERSION"), "-", env!("COMMIT_ID")), "LGPL", - "ndi", - "ndi", - "https://github.com/teltek/gst-plugin-ndi", - "2018-04-09" + env!("CARGO_PKG_NAME"), + env!("CARGO_PKG_NAME"), + env!("CARGO_PKG_REPOSITORY"), + env!("BUILD_REL_DATE") ); From 38e39d181e7e7573091f0344dda0e5f4ecde765f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 9 Jul 2019 18:26:42 +0300 Subject: [PATCH 113/199] Use correct library name for linking on Windows --- src/ndisys.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ndisys.rs b/src/ndisys.rs index 30e5ebf4..f0319efe 100644 --- a/src/ndisys.rs +++ b/src/ndisys.rs @@ -2,7 +2,9 @@ use std::ptr; -#[link(name = "ndi")] +#[cfg_attr(all(target_arch = "x86_64", target_os = "windows"), link(name = "Processing.NDI.Lib.x64"))] +#[cfg_attr(all(target_arch = "x86", target_os = "windows"), link(name = "Processing.NDI.Lib.x86"))] +#[cfg_attr(not(any(target_os = "windows", target_os = "macos")), link(name = "ndi"))] extern "C" { pub fn NDIlib_initialize() -> bool; pub fn NDIlib_find_create_v2( From bd1f8cb7d1d793ec2fceb3f6e3dab589082ebbcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 9 Jul 2019 19:16:53 +0300 Subject: [PATCH 114/199] Use NDIlib_find_wait_for_sources() instead of thread::sleep() --- src/lib.rs | 3 +-- src/ndisys.rs | 4 ++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7037fe2d..989a5a23 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,7 +20,6 @@ mod ndivideosrc; // use gst_plugin::base_src::*; use ndisys::*; use std::ffi::{CStr, CString}; -use std::{thread, time}; use std::collections::HashMap; use std::sync::Mutex; @@ -121,7 +120,7 @@ fn connect_ndi( let p_sources; // TODO Sleep 1s to wait for all sources - thread::sleep(time::Duration::from_millis(2000)); + NDIlib_find_wait_for_sources(pNDI_find, 2000); p_sources = NDIlib_find_get_current_sources(pNDI_find, &mut total_sources as *mut u32); // We need at least one source diff --git a/src/ndisys.rs b/src/ndisys.rs index 30e5ebf4..858ed758 100644 --- a/src/ndisys.rs +++ b/src/ndisys.rs @@ -8,6 +8,10 @@ extern "C" { pub fn NDIlib_find_create_v2( p_create_settings: *const NDIlib_find_create_t, ) -> NDIlib_find_instance_t; + pub fn NDIlib_find_wait_for_sources( + p_instance: NDIlib_find_instance_t, + timeout_in_ms: u32, + ) -> bool; pub fn NDIlib_find_get_current_sources( p_instance: NDIlib_find_instance_t, p_no_sources: *mut u32, From 7b8070c55da2de60ead2d9a99dd9bcfecc3aaa07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 9 Jul 2019 19:18:53 +0300 Subject: [PATCH 115/199] Use slice::from_raw_parts instead of Vec::from_raw_parts The latter must only be called on memory that was allocated by Rust for a Vec and will cause crashes depending on the platform otherwise. Also it would free the memory as if a Vec was allocated, which would free memory that we don't own to begin with. --- src/ndivideosrc.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index 0b31b2f2..98d75e9e 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -16,7 +16,7 @@ use gst_video; use std::sync::Mutex; use std::{i32, u32}; -use std::ptr; +use std::{slice, ptr}; use connect_ndi; use ndi_struct; @@ -502,7 +502,7 @@ impl BaseSrcImpl for NdiVideoSrc { let buff_size = (video_frame.yres * video_frame.line_stride_in_bytes) as usize; let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); { - let vec = Vec::from_raw_parts(video_frame.p_data as *mut u8, buff_size, buff_size); + let data = slice::from_raw_parts(video_frame.p_data as *mut u8, buff_size); // Newtek NDI yields times in 100ns intervals since the Unix Time let pts: gst::ClockTime = (pts * 100).into(); @@ -522,7 +522,7 @@ impl BaseSrcImpl for NdiVideoSrc { buffer.set_offset(timestamp_data.offset); timestamp_data.offset += 1; buffer.set_offset_end(timestamp_data.offset); - buffer.copy_from_slice(0, &vec).unwrap(); + buffer.copy_from_slice(0, data).unwrap(); } gst_log!(self.cat, obj: element, "Produced buffer {:?}", buffer); From b41d5839d43c2681cc71265d092539a09ea5bcf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 9 Jul 2019 19:21:57 +0300 Subject: [PATCH 116/199] Always free video frames with NDIlib_recv_free_video_v2() after last use And not potentially before or not at all. --- src/ndivideosrc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index 98d75e9e..480d450d 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -476,7 +476,6 @@ impl BaseSrcImpl for NdiVideoSrc { } if time >= (video_frame.timestamp as u64) { - NDIlib_recv_free_video_v2(pNDI_recv, &video_frame); gst_debug!(self.cat, obj: element, "Frame timestamp ({:?}) is lower than received in the first frame from NDI ({:?}), so skiping...", (video_frame.timestamp as u64), time); } else { skip_frame = false; @@ -526,6 +525,7 @@ impl BaseSrcImpl for NdiVideoSrc { } gst_log!(self.cat, obj: element, "Produced buffer {:?}", buffer); + NDIlib_recv_free_video_v2(pNDI_recv, &video_frame); Ok(buffer) } From c483270f27de01944a62a413ec1b0c743f9f9e40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 9 Jul 2019 19:24:40 +0300 Subject: [PATCH 117/199] Never return gst::FlowError::CustomError outside the element It's for internal-usage and must be converted to a normal error at the boundary to the base classes. --- src/ndiaudiosrc.rs | 2 +- src/ndivideosrc.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index d52348c5..a669b206 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -462,7 +462,7 @@ impl BaseSrcImpl for NdiAudioSrc { continue; } gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type none or error received, assuming that the source closed the stream...."]); - return Err(gst::FlowError::CustomError); + return Err(gst::FlowError::Error); } else if frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none && _settings.loss_threshold == 0 { diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index 0b31b2f2..a24f4457 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -462,7 +462,7 @@ impl BaseSrcImpl for NdiVideoSrc { continue; } gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type none or error received, assuming that the source closed the stream...."]); - return Err(gst::FlowError::CustomError); + return Err(gst::FlowError::Error); } else if frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none && _settings.loss_threshold == 0 { From a0c918de713a236386fe1b68cfeb7c1ae64d5b0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 9 Jul 2019 19:24:08 +0300 Subject: [PATCH 118/199] Never send an empty buffer but simply retry Sending an empty buffer will cause downstream to fail as it has the wrong size. --- src/ndivideosrc.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index 9b691560..912939d4 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -469,10 +469,10 @@ impl BaseSrcImpl for NdiVideoSrc { gst_debug!( self.cat, obj: element, - "No video frame received, sending empty buffer" + "No video frame received, retry" ); - let buffer = gst::Buffer::with_size(0).unwrap(); - return Ok(buffer); + count_frame_none += 1; + continue; } if time >= (video_frame.timestamp as u64) { From 48f8c498fa35d85cd0a9c16f1df28afebad5d0b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Thu, 11 Jul 2019 20:35:43 +0300 Subject: [PATCH 119/199] Wrap unsafe NDI C API in safe wrappers --- src/lib.rs | 3 +- src/ndi.rs | 605 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/ndisys.rs | 6 +- 3 files changed, 612 insertions(+), 2 deletions(-) create mode 100644 src/ndi.rs diff --git a/src/lib.rs b/src/lib.rs index a38bef82..6c41b2ee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,8 +13,9 @@ extern crate gstreamer_video as gst_video; extern crate lazy_static; extern crate byte_slice_cast; -mod ndiaudiosrc; pub mod ndisys; +pub mod ndi; +mod ndiaudiosrc; mod ndivideosrc; // use gst_plugin::base_src::*; diff --git a/src/ndi.rs b/src/ndi.rs new file mode 100644 index 00000000..08acef36 --- /dev/null +++ b/src/ndi.rs @@ -0,0 +1,605 @@ +use ndisys::*; +use std::ffi; +use std::mem; +use std::ptr; + +pub fn initialize() -> bool { + unsafe { NDIlib_initialize() } +} + +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))) + } + } + } +} + +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( + ptr::NonNull::new(sources_ptr.add(i as usize) as *mut _).unwrap(), + self, + )); + } + + sources + } + } +} + +impl Drop for FindInstance { + fn drop(&mut self) { + unsafe { + NDIlib_find_destroy(self.0.as_mut()); + } + } +} + +pub struct Source<'a>(ptr::NonNull, &'a FindInstance); + +unsafe impl<'a> Send for Source<'a> {} + +impl<'a> Source<'a> { + pub fn ndi_name(&self) -> &str { + unsafe { + assert!(!self.0.as_ref().p_ndi_name.is_null()); + ffi::CStr::from_ptr(self.0.as_ref().p_ndi_name) + .to_str() + .unwrap() + } + } + + pub fn ip_address(&self) -> &str { + unsafe { + assert!(!self.0.as_ref().p_ip_address.is_null()); + ffi::CStr::from_ptr(self.0.as_ref().p_ip_address) + .to_str() + .unwrap() + } + } +} + +pub struct RecvBuilder<'a> { + source_to_connect_to: &'a Source<'a>, + allow_video_fields: bool, + bandwidth: NDIlib_recv_bandwidth_e, + color_format: NDIlib_recv_color_format_e, + ndi_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_name = ffi::CString::new(self.ndi_name).unwrap(); + let ptr = NDIlib_recv_create_v3(&NDIlib_recv_create_v3_t { + source_to_connect_to: NDIlib_source_t { + p_ndi_name: self.source_to_connect_to.0.as_ref().p_ndi_name, + p_ip_address: self.source_to_connect_to.0.as_ref().p_ip_address, + }, + allow_video_fields: self.allow_video_fields, + bandwidth: self.bandwidth, + color_format: self.color_format, + p_ndi_name: ndi_name.as_ptr(), + }); + + if ptr.is_null() { + None + } else { + Some(RecvInstance(ptr::NonNull::new_unchecked(ptr))) + } + } + } +} + +pub struct RecvInstance(ptr::NonNull<::std::os::raw::c_void>); +unsafe impl Send for RecvInstance {} + +impl RecvInstance { + pub fn builder<'a>(source_to_connect_to: &'a Source, ndi_name: &'a str) -> RecvBuilder<'a> { + RecvBuilder { + source_to_connect_to, + allow_video_fields: true, + bandwidth: NDIlib_recv_bandwidth_e::NDIlib_recv_bandwidth_highest, + color_format: NDIlib_recv_color_format_e::NDIlib_recv_color_format_UYVY_BGRA, + ndi_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 recv_capture( + &self, + video: bool, + audio: bool, + metadata: bool, + timeout_in_ms: u32, + ) -> Result, ()> { + unsafe { + let mut video_frame = mem::zeroed(); + let mut audio_frame = mem::zeroed(); + let mut metadata_frame = mem::zeroed(); + + let res = NDIlib_recv_capture_v2( + self.0.as_ptr(), + if video { + &mut video_frame + } else { + ptr::null_mut() + }, + if audio { + &mut audio_frame + } else { + ptr::null_mut() + }, + if metadata { + &mut metadata_frame + } else { + ptr::null_mut() + }, + timeout_in_ms, + ); + + match res { + NDIlib_frame_type_e::NDIlib_frame_type_audio => { + assert!(audio); + Ok(Some(Frame::Audio(AudioFrame::Borrowed(audio_frame, self)))) + } + NDIlib_frame_type_e::NDIlib_frame_type_video => { + assert!(video); + Ok(Some(Frame::Video(VideoFrame::Borrowed(video_frame, self)))) + } + NDIlib_frame_type_e::NDIlib_frame_type_metadata => { + assert!(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 _) } + } +} + +pub struct Tally(NDIlib_tally_t); +unsafe impl Send for Tally {} + +impl Default for Tally { + fn default() -> Self { + Self(NDIlib_tally_t { + on_program: false, + 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 + } +} + +pub enum Frame<'a> { + Video(VideoFrame<'a>), + Audio(AudioFrame<'a>), + Metadata(MetadataFrame<'a>), +} + +pub enum VideoFrame<'a> { + //Owned(NDIlib_video_frame_v2_t, Option, Option>), + Borrowed(NDIlib_video_frame_v2_t, &'a RecvInstance), +} + +impl<'a> VideoFrame<'a> { + pub fn xres(&self) -> i32 { + match self { + VideoFrame::Borrowed(ref frame, _) => frame.xres, + } + } + + pub fn yres(&self) -> i32 { + match self { + VideoFrame::Borrowed(ref frame, _) => frame.yres, + } + } + + pub fn fourcc(&self) -> NDIlib_FourCC_type_e { + match self { + VideoFrame::Borrowed(ref frame, _) => frame.FourCC, + } + } + + pub fn frame_rate(&self) -> (i32, i32) { + match self { + VideoFrame::Borrowed(ref frame, _) => (frame.frame_rate_N, frame.frame_rate_D), + } + } + + pub fn picture_aspect_ratio(&self) -> f32 { + match self { + VideoFrame::Borrowed(ref frame, _) => frame.picture_aspect_ratio, + } + } + + pub fn frame_format_type(&self) -> NDIlib_frame_format_type_e { + match self { + VideoFrame::Borrowed(ref frame, _) => frame.frame_format_type, + } + } + + pub fn timecode(&self) -> i64 { + match self { + VideoFrame::Borrowed(ref frame, _) => frame.timecode, + } + } + + pub fn data(&self) -> &[u8] { + unsafe { + use std::slice; + match self { + VideoFrame::Borrowed(ref frame, _) => slice::from_raw_parts( + frame.p_data as *const u8, + (frame.yres * frame.line_stride_in_bytes) as usize, + ), + } + } + } + + pub fn line_stride_in_bytes(&self) -> i32 { + match self { + VideoFrame::Borrowed(ref frame, _) => frame.line_stride_in_bytes, + } + } + + pub fn metadata(&self) -> Option<&str> { + unsafe { + match self { + VideoFrame::Borrowed(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::Borrowed(ref frame, _) => frame.timestamp, + } + } + + pub fn as_ptr(&self) -> *const NDIlib_video_frame_v2_t { + match self { + VideoFrame::Borrowed(ref frame, _) => frame, + } + } +} + +impl<'a> Drop for VideoFrame<'a> { + #[allow(irrefutable_let_patterns)] + fn drop(&mut self) { + if let VideoFrame::Borrowed(ref frame, ref recv) = *self { + unsafe { + NDIlib_recv_free_video_v2(recv.0.as_ptr() as *mut _, frame); + } + } + } +} + +pub enum AudioFrame<'a> { + //Owned(NDIlib_audio_frame_v2_t, Option, Option>), + Borrowed(NDIlib_audio_frame_v2_t, &'a RecvInstance), +} + +impl<'a> AudioFrame<'a> { + pub fn sample_rate(&self) -> i32 { + match self { + AudioFrame::Borrowed(ref frame, _) => frame.sample_rate, + } + } + + pub fn no_channels(&self) -> i32 { + match self { + AudioFrame::Borrowed(ref frame, _) => frame.no_channels, + } + } + + pub fn no_samples(&self) -> i32 { + match self { + AudioFrame::Borrowed(ref frame, _) => frame.no_samples, + } + } + + pub fn timecode(&self) -> i64 { + match self { + AudioFrame::Borrowed(ref frame, _) => frame.timecode, + } + } + + pub fn data(&self) -> &[u8] { + unsafe { + use std::slice; + match self { + AudioFrame::Borrowed(ref frame, _) => slice::from_raw_parts( + frame.p_data as *const u8, + (frame.no_samples * frame.channel_stride_in_bytes) as usize, + ), + } + } + } + + pub fn channel_stride_in_bytes(&self) -> i32 { + match self { + AudioFrame::Borrowed(ref frame, _) => frame.channel_stride_in_bytes, + } + } + + pub fn metadata(&self) -> Option<&str> { + unsafe { + match self { + AudioFrame::Borrowed(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::Borrowed(ref frame, _) => frame.timestamp, + } + } + + pub fn as_ptr(&self) -> *const NDIlib_audio_frame_v2_t { + match self { + AudioFrame::Borrowed(ref frame, _) => frame, + } + } + + pub fn copy_to_interleaved_16s(&self, data: &mut [i16]) { + assert_eq!( + data.len(), + (self.no_samples() * self.no_channels() * 2) as usize + ); + + let mut dst = NDIlib_audio_frame_interleaved_16s_t { + sample_rate: self.sample_rate(), + no_channels: self.no_channels(), + no_samples: self.no_samples(), + timecode: self.timecode(), + reference_level: 0, + p_data: data.as_mut_ptr(), + }; + + unsafe { + NDIlib_util_audio_to_interleaved_16s_v2(self.as_ptr(), &mut dst); + } + } +} + +impl<'a> Drop for AudioFrame<'a> { + #[allow(irrefutable_let_patterns)] + fn drop(&mut self) { + if let AudioFrame::Borrowed(ref frame, ref recv) = *self { + unsafe { + NDIlib_recv_free_audio_v2(recv.0.as_ptr() as *mut _, frame); + } + } + } +} + +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 frame, ref recv) = *self { + unsafe { + NDIlib_recv_free_metadata(recv.0.as_ptr() as *mut _, frame); + } + } + } +} diff --git a/src/ndisys.rs b/src/ndisys.rs index 3701b4b7..13c2267e 100644 --- a/src/ndisys.rs +++ b/src/ndisys.rs @@ -21,7 +21,7 @@ extern "C" { pub fn NDIlib_recv_create_v3( p_create_settings: *const NDIlib_recv_create_v3_t, ) -> NDIlib_recv_instance_t; - pub fn NDIlib_find_destroy(p_instance: NDIlib_recv_instance_t); + pub fn NDIlib_find_destroy(p_instance: NDIlib_find_instance_t); pub fn NDIlib_recv_destroy(p_instance: NDIlib_recv_instance_t); pub fn NDIlib_destroy(); pub fn NDIlib_recv_set_tally( @@ -47,6 +47,10 @@ extern "C" { p_instance: NDIlib_recv_instance_t, p_audio_data: *const NDIlib_audio_frame_v2_t, ); + pub fn NDIlib_recv_free_metadata( + p_instance: NDIlib_recv_instance_t, + p_metadata: *const NDIlib_metadata_frame_t, + ); } pub type NDIlib_find_instance_t = *mut ::std::os::raw::c_void; From 861d216eed11037dc7f4d10a8409bc4f04ee2a50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Thu, 11 Jul 2019 21:56:30 +0300 Subject: [PATCH 120/199] Port audio/video source to the safe NDI SDK wrappers This also fixes various memory unsafety issues. And add lots of FIXME comments for code that is currently wrong. --- src/lib.rs | 211 +++++++++++++++---------------------- src/ndi.rs | 14 ++- src/ndiaudiosrc.rs | 251 ++++++++++++++++++++++----------------------- src/ndivideosrc.rs | 231 ++++++++++++++++++++--------------------- 4 files changed, 333 insertions(+), 374 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6c41b2ee..fee28102 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,3 @@ -#![allow(non_camel_case_types, non_upper_case_globals, non_snake_case)] - #[macro_use] extern crate glib; #[macro_use] @@ -18,57 +16,59 @@ pub mod ndi; mod ndiaudiosrc; mod ndivideosrc; -// use gst_plugin::base_src::*; use ndisys::*; -use std::ffi::{CStr, CString}; +use ndi::*; use std::collections::HashMap; use std::sync::Mutex; - -use gst::GstObjectExt; +use std::sync::atomic::{AtomicUsize, Ordering}; fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { + if !ndi::initialize() { + return Err(glib_bool_error!("Cannot initialize NDI")); + } + ndivideosrc::register(plugin)?; ndiaudiosrc::register(plugin)?; Ok(()) } -struct ndi_receiver_info { +struct ReceiverInfo { + id: usize, stream_name: String, ip: String, video: bool, audio: bool, - ndi_instance: NdiInstance, + ndi_instance: RecvInstance, initial_timestamp: u64, - id: i8, } struct Ndi { start_pts: gst::ClockTime, } -static mut ndi_struct: Ndi = Ndi { +static mut NDI_STRUCT: Ndi = Ndi { start_pts: gst::ClockTime(Some(0)), }; lazy_static! { - static ref hashmap_receivers: Mutex> = { + static ref HASHMAP_RECEIVERS: Mutex> = { let m = HashMap::new(); Mutex::new(m) }; } -static mut id_receiver: i8 = 0; +static ID_RECEIVER: AtomicUsize = AtomicUsize::new(0); fn connect_ndi( cat: gst::DebugCategory, element: &gst_base::BaseSrc, ip: &str, stream_name: &str, -) -> i8 { +) -> Option { gst_debug!(cat, obj: element, "Starting NDI connection..."); - let mut receivers = hashmap_receivers.lock().unwrap(); + let mut receivers = HASHMAP_RECEIVERS.lock().unwrap(); let mut audio = false; let mut video = false; @@ -92,143 +92,102 @@ fn connect_ndi( } else { val.audio = audio; } - return val.id; + return Some(val.id); } } } - unsafe { - if !NDIlib_initialize() { - gst_element_error!( - element, - gst::CoreError::Negotiation, - ["Cannot run NDI: NDIlib_initialize error"] - ); - return 0; - } - let NDI_find_create_desc: NDIlib_find_create_t = Default::default(); - let pNDI_find = NDIlib_find_create_v2(&NDI_find_create_desc); - if pNDI_find.is_null() { + let mut find = match FindInstance::builder().build() { + None => { gst_element_error!( element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_find_create_v2 error"] ); - return 0; - } + return None; + }, + Some(find) => find, + }; - let mut total_sources: u32 = 0; - let p_sources; + // TODO Sleep 1s to wait for all sources + find.wait_for_sources(2000); - // TODO Sleep 1s to wait for all sources - NDIlib_find_wait_for_sources(pNDI_find, 2000); - p_sources = NDIlib_find_get_current_sources(pNDI_find, &mut total_sources as *mut u32); + let sources = find.get_current_sources(); - // We need at least one source - if p_sources.is_null() { - gst_element_error!( - element, - gst::CoreError::Negotiation, - ["Error getting NDIlib_find_get_current_sources"] - ); - return 0; - } - - let mut no_source: isize = -1; - for i in 0..total_sources as isize { - if CStr::from_ptr((*p_sources.offset(i)).p_ndi_name) - .to_string_lossy() - .into_owned() - == stream_name - || CStr::from_ptr((*p_sources.offset(i)).p_ip_address) - .to_string_lossy() - .into_owned() - == ip - { - no_source = i; - break; - } - } - if no_source == -1 { - gst_element_error!(element, gst::ResourceError::OpenRead, ["Stream not found"]); - return 0; - } - - gst_debug!( - cat, - obj: element, - "Total sources in network {}: Connecting to NDI source with name '{}' and address '{}'", - total_sources, - CStr::from_ptr((*p_sources.offset(no_source)).p_ndi_name) - .to_string_lossy() - .into_owned(), - CStr::from_ptr((*p_sources.offset(no_source)).p_ip_address) - .to_string_lossy() - .into_owned() + // We need at least one source + if sources.is_empty() { + gst_element_error!( + element, + gst::CoreError::Negotiation, + ["Error getting NDIlib_find_get_current_sources"] ); + return None; + } - let source = *p_sources.offset(no_source); + let source = sources.iter().find(|s| { + s.ndi_name() == stream_name || s.ip_address() == ip + }); - let source_ip = CStr::from_ptr(source.p_ip_address) - .to_string_lossy() - .into_owned(); - let source_name = CStr::from_ptr(source.p_ndi_name) - .to_string_lossy() - .into_owned(); + let source = match source { + None => { + gst_element_error!(element, gst::ResourceError::OpenRead, ["Stream not found"]); + return None; + }, + Some(source) => source, + }; - let p_ndi_name = CString::new("Galicaster NDI Receiver").unwrap(); - let NDI_recv_create_desc = NDIlib_recv_create_v3_t { - source_to_connect_to: source, - p_ndi_name: p_ndi_name.as_ptr(), - ..Default::default() - }; + gst_debug!( + cat, + obj: element, + "Total sources in network {}: Connecting to NDI source with name '{}' and address '{}'", + sources.len(), + source.ndi_name(), + source.ip_address(), + ); - let pNDI_recv = NDIlib_recv_create_v3(&NDI_recv_create_desc); - if pNDI_recv.is_null() { + let recv = RecvInstance::builder(&source, "Galicaster NDI Receiver") + .bandwidth(NDIlib_recv_bandwidth_e::NDIlib_recv_bandwidth_highest) + .color_format(NDIlib_recv_color_format_e::NDIlib_recv_color_format_UYVY_BGRA) + .allow_video_fields(true) + .build(); + let recv = match recv { + None => { gst_element_error!( element, gst::CoreError::Negotiation, ["Cannot run NDI: NDIlib_recv_create_v3 error"] ); - return 0; - } + return None; + }, + Some(recv) => recv, + }; - NDIlib_find_destroy(pNDI_find); + recv.set_tally(&Tally::default()); - let tally_state: NDIlib_tally_t = Default::default(); - NDIlib_recv_set_tally(pNDI_recv, &tally_state); + let enable_hw_accel = MetadataFrame::new(0, Some("")); + recv.send_metadata(&enable_hw_accel); - let data = CString::new("").unwrap(); - let enable_hw_accel = NDIlib_metadata_frame_t { - length: data.to_bytes().len() as i32, - timecode: 0, - p_data: data.as_ptr(), - }; + let id_receiver = ID_RECEIVER.fetch_add(1, Ordering::SeqCst); + receivers.insert( + id_receiver, + ReceiverInfo { + stream_name: source.ndi_name().to_owned(), + ip: source.ip_address().to_owned(), + video, + audio, + ndi_instance: recv, + initial_timestamp: 0, + id: id_receiver, + }, + ); - NDIlib_recv_send_metadata(pNDI_recv, &enable_hw_accel); - - id_receiver += 1; - receivers.insert( - id_receiver, - ndi_receiver_info { - stream_name: source_name.clone(), - ip: source_ip.clone(), - video, - audio, - ndi_instance: NdiInstance { recv: pNDI_recv }, - initial_timestamp: 0, - id: id_receiver, - }, - ); - - gst_debug!(cat, obj: element, "Started NDI connection"); - id_receiver - } + gst_debug!(cat, obj: element, "Started NDI connection"); + Some(id_receiver) } -fn stop_ndi(cat: gst::DebugCategory, element: &gst_base::BaseSrc, id: i8) -> bool { +fn stop_ndi(cat: gst::DebugCategory, element: &gst_base::BaseSrc, id: usize) -> bool { gst_debug!(cat, obj: element, "Closing NDI connection..."); - let mut receivers = hashmap_receivers.lock().unwrap(); + let mut receivers = HASHMAP_RECEIVERS.lock().unwrap(); { let val = receivers.get_mut(&id).unwrap(); if val.video && val.audio { @@ -239,14 +198,6 @@ fn stop_ndi(cat: gst::DebugCategory, element: &gst_base::BaseSrc, id: i8) -> boo } return true; } - - let recv = &val.ndi_instance; - let pNDI_recv = recv.recv; - unsafe { - NDIlib_recv_destroy(pNDI_recv); - // ndi_struct.recv = None; - NDIlib_destroy(); - } } receivers.remove(&id); gst_debug!(cat, obj: element, "Closed NDI connection"); diff --git a/src/ndi.rs b/src/ndi.rs index 08acef36..355b0362 100644 --- a/src/ndi.rs +++ b/src/ndi.rs @@ -7,6 +7,7 @@ pub fn initialize() -> bool { unsafe { NDIlib_initialize() } } +#[derive(Debug)] pub struct FindBuilder<'a> { show_local_sources: bool, groups: Option<&'a str>, @@ -67,6 +68,7 @@ impl<'a> FindBuilder<'a> { } } +#[derive(Debug)] pub struct FindInstance(ptr::NonNull<::std::os::raw::c_void>); unsafe impl Send for FindInstance {} @@ -111,6 +113,7 @@ impl Drop for FindInstance { } } +#[derive(Debug)] pub struct Source<'a>(ptr::NonNull, &'a FindInstance); unsafe impl<'a> Send for Source<'a> {} @@ -135,6 +138,7 @@ impl<'a> Source<'a> { } } +#[derive(Debug)] pub struct RecvBuilder<'a> { source_to_connect_to: &'a Source<'a>, allow_video_fields: bool, @@ -185,6 +189,7 @@ impl<'a> RecvBuilder<'a> { } } +#[derive(Debug)] pub struct RecvInstance(ptr::NonNull<::std::os::raw::c_void>); unsafe impl Send for RecvInstance {} @@ -207,7 +212,7 @@ impl RecvInstance { unsafe { NDIlib_recv_send_metadata(self.0.as_ptr(), metadata.as_ptr()) } } - pub fn recv_capture( + pub fn capture( &self, video: bool, audio: bool, @@ -268,6 +273,7 @@ impl Drop for RecvInstance { } } +#[derive(Debug)] pub struct Tally(NDIlib_tally_t); unsafe impl Send for Tally {} @@ -297,12 +303,14 @@ impl Tally { } } +#[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>), Borrowed(NDIlib_video_frame_v2_t, &'a RecvInstance), @@ -407,6 +415,7 @@ impl<'a> Drop for VideoFrame<'a> { } } +#[derive(Debug)] pub enum AudioFrame<'a> { //Owned(NDIlib_audio_frame_v2_t, Option, Option>), Borrowed(NDIlib_audio_frame_v2_t, &'a RecvInstance), @@ -484,7 +493,7 @@ impl<'a> AudioFrame<'a> { pub fn copy_to_interleaved_16s(&self, data: &mut [i16]) { assert_eq!( data.len(), - (self.no_samples() * self.no_channels() * 2) as usize + (self.no_samples() * self.no_channels()) as usize ); let mut dst = NDIlib_audio_frame_interleaved_16s_t { @@ -513,6 +522,7 @@ impl<'a> Drop for AudioFrame<'a> { } } +#[derive(Debug)] pub enum MetadataFrame<'a> { Owned(NDIlib_metadata_frame_t, Option), Borrowed(NDIlib_metadata_frame_t, &'a RecvInstance), diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index a669b206..310dda07 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -1,5 +1,3 @@ -#![allow(non_camel_case_types, non_upper_case_globals, non_snake_case)] - use glib; use glib::subclass; use glib::subclass::prelude::*; @@ -14,22 +12,22 @@ use gst_base::subclass::prelude::*; use std::sync::Mutex; use std::{i32, u32}; -use std::ptr; - use connect_ndi; -use ndi_struct; -use ndisys::*; use stop_ndi; +use ndi::*; + +use NDI_STRUCT; +use HASHMAP_RECEIVERS; use byte_slice_cast::AsMutSliceOf; -use hashmap_receivers; #[derive(Debug, Clone)] struct Settings { stream_name: String, ip: String, loss_threshold: u32, - id_receiver: i8, + // FIXME: Should be in State + id_receiver: Option, latency: Option, } @@ -39,7 +37,7 @@ impl Default for Settings { stream_name: String::from("Fixed ndi stream name"), ip: String::from(""), loss_threshold: 5, - id_receiver: 0, + id_receiver: None, latency: None, } } @@ -248,25 +246,23 @@ impl ElementImpl for NdiAudioSrc { transition: gst::StateChange, ) -> Result { if transition == gst::StateChange::PausedToPlaying { - let mut receivers = hashmap_receivers.lock().unwrap(); + let mut receivers = HASHMAP_RECEIVERS.lock().unwrap(); let settings = self.settings.lock().unwrap(); - let receiver = receivers.get_mut(&settings.id_receiver).unwrap(); + let receiver = receivers.get_mut(&settings.id_receiver.unwrap()).unwrap(); let recv = &receiver.ndi_instance; - let pNDI_recv = recv.recv; - let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); + // FIXME error handling, make interruptable + let audio_frame = + loop { + match recv.capture(false, true, false, 1000) { + Err(_) => unimplemented!(), + Ok(None) => continue, + Ok(Some(Frame::Audio(frame))) => break frame, + _ => unreachable!(), + } + }; - unsafe { - while NDIlib_recv_capture_v2( - pNDI_recv, - ptr::null(), - &audio_frame, - ptr::null(), - 1000, - ) != NDIlib_frame_type_e::NDIlib_frame_type_audio - {} - } gst_debug!( self.cat, obj: element, @@ -274,14 +270,13 @@ impl ElementImpl for NdiAudioSrc { audio_frame ); - if receiver.initial_timestamp <= audio_frame.timestamp as u64 + // FIXME handle unset timestamp + if receiver.initial_timestamp <= audio_frame.timestamp() as u64 || receiver.initial_timestamp == 0 { - receiver.initial_timestamp = audio_frame.timestamp as u64; - } - unsafe { - NDIlib_recv_free_audio_v2(pNDI_recv, &audio_frame); + receiver.initial_timestamp = audio_frame.timestamp() as u64; } + gst_debug!( self.cat, obj: element, @@ -330,7 +325,7 @@ impl BaseSrcImpl for NdiAudioSrc { ); match settings.id_receiver { - 0 => Err(gst_error_msg!( + None => Err(gst_error_msg!( gst::ResourceError::NotFound, ["Could not connect to this source"] )), @@ -342,7 +337,7 @@ impl BaseSrcImpl for NdiAudioSrc { *self.state.lock().unwrap() = Default::default(); let settings = self.settings.lock().unwrap(); - stop_ndi(self.cat, element, settings.id_receiver); + stop_ndi(self.cat, element, settings.id_receiver.unwrap()); // Commented because when adding ndi destroy stopped in this line //*self.state.lock().unwrap() = Default::default(); Ok(()) @@ -372,24 +367,28 @@ impl BaseSrcImpl for NdiAudioSrc { } fn fixate(&self, element: &gst_base::BaseSrc, caps: gst::Caps) -> gst::Caps { - let receivers = hashmap_receivers.lock().unwrap(); + let receivers = HASHMAP_RECEIVERS.lock().unwrap(); let mut settings = self.settings.lock().unwrap(); - let receiver = receivers.get(&settings.id_receiver).unwrap(); + let receiver = receivers.get(&settings.id_receiver.unwrap()).unwrap(); let recv = &receiver.ndi_instance; - let pNDI_recv = recv.recv; - let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); + // FIXME: Should be done in create() and caps be updated as needed - unsafe { - while NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000) - != NDIlib_frame_type_e::NDIlib_frame_type_audio - {} - } + let audio_frame = + loop { + match recv.capture(false, true, false, 1000) { + Err(_) => unimplemented!(), + Ok(None) => continue, + Ok(Some(Frame::Audio(frame))) => break frame, + _ => unreachable!(), + } + }; - let no_samples = audio_frame.no_samples as u64; - let audio_rate = audio_frame.sample_rate; + // FIXME: Why? + let no_samples = audio_frame.no_samples() as u64; + let audio_rate = audio_frame.sample_rate(); settings.latency = gst::SECOND.mul_div_floor(no_samples, audio_rate as u64); let mut caps = gst::Caps::truncate(caps); @@ -397,21 +396,18 @@ impl BaseSrcImpl for NdiAudioSrc { let caps = caps.make_mut(); let s = caps.get_mut_structure(0).unwrap(); s.fixate_field_nearest_int("rate", audio_rate); - s.fixate_field_nearest_int("channels", audio_frame.no_channels); + s.fixate_field_nearest_int("channels", audio_frame.no_channels()); s.fixate_field_str("layout", "interleaved"); s.set_value( "channel-mask", gst::Bitmask::new(gst_audio::AudioChannelPosition::get_fallback_mask( - audio_frame.no_channels as u32, + audio_frame.no_channels() as u32, )) .to_send_value(), ); } let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); - unsafe { - NDIlib_recv_free_audio_v2(pNDI_recv, &audio_frame); - } self.parent_fixate(element, caps) } @@ -434,112 +430,113 @@ impl BaseSrcImpl for NdiAudioSrc { } Some(ref info) => info.clone(), }; - let receivers = hashmap_receivers.lock().unwrap(); + let receivers = HASHMAP_RECEIVERS.lock().unwrap(); - let recv = &receivers.get(&_settings.id_receiver).unwrap().ndi_instance; - let pNDI_recv = recv.recv; + let receiver = &receivers.get(&_settings.id_receiver.unwrap()).unwrap(); + let recv = &receiver.ndi_instance; - let pts: u64; - let audio_frame: NDIlib_audio_frame_v2_t = Default::default(); + let time = receiver.initial_timestamp; - unsafe { - let time = receivers - .get(&_settings.id_receiver) - .unwrap() - .initial_timestamp; + let mut count_frame_none = 0; + let audio_frame = + loop { + // FIXME: make interruptable + let res = + loop { + match recv.capture(false, true, false, 1000) { + Err(_) => break Err(()), + Ok(None) => break Ok(None), + Ok(Some(Frame::Audio(frame))) => break Ok(Some(frame)), + _ => unreachable!(), + } + }; - let mut skip_frame = true; - let mut count_frame_none = 0; - while skip_frame { - let frame_type = - NDIlib_recv_capture_v2(pNDI_recv, ptr::null(), &audio_frame, ptr::null(), 1000); - if (frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none - && _settings.loss_threshold != 0) - || frame_type == NDIlib_frame_type_e::NDIlib_frame_type_error - { + let audio_frame = match res { + Err(_) => { + gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type error received, assuming that the source closed the stream...."]); + return Err(gst::FlowError::Error); + }, + Ok(None) if _settings.loss_threshold != 0 => { if count_frame_none < _settings.loss_threshold { count_frame_none += 1; continue; } - gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type none or error received, assuming that the source closed the stream...."]); + gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type none received, assuming that the source closed the stream...."]); return Err(gst::FlowError::Error); - } else if frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none - && _settings.loss_threshold == 0 - { + }, + Ok(None) => { gst_debug!( self.cat, obj: element, - "No audio frame received, sending empty buffer" + "No audio frame received, retry" ); - let buffer = gst::Buffer::with_size(0).unwrap(); - return Ok(buffer); - } + count_frame_none += 1; + continue; + }, + Ok(Some(frame)) => frame, + }; - if time >= (audio_frame.timestamp as u64) { - NDIlib_recv_free_audio_v2(pNDI_recv, &audio_frame); - gst_debug!(self.cat, obj: element, "Frame timestamp ({:?}) is lower than received in the first frame from NDI ({:?}), so skiping...", (audio_frame.timestamp as u64), time); - } else { - skip_frame = false; - } + if time >= (audio_frame.timestamp() as u64) { + gst_debug!(self.cat, obj: element, "Frame timestamp ({:?}) is lower than received in the first frame from NDI ({:?}), so skiping...", (audio_frame.timestamp() as u64), time); + } else { + break audio_frame; } + }; - gst_log!( - self.cat, - obj: element, - "NDI audio frame received: {:?}", - (audio_frame) - ); + gst_log!( + self.cat, + obj: element, + "NDI audio frame received: {:?}", + audio_frame + ); - pts = audio_frame.timestamp as u64 - time; + let pts = audio_frame.timestamp() as u64 - time; - gst_log!( - self.cat, - obj: element, - "Calculated pts for audio frame: {:?}", - (pts) - ); + gst_log!( + self.cat, + obj: element, + "Calculated pts for audio frame: {:?}", + pts + ); - // We multiply by 2 because is the size in bytes of an i16 variable - let buff_size = (audio_frame.no_samples * 2 * audio_frame.no_channels) as usize; - let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); - { - if ndi_struct.start_pts == gst::ClockTime(Some(0)) { - ndi_struct.start_pts = + // We multiply by 2 because is the size in bytes of an i16 variable + let buff_size = (audio_frame.no_samples() * 2 * audio_frame.no_channels()) as usize; + let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); + { + // FIXME don't use static mut, also this calculation is wrong + unsafe { + if NDI_STRUCT.start_pts == gst::ClockTime(Some(0)) { + NDI_STRUCT.start_pts = element.get_clock().unwrap().get_time() - element.get_base_time(); } - - let buffer = buffer.get_mut().unwrap(); - - // Newtek NDI yields times in 100ns intervals since the Unix Time - let pts: gst::ClockTime = (pts * 100).into(); - buffer.set_pts(pts + ndi_struct.start_pts); - - let duration: gst::ClockTime = (((f64::from(audio_frame.no_samples) - / f64::from(audio_frame.sample_rate)) - * 1_000_000_000.0) as u64) - .into(); - buffer.set_duration(duration); - - buffer.set_offset(timestamp_data.offset); - timestamp_data.offset += audio_frame.no_samples as u64; - buffer.set_offset_end(timestamp_data.offset); - - let mut dst: NDIlib_audio_frame_interleaved_16s_t = Default::default(); - dst.reference_level = 0; - dst.p_data = buffer - .map_writable() - .unwrap() - .as_mut_slice_of::() - .unwrap() - .as_mut_ptr(); - NDIlib_util_audio_to_interleaved_16s_v2(&audio_frame, &mut dst); - NDIlib_recv_free_audio_v2(pNDI_recv, &audio_frame); } - gst_log!(self.cat, obj: element, "Produced buffer {:?}", buffer); + let buffer = buffer.get_mut().unwrap(); - Ok(buffer) + // Newtek NDI yields times in 100ns intervals since the Unix Time + let pts: gst::ClockTime = (pts * 100).into(); + buffer.set_pts(pts + unsafe { NDI_STRUCT.start_pts }); + + let duration: gst::ClockTime = (((f64::from(audio_frame.no_samples()) + / f64::from(audio_frame.sample_rate())) + * 1_000_000_000.0) as u64) + .into(); + buffer.set_duration(duration); + + buffer.set_offset(timestamp_data.offset); + timestamp_data.offset += audio_frame.no_samples() as u64; + buffer.set_offset_end(timestamp_data.offset); + + audio_frame.copy_to_interleaved_16s(buffer + .map_writable() + .unwrap() + .as_mut_slice_of::() + .unwrap()); } + + gst_log!(self.cat, obj: element, "Produced buffer {:?}", buffer); + + Ok(buffer) } } diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index 912939d4..67538fb7 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -1,5 +1,3 @@ -#![allow(non_camel_case_types, non_upper_case_globals, non_snake_case)] - use glib; use glib::subclass; use glib::subclass::prelude::*; @@ -16,21 +14,21 @@ use gst_video; use std::sync::Mutex; use std::{i32, u32}; -use std::{slice, ptr}; +use ndi::*; use connect_ndi; -use ndi_struct; -use ndisys::*; use stop_ndi; -use hashmap_receivers; +use NDI_STRUCT; +use HASHMAP_RECEIVERS; #[derive(Debug, Clone)] struct Settings { stream_name: String, ip: String, loss_threshold: u32, - id_receiver: i8, + // FIXME: should be in state + id_receiver: Option, latency: Option, } @@ -40,7 +38,7 @@ impl Default for Settings { stream_name: String::from("Fixed ndi stream name"), ip: String::from(""), loss_threshold: 5, - id_receiver: 0, + id_receiver: None, latency: None, } } @@ -256,25 +254,23 @@ impl ElementImpl for NdiVideoSrc { transition: gst::StateChange, ) -> Result { if transition == gst::StateChange::PausedToPlaying { - let mut receivers = hashmap_receivers.lock().unwrap(); + let mut receivers = HASHMAP_RECEIVERS.lock().unwrap(); let settings = self.settings.lock().unwrap(); - let receiver = receivers.get_mut(&settings.id_receiver).unwrap(); + let receiver = receivers.get_mut(&settings.id_receiver.unwrap()).unwrap(); let recv = &receiver.ndi_instance; - let pNDI_recv = recv.recv; - let video_frame: NDIlib_video_frame_v2_t = Default::default(); + // FIXME error handling, make interruptable + let video_frame = + loop { + match recv.capture(true, false, false, 1000) { + Err(_) => unimplemented!(), + Ok(None) => continue, + Ok(Some(Frame::Video(frame))) => break frame, + _ => unreachable!(), + } + }; - unsafe { - while NDIlib_recv_capture_v2( - pNDI_recv, - &video_frame, - ptr::null(), - ptr::null(), - 1000, - ) != NDIlib_frame_type_e::NDIlib_frame_type_video - {} - } gst_debug!( self.cat, obj: element, @@ -282,13 +278,12 @@ impl ElementImpl for NdiVideoSrc { video_frame ); - if receiver.initial_timestamp <= video_frame.timestamp as u64 + // FIXME handle unset timestamp + + if receiver.initial_timestamp <= video_frame.timestamp() as u64 || receiver.initial_timestamp == 0 { - receiver.initial_timestamp = video_frame.timestamp as u64; - } - unsafe { - NDIlib_recv_free_video_v2(pNDI_recv, &video_frame); + receiver.initial_timestamp = video_frame.timestamp() as u64; } gst_debug!( self.cat, @@ -331,13 +326,13 @@ impl BaseSrcImpl for NdiVideoSrc { settings.id_receiver = connect_ndi( self.cat, element, - &settings.ip.clone(), - &settings.stream_name.clone(), + &settings.ip, + &settings.stream_name, ); - // settings.id_receiver != 0 + // settings.id_receiver exists match settings.id_receiver { - 0 => Err(gst_error_msg!( + None => Err(gst_error_msg!( gst::ResourceError::NotFound, ["Could not connect to this source"] )), @@ -349,7 +344,7 @@ impl BaseSrcImpl for NdiVideoSrc { *self.state.lock().unwrap() = Default::default(); let settings = self.settings.lock().unwrap(); - stop_ndi(self.cat, element, settings.id_receiver); + stop_ndi(self.cat, element, settings.id_receiver.unwrap()); // Commented because when adding ndi destroy stopped in this line //*self.state.lock().unwrap() = Default::default(); Ok(()) @@ -379,39 +374,40 @@ impl BaseSrcImpl for NdiVideoSrc { } fn fixate(&self, element: &gst_base::BaseSrc, caps: gst::Caps) -> gst::Caps { - let receivers = hashmap_receivers.lock().unwrap(); + let receivers = HASHMAP_RECEIVERS.lock().unwrap(); let mut settings = self.settings.lock().unwrap(); - let receiver = receivers.get(&settings.id_receiver).unwrap(); + let receiver = receivers.get(&settings.id_receiver.unwrap()).unwrap(); let recv = &receiver.ndi_instance; - let pNDI_recv = recv.recv; - let video_frame: NDIlib_video_frame_v2_t = Default::default(); + // FIXME: Should be done in create() and caps be updated as needed + let video_frame = + loop { + match recv.capture(true, false, false, 1000) { + Err(_) => unimplemented!(), + Ok(None) => continue, + Ok(Some(Frame::Video(frame))) => break frame, + _ => unreachable!(), + } + }; - unsafe { - while NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000) - != NDIlib_frame_type_e::NDIlib_frame_type_video - {} - } + // FIXME: Why? settings.latency = gst::SECOND.mul_div_floor( - video_frame.frame_rate_D as u64, - video_frame.frame_rate_N as u64, + video_frame.frame_rate().1 as u64, + video_frame.frame_rate().0 as u64, ); let mut caps = gst::Caps::truncate(caps); { let caps = caps.make_mut(); let s = caps.get_mut_structure(0).unwrap(); - s.fixate_field_nearest_int("width", video_frame.xres); - s.fixate_field_nearest_int("height", video_frame.yres); + s.fixate_field_nearest_int("width", video_frame.xres()); + s.fixate_field_nearest_int("height", video_frame.yres()); s.fixate_field_nearest_fraction( "framerate", - Fraction::new(video_frame.frame_rate_N, video_frame.frame_rate_D), + Fraction::new(video_frame.frame_rate().0, video_frame.frame_rate().1), ); } - unsafe { - NDIlib_recv_free_video_v2(pNDI_recv, &video_frame); - } let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); self.parent_fixate(element, caps) } @@ -434,38 +430,41 @@ impl BaseSrcImpl for NdiVideoSrc { } Some(ref info) => info.clone(), }; - let receivers = hashmap_receivers.lock().unwrap(); + let receivers = HASHMAP_RECEIVERS.lock().unwrap(); - let recv = &receivers.get(&_settings.id_receiver).unwrap().ndi_instance; - let pNDI_recv = recv.recv; + let receiver = &receivers.get(&_settings.id_receiver.unwrap()).unwrap(); + let recv = &receiver.ndi_instance; - let pts: u64; - let video_frame: NDIlib_video_frame_v2_t = Default::default(); + let time = receiver.initial_timestamp; - unsafe { - let time = receivers - .get(&_settings.id_receiver) - .unwrap() - .initial_timestamp; + let mut count_frame_none = 0; + let video_frame = + loop { + // FIXME: make interruptable + let res = + loop { + match recv.capture(true, false, false, 1000) { + Err(_) => break Err(()), + Ok(None) => break Ok(None), + Ok(Some(Frame::Video(frame))) => break Ok(Some(frame)), + _ => unreachable!(), + } + }; - let mut skip_frame = true; - let mut count_frame_none = 0; - while skip_frame { - let frame_type = - NDIlib_recv_capture_v2(pNDI_recv, &video_frame, ptr::null(), ptr::null(), 1000); - if (frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none - && _settings.loss_threshold != 0) - || frame_type == NDIlib_frame_type_e::NDIlib_frame_type_error - { + let video_frame = match res { + Err(_) => { + gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type error received, assuming that the source closed the stream...."]); + return Err(gst::FlowError::Error); + }, + Ok(None) if _settings.loss_threshold != 0 => { if count_frame_none < _settings.loss_threshold { count_frame_none += 1; continue; } - gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type none or error received, assuming that the source closed the stream...."]); + gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type none received, assuming that the source closed the stream...."]); return Err(gst::FlowError::Error); - } else if frame_type == NDIlib_frame_type_e::NDIlib_frame_type_none - && _settings.loss_threshold == 0 - { + }, + Ok(None) => { gst_debug!( self.cat, obj: element, @@ -473,62 +472,64 @@ impl BaseSrcImpl for NdiVideoSrc { ); count_frame_none += 1; continue; - } + }, + Ok(Some(frame)) => frame, + }; - if time >= (video_frame.timestamp as u64) { - gst_debug!(self.cat, obj: element, "Frame timestamp ({:?}) is lower than received in the first frame from NDI ({:?}), so skiping...", (video_frame.timestamp as u64), time); - } else { - skip_frame = false; - } + if time >= (video_frame.timestamp() as u64) { + gst_debug!(self.cat, obj: element, "Frame timestamp ({:?}) is lower than received in the first frame from NDI ({:?}), so skiping...", (video_frame.timestamp() as u64), time); + } else { + break video_frame; } + }; - gst_log!( - self.cat, - obj: element, - "NDI video frame received: {:?}", - (video_frame) - ); + gst_log!( + self.cat, + obj: element, + "NDI video frame received: {:?}", + video_frame + ); - pts = video_frame.timestamp as u64 - time; + let pts = video_frame.timestamp() as u64 - time; - gst_log!( - self.cat, - obj: element, - "Calculated pts for video frame: {:?}", - (pts) - ); + gst_log!( + self.cat, + obj: element, + "Calculated pts for video frame: {:?}", + pts + ); - let buff_size = (video_frame.yres * video_frame.line_stride_in_bytes) as usize; - let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); - { - let data = slice::from_raw_parts(video_frame.p_data as *mut u8, buff_size); - // Newtek NDI yields times in 100ns intervals since the Unix Time - let pts: gst::ClockTime = (pts * 100).into(); + let buff_size = (video_frame.yres() * video_frame.line_stride_in_bytes()) as usize; + let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); + { + // Newtek NDI yields times in 100ns intervals since the Unix Time + let pts: gst::ClockTime = (pts * 100).into(); - let duration: gst::ClockTime = (((f64::from(video_frame.frame_rate_D) - / f64::from(video_frame.frame_rate_N)) - * 1_000_000_000.0) as u64) - .into(); - let buffer = buffer.get_mut().unwrap(); + let duration: gst::ClockTime = (((f64::from(video_frame.frame_rate().1) + / f64::from(video_frame.frame_rate().0)) + * 1_000_000_000.0) as u64) + .into(); + let buffer = buffer.get_mut().unwrap(); - if ndi_struct.start_pts == gst::ClockTime(Some(0)) { - ndi_struct.start_pts = + // FIXME don't use static mut, also this calculation is wrong + unsafe { + if NDI_STRUCT.start_pts == gst::ClockTime(Some(0)) { + NDI_STRUCT.start_pts = element.get_clock().unwrap().get_time() - element.get_base_time(); } - buffer.set_pts(pts + ndi_struct.start_pts); - buffer.set_duration(duration); - buffer.set_offset(timestamp_data.offset); - timestamp_data.offset += 1; - buffer.set_offset_end(timestamp_data.offset); - buffer.copy_from_slice(0, data).unwrap(); + buffer.set_pts(pts + NDI_STRUCT.start_pts); } - - gst_log!(self.cat, obj: element, "Produced buffer {:?}", buffer); - NDIlib_recv_free_video_v2(pNDI_recv, &video_frame); - - Ok(buffer) + buffer.set_duration(duration); + buffer.set_offset(timestamp_data.offset); + timestamp_data.offset += 1; + buffer.set_offset_end(timestamp_data.offset); + buffer.copy_from_slice(0, video_frame.data()).unwrap(); } + + gst_log!(self.cat, obj: element, "Produced buffer {:?}", buffer); + + Ok(buffer) } } From 71d8f7108cc316030537bd60ed20f168774131a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Thu, 11 Jul 2019 21:59:25 +0300 Subject: [PATCH 121/199] Remove unused FFI code --- src/ndisys.rs | 107 -------------------------------------------------- 1 file changed, 107 deletions(-) diff --git a/src/ndisys.rs b/src/ndisys.rs index 13c2267e..25c9adde 100644 --- a/src/ndisys.rs +++ b/src/ndisys.rs @@ -1,7 +1,5 @@ #![allow(non_camel_case_types, non_upper_case_globals, non_snake_case)] -use std::ptr; - #[cfg_attr(all(target_arch = "x86_64", target_os = "windows"), link(name = "Processing.NDI.Lib.x64"))] #[cfg_attr(all(target_arch = "x86", target_os = "windows"), link(name = "Processing.NDI.Lib.x86"))] #[cfg_attr(not(any(target_os = "windows", target_os = "macos")), link(name = "ndi"))] @@ -63,16 +61,6 @@ pub struct NDIlib_find_create_t { pub p_extra_ips: *const ::std::os::raw::c_char, } -impl Default for NDIlib_find_create_t { - fn default() -> Self { - NDIlib_find_create_t { - show_local_sources: true, - p_groups: ptr::null(), - p_extra_ips: ptr::null(), - } - } -} - #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct NDIlib_source_t { @@ -80,15 +68,6 @@ pub struct NDIlib_source_t { pub p_ip_address: *const ::std::os::raw::c_char, } -impl Default for NDIlib_source_t { - fn default() -> Self { - NDIlib_source_t { - p_ndi_name: ptr::null(), - p_ip_address: ptr::null(), - } - } -} - #[repr(i32)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum NDIlib_frame_type_e { @@ -153,28 +132,8 @@ pub struct NDIlib_recv_create_v3_t { pub p_ndi_name: *const ::std::os::raw::c_char, } -impl Default for NDIlib_recv_create_v3_t { - fn default() -> Self { - NDIlib_recv_create_v3_t { - source_to_connect_to: Default::default(), - allow_video_fields: true, - bandwidth: NDIlib_recv_bandwidth_e::NDIlib_recv_bandwidth_highest, - color_format: NDIlib_recv_color_format_e::NDIlib_recv_color_format_UYVY_BGRA, - p_ndi_name: ptr::null(), - } - } -} - pub type NDIlib_recv_instance_t = *mut ::std::os::raw::c_void; -//Rust wrapper around *mut ::std::os::raw::c_void -pub struct NdiInstance { - pub recv: NDIlib_recv_instance_t, - // pub audio: bool, -} - -unsafe impl ::std::marker::Send for NdiInstance {} - #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct NDIlib_tally_t { @@ -182,15 +141,6 @@ pub struct NDIlib_tally_t { pub on_preview: bool, } -impl Default for NDIlib_tally_t { - fn default() -> Self { - NDIlib_tally_t { - on_program: false, - on_preview: false, - } - } -} - #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct NDIlib_metadata_frame_t { @@ -199,16 +149,6 @@ pub struct NDIlib_metadata_frame_t { pub p_data: *const ::std::os::raw::c_char, } -impl Default for NDIlib_metadata_frame_t { - fn default() -> Self { - NDIlib_metadata_frame_t { - length: 0, - timecode: 0, //NDIlib_send_timecode_synthesize, - p_data: ptr::null(), - } - } -} - #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct NDIlib_video_frame_v2_t { @@ -226,25 +166,6 @@ pub struct NDIlib_video_frame_v2_t { pub timestamp: i64, } -impl Default for NDIlib_video_frame_v2_t { - fn default() -> Self { - NDIlib_video_frame_v2_t { - xres: 0, - yres: 0, - FourCC: NDIlib_FourCC_type_e::NDIlib_FourCC_type_UYVY, - frame_rate_N: 30000, - frame_rate_D: 1001, - picture_aspect_ratio: 0.0, - frame_format_type: NDIlib_frame_format_type_e::NDIlib_frame_format_type_progressive, - timecode: NDIlib_send_timecode_synthesize, - p_data: ptr::null(), - line_stride_in_bytes: 0, - p_metadata: ptr::null(), - timestamp: NDIlib_send_timecode_empty, - } - } -} - #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct NDIlib_audio_frame_v2_t { @@ -258,21 +179,6 @@ pub struct NDIlib_audio_frame_v2_t { pub timestamp: i64, } -impl Default for NDIlib_audio_frame_v2_t { - fn default() -> Self { - NDIlib_audio_frame_v2_t { - sample_rate: 48000, - no_channels: 2, - no_samples: 0, - timecode: NDIlib_send_timecode_synthesize, - p_data: ptr::null(), - channel_stride_in_bytes: 0, - p_metadata: ptr::null(), - timestamp: NDIlib_send_timecode_empty, - } - } -} - extern "C" { pub fn NDIlib_util_audio_to_interleaved_16s_v2( p_src: *const NDIlib_audio_frame_v2_t, @@ -295,16 +201,3 @@ pub struct NDIlib_audio_frame_interleaved_16s_t { pub reference_level: ::std::os::raw::c_int, pub p_data: *mut ::std::os::raw::c_short, } - -impl Default for NDIlib_audio_frame_interleaved_16s_t { - fn default() -> Self { - NDIlib_audio_frame_interleaved_16s_t { - sample_rate: 48000, - no_channels: 2, - no_samples: 0, - timecode: NDIlib_send_timecode_synthesize, - reference_level: 0, - p_data: ptr::null_mut(), - } - } -} From 25d7c9e74fd0ed4d52991f7ee7bc1d25e48c54c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Mon, 15 Jul 2019 17:56:29 +0300 Subject: [PATCH 122/199] Use glib::Type instead of element name or factory name for deciding between audio/video --- src/lib.rs | 28 ++++++++++------------------ src/ndiaudiosrc.rs | 2 +- src/ndivideosrc.rs | 2 +- 3 files changed, 12 insertions(+), 20 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index fee28102..1f63b22a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,9 @@ #[macro_use] extern crate glib; +use glib::prelude::*; +use glib::subclass::prelude::*; #[macro_use] extern crate gstreamer as gst; -use gst::prelude::*; extern crate gstreamer_audio as gst_audio; extern crate gstreamer_base as gst_base; extern crate gstreamer_video as gst_video; @@ -69,28 +70,18 @@ fn connect_ndi( gst_debug!(cat, obj: element, "Starting NDI connection..."); let mut receivers = HASHMAP_RECEIVERS.lock().unwrap(); - let mut audio = false; - let mut video = false; - if element - .get_factory() - .map(|f| f.get_name() == "ndiaudiosrc") - .unwrap_or(false) - { - audio = true; - } else { - video = true; - } + let video = element.get_type() == ndivideosrc::NdiVideoSrc::get_type(); for val in receivers.values_mut() { if val.ip == ip || val.stream_name == stream_name { - if (val.audio && val.video) || (val.audio && audio) || (val.video && video) { + if (val.audio && val.video) || (val.audio && !video) || (val.video && video) { continue; } else { if video { val.video = video; } else { - val.audio = audio; + val.audio = !video; } return Some(val.id); } @@ -174,7 +165,7 @@ fn connect_ndi( stream_name: source.ndi_name().to_owned(), ip: source.ip_address().to_owned(), video, - audio, + audio: !video, ndi_instance: recv, initial_timestamp: 0, id: id_receiver, @@ -191,10 +182,11 @@ fn stop_ndi(cat: gst::DebugCategory, element: &gst_base::BaseSrc, id: usize) -> { let val = receivers.get_mut(&id).unwrap(); if val.video && val.audio { - if element.get_name().contains("audiosrc") { - val.audio = false; - } else { + let video = element.get_type() == ndivideosrc::NdiVideoSrc::get_type(); + if video { val.video = false; + } else { + val.audio = false; } return true; } diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index 310dda07..401a6db2 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -89,7 +89,7 @@ struct TimestampData { offset: u64, } -struct NdiAudioSrc { +pub(crate) struct NdiAudioSrc { cat: gst::DebugCategory, settings: Mutex, state: Mutex, diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index 67538fb7..0b40dc60 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -90,7 +90,7 @@ struct TimestampData { offset: u64, } -struct NdiVideoSrc { +pub(crate) struct NdiVideoSrc { cat: gst::DebugCategory, settings: Mutex, state: Mutex, From 9afe1d74b8ff4b640c2be34fef6e0be06f242e2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Mon, 15 Jul 2019 18:00:51 +0300 Subject: [PATCH 123/199] Set on_program to true by default in the Tally --- src/ndi.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ndi.rs b/src/ndi.rs index 355b0362..e811868e 100644 --- a/src/ndi.rs +++ b/src/ndi.rs @@ -280,7 +280,7 @@ unsafe impl Send for Tally {} impl Default for Tally { fn default() -> Self { Self(NDIlib_tally_t { - on_program: false, + on_program: true, on_preview: false, }) } From 32289047a5d2df8db4a4d567048c46c006534bb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Mon, 15 Jul 2019 18:10:34 +0300 Subject: [PATCH 124/199] Remove receiver/latency from Settings to State struct And clean up usage of both. --- src/ndiaudiosrc.rs | 50 +++++++++++++++++++++++---------------------- src/ndivideosrc.rs | 51 +++++++++++++++++++++++----------------------- 2 files changed, 52 insertions(+), 49 deletions(-) diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index 401a6db2..86c8069a 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -26,9 +26,6 @@ struct Settings { stream_name: String, ip: String, loss_threshold: u32, - // FIXME: Should be in State - id_receiver: Option, - latency: Option, } impl Default for Settings { @@ -37,8 +34,6 @@ impl Default for Settings { stream_name: String::from("Fixed ndi stream name"), ip: String::from(""), loss_threshold: 5, - id_receiver: None, - latency: None, } } } @@ -77,11 +72,17 @@ static PROPERTIES: [subclass::Property; 3] = [ struct State { info: Option, + id_receiver: Option, + latency: Option, } impl Default for State { fn default() -> State { - State { info: None } + State { + info: None, + id_receiver: None, + latency: None, + } } } @@ -247,9 +248,9 @@ impl ElementImpl for NdiAudioSrc { ) -> Result { if transition == gst::StateChange::PausedToPlaying { let mut receivers = HASHMAP_RECEIVERS.lock().unwrap(); - let settings = self.settings.lock().unwrap(); + let state = self.state.lock().unwrap(); - let receiver = receivers.get_mut(&settings.id_receiver.unwrap()).unwrap(); + let receiver = receivers.get_mut(&state.id_receiver.unwrap()).unwrap(); let recv = &receiver.ndi_instance; // FIXME error handling, make interruptable @@ -316,15 +317,16 @@ impl BaseSrcImpl for NdiAudioSrc { fn start(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { *self.state.lock().unwrap() = Default::default(); - let mut settings = self.settings.lock().unwrap(); - settings.id_receiver = connect_ndi( + let settings = self.settings.lock().unwrap().clone(); + let mut state = self.state.lock().unwrap(); + state.id_receiver = connect_ndi( self.cat, element, &settings.ip.clone(), &settings.stream_name.clone(), ); - match settings.id_receiver { + match state.id_receiver { None => Err(gst_error_msg!( gst::ResourceError::NotFound, ["Could not connect to this source"] @@ -336,10 +338,11 @@ impl BaseSrcImpl for NdiAudioSrc { fn stop(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { *self.state.lock().unwrap() = Default::default(); - let settings = self.settings.lock().unwrap(); - stop_ndi(self.cat, element, settings.id_receiver.unwrap()); - // Commented because when adding ndi destroy stopped in this line - //*self.state.lock().unwrap() = Default::default(); + let mut state = self.state.lock().unwrap(); + if let Some(id_receiver) = state.id_receiver.take() { + stop_ndi(self.cat, element, id_receiver); + } + *state = State::default(); Ok(()) } @@ -351,11 +354,10 @@ impl BaseSrcImpl for NdiAudioSrc { return true; } if let QueryView::Latency(ref mut q) = query.view_mut() { - let settings = &*self.settings.lock().unwrap(); let state = self.state.lock().unwrap(); if let Some(ref _info) = state.info { - let latency = settings.latency.unwrap(); + let latency = state.latency.unwrap(); gst_debug!(self.cat, obj: element, "Returning latency {}", latency); q.set(true, latency, gst::CLOCK_TIME_NONE); return true; @@ -368,9 +370,9 @@ impl BaseSrcImpl for NdiAudioSrc { fn fixate(&self, element: &gst_base::BaseSrc, caps: gst::Caps) -> gst::Caps { let receivers = HASHMAP_RECEIVERS.lock().unwrap(); - let mut settings = self.settings.lock().unwrap(); + let mut state = self.state.lock().unwrap(); - let receiver = receivers.get(&settings.id_receiver.unwrap()).unwrap(); + let receiver = receivers.get(&state.id_receiver.unwrap()).unwrap(); let recv = &receiver.ndi_instance; @@ -389,7 +391,7 @@ impl BaseSrcImpl for NdiAudioSrc { // FIXME: Why? let no_samples = audio_frame.no_samples() as u64; let audio_rate = audio_frame.sample_rate(); - settings.latency = gst::SECOND.mul_div_floor(no_samples, audio_rate as u64); + state.latency = gst::SECOND.mul_div_floor(no_samples, audio_rate as u64); let mut caps = gst::Caps::truncate(caps); { @@ -418,7 +420,7 @@ impl BaseSrcImpl for NdiAudioSrc { _offset: u64, _length: u32, ) -> Result { - let _settings = &*self.settings.lock().unwrap(); + let settings = self.settings.lock().unwrap().clone(); let mut timestamp_data = self.timestamp_data.lock().unwrap(); @@ -432,7 +434,7 @@ impl BaseSrcImpl for NdiAudioSrc { }; let receivers = HASHMAP_RECEIVERS.lock().unwrap(); - let receiver = &receivers.get(&_settings.id_receiver.unwrap()).unwrap(); + let receiver = &receivers.get(&state.id_receiver.unwrap()).unwrap(); let recv = &receiver.ndi_instance; let time = receiver.initial_timestamp; @@ -456,8 +458,8 @@ impl BaseSrcImpl for NdiAudioSrc { gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type error received, assuming that the source closed the stream...."]); return Err(gst::FlowError::Error); }, - Ok(None) if _settings.loss_threshold != 0 => { - if count_frame_none < _settings.loss_threshold { + Ok(None) if settings.loss_threshold != 0 => { + if count_frame_none < settings.loss_threshold { count_frame_none += 1; continue; } diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index 0b40dc60..78c262c4 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -27,9 +27,6 @@ struct Settings { stream_name: String, ip: String, loss_threshold: u32, - // FIXME: should be in state - id_receiver: Option, - latency: Option, } impl Default for Settings { @@ -38,8 +35,6 @@ impl Default for Settings { stream_name: String::from("Fixed ndi stream name"), ip: String::from(""), loss_threshold: 5, - id_receiver: None, - latency: None, } } } @@ -78,11 +73,17 @@ static PROPERTIES: [subclass::Property; 3] = [ struct State { info: Option, + id_receiver: Option, + latency: Option, } impl Default for State { fn default() -> State { - State { info: None } + State { + info: None, + id_receiver: None, + latency: None, + } } } @@ -255,9 +256,9 @@ impl ElementImpl for NdiVideoSrc { ) -> Result { if transition == gst::StateChange::PausedToPlaying { let mut receivers = HASHMAP_RECEIVERS.lock().unwrap(); - let settings = self.settings.lock().unwrap(); + let state = self.state.lock().unwrap(); - let receiver = receivers.get_mut(&settings.id_receiver.unwrap()).unwrap(); + let receiver = receivers.get_mut(&state.id_receiver.unwrap()).unwrap(); let recv = &receiver.ndi_instance; // FIXME error handling, make interruptable @@ -322,8 +323,9 @@ impl BaseSrcImpl for NdiVideoSrc { fn start(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { *self.state.lock().unwrap() = Default::default(); - let mut settings = self.settings.lock().unwrap(); - settings.id_receiver = connect_ndi( + let mut state = self.state.lock().unwrap(); + let settings = self.settings.lock().unwrap().clone(); + state.id_receiver = connect_ndi( self.cat, element, &settings.ip, @@ -331,7 +333,7 @@ impl BaseSrcImpl for NdiVideoSrc { ); // settings.id_receiver exists - match settings.id_receiver { + match state.id_receiver { None => Err(gst_error_msg!( gst::ResourceError::NotFound, ["Could not connect to this source"] @@ -343,10 +345,11 @@ impl BaseSrcImpl for NdiVideoSrc { fn stop(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { *self.state.lock().unwrap() = Default::default(); - let settings = self.settings.lock().unwrap(); - stop_ndi(self.cat, element, settings.id_receiver.unwrap()); - // Commented because when adding ndi destroy stopped in this line - //*self.state.lock().unwrap() = Default::default(); + let mut state = self.state.lock().unwrap(); + if let Some(id_receiver) = state.id_receiver.take() { + stop_ndi(self.cat, element, id_receiver); + } + *state = State::default(); Ok(()) } @@ -358,11 +361,10 @@ impl BaseSrcImpl for NdiVideoSrc { return true; } if let QueryView::Latency(ref mut q) = query.view_mut() { - let settings = &*self.settings.lock().unwrap(); let state = self.state.lock().unwrap(); if let Some(ref _info) = state.info { - let latency = settings.latency.unwrap(); + let latency = state.latency.unwrap(); gst_debug!(self.cat, obj: element, "Returning latency {}", latency); q.set(true, latency, gst::CLOCK_TIME_NONE); return true; @@ -375,9 +377,9 @@ impl BaseSrcImpl for NdiVideoSrc { fn fixate(&self, element: &gst_base::BaseSrc, caps: gst::Caps) -> gst::Caps { let receivers = HASHMAP_RECEIVERS.lock().unwrap(); - let mut settings = self.settings.lock().unwrap(); + let mut state = self.state.lock().unwrap(); - let receiver = receivers.get(&settings.id_receiver.unwrap()).unwrap(); + let receiver = receivers.get(&state.id_receiver.unwrap()).unwrap(); let recv = &receiver.ndi_instance; // FIXME: Should be done in create() and caps be updated as needed @@ -392,7 +394,7 @@ impl BaseSrcImpl for NdiVideoSrc { }; // FIXME: Why? - settings.latency = gst::SECOND.mul_div_floor( + state.latency = gst::SECOND.mul_div_floor( video_frame.frame_rate().1 as u64, video_frame.frame_rate().0 as u64, ); @@ -419,9 +421,8 @@ impl BaseSrcImpl for NdiVideoSrc { _offset: u64, _length: u32, ) -> Result { - let _settings = &*self.settings.lock().unwrap(); - let mut timestamp_data = self.timestamp_data.lock().unwrap(); + let settings = self.settings.lock().unwrap().clone(); let state = self.state.lock().unwrap(); let _info = match state.info { None => { @@ -432,7 +433,7 @@ impl BaseSrcImpl for NdiVideoSrc { }; let receivers = HASHMAP_RECEIVERS.lock().unwrap(); - let receiver = &receivers.get(&_settings.id_receiver.unwrap()).unwrap(); + let receiver = &receivers.get(&state.id_receiver.unwrap()).unwrap(); let recv = &receiver.ndi_instance; let time = receiver.initial_timestamp; @@ -456,8 +457,8 @@ impl BaseSrcImpl for NdiVideoSrc { gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type error received, assuming that the source closed the stream...."]); return Err(gst::FlowError::Error); }, - Ok(None) if _settings.loss_threshold != 0 => { - if count_frame_none < _settings.loss_threshold { + Ok(None) if settings.loss_threshold != 0 => { + if count_frame_none < settings.loss_threshold { count_frame_none += 1; continue; } From 2ad413773ed03f37ca03d3a3348e9a563f698886 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Mon, 15 Jul 2019 18:26:51 +0300 Subject: [PATCH 125/199] Take the current running time as PTS for now and clean up timestamp/duration calculations We might want to take the timestamp provided by the NDI SDK at a later time but this needs to be done correctly. --- src/lib.rs | 10 ----- src/ndiaudiosrc.rs | 95 ++++++---------------------------------------- src/ndivideosrc.rs | 93 ++++++--------------------------------------- 3 files changed, 22 insertions(+), 176 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1f63b22a..31cf7846 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,17 +41,8 @@ struct ReceiverInfo { video: bool, audio: bool, ndi_instance: RecvInstance, - initial_timestamp: u64, } -struct Ndi { - start_pts: gst::ClockTime, -} - -static mut NDI_STRUCT: Ndi = Ndi { - start_pts: gst::ClockTime(Some(0)), -}; - lazy_static! { static ref HASHMAP_RECEIVERS: Mutex> = { let m = HashMap::new(); @@ -167,7 +158,6 @@ fn connect_ndi( video, audio: !video, ndi_instance: recv, - initial_timestamp: 0, id: id_receiver, }, ); diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index 86c8069a..6cc9ab8e 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -16,7 +16,6 @@ use connect_ndi; use stop_ndi; use ndi::*; -use NDI_STRUCT; use HASHMAP_RECEIVERS; use byte_slice_cast::AsMutSliceOf; @@ -86,15 +85,10 @@ impl Default for State { } } -struct TimestampData { - offset: u64, -} - pub(crate) struct NdiAudioSrc { cat: gst::DebugCategory, settings: Mutex, state: Mutex, - timestamp_data: Mutex, } impl ObjectSubclass for NdiAudioSrc { @@ -114,7 +108,6 @@ impl ObjectSubclass for NdiAudioSrc { ), settings: Mutex::new(Default::default()), state: Mutex::new(Default::default()), - timestamp_data: Mutex::new(TimestampData { offset: 0 }), } } @@ -241,52 +234,6 @@ impl ObjectImpl for NdiAudioSrc { } impl ElementImpl for NdiAudioSrc { - fn change_state( - &self, - element: &gst::Element, - transition: gst::StateChange, - ) -> Result { - if transition == gst::StateChange::PausedToPlaying { - let mut receivers = HASHMAP_RECEIVERS.lock().unwrap(); - let state = self.state.lock().unwrap(); - - let receiver = receivers.get_mut(&state.id_receiver.unwrap()).unwrap(); - let recv = &receiver.ndi_instance; - - // FIXME error handling, make interruptable - let audio_frame = - loop { - match recv.capture(false, true, false, 1000) { - Err(_) => unimplemented!(), - Ok(None) => continue, - Ok(Some(Frame::Audio(frame))) => break frame, - _ => unreachable!(), - } - }; - - gst_debug!( - self.cat, - obj: element, - "NDI audio frame received: {:?}", - audio_frame - ); - - // FIXME handle unset timestamp - if receiver.initial_timestamp <= audio_frame.timestamp() as u64 - || receiver.initial_timestamp == 0 - { - receiver.initial_timestamp = audio_frame.timestamp() as u64; - } - - gst_debug!( - self.cat, - obj: element, - "Setting initial timestamp to {}", - receiver.initial_timestamp - ); - } - self.parent_change_state(element, transition) - } } impl BaseSrcImpl for NdiAudioSrc { @@ -420,10 +367,8 @@ impl BaseSrcImpl for NdiAudioSrc { _offset: u64, _length: u32, ) -> Result { + // FIXME: Make sure to not have any mutexes locked while wait let settings = self.settings.lock().unwrap().clone(); - - let mut timestamp_data = self.timestamp_data.lock().unwrap(); - let state = self.state.lock().unwrap(); let _info = match state.info { None => { @@ -437,7 +382,7 @@ impl BaseSrcImpl for NdiAudioSrc { let receiver = &receivers.get(&state.id_receiver.unwrap()).unwrap(); let recv = &receiver.ndi_instance; - let time = receiver.initial_timestamp; + let clock = element.get_clock().unwrap(); let mut count_frame_none = 0; let audio_frame = @@ -478,13 +423,15 @@ impl BaseSrcImpl for NdiAudioSrc { Ok(Some(frame)) => frame, }; - if time >= (audio_frame.timestamp() as u64) { - gst_debug!(self.cat, obj: element, "Frame timestamp ({:?}) is lower than received in the first frame from NDI ({:?}), so skiping...", (audio_frame.timestamp() as u64), time); - } else { - break audio_frame; - } + break audio_frame; }; + // For now take the current running time as PTS. At a later time we + // will want to work with the timestamp given by the NDI SDK if available + let now = clock.get_time(); + let base_time = element.get_base_time(); + let pts = now - base_time; + gst_log!( self.cat, obj: element, @@ -492,8 +439,6 @@ impl BaseSrcImpl for NdiAudioSrc { audio_frame ); - let pts = audio_frame.timestamp() as u64 - time; - gst_log!( self.cat, obj: element, @@ -505,30 +450,12 @@ impl BaseSrcImpl for NdiAudioSrc { let buff_size = (audio_frame.no_samples() * 2 * audio_frame.no_channels()) as usize; let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); { - // FIXME don't use static mut, also this calculation is wrong - unsafe { - if NDI_STRUCT.start_pts == gst::ClockTime(Some(0)) { - NDI_STRUCT.start_pts = - element.get_clock().unwrap().get_time() - element.get_base_time(); - } - } - + let duration = gst::SECOND.mul_div_floor(audio_frame.no_samples() as u64, audio_frame.sample_rate() as u64).unwrap_or(gst::CLOCK_TIME_NONE); let buffer = buffer.get_mut().unwrap(); - // Newtek NDI yields times in 100ns intervals since the Unix Time - let pts: gst::ClockTime = (pts * 100).into(); - buffer.set_pts(pts + unsafe { NDI_STRUCT.start_pts }); - - let duration: gst::ClockTime = (((f64::from(audio_frame.no_samples()) - / f64::from(audio_frame.sample_rate())) - * 1_000_000_000.0) as u64) - .into(); + buffer.set_pts(pts); buffer.set_duration(duration); - buffer.set_offset(timestamp_data.offset); - timestamp_data.offset += audio_frame.no_samples() as u64; - buffer.set_offset_end(timestamp_data.offset); - audio_frame.copy_to_interleaved_16s(buffer .map_writable() .unwrap() diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index 78c262c4..545fb0c5 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -19,7 +19,6 @@ use ndi::*; use connect_ndi; use stop_ndi; -use NDI_STRUCT; use HASHMAP_RECEIVERS; #[derive(Debug, Clone)] @@ -87,15 +86,10 @@ impl Default for State { } } -struct TimestampData { - offset: u64, -} - pub(crate) struct NdiVideoSrc { cat: gst::DebugCategory, settings: Mutex, state: Mutex, - timestamp_data: Mutex, } impl ObjectSubclass for NdiVideoSrc { @@ -115,7 +109,6 @@ impl ObjectSubclass for NdiVideoSrc { ), settings: Mutex::new(Default::default()), state: Mutex::new(Default::default()), - timestamp_data: Mutex::new(TimestampData { offset: 0 }), } } @@ -249,52 +242,6 @@ impl ObjectImpl for NdiVideoSrc { } impl ElementImpl for NdiVideoSrc { - fn change_state( - &self, - element: &gst::Element, - transition: gst::StateChange, - ) -> Result { - if transition == gst::StateChange::PausedToPlaying { - let mut receivers = HASHMAP_RECEIVERS.lock().unwrap(); - let state = self.state.lock().unwrap(); - - let receiver = receivers.get_mut(&state.id_receiver.unwrap()).unwrap(); - let recv = &receiver.ndi_instance; - - // FIXME error handling, make interruptable - let video_frame = - loop { - match recv.capture(true, false, false, 1000) { - Err(_) => unimplemented!(), - Ok(None) => continue, - Ok(Some(Frame::Video(frame))) => break frame, - _ => unreachable!(), - } - }; - - gst_debug!( - self.cat, - obj: element, - "NDI video frame received: {:?}", - video_frame - ); - - // FIXME handle unset timestamp - - if receiver.initial_timestamp <= video_frame.timestamp() as u64 - || receiver.initial_timestamp == 0 - { - receiver.initial_timestamp = video_frame.timestamp() as u64; - } - gst_debug!( - self.cat, - obj: element, - "Setting initial timestamp to {}", - receiver.initial_timestamp - ); - } - self.parent_change_state(element, transition) - } } impl BaseSrcImpl for NdiVideoSrc { @@ -421,7 +368,7 @@ impl BaseSrcImpl for NdiVideoSrc { _offset: u64, _length: u32, ) -> Result { - let mut timestamp_data = self.timestamp_data.lock().unwrap(); + // FIXME: Make sure to not have any mutexes locked while wait let settings = self.settings.lock().unwrap().clone(); let state = self.state.lock().unwrap(); let _info = match state.info { @@ -436,7 +383,7 @@ impl BaseSrcImpl for NdiVideoSrc { let receiver = &receivers.get(&state.id_receiver.unwrap()).unwrap(); let recv = &receiver.ndi_instance; - let time = receiver.initial_timestamp; + let clock = element.get_clock().unwrap(); let mut count_frame_none = 0; let video_frame = @@ -477,13 +424,15 @@ impl BaseSrcImpl for NdiVideoSrc { Ok(Some(frame)) => frame, }; - if time >= (video_frame.timestamp() as u64) { - gst_debug!(self.cat, obj: element, "Frame timestamp ({:?}) is lower than received in the first frame from NDI ({:?}), so skiping...", (video_frame.timestamp() as u64), time); - } else { - break video_frame; - } + break video_frame; }; + // For now take the current running time as PTS. At a later time we + // will want to work with the timestamp given by the NDI SDK if available + let now = clock.get_time(); + let base_time = element.get_base_time(); + let pts = now - base_time; + gst_log!( self.cat, obj: element, @@ -491,8 +440,6 @@ impl BaseSrcImpl for NdiVideoSrc { video_frame ); - let pts = video_frame.timestamp() as u64 - time; - gst_log!( self.cat, obj: element, @@ -503,28 +450,10 @@ impl BaseSrcImpl for NdiVideoSrc { let buff_size = (video_frame.yres() * video_frame.line_stride_in_bytes()) as usize; let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); { - // Newtek NDI yields times in 100ns intervals since the Unix Time - let pts: gst::ClockTime = (pts * 100).into(); - - let duration: gst::ClockTime = (((f64::from(video_frame.frame_rate().1) - / f64::from(video_frame.frame_rate().0)) - * 1_000_000_000.0) as u64) - .into(); + let duration = gst::SECOND.mul_div_floor(video_frame.frame_rate().1 as u64, video_frame.frame_rate().0 as u64).unwrap_or(gst::CLOCK_TIME_NONE); let buffer = buffer.get_mut().unwrap(); - - // FIXME don't use static mut, also this calculation is wrong - unsafe { - if NDI_STRUCT.start_pts == gst::ClockTime(Some(0)) { - NDI_STRUCT.start_pts = - element.get_clock().unwrap().get_time() - element.get_base_time(); - } - - buffer.set_pts(pts + NDI_STRUCT.start_pts); - } + buffer.set_pts(pts); buffer.set_duration(duration); - buffer.set_offset(timestamp_data.offset); - timestamp_data.offset += 1; - buffer.set_offset_end(timestamp_data.offset); buffer.copy_from_slice(0, video_frame.data()).unwrap(); } From 4443e03cd2883fcb00e92eaaa4797c7fd361d4e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Mon, 15 Jul 2019 18:34:09 +0300 Subject: [PATCH 126/199] Remove wrong and unneeded latency query handling --- src/ndiaudiosrc.rs | 34 ++++++++-------------------------- src/ndivideosrc.rs | 33 +++++++-------------------------- 2 files changed, 15 insertions(+), 52 deletions(-) diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index 6cc9ab8e..c512f8b4 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -72,7 +72,6 @@ static PROPERTIES: [subclass::Property; 3] = [ struct State { info: Option, id_receiver: Option, - latency: Option, } impl Default for State { @@ -80,7 +79,6 @@ impl Default for State { State { info: None, id_receiver: None, - latency: None, } } } @@ -295,29 +293,20 @@ impl BaseSrcImpl for NdiAudioSrc { fn query(&self, element: &gst_base::BaseSrc, query: &mut gst::QueryRef) -> bool { use gst::QueryView; - if let QueryView::Scheduling(ref mut q) = query.view_mut() { - q.set(gst::SchedulingFlags::SEQUENTIAL, 1, -1, 0); - q.add_scheduling_modes(&[gst::PadMode::Push]); - return true; - } - if let QueryView::Latency(ref mut q) = query.view_mut() { - let state = self.state.lock().unwrap(); - if let Some(ref _info) = state.info { - let latency = state.latency.unwrap(); - gst_debug!(self.cat, obj: element, "Returning latency {}", latency); - q.set(true, latency, gst::CLOCK_TIME_NONE); - return true; - } else { - return false; + match query.view_mut() { + QueryView::Scheduling(ref mut q) => { + q.set(gst::SchedulingFlags::SEQUENTIAL, 1, -1, 0); + q.add_scheduling_modes(&[gst::PadMode::Push]); + true } + _ => BaseSrcImplExt::parent_query(self, element, query), } - BaseSrcImplExt::parent_query(self, element, query) } fn fixate(&self, element: &gst_base::BaseSrc, caps: gst::Caps) -> gst::Caps { let receivers = HASHMAP_RECEIVERS.lock().unwrap(); - let mut state = self.state.lock().unwrap(); + let state = self.state.lock().unwrap(); let receiver = receivers.get(&state.id_receiver.unwrap()).unwrap(); @@ -335,16 +324,11 @@ impl BaseSrcImpl for NdiAudioSrc { } }; - // FIXME: Why? - let no_samples = audio_frame.no_samples() as u64; - let audio_rate = audio_frame.sample_rate(); - state.latency = gst::SECOND.mul_div_floor(no_samples, audio_rate as u64); - let mut caps = gst::Caps::truncate(caps); { let caps = caps.make_mut(); let s = caps.get_mut_structure(0).unwrap(); - s.fixate_field_nearest_int("rate", audio_rate); + s.fixate_field_nearest_int("rate", audio_frame.sample_rate()); s.fixate_field_nearest_int("channels", audio_frame.no_channels()); s.fixate_field_str("layout", "interleaved"); s.set_value( @@ -356,8 +340,6 @@ impl BaseSrcImpl for NdiAudioSrc { ); } - let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); - self.parent_fixate(element, caps) } diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index 545fb0c5..e97d49ad 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -73,7 +73,6 @@ static PROPERTIES: [subclass::Property; 3] = [ struct State { info: Option, id_receiver: Option, - latency: Option, } impl Default for State { @@ -81,7 +80,6 @@ impl Default for State { State { info: None, id_receiver: None, - latency: None, } } } @@ -264,7 +262,6 @@ impl BaseSrcImpl for NdiVideoSrc { let mut state = self.state.lock().unwrap(); state.info = Some(info); - let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); Ok(()) } @@ -302,29 +299,20 @@ impl BaseSrcImpl for NdiVideoSrc { fn query(&self, element: &gst_base::BaseSrc, query: &mut gst::QueryRef) -> bool { use gst::QueryView; - if let QueryView::Scheduling(ref mut q) = query.view_mut() { - q.set(gst::SchedulingFlags::SEQUENTIAL, 1, -1, 0); - q.add_scheduling_modes(&[gst::PadMode::Push]); - return true; - } - if let QueryView::Latency(ref mut q) = query.view_mut() { - let state = self.state.lock().unwrap(); - if let Some(ref _info) = state.info { - let latency = state.latency.unwrap(); - gst_debug!(self.cat, obj: element, "Returning latency {}", latency); - q.set(true, latency, gst::CLOCK_TIME_NONE); - return true; - } else { - return false; + match query.view_mut() { + QueryView::Scheduling(ref mut q) => { + q.set(gst::SchedulingFlags::SEQUENTIAL, 1, -1, 0); + q.add_scheduling_modes(&[gst::PadMode::Push]); + true } + _ => BaseSrcImplExt::parent_query(self, element, query), } - BaseSrcImplExt::parent_query(self, element, query) } fn fixate(&self, element: &gst_base::BaseSrc, caps: gst::Caps) -> gst::Caps { let receivers = HASHMAP_RECEIVERS.lock().unwrap(); - let mut state = self.state.lock().unwrap(); + let state = self.state.lock().unwrap(); let receiver = receivers.get(&state.id_receiver.unwrap()).unwrap(); let recv = &receiver.ndi_instance; @@ -340,12 +328,6 @@ impl BaseSrcImpl for NdiVideoSrc { } }; - // FIXME: Why? - state.latency = gst::SECOND.mul_div_floor( - video_frame.frame_rate().1 as u64, - video_frame.frame_rate().0 as u64, - ); - let mut caps = gst::Caps::truncate(caps); { let caps = caps.make_mut(); @@ -357,7 +339,6 @@ impl BaseSrcImpl for NdiVideoSrc { Fraction::new(video_frame.frame_rate().0, video_frame.frame_rate().1), ); } - let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); self.parent_fixate(element, caps) } From 5022ff412cc002674f043a0ec7e1e079cbfe92ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Mon, 15 Jul 2019 18:59:58 +0300 Subject: [PATCH 127/199] Fix caps negotiation We can't just fixate to any close to what we receive right now but only support exactly the caps we receive. So check the format of each frame and negotiate exactly those caps as needed when receiving frames. Also re-negotiate if the caps are ever changing. --- src/lib.rs | 2 +- src/ndiaudiosrc.rs | 80 ++++++++++------------------------------- src/ndivideosrc.rs | 90 +++++++++++++++++++--------------------------- 3 files changed, 55 insertions(+), 117 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 31cf7846..1bdc571d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -130,7 +130,7 @@ fn connect_ndi( let recv = RecvInstance::builder(&source, "Galicaster NDI Receiver") .bandwidth(NDIlib_recv_bandwidth_e::NDIlib_recv_bandwidth_highest) .color_format(NDIlib_recv_color_format_e::NDIlib_recv_color_format_UYVY_BGRA) - .allow_video_fields(true) + .allow_video_fields(false) .build(); let recv = match recv { None => { diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index c512f8b4..be7a3cf5 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -132,7 +132,6 @@ impl ObjectSubclass for NdiAudioSrc { ("rate", &gst::IntRange::::new(1, i32::MAX)), ("channels", &gst::IntRange::::new(1, i32::MAX)), ("layout", &"interleaved"), - ("channel-mask", &gst::Bitmask::new(0)), ], ); @@ -235,30 +234,6 @@ impl ElementImpl for NdiAudioSrc { } impl BaseSrcImpl for NdiAudioSrc { - fn set_caps( - &self, - element: &gst_base::BaseSrc, - caps: &gst::Caps, - ) -> Result<(), gst::LoggableError> { - let info = match gst_audio::AudioInfo::from_caps(caps) { - None => { - return Err(gst_loggable_error!( - self.cat, - "Failed to build `AudioInfo` from caps {}", - caps - )); - } - Some(info) => info, - }; - - gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); - - let mut state = self.state.lock().unwrap(); - state.info = Some(info); - - Ok(()) - } - fn start(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { *self.state.lock().unwrap() = Default::default(); @@ -305,39 +280,12 @@ impl BaseSrcImpl for NdiAudioSrc { } fn fixate(&self, element: &gst_base::BaseSrc, caps: gst::Caps) -> gst::Caps { - let receivers = HASHMAP_RECEIVERS.lock().unwrap(); - let state = self.state.lock().unwrap(); - - let receiver = receivers.get(&state.id_receiver.unwrap()).unwrap(); - - let recv = &receiver.ndi_instance; - - // FIXME: Should be done in create() and caps be updated as needed - - let audio_frame = - loop { - match recv.capture(false, true, false, 1000) { - Err(_) => unimplemented!(), - Ok(None) => continue, - Ok(Some(Frame::Audio(frame))) => break frame, - _ => unreachable!(), - } - }; - let mut caps = gst::Caps::truncate(caps); { let caps = caps.make_mut(); let s = caps.get_mut_structure(0).unwrap(); - s.fixate_field_nearest_int("rate", audio_frame.sample_rate()); - s.fixate_field_nearest_int("channels", audio_frame.no_channels()); - s.fixate_field_str("layout", "interleaved"); - s.set_value( - "channel-mask", - gst::Bitmask::new(gst_audio::AudioChannelPosition::get_fallback_mask( - audio_frame.no_channels() as u32, - )) - .to_send_value(), - ); + s.fixate_field_nearest_int("rate", 48_000); + s.fixate_field_nearest_int("channels", 2); } self.parent_fixate(element, caps) @@ -351,14 +299,7 @@ impl BaseSrcImpl for NdiAudioSrc { ) -> Result { // FIXME: Make sure to not have any mutexes locked while wait let settings = self.settings.lock().unwrap().clone(); - let state = self.state.lock().unwrap(); - let _info = match state.info { - None => { - gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); - return Err(gst::FlowError::NotNegotiated); - } - Some(ref info) => info.clone(), - }; + let mut state = self.state.lock().unwrap(); let receivers = HASHMAP_RECEIVERS.lock().unwrap(); let receiver = &receivers.get(&state.id_receiver.unwrap()).unwrap(); @@ -421,6 +362,21 @@ impl BaseSrcImpl for NdiAudioSrc { audio_frame ); + let info = gst_audio::AudioInfo::new( + gst_audio::AUDIO_FORMAT_S16, + audio_frame.sample_rate() as u32, + audio_frame.no_channels() as u32, + ) + .build() + .unwrap(); + + if state.info.as_ref() != Some(&info) { + let caps = info.to_caps().unwrap(); + state.info = Some(info); + gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); + element.set_caps(&caps).map_err(|_| gst::FlowError::NotNegotiated)?; + } + gst_log!( self.cat, obj: element, diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index e97d49ad..2e2c22ce 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -8,13 +8,13 @@ use gst_base; use gst_base::prelude::*; use gst_base::subclass::prelude::*; -use gst::Fraction; use gst_video; use std::sync::Mutex; use std::{i32, u32}; use ndi::*; +use ndisys; use connect_ndi; use stop_ndi; @@ -243,28 +243,6 @@ impl ElementImpl for NdiVideoSrc { } impl BaseSrcImpl for NdiVideoSrc { - fn set_caps( - &self, - element: &gst_base::BaseSrc, - caps: &gst::Caps, - ) -> Result<(), gst::LoggableError> { - let info = match gst_video::VideoInfo::from_caps(caps) { - None => { - return Err(gst_loggable_error!( - self.cat, - "Failed to build `VideoInfo` from caps {}", - caps - )); - } - Some(info) => info, - }; - gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); - - let mut state = self.state.lock().unwrap(); - state.info = Some(info); - Ok(()) - } - fn start(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { *self.state.lock().unwrap() = Default::default(); let mut state = self.state.lock().unwrap(); @@ -311,34 +289,17 @@ impl BaseSrcImpl for NdiVideoSrc { } fn fixate(&self, element: &gst_base::BaseSrc, caps: gst::Caps) -> gst::Caps { - let receivers = HASHMAP_RECEIVERS.lock().unwrap(); - let state = self.state.lock().unwrap(); - - let receiver = receivers.get(&state.id_receiver.unwrap()).unwrap(); - let recv = &receiver.ndi_instance; - - // FIXME: Should be done in create() and caps be updated as needed - let video_frame = - loop { - match recv.capture(true, false, false, 1000) { - Err(_) => unimplemented!(), - Ok(None) => continue, - Ok(Some(Frame::Video(frame))) => break frame, - _ => unreachable!(), - } - }; - let mut caps = gst::Caps::truncate(caps); { let caps = caps.make_mut(); let s = caps.get_mut_structure(0).unwrap(); - s.fixate_field_nearest_int("width", video_frame.xres()); - s.fixate_field_nearest_int("height", video_frame.yres()); - s.fixate_field_nearest_fraction( - "framerate", - Fraction::new(video_frame.frame_rate().0, video_frame.frame_rate().1), - ); + s.fixate_field_nearest_int("width", 1920); + s.fixate_field_nearest_int("height", 1080); + if s.has_field("pixel-aspect-ratio") { + s.fixate_field_nearest_fraction("pixel-aspect-ratio", gst::Fraction::new(1, 1)); + } } + self.parent_fixate(element, caps) } @@ -351,14 +312,7 @@ impl BaseSrcImpl for NdiVideoSrc { ) -> Result { // FIXME: Make sure to not have any mutexes locked while wait let settings = self.settings.lock().unwrap().clone(); - let state = self.state.lock().unwrap(); - let _info = match state.info { - None => { - gst_element_error!(element, gst::CoreError::Negotiation, ["Have no caps yet"]); - return Err(gst::FlowError::NotNegotiated); - } - Some(ref info) => info.clone(), - }; + let mut state = self.state.lock().unwrap(); let receivers = HASHMAP_RECEIVERS.lock().unwrap(); let receiver = &receivers.get(&state.id_receiver.unwrap()).unwrap(); @@ -421,6 +375,32 @@ impl BaseSrcImpl for NdiVideoSrc { video_frame ); + let par = gst::Fraction::approximate_f32(video_frame.picture_aspect_ratio()).unwrap() * + gst::Fraction::new(video_frame.yres(), video_frame.xres()); + + let info = gst_video::VideoInfo::new( + gst_video::VideoFormat::Uyvy, + video_frame.xres() as u32, + video_frame.yres() as u32, + ) + .fps(gst::Fraction::from(video_frame.frame_rate())) + .par(par) + .interlace_mode(if video_frame.frame_format_type() == ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_progressive { + gst_video::VideoInterlaceMode::Progressive + } else { + gst_video::VideoInterlaceMode::Interleaved + } + ) + .build() + .unwrap(); + + if state.info.as_ref() != Some(&info) { + let caps = info.to_caps().unwrap(); + state.info = Some(info); + gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); + element.set_caps(&caps).map_err(|_| gst::FlowError::NotNegotiated)?; + } + gst_log!( self.cat, obj: element, @@ -435,6 +415,8 @@ impl BaseSrcImpl for NdiVideoSrc { let buffer = buffer.get_mut().unwrap(); buffer.set_pts(pts); buffer.set_duration(duration); + + // FIXME: This assumes that the strides match up buffer.copy_from_slice(0, video_frame.data()).unwrap(); } From 75eb959c80a6c4c1618c4ac9ee1ce8b68a11d0e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Mon, 15 Jul 2019 19:27:00 +0300 Subject: [PATCH 128/199] Add support for all other video formats --- src/ndisys.rs | 15 +++--- src/ndivideosrc.rs | 130 ++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 132 insertions(+), 13 deletions(-) diff --git a/src/ndisys.rs b/src/ndisys.rs index 25c9adde..9958c202 100644 --- a/src/ndisys.rs +++ b/src/ndisys.rs @@ -101,12 +101,15 @@ pub enum NDIlib_recv_color_format_e { #[repr(u32)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum NDIlib_FourCC_type_e { - NDIlib_FourCC_type_UYVY = 1_498_831_189, - NDIlib_FourCC_type_BGRA = 1_095_911_234, - NDIlib_FourCC_type_BGRX = 1_481_787_202, - NDIlib_FourCC_type_RGBA = 1_094_862_674, - NDIlib_FourCC_type_RGBX = 1_480_738_642, - NDIlib_FourCC_type_UYVA = 1_096_178_005, + NDIlib_FourCC_type_UYVY = 0x59_56_59_55, + NDIlib_FourCC_type_YV12 = 0x32_31_56_59, + NDIlib_FourCC_type_NV12 = 0x32_31_56_4e, + NDIlib_FourCC_type_I420 = 0x30_32_34_49, + NDIlib_FourCC_type_BGRA = 0x41_52_47_42, + NDIlib_FourCC_type_BGRX = 0x58_52_47_42, + NDIlib_FourCC_type_RGBA = 0x41_42_47_52, + NDIlib_FourCC_type_RGBX = 0x58_42_47_52, + NDIlib_FourCC_type_UYVA = 0x41_56_56_55, } #[repr(u32)] diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index 2e2c22ce..f0e5ceb7 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -126,10 +126,14 @@ impl ObjectSubclass for NdiVideoSrc { ( "format", &gst::List::new(&[ - //TODO add all formats &gst_video::VideoFormat::Uyvy.to_string(), - //&gst_video::VideoFormat::Rgb.to_string(), - //&gst_video::VideoFormat::Gray8.to_string(), + &gst_video::VideoFormat::Yv12.to_string(), + &gst_video::VideoFormat::Nv12.to_string(), + &gst_video::VideoFormat::I420.to_string(), + &gst_video::VideoFormat::Bgra.to_string(), + &gst_video::VideoFormat::Bgrx.to_string(), + &gst_video::VideoFormat::Rgba.to_string(), + &gst_video::VideoFormat::Rgbx.to_string(), ]), ), ("width", &gst::IntRange::::new(0, i32::MAX)), @@ -375,11 +379,24 @@ impl BaseSrcImpl for NdiVideoSrc { video_frame ); + // YV12 and I420 are swapped in the NDI SDK compared to GStreamer + let format = match video_frame.fourcc() { + ndisys::NDIlib_FourCC_type_e::NDIlib_FourCC_type_UYVY => gst_video::VideoFormat::Uyvy, + ndisys::NDIlib_FourCC_type_e::NDIlib_FourCC_type_YV12 => gst_video::VideoFormat::I420, + ndisys::NDIlib_FourCC_type_e::NDIlib_FourCC_type_NV12 => gst_video::VideoFormat::Nv12, + ndisys::NDIlib_FourCC_type_e::NDIlib_FourCC_type_I420 => gst_video::VideoFormat::Yv12, + ndisys::NDIlib_FourCC_type_e::NDIlib_FourCC_type_BGRA => gst_video::VideoFormat::Bgra, + ndisys::NDIlib_FourCC_type_e::NDIlib_FourCC_type_BGRX => gst_video::VideoFormat::Bgrx, + ndisys::NDIlib_FourCC_type_e::NDIlib_FourCC_type_RGBA => gst_video::VideoFormat::Rgba, + ndisys::NDIlib_FourCC_type_e::NDIlib_FourCC_type_RGBX => gst_video::VideoFormat::Rgbx, + ndisys::NDIlib_FourCC_type_e::NDIlib_FourCC_type_UYVA => gst_video::VideoFormat::Uyvy, + }; + let par = gst::Fraction::approximate_f32(video_frame.picture_aspect_ratio()).unwrap() * gst::Fraction::new(video_frame.yres(), video_frame.xres()); let info = gst_video::VideoInfo::new( - gst_video::VideoFormat::Uyvy, + format, video_frame.xres() as u32, video_frame.yres() as u32, ) @@ -415,11 +432,110 @@ impl BaseSrcImpl for NdiVideoSrc { let buffer = buffer.get_mut().unwrap(); buffer.set_pts(pts); buffer.set_duration(duration); - - // FIXME: This assumes that the strides match up - buffer.copy_from_slice(0, video_frame.data()).unwrap(); } + let buffer = { + let mut vframe = gst_video::VideoFrame::from_buffer_writable(buffer, &state.info.as_ref().unwrap()).unwrap(); + + match format { + gst_video::VideoFormat::Uyvy | + gst_video::VideoFormat::Bgra | + gst_video::VideoFormat::Bgrx | + gst_video::VideoFormat::Rgba | + gst_video::VideoFormat::Rgbx => { + let line_bytes = if format == gst_video::VideoFormat::Uyvy { + 2 * vframe.width() as usize + } else { + 4 * vframe.width() as usize + }; + let dest_stride = vframe.plane_stride()[0] as usize; + let dest = vframe.plane_data_mut(0).unwrap(); + let src_stride = video_frame.line_stride_in_bytes() as usize; + let src = video_frame.data(); + + for (dest, src) in dest.chunks_exact_mut(dest_stride).zip(src.chunks_exact(src_stride)) { + dest.copy_from_slice(src); + dest.copy_from_slice(&src[..line_bytes]); + } + }, + gst_video::VideoFormat::Nv12 => { + // First plane + { + let line_bytes = vframe.width() as usize; + let dest_stride = vframe.plane_stride()[0] as usize; + let dest = vframe.plane_data_mut(0).unwrap(); + let src_stride = video_frame.line_stride_in_bytes() as usize; + let src = video_frame.data(); + + for (dest, src) in dest.chunks_exact_mut(dest_stride).zip(src.chunks_exact(src_stride)) { + dest.copy_from_slice(&src[..line_bytes]); + } + } + + // Second plane + { + let line_bytes = vframe.width() as usize; + let dest_stride = vframe.plane_stride()[1] as usize; + let dest = vframe.plane_data_mut(1).unwrap(); + let src_stride = video_frame.line_stride_in_bytes() as usize; + let src = &video_frame.data()[(video_frame.yres() as usize * src_stride)..]; + + for (dest, src) in dest.chunks_exact_mut(dest_stride).zip(src.chunks_exact(src_stride)) { + dest.copy_from_slice(&src[..line_bytes]); + } + } + + }, + gst_video::VideoFormat::Yv12 | + gst_video::VideoFormat::I420 => { + // First plane + { + let line_bytes = vframe.width() as usize; + let dest_stride = vframe.plane_stride()[0] as usize; + let dest = vframe.plane_data_mut(0).unwrap(); + let src_stride = video_frame.line_stride_in_bytes() as usize; + let src = video_frame.data(); + + for (dest, src) in dest.chunks_exact_mut(dest_stride).zip(src.chunks_exact(src_stride)) { + dest.copy_from_slice(&src[..line_bytes]); + } + } + + // Second plane + { + let line_bytes = (vframe.width() as usize + 1) / 2; + let dest_stride = vframe.plane_stride()[1] as usize; + let dest = vframe.plane_data_mut(1).unwrap(); + let src_stride = video_frame.line_stride_in_bytes() as usize; + let src_stride1 = video_frame.line_stride_in_bytes() as usize / 2; + let src = &video_frame.data()[(video_frame.yres() as usize * src_stride)..]; + + for (dest, src) in dest.chunks_exact_mut(dest_stride).zip(src.chunks_exact(src_stride1)) { + dest.copy_from_slice(&src[..line_bytes]); + } + } + + // Third plane + { + let line_bytes = (vframe.width() as usize + 1) / 2; + let dest_stride = vframe.plane_stride()[2] as usize; + let dest = vframe.plane_data_mut(2).unwrap(); + let src_stride = video_frame.line_stride_in_bytes() as usize; + let src_stride1 = video_frame.line_stride_in_bytes() as usize / 2; + let src = &video_frame.data()[(video_frame.yres() as usize * src_stride + + (video_frame.yres() as usize + 1) / 2 * src_stride1)..]; + + for (dest, src) in dest.chunks_exact_mut(dest_stride).zip(src.chunks_exact(src_stride1)) { + dest.copy_from_slice(&src[..line_bytes]); + } + } + }, + _ => unreachable!(), + } + + vframe.into_buffer() + }; + gst_log!(self.cat, obj: element, "Produced buffer {:?}", buffer); Ok(buffer) From 4ebcd78acc9cee10d3f82f681d911c814b611e62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Mon, 15 Jul 2019 19:42:54 +0300 Subject: [PATCH 129/199] Print frame timestamp/timecode in debug output --- src/ndiaudiosrc.rs | 15 +++++++++++++-- src/ndivideosrc.rs | 14 ++++++++++++-- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index be7a3cf5..b2408752 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -15,6 +15,7 @@ use std::{i32, u32}; use connect_ndi; use stop_ndi; use ndi::*; +use ndisys; use HASHMAP_RECEIVERS; @@ -358,8 +359,18 @@ impl BaseSrcImpl for NdiAudioSrc { gst_log!( self.cat, obj: element, - "NDI audio frame received: {:?}", - audio_frame + "NDI audio frame received: {:?} with timecode {} and timestamp {}", + audio_frame, + if audio_frame.timecode() == ndisys::NDIlib_send_timecode_synthesize { + gst::CLOCK_TIME_NONE + } else { + gst::ClockTime::from(audio_frame.timecode() as u64 * 100) + }, + if audio_frame.timestamp() == ndisys::NDIlib_recv_timestamp_undefined { + gst::CLOCK_TIME_NONE + } else { + gst::ClockTime::from(audio_frame.timestamp() as u64 * 100) + }, ); let info = gst_audio::AudioInfo::new( diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index f0e5ceb7..9c97d86a 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -375,8 +375,18 @@ impl BaseSrcImpl for NdiVideoSrc { gst_log!( self.cat, obj: element, - "NDI video frame received: {:?}", - video_frame + "NDI video frame received: {:?} with timecode {} and timestamp {}", + video_frame, + if video_frame.timecode() == ndisys::NDIlib_send_timecode_synthesize { + gst::CLOCK_TIME_NONE + } else { + gst::ClockTime::from(video_frame.timecode() as u64 * 100) + }, + if video_frame.timestamp() == ndisys::NDIlib_recv_timestamp_undefined { + gst::CLOCK_TIME_NONE + } else { + gst::ClockTime::from(video_frame.timestamp() as u64 * 100) + }, ); // YV12 and I420 are swapped in the NDI SDK compared to GStreamer From cd8bca11aac0f84e5807d2fd7a13d57ab353f017 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Mon, 15 Jul 2019 19:43:22 +0300 Subject: [PATCH 130/199] Run everything to rustfmt to give it a consistent indentation --- src/lib.rs | 18 +++---- src/ndiaudiosrc.rs | 58 ++++++++++---------- src/ndisys.rs | 15 ++++-- src/ndivideosrc.rs | 131 +++++++++++++++++++++++++-------------------- 4 files changed, 123 insertions(+), 99 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1bdc571d..4c1b2028 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,17 +12,17 @@ extern crate gstreamer_video as gst_video; extern crate lazy_static; extern crate byte_slice_cast; -pub mod ndisys; pub mod ndi; mod ndiaudiosrc; +pub mod ndisys; mod ndivideosrc; -use ndisys::*; use ndi::*; +use ndisys::*; use std::collections::HashMap; -use std::sync::Mutex; use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Mutex; fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { if !ndi::initialize() { @@ -87,7 +87,7 @@ fn connect_ndi( ["Cannot run NDI: NDIlib_find_create_v2 error"] ); return None; - }, + } Some(find) => find, }; @@ -106,15 +106,15 @@ fn connect_ndi( return None; } - let source = sources.iter().find(|s| { - s.ndi_name() == stream_name || s.ip_address() == ip - }); + let source = sources + .iter() + .find(|s| s.ndi_name() == stream_name || s.ip_address() == ip); let source = match source { None => { gst_element_error!(element, gst::ResourceError::OpenRead, ["Stream not found"]); return None; - }, + } Some(source) => source, }; @@ -140,7 +140,7 @@ fn connect_ndi( ["Cannot run NDI: NDIlib_recv_create_v3 error"] ); return None; - }, + } Some(recv) => recv, }; diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index b2408752..6a9b067e 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -13,9 +13,9 @@ use std::sync::Mutex; use std::{i32, u32}; use connect_ndi; -use stop_ndi; use ndi::*; use ndisys; +use stop_ndi; use HASHMAP_RECEIVERS; @@ -231,8 +231,7 @@ impl ObjectImpl for NdiAudioSrc { } } -impl ElementImpl for NdiAudioSrc { -} +impl ElementImpl for NdiAudioSrc {} impl BaseSrcImpl for NdiAudioSrc { fn start(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { @@ -309,11 +308,9 @@ impl BaseSrcImpl for NdiAudioSrc { let clock = element.get_clock().unwrap(); let mut count_frame_none = 0; - let audio_frame = - loop { + let audio_frame = loop { // FIXME: make interruptable - let res = - loop { + let res = loop { match recv.capture(false, true, false, 1000) { Err(_) => break Err(()), Ok(None) => break Ok(None), @@ -326,7 +323,7 @@ impl BaseSrcImpl for NdiAudioSrc { Err(_) => { gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type error received, assuming that the source closed the stream...."]); return Err(gst::FlowError::Error); - }, + } Ok(None) if settings.loss_threshold != 0 => { if count_frame_none < settings.loss_threshold { count_frame_none += 1; @@ -334,16 +331,12 @@ impl BaseSrcImpl for NdiAudioSrc { } gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type none received, assuming that the source closed the stream...."]); return Err(gst::FlowError::Error); - }, + } Ok(None) => { - gst_debug!( - self.cat, - obj: element, - "No audio frame received, retry" - ); + gst_debug!(self.cat, obj: element, "No audio frame received, retry"); count_frame_none += 1; continue; - }, + } Ok(Some(frame)) => frame, }; @@ -374,18 +367,20 @@ impl BaseSrcImpl for NdiAudioSrc { ); let info = gst_audio::AudioInfo::new( - gst_audio::AUDIO_FORMAT_S16, - audio_frame.sample_rate() as u32, - audio_frame.no_channels() as u32, - ) - .build() - .unwrap(); + gst_audio::AUDIO_FORMAT_S16, + audio_frame.sample_rate() as u32, + audio_frame.no_channels() as u32, + ) + .build() + .unwrap(); if state.info.as_ref() != Some(&info) { let caps = info.to_caps().unwrap(); state.info = Some(info); gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); - element.set_caps(&caps).map_err(|_| gst::FlowError::NotNegotiated)?; + element + .set_caps(&caps) + .map_err(|_| gst::FlowError::NotNegotiated)?; } gst_log!( @@ -399,17 +394,24 @@ impl BaseSrcImpl for NdiAudioSrc { let buff_size = (audio_frame.no_samples() * 2 * audio_frame.no_channels()) as usize; let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); { - let duration = gst::SECOND.mul_div_floor(audio_frame.no_samples() as u64, audio_frame.sample_rate() as u64).unwrap_or(gst::CLOCK_TIME_NONE); + let duration = gst::SECOND + .mul_div_floor( + audio_frame.no_samples() as u64, + audio_frame.sample_rate() as u64, + ) + .unwrap_or(gst::CLOCK_TIME_NONE); let buffer = buffer.get_mut().unwrap(); buffer.set_pts(pts); buffer.set_duration(duration); - audio_frame.copy_to_interleaved_16s(buffer - .map_writable() - .unwrap() - .as_mut_slice_of::() - .unwrap()); + audio_frame.copy_to_interleaved_16s( + buffer + .map_writable() + .unwrap() + .as_mut_slice_of::() + .unwrap(), + ); } gst_log!(self.cat, obj: element, "Produced buffer {:?}", buffer); diff --git a/src/ndisys.rs b/src/ndisys.rs index 9958c202..70bd1450 100644 --- a/src/ndisys.rs +++ b/src/ndisys.rs @@ -1,8 +1,17 @@ #![allow(non_camel_case_types, non_upper_case_globals, non_snake_case)] -#[cfg_attr(all(target_arch = "x86_64", target_os = "windows"), link(name = "Processing.NDI.Lib.x64"))] -#[cfg_attr(all(target_arch = "x86", target_os = "windows"), link(name = "Processing.NDI.Lib.x86"))] -#[cfg_attr(not(any(target_os = "windows", target_os = "macos")), link(name = "ndi"))] +#[cfg_attr( + all(target_arch = "x86_64", target_os = "windows"), + link(name = "Processing.NDI.Lib.x64") +)] +#[cfg_attr( + all(target_arch = "x86", target_os = "windows"), + link(name = "Processing.NDI.Lib.x86") +)] +#[cfg_attr( + not(any(target_os = "windows", target_os = "macos")), + link(name = "ndi") +)] extern "C" { pub fn NDIlib_initialize() -> bool; pub fn NDIlib_find_create_v2( diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index 9c97d86a..8e8dd510 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -243,20 +243,14 @@ impl ObjectImpl for NdiVideoSrc { } } -impl ElementImpl for NdiVideoSrc { -} +impl ElementImpl for NdiVideoSrc {} impl BaseSrcImpl for NdiVideoSrc { fn start(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { *self.state.lock().unwrap() = Default::default(); let mut state = self.state.lock().unwrap(); let settings = self.settings.lock().unwrap().clone(); - state.id_receiver = connect_ndi( - self.cat, - element, - &settings.ip, - &settings.stream_name, - ); + state.id_receiver = connect_ndi(self.cat, element, &settings.ip, &settings.stream_name); // settings.id_receiver exists match state.id_receiver { @@ -325,11 +319,9 @@ impl BaseSrcImpl for NdiVideoSrc { let clock = element.get_clock().unwrap(); let mut count_frame_none = 0; - let video_frame = - loop { + let video_frame = loop { // FIXME: make interruptable - let res = - loop { + let res = loop { match recv.capture(true, false, false, 1000) { Err(_) => break Err(()), Ok(None) => break Ok(None), @@ -342,7 +334,7 @@ impl BaseSrcImpl for NdiVideoSrc { Err(_) => { gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type error received, assuming that the source closed the stream...."]); return Err(gst::FlowError::Error); - }, + } Ok(None) if settings.loss_threshold != 0 => { if count_frame_none < settings.loss_threshold { count_frame_none += 1; @@ -350,16 +342,12 @@ impl BaseSrcImpl for NdiVideoSrc { } gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type none received, assuming that the source closed the stream...."]); return Err(gst::FlowError::Error); - }, + } Ok(None) => { - gst_debug!( - self.cat, - obj: element, - "No video frame received, retry" - ); + gst_debug!(self.cat, obj: element, "No video frame received, retry"); count_frame_none += 1; continue; - }, + } Ok(Some(frame)) => frame, }; @@ -402,30 +390,32 @@ impl BaseSrcImpl for NdiVideoSrc { ndisys::NDIlib_FourCC_type_e::NDIlib_FourCC_type_UYVA => gst_video::VideoFormat::Uyvy, }; - let par = gst::Fraction::approximate_f32(video_frame.picture_aspect_ratio()).unwrap() * - gst::Fraction::new(video_frame.yres(), video_frame.xres()); + let par = gst::Fraction::approximate_f32(video_frame.picture_aspect_ratio()).unwrap() + * gst::Fraction::new(video_frame.yres(), video_frame.xres()); - let info = gst_video::VideoInfo::new( - format, - video_frame.xres() as u32, - video_frame.yres() as u32, - ) - .fps(gst::Fraction::from(video_frame.frame_rate())) - .par(par) - .interlace_mode(if video_frame.frame_format_type() == ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_progressive { - gst_video::VideoInterlaceMode::Progressive - } else { - gst_video::VideoInterlaceMode::Interleaved - } - ) - .build() - .unwrap(); + let info = + gst_video::VideoInfo::new(format, video_frame.xres() as u32, video_frame.yres() as u32) + .fps(gst::Fraction::from(video_frame.frame_rate())) + .par(par) + .interlace_mode( + if video_frame.frame_format_type() + == ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_progressive + { + gst_video::VideoInterlaceMode::Progressive + } else { + gst_video::VideoInterlaceMode::Interleaved + }, + ) + .build() + .unwrap(); if state.info.as_ref() != Some(&info) { let caps = info.to_caps().unwrap(); state.info = Some(info); gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); - element.set_caps(&caps).map_err(|_| gst::FlowError::NotNegotiated)?; + element + .set_caps(&caps) + .map_err(|_| gst::FlowError::NotNegotiated)?; } gst_log!( @@ -438,21 +428,28 @@ impl BaseSrcImpl for NdiVideoSrc { let buff_size = (video_frame.yres() * video_frame.line_stride_in_bytes()) as usize; let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); { - let duration = gst::SECOND.mul_div_floor(video_frame.frame_rate().1 as u64, video_frame.frame_rate().0 as u64).unwrap_or(gst::CLOCK_TIME_NONE); + let duration = gst::SECOND + .mul_div_floor( + video_frame.frame_rate().1 as u64, + video_frame.frame_rate().0 as u64, + ) + .unwrap_or(gst::CLOCK_TIME_NONE); let buffer = buffer.get_mut().unwrap(); buffer.set_pts(pts); buffer.set_duration(duration); } let buffer = { - let mut vframe = gst_video::VideoFrame::from_buffer_writable(buffer, &state.info.as_ref().unwrap()).unwrap(); + let mut vframe = + gst_video::VideoFrame::from_buffer_writable(buffer, &state.info.as_ref().unwrap()) + .unwrap(); match format { - gst_video::VideoFormat::Uyvy | - gst_video::VideoFormat::Bgra | - gst_video::VideoFormat::Bgrx | - gst_video::VideoFormat::Rgba | - gst_video::VideoFormat::Rgbx => { + gst_video::VideoFormat::Uyvy + | gst_video::VideoFormat::Bgra + | gst_video::VideoFormat::Bgrx + | gst_video::VideoFormat::Rgba + | gst_video::VideoFormat::Rgbx => { let line_bytes = if format == gst_video::VideoFormat::Uyvy { 2 * vframe.width() as usize } else { @@ -463,11 +460,14 @@ impl BaseSrcImpl for NdiVideoSrc { let src_stride = video_frame.line_stride_in_bytes() as usize; let src = video_frame.data(); - for (dest, src) in dest.chunks_exact_mut(dest_stride).zip(src.chunks_exact(src_stride)) { + for (dest, src) in dest + .chunks_exact_mut(dest_stride) + .zip(src.chunks_exact(src_stride)) + { dest.copy_from_slice(src); dest.copy_from_slice(&src[..line_bytes]); } - }, + } gst_video::VideoFormat::Nv12 => { // First plane { @@ -477,7 +477,10 @@ impl BaseSrcImpl for NdiVideoSrc { let src_stride = video_frame.line_stride_in_bytes() as usize; let src = video_frame.data(); - for (dest, src) in dest.chunks_exact_mut(dest_stride).zip(src.chunks_exact(src_stride)) { + for (dest, src) in dest + .chunks_exact_mut(dest_stride) + .zip(src.chunks_exact(src_stride)) + { dest.copy_from_slice(&src[..line_bytes]); } } @@ -490,14 +493,15 @@ impl BaseSrcImpl for NdiVideoSrc { let src_stride = video_frame.line_stride_in_bytes() as usize; let src = &video_frame.data()[(video_frame.yres() as usize * src_stride)..]; - for (dest, src) in dest.chunks_exact_mut(dest_stride).zip(src.chunks_exact(src_stride)) { + for (dest, src) in dest + .chunks_exact_mut(dest_stride) + .zip(src.chunks_exact(src_stride)) + { dest.copy_from_slice(&src[..line_bytes]); } } - - }, - gst_video::VideoFormat::Yv12 | - gst_video::VideoFormat::I420 => { + } + gst_video::VideoFormat::Yv12 | gst_video::VideoFormat::I420 => { // First plane { let line_bytes = vframe.width() as usize; @@ -506,7 +510,10 @@ impl BaseSrcImpl for NdiVideoSrc { let src_stride = video_frame.line_stride_in_bytes() as usize; let src = video_frame.data(); - for (dest, src) in dest.chunks_exact_mut(dest_stride).zip(src.chunks_exact(src_stride)) { + for (dest, src) in dest + .chunks_exact_mut(dest_stride) + .zip(src.chunks_exact(src_stride)) + { dest.copy_from_slice(&src[..line_bytes]); } } @@ -520,7 +527,10 @@ impl BaseSrcImpl for NdiVideoSrc { let src_stride1 = video_frame.line_stride_in_bytes() as usize / 2; let src = &video_frame.data()[(video_frame.yres() as usize * src_stride)..]; - for (dest, src) in dest.chunks_exact_mut(dest_stride).zip(src.chunks_exact(src_stride1)) { + for (dest, src) in dest + .chunks_exact_mut(dest_stride) + .zip(src.chunks_exact(src_stride1)) + { dest.copy_from_slice(&src[..line_bytes]); } } @@ -532,14 +542,17 @@ impl BaseSrcImpl for NdiVideoSrc { let dest = vframe.plane_data_mut(2).unwrap(); let src_stride = video_frame.line_stride_in_bytes() as usize; let src_stride1 = video_frame.line_stride_in_bytes() as usize / 2; - let src = &video_frame.data()[(video_frame.yres() as usize * src_stride + - (video_frame.yres() as usize + 1) / 2 * src_stride1)..]; + let src = &video_frame.data()[(video_frame.yres() as usize * src_stride + + (video_frame.yres() as usize + 1) / 2 * src_stride1)..]; - for (dest, src) in dest.chunks_exact_mut(dest_stride).zip(src.chunks_exact(src_stride1)) { + for (dest, src) in dest + .chunks_exact_mut(dest_stride) + .zip(src.chunks_exact(src_stride1)) + { dest.copy_from_slice(&src[..line_bytes]); } } - }, + } _ => unreachable!(), } From b91d23521f4e95d953fe089ae23edbf0b366884f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Mon, 15 Jul 2019 19:43:55 +0300 Subject: [PATCH 131/199] Remove unnecessary TODO comment --- src/ndiaudiosrc.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index 6a9b067e..1c0ab758 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -123,12 +123,7 @@ impl ObjectSubclass for NdiAudioSrc { &[ ( "format", - &gst::List::new(&[ - //TODO add more formats? - //&gst_audio::AUDIO_FORMAT_F32.to_string(), - //&gst_audio::AUDIO_FORMAT_F64.to_string(), - &gst_audio::AUDIO_FORMAT_S16.to_string(), - ]), + &gst::List::new(&[&gst_audio::AUDIO_FORMAT_S16.to_string()]), ), ("rate", &gst::IntRange::::new(1, i32::MAX)), ("channels", &gst::IntRange::::new(1, i32::MAX)), From a500b5297b513101da5024f9564eb9d207d84279 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Mon, 15 Jul 2019 19:46:22 +0300 Subject: [PATCH 132/199] Simplify boolean expression --- src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4c1b2028..dc3d511f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,13 +66,13 @@ fn connect_ndi( for val in receivers.values_mut() { if val.ip == ip || val.stream_name == stream_name { - if (val.audio && val.video) || (val.audio && !video) || (val.video && video) { + if (val.video || !video) && (val.audio || video) { continue; } else { if video { - val.video = video; + val.video = true; } else { - val.audio = !video; + val.audio = true; } return Some(val.id); } From fabcc654607680928f45a154f4bee039cd142ce4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 16 Jul 2019 12:14:37 +0300 Subject: [PATCH 133/199] Properly support interlaced video and signal it correctly in the caps and buffer flags --- Cargo.toml | 8 +++- src/lib.rs | 5 ++- src/ndi.rs | 19 +++++++-- src/ndivideosrc.rs | 103 +++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 124 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b774dde2..9f039393 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,16 +8,20 @@ description = "NewTek NDI Plugin" [dependencies] glib = { version = "0.8.0", features = ["subclassing"] } -gstreamer = { version = "0.14.0", features = ["subclassing"] } +gstreamer = { version = "0.14.3", features = ["subclassing"] } gstreamer-base = { version = "0.14.0", features = ["subclassing"] } gstreamer-audio = "0.14.0" -gstreamer-video = "0.14.0" +gstreamer-video = "0.14.3" lazy_static = "1.1.0" byte-slice-cast = "0.2.0" [build-dependencies] gst-plugin-version-helper = "0.1" +[features] +default = ["interlaced-fields"] +interlaced-fields = ["gstreamer/v1_16", "gstreamer-video/v1_16"] + [lib] name = "gstndi" crate-type = ["cdylib"] diff --git a/src/lib.rs b/src/lib.rs index dc3d511f..576978ab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -127,10 +127,13 @@ fn connect_ndi( source.ip_address(), ); + // FIXME: Property for the name and bandwidth + // FIXME: Ideally we would use NDIlib_recv_color_format_fastest here but that seems to be + // broken with interlaced content currently let recv = RecvInstance::builder(&source, "Galicaster NDI Receiver") .bandwidth(NDIlib_recv_bandwidth_e::NDIlib_recv_bandwidth_highest) .color_format(NDIlib_recv_color_format_e::NDIlib_recv_color_format_UYVY_BGRA) - .allow_video_fields(false) + .allow_video_fields(true) .build(); let recv = match recv { None => { diff --git a/src/ndi.rs b/src/ndi.rs index e811868e..6f7d6724 100644 --- a/src/ndi.rs +++ b/src/ndi.rs @@ -360,13 +360,24 @@ impl<'a> VideoFrame<'a> { } pub fn data(&self) -> &[u8] { + // 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_in_bytes() / 2 + } else { + self.yres() * self.line_stride_in_bytes() + }; + unsafe { use std::slice; match self { - VideoFrame::Borrowed(ref frame, _) => slice::from_raw_parts( - frame.p_data as *const u8, - (frame.yres * frame.line_stride_in_bytes) as usize, - ), + VideoFrame::Borrowed(ref frame, _) => { + slice::from_raw_parts(frame.p_data as *const u8, frame_size as usize) + } } } } diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index 8e8dd510..de1d24e8 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -9,6 +9,7 @@ use gst_base::prelude::*; use gst_base::subclass::prelude::*; use gst_video; +use gst_video::prelude::*; use std::sync::Mutex; use std::{i32, u32}; @@ -148,6 +149,23 @@ impl ObjectSubclass for NdiVideoSrc { ], ); + #[cfg(feature = "interlaced-fields")] + let caps = { + let mut tmp = caps.copy(); + { + let tmp = tmp.get_mut().unwrap(); + tmp.set_features_simple(Some(gst::CapsFeatures::new(&["format:Interlaced"]))); + } + + let mut caps = caps; + { + let caps = caps.get_mut().unwrap(); + caps.append(tmp); + } + + caps + }; + let src_pad_template = gst::PadTemplate::new( "src", gst::PadDirection::Src, @@ -393,7 +411,48 @@ impl BaseSrcImpl for NdiVideoSrc { let par = gst::Fraction::approximate_f32(video_frame.picture_aspect_ratio()).unwrap() * gst::Fraction::new(video_frame.yres(), video_frame.xres()); - let info = + #[cfg(feature = "interlaced-fields")] + let info = { + let mut builder = gst_video::VideoInfo::new( + format, + video_frame.xres() as u32, + video_frame.yres() as u32, + ) + .fps(gst::Fraction::from(video_frame.frame_rate())) + .par(par) + .interlace_mode(match video_frame.frame_format_type() { + ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_progressive => { + gst_video::VideoInterlaceMode::Progressive + } + ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_interleaved => { + gst_video::VideoInterlaceMode::Interleaved + } + _ => gst_video::VideoInterlaceMode::Alternate, + }); + + /* Requires GStreamer 1.12 at least */ + if video_frame.frame_format_type() + == ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_interleaved + { + builder = builder.field_order(gst_video::VideoFieldOrder::TopFieldFirst); + } + + builder.build().unwrap() + }; + + #[cfg(not(feature = "interlaced-fields"))] + let info = if video_frame.frame_format_type() + != ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_progressive + && video_frame.frame_format_type() + != ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_interleaved + { + gst_element_error!( + element, + gst::StreamError::Format, + ["Separate field interlacing not supported"] + ); + return Err(gst::FlowError::NotNegotiated); + } else { gst_video::VideoInfo::new(format, video_frame.xres() as u32, video_frame.yres() as u32) .fps(gst::Fraction::from(video_frame.frame_rate())) .par(par) @@ -407,7 +466,8 @@ impl BaseSrcImpl for NdiVideoSrc { }, ) .build() - .unwrap(); + .unwrap() + }; if state.info.as_ref() != Some(&info) { let caps = info.to_caps().unwrap(); @@ -425,8 +485,7 @@ impl BaseSrcImpl for NdiVideoSrc { pts ); - let buff_size = (video_frame.yres() * video_frame.line_stride_in_bytes()) as usize; - let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); + let mut buffer = gst::Buffer::with_size(state.info.as_ref().unwrap().size()).unwrap(); { let duration = gst::SECOND .mul_div_floor( @@ -437,6 +496,42 @@ impl BaseSrcImpl for NdiVideoSrc { let buffer = buffer.get_mut().unwrap(); buffer.set_pts(pts); buffer.set_duration(duration); + + #[cfg(feature = "interlaced-fields")] + { + match video_frame.frame_format_type() { + ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_interleaved => { + buffer.set_video_flags( + gst_video::VideoBufferFlags::INTERLACED + | gst_video::VideoBufferFlags::TFF, + ); + } + ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_field_0 => { + buffer.set_video_flags( + gst_video::VideoBufferFlags::INTERLACED + | gst_video::VideoBufferFlags::TOP_FIELD, + ); + } + ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_field_1 => { + buffer.set_video_flags( + gst_video::VideoBufferFlags::INTERLACED + | gst_video::VideoBufferFlags::BOTTOM_FIELD, + ); + } + _ => (), + }; + } + + #[cfg(not(feature = "interlaced-fields"))] + { + if video_frame.frame_format_type() + == ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_interleaved + { + buffer.set_video_flags( + gst_video::VideoBufferFlags::INTERLACED | gst_video::VideoBufferFlags::TFF, + ); + } + } } let buffer = { From a4890d329526b3f8a3a845beccb6a319f029433f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 16 Jul 2019 16:03:15 +0300 Subject: [PATCH 134/199] Add timestamp/timecode into ReferenceTimestampMeta on every buffer --- Cargo.toml | 3 ++- src/lib.rs | 10 ++++++++++ src/ndiaudiosrc.rs | 22 ++++++++++++++++++++++ src/ndivideosrc.rs | 22 ++++++++++++++++++++++ 4 files changed, 56 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 9f039393..c97cccf9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,8 +19,9 @@ byte-slice-cast = "0.2.0" gst-plugin-version-helper = "0.1" [features] -default = ["interlaced-fields"] +default = ["interlaced-fields", "reference-timestamps"] interlaced-fields = ["gstreamer/v1_16", "gstreamer-video/v1_16"] +reference-timestamps = ["gstreamer/v1_14"] [lib] name = "gstndi" diff --git a/src/lib.rs b/src/lib.rs index 576978ab..c7ab76e2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,6 +48,16 @@ lazy_static! { let m = HashMap::new(); Mutex::new(m) }; + + #[cfg(feature = "reference-timestamps")] + static ref TIMECODE_CAPS: gst::Caps = { + gst::Caps::new_simple("timestamp/x-ndi-timecode", &[]) + }; + + #[cfg(feature = "reference-timestamps")] + static ref TIMESTAMP_CAPS: gst::Caps = { + gst::Caps::new_simple("timestamp/x-ndi-timestamp", &[]) + }; } static ID_RECEIVER: AtomicUsize = AtomicUsize::new(0); diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index 1c0ab758..cb879911 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -18,6 +18,10 @@ use ndisys; use stop_ndi; use HASHMAP_RECEIVERS; +#[cfg(feature = "reference-timestamps")] +use TIMECODE_CAPS; +#[cfg(feature = "reference-timestamps")] +use TIMESTAMP_CAPS; use byte_slice_cast::AsMutSliceOf; @@ -400,6 +404,24 @@ impl BaseSrcImpl for NdiAudioSrc { buffer.set_pts(pts); buffer.set_duration(duration); + #[cfg(feature = "reference-timestamps")] + { + gst::ReferenceTimestampMeta::add( + buffer, + &*TIMECODE_CAPS, + gst::ClockTime::from(audio_frame.timecode() as u64 * 100), + gst::CLOCK_TIME_NONE, + ); + if audio_frame.timestamp() != ndisys::NDIlib_recv_timestamp_undefined { + gst::ReferenceTimestampMeta::add( + buffer, + &*TIMESTAMP_CAPS, + gst::ClockTime::from(audio_frame.timestamp() as u64 * 100), + gst::CLOCK_TIME_NONE, + ); + } + } + audio_frame.copy_to_interleaved_16s( buffer .map_writable() diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index de1d24e8..daf2192d 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -21,6 +21,10 @@ use connect_ndi; use stop_ndi; use HASHMAP_RECEIVERS; +#[cfg(feature = "reference-timestamps")] +use TIMECODE_CAPS; +#[cfg(feature = "reference-timestamps")] +use TIMESTAMP_CAPS; #[derive(Debug, Clone)] struct Settings { @@ -497,6 +501,24 @@ impl BaseSrcImpl for NdiVideoSrc { buffer.set_pts(pts); buffer.set_duration(duration); + #[cfg(feature = "reference-timestamps")] + { + gst::ReferenceTimestampMeta::add( + buffer, + &*TIMECODE_CAPS, + gst::ClockTime::from(video_frame.timecode() as u64 * 100), + gst::CLOCK_TIME_NONE, + ); + if video_frame.timestamp() != ndisys::NDIlib_recv_timestamp_undefined { + gst::ReferenceTimestampMeta::add( + buffer, + &*TIMESTAMP_CAPS, + gst::ClockTime::from(video_frame.timestamp() as u64 * 100), + gst::CLOCK_TIME_NONE, + ); + } + } + #[cfg(feature = "interlaced-fields")] { match video_frame.frame_format_type() { From 8b00e803169389829625ea00d972bdbc9565dfa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 16 Jul 2019 17:43:03 +0300 Subject: [PATCH 135/199] Add property for selecting between different timestamping modes --- Cargo.toml | 1 + src/lib.rs | 100 ++++++++++++++++++++++++++++++++ src/ndiaudiosrc.rs | 141 +++++++++++++++++++++++++++++++++++---------- src/ndivideosrc.rs | 138 ++++++++++++++++++++++++++++++++++---------- 4 files changed, 321 insertions(+), 59 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c97cccf9..d81e64e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ description = "NewTek NDI Plugin" [dependencies] glib = { version = "0.8.0", features = ["subclassing"] } +gobject-sys = "0.9" gstreamer = { version = "0.14.3", features = ["subclassing"] } gstreamer-base = { version = "0.14.0", features = ["subclassing"] } gstreamer-audio = "0.14.0" diff --git a/src/lib.rs b/src/lib.rs index c7ab76e2..4bef1639 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,6 +24,14 @@ use std::collections::HashMap; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Mutex; +#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] +#[repr(u32)] +pub enum TimestampMode { + ReceiveTime = 0, + Timecode = 1, + Timestamp = 2, +} + fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { if !ndi::initialize() { return Err(glib_bool_error!("Cannot initialize NDI")); @@ -199,6 +207,98 @@ fn stop_ndi(cat: gst::DebugCategory, element: &gst_base::BaseSrc, id: usize) -> true } +impl glib::translate::ToGlib for TimestampMode { + type GlibType = i32; + + fn to_glib(&self) -> i32 { + *self as i32 + } +} + +impl glib::translate::FromGlib for TimestampMode { + fn from_glib(value: i32) -> Self { + match value { + 0 => TimestampMode::ReceiveTime, + 1 => TimestampMode::Timecode, + 2 => TimestampMode::Timestamp, + _ => unreachable!(), + } + } +} + +impl StaticType for TimestampMode { + fn static_type() -> glib::Type { + timestamp_mode_get_type() + } +} + +impl<'a> glib::value::FromValueOptional<'a> for TimestampMode { + unsafe fn from_value_optional(value: &glib::Value) -> Option { + Some(glib::value::FromValue::from_value(value)) + } +} + +impl<'a> glib::value::FromValue<'a> for TimestampMode { + unsafe fn from_value(value: &glib::Value) -> Self { + use glib::translate::ToGlibPtr; + + glib::translate::from_glib(gobject_sys::g_value_get_enum(value.to_glib_none().0)) + } +} + +impl glib::value::SetValue for TimestampMode { + unsafe fn set_value(value: &mut glib::Value, this: &Self) { + use glib::translate::{ToGlib, ToGlibPtrMut}; + + gobject_sys::g_value_set_enum(value.to_glib_none_mut().0, this.to_glib()) + } +} + +fn timestamp_mode_get_type() -> glib::Type { + use std::sync::Once; + static ONCE: Once = Once::new(); + static mut TYPE: glib::Type = glib::Type::Invalid; + + ONCE.call_once(|| { + use std::ffi; + use std::ptr; + + static mut VALUES: [gobject_sys::GEnumValue; 4] = [ + gobject_sys::GEnumValue { + value: TimestampMode::ReceiveTime as i32, + value_name: b"Receive Time\0" as *const _ as *const _, + value_nick: b"receive-time\0" as *const _ as *const _, + }, + gobject_sys::GEnumValue { + value: TimestampMode::Timecode as i32, + value_name: b"NDI Timecode\0" as *const _ as *const _, + value_nick: b"timecode\0" as *const _ as *const _, + }, + gobject_sys::GEnumValue { + value: TimestampMode::Timestamp as i32, + value_name: b"NDI Timestamp\0" as *const _ as *const _, + value_nick: b"timestamp\0" as *const _ as *const _, + }, + gobject_sys::GEnumValue { + value: 0, + value_name: ptr::null(), + value_nick: ptr::null(), + }, + ]; + + let name = ffi::CString::new("GstNdiTimestampMode").unwrap(); + unsafe { + let type_ = gobject_sys::g_enum_register_static(name.as_ptr(), VALUES.as_ptr()); + TYPE = glib::translate::from_glib(type_); + } + }); + + unsafe { + assert_ne!(TYPE, glib::Type::Invalid); + TYPE + } +} + gst_plugin_define!( ndi, env!("CARGO_PKG_DESCRIPTION"), diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index cb879911..897f3342 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -17,6 +17,7 @@ use ndi::*; use ndisys; use stop_ndi; +use TimestampMode; use HASHMAP_RECEIVERS; #[cfg(feature = "reference-timestamps")] use TIMECODE_CAPS; @@ -30,6 +31,7 @@ struct Settings { stream_name: String, ip: String, loss_threshold: u32, + timestamp_mode: TimestampMode, } impl Default for Settings { @@ -38,32 +40,33 @@ impl Default for Settings { stream_name: String::from("Fixed ndi stream name"), ip: String::from(""), loss_threshold: 5, + timestamp_mode: TimestampMode::ReceiveTime, } } } -static PROPERTIES: [subclass::Property; 3] = [ - subclass::Property("stream-name", |_| { +static PROPERTIES: [subclass::Property; 4] = [ + subclass::Property("stream-name", |name| { glib::ParamSpec::string( - "stream-name", - "Sream Name", + name, + "Stream Name", "Name of the streaming device", None, glib::ParamFlags::READWRITE, ) }), - subclass::Property("ip", |_| { + subclass::Property("ip", |name| { glib::ParamSpec::string( - "ip", + name, "Stream IP", "IP of the streaming device. Ex: 127.0.0.1:5961", None, glib::ParamFlags::READWRITE, ) }), - subclass::Property("loss-threshold", |_| { + subclass::Property("loss-threshold", |name| { glib::ParamSpec::uint( - "loss-threshold", + name, "Loss threshold", "Loss threshold", 0, @@ -72,11 +75,22 @@ static PROPERTIES: [subclass::Property; 3] = [ glib::ParamFlags::READWRITE, ) }), + subclass::Property("timestamp-mode", |name| { + glib::ParamSpec::enum_( + name, + "Timestamp Mode", + "Timestamp information to use for outgoing PTS", + TimestampMode::static_type(), + TimestampMode::ReceiveTime as i32, + glib::ParamFlags::READWRITE, + ) + }), ]; struct State { info: Option, id_receiver: Option, + current_latency: gst::ClockTime, } impl Default for State { @@ -84,6 +98,7 @@ impl Default for State { State { info: None, id_receiver: None, + current_latency: gst::CLOCK_TIME_NONE, } } } @@ -177,7 +192,6 @@ impl ObjectImpl for NdiAudioSrc { stream_name ); settings.stream_name = stream_name; - drop(settings); } subclass::Property("ip", ..) => { let mut settings = self.settings.lock().unwrap(); @@ -190,7 +204,6 @@ impl ObjectImpl for NdiAudioSrc { ip ); settings.ip = ip; - drop(settings); } subclass::Property("loss-threshold", ..) => { let mut settings = self.settings.lock().unwrap(); @@ -203,7 +216,22 @@ impl ObjectImpl for NdiAudioSrc { loss_threshold ); settings.loss_threshold = loss_threshold; - drop(settings); + } + subclass::Property("timestamp-mode", ..) => { + let mut settings = self.settings.lock().unwrap(); + let timestamp_mode = value.get().unwrap(); + gst_debug!( + self.cat, + obj: basesrc, + "Changing timestamp mode from {:?} to {:?}", + settings.timestamp_mode, + timestamp_mode + ); + if settings.timestamp_mode != timestamp_mode { + let _ = basesrc + .post_message(&gst::Message::new_latency().src(Some(basesrc)).build()); + } + settings.timestamp_mode = timestamp_mode; } _ => unimplemented!(), } @@ -225,6 +253,10 @@ impl ObjectImpl for NdiAudioSrc { let settings = self.settings.lock().unwrap(); Ok(settings.loss_threshold.to_value()) } + subclass::Property("timestamp-mode", ..) => { + let settings = self.settings.lock().unwrap(); + Ok(settings.timestamp_mode.to_value()) + } _ => unimplemented!(), } } @@ -274,6 +306,24 @@ impl BaseSrcImpl for NdiAudioSrc { q.add_scheduling_modes(&[gst::PadMode::Push]); true } + QueryView::Latency(ref mut q) => { + let state = self.state.lock().unwrap(); + let settings = self.settings.lock().unwrap(); + + if state.current_latency.is_some() { + let latency = if settings.timestamp_mode == TimestampMode::Timestamp { + state.current_latency + } else { + 0.into() + }; + + gst_debug!(self.cat, obj: element, "Returning latency {}", latency); + q.set(true, latency, gst::CLOCK_TIME_NONE); + true + } else { + false + } + } _ => BaseSrcImplExt::parent_query(self, element, query), } } @@ -346,23 +396,52 @@ impl BaseSrcImpl for NdiAudioSrc { // will want to work with the timestamp given by the NDI SDK if available let now = clock.get_time(); let base_time = element.get_base_time(); - let pts = now - base_time; + let receive_time = now - base_time; + + let real_time_now = gst::ClockTime::from(glib::get_real_time() as u64 * 1000); + let timestamp = if audio_frame.timestamp() == ndisys::NDIlib_recv_timestamp_undefined { + gst::CLOCK_TIME_NONE + } else { + gst::ClockTime::from(audio_frame.timestamp() as u64 * 100) + }; + let timecode = gst::ClockTime::from(audio_frame.timecode() as u64 * 100); gst_log!( self.cat, obj: element, - "NDI audio frame received: {:?} with timecode {} and timestamp {}", + "NDI audio frame received: {:?} with timecode {} and timestamp {}, receive time {}, local time now {}", audio_frame, - if audio_frame.timecode() == ndisys::NDIlib_send_timecode_synthesize { - gst::CLOCK_TIME_NONE - } else { - gst::ClockTime::from(audio_frame.timecode() as u64 * 100) - }, - if audio_frame.timestamp() == ndisys::NDIlib_recv_timestamp_undefined { - gst::CLOCK_TIME_NONE - } else { - gst::ClockTime::from(audio_frame.timestamp() as u64 * 100) - }, + timecode, + timestamp, + receive_time, + real_time_now, + ); + + let pts = match settings.timestamp_mode { + TimestampMode::ReceiveTime => receive_time, + TimestampMode::Timecode => timecode, + TimestampMode::Timestamp if timestamp.is_none() => receive_time, + TimestampMode::Timestamp => { + // Timestamps are relative to the UNIX epoch + if real_time_now > timestamp { + let diff = real_time_now - timestamp; + if diff > receive_time { + 0.into() + } else { + receive_time - diff + } + } else { + let diff = timestamp - real_time_now; + receive_time + diff + } + } + }; + + gst_log!( + self.cat, + obj: element, + "Calculated pts for audio frame: {:?}", + pts ); let info = gst_audio::AudioInfo::new( @@ -376,18 +455,20 @@ impl BaseSrcImpl for NdiAudioSrc { if state.info.as_ref() != Some(&info) { let caps = info.to_caps().unwrap(); state.info = Some(info); + state.current_latency = gst::SECOND + .mul_div_ceil( + audio_frame.no_samples() as u64, + audio_frame.sample_rate() as u64, + ) + .unwrap_or(gst::CLOCK_TIME_NONE); + gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); element .set_caps(&caps) .map_err(|_| gst::FlowError::NotNegotiated)?; - } - gst_log!( - self.cat, - obj: element, - "Calculated pts for audio frame: {:?}", - pts - ); + let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); + } // We multiply by 2 because is the size in bytes of an i16 variable let buff_size = (audio_frame.no_samples() * 2 * audio_frame.no_channels()) as usize; diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index daf2192d..33e991c1 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -20,6 +20,7 @@ use ndisys; use connect_ndi; use stop_ndi; +use TimestampMode; use HASHMAP_RECEIVERS; #[cfg(feature = "reference-timestamps")] use TIMECODE_CAPS; @@ -31,6 +32,7 @@ struct Settings { stream_name: String, ip: String, loss_threshold: u32, + timestamp_mode: TimestampMode, } impl Default for Settings { @@ -39,32 +41,33 @@ impl Default for Settings { stream_name: String::from("Fixed ndi stream name"), ip: String::from(""), loss_threshold: 5, + timestamp_mode: TimestampMode::ReceiveTime, } } } -static PROPERTIES: [subclass::Property; 3] = [ - subclass::Property("stream-name", |_| { +static PROPERTIES: [subclass::Property; 4] = [ + subclass::Property("stream-name", |name| { glib::ParamSpec::string( - "stream-name", + name, "Stream Name", "Name of the streaming device", None, glib::ParamFlags::READWRITE, ) }), - subclass::Property("ip", |_| { + subclass::Property("ip", |name| { glib::ParamSpec::string( - "ip", + name, "Stream IP", "IP of the streaming device. Ex: 127.0.0.1:5961", None, glib::ParamFlags::READWRITE, ) }), - subclass::Property("loss-threshold", |_| { + subclass::Property("loss-threshold", |name| { glib::ParamSpec::uint( - "loss-threshold", + name, "Loss threshold", "Loss threshold", 0, @@ -73,11 +76,22 @@ static PROPERTIES: [subclass::Property; 3] = [ glib::ParamFlags::READWRITE, ) }), + subclass::Property("timestamp-mode", |name| { + glib::ParamSpec::enum_( + name, + "Timestamp Mode", + "Timestamp information to use for outgoing PTS", + TimestampMode::static_type(), + TimestampMode::ReceiveTime as i32, + glib::ParamFlags::READWRITE, + ) + }), ]; struct State { info: Option, id_receiver: Option, + current_latency: gst::ClockTime, } impl Default for State { @@ -85,6 +99,7 @@ impl Default for State { State { info: None, id_receiver: None, + current_latency: gst::CLOCK_TIME_NONE, } } } @@ -212,7 +227,6 @@ impl ObjectImpl for NdiVideoSrc { stream_name ); settings.stream_name = stream_name; - drop(settings); } subclass::Property("ip", ..) => { let mut settings = self.settings.lock().unwrap(); @@ -225,7 +239,6 @@ impl ObjectImpl for NdiVideoSrc { ip ); settings.ip = ip; - drop(settings); } subclass::Property("loss-threshold", ..) => { let mut settings = self.settings.lock().unwrap(); @@ -238,7 +251,22 @@ impl ObjectImpl for NdiVideoSrc { loss_threshold ); settings.loss_threshold = loss_threshold; - drop(settings); + } + subclass::Property("timestamp-mode", ..) => { + let mut settings = self.settings.lock().unwrap(); + let timestamp_mode = value.get().unwrap(); + gst_debug!( + self.cat, + obj: basesrc, + "Changing timestamp mode from {:?} to {:?}", + settings.timestamp_mode, + timestamp_mode + ); + if settings.timestamp_mode != timestamp_mode { + let _ = basesrc + .post_message(&gst::Message::new_latency().src(Some(basesrc)).build()); + } + settings.timestamp_mode = timestamp_mode; } _ => unimplemented!(), } @@ -260,6 +288,10 @@ impl ObjectImpl for NdiVideoSrc { let settings = self.settings.lock().unwrap(); Ok(settings.loss_threshold.to_value()) } + subclass::Property("timestamp-mode", ..) => { + let settings = self.settings.lock().unwrap(); + Ok(settings.timestamp_mode.to_value()) + } _ => unimplemented!(), } } @@ -304,6 +336,24 @@ impl BaseSrcImpl for NdiVideoSrc { q.add_scheduling_modes(&[gst::PadMode::Push]); true } + QueryView::Latency(ref mut q) => { + let state = self.state.lock().unwrap(); + let settings = self.settings.lock().unwrap(); + + if state.current_latency.is_some() { + let latency = if settings.timestamp_mode == TimestampMode::Timestamp { + state.current_latency + } else { + 0.into() + }; + + gst_debug!(self.cat, obj: element, "Returning latency {}", latency); + q.set(true, latency, gst::CLOCK_TIME_NONE); + true + } else { + false + } + } _ => BaseSrcImplExt::parent_query(self, element, query), } } @@ -380,23 +430,52 @@ impl BaseSrcImpl for NdiVideoSrc { // will want to work with the timestamp given by the NDI SDK if available let now = clock.get_time(); let base_time = element.get_base_time(); - let pts = now - base_time; + let receive_time = now - base_time; + + let real_time_now = gst::ClockTime::from(glib::get_real_time() as u64 * 1000); + let timestamp = if video_frame.timestamp() == ndisys::NDIlib_recv_timestamp_undefined { + gst::CLOCK_TIME_NONE + } else { + gst::ClockTime::from(video_frame.timestamp() as u64 * 100) + }; + let timecode = gst::ClockTime::from(video_frame.timecode() as u64 * 100); gst_log!( self.cat, obj: element, - "NDI video frame received: {:?} with timecode {} and timestamp {}", + "NDI video frame received: {:?} with timecode {} and timestamp {}, receive time {}, local time now {}", video_frame, - if video_frame.timecode() == ndisys::NDIlib_send_timecode_synthesize { - gst::CLOCK_TIME_NONE - } else { - gst::ClockTime::from(video_frame.timecode() as u64 * 100) - }, - if video_frame.timestamp() == ndisys::NDIlib_recv_timestamp_undefined { - gst::CLOCK_TIME_NONE - } else { - gst::ClockTime::from(video_frame.timestamp() as u64 * 100) - }, + timecode, + timestamp, + receive_time, + real_time_now, + ); + + let pts = match settings.timestamp_mode { + TimestampMode::ReceiveTime => receive_time, + TimestampMode::Timecode => timecode, + TimestampMode::Timestamp if timestamp.is_none() => receive_time, + TimestampMode::Timestamp => { + // Timestamps are relative to the UNIX epoch + if real_time_now > timestamp { + let diff = real_time_now - timestamp; + if diff > receive_time { + 0.into() + } else { + receive_time - diff + } + } else { + let diff = timestamp - real_time_now; + receive_time + diff + } + } + }; + + gst_log!( + self.cat, + obj: element, + "Calculated pts for video frame: {:?}", + pts ); // YV12 and I420 are swapped in the NDI SDK compared to GStreamer @@ -476,18 +555,19 @@ impl BaseSrcImpl for NdiVideoSrc { if state.info.as_ref() != Some(&info) { let caps = info.to_caps().unwrap(); state.info = Some(info); + state.current_latency = gst::SECOND + .mul_div_ceil( + video_frame.frame_rate().1 as u64, + video_frame.frame_rate().0 as u64, + ) + .unwrap_or(gst::CLOCK_TIME_NONE); gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); element .set_caps(&caps) .map_err(|_| gst::FlowError::NotNegotiated)?; - } - gst_log!( - self.cat, - obj: element, - "Calculated pts for video frame: {:?}", - pts - ); + let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); + } let mut buffer = gst::Buffer::with_size(state.info.as_ref().unwrap().size()).unwrap(); { From 34858762f7c654dd4d0061c5683ee647a608c540 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Wed, 17 Jul 2019 10:40:26 +0300 Subject: [PATCH 136/199] Consolidate element properties stream-name is called ndi-name everywhere in the NDI SDK and documentation ip is called ip-address everywhere Rename loss-threshold to timeout and change it to be in milliseconds instead of iterations. Add connect-timeout for timeout during connection Add bandwidth and receiver-ndi-name properties, and initialize the latter with a reasonable default value. --- README.md | 13 ++- src/lib.rs | 68 ++++++++------- src/ndi.rs | 76 ++++++++++++++--- src/ndiaudiosrc.rs | 198 ++++++++++++++++++++++++++++++------------ src/ndisys.rs | 14 ++- src/ndivideosrc.rs | 208 +++++++++++++++++++++++++++++++++------------ 6 files changed, 406 insertions(+), 171 deletions(-) diff --git a/README.md b/README.md index d9694a9b..86b0fa58 100644 --- a/README.md +++ b/README.md @@ -16,12 +16,12 @@ gst-inspect-1.0 ndivideosrc gst-inspect-1.0 ndiaudiosrc #Video pipeline -gst-launch-1.0 ndivideosrc stream-name="GC-DEV2 (OBS)" ! autovideosink +gst-launch-1.0 ndivideosrc ndi-name="GC-DEV2 (OBS)" ! autovideosink #Audio pipeline -gst-launch-1.0 ndiaudiosrc stream-name="GC-DEV2 (OBS)" ! autoaudiosink +gst-launch-1.0 ndiaudiosrc ndi-name="GC-DEV2 (OBS)" ! autoaudiosink #Video and audio pipeline -gst-launch-1.0 ndivideosrc stream-name="GC-DEV2 (OBS)" ! autovideosink ndiaudiosrc stream-name="GC-DEV2 (OBS)" ! autoaudiosink +gst-launch-1.0 ndivideosrc ndi-name="GC-DEV2 (OBS)" ! autovideosink ndiaudiosrc ndi-name="GC-DEV2 (OBS)" ! autoaudiosink ``` Feel free to contribute to this project. Some ways you can contribute are: @@ -63,11 +63,8 @@ gst-inspect-1.0 ndi More info about GStreamer plugins written in Rust: ---------------------------------- -https://github.com/sdroege/gstreamer-rs -https://github.com/sdroege/gst-plugin-rs - -https://coaxion.net/blog/2018/01/how-to-write-gstreamer-elements-in-rust-part-1-a-video-filter-for-converting-rgb-to-grayscale/ -https://coaxion.net/blog/2018/02/how-to-write-gstreamer-elements-in-rust-part-2-a-raw-audio-sine-wave-source/ +https://gitlab.freedesktop.org/gstreamer/gstreamer-rs +https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs License diff --git a/src/lib.rs b/src/lib.rs index 4bef1639..3ef1ee65 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,6 +23,7 @@ use ndisys::*; use std::collections::HashMap; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Mutex; +use std::time; #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] #[repr(u32)] @@ -44,8 +45,8 @@ fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { struct ReceiverInfo { id: usize, - stream_name: String, - ip: String, + ndi_name: String, + ip_address: String, video: bool, audio: bool, ndi_instance: RecvInstance, @@ -57,6 +58,10 @@ lazy_static! { Mutex::new(m) }; + static ref DEFAULT_RECEIVER_NDI_NAME: String = { + format!("GStreamer NDI Source {}-{}", env!("CARGO_PKG_VERSION"), env!("COMMIT_ID")) + }; + #[cfg(feature = "reference-timestamps")] static ref TIMECODE_CAPS: gst::Caps = { gst::Caps::new_simple("timestamp/x-ndi-timecode", &[]) @@ -73,8 +78,11 @@ static ID_RECEIVER: AtomicUsize = AtomicUsize::new(0); fn connect_ndi( cat: gst::DebugCategory, element: &gst_base::BaseSrc, - ip: &str, - stream_name: &str, + ip_address: Option<&str>, + ndi_name: Option<&str>, + receiver_ndi_name: &str, + connect_timeout: u32, + bandwidth: NDIlib_recv_bandwidth_e, ) -> Option { gst_debug!(cat, obj: element, "Starting NDI connection..."); @@ -83,7 +91,7 @@ fn connect_ndi( let video = element.get_type() == ndivideosrc::NdiVideoSrc::get_type(); for val in receivers.values_mut() { - if val.ip == ip || val.stream_name == stream_name { + if Some(val.ip_address.as_str()) == ip_address || Some(val.ndi_name.as_str()) == ndi_name { if (val.video || !video) && (val.audio || video) { continue; } else { @@ -109,47 +117,45 @@ fn connect_ndi( Some(find) => find, }; - // TODO Sleep 1s to wait for all sources - find.wait_for_sources(2000); + let timeout = time::Instant::now(); + let source = loop { + find.wait_for_sources(50); - let sources = find.get_current_sources(); + let sources = find.get_current_sources(); - // We need at least one source - if sources.is_empty() { - gst_element_error!( - element, - gst::CoreError::Negotiation, - ["Error getting NDIlib_find_get_current_sources"] + gst_debug!( + cat, + obj: element, + "Total sources found in network {}", + sources.len(), ); - return None; - } - let source = sources - .iter() - .find(|s| s.ndi_name() == stream_name || s.ip_address() == ip); + let source = sources + .iter() + .find(|s| Some(s.ndi_name()) == ndi_name || Some(s.ip_address()) == ip_address); - let source = match source { - None => { - gst_element_error!(element, gst::ResourceError::OpenRead, ["Stream not found"]); + if let Some(source) = source { + break source.to_owned(); + } + + if timeout.elapsed().as_millis() >= connect_timeout as u128 { + gst_element_error!(element, gst::ResourceError::NotFound, ["Stream not found"]); return None; } - Some(source) => source, }; gst_debug!( cat, obj: element, - "Total sources in network {}: Connecting to NDI source with name '{}' and address '{}'", - sources.len(), + "Connecting to NDI source with ndi-name '{}' and ip-address '{}'", source.ndi_name(), source.ip_address(), ); - // FIXME: Property for the name and bandwidth // FIXME: Ideally we would use NDIlib_recv_color_format_fastest here but that seems to be // broken with interlaced content currently - let recv = RecvInstance::builder(&source, "Galicaster NDI Receiver") - .bandwidth(NDIlib_recv_bandwidth_e::NDIlib_recv_bandwidth_highest) + let recv = RecvInstance::builder(&source, receiver_ndi_name) + .bandwidth(bandwidth) .color_format(NDIlib_recv_color_format_e::NDIlib_recv_color_format_UYVY_BGRA) .allow_video_fields(true) .build(); @@ -174,12 +180,12 @@ fn connect_ndi( receivers.insert( id_receiver, ReceiverInfo { - stream_name: source.ndi_name().to_owned(), - ip: source.ip_address().to_owned(), + id: id_receiver, + ndi_name: source.ndi_name().to_owned(), + ip_address: source.ip_address().to_owned(), video, audio: !video, ndi_instance: recv, - id: id_receiver, }, ); diff --git a/src/ndi.rs b/src/ndi.rs index 6f7d6724..46465224 100644 --- a/src/ndi.rs +++ b/src/ndi.rs @@ -94,7 +94,7 @@ impl FindInstance { let mut sources = vec![]; for i in 0..no_sources { - sources.push(Source( + sources.push(Source::Borrowed( ptr::NonNull::new(sources_ptr.add(i as usize) as *mut _).unwrap(), self, )); @@ -114,26 +114,76 @@ impl Drop for FindInstance { } #[derive(Debug)] -pub struct Source<'a>(ptr::NonNull, &'a FindInstance); +pub enum Source<'a> { + Borrowed(ptr::NonNull, &'a FindInstance), + Owned(NDIlib_source_t, ffi::CString, ffi::CString), +} unsafe impl<'a> Send for Source<'a> {} impl<'a> Source<'a> { pub fn ndi_name(&self) -> &str { unsafe { - assert!(!self.0.as_ref().p_ndi_name.is_null()); - ffi::CStr::from_ptr(self.0.as_ref().p_ndi_name) - .to_str() - .unwrap() + 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 ip_address(&self) -> &str { unsafe { - assert!(!self.0.as_ref().p_ip_address.is_null()); - ffi::CStr::from_ptr(self.0.as_ref().p_ip_address) - .to_str() - .unwrap() + let ptr = match *self { + Source::Borrowed(ptr, _) => &*ptr.as_ptr(), + Source::Owned(ref source, _, _) => source, + }; + + assert!(!ptr.p_ip_address.is_null()); + ffi::CStr::from_ptr(ptr.p_ip_address).to_str().unwrap() + } + } + + fn ndi_name_ptr(&self) -> *const ::std::os::raw::c_char { + unsafe { + match *self { + Source::Borrowed(ptr, _) => ptr.as_ref().p_ndi_name, + Source::Owned(_, ref ndi_name, _) => ndi_name.as_ptr(), + } + } + } + + fn ip_address_ptr(&self) -> *const ::std::os::raw::c_char { + unsafe { + match *self { + Source::Borrowed(ptr, _) => ptr.as_ref().p_ip_address, + Source::Owned(_, _, ref ip_address) => ip_address.as_ptr(), + } + } + } + + pub fn to_owned<'b>(&self) -> Source<'b> { + unsafe { + let (ndi_name, ip_address) = match *self { + Source::Borrowed(ptr, _) => (ptr.as_ref().p_ndi_name, ptr.as_ref().p_ip_address), + Source::Owned(_, ref ndi_name, ref ip_address) => { + (ndi_name.as_ptr(), ip_address.as_ptr()) + } + }; + + let ndi_name = ffi::CString::new(ffi::CStr::from_ptr(ndi_name).to_bytes()).unwrap(); + let ip_address = ffi::CString::new(ffi::CStr::from_ptr(ip_address).to_bytes()).unwrap(); + + Source::Owned( + NDIlib_source_t { + p_ndi_name: ndi_name.as_ptr(), + p_ip_address: ip_address.as_ptr(), + }, + ndi_name, + ip_address, + ) } } } @@ -171,8 +221,8 @@ impl<'a> RecvBuilder<'a> { let ndi_name = ffi::CString::new(self.ndi_name).unwrap(); let ptr = NDIlib_recv_create_v3(&NDIlib_recv_create_v3_t { source_to_connect_to: NDIlib_source_t { - p_ndi_name: self.source_to_connect_to.0.as_ref().p_ndi_name, - p_ip_address: self.source_to_connect_to.0.as_ref().p_ip_address, + p_ndi_name: self.source_to_connect_to.ndi_name_ptr(), + p_ip_address: self.source_to_connect_to.ip_address_ptr(), }, allow_video_fields: self.allow_video_fields, bandwidth: self.bandwidth, @@ -198,7 +248,7 @@ impl RecvInstance { RecvBuilder { source_to_connect_to, allow_video_fields: true, - bandwidth: NDIlib_recv_bandwidth_e::NDIlib_recv_bandwidth_highest, + bandwidth: NDIlib_recv_bandwidth_highest, color_format: NDIlib_recv_color_format_e::NDIlib_recv_color_format_UYVY_BGRA, ndi_name, } diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index 897f3342..bfadbcdd 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -10,6 +10,7 @@ use gst_base::prelude::*; use gst_base::subclass::prelude::*; use std::sync::Mutex; +use std::time; use std::{i32, u32}; use connect_ndi; @@ -18,6 +19,7 @@ use ndisys; use stop_ndi; use TimestampMode; +use DEFAULT_RECEIVER_NDI_NAME; use HASHMAP_RECEIVERS; #[cfg(feature = "reference-timestamps")] use TIMECODE_CAPS; @@ -28,50 +30,87 @@ use byte_slice_cast::AsMutSliceOf; #[derive(Debug, Clone)] struct Settings { - stream_name: String, - ip: String, - loss_threshold: u32, + ndi_name: Option, + ip_address: Option, + connect_timeout: u32, + timeout: u32, + receiver_ndi_name: String, + bandwidth: ndisys::NDIlib_recv_bandwidth_e, timestamp_mode: TimestampMode, } impl Default for Settings { fn default() -> Self { Settings { - stream_name: String::from("Fixed ndi stream name"), - ip: String::from(""), - loss_threshold: 5, + ndi_name: None, + ip_address: None, + receiver_ndi_name: DEFAULT_RECEIVER_NDI_NAME.clone(), + connect_timeout: 10000, + timeout: 5000, + bandwidth: ndisys::NDIlib_recv_bandwidth_highest, timestamp_mode: TimestampMode::ReceiveTime, } } } -static PROPERTIES: [subclass::Property; 4] = [ - subclass::Property("stream-name", |name| { +static PROPERTIES: [subclass::Property; 7] = [ + subclass::Property("ndi-name", |name| { glib::ParamSpec::string( name, - "Stream Name", - "Name of the streaming device", + "NDI Name", + "NDI stream name of the sender", None, glib::ParamFlags::READWRITE, ) }), - subclass::Property("ip", |name| { + subclass::Property("ip-address", |name| { glib::ParamSpec::string( name, - "Stream IP", - "IP of the streaming device. Ex: 127.0.0.1:5961", + "IP Address", + "IP address and port of the sender, e.g. 127.0.0.1:5961", None, glib::ParamFlags::READWRITE, ) }), - subclass::Property("loss-threshold", |name| { + subclass::Property("receiver-ndi-name", |name| { + glib::ParamSpec::string( + name, + "Receiver NDI Name", + "NDI stream name of this receiver", + Some(&*DEFAULT_RECEIVER_NDI_NAME), + glib::ParamFlags::READWRITE, + ) + }), + subclass::Property("connect-timeout", |name| { glib::ParamSpec::uint( name, - "Loss threshold", - "Loss threshold", + "Connect Timeout", + "Connection timeout in ms", 0, - 60, - 5, + u32::MAX, + 10000, + glib::ParamFlags::READWRITE, + ) + }), + subclass::Property("timeout", |name| { + glib::ParamSpec::uint( + name, + "Timeout", + "Receive timeout in ms", + 0, + u32::MAX, + 5000, + glib::ParamFlags::READWRITE, + ) + }), + subclass::Property("bandwidth", |name| { + glib::ParamSpec::int( + name, + "Bandwidth", + "Bandwidth, -10 metadata-only, 10 audio-only, 100 highest", + -10, + 100, + 100, glib::ParamFlags::READWRITE, ) }), @@ -134,7 +173,7 @@ impl ObjectSubclass for NdiAudioSrc { "NewTek NDI Audio Source", "Source", "NewTek NDI audio source", - "Ruben Gonzalez , Daniel Vilar ", + "Ruben Gonzalez , Daniel Vilar , Sebastian Dröge ", ); let caps = gst::Caps::new_simple( @@ -181,41 +220,78 @@ impl ObjectImpl for NdiAudioSrc { let basesrc = obj.downcast_ref::().unwrap(); match *prop { - subclass::Property("stream-name", ..) => { + subclass::Property("ndi-name", ..) => { let mut settings = self.settings.lock().unwrap(); - let stream_name = value.get().unwrap(); + let ndi_name = value.get(); gst_debug!( self.cat, obj: basesrc, - "Changing stream-name from {} to {}", - settings.stream_name, - stream_name + "Changing ndi-name from {:?} to {:?}", + settings.ndi_name, + ndi_name, ); - settings.stream_name = stream_name; + settings.ndi_name = ndi_name; } - subclass::Property("ip", ..) => { + subclass::Property("ip-address", ..) => { let mut settings = self.settings.lock().unwrap(); - let ip = value.get().unwrap(); + let ip_address = value.get(); gst_debug!( self.cat, obj: basesrc, - "Changing ip from {} to {}", - settings.ip, - ip + "Changing ip from {:?} to {:?}", + settings.ip_address, + ip_address, ); - settings.ip = ip; + settings.ip_address = ip_address; } - subclass::Property("loss-threshold", ..) => { + subclass::Property("receiver-ndi-name", ..) => { let mut settings = self.settings.lock().unwrap(); - let loss_threshold = value.get().unwrap(); + let receiver_ndi_name = value.get(); gst_debug!( self.cat, obj: basesrc, - "Changing loss threshold from {} to {}", - settings.loss_threshold, - loss_threshold + "Changing receiver-ndi-name from {:?} to {:?}", + settings.receiver_ndi_name, + receiver_ndi_name, ); - settings.loss_threshold = loss_threshold; + settings.receiver_ndi_name = + receiver_ndi_name.unwrap_or_else(|| DEFAULT_RECEIVER_NDI_NAME.clone()); + } + subclass::Property("connect-timeout", ..) => { + let mut settings = self.settings.lock().unwrap(); + let connect_timeout = value.get().unwrap(); + gst_debug!( + self.cat, + obj: basesrc, + "Changing connect-timeout from {} to {}", + settings.connect_timeout, + connect_timeout, + ); + settings.connect_timeout = connect_timeout; + } + subclass::Property("timeout", ..) => { + let mut settings = self.settings.lock().unwrap(); + let timeout = value.get().unwrap(); + gst_debug!( + self.cat, + obj: basesrc, + "Changing timeout from {} to {}", + settings.timeout, + timeout, + ); + settings.timeout = timeout; + } + subclass::Property("bandwidth", ..) => { + let mut settings = self.settings.lock().unwrap(); + let bandwidth = value.get().unwrap(); + gst_debug!( + self.cat, + obj: basesrc, + "Changing bandwidth from {} to {}", + settings.bandwidth, + bandwidth, + ); + settings.bandwidth = bandwidth; } subclass::Property("timestamp-mode", ..) => { let mut settings = self.settings.lock().unwrap(); @@ -241,17 +317,29 @@ impl ObjectImpl for NdiAudioSrc { let prop = &PROPERTIES[id]; match *prop { - subclass::Property("stream-name", ..) => { + subclass::Property("ndi-name", ..) => { let settings = self.settings.lock().unwrap(); - Ok(settings.stream_name.to_value()) + Ok(settings.ndi_name.to_value()) } - subclass::Property("ip", ..) => { + subclass::Property("ip-address", ..) => { let settings = self.settings.lock().unwrap(); - Ok(settings.ip.to_value()) + Ok(settings.ip_address.to_value()) } - subclass::Property("loss-threshold", ..) => { + subclass::Property("receiver-ndi-name", ..) => { let settings = self.settings.lock().unwrap(); - Ok(settings.loss_threshold.to_value()) + Ok(settings.receiver_ndi_name.to_value()) + } + subclass::Property("connect-timeout", ..) => { + let settings = self.settings.lock().unwrap(); + Ok(settings.connect_timeout.to_value()) + } + subclass::Property("timeout", ..) => { + let settings = self.settings.lock().unwrap(); + Ok(settings.timeout.to_value()) + } + subclass::Property("bandwidth", ..) => { + let settings = self.settings.lock().unwrap(); + Ok(settings.bandwidth.to_value()) } subclass::Property("timestamp-mode", ..) => { let settings = self.settings.lock().unwrap(); @@ -270,11 +358,15 @@ impl BaseSrcImpl for NdiAudioSrc { let settings = self.settings.lock().unwrap().clone(); let mut state = self.state.lock().unwrap(); + state.id_receiver = connect_ndi( self.cat, element, - &settings.ip.clone(), - &settings.stream_name.clone(), + settings.ip_address.as_ref().map(String::as_str), + settings.ndi_name.as_ref().map(String::as_str), + &settings.receiver_ndi_name, + settings.connect_timeout, + settings.bandwidth, ); match state.id_receiver { @@ -356,11 +448,11 @@ impl BaseSrcImpl for NdiAudioSrc { let clock = element.get_clock().unwrap(); - let mut count_frame_none = 0; + let timeout = time::Instant::now(); let audio_frame = loop { // FIXME: make interruptable let res = loop { - match recv.capture(false, true, false, 1000) { + match recv.capture(false, true, false, 50) { Err(_) => break Err(()), Ok(None) => break Ok(None), Ok(Some(Frame::Audio(frame))) => break Ok(Some(frame)), @@ -373,17 +465,11 @@ impl BaseSrcImpl for NdiAudioSrc { gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type error received, assuming that the source closed the stream...."]); return Err(gst::FlowError::Error); } - Ok(None) if settings.loss_threshold != 0 => { - if count_frame_none < settings.loss_threshold { - count_frame_none += 1; - continue; - } - gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type none received, assuming that the source closed the stream...."]); - return Err(gst::FlowError::Error); + Ok(None) if timeout.elapsed().as_millis() >= settings.timeout as u128 => { + return Err(gst::FlowError::Eos); } Ok(None) => { - gst_debug!(self.cat, obj: element, "No audio frame received, retry"); - count_frame_none += 1; + gst_debug!(self.cat, obj: element, "No audio frame received yet, retry"); continue; } Ok(Some(frame)) => frame, diff --git a/src/ndisys.rs b/src/ndisys.rs index 70bd1450..67399d0f 100644 --- a/src/ndisys.rs +++ b/src/ndisys.rs @@ -88,14 +88,12 @@ pub enum NDIlib_frame_type_e { NDIlib_frame_type_status_change = 100, } -#[repr(i32)] -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub enum NDIlib_recv_bandwidth_e { - NDIlib_recv_bandwidth_metadata_only = -10, - NDIlib_recv_bandwidth_audio_only = 10, - NDIlib_recv_bandwidth_lowest = 0, - NDIlib_recv_bandwidth_highest = 100, -} +pub type NDIlib_recv_bandwidth_e = i32; + +pub const NDIlib_recv_bandwidth_metadata_only: NDIlib_recv_bandwidth_e = -10; +pub const NDIlib_recv_bandwidth_audio_only: NDIlib_recv_bandwidth_e = 10; +pub const NDIlib_recv_bandwidth_lowest: NDIlib_recv_bandwidth_e = 0; +pub const NDIlib_recv_bandwidth_highest: NDIlib_recv_bandwidth_e = 100; #[repr(u32)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index 33e991c1..2fda6da5 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -12,6 +12,7 @@ use gst_video; use gst_video::prelude::*; use std::sync::Mutex; +use std::time; use std::{i32, u32}; use ndi::*; @@ -21,6 +22,7 @@ use connect_ndi; use stop_ndi; use TimestampMode; +use DEFAULT_RECEIVER_NDI_NAME; use HASHMAP_RECEIVERS; #[cfg(feature = "reference-timestamps")] use TIMECODE_CAPS; @@ -29,50 +31,87 @@ use TIMESTAMP_CAPS; #[derive(Debug, Clone)] struct Settings { - stream_name: String, - ip: String, - loss_threshold: u32, + ndi_name: Option, + ip_address: Option, + connect_timeout: u32, + timeout: u32, + receiver_ndi_name: String, + bandwidth: ndisys::NDIlib_recv_bandwidth_e, timestamp_mode: TimestampMode, } impl Default for Settings { fn default() -> Self { Settings { - stream_name: String::from("Fixed ndi stream name"), - ip: String::from(""), - loss_threshold: 5, + ndi_name: None, + ip_address: None, + receiver_ndi_name: DEFAULT_RECEIVER_NDI_NAME.clone(), + connect_timeout: 10000, + timeout: 5000, + bandwidth: ndisys::NDIlib_recv_bandwidth_highest, timestamp_mode: TimestampMode::ReceiveTime, } } } -static PROPERTIES: [subclass::Property; 4] = [ - subclass::Property("stream-name", |name| { +static PROPERTIES: [subclass::Property; 7] = [ + subclass::Property("ndi-name", |name| { glib::ParamSpec::string( name, - "Stream Name", - "Name of the streaming device", + "NDI Name", + "NDI stream name of the sender", None, glib::ParamFlags::READWRITE, ) }), - subclass::Property("ip", |name| { + subclass::Property("ip-address", |name| { glib::ParamSpec::string( name, - "Stream IP", - "IP of the streaming device. Ex: 127.0.0.1:5961", + "IP Address", + "IP address and port of the sender, e.g. 127.0.0.1:5961", None, glib::ParamFlags::READWRITE, ) }), - subclass::Property("loss-threshold", |name| { + subclass::Property("receiver-ndi-name", |name| { + glib::ParamSpec::string( + name, + "Receiver NDI Name", + "NDI stream name of this receiver", + Some(&*DEFAULT_RECEIVER_NDI_NAME), + glib::ParamFlags::READWRITE, + ) + }), + subclass::Property("connect-timeout", |name| { glib::ParamSpec::uint( name, - "Loss threshold", - "Loss threshold", + "Connect Timeout", + "Connection timeout in ms", 0, - 60, - 5, + u32::MAX, + 10000, + glib::ParamFlags::READWRITE, + ) + }), + subclass::Property("timeout", |name| { + glib::ParamSpec::uint( + name, + "Timeout", + "Receive timeout in ms", + 0, + u32::MAX, + 5000, + glib::ParamFlags::READWRITE, + ) + }), + subclass::Property("bandwidth", |name| { + glib::ParamSpec::int( + name, + "Bandwidth", + "Bandwidth, -10 metadata-only, 10 audio-only, 100 highest", + -10, + 100, + 100, glib::ParamFlags::READWRITE, ) }), @@ -135,7 +174,7 @@ impl ObjectSubclass for NdiVideoSrc { "NewTek NDI Video Source", "Source", "NewTek NDI video source", - "Ruben Gonzalez , Daniel Vilar ", + "Ruben Gonzalez , Daniel Vilar , Sebastian Dröge ", ); // On the src pad, we can produce F32/F64 with any sample rate @@ -216,41 +255,78 @@ impl ObjectImpl for NdiVideoSrc { let basesrc = obj.downcast_ref::().unwrap(); match *prop { - subclass::Property("stream-name", ..) => { + subclass::Property("ndi-name", ..) => { let mut settings = self.settings.lock().unwrap(); - let stream_name = value.get().unwrap(); + let ndi_name = value.get(); gst_debug!( self.cat, obj: basesrc, - "Changing stream-name from {} to {}", - settings.stream_name, - stream_name + "Changing ndi-name from {:?} to {:?}", + settings.ndi_name, + ndi_name, ); - settings.stream_name = stream_name; + settings.ndi_name = ndi_name; } - subclass::Property("ip", ..) => { + subclass::Property("ip-address", ..) => { let mut settings = self.settings.lock().unwrap(); - let ip = value.get().unwrap(); + let ip_address = value.get(); gst_debug!( self.cat, obj: basesrc, - "Changing ip from {} to {}", - settings.ip, - ip + "Changing ip from {:?} to {:?}", + settings.ip_address, + ip_address, ); - settings.ip = ip; + settings.ip_address = ip_address; } - subclass::Property("loss-threshold", ..) => { + subclass::Property("receiver-ndi-name", ..) => { let mut settings = self.settings.lock().unwrap(); - let loss_threshold = value.get().unwrap(); + let receiver_ndi_name = value.get(); gst_debug!( self.cat, obj: basesrc, - "Changing loss threshold from {} to {}", - settings.loss_threshold, - loss_threshold + "Changing receiver-ndi-name from {:?} to {:?}", + settings.receiver_ndi_name, + receiver_ndi_name, ); - settings.loss_threshold = loss_threshold; + settings.receiver_ndi_name = + receiver_ndi_name.unwrap_or_else(|| DEFAULT_RECEIVER_NDI_NAME.clone()); + } + subclass::Property("connect-timeout", ..) => { + let mut settings = self.settings.lock().unwrap(); + let connect_timeout = value.get().unwrap(); + gst_debug!( + self.cat, + obj: basesrc, + "Changing connect-timeout from {} to {}", + settings.connect_timeout, + connect_timeout, + ); + settings.connect_timeout = connect_timeout; + } + subclass::Property("timeout", ..) => { + let mut settings = self.settings.lock().unwrap(); + let timeout = value.get().unwrap(); + gst_debug!( + self.cat, + obj: basesrc, + "Changing timeout from {} to {}", + settings.timeout, + timeout, + ); + settings.timeout = timeout; + } + subclass::Property("bandwidth", ..) => { + let mut settings = self.settings.lock().unwrap(); + let bandwidth = value.get().unwrap(); + gst_debug!( + self.cat, + obj: basesrc, + "Changing bandwidth from {} to {}", + settings.bandwidth, + bandwidth, + ); + settings.bandwidth = bandwidth; } subclass::Property("timestamp-mode", ..) => { let mut settings = self.settings.lock().unwrap(); @@ -276,17 +352,29 @@ impl ObjectImpl for NdiVideoSrc { let prop = &PROPERTIES[id]; match *prop { - subclass::Property("stream-name", ..) => { + subclass::Property("ndi-name", ..) => { let settings = self.settings.lock().unwrap(); - Ok(settings.stream_name.to_value()) + Ok(settings.ndi_name.to_value()) } - subclass::Property("ip", ..) => { + subclass::Property("ip-address", ..) => { let settings = self.settings.lock().unwrap(); - Ok(settings.ip.to_value()) + Ok(settings.ip_address.to_value()) } - subclass::Property("loss-threshold", ..) => { + subclass::Property("receiver-ndi-name", ..) => { let settings = self.settings.lock().unwrap(); - Ok(settings.loss_threshold.to_value()) + Ok(settings.receiver_ndi_name.to_value()) + } + subclass::Property("connect-timeout", ..) => { + let settings = self.settings.lock().unwrap(); + Ok(settings.connect_timeout.to_value()) + } + subclass::Property("timeout", ..) => { + let settings = self.settings.lock().unwrap(); + Ok(settings.timeout.to_value()) + } + subclass::Property("bandwidth", ..) => { + let settings = self.settings.lock().unwrap(); + Ok(settings.bandwidth.to_value()) } subclass::Property("timestamp-mode", ..) => { let settings = self.settings.lock().unwrap(); @@ -304,7 +392,23 @@ impl BaseSrcImpl for NdiVideoSrc { *self.state.lock().unwrap() = Default::default(); let mut state = self.state.lock().unwrap(); let settings = self.settings.lock().unwrap().clone(); - state.id_receiver = connect_ndi(self.cat, element, &settings.ip, &settings.stream_name); + + if settings.ip_address.is_none() && settings.ndi_name.is_none() { + return Err(gst_error_msg!( + gst::LibraryError::Settings, + ["No IP address or NDI name given"] + )); + } + + state.id_receiver = connect_ndi( + self.cat, + element, + settings.ip_address.as_ref().map(String::as_str), + settings.ndi_name.as_ref().map(String::as_str), + &settings.receiver_ndi_name, + settings.connect_timeout, + settings.bandwidth, + ); // settings.id_receiver exists match state.id_receiver { @@ -390,11 +494,11 @@ impl BaseSrcImpl for NdiVideoSrc { let clock = element.get_clock().unwrap(); - let mut count_frame_none = 0; + let timeout = time::Instant::now(); let video_frame = loop { // FIXME: make interruptable let res = loop { - match recv.capture(true, false, false, 1000) { + match recv.capture(true, false, false, 50) { Err(_) => break Err(()), Ok(None) => break Ok(None), Ok(Some(Frame::Video(frame))) => break Ok(Some(frame)), @@ -407,17 +511,11 @@ impl BaseSrcImpl for NdiVideoSrc { gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type error received, assuming that the source closed the stream...."]); return Err(gst::FlowError::Error); } - Ok(None) if settings.loss_threshold != 0 => { - if count_frame_none < settings.loss_threshold { - count_frame_none += 1; - continue; - } - gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type none received, assuming that the source closed the stream...."]); - return Err(gst::FlowError::Error); + Ok(None) if timeout.elapsed().as_millis() >= settings.timeout as u128 => { + return Err(gst::FlowError::Eos); } Ok(None) => { - gst_debug!(self.cat, obj: element, "No video frame received, retry"); - count_frame_none += 1; + gst_debug!(self.cat, obj: element, "No video frame received yet, retry"); continue; } Ok(Some(frame)) => frame, From cd0726b037352511e5504aabef497b3edd20229a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Wed, 17 Jul 2019 11:01:07 +0300 Subject: [PATCH 137/199] Make RecvInstance reference counted and allow lock-less capturing --- src/ndi.rs | 42 ++++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/src/ndi.rs b/src/ndi.rs index 46465224..9c8f7483 100644 --- a/src/ndi.rs +++ b/src/ndi.rs @@ -2,6 +2,7 @@ use ndisys::*; use std::ffi; use std::mem; use std::ptr; +use std::sync::{Arc, Mutex}; pub fn initialize() -> bool { unsafe { NDIlib_initialize() } @@ -233,15 +234,27 @@ impl<'a> RecvBuilder<'a> { if ptr.is_null() { None } else { - Some(RecvInstance(ptr::NonNull::new_unchecked(ptr))) + Some(RecvInstance(Arc::new(( + RecvInstanceInner(ptr::NonNull::new_unchecked(ptr)), + Mutex::new(()), + )))) } } } } +// Any access to the RecvInstanceInner apart from calling the capture function must be protected by +// the mutex +#[derive(Debug, Clone)] +pub struct RecvInstance(Arc<(RecvInstanceInner, Mutex<()>)>); + #[derive(Debug)] -pub struct RecvInstance(ptr::NonNull<::std::os::raw::c_void>); -unsafe impl Send for RecvInstance {} +struct RecvInstanceInner(ptr::NonNull<::std::os::raw::c_void>); +unsafe impl Send for RecvInstanceInner {} + +// Not 100% true but we ensure safety with the mutex. The documentation says that only the +// capturing itself can be performed from multiple threads at once safely. +unsafe impl Sync for RecvInstanceInner {} impl RecvInstance { pub fn builder<'a>(source_to_connect_to: &'a Source, ndi_name: &'a str) -> RecvBuilder<'a> { @@ -255,11 +268,17 @@ impl RecvInstance { } pub fn set_tally(&self, tally: &Tally) -> bool { - unsafe { NDIlib_recv_set_tally(self.0.as_ptr(), &tally.0) } + unsafe { + let _lock = (self.0).1.lock().unwrap(); + NDIlib_recv_set_tally(((self.0).0).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()) } + unsafe { + let _lock = (self.0).1.lock().unwrap(); + NDIlib_recv_send_metadata(((self.0).0).0.as_ptr(), metadata.as_ptr()) + } } pub fn capture( @@ -270,12 +289,15 @@ impl RecvInstance { timeout_in_ms: u32, ) -> Result, ()> { unsafe { + // Capturing from multiple threads at once is safe according to the documentation + let ptr = ((self.0).0).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_v2( - self.0.as_ptr(), + ptr, if video { &mut video_frame } else { @@ -317,7 +339,7 @@ impl RecvInstance { } } -impl Drop for RecvInstance { +impl Drop for RecvInstanceInner { fn drop(&mut self) { unsafe { NDIlib_recv_destroy(self.0.as_ptr() as *mut _) } } @@ -470,7 +492,7 @@ impl<'a> Drop for VideoFrame<'a> { fn drop(&mut self) { if let VideoFrame::Borrowed(ref frame, ref recv) = *self { unsafe { - NDIlib_recv_free_video_v2(recv.0.as_ptr() as *mut _, frame); + NDIlib_recv_free_video_v2(((recv.0).0).0.as_ptr() as *mut _, frame); } } } @@ -577,7 +599,7 @@ impl<'a> Drop for AudioFrame<'a> { fn drop(&mut self) { if let AudioFrame::Borrowed(ref frame, ref recv) = *self { unsafe { - NDIlib_recv_free_audio_v2(recv.0.as_ptr() as *mut _, frame); + NDIlib_recv_free_audio_v2(((recv.0).0).0.as_ptr() as *mut _, frame); } } } @@ -669,7 +691,7 @@ impl<'a> Drop for MetadataFrame<'a> { fn drop(&mut self) { if let MetadataFrame::Borrowed(ref frame, ref recv) = *self { unsafe { - NDIlib_recv_free_metadata(recv.0.as_ptr() as *mut _, frame); + NDIlib_recv_free_metadata(((recv.0).0).0.as_ptr() as *mut _, frame); } } } From f27c2507c5f1983092463c6778f1c9eaedb2d9db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Wed, 17 Jul 2019 11:44:24 +0300 Subject: [PATCH 138/199] Refactor huge create() functions into multiple smaller functions And don't hold any mutexes when unnecessary, especially not while trying to receive a frame. --- src/ndiaudiosrc.rs | 103 +++++++++---- src/ndivideosrc.rs | 370 ++++++++++++++++++++++++++------------------- 2 files changed, 283 insertions(+), 190 deletions(-) diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index bfadbcdd..8e01dead 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -438,15 +438,20 @@ impl BaseSrcImpl for NdiAudioSrc { _offset: u64, _length: u32, ) -> Result { - // FIXME: Make sure to not have any mutexes locked while wait + self.capture(element) + } +} + +impl NdiAudioSrc { + fn capture(&self, element: &gst_base::BaseSrc) -> Result { let settings = self.settings.lock().unwrap().clone(); - let mut state = self.state.lock().unwrap(); - let receivers = HASHMAP_RECEIVERS.lock().unwrap(); - let receiver = &receivers.get(&state.id_receiver.unwrap()).unwrap(); - let recv = &receiver.ndi_instance; - - let clock = element.get_clock().unwrap(); + let recv = { + let state = self.state.lock().unwrap(); + let receivers = HASHMAP_RECEIVERS.lock().unwrap(); + let receiver = &receivers.get(&state.id_receiver.unwrap()).unwrap(); + receiver.ndi_instance.clone() + }; let timeout = time::Instant::now(); let audio_frame = loop { @@ -478,6 +483,46 @@ impl BaseSrcImpl for NdiAudioSrc { break audio_frame; }; + let pts = self.calculate_timestamp(element, &settings, &audio_frame); + let info = self.create_audio_info(element, &audio_frame)?; + + { + let mut state = self.state.lock().unwrap(); + if state.info.as_ref() != Some(&info) { + let caps = info.to_caps().unwrap(); + state.info = Some(info.clone()); + state.current_latency = gst::SECOND + .mul_div_ceil( + audio_frame.no_samples() as u64, + audio_frame.sample_rate() as u64, + ) + .unwrap_or(gst::CLOCK_TIME_NONE); + drop(state); + gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); + element + .set_caps(&caps) + .map_err(|_| gst::FlowError::NotNegotiated)?; + + let _ = + element.post_message(&gst::Message::new_latency().src(Some(element)).build()); + } + } + + let buffer = self.create_buffer(element, pts, &info, &audio_frame)?; + + gst_log!(self.cat, obj: element, "Produced buffer {:?}", buffer); + + Ok(buffer) + } + + fn calculate_timestamp( + &self, + element: &gst_base::BaseSrc, + settings: &Settings, + audio_frame: &AudioFrame, + ) -> gst::ClockTime { + let clock = element.get_clock().unwrap(); + // For now take the current running time as PTS. At a later time we // will want to work with the timestamp given by the NDI SDK if available let now = clock.get_time(); @@ -530,34 +575,32 @@ impl BaseSrcImpl for NdiAudioSrc { pts ); - let info = gst_audio::AudioInfo::new( + pts + } + + fn create_audio_info( + &self, + _element: &gst_base::BaseSrc, + audio_frame: &AudioFrame, + ) -> Result { + let builder = gst_audio::AudioInfo::new( gst_audio::AUDIO_FORMAT_S16, audio_frame.sample_rate() as u32, audio_frame.no_channels() as u32, - ) - .build() - .unwrap(); + ); - if state.info.as_ref() != Some(&info) { - let caps = info.to_caps().unwrap(); - state.info = Some(info); - state.current_latency = gst::SECOND - .mul_div_ceil( - audio_frame.no_samples() as u64, - audio_frame.sample_rate() as u64, - ) - .unwrap_or(gst::CLOCK_TIME_NONE); - - gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); - element - .set_caps(&caps) - .map_err(|_| gst::FlowError::NotNegotiated)?; - - let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); - } + Ok(builder.build().unwrap()) + } + fn create_buffer( + &self, + _element: &gst_base::BaseSrc, + pts: gst::ClockTime, + info: &gst_audio::AudioInfo, + audio_frame: &AudioFrame, + ) -> Result { // We multiply by 2 because is the size in bytes of an i16 variable - let buff_size = (audio_frame.no_samples() * 2 * audio_frame.no_channels()) as usize; + let buff_size = (audio_frame.no_samples() as u32 * info.bpf()) as usize; let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); { let duration = gst::SECOND @@ -598,8 +641,6 @@ impl BaseSrcImpl for NdiAudioSrc { ); } - gst_log!(self.cat, obj: element, "Produced buffer {:?}", buffer); - Ok(buffer) } } diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index 2fda6da5..97c4b816 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -484,15 +484,20 @@ impl BaseSrcImpl for NdiVideoSrc { _offset: u64, _length: u32, ) -> Result { - // FIXME: Make sure to not have any mutexes locked while wait + self.capture(element) + } +} + +impl NdiVideoSrc { + fn capture(&self, element: &gst_base::BaseSrc) -> Result { let settings = self.settings.lock().unwrap().clone(); - let mut state = self.state.lock().unwrap(); - let receivers = HASHMAP_RECEIVERS.lock().unwrap(); - let receiver = &receivers.get(&state.id_receiver.unwrap()).unwrap(); - let recv = &receiver.ndi_instance; - - let clock = element.get_clock().unwrap(); + let recv = { + let state = self.state.lock().unwrap(); + let receivers = HASHMAP_RECEIVERS.lock().unwrap(); + let receiver = &receivers.get(&state.id_receiver.unwrap()).unwrap(); + receiver.ndi_instance.clone() + }; let timeout = time::Instant::now(); let video_frame = loop { @@ -524,6 +529,46 @@ impl BaseSrcImpl for NdiVideoSrc { break video_frame; }; + let pts = self.calculate_timestamp(element, &settings, &video_frame); + let info = self.create_video_info(element, &video_frame)?; + + { + let mut state = self.state.lock().unwrap(); + if state.info.as_ref() != Some(&info) { + let caps = info.to_caps().unwrap(); + state.info = Some(info.clone()); + state.current_latency = gst::SECOND + .mul_div_ceil( + video_frame.frame_rate().1 as u64, + video_frame.frame_rate().0 as u64, + ) + .unwrap_or(gst::CLOCK_TIME_NONE); + drop(state); + gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); + element + .set_caps(&caps) + .map_err(|_| gst::FlowError::NotNegotiated)?; + + let _ = + element.post_message(&gst::Message::new_latency().src(Some(element)).build()); + } + } + + let buffer = self.create_buffer(element, pts, &info, &video_frame)?; + + gst_log!(self.cat, obj: element, "Produced buffer {:?}", buffer); + + Ok(buffer) + } + + fn calculate_timestamp( + &self, + element: &gst_base::BaseSrc, + settings: &Settings, + video_frame: &VideoFrame, + ) -> gst::ClockTime { + let clock = element.get_clock().unwrap(); + // For now take the current running time as PTS. At a later time we // will want to work with the timestamp given by the NDI SDK if available let now = clock.get_time(); @@ -576,6 +621,14 @@ impl BaseSrcImpl for NdiVideoSrc { pts ); + pts + } + + fn create_video_info( + &self, + _element: &gst_base::BaseSrc, + video_frame: &VideoFrame, + ) -> Result { // YV12 and I420 are swapped in the NDI SDK compared to GStreamer let format = match video_frame.fourcc() { ndisys::NDIlib_FourCC_type_e::NDIlib_FourCC_type_UYVY => gst_video::VideoFormat::Uyvy, @@ -593,7 +646,7 @@ impl BaseSrcImpl for NdiVideoSrc { * gst::Fraction::new(video_frame.yres(), video_frame.xres()); #[cfg(feature = "interlaced-fields")] - let info = { + { let mut builder = gst_video::VideoInfo::new( format, video_frame.xres() as u32, @@ -618,56 +671,52 @@ impl BaseSrcImpl for NdiVideoSrc { builder = builder.field_order(gst_video::VideoFieldOrder::TopFieldFirst); } - builder.build().unwrap() - }; - - #[cfg(not(feature = "interlaced-fields"))] - let info = if video_frame.frame_format_type() - != ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_progressive - && video_frame.frame_format_type() - != ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_interleaved - { - gst_element_error!( - element, - gst::StreamError::Format, - ["Separate field interlacing not supported"] - ); - return Err(gst::FlowError::NotNegotiated); - } else { - gst_video::VideoInfo::new(format, video_frame.xres() as u32, video_frame.yres() as u32) - .fps(gst::Fraction::from(video_frame.frame_rate())) - .par(par) - .interlace_mode( - if video_frame.frame_format_type() - == ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_progressive - { - gst_video::VideoInterlaceMode::Progressive - } else { - gst_video::VideoInterlaceMode::Interleaved - }, - ) - .build() - .unwrap() - }; - - if state.info.as_ref() != Some(&info) { - let caps = info.to_caps().unwrap(); - state.info = Some(info); - state.current_latency = gst::SECOND - .mul_div_ceil( - video_frame.frame_rate().1 as u64, - video_frame.frame_rate().0 as u64, - ) - .unwrap_or(gst::CLOCK_TIME_NONE); - gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); - element - .set_caps(&caps) - .map_err(|_| gst::FlowError::NotNegotiated)?; - - let _ = element.post_message(&gst::Message::new_latency().src(Some(element)).build()); + Ok(builder.build().unwrap()) } - let mut buffer = gst::Buffer::with_size(state.info.as_ref().unwrap().size()).unwrap(); + #[cfg(not(feature = "interlaced-fields"))] + { + if video_frame.frame_format_type() + != ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_progressive + && video_frame.frame_format_type() + != ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_interleaved + { + gst_element_error!( + element, + gst::StreamError::Format, + ["Separate field interlacing not supported"] + ); + return Err(gst::FlowError::NotNegotiated); + } + + let builder = gst_video::VideoInfo::new( + format, + video_frame.xres() as u32, + video_frame.yres() as u32, + ) + .fps(gst::Fraction::from(video_frame.frame_rate())) + .par(par) + .interlace_mode( + if video_frame.frame_format_type() + == ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_progressive + { + gst_video::VideoInterlaceMode::Progressive + } else { + gst_video::VideoInterlaceMode::Interleaved + }, + ); + Ok(builder.build().unwrap()); + } + } + + fn create_buffer( + &self, + element: &gst_base::BaseSrc, + pts: gst::ClockTime, + info: &gst_video::VideoInfo, + video_frame: &VideoFrame, + ) -> Result { + let mut buffer = gst::Buffer::with_size(info.size()).unwrap(); { let duration = gst::SECOND .mul_div_floor( @@ -734,22 +783,47 @@ impl BaseSrcImpl for NdiVideoSrc { } } - let buffer = { - let mut vframe = - gst_video::VideoFrame::from_buffer_writable(buffer, &state.info.as_ref().unwrap()) - .unwrap(); + self.copy_frame(element, info, buffer, video_frame) + } - match format { - gst_video::VideoFormat::Uyvy - | gst_video::VideoFormat::Bgra - | gst_video::VideoFormat::Bgrx - | gst_video::VideoFormat::Rgba - | gst_video::VideoFormat::Rgbx => { - let line_bytes = if format == gst_video::VideoFormat::Uyvy { - 2 * vframe.width() as usize - } else { - 4 * vframe.width() as usize - }; + fn copy_frame( + &self, + _element: &gst_base::BaseSrc, + info: &gst_video::VideoInfo, + buffer: gst::Buffer, + video_frame: &VideoFrame, + ) -> Result { + // FIXME: Error handling if frame dimensions don't match + let mut vframe = gst_video::VideoFrame::from_buffer_writable(buffer, info).unwrap(); + + match info.format() { + gst_video::VideoFormat::Uyvy + | gst_video::VideoFormat::Bgra + | gst_video::VideoFormat::Bgrx + | gst_video::VideoFormat::Rgba + | gst_video::VideoFormat::Rgbx => { + let line_bytes = if info.format() == gst_video::VideoFormat::Uyvy { + 2 * vframe.width() as usize + } else { + 4 * vframe.width() as usize + }; + let dest_stride = vframe.plane_stride()[0] as usize; + let dest = vframe.plane_data_mut(0).unwrap(); + let src_stride = video_frame.line_stride_in_bytes() as usize; + let src = video_frame.data(); + + for (dest, src) in dest + .chunks_exact_mut(dest_stride) + .zip(src.chunks_exact(src_stride)) + { + dest.copy_from_slice(src); + dest.copy_from_slice(&src[..line_bytes]); + } + } + gst_video::VideoFormat::Nv12 => { + // First plane + { + let line_bytes = vframe.width() as usize; let dest_stride = vframe.plane_stride()[0] as usize; let dest = vframe.plane_data_mut(0).unwrap(); let src_stride = video_frame.line_stride_in_bytes() as usize; @@ -759,104 +833,82 @@ impl BaseSrcImpl for NdiVideoSrc { .chunks_exact_mut(dest_stride) .zip(src.chunks_exact(src_stride)) { - dest.copy_from_slice(src); dest.copy_from_slice(&src[..line_bytes]); } } - gst_video::VideoFormat::Nv12 => { - // First plane + + // Second plane + { + let line_bytes = vframe.width() as usize; + let dest_stride = vframe.plane_stride()[1] as usize; + let dest = vframe.plane_data_mut(1).unwrap(); + let src_stride = video_frame.line_stride_in_bytes() as usize; + let src = &video_frame.data()[(video_frame.yres() as usize * src_stride)..]; + + for (dest, src) in dest + .chunks_exact_mut(dest_stride) + .zip(src.chunks_exact(src_stride)) { - let line_bytes = vframe.width() as usize; - let dest_stride = vframe.plane_stride()[0] as usize; - let dest = vframe.plane_data_mut(0).unwrap(); - let src_stride = video_frame.line_stride_in_bytes() as usize; - let src = video_frame.data(); - - for (dest, src) in dest - .chunks_exact_mut(dest_stride) - .zip(src.chunks_exact(src_stride)) - { - dest.copy_from_slice(&src[..line_bytes]); - } - } - - // Second plane - { - let line_bytes = vframe.width() as usize; - let dest_stride = vframe.plane_stride()[1] as usize; - let dest = vframe.plane_data_mut(1).unwrap(); - let src_stride = video_frame.line_stride_in_bytes() as usize; - let src = &video_frame.data()[(video_frame.yres() as usize * src_stride)..]; - - for (dest, src) in dest - .chunks_exact_mut(dest_stride) - .zip(src.chunks_exact(src_stride)) - { - dest.copy_from_slice(&src[..line_bytes]); - } + dest.copy_from_slice(&src[..line_bytes]); } } - gst_video::VideoFormat::Yv12 | gst_video::VideoFormat::I420 => { - // First plane - { - let line_bytes = vframe.width() as usize; - let dest_stride = vframe.plane_stride()[0] as usize; - let dest = vframe.plane_data_mut(0).unwrap(); - let src_stride = video_frame.line_stride_in_bytes() as usize; - let src = video_frame.data(); - - for (dest, src) in dest - .chunks_exact_mut(dest_stride) - .zip(src.chunks_exact(src_stride)) - { - dest.copy_from_slice(&src[..line_bytes]); - } - } - - // Second plane - { - let line_bytes = (vframe.width() as usize + 1) / 2; - let dest_stride = vframe.plane_stride()[1] as usize; - let dest = vframe.plane_data_mut(1).unwrap(); - let src_stride = video_frame.line_stride_in_bytes() as usize; - let src_stride1 = video_frame.line_stride_in_bytes() as usize / 2; - let src = &video_frame.data()[(video_frame.yres() as usize * src_stride)..]; - - for (dest, src) in dest - .chunks_exact_mut(dest_stride) - .zip(src.chunks_exact(src_stride1)) - { - dest.copy_from_slice(&src[..line_bytes]); - } - } - - // Third plane - { - let line_bytes = (vframe.width() as usize + 1) / 2; - let dest_stride = vframe.plane_stride()[2] as usize; - let dest = vframe.plane_data_mut(2).unwrap(); - let src_stride = video_frame.line_stride_in_bytes() as usize; - let src_stride1 = video_frame.line_stride_in_bytes() as usize / 2; - let src = &video_frame.data()[(video_frame.yres() as usize * src_stride - + (video_frame.yres() as usize + 1) / 2 * src_stride1)..]; - - for (dest, src) in dest - .chunks_exact_mut(dest_stride) - .zip(src.chunks_exact(src_stride1)) - { - dest.copy_from_slice(&src[..line_bytes]); - } - } - } - _ => unreachable!(), } + gst_video::VideoFormat::Yv12 | gst_video::VideoFormat::I420 => { + // First plane + { + let line_bytes = vframe.width() as usize; + let dest_stride = vframe.plane_stride()[0] as usize; + let dest = vframe.plane_data_mut(0).unwrap(); + let src_stride = video_frame.line_stride_in_bytes() as usize; + let src = video_frame.data(); - vframe.into_buffer() - }; + for (dest, src) in dest + .chunks_exact_mut(dest_stride) + .zip(src.chunks_exact(src_stride)) + { + dest.copy_from_slice(&src[..line_bytes]); + } + } - gst_log!(self.cat, obj: element, "Produced buffer {:?}", buffer); + // Second plane + { + let line_bytes = (vframe.width() as usize + 1) / 2; + let dest_stride = vframe.plane_stride()[1] as usize; + let dest = vframe.plane_data_mut(1).unwrap(); + let src_stride = video_frame.line_stride_in_bytes() as usize; + let src_stride1 = video_frame.line_stride_in_bytes() as usize / 2; + let src = &video_frame.data()[(video_frame.yres() as usize * src_stride)..]; - Ok(buffer) + for (dest, src) in dest + .chunks_exact_mut(dest_stride) + .zip(src.chunks_exact(src_stride1)) + { + dest.copy_from_slice(&src[..line_bytes]); + } + } + + // Third plane + { + let line_bytes = (vframe.width() as usize + 1) / 2; + let dest_stride = vframe.plane_stride()[2] as usize; + let dest = vframe.plane_data_mut(2).unwrap(); + let src_stride = video_frame.line_stride_in_bytes() as usize; + let src_stride1 = video_frame.line_stride_in_bytes() as usize / 2; + let src = &video_frame.data()[(video_frame.yres() as usize * src_stride + + (video_frame.yres() as usize + 1) / 2 * src_stride1)..]; + + for (dest, src) in dest + .chunks_exact_mut(dest_stride) + .zip(src.chunks_exact(src_stride1)) + { + dest.copy_from_slice(&src[..line_bytes]); + } + } + } + _ => unreachable!(), + } + + Ok(vframe.into_buffer()) } } From 19d25d20a7dafae89e4f57a9f49f0839ca8132c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Wed, 17 Jul 2019 12:12:58 +0300 Subject: [PATCH 139/199] Implement cancellation of connection attempts --- src/lib.rs | 13 +++++++++++- src/ndiaudiosrc.rs | 46 +++++++++++++++++++++++++++++++++++----- src/ndivideosrc.rs | 53 +++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 101 insertions(+), 11 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3ef1ee65..d0b78d90 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,7 +21,7 @@ use ndi::*; use ndisys::*; use std::collections::HashMap; -use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::atomic::{AtomicUsize, AtomicBool, Ordering}; use std::sync::Mutex; use std::time; @@ -83,6 +83,7 @@ fn connect_ndi( receiver_ndi_name: &str, connect_timeout: u32, bandwidth: NDIlib_recv_bandwidth_e, + cancel: &AtomicBool, ) -> Option { gst_debug!(cat, obj: element, "Starting NDI connection..."); @@ -119,8 +120,18 @@ fn connect_ndi( let timeout = time::Instant::now(); let source = loop { + if cancel.load(Ordering::SeqCst) { + gst_debug!(cat, obj: element, "Cancelled"); + return None; + } + find.wait_for_sources(50); + if cancel.load(Ordering::SeqCst) { + gst_debug!(cat, obj: element, "Cancelled"); + return None; + } + let sources = find.get_current_sources(); gst_debug!( diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index 8e01dead..51a6837a 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -10,6 +10,7 @@ use gst_base::prelude::*; use gst_base::subclass::prelude::*; use std::sync::Mutex; +use std::sync::atomic::{AtomicBool, Ordering}; use std::time; use std::{i32, u32}; @@ -146,6 +147,7 @@ pub(crate) struct NdiAudioSrc { cat: gst::DebugCategory, settings: Mutex, state: Mutex, + unlock: AtomicBool, } impl ObjectSubclass for NdiAudioSrc { @@ -165,6 +167,7 @@ impl ObjectSubclass for NdiAudioSrc { ), settings: Mutex::new(Default::default()), state: Mutex::new(Default::default()), + unlock: AtomicBool::new(false), } } @@ -353,13 +356,38 @@ impl ObjectImpl for NdiAudioSrc { impl ElementImpl for NdiAudioSrc {} impl BaseSrcImpl for NdiAudioSrc { + fn unlock(&self, element: &gst_base::BaseSrc) -> std::result::Result<(), gst::ErrorMessage> { + gst_debug!( + self.cat, + obj: element, + "Unlocking", + ); + self.unlock.store(true, Ordering::SeqCst); + Ok(()) + } + + fn unlock_stop(&self, element: &gst_base::BaseSrc) -> std::result::Result<(), gst::ErrorMessage> { + gst_debug!( + self.cat, + obj: element, + "Stop unlocking", + ); + self.unlock.store(false, Ordering::SeqCst); + Ok(()) + } + fn start(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { *self.state.lock().unwrap() = Default::default(); - let settings = self.settings.lock().unwrap().clone(); - let mut state = self.state.lock().unwrap(); - state.id_receiver = connect_ndi( + if settings.ip_address.is_none() && settings.ndi_name.is_none() { + return Err(gst_error_msg!( + gst::LibraryError::Settings, + ["No IP address or NDI name given"] + )); + } + + let id_receiver = connect_ndi( self.cat, element, settings.ip_address.as_ref().map(String::as_str), @@ -367,14 +395,22 @@ impl BaseSrcImpl for NdiAudioSrc { &settings.receiver_ndi_name, settings.connect_timeout, settings.bandwidth, + &self.unlock, ); - match state.id_receiver { + // settings.id_receiver exists + match id_receiver { + None if self.unlock.load(Ordering::SeqCst) => Ok(()), None => Err(gst_error_msg!( gst::ResourceError::NotFound, ["Could not connect to this source"] )), - _ => Ok(()), + Some(id_receiver) => { + let mut state = self.state.lock().unwrap(); + state.id_receiver = Some(id_receiver); + + Ok(()) + } } } diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index 97c4b816..02e353c3 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -12,6 +12,7 @@ use gst_video; use gst_video::prelude::*; use std::sync::Mutex; +use std::sync::atomic::{AtomicBool, Ordering}; use std::time; use std::{i32, u32}; @@ -147,6 +148,7 @@ pub(crate) struct NdiVideoSrc { cat: gst::DebugCategory, settings: Mutex, state: Mutex, + unlock: AtomicBool, } impl ObjectSubclass for NdiVideoSrc { @@ -166,6 +168,7 @@ impl ObjectSubclass for NdiVideoSrc { ), settings: Mutex::new(Default::default()), state: Mutex::new(Default::default()), + unlock: AtomicBool::new(false), } } @@ -385,12 +388,43 @@ impl ObjectImpl for NdiVideoSrc { } } -impl ElementImpl for NdiVideoSrc {} +impl ElementImpl for NdiVideoSrc { + fn change_state(&self, element: &gst::Element, transition: gst::StateChange) -> Result { + match transition { + gst::StateChange::ReadyToPaused => self.unlock.store(true, Ordering::SeqCst), + gst::StateChange::PausedToReady => self.unlock.store(false, Ordering::SeqCst), + _ => (), + } + + self.parent_change_state(element, transition) + } +} impl BaseSrcImpl for NdiVideoSrc { + fn unlock(&self, element: &gst_base::BaseSrc) -> std::result::Result<(), gst::ErrorMessage> { + gst_debug!( + self.cat, + obj: element, + "Unlocking", + ); + self.unlock.store(true, Ordering::SeqCst); + Ok(()) + } + + fn unlock_stop(&self, element: &gst_base::BaseSrc) -> std::result::Result<(), gst::ErrorMessage> { + gst_debug!( + self.cat, + obj: element, + "Stop unlocking", + ); + self.unlock.store(false, Ordering::SeqCst); + Ok(()) + } + fn start(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { + self.unlock.store(false, Ordering::SeqCst); + *self.state.lock().unwrap() = Default::default(); - let mut state = self.state.lock().unwrap(); let settings = self.settings.lock().unwrap().clone(); if settings.ip_address.is_none() && settings.ndi_name.is_none() { @@ -400,7 +434,7 @@ impl BaseSrcImpl for NdiVideoSrc { )); } - state.id_receiver = connect_ndi( + let id_receiver = connect_ndi( self.cat, element, settings.ip_address.as_ref().map(String::as_str), @@ -408,19 +442,28 @@ impl BaseSrcImpl for NdiVideoSrc { &settings.receiver_ndi_name, settings.connect_timeout, settings.bandwidth, + &self.unlock, ); // settings.id_receiver exists - match state.id_receiver { + match id_receiver { + None if self.unlock.load(Ordering::SeqCst) => Ok(()), None => Err(gst_error_msg!( gst::ResourceError::NotFound, ["Could not connect to this source"] )), - _ => Ok(()), + Some(id_receiver) => { + let mut state = self.state.lock().unwrap(); + state.id_receiver = Some(id_receiver); + + Ok(()) + } } } fn stop(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { + self.unlock.store(true, Ordering::SeqCst); + *self.state.lock().unwrap() = Default::default(); let mut state = self.state.lock().unwrap(); From 66d4fd1d90cb6bc8a8fa12ef154314d7b7e2ad22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Wed, 17 Jul 2019 19:10:20 +0300 Subject: [PATCH 140/199] Move connecting and capturing to separate threads This ensures that we'll be able to capture every frame even if downstream of the source is blocking for a moment, and also allows us to make all operations cancellable. --- src/lib.rs | 169 +----- src/ndi.rs | 30 +- src/ndiaudiosrc.rs | 323 +++-------- src/ndisys.rs | 24 +- src/ndivideosrc.rs | 537 +++--------------- src/receiver.rs | 1358 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 1567 insertions(+), 874 deletions(-) create mode 100644 src/receiver.rs diff --git a/src/lib.rs b/src/lib.rs index d0b78d90..a1e2d0e3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,13 +16,13 @@ pub mod ndi; mod ndiaudiosrc; pub mod ndisys; mod ndivideosrc; +pub mod receiver; use ndi::*; use ndisys::*; +use receiver::*; use std::collections::HashMap; -use std::sync::atomic::{AtomicUsize, AtomicBool, Ordering}; -use std::sync::Mutex; use std::time; #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] @@ -43,21 +43,7 @@ fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { Ok(()) } -struct ReceiverInfo { - id: usize, - ndi_name: String, - ip_address: String, - video: bool, - audio: bool, - ndi_instance: RecvInstance, -} - lazy_static! { - static ref HASHMAP_RECEIVERS: Mutex> = { - let m = HashMap::new(); - Mutex::new(m) - }; - static ref DEFAULT_RECEIVER_NDI_NAME: String = { format!("GStreamer NDI Source {}-{}", env!("CARGO_PKG_VERSION"), env!("COMMIT_ID")) }; @@ -73,157 +59,6 @@ lazy_static! { }; } -static ID_RECEIVER: AtomicUsize = AtomicUsize::new(0); - -fn connect_ndi( - cat: gst::DebugCategory, - element: &gst_base::BaseSrc, - ip_address: Option<&str>, - ndi_name: Option<&str>, - receiver_ndi_name: &str, - connect_timeout: u32, - bandwidth: NDIlib_recv_bandwidth_e, - cancel: &AtomicBool, -) -> Option { - gst_debug!(cat, obj: element, "Starting NDI connection..."); - - let mut receivers = HASHMAP_RECEIVERS.lock().unwrap(); - - let video = element.get_type() == ndivideosrc::NdiVideoSrc::get_type(); - - for val in receivers.values_mut() { - if Some(val.ip_address.as_str()) == ip_address || Some(val.ndi_name.as_str()) == ndi_name { - if (val.video || !video) && (val.audio || video) { - continue; - } else { - if video { - val.video = true; - } else { - val.audio = true; - } - return Some(val.id); - } - } - } - - let mut find = match FindInstance::builder().build() { - None => { - gst_element_error!( - element, - gst::CoreError::Negotiation, - ["Cannot run NDI: NDIlib_find_create_v2 error"] - ); - return None; - } - Some(find) => find, - }; - - let timeout = time::Instant::now(); - let source = loop { - if cancel.load(Ordering::SeqCst) { - gst_debug!(cat, obj: element, "Cancelled"); - return None; - } - - find.wait_for_sources(50); - - if cancel.load(Ordering::SeqCst) { - gst_debug!(cat, obj: element, "Cancelled"); - return None; - } - - let sources = find.get_current_sources(); - - gst_debug!( - cat, - obj: element, - "Total sources found in network {}", - sources.len(), - ); - - let source = sources - .iter() - .find(|s| Some(s.ndi_name()) == ndi_name || Some(s.ip_address()) == ip_address); - - if let Some(source) = source { - break source.to_owned(); - } - - if timeout.elapsed().as_millis() >= connect_timeout as u128 { - gst_element_error!(element, gst::ResourceError::NotFound, ["Stream not found"]); - return None; - } - }; - - gst_debug!( - cat, - obj: element, - "Connecting to NDI source with ndi-name '{}' and ip-address '{}'", - source.ndi_name(), - source.ip_address(), - ); - - // FIXME: Ideally we would use NDIlib_recv_color_format_fastest here but that seems to be - // broken with interlaced content currently - let recv = RecvInstance::builder(&source, receiver_ndi_name) - .bandwidth(bandwidth) - .color_format(NDIlib_recv_color_format_e::NDIlib_recv_color_format_UYVY_BGRA) - .allow_video_fields(true) - .build(); - let recv = match recv { - None => { - gst_element_error!( - element, - gst::CoreError::Negotiation, - ["Cannot run NDI: NDIlib_recv_create_v3 error"] - ); - return None; - } - Some(recv) => recv, - }; - - recv.set_tally(&Tally::default()); - - let enable_hw_accel = MetadataFrame::new(0, Some("")); - recv.send_metadata(&enable_hw_accel); - - let id_receiver = ID_RECEIVER.fetch_add(1, Ordering::SeqCst); - receivers.insert( - id_receiver, - ReceiverInfo { - id: id_receiver, - ndi_name: source.ndi_name().to_owned(), - ip_address: source.ip_address().to_owned(), - video, - audio: !video, - ndi_instance: recv, - }, - ); - - gst_debug!(cat, obj: element, "Started NDI connection"); - Some(id_receiver) -} - -fn stop_ndi(cat: gst::DebugCategory, element: &gst_base::BaseSrc, id: usize) -> bool { - gst_debug!(cat, obj: element, "Closing NDI connection..."); - let mut receivers = HASHMAP_RECEIVERS.lock().unwrap(); - { - let val = receivers.get_mut(&id).unwrap(); - if val.video && val.audio { - let video = element.get_type() == ndivideosrc::NdiVideoSrc::get_type(); - if video { - val.video = false; - } else { - val.audio = false; - } - return true; - } - } - receivers.remove(&id); - gst_debug!(cat, obj: element, "Closed NDI connection"); - true -} - impl glib::translate::ToGlib for TimestampMode { type GlibType = i32; diff --git a/src/ndi.rs b/src/ndi.rs index 9c8f7483..d99855b2 100644 --- a/src/ndi.rs +++ b/src/ndi.rs @@ -281,6 +281,15 @@ impl RecvInstance { } } + pub fn get_queue(&self) -> Queue { + unsafe { + let _lock = (self.0).1.lock().unwrap(); + let mut queue = mem::MaybeUninit::uninit(); + NDIlib_recv_get_queue(((self.0).0).0.as_ptr(), queue.as_mut_ptr()); + Queue(queue.assume_init()) + } + } + pub fn capture( &self, video: bool, @@ -490,7 +499,7 @@ impl<'a> VideoFrame<'a> { impl<'a> Drop for VideoFrame<'a> { #[allow(irrefutable_let_patterns)] fn drop(&mut self) { - if let VideoFrame::Borrowed(ref frame, ref recv) = *self { + if let VideoFrame::Borrowed(ref mut frame, ref recv) = *self { unsafe { NDIlib_recv_free_video_v2(((recv.0).0).0.as_ptr() as *mut _, frame); } @@ -597,7 +606,7 @@ impl<'a> AudioFrame<'a> { impl<'a> Drop for AudioFrame<'a> { #[allow(irrefutable_let_patterns)] fn drop(&mut self) { - if let AudioFrame::Borrowed(ref frame, ref recv) = *self { + if let AudioFrame::Borrowed(ref mut frame, ref recv) = *self { unsafe { NDIlib_recv_free_audio_v2(((recv.0).0).0.as_ptr() as *mut _, frame); } @@ -689,10 +698,25 @@ impl<'a> Default for MetadataFrame<'a> { impl<'a> Drop for MetadataFrame<'a> { fn drop(&mut self) { - if let MetadataFrame::Borrowed(ref frame, ref recv) = *self { + if let MetadataFrame::Borrowed(ref mut frame, ref recv) = *self { unsafe { NDIlib_recv_free_metadata(((recv.0).0).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 + } +} diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index 51a6837a..ebcf6dae 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -10,24 +10,16 @@ use gst_base::prelude::*; use gst_base::subclass::prelude::*; use std::sync::Mutex; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::time; use std::{i32, u32}; use connect_ndi; -use ndi::*; use ndisys; -use stop_ndi; +use Receiver; +use ReceiverControlHandle; +use ReceiverItem; use TimestampMode; use DEFAULT_RECEIVER_NDI_NAME; -use HASHMAP_RECEIVERS; -#[cfg(feature = "reference-timestamps")] -use TIMECODE_CAPS; -#[cfg(feature = "reference-timestamps")] -use TIMESTAMP_CAPS; - -use byte_slice_cast::AsMutSliceOf; #[derive(Debug, Clone)] struct Settings { @@ -129,7 +121,7 @@ static PROPERTIES: [subclass::Property; 7] = [ struct State { info: Option, - id_receiver: Option, + receiver: Option, current_latency: gst::ClockTime, } @@ -137,7 +129,7 @@ impl Default for State { fn default() -> State { State { info: None, - id_receiver: None, + receiver: None, current_latency: gst::CLOCK_TIME_NONE, } } @@ -147,7 +139,7 @@ pub(crate) struct NdiAudioSrc { cat: gst::DebugCategory, settings: Mutex, state: Mutex, - unlock: AtomicBool, + receiver_controller: Mutex>, } impl ObjectSubclass for NdiAudioSrc { @@ -167,7 +159,7 @@ impl ObjectSubclass for NdiAudioSrc { ), settings: Mutex::new(Default::default()), state: Mutex::new(Default::default()), - unlock: AtomicBool::new(false), + receiver_controller: Mutex::new(None), } } @@ -353,26 +345,52 @@ impl ObjectImpl for NdiAudioSrc { } } -impl ElementImpl for NdiAudioSrc {} +impl ElementImpl for NdiAudioSrc { + fn change_state( + &self, + element: &gst::Element, + transition: gst::StateChange, + ) -> Result { + match transition { + gst::StateChange::PausedToPlaying => { + if let Some(ref controller) = *self.receiver_controller.lock().unwrap() { + controller.set_playing(true); + } + } + gst::StateChange::PlayingToPaused => { + if let Some(ref controller) = *self.receiver_controller.lock().unwrap() { + controller.set_playing(false); + } + } + gst::StateChange::PausedToReady => { + if let Some(ref controller) = *self.receiver_controller.lock().unwrap() { + controller.shutdown(); + } + } + _ => (), + } + + self.parent_change_state(element, transition) + } +} impl BaseSrcImpl for NdiAudioSrc { fn unlock(&self, element: &gst_base::BaseSrc) -> std::result::Result<(), gst::ErrorMessage> { - gst_debug!( - self.cat, - obj: element, - "Unlocking", - ); - self.unlock.store(true, Ordering::SeqCst); + gst_debug!(self.cat, obj: element, "Unlocking",); + if let Some(ref controller) = *self.receiver_controller.lock().unwrap() { + controller.set_flushing(true); + } Ok(()) } - fn unlock_stop(&self, element: &gst_base::BaseSrc) -> std::result::Result<(), gst::ErrorMessage> { - gst_debug!( - self.cat, - obj: element, - "Stop unlocking", - ); - self.unlock.store(false, Ordering::SeqCst); + fn unlock_stop( + &self, + element: &gst_base::BaseSrc, + ) -> std::result::Result<(), gst::ErrorMessage> { + gst_debug!(self.cat, obj: element, "Stop unlocking",); + if let Some(ref controller) = *self.receiver_controller.lock().unwrap() { + controller.set_flushing(false); + } Ok(()) } @@ -387,7 +405,7 @@ impl BaseSrcImpl for NdiAudioSrc { )); } - let id_receiver = connect_ndi( + let receiver = connect_ndi( self.cat, element, settings.ip_address.as_ref().map(String::as_str), @@ -395,33 +413,32 @@ impl BaseSrcImpl for NdiAudioSrc { &settings.receiver_ndi_name, settings.connect_timeout, settings.bandwidth, - &self.unlock, + settings.timestamp_mode, + settings.timeout, ); // settings.id_receiver exists - match id_receiver { - None if self.unlock.load(Ordering::SeqCst) => Ok(()), + match receiver { None => Err(gst_error_msg!( gst::ResourceError::NotFound, ["Could not connect to this source"] )), - Some(id_receiver) => { + Some(receiver) => { + *self.receiver_controller.lock().unwrap() = + Some(receiver.receiver_control_handle()); let mut state = self.state.lock().unwrap(); - state.id_receiver = Some(id_receiver); + state.receiver = Some(receiver); Ok(()) } } } - fn stop(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { - *self.state.lock().unwrap() = Default::default(); - - let mut state = self.state.lock().unwrap(); - if let Some(id_receiver) = state.id_receiver.take() { - stop_ndi(self.cat, element, id_receiver); + fn stop(&self, _element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { + if let Some(ref controller) = self.receiver_controller.lock().unwrap().take() { + controller.shutdown(); } - *state = State::default(); + *self.state.lock().unwrap() = State::default(); Ok(()) } @@ -474,210 +491,42 @@ impl BaseSrcImpl for NdiAudioSrc { _offset: u64, _length: u32, ) -> Result { - self.capture(element) - } -} - -impl NdiAudioSrc { - fn capture(&self, element: &gst_base::BaseSrc) -> Result { - let settings = self.settings.lock().unwrap().clone(); - let recv = { - let state = self.state.lock().unwrap(); - let receivers = HASHMAP_RECEIVERS.lock().unwrap(); - let receiver = &receivers.get(&state.id_receiver.unwrap()).unwrap(); - receiver.ndi_instance.clone() - }; - - let timeout = time::Instant::now(); - let audio_frame = loop { - // FIXME: make interruptable - let res = loop { - match recv.capture(false, true, false, 50) { - Err(_) => break Err(()), - Ok(None) => break Ok(None), - Ok(Some(Frame::Audio(frame))) => break Ok(Some(frame)), - _ => unreachable!(), - } - }; - - let audio_frame = match res { - Err(_) => { - gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type error received, assuming that the source closed the stream...."]); + let mut state = self.state.lock().unwrap(); + match state.receiver.take() { + Some(recv) => recv, + None => { + gst_error!(self.cat, obj: element, "Have no receiver"); return Err(gst::FlowError::Error); } - Ok(None) if timeout.elapsed().as_millis() >= settings.timeout as u128 => { - return Err(gst::FlowError::Eos); - } - Ok(None) => { - gst_debug!(self.cat, obj: element, "No audio frame received yet, retry"); - continue; - } - Ok(Some(frame)) => frame, - }; - - break audio_frame; + } }; - let pts = self.calculate_timestamp(element, &settings, &audio_frame); - let info = self.create_audio_info(element, &audio_frame)?; + match recv.capture() { + ReceiverItem::AudioBuffer(buffer, info) => { + let mut state = self.state.lock().unwrap(); + state.receiver = Some(recv); + if state.info.as_ref() != Some(&info) { + let caps = info.to_caps().unwrap(); + state.info = Some(info.clone()); + state.current_latency = buffer.get_duration(); + drop(state); + gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); + element + .set_caps(&caps) + .map_err(|_| gst::FlowError::NotNegotiated)?; - { - let mut state = self.state.lock().unwrap(); - if state.info.as_ref() != Some(&info) { - let caps = info.to_caps().unwrap(); - state.info = Some(info.clone()); - state.current_latency = gst::SECOND - .mul_div_ceil( - audio_frame.no_samples() as u64, - audio_frame.sample_rate() as u64, - ) - .unwrap_or(gst::CLOCK_TIME_NONE); - drop(state); - gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); - element - .set_caps(&caps) - .map_err(|_| gst::FlowError::NotNegotiated)?; + let _ = element + .post_message(&gst::Message::new_latency().src(Some(element)).build()); + } - let _ = - element.post_message(&gst::Message::new_latency().src(Some(element)).build()); + Ok(buffer) } + ReceiverItem::Flushing => Err(gst::FlowError::Flushing), + ReceiverItem::Timeout => Err(gst::FlowError::Eos), + ReceiverItem::Error(err) => Err(err), + ReceiverItem::VideoBuffer(..) => unreachable!(), } - - let buffer = self.create_buffer(element, pts, &info, &audio_frame)?; - - gst_log!(self.cat, obj: element, "Produced buffer {:?}", buffer); - - Ok(buffer) - } - - fn calculate_timestamp( - &self, - element: &gst_base::BaseSrc, - settings: &Settings, - audio_frame: &AudioFrame, - ) -> gst::ClockTime { - let clock = element.get_clock().unwrap(); - - // For now take the current running time as PTS. At a later time we - // will want to work with the timestamp given by the NDI SDK if available - let now = clock.get_time(); - let base_time = element.get_base_time(); - let receive_time = now - base_time; - - let real_time_now = gst::ClockTime::from(glib::get_real_time() as u64 * 1000); - let timestamp = if audio_frame.timestamp() == ndisys::NDIlib_recv_timestamp_undefined { - gst::CLOCK_TIME_NONE - } else { - gst::ClockTime::from(audio_frame.timestamp() as u64 * 100) - }; - let timecode = gst::ClockTime::from(audio_frame.timecode() as u64 * 100); - - gst_log!( - self.cat, - obj: element, - "NDI audio frame received: {:?} with timecode {} and timestamp {}, receive time {}, local time now {}", - audio_frame, - timecode, - timestamp, - receive_time, - real_time_now, - ); - - let pts = match settings.timestamp_mode { - TimestampMode::ReceiveTime => receive_time, - TimestampMode::Timecode => timecode, - TimestampMode::Timestamp if timestamp.is_none() => receive_time, - TimestampMode::Timestamp => { - // Timestamps are relative to the UNIX epoch - if real_time_now > timestamp { - let diff = real_time_now - timestamp; - if diff > receive_time { - 0.into() - } else { - receive_time - diff - } - } else { - let diff = timestamp - real_time_now; - receive_time + diff - } - } - }; - - gst_log!( - self.cat, - obj: element, - "Calculated pts for audio frame: {:?}", - pts - ); - - pts - } - - fn create_audio_info( - &self, - _element: &gst_base::BaseSrc, - audio_frame: &AudioFrame, - ) -> Result { - let builder = gst_audio::AudioInfo::new( - gst_audio::AUDIO_FORMAT_S16, - audio_frame.sample_rate() as u32, - audio_frame.no_channels() as u32, - ); - - Ok(builder.build().unwrap()) - } - - fn create_buffer( - &self, - _element: &gst_base::BaseSrc, - pts: gst::ClockTime, - info: &gst_audio::AudioInfo, - audio_frame: &AudioFrame, - ) -> Result { - // We multiply by 2 because is the size in bytes of an i16 variable - let buff_size = (audio_frame.no_samples() as u32 * info.bpf()) as usize; - let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); - { - let duration = gst::SECOND - .mul_div_floor( - audio_frame.no_samples() as u64, - audio_frame.sample_rate() as u64, - ) - .unwrap_or(gst::CLOCK_TIME_NONE); - let buffer = buffer.get_mut().unwrap(); - - buffer.set_pts(pts); - buffer.set_duration(duration); - - #[cfg(feature = "reference-timestamps")] - { - gst::ReferenceTimestampMeta::add( - buffer, - &*TIMECODE_CAPS, - gst::ClockTime::from(audio_frame.timecode() as u64 * 100), - gst::CLOCK_TIME_NONE, - ); - if audio_frame.timestamp() != ndisys::NDIlib_recv_timestamp_undefined { - gst::ReferenceTimestampMeta::add( - buffer, - &*TIMESTAMP_CAPS, - gst::ClockTime::from(audio_frame.timestamp() as u64 * 100), - gst::CLOCK_TIME_NONE, - ); - } - } - - audio_frame.copy_to_interleaved_16s( - buffer - .map_writable() - .unwrap() - .as_mut_slice_of::() - .unwrap(), - ); - } - - Ok(buffer) } } diff --git a/src/ndisys.rs b/src/ndisys.rs index 67399d0f..0aee3d6d 100644 --- a/src/ndisys.rs +++ b/src/ndisys.rs @@ -41,22 +41,26 @@ extern "C" { ) -> bool; pub fn NDIlib_recv_capture_v2( p_instance: NDIlib_recv_instance_t, - p_video_data: *const NDIlib_video_frame_v2_t, - p_audio_data: *const NDIlib_audio_frame_v2_t, - p_metadata: *const NDIlib_metadata_frame_t, + p_video_data: *mut NDIlib_video_frame_v2_t, + p_audio_data: *mut NDIlib_audio_frame_v2_t, + p_metadata: *mut NDIlib_metadata_frame_t, timeout_in_ms: u32, ) -> NDIlib_frame_type_e; pub fn NDIlib_recv_free_video_v2( p_instance: NDIlib_recv_instance_t, - p_video_data: *const NDIlib_video_frame_v2_t, + p_video_data: *mut NDIlib_video_frame_v2_t, ); pub fn NDIlib_recv_free_audio_v2( p_instance: NDIlib_recv_instance_t, - p_audio_data: *const NDIlib_audio_frame_v2_t, + p_audio_data: *mut NDIlib_audio_frame_v2_t, ); pub fn NDIlib_recv_free_metadata( p_instance: NDIlib_recv_instance_t, - p_metadata: *const NDIlib_metadata_frame_t, + p_metadata: *mut NDIlib_metadata_frame_t, + ); + pub fn NDIlib_recv_get_queue( + p_instance: NDIlib_recv_instance_t, + p_total: *mut NDIlib_recv_queue_t, ); } @@ -151,6 +155,14 @@ pub struct NDIlib_tally_t { pub on_preview: bool, } +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct NDIlib_recv_queue_t { + pub video_frames: i32, + pub audio_frames: i32, + pub metadata_frames: i32, +} + #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct NDIlib_metadata_frame_t { diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index 02e353c3..0ca21f1b 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -9,26 +9,19 @@ use gst_base::prelude::*; use gst_base::subclass::prelude::*; use gst_video; -use gst_video::prelude::*; use std::sync::Mutex; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::time; use std::{i32, u32}; -use ndi::*; use ndisys; use connect_ndi; -use stop_ndi; +use Receiver; +use ReceiverControlHandle; +use ReceiverItem; use TimestampMode; use DEFAULT_RECEIVER_NDI_NAME; -use HASHMAP_RECEIVERS; -#[cfg(feature = "reference-timestamps")] -use TIMECODE_CAPS; -#[cfg(feature = "reference-timestamps")] -use TIMESTAMP_CAPS; #[derive(Debug, Clone)] struct Settings { @@ -130,16 +123,16 @@ static PROPERTIES: [subclass::Property; 7] = [ struct State { info: Option, - id_receiver: Option, current_latency: gst::ClockTime, + receiver: Option, } impl Default for State { fn default() -> State { State { info: None, - id_receiver: None, current_latency: gst::CLOCK_TIME_NONE, + receiver: None, } } } @@ -148,7 +141,7 @@ pub(crate) struct NdiVideoSrc { cat: gst::DebugCategory, settings: Mutex, state: Mutex, - unlock: AtomicBool, + receiver_controller: Mutex>, } impl ObjectSubclass for NdiVideoSrc { @@ -168,7 +161,7 @@ impl ObjectSubclass for NdiVideoSrc { ), settings: Mutex::new(Default::default()), state: Mutex::new(Default::default()), - unlock: AtomicBool::new(false), + receiver_controller: Mutex::new(None), } } @@ -389,10 +382,27 @@ impl ObjectImpl for NdiVideoSrc { } impl ElementImpl for NdiVideoSrc { - fn change_state(&self, element: &gst::Element, transition: gst::StateChange) -> Result { + fn change_state( + &self, + element: &gst::Element, + transition: gst::StateChange, + ) -> Result { match transition { - gst::StateChange::ReadyToPaused => self.unlock.store(true, Ordering::SeqCst), - gst::StateChange::PausedToReady => self.unlock.store(false, Ordering::SeqCst), + gst::StateChange::PausedToPlaying => { + if let Some(ref controller) = *self.receiver_controller.lock().unwrap() { + controller.set_playing(true); + } + } + gst::StateChange::PlayingToPaused => { + if let Some(ref controller) = *self.receiver_controller.lock().unwrap() { + controller.set_playing(false); + } + } + gst::StateChange::PausedToReady => { + if let Some(ref controller) = *self.receiver_controller.lock().unwrap() { + controller.shutdown(); + } + } _ => (), } @@ -402,28 +412,25 @@ impl ElementImpl for NdiVideoSrc { impl BaseSrcImpl for NdiVideoSrc { fn unlock(&self, element: &gst_base::BaseSrc) -> std::result::Result<(), gst::ErrorMessage> { - gst_debug!( - self.cat, - obj: element, - "Unlocking", - ); - self.unlock.store(true, Ordering::SeqCst); + gst_debug!(self.cat, obj: element, "Unlocking",); + if let Some(ref controller) = *self.receiver_controller.lock().unwrap() { + controller.set_flushing(true); + } Ok(()) } - fn unlock_stop(&self, element: &gst_base::BaseSrc) -> std::result::Result<(), gst::ErrorMessage> { - gst_debug!( - self.cat, - obj: element, - "Stop unlocking", - ); - self.unlock.store(false, Ordering::SeqCst); + fn unlock_stop( + &self, + element: &gst_base::BaseSrc, + ) -> std::result::Result<(), gst::ErrorMessage> { + gst_debug!(self.cat, obj: element, "Stop unlocking",); + if let Some(ref controller) = *self.receiver_controller.lock().unwrap() { + controller.set_flushing(false); + } Ok(()) } fn start(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { - self.unlock.store(false, Ordering::SeqCst); - *self.state.lock().unwrap() = Default::default(); let settings = self.settings.lock().unwrap().clone(); @@ -434,7 +441,7 @@ impl BaseSrcImpl for NdiVideoSrc { )); } - let id_receiver = connect_ndi( + let receiver = connect_ndi( self.cat, element, settings.ip_address.as_ref().map(String::as_str), @@ -442,35 +449,32 @@ impl BaseSrcImpl for NdiVideoSrc { &settings.receiver_ndi_name, settings.connect_timeout, settings.bandwidth, - &self.unlock, + settings.timestamp_mode, + settings.timeout, ); // settings.id_receiver exists - match id_receiver { - None if self.unlock.load(Ordering::SeqCst) => Ok(()), + match receiver { None => Err(gst_error_msg!( gst::ResourceError::NotFound, ["Could not connect to this source"] )), - Some(id_receiver) => { + Some(receiver) => { + *self.receiver_controller.lock().unwrap() = + Some(receiver.receiver_control_handle()); let mut state = self.state.lock().unwrap(); - state.id_receiver = Some(id_receiver); + state.receiver = Some(receiver); Ok(()) } } } - fn stop(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { - self.unlock.store(true, Ordering::SeqCst); - - *self.state.lock().unwrap() = Default::default(); - - let mut state = self.state.lock().unwrap(); - if let Some(id_receiver) = state.id_receiver.take() { - stop_ndi(self.cat, element, id_receiver); + fn stop(&self, _element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { + if let Some(ref controller) = self.receiver_controller.lock().unwrap().take() { + controller.shutdown(); } - *state = State::default(); + *self.state.lock().unwrap() = State::default(); Ok(()) } @@ -527,431 +531,42 @@ impl BaseSrcImpl for NdiVideoSrc { _offset: u64, _length: u32, ) -> Result { - self.capture(element) - } -} - -impl NdiVideoSrc { - fn capture(&self, element: &gst_base::BaseSrc) -> Result { - let settings = self.settings.lock().unwrap().clone(); - let recv = { - let state = self.state.lock().unwrap(); - let receivers = HASHMAP_RECEIVERS.lock().unwrap(); - let receiver = &receivers.get(&state.id_receiver.unwrap()).unwrap(); - receiver.ndi_instance.clone() - }; - - let timeout = time::Instant::now(); - let video_frame = loop { - // FIXME: make interruptable - let res = loop { - match recv.capture(true, false, false, 50) { - Err(_) => break Err(()), - Ok(None) => break Ok(None), - Ok(Some(Frame::Video(frame))) => break Ok(Some(frame)), - _ => unreachable!(), - } - }; - - let video_frame = match res { - Err(_) => { - gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type error received, assuming that the source closed the stream...."]); + let mut state = self.state.lock().unwrap(); + match state.receiver.take() { + Some(recv) => recv, + None => { + gst_error!(self.cat, obj: element, "Have no receiver"); return Err(gst::FlowError::Error); } - Ok(None) if timeout.elapsed().as_millis() >= settings.timeout as u128 => { - return Err(gst::FlowError::Eos); - } - Ok(None) => { - gst_debug!(self.cat, obj: element, "No video frame received yet, retry"); - continue; - } - Ok(Some(frame)) => frame, - }; - - break video_frame; - }; - - let pts = self.calculate_timestamp(element, &settings, &video_frame); - let info = self.create_video_info(element, &video_frame)?; - - { - let mut state = self.state.lock().unwrap(); - if state.info.as_ref() != Some(&info) { - let caps = info.to_caps().unwrap(); - state.info = Some(info.clone()); - state.current_latency = gst::SECOND - .mul_div_ceil( - video_frame.frame_rate().1 as u64, - video_frame.frame_rate().0 as u64, - ) - .unwrap_or(gst::CLOCK_TIME_NONE); - drop(state); - gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); - element - .set_caps(&caps) - .map_err(|_| gst::FlowError::NotNegotiated)?; - - let _ = - element.post_message(&gst::Message::new_latency().src(Some(element)).build()); - } - } - - let buffer = self.create_buffer(element, pts, &info, &video_frame)?; - - gst_log!(self.cat, obj: element, "Produced buffer {:?}", buffer); - - Ok(buffer) - } - - fn calculate_timestamp( - &self, - element: &gst_base::BaseSrc, - settings: &Settings, - video_frame: &VideoFrame, - ) -> gst::ClockTime { - let clock = element.get_clock().unwrap(); - - // For now take the current running time as PTS. At a later time we - // will want to work with the timestamp given by the NDI SDK if available - let now = clock.get_time(); - let base_time = element.get_base_time(); - let receive_time = now - base_time; - - let real_time_now = gst::ClockTime::from(glib::get_real_time() as u64 * 1000); - let timestamp = if video_frame.timestamp() == ndisys::NDIlib_recv_timestamp_undefined { - gst::CLOCK_TIME_NONE - } else { - gst::ClockTime::from(video_frame.timestamp() as u64 * 100) - }; - let timecode = gst::ClockTime::from(video_frame.timecode() as u64 * 100); - - gst_log!( - self.cat, - obj: element, - "NDI video frame received: {:?} with timecode {} and timestamp {}, receive time {}, local time now {}", - video_frame, - timecode, - timestamp, - receive_time, - real_time_now, - ); - - let pts = match settings.timestamp_mode { - TimestampMode::ReceiveTime => receive_time, - TimestampMode::Timecode => timecode, - TimestampMode::Timestamp if timestamp.is_none() => receive_time, - TimestampMode::Timestamp => { - // Timestamps are relative to the UNIX epoch - if real_time_now > timestamp { - let diff = real_time_now - timestamp; - if diff > receive_time { - 0.into() - } else { - receive_time - diff - } - } else { - let diff = timestamp - real_time_now; - receive_time + diff - } } }; - gst_log!( - self.cat, - obj: element, - "Calculated pts for video frame: {:?}", - pts - ); + match recv.capture() { + ReceiverItem::VideoBuffer(buffer, info) => { + let mut state = self.state.lock().unwrap(); + state.receiver = Some(recv); + if state.info.as_ref() != Some(&info) { + let caps = info.to_caps().unwrap(); + state.info = Some(info.clone()); + state.current_latency = buffer.get_duration(); + drop(state); + gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); + element + .set_caps(&caps) + .map_err(|_| gst::FlowError::NotNegotiated)?; - pts - } - - fn create_video_info( - &self, - _element: &gst_base::BaseSrc, - video_frame: &VideoFrame, - ) -> Result { - // YV12 and I420 are swapped in the NDI SDK compared to GStreamer - let format = match video_frame.fourcc() { - ndisys::NDIlib_FourCC_type_e::NDIlib_FourCC_type_UYVY => gst_video::VideoFormat::Uyvy, - ndisys::NDIlib_FourCC_type_e::NDIlib_FourCC_type_YV12 => gst_video::VideoFormat::I420, - ndisys::NDIlib_FourCC_type_e::NDIlib_FourCC_type_NV12 => gst_video::VideoFormat::Nv12, - ndisys::NDIlib_FourCC_type_e::NDIlib_FourCC_type_I420 => gst_video::VideoFormat::Yv12, - ndisys::NDIlib_FourCC_type_e::NDIlib_FourCC_type_BGRA => gst_video::VideoFormat::Bgra, - ndisys::NDIlib_FourCC_type_e::NDIlib_FourCC_type_BGRX => gst_video::VideoFormat::Bgrx, - ndisys::NDIlib_FourCC_type_e::NDIlib_FourCC_type_RGBA => gst_video::VideoFormat::Rgba, - ndisys::NDIlib_FourCC_type_e::NDIlib_FourCC_type_RGBX => gst_video::VideoFormat::Rgbx, - ndisys::NDIlib_FourCC_type_e::NDIlib_FourCC_type_UYVA => gst_video::VideoFormat::Uyvy, - }; - - let par = gst::Fraction::approximate_f32(video_frame.picture_aspect_ratio()).unwrap() - * gst::Fraction::new(video_frame.yres(), video_frame.xres()); - - #[cfg(feature = "interlaced-fields")] - { - let mut builder = gst_video::VideoInfo::new( - format, - video_frame.xres() as u32, - video_frame.yres() as u32, - ) - .fps(gst::Fraction::from(video_frame.frame_rate())) - .par(par) - .interlace_mode(match video_frame.frame_format_type() { - ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_progressive => { - gst_video::VideoInterlaceMode::Progressive + let _ = element + .post_message(&gst::Message::new_latency().src(Some(element)).build()); } - ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_interleaved => { - gst_video::VideoInterlaceMode::Interleaved - } - _ => gst_video::VideoInterlaceMode::Alternate, - }); - /* Requires GStreamer 1.12 at least */ - if video_frame.frame_format_type() - == ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_interleaved - { - builder = builder.field_order(gst_video::VideoFieldOrder::TopFieldFirst); + Ok(buffer) } - - Ok(builder.build().unwrap()) + ReceiverItem::Timeout => Err(gst::FlowError::Eos), + ReceiverItem::Flushing => Err(gst::FlowError::Flushing), + ReceiverItem::Error(err) => Err(err), + ReceiverItem::AudioBuffer(..) => unreachable!(), } - - #[cfg(not(feature = "interlaced-fields"))] - { - if video_frame.frame_format_type() - != ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_progressive - && video_frame.frame_format_type() - != ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_interleaved - { - gst_element_error!( - element, - gst::StreamError::Format, - ["Separate field interlacing not supported"] - ); - return Err(gst::FlowError::NotNegotiated); - } - - let builder = gst_video::VideoInfo::new( - format, - video_frame.xres() as u32, - video_frame.yres() as u32, - ) - .fps(gst::Fraction::from(video_frame.frame_rate())) - .par(par) - .interlace_mode( - if video_frame.frame_format_type() - == ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_progressive - { - gst_video::VideoInterlaceMode::Progressive - } else { - gst_video::VideoInterlaceMode::Interleaved - }, - ); - Ok(builder.build().unwrap()); - } - } - - fn create_buffer( - &self, - element: &gst_base::BaseSrc, - pts: gst::ClockTime, - info: &gst_video::VideoInfo, - video_frame: &VideoFrame, - ) -> Result { - let mut buffer = gst::Buffer::with_size(info.size()).unwrap(); - { - let duration = gst::SECOND - .mul_div_floor( - video_frame.frame_rate().1 as u64, - video_frame.frame_rate().0 as u64, - ) - .unwrap_or(gst::CLOCK_TIME_NONE); - let buffer = buffer.get_mut().unwrap(); - buffer.set_pts(pts); - buffer.set_duration(duration); - - #[cfg(feature = "reference-timestamps")] - { - gst::ReferenceTimestampMeta::add( - buffer, - &*TIMECODE_CAPS, - gst::ClockTime::from(video_frame.timecode() as u64 * 100), - gst::CLOCK_TIME_NONE, - ); - if video_frame.timestamp() != ndisys::NDIlib_recv_timestamp_undefined { - gst::ReferenceTimestampMeta::add( - buffer, - &*TIMESTAMP_CAPS, - gst::ClockTime::from(video_frame.timestamp() as u64 * 100), - gst::CLOCK_TIME_NONE, - ); - } - } - - #[cfg(feature = "interlaced-fields")] - { - match video_frame.frame_format_type() { - ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_interleaved => { - buffer.set_video_flags( - gst_video::VideoBufferFlags::INTERLACED - | gst_video::VideoBufferFlags::TFF, - ); - } - ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_field_0 => { - buffer.set_video_flags( - gst_video::VideoBufferFlags::INTERLACED - | gst_video::VideoBufferFlags::TOP_FIELD, - ); - } - ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_field_1 => { - buffer.set_video_flags( - gst_video::VideoBufferFlags::INTERLACED - | gst_video::VideoBufferFlags::BOTTOM_FIELD, - ); - } - _ => (), - }; - } - - #[cfg(not(feature = "interlaced-fields"))] - { - if video_frame.frame_format_type() - == ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_interleaved - { - buffer.set_video_flags( - gst_video::VideoBufferFlags::INTERLACED | gst_video::VideoBufferFlags::TFF, - ); - } - } - } - - self.copy_frame(element, info, buffer, video_frame) - } - - fn copy_frame( - &self, - _element: &gst_base::BaseSrc, - info: &gst_video::VideoInfo, - buffer: gst::Buffer, - video_frame: &VideoFrame, - ) -> Result { - // FIXME: Error handling if frame dimensions don't match - let mut vframe = gst_video::VideoFrame::from_buffer_writable(buffer, info).unwrap(); - - match info.format() { - gst_video::VideoFormat::Uyvy - | gst_video::VideoFormat::Bgra - | gst_video::VideoFormat::Bgrx - | gst_video::VideoFormat::Rgba - | gst_video::VideoFormat::Rgbx => { - let line_bytes = if info.format() == gst_video::VideoFormat::Uyvy { - 2 * vframe.width() as usize - } else { - 4 * vframe.width() as usize - }; - let dest_stride = vframe.plane_stride()[0] as usize; - let dest = vframe.plane_data_mut(0).unwrap(); - let src_stride = video_frame.line_stride_in_bytes() as usize; - let src = video_frame.data(); - - for (dest, src) in dest - .chunks_exact_mut(dest_stride) - .zip(src.chunks_exact(src_stride)) - { - dest.copy_from_slice(src); - dest.copy_from_slice(&src[..line_bytes]); - } - } - gst_video::VideoFormat::Nv12 => { - // First plane - { - let line_bytes = vframe.width() as usize; - let dest_stride = vframe.plane_stride()[0] as usize; - let dest = vframe.plane_data_mut(0).unwrap(); - let src_stride = video_frame.line_stride_in_bytes() as usize; - let src = video_frame.data(); - - for (dest, src) in dest - .chunks_exact_mut(dest_stride) - .zip(src.chunks_exact(src_stride)) - { - dest.copy_from_slice(&src[..line_bytes]); - } - } - - // Second plane - { - let line_bytes = vframe.width() as usize; - let dest_stride = vframe.plane_stride()[1] as usize; - let dest = vframe.plane_data_mut(1).unwrap(); - let src_stride = video_frame.line_stride_in_bytes() as usize; - let src = &video_frame.data()[(video_frame.yres() as usize * src_stride)..]; - - for (dest, src) in dest - .chunks_exact_mut(dest_stride) - .zip(src.chunks_exact(src_stride)) - { - dest.copy_from_slice(&src[..line_bytes]); - } - } - } - gst_video::VideoFormat::Yv12 | gst_video::VideoFormat::I420 => { - // First plane - { - let line_bytes = vframe.width() as usize; - let dest_stride = vframe.plane_stride()[0] as usize; - let dest = vframe.plane_data_mut(0).unwrap(); - let src_stride = video_frame.line_stride_in_bytes() as usize; - let src = video_frame.data(); - - for (dest, src) in dest - .chunks_exact_mut(dest_stride) - .zip(src.chunks_exact(src_stride)) - { - dest.copy_from_slice(&src[..line_bytes]); - } - } - - // Second plane - { - let line_bytes = (vframe.width() as usize + 1) / 2; - let dest_stride = vframe.plane_stride()[1] as usize; - let dest = vframe.plane_data_mut(1).unwrap(); - let src_stride = video_frame.line_stride_in_bytes() as usize; - let src_stride1 = video_frame.line_stride_in_bytes() as usize / 2; - let src = &video_frame.data()[(video_frame.yres() as usize * src_stride)..]; - - for (dest, src) in dest - .chunks_exact_mut(dest_stride) - .zip(src.chunks_exact(src_stride1)) - { - dest.copy_from_slice(&src[..line_bytes]); - } - } - - // Third plane - { - let line_bytes = (vframe.width() as usize + 1) / 2; - let dest_stride = vframe.plane_stride()[2] as usize; - let dest = vframe.plane_data_mut(2).unwrap(); - let src_stride = video_frame.line_stride_in_bytes() as usize; - let src_stride1 = video_frame.line_stride_in_bytes() as usize / 2; - let src = &video_frame.data()[(video_frame.yres() as usize * src_stride - + (video_frame.yres() as usize + 1) / 2 * src_stride1)..]; - - for (dest, src) in dest - .chunks_exact_mut(dest_stride) - .zip(src.chunks_exact(src_stride1)) - { - dest.copy_from_slice(&src[..line_bytes]); - } - } - } - _ => unreachable!(), - } - - Ok(vframe.into_buffer()) } } diff --git a/src/receiver.rs b/src/receiver.rs new file mode 100644 index 00000000..8981f886 --- /dev/null +++ b/src/receiver.rs @@ -0,0 +1,1358 @@ +use glib; +use glib::prelude::*; +use gst; +use gst::prelude::*; +use gst_video; +use gst_video::prelude::*; + +use byte_slice_cast::AsMutSliceOf; + +use std::collections::VecDeque; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::{Arc, Condvar, Mutex, Weak}; +use std::thread; + +use super::*; + +enum ReceiverInfo { + Connecting { + id: usize, + ndi_name: Option, + ip_address: Option, + video: Option>, + audio: Option>, + }, + Connected { + id: usize, + ndi_name: String, + ip_address: String, + recv: RecvInstance, + video: Option>, + audio: Option>, + }, +} + +lazy_static! { + static ref HASHMAP_RECEIVERS: Mutex> = { + let m = HashMap::new(); + Mutex::new(m) + }; +} + +static ID_RECEIVER: AtomicUsize = AtomicUsize::new(0); + +#[derive(Clone)] +pub struct Receiver(Arc); + +#[derive(Debug)] +pub enum ReceiverItem { + AudioBuffer(gst::Buffer, gst_audio::AudioInfo), + VideoBuffer(gst::Buffer, gst_video::VideoInfo), + Flushing, + Timeout, + Error(gst::FlowError), +} + +struct ReceiverInner { + id: usize, + + queue: ReceiverQueue, + + video: bool, + + recv: Mutex>, + recv_cond: Condvar, + + cat: gst::DebugCategory, + element: glib::WeakRef, + timestamp_mode: TimestampMode, + timeout: u32, + + thread: Mutex>>, +} + +#[derive(Clone)] +struct ReceiverQueue(Arc<(Mutex, Condvar)>); + +struct ReceiverQueueInner { + // If we should be capturing at all or go out of our capture loop + // + // This is true as long as the source element is in Paused/Playing + capturing: bool, + + // If we're flushing right now and all buffers should simply be discarded + // and capture() directly returns Flushing + flushing: bool, + + // If we're playing right now or not: if not we simply discard everything captured + playing: bool, + // Queue containing our buffers. This holds at most 5 buffers at a time. + // + // On timeout/error will contain a single item and then never be filled again + buffer_queue: VecDeque, + + error: Option, + timeout: bool, +} + +#[derive(Clone)] +pub struct ReceiverControlHandle { + queue: ReceiverQueue, +} + +impl ReceiverControlHandle { + pub fn set_flushing(&self, flushing: bool) { + let mut queue = (self.queue.0).0.lock().unwrap(); + queue.flushing = flushing; + (self.queue.0).1.notify_all(); + } + + pub fn set_playing(&self, playing: bool) { + let mut queue = (self.queue.0).0.lock().unwrap(); + queue.playing = playing; + } + + pub fn shutdown(&self) { + let mut queue = (self.queue.0).0.lock().unwrap(); + queue.capturing = false; + (self.queue.0).1.notify_all(); + } +} + +impl Receiver { + fn new( + info: &mut ReceiverInfo, + video: bool, + timestamp_mode: TimestampMode, + timeout: u32, + element: &gst_base::BaseSrc, + cat: gst::DebugCategory, + ) -> Self { + let (id, storage, recv) = if video { + match info { + ReceiverInfo::Connecting { + id, ref mut video, .. + } => (*id, video, None), + ReceiverInfo::Connected { + id, + ref mut video, + ref mut recv, + .. + } => (*id, video, Some(recv.clone())), + } + } else { + match info { + ReceiverInfo::Connecting { + id, ref mut audio, .. + } => (*id, audio, None), + ReceiverInfo::Connected { + id, + ref mut audio, + ref mut recv, + .. + } => (*id, audio, Some(recv.clone())), + } + }; + assert!(storage.is_none()); + + let receiver = Receiver(Arc::new(ReceiverInner { + id, + queue: ReceiverQueue(Arc::new(( + Mutex::new(ReceiverQueueInner { + capturing: true, + playing: false, + flushing: false, + buffer_queue: VecDeque::with_capacity(5), + error: None, + timeout: false, + }), + Condvar::new(), + ))), + video, + recv: Mutex::new(recv), + recv_cond: Condvar::new(), + cat, + element: element.downgrade(), + timestamp_mode, + timeout, + thread: Mutex::new(None), + })); + + let weak = Arc::downgrade(&receiver.0); + let thread = thread::spawn(move || { + use std::panic; + + let weak_clone = weak.clone(); + match panic::catch_unwind(panic::AssertUnwindSafe(move || receive_thread(&weak_clone))) + { + Ok(_) => (), + Err(_) => { + if let Some(receiver) = weak.upgrade().map(Receiver) { + if let Some(element) = receiver.0.element.upgrade() { + gst_element_error!( + element, + gst::LibraryError::Failed, + ["Panic while connecting to NDI source"] + ); + } + + let mut queue = (receiver.0.queue.0).0.lock().unwrap(); + queue.error = Some(gst::FlowError::Error); + (receiver.0.queue.0).1.notify_one(); + } + } + } + }); + + let weak = Arc::downgrade(&receiver.0); + *storage = Some(weak); + + *receiver.0.thread.lock().unwrap() = Some(thread); + + receiver + } + + pub fn receiver_control_handle(&self) -> ReceiverControlHandle { + ReceiverControlHandle { + queue: self.0.queue.clone(), + } + } + + pub fn set_flushing(&self, flushing: bool) { + let mut queue = (self.0.queue.0).0.lock().unwrap(); + queue.flushing = flushing; + (self.0.queue.0).1.notify_all(); + } + + pub fn set_playing(&self, playing: bool) { + let mut queue = (self.0.queue.0).0.lock().unwrap(); + queue.playing = playing; + } + + pub fn shutdown(&self) { + let mut queue = (self.0.queue.0).0.lock().unwrap(); + queue.capturing = false; + (self.0.queue.0).1.notify_all(); + } + + pub fn capture(&self) -> ReceiverItem { + let mut queue = (self.0.queue.0).0.lock().unwrap(); + loop { + if let Some(err) = queue.error { + return ReceiverItem::Error(err); + } else if queue.buffer_queue.is_empty() && queue.timeout { + return ReceiverItem::Timeout; + } else if queue.flushing || !queue.capturing { + return ReceiverItem::Flushing; + } else if let Some(item) = queue.buffer_queue.pop_front() { + return item; + } + + queue = (self.0.queue.0).1.wait(queue).unwrap(); + } + } +} + +impl Drop for ReceiverInner { + fn drop(&mut self) { + // Will shut down the receiver thread on the next iteration + let mut queue = (self.queue.0).0.lock().unwrap(); + queue.capturing = false; + drop(queue); + + let element = self.element.upgrade(); + + if let Some(ref element) = element { + gst_debug!(self.cat, obj: element, "Closing NDI connection..."); + } + + let mut receivers = HASHMAP_RECEIVERS.lock().unwrap(); + { + let val = receivers.get_mut(&self.id).unwrap(); + let (audio, video) = match val { + ReceiverInfo::Connecting { + ref mut audio, + ref mut video, + .. + } => (audio, video), + ReceiverInfo::Connected { + ref mut audio, + ref mut video, + .. + } => (audio, video), + }; + if video.is_some() && audio.is_some() { + if self.video { + *video = None; + } else { + *audio = None; + } + return; + } + } + receivers.remove(&self.id); + + if let Some(ref element) = element { + gst_debug!(self.cat, obj: element, "Closed NDI connection"); + } + } +} + +pub fn connect_ndi( + cat: gst::DebugCategory, + element: &gst_base::BaseSrc, + ip_address: Option<&str>, + ndi_name: Option<&str>, + receiver_ndi_name: &str, + connect_timeout: u32, + bandwidth: NDIlib_recv_bandwidth_e, + timestamp_mode: TimestampMode, + timeout: u32, +) -> Option { + gst_debug!(cat, obj: element, "Starting NDI connection..."); + + let mut receivers = HASHMAP_RECEIVERS.lock().unwrap(); + + let video = element.get_type() == ndivideosrc::NdiVideoSrc::get_type(); + + // Check if we already have a receiver for this very stream + for val in receivers.values_mut() { + let (val_audio, val_video, val_ip_address, val_ndi_name) = match val { + ReceiverInfo::Connecting { + ref mut audio, + ref mut video, + ref ip_address, + ref ndi_name, + .. + } => ( + audio, + video, + ip_address.as_ref().map(String::as_ref), + ndi_name.as_ref().map(String::as_ref), + ), + ReceiverInfo::Connected { + ref mut audio, + ref mut video, + ref ip_address, + ref ndi_name, + .. + } => ( + audio, + video, + Some(ip_address.as_str()), + Some(ndi_name.as_str()), + ), + }; + + if val_ip_address == ip_address || val_ndi_name == ndi_name { + if (val_video.is_some() || !video) && (val_audio.is_some() || video) { + gst_error!( + cat, + obj: element, + "Source with ndi-name '{:?}' and ip-address '{:?}' already in use for {}", + val_ndi_name, + val_ip_address, + if video { "video" } else { "audio" }, + ); + + return None; + } else { + return Some(Receiver::new( + val, + video, + timestamp_mode, + timeout, + element, + cat, + )); + } + } + } + + // Otherwise asynchronously search for it and return the receiver to the caller + let id_receiver = ID_RECEIVER.fetch_add(1, Ordering::SeqCst); + let mut info = ReceiverInfo::Connecting { + id: id_receiver, + ndi_name: ndi_name.map(String::from), + ip_address: ip_address.map(String::from), + video: None, + audio: None, + }; + + let receiver = Receiver::new(&mut info, video, timestamp_mode, timeout, element, cat); + + receivers.insert(id_receiver, info); + + let receiver_ndi_name = String::from(receiver_ndi_name); + let element = element.clone(); + thread::spawn(move || { + use std::panic; + + let res = match panic::catch_unwind(move || { + connect_ndi_async( + cat, + &element, + id_receiver, + receiver_ndi_name, + connect_timeout, + bandwidth, + ) + }) { + Ok(res) => res, + Err(_) => Err(Some(gst_error_msg!( + gst::LibraryError::Failed, + ["Panic while connecting to NDI source"] + ))), + }; + + match res { + Ok(_) => (), + Err(None) => { + gst_debug!(cat, "Shutting down while connecting"); + } + Err(Some(err)) => { + gst_error!(cat, "Error while connecting: {:?}", err); + let mut receivers = HASHMAP_RECEIVERS.lock().unwrap(); + let info = match receivers.get_mut(&id_receiver) { + None => return, + Some(val) => val, + }; + + let (audio, video) = match info { + ReceiverInfo::Connecting { + ref audio, + ref video, + .. + } => (audio, video), + ReceiverInfo::Connected { .. } => unreachable!(), + }; + + assert!(audio.is_some() || video.is_some()); + + if let Some(audio) = audio.as_ref().and_then(|v| v.upgrade()).map(Receiver) { + if let Some(element) = audio.0.element.upgrade() { + element.post_error_message(&err); + } + let audio_recv = audio.0.recv.lock().unwrap(); + let mut queue = (audio.0.queue.0).0.lock().unwrap(); + assert!(audio_recv.is_none()); + queue.error = Some(gst::FlowError::Error); + audio.0.recv_cond.notify_one(); + (audio.0.queue.0).1.notify_one(); + } + + if let Some(video) = video.as_ref().and_then(|v| v.upgrade()).map(Receiver) { + if let Some(element) = video.0.element.upgrade() { + element.post_error_message(&err); + } + let video_recv = video.0.recv.lock().unwrap(); + let mut queue = (video.0.queue.0).0.lock().unwrap(); + assert!(video_recv.is_none()); + queue.error = Some(gst::FlowError::Error); + video.0.recv_cond.notify_one(); + (video.0.queue.0).1.notify_one(); + } + } + } + }); + + Some(receiver) +} + +fn connect_ndi_async( + cat: gst::DebugCategory, + element: &gst_base::BaseSrc, + id_receiver: usize, + receiver_ndi_name: String, + connect_timeout: u32, + bandwidth: NDIlib_recv_bandwidth_e, +) -> Result<(), Option> { + let mut find = match FindInstance::builder().build() { + None => { + return Err(Some(gst_error_msg!( + gst::CoreError::Negotiation, + ["Cannot run NDI: NDIlib_find_create_v2 error"] + ))); + } + Some(find) => find, + }; + + let timer = time::Instant::now(); + let source = loop { + find.wait_for_sources(100); + let sources = find.get_current_sources(); + + gst_debug!( + cat, + obj: element, + "Total sources found in network {}", + sources.len(), + ); + + { + let receivers = HASHMAP_RECEIVERS.lock().unwrap(); + let info = match receivers.get(&id_receiver) { + None => return Err(None), + Some(val) => val, + }; + + let (ndi_name, ip_address) = match info { + ReceiverInfo::Connecting { + ref ndi_name, + ref ip_address, + ref audio, + ref video, + .. + } => { + assert!(audio.is_some() || video.is_some()); + (ndi_name, ip_address) + } + ReceiverInfo::Connected { .. } => unreachable!(), + }; + + let source = sources.iter().find(|s| { + Some(s.ndi_name()) == ndi_name.as_ref().map(String::as_str) + || Some(s.ip_address()) == ip_address.as_ref().map(String::as_str) + }); + + if let Some(source) = source { + break source.to_owned(); + } + } + + if timer.elapsed().as_millis() >= connect_timeout as u128 { + return Err(Some(gst_error_msg!( + gst::ResourceError::NotFound, + ["Stream not found"] + ))); + } + }; + + gst_debug!( + cat, + obj: element, + "Connecting to NDI source with ndi-name '{}' and ip-address '{}'", + source.ndi_name(), + source.ip_address(), + ); + + // FIXME: Ideally we would use NDIlib_recv_color_format_fastest here but that seems to be + // broken with interlaced content currently + let recv = RecvInstance::builder(&source, &receiver_ndi_name) + .bandwidth(bandwidth) + .color_format(NDIlib_recv_color_format_e::NDIlib_recv_color_format_UYVY_BGRA) + .allow_video_fields(true) + .build(); + let recv = match recv { + None => { + return Err(Some(gst_error_msg!( + gst::CoreError::Negotiation, + ["Cannot run NDI: NDIlib_recv_create_v3 error"] + ))); + } + Some(recv) => recv, + }; + + recv.set_tally(&Tally::default()); + + let enable_hw_accel = MetadataFrame::new(0, Some("")); + recv.send_metadata(&enable_hw_accel); + + let mut receivers = HASHMAP_RECEIVERS.lock().unwrap(); + let info = match receivers.get_mut(&id_receiver) { + None => return Err(None), + Some(val) => val, + }; + + let (audio, video) = match info { + ReceiverInfo::Connecting { + ref audio, + ref video, + .. + } => (audio.clone(), video.clone()), + ReceiverInfo::Connected { .. } => unreachable!(), + }; + + assert!(audio.is_some() || video.is_some()); + + *info = ReceiverInfo::Connected { + id: id_receiver, + ndi_name: source.ndi_name().to_owned(), + ip_address: source.ip_address().to_owned(), + recv: recv.clone(), + video: video.clone(), + audio: audio.clone(), + }; + + gst_debug!(cat, obj: element, "Started NDI connection"); + + if let Some(audio) = audio.and_then(|v| v.upgrade()).map(Receiver) { + let mut audio_recv = audio.0.recv.lock().unwrap(); + assert!(audio_recv.is_none()); + *audio_recv = Some(recv.clone()); + audio.0.recv_cond.notify_one(); + } + + if let Some(video) = video.and_then(|v| v.upgrade()).map(Receiver) { + let mut video_recv = video.0.recv.lock().unwrap(); + assert!(video_recv.is_none()); + *video_recv = Some(recv.clone()); + video.0.recv_cond.notify_one(); + } + + Ok(()) +} + +fn receive_thread(receiver: &Weak) { + // First loop until we actually are connected, or an error happened + let recv = { + let receiver = match receiver.upgrade().map(Receiver) { + None => return, + Some(receiver) => receiver, + }; + + let element = receiver.0.element.upgrade().unwrap(); + + let mut recv = receiver.0.recv.lock().unwrap(); + loop { + { + let queue = (receiver.0.queue.0).0.lock().unwrap(); + if !queue.capturing { + gst_debug!(receiver.0.cat, obj: &element, "Shutting down"); + return; + } + + // If an error happened in the meantime, just go out of here + if let Some(_) = queue.error { + gst_error!( + receiver.0.cat, + obj: &element, + "Error while waiting for connection" + ); + return; + } + } + + if let Some(ref recv) = *recv { + break recv.clone(); + } + + recv = receiver.0.recv_cond.wait(recv).unwrap(); + } + }; + + // Now first capture frames until the queues are empty so that we're sure that we output only + // the very latest frame that is available now + loop { + let receiver = match receiver.upgrade().map(Receiver) { + None => return, + Some(receiver) => receiver, + }; + + let element = receiver.0.element.upgrade().unwrap(); + + { + let queue = (receiver.0.queue.0).0.lock().unwrap(); + if !queue.capturing { + gst_debug!(receiver.0.cat, obj: &element, "Shutting down"); + return; + } + + // If an error happened in the meantime, just go out of here + if let Some(_) = queue.error { + gst_error!( + receiver.0.cat, + obj: &element, + "Error while waiting for connection" + ); + return; + } + } + + let queue = recv.get_queue(); + if (!receiver.0.video && queue.audio_frames() <= 1) + || (receiver.0.video && queue.video_frames() <= 1) + { + break; + } + + let _ = recv.capture(receiver.0.video, !receiver.0.video, false, 0); + } + + // And if that went fine, capture until we're done + loop { + let receiver = match receiver.upgrade().map(Receiver) { + None => break, + Some(receiver) => receiver, + }; + + let element = receiver.0.element.upgrade().unwrap(); + + { + let queue = (receiver.0.queue.0).0.lock().unwrap(); + if !queue.capturing { + gst_debug!(receiver.0.cat, obj: &element, "Shutting down"); + break; + } + } + + let res = if receiver.0.video { + receiver + .capture_video(&element, &recv) + .map(|(buffer, info)| ReceiverItem::VideoBuffer(buffer, info)) + } else { + receiver + .capture_audio(&element, &recv) + .map(|(buffer, info)| ReceiverItem::AudioBuffer(buffer, info)) + }; + + match res { + Ok(item) => { + let mut queue = (receiver.0.queue.0).0.lock().unwrap(); + while queue.buffer_queue.len() > 5 { + gst_warning!( + receiver.0.cat, + obj: &element, + "Dropping old buffer -- queue has {} items", + queue.buffer_queue.len() + ); + queue.buffer_queue.pop_front(); + } + queue.buffer_queue.push_back(item); + (receiver.0.queue.0).1.notify_one(); + } + Err(gst::FlowError::Eos) => { + gst_debug!(receiver.0.cat, obj: &element, "Signalling EOS"); + let mut queue = (receiver.0.queue.0).0.lock().unwrap(); + queue.timeout = true; + (receiver.0.queue.0).1.notify_one(); + } + Err(gst::FlowError::CustomError) => { + // Flushing, nothing to be done here except for emptying our queue + let mut queue = (receiver.0.queue.0).0.lock().unwrap(); + queue.buffer_queue.clear(); + (receiver.0.queue.0).1.notify_one(); + } + Err(err) => { + gst_error!(receiver.0.cat, obj: &element, "Signalling error"); + let mut queue = (receiver.0.queue.0).0.lock().unwrap(); + if queue.error.is_none() { + queue.error = Some(err); + } + (receiver.0.queue.0).1.notify_one(); + break; + } + } + } +} + +impl Receiver { + fn capture_video( + &self, + element: &gst_base::BaseSrc, + recv: &RecvInstance, + ) -> Result<(gst::Buffer, gst_video::VideoInfo), gst::FlowError> { + let timeout = time::Instant::now(); + let mut flushing; + let mut playing; + + let video_frame = loop { + { + let queue = (self.0.queue.0).0.lock().unwrap(); + playing = queue.playing; + flushing = queue.flushing; + if !queue.capturing { + gst_debug!(self.0.cat, obj: element, "Shutting down"); + return Err(gst::FlowError::Flushing); + } + } + + let res = match recv.capture(true, false, false, 50) { + Err(_) => Err(()), + Ok(None) => Ok(None), + Ok(Some(Frame::Video(frame))) => Ok(Some(frame)), + _ => unreachable!(), + }; + + let video_frame = match res { + Err(_) => { + gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type error received, assuming that the source closed the stream...."]); + return Err(gst::FlowError::Error); + } + Ok(None) if timeout.elapsed().as_millis() >= self.0.timeout as u128 => { + return Err(gst::FlowError::Eos); + } + Ok(None) => { + gst_debug!( + self.0.cat, + obj: element, + "No video frame received yet, retry" + ); + continue; + } + Ok(Some(frame)) => frame, + }; + + break video_frame; + }; + + // Simply read all video frames while flushing but don't copy them or anything to + // make sure that we're not accumulating anything here + if !playing || flushing { + gst_debug!(self.0.cat, obj: element, "Flushing, dropping buffer"); + return Err(gst::FlowError::CustomError); + } + + let pts = self.calculate_video_timestamp(element, &video_frame); + let info = self.create_video_info(element, &video_frame)?; + + let buffer = self.create_video_buffer(element, pts, &info, &video_frame)?; + + gst_log!(self.0.cat, obj: element, "Produced buffer {:?}", buffer); + + Ok((buffer, info)) + } + + fn calculate_video_timestamp( + &self, + element: &gst_base::BaseSrc, + video_frame: &VideoFrame, + ) -> gst::ClockTime { + let clock = element.get_clock().unwrap(); + + // For now take the current running time as PTS. At a later time we + // will want to work with the timestamp given by the NDI SDK if available + let now = clock.get_time(); + let base_time = element.get_base_time(); + let receive_time = now - base_time; + + let real_time_now = gst::ClockTime::from(glib::get_real_time() as u64 * 1000); + let timestamp = if video_frame.timestamp() == ndisys::NDIlib_recv_timestamp_undefined { + gst::CLOCK_TIME_NONE + } else { + gst::ClockTime::from(video_frame.timestamp() as u64 * 100) + }; + let timecode = gst::ClockTime::from(video_frame.timecode() as u64 * 100); + + gst_log!( + self.0.cat, + obj: element, + "NDI video frame received: {:?} with timecode {} and timestamp {}, receive time {}, local time now {}", + video_frame, + timecode, + timestamp, + receive_time, + real_time_now, + ); + + let pts = match self.0.timestamp_mode { + TimestampMode::ReceiveTime => receive_time, + TimestampMode::Timecode => timecode, + TimestampMode::Timestamp if timestamp.is_none() => receive_time, + TimestampMode::Timestamp => { + // Timestamps are relative to the UNIX epoch + if real_time_now > timestamp { + let diff = real_time_now - timestamp; + if diff > receive_time { + 0.into() + } else { + receive_time - diff + } + } else { + let diff = timestamp - real_time_now; + receive_time + diff + } + } + }; + + gst_log!( + self.0.cat, + obj: element, + "Calculated pts for video frame: {:?}", + pts + ); + + pts + } + + fn create_video_info( + &self, + _element: &gst_base::BaseSrc, + video_frame: &VideoFrame, + ) -> Result { + // YV12 and I420 are swapped in the NDI SDK compared to GStreamer + let format = match video_frame.fourcc() { + ndisys::NDIlib_FourCC_type_e::NDIlib_FourCC_type_UYVY => gst_video::VideoFormat::Uyvy, + ndisys::NDIlib_FourCC_type_e::NDIlib_FourCC_type_YV12 => gst_video::VideoFormat::I420, + ndisys::NDIlib_FourCC_type_e::NDIlib_FourCC_type_NV12 => gst_video::VideoFormat::Nv12, + ndisys::NDIlib_FourCC_type_e::NDIlib_FourCC_type_I420 => gst_video::VideoFormat::Yv12, + ndisys::NDIlib_FourCC_type_e::NDIlib_FourCC_type_BGRA => gst_video::VideoFormat::Bgra, + ndisys::NDIlib_FourCC_type_e::NDIlib_FourCC_type_BGRX => gst_video::VideoFormat::Bgrx, + ndisys::NDIlib_FourCC_type_e::NDIlib_FourCC_type_RGBA => gst_video::VideoFormat::Rgba, + ndisys::NDIlib_FourCC_type_e::NDIlib_FourCC_type_RGBX => gst_video::VideoFormat::Rgbx, + ndisys::NDIlib_FourCC_type_e::NDIlib_FourCC_type_UYVA => gst_video::VideoFormat::Uyvy, + }; + + let par = gst::Fraction::approximate_f32(video_frame.picture_aspect_ratio()).unwrap() + * gst::Fraction::new(video_frame.yres(), video_frame.xres()); + + #[cfg(feature = "interlaced-fields")] + { + let mut builder = gst_video::VideoInfo::new( + format, + video_frame.xres() as u32, + video_frame.yres() as u32, + ) + .fps(gst::Fraction::from(video_frame.frame_rate())) + .par(par) + .interlace_mode(match video_frame.frame_format_type() { + ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_progressive => { + gst_video::VideoInterlaceMode::Progressive + } + ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_interleaved => { + gst_video::VideoInterlaceMode::Interleaved + } + _ => gst_video::VideoInterlaceMode::Alternate, + }); + + /* Requires GStreamer 1.12 at least */ + if video_frame.frame_format_type() + == ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_interleaved + { + builder = builder.field_order(gst_video::VideoFieldOrder::TopFieldFirst); + } + + Ok(builder.build().unwrap()) + } + + #[cfg(not(feature = "interlaced-fields"))] + { + if video_frame.frame_format_type() + != ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_progressive + && video_frame.frame_format_type() + != ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_interleaved + { + gst_element_error!( + element, + gst::StreamError::Format, + ["Separate field interlacing not supported"] + ); + return Err(gst::FlowError::NotNegotiated); + } + + let builder = gst_video::VideoInfo::new( + format, + video_frame.xres() as u32, + video_frame.yres() as u32, + ) + .fps(gst::Fraction::from(video_frame.frame_rate())) + .par(par) + .interlace_mode( + if video_frame.frame_format_type() + == ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_progressive + { + gst_video::VideoInterlaceMode::Progressive + } else { + gst_video::VideoInterlaceMode::Interleaved + }, + ); + Ok(builder.build().unwrap()); + } + } + + fn create_video_buffer( + &self, + element: &gst_base::BaseSrc, + pts: gst::ClockTime, + info: &gst_video::VideoInfo, + video_frame: &VideoFrame, + ) -> Result { + let mut buffer = gst::Buffer::with_size(info.size()).unwrap(); + { + let duration = gst::SECOND + .mul_div_floor( + video_frame.frame_rate().1 as u64, + video_frame.frame_rate().0 as u64, + ) + .unwrap_or(gst::CLOCK_TIME_NONE); + let buffer = buffer.get_mut().unwrap(); + buffer.set_pts(pts); + buffer.set_duration(duration); + + #[cfg(feature = "reference-timestamps")] + { + gst::ReferenceTimestampMeta::add( + buffer, + &*TIMECODE_CAPS, + gst::ClockTime::from(video_frame.timecode() as u64 * 100), + gst::CLOCK_TIME_NONE, + ); + if video_frame.timestamp() != ndisys::NDIlib_recv_timestamp_undefined { + gst::ReferenceTimestampMeta::add( + buffer, + &*TIMESTAMP_CAPS, + gst::ClockTime::from(video_frame.timestamp() as u64 * 100), + gst::CLOCK_TIME_NONE, + ); + } + } + + #[cfg(feature = "interlaced-fields")] + { + match video_frame.frame_format_type() { + ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_interleaved => { + buffer.set_video_flags( + gst_video::VideoBufferFlags::INTERLACED + | gst_video::VideoBufferFlags::TFF, + ); + } + ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_field_0 => { + buffer.set_video_flags( + gst_video::VideoBufferFlags::INTERLACED + | gst_video::VideoBufferFlags::TOP_FIELD, + ); + } + ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_field_1 => { + buffer.set_video_flags( + gst_video::VideoBufferFlags::INTERLACED + | gst_video::VideoBufferFlags::BOTTOM_FIELD, + ); + } + _ => (), + }; + } + + #[cfg(not(feature = "interlaced-fields"))] + { + if video_frame.frame_format_type() + == ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_interleaved + { + buffer.set_video_flags( + gst_video::VideoBufferFlags::INTERLACED | gst_video::VideoBufferFlags::TFF, + ); + } + } + } + + self.copy_video_frame(element, info, buffer, video_frame) + } + + fn copy_video_frame( + &self, + _element: &gst_base::BaseSrc, + info: &gst_video::VideoInfo, + buffer: gst::Buffer, + video_frame: &VideoFrame, + ) -> Result { + // FIXME: Error handling if frame dimensions don't match + let mut vframe = gst_video::VideoFrame::from_buffer_writable(buffer, info).unwrap(); + + match info.format() { + gst_video::VideoFormat::Uyvy + | gst_video::VideoFormat::Bgra + | gst_video::VideoFormat::Bgrx + | gst_video::VideoFormat::Rgba + | gst_video::VideoFormat::Rgbx => { + let line_bytes = if info.format() == gst_video::VideoFormat::Uyvy { + 2 * vframe.width() as usize + } else { + 4 * vframe.width() as usize + }; + let dest_stride = vframe.plane_stride()[0] as usize; + let dest = vframe.plane_data_mut(0).unwrap(); + let src_stride = video_frame.line_stride_in_bytes() as usize; + let src = video_frame.data(); + + for (dest, src) in dest + .chunks_exact_mut(dest_stride) + .zip(src.chunks_exact(src_stride)) + { + dest.copy_from_slice(src); + dest.copy_from_slice(&src[..line_bytes]); + } + } + gst_video::VideoFormat::Nv12 => { + // First plane + { + let line_bytes = vframe.width() as usize; + let dest_stride = vframe.plane_stride()[0] as usize; + let dest = vframe.plane_data_mut(0).unwrap(); + let src_stride = video_frame.line_stride_in_bytes() as usize; + let src = video_frame.data(); + + for (dest, src) in dest + .chunks_exact_mut(dest_stride) + .zip(src.chunks_exact(src_stride)) + { + dest.copy_from_slice(&src[..line_bytes]); + } + } + + // Second plane + { + let line_bytes = vframe.width() as usize; + let dest_stride = vframe.plane_stride()[1] as usize; + let dest = vframe.plane_data_mut(1).unwrap(); + let src_stride = video_frame.line_stride_in_bytes() as usize; + let src = &video_frame.data()[(video_frame.yres() as usize * src_stride)..]; + + for (dest, src) in dest + .chunks_exact_mut(dest_stride) + .zip(src.chunks_exact(src_stride)) + { + dest.copy_from_slice(&src[..line_bytes]); + } + } + } + gst_video::VideoFormat::Yv12 | gst_video::VideoFormat::I420 => { + // First plane + { + let line_bytes = vframe.width() as usize; + let dest_stride = vframe.plane_stride()[0] as usize; + let dest = vframe.plane_data_mut(0).unwrap(); + let src_stride = video_frame.line_stride_in_bytes() as usize; + let src = video_frame.data(); + + for (dest, src) in dest + .chunks_exact_mut(dest_stride) + .zip(src.chunks_exact(src_stride)) + { + dest.copy_from_slice(&src[..line_bytes]); + } + } + + // Second plane + { + let line_bytes = (vframe.width() as usize + 1) / 2; + let dest_stride = vframe.plane_stride()[1] as usize; + let dest = vframe.plane_data_mut(1).unwrap(); + let src_stride = video_frame.line_stride_in_bytes() as usize; + let src_stride1 = video_frame.line_stride_in_bytes() as usize / 2; + let src = &video_frame.data()[(video_frame.yres() as usize * src_stride)..]; + + for (dest, src) in dest + .chunks_exact_mut(dest_stride) + .zip(src.chunks_exact(src_stride1)) + { + dest.copy_from_slice(&src[..line_bytes]); + } + } + + // Third plane + { + let line_bytes = (vframe.width() as usize + 1) / 2; + let dest_stride = vframe.plane_stride()[2] as usize; + let dest = vframe.plane_data_mut(2).unwrap(); + let src_stride = video_frame.line_stride_in_bytes() as usize; + let src_stride1 = video_frame.line_stride_in_bytes() as usize / 2; + let src = &video_frame.data()[(video_frame.yres() as usize * src_stride + + (video_frame.yres() as usize + 1) / 2 * src_stride1)..]; + + for (dest, src) in dest + .chunks_exact_mut(dest_stride) + .zip(src.chunks_exact(src_stride1)) + { + dest.copy_from_slice(&src[..line_bytes]); + } + } + } + _ => unreachable!(), + } + + Ok(vframe.into_buffer()) + } + + fn capture_audio( + &self, + element: &gst_base::BaseSrc, + recv: &RecvInstance, + ) -> Result<(gst::Buffer, gst_audio::AudioInfo), gst::FlowError> { + let timeout = time::Instant::now(); + let mut flushing; + let mut playing; + + let audio_frame = loop { + { + let queue = (self.0.queue.0).0.lock().unwrap(); + flushing = queue.flushing; + playing = queue.playing; + if !queue.capturing { + gst_debug!(self.0.cat, obj: element, "Shutting down"); + return Err(gst::FlowError::Flushing); + } + } + + let res = match recv.capture(false, true, false, 50) { + Err(_) => Err(()), + Ok(None) => Ok(None), + Ok(Some(Frame::Audio(frame))) => Ok(Some(frame)), + _ => unreachable!(), + }; + + let audio_frame = match res { + Err(_) => { + gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type error received, assuming that the source closed the stream...."]); + return Err(gst::FlowError::Error); + } + Ok(None) if timeout.elapsed().as_millis() >= self.0.timeout as u128 => { + return Err(gst::FlowError::Eos); + } + Ok(None) => { + gst_debug!( + self.0.cat, + obj: element, + "No audio frame received yet, retry" + ); + continue; + } + Ok(Some(frame)) => frame, + }; + + break audio_frame; + }; + + // Simply read all video frames while flushing but don't copy them or anything to + // make sure that we're not accumulating anything here + if !playing || flushing { + gst_debug!(self.0.cat, obj: element, "Flushing, dropping buffer"); + return Err(gst::FlowError::CustomError); + } + + let pts = self.calculate_audio_timestamp(element, &audio_frame); + let info = self.create_audio_info(element, &audio_frame)?; + + let buffer = self.create_audio_buffer(element, pts, &info, &audio_frame)?; + + gst_log!(self.0.cat, obj: element, "Produced buffer {:?}", buffer); + + Ok((buffer, info)) + } + + fn calculate_audio_timestamp( + &self, + element: &gst_base::BaseSrc, + audio_frame: &AudioFrame, + ) -> gst::ClockTime { + let clock = element.get_clock().unwrap(); + + // For now take the current running time as PTS. At a later time we + // will want to work with the timestamp given by the NDI SDK if available + let now = clock.get_time(); + let base_time = element.get_base_time(); + let receive_time = now - base_time; + + let real_time_now = gst::ClockTime::from(glib::get_real_time() as u64 * 1000); + let timestamp = if audio_frame.timestamp() == ndisys::NDIlib_recv_timestamp_undefined { + gst::CLOCK_TIME_NONE + } else { + gst::ClockTime::from(audio_frame.timestamp() as u64 * 100) + }; + let timecode = gst::ClockTime::from(audio_frame.timecode() as u64 * 100); + + gst_log!( + self.0.cat, + obj: element, + "NDI audio frame received: {:?} with timecode {} and timestamp {}, receive time {}, local time now {}", + audio_frame, + timecode, + timestamp, + receive_time, + real_time_now, + ); + + let pts = match self.0.timestamp_mode { + TimestampMode::ReceiveTime => receive_time, + TimestampMode::Timecode => timecode, + TimestampMode::Timestamp if timestamp.is_none() => receive_time, + TimestampMode::Timestamp => { + // Timestamps are relative to the UNIX epoch + if real_time_now > timestamp { + let diff = real_time_now - timestamp; + if diff > receive_time { + 0.into() + } else { + receive_time - diff + } + } else { + let diff = timestamp - real_time_now; + receive_time + diff + } + } + }; + + gst_log!( + self.0.cat, + obj: element, + "Calculated pts for audio frame: {:?}", + pts + ); + + pts + } + + fn create_audio_info( + &self, + _element: &gst_base::BaseSrc, + audio_frame: &AudioFrame, + ) -> Result { + let builder = gst_audio::AudioInfo::new( + gst_audio::AUDIO_FORMAT_S16, + audio_frame.sample_rate() as u32, + audio_frame.no_channels() as u32, + ); + + Ok(builder.build().unwrap()) + } + + fn create_audio_buffer( + &self, + _element: &gst_base::BaseSrc, + pts: gst::ClockTime, + info: &gst_audio::AudioInfo, + audio_frame: &AudioFrame, + ) -> Result { + // We multiply by 2 because is the size in bytes of an i16 variable + let buff_size = (audio_frame.no_samples() as u32 * info.bpf()) as usize; + let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); + { + let duration = gst::SECOND + .mul_div_floor( + audio_frame.no_samples() as u64, + audio_frame.sample_rate() as u64, + ) + .unwrap_or(gst::CLOCK_TIME_NONE); + let buffer = buffer.get_mut().unwrap(); + + buffer.set_pts(pts); + buffer.set_duration(duration); + + #[cfg(feature = "reference-timestamps")] + { + gst::ReferenceTimestampMeta::add( + buffer, + &*TIMECODE_CAPS, + gst::ClockTime::from(audio_frame.timecode() as u64 * 100), + gst::CLOCK_TIME_NONE, + ); + if audio_frame.timestamp() != ndisys::NDIlib_recv_timestamp_undefined { + gst::ReferenceTimestampMeta::add( + buffer, + &*TIMESTAMP_CAPS, + gst::ClockTime::from(audio_frame.timestamp() as u64 * 100), + gst::CLOCK_TIME_NONE, + ); + } + } + + audio_frame.copy_to_interleaved_16s( + buffer + .map_writable() + .unwrap() + .as_mut_slice_of::() + .unwrap(), + ); + } + + Ok(buffer) + } +} From 33370e42adc715b8d20361198d6f49633cd3c976 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Fri, 19 Jul 2019 00:05:31 +0300 Subject: [PATCH 141/199] Collect observations for the timestamp/receive time mappings and smoothen them This allows keeping audio/video more in sync with how the sender was sending it, while also handling network jitter and clock drift in a reasonable way. --- Cargo.toml | 3 +- src/lib.rs | 1 + src/receiver.rs | 408 ++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 361 insertions(+), 51 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d81e64e2..44b5b993 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,10 +9,11 @@ description = "NewTek NDI Plugin" [dependencies] glib = { version = "0.8.0", features = ["subclassing"] } gobject-sys = "0.9" -gstreamer = { version = "0.14.3", features = ["subclassing"] } +gstreamer = { version = "0.14.3", features = ["subclassing", "v1_12"] } gstreamer-base = { version = "0.14.0", features = ["subclassing"] } gstreamer-audio = "0.14.0" gstreamer-video = "0.14.3" +gstreamer-sys = "0.8" lazy_static = "1.1.0" byte-slice-cast = "0.2.0" diff --git a/src/lib.rs b/src/lib.rs index a1e2d0e3..b3d8190d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ use glib::subclass::prelude::*; extern crate gstreamer as gst; extern crate gstreamer_audio as gst_audio; extern crate gstreamer_base as gst_base; +extern crate gstreamer_sys as gst_sys; extern crate gstreamer_video as gst_video; #[macro_use] diff --git a/src/receiver.rs b/src/receiver.rs index 8981f886..e9b98d7f 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -7,6 +7,7 @@ use gst_video::prelude::*; use byte_slice_cast::AsMutSliceOf; +use std::cmp; use std::collections::VecDeque; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{Arc, Condvar, Mutex, Weak}; @@ -21,6 +22,7 @@ enum ReceiverInfo { ip_address: Option, video: Option>, audio: Option>, + observations: Observations, }, Connected { id: usize, @@ -29,6 +31,7 @@ enum ReceiverInfo { recv: RecvInstance, video: Option>, audio: Option>, + observations: Observations, }, } @@ -63,6 +66,8 @@ struct ReceiverInner { recv: Mutex>, recv_cond: Condvar, + observations: Observations, + cat: gst::DebugCategory, element: glib::WeakRef, timestamp_mode: TimestampMode, @@ -95,6 +100,226 @@ struct ReceiverQueueInner { timeout: bool, } +// 100 frames observations window over which we calculate the timestamp drift +// between sender and receiver. A bigger window allows more smoothing out of +// network effects +const WINDOW_LENGTH: usize = 100; +#[derive(Clone)] +struct Observations(Arc>); +struct ObservationsInner { + // NDI timestamp - GStreamer clock time tuples + values: Vec<(u64, u64)>, + values_tmp: [(u64, u64); WINDOW_LENGTH], + current_mapping: TimeMapping, + next_mapping: TimeMapping, + time_mapping_pending: bool, + + // How many frames we skipped since last observation + // we took + skip_count: usize, + // How many frames we skip in this period. once skip_count + // reaches this, we take another observation + skip_period: usize, + // How many observations are left until we update the skip_period + // again. This is always initialized to WINDOW_LENGTH + skip_period_update_in: usize, +} + +#[derive(Clone, Copy, Debug)] +struct TimeMapping { + xbase: u64, + b: u64, + num: u64, + den: u64, +} + +impl Observations { + fn new() -> Self { + Self(Arc::new(Mutex::new(ObservationsInner { + values: Vec::with_capacity(WINDOW_LENGTH), + values_tmp: [(0, 0); WINDOW_LENGTH], + current_mapping: TimeMapping::default(), + next_mapping: TimeMapping::default(), + time_mapping_pending: false, + skip_count: 0, + skip_period: 1, + skip_period_update_in: WINDOW_LENGTH, + }))) + } + + fn process( + &self, + cat: gst::DebugCategory, + element: &gst_base::BaseSrc, + time: (gst::ClockTime, gst::ClockTime), + duration: gst::ClockTime, + ) -> (gst::ClockTime, gst::ClockTime) { + assert!(time.1.is_some()); + if time.0.is_none() { + return (time.1, duration); + } + + let time = (time.0.unwrap(), time.1.unwrap()); + + let mut inner = self.0.lock().unwrap(); + let ObservationsInner { + ref mut values, + ref mut values_tmp, + ref mut current_mapping, + ref mut next_mapping, + ref mut time_mapping_pending, + ref mut skip_count, + ref mut skip_period, + ref mut skip_period_update_in, + } = *inner; + + if values.is_empty() { + current_mapping.xbase = time.0; + current_mapping.b = time.1; + current_mapping.num = 1; + current_mapping.den = 1; + } + + if *skip_count == 0 { + *skip_count += 1; + if *skip_count >= *skip_period { + *skip_count = 0; + } + *skip_period_update_in -= 1; + if *skip_period_update_in == 0 { + *skip_period_update_in = WINDOW_LENGTH; + + // Start by first updating every frame, then every second frame, then every third + // frame, etc. until we update once every quarter second + let framerate = (gst::SECOND / duration).unwrap_or(25) as usize; + + if *skip_period < framerate / 4 + 1 { + *skip_period += 1; + } else { + *skip_period = framerate / 4 + 1; + } + } + + assert!(values.len() <= WINDOW_LENGTH); + + if values.len() == WINDOW_LENGTH { + values.remove(0); + } + values.push(time); + + if let Some((num, den, b, xbase, r_squared)) = + calculate_linear_regression(values, Some(values_tmp)) + { + next_mapping.xbase = xbase; + next_mapping.b = b; + next_mapping.num = num; + next_mapping.den = den; + *time_mapping_pending = true; + gst_debug!( + cat, + obj: element, + "Calculated new time mapping: GStreamer time = {} * (NDI time - {}) + {} ({})", + next_mapping.num as f64 / next_mapping.den as f64, + gst::ClockTime::from(next_mapping.xbase), + gst::ClockTime::from(next_mapping.b), + r_squared, + ); + } + } else { + *skip_count += 1; + if *skip_count >= *skip_period { + *skip_count = 0; + } + } + + if *time_mapping_pending { + let expected = gst::Clock::adjust_with_calibration( + time.0.into(), + current_mapping.xbase.into(), + current_mapping.b.into(), + current_mapping.num.into(), + current_mapping.den.into(), + ) + .unwrap(); + let new_calculated = gst::Clock::adjust_with_calibration( + time.0.into(), + next_mapping.xbase.into(), + next_mapping.b.into(), + next_mapping.num.into(), + next_mapping.den.into(), + ) + .unwrap(); + + let diff = if new_calculated > expected { + new_calculated - expected + } else { + expected - new_calculated + }; + + // Allow at most 5% frame duration or 2ms difference per frame + let max_diff = cmp::max( + (duration / 10).unwrap_or(2 * gst::MSECOND_VAL), + 2 * gst::MSECOND_VAL, + ); + + if diff > max_diff { + gst_debug!( + cat, + obj: element, + "New time mapping causes difference {} but only {} allowed", + gst::ClockTime::from(diff), + gst::ClockTime::from(max_diff), + ); + + if new_calculated > expected { + current_mapping.b = expected + max_diff; + current_mapping.xbase = time.0; + } else { + current_mapping.b = expected - max_diff; + current_mapping.xbase = time.0; + } + } else { + *current_mapping = *next_mapping; + } + } + + let converted_timestamp = gst::Clock::adjust_with_calibration( + time.0.into(), + current_mapping.xbase.into(), + current_mapping.b.into(), + current_mapping.num.into(), + current_mapping.den.into(), + ); + let converted_duration = duration + .mul_div_floor(current_mapping.num, current_mapping.den) + .unwrap_or(gst::CLOCK_TIME_NONE); + + gst_debug!( + cat, + obj: element, + "Converted timestamp {}/{} to {}, duration {} to {}", + gst::ClockTime::from(time.0), + gst::ClockTime::from(time.1), + converted_timestamp, + duration, + converted_duration, + ); + + (converted_timestamp, converted_duration) + } +} + +impl Default for TimeMapping { + fn default() -> Self { + Self { + xbase: 0, + b: 0, + num: 1, + den: 1, + } + } +} + #[derive(Clone)] pub struct ReceiverControlHandle { queue: ReceiverQueue, @@ -128,29 +353,37 @@ impl Receiver { element: &gst_base::BaseSrc, cat: gst::DebugCategory, ) -> Self { - let (id, storage, recv) = if video { + let (id, storage, recv, observations) = if video { match info { ReceiverInfo::Connecting { - id, ref mut video, .. - } => (*id, video, None), + id, + ref mut video, + ref observations, + .. + } => (*id, video, None, observations), ReceiverInfo::Connected { id, ref mut video, ref mut recv, + ref observations, .. - } => (*id, video, Some(recv.clone())), + } => (*id, video, Some(recv.clone()), observations), } } else { match info { ReceiverInfo::Connecting { - id, ref mut audio, .. - } => (*id, audio, None), + id, + ref mut audio, + ref observations, + .. + } => (*id, audio, None, observations), ReceiverInfo::Connected { id, ref mut audio, ref mut recv, + ref observations, .. - } => (*id, audio, Some(recv.clone())), + } => (*id, audio, Some(recv.clone()), observations), } }; assert!(storage.is_none()); @@ -171,6 +404,7 @@ impl Receiver { video, recv: Mutex::new(recv), recv_cond: Condvar::new(), + observations: observations.clone(), cat, element: element.downgrade(), timestamp_mode, @@ -377,6 +611,7 @@ pub fn connect_ndi( ip_address: ip_address.map(String::from), video: None, audio: None, + observations: Observations::new(), }; let receiver = Receiver::new(&mut info, video, timestamp_mode, timeout, element, cat); @@ -564,12 +799,13 @@ fn connect_ndi_async( Some(val) => val, }; - let (audio, video) = match info { + let (audio, video, observations) = match info { ReceiverInfo::Connecting { ref audio, ref video, + ref observations, .. - } => (audio.clone(), video.clone()), + } => (audio.clone(), video.clone(), observations), ReceiverInfo::Connected { .. } => unreachable!(), }; @@ -582,6 +818,7 @@ fn connect_ndi_async( recv: recv.clone(), video: video.clone(), audio: audio.clone(), + observations: observations.clone(), }; gst_debug!(cat, obj: element, "Started NDI connection"); @@ -796,6 +1033,8 @@ impl Receiver { break video_frame; }; + let (pts, duration) = self.calculate_video_timestamp(element, &video_frame); + // Simply read all video frames while flushing but don't copy them or anything to // make sure that we're not accumulating anything here if !playing || flushing { @@ -803,10 +1042,9 @@ impl Receiver { return Err(gst::FlowError::CustomError); } - let pts = self.calculate_video_timestamp(element, &video_frame); let info = self.create_video_info(element, &video_frame)?; - let buffer = self.create_video_buffer(element, pts, &info, &video_frame)?; + let buffer = self.create_video_buffer(element, pts, duration, &info, &video_frame)?; gst_log!(self.0.cat, obj: element, "Produced buffer {:?}", buffer); @@ -817,7 +1055,7 @@ impl Receiver { &self, element: &gst_base::BaseSrc, video_frame: &VideoFrame, - ) -> gst::ClockTime { + ) -> (gst::ClockTime, gst::ClockTime) { let clock = element.get_clock().unwrap(); // For now take the current running time as PTS. At a later time we @@ -834,33 +1072,46 @@ impl Receiver { }; let timecode = gst::ClockTime::from(video_frame.timecode() as u64 * 100); + let duration = gst::SECOND + .mul_div_floor( + video_frame.frame_rate().1 as u64, + video_frame.frame_rate().0 as u64, + ) + .unwrap_or(gst::CLOCK_TIME_NONE); + gst_log!( self.0.cat, obj: element, - "NDI video frame received: {:?} with timecode {} and timestamp {}, receive time {}, local time now {}", + "NDI video frame received: {:?} with timecode {}, timestamp {}, duration {}, receive time {}, local time now {}", video_frame, timecode, timestamp, + duration, receive_time, real_time_now, ); - let pts = match self.0.timestamp_mode { - TimestampMode::ReceiveTime => receive_time, - TimestampMode::Timecode => timecode, - TimestampMode::Timestamp if timestamp.is_none() => receive_time, + let (pts, duration) = match self.0.timestamp_mode { + TimestampMode::ReceiveTime => self.0.observations.process( + self.0.cat, + element, + (timestamp, receive_time), + duration, + ), + TimestampMode::Timecode => (timecode, duration), + TimestampMode::Timestamp if timestamp.is_none() => (receive_time, duration), TimestampMode::Timestamp => { // Timestamps are relative to the UNIX epoch if real_time_now > timestamp { let diff = real_time_now - timestamp; if diff > receive_time { - 0.into() + (0.into(), duration) } else { - receive_time - diff + (receive_time - diff, duration) } } else { let diff = timestamp - real_time_now; - receive_time + diff + (receive_time + diff, duration) } } }; @@ -868,11 +1119,12 @@ impl Receiver { gst_log!( self.0.cat, obj: element, - "Calculated pts for video frame: {:?}", - pts + "Calculated PTS for video frame {}, duration {}", + pts, + duration, ); - pts + (pts, duration) } fn create_video_info( @@ -915,7 +1167,6 @@ impl Receiver { _ => gst_video::VideoInterlaceMode::Alternate, }); - /* Requires GStreamer 1.12 at least */ if video_frame.frame_format_type() == ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_interleaved { @@ -956,6 +1207,13 @@ impl Receiver { gst_video::VideoInterlaceMode::Interleaved }, ); + + if video_frame.frame_format_type() + == ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_interleaved + { + builder = builder.field_order(gst_video::VideoFieldOrder::TopFieldFirst); + } + Ok(builder.build().unwrap()); } } @@ -964,17 +1222,12 @@ impl Receiver { &self, element: &gst_base::BaseSrc, pts: gst::ClockTime, + duration: gst::ClockTime, info: &gst_video::VideoInfo, video_frame: &VideoFrame, ) -> Result { let mut buffer = gst::Buffer::with_size(info.size()).unwrap(); { - let duration = gst::SECOND - .mul_div_floor( - video_frame.frame_rate().1 as u64, - video_frame.frame_rate().0 as u64, - ) - .unwrap_or(gst::CLOCK_TIME_NONE); let buffer = buffer.get_mut().unwrap(); buffer.set_pts(pts); buffer.set_duration(duration); @@ -1211,6 +1464,8 @@ impl Receiver { break audio_frame; }; + let (pts, duration) = self.calculate_audio_timestamp(element, &audio_frame); + // Simply read all video frames while flushing but don't copy them or anything to // make sure that we're not accumulating anything here if !playing || flushing { @@ -1218,10 +1473,9 @@ impl Receiver { return Err(gst::FlowError::CustomError); } - let pts = self.calculate_audio_timestamp(element, &audio_frame); let info = self.create_audio_info(element, &audio_frame)?; - let buffer = self.create_audio_buffer(element, pts, &info, &audio_frame)?; + let buffer = self.create_audio_buffer(element, pts, duration, &info, &audio_frame)?; gst_log!(self.0.cat, obj: element, "Produced buffer {:?}", buffer); @@ -1232,7 +1486,7 @@ impl Receiver { &self, element: &gst_base::BaseSrc, audio_frame: &AudioFrame, - ) -> gst::ClockTime { + ) -> (gst::ClockTime, gst::ClockTime) { let clock = element.get_clock().unwrap(); // For now take the current running time as PTS. At a later time we @@ -1249,33 +1503,46 @@ impl Receiver { }; let timecode = gst::ClockTime::from(audio_frame.timecode() as u64 * 100); + let duration = gst::SECOND + .mul_div_floor( + audio_frame.no_samples() as u64, + audio_frame.sample_rate() as u64, + ) + .unwrap_or(gst::CLOCK_TIME_NONE); + gst_log!( self.0.cat, obj: element, - "NDI audio frame received: {:?} with timecode {} and timestamp {}, receive time {}, local time now {}", + "NDI audio frame received: {:?} with timecode {}, timestamp {}, duration {}, receive time {}, local time now {}", audio_frame, timecode, timestamp, + duration, receive_time, real_time_now, ); - let pts = match self.0.timestamp_mode { - TimestampMode::ReceiveTime => receive_time, - TimestampMode::Timecode => timecode, - TimestampMode::Timestamp if timestamp.is_none() => receive_time, + let (pts, duration) = match self.0.timestamp_mode { + TimestampMode::ReceiveTime => self.0.observations.process( + self.0.cat, + element, + (timestamp, receive_time), + duration, + ), + TimestampMode::Timecode => (timecode, duration), + TimestampMode::Timestamp if timestamp.is_none() => (receive_time, duration), TimestampMode::Timestamp => { // Timestamps are relative to the UNIX epoch if real_time_now > timestamp { let diff = real_time_now - timestamp; if diff > receive_time { - 0.into() + (0.into(), duration) } else { - receive_time - diff + (receive_time - diff, duration) } } else { let diff = timestamp - real_time_now; - receive_time + diff + (receive_time + diff, duration) } } }; @@ -1283,11 +1550,12 @@ impl Receiver { gst_log!( self.0.cat, obj: element, - "Calculated pts for audio frame: {:?}", - pts + "Calculated PTS for audio frame {}, duration {}", + pts, + duration, ); - pts + (pts, duration) } fn create_audio_info( @@ -1308,6 +1576,7 @@ impl Receiver { &self, _element: &gst_base::BaseSrc, pts: gst::ClockTime, + duration: gst::ClockTime, info: &gst_audio::AudioInfo, audio_frame: &AudioFrame, ) -> Result { @@ -1315,12 +1584,6 @@ impl Receiver { let buff_size = (audio_frame.no_samples() as u32 * info.bpf()) as usize; let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); { - let duration = gst::SECOND - .mul_div_floor( - audio_frame.no_samples() as u64, - audio_frame.sample_rate() as u64, - ) - .unwrap_or(gst::CLOCK_TIME_NONE); let buffer = buffer.get_mut().unwrap(); buffer.set_pts(pts); @@ -1356,3 +1619,48 @@ impl Receiver { Ok(buffer) } } + +// FIXME: Requires https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/merge_requests/307 +pub fn calculate_linear_regression( + xy: &[(u64, u64)], + temp: Option<&mut [(u64, u64)]>, +) -> Option<(u64, u64, u64, u64, f64)> { + unsafe { + use glib::translate::from_glib; + use std::mem; + use std::ptr; + + assert_eq!(mem::size_of::() * 2, mem::size_of::<(u64, u64)>()); + assert_eq!(mem::align_of::(), mem::align_of::<(u64, u64)>()); + assert!(temp.as_ref().map(|temp| temp.len()).unwrap_or(xy.len()) >= xy.len()); + + let mut m_num = mem::MaybeUninit::uninit(); + let mut m_denom = mem::MaybeUninit::uninit(); + let mut b = mem::MaybeUninit::uninit(); + let mut xbase = mem::MaybeUninit::uninit(); + let mut r_squared = mem::MaybeUninit::uninit(); + + let res = from_glib(gst_sys::gst_calculate_linear_regression( + xy.as_ptr() as *const u64, + temp.map(|temp| temp.as_mut_ptr() as *mut u64) + .unwrap_or(ptr::null_mut()), + xy.len() as u32, + m_num.as_mut_ptr(), + m_denom.as_mut_ptr(), + b.as_mut_ptr(), + xbase.as_mut_ptr(), + r_squared.as_mut_ptr(), + )); + if res { + Some(( + m_num.assume_init(), + m_denom.assume_init(), + b.assume_init(), + xbase.assume_init(), + r_squared.assume_init(), + )) + } else { + None + } + } +} From fa9f788190f562a4fcaa15393b994e58cfa97df3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Fri, 19 Jul 2019 11:32:04 +0300 Subject: [PATCH 142/199] Improve type-safety a bit by making the Receiver generic over the stream type --- src/lib.rs | 1 - src/ndiaudiosrc.rs | 8 +- src/ndivideosrc.rs | 8 +- src/receiver.rs | 246 ++++++++++++++++++++++++++++----------------- 4 files changed, 160 insertions(+), 103 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index b3d8190d..596496eb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,6 @@ #[macro_use] extern crate glib; use glib::prelude::*; -use glib::subclass::prelude::*; #[macro_use] extern crate gstreamer as gst; extern crate gstreamer_audio as gst_audio; diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index ebcf6dae..e02871e5 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -15,6 +15,7 @@ use std::{i32, u32}; use connect_ndi; use ndisys; +use AudioReceiver; use Receiver; use ReceiverControlHandle; use ReceiverItem; @@ -121,7 +122,7 @@ static PROPERTIES: [subclass::Property; 7] = [ struct State { info: Option, - receiver: Option, + receiver: Option>, current_latency: gst::ClockTime, } @@ -139,7 +140,7 @@ pub(crate) struct NdiAudioSrc { cat: gst::DebugCategory, settings: Mutex, state: Mutex, - receiver_controller: Mutex>, + receiver_controller: Mutex>>, } impl ObjectSubclass for NdiAudioSrc { @@ -503,7 +504,7 @@ impl BaseSrcImpl for NdiAudioSrc { }; match recv.capture() { - ReceiverItem::AudioBuffer(buffer, info) => { + ReceiverItem::Buffer(buffer, info) => { let mut state = self.state.lock().unwrap(); state.receiver = Some(recv); if state.info.as_ref() != Some(&info) { @@ -525,7 +526,6 @@ impl BaseSrcImpl for NdiAudioSrc { ReceiverItem::Flushing => Err(gst::FlowError::Flushing), ReceiverItem::Timeout => Err(gst::FlowError::Eos), ReceiverItem::Error(err) => Err(err), - ReceiverItem::VideoBuffer(..) => unreachable!(), } } } diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index 0ca21f1b..12539132 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -21,6 +21,7 @@ use Receiver; use ReceiverControlHandle; use ReceiverItem; use TimestampMode; +use VideoReceiver; use DEFAULT_RECEIVER_NDI_NAME; #[derive(Debug, Clone)] @@ -124,7 +125,7 @@ static PROPERTIES: [subclass::Property; 7] = [ struct State { info: Option, current_latency: gst::ClockTime, - receiver: Option, + receiver: Option>, } impl Default for State { @@ -141,7 +142,7 @@ pub(crate) struct NdiVideoSrc { cat: gst::DebugCategory, settings: Mutex, state: Mutex, - receiver_controller: Mutex>, + receiver_controller: Mutex>>, } impl ObjectSubclass for NdiVideoSrc { @@ -543,7 +544,7 @@ impl BaseSrcImpl for NdiVideoSrc { }; match recv.capture() { - ReceiverItem::VideoBuffer(buffer, info) => { + ReceiverItem::Buffer(buffer, info) => { let mut state = self.state.lock().unwrap(); state.receiver = Some(recv); if state.info.as_ref() != Some(&info) { @@ -565,7 +566,6 @@ impl BaseSrcImpl for NdiVideoSrc { ReceiverItem::Timeout => Err(gst::FlowError::Eos), ReceiverItem::Flushing => Err(gst::FlowError::Flushing), ReceiverItem::Error(err) => Err(err), - ReceiverItem::AudioBuffer(..) => unreachable!(), } } } diff --git a/src/receiver.rs b/src/receiver.rs index e9b98d7f..0c7db16d 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -20,8 +20,8 @@ enum ReceiverInfo { id: usize, ndi_name: Option, ip_address: Option, - video: Option>, - audio: Option>, + video: Option>>, + audio: Option>>, observations: Observations, }, Connected { @@ -29,8 +29,8 @@ enum ReceiverInfo { ndi_name: String, ip_address: String, recv: RecvInstance, - video: Option>, - audio: Option>, + video: Option>>, + audio: Option>>, observations: Observations, }, } @@ -44,24 +44,44 @@ lazy_static! { static ID_RECEIVER: AtomicUsize = AtomicUsize::new(0); -#[derive(Clone)] -pub struct Receiver(Arc); +pub trait ReceiverType: 'static { + type InfoType: Send + 'static; + const IS_VIDEO: bool; +} + +pub enum AudioReceiver {} +pub enum VideoReceiver {} + +impl ReceiverType for AudioReceiver { + type InfoType = gst_audio::AudioInfo; + const IS_VIDEO: bool = false; +} + +impl ReceiverType for VideoReceiver { + type InfoType = gst_video::VideoInfo; + const IS_VIDEO: bool = true; +} + +pub struct Receiver(Arc>); + +impl Clone for Receiver { + fn clone(&self) -> Self { + Receiver(self.0.clone()) + } +} #[derive(Debug)] -pub enum ReceiverItem { - AudioBuffer(gst::Buffer, gst_audio::AudioInfo), - VideoBuffer(gst::Buffer, gst_video::VideoInfo), +pub enum ReceiverItem { + Buffer(gst::Buffer, T::InfoType), Flushing, Timeout, Error(gst::FlowError), } -struct ReceiverInner { +pub struct ReceiverInner { id: usize, - queue: ReceiverQueue, - - video: bool, + queue: ReceiverQueue, recv: Mutex>, recv_cond: Condvar, @@ -76,10 +96,15 @@ struct ReceiverInner { thread: Mutex>>, } -#[derive(Clone)] -struct ReceiverQueue(Arc<(Mutex, Condvar)>); +struct ReceiverQueue(Arc<(Mutex>, Condvar)>); -struct ReceiverQueueInner { +impl Clone for ReceiverQueue { + fn clone(&self) -> Self { + ReceiverQueue(self.0.clone()) + } +} + +struct ReceiverQueueInner { // If we should be capturing at all or go out of our capture loop // // This is true as long as the source element is in Paused/Playing @@ -94,7 +119,7 @@ struct ReceiverQueueInner { // Queue containing our buffers. This holds at most 5 buffers at a time. // // On timeout/error will contain a single item and then never be filled again - buffer_queue: VecDeque, + buffer_queue: VecDeque<(gst::Buffer, T::InfoType)>, error: Option, timeout: bool, @@ -320,12 +345,19 @@ impl Default for TimeMapping { } } -#[derive(Clone)] -pub struct ReceiverControlHandle { - queue: ReceiverQueue, +pub struct ReceiverControlHandle { + queue: ReceiverQueue, } -impl ReceiverControlHandle { +impl Clone for ReceiverControlHandle { + fn clone(&self) -> Self { + ReceiverControlHandle { + queue: self.queue.clone(), + } + } +} + +impl ReceiverControlHandle { pub fn set_flushing(&self, flushing: bool) { let mut queue = (self.queue.0).0.lock().unwrap(); queue.flushing = flushing; @@ -344,49 +376,34 @@ impl ReceiverControlHandle { } } -impl Receiver { +impl Receiver { fn new( info: &mut ReceiverInfo, - video: bool, timestamp_mode: TimestampMode, timeout: u32, element: &gst_base::BaseSrc, cat: gst::DebugCategory, - ) -> Self { - let (id, storage, recv, observations) = if video { - match info { - ReceiverInfo::Connecting { - id, - ref mut video, - ref observations, - .. - } => (*id, video, None, observations), - ReceiverInfo::Connected { - id, - ref mut video, - ref mut recv, - ref observations, - .. - } => (*id, video, Some(recv.clone()), observations), - } - } else { - match info { - ReceiverInfo::Connecting { - id, - ref mut audio, - ref observations, - .. - } => (*id, audio, None, observations), - ReceiverInfo::Connected { - id, - ref mut audio, - ref mut recv, - ref observations, - .. - } => (*id, audio, Some(recv.clone()), observations), - } + ) -> Self + where + Receiver: ReceiverCapture, + { + let (id, storage_video, storage_audio, recv, observations) = match info { + ReceiverInfo::Connecting { + id, + ref observations, + ref mut audio, + ref mut video, + .. + } => (*id, video, audio, None, observations), + ReceiverInfo::Connected { + id, + ref mut recv, + ref observations, + ref mut audio, + ref mut video, + .. + } => (*id, video, audio, Some(recv.clone()), observations), }; - assert!(storage.is_none()); let receiver = Receiver(Arc::new(ReceiverInner { id, @@ -401,7 +418,6 @@ impl Receiver { }), Condvar::new(), ))), - video, recv: Mutex::new(recv), recv_cond: Condvar::new(), observations: observations.clone(), @@ -439,14 +455,14 @@ impl Receiver { }); let weak = Arc::downgrade(&receiver.0); - *storage = Some(weak); + Self::store_internal(storage_video, storage_audio, weak); *receiver.0.thread.lock().unwrap() = Some(thread); receiver } - pub fn receiver_control_handle(&self) -> ReceiverControlHandle { + pub fn receiver_control_handle(&self) -> ReceiverControlHandle { ReceiverControlHandle { queue: self.0.queue.clone(), } @@ -469,7 +485,7 @@ impl Receiver { (self.0.queue.0).1.notify_all(); } - pub fn capture(&self) -> ReceiverItem { + pub fn capture(&self) -> ReceiverItem { let mut queue = (self.0.queue.0).0.lock().unwrap(); loop { if let Some(err) = queue.error { @@ -478,8 +494,8 @@ impl Receiver { return ReceiverItem::Timeout; } else if queue.flushing || !queue.capturing { return ReceiverItem::Flushing; - } else if let Some(item) = queue.buffer_queue.pop_front() { - return item; + } else if let Some((buffer, info)) = queue.buffer_queue.pop_front() { + return ReceiverItem::Buffer(buffer, info); } queue = (self.0.queue.0).1.wait(queue).unwrap(); @@ -487,7 +503,7 @@ impl Receiver { } } -impl Drop for ReceiverInner { +impl Drop for ReceiverInner { fn drop(&mut self) { // Will shut down the receiver thread on the next iteration let mut queue = (self.queue.0).0.lock().unwrap(); @@ -516,7 +532,7 @@ impl Drop for ReceiverInner { } => (audio, video), }; if video.is_some() && audio.is_some() { - if self.video { + if T::IS_VIDEO { *video = None; } else { *audio = None; @@ -532,7 +548,7 @@ impl Drop for ReceiverInner { } } -pub fn connect_ndi( +pub fn connect_ndi( cat: gst::DebugCategory, element: &gst_base::BaseSrc, ip_address: Option<&str>, @@ -542,13 +558,14 @@ pub fn connect_ndi( bandwidth: NDIlib_recv_bandwidth_e, timestamp_mode: TimestampMode, timeout: u32, -) -> Option { +) -> Option> +where + Receiver: ReceiverCapture, +{ gst_debug!(cat, obj: element, "Starting NDI connection..."); let mut receivers = HASHMAP_RECEIVERS.lock().unwrap(); - let video = element.get_type() == ndivideosrc::NdiVideoSrc::get_type(); - // Check if we already have a receiver for this very stream for val in receivers.values_mut() { let (val_audio, val_video, val_ip_address, val_ndi_name) = match val { @@ -579,26 +596,19 @@ pub fn connect_ndi( }; if val_ip_address == ip_address || val_ndi_name == ndi_name { - if (val_video.is_some() || !video) && (val_audio.is_some() || video) { + if (val_video.is_some() || !T::IS_VIDEO) && (val_audio.is_some() || T::IS_VIDEO) { gst_error!( cat, obj: element, "Source with ndi-name '{:?}' and ip-address '{:?}' already in use for {}", val_ndi_name, val_ip_address, - if video { "video" } else { "audio" }, + if T::IS_VIDEO { "video" } else { "audio" }, ); return None; } else { - return Some(Receiver::new( - val, - video, - timestamp_mode, - timeout, - element, - cat, - )); + return Some(Receiver::new(val, timestamp_mode, timeout, element, cat)); } } } @@ -614,7 +624,7 @@ pub fn connect_ndi( observations: Observations::new(), }; - let receiver = Receiver::new(&mut info, video, timestamp_mode, timeout, element, cat); + let receiver = Receiver::new(&mut info, timestamp_mode, timeout, element, cat); receivers.insert(id_receiver, info); @@ -840,7 +850,10 @@ fn connect_ndi_async( Ok(()) } -fn receive_thread(receiver: &Weak) { +fn receive_thread(receiver: &Weak>) +where + Receiver: ReceiverCapture, +{ // First loop until we actually are connected, or an error happened let recv = { let receiver = match receiver.upgrade().map(Receiver) { @@ -907,13 +920,12 @@ fn receive_thread(receiver: &Weak) { } let queue = recv.get_queue(); - if (!receiver.0.video && queue.audio_frames() <= 1) - || (receiver.0.video && queue.video_frames() <= 1) + if (!T::IS_VIDEO && queue.audio_frames() <= 1) || (T::IS_VIDEO && queue.video_frames() <= 1) { break; } - let _ = recv.capture(receiver.0.video, !receiver.0.video, false, 0); + let _ = recv.capture(T::IS_VIDEO, !T::IS_VIDEO, false, 0); } // And if that went fine, capture until we're done @@ -933,15 +945,7 @@ fn receive_thread(receiver: &Weak) { } } - let res = if receiver.0.video { - receiver - .capture_video(&element, &recv) - .map(|(buffer, info)| ReceiverItem::VideoBuffer(buffer, info)) - } else { - receiver - .capture_audio(&element, &recv) - .map(|(buffer, info)| ReceiverItem::AudioBuffer(buffer, info)) - }; + let res = receiver.capture_internal(&element, &recv); match res { Ok(item) => { @@ -983,7 +987,59 @@ fn receive_thread(receiver: &Weak) { } } -impl Receiver { +pub trait ReceiverCapture { + fn capture_internal( + &self, + element: &gst_base::BaseSrc, + recv: &RecvInstance, + ) -> Result<(gst::Buffer, T::InfoType), gst::FlowError>; + + fn store_internal( + storage_video: &mut Option>>, + storage_audio: &mut Option>>, + weak: Weak>, + ); +} + +impl ReceiverCapture for Receiver { + fn capture_internal( + &self, + element: &gst_base::BaseSrc, + recv: &RecvInstance, + ) -> Result<(gst::Buffer, gst_video::VideoInfo), gst::FlowError> { + self.capture_video(element, recv) + } + + fn store_internal( + storage_video: &mut Option>>, + _storage_audio: &mut Option>>, + weak: Weak>, + ) { + assert!(storage_video.is_none()); + *storage_video = Some(weak); + } +} + +impl ReceiverCapture for Receiver { + fn capture_internal( + &self, + element: &gst_base::BaseSrc, + recv: &RecvInstance, + ) -> Result<(gst::Buffer, gst_audio::AudioInfo), gst::FlowError> { + self.capture_audio(element, recv) + } + + fn store_internal( + _storage_video: &mut Option>>, + storage_audio: &mut Option>>, + weak: Weak>, + ) { + assert!(storage_audio.is_none()); + *storage_audio = Some(weak); + } +} + +impl Receiver { fn capture_video( &self, element: &gst_base::BaseSrc, @@ -1414,7 +1470,9 @@ impl Receiver { Ok(vframe.into_buffer()) } +} +impl Receiver { fn capture_audio( &self, element: &gst_base::BaseSrc, From 5e5007091e8a87fa72b33107135e9c0edc5f6c4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Fri, 19 Jul 2019 11:37:33 +0300 Subject: [PATCH 143/199] Always return 1 frame of latency except for timecode mode This allows for some slack when receiving, as up to 1 frame difference between receive times can easily happen because of capturing alone. And return 5 frames maximum latency as that is the size of our internal queue. --- src/ndiaudiosrc.rs | 14 +++++++++++--- src/ndivideosrc.rs | 14 +++++++++++--- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index e02871e5..0ea2c780 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -457,14 +457,22 @@ impl BaseSrcImpl for NdiAudioSrc { let settings = self.settings.lock().unwrap(); if state.current_latency.is_some() { - let latency = if settings.timestamp_mode == TimestampMode::Timestamp { + let min = if settings.timestamp_mode != TimestampMode::Timecode { state.current_latency } else { 0.into() }; - gst_debug!(self.cat, obj: element, "Returning latency {}", latency); - q.set(true, latency, gst::CLOCK_TIME_NONE); + let max = 5 * state.current_latency; + + gst_debug!( + self.cat, + obj: element, + "Returning latency min {} max {}", + min, + max + ); + q.set(true, min, max); true } else { false diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index 12539132..dc593d9e 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -493,14 +493,22 @@ impl BaseSrcImpl for NdiVideoSrc { let settings = self.settings.lock().unwrap(); if state.current_latency.is_some() { - let latency = if settings.timestamp_mode == TimestampMode::Timestamp { + let min = if settings.timestamp_mode != TimestampMode::Timecode { state.current_latency } else { 0.into() }; - gst_debug!(self.cat, obj: element, "Returning latency {}", latency); - q.set(true, latency, gst::CLOCK_TIME_NONE); + let max = 5 * state.current_latency; + + gst_debug!( + self.cat, + obj: element, + "Returning latency min {} max {}", + min, + max + ); + q.set(true, min, max); true } else { false From d352a0c20d836a0e670af165bc464422618841a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Fri, 19 Jul 2019 12:15:08 +0300 Subject: [PATCH 144/199] Print all found sources in the debug log --- src/receiver.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/receiver.rs b/src/receiver.rs index 0c7db16d..9c9016bb 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -724,7 +724,7 @@ fn connect_ndi_async( let timer = time::Instant::now(); let source = loop { - find.wait_for_sources(100); + let new_sources = find.wait_for_sources(100); let sources = find.get_current_sources(); gst_debug!( @@ -734,6 +734,18 @@ fn connect_ndi_async( sources.len(), ); + if new_sources { + for source in &sources { + gst_debug!( + cat, + obj: element, + "Found source '{}' with IP {}", + source.ndi_name(), + source.ip_address(), + ); + } + } + { let receivers = HASHMAP_RECEIVERS.lock().unwrap(); let info = match receivers.get(&id_receiver) { From 98c290602c5c0d7e2da5035df9e1a827ceab1acc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Fri, 19 Jul 2019 12:51:06 +0300 Subject: [PATCH 145/199] Improve error reporting/handling And minor refactoring. --- src/ndi.rs | 2 +- src/ndiaudiosrc.rs | 20 ++- src/ndivideosrc.rs | 20 ++- src/receiver.rs | 367 ++++++++++++++++++++++++--------------------- 4 files changed, 228 insertions(+), 181 deletions(-) diff --git a/src/ndi.rs b/src/ndi.rs index d99855b2..7ad3b0e9 100644 --- a/src/ndi.rs +++ b/src/ndi.rs @@ -96,7 +96,7 @@ impl FindInstance { let mut sources = vec![]; for i in 0..no_sources { sources.push(Source::Borrowed( - ptr::NonNull::new(sources_ptr.add(i as usize) as *mut _).unwrap(), + ptr::NonNull::new_unchecked(sources_ptr.add(i as usize) as *mut _), self, )); } diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index 0ea2c780..54bc37dd 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -516,14 +516,26 @@ impl BaseSrcImpl for NdiAudioSrc { let mut state = self.state.lock().unwrap(); state.receiver = Some(recv); if state.info.as_ref() != Some(&info) { - let caps = info.to_caps().unwrap(); + let caps = info.to_caps().ok_or_else(|| { + gst_element_error!( + element, + gst::ResourceError::Settings, + ["Invalid audio info received: {:?}", info] + ); + gst::FlowError::NotNegotiated + })?; state.info = Some(info.clone()); state.current_latency = buffer.get_duration(); drop(state); gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); - element - .set_caps(&caps) - .map_err(|_| gst::FlowError::NotNegotiated)?; + element.set_caps(&caps).map_err(|_| { + gst_element_error!( + element, + gst::CoreError::Negotiation, + ["Failed to negotiate caps: {:?}", caps] + ); + gst::FlowError::NotNegotiated + })?; let _ = element .post_message(&gst::Message::new_latency().src(Some(element)).build()); diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index dc593d9e..d6943844 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -556,14 +556,26 @@ impl BaseSrcImpl for NdiVideoSrc { let mut state = self.state.lock().unwrap(); state.receiver = Some(recv); if state.info.as_ref() != Some(&info) { - let caps = info.to_caps().unwrap(); + let caps = info.to_caps().ok_or_else(|| { + gst_element_error!( + element, + gst::ResourceError::Settings, + ["Invalid audio info received: {:?}", info] + ); + gst::FlowError::NotNegotiated + })?; state.info = Some(info.clone()); state.current_latency = buffer.get_duration(); drop(state); gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); - element - .set_caps(&caps) - .map_err(|_| gst::FlowError::NotNegotiated)?; + element.set_caps(&caps).map_err(|_| { + gst_element_error!( + element, + gst::CoreError::Negotiation, + ["Failed to negotiate caps: {:?}", caps] + ); + gst::FlowError::NotNegotiated + })?; let _ = element .post_message(&gst::Message::new_latency().src(Some(element)).build()); diff --git a/src/receiver.rs b/src/receiver.rs index 9c9016bb..023ec6c7 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -264,47 +264,53 @@ impl Observations { current_mapping.b.into(), current_mapping.num.into(), current_mapping.den.into(), - ) - .unwrap(); + ); let new_calculated = gst::Clock::adjust_with_calibration( time.0.into(), next_mapping.xbase.into(), next_mapping.b.into(), next_mapping.num.into(), next_mapping.den.into(), - ) - .unwrap(); - - let diff = if new_calculated > expected { - new_calculated - expected - } else { - expected - new_calculated - }; - - // Allow at most 5% frame duration or 2ms difference per frame - let max_diff = cmp::max( - (duration / 10).unwrap_or(2 * gst::MSECOND_VAL), - 2 * gst::MSECOND_VAL, ); - if diff > max_diff { - gst_debug!( - cat, - obj: element, - "New time mapping causes difference {} but only {} allowed", - gst::ClockTime::from(diff), - gst::ClockTime::from(max_diff), + if let (Some(expected), Some(new_calculated)) = (*expected, *new_calculated) { + let diff = if new_calculated > expected { + new_calculated - expected + } else { + expected - new_calculated + }; + + // Allow at most 5% frame duration or 2ms difference per frame + let max_diff = cmp::max( + (duration / 10).unwrap_or(2 * gst::MSECOND_VAL), + 2 * gst::MSECOND_VAL, ); - if new_calculated > expected { - current_mapping.b = expected + max_diff; - current_mapping.xbase = time.0; + if diff > max_diff { + gst_debug!( + cat, + obj: element, + "New time mapping causes difference {} but only {} allowed", + gst::ClockTime::from(diff), + gst::ClockTime::from(max_diff), + ); + + if new_calculated > expected { + current_mapping.b = expected + max_diff; + current_mapping.xbase = time.0; + } else { + current_mapping.b = expected - max_diff; + current_mapping.xbase = time.0; + } } else { - current_mapping.b = expected - max_diff; - current_mapping.xbase = time.0; + *current_mapping = *next_mapping; } } else { - *current_mapping = *next_mapping; + gst_warning!( + cat, + obj: element, + "Failed to calculate timestamps based on new mapping", + ); } } @@ -597,13 +603,15 @@ where if val_ip_address == ip_address || val_ndi_name == ndi_name { if (val_video.is_some() || !T::IS_VIDEO) && (val_audio.is_some() || T::IS_VIDEO) { - gst_error!( - cat, - obj: element, - "Source with ndi-name '{:?}' and ip-address '{:?}' already in use for {}", - val_ndi_name, - val_ip_address, - if T::IS_VIDEO { "video" } else { "audio" }, + gst_element_error!( + element, + gst::ResourceError::OpenRead, + [ + "Source with ndi-name '{:?}' and ip-address '{:?}' already in use for {}", + val_ndi_name, + val_ip_address, + if T::IS_VIDEO { "video" } else { "audio" } + ] ); return None; @@ -744,9 +752,7 @@ fn connect_ndi_async( source.ip_address(), ); } - } - { let receivers = HASHMAP_RECEIVERS.lock().unwrap(); let info = match receivers.get(&id_receiver) { None => return Err(None), @@ -804,7 +810,7 @@ fn connect_ndi_async( None => { return Err(Some(gst_error_msg!( gst::CoreError::Negotiation, - ["Cannot run NDI: NDIlib_recv_create_v3 error"] + ["Failed to connect to source"] ))); } Some(recv) => recv, @@ -873,7 +879,10 @@ where Some(receiver) => receiver, }; - let element = receiver.0.element.upgrade().unwrap(); + let element = match receiver.0.element.upgrade() { + None => return, + Some(element) => element, + }; let mut recv = receiver.0.recv.lock().unwrap(); loop { @@ -911,7 +920,10 @@ where Some(receiver) => receiver, }; - let element = receiver.0.element.upgrade().unwrap(); + let element = match receiver.0.element.upgrade() { + None => return, + Some(element) => element, + }; { let queue = (receiver.0.queue.0).0.lock().unwrap(); @@ -947,7 +959,10 @@ where Some(receiver) => receiver, }; - let element = receiver.0.element.upgrade().unwrap(); + let element = match receiver.0.element.upgrade() { + None => return, + Some(element) => element, + }; { let queue = (receiver.0.queue.0).0.lock().unwrap(); @@ -1032,6 +1047,78 @@ impl ReceiverCapture for Receiver { } } +impl Receiver { + fn calculate_timestamp( + &self, + element: &gst_base::BaseSrc, + timestamp: i64, + timecode: i64, + duration: gst::ClockTime, + ) -> (gst::ClockTime, gst::ClockTime) { + let clock = element.get_clock().unwrap(); + + // For now take the current running time as PTS. At a later time we + // will want to work with the timestamp given by the NDI SDK if available + let now = clock.get_time(); + let base_time = element.get_base_time(); + let receive_time = now - base_time; + + let real_time_now = gst::ClockTime::from(glib::get_real_time() as u64 * 1000); + let timestamp = if timestamp == ndisys::NDIlib_recv_timestamp_undefined { + gst::CLOCK_TIME_NONE + } else { + gst::ClockTime::from(timestamp as u64 * 100) + }; + let timecode = gst::ClockTime::from(timecode as u64 * 100); + + gst_log!( + self.0.cat, + obj: element, + "Received frame with timecode {}, timestamp {}, duration {}, receive time {}, local time now {}", + timecode, + timestamp, + duration, + receive_time, + real_time_now, + ); + + let (pts, duration) = match self.0.timestamp_mode { + TimestampMode::ReceiveTime => self.0.observations.process( + self.0.cat, + element, + (timestamp, receive_time), + duration, + ), + TimestampMode::Timecode => (timecode, duration), + TimestampMode::Timestamp if timestamp.is_none() => (receive_time, duration), + TimestampMode::Timestamp => { + // Timestamps are relative to the UNIX epoch + if real_time_now > timestamp { + let diff = real_time_now - timestamp; + if diff > receive_time { + (0.into(), duration) + } else { + (receive_time - diff, duration) + } + } else { + let diff = timestamp - real_time_now; + (receive_time + diff, duration) + } + } + }; + + gst_log!( + self.0.cat, + obj: element, + "Calculated PTS {}, duration {}", + pts, + duration, + ); + + (pts, duration) + } +} + impl ReceiverCapture for Receiver { fn capture_internal( &self, @@ -1081,10 +1168,15 @@ impl Receiver { let video_frame = match res { Err(_) => { - gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type error received, assuming that the source closed the stream...."]); + gst_element_error!( + element, + gst::ResourceError::Read, + ["Error receiving frame"] + ); return Err(gst::FlowError::Error); } Ok(None) if timeout.elapsed().as_millis() >= self.0.timeout as u128 => { + gst_debug!(self.0.cat, obj: element, "Timed out -- assuming EOS",); return Err(gst::FlowError::Eos); } Ok(None) => { @@ -1101,6 +1193,13 @@ impl Receiver { break video_frame; }; + gst_debug!( + self.0.cat, + obj: element, + "Received video frame {:?}", + video_frame, + ); + let (pts, duration) = self.calculate_video_timestamp(element, &video_frame); // Simply read all video frames while flushing but don't copy them or anything to @@ -1124,22 +1223,6 @@ impl Receiver { element: &gst_base::BaseSrc, video_frame: &VideoFrame, ) -> (gst::ClockTime, gst::ClockTime) { - let clock = element.get_clock().unwrap(); - - // For now take the current running time as PTS. At a later time we - // will want to work with the timestamp given by the NDI SDK if available - let now = clock.get_time(); - let base_time = element.get_base_time(); - let receive_time = now - base_time; - - let real_time_now = gst::ClockTime::from(glib::get_real_time() as u64 * 1000); - let timestamp = if video_frame.timestamp() == ndisys::NDIlib_recv_timestamp_undefined { - gst::CLOCK_TIME_NONE - } else { - gst::ClockTime::from(video_frame.timestamp() as u64 * 100) - }; - let timecode = gst::ClockTime::from(video_frame.timecode() as u64 * 100); - let duration = gst::SECOND .mul_div_floor( video_frame.frame_rate().1 as u64, @@ -1147,57 +1230,17 @@ impl Receiver { ) .unwrap_or(gst::CLOCK_TIME_NONE); - gst_log!( - self.0.cat, - obj: element, - "NDI video frame received: {:?} with timecode {}, timestamp {}, duration {}, receive time {}, local time now {}", - video_frame, - timecode, - timestamp, + self.calculate_timestamp( + element, + video_frame.timestamp(), + video_frame.timecode(), duration, - receive_time, - real_time_now, - ); - - let (pts, duration) = match self.0.timestamp_mode { - TimestampMode::ReceiveTime => self.0.observations.process( - self.0.cat, - element, - (timestamp, receive_time), - duration, - ), - TimestampMode::Timecode => (timecode, duration), - TimestampMode::Timestamp if timestamp.is_none() => (receive_time, duration), - TimestampMode::Timestamp => { - // Timestamps are relative to the UNIX epoch - if real_time_now > timestamp { - let diff = real_time_now - timestamp; - if diff > receive_time { - (0.into(), duration) - } else { - (receive_time - diff, duration) - } - } else { - let diff = timestamp - real_time_now; - (receive_time + diff, duration) - } - } - }; - - gst_log!( - self.0.cat, - obj: element, - "Calculated PTS for video frame {}, duration {}", - pts, - duration, - ); - - (pts, duration) + ) } fn create_video_info( &self, - _element: &gst_base::BaseSrc, + element: &gst_base::BaseSrc, video_frame: &VideoFrame, ) -> Result { // YV12 and I420 are swapped in the NDI SDK compared to GStreamer @@ -1213,7 +1256,8 @@ impl Receiver { ndisys::NDIlib_FourCC_type_e::NDIlib_FourCC_type_UYVA => gst_video::VideoFormat::Uyvy, }; - let par = gst::Fraction::approximate_f32(video_frame.picture_aspect_ratio()).unwrap() + let par = gst::Fraction::approximate_f32(video_frame.picture_aspect_ratio()) + .unwrap_or(gst::Fraction::new(1, 1)) * gst::Fraction::new(video_frame.yres(), video_frame.xres()); #[cfg(feature = "interlaced-fields")] @@ -1241,7 +1285,15 @@ impl Receiver { builder = builder.field_order(gst_video::VideoFieldOrder::TopFieldFirst); } - Ok(builder.build().unwrap()) + builder.build().ok_or_else(|| { + gst_element_error!( + element, + gst::StreamError::Format, + ["Invalid video format configuration"] + ); + + gst::FlowError::NotNegotiated + }) } #[cfg(not(feature = "interlaced-fields"))] @@ -1282,7 +1334,15 @@ impl Receiver { builder = builder.field_order(gst_video::VideoFieldOrder::TopFieldFirst); } - Ok(builder.build().unwrap()); + builder.build().ok_or_else(|| { + gst_element_error!( + element, + gst::StreamError::Format, + ["Invalid video format configuration"] + ); + + gst::FlowError::NotNegotiated + }) } } @@ -1365,7 +1425,6 @@ impl Receiver { buffer: gst::Buffer, video_frame: &VideoFrame, ) -> Result { - // FIXME: Error handling if frame dimensions don't match let mut vframe = gst_video::VideoFrame::from_buffer_writable(buffer, info).unwrap(); match info.format() { @@ -1514,10 +1573,15 @@ impl Receiver { let audio_frame = match res { Err(_) => { - gst_element_error!(element, gst::ResourceError::Read, ["NDI frame type error received, assuming that the source closed the stream...."]); + gst_element_error!( + element, + gst::ResourceError::Read, + ["Error receiving frame"] + ); return Err(gst::FlowError::Error); } Ok(None) if timeout.elapsed().as_millis() >= self.0.timeout as u128 => { + gst_debug!(self.0.cat, obj: element, "Timed out -- assuming EOS",); return Err(gst::FlowError::Eos); } Ok(None) => { @@ -1534,6 +1598,13 @@ impl Receiver { break audio_frame; }; + gst_debug!( + self.0.cat, + obj: element, + "Received audio frame {:?}", + audio_frame, + ); + let (pts, duration) = self.calculate_audio_timestamp(element, &audio_frame); // Simply read all video frames while flushing but don't copy them or anything to @@ -1557,22 +1628,6 @@ impl Receiver { element: &gst_base::BaseSrc, audio_frame: &AudioFrame, ) -> (gst::ClockTime, gst::ClockTime) { - let clock = element.get_clock().unwrap(); - - // For now take the current running time as PTS. At a later time we - // will want to work with the timestamp given by the NDI SDK if available - let now = clock.get_time(); - let base_time = element.get_base_time(); - let receive_time = now - base_time; - - let real_time_now = gst::ClockTime::from(glib::get_real_time() as u64 * 1000); - let timestamp = if audio_frame.timestamp() == ndisys::NDIlib_recv_timestamp_undefined { - gst::CLOCK_TIME_NONE - } else { - gst::ClockTime::from(audio_frame.timestamp() as u64 * 100) - }; - let timecode = gst::ClockTime::from(audio_frame.timecode() as u64 * 100); - let duration = gst::SECOND .mul_div_floor( audio_frame.no_samples() as u64, @@ -1580,57 +1635,17 @@ impl Receiver { ) .unwrap_or(gst::CLOCK_TIME_NONE); - gst_log!( - self.0.cat, - obj: element, - "NDI audio frame received: {:?} with timecode {}, timestamp {}, duration {}, receive time {}, local time now {}", - audio_frame, - timecode, - timestamp, + self.calculate_timestamp( + element, + audio_frame.timestamp(), + audio_frame.timecode(), duration, - receive_time, - real_time_now, - ); - - let (pts, duration) = match self.0.timestamp_mode { - TimestampMode::ReceiveTime => self.0.observations.process( - self.0.cat, - element, - (timestamp, receive_time), - duration, - ), - TimestampMode::Timecode => (timecode, duration), - TimestampMode::Timestamp if timestamp.is_none() => (receive_time, duration), - TimestampMode::Timestamp => { - // Timestamps are relative to the UNIX epoch - if real_time_now > timestamp { - let diff = real_time_now - timestamp; - if diff > receive_time { - (0.into(), duration) - } else { - (receive_time - diff, duration) - } - } else { - let diff = timestamp - real_time_now; - (receive_time + diff, duration) - } - } - }; - - gst_log!( - self.0.cat, - obj: element, - "Calculated PTS for audio frame {}, duration {}", - pts, - duration, - ); - - (pts, duration) + ) } fn create_audio_info( &self, - _element: &gst_base::BaseSrc, + element: &gst_base::BaseSrc, audio_frame: &AudioFrame, ) -> Result { let builder = gst_audio::AudioInfo::new( @@ -1639,7 +1654,15 @@ impl Receiver { audio_frame.no_channels() as u32, ); - Ok(builder.build().unwrap()) + builder.build().ok_or_else(|| { + gst_element_error!( + element, + gst::StreamError::Format, + ["Invalid audio format configuration"] + ); + + gst::FlowError::NotNegotiated + }) } fn create_audio_buffer( From bbdfae8cdd509e3ed0ebb5d3fdd588d703c888be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Fri, 19 Jul 2019 12:55:18 +0300 Subject: [PATCH 146/199] Compare IP addresses in lowercase Only really relevant for IPv6. --- src/receiver.rs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/receiver.rs b/src/receiver.rs index 023ec6c7..ab248ab0 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -570,6 +570,8 @@ where { gst_debug!(cat, obj: element, "Starting NDI connection..."); + let ip_address = ip_address.map(str::to_lowercase); + let mut receivers = HASHMAP_RECEIVERS.lock().unwrap(); // Check if we already have a receiver for this very stream @@ -584,7 +586,7 @@ where } => ( audio, video, - ip_address.as_ref().map(String::as_ref), + ip_address.as_ref(), ndi_name.as_ref().map(String::as_ref), ), ReceiverInfo::Connected { @@ -593,15 +595,10 @@ where ref ip_address, ref ndi_name, .. - } => ( - audio, - video, - Some(ip_address.as_str()), - Some(ndi_name.as_str()), - ), + } => (audio, video, Some(ip_address), Some(ndi_name.as_str())), }; - if val_ip_address == ip_address || val_ndi_name == ndi_name { + if val_ip_address == ip_address.as_ref() || val_ndi_name == ndi_name { if (val_video.is_some() || !T::IS_VIDEO) && (val_audio.is_some() || T::IS_VIDEO) { gst_element_error!( element, @@ -626,7 +623,7 @@ where let mut info = ReceiverInfo::Connecting { id: id_receiver, ndi_name: ndi_name.map(String::from), - ip_address: ip_address.map(String::from), + ip_address, video: None, audio: None, observations: Observations::new(), @@ -775,7 +772,7 @@ fn connect_ndi_async( let source = sources.iter().find(|s| { Some(s.ndi_name()) == ndi_name.as_ref().map(String::as_str) - || Some(s.ip_address()) == ip_address.as_ref().map(String::as_str) + || Some(&s.ip_address().to_lowercase()) == ip_address.as_ref() }); if let Some(source) = source { @@ -842,7 +839,7 @@ fn connect_ndi_async( *info = ReceiverInfo::Connected { id: id_receiver, ndi_name: source.ndi_name().to_owned(), - ip_address: source.ip_address().to_owned(), + ip_address: source.ip_address().to_lowercase(), recv: recv.clone(), video: video.clone(), audio: audio.clone(), From 3ac1a6b288bb553aab7918b8e04d74ea4045d4d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Fri, 19 Jul 2019 13:00:27 +0300 Subject: [PATCH 147/199] Handle receiving in the beginning without clock gracefully We'll only have a clock once the source element is actually in Playing. --- src/receiver.rs | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/receiver.rs b/src/receiver.rs index ab248ab0..375c18d0 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -1051,8 +1051,11 @@ impl Receiver { timestamp: i64, timecode: i64, duration: gst::ClockTime, - ) -> (gst::ClockTime, gst::ClockTime) { - let clock = element.get_clock().unwrap(); + ) -> Option<(gst::ClockTime, gst::ClockTime)> { + let clock = match element.get_clock() { + None => return None, + Some(clock) => clock, + }; // For now take the current running time as PTS. At a later time we // will want to work with the timestamp given by the NDI SDK if available @@ -1112,7 +1115,7 @@ impl Receiver { duration, ); - (pts, duration) + Some((pts, duration)) } } @@ -1197,7 +1200,12 @@ impl Receiver { video_frame, ); - let (pts, duration) = self.calculate_video_timestamp(element, &video_frame); + let (pts, duration) = self + .calculate_video_timestamp(element, &video_frame) + .ok_or_else(|| { + gst_debug!(self.0.cat, obj: element, "Flushing, dropping buffer"); + gst::FlowError::CustomError + })?; // Simply read all video frames while flushing but don't copy them or anything to // make sure that we're not accumulating anything here @@ -1219,7 +1227,7 @@ impl Receiver { &self, element: &gst_base::BaseSrc, video_frame: &VideoFrame, - ) -> (gst::ClockTime, gst::ClockTime) { + ) -> Option<(gst::ClockTime, gst::ClockTime)> { let duration = gst::SECOND .mul_div_floor( video_frame.frame_rate().1 as u64, @@ -1602,7 +1610,12 @@ impl Receiver { audio_frame, ); - let (pts, duration) = self.calculate_audio_timestamp(element, &audio_frame); + let (pts, duration) = self + .calculate_audio_timestamp(element, &audio_frame) + .ok_or_else(|| { + gst_debug!(self.0.cat, obj: element, "Flushing, dropping buffer"); + gst::FlowError::CustomError + })?; // Simply read all video frames while flushing but don't copy them or anything to // make sure that we're not accumulating anything here @@ -1624,7 +1637,7 @@ impl Receiver { &self, element: &gst_base::BaseSrc, audio_frame: &AudioFrame, - ) -> (gst::ClockTime, gst::ClockTime) { + ) -> Option<(gst::ClockTime, gst::ClockTime)> { let duration = gst::SECOND .mul_div_floor( audio_frame.no_samples() as u64, From cd741f37be3269dacd7fe7155e1e8e5fcf265d62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Mon, 22 Jul 2019 09:12:04 +0300 Subject: [PATCH 148/199] Fix compilation with --no-default-features --- Cargo.toml | 2 +- src/lib.rs | 22 ++++++++++++---------- src/receiver.rs | 2 +- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 44b5b993..4da1e160 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ gobject-sys = "0.9" gstreamer = { version = "0.14.3", features = ["subclassing", "v1_12"] } gstreamer-base = { version = "0.14.0", features = ["subclassing"] } gstreamer-audio = "0.14.0" -gstreamer-video = "0.14.3" +gstreamer-video = { version = "0.14.3", features = ["v1_12"] } gstreamer-sys = "0.8" lazy_static = "1.1.0" byte-slice-cast = "0.2.0" diff --git a/src/lib.rs b/src/lib.rs index 596496eb..6c75e3bb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,18 +45,20 @@ fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { lazy_static! { static ref DEFAULT_RECEIVER_NDI_NAME: String = { - format!("GStreamer NDI Source {}-{}", env!("CARGO_PKG_VERSION"), env!("COMMIT_ID")) + format!( + "GStreamer NDI Source {}-{}", + env!("CARGO_PKG_VERSION"), + env!("COMMIT_ID") + ) }; +} - #[cfg(feature = "reference-timestamps")] - static ref TIMECODE_CAPS: gst::Caps = { - gst::Caps::new_simple("timestamp/x-ndi-timecode", &[]) - }; - - #[cfg(feature = "reference-timestamps")] - static ref TIMESTAMP_CAPS: gst::Caps = { - gst::Caps::new_simple("timestamp/x-ndi-timestamp", &[]) - }; +#[cfg(feature = "reference-timestamps")] +lazy_static! { + static ref TIMECODE_CAPS: gst::Caps = + { gst::Caps::new_simple("timestamp/x-ndi-timecode", &[]) }; + static ref TIMESTAMP_CAPS: gst::Caps = + { gst::Caps::new_simple("timestamp/x-ndi-timestamp", &[]) }; } impl glib::translate::ToGlib for TimestampMode { diff --git a/src/receiver.rs b/src/receiver.rs index 375c18d0..8f23f7fa 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -1316,7 +1316,7 @@ impl Receiver { return Err(gst::FlowError::NotNegotiated); } - let builder = gst_video::VideoInfo::new( + let mut builder = gst_video::VideoInfo::new( format, video_frame.xres() as u32, video_frame.yres() as u32, From 060190d59a17cb91fd3ea9e0acc26efbf79928c1 Mon Sep 17 00:00:00 2001 From: Ruben Gonzalez Date: Tue, 23 Jul 2019 07:22:52 +0200 Subject: [PATCH 149/199] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 86b0fa58..a587f4f6 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs License ------- -This plugin is licensed under the LGPL - see the ([LICENSE](LICENSE) file for details +This plugin is licensed under the LGPL - see the [LICENSE](LICENSE) file for details Acknowledgments From 57d928ad96585093189f1c2cf724535ae6938127 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 23 Jul 2019 14:20:56 +0300 Subject: [PATCH 150/199] Switch to 2018 edition Fixes https://github.com/teltek/gst-plugin-ndi/issues/15 --- Cargo.toml | 1 + src/lib.rs | 6 +++--- src/ndi.rs | 2 +- src/ndiaudiosrc.rs | 17 ++++++++--------- src/ndivideosrc.rs | 18 ++++++++---------- 5 files changed, 21 insertions(+), 23 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4da1e160..a4eb2e4a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ authors = ["Ruben Gonzalez ", "Daniel Vilar Date: Tue, 23 Jul 2019 14:21:51 +0300 Subject: [PATCH 151/199] Add myself to the authors list in Cargo.toml --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a4eb2e4a..35f531b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "gst-plugin-ndi" version = "1.0.0" -authors = ["Ruben Gonzalez ", "Daniel Vilar "] +authors = ["Ruben Gonzalez ", "Daniel Vilar ", "Sebastian Dröge "] repository = "https://github.com/teltek/gst-plugin-ndi" license = "LGPL" description = "NewTek NDI Plugin" From b0b42f8f1703b24ced8f7b6634e33a4544d25d21 Mon Sep 17 00:00:00 2001 From: rubenrua Date: Tue, 30 Jul 2019 17:29:42 +0200 Subject: [PATCH 152/199] Fix travis CI --- .travis.yml | 4 ++-- src/receiver.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 691885e9..c8060043 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,5 +26,5 @@ install: - rustup component add clippy-preview script: - cargo fmt -- --check - - touch ./src/*.rs && cargo clippy -- -A clippy::cast_ptr_alignment -A clippy::new_ret_no_self - - cargo build \ No newline at end of file + - cargo build --no-default-features + - touch ./src/*.rs && cargo clippy --no-default-features -- -A clippy::cast_ptr_alignment -A clippy::new_ret_no_self -Aclippy::or_fun_call -A clippy::cast_lossless -A clippy::too_many_arguments \ No newline at end of file diff --git a/src/receiver.rs b/src/receiver.rs index 8f23f7fa..ba72775e 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -891,7 +891,7 @@ where } // If an error happened in the meantime, just go out of here - if let Some(_) = queue.error { + if queue.error.is_some() { gst_error!( receiver.0.cat, obj: &element, @@ -930,7 +930,7 @@ where } // If an error happened in the meantime, just go out of here - if let Some(_) = queue.error { + if queue.error.is_some() { gst_error!( receiver.0.cat, obj: &element, From 892e54ac4d01cc0e7487001e7afce4dc33768301 Mon Sep 17 00:00:00 2001 From: rubenrua Date: Tue, 30 Jul 2019 17:52:15 +0200 Subject: [PATCH 153/199] Reorder ndisys to reduce complexity --- src/ndisys.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ndisys.rs b/src/ndisys.rs index 0aee3d6d..90bff495 100644 --- a/src/ndisys.rs +++ b/src/ndisys.rs @@ -14,9 +14,11 @@ )] extern "C" { pub fn NDIlib_initialize() -> bool; + pub fn NDIlib_destroy(); pub fn NDIlib_find_create_v2( p_create_settings: *const NDIlib_find_create_t, ) -> NDIlib_find_instance_t; + pub fn NDIlib_find_destroy(p_instance: NDIlib_find_instance_t); pub fn NDIlib_find_wait_for_sources( p_instance: NDIlib_find_instance_t, timeout_in_ms: u32, @@ -28,9 +30,7 @@ extern "C" { pub fn NDIlib_recv_create_v3( p_create_settings: *const NDIlib_recv_create_v3_t, ) -> NDIlib_recv_instance_t; - pub fn NDIlib_find_destroy(p_instance: NDIlib_find_instance_t); pub fn NDIlib_recv_destroy(p_instance: NDIlib_recv_instance_t); - pub fn NDIlib_destroy(); pub fn NDIlib_recv_set_tally( p_instance: NDIlib_recv_instance_t, p_tally: *const NDIlib_tally_t, From 7246b1b14735561a34f073de6e5123f32a4cb69b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 6 Aug 2019 18:48:24 +0300 Subject: [PATCH 154/199] Don't do anything in BaseSrc::negotiate() We can't negotiate anything meaningful with downstream and will always set the caps based on the data we receive in ::create() later. --- src/ndiaudiosrc.rs | 13 ++++++++----- src/ndivideosrc.rs | 13 ++++++++----- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index 6ddcdb8a..880c90b4 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -375,7 +375,13 @@ impl ElementImpl for NdiAudioSrc { } impl BaseSrcImpl for NdiAudioSrc { - fn unlock(&self, element: &gst_base::BaseSrc) -> std::result::Result<(), gst::ErrorMessage> { + fn negotiate(&self, _element: &gst_base::BaseSrc) -> Result<(), gst::LoggableError> { + // Always succeed here without doing anything: we will set the caps once we received a + // buffer, there's nothing we can negotiate + Ok(()) + } + + fn unlock(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { gst_debug!(self.cat, obj: element, "Unlocking",); if let Some(ref controller) = *self.receiver_controller.lock().unwrap() { controller.set_flushing(true); @@ -383,10 +389,7 @@ impl BaseSrcImpl for NdiAudioSrc { Ok(()) } - fn unlock_stop( - &self, - element: &gst_base::BaseSrc, - ) -> std::result::Result<(), gst::ErrorMessage> { + fn unlock_stop(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { gst_debug!(self.cat, obj: element, "Stop unlocking",); if let Some(ref controller) = *self.receiver_controller.lock().unwrap() { controller.set_flushing(false); diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index 2a25f10b..6ae64016 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -410,7 +410,13 @@ impl ElementImpl for NdiVideoSrc { } impl BaseSrcImpl for NdiVideoSrc { - fn unlock(&self, element: &gst_base::BaseSrc) -> std::result::Result<(), gst::ErrorMessage> { + fn negotiate(&self, _element: &gst_base::BaseSrc) -> Result<(), gst::LoggableError> { + // Always succeed here without doing anything: we will set the caps once we received a + // buffer, there's nothing we can negotiate + Ok(()) + } + + fn unlock(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { gst_debug!(self.cat, obj: element, "Unlocking",); if let Some(ref controller) = *self.receiver_controller.lock().unwrap() { controller.set_flushing(true); @@ -418,10 +424,7 @@ impl BaseSrcImpl for NdiVideoSrc { Ok(()) } - fn unlock_stop( - &self, - element: &gst_base::BaseSrc, - ) -> std::result::Result<(), gst::ErrorMessage> { + fn unlock_stop(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { gst_debug!(self.cat, obj: element, "Stop unlocking",); if let Some(ref controller) = *self.receiver_controller.lock().unwrap() { controller.set_flushing(false); From 8e3c6c39b78cce72d6d09c3e9e4d8c42c0547495 Mon Sep 17 00:00:00 2001 From: Ruben Gonzalez Date: Tue, 6 Aug 2019 23:23:22 +0200 Subject: [PATCH 155/199] Update README (fix #38) --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a587f4f6..9344ffab 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ GStreamer NDI Plugin for Linux ==================== -*Compiled and tested with Ubuntu 16.04.5, GStreamer 1.8.3 and NDI SDK 3.0.9 and 3.5.1* +*Compiled and tested with Ubuntu 16.04.5, GStreamer 1.12 and NDI SDK 3.5.1* This is a plugin for the [GStreamer](https://gstreamer.freedesktop.org/) multimedia framework that allows GStreamer to receive a stream from a [NDI](https://www.newtek.com/ndi/) source. This plugin has been developed by [Teltek](http://teltek.es/) and was funded by the [University of the Arts London](https://www.arts.ac.uk/) and [The University of Manchester](https://www.manchester.ac.uk/). @@ -53,6 +53,8 @@ export GST_PLUGIN_PATH=`pwd`/target/debug gst-inspect-1.0 ndi ``` +By defult GStreamer 1.16 is required, to use only GStreamer 1.12 instead of 1.16, pass `--disable-default-features` to cargo. Only a subset of video formats is supported with this GStreamer version. + If all went ok, you should see info related to the NDI element. To make the plugin available without using `GST_PLUGIN_PATH` it's necessary to copy the plugin to the gstreamer plugins folder. ``` cargo build --release From 06e5d7cb95e797c528ed01252e97348f5ce20e6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Wed, 14 Aug 2019 22:49:55 +0300 Subject: [PATCH 156/199] Depend on gstreamer 0.14.4 for gst::calculate_linear_regression() Instead of having our own unsafe binding for it. --- Cargo.toml | 3 +-- src/lib.rs | 1 - src/receiver.rs | 47 +---------------------------------------------- 3 files changed, 2 insertions(+), 49 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 35f531b7..c0fd4236 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,11 +10,10 @@ edition = "2018" [dependencies] glib = { version = "0.8.0", features = ["subclassing"] } gobject-sys = "0.9" -gstreamer = { version = "0.14.3", features = ["subclassing", "v1_12"] } +gstreamer = { version = "0.14.4", features = ["subclassing", "v1_12"] } gstreamer-base = { version = "0.14.0", features = ["subclassing"] } gstreamer-audio = "0.14.0" gstreamer-video = { version = "0.14.3", features = ["v1_12"] } -gstreamer-sys = "0.8" lazy_static = "1.1.0" byte-slice-cast = "0.2.0" diff --git a/src/lib.rs b/src/lib.rs index 32058b28..190b4ec5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,6 @@ use glib::prelude::*; extern crate gstreamer as gst; extern crate gstreamer_audio as gst_audio; extern crate gstreamer_base as gst_base; -extern crate gstreamer_sys as gst_sys; extern crate gstreamer_video as gst_video; #[macro_use] diff --git a/src/receiver.rs b/src/receiver.rs index ba72775e..b8ea1822 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -233,7 +233,7 @@ impl Observations { values.push(time); if let Some((num, den, b, xbase, r_squared)) = - calculate_linear_regression(values, Some(values_tmp)) + gst::calculate_linear_regression(values, Some(values_tmp)) { next_mapping.xbase = xbase; next_mapping.b = b; @@ -1722,48 +1722,3 @@ impl Receiver { Ok(buffer) } } - -// FIXME: Requires https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/merge_requests/307 -pub fn calculate_linear_regression( - xy: &[(u64, u64)], - temp: Option<&mut [(u64, u64)]>, -) -> Option<(u64, u64, u64, u64, f64)> { - unsafe { - use glib::translate::from_glib; - use std::mem; - use std::ptr; - - assert_eq!(mem::size_of::() * 2, mem::size_of::<(u64, u64)>()); - assert_eq!(mem::align_of::(), mem::align_of::<(u64, u64)>()); - assert!(temp.as_ref().map(|temp| temp.len()).unwrap_or(xy.len()) >= xy.len()); - - let mut m_num = mem::MaybeUninit::uninit(); - let mut m_denom = mem::MaybeUninit::uninit(); - let mut b = mem::MaybeUninit::uninit(); - let mut xbase = mem::MaybeUninit::uninit(); - let mut r_squared = mem::MaybeUninit::uninit(); - - let res = from_glib(gst_sys::gst_calculate_linear_regression( - xy.as_ptr() as *const u64, - temp.map(|temp| temp.as_mut_ptr() as *mut u64) - .unwrap_or(ptr::null_mut()), - xy.len() as u32, - m_num.as_mut_ptr(), - m_denom.as_mut_ptr(), - b.as_mut_ptr(), - xbase.as_mut_ptr(), - r_squared.as_mut_ptr(), - )); - if res { - Some(( - m_num.assume_init(), - m_denom.assume_init(), - b.assume_init(), - xbase.assume_init(), - r_squared.assume_init(), - )) - } else { - None - } - } -} From 5e10693aa692f41efc6d6146ed3420a2012efe95 Mon Sep 17 00:00:00 2001 From: Amaury Denoyelle Date: Fri, 23 Aug 2019 15:35:06 +0200 Subject: [PATCH 157/199] fix comparaison to detect source uniqueness in a pipeline, fixes #42 --- src/receiver.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/receiver.rs b/src/receiver.rs index ba72775e..fe3336de 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -598,7 +598,9 @@ where } => (audio, video, Some(ip_address), Some(ndi_name.as_str())), }; - if val_ip_address == ip_address.as_ref() || val_ndi_name == ndi_name { + if (val_ip_address.is_some() && val_ip_address == ip_address.as_ref()) + || (val_ip_address.is_none() && val_ndi_name == ndi_name) + { if (val_video.is_some() || !T::IS_VIDEO) && (val_audio.is_some() || T::IS_VIDEO) { gst_element_error!( element, From 5923adf333b2015b747f5eb50859ff8136a13dd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Wed, 18 Dec 2019 19:34:00 +0200 Subject: [PATCH 158/199] Update to gstreamer 0.15 bindings release --- Cargo.toml | 10 +++++----- src/ndiaudiosrc.rs | 16 ++++++++-------- src/ndivideosrc.rs | 16 ++++++++-------- src/receiver.rs | 6 +++--- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c0fd4236..835c7c40 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,12 +8,12 @@ description = "NewTek NDI Plugin" edition = "2018" [dependencies] -glib = { version = "0.8.0", features = ["subclassing"] } +glib = "0.9" gobject-sys = "0.9" -gstreamer = { version = "0.14.4", features = ["subclassing", "v1_12"] } -gstreamer-base = { version = "0.14.0", features = ["subclassing"] } -gstreamer-audio = "0.14.0" -gstreamer-video = { version = "0.14.3", features = ["v1_12"] } +gstreamer = { version = "0.15", features = ["v1_12"] } +gstreamer-base = "0.15" +gstreamer-audio = "0.15" +gstreamer-video = { version = "0.15", features = ["v1_12"] } lazy_static = "1.1.0" byte-slice-cast = "0.2.0" diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index 880c90b4..5a9118d8 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -217,7 +217,7 @@ impl ObjectImpl for NdiAudioSrc { match *prop { subclass::Property("ndi-name", ..) => { let mut settings = self.settings.lock().unwrap(); - let ndi_name = value.get(); + let ndi_name = value.get().unwrap(); gst_debug!( self.cat, obj: basesrc, @@ -229,7 +229,7 @@ impl ObjectImpl for NdiAudioSrc { } subclass::Property("ip-address", ..) => { let mut settings = self.settings.lock().unwrap(); - let ip_address = value.get(); + let ip_address = value.get().unwrap(); gst_debug!( self.cat, obj: basesrc, @@ -241,7 +241,7 @@ impl ObjectImpl for NdiAudioSrc { } subclass::Property("receiver-ndi-name", ..) => { let mut settings = self.settings.lock().unwrap(); - let receiver_ndi_name = value.get(); + let receiver_ndi_name = value.get().unwrap(); gst_debug!( self.cat, obj: basesrc, @@ -254,7 +254,7 @@ impl ObjectImpl for NdiAudioSrc { } subclass::Property("connect-timeout", ..) => { let mut settings = self.settings.lock().unwrap(); - let connect_timeout = value.get().unwrap(); + let connect_timeout = value.get_some().unwrap(); gst_debug!( self.cat, obj: basesrc, @@ -266,7 +266,7 @@ impl ObjectImpl for NdiAudioSrc { } subclass::Property("timeout", ..) => { let mut settings = self.settings.lock().unwrap(); - let timeout = value.get().unwrap(); + let timeout = value.get_some().unwrap(); gst_debug!( self.cat, obj: basesrc, @@ -278,7 +278,7 @@ impl ObjectImpl for NdiAudioSrc { } subclass::Property("bandwidth", ..) => { let mut settings = self.settings.lock().unwrap(); - let bandwidth = value.get().unwrap(); + let bandwidth = value.get_some().unwrap(); gst_debug!( self.cat, obj: basesrc, @@ -290,7 +290,7 @@ impl ObjectImpl for NdiAudioSrc { } subclass::Property("timestamp-mode", ..) => { let mut settings = self.settings.lock().unwrap(); - let timestamp_mode = value.get().unwrap(); + let timestamp_mode = value.get_some().unwrap(); gst_debug!( self.cat, obj: basesrc, @@ -518,7 +518,7 @@ impl BaseSrcImpl for NdiAudioSrc { let mut state = self.state.lock().unwrap(); state.receiver = Some(recv); if state.info.as_ref() != Some(&info) { - let caps = info.to_caps().ok_or_else(|| { + let caps = info.to_caps().map_err(|_| { gst_element_error!( element, gst::ResourceError::Settings, diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index 6ae64016..9b89c28f 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -252,7 +252,7 @@ impl ObjectImpl for NdiVideoSrc { match *prop { subclass::Property("ndi-name", ..) => { let mut settings = self.settings.lock().unwrap(); - let ndi_name = value.get(); + let ndi_name = value.get().unwrap(); gst_debug!( self.cat, obj: basesrc, @@ -264,7 +264,7 @@ impl ObjectImpl for NdiVideoSrc { } subclass::Property("ip-address", ..) => { let mut settings = self.settings.lock().unwrap(); - let ip_address = value.get(); + let ip_address = value.get().unwrap(); gst_debug!( self.cat, obj: basesrc, @@ -276,7 +276,7 @@ impl ObjectImpl for NdiVideoSrc { } subclass::Property("receiver-ndi-name", ..) => { let mut settings = self.settings.lock().unwrap(); - let receiver_ndi_name = value.get(); + let receiver_ndi_name = value.get().unwrap(); gst_debug!( self.cat, obj: basesrc, @@ -289,7 +289,7 @@ impl ObjectImpl for NdiVideoSrc { } subclass::Property("connect-timeout", ..) => { let mut settings = self.settings.lock().unwrap(); - let connect_timeout = value.get().unwrap(); + let connect_timeout = value.get_some().unwrap(); gst_debug!( self.cat, obj: basesrc, @@ -301,7 +301,7 @@ impl ObjectImpl for NdiVideoSrc { } subclass::Property("timeout", ..) => { let mut settings = self.settings.lock().unwrap(); - let timeout = value.get().unwrap(); + let timeout = value.get_some().unwrap(); gst_debug!( self.cat, obj: basesrc, @@ -313,7 +313,7 @@ impl ObjectImpl for NdiVideoSrc { } subclass::Property("bandwidth", ..) => { let mut settings = self.settings.lock().unwrap(); - let bandwidth = value.get().unwrap(); + let bandwidth = value.get_some().unwrap(); gst_debug!( self.cat, obj: basesrc, @@ -325,7 +325,7 @@ impl ObjectImpl for NdiVideoSrc { } subclass::Property("timestamp-mode", ..) => { let mut settings = self.settings.lock().unwrap(); - let timestamp_mode = value.get().unwrap(); + let timestamp_mode = value.get_some().unwrap(); gst_debug!( self.cat, obj: basesrc, @@ -557,7 +557,7 @@ impl BaseSrcImpl for NdiVideoSrc { let mut state = self.state.lock().unwrap(); state.receiver = Some(recv); if state.info.as_ref() != Some(&info) { - let caps = info.to_caps().ok_or_else(|| { + let caps = info.to_caps().map_err(|_| { gst_element_error!( element, gst::ResourceError::Settings, diff --git a/src/receiver.rs b/src/receiver.rs index 4d40d83e..17d18897 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -1292,7 +1292,7 @@ impl Receiver { builder = builder.field_order(gst_video::VideoFieldOrder::TopFieldFirst); } - builder.build().ok_or_else(|| { + builder.build().map_err(|_| { gst_element_error!( element, gst::StreamError::Format, @@ -1341,7 +1341,7 @@ impl Receiver { builder = builder.field_order(gst_video::VideoFieldOrder::TopFieldFirst); } - builder.build().ok_or_else(|| { + builder.build().map_err(|_| { gst_element_error!( element, gst::StreamError::Format, @@ -1666,7 +1666,7 @@ impl Receiver { audio_frame.no_channels() as u32, ); - builder.build().ok_or_else(|| { + builder.build().map_err(|_| { gst_element_error!( element, gst::StreamError::Format, From 6aea804c6ccec9ac3ffa351f766fb84f97bf9257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Thu, 19 Dec 2019 00:49:09 +0200 Subject: [PATCH 159/199] Update byte-slice-cast to 0.3 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 835c7c40..388e4363 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ gstreamer-base = "0.15" gstreamer-audio = "0.15" gstreamer-video = { version = "0.15", features = ["v1_12"] } lazy_static = "1.1.0" -byte-slice-cast = "0.2.0" +byte-slice-cast = "0.3.0" [build-dependencies] gst-plugin-version-helper = "0.1" From 05b807aff0e46f33f26202d7e8b2202cc4bafea4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Thu, 2 Jan 2020 13:06:53 +0200 Subject: [PATCH 160/199] Fix some minor clippy warnings --- src/ndiaudiosrc.rs | 2 +- src/ndivideosrc.rs | 2 +- src/receiver.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index 5a9118d8..2d06bb34 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -526,7 +526,7 @@ impl BaseSrcImpl for NdiAudioSrc { ); gst::FlowError::NotNegotiated })?; - state.info = Some(info.clone()); + state.info = Some(info); state.current_latency = buffer.get_duration(); drop(state); gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index 9b89c28f..790385bc 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -565,7 +565,7 @@ impl BaseSrcImpl for NdiVideoSrc { ); gst::FlowError::NotNegotiated })?; - state.info = Some(info.clone()); + state.info = Some(info); state.current_latency = buffer.get_duration(); drop(state); gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); diff --git a/src/receiver.rs b/src/receiver.rs index 17d18897..7b9b0a5c 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -860,7 +860,7 @@ fn connect_ndi_async( if let Some(video) = video.and_then(|v| v.upgrade()).map(Receiver) { let mut video_recv = video.0.recv.lock().unwrap(); assert!(video_recv.is_none()); - *video_recv = Some(recv.clone()); + *video_recv = Some(recv); video.0.recv_cond.notify_one(); } @@ -1264,7 +1264,7 @@ impl Receiver { }; let par = gst::Fraction::approximate_f32(video_frame.picture_aspect_ratio()) - .unwrap_or(gst::Fraction::new(1, 1)) + .unwrap_or_else(|| gst::Fraction::new(1, 1)) * gst::Fraction::new(video_frame.yres(), video_frame.xres()); #[cfg(feature = "interlaced-fields")] From afc301de3a1bd575ee8602844a53fb9719170564 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Thu, 2 Jan 2020 14:00:47 +0200 Subject: [PATCH 161/199] Port to NDI SDK v4 Most notably this allows the video line stride to be set to 0, in which case a default value is to be used, and it deprecates selecting NDI sources by their IP address and because of that we remove the relevant properties. Usually the SDK will give an URL instead of an IP address now, so usage would've been broken anyway. --- README.md | 2 +- src/ndi.rs | 65 +++++++++++++++++++++---------- src/ndiaudiosrc.rs | 38 +++--------------- src/ndisys.rs | 37 +++++++++--------- src/ndivideosrc.rs | 38 +++--------------- src/receiver.rs | 96 +++++++++++++++++++++------------------------- 6 files changed, 119 insertions(+), 157 deletions(-) diff --git a/README.md b/README.md index 9344ffab..5704660e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ GStreamer NDI Plugin for Linux ==================== -*Compiled and tested with Ubuntu 16.04.5, GStreamer 1.12 and NDI SDK 3.5.1* +*Compiled and tested with NDI SDK 3.5, 3.8, 4.0 and 4.1* This is a plugin for the [GStreamer](https://gstreamer.freedesktop.org/) multimedia framework that allows GStreamer to receive a stream from a [NDI](https://www.newtek.com/ndi/) source. This plugin has been developed by [Teltek](http://teltek.es/) and was funded by the [University of the Arts London](https://www.arts.ac.uk/) and [The University of Manchester](https://www.manchester.ac.uk/). diff --git a/src/ndi.rs b/src/ndi.rs index 6f04ad45..f17170b8 100644 --- a/src/ndi.rs +++ b/src/ndi.rs @@ -1,3 +1,4 @@ +use crate::ndisys; use crate::ndisys::*; use std::ffi; use std::mem; @@ -135,15 +136,15 @@ impl<'a> Source<'a> { } } - pub fn ip_address(&self) -> &str { + 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_ip_address.is_null()); - ffi::CStr::from_ptr(ptr.p_ip_address).to_str().unwrap() + assert!(!ptr.p_url_address.is_null()); + ffi::CStr::from_ptr(ptr.p_url_address).to_str().unwrap() } } @@ -156,34 +157,35 @@ impl<'a> Source<'a> { } } - fn ip_address_ptr(&self) -> *const ::std::os::raw::c_char { + fn url_address_ptr(&self) -> *const ::std::os::raw::c_char { unsafe { match *self { - Source::Borrowed(ptr, _) => ptr.as_ref().p_ip_address, - Source::Owned(_, _, ref ip_address) => ip_address.as_ptr(), + Source::Borrowed(ptr, _) => ptr.as_ref().p_url_address, + Source::Owned(_, _, ref url_address) => url_address.as_ptr(), } } } pub fn to_owned<'b>(&self) -> Source<'b> { unsafe { - let (ndi_name, ip_address) = match *self { - Source::Borrowed(ptr, _) => (ptr.as_ref().p_ndi_name, ptr.as_ref().p_ip_address), - Source::Owned(_, ref ndi_name, ref ip_address) => { - (ndi_name.as_ptr(), ip_address.as_ptr()) + 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 ip_address = ffi::CString::new(ffi::CStr::from_ptr(ip_address).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_ip_address: ip_address.as_ptr(), + p_url_address: url_address.as_ptr(), }, ndi_name, - ip_address, + url_address, ) } } @@ -223,12 +225,12 @@ impl<'a> RecvBuilder<'a> { let ptr = NDIlib_recv_create_v3(&NDIlib_recv_create_v3_t { source_to_connect_to: NDIlib_source_t { p_ndi_name: self.source_to_connect_to.ndi_name_ptr(), - p_ip_address: self.source_to_connect_to.ip_address_ptr(), + p_url_address: self.source_to_connect_to.url_address_ptr(), }, allow_video_fields: self.allow_video_fields, bandwidth: self.bandwidth, color_format: self.color_format, - p_ndi_name: ndi_name.as_ptr(), + p_ndi_recv_name: ndi_name.as_ptr(), }); if ptr.is_null() { @@ -410,7 +412,7 @@ impl<'a> VideoFrame<'a> { } } - pub fn fourcc(&self) -> NDIlib_FourCC_type_e { + pub fn fourcc(&self) -> NDIlib_FourCC_video_type_e { match self { VideoFrame::Borrowed(ref frame, _) => frame.FourCC, } @@ -448,9 +450,9 @@ impl<'a> VideoFrame<'a> { || self.frame_format_type() == NDIlib_frame_format_type_e::NDIlib_frame_format_type_field_1 { - self.yres() * self.line_stride_in_bytes() / 2 + self.yres() * self.line_stride_or_data_size_in_bytes() / 2 } else { - self.yres() * self.line_stride_in_bytes() + self.yres() * self.line_stride_or_data_size_in_bytes() }; unsafe { @@ -463,9 +465,32 @@ impl<'a> VideoFrame<'a> { } } - pub fn line_stride_in_bytes(&self) -> i32 { + pub fn line_stride_or_data_size_in_bytes(&self) -> i32 { match self { - VideoFrame::Borrowed(ref frame, _) => frame.line_stride_in_bytes, + VideoFrame::Borrowed(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, + } + } } } diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index 2d06bb34..47a41fdc 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -24,7 +24,6 @@ use crate::DEFAULT_RECEIVER_NDI_NAME; #[derive(Debug, Clone)] struct Settings { ndi_name: Option, - ip_address: Option, connect_timeout: u32, timeout: u32, receiver_ndi_name: String, @@ -36,7 +35,6 @@ impl Default for Settings { fn default() -> Self { Settings { ndi_name: None, - ip_address: None, receiver_ndi_name: DEFAULT_RECEIVER_NDI_NAME.clone(), connect_timeout: 10000, timeout: 5000, @@ -46,7 +44,7 @@ impl Default for Settings { } } -static PROPERTIES: [subclass::Property; 7] = [ +static PROPERTIES: [subclass::Property; 6] = [ subclass::Property("ndi-name", |name| { glib::ParamSpec::string( name, @@ -56,15 +54,6 @@ static PROPERTIES: [subclass::Property; 7] = [ glib::ParamFlags::READWRITE, ) }), - subclass::Property("ip-address", |name| { - glib::ParamSpec::string( - name, - "IP Address", - "IP address and port of the sender, e.g. 127.0.0.1:5961", - None, - glib::ParamFlags::READWRITE, - ) - }), subclass::Property("receiver-ndi-name", |name| { glib::ParamSpec::string( name, @@ -227,18 +216,6 @@ impl ObjectImpl for NdiAudioSrc { ); settings.ndi_name = ndi_name; } - subclass::Property("ip-address", ..) => { - let mut settings = self.settings.lock().unwrap(); - let ip_address = value.get().unwrap(); - gst_debug!( - self.cat, - obj: basesrc, - "Changing ip from {:?} to {:?}", - settings.ip_address, - ip_address, - ); - settings.ip_address = ip_address; - } subclass::Property("receiver-ndi-name", ..) => { let mut settings = self.settings.lock().unwrap(); let receiver_ndi_name = value.get().unwrap(); @@ -316,10 +293,6 @@ impl ObjectImpl for NdiAudioSrc { let settings = self.settings.lock().unwrap(); Ok(settings.ndi_name.to_value()) } - subclass::Property("ip-address", ..) => { - let settings = self.settings.lock().unwrap(); - Ok(settings.ip_address.to_value()) - } subclass::Property("receiver-ndi-name", ..) => { let settings = self.settings.lock().unwrap(); Ok(settings.receiver_ndi_name.to_value()) @@ -401,18 +374,19 @@ impl BaseSrcImpl for NdiAudioSrc { *self.state.lock().unwrap() = Default::default(); let settings = self.settings.lock().unwrap().clone(); - if settings.ip_address.is_none() && settings.ndi_name.is_none() { + let ndi_name = if let Some(ref ndi_name) = settings.ndi_name { + ndi_name + } else { return Err(gst_error_msg!( gst::LibraryError::Settings, ["No IP address or NDI name given"] )); - } + }; let receiver = connect_ndi( self.cat, element, - settings.ip_address.as_ref().map(String::as_str), - settings.ndi_name.as_ref().map(String::as_str), + ndi_name, &settings.receiver_ndi_name, settings.connect_timeout, settings.bandwidth, diff --git a/src/ndisys.rs b/src/ndisys.rs index 90bff495..ddadd43d 100644 --- a/src/ndisys.rs +++ b/src/ndisys.rs @@ -78,7 +78,7 @@ pub struct NDIlib_find_create_t { #[derive(Debug, Copy, Clone)] pub struct NDIlib_source_t { pub p_ndi_name: *const ::std::os::raw::c_char, - pub p_ip_address: *const ::std::os::raw::c_char, + pub p_url_address: *const ::std::os::raw::c_char, } #[repr(i32)] @@ -107,21 +107,21 @@ pub enum NDIlib_recv_color_format_e { NDIlib_recv_color_format_RGBX_RGBA = 2, NDIlib_recv_color_format_UYVY_RGBA = 3, NDIlib_recv_color_format_fastest = 100, + NDIlib_recv_color_format_best = 101, } -#[repr(u32)] -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub enum NDIlib_FourCC_type_e { - NDIlib_FourCC_type_UYVY = 0x59_56_59_55, - NDIlib_FourCC_type_YV12 = 0x32_31_56_59, - NDIlib_FourCC_type_NV12 = 0x32_31_56_4e, - NDIlib_FourCC_type_I420 = 0x30_32_34_49, - NDIlib_FourCC_type_BGRA = 0x41_52_47_42, - NDIlib_FourCC_type_BGRX = 0x58_52_47_42, - NDIlib_FourCC_type_RGBA = 0x41_42_47_52, - NDIlib_FourCC_type_RGBX = 0x58_42_47_52, - NDIlib_FourCC_type_UYVA = 0x41_56_56_55, -} +pub type NDIlib_FourCC_video_type_e = u32; +pub const NDIlib_FourCC_video_type_UYVY: NDIlib_FourCC_video_type_e = 0x59_56_59_55; +pub const NDIlib_FourCC_video_type_UYVA: NDIlib_FourCC_video_type_e = 0x41_56_56_55; +pub const NDIlib_FourCC_video_type_P216: NDIlib_FourCC_video_type_e = 0x36_31_32_50; +pub const NDIlib_FourCC_video_type_PA16: NDIlib_FourCC_video_type_e = 0x36_31_41_50; +pub const NDIlib_FourCC_video_type_YV12: NDIlib_FourCC_video_type_e = 0x32_31_56_59; +pub const NDIlib_FourCC_video_type_I420: NDIlib_FourCC_video_type_e = 0x30_32_34_49; +pub const NDIlib_FourCC_video_type_NV12: NDIlib_FourCC_video_type_e = 0x32_31_56_4e; +pub const NDIlib_FourCC_video_type_BGRA: NDIlib_FourCC_video_type_e = 0x41_52_47_42; +pub const NDIlib_FourCC_video_type_BGRX: NDIlib_FourCC_video_type_e = 0x58_52_47_42; +pub const NDIlib_FourCC_video_type_RGBA: NDIlib_FourCC_video_type_e = 0x41_42_47_52; +pub const NDIlib_FourCC_video_type_RGBX: NDIlib_FourCC_video_type_e = 0x58_42_47_52; #[repr(u32)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] @@ -133,7 +133,6 @@ pub enum NDIlib_frame_format_type_e { } pub const NDIlib_send_timecode_synthesize: i64 = ::std::i64::MAX; -pub const NDIlib_send_timecode_empty: i64 = 0; pub const NDIlib_recv_timestamp_undefined: i64 = ::std::i64::MAX; #[repr(C)] @@ -143,7 +142,7 @@ pub struct NDIlib_recv_create_v3_t { pub color_format: NDIlib_recv_color_format_e, pub bandwidth: NDIlib_recv_bandwidth_e, pub allow_video_fields: bool, - pub p_ndi_name: *const ::std::os::raw::c_char, + pub p_ndi_recv_name: *const ::std::os::raw::c_char, } pub type NDIlib_recv_instance_t = *mut ::std::os::raw::c_void; @@ -176,14 +175,14 @@ pub struct NDIlib_metadata_frame_t { pub struct NDIlib_video_frame_v2_t { pub xres: ::std::os::raw::c_int, pub yres: ::std::os::raw::c_int, - pub FourCC: NDIlib_FourCC_type_e, + pub FourCC: NDIlib_FourCC_video_type_e, pub frame_rate_N: ::std::os::raw::c_int, pub frame_rate_D: ::std::os::raw::c_int, pub picture_aspect_ratio: ::std::os::raw::c_float, pub frame_format_type: NDIlib_frame_format_type_e, pub timecode: i64, pub p_data: *const ::std::os::raw::c_char, - pub line_stride_in_bytes: ::std::os::raw::c_int, + pub line_stride_or_data_size_in_bytes: ::std::os::raw::c_int, pub p_metadata: *const ::std::os::raw::c_char, pub timestamp: i64, } @@ -221,5 +220,5 @@ pub struct NDIlib_audio_frame_interleaved_16s_t { pub no_samples: ::std::os::raw::c_int, pub timecode: i64, pub reference_level: ::std::os::raw::c_int, - pub p_data: *mut ::std::os::raw::c_short, + pub p_data: *mut i16, } diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index 790385bc..359ede6f 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -25,7 +25,6 @@ use crate::DEFAULT_RECEIVER_NDI_NAME; #[derive(Debug, Clone)] struct Settings { ndi_name: Option, - ip_address: Option, connect_timeout: u32, timeout: u32, receiver_ndi_name: String, @@ -37,7 +36,6 @@ impl Default for Settings { fn default() -> Self { Settings { ndi_name: None, - ip_address: None, receiver_ndi_name: DEFAULT_RECEIVER_NDI_NAME.clone(), connect_timeout: 10000, timeout: 5000, @@ -47,7 +45,7 @@ impl Default for Settings { } } -static PROPERTIES: [subclass::Property; 7] = [ +static PROPERTIES: [subclass::Property; 6] = [ subclass::Property("ndi-name", |name| { glib::ParamSpec::string( name, @@ -57,15 +55,6 @@ static PROPERTIES: [subclass::Property; 7] = [ glib::ParamFlags::READWRITE, ) }), - subclass::Property("ip-address", |name| { - glib::ParamSpec::string( - name, - "IP Address", - "IP address and port of the sender, e.g. 127.0.0.1:5961", - None, - glib::ParamFlags::READWRITE, - ) - }), subclass::Property("receiver-ndi-name", |name| { glib::ParamSpec::string( name, @@ -262,18 +251,6 @@ impl ObjectImpl for NdiVideoSrc { ); settings.ndi_name = ndi_name; } - subclass::Property("ip-address", ..) => { - let mut settings = self.settings.lock().unwrap(); - let ip_address = value.get().unwrap(); - gst_debug!( - self.cat, - obj: basesrc, - "Changing ip from {:?} to {:?}", - settings.ip_address, - ip_address, - ); - settings.ip_address = ip_address; - } subclass::Property("receiver-ndi-name", ..) => { let mut settings = self.settings.lock().unwrap(); let receiver_ndi_name = value.get().unwrap(); @@ -351,10 +328,6 @@ impl ObjectImpl for NdiVideoSrc { let settings = self.settings.lock().unwrap(); Ok(settings.ndi_name.to_value()) } - subclass::Property("ip-address", ..) => { - let settings = self.settings.lock().unwrap(); - Ok(settings.ip_address.to_value()) - } subclass::Property("receiver-ndi-name", ..) => { let settings = self.settings.lock().unwrap(); Ok(settings.receiver_ndi_name.to_value()) @@ -436,18 +409,19 @@ impl BaseSrcImpl for NdiVideoSrc { *self.state.lock().unwrap() = Default::default(); let settings = self.settings.lock().unwrap().clone(); - if settings.ip_address.is_none() && settings.ndi_name.is_none() { + let ndi_name = if let Some(ref ndi_name) = settings.ndi_name { + ndi_name + } else { return Err(gst_error_msg!( gst::LibraryError::Settings, ["No IP address or NDI name given"] )); - } + }; let receiver = connect_ndi( self.cat, element, - settings.ip_address.as_ref().map(String::as_str), - settings.ndi_name.as_ref().map(String::as_str), + ndi_name, &settings.receiver_ndi_name, settings.connect_timeout, settings.bandwidth, diff --git a/src/receiver.rs b/src/receiver.rs index 7b9b0a5c..3b8dab30 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -18,8 +18,7 @@ use super::*; enum ReceiverInfo { Connecting { id: usize, - ndi_name: Option, - ip_address: Option, + ndi_name: String, video: Option>>, audio: Option>>, observations: Observations, @@ -27,7 +26,6 @@ enum ReceiverInfo { Connected { id: usize, ndi_name: String, - ip_address: String, recv: RecvInstance, video: Option>>, audio: Option>>, @@ -557,8 +555,7 @@ impl Drop for ReceiverInner { pub fn connect_ndi( cat: gst::DebugCategory, element: &gst_base::BaseSrc, - ip_address: Option<&str>, - ndi_name: Option<&str>, + ndi_name: &str, receiver_ndi_name: &str, connect_timeout: u32, bandwidth: NDIlib_recv_bandwidth_e, @@ -570,45 +567,33 @@ where { gst_debug!(cat, obj: element, "Starting NDI connection..."); - let ip_address = ip_address.map(str::to_lowercase); - let mut receivers = HASHMAP_RECEIVERS.lock().unwrap(); // Check if we already have a receiver for this very stream for val in receivers.values_mut() { - let (val_audio, val_video, val_ip_address, val_ndi_name) = match val { + let (val_audio, val_video, val_ndi_name) = match val { ReceiverInfo::Connecting { ref mut audio, ref mut video, - ref ip_address, ref ndi_name, .. - } => ( - audio, - video, - ip_address.as_ref(), - ndi_name.as_ref().map(String::as_ref), - ), + } => (audio, video, ndi_name.as_str()), ReceiverInfo::Connected { ref mut audio, ref mut video, - ref ip_address, ref ndi_name, .. - } => (audio, video, Some(ip_address), Some(ndi_name.as_str())), + } => (audio, video, ndi_name.as_str()), }; - if (val_ip_address.is_some() && val_ip_address == ip_address.as_ref()) - || (val_ip_address.is_none() && val_ndi_name == ndi_name) - { + if val_ndi_name == ndi_name { if (val_video.is_some() || !T::IS_VIDEO) && (val_audio.is_some() || T::IS_VIDEO) { gst_element_error!( element, gst::ResourceError::OpenRead, [ - "Source with ndi-name '{:?}' and ip-address '{:?}' already in use for {}", + "Source with ndi-name '{}' already in use for {}", val_ndi_name, - val_ip_address, if T::IS_VIDEO { "video" } else { "audio" } ] ); @@ -624,8 +609,7 @@ where let id_receiver = ID_RECEIVER.fetch_add(1, Ordering::SeqCst); let mut info = ReceiverInfo::Connecting { id: id_receiver, - ndi_name: ndi_name.map(String::from), - ip_address, + ndi_name: String::from(ndi_name), video: None, audio: None, observations: Observations::new(), @@ -746,9 +730,9 @@ fn connect_ndi_async( gst_debug!( cat, obj: element, - "Found source '{}' with IP {}", + "Found source '{}' with URL {}", source.ndi_name(), - source.ip_address(), + source.url_address(), ); } @@ -758,24 +742,20 @@ fn connect_ndi_async( Some(val) => val, }; - let (ndi_name, ip_address) = match info { + let ndi_name = match info { ReceiverInfo::Connecting { ref ndi_name, - ref ip_address, ref audio, ref video, .. } => { assert!(audio.is_some() || video.is_some()); - (ndi_name, ip_address) + ndi_name } ReceiverInfo::Connected { .. } => unreachable!(), }; - let source = sources.iter().find(|s| { - Some(s.ndi_name()) == ndi_name.as_ref().map(String::as_str) - || Some(&s.ip_address().to_lowercase()) == ip_address.as_ref() - }); + let source = sources.iter().find(|s| s.ndi_name() == ndi_name.as_str()); if let Some(source) = source { break source.to_owned(); @@ -793,9 +773,9 @@ fn connect_ndi_async( gst_debug!( cat, obj: element, - "Connecting to NDI source with ndi-name '{}' and ip-address '{}'", + "Connecting to NDI source with ndi-name '{}' and URL {}", source.ndi_name(), - source.ip_address(), + source.url_address(), ); // FIXME: Ideally we would use NDIlib_recv_color_format_fastest here but that seems to be @@ -841,7 +821,6 @@ fn connect_ndi_async( *info = ReceiverInfo::Connected { id: id_receiver, ndi_name: source.ndi_name().to_owned(), - ip_address: source.ip_address().to_lowercase(), recv: recv.clone(), video: video.clone(), audio: audio.clone(), @@ -1252,15 +1231,26 @@ impl Receiver { ) -> Result { // YV12 and I420 are swapped in the NDI SDK compared to GStreamer let format = match video_frame.fourcc() { - ndisys::NDIlib_FourCC_type_e::NDIlib_FourCC_type_UYVY => gst_video::VideoFormat::Uyvy, - ndisys::NDIlib_FourCC_type_e::NDIlib_FourCC_type_YV12 => gst_video::VideoFormat::I420, - ndisys::NDIlib_FourCC_type_e::NDIlib_FourCC_type_NV12 => gst_video::VideoFormat::Nv12, - ndisys::NDIlib_FourCC_type_e::NDIlib_FourCC_type_I420 => gst_video::VideoFormat::Yv12, - ndisys::NDIlib_FourCC_type_e::NDIlib_FourCC_type_BGRA => gst_video::VideoFormat::Bgra, - ndisys::NDIlib_FourCC_type_e::NDIlib_FourCC_type_BGRX => gst_video::VideoFormat::Bgrx, - ndisys::NDIlib_FourCC_type_e::NDIlib_FourCC_type_RGBA => gst_video::VideoFormat::Rgba, - ndisys::NDIlib_FourCC_type_e::NDIlib_FourCC_type_RGBX => gst_video::VideoFormat::Rgbx, - ndisys::NDIlib_FourCC_type_e::NDIlib_FourCC_type_UYVA => gst_video::VideoFormat::Uyvy, + ndisys::NDIlib_FourCC_video_type_UYVY => gst_video::VideoFormat::Uyvy, + // FIXME: This drops the alpha plane! + ndisys::NDIlib_FourCC_video_type_UYVA => gst_video::VideoFormat::Uyvy, + ndisys::NDIlib_FourCC_video_type_YV12 => gst_video::VideoFormat::I420, + ndisys::NDIlib_FourCC_video_type_NV12 => gst_video::VideoFormat::Nv12, + ndisys::NDIlib_FourCC_video_type_I420 => gst_video::VideoFormat::Yv12, + ndisys::NDIlib_FourCC_video_type_BGRA => gst_video::VideoFormat::Bgra, + ndisys::NDIlib_FourCC_video_type_BGRX => gst_video::VideoFormat::Bgrx, + ndisys::NDIlib_FourCC_video_type_RGBA => gst_video::VideoFormat::Rgba, + ndisys::NDIlib_FourCC_video_type_RGBX => gst_video::VideoFormat::Rgbx, + _ => { + gst_element_error!( + element, + gst::StreamError::Format, + ["Unsupported video fourcc {:08x}", video_frame.fourcc()] + ); + + return Err(gst::FlowError::NotNegotiated); + } // TODO: NDIlib_FourCC_video_type_P216 and NDIlib_FourCC_video_type_PA16 not + // supported by GStreamer }; let par = gst::Fraction::approximate_f32(video_frame.picture_aspect_ratio()) @@ -1447,7 +1437,7 @@ impl Receiver { }; let dest_stride = vframe.plane_stride()[0] as usize; let dest = vframe.plane_data_mut(0).unwrap(); - let src_stride = video_frame.line_stride_in_bytes() as usize; + let src_stride = video_frame.line_stride_or_data_size_in_bytes() as usize; let src = video_frame.data(); for (dest, src) in dest @@ -1464,7 +1454,7 @@ impl Receiver { let line_bytes = vframe.width() as usize; let dest_stride = vframe.plane_stride()[0] as usize; let dest = vframe.plane_data_mut(0).unwrap(); - let src_stride = video_frame.line_stride_in_bytes() as usize; + let src_stride = video_frame.line_stride_or_data_size_in_bytes() as usize; let src = video_frame.data(); for (dest, src) in dest @@ -1480,7 +1470,7 @@ impl Receiver { let line_bytes = vframe.width() as usize; let dest_stride = vframe.plane_stride()[1] as usize; let dest = vframe.plane_data_mut(1).unwrap(); - let src_stride = video_frame.line_stride_in_bytes() as usize; + let src_stride = video_frame.line_stride_or_data_size_in_bytes() as usize; let src = &video_frame.data()[(video_frame.yres() as usize * src_stride)..]; for (dest, src) in dest @@ -1497,7 +1487,7 @@ impl Receiver { let line_bytes = vframe.width() as usize; let dest_stride = vframe.plane_stride()[0] as usize; let dest = vframe.plane_data_mut(0).unwrap(); - let src_stride = video_frame.line_stride_in_bytes() as usize; + let src_stride = video_frame.line_stride_or_data_size_in_bytes() as usize; let src = video_frame.data(); for (dest, src) in dest @@ -1513,8 +1503,8 @@ impl Receiver { let line_bytes = (vframe.width() as usize + 1) / 2; let dest_stride = vframe.plane_stride()[1] as usize; let dest = vframe.plane_data_mut(1).unwrap(); - let src_stride = video_frame.line_stride_in_bytes() as usize; - let src_stride1 = video_frame.line_stride_in_bytes() as usize / 2; + let src_stride = video_frame.line_stride_or_data_size_in_bytes() as usize; + let src_stride1 = video_frame.line_stride_or_data_size_in_bytes() as usize / 2; let src = &video_frame.data()[(video_frame.yres() as usize * src_stride)..]; for (dest, src) in dest @@ -1530,8 +1520,8 @@ impl Receiver { let line_bytes = (vframe.width() as usize + 1) / 2; let dest_stride = vframe.plane_stride()[2] as usize; let dest = vframe.plane_data_mut(2).unwrap(); - let src_stride = video_frame.line_stride_in_bytes() as usize; - let src_stride1 = video_frame.line_stride_in_bytes() as usize / 2; + let src_stride = video_frame.line_stride_or_data_size_in_bytes() as usize; + let src_stride1 = video_frame.line_stride_or_data_size_in_bytes() as usize / 2; let src = &video_frame.data()[(video_frame.yres() as usize * src_stride + (video_frame.yres() as usize + 1) / 2 * src_stride1)..]; From f2a4699d1301fd6c4b97524b91594d0ec076cad6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Thu, 16 Jan 2020 10:27:50 +0200 Subject: [PATCH 162/199] Add a device provider for listing available sources Fixes https://github.com/teltek/gst-plugin-ndi/issues/36 --- Cargo.toml | 1 + src/device_provider.rs | 315 +++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 + src/ndi.rs | 7 + 4 files changed, 325 insertions(+) create mode 100644 src/device_provider.rs diff --git a/Cargo.toml b/Cargo.toml index 388e4363..7437f0f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ gstreamer-audio = "0.15" gstreamer-video = { version = "0.15", features = ["v1_12"] } lazy_static = "1.1.0" byte-slice-cast = "0.3.0" +once_cell = "1.0" [build-dependencies] gst-plugin-version-helper = "0.1" diff --git a/src/device_provider.rs b/src/device_provider.rs new file mode 100644 index 00000000..3059e10d --- /dev/null +++ b/src/device_provider.rs @@ -0,0 +1,315 @@ +use glib; +use glib::subclass; +use gst; +use gst::prelude::*; +use gst::subclass::prelude::*; + +use once_cell::sync::OnceCell; + +use std::sync::atomic; +use std::sync::Mutex; +use std::thread; + +use crate::ndi; + +#[derive(Debug)] +struct DeviceProvider { + cat: gst::DebugCategory, + thread: Mutex>>, + current_devices: Mutex>, + find: Mutex>, + is_running: atomic::AtomicBool, +} + +impl ObjectSubclass for DeviceProvider { + const NAME: &'static str = "NdiDeviceProvider"; + type ParentType = gst::DeviceProvider; + type Instance = subclass::simple::InstanceStruct; + type Class = subclass::simple::ClassStruct; + + glib_object_subclass!(); + + fn new() -> Self { + Self { + cat: gst::DebugCategory::new( + "ndideviceprovider", + gst::DebugColorFlags::empty(), + Some("NewTek NDI Device Provider"), + ), + thread: Mutex::new(None), + current_devices: Mutex::new(vec![]), + find: Mutex::new(None), + is_running: atomic::AtomicBool::new(false), + } + } + + fn class_init(klass: &mut subclass::simple::ClassStruct) { + klass.set_metadata( + "NewTek NDI Device Provider", + "Source/Audio/Video/Network", + "NewTek NDI Device Provider", + "Ruben Gonzalez , Daniel Vilar , Sebastian Dröge ", + ); + } +} + +impl ObjectImpl for DeviceProvider { + glib_object_impl!(); +} + +impl DeviceProviderImpl for DeviceProvider { + fn probe(&self, _device_provider: &gst::DeviceProvider) -> Vec { + self.current_devices.lock().unwrap().clone() + } + fn start(&self, device_provider: &gst::DeviceProvider) -> Result<(), gst::LoggableError> { + let mut thread_guard = self.thread.lock().unwrap(); + if thread_guard.is_some() { + gst_log!( + self.cat, + obj: device_provider, + "Device provider already started" + ); + return Ok(()); + } + + self.is_running.store(true, atomic::Ordering::SeqCst); + + let device_provider_weak = device_provider.downgrade(); + let mut first = true; + *thread_guard = Some(thread::spawn(move || { + let device_provider = match device_provider_weak.upgrade() { + None => return, + Some(device_provider) => device_provider, + }; + + let imp = DeviceProvider::from_instance(&device_provider); + { + let mut find_guard = imp.find.lock().unwrap(); + if find_guard.is_some() { + gst_log!(imp.cat, obj: &device_provider, "Already started"); + return; + } + + let find = match ndi::FindInstance::builder().build() { + None => { + gst_error!( + imp.cat, + obj: &device_provider, + "Failed to create Find instance" + ); + return; + } + Some(find) => find, + }; + *find_guard = Some(find); + } + + loop { + let device_provider = match device_provider_weak.upgrade() { + None => break, + Some(device_provider) => device_provider, + }; + + let imp = DeviceProvider::from_instance(&device_provider); + if !imp.is_running.load(atomic::Ordering::SeqCst) { + break; + } + + imp.poll(&device_provider, first); + first = false; + } + })); + + Ok(()) + } + fn stop(&self, _device_provider: &gst::DeviceProvider) { + if let Some(_thread) = self.thread.lock().unwrap().take() { + self.is_running.store(false, atomic::Ordering::SeqCst); + // Don't actually join because that might take a while + } + } +} + +impl DeviceProvider { + fn poll(&self, device_provider: &gst::DeviceProvider, first: bool) { + let mut find_guard = self.find.lock().unwrap(); + let find = match *find_guard { + None => return, + Some(ref mut find) => find, + }; + + if !find.wait_for_sources(if first { 1000 } else { 5000 }) { + gst_trace!(self.cat, obj: device_provider, "No new sources found"); + return; + } + + let sources = find.get_current_sources(); + let mut sources = sources.iter().map(|s| s.to_owned()).collect::>(); + + let mut current_devices_guard = self.current_devices.lock().unwrap(); + let mut expired_devices = vec![]; + let mut remaining_sources = vec![]; + + // First check for each device we previously knew if it's still available + for old_device in &*current_devices_guard { + let old_device_imp = Device::from_instance(old_device); + let old_source = old_device_imp.source.get().unwrap(); + + if !sources.contains(&old_source.0) { + gst_log!( + self.cat, + obj: device_provider, + "Source {:?} disappeared", + old_source + ); + expired_devices.push(old_device.clone()); + } else { + // Otherwise remember that we had it before already and don't have to announce it + // again. After the loop we're going to remove these all from the sources vec. + remaining_sources.push(old_source.0.to_owned()); + } + } + + for remaining_source in remaining_sources { + sources.retain(|s| s != &remaining_source); + } + + // Remove all expired devices from the list of cached devices + current_devices_guard.retain(|d| !expired_devices.contains(d)); + // And also notify the device provider of them having disappeared + for old_device in expired_devices { + device_provider.device_remove(&old_device); + } + + // Now go through all new devices and announce them + for source in sources { + gst_log!( + self.cat, + obj: device_provider, + "Source {:?} appeared", + source + ); + // Add once for audio, another time for video + let device = Device::new(&source, true); + device_provider.device_add(&device); + current_devices_guard.push(device); + + let device = Device::new(&source, false); + device_provider.device_add(&device); + current_devices_guard.push(device); + } + } +} + +#[derive(Debug)] +struct Device { + cat: gst::DebugCategory, + source: OnceCell<(ndi::Source<'static>, glib::Type)>, +} + +impl ObjectSubclass for Device { + const NAME: &'static str = "NdiDevice"; + type ParentType = gst::Device; + type Instance = subclass::simple::InstanceStruct; + type Class = subclass::simple::ClassStruct; + + glib_object_subclass!(); + + fn new() -> Self { + Self { + cat: gst::DebugCategory::new( + "ndidevice", + gst::DebugColorFlags::empty(), + Some("NewTek NDI Device"), + ), + source: OnceCell::new(), + } + } +} + +impl ObjectImpl for Device { + glib_object_impl!(); +} + +impl DeviceImpl for Device { + fn create_element( + &self, + _device: &gst::Device, + name: Option<&str>, + ) -> Result { + let source_info = self.source.get().unwrap(); + let element = glib::Object::new( + source_info.1, + &[ + ("name", &name), + ("ndi-name", &source_info.0.ndi_name()), + ("url-address", &source_info.0.url_address()), + ], + ) + .unwrap() + .dynamic_cast::() + .unwrap(); + + Ok(element) + } +} + +impl Device { + fn new(source: &ndi::Source<'_>, is_audio: bool) -> gst::Device { + let display_name = format!( + "{} ({})", + source.ndi_name(), + if is_audio { "Audio" } else { "Video" } + ); + let device_class = format!( + "Source/{}/Network", + if is_audio { "Audio" } else { "Video" } + ); + + // Get the caps from the template caps of the corresponding source element + let element_type = if is_audio { + crate::ndiaudiosrc::NdiAudioSrc::get_type() + } else { + crate::ndivideosrc::NdiVideoSrc::get_type() + }; + let element_class = gst::ElementClass::from_type(element_type).unwrap(); + let templ = element_class.get_pad_template("src").unwrap(); + let caps = templ.get_caps().unwrap(); + + // Put the url-address into the extra properties + let extra_properties = gst::Structure::builder("properties") + .field("ndi-name", &source.ndi_name()) + .field("url-address", &source.url_address()) + .build(); + + let device = glib::Object::new( + Device::get_type(), + &[ + ("caps", &caps), + ("display-name", &display_name), + ("device-class", &device_class), + ("properties", &extra_properties), + ], + ) + .unwrap() + .dynamic_cast::() + .unwrap(); + let device_impl = Device::from_instance(&device); + + device_impl + .source + .set((source.to_owned(), element_type)) + .unwrap(); + + device + } +} + +pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { + gst::DeviceProvider::register( + Some(plugin), + "ndideviceprovider", + gst::Rank::Primary, + DeviceProvider::get_type(), + ) +} diff --git a/src/lib.rs b/src/lib.rs index 190b4ec5..5f608ad5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,7 @@ extern crate gstreamer_video as gst_video; extern crate lazy_static; extern crate byte_slice_cast; +mod device_provider; pub mod ndi; mod ndiaudiosrc; pub mod ndisys; @@ -39,6 +40,7 @@ fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { ndivideosrc::register(plugin)?; ndiaudiosrc::register(plugin)?; + device_provider::register(plugin)?; Ok(()) } diff --git a/src/ndi.rs b/src/ndi.rs index f17170b8..060e0a45 100644 --- a/src/ndi.rs +++ b/src/ndi.rs @@ -122,6 +122,7 @@ pub enum Source<'a> { } unsafe impl<'a> Send for Source<'a> {} +unsafe impl<'a> Sync for Source<'a> {} impl<'a> Source<'a> { pub fn ndi_name(&self) -> &str { @@ -191,6 +192,12 @@ impl<'a> Source<'a> { } } +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> { source_to_connect_to: &'a Source<'a>, From 8d2c025e47f9c2b20384c324383561c293b7910b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Thu, 16 Jan 2020 11:14:10 +0200 Subject: [PATCH 163/199] Allow creating source elements from the device provider and don't do discovery during element setup anymore While making it possible to create elements from the device provider, this also speeds up the connection generally when starting up the elements. Also add the url-address property for additional filtering in addition to the NDI name. --- src/ndi.rs | 46 ++--- src/ndiaudiosrc.rs | 38 +++- src/ndivideosrc.rs | 40 +++- src/receiver.rs | 449 ++++++++++----------------------------------- 4 files changed, 183 insertions(+), 390 deletions(-) diff --git a/src/ndi.rs b/src/ndi.rs index 060e0a45..516631e1 100644 --- a/src/ndi.rs +++ b/src/ndi.rs @@ -149,24 +149,6 @@ impl<'a> Source<'a> { } } - fn ndi_name_ptr(&self) -> *const ::std::os::raw::c_char { - unsafe { - match *self { - Source::Borrowed(ptr, _) => ptr.as_ref().p_ndi_name, - Source::Owned(_, ref ndi_name, _) => ndi_name.as_ptr(), - } - } - } - - fn url_address_ptr(&self) -> *const ::std::os::raw::c_char { - unsafe { - match *self { - Source::Borrowed(ptr, _) => ptr.as_ref().p_url_address, - Source::Owned(_, _, ref url_address) => url_address.as_ptr(), - } - } - } - pub fn to_owned<'b>(&self) -> Source<'b> { unsafe { let (ndi_name, url_address) = match *self { @@ -200,11 +182,11 @@ impl<'a> PartialEq for Source<'a> { #[derive(Debug)] pub struct RecvBuilder<'a> { - source_to_connect_to: &'a Source<'a>, + source_to_connect_to: (&'a str, Option<&'a str>), allow_video_fields: bool, bandwidth: NDIlib_recv_bandwidth_e, color_format: NDIlib_recv_color_format_e, - ndi_name: &'a str, + ndi_recv_name: &'a str, } impl<'a> RecvBuilder<'a> { @@ -228,16 +210,25 @@ impl<'a> RecvBuilder<'a> { pub fn build(self) -> Option { unsafe { - let ndi_name = ffi::CString::new(self.ndi_name).unwrap(); + let ndi_recv_name = ffi::CString::new(self.ndi_recv_name).unwrap(); + let ndi_name = ffi::CString::new(self.source_to_connect_to.0).unwrap(); + let url_address = self + .source_to_connect_to + .1 + .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: self.source_to_connect_to.ndi_name_ptr(), - p_url_address: self.source_to_connect_to.url_address_ptr(), + p_ndi_name: ndi_name.as_ptr(), + 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_name.as_ptr(), + p_ndi_recv_name: ndi_recv_name.as_ptr(), }); if ptr.is_null() { @@ -266,13 +257,16 @@ unsafe impl Send for RecvInstanceInner {} unsafe impl Sync for RecvInstanceInner {} impl RecvInstance { - pub fn builder<'a>(source_to_connect_to: &'a Source, ndi_name: &'a str) -> RecvBuilder<'a> { + pub fn builder<'a>( + source_to_connect_to: (&'a str, Option<&'a str>), + ndi_recv_name: &'a str, + ) -> RecvBuilder<'a> { RecvBuilder { source_to_connect_to, allow_video_fields: true, bandwidth: NDIlib_recv_bandwidth_highest, color_format: NDIlib_recv_color_format_e::NDIlib_recv_color_format_UYVY_BGRA, - ndi_name, + ndi_recv_name, } } diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index 47a41fdc..884436d5 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -24,6 +24,7 @@ use crate::DEFAULT_RECEIVER_NDI_NAME; #[derive(Debug, Clone)] struct Settings { ndi_name: Option, + url_address: Option, connect_timeout: u32, timeout: u32, receiver_ndi_name: String, @@ -35,6 +36,7 @@ impl Default for Settings { fn default() -> Self { Settings { ndi_name: None, + url_address: None, receiver_ndi_name: DEFAULT_RECEIVER_NDI_NAME.clone(), connect_timeout: 10000, timeout: 5000, @@ -44,7 +46,7 @@ impl Default for Settings { } } -static PROPERTIES: [subclass::Property; 6] = [ +static PROPERTIES: [subclass::Property; 7] = [ subclass::Property("ndi-name", |name| { glib::ParamSpec::string( name, @@ -54,6 +56,15 @@ static PROPERTIES: [subclass::Property; 6] = [ glib::ParamFlags::READWRITE, ) }), + subclass::Property("url-address", |name| { + glib::ParamSpec::string( + name, + "URL/Address", + "URL/address and port of the sender, e.g. 127.0.0.1:5961. This is used as an additional filter together with the NDI name.", + None, + glib::ParamFlags::READWRITE, + ) + }), subclass::Property("receiver-ndi-name", |name| { glib::ParamSpec::string( name, @@ -216,6 +227,18 @@ impl ObjectImpl for NdiAudioSrc { ); settings.ndi_name = ndi_name; } + subclass::Property("url-address", ..) => { + let mut settings = self.settings.lock().unwrap(); + let url_address = value.get().unwrap(); + gst_debug!( + self.cat, + obj: basesrc, + "Changing url-address from {:?} to {:?}", + settings.url_address, + url_address, + ); + settings.url_address = url_address; + } subclass::Property("receiver-ndi-name", ..) => { let mut settings = self.settings.lock().unwrap(); let receiver_ndi_name = value.get().unwrap(); @@ -293,6 +316,10 @@ impl ObjectImpl for NdiAudioSrc { let settings = self.settings.lock().unwrap(); Ok(settings.ndi_name.to_value()) } + subclass::Property("url-address", ..) => { + let settings = self.settings.lock().unwrap(); + Ok(settings.url_address.to_value()) + } subclass::Property("receiver-ndi-name", ..) => { let settings = self.settings.lock().unwrap(); Ok(settings.receiver_ndi_name.to_value()) @@ -374,19 +401,18 @@ impl BaseSrcImpl for NdiAudioSrc { *self.state.lock().unwrap() = Default::default(); let settings = self.settings.lock().unwrap().clone(); - let ndi_name = if let Some(ref ndi_name) = settings.ndi_name { - ndi_name - } else { + if settings.ndi_name.is_none() { return Err(gst_error_msg!( gst::LibraryError::Settings, ["No IP address or NDI name given"] )); - }; + } let receiver = connect_ndi( self.cat, element, - ndi_name, + settings.ndi_name.as_ref().unwrap().as_str(), + settings.url_address.as_ref().map(String::as_str), &settings.receiver_ndi_name, settings.connect_timeout, settings.bandwidth, diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index 359ede6f..c4d048bf 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -25,6 +25,7 @@ use crate::DEFAULT_RECEIVER_NDI_NAME; #[derive(Debug, Clone)] struct Settings { ndi_name: Option, + url_address: Option, connect_timeout: u32, timeout: u32, receiver_ndi_name: String, @@ -36,6 +37,7 @@ impl Default for Settings { fn default() -> Self { Settings { ndi_name: None, + url_address: None, receiver_ndi_name: DEFAULT_RECEIVER_NDI_NAME.clone(), connect_timeout: 10000, timeout: 5000, @@ -45,7 +47,7 @@ impl Default for Settings { } } -static PROPERTIES: [subclass::Property; 6] = [ +static PROPERTIES: [subclass::Property; 7] = [ subclass::Property("ndi-name", |name| { glib::ParamSpec::string( name, @@ -55,6 +57,15 @@ static PROPERTIES: [subclass::Property; 6] = [ glib::ParamFlags::READWRITE, ) }), + subclass::Property("url-address", |name| { + glib::ParamSpec::string( + name, + "URL/Address", + "URL/address and port of the sender, e.g. 127.0.0.1:5961. This is used as an additional filter together with the NDI name.", + None, + glib::ParamFlags::READWRITE, + ) + }), subclass::Property("receiver-ndi-name", |name| { glib::ParamSpec::string( name, @@ -251,6 +262,18 @@ impl ObjectImpl for NdiVideoSrc { ); settings.ndi_name = ndi_name; } + subclass::Property("url-address", ..) => { + let mut settings = self.settings.lock().unwrap(); + let url_address = value.get().unwrap(); + gst_debug!( + self.cat, + obj: basesrc, + "Changing url-address from {:?} to {:?}", + settings.url_address, + url_address, + ); + settings.url_address = url_address; + } subclass::Property("receiver-ndi-name", ..) => { let mut settings = self.settings.lock().unwrap(); let receiver_ndi_name = value.get().unwrap(); @@ -328,6 +351,10 @@ impl ObjectImpl for NdiVideoSrc { let settings = self.settings.lock().unwrap(); Ok(settings.ndi_name.to_value()) } + subclass::Property("url-address", ..) => { + let settings = self.settings.lock().unwrap(); + Ok(settings.url_address.to_value()) + } subclass::Property("receiver-ndi-name", ..) => { let settings = self.settings.lock().unwrap(); Ok(settings.receiver_ndi_name.to_value()) @@ -409,19 +436,18 @@ impl BaseSrcImpl for NdiVideoSrc { *self.state.lock().unwrap() = Default::default(); let settings = self.settings.lock().unwrap().clone(); - let ndi_name = if let Some(ref ndi_name) = settings.ndi_name { - ndi_name - } else { + if settings.ndi_name.is_none() { return Err(gst_error_msg!( gst::LibraryError::Settings, - ["No IP address or NDI name given"] + ["No NDI name given"] )); - }; + } let receiver = connect_ndi( self.cat, element, - ndi_name, + settings.ndi_name.as_ref().unwrap().as_str(), + settings.url_address.as_ref().map(String::as_str), &settings.receiver_ndi_name, settings.connect_timeout, settings.bandwidth, diff --git a/src/receiver.rs b/src/receiver.rs index 3b8dab30..371cdf2b 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -9,28 +9,20 @@ use byte_slice_cast::AsMutSliceOf; use std::cmp; use std::collections::VecDeque; -use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{Arc, Condvar, Mutex, Weak}; use std::thread; use super::*; -enum ReceiverInfo { - Connecting { - id: usize, - ndi_name: String, - video: Option>>, - audio: Option>>, - observations: Observations, - }, - Connected { - id: usize, - ndi_name: String, - recv: RecvInstance, - video: Option>>, - audio: Option>>, - observations: Observations, - }, +pub struct ReceiverInfo { + id: usize, + ndi_name: String, + url_address: Option, + recv: RecvInstance, + video: Option>>, + audio: Option>>, + observations: Observations, } lazy_static! { @@ -81,15 +73,17 @@ pub struct ReceiverInner { queue: ReceiverQueue, - recv: Mutex>, - recv_cond: Condvar, + recv: Mutex, observations: Observations, cat: gst::DebugCategory, element: glib::WeakRef, timestamp_mode: TimestampMode, + + first_frame: AtomicBool, timeout: u32, + connect_timeout: u32, thread: Mutex>>, } @@ -385,32 +379,15 @@ impl Receiver { info: &mut ReceiverInfo, timestamp_mode: TimestampMode, timeout: u32, + connect_timeout: u32, element: &gst_base::BaseSrc, cat: gst::DebugCategory, ) -> Self where Receiver: ReceiverCapture, { - let (id, storage_video, storage_audio, recv, observations) = match info { - ReceiverInfo::Connecting { - id, - ref observations, - ref mut audio, - ref mut video, - .. - } => (*id, video, audio, None, observations), - ReceiverInfo::Connected { - id, - ref mut recv, - ref observations, - ref mut audio, - ref mut video, - .. - } => (*id, video, audio, Some(recv.clone()), observations), - }; - let receiver = Receiver(Arc::new(ReceiverInner { - id, + id: info.id, queue: ReceiverQueue(Arc::new(( Mutex::new(ReceiverQueueInner { capturing: true, @@ -422,13 +399,14 @@ impl Receiver { }), Condvar::new(), ))), - recv: Mutex::new(recv), - recv_cond: Condvar::new(), - observations: observations.clone(), + recv: Mutex::new(info.recv.clone()), + observations: info.observations.clone(), cat, element: element.downgrade(), timestamp_mode, + first_frame: AtomicBool::new(true), timeout, + connect_timeout, thread: Mutex::new(None), })); @@ -459,7 +437,7 @@ impl Receiver { }); let weak = Arc::downgrade(&receiver.0); - Self::store_internal(storage_video, storage_audio, weak); + Self::store_internal(info, weak); *receiver.0.thread.lock().unwrap() = Some(thread); @@ -522,24 +500,12 @@ impl Drop for ReceiverInner { let mut receivers = HASHMAP_RECEIVERS.lock().unwrap(); { - let val = receivers.get_mut(&self.id).unwrap(); - let (audio, video) = match val { - ReceiverInfo::Connecting { - ref mut audio, - ref mut video, - .. - } => (audio, video), - ReceiverInfo::Connected { - ref mut audio, - ref mut video, - .. - } => (audio, video), - }; - if video.is_some() && audio.is_some() { + let receiver = receivers.get_mut(&self.id).unwrap(); + if receiver.audio.is_some() && receiver.video.is_some() { if T::IS_VIDEO { - *video = None; + receiver.video = None; } else { - *audio = None; + receiver.audio = None; } return; } @@ -556,6 +522,7 @@ pub fn connect_ndi( cat: gst::DebugCategory, element: &gst_base::BaseSrc, ndi_name: &str, + url_address: Option<&str>, receiver_ndi_name: &str, connect_timeout: u32, bandwidth: NDIlib_recv_bandwidth_e, @@ -570,227 +537,61 @@ where let mut receivers = HASHMAP_RECEIVERS.lock().unwrap(); // Check if we already have a receiver for this very stream - for val in receivers.values_mut() { - let (val_audio, val_video, val_ndi_name) = match val { - ReceiverInfo::Connecting { - ref mut audio, - ref mut video, - ref ndi_name, - .. - } => (audio, video, ndi_name.as_str()), - ReceiverInfo::Connected { - ref mut audio, - ref mut video, - ref ndi_name, - .. - } => (audio, video, ndi_name.as_str()), - }; - - if val_ndi_name == ndi_name { - if (val_video.is_some() || !T::IS_VIDEO) && (val_audio.is_some() || T::IS_VIDEO) { + for receiver in receivers.values_mut() { + if receiver.ndi_name == ndi_name + && receiver.url_address.as_ref().map(String::as_str) == url_address + { + if (receiver.video.is_some() || !T::IS_VIDEO) + && (receiver.audio.is_some() || T::IS_VIDEO) + { gst_element_error!( element, gst::ResourceError::OpenRead, [ "Source with ndi-name '{}' already in use for {}", - val_ndi_name, + receiver.ndi_name, if T::IS_VIDEO { "video" } else { "audio" } ] ); return None; } else { - return Some(Receiver::new(val, timestamp_mode, timeout, element, cat)); + return Some(Receiver::new( + receiver, + timestamp_mode, + timeout, + connect_timeout, + element, + cat, + )); } } } - // Otherwise asynchronously search for it and return the receiver to the caller - let id_receiver = ID_RECEIVER.fetch_add(1, Ordering::SeqCst); - let mut info = ReceiverInfo::Connecting { - id: id_receiver, - ndi_name: String::from(ndi_name), - video: None, - audio: None, - observations: Observations::new(), - }; - - let receiver = Receiver::new(&mut info, timestamp_mode, timeout, element, cat); - - receivers.insert(id_receiver, info); - - let receiver_ndi_name = String::from(receiver_ndi_name); - let element = element.clone(); - thread::spawn(move || { - use std::panic; - - let res = match panic::catch_unwind(move || { - connect_ndi_async( - cat, - &element, - id_receiver, - receiver_ndi_name, - connect_timeout, - bandwidth, - ) - }) { - Ok(res) => res, - Err(_) => Err(Some(gst_error_msg!( - gst::LibraryError::Failed, - ["Panic while connecting to NDI source"] - ))), - }; - - match res { - Ok(_) => (), - Err(None) => { - gst_debug!(cat, "Shutting down while connecting"); - } - Err(Some(err)) => { - gst_error!(cat, "Error while connecting: {:?}", err); - let mut receivers = HASHMAP_RECEIVERS.lock().unwrap(); - let info = match receivers.get_mut(&id_receiver) { - None => return, - Some(val) => val, - }; - - let (audio, video) = match info { - ReceiverInfo::Connecting { - ref audio, - ref video, - .. - } => (audio, video), - ReceiverInfo::Connected { .. } => unreachable!(), - }; - - assert!(audio.is_some() || video.is_some()); - - if let Some(audio) = audio.as_ref().and_then(|v| v.upgrade()).map(Receiver) { - if let Some(element) = audio.0.element.upgrade() { - element.post_error_message(&err); - } - let audio_recv = audio.0.recv.lock().unwrap(); - let mut queue = (audio.0.queue.0).0.lock().unwrap(); - assert!(audio_recv.is_none()); - queue.error = Some(gst::FlowError::Error); - audio.0.recv_cond.notify_one(); - (audio.0.queue.0).1.notify_one(); - } - - if let Some(video) = video.as_ref().and_then(|v| v.upgrade()).map(Receiver) { - if let Some(element) = video.0.element.upgrade() { - element.post_error_message(&err); - } - let video_recv = video.0.recv.lock().unwrap(); - let mut queue = (video.0.queue.0).0.lock().unwrap(); - assert!(video_recv.is_none()); - queue.error = Some(gst::FlowError::Error); - video.0.recv_cond.notify_one(); - (video.0.queue.0).1.notify_one(); - } - } - } - }); - - Some(receiver) -} - -fn connect_ndi_async( - cat: gst::DebugCategory, - element: &gst_base::BaseSrc, - id_receiver: usize, - receiver_ndi_name: String, - connect_timeout: u32, - bandwidth: NDIlib_recv_bandwidth_e, -) -> Result<(), Option> { - let mut find = match FindInstance::builder().build() { - None => { - return Err(Some(gst_error_msg!( - gst::CoreError::Negotiation, - ["Cannot run NDI: NDIlib_find_create_v2 error"] - ))); - } - Some(find) => find, - }; - - let timer = time::Instant::now(); - let source = loop { - let new_sources = find.wait_for_sources(100); - let sources = find.get_current_sources(); - - gst_debug!( - cat, - obj: element, - "Total sources found in network {}", - sources.len(), - ); - - if new_sources { - for source in &sources { - gst_debug!( - cat, - obj: element, - "Found source '{}' with URL {}", - source.ndi_name(), - source.url_address(), - ); - } - - let receivers = HASHMAP_RECEIVERS.lock().unwrap(); - let info = match receivers.get(&id_receiver) { - None => return Err(None), - Some(val) => val, - }; - - let ndi_name = match info { - ReceiverInfo::Connecting { - ref ndi_name, - ref audio, - ref video, - .. - } => { - assert!(audio.is_some() || video.is_some()); - ndi_name - } - ReceiverInfo::Connected { .. } => unreachable!(), - }; - - let source = sources.iter().find(|s| s.ndi_name() == ndi_name.as_str()); - - if let Some(source) = source { - break source.to_owned(); - } - } - - if timer.elapsed().as_millis() >= connect_timeout as u128 { - return Err(Some(gst_error_msg!( - gst::ResourceError::NotFound, - ["Stream not found"] - ))); - } - }; - + // Otherwise create a new one and return it to the caller gst_debug!( cat, obj: element, - "Connecting to NDI source with ndi-name '{}' and URL {}", - source.ndi_name(), - source.url_address(), + "Connecting to NDI source with ndi-name '{}' and URL/Address {:?}", + ndi_name, + url_address, ); // FIXME: Ideally we would use NDIlib_recv_color_format_fastest here but that seems to be // broken with interlaced content currently - let recv = RecvInstance::builder(&source, &receiver_ndi_name) + let recv = RecvInstance::builder((ndi_name, url_address), &receiver_ndi_name) .bandwidth(bandwidth) .color_format(NDIlib_recv_color_format_e::NDIlib_recv_color_format_UYVY_BGRA) .allow_video_fields(true) .build(); let recv = match recv { None => { - return Err(Some(gst_error_msg!( + gst_element_error!( + element, gst::CoreError::Negotiation, ["Failed to connect to source"] - ))); + ); + return None; } Some(recv) => recv, }; @@ -800,96 +601,36 @@ fn connect_ndi_async( let enable_hw_accel = MetadataFrame::new(0, Some("")); recv.send_metadata(&enable_hw_accel); - let mut receivers = HASHMAP_RECEIVERS.lock().unwrap(); - let info = match receivers.get_mut(&id_receiver) { - None => return Err(None), - Some(val) => val, - }; - - let (audio, video, observations) = match info { - ReceiverInfo::Connecting { - ref audio, - ref video, - ref observations, - .. - } => (audio.clone(), video.clone(), observations), - ReceiverInfo::Connected { .. } => unreachable!(), - }; - - assert!(audio.is_some() || video.is_some()); - - *info = ReceiverInfo::Connected { + let id_receiver = ID_RECEIVER.fetch_add(1, Ordering::SeqCst); + let mut info = ReceiverInfo { id: id_receiver, - ndi_name: source.ndi_name().to_owned(), - recv: recv.clone(), - video: video.clone(), - audio: audio.clone(), - observations: observations.clone(), + ndi_name: String::from(ndi_name), + url_address: url_address.map(String::from), + recv, + video: None, + audio: None, + observations: Observations::new(), }; - gst_debug!(cat, obj: element, "Started NDI connection"); + // This will set info.audio/video accordingly + let receiver = Receiver::new( + &mut info, + timestamp_mode, + timeout, + connect_timeout, + element, + cat, + ); - if let Some(audio) = audio.and_then(|v| v.upgrade()).map(Receiver) { - let mut audio_recv = audio.0.recv.lock().unwrap(); - assert!(audio_recv.is_none()); - *audio_recv = Some(recv.clone()); - audio.0.recv_cond.notify_one(); - } + receivers.insert(id_receiver, info); - if let Some(video) = video.and_then(|v| v.upgrade()).map(Receiver) { - let mut video_recv = video.0.recv.lock().unwrap(); - assert!(video_recv.is_none()); - *video_recv = Some(recv); - video.0.recv_cond.notify_one(); - } - - Ok(()) + Some(receiver) } fn receive_thread(receiver: &Weak>) where Receiver: ReceiverCapture, { - // First loop until we actually are connected, or an error happened - let recv = { - let receiver = match receiver.upgrade().map(Receiver) { - None => return, - Some(receiver) => receiver, - }; - - let element = match receiver.0.element.upgrade() { - None => return, - Some(element) => element, - }; - - let mut recv = receiver.0.recv.lock().unwrap(); - loop { - { - let queue = (receiver.0.queue.0).0.lock().unwrap(); - if !queue.capturing { - gst_debug!(receiver.0.cat, obj: &element, "Shutting down"); - return; - } - - // If an error happened in the meantime, just go out of here - if queue.error.is_some() { - gst_error!( - receiver.0.cat, - obj: &element, - "Error while waiting for connection" - ); - return; - } - } - - if let Some(ref recv) = *recv { - break recv.clone(); - } - - recv = receiver.0.recv_cond.wait(recv).unwrap(); - } - }; - // Now first capture frames until the queues are empty so that we're sure that we output only // the very latest frame that is available now loop { @@ -921,6 +662,8 @@ where } } + let recv = receiver.0.recv.lock().unwrap(); + let queue = recv.get_queue(); if (!T::IS_VIDEO && queue.audio_frames() <= 1) || (T::IS_VIDEO && queue.video_frames() <= 1) { @@ -950,6 +693,8 @@ where } } + let recv = receiver.0.recv.lock().unwrap(); + let res = receiver.capture_internal(&element, &recv); match res { @@ -999,11 +744,7 @@ pub trait ReceiverCapture { recv: &RecvInstance, ) -> Result<(gst::Buffer, T::InfoType), gst::FlowError>; - fn store_internal( - storage_video: &mut Option>>, - storage_audio: &mut Option>>, - weak: Weak>, - ); + fn store_internal(info: &mut ReceiverInfo, weak: Weak>); } impl ReceiverCapture for Receiver { @@ -1015,13 +756,9 @@ impl ReceiverCapture for Receiver { self.capture_video(element, recv) } - fn store_internal( - storage_video: &mut Option>>, - _storage_audio: &mut Option>>, - weak: Weak>, - ) { - assert!(storage_video.is_none()); - *storage_video = Some(weak); + fn store_internal(info: &mut ReceiverInfo, weak: Weak>) { + assert!(info.video.is_none()); + info.video = Some(weak); } } @@ -1109,13 +846,9 @@ impl ReceiverCapture for Receiver { self.capture_audio(element, recv) } - fn store_internal( - _storage_video: &mut Option>>, - storage_audio: &mut Option>>, - weak: Weak>, - ) { - assert!(storage_audio.is_none()); - *storage_audio = Some(weak); + fn store_internal(info: &mut ReceiverInfo, weak: Weak>) { + assert!(info.audio.is_none()); + info.audio = Some(weak); } } @@ -1125,7 +858,12 @@ impl Receiver { element: &gst_base::BaseSrc, recv: &RecvInstance, ) -> Result<(gst::Buffer, gst_video::VideoInfo), gst::FlowError> { - let timeout = time::Instant::now(); + let timer = time::Instant::now(); + let timeout = if self.0.first_frame.load(Ordering::SeqCst) { + self.0.connect_timeout + } else { + self.0.timeout + }; let mut flushing; let mut playing; @@ -1156,7 +894,7 @@ impl Receiver { ); return Err(gst::FlowError::Error); } - Ok(None) if timeout.elapsed().as_millis() >= self.0.timeout as u128 => { + Ok(None) if timer.elapsed().as_millis() >= timeout as u128 => { gst_debug!(self.0.cat, obj: element, "Timed out -- assuming EOS",); return Err(gst::FlowError::Eos); } @@ -1174,6 +912,8 @@ impl Receiver { break video_frame; }; + self.0.first_frame.store(false, Ordering::SeqCst); + gst_debug!( self.0.cat, obj: element, @@ -1546,7 +1286,12 @@ impl Receiver { element: &gst_base::BaseSrc, recv: &RecvInstance, ) -> Result<(gst::Buffer, gst_audio::AudioInfo), gst::FlowError> { - let timeout = time::Instant::now(); + let timer = time::Instant::now(); + let timeout = if self.0.first_frame.load(Ordering::SeqCst) { + self.0.connect_timeout + } else { + self.0.timeout + }; let mut flushing; let mut playing; @@ -1577,7 +1322,7 @@ impl Receiver { ); return Err(gst::FlowError::Error); } - Ok(None) if timeout.elapsed().as_millis() >= self.0.timeout as u128 => { + Ok(None) if timer.elapsed().as_millis() >= timeout as u128 => { gst_debug!(self.0.cat, obj: element, "Timed out -- assuming EOS",); return Err(gst::FlowError::Eos); } @@ -1595,6 +1340,8 @@ impl Receiver { break audio_frame; }; + self.0.first_frame.store(false, Ordering::SeqCst); + gst_debug!( self.0.cat, obj: element, From 4d620cd737c386eac3076dd1dac8d3ae80906a32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Thu, 16 Jan 2020 12:18:57 +0200 Subject: [PATCH 164/199] Allow connecting to either an NDI name or URL/address again Apparently the SDK allows both but the documentation was a bit confusing. --- src/ndi.rs | 22 +++++++++++++++------- src/ndiaudiosrc.rs | 8 ++++---- src/ndivideosrc.rs | 10 ++++++---- src/receiver.rs | 29 +++++++++++++++++++++-------- 4 files changed, 46 insertions(+), 23 deletions(-) diff --git a/src/ndi.rs b/src/ndi.rs index 516631e1..1ff54499 100644 --- a/src/ndi.rs +++ b/src/ndi.rs @@ -182,7 +182,8 @@ impl<'a> PartialEq for Source<'a> { #[derive(Debug)] pub struct RecvBuilder<'a> { - source_to_connect_to: (&'a str, Option<&'a str>), + 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, @@ -211,15 +212,20 @@ impl<'a> RecvBuilder<'a> { pub fn build(self) -> Option { unsafe { let ndi_recv_name = ffi::CString::new(self.ndi_recv_name).unwrap(); - let ndi_name = ffi::CString::new(self.source_to_connect_to.0).unwrap(); + let ndi_name = self + .ndi_name + .as_ref() + .map(|s| ffi::CString::new(*s).unwrap()); let url_address = self - .source_to_connect_to - .1 + .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_ptr(), + 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()) @@ -258,11 +264,13 @@ unsafe impl Sync for RecvInstanceInner {} impl RecvInstance { pub fn builder<'a>( - source_to_connect_to: (&'a str, Option<&'a str>), + ndi_name: Option<&'a str>, + url_address: Option<&'a str>, ndi_recv_name: &'a str, ) -> RecvBuilder<'a> { RecvBuilder { - source_to_connect_to, + ndi_name, + url_address, allow_video_fields: true, bandwidth: NDIlib_recv_bandwidth_highest, color_format: NDIlib_recv_color_format_e::NDIlib_recv_color_format_UYVY_BGRA, diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index 884436d5..cfcf390a 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -60,7 +60,7 @@ static PROPERTIES: [subclass::Property; 7] = [ glib::ParamSpec::string( name, "URL/Address", - "URL/address and port of the sender, e.g. 127.0.0.1:5961. This is used as an additional filter together with the NDI name.", + "URL/address and port of the sender, e.g. 127.0.0.1:5961", None, glib::ParamFlags::READWRITE, ) @@ -401,17 +401,17 @@ impl BaseSrcImpl for NdiAudioSrc { *self.state.lock().unwrap() = Default::default(); let settings = self.settings.lock().unwrap().clone(); - if settings.ndi_name.is_none() { + if settings.ndi_name.is_none() && settings.url_address.is_none() { return Err(gst_error_msg!( gst::LibraryError::Settings, - ["No IP address or NDI name given"] + ["No NDI name or URL/address given"] )); } let receiver = connect_ndi( self.cat, element, - settings.ndi_name.as_ref().unwrap().as_str(), + settings.ndi_name.as_ref().map(String::as_str), settings.url_address.as_ref().map(String::as_str), &settings.receiver_ndi_name, settings.connect_timeout, diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index c4d048bf..e983d9fe 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -61,7 +61,7 @@ static PROPERTIES: [subclass::Property; 7] = [ glib::ParamSpec::string( name, "URL/Address", - "URL/address and port of the sender, e.g. 127.0.0.1:5961. This is used as an additional filter together with the NDI name.", + "URL/address and port of the sender, e.g. 127.0.0.1:5961", None, glib::ParamFlags::READWRITE, ) @@ -436,17 +436,17 @@ impl BaseSrcImpl for NdiVideoSrc { *self.state.lock().unwrap() = Default::default(); let settings = self.settings.lock().unwrap().clone(); - if settings.ndi_name.is_none() { + if settings.ndi_name.is_none() && settings.url_address.is_none() { return Err(gst_error_msg!( gst::LibraryError::Settings, - ["No NDI name given"] + ["No NDI name or URL/address given"] )); } let receiver = connect_ndi( self.cat, element, - settings.ndi_name.as_ref().unwrap().as_str(), + settings.ndi_name.as_ref().map(String::as_str), settings.url_address.as_ref().map(String::as_str), &settings.receiver_ndi_name, settings.connect_timeout, @@ -502,6 +502,8 @@ impl BaseSrcImpl for NdiVideoSrc { let max = 5 * state.current_latency; + println!("Returning latency min {} max {}", min, max,); + gst_debug!( self.cat, obj: element, diff --git a/src/receiver.rs b/src/receiver.rs index 371cdf2b..ba5dc399 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -17,7 +17,7 @@ use super::*; pub struct ReceiverInfo { id: usize, - ndi_name: String, + ndi_name: Option, url_address: Option, recv: RecvInstance, video: Option>>, @@ -521,7 +521,7 @@ impl Drop for ReceiverInner { pub fn connect_ndi( cat: gst::DebugCategory, element: &gst_base::BaseSrc, - ndi_name: &str, + ndi_name: Option<&str>, url_address: Option<&str>, receiver_ndi_name: &str, connect_timeout: u32, @@ -534,12 +534,24 @@ where { gst_debug!(cat, obj: element, "Starting NDI connection..."); + assert!(ndi_name.is_some() || url_address.is_some()); + let mut receivers = HASHMAP_RECEIVERS.lock().unwrap(); // Check if we already have a receiver for this very stream for receiver in receivers.values_mut() { - if receiver.ndi_name == ndi_name - && receiver.url_address.as_ref().map(String::as_str) == url_address + // If both are provided they both must match, if only one is provided + // then that one has to match and the other one does not matter + if (ndi_name.is_some() + && url_address.is_some() + && receiver.ndi_name.as_ref().map(String::as_str) == ndi_name + && receiver.url_address.as_ref().map(String::as_str) == url_address) + || (ndi_name.is_some() + && url_address.is_none() + && receiver.ndi_name.as_ref().map(String::as_str) == ndi_name) + || (ndi_name.is_none() + && url_address.is_some() + && receiver.url_address.as_ref().map(String::as_str) == url_address) { if (receiver.video.is_some() || !T::IS_VIDEO) && (receiver.audio.is_some() || T::IS_VIDEO) @@ -548,8 +560,9 @@ where element, gst::ResourceError::OpenRead, [ - "Source with ndi-name '{}' already in use for {}", + "Source with NDI name '{:?}' / URL/address '{:?}' already in use for {}", receiver.ndi_name, + receiver.url_address, if T::IS_VIDEO { "video" } else { "audio" } ] ); @@ -572,14 +585,14 @@ where gst_debug!( cat, obj: element, - "Connecting to NDI source with ndi-name '{}' and URL/Address {:?}", + "Connecting to NDI source with NDI name '{:?}' and URL/Address {:?}", ndi_name, url_address, ); // FIXME: Ideally we would use NDIlib_recv_color_format_fastest here but that seems to be // broken with interlaced content currently - let recv = RecvInstance::builder((ndi_name, url_address), &receiver_ndi_name) + let recv = RecvInstance::builder(ndi_name, url_address, &receiver_ndi_name) .bandwidth(bandwidth) .color_format(NDIlib_recv_color_format_e::NDIlib_recv_color_format_UYVY_BGRA) .allow_video_fields(true) @@ -604,7 +617,7 @@ where let id_receiver = ID_RECEIVER.fetch_add(1, Ordering::SeqCst); let mut info = ReceiverInfo { id: id_receiver, - ndi_name: String::from(ndi_name), + ndi_name: ndi_name.map(String::from), url_address: url_address.map(String::from), recv, video: None, From 5fa3b689f05b293753bd4b063f56bf71d1e15e05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Mon, 27 Jul 2020 16:08:56 +0300 Subject: [PATCH 165/199] Update to gstreamer-rs 0.16 --- Cargo.toml | 14 +++++++------- src/lib.rs | 6 ++---- src/ndiaudiosrc.rs | 18 ++++++++++-------- src/ndivideosrc.rs | 18 ++++++++++-------- src/receiver.rs | 6 +++--- 5 files changed, 32 insertions(+), 30 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7437f0f8..c0041b98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,18 +8,18 @@ description = "NewTek NDI Plugin" edition = "2018" [dependencies] -glib = "0.9" -gobject-sys = "0.9" -gstreamer = { version = "0.15", features = ["v1_12"] } -gstreamer-base = "0.15" -gstreamer-audio = "0.15" -gstreamer-video = { version = "0.15", features = ["v1_12"] } +glib = "0.10" +gobject-sys = "0.10" +gstreamer = { version = "0.16", features = ["v1_12"] } +gstreamer-base = "0.16" +gstreamer-audio = "0.16" +gstreamer-video = { version = "0.16", features = ["v1_12"] } lazy_static = "1.1.0" byte-slice-cast = "0.3.0" once_cell = "1.0" [build-dependencies] -gst-plugin-version-helper = "0.1" +gst-plugin-version-helper = "0.2" [features] default = ["interlaced-fields", "reference-timestamps"] diff --git a/src/lib.rs b/src/lib.rs index 5f608ad5..21409f15 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,10 +56,8 @@ lazy_static! { #[cfg(feature = "reference-timestamps")] lazy_static! { - static ref TIMECODE_CAPS: gst::Caps = - { gst::Caps::new_simple("timestamp/x-ndi-timecode", &[]) }; - static ref TIMESTAMP_CAPS: gst::Caps = - { gst::Caps::new_simple("timestamp/x-ndi-timestamp", &[]) }; + static ref TIMECODE_CAPS: gst::Caps = gst::Caps::new_simple("timestamp/x-ndi-timecode", &[]); + static ref TIMESTAMP_CAPS: gst::Caps = gst::Caps::new_simple("timestamp/x-ndi-timestamp", &[]); } impl glib::translate::ToGlib for TimestampMode { diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index cfcf390a..343eb547 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -6,6 +6,7 @@ use gst::subclass::prelude::*; use gst_audio; use gst_base; use gst_base::prelude::*; +use gst_base::subclass::base_src::CreateSuccess; use gst_base::subclass::prelude::*; use std::sync::Mutex; @@ -299,8 +300,8 @@ impl ObjectImpl for NdiAudioSrc { timestamp_mode ); if settings.timestamp_mode != timestamp_mode { - let _ = basesrc - .post_message(&gst::Message::new_latency().src(Some(basesrc)).build()); + let _ = + basesrc.post_message(gst::message::Latency::builder().src(basesrc).build()); } settings.timestamp_mode = timestamp_mode; } @@ -484,8 +485,8 @@ impl BaseSrcImpl for NdiAudioSrc { } } - fn fixate(&self, element: &gst_base::BaseSrc, caps: gst::Caps) -> gst::Caps { - let mut caps = gst::Caps::truncate(caps); + fn fixate(&self, element: &gst_base::BaseSrc, mut caps: gst::Caps) -> gst::Caps { + caps.truncate(); { let caps = caps.make_mut(); let s = caps.get_mut_structure(0).unwrap(); @@ -500,8 +501,9 @@ impl BaseSrcImpl for NdiAudioSrc { &self, element: &gst_base::BaseSrc, _offset: u64, + _buffer: Option<&mut gst::BufferRef>, _length: u32, - ) -> Result { + ) -> Result { let recv = { let mut state = self.state.lock().unwrap(); match state.receiver.take() { @@ -539,11 +541,11 @@ impl BaseSrcImpl for NdiAudioSrc { gst::FlowError::NotNegotiated })?; - let _ = element - .post_message(&gst::Message::new_latency().src(Some(element)).build()); + let _ = + element.post_message(gst::message::Latency::builder().src(element).build()); } - Ok(buffer) + Ok(CreateSuccess::NewBuffer(buffer)) } ReceiverItem::Flushing => Err(gst::FlowError::Flushing), ReceiverItem::Timeout => Err(gst::FlowError::Eos), diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index e983d9fe..98ecf10d 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -5,6 +5,7 @@ use gst::prelude::*; use gst::subclass::prelude::*; use gst_base; use gst_base::prelude::*; +use gst_base::subclass::base_src::CreateSuccess; use gst_base::subclass::prelude::*; use gst_video; @@ -334,8 +335,8 @@ impl ObjectImpl for NdiVideoSrc { timestamp_mode ); if settings.timestamp_mode != timestamp_mode { - let _ = basesrc - .post_message(&gst::Message::new_latency().src(Some(basesrc)).build()); + let _ = + basesrc.post_message(gst::message::Latency::builder().src(basesrc).build()); } settings.timestamp_mode = timestamp_mode; } @@ -521,8 +522,8 @@ impl BaseSrcImpl for NdiVideoSrc { } } - fn fixate(&self, element: &gst_base::BaseSrc, caps: gst::Caps) -> gst::Caps { - let mut caps = gst::Caps::truncate(caps); + fn fixate(&self, element: &gst_base::BaseSrc, mut caps: gst::Caps) -> gst::Caps { + caps.truncate(); { let caps = caps.make_mut(); let s = caps.get_mut_structure(0).unwrap(); @@ -541,8 +542,9 @@ impl BaseSrcImpl for NdiVideoSrc { &self, element: &gst_base::BaseSrc, _offset: u64, + _buffer: Option<&mut gst::BufferRef>, _length: u32, - ) -> Result { + ) -> Result { let recv = { let mut state = self.state.lock().unwrap(); match state.receiver.take() { @@ -580,11 +582,11 @@ impl BaseSrcImpl for NdiVideoSrc { gst::FlowError::NotNegotiated })?; - let _ = element - .post_message(&gst::Message::new_latency().src(Some(element)).build()); + let _ = + element.post_message(gst::message::Latency::builder().src(element).build()); } - Ok(buffer) + Ok(CreateSuccess::NewBuffer(buffer)) } ReceiverItem::Timeout => Err(gst::FlowError::Eos), ReceiverItem::Flushing => Err(gst::FlowError::Flushing), diff --git a/src/receiver.rs b/src/receiver.rs index ba5dc399..b84ef613 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -1012,7 +1012,7 @@ impl Receiver { #[cfg(feature = "interlaced-fields")] { - let mut builder = gst_video::VideoInfo::new( + let mut builder = gst_video::VideoInfo::builder( format, video_frame.xres() as u32, video_frame.yres() as u32, @@ -1061,7 +1061,7 @@ impl Receiver { return Err(gst::FlowError::NotNegotiated); } - let mut builder = gst_video::VideoInfo::new( + let mut builder = gst_video::VideoInfo::builder( format, video_frame.xres() as u32, video_frame.yres() as u32, @@ -1410,7 +1410,7 @@ impl Receiver { element: &gst_base::BaseSrc, audio_frame: &AudioFrame, ) -> Result { - let builder = gst_audio::AudioInfo::new( + let builder = gst_audio::AudioInfo::builder( gst_audio::AUDIO_FORMAT_S16, audio_frame.sample_rate() as u32, audio_frame.no_channels() as u32, From 3ea203825a2fcf4a568e6871bdb30552bfbac916 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 1 Sep 2020 21:38:13 +0300 Subject: [PATCH 166/199] Only consider timeouts if configure bigger than 0 Configuring 0 will never time out. --- src/receiver.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/receiver.rs b/src/receiver.rs index ba5dc399..6b5bd1b9 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -907,7 +907,7 @@ impl Receiver { ); return Err(gst::FlowError::Error); } - Ok(None) if timer.elapsed().as_millis() >= timeout as u128 => { + Ok(None) if timeout > 0 && timer.elapsed().as_millis() >= timeout as u128 => { gst_debug!(self.0.cat, obj: element, "Timed out -- assuming EOS",); return Err(gst::FlowError::Eos); } @@ -1335,7 +1335,7 @@ impl Receiver { ); return Err(gst::FlowError::Error); } - Ok(None) if timer.elapsed().as_millis() >= timeout as u128 => { + Ok(None) if timeout > 0 && timer.elapsed().as_millis() >= timeout as u128 => { gst_debug!(self.0.cat, obj: element, "Timed out -- assuming EOS",); return Err(gst::FlowError::Eos); } From eee4a65d199e9a2328400af9ec5e162afc644eff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 23 Feb 2021 12:29:04 +0200 Subject: [PATCH 167/199] Update to byte-slice-cast 1.0 and switch from lazy_static to once_cell --- Cargo.toml | 3 +-- src/lib.rs | 29 ++++++++++++++--------------- src/receiver.rs | 12 ++++++------ 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c0041b98..16732313 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,8 +14,7 @@ gstreamer = { version = "0.16", features = ["v1_12"] } gstreamer-base = "0.16" gstreamer-audio = "0.16" gstreamer-video = { version = "0.16", features = ["v1_12"] } -lazy_static = "1.1.0" -byte-slice-cast = "0.3.0" +byte-slice-cast = "1" once_cell = "1.0" [build-dependencies] diff --git a/src/lib.rs b/src/lib.rs index 21409f15..c00b699a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,8 +7,6 @@ extern crate gstreamer_audio as gst_audio; extern crate gstreamer_base as gst_base; extern crate gstreamer_video as gst_video; -#[macro_use] -extern crate lazy_static; extern crate byte_slice_cast; mod device_provider; @@ -25,6 +23,8 @@ use crate::receiver::*; use std::collections::HashMap; use std::time; +use once_cell::sync::Lazy; + #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] #[repr(u32)] pub enum TimestampMode { @@ -44,21 +44,20 @@ fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { Ok(()) } -lazy_static! { - static ref DEFAULT_RECEIVER_NDI_NAME: String = { - format!( - "GStreamer NDI Source {}-{}", - env!("CARGO_PKG_VERSION"), - env!("COMMIT_ID") - ) - }; -} +static DEFAULT_RECEIVER_NDI_NAME: Lazy = Lazy::new(|| { + format!( + "GStreamer NDI Source {}-{}", + env!("CARGO_PKG_VERSION"), + env!("COMMIT_ID") + ) +}); #[cfg(feature = "reference-timestamps")] -lazy_static! { - static ref TIMECODE_CAPS: gst::Caps = gst::Caps::new_simple("timestamp/x-ndi-timecode", &[]); - static ref TIMESTAMP_CAPS: gst::Caps = gst::Caps::new_simple("timestamp/x-ndi-timestamp", &[]); -} +static TIMECODE_CAPS: Lazy = + Lazy::new(|| gst::Caps::new_simple("timestamp/x-ndi-timecode", &[])); +#[cfg(feature = "reference-timestamps")] +static TIMESTAMP_CAPS: Lazy = + Lazy::new(|| gst::Caps::new_simple("timestamp/x-ndi-timestamp", &[])); impl glib::translate::ToGlib for TimestampMode { type GlibType = i32; diff --git a/src/receiver.rs b/src/receiver.rs index f7db24b2..c7f86154 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -13,6 +13,8 @@ use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{Arc, Condvar, Mutex, Weak}; use std::thread; +use once_cell::sync::Lazy; + use super::*; pub struct ReceiverInfo { @@ -25,12 +27,10 @@ pub struct ReceiverInfo { observations: Observations, } -lazy_static! { - static ref HASHMAP_RECEIVERS: Mutex> = { - let m = HashMap::new(); - Mutex::new(m) - }; -} +static HASHMAP_RECEIVERS: Lazy>> = Lazy::new(|| { + let m = HashMap::new(); + Mutex::new(m) +}); static ID_RECEIVER: AtomicUsize = AtomicUsize::new(0); From 0aef5152a707e1c08182bfdc01534b3333b82260 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 23 Feb 2021 12:37:24 +0200 Subject: [PATCH 168/199] Simplify code for Rust 2018 a bit --- Cargo.toml | 12 ++++++------ src/device_provider.rs | 9 +++++---- src/lib.rs | 13 ++----------- src/ndiaudiosrc.rs | 5 +++-- src/ndivideosrc.rs | 5 +++-- src/receiver.rs | 1 + 6 files changed, 20 insertions(+), 25 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 16732313..5d4d99c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,10 +10,10 @@ edition = "2018" [dependencies] glib = "0.10" gobject-sys = "0.10" -gstreamer = { version = "0.16", features = ["v1_12"] } -gstreamer-base = "0.16" -gstreamer-audio = "0.16" -gstreamer-video = { version = "0.16", features = ["v1_12"] } +gst = { package = "gstreamer", version = "0.16", features = ["v1_12"] } +gst-base = { package = "gstreamer-base", version = "0.16" } +gst-audio = { package = "gstreamer-audio", version = "0.16" } +gst-video = { package = "gstreamer-video", version = "0.16", features = ["v1_12"] } byte-slice-cast = "1" once_cell = "1.0" @@ -22,8 +22,8 @@ gst-plugin-version-helper = "0.2" [features] default = ["interlaced-fields", "reference-timestamps"] -interlaced-fields = ["gstreamer/v1_16", "gstreamer-video/v1_16"] -reference-timestamps = ["gstreamer/v1_14"] +interlaced-fields = ["gst/v1_16", "gst-video/v1_16"] +reference-timestamps = ["gst/v1_14"] [lib] name = "gstndi" diff --git a/src/device_provider.rs b/src/device_provider.rs index 3059e10d..29a3dbd9 100644 --- a/src/device_provider.rs +++ b/src/device_provider.rs @@ -3,6 +3,7 @@ use glib::subclass; use gst; use gst::prelude::*; use gst::subclass::prelude::*; +use gst::{gst_error, gst_log, gst_trace}; use once_cell::sync::OnceCell; @@ -27,7 +28,7 @@ impl ObjectSubclass for DeviceProvider { type Instance = subclass::simple::InstanceStruct; type Class = subclass::simple::ClassStruct; - glib_object_subclass!(); + glib::glib_object_subclass!(); fn new() -> Self { Self { @@ -54,7 +55,7 @@ impl ObjectSubclass for DeviceProvider { } impl ObjectImpl for DeviceProvider { - glib_object_impl!(); + glib::glib_object_impl!(); } impl DeviceProviderImpl for DeviceProvider { @@ -213,7 +214,7 @@ impl ObjectSubclass for Device { type Instance = subclass::simple::InstanceStruct; type Class = subclass::simple::ClassStruct; - glib_object_subclass!(); + glib::glib_object_subclass!(); fn new() -> Self { Self { @@ -228,7 +229,7 @@ impl ObjectSubclass for Device { } impl ObjectImpl for Device { - glib_object_impl!(); + glib::glib_object_impl!(); } impl DeviceImpl for Device { diff --git a/src/lib.rs b/src/lib.rs index c00b699a..6bbaeb3a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,13 +1,4 @@ -#[macro_use] -extern crate glib; use glib::prelude::*; -#[macro_use] -extern crate gstreamer as gst; -extern crate gstreamer_audio as gst_audio; -extern crate gstreamer_base as gst_base; -extern crate gstreamer_video as gst_video; - -extern crate byte_slice_cast; mod device_provider; pub mod ndi; @@ -35,7 +26,7 @@ pub enum TimestampMode { fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { if !ndi::initialize() { - return Err(glib_bool_error!("Cannot initialize NDI")); + return Err(glib::glib_bool_error!("Cannot initialize NDI")); } ndivideosrc::register(plugin)?; @@ -151,7 +142,7 @@ fn timestamp_mode_get_type() -> glib::Type { } } -gst_plugin_define!( +gst::gst_plugin_define!( ndi, env!("CARGO_PKG_DESCRIPTION"), plugin_init, diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index 343eb547..5de8af3b 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -3,6 +3,7 @@ use glib::subclass; use gst; use gst::prelude::*; use gst::subclass::prelude::*; +use gst::{gst_debug, gst_element_error, gst_error, gst_error_msg}; use gst_audio; use gst_base; use gst_base::prelude::*; @@ -149,7 +150,7 @@ impl ObjectSubclass for NdiAudioSrc { type Instance = gst::subclass::ElementInstanceStruct; type Class = subclass::simple::ClassStruct; - glib_object_subclass!(); + glib::glib_object_subclass!(); fn new() -> Self { Self { @@ -199,7 +200,7 @@ impl ObjectSubclass for NdiAudioSrc { } impl ObjectImpl for NdiAudioSrc { - glib_object_impl!(); + glib::glib_object_impl!(); fn constructed(&self, obj: &glib::Object) { self.parent_constructed(obj); diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index 98ecf10d..559e18db 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -3,6 +3,7 @@ use glib::subclass; use gst; use gst::prelude::*; use gst::subclass::prelude::*; +use gst::{gst_debug, gst_element_error, gst_error, gst_error_msg}; use gst_base; use gst_base::prelude::*; use gst_base::subclass::base_src::CreateSuccess; @@ -150,7 +151,7 @@ impl ObjectSubclass for NdiVideoSrc { type Instance = gst::subclass::ElementInstanceStruct; type Class = subclass::simple::ClassStruct; - glib_object_subclass!(); + glib::glib_object_subclass!(); fn new() -> Self { Self { @@ -234,7 +235,7 @@ impl ObjectSubclass for NdiVideoSrc { } impl ObjectImpl for NdiVideoSrc { - glib_object_impl!(); + glib::glib_object_impl!(); fn constructed(&self, obj: &glib::Object) { self.parent_constructed(obj); diff --git a/src/receiver.rs b/src/receiver.rs index c7f86154..8b5b4bc1 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -2,6 +2,7 @@ use glib; use glib::prelude::*; use gst; use gst::prelude::*; +use gst::{gst_debug, gst_element_error, gst_error, gst_log, gst_warning}; use gst_video; use gst_video::prelude::*; From bb1e1b1529efea35e525f98417ec42fbd4eed5ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 23 Feb 2021 12:40:28 +0200 Subject: [PATCH 169/199] Use glib GEnum derive macro instead of implementing this manually --- Cargo.toml | 1 - src/lib.rs | 98 +++--------------------------------------------------- 2 files changed, 5 insertions(+), 94 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5d4d99c1..00f97e71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,6 @@ edition = "2018" [dependencies] glib = "0.10" -gobject-sys = "0.10" gst = { package = "gstreamer", version = "0.16", features = ["v1_12"] } gst-base = { package = "gstreamer-base", version = "0.16" } gst-audio = { package = "gstreamer-audio", version = "0.16" } diff --git a/src/lib.rs b/src/lib.rs index 6bbaeb3a..0a665d18 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,11 +16,15 @@ use std::time; use once_cell::sync::Lazy; -#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] +#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, glib::GEnum)] #[repr(u32)] +#[genum(type_name = "GstNdiTimestampMode")] pub enum TimestampMode { + #[genum(name = "Receive Time", nick = "receive-time")] ReceiveTime = 0, + #[genum(name = "NDI Timecode", nick = "timecode")] Timecode = 1, + #[genum(name = "NDI Timestamp", nick = "timestamp")] Timestamp = 2, } @@ -50,98 +54,6 @@ static TIMECODE_CAPS: Lazy = static TIMESTAMP_CAPS: Lazy = Lazy::new(|| gst::Caps::new_simple("timestamp/x-ndi-timestamp", &[])); -impl glib::translate::ToGlib for TimestampMode { - type GlibType = i32; - - fn to_glib(&self) -> i32 { - *self as i32 - } -} - -impl glib::translate::FromGlib for TimestampMode { - fn from_glib(value: i32) -> Self { - match value { - 0 => TimestampMode::ReceiveTime, - 1 => TimestampMode::Timecode, - 2 => TimestampMode::Timestamp, - _ => unreachable!(), - } - } -} - -impl StaticType for TimestampMode { - fn static_type() -> glib::Type { - timestamp_mode_get_type() - } -} - -impl<'a> glib::value::FromValueOptional<'a> for TimestampMode { - unsafe fn from_value_optional(value: &glib::Value) -> Option { - Some(glib::value::FromValue::from_value(value)) - } -} - -impl<'a> glib::value::FromValue<'a> for TimestampMode { - unsafe fn from_value(value: &glib::Value) -> Self { - use glib::translate::ToGlibPtr; - - glib::translate::from_glib(gobject_sys::g_value_get_enum(value.to_glib_none().0)) - } -} - -impl glib::value::SetValue for TimestampMode { - unsafe fn set_value(value: &mut glib::Value, this: &Self) { - use glib::translate::{ToGlib, ToGlibPtrMut}; - - gobject_sys::g_value_set_enum(value.to_glib_none_mut().0, this.to_glib()) - } -} - -fn timestamp_mode_get_type() -> glib::Type { - use std::sync::Once; - static ONCE: Once = Once::new(); - static mut TYPE: glib::Type = glib::Type::Invalid; - - ONCE.call_once(|| { - use std::ffi; - use std::ptr; - - static mut VALUES: [gobject_sys::GEnumValue; 4] = [ - gobject_sys::GEnumValue { - value: TimestampMode::ReceiveTime as i32, - value_name: b"Receive Time\0" as *const _ as *const _, - value_nick: b"receive-time\0" as *const _ as *const _, - }, - gobject_sys::GEnumValue { - value: TimestampMode::Timecode as i32, - value_name: b"NDI Timecode\0" as *const _ as *const _, - value_nick: b"timecode\0" as *const _ as *const _, - }, - gobject_sys::GEnumValue { - value: TimestampMode::Timestamp as i32, - value_name: b"NDI Timestamp\0" as *const _ as *const _, - value_nick: b"timestamp\0" as *const _ as *const _, - }, - gobject_sys::GEnumValue { - value: 0, - value_name: ptr::null(), - value_nick: ptr::null(), - }, - ]; - - let name = ffi::CString::new("GstNdiTimestampMode").unwrap(); - unsafe { - let type_ = gobject_sys::g_enum_register_static(name.as_ptr(), VALUES.as_ptr()); - TYPE = glib::translate::from_glib(type_); - } - }); - - unsafe { - assert_ne!(TYPE, glib::Type::Invalid); - TYPE - } -} - gst::gst_plugin_define!( ndi, env!("CARGO_PKG_DESCRIPTION"), From 4e93604fe9e5c6913769ef8ba86660402c0dd8ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 23 Feb 2021 12:46:27 +0200 Subject: [PATCH 170/199] Fix various clippy warnings --- src/device_provider.rs | 2 -- src/ndiaudiosrc.rs | 8 ++------ src/ndivideosrc.rs | 8 ++------ src/receiver.rs | 25 +++++++++++-------------- 4 files changed, 15 insertions(+), 28 deletions(-) diff --git a/src/device_provider.rs b/src/device_provider.rs index 29a3dbd9..e2155740 100644 --- a/src/device_provider.rs +++ b/src/device_provider.rs @@ -1,6 +1,4 @@ -use glib; use glib::subclass; -use gst; use gst::prelude::*; use gst::subclass::prelude::*; use gst::{gst_error, gst_log, gst_trace}; diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index 5de8af3b..4a05f441 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -1,11 +1,7 @@ -use glib; use glib::subclass; -use gst; use gst::prelude::*; use gst::subclass::prelude::*; use gst::{gst_debug, gst_element_error, gst_error, gst_error_msg}; -use gst_audio; -use gst_base; use gst_base::prelude::*; use gst_base::subclass::base_src::CreateSuccess; use gst_base::subclass::prelude::*; @@ -413,8 +409,8 @@ impl BaseSrcImpl for NdiAudioSrc { let receiver = connect_ndi( self.cat, element, - settings.ndi_name.as_ref().map(String::as_str), - settings.url_address.as_ref().map(String::as_str), + settings.ndi_name.as_deref(), + settings.url_address.as_deref(), &settings.receiver_ndi_name, settings.connect_timeout, settings.bandwidth, diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index 559e18db..ba358bb4 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -1,14 +1,10 @@ -use glib; use glib::subclass; -use gst; use gst::prelude::*; use gst::subclass::prelude::*; use gst::{gst_debug, gst_element_error, gst_error, gst_error_msg}; -use gst_base; use gst_base::prelude::*; use gst_base::subclass::base_src::CreateSuccess; use gst_base::subclass::prelude::*; -use gst_video; use std::sync::Mutex; use std::{i32, u32}; @@ -448,8 +444,8 @@ impl BaseSrcImpl for NdiVideoSrc { let receiver = connect_ndi( self.cat, element, - settings.ndi_name.as_ref().map(String::as_str), - settings.url_address.as_ref().map(String::as_str), + settings.ndi_name.as_deref(), + settings.url_address.as_deref(), &settings.receiver_ndi_name, settings.connect_timeout, settings.bandwidth, diff --git a/src/receiver.rs b/src/receiver.rs index 8b5b4bc1..b2299620 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -1,9 +1,6 @@ -use glib; use glib::prelude::*; -use gst; use gst::prelude::*; use gst::{gst_debug, gst_element_error, gst_error, gst_log, gst_warning}; -use gst_video; use gst_video::prelude::*; use byte_slice_cast::AsMutSliceOf; @@ -545,14 +542,14 @@ where // then that one has to match and the other one does not matter if (ndi_name.is_some() && url_address.is_some() - && receiver.ndi_name.as_ref().map(String::as_str) == ndi_name - && receiver.url_address.as_ref().map(String::as_str) == url_address) + && receiver.ndi_name.as_deref() == ndi_name + && receiver.url_address.as_deref() == url_address) || (ndi_name.is_some() && url_address.is_none() - && receiver.ndi_name.as_ref().map(String::as_str) == ndi_name) + && receiver.ndi_name.as_deref() == ndi_name) || (ndi_name.is_none() && url_address.is_some() - && receiver.url_address.as_ref().map(String::as_str) == url_address) + && receiver.url_address.as_deref() == url_address) { if (receiver.video.is_some() || !T::IS_VIDEO) && (receiver.audio.is_some() || T::IS_VIDEO) @@ -951,7 +948,7 @@ impl Receiver { let info = self.create_video_info(element, &video_frame)?; - let buffer = self.create_video_buffer(element, pts, duration, &info, &video_frame)?; + let buffer = self.create_video_buffer(element, pts, duration, &info, &video_frame); gst_log!(self.0.cat, obj: element, "Produced buffer {:?}", buffer); @@ -1104,7 +1101,7 @@ impl Receiver { duration: gst::ClockTime, info: &gst_video::VideoInfo, video_frame: &VideoFrame, - ) -> Result { + ) -> gst::Buffer { let mut buffer = gst::Buffer::with_size(info.size()).unwrap(); { let buffer = buffer.get_mut().unwrap(); @@ -1175,7 +1172,7 @@ impl Receiver { info: &gst_video::VideoInfo, buffer: gst::Buffer, video_frame: &VideoFrame, - ) -> Result { + ) -> gst::Buffer { let mut vframe = gst_video::VideoFrame::from_buffer_writable(buffer, info).unwrap(); match info.format() { @@ -1290,7 +1287,7 @@ impl Receiver { _ => unreachable!(), } - Ok(vframe.into_buffer()) + vframe.into_buffer() } } @@ -1379,7 +1376,7 @@ impl Receiver { let info = self.create_audio_info(element, &audio_frame)?; - let buffer = self.create_audio_buffer(element, pts, duration, &info, &audio_frame)?; + let buffer = self.create_audio_buffer(element, pts, duration, &info, &audio_frame); gst_log!(self.0.cat, obj: element, "Produced buffer {:?}", buffer); @@ -1435,7 +1432,7 @@ impl Receiver { duration: gst::ClockTime, info: &gst_audio::AudioInfo, audio_frame: &AudioFrame, - ) -> Result { + ) -> gst::Buffer { // We multiply by 2 because is the size in bytes of an i16 variable let buff_size = (audio_frame.no_samples() as u32 * info.bpf()) as usize; let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); @@ -1472,6 +1469,6 @@ impl Receiver { ); } - Ok(buffer) + buffer } } From 84e4fe7f59df6dd044bd9dbcbdf4621910bb4717 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Thu, 25 Feb 2021 11:31:32 +0200 Subject: [PATCH 171/199] Add new timestamp mode based on the receive time and timecode In addition to the old one based on the receive time and timestamp. Also make that new mode the default as it will usually give more accurate results because the timestamp is just the send time while the timecode is usually set by the sender based on the media timestamps. --- src/lib.rs | 10 ++++++---- src/ndiaudiosrc.rs | 4 ++-- src/ndivideosrc.rs | 4 ++-- src/receiver.rs | 7 ++++++- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0a665d18..8d201a5f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,12 +20,14 @@ use once_cell::sync::Lazy; #[repr(u32)] #[genum(type_name = "GstNdiTimestampMode")] pub enum TimestampMode { - #[genum(name = "Receive Time", nick = "receive-time")] - ReceiveTime = 0, + #[genum(name = "Receive Time / Timecode", nick = "receive-time-vs-timecode")] + ReceiveTimeTimecode = 0, + #[genum(name = "Receive Time / Timestamp", nick = "receive-time-vs-timestamp")] + ReceiveTimeTimestamp = 1, #[genum(name = "NDI Timecode", nick = "timecode")] - Timecode = 1, + Timecode = 2, #[genum(name = "NDI Timestamp", nick = "timestamp")] - Timestamp = 2, + Timestamp = 3, } fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index 4a05f441..c4a0ca0a 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -39,7 +39,7 @@ impl Default for Settings { connect_timeout: 10000, timeout: 5000, bandwidth: ndisys::NDIlib_recv_bandwidth_highest, - timestamp_mode: TimestampMode::ReceiveTime, + timestamp_mode: TimestampMode::ReceiveTimeTimecode, } } } @@ -111,7 +111,7 @@ static PROPERTIES: [subclass::Property; 7] = [ "Timestamp Mode", "Timestamp information to use for outgoing PTS", TimestampMode::static_type(), - TimestampMode::ReceiveTime as i32, + TimestampMode::ReceiveTimeTimecode as i32, glib::ParamFlags::READWRITE, ) }), diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index ba358bb4..e73204c5 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -40,7 +40,7 @@ impl Default for Settings { connect_timeout: 10000, timeout: 5000, bandwidth: ndisys::NDIlib_recv_bandwidth_highest, - timestamp_mode: TimestampMode::ReceiveTime, + timestamp_mode: TimestampMode::ReceiveTimeTimecode, } } } @@ -112,7 +112,7 @@ static PROPERTIES: [subclass::Property; 7] = [ "Timestamp Mode", "Timestamp information to use for outgoing PTS", TimestampMode::static_type(), - TimestampMode::ReceiveTime as i32, + TimestampMode::ReceiveTimeTimecode as i32, glib::ParamFlags::READWRITE, ) }), diff --git a/src/receiver.rs b/src/receiver.rs index b2299620..d88e95f9 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -812,7 +812,12 @@ impl Receiver { ); let (pts, duration) = match self.0.timestamp_mode { - TimestampMode::ReceiveTime => self.0.observations.process( + TimestampMode::ReceiveTimeTimecode => { + self.0 + .observations + .process(self.0.cat, element, (timecode, receive_time), duration) + } + TimestampMode::ReceiveTimeTimestamp => self.0.observations.process( self.0.cat, element, (timestamp, receive_time), From 83962cbb8ca2f69904cbfca7d60f35634b1d8745 Mon Sep 17 00:00:00 2001 From: Luke Moscrop Date: Tue, 23 Feb 2021 13:19:31 +0200 Subject: [PATCH 172/199] Add FFI bindings for NDIlib_send_instance_t and related functions --- src/ndisys.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/ndisys.rs b/src/ndisys.rs index ddadd43d..f1c99133 100644 --- a/src/ndisys.rs +++ b/src/ndisys.rs @@ -62,6 +62,20 @@ extern "C" { p_instance: NDIlib_recv_instance_t, p_total: *mut NDIlib_recv_queue_t, ); + pub fn NDIlib_send_create( + p_create_settings: *const NDIlib_send_create_t + ) -> NDIlib_send_instance_t; + pub fn NDIlib_send_destroy( + p_instance: NDIlib_send_instance_t + ); + pub fn NDIlib_send_send_video_v2( + p_instance: NDIlib_send_instance_t, + p_video_data: *const NDIlib_video_frame_v2_t + ); + pub fn NDIlib_send_send_audio_v2( + p_instance: NDIlib_send_instance_t, + p_audio_data: *const NDIlib_audio_frame_v2_t + ); } pub type NDIlib_find_instance_t = *mut ::std::os::raw::c_void; @@ -147,6 +161,17 @@ pub struct NDIlib_recv_create_v3_t { pub type NDIlib_recv_instance_t = *mut ::std::os::raw::c_void; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct NDIlib_send_create_t { + pub p_ndi_name: *const ::std::os::raw::c_char, + pub p_groups: *const ::std::os::raw::c_char, + pub clock_video: bool, + pub clock_audio: bool, +} + +pub type NDIlib_send_instance_t = *mut ::std::os::raw::c_void; + #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct NDIlib_tally_t { From e7b3b87757e0337d24afeb56be7ba29612b6986b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 23 Feb 2021 15:26:58 +0200 Subject: [PATCH 173/199] Add safe bindings for the send API Including creation of frames from GStreamer buffers. --- src/ndi.rs | 340 +++++++++++++++++++++++++++++++++++++++++++++----- src/ndisys.rs | 10 +- 2 files changed, 313 insertions(+), 37 deletions(-) diff --git a/src/ndi.rs b/src/ndi.rs index 1ff54499..6c963efc 100644 --- a/src/ndi.rs +++ b/src/ndi.rs @@ -5,6 +5,8 @@ use std::mem; use std::ptr; use std::sync::{Arc, Mutex}; +use byte_slice_cast::*; + pub fn initialize() -> bool { unsafe { NDIlib_initialize() } } @@ -339,11 +341,17 @@ impl RecvInstance { match res { NDIlib_frame_type_e::NDIlib_frame_type_audio => { assert!(audio); - Ok(Some(Frame::Audio(AudioFrame::Borrowed(audio_frame, self)))) + Ok(Some(Frame::Audio(AudioFrame::BorrowedRecv( + audio_frame, + self, + )))) } NDIlib_frame_type_e::NDIlib_frame_type_video => { assert!(video); - Ok(Some(Frame::Video(VideoFrame::Borrowed(video_frame, self)))) + Ok(Some(Frame::Video(VideoFrame::BorrowedRecv( + video_frame, + self, + )))) } NDIlib_frame_type_e::NDIlib_frame_type_metadata => { assert!(metadata); @@ -365,6 +373,80 @@ impl Drop for RecvInstanceInner { } } +#[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<'a>(ndi_name: &'a str) -> SendBuilder<'a> { + 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_v2(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 {} @@ -405,49 +487,67 @@ pub enum Frame<'a> { #[derive(Debug)] pub enum VideoFrame<'a> { //Owned(NDIlib_video_frame_v2_t, Option, Option>), - Borrowed(NDIlib_video_frame_v2_t, &'a RecvInstance), + 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::Borrowed(ref frame, _) => frame.xres, + VideoFrame::BorrowedRecv(ref frame, _) | VideoFrame::BorrowedGst(ref frame, _) => { + frame.xres + } } } pub fn yres(&self) -> i32 { match self { - VideoFrame::Borrowed(ref frame, _) => frame.yres, + VideoFrame::BorrowedRecv(ref frame, _) | VideoFrame::BorrowedGst(ref frame, _) => { + frame.yres + } } } pub fn fourcc(&self) -> NDIlib_FourCC_video_type_e { match self { - VideoFrame::Borrowed(ref frame, _) => frame.FourCC, + VideoFrame::BorrowedRecv(ref frame, _) | VideoFrame::BorrowedGst(ref frame, _) => { + frame.FourCC + } } } pub fn frame_rate(&self) -> (i32, i32) { match self { - VideoFrame::Borrowed(ref frame, _) => (frame.frame_rate_N, frame.frame_rate_D), + 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::Borrowed(ref frame, _) => frame.picture_aspect_ratio, + 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::Borrowed(ref frame, _) => frame.frame_format_type, + VideoFrame::BorrowedRecv(ref frame, _) | VideoFrame::BorrowedGst(ref frame, _) => { + frame.frame_format_type + } } } pub fn timecode(&self) -> i64 { match self { - VideoFrame::Borrowed(ref frame, _) => frame.timecode, + VideoFrame::BorrowedRecv(ref frame, _) | VideoFrame::BorrowedGst(ref frame, _) => { + frame.timecode + } } } @@ -467,7 +567,7 @@ impl<'a> VideoFrame<'a> { unsafe { use std::slice; match self { - VideoFrame::Borrowed(ref frame, _) => { + VideoFrame::BorrowedRecv(ref frame, _) | VideoFrame::BorrowedGst(ref frame, _) => { slice::from_raw_parts(frame.p_data as *const u8, frame_size as usize) } } @@ -476,7 +576,7 @@ impl<'a> VideoFrame<'a> { pub fn line_stride_or_data_size_in_bytes(&self) -> i32 { match self { - VideoFrame::Borrowed(ref frame, _) => { + VideoFrame::BorrowedRecv(ref frame, _) | VideoFrame::BorrowedGst(ref frame, _) => { let stride = frame.line_stride_or_data_size_in_bytes; if stride != 0 { @@ -506,7 +606,7 @@ impl<'a> VideoFrame<'a> { pub fn metadata(&self) -> Option<&str> { unsafe { match self { - VideoFrame::Borrowed(ref frame, _) => { + VideoFrame::BorrowedRecv(ref frame, _) | VideoFrame::BorrowedGst(ref frame, _) => { if frame.p_metadata.is_null() { None } else { @@ -519,21 +619,137 @@ impl<'a> VideoFrame<'a> { pub fn timestamp(&self) -> i64 { match self { - VideoFrame::Borrowed(ref frame, _) => frame.timestamp, + VideoFrame::BorrowedRecv(ref frame, _) | VideoFrame::BorrowedGst(ref frame, _) => { + frame.timestamp + } } } pub fn as_ptr(&self) -> *const NDIlib_video_frame_v2_t { match self { - VideoFrame::Borrowed(ref frame, _) => frame, + 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 i8, + 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::Borrowed(ref mut frame, ref recv) = *self { + if let VideoFrame::BorrowedRecv(ref mut frame, ref recv) = *self { unsafe { NDIlib_recv_free_video_v2(((recv.0).0).0.as_ptr() as *mut _, frame); } @@ -543,32 +759,44 @@ impl<'a> Drop for VideoFrame<'a> { #[derive(Debug)] pub enum AudioFrame<'a> { - //Owned(NDIlib_audio_frame_v2_t, Option, Option>), - Borrowed(NDIlib_audio_frame_v2_t, &'a RecvInstance), + Owned( + NDIlib_audio_frame_v2_t, + Option, + Option>, + ), + BorrowedRecv(NDIlib_audio_frame_v2_t, &'a RecvInstance), } impl<'a> AudioFrame<'a> { pub fn sample_rate(&self) -> i32 { match self { - AudioFrame::Borrowed(ref frame, _) => frame.sample_rate, + AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => { + frame.sample_rate + } } } pub fn no_channels(&self) -> i32 { match self { - AudioFrame::Borrowed(ref frame, _) => frame.no_channels, + AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => { + frame.no_channels + } } } pub fn no_samples(&self) -> i32 { match self { - AudioFrame::Borrowed(ref frame, _) => frame.no_samples, + AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => { + frame.no_samples + } } } pub fn timecode(&self) -> i64 { match self { - AudioFrame::Borrowed(ref frame, _) => frame.timecode, + AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => { + frame.timecode + } } } @@ -576,24 +804,28 @@ impl<'a> AudioFrame<'a> { unsafe { use std::slice; match self { - AudioFrame::Borrowed(ref frame, _) => slice::from_raw_parts( - frame.p_data as *const u8, - (frame.no_samples * frame.channel_stride_in_bytes) as usize, - ), + AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => { + slice::from_raw_parts( + frame.p_data as *const u8, + (frame.no_samples * frame.channel_stride_in_bytes) as usize, + ) + } } } } pub fn channel_stride_in_bytes(&self) -> i32 { match self { - AudioFrame::Borrowed(ref frame, _) => frame.channel_stride_in_bytes, + AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => { + frame.channel_stride_in_bytes + } } } pub fn metadata(&self) -> Option<&str> { unsafe { match self { - AudioFrame::Borrowed(ref frame, _) => { + AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => { if frame.p_metadata.is_null() { None } else { @@ -606,13 +838,15 @@ impl<'a> AudioFrame<'a> { pub fn timestamp(&self) -> i64 { match self { - AudioFrame::Borrowed(ref frame, _) => frame.timestamp, + AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => { + frame.timestamp + } } } pub fn as_ptr(&self) -> *const NDIlib_audio_frame_v2_t { match self { - AudioFrame::Borrowed(ref frame, _) => frame, + AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => frame, } } @@ -635,12 +869,56 @@ impl<'a> AudioFrame<'a> { NDIlib_util_audio_to_interleaved_16s_v2(self.as_ptr(), &mut dst); } } + + pub fn try_from_interleaved_16s( + info: &gst_audio::AudioInfo, + buffer: &gst::BufferRef, + timecode: i64, + ) -> Result { + if info.format() != gst_audio::AUDIO_FORMAT_S16 { + return Err(()); + } + + let map = buffer.map_readable().map_err(|_| ())?; + let src_data = map.as_slice_of::().map_err(|_| ())?; + + let src = NDIlib_audio_frame_interleaved_16s_t { + sample_rate: info.rate() as i32, + no_channels: info.channels() as i32, + no_samples: src_data.len() as i32 / info.channels() as i32, + timecode, + reference_level: 0, + p_data: src_data.as_ptr() as *mut i16, + }; + + let channel_stride_in_bytes = src.no_samples * mem::size_of::() as i32; + let mut dest_data = + Vec::with_capacity(channel_stride_in_bytes as usize * info.channels() as usize); + + let mut dest = NDIlib_audio_frame_v2_t { + sample_rate: src.sample_rate, + no_channels: src.no_channels, + no_samples: src.no_samples, + timecode: src.timecode, + p_data: dest_data.as_mut_ptr(), + channel_stride_in_bytes, + p_metadata: ptr::null(), + timestamp: 0, + }; + + unsafe { + NDIlib_util_audio_from_interleaved_16s_v2(&src, &mut dest); + dest_data.set_len(dest_data.capacity()); + } + + 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::Borrowed(ref mut frame, ref recv) = *self { + if let AudioFrame::BorrowedRecv(ref mut frame, ref recv) = *self { unsafe { NDIlib_recv_free_audio_v2(((recv.0).0).0.as_ptr() as *mut _, frame); } diff --git a/src/ndisys.rs b/src/ndisys.rs index f1c99133..c5ad3342 100644 --- a/src/ndisys.rs +++ b/src/ndisys.rs @@ -63,18 +63,16 @@ extern "C" { p_total: *mut NDIlib_recv_queue_t, ); pub fn NDIlib_send_create( - p_create_settings: *const NDIlib_send_create_t + p_create_settings: *const NDIlib_send_create_t, ) -> NDIlib_send_instance_t; - pub fn NDIlib_send_destroy( - p_instance: NDIlib_send_instance_t - ); + pub fn NDIlib_send_destroy(p_instance: NDIlib_send_instance_t); pub fn NDIlib_send_send_video_v2( p_instance: NDIlib_send_instance_t, - p_video_data: *const NDIlib_video_frame_v2_t + p_video_data: *const NDIlib_video_frame_v2_t, ); pub fn NDIlib_send_send_audio_v2( p_instance: NDIlib_send_instance_t, - p_audio_data: *const NDIlib_audio_frame_v2_t + p_audio_data: *const NDIlib_audio_frame_v2_t, ); } From 2345c455c142812974ed1a760bab8c92f5ff6931 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Thu, 25 Feb 2021 21:00:33 +0200 Subject: [PATCH 174/199] Add initial version of NDI sink The sink can accept audio or video directly, or if both should be provided at once it is necesary to use the ndisinkcombiner before the ndisink to merge both audio and video into the same stream. Fixes https://github.com/teltek/gst-plugin-ndi/issues/10 --- Cargo.toml | 3 +- src/lib.rs | 14 +- src/ndisink.rs | 358 +++++++++++++++++++++++ src/ndisinkcombiner.rs | 637 +++++++++++++++++++++++++++++++++++++++++ src/ndisinkmeta.rs | 145 ++++++++++ 5 files changed, 1155 insertions(+), 2 deletions(-) create mode 100644 src/ndisink.rs create mode 100644 src/ndisinkcombiner.rs create mode 100644 src/ndisinkmeta.rs diff --git a/Cargo.toml b/Cargo.toml index 00f97e71..379583f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,9 +20,10 @@ once_cell = "1.0" gst-plugin-version-helper = "0.2" [features] -default = ["interlaced-fields", "reference-timestamps"] +default = ["interlaced-fields", "reference-timestamps", "sink"] interlaced-fields = ["gst/v1_16", "gst-video/v1_16"] reference-timestamps = ["gst/v1_14"] +sink = ["gst/v1_18", "gst-base/v1_18"] [lib] name = "gstndi" diff --git a/src/lib.rs b/src/lib.rs index 8d201a5f..5c0366b9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,12 @@ use glib::prelude::*; mod device_provider; pub mod ndi; mod ndiaudiosrc; +#[cfg(feature = "sink")] +mod ndisink; +#[cfg(feature = "sink")] +mod ndisinkcombiner; +#[cfg(feature = "sink")] +pub mod ndisinkmeta; pub mod ndisys; mod ndivideosrc; pub mod receiver; @@ -35,9 +41,15 @@ fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { return Err(glib::glib_bool_error!("Cannot initialize NDI")); } + device_provider::register(plugin)?; + ndivideosrc::register(plugin)?; ndiaudiosrc::register(plugin)?; - device_provider::register(plugin)?; + #[cfg(feature = "sink")] + { + ndisinkcombiner::register(plugin)?; + ndisink::register(plugin)?; + } Ok(()) } diff --git a/src/ndisink.rs b/src/ndisink.rs new file mode 100644 index 00000000..b4f5a0bb --- /dev/null +++ b/src/ndisink.rs @@ -0,0 +1,358 @@ +use glib::subclass; +use glib::subclass::prelude::*; +use gst::prelude::*; +use gst::subclass::prelude::*; +use gst::{gst_debug, gst_error, gst_error_msg, gst_info, gst_loggable_error, gst_trace}; +use gst_base::{subclass::prelude::*, BaseSinkExtManual}; + +use std::sync::Mutex; + +use once_cell::sync::Lazy; + +use super::ndi::SendInstance; + +static DEFAULT_SENDER_NDI_NAME: Lazy = Lazy::new(|| { + format!( + "GStreamer NDI Sink {}-{}", + env!("CARGO_PKG_VERSION"), + env!("COMMIT_ID") + ) +}); + +#[derive(Debug)] +struct Settings { + ndi_name: String, +} + +impl Default for Settings { + fn default() -> Self { + Settings { + ndi_name: DEFAULT_SENDER_NDI_NAME.clone(), + } + } +} + +static PROPERTIES: [subclass::Property; 1] = [subclass::Property("ndi-name", |name| { + glib::ParamSpec::string( + name, + "NDI Name", + "NDI Name to use", + Some(DEFAULT_SENDER_NDI_NAME.as_ref()), + glib::ParamFlags::READWRITE, + ) +})]; + +struct State { + send: SendInstance, + video_info: Option, + audio_info: Option, +} + +pub struct NdiSink { + settings: Mutex, + state: Mutex>, +} + +static CAT: Lazy = Lazy::new(|| { + gst::DebugCategory::new("ndisink", gst::DebugColorFlags::empty(), Some("NDI Sink")) +}); + +impl ObjectSubclass for NdiSink { + const NAME: &'static str = "NdiSink"; + type ParentType = gst_base::BaseSink; + type Instance = gst::subclass::ElementInstanceStruct; + type Class = subclass::simple::ClassStruct; + + glib::glib_object_subclass!(); + + fn new() -> Self { + Self { + settings: Mutex::new(Default::default()), + state: Mutex::new(Default::default()), + } + } + + fn class_init(klass: &mut subclass::simple::ClassStruct) { + klass.set_metadata( + "NDI Sink", + "Sink/Audio/Video", + "Render as an NDI stream", + "Sebastian Dröge ", + ); + + let caps = gst::Caps::builder_full() + .structure( + gst::Structure::builder("video/x-raw") + .field( + "format", + &gst::List::new(&[ + &gst_video::VideoFormat::Uyvy.to_str(), + &gst_video::VideoFormat::I420.to_str(), + &gst_video::VideoFormat::Nv12.to_str(), + &gst_video::VideoFormat::Nv21.to_str(), + &gst_video::VideoFormat::Yv12.to_str(), + &gst_video::VideoFormat::Bgra.to_str(), + &gst_video::VideoFormat::Bgrx.to_str(), + &gst_video::VideoFormat::Rgba.to_str(), + &gst_video::VideoFormat::Rgbx.to_str(), + ]), + ) + .field("width", &gst::IntRange::::new(1, std::i32::MAX)) + .field("height", &gst::IntRange::::new(1, std::i32::MAX)) + .field( + "framerate", + &gst::FractionRange::new( + gst::Fraction::new(0, 1), + gst::Fraction::new(std::i32::MAX, 1), + ), + ) + .build(), + ) + .structure( + gst::Structure::builder("audio/x-raw") + .field("format", &gst_audio::AUDIO_FORMAT_S16.to_str()) + .field("rate", &gst::IntRange::::new(1, i32::MAX)) + .field("channels", &gst::IntRange::::new(1, i32::MAX)) + .field("layout", &"interleaved") + .build(), + ) + .build(); + + let sink_pad_template = gst::PadTemplate::new( + "sink", + gst::PadDirection::Sink, + gst::PadPresence::Always, + &caps, + ) + .unwrap(); + klass.add_pad_template(sink_pad_template); + + klass.install_properties(&PROPERTIES); + } +} + +impl ObjectImpl for NdiSink { + glib::glib_object_impl!(); + + fn set_property(&self, _obj: &glib::Object, id: usize, value: &glib::Value) { + let prop = &PROPERTIES[id]; + match *prop { + subclass::Property("ndi-name", ..) => { + let mut settings = self.settings.lock().unwrap(); + settings.ndi_name = value + .get::() + .unwrap() + .unwrap_or_else(|| DEFAULT_SENDER_NDI_NAME.clone()); + } + _ => unimplemented!(), + }; + } + + fn get_property(&self, _obj: &glib::Object, id: usize) -> Result { + let prop = &PROPERTIES[id]; + match *prop { + subclass::Property("ndi-name", ..) => { + let settings = self.settings.lock().unwrap(); + Ok(settings.ndi_name.to_value()) + } + _ => unimplemented!(), + } + } +} + +impl ElementImpl for NdiSink {} + +impl BaseSinkImpl for NdiSink { + fn start(&self, element: &gst_base::BaseSink) -> Result<(), gst::ErrorMessage> { + let mut state_storage = self.state.lock().unwrap(); + let settings = self.settings.lock().unwrap(); + + let send = SendInstance::builder(&settings.ndi_name) + .build() + .ok_or_else(|| { + gst_error_msg!( + gst::ResourceError::OpenWrite, + ["Could not create send instance"] + ) + })?; + + let state = State { + send, + video_info: None, + audio_info: None, + }; + *state_storage = Some(state); + gst_info!(CAT, obj: element, "Started"); + + Ok(()) + } + + fn stop(&self, element: &gst_base::BaseSink) -> Result<(), gst::ErrorMessage> { + let mut state_storage = self.state.lock().unwrap(); + + *state_storage = None; + gst_info!(CAT, obj: element, "Stopped"); + + Ok(()) + } + + fn unlock(&self, _element: &gst_base::BaseSink) -> Result<(), gst::ErrorMessage> { + Ok(()) + } + + fn unlock_stop(&self, _element: &gst_base::BaseSink) -> Result<(), gst::ErrorMessage> { + Ok(()) + } + + fn set_caps( + &self, + element: &gst_base::BaseSink, + caps: &gst::Caps, + ) -> Result<(), gst::LoggableError> { + gst_debug!(CAT, obj: element, "Setting caps {}", caps); + + let mut state_storage = self.state.lock().unwrap(); + let state = match &mut *state_storage { + None => return Err(gst_loggable_error!(CAT, "Sink not started yet")), + Some(ref mut state) => state, + }; + + let s = caps.get_structure(0).unwrap(); + if s.get_name() == "video/x-raw" { + let info = gst_video::VideoInfo::from_caps(caps) + .map_err(|_| gst_loggable_error!(CAT, "Couldn't parse caps {}", caps))?; + + state.video_info = Some(info); + state.audio_info = None; + } else { + let info = gst_audio::AudioInfo::from_caps(caps) + .map_err(|_| gst_loggable_error!(CAT, "Couldn't parse caps {}", caps))?; + + state.audio_info = Some(info); + state.video_info = None; + } + + Ok(()) + } + + fn render( + &self, + element: &gst_base::BaseSink, + buffer: &gst::Buffer, + ) -> Result { + let mut state_storage = self.state.lock().unwrap(); + let state = match &mut *state_storage { + None => return Err(gst::FlowError::Error), + Some(ref mut state) => state, + }; + + if let Some(ref info) = state.video_info { + if let Some(audio_meta) = buffer.get_meta::() { + for (buffer, info, timecode) in audio_meta.buffers() { + let frame = + crate::ndi::AudioFrame::try_from_interleaved_16s(info, buffer, *timecode) + .map_err(|_| { + gst_error!(CAT, obj: element, "Unsupported audio frame"); + gst::FlowError::NotNegotiated + })?; + + gst_trace!( + CAT, + obj: element, + "Sending audio buffer {:?} with timecode {} and format {:?}", + buffer, + if *timecode < 0 { + gst::CLOCK_TIME_NONE + } else { + gst::ClockTime::from(*timecode as u64 * 100) + }, + info, + ); + state.send.send_audio(&frame); + } + } + + // Skip empty/gap buffers from ndisinkcombiner + if buffer.get_size() != 0 { + let timecode = element + .get_segment() + .downcast::() + .ok() + .and_then(|segment| { + *(segment.to_running_time(buffer.get_pts()) + element.get_base_time()) + }) + .map(|time| (time / 100) as i64) + .unwrap_or(crate::ndisys::NDIlib_send_timecode_synthesize); + + let frame = gst_video::VideoFrameRef::from_buffer_ref_readable(buffer, info) + .map_err(|_| { + gst_error!(CAT, obj: element, "Failed to map buffer"); + gst::FlowError::Error + })?; + + let frame = crate::ndi::VideoFrame::try_from_video_frame(&frame, timecode) + .map_err(|_| { + gst_error!(CAT, obj: element, "Unsupported video frame"); + gst::FlowError::NotNegotiated + })?; + + gst_trace!( + CAT, + obj: element, + "Sending video buffer {:?} with timecode {} and format {:?}", + buffer, + if timecode < 0 { + gst::CLOCK_TIME_NONE + } else { + gst::ClockTime::from(timecode as u64 * 100) + }, + info + ); + state.send.send_video(&frame); + } + } else if let Some(ref info) = state.audio_info { + let timecode = element + .get_segment() + .downcast::() + .ok() + .and_then(|segment| { + *(segment.to_running_time(buffer.get_pts()) + element.get_base_time()) + }) + .map(|time| (time / 100) as i64) + .unwrap_or(crate::ndisys::NDIlib_send_timecode_synthesize); + + let frame = crate::ndi::AudioFrame::try_from_interleaved_16s(info, buffer, timecode) + .map_err(|_| { + gst_error!(CAT, obj: element, "Unsupported audio frame"); + gst::FlowError::NotNegotiated + })?; + + gst_trace!( + CAT, + obj: element, + "Sending audio buffer {:?} with timecode {} and format {:?}", + buffer, + if timecode < 0 { + gst::CLOCK_TIME_NONE + } else { + gst::ClockTime::from(timecode as u64 * 100) + }, + info, + ); + state.send.send_audio(&frame); + } else { + return Err(gst::FlowError::Error); + } + + Ok(gst::FlowSuccess::Ok) + } +} + +pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { + gst::Element::register( + Some(plugin), + "ndisink", + gst::Rank::None, + NdiSink::get_type(), + ) +} diff --git a/src/ndisinkcombiner.rs b/src/ndisinkcombiner.rs new file mode 100644 index 00000000..ddc91798 --- /dev/null +++ b/src/ndisinkcombiner.rs @@ -0,0 +1,637 @@ +use glib::prelude::*; +use glib::subclass; +use glib::subclass::prelude::*; +use gst::prelude::*; +use gst::subclass::prelude::*; +use gst::{gst_debug, gst_error, gst_trace, gst_warning}; +use gst_base::prelude::*; +use gst_base::subclass::prelude::*; + +use std::mem; +use std::sync::Mutex; + +static CAT: once_cell::sync::Lazy = once_cell::sync::Lazy::new(|| { + gst::DebugCategory::new( + "ndisinkcombiner", + gst::DebugColorFlags::empty(), + Some("NDI sink audio/video combiner"), + ) +}); + +struct State { + // Note that this applies to the currently pending buffer on the pad and *not* + // to the current_video_buffer below! + video_info: Option, + audio_info: Option, + current_video_buffer: Option<(gst::Buffer, gst::ClockTime)>, + current_audio_buffers: Vec<(gst::Buffer, gst_audio::AudioInfo, i64)>, +} + +struct NdiSinkCombiner { + video_pad: gst_base::AggregatorPad, + audio_pad: Mutex>, + state: Mutex>, +} + +impl ObjectSubclass for NdiSinkCombiner { + const NAME: &'static str = "NdiSinkCombiner"; + type ParentType = gst_base::Aggregator; + type Instance = gst::subclass::ElementInstanceStruct; + type Class = subclass::simple::ClassStruct; + + glib::glib_object_subclass!(); + + fn class_init(klass: &mut subclass::simple::ClassStruct) { + klass.set_metadata( + "NDI Sink Combiner", + "Combiner/Audio/Video", + "NDI sink audio/video combiner", + "Sebastian Dröge ", + ); + + let caps = gst::Caps::builder("video/x-raw") + .field( + "format", + &gst::List::new(&[ + &gst_video::VideoFormat::Uyvy.to_str(), + &gst_video::VideoFormat::I420.to_str(), + &gst_video::VideoFormat::Nv12.to_str(), + &gst_video::VideoFormat::Nv21.to_str(), + &gst_video::VideoFormat::Yv12.to_str(), + &gst_video::VideoFormat::Bgra.to_str(), + &gst_video::VideoFormat::Bgrx.to_str(), + &gst_video::VideoFormat::Rgba.to_str(), + &gst_video::VideoFormat::Rgbx.to_str(), + ]), + ) + .field("width", &gst::IntRange::::new(1, i32::MAX)) + .field("height", &gst::IntRange::::new(1, i32::MAX)) + .field( + "framerate", + &gst::FractionRange::new( + gst::Fraction::new(1, i32::MAX), + gst::Fraction::new(i32::MAX, 1), + ), + ) + .build(); + let src_pad_template = gst::PadTemplate::with_gtype( + "src", + gst::PadDirection::Src, + gst::PadPresence::Always, + &caps, + gst_base::AggregatorPad::static_type(), + ) + .unwrap(); + klass.add_pad_template(src_pad_template); + + let sink_pad_template = gst::PadTemplate::with_gtype( + "video", + gst::PadDirection::Sink, + gst::PadPresence::Always, + &caps, + gst_base::AggregatorPad::static_type(), + ) + .unwrap(); + klass.add_pad_template(sink_pad_template); + + let caps = gst::Caps::builder("audio/x-raw") + .field("format", &gst_audio::AUDIO_FORMAT_S16.to_str()) + .field("rate", &gst::IntRange::::new(1, i32::MAX)) + .field("channels", &gst::IntRange::::new(1, i32::MAX)) + .field("layout", &"interleaved") + .build(); + let sink_pad_template = gst::PadTemplate::with_gtype( + "audio", + gst::PadDirection::Sink, + gst::PadPresence::Request, + &caps, + gst_base::AggregatorPad::static_type(), + ) + .unwrap(); + klass.add_pad_template(sink_pad_template); + } + + fn with_class(klass: &Self::Class) -> Self { + let templ = klass.get_pad_template("video").unwrap(); + let video_pad = + gst::PadBuilder::::from_template(&templ, Some("video")) + .build(); + + Self { + video_pad, + audio_pad: Mutex::new(None), + state: Mutex::new(None), + } + } +} + +impl ObjectImpl for NdiSinkCombiner { + glib::glib_object_impl!(); + + fn constructed(&self, obj: &glib::Object) { + let element = obj.downcast_ref::().unwrap(); + element.add_pad(&self.video_pad).unwrap(); + + self.parent_constructed(obj); + } +} + +impl ElementImpl for NdiSinkCombiner { + fn release_pad(&self, element: &gst::Element, pad: &gst::Pad) { + let mut audio_pad_storage = self.audio_pad.lock().unwrap(); + + if audio_pad_storage.as_ref().map(|p| p.upcast_ref()) == Some(pad) { + gst_debug!(CAT, obj: element, "Release audio pad"); + self.parent_release_pad(element, pad); + *audio_pad_storage = None; + } + } +} + +impl AggregatorImpl for NdiSinkCombiner { + fn create_new_pad( + &self, + agg: &gst_base::Aggregator, + templ: &gst::PadTemplate, + _req_name: Option<&str>, + _caps: Option<&gst::Caps>, + ) -> Option { + let mut audio_pad_storage = self.audio_pad.lock().unwrap(); + + if audio_pad_storage.is_some() { + gst_error!(CAT, obj: agg, "Audio pad already requested"); + return None; + } + + let sink_templ = agg.get_pad_template("audio").unwrap(); + if templ != &sink_templ { + gst_error!(CAT, obj: agg, "Wrong pad template"); + return None; + } + + let pad = + gst::PadBuilder::::from_template(templ, Some("audio")).build(); + *audio_pad_storage = Some(pad.clone()); + + gst_debug!(CAT, obj: agg, "Requested audio pad"); + + Some(pad) + } + + fn start(&self, agg: &gst_base::Aggregator) -> Result<(), gst::ErrorMessage> { + let mut state_storage = self.state.lock().unwrap(); + *state_storage = Some(State { + audio_info: None, + video_info: None, + current_video_buffer: None, + current_audio_buffers: Vec::new(), + }); + + gst_debug!(CAT, obj: agg, "Started"); + + Ok(()) + } + + fn stop(&self, agg: &gst_base::Aggregator) -> Result<(), gst::ErrorMessage> { + // Drop our state now + let _ = self.state.lock().unwrap().take(); + + gst_debug!(CAT, obj: agg, "Stopped"); + + Ok(()) + } + + fn get_next_time(&self, _agg: &gst_base::Aggregator) -> gst::ClockTime { + // FIXME: What to do here? We don't really know when the next buffer is expected + gst::CLOCK_TIME_NONE + } + + fn clip( + &self, + agg: &gst_base::Aggregator, + agg_pad: &gst_base::AggregatorPad, + mut buffer: gst::Buffer, + ) -> Option { + let segment = match agg_pad.get_segment().downcast::() { + Ok(segment) => segment, + Err(_) => { + gst_error!(CAT, obj: agg, "Only TIME segments supported"); + return Some(buffer); + } + }; + + let pts = buffer.get_pts(); + if pts.is_none() { + gst_error!(CAT, obj: agg, "Only buffers with PTS supported"); + return Some(buffer); + } + + let duration = if buffer.get_duration().is_some() { + buffer.get_duration() + } else { + gst::CLOCK_TIME_NONE + }; + + gst_trace!( + CAT, + obj: agg_pad, + "Clipping buffer {:?} with PTS {} and duration {}", + buffer, + pts, + duration + ); + + let state_storage = self.state.lock().unwrap(); + let state = match &*state_storage { + Some(ref state) => state, + None => return None, + }; + + let duration = if buffer.get_duration().is_some() { + buffer.get_duration() + } else if let Some(ref audio_info) = state.audio_info { + gst::SECOND + .mul_div_floor( + buffer.get_size() as u64, + audio_info.rate() as u64 * audio_info.bpf() as u64, + ) + .unwrap() + } else if let Some(ref video_info) = state.video_info { + if *video_info.fps().numer() > 0 { + gst::SECOND + .mul_div_floor( + *video_info.fps().denom() as u64, + *video_info.fps().numer() as u64, + ) + .unwrap() + } else { + gst::CLOCK_TIME_NONE + } + } else { + unreachable!() + }; + + gst_debug!( + CAT, + obj: agg_pad, + "Clipping buffer {:?} with PTS {} and duration {}", + buffer, + pts, + duration + ); + + if agg_pad == &self.video_pad { + segment.clip(pts, pts + duration).map(|(start, stop)| { + { + let buffer = buffer.make_mut(); + buffer.set_pts(start); + if duration.is_some() { + buffer.set_duration(stop - start); + } + } + + buffer + }) + } else if let Some(ref audio_info) = state.audio_info { + gst_audio::audio_buffer_clip( + buffer, + segment.upcast_ref(), + audio_info.rate(), + audio_info.bpf(), + ) + } else { + // Can't really have audio buffers without caps + unreachable!(); + } + } + + fn aggregate( + &self, + agg: &gst_base::Aggregator, + timeout: bool, + ) -> Result { + // FIXME: Can't really happen because we always return NONE from get_next_time() but that + // should be improved! + assert!(!timeout); + + // Because peek_buffer() can call into clip() and that would take the state lock again, + // first try getting buffers from both pads here + let video_buffer_and_segment = match self.video_pad.peek_buffer() { + Some(video_buffer) => { + let video_segment = self.video_pad.get_segment(); + let video_segment = match video_segment.downcast::() { + Ok(video_segment) => video_segment, + Err(video_segment) => { + gst_error!( + CAT, + obj: agg, + "Video segment of wrong format {:?}", + video_segment.get_format() + ); + return Err(gst::FlowError::Error); + } + }; + + Some((video_buffer, video_segment)) + } + None if !self.video_pad.is_eos() => { + gst_trace!(CAT, obj: agg, "Waiting for video buffer"); + return Err(gst_base::AGGREGATOR_FLOW_NEED_DATA); + } + None => None, + }; + + let audio_buffer_segment_and_pad; + if let Some(audio_pad) = self.audio_pad.lock().unwrap().clone() { + audio_buffer_segment_and_pad = match audio_pad.peek_buffer() { + Some(audio_buffer) if audio_buffer.get_size() == 0 => { + // Skip empty/gap audio buffer + audio_pad.drop_buffer(); + gst_trace!(CAT, obj: agg, "Empty audio buffer, waiting for next"); + return Err(gst_base::AGGREGATOR_FLOW_NEED_DATA); + } + Some(audio_buffer) => { + let audio_segment = audio_pad.get_segment(); + let audio_segment = match audio_segment.downcast::() { + Ok(audio_segment) => audio_segment, + Err(audio_segment) => { + gst_error!( + CAT, + obj: agg, + "Audio segment of wrong format {:?}", + audio_segment.get_format() + ); + return Err(gst::FlowError::Error); + } + }; + + Some((audio_buffer, audio_segment, audio_pad)) + } + None if !audio_pad.is_eos() => { + gst_trace!(CAT, obj: agg, "Waiting for audio buffer"); + return Err(gst_base::AGGREGATOR_FLOW_NEED_DATA); + } + None => None, + }; + } else { + audio_buffer_segment_and_pad = None; + } + + let mut state_storage = self.state.lock().unwrap(); + let state = match &mut *state_storage { + Some(ref mut state) => state, + None => return Err(gst::FlowError::Flushing), + }; + + let (mut current_video_buffer, current_video_running_time_end, next_video_buffer) = + if let Some((video_buffer, video_segment)) = video_buffer_and_segment { + let video_running_time = video_segment.to_running_time(video_buffer.get_pts()); + assert!(video_running_time.is_some()); + + match state.current_video_buffer { + None => { + gst_trace!(CAT, obj: agg, "First video buffer, waiting for second"); + state.current_video_buffer = Some((video_buffer, video_running_time)); + drop(state_storage); + self.video_pad.drop_buffer(); + return Err(gst_base::AGGREGATOR_FLOW_NEED_DATA); + } + Some((ref buffer, _)) => ( + buffer.clone(), + video_running_time, + Some((video_buffer, video_running_time)), + ), + } + } else { + match (&state.current_video_buffer, &audio_buffer_segment_and_pad) { + (None, None) => { + gst_trace!( + CAT, + obj: agg, + "All pads are EOS and no buffers are queued, finishing" + ); + return Err(gst::FlowError::Eos); + } + (None, Some((ref audio_buffer, ref audio_segment, _))) => { + // Create an empty dummy buffer for attaching the audio. This is going to + // be dropped by the sink later. + let audio_running_time = + audio_segment.to_running_time(audio_buffer.get_pts()); + assert!(audio_running_time.is_some()); + + let video_segment = self.video_pad.get_segment(); + let video_segment = match video_segment.downcast::() { + Ok(video_segment) => video_segment, + Err(video_segment) => { + gst_error!( + CAT, + obj: agg, + "Video segment of wrong format {:?}", + video_segment.get_format() + ); + return Err(gst::FlowError::Error); + } + }; + let video_pts = + video_segment.position_from_running_time(audio_running_time); + if video_pts.is_none() { + gst_warning!(CAT, obj: agg, "Can't output more audio after video EOS"); + return Err(gst::FlowError::Eos); + } + + let mut buffer = gst::Buffer::new(); + { + let buffer = buffer.get_mut().unwrap(); + buffer.set_pts(video_pts); + } + + (buffer, gst::CLOCK_TIME_NONE, None) + } + (Some((ref buffer, _)), _) => (buffer.clone(), gst::CLOCK_TIME_NONE, None), + } + }; + + if let Some((audio_buffer, audio_segment, audio_pad)) = audio_buffer_segment_and_pad { + let audio_info = match state.audio_info { + Some(ref audio_info) => audio_info, + None => { + gst_error!(CAT, obj: agg, "Have no audio caps"); + return Err(gst::FlowError::NotNegotiated); + } + }; + + let audio_running_time = audio_segment.to_running_time(audio_buffer.get_pts()); + assert!(audio_running_time.is_some()); + let duration = gst::SECOND + .mul_div_floor( + audio_buffer.get_size() as u64 / audio_info.bpf() as u64, + audio_info.rate() as u64, + ) + .unwrap_or(gst::CLOCK_TIME_NONE); + let audio_running_time_end = audio_running_time + duration; + assert!(audio_running_time_end.is_some()); + + if audio_running_time_end <= current_video_running_time_end + || current_video_running_time_end.is_none() + { + let timecode = (audio_running_time + agg.get_base_time()) + .map(|t| (t / 100) as i64) + .unwrap_or(crate::ndisys::NDIlib_send_timecode_synthesize); + + gst_trace!( + CAT, + obj: agg, + "Including audio buffer {:?} with timecode {}: {} <= {}", + audio_buffer, + timecode, + audio_running_time_end, + current_video_running_time_end, + ); + state + .current_audio_buffers + .push((audio_buffer, audio_info.clone(), timecode)); + audio_pad.drop_buffer(); + + // If there is still video data, wait for the next audio buffer or EOS, + // otherwise just output the dummy video buffer directly. + if current_video_running_time_end.is_some() { + return Err(gst_base::AGGREGATOR_FLOW_NEED_DATA); + } + } + + // Otherwise finish this video buffer with all audio that has accumulated so + // far + } + + let audio_buffers = mem::replace(&mut state.current_audio_buffers, Vec::new()); + + if !audio_buffers.is_empty() { + let current_video_buffer = current_video_buffer.make_mut(); + crate::ndisinkmeta::NdiSinkAudioMeta::add(current_video_buffer, audio_buffers); + } + + if let Some((video_buffer, video_running_time)) = next_video_buffer { + state.current_video_buffer = Some((video_buffer, video_running_time)); + drop(state_storage); + self.video_pad.drop_buffer(); + } else { + state.current_video_buffer = None; + drop(state_storage); + } + + gst_trace!( + CAT, + obj: agg, + "Finishing video buffer {:?}", + current_video_buffer + ); + agg.finish_buffer(current_video_buffer) + } + + fn sink_event( + &self, + agg: &gst_base::Aggregator, + pad: &gst_base::AggregatorPad, + event: gst::Event, + ) -> bool { + use gst::EventView; + + match event.view() { + EventView::Caps(caps) => { + let caps = caps.get_caps_owned(); + + let mut state_storage = self.state.lock().unwrap(); + let state = match &mut *state_storage { + Some(ref mut state) => state, + None => return false, + }; + + if pad == &self.video_pad { + let info = match gst_video::VideoInfo::from_caps(&caps) { + Ok(info) => info, + Err(_) => { + gst_error!(CAT, obj: pad, "Failed to parse caps {:?}", caps); + return false; + } + }; + + // 2 frames latency because we queue 1 frame and wait until audio + // up to the end of that frame has arrived. + let latency = if *info.fps().numer() > 0 { + gst::SECOND + .mul_div_floor( + 2 * *info.fps().denom() as u64, + *info.fps().numer() as u64, + ) + .unwrap_or(80 * gst::MSECOND) + } else { + // let's assume 25fps and 2 frames latency + 80 * gst::MSECOND + }; + + state.video_info = Some(info); + + drop(state_storage); + + agg.set_latency(latency, gst::CLOCK_TIME_NONE); + + // The video caps are passed through as the audio is included only in a meta + agg.set_src_caps(&caps); + } else { + let info = match gst_audio::AudioInfo::from_caps(&caps) { + Ok(info) => info, + Err(_) => { + gst_error!(CAT, obj: pad, "Failed to parse caps {:?}", caps); + return false; + } + }; + + state.audio_info = Some(info); + } + } + // The video segment is passed through as-is and the video timestamps are preserved + EventView::Segment(segment) if pad == &self.video_pad => { + let segment = segment.get_segment(); + gst_debug!(CAT, obj: agg, "Updating segment {:?}", segment); + agg.update_segment(segment); + } + _ => (), + } + + self.parent_sink_event(agg, pad, event) + } + + fn sink_query( + &self, + agg: &gst_base::Aggregator, + pad: &gst_base::AggregatorPad, + query: &mut gst::QueryRef, + ) -> bool { + use gst::QueryView; + + match query.view_mut() { + QueryView::Caps(_) if pad == &self.video_pad => { + // Directly forward caps queries + let srcpad = agg.get_static_pad("src").unwrap(); + return srcpad.peer_query(query); + } + _ => (), + } + + self.parent_sink_query(agg, pad, query) + } + + fn negotiate(&self, _agg: &gst_base::Aggregator) -> bool { + // No negotiation needed as the video caps are just passed through + true + } +} + +pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { + gst::Element::register( + Some(plugin), + "ndisinkcombiner", + gst::Rank::None, + NdiSinkCombiner::get_type(), + ) +} diff --git a/src/ndisinkmeta.rs b/src/ndisinkmeta.rs new file mode 100644 index 00000000..f53449ec --- /dev/null +++ b/src/ndisinkmeta.rs @@ -0,0 +1,145 @@ +use gst::gst_sys; +use gst::prelude::*; +use std::fmt; +use std::mem; + +#[repr(transparent)] +pub struct NdiSinkAudioMeta(imp::NdiSinkAudioMeta); + +unsafe impl Send for NdiSinkAudioMeta {} +unsafe impl Sync for NdiSinkAudioMeta {} + +impl NdiSinkAudioMeta { + pub fn add( + buffer: &mut gst::BufferRef, + buffers: Vec<(gst::Buffer, gst_audio::AudioInfo, i64)>, + ) -> gst::MetaRefMut { + unsafe { + // Manually dropping because gst_buffer_add_meta() takes ownership of the + // content of the struct + let mut params = mem::ManuallyDrop::new(imp::NdiSinkAudioMetaParams { buffers }); + + let meta = gst_sys::gst_buffer_add_meta( + buffer.as_mut_ptr(), + imp::ndi_sink_audio_meta_get_info(), + &mut *params as *mut imp::NdiSinkAudioMetaParams as glib::glib_sys::gpointer, + ) as *mut imp::NdiSinkAudioMeta; + + Self::from_mut_ptr(buffer, meta) + } + } + + pub fn buffers(&self) -> &[(gst::Buffer, gst_audio::AudioInfo, i64)] { + &self.0.buffers + } +} + +unsafe impl MetaAPI for NdiSinkAudioMeta { + type GstType = imp::NdiSinkAudioMeta; + + fn get_meta_api() -> glib::Type { + imp::ndi_sink_audio_meta_api_get_type() + } +} + +impl fmt::Debug for NdiSinkAudioMeta { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("NdiSinkAudioMeta") + .field("buffers", &self.buffers()) + .finish() + } +} + +mod imp { + use glib::glib_sys; + use glib::translate::*; + use gst::gst_sys; + use once_cell::sync::Lazy; + use std::mem; + use std::ptr; + + pub(super) struct NdiSinkAudioMetaParams { + pub buffers: Vec<(gst::Buffer, gst_audio::AudioInfo, i64)>, + } + + #[repr(C)] + pub struct NdiSinkAudioMeta { + parent: gst_sys::GstMeta, + pub(super) buffers: Vec<(gst::Buffer, gst_audio::AudioInfo, i64)>, + } + + pub(super) fn ndi_sink_audio_meta_api_get_type() -> glib::Type { + static TYPE: Lazy = Lazy::new(|| unsafe { + let t = from_glib(gst_sys::gst_meta_api_type_register( + b"GstNdiSinkAudioMetaAPI\0".as_ptr() as *const _, + [ptr::null::()].as_ptr() as *mut *const _, + )); + + assert_ne!(t, glib::Type::Invalid); + + t + }); + + *TYPE + } + + unsafe extern "C" fn ndi_sink_audio_meta_init( + meta: *mut gst_sys::GstMeta, + params: glib_sys::gpointer, + _buffer: *mut gst_sys::GstBuffer, + ) -> glib_sys::gboolean { + assert!(!params.is_null()); + + let meta = &mut *(meta as *mut NdiSinkAudioMeta); + let params = ptr::read(params as *const NdiSinkAudioMetaParams); + + ptr::write(&mut meta.buffers, params.buffers); + + true.to_glib() + } + + unsafe extern "C" fn ndi_sink_audio_meta_free( + meta: *mut gst_sys::GstMeta, + _buffer: *mut gst_sys::GstBuffer, + ) { + let meta = &mut *(meta as *mut NdiSinkAudioMeta); + + ptr::drop_in_place(&mut meta.buffers); + } + + unsafe extern "C" fn ndi_sink_audio_meta_transform( + dest: *mut gst_sys::GstBuffer, + meta: *mut gst_sys::GstMeta, + _buffer: *mut gst_sys::GstBuffer, + _type_: glib_sys::GQuark, + _data: glib_sys::gpointer, + ) -> glib_sys::gboolean { + let meta = &*(meta as *mut NdiSinkAudioMeta); + + super::NdiSinkAudioMeta::add(gst::BufferRef::from_mut_ptr(dest), meta.buffers.clone()); + + true.to_glib() + } + + pub(super) fn ndi_sink_audio_meta_get_info() -> *const gst_sys::GstMetaInfo { + struct MetaInfo(ptr::NonNull); + unsafe impl Send for MetaInfo {} + unsafe impl Sync for MetaInfo {} + + static META_INFO: Lazy = Lazy::new(|| unsafe { + MetaInfo( + ptr::NonNull::new(gst_sys::gst_meta_register( + ndi_sink_audio_meta_api_get_type().to_glib(), + b"GstNdiSinkAudioMeta\0".as_ptr() as *const _, + mem::size_of::(), + Some(ndi_sink_audio_meta_init), + Some(ndi_sink_audio_meta_free), + Some(ndi_sink_audio_meta_transform), + ) as *mut gst_sys::GstMetaInfo) + .expect("Failed to register meta API"), + ) + }); + + META_INFO.0.as_ptr() + } +} From 7604a0c5964b45cf56fd0de24ec9780f5e8b1935 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Mon, 2 Aug 2021 08:45:32 +0300 Subject: [PATCH 175/199] Make the maximum receive queue length size configurable --- src/ndiaudiosrc.rs | 32 +++++++++++++++++++++++++++++++- src/ndivideosrc.rs | 32 +++++++++++++++++++++++++++++++- src/receiver.rs | 10 ++++++++-- 3 files changed, 70 insertions(+), 4 deletions(-) diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc.rs index c4a0ca0a..b4cff6fa 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc.rs @@ -25,6 +25,7 @@ struct Settings { url_address: Option, connect_timeout: u32, timeout: u32, + max_queue_length: u32, receiver_ndi_name: String, bandwidth: ndisys::NDIlib_recv_bandwidth_e, timestamp_mode: TimestampMode, @@ -38,13 +39,14 @@ impl Default for Settings { receiver_ndi_name: DEFAULT_RECEIVER_NDI_NAME.clone(), connect_timeout: 10000, timeout: 5000, + max_queue_length: 5, bandwidth: ndisys::NDIlib_recv_bandwidth_highest, timestamp_mode: TimestampMode::ReceiveTimeTimecode, } } } -static PROPERTIES: [subclass::Property; 7] = [ +static PROPERTIES: [subclass::Property; 8] = [ subclass::Property("ndi-name", |name| { glib::ParamSpec::string( name, @@ -94,6 +96,17 @@ static PROPERTIES: [subclass::Property; 7] = [ glib::ParamFlags::READWRITE, ) }), + subclass::Property("max-queue-length", |name| { + glib::ParamSpec::uint( + name, + "Max Queue Length", + "Maximum receive queue length", + 0, + u32::MAX, + 5, + glib::ParamFlags::READWRITE, + ) + }), subclass::Property("bandwidth", |name| { glib::ParamSpec::int( name, @@ -274,6 +287,18 @@ impl ObjectImpl for NdiAudioSrc { ); settings.timeout = timeout; } + subclass::Property("max-queue-length", ..) => { + let mut settings = self.settings.lock().unwrap(); + let max_queue_length = value.get_some().unwrap(); + gst_debug!( + self.cat, + obj: basesrc, + "Changing max-queue-length from {} to {}", + settings.max_queue_length, + max_queue_length, + ); + settings.max_queue_length = max_queue_length; + } subclass::Property("bandwidth", ..) => { let mut settings = self.settings.lock().unwrap(); let bandwidth = value.get_some().unwrap(); @@ -330,6 +355,10 @@ impl ObjectImpl for NdiAudioSrc { let settings = self.settings.lock().unwrap(); Ok(settings.timeout.to_value()) } + subclass::Property("max-queue-length", ..) => { + let settings = self.settings.lock().unwrap(); + Ok(settings.max_queue_length.to_value()) + } subclass::Property("bandwidth", ..) => { let settings = self.settings.lock().unwrap(); Ok(settings.bandwidth.to_value()) @@ -416,6 +445,7 @@ impl BaseSrcImpl for NdiAudioSrc { settings.bandwidth, settings.timestamp_mode, settings.timeout, + settings.max_queue_length as usize, ); // settings.id_receiver exists diff --git a/src/ndivideosrc.rs b/src/ndivideosrc.rs index e73204c5..7e41d231 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc.rs @@ -26,6 +26,7 @@ struct Settings { url_address: Option, connect_timeout: u32, timeout: u32, + max_queue_length: u32, receiver_ndi_name: String, bandwidth: ndisys::NDIlib_recv_bandwidth_e, timestamp_mode: TimestampMode, @@ -39,13 +40,14 @@ impl Default for Settings { receiver_ndi_name: DEFAULT_RECEIVER_NDI_NAME.clone(), connect_timeout: 10000, timeout: 5000, + max_queue_length: 5, bandwidth: ndisys::NDIlib_recv_bandwidth_highest, timestamp_mode: TimestampMode::ReceiveTimeTimecode, } } } -static PROPERTIES: [subclass::Property; 7] = [ +static PROPERTIES: [subclass::Property; 8] = [ subclass::Property("ndi-name", |name| { glib::ParamSpec::string( name, @@ -95,6 +97,17 @@ static PROPERTIES: [subclass::Property; 7] = [ glib::ParamFlags::READWRITE, ) }), + subclass::Property("max-queue-length", |name| { + glib::ParamSpec::uint( + name, + "Max Queue Length", + "Maximum receive queue length", + 0, + u32::MAX, + 5, + glib::ParamFlags::READWRITE, + ) + }), subclass::Property("bandwidth", |name| { glib::ParamSpec::int( name, @@ -309,6 +322,18 @@ impl ObjectImpl for NdiVideoSrc { ); settings.timeout = timeout; } + subclass::Property("max-queue-length", ..) => { + let mut settings = self.settings.lock().unwrap(); + let max_queue_length = value.get_some().unwrap(); + gst_debug!( + self.cat, + obj: basesrc, + "Changing max-queue-length from {} to {}", + settings.max_queue_length, + max_queue_length, + ); + settings.max_queue_length = max_queue_length; + } subclass::Property("bandwidth", ..) => { let mut settings = self.settings.lock().unwrap(); let bandwidth = value.get_some().unwrap(); @@ -365,6 +390,10 @@ impl ObjectImpl for NdiVideoSrc { let settings = self.settings.lock().unwrap(); Ok(settings.timeout.to_value()) } + subclass::Property("max-queue-length", ..) => { + let settings = self.settings.lock().unwrap(); + Ok(settings.max_queue_length.to_value()) + } subclass::Property("bandwidth", ..) => { let settings = self.settings.lock().unwrap(); Ok(settings.bandwidth.to_value()) @@ -451,6 +480,7 @@ impl BaseSrcImpl for NdiVideoSrc { settings.bandwidth, settings.timestamp_mode, settings.timeout, + settings.max_queue_length as usize, ); // settings.id_receiver exists diff --git a/src/receiver.rs b/src/receiver.rs index d88e95f9..dccb1907 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -70,6 +70,7 @@ pub struct ReceiverInner { id: usize, queue: ReceiverQueue, + max_queue_length: usize, recv: Mutex, @@ -378,6 +379,7 @@ impl Receiver { timestamp_mode: TimestampMode, timeout: u32, connect_timeout: u32, + max_queue_length: usize, element: &gst_base::BaseSrc, cat: gst::DebugCategory, ) -> Self @@ -391,12 +393,13 @@ impl Receiver { capturing: true, playing: false, flushing: false, - buffer_queue: VecDeque::with_capacity(5), + buffer_queue: VecDeque::with_capacity(max_queue_length), error: None, timeout: false, }), Condvar::new(), ))), + max_queue_length, recv: Mutex::new(info.recv.clone()), observations: info.observations.clone(), cat, @@ -526,6 +529,7 @@ pub fn connect_ndi( bandwidth: NDIlib_recv_bandwidth_e, timestamp_mode: TimestampMode, timeout: u32, + max_queue_length: usize, ) -> Option> where Receiver: ReceiverCapture, @@ -572,6 +576,7 @@ where timestamp_mode, timeout, connect_timeout, + max_queue_length, element, cat, )); @@ -629,6 +634,7 @@ where timestamp_mode, timeout, connect_timeout, + max_queue_length, element, cat, ); @@ -711,7 +717,7 @@ where match res { Ok(item) => { let mut queue = (receiver.0.queue.0).0.lock().unwrap(); - while queue.buffer_queue.len() > 5 { + while queue.buffer_queue.len() > receiver.0.max_queue_length { gst_warning!( receiver.0.cat, obj: &element, From 291d951b01bfc79b88b286855862cea471396bb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Mon, 13 Sep 2021 12:26:56 +0300 Subject: [PATCH 176/199] Update to gstreamer-rs 0.17 --- Cargo.toml | 12 +- build.rs | 4 +- .../imp.rs} | 114 ++--- src/device_provider/mod.rs | 26 + src/lib.rs | 6 +- src/ndi.rs | 8 +- src/{ndiaudiosrc.rs => ndiaudiosrc/imp.rs} | 410 ++++++++------- src/ndiaudiosrc/mod.rs | 19 + src/{ndisink.rs => ndisink/imp.rs} | 260 +++++----- src/ndisink/mod.rs | 19 + .../imp.rs} | 340 +++++++------ src/ndisinkcombiner/mod.rs | 19 + src/ndisinkmeta.rs | 53 +- src/{ndivideosrc.rs => ndivideosrc/imp.rs} | 478 +++++++++--------- src/ndivideosrc/mod.rs | 19 + src/receiver.rs | 241 +++++---- 16 files changed, 1036 insertions(+), 992 deletions(-) rename src/{device_provider.rs => device_provider/imp.rs} (78%) create mode 100644 src/device_provider/mod.rs rename src/{ndiaudiosrc.rs => ndiaudiosrc/imp.rs} (62%) create mode 100644 src/ndiaudiosrc/mod.rs rename src/{ndisink.rs => ndisink/imp.rs} (52%) create mode 100644 src/ndisink/mod.rs rename src/{ndisinkcombiner.rs => ndisinkcombiner/imp.rs} (69%) create mode 100644 src/ndisinkcombiner/mod.rs rename src/{ndivideosrc.rs => ndivideosrc/imp.rs} (59%) create mode 100644 src/ndivideosrc/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 379583f3..8b587426 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,16 +8,16 @@ description = "NewTek NDI Plugin" edition = "2018" [dependencies] -glib = "0.10" -gst = { package = "gstreamer", version = "0.16", features = ["v1_12"] } -gst-base = { package = "gstreamer-base", version = "0.16" } -gst-audio = { package = "gstreamer-audio", version = "0.16" } -gst-video = { package = "gstreamer-video", version = "0.16", features = ["v1_12"] } +glib = "0.14" +gst = { package = "gstreamer", version = "0.17", features = ["v1_12"] } +gst-base = { package = "gstreamer-base", version = "0.17" } +gst-audio = { package = "gstreamer-audio", version = "0.17" } +gst-video = { package = "gstreamer-video", version = "0.17", features = ["v1_12"] } byte-slice-cast = "1" once_cell = "1.0" [build-dependencies] -gst-plugin-version-helper = "0.2" +gst-plugin-version-helper = "0.7" [features] default = ["interlaced-fields", "reference-timestamps", "sink"] diff --git a/build.rs b/build.rs index 0d1ddb61..cda12e57 100644 --- a/build.rs +++ b/build.rs @@ -1,5 +1,3 @@ -extern crate gst_plugin_version_helper; - fn main() { - gst_plugin_version_helper::get_info() + gst_plugin_version_helper::info() } diff --git a/src/device_provider.rs b/src/device_provider/imp.rs similarity index 78% rename from src/device_provider.rs rename to src/device_provider/imp.rs index e2155740..5933741d 100644 --- a/src/device_provider.rs +++ b/src/device_provider/imp.rs @@ -1,4 +1,3 @@ -use glib::subclass; use gst::prelude::*; use gst::subclass::prelude::*; use gst::{gst_error, gst_log, gst_trace}; @@ -9,24 +8,24 @@ use std::sync::atomic; use std::sync::Mutex; use std::thread; +use once_cell::sync::Lazy; + use crate::ndi; #[derive(Debug)] -struct DeviceProvider { +pub struct DeviceProvider { cat: gst::DebugCategory, thread: Mutex>>, - current_devices: Mutex>, + current_devices: Mutex>, find: Mutex>, is_running: atomic::AtomicBool, } +#[glib::object_subclass] impl ObjectSubclass for DeviceProvider { const NAME: &'static str = "NdiDeviceProvider"; + type Type = super::DeviceProvider; type ParentType = gst::DeviceProvider; - type Instance = subclass::simple::InstanceStruct; - type Class = subclass::simple::ClassStruct; - - glib::glib_object_subclass!(); fn new() -> Self { Self { @@ -41,26 +40,32 @@ impl ObjectSubclass for DeviceProvider { is_running: atomic::AtomicBool::new(false), } } - - fn class_init(klass: &mut subclass::simple::ClassStruct) { - klass.set_metadata( - "NewTek NDI Device Provider", - "Source/Audio/Video/Network", - "NewTek NDI Device Provider", - "Ruben Gonzalez , Daniel Vilar , Sebastian Dröge ", - ); - } } -impl ObjectImpl for DeviceProvider { - glib::glib_object_impl!(); -} +impl ObjectImpl for DeviceProvider {} impl DeviceProviderImpl for DeviceProvider { - fn probe(&self, _device_provider: &gst::DeviceProvider) -> Vec { - self.current_devices.lock().unwrap().clone() + fn metadata() -> Option<&'static gst::subclass::DeviceProviderMetadata> { + static METADATA: Lazy = Lazy::new(|| { + gst::subclass::DeviceProviderMetadata::new("NewTek NDI Device Provider", + "Source/Audio/Video/Network", + "NewTek NDI Device Provider", + "Ruben Gonzalez , Daniel Vilar , Sebastian Dröge ") + }); + + Some(&*METADATA) } - fn start(&self, device_provider: &gst::DeviceProvider) -> Result<(), gst::LoggableError> { + + fn probe(&self, _device_provider: &Self::Type) -> Vec { + self.current_devices + .lock() + .unwrap() + .iter() + .map(|d| d.clone().upcast()) + .collect() + } + + fn start(&self, device_provider: &Self::Type) -> Result<(), gst::LoggableError> { let mut thread_guard = self.thread.lock().unwrap(); if thread_guard.is_some() { gst_log!( @@ -121,7 +126,8 @@ impl DeviceProviderImpl for DeviceProvider { Ok(()) } - fn stop(&self, _device_provider: &gst::DeviceProvider) { + + fn stop(&self, _device_provider: &Self::Type) { if let Some(_thread) = self.thread.lock().unwrap().take() { self.is_running.store(false, atomic::Ordering::SeqCst); // Don't actually join because that might take a while @@ -130,7 +136,7 @@ impl DeviceProviderImpl for DeviceProvider { } impl DeviceProvider { - fn poll(&self, device_provider: &gst::DeviceProvider, first: bool) { + fn poll(&self, device_provider: &super::DeviceProvider, first: bool) { let mut find_guard = self.find.lock().unwrap(); let find = match *find_guard { None => return, @@ -189,11 +195,11 @@ impl DeviceProvider { source ); // Add once for audio, another time for video - let device = Device::new(&source, true); + let device = super::Device::new(&source, true); device_provider.device_add(&device); current_devices_guard.push(device); - let device = Device::new(&source, false); + let device = super::Device::new(&source, false); device_provider.device_add(&device); current_devices_guard.push(device); } @@ -201,18 +207,16 @@ impl DeviceProvider { } #[derive(Debug)] -struct Device { +pub struct Device { cat: gst::DebugCategory, source: OnceCell<(ndi::Source<'static>, glib::Type)>, } +#[glib::object_subclass] impl ObjectSubclass for Device { const NAME: &'static str = "NdiDevice"; + type Type = super::Device; type ParentType = gst::Device; - type Instance = subclass::simple::InstanceStruct; - type Class = subclass::simple::ClassStruct; - - glib::glib_object_subclass!(); fn new() -> Self { Self { @@ -226,18 +230,16 @@ impl ObjectSubclass for Device { } } -impl ObjectImpl for Device { - glib::glib_object_impl!(); -} +impl ObjectImpl for Device {} impl DeviceImpl for Device { fn create_element( &self, - _device: &gst::Device, + _device: &Self::Type, name: Option<&str>, ) -> Result { let source_info = self.source.get().unwrap(); - let element = glib::Object::new( + let element = glib::Object::with_type( source_info.1, &[ ("name", &name), @@ -253,8 +255,8 @@ impl DeviceImpl for Device { } } -impl Device { - fn new(source: &ndi::Source<'_>, is_audio: bool) -> gst::Device { +impl super::Device { + fn new(source: &ndi::Source<'_>, is_audio: bool) -> super::Device { let display_name = format!( "{} ({})", source.ndi_name(), @@ -267,13 +269,13 @@ impl Device { // Get the caps from the template caps of the corresponding source element let element_type = if is_audio { - crate::ndiaudiosrc::NdiAudioSrc::get_type() + crate::ndiaudiosrc::NdiAudioSrc::static_type() } else { - crate::ndivideosrc::NdiVideoSrc::get_type() + crate::ndivideosrc::NdiVideoSrc::static_type() }; - let element_class = gst::ElementClass::from_type(element_type).unwrap(); - let templ = element_class.get_pad_template("src").unwrap(); - let caps = templ.get_caps().unwrap(); + let element_class = glib::Class::::from_type(element_type).unwrap(); + let templ = element_class.pad_template("src").unwrap(); + let caps = templ.caps(); // Put the url-address into the extra properties let extra_properties = gst::Structure::builder("properties") @@ -281,17 +283,12 @@ impl Device { .field("url-address", &source.url_address()) .build(); - let device = glib::Object::new( - Device::get_type(), - &[ - ("caps", &caps), - ("display-name", &display_name), - ("device-class", &device_class), - ("properties", &extra_properties), - ], - ) - .unwrap() - .dynamic_cast::() + let device = glib::Object::new::(&[ + ("caps", &caps), + ("display-name", &display_name), + ("device-class", &device_class), + ("properties", &extra_properties), + ]) .unwrap(); let device_impl = Device::from_instance(&device); @@ -303,12 +300,3 @@ impl Device { device } } - -pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { - gst::DeviceProvider::register( - Some(plugin), - "ndideviceprovider", - gst::Rank::Primary, - DeviceProvider::get_type(), - ) -} diff --git a/src/device_provider/mod.rs b/src/device_provider/mod.rs new file mode 100644 index 00000000..cda26ece --- /dev/null +++ b/src/device_provider/mod.rs @@ -0,0 +1,26 @@ +use glib::prelude::*; + +mod imp; + +glib::wrapper! { + pub struct DeviceProvider(ObjectSubclass) @extends gst::DeviceProvider, gst::Object; +} + +unsafe impl Send for DeviceProvider {} +unsafe impl Sync for DeviceProvider {} + +glib::wrapper! { + pub struct Device(ObjectSubclass) @extends gst::Device, gst::Object; +} + +unsafe impl Send for Device {} +unsafe impl Sync for Device {} + +pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { + gst::DeviceProvider::register( + Some(plugin), + "ndideviceprovider", + gst::Rank::Primary, + DeviceProvider::static_type(), + ) +} diff --git a/src/lib.rs b/src/lib.rs index 5c0366b9..752ea686 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,3 @@ -use glib::prelude::*; - mod device_provider; pub mod ndi; mod ndiaudiosrc; @@ -38,7 +36,7 @@ pub enum TimestampMode { fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { if !ndi::initialize() { - return Err(glib::glib_bool_error!("Cannot initialize NDI")); + return Err(glib::bool_error!("Cannot initialize NDI")); } device_provider::register(plugin)?; @@ -68,7 +66,7 @@ static TIMECODE_CAPS: Lazy = static TIMESTAMP_CAPS: Lazy = Lazy::new(|| gst::Caps::new_simple("timestamp/x-ndi-timestamp", &[])); -gst::gst_plugin_define!( +gst::plugin_define!( ndi, env!("CARGO_PKG_DESCRIPTION"), plugin_init, diff --git a/src/ndi.rs b/src/ndi.rs index 6c963efc..b4043b92 100644 --- a/src/ndi.rs +++ b/src/ndi.rs @@ -420,7 +420,7 @@ pub struct SendInstance(ptr::NonNull<::std::os::raw::c_void>); unsafe impl Send for SendInstance {} impl SendInstance { - pub fn builder<'a>(ndi_name: &'a str) -> SendBuilder<'a> { + pub fn builder(ndi_name: &str) -> SendBuilder { SendBuilder { ndi_name, clock_video: false, @@ -749,7 +749,7 @@ impl<'a> VideoFrame<'a> { impl<'a> Drop for VideoFrame<'a> { #[allow(irrefutable_let_patterns)] fn drop(&mut self) { - if let VideoFrame::BorrowedRecv(ref mut frame, ref recv) = *self { + if let VideoFrame::BorrowedRecv(ref mut frame, recv) = *self { unsafe { NDIlib_recv_free_video_v2(((recv.0).0).0.as_ptr() as *mut _, frame); } @@ -918,7 +918,7 @@ impl<'a> AudioFrame<'a> { impl<'a> Drop for AudioFrame<'a> { #[allow(irrefutable_let_patterns)] fn drop(&mut self) { - if let AudioFrame::BorrowedRecv(ref mut frame, ref recv) = *self { + if let AudioFrame::BorrowedRecv(ref mut frame, recv) = *self { unsafe { NDIlib_recv_free_audio_v2(((recv.0).0).0.as_ptr() as *mut _, frame); } @@ -1010,7 +1010,7 @@ impl<'a> Default for MetadataFrame<'a> { impl<'a> Drop for MetadataFrame<'a> { fn drop(&mut self) { - if let MetadataFrame::Borrowed(ref mut frame, ref recv) = *self { + if let MetadataFrame::Borrowed(ref mut frame, recv) = *self { unsafe { NDIlib_recv_free_metadata(((recv.0).0).0.as_ptr() as *mut _, frame); } diff --git a/src/ndiaudiosrc.rs b/src/ndiaudiosrc/imp.rs similarity index 62% rename from src/ndiaudiosrc.rs rename to src/ndiaudiosrc/imp.rs index b4cff6fa..3891a41d 100644 --- a/src/ndiaudiosrc.rs +++ b/src/ndiaudiosrc/imp.rs @@ -1,7 +1,6 @@ -use glib::subclass; use gst::prelude::*; use gst::subclass::prelude::*; -use gst::{gst_debug, gst_element_error, gst_error, gst_error_msg}; +use gst::{gst_debug, gst_error}; use gst_base::prelude::*; use gst_base::subclass::base_src::CreateSuccess; use gst_base::subclass::prelude::*; @@ -9,6 +8,8 @@ use gst_base::subclass::prelude::*; use std::sync::Mutex; use std::{i32, u32}; +use once_cell::sync::Lazy; + use crate::connect_ndi; use crate::ndisys; @@ -46,94 +47,10 @@ impl Default for Settings { } } -static PROPERTIES: [subclass::Property; 8] = [ - subclass::Property("ndi-name", |name| { - glib::ParamSpec::string( - name, - "NDI Name", - "NDI stream name of the sender", - None, - glib::ParamFlags::READWRITE, - ) - }), - subclass::Property("url-address", |name| { - glib::ParamSpec::string( - name, - "URL/Address", - "URL/address and port of the sender, e.g. 127.0.0.1:5961", - None, - glib::ParamFlags::READWRITE, - ) - }), - subclass::Property("receiver-ndi-name", |name| { - glib::ParamSpec::string( - name, - "Receiver NDI Name", - "NDI stream name of this receiver", - Some(&*DEFAULT_RECEIVER_NDI_NAME), - glib::ParamFlags::READWRITE, - ) - }), - subclass::Property("connect-timeout", |name| { - glib::ParamSpec::uint( - name, - "Connect Timeout", - "Connection timeout in ms", - 0, - u32::MAX, - 10000, - glib::ParamFlags::READWRITE, - ) - }), - subclass::Property("timeout", |name| { - glib::ParamSpec::uint( - name, - "Timeout", - "Receive timeout in ms", - 0, - u32::MAX, - 5000, - glib::ParamFlags::READWRITE, - ) - }), - subclass::Property("max-queue-length", |name| { - glib::ParamSpec::uint( - name, - "Max Queue Length", - "Maximum receive queue length", - 0, - u32::MAX, - 5, - glib::ParamFlags::READWRITE, - ) - }), - subclass::Property("bandwidth", |name| { - glib::ParamSpec::int( - name, - "Bandwidth", - "Bandwidth, -10 metadata-only, 10 audio-only, 100 highest", - -10, - 100, - 100, - glib::ParamFlags::READWRITE, - ) - }), - subclass::Property("timestamp-mode", |name| { - glib::ParamSpec::enum_( - name, - "Timestamp Mode", - "Timestamp information to use for outgoing PTS", - TimestampMode::static_type(), - TimestampMode::ReceiveTimeTimecode as i32, - glib::ParamFlags::READWRITE, - ) - }), -]; - struct State { info: Option, receiver: Option>, - current_latency: gst::ClockTime, + current_latency: Option, } impl Default for State { @@ -141,25 +58,23 @@ impl Default for State { State { info: None, receiver: None, - current_latency: gst::CLOCK_TIME_NONE, + current_latency: gst::ClockTime::NONE, } } } -pub(crate) struct NdiAudioSrc { +pub struct NdiAudioSrc { cat: gst::DebugCategory, settings: Mutex, state: Mutex, receiver_controller: Mutex>>, } +#[glib::object_subclass] impl ObjectSubclass for NdiAudioSrc { const NAME: &'static str = "NdiAudioSrc"; + type Type = super::NdiAudioSrc; type ParentType = gst_base::BaseSrc; - type Instance = gst::subclass::ElementInstanceStruct; - type Class = subclass::simple::ClassStruct; - - glib::glib_object_subclass!(); fn new() -> Self { Self { @@ -173,89 +88,130 @@ impl ObjectSubclass for NdiAudioSrc { receiver_controller: Mutex::new(None), } } - - fn class_init(klass: &mut subclass::simple::ClassStruct) { - klass.set_metadata( - "NewTek NDI Audio Source", - "Source", - "NewTek NDI audio source", - "Ruben Gonzalez , Daniel Vilar , Sebastian Dröge ", - ); - - let caps = gst::Caps::new_simple( - "audio/x-raw", - &[ - ( - "format", - &gst::List::new(&[&gst_audio::AUDIO_FORMAT_S16.to_string()]), - ), - ("rate", &gst::IntRange::::new(1, i32::MAX)), - ("channels", &gst::IntRange::::new(1, i32::MAX)), - ("layout", &"interleaved"), - ], - ); - - let src_pad_template = gst::PadTemplate::new( - "src", - gst::PadDirection::Src, - gst::PadPresence::Always, - &caps, - ) - .unwrap(); - klass.add_pad_template(src_pad_template); - - klass.install_properties(&PROPERTIES); - } } impl ObjectImpl for NdiAudioSrc { - glib::glib_object_impl!(); + fn properties() -> &'static [glib::ParamSpec] { + static PROPERTIES: Lazy> = Lazy::new(|| { + vec![ + glib::ParamSpec::new_string( + "ndi-name", + "NDI Name", + "NDI stream name of the sender", + None, + glib::ParamFlags::READWRITE, + ), + glib::ParamSpec::new_string( + "url-address", + "URL/Address", + "URL/address and port of the sender, e.g. 127.0.0.1:5961", + None, + glib::ParamFlags::READWRITE, + ), + glib::ParamSpec::new_string( + "receiver-ndi-name", + "Receiver NDI Name", + "NDI stream name of this receiver", + Some(&*DEFAULT_RECEIVER_NDI_NAME), + glib::ParamFlags::READWRITE, + ), + glib::ParamSpec::new_uint( + "connect-timeout", + "Connect Timeout", + "Connection timeout in ms", + 0, + u32::MAX, + 10000, + glib::ParamFlags::READWRITE, + ), + glib::ParamSpec::new_uint( + "timeout", + "Timeout", + "Receive timeout in ms", + 0, + u32::MAX, + 5000, + glib::ParamFlags::READWRITE, + ), + glib::ParamSpec::new_uint( + "max-queue-length", + "Max Queue Length", + "Maximum receive queue length", + 0, + u32::MAX, + 5, + glib::ParamFlags::READWRITE, + ), + glib::ParamSpec::new_int( + "bandwidth", + "Bandwidth", + "Bandwidth, -10 metadata-only, 10 audio-only, 100 highest", + -10, + 100, + 100, + glib::ParamFlags::READWRITE, + ), + glib::ParamSpec::new_enum( + "timestamp-mode", + "Timestamp Mode", + "Timestamp information to use for outgoing PTS", + TimestampMode::static_type(), + TimestampMode::ReceiveTimeTimecode as i32, + glib::ParamFlags::READWRITE, + ), + ] + }); - fn constructed(&self, obj: &glib::Object) { - self.parent_constructed(obj); - - let basesrc = obj.downcast_ref::().unwrap(); - // Initialize live-ness and notify the base class that - // we'd like to operate in Time format - basesrc.set_live(true); - basesrc.set_format(gst::Format::Time); + PROPERTIES.as_ref() } - fn set_property(&self, obj: &glib::Object, id: usize, value: &glib::Value) { - let prop = &PROPERTIES[id]; - let basesrc = obj.downcast_ref::().unwrap(); + fn constructed(&self, obj: &Self::Type) { + self.parent_constructed(obj); - match *prop { - subclass::Property("ndi-name", ..) => { + // Initialize live-ness and notify the base class that + // we'd like to operate in Time format + obj.set_live(true); + obj.set_format(gst::Format::Time); + } + + fn set_property( + &self, + obj: &Self::Type, + _id: usize, + value: &glib::Value, + pspec: &glib::ParamSpec, + ) { + match pspec.name() { + "ndi-name" => { let mut settings = self.settings.lock().unwrap(); let ndi_name = value.get().unwrap(); gst_debug!( self.cat, - obj: basesrc, + obj: obj, "Changing ndi-name from {:?} to {:?}", settings.ndi_name, ndi_name, ); settings.ndi_name = ndi_name; } - subclass::Property("url-address", ..) => { + "url-address" => { let mut settings = self.settings.lock().unwrap(); let url_address = value.get().unwrap(); gst_debug!( self.cat, - obj: basesrc, + obj: obj, "Changing url-address from {:?} to {:?}", settings.url_address, url_address, ); settings.url_address = url_address; } - subclass::Property("receiver-ndi-name", ..) => { + "receiver-ndi-name" => { let mut settings = self.settings.lock().unwrap(); - let receiver_ndi_name = value.get().unwrap(); + let receiver_ndi_name = value.get::>().unwrap(); gst_debug!( self.cat, - obj: basesrc, + obj: obj, "Changing receiver-ndi-name from {:?} to {:?}", settings.receiver_ndi_name, receiver_ndi_name, @@ -263,67 +219,66 @@ impl ObjectImpl for NdiAudioSrc { settings.receiver_ndi_name = receiver_ndi_name.unwrap_or_else(|| DEFAULT_RECEIVER_NDI_NAME.clone()); } - subclass::Property("connect-timeout", ..) => { + "connect-timeout" => { let mut settings = self.settings.lock().unwrap(); - let connect_timeout = value.get_some().unwrap(); + let connect_timeout = value.get().unwrap(); gst_debug!( self.cat, - obj: basesrc, + obj: obj, "Changing connect-timeout from {} to {}", settings.connect_timeout, connect_timeout, ); settings.connect_timeout = connect_timeout; } - subclass::Property("timeout", ..) => { + "timeout" => { let mut settings = self.settings.lock().unwrap(); - let timeout = value.get_some().unwrap(); + let timeout = value.get().unwrap(); gst_debug!( self.cat, - obj: basesrc, + obj: obj, "Changing timeout from {} to {}", settings.timeout, timeout, ); settings.timeout = timeout; } - subclass::Property("max-queue-length", ..) => { + "max-queue-length" => { let mut settings = self.settings.lock().unwrap(); - let max_queue_length = value.get_some().unwrap(); + let max_queue_length = value.get().unwrap(); gst_debug!( self.cat, - obj: basesrc, + obj: obj, "Changing max-queue-length from {} to {}", settings.max_queue_length, max_queue_length, ); settings.max_queue_length = max_queue_length; } - subclass::Property("bandwidth", ..) => { + "bandwidth" => { let mut settings = self.settings.lock().unwrap(); - let bandwidth = value.get_some().unwrap(); + let bandwidth = value.get().unwrap(); gst_debug!( self.cat, - obj: basesrc, + obj: obj, "Changing bandwidth from {} to {}", settings.bandwidth, bandwidth, ); settings.bandwidth = bandwidth; } - subclass::Property("timestamp-mode", ..) => { + "timestamp-mode" => { let mut settings = self.settings.lock().unwrap(); - let timestamp_mode = value.get_some().unwrap(); + let timestamp_mode = value.get().unwrap(); gst_debug!( self.cat, - obj: basesrc, + obj: obj, "Changing timestamp mode from {:?} to {:?}", settings.timestamp_mode, timestamp_mode ); if settings.timestamp_mode != timestamp_mode { - let _ = - basesrc.post_message(gst::message::Latency::builder().src(basesrc).build()); + let _ = obj.post_message(gst::message::Latency::builder().src(obj).build()); } settings.timestamp_mode = timestamp_mode; } @@ -331,41 +286,39 @@ impl ObjectImpl for NdiAudioSrc { } } - fn get_property(&self, _obj: &glib::Object, id: usize) -> Result { - let prop = &PROPERTIES[id]; - - match *prop { - subclass::Property("ndi-name", ..) => { + fn property(&self, _obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { + match pspec.name() { + "ndi-name" => { let settings = self.settings.lock().unwrap(); - Ok(settings.ndi_name.to_value()) + settings.ndi_name.to_value() } - subclass::Property("url-address", ..) => { + "url-address" => { let settings = self.settings.lock().unwrap(); - Ok(settings.url_address.to_value()) + settings.url_address.to_value() } - subclass::Property("receiver-ndi-name", ..) => { + "receiver-ndi-name" => { let settings = self.settings.lock().unwrap(); - Ok(settings.receiver_ndi_name.to_value()) + settings.receiver_ndi_name.to_value() } - subclass::Property("connect-timeout", ..) => { + "connect-timeout" => { let settings = self.settings.lock().unwrap(); - Ok(settings.connect_timeout.to_value()) + settings.connect_timeout.to_value() } - subclass::Property("timeout", ..) => { + "timeout" => { let settings = self.settings.lock().unwrap(); - Ok(settings.timeout.to_value()) + settings.timeout.to_value() } - subclass::Property("max-queue-length", ..) => { + "max-queue-length" => { let settings = self.settings.lock().unwrap(); - Ok(settings.max_queue_length.to_value()) + settings.max_queue_length.to_value() } - subclass::Property("bandwidth", ..) => { + "bandwidth" => { let settings = self.settings.lock().unwrap(); - Ok(settings.bandwidth.to_value()) + settings.bandwidth.to_value() } - subclass::Property("timestamp-mode", ..) => { + "timestamp-mode" => { let settings = self.settings.lock().unwrap(); - Ok(settings.timestamp_mode.to_value()) + settings.timestamp_mode.to_value() } _ => unimplemented!(), } @@ -373,9 +326,51 @@ impl ObjectImpl for NdiAudioSrc { } impl ElementImpl for NdiAudioSrc { + fn metadata() -> Option<&'static gst::subclass::ElementMetadata> { + static ELEMENT_METADATA: Lazy = Lazy::new(|| { + gst::subclass::ElementMetadata::new( + "NewTek NDI Audio Source", + "Source", + "NewTek NDI audio source", + "Ruben Gonzalez , Daniel Vilar , Sebastian Dröge ", + ) + }); + + Some(&*ELEMENT_METADATA) + } + + fn pad_templates() -> &'static [gst::PadTemplate] { + static PAD_TEMPLATES: Lazy> = Lazy::new(|| { + let caps = gst::Caps::new_simple( + "audio/x-raw", + &[ + ( + "format", + &gst::List::new(&[&gst_audio::AUDIO_FORMAT_S16.to_string()]), + ), + ("rate", &gst::IntRange::::new(1, i32::MAX)), + ("channels", &gst::IntRange::::new(1, i32::MAX)), + ("layout", &"interleaved"), + ], + ); + + let audio_src_pad_template = gst::PadTemplate::new( + "src", + gst::PadDirection::Src, + gst::PadPresence::Sometimes, + &caps, + ) + .unwrap(); + + vec![audio_src_pad_template] + }); + + PAD_TEMPLATES.as_ref() + } + fn change_state( &self, - element: &gst::Element, + element: &Self::Type, transition: gst::StateChange, ) -> Result { match transition { @@ -402,13 +397,13 @@ impl ElementImpl for NdiAudioSrc { } impl BaseSrcImpl for NdiAudioSrc { - fn negotiate(&self, _element: &gst_base::BaseSrc) -> Result<(), gst::LoggableError> { + fn negotiate(&self, _element: &Self::Type) -> Result<(), gst::LoggableError> { // Always succeed here without doing anything: we will set the caps once we received a // buffer, there's nothing we can negotiate Ok(()) } - fn unlock(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { + fn unlock(&self, element: &Self::Type) -> Result<(), gst::ErrorMessage> { gst_debug!(self.cat, obj: element, "Unlocking",); if let Some(ref controller) = *self.receiver_controller.lock().unwrap() { controller.set_flushing(true); @@ -416,7 +411,7 @@ impl BaseSrcImpl for NdiAudioSrc { Ok(()) } - fn unlock_stop(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { + fn unlock_stop(&self, element: &Self::Type) -> Result<(), gst::ErrorMessage> { gst_debug!(self.cat, obj: element, "Stop unlocking",); if let Some(ref controller) = *self.receiver_controller.lock().unwrap() { controller.set_flushing(false); @@ -424,12 +419,12 @@ impl BaseSrcImpl for NdiAudioSrc { Ok(()) } - fn start(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { + fn start(&self, element: &Self::Type) -> Result<(), gst::ErrorMessage> { *self.state.lock().unwrap() = Default::default(); let settings = self.settings.lock().unwrap().clone(); if settings.ndi_name.is_none() && settings.url_address.is_none() { - return Err(gst_error_msg!( + return Err(gst::error_msg!( gst::LibraryError::Settings, ["No NDI name or URL/address given"] )); @@ -437,7 +432,7 @@ impl BaseSrcImpl for NdiAudioSrc { let receiver = connect_ndi( self.cat, - element, + element.upcast_ref(), settings.ndi_name.as_deref(), settings.url_address.as_deref(), &settings.receiver_ndi_name, @@ -450,7 +445,7 @@ impl BaseSrcImpl for NdiAudioSrc { // settings.id_receiver exists match receiver { - None => Err(gst_error_msg!( + None => Err(gst::error_msg!( gst::ResourceError::NotFound, ["Could not connect to this source"] )), @@ -465,7 +460,7 @@ impl BaseSrcImpl for NdiAudioSrc { } } - fn stop(&self, _element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { + fn stop(&self, _element: &Self::Type) -> Result<(), gst::ErrorMessage> { if let Some(ref controller) = self.receiver_controller.lock().unwrap().take() { controller.shutdown(); } @@ -473,7 +468,7 @@ impl BaseSrcImpl for NdiAudioSrc { Ok(()) } - fn query(&self, element: &gst_base::BaseSrc, query: &mut gst::QueryRef) -> bool { + fn query(&self, element: &Self::Type, query: &mut gst::QueryRef) -> bool { use gst::QueryView; match query.view_mut() { @@ -486,14 +481,14 @@ impl BaseSrcImpl for NdiAudioSrc { let state = self.state.lock().unwrap(); let settings = self.settings.lock().unwrap(); - if state.current_latency.is_some() { + if let Some(latency) = state.current_latency { let min = if settings.timestamp_mode != TimestampMode::Timecode { - state.current_latency + latency } else { - 0.into() + gst::ClockTime::ZERO }; - let max = 5 * state.current_latency; + let max = 5 * latency; gst_debug!( self.cat, @@ -512,11 +507,11 @@ impl BaseSrcImpl for NdiAudioSrc { } } - fn fixate(&self, element: &gst_base::BaseSrc, mut caps: gst::Caps) -> gst::Caps { + fn fixate(&self, element: &Self::Type, mut caps: gst::Caps) -> gst::Caps { caps.truncate(); { let caps = caps.make_mut(); - let s = caps.get_mut_structure(0).unwrap(); + let s = caps.structure_mut(0).unwrap(); s.fixate_field_nearest_int("rate", 48_000); s.fixate_field_nearest_int("channels", 2); } @@ -526,7 +521,7 @@ impl BaseSrcImpl for NdiAudioSrc { fn create( &self, - element: &gst_base::BaseSrc, + element: &Self::Type, _offset: u64, _buffer: Option<&mut gst::BufferRef>, _length: u32, @@ -548,7 +543,7 @@ impl BaseSrcImpl for NdiAudioSrc { state.receiver = Some(recv); if state.info.as_ref() != Some(&info) { let caps = info.to_caps().map_err(|_| { - gst_element_error!( + gst::element_error!( element, gst::ResourceError::Settings, ["Invalid audio info received: {:?}", info] @@ -556,11 +551,11 @@ impl BaseSrcImpl for NdiAudioSrc { gst::FlowError::NotNegotiated })?; state.info = Some(info); - state.current_latency = buffer.get_duration(); + state.current_latency = buffer.duration(); drop(state); gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); element.set_caps(&caps).map_err(|_| { - gst_element_error!( + gst::element_error!( element, gst::CoreError::Negotiation, ["Failed to negotiate caps: {:?}", caps] @@ -580,12 +575,3 @@ impl BaseSrcImpl for NdiAudioSrc { } } } - -pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { - gst::Element::register( - Some(plugin), - "ndiaudiosrc", - gst::Rank::None, - NdiAudioSrc::get_type(), - ) -} diff --git a/src/ndiaudiosrc/mod.rs b/src/ndiaudiosrc/mod.rs new file mode 100644 index 00000000..11b94849 --- /dev/null +++ b/src/ndiaudiosrc/mod.rs @@ -0,0 +1,19 @@ +use glib::prelude::*; + +mod imp; + +glib::wrapper! { + pub struct NdiAudioSrc(ObjectSubclass) @extends gst_base::BaseSrc, gst::Element, gst::Object; +} + +unsafe impl Send for NdiAudioSrc {} +unsafe impl Sync for NdiAudioSrc {} + +pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { + gst::Element::register( + Some(plugin), + "ndiaudiosrc", + gst::Rank::None, + NdiAudioSrc::static_type(), + ) +} diff --git a/src/ndisink.rs b/src/ndisink/imp.rs similarity index 52% rename from src/ndisink.rs rename to src/ndisink/imp.rs index b4f5a0bb..fc23ff01 100644 --- a/src/ndisink.rs +++ b/src/ndisink/imp.rs @@ -1,15 +1,15 @@ -use glib::subclass; use glib::subclass::prelude::*; use gst::prelude::*; use gst::subclass::prelude::*; -use gst::{gst_debug, gst_error, gst_error_msg, gst_info, gst_loggable_error, gst_trace}; -use gst_base::{subclass::prelude::*, BaseSinkExtManual}; +use gst::{gst_debug, gst_error, gst_info, gst_trace}; +use gst_base::prelude::*; +use gst_base::subclass::prelude::*; use std::sync::Mutex; use once_cell::sync::Lazy; -use super::ndi::SendInstance; +use crate::ndi::SendInstance; static DEFAULT_SENDER_NDI_NAME: Lazy = Lazy::new(|| { format!( @@ -32,16 +32,6 @@ impl Default for Settings { } } -static PROPERTIES: [subclass::Property; 1] = [subclass::Property("ndi-name", |name| { - glib::ParamSpec::string( - name, - "NDI Name", - "NDI Name to use", - Some(DEFAULT_SENDER_NDI_NAME.as_ref()), - glib::ParamFlags::READWRITE, - ) -})]; - struct State { send: SendInstance, video_info: Option, @@ -57,13 +47,11 @@ static CAT: Lazy = Lazy::new(|| { gst::DebugCategory::new("ndisink", gst::DebugColorFlags::empty(), Some("NDI Sink")) }); +#[glib::object_subclass] impl ObjectSubclass for NdiSink { const NAME: &'static str = "NdiSink"; + type Type = super::NdiSink; type ParentType = gst_base::BaseSink; - type Instance = gst::subclass::ElementInstanceStruct; - type Class = subclass::simple::ClassStruct; - - glib::glib_object_subclass!(); fn new() -> Self { Self { @@ -71,106 +59,129 @@ impl ObjectSubclass for NdiSink { state: Mutex::new(Default::default()), } } - - fn class_init(klass: &mut subclass::simple::ClassStruct) { - klass.set_metadata( - "NDI Sink", - "Sink/Audio/Video", - "Render as an NDI stream", - "Sebastian Dröge ", - ); - - let caps = gst::Caps::builder_full() - .structure( - gst::Structure::builder("video/x-raw") - .field( - "format", - &gst::List::new(&[ - &gst_video::VideoFormat::Uyvy.to_str(), - &gst_video::VideoFormat::I420.to_str(), - &gst_video::VideoFormat::Nv12.to_str(), - &gst_video::VideoFormat::Nv21.to_str(), - &gst_video::VideoFormat::Yv12.to_str(), - &gst_video::VideoFormat::Bgra.to_str(), - &gst_video::VideoFormat::Bgrx.to_str(), - &gst_video::VideoFormat::Rgba.to_str(), - &gst_video::VideoFormat::Rgbx.to_str(), - ]), - ) - .field("width", &gst::IntRange::::new(1, std::i32::MAX)) - .field("height", &gst::IntRange::::new(1, std::i32::MAX)) - .field( - "framerate", - &gst::FractionRange::new( - gst::Fraction::new(0, 1), - gst::Fraction::new(std::i32::MAX, 1), - ), - ) - .build(), - ) - .structure( - gst::Structure::builder("audio/x-raw") - .field("format", &gst_audio::AUDIO_FORMAT_S16.to_str()) - .field("rate", &gst::IntRange::::new(1, i32::MAX)) - .field("channels", &gst::IntRange::::new(1, i32::MAX)) - .field("layout", &"interleaved") - .build(), - ) - .build(); - - let sink_pad_template = gst::PadTemplate::new( - "sink", - gst::PadDirection::Sink, - gst::PadPresence::Always, - &caps, - ) - .unwrap(); - klass.add_pad_template(sink_pad_template); - - klass.install_properties(&PROPERTIES); - } } impl ObjectImpl for NdiSink { - glib::glib_object_impl!(); + fn properties() -> &'static [glib::ParamSpec] { + static PROPERTIES: Lazy> = Lazy::new(|| { + vec![glib::ParamSpec::new_string( + "ndi-name", + "NDI Name", + "NDI Name to use", + Some(DEFAULT_SENDER_NDI_NAME.as_ref()), + glib::ParamFlags::READWRITE, + )] + }); - fn set_property(&self, _obj: &glib::Object, id: usize, value: &glib::Value) { - let prop = &PROPERTIES[id]; - match *prop { - subclass::Property("ndi-name", ..) => { + PROPERTIES.as_ref() + } + + fn set_property( + &self, + _obj: &Self::Type, + _id: usize, + value: &glib::Value, + pspec: &glib::ParamSpec, + ) { + match pspec.name() { + "ndi-name" => { let mut settings = self.settings.lock().unwrap(); settings.ndi_name = value .get::() - .unwrap() - .unwrap_or_else(|| DEFAULT_SENDER_NDI_NAME.clone()); + .unwrap_or_else(|_| DEFAULT_SENDER_NDI_NAME.clone()); } _ => unimplemented!(), }; } - fn get_property(&self, _obj: &glib::Object, id: usize) -> Result { - let prop = &PROPERTIES[id]; - match *prop { - subclass::Property("ndi-name", ..) => { + fn property(&self, _obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { + match pspec.name() { + "ndi-name" => { let settings = self.settings.lock().unwrap(); - Ok(settings.ndi_name.to_value()) + settings.ndi_name.to_value() } _ => unimplemented!(), } } } -impl ElementImpl for NdiSink {} +impl ElementImpl for NdiSink { + fn metadata() -> Option<&'static gst::subclass::ElementMetadata> { + static ELEMENT_METADATA: Lazy = Lazy::new(|| { + gst::subclass::ElementMetadata::new( + "NDI Sink", + "Sink/Audio/Video", + "Render as an NDI stream", + "Sebastian Dröge ", + ) + }); + + Some(&*ELEMENT_METADATA) + } + + fn pad_templates() -> &'static [gst::PadTemplate] { + static PAD_TEMPLATES: Lazy> = Lazy::new(|| { + let caps = gst::Caps::builder_full() + .structure( + gst::Structure::builder("video/x-raw") + .field( + "format", + &gst::List::new(&[ + &gst_video::VideoFormat::Uyvy.to_str(), + &gst_video::VideoFormat::I420.to_str(), + &gst_video::VideoFormat::Nv12.to_str(), + &gst_video::VideoFormat::Nv21.to_str(), + &gst_video::VideoFormat::Yv12.to_str(), + &gst_video::VideoFormat::Bgra.to_str(), + &gst_video::VideoFormat::Bgrx.to_str(), + &gst_video::VideoFormat::Rgba.to_str(), + &gst_video::VideoFormat::Rgbx.to_str(), + ]), + ) + .field("width", &gst::IntRange::::new(1, std::i32::MAX)) + .field("height", &gst::IntRange::::new(1, std::i32::MAX)) + .field( + "framerate", + &gst::FractionRange::new( + gst::Fraction::new(0, 1), + gst::Fraction::new(std::i32::MAX, 1), + ), + ) + .build(), + ) + .structure( + gst::Structure::builder("audio/x-raw") + .field("format", &gst_audio::AUDIO_FORMAT_S16.to_str()) + .field("rate", &gst::IntRange::::new(1, i32::MAX)) + .field("channels", &gst::IntRange::::new(1, i32::MAX)) + .field("layout", &"interleaved") + .build(), + ) + .build(); + + let sink_pad_template = gst::PadTemplate::new( + "sink", + gst::PadDirection::Sink, + gst::PadPresence::Always, + &caps, + ) + .unwrap(); + vec![sink_pad_template] + }); + + PAD_TEMPLATES.as_ref() + } +} impl BaseSinkImpl for NdiSink { - fn start(&self, element: &gst_base::BaseSink) -> Result<(), gst::ErrorMessage> { + fn start(&self, element: &Self::Type) -> Result<(), gst::ErrorMessage> { let mut state_storage = self.state.lock().unwrap(); let settings = self.settings.lock().unwrap(); let send = SendInstance::builder(&settings.ndi_name) .build() .ok_or_else(|| { - gst_error_msg!( + gst::error_msg!( gst::ResourceError::OpenWrite, ["Could not create send instance"] ) @@ -187,7 +198,7 @@ impl BaseSinkImpl for NdiSink { Ok(()) } - fn stop(&self, element: &gst_base::BaseSink) -> Result<(), gst::ErrorMessage> { + fn stop(&self, element: &Self::Type) -> Result<(), gst::ErrorMessage> { let mut state_storage = self.state.lock().unwrap(); *state_storage = None; @@ -196,37 +207,33 @@ impl BaseSinkImpl for NdiSink { Ok(()) } - fn unlock(&self, _element: &gst_base::BaseSink) -> Result<(), gst::ErrorMessage> { + fn unlock(&self, _element: &Self::Type) -> Result<(), gst::ErrorMessage> { Ok(()) } - fn unlock_stop(&self, _element: &gst_base::BaseSink) -> Result<(), gst::ErrorMessage> { + fn unlock_stop(&self, _element: &Self::Type) -> Result<(), gst::ErrorMessage> { Ok(()) } - fn set_caps( - &self, - element: &gst_base::BaseSink, - caps: &gst::Caps, - ) -> Result<(), gst::LoggableError> { + fn set_caps(&self, element: &Self::Type, caps: &gst::Caps) -> Result<(), gst::LoggableError> { gst_debug!(CAT, obj: element, "Setting caps {}", caps); let mut state_storage = self.state.lock().unwrap(); let state = match &mut *state_storage { - None => return Err(gst_loggable_error!(CAT, "Sink not started yet")), + None => return Err(gst::loggable_error!(CAT, "Sink not started yet")), Some(ref mut state) => state, }; - let s = caps.get_structure(0).unwrap(); - if s.get_name() == "video/x-raw" { + let s = caps.structure(0).unwrap(); + if s.name() == "video/x-raw" { let info = gst_video::VideoInfo::from_caps(caps) - .map_err(|_| gst_loggable_error!(CAT, "Couldn't parse caps {}", caps))?; + .map_err(|_| gst::loggable_error!(CAT, "Couldn't parse caps {}", caps))?; state.video_info = Some(info); state.audio_info = None; } else { let info = gst_audio::AudioInfo::from_caps(caps) - .map_err(|_| gst_loggable_error!(CAT, "Couldn't parse caps {}", caps))?; + .map_err(|_| gst::loggable_error!(CAT, "Couldn't parse caps {}", caps))?; state.audio_info = Some(info); state.video_info = None; @@ -237,7 +244,7 @@ impl BaseSinkImpl for NdiSink { fn render( &self, - element: &gst_base::BaseSink, + element: &Self::Type, buffer: &gst::Buffer, ) -> Result { let mut state_storage = self.state.lock().unwrap(); @@ -247,7 +254,7 @@ impl BaseSinkImpl for NdiSink { }; if let Some(ref info) = state.video_info { - if let Some(audio_meta) = buffer.get_meta::() { + if let Some(audio_meta) = buffer.meta::() { for (buffer, info, timecode) in audio_meta.buffers() { let frame = crate::ndi::AudioFrame::try_from_interleaved_16s(info, buffer, *timecode) @@ -262,9 +269,9 @@ impl BaseSinkImpl for NdiSink { "Sending audio buffer {:?} with timecode {} and format {:?}", buffer, if *timecode < 0 { - gst::CLOCK_TIME_NONE + gst::ClockTime::NONE.display() } else { - gst::ClockTime::from(*timecode as u64 * 100) + Some(gst::ClockTime::from_nseconds(*timecode as u64 * 100)).display() }, info, ); @@ -273,15 +280,18 @@ impl BaseSinkImpl for NdiSink { } // Skip empty/gap buffers from ndisinkcombiner - if buffer.get_size() != 0 { + if buffer.size() != 0 { let timecode = element - .get_segment() + .segment() .downcast::() .ok() .and_then(|segment| { - *(segment.to_running_time(buffer.get_pts()) + element.get_base_time()) + segment + .to_running_time(buffer.pts()) + .zip(element.base_time()) }) - .map(|time| (time / 100) as i64) + .and_then(|(running_time, base_time)| running_time.checked_add(base_time)) + .map(|time| (time.nseconds() / 100) as i64) .unwrap_or(crate::ndisys::NDIlib_send_timecode_synthesize); let frame = gst_video::VideoFrameRef::from_buffer_ref_readable(buffer, info) @@ -302,9 +312,9 @@ impl BaseSinkImpl for NdiSink { "Sending video buffer {:?} with timecode {} and format {:?}", buffer, if timecode < 0 { - gst::CLOCK_TIME_NONE + gst::ClockTime::NONE.display() } else { - gst::ClockTime::from(timecode as u64 * 100) + Some(gst::ClockTime::from_nseconds(timecode as u64 * 100)).display() }, info ); @@ -312,13 +322,16 @@ impl BaseSinkImpl for NdiSink { } } else if let Some(ref info) = state.audio_info { let timecode = element - .get_segment() + .segment() .downcast::() .ok() .and_then(|segment| { - *(segment.to_running_time(buffer.get_pts()) + element.get_base_time()) + segment + .to_running_time(buffer.pts()) + .zip(element.base_time()) }) - .map(|time| (time / 100) as i64) + .and_then(|(running_time, base_time)| running_time.checked_add(base_time)) + .map(|time| (time.nseconds() / 100) as i64) .unwrap_or(crate::ndisys::NDIlib_send_timecode_synthesize); let frame = crate::ndi::AudioFrame::try_from_interleaved_16s(info, buffer, timecode) @@ -333,9 +346,9 @@ impl BaseSinkImpl for NdiSink { "Sending audio buffer {:?} with timecode {} and format {:?}", buffer, if timecode < 0 { - gst::CLOCK_TIME_NONE + gst::ClockTime::NONE.display() } else { - gst::ClockTime::from(timecode as u64 * 100) + Some(gst::ClockTime::from_nseconds(timecode as u64 * 100)).display() }, info, ); @@ -347,12 +360,3 @@ impl BaseSinkImpl for NdiSink { Ok(gst::FlowSuccess::Ok) } } - -pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { - gst::Element::register( - Some(plugin), - "ndisink", - gst::Rank::None, - NdiSink::get_type(), - ) -} diff --git a/src/ndisink/mod.rs b/src/ndisink/mod.rs new file mode 100644 index 00000000..8d6d955a --- /dev/null +++ b/src/ndisink/mod.rs @@ -0,0 +1,19 @@ +use glib::prelude::*; + +mod imp; + +glib::wrapper! { + pub struct NdiSink(ObjectSubclass) @extends gst_base::BaseSink, gst::Element, gst::Object; +} + +unsafe impl Send for NdiSink {} +unsafe impl Sync for NdiSink {} + +pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { + gst::Element::register( + Some(plugin), + "ndisink", + gst::Rank::None, + NdiSink::static_type(), + ) +} diff --git a/src/ndisinkcombiner.rs b/src/ndisinkcombiner/imp.rs similarity index 69% rename from src/ndisinkcombiner.rs rename to src/ndisinkcombiner/imp.rs index ddc91798..9ba0e28a 100644 --- a/src/ndisinkcombiner.rs +++ b/src/ndisinkcombiner/imp.rs @@ -1,5 +1,4 @@ use glib::prelude::*; -use glib::subclass; use glib::subclass::prelude::*; use gst::prelude::*; use gst::subclass::prelude::*; @@ -7,6 +6,8 @@ use gst::{gst_debug, gst_error, gst_trace, gst_warning}; use gst_base::prelude::*; use gst_base::subclass::prelude::*; +use once_cell::sync::Lazy; + use std::mem; use std::sync::Mutex; @@ -27,92 +28,20 @@ struct State { current_audio_buffers: Vec<(gst::Buffer, gst_audio::AudioInfo, i64)>, } -struct NdiSinkCombiner { +pub struct NdiSinkCombiner { video_pad: gst_base::AggregatorPad, audio_pad: Mutex>, state: Mutex>, } +#[glib::object_subclass] impl ObjectSubclass for NdiSinkCombiner { const NAME: &'static str = "NdiSinkCombiner"; + type Type = super::NdiSinkCombiner; type ParentType = gst_base::Aggregator; - type Instance = gst::subclass::ElementInstanceStruct; - type Class = subclass::simple::ClassStruct; - - glib::glib_object_subclass!(); - - fn class_init(klass: &mut subclass::simple::ClassStruct) { - klass.set_metadata( - "NDI Sink Combiner", - "Combiner/Audio/Video", - "NDI sink audio/video combiner", - "Sebastian Dröge ", - ); - - let caps = gst::Caps::builder("video/x-raw") - .field( - "format", - &gst::List::new(&[ - &gst_video::VideoFormat::Uyvy.to_str(), - &gst_video::VideoFormat::I420.to_str(), - &gst_video::VideoFormat::Nv12.to_str(), - &gst_video::VideoFormat::Nv21.to_str(), - &gst_video::VideoFormat::Yv12.to_str(), - &gst_video::VideoFormat::Bgra.to_str(), - &gst_video::VideoFormat::Bgrx.to_str(), - &gst_video::VideoFormat::Rgba.to_str(), - &gst_video::VideoFormat::Rgbx.to_str(), - ]), - ) - .field("width", &gst::IntRange::::new(1, i32::MAX)) - .field("height", &gst::IntRange::::new(1, i32::MAX)) - .field( - "framerate", - &gst::FractionRange::new( - gst::Fraction::new(1, i32::MAX), - gst::Fraction::new(i32::MAX, 1), - ), - ) - .build(); - let src_pad_template = gst::PadTemplate::with_gtype( - "src", - gst::PadDirection::Src, - gst::PadPresence::Always, - &caps, - gst_base::AggregatorPad::static_type(), - ) - .unwrap(); - klass.add_pad_template(src_pad_template); - - let sink_pad_template = gst::PadTemplate::with_gtype( - "video", - gst::PadDirection::Sink, - gst::PadPresence::Always, - &caps, - gst_base::AggregatorPad::static_type(), - ) - .unwrap(); - klass.add_pad_template(sink_pad_template); - - let caps = gst::Caps::builder("audio/x-raw") - .field("format", &gst_audio::AUDIO_FORMAT_S16.to_str()) - .field("rate", &gst::IntRange::::new(1, i32::MAX)) - .field("channels", &gst::IntRange::::new(1, i32::MAX)) - .field("layout", &"interleaved") - .build(); - let sink_pad_template = gst::PadTemplate::with_gtype( - "audio", - gst::PadDirection::Sink, - gst::PadPresence::Request, - &caps, - gst_base::AggregatorPad::static_type(), - ) - .unwrap(); - klass.add_pad_template(sink_pad_template); - } fn with_class(klass: &Self::Class) -> Self { - let templ = klass.get_pad_template("video").unwrap(); + let templ = klass.pad_template("video").unwrap(); let video_pad = gst::PadBuilder::::from_template(&templ, Some("video")) .build(); @@ -126,18 +55,97 @@ impl ObjectSubclass for NdiSinkCombiner { } impl ObjectImpl for NdiSinkCombiner { - glib::glib_object_impl!(); - - fn constructed(&self, obj: &glib::Object) { - let element = obj.downcast_ref::().unwrap(); - element.add_pad(&self.video_pad).unwrap(); + fn constructed(&self, obj: &Self::Type) { + obj.add_pad(&self.video_pad).unwrap(); self.parent_constructed(obj); } } impl ElementImpl for NdiSinkCombiner { - fn release_pad(&self, element: &gst::Element, pad: &gst::Pad) { + fn metadata() -> Option<&'static gst::subclass::ElementMetadata> { + static ELEMENT_METADATA: Lazy = Lazy::new(|| { + gst::subclass::ElementMetadata::new( + "NDI Sink Combiner", + "Combiner/Audio/Video", + "NDI sink audio/video combiner", + "Sebastian Dröge ", + ) + }); + + Some(&*ELEMENT_METADATA) + } + + fn pad_templates() -> &'static [gst::PadTemplate] { + static PAD_TEMPLATES: Lazy> = Lazy::new(|| { + let caps = gst::Caps::builder("video/x-raw") + .field( + "format", + &gst::List::new(&[ + &gst_video::VideoFormat::Uyvy.to_str(), + &gst_video::VideoFormat::I420.to_str(), + &gst_video::VideoFormat::Nv12.to_str(), + &gst_video::VideoFormat::Nv21.to_str(), + &gst_video::VideoFormat::Yv12.to_str(), + &gst_video::VideoFormat::Bgra.to_str(), + &gst_video::VideoFormat::Bgrx.to_str(), + &gst_video::VideoFormat::Rgba.to_str(), + &gst_video::VideoFormat::Rgbx.to_str(), + ]), + ) + .field("width", &gst::IntRange::::new(1, i32::MAX)) + .field("height", &gst::IntRange::::new(1, i32::MAX)) + .field( + "framerate", + &gst::FractionRange::new( + gst::Fraction::new(1, i32::MAX), + gst::Fraction::new(i32::MAX, 1), + ), + ) + .build(); + let src_pad_template = gst::PadTemplate::with_gtype( + "src", + gst::PadDirection::Src, + gst::PadPresence::Always, + &caps, + gst_base::AggregatorPad::static_type(), + ) + .unwrap(); + + let video_sink_pad_template = gst::PadTemplate::with_gtype( + "video", + gst::PadDirection::Sink, + gst::PadPresence::Always, + &caps, + gst_base::AggregatorPad::static_type(), + ) + .unwrap(); + + let caps = gst::Caps::builder("audio/x-raw") + .field("format", &gst_audio::AUDIO_FORMAT_S16.to_str()) + .field("rate", &gst::IntRange::::new(1, i32::MAX)) + .field("channels", &gst::IntRange::::new(1, i32::MAX)) + .field("layout", &"interleaved") + .build(); + let audio_sink_pad_template = gst::PadTemplate::with_gtype( + "audio", + gst::PadDirection::Sink, + gst::PadPresence::Request, + &caps, + gst_base::AggregatorPad::static_type(), + ) + .unwrap(); + vec![ + src_pad_template, + video_sink_pad_template, + audio_sink_pad_template, + ] + }); + + PAD_TEMPLATES.as_ref() + } + + fn release_pad(&self, element: &Self::Type, pad: &gst::Pad) { let mut audio_pad_storage = self.audio_pad.lock().unwrap(); if audio_pad_storage.as_ref().map(|p| p.upcast_ref()) == Some(pad) { @@ -151,7 +159,7 @@ impl ElementImpl for NdiSinkCombiner { impl AggregatorImpl for NdiSinkCombiner { fn create_new_pad( &self, - agg: &gst_base::Aggregator, + agg: &Self::Type, templ: &gst::PadTemplate, _req_name: Option<&str>, _caps: Option<&gst::Caps>, @@ -163,7 +171,7 @@ impl AggregatorImpl for NdiSinkCombiner { return None; } - let sink_templ = agg.get_pad_template("audio").unwrap(); + let sink_templ = agg.pad_template("audio").unwrap(); if templ != &sink_templ { gst_error!(CAT, obj: agg, "Wrong pad template"); return None; @@ -178,7 +186,7 @@ impl AggregatorImpl for NdiSinkCombiner { Some(pad) } - fn start(&self, agg: &gst_base::Aggregator) -> Result<(), gst::ErrorMessage> { + fn start(&self, agg: &Self::Type) -> Result<(), gst::ErrorMessage> { let mut state_storage = self.state.lock().unwrap(); *state_storage = Some(State { audio_info: None, @@ -192,7 +200,7 @@ impl AggregatorImpl for NdiSinkCombiner { Ok(()) } - fn stop(&self, agg: &gst_base::Aggregator) -> Result<(), gst::ErrorMessage> { + fn stop(&self, agg: &Self::Type) -> Result<(), gst::ErrorMessage> { // Drop our state now let _ = self.state.lock().unwrap().take(); @@ -201,18 +209,18 @@ impl AggregatorImpl for NdiSinkCombiner { Ok(()) } - fn get_next_time(&self, _agg: &gst_base::Aggregator) -> gst::ClockTime { + fn next_time(&self, _agg: &Self::Type) -> Option { // FIXME: What to do here? We don't really know when the next buffer is expected - gst::CLOCK_TIME_NONE + gst::ClockTime::NONE } fn clip( &self, - agg: &gst_base::Aggregator, + agg: &Self::Type, agg_pad: &gst_base::AggregatorPad, mut buffer: gst::Buffer, ) -> Option { - let segment = match agg_pad.get_segment().downcast::() { + let segment = match agg_pad.segment().downcast::() { Ok(segment) => segment, Err(_) => { gst_error!(CAT, obj: agg, "Only TIME segments supported"); @@ -220,25 +228,21 @@ impl AggregatorImpl for NdiSinkCombiner { } }; - let pts = buffer.get_pts(); + let pts = buffer.pts(); if pts.is_none() { gst_error!(CAT, obj: agg, "Only buffers with PTS supported"); return Some(buffer); } - let duration = if buffer.get_duration().is_some() { - buffer.get_duration() - } else { - gst::CLOCK_TIME_NONE - }; + let duration = buffer.duration(); gst_trace!( CAT, obj: agg_pad, "Clipping buffer {:?} with PTS {} and duration {}", buffer, - pts, - duration + pts.display(), + duration.display(), ); let state_storage = self.state.lock().unwrap(); @@ -247,25 +251,21 @@ impl AggregatorImpl for NdiSinkCombiner { None => return None, }; - let duration = if buffer.get_duration().is_some() { - buffer.get_duration() + let duration = if duration.is_some() { + duration } else if let Some(ref audio_info) = state.audio_info { - gst::SECOND - .mul_div_floor( - buffer.get_size() as u64, - audio_info.rate() as u64 * audio_info.bpf() as u64, - ) - .unwrap() + gst::ClockTime::SECOND.mul_div_floor( + buffer.size() as u64, + audio_info.rate() as u64 * audio_info.bpf() as u64, + ) } else if let Some(ref video_info) = state.video_info { if *video_info.fps().numer() > 0 { - gst::SECOND - .mul_div_floor( - *video_info.fps().denom() as u64, - *video_info.fps().numer() as u64, - ) - .unwrap() + gst::ClockTime::SECOND.mul_div_floor( + *video_info.fps().denom() as u64, + *video_info.fps().numer() as u64, + ) } else { - gst::CLOCK_TIME_NONE + gst::ClockTime::NONE } } else { unreachable!() @@ -276,18 +276,23 @@ impl AggregatorImpl for NdiSinkCombiner { obj: agg_pad, "Clipping buffer {:?} with PTS {} and duration {}", buffer, - pts, - duration + pts.display(), + duration.display(), ); if agg_pad == &self.video_pad { - segment.clip(pts, pts + duration).map(|(start, stop)| { + let end_pts = pts + .zip(duration) + .and_then(|(pts, duration)| pts.checked_add(duration)); + + segment.clip(pts, end_pts).map(|(start, stop)| { { let buffer = buffer.make_mut(); buffer.set_pts(start); - if duration.is_some() { - buffer.set_duration(stop - start); - } + buffer.set_duration( + stop.zip(start) + .and_then(|(stop, start)| stop.checked_sub(start)), + ); } buffer @@ -307,7 +312,7 @@ impl AggregatorImpl for NdiSinkCombiner { fn aggregate( &self, - agg: &gst_base::Aggregator, + agg: &Self::Type, timeout: bool, ) -> Result { // FIXME: Can't really happen because we always return NONE from get_next_time() but that @@ -318,7 +323,7 @@ impl AggregatorImpl for NdiSinkCombiner { // first try getting buffers from both pads here let video_buffer_and_segment = match self.video_pad.peek_buffer() { Some(video_buffer) => { - let video_segment = self.video_pad.get_segment(); + let video_segment = self.video_pad.segment(); let video_segment = match video_segment.downcast::() { Ok(video_segment) => video_segment, Err(video_segment) => { @@ -326,7 +331,7 @@ impl AggregatorImpl for NdiSinkCombiner { CAT, obj: agg, "Video segment of wrong format {:?}", - video_segment.get_format() + video_segment.format() ); return Err(gst::FlowError::Error); } @@ -344,14 +349,14 @@ impl AggregatorImpl for NdiSinkCombiner { let audio_buffer_segment_and_pad; if let Some(audio_pad) = self.audio_pad.lock().unwrap().clone() { audio_buffer_segment_and_pad = match audio_pad.peek_buffer() { - Some(audio_buffer) if audio_buffer.get_size() == 0 => { + Some(audio_buffer) if audio_buffer.size() == 0 => { // Skip empty/gap audio buffer audio_pad.drop_buffer(); gst_trace!(CAT, obj: agg, "Empty audio buffer, waiting for next"); return Err(gst_base::AGGREGATOR_FLOW_NEED_DATA); } Some(audio_buffer) => { - let audio_segment = audio_pad.get_segment(); + let audio_segment = audio_pad.segment(); let audio_segment = match audio_segment.downcast::() { Ok(audio_segment) => audio_segment, Err(audio_segment) => { @@ -359,7 +364,7 @@ impl AggregatorImpl for NdiSinkCombiner { CAT, obj: agg, "Audio segment of wrong format {:?}", - audio_segment.get_format() + audio_segment.format() ); return Err(gst::FlowError::Error); } @@ -385,8 +390,7 @@ impl AggregatorImpl for NdiSinkCombiner { let (mut current_video_buffer, current_video_running_time_end, next_video_buffer) = if let Some((video_buffer, video_segment)) = video_buffer_and_segment { - let video_running_time = video_segment.to_running_time(video_buffer.get_pts()); - assert!(video_running_time.is_some()); + let video_running_time = video_segment.to_running_time(video_buffer.pts()).unwrap(); match state.current_video_buffer { None => { @@ -398,7 +402,7 @@ impl AggregatorImpl for NdiSinkCombiner { } Some((ref buffer, _)) => ( buffer.clone(), - video_running_time, + Some(video_running_time), Some((video_buffer, video_running_time)), ), } @@ -416,10 +420,9 @@ impl AggregatorImpl for NdiSinkCombiner { // Create an empty dummy buffer for attaching the audio. This is going to // be dropped by the sink later. let audio_running_time = - audio_segment.to_running_time(audio_buffer.get_pts()); - assert!(audio_running_time.is_some()); + audio_segment.to_running_time(audio_buffer.pts()).unwrap(); - let video_segment = self.video_pad.get_segment(); + let video_segment = self.video_pad.segment(); let video_segment = match video_segment.downcast::() { Ok(video_segment) => video_segment, Err(video_segment) => { @@ -427,7 +430,7 @@ impl AggregatorImpl for NdiSinkCombiner { CAT, obj: agg, "Video segment of wrong format {:?}", - video_segment.get_format() + video_segment.format() ); return Err(gst::FlowError::Error); } @@ -445,9 +448,9 @@ impl AggregatorImpl for NdiSinkCombiner { buffer.set_pts(video_pts); } - (buffer, gst::CLOCK_TIME_NONE, None) + (buffer, gst::ClockTime::NONE, None) } - (Some((ref buffer, _)), _) => (buffer.clone(), gst::CLOCK_TIME_NONE, None), + (Some((ref buffer, _)), _) => (buffer.clone(), gst::ClockTime::NONE, None), } }; @@ -460,22 +463,26 @@ impl AggregatorImpl for NdiSinkCombiner { } }; - let audio_running_time = audio_segment.to_running_time(audio_buffer.get_pts()); - assert!(audio_running_time.is_some()); - let duration = gst::SECOND - .mul_div_floor( - audio_buffer.get_size() as u64 / audio_info.bpf() as u64, - audio_info.rate() as u64, - ) - .unwrap_or(gst::CLOCK_TIME_NONE); - let audio_running_time_end = audio_running_time + duration; - assert!(audio_running_time_end.is_some()); + let audio_running_time = audio_segment.to_running_time(audio_buffer.pts()); + let duration = gst::ClockTime::SECOND.mul_div_floor( + audio_buffer.size() as u64 / audio_info.bpf() as u64, + audio_info.rate() as u64, + ); + let audio_running_time_end = audio_running_time + .zip(duration) + .and_then(|(running_time, duration)| running_time.checked_add(duration)); - if audio_running_time_end <= current_video_running_time_end - || current_video_running_time_end.is_none() + if audio_running_time_end + .zip(current_video_running_time_end) + .map(|(audio, video)| audio <= video) + .unwrap_or(true) { - let timecode = (audio_running_time + agg.get_base_time()) - .map(|t| (t / 100) as i64) + let timecode = agg + .base_time() + .zip(audio_running_time) + .map(|(base_time, audio_running_time)| { + ((base_time.nseconds() + audio_running_time.nseconds()) / 100) as i64 + }) .unwrap_or(crate::ndisys::NDIlib_send_timecode_synthesize); gst_trace!( @@ -484,8 +491,8 @@ impl AggregatorImpl for NdiSinkCombiner { "Including audio buffer {:?} with timecode {}: {} <= {}", audio_buffer, timecode, - audio_running_time_end, - current_video_running_time_end, + audio_running_time_end.display(), + current_video_running_time_end.display(), ); state .current_audio_buffers @@ -503,7 +510,7 @@ impl AggregatorImpl for NdiSinkCombiner { // far } - let audio_buffers = mem::replace(&mut state.current_audio_buffers, Vec::new()); + let audio_buffers = mem::take(&mut state.current_audio_buffers); if !audio_buffers.is_empty() { let current_video_buffer = current_video_buffer.make_mut(); @@ -530,7 +537,7 @@ impl AggregatorImpl for NdiSinkCombiner { fn sink_event( &self, - agg: &gst_base::Aggregator, + agg: &Self::Type, pad: &gst_base::AggregatorPad, event: gst::Event, ) -> bool { @@ -538,7 +545,7 @@ impl AggregatorImpl for NdiSinkCombiner { match event.view() { EventView::Caps(caps) => { - let caps = caps.get_caps_owned(); + let caps = caps.caps_owned(); let mut state_storage = self.state.lock().unwrap(); let state = match &mut *state_storage { @@ -558,22 +565,22 @@ impl AggregatorImpl for NdiSinkCombiner { // 2 frames latency because we queue 1 frame and wait until audio // up to the end of that frame has arrived. let latency = if *info.fps().numer() > 0 { - gst::SECOND + gst::ClockTime::SECOND .mul_div_floor( 2 * *info.fps().denom() as u64, *info.fps().numer() as u64, ) - .unwrap_or(80 * gst::MSECOND) + .unwrap_or(80 * gst::ClockTime::MSECOND) } else { // let's assume 25fps and 2 frames latency - 80 * gst::MSECOND + 80 * gst::ClockTime::MSECOND }; state.video_info = Some(info); drop(state_storage); - agg.set_latency(latency, gst::CLOCK_TIME_NONE); + agg.set_latency(latency, gst::ClockTime::NONE); // The video caps are passed through as the audio is included only in a meta agg.set_src_caps(&caps); @@ -591,7 +598,7 @@ impl AggregatorImpl for NdiSinkCombiner { } // The video segment is passed through as-is and the video timestamps are preserved EventView::Segment(segment) if pad == &self.video_pad => { - let segment = segment.get_segment(); + let segment = segment.segment(); gst_debug!(CAT, obj: agg, "Updating segment {:?}", segment); agg.update_segment(segment); } @@ -603,7 +610,7 @@ impl AggregatorImpl for NdiSinkCombiner { fn sink_query( &self, - agg: &gst_base::Aggregator, + agg: &Self::Type, pad: &gst_base::AggregatorPad, query: &mut gst::QueryRef, ) -> bool { @@ -612,7 +619,7 @@ impl AggregatorImpl for NdiSinkCombiner { match query.view_mut() { QueryView::Caps(_) if pad == &self.video_pad => { // Directly forward caps queries - let srcpad = agg.get_static_pad("src").unwrap(); + let srcpad = agg.static_pad("src").unwrap(); return srcpad.peer_query(query); } _ => (), @@ -621,17 +628,8 @@ impl AggregatorImpl for NdiSinkCombiner { self.parent_sink_query(agg, pad, query) } - fn negotiate(&self, _agg: &gst_base::Aggregator) -> bool { + fn negotiate(&self, _agg: &Self::Type) -> bool { // No negotiation needed as the video caps are just passed through true } } - -pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { - gst::Element::register( - Some(plugin), - "ndisinkcombiner", - gst::Rank::None, - NdiSinkCombiner::get_type(), - ) -} diff --git a/src/ndisinkcombiner/mod.rs b/src/ndisinkcombiner/mod.rs new file mode 100644 index 00000000..b86c4cab --- /dev/null +++ b/src/ndisinkcombiner/mod.rs @@ -0,0 +1,19 @@ +use glib::prelude::*; + +mod imp; + +glib::wrapper! { + pub struct NdiSinkCombiner(ObjectSubclass) @extends gst_base::Aggregator, gst::Element, gst::Object; +} + +unsafe impl Send for NdiSinkCombiner {} +unsafe impl Sync for NdiSinkCombiner {} + +pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { + gst::Element::register( + Some(plugin), + "ndisinkcombiner", + gst::Rank::None, + NdiSinkCombiner::static_type(), + ) +} diff --git a/src/ndisinkmeta.rs b/src/ndisinkmeta.rs index f53449ec..14aee926 100644 --- a/src/ndisinkmeta.rs +++ b/src/ndisinkmeta.rs @@ -1,4 +1,3 @@ -use gst::gst_sys; use gst::prelude::*; use std::fmt; use std::mem; @@ -19,10 +18,10 @@ impl NdiSinkAudioMeta { // content of the struct let mut params = mem::ManuallyDrop::new(imp::NdiSinkAudioMetaParams { buffers }); - let meta = gst_sys::gst_buffer_add_meta( + let meta = gst::ffi::gst_buffer_add_meta( buffer.as_mut_ptr(), imp::ndi_sink_audio_meta_get_info(), - &mut *params as *mut imp::NdiSinkAudioMetaParams as glib::glib_sys::gpointer, + &mut *params as *mut imp::NdiSinkAudioMetaParams as glib::ffi::gpointer, ) as *mut imp::NdiSinkAudioMeta; Self::from_mut_ptr(buffer, meta) @@ -37,7 +36,7 @@ impl NdiSinkAudioMeta { unsafe impl MetaAPI for NdiSinkAudioMeta { type GstType = imp::NdiSinkAudioMeta; - fn get_meta_api() -> glib::Type { + fn meta_api() -> glib::Type { imp::ndi_sink_audio_meta_api_get_type() } } @@ -51,9 +50,7 @@ impl fmt::Debug for NdiSinkAudioMeta { } mod imp { - use glib::glib_sys; use glib::translate::*; - use gst::gst_sys; use once_cell::sync::Lazy; use std::mem; use std::ptr; @@ -64,18 +61,18 @@ mod imp { #[repr(C)] pub struct NdiSinkAudioMeta { - parent: gst_sys::GstMeta, + parent: gst::ffi::GstMeta, pub(super) buffers: Vec<(gst::Buffer, gst_audio::AudioInfo, i64)>, } pub(super) fn ndi_sink_audio_meta_api_get_type() -> glib::Type { static TYPE: Lazy = Lazy::new(|| unsafe { - let t = from_glib(gst_sys::gst_meta_api_type_register( + let t = from_glib(gst::ffi::gst_meta_api_type_register( b"GstNdiSinkAudioMetaAPI\0".as_ptr() as *const _, [ptr::null::()].as_ptr() as *mut *const _, )); - assert_ne!(t, glib::Type::Invalid); + assert_ne!(t, glib::Type::INVALID); t }); @@ -84,10 +81,10 @@ mod imp { } unsafe extern "C" fn ndi_sink_audio_meta_init( - meta: *mut gst_sys::GstMeta, - params: glib_sys::gpointer, - _buffer: *mut gst_sys::GstBuffer, - ) -> glib_sys::gboolean { + meta: *mut gst::ffi::GstMeta, + params: glib::ffi::gpointer, + _buffer: *mut gst::ffi::GstBuffer, + ) -> glib::ffi::gboolean { assert!(!params.is_null()); let meta = &mut *(meta as *mut NdiSinkAudioMeta); @@ -95,12 +92,12 @@ mod imp { ptr::write(&mut meta.buffers, params.buffers); - true.to_glib() + true.into_glib() } unsafe extern "C" fn ndi_sink_audio_meta_free( - meta: *mut gst_sys::GstMeta, - _buffer: *mut gst_sys::GstBuffer, + meta: *mut gst::ffi::GstMeta, + _buffer: *mut gst::ffi::GstBuffer, ) { let meta = &mut *(meta as *mut NdiSinkAudioMeta); @@ -108,34 +105,34 @@ mod imp { } unsafe extern "C" fn ndi_sink_audio_meta_transform( - dest: *mut gst_sys::GstBuffer, - meta: *mut gst_sys::GstMeta, - _buffer: *mut gst_sys::GstBuffer, - _type_: glib_sys::GQuark, - _data: glib_sys::gpointer, - ) -> glib_sys::gboolean { + dest: *mut gst::ffi::GstBuffer, + meta: *mut gst::ffi::GstMeta, + _buffer: *mut gst::ffi::GstBuffer, + _type_: glib::ffi::GQuark, + _data: glib::ffi::gpointer, + ) -> glib::ffi::gboolean { let meta = &*(meta as *mut NdiSinkAudioMeta); super::NdiSinkAudioMeta::add(gst::BufferRef::from_mut_ptr(dest), meta.buffers.clone()); - true.to_glib() + true.into_glib() } - pub(super) fn ndi_sink_audio_meta_get_info() -> *const gst_sys::GstMetaInfo { - struct MetaInfo(ptr::NonNull); + pub(super) fn ndi_sink_audio_meta_get_info() -> *const gst::ffi::GstMetaInfo { + struct MetaInfo(ptr::NonNull); unsafe impl Send for MetaInfo {} unsafe impl Sync for MetaInfo {} static META_INFO: Lazy = Lazy::new(|| unsafe { MetaInfo( - ptr::NonNull::new(gst_sys::gst_meta_register( - ndi_sink_audio_meta_api_get_type().to_glib(), + ptr::NonNull::new(gst::ffi::gst_meta_register( + ndi_sink_audio_meta_api_get_type().into_glib(), b"GstNdiSinkAudioMeta\0".as_ptr() as *const _, mem::size_of::(), Some(ndi_sink_audio_meta_init), Some(ndi_sink_audio_meta_free), Some(ndi_sink_audio_meta_transform), - ) as *mut gst_sys::GstMetaInfo) + ) as *mut gst::ffi::GstMetaInfo) .expect("Failed to register meta API"), ) }); diff --git a/src/ndivideosrc.rs b/src/ndivideosrc/imp.rs similarity index 59% rename from src/ndivideosrc.rs rename to src/ndivideosrc/imp.rs index 7e41d231..d30ee802 100644 --- a/src/ndivideosrc.rs +++ b/src/ndivideosrc/imp.rs @@ -1,7 +1,6 @@ -use glib::subclass; use gst::prelude::*; use gst::subclass::prelude::*; -use gst::{gst_debug, gst_element_error, gst_error, gst_error_msg}; +use gst::{gst_debug, gst_error}; use gst_base::prelude::*; use gst_base::subclass::base_src::CreateSuccess; use gst_base::subclass::prelude::*; @@ -9,6 +8,8 @@ use gst_base::subclass::prelude::*; use std::sync::Mutex; use std::{i32, u32}; +use once_cell::sync::Lazy; + use crate::ndisys; use crate::connect_ndi; @@ -47,93 +48,9 @@ impl Default for Settings { } } -static PROPERTIES: [subclass::Property; 8] = [ - subclass::Property("ndi-name", |name| { - glib::ParamSpec::string( - name, - "NDI Name", - "NDI stream name of the sender", - None, - glib::ParamFlags::READWRITE, - ) - }), - subclass::Property("url-address", |name| { - glib::ParamSpec::string( - name, - "URL/Address", - "URL/address and port of the sender, e.g. 127.0.0.1:5961", - None, - glib::ParamFlags::READWRITE, - ) - }), - subclass::Property("receiver-ndi-name", |name| { - glib::ParamSpec::string( - name, - "Receiver NDI Name", - "NDI stream name of this receiver", - Some(&*DEFAULT_RECEIVER_NDI_NAME), - glib::ParamFlags::READWRITE, - ) - }), - subclass::Property("connect-timeout", |name| { - glib::ParamSpec::uint( - name, - "Connect Timeout", - "Connection timeout in ms", - 0, - u32::MAX, - 10000, - glib::ParamFlags::READWRITE, - ) - }), - subclass::Property("timeout", |name| { - glib::ParamSpec::uint( - name, - "Timeout", - "Receive timeout in ms", - 0, - u32::MAX, - 5000, - glib::ParamFlags::READWRITE, - ) - }), - subclass::Property("max-queue-length", |name| { - glib::ParamSpec::uint( - name, - "Max Queue Length", - "Maximum receive queue length", - 0, - u32::MAX, - 5, - glib::ParamFlags::READWRITE, - ) - }), - subclass::Property("bandwidth", |name| { - glib::ParamSpec::int( - name, - "Bandwidth", - "Bandwidth, -10 metadata-only, 10 audio-only, 100 highest", - -10, - 100, - 100, - glib::ParamFlags::READWRITE, - ) - }), - subclass::Property("timestamp-mode", |name| { - glib::ParamSpec::enum_( - name, - "Timestamp Mode", - "Timestamp information to use for outgoing PTS", - TimestampMode::static_type(), - TimestampMode::ReceiveTimeTimecode as i32, - glib::ParamFlags::READWRITE, - ) - }), -]; - struct State { info: Option, - current_latency: gst::ClockTime, + current_latency: Option, receiver: Option>, } @@ -141,26 +58,24 @@ impl Default for State { fn default() -> State { State { info: None, - current_latency: gst::CLOCK_TIME_NONE, + current_latency: gst::ClockTime::NONE, receiver: None, } } } -pub(crate) struct NdiVideoSrc { +pub struct NdiVideoSrc { cat: gst::DebugCategory, settings: Mutex, state: Mutex, receiver_controller: Mutex>>, } +#[glib::object_subclass] impl ObjectSubclass for NdiVideoSrc { const NAME: &'static str = "NdiVideoSrc"; + type Type = super::NdiVideoSrc; type ParentType = gst_base::BaseSrc; - type Instance = gst::subclass::ElementInstanceStruct; - type Class = subclass::simple::ClassStruct; - - glib::glib_object_subclass!(); fn new() -> Self { Self { @@ -174,123 +89,130 @@ impl ObjectSubclass for NdiVideoSrc { receiver_controller: Mutex::new(None), } } - - fn class_init(klass: &mut subclass::simple::ClassStruct) { - klass.set_metadata( - "NewTek NDI Video Source", - "Source", - "NewTek NDI video source", - "Ruben Gonzalez , Daniel Vilar , Sebastian Dröge ", - ); - - // On the src pad, we can produce F32/F64 with any sample rate - // and any number of channels - let caps = gst::Caps::new_simple( - "video/x-raw", - &[ - ( - "format", - &gst::List::new(&[ - &gst_video::VideoFormat::Uyvy.to_string(), - &gst_video::VideoFormat::Yv12.to_string(), - &gst_video::VideoFormat::Nv12.to_string(), - &gst_video::VideoFormat::I420.to_string(), - &gst_video::VideoFormat::Bgra.to_string(), - &gst_video::VideoFormat::Bgrx.to_string(), - &gst_video::VideoFormat::Rgba.to_string(), - &gst_video::VideoFormat::Rgbx.to_string(), - ]), - ), - ("width", &gst::IntRange::::new(0, i32::MAX)), - ("height", &gst::IntRange::::new(0, i32::MAX)), - ( - "framerate", - &gst::FractionRange::new( - gst::Fraction::new(0, 1), - gst::Fraction::new(i32::MAX, 1), - ), - ), - ], - ); - - #[cfg(feature = "interlaced-fields")] - let caps = { - let mut tmp = caps.copy(); - { - let tmp = tmp.get_mut().unwrap(); - tmp.set_features_simple(Some(gst::CapsFeatures::new(&["format:Interlaced"]))); - } - - let mut caps = caps; - { - let caps = caps.get_mut().unwrap(); - caps.append(tmp); - } - - caps - }; - - let src_pad_template = gst::PadTemplate::new( - "src", - gst::PadDirection::Src, - gst::PadPresence::Always, - &caps, - ) - .unwrap(); - klass.add_pad_template(src_pad_template); - - klass.install_properties(&PROPERTIES); - } } impl ObjectImpl for NdiVideoSrc { - glib::glib_object_impl!(); + fn properties() -> &'static [glib::ParamSpec] { + static PROPERTIES: Lazy> = Lazy::new(|| { + vec![ + glib::ParamSpec::new_string( + "ndi-name", + "NDI Name", + "NDI stream name of the sender", + None, + glib::ParamFlags::READWRITE, + ), + glib::ParamSpec::new_string( + "url-address", + "URL/Address", + "URL/address and port of the sender, e.g. 127.0.0.1:5961", + None, + glib::ParamFlags::READWRITE, + ), + glib::ParamSpec::new_string( + "receiver-ndi-name", + "Receiver NDI Name", + "NDI stream name of this receiver", + Some(&*DEFAULT_RECEIVER_NDI_NAME), + glib::ParamFlags::READWRITE, + ), + glib::ParamSpec::new_uint( + "connect-timeout", + "Connect Timeout", + "Connection timeout in ms", + 0, + u32::MAX, + 10000, + glib::ParamFlags::READWRITE, + ), + glib::ParamSpec::new_uint( + "timeout", + "Timeout", + "Receive timeout in ms", + 0, + u32::MAX, + 5000, + glib::ParamFlags::READWRITE, + ), + glib::ParamSpec::new_uint( + "max-queue-length", + "Max Queue Length", + "Maximum receive queue length", + 0, + u32::MAX, + 5, + glib::ParamFlags::READWRITE, + ), + glib::ParamSpec::new_int( + "bandwidth", + "Bandwidth", + "Bandwidth, -10 metadata-only, 10 audio-only, 100 highest", + -10, + 100, + 100, + glib::ParamFlags::READWRITE, + ), + glib::ParamSpec::new_enum( + "timestamp-mode", + "Timestamp Mode", + "Timestamp information to use for outgoing PTS", + TimestampMode::static_type(), + TimestampMode::ReceiveTimeTimecode as i32, + glib::ParamFlags::READWRITE, + ), + ] + }); - fn constructed(&self, obj: &glib::Object) { - self.parent_constructed(obj); - - let basesrc = obj.downcast_ref::().unwrap(); - // Initialize live-ness and notify the base class that - // we'd like to operate in Time format - basesrc.set_live(true); - basesrc.set_format(gst::Format::Time); + PROPERTIES.as_ref() } - fn set_property(&self, obj: &glib::Object, id: usize, value: &glib::Value) { - let prop = &PROPERTIES[id]; - let basesrc = obj.downcast_ref::().unwrap(); + fn constructed(&self, obj: &Self::Type) { + self.parent_constructed(obj); - match *prop { - subclass::Property("ndi-name", ..) => { + // Initialize live-ness and notify the base class that + // we'd like to operate in Time format + obj.set_live(true); + obj.set_format(gst::Format::Time); + } + + fn set_property( + &self, + obj: &Self::Type, + _id: usize, + value: &glib::Value, + pspec: &glib::ParamSpec, + ) { + match pspec.name() { + "ndi-name" => { let mut settings = self.settings.lock().unwrap(); let ndi_name = value.get().unwrap(); gst_debug!( self.cat, - obj: basesrc, + obj: obj, "Changing ndi-name from {:?} to {:?}", settings.ndi_name, ndi_name, ); settings.ndi_name = ndi_name; } - subclass::Property("url-address", ..) => { + "url-address" => { let mut settings = self.settings.lock().unwrap(); let url_address = value.get().unwrap(); gst_debug!( self.cat, - obj: basesrc, + obj: obj, "Changing url-address from {:?} to {:?}", settings.url_address, url_address, ); settings.url_address = url_address; } - subclass::Property("receiver-ndi-name", ..) => { + "receiver-ndi-name" => { let mut settings = self.settings.lock().unwrap(); - let receiver_ndi_name = value.get().unwrap(); + let receiver_ndi_name = value.get::>().unwrap(); gst_debug!( self.cat, - obj: basesrc, + obj: obj, "Changing receiver-ndi-name from {:?} to {:?}", settings.receiver_ndi_name, receiver_ndi_name, @@ -298,67 +220,66 @@ impl ObjectImpl for NdiVideoSrc { settings.receiver_ndi_name = receiver_ndi_name.unwrap_or_else(|| DEFAULT_RECEIVER_NDI_NAME.clone()); } - subclass::Property("connect-timeout", ..) => { + "connect-timeout" => { let mut settings = self.settings.lock().unwrap(); - let connect_timeout = value.get_some().unwrap(); + let connect_timeout = value.get().unwrap(); gst_debug!( self.cat, - obj: basesrc, + obj: obj, "Changing connect-timeout from {} to {}", settings.connect_timeout, connect_timeout, ); settings.connect_timeout = connect_timeout; } - subclass::Property("timeout", ..) => { + "timeout" => { let mut settings = self.settings.lock().unwrap(); - let timeout = value.get_some().unwrap(); + let timeout = value.get().unwrap(); gst_debug!( self.cat, - obj: basesrc, + obj: obj, "Changing timeout from {} to {}", settings.timeout, timeout, ); settings.timeout = timeout; } - subclass::Property("max-queue-length", ..) => { + "max-queue-length" => { let mut settings = self.settings.lock().unwrap(); - let max_queue_length = value.get_some().unwrap(); + let max_queue_length = value.get().unwrap(); gst_debug!( self.cat, - obj: basesrc, + obj: obj, "Changing max-queue-length from {} to {}", settings.max_queue_length, max_queue_length, ); settings.max_queue_length = max_queue_length; } - subclass::Property("bandwidth", ..) => { + "bandwidth" => { let mut settings = self.settings.lock().unwrap(); - let bandwidth = value.get_some().unwrap(); + let bandwidth = value.get().unwrap(); gst_debug!( self.cat, - obj: basesrc, + obj: obj, "Changing bandwidth from {} to {}", settings.bandwidth, bandwidth, ); settings.bandwidth = bandwidth; } - subclass::Property("timestamp-mode", ..) => { + "timestamp-mode" => { let mut settings = self.settings.lock().unwrap(); - let timestamp_mode = value.get_some().unwrap(); + let timestamp_mode = value.get().unwrap(); gst_debug!( self.cat, - obj: basesrc, + obj: obj, "Changing timestamp mode from {:?} to {:?}", settings.timestamp_mode, timestamp_mode ); if settings.timestamp_mode != timestamp_mode { - let _ = - basesrc.post_message(gst::message::Latency::builder().src(basesrc).build()); + let _ = obj.post_message(gst::message::Latency::builder().src(obj).build()); } settings.timestamp_mode = timestamp_mode; } @@ -366,41 +287,39 @@ impl ObjectImpl for NdiVideoSrc { } } - fn get_property(&self, _obj: &glib::Object, id: usize) -> Result { - let prop = &PROPERTIES[id]; - - match *prop { - subclass::Property("ndi-name", ..) => { + fn property(&self, _obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { + match pspec.name() { + "ndi-name" => { let settings = self.settings.lock().unwrap(); - Ok(settings.ndi_name.to_value()) + settings.ndi_name.to_value() } - subclass::Property("url-address", ..) => { + "url-address" => { let settings = self.settings.lock().unwrap(); - Ok(settings.url_address.to_value()) + settings.url_address.to_value() } - subclass::Property("receiver-ndi-name", ..) => { + "receiver-ndi-name" => { let settings = self.settings.lock().unwrap(); - Ok(settings.receiver_ndi_name.to_value()) + settings.receiver_ndi_name.to_value() } - subclass::Property("connect-timeout", ..) => { + "connect-timeout" => { let settings = self.settings.lock().unwrap(); - Ok(settings.connect_timeout.to_value()) + settings.connect_timeout.to_value() } - subclass::Property("timeout", ..) => { + "timeout" => { let settings = self.settings.lock().unwrap(); - Ok(settings.timeout.to_value()) + settings.timeout.to_value() } - subclass::Property("max-queue-length", ..) => { + "max-queue-length" => { let settings = self.settings.lock().unwrap(); - Ok(settings.max_queue_length.to_value()) + settings.max_queue_length.to_value() } - subclass::Property("bandwidth", ..) => { + "bandwidth" => { let settings = self.settings.lock().unwrap(); - Ok(settings.bandwidth.to_value()) + settings.bandwidth.to_value() } - subclass::Property("timestamp-mode", ..) => { + "timestamp-mode" => { let settings = self.settings.lock().unwrap(); - Ok(settings.timestamp_mode.to_value()) + settings.timestamp_mode.to_value() } _ => unimplemented!(), } @@ -408,9 +327,85 @@ impl ObjectImpl for NdiVideoSrc { } impl ElementImpl for NdiVideoSrc { + fn metadata() -> Option<&'static gst::subclass::ElementMetadata> { + static ELEMENT_METADATA: Lazy = Lazy::new(|| { + gst::subclass::ElementMetadata::new( + "NewTek NDI Video Source", + "Source", + "NewTek NDI video source", + "Ruben Gonzalez , Daniel Vilar , Sebastian Dröge ", + ) + }); + + Some(&*ELEMENT_METADATA) + } + + fn pad_templates() -> &'static [gst::PadTemplate] { + static PAD_TEMPLATES: Lazy> = Lazy::new(|| { + // On the src pad, we can produce F32/F64 with any sample rate + // and any number of channels + let caps = gst::Caps::new_simple( + "video/x-raw", + &[ + ( + "format", + &gst::List::new(&[ + &gst_video::VideoFormat::Uyvy.to_string(), + &gst_video::VideoFormat::Yv12.to_string(), + &gst_video::VideoFormat::Nv12.to_string(), + &gst_video::VideoFormat::I420.to_string(), + &gst_video::VideoFormat::Bgra.to_string(), + &gst_video::VideoFormat::Bgrx.to_string(), + &gst_video::VideoFormat::Rgba.to_string(), + &gst_video::VideoFormat::Rgbx.to_string(), + ]), + ), + ("width", &gst::IntRange::::new(0, i32::MAX)), + ("height", &gst::IntRange::::new(0, i32::MAX)), + ( + "framerate", + &gst::FractionRange::new( + gst::Fraction::new(0, 1), + gst::Fraction::new(i32::MAX, 1), + ), + ), + ], + ); + + #[cfg(feature = "interlaced-fields")] + let caps = { + let mut tmp = caps.copy(); + { + let tmp = tmp.get_mut().unwrap(); + tmp.set_features_simple(Some(gst::CapsFeatures::new(&["format:Interlaced"]))); + } + + let mut caps = caps; + { + let caps = caps.get_mut().unwrap(); + caps.append(tmp); + } + + caps + }; + + let src_pad_template = gst::PadTemplate::new( + "src", + gst::PadDirection::Src, + gst::PadPresence::Always, + &caps, + ) + .unwrap(); + + vec![src_pad_template] + }); + + PAD_TEMPLATES.as_ref() + } + fn change_state( &self, - element: &gst::Element, + element: &Self::Type, transition: gst::StateChange, ) -> Result { match transition { @@ -437,13 +432,13 @@ impl ElementImpl for NdiVideoSrc { } impl BaseSrcImpl for NdiVideoSrc { - fn negotiate(&self, _element: &gst_base::BaseSrc) -> Result<(), gst::LoggableError> { + fn negotiate(&self, _element: &Self::Type) -> Result<(), gst::LoggableError> { // Always succeed here without doing anything: we will set the caps once we received a // buffer, there's nothing we can negotiate Ok(()) } - fn unlock(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { + fn unlock(&self, element: &Self::Type) -> Result<(), gst::ErrorMessage> { gst_debug!(self.cat, obj: element, "Unlocking",); if let Some(ref controller) = *self.receiver_controller.lock().unwrap() { controller.set_flushing(true); @@ -451,7 +446,7 @@ impl BaseSrcImpl for NdiVideoSrc { Ok(()) } - fn unlock_stop(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { + fn unlock_stop(&self, element: &Self::Type) -> Result<(), gst::ErrorMessage> { gst_debug!(self.cat, obj: element, "Stop unlocking",); if let Some(ref controller) = *self.receiver_controller.lock().unwrap() { controller.set_flushing(false); @@ -459,12 +454,12 @@ impl BaseSrcImpl for NdiVideoSrc { Ok(()) } - fn start(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { + fn start(&self, element: &Self::Type) -> Result<(), gst::ErrorMessage> { *self.state.lock().unwrap() = Default::default(); let settings = self.settings.lock().unwrap().clone(); if settings.ndi_name.is_none() && settings.url_address.is_none() { - return Err(gst_error_msg!( + return Err(gst::error_msg!( gst::LibraryError::Settings, ["No NDI name or URL/address given"] )); @@ -472,7 +467,7 @@ impl BaseSrcImpl for NdiVideoSrc { let receiver = connect_ndi( self.cat, - element, + element.upcast_ref(), settings.ndi_name.as_deref(), settings.url_address.as_deref(), &settings.receiver_ndi_name, @@ -485,7 +480,7 @@ impl BaseSrcImpl for NdiVideoSrc { // settings.id_receiver exists match receiver { - None => Err(gst_error_msg!( + None => Err(gst::error_msg!( gst::ResourceError::NotFound, ["Could not connect to this source"] )), @@ -500,7 +495,7 @@ impl BaseSrcImpl for NdiVideoSrc { } } - fn stop(&self, _element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> { + fn stop(&self, _element: &Self::Type) -> Result<(), gst::ErrorMessage> { if let Some(ref controller) = self.receiver_controller.lock().unwrap().take() { controller.shutdown(); } @@ -508,7 +503,7 @@ impl BaseSrcImpl for NdiVideoSrc { Ok(()) } - fn query(&self, element: &gst_base::BaseSrc, query: &mut gst::QueryRef) -> bool { + fn query(&self, element: &Self::Type, query: &mut gst::QueryRef) -> bool { use gst::QueryView; match query.view_mut() { @@ -521,14 +516,14 @@ impl BaseSrcImpl for NdiVideoSrc { let state = self.state.lock().unwrap(); let settings = self.settings.lock().unwrap(); - if state.current_latency.is_some() { + if let Some(latency) = state.current_latency { let min = if settings.timestamp_mode != TimestampMode::Timecode { - state.current_latency + latency } else { - 0.into() + gst::ClockTime::ZERO }; - let max = 5 * state.current_latency; + let max = 5 * latency; println!("Returning latency min {} max {}", min, max,); @@ -549,11 +544,11 @@ impl BaseSrcImpl for NdiVideoSrc { } } - fn fixate(&self, element: &gst_base::BaseSrc, mut caps: gst::Caps) -> gst::Caps { + fn fixate(&self, element: &Self::Type, mut caps: gst::Caps) -> gst::Caps { caps.truncate(); { let caps = caps.make_mut(); - let s = caps.get_mut_structure(0).unwrap(); + let s = caps.structure_mut(0).unwrap(); s.fixate_field_nearest_int("width", 1920); s.fixate_field_nearest_int("height", 1080); if s.has_field("pixel-aspect-ratio") { @@ -567,7 +562,7 @@ impl BaseSrcImpl for NdiVideoSrc { //Creates the video buffers fn create( &self, - element: &gst_base::BaseSrc, + element: &Self::Type, _offset: u64, _buffer: Option<&mut gst::BufferRef>, _length: u32, @@ -589,7 +584,7 @@ impl BaseSrcImpl for NdiVideoSrc { state.receiver = Some(recv); if state.info.as_ref() != Some(&info) { let caps = info.to_caps().map_err(|_| { - gst_element_error!( + gst::element_error!( element, gst::ResourceError::Settings, ["Invalid audio info received: {:?}", info] @@ -597,11 +592,11 @@ impl BaseSrcImpl for NdiVideoSrc { gst::FlowError::NotNegotiated })?; state.info = Some(info); - state.current_latency = buffer.get_duration(); + state.current_latency = buffer.duration(); drop(state); gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); element.set_caps(&caps).map_err(|_| { - gst_element_error!( + gst::element_error!( element, gst::CoreError::Negotiation, ["Failed to negotiate caps: {:?}", caps] @@ -621,12 +616,3 @@ impl BaseSrcImpl for NdiVideoSrc { } } } - -pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { - gst::Element::register( - Some(plugin), - "ndivideosrc", - gst::Rank::None, - NdiVideoSrc::get_type(), - ) -} diff --git a/src/ndivideosrc/mod.rs b/src/ndivideosrc/mod.rs new file mode 100644 index 00000000..ba9a7552 --- /dev/null +++ b/src/ndivideosrc/mod.rs @@ -0,0 +1,19 @@ +use glib::prelude::*; + +mod imp; + +glib::wrapper! { + pub struct NdiVideoSrc(ObjectSubclass) @extends gst_base::BaseSrc, gst::Element, gst::Object; +} + +unsafe impl Send for NdiVideoSrc {} +unsafe impl Sync for NdiVideoSrc {} + +pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { + gst::Element::register( + Some(plugin), + "ndivideosrc", + gst::Rank::None, + NdiVideoSrc::static_type(), + ) +} diff --git a/src/receiver.rs b/src/receiver.rs index dccb1907..8f7d12a0 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -1,6 +1,6 @@ use glib::prelude::*; use gst::prelude::*; -use gst::{gst_debug, gst_element_error, gst_error, gst_log, gst_warning}; +use gst::{gst_debug, gst_error, gst_log, gst_warning}; use gst_video::prelude::*; use byte_slice_cast::AsMutSliceOf; @@ -167,15 +167,14 @@ impl Observations { &self, cat: gst::DebugCategory, element: &gst_base::BaseSrc, - time: (gst::ClockTime, gst::ClockTime), - duration: gst::ClockTime, - ) -> (gst::ClockTime, gst::ClockTime) { - assert!(time.1.is_some()); + time: (Option, gst::ClockTime), + duration: Option, + ) -> (gst::ClockTime, Option) { if time.0.is_none() { return (time.1, duration); } - let time = (time.0.unwrap(), time.1.unwrap()); + let time = (time.0.unwrap(), time.1); let mut inner = self.0.lock().unwrap(); let ObservationsInner { @@ -190,8 +189,8 @@ impl Observations { } = *inner; if values.is_empty() { - current_mapping.xbase = time.0; - current_mapping.b = time.1; + current_mapping.xbase = time.0.nseconds(); + current_mapping.b = time.1.nseconds(); current_mapping.num = 1; current_mapping.den = 1; } @@ -207,7 +206,9 @@ impl Observations { // Start by first updating every frame, then every second frame, then every third // frame, etc. until we update once every quarter second - let framerate = (gst::SECOND / duration).unwrap_or(25) as usize; + let framerate = gst::ClockTime::SECOND + .checked_div(duration.unwrap_or(40 * gst::ClockTime::MSECOND).nseconds()) + .unwrap_or(25) as usize; if *skip_period < framerate / 4 + 1 { *skip_period += 1; @@ -221,7 +222,7 @@ impl Observations { if values.len() == WINDOW_LENGTH { values.remove(0); } - values.push(time); + values.push((time.0.nseconds(), time.1.nseconds())); if let Some((num, den, b, xbase, r_squared)) = gst::calculate_linear_regression(values, Some(values_tmp)) @@ -236,8 +237,8 @@ impl Observations { obj: element, "Calculated new time mapping: GStreamer time = {} * (NDI time - {}) + {} ({})", next_mapping.num as f64 / next_mapping.den as f64, - gst::ClockTime::from(next_mapping.xbase), - gst::ClockTime::from(next_mapping.b), + gst::ClockTime::from_nseconds(next_mapping.xbase), + gst::ClockTime::from_nseconds(next_mapping.b), r_squared, ); } @@ -250,81 +251,72 @@ impl Observations { if *time_mapping_pending { let expected = gst::Clock::adjust_with_calibration( - time.0.into(), - current_mapping.xbase.into(), - current_mapping.b.into(), - current_mapping.num.into(), - current_mapping.den.into(), + time.0, + gst::ClockTime::from_nseconds(current_mapping.xbase), + gst::ClockTime::from_nseconds(current_mapping.b), + gst::ClockTime::from_nseconds(current_mapping.num), + gst::ClockTime::from_nseconds(current_mapping.den), ); let new_calculated = gst::Clock::adjust_with_calibration( - time.0.into(), - next_mapping.xbase.into(), - next_mapping.b.into(), - next_mapping.num.into(), - next_mapping.den.into(), + time.0, + gst::ClockTime::from_nseconds(next_mapping.xbase), + gst::ClockTime::from_nseconds(next_mapping.b), + gst::ClockTime::from_nseconds(next_mapping.num), + gst::ClockTime::from_nseconds(next_mapping.den), ); - if let (Some(expected), Some(new_calculated)) = (*expected, *new_calculated) { - let diff = if new_calculated > expected { - new_calculated - expected - } else { - expected - new_calculated - }; - - // Allow at most 5% frame duration or 2ms difference per frame - let max_diff = cmp::max( - (duration / 10).unwrap_or(2 * gst::MSECOND_VAL), - 2 * gst::MSECOND_VAL, - ); - - if diff > max_diff { - gst_debug!( - cat, - obj: element, - "New time mapping causes difference {} but only {} allowed", - gst::ClockTime::from(diff), - gst::ClockTime::from(max_diff), - ); - - if new_calculated > expected { - current_mapping.b = expected + max_diff; - current_mapping.xbase = time.0; - } else { - current_mapping.b = expected - max_diff; - current_mapping.xbase = time.0; - } - } else { - *current_mapping = *next_mapping; - } + let diff = if new_calculated > expected { + new_calculated - expected } else { - gst_warning!( + expected - new_calculated + }; + + // Allow at most 5% frame duration or 2ms difference per frame + let max_diff = cmp::max( + (duration.map(|d| d / 10)).unwrap_or(2 * gst::ClockTime::MSECOND), + 2 * gst::ClockTime::MSECOND, + ); + + if diff > max_diff { + gst_debug!( cat, obj: element, - "Failed to calculate timestamps based on new mapping", + "New time mapping causes difference {} but only {} allowed", + diff, + max_diff, ); + + if new_calculated > expected { + current_mapping.b = (expected + max_diff).nseconds(); + current_mapping.xbase = time.0.nseconds(); + } else { + current_mapping.b = (expected - max_diff).nseconds(); + current_mapping.xbase = time.0.nseconds(); + } + } else { + *current_mapping = *next_mapping; } } let converted_timestamp = gst::Clock::adjust_with_calibration( - time.0.into(), - current_mapping.xbase.into(), - current_mapping.b.into(), - current_mapping.num.into(), - current_mapping.den.into(), + time.0, + gst::ClockTime::from_nseconds(current_mapping.xbase), + gst::ClockTime::from_nseconds(current_mapping.b), + gst::ClockTime::from_nseconds(current_mapping.num), + gst::ClockTime::from_nseconds(current_mapping.den), ); - let converted_duration = duration - .mul_div_floor(current_mapping.num, current_mapping.den) - .unwrap_or(gst::CLOCK_TIME_NONE); + let converted_duration = + duration.and_then(|d| d.mul_div_floor(current_mapping.num, current_mapping.den)); gst_debug!( cat, obj: element, "Converted timestamp {}/{} to {}, duration {} to {}", - gst::ClockTime::from(time.0), - gst::ClockTime::from(time.1), - converted_timestamp, - duration, - converted_duration, + time.0, + time.1, + converted_timestamp.display(), + duration.display(), + converted_duration.display(), ); (converted_timestamp, converted_duration) @@ -422,7 +414,7 @@ impl Receiver { Err(_) => { if let Some(receiver) = weak.upgrade().map(Receiver) { if let Some(element) = receiver.0.element.upgrade() { - gst_element_error!( + gst::element_error!( element, gst::LibraryError::Failed, ["Panic while connecting to NDI source"] @@ -558,7 +550,7 @@ where if (receiver.video.is_some() || !T::IS_VIDEO) && (receiver.audio.is_some() || T::IS_VIDEO) { - gst_element_error!( + gst::element_error!( element, gst::ResourceError::OpenRead, [ @@ -595,14 +587,14 @@ where // FIXME: Ideally we would use NDIlib_recv_color_format_fastest here but that seems to be // broken with interlaced content currently - let recv = RecvInstance::builder(ndi_name, url_address, &receiver_ndi_name) + let recv = RecvInstance::builder(ndi_name, url_address, receiver_ndi_name) .bandwidth(bandwidth) .color_format(NDIlib_recv_color_format_e::NDIlib_recv_color_format_UYVY_BGRA) .allow_video_fields(true) .build(); let recv = match recv { None => { - gst_element_error!( + gst::element_error!( element, gst::CoreError::Negotiation, ["Failed to connect to source"] @@ -785,44 +777,42 @@ impl Receiver { element: &gst_base::BaseSrc, timestamp: i64, timecode: i64, - duration: gst::ClockTime, - ) -> Option<(gst::ClockTime, gst::ClockTime)> { - let clock = match element.get_clock() { - None => return None, - Some(clock) => clock, - }; + duration: Option, + ) -> Option<(gst::ClockTime, Option)> { + let clock = element.clock()?; // For now take the current running time as PTS. At a later time we // will want to work with the timestamp given by the NDI SDK if available - let now = clock.get_time(); - let base_time = element.get_base_time(); + let now = clock.time()?; + let base_time = element.base_time()?; let receive_time = now - base_time; - let real_time_now = gst::ClockTime::from(glib::get_real_time() as u64 * 1000); + let real_time_now = gst::ClockTime::from_nseconds(glib::real_time() as u64 * 1000); let timestamp = if timestamp == ndisys::NDIlib_recv_timestamp_undefined { - gst::CLOCK_TIME_NONE + gst::ClockTime::NONE } else { - gst::ClockTime::from(timestamp as u64 * 100) + Some(gst::ClockTime::from_nseconds(timestamp as u64 * 100)) }; - let timecode = gst::ClockTime::from(timecode as u64 * 100); + let timecode = gst::ClockTime::from_nseconds(timecode as u64 * 100); gst_log!( self.0.cat, obj: element, "Received frame with timecode {}, timestamp {}, duration {}, receive time {}, local time now {}", timecode, - timestamp, - duration, - receive_time, + timestamp.display(), + duration.display(), + receive_time.display(), real_time_now, ); let (pts, duration) = match self.0.timestamp_mode { - TimestampMode::ReceiveTimeTimecode => { - self.0 - .observations - .process(self.0.cat, element, (timecode, receive_time), duration) - } + TimestampMode::ReceiveTimeTimecode => self.0.observations.process( + self.0.cat, + element, + (Some(timecode), receive_time), + duration, + ), TimestampMode::ReceiveTimeTimestamp => self.0.observations.process( self.0.cat, element, @@ -833,10 +823,11 @@ impl Receiver { TimestampMode::Timestamp if timestamp.is_none() => (receive_time, duration), TimestampMode::Timestamp => { // Timestamps are relative to the UNIX epoch + let timestamp = timestamp?; if real_time_now > timestamp { let diff = real_time_now - timestamp; if diff > receive_time { - (0.into(), duration) + (gst::ClockTime::ZERO, duration) } else { (receive_time - diff, duration) } @@ -851,8 +842,8 @@ impl Receiver { self.0.cat, obj: element, "Calculated PTS {}, duration {}", - pts, - duration, + pts.display(), + duration.display(), ); Some((pts, duration)) @@ -909,7 +900,7 @@ impl Receiver { let video_frame = match res { Err(_) => { - gst_element_error!( + gst::element_error!( element, gst::ResourceError::Read, ["Error receiving frame"] @@ -970,13 +961,11 @@ impl Receiver { &self, element: &gst_base::BaseSrc, video_frame: &VideoFrame, - ) -> Option<(gst::ClockTime, gst::ClockTime)> { - let duration = gst::SECOND - .mul_div_floor( - video_frame.frame_rate().1 as u64, - video_frame.frame_rate().0 as u64, - ) - .unwrap_or(gst::CLOCK_TIME_NONE); + ) -> Option<(gst::ClockTime, Option)> { + let duration = gst::ClockTime::SECOND.mul_div_floor( + video_frame.frame_rate().1 as u64, + video_frame.frame_rate().0 as u64, + ); self.calculate_timestamp( element, @@ -1004,7 +993,7 @@ impl Receiver { ndisys::NDIlib_FourCC_video_type_RGBA => gst_video::VideoFormat::Rgba, ndisys::NDIlib_FourCC_video_type_RGBX => gst_video::VideoFormat::Rgbx, _ => { - gst_element_error!( + gst::element_error!( element, gst::StreamError::Format, ["Unsupported video fourcc {:08x}", video_frame.fourcc()] @@ -1045,7 +1034,7 @@ impl Receiver { } builder.build().map_err(|_| { - gst_element_error!( + gst::element_error!( element, gst::StreamError::Format, ["Invalid video format configuration"] @@ -1062,7 +1051,7 @@ impl Receiver { && video_frame.frame_format_type() != ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_interleaved { - gst_element_error!( + gst::element_error!( element, gst::StreamError::Format, ["Separate field interlacing not supported"] @@ -1094,7 +1083,7 @@ impl Receiver { } builder.build().map_err(|_| { - gst_element_error!( + gst::element_error!( element, gst::StreamError::Format, ["Invalid video format configuration"] @@ -1109,7 +1098,7 @@ impl Receiver { &self, element: &gst_base::BaseSrc, pts: gst::ClockTime, - duration: gst::ClockTime, + duration: Option, info: &gst_video::VideoInfo, video_frame: &VideoFrame, ) -> gst::Buffer { @@ -1124,15 +1113,15 @@ impl Receiver { gst::ReferenceTimestampMeta::add( buffer, &*TIMECODE_CAPS, - gst::ClockTime::from(video_frame.timecode() as u64 * 100), - gst::CLOCK_TIME_NONE, + gst::ClockTime::from_nseconds(video_frame.timecode() as u64 * 100), + gst::ClockTime::NONE, ); if video_frame.timestamp() != ndisys::NDIlib_recv_timestamp_undefined { gst::ReferenceTimestampMeta::add( buffer, &*TIMESTAMP_CAPS, - gst::ClockTime::from(video_frame.timestamp() as u64 * 100), - gst::CLOCK_TIME_NONE, + gst::ClockTime::from_nseconds(video_frame.timestamp() as u64 * 100), + gst::ClockTime::NONE, ); } } @@ -1337,7 +1326,7 @@ impl Receiver { let audio_frame = match res { Err(_) => { - gst_element_error!( + gst::element_error!( element, gst::ResourceError::Read, ["Error receiving frame"] @@ -1398,13 +1387,11 @@ impl Receiver { &self, element: &gst_base::BaseSrc, audio_frame: &AudioFrame, - ) -> Option<(gst::ClockTime, gst::ClockTime)> { - let duration = gst::SECOND - .mul_div_floor( - audio_frame.no_samples() as u64, - audio_frame.sample_rate() as u64, - ) - .unwrap_or(gst::CLOCK_TIME_NONE); + ) -> Option<(gst::ClockTime, Option)> { + let duration = gst::ClockTime::SECOND.mul_div_floor( + audio_frame.no_samples() as u64, + audio_frame.sample_rate() as u64, + ); self.calculate_timestamp( element, @@ -1426,7 +1413,7 @@ impl Receiver { ); builder.build().map_err(|_| { - gst_element_error!( + gst::element_error!( element, gst::StreamError::Format, ["Invalid audio format configuration"] @@ -1440,7 +1427,7 @@ impl Receiver { &self, _element: &gst_base::BaseSrc, pts: gst::ClockTime, - duration: gst::ClockTime, + duration: Option, info: &gst_audio::AudioInfo, audio_frame: &AudioFrame, ) -> gst::Buffer { @@ -1458,15 +1445,15 @@ impl Receiver { gst::ReferenceTimestampMeta::add( buffer, &*TIMECODE_CAPS, - gst::ClockTime::from(audio_frame.timecode() as u64 * 100), - gst::CLOCK_TIME_NONE, + gst::ClockTime::from_nseconds(audio_frame.timecode() as u64 * 100), + gst::ClockTime::NONE, ); if audio_frame.timestamp() != ndisys::NDIlib_recv_timestamp_undefined { gst::ReferenceTimestampMeta::add( buffer, &*TIMESTAMP_CAPS, - gst::ClockTime::from(audio_frame.timestamp() as u64 * 100), - gst::CLOCK_TIME_NONE, + gst::ClockTime::from_nseconds(audio_frame.timestamp() as u64 * 100), + gst::ClockTime::NONE, ); } } From b3184b45bc21a9f1cc2b45421d2e3dacc7c1cedf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Wed, 29 Sep 2021 13:45:04 +0300 Subject: [PATCH 177/199] Combine audio/video source into a single element and add a demuxer for that This simplifies the code considerably. --- README.md | 14 +- src/device_provider/imp.rs | 91 +--- src/lib.rs | 11 +- src/ndi.rs | 98 +--- src/ndiaudiosrc/mod.rs | 19 - src/{ndiaudiosrc => ndisrc}/imp.rs | 228 ++++---- src/ndisrc/mod.rs | 19 + src/ndisrcdemux/imp.rs | 280 ++++++++++ src/ndisrcdemux/mod.rs | 19 + src/ndisrcmeta.rs | 158 ++++++ src/ndivideosrc/imp.rs | 618 ---------------------- src/ndivideosrc/mod.rs | 19 - src/receiver.rs | 824 +++++++++-------------------- 13 files changed, 920 insertions(+), 1478 deletions(-) delete mode 100644 src/ndiaudiosrc/mod.rs rename src/{ndiaudiosrc => ndisrc}/imp.rs (75%) create mode 100644 src/ndisrc/mod.rs create mode 100644 src/ndisrcdemux/imp.rs create mode 100644 src/ndisrcdemux/mod.rs create mode 100644 src/ndisrcmeta.rs delete mode 100644 src/ndivideosrc/imp.rs delete mode 100644 src/ndivideosrc/mod.rs diff --git a/README.md b/README.md index 5704660e..81c4da23 100644 --- a/README.md +++ b/README.md @@ -12,16 +12,14 @@ Some examples of how to use these elements from the command line: ``` #Information about the elements gst-inspect-1.0 ndi -gst-inspect-1.0 ndivideosrc -gst-inspect-1.0 ndiaudiosrc +gst-inspect-1.0 ndisrc +gst-inspect-1.0 ndisink -#Video pipeline -gst-launch-1.0 ndivideosrc ndi-name="GC-DEV2 (OBS)" ! autovideosink -#Audio pipeline -gst-launch-1.0 ndiaudiosrc ndi-name="GC-DEV2 (OBS)" ! autoaudiosink +# Audio/Video source pipeline +gst-launch-1.0 ndisrc ndi-name="GC-DEV2 (OBS)" ! ndisrcdemux name=demux demux.video ! queue ! videoconvert ! autovideosink demux.audio ! queue ! audioconvert ! autoaudiosink -#Video and audio pipeline -gst-launch-1.0 ndivideosrc ndi-name="GC-DEV2 (OBS)" ! autovideosink ndiaudiosrc ndi-name="GC-DEV2 (OBS)" ! autoaudiosink +# Audio/Video sink pipeline +gst-launch-1.0 videotestsrc is-live=true ! video/x-raw,format=UYVY ! ndisinkcombiner name=combiner ! ndisink ndi-name="My NDI source" audiotestsrc is-live=true ! combiner.audio ``` Feel free to contribute to this project. Some ways you can contribute are: diff --git a/src/device_provider/imp.rs b/src/device_provider/imp.rs index 5933741d..956a531b 100644 --- a/src/device_provider/imp.rs +++ b/src/device_provider/imp.rs @@ -12,9 +12,16 @@ use once_cell::sync::Lazy; use crate::ndi; +static CAT: Lazy = Lazy::new(|| { + gst::DebugCategory::new( + "ndideviceprovider", + gst::DebugColorFlags::empty(), + Some("NewTek NDI Device Provider"), + ) +}); + #[derive(Debug)] pub struct DeviceProvider { - cat: gst::DebugCategory, thread: Mutex>>, current_devices: Mutex>, find: Mutex>, @@ -29,11 +36,6 @@ impl ObjectSubclass for DeviceProvider { fn new() -> Self { Self { - cat: gst::DebugCategory::new( - "ndideviceprovider", - gst::DebugColorFlags::empty(), - Some("NewTek NDI Device Provider"), - ), thread: Mutex::new(None), current_devices: Mutex::new(vec![]), find: Mutex::new(None), @@ -68,11 +70,7 @@ impl DeviceProviderImpl for DeviceProvider { fn start(&self, device_provider: &Self::Type) -> Result<(), gst::LoggableError> { let mut thread_guard = self.thread.lock().unwrap(); if thread_guard.is_some() { - gst_log!( - self.cat, - obj: device_provider, - "Device provider already started" - ); + gst_log!(CAT, obj: device_provider, "Device provider already started"); return Ok(()); } @@ -90,17 +88,13 @@ impl DeviceProviderImpl for DeviceProvider { { let mut find_guard = imp.find.lock().unwrap(); if find_guard.is_some() { - gst_log!(imp.cat, obj: &device_provider, "Already started"); + gst_log!(CAT, obj: &device_provider, "Already started"); return; } let find = match ndi::FindInstance::builder().build() { None => { - gst_error!( - imp.cat, - obj: &device_provider, - "Failed to create Find instance" - ); + gst_error!(CAT, obj: &device_provider, "Failed to create Find instance"); return; } Some(find) => find, @@ -144,7 +138,7 @@ impl DeviceProvider { }; if !find.wait_for_sources(if first { 1000 } else { 5000 }) { - gst_trace!(self.cat, obj: device_provider, "No new sources found"); + gst_trace!(CAT, obj: device_provider, "No new sources found"); return; } @@ -160,9 +154,9 @@ impl DeviceProvider { let old_device_imp = Device::from_instance(old_device); let old_source = old_device_imp.source.get().unwrap(); - if !sources.contains(&old_source.0) { + if !sources.contains(&*old_source) { gst_log!( - self.cat, + CAT, obj: device_provider, "Source {:?} disappeared", old_source @@ -171,7 +165,7 @@ impl DeviceProvider { } else { // Otherwise remember that we had it before already and don't have to announce it // again. After the loop we're going to remove these all from the sources vec. - remaining_sources.push(old_source.0.to_owned()); + remaining_sources.push(old_source.to_owned()); } } @@ -188,18 +182,8 @@ impl DeviceProvider { // Now go through all new devices and announce them for source in sources { - gst_log!( - self.cat, - obj: device_provider, - "Source {:?} appeared", - source - ); - // Add once for audio, another time for video - let device = super::Device::new(&source, true); - device_provider.device_add(&device); - current_devices_guard.push(device); - - let device = super::Device::new(&source, false); + gst_log!(CAT, obj: device_provider, "Source {:?} appeared", source); + let device = super::Device::new(&source); device_provider.device_add(&device); current_devices_guard.push(device); } @@ -208,8 +192,7 @@ impl DeviceProvider { #[derive(Debug)] pub struct Device { - cat: gst::DebugCategory, - source: OnceCell<(ndi::Source<'static>, glib::Type)>, + source: OnceCell>, } #[glib::object_subclass] @@ -220,11 +203,6 @@ impl ObjectSubclass for Device { fn new() -> Self { Self { - cat: gst::DebugCategory::new( - "ndidevice", - gst::DebugColorFlags::empty(), - Some("NewTek NDI Device"), - ), source: OnceCell::new(), } } @@ -240,11 +218,11 @@ impl DeviceImpl for Device { ) -> Result { let source_info = self.source.get().unwrap(); let element = glib::Object::with_type( - source_info.1, + crate::ndisrc::NdiSrc::static_type(), &[ ("name", &name), - ("ndi-name", &source_info.0.ndi_name()), - ("url-address", &source_info.0.url_address()), + ("ndi-name", &source_info.ndi_name()), + ("url-address", &source_info.url_address()), ], ) .unwrap() @@ -256,24 +234,12 @@ impl DeviceImpl for Device { } impl super::Device { - fn new(source: &ndi::Source<'_>, is_audio: bool) -> super::Device { - let display_name = format!( - "{} ({})", - source.ndi_name(), - if is_audio { "Audio" } else { "Video" } - ); - let device_class = format!( - "Source/{}/Network", - if is_audio { "Audio" } else { "Video" } - ); + fn new(source: &ndi::Source<'_>) -> super::Device { + let display_name = source.ndi_name(); + let device_class = "Source/Audio/Video/Network"; - // Get the caps from the template caps of the corresponding source element - let element_type = if is_audio { - crate::ndiaudiosrc::NdiAudioSrc::static_type() - } else { - crate::ndivideosrc::NdiVideoSrc::static_type() - }; - let element_class = glib::Class::::from_type(element_type).unwrap(); + let element_class = + glib::Class::::from_type(crate::ndisrc::NdiSrc::static_type()).unwrap(); let templ = element_class.pad_template("src").unwrap(); let caps = templ.caps(); @@ -292,10 +258,7 @@ impl super::Device { .unwrap(); let device_impl = Device::from_instance(&device); - device_impl - .source - .set((source.to_owned(), element_type)) - .unwrap(); + device_impl.source.set(source.to_owned()).unwrap(); device } diff --git a/src/lib.rs b/src/lib.rs index 752ea686..790c5bb1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,21 +1,21 @@ mod device_provider; pub mod ndi; -mod ndiaudiosrc; #[cfg(feature = "sink")] mod ndisink; #[cfg(feature = "sink")] mod ndisinkcombiner; #[cfg(feature = "sink")] pub mod ndisinkmeta; +mod ndisrc; +mod ndisrcdemux; +pub mod ndisrcmeta; pub mod ndisys; -mod ndivideosrc; pub mod receiver; use crate::ndi::*; use crate::ndisys::*; use crate::receiver::*; -use std::collections::HashMap; use std::time; use once_cell::sync::Lazy; @@ -41,8 +41,9 @@ fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { device_provider::register(plugin)?; - ndivideosrc::register(plugin)?; - ndiaudiosrc::register(plugin)?; + ndisrc::register(plugin)?; + ndisrcdemux::register(plugin)?; + #[cfg(feature = "sink")] { ndisinkcombiner::register(plugin)?; diff --git a/src/ndi.rs b/src/ndi.rs index b4043b92..648db019 100644 --- a/src/ndi.rs +++ b/src/ndi.rs @@ -3,7 +3,6 @@ use crate::ndisys::*; use std::ffi; use std::mem; use std::ptr; -use std::sync::{Arc, Mutex}; use byte_slice_cast::*; @@ -242,27 +241,16 @@ impl<'a> RecvBuilder<'a> { if ptr.is_null() { None } else { - Some(RecvInstance(Arc::new(( - RecvInstanceInner(ptr::NonNull::new_unchecked(ptr)), - Mutex::new(()), - )))) + Some(RecvInstance(ptr::NonNull::new_unchecked(ptr))) } } } } -// Any access to the RecvInstanceInner apart from calling the capture function must be protected by -// the mutex #[derive(Debug, Clone)] -pub struct RecvInstance(Arc<(RecvInstanceInner, Mutex<()>)>); +pub struct RecvInstance(ptr::NonNull<::std::os::raw::c_void>); -#[derive(Debug)] -struct RecvInstanceInner(ptr::NonNull<::std::os::raw::c_void>); -unsafe impl Send for RecvInstanceInner {} - -// Not 100% true but we ensure safety with the mutex. The documentation says that only the -// capturing itself can be performed from multiple threads at once safely. -unsafe impl Sync for RecvInstanceInner {} +unsafe impl Send for RecvInstance {} impl RecvInstance { pub fn builder<'a>( @@ -281,38 +269,24 @@ impl RecvInstance { } pub fn set_tally(&self, tally: &Tally) -> bool { - unsafe { - let _lock = (self.0).1.lock().unwrap(); - NDIlib_recv_set_tally(((self.0).0).0.as_ptr(), &tally.0) - } + unsafe { NDIlib_recv_set_tally(self.0.as_ptr(), &tally.0) } } pub fn send_metadata(&self, metadata: &MetadataFrame) -> bool { - unsafe { - let _lock = (self.0).1.lock().unwrap(); - NDIlib_recv_send_metadata(((self.0).0).0.as_ptr(), metadata.as_ptr()) - } + unsafe { NDIlib_recv_send_metadata(self.0.as_ptr(), metadata.as_ptr()) } } pub fn get_queue(&self) -> Queue { unsafe { - let _lock = (self.0).1.lock().unwrap(); let mut queue = mem::MaybeUninit::uninit(); - NDIlib_recv_get_queue(((self.0).0).0.as_ptr(), queue.as_mut_ptr()); + NDIlib_recv_get_queue(self.0.as_ptr(), queue.as_mut_ptr()); Queue(queue.assume_init()) } } - pub fn capture( - &self, - video: bool, - audio: bool, - metadata: bool, - timeout_in_ms: u32, - ) -> Result, ()> { + pub fn capture(&self, timeout_in_ms: u32) -> Result, ()> { unsafe { - // Capturing from multiple threads at once is safe according to the documentation - let ptr = ((self.0).0).0.as_ptr(); + let ptr = self.0.as_ptr(); let mut video_frame = mem::zeroed(); let mut audio_frame = mem::zeroed(); @@ -320,46 +294,22 @@ impl RecvInstance { let res = NDIlib_recv_capture_v2( ptr, - if video { - &mut video_frame - } else { - ptr::null_mut() - }, - if audio { - &mut audio_frame - } else { - ptr::null_mut() - }, - if metadata { - &mut metadata_frame - } else { - ptr::null_mut() - }, + &mut video_frame, + &mut audio_frame, + &mut metadata_frame, timeout_in_ms, ); match res { - NDIlib_frame_type_e::NDIlib_frame_type_audio => { - assert!(audio); - Ok(Some(Frame::Audio(AudioFrame::BorrowedRecv( - audio_frame, - self, - )))) - } - NDIlib_frame_type_e::NDIlib_frame_type_video => { - assert!(video); - Ok(Some(Frame::Video(VideoFrame::BorrowedRecv( - video_frame, - self, - )))) - } - NDIlib_frame_type_e::NDIlib_frame_type_metadata => { - assert!(metadata); - Ok(Some(Frame::Metadata(MetadataFrame::Borrowed( - metadata_frame, - self, - )))) - } + 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), } @@ -367,7 +317,7 @@ impl RecvInstance { } } -impl Drop for RecvInstanceInner { +impl Drop for RecvInstance { fn drop(&mut self) { unsafe { NDIlib_recv_destroy(self.0.as_ptr() as *mut _) } } @@ -751,7 +701,7 @@ impl<'a> Drop for VideoFrame<'a> { fn drop(&mut self) { if let VideoFrame::BorrowedRecv(ref mut frame, recv) = *self { unsafe { - NDIlib_recv_free_video_v2(((recv.0).0).0.as_ptr() as *mut _, frame); + NDIlib_recv_free_video_v2(recv.0.as_ptr() as *mut _, frame); } } } @@ -920,7 +870,7 @@ impl<'a> Drop for AudioFrame<'a> { fn drop(&mut self) { if let AudioFrame::BorrowedRecv(ref mut frame, recv) = *self { unsafe { - NDIlib_recv_free_audio_v2(((recv.0).0).0.as_ptr() as *mut _, frame); + NDIlib_recv_free_audio_v2(recv.0.as_ptr() as *mut _, frame); } } } @@ -1012,7 +962,7 @@ 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).0).0.as_ptr() as *mut _, frame); + NDIlib_recv_free_metadata(recv.0.as_ptr() as *mut _, frame); } } } diff --git a/src/ndiaudiosrc/mod.rs b/src/ndiaudiosrc/mod.rs deleted file mode 100644 index 11b94849..00000000 --- a/src/ndiaudiosrc/mod.rs +++ /dev/null @@ -1,19 +0,0 @@ -use glib::prelude::*; - -mod imp; - -glib::wrapper! { - pub struct NdiAudioSrc(ObjectSubclass) @extends gst_base::BaseSrc, gst::Element, gst::Object; -} - -unsafe impl Send for NdiAudioSrc {} -unsafe impl Sync for NdiAudioSrc {} - -pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { - gst::Element::register( - Some(plugin), - "ndiaudiosrc", - gst::Rank::None, - NdiAudioSrc::static_type(), - ) -} diff --git a/src/ndiaudiosrc/imp.rs b/src/ndisrc/imp.rs similarity index 75% rename from src/ndiaudiosrc/imp.rs rename to src/ndisrc/imp.rs index 3891a41d..4ab3e28d 100644 --- a/src/ndiaudiosrc/imp.rs +++ b/src/ndisrc/imp.rs @@ -10,16 +10,24 @@ use std::{i32, u32}; use once_cell::sync::Lazy; -use crate::connect_ndi; use crate::ndisys; -use crate::AudioReceiver; +use crate::ndisrcmeta; +use crate::Buffer; use crate::Receiver; use crate::ReceiverControlHandle; use crate::ReceiverItem; use crate::TimestampMode; use crate::DEFAULT_RECEIVER_NDI_NAME; +static CAT: Lazy = Lazy::new(|| { + gst::DebugCategory::new( + "ndisrc", + gst::DebugColorFlags::empty(), + Some("NewTek NDI Source"), + ) +}); + #[derive(Debug, Clone)] struct Settings { ndi_name: Option, @@ -40,7 +48,7 @@ impl Default for Settings { receiver_ndi_name: DEFAULT_RECEIVER_NDI_NAME.clone(), connect_timeout: 10000, timeout: 5000, - max_queue_length: 5, + max_queue_length: 10, bandwidth: ndisys::NDIlib_recv_bandwidth_highest, timestamp_mode: TimestampMode::ReceiveTimeTimecode, } @@ -48,41 +56,41 @@ impl Default for Settings { } struct State { - info: Option, - receiver: Option>, + video_info: Option, + video_caps: Option, + audio_info: Option, + audio_caps: Option, current_latency: Option, + receiver: Option, } impl Default for State { fn default() -> State { State { - info: None, - receiver: None, + video_info: None, + video_caps: None, + audio_info: None, + audio_caps: None, current_latency: gst::ClockTime::NONE, + receiver: None, } } } -pub struct NdiAudioSrc { - cat: gst::DebugCategory, +pub struct NdiSrc { settings: Mutex, state: Mutex, - receiver_controller: Mutex>>, + receiver_controller: Mutex>, } #[glib::object_subclass] -impl ObjectSubclass for NdiAudioSrc { - const NAME: &'static str = "NdiAudioSrc"; - type Type = super::NdiAudioSrc; +impl ObjectSubclass for NdiSrc { + const NAME: &'static str = "NdiSrc"; + type Type = super::NdiSrc; type ParentType = gst_base::BaseSrc; fn new() -> Self { Self { - cat: gst::DebugCategory::new( - "ndiaudiosrc", - gst::DebugColorFlags::empty(), - Some("NewTek NDI Audio Source"), - ), settings: Mutex::new(Default::default()), state: Mutex::new(Default::default()), receiver_controller: Mutex::new(None), @@ -90,7 +98,7 @@ impl ObjectSubclass for NdiAudioSrc { } } -impl ObjectImpl for NdiAudioSrc { +impl ObjectImpl for NdiSrc { fn properties() -> &'static [glib::ParamSpec] { static PROPERTIES: Lazy> = Lazy::new(|| { vec![ @@ -139,7 +147,7 @@ impl ObjectImpl for NdiAudioSrc { "Maximum receive queue length", 0, u32::MAX, - 5, + 10, glib::ParamFlags::READWRITE, ), glib::ParamSpec::new_int( @@ -186,7 +194,7 @@ impl ObjectImpl for NdiAudioSrc { let mut settings = self.settings.lock().unwrap(); let ndi_name = value.get().unwrap(); gst_debug!( - self.cat, + CAT, obj: obj, "Changing ndi-name from {:?} to {:?}", settings.ndi_name, @@ -198,7 +206,7 @@ impl ObjectImpl for NdiAudioSrc { let mut settings = self.settings.lock().unwrap(); let url_address = value.get().unwrap(); gst_debug!( - self.cat, + CAT, obj: obj, "Changing url-address from {:?} to {:?}", settings.url_address, @@ -210,7 +218,7 @@ impl ObjectImpl for NdiAudioSrc { let mut settings = self.settings.lock().unwrap(); let receiver_ndi_name = value.get::>().unwrap(); gst_debug!( - self.cat, + CAT, obj: obj, "Changing receiver-ndi-name from {:?} to {:?}", settings.receiver_ndi_name, @@ -223,7 +231,7 @@ impl ObjectImpl for NdiAudioSrc { let mut settings = self.settings.lock().unwrap(); let connect_timeout = value.get().unwrap(); gst_debug!( - self.cat, + CAT, obj: obj, "Changing connect-timeout from {} to {}", settings.connect_timeout, @@ -235,7 +243,7 @@ impl ObjectImpl for NdiAudioSrc { let mut settings = self.settings.lock().unwrap(); let timeout = value.get().unwrap(); gst_debug!( - self.cat, + CAT, obj: obj, "Changing timeout from {} to {}", settings.timeout, @@ -247,7 +255,7 @@ impl ObjectImpl for NdiAudioSrc { let mut settings = self.settings.lock().unwrap(); let max_queue_length = value.get().unwrap(); gst_debug!( - self.cat, + CAT, obj: obj, "Changing max-queue-length from {} to {}", settings.max_queue_length, @@ -259,7 +267,7 @@ impl ObjectImpl for NdiAudioSrc { let mut settings = self.settings.lock().unwrap(); let bandwidth = value.get().unwrap(); gst_debug!( - self.cat, + CAT, obj: obj, "Changing bandwidth from {} to {}", settings.bandwidth, @@ -271,7 +279,7 @@ impl ObjectImpl for NdiAudioSrc { let mut settings = self.settings.lock().unwrap(); let timestamp_mode = value.get().unwrap(); gst_debug!( - self.cat, + CAT, obj: obj, "Changing timestamp mode from {:?} to {:?}", settings.timestamp_mode, @@ -325,13 +333,13 @@ impl ObjectImpl for NdiAudioSrc { } } -impl ElementImpl for NdiAudioSrc { +impl ElementImpl for NdiSrc { fn metadata() -> Option<&'static gst::subclass::ElementMetadata> { static ELEMENT_METADATA: Lazy = Lazy::new(|| { gst::subclass::ElementMetadata::new( - "NewTek NDI Audio Source", - "Source", - "NewTek NDI audio source", + "NewTek NDI Source", + "Source/Audio/Video/Network", + "NewTek NDI source", "Ruben Gonzalez , Daniel Vilar , Sebastian Dröge ", ) }); @@ -341,28 +349,15 @@ impl ElementImpl for NdiAudioSrc { fn pad_templates() -> &'static [gst::PadTemplate] { static PAD_TEMPLATES: Lazy> = Lazy::new(|| { - let caps = gst::Caps::new_simple( - "audio/x-raw", - &[ - ( - "format", - &gst::List::new(&[&gst_audio::AUDIO_FORMAT_S16.to_string()]), - ), - ("rate", &gst::IntRange::::new(1, i32::MAX)), - ("channels", &gst::IntRange::::new(1, i32::MAX)), - ("layout", &"interleaved"), - ], - ); - - let audio_src_pad_template = gst::PadTemplate::new( + let src_pad_template = gst::PadTemplate::new( "src", gst::PadDirection::Src, - gst::PadPresence::Sometimes, - &caps, + gst::PadPresence::Always, + &gst::Caps::builder("application/x-ndi").build(), ) .unwrap(); - vec![audio_src_pad_template] + vec![src_pad_template] }); PAD_TEMPLATES.as_ref() @@ -396,15 +391,15 @@ impl ElementImpl for NdiAudioSrc { } } -impl BaseSrcImpl for NdiAudioSrc { - fn negotiate(&self, _element: &Self::Type) -> Result<(), gst::LoggableError> { - // Always succeed here without doing anything: we will set the caps once we received a - // buffer, there's nothing we can negotiate - Ok(()) +impl BaseSrcImpl for NdiSrc { + fn negotiate(&self, element: &Self::Type) -> Result<(), gst::LoggableError> { + element + .set_caps(&gst::Caps::builder("application/x-ndi").build()) + .map_err(|_| gst::loggable_error!(CAT, "Failed to negotiate caps",)) } fn unlock(&self, element: &Self::Type) -> Result<(), gst::ErrorMessage> { - gst_debug!(self.cat, obj: element, "Unlocking",); + gst_debug!(CAT, obj: element, "Unlocking",); if let Some(ref controller) = *self.receiver_controller.lock().unwrap() { controller.set_flushing(true); } @@ -412,7 +407,7 @@ impl BaseSrcImpl for NdiAudioSrc { } fn unlock_stop(&self, element: &Self::Type) -> Result<(), gst::ErrorMessage> { - gst_debug!(self.cat, obj: element, "Stop unlocking",); + gst_debug!(CAT, obj: element, "Stop unlocking",); if let Some(ref controller) = *self.receiver_controller.lock().unwrap() { controller.set_flushing(false); } @@ -430,8 +425,7 @@ impl BaseSrcImpl for NdiAudioSrc { )); } - let receiver = connect_ndi( - self.cat, + let receiver = Receiver::connect( element.upcast_ref(), settings.ndi_name.as_deref(), settings.url_address.as_deref(), @@ -443,7 +437,6 @@ impl BaseSrcImpl for NdiAudioSrc { settings.max_queue_length as usize, ); - // settings.id_receiver exists match receiver { None => Err(gst::error_msg!( gst::ResourceError::NotFound, @@ -488,10 +481,10 @@ impl BaseSrcImpl for NdiAudioSrc { gst::ClockTime::ZERO }; - let max = 5 * latency; + let max = settings.max_queue_length as u64 * latency; gst_debug!( - self.cat, + CAT, obj: element, "Returning latency min {} max {}", min, @@ -507,18 +500,6 @@ impl BaseSrcImpl for NdiAudioSrc { } } - fn fixate(&self, element: &Self::Type, mut caps: gst::Caps) -> gst::Caps { - caps.truncate(); - { - let caps = caps.make_mut(); - let s = caps.structure_mut(0).unwrap(); - s.fixate_field_nearest_int("rate", 48_000); - s.fixate_field_nearest_int("channels", 2); - } - - self.parent_fixate(element, caps) - } - fn create( &self, element: &Self::Type, @@ -531,46 +512,87 @@ impl BaseSrcImpl for NdiAudioSrc { match state.receiver.take() { Some(recv) => recv, None => { - gst_error!(self.cat, obj: element, "Have no receiver"); + gst_error!(CAT, obj: element, "Have no receiver"); return Err(gst::FlowError::Error); } } }; - match recv.capture() { - ReceiverItem::Buffer(buffer, info) => { - let mut state = self.state.lock().unwrap(); - state.receiver = Some(recv); - if state.info.as_ref() != Some(&info) { - let caps = info.to_caps().map_err(|_| { - gst::element_error!( - element, - gst::ResourceError::Settings, - ["Invalid audio info received: {:?}", info] - ); - gst::FlowError::NotNegotiated - })?; - state.info = Some(info); - state.current_latency = buffer.duration(); - drop(state); - gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); - element.set_caps(&caps).map_err(|_| { - gst::element_error!( - element, - gst::CoreError::Negotiation, - ["Failed to negotiate caps: {:?}", caps] - ); - gst::FlowError::NotNegotiated - })?; + let res = recv.capture(); - let _ = - element.post_message(gst::message::Latency::builder().src(element).build()); - } + let mut state = self.state.lock().unwrap(); + state.receiver = Some(recv); + + match res { + ReceiverItem::Buffer(buffer) => { + let buffer = match buffer { + Buffer::Audio(mut buffer, info) => { + if state.audio_info.as_ref() != Some(&info) { + let caps = info.to_caps().map_err(|_| { + gst::element_error!( + element, + gst::ResourceError::Settings, + ["Invalid audio info received: {:?}", info] + ); + gst::FlowError::NotNegotiated + })?; + state.audio_info = Some(info); + state.audio_caps = Some(caps); + } + + { + let buffer = buffer.get_mut().unwrap(); + ndisrcmeta::NdiSrcMeta::add( + buffer, + ndisrcmeta::StreamType::Audio, + state.audio_caps.as_ref().unwrap(), + ); + } + + buffer + } + Buffer::Video(mut buffer, info) => { + let mut latency_changed = false; + + if state.video_info.as_ref() != Some(&info) { + let caps = info.to_caps().map_err(|_| { + gst::element_error!( + element, + gst::ResourceError::Settings, + ["Invalid audio info received: {:?}", info] + ); + gst::FlowError::NotNegotiated + })?; + state.video_info = Some(info); + state.video_caps = Some(caps); + latency_changed = state.current_latency != buffer.duration(); + state.current_latency = buffer.duration(); + } + + { + let buffer = buffer.get_mut().unwrap(); + ndisrcmeta::NdiSrcMeta::add( + buffer, + ndisrcmeta::StreamType::Video, + state.video_caps.as_ref().unwrap(), + ); + } + + drop(state); + if latency_changed { + let _ = element.post_message( + gst::message::Latency::builder().src(element).build(), + ); + } + + buffer + } + }; Ok(CreateSuccess::NewBuffer(buffer)) } - ReceiverItem::Flushing => Err(gst::FlowError::Flushing), ReceiverItem::Timeout => Err(gst::FlowError::Eos), + ReceiverItem::Flushing => Err(gst::FlowError::Flushing), ReceiverItem::Error(err) => Err(err), } } diff --git a/src/ndisrc/mod.rs b/src/ndisrc/mod.rs new file mode 100644 index 00000000..e603d69d --- /dev/null +++ b/src/ndisrc/mod.rs @@ -0,0 +1,19 @@ +use glib::prelude::*; + +mod imp; + +glib::wrapper! { + pub struct NdiSrc(ObjectSubclass) @extends gst_base::BaseSrc, gst::Element, gst::Object; +} + +unsafe impl Send for NdiSrc {} +unsafe impl Sync for NdiSrc {} + +pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { + gst::Element::register( + Some(plugin), + "ndisrc", + gst::Rank::None, + NdiSrc::static_type(), + ) +} diff --git a/src/ndisrcdemux/imp.rs b/src/ndisrcdemux/imp.rs new file mode 100644 index 00000000..9cf8180c --- /dev/null +++ b/src/ndisrcdemux/imp.rs @@ -0,0 +1,280 @@ +use gst::prelude::*; +use gst::subclass::prelude::*; +use gst::{gst_debug, gst_error, gst_log}; + +use std::sync::Mutex; + +use once_cell::sync::Lazy; + +use crate::ndisrcmeta; + +static CAT: Lazy = Lazy::new(|| { + gst::DebugCategory::new( + "ndisrcdemux", + gst::DebugColorFlags::empty(), + Some("NewTek NDI Source Demuxer"), + ) +}); + +#[derive(Default)] +struct State { + combiner: gst_base::UniqueFlowCombiner, + video_pad: Option, + video_caps: Option, + + audio_pad: Option, + audio_caps: Option, +} + +pub struct NdiSrcDemux { + sinkpad: gst::Pad, + state: Mutex, +} + +#[glib::object_subclass] +impl ObjectSubclass for NdiSrcDemux { + const NAME: &'static str = "NdiSrcDemux"; + type Type = super::NdiSrcDemux; + type ParentType = gst::Element; + + fn with_class(klass: &Self::Class) -> Self { + let templ = klass.pad_template("sink").unwrap(); + let sinkpad = gst::Pad::builder_with_template(&templ, Some("sink")) + .flags(gst::PadFlags::FIXED_CAPS) + .chain_function(|pad, parent, buffer| { + NdiSrcDemux::catch_panic_pad_function( + parent, + || Err(gst::FlowError::Error), + |self_, element| self_.sink_chain(pad, element, buffer), + ) + }) + .build(); + + Self { + sinkpad, + state: Mutex::new(State::default()), + } + } +} + +impl ObjectImpl for NdiSrcDemux { + fn constructed(&self, obj: &Self::Type) { + self.parent_constructed(obj); + + obj.add_pad(&self.sinkpad).unwrap(); + } +} + +impl ElementImpl for NdiSrcDemux { + fn metadata() -> Option<&'static gst::subclass::ElementMetadata> { + static ELEMENT_METADATA: Lazy = Lazy::new(|| { + gst::subclass::ElementMetadata::new( + "NewTek NDI Source Demuxer", + "Demuxer/Audio/Video", + "NewTek NDI source demuxer", + "Sebastian Dröge ", + ) + }); + + Some(&*ELEMENT_METADATA) + } + + fn pad_templates() -> &'static [gst::PadTemplate] { + static PAD_TEMPLATES: Lazy> = Lazy::new(|| { + let sink_pad_template = gst::PadTemplate::new( + "sink", + gst::PadDirection::Sink, + gst::PadPresence::Always, + &gst::Caps::builder("application/x-ndi").build(), + ) + .unwrap(); + + let audio_src_pad_template = gst::PadTemplate::new( + "audio", + gst::PadDirection::Src, + gst::PadPresence::Sometimes, + &gst::Caps::builder("audio/x-raw").build(), + ) + .unwrap(); + + let video_src_pad_template = gst::PadTemplate::new( + "video", + gst::PadDirection::Src, + gst::PadPresence::Sometimes, + &gst::Caps::builder("video/x-raw").build(), + ) + .unwrap(); + + vec![ + sink_pad_template, + audio_src_pad_template, + video_src_pad_template, + ] + }); + + PAD_TEMPLATES.as_ref() + } + + fn change_state( + &self, + element: &Self::Type, + transition: gst::StateChange, + ) -> Result { + let res = self.parent_change_state(element, transition)?; + + match transition { + gst::StateChange::PausedToReady => { + let mut state = self.state.lock().unwrap(); + for pad in [state.audio_pad.take(), state.video_pad.take()] + .iter() + .flatten() + { + element.remove_pad(pad).unwrap(); + } + *state = State::default(); + } + _ => (), + } + + Ok(res) + } +} + +impl NdiSrcDemux { + fn sink_chain( + &self, + pad: &gst::Pad, + element: &super::NdiSrcDemux, + buffer: gst::Buffer, + ) -> Result { + gst_log!(CAT, obj: pad, "Handling buffer {:?}", buffer); + + let meta = buffer.meta::().ok_or_else(|| { + gst_error!(CAT, obj: element, "Buffer without NDI source meta"); + gst::FlowError::Error + })?; + + let mut events = vec![]; + let srcpad; + let mut add_pad = false; + + let mut state = self.state.lock().unwrap(); + let caps = meta.caps(); + match meta.stream_type() { + ndisrcmeta::StreamType::Audio => { + if let Some(ref pad) = state.audio_pad { + srcpad = pad.clone(); + } else { + gst_debug!(CAT, obj: element, "Adding audio pad with caps {}", caps); + + let klass = element.element_class(); + let templ = klass.pad_template("audio").unwrap(); + let pad = gst::Pad::builder_with_template(&templ, Some("audio")) + .flags(gst::PadFlags::FIXED_CAPS) + .build(); + + let mut caps_event = Some(gst::event::Caps::new(&caps)); + + self.sinkpad.sticky_events_foreach(|ev| { + if ev.type_() < gst::EventType::Caps { + events.push(ev.clone()); + } else { + if let Some(ev) = caps_event.take() { + events.push(ev); + } + + if ev.type_() != gst::EventType::Caps { + events.push(ev.clone()); + } + } + + Ok(Some(ev)) + }); + + state.audio_caps = Some(caps.clone()); + state.audio_pad = Some(pad.clone()); + + let _ = pad.set_active(true); + for ev in events.drain(..) { + let _ = pad.store_sticky_event(&ev); + } + + state.combiner.add_pad(&pad); + + add_pad = true; + srcpad = pad; + } + + if state.audio_caps.as_ref() != Some(&caps) { + gst_debug!(CAT, obj: element, "Audio caps changed to {}", caps); + events.push(gst::event::Caps::new(&caps)); + state.audio_caps = Some(caps); + } + } + ndisrcmeta::StreamType::Video => { + if let Some(ref pad) = state.video_pad { + srcpad = pad.clone(); + } else { + gst_debug!(CAT, obj: element, "Adding video pad with caps {}", caps); + + let klass = element.element_class(); + let templ = klass.pad_template("video").unwrap(); + let pad = gst::Pad::builder_with_template(&templ, Some("video")) + .flags(gst::PadFlags::FIXED_CAPS) + .build(); + + let mut caps_event = Some(gst::event::Caps::new(&caps)); + + self.sinkpad.sticky_events_foreach(|ev| { + if ev.type_() < gst::EventType::Caps { + events.push(ev.clone()); + } else { + if let Some(ev) = caps_event.take() { + events.push(ev); + } + + if ev.type_() != gst::EventType::Caps { + events.push(ev.clone()); + } + } + + Ok(Some(ev)) + }); + + state.video_caps = Some(caps.clone()); + state.video_pad = Some(pad.clone()); + + let _ = pad.set_active(true); + for ev in events.drain(..) { + let _ = pad.store_sticky_event(&ev); + } + + state.combiner.add_pad(&pad); + + add_pad = true; + srcpad = pad; + } + + if state.video_caps.as_ref() != Some(&caps) { + gst_debug!(CAT, obj: element, "Video caps changed to {}", caps); + events.push(gst::event::Caps::new(&caps)); + state.video_caps = Some(caps); + } + } + } + drop(state); + + if add_pad { + element.add_pad(&srcpad).unwrap(); + } + + for ev in events { + srcpad.push_event(ev); + } + + let res = srcpad.push(buffer); + + let mut state = self.state.lock().unwrap(); + state.combiner.update_pad_flow(&srcpad, res) + } +} diff --git a/src/ndisrcdemux/mod.rs b/src/ndisrcdemux/mod.rs new file mode 100644 index 00000000..12c78f72 --- /dev/null +++ b/src/ndisrcdemux/mod.rs @@ -0,0 +1,19 @@ +use glib::prelude::*; + +mod imp; + +glib::wrapper! { + pub struct NdiSrcDemux(ObjectSubclass) @extends gst::Element, gst::Object; +} + +unsafe impl Send for NdiSrcDemux {} +unsafe impl Sync for NdiSrcDemux {} + +pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { + gst::Element::register( + Some(plugin), + "ndisrcdemux", + gst::Rank::Primary, + NdiSrcDemux::static_type(), + ) +} diff --git a/src/ndisrcmeta.rs b/src/ndisrcmeta.rs new file mode 100644 index 00000000..c3f04651 --- /dev/null +++ b/src/ndisrcmeta.rs @@ -0,0 +1,158 @@ +use gst::prelude::*; +use std::fmt; +use std::mem; + +#[repr(transparent)] +pub struct NdiSrcMeta(imp::NdiSrcMeta); + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum StreamType { + Audio, + Video, +} + +unsafe impl Send for NdiSrcMeta {} +unsafe impl Sync for NdiSrcMeta {} + +impl NdiSrcMeta { + pub fn add<'a>( + buffer: &'a mut gst::BufferRef, + stream_type: StreamType, + caps: &gst::Caps, + ) -> gst::MetaRefMut<'a, Self, gst::meta::Standalone> { + unsafe { + // Manually dropping because gst_buffer_add_meta() takes ownership of the + // content of the struct + let mut params = mem::ManuallyDrop::new(imp::NdiSrcMetaParams { + caps: caps.clone(), + stream_type, + }); + + let meta = gst::ffi::gst_buffer_add_meta( + buffer.as_mut_ptr(), + imp::ndi_src_meta_get_info(), + &mut *params as *mut imp::NdiSrcMetaParams as glib::ffi::gpointer, + ) as *mut imp::NdiSrcMeta; + + Self::from_mut_ptr(buffer, meta) + } + } + + pub fn stream_type(&self) -> StreamType { + self.0.stream_type + } + + pub fn caps(&self) -> gst::Caps { + self.0.caps.clone() + } +} + +unsafe impl MetaAPI for NdiSrcMeta { + type GstType = imp::NdiSrcMeta; + + fn meta_api() -> glib::Type { + imp::ndi_src_meta_api_get_type() + } +} + +impl fmt::Debug for NdiSrcMeta { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("NdiSrcMeta") + .field("stream_type", &self.stream_type()) + .field("caps", &self.caps()) + .finish() + } +} + +mod imp { + use super::StreamType; + use glib::translate::*; + use once_cell::sync::Lazy; + use std::mem; + use std::ptr; + + pub(super) struct NdiSrcMetaParams { + pub caps: gst::Caps, + pub stream_type: StreamType, + } + + #[repr(C)] + pub struct NdiSrcMeta { + parent: gst::ffi::GstMeta, + pub(super) caps: gst::Caps, + pub(super) stream_type: StreamType, + } + + pub(super) fn ndi_src_meta_api_get_type() -> glib::Type { + static TYPE: Lazy = Lazy::new(|| unsafe { + let t = from_glib(gst::ffi::gst_meta_api_type_register( + b"GstNdiSrcMetaAPI\0".as_ptr() as *const _, + [ptr::null::()].as_ptr() as *mut *const _, + )); + + assert_ne!(t, glib::Type::INVALID); + + t + }); + + *TYPE + } + + unsafe extern "C" fn ndi_src_meta_init( + meta: *mut gst::ffi::GstMeta, + params: glib::ffi::gpointer, + _buffer: *mut gst::ffi::GstBuffer, + ) -> glib::ffi::gboolean { + assert!(!params.is_null()); + + let meta = &mut *(meta as *mut NdiSrcMeta); + let params = ptr::read(params as *const NdiSrcMetaParams); + + ptr::write(&mut meta.stream_type, params.stream_type); + ptr::write(&mut meta.caps, params.caps); + + true.into_glib() + } + + unsafe extern "C" fn ndi_src_meta_free( + meta: *mut gst::ffi::GstMeta, + _buffer: *mut gst::ffi::GstBuffer, + ) { + let meta = &mut *(meta as *mut NdiSrcMeta); + + ptr::drop_in_place(&mut meta.stream_type); + ptr::drop_in_place(&mut meta.caps); + } + + unsafe extern "C" fn ndi_src_meta_transform( + _dest: *mut gst::ffi::GstBuffer, + _meta: *mut gst::ffi::GstMeta, + _buffer: *mut gst::ffi::GstBuffer, + _type_: glib::ffi::GQuark, + _data: glib::ffi::gpointer, + ) -> glib::ffi::gboolean { + false.into_glib() + } + + pub(super) fn ndi_src_meta_get_info() -> *const gst::ffi::GstMetaInfo { + struct MetaInfo(ptr::NonNull); + unsafe impl Send for MetaInfo {} + unsafe impl Sync for MetaInfo {} + + static META_INFO: Lazy = Lazy::new(|| unsafe { + MetaInfo( + ptr::NonNull::new(gst::ffi::gst_meta_register( + ndi_src_meta_api_get_type().into_glib(), + b"GstNdiSrcMeta\0".as_ptr() as *const _, + mem::size_of::(), + Some(ndi_src_meta_init), + Some(ndi_src_meta_free), + Some(ndi_src_meta_transform), + ) as *mut gst::ffi::GstMetaInfo) + .expect("Failed to register meta API"), + ) + }); + + META_INFO.0.as_ptr() + } +} diff --git a/src/ndivideosrc/imp.rs b/src/ndivideosrc/imp.rs deleted file mode 100644 index d30ee802..00000000 --- a/src/ndivideosrc/imp.rs +++ /dev/null @@ -1,618 +0,0 @@ -use gst::prelude::*; -use gst::subclass::prelude::*; -use gst::{gst_debug, gst_error}; -use gst_base::prelude::*; -use gst_base::subclass::base_src::CreateSuccess; -use gst_base::subclass::prelude::*; - -use std::sync::Mutex; -use std::{i32, u32}; - -use once_cell::sync::Lazy; - -use crate::ndisys; - -use crate::connect_ndi; - -use crate::Receiver; -use crate::ReceiverControlHandle; -use crate::ReceiverItem; -use crate::TimestampMode; -use crate::VideoReceiver; -use crate::DEFAULT_RECEIVER_NDI_NAME; - -#[derive(Debug, Clone)] -struct Settings { - ndi_name: Option, - url_address: Option, - connect_timeout: u32, - timeout: u32, - max_queue_length: u32, - receiver_ndi_name: String, - bandwidth: ndisys::NDIlib_recv_bandwidth_e, - timestamp_mode: TimestampMode, -} - -impl Default for Settings { - fn default() -> Self { - Settings { - ndi_name: None, - url_address: None, - receiver_ndi_name: DEFAULT_RECEIVER_NDI_NAME.clone(), - connect_timeout: 10000, - timeout: 5000, - max_queue_length: 5, - bandwidth: ndisys::NDIlib_recv_bandwidth_highest, - timestamp_mode: TimestampMode::ReceiveTimeTimecode, - } - } -} - -struct State { - info: Option, - current_latency: Option, - receiver: Option>, -} - -impl Default for State { - fn default() -> State { - State { - info: None, - current_latency: gst::ClockTime::NONE, - receiver: None, - } - } -} - -pub struct NdiVideoSrc { - cat: gst::DebugCategory, - settings: Mutex, - state: Mutex, - receiver_controller: Mutex>>, -} - -#[glib::object_subclass] -impl ObjectSubclass for NdiVideoSrc { - const NAME: &'static str = "NdiVideoSrc"; - type Type = super::NdiVideoSrc; - type ParentType = gst_base::BaseSrc; - - fn new() -> Self { - Self { - cat: gst::DebugCategory::new( - "ndivideosrc", - gst::DebugColorFlags::empty(), - Some("NewTek NDI Video Source"), - ), - settings: Mutex::new(Default::default()), - state: Mutex::new(Default::default()), - receiver_controller: Mutex::new(None), - } - } -} - -impl ObjectImpl for NdiVideoSrc { - fn properties() -> &'static [glib::ParamSpec] { - static PROPERTIES: Lazy> = Lazy::new(|| { - vec![ - glib::ParamSpec::new_string( - "ndi-name", - "NDI Name", - "NDI stream name of the sender", - None, - glib::ParamFlags::READWRITE, - ), - glib::ParamSpec::new_string( - "url-address", - "URL/Address", - "URL/address and port of the sender, e.g. 127.0.0.1:5961", - None, - glib::ParamFlags::READWRITE, - ), - glib::ParamSpec::new_string( - "receiver-ndi-name", - "Receiver NDI Name", - "NDI stream name of this receiver", - Some(&*DEFAULT_RECEIVER_NDI_NAME), - glib::ParamFlags::READWRITE, - ), - glib::ParamSpec::new_uint( - "connect-timeout", - "Connect Timeout", - "Connection timeout in ms", - 0, - u32::MAX, - 10000, - glib::ParamFlags::READWRITE, - ), - glib::ParamSpec::new_uint( - "timeout", - "Timeout", - "Receive timeout in ms", - 0, - u32::MAX, - 5000, - glib::ParamFlags::READWRITE, - ), - glib::ParamSpec::new_uint( - "max-queue-length", - "Max Queue Length", - "Maximum receive queue length", - 0, - u32::MAX, - 5, - glib::ParamFlags::READWRITE, - ), - glib::ParamSpec::new_int( - "bandwidth", - "Bandwidth", - "Bandwidth, -10 metadata-only, 10 audio-only, 100 highest", - -10, - 100, - 100, - glib::ParamFlags::READWRITE, - ), - glib::ParamSpec::new_enum( - "timestamp-mode", - "Timestamp Mode", - "Timestamp information to use for outgoing PTS", - TimestampMode::static_type(), - TimestampMode::ReceiveTimeTimecode as i32, - glib::ParamFlags::READWRITE, - ), - ] - }); - - PROPERTIES.as_ref() - } - - fn constructed(&self, obj: &Self::Type) { - self.parent_constructed(obj); - - // Initialize live-ness and notify the base class that - // we'd like to operate in Time format - obj.set_live(true); - obj.set_format(gst::Format::Time); - } - - fn set_property( - &self, - obj: &Self::Type, - _id: usize, - value: &glib::Value, - pspec: &glib::ParamSpec, - ) { - match pspec.name() { - "ndi-name" => { - let mut settings = self.settings.lock().unwrap(); - let ndi_name = value.get().unwrap(); - gst_debug!( - self.cat, - obj: obj, - "Changing ndi-name from {:?} to {:?}", - settings.ndi_name, - ndi_name, - ); - settings.ndi_name = ndi_name; - } - "url-address" => { - let mut settings = self.settings.lock().unwrap(); - let url_address = value.get().unwrap(); - gst_debug!( - self.cat, - obj: obj, - "Changing url-address from {:?} to {:?}", - settings.url_address, - url_address, - ); - settings.url_address = url_address; - } - "receiver-ndi-name" => { - let mut settings = self.settings.lock().unwrap(); - let receiver_ndi_name = value.get::>().unwrap(); - gst_debug!( - self.cat, - obj: obj, - "Changing receiver-ndi-name from {:?} to {:?}", - settings.receiver_ndi_name, - receiver_ndi_name, - ); - settings.receiver_ndi_name = - receiver_ndi_name.unwrap_or_else(|| DEFAULT_RECEIVER_NDI_NAME.clone()); - } - "connect-timeout" => { - let mut settings = self.settings.lock().unwrap(); - let connect_timeout = value.get().unwrap(); - gst_debug!( - self.cat, - obj: obj, - "Changing connect-timeout from {} to {}", - settings.connect_timeout, - connect_timeout, - ); - settings.connect_timeout = connect_timeout; - } - "timeout" => { - let mut settings = self.settings.lock().unwrap(); - let timeout = value.get().unwrap(); - gst_debug!( - self.cat, - obj: obj, - "Changing timeout from {} to {}", - settings.timeout, - timeout, - ); - settings.timeout = timeout; - } - "max-queue-length" => { - let mut settings = self.settings.lock().unwrap(); - let max_queue_length = value.get().unwrap(); - gst_debug!( - self.cat, - obj: obj, - "Changing max-queue-length from {} to {}", - settings.max_queue_length, - max_queue_length, - ); - settings.max_queue_length = max_queue_length; - } - "bandwidth" => { - let mut settings = self.settings.lock().unwrap(); - let bandwidth = value.get().unwrap(); - gst_debug!( - self.cat, - obj: obj, - "Changing bandwidth from {} to {}", - settings.bandwidth, - bandwidth, - ); - settings.bandwidth = bandwidth; - } - "timestamp-mode" => { - let mut settings = self.settings.lock().unwrap(); - let timestamp_mode = value.get().unwrap(); - gst_debug!( - self.cat, - obj: obj, - "Changing timestamp mode from {:?} to {:?}", - settings.timestamp_mode, - timestamp_mode - ); - if settings.timestamp_mode != timestamp_mode { - let _ = obj.post_message(gst::message::Latency::builder().src(obj).build()); - } - settings.timestamp_mode = timestamp_mode; - } - _ => unimplemented!(), - } - } - - fn property(&self, _obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { - match pspec.name() { - "ndi-name" => { - let settings = self.settings.lock().unwrap(); - settings.ndi_name.to_value() - } - "url-address" => { - let settings = self.settings.lock().unwrap(); - settings.url_address.to_value() - } - "receiver-ndi-name" => { - let settings = self.settings.lock().unwrap(); - settings.receiver_ndi_name.to_value() - } - "connect-timeout" => { - let settings = self.settings.lock().unwrap(); - settings.connect_timeout.to_value() - } - "timeout" => { - let settings = self.settings.lock().unwrap(); - settings.timeout.to_value() - } - "max-queue-length" => { - let settings = self.settings.lock().unwrap(); - settings.max_queue_length.to_value() - } - "bandwidth" => { - let settings = self.settings.lock().unwrap(); - settings.bandwidth.to_value() - } - "timestamp-mode" => { - let settings = self.settings.lock().unwrap(); - settings.timestamp_mode.to_value() - } - _ => unimplemented!(), - } - } -} - -impl ElementImpl for NdiVideoSrc { - fn metadata() -> Option<&'static gst::subclass::ElementMetadata> { - static ELEMENT_METADATA: Lazy = Lazy::new(|| { - gst::subclass::ElementMetadata::new( - "NewTek NDI Video Source", - "Source", - "NewTek NDI video source", - "Ruben Gonzalez , Daniel Vilar , Sebastian Dröge ", - ) - }); - - Some(&*ELEMENT_METADATA) - } - - fn pad_templates() -> &'static [gst::PadTemplate] { - static PAD_TEMPLATES: Lazy> = Lazy::new(|| { - // On the src pad, we can produce F32/F64 with any sample rate - // and any number of channels - let caps = gst::Caps::new_simple( - "video/x-raw", - &[ - ( - "format", - &gst::List::new(&[ - &gst_video::VideoFormat::Uyvy.to_string(), - &gst_video::VideoFormat::Yv12.to_string(), - &gst_video::VideoFormat::Nv12.to_string(), - &gst_video::VideoFormat::I420.to_string(), - &gst_video::VideoFormat::Bgra.to_string(), - &gst_video::VideoFormat::Bgrx.to_string(), - &gst_video::VideoFormat::Rgba.to_string(), - &gst_video::VideoFormat::Rgbx.to_string(), - ]), - ), - ("width", &gst::IntRange::::new(0, i32::MAX)), - ("height", &gst::IntRange::::new(0, i32::MAX)), - ( - "framerate", - &gst::FractionRange::new( - gst::Fraction::new(0, 1), - gst::Fraction::new(i32::MAX, 1), - ), - ), - ], - ); - - #[cfg(feature = "interlaced-fields")] - let caps = { - let mut tmp = caps.copy(); - { - let tmp = tmp.get_mut().unwrap(); - tmp.set_features_simple(Some(gst::CapsFeatures::new(&["format:Interlaced"]))); - } - - let mut caps = caps; - { - let caps = caps.get_mut().unwrap(); - caps.append(tmp); - } - - caps - }; - - let src_pad_template = gst::PadTemplate::new( - "src", - gst::PadDirection::Src, - gst::PadPresence::Always, - &caps, - ) - .unwrap(); - - vec![src_pad_template] - }); - - PAD_TEMPLATES.as_ref() - } - - fn change_state( - &self, - element: &Self::Type, - transition: gst::StateChange, - ) -> Result { - match transition { - gst::StateChange::PausedToPlaying => { - if let Some(ref controller) = *self.receiver_controller.lock().unwrap() { - controller.set_playing(true); - } - } - gst::StateChange::PlayingToPaused => { - if let Some(ref controller) = *self.receiver_controller.lock().unwrap() { - controller.set_playing(false); - } - } - gst::StateChange::PausedToReady => { - if let Some(ref controller) = *self.receiver_controller.lock().unwrap() { - controller.shutdown(); - } - } - _ => (), - } - - self.parent_change_state(element, transition) - } -} - -impl BaseSrcImpl for NdiVideoSrc { - fn negotiate(&self, _element: &Self::Type) -> Result<(), gst::LoggableError> { - // Always succeed here without doing anything: we will set the caps once we received a - // buffer, there's nothing we can negotiate - Ok(()) - } - - fn unlock(&self, element: &Self::Type) -> Result<(), gst::ErrorMessage> { - gst_debug!(self.cat, obj: element, "Unlocking",); - if let Some(ref controller) = *self.receiver_controller.lock().unwrap() { - controller.set_flushing(true); - } - Ok(()) - } - - fn unlock_stop(&self, element: &Self::Type) -> Result<(), gst::ErrorMessage> { - gst_debug!(self.cat, obj: element, "Stop unlocking",); - if let Some(ref controller) = *self.receiver_controller.lock().unwrap() { - controller.set_flushing(false); - } - Ok(()) - } - - fn start(&self, element: &Self::Type) -> Result<(), gst::ErrorMessage> { - *self.state.lock().unwrap() = Default::default(); - let settings = self.settings.lock().unwrap().clone(); - - if settings.ndi_name.is_none() && settings.url_address.is_none() { - return Err(gst::error_msg!( - gst::LibraryError::Settings, - ["No NDI name or URL/address given"] - )); - } - - let receiver = connect_ndi( - self.cat, - element.upcast_ref(), - settings.ndi_name.as_deref(), - settings.url_address.as_deref(), - &settings.receiver_ndi_name, - settings.connect_timeout, - settings.bandwidth, - settings.timestamp_mode, - settings.timeout, - settings.max_queue_length as usize, - ); - - // settings.id_receiver exists - match receiver { - None => Err(gst::error_msg!( - gst::ResourceError::NotFound, - ["Could not connect to this source"] - )), - Some(receiver) => { - *self.receiver_controller.lock().unwrap() = - Some(receiver.receiver_control_handle()); - let mut state = self.state.lock().unwrap(); - state.receiver = Some(receiver); - - Ok(()) - } - } - } - - fn stop(&self, _element: &Self::Type) -> Result<(), gst::ErrorMessage> { - if let Some(ref controller) = self.receiver_controller.lock().unwrap().take() { - controller.shutdown(); - } - *self.state.lock().unwrap() = State::default(); - Ok(()) - } - - fn query(&self, element: &Self::Type, query: &mut gst::QueryRef) -> bool { - use gst::QueryView; - - match query.view_mut() { - QueryView::Scheduling(ref mut q) => { - q.set(gst::SchedulingFlags::SEQUENTIAL, 1, -1, 0); - q.add_scheduling_modes(&[gst::PadMode::Push]); - true - } - QueryView::Latency(ref mut q) => { - let state = self.state.lock().unwrap(); - let settings = self.settings.lock().unwrap(); - - if let Some(latency) = state.current_latency { - let min = if settings.timestamp_mode != TimestampMode::Timecode { - latency - } else { - gst::ClockTime::ZERO - }; - - let max = 5 * latency; - - println!("Returning latency min {} max {}", min, max,); - - gst_debug!( - self.cat, - obj: element, - "Returning latency min {} max {}", - min, - max - ); - q.set(true, min, max); - true - } else { - false - } - } - _ => BaseSrcImplExt::parent_query(self, element, query), - } - } - - fn fixate(&self, element: &Self::Type, mut caps: gst::Caps) -> gst::Caps { - caps.truncate(); - { - let caps = caps.make_mut(); - let s = caps.structure_mut(0).unwrap(); - s.fixate_field_nearest_int("width", 1920); - s.fixate_field_nearest_int("height", 1080); - if s.has_field("pixel-aspect-ratio") { - s.fixate_field_nearest_fraction("pixel-aspect-ratio", gst::Fraction::new(1, 1)); - } - } - - self.parent_fixate(element, caps) - } - - //Creates the video buffers - fn create( - &self, - element: &Self::Type, - _offset: u64, - _buffer: Option<&mut gst::BufferRef>, - _length: u32, - ) -> Result { - let recv = { - let mut state = self.state.lock().unwrap(); - match state.receiver.take() { - Some(recv) => recv, - None => { - gst_error!(self.cat, obj: element, "Have no receiver"); - return Err(gst::FlowError::Error); - } - } - }; - - match recv.capture() { - ReceiverItem::Buffer(buffer, info) => { - let mut state = self.state.lock().unwrap(); - state.receiver = Some(recv); - if state.info.as_ref() != Some(&info) { - let caps = info.to_caps().map_err(|_| { - gst::element_error!( - element, - gst::ResourceError::Settings, - ["Invalid audio info received: {:?}", info] - ); - gst::FlowError::NotNegotiated - })?; - state.info = Some(info); - state.current_latency = buffer.duration(); - drop(state); - gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps); - element.set_caps(&caps).map_err(|_| { - gst::element_error!( - element, - gst::CoreError::Negotiation, - ["Failed to negotiate caps: {:?}", caps] - ); - gst::FlowError::NotNegotiated - })?; - - let _ = - element.post_message(gst::message::Latency::builder().src(element).build()); - } - - Ok(CreateSuccess::NewBuffer(buffer)) - } - ReceiverItem::Timeout => Err(gst::FlowError::Eos), - ReceiverItem::Flushing => Err(gst::FlowError::Flushing), - ReceiverItem::Error(err) => Err(err), - } - } -} diff --git a/src/ndivideosrc/mod.rs b/src/ndivideosrc/mod.rs deleted file mode 100644 index ba9a7552..00000000 --- a/src/ndivideosrc/mod.rs +++ /dev/null @@ -1,19 +0,0 @@ -use glib::prelude::*; - -mod imp; - -glib::wrapper! { - pub struct NdiVideoSrc(ObjectSubclass) @extends gst_base::BaseSrc, gst::Element, gst::Object; -} - -unsafe impl Send for NdiVideoSrc {} -unsafe impl Sync for NdiVideoSrc {} - -pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { - gst::Element::register( - Some(plugin), - "ndivideosrc", - gst::Rank::None, - NdiVideoSrc::static_type(), - ) -} diff --git a/src/receiver.rs b/src/receiver.rs index 8f7d12a0..9154fbc7 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -7,99 +7,57 @@ use byte_slice_cast::AsMutSliceOf; use std::cmp; use std::collections::VecDeque; -use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{Arc, Condvar, Mutex, Weak}; use std::thread; -use once_cell::sync::Lazy; - use super::*; -pub struct ReceiverInfo { - id: usize, - ndi_name: Option, - url_address: Option, - recv: RecvInstance, - video: Option>>, - audio: Option>>, - observations: Observations, -} - -static HASHMAP_RECEIVERS: Lazy>> = Lazy::new(|| { - let m = HashMap::new(); - Mutex::new(m) +static CAT: Lazy = Lazy::new(|| { + gst::DebugCategory::new( + "ndireceiver", + gst::DebugColorFlags::empty(), + Some("NewTek NDI receiver"), + ) }); -static ID_RECEIVER: AtomicUsize = AtomicUsize::new(0); +#[derive(Clone)] +pub struct Receiver(Arc); -pub trait ReceiverType: 'static { - type InfoType: Send + 'static; - const IS_VIDEO: bool; -} - -pub enum AudioReceiver {} -pub enum VideoReceiver {} - -impl ReceiverType for AudioReceiver { - type InfoType = gst_audio::AudioInfo; - const IS_VIDEO: bool = false; -} - -impl ReceiverType for VideoReceiver { - type InfoType = gst_video::VideoInfo; - const IS_VIDEO: bool = true; -} - -pub struct Receiver(Arc>); - -impl Clone for Receiver { - fn clone(&self) -> Self { - Receiver(self.0.clone()) - } +#[derive(Debug)] +pub enum Buffer { + Audio(gst::Buffer, gst_audio::AudioInfo), + Video(gst::Buffer, gst_video::VideoInfo), } #[derive(Debug)] -pub enum ReceiverItem { - Buffer(gst::Buffer, T::InfoType), +pub enum ReceiverItem { + Buffer(Buffer), Flushing, Timeout, Error(gst::FlowError), } -pub struct ReceiverInner { - id: usize, - - queue: ReceiverQueue, +pub struct ReceiverInner { + queue: ReceiverQueue, max_queue_length: usize, - recv: Mutex, - observations: Observations, - cat: gst::DebugCategory, element: glib::WeakRef, timestamp_mode: TimestampMode, - first_frame: AtomicBool, timeout: u32, connect_timeout: u32, thread: Mutex>>, } -struct ReceiverQueue(Arc<(Mutex>, Condvar)>); +#[derive(Clone)] +struct ReceiverQueue(Arc<(Mutex, Condvar)>); -impl Clone for ReceiverQueue { - fn clone(&self) -> Self { - ReceiverQueue(self.0.clone()) - } -} - -struct ReceiverQueueInner { - // If we should be capturing at all or go out of our capture loop - // - // This is true as long as the source element is in Paused/Playing - capturing: bool, +struct ReceiverQueueInner { + // Set to true when the capture thread should be stopped + shutdown: bool, // If we're flushing right now and all buffers should simply be discarded // and capture() directly returns Flushing @@ -110,7 +68,7 @@ struct ReceiverQueueInner { // Queue containing our buffers. This holds at most 5 buffers at a time. // // On timeout/error will contain a single item and then never be filled again - buffer_queue: VecDeque<(gst::Buffer, T::InfoType)>, + buffer_queue: VecDeque, error: Option, timeout: bool, @@ -165,7 +123,6 @@ impl Observations { fn process( &self, - cat: gst::DebugCategory, element: &gst_base::BaseSrc, time: (Option, gst::ClockTime), duration: Option, @@ -233,7 +190,7 @@ impl Observations { next_mapping.den = den; *time_mapping_pending = true; gst_debug!( - cat, + CAT, obj: element, "Calculated new time mapping: GStreamer time = {} * (NDI time - {}) + {} ({})", next_mapping.num as f64 / next_mapping.den as f64, @@ -279,7 +236,7 @@ impl Observations { if diff > max_diff { gst_debug!( - cat, + CAT, obj: element, "New time mapping causes difference {} but only {} allowed", diff, @@ -309,7 +266,7 @@ impl Observations { duration.and_then(|d| d.mul_div_floor(current_mapping.num, current_mapping.den)); gst_debug!( - cat, + CAT, obj: element, "Converted timestamp {}/{} to {}, duration {} to {}", time.0, @@ -334,19 +291,12 @@ impl Default for TimeMapping { } } -pub struct ReceiverControlHandle { - queue: ReceiverQueue, +#[derive(Clone)] +pub struct ReceiverControlHandle { + queue: ReceiverQueue, } -impl Clone for ReceiverControlHandle { - fn clone(&self) -> Self { - ReceiverControlHandle { - queue: self.queue.clone(), - } - } -} - -impl ReceiverControlHandle { +impl ReceiverControlHandle { pub fn set_flushing(&self, flushing: bool) { let mut queue = (self.queue.0).0.lock().unwrap(); queue.flushing = flushing; @@ -360,29 +310,39 @@ impl ReceiverControlHandle { pub fn shutdown(&self) { let mut queue = (self.queue.0).0.lock().unwrap(); - queue.capturing = false; + queue.shutdown = true; (self.queue.0).1.notify_all(); } } -impl Receiver { +impl Drop for ReceiverInner { + fn drop(&mut self) { + // Will shut down the receiver thread on the next iteration + let mut queue = (self.queue.0).0.lock().unwrap(); + queue.shutdown = true; + drop(queue); + + let element = self.element.upgrade(); + + if let Some(ref element) = element { + gst_debug!(CAT, obj: element, "Closed NDI connection"); + } + } +} + +impl Receiver { fn new( - info: &mut ReceiverInfo, + recv: RecvInstance, timestamp_mode: TimestampMode, timeout: u32, connect_timeout: u32, max_queue_length: usize, element: &gst_base::BaseSrc, - cat: gst::DebugCategory, - ) -> Self - where - Receiver: ReceiverCapture, - { + ) -> Self { let receiver = Receiver(Arc::new(ReceiverInner { - id: info.id, queue: ReceiverQueue(Arc::new(( Mutex::new(ReceiverQueueInner { - capturing: true, + shutdown: false, playing: false, flushing: false, buffer_queue: VecDeque::with_capacity(max_queue_length), @@ -392,12 +352,9 @@ impl Receiver { Condvar::new(), ))), max_queue_length, - recv: Mutex::new(info.recv.clone()), - observations: info.observations.clone(), - cat, + observations: Observations::new(), element: element.downgrade(), timestamp_mode, - first_frame: AtomicBool::new(true), timeout, connect_timeout, thread: Mutex::new(None), @@ -408,8 +365,9 @@ impl Receiver { use std::panic; let weak_clone = weak.clone(); - match panic::catch_unwind(panic::AssertUnwindSafe(move || receive_thread(&weak_clone))) - { + match panic::catch_unwind(panic::AssertUnwindSafe(move || { + Self::receive_thread(&weak_clone, recv) + })) { Ok(_) => (), Err(_) => { if let Some(receiver) = weak.upgrade().map(Receiver) { @@ -429,15 +387,12 @@ impl Receiver { } }); - let weak = Arc::downgrade(&receiver.0); - Self::store_internal(info, weak); - *receiver.0.thread.lock().unwrap() = Some(thread); receiver } - pub fn receiver_control_handle(&self) -> ReceiverControlHandle { + pub fn receiver_control_handle(&self) -> ReceiverControlHandle { ReceiverControlHandle { queue: self.0.queue.clone(), } @@ -456,322 +411,212 @@ impl Receiver { pub fn shutdown(&self) { let mut queue = (self.0.queue.0).0.lock().unwrap(); - queue.capturing = false; + queue.shutdown = true; (self.0.queue.0).1.notify_all(); } - pub fn capture(&self) -> ReceiverItem { + pub fn capture(&self) -> ReceiverItem { let mut queue = (self.0.queue.0).0.lock().unwrap(); loop { if let Some(err) = queue.error { return ReceiverItem::Error(err); } else if queue.buffer_queue.is_empty() && queue.timeout { return ReceiverItem::Timeout; - } else if queue.flushing || !queue.capturing { + } else if queue.flushing || queue.shutdown { return ReceiverItem::Flushing; - } else if let Some((buffer, info)) = queue.buffer_queue.pop_front() { - return ReceiverItem::Buffer(buffer, info); + } else if let Some(buffer) = queue.buffer_queue.pop_front() { + return ReceiverItem::Buffer(buffer); } queue = (self.0.queue.0).1.wait(queue).unwrap(); } } -} -impl Drop for ReceiverInner { - fn drop(&mut self) { - // Will shut down the receiver thread on the next iteration - let mut queue = (self.queue.0).0.lock().unwrap(); - queue.capturing = false; - drop(queue); + pub fn connect( + element: &gst_base::BaseSrc, + ndi_name: Option<&str>, + url_address: Option<&str>, + receiver_ndi_name: &str, + connect_timeout: u32, + bandwidth: NDIlib_recv_bandwidth_e, + timestamp_mode: TimestampMode, + timeout: u32, + max_queue_length: usize, + ) -> Option { + gst_debug!(CAT, obj: element, "Starting NDI connection..."); - let element = self.element.upgrade(); + assert!(ndi_name.is_some() || url_address.is_some()); - if let Some(ref element) = element { - gst_debug!(self.cat, obj: element, "Closing NDI connection..."); - } + gst_debug!( + CAT, + obj: element, + "Connecting to NDI source with NDI name '{:?}' and URL/Address {:?}", + ndi_name, + url_address, + ); - let mut receivers = HASHMAP_RECEIVERS.lock().unwrap(); - { - let receiver = receivers.get_mut(&self.id).unwrap(); - if receiver.audio.is_some() && receiver.video.is_some() { - if T::IS_VIDEO { - receiver.video = None; - } else { - receiver.audio = None; - } - return; - } - } - receivers.remove(&self.id); - - if let Some(ref element) = element { - gst_debug!(self.cat, obj: element, "Closed NDI connection"); - } - } -} - -pub fn connect_ndi( - cat: gst::DebugCategory, - element: &gst_base::BaseSrc, - ndi_name: Option<&str>, - url_address: Option<&str>, - receiver_ndi_name: &str, - connect_timeout: u32, - bandwidth: NDIlib_recv_bandwidth_e, - timestamp_mode: TimestampMode, - timeout: u32, - max_queue_length: usize, -) -> Option> -where - Receiver: ReceiverCapture, -{ - gst_debug!(cat, obj: element, "Starting NDI connection..."); - - assert!(ndi_name.is_some() || url_address.is_some()); - - let mut receivers = HASHMAP_RECEIVERS.lock().unwrap(); - - // Check if we already have a receiver for this very stream - for receiver in receivers.values_mut() { - // If both are provided they both must match, if only one is provided - // then that one has to match and the other one does not matter - if (ndi_name.is_some() - && url_address.is_some() - && receiver.ndi_name.as_deref() == ndi_name - && receiver.url_address.as_deref() == url_address) - || (ndi_name.is_some() - && url_address.is_none() - && receiver.ndi_name.as_deref() == ndi_name) - || (ndi_name.is_none() - && url_address.is_some() - && receiver.url_address.as_deref() == url_address) - { - if (receiver.video.is_some() || !T::IS_VIDEO) - && (receiver.audio.is_some() || T::IS_VIDEO) - { + // FIXME: Ideally we would use NDIlib_recv_color_format_fastest here but that seems to be + // broken with interlaced content currently + let recv = RecvInstance::builder(ndi_name, url_address, receiver_ndi_name) + .bandwidth(bandwidth) + .color_format(NDIlib_recv_color_format_e::NDIlib_recv_color_format_UYVY_BGRA) + .allow_video_fields(true) + .build(); + let recv = match recv { + None => { gst::element_error!( element, - gst::ResourceError::OpenRead, - [ - "Source with NDI name '{:?}' / URL/address '{:?}' already in use for {}", - receiver.ndi_name, - receiver.url_address, - if T::IS_VIDEO { "video" } else { "audio" } - ] + gst::CoreError::Negotiation, + ["Failed to connect to source"] ); - return None; + } + Some(recv) => recv, + }; + + recv.set_tally(&Tally::default()); + + let enable_hw_accel = MetadataFrame::new(0, Some("")); + recv.send_metadata(&enable_hw_accel); + + // This will set info.audio/video accordingly + let receiver = Receiver::new( + recv, + timestamp_mode, + timeout, + connect_timeout, + max_queue_length, + element, + ); + + Some(receiver) + } + + fn receive_thread(receiver: &Weak, recv: RecvInstance) { + let mut first_frame = true; + let mut timer = time::Instant::now(); + + // Capture until error or shutdown + loop { + let receiver = match receiver.upgrade().map(Receiver) { + None => break, + Some(receiver) => receiver, + }; + + let element = match receiver.0.element.upgrade() { + None => return, + Some(element) => element, + }; + + let flushing = { + let queue = (receiver.0.queue.0).0.lock().unwrap(); + if queue.shutdown { + gst_debug!(CAT, obj: &element, "Shutting down"); + break; + } + + // If an error happened in the meantime, just go out of here + if queue.error.is_some() { + gst_error!(CAT, obj: &element, "Error while waiting for connection"); + return; + } + + queue.flushing + }; + + let timeout = if first_frame { + receiver.0.connect_timeout } else { - return Some(Receiver::new( - receiver, - timestamp_mode, - timeout, - connect_timeout, - max_queue_length, - element, - cat, - )); - } - } - } + receiver.0.timeout + }; - // Otherwise create a new one and return it to the caller - gst_debug!( - cat, - obj: element, - "Connecting to NDI source with NDI name '{:?}' and URL/Address {:?}", - ndi_name, - url_address, - ); - - // FIXME: Ideally we would use NDIlib_recv_color_format_fastest here but that seems to be - // broken with interlaced content currently - let recv = RecvInstance::builder(ndi_name, url_address, receiver_ndi_name) - .bandwidth(bandwidth) - .color_format(NDIlib_recv_color_format_e::NDIlib_recv_color_format_UYVY_BGRA) - .allow_video_fields(true) - .build(); - let recv = match recv { - None => { - gst::element_error!( - element, - gst::CoreError::Negotiation, - ["Failed to connect to source"] - ); - return None; - } - Some(recv) => recv, - }; - - recv.set_tally(&Tally::default()); - - let enable_hw_accel = MetadataFrame::new(0, Some("")); - recv.send_metadata(&enable_hw_accel); - - let id_receiver = ID_RECEIVER.fetch_add(1, Ordering::SeqCst); - let mut info = ReceiverInfo { - id: id_receiver, - ndi_name: ndi_name.map(String::from), - url_address: url_address.map(String::from), - recv, - video: None, - audio: None, - observations: Observations::new(), - }; - - // This will set info.audio/video accordingly - let receiver = Receiver::new( - &mut info, - timestamp_mode, - timeout, - connect_timeout, - max_queue_length, - element, - cat, - ); - - receivers.insert(id_receiver, info); - - Some(receiver) -} - -fn receive_thread(receiver: &Weak>) -where - Receiver: ReceiverCapture, -{ - // Now first capture frames until the queues are empty so that we're sure that we output only - // the very latest frame that is available now - loop { - let receiver = match receiver.upgrade().map(Receiver) { - None => return, - Some(receiver) => receiver, - }; - - let element = match receiver.0.element.upgrade() { - None => return, - Some(element) => element, - }; - - { - let queue = (receiver.0.queue.0).0.lock().unwrap(); - if !queue.capturing { - gst_debug!(receiver.0.cat, obj: &element, "Shutting down"); - return; - } - - // If an error happened in the meantime, just go out of here - if queue.error.is_some() { - gst_error!( - receiver.0.cat, - obj: &element, - "Error while waiting for connection" - ); - return; - } - } - - let recv = receiver.0.recv.lock().unwrap(); - - let queue = recv.get_queue(); - if (!T::IS_VIDEO && queue.audio_frames() <= 1) || (T::IS_VIDEO && queue.video_frames() <= 1) - { - break; - } - - let _ = recv.capture(T::IS_VIDEO, !T::IS_VIDEO, false, 0); - } - - // And if that went fine, capture until we're done - loop { - let receiver = match receiver.upgrade().map(Receiver) { - None => break, - Some(receiver) => receiver, - }; - - let element = match receiver.0.element.upgrade() { - None => return, - Some(element) => element, - }; - - { - let queue = (receiver.0.queue.0).0.lock().unwrap(); - if !queue.capturing { - gst_debug!(receiver.0.cat, obj: &element, "Shutting down"); - break; - } - } - - let recv = receiver.0.recv.lock().unwrap(); - - let res = receiver.capture_internal(&element, &recv); - - match res { - Ok(item) => { - let mut queue = (receiver.0.queue.0).0.lock().unwrap(); - while queue.buffer_queue.len() > receiver.0.max_queue_length { - gst_warning!( - receiver.0.cat, - obj: &element, - "Dropping old buffer -- queue has {} items", - queue.buffer_queue.len() + let res = match recv.capture(50) { + _ if flushing => { + gst_debug!(CAT, obj: &element, "Flushing"); + Err(gst::FlowError::Flushing) + } + Err(_) => { + gst::element_error!( + element, + gst::ResourceError::Read, + ["Error receiving frame"] ); - queue.buffer_queue.pop_front(); + Err(gst::FlowError::Error) } - queue.buffer_queue.push_back(item); - (receiver.0.queue.0).1.notify_one(); - } - Err(gst::FlowError::Eos) => { - gst_debug!(receiver.0.cat, obj: &element, "Signalling EOS"); - let mut queue = (receiver.0.queue.0).0.lock().unwrap(); - queue.timeout = true; - (receiver.0.queue.0).1.notify_one(); - } - Err(gst::FlowError::CustomError) => { - // Flushing, nothing to be done here except for emptying our queue - let mut queue = (receiver.0.queue.0).0.lock().unwrap(); - queue.buffer_queue.clear(); - (receiver.0.queue.0).1.notify_one(); - } - Err(err) => { - gst_error!(receiver.0.cat, obj: &element, "Signalling error"); - let mut queue = (receiver.0.queue.0).0.lock().unwrap(); - if queue.error.is_none() { - queue.error = Some(err); + Ok(None) if timeout > 0 && timer.elapsed().as_millis() >= timeout as u128 => { + gst_debug!(CAT, obj: &element, "Timed out -- assuming EOS",); + Err(gst::FlowError::Eos) + } + Ok(None) => { + gst_debug!(CAT, obj: &element, "No frame received yet, retry"); + continue; + } + Ok(Some(Frame::Video(frame))) => { + first_frame = false; + receiver.create_video_buffer_and_info(&element, frame) + } + Ok(Some(Frame::Audio(frame))) => { + first_frame = false; + receiver.create_audio_buffer_and_info(&element, frame) + } + Ok(Some(Frame::Metadata(frame))) => { + if let Some(metadata) = frame.metadata() { + gst_debug!( + CAT, + obj: &element, + "Received metadata at timecode {}: {}", + gst::ClockTime::from_nseconds(frame.timecode() as u64 * 100), + metadata, + ); + } + + continue; + } + }; + + match res { + Ok(item) => { + let mut queue = (receiver.0.queue.0).0.lock().unwrap(); + while queue.buffer_queue.len() > receiver.0.max_queue_length { + gst_warning!( + CAT, + obj: &element, + "Dropping old buffer -- queue has {} items", + queue.buffer_queue.len() + ); + queue.buffer_queue.pop_front(); + } + queue.buffer_queue.push_back(item); + (receiver.0.queue.0).1.notify_one(); + timer = time::Instant::now(); + } + Err(gst::FlowError::Eos) => { + gst_debug!(CAT, obj: &element, "Signalling EOS"); + let mut queue = (receiver.0.queue.0).0.lock().unwrap(); + queue.timeout = true; + (receiver.0.queue.0).1.notify_one(); + break; + } + Err(gst::FlowError::Flushing) => { + // Flushing, nothing to be done here except for emptying our queue + let mut queue = (receiver.0.queue.0).0.lock().unwrap(); + queue.buffer_queue.clear(); + (receiver.0.queue.0).1.notify_one(); + timer = time::Instant::now(); + } + Err(err) => { + gst_error!(CAT, obj: &element, "Signalling error"); + let mut queue = (receiver.0.queue.0).0.lock().unwrap(); + if queue.error.is_none() { + queue.error = Some(err); + } + (receiver.0.queue.0).1.notify_one(); + break; } - (receiver.0.queue.0).1.notify_one(); - break; } } } -} -pub trait ReceiverCapture { - fn capture_internal( - &self, - element: &gst_base::BaseSrc, - recv: &RecvInstance, - ) -> Result<(gst::Buffer, T::InfoType), gst::FlowError>; - - fn store_internal(info: &mut ReceiverInfo, weak: Weak>); -} - -impl ReceiverCapture for Receiver { - fn capture_internal( - &self, - element: &gst_base::BaseSrc, - recv: &RecvInstance, - ) -> Result<(gst::Buffer, gst_video::VideoInfo), gst::FlowError> { - self.capture_video(element, recv) - } - - fn store_internal(info: &mut ReceiverInfo, weak: Weak>) { - assert!(info.video.is_none()); - info.video = Some(weak); - } -} - -impl Receiver { fn calculate_timestamp( &self, element: &gst_base::BaseSrc, @@ -796,7 +641,7 @@ impl Receiver { let timecode = gst::ClockTime::from_nseconds(timecode as u64 * 100); gst_log!( - self.0.cat, + CAT, obj: element, "Received frame with timecode {}, timestamp {}, duration {}, receive time {}, local time now {}", timecode, @@ -807,18 +652,16 @@ impl Receiver { ); let (pts, duration) = match self.0.timestamp_mode { - TimestampMode::ReceiveTimeTimecode => self.0.observations.process( - self.0.cat, - element, - (Some(timecode), receive_time), - duration, - ), - TimestampMode::ReceiveTimeTimestamp => self.0.observations.process( - self.0.cat, - element, - (timestamp, receive_time), - duration, - ), + TimestampMode::ReceiveTimeTimecode => { + self.0 + .observations + .process(element, (Some(timecode), receive_time), duration) + } + TimestampMode::ReceiveTimeTimestamp => { + self.0 + .observations + .process(element, (timestamp, receive_time), duration) + } TimestampMode::Timecode => (timecode, duration), TimestampMode::Timestamp if timestamp.is_none() => (receive_time, duration), TimestampMode::Timestamp => { @@ -839,7 +682,7 @@ impl Receiver { }; gst_log!( - self.0.cat, + CAT, obj: element, "Calculated PTS {}, duration {}", pts.display(), @@ -848,113 +691,28 @@ impl Receiver { Some((pts, duration)) } -} -impl ReceiverCapture for Receiver { - fn capture_internal( + fn create_video_buffer_and_info( &self, element: &gst_base::BaseSrc, - recv: &RecvInstance, - ) -> Result<(gst::Buffer, gst_audio::AudioInfo), gst::FlowError> { - self.capture_audio(element, recv) - } - - fn store_internal(info: &mut ReceiverInfo, weak: Weak>) { - assert!(info.audio.is_none()); - info.audio = Some(weak); - } -} - -impl Receiver { - fn capture_video( - &self, - element: &gst_base::BaseSrc, - recv: &RecvInstance, - ) -> Result<(gst::Buffer, gst_video::VideoInfo), gst::FlowError> { - let timer = time::Instant::now(); - let timeout = if self.0.first_frame.load(Ordering::SeqCst) { - self.0.connect_timeout - } else { - self.0.timeout - }; - let mut flushing; - let mut playing; - - let video_frame = loop { - { - let queue = (self.0.queue.0).0.lock().unwrap(); - playing = queue.playing; - flushing = queue.flushing; - if !queue.capturing { - gst_debug!(self.0.cat, obj: element, "Shutting down"); - return Err(gst::FlowError::Flushing); - } - } - - let res = match recv.capture(true, false, false, 50) { - Err(_) => Err(()), - Ok(None) => Ok(None), - Ok(Some(Frame::Video(frame))) => Ok(Some(frame)), - _ => unreachable!(), - }; - - let video_frame = match res { - Err(_) => { - gst::element_error!( - element, - gst::ResourceError::Read, - ["Error receiving frame"] - ); - return Err(gst::FlowError::Error); - } - Ok(None) if timeout > 0 && timer.elapsed().as_millis() >= timeout as u128 => { - gst_debug!(self.0.cat, obj: element, "Timed out -- assuming EOS",); - return Err(gst::FlowError::Eos); - } - Ok(None) => { - gst_debug!( - self.0.cat, - obj: element, - "No video frame received yet, retry" - ); - continue; - } - Ok(Some(frame)) => frame, - }; - - break video_frame; - }; - - self.0.first_frame.store(false, Ordering::SeqCst); - - gst_debug!( - self.0.cat, - obj: element, - "Received video frame {:?}", - video_frame, - ); + video_frame: VideoFrame, + ) -> Result { + gst_debug!(CAT, obj: element, "Received video frame {:?}", video_frame); let (pts, duration) = self .calculate_video_timestamp(element, &video_frame) .ok_or_else(|| { - gst_debug!(self.0.cat, obj: element, "Flushing, dropping buffer"); - gst::FlowError::CustomError + gst_debug!(CAT, obj: element, "Flushing, dropping buffer"); + gst::FlowError::Flushing })?; - // Simply read all video frames while flushing but don't copy them or anything to - // make sure that we're not accumulating anything here - if !playing || flushing { - gst_debug!(self.0.cat, obj: element, "Flushing, dropping buffer"); - return Err(gst::FlowError::CustomError); - } - let info = self.create_video_info(element, &video_frame)?; let buffer = self.create_video_buffer(element, pts, duration, &info, &video_frame); - gst_log!(self.0.cat, obj: element, "Produced buffer {:?}", buffer); + gst_log!(CAT, obj: element, "Produced video buffer {:?}", buffer); - Ok((buffer, info)) + Ok(Buffer::Video(buffer, info)) } fn calculate_video_timestamp( @@ -1289,98 +1047,28 @@ impl Receiver { vframe.into_buffer() } -} -impl Receiver { - fn capture_audio( + fn create_audio_buffer_and_info( &self, element: &gst_base::BaseSrc, - recv: &RecvInstance, - ) -> Result<(gst::Buffer, gst_audio::AudioInfo), gst::FlowError> { - let timer = time::Instant::now(); - let timeout = if self.0.first_frame.load(Ordering::SeqCst) { - self.0.connect_timeout - } else { - self.0.timeout - }; - let mut flushing; - let mut playing; - - let audio_frame = loop { - { - let queue = (self.0.queue.0).0.lock().unwrap(); - flushing = queue.flushing; - playing = queue.playing; - if !queue.capturing { - gst_debug!(self.0.cat, obj: element, "Shutting down"); - return Err(gst::FlowError::Flushing); - } - } - - let res = match recv.capture(false, true, false, 50) { - Err(_) => Err(()), - Ok(None) => Ok(None), - Ok(Some(Frame::Audio(frame))) => Ok(Some(frame)), - _ => unreachable!(), - }; - - let audio_frame = match res { - Err(_) => { - gst::element_error!( - element, - gst::ResourceError::Read, - ["Error receiving frame"] - ); - return Err(gst::FlowError::Error); - } - Ok(None) if timeout > 0 && timer.elapsed().as_millis() >= timeout as u128 => { - gst_debug!(self.0.cat, obj: element, "Timed out -- assuming EOS",); - return Err(gst::FlowError::Eos); - } - Ok(None) => { - gst_debug!( - self.0.cat, - obj: element, - "No audio frame received yet, retry" - ); - continue; - } - Ok(Some(frame)) => frame, - }; - - break audio_frame; - }; - - self.0.first_frame.store(false, Ordering::SeqCst); - - gst_debug!( - self.0.cat, - obj: element, - "Received audio frame {:?}", - audio_frame, - ); + audio_frame: AudioFrame, + ) -> Result { + gst_debug!(CAT, obj: element, "Received audio frame {:?}", audio_frame); let (pts, duration) = self .calculate_audio_timestamp(element, &audio_frame) .ok_or_else(|| { - gst_debug!(self.0.cat, obj: element, "Flushing, dropping buffer"); - gst::FlowError::CustomError + gst_debug!(CAT, obj: element, "Flushing, dropping buffer"); + gst::FlowError::Flushing })?; - // Simply read all video frames while flushing but don't copy them or anything to - // make sure that we're not accumulating anything here - if !playing || flushing { - gst_debug!(self.0.cat, obj: element, "Flushing, dropping buffer"); - return Err(gst::FlowError::CustomError); - } - let info = self.create_audio_info(element, &audio_frame)?; let buffer = self.create_audio_buffer(element, pts, duration, &info, &audio_frame); - gst_log!(self.0.cat, obj: element, "Produced buffer {:?}", buffer); + gst_log!(CAT, obj: element, "Produced audio buffer {:?}", buffer); - Ok((buffer, info)) + Ok(Buffer::Audio(buffer, info)) } fn calculate_audio_timestamp( From febb2fb03594dc8d6cdef07fd8df5939715f8d4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Wed, 29 Sep 2021 16:44:16 +0300 Subject: [PATCH 178/199] Add receive-time timestamping mode This directly uses the receive times of each packet. --- src/lib.rs | 2 ++ src/ndisrc/imp.rs | 5 ++++- src/receiver.rs | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 790c5bb1..ab2ff161 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,6 +32,8 @@ pub enum TimestampMode { Timecode = 2, #[genum(name = "NDI Timestamp", nick = "timestamp")] Timestamp = 3, + #[genum(name = "Receive Time", nick = "receive-time")] + ReceiveTime = 4, } fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { diff --git a/src/ndisrc/imp.rs b/src/ndisrc/imp.rs index 4ab3e28d..6467596d 100644 --- a/src/ndisrc/imp.rs +++ b/src/ndisrc/imp.rs @@ -475,7 +475,10 @@ impl BaseSrcImpl for NdiSrc { let settings = self.settings.lock().unwrap(); if let Some(latency) = state.current_latency { - let min = if settings.timestamp_mode != TimestampMode::Timecode { + let min = if matches!( + settings.timestamp_mode, + TimestampMode::ReceiveTimeTimecode | TimestampMode::ReceiveTimeTimestamp + ) { latency } else { gst::ClockTime::ZERO diff --git a/src/receiver.rs b/src/receiver.rs index 9154fbc7..88ada59b 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -679,6 +679,7 @@ impl Receiver { (receive_time + diff, duration) } } + TimestampMode::ReceiveTime => (receive_time, duration), }; gst_log!( From 0c89e0819f2e9345adc4c2f242f8f936ee5d2eca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Wed, 29 Sep 2021 16:44:37 +0300 Subject: [PATCH 179/199] Use gst::Element::current_running_time() instead of manually implementing it --- src/receiver.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/receiver.rs b/src/receiver.rs index 88ada59b..2054e8f6 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -624,13 +624,7 @@ impl Receiver { timecode: i64, duration: Option, ) -> Option<(gst::ClockTime, Option)> { - let clock = element.clock()?; - - // For now take the current running time as PTS. At a later time we - // will want to work with the timestamp given by the NDI SDK if available - let now = clock.time()?; - let base_time = element.base_time()?; - let receive_time = now - base_time; + let receive_time = element.current_running_time()?; let real_time_now = gst::ClockTime::from_nseconds(glib::real_time() as u64 * 1000); let timestamp = if timestamp == ndisys::NDIlib_recv_timestamp_undefined { From 9a53bcd4054167389b6be02a43859dbf9fe30ed5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Thu, 30 Sep 2021 13:24:46 +0300 Subject: [PATCH 180/199] Implement remote/local clock estimation with the same algorithm as the RTP jitterbuffer This gives fewer jumps and generally leads to smoother and more correct results, while at the same time also being faster. --- src/receiver.rs | 346 ++++++++++++++++++++++++------------------------ 1 file changed, 173 insertions(+), 173 deletions(-) diff --git a/src/receiver.rs b/src/receiver.rs index 2054e8f6..dd901edb 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -1,6 +1,6 @@ use glib::prelude::*; use gst::prelude::*; -use gst::{gst_debug, gst_error, gst_log, gst_warning}; +use gst::{gst_debug, gst_error, gst_log, gst_trace, gst_warning}; use gst_video::prelude::*; use byte_slice_cast::AsMutSliceOf; @@ -74,29 +74,34 @@ struct ReceiverQueueInner { timeout: bool, } -// 100 frames observations window over which we calculate the timestamp drift -// between sender and receiver. A bigger window allows more smoothing out of -// network effects -const WINDOW_LENGTH: usize = 100; +const WINDOW_LENGTH: u64 = 512; +const WINDOW_DURATION: u64 = 2_000_000_000; + #[derive(Clone)] struct Observations(Arc>); -struct ObservationsInner { - // NDI timestamp - GStreamer clock time tuples - values: Vec<(u64, u64)>, - values_tmp: [(u64, u64); WINDOW_LENGTH], - current_mapping: TimeMapping, - next_mapping: TimeMapping, - time_mapping_pending: bool, - // How many frames we skipped since last observation - // we took - skip_count: usize, - // How many frames we skip in this period. once skip_count - // reaches this, we take another observation - skip_period: usize, - // How many observations are left until we update the skip_period - // again. This is always initialized to WINDOW_LENGTH - skip_period_update_in: usize, +struct ObservationsInner { + base_remote_time: Option, + base_local_time: Option, + deltas: VecDeque, + min_delta: i64, + skew: i64, + filling: bool, + window_size: usize, +} + +impl Default for ObservationsInner { + fn default() -> ObservationsInner { + ObservationsInner { + base_local_time: None, + base_remote_time: None, + deltas: VecDeque::new(), + min_delta: 0, + skew: 0, + filling: true, + window_size: 0, + } + } } #[derive(Clone, Copy, Debug)] @@ -109,18 +114,12 @@ struct TimeMapping { impl Observations { fn new() -> Self { - Self(Arc::new(Mutex::new(ObservationsInner { - values: Vec::with_capacity(WINDOW_LENGTH), - values_tmp: [(0, 0); WINDOW_LENGTH], - current_mapping: TimeMapping::default(), - next_mapping: TimeMapping::default(), - time_mapping_pending: false, - skip_count: 0, - skip_period: 1, - skip_period_update_in: WINDOW_LENGTH, - }))) + Self(Arc::new(Mutex::new(ObservationsInner::default()))) } + // Based on the algorithm used in GStreamer's rtpjitterbuffer, which comes from + // Fober, Orlarey and Letz, 2005, "Real Time Clock Skew Estimation over Network Delays": + // http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.102.1546 fn process( &self, element: &gst_base::BaseSrc, @@ -132,151 +131,152 @@ impl Observations { } let time = (time.0.unwrap(), time.1); + let remote_time = time.0.nseconds(); + let local_time = time.1.nseconds(); - let mut inner = self.0.lock().unwrap(); - let ObservationsInner { - ref mut values, - ref mut values_tmp, - ref mut current_mapping, - ref mut next_mapping, - ref mut time_mapping_pending, - ref mut skip_count, - ref mut skip_period, - ref mut skip_period_update_in, - } = *inner; - - if values.is_empty() { - current_mapping.xbase = time.0.nseconds(); - current_mapping.b = time.1.nseconds(); - current_mapping.num = 1; - current_mapping.den = 1; - } - - if *skip_count == 0 { - *skip_count += 1; - if *skip_count >= *skip_period { - *skip_count = 0; - } - *skip_period_update_in -= 1; - if *skip_period_update_in == 0 { - *skip_period_update_in = WINDOW_LENGTH; - - // Start by first updating every frame, then every second frame, then every third - // frame, etc. until we update once every quarter second - let framerate = gst::ClockTime::SECOND - .checked_div(duration.unwrap_or(40 * gst::ClockTime::MSECOND).nseconds()) - .unwrap_or(25) as usize; - - if *skip_period < framerate / 4 + 1 { - *skip_period += 1; - } else { - *skip_period = framerate / 4 + 1; - } - } - - assert!(values.len() <= WINDOW_LENGTH); - - if values.len() == WINDOW_LENGTH { - values.remove(0); - } - values.push((time.0.nseconds(), time.1.nseconds())); - - if let Some((num, den, b, xbase, r_squared)) = - gst::calculate_linear_regression(values, Some(values_tmp)) - { - next_mapping.xbase = xbase; - next_mapping.b = b; - next_mapping.num = num; - next_mapping.den = den; - *time_mapping_pending = true; - gst_debug!( - CAT, - obj: element, - "Calculated new time mapping: GStreamer time = {} * (NDI time - {}) + {} ({})", - next_mapping.num as f64 / next_mapping.den as f64, - gst::ClockTime::from_nseconds(next_mapping.xbase), - gst::ClockTime::from_nseconds(next_mapping.b), - r_squared, - ); - } - } else { - *skip_count += 1; - if *skip_count >= *skip_period { - *skip_count = 0; - } - } - - if *time_mapping_pending { - let expected = gst::Clock::adjust_with_calibration( - time.0, - gst::ClockTime::from_nseconds(current_mapping.xbase), - gst::ClockTime::from_nseconds(current_mapping.b), - gst::ClockTime::from_nseconds(current_mapping.num), - gst::ClockTime::from_nseconds(current_mapping.den), - ); - let new_calculated = gst::Clock::adjust_with_calibration( - time.0, - gst::ClockTime::from_nseconds(next_mapping.xbase), - gst::ClockTime::from_nseconds(next_mapping.b), - gst::ClockTime::from_nseconds(next_mapping.num), - gst::ClockTime::from_nseconds(next_mapping.den), - ); - - let diff = if new_calculated > expected { - new_calculated - expected - } else { - expected - new_calculated - }; - - // Allow at most 5% frame duration or 2ms difference per frame - let max_diff = cmp::max( - (duration.map(|d| d / 10)).unwrap_or(2 * gst::ClockTime::MSECOND), - 2 * gst::ClockTime::MSECOND, - ); - - if diff > max_diff { - gst_debug!( - CAT, - obj: element, - "New time mapping causes difference {} but only {} allowed", - diff, - max_diff, - ); - - if new_calculated > expected { - current_mapping.b = (expected + max_diff).nseconds(); - current_mapping.xbase = time.0.nseconds(); - } else { - current_mapping.b = (expected - max_diff).nseconds(); - current_mapping.xbase = time.0.nseconds(); - } - } else { - *current_mapping = *next_mapping; - } - } - - let converted_timestamp = gst::Clock::adjust_with_calibration( - time.0, - gst::ClockTime::from_nseconds(current_mapping.xbase), - gst::ClockTime::from_nseconds(current_mapping.b), - gst::ClockTime::from_nseconds(current_mapping.num), - gst::ClockTime::from_nseconds(current_mapping.den), - ); - let converted_duration = - duration.and_then(|d| d.mul_div_floor(current_mapping.num, current_mapping.den)); - - gst_debug!( + gst_trace!( CAT, obj: element, - "Converted timestamp {}/{} to {}, duration {} to {}", - time.0, - time.1, - converted_timestamp.display(), - duration.display(), - converted_duration.display(), + "Local time {}, remote time {}", + gst::ClockTime::from_nseconds(local_time), + gst::ClockTime::from_nseconds(remote_time), ); - (converted_timestamp, converted_duration) + let mut inner = self.0.lock().unwrap(); + + let (base_remote_time, base_local_time) = + match (inner.base_remote_time, inner.base_local_time) { + (Some(remote), Some(local)) => (remote, local), + _ => { + gst_debug!( + CAT, + obj: element, + "Initializing base time: local {}, remote {}", + gst::ClockTime::from_nseconds(local_time), + gst::ClockTime::from_nseconds(remote_time), + ); + inner.base_remote_time = Some(remote_time); + inner.base_local_time = Some(local_time); + + return (gst::ClockTime::from_nseconds(local_time), duration); + } + }; + + let remote_diff = remote_time.saturating_sub(base_remote_time); + let local_diff = local_time.saturating_sub(base_local_time); + let delta = (local_diff as i64) - (remote_diff as i64); + + gst_trace!( + CAT, + obj: element, + "Local diff {}, remote diff {}, delta {}", + gst::ClockTime::from_nseconds(local_diff), + gst::ClockTime::from_nseconds(remote_diff), + delta, + ); + + if remote_diff > 0 && local_diff > 0 { + let slope = (local_diff as f64) / (remote_diff as f64); + if slope < 0.8 || slope > 1.2 { + gst_warning!( + CAT, + obj: element, + "Too small/big slope {}, resetting", + slope + ); + *inner = ObservationsInner::default(); + gst_debug!( + CAT, + obj: element, + "Initializing base time: local {}, remote {}", + gst::ClockTime::from_nseconds(local_time), + gst::ClockTime::from_nseconds(remote_time), + ); + inner.base_remote_time = Some(remote_time); + inner.base_local_time = Some(local_time); + + return (gst::ClockTime::from_nseconds(local_time), duration); + } + } + + if (delta > inner.skew && delta - inner.skew > 1_000_000_000) + || (delta < inner.skew && inner.skew - delta > 1_000_000_000) + { + gst_warning!( + CAT, + obj: element, + "Delta {} too far from skew {}, resetting", + delta, + inner.skew + ); + *inner = ObservationsInner::default(); + gst_debug!( + CAT, + obj: element, + "Initializing base time: local {}, remote {}", + gst::ClockTime::from_nseconds(local_time), + gst::ClockTime::from_nseconds(remote_time), + ); + inner.base_remote_time = Some(remote_time); + inner.base_local_time = Some(local_time); + + return (gst::ClockTime::from_nseconds(local_time), duration); + } + + if inner.filling { + if inner.deltas.is_empty() || delta < inner.min_delta { + inner.min_delta = delta; + } + inner.deltas.push_back(delta); + + if remote_diff > WINDOW_DURATION || inner.deltas.len() as u64 == WINDOW_LENGTH { + inner.window_size = inner.deltas.len(); + inner.skew = inner.min_delta; + inner.filling = false; + } else { + let perc_time = remote_diff.mul_div_floor(100, WINDOW_DURATION).unwrap() as i64; + let perc_window = (inner.deltas.len() as u64) + .mul_div_floor(100, WINDOW_LENGTH) + .unwrap() as i64; + let perc = cmp::max(perc_time, perc_window); + + inner.skew = (perc * inner.min_delta + ((10_000 - perc) * inner.skew)) / 10_000; + } + } else { + let old = inner.deltas.pop_front().unwrap(); + inner.deltas.push_back(delta); + + if delta <= inner.min_delta { + inner.min_delta = delta; + } else if old == inner.min_delta { + inner.min_delta = inner.deltas.iter().copied().min().unwrap(); + } + + inner.skew = (inner.min_delta + (124 * inner.skew)) / 125; + } + + let out_time = base_local_time + remote_diff; + let out_time = if inner.skew < 0 { + out_time.saturating_sub((-inner.skew) as u64) + } else { + out_time + (inner.skew as u64) + }; + + gst_trace!( + CAT, + obj: element, + "Skew {}, min delta {}", + inner.skew, + inner.min_delta + ); + gst_trace!( + CAT, + obj: element, + "Outputting {}", + gst::ClockTime::from_nseconds(out_time) + ); + + (gst::ClockTime::from_nseconds(out_time), duration) } } @@ -285,8 +285,8 @@ impl Default for TimeMapping { Self { xbase: 0, b: 0, - num: 1, - den: 1, + num: 0, + den: 0, } } } From 0911775142c5886d7bfb557facc0ce1e3a7dbba5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Thu, 30 Sep 2021 13:33:18 +0300 Subject: [PATCH 181/199] Set the discont flag on the first audio/video buffer --- src/receiver.rs | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/receiver.rs b/src/receiver.rs index dd901edb..43cb7741 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -493,6 +493,8 @@ impl Receiver { } fn receive_thread(receiver: &Weak, recv: RecvInstance) { + let mut first_video_frame = true; + let mut first_audio_frame = true; let mut first_frame = true; let mut timer = time::Instant::now(); @@ -553,11 +555,31 @@ impl Receiver { } Ok(Some(Frame::Video(frame))) => { first_frame = false; - receiver.create_video_buffer_and_info(&element, frame) + let mut buffer = receiver.create_video_buffer_and_info(&element, frame); + if first_video_frame { + if let Ok(Buffer::Video(ref mut buffer, _)) = buffer { + buffer + .get_mut() + .unwrap() + .set_flags(gst::BufferFlags::DISCONT); + first_video_frame = false; + } + } + buffer } Ok(Some(Frame::Audio(frame))) => { first_frame = false; - receiver.create_audio_buffer_and_info(&element, frame) + let mut buffer = receiver.create_audio_buffer_and_info(&element, frame); + if first_audio_frame { + if let Ok(Buffer::Video(ref mut buffer, _)) = buffer { + buffer + .get_mut() + .unwrap() + .set_flags(gst::BufferFlags::DISCONT); + first_audio_frame = false; + } + } + buffer } Ok(Some(Frame::Metadata(frame))) => { if let Some(metadata) = frame.metadata() { From e642d6a4c1417a6a79b63f4830c83071458a1af1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Thu, 30 Sep 2021 13:37:30 +0300 Subject: [PATCH 182/199] Set the RESYNC flag on buffers after time tracking state was reset --- src/receiver.rs | 60 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/src/receiver.rs b/src/receiver.rs index 43cb7741..1cc413b4 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -125,9 +125,9 @@ impl Observations { element: &gst_base::BaseSrc, time: (Option, gst::ClockTime), duration: Option, - ) -> (gst::ClockTime, Option) { + ) -> (gst::ClockTime, Option, bool) { if time.0.is_none() { - return (time.1, duration); + return (time.1, duration, false); } let time = (time.0.unwrap(), time.1); @@ -158,7 +158,7 @@ impl Observations { inner.base_remote_time = Some(remote_time); inner.base_local_time = Some(local_time); - return (gst::ClockTime::from_nseconds(local_time), duration); + return (gst::ClockTime::from_nseconds(local_time), duration, true); } }; @@ -184,7 +184,10 @@ impl Observations { "Too small/big slope {}, resetting", slope ); + + let discont = !inner.deltas.is_empty(); *inner = ObservationsInner::default(); + gst_debug!( CAT, obj: element, @@ -195,7 +198,7 @@ impl Observations { inner.base_remote_time = Some(remote_time); inner.base_local_time = Some(local_time); - return (gst::ClockTime::from_nseconds(local_time), duration); + return (gst::ClockTime::from_nseconds(local_time), duration, discont); } } @@ -209,7 +212,10 @@ impl Observations { delta, inner.skew ); + + let discont = !inner.deltas.is_empty(); *inner = ObservationsInner::default(); + gst_debug!( CAT, obj: element, @@ -220,7 +226,7 @@ impl Observations { inner.base_remote_time = Some(remote_time); inner.base_local_time = Some(local_time); - return (gst::ClockTime::from_nseconds(local_time), duration); + return (gst::ClockTime::from_nseconds(local_time), duration, discont); } if inner.filling { @@ -276,7 +282,7 @@ impl Observations { gst::ClockTime::from_nseconds(out_time) ); - (gst::ClockTime::from_nseconds(out_time), duration) + (gst::ClockTime::from_nseconds(out_time), duration, false) } } @@ -645,7 +651,7 @@ impl Receiver { timestamp: i64, timecode: i64, duration: Option, - ) -> Option<(gst::ClockTime, Option)> { + ) -> Option<(gst::ClockTime, Option, bool)> { let receive_time = element.current_running_time()?; let real_time_now = gst::ClockTime::from_nseconds(glib::real_time() as u64 * 1000); @@ -667,7 +673,7 @@ impl Receiver { real_time_now, ); - let (pts, duration) = match self.0.timestamp_mode { + let (pts, duration, discont) = match self.0.timestamp_mode { TimestampMode::ReceiveTimeTimecode => { self.0 .observations @@ -678,24 +684,24 @@ impl Receiver { .observations .process(element, (timestamp, receive_time), duration) } - TimestampMode::Timecode => (timecode, duration), - TimestampMode::Timestamp if timestamp.is_none() => (receive_time, duration), + TimestampMode::Timecode => (timecode, duration, false), + TimestampMode::Timestamp if timestamp.is_none() => (receive_time, duration, false), TimestampMode::Timestamp => { // Timestamps are relative to the UNIX epoch let timestamp = timestamp?; if real_time_now > timestamp { let diff = real_time_now - timestamp; if diff > receive_time { - (gst::ClockTime::ZERO, duration) + (gst::ClockTime::ZERO, duration, false) } else { - (receive_time - diff, duration) + (receive_time - diff, duration, false) } } else { let diff = timestamp - real_time_now; - (receive_time + diff, duration) + (receive_time + diff, duration, false) } } - TimestampMode::ReceiveTime => (receive_time, duration), + TimestampMode::ReceiveTime => (receive_time, duration, false), }; gst_log!( @@ -706,7 +712,7 @@ impl Receiver { duration.display(), ); - Some((pts, duration)) + Some((pts, duration, discont)) } fn create_video_buffer_and_info( @@ -716,7 +722,7 @@ impl Receiver { ) -> Result { gst_debug!(CAT, obj: element, "Received video frame {:?}", video_frame); - let (pts, duration) = self + let (pts, duration, discont) = self .calculate_video_timestamp(element, &video_frame) .ok_or_else(|| { gst_debug!(CAT, obj: element, "Flushing, dropping buffer"); @@ -725,7 +731,13 @@ impl Receiver { let info = self.create_video_info(element, &video_frame)?; - let buffer = self.create_video_buffer(element, pts, duration, &info, &video_frame); + let mut buffer = self.create_video_buffer(element, pts, duration, &info, &video_frame); + if discont { + buffer + .get_mut() + .unwrap() + .set_flags(gst::BufferFlags::RESYNC); + } gst_log!(CAT, obj: element, "Produced video buffer {:?}", buffer); @@ -736,7 +748,7 @@ impl Receiver { &self, element: &gst_base::BaseSrc, video_frame: &VideoFrame, - ) -> Option<(gst::ClockTime, Option)> { + ) -> Option<(gst::ClockTime, Option, bool)> { let duration = gst::ClockTime::SECOND.mul_div_floor( video_frame.frame_rate().1 as u64, video_frame.frame_rate().0 as u64, @@ -1072,7 +1084,7 @@ impl Receiver { ) -> Result { gst_debug!(CAT, obj: element, "Received audio frame {:?}", audio_frame); - let (pts, duration) = self + let (pts, duration, discont) = self .calculate_audio_timestamp(element, &audio_frame) .ok_or_else(|| { gst_debug!(CAT, obj: element, "Flushing, dropping buffer"); @@ -1081,7 +1093,13 @@ impl Receiver { let info = self.create_audio_info(element, &audio_frame)?; - let buffer = self.create_audio_buffer(element, pts, duration, &info, &audio_frame); + let mut buffer = self.create_audio_buffer(element, pts, duration, &info, &audio_frame); + if discont { + buffer + .get_mut() + .unwrap() + .set_flags(gst::BufferFlags::RESYNC); + } gst_log!(CAT, obj: element, "Produced audio buffer {:?}", buffer); @@ -1092,7 +1110,7 @@ impl Receiver { &self, element: &gst_base::BaseSrc, audio_frame: &AudioFrame, - ) -> Option<(gst::ClockTime, Option)> { + ) -> Option<(gst::ClockTime, Option, bool)> { let duration = gst::ClockTime::SECOND.mul_div_floor( audio_frame.no_samples() as u64, audio_frame.sample_rate() as u64, From c8f12b8c3bdd08d5f829b22e7d0d306c77789fc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Thu, 30 Sep 2021 15:53:51 +0300 Subject: [PATCH 183/199] Use a const function to create fourccs from strings --- src/ndisys.rs | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/ndisys.rs b/src/ndisys.rs index c5ad3342..f40da31b 100644 --- a/src/ndisys.rs +++ b/src/ndisys.rs @@ -122,18 +122,25 @@ pub enum NDIlib_recv_color_format_e { NDIlib_recv_color_format_best = 101, } +const fn make_fourcc(fourcc: &[u8; 4]) -> u32 { + ((fourcc[0] as u32) << 0) + | ((fourcc[1] as u32) << 8) + | ((fourcc[2] as u32) << 16) + | ((fourcc[3] as u32) << 24) +} + pub type NDIlib_FourCC_video_type_e = u32; -pub const NDIlib_FourCC_video_type_UYVY: NDIlib_FourCC_video_type_e = 0x59_56_59_55; -pub const NDIlib_FourCC_video_type_UYVA: NDIlib_FourCC_video_type_e = 0x41_56_56_55; -pub const NDIlib_FourCC_video_type_P216: NDIlib_FourCC_video_type_e = 0x36_31_32_50; -pub const NDIlib_FourCC_video_type_PA16: NDIlib_FourCC_video_type_e = 0x36_31_41_50; -pub const NDIlib_FourCC_video_type_YV12: NDIlib_FourCC_video_type_e = 0x32_31_56_59; -pub const NDIlib_FourCC_video_type_I420: NDIlib_FourCC_video_type_e = 0x30_32_34_49; -pub const NDIlib_FourCC_video_type_NV12: NDIlib_FourCC_video_type_e = 0x32_31_56_4e; -pub const NDIlib_FourCC_video_type_BGRA: NDIlib_FourCC_video_type_e = 0x41_52_47_42; -pub const NDIlib_FourCC_video_type_BGRX: NDIlib_FourCC_video_type_e = 0x58_52_47_42; -pub const NDIlib_FourCC_video_type_RGBA: NDIlib_FourCC_video_type_e = 0x41_42_47_52; -pub const NDIlib_FourCC_video_type_RGBX: NDIlib_FourCC_video_type_e = 0x58_42_47_52; +pub const NDIlib_FourCC_video_type_UYVY: NDIlib_FourCC_video_type_e = make_fourcc(b"UYVY"); +pub const NDIlib_FourCC_video_type_UYVA: NDIlib_FourCC_video_type_e = make_fourcc(b"UYVA"); +pub const NDIlib_FourCC_video_type_P216: NDIlib_FourCC_video_type_e = make_fourcc(b"P216"); +pub const NDIlib_FourCC_video_type_PA16: NDIlib_FourCC_video_type_e = make_fourcc(b"PA16"); +pub const NDIlib_FourCC_video_type_YV12: NDIlib_FourCC_video_type_e = make_fourcc(b"YV12"); +pub const NDIlib_FourCC_video_type_I420: NDIlib_FourCC_video_type_e = make_fourcc(b"I420"); +pub const NDIlib_FourCC_video_type_NV12: NDIlib_FourCC_video_type_e = make_fourcc(b"NV12"); +pub const NDIlib_FourCC_video_type_BGRA: NDIlib_FourCC_video_type_e = make_fourcc(b"BGRA"); +pub const NDIlib_FourCC_video_type_BGRX: NDIlib_FourCC_video_type_e = make_fourcc(b"BGRX"); +pub const NDIlib_FourCC_video_type_RGBA: NDIlib_FourCC_video_type_e = make_fourcc(b"RGBA"); +pub const NDIlib_FourCC_video_type_RGBX: NDIlib_FourCC_video_type_e = make_fourcc(b"RGBX"); #[repr(u32)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] From 7483a66b660c883ea7aff32f241a69f1e25cf51e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Thu, 30 Sep 2021 15:59:18 +0300 Subject: [PATCH 184/199] Convert NDIlib_recv_color_format_e to a type alias with constants There are also other values available. --- src/ndi.rs | 2 +- src/ndisys.rs | 17 +++++++---------- src/receiver.rs | 2 +- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/ndi.rs b/src/ndi.rs index 648db019..5c41b682 100644 --- a/src/ndi.rs +++ b/src/ndi.rs @@ -263,7 +263,7 @@ impl RecvInstance { url_address, allow_video_fields: true, bandwidth: NDIlib_recv_bandwidth_highest, - color_format: NDIlib_recv_color_format_e::NDIlib_recv_color_format_UYVY_BGRA, + color_format: NDIlib_recv_color_format_UYVY_BGRA, ndi_recv_name, } } diff --git a/src/ndisys.rs b/src/ndisys.rs index f40da31b..f5707125 100644 --- a/src/ndisys.rs +++ b/src/ndisys.rs @@ -111,16 +111,13 @@ pub const NDIlib_recv_bandwidth_audio_only: NDIlib_recv_bandwidth_e = 10; pub const NDIlib_recv_bandwidth_lowest: NDIlib_recv_bandwidth_e = 0; pub const NDIlib_recv_bandwidth_highest: NDIlib_recv_bandwidth_e = 100; -#[repr(u32)] -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub enum NDIlib_recv_color_format_e { - NDIlib_recv_color_format_BGRX_BGRA = 0, - NDIlib_recv_color_format_UYVY_BGRA = 1, - NDIlib_recv_color_format_RGBX_RGBA = 2, - NDIlib_recv_color_format_UYVY_RGBA = 3, - NDIlib_recv_color_format_fastest = 100, - NDIlib_recv_color_format_best = 101, -} +pub type NDIlib_recv_color_format_e = u32; +pub const NDIlib_recv_color_format_BGRX_BGRA: NDIlib_recv_color_format_e = 0; +pub const NDIlib_recv_color_format_UYVY_BGRA: NDIlib_recv_color_format_e = 1; +pub const NDIlib_recv_color_format_RGBX_RGBA: NDIlib_recv_color_format_e = 2; +pub const NDIlib_recv_color_format_UYVY_RGBA: NDIlib_recv_color_format_e = 3; +pub const NDIlib_recv_color_format_fastest: NDIlib_recv_color_format_e = 100; +pub const NDIlib_recv_color_format_best: NDIlib_recv_color_format_e = 101; const fn make_fourcc(fourcc: &[u8; 4]) -> u32 { ((fourcc[0] as u32) << 0) diff --git a/src/receiver.rs b/src/receiver.rs index 1cc413b4..d6356a88 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -465,7 +465,7 @@ impl Receiver { // broken with interlaced content currently let recv = RecvInstance::builder(ndi_name, url_address, receiver_ndi_name) .bandwidth(bandwidth) - .color_format(NDIlib_recv_color_format_e::NDIlib_recv_color_format_UYVY_BGRA) + .color_format(NDIlib_recv_color_format_UYVY_BGRA) .allow_video_fields(true) .build(); let recv = match recv { From 8cf682d72bac664e3c4c1326415831afcdae6c55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Thu, 30 Sep 2021 22:44:31 +0300 Subject: [PATCH 185/199] Move to NDIlib_recv_capture_v3() and NDIlib_send_send_audio_v3() These allow more control over the data that is being sent/received, but require NDI SDK 4.0 or newer. --- src/ndi.rs | 135 +++++++++++++++++++++---------------- src/ndisink/imp.rs | 11 ++- src/ndisinkcombiner/imp.rs | 2 +- src/ndisys.rs | 43 ++++-------- src/receiver.rs | 82 ++++++++++++++-------- 5 files changed, 150 insertions(+), 123 deletions(-) diff --git a/src/ndi.rs b/src/ndi.rs index 5c41b682..e184c758 100644 --- a/src/ndi.rs +++ b/src/ndi.rs @@ -292,7 +292,7 @@ impl RecvInstance { let mut audio_frame = mem::zeroed(); let mut metadata_frame = mem::zeroed(); - let res = NDIlib_recv_capture_v2( + let res = NDIlib_recv_capture_v3( ptr, &mut video_frame, &mut audio_frame, @@ -386,7 +386,7 @@ impl SendInstance { pub fn send_audio(&mut self, frame: &AudioFrame) { unsafe { - NDIlib_send_send_audio_v2(self.0.as_ptr(), frame.as_ptr()); + NDIlib_send_send_audio_v3(self.0.as_ptr(), frame.as_ptr()); } } } @@ -501,7 +501,26 @@ impl<'a> VideoFrame<'a> { } } - pub fn data(&self) -> &[u8] { + 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) + { + return None; + } + // FIXME: Unclear if this is correct. Needs to be validated against an actual // interlaced stream let frame_size = if self.frame_format_type() @@ -518,7 +537,10 @@ impl<'a> VideoFrame<'a> { use std::slice; match self { VideoFrame::BorrowedRecv(ref frame, _) | VideoFrame::BorrowedGst(ref frame, _) => { - slice::from_raw_parts(frame.p_data as *const u8, frame_size as usize) + Some(slice::from_raw_parts( + frame.p_data as *const u8, + frame_size as usize, + )) } } } @@ -710,11 +732,11 @@ impl<'a> Drop for VideoFrame<'a> { #[derive(Debug)] pub enum AudioFrame<'a> { Owned( - NDIlib_audio_frame_v2_t, + NDIlib_audio_frame_v3_t, Option, Option>, ), - BorrowedRecv(NDIlib_audio_frame_v2_t, &'a RecvInstance), + BorrowedRecv(NDIlib_audio_frame_v3_t, &'a RecvInstance), } impl<'a> AudioFrame<'a> { @@ -750,24 +772,39 @@ impl<'a> AudioFrame<'a> { } } - pub fn data(&self) -> &[u8] { + 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 None; + } + match self { AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => { - slice::from_raw_parts( + Some(slice::from_raw_parts( frame.p_data as *const u8, - (frame.no_samples * frame.channel_stride_in_bytes) as usize, - ) + (frame.no_channels * frame.channel_stride_or_data_size_in_bytes) as usize, + )) } } } } - pub fn channel_stride_in_bytes(&self) -> i32 { + pub fn channel_stride_or_data_size_in_bytes(&self) -> i32 { match self { AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => { - frame.channel_stride_in_bytes + frame.channel_stride_or_data_size_in_bytes } } } @@ -794,73 +831,55 @@ impl<'a> AudioFrame<'a> { } } - pub fn as_ptr(&self) -> *const NDIlib_audio_frame_v2_t { + pub fn as_ptr(&self) -> *const NDIlib_audio_frame_v3_t { match self { AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => frame, } } - pub fn copy_to_interleaved_16s(&self, data: &mut [i16]) { - assert_eq!( - data.len(), - (self.no_samples() * self.no_channels()) as usize - ); - - let mut dst = NDIlib_audio_frame_interleaved_16s_t { - sample_rate: self.sample_rate(), - no_channels: self.no_channels(), - no_samples: self.no_samples(), - timecode: self.timecode(), - reference_level: 0, - p_data: data.as_mut_ptr(), - }; - - unsafe { - NDIlib_util_audio_to_interleaved_16s_v2(self.as_ptr(), &mut dst); - } - } - - pub fn try_from_interleaved_16s( + pub fn try_from_buffer( info: &gst_audio::AudioInfo, buffer: &gst::BufferRef, timecode: i64, ) -> Result { - if info.format() != gst_audio::AUDIO_FORMAT_S16 { + 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 src_data = map.as_slice_of::().map_err(|_| ())?; - let src = NDIlib_audio_frame_interleaved_16s_t { + 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.into_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: src_data.len() as i32 / info.channels() as i32, + no_samples, timecode, - reference_level: 0, - p_data: src_data.as_ptr() as *mut i16, - }; - - let channel_stride_in_bytes = src.no_samples * mem::size_of::() as i32; - let mut dest_data = - Vec::with_capacity(channel_stride_in_bytes as usize * info.channels() as usize); - - let mut dest = NDIlib_audio_frame_v2_t { - sample_rate: src.sample_rate, - no_channels: src.no_channels, - no_samples: src.no_samples, - timecode: src.timecode, + FourCC: NDIlib_FourCC_audio_type_FLTp, p_data: dest_data.as_mut_ptr(), - channel_stride_in_bytes, + channel_stride_or_data_size_in_bytes, p_metadata: ptr::null(), timestamp: 0, }; - unsafe { - NDIlib_util_audio_from_interleaved_16s_v2(&src, &mut dest); - dest_data.set_len(dest_data.capacity()); - } - Ok(AudioFrame::Owned(dest, None, Some(dest_data))) } } @@ -870,7 +889,7 @@ impl<'a> Drop for AudioFrame<'a> { fn drop(&mut self) { if let AudioFrame::BorrowedRecv(ref mut frame, recv) = *self { unsafe { - NDIlib_recv_free_audio_v2(recv.0.as_ptr() as *mut _, frame); + NDIlib_recv_free_audio_v3(recv.0.as_ptr() as *mut _, frame); } } } diff --git a/src/ndisink/imp.rs b/src/ndisink/imp.rs index fc23ff01..417da0fe 100644 --- a/src/ndisink/imp.rs +++ b/src/ndisink/imp.rs @@ -151,7 +151,7 @@ impl ElementImpl for NdiSink { ) .structure( gst::Structure::builder("audio/x-raw") - .field("format", &gst_audio::AUDIO_FORMAT_S16.to_str()) + .field("format", &gst_audio::AUDIO_FORMAT_F32.to_str()) .field("rate", &gst::IntRange::::new(1, i32::MAX)) .field("channels", &gst::IntRange::::new(1, i32::MAX)) .field("layout", &"interleaved") @@ -256,9 +256,8 @@ impl BaseSinkImpl for NdiSink { if let Some(ref info) = state.video_info { if let Some(audio_meta) = buffer.meta::() { for (buffer, info, timecode) in audio_meta.buffers() { - let frame = - crate::ndi::AudioFrame::try_from_interleaved_16s(info, buffer, *timecode) - .map_err(|_| { + let frame = crate::ndi::AudioFrame::try_from_buffer(info, buffer, *timecode) + .map_err(|_| { gst_error!(CAT, obj: element, "Unsupported audio frame"); gst::FlowError::NotNegotiated })?; @@ -334,8 +333,8 @@ impl BaseSinkImpl for NdiSink { .map(|time| (time.nseconds() / 100) as i64) .unwrap_or(crate::ndisys::NDIlib_send_timecode_synthesize); - let frame = crate::ndi::AudioFrame::try_from_interleaved_16s(info, buffer, timecode) - .map_err(|_| { + let frame = + crate::ndi::AudioFrame::try_from_buffer(info, buffer, timecode).map_err(|_| { gst_error!(CAT, obj: element, "Unsupported audio frame"); gst::FlowError::NotNegotiated })?; diff --git a/src/ndisinkcombiner/imp.rs b/src/ndisinkcombiner/imp.rs index 9ba0e28a..4e1a1659 100644 --- a/src/ndisinkcombiner/imp.rs +++ b/src/ndisinkcombiner/imp.rs @@ -122,7 +122,7 @@ impl ElementImpl for NdiSinkCombiner { .unwrap(); let caps = gst::Caps::builder("audio/x-raw") - .field("format", &gst_audio::AUDIO_FORMAT_S16.to_str()) + .field("format", &gst_audio::AUDIO_FORMAT_F32.to_str()) .field("rate", &gst::IntRange::::new(1, i32::MAX)) .field("channels", &gst::IntRange::::new(1, i32::MAX)) .field("layout", &"interleaved") diff --git a/src/ndisys.rs b/src/ndisys.rs index f5707125..eb0cdd51 100644 --- a/src/ndisys.rs +++ b/src/ndisys.rs @@ -39,10 +39,10 @@ extern "C" { p_instance: NDIlib_recv_instance_t, p_metadata: *const NDIlib_metadata_frame_t, ) -> bool; - pub fn NDIlib_recv_capture_v2( + pub fn NDIlib_recv_capture_v3( p_instance: NDIlib_recv_instance_t, p_video_data: *mut NDIlib_video_frame_v2_t, - p_audio_data: *mut NDIlib_audio_frame_v2_t, + p_audio_data: *mut NDIlib_audio_frame_v3_t, p_metadata: *mut NDIlib_metadata_frame_t, timeout_in_ms: u32, ) -> NDIlib_frame_type_e; @@ -50,9 +50,9 @@ extern "C" { p_instance: NDIlib_recv_instance_t, p_video_data: *mut NDIlib_video_frame_v2_t, ); - pub fn NDIlib_recv_free_audio_v2( + pub fn NDIlib_recv_free_audio_v3( p_instance: NDIlib_recv_instance_t, - p_audio_data: *mut NDIlib_audio_frame_v2_t, + p_audio_data: *mut NDIlib_audio_frame_v3_t, ); pub fn NDIlib_recv_free_metadata( p_instance: NDIlib_recv_instance_t, @@ -70,9 +70,9 @@ extern "C" { p_instance: NDIlib_send_instance_t, p_video_data: *const NDIlib_video_frame_v2_t, ); - pub fn NDIlib_send_send_audio_v2( + pub fn NDIlib_send_send_audio_v3( p_instance: NDIlib_send_instance_t, - p_audio_data: *const NDIlib_audio_frame_v2_t, + p_audio_data: *const NDIlib_audio_frame_v3_t, ); } @@ -139,6 +139,9 @@ pub const NDIlib_FourCC_video_type_BGRX: NDIlib_FourCC_video_type_e = make_fourc pub const NDIlib_FourCC_video_type_RGBA: NDIlib_FourCC_video_type_e = make_fourcc(b"RGBA"); pub const NDIlib_FourCC_video_type_RGBX: NDIlib_FourCC_video_type_e = make_fourcc(b"RGBX"); +pub type NDIlib_FourCC_audio_type_e = u32; +pub const NDIlib_FourCC_audio_type_FLTp: NDIlib_FourCC_video_type_e = make_fourcc(b"FLTp"); + #[repr(u32)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum NDIlib_frame_format_type_e { @@ -216,36 +219,14 @@ pub struct NDIlib_video_frame_v2_t { #[repr(C)] #[derive(Debug, Copy, Clone)] -pub struct NDIlib_audio_frame_v2_t { +pub struct NDIlib_audio_frame_v3_t { pub sample_rate: ::std::os::raw::c_int, pub no_channels: ::std::os::raw::c_int, pub no_samples: ::std::os::raw::c_int, pub timecode: i64, + pub FourCC: NDIlib_FourCC_audio_type_e, pub p_data: *const ::std::os::raw::c_float, - pub channel_stride_in_bytes: ::std::os::raw::c_int, + pub channel_stride_or_data_size_in_bytes: ::std::os::raw::c_int, pub p_metadata: *const ::std::os::raw::c_char, pub timestamp: i64, } - -extern "C" { - pub fn NDIlib_util_audio_to_interleaved_16s_v2( - p_src: *const NDIlib_audio_frame_v2_t, - p_dst: *mut NDIlib_audio_frame_interleaved_16s_t, - ); - - pub fn NDIlib_util_audio_from_interleaved_16s_v2( - p_src: *const NDIlib_audio_frame_interleaved_16s_t, - p_dst: *mut NDIlib_audio_frame_v2_t, - ); -} - -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct NDIlib_audio_frame_interleaved_16s_t { - pub sample_rate: ::std::os::raw::c_int, - pub no_channels: ::std::os::raw::c_int, - pub no_samples: ::std::os::raw::c_int, - pub timecode: i64, - pub reference_level: ::std::os::raw::c_int, - pub p_data: *mut i16, -} diff --git a/src/receiver.rs b/src/receiver.rs index d6356a88..dd457492 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -3,7 +3,7 @@ use gst::prelude::*; use gst::{gst_debug, gst_error, gst_log, gst_trace, gst_warning}; use gst_video::prelude::*; -use byte_slice_cast::AsMutSliceOf; +use byte_slice_cast::*; use std::cmp; use std::collections::VecDeque; @@ -731,7 +731,7 @@ impl Receiver { let info = self.create_video_info(element, &video_frame)?; - let mut buffer = self.create_video_buffer(element, pts, duration, &info, &video_frame); + let mut buffer = self.create_video_buffer(element, pts, duration, &info, &video_frame)?; if discont { buffer .get_mut() @@ -888,8 +888,8 @@ impl Receiver { duration: Option, info: &gst_video::VideoInfo, video_frame: &VideoFrame, - ) -> gst::Buffer { - let mut buffer = gst::Buffer::with_size(info.size()).unwrap(); + ) -> Result { + let mut buffer = self.copy_video_frame(element, info, video_frame)?; { let buffer = buffer.get_mut().unwrap(); buffer.set_pts(pts); @@ -950,16 +950,18 @@ impl Receiver { } } - self.copy_video_frame(element, info, buffer, video_frame) + Ok(buffer) } fn copy_video_frame( &self, _element: &gst_base::BaseSrc, info: &gst_video::VideoInfo, - buffer: gst::Buffer, video_frame: &VideoFrame, - ) -> gst::Buffer { + ) -> Result { + let src = video_frame.data().ok_or(gst::FlowError::NotNegotiated)?; + + let buffer = gst::Buffer::with_size(info.size()).unwrap(); let mut vframe = gst_video::VideoFrame::from_buffer_writable(buffer, info).unwrap(); match info.format() { @@ -976,7 +978,6 @@ impl Receiver { let dest_stride = vframe.plane_stride()[0] as usize; let dest = vframe.plane_data_mut(0).unwrap(); let src_stride = video_frame.line_stride_or_data_size_in_bytes() as usize; - let src = video_frame.data(); for (dest, src) in dest .chunks_exact_mut(dest_stride) @@ -993,7 +994,6 @@ impl Receiver { let dest_stride = vframe.plane_stride()[0] as usize; let dest = vframe.plane_data_mut(0).unwrap(); let src_stride = video_frame.line_stride_or_data_size_in_bytes() as usize; - let src = video_frame.data(); for (dest, src) in dest .chunks_exact_mut(dest_stride) @@ -1009,7 +1009,7 @@ impl Receiver { let dest_stride = vframe.plane_stride()[1] as usize; let dest = vframe.plane_data_mut(1).unwrap(); let src_stride = video_frame.line_stride_or_data_size_in_bytes() as usize; - let src = &video_frame.data()[(video_frame.yres() as usize * src_stride)..]; + let src = &src[(video_frame.yres() as usize * src_stride)..]; for (dest, src) in dest .chunks_exact_mut(dest_stride) @@ -1026,7 +1026,6 @@ impl Receiver { let dest_stride = vframe.plane_stride()[0] as usize; let dest = vframe.plane_data_mut(0).unwrap(); let src_stride = video_frame.line_stride_or_data_size_in_bytes() as usize; - let src = video_frame.data(); for (dest, src) in dest .chunks_exact_mut(dest_stride) @@ -1043,7 +1042,7 @@ impl Receiver { let dest = vframe.plane_data_mut(1).unwrap(); let src_stride = video_frame.line_stride_or_data_size_in_bytes() as usize; let src_stride1 = video_frame.line_stride_or_data_size_in_bytes() as usize / 2; - let src = &video_frame.data()[(video_frame.yres() as usize * src_stride)..]; + let src = &src[(video_frame.yres() as usize * src_stride)..]; for (dest, src) in dest .chunks_exact_mut(dest_stride) @@ -1060,7 +1059,7 @@ impl Receiver { let dest = vframe.plane_data_mut(2).unwrap(); let src_stride = video_frame.line_stride_or_data_size_in_bytes() as usize; let src_stride1 = video_frame.line_stride_or_data_size_in_bytes() as usize / 2; - let src = &video_frame.data()[(video_frame.yres() as usize * src_stride + let src = &src[(video_frame.yres() as usize * src_stride + (video_frame.yres() as usize + 1) / 2 * src_stride1)..]; for (dest, src) in dest @@ -1074,7 +1073,7 @@ impl Receiver { _ => unreachable!(), } - vframe.into_buffer() + Ok(vframe.into_buffer()) } fn create_audio_buffer_and_info( @@ -1093,7 +1092,7 @@ impl Receiver { let info = self.create_audio_info(element, &audio_frame)?; - let mut buffer = self.create_audio_buffer(element, pts, duration, &info, &audio_frame); + let mut buffer = self.create_audio_buffer(element, pts, duration, &info, &audio_frame)?; if discont { buffer .get_mut() @@ -1129,13 +1128,22 @@ impl Receiver { element: &gst_base::BaseSrc, audio_frame: &AudioFrame, ) -> Result { + if audio_frame.fourcc() != NDIlib_FourCC_audio_type_FLTp { + gst::element_error!( + element, + gst::StreamError::Format, + ["Unsupported audio fourcc {:08x}", audio_frame.fourcc()] + ); + return Err(gst::FlowError::NotNegotiated); + } + let builder = gst_audio::AudioInfo::builder( - gst_audio::AUDIO_FORMAT_S16, + gst_audio::AUDIO_FORMAT_F32, audio_frame.sample_rate() as u32, audio_frame.no_channels() as u32, ); - builder.build().map_err(|_| { + let info = builder.build().map_err(|_| { gst::element_error!( element, gst::StreamError::Format, @@ -1143,7 +1151,9 @@ impl Receiver { ); gst::FlowError::NotNegotiated - }) + })?; + + Ok(info) } fn create_audio_buffer( @@ -1153,9 +1163,10 @@ impl Receiver { duration: Option, info: &gst_audio::AudioInfo, audio_frame: &AudioFrame, - ) -> gst::Buffer { - // We multiply by 2 because is the size in bytes of an i16 variable + ) -> Result { + let src = audio_frame.data().ok_or(gst::FlowError::NotNegotiated)?; let buff_size = (audio_frame.no_samples() as u32 * info.bpf()) as usize; + let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); { let buffer = buffer.get_mut().unwrap(); @@ -1181,15 +1192,32 @@ impl Receiver { } } - audio_frame.copy_to_interleaved_16s( - buffer - .map_writable() - .unwrap() - .as_mut_slice_of::() - .unwrap(), + let mut dest = buffer.map_writable().unwrap(); + let dest = dest + .as_mut_slice_of::() + .map_err(|_| gst::FlowError::NotNegotiated)?; + assert!( + dest.len() + == audio_frame.no_samples() as usize * audio_frame.no_channels() as usize ); + + for (channel, samples) in src + .chunks_exact(audio_frame.channel_stride_or_data_size_in_bytes() as usize) + .enumerate() + { + let samples = samples + .as_slice_of::() + .map_err(|_| gst::FlowError::NotNegotiated)?; + + for (i, sample) in samples[..audio_frame.no_samples() as usize] + .into_iter() + .enumerate() + { + dest[i * (audio_frame.no_channels() as usize) + channel] = *sample; + } + } } - buffer + Ok(buffer) } } From ce45f5a6730c195301c22efc610c939dd507ca34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Thu, 30 Sep 2021 22:45:55 +0300 Subject: [PATCH 186/199] Update README.md --- README.md | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 81c4da23..1f5ece55 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,28 @@ GStreamer NDI Plugin for Linux ==================== -*Compiled and tested with NDI SDK 3.5, 3.8, 4.0 and 4.1* +*Compiled and tested with NDI SDK 4.0, 4.1 and 5.0* This is a plugin for the [GStreamer](https://gstreamer.freedesktop.org/) multimedia framework that allows GStreamer to receive a stream from a [NDI](https://www.newtek.com/ndi/) source. This plugin has been developed by [Teltek](http://teltek.es/) and was funded by the [University of the Arts London](https://www.arts.ac.uk/) and [The University of Manchester](https://www.manchester.ac.uk/). -Currently the plugin has two source elements, `ndivideosrc` to get video from the stream and `ndiaudiosrc` for audio. By just providing the name or the ip of the stream, all the information required from the stream is picked up automatically, such as resolution, framerate, audio channels, ... +Currently the plugin has a source element for receiving from NDI sources, a sink element to provide an NDI source and a device provider for discovering NDI sources on the network. Some examples of how to use these elements from the command line: -``` -#Information about the elements -gst-inspect-1.0 ndi -gst-inspect-1.0 ndisrc -gst-inspect-1.0 ndisink +```console +# Information about the elements +$ gst-inspect-1.0 ndi +$ gst-inspect-1.0 ndisrc +$ gst-inspect-1.0 ndisink + +# Discover all NDI sources on the network +$ gst-device-monitor-1.0 -f Source/Network:application/x-ndi # Audio/Video source pipeline -gst-launch-1.0 ndisrc ndi-name="GC-DEV2 (OBS)" ! ndisrcdemux name=demux demux.video ! queue ! videoconvert ! autovideosink demux.audio ! queue ! audioconvert ! autoaudiosink +$ gst-launch-1.0 ndisrc ndi-name="GC-DEV2 (OBS)" ! ndisrcdemux name=demux demux.video ! queue ! videoconvert ! autovideosink demux.audio ! queue ! audioconvert ! autoaudiosink # Audio/Video sink pipeline -gst-launch-1.0 videotestsrc is-live=true ! video/x-raw,format=UYVY ! ndisinkcombiner name=combiner ! ndisink ndi-name="My NDI source" audiotestsrc is-live=true ! combiner.audio +$ gst-launch-1.0 videotestsrc is-live=true ! video/x-raw,format=UYVY ! ndisinkcombiner name=combiner ! ndisink ndi-name="My NDI source" audiotestsrc is-live=true ! combiner.audio ``` Feel free to contribute to this project. Some ways you can contribute are: @@ -30,11 +33,9 @@ Compilation of the NDI element ------- To compile the NDI element it's necessary to install Rust, the NDI SDK and the following packages for gstreamer: -``` -apt-get install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \ - gstreamer1.0-plugins-base gstreamer1.0-plugins-good \ - gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly \ - gstreamer1.0-libav libgstrtspserver-1.0-dev +```console +$ apt-get install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \ + gstreamer1.0-plugins-base ``` To install the required NDI library there are two options: @@ -54,11 +55,12 @@ gst-inspect-1.0 ndi By defult GStreamer 1.16 is required, to use only GStreamer 1.12 instead of 1.16, pass `--disable-default-features` to cargo. Only a subset of video formats is supported with this GStreamer version. If all went ok, you should see info related to the NDI element. To make the plugin available without using `GST_PLUGIN_PATH` it's necessary to copy the plugin to the gstreamer plugins folder. -``` -cargo build --release -sudo install -o root -g root -m 644 target/release/libgstndi.so /usr/lib/x86_64-linux-gnu/gstreamer-1.0/ -sudo ldconfig -gst-inspect-1.0 ndi + +```console +$ cargo build --release +$ sudo install -o root -g root -m 644 target/release/libgstndi.so /usr/lib/x86_64-linux-gnu/gstreamer-1.0/ +$ sudo ldconfig +$ gst-inspect-1.0 ndi ``` More info about GStreamer plugins written in Rust: From b98efea5aa0453a2cc903d349c48f50a6a08a29b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Fri, 1 Oct 2021 10:17:57 +0300 Subject: [PATCH 187/199] Allow selecting the receive color format in the source --- src/lib.rs | 31 +++++++++++++++++++++++++++++++ src/ndisrc/imp.rs | 28 ++++++++++++++++++++++++++++ src/receiver.rs | 3 ++- 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index ab2ff161..d138bd30 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,6 +36,37 @@ pub enum TimestampMode { ReceiveTime = 4, } +#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, glib::GEnum)] +#[repr(u32)] +#[genum(type_name = "GstNdiRecvColorFormat")] +pub enum RecvColorFormat { + #[genum(name = "BGRX or BGRA", nick = "bgrx-bgra")] + BgrxBgra = 0, + #[genum(name = "UYVY or BGRA", nick = "uyvy-bgra")] + UyvyBgra = 1, + #[genum(name = "RGBX or RGBA", nick = "rgbx-rgba")] + RgbxRgba = 2, + #[genum(name = "UYVY or RGBA", nick = "uyvy-rgba")] + UyvyRgba = 3, + #[genum(name = "Fastest", nick = "fastest")] + Fastest = 4, + #[genum(name = "Best", nick = "best")] + Best = 5, +} + +impl From for NDIlib_recv_color_format_e { + fn from(v: RecvColorFormat) -> Self { + match v { + RecvColorFormat::BgrxBgra => NDIlib_recv_color_format_BGRX_BGRA, + RecvColorFormat::UyvyBgra => NDIlib_recv_color_format_UYVY_BGRA, + RecvColorFormat::RgbxRgba => NDIlib_recv_color_format_RGBX_RGBA, + RecvColorFormat::UyvyRgba => NDIlib_recv_color_format_UYVY_RGBA, + RecvColorFormat::Fastest => NDIlib_recv_color_format_fastest, + RecvColorFormat::Best => NDIlib_recv_color_format_best, + } + } +} + fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { if !ndi::initialize() { return Err(glib::bool_error!("Cannot initialize NDI")); diff --git a/src/ndisrc/imp.rs b/src/ndisrc/imp.rs index 6467596d..175ba23a 100644 --- a/src/ndisrc/imp.rs +++ b/src/ndisrc/imp.rs @@ -17,6 +17,7 @@ use crate::Buffer; use crate::Receiver; use crate::ReceiverControlHandle; use crate::ReceiverItem; +use crate::RecvColorFormat; use crate::TimestampMode; use crate::DEFAULT_RECEIVER_NDI_NAME; @@ -37,6 +38,7 @@ struct Settings { max_queue_length: u32, receiver_ndi_name: String, bandwidth: ndisys::NDIlib_recv_bandwidth_e, + color_format: RecvColorFormat, timestamp_mode: TimestampMode, } @@ -50,6 +52,7 @@ impl Default for Settings { timeout: 5000, max_queue_length: 10, bandwidth: ndisys::NDIlib_recv_bandwidth_highest, + color_format: RecvColorFormat::UyvyBgra, timestamp_mode: TimestampMode::ReceiveTimeTimecode, } } @@ -159,6 +162,14 @@ impl ObjectImpl for NdiSrc { 100, glib::ParamFlags::READWRITE, ), + glib::ParamSpec::new_enum( + "color-format", + "Color Format", + "Receive color format", + RecvColorFormat::static_type(), + RecvColorFormat::UyvyBgra as u32 as i32, + glib::ParamFlags::READWRITE, + ), glib::ParamSpec::new_enum( "timestamp-mode", "Timestamp Mode", @@ -275,6 +286,18 @@ impl ObjectImpl for NdiSrc { ); settings.bandwidth = bandwidth; } + "color-format" => { + let mut settings = self.settings.lock().unwrap(); + let color_format = value.get().unwrap(); + gst_debug!( + CAT, + obj: obj, + "Changing color format from {:?} to {:?}", + settings.color_format, + color_format, + ); + settings.color_format = color_format; + } "timestamp-mode" => { let mut settings = self.settings.lock().unwrap(); let timestamp_mode = value.get().unwrap(); @@ -324,6 +347,10 @@ impl ObjectImpl for NdiSrc { let settings = self.settings.lock().unwrap(); settings.bandwidth.to_value() } + "color-format" => { + let settings = self.settings.lock().unwrap(); + settings.color_format.to_value() + } "timestamp-mode" => { let settings = self.settings.lock().unwrap(); settings.timestamp_mode.to_value() @@ -432,6 +459,7 @@ impl BaseSrcImpl for NdiSrc { &settings.receiver_ndi_name, settings.connect_timeout, settings.bandwidth, + settings.color_format.into(), settings.timestamp_mode, settings.timeout, settings.max_queue_length as usize, diff --git a/src/receiver.rs b/src/receiver.rs index dd457492..68af6059 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -445,6 +445,7 @@ impl Receiver { receiver_ndi_name: &str, connect_timeout: u32, bandwidth: NDIlib_recv_bandwidth_e, + color_format: NDIlib_recv_color_format_e, timestamp_mode: TimestampMode, timeout: u32, max_queue_length: usize, @@ -465,7 +466,7 @@ impl Receiver { // broken with interlaced content currently let recv = RecvInstance::builder(ndi_name, url_address, receiver_ndi_name) .bandwidth(bandwidth) - .color_format(NDIlib_recv_color_format_UYVY_BGRA) + .color_format(color_format) .allow_video_fields(true) .build(); let recv = match recv { From db6b9531ca5a865a1bad0204db2d7072a4a6c9e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Thu, 30 Sep 2021 23:18:31 +0300 Subject: [PATCH 188/199] Add various header definitions from the NDI advanced SDK --- Cargo.toml | 1 + src/ndisys.rs | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 8b587426..f63a3eb9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ default = ["interlaced-fields", "reference-timestamps", "sink"] interlaced-fields = ["gst/v1_16", "gst-video/v1_16"] reference-timestamps = ["gst/v1_14"] sink = ["gst/v1_18", "gst-base/v1_18"] +advanced-sdk = [] [lib] name = "gstndi" diff --git a/src/ndisys.rs b/src/ndisys.rs index eb0cdd51..d5695570 100644 --- a/src/ndisys.rs +++ b/src/ndisys.rs @@ -118,6 +118,22 @@ pub const NDIlib_recv_color_format_RGBX_RGBA: NDIlib_recv_color_format_e = 2; pub const NDIlib_recv_color_format_UYVY_RGBA: NDIlib_recv_color_format_e = 3; pub const NDIlib_recv_color_format_fastest: NDIlib_recv_color_format_e = 100; pub const NDIlib_recv_color_format_best: NDIlib_recv_color_format_e = 101; +#[cfg(feature = "advanced-sdk")] +pub const NDIlib_recv_color_format_ex_compressed: NDIlib_recv_color_format_e = 300; +#[cfg(feature = "advanced-sdk")] +pub const NDIlib_recv_color_format_ex_compressed_v2: NDIlib_recv_color_format_e = 301; +#[cfg(feature = "advanced-sdk")] +pub const NDIlib_recv_color_format_ex_compressed_v3: NDIlib_recv_color_format_e = 302; +#[cfg(feature = "advanced-sdk")] +pub const NDIlib_recv_color_format_ex_compressed_v3_with_audio: NDIlib_recv_color_format_e = 304; +#[cfg(feature = "advanced-sdk")] +pub const NDIlib_recv_color_format_ex_compressed_v4: NDIlib_recv_color_format_e = 303; +#[cfg(feature = "advanced-sdk")] +pub const NDIlib_recv_color_format_ex_compressed_v4_with_audio: NDIlib_recv_color_format_e = 305; +#[cfg(feature = "advanced-sdk")] +pub const NDIlib_recv_color_format_ex_compressed_v5: NDIlib_recv_color_format_e = 307; +#[cfg(feature = "advanced-sdk")] +pub const NDIlib_recv_color_format_ex_compressed_v5_with_audio: NDIlib_recv_color_format_e = 308; const fn make_fourcc(fourcc: &[u8; 4]) -> u32 { ((fourcc[0] as u32) << 0) @@ -139,8 +155,66 @@ pub const NDIlib_FourCC_video_type_BGRX: NDIlib_FourCC_video_type_e = make_fourc pub const NDIlib_FourCC_video_type_RGBA: NDIlib_FourCC_video_type_e = make_fourcc(b"RGBA"); pub const NDIlib_FourCC_video_type_RGBX: NDIlib_FourCC_video_type_e = make_fourcc(b"RGBX"); +#[cfg(feature = "advanced-sdk")] +pub const NDIlib_FourCC_video_type_ex_SHQ0_highest_bandwidth: NDIlib_FourCC_video_type_e = + make_fourcc(b"SHQ0"); +#[cfg(feature = "advanced-sdk")] +pub const NDIlib_FourCC_video_type_ex_SHQ2_highest_bandwidth: NDIlib_FourCC_video_type_e = + make_fourcc(b"SHQ2"); +#[cfg(feature = "advanced-sdk")] +pub const NDIlib_FourCC_video_type_ex_SHQ7_highest_bandwidth: NDIlib_FourCC_video_type_e = + make_fourcc(b"SHQ7"); +#[cfg(feature = "advanced-sdk")] +pub const NDIlib_FourCC_video_type_ex_SHQ0_lowest_bandwidth: NDIlib_FourCC_video_type_e = + make_fourcc(b"shq0"); +#[cfg(feature = "advanced-sdk")] +pub const NDIlib_FourCC_video_type_ex_SHQ2_lowest_bandwidth: NDIlib_FourCC_video_type_e = + make_fourcc(b"shq2"); +#[cfg(feature = "advanced-sdk")] +pub const NDIlib_FourCC_video_type_ex_SHQ7_lowest_bandwidth: NDIlib_FourCC_video_type_e = + make_fourcc(b"shq7"); +#[cfg(feature = "advanced-sdk")] +pub const NDIlib_FourCC_video_type_ex_H264_highest_bandwidth: NDIlib_FourCC_video_type_e = + make_fourcc(b"H264"); +#[cfg(feature = "advanced-sdk")] +pub const NDIlib_FourCC_video_type_ex_H264_lowest_bandwidth: NDIlib_FourCC_video_type_e = + make_fourcc(b"h264"); +#[cfg(feature = "advanced-sdk")] +pub const NDIlib_FourCC_video_type_ex_HEVC_highest_bandwidth: NDIlib_FourCC_video_type_e = + make_fourcc(b"HEVC"); +#[cfg(feature = "advanced-sdk")] +pub const NDIlib_FourCC_video_type_ex_HEVC_lowest_bandwidth: NDIlib_FourCC_video_type_e = + make_fourcc(b"hevc"); +#[cfg(feature = "advanced-sdk")] +pub const NDIlib_FourCC_video_type_ex_H264_alpha_highest_bandwidth: NDIlib_FourCC_video_type_e = + make_fourcc(b"A264"); +#[cfg(feature = "advanced-sdk")] +pub const NDIlib_FourCC_video_type_ex_H264_alpha_lowest_bandwidth: NDIlib_FourCC_video_type_e = + make_fourcc(b"a264"); +#[cfg(feature = "advanced-sdk")] +pub const NDIlib_FourCC_video_type_ex_HEVC_alpha_highest_bandwidth: NDIlib_FourCC_video_type_e = + make_fourcc(b"AEVC"); +#[cfg(feature = "advanced-sdk")] +pub const NDIlib_FourCC_video_type_ex_HEVC_alpha_lowest_bandwidth: NDIlib_FourCC_video_type_e = + make_fourcc(b"aevc"); + pub type NDIlib_FourCC_audio_type_e = u32; pub const NDIlib_FourCC_audio_type_FLTp: NDIlib_FourCC_video_type_e = make_fourcc(b"FLTp"); +#[cfg(feature = "advanced-sdk")] +pub const NDIlib_FourCC_audio_type_AAC: NDIlib_FourCC_audio_type_e = 0x000000ff; +#[cfg(feature = "advanced-sdk")] +pub const NDIlib_FourCC_audio_type_Opus: NDIlib_FourCC_audio_type_e = make_fourcc(b"Opus"); + +#[cfg(feature = "advanced-sdk")] +pub type NDIlib_compressed_FourCC_type_e = u32; +#[cfg(feature = "advanced-sdk")] +pub const NDIlib_compressed_FourCC_type_H264: NDIlib_compressed_FourCC_type_e = + make_fourcc(b"H264"); +#[cfg(feature = "advanced-sdk")] +pub const NDIlib_compressed_FourCC_type_HEVC: NDIlib_compressed_FourCC_type_e = + make_fourcc(b"HEVC"); +#[cfg(feature = "advanced-sdk")] +pub const NDIlib_compressed_FourCC_type_AAC: NDIlib_compressed_FourCC_type_e = 0x000000ff; #[repr(u32)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] @@ -230,3 +304,23 @@ pub struct NDIlib_audio_frame_v3_t { pub p_metadata: *const ::std::os::raw::c_char, pub timestamp: i64, } + +#[cfg(feature = "advanced-sdk")] +#[repr(packed)] +#[derive(Debug, Copy, Clone)] +pub struct NDIlib_compressed_packet_t { + pub version: u32, + pub fourcc: NDIlib_compressed_FourCC_type_e, + pub pts: i64, + pub dts: i64, + pub reserved: u64, + pub flags: u32, + pub data_size: u32, + pub extra_data_size: u32, +} + +#[cfg(feature = "advanced-sdk")] +pub const NDIlib_compressed_packet_flags_keyframe: u32 = 1; + +#[cfg(feature = "advanced-sdk")] +pub const NDIlib_compressed_packet_version_0: u32 = 44; From f890abe5cb63f6a9f73bc9db90e42c13e15dbcc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Fri, 1 Oct 2021 12:47:43 +0300 Subject: [PATCH 189/199] Add support for receiving compressed data from the source This requires building against and using the NDI Advanced SDK and is opt-in via the "advanced-sdk" cargo feature. --- Cargo.toml | 1 + src/lib.rs | 46 +++ src/ndi.rs | 235 +++++++++-- src/ndisrc/imp.rs | 4 +- src/receiver.rs | 967 +++++++++++++++++++++++++++++++++------------- 5 files changed, 947 insertions(+), 306 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f63a3eb9..441f80cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ gst-audio = { package = "gstreamer-audio", version = "0.17" } gst-video = { package = "gstreamer-video", version = "0.17", features = ["v1_12"] } byte-slice-cast = "1" once_cell = "1.0" +byteorder = "1.0" [build-dependencies] gst-plugin-version-helper = "0.7" diff --git a/src/lib.rs b/src/lib.rs index d138bd30..2c4302d7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -52,6 +52,30 @@ pub enum RecvColorFormat { Fastest = 4, #[genum(name = "Best", nick = "best")] Best = 5, + #[cfg(feature = "advanced-sdk")] + #[genum(name = "Compressed v1", nick = "compressed-v1")] + CompressedV1 = 6, + #[cfg(feature = "advanced-sdk")] + #[genum(name = "Compressed v2", nick = "compressed-v2")] + CompressedV2 = 7, + #[cfg(feature = "advanced-sdk")] + #[genum(name = "Compressed v3", nick = "compressed-v3")] + CompressedV3 = 8, + #[cfg(feature = "advanced-sdk")] + #[genum(name = "Compressed v3 with audio", nick = "compressed-v3-with-audio")] + CompressedV3WithAudio = 9, + #[cfg(feature = "advanced-sdk")] + #[genum(name = "Compressed v4", nick = "compressed-v4")] + CompressedV4 = 10, + #[cfg(feature = "advanced-sdk")] + #[genum(name = "Compressed v4 with audio", nick = "compressed-v4-with-audio")] + CompressedV4WithAudio = 11, + #[cfg(feature = "advanced-sdk")] + #[genum(name = "Compressed v5", nick = "compressed-v5")] + CompressedV5 = 12, + #[cfg(feature = "advanced-sdk")] + #[genum(name = "Compressed v5 with audio", nick = "compressed-v5-with-audio")] + CompressedV5WithAudio = 13, } impl From for NDIlib_recv_color_format_e { @@ -63,6 +87,28 @@ impl From for NDIlib_recv_color_format_e { RecvColorFormat::UyvyRgba => NDIlib_recv_color_format_UYVY_RGBA, RecvColorFormat::Fastest => NDIlib_recv_color_format_fastest, RecvColorFormat::Best => NDIlib_recv_color_format_best, + #[cfg(feature = "advanced-sdk")] + RecvColorFormat::CompressedV1 => NDIlib_recv_color_format_ex_compressed, + #[cfg(feature = "advanced-sdk")] + RecvColorFormat::CompressedV2 => NDIlib_recv_color_format_ex_compressed_v2, + #[cfg(feature = "advanced-sdk")] + RecvColorFormat::CompressedV3 => NDIlib_recv_color_format_ex_compressed_v3, + #[cfg(feature = "advanced-sdk")] + RecvColorFormat::CompressedV3WithAudio => { + NDIlib_recv_color_format_ex_compressed_v3_with_audio + } + #[cfg(feature = "advanced-sdk")] + RecvColorFormat::CompressedV4 => NDIlib_recv_color_format_ex_compressed_v4, + #[cfg(feature = "advanced-sdk")] + RecvColorFormat::CompressedV4WithAudio => { + NDIlib_recv_color_format_ex_compressed_v4_with_audio + } + #[cfg(feature = "advanced-sdk")] + RecvColorFormat::CompressedV5 => NDIlib_recv_color_format_ex_compressed_v5, + #[cfg(feature = "advanced-sdk")] + RecvColorFormat::CompressedV5WithAudio => { + NDIlib_recv_color_format_ex_compressed_v5_with_audio + } } } } diff --git a/src/ndi.rs b/src/ndi.rs index e184c758..c54176bc 100644 --- a/src/ndi.rs +++ b/src/ndi.rs @@ -503,7 +503,8 @@ impl<'a> VideoFrame<'a> { pub fn data(&self) -> Option<&[u8]> { let fourcc = self.fourcc(); - if ![ + + if [ NDIlib_FourCC_video_type_UYVY, NDIlib_FourCC_video_type_UYVA, NDIlib_FourCC_video_type_P216, @@ -518,31 +519,126 @@ impl<'a> VideoFrame<'a> { ] .contains(&fourcc) { - return None; - } + // 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() + }; - // 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() - }; - - unsafe { - use std::slice; - match self { - VideoFrame::BorrowedRecv(ref frame, _) | VideoFrame::BorrowedGst(ref frame, _) => { - Some(slice::from_raw_parts( + 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 + }, + }) } } @@ -786,18 +882,93 @@ impl<'a> AudioFrame<'a> { let fourcc = self.fourcc(); - if ![NDIlib_FourCC_audio_type_FLTp].contains(&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; } - match self { + let data = match self { AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => { - Some(slice::from_raw_parts( + slice::from_raw_parts( frame.p_data as *const u8, - (frame.no_channels * frame.channel_stride_or_data_size_in_bytes) as usize, - )) + 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 + }, + }) } } @@ -860,7 +1031,7 @@ impl<'a> AudioFrame<'a> { 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.into_iter().enumerate() { + for (c, sample) in samples.iter().enumerate() { ptr::write(dest_ptr.add(c * no_samples as usize + i), *sample); } } @@ -895,6 +1066,16 @@ impl<'a> Drop for AudioFrame<'a> { } } +#[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), diff --git a/src/ndisrc/imp.rs b/src/ndisrc/imp.rs index 175ba23a..3d1200f2 100644 --- a/src/ndisrc/imp.rs +++ b/src/ndisrc/imp.rs @@ -59,9 +59,9 @@ impl Default for Settings { } struct State { - video_info: Option, + video_info: Option, video_caps: Option, - audio_info: Option, + audio_info: Option, audio_caps: Option, current_latency: Option, receiver: Option, diff --git a/src/receiver.rs b/src/receiver.rs index 68af6059..d44eea25 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -23,10 +23,155 @@ static CAT: Lazy = Lazy::new(|| { #[derive(Clone)] pub struct Receiver(Arc); +#[derive(Debug, PartialEq, Eq)] +pub enum AudioInfo { + AudioInfo(gst_audio::AudioInfo), + #[cfg(feature = "advanced-sdk")] + OpusInfo { + sample_rate: i32, + no_channels: i32, + }, + #[cfg(feature = "advanced-sdk")] + AacInfo { + sample_rate: i32, + no_channels: i32, + codec_data: [u8; 2], + }, +} + +impl AudioInfo { + pub fn to_caps(&self) -> Result { + match self { + AudioInfo::AudioInfo(ref info) => info.to_caps(), + #[cfg(feature = "advanced-sdk")] + AudioInfo::OpusInfo { + sample_rate, + no_channels, + } => Ok(gst::Caps::builder("audio/x-opus") + .field("channels", *no_channels) + .field("rate", *sample_rate) + .field("channel-mapping-family", 0i32) + .build()), + #[cfg(feature = "advanced-sdk")] + AudioInfo::AacInfo { + sample_rate, + no_channels, + codec_data, + } => Ok(gst::Caps::builder("audio/mpeg") + .field("channels", *no_channels) + .field("rate", *sample_rate) + .field("mpegversion", 4i32) + .field("stream-format", "raw") + .field("codec_data", gst::Buffer::from_mut_slice(*codec_data)) + .build()), + } + } +} + +#[derive(Debug, PartialEq, Eq)] +pub enum VideoInfo { + VideoInfo(gst_video::VideoInfo), + #[cfg(feature = "advanced-sdk")] + SpeedHQInfo { + variant: String, + xres: i32, + yres: i32, + fps_n: i32, + fps_d: i32, + par_n: i32, + par_d: i32, + interlace_mode: gst_video::VideoInterlaceMode, + }, + #[cfg(feature = "advanced-sdk")] + H264Info { + xres: i32, + yres: i32, + fps_n: i32, + fps_d: i32, + par_n: i32, + par_d: i32, + interlace_mode: gst_video::VideoInterlaceMode, + }, + #[cfg(feature = "advanced-sdk")] + H265Info { + xres: i32, + yres: i32, + fps_n: i32, + fps_d: i32, + par_n: i32, + par_d: i32, + interlace_mode: gst_video::VideoInterlaceMode, + }, +} + +impl VideoInfo { + pub fn to_caps(&self) -> Result { + match self { + VideoInfo::VideoInfo(ref info) => info.to_caps(), + #[cfg(feature = "advanced-sdk")] + VideoInfo::SpeedHQInfo { + ref variant, + xres, + yres, + fps_n, + fps_d, + par_n, + par_d, + interlace_mode, + } => Ok(gst::Caps::builder("video/x-speedhq") + .field("width", *xres) + .field("height", *yres) + .field("framerate", gst::Fraction::new(*fps_n, *fps_d)) + .field("pixel-aspect-ratio", gst::Fraction::new(*par_n, *par_d)) + .field("interlace-mode", interlace_mode.to_str()) + .field("variant", variant) + .build()), + #[cfg(feature = "advanced-sdk")] + VideoInfo::H264Info { + xres, + yres, + fps_n, + fps_d, + par_n, + par_d, + interlace_mode, + .. + } => Ok(gst::Caps::builder("video/x-h264") + .field("width", *xres) + .field("height", *yres) + .field("framerate", gst::Fraction::new(*fps_n, *fps_d)) + .field("pixel-aspect-ratio", gst::Fraction::new(*par_n, *par_d)) + .field("interlace-mode", interlace_mode.to_str()) + .field("stream-format", "byte-stream") + .field("alignment", "au") + .build()), + #[cfg(feature = "advanced-sdk")] + VideoInfo::H265Info { + xres, + yres, + fps_n, + fps_d, + par_n, + par_d, + interlace_mode, + .. + } => Ok(gst::Caps::builder("video/x-h265") + .field("width", *xres) + .field("height", *yres) + .field("framerate", gst::Fraction::new(*fps_n, *fps_d)) + .field("pixel-aspect-ratio", gst::Fraction::new(*par_n, *par_d)) + .field("interlace-mode", interlace_mode.to_str()) + .field("stream-format", "byte-stream") + .field("alignment", "au") + .build()), + } + } +} + #[derive(Debug)] pub enum Buffer { - Audio(gst::Buffer, gst_audio::AudioInfo), - Video(gst::Buffer, gst_video::VideoInfo), + Audio(gst::Buffer, AudioInfo), + Video(gst::Buffer, VideoInfo), } #[derive(Debug)] @@ -177,7 +322,7 @@ impl Observations { if remote_diff > 0 && local_diff > 0 { let slope = (local_diff as f64) / (remote_diff as f64); - if slope < 0.8 || slope > 1.2 { + if !(0.8..1.2).contains(&slope) { gst_warning!( CAT, obj: element, @@ -767,78 +912,23 @@ impl Receiver { &self, element: &gst_base::BaseSrc, video_frame: &VideoFrame, - ) -> Result { - // YV12 and I420 are swapped in the NDI SDK compared to GStreamer - let format = match video_frame.fourcc() { - ndisys::NDIlib_FourCC_video_type_UYVY => gst_video::VideoFormat::Uyvy, - // FIXME: This drops the alpha plane! - ndisys::NDIlib_FourCC_video_type_UYVA => gst_video::VideoFormat::Uyvy, - ndisys::NDIlib_FourCC_video_type_YV12 => gst_video::VideoFormat::I420, - ndisys::NDIlib_FourCC_video_type_NV12 => gst_video::VideoFormat::Nv12, - ndisys::NDIlib_FourCC_video_type_I420 => gst_video::VideoFormat::Yv12, - ndisys::NDIlib_FourCC_video_type_BGRA => gst_video::VideoFormat::Bgra, - ndisys::NDIlib_FourCC_video_type_BGRX => gst_video::VideoFormat::Bgrx, - ndisys::NDIlib_FourCC_video_type_RGBA => gst_video::VideoFormat::Rgba, - ndisys::NDIlib_FourCC_video_type_RGBX => gst_video::VideoFormat::Rgbx, - _ => { - gst::element_error!( - element, - gst::StreamError::Format, - ["Unsupported video fourcc {:08x}", video_frame.fourcc()] - ); - - return Err(gst::FlowError::NotNegotiated); - } // TODO: NDIlib_FourCC_video_type_P216 and NDIlib_FourCC_video_type_PA16 not - // supported by GStreamer - }; + ) -> Result { + let fourcc = video_frame.fourcc(); let par = gst::Fraction::approximate_f32(video_frame.picture_aspect_ratio()) .unwrap_or_else(|| gst::Fraction::new(1, 1)) * gst::Fraction::new(video_frame.yres(), video_frame.xres()); - - #[cfg(feature = "interlaced-fields")] - { - let mut builder = gst_video::VideoInfo::builder( - format, - video_frame.xres() as u32, - video_frame.yres() as u32, - ) - .fps(gst::Fraction::from(video_frame.frame_rate())) - .par(par) - .interlace_mode(match video_frame.frame_format_type() { - ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_progressive => { - gst_video::VideoInterlaceMode::Progressive - } - ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_interleaved => { - gst_video::VideoInterlaceMode::Interleaved - } - _ => gst_video::VideoInterlaceMode::Alternate, - }); - - if video_frame.frame_format_type() - == ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_interleaved - { - builder = builder.field_order(gst_video::VideoFieldOrder::TopFieldFirst); + let interlace_mode = match video_frame.frame_format_type() { + ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_progressive => { + gst_video::VideoInterlaceMode::Progressive } - - builder.build().map_err(|_| { - gst::element_error!( - element, - gst::StreamError::Format, - ["Invalid video format configuration"] - ); - - gst::FlowError::NotNegotiated - }) - } - - #[cfg(not(feature = "interlaced-fields"))] - { - if video_frame.frame_format_type() - != ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_progressive - && video_frame.frame_format_type() - != ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_interleaved - { + ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_interleaved => { + gst_video::VideoInterlaceMode::Interleaved + } + #[cfg(feature = "interlaced-fields")] + _ => gst_video::VideoInterlaceMode::Alternate, + #[cfg(not(feature = "interlaced-fields"))] + _ => { gst::element_error!( element, gst::StreamError::Format, @@ -846,40 +936,228 @@ impl Receiver { ); return Err(gst::FlowError::NotNegotiated); } + }; - let mut builder = gst_video::VideoInfo::builder( - format, - video_frame.xres() as u32, - video_frame.yres() as u32, - ) - .fps(gst::Fraction::from(video_frame.frame_rate())) - .par(par) - .interlace_mode( - if video_frame.frame_format_type() - == ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_progressive - { - gst_video::VideoInterlaceMode::Progressive - } else { - gst_video::VideoInterlaceMode::Interleaved - }, - ); + if [ + 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_BGRX, + ] + .contains(&fourcc) + { + // YV12 and I420 are swapped in the NDI SDK compared to GStreamer + let format = match video_frame.fourcc() { + ndisys::NDIlib_FourCC_video_type_UYVY => gst_video::VideoFormat::Uyvy, + // FIXME: This drops the alpha plane! + ndisys::NDIlib_FourCC_video_type_UYVA => gst_video::VideoFormat::Uyvy, + ndisys::NDIlib_FourCC_video_type_YV12 => gst_video::VideoFormat::I420, + ndisys::NDIlib_FourCC_video_type_NV12 => gst_video::VideoFormat::Nv12, + ndisys::NDIlib_FourCC_video_type_I420 => gst_video::VideoFormat::Yv12, + ndisys::NDIlib_FourCC_video_type_BGRA => gst_video::VideoFormat::Bgra, + ndisys::NDIlib_FourCC_video_type_BGRX => gst_video::VideoFormat::Bgrx, + ndisys::NDIlib_FourCC_video_type_RGBA => gst_video::VideoFormat::Rgba, + ndisys::NDIlib_FourCC_video_type_RGBX => gst_video::VideoFormat::Rgbx, + _ => { + gst::element_error!( + element, + gst::StreamError::Format, + ["Unsupported video fourcc {:08x}", video_frame.fourcc()] + ); - if video_frame.frame_format_type() - == ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_interleaved + return Err(gst::FlowError::NotNegotiated); + } // TODO: NDIlib_FourCC_video_type_P216 and NDIlib_FourCC_video_type_PA16 not + // supported by GStreamer + }; + + #[cfg(feature = "interlaced-fields")] { - builder = builder.field_order(gst_video::VideoFieldOrder::TopFieldFirst); + let mut builder = gst_video::VideoInfo::builder( + format, + video_frame.xres() as u32, + video_frame.yres() as u32, + ) + .fps(gst::Fraction::from(video_frame.frame_rate())) + .par(par) + .interlace_mode(interlace_mode); + + if video_frame.frame_format_type() + == ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_interleaved + { + builder = builder.field_order(gst_video::VideoFieldOrder::TopFieldFirst); + } + + return Ok(VideoInfo::VideoInfo(builder.build().map_err(|_| { + gst::element_error!( + element, + gst::StreamError::Format, + ["Invalid video format configuration"] + ); + + gst::FlowError::NotNegotiated + })?)); } - builder.build().map_err(|_| { - gst::element_error!( - element, - gst::StreamError::Format, - ["Invalid video format configuration"] - ); + #[cfg(not(feature = "interlaced-fields"))] + { + let mut builder = gst_video::VideoInfo::builder( + format, + video_frame.xres() as u32, + video_frame.yres() as u32, + ) + .fps(gst::Fraction::from(video_frame.frame_rate())) + .par(par) + .interlace_mode(interlace_mode); - gst::FlowError::NotNegotiated - }) + if video_frame.frame_format_type() + == ndisys::NDIlib_frame_format_type_e::NDIlib_frame_format_type_interleaved + { + builder = builder.field_order(gst_video::VideoFieldOrder::TopFieldFirst); + } + + return Ok(VideoInfo::VideoInfo(builder.build().map_err(|_| { + gst::element_error!( + element, + gst::StreamError::Format, + ["Invalid video format configuration"] + ); + + gst::FlowError::NotNegotiated + })?)); + } } + + #[cfg(feature = "advanced-sdk")] + if [ + ndisys::NDIlib_FourCC_video_type_ex_SHQ0_highest_bandwidth, + ndisys::NDIlib_FourCC_video_type_ex_SHQ2_highest_bandwidth, + ndisys::NDIlib_FourCC_video_type_ex_SHQ7_highest_bandwidth, + ndisys::NDIlib_FourCC_video_type_ex_SHQ0_lowest_bandwidth, + ndisys::NDIlib_FourCC_video_type_ex_SHQ2_lowest_bandwidth, + ndisys::NDIlib_FourCC_video_type_ex_SHQ7_lowest_bandwidth, + ] + .contains(&fourcc) + { + let variant = match fourcc { + ndisys::NDIlib_FourCC_video_type_ex_SHQ0_highest_bandwidth + | ndisys::NDIlib_FourCC_video_type_ex_SHQ0_lowest_bandwidth => String::from("SHQ0"), + ndisys::NDIlib_FourCC_video_type_ex_SHQ2_highest_bandwidth + | ndisys::NDIlib_FourCC_video_type_ex_SHQ2_lowest_bandwidth => String::from("SHQ2"), + ndisys::NDIlib_FourCC_video_type_ex_SHQ7_highest_bandwidth + | ndisys::NDIlib_FourCC_video_type_ex_SHQ7_lowest_bandwidth => String::from("SHQ7"), + _ => { + gst::element_error!( + element, + gst::StreamError::Format, + [ + "Unsupported SpeedHQ video fourcc {:08x}", + video_frame.fourcc() + ] + ); + + return Err(gst::FlowError::NotNegotiated); + } + }; + + return Ok(VideoInfo::SpeedHQInfo { + variant, + xres: video_frame.xres(), + yres: video_frame.yres(), + fps_n: video_frame.frame_rate().0, + fps_d: video_frame.frame_rate().1, + par_n: *par.numer(), + par_d: *par.denom(), + interlace_mode, + }); + } + + #[cfg(feature = "advanced-sdk")] + if [ + ndisys::NDIlib_FourCC_video_type_ex_H264_highest_bandwidth, + ndisys::NDIlib_FourCC_video_type_ex_H264_lowest_bandwidth, + ndisys::NDIlib_FourCC_video_type_ex_H264_alpha_highest_bandwidth, + ndisys::NDIlib_FourCC_video_type_ex_H264_alpha_lowest_bandwidth, + ] + .contains(&fourcc) + { + let compressed_packet = video_frame.compressed_packet().ok_or_else(|| { + gst_error!( + CAT, + obj: element, + "Video packet doesn't have compressed packet start" + ); + gst::element_error!(element, gst::StreamError::Format, ["Invalid video packet"]); + + gst::FlowError::Error + })?; + + if compressed_packet.fourcc != NDIlib_compressed_FourCC_type_H264 { + gst_error!(CAT, obj: element, "Non-H264 video packet"); + gst::element_error!(element, gst::StreamError::Format, ["Invalid video packet"]); + + return Err(gst::FlowError::Error); + } + + return Ok(VideoInfo::H264Info { + xres: video_frame.xres(), + yres: video_frame.yres(), + fps_n: video_frame.frame_rate().0, + fps_d: video_frame.frame_rate().1, + par_n: *par.numer(), + par_d: *par.denom(), + interlace_mode, + }); + } + + #[cfg(feature = "advanced-sdk")] + if [ + ndisys::NDIlib_FourCC_video_type_ex_HEVC_highest_bandwidth, + ndisys::NDIlib_FourCC_video_type_ex_HEVC_lowest_bandwidth, + ndisys::NDIlib_FourCC_video_type_ex_HEVC_alpha_highest_bandwidth, + ndisys::NDIlib_FourCC_video_type_ex_HEVC_alpha_lowest_bandwidth, + ] + .contains(&fourcc) + { + let compressed_packet = video_frame.compressed_packet().ok_or_else(|| { + gst_error!( + CAT, + obj: element, + "Video packet doesn't have compressed packet start" + ); + gst::element_error!(element, gst::StreamError::Format, ["Invalid video packet"]); + + gst::FlowError::Error + })?; + + if compressed_packet.fourcc != NDIlib_compressed_FourCC_type_HEVC { + gst_error!(CAT, obj: element, "Non-H265 video packet"); + gst::element_error!(element, gst::StreamError::Format, ["Invalid video packet"]); + + return Err(gst::FlowError::Error); + } + + return Ok(VideoInfo::H265Info { + xres: video_frame.xres(), + yres: video_frame.yres(), + fps_n: video_frame.frame_rate().0, + fps_d: video_frame.frame_rate().1, + par_n: *par.numer(), + par_d: *par.denom(), + interlace_mode, + }); + } + + gst::element_error!( + element, + gst::StreamError::Format, + ["Unsupported video fourcc {:08x}", video_frame.fourcc()] + ); + Err(gst::FlowError::NotNegotiated) } fn create_video_buffer( @@ -887,7 +1165,7 @@ impl Receiver { element: &gst_base::BaseSrc, pts: gst::ClockTime, duration: Option, - info: &gst_video::VideoInfo, + info: &VideoInfo, video_frame: &VideoFrame, ) -> Result { let mut buffer = self.copy_video_frame(element, info, video_frame)?; @@ -956,125 +1234,181 @@ impl Receiver { fn copy_video_frame( &self, - _element: &gst_base::BaseSrc, - info: &gst_video::VideoInfo, + #[allow(unused_variables)] element: &gst_base::BaseSrc, + info: &VideoInfo, video_frame: &VideoFrame, ) -> Result { - let src = video_frame.data().ok_or(gst::FlowError::NotNegotiated)?; + match info { + VideoInfo::VideoInfo(ref info) => { + let src = video_frame.data().ok_or(gst::FlowError::Error)?; - let buffer = gst::Buffer::with_size(info.size()).unwrap(); - let mut vframe = gst_video::VideoFrame::from_buffer_writable(buffer, info).unwrap(); + let buffer = gst::Buffer::with_size(info.size()).unwrap(); + let mut vframe = gst_video::VideoFrame::from_buffer_writable(buffer, info).unwrap(); - match info.format() { - gst_video::VideoFormat::Uyvy - | gst_video::VideoFormat::Bgra - | gst_video::VideoFormat::Bgrx - | gst_video::VideoFormat::Rgba - | gst_video::VideoFormat::Rgbx => { - let line_bytes = if info.format() == gst_video::VideoFormat::Uyvy { - 2 * vframe.width() as usize - } else { - 4 * vframe.width() as usize - }; - let dest_stride = vframe.plane_stride()[0] as usize; - let dest = vframe.plane_data_mut(0).unwrap(); - let src_stride = video_frame.line_stride_or_data_size_in_bytes() as usize; + match info.format() { + gst_video::VideoFormat::Uyvy + | gst_video::VideoFormat::Bgra + | gst_video::VideoFormat::Bgrx + | gst_video::VideoFormat::Rgba + | gst_video::VideoFormat::Rgbx => { + let line_bytes = if info.format() == gst_video::VideoFormat::Uyvy { + 2 * vframe.width() as usize + } else { + 4 * vframe.width() as usize + }; + let dest_stride = vframe.plane_stride()[0] as usize; + let dest = vframe.plane_data_mut(0).unwrap(); + let src_stride = video_frame.line_stride_or_data_size_in_bytes() as usize; - for (dest, src) in dest - .chunks_exact_mut(dest_stride) - .zip(src.chunks_exact(src_stride)) - { - dest.copy_from_slice(src); - dest.copy_from_slice(&src[..line_bytes]); + for (dest, src) in dest + .chunks_exact_mut(dest_stride) + .zip(src.chunks_exact(src_stride)) + { + dest.copy_from_slice(src); + dest.copy_from_slice(&src[..line_bytes]); + } + } + gst_video::VideoFormat::Nv12 => { + // First plane + { + let line_bytes = vframe.width() as usize; + let dest_stride = vframe.plane_stride()[0] as usize; + let dest = vframe.plane_data_mut(0).unwrap(); + let src_stride = + video_frame.line_stride_or_data_size_in_bytes() as usize; + + for (dest, src) in dest + .chunks_exact_mut(dest_stride) + .zip(src.chunks_exact(src_stride)) + { + dest.copy_from_slice(&src[..line_bytes]); + } + } + + // Second plane + { + let line_bytes = vframe.width() as usize; + let dest_stride = vframe.plane_stride()[1] as usize; + let dest = vframe.plane_data_mut(1).unwrap(); + let src_stride = + video_frame.line_stride_or_data_size_in_bytes() as usize; + let src = &src[(video_frame.yres() as usize * src_stride)..]; + + for (dest, src) in dest + .chunks_exact_mut(dest_stride) + .zip(src.chunks_exact(src_stride)) + { + dest.copy_from_slice(&src[..line_bytes]); + } + } + } + gst_video::VideoFormat::Yv12 | gst_video::VideoFormat::I420 => { + // First plane + { + let line_bytes = vframe.width() as usize; + let dest_stride = vframe.plane_stride()[0] as usize; + let dest = vframe.plane_data_mut(0).unwrap(); + let src_stride = + video_frame.line_stride_or_data_size_in_bytes() as usize; + + for (dest, src) in dest + .chunks_exact_mut(dest_stride) + .zip(src.chunks_exact(src_stride)) + { + dest.copy_from_slice(&src[..line_bytes]); + } + } + + // Second plane + { + let line_bytes = (vframe.width() as usize + 1) / 2; + let dest_stride = vframe.plane_stride()[1] as usize; + let dest = vframe.plane_data_mut(1).unwrap(); + let src_stride = + video_frame.line_stride_or_data_size_in_bytes() as usize; + let src_stride1 = + video_frame.line_stride_or_data_size_in_bytes() as usize / 2; + let src = &src[(video_frame.yres() as usize * src_stride)..]; + + for (dest, src) in dest + .chunks_exact_mut(dest_stride) + .zip(src.chunks_exact(src_stride1)) + { + dest.copy_from_slice(&src[..line_bytes]); + } + } + + // Third plane + { + let line_bytes = (vframe.width() as usize + 1) / 2; + let dest_stride = vframe.plane_stride()[2] as usize; + let dest = vframe.plane_data_mut(2).unwrap(); + let src_stride = + video_frame.line_stride_or_data_size_in_bytes() as usize; + let src_stride1 = + video_frame.line_stride_or_data_size_in_bytes() as usize / 2; + let src = &src[(video_frame.yres() as usize * src_stride + + (video_frame.yres() as usize + 1) / 2 * src_stride1)..]; + + for (dest, src) in dest + .chunks_exact_mut(dest_stride) + .zip(src.chunks_exact(src_stride1)) + { + dest.copy_from_slice(&src[..line_bytes]); + } + } + } + _ => unreachable!(), } + + Ok(vframe.into_buffer()) } - gst_video::VideoFormat::Nv12 => { - // First plane - { - let line_bytes = vframe.width() as usize; - let dest_stride = vframe.plane_stride()[0] as usize; - let dest = vframe.plane_data_mut(0).unwrap(); - let src_stride = video_frame.line_stride_or_data_size_in_bytes() as usize; + #[cfg(feature = "advanced-sdk")] + VideoInfo::SpeedHQInfo { .. } => { + let data = video_frame.data().ok_or_else(|| { + gst_error!(CAT, obj: element, "Video packet has no data"); + gst::element_error!( + element, + gst::StreamError::Format, + ["Invalid video packet"] + ); - for (dest, src) in dest - .chunks_exact_mut(dest_stride) - .zip(src.chunks_exact(src_stride)) - { - dest.copy_from_slice(&src[..line_bytes]); - } - } + gst::FlowError::Error + })?; - // Second plane - { - let line_bytes = vframe.width() as usize; - let dest_stride = vframe.plane_stride()[1] as usize; - let dest = vframe.plane_data_mut(1).unwrap(); - let src_stride = video_frame.line_stride_or_data_size_in_bytes() as usize; - let src = &src[(video_frame.yres() as usize * src_stride)..]; - - for (dest, src) in dest - .chunks_exact_mut(dest_stride) - .zip(src.chunks_exact(src_stride)) - { - dest.copy_from_slice(&src[..line_bytes]); - } - } + Ok(gst::Buffer::from_mut_slice(Vec::from(data))) } - gst_video::VideoFormat::Yv12 | gst_video::VideoFormat::I420 => { - // First plane - { - let line_bytes = vframe.width() as usize; - let dest_stride = vframe.plane_stride()[0] as usize; - let dest = vframe.plane_data_mut(0).unwrap(); - let src_stride = video_frame.line_stride_or_data_size_in_bytes() as usize; + #[cfg(feature = "advanced-sdk")] + VideoInfo::H264Info { .. } | VideoInfo::H265Info { .. } => { + let compressed_packet = video_frame.compressed_packet().ok_or_else(|| { + gst_error!( + CAT, + obj: element, + "Video packet doesn't have compressed packet start" + ); + gst::element_error!( + element, + gst::StreamError::Format, + ["Invalid video packet"] + ); - for (dest, src) in dest - .chunks_exact_mut(dest_stride) - .zip(src.chunks_exact(src_stride)) - { - dest.copy_from_slice(&src[..line_bytes]); - } + gst::FlowError::Error + })?; + + let mut buffer = Vec::new(); + if let Some(extra_data) = compressed_packet.extra_data { + buffer.extend_from_slice(extra_data); + } + buffer.extend_from_slice(compressed_packet.data); + let mut buffer = gst::Buffer::from_mut_slice(buffer); + if !compressed_packet.key_frame { + let buffer = buffer.get_mut().unwrap(); + buffer.set_flags(gst::BufferFlags::DELTA_UNIT); } - // Second plane - { - let line_bytes = (vframe.width() as usize + 1) / 2; - let dest_stride = vframe.plane_stride()[1] as usize; - let dest = vframe.plane_data_mut(1).unwrap(); - let src_stride = video_frame.line_stride_or_data_size_in_bytes() as usize; - let src_stride1 = video_frame.line_stride_or_data_size_in_bytes() as usize / 2; - let src = &src[(video_frame.yres() as usize * src_stride)..]; - - for (dest, src) in dest - .chunks_exact_mut(dest_stride) - .zip(src.chunks_exact(src_stride1)) - { - dest.copy_from_slice(&src[..line_bytes]); - } - } - - // Third plane - { - let line_bytes = (vframe.width() as usize + 1) / 2; - let dest_stride = vframe.plane_stride()[2] as usize; - let dest = vframe.plane_data_mut(2).unwrap(); - let src_stride = video_frame.line_stride_or_data_size_in_bytes() as usize; - let src_stride1 = video_frame.line_stride_or_data_size_in_bytes() as usize / 2; - let src = &src[(video_frame.yres() as usize * src_stride - + (video_frame.yres() as usize + 1) / 2 * src_stride1)..]; - - for (dest, src) in dest - .chunks_exact_mut(dest_stride) - .zip(src.chunks_exact(src_stride1)) - { - dest.copy_from_slice(&src[..line_bytes]); - } - } + Ok(buffer) } - _ => unreachable!(), } - - Ok(vframe.into_buffer()) } fn create_audio_buffer_and_info( @@ -1128,97 +1462,176 @@ impl Receiver { &self, element: &gst_base::BaseSrc, audio_frame: &AudioFrame, - ) -> Result { - if audio_frame.fourcc() != NDIlib_FourCC_audio_type_FLTp { - gst::element_error!( - element, - gst::StreamError::Format, - ["Unsupported audio fourcc {:08x}", audio_frame.fourcc()] + ) -> Result { + let fourcc = audio_frame.fourcc(); + + if [NDIlib_FourCC_audio_type_FLTp].contains(&fourcc) { + let builder = gst_audio::AudioInfo::builder( + gst_audio::AUDIO_FORMAT_F32, + audio_frame.sample_rate() as u32, + audio_frame.no_channels() as u32, ); - return Err(gst::FlowError::NotNegotiated); + + let info = builder.build().map_err(|_| { + gst::element_error!( + element, + gst::StreamError::Format, + ["Invalid audio format configuration"] + ); + + gst::FlowError::NotNegotiated + })?; + + return Ok(AudioInfo::AudioInfo(info)); } - let builder = gst_audio::AudioInfo::builder( - gst_audio::AUDIO_FORMAT_F32, - audio_frame.sample_rate() as u32, - audio_frame.no_channels() as u32, + #[cfg(feature = "advanced-sdk")] + if [NDIlib_FourCC_audio_type_AAC].contains(&fourcc) { + use std::convert::TryInto; + + let compressed_packet = audio_frame.compressed_packet().ok_or_else(|| { + gst_error!( + CAT, + obj: element, + "Audio packet doesn't have compressed packet start" + ); + gst::element_error!(element, gst::StreamError::Format, ["Invalid audio packet"]); + + gst::FlowError::Error + })?; + + if compressed_packet.fourcc != NDIlib_compressed_FourCC_type_AAC { + gst_error!(CAT, obj: element, "Non-AAC audio packet"); + gst::element_error!(element, gst::StreamError::Format, ["Invalid audio packet"]); + + return Err(gst::FlowError::Error); + } + + return Ok(AudioInfo::AacInfo { + sample_rate: audio_frame.sample_rate(), + no_channels: audio_frame.no_channels(), + codec_data: compressed_packet + .extra_data + .ok_or(gst::FlowError::NotNegotiated)? + .try_into() + .map_err(|_| gst::FlowError::NotNegotiated)?, + }); + } + + #[cfg(feature = "advanced-sdk")] + if [NDIlib_FourCC_audio_type_Opus].contains(&fourcc) {} + + gst::element_error!( + element, + gst::StreamError::Format, + ["Unsupported audio fourcc {:08x}", audio_frame.fourcc()] ); - - let info = builder.build().map_err(|_| { - gst::element_error!( - element, - gst::StreamError::Format, - ["Invalid audio format configuration"] - ); - - gst::FlowError::NotNegotiated - })?; - - Ok(info) + Err(gst::FlowError::NotNegotiated) } fn create_audio_buffer( &self, - _element: &gst_base::BaseSrc, + #[allow(unused_variables)] element: &gst_base::BaseSrc, pts: gst::ClockTime, duration: Option, - info: &gst_audio::AudioInfo, + info: &AudioInfo, audio_frame: &AudioFrame, ) -> Result { - let src = audio_frame.data().ok_or(gst::FlowError::NotNegotiated)?; - let buff_size = (audio_frame.no_samples() as u32 * info.bpf()) as usize; + match info { + AudioInfo::AudioInfo(ref info) => { + let src = audio_frame.data().ok_or(gst::FlowError::Error)?; + let buff_size = (audio_frame.no_samples() as u32 * info.bpf()) as usize; - let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); - { - let buffer = buffer.get_mut().unwrap(); - - buffer.set_pts(pts); - buffer.set_duration(duration); - - #[cfg(feature = "reference-timestamps")] - { - gst::ReferenceTimestampMeta::add( - buffer, - &*TIMECODE_CAPS, - gst::ClockTime::from_nseconds(audio_frame.timecode() as u64 * 100), - gst::ClockTime::NONE, - ); - if audio_frame.timestamp() != ndisys::NDIlib_recv_timestamp_undefined { - gst::ReferenceTimestampMeta::add( - buffer, - &*TIMESTAMP_CAPS, - gst::ClockTime::from_nseconds(audio_frame.timestamp() as u64 * 100), - gst::ClockTime::NONE, - ); - } - } - - let mut dest = buffer.map_writable().unwrap(); - let dest = dest - .as_mut_slice_of::() - .map_err(|_| gst::FlowError::NotNegotiated)?; - assert!( - dest.len() - == audio_frame.no_samples() as usize * audio_frame.no_channels() as usize - ); - - for (channel, samples) in src - .chunks_exact(audio_frame.channel_stride_or_data_size_in_bytes() as usize) - .enumerate() - { - let samples = samples - .as_slice_of::() - .map_err(|_| gst::FlowError::NotNegotiated)?; - - for (i, sample) in samples[..audio_frame.no_samples() as usize] - .into_iter() - .enumerate() + let mut buffer = gst::Buffer::with_size(buff_size).unwrap(); { - dest[i * (audio_frame.no_channels() as usize) + channel] = *sample; + let buffer = buffer.get_mut().unwrap(); + + buffer.set_pts(pts); + buffer.set_duration(duration); + + #[cfg(feature = "reference-timestamps")] + { + gst::ReferenceTimestampMeta::add( + buffer, + &*TIMECODE_CAPS, + gst::ClockTime::from_nseconds(audio_frame.timecode() as u64 * 100), + gst::ClockTime::NONE, + ); + if audio_frame.timestamp() != ndisys::NDIlib_recv_timestamp_undefined { + gst::ReferenceTimestampMeta::add( + buffer, + &*TIMESTAMP_CAPS, + gst::ClockTime::from_nseconds(audio_frame.timestamp() as u64 * 100), + gst::ClockTime::NONE, + ); + } + } + + let mut dest = buffer.map_writable().unwrap(); + let dest = dest + .as_mut_slice_of::() + .map_err(|_| gst::FlowError::NotNegotiated)?; + assert!( + dest.len() + == audio_frame.no_samples() as usize + * audio_frame.no_channels() as usize + ); + + for (channel, samples) in src + .chunks_exact(audio_frame.channel_stride_or_data_size_in_bytes() as usize) + .enumerate() + { + let samples = samples + .as_slice_of::() + .map_err(|_| gst::FlowError::NotNegotiated)?; + + for (i, sample) in samples[..audio_frame.no_samples() as usize] + .iter() + .enumerate() + { + dest[i * (audio_frame.no_channels() as usize) + channel] = *sample; + } + } } + + Ok(buffer) + } + #[cfg(feature = "advanced-sdk")] + AudioInfo::OpusInfo { .. } => { + let data = audio_frame.data().ok_or_else(|| { + gst_error!(CAT, obj: element, "Audio packet has no data"); + gst::element_error!( + element, + gst::StreamError::Format, + ["Invalid audio packet"] + ); + + gst::FlowError::Error + })?; + + Ok(gst::Buffer::from_mut_slice(Vec::from(data))) + } + #[cfg(feature = "advanced-sdk")] + AudioInfo::AacInfo { .. } => { + let compressed_packet = audio_frame.compressed_packet().ok_or_else(|| { + gst_error!( + CAT, + obj: element, + "Audio packet doesn't have compressed packet start" + ); + gst::element_error!( + element, + gst::StreamError::Format, + ["Invalid audio packet"] + ); + + gst::FlowError::Error + })?; + + Ok(gst::Buffer::from_mut_slice(Vec::from( + compressed_packet.data, + ))) } } - - Ok(buffer) } } From 8585ef1e66ec0fa70b24e458541ca2a0f75327df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Wed, 6 Oct 2021 12:53:51 +0300 Subject: [PATCH 190/199] Require at least version 0.17.4 of the GStreamer core bindings Fixes https://github.com/teltek/gst-plugin-ndi/issues/75 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 441f80cc..bb64db63 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ edition = "2018" [dependencies] glib = "0.14" -gst = { package = "gstreamer", version = "0.17", features = ["v1_12"] } +gst = { package = "gstreamer", version = "0.17.4", features = ["v1_12"] } gst-base = { package = "gstreamer-base", version = "0.17" } gst-audio = { package = "gstreamer-audio", version = "0.17" } gst-video = { package = "gstreamer-video", version = "0.17", features = ["v1_12"] } From 3acaaa50f4acfedaa6b731db8520e46ad87c2219 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Fri, 5 Nov 2021 09:50:20 +0200 Subject: [PATCH 191/199] Fix compilation on platforms with unsigned `c_char` Fixes https://github.com/teltek/gst-plugin-ndi/issues/83 --- src/ndi.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ndi.rs b/src/ndi.rs index c54176bc..a12e03c8 100644 --- a/src/ndi.rs +++ b/src/ndi.rs @@ -804,7 +804,7 @@ impl<'a> VideoFrame<'a> { picture_aspect_ratio, frame_format_type, timecode, - p_data: frame.plane_data(0).unwrap().as_ptr() as *const i8, + 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, From d568d85c4b68376d0a627d37552e6f957e03ff84 Mon Sep 17 00:00:00 2001 From: Samuel Date: Tue, 9 Nov 2021 09:34:07 +0100 Subject: [PATCH 192/199] fixed doc --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5704660e..bc22ed4b 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,8 @@ export GST_PLUGIN_PATH=`pwd`/target/debug gst-inspect-1.0 ndi ``` -By defult GStreamer 1.16 is required, to use only GStreamer 1.12 instead of 1.16, pass `--disable-default-features` to cargo. Only a subset of video formats is supported with this GStreamer version. +By defult GStreamer 1.18 is required, to use an older version. You can build with `$ cargo build --no-default-features --features whatever_you_want_to_enable_of_the_above_features` + If all went ok, you should see info related to the NDI element. To make the plugin available without using `GST_PLUGIN_PATH` it's necessary to copy the plugin to the gstreamer plugins folder. ``` From 4a8848e5e9e101d20000d8e063685464d63bc9b5 Mon Sep 17 00:00:00 2001 From: Samuel Date: Fri, 12 Nov 2021 13:10:42 +0100 Subject: [PATCH 193/199] fixed Doc --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f3f91e17..5e77e3aa 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ export GST_PLUGIN_PATH=`pwd`/target/debug gst-inspect-1.0 ndi ``` -By defult GStreamer 1.18 is required, to use an older version. You can build with `$ cargo build --no-default-features --features whatever_you_want_to_enable_of_the_above_features` +By default GStreamer 1.18 is required, to use an older version. You can build with `$ cargo build --no-default-features --features whatever_you_want_to_enable_of_the_above_features` If all went ok, you should see info related to the NDI element. To make the plugin available without using `GST_PLUGIN_PATH` it's necessary to copy the plugin to the gstreamer plugins folder. From c221e9e870ec6da7d4df297cb4a15f6850a822d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sat, 22 Jan 2022 12:12:03 +0200 Subject: [PATCH 194/199] Update to gstreamer-rs 0.18 --- Cargo.toml | 10 ++++----- src/device_provider/imp.rs | 4 ++++ src/lib.rs | 46 +++++++++++++++++++------------------- src/ndi.rs | 6 ++--- src/ndisink/imp.rs | 4 +++- src/ndisinkcombiner/imp.rs | 15 ++++++------- src/ndisrc/imp.rs | 20 +++++++++-------- src/ndisrcdemux/imp.rs | 6 +++-- src/receiver.rs | 12 +++++----- 9 files changed, 66 insertions(+), 57 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bb64db63..95447083 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,11 +8,11 @@ description = "NewTek NDI Plugin" edition = "2018" [dependencies] -glib = "0.14" -gst = { package = "gstreamer", version = "0.17.4", features = ["v1_12"] } -gst-base = { package = "gstreamer-base", version = "0.17" } -gst-audio = { package = "gstreamer-audio", version = "0.17" } -gst-video = { package = "gstreamer-video", version = "0.17", features = ["v1_12"] } +glib = "0.15" +gst = { package = "gstreamer", version = "0.18", features = ["v1_12"] } +gst-base = { package = "gstreamer-base", version = "0.18" } +gst-audio = { package = "gstreamer-audio", version = "0.18" } +gst-video = { package = "gstreamer-video", version = "0.18", features = ["v1_12"] } byte-slice-cast = "1" once_cell = "1.0" byteorder = "1.0" diff --git a/src/device_provider/imp.rs b/src/device_provider/imp.rs index 956a531b..8484511e 100644 --- a/src/device_provider/imp.rs +++ b/src/device_provider/imp.rs @@ -46,6 +46,8 @@ impl ObjectSubclass for DeviceProvider { impl ObjectImpl for DeviceProvider {} +impl GstObjectImpl for DeviceProvider {} + impl DeviceProviderImpl for DeviceProvider { fn metadata() -> Option<&'static gst::subclass::DeviceProviderMetadata> { static METADATA: Lazy = Lazy::new(|| { @@ -210,6 +212,8 @@ impl ObjectSubclass for Device { impl ObjectImpl for Device {} +impl GstObjectImpl for Device {} + impl DeviceImpl for Device { fn create_element( &self, diff --git a/src/lib.rs b/src/lib.rs index 2c4302d7..dfdabf29 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,61 +20,61 @@ use std::time; use once_cell::sync::Lazy; -#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, glib::GEnum)] +#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, glib::Enum)] #[repr(u32)] -#[genum(type_name = "GstNdiTimestampMode")] +#[enum_type(name = "GstNdiTimestampMode")] pub enum TimestampMode { - #[genum(name = "Receive Time / Timecode", nick = "receive-time-vs-timecode")] + #[enum_value(name = "Receive Time / Timecode", nick = "receive-time-vs-timecode")] ReceiveTimeTimecode = 0, - #[genum(name = "Receive Time / Timestamp", nick = "receive-time-vs-timestamp")] + #[enum_value(name = "Receive Time / Timestamp", nick = "receive-time-vs-timestamp")] ReceiveTimeTimestamp = 1, - #[genum(name = "NDI Timecode", nick = "timecode")] + #[enum_value(name = "NDI Timecode", nick = "timecode")] Timecode = 2, - #[genum(name = "NDI Timestamp", nick = "timestamp")] + #[enum_value(name = "NDI Timestamp", nick = "timestamp")] Timestamp = 3, - #[genum(name = "Receive Time", nick = "receive-time")] + #[enum_value(name = "Receive Time", nick = "receive-time")] ReceiveTime = 4, } -#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, glib::GEnum)] +#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, glib::Enum)] #[repr(u32)] -#[genum(type_name = "GstNdiRecvColorFormat")] +#[enum_type(name = "GstNdiRecvColorFormat")] pub enum RecvColorFormat { - #[genum(name = "BGRX or BGRA", nick = "bgrx-bgra")] + #[enum_value(name = "BGRX or BGRA", nick = "bgrx-bgra")] BgrxBgra = 0, - #[genum(name = "UYVY or BGRA", nick = "uyvy-bgra")] + #[enum_value(name = "UYVY or BGRA", nick = "uyvy-bgra")] UyvyBgra = 1, - #[genum(name = "RGBX or RGBA", nick = "rgbx-rgba")] + #[enum_value(name = "RGBX or RGBA", nick = "rgbx-rgba")] RgbxRgba = 2, - #[genum(name = "UYVY or RGBA", nick = "uyvy-rgba")] + #[enum_value(name = "UYVY or RGBA", nick = "uyvy-rgba")] UyvyRgba = 3, - #[genum(name = "Fastest", nick = "fastest")] + #[enum_value(name = "Fastest", nick = "fastest")] Fastest = 4, - #[genum(name = "Best", nick = "best")] + #[enum_value(name = "Best", nick = "best")] Best = 5, #[cfg(feature = "advanced-sdk")] - #[genum(name = "Compressed v1", nick = "compressed-v1")] + #[enum_value(name = "Compressed v1", nick = "compressed-v1")] CompressedV1 = 6, #[cfg(feature = "advanced-sdk")] - #[genum(name = "Compressed v2", nick = "compressed-v2")] + #[enum_value(name = "Compressed v2", nick = "compressed-v2")] CompressedV2 = 7, #[cfg(feature = "advanced-sdk")] - #[genum(name = "Compressed v3", nick = "compressed-v3")] + #[enum_value(name = "Compressed v3", nick = "compressed-v3")] CompressedV3 = 8, #[cfg(feature = "advanced-sdk")] - #[genum(name = "Compressed v3 with audio", nick = "compressed-v3-with-audio")] + #[enum_value(name = "Compressed v3 with audio", nick = "compressed-v3-with-audio")] CompressedV3WithAudio = 9, #[cfg(feature = "advanced-sdk")] - #[genum(name = "Compressed v4", nick = "compressed-v4")] + #[enum_value(name = "Compressed v4", nick = "compressed-v4")] CompressedV4 = 10, #[cfg(feature = "advanced-sdk")] - #[genum(name = "Compressed v4 with audio", nick = "compressed-v4-with-audio")] + #[enum_value(name = "Compressed v4 with audio", nick = "compressed-v4-with-audio")] CompressedV4WithAudio = 11, #[cfg(feature = "advanced-sdk")] - #[genum(name = "Compressed v5", nick = "compressed-v5")] + #[enum_value(name = "Compressed v5", nick = "compressed-v5")] CompressedV5 = 12, #[cfg(feature = "advanced-sdk")] - #[genum(name = "Compressed v5 with audio", nick = "compressed-v5-with-audio")] + #[enum_value(name = "Compressed v5 with audio", nick = "compressed-v5-with-audio")] CompressedV5WithAudio = 13, } diff --git a/src/ndi.rs b/src/ndi.rs index a12e03c8..24507b88 100644 --- a/src/ndi.rs +++ b/src/ndi.rs @@ -793,14 +793,14 @@ impl<'a> VideoFrame<'a> { 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; + 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(), + frame_rate_N: frame.info().fps().numer(), + frame_rate_D: frame.info().fps().denom(), picture_aspect_ratio, frame_format_type, timecode, diff --git a/src/ndisink/imp.rs b/src/ndisink/imp.rs index 417da0fe..86c31032 100644 --- a/src/ndisink/imp.rs +++ b/src/ndisink/imp.rs @@ -64,7 +64,7 @@ impl ObjectSubclass for NdiSink { impl ObjectImpl for NdiSink { fn properties() -> &'static [glib::ParamSpec] { static PROPERTIES: Lazy> = Lazy::new(|| { - vec![glib::ParamSpec::new_string( + vec![glib::ParamSpecString::new( "ndi-name", "NDI Name", "NDI Name to use", @@ -105,6 +105,8 @@ impl ObjectImpl for NdiSink { } } +impl GstObjectImpl for NdiSink {} + impl ElementImpl for NdiSink { fn metadata() -> Option<&'static gst::subclass::ElementMetadata> { static ELEMENT_METADATA: Lazy = Lazy::new(|| { diff --git a/src/ndisinkcombiner/imp.rs b/src/ndisinkcombiner/imp.rs index 4e1a1659..20fd8816 100644 --- a/src/ndisinkcombiner/imp.rs +++ b/src/ndisinkcombiner/imp.rs @@ -62,6 +62,8 @@ impl ObjectImpl for NdiSinkCombiner { } } +impl GstObjectImpl for NdiSinkCombiner {} + impl ElementImpl for NdiSinkCombiner { fn metadata() -> Option<&'static gst::subclass::ElementMetadata> { static ELEMENT_METADATA: Lazy = Lazy::new(|| { @@ -259,10 +261,10 @@ impl AggregatorImpl for NdiSinkCombiner { audio_info.rate() as u64 * audio_info.bpf() as u64, ) } else if let Some(ref video_info) = state.video_info { - if *video_info.fps().numer() > 0 { + if video_info.fps().numer() > 0 { gst::ClockTime::SECOND.mul_div_floor( - *video_info.fps().denom() as u64, - *video_info.fps().numer() as u64, + video_info.fps().denom() as u64, + video_info.fps().numer() as u64, ) } else { gst::ClockTime::NONE @@ -564,12 +566,9 @@ impl AggregatorImpl for NdiSinkCombiner { // 2 frames latency because we queue 1 frame and wait until audio // up to the end of that frame has arrived. - let latency = if *info.fps().numer() > 0 { + let latency = if info.fps().numer() > 0 { gst::ClockTime::SECOND - .mul_div_floor( - 2 * *info.fps().denom() as u64, - *info.fps().numer() as u64, - ) + .mul_div_floor(2 * info.fps().denom() as u64, info.fps().numer() as u64) .unwrap_or(80 * gst::ClockTime::MSECOND) } else { // let's assume 25fps and 2 frames latency diff --git a/src/ndisrc/imp.rs b/src/ndisrc/imp.rs index 3d1200f2..8bed052a 100644 --- a/src/ndisrc/imp.rs +++ b/src/ndisrc/imp.rs @@ -105,28 +105,28 @@ impl ObjectImpl for NdiSrc { fn properties() -> &'static [glib::ParamSpec] { static PROPERTIES: Lazy> = Lazy::new(|| { vec![ - glib::ParamSpec::new_string( + glib::ParamSpecString::new( "ndi-name", "NDI Name", "NDI stream name of the sender", None, glib::ParamFlags::READWRITE, ), - glib::ParamSpec::new_string( + glib::ParamSpecString::new( "url-address", "URL/Address", "URL/address and port of the sender, e.g. 127.0.0.1:5961", None, glib::ParamFlags::READWRITE, ), - glib::ParamSpec::new_string( + glib::ParamSpecString::new( "receiver-ndi-name", "Receiver NDI Name", "NDI stream name of this receiver", Some(&*DEFAULT_RECEIVER_NDI_NAME), glib::ParamFlags::READWRITE, ), - glib::ParamSpec::new_uint( + glib::ParamSpecUInt::new( "connect-timeout", "Connect Timeout", "Connection timeout in ms", @@ -135,7 +135,7 @@ impl ObjectImpl for NdiSrc { 10000, glib::ParamFlags::READWRITE, ), - glib::ParamSpec::new_uint( + glib::ParamSpecUInt::new( "timeout", "Timeout", "Receive timeout in ms", @@ -144,7 +144,7 @@ impl ObjectImpl for NdiSrc { 5000, glib::ParamFlags::READWRITE, ), - glib::ParamSpec::new_uint( + glib::ParamSpecUInt::new( "max-queue-length", "Max Queue Length", "Maximum receive queue length", @@ -153,7 +153,7 @@ impl ObjectImpl for NdiSrc { 10, glib::ParamFlags::READWRITE, ), - glib::ParamSpec::new_int( + glib::ParamSpecInt::new( "bandwidth", "Bandwidth", "Bandwidth, -10 metadata-only, 10 audio-only, 100 highest", @@ -162,7 +162,7 @@ impl ObjectImpl for NdiSrc { 100, glib::ParamFlags::READWRITE, ), - glib::ParamSpec::new_enum( + glib::ParamSpecEnum::new( "color-format", "Color Format", "Receive color format", @@ -170,7 +170,7 @@ impl ObjectImpl for NdiSrc { RecvColorFormat::UyvyBgra as u32 as i32, glib::ParamFlags::READWRITE, ), - glib::ParamSpec::new_enum( + glib::ParamSpecEnum::new( "timestamp-mode", "Timestamp Mode", "Timestamp information to use for outgoing PTS", @@ -360,6 +360,8 @@ impl ObjectImpl for NdiSrc { } } +impl GstObjectImpl for NdiSrc {} + impl ElementImpl for NdiSrc { fn metadata() -> Option<&'static gst::subclass::ElementMetadata> { static ELEMENT_METADATA: Lazy = Lazy::new(|| { diff --git a/src/ndisrcdemux/imp.rs b/src/ndisrcdemux/imp.rs index 9cf8180c..67b38d79 100644 --- a/src/ndisrcdemux/imp.rs +++ b/src/ndisrcdemux/imp.rs @@ -65,6 +65,8 @@ impl ObjectImpl for NdiSrcDemux { } } +impl GstObjectImpl for NdiSrcDemux {} + impl ElementImpl for NdiSrcDemux { fn metadata() -> Option<&'static gst::subclass::ElementMetadata> { static ELEMENT_METADATA: Lazy = Lazy::new(|| { @@ -188,7 +190,7 @@ impl NdiSrcDemux { } } - Ok(Some(ev)) + std::ops::ControlFlow::Continue(gst::EventForeachAction::Keep) }); state.audio_caps = Some(caps.clone()); @@ -238,7 +240,7 @@ impl NdiSrcDemux { } } - Ok(Some(ev)) + std::ops::ControlFlow::Continue(gst::EventForeachAction::Keep) }); state.video_caps = Some(caps.clone()); diff --git a/src/receiver.rs b/src/receiver.rs index d44eea25..6098f4c1 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -1070,8 +1070,8 @@ impl Receiver { yres: video_frame.yres(), fps_n: video_frame.frame_rate().0, fps_d: video_frame.frame_rate().1, - par_n: *par.numer(), - par_d: *par.denom(), + par_n: par.numer(), + par_d: par.denom(), interlace_mode, }); } @@ -1108,8 +1108,8 @@ impl Receiver { yres: video_frame.yres(), fps_n: video_frame.frame_rate().0, fps_d: video_frame.frame_rate().1, - par_n: *par.numer(), - par_d: *par.denom(), + par_n: par.numer(), + par_d: par.denom(), interlace_mode, }); } @@ -1146,8 +1146,8 @@ impl Receiver { yres: video_frame.yres(), fps_n: video_frame.frame_rate().0, fps_d: video_frame.frame_rate().1, - par_n: *par.numer(), - par_d: *par.denom(), + par_n: par.numer(), + par_d: par.denom(), interlace_mode, }); } From ddb3bde94274faf71ac8e1f6fab5f0915ecf1e59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sat, 22 Jan 2022 12:13:04 +0200 Subject: [PATCH 195/199] Remove unused `TimeMapping` struct --- src/receiver.rs | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/src/receiver.rs b/src/receiver.rs index 6098f4c1..40ceddc6 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -249,14 +249,6 @@ impl Default for ObservationsInner { } } -#[derive(Clone, Copy, Debug)] -struct TimeMapping { - xbase: u64, - b: u64, - num: u64, - den: u64, -} - impl Observations { fn new() -> Self { Self(Arc::new(Mutex::new(ObservationsInner::default()))) @@ -431,17 +423,6 @@ impl Observations { } } -impl Default for TimeMapping { - fn default() -> Self { - Self { - xbase: 0, - b: 0, - num: 0, - den: 0, - } - } -} - #[derive(Clone)] pub struct ReceiverControlHandle { queue: ReceiverQueue, From b682833cca2d7d82c22b226b42648091842678c9 Mon Sep 17 00:00:00 2001 From: Seungha Yang Date: Mon, 28 Feb 2022 22:16:01 +0900 Subject: [PATCH 196/199] Set discont flag on the first audio buffer correctly Fixing typo which should check audio buffer not video --- src/receiver.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/receiver.rs b/src/receiver.rs index 40ceddc6..15514f9c 100644 --- a/src/receiver.rs +++ b/src/receiver.rs @@ -704,7 +704,7 @@ impl Receiver { first_frame = false; let mut buffer = receiver.create_audio_buffer_and_info(&element, frame); if first_audio_frame { - if let Ok(Buffer::Video(ref mut buffer, _)) = buffer { + if let Ok(Buffer::Audio(ref mut buffer, _)) = buffer { buffer .get_mut() .unwrap() From 71dd5182f3b5e4db6b8ede5d0b37c6935c3fdf3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chanal?= Date: Fri, 25 Mar 2022 10:18:34 +0100 Subject: [PATCH 197/199] Drop NdiSrcMeta after ndisrcdemux The NdiSrcMeta is no longer needed after the demux and if kept, it generates many errors as the transform_func() always false. --- src/ndisrcdemux/imp.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ndisrcdemux/imp.rs b/src/ndisrcdemux/imp.rs index 67b38d79..47ca9c5d 100644 --- a/src/ndisrcdemux/imp.rs +++ b/src/ndisrcdemux/imp.rs @@ -147,11 +147,11 @@ impl NdiSrcDemux { &self, pad: &gst::Pad, element: &super::NdiSrcDemux, - buffer: gst::Buffer, + mut buffer: gst::Buffer, ) -> Result { gst_log!(CAT, obj: pad, "Handling buffer {:?}", buffer); - let meta = buffer.meta::().ok_or_else(|| { + let meta = buffer.make_mut().meta_mut::().ok_or_else(|| { gst_error!(CAT, obj: element, "Buffer without NDI source meta"); gst::FlowError::Error })?; @@ -265,6 +265,7 @@ impl NdiSrcDemux { } } drop(state); + meta.remove().unwrap(); if add_pad { element.add_pad(&srcpad).unwrap(); From 03d4d916f5d48ccf15c6bb0bef86e55a1b4f0ae4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chanal?= Date: Tue, 5 Apr 2022 16:02:32 +0200 Subject: [PATCH 198/199] Error out on EOS event if no src pad exist This let the application get an error message when ndisrc emits an EOS triggered by 'connect-timeout' for instance. Without this, a simple gst-launch-1.0 ndisrc pad-name="badname" connect-timeout=1000 ! ndisrcdemux ! fakesink will block. --- src/ndisrcdemux/imp.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/ndisrcdemux/imp.rs b/src/ndisrcdemux/imp.rs index 47ca9c5d..a9589a75 100644 --- a/src/ndisrcdemux/imp.rs +++ b/src/ndisrcdemux/imp.rs @@ -48,6 +48,13 @@ impl ObjectSubclass for NdiSrcDemux { |self_, element| self_.sink_chain(pad, element, buffer), ) }) + .event_function(|pad, parent, event| { + NdiSrcDemux::catch_panic_pad_function( + parent, + || false, + |self_, element| self_.sink_event(pad, element, event), + ) + }) .build(); Self { @@ -280,4 +287,26 @@ impl NdiSrcDemux { let mut state = self.state.lock().unwrap(); state.combiner.update_pad_flow(&srcpad, res) } + + fn sink_event(&self, + pad: &gst::Pad, + element: &super::NdiSrcDemux, + event: gst::Event + ) -> bool { + use gst::EventView; + + gst_log!(CAT, obj: pad, "Handling event {:?}", event); + if let EventView::Eos(_) = event.view() { + if element.num_src_pads() == 0 { + // error out on EOS if no src pad are available + gst::element_error!( + element, + gst::StreamError::Demux, + ["EOS without available srcpad(s)"] + ); + } + } + pad.event_default(Some(element), event) + } + } From 9c540d8abb07d931dc3c6e02be125725fe98f7d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Wed, 12 Oct 2022 19:25:32 +0300 Subject: [PATCH 199/199] Move everything to net/ndi for preparing to merge into gst-plugins-rs --- .gitignore | 64 --------------------- .travis.yml | 30 ---------- Cargo.toml => net/ndi/Cargo.toml | 0 LICENSE => net/ndi/LICENSE | 0 README.md => net/ndi/README.md | 0 build.rs => net/ndi/build.rs | 0 {src => net/ndi/src}/device_provider/imp.rs | 0 {src => net/ndi/src}/device_provider/mod.rs | 0 {src => net/ndi/src}/lib.rs | 0 {src => net/ndi/src}/ndi.rs | 0 {src => net/ndi/src}/ndisink/imp.rs | 0 {src => net/ndi/src}/ndisink/mod.rs | 0 {src => net/ndi/src}/ndisinkcombiner/imp.rs | 0 {src => net/ndi/src}/ndisinkcombiner/mod.rs | 0 {src => net/ndi/src}/ndisinkmeta.rs | 0 {src => net/ndi/src}/ndisrc/imp.rs | 0 {src => net/ndi/src}/ndisrc/mod.rs | 0 {src => net/ndi/src}/ndisrcdemux/imp.rs | 0 {src => net/ndi/src}/ndisrcdemux/mod.rs | 0 {src => net/ndi/src}/ndisrcmeta.rs | 0 {src => net/ndi/src}/ndisys.rs | 0 {src => net/ndi/src}/receiver.rs | 0 22 files changed, 94 deletions(-) delete mode 100644 .gitignore delete mode 100644 .travis.yml rename Cargo.toml => net/ndi/Cargo.toml (100%) rename LICENSE => net/ndi/LICENSE (100%) rename README.md => net/ndi/README.md (100%) rename build.rs => net/ndi/build.rs (100%) rename {src => net/ndi/src}/device_provider/imp.rs (100%) rename {src => net/ndi/src}/device_provider/mod.rs (100%) rename {src => net/ndi/src}/lib.rs (100%) rename {src => net/ndi/src}/ndi.rs (100%) rename {src => net/ndi/src}/ndisink/imp.rs (100%) rename {src => net/ndi/src}/ndisink/mod.rs (100%) rename {src => net/ndi/src}/ndisinkcombiner/imp.rs (100%) rename {src => net/ndi/src}/ndisinkcombiner/mod.rs (100%) rename {src => net/ndi/src}/ndisinkmeta.rs (100%) rename {src => net/ndi/src}/ndisrc/imp.rs (100%) rename {src => net/ndi/src}/ndisrc/mod.rs (100%) rename {src => net/ndi/src}/ndisrcdemux/imp.rs (100%) rename {src => net/ndi/src}/ndisrcdemux/mod.rs (100%) rename {src => net/ndi/src}/ndisrcmeta.rs (100%) rename {src => net/ndi/src}/ndisys.rs (100%) rename {src => net/ndi/src}/receiver.rs (100%) diff --git a/.gitignore b/.gitignore deleted file mode 100644 index bbec86db..00000000 --- a/.gitignore +++ /dev/null @@ -1,64 +0,0 @@ - -# Created by https://www.gitignore.io/api/rust,emacs - -### Emacs ### -# -*- mode: gitignore; -*- -*~ -\#*\# -/.emacs.desktop -/.emacs.desktop.lock -*.elc -auto-save-list -tramp -.\#* - -# Org-mode -.org-id-locations -*_archive - -# flymake-mode -*_flymake.* - -# eshell files -/eshell/history -/eshell/lastdir - -# elpa packages -/elpa/ - -# reftex files -*.rel - -# AUCTeX auto folder -/auto/ - -# cask packages -.cask/ -dist/ - -# Flycheck -flycheck_*.el - -# server auth directory -/server/ - -# projectiles files -.projectile - -# directory configuration -.dir-locals.el - -### Rust ### -# Generated by Cargo -# will have compiled files and executables -/target/ - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock - -# These are backup files generated by rustfmt -**/*.rs.bk - - -# End of https://www.gitignore.io/api/rust,emacs diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index c8060043..00000000 --- a/.travis.yml +++ /dev/null @@ -1,30 +0,0 @@ -language: rust -rust: - - stable -addons: - apt: - packages: - - libgstreamer1.0-dev - - libgstreamer-plugins-base1.0-dev - - gstreamer1.0-plugins-base - - gstreamer1.0-plugins-good - - gstreamer1.0-plugins-bad - - gstreamer1.0-plugins-ugly - - gstreamer1.0-libav -before_install: - - curl -kLO https://slepin.fr/obs-ndi/ci/ndisdk-slim-v3.5-linux.zip -f --retry 5 && unzip ./ndisdk-slim-v3.5-linux.zip && rm ./ndisdk-slim-v3.5-linux.zip - - sudo cp ndisdk/lib/x86_64-linux-gnu/libndi.so.3.5.1 /usr/local/lib/libndi.so && sudo ldconfig - - curl -L https://people.freedesktop.org/~slomo/gstreamer-1.14.3.tar.gz | tar xz - - sed -i "s;prefix=/root/gstreamer;prefix=$PWD/gstreamer;g" $PWD/gstreamer/lib/x86_64-linux-gnu/pkgconfig/*.pc - - export PKG_CONFIG_PATH=$PWD/gstreamer/lib/x86_64-linux-gnu/pkgconfig - - export GST_PLUGIN_SYSTEM_PATH=$PWD/gstreamer/lib/x86_64-linux-gnu/gstreamer-1.0 - - export GST_PLUGIN_SCANNER=$PWD/gstreamer/libexec/gstreamer-1.0/gst-plugin-scanner - - export PATH=$PATH:$PWD/gstreamer/bin - - export LD_LIBRARY_PATH=$PWD/gstreamer/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH -install: - - rustup component add rustfmt-preview - - rustup component add clippy-preview -script: - - cargo fmt -- --check - - cargo build --no-default-features - - touch ./src/*.rs && cargo clippy --no-default-features -- -A clippy::cast_ptr_alignment -A clippy::new_ret_no_self -Aclippy::or_fun_call -A clippy::cast_lossless -A clippy::too_many_arguments \ No newline at end of file diff --git a/Cargo.toml b/net/ndi/Cargo.toml similarity index 100% rename from Cargo.toml rename to net/ndi/Cargo.toml diff --git a/LICENSE b/net/ndi/LICENSE similarity index 100% rename from LICENSE rename to net/ndi/LICENSE diff --git a/README.md b/net/ndi/README.md similarity index 100% rename from README.md rename to net/ndi/README.md diff --git a/build.rs b/net/ndi/build.rs similarity index 100% rename from build.rs rename to net/ndi/build.rs diff --git a/src/device_provider/imp.rs b/net/ndi/src/device_provider/imp.rs similarity index 100% rename from src/device_provider/imp.rs rename to net/ndi/src/device_provider/imp.rs diff --git a/src/device_provider/mod.rs b/net/ndi/src/device_provider/mod.rs similarity index 100% rename from src/device_provider/mod.rs rename to net/ndi/src/device_provider/mod.rs diff --git a/src/lib.rs b/net/ndi/src/lib.rs similarity index 100% rename from src/lib.rs rename to net/ndi/src/lib.rs diff --git a/src/ndi.rs b/net/ndi/src/ndi.rs similarity index 100% rename from src/ndi.rs rename to net/ndi/src/ndi.rs diff --git a/src/ndisink/imp.rs b/net/ndi/src/ndisink/imp.rs similarity index 100% rename from src/ndisink/imp.rs rename to net/ndi/src/ndisink/imp.rs diff --git a/src/ndisink/mod.rs b/net/ndi/src/ndisink/mod.rs similarity index 100% rename from src/ndisink/mod.rs rename to net/ndi/src/ndisink/mod.rs diff --git a/src/ndisinkcombiner/imp.rs b/net/ndi/src/ndisinkcombiner/imp.rs similarity index 100% rename from src/ndisinkcombiner/imp.rs rename to net/ndi/src/ndisinkcombiner/imp.rs diff --git a/src/ndisinkcombiner/mod.rs b/net/ndi/src/ndisinkcombiner/mod.rs similarity index 100% rename from src/ndisinkcombiner/mod.rs rename to net/ndi/src/ndisinkcombiner/mod.rs diff --git a/src/ndisinkmeta.rs b/net/ndi/src/ndisinkmeta.rs similarity index 100% rename from src/ndisinkmeta.rs rename to net/ndi/src/ndisinkmeta.rs diff --git a/src/ndisrc/imp.rs b/net/ndi/src/ndisrc/imp.rs similarity index 100% rename from src/ndisrc/imp.rs rename to net/ndi/src/ndisrc/imp.rs diff --git a/src/ndisrc/mod.rs b/net/ndi/src/ndisrc/mod.rs similarity index 100% rename from src/ndisrc/mod.rs rename to net/ndi/src/ndisrc/mod.rs diff --git a/src/ndisrcdemux/imp.rs b/net/ndi/src/ndisrcdemux/imp.rs similarity index 100% rename from src/ndisrcdemux/imp.rs rename to net/ndi/src/ndisrcdemux/imp.rs diff --git a/src/ndisrcdemux/mod.rs b/net/ndi/src/ndisrcdemux/mod.rs similarity index 100% rename from src/ndisrcdemux/mod.rs rename to net/ndi/src/ndisrcdemux/mod.rs diff --git a/src/ndisrcmeta.rs b/net/ndi/src/ndisrcmeta.rs similarity index 100% rename from src/ndisrcmeta.rs rename to net/ndi/src/ndisrcmeta.rs diff --git a/src/ndisys.rs b/net/ndi/src/ndisys.rs similarity index 100% rename from src/ndisys.rs rename to net/ndi/src/ndisys.rs diff --git a/src/receiver.rs b/net/ndi/src/receiver.rs similarity index 100% rename from src/receiver.rs rename to net/ndi/src/receiver.rs