Coverage Report

Created: 2026-06-10 07:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mp4parse-rust/mp4parse_capi/src/lib.rs
Line
Count
Source
1
//! C API for mp4parse module.
2
//!
3
//! Parses ISO Base Media Format aka video/mp4 streams.
4
//!
5
//! # Examples
6
//!
7
//! ```rust
8
//! use std::io::Read;
9
//!
10
//! extern fn buf_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize {
11
//!    let mut input: &mut std::fs::File = unsafe { &mut *(userdata as *mut _) };
12
//!    let mut buf = unsafe { std::slice::from_raw_parts_mut(buf, size) };
13
//!    match input.read(&mut buf) {
14
//!        Ok(n) => n as isize,
15
//!        Err(_) => -1,
16
//!    }
17
//! }
18
//! let capi_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
19
//! let mut file = std::fs::File::open(capi_dir + "/../mp4parse/tests/minimal.mp4").unwrap();
20
//! let io = mp4parse_capi::Mp4parseIo {
21
//!     read: Some(buf_read),
22
//!     userdata: &mut file as *mut _ as *mut std::os::raw::c_void
23
//! };
24
//! let mut parser = std::ptr::null_mut();
25
//! unsafe {
26
//!     let rv = mp4parse_capi::mp4parse_new(&io, &mut parser);
27
//!     assert_eq!(rv, mp4parse_capi::Mp4parseStatus::Ok);
28
//!     assert!(!parser.is_null());
29
//!     mp4parse_capi::mp4parse_free(parser);
30
//! }
31
//! ```
32
33
// This Source Code Form is subject to the terms of the Mozilla Public
34
// License, v. 2.0. If a copy of the MPL was not distributed with this
35
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
36
37
use byteorder::WriteBytesExt;
38
use fallible_collections::TryReserveError;
39
use mp4parse::unstable::rational_scale;
40
use std::convert::TryFrom;
41
use std::convert::TryInto;
42
use std::hash::Hash;
43
44
use std::io::Read;
45
46
// Symbols we need from our rust api.
47
use mp4parse::serialize_opus_header;
48
use mp4parse::unstable::{create_sample_table, CheckedInteger, Indice};
49
use mp4parse::AV1ConfigBox;
50
use mp4parse::AudioCodecSpecific;
51
use mp4parse::AvifContext;
52
use mp4parse::CodecType;
53
use mp4parse::MediaContext;
54
// Re-exported so consumers don't have to depend on mp4parse as well
55
pub use mp4parse::ParseStrictness;
56
use mp4parse::SampleEntry;
57
pub use mp4parse::Status as Mp4parseStatus;
58
use mp4parse::Track;
59
use mp4parse::TrackType;
60
use mp4parse::TryBox;
61
use mp4parse::TryHashMap;
62
use mp4parse::TryVec;
63
use mp4parse::VideoCodecSpecific;
64
65
// To ensure we don't use stdlib allocating types by accident
66
#[allow(dead_code)]
67
struct Vec;
68
#[allow(dead_code)]
69
struct Box;
70
#[allow(dead_code)]
71
struct HashMap;
72
#[allow(dead_code)]
73
struct String;
74
75
#[repr(C)]
76
#[derive(PartialEq, Eq, Debug, Default)]
77
pub enum Mp4parseTrackType {
78
    #[default]
79
    Video = 0,
80
    Picture = 1,
81
    AuxiliaryVideo = 2,
82
    Audio = 3,
83
    Metadata = 4,
84
}
85
86
#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
87
#[repr(C)]
88
#[derive(PartialEq, Eq, Debug, Default)]
89
pub enum Mp4parseCodec {
90
    #[default]
91
    Unknown,
92
    Aac,
93
    Flac,
94
    Opus,
95
    Avc,
96
    Vp9,
97
    Av1,
98
    Mp3,
99
    Mp4v,
100
    Jpeg, // for QT JPEG atom in video track
101
    Ac3,
102
    Ec3,
103
    Alac,
104
    H263,
105
    Hevc,
106
    #[cfg(feature = "3gpp")]
107
    AMRNB,
108
    #[cfg(feature = "3gpp")]
109
    AMRWB,
110
    XHEAAC, // xHE-AAC (Extended High Efficiency AAC)
111
}
112
113
#[repr(C)]
114
#[derive(PartialEq, Eq, Debug, Default)]
115
pub enum Mp4ParseEncryptionSchemeType {
116
    #[default]
117
    None,
118
    Cenc,
119
    Cbc1,
120
    Cens,
121
    Cbcs,
122
    // Schemes also have a version component. At the time of writing, this does
123
    // not impact handling, so we do not expose it. Note that this may need to
124
    // be exposed in future, should the spec change.
125
}
126
127
#[repr(C)]
128
#[derive(Default, Debug)]
129
pub struct Mp4parseTrackInfo {
130
    pub track_type: Mp4parseTrackType,
131
    pub track_id: u32,
132
    pub duration: u64,
133
    pub media_time: CheckedInteger<i64>,
134
    pub time_scale: u32,
135
}
136
137
#[repr(C)]
138
#[derive(Debug)]
139
pub struct Mp4parseByteData {
140
    pub length: usize,
141
    // cheddar can't handle generic type, so it needs to be multiple data types here.
142
    pub data: *const u8,
143
    pub indices: *const Indice,
144
}
145
146
impl Mp4parseByteData {
147
1.24k
    fn with_data(slice: &[u8]) -> Self {
148
        Self {
149
1.24k
            length: slice.len(),
150
1.24k
            data: if !slice.is_empty() {
151
62
                slice.as_ptr()
152
            } else {
153
1.18k
                std::ptr::null()
154
            },
155
1.24k
            indices: std::ptr::null(),
156
        }
157
1.24k
    }
158
}
159
160
impl Default for Mp4parseByteData {
161
294k
    fn default() -> Self {
162
294k
        Self {
163
294k
            length: 0,
164
294k
            data: std::ptr::null(),
165
294k
            indices: std::ptr::null(),
166
294k
        }
167
294k
    }
168
}
169
170
impl Mp4parseByteData {
171
    /// Populate the data pointer and length from a byte slice.
172
    ///
173
    /// The returned pointer borrows directly from `data`; the caller must
174
    /// ensure the backing storage outlives any C code that dereferences it.
175
    /// For empty slices we return a null pointer to avoid exposing Rust's
176
    /// dangling empty-slice pointer (e.g. 0x1) over FFI.
177
    /// See [`Mp4parseParser`] for the caching strategy that guarantees this.
178
47.1k
    fn set_data(&mut self, data: &[u8]) {
179
47.1k
        self.length = data.len();
180
47.1k
        self.data = if data.is_empty() {
181
715
            std::ptr::null()
182
        } else {
183
46.3k
            data.as_ptr()
184
        };
185
47.1k
    }
186
187
    /// For empty slices we return a null pointer to avoid exposing Rust's
188
    /// dangling empty-slice pointer (e.g. 0x1) over FFI.
189
629
    fn set_indices(&mut self, data: &[Indice]) {
190
629
        self.length = data.len();
191
629
        self.indices = if data.is_empty() {
192
502
            std::ptr::null()
193
        } else {
194
127
            data.as_ptr()
195
        };
196
629
    }
197
}
198
199
#[repr(C)]
200
#[derive(Default)]
201
pub struct Mp4parsePsshInfo {
202
    pub data: Mp4parseByteData,
203
}
204
205
#[repr(u8)]
206
#[derive(Debug, PartialEq, Eq, Default)]
207
pub enum OptionalFourCc {
208
    #[default]
209
    None,
210
    Some([u8; 4]),
211
}
212
213
#[repr(C)]
214
#[derive(Default, Debug)]
215
pub struct Mp4parseSinfInfo {
216
    pub original_format: OptionalFourCc,
217
    pub scheme_type: Mp4ParseEncryptionSchemeType,
218
    pub is_encrypted: u8,
219
    pub iv_size: u8,
220
    pub kid: Mp4parseByteData,
221
    // Members for pattern encryption schemes, may be 0 (u8) or empty
222
    // (Mp4parseByteData) if pattern encryption is not in use
223
    pub crypt_byte_block: u8,
224
    pub skip_byte_block: u8,
225
    pub constant_iv: Mp4parseByteData,
226
    // End pattern encryption scheme members
227
}
228
229
#[repr(C)]
230
#[derive(Default, Debug)]
231
pub struct Mp4parseTrackAudioSampleInfo {
232
    pub codec_type: Mp4parseCodec,
233
    pub channels: u16,
234
    pub bit_depth: u16,
235
    pub sample_rate: u32,
236
    pub profile: u16,
237
    pub extended_profile: u16,
238
    pub codec_specific_config: Mp4parseByteData,
239
    pub extra_data: Mp4parseByteData,
240
    pub protected_data: Mp4parseSinfInfo,
241
}
242
243
#[repr(C)]
244
#[derive(Debug)]
245
pub struct Mp4parseTrackAudioInfo {
246
    pub sample_info_count: u32,
247
    pub sample_info: *const Mp4parseTrackAudioSampleInfo,
248
}
249
250
impl Default for Mp4parseTrackAudioInfo {
251
85.6k
    fn default() -> Self {
252
85.6k
        Self {
253
85.6k
            sample_info_count: 0,
254
85.6k
            sample_info: std::ptr::null(),
255
85.6k
        }
256
85.6k
    }
257
}
258
259
/// Mastering display colour volume from an `mdcv` box (ISO 14496-12).
260
/// Primary indices are R\[0\], G\[1\], B\[2\]. Divide chromaticity values by 50000.0
261
/// and luminance values by 10000.0 to obtain physical units (chromaticity, cd/m²).
262
#[repr(C)]
263
#[derive(Default, Debug)]
264
pub struct Mp4parseMasteringDisplayColourVolume {
265
    pub display_primaries_x: [u16; 3],
266
    pub display_primaries_y: [u16; 3],
267
    pub white_point_x: u16,
268
    pub white_point_y: u16,
269
    /// In units of 0.0001 cd/m²
270
    pub max_display_mastering_luminance: u32,
271
    /// In units of 0.0001 cd/m²
272
    pub min_display_mastering_luminance: u32,
273
}
274
275
/// Content light level from a `clli` box (ISO 14496-12).
276
#[repr(C)]
277
#[derive(Default, Debug)]
278
pub struct Mp4parseContentLightLevel {
279
    /// Maximum content light level in cd/m²
280
    pub max_content_light_level: u16,
281
    /// Maximum picture average light level in cd/m²
282
    pub max_pic_average_light_level: u16,
283
}
284
285
#[repr(C)]
286
#[derive(Default, Debug)]
287
pub struct Mp4parseTrackVideoSampleInfo {
288
    pub codec_type: Mp4parseCodec,
289
    pub image_width: u16,
290
    pub image_height: u16,
291
    pub extra_data: Mp4parseByteData,
292
    pub protected_data: Mp4parseSinfInfo,
293
    /// True when a `colr` box with `colour_type = 'nclx'` was present. When false,
294
    /// the CICP fields below are all zero and must not be interpreted.
295
    pub has_colour_info: bool,
296
    /// CICP colour primaries (ISO 23091-2 § 8.1). Valid only when `has_colour_info`.
297
    pub colour_primaries: u8,
298
    /// CICP transfer characteristics (ISO 23091-2 § 8.2). Valid only when `has_colour_info`.
299
    pub transfer_characteristics: u8,
300
    /// CICP matrix coefficients (ISO 23091-2 § 8.3). Valid only when `has_colour_info`.
301
    /// Note: value 0 is a valid CICP value (Identity/GBR), not an absence indicator.
302
    pub matrix_coefficients: u8,
303
    /// Full range flag from the colr nclx box. Valid only when `has_colour_info`.
304
    pub full_range_flag: bool,
305
    /// True when an `mdcv` box was present. When false, `mastering_display` must not be read.
306
    pub has_mastering_display: bool,
307
    pub mastering_display: Mp4parseMasteringDisplayColourVolume,
308
    /// True when a `clli` box was present. When false, `content_light_level` must not be read.
309
    pub has_content_light_level: bool,
310
    pub content_light_level: Mp4parseContentLightLevel,
311
}
312
313
#[repr(C)]
314
#[derive(Debug)]
315
pub struct Mp4parseTrackVideoInfo {
316
    pub display_width: u32,
317
    pub display_height: u32,
318
    pub rotation: u16,
319
    pub sample_info_count: u32,
320
    pub sample_info: *const Mp4parseTrackVideoSampleInfo,
321
    pub pixel_aspect_ratio: f32,
322
}
323
324
impl Default for Mp4parseTrackVideoInfo {
325
24.7k
    fn default() -> Self {
326
24.7k
        Self {
327
24.7k
            display_width: 0,
328
24.7k
            display_height: 0,
329
24.7k
            rotation: 0,
330
24.7k
            sample_info_count: 0,
331
24.7k
            sample_info: std::ptr::null(),
332
24.7k
            pixel_aspect_ratio: 0.0,
333
24.7k
        }
334
24.7k
    }
335
}
336
337
#[repr(C)]
338
#[derive(Default, Debug)]
339
pub struct Mp4parseFragmentInfo {
340
    pub fragment_duration: u64, // in ticks
341
    pub time_scale: u64,
342
    // TODO:
343
    // info in trex box.
344
}
345
346
/// Parser state for MP4 files, exposed to C callers via raw pointer.
347
///
348
/// # Pointer stability
349
///
350
/// Several C API functions return raw pointers into data cached on this
351
/// struct (e.g. sample descriptions, indice tables, PSSH data). Those
352
/// pointers remain valid for the lifetime of the parser, because each
353
/// getter populates its cache **at most once** per key and never replaces
354
/// or reallocates the cached entry afterward.
355
///
356
/// If you add a new getter that hands a raw pointer to C:
357
/// - Store the backing data on this struct so it lives long enough.
358
/// - Return the existing cached entry when the same key is requested
359
///   again; **never** unconditionally rebuild and re-insert, as the
360
///   `insert` would drop the old value and invalidate the pointer C is
361
///   still holding.
362
/// - Add a `repeated_*` regression test that calls the getter twice and
363
///   asserts pointer equality.
364
#[derive(Default)]
365
pub struct Mp4parseParser {
366
    context: MediaContext,
367
    opus_header: TryHashMap<(u32, usize), TryVec<u8>>,
368
    pssh_data: Option<TryVec<u8>>,
369
    sample_table: TryHashMap<u32, TryVec<Indice>>,
370
    // Store a mapping from track index (not id) to associated sample
371
    // descriptions. Because each track has a variable number of sample
372
    // descriptions, and because we need the data to live long enough to be
373
    // copied out by callers, we store these on the parser struct.
374
    audio_track_sample_descriptions: TryHashMap<u32, TryVec<Mp4parseTrackAudioSampleInfo>>,
375
    video_track_sample_descriptions: TryHashMap<u32, TryVec<Mp4parseTrackVideoSampleInfo>>,
376
}
377
378
#[repr(C)]
379
#[derive(Debug, Default)]
380
pub enum Mp4parseAvifLoopMode {
381
    #[default]
382
    NoEdits,
383
    LoopByCount,
384
    LoopInfinitely,
385
}
386
387
#[repr(C)]
388
#[derive(Debug)]
389
pub struct Mp4parseAvifInfo {
390
    pub premultiplied_alpha: bool,
391
    pub major_brand: [u8; 4],
392
    pub unsupported_features_bitfield: u32,
393
    /// The size of the image; should never be null unless using permissive parsing
394
    pub spatial_extents: *const mp4parse::ImageSpatialExtentsProperty,
395
    pub nclx_colour_information: *const mp4parse::NclxColourInformation,
396
    pub icc_colour_information: Mp4parseByteData,
397
    pub image_rotation: mp4parse::ImageRotation,
398
    pub image_mirror: *const mp4parse::ImageMirror,
399
    pub pixel_aspect_ratio: *const mp4parse::PixelAspectRatio,
400
401
    /// Whether there is a `pitm` reference to the color image present.
402
    pub has_primary_item: bool,
403
    /// Bit depth for the item referenced by `pitm`, or 0 if values are inconsistent.
404
    pub primary_item_bit_depth: u8,
405
    /// Whether there is an `auxl` reference to the `pitm`-accompanying
406
    /// alpha image present.
407
    pub has_alpha_item: bool,
408
    /// Bit depth for the alpha item used by the `pitm`, or 0 if values are inconsistent.
409
    pub alpha_item_bit_depth: u8,
410
411
    /// Whether there is a sequence. Can be true with no primary image.
412
    pub has_sequence: bool,
413
    /// Indicates whether the EditListBox requests that the image be looped.
414
    pub loop_mode: Mp4parseAvifLoopMode,
415
    /// Number of times to loop the animation during playback.
416
    ///
417
    /// The duration of the animation specified in `elst` must be looped to fill the
418
    /// duration of the color track. If the resulting loop count is not an integer,
419
    /// then it will be ceiled to play past and fill the entire track's duration.
420
    pub loop_count: u64,
421
    /// The color track's ID, which must be valid if has_sequence is true.
422
    pub color_track_id: u32,
423
    pub color_track_bit_depth: u8,
424
    /// The track ID of the alpha track, will be 0 if no alpha track is present.
425
    pub alpha_track_id: u32,
426
    pub alpha_track_bit_depth: u8,
427
}
428
429
#[repr(C)]
430
#[derive(Debug)]
431
pub struct Mp4parseAvifImage {
432
    pub primary_image: Mp4parseByteData,
433
    /// If no alpha item exists, members' `.length` will be 0 and `.data` will be null
434
    pub alpha_image: Mp4parseByteData,
435
}
436
437
/// A unified interface for the parsers which have different contexts, but
438
/// share the same pattern of construction. This allows unification of
439
/// argument validation from C and minimizes the surface of unsafe code.
440
trait ContextParser
441
where
442
    Self: Sized,
443
{
444
    type Context;
445
446
    fn with_context(context: Self::Context) -> Self;
447
448
    fn read<T: Read>(io: &mut T, strictness: ParseStrictness) -> mp4parse::Result<Self::Context>;
449
}
450
451
impl Mp4parseParser {
452
13.3k
    fn context(&self) -> &MediaContext {
453
13.3k
        &self.context
454
13.3k
    }
455
456
110k
    fn context_mut(&mut self) -> &mut MediaContext {
457
110k
        &mut self.context
458
110k
    }
459
}
460
461
impl ContextParser for Mp4parseParser {
462
    type Context = MediaContext;
463
464
486
    fn with_context(context: Self::Context) -> Self {
465
486
        Self {
466
486
            context,
467
486
            ..Default::default()
468
486
        }
469
486
    }
470
471
1.02k
    fn read<T: Read>(io: &mut T, strictness: ParseStrictness) -> mp4parse::Result<Self::Context> {
472
1.02k
        let r = mp4parse::read_mp4(io, strictness);
473
1.02k
        log::debug!("mp4parse::read_mp4 -> {r:?}");
474
1.02k
        r
475
1.02k
    }
476
}
477
478
#[derive(Default)]
479
pub struct Mp4parseAvifParser {
480
    context: AvifContext,
481
    sample_table: TryHashMap<u32, TryVec<Indice>>,
482
}
483
484
trait CacheInsertExt<K, V> {
485
    fn insert_cache_entry(&mut self, key: K, value: V) -> Result<(), TryReserveError>;
486
}
487
488
impl<K, V> CacheInsertExt<K, V> for TryHashMap<K, V>
489
where
490
    K: Eq + Hash,
491
{
492
78.6k
    fn insert_cache_entry(&mut self, key: K, value: V) -> Result<(), TryReserveError> {
493
78.6k
        let replaced = self.insert(key, value)?;
494
78.6k
        debug_assert!(
495
0
            replaced.is_none(),
496
            "cache entries must never be replaced once published"
497
        );
498
78.6k
        Ok(())
499
78.6k
    }
<fallible_collections::hashmap::TryHashMap<(u32, usize), fallible_collections::vec::TryVec<u8>> as mp4parse_capi::CacheInsertExt<(u32, usize), fallible_collections::vec::TryVec<u8>>>::insert_cache_entry
Line
Count
Source
492
34.9k
    fn insert_cache_entry(&mut self, key: K, value: V) -> Result<(), TryReserveError> {
493
34.9k
        let replaced = self.insert(key, value)?;
494
34.9k
        debug_assert!(
495
0
            replaced.is_none(),
496
            "cache entries must never be replaced once published"
497
        );
498
34.9k
        Ok(())
499
34.9k
    }
<fallible_collections::hashmap::TryHashMap<u32, fallible_collections::vec::TryVec<mp4parse_capi::Mp4parseTrackAudioSampleInfo>> as mp4parse_capi::CacheInsertExt<u32, fallible_collections::vec::TryVec<mp4parse_capi::Mp4parseTrackAudioSampleInfo>>>::insert_cache_entry
Line
Count
Source
492
35.7k
    fn insert_cache_entry(&mut self, key: K, value: V) -> Result<(), TryReserveError> {
493
35.7k
        let replaced = self.insert(key, value)?;
494
35.7k
        debug_assert!(
495
0
            replaced.is_none(),
496
            "cache entries must never be replaced once published"
497
        );
498
35.7k
        Ok(())
499
35.7k
    }
<fallible_collections::hashmap::TryHashMap<u32, fallible_collections::vec::TryVec<mp4parse_capi::Mp4parseTrackVideoSampleInfo>> as mp4parse_capi::CacheInsertExt<u32, fallible_collections::vec::TryVec<mp4parse_capi::Mp4parseTrackVideoSampleInfo>>>::insert_cache_entry
Line
Count
Source
492
7.33k
    fn insert_cache_entry(&mut self, key: K, value: V) -> Result<(), TryReserveError> {
493
7.33k
        let replaced = self.insert(key, value)?;
494
7.33k
        debug_assert!(
495
0
            replaced.is_none(),
496
            "cache entries must never be replaced once published"
497
        );
498
7.33k
        Ok(())
499
7.33k
    }
<fallible_collections::hashmap::TryHashMap<u32, fallible_collections::vec::TryVec<mp4parse::unstable::Indice>> as mp4parse_capi::CacheInsertExt<u32, fallible_collections::vec::TryVec<mp4parse::unstable::Indice>>>::insert_cache_entry
Line
Count
Source
492
629
    fn insert_cache_entry(&mut self, key: K, value: V) -> Result<(), TryReserveError> {
493
629
        let replaced = self.insert(key, value)?;
494
629
        debug_assert!(
495
0
            replaced.is_none(),
496
            "cache entries must never be replaced once published"
497
        );
498
629
        Ok(())
499
629
    }
500
}
501
502
impl Mp4parseAvifParser {
503
832
    fn context(&self) -> &AvifContext {
504
832
        &self.context
505
832
    }
506
}
507
508
impl ContextParser for Mp4parseAvifParser {
509
    type Context = AvifContext;
510
511
416
    fn with_context(context: Self::Context) -> Self {
512
416
        Self {
513
416
            context,
514
416
            ..Default::default()
515
416
        }
516
416
    }
517
518
12.2k
    fn read<T: Read>(io: &mut T, strictness: ParseStrictness) -> mp4parse::Result<Self::Context> {
519
12.2k
        let r = mp4parse::read_avif(io, strictness);
520
12.2k
        if r.is_err() {
521
11.8k
            log::debug!("{r:?}");
522
416
        }
523
12.2k
        log::trace!("mp4parse::read_avif -> {r:?}");
524
12.2k
        r
525
12.2k
    }
526
}
527
528
#[repr(C)]
529
#[derive(Clone)]
530
pub struct Mp4parseIo {
531
    pub read: Option<
532
        extern "C" fn(buffer: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize,
533
    >,
534
    pub userdata: *mut std::os::raw::c_void,
535
}
536
537
impl Read for Mp4parseIo {
538
17.1M
    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
539
17.1M
        if buf.len() > isize::MAX as usize {
540
0
            return Err(std::io::Error::other(
541
0
                "buf length overflow in Mp4parseIo Read impl",
542
0
            ));
543
17.1M
        }
544
17.1M
        let rv = self.read.unwrap()(buf.as_mut_ptr(), buf.len(), self.userdata);
545
17.1M
        if rv >= 0 {
546
17.1M
            Ok(rv as usize)
547
        } else {
548
0
            Err(std::io::Error::other("I/O error in Mp4parseIo Read impl"))
549
        }
550
17.1M
    }
551
}
552
553
// C API wrapper functions.
554
555
/// Allocate an `Mp4parseParser*` to read from the supplied `Mp4parseIo` and
556
/// parse the content from the `Mp4parseIo` argument until EOF or error.
557
///
558
/// # Safety
559
///
560
/// This function is unsafe because it dereferences the `io` and `parser_out`
561
/// pointers given to it. The caller should ensure that the `Mp4ParseIo`
562
/// struct passed in is a valid pointer. The caller should also ensure the
563
/// members of io are valid: the `read` function should be sanely implemented,
564
/// and the `userdata` pointer should be valid. The `parser_out` should be a
565
/// valid pointer to a location containing a null pointer. Upon successful
566
/// return (`Mp4parseStatus::Ok`), that location will contain the address of
567
/// an `Mp4parseParser` allocated by this function.
568
///
569
/// To avoid leaking memory, any successful return of this function must be
570
/// paired with a call to `mp4parse_free`. In the event of error, no memory
571
/// will be allocated and `mp4parse_free` must *not* be called.
572
#[no_mangle]
573
1.02k
pub unsafe extern "C" fn mp4parse_new(
574
1.02k
    io: *const Mp4parseIo,
575
1.02k
    parser_out: *mut *mut Mp4parseParser,
576
1.02k
) -> Mp4parseStatus {
577
1.02k
    mp4parse_new_common(io, ParseStrictness::Normal, parser_out)
578
1.02k
}
579
580
/// Allocate an `Mp4parseAvifParser*` to read from the supplied `Mp4parseIo`.
581
///
582
/// See mp4parse_new; this function is identical except that it allocates an
583
/// `Mp4parseAvifParser`, which (when successful) must be paired with a call
584
/// to mp4parse_avif_free.
585
///
586
/// # Safety
587
///
588
/// Same as mp4parse_new.
589
#[no_mangle]
590
12.2k
pub unsafe extern "C" fn mp4parse_avif_new(
591
12.2k
    io: *const Mp4parseIo,
592
12.2k
    strictness: ParseStrictness,
593
12.2k
    parser_out: *mut *mut Mp4parseAvifParser,
594
12.2k
) -> Mp4parseStatus {
595
12.2k
    mp4parse_new_common(io, strictness, parser_out)
596
12.2k
}
597
598
13.2k
unsafe fn mp4parse_new_common<P: ContextParser>(
599
13.2k
    io: *const Mp4parseIo,
600
13.2k
    strictness: ParseStrictness,
601
13.2k
    parser_out: *mut *mut P,
602
13.2k
) -> Mp4parseStatus {
603
    // Validate arguments from C.
604
13.2k
    if io.is_null()
605
13.2k
        || (*io).userdata.is_null()
606
13.2k
        || (*io).read.is_none()
607
13.2k
        || parser_out.is_null()
608
13.2k
        || !(*parser_out).is_null()
609
    {
610
0
        Mp4parseStatus::BadArg
611
    } else {
612
13.2k
        match mp4parse_new_common_safe(&mut (*io).clone(), strictness) {
613
902
            Ok(parser) => {
614
902
                *parser_out = parser;
615
902
                Mp4parseStatus::Ok
616
            }
617
12.3k
            Err(status) => status,
618
        }
619
    }
620
13.2k
}
mp4parse_capi::mp4parse_new_common::<mp4parse_capi::Mp4parseParser>
Line
Count
Source
598
1.02k
unsafe fn mp4parse_new_common<P: ContextParser>(
599
1.02k
    io: *const Mp4parseIo,
600
1.02k
    strictness: ParseStrictness,
601
1.02k
    parser_out: *mut *mut P,
602
1.02k
) -> Mp4parseStatus {
603
    // Validate arguments from C.
604
1.02k
    if io.is_null()
605
1.02k
        || (*io).userdata.is_null()
606
1.02k
        || (*io).read.is_none()
607
1.02k
        || parser_out.is_null()
608
1.02k
        || !(*parser_out).is_null()
609
    {
610
0
        Mp4parseStatus::BadArg
611
    } else {
612
1.02k
        match mp4parse_new_common_safe(&mut (*io).clone(), strictness) {
613
486
            Ok(parser) => {
614
486
                *parser_out = parser;
615
486
                Mp4parseStatus::Ok
616
            }
617
534
            Err(status) => status,
618
        }
619
    }
620
1.02k
}
mp4parse_capi::mp4parse_new_common::<mp4parse_capi::Mp4parseAvifParser>
Line
Count
Source
598
12.2k
unsafe fn mp4parse_new_common<P: ContextParser>(
599
12.2k
    io: *const Mp4parseIo,
600
12.2k
    strictness: ParseStrictness,
601
12.2k
    parser_out: *mut *mut P,
602
12.2k
) -> Mp4parseStatus {
603
    // Validate arguments from C.
604
12.2k
    if io.is_null()
605
12.2k
        || (*io).userdata.is_null()
606
12.2k
        || (*io).read.is_none()
607
12.2k
        || parser_out.is_null()
608
12.2k
        || !(*parser_out).is_null()
609
    {
610
0
        Mp4parseStatus::BadArg
611
    } else {
612
12.2k
        match mp4parse_new_common_safe(&mut (*io).clone(), strictness) {
613
416
            Ok(parser) => {
614
416
                *parser_out = parser;
615
416
                Mp4parseStatus::Ok
616
            }
617
11.8k
            Err(status) => status,
618
        }
619
    }
620
12.2k
}
621
622
13.2k
fn mp4parse_new_common_safe<T: Read, P: ContextParser>(
623
13.2k
    io: &mut T,
624
13.2k
    strictness: ParseStrictness,
625
13.2k
) -> Result<*mut P, Mp4parseStatus> {
626
13.2k
    P::read(io, strictness)
627
13.2k
        .map(P::with_context)
628
13.2k
        .and_then(|x| TryBox::try_new(x).map_err(mp4parse::Error::from))
mp4parse_capi::mp4parse_new_common_safe::<mp4parse_capi::Mp4parseIo, mp4parse_capi::Mp4parseParser>::{closure#0}
Line
Count
Source
628
486
        .and_then(|x| TryBox::try_new(x).map_err(mp4parse::Error::from))
mp4parse_capi::mp4parse_new_common_safe::<mp4parse_capi::Mp4parseIo, mp4parse_capi::Mp4parseAvifParser>::{closure#0}
Line
Count
Source
628
416
        .and_then(|x| TryBox::try_new(x).map_err(mp4parse::Error::from))
629
13.2k
        .map(TryBox::into_raw)
630
13.2k
        .map_err(Mp4parseStatus::from)
631
13.2k
}
mp4parse_capi::mp4parse_new_common_safe::<mp4parse_capi::Mp4parseIo, mp4parse_capi::Mp4parseParser>
Line
Count
Source
622
1.02k
fn mp4parse_new_common_safe<T: Read, P: ContextParser>(
623
1.02k
    io: &mut T,
624
1.02k
    strictness: ParseStrictness,
625
1.02k
) -> Result<*mut P, Mp4parseStatus> {
626
1.02k
    P::read(io, strictness)
627
1.02k
        .map(P::with_context)
628
1.02k
        .and_then(|x| TryBox::try_new(x).map_err(mp4parse::Error::from))
629
1.02k
        .map(TryBox::into_raw)
630
1.02k
        .map_err(Mp4parseStatus::from)
631
1.02k
}
mp4parse_capi::mp4parse_new_common_safe::<mp4parse_capi::Mp4parseIo, mp4parse_capi::Mp4parseAvifParser>
Line
Count
Source
622
12.2k
fn mp4parse_new_common_safe<T: Read, P: ContextParser>(
623
12.2k
    io: &mut T,
624
12.2k
    strictness: ParseStrictness,
625
12.2k
) -> Result<*mut P, Mp4parseStatus> {
626
12.2k
    P::read(io, strictness)
627
12.2k
        .map(P::with_context)
628
12.2k
        .and_then(|x| TryBox::try_new(x).map_err(mp4parse::Error::from))
629
12.2k
        .map(TryBox::into_raw)
630
12.2k
        .map_err(Mp4parseStatus::from)
631
12.2k
}
632
633
/// Free an `Mp4parseParser*` allocated by `mp4parse_new()`.
634
///
635
/// # Safety
636
///
637
/// This function is unsafe because it creates a box from a raw pointer.
638
/// Callers should ensure that the parser pointer points to a valid
639
/// `Mp4parseParser` created by `mp4parse_new`.
640
#[no_mangle]
641
486
pub unsafe extern "C" fn mp4parse_free(parser: *mut Mp4parseParser) {
642
486
    assert!(!parser.is_null());
643
486
    let _ = TryBox::from_raw(parser);
644
486
}
645
646
/// Free an `Mp4parseAvifParser*` allocated by `mp4parse_avif_new()`.
647
///
648
/// # Safety
649
///
650
/// This function is unsafe because it creates a box from a raw pointer.
651
/// Callers should ensure that the parser pointer points to a valid
652
/// `Mp4parseAvifParser` created by `mp4parse_avif_new`.
653
#[no_mangle]
654
416
pub unsafe extern "C" fn mp4parse_avif_free(parser: *mut Mp4parseAvifParser) {
655
416
    assert!(!parser.is_null());
656
416
    let _ = TryBox::from_raw(parser);
657
416
}
658
659
/// Return the number of tracks parsed by previous `mp4parse_read()` call.
660
///
661
/// # Safety
662
///
663
/// This function is unsafe because it dereferences both the parser and count
664
/// raw pointers passed into it. Callers should ensure the parser pointer
665
/// points to a valid `Mp4parseParser`, and that the count pointer points an
666
/// appropriate memory location to have a `u32` written to.
667
#[no_mangle]
668
486
pub unsafe extern "C" fn mp4parse_get_track_count(
669
486
    parser: *const Mp4parseParser,
670
486
    count: *mut u32,
671
486
) -> Mp4parseStatus {
672
    // Validate arguments from C.
673
486
    if parser.is_null() || count.is_null() {
674
0
        return Mp4parseStatus::BadArg;
675
486
    }
676
486
    let context = (*parser).context();
677
678
    // Make sure the track count fits in a u32.
679
486
    if context.tracks.len() > u32::MAX as usize {
680
0
        return Mp4parseStatus::Invalid;
681
486
    }
682
486
    *count = context.tracks.len() as u32;
683
486
    Mp4parseStatus::Ok
684
486
}
685
686
/// Fill the supplied `Mp4parseTrackInfo` with metadata for `track`.
687
///
688
/// # Safety
689
///
690
/// This function is unsafe because it dereferences the the parser and info raw
691
/// pointers passed to it. Callers should ensure the parser pointer points to a
692
/// valid `Mp4parseParser` and that the info pointer points to a valid
693
/// `Mp4parseTrackInfo`.
694
#[no_mangle]
695
55.2k
pub unsafe extern "C" fn mp4parse_get_track_info(
696
55.2k
    parser: *mut Mp4parseParser,
697
55.2k
    track_index: u32,
698
55.2k
    info: *mut Mp4parseTrackInfo,
699
55.2k
) -> Mp4parseStatus {
700
55.2k
    if parser.is_null() || info.is_null() {
701
0
        return Mp4parseStatus::BadArg;
702
55.2k
    }
703
704
    // Initialize fields to default values to ensure all fields are always valid.
705
55.2k
    *info = Default::default();
706
707
55.2k
    let context = (*parser).context_mut();
708
55.2k
    let track_index: usize = track_index as usize;
709
55.2k
    let info: &mut Mp4parseTrackInfo = &mut *info;
710
711
55.2k
    if track_index >= context.tracks.len() {
712
0
        return Mp4parseStatus::BadArg;
713
55.2k
    }
714
715
55.2k
    info.track_type = match context.tracks[track_index].track_type {
716
9.95k
        TrackType::Video => Mp4parseTrackType::Video,
717
5
        TrackType::Picture => Mp4parseTrackType::Picture,
718
40
        TrackType::AuxiliaryVideo => Mp4parseTrackType::AuxiliaryVideo,
719
42.8k
        TrackType::Audio => Mp4parseTrackType::Audio,
720
0
        TrackType::Metadata => Mp4parseTrackType::Metadata,
721
2.43k
        TrackType::Unknown => return Mp4parseStatus::Unsupported,
722
    };
723
724
52.8k
    let track = &context.tracks[track_index];
725
726
52.8k
    if let (Some(timescale), Some(context_timescale)) = (track.timescale, context.timescale) {
727
38.4k
        info.time_scale = timescale.0 as u32;
728
38.4k
        let media_time: CheckedInteger<u64> = track
729
38.4k
            .media_time
730
38.4k
            .map_or(0.into(), |media_time| media_time.0.into());
731
732
        // Empty duration is in the context's timescale, convert it and return it in the track's
733
        // timescale
734
38.4k
        let empty_duration: CheckedInteger<u64> =
735
38.4k
            match track.empty_duration.map_or(Some(0), |empty_duration| {
736
910
                rational_scale(empty_duration.0, context_timescale.0, timescale.0)
737
910
            }) {
738
38.4k
                Some(time) => mp4parse::unstable::CheckedInteger(time),
739
0
                None => return Mp4parseStatus::Invalid,
740
            };
741
742
38.4k
        info.media_time = match media_time - empty_duration {
743
38.4k
            Some(difference) => difference,
744
1
            None => return Mp4parseStatus::Invalid,
745
        };
746
747
        // If an edited duration (from elst) is present, use that instead of the raw track duration
748
        // This is important for files produced by afconvert which use elst to specify the actual
749
        // duration
750
38.4k
        if let Some(edited_duration) = track.edited_duration {
751
            // edited_duration is in context timescale, need to convert to track timescale
752
909
            match rational_scale(edited_duration.0, context_timescale.0, timescale.0) {
753
909
                Some(duration) => info.duration = duration,
754
0
                None => return Mp4parseStatus::Invalid,
755
            }
756
        } else {
757
37.5k
            match track.duration {
758
34.9k
                Some(duration) => info.duration = duration.0,
759
                None => {
760
                    // Duration unknown; stagefright returns 0 for this.
761
2.53k
                    info.duration = 0
762
                }
763
            }
764
        }
765
    } else {
766
14.3k
        return Mp4parseStatus::Invalid;
767
    }
768
769
38.4k
    info.track_id = match track.track_id {
770
26.9k
        Some(track_id) => track_id,
771
11.4k
        None => return Mp4parseStatus::Invalid,
772
    };
773
26.9k
    Mp4parseStatus::Ok
774
55.2k
}
775
776
/// Fill the supplied `Mp4parseTrackAudioInfo` with metadata for `track`.
777
///
778
/// # Safety
779
///
780
/// This function is unsafe because it dereferences the the parser and info raw
781
/// pointers passed to it. Callers should ensure the parser pointer points to a
782
/// valid `Mp4parseParser` and that the info pointer points to a valid
783
/// `Mp4parseTrackAudioInfo`.
784
#[no_mangle]
785
42.8k
pub unsafe extern "C" fn mp4parse_get_track_audio_info(
786
42.8k
    parser: *mut Mp4parseParser,
787
42.8k
    track_index: u32,
788
42.8k
    info: *mut Mp4parseTrackAudioInfo,
789
42.8k
) -> Mp4parseStatus {
790
42.8k
    if parser.is_null() || info.is_null() {
791
0
        return Mp4parseStatus::BadArg;
792
42.8k
    }
793
794
    // Initialize fields to default values to ensure all fields are always valid.
795
42.8k
    *info = Default::default();
796
797
42.8k
    get_track_audio_info(&mut *parser, track_index, &mut *info).into()
798
42.8k
}
799
800
42.8k
fn get_track_audio_info(
801
42.8k
    parser: &mut Mp4parseParser,
802
42.8k
    track_index: u32,
803
42.8k
    info: &mut Mp4parseTrackAudioInfo,
804
42.8k
) -> Result<(), Mp4parseStatus> {
805
42.8k
    if let Some(sample_info) = parser.audio_track_sample_descriptions.get(&track_index) {
806
0
        info.sample_info_count = sample_info.len() as u32;
807
0
        info.sample_info = if sample_info.is_empty() {
808
0
            std::ptr::null()
809
        } else {
810
0
            sample_info.as_ptr()
811
        };
812
0
        return Ok(());
813
42.8k
    }
814
815
    let Mp4parseParser {
816
42.8k
        context,
817
42.8k
        opus_header,
818
        ..
819
42.8k
    } = parser;
820
821
42.8k
    if track_index as usize >= context.tracks.len() {
822
0
        return Err(Mp4parseStatus::BadArg);
823
42.8k
    }
824
825
42.8k
    let track = &context.tracks[track_index as usize];
826
827
42.8k
    if track.track_type != TrackType::Audio {
828
0
        return Err(Mp4parseStatus::Invalid);
829
42.8k
    }
830
831
    // Handle track.stsd
832
42.8k
    let stsd = match track.stsd {
833
38.3k
        Some(ref stsd) => stsd,
834
4.48k
        None => return Err(Mp4parseStatus::Invalid), // Stsd should be present
835
    };
836
837
38.3k
    if stsd.descriptions.is_empty() {
838
28
        return Err(Mp4parseStatus::Invalid); // Should have at least 1 description
839
38.2k
    }
840
841
38.2k
    let mut audio_sample_infos = TryVec::with_capacity(stsd.descriptions.len())?;
842
38.2k
    for (desc_i, description) in stsd.descriptions.iter().enumerate() {
843
38.2k
        let mut sample_info = Mp4parseTrackAudioSampleInfo::default();
844
38.2k
        let audio = match description {
845
35.7k
            SampleEntry::Audio(a) => a,
846
2.52k
            _ => return Err(Mp4parseStatus::Invalid),
847
        };
848
849
        // UNKNOWN for unsupported format.
850
123
        sample_info.codec_type = match audio.codec_specific {
851
34.9k
            AudioCodecSpecific::OpusSpecificBox(_) => Mp4parseCodec::Opus,
852
0
            AudioCodecSpecific::FLACSpecificBox(_) => Mp4parseCodec::Flac,
853
819
            AudioCodecSpecific::ES_Descriptor(ref esds) if esds.audio_codec == CodecType::AAC => {
854
696
                Mp4parseCodec::Aac
855
            }
856
123
            AudioCodecSpecific::ES_Descriptor(ref esds)
857
123
                if esds.audio_codec == CodecType::XHEAAC =>
858
            {
859
0
                Mp4parseCodec::XHEAAC
860
            }
861
123
            AudioCodecSpecific::ES_Descriptor(ref esds) if esds.audio_codec == CodecType::MP3 => {
862
0
                Mp4parseCodec::Mp3
863
            }
864
            AudioCodecSpecific::ES_Descriptor(_) | AudioCodecSpecific::LPCM => {
865
123
                Mp4parseCodec::Unknown
866
            }
867
47
            AudioCodecSpecific::MP3 => Mp4parseCodec::Mp3,
868
0
            AudioCodecSpecific::ALACSpecificBox(_) => Mp4parseCodec::Alac,
869
            #[cfg(feature = "3gpp")]
870
            AudioCodecSpecific::AMRSpecificBox(_) => {
871
                if audio.codec_type == CodecType::AMRNB {
872
                    Mp4parseCodec::AMRNB
873
                } else {
874
                    Mp4parseCodec::AMRWB
875
                }
876
            }
877
        };
878
35.7k
        sample_info.channels = audio.channelcount as u16;
879
35.7k
        sample_info.bit_depth = audio.samplesize;
880
35.7k
        sample_info.sample_rate = audio.samplerate as u32;
881
        // sample_info.profile is handled below on a per case basis
882
883
35.7k
        match audio.codec_specific {
884
819
            AudioCodecSpecific::ES_Descriptor(ref esds) => {
885
819
                if esds.codec_esds.len() > u32::MAX as usize {
886
0
                    return Err(Mp4parseStatus::Invalid);
887
819
                }
888
819
                sample_info.extra_data.set_data(&esds.codec_esds);
889
819
                sample_info
890
819
                    .codec_specific_config
891
819
                    .set_data(&esds.decoder_specific_data);
892
819
                if let Some(rate) = esds.audio_sample_rate {
893
564
                    sample_info.sample_rate = rate;
894
564
                }
895
819
                if let Some(channels) = esds.audio_channel_count {
896
564
                    sample_info.channels = channels;
897
564
                }
898
819
                if let Some(profile) = esds.audio_object_type {
899
564
                    sample_info.profile = profile;
900
564
                }
901
819
                sample_info.extended_profile = match esds.extended_audio_object_type {
902
286
                    Some(extended_profile) => extended_profile,
903
533
                    _ => sample_info.profile,
904
                };
905
            }
906
0
            AudioCodecSpecific::FLACSpecificBox(ref flac) => {
907
                // Return the STREAMINFO metadata block in the codec_specific.
908
0
                let streaminfo = &flac.blocks[0];
909
0
                if streaminfo.block_type != 0 || streaminfo.data.len() != 34 {
910
0
                    return Err(Mp4parseStatus::Invalid);
911
0
                }
912
0
                sample_info.codec_specific_config.set_data(&streaminfo.data);
913
            }
914
34.9k
            AudioCodecSpecific::OpusSpecificBox(ref opus) => {
915
34.9k
                let mut v = TryVec::new();
916
34.9k
                match serialize_opus_header(opus, &mut v) {
917
                    Err(_) => {
918
0
                        return Err(Mp4parseStatus::Invalid);
919
                    }
920
                    Ok(_) => {
921
34.9k
                        opus_header.insert_cache_entry((track_index, desc_i), v)?;
922
34.9k
                        if let Some(v) = opus_header.get(&(track_index, desc_i)) {
923
34.9k
                            if v.len() > u32::MAX as usize {
924
0
                                return Err(Mp4parseStatus::Invalid);
925
34.9k
                            }
926
34.9k
                            sample_info.codec_specific_config.set_data(v);
927
0
                        }
928
                    }
929
                }
930
            }
931
0
            AudioCodecSpecific::ALACSpecificBox(ref alac) => {
932
0
                sample_info.codec_specific_config.set_data(&alac.data);
933
0
            }
934
47
            AudioCodecSpecific::MP3 | AudioCodecSpecific::LPCM => (),
935
            #[cfg(feature = "3gpp")]
936
            AudioCodecSpecific::AMRSpecificBox(_) => (),
937
        }
938
939
35.7k
        if let Some(p) = audio
940
35.7k
            .protection_info
941
35.7k
            .iter()
942
35.7k
            .find(|sinf| sinf.tenc.is_some())
943
        {
944
339
            sample_info.protected_data.original_format =
945
339
                OptionalFourCc::Some(p.original_format.value);
946
339
            sample_info.protected_data.scheme_type = match p.scheme_type {
947
105
                Some(ref scheme_type_box) => {
948
105
                    match scheme_type_box.scheme_type.value.as_ref() {
949
105
                        b"cenc" => Mp4ParseEncryptionSchemeType::Cenc,
950
0
                        b"cbcs" => Mp4ParseEncryptionSchemeType::Cbcs,
951
                        // We don't support other schemes, and shouldn't reach
952
                        // this case. Try to gracefully handle by treating as
953
                        // no encryption case.
954
105
                        _ => Mp4ParseEncryptionSchemeType::None,
955
                    }
956
                }
957
234
                None => Mp4ParseEncryptionSchemeType::None,
958
            };
959
339
            if let Some(ref tenc) = p.tenc {
960
339
                sample_info.protected_data.is_encrypted = tenc.is_encrypted;
961
339
                sample_info.protected_data.iv_size = tenc.iv_size;
962
339
                sample_info.protected_data.kid.set_data(&(tenc.kid));
963
339
                sample_info.protected_data.crypt_byte_block =
964
339
                    tenc.crypt_byte_block_count.unwrap_or(0);
965
339
                sample_info.protected_data.skip_byte_block =
966
339
                    tenc.skip_byte_block_count.unwrap_or(0);
967
339
                if let Some(ref iv_vec) = tenc.constant_iv {
968
0
                    if iv_vec.len() > u32::MAX as usize {
969
0
                        return Err(Mp4parseStatus::Invalid);
970
0
                    }
971
0
                    sample_info.protected_data.constant_iv.set_data(iv_vec);
972
339
                };
973
0
            }
974
35.4k
        }
975
35.7k
        audio_sample_infos.push(sample_info)?;
976
    }
977
978
35.7k
    parser
979
35.7k
        .audio_track_sample_descriptions
980
35.7k
        .insert_cache_entry(track_index, audio_sample_infos)?;
981
35.7k
    match parser.audio_track_sample_descriptions.get(&track_index) {
982
35.7k
        Some(sample_info) => {
983
35.7k
            if sample_info.len() > u32::MAX as usize {
984
                // Should never happen due to upper limits on number of sample
985
                // descriptions a track can have, but lets be safe.
986
0
                return Err(Mp4parseStatus::Invalid);
987
35.7k
            }
988
35.7k
            info.sample_info_count = sample_info.len() as u32;
989
35.7k
            info.sample_info = if sample_info.is_empty() {
990
0
                std::ptr::null()
991
            } else {
992
35.7k
                sample_info.as_ptr()
993
            };
994
        }
995
0
        None => return Err(Mp4parseStatus::Invalid), // Shouldn't happen, we just inserted the info!
996
    }
997
998
35.7k
    Ok(())
999
42.8k
}
1000
1001
/// Fill the supplied `Mp4parseTrackVideoInfo` with metadata for `track`.
1002
///
1003
/// # Safety
1004
///
1005
/// This function is unsafe because it dereferences the the parser and info raw
1006
/// pointers passed to it. Callers should ensure the parser pointer points to a
1007
/// valid `Mp4parseParser` and that the info pointer points to a valid
1008
/// `Mp4parseTrackVideoInfo`.
1009
#[no_mangle]
1010
12.3k
pub unsafe extern "C" fn mp4parse_get_track_video_info(
1011
12.3k
    parser: *mut Mp4parseParser,
1012
12.3k
    track_index: u32,
1013
12.3k
    info: *mut Mp4parseTrackVideoInfo,
1014
12.3k
) -> Mp4parseStatus {
1015
12.3k
    if parser.is_null() || info.is_null() {
1016
0
        return Mp4parseStatus::BadArg;
1017
12.3k
    }
1018
1019
    // Initialize fields to default values to ensure all fields are always valid.
1020
12.3k
    *info = Default::default();
1021
1022
12.3k
    mp4parse_get_track_video_info_safe(&mut *parser, track_index, &mut *info).into()
1023
12.3k
}
1024
1025
12.3k
fn mp4parse_get_track_video_info_safe(
1026
12.3k
    parser: &mut Mp4parseParser,
1027
12.3k
    track_index: u32,
1028
12.3k
    info: &mut Mp4parseTrackVideoInfo,
1029
12.3k
) -> Result<(), Mp4parseStatus> {
1030
12.3k
    let context = parser.context();
1031
1032
12.3k
    if track_index as usize >= context.tracks.len() {
1033
0
        return Err(Mp4parseStatus::BadArg);
1034
12.3k
    }
1035
1036
12.3k
    let track = &context.tracks[track_index as usize];
1037
1038
12.3k
    if track.track_type != TrackType::Video {
1039
2.43k
        return Err(Mp4parseStatus::Invalid);
1040
9.95k
    }
1041
1042
    // Handle track.tkhd
1043
9.95k
    if let Some(ref tkhd) = track.tkhd {
1044
9.37k
        info.display_width = tkhd.width >> 16; // 16.16 fixed point
1045
9.37k
        info.display_height = tkhd.height >> 16; // 16.16 fixed point
1046
9.37k
        let matrix = (
1047
9.37k
            tkhd.matrix.a >> 16,
1048
9.37k
            tkhd.matrix.b >> 16,
1049
9.37k
            tkhd.matrix.c >> 16,
1050
9.37k
            tkhd.matrix.d >> 16,
1051
9.37k
        );
1052
9.37k
        info.rotation = match matrix {
1053
0
            (0, 1, -1, 0) => 90,   // rotate 90 degrees
1054
0
            (-1, 0, 0, -1) => 180, // rotate 180 degrees
1055
0
            (0, -1, 1, 0) => 270,  // rotate 270 degrees
1056
9.37k
            _ => 0,
1057
        };
1058
    } else {
1059
584
        return Err(Mp4parseStatus::Invalid);
1060
    }
1061
1062
9.37k
    if let Some(ref stsd) = track.stsd {
1063
10.4k
        for description in stsd.descriptions.iter() {
1064
10.4k
            if let SampleEntry::Video(video) = description {
1065
9.50k
                if let Some(ratio) = video.pixel_aspect_ratio {
1066
5.54k
                    info.pixel_aspect_ratio = ratio;
1067
5.54k
                }
1068
978
            }
1069
        }
1070
949
    }
1071
1072
9.37k
    if let Some(sample_info) = parser.video_track_sample_descriptions.get(&track_index) {
1073
0
        info.sample_info_count = sample_info.len() as u32;
1074
0
        info.sample_info = if sample_info.is_empty() {
1075
0
            std::ptr::null()
1076
        } else {
1077
0
            sample_info.as_ptr()
1078
        };
1079
0
        return Ok(());
1080
9.37k
    }
1081
1082
    // Handle track.stsd
1083
9.37k
    let stsd = match track.stsd {
1084
8.42k
        Some(ref stsd) => stsd,
1085
949
        None => return Err(Mp4parseStatus::Invalid), // Stsd should be present
1086
    };
1087
1088
8.42k
    if stsd.descriptions.is_empty() {
1089
168
        return Err(Mp4parseStatus::Invalid); // Should have at least 1 description
1090
8.25k
    }
1091
1092
8.25k
    let mut video_sample_infos = TryVec::with_capacity(stsd.descriptions.len())?;
1093
10.0k
    for description in stsd.descriptions.iter() {
1094
10.0k
        let mut sample_info = Mp4parseTrackVideoSampleInfo::default();
1095
10.0k
        let video = match description {
1096
9.15k
            SampleEntry::Video(v) => v,
1097
925
            _ => return Err(Mp4parseStatus::Invalid),
1098
        };
1099
1100
        // UNKNOWN for unsupported format.
1101
9.15k
        sample_info.codec_type = match video.codec_specific {
1102
0
            VideoCodecSpecific::VPxConfig(_) => Mp4parseCodec::Vp9,
1103
2.51k
            VideoCodecSpecific::AV1Config(_) => Mp4parseCodec::Av1,
1104
871
            VideoCodecSpecific::AVCConfig(_) => Mp4parseCodec::Avc,
1105
0
            VideoCodecSpecific::H263Config(_) => Mp4parseCodec::H263,
1106
0
            VideoCodecSpecific::HEVCConfig(_) => Mp4parseCodec::Hevc,
1107
            #[cfg(feature = "mp4v")]
1108
            VideoCodecSpecific::ESDSConfig(_) => Mp4parseCodec::Mp4v,
1109
            #[cfg(not(feature = "mp4v"))]
1110
            VideoCodecSpecific::ESDSConfig(_) =>
1111
            // MP4V (14496-2) video is unsupported.
1112
            {
1113
5.76k
                Mp4parseCodec::Unknown
1114
            }
1115
        };
1116
9.15k
        sample_info.image_width = video.width;
1117
9.15k
        sample_info.image_height = video.height;
1118
1119
9.15k
        match video.codec_specific {
1120
2.51k
            VideoCodecSpecific::AV1Config(ref config) => {
1121
2.51k
                sample_info.extra_data.set_data(&config.raw_config);
1122
2.51k
            }
1123
871
            VideoCodecSpecific::AVCConfig(ref data)
1124
5.76k
            | VideoCodecSpecific::ESDSConfig(ref data)
1125
6.63k
            | VideoCodecSpecific::HEVCConfig(ref data) => {
1126
6.63k
                sample_info.extra_data.set_data(data);
1127
6.63k
            }
1128
0
            _ => {}
1129
        }
1130
1131
9.15k
        if let Some(p) = video
1132
9.15k
            .protection_info
1133
9.15k
            .iter()
1134
9.15k
            .find(|sinf| sinf.tenc.is_some())
1135
        {
1136
353
            sample_info.protected_data.original_format =
1137
353
                OptionalFourCc::Some(p.original_format.value);
1138
353
            sample_info.protected_data.scheme_type = match p.scheme_type {
1139
8
                Some(ref scheme_type_box) => {
1140
8
                    match scheme_type_box.scheme_type.value.as_ref() {
1141
8
                        b"cenc" => Mp4ParseEncryptionSchemeType::Cenc,
1142
0
                        b"cbcs" => Mp4ParseEncryptionSchemeType::Cbcs,
1143
                        // We don't support other schemes, and shouldn't reach
1144
                        // this case. Try to gracefully handle by treating as
1145
                        // no encryption case.
1146
8
                        _ => Mp4ParseEncryptionSchemeType::None,
1147
                    }
1148
                }
1149
345
                None => Mp4ParseEncryptionSchemeType::None,
1150
            };
1151
353
            if let Some(ref tenc) = p.tenc {
1152
353
                sample_info.protected_data.is_encrypted = tenc.is_encrypted;
1153
353
                sample_info.protected_data.iv_size = tenc.iv_size;
1154
353
                sample_info.protected_data.kid.set_data(&(tenc.kid));
1155
353
                sample_info.protected_data.crypt_byte_block =
1156
353
                    tenc.crypt_byte_block_count.unwrap_or(0);
1157
353
                sample_info.protected_data.skip_byte_block =
1158
353
                    tenc.skip_byte_block_count.unwrap_or(0);
1159
353
                if let Some(ref iv_vec) = tenc.constant_iv {
1160
230
                    if iv_vec.len() > u32::MAX as usize {
1161
0
                        return Err(Mp4parseStatus::Invalid);
1162
230
                    }
1163
230
                    sample_info.protected_data.constant_iv.set_data(iv_vec);
1164
123
                };
1165
0
            }
1166
8.80k
        }
1167
0
        if let Some(mp4parse::ColourInformation::Nclx(ref nclx)) = video.colour_info {
1168
0
            sample_info.has_colour_info = true;
1169
0
            sample_info.colour_primaries = nclx.colour_primaries;
1170
0
            sample_info.transfer_characteristics = nclx.transfer_characteristics;
1171
0
            sample_info.matrix_coefficients = nclx.matrix_coefficients;
1172
0
            sample_info.full_range_flag = nclx.full_range_flag;
1173
9.15k
        }
1174
9.15k
        if let Some(ref mdcv) = video.hdr_mastering_display {
1175
0
            sample_info.has_mastering_display = true;
1176
0
            sample_info.mastering_display = Mp4parseMasteringDisplayColourVolume {
1177
0
                display_primaries_x: mdcv.display_primaries_x,
1178
0
                display_primaries_y: mdcv.display_primaries_y,
1179
0
                white_point_x: mdcv.white_point_x,
1180
0
                white_point_y: mdcv.white_point_y,
1181
0
                max_display_mastering_luminance: mdcv.max_display_mastering_luminance,
1182
0
                min_display_mastering_luminance: mdcv.min_display_mastering_luminance,
1183
0
            };
1184
9.15k
        }
1185
9.15k
        if let Some(ref clli) = video.hdr_content_light_level {
1186
0
            sample_info.has_content_light_level = true;
1187
0
            sample_info.content_light_level = Mp4parseContentLightLevel {
1188
0
                max_content_light_level: clli.max_content_light_level,
1189
0
                max_pic_average_light_level: clli.max_pic_average_light_level,
1190
0
            };
1191
9.15k
        }
1192
1193
9.15k
        video_sample_infos.push(sample_info)?;
1194
    }
1195
1196
7.33k
    parser
1197
7.33k
        .video_track_sample_descriptions
1198
7.33k
        .insert_cache_entry(track_index, video_sample_infos)?;
1199
7.33k
    match parser.video_track_sample_descriptions.get(&track_index) {
1200
7.33k
        Some(sample_info) => {
1201
7.33k
            if sample_info.len() > u32::MAX as usize {
1202
                // Should never happen due to upper limits on number of sample
1203
                // descriptions a track can have, but lets be safe.
1204
0
                return Err(Mp4parseStatus::Invalid);
1205
7.33k
            }
1206
7.33k
            info.sample_info_count = sample_info.len() as u32;
1207
7.33k
            info.sample_info = if sample_info.is_empty() {
1208
0
                std::ptr::null()
1209
            } else {
1210
7.33k
                sample_info.as_ptr()
1211
            };
1212
        }
1213
0
        None => return Err(Mp4parseStatus::Invalid), // Shouldn't happen, we just inserted the info!
1214
    }
1215
7.33k
    Ok(())
1216
12.3k
}
1217
1218
/// Return a struct containing meta information read by previous
1219
/// `mp4parse_avif_new()` call.
1220
///
1221
/// `color_track_id`and `alpha_track_id` will be 0 if has_sequence is false.
1222
/// `alpha_track_id` will be 0 if no alpha aux track is present.
1223
///
1224
/// # Safety
1225
///
1226
/// This function is unsafe because it dereferences both the parser and
1227
/// avif_info raw pointers passed into it. Callers should ensure the parser
1228
/// pointer points to a valid `Mp4parseAvifParser`, and that the avif_info
1229
/// pointer points to a valid `Mp4parseAvifInfo`.
1230
#[no_mangle]
1231
416
pub unsafe extern "C" fn mp4parse_avif_get_info(
1232
416
    parser: *const Mp4parseAvifParser,
1233
416
    avif_info: *mut Mp4parseAvifInfo,
1234
416
) -> Mp4parseStatus {
1235
416
    if parser.is_null() || avif_info.is_null() {
1236
0
        return Mp4parseStatus::BadArg;
1237
416
    }
1238
1239
416
    if let Ok(info) = mp4parse_avif_get_info_safe((*parser).context()) {
1240
413
        *avif_info = info;
1241
413
        Mp4parseStatus::Ok
1242
    } else {
1243
3
        Mp4parseStatus::Invalid
1244
    }
1245
416
}
1246
1247
416
fn mp4parse_avif_get_info_safe(context: &AvifContext) -> mp4parse::Result<Mp4parseAvifInfo> {
1248
413
    let info = Mp4parseAvifInfo {
1249
416
        premultiplied_alpha: context.premultiplied_alpha,
1250
416
        major_brand: context.major_brand.value,
1251
416
        unsupported_features_bitfield: context.unsupported_features.into_bitfield(),
1252
416
        spatial_extents: context.spatial_extents_ptr()?,
1253
416
        nclx_colour_information: context
1254
416
            .nclx_colour_information_ptr()
1255
416
            .unwrap_or(Ok(std::ptr::null()))?,
1256
416
        icc_colour_information: Mp4parseByteData::with_data(
1257
416
            context.icc_colour_information().unwrap_or(Ok(&[]))?,
1258
        ),
1259
416
        image_rotation: context.image_rotation()?,
1260
416
        image_mirror: context.image_mirror_ptr()?,
1261
416
        pixel_aspect_ratio: context.pixel_aspect_ratio_ptr()?,
1262
1263
413
        has_primary_item: context.primary_item_is_present(),
1264
        primary_item_bit_depth: 0,
1265
413
        has_alpha_item: context.alpha_item_is_present(),
1266
        alpha_item_bit_depth: 0,
1267
1268
        has_sequence: false,
1269
413
        loop_mode: Mp4parseAvifLoopMode::NoEdits,
1270
        loop_count: 0,
1271
        color_track_id: 0,
1272
        color_track_bit_depth: 0,
1273
        alpha_track_id: 0,
1274
        alpha_track_bit_depth: 0,
1275
    };
1276
1277
826
    fn get_bit_depth(data: &[u8]) -> u8 {
1278
826
        if !data.is_empty() && data.iter().all(|v| *v == data[0]) {
1279
19
            data[0]
1280
        } else {
1281
807
            0
1282
        }
1283
826
    }
1284
413
    let primary_item_bit_depth =
1285
413
        get_bit_depth(context.primary_item_bits_per_channel().unwrap_or(Ok(&[]))?);
1286
413
    let alpha_item_bit_depth =
1287
413
        get_bit_depth(context.alpha_item_bits_per_channel().unwrap_or(Ok(&[]))?);
1288
1289
413
    if let Some(sequence) = &context.sequence {
1290
        // Tracks must have track_id and samples
1291
408
        fn get_track<T>(tracks: &TryVec<Track>, pred: T) -> Option<&Track>
1292
408
        where
1293
408
            T: Fn(&Track) -> bool,
1294
        {
1295
2.49k
            tracks.iter().find(|track| {
1296
2.49k
                if track.track_id.is_none() {
1297
1.12k
                    return false;
1298
1.36k
                }
1299
1.36k
                match &track.stsc {
1300
836
                    Some(stsc) => {
1301
836
                        if stsc.samples.is_empty() {
1302
120
                            return false;
1303
716
                        }
1304
716
                        if !pred(track) {
1305
510
                            return false;
1306
206
                        }
1307
226
                        stsc.samples.iter().any(|chunk| chunk.samples_per_chunk > 0)
mp4parse_capi::mp4parse_avif_get_info_safe::get_track::<mp4parse_capi::mp4parse_avif_get_info_safe::{closure#0}>::{closure#0}::{closure#0}
Line
Count
Source
1307
218
                        stsc.samples.iter().any(|chunk| chunk.samples_per_chunk > 0)
mp4parse_capi::mp4parse_avif_get_info_safe::get_track::<mp4parse_capi::mp4parse_avif_get_info_safe::{closure#1}>::{closure#0}::{closure#0}
Line
Count
Source
1307
8
                        stsc.samples.iter().any(|chunk| chunk.samples_per_chunk > 0)
1308
                    }
1309
533
                    _ => false,
1310
                }
1311
2.49k
            })
mp4parse_capi::mp4parse_avif_get_info_safe::get_track::<mp4parse_capi::mp4parse_avif_get_info_safe::{closure#0}>::{closure#0}
Line
Count
Source
1295
1.10k
            tracks.iter().find(|track| {
1296
1.10k
                if track.track_id.is_none() {
1297
719
                    return false;
1298
385
                }
1299
385
                match &track.stsc {
1300
249
                    Some(stsc) => {
1301
249
                        if stsc.samples.is_empty() {
1302
47
                            return false;
1303
202
                        }
1304
202
                        if !pred(track) {
1305
0
                            return false;
1306
202
                        }
1307
202
                        stsc.samples.iter().any(|chunk| chunk.samples_per_chunk > 0)
1308
                    }
1309
136
                    _ => false,
1310
                }
1311
1.10k
            })
mp4parse_capi::mp4parse_avif_get_info_safe::get_track::<mp4parse_capi::mp4parse_avif_get_info_safe::{closure#1}>::{closure#0}
Line
Count
Source
1295
1.38k
            tracks.iter().find(|track| {
1296
1.38k
                if track.track_id.is_none() {
1297
402
                    return false;
1298
984
                }
1299
984
                match &track.stsc {
1300
587
                    Some(stsc) => {
1301
587
                        if stsc.samples.is_empty() {
1302
73
                            return false;
1303
514
                        }
1304
514
                        if !pred(track) {
1305
510
                            return false;
1306
4
                        }
1307
4
                        stsc.samples.iter().any(|chunk| chunk.samples_per_chunk > 0)
1308
                    }
1309
397
                    _ => false,
1310
                }
1311
1.38k
            })
1312
408
        }
mp4parse_capi::mp4parse_avif_get_info_safe::get_track::<mp4parse_capi::mp4parse_avif_get_info_safe::{closure#0}>
Line
Count
Source
1291
217
        fn get_track<T>(tracks: &TryVec<Track>, pred: T) -> Option<&Track>
1292
217
        where
1293
217
            T: Fn(&Track) -> bool,
1294
        {
1295
217
            tracks.iter().find(|track| {
1296
                if track.track_id.is_none() {
1297
                    return false;
1298
                }
1299
                match &track.stsc {
1300
                    Some(stsc) => {
1301
                        if stsc.samples.is_empty() {
1302
                            return false;
1303
                        }
1304
                        if !pred(track) {
1305
                            return false;
1306
                        }
1307
                        stsc.samples.iter().any(|chunk| chunk.samples_per_chunk > 0)
1308
                    }
1309
                    _ => false,
1310
                }
1311
            })
1312
217
        }
mp4parse_capi::mp4parse_avif_get_info_safe::get_track::<mp4parse_capi::mp4parse_avif_get_info_safe::{closure#1}>
Line
Count
Source
1291
191
        fn get_track<T>(tracks: &TryVec<Track>, pred: T) -> Option<&Track>
1292
191
        where
1293
191
            T: Fn(&Track) -> bool,
1294
        {
1295
191
            tracks.iter().find(|track| {
1296
                if track.track_id.is_none() {
1297
                    return false;
1298
                }
1299
                match &track.stsc {
1300
                    Some(stsc) => {
1301
                        if stsc.samples.is_empty() {
1302
                            return false;
1303
                        }
1304
                        if !pred(track) {
1305
                            return false;
1306
                        }
1307
                        stsc.samples.iter().any(|chunk| chunk.samples_per_chunk > 0)
1308
                    }
1309
                    _ => false,
1310
                }
1311
            })
1312
191
        }
1313
1314
        // Color track will be the first track found
1315
217
        let color_track = match get_track(&sequence.tracks, |_| true) {
1316
191
            Some(v) => v,
1317
26
            _ => return Ok(info),
1318
        };
1319
1320
        // Alpha track will be the first track found with auxl.aux_for_track_id set to color_track's id
1321
514
        let alpha_track = get_track(&sequence.tracks, |track| match &track.tref {
1322
107
            Some(tref) => tref.has_auxl_reference(color_track.track_id.unwrap()),
1323
407
            _ => false,
1324
514
        });
1325
1326
193
        fn get_av1c(track: &Track) -> Option<&AV1ConfigBox> {
1327
193
            if let Some(stsd) = &track.stsd {
1328
191
                for entry in &stsd.descriptions {
1329
171
                    if let SampleEntry::Video(video_entry) = entry {
1330
144
                        if let VideoCodecSpecific::AV1Config(av1c) = &video_entry.codec_specific {
1331
144
                            return Some(av1c);
1332
0
                        }
1333
27
                    }
1334
                }
1335
29
            }
1336
1337
49
            None
1338
193
        }
1339
1340
191
        let color_track_id = color_track.track_id.unwrap();
1341
191
        let color_track_bit_depth = match get_av1c(color_track) {
1342
143
            Some(av1c) => av1c.bit_depth,
1343
48
            _ => return Ok(info),
1344
        };
1345
1346
143
        let (alpha_track_id, alpha_track_bit_depth) = match alpha_track {
1347
2
            Some(track) => (
1348
2
                track.track_id.unwrap(),
1349
2
                match get_av1c(track) {
1350
1
                    Some(av1c) => av1c.bit_depth,
1351
1
                    _ => return Ok(info),
1352
                },
1353
            ),
1354
141
            _ => (0, 0),
1355
        };
1356
1357
142
        let (loop_mode, loop_count) = match color_track.tkhd.as_ref().map(|tkhd| tkhd.duration) {
1358
142
            Some(movie_duration) if movie_duration == u64::MAX => {
1359
1
                (Mp4parseAvifLoopMode::LoopInfinitely, 0)
1360
            }
1361
141
            Some(movie_duration) => match color_track.looped {
1362
101
                Some(true) => match color_track.edited_duration.map(|v| v.0) {
1363
100
                    Some(segment_duration) => {
1364
100
                        match movie_duration.checked_div(segment_duration).and_then(|n| {
1365
99
                            match movie_duration.checked_rem(segment_duration) {
1366
1
                                Some(0) => Some(n.saturating_sub(1)),
1367
98
                                Some(_) => Some(n),
1368
0
                                None => None,
1369
                            }
1370
99
                        }) {
1371
99
                            Some(n) => (Mp4parseAvifLoopMode::LoopByCount, n),
1372
1
                            None => (Mp4parseAvifLoopMode::LoopInfinitely, 0),
1373
                        }
1374
                    }
1375
1
                    None => (Mp4parseAvifLoopMode::NoEdits, 0),
1376
                },
1377
3
                Some(false) => (Mp4parseAvifLoopMode::LoopByCount, 0),
1378
37
                None => (Mp4parseAvifLoopMode::NoEdits, 0),
1379
            },
1380
0
            None => (Mp4parseAvifLoopMode::LoopInfinitely, 0),
1381
        };
1382
1383
142
        return Ok(Mp4parseAvifInfo {
1384
142
            primary_item_bit_depth,
1385
142
            alpha_item_bit_depth,
1386
142
            has_sequence: true,
1387
142
            loop_mode,
1388
142
            loop_count,
1389
142
            color_track_id,
1390
142
            color_track_bit_depth,
1391
142
            alpha_track_id,
1392
142
            alpha_track_bit_depth,
1393
142
            ..info
1394
142
        });
1395
196
    }
1396
1397
196
    Ok(info)
1398
416
}
1399
1400
/// Return a pointer to the primary item parsed by previous `mp4parse_avif_new()` call.
1401
///
1402
/// # Safety
1403
///
1404
/// This function is unsafe because it dereferences both the parser and
1405
/// avif_image raw pointers passed into it. Callers should ensure the parser
1406
/// pointer points to a valid `Mp4parseAvifParser`, and that the avif_image
1407
/// pointer points to a valid `Mp4parseAvifImage`. If there was not a previous
1408
/// successful call to `mp4parse_avif_read()`, no guarantees are made as to
1409
/// the state of `avif_image`. If `avif_image.alpha_image.coded_data` is set to
1410
/// a positive `length` and non-null `data`, then the `avif_image` contains a
1411
/// valid alpha channel data. Otherwise, the image is opaque.
1412
#[no_mangle]
1413
0
pub unsafe extern "C" fn mp4parse_avif_get_image(
1414
0
    parser: *const Mp4parseAvifParser,
1415
0
    avif_image: *mut Mp4parseAvifImage,
1416
0
) -> Mp4parseStatus {
1417
0
    if parser.is_null() || avif_image.is_null() {
1418
0
        return Mp4parseStatus::BadArg;
1419
0
    }
1420
1421
0
    if let Ok(image) = mp4parse_avif_get_image_safe(&*parser) {
1422
0
        *avif_image = image;
1423
0
        Mp4parseStatus::Ok
1424
    } else {
1425
0
        Mp4parseStatus::Invalid
1426
    }
1427
0
}
1428
1429
416
pub fn mp4parse_avif_get_image_safe(
1430
416
    parser: &Mp4parseAvifParser,
1431
416
) -> mp4parse::Result<Mp4parseAvifImage> {
1432
416
    let context = parser.context();
1433
416
    Ok(Mp4parseAvifImage {
1434
416
        primary_image: Mp4parseByteData::with_data(
1435
416
            context.primary_item_coded_data().unwrap_or(&[]),
1436
416
        ),
1437
416
        alpha_image: Mp4parseByteData::with_data(context.alpha_item_coded_data().unwrap_or(&[])),
1438
416
    })
1439
416
}
1440
1441
/// Fill the supplied `Mp4parseByteData` with index information from `track`.
1442
///
1443
/// # Safety
1444
///
1445
/// This function is unsafe because it dereferences the the parser and indices
1446
/// raw pointers passed to it. Callers should ensure the parser pointer points
1447
/// to a valid `Mp4parseParser` and that the indices pointer points to a valid
1448
/// `Mp4parseByteData`.
1449
#[no_mangle]
1450
55.2k
pub unsafe extern "C" fn mp4parse_get_indice_table(
1451
55.2k
    parser: *mut Mp4parseParser,
1452
55.2k
    track_id: u32,
1453
55.2k
    indices: *mut Mp4parseByteData,
1454
55.2k
) -> Mp4parseStatus {
1455
55.2k
    if parser.is_null() {
1456
0
        return Mp4parseStatus::BadArg;
1457
55.2k
    }
1458
1459
    // Initialize fields to default values to ensure all fields are always valid.
1460
55.2k
    *indices = Default::default();
1461
1462
55.2k
    get_indice_table(
1463
55.2k
        &(*parser).context,
1464
55.2k
        &mut (*parser).sample_table,
1465
55.2k
        track_id,
1466
55.2k
        &mut *indices,
1467
    )
1468
55.2k
    .into()
1469
55.2k
}
1470
1471
/// Fill the supplied `Mp4parseByteData` with index information from `track`.
1472
///
1473
/// # Safety
1474
///
1475
/// This function is unsafe because it dereferences both the parser and
1476
/// indices raw pointers passed to it. Callers should ensure the parser
1477
/// points to a valid `Mp4parseAvifParser` and indices points to a valid
1478
/// `Mp4parseByteData`.
1479
#[no_mangle]
1480
0
pub unsafe extern "C" fn mp4parse_avif_get_indice_table(
1481
0
    parser: *mut Mp4parseAvifParser,
1482
0
    track_id: u32,
1483
0
    indices: *mut Mp4parseByteData,
1484
0
    timescale: *mut u64,
1485
0
) -> Mp4parseStatus {
1486
0
    if parser.is_null() {
1487
0
        return Mp4parseStatus::BadArg;
1488
0
    }
1489
1490
0
    if indices.is_null() {
1491
0
        return Mp4parseStatus::BadArg;
1492
0
    }
1493
1494
0
    if timescale.is_null() {
1495
0
        return Mp4parseStatus::BadArg;
1496
0
    }
1497
1498
    // Initialize fields to default values to ensure all fields are always valid.
1499
0
    *indices = Default::default();
1500
1501
0
    if let Some(sequence) = &(*parser).context.sequence {
1502
        // Use the top level timescale, and the track timescale if present.
1503
0
        let mut found_timescale = false;
1504
0
        if let Some(context_timescale) = sequence.timescale {
1505
0
            *timescale = context_timescale.0;
1506
0
            found_timescale = true;
1507
0
        }
1508
0
        let maybe_track_timescale = match sequence
1509
0
            .tracks
1510
0
            .iter()
1511
0
            .find(|track| track.track_id == Some(track_id))
1512
        {
1513
0
            Some(track) => track.timescale,
1514
0
            _ => None,
1515
        };
1516
0
        if let Some(track_timescale) = maybe_track_timescale {
1517
0
            found_timescale = true;
1518
0
            *timescale = track_timescale.0;
1519
0
        }
1520
0
        if !found_timescale {
1521
0
            return Mp4parseStatus::Invalid;
1522
0
        }
1523
0
        return get_indice_table(
1524
0
            sequence,
1525
0
            &mut (*parser).sample_table,
1526
0
            track_id,
1527
0
            &mut *indices,
1528
        )
1529
0
        .into();
1530
0
    }
1531
1532
0
    Mp4parseStatus::BadArg
1533
0
}
1534
1535
55.2k
fn get_indice_table(
1536
55.2k
    context: &MediaContext,
1537
55.2k
    sample_table_cache: &mut TryHashMap<u32, TryVec<Indice>>,
1538
55.2k
    track_id: u32,
1539
55.2k
    indices: &mut Mp4parseByteData,
1540
55.2k
) -> Result<(), Mp4parseStatus> {
1541
55.2k
    let tracks = &context.tracks;
1542
30.5M
    let track = match tracks.iter().find(|track| track.track_id == Some(track_id)) {
1543
1.63k
        Some(t) => t,
1544
53.6k
        _ => return Err(Mp4parseStatus::Invalid),
1545
    };
1546
1547
1.63k
    if let Some(v) = sample_table_cache.get(&track_id) {
1548
0
        indices.set_indices(v);
1549
0
        return Ok(());
1550
1.63k
    }
1551
1552
1.63k
    let media_time = match &track.media_time {
1553
463
        &Some(t) => i64::try_from(t.0).ok().map(Into::into),
1554
1.17k
        _ => None,
1555
    };
1556
1557
1.63k
    let empty_duration: Option<CheckedInteger<_>> = match &track.empty_duration {
1558
463
        &Some(e) => i64::try_from(e.0).ok().map(Into::into),
1559
1.17k
        _ => None,
1560
    };
1561
1562
    // Find the track start offset time from 'elst'.
1563
    // 'media_time' maps start time onward, 'empty_duration' adds time offset
1564
    // before first frame is displayed.
1565
1.63k
    let offset_time = match (empty_duration, media_time) {
1566
463
        (Some(e), Some(m)) => (e - m).ok_or(Err(Mp4parseStatus::Invalid))?,
1567
0
        (Some(e), None) => e,
1568
0
        (None, Some(m)) => m,
1569
1.17k
        _ => 0.into(),
1570
    };
1571
1572
1.63k
    if let Some(v) = create_sample_table(track, offset_time) {
1573
629
        indices.set_indices(&v);
1574
629
        sample_table_cache.insert_cache_entry(track_id, v)?;
1575
629
        return Ok(());
1576
1.00k
    }
1577
1578
1.00k
    Err(Mp4parseStatus::Invalid)
1579
55.2k
}
1580
1581
/// Fill the supplied `Mp4parseFragmentInfo` with metadata from fragmented file.
1582
///
1583
/// # Safety
1584
///
1585
/// This function is unsafe because it dereferences the the parser and
1586
/// info raw pointers passed to it. Callers should ensure the parser
1587
/// pointer points to a valid `Mp4parseParser` and that the info pointer points
1588
/// to a valid `Mp4parseFragmentInfo`.
1589
#[no_mangle]
1590
486
pub unsafe extern "C" fn mp4parse_get_fragment_info(
1591
486
    parser: *mut Mp4parseParser,
1592
486
    info: *mut Mp4parseFragmentInfo,
1593
486
) -> Mp4parseStatus {
1594
486
    if parser.is_null() || info.is_null() {
1595
0
        return Mp4parseStatus::BadArg;
1596
486
    }
1597
1598
    // Initialize fields to default values to ensure all fields are always valid.
1599
486
    *info = Default::default();
1600
1601
486
    let context = (*parser).context();
1602
486
    let info: &mut Mp4parseFragmentInfo = &mut *info;
1603
1604
486
    info.fragment_duration = 0;
1605
1606
486
    let duration = match context.mvex {
1607
270
        Some(ref mvex) => mvex.fragment_duration,
1608
216
        None => return Mp4parseStatus::Invalid,
1609
    };
1610
1611
270
    if let (Some(time), Some(scale)) = (duration, context.timescale) {
1612
204
        info.fragment_duration = time.0;
1613
204
        info.time_scale = scale.0;
1614
204
        return Mp4parseStatus::Ok;
1615
66
    };
1616
66
    Mp4parseStatus::Invalid
1617
486
}
1618
1619
/// Determine if an mp4 file is fragmented. A fragmented file needs mvex table
1620
/// and contains no data in stts, stsc, and stco boxes.
1621
///
1622
/// # Safety
1623
///
1624
/// This function is unsafe because it dereferences the the parser and
1625
/// fragmented raw pointers passed to it. Callers should ensure the parser
1626
/// pointer points to a valid `Mp4parseParser` and that the fragmented pointer
1627
/// points to an appropriate memory location to have a `u8` written to.
1628
#[no_mangle]
1629
55.2k
pub unsafe extern "C" fn mp4parse_is_fragmented(
1630
55.2k
    parser: *mut Mp4parseParser,
1631
55.2k
    track_id: u32,
1632
55.2k
    fragmented: *mut u8,
1633
55.2k
) -> Mp4parseStatus {
1634
55.2k
    if parser.is_null() {
1635
0
        return Mp4parseStatus::BadArg;
1636
55.2k
    }
1637
1638
55.2k
    let context = (*parser).context_mut();
1639
55.2k
    let tracks = &context.tracks;
1640
55.2k
    (*fragmented) = false as u8;
1641
1642
55.2k
    if context.mvex.is_none() {
1643
3.10k
        return Mp4parseStatus::Ok;
1644
52.1k
    }
1645
1646
    // check sample tables.
1647
52.1k
    let mut iter = tracks.iter();
1648
30.3M
    iter.find(|track| track.track_id == Some(track_id))
1649
52.1k
        .map_or(Mp4parseStatus::BadArg, |track| {
1650
1.26k
            match (&track.stsc, &track.stco, &track.stts) {
1651
430
                (Some(stsc), Some(stco), Some(stts))
1652
430
                    if stsc.samples.is_empty()
1653
417
                        && stco.offsets.is_empty()
1654
309
                        && stts.samples.is_empty() =>
1655
                {
1656
307
                    (*fragmented) = true as u8
1657
                }
1658
953
                _ => {}
1659
            };
1660
1.26k
            Mp4parseStatus::Ok
1661
1.26k
        })
1662
55.2k
}
1663
1664
/// Get 'pssh' system id and 'pssh' box content for eme playback.
1665
///
1666
/// The data format of the `info` struct passed to gecko is:
1667
///
1668
/// - system id (16 byte uuid)
1669
/// - pssh box size (32-bit native endian)
1670
/// - pssh box content (including header)
1671
///
1672
/// # Safety
1673
///
1674
/// This function is unsafe because it dereferences the the parser and
1675
/// info raw pointers passed to it. Callers should ensure the parser
1676
/// pointer points to a valid `Mp4parseParser` and that the fragmented pointer
1677
/// points to a valid `Mp4parsePsshInfo`.
1678
#[no_mangle]
1679
486
pub unsafe extern "C" fn mp4parse_get_pssh_info(
1680
486
    parser: *mut Mp4parseParser,
1681
486
    info: *mut Mp4parsePsshInfo,
1682
486
) -> Mp4parseStatus {
1683
486
    if parser.is_null() || info.is_null() {
1684
0
        return Mp4parseStatus::BadArg;
1685
486
    }
1686
1687
    // Initialize fields to default values to ensure all fields are always valid.
1688
486
    *info = Default::default();
1689
1690
486
    get_pssh_info(&mut *parser, &mut *info).into()
1691
486
}
1692
1693
486
fn get_pssh_info(
1694
486
    parser: &mut Mp4parseParser,
1695
486
    info: &mut Mp4parsePsshInfo,
1696
486
) -> Result<(), Mp4parseStatus> {
1697
    let Mp4parseParser {
1698
486
        context, pssh_data, ..
1699
486
    } = parser;
1700
1701
486
    if pssh_data.is_none() {
1702
486
        let mut tmp = TryVec::new();
1703
890
        for pssh in &context.psshs {
1704
404
            let content_len = pssh
1705
404
                .box_content
1706
404
                .len()
1707
404
                .try_into()
1708
404
                .map_err(|_| Mp4parseStatus::Invalid)?;
1709
404
            let mut data_len = TryVec::new();
1710
404
            data_len.write_u32::<byteorder::NativeEndian>(content_len)?;
1711
404
            tmp.extend_from_slice(pssh.system_id.as_slice())?;
1712
404
            tmp.extend_from_slice(data_len.as_slice())?;
1713
404
            tmp.extend_from_slice(pssh.box_content.as_slice())?;
1714
        }
1715
486
        *pssh_data = Some(tmp);
1716
0
    }
1717
1718
486
    match pssh_data {
1719
486
        Some(ref data) => info.data.set_data(data),
1720
0
        None => info.data = Default::default(),
1721
    }
1722
1723
486
    Ok(())
1724
486
}
1725
1726
#[cfg(test)]
1727
extern "C" fn error_read(_: *mut u8, _: usize, _: *mut std::os::raw::c_void) -> isize {
1728
    -1
1729
}
1730
1731
#[cfg(test)]
1732
extern "C" fn valid_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize {
1733
    let input: &mut std::fs::File = unsafe { &mut *(userdata as *mut _) };
1734
1735
    let buf = unsafe { std::slice::from_raw_parts_mut(buf, size) };
1736
    match input.read(buf) {
1737
        Ok(n) => n as isize,
1738
        Err(_) => -1,
1739
    }
1740
}
1741
1742
#[test]
1743
fn get_track_count_null_parser() {
1744
    unsafe {
1745
        let mut count: u32 = 0;
1746
        let rv = mp4parse_get_track_count(std::ptr::null(), std::ptr::null_mut());
1747
        assert_eq!(rv, Mp4parseStatus::BadArg);
1748
        let rv = mp4parse_get_track_count(std::ptr::null(), &mut count);
1749
        assert_eq!(rv, Mp4parseStatus::BadArg);
1750
    }
1751
}
1752
1753
#[test]
1754
fn arg_validation() {
1755
    unsafe {
1756
        let rv = mp4parse_new(std::ptr::null(), std::ptr::null_mut());
1757
        assert_eq!(rv, Mp4parseStatus::BadArg);
1758
1759
        // Passing a null Mp4parseIo is an error.
1760
        let mut parser = std::ptr::null_mut();
1761
        let rv = mp4parse_new(std::ptr::null(), &mut parser);
1762
        assert_eq!(rv, Mp4parseStatus::BadArg);
1763
        assert!(parser.is_null());
1764
1765
        let null_mut: *mut std::os::raw::c_void = std::ptr::null_mut();
1766
1767
        // Passing an Mp4parseIo with null members is an error.
1768
        let io = Mp4parseIo {
1769
            read: None,
1770
            userdata: null_mut,
1771
        };
1772
        let mut parser = std::ptr::null_mut();
1773
        let rv = mp4parse_new(&io, &mut parser);
1774
        assert_eq!(rv, Mp4parseStatus::BadArg);
1775
        assert!(parser.is_null());
1776
1777
        let mut dummy_value = 42;
1778
        let io = Mp4parseIo {
1779
            read: None,
1780
            userdata: &mut dummy_value as *mut _ as *mut std::os::raw::c_void,
1781
        };
1782
        let mut parser = std::ptr::null_mut();
1783
        let rv = mp4parse_new(&io, &mut parser);
1784
        assert_eq!(rv, Mp4parseStatus::BadArg);
1785
        assert!(parser.is_null());
1786
1787
        let mut dummy_info = Mp4parseTrackInfo {
1788
            track_type: Mp4parseTrackType::Video,
1789
            ..Default::default()
1790
        };
1791
        assert_eq!(
1792
            Mp4parseStatus::BadArg,
1793
            mp4parse_get_track_info(std::ptr::null_mut(), 0, &mut dummy_info)
1794
        );
1795
1796
        let mut dummy_video = Default::default();
1797
        assert_eq!(
1798
            Mp4parseStatus::BadArg,
1799
            mp4parse_get_track_video_info(std::ptr::null_mut(), 0, &mut dummy_video)
1800
        );
1801
1802
        let mut dummy_audio = Default::default();
1803
        assert_eq!(
1804
            Mp4parseStatus::BadArg,
1805
            mp4parse_get_track_audio_info(std::ptr::null_mut(), 0, &mut dummy_audio)
1806
        );
1807
    }
1808
}
1809
1810
#[test]
1811
fn parser_input_must_be_null() {
1812
    let mut dummy_value = 42;
1813
    let io = Mp4parseIo {
1814
        read: Some(error_read),
1815
        userdata: &mut dummy_value as *mut _ as *mut std::os::raw::c_void,
1816
    };
1817
    let mut parser = 0xDEAD_BEEF as *mut _;
1818
    let rv = unsafe { mp4parse_new(&io, &mut parser) };
1819
    assert_eq!(rv, Mp4parseStatus::BadArg);
1820
}
1821
1822
#[test]
1823
fn arg_validation_with_parser() {
1824
    unsafe {
1825
        let mut dummy_value = 42;
1826
        let io = Mp4parseIo {
1827
            read: Some(error_read),
1828
            userdata: &mut dummy_value as *mut _ as *mut std::os::raw::c_void,
1829
        };
1830
        let mut parser = std::ptr::null_mut();
1831
        let rv = mp4parse_new(&io, &mut parser);
1832
        assert_eq!(rv, Mp4parseStatus::Io);
1833
        assert!(parser.is_null());
1834
1835
        // Null info pointers are an error.
1836
        assert_eq!(
1837
            Mp4parseStatus::BadArg,
1838
            mp4parse_get_track_info(parser, 0, std::ptr::null_mut())
1839
        );
1840
        assert_eq!(
1841
            Mp4parseStatus::BadArg,
1842
            mp4parse_get_track_video_info(parser, 0, std::ptr::null_mut())
1843
        );
1844
        assert_eq!(
1845
            Mp4parseStatus::BadArg,
1846
            mp4parse_get_track_audio_info(parser, 0, std::ptr::null_mut())
1847
        );
1848
1849
        let mut dummy_info = Mp4parseTrackInfo {
1850
            track_type: Mp4parseTrackType::Video,
1851
            ..Default::default()
1852
        };
1853
        assert_eq!(
1854
            Mp4parseStatus::BadArg,
1855
            mp4parse_get_track_info(parser, 0, &mut dummy_info)
1856
        );
1857
1858
        let mut dummy_video = Default::default();
1859
        assert_eq!(
1860
            Mp4parseStatus::BadArg,
1861
            mp4parse_get_track_video_info(parser, 0, &mut dummy_video)
1862
        );
1863
1864
        let mut dummy_audio = Default::default();
1865
        assert_eq!(
1866
            Mp4parseStatus::BadArg,
1867
            mp4parse_get_track_audio_info(parser, 0, &mut dummy_audio)
1868
        );
1869
    }
1870
}
1871
1872
#[cfg(test)]
1873
fn parse_minimal_mp4() -> *mut Mp4parseParser {
1874
    let mut file = std::fs::File::open("../mp4parse/tests/minimal.mp4").unwrap();
1875
    let io = Mp4parseIo {
1876
        read: Some(valid_read),
1877
        userdata: &mut file as *mut _ as *mut std::os::raw::c_void,
1878
    };
1879
    let mut parser = std::ptr::null_mut();
1880
    let rv = unsafe { mp4parse_new(&io, &mut parser) };
1881
    assert_eq!(Mp4parseStatus::Ok, rv);
1882
    parser
1883
}
1884
1885
#[test]
1886
fn minimal_mp4_parse_ok() {
1887
    let parser = parse_minimal_mp4();
1888
1889
    assert!(!parser.is_null());
1890
1891
    unsafe {
1892
        mp4parse_free(parser);
1893
    }
1894
}
1895
1896
#[test]
1897
fn minimal_mp4_get_track_cout() {
1898
    let parser = parse_minimal_mp4();
1899
1900
    let mut count: u32 = 0;
1901
    assert_eq!(Mp4parseStatus::Ok, unsafe {
1902
        mp4parse_get_track_count(parser, &mut count)
1903
    });
1904
    assert_eq!(2, count);
1905
1906
    unsafe {
1907
        mp4parse_free(parser);
1908
    }
1909
}
1910
1911
#[test]
1912
fn minimal_mp4_get_track_info() {
1913
    let parser = parse_minimal_mp4();
1914
1915
    let mut info = Mp4parseTrackInfo {
1916
        track_type: Mp4parseTrackType::Video,
1917
        ..Default::default()
1918
    };
1919
    assert_eq!(Mp4parseStatus::Ok, unsafe {
1920
        mp4parse_get_track_info(parser, 0, &mut info)
1921
    });
1922
    assert_eq!(info.track_type, Mp4parseTrackType::Video);
1923
    assert_eq!(info.track_id, 1);
1924
    assert_eq!(info.duration, 512);
1925
    assert_eq!(info.media_time, 0);
1926
1927
    assert_eq!(Mp4parseStatus::Ok, unsafe {
1928
        mp4parse_get_track_info(parser, 1, &mut info)
1929
    });
1930
    assert_eq!(info.track_type, Mp4parseTrackType::Audio);
1931
    assert_eq!(info.track_id, 2);
1932
    // Note: this file has an elst, and so this duration is from elst
1933
    assert_eq!(info.duration, 1920);
1934
    assert_eq!(info.media_time, 1024);
1935
1936
    unsafe {
1937
        mp4parse_free(parser);
1938
    }
1939
}
1940
1941
#[test]
1942
fn minimal_mp4_get_track_video_info() {
1943
    let parser = parse_minimal_mp4();
1944
1945
    let mut video = Mp4parseTrackVideoInfo::default();
1946
    assert_eq!(Mp4parseStatus::Ok, unsafe {
1947
        mp4parse_get_track_video_info(parser, 0, &mut video)
1948
    });
1949
    assert_eq!(video.display_width, 320);
1950
    assert_eq!(video.display_height, 240);
1951
    assert_eq!(video.sample_info_count, 1);
1952
1953
    unsafe {
1954
        assert_eq!((*video.sample_info).image_width, 320);
1955
        assert_eq!((*video.sample_info).image_height, 240);
1956
    }
1957
1958
    unsafe {
1959
        mp4parse_free(parser);
1960
    }
1961
}
1962
1963
#[test]
1964
fn minimal_mp4_get_track_audio_info() {
1965
    let parser = parse_minimal_mp4();
1966
1967
    let mut audio = Mp4parseTrackAudioInfo::default();
1968
    assert_eq!(Mp4parseStatus::Ok, unsafe {
1969
        mp4parse_get_track_audio_info(parser, 1, &mut audio)
1970
    });
1971
    assert_eq!(audio.sample_info_count, 1);
1972
1973
    unsafe {
1974
        assert_eq!((*audio.sample_info).channels, 1);
1975
        assert_eq!((*audio.sample_info).bit_depth, 16);
1976
        assert_eq!((*audio.sample_info).sample_rate, 48000);
1977
    }
1978
1979
    unsafe {
1980
        mp4parse_free(parser);
1981
    }
1982
}
1983
1984
#[test]
1985
fn minimal_mp4_get_track_info_invalid_track_number() {
1986
    let parser = parse_minimal_mp4();
1987
1988
    let mut info = Mp4parseTrackInfo {
1989
        track_type: Mp4parseTrackType::Video,
1990
        ..Default::default()
1991
    };
1992
    assert_eq!(Mp4parseStatus::BadArg, unsafe {
1993
        mp4parse_get_track_info(parser, 3, &mut info)
1994
    });
1995
    assert_eq!(info.track_type, Mp4parseTrackType::Video);
1996
    assert_eq!(info.track_id, 0);
1997
    assert_eq!(info.duration, 0);
1998
    assert_eq!(info.media_time, 0);
1999
2000
    let mut video = Mp4parseTrackVideoInfo::default();
2001
    assert_eq!(Mp4parseStatus::BadArg, unsafe {
2002
        mp4parse_get_track_video_info(parser, 3, &mut video)
2003
    });
2004
    assert_eq!(video.display_width, 0);
2005
    assert_eq!(video.display_height, 0);
2006
    assert_eq!(video.sample_info_count, 0);
2007
2008
    let mut audio = Default::default();
2009
    assert_eq!(Mp4parseStatus::BadArg, unsafe {
2010
        mp4parse_get_track_audio_info(parser, 3, &mut audio)
2011
    });
2012
    assert_eq!(audio.sample_info_count, 0);
2013
2014
    unsafe {
2015
        mp4parse_free(parser);
2016
    }
2017
}
2018
2019
#[test]
2020
fn repeated_get_track_audio_info_returns_stable_pointer() {
2021
    let parser = parse_minimal_mp4();
2022
2023
    unsafe {
2024
        let mut audio1 = Mp4parseTrackAudioInfo::default();
2025
        assert_eq!(
2026
            Mp4parseStatus::Ok,
2027
            mp4parse_get_track_audio_info(parser, 1, &mut audio1)
2028
        );
2029
        assert_eq!(audio1.sample_info_count, 1);
2030
        let ptr1 = audio1.sample_info;
2031
2032
        // Query the same track again — must return the cached pointer,
2033
        // not rebuild (which would drop the old Vec and invalidate ptr1).
2034
        let mut audio2 = Mp4parseTrackAudioInfo::default();
2035
        assert_eq!(
2036
            Mp4parseStatus::Ok,
2037
            mp4parse_get_track_audio_info(parser, 1, &mut audio2)
2038
        );
2039
        assert_eq!(audio2.sample_info_count, 1);
2040
        let ptr2 = audio2.sample_info;
2041
2042
        // Pointer must be stable across calls.
2043
        assert_eq!(ptr1, ptr2);
2044
2045
        // Data behind the first pointer must still be valid.
2046
        assert_eq!((*audio1.sample_info).channels, 1);
2047
        assert_eq!((*audio1.sample_info).bit_depth, 16);
2048
        assert_eq!((*audio1.sample_info).sample_rate, 48000);
2049
2050
        mp4parse_free(parser);
2051
    }
2052
}
2053
2054
// Test file: tests/opus_audioinit_two_desc.mp4
2055
//
2056
// Generated from opus_audioinit.mp4 by duplicating the Opus stsd entry so
2057
// the track has two sample descriptions.  The second entry has
2058
// channelcount = 2 and dOps output_channel_count = 2 (vs 1 in the
2059
// original) so the two descriptions are distinguishable.
2060
#[test]
2061
fn multi_opus_description_codec_specific_pointers_are_valid() {
2062
    let mut file = std::fs::File::open("tests/opus_audioinit_two_desc.mp4").expect("Unknown file");
2063
    let io = Mp4parseIo {
2064
        read: Some(valid_read),
2065
        userdata: &mut file as *mut _ as *mut std::os::raw::c_void,
2066
    };
2067
2068
    unsafe {
2069
        let mut parser = std::ptr::null_mut();
2070
        let rv = mp4parse_new(&io, &mut parser);
2071
        assert_eq!(rv, Mp4parseStatus::Ok);
2072
2073
        let mut audio = Mp4parseTrackAudioInfo::default();
2074
        let rv = mp4parse_get_track_audio_info(parser, 0, &mut audio);
2075
        assert_eq!(rv, Mp4parseStatus::Ok);
2076
        assert_eq!(audio.sample_info_count, 2);
2077
2078
        let si0 = &*audio.sample_info;
2079
        let si1 = &*audio.sample_info.add(1);
2080
2081
        // Both descriptions must be Opus.
2082
        assert_eq!(si0.codec_type, Mp4parseCodec::Opus);
2083
        assert_eq!(si1.codec_type, Mp4parseCodec::Opus);
2084
2085
        // Distinguish the two entries by channel count.
2086
        assert_eq!(si0.channels, 1);
2087
        assert_eq!(si1.channels, 2);
2088
2089
        // Both must have non-empty, non-null codec_specific_config.
2090
        assert!(si0.codec_specific_config.length > 0);
2091
        assert!(!si0.codec_specific_config.data.is_null());
2092
        assert!(si1.codec_specific_config.length > 0);
2093
        assert!(!si1.codec_specific_config.data.is_null());
2094
2095
        // The opus_header map must have a separate entry per description.
2096
        let parser_ref = &*parser;
2097
        assert_eq!(
2098
            parser_ref.opus_header.len(),
2099
            2,
2100
            "opus_header must have one entry per stsd description, not per track"
2101
        );
2102
2103
        // Pointers must be distinct (backed by separate opus_header entries).
2104
        assert_ne!(
2105
            si0.codec_specific_config.data,
2106
            si1.codec_specific_config.data
2107
        );
2108
2109
        // Dereference the data to exercise the UAF under ASAN/valgrind.
2110
        let config0 = std::slice::from_raw_parts(
2111
            si0.codec_specific_config.data,
2112
            si0.codec_specific_config.length,
2113
        );
2114
        let config1 = std::slice::from_raw_parts(
2115
            si1.codec_specific_config.data,
2116
            si1.codec_specific_config.length,
2117
        );
2118
2119
        // Serialized opus headers differ because channel counts differ.
2120
        assert_eq!(config0.len(), config1.len());
2121
        assert_ne!(config0, config1);
2122
2123
        mp4parse_free(parser);
2124
    }
2125
}
2126
2127
#[test]
2128
fn repeated_get_track_video_info_returns_stable_pointer() {
2129
    let parser = parse_minimal_mp4();
2130
2131
    unsafe {
2132
        let mut video1 = Mp4parseTrackVideoInfo::default();
2133
        assert_eq!(
2134
            Mp4parseStatus::Ok,
2135
            mp4parse_get_track_video_info(parser, 0, &mut video1)
2136
        );
2137
        assert_eq!(video1.sample_info_count, 1);
2138
        let ptr1 = video1.sample_info;
2139
2140
        // Query the same track again — must return the cached pointer.
2141
        let mut video2 = Mp4parseTrackVideoInfo::default();
2142
        assert_eq!(
2143
            Mp4parseStatus::Ok,
2144
            mp4parse_get_track_video_info(parser, 0, &mut video2)
2145
        );
2146
        assert_eq!(video2.sample_info_count, 1);
2147
        let ptr2 = video2.sample_info;
2148
2149
        // Pointer must be stable across calls.
2150
        assert_eq!(ptr1, ptr2);
2151
2152
        // Data behind the first pointer must still be valid.
2153
        assert_eq!((*video1.sample_info).image_width, 320);
2154
        assert_eq!((*video1.sample_info).image_height, 240);
2155
2156
        // tkhd-derived fields must be populated on the cached path too
2157
        // (the caller zeroes info before each call).
2158
        assert_eq!(video2.display_width, 320);
2159
        assert_eq!(video2.display_height, 240);
2160
2161
        mp4parse_free(parser);
2162
    }
2163
}
2164
2165
#[test]
2166
fn parse_no_timescale() {
2167
    let mut file = std::fs::File::open("tests/no_timescale.mp4").expect("Unknown file");
2168
    let io = Mp4parseIo {
2169
        read: Some(valid_read),
2170
        userdata: &mut file as *mut _ as *mut std::os::raw::c_void,
2171
    };
2172
2173
    unsafe {
2174
        let mut parser = std::ptr::null_mut();
2175
        let mut rv = mp4parse_new(&io, &mut parser);
2176
        assert_eq!(rv, Mp4parseStatus::Ok);
2177
        assert!(!parser.is_null());
2178
2179
        // The file has a video track, but the track has a timescale of 0, so.
2180
        let mut track_info = Mp4parseTrackInfo::default();
2181
        rv = mp4parse_get_track_info(parser, 0, &mut track_info);
2182
        assert_eq!(rv, Mp4parseStatus::Invalid);
2183
    };
2184
}