ndi: Refactor frame structs to have static lifetimes

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1365>
This commit is contained in:
Sebastian Dröge 2023-10-18 19:04:45 +03:00
parent eb137ec6dc
commit 99d7cce0d6
2 changed files with 215 additions and 195 deletions

View file

@ -6,6 +6,7 @@ use std::ffi;
use std::fmt;
use std::mem;
use std::ptr;
use std::sync::Arc;
use byte_slice_cast::*;
@ -247,14 +248,32 @@ impl<'a> RecvBuilder<'a> {
if ptr.is_null() {
None
} else {
Some(RecvInstance(ptr::NonNull::new_unchecked(ptr)))
#[allow(clippy::arc_with_non_send_sync)]
Some(RecvInstance(Arc::new(RecvInstancePtr(
ptr::NonNull::new_unchecked(ptr),
))))
}
}
}
}
#[derive(Debug, Clone)]
struct RecvInstancePtr(ptr::NonNull<::std::os::raw::c_void>);
impl Drop for RecvInstancePtr {
fn drop(&mut self) {
unsafe { NDIlib_recv_destroy(self.0.as_ptr() as *mut _) }
}
}
impl RecvInstancePtr {
fn as_ptr(&self) -> *mut ::std::os::raw::c_void {
self.0.as_ptr()
}
}
#[derive(Debug)]
pub struct RecvInstance(ptr::NonNull<::std::os::raw::c_void>);
pub struct RecvInstance(Arc<RecvInstancePtr>);
unsafe impl Send for RecvInstance {}
@ -318,15 +337,17 @@ impl RecvInstance {
);
match res {
NDIlib_frame_type_e::NDIlib_frame_type_audio => Ok(Some(Frame::Audio(
AudioFrame::BorrowedRecv(audio_frame, self),
))),
NDIlib_frame_type_e::NDIlib_frame_type_video => Ok(Some(Frame::Video(
VideoFrame::BorrowedRecv(video_frame, self),
))),
NDIlib_frame_type_e::NDIlib_frame_type_metadata => Ok(Some(Frame::Metadata(
MetadataFrame::Borrowed(metadata_frame, self),
))),
NDIlib_frame_type_e::NDIlib_frame_type_audio => Ok(Some(Frame::Audio(AudioFrame(
AudioFrameInner::BorrowedRecv(audio_frame, Arc::clone(&self.0)),
)))),
NDIlib_frame_type_e::NDIlib_frame_type_video => Ok(Some(Frame::Video(VideoFrame(
VideoFrameInner::BorrowedRecv(video_frame, Arc::clone(&self.0)),
)))),
NDIlib_frame_type_e::NDIlib_frame_type_metadata => {
Ok(Some(Frame::Metadata(MetadataFrame(
MetadataFrameInner::Borrowed(metadata_frame, Arc::clone(&self.0)),
))))
}
NDIlib_frame_type_e::NDIlib_frame_type_error => Err(ReceiveError),
_ => Ok(None),
}
@ -334,12 +355,6 @@ impl RecvInstance {
}
}
impl Drop for RecvInstance {
fn drop(&mut self) {
unsafe { NDIlib_recv_destroy(self.0.as_ptr() as *mut _) }
}
}
#[derive(Debug)]
pub struct SendBuilder<'a> {
ndi_name: &'a str,
@ -420,7 +435,6 @@ impl Drop for SendInstance {
#[derive(Debug)]
pub struct Tally(NDIlib_tally_t);
unsafe impl Send for Tally {}
impl Default for Tally {
fn default() -> Self {
@ -449,23 +463,40 @@ impl Tally {
}
#[derive(Debug)]
pub enum Frame<'a> {
Video(VideoFrame<'a>),
Audio(AudioFrame<'a>),
Metadata(MetadataFrame<'a>),
pub enum Frame {
Video(VideoFrame),
Audio(AudioFrame),
Metadata(MetadataFrame),
}
#[derive(Debug)]
pub enum VideoFrame<'a> {
pub struct VideoFrame(VideoFrameInner);
#[derive(Debug)]
enum VideoFrameInner {
//Owned(NDIlib_video_frame_v2_t, Option<ffi::CString>, Option<Vec<u8>>),
BorrowedRecv(NDIlib_video_frame_v2_t, &'a RecvInstance),
BorrowedRecv(NDIlib_video_frame_v2_t, Arc<RecvInstancePtr>),
BorrowedGst(
NDIlib_video_frame_v2_t,
&'a gst_video::VideoFrameRef<&'a gst::BufferRef>,
Option<&'a std::ffi::CStr>,
gst_video::VideoFrame<gst_video::video_frame::Readable>,
Option<std::ffi::CString>,
),
}
impl Drop for VideoFrameInner {
#[allow(irrefutable_let_patterns)]
fn drop(&mut self) {
if let VideoFrameInner::BorrowedRecv(ref mut frame, ref recv) = *self {
unsafe {
NDIlib_recv_free_video_v2(recv.0.as_ptr() as *mut _, frame);
}
}
}
}
unsafe impl Send for VideoFrameInner {}
unsafe impl Sync for VideoFrameInner {}
#[derive(Debug, Copy, Clone)]
pub struct TryFromVideoFrameError;
@ -477,60 +508,55 @@ impl fmt::Display for TryFromVideoFrameError {
impl std::error::Error for TryFromVideoFrameError {}
impl<'a> VideoFrame<'a> {
impl VideoFrame {
pub fn xres(&self) -> i32 {
match self {
VideoFrame::BorrowedRecv(ref frame, _) | VideoFrame::BorrowedGst(ref frame, ..) => {
frame.xres
}
match self.0 {
VideoFrameInner::BorrowedRecv(ref frame, _)
| VideoFrameInner::BorrowedGst(ref frame, ..) => frame.xres,
}
}
pub fn yres(&self) -> i32 {
match self {
VideoFrame::BorrowedRecv(ref frame, _) | VideoFrame::BorrowedGst(ref frame, ..) => {
frame.yres
}
match self.0 {
VideoFrameInner::BorrowedRecv(ref frame, _)
| VideoFrameInner::BorrowedGst(ref frame, ..) => frame.yres,
}
}
pub fn fourcc(&self) -> NDIlib_FourCC_video_type_e {
match self {
VideoFrame::BorrowedRecv(ref frame, _) | VideoFrame::BorrowedGst(ref frame, ..) => {
frame.FourCC
}
match self.0 {
VideoFrameInner::BorrowedRecv(ref frame, _)
| VideoFrameInner::BorrowedGst(ref frame, ..) => frame.FourCC,
}
}
pub fn frame_rate(&self) -> (i32, i32) {
match self {
VideoFrame::BorrowedRecv(ref frame, _) | VideoFrame::BorrowedGst(ref frame, ..) => {
match self.0 {
VideoFrameInner::BorrowedRecv(ref frame, _)
| VideoFrameInner::BorrowedGst(ref frame, ..) => {
(frame.frame_rate_N, frame.frame_rate_D)
}
}
}
pub fn picture_aspect_ratio(&self) -> f32 {
match self {
VideoFrame::BorrowedRecv(ref frame, _) | VideoFrame::BorrowedGst(ref frame, ..) => {
frame.picture_aspect_ratio
}
match self.0 {
VideoFrameInner::BorrowedRecv(ref frame, _)
| VideoFrameInner::BorrowedGst(ref frame, ..) => frame.picture_aspect_ratio,
}
}
pub fn frame_format_type(&self) -> NDIlib_frame_format_type_e {
match self {
VideoFrame::BorrowedRecv(ref frame, _) | VideoFrame::BorrowedGst(ref frame, ..) => {
frame.frame_format_type
}
match self.0 {
VideoFrameInner::BorrowedRecv(ref frame, _)
| VideoFrameInner::BorrowedGst(ref frame, ..) => frame.frame_format_type,
}
}
pub fn timecode(&self) -> i64 {
match self {
VideoFrame::BorrowedRecv(ref frame, _) | VideoFrame::BorrowedGst(ref frame, ..) => {
frame.timecode
}
match self.0 {
VideoFrameInner::BorrowedRecv(ref frame, _)
| VideoFrameInner::BorrowedGst(ref frame, ..) => frame.timecode,
}
}
@ -566,9 +592,9 @@ impl<'a> VideoFrame<'a> {
return unsafe {
use std::slice;
match self {
VideoFrame::BorrowedRecv(ref frame, ..)
| VideoFrame::BorrowedGst(ref frame, ..) => Some(slice::from_raw_parts(
match self.0 {
VideoFrameInner::BorrowedRecv(ref frame, ..)
| VideoFrameInner::BorrowedGst(ref frame, ..) => Some(slice::from_raw_parts(
frame.p_data as *const u8,
frame_size as usize,
)),
@ -589,9 +615,9 @@ impl<'a> VideoFrame<'a> {
{
return unsafe {
use std::slice;
match self {
VideoFrame::BorrowedRecv(ref frame, ..)
| VideoFrame::BorrowedGst(ref frame, ..) => Some(slice::from_raw_parts(
match self.0 {
VideoFrameInner::BorrowedRecv(ref frame, ..)
| VideoFrameInner::BorrowedGst(ref frame, ..) => Some(slice::from_raw_parts(
frame.p_data as *const u8,
frame.line_stride_or_data_size_in_bytes as usize,
)),
@ -626,9 +652,9 @@ impl<'a> VideoFrame<'a> {
return None;
}
let data = match self {
VideoFrame::BorrowedRecv(ref frame, ..)
| VideoFrame::BorrowedGst(ref frame, ..) => slice::from_raw_parts(
let data = match self.0 {
VideoFrameInner::BorrowedRecv(ref frame, ..)
| VideoFrameInner::BorrowedGst(ref frame, ..) => slice::from_raw_parts(
frame.p_data as *const u8,
frame.line_stride_or_data_size_in_bytes as usize,
),
@ -675,8 +701,9 @@ impl<'a> VideoFrame<'a> {
}
pub fn line_stride_or_data_size_in_bytes(&self) -> i32 {
match self {
VideoFrame::BorrowedRecv(ref frame, _) | VideoFrame::BorrowedGst(ref frame, ..) => {
match self.0 {
VideoFrameInner::BorrowedRecv(ref frame, _)
| VideoFrameInner::BorrowedGst(ref frame, ..) => {
let stride = frame.line_stride_or_data_size_in_bytes;
if stride != 0 {
@ -705,8 +732,9 @@ impl<'a> VideoFrame<'a> {
pub fn metadata(&self) -> Option<&str> {
unsafe {
match self {
VideoFrame::BorrowedRecv(ref frame, _) | VideoFrame::BorrowedGst(ref frame, ..) => {
match self.0 {
VideoFrameInner::BorrowedRecv(ref frame, _)
| VideoFrameInner::BorrowedGst(ref frame, ..) => {
if frame.p_metadata.is_null() {
None
} else {
@ -718,24 +746,22 @@ impl<'a> VideoFrame<'a> {
}
pub fn timestamp(&self) -> i64 {
match self {
VideoFrame::BorrowedRecv(ref frame, _) | VideoFrame::BorrowedGst(ref frame, ..) => {
frame.timestamp
}
match self.0 {
VideoFrameInner::BorrowedRecv(ref frame, _)
| VideoFrameInner::BorrowedGst(ref frame, ..) => frame.timestamp,
}
}
pub fn as_ptr(&self) -> *const NDIlib_video_frame_v2_t {
match self {
VideoFrame::BorrowedRecv(ref frame, _) | VideoFrame::BorrowedGst(ref frame, ..) => {
frame
}
match self.0 {
VideoFrameInner::BorrowedRecv(ref frame, _)
| VideoFrameInner::BorrowedGst(ref frame, ..) => frame,
}
}
pub fn try_from_video_frame(
frame: &'a gst_video::VideoFrameRef<&'a gst::BufferRef>,
metadata: Option<&'a std::ffi::CStr>,
frame: gst_video::VideoFrame<gst_video::video_frame::Readable>,
metadata: Option<std::ffi::CString>,
timecode: i64,
) -> Result<Self, TryFromVideoFrameError> {
// Planar formats must be in contiguous memory
@ -841,35 +867,43 @@ impl<'a> VideoFrame<'a> {
timecode,
p_data: frame.plane_data(0).unwrap().as_ptr() as *const ::std::os::raw::c_char,
line_stride_or_data_size_in_bytes: frame.plane_stride()[0],
p_metadata: metadata.map_or(ptr::null(), |meta| meta.as_ptr()),
p_metadata: metadata.as_ref().map_or(ptr::null(), |meta| meta.as_ptr()),
timestamp: 0,
};
Ok(VideoFrame::BorrowedGst(ndi_frame, frame, metadata))
}
}
impl<'a> Drop for VideoFrame<'a> {
#[allow(irrefutable_let_patterns)]
fn drop(&mut self) {
if let VideoFrame::BorrowedRecv(ref mut frame, recv) = *self {
unsafe {
NDIlib_recv_free_video_v2(recv.0.as_ptr() as *mut _, frame);
}
}
Ok(VideoFrame(VideoFrameInner::BorrowedGst(
ndi_frame, frame, metadata,
)))
}
}
#[derive(Debug)]
pub enum AudioFrame<'a> {
pub struct AudioFrame(AudioFrameInner);
#[derive(Debug)]
enum AudioFrameInner {
Owned(
NDIlib_audio_frame_v3_t,
Option<ffi::CString>,
Option<Vec<f32>>,
),
BorrowedRecv(NDIlib_audio_frame_v3_t, &'a RecvInstance),
BorrowedRecv(NDIlib_audio_frame_v3_t, Arc<RecvInstancePtr>),
}
impl Drop for AudioFrameInner {
#[allow(irrefutable_let_patterns)]
fn drop(&mut self) {
if let AudioFrameInner::BorrowedRecv(ref mut frame, ref recv) = *self {
unsafe {
NDIlib_recv_free_audio_v3(recv.0.as_ptr() as *mut _, frame);
}
}
}
}
unsafe impl Send for AudioFrameInner {}
unsafe impl Sync for AudioFrameInner {}
#[derive(Debug, Copy, Clone)]
pub struct TryFromAudioBufferError;
@ -881,44 +915,39 @@ impl fmt::Display for TryFromAudioBufferError {
impl std::error::Error for TryFromAudioBufferError {}
impl<'a> AudioFrame<'a> {
impl AudioFrame {
pub fn sample_rate(&self) -> i32 {
match self {
AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => {
frame.sample_rate
}
match self.0 {
AudioFrameInner::BorrowedRecv(ref frame, _)
| AudioFrameInner::Owned(ref frame, _, _) => frame.sample_rate,
}
}
pub fn no_channels(&self) -> i32 {
match self {
AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => {
frame.no_channels
}
match self.0 {
AudioFrameInner::BorrowedRecv(ref frame, _)
| AudioFrameInner::Owned(ref frame, _, _) => frame.no_channels,
}
}
pub fn no_samples(&self) -> i32 {
match self {
AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => {
frame.no_samples
}
match self.0 {
AudioFrameInner::BorrowedRecv(ref frame, _)
| AudioFrameInner::Owned(ref frame, _, _) => frame.no_samples,
}
}
pub fn timecode(&self) -> i64 {
match self {
AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => {
frame.timecode
}
match self.0 {
AudioFrameInner::BorrowedRecv(ref frame, _)
| AudioFrameInner::Owned(ref frame, _, _) => frame.timecode,
}
}
pub fn fourcc(&self) -> NDIlib_FourCC_audio_type_e {
match self {
AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => {
frame.FourCC
}
match self.0 {
AudioFrameInner::BorrowedRecv(ref frame, _)
| AudioFrameInner::Owned(ref frame, _, _) => frame.FourCC,
}
}
@ -929,26 +958,23 @@ impl<'a> AudioFrame<'a> {
let fourcc = self.fourcc();
if [NDIlib_FourCC_audio_type_FLTp].contains(&fourcc) {
return match self {
AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => {
Some(slice::from_raw_parts(
frame.p_data as *const u8,
(frame.no_channels * frame.channel_stride_or_data_size_in_bytes)
as usize,
))
}
return match self.0 {
AudioFrameInner::BorrowedRecv(ref frame, _)
| AudioFrameInner::Owned(ref frame, _, _) => Some(slice::from_raw_parts(
frame.p_data as *const u8,
(frame.no_channels * frame.channel_stride_or_data_size_in_bytes) as usize,
)),
};
}
#[cfg(feature = "advanced-sdk")]
if [NDIlib_FourCC_audio_type_Opus].contains(&fourcc) {
return match self {
AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => {
Some(slice::from_raw_parts(
frame.p_data as *const u8,
frame.channel_stride_or_data_size_in_bytes as usize,
))
}
return match self.0 {
AudioFrameInner::BorrowedRecv(ref frame, _)
| AudioFrameInner::Owned(ref frame, _, _) => Some(slice::from_raw_parts(
frame.p_data as *const u8,
frame.channel_stride_or_data_size_in_bytes as usize,
)),
};
}
@ -969,13 +995,12 @@ impl<'a> AudioFrame<'a> {
return None;
}
let data = match self {
AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => {
slice::from_raw_parts(
frame.p_data as *const u8,
frame.channel_stride_or_data_size_in_bytes as usize,
)
}
let data = match self.0 {
AudioFrameInner::BorrowedRecv(ref frame, _)
| AudioFrameInner::Owned(ref frame, _, _) => slice::from_raw_parts(
frame.p_data as *const u8,
frame.channel_stride_or_data_size_in_bytes as usize,
),
};
let mut cursor = Cursor::new(data);
@ -1019,17 +1044,17 @@ impl<'a> AudioFrame<'a> {
}
pub fn channel_stride_or_data_size_in_bytes(&self) -> i32 {
match self {
AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => {
frame.channel_stride_or_data_size_in_bytes
}
match self.0 {
AudioFrameInner::BorrowedRecv(ref frame, _)
| AudioFrameInner::Owned(ref frame, _, _) => frame.channel_stride_or_data_size_in_bytes,
}
}
pub fn metadata(&self) -> Option<&str> {
unsafe {
match self {
AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => {
match self.0 {
AudioFrameInner::BorrowedRecv(ref frame, _)
| AudioFrameInner::Owned(ref frame, _, _) => {
if frame.p_metadata.is_null() {
None
} else {
@ -1041,16 +1066,16 @@ impl<'a> AudioFrame<'a> {
}
pub fn timestamp(&self) -> i64 {
match self {
AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => {
frame.timestamp
}
match self.0 {
AudioFrameInner::BorrowedRecv(ref frame, _)
| AudioFrameInner::Owned(ref frame, _, _) => frame.timestamp,
}
}
pub fn as_ptr(&self) -> *const NDIlib_audio_frame_v3_t {
match self {
AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => frame,
match self.0 {
AudioFrameInner::BorrowedRecv(ref frame, _)
| AudioFrameInner::Owned(ref frame, _, _) => frame,
}
}
@ -1099,18 +1124,11 @@ impl<'a> AudioFrame<'a> {
timestamp: 0,
};
Ok(AudioFrame::Owned(dest, None, Some(dest_data)))
}
}
impl<'a> Drop for AudioFrame<'a> {
#[allow(irrefutable_let_patterns)]
fn drop(&mut self) {
if let AudioFrame::BorrowedRecv(ref mut frame, recv) = *self {
unsafe {
NDIlib_recv_free_audio_v3(recv.0.as_ptr() as *mut _, frame);
}
}
Ok(AudioFrame(AudioFrameInner::Owned(
dest,
None,
Some(dest_data),
)))
}
}
@ -1125,16 +1143,32 @@ pub struct CompressedPacket<'a> {
}
#[derive(Debug)]
pub enum MetadataFrame<'a> {
pub struct MetadataFrame(MetadataFrameInner);
#[derive(Debug)]
enum MetadataFrameInner {
Owned(NDIlib_metadata_frame_t, Option<ffi::CString>),
Borrowed(NDIlib_metadata_frame_t, &'a RecvInstance),
Borrowed(NDIlib_metadata_frame_t, Arc<RecvInstancePtr>),
}
impl<'a> MetadataFrame<'a> {
impl Drop for MetadataFrameInner {
fn drop(&mut self) {
if let MetadataFrameInner::Borrowed(ref mut frame, ref recv) = *self {
unsafe {
NDIlib_recv_free_metadata(recv.0.as_ptr() as *mut _, frame);
}
}
}
}
unsafe impl Send for MetadataFrameInner {}
unsafe impl Sync for MetadataFrameInner {}
impl MetadataFrame {
pub fn new(timecode: i64, data: Option<&str>) -> Self {
let data = data.map(|s| ffi::CString::new(s).unwrap());
MetadataFrame::Owned(
MetadataFrame(MetadataFrameInner::Owned(
NDIlib_metadata_frame_t {
length: data
.as_ref()
@ -1147,23 +1181,23 @@ impl<'a> MetadataFrame<'a> {
.unwrap_or(ptr::null_mut()),
},
data,
)
))
}
pub fn timecode(&self) -> i64 {
match self {
MetadataFrame::Owned(ref frame, _) => frame.timecode,
MetadataFrame::Borrowed(ref frame, _) => frame.timecode,
match self.0 {
MetadataFrameInner::Owned(ref frame, _) => frame.timecode,
MetadataFrameInner::Borrowed(ref frame, _) => frame.timecode,
}
}
pub fn metadata(&self) -> Option<&str> {
unsafe {
match self {
MetadataFrame::Owned(_, ref metadata) => {
match self.0 {
MetadataFrameInner::Owned(_, ref metadata) => {
metadata.as_ref().map(|s| s.to_str().unwrap())
}
MetadataFrame::Borrowed(ref frame, _) => {
MetadataFrameInner::Borrowed(ref frame, _) => {
if frame.p_data.is_null() || frame.length == 0 {
None
} else if frame.length != 0 {
@ -1186,33 +1220,23 @@ impl<'a> MetadataFrame<'a> {
}
pub fn as_ptr(&self) -> *const NDIlib_metadata_frame_t {
match self {
MetadataFrame::Owned(ref frame, _) => frame,
MetadataFrame::Borrowed(ref frame, _) => frame,
match self.0 {
MetadataFrameInner::Owned(ref frame, _) => frame,
MetadataFrameInner::Borrowed(ref frame, _) => frame,
}
}
}
impl<'a> Default for MetadataFrame<'a> {
impl Default for MetadataFrame {
fn default() -> Self {
MetadataFrame::Owned(
MetadataFrame(MetadataFrameInner::Owned(
NDIlib_metadata_frame_t {
length: 0,
timecode: 0, //NDIlib_send_timecode_synthesize,
p_data: ptr::null(),
},
None,
)
}
}
impl<'a> Drop for MetadataFrame<'a> {
fn drop(&mut self) {
if let MetadataFrame::Borrowed(ref mut frame, recv) = *self {
unsafe {
NDIlib_recv_free_metadata(recv.0.as_ptr() as *mut _, frame);
}
}
))
}
}

View file

@ -315,21 +315,17 @@ impl BaseSinkImpl for NdiSink {
ndi_meta = ndi_cc_encoder.encode(buffer);
}
let frame = gst_video::VideoFrameRef::from_buffer_ref_readable(buffer, info)
let frame = gst_video::VideoFrame::from_buffer_readable(buffer.clone(), info)
.map_err(|_| {
gst::error!(CAT, imp: self, "Failed to map buffer");
gst::FlowError::Error
})?;
let frame = crate::ndi::VideoFrame::try_from_video_frame(
&frame,
ndi_meta.as_deref(),
timecode,
)
.map_err(|_| {
gst::error!(CAT, imp: self, "Unsupported video frame");
gst::FlowError::NotNegotiated
})?;
let frame = crate::ndi::VideoFrame::try_from_video_frame(frame, ndi_meta, timecode)
.map_err(|_| {
gst::error!(CAT, imp: self, "Unsupported video frame");
gst::FlowError::NotNegotiated
})?;
gst::trace!(
CAT,