diff options
Diffstat (limited to '')
19 files changed, 3472 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..b140363588 --- /dev/null +++ b/third_party/rust/mp4parse_capi/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"6b2b4576bf1bca3cf6b019f297a84189a3dd595353c451a4af38246ec14bcbef","LICENSE":"fab3dd6bdab226f1c08630b1dd917e11fcb4ec5e1e020e2c16f83a0a13863e85","README.md":"f776ed4bbb7b58a5684402a9c5c28dfe1fa02b6b184139b2c2c49384cc1e3723","cbindgen.toml":"62066cd34285ab9e7f1cc5db8950a51e9e080f5a85bd55ad43d7022e4eae2758","examples/dump.rs":"2a3cdebc5ed6f0f6b640e6722cd13fc7f4534774eb057b369a791c2eddb8132d","src/lib.rs":"4761968b061a78ea849ce3e9fdf3009dbddd4c6b0026a765f0e959b30233017a","tests/loop_1.avif":"bf8777d103e023a0da94f16dbe89b7081d13fe8b50e6418c69381a23561a5a84","tests/loop_2.avif":"be5f53417f8432342c0e0cbace60e33a791f2e98a03a44c4a9f5af3ec0bf6906","tests/loop_ceiled_4.avif":"136c36b7d2f5839c9107e56b6e3ce327d8b97bea291756f0ce377e6011716712","tests/loop_forever.avif":"aa425b4da99646d3b71d63f6b1741b60a2331ee2c9ab3bcf2fbc05b0cc832565","tests/no_edts.avif":"8940e97673d7d67c283a0f8f52fdbc45a9b74d5fb6c430507d406b70e4bbfe5f","tests/test_avis.rs":"d480b104ab2dfde7a25afd6705532caf7988aea21fc955dcf2f86fc8a5e85151","tests/test_chunk_out_of_range.rs":"4039d0db0ee5973787e4ca14cea510fd958ae5d21856a79240a5e7b826caa18d","tests/test_encryption.rs":"f62131a36b0516caf9e2c48f8aea060d300b0f5c8a32bc54d31cbc97aa25b4e6","tests/test_fragment.rs":"d3f805cc2107481ee9a989818af3addbb3ea1faf7422ea7f4416591d03031318","tests/test_rotation.rs":"23fa4898eca2e17255bc1ba2f538707a6554fb4644bb75f80548ae56a7cd2d44","tests/test_sample_table.rs":"53ed6a5e8db463ad8dc0300116f470c2aadd39896e6ba4cabcd01c6b9a7b5c59","tests/test_workaround_stsc.rs":"1d17a394f55e1524c30888bfe1e57e2b0457444b79c23eb91b02d2edf859c9ad"},"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..192097dbed --- /dev/null +++ b/third_party/rust/mp4parse_capi/Cargo.toml @@ -0,0 +1,54 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2018" +name = "mp4parse_capi" +version = "0.17.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>", +] +exclude = ["*.mp4"] +description = "Parser for ISO base media file format (mp4)" +documentation = "https://docs.rs/mp4parse_capi/" +readme = "README.md" +license = "MPL-2.0" +repository = "https://github.com/mozilla/mp4parse-rust" + +[dependencies] +byteorder = "1.2.1" +log = "0.4" +num-traits = "0.2.14" + +[dependencies.fallible_collections] +version = "0.4" +features = ["std_io"] + +[dependencies.mp4parse] +version = "0.17.0" +path = "../mp4parse" +features = ["unstable-api"] + +[dev-dependencies] +env_logger = "0.9" + +[features] +3gpp = ["mp4parse/3gpp"] +meta-xml = ["mp4parse/meta-xml"] +missing-pixi-permitted = ["mp4parse/missing-pixi-permitted"] +mp4v = ["mp4parse/mp4v"] + +[badges.travis-ci] +repository = "https://github.com/mozilla/mp4parse-rust" 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..86cf9acdb5 --- /dev/null +++ b/third_party/rust/mp4parse_capi/examples/dump.rs @@ -0,0 +1,200 @@ +use log::info; +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: {rv:?}, '-v' for more info"); + 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 + | Mp4parseTrackType::Picture + | Mp4parseTrackType::AuxiliaryVideo => { + 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..5b362f303f --- /dev/null +++ b/third_party/rust/mp4parse_capi/src/lib.rs @@ -0,0 +1,1863 @@ +//! C API for mp4parse module. +//! +//! Parses ISO Base Media Format aka video/mp4 streams. +//! +//! # Examples +//! +//! ```rust +//! 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/. + +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, CheckedInteger, Indice}; +use mp4parse::AV1ConfigBox; +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::Track; +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, Eq, Debug, Default)] +pub enum Mp4parseTrackType { + #[default] + Video = 0, + Picture = 1, + AuxiliaryVideo = 2, + Audio = 3, + Metadata = 4, +} + +#[allow(non_camel_case_types, clippy::upper_case_acronyms)] +#[repr(C)] +#[derive(PartialEq, Eq, Debug, Default)] +pub enum Mp4parseCodec { + #[default] + 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, +} + +#[repr(C)] +#[derive(PartialEq, Eq, Debug, Default)] +pub enum Mp4ParseEncryptionSchemeType { + #[default] + 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. +} + +#[repr(C)] +#[derive(Default, Debug)] +pub struct Mp4parseTrackInfo { + pub track_type: Mp4parseTrackType, + pub track_id: u32, + pub duration: u64, + pub media_time: CheckedInteger<i64>, + pub time_scale: u32, +} + +#[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, Eq)] +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, // in ticks + pub time_scale: 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, Default)] +pub enum Mp4parseAvifLoopMode { + #[default] + NoEdits, + LoopByCount, + LoopInfinitely, +} + +#[repr(C)] +#[derive(Debug)] +pub struct Mp4parseAvifInfo { + pub premultiplied_alpha: bool, + pub major_brand: [u8; 4], + pub unsupported_features_bitfield: u32, + /// 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, + + /// Whether there is a `pitm` reference to the color image present. + pub has_primary_item: bool, + /// Bit depth for the item referenced by `pitm`, or 0 if values are inconsistent. + pub primary_item_bit_depth: u8, + /// Whether there is an `auxl` reference to the `pitm`-accompanying + /// alpha image present. + pub has_alpha_item: bool, + /// Bit depth for the alpha item used by the `pitm`, or 0 if values are inconsistent. + pub alpha_item_bit_depth: u8, + + /// Whether there is a sequence. Can be true with no primary image. + pub has_sequence: bool, + /// Indicates whether the EditListBox requests that the image be looped. + pub loop_mode: Mp4parseAvifLoopMode, + /// Number of times to loop the animation during playback. + /// + /// The duration of the animation specified in `elst` must be looped to fill the + /// duration of the color track. If the resulting loop count is not an integer, + /// then it will be ceiled to play past and fill the entire track's duration. + pub loop_count: u64, + /// The color track's ID, which must be valid if has_sequence is true. + pub color_track_id: u32, + pub color_track_bit_depth: u8, + /// The track ID of the alpha track, will be 0 if no alpha track is present. + pub alpha_track_id: u32, + pub alpha_track_bit_depth: u8, +} + +#[repr(C)] +#[derive(Debug)] +pub struct Mp4parseAvifImage { + pub primary_image: Mp4parseByteData, + /// If no alpha item exists, members' `.length` will be 0 and `.data` will be null + pub alpha_image: Mp4parseByteData, +} + +/// 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 + } +} + +#[derive(Default)] +pub struct Mp4parseAvifParser { + context: AvifContext, + sample_table: TryHashMap<u32, TryVec<Indice>>, +} + +impl Mp4parseAvifParser { + fn context(&self) -> &AvifContext { + &self.context + } +} + +impl ContextParser for Mp4parseAvifParser { + type Context = AvifContext; + + 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_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::Picture => Mp4parseTrackType::Picture, + TrackType::AuxiliaryVideo => Mp4parseTrackType::AuxiliaryVideo, + TrackType::Audio => Mp4parseTrackType::Audio, + TrackType::Metadata => Mp4parseTrackType::Metadata, + TrackType::Unknown => return Mp4parseStatus::Unsupported, + }; + + let track = &context.tracks[track_index]; + + if let (Some(timescale), Some(_)) = (track.timescale, context.timescale) { + info.time_scale = timescale.0 as u32; + let media_time: CheckedInteger<u64> = track + .media_time + .map_or(0.into(), |media_time| media_time.0.into()); + + let empty_duration: CheckedInteger<u64> = track + .empty_duration + .map_or(0.into(), |empty_duration| empty_duration.0.into()); + + info.media_time = match media_time - empty_duration { + Some(difference) => difference, + None => return Mp4parseStatus::Invalid, + }; + + match track.duration { + Some(duration) => info.duration = duration.0, + None => { + // 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 struct containing meta information read by previous +/// `mp4parse_avif_new()` call. +/// +/// `color_track_id`and `alpha_track_id` will be 0 if has_sequence is false. +/// `alpha_track_id` will be 0 if no alpha aux track is present. +/// +/// # Safety +/// +/// This function is unsafe because it dereferences both the parser and +/// avif_info raw pointers passed into it. Callers should ensure the parser +/// pointer points to a valid `Mp4parseAvifParser`, and that the avif_info +/// pointer points to a valid `Mp4parseAvifInfo`. +#[no_mangle] +pub unsafe extern "C" fn mp4parse_avif_get_info( + parser: *const Mp4parseAvifParser, + avif_info: *mut Mp4parseAvifInfo, +) -> Mp4parseStatus { + if parser.is_null() || avif_info.is_null() { + return Mp4parseStatus::BadArg; + } + + if let Ok(info) = mp4parse_avif_get_info_safe((*parser).context()) { + *avif_info = info; + Mp4parseStatus::Ok + } else { + Mp4parseStatus::Invalid + } +} + +fn mp4parse_avif_get_info_safe(context: &AvifContext) -> mp4parse::Result<Mp4parseAvifInfo> { + let info = Mp4parseAvifInfo { + premultiplied_alpha: context.premultiplied_alpha, + major_brand: context.major_brand.value, + unsupported_features_bitfield: context.unsupported_features.into_bitfield(), + 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()?, + + has_primary_item: context.primary_item_is_present(), + primary_item_bit_depth: 0, + has_alpha_item: context.alpha_item_is_present(), + alpha_item_bit_depth: 0, + + has_sequence: false, + loop_mode: Mp4parseAvifLoopMode::NoEdits, + loop_count: 0, + color_track_id: 0, + color_track_bit_depth: 0, + alpha_track_id: 0, + alpha_track_bit_depth: 0, + }; + + fn get_bit_depth(data: &[u8]) -> u8 { + if !data.is_empty() && data.iter().all(|v| *v == data[0]) { + data[0] + } else { + 0 + } + } + let primary_item_bit_depth = + get_bit_depth(context.primary_item_bits_per_channel().unwrap_or(Ok(&[]))?); + let alpha_item_bit_depth = + get_bit_depth(context.alpha_item_bits_per_channel().unwrap_or(Ok(&[]))?); + + if let Some(sequence) = &context.sequence { + // Tracks must have track_id and samples + fn get_track<T>(tracks: &TryVec<Track>, pred: T) -> Option<&Track> + where + T: Fn(&Track) -> bool, + { + tracks.iter().find(|track| { + if track.track_id.is_none() { + return false; + } + match &track.stsc { + Some(stsc) => { + if stsc.samples.is_empty() { + return false; + } + if !pred(track) { + return false; + } + stsc.samples.iter().any(|chunk| chunk.samples_per_chunk > 0) + } + _ => false, + } + }) + } + + // Color track will be the first track found + let color_track = match get_track(&sequence.tracks, |_| true) { + Some(v) => v, + _ => return Ok(info), + }; + + // Alpha track will be the first track found with auxl.aux_for_track_id set to color_track's id + let alpha_track = get_track(&sequence.tracks, |track| match &track.tref { + Some(tref) => tref.has_auxl_reference(color_track.track_id.unwrap()), + _ => false, + }); + + fn get_av1c(track: &Track) -> Option<&AV1ConfigBox> { + if let Some(stsd) = &track.stsd { + for entry in &stsd.descriptions { + if let SampleEntry::Video(video_entry) = entry { + if let VideoCodecSpecific::AV1Config(av1c) = &video_entry.codec_specific { + return Some(av1c); + } + } + } + } + + None + } + + let color_track_id = color_track.track_id.unwrap(); + let color_track_bit_depth = match get_av1c(color_track) { + Some(av1c) => av1c.bit_depth, + _ => return Ok(info), + }; + + let (alpha_track_id, alpha_track_bit_depth) = match alpha_track { + Some(track) => ( + track.track_id.unwrap(), + match get_av1c(track) { + Some(av1c) => av1c.bit_depth, + _ => return Ok(info), + }, + ), + _ => (0, 0), + }; + + let (loop_mode, loop_count) = match color_track.tkhd.as_ref().map(|tkhd| tkhd.duration) { + Some(movie_duration) if movie_duration == std::u64::MAX => { + (Mp4parseAvifLoopMode::LoopInfinitely, 0) + } + Some(movie_duration) => match color_track.looped { + Some(true) => match color_track.edited_duration.map(|v| v.0) { + Some(segment_duration) => { + match movie_duration.checked_div(segment_duration).and_then(|n| { + match movie_duration.checked_rem(segment_duration) { + Some(0) => Some(n.saturating_sub(1)), + Some(_) => Some(n), + None => None, + } + }) { + Some(n) => (Mp4parseAvifLoopMode::LoopByCount, n), + None => (Mp4parseAvifLoopMode::LoopInfinitely, 0), + } + } + None => (Mp4parseAvifLoopMode::NoEdits, 0), + }, + Some(false) => (Mp4parseAvifLoopMode::LoopByCount, 0), + None => (Mp4parseAvifLoopMode::NoEdits, 0), + }, + None => (Mp4parseAvifLoopMode::LoopInfinitely, 0), + }; + + return Ok(Mp4parseAvifInfo { + primary_item_bit_depth, + alpha_item_bit_depth, + has_sequence: true, + loop_mode, + loop_count, + color_track_id, + color_track_bit_depth, + alpha_track_id, + alpha_track_bit_depth, + ..info + }); + } + + Ok(info) +} + +/// 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(); + Ok(Mp4parseAvifImage { + primary_image: Mp4parseByteData::with_data( + context.primary_item_coded_data().unwrap_or(&[]), + ), + alpha_image: Mp4parseByteData::with_data(context.alpha_item_coded_data().unwrap_or(&[])), + }) +} + +/// 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( + &(*parser).context, + &mut (*parser).sample_table, + track_id, + &mut *indices, + ) + .into() +} + +/// Fill the supplied `Mp4parseByteData` with index information from `track`. +/// +/// # Safety +/// +/// This function is unsafe because it dereferences both the parser and +/// indices raw pointers passed to it. Callers should ensure the parser +/// points to a valid `Mp4parseAvifParser` and indices points to a valid +/// `Mp4parseByteData`. +#[no_mangle] +pub unsafe extern "C" fn mp4parse_avif_get_indice_table( + parser: *mut Mp4parseAvifParser, + track_id: u32, + indices: *mut Mp4parseByteData, + timescale: *mut u64, +) -> Mp4parseStatus { + if parser.is_null() { + return Mp4parseStatus::BadArg; + } + + if indices.is_null() { + return Mp4parseStatus::BadArg; + } + + if timescale.is_null() { + return Mp4parseStatus::BadArg; + } + + // Initialize fields to default values to ensure all fields are always valid. + *indices = Default::default(); + + if let Some(sequence) = &(*parser).context.sequence { + // Use the top level timescale, and the track timescale if present. + let mut found_timescale = false; + if let Some(context_timescale) = sequence.timescale { + *timescale = context_timescale.0; + found_timescale = true; + } + let maybe_track_timescale = match sequence + .tracks + .iter() + .find(|track| track.track_id == Some(track_id)) + { + Some(track) => track.timescale, + _ => None, + }; + if let Some(track_timescale) = maybe_track_timescale { + found_timescale = true; + *timescale = track_timescale.0; + } + if !found_timescale { + return Mp4parseStatus::Invalid; + } + return get_indice_table( + sequence, + &mut (*parser).sample_table, + track_id, + &mut *indices, + ) + .into(); + } + + Mp4parseStatus::BadArg +} + +fn get_indice_table( + context: &MediaContext, + sample_table_cache: &mut TryHashMap<u32, TryVec<Indice>>, + track_id: u32, + indices: &mut Mp4parseByteData, +) -> Result<(), Mp4parseStatus> { + 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) = sample_table_cache.get(&track_id) { + indices.set_indices(v); + return Ok(()); + } + + let media_time = match &track.media_time { + &Some(t) => i64::try_from(t.0).ok().map(Into::into), + _ => None, + }; + + let empty_duration: Option<CheckedInteger<_>> = match &track.empty_duration { + &Some(e) => i64::try_from(e.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); + sample_table_cache.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 = time.0; + info.time_scale = scale.0; + return Mp4parseStatus::Ok; + }; + Mp4parseStatus::Invalid +} + +/// 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(stsc), Some(stco), Some(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, 512); + 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, 2944); + assert_eq!(info.media_time, 1024); + + 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); + } +} + +#[test] +fn parse_no_timescale() { + let mut file = std::fs::File::open("tests/no_timescale.mp4").expect("Unknown file"); + let io = Mp4parseIo { + read: Some(valid_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()); + + // The file has a video track, but the track has a timescale of 0, so. + let mut track_info = Mp4parseTrackInfo::default(); + rv = mp4parse_get_track_info(parser, 0, &mut track_info); + assert_eq!(rv, Mp4parseStatus::Invalid); + }; +} diff --git a/third_party/rust/mp4parse_capi/tests/loop_1.avif b/third_party/rust/mp4parse_capi/tests/loop_1.avif Binary files differnew file mode 100644 index 0000000000..0402755eff --- /dev/null +++ b/third_party/rust/mp4parse_capi/tests/loop_1.avif diff --git a/third_party/rust/mp4parse_capi/tests/loop_2.avif b/third_party/rust/mp4parse_capi/tests/loop_2.avif Binary files differnew file mode 100644 index 0000000000..4ed7426588 --- /dev/null +++ b/third_party/rust/mp4parse_capi/tests/loop_2.avif diff --git a/third_party/rust/mp4parse_capi/tests/loop_ceiled_4.avif b/third_party/rust/mp4parse_capi/tests/loop_ceiled_4.avif Binary files differnew file mode 100644 index 0000000000..1274547dff --- /dev/null +++ b/third_party/rust/mp4parse_capi/tests/loop_ceiled_4.avif diff --git a/third_party/rust/mp4parse_capi/tests/loop_forever.avif b/third_party/rust/mp4parse_capi/tests/loop_forever.avif Binary files differnew file mode 100644 index 0000000000..b24b4f29d3 --- /dev/null +++ b/third_party/rust/mp4parse_capi/tests/loop_forever.avif diff --git a/third_party/rust/mp4parse_capi/tests/no_edts.avif b/third_party/rust/mp4parse_capi/tests/no_edts.avif Binary files differnew file mode 100644 index 0000000000..9dc21fb010 --- /dev/null +++ b/third_party/rust/mp4parse_capi/tests/no_edts.avif diff --git a/third_party/rust/mp4parse_capi/tests/test_avis.rs b/third_party/rust/mp4parse_capi/tests/test_avis.rs new file mode 100644 index 0000000000..a6e5a1c64e --- /dev/null +++ b/third_party/rust/mp4parse_capi/tests/test_avis.rs @@ -0,0 +1,113 @@ +use mp4parse_capi::*; +use num_traits::ToPrimitive; +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, + } +} + +unsafe fn parse_file_and_get_info(path: &str) -> (*mut Mp4parseAvifParser, Mp4parseAvifInfo) { + let mut file = std::fs::File::open(path).expect("Unknown file"); + let io = Mp4parseIo { + read: Some(buf_read), + userdata: &mut file as *mut _ as *mut std::os::raw::c_void, + }; + + let mut parser = std::ptr::null_mut(); + let mut rv = mp4parse_avif_new(&io, ParseStrictness::Normal, &mut parser); + assert_eq!(rv, Mp4parseStatus::Ok); + assert!(!parser.is_null()); + + let mut info = Mp4parseAvifInfo { + premultiplied_alpha: Default::default(), + major_brand: Default::default(), + unsupported_features_bitfield: Default::default(), + spatial_extents: std::ptr::null(), + nclx_colour_information: std::ptr::null(), + icc_colour_information: Default::default(), + image_rotation: mp4parse::ImageRotation::D0, + image_mirror: std::ptr::null(), + pixel_aspect_ratio: std::ptr::null(), + has_primary_item: Default::default(), + primary_item_bit_depth: Default::default(), + has_alpha_item: Default::default(), + alpha_item_bit_depth: Default::default(), + has_sequence: Default::default(), + loop_mode: Default::default(), + loop_count: Default::default(), + color_track_id: Default::default(), + color_track_bit_depth: Default::default(), + alpha_track_id: Default::default(), + alpha_track_bit_depth: Default::default(), + }; + rv = mp4parse_avif_get_info(parser, &mut info); + assert_eq!(rv, Mp4parseStatus::Ok); + (parser, info) +} + +fn check_loop_count(path: &str, expected_loop_count: i64) { + let (parser, info) = unsafe { parse_file_and_get_info(path) }; + match info.loop_mode { + Mp4parseAvifLoopMode::NoEdits => assert_eq!(expected_loop_count, -1), + Mp4parseAvifLoopMode::LoopByCount => { + assert_eq!(info.loop_count.to_i64(), Some(expected_loop_count)) + } + Mp4parseAvifLoopMode::LoopInfinitely => assert_eq!(expected_loop_count, std::i64::MIN), + } + + unsafe { mp4parse_avif_free(parser) }; +} + +fn check_timescale(path: &str, expected_timescale: u64) { + let (parser, info) = unsafe { parse_file_and_get_info(path) }; + + let mut indices: Mp4parseByteData = Mp4parseByteData::default(); + let mut timescale: u64 = 0; + let rv = unsafe { + mp4parse_avif_get_indice_table(parser, info.color_track_id, &mut indices, &mut timescale) + }; + + assert_eq!(rv, Mp4parseStatus::Ok); + assert_eq!(timescale, expected_timescale); + + unsafe { mp4parse_avif_free(parser) }; +} + +#[test] +fn loop_once() { + check_loop_count("tests/loop_1.avif", 1); +} + +#[test] +fn loop_twice() { + check_loop_count("tests/loop_2.avif", 2); +} + +#[test] +fn loop_four_times_due_to_ceiling() { + check_loop_count("tests/loop_ceiled_4.avif", 4); +} + +#[test] +fn loop_forever() { + check_loop_count("tests/loop_forever.avif", std::i64::MIN); +} + +#[test] +fn no_edts() { + check_loop_count("tests/no_edts.avif", -1); +} + +#[test] +fn check_timescales() { + check_timescale("tests/loop_1.avif", 2); + check_timescale("tests/loop_2.avif", 2); + check_timescale("tests/loop_ceiled_4.avif", 2); + check_timescale("tests/loop_forever.avif", 2); + check_timescale("tests/no_edts.avif", 16384); +} 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..26c2f506e1 --- /dev/null +++ b/third_party/rust/mp4parse_capi/tests/test_chunk_out_of_range.rs @@ -0,0 +1,44 @@ +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..03f5963044 --- /dev/null +++ b/third_party/rust/mp4parse_capi/tests/test_encryption.rs @@ -0,0 +1,295 @@ +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..38bc569fa3 --- /dev/null +++ b/third_party/rust/mp4parse_capi/tests/test_fragment.rs @@ -0,0 +1,119 @@ +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); + assert_eq!(track_info.time_scale, 22050); + + 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); + assert_eq!(fragment_info.time_scale, 1000); + + 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); + assert_eq!(track_info.time_scale, 48000); + + 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..2ea47a5d52 --- /dev/null +++ b/third_party/rust/mp4parse_capi/tests/test_rotation.rs @@ -0,0 +1,40 @@ +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..820eed64f3 --- /dev/null +++ b/third_party/rust/mp4parse_capi/tests/test_sample_table.rs @@ -0,0 +1,278 @@ +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: 1024.into(), + start_decode: 0.into(), + sync: true, + }; + let audio_indice_215 = Indice { + start_offset: 283_550.into(), + end_offset: 283_556.into(), + start_composition: 220160.into(), + end_composition: 221184.into(), + start_decode: 220160.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: 876995.into(), + end_composition: 879996.into(), + start_decode: 873900.into(), + sync: false, + }; + let video_indice_292 = Indice { + start_offset: 280_855.into(), + end_offset: 281_297.into(), + start_composition: 873996.into(), + end_composition: 876995.into(), + start_decode: 873901.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: 888995.into(), + end_composition: 888996.into(), + start_decode: 885900.into(), + sync: false, + }; + let video_indice_296 = Indice { + start_offset: 283_092.into(), + end_offset: 283_526.into(), + start_composition: 885996.into(), + end_composition: 888995.into(), + start_decode: 885901.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: (-1600).into(), + end_composition: (-576).into(), + start_decode: 0.into(), + sync: true, + }; + let audio_indice_1 = Indice { + start_offset: 7363.into(), + end_offset: 7735.into(), + start_composition: (-576).into(), + end_composition: 448.into(), + start_decode: 1024.into(), + sync: true, + }; + let audio_indice_2 = Indice { + start_offset: 7735.into(), + end_offset: 8106.into(), + start_composition: 448.into(), + end_composition: 1472.into(), + start_decode: 2048.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: 100.into(), + start_decode: 0.into(), + sync: true, + }; + let video_indice_1 = Indice { + start_offset: 890.into(), + end_offset: 913.into(), + start_composition: 400.into(), + end_composition: 500.into(), + start_decode: 100.into(), + sync: false, + }; + let video_indice_2 = Indice { + start_offset: 913.into(), + end_offset: 934.into(), + start_composition: 200.into(), + end_composition: 300.into(), + start_decode: 200.into(), + sync: false, + }; + let video_indice_3 = Indice { + start_offset: 934.into(), + end_offset: 955.into(), + start_composition: 100.into(), + end_composition: 200.into(), + start_decode: 300.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..197024086f --- /dev/null +++ b/third_party/rust/mp4parse_capi/tests/test_workaround_stsc.rs @@ -0,0 +1,45 @@ +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); + } +} |