Skip to main content

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!(
161                                "Overlapping audio channel offsets: offset {} for channel {} and offset {} for channel {} with a plane size of {}",
162                                offset,
163                                i,
164                                other_offset,
165                                j,
166                                plane_size
167                            ));
168                        }
169                    }
170                }
171
172                max_offset.unwrap()
173            };
174
175            if max_offset + plane_size > buffer.size() {
176                return Err(glib::bool_error!(
177                    "Audio channel offsets out of bounds: max offset {} with plane size {} and buffer size {}",
178                    max_offset,
179                    plane_size,
180                    buffer.size()
181                ));
182            }
183        }
184
185        unsafe {
186            let meta = ffi::gst_buffer_add_audio_meta(
187                buffer.as_mut_ptr(),
188                info.to_glib_none().0,
189                samples,
190                if offsets.is_empty() {
191                    ptr::null_mut()
192                } else {
193                    offsets.as_ptr() as *mut _
194                },
195            );
196
197            if meta.is_null() {
198                return Err(glib::bool_error!("Failed to add audio meta"));
199            }
200
201            Ok(Self::from_mut_ptr(buffer, meta))
202        }
203    }
204
205    #[doc(alias = "get_info")]
206    #[inline]
207    pub fn info(&self) -> &crate::AudioInfo {
208        unsafe { &*(&self.0.info as *const ffi::GstAudioInfo as *const crate::AudioInfo) }
209    }
210
211    #[doc(alias = "get_samples")]
212    #[inline]
213    pub fn samples(&self) -> usize {
214        self.0.samples
215    }
216
217    #[doc(alias = "get_offsets")]
218    #[inline]
219    pub fn offsets(&self) -> &[usize] {
220        if self.0.offsets.is_null() || self.0.info.channels < 1 {
221            return &[];
222        }
223
224        unsafe { slice::from_raw_parts(self.0.offsets, self.0.info.channels as usize) }
225    }
226}
227
228#[cfg(feature = "v1_16")]
229#[cfg_attr(docsrs, doc(cfg(feature = "v1_16")))]
230unsafe impl MetaAPI for AudioMeta {
231    type GstType = ffi::GstAudioMeta;
232
233    #[doc(alias = "gst_audio_meta_api_get_type")]
234    #[inline]
235    fn meta_api() -> glib::Type {
236        unsafe { from_glib(ffi::gst_audio_meta_api_get_type()) }
237    }
238}
239
240#[cfg(feature = "v1_16")]
241#[cfg_attr(docsrs, doc(cfg(feature = "v1_16")))]
242impl fmt::Debug for AudioMeta {
243    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
244        f.debug_struct("AudioMeta")
245            .field("info", &self.info())
246            .field("samples", &self.samples())
247            .field("offsets", &self.offsets())
248            .finish()
249    }
250}
251
252/// Meta containing Audio Level Indication: https://tools.ietf.org/html/rfc6464
253#[cfg(feature = "v1_20")]
254#[cfg_attr(docsrs, doc(cfg(feature = "v1_20")))]
255#[repr(transparent)]
256#[doc(alias = "GstAudioLevelMeta")]
257pub struct AudioLevelMeta(ffi::GstAudioLevelMeta);
258
259#[cfg(feature = "v1_20")]
260#[cfg_attr(docsrs, doc(cfg(feature = "v1_20")))]
261unsafe impl Send for AudioLevelMeta {}
262#[cfg(feature = "v1_20")]
263#[cfg_attr(docsrs, doc(cfg(feature = "v1_20")))]
264unsafe impl Sync for AudioLevelMeta {}
265
266#[cfg(feature = "v1_20")]
267#[cfg_attr(docsrs, doc(cfg(feature = "v1_20")))]
268impl AudioLevelMeta {
269    #[doc(alias = "gst_buffer_add_audio_level_meta")]
270    pub fn add(
271        buffer: &mut gst::BufferRef,
272        level: u8,
273        voice_activity: bool,
274    ) -> gst::MetaRefMut<'_, Self, gst::meta::Standalone> {
275        skip_assert_initialized!();
276        unsafe {
277            let meta = ffi::gst_buffer_add_audio_level_meta(
278                buffer.as_mut_ptr(),
279                level,
280                voice_activity.into_glib(),
281            );
282
283            Self::from_mut_ptr(buffer, meta)
284        }
285    }
286
287    #[doc(alias = "get_level")]
288    #[inline]
289    pub fn level(&self) -> u8 {
290        self.0.level
291    }
292
293    #[doc(alias = "get_voice_activity")]
294    #[inline]
295    pub fn voice_activity(&self) -> bool {
296        unsafe { from_glib(self.0.voice_activity) }
297    }
298}
299
300#[cfg(feature = "v1_20")]
301#[cfg_attr(docsrs, doc(cfg(feature = "v1_20")))]
302unsafe impl MetaAPI for AudioLevelMeta {
303    type GstType = ffi::GstAudioLevelMeta;
304
305    #[doc(alias = "gst_audio_level_meta_api_get_type")]
306    #[inline]
307    fn meta_api() -> glib::Type {
308        unsafe { from_glib(ffi::gst_audio_level_meta_api_get_type()) }
309    }
310}
311
312#[cfg(feature = "v1_20")]
313#[cfg_attr(docsrs, doc(cfg(feature = "v1_20")))]
314impl fmt::Debug for AudioLevelMeta {
315    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
316        f.debug_struct("AudioLevelMeta")
317            .field("level", &self.level())
318            .field("voice_activity", &self.voice_activity())
319            .finish()
320    }
321}
322
323pub mod tags {
324    gst::impl_meta_tag!(Audio, crate::ffi::GST_META_TAG_AUDIO_STR);
325    gst::impl_meta_tag!(Channels, crate::ffi::GST_META_TAG_AUDIO_CHANNELS_STR);
326    gst::impl_meta_tag!(Rate, crate::ffi::GST_META_TAG_AUDIO_RATE_STR);
327    #[cfg(feature = "v1_24")]
328    gst::impl_meta_tag!(
329        DSDPlaneOffsets,
330        crate::ffi::GST_META_TAG_DSD_PLANE_OFFSETS_STR
331    );
332}
333
334#[cfg(test)]
335mod tests {
336    use super::*;
337
338    #[test]
339    fn test_add_get_audio_clipping_meta() {
340        use gst::prelude::*;
341
342        gst::init().unwrap();
343
344        let mut buffer = gst::Buffer::with_size(1024).unwrap();
345
346        let start = 1.default_format();
347        let stop = 2.default_format();
348
349        {
350            let cmeta = AudioClippingMeta::add(buffer.get_mut().unwrap(), start, stop);
351            assert_eq!(cmeta.start().try_into(), Ok(Some(start)));
352            assert_eq!(cmeta.end().try_into(), Ok(Some(stop)));
353        }
354
355        {
356            let cmeta = buffer.meta::<AudioClippingMeta>().unwrap();
357            assert_eq!(cmeta.start().try_into(), Ok(Some(start)));
358            assert_eq!(cmeta.end().try_into(), Ok(Some(stop)));
359
360            assert!(cmeta.has_tag::<tags::Audio>());
361        }
362    }
363
364    #[cfg(feature = "v1_20")]
365    #[test]
366    fn test_add_get_audio_level_meta() {
367        gst::init().unwrap();
368
369        let mut buffer = gst::Buffer::with_size(1024).unwrap();
370
371        {
372            let cmeta = AudioLevelMeta::add(buffer.get_mut().unwrap(), 10, true);
373            assert_eq!(cmeta.level(), 10);
374            assert!(cmeta.voice_activity());
375        }
376
377        {
378            let cmeta = buffer.meta::<AudioLevelMeta>().unwrap();
379            assert_eq!(cmeta.level(), 10);
380            assert!(cmeta.voice_activity());
381        }
382    }
383}