Skip to main content

apple_cf/cm/
format_description.rs

1//! `CMFormatDescription` - Media format description
2
3#![allow(dead_code)]
4
5use crate::ffi;
6use std::fmt;
7
8pub struct CMFormatDescription(*mut std::ffi::c_void);
9
10impl PartialEq for CMFormatDescription {
11    fn eq(&self, other: &Self) -> bool {
12        self.0 == other.0
13    }
14}
15
16impl Eq for CMFormatDescription {}
17
18impl std::hash::Hash for CMFormatDescription {
19    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
20        unsafe {
21            let hash_value = ffi::cm_format_description_hash(self.0);
22            hash_value.hash(state);
23        }
24    }
25}
26
27/// Common media type constants
28pub mod media_types {
29    use crate::utils::four_char_code::FourCharCode;
30
31    /// Video media type ('vide')
32    pub const VIDEO: FourCharCode = FourCharCode::from_bytes(*b"vide");
33    /// Audio media type ('soun')
34    pub const AUDIO: FourCharCode = FourCharCode::from_bytes(*b"soun");
35    /// Muxed media type ('mux ')
36    pub const MUXED: FourCharCode = FourCharCode::from_bytes(*b"mux ");
37    /// Text/subtitle media type ('text')
38    pub const TEXT: FourCharCode = FourCharCode::from_bytes(*b"text");
39    /// Closed caption media type ('clcp')
40    pub const CLOSED_CAPTION: FourCharCode = FourCharCode::from_bytes(*b"clcp");
41    /// Metadata media type ('meta')
42    pub const METADATA: FourCharCode = FourCharCode::from_bytes(*b"meta");
43    /// Timecode media type ('tmcd')
44    pub const TIMECODE: FourCharCode = FourCharCode::from_bytes(*b"tmcd");
45}
46
47/// Common codec type constants
48pub mod codec_types {
49    use crate::utils::four_char_code::FourCharCode;
50
51    // Video codecs
52    /// H.264/AVC ('avc1')
53    pub const H264: FourCharCode = FourCharCode::from_bytes(*b"avc1");
54    /// HEVC/H.265 ('hvc1')
55    pub const HEVC: FourCharCode = FourCharCode::from_bytes(*b"hvc1");
56    /// HEVC/H.265 alternative ('hev1')
57    pub const HEVC_2: FourCharCode = FourCharCode::from_bytes(*b"hev1");
58    /// JPEG ('jpeg')
59    pub const JPEG: FourCharCode = FourCharCode::from_bytes(*b"jpeg");
60    /// Apple `ProRes` 422 ('apcn')
61    pub const PRORES_422: FourCharCode = FourCharCode::from_bytes(*b"apcn");
62    /// Apple `ProRes` 4444 ('ap4h')
63    pub const PRORES_4444: FourCharCode = FourCharCode::from_bytes(*b"ap4h");
64
65    // Audio codecs
66    /// AAC ('aac ')
67    pub const AAC: FourCharCode = FourCharCode::from_bytes(*b"aac ");
68    /// Linear PCM ('lpcm')
69    pub const LPCM: FourCharCode = FourCharCode::from_bytes(*b"lpcm");
70    /// Apple Lossless ('alac')
71    pub const ALAC: FourCharCode = FourCharCode::from_bytes(*b"alac");
72    /// Opus ('opus')
73    pub const OPUS: FourCharCode = FourCharCode::from_bytes(*b"opus");
74    /// FLAC ('flac')
75    pub const FLAC: FourCharCode = FourCharCode::from_bytes(*b"flac");
76}
77
78impl CMFormatDescription {
79    pub fn from_raw(ptr: *mut std::ffi::c_void) -> Option<Self> {
80        if ptr.is_null() {
81            None
82        } else {
83            Some(Self(ptr))
84        }
85    }
86
87    /// # Safety
88    /// The caller must ensure the pointer is a valid `CMFormatDescription` pointer.
89    pub const unsafe fn from_ptr(ptr: *mut std::ffi::c_void) -> Self {
90        Self(ptr)
91    }
92
93    #[must_use] 
94    pub const fn as_ptr(&self) -> *mut std::ffi::c_void {
95        self.0
96    }
97
98    /// Get the media type as a raw u32 value
99    #[must_use] 
100    pub fn media_type_raw(&self) -> u32 {
101        unsafe { ffi::cm_format_description_get_media_type(self.0) }
102    }
103
104    /// Get the media type as `FourCharCode`
105    #[must_use] 
106    pub fn media_type(&self) -> crate::utils::four_char_code::FourCharCode {
107        crate::utils::four_char_code::FourCharCode::from(self.media_type_raw())
108    }
109
110    /// Get the media subtype (codec type) as a raw u32 value
111    #[must_use] 
112    pub fn media_subtype_raw(&self) -> u32 {
113        unsafe { ffi::cm_format_description_get_media_subtype(self.0) }
114    }
115
116    /// Get the media subtype as `FourCharCode`
117    #[must_use] 
118    pub fn media_subtype(&self) -> crate::utils::four_char_code::FourCharCode {
119        crate::utils::four_char_code::FourCharCode::from(self.media_subtype_raw())
120    }
121
122    /// Get format description extensions
123    #[must_use] 
124    pub fn extensions(&self) -> Option<*const std::ffi::c_void> {
125        unsafe {
126            let ptr = ffi::cm_format_description_get_extensions(self.0);
127            if ptr.is_null() {
128                None
129            } else {
130                Some(ptr)
131            }
132        }
133    }
134
135    /// Check if this is a video format description
136    #[must_use] 
137    pub fn is_video(&self) -> bool {
138        self.media_type() == media_types::VIDEO
139    }
140
141    /// Check if this is an audio format description
142    #[must_use] 
143    pub fn is_audio(&self) -> bool {
144        self.media_type() == media_types::AUDIO
145    }
146
147    /// Check if this is a muxed format description
148    #[must_use] 
149    pub fn is_muxed(&self) -> bool {
150        self.media_type() == media_types::MUXED
151    }
152
153    /// Check if this is a text/subtitle format description
154    #[must_use] 
155    pub fn is_text(&self) -> bool {
156        self.media_type() == media_types::TEXT
157    }
158
159    /// Check if this is a closed caption format description
160    #[must_use] 
161    pub fn is_closed_caption(&self) -> bool {
162        self.media_type() == media_types::CLOSED_CAPTION
163    }
164
165    /// Check if this is a metadata format description
166    #[must_use] 
167    pub fn is_metadata(&self) -> bool {
168        self.media_type() == media_types::METADATA
169    }
170
171    /// Check if this is a timecode format description
172    #[must_use] 
173    pub fn is_timecode(&self) -> bool {
174        self.media_type() == media_types::TIMECODE
175    }
176
177    /// Get a human-readable string for the media type
178    #[must_use] 
179    pub fn media_type_string(&self) -> String {
180        self.media_type().display()
181    }
182
183    /// Get a human-readable string for the media subtype (codec)
184    #[must_use] 
185    pub fn media_subtype_string(&self) -> String {
186        self.media_subtype().display()
187    }
188
189    /// Check if the codec is H.264
190    #[must_use] 
191    pub fn is_h264(&self) -> bool {
192        self.media_subtype() == codec_types::H264
193    }
194
195    /// Check if the codec is HEVC/H.265
196    #[must_use] 
197    pub fn is_hevc(&self) -> bool {
198        let subtype = self.media_subtype();
199        subtype == codec_types::HEVC || subtype == codec_types::HEVC_2
200    }
201
202    /// Check if the codec is AAC
203    #[must_use] 
204    pub fn is_aac(&self) -> bool {
205        self.media_subtype() == codec_types::AAC
206    }
207
208    /// Check if the codec is PCM
209    #[must_use] 
210    pub fn is_pcm(&self) -> bool {
211        self.media_subtype() == codec_types::LPCM
212    }
213
214    /// Check if the codec is `ProRes`
215    #[must_use] 
216    pub fn is_prores(&self) -> bool {
217        let subtype = self.media_subtype();
218        subtype == codec_types::PRORES_422 || subtype == codec_types::PRORES_4444
219    }
220
221    /// Check if the codec is Apple Lossless (ALAC)
222    #[must_use] 
223    pub fn is_alac(&self) -> bool {
224        self.media_subtype() == codec_types::ALAC
225    }
226
227    // Audio format description methods
228
229    /// Get the audio sample rate in Hz
230    ///
231    /// Returns `None` if this is not an audio format description.
232    #[must_use] 
233    pub fn audio_sample_rate(&self) -> Option<f64> {
234        if !self.is_audio() {
235            return None;
236        }
237        let rate = unsafe { ffi::cm_format_description_get_audio_sample_rate(self.0) };
238        if rate > 0.0 {
239            Some(rate)
240        } else {
241            None
242        }
243    }
244
245    /// Get the number of audio channels
246    ///
247    /// Returns `None` if this is not an audio format description.
248    #[must_use] 
249    pub fn audio_channel_count(&self) -> Option<u32> {
250        if !self.is_audio() {
251            return None;
252        }
253        let count = unsafe { ffi::cm_format_description_get_audio_channel_count(self.0) };
254        if count > 0 {
255            Some(count)
256        } else {
257            None
258        }
259    }
260
261    /// Get the bits per audio channel
262    ///
263    /// Returns `None` if this is not an audio format description.
264    #[must_use] 
265    pub fn audio_bits_per_channel(&self) -> Option<u32> {
266        if !self.is_audio() {
267            return None;
268        }
269        let bits = unsafe { ffi::cm_format_description_get_audio_bits_per_channel(self.0) };
270        if bits > 0 {
271            Some(bits)
272        } else {
273            None
274        }
275    }
276
277    /// Get the bytes per audio frame
278    ///
279    /// Returns `None` if this is not an audio format description.
280    #[must_use] 
281    pub fn audio_bytes_per_frame(&self) -> Option<u32> {
282        if !self.is_audio() {
283            return None;
284        }
285        let bytes = unsafe { ffi::cm_format_description_get_audio_bytes_per_frame(self.0) };
286        if bytes > 0 {
287            Some(bytes)
288        } else {
289            None
290        }
291    }
292
293    /// Get the audio format flags
294    ///
295    /// Returns `None` if this is not an audio format description.
296    #[must_use] 
297    pub fn audio_format_flags(&self) -> Option<u32> {
298        if !self.is_audio() {
299            return None;
300        }
301        Some(unsafe { ffi::cm_format_description_get_audio_format_flags(self.0) })
302    }
303
304    /// Check if audio is float format (based on format flags)
305    #[must_use] 
306    pub fn audio_is_float(&self) -> bool {
307        // kAudioFormatFlagIsFloat = 1
308        self.audio_format_flags().is_some_and(|f| f & 1 != 0)
309    }
310
311    /// Check if audio is big-endian (based on format flags)
312    #[must_use] 
313    pub fn audio_is_big_endian(&self) -> bool {
314        // kAudioFormatFlagIsBigEndian = 2
315        self.audio_format_flags().is_some_and(|f| f & 2 != 0)
316    }
317}
318
319impl Clone for CMFormatDescription {
320    fn clone(&self) -> Self {
321        unsafe {
322            let ptr = ffi::cm_format_description_retain(self.0);
323            Self(ptr)
324        }
325    }
326}
327
328impl Drop for CMFormatDescription {
329    fn drop(&mut self) {
330        unsafe {
331            ffi::cm_format_description_release(self.0);
332        }
333    }
334}
335
336unsafe impl Send for CMFormatDescription {}
337unsafe impl Sync for CMFormatDescription {}
338
339impl fmt::Debug for CMFormatDescription {
340    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
341        f.debug_struct("CMFormatDescription")
342            .field("media_type", &self.media_type_string())
343            .field("codec", &self.media_subtype_string())
344            .finish()
345    }
346}
347
348impl fmt::Display for CMFormatDescription {
349    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
350        write!(
351            f,
352            "CMFormatDescription(type: 0x{:08X}, subtype: 0x{:08X})",
353            self.media_type_raw(),
354            self.media_subtype_raw()
355        )
356    }
357}