├── .github ├── dependabot.yml └── workflows │ └── rust.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── examples └── dump.rs ├── fuzz ├── .gitignore ├── Cargo.toml ├── README.md ├── corpus │ └── fuzz_target_1 │ │ ├── 0aa521571d99b4023dbc61c8624c062b1f3a6042 │ │ ├── 0ced5060f4cbe412db2c394281cf8785874d4b7d │ │ ├── 1590afc907dea4c92bc44badb2c8f88b1861340e │ │ ├── 1d2dadcba40b7ada145b16857ecfcded2fe41335 │ │ ├── 1d9dd9f4b2beda74aa763e8c792caac9aa57ab6b │ │ ├── 20cdf75ce6a62d557edd580754dd086c8dd48058 │ │ ├── 32042666d133a2b64c14162da58e27a822b9b787 │ │ ├── 3479bee60245aced929b12ff341b0daac800c7fb │ │ ├── 43836167a46c2f149a5cb8bd5c33841e67609f39 │ │ ├── 43ae443d6f9aef9555a0a63d2d1c9d295e46e2bb │ │ ├── 4870e38f27868af8f104fcb31d9446786e9eafd1 │ │ ├── 48ccc38f8cd94bd5af16e9e6c600956114dd580e │ │ ├── 497fa2da8ee07b794357d5274ded95d4eeae750c │ │ ├── 5bd4666da6e3266917416e3b2031598a73b1173b │ │ ├── 5c10b5b2cd673a0616d529aa5234b12ee7153808 │ │ ├── 5da6bab016c97ac556ffc8db09a9f5505a9b8310 │ │ ├── 5dc05b7f8ee89e3c752c3c70e1c3f44f40dd2d09 │ │ ├── 7ae86108103e0ca5d5a2091ab8f472e814fbaec7 │ │ ├── 8070fa57018ba272ac2db36d467c69cf6d4bdd35 │ │ ├── 81a595147b6783b893dff844f5c9b95e911f46bc │ │ ├── 9294824674f99b7c76832df16304da6997af89f4 │ │ ├── 9c8c13d22da29c9455b521bd739db89d74191097 │ │ ├── a912cb1ece77f8dafda3f853e9d084823227e561 │ │ ├── aac93091e275cd5b1a516262597704002cfa9601 │ │ ├── b667ccdfa6d59c903325a1d907d3548aadedb5e5 │ │ ├── c89aab735713aeed2c391de00c1070eb20f21704 │ │ ├── cb626690bb8c902353a7031335756f1c29849e68 │ │ ├── cf767430bf829edfd337d6630ef96abd46a81932 │ │ ├── d5ccc769b1ade407b2a13400a3427134a82b7d65 │ │ ├── d64fd0275b90afb1afc0e0d6af34d500b1e154d5 │ │ ├── e20bdce6743cf658a9286a701b2ab01241223a76 │ │ ├── ec2345f4ed376e6b9e7fc87215a34ded6c9ca981 │ │ ├── efd4333cb80d6beeba760ef00339ff8ed04d1e72 │ │ ├── f029e1b091b1a4c904b0590c4a3d87c5131b0f90 │ │ └── f0c6f5313108637c1d3043dbec7dec340ef0f268 └── fuzz_targets │ └── fuzz_target_1.rs ├── release.toml └── src └── lib.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | 9 | - package-ecosystem: github-actions 10 | directory: / 11 | schedule: 12 | interval: weekly -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v4 12 | - name: Check formatting 13 | run: cargo fmt --check 14 | - name: Build 15 | run: cargo build --all-targets --verbose 16 | - name: Run tests 17 | run: cargo test --all-targets --verbose 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /resource/ 3 | **/*.rs.bk 4 | **/.*.swp 5 | Cargo.lock 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## Unreleased 4 | 5 | ### Changed 6 | - Switched to Rust 2021 edition 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "adts-reader" 3 | version = "0.3.0" 4 | authors = ["David Holroyd "] 5 | description = "Rust reader for Audio Data Transport Stream (ADTS) frames" 6 | categories = [ "multimedia::audio", "parser-implementations" ] 7 | keywords = [ "ADTS", "MPEG", "AAC", "ISO-13818-7" ] 8 | repository = "https://github.com/dholroyd/adts-reader" 9 | readme = "README.md" 10 | license = "MIT/Apache-2.0" 11 | edition = "2021" 12 | 13 | [dependencies] 14 | 15 | [dev-dependencies] 16 | bitstream-io = "2.2.0" 17 | hexdump = "0.1.0" 18 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 David Holroyd 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # adts-reader 2 | A Rust parser for the [Audio Data Transport Stream](https://wiki.multimedia.cx/index.php/ADTS) 3 | framing format used to carry encoded AAC audio data. 4 | 5 | [![crates.io version](https://img.shields.io/crates/v/adts-reader.svg)](https://crates.io/crates/adts-reader) 6 | [![Documentation](https://docs.rs/adts-reader/badge.svg)](https://docs.rs/adts-reader) 7 | 8 | 👉 **NB** This is not an AAC decoder, nor is it able to parse the syntax of the AAC bitstream within the ADTS payload. 9 | 10 | Calling code should, 11 | - Provide an implementation of `AdtsConsumer` which will recieve callbacks as ADTS frame payloads are found 12 | - Pass buffers containing ADTS data into the `AdtsParser::push()` method 13 | 14 | ## Incremental parsing 15 | The byte slice passed to `push()` need not end exactly at the boundry of an ADTS frame. Partial ADTS data 16 | remaining at the end of the given slice will be buffered internally in the parser, and the continuation of the ADTS 17 | data must be provided in the subsequent call to `push()`. This construction is intended to make it convinient to pass 18 | payloads of the individual _MPEG Transport Stream_ packets in which ADTS data is commonly embedded, without having to 19 | pay the cost of reassembling entire _PES packets_. 20 | 21 | ## Encoder configuration 22 | ADTS frames include header data indicating the AAC encoder configuration, which will be made available to the calling 23 | code through the provided implementation of `AdtsConsumer::new_config()`. 24 | 25 | Configuration data is provided at stream start, and to simplify calling code the parser will only call 26 | `AdtsConsumer::new_config()` again if and when the audio configuration is found to change. 27 | 28 | ## Supported ADTS syntax 29 | 30 | * Fixed header fields 31 | * [x] `mpeg_version` 32 | * [x] `protection` 33 | * [x] `audio_object_type` 34 | * [x] `sampling_frequency` 35 | * [x] `private_bit` 36 | * [x] `channel_configuration` 37 | * [x] `originality` 38 | * [x] `home` 39 | * Variable header fields data 40 | * [ ] `copyright_identifier` / `copyright_number` - deriving these values is not supported, since I've not seen any 41 | example bitstreams that use them 42 | * [x] `buffer_fullness` 43 | * [x] `number_of_blocks` 44 | * [ ] `crc` - not currently available (also, the CRC does not apply to all payload bytes; _cyclic reduncancy check_ 45 | requires AAC bitstream parsing) 46 | * AAC payload data 47 | * A `&[u8]` byte slice containing a complete ADTS frame payload (which might be composed of one or more AAC blocks, 48 | per _number_of_blocks_) 49 | -------------------------------------------------------------------------------- /examples/dump.rs: -------------------------------------------------------------------------------- 1 | extern crate adts_reader; 2 | extern crate hexdump; 3 | 4 | use adts_reader::*; 5 | use std::env; 6 | use std::fs::File; 7 | use std::io; 8 | 9 | struct DumpAdtsConsumer { 10 | frame_count: usize, 11 | } 12 | impl AdtsConsumer for DumpAdtsConsumer { 13 | fn new_config( 14 | &mut self, 15 | mpeg_version: MpegVersion, 16 | protection: ProtectionIndicator, 17 | aot: AudioObjectType, 18 | freq: SamplingFrequency, 19 | private_bit: u8, 20 | channels: ChannelConfiguration, 21 | originality: Originality, 22 | home: u8, 23 | ) { 24 | println!("New ADTS configuration found"); 25 | println!( 26 | "{:?} {:?} {:?} {:?} private_bit={} {:?} {:?} home={}", 27 | mpeg_version, protection, aot, freq, private_bit, channels, originality, home 28 | ); 29 | } 30 | fn payload(&mut self, buffer_fullness: u16, number_of_blocks: u8, buf: &[u8]) { 31 | println!( 32 | "ADTS Frame buffer_fullness={} blocks={}", 33 | buffer_fullness, number_of_blocks 34 | ); 35 | hexdump::hexdump(buf); 36 | self.frame_count += 1; 37 | } 38 | fn error(&mut self, err: AdtsParseError) { 39 | println!("Error: {:?}", err); 40 | } 41 | } 42 | 43 | fn run(mut r: R) -> io::Result<()> 44 | where 45 | R: io::Read, 46 | R: Sized, 47 | { 48 | const LEN: usize = 1024 * 1024; 49 | let mut buf = [0u8; LEN]; 50 | let mut byte_count = 0; 51 | let mut parser = AdtsParser::new(DumpAdtsConsumer { frame_count: 0 }); 52 | loop { 53 | match r.read(&mut buf[..])? { 54 | 0 => break, 55 | n => { 56 | let target = &mut buf[0..n]; 57 | parser.push(target); 58 | byte_count += n; 59 | } 60 | }; 61 | } 62 | println!( 63 | "Processed {} bytes, {} ADTS frames", 64 | byte_count, parser.consumer.frame_count 65 | ); 66 | Ok(()) 67 | } 68 | 69 | fn main() { 70 | let mut args = env::args(); 71 | args.next(); 72 | let name = args.next().unwrap(); 73 | let f = File::open(&name).expect(&format!("file not found: {}", &name)); 74 | run(f).expect(&format!("error reading {}", &name)); 75 | } 76 | -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | target 3 | artifacts 4 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "adts-reader-fuzz" 3 | version = "0.0.1" 4 | authors = ["Automatically generated"] 5 | publish = false 6 | edition = "2021" 7 | 8 | [package.metadata] 9 | cargo-fuzz = true 10 | 11 | [dependencies] 12 | adts-reader = { path = ".." } 13 | libfuzzer-sys = "0.3" 14 | 15 | # Prevent this from interfering with workspaces 16 | [workspace] 17 | members = ["."] 18 | 19 | [[bin]] 20 | name = "fuzz_target_1" 21 | path = "fuzz_targets/fuzz_target_1.rs" 22 | -------------------------------------------------------------------------------- /fuzz/README.md: -------------------------------------------------------------------------------- 1 | # Fuzz testing `adts-reader` 2 | 3 | ## Setup 4 | 5 | If you don't already have [cargo-fuzz](https://rust-fuzz.github.io/book/introduction.html) installed. 6 | 7 | ``` 8 | cargo +nightly install cargo-fuzz 9 | ``` 10 | 11 | ## Testing 12 | 13 | To perform fuzz testing, 14 | 15 | ``` 16 | cargo +nightly fuzz run fuzz_target_1 17 | ``` 18 | 19 | (The fuzz test will keep running until it either finds a fault, or you kill the process.) -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_target_1/0aa521571d99b4023dbc61c8624c062b1f3a6042: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dholroyd/adts-reader/b61cfe7053e246d4b3c0e44c7d451311f97628f8/fuzz/corpus/fuzz_target_1/0aa521571d99b4023dbc61c8624c062b1f3a6042 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_target_1/0ced5060f4cbe412db2c394281cf8785874d4b7d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dholroyd/adts-reader/b61cfe7053e246d4b3c0e44c7d451311f97628f8/fuzz/corpus/fuzz_target_1/0ced5060f4cbe412db2c394281cf8785874d4b7d -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_target_1/1590afc907dea4c92bc44badb2c8f88b1861340e: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dholroyd/adts-reader/b61cfe7053e246d4b3c0e44c7d451311f97628f8/fuzz/corpus/fuzz_target_1/1590afc907dea4c92bc44badb2c8f88b1861340e -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_target_1/1d2dadcba40b7ada145b16857ecfcded2fe41335: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dholroyd/adts-reader/b61cfe7053e246d4b3c0e44c7d451311f97628f8/fuzz/corpus/fuzz_target_1/1d2dadcba40b7ada145b16857ecfcded2fe41335 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_target_1/1d9dd9f4b2beda74aa763e8c792caac9aa57ab6b: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dholroyd/adts-reader/b61cfe7053e246d4b3c0e44c7d451311f97628f8/fuzz/corpus/fuzz_target_1/1d9dd9f4b2beda74aa763e8c792caac9aa57ab6b -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_target_1/20cdf75ce6a62d557edd580754dd086c8dd48058: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dholroyd/adts-reader/b61cfe7053e246d4b3c0e44c7d451311f97628f8/fuzz/corpus/fuzz_target_1/20cdf75ce6a62d557edd580754dd086c8dd48058 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_target_1/32042666d133a2b64c14162da58e27a822b9b787: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dholroyd/adts-reader/b61cfe7053e246d4b3c0e44c7d451311f97628f8/fuzz/corpus/fuzz_target_1/32042666d133a2b64c14162da58e27a822b9b787 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_target_1/3479bee60245aced929b12ff341b0daac800c7fb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dholroyd/adts-reader/b61cfe7053e246d4b3c0e44c7d451311f97628f8/fuzz/corpus/fuzz_target_1/3479bee60245aced929b12ff341b0daac800c7fb -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_target_1/43836167a46c2f149a5cb8bd5c33841e67609f39: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dholroyd/adts-reader/b61cfe7053e246d4b3c0e44c7d451311f97628f8/fuzz/corpus/fuzz_target_1/43836167a46c2f149a5cb8bd5c33841e67609f39 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_target_1/43ae443d6f9aef9555a0a63d2d1c9d295e46e2bb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dholroyd/adts-reader/b61cfe7053e246d4b3c0e44c7d451311f97628f8/fuzz/corpus/fuzz_target_1/43ae443d6f9aef9555a0a63d2d1c9d295e46e2bb -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_target_1/4870e38f27868af8f104fcb31d9446786e9eafd1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dholroyd/adts-reader/b61cfe7053e246d4b3c0e44c7d451311f97628f8/fuzz/corpus/fuzz_target_1/4870e38f27868af8f104fcb31d9446786e9eafd1 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_target_1/48ccc38f8cd94bd5af16e9e6c600956114dd580e: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dholroyd/adts-reader/b61cfe7053e246d4b3c0e44c7d451311f97628f8/fuzz/corpus/fuzz_target_1/48ccc38f8cd94bd5af16e9e6c600956114dd580e -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_target_1/497fa2da8ee07b794357d5274ded95d4eeae750c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dholroyd/adts-reader/b61cfe7053e246d4b3c0e44c7d451311f97628f8/fuzz/corpus/fuzz_target_1/497fa2da8ee07b794357d5274ded95d4eeae750c -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_target_1/5bd4666da6e3266917416e3b2031598a73b1173b: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dholroyd/adts-reader/b61cfe7053e246d4b3c0e44c7d451311f97628f8/fuzz/corpus/fuzz_target_1/5bd4666da6e3266917416e3b2031598a73b1173b -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_target_1/5c10b5b2cd673a0616d529aa5234b12ee7153808: -------------------------------------------------------------------------------- 1 | , -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_target_1/5da6bab016c97ac556ffc8db09a9f5505a9b8310: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dholroyd/adts-reader/b61cfe7053e246d4b3c0e44c7d451311f97628f8/fuzz/corpus/fuzz_target_1/5da6bab016c97ac556ffc8db09a9f5505a9b8310 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_target_1/5dc05b7f8ee89e3c752c3c70e1c3f44f40dd2d09: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dholroyd/adts-reader/b61cfe7053e246d4b3c0e44c7d451311f97628f8/fuzz/corpus/fuzz_target_1/5dc05b7f8ee89e3c752c3c70e1c3f44f40dd2d09 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_target_1/7ae86108103e0ca5d5a2091ab8f472e814fbaec7: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dholroyd/adts-reader/b61cfe7053e246d4b3c0e44c7d451311f97628f8/fuzz/corpus/fuzz_target_1/7ae86108103e0ca5d5a2091ab8f472e814fbaec7 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_target_1/8070fa57018ba272ac2db36d467c69cf6d4bdd35: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dholroyd/adts-reader/b61cfe7053e246d4b3c0e44c7d451311f97628f8/fuzz/corpus/fuzz_target_1/8070fa57018ba272ac2db36d467c69cf6d4bdd35 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_target_1/81a595147b6783b893dff844f5c9b95e911f46bc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dholroyd/adts-reader/b61cfe7053e246d4b3c0e44c7d451311f97628f8/fuzz/corpus/fuzz_target_1/81a595147b6783b893dff844f5c9b95e911f46bc -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_target_1/9294824674f99b7c76832df16304da6997af89f4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dholroyd/adts-reader/b61cfe7053e246d4b3c0e44c7d451311f97628f8/fuzz/corpus/fuzz_target_1/9294824674f99b7c76832df16304da6997af89f4 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_target_1/9c8c13d22da29c9455b521bd739db89d74191097: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dholroyd/adts-reader/b61cfe7053e246d4b3c0e44c7d451311f97628f8/fuzz/corpus/fuzz_target_1/9c8c13d22da29c9455b521bd739db89d74191097 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_target_1/a912cb1ece77f8dafda3f853e9d084823227e561: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dholroyd/adts-reader/b61cfe7053e246d4b3c0e44c7d451311f97628f8/fuzz/corpus/fuzz_target_1/a912cb1ece77f8dafda3f853e9d084823227e561 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_target_1/aac93091e275cd5b1a516262597704002cfa9601: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dholroyd/adts-reader/b61cfe7053e246d4b3c0e44c7d451311f97628f8/fuzz/corpus/fuzz_target_1/aac93091e275cd5b1a516262597704002cfa9601 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_target_1/b667ccdfa6d59c903325a1d907d3548aadedb5e5: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_target_1/c89aab735713aeed2c391de00c1070eb20f21704: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dholroyd/adts-reader/b61cfe7053e246d4b3c0e44c7d451311f97628f8/fuzz/corpus/fuzz_target_1/c89aab735713aeed2c391de00c1070eb20f21704 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_target_1/cb626690bb8c902353a7031335756f1c29849e68: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dholroyd/adts-reader/b61cfe7053e246d4b3c0e44c7d451311f97628f8/fuzz/corpus/fuzz_target_1/cb626690bb8c902353a7031335756f1c29849e68 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_target_1/cf767430bf829edfd337d6630ef96abd46a81932: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dholroyd/adts-reader/b61cfe7053e246d4b3c0e44c7d451311f97628f8/fuzz/corpus/fuzz_target_1/cf767430bf829edfd337d6630ef96abd46a81932 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_target_1/d5ccc769b1ade407b2a13400a3427134a82b7d65: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dholroyd/adts-reader/b61cfe7053e246d4b3c0e44c7d451311f97628f8/fuzz/corpus/fuzz_target_1/d5ccc769b1ade407b2a13400a3427134a82b7d65 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_target_1/d64fd0275b90afb1afc0e0d6af34d500b1e154d5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dholroyd/adts-reader/b61cfe7053e246d4b3c0e44c7d451311f97628f8/fuzz/corpus/fuzz_target_1/d64fd0275b90afb1afc0e0d6af34d500b1e154d5 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_target_1/e20bdce6743cf658a9286a701b2ab01241223a76: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dholroyd/adts-reader/b61cfe7053e246d4b3c0e44c7d451311f97628f8/fuzz/corpus/fuzz_target_1/e20bdce6743cf658a9286a701b2ab01241223a76 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_target_1/ec2345f4ed376e6b9e7fc87215a34ded6c9ca981: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dholroyd/adts-reader/b61cfe7053e246d4b3c0e44c7d451311f97628f8/fuzz/corpus/fuzz_target_1/ec2345f4ed376e6b9e7fc87215a34ded6c9ca981 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_target_1/efd4333cb80d6beeba760ef00339ff8ed04d1e72: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dholroyd/adts-reader/b61cfe7053e246d4b3c0e44c7d451311f97628f8/fuzz/corpus/fuzz_target_1/efd4333cb80d6beeba760ef00339ff8ed04d1e72 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_target_1/f029e1b091b1a4c904b0590c4a3d87c5131b0f90: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dholroyd/adts-reader/b61cfe7053e246d4b3c0e44c7d451311f97628f8/fuzz/corpus/fuzz_target_1/f029e1b091b1a4c904b0590c4a3d87c5131b0f90 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_target_1/f0c6f5313108637c1d3043dbec7dec340ef0f268: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dholroyd/adts-reader/b61cfe7053e246d4b3c0e44c7d451311f97628f8/fuzz/corpus/fuzz_target_1/f0c6f5313108637c1d3043dbec7dec340ef0f268 -------------------------------------------------------------------------------- /fuzz/fuzz_targets/fuzz_target_1.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #[macro_use] 3 | extern crate libfuzzer_sys; 4 | extern crate adts_reader; 5 | 6 | use adts_reader::*; 7 | 8 | struct NullConsumer {} 9 | 10 | impl AdtsConsumer for NullConsumer { 11 | fn new_config( 12 | &mut self, 13 | mpeg_version: MpegVersion, 14 | protection: ProtectionIndicator, 15 | aot: AudioObjectType, 16 | freq: SamplingFrequency, 17 | private_bit: u8, 18 | channels: ChannelConfiguration, 19 | originality: Originality, 20 | home: u8, 21 | ) { 22 | } 23 | fn payload(&mut self, buffer_fullness: u16, number_of_blocks: u8, buf: &[u8]) {} 24 | fn error(&mut self, err: AdtsParseError) {} 25 | } 26 | 27 | fuzz_target!(|data: &[u8]| { 28 | let mut p = AdtsParser::new(NullConsumer {}); 29 | p.push(data); 30 | }); 31 | -------------------------------------------------------------------------------- /release.toml: -------------------------------------------------------------------------------- 1 | pre-release-replacements = [ 2 | {file="CHANGELOG.md", search="## Unreleased", replace="## Unreleased\n\n## {{version}} - {{date}}", exactly=1}, 3 | ] -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A Rust parser for the [Audio Data Transport Stream](https://wiki.multimedia.cx/index.php/ADTS) 2 | //! framing format often used to carry encoded AAC audio data. 3 | //! 4 | //! [`AdtsHeader`](struct.AdtsHeader.html) is the primary type provided by this crate. 5 | //! 6 | //! Given a buffer containing some number of ADTS frames, the first frame may be inspected by 7 | //! constructing a header instance with, 8 | //! 9 | //! ```rust 10 | //! use adts_reader::AdtsHeader; 11 | //! # let buf: Vec = vec!(0xff, 0xf0, 0, 0, 1, 0x20, 0, 0, 0); 12 | //! // let buf = ...; 13 | //! match AdtsHeader::from_bytes(&buf) { 14 | //! Ok(header) => println!("length (headers+payload) is {}", header.frame_length()), 15 | //! Err(e) => panic!("failed to read header: {:?}", e), 16 | //! } 17 | //! ``` 18 | //! 19 | //! # Unsupported 20 | //! 21 | //! - Resynchronising `AdtsParser` after encountering bitstream error (we could search for 22 | //! sync-word) 23 | //! - Copyright identifiers (I don't have any example bitstreams to try) 24 | //! - CRC handling (probably needs to be implemented as part of AAC bitstream parsing) 25 | 26 | #![forbid(unsafe_code)] 27 | #![deny(rust_2018_idioms, future_incompatible)] 28 | 29 | // TODO: might be better to implement AdtsParser as an iterator, rather then doing callbacks into a 30 | // trait implementation -- it looked hard to implement though! 31 | 32 | use std::fmt; 33 | 34 | #[derive(Debug)] 35 | pub enum AdtsHeaderError { 36 | /// Indicates that the given buffer did not start with the required sequence of 12 '1'-bits 37 | /// (`0xfff`). 38 | BadSyncWord(u16), 39 | NotEnoughData { 40 | expected: usize, 41 | actual: usize, 42 | }, 43 | /// The frame_length field stored in the ADTS header is invalid as it holds a value smaller 44 | /// than the size of the header fields 45 | BadFrameLength { 46 | minimum: usize, 47 | actual: usize, 48 | }, 49 | } 50 | 51 | /// Error indicating that not enough data was provided to `AdtsHeader` to be able to extract the 52 | /// whole ADTS payload following the header fields. 53 | #[derive(Debug, PartialEq)] 54 | pub struct PayloadError { 55 | pub expected: usize, 56 | pub actual: usize, 57 | } 58 | 59 | #[derive(Debug, PartialEq)] 60 | pub enum MpegVersion { 61 | Mpeg2, 62 | Mpeg4, 63 | } 64 | 65 | #[derive(Debug, PartialEq, Clone, Copy)] 66 | pub enum AudioObjectType { 67 | /// 'Main' profile 68 | AacMain, 69 | /// 'Low Complexity' profile 70 | AacLC, 71 | /// 'Scalable Sample Rate' profile 72 | AacSSR, 73 | /// 'Long Term Prediction' profile 74 | AacLTP, 75 | } 76 | 77 | #[derive(Debug, PartialEq)] 78 | pub enum ProtectionIndicator { 79 | CrcPresent, 80 | CrcAbsent, 81 | } 82 | 83 | #[derive(Debug, PartialEq, Clone, Copy)] 84 | pub enum SamplingFrequency { 85 | /// 96kHz 86 | Freq96000 = 0x0, 87 | /// 88.2kHz 88 | Freq88200 = 0x1, 89 | /// 64kHz 90 | Freq64000 = 0x2, 91 | /// 48kHz 92 | Freq48000 = 0x3, 93 | /// 44.1kHz 94 | Freq44100 = 0x4, 95 | /// 32kHz 96 | Freq32000 = 0x5, 97 | /// 24kHz 98 | Freq24000 = 0x6, 99 | /// 22.05kHz 100 | Freq22050 = 0x7, 101 | /// 16kHz 102 | Freq16000 = 0x8, 103 | /// 12kHz 104 | Freq12000 = 0x9, 105 | /// 11.025kHz 106 | Freq11025 = 0xa, 107 | /// 8kHz 108 | Freq8000 = 0xb, 109 | FreqReserved0xc = 0xc, 110 | FreqReserved0xd = 0xd, 111 | FreqReserved0xe = 0xe, 112 | FreqReserved0xf = 0xf, 113 | } 114 | 115 | impl From for SamplingFrequency { 116 | fn from(value: u8) -> SamplingFrequency { 117 | match value { 118 | 0x0 => SamplingFrequency::Freq96000, 119 | 0x1 => SamplingFrequency::Freq88200, 120 | 0x2 => SamplingFrequency::Freq64000, 121 | 0x3 => SamplingFrequency::Freq48000, 122 | 0x4 => SamplingFrequency::Freq44100, 123 | 0x5 => SamplingFrequency::Freq32000, 124 | 0x6 => SamplingFrequency::Freq24000, 125 | 0x7 => SamplingFrequency::Freq22050, 126 | 0x8 => SamplingFrequency::Freq16000, 127 | 0x9 => SamplingFrequency::Freq12000, 128 | 0xa => SamplingFrequency::Freq11025, 129 | 0xb => SamplingFrequency::Freq8000, 130 | 0xc => SamplingFrequency::FreqReserved0xc, 131 | 0xd => SamplingFrequency::FreqReserved0xd, 132 | 0xe => SamplingFrequency::FreqReserved0xe, 133 | 0xf => SamplingFrequency::FreqReserved0xf, 134 | _ => panic!( 135 | "invalid value {:#x} when parsing SamplingFrequency, expected a 4 bit value", 136 | value 137 | ), 138 | } 139 | } 140 | } 141 | 142 | impl SamplingFrequency { 143 | pub fn freq(&self) -> Option { 144 | match self { 145 | SamplingFrequency::Freq96000 => Some(96000), 146 | SamplingFrequency::Freq88200 => Some(88200), 147 | SamplingFrequency::Freq64000 => Some(64000), 148 | SamplingFrequency::Freq48000 => Some(48000), 149 | SamplingFrequency::Freq44100 => Some(44100), 150 | SamplingFrequency::Freq32000 => Some(32000), 151 | SamplingFrequency::Freq24000 => Some(24000), 152 | SamplingFrequency::Freq22050 => Some(22050), 153 | SamplingFrequency::Freq16000 => Some(16000), 154 | SamplingFrequency::Freq12000 => Some(12000), 155 | SamplingFrequency::Freq11025 => Some(11025), 156 | SamplingFrequency::Freq8000 => Some(8000), 157 | SamplingFrequency::FreqReserved0xc => None, 158 | SamplingFrequency::FreqReserved0xd => None, 159 | SamplingFrequency::FreqReserved0xe => None, 160 | SamplingFrequency::FreqReserved0xf => None, 161 | } 162 | } 163 | } 164 | 165 | #[derive(Debug, PartialEq, Clone, Copy)] 166 | pub enum ChannelConfiguration { 167 | ObjectTypeSpecificConfig = 0x0, 168 | Mono = 0x1, 169 | Stereo = 0x2, 170 | Three = 0x3, 171 | Four = 0x4, 172 | Five = 0x5, 173 | FiveOne = 0x6, 174 | SevenOne = 0x7, 175 | } 176 | impl From for ChannelConfiguration { 177 | fn from(value: u8) -> ChannelConfiguration { 178 | match value { 179 | 0x0 => ChannelConfiguration::ObjectTypeSpecificConfig, 180 | 0x1 => ChannelConfiguration::Mono, 181 | 0x2 => ChannelConfiguration::Stereo, 182 | 0x3 => ChannelConfiguration::Three, 183 | 0x4 => ChannelConfiguration::Four, 184 | 0x5 => ChannelConfiguration::Five, 185 | 0x6 => ChannelConfiguration::FiveOne, 186 | 0x7 => ChannelConfiguration::SevenOne, 187 | _ => panic!( 188 | "invalid value {:#x} when parsing ChannelConfiguration, expected a 3 bit value", 189 | value 190 | ), 191 | } 192 | } 193 | } 194 | 195 | #[derive(Debug, PartialEq)] 196 | pub enum Originality { 197 | Original, 198 | Copy, 199 | } 200 | 201 | #[derive(Debug, PartialEq)] 202 | pub enum CopyrightIdentificationStart { 203 | Start, 204 | Other, 205 | } 206 | 207 | /// Extract information for a single ADTS frame from the start of the given byte buffer . 208 | pub struct AdtsHeader<'buf> { 209 | buf: &'buf [u8], 210 | } 211 | impl<'buf> AdtsHeader<'buf> { 212 | /// Construct an instance by borrowing the given byte buffer. The given buffer may be longer 213 | /// then the ADTS frame, in which case the rest of the buffer is ignored. 214 | /// 215 | /// 216 | /// Note that this function returns `Err` if there is not enough data to parse the whole 217 | /// header, but it can return `Ok` even if there is not enough data in the given buffer to hold 218 | /// the whole of the payload that the header indicates should be present (however _if_ there is 219 | /// not enough data to hold the payload, then [`payload()`](#method.payload) will return 220 | /// `None`). 221 | pub fn from_bytes(buf: &'buf [u8]) -> Result, AdtsHeaderError> { 222 | assert!(!buf.is_empty()); 223 | let header_len = 7; 224 | Self::check_len(header_len, buf.len())?; 225 | let header = AdtsHeader { buf }; 226 | if header.sync_word() != 0xfff { 227 | return Err(AdtsHeaderError::BadSyncWord(header.sync_word())); 228 | } 229 | let crc_len = 2; 230 | if header.protection() == ProtectionIndicator::CrcPresent { 231 | Self::check_len(header_len + crc_len, buf.len())?; 232 | } 233 | if header.frame_length() < header.header_length() { 234 | return Err(AdtsHeaderError::BadFrameLength { 235 | actual: header.frame_length() as usize, 236 | minimum: header.header_length() as usize, 237 | }); 238 | } 239 | Ok(header) 240 | } 241 | 242 | fn check_len(expected: usize, actual: usize) -> Result<(), AdtsHeaderError> { 243 | if actual < expected { 244 | Err(AdtsHeaderError::NotEnoughData { expected, actual }) 245 | } else { 246 | Ok(()) 247 | } 248 | } 249 | 250 | fn header_length(&self) -> u16 { 251 | let fixed_len = 7; 252 | if self.protection() == ProtectionIndicator::CrcPresent { 253 | fixed_len + 2 254 | } else { 255 | fixed_len 256 | } 257 | } 258 | 259 | fn sync_word(&self) -> u16 { 260 | u16::from(self.buf[0]) << 4 | u16::from(self.buf[1] >> 4) 261 | } 262 | 263 | pub fn mpeg_version(&self) -> MpegVersion { 264 | if self.buf[1] & 0b0000_1000 != 0 { 265 | MpegVersion::Mpeg2 266 | } else { 267 | MpegVersion::Mpeg4 268 | } 269 | } 270 | 271 | pub fn protection(&self) -> ProtectionIndicator { 272 | if self.buf[1] & 0b0000_0001 != 0 { 273 | ProtectionIndicator::CrcAbsent 274 | } else { 275 | ProtectionIndicator::CrcPresent 276 | } 277 | } 278 | 279 | // Indicates what type of AAC data this stream contains 280 | pub fn audio_object_type(&self) -> AudioObjectType { 281 | match self.buf[2] & 0b1100_0000 { 282 | 0b0000_0000 => AudioObjectType::AacMain, 283 | 0b0100_0000 => AudioObjectType::AacLC, 284 | 0b1000_0000 => AudioObjectType::AacSSR, 285 | 0b1100_0000 => AudioObjectType::AacLTP, 286 | v => panic!("impossible value {:#b}", v), 287 | } 288 | } 289 | 290 | pub fn sampling_frequency(&self) -> SamplingFrequency { 291 | SamplingFrequency::from(self.buf[2] >> 2 & 0b1111) 292 | } 293 | 294 | /// either 1 or 0 295 | pub fn private_bit(&self) -> u8 { 296 | self.buf[2] & 1 297 | } 298 | 299 | pub fn channel_configuration(&self) -> ChannelConfiguration { 300 | ChannelConfiguration::from(self.buf[2] << 2 & 0b0100 | self.buf[3] >> 6) 301 | } 302 | 303 | pub fn originality(&self) -> Originality { 304 | if self.buf[3] & 0b0010_0000 != 0 { 305 | Originality::Copy 306 | } else { 307 | Originality::Original 308 | } 309 | } 310 | 311 | /// either 1 or 0 312 | pub fn home(&self) -> u8 { 313 | self.buf[3] >> 4 & 1 314 | } 315 | 316 | /// either 1 or 0 317 | pub fn copyright_identification_bit(&self) -> u8 { 318 | self.buf[3] >> 3 & 1 319 | } 320 | 321 | pub fn copyright_identification_start(&self) -> CopyrightIdentificationStart { 322 | if self.buf[3] & 0b0000_0100 != 0 { 323 | CopyrightIdentificationStart::Start 324 | } else { 325 | CopyrightIdentificationStart::Other 326 | } 327 | } 328 | 329 | /// length of this frame, including the length of the header. 330 | pub fn frame_length(&self) -> u16 { 331 | u16::from(self.buf[3] & 0b11) << 11 332 | | u16::from(self.buf[4]) << 3 333 | | u16::from(self.buf[5]) >> 5 334 | } 335 | 336 | /// Calculates the length of the frame payload from the `frame_length` header value, and the 337 | /// total size of headers in this frame -- returning `None` if the `frame_length` header had a 338 | /// value too small to even include the headers 339 | pub fn payload_length(&self) -> Option { 340 | let diff = self.frame_length() as i16 - self.header_length() as i16; 341 | if diff >= 0 { 342 | Some(diff as u16) 343 | } else { 344 | None 345 | } 346 | } 347 | 348 | pub fn adts_buffer_fullness(&self) -> u16 { 349 | u16::from(self.buf[5] & 0b00000011) << 6 | u16::from(self.buf[6]) >> 2 350 | } 351 | 352 | /// Gives the 16-bit cyclic redundancy check value stored in this frame header, or `None` if 353 | /// the header does not supply a CRC. 354 | /// 355 | /// NB the implementation doesn't currently check that the CRC is correct 356 | pub fn crc(&self) -> Option { 357 | match self.protection() { 358 | ProtectionIndicator::CrcAbsent => None, 359 | ProtectionIndicator::CrcPresent => { 360 | Some(u16::from(self.buf[7]) << 8 | u16::from(self.buf[8])) 361 | } 362 | } 363 | } 364 | 365 | /// The number of data blocks in the frame, a value between 1 and 4 inclusive. 366 | /// 367 | /// (Note that in the serialised ADTS data stores the _number of blocks - 1_. This method 368 | /// returns the actual number of blocks by adding one to the serialised value.) 369 | /// 370 | /// Most streams store a single block per ADTS frame 371 | pub fn number_of_raw_data_blocks_in_frame(&self) -> u8 { 372 | (self.buf[6] & 0b11) + 1 373 | } 374 | 375 | /// The payload AAC data inside this ADTS frame 376 | pub fn payload(&self) -> Result<&'buf [u8], PayloadError> { 377 | let len = self.frame_length() as usize; 378 | if self.buf.len() < len { 379 | Err(PayloadError { 380 | expected: len, 381 | actual: self.buf.len(), 382 | }) 383 | } else { 384 | Ok(&self.buf[self.header_length() as usize..len]) 385 | } 386 | } 387 | } 388 | impl<'buf> fmt::Debug for AdtsHeader<'buf> { 389 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { 390 | f.debug_struct("AdtsHeader") 391 | .field("mpeg_version", &self.mpeg_version()) 392 | .field("protection", &self.protection()) 393 | .field("audio_object_type", &self.audio_object_type()) 394 | .field("sampling_frequency", &self.sampling_frequency()) 395 | .field("private_bit", &self.private_bit()) 396 | .field("channel_configuration", &self.channel_configuration()) 397 | .field("originality", &self.originality()) 398 | .field("home", &self.home()) 399 | .field( 400 | "copyright_identification_bit", 401 | &self.copyright_identification_bit(), 402 | ) 403 | .field( 404 | "copyright_identification_start", 405 | &self.copyright_identification_start(), 406 | ) 407 | .field("frame_length", &self.frame_length()) 408 | .field("adts_buffer_fullness", &self.adts_buffer_fullness()) 409 | .field("crc", &self.crc()) 410 | .field( 411 | "number_of_raw_data_blocks_in_frame", 412 | &self.number_of_raw_data_blocks_in_frame(), 413 | ) 414 | .finish() 415 | } 416 | } 417 | 418 | #[derive(Debug, PartialEq)] 419 | pub enum CopyrightIdErr { 420 | TooFewBits, 421 | TooManyBits, 422 | } 423 | 424 | #[derive(Debug, PartialEq)] 425 | pub struct CopyrightIdentification { 426 | pub copyright_identifier: u8, 427 | pub copyright_number: u64, 428 | } 429 | 430 | #[derive(PartialEq)] 431 | enum AdtsState { 432 | Start, 433 | Incomplete, 434 | Error, 435 | } 436 | 437 | #[derive(Debug, PartialEq)] 438 | pub enum AdtsParseError { 439 | BadSyncWord, 440 | BadFrameLength, 441 | } 442 | 443 | /// Trait to be implemented by types that wish to consume the ADTS data produced by [`AdtsParser`](struct.AdtsParser.html). 444 | /// 445 | /// # Example 446 | /// 447 | /// ```rust 448 | /// use adts_reader::*; 449 | /// 450 | /// struct MyConsumer { } 451 | /// impl AdtsConsumer for MyConsumer { 452 | /// fn new_config(&mut self, mpeg_version: MpegVersion, protection: ProtectionIndicator, aot: AudioObjectType, freq: SamplingFrequency, private_bit: u8, channels: ChannelConfiguration, originality: Originality, home: u8) { 453 | /// println!("Configuration {:?} {:?} {:?}", aot, freq, channels); 454 | /// } 455 | /// fn payload(&mut self, buffer_fullness: u16, number_of_blocks: u8, buf: &[u8]) { 456 | /// println!(" - frame of {} bytes", buf.len()); 457 | /// } 458 | /// fn error(&mut self, err: AdtsParseError) { 459 | /// println!(" - oops: {:?}", err); 460 | /// } 461 | /// } 462 | /// 463 | /// let consumer = MyConsumer { }; 464 | /// let parser = AdtsParser::new(consumer); 465 | /// ``` 466 | pub trait AdtsConsumer { 467 | /// Called when a new configuration is found within the ADTS bitstream 468 | /// 469 | /// An ADTS bitstream should have the same configuration throughout, so this would usually just 470 | /// be called once at the beginning of the stream. The audio configuration header values do 471 | /// however appear in every frame (so that the bitstream format can support seeking, not that 472 | /// this implementation helps there) and so it would be possible for a malformed bitstream to 473 | /// signal a configuration change part way through. 474 | fn new_config( 475 | &mut self, 476 | mpeg_version: MpegVersion, 477 | protection: ProtectionIndicator, 478 | aot: AudioObjectType, 479 | freq: SamplingFrequency, 480 | private_bit: u8, 481 | channels: ChannelConfiguration, 482 | originality: Originality, 483 | home: u8, 484 | ); 485 | 486 | /// called with the ADTS frame payload, and frame-specific header values 487 | fn payload(&mut self, buffer_fullness: u16, number_of_blocks: u8, buf: &[u8]); 488 | 489 | /// called if AdtsParser encounters an error in the ADTS bitstream. 490 | fn error(&mut self, err: AdtsParseError); 491 | } 492 | 493 | /// Find ADTS frames within provided buffers of data, announcing audio configuration as it is 494 | /// discovered (normally just once at the start, but possibly changing during the stream if the 495 | /// stream is malformed). 496 | /// 497 | /// Does not currently try to handle re-synchronise with the ADTS bitstream on encountering bad 498 | /// data. 499 | pub struct AdtsParser 500 | where 501 | C: AdtsConsumer, 502 | { 503 | pub consumer: C, 504 | current_config: [u8; 3], 505 | state: AdtsState, 506 | incomplete_frame: Vec, 507 | desired_data_len: Option, 508 | } 509 | impl AdtsParser 510 | where 511 | C: AdtsConsumer, 512 | { 513 | pub fn new(consumer: C) -> AdtsParser { 514 | AdtsParser { 515 | consumer, 516 | current_config: [0; 3], 517 | state: AdtsState::Start, 518 | incomplete_frame: vec![], 519 | desired_data_len: None, 520 | } 521 | } 522 | 523 | fn is_new_config(&self, header_data: &[u8]) -> bool { 524 | self.current_config != header_data[0..3] 525 | } 526 | 527 | fn remember(&mut self, remaining_data: &[u8], desired_data_len: usize) { 528 | self.state = AdtsState::Incomplete; 529 | self.incomplete_frame.clear(); 530 | self.incomplete_frame.extend_from_slice(remaining_data); 531 | self.desired_data_len = Some(desired_data_len); 532 | } 533 | 534 | /// Initialize or re-initialize parser state. Call this function before processing a group of 535 | /// ADTS frames to ensure that any error state due to processing an earlier group of ADTS 536 | /// frames is cleared. 537 | pub fn start(&mut self) { 538 | if self.state == AdtsState::Incomplete { 539 | self.incomplete_frame.clear(); 540 | self.desired_data_len = None; 541 | eprintln!("ADTS: incomplete data buffer dropped by call to start()"); 542 | } 543 | self.state = AdtsState::Start; 544 | } 545 | 546 | /// Extracts information about each ADTS frame in the given buffer, which is passed to the 547 | /// `AdtsConsumer` implementation supplied at construction time. 548 | /// 549 | /// If the given buffer ends part-way through an ADTS frame, the remaining unconsumed data 550 | /// will be buffered inside this AdtsParser instance, and the rest of the ADTS frame may be 551 | /// passed in another buffer in the next call to this method. 552 | pub fn push(&mut self, adts_buf: &[u8]) { 553 | let mut buf = adts_buf; 554 | match self.state { 555 | AdtsState::Error => return, // TODO: resync to recover from bitstream errors 556 | AdtsState::Incomplete => { 557 | // on last call to push(), the end of the adts_buf held the start of an ADTS 558 | // frame, and we copied that data into incomplete_buffer, so now lets try to add 559 | // enough initial bytes from the adts_buf given to this call to get a complete 560 | // frame 561 | loop { 562 | let bytes_needed_to_complete_frame = 563 | self.desired_data_len.unwrap() - self.incomplete_frame.len(); 564 | if buf.len() < bytes_needed_to_complete_frame { 565 | self.incomplete_frame.extend_from_slice(buf); 566 | return; 567 | } 568 | self.incomplete_frame 569 | .extend_from_slice(&buf[..bytes_needed_to_complete_frame]); 570 | buf = &buf[bytes_needed_to_complete_frame..]; 571 | let mut still_more = false; // TODO: this is horrible 572 | match AdtsHeader::from_bytes(&self.incomplete_frame[..]) { 573 | Ok(header) => { 574 | if (header.frame_length() as usize) > self.incomplete_frame.len() { 575 | self.desired_data_len = Some(header.frame_length() as usize); 576 | still_more = true; 577 | } else { 578 | if self.is_new_config(&self.incomplete_frame[..]) { 579 | Self::push_config( 580 | &mut self.current_config, 581 | &mut self.consumer, 582 | &header, 583 | &self.incomplete_frame[..], 584 | ); 585 | } 586 | Self::push_payload(&mut self.consumer, header); 587 | self.state = AdtsState::Start; 588 | } 589 | } 590 | Err(e) => { 591 | self.state = AdtsState::Error; 592 | match e { 593 | AdtsHeaderError::BadSyncWord { .. } => { 594 | self.consumer.error(AdtsParseError::BadSyncWord); 595 | return; 596 | } 597 | AdtsHeaderError::BadFrameLength { .. } => { 598 | self.consumer.error(AdtsParseError::BadFrameLength); 599 | return; 600 | } 601 | AdtsHeaderError::NotEnoughData { expected, .. } => { 602 | self.desired_data_len = Some(expected); 603 | still_more = true; 604 | } 605 | } 606 | } 607 | } 608 | if !still_more { 609 | break; 610 | } 611 | } 612 | } 613 | AdtsState::Start => (), 614 | }; 615 | let mut pos = 0; 616 | while pos < buf.len() { 617 | let remaining_data = &buf[pos..]; 618 | let h = match AdtsHeader::from_bytes(remaining_data) { 619 | Ok(header) => header, 620 | Err(e) => { 621 | self.state = AdtsState::Error; 622 | match e { 623 | AdtsHeaderError::BadSyncWord { .. } => { 624 | self.consumer.error(AdtsParseError::BadSyncWord) 625 | } 626 | AdtsHeaderError::BadFrameLength { .. } => { 627 | self.consumer.error(AdtsParseError::BadFrameLength); 628 | return; 629 | } 630 | AdtsHeaderError::NotEnoughData { expected, .. } => { 631 | self.remember(remaining_data, expected); 632 | return; 633 | } 634 | } 635 | return; 636 | } 637 | }; 638 | let new_pos = pos + h.frame_length() as usize; 639 | if new_pos > buf.len() { 640 | self.remember(remaining_data, h.frame_length() as usize); 641 | return; 642 | } 643 | if self.is_new_config(remaining_data) { 644 | Self::push_config( 645 | &mut self.current_config, 646 | &mut self.consumer, 647 | &h, 648 | remaining_data, 649 | ); 650 | } 651 | Self::push_payload(&mut self.consumer, h); 652 | self.state = AdtsState::Start; 653 | pos = new_pos; 654 | } 655 | } 656 | 657 | fn push_config( 658 | current_config: &mut [u8; 3], 659 | consumer: &mut C, 660 | h: &AdtsHeader<'_>, 661 | frame_buffer: &[u8], 662 | ) { 663 | current_config.copy_from_slice(&frame_buffer[0..3]); 664 | consumer.new_config( 665 | h.mpeg_version(), 666 | h.protection(), 667 | h.audio_object_type(), 668 | h.sampling_frequency(), 669 | h.private_bit(), 670 | h.channel_configuration(), 671 | h.originality(), 672 | h.home(), 673 | ); 674 | } 675 | 676 | fn push_payload(consumer: &mut C, h: AdtsHeader<'_>) { 677 | match h.payload() { 678 | Ok(payload) => { 679 | consumer.payload( 680 | h.adts_buffer_fullness(), 681 | h.number_of_raw_data_blocks_in_frame(), 682 | payload, 683 | ); 684 | } 685 | Err(PayloadError { expected, actual }) => { 686 | // since we checked we had enough data for the whole frame above, this must be 687 | // a bug, 688 | panic!( 689 | "Unexpected payload size mismatch: expected {}, actual size {}", 690 | expected, actual 691 | ); 692 | } 693 | } 694 | } 695 | } 696 | 697 | #[cfg(test)] 698 | mod tests { 699 | use super::*; 700 | use bitstream_io::{BigEndian, BitWrite, BitWriter, BE}; 701 | use std::io; 702 | 703 | fn make_test_data(builder: F) -> Vec 704 | where 705 | F: Fn(BitWriter<&mut Vec, BE>) -> Result<(), io::Error>, 706 | { 707 | let mut data: Vec = Vec::new(); 708 | builder(BitWriter::endian(&mut data, BigEndian)).unwrap(); 709 | data 710 | } 711 | 712 | fn write_frame(w: &mut BitWriter<&mut Vec, BE>) -> Result<(), io::Error> { 713 | w.write(12, 0xfff)?; // sync_word 714 | w.write(1, 0)?; // mpeg_version 715 | w.write(2, 0)?; // layer 716 | w.write(1, 1)?; // protection_absent 717 | w.write(2, 0)?; // object_type 718 | w.write(4, 0b0011)?; // sampling_frequency_index 719 | w.write(1, 1)?; // private_bit 720 | w.write(3, 2)?; // channel_configuration 721 | w.write(1, 1)?; // original_copy 722 | w.write(1, 0)?; // home 723 | w.write(1, 0)?; // copyright_identification_bit 724 | w.write(1, 1)?; // copyright_identification_start 725 | w.write(13, 8)?; // frame_length 726 | w.write(11, 123)?; // adts_buffer_fullness 727 | w.write(2, 0)?; // number_of_raw_data_blocks_in_frame 728 | w.write(8, 0b10000001) // 1 byte of payload data 729 | } 730 | 731 | #[test] 732 | fn no_crc() { 733 | let header_data = make_test_data(|mut w| write_frame(&mut w)); 734 | let header = AdtsHeader::from_bytes(&header_data[..]).unwrap(); 735 | assert_eq!(header.mpeg_version(), MpegVersion::Mpeg4); 736 | assert_eq!(header.protection(), ProtectionIndicator::CrcAbsent); 737 | assert_eq!(header.audio_object_type(), AudioObjectType::AacMain); 738 | assert_eq!(header.sampling_frequency(), SamplingFrequency::Freq48000); 739 | assert_eq!(header.sampling_frequency().freq(), Some(48000)); 740 | assert_eq!(header.channel_configuration(), ChannelConfiguration::Stereo); 741 | assert_eq!(header.originality(), Originality::Copy); 742 | assert_eq!(header.home(), 0); 743 | assert_eq!(header.copyright_identification_bit(), 0); 744 | assert_eq!( 745 | header.copyright_identification_start(), 746 | CopyrightIdentificationStart::Start 747 | ); 748 | assert_eq!(header.frame_length(), 8); 749 | assert_eq!(header.payload_length(), Some(8 - 7)); 750 | assert_eq!(header.adts_buffer_fullness(), 123); 751 | assert_eq!(header.number_of_raw_data_blocks_in_frame(), 1); 752 | assert_eq!(header.payload(), Ok(&[0b10000001][..])); 753 | } 754 | 755 | struct MockConsumer { 756 | seq: usize, 757 | payload_seq: usize, 758 | payload_size: Option, 759 | } 760 | impl MockConsumer { 761 | pub fn new() -> MockConsumer { 762 | MockConsumer { 763 | seq: 0, 764 | payload_seq: 0, 765 | payload_size: None, 766 | } 767 | } 768 | pub fn assert_seq(&mut self, expected: usize) { 769 | assert_eq!(expected, self.seq); 770 | self.seq += 1; 771 | } 772 | } 773 | impl AdtsConsumer for MockConsumer { 774 | // TODO: assertions are terribly brittle 775 | fn new_config( 776 | &mut self, 777 | mpeg_version: MpegVersion, 778 | _protection: ProtectionIndicator, 779 | _aot: AudioObjectType, 780 | _freq: SamplingFrequency, 781 | _private_bit: u8, 782 | _channels: ChannelConfiguration, 783 | _originality: Originality, 784 | _home: u8, 785 | ) { 786 | self.assert_seq(0); 787 | assert_eq!(mpeg_version, MpegVersion::Mpeg4); 788 | } 789 | fn payload(&mut self, _buffer_fullness: u16, _number_of_blocks: u8, buf: &[u8]) { 790 | self.payload_seq += 1; 791 | let new_payload_seq = self.payload_seq; 792 | self.assert_seq(new_payload_seq); 793 | self.payload_size = Some(buf.len()); 794 | } 795 | fn error(&mut self, err: AdtsParseError) { 796 | panic!("no errors expected in bitstream: {:?}", err); 797 | } 798 | } 799 | 800 | #[test] 801 | fn parser() { 802 | let header_data = make_test_data(|mut w| { 803 | write_frame(&mut w)?; 804 | write_frame(&mut w) 805 | }); 806 | for split in 0..header_data.len() { 807 | let mut parser = AdtsParser::new(MockConsumer::new()); 808 | let (head, tail) = header_data.split_at(split); 809 | parser.push(head); 810 | parser.push(tail); 811 | assert_eq!(2, parser.consumer.payload_seq); 812 | assert_eq!(Some(1), parser.consumer.payload_size); 813 | } 814 | } 815 | 816 | #[test] 817 | fn too_short() { 818 | let header_data = make_test_data(|mut w| write_frame(&mut w)); 819 | let mut parser = AdtsParser::new(MockConsumer::new()); 820 | parser.push(&header_data[..5]); 821 | parser.push(&header_data[5..7]); 822 | } 823 | } 824 | --------------------------------------------------------------------------------