1use std::{
4 ffi::CString,
5 fmt::Debug,
6 ptr::NonNull,
7 sync::{Arc, Mutex},
8 time::Duration,
9};
10
11use static_assertions::assert_impl_all;
12
13use crate::{
14 bindings,
15 blocking_update::BlockingUpdate,
16 enums::NDIRecvError,
17 frame::{
18 audio::AudioFrame,
19 generic::{AsFFIReadable, AsFFIWritable, FFIReadablePtrError},
20 metadata::MetadataFrame,
21 video::VideoFrame,
22 },
23 source::{NDISourceLike, NDISourceRef},
24 tally::Tally,
25 util::{SourceNameError, duration_to_ms, validate_source_name},
26};
27
28#[non_exhaustive]
30#[derive(Debug, Clone)]
31pub struct NDISenderBuilder {
32 pub name: Option<CString>,
33 pub groups: Option<CString>,
34 pub clock_video: bool,
35 pub clock_audio: bool,
36}
37
38#[allow(clippy::derivable_impls)]
39impl Default for NDISenderBuilder {
40 fn default() -> Self {
41 Self {
42 name: None,
43 groups: None,
44 clock_video: false,
45 clock_audio: false,
46 }
47 }
48}
49
50impl NDISenderBuilder {
51 pub fn new() -> Self {
52 Self::default()
53 }
54
55 pub fn name(mut self, name: &str) -> Result<Self, SourceNameError> {
61 self.name = Some(validate_source_name(name)?);
62 Ok(self)
63 }
64
65 pub fn groups(mut self, groups: &str) -> Self {
74 self.groups = Some(CString::new(groups).unwrap());
75 self
76 }
77
78 pub fn clock_video(mut self, clock_video: bool) -> Self {
82 self.clock_video = clock_video;
83 self
84 }
85
86 pub fn clock_audio(mut self, clock_audio: bool) -> Self {
90 self.clock_audio = clock_audio;
91 self
92 }
93}
94
95impl NDISenderBuilder {
96 pub fn build(self) -> Result<NDISender, NDISenderBuilderError> {
97 let options = bindings::NDIlib_send_create_t {
98 p_ndi_name: self.name.as_ref().map_or(std::ptr::null(), |s| s.as_ptr()),
99 p_groups: self
100 .groups
101 .as_ref()
102 .map_or(std::ptr::null(), |s| s.as_ptr()),
103 clock_video: self.clock_video,
104 clock_audio: self.clock_audio,
105 };
106
107 let handle = unsafe { bindings::NDIlib_send_create(&options) };
108
109 if let Some(handle) = NonNull::new(handle) {
110 Ok(NDISender {
111 handle: Arc::new(RawSender { handle }),
112 in_transmission: Mutex::new(None),
113 })
114 } else {
115 Err(NDISenderBuilderError::CreationFailed)
116 }
117 }
118}
119
120#[non_exhaustive]
121#[derive(Debug, Clone, Copy, PartialEq, Eq)]
122pub enum NDISenderBuilderError {
123 CreationFailed,
124}
125
126#[derive(PartialEq, Eq)]
127pub(crate) struct RawSender {
128 handle: NonNull<bindings::NDIlib_send_instance_type>,
129}
130impl RawSender {
131 pub(crate) fn raw_ptr(&self) -> bindings::NDIlib_send_instance_t {
132 self.handle.as_ptr()
133 }
134}
135
136impl Drop for RawSender {
137 fn drop(&mut self) {
138 unsafe { bindings::NDIlib_send_destroy(self.raw_ptr()) };
139 }
140}
141
142impl Debug for RawSender {
143 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144 f.debug_struct("RawSender")
145 .field("raw_ptr", &self.raw_ptr())
146 .finish()
147 }
148}
149
150unsafe impl Send for RawSender {}
151unsafe impl Sync for RawSender {}
152
153pub struct NDISender {
160 handle: Arc<RawSender>,
161 in_transmission: Mutex<Option<Arc<VideoFrame>>>,
162}
163
164assert_impl_all!(
165 NDISender: Send, Sync,
166);
167
168impl NDISender {
169 pub fn send_video_sync(&self, frame: &VideoFrame) -> Result<(), SendFrameError> {
173 let ptr = frame.to_ffi_send_frame_ptr().map_err(|err| match err {
174 crate::frame::generic::FFIReadablePtrError::NotReadable(desc) => {
175 SendFrameError::NotSendable(desc)
176 }
177 })?;
178
179 unsafe {
180 bindings::NDIlib_send_send_video_v2(self.handle.raw_ptr(), ptr);
181 self.write_in_transmission(None);
182 }
183
184 Ok(())
185 }
186
187 unsafe fn write_in_transmission(&self, frame: Option<Arc<VideoFrame>>) {
196 match self.in_transmission.lock() {
197 Ok(mut guard) => {
198 *guard = frame;
199 }
200 Err(mut e) => {
201 **e.get_mut() = frame;
202 self.in_transmission.clear_poison();
203 }
204 }
205 }
206
207 pub fn send_video_async(&self, frame: Arc<VideoFrame>) -> Result<(), SendFrameError> {
228 let ptr = frame.to_ffi_send_frame_ptr().map_err(|err| match err {
229 FFIReadablePtrError::NotReadable(desc) => SendFrameError::NotSendable(desc),
230 })?;
231
232 unsafe {
233 bindings::NDIlib_send_send_video_async_v2(self.handle.raw_ptr(), ptr);
234 self.write_in_transmission(Some(frame));
235 }
236
237 Ok(())
238 }
239
240 pub fn flush_async_video(&self) {
242 unsafe {
243 bindings::NDIlib_send_send_video_async_v2(self.handle.raw_ptr(), std::ptr::null_mut());
244 self.write_in_transmission(None);
245 }
246 }
247
248 pub fn send_audio(&self, frame: &AudioFrame) -> Result<(), SendFrameError> {
249 let ptr = frame.to_ffi_send_frame_ptr().map_err(|err| match err {
250 FFIReadablePtrError::NotReadable(desc) => SendFrameError::NotSendable(desc),
251 })?;
252
253 unsafe {
254 bindings::NDIlib_send_send_audio_v3(self.handle.raw_ptr(), ptr);
255 }
256
257 Ok(())
258 }
259
260 pub fn send_metadata(&self, frame: &MetadataFrame) -> Result<(), SendFrameError> {
261 let ptr = frame.to_ffi_send_frame_ptr().map_err(|err| match err {
262 FFIReadablePtrError::NotReadable(desc) => SendFrameError::NotSendable(desc),
263 })?;
264
265 unsafe {
266 bindings::NDIlib_send_send_metadata(self.handle.raw_ptr(), ptr);
267 }
268
269 Ok(())
270 }
271
272 pub fn recv_metadata(
274 &self,
275 meta: &mut MetadataFrame,
276 timeout: Duration,
277 ) -> Result<Option<()>, NDIRecvError> {
278 let ptr = meta.to_ffi_recv_frame_ptr();
279
280 if ptr.is_null() {
281 eprintln!(
282 "[Warning] The frame given to NDISender::recv_metadata is not writable, ignoring"
283 );
284 return Ok(None);
285 }
286
287 let timeout: u32 = timeout.as_millis().try_into().unwrap_or(u32::MAX);
288
289 let recv_type =
290 unsafe { bindings::NDIlib_send_capture(self.handle.raw_ptr(), ptr, timeout) };
291
292 match recv_type {
293 bindings::NDIlib_frame_type_e_NDIlib_frame_type_video
294 | bindings::NDIlib_frame_type_e_NDIlib_frame_type_audio => {
295 panic!("[Fatal FFI Error] Invalid enum discriminant");
296 }
297 bindings::NDIlib_frame_type_e_NDIlib_frame_type_metadata => {
298 meta.alloc.update_from_sender(self.handle.clone());
299 Ok(Some(()))
300 }
301 bindings::NDIlib_frame_type_e_NDIlib_frame_type_none => Ok(None),
302 discriminant => {
303 eprintln!("NDI SDK returned an unknown frame type: {:?}", discriminant);
304
305 meta.assert_unwritten();
306
307 Err(NDIRecvError::UnknownType)
308 }
309 }
310 }
311
312 pub fn get_tally(&self) -> Tally {
314 let mut tally = bindings::NDIlib_tally_t {
315 on_program: false,
316 on_preview: false,
317 };
318
319 unsafe { bindings::NDIlib_send_get_tally(self.handle.raw_ptr(), &mut tally, 0) };
320
321 Tally::from_ffi(&tally)
322 }
323
324 pub fn get_tally_update(&self, timeout: Duration) -> BlockingUpdate<Tally> {
326 let timeout: u32 = duration_to_ms(timeout);
327
328 let mut tally = bindings::NDIlib_tally_t {
329 on_program: false,
330 on_preview: false,
331 };
332
333 let changed = unsafe {
334 bindings::NDIlib_send_get_tally(self.handle.raw_ptr(), &mut tally, timeout)
339 };
340
341 BlockingUpdate::new(Tally::from_ffi(&tally), changed)
342 }
343
344 pub fn get_num_connections_update(&self, timeout: Duration) -> usize {
346 let timeout: u32 = duration_to_ms(timeout);
347
348 let no_conns =
349 unsafe { bindings::NDIlib_send_get_no_connections(self.handle.raw_ptr(), timeout) };
350
351 no_conns
352 .try_into()
353 .expect("[Fatal FFI Error] NDI SDK returned an invalid number of connections")
354 }
355
356 pub fn set_failover(&self, failover_src: impl NDISourceLike) {
360 failover_src.with_descriptor(|src_ptr| {
361 unsafe { bindings::NDIlib_send_set_failover(self.handle.raw_ptr(), src_ptr) };
362 });
363 }
364
365 pub fn get_source<'a>(&'a self) -> NDISourceRef<'a> {
367 let source = unsafe { bindings::NDIlib_send_get_source_name(self.handle.raw_ptr()) };
368
369 unsafe {
370 NDISourceRef::from(
371 *source
372 .as_ref()
373 .expect("[Fatal FFI Error] NDI SDK returned nullptr for source descriptor"),
374 )
375 }
376 }
377
378 pub fn add_connection_metadata(&self, meta: &MetadataFrame) -> Result<(), SendFrameError> {
380 let ptr = meta.to_ffi_send_frame_ptr().map_err(|err| match err {
381 crate::frame::generic::FFIReadablePtrError::NotReadable(desc) => {
382 SendFrameError::NotSendable(desc)
383 }
384 })?;
385
386 unsafe {
387 bindings::NDIlib_send_add_connection_metadata(self.handle.raw_ptr(), ptr);
388 }
389
390 Ok(())
391 }
392
393 pub fn clear_connection_metadata(&self) {
395 unsafe { bindings::NDIlib_send_clear_connection_metadata(self.handle.raw_ptr()) };
396 }
397}
398
399impl Drop for NDISender {
400 fn drop(&mut self) {
401 self.flush_async_video();
404 }
405}
406
407#[derive(Debug, PartialEq, Eq)]
408pub enum SendFrameError {
409 NotSendable(&'static str),
410}