1 //! C API for mp4parse module.
2 //!
3 //! Parses ISO Base Media Format aka video/mp4 streams.
4 //!
5 //! # Examples
6 //!
7 //! ```rust
8 //! extern crate mp4parse_capi;
9 //! use std::io::Read;
10 //!
11 //! extern fn buf_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize {
12 //! let mut input: &mut std::fs::File = unsafe { &mut *(userdata as *mut _) };
13 //! let mut buf = unsafe { std::slice::from_raw_parts_mut(buf, size) };
14 //! match input.read(&mut buf) {
15 //! Ok(n) => n as isize,
16 //! Err(_) => -1,
17 //! }
18 //! }
19 //!
20 //! let mut file = std::fs::File::open("../mp4parse/tests/minimal.mp4").unwrap();
21 //! let io = mp4parse_capi::Mp4parseIo {
22 //! read: Some(buf_read),
23 //! userdata: &mut file as *mut _ as *mut std::os::raw::c_void
24 //! };
25 //! unsafe {
26 //! let parser = mp4parse_capi::mp4parse_new(&io);
27 //! let rv = mp4parse_capi::mp4parse_read(parser);
28 //! assert_eq!(rv, mp4parse_capi::Mp4parseStatus::Ok);
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 extern crate mp4parse;
38 extern crate byteorder;
39 extern crate num_traits;
40
41 use std::io::Read;
42 use std::collections::HashMap;
43 use byteorder::WriteBytesExt;
44 use num_traits::{PrimInt, Zero};
45
46 // Symbols we need from our rust api.
47 use mp4parse::MediaContext;
48 use mp4parse::TrackType;
49 use mp4parse::read_mp4;
50 use mp4parse::Error;
51 use mp4parse::SampleEntry;
52 use mp4parse::AudioCodecSpecific;
53 use mp4parse::VideoCodecSpecific;
54 use mp4parse::MediaTimeScale;
55 use mp4parse::MediaScaledTime;
56 use mp4parse::TrackTimeScale;
57 use mp4parse::TrackScaledTime;
58 use mp4parse::serialize_opus_header;
59 use mp4parse::CodecType;
60 use mp4parse::Track;
61 use mp4parse::vec_push;
62
63 #[repr(C)]
64 #[derive(PartialEq, Debug)]
65 pub enum Mp4parseStatus {
66 Ok = 0,
67 BadArg = 1,
68 Invalid = 2,
69 Unsupported = 3,
70 Eof = 4,
71 Io = 5,
72 Oom = 6,
73 }
74
75 #[repr(C)]
76 #[derive(PartialEq, Debug)]
77 pub enum Mp4parseTrackType {
78 Video = 0,
79 Audio = 1,
80 }
81
82 impl Default for Mp4parseTrackType {
default() -> Self83 fn default() -> Self { Mp4parseTrackType::Video }
84 }
85
86 #[allow(non_camel_case_types)]
87 #[repr(C)]
88 #[derive(PartialEq, Debug)]
89 pub enum Mp4parseCodec {
90 Unknown,
91 Aac,
92 Flac,
93 Opus,
94 Avc,
95 Vp9,
96 Mp3,
97 Mp4v,
98 Jpeg, // for QT JPEG atom in video track
99 Ac3,
100 Ec3,
101 Alac,
102 }
103
104 impl Default for Mp4parseCodec {
default() -> Self105 fn default() -> Self { Mp4parseCodec::Unknown }
106 }
107
108 #[repr(C)]
109 #[derive(Default, Debug)]
110 pub struct Mp4parseTrackInfo {
111 pub track_type: Mp4parseTrackType,
112 pub codec: Mp4parseCodec,
113 pub track_id: u32,
114 pub duration: u64,
115 pub media_time: i64, // wants to be u64? understand how elst adjustment works
116 // TODO(kinetik): include crypto guff
117 }
118
119 #[repr(C)]
120 #[derive(Default, Debug, PartialEq)]
121 pub struct Mp4parseIndice {
122 pub start_offset: u64,
123 pub end_offset: u64,
124 pub start_composition: i64,
125 pub end_composition: i64,
126 pub start_decode: i64,
127 pub sync: bool,
128 }
129
130 #[repr(C)]
131 #[derive(Debug)]
132 pub struct Mp4parseByteData {
133 pub length: u32,
134 // cheddar can't handle generic type, so it needs to be multiple data types here.
135 pub data: *const u8,
136 pub indices: *const Mp4parseIndice,
137 }
138
139 impl Default for Mp4parseByteData {
default() -> Self140 fn default() -> Self {
141 Self {
142 length: 0,
143 data: std::ptr::null(),
144 indices: std::ptr::null(),
145 }
146 }
147 }
148
149 impl Mp4parseByteData {
set_data(&mut self, data: &[u8])150 fn set_data(&mut self, data: &[u8]) {
151 self.length = data.len() as u32;
152 self.data = data.as_ptr();
153 }
154
set_indices(&mut self, data: &[Mp4parseIndice])155 fn set_indices(&mut self, data: &[Mp4parseIndice]) {
156 self.length = data.len() as u32;
157 self.indices = data.as_ptr();
158 }
159 }
160
161 #[repr(C)]
162 #[derive(Default)]
163 pub struct Mp4parsePsshInfo {
164 pub data: Mp4parseByteData,
165 }
166
167 #[repr(C)]
168 #[derive(Default, Debug)]
169 pub struct Mp4parseSinfInfo {
170 pub is_encrypted: u32,
171 pub iv_size: u8,
172 pub kid: Mp4parseByteData,
173 }
174
175 #[repr(C)]
176 #[derive(Default, Debug)]
177 pub struct Mp4parseTrackAudioInfo {
178 pub channels: u16,
179 pub bit_depth: u16,
180 pub sample_rate: u32,
181 pub profile: u16,
182 pub codec_specific_config: Mp4parseByteData,
183 pub extra_data: Mp4parseByteData,
184 pub protected_data: Mp4parseSinfInfo,
185 }
186
187 #[repr(C)]
188 #[derive(Default, Debug)]
189 pub struct Mp4parseTrackVideoInfo {
190 pub display_width: u32,
191 pub display_height: u32,
192 pub image_width: u16,
193 pub image_height: u16,
194 pub rotation: u16,
195 pub extra_data: Mp4parseByteData,
196 pub protected_data: Mp4parseSinfInfo,
197 }
198
199 #[repr(C)]
200 #[derive(Default, Debug)]
201 pub struct Mp4parseFragmentInfo {
202 pub fragment_duration: u64,
203 // TODO:
204 // info in trex box.
205 }
206
207 pub struct Mp4parseParser {
208 context: MediaContext,
209 io: Mp4parseIo,
210 poisoned: bool,
211 opus_header: HashMap<u32, Vec<u8>>,
212 pssh_data: Vec<u8>,
213 sample_table: HashMap<u32, Vec<Mp4parseIndice>>,
214 }
215
216 impl Mp4parseParser {
context(&self) -> &MediaContext217 fn context(&self) -> &MediaContext {
218 &self.context
219 }
220
context_mut(&mut self) -> &mut MediaContext221 fn context_mut(&mut self) -> &mut MediaContext {
222 &mut self.context
223 }
224
io_mut(&mut self) -> &mut Mp4parseIo225 fn io_mut(&mut self) -> &mut Mp4parseIo {
226 &mut self.io
227 }
228
poisoned(&self) -> bool229 fn poisoned(&self) -> bool {
230 self.poisoned
231 }
232
set_poisoned(&mut self, poisoned: bool)233 fn set_poisoned(&mut self, poisoned: bool) {
234 self.poisoned = poisoned;
235 }
236
opus_header_mut(&mut self) -> &mut HashMap<u32, Vec<u8>>237 fn opus_header_mut(&mut self) -> &mut HashMap<u32, Vec<u8>> {
238 &mut self.opus_header
239 }
240
pssh_data_mut(&mut self) -> &mut Vec<u8>241 fn pssh_data_mut(&mut self) -> &mut Vec<u8> {
242 &mut self.pssh_data
243 }
244
sample_table_mut(&mut self) -> &mut HashMap<u32, Vec<Mp4parseIndice>>245 fn sample_table_mut(&mut self) -> &mut HashMap<u32, Vec<Mp4parseIndice>> {
246 &mut self.sample_table
247 }
248 }
249
250 #[repr(C)]
251 #[derive(Clone)]
252 pub struct Mp4parseIo {
253 pub read: Option<extern fn(buffer: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize>,
254 pub userdata: *mut std::os::raw::c_void,
255 }
256
257 impl Read for Mp4parseIo {
read(&mut self, buf: &mut [u8]) -> std::io::Result<usize>258 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
259 if buf.len() > isize::max_value() as usize {
260 return Err(std::io::Error::new(std::io::ErrorKind::Other, "buf length overflow in Mp4parseIo Read impl"));
261 }
262 let rv = self.read.unwrap()(buf.as_mut_ptr(), buf.len(), self.userdata);
263 if rv >= 0 {
264 Ok(rv as usize)
265 } else {
266 Err(std::io::Error::new(std::io::ErrorKind::Other, "I/O error in Mp4parseIo Read impl"))
267 }
268 }
269 }
270
271 // C API wrapper functions.
272
273 /// Allocate an `Mp4parseParser*` to read from the supplied `Mp4parseIo`.
274 #[no_mangle]
mp4parse_new(io: *const Mp4parseIo) -> *mut Mp4parseParser275 pub unsafe extern fn mp4parse_new(io: *const Mp4parseIo) -> *mut Mp4parseParser {
276 if io.is_null() || (*io).userdata.is_null() {
277 return std::ptr::null_mut();
278 }
279 if (*io).read.is_none() {
280 return std::ptr::null_mut();
281 }
282 let parser = Box::new(Mp4parseParser {
283 context: MediaContext::new(),
284 io: (*io).clone(),
285 poisoned: false,
286 opus_header: HashMap::new(),
287 pssh_data: Vec::new(),
288 sample_table: HashMap::new(),
289 });
290
291 Box::into_raw(parser)
292 }
293
294 /// Free an `Mp4parseParser*` allocated by `mp4parse_new()`.
295 #[no_mangle]
mp4parse_free(parser: *mut Mp4parseParser)296 pub unsafe extern fn mp4parse_free(parser: *mut Mp4parseParser) {
297 assert!(!parser.is_null());
298 let _ = Box::from_raw(parser);
299 }
300
301 /// Run the `Mp4parseParser*` allocated by `mp4parse_new()` until EOF or error.
302 #[no_mangle]
mp4parse_read(parser: *mut Mp4parseParser) -> Mp4parseStatus303 pub unsafe extern fn mp4parse_read(parser: *mut Mp4parseParser) -> Mp4parseStatus {
304 // Validate arguments from C.
305 if parser.is_null() || (*parser).poisoned() {
306 return Mp4parseStatus::BadArg;
307 }
308
309 let context = (*parser).context_mut();
310 let io = (*parser).io_mut();
311
312 let r = read_mp4(io, context);
313 match r {
314 Ok(_) => Mp4parseStatus::Ok,
315 Err(Error::NoMoov) | Err(Error::InvalidData(_)) => {
316 // Block further calls. We've probable lost sync.
317 (*parser).set_poisoned(true);
318 Mp4parseStatus::Invalid
319 }
320 Err(Error::Unsupported(_)) => Mp4parseStatus::Unsupported,
321 Err(Error::UnexpectedEOF) => Mp4parseStatus::Eof,
322 Err(Error::Io(_)) => {
323 // Block further calls after a read failure.
324 // Getting std::io::ErrorKind::UnexpectedEof is normal
325 // but our From trait implementation should have converted
326 // those to our Error::UnexpectedEOF variant.
327 (*parser).set_poisoned(true);
328 Mp4parseStatus::Io
329 },
330 Err(Error::OutOfMemory) => Mp4parseStatus::Oom,
331 }
332 }
333
334 /// Return the number of tracks parsed by previous `mp4parse_read()` call.
335 #[no_mangle]
mp4parse_get_track_count(parser: *const Mp4parseParser, count: *mut u32) -> Mp4parseStatus336 pub unsafe extern fn mp4parse_get_track_count(parser: *const Mp4parseParser, count: *mut u32) -> Mp4parseStatus {
337 // Validate arguments from C.
338 if parser.is_null() || count.is_null() || (*parser).poisoned() {
339 return Mp4parseStatus::BadArg;
340 }
341 let context = (*parser).context();
342
343 // Make sure the track count fits in a u32.
344 if context.tracks.len() > u32::max_value() as usize {
345 return Mp4parseStatus::Invalid;
346 }
347 *count = context.tracks.len() as u32;
348 Mp4parseStatus::Ok
349 }
350
351 /// Calculate numerator * scale / denominator, if possible.
352 ///
353 /// Applying the associativity of integer arithmetic, we divide first
354 /// and add the remainder after multiplying each term separately
355 /// to preserve precision while leaving more headroom. That is,
356 /// (n * s) / d is split into floor(n / d) * s + (n % d) * s / d.
357 ///
358 /// Return None on overflow or if the denominator is zero.
rational_scale<T, S>(numerator: T, denominator: T, scale2: S) -> Option<T> where T: PrimInt + Zero, S: PrimInt359 fn rational_scale<T, S>(numerator: T, denominator: T, scale2: S) -> Option<T>
360 where T: PrimInt + Zero, S: PrimInt {
361 if denominator.is_zero() {
362 return None;
363 }
364
365 let integer = numerator / denominator;
366 let remainder = numerator % denominator;
367 num_traits::cast(scale2).and_then(|s| {
368 match integer.checked_mul(&s) {
369 Some(integer) => remainder.checked_mul(&s)
370 .and_then(|remainder| (remainder/denominator).checked_add(&integer)),
371 None => None,
372 }
373 })
374 }
375
media_time_to_us(time: MediaScaledTime, scale: MediaTimeScale) -> Option<u64>376 fn media_time_to_us(time: MediaScaledTime, scale: MediaTimeScale) -> Option<u64> {
377 let microseconds_per_second = 1000000;
378 rational_scale::<u64, u64>(time.0, scale.0, microseconds_per_second)
379 }
380
track_time_to_us<T>(time: TrackScaledTime<T>, scale: TrackTimeScale<T>) -> Option<T> where T: PrimInt + Zero381 fn track_time_to_us<T>(time: TrackScaledTime<T>, scale: TrackTimeScale<T>) -> Option<T>
382 where T: PrimInt + Zero {
383 assert_eq!(time.1, scale.1);
384 let microseconds_per_second = 1000000;
385 rational_scale::<T, u64>(time.0, scale.0, microseconds_per_second)
386 }
387
388 /// Fill the supplied `Mp4parseTrackInfo` with metadata for `track`.
389 #[no_mangle]
mp4parse_get_track_info(parser: *mut Mp4parseParser, track_index: u32, info: *mut Mp4parseTrackInfo) -> Mp4parseStatus390 pub unsafe extern fn mp4parse_get_track_info(parser: *mut Mp4parseParser, track_index: u32, info: *mut Mp4parseTrackInfo) -> Mp4parseStatus {
391 if parser.is_null() || info.is_null() || (*parser).poisoned() {
392 return Mp4parseStatus::BadArg;
393 }
394
395 // Initialize fields to default values to ensure all fields are always valid.
396 *info = Default::default();
397
398 let context = (*parser).context_mut();
399 let track_index: usize = track_index as usize;
400 let info: &mut Mp4parseTrackInfo = &mut *info;
401
402 if track_index >= context.tracks.len() {
403 return Mp4parseStatus::BadArg;
404 }
405
406 info.track_type = match context.tracks[track_index].track_type {
407 TrackType::Video => Mp4parseTrackType::Video,
408 TrackType::Audio => Mp4parseTrackType::Audio,
409 TrackType::Unknown => return Mp4parseStatus::Unsupported,
410 };
411
412 // Return UNKNOWN for unsupported format.
413 info.codec = match context.tracks[track_index].data {
414 Some(SampleEntry::Audio(ref audio)) => match audio.codec_specific {
415 AudioCodecSpecific::OpusSpecificBox(_) =>
416 Mp4parseCodec::Opus,
417 AudioCodecSpecific::FLACSpecificBox(_) =>
418 Mp4parseCodec::Flac,
419 AudioCodecSpecific::ES_Descriptor(ref esds) if esds.audio_codec == CodecType::AAC =>
420 Mp4parseCodec::Aac,
421 AudioCodecSpecific::ES_Descriptor(ref esds) if esds.audio_codec == CodecType::MP3 =>
422 Mp4parseCodec::Mp3,
423 AudioCodecSpecific::ES_Descriptor(_) | AudioCodecSpecific::LPCM =>
424 Mp4parseCodec::Unknown,
425 AudioCodecSpecific::MP3 =>
426 Mp4parseCodec::Mp3,
427 AudioCodecSpecific::ALACSpecificBox(_) =>
428 Mp4parseCodec::Alac,
429 },
430 Some(SampleEntry::Video(ref video)) => match video.codec_specific {
431 VideoCodecSpecific::VPxConfig(_) =>
432 Mp4parseCodec::Vp9,
433 VideoCodecSpecific::AVCConfig(_) =>
434 Mp4parseCodec::Avc,
435 VideoCodecSpecific::ESDSConfig(_) => // MP4V (14496-2) video is unsupported.
436 Mp4parseCodec::Unknown,
437 },
438 _ => Mp4parseCodec::Unknown,
439 };
440
441 let track = &context.tracks[track_index];
442
443 if let (Some(track_timescale),
444 Some(context_timescale)) = (track.timescale,
445 context.timescale) {
446 let media_time =
447 match track.media_time.map_or(Some(0), |media_time| {
448 track_time_to_us(media_time, track_timescale) }) {
449 Some(time) => time as i64,
450 None => return Mp4parseStatus::Invalid,
451 };
452 let empty_duration =
453 match track.empty_duration.map_or(Some(0), |empty_duration| {
454 media_time_to_us(empty_duration, context_timescale) }) {
455 Some(time) => time as i64,
456 None => return Mp4parseStatus::Invalid,
457 };
458 info.media_time = media_time - empty_duration;
459
460 if let Some(track_duration) = track.duration {
461 match track_time_to_us(track_duration, track_timescale) {
462 Some(duration) => info.duration = duration,
463 None => return Mp4parseStatus::Invalid,
464 }
465 } else {
466 // Duration unknown; stagefright returns 0 for this.
467 info.duration = 0
468 }
469 } else {
470 return Mp4parseStatus::Invalid
471 }
472
473 info.track_id = match track.track_id {
474 Some(track_id) => track_id,
475 None => return Mp4parseStatus::Invalid,
476 };
477
478 Mp4parseStatus::Ok
479 }
480
481 /// Fill the supplied `Mp4parseTrackAudioInfo` with metadata for `track`.
482 #[no_mangle]
mp4parse_get_track_audio_info(parser: *mut Mp4parseParser, track_index: u32, info: *mut Mp4parseTrackAudioInfo) -> Mp4parseStatus483 pub unsafe extern fn mp4parse_get_track_audio_info(parser: *mut Mp4parseParser, track_index: u32, info: *mut Mp4parseTrackAudioInfo) -> Mp4parseStatus {
484 if parser.is_null() || info.is_null() || (*parser).poisoned() {
485 return Mp4parseStatus::BadArg;
486 }
487
488 // Initialize fields to default values to ensure all fields are always valid.
489 *info = Default::default();
490
491 let context = (*parser).context_mut();
492
493 if track_index as usize >= context.tracks.len() {
494 return Mp4parseStatus::BadArg;
495 }
496
497 let track = &context.tracks[track_index as usize];
498
499 match track.track_type {
500 TrackType::Audio => {}
501 _ => return Mp4parseStatus::Invalid,
502 };
503
504 let audio = match track.data {
505 Some(ref data) => data,
506 None => return Mp4parseStatus::Invalid,
507 };
508
509 let audio = match *audio {
510 SampleEntry::Audio(ref x) => x,
511 _ => return Mp4parseStatus::Invalid,
512 };
513
514 (*info).channels = audio.channelcount as u16;
515 (*info).bit_depth = audio.samplesize;
516 (*info).sample_rate = audio.samplerate as u32;
517
518 match audio.codec_specific {
519 AudioCodecSpecific::ES_Descriptor(ref v) => {
520 if v.codec_esds.len() > std::u32::MAX as usize {
521 return Mp4parseStatus::Invalid;
522 }
523 (*info).extra_data.length = v.codec_esds.len() as u32;
524 (*info).extra_data.data = v.codec_esds.as_ptr();
525 (*info).codec_specific_config.length = v.decoder_specific_data.len() as u32;
526 (*info).codec_specific_config.data = v.decoder_specific_data.as_ptr();
527 if let Some(rate) = v.audio_sample_rate {
528 (*info).sample_rate = rate;
529 }
530 if let Some(channels) = v.audio_channel_count {
531 (*info).channels = channels;
532 }
533 if let Some(profile) = v.audio_object_type {
534 (*info).profile = profile;
535 }
536 }
537 AudioCodecSpecific::FLACSpecificBox(ref flac) => {
538 // Return the STREAMINFO metadata block in the codec_specific.
539 let streaminfo = &flac.blocks[0];
540 if streaminfo.block_type != 0 || streaminfo.data.len() != 34 {
541 return Mp4parseStatus::Invalid;
542 }
543 (*info).extra_data.length = streaminfo.data.len() as u32;
544 (*info).extra_data.data = streaminfo.data.as_ptr();
545 }
546 AudioCodecSpecific::OpusSpecificBox(ref opus) => {
547 let mut v = Vec::new();
548 match serialize_opus_header(opus, &mut v) {
549 Err(_) => {
550 return Mp4parseStatus::Invalid;
551 }
552 Ok(_) => {
553 let header = (*parser).opus_header_mut();
554 header.insert(track_index, v);
555 if let Some(v) = header.get(&track_index) {
556 if v.len() > std::u32::MAX as usize {
557 return Mp4parseStatus::Invalid;
558 }
559 (*info).extra_data.length = v.len() as u32;
560 (*info).extra_data.data = v.as_ptr();
561 }
562 }
563 }
564 }
565 AudioCodecSpecific::ALACSpecificBox(ref alac) => {
566 (*info).extra_data.length = alac.data.len() as u32;
567 (*info).extra_data.data = alac.data.as_ptr();
568 }
569 AudioCodecSpecific::MP3 | AudioCodecSpecific::LPCM => (),
570 }
571
572 if let Some(p) = audio.protection_info.iter().find(|sinf| sinf.tenc.is_some()) {
573 if let Some(ref tenc) = p.tenc {
574 (*info).protected_data.is_encrypted = tenc.is_encrypted;
575 (*info).protected_data.iv_size = tenc.iv_size;
576 (*info).protected_data.kid.set_data(&(tenc.kid));
577 }
578 }
579
580 Mp4parseStatus::Ok
581 }
582
583 /// Fill the supplied `Mp4parseTrackVideoInfo` with metadata for `track`.
584 #[no_mangle]
mp4parse_get_track_video_info(parser: *mut Mp4parseParser, track_index: u32, info: *mut Mp4parseTrackVideoInfo) -> Mp4parseStatus585 pub unsafe extern fn mp4parse_get_track_video_info(parser: *mut Mp4parseParser, track_index: u32, info: *mut Mp4parseTrackVideoInfo) -> Mp4parseStatus {
586 if parser.is_null() || info.is_null() || (*parser).poisoned() {
587 return Mp4parseStatus::BadArg;
588 }
589
590 // Initialize fields to default values to ensure all fields are always valid.
591 *info = Default::default();
592
593 let context = (*parser).context_mut();
594
595 if track_index as usize >= context.tracks.len() {
596 return Mp4parseStatus::BadArg;
597 }
598
599 let track = &context.tracks[track_index as usize];
600
601 match track.track_type {
602 TrackType::Video => {}
603 _ => return Mp4parseStatus::Invalid,
604 };
605
606 let video = match track.data {
607 Some(ref data) => data,
608 None => return Mp4parseStatus::Invalid,
609 };
610
611 let video = match *video {
612 SampleEntry::Video(ref x) => x,
613 _ => return Mp4parseStatus::Invalid,
614 };
615
616 if let Some(ref tkhd) = track.tkhd {
617 (*info).display_width = tkhd.width >> 16; // 16.16 fixed point
618 (*info).display_height = tkhd.height >> 16; // 16.16 fixed point
619 let matrix = (tkhd.matrix.a >> 16, tkhd.matrix.b >> 16,
620 tkhd.matrix.c >> 16, tkhd.matrix.d >> 16);
621 (*info).rotation = match matrix {
622 ( 0, 1, -1, 0) => 90, // rotate 90 degrees
623 (-1, 0, 0, -1) => 180, // rotate 180 degrees
624 ( 0, -1, 1, 0) => 270, // rotate 270 degrees
625 _ => 0,
626 };
627 } else {
628 return Mp4parseStatus::Invalid;
629 }
630 (*info).image_width = video.width;
631 (*info).image_height = video.height;
632
633 match video.codec_specific {
634 VideoCodecSpecific::AVCConfig(ref data) | VideoCodecSpecific::ESDSConfig(ref data) => {
635 (*info).extra_data.set_data(data);
636 },
637 _ => {}
638 }
639
640 if let Some(p) = video.protection_info.iter().find(|sinf| sinf.tenc.is_some()) {
641 if let Some(ref tenc) = p.tenc {
642 (*info).protected_data.is_encrypted = tenc.is_encrypted;
643 (*info).protected_data.iv_size = tenc.iv_size;
644 (*info).protected_data.kid.set_data(&(tenc.kid));
645 }
646 }
647
648 Mp4parseStatus::Ok
649 }
650
651 #[no_mangle]
mp4parse_get_indice_table(parser: *mut Mp4parseParser, track_id: u32, indices: *mut Mp4parseByteData) -> Mp4parseStatus652 pub unsafe extern fn mp4parse_get_indice_table(parser: *mut Mp4parseParser, track_id: u32, indices: *mut Mp4parseByteData) -> Mp4parseStatus {
653 if parser.is_null() || (*parser).poisoned() {
654 return Mp4parseStatus::BadArg;
655 }
656
657 // Initialize fields to default values to ensure all fields are always valid.
658 *indices = Default::default();
659
660 let context = (*parser).context();
661 let tracks = &context.tracks;
662 let track = match tracks.iter().find(|track| track.track_id == Some(track_id)) {
663 Some(t) => t,
664 _ => return Mp4parseStatus::Invalid,
665 };
666
667 let index_table = (*parser).sample_table_mut();
668 if let Some(v) = index_table.get(&track_id) {
669 (*indices).set_indices(v);
670 return Mp4parseStatus::Ok;
671 }
672
673 let media_time = match (&track.media_time, &track.timescale) {
674 (&Some(t), &Some(s)) => {
675 track_time_to_us(t, s).map(|v| v as i64)
676 },
677 _ => None,
678 };
679
680 let empty_duration = match (&track.empty_duration, &context.timescale) {
681 (&Some(e), &Some(s)) => {
682 media_time_to_us(e, s).map(|v| v as i64)
683 },
684 _ => None
685 };
686
687 // Find the track start offset time from 'elst'.
688 // 'media_time' maps start time onward, 'empty_duration' adds time offset
689 // before first frame is displayed.
690 let offset_time = match (empty_duration, media_time) {
691 (Some(e), Some(m)) => e - m,
692 (Some(e), None) => e,
693 (None, Some(m)) => m,
694 _ => 0,
695 };
696
697 if let Some(v) = create_sample_table(track, offset_time) {
698 (*indices).set_indices(&v);
699 index_table.insert(track_id, v);
700 return Mp4parseStatus::Ok;
701 }
702
703 Mp4parseStatus::Invalid
704 }
705
706 // Convert a 'ctts' compact table to full table by iterator,
707 // (sample_with_the_same_offset_count, offset) => (offset), (offset), (offset) ...
708 //
709 // For example:
710 // (2, 10), (4, 9) into (10, 10, 9, 9, 9, 9) by calling next_offset_time().
711 struct TimeOffsetIterator<'a> {
712 cur_sample_range: std::ops::Range<u32>,
713 cur_offset: i64,
714 ctts_iter: Option<std::slice::Iter<'a, mp4parse::TimeOffset>>,
715 track_id: usize,
716 }
717
718 impl<'a> Iterator for TimeOffsetIterator<'a> {
719 type Item = i64;
720
next(&mut self) -> Option<i64>721 fn next(&mut self) -> Option<i64> {
722 let has_sample = self.cur_sample_range.next()
723 .or_else(|| {
724 // At end of current TimeOffset, find the next TimeOffset.
725 let iter = match self.ctts_iter {
726 Some(ref mut v) => v,
727 _ => return None,
728 };
729 let offset_version;
730 self.cur_sample_range = match iter.next() {
731 Some(v) => {
732 offset_version = v.time_offset;
733 (0 .. v.sample_count)
734 },
735 _ => {
736 offset_version = mp4parse::TimeOffsetVersion::Version0(0);
737 (0 .. 0)
738 },
739 };
740
741 self.cur_offset = match offset_version {
742 mp4parse::TimeOffsetVersion::Version0(i) => i as i64,
743 mp4parse::TimeOffsetVersion::Version1(i) => i as i64,
744 };
745
746 self.cur_sample_range.next()
747 });
748
749 has_sample.and(Some(self.cur_offset))
750 }
751 }
752
753 impl<'a> TimeOffsetIterator<'a> {
next_offset_time(&mut self) -> TrackScaledTime<i64>754 fn next_offset_time(&mut self) -> TrackScaledTime<i64> {
755 match self.next() {
756 Some(v) => TrackScaledTime::<i64>(v as i64, self.track_id),
757 _ => TrackScaledTime::<i64>(0, self.track_id),
758 }
759 }
760 }
761
762 // Convert 'stts' compact table to full table by iterator,
763 // (sample_count_with_the_same_time, time) => (time, time, time) ... repeats
764 // sample_count_with_the_same_time.
765 //
766 // For example:
767 // (2, 3000), (1, 2999) to (3000, 3000, 2999).
768 struct TimeToSampleIterator<'a> {
769 cur_sample_count: std::ops::Range<u32>,
770 cur_sample_delta: u32,
771 stts_iter: std::slice::Iter<'a, mp4parse::Sample>,
772 track_id: usize,
773 }
774
775 impl<'a> Iterator for TimeToSampleIterator<'a> {
776 type Item = u32;
777
next(&mut self) -> Option<u32>778 fn next(&mut self) -> Option<u32> {
779 let has_sample = self.cur_sample_count.next()
780 .or_else(|| {
781 self.cur_sample_count = match self.stts_iter.next() {
782 Some(v) => {
783 self.cur_sample_delta = v.sample_delta;
784 (0 .. v.sample_count)
785 },
786 _ => (0 .. 0),
787 };
788
789 self.cur_sample_count.next()
790 });
791
792 has_sample.and(Some(self.cur_sample_delta))
793 }
794 }
795
796 impl<'a> TimeToSampleIterator<'a> {
next_delta(&mut self) -> TrackScaledTime<i64>797 fn next_delta(&mut self) -> TrackScaledTime<i64> {
798 match self.next() {
799 Some(v) => TrackScaledTime::<i64>(v as i64, self.track_id),
800 _ => TrackScaledTime::<i64>(0, self.track_id),
801 }
802 }
803 }
804
805 // Convert 'stco' compact table to full table by iterator.
806 // (start_chunk_num, sample_number) => (start_chunk_num, sample_number),
807 // (start_chunk_num + 1, sample_number),
808 // (start_chunk_num + 2, sample_number),
809 // ...
810 // (next start_chunk_num, next sample_number),
811 // ...
812 //
813 // For example:
814 // (1, 5), (5, 10), (9, 2) => (1, 5), (2, 5), (3, 5), (4, 5), (5, 10), (6, 10),
815 // (7, 10), (8, 10), (9, 2)
816 struct SampleToChunkIterator<'a> {
817 chunks: std::ops::Range<u32>,
818 sample_count: u32,
819 stsc_peek_iter: std::iter::Peekable<std::slice::Iter<'a, mp4parse::SampleToChunk>>,
820 remain_chunk_count: u32, // total chunk number from 'stco'.
821 }
822
823 impl<'a> Iterator for SampleToChunkIterator<'a> {
824 type Item = (u32, u32);
825
next(&mut self) -> Option<(u32, u32)>826 fn next(&mut self) -> Option<(u32, u32)> {
827 let has_chunk = self.chunks.next()
828 .or_else(|| {
829 self.chunks = match (self.stsc_peek_iter.next(), self.stsc_peek_iter.peek()) {
830 (Some(next), Some(peek)) if next.first_chunk > 0 && peek.first_chunk > 0 => {
831 self.sample_count = next.samples_per_chunk;
832 ((next.first_chunk - 1) .. (peek.first_chunk - 1))
833 },
834 (Some(next), None) if next.first_chunk > 0 => {
835 self.sample_count = next.samples_per_chunk;
836 // Total chunk number in 'stsc' could be different to 'stco',
837 // there could be more chunks at the last 'stsc' record.
838 match next.first_chunk.checked_add(self.remain_chunk_count) {
839 Some(r) => ((next.first_chunk - 1) .. r - 1),
840 _ => (0 .. 0),
841 }
842 },
843 _ => (0 .. 0),
844 };
845
846 self.remain_chunk_count.checked_sub(self.chunks.len() as u32).and_then(|res| {
847 self.remain_chunk_count = res;
848 self.chunks.next()
849 })
850 });
851
852 has_chunk.map_or(None, |id| { Some((id, self.sample_count)) })
853 }
854 }
855
create_sample_table(track: &Track, track_offset_time: i64) -> Option<Vec<Mp4parseIndice>>856 fn create_sample_table(track: &Track, track_offset_time: i64) -> Option<Vec<Mp4parseIndice>> {
857 let timescale = match track.timescale {
858 Some(ref t) => TrackTimeScale::<i64>(t.0 as i64, t.1),
859 _ => TrackTimeScale::<i64>(0, 0),
860 };
861
862 let (stsc, stco, stsz, stts) =
863 match (&track.stsc, &track.stco, &track.stsz, &track.stts) {
864 (&Some(ref a), &Some(ref b), &Some(ref c), &Some(ref d)) => (a, b, c, d),
865 _ => return None,
866 };
867
868 // According to spec, no sync table means every sample is sync sample.
869 let has_sync_table = match track.stss {
870 Some(_) => true,
871 _ => false,
872 };
873
874 let mut sample_table = Vec::new();
875 let mut sample_size_iter = stsz.sample_sizes.iter();
876
877 // Get 'stsc' iterator for (chunk_id, chunk_sample_count) and calculate the sample
878 // offset address.
879 let stsc_iter = SampleToChunkIterator {
880 chunks: (0 .. 0),
881 sample_count: 0,
882 stsc_peek_iter: stsc.samples.as_slice().iter().peekable(),
883 remain_chunk_count: stco.offsets.len() as u32,
884 };
885
886 for i in stsc_iter {
887 let chunk_id = i.0 as usize;
888 let sample_counts = i.1;
889 let mut cur_position = match stco.offsets.get(chunk_id) {
890 Some(&i) => i,
891 _ => return None,
892 };
893 for _ in 0 .. sample_counts {
894 let start_offset = cur_position;
895 let end_offset = match (stsz.sample_size, sample_size_iter.next()) {
896 (_, Some(t)) => start_offset + *t as u64,
897 (t, _) if t > 0 => start_offset + t as u64,
898 _ => 0,
899 };
900 if end_offset == 0 {
901 return None;
902 }
903 cur_position = end_offset;
904
905 let res = vec_push(&mut sample_table, Mp4parseIndice {
906 start_offset: start_offset,
907 end_offset: end_offset,
908 start_composition: 0,
909 end_composition: 0,
910 start_decode: 0,
911 sync: !has_sync_table,
912 });
913 if res.is_err() {
914 return None;
915 }
916 }
917 }
918
919 // Mark the sync sample in sample_table according to 'stss'.
920 if let Some(ref v) = track.stss {
921 for iter in &v.samples {
922 match iter.checked_sub(1).and_then(|idx| { sample_table.get_mut(idx as usize) }) {
923 Some(elem) => elem.sync = true,
924 _ => return None,
925 }
926 }
927 }
928
929 let ctts_iter = match track.ctts {
930 Some(ref v) => Some(v.samples.as_slice().iter()),
931 _ => None,
932 };
933
934 let mut ctts_offset_iter = TimeOffsetIterator {
935 cur_sample_range: (0 .. 0),
936 cur_offset: 0,
937 ctts_iter: ctts_iter,
938 track_id: track.id,
939 };
940
941 let mut stts_iter = TimeToSampleIterator {
942 cur_sample_count: (0 .. 0),
943 cur_sample_delta: 0,
944 stts_iter: stts.samples.as_slice().iter(),
945 track_id: track.id,
946 };
947
948 // sum_delta is the sum of stts_iter delta.
949 // According to sepc:
950 // decode time => DT(n) = DT(n-1) + STTS(n)
951 // composition time => CT(n) = DT(n) + CTTS(n)
952 // Note:
953 // composition time needs to add the track offset time from 'elst' table.
954 let mut sum_delta = TrackScaledTime::<i64>(0, track.id);
955 for sample in sample_table.as_mut_slice() {
956 let decode_time = sum_delta;
957 sum_delta = sum_delta + stts_iter.next_delta();
958
959 // ctts_offset is the current sample offset time.
960 let ctts_offset = ctts_offset_iter.next_offset_time();
961
962 let start_composition = track_time_to_us(decode_time + ctts_offset, timescale);
963
964 let end_composition = track_time_to_us(sum_delta + ctts_offset, timescale);
965
966 let start_decode = track_time_to_us(decode_time, timescale);
967
968 match (start_composition, end_composition, start_decode) {
969 (Some(s_c), Some(e_c), Some(s_d)) => {
970 sample.start_composition = s_c + track_offset_time;
971 sample.end_composition = e_c + track_offset_time;
972 sample.start_decode = s_d;
973 },
974 _ => return None,
975 }
976 }
977
978 // Correct composition end time due to 'ctts' causes composition time re-ordering.
979 //
980 // Composition end time is not in specification. However, gecko needs it, so we need to
981 // calculate to correct the composition end time.
982 if sample_table.len() > 0 {
983 // Create an index table refers to sample_table and sorted by start_composisiton time.
984 let mut sort_table = Vec::new();
985 for i in 0 .. sample_table.len() {
986 if vec_push(&mut sort_table, i).is_err() {
987 return None;
988 }
989 }
990
991 sort_table.sort_by_key(|i| {
992 match sample_table.get(*i) {
993 Some(v) => {
994 v.start_composition
995 },
996 _ => 0,
997 }
998 });
999
1000 let iter = sort_table.iter();
1001 for i in 0 .. (iter.len() - 1) {
1002 let current_index = sort_table[i];
1003 let peek_index = sort_table[i + 1];
1004 let next_start_composition_time = sample_table[peek_index].start_composition;
1005 let sample = &mut sample_table[current_index];
1006 sample.end_composition = next_start_composition_time;
1007 }
1008 }
1009
1010 Some(sample_table)
1011 }
1012
1013 /// Fill the supplied `Mp4parseFragmentInfo` with metadata from fragmented file.
1014 #[no_mangle]
mp4parse_get_fragment_info(parser: *mut Mp4parseParser, info: *mut Mp4parseFragmentInfo) -> Mp4parseStatus1015 pub unsafe extern fn mp4parse_get_fragment_info(parser: *mut Mp4parseParser, info: *mut Mp4parseFragmentInfo) -> Mp4parseStatus {
1016 if parser.is_null() || info.is_null() || (*parser).poisoned() {
1017 return Mp4parseStatus::BadArg;
1018 }
1019
1020 // Initialize fields to default values to ensure all fields are always valid.
1021 *info = Default::default();
1022
1023 let context = (*parser).context();
1024 let info: &mut Mp4parseFragmentInfo = &mut *info;
1025
1026 info.fragment_duration = 0;
1027
1028 let duration = match context.mvex {
1029 Some(ref mvex) => mvex.fragment_duration,
1030 None => return Mp4parseStatus::Invalid,
1031 };
1032
1033 if let (Some(time), Some(scale)) = (duration, context.timescale) {
1034 info.fragment_duration = match media_time_to_us(time, scale) {
1035 Some(time_us) => time_us as u64,
1036 None => return Mp4parseStatus::Invalid,
1037 }
1038 }
1039
1040 Mp4parseStatus::Ok
1041 }
1042
1043 /// A fragmented file needs mvex table and contains no data in stts, stsc, and stco boxes.
1044 #[no_mangle]
mp4parse_is_fragmented(parser: *mut Mp4parseParser, track_id: u32, fragmented: *mut u8) -> Mp4parseStatus1045 pub unsafe extern fn mp4parse_is_fragmented(parser: *mut Mp4parseParser, track_id: u32, fragmented: *mut u8) -> Mp4parseStatus {
1046 if parser.is_null() || (*parser).poisoned() {
1047 return Mp4parseStatus::BadArg;
1048 }
1049
1050 let context = (*parser).context_mut();
1051 let tracks = &context.tracks;
1052 (*fragmented) = false as u8;
1053
1054 if context.mvex.is_none() {
1055 return Mp4parseStatus::Ok;
1056 }
1057
1058 // check sample tables.
1059 let mut iter = tracks.iter();
1060 iter.find(|track| track.track_id == Some(track_id)).map_or(Mp4parseStatus::BadArg, |track| {
1061 match (&track.stsc, &track.stco, &track.stts) {
1062 (&Some(ref stsc), &Some(ref stco), &Some(ref stts))
1063 if stsc.samples.is_empty() && stco.offsets.is_empty() && stts.samples.is_empty() => (*fragmented) = true as u8,
1064 _ => {},
1065 };
1066 Mp4parseStatus::Ok
1067 })
1068 }
1069
1070 /// Get 'pssh' system id and 'pssh' box content for eme playback.
1071 ///
1072 /// The data format of the `info` struct passed to gecko is:
1073 ///
1074 /// - system id (16 byte uuid)
1075 /// - pssh box size (32-bit native endian)
1076 /// - pssh box content (including header)
1077 #[no_mangle]
mp4parse_get_pssh_info(parser: *mut Mp4parseParser, info: *mut Mp4parsePsshInfo) -> Mp4parseStatus1078 pub unsafe extern fn mp4parse_get_pssh_info(parser: *mut Mp4parseParser, info: *mut Mp4parsePsshInfo) -> Mp4parseStatus {
1079 if parser.is_null() || info.is_null() || (*parser).poisoned() {
1080 return Mp4parseStatus::BadArg;
1081 }
1082
1083 // Initialize fields to default values to ensure all fields are always valid.
1084 *info = Default::default();
1085
1086 let context = (*parser).context_mut();
1087 let pssh_data = (*parser).pssh_data_mut();
1088 let info: &mut Mp4parsePsshInfo = &mut *info;
1089
1090 pssh_data.clear();
1091 for pssh in &context.psshs {
1092 let content_len = pssh.box_content.len();
1093 if content_len > std::u32::MAX as usize {
1094 return Mp4parseStatus::Invalid;
1095 }
1096 let mut data_len = Vec::new();
1097 if data_len.write_u32::<byteorder::NativeEndian>(content_len as u32).is_err() {
1098 return Mp4parseStatus::Io;
1099 }
1100 pssh_data.extend_from_slice(pssh.system_id.as_slice());
1101 pssh_data.extend_from_slice(data_len.as_slice());
1102 pssh_data.extend_from_slice(pssh.box_content.as_slice());
1103 }
1104
1105 info.data.set_data(pssh_data);
1106
1107 Mp4parseStatus::Ok
1108 }
1109
1110 #[cfg(test)]
panic_read(_: *mut u8, _: usize, _: *mut std::os::raw::c_void) -> isize1111 extern fn panic_read(_: *mut u8, _: usize, _: *mut std::os::raw::c_void) -> isize {
1112 panic!("panic_read shouldn't be called in these tests");
1113 }
1114
1115 #[cfg(test)]
error_read(_: *mut u8, _: usize, _: *mut std::os::raw::c_void) -> isize1116 extern fn error_read(_: *mut u8, _: usize, _: *mut std::os::raw::c_void) -> isize {
1117 -1
1118 }
1119
1120 #[cfg(test)]
valid_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize1121 extern fn valid_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize {
1122 let input: &mut std::fs::File = unsafe { &mut *(userdata as *mut _) };
1123
1124 let mut buf = unsafe { std::slice::from_raw_parts_mut(buf, size) };
1125 match input.read(&mut buf) {
1126 Ok(n) => n as isize,
1127 Err(_) => -1,
1128 }
1129 }
1130
1131 #[test]
new_parser()1132 fn new_parser() {
1133 let mut dummy_value: u32 = 42;
1134 let io = Mp4parseIo {
1135 read: Some(panic_read),
1136 userdata: &mut dummy_value as *mut _ as *mut std::os::raw::c_void,
1137 };
1138 unsafe {
1139 let parser = mp4parse_new(&io);
1140 assert!(!parser.is_null());
1141 mp4parse_free(parser);
1142 }
1143 }
1144
1145 #[test]
get_track_count_null_parser()1146 fn get_track_count_null_parser() {
1147 unsafe {
1148 let mut count: u32 = 0;
1149 let rv = mp4parse_get_track_count(std::ptr::null(), std::ptr::null_mut());
1150 assert_eq!(rv, Mp4parseStatus::BadArg);
1151 let rv = mp4parse_get_track_count(std::ptr::null(), &mut count);
1152 assert_eq!(rv, Mp4parseStatus::BadArg);
1153 }
1154 }
1155
1156 #[test]
arg_validation()1157 fn arg_validation() {
1158 unsafe {
1159 // Passing a null Mp4parseIo is an error.
1160 let parser = mp4parse_new(std::ptr::null());
1161 assert!(parser.is_null());
1162
1163 let null_mut: *mut std::os::raw::c_void = std::ptr::null_mut();
1164
1165 // Passing an Mp4parseIo with null members is an error.
1166 let io = Mp4parseIo { read: None,
1167 userdata: null_mut };
1168 let parser = mp4parse_new(&io);
1169 assert!(parser.is_null());
1170
1171 let io = Mp4parseIo { read: Some(panic_read),
1172 userdata: null_mut };
1173 let parser = mp4parse_new(&io);
1174 assert!(parser.is_null());
1175
1176 let mut dummy_value = 42;
1177 let io = Mp4parseIo {
1178 read: None,
1179 userdata: &mut dummy_value as *mut _ as *mut std::os::raw::c_void,
1180 };
1181 let parser = mp4parse_new(&io);
1182 assert!(parser.is_null());
1183
1184 // Passing a null Mp4parseParser is an error.
1185 assert_eq!(Mp4parseStatus::BadArg, mp4parse_read(std::ptr::null_mut()));
1186
1187 let mut dummy_info = Mp4parseTrackInfo {
1188 track_type: Mp4parseTrackType::Video,
1189 codec: Mp4parseCodec::Unknown,
1190 track_id: 0,
1191 duration: 0,
1192 media_time: 0,
1193 };
1194 assert_eq!(Mp4parseStatus::BadArg, mp4parse_get_track_info(std::ptr::null_mut(), 0, &mut dummy_info));
1195
1196 let mut dummy_video = Mp4parseTrackVideoInfo {
1197 display_width: 0,
1198 display_height: 0,
1199 image_width: 0,
1200 image_height: 0,
1201 rotation: 0,
1202 extra_data: Mp4parseByteData::default(),
1203 protected_data: Default::default(),
1204 };
1205 assert_eq!(Mp4parseStatus::BadArg, mp4parse_get_track_video_info(std::ptr::null_mut(), 0, &mut dummy_video));
1206
1207 let mut dummy_audio = Default::default();
1208 assert_eq!(Mp4parseStatus::BadArg, mp4parse_get_track_audio_info(std::ptr::null_mut(), 0, &mut dummy_audio));
1209 }
1210 }
1211
1212 #[test]
arg_validation_with_parser()1213 fn arg_validation_with_parser() {
1214 unsafe {
1215 let mut dummy_value = 42;
1216 let io = Mp4parseIo {
1217 read: Some(error_read),
1218 userdata: &mut dummy_value as *mut _ as *mut std::os::raw::c_void,
1219 };
1220 let parser = mp4parse_new(&io);
1221 assert!(!parser.is_null());
1222
1223 // Our Mp4parseIo read should simply fail with an error.
1224 assert_eq!(Mp4parseStatus::Io, mp4parse_read(parser));
1225
1226 // The parser is now poisoned and unusable.
1227 assert_eq!(Mp4parseStatus::BadArg, mp4parse_read(parser));
1228
1229 // Null info pointers are an error.
1230 assert_eq!(Mp4parseStatus::BadArg, mp4parse_get_track_info(parser, 0, std::ptr::null_mut()));
1231 assert_eq!(Mp4parseStatus::BadArg, mp4parse_get_track_video_info(parser, 0, std::ptr::null_mut()));
1232 assert_eq!(Mp4parseStatus::BadArg, mp4parse_get_track_audio_info(parser, 0, std::ptr::null_mut()));
1233
1234 let mut dummy_info = Mp4parseTrackInfo {
1235 track_type: Mp4parseTrackType::Video,
1236 codec: Mp4parseCodec::Unknown,
1237 track_id: 0,
1238 duration: 0,
1239 media_time: 0,
1240 };
1241 assert_eq!(Mp4parseStatus::BadArg, mp4parse_get_track_info(parser, 0, &mut dummy_info));
1242
1243 let mut dummy_video = Mp4parseTrackVideoInfo {
1244 display_width: 0,
1245 display_height: 0,
1246 image_width: 0,
1247 image_height: 0,
1248 rotation: 0,
1249 extra_data: Mp4parseByteData::default(),
1250 protected_data: Default::default(),
1251 };
1252 assert_eq!(Mp4parseStatus::BadArg, mp4parse_get_track_video_info(parser, 0, &mut dummy_video));
1253
1254 let mut dummy_audio = Default::default();
1255 assert_eq!(Mp4parseStatus::BadArg, mp4parse_get_track_audio_info(parser, 0, &mut dummy_audio));
1256
1257 mp4parse_free(parser);
1258 }
1259 }
1260
1261 #[test]
get_track_count_poisoned_parser()1262 fn get_track_count_poisoned_parser() {
1263 unsafe {
1264 let mut dummy_value = 42;
1265 let io = Mp4parseIo {
1266 read: Some(error_read),
1267 userdata: &mut dummy_value as *mut _ as *mut std::os::raw::c_void,
1268 };
1269 let parser = mp4parse_new(&io);
1270 assert!(!parser.is_null());
1271
1272 // Our Mp4parseIo read should simply fail with an error.
1273 assert_eq!(Mp4parseStatus::Io, mp4parse_read(parser));
1274
1275 let mut count: u32 = 0;
1276 let rv = mp4parse_get_track_count(parser, &mut count);
1277 assert_eq!(rv, Mp4parseStatus::BadArg);
1278
1279 mp4parse_free(parser);
1280 }
1281 }
1282
1283 #[test]
arg_validation_with_data()1284 fn arg_validation_with_data() {
1285 unsafe {
1286 let mut file = std::fs::File::open("../mp4parse/tests/minimal.mp4").unwrap();
1287 let io = Mp4parseIo { read: Some(valid_read),
1288 userdata: &mut file as *mut _ as *mut std::os::raw::c_void };
1289 let parser = mp4parse_new(&io);
1290 assert!(!parser.is_null());
1291
1292 assert_eq!(Mp4parseStatus::Ok, mp4parse_read(parser));
1293
1294 let mut count: u32 = 0;
1295 assert_eq!(Mp4parseStatus::Ok, mp4parse_get_track_count(parser, &mut count));
1296 assert_eq!(2, count);
1297
1298 let mut info = Mp4parseTrackInfo {
1299 track_type: Mp4parseTrackType::Video,
1300 codec: Mp4parseCodec::Unknown,
1301 track_id: 0,
1302 duration: 0,
1303 media_time: 0,
1304 };
1305 assert_eq!(Mp4parseStatus::Ok, mp4parse_get_track_info(parser, 0, &mut info));
1306 assert_eq!(info.track_type, Mp4parseTrackType::Video);
1307 assert_eq!(info.codec, Mp4parseCodec::Avc);
1308 assert_eq!(info.track_id, 1);
1309 assert_eq!(info.duration, 40000);
1310 assert_eq!(info.media_time, 0);
1311
1312 assert_eq!(Mp4parseStatus::Ok, mp4parse_get_track_info(parser, 1, &mut info));
1313 assert_eq!(info.track_type, Mp4parseTrackType::Audio);
1314 assert_eq!(info.codec, Mp4parseCodec::Aac);
1315 assert_eq!(info.track_id, 2);
1316 assert_eq!(info.duration, 61333);
1317 assert_eq!(info.media_time, 21333);
1318
1319 let mut video = Mp4parseTrackVideoInfo {
1320 display_width: 0,
1321 display_height: 0,
1322 image_width: 0,
1323 image_height: 0,
1324 rotation: 0,
1325 extra_data: Mp4parseByteData::default(),
1326 protected_data: Default::default(),
1327 };
1328 assert_eq!(Mp4parseStatus::Ok, mp4parse_get_track_video_info(parser, 0, &mut video));
1329 assert_eq!(video.display_width, 320);
1330 assert_eq!(video.display_height, 240);
1331 assert_eq!(video.image_width, 320);
1332 assert_eq!(video.image_height, 240);
1333
1334 let mut audio = Default::default();
1335 assert_eq!(Mp4parseStatus::Ok, mp4parse_get_track_audio_info(parser, 1, &mut audio));
1336 assert_eq!(audio.channels, 1);
1337 assert_eq!(audio.bit_depth, 16);
1338 assert_eq!(audio.sample_rate, 48000);
1339
1340 // Test with an invalid track number.
1341 let mut info = Mp4parseTrackInfo {
1342 track_type: Mp4parseTrackType::Video,
1343 codec: Mp4parseCodec::Unknown,
1344 track_id: 0,
1345 duration: 0,
1346 media_time: 0,
1347 };
1348 assert_eq!(Mp4parseStatus::BadArg, mp4parse_get_track_info(parser, 3, &mut info));
1349 assert_eq!(info.track_type, Mp4parseTrackType::Video);
1350 assert_eq!(info.codec, Mp4parseCodec::Unknown);
1351 assert_eq!(info.track_id, 0);
1352 assert_eq!(info.duration, 0);
1353 assert_eq!(info.media_time, 0);
1354
1355 let mut video = Mp4parseTrackVideoInfo {
1356 display_width: 0,
1357 display_height: 0,
1358 image_width: 0,
1359 image_height: 0,
1360 rotation: 0,
1361 extra_data: Mp4parseByteData::default(),
1362 protected_data: Default::default(),
1363 };
1364 assert_eq!(Mp4parseStatus::BadArg, mp4parse_get_track_video_info(parser, 3, &mut video));
1365 assert_eq!(video.display_width, 0);
1366 assert_eq!(video.display_height, 0);
1367 assert_eq!(video.image_width, 0);
1368 assert_eq!(video.image_height, 0);
1369
1370 let mut audio = Default::default();
1371 assert_eq!(Mp4parseStatus::BadArg, mp4parse_get_track_audio_info(parser, 3, &mut audio));
1372 assert_eq!(audio.channels, 0);
1373 assert_eq!(audio.bit_depth, 0);
1374 assert_eq!(audio.sample_rate, 0);
1375
1376 mp4parse_free(parser);
1377 }
1378 }
1379
1380 #[test]
rational_scale_overflow()1381 fn rational_scale_overflow() {
1382 assert_eq!(rational_scale::<u64, u64>(17, 3, 1000), Some(5666));
1383 let large = 0x4000_0000_0000_0000;
1384 assert_eq!(rational_scale::<u64, u64>(large, 2, 2), Some(large));
1385 assert_eq!(rational_scale::<u64, u64>(large, 4, 4), Some(large));
1386 assert_eq!(rational_scale::<u64, u64>(large, 2, 8), None);
1387 assert_eq!(rational_scale::<u64, u64>(large, 8, 4), Some(large/2));
1388 assert_eq!(rational_scale::<u64, u64>(large + 1, 4, 4), Some(large+1));
1389 assert_eq!(rational_scale::<u64, u64>(large, 40, 1000), None);
1390 }
1391
1392 #[test]
media_time_overflow()1393 fn media_time_overflow() {
1394 let scale = MediaTimeScale(90000);
1395 let duration = MediaScaledTime(9007199254710000);
1396 assert_eq!(media_time_to_us(duration, scale), Some(100079991719000000));
1397 }
1398
1399 #[test]
track_time_overflow()1400 fn track_time_overflow() {
1401 let scale = TrackTimeScale(44100u64, 0);
1402 let duration = TrackScaledTime(4413527634807900u64, 0);
1403 assert_eq!(track_time_to_us(duration, scale), Some(100079991719000000));
1404 }
1405