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