Merge pull request #74 from sdroege/src-compressed-data

Add support for receiving compressed data from the source
This commit is contained in:
Samuel Alonso Rodriguez 2021-10-02 11:02:29 +02:00 committed by GitHub
commit 74b47f016f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 1242 additions and 413 deletions

View file

@ -15,6 +15,7 @@ gst-audio = { package = "gstreamer-audio", version = "0.17" }
gst-video = { package = "gstreamer-video", version = "0.17", features = ["v1_12"] }
byte-slice-cast = "1"
once_cell = "1.0"
byteorder = "1.0"
[build-dependencies]
gst-plugin-version-helper = "0.7"
@ -24,6 +25,7 @@ default = ["interlaced-fields", "reference-timestamps", "sink"]
interlaced-fields = ["gst/v1_16", "gst-video/v1_16"]
reference-timestamps = ["gst/v1_14"]
sink = ["gst/v1_18", "gst-base/v1_18"]
advanced-sdk = []
[lib]
name = "gstndi"

View file

@ -1,25 +1,28 @@
GStreamer NDI Plugin for Linux
====================
*Compiled and tested with NDI SDK 3.5, 3.8, 4.0 and 4.1*
*Compiled and tested with NDI SDK 4.0, 4.1 and 5.0*
This is a plugin for the [GStreamer](https://gstreamer.freedesktop.org/) multimedia framework that allows GStreamer to receive a stream from a [NDI](https://www.newtek.com/ndi/) source. This plugin has been developed by [Teltek](http://teltek.es/) and was funded by the [University of the Arts London](https://www.arts.ac.uk/) and [The University of Manchester](https://www.manchester.ac.uk/).
Currently the plugin has two source elements, `ndivideosrc` to get video from the stream and `ndiaudiosrc` for audio. By just providing the name or the ip of the stream, all the information required from the stream is picked up automatically, such as resolution, framerate, audio channels, ...
Currently the plugin has a source element for receiving from NDI sources, a sink element to provide an NDI source and a device provider for discovering NDI sources on the network.
Some examples of how to use these elements from the command line:
```
#Information about the elements
gst-inspect-1.0 ndi
gst-inspect-1.0 ndisrc
gst-inspect-1.0 ndisink
```console
# Information about the elements
$ gst-inspect-1.0 ndi
$ gst-inspect-1.0 ndisrc
$ gst-inspect-1.0 ndisink
# Discover all NDI sources on the network
$ gst-device-monitor-1.0 -f Source/Network:application/x-ndi
# Audio/Video source pipeline
gst-launch-1.0 ndisrc ndi-name="GC-DEV2 (OBS)" ! ndisrcdemux name=demux demux.video ! queue ! videoconvert ! autovideosink demux.audio ! queue ! audioconvert ! autoaudiosink
$ gst-launch-1.0 ndisrc ndi-name="GC-DEV2 (OBS)" ! ndisrcdemux name=demux demux.video ! queue ! videoconvert ! autovideosink demux.audio ! queue ! audioconvert ! autoaudiosink
# Audio/Video sink pipeline
gst-launch-1.0 videotestsrc is-live=true ! video/x-raw,format=UYVY ! ndisinkcombiner name=combiner ! ndisink ndi-name="My NDI source" audiotestsrc is-live=true ! combiner.audio
$ gst-launch-1.0 videotestsrc is-live=true ! video/x-raw,format=UYVY ! ndisinkcombiner name=combiner ! ndisink ndi-name="My NDI source" audiotestsrc is-live=true ! combiner.audio
```
Feel free to contribute to this project. Some ways you can contribute are:
@ -30,11 +33,9 @@ Compilation of the NDI element
-------
To compile the NDI element it's necessary to install Rust, the NDI SDK and the following packages for gstreamer:
```
apt-get install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \
gstreamer1.0-plugins-base gstreamer1.0-plugins-good \
gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly \
gstreamer1.0-libav libgstrtspserver-1.0-dev
```console
$ apt-get install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \
gstreamer1.0-plugins-base
```
To install the required NDI library there are two options:
@ -54,11 +55,12 @@ gst-inspect-1.0 ndi
By defult GStreamer 1.16 is required, to use only GStreamer 1.12 instead of 1.16, pass `--disable-default-features` to cargo. Only a subset of video formats is supported with this GStreamer version.
If all went ok, you should see info related to the NDI element. To make the plugin available without using `GST_PLUGIN_PATH` it's necessary to copy the plugin to the gstreamer plugins folder.
```
cargo build --release
sudo install -o root -g root -m 644 target/release/libgstndi.so /usr/lib/x86_64-linux-gnu/gstreamer-1.0/
sudo ldconfig
gst-inspect-1.0 ndi
```console
$ cargo build --release
$ sudo install -o root -g root -m 644 target/release/libgstndi.so /usr/lib/x86_64-linux-gnu/gstreamer-1.0/
$ sudo ldconfig
$ gst-inspect-1.0 ndi
```
More info about GStreamer plugins written in Rust:

View file

@ -36,6 +36,83 @@ pub enum TimestampMode {
ReceiveTime = 4,
}
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, glib::GEnum)]
#[repr(u32)]
#[genum(type_name = "GstNdiRecvColorFormat")]
pub enum RecvColorFormat {
#[genum(name = "BGRX or BGRA", nick = "bgrx-bgra")]
BgrxBgra = 0,
#[genum(name = "UYVY or BGRA", nick = "uyvy-bgra")]
UyvyBgra = 1,
#[genum(name = "RGBX or RGBA", nick = "rgbx-rgba")]
RgbxRgba = 2,
#[genum(name = "UYVY or RGBA", nick = "uyvy-rgba")]
UyvyRgba = 3,
#[genum(name = "Fastest", nick = "fastest")]
Fastest = 4,
#[genum(name = "Best", nick = "best")]
Best = 5,
#[cfg(feature = "advanced-sdk")]
#[genum(name = "Compressed v1", nick = "compressed-v1")]
CompressedV1 = 6,
#[cfg(feature = "advanced-sdk")]
#[genum(name = "Compressed v2", nick = "compressed-v2")]
CompressedV2 = 7,
#[cfg(feature = "advanced-sdk")]
#[genum(name = "Compressed v3", nick = "compressed-v3")]
CompressedV3 = 8,
#[cfg(feature = "advanced-sdk")]
#[genum(name = "Compressed v3 with audio", nick = "compressed-v3-with-audio")]
CompressedV3WithAudio = 9,
#[cfg(feature = "advanced-sdk")]
#[genum(name = "Compressed v4", nick = "compressed-v4")]
CompressedV4 = 10,
#[cfg(feature = "advanced-sdk")]
#[genum(name = "Compressed v4 with audio", nick = "compressed-v4-with-audio")]
CompressedV4WithAudio = 11,
#[cfg(feature = "advanced-sdk")]
#[genum(name = "Compressed v5", nick = "compressed-v5")]
CompressedV5 = 12,
#[cfg(feature = "advanced-sdk")]
#[genum(name = "Compressed v5 with audio", nick = "compressed-v5-with-audio")]
CompressedV5WithAudio = 13,
}
impl From<RecvColorFormat> for NDIlib_recv_color_format_e {
fn from(v: RecvColorFormat) -> Self {
match v {
RecvColorFormat::BgrxBgra => NDIlib_recv_color_format_BGRX_BGRA,
RecvColorFormat::UyvyBgra => NDIlib_recv_color_format_UYVY_BGRA,
RecvColorFormat::RgbxRgba => NDIlib_recv_color_format_RGBX_RGBA,
RecvColorFormat::UyvyRgba => NDIlib_recv_color_format_UYVY_RGBA,
RecvColorFormat::Fastest => NDIlib_recv_color_format_fastest,
RecvColorFormat::Best => NDIlib_recv_color_format_best,
#[cfg(feature = "advanced-sdk")]
RecvColorFormat::CompressedV1 => NDIlib_recv_color_format_ex_compressed,
#[cfg(feature = "advanced-sdk")]
RecvColorFormat::CompressedV2 => NDIlib_recv_color_format_ex_compressed_v2,
#[cfg(feature = "advanced-sdk")]
RecvColorFormat::CompressedV3 => NDIlib_recv_color_format_ex_compressed_v3,
#[cfg(feature = "advanced-sdk")]
RecvColorFormat::CompressedV3WithAudio => {
NDIlib_recv_color_format_ex_compressed_v3_with_audio
}
#[cfg(feature = "advanced-sdk")]
RecvColorFormat::CompressedV4 => NDIlib_recv_color_format_ex_compressed_v4,
#[cfg(feature = "advanced-sdk")]
RecvColorFormat::CompressedV4WithAudio => {
NDIlib_recv_color_format_ex_compressed_v4_with_audio
}
#[cfg(feature = "advanced-sdk")]
RecvColorFormat::CompressedV5 => NDIlib_recv_color_format_ex_compressed_v5,
#[cfg(feature = "advanced-sdk")]
RecvColorFormat::CompressedV5WithAudio => {
NDIlib_recv_color_format_ex_compressed_v5_with_audio
}
}
}
}
fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
if !ndi::initialize() {
return Err(glib::bool_error!("Cannot initialize NDI"));

View file

@ -263,7 +263,7 @@ impl RecvInstance {
url_address,
allow_video_fields: true,
bandwidth: NDIlib_recv_bandwidth_highest,
color_format: NDIlib_recv_color_format_e::NDIlib_recv_color_format_UYVY_BGRA,
color_format: NDIlib_recv_color_format_UYVY_BGRA,
ndi_recv_name,
}
}
@ -292,7 +292,7 @@ impl RecvInstance {
let mut audio_frame = mem::zeroed();
let mut metadata_frame = mem::zeroed();
let res = NDIlib_recv_capture_v2(
let res = NDIlib_recv_capture_v3(
ptr,
&mut video_frame,
&mut audio_frame,
@ -386,7 +386,7 @@ impl SendInstance {
pub fn send_audio(&mut self, frame: &AudioFrame) {
unsafe {
NDIlib_send_send_audio_v2(self.0.as_ptr(), frame.as_ptr());
NDIlib_send_send_audio_v3(self.0.as_ptr(), frame.as_ptr());
}
}
}
@ -501,26 +501,144 @@ impl<'a> VideoFrame<'a> {
}
}
pub fn data(&self) -> &[u8] {
// FIXME: Unclear if this is correct. Needs to be validated against an actual
// interlaced stream
let frame_size = if self.frame_format_type()
== NDIlib_frame_format_type_e::NDIlib_frame_format_type_field_0
|| self.frame_format_type()
== NDIlib_frame_format_type_e::NDIlib_frame_format_type_field_1
pub fn data(&self) -> Option<&[u8]> {
let fourcc = self.fourcc();
if [
NDIlib_FourCC_video_type_UYVY,
NDIlib_FourCC_video_type_UYVA,
NDIlib_FourCC_video_type_P216,
NDIlib_FourCC_video_type_PA16,
NDIlib_FourCC_video_type_YV12,
NDIlib_FourCC_video_type_I420,
NDIlib_FourCC_video_type_NV12,
NDIlib_FourCC_video_type_BGRA,
NDIlib_FourCC_video_type_BGRX,
NDIlib_FourCC_video_type_RGBA,
NDIlib_FourCC_video_type_RGBX,
]
.contains(&fourcc)
{
self.yres() * self.line_stride_or_data_size_in_bytes() / 2
} else {
self.yres() * self.line_stride_or_data_size_in_bytes()
};
// FIXME: Unclear if this is correct. Needs to be validated against an actual
// interlaced stream
let frame_size = if self.frame_format_type()
== NDIlib_frame_format_type_e::NDIlib_frame_format_type_field_0
|| self.frame_format_type()
== NDIlib_frame_format_type_e::NDIlib_frame_format_type_field_1
{
self.yres() * self.line_stride_or_data_size_in_bytes() / 2
} else {
self.yres() * self.line_stride_or_data_size_in_bytes()
};
return unsafe {
use std::slice;
match self {
VideoFrame::BorrowedRecv(ref frame, _)
| VideoFrame::BorrowedGst(ref frame, _) => Some(slice::from_raw_parts(
frame.p_data as *const u8,
frame_size as usize,
)),
}
};
}
#[cfg(feature = "advanced-sdk")]
if [
NDIlib_FourCC_video_type_ex_SHQ0_highest_bandwidth,
NDIlib_FourCC_video_type_ex_SHQ2_highest_bandwidth,
NDIlib_FourCC_video_type_ex_SHQ7_highest_bandwidth,
NDIlib_FourCC_video_type_ex_SHQ0_lowest_bandwidth,
NDIlib_FourCC_video_type_ex_SHQ2_lowest_bandwidth,
NDIlib_FourCC_video_type_ex_SHQ7_lowest_bandwidth,
]
.contains(&fourcc)
{
return unsafe {
use std::slice;
match self {
VideoFrame::BorrowedRecv(ref frame, _)
| VideoFrame::BorrowedGst(ref frame, _) => Some(slice::from_raw_parts(
frame.p_data as *const u8,
frame.line_stride_or_data_size_in_bytes as usize,
)),
}
};
}
None
}
#[cfg(feature = "advanced-sdk")]
pub fn compressed_packet(&self) -> Option<CompressedPacket> {
use byteorder::{LittleEndian, ReadBytesExt};
use std::io::Cursor;
use std::slice;
unsafe {
use std::slice;
match self {
VideoFrame::BorrowedRecv(ref frame, _) | VideoFrame::BorrowedGst(ref frame, _) => {
slice::from_raw_parts(frame.p_data as *const u8, frame_size as usize)
}
let fourcc = self.fourcc();
if ![
NDIlib_FourCC_video_type_ex_H264_highest_bandwidth,
NDIlib_FourCC_video_type_ex_H264_lowest_bandwidth,
NDIlib_FourCC_video_type_ex_HEVC_highest_bandwidth,
NDIlib_FourCC_video_type_ex_HEVC_lowest_bandwidth,
NDIlib_FourCC_video_type_ex_H264_alpha_highest_bandwidth,
NDIlib_FourCC_video_type_ex_H264_alpha_lowest_bandwidth,
NDIlib_FourCC_video_type_ex_HEVC_alpha_highest_bandwidth,
NDIlib_FourCC_video_type_ex_HEVC_alpha_lowest_bandwidth,
]
.contains(&fourcc)
{
return None;
}
let data = match self {
VideoFrame::BorrowedRecv(ref frame, _) | VideoFrame::BorrowedGst(ref frame, _) => {
slice::from_raw_parts(
frame.p_data as *const u8,
frame.line_stride_or_data_size_in_bytes as usize,
)
}
};
let mut cursor = Cursor::new(data);
let version = cursor.read_u32::<LittleEndian>().ok()?;
if version != ndisys::NDIlib_compressed_packet_version_0 {
return None;
}
let fourcc = cursor.read_u32::<LittleEndian>().ok()?;
let pts = cursor.read_i64::<LittleEndian>().ok()?;
let dts = cursor.read_i64::<LittleEndian>().ok()?;
let _reserved = cursor.read_u64::<LittleEndian>().ok()?;
let flags = cursor.read_u32::<LittleEndian>().ok()?;
let data_size = cursor.read_u32::<LittleEndian>().ok()?;
let extra_data_size = cursor.read_u32::<LittleEndian>().ok()?;
let expected_size = (ndisys::NDIlib_compressed_packet_version_0 as usize)
.checked_add(data_size as usize)?
.checked_add(extra_data_size as usize)?;
if data.len() < expected_size {
return None;
}
Some(CompressedPacket {
fourcc,
pts,
dts,
key_frame: flags & ndisys::NDIlib_compressed_packet_flags_keyframe != 0,
data: &data[ndisys::NDIlib_compressed_packet_version_0 as usize..]
[..data_size as usize],
extra_data: if extra_data_size > 0 {
Some(
&data[ndisys::NDIlib_compressed_packet_version_0 as usize
+ data_size as usize..][..extra_data_size as usize],
)
} else {
None
},
})
}
}
@ -710,11 +828,11 @@ impl<'a> Drop for VideoFrame<'a> {
#[derive(Debug)]
pub enum AudioFrame<'a> {
Owned(
NDIlib_audio_frame_v2_t,
NDIlib_audio_frame_v3_t,
Option<ffi::CString>,
Option<Vec<f32>>,
),
BorrowedRecv(NDIlib_audio_frame_v2_t, &'a RecvInstance),
BorrowedRecv(NDIlib_audio_frame_v3_t, &'a RecvInstance),
}
impl<'a> AudioFrame<'a> {
@ -750,24 +868,114 @@ impl<'a> AudioFrame<'a> {
}
}
pub fn data(&self) -> &[u8] {
unsafe {
use std::slice;
match self {
AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => {
slice::from_raw_parts(
frame.p_data as *const u8,
(frame.no_samples * frame.channel_stride_in_bytes) as usize,
)
}
pub fn fourcc(&self) -> NDIlib_FourCC_audio_type_e {
match self {
AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => {
frame.FourCC
}
}
}
pub fn channel_stride_in_bytes(&self) -> i32 {
pub fn data(&self) -> Option<&[u8]> {
unsafe {
use std::slice;
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,
))
}
};
}
#[cfg(feature = "advanced-sdk")]
if [NDIlib_FourCC_audio_type_Opus].contains(&fourcc) {
return match self {
AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => {
Some(slice::from_raw_parts(
frame.p_data as *const u8,
frame.channel_stride_or_data_size_in_bytes as usize,
))
}
};
}
None
}
}
#[cfg(feature = "advanced-sdk")]
pub fn compressed_packet(&self) -> Option<CompressedPacket> {
use byteorder::{LittleEndian, ReadBytesExt};
use std::io::Cursor;
use std::slice;
unsafe {
let fourcc = self.fourcc();
if ![NDIlib_FourCC_audio_type_AAC].contains(&fourcc) {
return None;
}
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 mut cursor = Cursor::new(data);
let version = cursor.read_u32::<LittleEndian>().ok()?;
if version != ndisys::NDIlib_compressed_packet_version_0 {
return None;
}
let fourcc = cursor.read_u32::<LittleEndian>().ok()?;
let pts = cursor.read_i64::<LittleEndian>().ok()?;
let dts = cursor.read_i64::<LittleEndian>().ok()?;
let _reserved = cursor.read_u64::<LittleEndian>().ok()?;
let flags = cursor.read_u32::<LittleEndian>().ok()?;
let data_size = cursor.read_u32::<LittleEndian>().ok()?;
let extra_data_size = cursor.read_u32::<LittleEndian>().ok()?;
let expected_size = (ndisys::NDIlib_compressed_packet_version_0 as usize)
.checked_add(data_size as usize)?
.checked_add(extra_data_size as usize)?;
if data.len() < expected_size {
return None;
}
Some(CompressedPacket {
fourcc,
pts,
dts,
key_frame: flags & ndisys::NDIlib_compressed_packet_flags_keyframe != 0,
data: &data[ndisys::NDIlib_compressed_packet_version_0 as usize..]
[..data_size as usize],
extra_data: if extra_data_size > 0 {
Some(
&data[ndisys::NDIlib_compressed_packet_version_0 as usize
+ data_size as usize..][..extra_data_size as usize],
)
} else {
None
},
})
}
}
pub fn channel_stride_or_data_size_in_bytes(&self) -> i32 {
match self {
AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => {
frame.channel_stride_in_bytes
frame.channel_stride_or_data_size_in_bytes
}
}
}
@ -794,73 +1002,55 @@ impl<'a> AudioFrame<'a> {
}
}
pub fn as_ptr(&self) -> *const NDIlib_audio_frame_v2_t {
pub fn as_ptr(&self) -> *const NDIlib_audio_frame_v3_t {
match self {
AudioFrame::BorrowedRecv(ref frame, _) | AudioFrame::Owned(ref frame, _, _) => frame,
}
}
pub fn copy_to_interleaved_16s(&self, data: &mut [i16]) {
assert_eq!(
data.len(),
(self.no_samples() * self.no_channels()) as usize
);
let mut dst = NDIlib_audio_frame_interleaved_16s_t {
sample_rate: self.sample_rate(),
no_channels: self.no_channels(),
no_samples: self.no_samples(),
timecode: self.timecode(),
reference_level: 0,
p_data: data.as_mut_ptr(),
};
unsafe {
NDIlib_util_audio_to_interleaved_16s_v2(self.as_ptr(), &mut dst);
}
}
pub fn try_from_interleaved_16s(
pub fn try_from_buffer(
info: &gst_audio::AudioInfo,
buffer: &gst::BufferRef,
timecode: i64,
) -> Result<Self, ()> {
if info.format() != gst_audio::AUDIO_FORMAT_S16 {
if info.format() != gst_audio::AUDIO_FORMAT_F32 {
return Err(());
}
let map = buffer.map_readable().map_err(|_| ())?;
let src_data = map.as_slice_of::<i16>().map_err(|_| ())?;
let src_data = map.as_slice_of::<f32>().map_err(|_| ())?;
let src = NDIlib_audio_frame_interleaved_16s_t {
let no_samples = src_data.len() as i32 / info.channels() as i32;
let channel_stride_or_data_size_in_bytes = no_samples * mem::size_of::<f32>() as i32;
let mut dest_data =
Vec::<f32>::with_capacity(no_samples as usize * info.channels() as usize);
assert_eq!(dest_data.capacity(), src_data.len());
unsafe {
let dest_ptr = dest_data.as_mut_ptr();
for (i, samples) in src_data.chunks_exact(info.channels() as usize).enumerate() {
for (c, sample) in samples.iter().enumerate() {
ptr::write(dest_ptr.add(c * no_samples as usize + i), *sample);
}
}
dest_data.set_len(no_samples as usize * info.channels() as usize);
}
let dest = NDIlib_audio_frame_v3_t {
sample_rate: info.rate() as i32,
no_channels: info.channels() as i32,
no_samples: src_data.len() as i32 / info.channels() as i32,
no_samples,
timecode,
reference_level: 0,
p_data: src_data.as_ptr() as *mut i16,
};
let channel_stride_in_bytes = src.no_samples * mem::size_of::<f32>() as i32;
let mut dest_data =
Vec::with_capacity(channel_stride_in_bytes as usize * info.channels() as usize);
let mut dest = NDIlib_audio_frame_v2_t {
sample_rate: src.sample_rate,
no_channels: src.no_channels,
no_samples: src.no_samples,
timecode: src.timecode,
FourCC: NDIlib_FourCC_audio_type_FLTp,
p_data: dest_data.as_mut_ptr(),
channel_stride_in_bytes,
channel_stride_or_data_size_in_bytes,
p_metadata: ptr::null(),
timestamp: 0,
};
unsafe {
NDIlib_util_audio_from_interleaved_16s_v2(&src, &mut dest);
dest_data.set_len(dest_data.capacity());
}
Ok(AudioFrame::Owned(dest, None, Some(dest_data)))
}
}
@ -870,12 +1060,22 @@ impl<'a> Drop for AudioFrame<'a> {
fn drop(&mut self) {
if let AudioFrame::BorrowedRecv(ref mut frame, recv) = *self {
unsafe {
NDIlib_recv_free_audio_v2(recv.0.as_ptr() as *mut _, frame);
NDIlib_recv_free_audio_v3(recv.0.as_ptr() as *mut _, frame);
}
}
}
}
#[cfg(feature = "advanced-sdk")]
pub struct CompressedPacket<'a> {
pub fourcc: ndisys::NDIlib_compressed_FourCC_type_e,
pub pts: i64,
pub dts: i64,
pub key_frame: bool,
pub data: &'a [u8],
pub extra_data: Option<&'a [u8]>,
}
#[derive(Debug)]
pub enum MetadataFrame<'a> {
Owned(NDIlib_metadata_frame_t, Option<ffi::CString>),

View file

@ -151,7 +151,7 @@ impl ElementImpl for NdiSink {
)
.structure(
gst::Structure::builder("audio/x-raw")
.field("format", &gst_audio::AUDIO_FORMAT_S16.to_str())
.field("format", &gst_audio::AUDIO_FORMAT_F32.to_str())
.field("rate", &gst::IntRange::<i32>::new(1, i32::MAX))
.field("channels", &gst::IntRange::<i32>::new(1, i32::MAX))
.field("layout", &"interleaved")
@ -256,9 +256,8 @@ impl BaseSinkImpl for NdiSink {
if let Some(ref info) = state.video_info {
if let Some(audio_meta) = buffer.meta::<crate::ndisinkmeta::NdiSinkAudioMeta>() {
for (buffer, info, timecode) in audio_meta.buffers() {
let frame =
crate::ndi::AudioFrame::try_from_interleaved_16s(info, buffer, *timecode)
.map_err(|_| {
let frame = crate::ndi::AudioFrame::try_from_buffer(info, buffer, *timecode)
.map_err(|_| {
gst_error!(CAT, obj: element, "Unsupported audio frame");
gst::FlowError::NotNegotiated
})?;
@ -334,8 +333,8 @@ impl BaseSinkImpl for NdiSink {
.map(|time| (time.nseconds() / 100) as i64)
.unwrap_or(crate::ndisys::NDIlib_send_timecode_synthesize);
let frame = crate::ndi::AudioFrame::try_from_interleaved_16s(info, buffer, timecode)
.map_err(|_| {
let frame =
crate::ndi::AudioFrame::try_from_buffer(info, buffer, timecode).map_err(|_| {
gst_error!(CAT, obj: element, "Unsupported audio frame");
gst::FlowError::NotNegotiated
})?;

View file

@ -122,7 +122,7 @@ impl ElementImpl for NdiSinkCombiner {
.unwrap();
let caps = gst::Caps::builder("audio/x-raw")
.field("format", &gst_audio::AUDIO_FORMAT_S16.to_str())
.field("format", &gst_audio::AUDIO_FORMAT_F32.to_str())
.field("rate", &gst::IntRange::<i32>::new(1, i32::MAX))
.field("channels", &gst::IntRange::<i32>::new(1, i32::MAX))
.field("layout", &"interleaved")

View file

@ -17,6 +17,7 @@ use crate::Buffer;
use crate::Receiver;
use crate::ReceiverControlHandle;
use crate::ReceiverItem;
use crate::RecvColorFormat;
use crate::TimestampMode;
use crate::DEFAULT_RECEIVER_NDI_NAME;
@ -37,6 +38,7 @@ struct Settings {
max_queue_length: u32,
receiver_ndi_name: String,
bandwidth: ndisys::NDIlib_recv_bandwidth_e,
color_format: RecvColorFormat,
timestamp_mode: TimestampMode,
}
@ -50,15 +52,16 @@ impl Default for Settings {
timeout: 5000,
max_queue_length: 10,
bandwidth: ndisys::NDIlib_recv_bandwidth_highest,
color_format: RecvColorFormat::UyvyBgra,
timestamp_mode: TimestampMode::ReceiveTimeTimecode,
}
}
}
struct State {
video_info: Option<gst_video::VideoInfo>,
video_info: Option<crate::VideoInfo>,
video_caps: Option<gst::Caps>,
audio_info: Option<gst_audio::AudioInfo>,
audio_info: Option<crate::AudioInfo>,
audio_caps: Option<gst::Caps>,
current_latency: Option<gst::ClockTime>,
receiver: Option<Receiver>,
@ -159,6 +162,14 @@ impl ObjectImpl for NdiSrc {
100,
glib::ParamFlags::READWRITE,
),
glib::ParamSpec::new_enum(
"color-format",
"Color Format",
"Receive color format",
RecvColorFormat::static_type(),
RecvColorFormat::UyvyBgra as u32 as i32,
glib::ParamFlags::READWRITE,
),
glib::ParamSpec::new_enum(
"timestamp-mode",
"Timestamp Mode",
@ -275,6 +286,18 @@ impl ObjectImpl for NdiSrc {
);
settings.bandwidth = bandwidth;
}
"color-format" => {
let mut settings = self.settings.lock().unwrap();
let color_format = value.get().unwrap();
gst_debug!(
CAT,
obj: obj,
"Changing color format from {:?} to {:?}",
settings.color_format,
color_format,
);
settings.color_format = color_format;
}
"timestamp-mode" => {
let mut settings = self.settings.lock().unwrap();
let timestamp_mode = value.get().unwrap();
@ -324,6 +347,10 @@ impl ObjectImpl for NdiSrc {
let settings = self.settings.lock().unwrap();
settings.bandwidth.to_value()
}
"color-format" => {
let settings = self.settings.lock().unwrap();
settings.color_format.to_value()
}
"timestamp-mode" => {
let settings = self.settings.lock().unwrap();
settings.timestamp_mode.to_value()
@ -432,6 +459,7 @@ impl BaseSrcImpl for NdiSrc {
&settings.receiver_ndi_name,
settings.connect_timeout,
settings.bandwidth,
settings.color_format.into(),
settings.timestamp_mode,
settings.timeout,
settings.max_queue_length as usize,

View file

@ -39,10 +39,10 @@ extern "C" {
p_instance: NDIlib_recv_instance_t,
p_metadata: *const NDIlib_metadata_frame_t,
) -> bool;
pub fn NDIlib_recv_capture_v2(
pub fn NDIlib_recv_capture_v3(
p_instance: NDIlib_recv_instance_t,
p_video_data: *mut NDIlib_video_frame_v2_t,
p_audio_data: *mut NDIlib_audio_frame_v2_t,
p_audio_data: *mut NDIlib_audio_frame_v3_t,
p_metadata: *mut NDIlib_metadata_frame_t,
timeout_in_ms: u32,
) -> NDIlib_frame_type_e;
@ -50,9 +50,9 @@ extern "C" {
p_instance: NDIlib_recv_instance_t,
p_video_data: *mut NDIlib_video_frame_v2_t,
);
pub fn NDIlib_recv_free_audio_v2(
pub fn NDIlib_recv_free_audio_v3(
p_instance: NDIlib_recv_instance_t,
p_audio_data: *mut NDIlib_audio_frame_v2_t,
p_audio_data: *mut NDIlib_audio_frame_v3_t,
);
pub fn NDIlib_recv_free_metadata(
p_instance: NDIlib_recv_instance_t,
@ -70,9 +70,9 @@ extern "C" {
p_instance: NDIlib_send_instance_t,
p_video_data: *const NDIlib_video_frame_v2_t,
);
pub fn NDIlib_send_send_audio_v2(
pub fn NDIlib_send_send_audio_v3(
p_instance: NDIlib_send_instance_t,
p_audio_data: *const NDIlib_audio_frame_v2_t,
p_audio_data: *const NDIlib_audio_frame_v3_t,
);
}
@ -111,29 +111,110 @@ pub const NDIlib_recv_bandwidth_audio_only: NDIlib_recv_bandwidth_e = 10;
pub const NDIlib_recv_bandwidth_lowest: NDIlib_recv_bandwidth_e = 0;
pub const NDIlib_recv_bandwidth_highest: NDIlib_recv_bandwidth_e = 100;
#[repr(u32)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum NDIlib_recv_color_format_e {
NDIlib_recv_color_format_BGRX_BGRA = 0,
NDIlib_recv_color_format_UYVY_BGRA = 1,
NDIlib_recv_color_format_RGBX_RGBA = 2,
NDIlib_recv_color_format_UYVY_RGBA = 3,
NDIlib_recv_color_format_fastest = 100,
NDIlib_recv_color_format_best = 101,
pub type NDIlib_recv_color_format_e = u32;
pub const NDIlib_recv_color_format_BGRX_BGRA: NDIlib_recv_color_format_e = 0;
pub const NDIlib_recv_color_format_UYVY_BGRA: NDIlib_recv_color_format_e = 1;
pub const NDIlib_recv_color_format_RGBX_RGBA: NDIlib_recv_color_format_e = 2;
pub const NDIlib_recv_color_format_UYVY_RGBA: NDIlib_recv_color_format_e = 3;
pub const NDIlib_recv_color_format_fastest: NDIlib_recv_color_format_e = 100;
pub const NDIlib_recv_color_format_best: NDIlib_recv_color_format_e = 101;
#[cfg(feature = "advanced-sdk")]
pub const NDIlib_recv_color_format_ex_compressed: NDIlib_recv_color_format_e = 300;
#[cfg(feature = "advanced-sdk")]
pub const NDIlib_recv_color_format_ex_compressed_v2: NDIlib_recv_color_format_e = 301;
#[cfg(feature = "advanced-sdk")]
pub const NDIlib_recv_color_format_ex_compressed_v3: NDIlib_recv_color_format_e = 302;
#[cfg(feature = "advanced-sdk")]
pub const NDIlib_recv_color_format_ex_compressed_v3_with_audio: NDIlib_recv_color_format_e = 304;
#[cfg(feature = "advanced-sdk")]
pub const NDIlib_recv_color_format_ex_compressed_v4: NDIlib_recv_color_format_e = 303;
#[cfg(feature = "advanced-sdk")]
pub const NDIlib_recv_color_format_ex_compressed_v4_with_audio: NDIlib_recv_color_format_e = 305;
#[cfg(feature = "advanced-sdk")]
pub const NDIlib_recv_color_format_ex_compressed_v5: NDIlib_recv_color_format_e = 307;
#[cfg(feature = "advanced-sdk")]
pub const NDIlib_recv_color_format_ex_compressed_v5_with_audio: NDIlib_recv_color_format_e = 308;
const fn make_fourcc(fourcc: &[u8; 4]) -> u32 {
((fourcc[0] as u32) << 0)
| ((fourcc[1] as u32) << 8)
| ((fourcc[2] as u32) << 16)
| ((fourcc[3] as u32) << 24)
}
pub type NDIlib_FourCC_video_type_e = u32;
pub const NDIlib_FourCC_video_type_UYVY: NDIlib_FourCC_video_type_e = 0x59_56_59_55;
pub const NDIlib_FourCC_video_type_UYVA: NDIlib_FourCC_video_type_e = 0x41_56_56_55;
pub const NDIlib_FourCC_video_type_P216: NDIlib_FourCC_video_type_e = 0x36_31_32_50;
pub const NDIlib_FourCC_video_type_PA16: NDIlib_FourCC_video_type_e = 0x36_31_41_50;
pub const NDIlib_FourCC_video_type_YV12: NDIlib_FourCC_video_type_e = 0x32_31_56_59;
pub const NDIlib_FourCC_video_type_I420: NDIlib_FourCC_video_type_e = 0x30_32_34_49;
pub const NDIlib_FourCC_video_type_NV12: NDIlib_FourCC_video_type_e = 0x32_31_56_4e;
pub const NDIlib_FourCC_video_type_BGRA: NDIlib_FourCC_video_type_e = 0x41_52_47_42;
pub const NDIlib_FourCC_video_type_BGRX: NDIlib_FourCC_video_type_e = 0x58_52_47_42;
pub const NDIlib_FourCC_video_type_RGBA: NDIlib_FourCC_video_type_e = 0x41_42_47_52;
pub const NDIlib_FourCC_video_type_RGBX: NDIlib_FourCC_video_type_e = 0x58_42_47_52;
pub const NDIlib_FourCC_video_type_UYVY: NDIlib_FourCC_video_type_e = make_fourcc(b"UYVY");
pub const NDIlib_FourCC_video_type_UYVA: NDIlib_FourCC_video_type_e = make_fourcc(b"UYVA");
pub const NDIlib_FourCC_video_type_P216: NDIlib_FourCC_video_type_e = make_fourcc(b"P216");
pub const NDIlib_FourCC_video_type_PA16: NDIlib_FourCC_video_type_e = make_fourcc(b"PA16");
pub const NDIlib_FourCC_video_type_YV12: NDIlib_FourCC_video_type_e = make_fourcc(b"YV12");
pub const NDIlib_FourCC_video_type_I420: NDIlib_FourCC_video_type_e = make_fourcc(b"I420");
pub const NDIlib_FourCC_video_type_NV12: NDIlib_FourCC_video_type_e = make_fourcc(b"NV12");
pub const NDIlib_FourCC_video_type_BGRA: NDIlib_FourCC_video_type_e = make_fourcc(b"BGRA");
pub const NDIlib_FourCC_video_type_BGRX: NDIlib_FourCC_video_type_e = make_fourcc(b"BGRX");
pub const NDIlib_FourCC_video_type_RGBA: NDIlib_FourCC_video_type_e = make_fourcc(b"RGBA");
pub const NDIlib_FourCC_video_type_RGBX: NDIlib_FourCC_video_type_e = make_fourcc(b"RGBX");
#[cfg(feature = "advanced-sdk")]
pub const NDIlib_FourCC_video_type_ex_SHQ0_highest_bandwidth: NDIlib_FourCC_video_type_e =
make_fourcc(b"SHQ0");
#[cfg(feature = "advanced-sdk")]
pub const NDIlib_FourCC_video_type_ex_SHQ2_highest_bandwidth: NDIlib_FourCC_video_type_e =
make_fourcc(b"SHQ2");
#[cfg(feature = "advanced-sdk")]
pub const NDIlib_FourCC_video_type_ex_SHQ7_highest_bandwidth: NDIlib_FourCC_video_type_e =
make_fourcc(b"SHQ7");
#[cfg(feature = "advanced-sdk")]
pub const NDIlib_FourCC_video_type_ex_SHQ0_lowest_bandwidth: NDIlib_FourCC_video_type_e =
make_fourcc(b"shq0");
#[cfg(feature = "advanced-sdk")]
pub const NDIlib_FourCC_video_type_ex_SHQ2_lowest_bandwidth: NDIlib_FourCC_video_type_e =
make_fourcc(b"shq2");
#[cfg(feature = "advanced-sdk")]
pub const NDIlib_FourCC_video_type_ex_SHQ7_lowest_bandwidth: NDIlib_FourCC_video_type_e =
make_fourcc(b"shq7");
#[cfg(feature = "advanced-sdk")]
pub const NDIlib_FourCC_video_type_ex_H264_highest_bandwidth: NDIlib_FourCC_video_type_e =
make_fourcc(b"H264");
#[cfg(feature = "advanced-sdk")]
pub const NDIlib_FourCC_video_type_ex_H264_lowest_bandwidth: NDIlib_FourCC_video_type_e =
make_fourcc(b"h264");
#[cfg(feature = "advanced-sdk")]
pub const NDIlib_FourCC_video_type_ex_HEVC_highest_bandwidth: NDIlib_FourCC_video_type_e =
make_fourcc(b"HEVC");
#[cfg(feature = "advanced-sdk")]
pub const NDIlib_FourCC_video_type_ex_HEVC_lowest_bandwidth: NDIlib_FourCC_video_type_e =
make_fourcc(b"hevc");
#[cfg(feature = "advanced-sdk")]
pub const NDIlib_FourCC_video_type_ex_H264_alpha_highest_bandwidth: NDIlib_FourCC_video_type_e =
make_fourcc(b"A264");
#[cfg(feature = "advanced-sdk")]
pub const NDIlib_FourCC_video_type_ex_H264_alpha_lowest_bandwidth: NDIlib_FourCC_video_type_e =
make_fourcc(b"a264");
#[cfg(feature = "advanced-sdk")]
pub const NDIlib_FourCC_video_type_ex_HEVC_alpha_highest_bandwidth: NDIlib_FourCC_video_type_e =
make_fourcc(b"AEVC");
#[cfg(feature = "advanced-sdk")]
pub const NDIlib_FourCC_video_type_ex_HEVC_alpha_lowest_bandwidth: NDIlib_FourCC_video_type_e =
make_fourcc(b"aevc");
pub type NDIlib_FourCC_audio_type_e = u32;
pub const NDIlib_FourCC_audio_type_FLTp: NDIlib_FourCC_video_type_e = make_fourcc(b"FLTp");
#[cfg(feature = "advanced-sdk")]
pub const NDIlib_FourCC_audio_type_AAC: NDIlib_FourCC_audio_type_e = 0x000000ff;
#[cfg(feature = "advanced-sdk")]
pub const NDIlib_FourCC_audio_type_Opus: NDIlib_FourCC_audio_type_e = make_fourcc(b"Opus");
#[cfg(feature = "advanced-sdk")]
pub type NDIlib_compressed_FourCC_type_e = u32;
#[cfg(feature = "advanced-sdk")]
pub const NDIlib_compressed_FourCC_type_H264: NDIlib_compressed_FourCC_type_e =
make_fourcc(b"H264");
#[cfg(feature = "advanced-sdk")]
pub const NDIlib_compressed_FourCC_type_HEVC: NDIlib_compressed_FourCC_type_e =
make_fourcc(b"HEVC");
#[cfg(feature = "advanced-sdk")]
pub const NDIlib_compressed_FourCC_type_AAC: NDIlib_compressed_FourCC_type_e = 0x000000ff;
#[repr(u32)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
@ -212,36 +293,34 @@ pub struct NDIlib_video_frame_v2_t {
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct NDIlib_audio_frame_v2_t {
pub struct NDIlib_audio_frame_v3_t {
pub sample_rate: ::std::os::raw::c_int,
pub no_channels: ::std::os::raw::c_int,
pub no_samples: ::std::os::raw::c_int,
pub timecode: i64,
pub FourCC: NDIlib_FourCC_audio_type_e,
pub p_data: *const ::std::os::raw::c_float,
pub channel_stride_in_bytes: ::std::os::raw::c_int,
pub channel_stride_or_data_size_in_bytes: ::std::os::raw::c_int,
pub p_metadata: *const ::std::os::raw::c_char,
pub timestamp: i64,
}
extern "C" {
pub fn NDIlib_util_audio_to_interleaved_16s_v2(
p_src: *const NDIlib_audio_frame_v2_t,
p_dst: *mut NDIlib_audio_frame_interleaved_16s_t,
);
pub fn NDIlib_util_audio_from_interleaved_16s_v2(
p_src: *const NDIlib_audio_frame_interleaved_16s_t,
p_dst: *mut NDIlib_audio_frame_v2_t,
);
}
#[repr(C)]
#[cfg(feature = "advanced-sdk")]
#[repr(packed)]
#[derive(Debug, Copy, Clone)]
pub struct NDIlib_audio_frame_interleaved_16s_t {
pub sample_rate: ::std::os::raw::c_int,
pub no_channels: ::std::os::raw::c_int,
pub no_samples: ::std::os::raw::c_int,
pub timecode: i64,
pub reference_level: ::std::os::raw::c_int,
pub p_data: *mut i16,
pub struct NDIlib_compressed_packet_t {
pub version: u32,
pub fourcc: NDIlib_compressed_FourCC_type_e,
pub pts: i64,
pub dts: i64,
pub reserved: u64,
pub flags: u32,
pub data_size: u32,
pub extra_data_size: u32,
}
#[cfg(feature = "advanced-sdk")]
pub const NDIlib_compressed_packet_flags_keyframe: u32 = 1;
#[cfg(feature = "advanced-sdk")]
pub const NDIlib_compressed_packet_version_0: u32 = 44;

File diff suppressed because it is too large Load diff