gst-plugins-rs/src/ndiaudiosrc.rs

566 lines
19 KiB
Rust
Raw Normal View History

2018-04-09 05:53:04 +00:00
use glib;
2018-12-11 16:47:03 +00:00
use glib::subclass;
2018-04-09 05:53:04 +00:00
use gst;
use gst::prelude::*;
2018-12-11 16:47:03 +00:00
use gst::subclass::prelude::*;
2021-02-23 10:37:24 +00:00
use gst::{gst_debug, gst_element_error, gst_error, gst_error_msg};
2018-06-15 13:16:25 +00:00
use gst_audio;
2018-12-11 16:47:03 +00:00
use gst_base;
2018-04-09 05:53:04 +00:00
use gst_base::prelude::*;
2020-07-27 13:08:56 +00:00
use gst_base::subclass::base_src::CreateSuccess;
2018-12-11 16:47:03 +00:00
use gst_base::subclass::prelude::*;
2018-04-09 05:53:04 +00:00
use std::sync::Mutex;
use std::{i32, u32};
use crate::connect_ndi;
use crate::ndisys;
use crate::AudioReceiver;
use crate::Receiver;
use crate::ReceiverControlHandle;
use crate::ReceiverItem;
use crate::TimestampMode;
use crate::DEFAULT_RECEIVER_NDI_NAME;
2018-04-12 15:17:59 +00:00
#[derive(Debug, Clone)]
2018-04-09 05:53:04 +00:00
struct Settings {
ndi_name: Option<String>,
url_address: Option<String>,
connect_timeout: u32,
timeout: u32,
receiver_ndi_name: String,
bandwidth: ndisys::NDIlib_recv_bandwidth_e,
timestamp_mode: TimestampMode,
2018-04-09 05:53:04 +00:00
}
impl Default for Settings {
fn default() -> Self {
Settings {
ndi_name: None,
url_address: None,
receiver_ndi_name: DEFAULT_RECEIVER_NDI_NAME.clone(),
connect_timeout: 10000,
timeout: 5000,
bandwidth: ndisys::NDIlib_recv_bandwidth_highest,
timestamp_mode: TimestampMode::ReceiveTime,
2018-04-09 05:53:04 +00:00
}
}
}
static PROPERTIES: [subclass::Property; 7] = [
subclass::Property("ndi-name", |name| {
2019-03-26 16:41:28 +00:00
glib::ParamSpec::string(
name,
"NDI Name",
"NDI stream name of the sender",
2019-03-26 16:41:28 +00:00
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| {
2019-03-26 16:41:28 +00:00
glib::ParamSpec::uint(
name,
"Timeout",
"Receive timeout in ms",
2019-03-26 16:41:28 +00:00
0,
u32::MAX,
5000,
glib::ParamFlags::READWRITE,
)
}),
subclass::Property("bandwidth", |name| {
glib::ParamSpec::int(
name,
"Bandwidth",
"Bandwidth, -10 metadata-only, 10 audio-only, 100 highest",
-10,
100,
100,
2019-03-26 16:41:28 +00:00
glib::ParamFlags::READWRITE,
)
}),
subclass::Property("timestamp-mode", |name| {
glib::ParamSpec::enum_(
name,
"Timestamp Mode",
"Timestamp information to use for outgoing PTS",
TimestampMode::static_type(),
TimestampMode::ReceiveTime as i32,
glib::ParamFlags::READWRITE,
)
}),
2018-04-09 05:53:04 +00:00
];
struct State {
2018-06-15 13:16:25 +00:00
info: Option<gst_audio::AudioInfo>,
receiver: Option<Receiver<AudioReceiver>>,
current_latency: gst::ClockTime,
2018-04-09 05:53:04 +00:00
}
impl Default for State {
fn default() -> State {
State {
info: None,
receiver: None,
current_latency: gst::CLOCK_TIME_NONE,
}
2018-04-09 05:53:04 +00:00
}
}
pub(crate) struct NdiAudioSrc {
2018-04-09 05:53:04 +00:00
cat: gst::DebugCategory,
settings: Mutex<Settings>,
state: Mutex<State>,
receiver_controller: Mutex<Option<ReceiverControlHandle<AudioReceiver>>>,
2018-04-09 05:53:04 +00:00
}
2018-12-11 16:47:03 +00:00
impl ObjectSubclass for NdiAudioSrc {
const NAME: &'static str = "NdiAudioSrc";
type ParentType = gst_base::BaseSrc;
type Instance = gst::subclass::ElementInstanceStruct<Self>;
type Class = subclass::simple::ClassStruct<Self>;
2021-02-23 10:37:24 +00:00
glib::glib_object_subclass!();
2018-04-09 05:53:04 +00:00
2018-12-11 16:47:03 +00:00
fn new() -> Self {
Self {
2018-04-09 05:53:04 +00:00
cat: gst::DebugCategory::new(
2018-06-12 12:40:17 +00:00
"ndiaudiosrc",
2018-04-09 05:53:04 +00:00
gst::DebugColorFlags::empty(),
Some("NewTek NDI Audio Source"),
2018-04-09 05:53:04 +00:00
),
settings: Mutex::new(Default::default()),
state: Mutex::new(Default::default()),
receiver_controller: Mutex::new(None),
2018-12-11 16:47:03 +00:00
}
2018-04-09 05:53:04 +00:00
}
2018-12-11 16:47:03 +00:00
fn class_init(klass: &mut subclass::simple::ClassStruct<Self>) {
2018-04-09 05:53:04 +00:00
klass.set_metadata(
2018-06-12 12:40:17 +00:00
"NewTek NDI Audio Source",
2018-04-09 05:53:04 +00:00
"Source",
"NewTek NDI audio source",
"Ruben Gonzalez <rubenrua@teltek.es>, Daniel Vilar <daniel.peiteado@teltek.es>, Sebastian Dröge <sebastian@centricular.com>",
2018-04-09 05:53:04 +00:00
);
let caps = gst::Caps::new_simple(
2018-06-15 13:16:25 +00:00
"audio/x-raw",
2018-04-09 05:53:04 +00:00
&[
2019-03-26 16:41:28 +00:00
(
"format",
2019-07-15 16:43:55 +00:00
&gst::List::new(&[&gst_audio::AUDIO_FORMAT_S16.to_string()]),
2018-04-09 05:53:04 +00:00
),
2018-06-15 13:16:25 +00:00
("rate", &gst::IntRange::<i32>::new(1, i32::MAX)),
("channels", &gst::IntRange::<i32>::new(1, i32::MAX)),
("layout", &"interleaved"),
2019-03-26 16:41:28 +00:00
],
);
2018-12-11 16:47:03 +00:00
2019-03-26 16:41:28 +00:00
let src_pad_template = gst::PadTemplate::new(
"src",
gst::PadDirection::Src,
gst::PadPresence::Always,
&caps,
)
.unwrap();
klass.add_pad_template(src_pad_template);
2018-12-11 16:47:03 +00:00
2019-03-26 16:41:28 +00:00
klass.install_properties(&PROPERTIES);
2018-12-11 16:47:03 +00:00
}
2019-03-26 16:41:28 +00:00
}
2018-09-18 11:25:24 +00:00
2019-03-26 16:41:28 +00:00
impl ObjectImpl for NdiAudioSrc {
2021-02-23 10:37:24 +00:00
glib::glib_object_impl!();
2018-05-31 09:16:29 +00:00
2019-03-26 16:41:28 +00:00
fn constructed(&self, obj: &glib::Object) {
self.parent_constructed(obj);
2018-12-11 16:47:03 +00:00
2019-03-26 16:41:28 +00:00
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);
}
2018-12-11 16:47:03 +00:00
2019-03-26 16:41:28 +00:00
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();
match *prop {
subclass::Property("ndi-name", ..) => {
let mut settings = self.settings.lock().unwrap();
let ndi_name = value.get().unwrap();
gst_debug!(
self.cat,
obj: basesrc,
"Changing ndi-name from {:?} to {:?}",
settings.ndi_name,
ndi_name,
);
settings.ndi_name = ndi_name;
}
subclass::Property("url-address", ..) => {
let mut settings = self.settings.lock().unwrap();
let url_address = value.get().unwrap();
gst_debug!(
self.cat,
obj: basesrc,
"Changing url-address from {:?} to {:?}",
settings.url_address,
url_address,
);
settings.url_address = url_address;
}
subclass::Property("receiver-ndi-name", ..) => {
2019-03-26 16:41:28 +00:00
let mut settings = self.settings.lock().unwrap();
let receiver_ndi_name = value.get().unwrap();
2019-03-26 16:41:28 +00:00
gst_debug!(
self.cat,
obj: basesrc,
"Changing receiver-ndi-name from {:?} to {:?}",
settings.receiver_ndi_name,
receiver_ndi_name,
2019-03-26 16:41:28 +00:00
);
settings.receiver_ndi_name =
receiver_ndi_name.unwrap_or_else(|| DEFAULT_RECEIVER_NDI_NAME.clone());
2019-03-26 16:41:28 +00:00
}
subclass::Property("connect-timeout", ..) => {
2019-03-26 16:41:28 +00:00
let mut settings = self.settings.lock().unwrap();
let connect_timeout = value.get_some().unwrap();
2019-03-26 16:41:28 +00:00
gst_debug!(
self.cat,
obj: basesrc,
"Changing connect-timeout from {} to {}",
settings.connect_timeout,
connect_timeout,
2019-03-26 16:41:28 +00:00
);
settings.connect_timeout = connect_timeout;
}
subclass::Property("timeout", ..) => {
let mut settings = self.settings.lock().unwrap();
let timeout = value.get_some().unwrap();
gst_debug!(
self.cat,
obj: basesrc,
"Changing timeout from {} to {}",
settings.timeout,
timeout,
);
settings.timeout = timeout;
}
subclass::Property("bandwidth", ..) => {
let mut settings = self.settings.lock().unwrap();
let bandwidth = value.get_some().unwrap();
gst_debug!(
self.cat,
obj: basesrc,
"Changing bandwidth from {} to {}",
settings.bandwidth,
bandwidth,
);
settings.bandwidth = bandwidth;
}
subclass::Property("timestamp-mode", ..) => {
let mut settings = self.settings.lock().unwrap();
let timestamp_mode = value.get_some().unwrap();
gst_debug!(
self.cat,
obj: basesrc,
"Changing timestamp mode from {:?} to {:?}",
settings.timestamp_mode,
timestamp_mode
);
if settings.timestamp_mode != timestamp_mode {
2020-07-27 13:08:56 +00:00
let _ =
basesrc.post_message(gst::message::Latency::builder().src(basesrc).build());
}
settings.timestamp_mode = timestamp_mode;
}
2019-03-26 16:41:28 +00:00
_ => unimplemented!(),
2018-04-09 05:53:04 +00:00
}
2019-03-26 16:41:28 +00:00
}
2018-04-09 05:53:04 +00:00
2019-03-26 16:41:28 +00:00
fn get_property(&self, _obj: &glib::Object, id: usize) -> Result<glib::Value, ()> {
let prop = &PROPERTIES[id];
2018-09-18 09:53:12 +00:00
2019-03-26 16:41:28 +00:00
match *prop {
subclass::Property("ndi-name", ..) => {
let settings = self.settings.lock().unwrap();
Ok(settings.ndi_name.to_value())
}
subclass::Property("url-address", ..) => {
let settings = self.settings.lock().unwrap();
Ok(settings.url_address.to_value())
}
subclass::Property("receiver-ndi-name", ..) => {
let settings = self.settings.lock().unwrap();
Ok(settings.receiver_ndi_name.to_value())
}
subclass::Property("connect-timeout", ..) => {
2019-03-26 16:41:28 +00:00
let settings = self.settings.lock().unwrap();
Ok(settings.connect_timeout.to_value())
2019-03-26 16:41:28 +00:00
}
subclass::Property("timeout", ..) => {
2019-03-26 16:41:28 +00:00
let settings = self.settings.lock().unwrap();
Ok(settings.timeout.to_value())
2019-03-26 16:41:28 +00:00
}
subclass::Property("bandwidth", ..) => {
2019-03-26 16:41:28 +00:00
let settings = self.settings.lock().unwrap();
Ok(settings.bandwidth.to_value())
}
subclass::Property("timestamp-mode", ..) => {
let settings = self.settings.lock().unwrap();
Ok(settings.timestamp_mode.to_value())
}
2019-03-26 16:41:28 +00:00
_ => unimplemented!(),
2018-04-09 05:53:04 +00:00
}
}
2019-03-26 16:41:28 +00:00
}
2018-04-09 05:53:04 +00:00
impl ElementImpl for NdiAudioSrc {
fn change_state(
&self,
element: &gst::Element,
transition: gst::StateChange,
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
match transition {
gst::StateChange::PausedToPlaying => {
if let Some(ref controller) = *self.receiver_controller.lock().unwrap() {
controller.set_playing(true);
}
}
gst::StateChange::PlayingToPaused => {
if let Some(ref controller) = *self.receiver_controller.lock().unwrap() {
controller.set_playing(false);
}
}
gst::StateChange::PausedToReady => {
if let Some(ref controller) = *self.receiver_controller.lock().unwrap() {
controller.shutdown();
}
}
_ => (),
}
self.parent_change_state(element, transition)
}
}
2018-04-09 05:53:04 +00:00
2019-03-26 16:41:28 +00:00
impl BaseSrcImpl for NdiAudioSrc {
fn negotiate(&self, _element: &gst_base::BaseSrc) -> Result<(), gst::LoggableError> {
// Always succeed here without doing anything: we will set the caps once we received a
// buffer, there's nothing we can negotiate
Ok(())
}
fn unlock(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> {
gst_debug!(self.cat, obj: element, "Unlocking",);
if let Some(ref controller) = *self.receiver_controller.lock().unwrap() {
controller.set_flushing(true);
}
Ok(())
}
fn unlock_stop(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> {
gst_debug!(self.cat, obj: element, "Stop unlocking",);
if let Some(ref controller) = *self.receiver_controller.lock().unwrap() {
controller.set_flushing(false);
}
Ok(())
}
2019-03-26 16:41:28 +00:00
fn start(&self, element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> {
*self.state.lock().unwrap() = Default::default();
let settings = self.settings.lock().unwrap().clone();
if settings.ndi_name.is_none() && settings.url_address.is_none() {
return Err(gst_error_msg!(
gst::LibraryError::Settings,
["No NDI name or URL/address given"]
));
}
let receiver = connect_ndi(
2019-03-26 16:41:28 +00:00
self.cat,
element,
settings.ndi_name.as_ref().map(String::as_str),
settings.url_address.as_ref().map(String::as_str),
&settings.receiver_ndi_name,
settings.connect_timeout,
settings.bandwidth,
settings.timestamp_mode,
settings.timeout,
2019-03-26 16:41:28 +00:00
);
2018-04-09 05:53:04 +00:00
// settings.id_receiver exists
match receiver {
None => Err(gst_error_msg!(
gst::ResourceError::NotFound,
2019-02-28 11:13:40 +00:00
["Could not connect to this source"]
)),
Some(receiver) => {
*self.receiver_controller.lock().unwrap() =
Some(receiver.receiver_control_handle());
let mut state = self.state.lock().unwrap();
state.receiver = Some(receiver);
Ok(())
}
2018-12-11 16:47:03 +00:00
}
2019-03-26 16:41:28 +00:00
}
fn stop(&self, _element: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> {
if let Some(ref controller) = self.receiver_controller.lock().unwrap().take() {
controller.shutdown();
}
*self.state.lock().unwrap() = State::default();
2019-03-26 16:41:28 +00:00
Ok(())
}
fn query(&self, element: &gst_base::BaseSrc, query: &mut gst::QueryRef) -> bool {
use gst::QueryView;
match query.view_mut() {
QueryView::Scheduling(ref mut q) => {
q.set(gst::SchedulingFlags::SEQUENTIAL, 1, -1, 0);
q.add_scheduling_modes(&[gst::PadMode::Push]);
true
2018-09-24 13:46:36 +00:00
}
QueryView::Latency(ref mut q) => {
let state = self.state.lock().unwrap();
let settings = self.settings.lock().unwrap();
if state.current_latency.is_some() {
let min = if settings.timestamp_mode != TimestampMode::Timecode {
state.current_latency
} else {
0.into()
};
let max = 5 * state.current_latency;
gst_debug!(
self.cat,
obj: element,
"Returning latency min {} max {}",
min,
max
);
q.set(true, min, max);
true
} else {
false
}
}
_ => BaseSrcImplExt::parent_query(self, element, query),
2018-09-24 13:46:36 +00:00
}
2019-03-26 16:41:28 +00:00
}
2018-07-02 12:07:51 +00:00
2020-07-27 13:08:56 +00:00
fn fixate(&self, element: &gst_base::BaseSrc, mut caps: gst::Caps) -> gst::Caps {
caps.truncate();
2019-03-26 16:41:28 +00:00
{
let caps = caps.make_mut();
let s = caps.get_mut_structure(0).unwrap();
s.fixate_field_nearest_int("rate", 48_000);
s.fixate_field_nearest_int("channels", 2);
2019-03-26 16:41:28 +00:00
}
2019-03-26 16:41:28 +00:00
self.parent_fixate(element, caps)
}
2019-03-26 16:41:28 +00:00
fn create(
&self,
element: &gst_base::BaseSrc,
_offset: u64,
2020-07-27 13:08:56 +00:00
_buffer: Option<&mut gst::BufferRef>,
2019-03-26 16:41:28 +00:00
_length: u32,
2020-07-27 13:08:56 +00:00
) -> Result<CreateSuccess, gst::FlowError> {
let recv = {
let mut state = self.state.lock().unwrap();
match state.receiver.take() {
Some(recv) => recv,
None => {
gst_error!(self.cat, obj: element, "Have no receiver");
return Err(gst::FlowError::Error);
}
}
};
match recv.capture() {
ReceiverItem::Buffer(buffer, info) => {
let mut state = self.state.lock().unwrap();
state.receiver = Some(recv);
if state.info.as_ref() != Some(&info) {
let caps = info.to_caps().map_err(|_| {
gst_element_error!(
element,
gst::ResourceError::Settings,
["Invalid audio info received: {:?}", info]
);
gst::FlowError::NotNegotiated
})?;
2020-01-02 11:06:53 +00:00
state.info = Some(info);
state.current_latency = buffer.get_duration();
drop(state);
gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps);
element.set_caps(&caps).map_err(|_| {
gst_element_error!(
element,
gst::CoreError::Negotiation,
["Failed to negotiate caps: {:?}", caps]
);
gst::FlowError::NotNegotiated
})?;
2020-07-27 13:08:56 +00:00
let _ =
element.post_message(gst::message::Latency::builder().src(element).build());
}
2020-07-27 13:08:56 +00:00
Ok(CreateSuccess::NewBuffer(buffer))
}
ReceiverItem::Flushing => Err(gst::FlowError::Flushing),
ReceiverItem::Timeout => Err(gst::FlowError::Eos),
ReceiverItem::Error(err) => Err(err),
2018-05-31 09:16:29 +00:00
}
2018-09-18 09:53:12 +00:00
}
2019-03-26 16:41:28 +00:00
}
2018-09-18 09:53:12 +00:00
2019-03-26 16:41:28 +00:00
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
gst::Element::register(
Some(plugin),
"ndiaudiosrc",
gst::Rank::None,
NdiAudioSrc::get_type(),
)
2019-03-26 16:41:28 +00:00
}