diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /third_party/rust/mp4parse_capi | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/mp4parse_capi')
-rw-r--r-- | third_party/rust/mp4parse_capi/.cargo-checksum.json | 1 | ||||
-rw-r--r-- | third_party/rust/mp4parse_capi/Cargo.toml | 40 | ||||
-rw-r--r-- | third_party/rust/mp4parse_capi/LICENSE | 373 | ||||
-rw-r--r-- | third_party/rust/mp4parse_capi/README.md | 2 | ||||
-rw-r--r-- | third_party/rust/mp4parse_capi/cbindgen.toml | 45 | ||||
-rw-r--r-- | third_party/rust/mp4parse_capi/examples/dump.rs | 205 | ||||
-rw-r--r-- | third_party/rust/mp4parse_capi/src/lib.rs | 1619 | ||||
-rw-r--r-- | third_party/rust/mp4parse_capi/tests/test_chunk_out_of_range.rs | 45 | ||||
-rw-r--r-- | third_party/rust/mp4parse_capi/tests/test_encryption.rs | 296 | ||||
-rw-r--r-- | third_party/rust/mp4parse_capi/tests/test_fragment.rs | 117 | ||||
-rw-r--r-- | third_party/rust/mp4parse_capi/tests/test_rotation.rs | 41 | ||||
-rw-r--r-- | third_party/rust/mp4parse_capi/tests/test_sample_table.rs | 280 | ||||
-rw-r--r-- | third_party/rust/mp4parse_capi/tests/test_workaround_stsc.rs | 46 |
13 files changed, 3110 insertions, 0 deletions
diff --git a/third_party/rust/mp4parse_capi/.cargo-checksum.json b/third_party/rust/mp4parse_capi/.cargo-checksum.json new file mode 100644 index 0000000000..e6f11e0f58 --- /dev/null +++ b/third_party/rust/mp4parse_capi/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"5074d01f075fd3c870f6e7f864e5a9b1ac7eb2af671110b58d9b1f3b830f72df","LICENSE":"fab3dd6bdab226f1c08630b1dd917e11fcb4ec5e1e020e2c16f83a0a13863e85","README.md":"f776ed4bbb7b58a5684402a9c5c28dfe1fa02b6b184139b2c2c49384cc1e3723","cbindgen.toml":"62066cd34285ab9e7f1cc5db8950a51e9e080f5a85bd55ad43d7022e4eae2758","examples/dump.rs":"a22630f5f1434d4832f9113dcb18161c0248465e8844d470da3c76bb9910677a","src/lib.rs":"21d0b024037be9ced0f4647925bde55ca5e3fd4272a89847a4431235244b2e34","tests/test_chunk_out_of_range.rs":"73ffb5b60e826f6136d22142c030d17d0f72b85c675ccbf1300c84f9deb73131","tests/test_encryption.rs":"196ba22efc3e693c940bcc1e45d29bec9cf290b81cf77a68c1d254f6b38e6ae3","tests/test_fragment.rs":"a4b275d7159c50b265db583a1cc8255bd0a141e3a44432355713b895a7970d37","tests/test_rotation.rs":"a5aa6cc88a327ec90d6898b2c4f5ac397667ce349d829deae1af46c230be9cb6","tests/test_sample_table.rs":"d191fe5836c58d4bdffd7390da029bb5371f8afb5b1a8d636e15ae0dd6b5f4c8","tests/test_workaround_stsc.rs":"85cd2546224b5c4891a60d86e2d302a56e0c0798c2636ad241603a00ebfa46b5"},"package":null}
\ No newline at end of file diff --git a/third_party/rust/mp4parse_capi/Cargo.toml b/third_party/rust/mp4parse_capi/Cargo.toml new file mode 100644 index 0000000000..57ae8fcfa1 --- /dev/null +++ b/third_party/rust/mp4parse_capi/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "mp4parse_capi" +version = "0.13.0" +authors = [ + "Ralph Giles <giles@mozilla.com>", + "Matthew Gregan <kinetik@flim.org>", + "Alfredo Yang <ayang@mozilla.com>", + "Jon Bauman <jbauman@mozilla.com>", + "Bryce Seager van Dyk <bvandyk@mozilla.com>", +] + +description = "Parser for ISO base media file format (mp4)" +documentation = "https://docs.rs/mp4parse_capi/" +license = "MPL-2.0" + +repository = "https://github.com/mozilla/mp4parse-rust" + +# Avoid complaints about trying to package test files. +exclude = [ + "*.mp4", +] + +[badges] +travis-ci = { repository = "https://github.com/mozilla/mp4parse-rust" } + +[dependencies] +byteorder = "1.2.1" +fallible_collections = { version = "0.4", features = ["std_io"] } +log = "0.4" +mp4parse = { version = "0.13.0", path = "../mp4parse", features = ["unstable-api"] } +num-traits = "0.2.14" + +[dev-dependencies] +env_logger = "0.8" + +[features] +missing-pixi-permitted = ["mp4parse/missing-pixi-permitted"] +3gpp = ["mp4parse/3gpp"] +meta-xml = ["mp4parse/meta-xml"] +mp4v = ["mp4parse/mp4v"] diff --git a/third_party/rust/mp4parse_capi/LICENSE b/third_party/rust/mp4parse_capi/LICENSE new file mode 100644 index 0000000000..14e2f777f6 --- /dev/null +++ b/third_party/rust/mp4parse_capi/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/third_party/rust/mp4parse_capi/README.md b/third_party/rust/mp4parse_capi/README.md new file mode 100644 index 0000000000..de01503734 --- /dev/null +++ b/third_party/rust/mp4parse_capi/README.md @@ -0,0 +1,2 @@ +`mp4parse-capi` is a C API that exposes the functionality of `mp4parse`. +See [the README in the mp4parse-rust repo](https://github.com/mozilla/mp4parse-rust/blob/master/README.md) for more details.
\ No newline at end of file diff --git a/third_party/rust/mp4parse_capi/cbindgen.toml b/third_party/rust/mp4parse_capi/cbindgen.toml new file mode 100644 index 0000000000..858d09921a --- /dev/null +++ b/third_party/rust/mp4parse_capi/cbindgen.toml @@ -0,0 +1,45 @@ +header = """/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */""" +autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. */ +#ifndef mp4parse_rust_mp4parse_h +#error "Don't include this file directly, instead include mp4parse.h" +#endif +""" +include_version = true +braces = "SameLine" +line_length = 100 +tab_width = 2 +language = "C" +cpp_compat = true + +[enum] +rename_variants = "QualifiedScreamingSnakeCase" + +[defines] +"feature = 3gpp" = "MP4PARSE_FEATURE_3GPP" +"feature = meta-xml" = "MP4PARSE_FEATURE_META_XML" +"feature = unstable-api" = "MP4PARSE_UNSTABLE_API" + +[parse] +parse_deps = true +include = ["mp4parse"] + +[export] +# `mp4parse::Status` was previously defined as `mp4parse_capi::Mp4parseStatus`, +# but now is referenced from `mp4parse_capi` via `pub use mp4parse::Status as Mp4parseStatus`, +# the name `Status` does not appear in the public C API, so we must force its inclusion. +# Similarly, we must force the inclusion of `mp4parse::Feature` as well. +include = ["Status", "Feature"] + +[export.rename] +# We need to declare these types in mp4parse, but we rename them in the generated +# header to match mp4parse_capi naming conventions +"Status" = "Mp4parseStatus" +"Feature" = "Mp4parseFeature" +"ParseStrictness" = "Mp4parseStrictness" +"ImageSpatialExtentsProperty" = "Mp4parseImageSpatialExtents" +"ImageRotation" = "Mp4parseIrot" +"ImageMirror" = "Mp4parseImir" +"Indice" = "Mp4parseIndice" +"NclxColourInformation" = "Mp4parseNclxColourInformation" diff --git a/third_party/rust/mp4parse_capi/examples/dump.rs b/third_party/rust/mp4parse_capi/examples/dump.rs new file mode 100644 index 0000000000..1392b304f8 --- /dev/null +++ b/third_party/rust/mp4parse_capi/examples/dump.rs @@ -0,0 +1,205 @@ +extern crate mp4parse; +extern crate mp4parse_capi; + +#[macro_use] +extern crate log; + +extern crate env_logger; + +use mp4parse::ParseStrictness; +use mp4parse_capi::*; +use std::env; +use std::fs::File; +use std::io::Read; + +extern "C" fn buf_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize { + let input: &mut std::fs::File = unsafe { &mut *(userdata as *mut _) }; + let buf = unsafe { std::slice::from_raw_parts_mut(buf, size) }; + match input.read(buf) { + Ok(n) => n as isize, + Err(_) => -1, + } +} + +fn dump_avif(filename: &str, strictness: ParseStrictness) { + let mut file = File::open(filename).expect("Unknown file"); + let io = Mp4parseIo { + read: Some(buf_read), + userdata: &mut file as *mut _ as *mut std::os::raw::c_void, + }; + + unsafe { + let mut parser = std::ptr::null_mut(); + let rv = mp4parse_avif_new(&io, strictness, &mut parser); + println!("mp4parse_avif_new -> {:?}", rv); + if rv == Mp4parseStatus::Ok { + println!( + "mp4parse_avif_get_image_safe -> {:?}", + mp4parse_avif_get_image_safe(&*parser) + ); + } + } +} + +fn dump_file(filename: &str, strictness: ParseStrictness) { + let mut file = File::open(filename).expect("Unknown file"); + let io = Mp4parseIo { + read: Some(buf_read), + userdata: &mut file as *mut _ as *mut std::os::raw::c_void, + }; + + unsafe { + let mut parser = std::ptr::null_mut(); + let rv = mp4parse_new(&io, &mut parser); + + match rv { + Mp4parseStatus::Ok => (), + Mp4parseStatus::Invalid => { + println!("-- failed to parse as mp4 video, trying AVIF"); + dump_avif(filename, strictness); + } + _ => { + println!("-- fail to parse: {:?}, '-v' for more info", rv); + return; + } + } + + let mut frag_info = Mp4parseFragmentInfo::default(); + match mp4parse_get_fragment_info(parser, &mut frag_info) { + Mp4parseStatus::Ok => { + println!("-- mp4parse_fragment_info {:?}", frag_info); + } + rv => { + println!("-- mp4parse_fragment_info failed with {:?}", rv); + return; + } + } + + let mut counts: u32 = 0; + match mp4parse_get_track_count(parser, &mut counts) { + Mp4parseStatus::Ok => (), + _ => { + println!("-- mp4parse_get_track_count failed"); + return; + } + } + + for i in 0..counts { + let mut track_info = Mp4parseTrackInfo { + track_type: Mp4parseTrackType::Audio, + ..Default::default() + }; + match mp4parse_get_track_info(parser, i, &mut track_info) { + Mp4parseStatus::Ok => { + println!("-- mp4parse_get_track_info {:?}", track_info); + } + _ => { + println!("-- mp4parse_get_track_info failed, track id: {}", i); + return; + } + } + + match track_info.track_type { + Mp4parseTrackType::Audio => { + let mut audio_info = Mp4parseTrackAudioInfo::default(); + match mp4parse_get_track_audio_info(parser, i, &mut audio_info) { + Mp4parseStatus::Ok => { + println!("-- mp4parse_get_track_audio_info {:?}", audio_info); + for i in 0..audio_info.sample_info_count as isize { + let sample_info = audio_info.sample_info.offset(i); + println!( + " -- mp4parse_get_track_audio_info sample_info[{:?}] {:?}", + i, *sample_info + ); + } + } + _ => { + println!("-- mp4parse_get_track_audio_info failed, track id: {}", i); + return; + } + } + } + Mp4parseTrackType::Video => { + let mut video_info = Mp4parseTrackVideoInfo::default(); + match mp4parse_get_track_video_info(parser, i, &mut video_info) { + Mp4parseStatus::Ok => { + println!("-- mp4parse_get_track_video_info {:?}", video_info); + for i in 0..video_info.sample_info_count as isize { + let sample_info = video_info.sample_info.offset(i); + println!( + " -- mp4parse_get_track_video_info sample_info[{:?}] {:?}", + i, *sample_info + ); + } + } + _ => { + println!("-- mp4parse_get_track_video_info failed, track id: {}", i); + return; + } + } + } + Mp4parseTrackType::Metadata => { + println!("TODO metadata track"); + } + } + + let mut indices = Mp4parseByteData::default(); + match mp4parse_get_indice_table(parser, track_info.track_id, &mut indices) { + Mp4parseStatus::Ok => { + println!( + "-- mp4parse_get_indice_table track_id {} indices {:?}", + track_info.track_id, indices + ); + } + _ => { + println!( + "-- mp4parse_get_indice_table failed, track_info.track_id: {}", + track_info.track_id + ); + return; + } + } + } + mp4parse_free(parser); + } +} + +fn main() { + let mut dump_func: fn(&str, ParseStrictness) = dump_file; + let mut verbose = false; + let mut strictness = ParseStrictness::Normal; + let mut filenames = Vec::new(); + for arg in env::args().skip(1) { + match arg.as_str() { + "--avif" => dump_func = dump_avif, + "--strict" => strictness = ParseStrictness::Strict, + "--permissive" => strictness = ParseStrictness::Permissive, + "-v" | "--verbose" => verbose = true, + _ => { + if let Some("-") = arg.get(0..1) { + eprintln!("Ignoring unknown switch {:?}", arg); + } else { + filenames.push(arg) + } + } + } + } + + if filenames.is_empty() { + eprintln!("No files to dump, exiting..."); + return; + } + + let env = env_logger::Env::default(); + let mut logger = env_logger::Builder::from_env(env); + if verbose { + logger.filter(None, log::LevelFilter::Debug); + } + logger.init(); + + for filename in filenames { + info!("-- dump of '{}' --", filename); + dump_func(filename.as_str(), strictness); + info!("-- end of '{}' --", filename); + } +} diff --git a/third_party/rust/mp4parse_capi/src/lib.rs b/third_party/rust/mp4parse_capi/src/lib.rs new file mode 100644 index 0000000000..627015e69f --- /dev/null +++ b/third_party/rust/mp4parse_capi/src/lib.rs @@ -0,0 +1,1619 @@ +//! C API for mp4parse module. +//! +//! Parses ISO Base Media Format aka video/mp4 streams. +//! +//! # Examples +//! +//! ```rust +//! extern crate mp4parse_capi; +//! use std::io::Read; +//! +//! extern fn buf_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize { +//! let mut input: &mut std::fs::File = unsafe { &mut *(userdata as *mut _) }; +//! let mut buf = unsafe { std::slice::from_raw_parts_mut(buf, size) }; +//! match input.read(&mut buf) { +//! Ok(n) => n as isize, +//! Err(_) => -1, +//! } +//! } +//! let capi_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); +//! let mut file = std::fs::File::open(capi_dir + "/../mp4parse/tests/minimal.mp4").unwrap(); +//! let io = mp4parse_capi::Mp4parseIo { +//! read: Some(buf_read), +//! userdata: &mut file as *mut _ as *mut std::os::raw::c_void +//! }; +//! let mut parser = std::ptr::null_mut(); +//! unsafe { +//! let rv = mp4parse_capi::mp4parse_new(&io, &mut parser); +//! assert_eq!(rv, mp4parse_capi::Mp4parseStatus::Ok); +//! assert!(!parser.is_null()); +//! mp4parse_capi::mp4parse_free(parser); +//! } +//! ``` + +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +extern crate byteorder; +extern crate log; +extern crate mp4parse; +extern crate num_traits; + +use byteorder::WriteBytesExt; +use std::convert::TryFrom; +use std::convert::TryInto; + +use std::io::Read; + +// Symbols we need from our rust api. +use mp4parse::serialize_opus_header; +use mp4parse::unstable::{ + create_sample_table, media_time_to_us, track_time_to_us, CheckedInteger, Indice, Microseconds, +}; +use mp4parse::AudioCodecSpecific; +use mp4parse::AvifContext; +use mp4parse::CodecType; +use mp4parse::MediaContext; +// Re-exported so consumers don't have to depend on mp4parse as well +pub use mp4parse::ParseStrictness; +use mp4parse::SampleEntry; +pub use mp4parse::Status as Mp4parseStatus; +use mp4parse::TrackType; +use mp4parse::TryBox; +use mp4parse::TryHashMap; +use mp4parse::TryVec; +use mp4parse::VideoCodecSpecific; + +// To ensure we don't use stdlib allocating types by accident +#[allow(dead_code)] +struct Vec; +#[allow(dead_code)] +struct Box; +#[allow(dead_code)] +struct HashMap; +#[allow(dead_code)] +struct String; + +#[repr(C)] +#[derive(PartialEq, Debug)] +pub enum Mp4parseTrackType { + Video = 0, + Audio = 1, + Metadata = 2, +} + +impl Default for Mp4parseTrackType { + fn default() -> Self { + Mp4parseTrackType::Video + } +} + +#[allow(non_camel_case_types, clippy::upper_case_acronyms)] +#[repr(C)] +#[derive(PartialEq, Debug)] +pub enum Mp4parseCodec { + Unknown, + Aac, + Flac, + Opus, + Avc, + Vp9, + Av1, + Mp3, + Mp4v, + Jpeg, // for QT JPEG atom in video track + Ac3, + Ec3, + Alac, + H263, + #[cfg(feature = "3gpp")] + AMRNB, + #[cfg(feature = "3gpp")] + AMRWB, +} + +impl Default for Mp4parseCodec { + fn default() -> Self { + Mp4parseCodec::Unknown + } +} + +#[repr(C)] +#[derive(PartialEq, Debug)] +pub enum Mp4ParseEncryptionSchemeType { + None, + Cenc, + Cbc1, + Cens, + Cbcs, + // Schemes also have a version component. At the time of writing, this does + // not impact handling, so we do not expose it. Note that this may need to + // be exposed in future, should the spec change. +} + +impl Default for Mp4ParseEncryptionSchemeType { + fn default() -> Self { + Mp4ParseEncryptionSchemeType::None + } +} + +#[repr(C)] +#[derive(Default, Debug)] +pub struct Mp4parseTrackInfo { + pub track_type: Mp4parseTrackType, + pub track_id: u32, + pub duration: u64, + pub media_time: CheckedInteger<i64>, // wants to be u64? understand how elst adjustment works + // TODO(kinetik): include crypto guff + // If this changes to u64, we can get rid of the strange + // impl Sub for CheckedInteger<u64> +} + +#[repr(C)] +#[derive(Debug)] +pub struct Mp4parseByteData { + pub length: usize, + // cheddar can't handle generic type, so it needs to be multiple data types here. + pub data: *const u8, + pub indices: *const Indice, +} + +impl Mp4parseByteData { + fn with_data(slice: &[u8]) -> Self { + Self { + length: slice.len(), + data: if !slice.is_empty() { + slice.as_ptr() + } else { + std::ptr::null() + }, + indices: std::ptr::null(), + } + } +} + +impl Default for Mp4parseByteData { + fn default() -> Self { + Self { + length: 0, + data: std::ptr::null(), + indices: std::ptr::null(), + } + } +} + +impl Mp4parseByteData { + fn set_data(&mut self, data: &[u8]) { + self.length = data.len(); + self.data = data.as_ptr(); + } + + fn set_indices(&mut self, data: &[Indice]) { + self.length = data.len(); + self.indices = data.as_ptr(); + } +} + +#[repr(C)] +#[derive(Default)] +pub struct Mp4parsePsshInfo { + pub data: Mp4parseByteData, +} + +#[repr(u8)] +#[derive(Debug, PartialEq)] +pub enum OptionalFourCc { + None, + Some([u8; 4]), +} + +impl Default for OptionalFourCc { + fn default() -> Self { + Self::None + } +} + +#[repr(C)] +#[derive(Default, Debug)] +pub struct Mp4parseSinfInfo { + pub original_format: OptionalFourCc, + pub scheme_type: Mp4ParseEncryptionSchemeType, + pub is_encrypted: u8, + pub iv_size: u8, + pub kid: Mp4parseByteData, + // Members for pattern encryption schemes, may be 0 (u8) or empty + // (Mp4parseByteData) if pattern encryption is not in use + pub crypt_byte_block: u8, + pub skip_byte_block: u8, + pub constant_iv: Mp4parseByteData, + // End pattern encryption scheme members +} + +#[repr(C)] +#[derive(Default, Debug)] +pub struct Mp4parseTrackAudioSampleInfo { + pub codec_type: Mp4parseCodec, + pub channels: u16, + pub bit_depth: u16, + pub sample_rate: u32, + pub profile: u16, + pub extended_profile: u16, + pub codec_specific_config: Mp4parseByteData, + pub extra_data: Mp4parseByteData, + pub protected_data: Mp4parseSinfInfo, +} + +#[repr(C)] +#[derive(Debug)] +pub struct Mp4parseTrackAudioInfo { + pub sample_info_count: u32, + pub sample_info: *const Mp4parseTrackAudioSampleInfo, +} + +impl Default for Mp4parseTrackAudioInfo { + fn default() -> Self { + Self { + sample_info_count: 0, + sample_info: std::ptr::null(), + } + } +} + +#[repr(C)] +#[derive(Default, Debug)] +pub struct Mp4parseTrackVideoSampleInfo { + pub codec_type: Mp4parseCodec, + pub image_width: u16, + pub image_height: u16, + pub extra_data: Mp4parseByteData, + pub protected_data: Mp4parseSinfInfo, +} + +#[repr(C)] +#[derive(Debug)] +pub struct Mp4parseTrackVideoInfo { + pub display_width: u32, + pub display_height: u32, + pub rotation: u16, + pub sample_info_count: u32, + pub sample_info: *const Mp4parseTrackVideoSampleInfo, +} + +impl Default for Mp4parseTrackVideoInfo { + fn default() -> Self { + Self { + display_width: 0, + display_height: 0, + rotation: 0, + sample_info_count: 0, + sample_info: std::ptr::null(), + } + } +} + +#[repr(C)] +#[derive(Default, Debug)] +pub struct Mp4parseFragmentInfo { + pub fragment_duration: u64, + // TODO: + // info in trex box. +} + +#[derive(Default)] +pub struct Mp4parseParser { + context: MediaContext, + opus_header: TryHashMap<u32, TryVec<u8>>, + pssh_data: TryVec<u8>, + sample_table: TryHashMap<u32, TryVec<Indice>>, + // Store a mapping from track index (not id) to associated sample + // descriptions. Because each track has a variable number of sample + // descriptions, and because we need the data to live long enough to be + // copied out by callers, we store these on the parser struct. + audio_track_sample_descriptions: TryHashMap<u32, TryVec<Mp4parseTrackAudioSampleInfo>>, + video_track_sample_descriptions: TryHashMap<u32, TryVec<Mp4parseTrackVideoSampleInfo>>, +} + +#[repr(C)] +#[derive(Debug)] +pub struct Mp4parseAvifImageItem { + pub coded_data: Mp4parseByteData, + pub bits_per_channel: Mp4parseByteData, +} + +#[repr(C)] +#[derive(Debug)] +pub struct Mp4parseAvifImage { + pub primary_image: Mp4parseAvifImageItem, + /// The size of the image; should never be null unless using permissive parsing + pub spatial_extents: *const mp4parse::ImageSpatialExtentsProperty, + pub nclx_colour_information: *const mp4parse::NclxColourInformation, + pub icc_colour_information: Mp4parseByteData, + pub image_rotation: mp4parse::ImageRotation, + pub image_mirror: *const mp4parse::ImageMirror, + pub pixel_aspect_ratio: *const mp4parse::PixelAspectRatio, + /// If no alpha item exists, members' `.length` will be 0 and `.data` will be null + pub alpha_image: Mp4parseAvifImageItem, + pub premultiplied_alpha: bool, + pub major_brand: [u8; 4], + pub has_sequence: bool, + pub unsupported_features_bitfield: u32, +} + +/// A unified interface for the parsers which have different contexts, but +/// share the same pattern of construction. This allows unification of +/// argument validation from C and minimizes the surface of unsafe code. +trait ContextParser +where + Self: Sized, +{ + type Context; + + fn with_context(context: Self::Context) -> Self; + + fn read<T: Read>(io: &mut T, strictness: ParseStrictness) -> mp4parse::Result<Self::Context>; +} + +impl Mp4parseParser { + fn context(&self) -> &MediaContext { + &self.context + } + + fn context_mut(&mut self) -> &mut MediaContext { + &mut self.context + } +} + +impl ContextParser for Mp4parseParser { + type Context = MediaContext; + + fn with_context(context: Self::Context) -> Self { + Self { + context, + ..Default::default() + } + } + + fn read<T: Read>(io: &mut T, _strictness: ParseStrictness) -> mp4parse::Result<Self::Context> { + let r = mp4parse::read_mp4(io); + log::debug!("mp4parse::read_mp4 -> {:?}", r); + r + } +} + +pub struct Mp4parseAvifParser { + context: AvifContext, +} + +impl Mp4parseAvifParser { + fn context(&self) -> &AvifContext { + &self.context + } +} + +impl ContextParser for Mp4parseAvifParser { + type Context = AvifContext; + + fn with_context(context: Self::Context) -> Self { + Self { context } + } + + fn read<T: Read>(io: &mut T, strictness: ParseStrictness) -> mp4parse::Result<Self::Context> { + let r = mp4parse::read_avif(io, strictness); + if r.is_err() { + log::debug!("{:?}", r); + } + log::trace!("mp4parse::read_avif -> {:?}", r); + r + } +} + +#[repr(C)] +#[derive(Clone)] +pub struct Mp4parseIo { + pub read: Option< + extern "C" fn(buffer: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize, + >, + pub userdata: *mut std::os::raw::c_void, +} + +impl Read for Mp4parseIo { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { + if buf.len() > isize::max_value() as usize { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + "buf length overflow in Mp4parseIo Read impl", + )); + } + let rv = self.read.unwrap()(buf.as_mut_ptr(), buf.len(), self.userdata); + if rv >= 0 { + Ok(rv as usize) + } else { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "I/O error in Mp4parseIo Read impl", + )) + } + } +} + +// C API wrapper functions. + +/// Allocate an `Mp4parseParser*` to read from the supplied `Mp4parseIo` and +/// parse the content from the `Mp4parseIo` argument until EOF or error. +/// +/// # Safety +/// +/// This function is unsafe because it dereferences the `io` and `parser_out` +/// pointers given to it. The caller should ensure that the `Mp4ParseIo` +/// struct passed in is a valid pointer. The caller should also ensure the +/// members of io are valid: the `read` function should be sanely implemented, +/// and the `userdata` pointer should be valid. The `parser_out` should be a +/// valid pointer to a location containing a null pointer. Upon successful +/// return (`Mp4parseStatus::Ok`), that location will contain the address of +/// an `Mp4parseParser` allocated by this function. +/// +/// To avoid leaking memory, any successful return of this function must be +/// paired with a call to `mp4parse_free`. In the event of error, no memory +/// will be allocated and `mp4parse_free` must *not* be called. +#[no_mangle] +pub unsafe extern "C" fn mp4parse_new( + io: *const Mp4parseIo, + parser_out: *mut *mut Mp4parseParser, +) -> Mp4parseStatus { + mp4parse_new_common(io, ParseStrictness::Normal, parser_out) +} + +/// Allocate an `Mp4parseAvifParser*` to read from the supplied `Mp4parseIo`. +/// +/// See mp4parse_new; this function is identical except that it allocates an +/// `Mp4parseAvifParser`, which (when successful) must be paired with a call +/// to mp4parse_avif_free. +/// +/// # Safety +/// +/// Same as mp4parse_new. +#[no_mangle] +pub unsafe extern "C" fn mp4parse_avif_new( + io: *const Mp4parseIo, + strictness: ParseStrictness, + parser_out: *mut *mut Mp4parseAvifParser, +) -> Mp4parseStatus { + mp4parse_new_common(io, strictness, parser_out) +} + +unsafe fn mp4parse_new_common<P: ContextParser>( + io: *const Mp4parseIo, + strictness: ParseStrictness, + parser_out: *mut *mut P, +) -> Mp4parseStatus { + // Validate arguments from C. + if io.is_null() + || (*io).userdata.is_null() + || (*io).read.is_none() + || parser_out.is_null() + || !(*parser_out).is_null() + { + Mp4parseStatus::BadArg + } else { + match mp4parse_new_common_safe(&mut (*io).clone(), strictness) { + Ok(parser) => { + *parser_out = parser; + Mp4parseStatus::Ok + } + Err(status) => status, + } + } +} + +fn mp4parse_new_common_safe<T: Read, P: ContextParser>( + io: &mut T, + strictness: ParseStrictness, +) -> Result<*mut P, Mp4parseStatus> { + P::read(io, strictness) + .map(P::with_context) + .and_then(|x| TryBox::try_new(x).map_err(mp4parse::Error::from)) + .map(TryBox::into_raw) + .map_err(Mp4parseStatus::from) +} + +/// Free an `Mp4parseParser*` allocated by `mp4parse_new()`. +/// +/// # Safety +/// +/// This function is unsafe because it creates a box from a raw pointer. +/// Callers should ensure that the parser pointer points to a valid +/// `Mp4parseParser` created by `mp4parse_new`. +#[no_mangle] +pub unsafe extern "C" fn mp4parse_free(parser: *mut Mp4parseParser) { + assert!(!parser.is_null()); + let _ = TryBox::from_raw(parser); +} + +/// Free an `Mp4parseAvifParser*` allocated by `mp4parse_avif_new()`. +/// +/// # Safety +/// +/// This function is unsafe because it creates a box from a raw pointer. +/// Callers should ensure that the parser pointer points to a valid +/// `Mp4parseAvifParser` created by `mp4parse_avif_new`. +#[no_mangle] +pub unsafe extern "C" fn mp4parse_avif_free(parser: *mut Mp4parseAvifParser) { + assert!(!parser.is_null()); + let _ = TryBox::from_raw(parser); +} + +/// Return the number of tracks parsed by previous `mp4parse_read()` call. +/// +/// # Safety +/// +/// This function is unsafe because it dereferences both the parser and count +/// raw pointers passed into it. Callers should ensure the parser pointer +/// points to a valid `Mp4parseParser`, and that the count pointer points an +/// appropriate memory location to have a `u32` written to. +#[no_mangle] +pub unsafe extern "C" fn mp4parse_get_track_count( + parser: *const Mp4parseParser, + count: *mut u32, +) -> Mp4parseStatus { + // Validate arguments from C. + if parser.is_null() || count.is_null() { + return Mp4parseStatus::BadArg; + } + let context = (*parser).context(); + + // Make sure the track count fits in a u32. + if context.tracks.len() > u32::max_value() as usize { + return Mp4parseStatus::Invalid; + } + *count = context.tracks.len() as u32; + Mp4parseStatus::Ok +} + +/// Fill the supplied `Mp4parseTrackInfo` with metadata for `track`. +/// +/// # Safety +/// +/// This function is unsafe because it dereferences the the parser and info raw +/// pointers passed to it. Callers should ensure the parser pointer points to a +/// valid `Mp4parseParser` and that the info pointer points to a valid +/// `Mp4parseTrackInfo`. +#[no_mangle] +pub unsafe extern "C" fn mp4parse_get_track_info( + parser: *mut Mp4parseParser, + track_index: u32, + info: *mut Mp4parseTrackInfo, +) -> Mp4parseStatus { + if parser.is_null() || info.is_null() { + return Mp4parseStatus::BadArg; + } + + // Initialize fields to default values to ensure all fields are always valid. + *info = Default::default(); + + let context = (*parser).context_mut(); + let track_index: usize = track_index as usize; + let info: &mut Mp4parseTrackInfo = &mut *info; + + if track_index >= context.tracks.len() { + return Mp4parseStatus::BadArg; + } + + info.track_type = match context.tracks[track_index].track_type { + TrackType::Video => Mp4parseTrackType::Video, + TrackType::Audio => Mp4parseTrackType::Audio, + TrackType::Metadata => Mp4parseTrackType::Metadata, + TrackType::Unknown => return Mp4parseStatus::Unsupported, + }; + + let track = &context.tracks[track_index]; + + if let (Some(track_timescale), Some(context_timescale)) = (track.timescale, context.timescale) { + let media_time: CheckedInteger<_> = match track + .media_time + .map_or(Some(Microseconds(0)), |media_time| { + track_time_to_us(media_time, track_timescale) + }) { + Some(time) => time.0.into(), + None => return Mp4parseStatus::Invalid, + }; + let empty_duration: CheckedInteger<_> = match track + .empty_duration + .map_or(Some(Microseconds(0)), |empty_duration| { + media_time_to_us(empty_duration, context_timescale) + }) { + Some(time) => time.0.into(), + None => return Mp4parseStatus::Invalid, + }; + info.media_time = match media_time - empty_duration { + Some(difference) => difference, + None => return Mp4parseStatus::Invalid, + }; + + if let Some(track_duration) = track.duration { + match track_time_to_us(track_duration, track_timescale) { + Some(duration) => info.duration = duration.0, + None => return Mp4parseStatus::Invalid, + } + } else { + // Duration unknown; stagefright returns 0 for this. + info.duration = 0 + } + } else { + return Mp4parseStatus::Invalid; + } + + info.track_id = match track.track_id { + Some(track_id) => track_id, + None => return Mp4parseStatus::Invalid, + }; + + Mp4parseStatus::Ok +} + +/// Fill the supplied `Mp4parseTrackAudioInfo` with metadata for `track`. +/// +/// # Safety +/// +/// This function is unsafe because it dereferences the the parser and info raw +/// pointers passed to it. Callers should ensure the parser pointer points to a +/// valid `Mp4parseParser` and that the info pointer points to a valid +/// `Mp4parseTrackAudioInfo`. +#[no_mangle] +pub unsafe extern "C" fn mp4parse_get_track_audio_info( + parser: *mut Mp4parseParser, + track_index: u32, + info: *mut Mp4parseTrackAudioInfo, +) -> Mp4parseStatus { + if parser.is_null() || info.is_null() { + return Mp4parseStatus::BadArg; + } + + // Initialize fields to default values to ensure all fields are always valid. + *info = Default::default(); + + get_track_audio_info(&mut *parser, track_index, &mut *info).into() +} + +fn get_track_audio_info( + parser: &mut Mp4parseParser, + track_index: u32, + info: &mut Mp4parseTrackAudioInfo, +) -> Result<(), Mp4parseStatus> { + let Mp4parseParser { + context, + opus_header, + .. + } = parser; + + if track_index as usize >= context.tracks.len() { + return Err(Mp4parseStatus::BadArg); + } + + let track = &context.tracks[track_index as usize]; + + if track.track_type != TrackType::Audio { + return Err(Mp4parseStatus::Invalid); + } + + // Handle track.stsd + let stsd = match track.stsd { + Some(ref stsd) => stsd, + None => return Err(Mp4parseStatus::Invalid), // Stsd should be present + }; + + if stsd.descriptions.is_empty() { + return Err(Mp4parseStatus::Invalid); // Should have at least 1 description + } + + let mut audio_sample_infos = TryVec::with_capacity(stsd.descriptions.len())?; + for description in stsd.descriptions.iter() { + let mut sample_info = Mp4parseTrackAudioSampleInfo::default(); + let audio = match description { + SampleEntry::Audio(a) => a, + _ => return Err(Mp4parseStatus::Invalid), + }; + + // UNKNOWN for unsupported format. + sample_info.codec_type = match audio.codec_specific { + AudioCodecSpecific::OpusSpecificBox(_) => Mp4parseCodec::Opus, + AudioCodecSpecific::FLACSpecificBox(_) => Mp4parseCodec::Flac, + AudioCodecSpecific::ES_Descriptor(ref esds) if esds.audio_codec == CodecType::AAC => { + Mp4parseCodec::Aac + } + AudioCodecSpecific::ES_Descriptor(ref esds) if esds.audio_codec == CodecType::MP3 => { + Mp4parseCodec::Mp3 + } + AudioCodecSpecific::ES_Descriptor(_) | AudioCodecSpecific::LPCM => { + Mp4parseCodec::Unknown + } + AudioCodecSpecific::MP3 => Mp4parseCodec::Mp3, + AudioCodecSpecific::ALACSpecificBox(_) => Mp4parseCodec::Alac, + #[cfg(feature = "3gpp")] + AudioCodecSpecific::AMRSpecificBox(_) => { + if audio.codec_type == CodecType::AMRNB { + Mp4parseCodec::AMRNB + } else { + Mp4parseCodec::AMRWB + } + } + }; + sample_info.channels = audio.channelcount as u16; + sample_info.bit_depth = audio.samplesize; + sample_info.sample_rate = audio.samplerate as u32; + // sample_info.profile is handled below on a per case basis + + match audio.codec_specific { + AudioCodecSpecific::ES_Descriptor(ref esds) => { + if esds.codec_esds.len() > std::u32::MAX as usize { + return Err(Mp4parseStatus::Invalid); + } + sample_info.extra_data.length = esds.codec_esds.len(); + sample_info.extra_data.data = esds.codec_esds.as_ptr(); + sample_info.codec_specific_config.length = esds.decoder_specific_data.len(); + sample_info.codec_specific_config.data = esds.decoder_specific_data.as_ptr(); + if let Some(rate) = esds.audio_sample_rate { + sample_info.sample_rate = rate; + } + if let Some(channels) = esds.audio_channel_count { + sample_info.channels = channels; + } + if let Some(profile) = esds.audio_object_type { + sample_info.profile = profile; + } + sample_info.extended_profile = match esds.extended_audio_object_type { + Some(extended_profile) => extended_profile, + _ => sample_info.profile, + }; + } + AudioCodecSpecific::FLACSpecificBox(ref flac) => { + // Return the STREAMINFO metadata block in the codec_specific. + let streaminfo = &flac.blocks[0]; + if streaminfo.block_type != 0 || streaminfo.data.len() != 34 { + return Err(Mp4parseStatus::Invalid); + } + sample_info.codec_specific_config.length = streaminfo.data.len(); + sample_info.codec_specific_config.data = streaminfo.data.as_ptr(); + } + AudioCodecSpecific::OpusSpecificBox(ref opus) => { + let mut v = TryVec::new(); + match serialize_opus_header(opus, &mut v) { + Err(_) => { + return Err(Mp4parseStatus::Invalid); + } + Ok(_) => { + opus_header.insert(track_index, v)?; + if let Some(v) = opus_header.get(&track_index) { + if v.len() > std::u32::MAX as usize { + return Err(Mp4parseStatus::Invalid); + } + sample_info.codec_specific_config.length = v.len(); + sample_info.codec_specific_config.data = v.as_ptr(); + } + } + } + } + AudioCodecSpecific::ALACSpecificBox(ref alac) => { + sample_info.codec_specific_config.length = alac.data.len(); + sample_info.codec_specific_config.data = alac.data.as_ptr(); + } + AudioCodecSpecific::MP3 | AudioCodecSpecific::LPCM => (), + #[cfg(feature = "3gpp")] + AudioCodecSpecific::AMRSpecificBox(_) => (), + } + + if let Some(p) = audio + .protection_info + .iter() + .find(|sinf| sinf.tenc.is_some()) + { + sample_info.protected_data.original_format = + OptionalFourCc::Some(p.original_format.value); + sample_info.protected_data.scheme_type = match p.scheme_type { + Some(ref scheme_type_box) => { + match scheme_type_box.scheme_type.value.as_ref() { + b"cenc" => Mp4ParseEncryptionSchemeType::Cenc, + b"cbcs" => Mp4ParseEncryptionSchemeType::Cbcs, + // We don't support other schemes, and shouldn't reach + // this case. Try to gracefully handle by treating as + // no encryption case. + _ => Mp4ParseEncryptionSchemeType::None, + } + } + None => Mp4ParseEncryptionSchemeType::None, + }; + if let Some(ref tenc) = p.tenc { + sample_info.protected_data.is_encrypted = tenc.is_encrypted; + sample_info.protected_data.iv_size = tenc.iv_size; + sample_info.protected_data.kid.set_data(&(tenc.kid)); + sample_info.protected_data.crypt_byte_block = + tenc.crypt_byte_block_count.unwrap_or(0); + sample_info.protected_data.skip_byte_block = + tenc.skip_byte_block_count.unwrap_or(0); + if let Some(ref iv_vec) = tenc.constant_iv { + if iv_vec.len() > std::u32::MAX as usize { + return Err(Mp4parseStatus::Invalid); + } + sample_info.protected_data.constant_iv.set_data(iv_vec); + }; + } + } + audio_sample_infos.push(sample_info)?; + } + + parser + .audio_track_sample_descriptions + .insert(track_index, audio_sample_infos)?; + match parser.audio_track_sample_descriptions.get(&track_index) { + Some(sample_info) => { + if sample_info.len() > std::u32::MAX as usize { + // Should never happen due to upper limits on number of sample + // descriptions a track can have, but lets be safe. + return Err(Mp4parseStatus::Invalid); + } + info.sample_info_count = sample_info.len() as u32; + info.sample_info = sample_info.as_ptr(); + } + None => return Err(Mp4parseStatus::Invalid), // Shouldn't happen, we just inserted the info! + } + + Ok(()) +} + +/// Fill the supplied `Mp4parseTrackVideoInfo` with metadata for `track`. +/// +/// # Safety +/// +/// This function is unsafe because it dereferences the the parser and info raw +/// pointers passed to it. Callers should ensure the parser pointer points to a +/// valid `Mp4parseParser` and that the info pointer points to a valid +/// `Mp4parseTrackVideoInfo`. +#[no_mangle] +pub unsafe extern "C" fn mp4parse_get_track_video_info( + parser: *mut Mp4parseParser, + track_index: u32, + info: *mut Mp4parseTrackVideoInfo, +) -> Mp4parseStatus { + if parser.is_null() || info.is_null() { + return Mp4parseStatus::BadArg; + } + + // Initialize fields to default values to ensure all fields are always valid. + *info = Default::default(); + + mp4parse_get_track_video_info_safe(&mut *parser, track_index, &mut *info).into() +} + +fn mp4parse_get_track_video_info_safe( + parser: &mut Mp4parseParser, + track_index: u32, + info: &mut Mp4parseTrackVideoInfo, +) -> Result<(), Mp4parseStatus> { + let context = parser.context(); + + if track_index as usize >= context.tracks.len() { + return Err(Mp4parseStatus::BadArg); + } + + let track = &context.tracks[track_index as usize]; + + if track.track_type != TrackType::Video { + return Err(Mp4parseStatus::Invalid); + } + + // Handle track.tkhd + if let Some(ref tkhd) = track.tkhd { + info.display_width = tkhd.width >> 16; // 16.16 fixed point + info.display_height = tkhd.height >> 16; // 16.16 fixed point + let matrix = ( + tkhd.matrix.a >> 16, + tkhd.matrix.b >> 16, + tkhd.matrix.c >> 16, + tkhd.matrix.d >> 16, + ); + info.rotation = match matrix { + (0, 1, -1, 0) => 90, // rotate 90 degrees + (-1, 0, 0, -1) => 180, // rotate 180 degrees + (0, -1, 1, 0) => 270, // rotate 270 degrees + _ => 0, + }; + } else { + return Err(Mp4parseStatus::Invalid); + } + + // Handle track.stsd + let stsd = match track.stsd { + Some(ref stsd) => stsd, + None => return Err(Mp4parseStatus::Invalid), // Stsd should be present + }; + + if stsd.descriptions.is_empty() { + return Err(Mp4parseStatus::Invalid); // Should have at least 1 description + } + + let mut video_sample_infos = TryVec::with_capacity(stsd.descriptions.len())?; + for description in stsd.descriptions.iter() { + let mut sample_info = Mp4parseTrackVideoSampleInfo::default(); + let video = match description { + SampleEntry::Video(v) => v, + _ => return Err(Mp4parseStatus::Invalid), + }; + + // UNKNOWN for unsupported format. + sample_info.codec_type = match video.codec_specific { + VideoCodecSpecific::VPxConfig(_) => Mp4parseCodec::Vp9, + VideoCodecSpecific::AV1Config(_) => Mp4parseCodec::Av1, + VideoCodecSpecific::AVCConfig(_) => Mp4parseCodec::Avc, + VideoCodecSpecific::H263Config(_) => Mp4parseCodec::H263, + #[cfg(feature = "mp4v")] + VideoCodecSpecific::ESDSConfig(_) => Mp4parseCodec::Mp4v, + #[cfg(not(feature = "mp4v"))] + VideoCodecSpecific::ESDSConfig(_) => + // MP4V (14496-2) video is unsupported. + { + Mp4parseCodec::Unknown + } + }; + sample_info.image_width = video.width; + sample_info.image_height = video.height; + + match video.codec_specific { + VideoCodecSpecific::AV1Config(ref config) => { + sample_info.extra_data.set_data(&config.raw_config); + } + VideoCodecSpecific::AVCConfig(ref data) | VideoCodecSpecific::ESDSConfig(ref data) => { + sample_info.extra_data.set_data(data); + } + _ => {} + } + + if let Some(p) = video + .protection_info + .iter() + .find(|sinf| sinf.tenc.is_some()) + { + sample_info.protected_data.original_format = + OptionalFourCc::Some(p.original_format.value); + sample_info.protected_data.scheme_type = match p.scheme_type { + Some(ref scheme_type_box) => { + match scheme_type_box.scheme_type.value.as_ref() { + b"cenc" => Mp4ParseEncryptionSchemeType::Cenc, + b"cbcs" => Mp4ParseEncryptionSchemeType::Cbcs, + // We don't support other schemes, and shouldn't reach + // this case. Try to gracefully handle by treating as + // no encryption case. + _ => Mp4ParseEncryptionSchemeType::None, + } + } + None => Mp4ParseEncryptionSchemeType::None, + }; + if let Some(ref tenc) = p.tenc { + sample_info.protected_data.is_encrypted = tenc.is_encrypted; + sample_info.protected_data.iv_size = tenc.iv_size; + sample_info.protected_data.kid.set_data(&(tenc.kid)); + sample_info.protected_data.crypt_byte_block = + tenc.crypt_byte_block_count.unwrap_or(0); + sample_info.protected_data.skip_byte_block = + tenc.skip_byte_block_count.unwrap_or(0); + if let Some(ref iv_vec) = tenc.constant_iv { + if iv_vec.len() > std::u32::MAX as usize { + return Err(Mp4parseStatus::Invalid); + } + sample_info.protected_data.constant_iv.set_data(iv_vec); + }; + } + } + video_sample_infos.push(sample_info)?; + } + + parser + .video_track_sample_descriptions + .insert(track_index, video_sample_infos)?; + match parser.video_track_sample_descriptions.get(&track_index) { + Some(sample_info) => { + if sample_info.len() > std::u32::MAX as usize { + // Should never happen due to upper limits on number of sample + // descriptions a track can have, but lets be safe. + return Err(Mp4parseStatus::Invalid); + } + info.sample_info_count = sample_info.len() as u32; + info.sample_info = sample_info.as_ptr(); + } + None => return Err(Mp4parseStatus::Invalid), // Shouldn't happen, we just inserted the info! + } + Ok(()) +} + +/// Return a pointer to the primary item parsed by previous `mp4parse_avif_new()` call. +/// +/// # Safety +/// +/// This function is unsafe because it dereferences both the parser and +/// avif_image raw pointers passed into it. Callers should ensure the parser +/// pointer points to a valid `Mp4parseAvifParser`, and that the avif_image +/// pointer points to a valid `Mp4parseAvifImage`. If there was not a previous +/// successful call to `mp4parse_avif_read()`, no guarantees are made as to +/// the state of `avif_image`. If `avif_image.alpha_image.coded_data` is set to +/// a positive `length` and non-null `data`, then the `avif_image` contains a +/// valid alpha channel data. Otherwise, the image is opaque. +#[no_mangle] +pub unsafe extern "C" fn mp4parse_avif_get_image( + parser: *const Mp4parseAvifParser, + avif_image: *mut Mp4parseAvifImage, +) -> Mp4parseStatus { + if parser.is_null() || avif_image.is_null() { + return Mp4parseStatus::BadArg; + } + + if let Ok(image) = mp4parse_avif_get_image_safe(&*parser) { + *avif_image = image; + Mp4parseStatus::Ok + } else { + Mp4parseStatus::Invalid + } +} + +pub fn mp4parse_avif_get_image_safe( + parser: &Mp4parseAvifParser, +) -> mp4parse::Result<Mp4parseAvifImage> { + let context = parser.context(); + + let primary_image = Mp4parseAvifImageItem { + coded_data: Mp4parseByteData::with_data(context.primary_item_coded_data().unwrap_or(&[])), + bits_per_channel: Mp4parseByteData::with_data( + context.primary_item_bits_per_channel().unwrap_or(Ok(&[]))?, + ), + }; + + // If there is no alpha present, all the `Mp4parseByteData`s will be zero length + let alpha_image = Mp4parseAvifImageItem { + coded_data: Mp4parseByteData::with_data(context.alpha_item_coded_data().unwrap_or(&[])), + bits_per_channel: Mp4parseByteData::with_data( + context.alpha_item_bits_per_channel().unwrap_or(Ok(&[]))?, + ), + }; + + Ok(Mp4parseAvifImage { + primary_image, + spatial_extents: context.spatial_extents_ptr()?, + nclx_colour_information: context + .nclx_colour_information_ptr() + .unwrap_or(Ok(std::ptr::null()))?, + icc_colour_information: Mp4parseByteData::with_data( + context.icc_colour_information().unwrap_or(Ok(&[]))?, + ), + image_rotation: context.image_rotation()?, + image_mirror: context.image_mirror_ptr()?, + pixel_aspect_ratio: context.pixel_aspect_ratio_ptr()?, + alpha_image, + premultiplied_alpha: context.premultiplied_alpha, + major_brand: context.major_brand.value, + has_sequence: context.has_sequence, + unsupported_features_bitfield: context.unsupported_features.into_bitfield(), + }) +} + +/// Fill the supplied `Mp4parseByteData` with index information from `track`. +/// +/// # Safety +/// +/// This function is unsafe because it dereferences the the parser and indices +/// raw pointers passed to it. Callers should ensure the parser pointer points +/// to a valid `Mp4parseParser` and that the indices pointer points to a valid +/// `Mp4parseByteData`. +#[no_mangle] +pub unsafe extern "C" fn mp4parse_get_indice_table( + parser: *mut Mp4parseParser, + track_id: u32, + indices: *mut Mp4parseByteData, +) -> Mp4parseStatus { + if parser.is_null() { + return Mp4parseStatus::BadArg; + } + + // Initialize fields to default values to ensure all fields are always valid. + *indices = Default::default(); + + get_indice_table(&mut *parser, track_id, &mut *indices).into() +} + +fn get_indice_table( + parser: &mut Mp4parseParser, + track_id: u32, + indices: &mut Mp4parseByteData, +) -> Result<(), Mp4parseStatus> { + let Mp4parseParser { + context, + sample_table: index_table, + .. + } = parser; + let tracks = &context.tracks; + let track = match tracks.iter().find(|track| track.track_id == Some(track_id)) { + Some(t) => t, + _ => return Err(Mp4parseStatus::Invalid), + }; + + if let Some(v) = index_table.get(&track_id) { + indices.set_indices(v); + return Ok(()); + } + + let media_time = match (&track.media_time, &track.timescale) { + (&Some(t), &Some(s)) => track_time_to_us(t, s) + .and_then(|v| i64::try_from(v.0).ok()) + .map(Into::into), + _ => None, + }; + + let empty_duration: Option<CheckedInteger<_>> = + match (&track.empty_duration, &context.timescale) { + (&Some(e), &Some(s)) => media_time_to_us(e, s) + .and_then(|v| i64::try_from(v.0).ok()) + .map(Into::into), + _ => None, + }; + + // Find the track start offset time from 'elst'. + // 'media_time' maps start time onward, 'empty_duration' adds time offset + // before first frame is displayed. + let offset_time = match (empty_duration, media_time) { + (Some(e), Some(m)) => (e - m).ok_or(Err(Mp4parseStatus::Invalid))?, + (Some(e), None) => e, + (None, Some(m)) => m, + _ => 0.into(), + }; + + if let Some(v) = create_sample_table(track, offset_time) { + indices.set_indices(&v); + index_table.insert(track_id, v)?; + return Ok(()); + } + + Err(Mp4parseStatus::Invalid) +} + +/// Fill the supplied `Mp4parseFragmentInfo` with metadata from fragmented file. +/// +/// # Safety +/// +/// This function is unsafe because it dereferences the the parser and +/// info raw pointers passed to it. Callers should ensure the parser +/// pointer points to a valid `Mp4parseParser` and that the info pointer points +/// to a valid `Mp4parseFragmentInfo`. + +#[no_mangle] +pub unsafe extern "C" fn mp4parse_get_fragment_info( + parser: *mut Mp4parseParser, + info: *mut Mp4parseFragmentInfo, +) -> Mp4parseStatus { + if parser.is_null() || info.is_null() { + return Mp4parseStatus::BadArg; + } + + // Initialize fields to default values to ensure all fields are always valid. + *info = Default::default(); + + let context = (*parser).context(); + let info: &mut Mp4parseFragmentInfo = &mut *info; + + info.fragment_duration = 0; + + let duration = match context.mvex { + Some(ref mvex) => mvex.fragment_duration, + None => return Mp4parseStatus::Invalid, + }; + + if let (Some(time), Some(scale)) = (duration, context.timescale) { + info.fragment_duration = match media_time_to_us(time, scale) { + Some(time_us) => time_us.0 as u64, + None => return Mp4parseStatus::Invalid, + } + } + + Mp4parseStatus::Ok +} + +/// Determine if an mp4 file is fragmented. A fragmented file needs mvex table +/// and contains no data in stts, stsc, and stco boxes. +/// +/// # Safety +/// +/// This function is unsafe because it dereferences the the parser and +/// fragmented raw pointers passed to it. Callers should ensure the parser +/// pointer points to a valid `Mp4parseParser` and that the fragmented pointer +/// points to an appropriate memory location to have a `u8` written to. +#[no_mangle] +pub unsafe extern "C" fn mp4parse_is_fragmented( + parser: *mut Mp4parseParser, + track_id: u32, + fragmented: *mut u8, +) -> Mp4parseStatus { + if parser.is_null() { + return Mp4parseStatus::BadArg; + } + + let context = (*parser).context_mut(); + let tracks = &context.tracks; + (*fragmented) = false as u8; + + if context.mvex.is_none() { + return Mp4parseStatus::Ok; + } + + // check sample tables. + let mut iter = tracks.iter(); + iter.find(|track| track.track_id == Some(track_id)) + .map_or(Mp4parseStatus::BadArg, |track| { + match (&track.stsc, &track.stco, &track.stts) { + (&Some(ref stsc), &Some(ref stco), &Some(ref stts)) + if stsc.samples.is_empty() + && stco.offsets.is_empty() + && stts.samples.is_empty() => + { + (*fragmented) = true as u8 + } + _ => {} + }; + Mp4parseStatus::Ok + }) +} + +/// Get 'pssh' system id and 'pssh' box content for eme playback. +/// +/// The data format of the `info` struct passed to gecko is: +/// +/// - system id (16 byte uuid) +/// - pssh box size (32-bit native endian) +/// - pssh box content (including header) +/// +/// # Safety +/// +/// This function is unsafe because it dereferences the the parser and +/// info raw pointers passed to it. Callers should ensure the parser +/// pointer points to a valid `Mp4parseParser` and that the fragmented pointer +/// points to a valid `Mp4parsePsshInfo`. +#[no_mangle] +pub unsafe extern "C" fn mp4parse_get_pssh_info( + parser: *mut Mp4parseParser, + info: *mut Mp4parsePsshInfo, +) -> Mp4parseStatus { + if parser.is_null() || info.is_null() { + return Mp4parseStatus::BadArg; + } + + // Initialize fields to default values to ensure all fields are always valid. + *info = Default::default(); + + get_pssh_info(&mut *parser, &mut *info).into() +} + +fn get_pssh_info( + parser: &mut Mp4parseParser, + info: &mut Mp4parsePsshInfo, +) -> Result<(), Mp4parseStatus> { + let Mp4parseParser { + context, pssh_data, .. + } = parser; + + pssh_data.clear(); + for pssh in &context.psshs { + let content_len = pssh + .box_content + .len() + .try_into() + .map_err(|_| Mp4parseStatus::Invalid)?; + let mut data_len = TryVec::new(); + data_len.write_u32::<byteorder::NativeEndian>(content_len)?; + pssh_data.extend_from_slice(pssh.system_id.as_slice())?; + pssh_data.extend_from_slice(data_len.as_slice())?; + pssh_data.extend_from_slice(pssh.box_content.as_slice())?; + } + + info.data.set_data(pssh_data); + + Ok(()) +} + +#[cfg(test)] +extern "C" fn error_read(_: *mut u8, _: usize, _: *mut std::os::raw::c_void) -> isize { + -1 +} + +#[cfg(test)] +extern "C" fn valid_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize { + let input: &mut std::fs::File = unsafe { &mut *(userdata as *mut _) }; + + let buf = unsafe { std::slice::from_raw_parts_mut(buf, size) }; + match input.read(buf) { + Ok(n) => n as isize, + Err(_) => -1, + } +} + +#[test] +fn get_track_count_null_parser() { + unsafe { + let mut count: u32 = 0; + let rv = mp4parse_get_track_count(std::ptr::null(), std::ptr::null_mut()); + assert_eq!(rv, Mp4parseStatus::BadArg); + let rv = mp4parse_get_track_count(std::ptr::null(), &mut count); + assert_eq!(rv, Mp4parseStatus::BadArg); + } +} + +#[test] +fn arg_validation() { + unsafe { + let rv = mp4parse_new(std::ptr::null(), std::ptr::null_mut()); + assert_eq!(rv, Mp4parseStatus::BadArg); + + // Passing a null Mp4parseIo is an error. + let mut parser = std::ptr::null_mut(); + let rv = mp4parse_new(std::ptr::null(), &mut parser); + assert_eq!(rv, Mp4parseStatus::BadArg); + assert!(parser.is_null()); + + let null_mut: *mut std::os::raw::c_void = std::ptr::null_mut(); + + // Passing an Mp4parseIo with null members is an error. + let io = Mp4parseIo { + read: None, + userdata: null_mut, + }; + let mut parser = std::ptr::null_mut(); + let rv = mp4parse_new(&io, &mut parser); + assert_eq!(rv, Mp4parseStatus::BadArg); + assert!(parser.is_null()); + + let mut dummy_value = 42; + let io = Mp4parseIo { + read: None, + userdata: &mut dummy_value as *mut _ as *mut std::os::raw::c_void, + }; + let mut parser = std::ptr::null_mut(); + let rv = mp4parse_new(&io, &mut parser); + assert_eq!(rv, Mp4parseStatus::BadArg); + assert!(parser.is_null()); + + let mut dummy_info = Mp4parseTrackInfo { + track_type: Mp4parseTrackType::Video, + ..Default::default() + }; + assert_eq!( + Mp4parseStatus::BadArg, + mp4parse_get_track_info(std::ptr::null_mut(), 0, &mut dummy_info) + ); + + let mut dummy_video = Mp4parseTrackVideoInfo { + display_width: 0, + display_height: 0, + rotation: 0, + sample_info_count: 0, + sample_info: std::ptr::null(), + }; + assert_eq!( + Mp4parseStatus::BadArg, + mp4parse_get_track_video_info(std::ptr::null_mut(), 0, &mut dummy_video) + ); + + let mut dummy_audio = Default::default(); + assert_eq!( + Mp4parseStatus::BadArg, + mp4parse_get_track_audio_info(std::ptr::null_mut(), 0, &mut dummy_audio) + ); + } +} + +#[test] +fn parser_input_must_be_null() { + let mut dummy_value = 42; + let io = Mp4parseIo { + read: Some(error_read), + userdata: &mut dummy_value as *mut _ as *mut std::os::raw::c_void, + }; + let mut parser = 0xDEAD_BEEF as *mut _; + let rv = unsafe { mp4parse_new(&io, &mut parser) }; + assert_eq!(rv, Mp4parseStatus::BadArg); +} + +#[test] +fn arg_validation_with_parser() { + unsafe { + let mut dummy_value = 42; + let io = Mp4parseIo { + read: Some(error_read), + userdata: &mut dummy_value as *mut _ as *mut std::os::raw::c_void, + }; + let mut parser = std::ptr::null_mut(); + let rv = mp4parse_new(&io, &mut parser); + assert_eq!(rv, Mp4parseStatus::Io); + assert!(parser.is_null()); + + // Null info pointers are an error. + assert_eq!( + Mp4parseStatus::BadArg, + mp4parse_get_track_info(parser, 0, std::ptr::null_mut()) + ); + assert_eq!( + Mp4parseStatus::BadArg, + mp4parse_get_track_video_info(parser, 0, std::ptr::null_mut()) + ); + assert_eq!( + Mp4parseStatus::BadArg, + mp4parse_get_track_audio_info(parser, 0, std::ptr::null_mut()) + ); + + let mut dummy_info = Mp4parseTrackInfo { + track_type: Mp4parseTrackType::Video, + ..Default::default() + }; + assert_eq!( + Mp4parseStatus::BadArg, + mp4parse_get_track_info(parser, 0, &mut dummy_info) + ); + + let mut dummy_video = Mp4parseTrackVideoInfo { + display_width: 0, + display_height: 0, + rotation: 0, + sample_info_count: 0, + sample_info: std::ptr::null(), + }; + assert_eq!( + Mp4parseStatus::BadArg, + mp4parse_get_track_video_info(parser, 0, &mut dummy_video) + ); + + let mut dummy_audio = Default::default(); + assert_eq!( + Mp4parseStatus::BadArg, + mp4parse_get_track_audio_info(parser, 0, &mut dummy_audio) + ); + } +} + +#[cfg(test)] +fn parse_minimal_mp4() -> *mut Mp4parseParser { + let mut file = std::fs::File::open("../mp4parse/tests/minimal.mp4").unwrap(); + let io = Mp4parseIo { + read: Some(valid_read), + userdata: &mut file as *mut _ as *mut std::os::raw::c_void, + }; + let mut parser = std::ptr::null_mut(); + let rv = unsafe { mp4parse_new(&io, &mut parser) }; + assert_eq!(Mp4parseStatus::Ok, rv); + parser +} + +#[test] +fn minimal_mp4_parse_ok() { + let parser = parse_minimal_mp4(); + + assert!(!parser.is_null()); + + unsafe { + mp4parse_free(parser); + } +} + +#[test] +fn minimal_mp4_get_track_cout() { + let parser = parse_minimal_mp4(); + + let mut count: u32 = 0; + assert_eq!(Mp4parseStatus::Ok, unsafe { + mp4parse_get_track_count(parser, &mut count) + }); + assert_eq!(2, count); + + unsafe { + mp4parse_free(parser); + } +} + +#[test] +fn minimal_mp4_get_track_info() { + let parser = parse_minimal_mp4(); + + let mut info = Mp4parseTrackInfo { + track_type: Mp4parseTrackType::Video, + ..Default::default() + }; + assert_eq!(Mp4parseStatus::Ok, unsafe { + mp4parse_get_track_info(parser, 0, &mut info) + }); + assert_eq!(info.track_type, Mp4parseTrackType::Video); + assert_eq!(info.track_id, 1); + assert_eq!(info.duration, 40000); + assert_eq!(info.media_time, 0); + + assert_eq!(Mp4parseStatus::Ok, unsafe { + mp4parse_get_track_info(parser, 1, &mut info) + }); + assert_eq!(info.track_type, Mp4parseTrackType::Audio); + assert_eq!(info.track_id, 2); + assert_eq!(info.duration, 61333); + assert_eq!(info.media_time, 21333); + + unsafe { + mp4parse_free(parser); + } +} + +#[test] +fn minimal_mp4_get_track_video_info() { + let parser = parse_minimal_mp4(); + + let mut video = Mp4parseTrackVideoInfo::default(); + assert_eq!(Mp4parseStatus::Ok, unsafe { + mp4parse_get_track_video_info(parser, 0, &mut video) + }); + assert_eq!(video.display_width, 320); + assert_eq!(video.display_height, 240); + assert_eq!(video.sample_info_count, 1); + + unsafe { + assert_eq!((*video.sample_info).image_width, 320); + assert_eq!((*video.sample_info).image_height, 240); + } + + unsafe { + mp4parse_free(parser); + } +} + +#[test] +fn minimal_mp4_get_track_audio_info() { + let parser = parse_minimal_mp4(); + + let mut audio = Mp4parseTrackAudioInfo::default(); + assert_eq!(Mp4parseStatus::Ok, unsafe { + mp4parse_get_track_audio_info(parser, 1, &mut audio) + }); + assert_eq!(audio.sample_info_count, 1); + + unsafe { + assert_eq!((*audio.sample_info).channels, 1); + assert_eq!((*audio.sample_info).bit_depth, 16); + assert_eq!((*audio.sample_info).sample_rate, 48000); + } + + unsafe { + mp4parse_free(parser); + } +} + +#[test] +fn minimal_mp4_get_track_info_invalid_track_number() { + let parser = parse_minimal_mp4(); + + let mut info = Mp4parseTrackInfo { + track_type: Mp4parseTrackType::Video, + ..Default::default() + }; + assert_eq!(Mp4parseStatus::BadArg, unsafe { + mp4parse_get_track_info(parser, 3, &mut info) + }); + assert_eq!(info.track_type, Mp4parseTrackType::Video); + assert_eq!(info.track_id, 0); + assert_eq!(info.duration, 0); + assert_eq!(info.media_time, 0); + + let mut video = Mp4parseTrackVideoInfo::default(); + assert_eq!(Mp4parseStatus::BadArg, unsafe { + mp4parse_get_track_video_info(parser, 3, &mut video) + }); + assert_eq!(video.display_width, 0); + assert_eq!(video.display_height, 0); + assert_eq!(video.sample_info_count, 0); + + let mut audio = Default::default(); + assert_eq!(Mp4parseStatus::BadArg, unsafe { + mp4parse_get_track_audio_info(parser, 3, &mut audio) + }); + assert_eq!(audio.sample_info_count, 0); + + unsafe { + mp4parse_free(parser); + } +} diff --git a/third_party/rust/mp4parse_capi/tests/test_chunk_out_of_range.rs b/third_party/rust/mp4parse_capi/tests/test_chunk_out_of_range.rs new file mode 100644 index 0000000000..b3a9543b40 --- /dev/null +++ b/third_party/rust/mp4parse_capi/tests/test_chunk_out_of_range.rs @@ -0,0 +1,45 @@ +extern crate mp4parse_capi; +use mp4parse_capi::*; +use std::io::Read; + +extern "C" fn buf_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize { + let input: &mut std::fs::File = unsafe { &mut *(userdata as *mut _) }; + let buf = unsafe { std::slice::from_raw_parts_mut(buf, size) }; + match input.read(buf) { + Ok(n) => n as isize, + Err(_) => -1, + } +} + +#[test] +fn parse_out_of_chunk_range() { + let mut file = std::fs::File::open("tests/chunk_out_of_range.mp4").expect("Unknown file"); + let io = Mp4parseIo { + read: Some(buf_read), + userdata: &mut file as *mut _ as *mut std::os::raw::c_void, + }; + + unsafe { + let mut parser = std::ptr::null_mut(); + let mut rv = mp4parse_new(&io, &mut parser); + assert_eq!(rv, Mp4parseStatus::Ok); + assert!(!parser.is_null()); + + let mut counts: u32 = 0; + rv = mp4parse_get_track_count(parser, &mut counts); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(counts, 1); + + // its first chunk is out of range. + // <SampleToChunkBox EntryCount="1"> + // <BoxInfo Size="28" Type="stsc"/> + // <FullBoxInfo Version="0" Flags="0x0"/> + // <SampleToChunkEntry FirstChunk="16777217" SamplesPerChunk="17" SampleDescriptionIndex="1"/> + // + let mut indice = Mp4parseByteData::default(); + let rv = mp4parse_get_indice_table(parser, 1, &mut indice); + assert_eq!(rv, Mp4parseStatus::Invalid); + + mp4parse_free(parser); + } +} diff --git a/third_party/rust/mp4parse_capi/tests/test_encryption.rs b/third_party/rust/mp4parse_capi/tests/test_encryption.rs new file mode 100644 index 0000000000..1a47e329fc --- /dev/null +++ b/third_party/rust/mp4parse_capi/tests/test_encryption.rs @@ -0,0 +1,296 @@ +extern crate mp4parse_capi; +use mp4parse_capi::*; +use std::io::Read; + +extern "C" fn buf_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize { + let input: &mut std::fs::File = unsafe { &mut *(userdata as *mut _) }; + let buf = unsafe { std::slice::from_raw_parts_mut(buf, size) }; + match input.read(buf) { + Ok(n) => n as isize, + Err(_) => -1, + } +} + +#[test] +fn parse_cenc() { + let mut file = std::fs::File::open("tests/short-cenc.mp4").expect("Unknown file"); + let io = Mp4parseIo { + read: Some(buf_read), + userdata: &mut file as *mut _ as *mut std::os::raw::c_void, + }; + + unsafe { + let mut parser = std::ptr::null_mut(); + let mut rv = mp4parse_new(&io, &mut parser); + assert_eq!(rv, Mp4parseStatus::Ok); + assert!(!parser.is_null()); + let mut counts: u32 = 0; + rv = mp4parse_get_track_count(parser, &mut counts); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(counts, 2); + + // Make sure we have a video track and it's at index 0 + let mut video_track_info = Mp4parseTrackInfo::default(); + rv = mp4parse_get_track_info(parser, 0, &mut video_track_info); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(video_track_info.track_type, Mp4parseTrackType::Video); + + // Make sure we have a audio track and it's at index 1 + let mut audio_track_info = Mp4parseTrackInfo::default(); + rv = mp4parse_get_track_info(parser, 1, &mut audio_track_info); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(audio_track_info.track_type, Mp4parseTrackType::Audio); + + // Verify video track and crypto information + let mut video = Mp4parseTrackVideoInfo::default(); + rv = mp4parse_get_track_video_info(parser, 0, &mut video); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(video.sample_info_count, 1); + assert_eq!((*video.sample_info).codec_type, Mp4parseCodec::Avc); + assert_eq!((*video.sample_info).image_width, 320); + assert_eq!((*video.sample_info).image_height, 240); + let protected_data = &(*video.sample_info).protected_data; + assert_eq!( + protected_data.original_format, + OptionalFourCc::Some(*b"avc1") + ); + assert_eq!( + protected_data.scheme_type, + Mp4ParseEncryptionSchemeType::Cenc + ); + assert_eq!(protected_data.is_encrypted, 0x01); + assert_eq!(protected_data.iv_size, 16); + assert_eq!(protected_data.kid.length, 16); + let expected_kid = [ + 0x7e, 0x57, 0x1d, 0x01, 0x7e, 0x57, 0x1d, 0x01, 0x7e, 0x57, 0x1d, 0x01, 0x7e, 0x57, + 0x1d, 0x01, + ]; + for (i, expected_byte) in expected_kid.iter().enumerate() { + assert_eq!(&(*protected_data.kid.data.add(i)), expected_byte); + } + assert_eq!(protected_data.crypt_byte_block, 0); + assert_eq!(protected_data.skip_byte_block, 0); + assert_eq!(protected_data.constant_iv.length, 0); + + // Verify audio track and crypto information + let mut audio = Mp4parseTrackAudioInfo::default(); + rv = mp4parse_get_track_audio_info(parser, 1, &mut audio); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(audio.sample_info_count, 1); + assert_eq!((*audio.sample_info).codec_type, Mp4parseCodec::Aac); + assert_eq!((*audio.sample_info).channels, 2); + assert_eq!((*audio.sample_info).bit_depth, 16); + assert_eq!((*audio.sample_info).sample_rate, 44100); + let protected_data = &(*audio.sample_info).protected_data; + assert_eq!( + protected_data.original_format, + OptionalFourCc::Some(*b"mp4a") + ); + assert_eq!(protected_data.is_encrypted, 0x01); + assert_eq!(protected_data.iv_size, 16); + assert_eq!(protected_data.kid.length, 16); + let expected_kid = [ + 0x7e, 0x57, 0x1d, 0x02, 0x7e, 0x57, 0x1d, 0x02, 0x7e, 0x57, 0x1d, 0x02, 0x7e, 0x57, + 0x1d, 0x02, + ]; + for (i, expected_byte) in expected_kid.iter().enumerate() { + assert_eq!(&(*protected_data.kid.data.add(i)), expected_byte); + } + assert_eq!(protected_data.crypt_byte_block, 0); + assert_eq!(protected_data.skip_byte_block, 0); + assert_eq!(protected_data.constant_iv.length, 0); + } +} + +#[test] +fn parse_cbcs() { + let mut file = std::fs::File::open("tests/bipbop_cbcs_video_init.mp4").expect("Unknown file"); + let io = Mp4parseIo { + read: Some(buf_read), + userdata: &mut file as *mut _ as *mut std::os::raw::c_void, + }; + + unsafe { + let mut parser = std::ptr::null_mut(); + let mut rv = mp4parse_new(&io, &mut parser); + assert_eq!(rv, Mp4parseStatus::Ok); + assert!(!parser.is_null()); + let mut counts: u32 = 0; + rv = mp4parse_get_track_count(parser, &mut counts); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(counts, 1); + + // Make sure we have a video track + let mut video_track_info = Mp4parseTrackInfo::default(); + rv = mp4parse_get_track_info(parser, 0, &mut video_track_info); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(video_track_info.track_type, Mp4parseTrackType::Video); + + // Verify video track and crypto information + let mut video = Mp4parseTrackVideoInfo::default(); + rv = mp4parse_get_track_video_info(parser, 0, &mut video); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(video.sample_info_count, 2); + assert_eq!((*video.sample_info).codec_type, Mp4parseCodec::Avc); + assert_eq!((*video.sample_info).image_width, 400); + assert_eq!((*video.sample_info).image_height, 300); + let protected_data = &(*video.sample_info).protected_data; + assert_eq!( + protected_data.original_format, + OptionalFourCc::Some(*b"avc1") + ); + assert_eq!( + protected_data.scheme_type, + Mp4ParseEncryptionSchemeType::Cbcs + ); + assert_eq!(protected_data.is_encrypted, 0x01); + assert_eq!(protected_data.iv_size, 0); + assert_eq!(protected_data.kid.length, 16); + let expected_kid = [ + 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, + 0x1d, 0x21, + ]; + for (i, expected_byte) in expected_kid.iter().enumerate() { + assert_eq!(&(*protected_data.kid.data.add(i)), expected_byte); + } + assert_eq!(protected_data.crypt_byte_block, 1); + assert_eq!(protected_data.skip_byte_block, 9); + assert_eq!(protected_data.constant_iv.length, 16); + let expected_iv = [ + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0x11, 0x22, 0x33, 0x44, + 0x55, 0x66, + ]; + for (i, expected_byte) in expected_iv.iter().enumerate() { + assert_eq!(&(*protected_data.constant_iv.data.add(i)), expected_byte); + } + } +} + +#[test] +fn parse_unencrypted() { + // Ensure the encryption related data is not populated for files without + // encryption metadata. + let mut file = std::fs::File::open("tests/opus_audioinit.mp4").expect("Unknown file"); + let io = Mp4parseIo { + read: Some(buf_read), + userdata: &mut file as *mut _ as *mut std::os::raw::c_void, + }; + + unsafe { + let mut parser = std::ptr::null_mut(); + let mut rv = mp4parse_new(&io, &mut parser); + assert_eq!(rv, Mp4parseStatus::Ok); + assert!(!parser.is_null()); + + let mut counts: u32 = 0; + rv = mp4parse_get_track_count(parser, &mut counts); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(counts, 1); + + let mut track_info = Mp4parseTrackInfo::default(); + rv = mp4parse_get_track_info(parser, 0, &mut track_info); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(track_info.track_type, Mp4parseTrackType::Audio); + + let mut audio = Mp4parseTrackAudioInfo::default(); + rv = mp4parse_get_track_audio_info(parser, 0, &mut audio); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(audio.sample_info_count, 1); + let protected_data = &(*audio.sample_info).protected_data; + assert_eq!(protected_data.original_format, OptionalFourCc::None); + assert_eq!( + protected_data.scheme_type, + Mp4ParseEncryptionSchemeType::None + ); + assert_eq!(protected_data.is_encrypted, 0x00); + assert_eq!(protected_data.iv_size, 0); + assert_eq!(protected_data.kid.length, 0); + assert_eq!(protected_data.crypt_byte_block, 0); + assert_eq!(protected_data.skip_byte_block, 0); + assert_eq!(protected_data.constant_iv.length, 0); + } +} + +#[test] +fn parse_encrypted_av1() { + // For reference, this file was created from the av1.mp4 in mozilla's media tests using + // shaka-packager. The following command was used to produce the file: + // ``` + // packager-win.exe in=av1.mp4,stream=video,output=av1-clearkey-cbcs-video.mp4 + // --protection_scheme cbcs --enable_raw_key_encryption + // --keys label=:key_id=00112233445566778899AABBCCDDEEFF:key=00112233445566778899AABBCCDDEEFF + // --iv 11223344556677889900112233445566 + // ``` + let mut file = std::fs::File::open("tests/av1-clearkey-cbcs-video.mp4").expect("Unknown file"); + let io = Mp4parseIo { + read: Some(buf_read), + userdata: &mut file as *mut _ as *mut std::os::raw::c_void, + }; + + unsafe { + let mut parser = std::ptr::null_mut(); + let mut rv = mp4parse_new(&io, &mut parser); + assert_eq!(rv, Mp4parseStatus::Ok); + assert!(!parser.is_null()); + let mut counts: u32 = 0; + rv = mp4parse_get_track_count(parser, &mut counts); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(counts, 1); + + // Make sure we have a video track + let mut video_track_info = Mp4parseTrackInfo::default(); + rv = mp4parse_get_track_info(parser, 0, &mut video_track_info); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(video_track_info.track_type, Mp4parseTrackType::Video); + + // Verify video track and crypto information + let mut video = Mp4parseTrackVideoInfo::default(); + rv = mp4parse_get_track_video_info(parser, 0, &mut video); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(video.sample_info_count, 2); + assert_eq!((*video.sample_info).codec_type, Mp4parseCodec::Av1); + assert_eq!((*video.sample_info).image_width, 160); + assert_eq!((*video.sample_info).image_height, 90); + + // Check that extra data binary blob. + let expected_extra_data = [ + 0x81, 0x00, 0x0c, 0x00, 0x0a, 0x0a, 0x00, 0x00, 0x00, 0x03, 0xb4, 0xfd, 0x97, 0xff, + 0xe6, 0x01, + ]; + let extra_data = &(*video.sample_info).extra_data; + assert_eq!(extra_data.length, 16); + for (i, expected_byte) in expected_extra_data.iter().enumerate() { + assert_eq!(&(*extra_data.data.add(i)), expected_byte); + } + + let protected_data = &(*video.sample_info).protected_data; + assert_eq!( + protected_data.original_format, + OptionalFourCc::Some(*b"av01") + ); + assert_eq!( + protected_data.scheme_type, + Mp4ParseEncryptionSchemeType::Cbcs + ); + assert_eq!(protected_data.is_encrypted, 0x01); + assert_eq!(protected_data.iv_size, 0); + assert_eq!(protected_data.kid.length, 16); + let expected_kid = [ + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, + 0xee, 0xff, + ]; + for (i, expected_byte) in expected_kid.iter().enumerate() { + assert_eq!(&(*protected_data.kid.data.add(i)), expected_byte); + } + assert_eq!(protected_data.crypt_byte_block, 1); + assert_eq!(protected_data.skip_byte_block, 9); + assert_eq!(protected_data.constant_iv.length, 16); + let expected_iv = [ + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0x11, 0x22, 0x33, 0x44, + 0x55, 0x66, + ]; + for (i, expected_byte) in expected_iv.iter().enumerate() { + assert_eq!(&(*protected_data.constant_iv.data.add(i)), expected_byte); + } + } +} diff --git a/third_party/rust/mp4parse_capi/tests/test_fragment.rs b/third_party/rust/mp4parse_capi/tests/test_fragment.rs new file mode 100644 index 0000000000..d6ebe1f177 --- /dev/null +++ b/third_party/rust/mp4parse_capi/tests/test_fragment.rs @@ -0,0 +1,117 @@ +extern crate mp4parse_capi; +use mp4parse_capi::*; +use std::io::Read; + +extern "C" fn buf_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize { + let input: &mut std::fs::File = unsafe { &mut *(userdata as *mut _) }; + let buf = unsafe { std::slice::from_raw_parts_mut(buf, size) }; + match input.read(buf) { + Ok(n) => n as isize, + Err(_) => -1, + } +} + +#[test] +fn parse_fragment() { + let mut file = std::fs::File::open("tests/bipbop_audioinit.mp4").expect("Unknown file"); + let io = Mp4parseIo { + read: Some(buf_read), + userdata: &mut file as *mut _ as *mut std::os::raw::c_void, + }; + + unsafe { + let mut parser = std::ptr::null_mut(); + let mut rv = mp4parse_new(&io, &mut parser); + assert_eq!(rv, Mp4parseStatus::Ok); + assert!(!parser.is_null()); + + let mut counts: u32 = 0; + rv = mp4parse_get_track_count(parser, &mut counts); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(counts, 1); + + let mut track_info = Mp4parseTrackInfo::default(); + rv = mp4parse_get_track_info(parser, 0, &mut track_info); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(track_info.track_type, Mp4parseTrackType::Audio); + assert_eq!(track_info.track_id, 1); + assert_eq!(track_info.duration, 0); + assert_eq!(track_info.media_time, 0); + + let mut audio = Default::default(); + rv = mp4parse_get_track_audio_info(parser, 0, &mut audio); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(audio.sample_info_count, 1); + + assert_eq!((*audio.sample_info).codec_type, Mp4parseCodec::Aac); + assert_eq!((*audio.sample_info).channels, 2); + assert_eq!((*audio.sample_info).bit_depth, 16); + assert_eq!((*audio.sample_info).sample_rate, 22050); + assert_eq!((*audio.sample_info).extra_data.length, 27); + assert_eq!((*audio.sample_info).codec_specific_config.length, 2); + + let mut is_fragmented_file: u8 = 0; + rv = mp4parse_is_fragmented(parser, track_info.track_id, &mut is_fragmented_file); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(is_fragmented_file, 1); + + let mut fragment_info = Mp4parseFragmentInfo::default(); + rv = mp4parse_get_fragment_info(parser, &mut fragment_info); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(fragment_info.fragment_duration, 10_032_000); + + mp4parse_free(parser); + } +} + +#[test] +fn parse_opus_fragment() { + let mut file = std::fs::File::open("tests/opus_audioinit.mp4").expect("Unknown file"); + let io = Mp4parseIo { + read: Some(buf_read), + userdata: &mut file as *mut _ as *mut std::os::raw::c_void, + }; + + unsafe { + let mut parser = std::ptr::null_mut(); + let mut rv = mp4parse_new(&io, &mut parser); + assert_eq!(rv, Mp4parseStatus::Ok); + assert!(!parser.is_null()); + + let mut counts: u32 = 0; + rv = mp4parse_get_track_count(parser, &mut counts); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(counts, 1); + + let mut track_info = Mp4parseTrackInfo::default(); + rv = mp4parse_get_track_info(parser, 0, &mut track_info); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(track_info.track_type, Mp4parseTrackType::Audio); + assert_eq!(track_info.track_id, 1); + assert_eq!(track_info.duration, 0); + assert_eq!(track_info.media_time, 0); + + let mut audio = Default::default(); + rv = mp4parse_get_track_audio_info(parser, 0, &mut audio); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(audio.sample_info_count, 1); + + assert_eq!((*audio.sample_info).codec_type, Mp4parseCodec::Opus); + assert_eq!((*audio.sample_info).channels, 1); + assert_eq!((*audio.sample_info).bit_depth, 16); + assert_eq!((*audio.sample_info).sample_rate, 48000); + assert_eq!((*audio.sample_info).extra_data.length, 0); + assert_eq!((*audio.sample_info).codec_specific_config.length, 19); + + let mut is_fragmented_file: u8 = 0; + rv = mp4parse_is_fragmented(parser, track_info.track_id, &mut is_fragmented_file); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(is_fragmented_file, 1); + + let mut fragment_info = Mp4parseFragmentInfo::default(); + rv = mp4parse_get_fragment_info(parser, &mut fragment_info); + assert_eq!(rv, Mp4parseStatus::Ok); + + mp4parse_free(parser); + } +} diff --git a/third_party/rust/mp4parse_capi/tests/test_rotation.rs b/third_party/rust/mp4parse_capi/tests/test_rotation.rs new file mode 100644 index 0000000000..918c9fbeae --- /dev/null +++ b/third_party/rust/mp4parse_capi/tests/test_rotation.rs @@ -0,0 +1,41 @@ +extern crate mp4parse_capi; +use mp4parse_capi::*; +use std::io::Read; + +extern "C" fn buf_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize { + let input: &mut std::fs::File = unsafe { &mut *(userdata as *mut _) }; + let buf = unsafe { std::slice::from_raw_parts_mut(buf, size) }; + match input.read(buf) { + Ok(n) => n as isize, + Err(_) => -1, + } +} + +#[test] +fn parse_rotation() { + let mut file = std::fs::File::open("tests/video_rotation_90.mp4").expect("Unknown file"); + let io = Mp4parseIo { + read: Some(buf_read), + userdata: &mut file as *mut _ as *mut std::os::raw::c_void, + }; + + unsafe { + let mut parser = std::ptr::null_mut(); + let mut rv = mp4parse_new(&io, &mut parser); + assert_eq!(rv, Mp4parseStatus::Ok); + assert!(!parser.is_null()); + + let mut counts: u32 = 0; + rv = mp4parse_get_track_count(parser, &mut counts); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(counts, 1); + + let mut video = Mp4parseTrackVideoInfo::default(); + + let rv = mp4parse_get_track_video_info(parser, 0, &mut video); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(video.rotation, 90); + + mp4parse_free(parser); + } +} diff --git a/third_party/rust/mp4parse_capi/tests/test_sample_table.rs b/third_party/rust/mp4parse_capi/tests/test_sample_table.rs new file mode 100644 index 0000000000..ad030a94c5 --- /dev/null +++ b/third_party/rust/mp4parse_capi/tests/test_sample_table.rs @@ -0,0 +1,280 @@ +extern crate mp4parse; +extern crate mp4parse_capi; +use mp4parse::unstable::Indice; +use mp4parse_capi::*; +use std::io::Read; + +extern "C" fn buf_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize { + let input: &mut std::fs::File = unsafe { &mut *(userdata as *mut _) }; + let buf = unsafe { std::slice::from_raw_parts_mut(buf, size) }; + match input.read(buf) { + Ok(n) => n as isize, + Err(_) => -1, + } +} + +#[test] +fn parse_sample_table() { + let mut file = + std::fs::File::open("tests/bipbop_nonfragment_header.mp4").expect("Unknown file"); + let io = Mp4parseIo { + read: Some(buf_read), + userdata: &mut file as *mut _ as *mut std::os::raw::c_void, + }; + + unsafe { + let mut parser = std::ptr::null_mut(); + let mut rv = mp4parse_new(&io, &mut parser); + assert_eq!(rv, Mp4parseStatus::Ok); + assert!(!parser.is_null()); + + let mut counts: u32 = 0; + rv = mp4parse_get_track_count(parser, &mut counts); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(counts, 2); + + let mut track_info = Mp4parseTrackInfo::default(); + rv = mp4parse_get_track_info(parser, 1, &mut track_info); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(track_info.track_type, Mp4parseTrackType::Audio); + + // Check audio smaple table + let mut is_fragmented_file: u8 = 0; + rv = mp4parse_is_fragmented(parser, track_info.track_id, &mut is_fragmented_file); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(is_fragmented_file, 0); + + let mut indice = Mp4parseByteData::default(); + rv = mp4parse_get_indice_table(parser, track_info.track_id, &mut indice); + assert_eq!(rv, Mp4parseStatus::Ok); + + // Compare the value from stagefright. + let audio_indice_0 = Indice { + start_offset: 27_046.into(), + end_offset: 27_052.into(), + start_composition: 0.into(), + end_composition: 46_439.into(), + start_decode: 0.into(), + sync: true, + }; + let audio_indice_215 = Indice { + start_offset: 283_550.into(), + end_offset: 283_556.into(), + start_composition: 9_984_580.into(), + end_composition: 10_031_020.into(), + start_decode: 9_984_580.into(), + sync: true, + }; + assert_eq!(indice.length, 216); + assert_eq!(*indice.indices.offset(0), audio_indice_0); + assert_eq!(*indice.indices.offset(215), audio_indice_215); + + // Check video smaple table + rv = mp4parse_get_track_info(parser, 0, &mut track_info); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(track_info.track_type, Mp4parseTrackType::Video); + + let mut is_fragmented_file: u8 = 0; + rv = mp4parse_is_fragmented(parser, track_info.track_id, &mut is_fragmented_file); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(is_fragmented_file, 0); + + let mut indice = Mp4parseByteData::default(); + rv = mp4parse_get_indice_table(parser, track_info.track_id, &mut indice); + assert_eq!(rv, Mp4parseStatus::Ok); + + // Compare the last few data from stagefright. + let video_indice_291 = Indice { + start_offset: 280_226.into(), + end_offset: 280_855.into(), + start_composition: 9_838_333.into(), + end_composition: 9_871_677.into(), + start_decode: 9_710_000.into(), + sync: false, + }; + let video_indice_292 = Indice { + start_offset: 280_855.into(), + end_offset: 281_297.into(), + start_composition: 9_805_011.into(), + end_composition: 9_838_333.into(), + start_decode: 9_710_011.into(), + sync: false, + }; + // TODO: start_composition time in stagefright is 9905000, but it is 9904999 in parser, it + // could be rounding error. + //let video_indice_293 = Indice { start_offset: 281_297, end_offset: 281_919, start_composition: 9_905_000, end_composition: 9_938_344, start_decode: 9_776_666, sync: false }; + //let video_indice_294 = Indice { start_offset: 281_919, end_offset: 282_391, start_composition: 9_871_677, end_composition: 9_905_000, start_decode: 9_776_677, sync: false }; + let video_indice_295 = Indice { + start_offset: 282_391.into(), + end_offset: 283_032.into(), + start_composition: 9_971_666.into(), + end_composition: 9_971_677.into(), + start_decode: 9_843_333.into(), + sync: false, + }; + let video_indice_296 = Indice { + start_offset: 283_092.into(), + end_offset: 283_526.into(), + start_composition: 9_938_344.into(), + end_composition: 9_971_666.into(), + start_decode: 9_843_344.into(), + sync: false, + }; + + assert_eq!(indice.length, 297); + assert_eq!(*indice.indices.offset(291), video_indice_291); + assert_eq!(*indice.indices.offset(292), video_indice_292); + //assert_eq!(*indice.indices.offset(293), video_indice_293); + //assert_eq!(*indice.indices.offset(294), video_indice_294); + assert_eq!(*indice.indices.offset(295), video_indice_295); + assert_eq!(*indice.indices.offset(296), video_indice_296); + + mp4parse_free(parser); + } +} + +#[test] +fn parse_sample_table_with_elst() { + let mut file = std::fs::File::open("tests/short-cenc.mp4").expect("Unknown file"); + let io = Mp4parseIo { + read: Some(buf_read), + userdata: &mut file as *mut _ as *mut std::os::raw::c_void, + }; + + unsafe { + let mut parser = std::ptr::null_mut(); + let mut rv = mp4parse_new(&io, &mut parser); + assert_eq!(rv, Mp4parseStatus::Ok); + assert!(!parser.is_null()); + + let mut counts: u32 = 0; + rv = mp4parse_get_track_count(parser, &mut counts); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(counts, 2); + + let mut track_info = Mp4parseTrackInfo::default(); + rv = mp4parse_get_track_info(parser, 1, &mut track_info); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(track_info.track_type, Mp4parseTrackType::Audio); + + // Check audio sample table + let mut is_fragmented_file: u8 = std::u8::MAX; + rv = mp4parse_is_fragmented(parser, track_info.track_id, &mut is_fragmented_file); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(is_fragmented_file, 0); + + let mut indice = Mp4parseByteData::default(); + rv = mp4parse_get_indice_table(parser, track_info.track_id, &mut indice); + assert_eq!(rv, Mp4parseStatus::Ok); + + // Compare the value from stagefright. + // Due to 'elst', the start_composition and end_composition are negative + // at first two samples. + let audio_indice_0 = Indice { + start_offset: 6992.into(), + end_offset: 7363.into(), + start_composition: (-36281).into(), + end_composition: (-13062).into(), + start_decode: 0.into(), + sync: true, + }; + let audio_indice_1 = Indice { + start_offset: 7363.into(), + end_offset: 7735.into(), + start_composition: (-13062).into(), + end_composition: 10158.into(), + start_decode: 23219.into(), + sync: true, + }; + let audio_indice_2 = Indice { + start_offset: 7735.into(), + end_offset: 8106.into(), + start_composition: 10158.into(), + end_composition: 33378.into(), + start_decode: 46439.into(), + sync: true, + }; + assert_eq!(indice.length, 21); + assert_eq!(*indice.indices.offset(0), audio_indice_0); + assert_eq!(*indice.indices.offset(1), audio_indice_1); + assert_eq!(*indice.indices.offset(2), audio_indice_2); + + mp4parse_free(parser); + } +} + +#[test] +fn parse_sample_table_with_negative_ctts() { + let mut file = std::fs::File::open("tests/white.mp4").expect("Unknown file"); + let io = Mp4parseIo { + read: Some(buf_read), + userdata: &mut file as *mut _ as *mut std::os::raw::c_void, + }; + + unsafe { + let mut parser = std::ptr::null_mut(); + let mut rv = mp4parse_new(&io, &mut parser); + assert_eq!(rv, Mp4parseStatus::Ok); + assert!(!parser.is_null()); + + let mut counts: u32 = 0; + rv = mp4parse_get_track_count(parser, &mut counts); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(counts, 1); + + let mut track_info = Mp4parseTrackInfo::default(); + rv = mp4parse_get_track_info(parser, 0, &mut track_info); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(track_info.track_type, Mp4parseTrackType::Video); + + let mut is_fragmented_file: u8 = std::u8::MAX; + rv = mp4parse_is_fragmented(parser, track_info.track_id, &mut is_fragmented_file); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(is_fragmented_file, 0); + + let mut indice = Mp4parseByteData::default(); + rv = mp4parse_get_indice_table(parser, track_info.track_id, &mut indice); + assert_eq!(rv, Mp4parseStatus::Ok); + + // There are negative value in 'ctts' table. + let video_indice_0 = Indice { + start_offset: 48.into(), + end_offset: 890.into(), + start_composition: 0.into(), + end_composition: 33_333.into(), + start_decode: 0.into(), + sync: true, + }; + let video_indice_1 = Indice { + start_offset: 890.into(), + end_offset: 913.into(), + start_composition: 133_333.into(), + end_composition: 166_666.into(), + start_decode: 33_333.into(), + sync: false, + }; + let video_indice_2 = Indice { + start_offset: 913.into(), + end_offset: 934.into(), + start_composition: 66_666.into(), + end_composition: 100_000.into(), + start_decode: 66_666.into(), + sync: false, + }; + let video_indice_3 = Indice { + start_offset: 934.into(), + end_offset: 955.into(), + start_composition: 33_333.into(), + end_composition: 66_666.into(), + start_decode: 100_000.into(), + sync: false, + }; + assert_eq!(indice.length, 300); + assert_eq!(*indice.indices.offset(0), video_indice_0); + assert_eq!(*indice.indices.offset(1), video_indice_1); + assert_eq!(*indice.indices.offset(2), video_indice_2); + assert_eq!(*indice.indices.offset(3), video_indice_3); + + mp4parse_free(parser); + } +} diff --git a/third_party/rust/mp4parse_capi/tests/test_workaround_stsc.rs b/third_party/rust/mp4parse_capi/tests/test_workaround_stsc.rs new file mode 100644 index 0000000000..99c92a7fef --- /dev/null +++ b/third_party/rust/mp4parse_capi/tests/test_workaround_stsc.rs @@ -0,0 +1,46 @@ +extern crate mp4parse_capi; +use mp4parse_capi::*; +use std::io::Read; + +extern "C" fn buf_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize { + let input: &mut std::fs::File = unsafe { &mut *(userdata as *mut _) }; + let buf = unsafe { std::slice::from_raw_parts_mut(buf, size) }; + match input.read(buf) { + Ok(n) => n as isize, + Err(_) => -1, + } +} + +#[test] +fn parse_invalid_stsc_table() { + let mut file = std::fs::File::open("tests/zero_empty_stsc.mp4").expect("Unknown file"); + let io = Mp4parseIo { + read: Some(buf_read), + userdata: &mut file as *mut _ as *mut std::os::raw::c_void, + }; + + unsafe { + let mut parser = std::ptr::null_mut(); + let rv = mp4parse_new(&io, &mut parser); + + assert_eq!(rv, Mp4parseStatus::Ok); + assert!(!parser.is_null()); + + let mut counts: u32 = 0; + let rv = mp4parse_get_track_count(parser, &mut counts); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(counts, 2); + + let mut indice_video = Mp4parseByteData::default(); + let rv = mp4parse_get_indice_table(parser, 1, &mut indice_video); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(indice_video.length, 1040); + + let mut indice_audio = Mp4parseByteData::default(); + let rv = mp4parse_get_indice_table(parser, 2, &mut indice_audio); + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(indice_audio.length, 1952); + + mp4parse_free(parser); + } +} |