1 | // Take a look at the license at the top of the repository in the LICENSE file. |
2 | |
3 | use std::fmt; |
4 | #[cfg (feature = "v1_16" )] |
5 | #[cfg_attr (docsrs, doc(cfg(feature = "v1_16" )))] |
6 | use std::ptr; |
7 | #[cfg (feature = "v1_16" )] |
8 | #[cfg_attr (docsrs, doc(cfg(feature = "v1_16" )))] |
9 | use std::slice; |
10 | |
11 | use glib::translate::{from_glib, IntoGlib}; |
12 | #[cfg (feature = "v1_16" )] |
13 | #[cfg_attr (docsrs, doc(cfg(feature = "v1_16" )))] |
14 | use glib::translate::{from_glib_none, ToGlibPtr}; |
15 | use gst::prelude::*; |
16 | |
17 | use crate::ffi; |
18 | |
19 | #[repr (transparent)] |
20 | #[doc (alias = "GstAudioClippingMeta" )] |
21 | pub struct AudioClippingMeta(ffi::GstAudioClippingMeta); |
22 | |
23 | unsafe impl Send for AudioClippingMeta {} |
24 | unsafe impl Sync for AudioClippingMeta {} |
25 | |
26 | impl 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 | |
60 | unsafe 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(val:ffi::gst_audio_clipping_meta_api_get_type()) } |
67 | } |
68 | } |
69 | |
70 | impl fmt::Debug for AudioClippingMeta { |
71 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
72 | f&mut DebugStruct<'_, '_>.debug_struct("AudioClippingMeta" ) |
73 | .field("start" , &self.start()) |
74 | .field(name:"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" )] |
83 | pub struct AudioMeta(ffi::GstAudioMeta); |
84 | |
85 | #[cfg (feature = "v1_16" )] |
86 | #[cfg_attr (docsrs, doc(cfg(feature = "v1_16" )))] |
87 | unsafe impl Send for AudioMeta {} |
88 | #[cfg (feature = "v1_16" )] |
89 | #[cfg_attr (docsrs, doc(cfg(feature = "v1_16" )))] |
90 | unsafe impl Sync for AudioMeta {} |
91 | |
92 | #[cfg (feature = "v1_16" )] |
93 | #[cfg_attr (docsrs, doc(cfg(feature = "v1_16" )))] |
94 | impl 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" )))] |
207 | unsafe 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" )))] |
219 | impl 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" )] |
233 | pub struct AudioLevelMeta(ffi::GstAudioLevelMeta); |
234 | |
235 | #[cfg (feature = "v1_20" )] |
236 | #[cfg_attr (docsrs, doc(cfg(feature = "v1_20" )))] |
237 | unsafe impl Send for AudioLevelMeta {} |
238 | #[cfg (feature = "v1_20" )] |
239 | #[cfg_attr (docsrs, doc(cfg(feature = "v1_20" )))] |
240 | unsafe impl Sync for AudioLevelMeta {} |
241 | |
242 | #[cfg (feature = "v1_20" )] |
243 | #[cfg_attr (docsrs, doc(cfg(feature = "v1_20" )))] |
244 | impl 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" )))] |
278 | unsafe 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" )))] |
290 | impl 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 | |
299 | pub 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)] |
311 | mod 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 | } |
360 | |