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