Coverage Report

Created: 2026-04-12 07:01

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