summaryrefslogtreecommitdiffstats
path: root/third_party/rust/mp4parse_capi
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--third_party/rust/mp4parse_capi/.cargo-checksum.json1
-rw-r--r--third_party/rust/mp4parse_capi/Cargo.toml54
-rw-r--r--third_party/rust/mp4parse_capi/LICENSE373
-rw-r--r--third_party/rust/mp4parse_capi/README.md2
-rw-r--r--third_party/rust/mp4parse_capi/cbindgen.toml45
-rw-r--r--third_party/rust/mp4parse_capi/examples/dump.rs200
-rw-r--r--third_party/rust/mp4parse_capi/src/lib.rs1863
-rw-r--r--third_party/rust/mp4parse_capi/tests/loop_1.avifbin0 -> 2967 bytes
-rw-r--r--third_party/rust/mp4parse_capi/tests/loop_2.avifbin0 -> 2967 bytes
-rw-r--r--third_party/rust/mp4parse_capi/tests/loop_ceiled_4.avifbin0 -> 2967 bytes
-rw-r--r--third_party/rust/mp4parse_capi/tests/loop_forever.avifbin0 -> 2967 bytes
-rw-r--r--third_party/rust/mp4parse_capi/tests/no_edts.avifbin0 -> 2052 bytes
-rw-r--r--third_party/rust/mp4parse_capi/tests/test_avis.rs113
-rw-r--r--third_party/rust/mp4parse_capi/tests/test_chunk_out_of_range.rs44
-rw-r--r--third_party/rust/mp4parse_capi/tests/test_encryption.rs295
-rw-r--r--third_party/rust/mp4parse_capi/tests/test_fragment.rs119
-rw-r--r--third_party/rust/mp4parse_capi/tests/test_rotation.rs40
-rw-r--r--third_party/rust/mp4parse_capi/tests/test_sample_table.rs278
-rw-r--r--third_party/rust/mp4parse_capi/tests/test_workaround_stsc.rs45
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
new file mode 100644
index 0000000000..0402755eff
--- /dev/null
+++ b/third_party/rust/mp4parse_capi/tests/loop_1.avif
Binary files differ
diff --git a/third_party/rust/mp4parse_capi/tests/loop_2.avif b/third_party/rust/mp4parse_capi/tests/loop_2.avif
new file mode 100644
index 0000000000..4ed7426588
--- /dev/null
+++ b/third_party/rust/mp4parse_capi/tests/loop_2.avif
Binary files differ
diff --git a/third_party/rust/mp4parse_capi/tests/loop_ceiled_4.avif b/third_party/rust/mp4parse_capi/tests/loop_ceiled_4.avif
new file mode 100644
index 0000000000..1274547dff
--- /dev/null
+++ b/third_party/rust/mp4parse_capi/tests/loop_ceiled_4.avif
Binary files differ
diff --git a/third_party/rust/mp4parse_capi/tests/loop_forever.avif b/third_party/rust/mp4parse_capi/tests/loop_forever.avif
new file mode 100644
index 0000000000..b24b4f29d3
--- /dev/null
+++ b/third_party/rust/mp4parse_capi/tests/loop_forever.avif
Binary files differ
diff --git a/third_party/rust/mp4parse_capi/tests/no_edts.avif b/third_party/rust/mp4parse_capi/tests/no_edts.avif
new file mode 100644
index 0000000000..9dc21fb010
--- /dev/null
+++ b/third_party/rust/mp4parse_capi/tests/no_edts.avif
Binary files differ
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);
+ }
+}