mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2024-12-29 13:30:34 +00:00
Update to gstreamer-rs 0.17
This commit is contained in:
parent
160571e251
commit
291d951b01
16 changed files with 1036 additions and 992 deletions
12
Cargo.toml
12
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"]
|
||||
|
|
4
build.rs
4
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()
|
||||
}
|
||||
|
|
|
@ -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<Option<thread::JoinHandle<()>>>,
|
||||
current_devices: Mutex<Vec<gst::Device>>,
|
||||
current_devices: Mutex<Vec<super::Device>>,
|
||||
find: Mutex<Option<ndi::FindInstance>>,
|
||||
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<Self>;
|
||||
type Class = subclass::simple::ClassStruct<Self>;
|
||||
|
||||
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<Self>) {
|
||||
klass.set_metadata(
|
||||
"NewTek NDI Device Provider",
|
||||
"Source/Audio/Video/Network",
|
||||
"NewTek NDI Device Provider",
|
||||
"Ruben Gonzalez <rubenrua@teltek.es>, Daniel Vilar <daniel.peiteado@teltek.es>, Sebastian Dröge <sebastian@centricular.com>",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for DeviceProvider {
|
||||
glib::glib_object_impl!();
|
||||
}
|
||||
impl ObjectImpl for DeviceProvider {}
|
||||
|
||||
impl DeviceProviderImpl for DeviceProvider {
|
||||
fn probe(&self, _device_provider: &gst::DeviceProvider) -> Vec<gst::Device> {
|
||||
self.current_devices.lock().unwrap().clone()
|
||||
fn metadata() -> Option<&'static gst::subclass::DeviceProviderMetadata> {
|
||||
static METADATA: Lazy<gst::subclass::DeviceProviderMetadata> = Lazy::new(|| {
|
||||
gst::subclass::DeviceProviderMetadata::new("NewTek NDI Device Provider",
|
||||
"Source/Audio/Video/Network",
|
||||
"NewTek NDI Device Provider",
|
||||
"Ruben Gonzalez <rubenrua@teltek.es>, Daniel Vilar <daniel.peiteado@teltek.es>, Sebastian Dröge <sebastian@centricular.com>")
|
||||
});
|
||||
|
||||
Some(&*METADATA)
|
||||
}
|
||||
fn start(&self, device_provider: &gst::DeviceProvider) -> Result<(), gst::LoggableError> {
|
||||
|
||||
fn probe(&self, _device_provider: &Self::Type) -> Vec<gst::Device> {
|
||||
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<Self>;
|
||||
type Class = subclass::simple::ClassStruct<Self>;
|
||||
|
||||
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<gst::Element, gst::LoggableError> {
|
||||
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::<gst::Element>::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::<gst::Device>()
|
||||
let device = glib::Object::new::<super::Device>(&[
|
||||
("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(),
|
||||
)
|
||||
}
|
26
src/device_provider/mod.rs
Normal file
26
src/device_provider/mod.rs
Normal file
|
@ -0,0 +1,26 @@
|
|||
use glib::prelude::*;
|
||||
|
||||
mod imp;
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct DeviceProvider(ObjectSubclass<imp::DeviceProvider>) @extends gst::DeviceProvider, gst::Object;
|
||||
}
|
||||
|
||||
unsafe impl Send for DeviceProvider {}
|
||||
unsafe impl Sync for DeviceProvider {}
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct Device(ObjectSubclass<imp::Device>) @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(),
|
||||
)
|
||||
}
|
|
@ -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<gst::Caps> =
|
|||
static TIMESTAMP_CAPS: Lazy<gst::Caps> =
|
||||
Lazy::new(|| gst::Caps::new_simple("timestamp/x-ndi-timestamp", &[]));
|
||||
|
||||
gst::gst_plugin_define!(
|
||||
gst::plugin_define!(
|
||||
ndi,
|
||||
env!("CARGO_PKG_DESCRIPTION"),
|
||||
plugin_init,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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<gst_audio::AudioInfo>,
|
||||
receiver: Option<Receiver<AudioReceiver>>,
|
||||
current_latency: gst::ClockTime,
|
||||
current_latency: Option<gst::ClockTime>,
|
||||
}
|
||||
|
||||
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<Settings>,
|
||||
state: Mutex<State>,
|
||||
receiver_controller: Mutex<Option<ReceiverControlHandle<AudioReceiver>>>,
|
||||
}
|
||||
|
||||
#[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<Self>;
|
||||
type Class = subclass::simple::ClassStruct<Self>;
|
||||
|
||||
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<Self>) {
|
||||
klass.set_metadata(
|
||||
"NewTek NDI Audio Source",
|
||||
"Source",
|
||||
"NewTek NDI audio source",
|
||||
"Ruben Gonzalez <rubenrua@teltek.es>, Daniel Vilar <daniel.peiteado@teltek.es>, Sebastian Dröge <sebastian@centricular.com>",
|
||||
);
|
||||
|
||||
let caps = gst::Caps::new_simple(
|
||||
"audio/x-raw",
|
||||
&[
|
||||
(
|
||||
"format",
|
||||
&gst::List::new(&[&gst_audio::AUDIO_FORMAT_S16.to_string()]),
|
||||
),
|
||||
("rate", &gst::IntRange::<i32>::new(1, i32::MAX)),
|
||||
("channels", &gst::IntRange::<i32>::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<Vec<glib::ParamSpec>> = 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::<gst_base::BaseSrc>().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::<gst_base::BaseSrc>().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::<Option<String>>().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<glib::Value, ()> {
|
||||
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<gst::subclass::ElementMetadata> = Lazy::new(|| {
|
||||
gst::subclass::ElementMetadata::new(
|
||||
"NewTek NDI Audio Source",
|
||||
"Source",
|
||||
"NewTek NDI audio source",
|
||||
"Ruben Gonzalez <rubenrua@teltek.es>, Daniel Vilar <daniel.peiteado@teltek.es>, Sebastian Dröge <sebastian@centricular.com>",
|
||||
)
|
||||
});
|
||||
|
||||
Some(&*ELEMENT_METADATA)
|
||||
}
|
||||
|
||||
fn pad_templates() -> &'static [gst::PadTemplate] {
|
||||
static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = 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::<i32>::new(1, i32::MAX)),
|
||||
("channels", &gst::IntRange::<i32>::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<gst::StateChangeSuccess, gst::StateChangeError> {
|
||||
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(),
|
||||
)
|
||||
}
|
19
src/ndiaudiosrc/mod.rs
Normal file
19
src/ndiaudiosrc/mod.rs
Normal file
|
@ -0,0 +1,19 @@
|
|||
use glib::prelude::*;
|
||||
|
||||
mod imp;
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct NdiAudioSrc(ObjectSubclass<imp::NdiAudioSrc>) @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(),
|
||||
)
|
||||
}
|
|
@ -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<String> = 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<gst_video::VideoInfo>,
|
||||
|
@ -57,13 +47,11 @@ static CAT: Lazy<gst::DebugCategory> = 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<Self>;
|
||||
type Class = subclass::simple::ClassStruct<Self>;
|
||||
|
||||
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<Self>) {
|
||||
klass.set_metadata(
|
||||
"NDI Sink",
|
||||
"Sink/Audio/Video",
|
||||
"Render as an NDI stream",
|
||||
"Sebastian Dröge <sebastian@centricular.com>",
|
||||
);
|
||||
|
||||
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::<i32>::new(1, std::i32::MAX))
|
||||
.field("height", &gst::IntRange::<i32>::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::<i32>::new(1, i32::MAX))
|
||||
.field("channels", &gst::IntRange::<i32>::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<Vec<glib::ParamSpec>> = 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::<String>()
|
||||
.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<glib::Value, ()> {
|
||||
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<gst::subclass::ElementMetadata> = Lazy::new(|| {
|
||||
gst::subclass::ElementMetadata::new(
|
||||
"NDI Sink",
|
||||
"Sink/Audio/Video",
|
||||
"Render as an NDI stream",
|
||||
"Sebastian Dröge <sebastian@centricular.com>",
|
||||
)
|
||||
});
|
||||
|
||||
Some(&*ELEMENT_METADATA)
|
||||
}
|
||||
|
||||
fn pad_templates() -> &'static [gst::PadTemplate] {
|
||||
static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = 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::<i32>::new(1, std::i32::MAX))
|
||||
.field("height", &gst::IntRange::<i32>::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::<i32>::new(1, i32::MAX))
|
||||
.field("channels", &gst::IntRange::<i32>::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<gst::FlowSuccess, gst::FlowError> {
|
||||
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::<crate::ndisinkmeta::NdiSinkAudioMeta>() {
|
||||
if let Some(audio_meta) = buffer.meta::<crate::ndisinkmeta::NdiSinkAudioMeta>() {
|
||||
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::<gst::ClockTime>()
|
||||
.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::<gst::ClockTime>()
|
||||
.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(),
|
||||
)
|
||||
}
|
19
src/ndisink/mod.rs
Normal file
19
src/ndisink/mod.rs
Normal file
|
@ -0,0 +1,19 @@
|
|||
use glib::prelude::*;
|
||||
|
||||
mod imp;
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct NdiSink(ObjectSubclass<imp::NdiSink>) @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(),
|
||||
)
|
||||
}
|
|
@ -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<Option<gst_base::AggregatorPad>>,
|
||||
state: Mutex<Option<State>>,
|
||||
}
|
||||
|
||||
#[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<Self>;
|
||||
type Class = subclass::simple::ClassStruct<Self>;
|
||||
|
||||
glib::glib_object_subclass!();
|
||||
|
||||
fn class_init(klass: &mut subclass::simple::ClassStruct<Self>) {
|
||||
klass.set_metadata(
|
||||
"NDI Sink Combiner",
|
||||
"Combiner/Audio/Video",
|
||||
"NDI sink audio/video combiner",
|
||||
"Sebastian Dröge <sebastian@centricular.com>",
|
||||
);
|
||||
|
||||
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::<i32>::new(1, i32::MAX))
|
||||
.field("height", &gst::IntRange::<i32>::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::<i32>::new(1, i32::MAX))
|
||||
.field("channels", &gst::IntRange::<i32>::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::<gst_base::AggregatorPad>::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::<gst::Element>().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<gst::subclass::ElementMetadata> = Lazy::new(|| {
|
||||
gst::subclass::ElementMetadata::new(
|
||||
"NDI Sink Combiner",
|
||||
"Combiner/Audio/Video",
|
||||
"NDI sink audio/video combiner",
|
||||
"Sebastian Dröge <sebastian@centricular.com>",
|
||||
)
|
||||
});
|
||||
|
||||
Some(&*ELEMENT_METADATA)
|
||||
}
|
||||
|
||||
fn pad_templates() -> &'static [gst::PadTemplate] {
|
||||
static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = 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::<i32>::new(1, i32::MAX))
|
||||
.field("height", &gst::IntRange::<i32>::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::<i32>::new(1, i32::MAX))
|
||||
.field("channels", &gst::IntRange::<i32>::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<gst::ClockTime> {
|
||||
// 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<gst::Buffer> {
|
||||
let segment = match agg_pad.get_segment().downcast::<gst::ClockTime>() {
|
||||
let segment = match agg_pad.segment().downcast::<gst::ClockTime>() {
|
||||
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<gst::FlowSuccess, gst::FlowError> {
|
||||
// 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::<gst::ClockTime>() {
|
||||
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::<gst::ClockTime>() {
|
||||
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::<gst::ClockTime>() {
|
||||
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(),
|
||||
)
|
||||
}
|
19
src/ndisinkcombiner/mod.rs
Normal file
19
src/ndisinkcombiner/mod.rs
Normal file
|
@ -0,0 +1,19 @@
|
|||
use glib::prelude::*;
|
||||
|
||||
mod imp;
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct NdiSinkCombiner(ObjectSubclass<imp::NdiSinkCombiner>) @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(),
|
||||
)
|
||||
}
|
|
@ -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<glib::Type> = 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::<std::os::raw::c_char>()].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<gst_sys::GstMetaInfo>);
|
||||
pub(super) fn ndi_sink_audio_meta_get_info() -> *const gst::ffi::GstMetaInfo {
|
||||
struct MetaInfo(ptr::NonNull<gst::ffi::GstMetaInfo>);
|
||||
unsafe impl Send for MetaInfo {}
|
||||
unsafe impl Sync for MetaInfo {}
|
||||
|
||||
static META_INFO: Lazy<MetaInfo> = 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::<NdiSinkAudioMeta>(),
|
||||
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"),
|
||||
)
|
||||
});
|
||||
|
|
|
@ -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<gst_video::VideoInfo>,
|
||||
current_latency: gst::ClockTime,
|
||||
current_latency: Option<gst::ClockTime>,
|
||||
receiver: Option<Receiver<VideoReceiver>>,
|
||||
}
|
||||
|
||||
|
@ -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<Settings>,
|
||||
state: Mutex<State>,
|
||||
receiver_controller: Mutex<Option<ReceiverControlHandle<VideoReceiver>>>,
|
||||
}
|
||||
|
||||
#[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<Self>;
|
||||
type Class = subclass::simple::ClassStruct<Self>;
|
||||
|
||||
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<Self>) {
|
||||
klass.set_metadata(
|
||||
"NewTek NDI Video Source",
|
||||
"Source",
|
||||
"NewTek NDI video source",
|
||||
"Ruben Gonzalez <rubenrua@teltek.es>, Daniel Vilar <daniel.peiteado@teltek.es>, Sebastian Dröge <sebastian@centricular.com>",
|
||||
);
|
||||
|
||||
// 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::<i32>::new(0, i32::MAX)),
|
||||
("height", &gst::IntRange::<i32>::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<Vec<glib::ParamSpec>> = 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::<gst_base::BaseSrc>().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::<gst_base::BaseSrc>().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::<Option<String>>().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<glib::Value, ()> {
|
||||
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<gst::subclass::ElementMetadata> = Lazy::new(|| {
|
||||
gst::subclass::ElementMetadata::new(
|
||||
"NewTek NDI Video Source",
|
||||
"Source",
|
||||
"NewTek NDI video source",
|
||||
"Ruben Gonzalez <rubenrua@teltek.es>, Daniel Vilar <daniel.peiteado@teltek.es>, Sebastian Dröge <sebastian@centricular.com>",
|
||||
)
|
||||
});
|
||||
|
||||
Some(&*ELEMENT_METADATA)
|
||||
}
|
||||
|
||||
fn pad_templates() -> &'static [gst::PadTemplate] {
|
||||
static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = 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::<i32>::new(0, i32::MAX)),
|
||||
("height", &gst::IntRange::<i32>::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<gst::StateChangeSuccess, gst::StateChangeError> {
|
||||
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(),
|
||||
)
|
||||
}
|
19
src/ndivideosrc/mod.rs
Normal file
19
src/ndivideosrc/mod.rs
Normal file
|
@ -0,0 +1,19 @@
|
|||
use glib::prelude::*;
|
||||
|
||||
mod imp;
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct NdiVideoSrc(ObjectSubclass<imp::NdiVideoSrc>) @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(),
|
||||
)
|
||||
}
|
241
src/receiver.rs
241
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>, gst::ClockTime),
|
||||
duration: Option<gst::ClockTime>,
|
||||
) -> (gst::ClockTime, Option<gst::ClockTime>) {
|
||||
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<T: ReceiverType> Receiver<T> {
|
|||
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<T: ReceiverType> Receiver<T> {
|
|||
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<gst::ClockTime>,
|
||||
) -> Option<(gst::ClockTime, Option<gst::ClockTime>)> {
|
||||
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<T: ReceiverType> Receiver<T> {
|
|||
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<T: ReceiverType> Receiver<T> {
|
|||
self.0.cat,
|
||||
obj: element,
|
||||
"Calculated PTS {}, duration {}",
|
||||
pts,
|
||||
duration,
|
||||
pts.display(),
|
||||
duration.display(),
|
||||
);
|
||||
|
||||
Some((pts, duration))
|
||||
|
@ -909,7 +900,7 @@ impl Receiver<VideoReceiver> {
|
|||
|
||||
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<VideoReceiver> {
|
|||
&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<gst::ClockTime>)> {
|
||||
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<VideoReceiver> {
|
|||
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<VideoReceiver> {
|
|||
}
|
||||
|
||||
builder.build().map_err(|_| {
|
||||
gst_element_error!(
|
||||
gst::element_error!(
|
||||
element,
|
||||
gst::StreamError::Format,
|
||||
["Invalid video format configuration"]
|
||||
|
@ -1062,7 +1051,7 @@ impl Receiver<VideoReceiver> {
|
|||
&& 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<VideoReceiver> {
|
|||
}
|
||||
|
||||
builder.build().map_err(|_| {
|
||||
gst_element_error!(
|
||||
gst::element_error!(
|
||||
element,
|
||||
gst::StreamError::Format,
|
||||
["Invalid video format configuration"]
|
||||
|
@ -1109,7 +1098,7 @@ impl Receiver<VideoReceiver> {
|
|||
&self,
|
||||
element: &gst_base::BaseSrc,
|
||||
pts: gst::ClockTime,
|
||||
duration: gst::ClockTime,
|
||||
duration: Option<gst::ClockTime>,
|
||||
info: &gst_video::VideoInfo,
|
||||
video_frame: &VideoFrame,
|
||||
) -> gst::Buffer {
|
||||
|
@ -1124,15 +1113,15 @@ impl Receiver<VideoReceiver> {
|
|||
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<AudioReceiver> {
|
|||
|
||||
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<AudioReceiver> {
|
|||
&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<gst::ClockTime>)> {
|
||||
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<AudioReceiver> {
|
|||
);
|
||||
|
||||
builder.build().map_err(|_| {
|
||||
gst_element_error!(
|
||||
gst::element_error!(
|
||||
element,
|
||||
gst::StreamError::Format,
|
||||
["Invalid audio format configuration"]
|
||||
|
@ -1440,7 +1427,7 @@ impl Receiver<AudioReceiver> {
|
|||
&self,
|
||||
_element: &gst_base::BaseSrc,
|
||||
pts: gst::ClockTime,
|
||||
duration: gst::ClockTime,
|
||||
duration: Option<gst::ClockTime>,
|
||||
info: &gst_audio::AudioInfo,
|
||||
audio_frame: &AudioFrame,
|
||||
) -> gst::Buffer {
|
||||
|
@ -1458,15 +1445,15 @@ impl Receiver<AudioReceiver> {
|
|||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue