gstreamer_audio/
audio_meta.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::fmt;
4#[cfg(feature = "v1_16")]
5#[cfg_attr(docsrs, doc(cfg(feature = "v1_16")))]
6use std::ptr;
7#[cfg(feature = "v1_16")]
8#[cfg_attr(docsrs, doc(cfg(feature = "v1_16")))]
9use std::slice;
10
11use glib::translate::{from_glib, IntoGlib};
12#[cfg(feature = "v1_16")]
13#[cfg_attr(docsrs, doc(cfg(feature = "v1_16")))]
14use glib::translate::{from_glib_none, ToGlibPtr};
15use gst::prelude::*;
16
17use crate::ffi;
18
19/// Extra buffer metadata describing how much audio has to be clipped from
20/// the start or end of a buffer. This is used for compressed formats, where
21/// the first frame usually has some additional samples due to encoder and
22/// decoder delays, and the last frame usually has some additional samples to
23/// be able to fill the complete last frame.
24///
25/// This is used to ensure that decoded data in the end has the same amount of
26/// samples, and multiply decoded streams can be gaplessly concatenated.
27///
28/// Note: If clipping of the start is done by adjusting the segment, this meta
29/// has to be dropped from buffers as otherwise clipping could happen twice.
30#[repr(transparent)]
31#[doc(alias = "GstAudioClippingMeta")]
32pub struct AudioClippingMeta(ffi::GstAudioClippingMeta);
33
34unsafe impl Send for AudioClippingMeta {}
35unsafe impl Sync for AudioClippingMeta {}
36
37impl AudioClippingMeta {
38    #[doc(alias = "gst_buffer_add_audio_clipping_meta")]
39    pub fn add<V: gst::format::FormattedValue>(
40        buffer: &mut gst::BufferRef,
41        start: V,
42        end: V,
43    ) -> gst::MetaRefMut<Self, gst::meta::Standalone> {
44        skip_assert_initialized!();
45        assert_eq!(start.format(), end.format());
46        unsafe {
47            let meta = ffi::gst_buffer_add_audio_clipping_meta(
48                buffer.as_mut_ptr(),
49                start.format().into_glib(),
50                start.into_raw_value() as u64,
51                end.into_raw_value() as u64,
52            );
53
54            Self::from_mut_ptr(buffer, meta)
55        }
56    }
57
58    #[doc(alias = "get_start")]
59    #[inline]
60    pub fn start(&self) -> gst::GenericFormattedValue {
61        unsafe { gst::GenericFormattedValue::new(from_glib(self.0.format), self.0.start as i64) }
62    }
63
64    #[doc(alias = "get_end")]
65    #[inline]
66    pub fn end(&self) -> gst::GenericFormattedValue {
67        unsafe { gst::GenericFormattedValue::new(from_glib(self.0.format), self.0.end as i64) }
68    }
69}
70
71unsafe impl MetaAPI for AudioClippingMeta {
72    type GstType = ffi::GstAudioClippingMeta;
73
74    #[doc(alias = "gst_audio_clipping_meta_api_get_type")]
75    #[inline]
76    fn meta_api() -> glib::Type {
77        unsafe { from_glib(ffi::gst_audio_clipping_meta_api_get_type()) }
78    }
79}
80
81impl fmt::Debug for AudioClippingMeta {
82    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
83        f.debug_struct("AudioClippingMeta")
84            .field("start", &self.start())
85            .field("end", &self.end())
86            .finish()
87    }
88}
89
90/// `GstAudioDownmixMeta` defines an audio downmix matrix to be send along with
91/// audio buffers. These functions in this module help to create and attach the
92/// meta as well as extracting it.
93#[cfg(feature = "v1_16")]
94#[cfg_attr(docsrs, doc(cfg(feature = "v1_16")))]
95#[repr(transparent)]
96#[doc(alias = "GstAudioMeta")]
97pub struct AudioMeta(ffi::GstAudioMeta);
98
99#[cfg(feature = "v1_16")]
100#[cfg_attr(docsrs, doc(cfg(feature = "v1_16")))]
101unsafe impl Send for AudioMeta {}
102#[cfg(feature = "v1_16")]
103#[cfg_attr(docsrs, doc(cfg(feature = "v1_16")))]
104unsafe impl Sync for AudioMeta {}
105
106#[cfg(feature = "v1_16")]
107#[cfg_attr(docsrs, doc(cfg(feature = "v1_16")))]
108impl AudioMeta {
109    #[doc(alias = "gst_buffer_add_audio_meta")]
110    pub fn add<'a>(
111        buffer: &'a mut gst::BufferRef,
112        info: &crate::AudioInfo,
113        samples: usize,
114        offsets: &[usize],
115    ) -> Result<gst::MetaRefMut<'a, Self, gst::meta::Standalone>, glib::BoolError> {
116        skip_assert_initialized!();
117
118        if !info.is_valid() {
119            return Err(glib::bool_error!("Invalid audio info"));
120        }
121
122        if info.rate() == 0
123            || info.channels() == 0
124            || info.format() == crate::AudioFormat::Unknown
125            || info.format() == crate::AudioFormat::Encoded
126        {
127            return Err(glib::bool_error!("Unsupported audio format {:?}", info));
128        }
129
130        if !offsets.is_empty() && info.layout() != crate::AudioLayout::NonInterleaved {
131            return Err(glib::bool_error!(
132                "Channel offsets only supported for non-interleaved audio"
133            ));
134        }
135
136        if !offsets.is_empty() && offsets.len() != info.channels() as usize {
137            return Err(glib::bool_error!(
138                "Number of channel offsets different than number of channels ({} != {})",
139                offsets.len(),
140                info.channels()
141            ));
142        }
143
144        if info.layout() == crate::AudioLayout::NonInterleaved {
145            let plane_size = samples * (info.width() / 8) as usize;
146            let max_offset = if offsets.is_empty() {
147                plane_size * (info.channels() - 1) as usize
148            } else {
149                let mut max_offset = None;
150
151                for (i, offset) in offsets.iter().copied().enumerate() {
152                    if let Some(current_max_offset) = max_offset {
153                        max_offset = Some(std::cmp::max(current_max_offset, offset));
154                    } else {
155                        max_offset = Some(offset);
156                    }
157
158                    for (j, other_offset) in offsets.iter().copied().enumerate() {
159                        if i != j
160                            && !(other_offset + plane_size <= offset
161                                || offset + plane_size <= other_offset)
162                        {
163                            return Err(glib::bool_error!("Overlapping audio channel offsets: offset {} for channel {} and offset {} for channel {} with a plane size of {}", offset, i, other_offset, j, plane_size));
164                        }
165                    }
166                }
167
168                max_offset.unwrap()
169            };
170
171            if max_offset + plane_size > buffer.size() {
172                return Err(glib::bool_error!("Audio channel offsets out of bounds: max offset {} with plane size {} and buffer size {}", max_offset, plane_size, buffer.size()));
173            }
174        }
175
176        unsafe {
177            let meta = ffi::gst_buffer_add_audio_meta(
178                buffer.as_mut_ptr(),
179                info.to_glib_none().0,
180                samples,
181                if offsets.is_empty() {
182                    ptr::null_mut()
183                } else {
184                    offsets.as_ptr() as *mut _
185                },
186            );
187
188            if meta.is_null() {
189                return Err(glib::bool_error!("Failed to add audio meta"));
190            }
191
192            Ok(Self::from_mut_ptr(buffer, meta))
193        }
194    }
195
196    #[doc(alias = "get_info")]
197    #[inline]
198    pub fn info(&self) -> crate::AudioInfo {
199        unsafe { from_glib_none(&self.0.info as *const _) }
200    }
201
202    #[doc(alias = "get_samples")]
203    #[inline]
204    pub fn samples(&self) -> usize {
205        self.0.samples
206    }
207
208    #[doc(alias = "get_offsets")]
209    #[inline]
210    pub fn offsets(&self) -> &[usize] {
211        if self.0.offsets.is_null() || self.0.info.channels < 1 {
212            return &[];
213        }
214
215        unsafe { slice::from_raw_parts(self.0.offsets, self.0.info.channels as usize) }
216    }
217}
218
219#[cfg(feature = "v1_16")]
220#[cfg_attr(docsrs, doc(cfg(feature = "v1_16")))]
221unsafe impl MetaAPI for AudioMeta {
222    type GstType = ffi::GstAudioMeta;
223
224    #[doc(alias = "gst_audio_meta_api_get_type")]
225    #[inline]
226    fn meta_api() -> glib::Type {
227        unsafe { from_glib(ffi::gst_audio_meta_api_get_type()) }
228    }
229}
230
231#[cfg(feature = "v1_16")]
232#[cfg_attr(docsrs, doc(cfg(feature = "v1_16")))]
233impl fmt::Debug for AudioMeta {
234    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
235        f.debug_struct("AudioMeta")
236            .field("info", &self.info())
237            .field("samples", &self.samples())
238            .field("offsets", &self.offsets())
239            .finish()
240    }
241}
242
243/// Meta containing Audio Level Indication: https://tools.ietf.org/html/rfc6464
244#[cfg(feature = "v1_20")]
245#[cfg_attr(docsrs, doc(cfg(feature = "v1_20")))]
246#[repr(transparent)]
247#[doc(alias = "GstAudioLevelMeta")]
248pub struct AudioLevelMeta(ffi::GstAudioLevelMeta);
249
250#[cfg(feature = "v1_20")]
251#[cfg_attr(docsrs, doc(cfg(feature = "v1_20")))]
252unsafe impl Send for AudioLevelMeta {}
253#[cfg(feature = "v1_20")]
254#[cfg_attr(docsrs, doc(cfg(feature = "v1_20")))]
255unsafe impl Sync for AudioLevelMeta {}
256
257#[cfg(feature = "v1_20")]
258#[cfg_attr(docsrs, doc(cfg(feature = "v1_20")))]
259impl AudioLevelMeta {
260    #[doc(alias = "gst_buffer_add_audio_level_meta")]
261    pub fn add(
262        buffer: &mut gst::BufferRef,
263        level: u8,
264        voice_activity: bool,
265    ) -> gst::MetaRefMut<Self, gst::meta::Standalone> {
266        skip_assert_initialized!();
267        unsafe {
268            let meta = ffi::gst_buffer_add_audio_level_meta(
269                buffer.as_mut_ptr(),
270                level,
271                voice_activity.into_glib(),
272            );
273
274            Self::from_mut_ptr(buffer, meta)
275        }
276    }
277
278    #[doc(alias = "get_level")]
279    #[inline]
280    pub fn level(&self) -> u8 {
281        self.0.level
282    }
283
284    #[doc(alias = "get_voice_activity")]
285    #[inline]
286    pub fn voice_activity(&self) -> bool {
287        unsafe { from_glib(self.0.voice_activity) }
288    }
289}
290
291#[cfg(feature = "v1_20")]
292#[cfg_attr(docsrs, doc(cfg(feature = "v1_20")))]
293unsafe impl MetaAPI for AudioLevelMeta {
294    type GstType = ffi::GstAudioLevelMeta;
295
296    #[doc(alias = "gst_audio_level_meta_api_get_type")]
297    #[inline]
298    fn meta_api() -> glib::Type {
299        unsafe { from_glib(ffi::gst_audio_level_meta_api_get_type()) }
300    }
301}
302
303#[cfg(feature = "v1_20")]
304#[cfg_attr(docsrs, doc(cfg(feature = "v1_20")))]
305impl fmt::Debug for AudioLevelMeta {
306    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
307        f.debug_struct("AudioLevelMeta")
308            .field("level", &self.level())
309            .field("voice_activity", &self.voice_activity())
310            .finish()
311    }
312}
313
314pub mod tags {
315    gst::impl_meta_tag!(Audio, crate::ffi::GST_META_TAG_AUDIO_STR);
316    gst::impl_meta_tag!(Channels, crate::ffi::GST_META_TAG_AUDIO_CHANNELS_STR);
317    gst::impl_meta_tag!(Rate, crate::ffi::GST_META_TAG_AUDIO_RATE_STR);
318    #[cfg(feature = "v1_24")]
319    gst::impl_meta_tag!(
320        DSDPlaneOffsets,
321        crate::ffi::GST_META_TAG_DSD_PLANE_OFFSETS_STR
322    );
323}
324
325#[cfg(test)]
326mod tests {
327    use super::*;
328
329    #[test]
330    fn test_add_get_audio_clipping_meta() {
331        use gst::prelude::*;
332
333        gst::init().unwrap();
334
335        let mut buffer = gst::Buffer::with_size(1024).unwrap();
336
337        let start = 1.default_format();
338        let stop = 2.default_format();
339
340        {
341            let cmeta = AudioClippingMeta::add(buffer.get_mut().unwrap(), start, stop);
342            assert_eq!(cmeta.start().try_into(), Ok(Some(start)));
343            assert_eq!(cmeta.end().try_into(), Ok(Some(stop)));
344        }
345
346        {
347            let cmeta = buffer.meta::<AudioClippingMeta>().unwrap();
348            assert_eq!(cmeta.start().try_into(), Ok(Some(start)));
349            assert_eq!(cmeta.end().try_into(), Ok(Some(stop)));
350
351            assert!(cmeta.has_tag::<tags::Audio>());
352        }
353    }
354
355    #[cfg(feature = "v1_20")]
356    #[test]
357    fn test_add_get_audio_level_meta() {
358        gst::init().unwrap();
359
360        let mut buffer = gst::Buffer::with_size(1024).unwrap();
361
362        {
363            let cmeta = AudioLevelMeta::add(buffer.get_mut().unwrap(), 10, true);
364            assert_eq!(cmeta.level(), 10);
365            assert!(cmeta.voice_activity());
366        }
367
368        {
369            let cmeta = buffer.meta::<AudioLevelMeta>().unwrap();
370            assert_eq!(cmeta.level(), 10);
371            assert!(cmeta.voice_activity());
372        }
373    }
374}