├── .github ├── actions-rs │ └── grcov.yml └── workflows │ ├── cargo.yml │ └── grcov.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── benches └── packet_bench.rs ├── codecov.yml ├── doc └── webrtc.rs.png └── src ├── codecs ├── g7xx │ ├── g7xx_test.rs │ └── mod.rs ├── h264 │ ├── h264_test.rs │ └── mod.rs ├── h265 │ ├── h265_test.rs │ └── mod.rs ├── mod.rs ├── opus │ ├── mod.rs │ └── opus_test.rs ├── vp8 │ ├── mod.rs │ └── vp8_test.rs └── vp9 │ ├── mod.rs │ └── vp9_test.rs ├── error.rs ├── extension ├── abs_send_time_extension │ ├── abs_send_time_extension_test.rs │ └── mod.rs ├── audio_level_extension │ ├── audio_level_extension_test.rs │ └── mod.rs ├── mod.rs └── transport_cc_extension │ ├── mod.rs │ └── transport_cc_extension_test.rs ├── header.rs ├── lib.rs ├── packet ├── mod.rs └── packet_test.rs ├── packetizer ├── mod.rs └── packetizer_test.rs └── sequence.rs /.github/actions-rs/grcov.yml: -------------------------------------------------------------------------------- 1 | branch: true 2 | ignore-not-existing: true 3 | llvm: true 4 | filter: covered 5 | output-type: lcov 6 | output-path: ./lcov.info 7 | source-dir: . 8 | ignore: 9 | - "/*" 10 | - "C:/*" 11 | - "../*" 12 | excl-line: "#\\[derive\\(" 13 | excl-start: "mod tests \\{" 14 | excl-br-line: "#\\[derive\\(" 15 | excl-br-start: "mod tests \\{" -------------------------------------------------------------------------------- /.github/workflows/cargo.yml: -------------------------------------------------------------------------------- 1 | name: cargo 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | check_and_test: 14 | name: Check and test 15 | strategy: 16 | matrix: 17 | os: ['ubuntu-latest', 'macos-latest', 'windows-latest'] 18 | toolchain: 19 | - 1.56.1 # min supported version (https://github.com/webrtc-rs/webrtc/#toolchain) 20 | - stable 21 | runs-on: ${{ matrix.os }} 22 | steps: 23 | - uses: actions/checkout@v3 24 | - name: Cache cargo registry 25 | uses: actions/cache@v3 26 | with: 27 | path: ~/.cargo/registry 28 | key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} 29 | - uses: actions-rs/toolchain@v1 30 | with: 31 | toolchain: ${{ matrix.toolchain }} 32 | profile: minimal 33 | override: true 34 | - uses: actions-rs/cargo@v1 35 | with: 36 | command: check 37 | - uses: actions-rs/cargo@v1 38 | with: 39 | command: test 40 | 41 | rustfmt_and_clippy: 42 | name: Check rustfmt style and run clippy 43 | runs-on: ubuntu-latest 44 | steps: 45 | - uses: actions/checkout@v3 46 | - uses: actions-rs/toolchain@v1 47 | with: 48 | toolchain: stable 49 | profile: minimal 50 | components: clippy, rustfmt 51 | override: true 52 | - name: Cache cargo registry 53 | uses: actions/cache@v3 54 | with: 55 | path: ~/.cargo/registry 56 | key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} 57 | - name: Run clippy 58 | uses: actions-rs/cargo@v1 59 | with: 60 | command: clippy 61 | args: -- -D warnings 62 | - name: Check formating 63 | uses: actions-rs/cargo@v1 64 | with: 65 | command: fmt 66 | args: --all -- --check 67 | 68 | minimal_versions: 69 | name: Compile and test with minimal versions 70 | strategy: 71 | matrix: 72 | # TODO: add 'windows-latest' once criterion#atty version is upgraded 73 | # "0.2" min version (0.2.0) depends on winapi-0.2.4 which does not 74 | # compile on nightly. 75 | # https://github.com/bheisler/criterion.rs/pull/587 76 | os: ['ubuntu-latest', 'macos-latest'] 77 | runs-on: ${{ matrix.os }} 78 | steps: 79 | - uses: actions/checkout@v3 80 | - name: Install latest nightly 81 | uses: actions-rs/toolchain@v1 82 | with: 83 | toolchain: nightly 84 | override: true 85 | - uses: taiki-e/install-action@cargo-hack 86 | - uses: taiki-e/install-action@cargo-minimal-versions 87 | - run: cargo minimal-versions check --workspace --all-features --ignore-private -v 88 | - run: cargo minimal-versions build --workspace --all-features --ignore-private -v 89 | - run: cargo minimal-versions test --workspace --all-features -v 90 | -------------------------------------------------------------------------------- /.github/workflows/grcov.yml: -------------------------------------------------------------------------------- 1 | name: coverage 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | grcov: 14 | name: Coverage 15 | runs-on: ${{ matrix.os }} 16 | strategy: 17 | matrix: 18 | os: 19 | - ubuntu-latest 20 | toolchain: 21 | - nightly 22 | cargo_flags: 23 | - "--all-features" 24 | steps: 25 | - name: Checkout source code 26 | uses: actions/checkout@v2 27 | 28 | - name: Install Rust 29 | uses: actions-rs/toolchain@v1 30 | with: 31 | profile: minimal 32 | toolchain: ${{ matrix.toolchain }} 33 | override: true 34 | 35 | - name: Install grcov 36 | uses: actions-rs/install@v0.1 37 | with: 38 | crate: grcov 39 | version: latest 40 | use-tool-cache: true 41 | 42 | - name: Test 43 | uses: actions-rs/cargo@v1 44 | with: 45 | command: test 46 | args: --all --no-fail-fast ${{ matrix.cargo_flags }} 47 | env: 48 | CARGO_INCREMENTAL: "0" 49 | RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort -Cdebug-assertions=off' 50 | RUSTDOCFLAGS: '-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort -Cdebug-assertions=off' 51 | 52 | - name: Generate coverage data 53 | id: grcov 54 | # uses: actions-rs/grcov@v0.1 55 | run: | 56 | grcov target/debug/ \ 57 | --branch \ 58 | --llvm \ 59 | --source-dir . \ 60 | --output-path lcov.info \ 61 | --ignore='/**' \ 62 | --ignore='C:/**' \ 63 | --ignore='../**' \ 64 | --ignore-not-existing \ 65 | --excl-line "#\\[derive\\(" \ 66 | --excl-br-line "#\\[derive\\(" \ 67 | --excl-start "#\\[cfg\\(test\\)\\]" \ 68 | --excl-br-start "#\\[cfg\\(test\\)\\]" \ 69 | --commit-sha ${{ github.sha }} \ 70 | --service-job-id ${{ github.job }} \ 71 | --service-name "GitHub Actions" \ 72 | --service-number ${{ github.run_id }} 73 | - name: Upload coverage as artifact 74 | uses: actions/upload-artifact@v2 75 | with: 76 | name: lcov.info 77 | # path: ${{ steps.grcov.outputs.report }} 78 | path: lcov.info 79 | 80 | - name: Upload coverage to codecov.io 81 | uses: codecov/codecov-action@v1 82 | with: 83 | # file: ${{ steps.grcov.outputs.report }} 84 | file: lcov.info 85 | fail_ci_if_error: true 86 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | /.idea/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rtp" 3 | version = "0.6.6" 4 | authors = ["Rain Liu ", "Michael Uti "] 5 | edition = "2018" 6 | description = "A pure Rust implementation of RTP" 7 | license = "MIT/Apache-2.0" 8 | documentation = "https://docs.rs/rtp" 9 | homepage = "https://webrtc.rs" 10 | repository = "https://github.com/webrtc-rs/rtp" 11 | 12 | [dependencies] 13 | util = { package = "webrtc-util", version = "0.5.4", default-features = false, features = ["marshal"] } 14 | bytes = "1" 15 | rand = "0.8.5" 16 | thiserror = "1.0" 17 | async-trait = "0.1.56" 18 | 19 | [dev-dependencies] 20 | chrono = "0.4.19" 21 | criterion = "0.3.5" 22 | tokio = { version = "1.19", features = ["full"] } 23 | tokio-test = "0.4.0" # must match the min version of the `tokio` crate above 24 | 25 | [[bench]] 26 | name = "packet_bench" 27 | harness = false 28 | -------------------------------------------------------------------------------- /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 | MIT License 2 | 3 | Copyright (c) 2021 WebRTC.rs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Crate moved 2 | 3 | As of the 23rd of August 2022 this crate has been migrated to the [`webrtc-rs/webrtc`](http://github.com/webrtc-rs/webrtc/) monorepo. 4 | -------------------------------------------------------------------------------- /benches/packet_bench.rs: -------------------------------------------------------------------------------- 1 | use bytes::{Bytes, BytesMut}; 2 | use criterion::{criterion_group, criterion_main, Criterion}; 3 | use rtp::{header::*, packet::*}; 4 | use util::marshal::{Marshal, MarshalSize, Unmarshal}; 5 | 6 | fn benchmark_packet(c: &mut Criterion) { 7 | let pkt = Packet { 8 | header: Header { 9 | extension: true, 10 | csrc: vec![1, 2], 11 | extension_profile: EXTENSION_PROFILE_TWO_BYTE, 12 | extensions: vec![ 13 | Extension { 14 | id: 1, 15 | payload: Bytes::from_static(&[3, 4]), 16 | }, 17 | Extension { 18 | id: 2, 19 | payload: Bytes::from_static(&[5, 6]), 20 | }, 21 | ], 22 | ..Default::default() 23 | }, 24 | payload: Bytes::from_static(&[0xFFu8; 15]), //vec![0x07, 0x08, 0x09, 0x0a], //MTU=1500 25 | ..Default::default() 26 | }; 27 | let raw = pkt.marshal().unwrap(); 28 | let buf = &mut raw.clone(); 29 | let p = Packet::unmarshal(buf).unwrap(); 30 | if pkt != p { 31 | panic!( 32 | "marshal or unmarshal not correct: \npkt: {:?} \nvs \np: {:?}", 33 | pkt, p 34 | ); 35 | } 36 | 37 | /////////////////////////////////////////////////////////////////////////////////////////////// 38 | let mut buf = BytesMut::with_capacity(pkt.marshal_size()); 39 | buf.resize(pkt.marshal_size(), 0); 40 | c.bench_function("Benchmark MarshalTo", |b| { 41 | b.iter(|| { 42 | let _ = pkt.marshal_to(&mut buf).unwrap(); 43 | }) 44 | }); 45 | 46 | c.bench_function("Benchmark Marshal", |b| { 47 | b.iter(|| { 48 | let _ = pkt.marshal().unwrap(); 49 | }) 50 | }); 51 | 52 | c.bench_function("Benchmark Unmarshal ", |b| { 53 | b.iter(|| { 54 | let buf = &mut raw.clone(); 55 | let _ = Packet::unmarshal(buf).unwrap(); 56 | }) 57 | }); 58 | } 59 | 60 | criterion_group!(benches, benchmark_packet); 61 | criterion_main!(benches); 62 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | require_ci_to_pass: yes 3 | max_report_age: off 4 | token: cd48e18f-3916-4a20-ba56-81354d68a5d2 5 | 6 | coverage: 7 | precision: 2 8 | round: down 9 | range: 50..90 10 | status: 11 | project: 12 | default: 13 | enabled: no 14 | threshold: 0.2 15 | if_not_found: success 16 | patch: 17 | default: 18 | enabled: no 19 | if_not_found: success 20 | changes: 21 | default: 22 | enabled: no 23 | if_not_found: success 24 | -------------------------------------------------------------------------------- /doc/webrtc.rs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webrtc-rs/rtp/ee39d4ea79bd33c01b5b802abdffb183ef586416/doc/webrtc.rs.png -------------------------------------------------------------------------------- /src/codecs/g7xx/g7xx_test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[test] 4 | fn test_g7xx_payload() -> Result<()> { 5 | let mut pck = G711Payloader::default(); 6 | 7 | const TEST_LEN: usize = 10000; 8 | const TEST_MTU: usize = 1500; 9 | 10 | //generate random 8-bit g722 samples 11 | let samples: Vec = (0..TEST_LEN).map(|_| rand::random::()).collect(); 12 | 13 | //make a copy, for payloader input 14 | let mut samples_in = vec![0u8; TEST_LEN]; 15 | samples_in.clone_from_slice(&samples); 16 | let samples_in = Bytes::copy_from_slice(&samples_in); 17 | 18 | //split our samples into payloads 19 | let payloads = pck.payload(TEST_MTU, &samples_in)?; 20 | 21 | let outcnt = ((TEST_LEN as f64) / (TEST_MTU as f64)).ceil() as usize; 22 | assert_eq!( 23 | outcnt, 24 | payloads.len(), 25 | "Generated {} payloads instead of {}", 26 | payloads.len(), 27 | outcnt 28 | ); 29 | assert_eq!(&samples, &samples_in, "Modified input samples"); 30 | 31 | let samples_out = payloads.concat(); 32 | assert_eq!(&samples_out, &samples_in, "Output samples don't match"); 33 | 34 | let empty = Bytes::from_static(&[]); 35 | let payload = Bytes::from_static(&[0x90, 0x90, 0x90]); 36 | 37 | // Positive MTU, empty payload 38 | let result = pck.payload(1, &empty)?; 39 | assert!(result.is_empty(), "Generated payload should be empty"); 40 | 41 | // 0 MTU, small payload 42 | let result = pck.payload(0, &payload)?; 43 | assert_eq!(result.len(), 0, "Generated payload should be empty"); 44 | 45 | // Positive MTU, small payload 46 | let result = pck.payload(10, &payload)?; 47 | assert_eq!(result.len(), 1, "Generated payload should be the 1"); 48 | 49 | Ok(()) 50 | } 51 | -------------------------------------------------------------------------------- /src/codecs/g7xx/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod g7xx_test; 3 | 4 | use crate::error::Result; 5 | use crate::packetizer::Payloader; 6 | 7 | use bytes::Bytes; 8 | 9 | /// G711Payloader payloads G711 packets 10 | pub type G711Payloader = G7xxPayloader; 11 | /// G722Payloader payloads G722 packets 12 | pub type G722Payloader = G7xxPayloader; 13 | 14 | #[derive(Default, Debug, Copy, Clone)] 15 | pub struct G7xxPayloader; 16 | 17 | impl Payloader for G7xxPayloader { 18 | /// Payload fragments an G7xx packet across one or more byte arrays 19 | fn payload(&mut self, mtu: usize, payload: &Bytes) -> Result> { 20 | if payload.is_empty() || mtu == 0 { 21 | return Ok(vec![]); 22 | } 23 | 24 | let mut payload_data_remaining = payload.len(); 25 | let mut payload_data_index = 0; 26 | let mut payloads = Vec::with_capacity(payload_data_remaining / mtu); 27 | while payload_data_remaining > 0 { 28 | let current_fragment_size = std::cmp::min(mtu, payload_data_remaining); 29 | payloads.push( 30 | payload.slice(payload_data_index..payload_data_index + current_fragment_size), 31 | ); 32 | 33 | payload_data_remaining -= current_fragment_size; 34 | payload_data_index += current_fragment_size; 35 | } 36 | 37 | Ok(payloads) 38 | } 39 | 40 | fn clone_to(&self) -> Box { 41 | Box::new(*self) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/codecs/h264/h264_test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[test] 4 | fn test_h264_payload() -> Result<()> { 5 | let empty = Bytes::from_static(&[]); 6 | let small_payload = Bytes::from_static(&[0x90, 0x90, 0x90]); 7 | let multiple_payload = Bytes::from_static(&[0x00, 0x00, 0x01, 0x90, 0x00, 0x00, 0x01, 0x90]); 8 | let large_payload = Bytes::from_static(&[ 9 | 0x00, 0x00, 0x01, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 10 | 0x12, 0x13, 0x14, 0x15, 11 | ]); 12 | let large_payload_packetized = vec![ 13 | Bytes::from_static(&[0x1c, 0x80, 0x01, 0x02, 0x03]), 14 | Bytes::from_static(&[0x1c, 0x00, 0x04, 0x05, 0x06]), 15 | Bytes::from_static(&[0x1c, 0x00, 0x07, 0x08, 0x09]), 16 | Bytes::from_static(&[0x1c, 0x00, 0x10, 0x11, 0x12]), 17 | Bytes::from_static(&[0x1c, 0x40, 0x13, 0x14, 0x15]), 18 | ]; 19 | 20 | let mut pck = H264Payloader::default(); 21 | 22 | // Positive MTU, empty payload 23 | let result = pck.payload(1, &empty)?; 24 | assert!(result.is_empty(), "Generated payload should be empty"); 25 | 26 | // 0 MTU, small payload 27 | let result = pck.payload(0, &small_payload)?; 28 | assert_eq!(result.len(), 0, "Generated payload should be empty"); 29 | 30 | // Positive MTU, small payload 31 | let result = pck.payload(1, &small_payload)?; 32 | assert_eq!(result.len(), 0, "Generated payload should be empty"); 33 | 34 | // Positive MTU, small payload 35 | let result = pck.payload(5, &small_payload)?; 36 | assert_eq!(result.len(), 1, "Generated payload should be the 1"); 37 | assert_eq!( 38 | result[0].len(), 39 | small_payload.len(), 40 | "Generated payload should be the same size as original payload size" 41 | ); 42 | 43 | // Multiple NALU in a single payload 44 | let result = pck.payload(5, &multiple_payload)?; 45 | assert_eq!(result.len(), 2, "2 nal units should be broken out"); 46 | for i in 0..2 { 47 | assert_eq!( 48 | result[i].len(), 49 | 1, 50 | "Payload {} of 2 is packed incorrectly", 51 | i + 1, 52 | ); 53 | } 54 | 55 | // Large Payload split across multiple RTP Packets 56 | let result = pck.payload(5, &large_payload)?; 57 | assert_eq!( 58 | result, large_payload_packetized, 59 | "FU-A packetization failed" 60 | ); 61 | 62 | // Nalu type 9 or 12 63 | let small_payload2 = Bytes::from_static(&[0x09, 0x00, 0x00]); 64 | let result = pck.payload(5, &small_payload2)?; 65 | assert_eq!(result.len(), 0, "Generated payload should be empty"); 66 | 67 | Ok(()) 68 | } 69 | 70 | #[test] 71 | fn test_h264_packet_unmarshal() -> Result<()> { 72 | let single_payload = Bytes::from_static(&[0x90, 0x90, 0x90]); 73 | let single_payload_unmarshaled = 74 | Bytes::from_static(&[0x00, 0x00, 0x00, 0x01, 0x90, 0x90, 0x90]); 75 | let single_payload_unmarshaled_avc = 76 | Bytes::from_static(&[0x00, 0x00, 0x00, 0x03, 0x90, 0x90, 0x90]); 77 | 78 | let large_payload = Bytes::from_static(&[ 79 | 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 80 | 0x11, 0x12, 0x13, 0x14, 0x15, 81 | ]); 82 | let large_payload_avc = Bytes::from_static(&[ 83 | 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 84 | 0x11, 0x12, 0x13, 0x14, 0x15, 85 | ]); 86 | let large_payload_packetized = vec![ 87 | Bytes::from_static(&[0x1c, 0x80, 0x01, 0x02, 0x03]), 88 | Bytes::from_static(&[0x1c, 0x00, 0x04, 0x05, 0x06]), 89 | Bytes::from_static(&[0x1c, 0x00, 0x07, 0x08, 0x09]), 90 | Bytes::from_static(&[0x1c, 0x00, 0x10, 0x11, 0x12]), 91 | Bytes::from_static(&[0x1c, 0x40, 0x13, 0x14, 0x15]), 92 | ]; 93 | 94 | let single_payload_multi_nalu = Bytes::from_static(&[ 95 | 0x78, 0x00, 0x0f, 0x67, 0x42, 0xc0, 0x1f, 0x1a, 0x32, 0x35, 0x01, 0x40, 0x7a, 0x40, 0x3c, 96 | 0x22, 0x11, 0xa8, 0x00, 0x05, 0x68, 0x1a, 0x34, 0xe3, 0xc8, 97 | ]); 98 | let single_payload_multi_nalu_unmarshaled = Bytes::from_static(&[ 99 | 0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0xc0, 0x1f, 0x1a, 0x32, 0x35, 0x01, 0x40, 0x7a, 0x40, 100 | 0x3c, 0x22, 0x11, 0xa8, 0x00, 0x00, 0x00, 0x01, 0x68, 0x1a, 0x34, 0xe3, 0xc8, 101 | ]); 102 | let single_payload_multi_nalu_unmarshaled_avc = Bytes::from_static(&[ 103 | 0x00, 0x00, 0x00, 0x0f, 0x67, 0x42, 0xc0, 0x1f, 0x1a, 0x32, 0x35, 0x01, 0x40, 0x7a, 0x40, 104 | 0x3c, 0x22, 0x11, 0xa8, 0x00, 0x00, 0x00, 0x05, 0x68, 0x1a, 0x34, 0xe3, 0xc8, 105 | ]); 106 | 107 | let incomplete_single_payload_multi_nalu = Bytes::from_static(&[ 108 | 0x78, 0x00, 0x0f, 0x67, 0x42, 0xc0, 0x1f, 0x1a, 0x32, 0x35, 0x01, 0x40, 0x7a, 0x40, 0x3c, 109 | 0x22, 0x11, 110 | ]); 111 | 112 | let mut pkt = H264Packet::default(); 113 | let mut avc_pkt = H264Packet { 114 | is_avc: true, 115 | ..Default::default() 116 | }; 117 | 118 | let data = Bytes::from_static(&[]); 119 | let result = pkt.depacketize(&data); 120 | assert!(result.is_err(), "Unmarshal did not fail on nil payload"); 121 | 122 | let data = Bytes::from_static(&[0x00, 0x00]); 123 | let result = pkt.depacketize(&data); 124 | assert!( 125 | result.is_err(), 126 | "Unmarshal accepted a packet that is too small for a payload and header" 127 | ); 128 | 129 | let data = Bytes::from_static(&[0xFF, 0x00, 0x00]); 130 | let result = pkt.depacketize(&data); 131 | assert!( 132 | result.is_err(), 133 | "Unmarshal accepted a packet with a NALU Type we don't handle" 134 | ); 135 | 136 | let result = pkt.depacketize(&incomplete_single_payload_multi_nalu); 137 | assert!( 138 | result.is_err(), 139 | "Unmarshal accepted a STAP-A packet with insufficient data" 140 | ); 141 | 142 | let payload = pkt.depacketize(&single_payload)?; 143 | assert_eq!( 144 | payload, single_payload_unmarshaled, 145 | "Unmarshaling a single payload shouldn't modify the payload" 146 | ); 147 | 148 | let payload = avc_pkt.depacketize(&single_payload)?; 149 | assert_eq!( 150 | payload, single_payload_unmarshaled_avc, 151 | "Unmarshaling a single payload into avc stream shouldn't modify the payload" 152 | ); 153 | 154 | let mut large_payload_result = BytesMut::new(); 155 | for p in &large_payload_packetized { 156 | let payload = pkt.depacketize(p)?; 157 | large_payload_result.put(&*payload.clone()); 158 | } 159 | assert_eq!( 160 | large_payload_result.freeze(), 161 | large_payload, 162 | "Failed to unmarshal a large payload" 163 | ); 164 | 165 | let mut large_payload_result_avc = BytesMut::new(); 166 | for p in &large_payload_packetized { 167 | let payload = avc_pkt.depacketize(p)?; 168 | large_payload_result_avc.put(&*payload.clone()); 169 | } 170 | assert_eq!( 171 | large_payload_result_avc.freeze(), 172 | large_payload_avc, 173 | "Failed to unmarshal a large payload into avc stream" 174 | ); 175 | 176 | let payload = pkt.depacketize(&single_payload_multi_nalu)?; 177 | assert_eq!( 178 | payload, single_payload_multi_nalu_unmarshaled, 179 | "Failed to unmarshal a single packet with multiple NALUs" 180 | ); 181 | 182 | let payload = avc_pkt.depacketize(&single_payload_multi_nalu)?; 183 | assert_eq!( 184 | payload, single_payload_multi_nalu_unmarshaled_avc, 185 | "Failed to unmarshal a single packet with multiple NALUs into avc stream" 186 | ); 187 | 188 | Ok(()) 189 | } 190 | 191 | #[test] 192 | fn test_h264_partition_head_checker_is_partition_head() -> Result<()> { 193 | let h264 = H264Packet::default(); 194 | let empty_nalu = Bytes::from_static(&[]); 195 | assert!( 196 | !h264.is_partition_head(&empty_nalu), 197 | "empty nalu must not be a partition head" 198 | ); 199 | 200 | let single_nalu = Bytes::from_static(&[1, 0]); 201 | assert!( 202 | h264.is_partition_head(&single_nalu), 203 | "single nalu must be a partition head" 204 | ); 205 | 206 | let stapa_nalu = Bytes::from_static(&[STAPA_NALU_TYPE, 0]); 207 | assert!( 208 | h264.is_partition_head(&stapa_nalu), 209 | "stapa nalu must be a partition head" 210 | ); 211 | 212 | let fua_start_nalu = Bytes::from_static(&[FUA_NALU_TYPE, FU_START_BITMASK]); 213 | assert!( 214 | h264.is_partition_head(&fua_start_nalu), 215 | "fua start nalu must be a partition head" 216 | ); 217 | 218 | let fua_end_nalu = Bytes::from_static(&[FUA_NALU_TYPE, FU_END_BITMASK]); 219 | assert!( 220 | !h264.is_partition_head(&fua_end_nalu), 221 | "fua end nalu must not be a partition head" 222 | ); 223 | 224 | let fub_start_nalu = Bytes::from_static(&[FUB_NALU_TYPE, FU_START_BITMASK]); 225 | assert!( 226 | h264.is_partition_head(&fub_start_nalu), 227 | "fub start nalu must be a partition head" 228 | ); 229 | 230 | let fub_end_nalu = Bytes::from_static(&[FUB_NALU_TYPE, FU_END_BITMASK]); 231 | assert!( 232 | !h264.is_partition_head(&fub_end_nalu), 233 | "fub end nalu must not be a partition head" 234 | ); 235 | 236 | Ok(()) 237 | } 238 | 239 | #[test] 240 | fn test_h264_payloader_payload_sps_and_pps_handling() -> Result<()> { 241 | let mut pck = H264Payloader::default(); 242 | let expected = vec![ 243 | Bytes::from_static(&[ 244 | 0x78, 0x00, 0x03, 0x07, 0x00, 0x01, 0x00, 0x03, 0x08, 0x02, 0x03, 245 | ]), 246 | Bytes::from_static(&[0x05, 0x04, 0x05]), 247 | ]; 248 | 249 | // When packetizing SPS and PPS are emitted with following NALU 250 | let res = pck.payload(1500, &Bytes::from_static(&[0x07, 0x00, 0x01]))?; 251 | assert!(res.is_empty(), "Generated payload should be empty"); 252 | 253 | let res = pck.payload(1500, &Bytes::from_static(&[0x08, 0x02, 0x03]))?; 254 | assert!(res.is_empty(), "Generated payload should be empty"); 255 | 256 | let actual = pck.payload(1500, &Bytes::from_static(&[0x05, 0x04, 0x05]))?; 257 | assert_eq!(actual, expected, "SPS and PPS aren't packed together"); 258 | 259 | Ok(()) 260 | } 261 | -------------------------------------------------------------------------------- /src/codecs/h264/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod h264_test; 3 | 4 | use crate::{ 5 | error::{Error, Result}, 6 | packetizer::{Depacketizer, Payloader}, 7 | }; 8 | 9 | use bytes::{BufMut, Bytes, BytesMut}; 10 | 11 | /// H264Payloader payloads H264 packets 12 | #[derive(Default, Debug, Clone)] 13 | pub struct H264Payloader { 14 | sps_nalu: Option, 15 | pps_nalu: Option, 16 | } 17 | 18 | pub const STAPA_NALU_TYPE: u8 = 24; 19 | pub const FUA_NALU_TYPE: u8 = 28; 20 | pub const FUB_NALU_TYPE: u8 = 29; 21 | pub const SPS_NALU_TYPE: u8 = 7; 22 | pub const PPS_NALU_TYPE: u8 = 8; 23 | pub const AUD_NALU_TYPE: u8 = 9; 24 | pub const FILLER_NALU_TYPE: u8 = 12; 25 | 26 | pub const FUA_HEADER_SIZE: usize = 2; 27 | pub const STAPA_HEADER_SIZE: usize = 1; 28 | pub const STAPA_NALU_LENGTH_SIZE: usize = 2; 29 | 30 | pub const NALU_TYPE_BITMASK: u8 = 0x1F; 31 | pub const NALU_REF_IDC_BITMASK: u8 = 0x60; 32 | pub const FU_START_BITMASK: u8 = 0x80; 33 | pub const FU_END_BITMASK: u8 = 0x40; 34 | 35 | pub const OUTPUT_STAP_AHEADER: u8 = 0x78; 36 | 37 | pub static ANNEXB_NALUSTART_CODE: Bytes = Bytes::from_static(&[0x00, 0x00, 0x00, 0x01]); 38 | 39 | impl H264Payloader { 40 | fn next_ind(nalu: &Bytes, start: usize) -> (isize, isize) { 41 | let mut zero_count = 0; 42 | 43 | for (i, &b) in nalu[start..].iter().enumerate() { 44 | if b == 0 { 45 | zero_count += 1; 46 | continue; 47 | } else if b == 1 && zero_count >= 2 { 48 | return ((start + i - zero_count) as isize, zero_count as isize + 1); 49 | } 50 | zero_count = 0 51 | } 52 | (-1, -1) 53 | } 54 | 55 | fn emit(&mut self, nalu: &Bytes, mtu: usize, payloads: &mut Vec) { 56 | if nalu.is_empty() { 57 | return; 58 | } 59 | 60 | let nalu_type = nalu[0] & NALU_TYPE_BITMASK; 61 | let nalu_ref_idc = nalu[0] & NALU_REF_IDC_BITMASK; 62 | 63 | if nalu_type == AUD_NALU_TYPE || nalu_type == FILLER_NALU_TYPE { 64 | return; 65 | } else if nalu_type == SPS_NALU_TYPE { 66 | self.sps_nalu = Some(nalu.clone()); 67 | return; 68 | } else if nalu_type == PPS_NALU_TYPE { 69 | self.pps_nalu = Some(nalu.clone()); 70 | return; 71 | } else if let (Some(sps_nalu), Some(pps_nalu)) = (&self.sps_nalu, &self.pps_nalu) { 72 | // Pack current NALU with SPS and PPS as STAP-A 73 | let sps_len = (sps_nalu.len() as u16).to_be_bytes(); 74 | let pps_len = (pps_nalu.len() as u16).to_be_bytes(); 75 | 76 | let mut stap_a_nalu = Vec::with_capacity(1 + 2 + sps_nalu.len() + 2 + pps_nalu.len()); 77 | stap_a_nalu.push(OUTPUT_STAP_AHEADER); 78 | stap_a_nalu.extend(sps_len); 79 | stap_a_nalu.extend_from_slice(sps_nalu); 80 | stap_a_nalu.extend(pps_len); 81 | stap_a_nalu.extend_from_slice(pps_nalu); 82 | if stap_a_nalu.len() <= mtu { 83 | payloads.push(Bytes::from(stap_a_nalu)); 84 | } 85 | } 86 | 87 | if self.sps_nalu.is_some() && self.pps_nalu.is_some() { 88 | self.sps_nalu = None; 89 | self.pps_nalu = None; 90 | } 91 | 92 | // Single NALU 93 | if nalu.len() <= mtu { 94 | payloads.push(nalu.clone()); 95 | return; 96 | } 97 | 98 | // FU-A 99 | let max_fragment_size = mtu as isize - FUA_HEADER_SIZE as isize; 100 | 101 | // The FU payload consists of fragments of the payload of the fragmented 102 | // NAL unit so that if the fragmentation unit payloads of consecutive 103 | // FUs are sequentially concatenated, the payload of the fragmented NAL 104 | // unit can be reconstructed. The NAL unit type octet of the fragmented 105 | // NAL unit is not included as such in the fragmentation unit payload, 106 | // but rather the information of the NAL unit type octet of the 107 | // fragmented NAL unit is conveyed in the F and NRI fields of the FU 108 | // indicator octet of the fragmentation unit and in the type field of 109 | // the FU header. An FU payload MAY have any number of octets and MAY 110 | // be empty. 111 | 112 | let nalu_data = nalu; 113 | // According to the RFC, the first octet is skipped due to redundant information 114 | let mut nalu_data_index = 1; 115 | let nalu_data_length = nalu.len() as isize - nalu_data_index; 116 | let mut nalu_data_remaining = nalu_data_length; 117 | 118 | if std::cmp::min(max_fragment_size, nalu_data_remaining) <= 0 { 119 | return; 120 | } 121 | 122 | while nalu_data_remaining > 0 { 123 | let current_fragment_size = std::cmp::min(max_fragment_size, nalu_data_remaining); 124 | //out: = make([]byte, fuaHeaderSize + currentFragmentSize) 125 | let mut out = BytesMut::with_capacity(FUA_HEADER_SIZE + current_fragment_size as usize); 126 | // +---------------+ 127 | // |0|1|2|3|4|5|6|7| 128 | // +-+-+-+-+-+-+-+-+ 129 | // |F|NRI| Type | 130 | // +---------------+ 131 | let b0 = FUA_NALU_TYPE | nalu_ref_idc; 132 | out.put_u8(b0); 133 | 134 | // +---------------+ 135 | //|0|1|2|3|4|5|6|7| 136 | //+-+-+-+-+-+-+-+-+ 137 | //|S|E|R| Type | 138 | //+---------------+ 139 | 140 | let mut b1 = nalu_type; 141 | if nalu_data_remaining == nalu_data_length { 142 | // Set start bit 143 | b1 |= 1 << 7; 144 | } else if nalu_data_remaining - current_fragment_size == 0 { 145 | // Set end bit 146 | b1 |= 1 << 6; 147 | } 148 | out.put_u8(b1); 149 | 150 | out.put( 151 | &nalu_data 152 | [nalu_data_index as usize..(nalu_data_index + current_fragment_size) as usize], 153 | ); 154 | payloads.push(out.freeze()); 155 | 156 | nalu_data_remaining -= current_fragment_size; 157 | nalu_data_index += current_fragment_size; 158 | } 159 | } 160 | } 161 | 162 | impl Payloader for H264Payloader { 163 | /// Payload fragments a H264 packet across one or more byte arrays 164 | fn payload(&mut self, mtu: usize, payload: &Bytes) -> Result> { 165 | if payload.is_empty() || mtu == 0 { 166 | return Ok(vec![]); 167 | } 168 | 169 | let mut payloads = vec![]; 170 | 171 | let (mut next_ind_start, mut next_ind_len) = H264Payloader::next_ind(payload, 0); 172 | if next_ind_start == -1 { 173 | self.emit(payload, mtu, &mut payloads); 174 | } else { 175 | while next_ind_start != -1 { 176 | let prev_start = (next_ind_start + next_ind_len) as usize; 177 | let (next_ind_start2, next_ind_len2) = H264Payloader::next_ind(payload, prev_start); 178 | next_ind_start = next_ind_start2; 179 | next_ind_len = next_ind_len2; 180 | if next_ind_start != -1 { 181 | self.emit( 182 | &payload.slice(prev_start..next_ind_start as usize), 183 | mtu, 184 | &mut payloads, 185 | ); 186 | } else { 187 | // Emit until end of stream, no end indicator found 188 | self.emit(&payload.slice(prev_start..), mtu, &mut payloads); 189 | } 190 | } 191 | } 192 | 193 | Ok(payloads) 194 | } 195 | 196 | fn clone_to(&self) -> Box { 197 | Box::new(self.clone()) 198 | } 199 | } 200 | 201 | /// H264Packet represents the H264 header that is stored in the payload of an RTP Packet 202 | #[derive(PartialEq, Eq, Debug, Default, Clone)] 203 | pub struct H264Packet { 204 | pub is_avc: bool, 205 | fua_buffer: Option, 206 | } 207 | 208 | impl Depacketizer for H264Packet { 209 | /// depacketize parses the passed byte slice and stores the result in the H264Packet this method is called upon 210 | fn depacketize(&mut self, packet: &Bytes) -> Result { 211 | if packet.len() <= 2 { 212 | return Err(Error::ErrShortPacket); 213 | } 214 | 215 | let mut payload = BytesMut::new(); 216 | 217 | // NALU Types 218 | // https://tools.ietf.org/html/rfc6184#section-5.4 219 | let b0 = packet[0]; 220 | let nalu_type = b0 & NALU_TYPE_BITMASK; 221 | 222 | match nalu_type { 223 | 1..=23 => { 224 | if self.is_avc { 225 | payload.put_u32(packet.len() as u32); 226 | } else { 227 | payload.put(&*ANNEXB_NALUSTART_CODE); 228 | } 229 | payload.put(&*packet.clone()); 230 | Ok(payload.freeze()) 231 | } 232 | STAPA_NALU_TYPE => { 233 | let mut curr_offset = STAPA_HEADER_SIZE; 234 | while curr_offset < packet.len() { 235 | let nalu_size = 236 | ((packet[curr_offset] as usize) << 8) | packet[curr_offset + 1] as usize; 237 | curr_offset += STAPA_NALU_LENGTH_SIZE; 238 | 239 | if packet.len() < curr_offset + nalu_size { 240 | return Err(Error::StapASizeLargerThanBuffer( 241 | nalu_size, 242 | packet.len() - curr_offset, 243 | )); 244 | } 245 | 246 | if self.is_avc { 247 | payload.put_u32(nalu_size as u32); 248 | } else { 249 | payload.put(&*ANNEXB_NALUSTART_CODE); 250 | } 251 | payload.put(&*packet.slice(curr_offset..curr_offset + nalu_size)); 252 | curr_offset += nalu_size; 253 | } 254 | 255 | Ok(payload.freeze()) 256 | } 257 | FUA_NALU_TYPE => { 258 | if packet.len() < FUA_HEADER_SIZE as usize { 259 | return Err(Error::ErrShortPacket); 260 | } 261 | 262 | if self.fua_buffer.is_none() { 263 | self.fua_buffer = Some(BytesMut::new()); 264 | } 265 | 266 | if let Some(fua_buffer) = &mut self.fua_buffer { 267 | fua_buffer.put(&*packet.slice(FUA_HEADER_SIZE as usize..)); 268 | } 269 | 270 | let b1 = packet[1]; 271 | if b1 & FU_END_BITMASK != 0 { 272 | let nalu_ref_idc = b0 & NALU_REF_IDC_BITMASK; 273 | let fragmented_nalu_type = b1 & NALU_TYPE_BITMASK; 274 | 275 | if let Some(fua_buffer) = self.fua_buffer.take() { 276 | if self.is_avc { 277 | payload.put_u32((fua_buffer.len() + 1) as u32); 278 | } else { 279 | payload.put(&*ANNEXB_NALUSTART_CODE); 280 | } 281 | payload.put_u8(nalu_ref_idc | fragmented_nalu_type); 282 | payload.put(fua_buffer); 283 | } 284 | 285 | Ok(payload.freeze()) 286 | } else { 287 | Ok(Bytes::new()) 288 | } 289 | } 290 | _ => Err(Error::NaluTypeIsNotHandled(nalu_type)), 291 | } 292 | } 293 | 294 | /// is_partition_head checks if this is the head of a packetized nalu stream. 295 | fn is_partition_head(&self, payload: &Bytes) -> bool { 296 | if payload.len() < 2 { 297 | return false; 298 | } 299 | 300 | if payload[0] & NALU_TYPE_BITMASK == FUA_NALU_TYPE 301 | || payload[0] & NALU_TYPE_BITMASK == FUB_NALU_TYPE 302 | { 303 | (payload[1] & FU_START_BITMASK) != 0 304 | } else { 305 | true 306 | } 307 | } 308 | 309 | fn is_partition_tail(&self, marker: bool, _payload: &Bytes) -> bool { 310 | marker 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /src/codecs/h265/h265_test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[test] 4 | fn test_h265_nalu_header() -> Result<()> { 5 | #[derive(Default)] 6 | struct TestType { 7 | raw_header: Bytes, 8 | 9 | fbit: bool, 10 | typ: u8, 11 | layer_id: u8, 12 | tid: u8, 13 | 14 | is_ap: bool, 15 | is_fu: bool, 16 | is_paci: bool, 17 | } 18 | 19 | let tests = vec![ 20 | // fbit 21 | TestType { 22 | raw_header: Bytes::from_static(&[0x80, 0x00]), 23 | typ: 0, 24 | layer_id: 0, 25 | tid: 0, 26 | fbit: true, 27 | ..Default::default() 28 | }, 29 | // VPS_NUT 30 | TestType { 31 | raw_header: Bytes::from_static(&[0x40, 0x01]), 32 | typ: 32, 33 | layer_id: 0, 34 | tid: 1, 35 | ..Default::default() 36 | }, 37 | // SPS_NUT 38 | TestType { 39 | raw_header: Bytes::from_static(&[0x42, 0x01]), 40 | typ: 33, 41 | layer_id: 0, 42 | tid: 1, 43 | ..Default::default() 44 | }, 45 | // PPS_NUT 46 | TestType { 47 | raw_header: Bytes::from_static(&[0x44, 0x01]), 48 | typ: 34, 49 | layer_id: 0, 50 | tid: 1, 51 | ..Default::default() 52 | }, 53 | // PREFIX_SEI_NUT 54 | TestType { 55 | raw_header: Bytes::from_static(&[0x4e, 0x01]), 56 | typ: 39, 57 | layer_id: 0, 58 | tid: 1, 59 | ..Default::default() 60 | }, 61 | // Fragmentation Unit 62 | TestType { 63 | raw_header: Bytes::from_static(&[0x62, 0x01]), 64 | typ: H265NALU_FRAGMENTATION_UNIT_TYPE, 65 | layer_id: 0, 66 | tid: 1, 67 | is_fu: true, 68 | ..Default::default() 69 | }, 70 | ]; 71 | 72 | for cur in tests { 73 | let header = H265NALUHeader::new(cur.raw_header[0], cur.raw_header[1]); 74 | 75 | assert_eq!(header.f(), cur.fbit, "invalid F bit"); 76 | assert_eq!(header.nalu_type(), cur.typ, "invalid type"); 77 | 78 | // For any type < 32, NAL is a VLC NAL unit. 79 | assert_eq!( 80 | header.is_type_vcl_unit(), 81 | (header.nalu_type() < 32), 82 | "invalid IsTypeVCLUnit" 83 | ); 84 | assert_eq!( 85 | header.is_aggregation_packet(), 86 | cur.is_ap, 87 | "invalid type (aggregation packet)" 88 | ); 89 | assert_eq!( 90 | header.is_fragmentation_unit(), 91 | cur.is_fu, 92 | "invalid type (fragmentation unit)" 93 | ); 94 | assert_eq!(header.is_paci_packet(), cur.is_paci, "invalid type (PACI)"); 95 | assert_eq!(header.layer_id(), cur.layer_id, "invalid layer_id"); 96 | assert_eq!(header.tid(), cur.tid, "invalid tid"); 97 | } 98 | 99 | Ok(()) 100 | } 101 | 102 | #[test] 103 | fn test_h265_fu_header() -> Result<()> { 104 | #[derive(Default)] 105 | struct TestType { 106 | header: H265FragmentationUnitHeader, 107 | 108 | s: bool, 109 | e: bool, 110 | typ: u8, 111 | } 112 | 113 | let tests = vec![ 114 | // Start | IDR_W_RADL 115 | TestType { 116 | header: H265FragmentationUnitHeader(0x93), 117 | s: true, 118 | e: false, 119 | typ: 19, 120 | }, 121 | // Continuation | IDR_W_RADL 122 | TestType { 123 | header: H265FragmentationUnitHeader(0x13), 124 | s: false, 125 | e: false, 126 | typ: 19, 127 | }, 128 | // End | IDR_W_RADL 129 | TestType { 130 | header: H265FragmentationUnitHeader(0x53), 131 | s: false, 132 | e: true, 133 | typ: 19, 134 | }, 135 | // Start | TRAIL_R 136 | TestType { 137 | header: H265FragmentationUnitHeader(0x81), 138 | s: true, 139 | e: false, 140 | typ: 1, 141 | }, 142 | // Continuation | TRAIL_R 143 | TestType { 144 | header: H265FragmentationUnitHeader(0x01), 145 | s: false, 146 | e: false, 147 | typ: 1, 148 | }, 149 | // End | TRAIL_R 150 | TestType { 151 | header: H265FragmentationUnitHeader(0x41), 152 | s: false, 153 | e: true, 154 | typ: 1, 155 | }, 156 | ]; 157 | 158 | for cur in tests { 159 | assert_eq!(cur.header.s(), cur.s, "invalid s field"); 160 | assert_eq!(cur.header.e(), cur.e, "invalid e field"); 161 | assert_eq!(cur.header.fu_type(), cur.typ, "invalid FuType field"); 162 | } 163 | 164 | Ok(()) 165 | } 166 | 167 | #[test] 168 | fn test_h265_single_nalunit_packet() -> Result<()> { 169 | #[derive(Default)] 170 | struct TestType { 171 | raw: Bytes, 172 | with_donl: bool, 173 | expected_packet: Option, 174 | expected_err: Option, 175 | } 176 | 177 | let tests = vec![ 178 | TestType { 179 | raw: Bytes::from_static(&[]), 180 | expected_err: Some(Error::ErrShortPacket), 181 | ..Default::default() 182 | }, 183 | TestType { 184 | raw: Bytes::from_static(&[0x62]), 185 | expected_err: Some(Error::ErrShortPacket), 186 | ..Default::default() 187 | }, 188 | TestType { 189 | raw: Bytes::from_static(&[0x62, 0x01, 0x93]), 190 | expected_err: Some(Error::ErrShortPacket), 191 | ..Default::default() 192 | }, 193 | // FBit enabled in H265NALUHeader 194 | TestType { 195 | raw: Bytes::from_static(&[0x80, 0x01, 0x93, 0xaf, 0xaf, 0xaf, 0xaf]), 196 | expected_err: Some(Error::ErrH265CorruptedPacket), 197 | ..Default::default() 198 | }, 199 | // Type '49' in H265NALUHeader 200 | TestType { 201 | raw: Bytes::from_static(&[0x62, 0x01, 0x93, 0xaf, 0xaf, 0xaf, 0xaf]), 202 | expected_err: Some(Error::ErrInvalidH265PacketType), 203 | ..Default::default() 204 | }, 205 | // Type '50' in H265NALUHeader 206 | TestType { 207 | raw: Bytes::from_static(&[0x64, 0x01, 0x93, 0xaf, 0xaf, 0xaf, 0xaf]), 208 | expected_err: Some(Error::ErrInvalidH265PacketType), 209 | ..Default::default() 210 | }, 211 | TestType { 212 | raw: Bytes::from_static(&[0x01, 0x01, 0xab, 0xcd, 0xef]), 213 | expected_packet: Some(H265SingleNALUnitPacket { 214 | payload_header: H265NALUHeader::new(0x01, 0x01), 215 | payload: Bytes::from_static(&[0xab, 0xcd, 0xef]), 216 | ..Default::default() 217 | }), 218 | ..Default::default() 219 | }, 220 | // DONL, payload too small 221 | TestType { 222 | raw: Bytes::from_static(&[0x01, 0x01, 0x93, 0xaf]), 223 | expected_err: Some(Error::ErrShortPacket), 224 | with_donl: true, 225 | ..Default::default() 226 | }, 227 | TestType { 228 | raw: Bytes::from_static(&[0x01, 0x01, 0xaa, 0xbb, 0xcc]), 229 | expected_packet: Some(H265SingleNALUnitPacket { 230 | payload_header: H265NALUHeader::new(0x01, 0x01), 231 | donl: Some((0xaa << 8) | 0xbb), 232 | payload: Bytes::from_static(&[0xcc]), 233 | ..Default::default() 234 | }), 235 | with_donl: true, 236 | ..Default::default() 237 | }, 238 | ]; 239 | 240 | for cur in tests { 241 | let mut parsed = H265SingleNALUnitPacket::default(); 242 | if cur.with_donl { 243 | parsed.with_donl(cur.with_donl); 244 | } 245 | 246 | let result = parsed.depacketize(&cur.raw); 247 | 248 | if cur.expected_err.is_some() && result.is_ok() { 249 | assert!(false, "should error"); 250 | } else if cur.expected_err.is_none() && result.is_err() { 251 | assert!(false, "should not error"); 252 | } 253 | 254 | if let Some(expected_packet) = cur.expected_packet { 255 | assert_eq!( 256 | expected_packet.payload_header(), 257 | parsed.payload_header(), 258 | "invalid payload header" 259 | ); 260 | assert_eq!(expected_packet.donl(), parsed.donl(), "invalid DONL"); 261 | 262 | assert_eq!( 263 | expected_packet.payload(), 264 | parsed.payload(), 265 | "invalid payload" 266 | ); 267 | } 268 | } 269 | 270 | Ok(()) 271 | } 272 | 273 | #[test] 274 | fn test_h265_aggregation_packet() -> Result<()> { 275 | #[derive(Default)] 276 | struct TestType { 277 | raw: Bytes, 278 | with_donl: bool, 279 | expected_packet: Option, 280 | expected_err: Option, 281 | } 282 | 283 | let tests = vec![ 284 | TestType { 285 | raw: Bytes::from_static(&[]), 286 | expected_err: Some(Error::ErrShortPacket), 287 | ..Default::default() 288 | }, 289 | TestType { 290 | raw: Bytes::from_static(&[0x62]), 291 | expected_err: Some(Error::ErrShortPacket), 292 | ..Default::default() 293 | }, 294 | TestType { 295 | raw: Bytes::from_static(&[0x62, 0x01, 0x93]), 296 | expected_err: Some(Error::ErrShortPacket), 297 | ..Default::default() 298 | }, 299 | // FBit enabled in H265NALUHeader 300 | TestType { 301 | raw: Bytes::from_static(&[0x80, 0x01, 0x93, 0xaf, 0xaf, 0xaf, 0xaf]), 302 | expected_err: Some(Error::ErrH265CorruptedPacket), 303 | ..Default::default() 304 | }, 305 | // Type '48' in H265NALUHeader 306 | TestType { 307 | raw: Bytes::from_static(&[0xE0, 0x01, 0x93, 0xaf, 0xaf, 0xaf, 0xaf]), 308 | expected_err: Some(Error::ErrInvalidH265PacketType), 309 | ..Default::default() 310 | }, 311 | // Small payload 312 | TestType { 313 | raw: Bytes::from_static(&[0x60, 0x01, 0x00, 0x1]), 314 | expected_err: Some(Error::ErrShortPacket), 315 | ..Default::default() 316 | }, 317 | // Small payload 318 | TestType { 319 | raw: Bytes::from_static(&[0x60, 0x01, 0x00]), 320 | expected_err: Some(Error::ErrShortPacket), 321 | with_donl: true, 322 | ..Default::default() 323 | }, 324 | // Small payload 325 | TestType { 326 | raw: Bytes::from_static(&[0x60, 0x01, 0x00, 0x1]), 327 | expected_err: Some(Error::ErrShortPacket), 328 | with_donl: true, 329 | ..Default::default() 330 | }, 331 | // Small payload 332 | TestType { 333 | raw: Bytes::from_static(&[0x60, 0x01, 0x00, 0x01, 0x02]), 334 | expected_err: Some(Error::ErrShortPacket), 335 | with_donl: true, 336 | ..Default::default() 337 | }, 338 | // Single Aggregation Unit 339 | TestType { 340 | raw: Bytes::from_static(&[0x60, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00]), 341 | expected_err: Some(Error::ErrShortPacket), 342 | with_donl: true, 343 | ..Default::default() 344 | }, 345 | // Incomplete second Aggregation Unit 346 | TestType { 347 | raw: Bytes::from_static(&[ 348 | 0x60, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, // DONL 349 | 0x00, 350 | ]), 351 | expected_err: Some(Error::ErrShortPacket), 352 | with_donl: true, 353 | ..Default::default() 354 | }, 355 | // Incomplete second Aggregation Unit 356 | TestType { 357 | raw: Bytes::from_static(&[ 358 | 0x60, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 359 | // DONL, NAL Unit size (2 bytes) 360 | 0x00, 0x55, 0x55, 361 | ]), 362 | expected_err: Some(Error::ErrShortPacket), 363 | with_donl: true, 364 | ..Default::default() 365 | }, 366 | // Valid Second Aggregation Unit 367 | TestType { 368 | raw: Bytes::from_static(&[ 369 | 0x60, 0x01, 0xcc, 0xdd, 0x00, 0x02, 0xff, 0xee, 370 | // DONL, NAL Unit size (2 bytes), Payload 371 | 0x77, 0x00, 0x01, 0xaa, 372 | ]), 373 | with_donl: true, 374 | expected_packet: Some(H265AggregationPacket { 375 | first_unit: Some(H265AggregationUnitFirst { 376 | donl: Some(0xccdd), 377 | nal_unit_size: 2, 378 | nal_unit: Bytes::from_static(&[0xff, 0xee]), 379 | }), 380 | other_units: vec![H265AggregationUnit { 381 | dond: Some(0x77), 382 | nal_unit_size: 1, 383 | nal_unit: Bytes::from_static(&[0xaa]), 384 | }], 385 | might_need_donl: false, 386 | }), 387 | ..Default::default() 388 | }, 389 | ]; 390 | 391 | for cur in tests { 392 | let mut parsed = H265AggregationPacket::default(); 393 | if cur.with_donl { 394 | parsed.with_donl(cur.with_donl); 395 | } 396 | 397 | let result = parsed.depacketize(&cur.raw); 398 | 399 | if cur.expected_err.is_some() && result.is_ok() { 400 | assert!(false, "should error"); 401 | } else if cur.expected_err.is_none() && result.is_err() { 402 | assert!(false, "should not error"); 403 | } 404 | 405 | if let Some(expected_packet) = cur.expected_packet { 406 | if let (Some(first_unit), Some(parsed_first_unit)) = 407 | (expected_packet.first_unit(), parsed.first_unit()) 408 | { 409 | assert_eq!( 410 | parsed_first_unit.nal_unit_size, first_unit.nal_unit_size, 411 | "invalid first unit NALUSize" 412 | ); 413 | assert_eq!( 414 | first_unit.donl(), 415 | parsed_first_unit.donl(), 416 | "invalid first unit DONL" 417 | ); 418 | assert_eq!( 419 | first_unit.nal_unit(), 420 | parsed_first_unit.nal_unit(), 421 | "invalid first unit NalUnit" 422 | ); 423 | } 424 | 425 | assert_eq!( 426 | expected_packet.other_units().len(), 427 | parsed.other_units().len(), 428 | "number of other units mismatch" 429 | ); 430 | 431 | for ndx in 0..expected_packet.other_units().len() { 432 | assert_eq!( 433 | parsed.other_units()[ndx].nalu_size(), 434 | expected_packet.other_units()[ndx].nalu_size(), 435 | "invalid unit NALUSize" 436 | ); 437 | 438 | assert_eq!( 439 | expected_packet.other_units()[ndx].dond(), 440 | parsed.other_units()[ndx].dond(), 441 | "invalid unit DOND" 442 | ); 443 | 444 | assert_eq!( 445 | expected_packet.other_units()[ndx].nal_unit(), 446 | parsed.other_units()[ndx].nal_unit(), 447 | "invalid first unit NalUnit" 448 | ); 449 | } 450 | 451 | assert_eq!( 452 | expected_packet.other_units(), 453 | parsed.other_units(), 454 | "invalid payload" 455 | ); 456 | } 457 | } 458 | 459 | Ok(()) 460 | } 461 | 462 | #[test] 463 | fn test_h265_fragmentation_unit_packet() -> Result<()> { 464 | #[derive(Default)] 465 | struct TestType { 466 | raw: Bytes, 467 | with_donl: bool, 468 | expected_fu: Option, 469 | expected_err: Option, 470 | } 471 | let tests = vec![ 472 | TestType { 473 | raw: Bytes::from_static(&[]), 474 | expected_err: Some(Error::ErrShortPacket), 475 | ..Default::default() 476 | }, 477 | TestType { 478 | raw: Bytes::from_static(&[0x62]), 479 | expected_err: Some(Error::ErrShortPacket), 480 | ..Default::default() 481 | }, 482 | TestType { 483 | raw: Bytes::from_static(&[0x62, 0x01]), 484 | expected_err: Some(Error::ErrShortPacket), 485 | ..Default::default() 486 | }, 487 | TestType { 488 | raw: Bytes::from_static(&[0x62, 0x01, 0x93]), 489 | expected_err: Some(Error::ErrShortPacket), 490 | ..Default::default() 491 | }, 492 | // FBit enabled in H265NALUHeader 493 | TestType { 494 | raw: Bytes::from_static(&[0x80, 0x01, 0x93, 0xaf]), 495 | expected_err: Some(Error::ErrH265CorruptedPacket), 496 | ..Default::default() 497 | }, 498 | // Type not '49' in H265NALUHeader 499 | TestType { 500 | raw: Bytes::from_static(&[0x40, 0x01, 0x93, 0xaf]), 501 | expected_err: Some(Error::ErrInvalidH265PacketType), 502 | ..Default::default() 503 | }, 504 | TestType { 505 | raw: Bytes::from_static(&[0x62, 0x01, 0x93, 0xaf]), 506 | expected_fu: Some(H265FragmentationUnitPacket { 507 | payload_header: H265NALUHeader::new(0x62, 0x01), 508 | fu_header: H265FragmentationUnitHeader(0x93), 509 | donl: None, 510 | payload: Bytes::from_static(&[0xaf]), 511 | might_need_donl: false, 512 | }), 513 | ..Default::default() 514 | }, 515 | TestType { 516 | raw: Bytes::from_static(&[0x62, 0x01, 0x93, 0xcc]), 517 | with_donl: true, 518 | expected_err: Some(Error::ErrShortPacket), 519 | ..Default::default() 520 | }, 521 | TestType { 522 | raw: Bytes::from_static(&[0x62, 0x01, 0x93, 0xcc, 0xdd, 0xaf, 0x0d, 0x5a]), 523 | with_donl: true, 524 | expected_fu: Some(H265FragmentationUnitPacket { 525 | payload_header: H265NALUHeader::new(0x62, 0x01), 526 | fu_header: H265FragmentationUnitHeader(0x93), 527 | donl: Some((0xcc << 8) | 0xdd), 528 | payload: Bytes::from_static(&[0xaf, 0x0d, 0x5a]), 529 | might_need_donl: false, 530 | }), 531 | ..Default::default() 532 | }, 533 | ]; 534 | 535 | for cur in tests { 536 | let mut parsed = H265FragmentationUnitPacket::default(); 537 | if cur.with_donl { 538 | parsed.with_donl(cur.with_donl); 539 | } 540 | 541 | let result = parsed.depacketize(&cur.raw); 542 | 543 | if cur.expected_err.is_some() && result.is_ok() { 544 | assert!(false, "should error"); 545 | } else if cur.expected_err.is_none() && result.is_err() { 546 | assert!(false, "should not error"); 547 | } 548 | 549 | if let Some(expected_fu) = &cur.expected_fu { 550 | assert_eq!( 551 | parsed.payload_header(), 552 | expected_fu.payload_header(), 553 | "invalid payload header" 554 | ); 555 | assert_eq!( 556 | parsed.fu_header(), 557 | expected_fu.fu_header(), 558 | "invalid FU header" 559 | ); 560 | assert_eq!(parsed.donl(), expected_fu.donl(), "invalid DONL"); 561 | assert_eq!(parsed.payload(), expected_fu.payload(), "invalid Payload"); 562 | } 563 | } 564 | 565 | Ok(()) 566 | } 567 | 568 | #[test] 569 | fn test_h265_temporal_scalability_control_information() -> Result<()> { 570 | #[derive(Default)] 571 | struct TestType { 572 | value: H265TSCI, 573 | expected_tl0picidx: u8, 574 | expected_irap_pic_id: u8, 575 | expected_s: bool, 576 | expected_e: bool, 577 | expected_res: u8, 578 | } 579 | 580 | let tests = vec![ 581 | TestType { 582 | value: H265TSCI(((0xCA) << 24) | ((0xFE) << 16)), 583 | expected_tl0picidx: 0xCA, 584 | expected_irap_pic_id: 0xFE, 585 | ..Default::default() 586 | }, 587 | TestType { 588 | value: H265TSCI((1) << 15), 589 | expected_s: true, 590 | ..Default::default() 591 | }, 592 | TestType { 593 | value: H265TSCI((1) << 14), 594 | expected_e: true, 595 | ..Default::default() 596 | }, 597 | TestType { 598 | value: H265TSCI((0x0A) << 8), 599 | expected_res: 0x0A, 600 | ..Default::default() 601 | }, 602 | // Sets RES, and force sets S and E to 0. 603 | TestType { 604 | value: H265TSCI(((0xAA) << 8) & (u32::MAX ^ ((1) << 15)) & (u32::MAX ^ ((1) << 14))), 605 | expected_res: 0xAA & 0b00111111, 606 | ..Default::default() 607 | }, 608 | ]; 609 | 610 | for cur in tests { 611 | assert_eq!( 612 | cur.value.tl0picidx(), 613 | cur.expected_tl0picidx, 614 | "invalid TL0PICIDX" 615 | ); 616 | assert_eq!( 617 | cur.value.irap_pic_id(), 618 | cur.expected_irap_pic_id, 619 | "invalid IrapPicID" 620 | ); 621 | assert_eq!(cur.value.s(), cur.expected_s, "invalid S"); 622 | assert_eq!(cur.value.e(), cur.expected_e, "invalid E"); 623 | assert_eq!(cur.value.res(), cur.expected_res, "invalid RES"); 624 | } 625 | 626 | Ok(()) 627 | } 628 | 629 | #[test] 630 | fn test_h265_paci_packet() -> Result<()> { 631 | #[derive(Default)] 632 | struct TestType { 633 | raw: Bytes, 634 | expected_fu: Option, 635 | expected_err: Option, 636 | } 637 | 638 | let tests = vec![ 639 | TestType { 640 | raw: Bytes::from_static(&[]), 641 | expected_err: Some(Error::ErrShortPacket), 642 | ..Default::default() 643 | }, 644 | TestType { 645 | raw: Bytes::from_static(&[0x62, 0x01, 0x93]), 646 | expected_err: Some(Error::ErrShortPacket), 647 | ..Default::default() 648 | }, 649 | // FBit enabled in H265NALUHeader 650 | TestType { 651 | raw: Bytes::from_static(&[0x80, 0x01, 0x93, 0xaf, 0xaf, 0xaf, 0xaf]), 652 | expected_err: Some(Error::ErrH265CorruptedPacket), 653 | ..Default::default() 654 | }, 655 | // Type not '50' in H265NALUHeader 656 | TestType { 657 | raw: Bytes::from_static(&[0x40, 0x01, 0x93, 0xaf, 0xaf, 0xaf, 0xaf]), 658 | expected_err: Some(Error::ErrInvalidH265PacketType), 659 | ..Default::default() 660 | }, 661 | // Invalid header extension size 662 | TestType { 663 | raw: Bytes::from_static(&[0x64, 0x01, 0x93, 0xaf, 0xaf, 0xaf, 0xaf]), 664 | expected_err: Some(Error::ErrInvalidH265PacketType), 665 | ..Default::default() 666 | }, 667 | // No Header Extension 668 | TestType { 669 | raw: Bytes::from_static(&[0x64, 0x01, 0x64, 0x00, 0xab, 0xcd, 0xef]), 670 | expected_fu: Some(H265PACIPacket { 671 | payload_header: H265NALUHeader::new(0x64, 0x01), 672 | paci_header_fields: ((0x64) << 8), 673 | phes: Bytes::from_static(&[]), 674 | payload: Bytes::from_static(&[0xab, 0xcd, 0xef]), 675 | }), 676 | ..Default::default() 677 | }, 678 | // Header Extension 1 byte 679 | TestType { 680 | raw: Bytes::from_static(&[0x64, 0x01, 0x64, 0x10, 0xff, 0xab, 0xcd, 0xef]), 681 | expected_fu: Some(H265PACIPacket { 682 | payload_header: H265NALUHeader::new(0x64, 0x01), 683 | paci_header_fields: ((0x64) << 8) | (0x10), 684 | phes: Bytes::from_static(&[0xff]), 685 | payload: Bytes::from_static(&[0xab, 0xcd, 0xef]), 686 | }), 687 | ..Default::default() 688 | }, 689 | // Header Extension TSCI 690 | TestType { 691 | raw: Bytes::from_static(&[ 692 | 0x64, 0x01, 0x64, 0b00111000, 0xaa, 0xbb, 0x80, 0xab, 0xcd, 0xef, 693 | ]), 694 | expected_fu: Some(H265PACIPacket { 695 | payload_header: H265NALUHeader::new(0x64, 0x01), 696 | paci_header_fields: ((0x64) << 8) | (0b00111000), 697 | phes: Bytes::from_static(&[0xaa, 0xbb, 0x80]), 698 | payload: Bytes::from_static(&[0xab, 0xcd, 0xef]), 699 | }), 700 | ..Default::default() 701 | }, 702 | ]; 703 | 704 | for cur in tests { 705 | let mut parsed = H265PACIPacket::default(); 706 | 707 | let result = parsed.depacketize(&cur.raw); 708 | 709 | if cur.expected_err.is_some() && result.is_ok() { 710 | assert!(false, "should error"); 711 | } else if cur.expected_err.is_none() && result.is_err() { 712 | assert!(false, "should not error"); 713 | } 714 | 715 | if let Some(expected_fu) = &cur.expected_fu { 716 | assert_eq!( 717 | expected_fu.payload_header(), 718 | parsed.payload_header(), 719 | "invalid PayloadHeader" 720 | ); 721 | assert_eq!(expected_fu.a(), parsed.a(), "invalid A"); 722 | assert_eq!(expected_fu.ctype(), parsed.ctype(), "invalid CType"); 723 | assert_eq!(expected_fu.phs_size(), parsed.phs_size(), "invalid PHSsize"); 724 | assert_eq!(expected_fu.f0(), parsed.f0(), "invalid F0"); 725 | assert_eq!(expected_fu.f1(), parsed.f1(), "invalid F1"); 726 | assert_eq!(expected_fu.f2(), parsed.f2(), "invalid F2"); 727 | assert_eq!(expected_fu.y(), parsed.y(), "invalid Y"); 728 | assert_eq!(expected_fu.phes(), parsed.phes(), "invalid PHES"); 729 | assert_eq!(expected_fu.payload(), parsed.payload(), "invalid Payload"); 730 | assert_eq!(expected_fu.tsci(), parsed.tsci(), "invalid TSCI"); 731 | } 732 | } 733 | 734 | Ok(()) 735 | } 736 | 737 | #[test] 738 | fn test_h265_packet() -> Result<()> { 739 | #[derive(Default)] 740 | struct TestType { 741 | raw: Bytes, 742 | with_donl: bool, 743 | expected_packet_type: Option, 744 | expected_err: Option, 745 | } 746 | let tests = vec![ 747 | TestType { 748 | raw: Bytes::from_static(&[]), 749 | expected_err: Some(Error::ErrShortPacket), 750 | ..Default::default() 751 | }, 752 | TestType { 753 | raw: Bytes::from_static(&[0x62, 0x01, 0x93]), 754 | expected_err: Some(Error::ErrShortPacket), 755 | ..Default::default() 756 | }, 757 | TestType { 758 | raw: Bytes::from_static(&[0x64, 0x01, 0x93, 0xaf]), 759 | expected_err: Some(Error::ErrShortPacket), 760 | ..Default::default() 761 | }, 762 | TestType { 763 | raw: Bytes::from_static(&[0x01, 0x01]), 764 | with_donl: true, 765 | expected_err: Some(Error::ErrShortPacket), 766 | ..Default::default() 767 | }, 768 | // FBit enabled in H265NALUHeader 769 | TestType { 770 | raw: Bytes::from_static(&[0x80, 0x01, 0x93, 0xaf, 0xaf, 0xaf, 0xaf]), 771 | expected_err: Some(Error::ErrH265CorruptedPacket), 772 | ..Default::default() 773 | }, 774 | // Valid H265SingleNALUnitPacket 775 | TestType { 776 | raw: Bytes::from_static(&[0x01, 0x01, 0xab, 0xcd, 0xef]), 777 | expected_packet_type: Some(H265Payload::H265SingleNALUnitPacket( 778 | H265SingleNALUnitPacket::default(), 779 | )), 780 | ..Default::default() 781 | }, 782 | // Invalid H265SingleNALUnitPacket 783 | TestType { 784 | raw: Bytes::from_static(&[0x01, 0x01, 0x93, 0xaf]), 785 | expected_err: Some(Error::ErrShortPacket), 786 | with_donl: true, 787 | ..Default::default() 788 | }, 789 | // Valid H265PACIPacket 790 | TestType { 791 | raw: Bytes::from_static(&[ 792 | 0x64, 0x01, 0x64, 0b00111000, 0xaa, 0xbb, 0x80, 0xab, 0xcd, 0xef, 793 | ]), 794 | expected_packet_type: Some(H265Payload::H265PACIPacket(H265PACIPacket::default())), 795 | ..Default::default() 796 | }, 797 | // Valid H265FragmentationUnitPacket 798 | TestType { 799 | raw: Bytes::from_static(&[0x62, 0x01, 0x93, 0xcc, 0xdd, 0xaf, 0x0d, 0x5a]), 800 | expected_packet_type: Some(H265Payload::H265FragmentationUnitPacket( 801 | H265FragmentationUnitPacket::default(), 802 | )), 803 | with_donl: true, 804 | ..Default::default() 805 | }, 806 | // Valid H265AggregationPacket 807 | TestType { 808 | raw: Bytes::from_static(&[ 809 | 0x60, 0x01, 0xcc, 0xdd, 0x00, 0x02, 0xff, 0xee, 0x77, 0x00, 0x01, 0xaa, 810 | ]), 811 | expected_packet_type: Some(H265Payload::H265AggregationPacket( 812 | H265AggregationPacket::default(), 813 | )), 814 | with_donl: true, 815 | ..Default::default() 816 | }, 817 | // Invalid H265AggregationPacket 818 | TestType { 819 | raw: Bytes::from_static(&[0x60, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00]), 820 | expected_err: Some(Error::ErrShortPacket), 821 | with_donl: true, 822 | ..Default::default() 823 | }, 824 | ]; 825 | 826 | for cur in tests { 827 | let mut pck = H265Packet::default(); 828 | if cur.with_donl { 829 | pck.with_donl(true); 830 | } 831 | 832 | let result = pck.depacketize(&cur.raw); 833 | 834 | if cur.expected_err.is_some() && result.is_ok() { 835 | assert!(false, "should error"); 836 | } else if cur.expected_err.is_none() && result.is_err() { 837 | assert!(false, "should not error"); 838 | } 839 | 840 | if cur.expected_err.is_some() { 841 | continue; 842 | } 843 | 844 | if let Some(expected_packet_type) = &cur.expected_packet_type { 845 | //TODO: assert_eq!(pck.packet(), expected_packet_type, "invalid packet type"); 846 | let pck_packet = pck.payload(); 847 | match (pck_packet, expected_packet_type) { 848 | ( 849 | &H265Payload::H265SingleNALUnitPacket(_), 850 | &H265Payload::H265SingleNALUnitPacket(_), 851 | ) => assert!(true), 852 | ( 853 | &H265Payload::H265FragmentationUnitPacket(_), 854 | &H265Payload::H265FragmentationUnitPacket(_), 855 | ) => assert!(true), 856 | ( 857 | &H265Payload::H265AggregationPacket(_), 858 | &H265Payload::H265AggregationPacket(_), 859 | ) => assert!(true), 860 | (&H265Payload::H265PACIPacket(_), &H265Payload::H265PACIPacket(_)) => { 861 | assert!(true) 862 | } 863 | _ => assert!(false), 864 | }; 865 | } 866 | } 867 | 868 | Ok(()) 869 | } 870 | 871 | #[test] 872 | fn test_h265_packet_real() -> Result<()> { 873 | // Tests decoding of real H265 payloads extracted from a Wireshark dump. 874 | let tests = vec![ 875 | b"\x40\x01\x0c\x01\xff\xff\x01\x60\x00\x00\x03\x00\xb0\x00\x00\x03\x00\x00\x03\x00\x7b\xac\x09".to_vec(), 876 | b"\x42\x01\x01\x01\x60\x00\x00\x03\x00\xb0\x00\x00\x03\x00\x00\x03\x00\x7b\xa0\x03\xc0\x80\x10\xe5\x8d\xae\x49\x32\xf4\xdc\x04\x04\x04\x02".to_vec(), 877 | b"\x44\x01\xc0\xf2\xf0\x3c\x90".to_vec(), 878 | b"\x4e\x01\xe5\x04\x61\x0c\x00\x00\x80".to_vec(), 879 | b"\x62\x01\x93\xaf\x0d\x5a\xfe\x67\x77\x29\xc0\x74\xf3\x57\x4c\x16\x94\xaa\x7c\x2a\x64\x5f\xe9\xa5\xb7\x2a\xa3\x95\x9d\x94\xa7\xb4\xd3\xc4\x4a\xb1\xb7\x69\xca\xbe\x75\xc5\x64\xa8\x97\x4b\x8a\xbf\x7e\xf0\x0f\xc3\x22\x60\x67\xab\xae\x96\xd6\x99\xca\x7a\x8d\x35\x93\x1a\x67\x60\xe7\xbe\x7e\x13\x95\x3c\xe0\x11\xc1\xc1\xa7\x48\xef\xf7\x7b\xb0\xeb\x35\x49\x81\x4e\x4e\x54\xf7\x31\x6a\x38\xa1\xa7\x0c\xd6\xbe\x3b\x25\xba\x08\x19\x0b\x49\xfd\x90\xbb\x73\x7a\x45\x8c\xb9\x73\x43\x04\xc5\x5f\xda\x0f\xd5\x70\x4c\x11\xee\x72\xb8\x6a\xb4\x95\x62\x64\xb6\x23\x14\x7e\xdb\x0e\xa5\x0f\x86\x31\xe4\xd1\x64\x56\x43\xf6\xb7\xe7\x1b\x93\x4a\xeb\xd0\xa6\xe3\x1f\xce\xda\x15\x67\x05\xb6\x77\x36\x8b\x27\x5b\xc6\xf2\x95\xb8\x2b\xcc\x9b\x0a\x03\x05\xbe\xc3\xd3\x85\xf5\x69\xb6\x19\x1f\x63\x2d\x8b\x65\x9e\xc3\x9d\xd2\x44\xb3\x7c\x86\x3b\xea\xa8\x5d\x02\xe5\x40\x03\x20\x76\x48\xff\xf6\x2b\x0d\x18\xd6\x4d\x49\x70\x1a\x5e\xb2\x89\xca\xec\x71\x41\x79\x4e\x94\x17\x0c\x57\x51\x55\x14\x61\x40\x46\x4b\x3e\x17\xb2\xc8\xbd\x1c\x06\x13\x91\x72\xf8\xc8\xfc\x6f\xb0\x30\x9a\xec\x3b\xa6\xc9\x33\x0b\xa5\xe5\xf4\x65\x7a\x29\x8b\x76\x62\x81\x12\xaf\x20\x4c\xd9\x21\x23\x9e\xeb\xc9\x0e\x5b\x29\x35\x7f\x41\xcd\xce\xa1\xc4\xbe\x01\x30\xb9\x11\xc3\xb1\xe4\xce\x45\xd2\x5c\xb3\x1e\x69\x78\xba\xb1\x72\xe4\x88\x54\xd8\x5d\xd0\xa8\x3a\x74\xad\xe5\xc7\xc1\x59\x7c\x78\x15\x26\x37\x3d\x50\xae\xb3\xa4\x5b\x6c\x7d\x65\x66\x85\x4d\x16\x9a\x67\x74\xad\x55\x32\x3a\x84\x85\x0b\x6a\xeb\x24\x97\xb4\x20\x4d\xca\x41\x61\x7a\xd1\x7b\x60\xdb\x7f\xd5\x61\x22\xcf\xd1\x7e\x4c\xf3\x85\xfd\x13\x63\xe4\x9d\xed\xac\x13\x0a\xa0\x92\xb7\x34\xde\x65\x0f\xd9\x0f\x9b\xac\xe2\x47\xe8\x5c\xb3\x11\x8e\xc6\x08\x19\xd0\xb0\x85\x52\xc8\x5c\x1b\x08\x0a\xce\xc9\x6b\xa7\xef\x95\x2f\xd0\xb8\x63\xe5\x4c\xd4\xed\x6e\x87\xe9\xd4\x0a\xe6\x11\x44\x63\x00\x94\x18\xe9\x28\xba\xcf\x92\x43\x06\x59\xdd\x37\x4f\xd3\xef\x9d\x31\x5e\x9b\x48\xf9\x1f\x3e\x7b\x95\x3a\xbd\x1f\x71\x55\x0c\x06\xf9\x86\xf8\x3d\x39\x16\x50\xb3\x21\x11\x19\x6f\x70\xa9\x48\xe8\xbb\x0a\x11\x23\xf8\xab\xfe\x44\xe0\xbb\xe8\x64\xfa\x85\xe4\x02\x55\x88\x41\xc6\x30\x7f\x10\xad\x75\x02\x4b\xef\xe1\x0b\x06\x3c\x10\x49\x83\xf9\xd1\x3e\x3e\x67\x86\x4c\xf8\x9d\xde\x5a\xc4\xc8\xcf\xb6\xf4\xb0\xd3\x34\x58\xd4\x7b\x4d\xd3\x37\x63\xb2\x48\x8a\x7e\x20\x00\xde\xb4\x42\x8f\xda\xe9\x43\x9e\x0c\x16\xce\x79\xac\x2c\x70\xc1\x89\x05\x36\x62\x6e\xd9\xbc\xfb\x63\xc6\x79\x89\x3c\x90\x89\x2b\xd1\x8c\xe0\xc2\x54\xc7\xd6\xb4\xe8\x9e\x96\x55\x6e\x7b\xd5\x7f\xac\xd4\xa7\x1c\xa0\xdf\x01\x30\xad\xc0\x9f\x69\x06\x10\x43\x7f\xf4\x5d\x62\xa3\xea\x73\xf2\x14\x79\x19\x13\xea\x59\x14\x79\xa8\xe7\xce\xce\x44\x25\x13\x41\x18\x57\xdd\xce\xe4\xbe\xcc\x20\x80\x29\x71\x73\xa7\x7c\x86\x39\x76\xf4\xa7\x1c\x63\x24\x21\x93\x1e\xb5\x9a\x5c\x8a\x9e\xda\x8b\x9d\x88\x97\xfc\x98\x7d\x26\x74\x04\x1f\xa8\x10\x4f\x45\xcd\x46\xe8\x28\xe4\x8e\x59\x67\x63\x4a\xcf\x1e\xed\xdd\xbb\x79\x2f\x8d\x94\xab\xfc\xdb\xc5\x79\x1a\x4d\xcd\x53\x41\xdf\xd1\x7a\x8f\x46\x3e\x1f\x79\x88\xe3\xee\x9f\xc4\xc1\xe6\x2e\x89\x4d\x28\xc9\xca\x28\xc2\x0a\xc5\xc7\xf1\x22\xcd\xb3\x36\xfa\xe3\x7e\xa6\xcd\x95\x55\x5e\x0e\x1a\x75\x7f\x65\x27\xd3\x37\x4f\x23\xc5\xab\x49\x68\x4e\x02\xb5\xbf\xd7\x95\xc0\x78\x67\xbc\x1a\xe9\xae\x6f\x44\x58\x8a\xc2\xce\x42\x98\x4e\x77\xc7\x2a\xa0\xa7\x7d\xe4\x3b\xd1\x20\x82\x1a\xd3\xe2\xc7\x76\x5d\x06\x46\xb5\x24\xd7\xfb\x57\x63\x2b\x19\x51\x48\x65\x6d\xfb\xe0\x98\xd1\x14\x0e\x17\x64\x29\x34\x6f\x6e\x66\x9e\x8d\xc9\x89\x49\x69\xee\x74\xf3\x35\xe6\x8b\x67\x56\x95\x7f\x1b\xe9\xed\x8c\x0f\xe2\x19\x59\xbf\x03\x35\x55\x3c\x04\xbc\x40\x52\x90\x10\x08\xad\xa7\x65\xe0\x31\xcb\xcf\x3d\xd4\x62\x68\x01\x0d\xed\xf5\x28\x64\x2d\xaa\x7c\x99\x15\x8d\x70\x32\x53\xb8\x9d\x0a\x3c\xbf\x91\x02\x04\xd0\xee\x87\xce\x04\xcc\x3e\xa8\x20\xfd\x97\xdf\xbf\x4a\xbc\xfc\xc9\x7c\x77\x21\xcc\x23\x6f\x59\x38\xd8\xd9\xa0\x0e\xb1\x23\x4e\x04\x3f\x14\x9e\xcc\x05\x54\xab\x20\x69\xed\xa4\xd5\x1d\xb4\x1b\x52\xed\x6a\xea\xeb\x7f\xd1\xbc\xfd\x75\x20\xa0\x1c\x59\x8c\x5a\xa1\x2a\x70\x64\x11\xb1\x7b\xc1\x24\x80\x28\x51\x4c\x94\xa1\x95\x64\x72\xe8\x90\x67\x38\x74\x2b\xab\x38\x46\x12\x71\xce\x19\x98\x98\xf7\x89\xd4\xfe\x2f\x2a\xc5\x61\x20\xd0\xa4\x1a\x51\x3c\x82\xc8\x18\x31\x7a\x10\xe8\x1c\xc6\x95\x5a\xa0\x82\x88\xce\x8f\x4b\x47\x85\x7e\x89\x95\x95\x52\x1e\xac\xce\x45\x57\x61\x38\x97\x2b\x62\xa5\x14\x6f\xc3\xaa\x6c\x35\x83\xc9\xa3\x1e\x30\x89\xf4\xb1\xea\x4f\x39\xde\xde\xc7\x46\x5c\x0e\x85\x41\xec\x6a\xa4\xcb\xee\x70\x9c\x57\xd9\xf4\xa1\xc3\x9c\x2a\x0a\xf0\x5d\x58\xb0\xae\xd4\xdc\xc5\x6a\xa8\x34\xfa\x23\xef\xef\x08\x39\xc3\x3d\xea\x11\x6e\x6a\xe0\x1e\xd0\x52\xa8\xc3\x6e\xc9\x1c\xfc\xd0\x0c\x4c\xea\x0d\x82\xcb\xdd\x29\x1a\xc4\x4f\x6e\xa3\x4d\xcb\x7a\x38\x77\xe5\x15\x6e\xad\xfa\x9d\x2f\x02\xb6\x39\x84\x3a\x60\x8f\x71\x9f\x92\xe5\x24\x4f\xbd\x18\x49\xd5\xef\xbf\x70\xfb\xd1\x4c\x2e\xfc\x2f\x36\xf3\x00\x31\x2e\x90\x18\xcc\xf4\x71\xb9\xe4\xf9\xbe\xcb\x5e\xff\xf3\xe7\xf8\xca\x03\x60\x66\xb3\xc9\x5a\xf9\x74\x09\x02\x57\xb6\x90\x94\xfc\x41\x35\xdc\x35\x3f\x32\x7a\xa6\xa5\xcd\x8a\x8f\xc8\x3d\xc8\x81\xc3\xec\x37\x74\x86\x61\x41\x0d\xc5\xe2\xc8\x0c\x84\x2b\x3b\x71\x58\xde\x1b\xe3\x20\x65\x2e\x76\xf4\x98\xd8\xaa\x78\xe6\xeb\xb8\x85\x0d\xa0\xd0\xf5\x57\x64\x01\x58\x55\x82\xd5\x0f\x2d\x9c\x3e\x2a\xa0\x7e\xaf\x42\xf3\x37\xd1\xb3\xaf\xda\x5b\xa9\xda\xe3\x89\x5d\xf1\xca\xa5\x12\x3d\xe7\x91\x95\x53\x21\x72\xca\x7f\xf6\x79\x59\x21\xcf\x30\x18\xfb\x78\x55\x40\x59\xc3\xf9\xf1\xdd\x58\x44\x5e\x83\x11\x5c\x2d\x1d\x91\xf6\x01\x3d\x3f\xd4\x33\x81\x66\x6c\x40\x7a\x9d\x70\x10\x58\xe6\x53\xad\x85\x11\x99\x3e\x4b\xbc\x31\xc6\x78\x9d\x79\xc5\xde\x9f\x2e\x43\xfa\x76\x84\x2f\xfd\x28\x75\x12\x48\x25\xfd\x15\x8c\x29\x6a\x91\xa4\x63\xc0\xa2\x8c\x41\x3c\xf1\xb0\xf8\xdf\x66\xeb\xbd\x14\x88\xa9\x81\xa7\x35\xc4\x41\x40\x6c\x10\x3f\x09\xbd\xb5\xd3\x7a\xee\x4b\xd5\x86\xff\x36\x03\x6b\x78\xde".to_vec(), 880 | b"\x62\x01\x53\x8a\xe9\x25\xe1\x06\x09\x8e\xba\x12\x74\x87\x09\x9a\x95\xe4\x86\x62\x2b\x4b\xf9\xa6\x2e\x7b\x35\x43\xf7\x39\x99\x0f\x3b\x6f\xfd\x1a\x6e\x23\x54\x70\xb5\x1d\x10\x1c\x63\x40\x96\x99\x41\xb6\x96\x0b\x70\x98\xec\x17\xb0\xaa\xdc\x4a\xab\xe8\x3b\xb7\x6b\x00\x1c\x5b\xc3\xe0\xa2\x8b\x7c\x17\xc8\x92\xc9\xb0\x92\xb6\x70\x84\x95\x30".to_vec(), 881 | b"\x4e\x01\xe5\x04\x35\xac\x00\x00\x80".to_vec(), 882 | b"\x62\x01\x41\xb0\x75\x5c\x27\x46\xef\x8a\xe7\x1d\x50\x38\xb2\x13\x33\xe0\x79\x35\x1b\xc2\xb5\x79\x73\xe7\xc2\x6f\xb9\x1a\x8c\x21\x0e\xa9\x54\x17\x6c\x41\xab\xc8\x16\x57\xec\x5e\xeb\x89\x3b\xa9\x90\x8c\xff\x4d\x46\x8b\xf0\xd9\xc0\xd0\x51\xcf\x8b\x88\xf1\x5f\x1e\x9e\xc1\xb9\x1f\xe3\x06\x45\x35\x8a\x47\xe8\x9a\xf2\x4f\x19\x4c\xf8\xce\x68\x1b\x63\x34\x11\x75\xea\xe5\xb1\x0f\x38\xcc\x05\x09\x8b\x3e\x2b\x88\x84\x9d\xc5\x03\xc3\xc0\x90\x32\xe2\x45\x69\xb1\xe5\xf7\x68\x6b\x16\x90\xa0\x40\xe6\x18\x74\xd8\x68\xf3\x34\x38\x99\xf2\x6c\xb7\x1a\x35\x21\xca\x52\x56\x4c\x7f\xb2\xa3\xd5\xb8\x40\x50\x48\x3e\xdc\xdf\x0b\xf5\x54\x5a\x15\x1a\xe2\xc3\xb4\x94\xda\x3f\xb5\x34\xa2\xca\xbc\x2f\xe0\xa4\xe5\x69\xf4\xbf\x62\x4d\x15\x21\x1b\x11\xfc\x39\xaa\x86\x74\x96\x63\xfd\x07\x53\x26\xf6\x34\x72\xeb\x14\x37\x98\x0d\xf4\x68\x91\x2c\x6b\x46\x83\x88\x82\x04\x8b\x9f\xb8\x32\x73\x75\x8b\xf9\xac\x71\x42\xd1\x2d\xb4\x28\x28\xf5\x78\xe0\x32\xf3\xe1\xfc\x43\x6b\xf9\x92\xf7\x48\xfe\x7f\xc0\x17\xbd\xfd\xba\x2f\x58\x6f\xee\x84\x03\x18\xce\xb0\x9d\x8d\xeb\x22\xf1\xfc\xb1\xcf\xff\x2f\xb2\x9f\x6c\xe5\xb4\x69\xdc\xdd\x20\x93\x00\x30\xad\x56\x04\x66\x7e\xa3\x3c\x18\x4b\x43\x66\x00\x27\x1e\x1c\x09\x11\xd8\xf4\x8a\x9e\xc5\x6a\x94\xe5\xae\x0b\x8a\xbe\x84\xda\xe5\x44\x7f\x38\x1c\xe7\xbb\x03\x19\x66\xe1\x5d\x1d\xc1\xbd\x3d\xc6\xb7\xe3\xff\x7f\x8e\xff\x1e\xf6\x9e\x6f\x58\x27\x74\x65\xef\x02\x5d\xa4\xde\x27\x7f\x51\xe3\x4b\x9e\x3f\x79\x83\xbd\x1b\x8f\x0d\x77\xfb\xbc\xc5\x9f\x15\xa7\x4e\x05\x8a\x24\x97\x66\xb2\x7c\xf6\xe1\x84\x54\xdb\x39\x5e\xf6\x1b\x8f\x05\x73\x1d\xb6\x8e\xd7\x09\x9a\xc5\x92\x80".to_vec(), 883 | ]; 884 | 885 | for cur in tests { 886 | let mut pck = H265Packet::default(); 887 | let _ = pck.depacketize(&Bytes::from(cur))?; 888 | } 889 | 890 | Ok(()) 891 | } 892 | -------------------------------------------------------------------------------- /src/codecs/h265/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{Error, Result}; 2 | use crate::packetizer::Depacketizer; 3 | use bytes::Bytes; 4 | 5 | #[cfg(test)] 6 | mod h265_test; 7 | 8 | /// 9 | /// Network Abstraction Unit Header implementation 10 | /// 11 | 12 | const H265NALU_HEADER_SIZE: usize = 2; 13 | /// https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.2 14 | const H265NALU_AGGREGATION_PACKET_TYPE: u8 = 48; 15 | /// https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.3 16 | const H265NALU_FRAGMENTATION_UNIT_TYPE: u8 = 49; 17 | /// https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.4 18 | const H265NALU_PACI_PACKET_TYPE: u8 = 50; 19 | 20 | /// H265NALUHeader is a H265 NAL Unit Header 21 | /// https://datatracker.ietf.org/doc/html/rfc7798#section-1.1.4 22 | /// +---------------+---------------+ 23 | /// |0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7| 24 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 25 | /// |F| Type | layer_id | tid | 26 | /// +-------------+-----------------+ 27 | #[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] 28 | pub struct H265NALUHeader(pub u16); 29 | 30 | impl H265NALUHeader { 31 | fn new(high_byte: u8, low_byte: u8) -> Self { 32 | H265NALUHeader(((high_byte as u16) << 8) | low_byte as u16) 33 | } 34 | 35 | /// f is the forbidden bit, should always be 0. 36 | pub fn f(&self) -> bool { 37 | (self.0 >> 15) != 0 38 | } 39 | 40 | /// nalu_type of NAL Unit. 41 | pub fn nalu_type(&self) -> u8 { 42 | // 01111110 00000000 43 | const MASK: u16 = 0b01111110 << 8; 44 | ((self.0 & MASK) >> (8 + 1)) as u8 45 | } 46 | 47 | /// is_type_vcl_unit returns whether or not the NAL Unit type is a VCL NAL unit. 48 | pub fn is_type_vcl_unit(&self) -> bool { 49 | // Type is coded on 6 bits 50 | const MSB_MASK: u8 = 0b00100000; 51 | (self.nalu_type() & MSB_MASK) == 0 52 | } 53 | 54 | /// layer_id should always be 0 in non-3D HEVC context. 55 | pub fn layer_id(&self) -> u8 { 56 | // 00000001 11111000 57 | const MASK: u16 = (0b00000001 << 8) | 0b11111000; 58 | ((self.0 & MASK) >> 3) as u8 59 | } 60 | 61 | /// tid is the temporal identifier of the NAL unit +1. 62 | pub fn tid(&self) -> u8 { 63 | const MASK: u16 = 0b00000111; 64 | (self.0 & MASK) as u8 65 | } 66 | 67 | /// is_aggregation_packet returns whether or not the packet is an Aggregation packet. 68 | pub fn is_aggregation_packet(&self) -> bool { 69 | self.nalu_type() == H265NALU_AGGREGATION_PACKET_TYPE 70 | } 71 | 72 | /// is_fragmentation_unit returns whether or not the packet is a Fragmentation Unit packet. 73 | pub fn is_fragmentation_unit(&self) -> bool { 74 | self.nalu_type() == H265NALU_FRAGMENTATION_UNIT_TYPE 75 | } 76 | 77 | /// is_paci_packet returns whether or not the packet is a PACI packet. 78 | pub fn is_paci_packet(&self) -> bool { 79 | self.nalu_type() == H265NALU_PACI_PACKET_TYPE 80 | } 81 | } 82 | 83 | /// 84 | /// Single NAL Unit Packet implementation 85 | /// 86 | /// H265SingleNALUnitPacket represents a NALU packet, containing exactly one NAL unit. 87 | /// 0 1 2 3 88 | /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 89 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 90 | /// | PayloadHdr | DONL (conditional) | 91 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 92 | /// | | 93 | /// | NAL unit payload data | 94 | /// | | 95 | /// | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 96 | /// | :...OPTIONAL RTP padding | 97 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 98 | /// 99 | /// Reference: https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.1 100 | #[derive(Default, Debug, Clone, PartialEq, Eq)] 101 | pub struct H265SingleNALUnitPacket { 102 | /// payload_header is the header of the H265 packet. 103 | payload_header: H265NALUHeader, 104 | /// donl is a 16-bit field, that may or may not be present. 105 | donl: Option, 106 | /// payload of the fragmentation unit. 107 | payload: Bytes, 108 | 109 | might_need_donl: bool, 110 | } 111 | 112 | impl H265SingleNALUnitPacket { 113 | /// with_donl can be called to specify whether or not DONL might be parsed. 114 | /// DONL may need to be parsed if `sprop-max-don-diff` is greater than 0 on the RTP stream. 115 | pub fn with_donl(&mut self, value: bool) { 116 | self.might_need_donl = value; 117 | } 118 | 119 | /// depacketize parses the passed byte slice and stores the result in the H265SingleNALUnitPacket this method is called upon. 120 | fn depacketize(&mut self, payload: &Bytes) -> Result<()> { 121 | if payload.len() <= H265NALU_HEADER_SIZE { 122 | return Err(Error::ErrShortPacket); 123 | } 124 | 125 | let payload_header = H265NALUHeader::new(payload[0], payload[1]); 126 | if payload_header.f() { 127 | return Err(Error::ErrH265CorruptedPacket); 128 | } 129 | if payload_header.is_fragmentation_unit() 130 | || payload_header.is_paci_packet() 131 | || payload_header.is_aggregation_packet() 132 | { 133 | return Err(Error::ErrInvalidH265PacketType); 134 | } 135 | 136 | let mut payload = payload.slice(2..); 137 | 138 | if self.might_need_donl { 139 | // sizeof(uint16) 140 | if payload.len() <= 2 { 141 | return Err(Error::ErrShortPacket); 142 | } 143 | 144 | let donl = ((payload[0] as u16) << 8) | (payload[1] as u16); 145 | self.donl = Some(donl); 146 | payload = payload.slice(2..); 147 | } 148 | 149 | self.payload_header = payload_header; 150 | self.payload = payload; 151 | 152 | Ok(()) 153 | } 154 | 155 | /// payload_header returns the NALU header of the packet. 156 | pub fn payload_header(&self) -> H265NALUHeader { 157 | self.payload_header 158 | } 159 | 160 | /// donl returns the DONL of the packet. 161 | pub fn donl(&self) -> Option { 162 | self.donl 163 | } 164 | 165 | /// payload returns the Fragmentation Unit packet payload. 166 | pub fn payload(&self) -> Bytes { 167 | self.payload.clone() 168 | } 169 | } 170 | 171 | /// 172 | /// Aggregation Packets implementation 173 | /// 174 | /// H265AggregationUnitFirst represent the First Aggregation Unit in an AP. 175 | /// 176 | /// 0 1 2 3 177 | /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 178 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 179 | /// : DONL (conditional) | NALU size | 180 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 181 | /// | NALU size | | 182 | /// +-+-+-+-+-+-+-+-+ NAL unit | 183 | /// | | 184 | /// | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 185 | /// | : 186 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 187 | /// 188 | /// Reference: https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.2 189 | #[derive(Default, Debug, Clone, PartialEq, Eq)] 190 | pub struct H265AggregationUnitFirst { 191 | donl: Option, 192 | nal_unit_size: u16, 193 | nal_unit: Bytes, 194 | } 195 | 196 | impl H265AggregationUnitFirst { 197 | /// donl field, when present, specifies the value of the 16 least 198 | /// significant bits of the decoding order number of the aggregated NAL 199 | /// unit. 200 | pub fn donl(&self) -> Option { 201 | self.donl 202 | } 203 | 204 | /// nalu_size represents the size, in bytes, of the nal_unit. 205 | pub fn nalu_size(&self) -> u16 { 206 | self.nal_unit_size 207 | } 208 | 209 | /// nal_unit payload. 210 | pub fn nal_unit(&self) -> Bytes { 211 | self.nal_unit.clone() 212 | } 213 | } 214 | 215 | /// H265AggregationUnit represent the an Aggregation Unit in an AP, which is not the first one. 216 | /// 217 | /// 0 1 2 3 218 | /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 219 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 220 | /// : DOND (cond) | NALU size | 221 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 222 | /// | | 223 | /// | NAL unit | 224 | /// | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 225 | /// | : 226 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 227 | /// 228 | /// Reference: https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.2 229 | #[derive(Default, Debug, Clone, PartialEq, Eq)] 230 | pub struct H265AggregationUnit { 231 | dond: Option, 232 | nal_unit_size: u16, 233 | nal_unit: Bytes, 234 | } 235 | 236 | impl H265AggregationUnit { 237 | /// dond field plus 1 specifies the difference between 238 | /// the decoding order number values of the current aggregated NAL unit 239 | /// and the preceding aggregated NAL unit in the same AP. 240 | pub fn dond(&self) -> Option { 241 | self.dond 242 | } 243 | 244 | /// nalu_size represents the size, in bytes, of the nal_unit. 245 | pub fn nalu_size(&self) -> u16 { 246 | self.nal_unit_size 247 | } 248 | 249 | /// nal_unit payload. 250 | pub fn nal_unit(&self) -> Bytes { 251 | self.nal_unit.clone() 252 | } 253 | } 254 | 255 | /// H265AggregationPacket represents an Aggregation packet. 256 | /// 0 1 2 3 257 | /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 258 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 259 | /// | PayloadHdr (Type=48) | | 260 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 261 | /// | | 262 | /// | two or more aggregation units | 263 | /// | | 264 | /// | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 265 | /// | :...OPTIONAL RTP padding | 266 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 267 | /// 268 | /// Reference: https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.2 269 | #[derive(Default, Debug, Clone, PartialEq, Eq)] 270 | pub struct H265AggregationPacket { 271 | first_unit: Option, 272 | other_units: Vec, 273 | 274 | might_need_donl: bool, 275 | } 276 | 277 | impl H265AggregationPacket { 278 | /// with_donl can be called to specify whether or not DONL might be parsed. 279 | /// DONL may need to be parsed if `sprop-max-don-diff` is greater than 0 on the RTP stream. 280 | pub fn with_donl(&mut self, value: bool) { 281 | self.might_need_donl = value; 282 | } 283 | 284 | /// depacketize parses the passed byte slice and stores the result in the H265AggregationPacket this method is called upon. 285 | fn depacketize(&mut self, payload: &Bytes) -> Result<()> { 286 | if payload.len() <= H265NALU_HEADER_SIZE { 287 | return Err(Error::ErrShortPacket); 288 | } 289 | 290 | let payload_header = H265NALUHeader::new(payload[0], payload[1]); 291 | if payload_header.f() { 292 | return Err(Error::ErrH265CorruptedPacket); 293 | } 294 | if !payload_header.is_aggregation_packet() { 295 | return Err(Error::ErrInvalidH265PacketType); 296 | } 297 | 298 | // First parse the first aggregation unit 299 | let mut payload = payload.slice(2..); 300 | let mut first_unit = H265AggregationUnitFirst::default(); 301 | 302 | if self.might_need_donl { 303 | if payload.len() < 2 { 304 | return Err(Error::ErrShortPacket); 305 | } 306 | 307 | let donl = ((payload[0] as u16) << 8) | (payload[1] as u16); 308 | first_unit.donl = Some(donl); 309 | 310 | payload = payload.slice(2..); 311 | } 312 | if payload.len() < 2 { 313 | return Err(Error::ErrShortPacket); 314 | } 315 | first_unit.nal_unit_size = ((payload[0] as u16) << 8) | (payload[1] as u16); 316 | payload = payload.slice(2..); 317 | 318 | if payload.len() < first_unit.nal_unit_size as usize { 319 | return Err(Error::ErrShortPacket); 320 | } 321 | 322 | first_unit.nal_unit = payload.slice(..first_unit.nal_unit_size as usize); 323 | payload = payload.slice(first_unit.nal_unit_size as usize..); 324 | 325 | // Parse remaining Aggregation Units 326 | let mut units = vec![]; //H265AggregationUnit 327 | loop { 328 | let mut unit = H265AggregationUnit::default(); 329 | 330 | if self.might_need_donl { 331 | if payload.is_empty() { 332 | break; 333 | } 334 | 335 | let dond = payload[0]; 336 | unit.dond = Some(dond); 337 | 338 | payload = payload.slice(1..); 339 | } 340 | 341 | if payload.len() < 2 { 342 | break; 343 | } 344 | unit.nal_unit_size = ((payload[0] as u16) << 8) | (payload[1] as u16); 345 | payload = payload.slice(2..); 346 | 347 | if payload.len() < unit.nal_unit_size as usize { 348 | break; 349 | } 350 | 351 | unit.nal_unit = payload.slice(..unit.nal_unit_size as usize); 352 | payload = payload.slice(unit.nal_unit_size as usize..); 353 | 354 | units.push(unit); 355 | } 356 | 357 | // There need to be **at least** two Aggregation Units (first + another one) 358 | if units.is_empty() { 359 | return Err(Error::ErrShortPacket); 360 | } 361 | 362 | self.first_unit = Some(first_unit); 363 | self.other_units = units; 364 | 365 | Ok(()) 366 | } 367 | 368 | /// first_unit returns the first Aggregated Unit of the packet. 369 | pub fn first_unit(&self) -> Option<&H265AggregationUnitFirst> { 370 | self.first_unit.as_ref() 371 | } 372 | 373 | /// other_units returns the all the other Aggregated Unit of the packet (excluding the first one). 374 | pub fn other_units(&self) -> &[H265AggregationUnit] { 375 | self.other_units.as_slice() 376 | } 377 | } 378 | 379 | /// 380 | /// Fragmentation Unit implementation 381 | /// 382 | 383 | const H265FRAGMENTATION_UNIT_HEADER_SIZE: usize = 1; 384 | 385 | /// H265FragmentationUnitHeader is a H265 FU Header 386 | /// +---------------+ 387 | /// |0|1|2|3|4|5|6|7| 388 | /// +-+-+-+-+-+-+-+-+ 389 | /// |S|E| fu_type | 390 | /// +---------------+ 391 | #[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] 392 | pub struct H265FragmentationUnitHeader(pub u8); 393 | 394 | impl H265FragmentationUnitHeader { 395 | /// s represents the start of a fragmented NAL unit. 396 | pub fn s(&self) -> bool { 397 | const MASK: u8 = 0b10000000; 398 | ((self.0 & MASK) >> 7) != 0 399 | } 400 | 401 | /// e represents the end of a fragmented NAL unit. 402 | pub fn e(&self) -> bool { 403 | const MASK: u8 = 0b01000000; 404 | ((self.0 & MASK) >> 6) != 0 405 | } 406 | 407 | /// fu_type MUST be equal to the field Type of the fragmented NAL unit. 408 | pub fn fu_type(&self) -> u8 { 409 | const MASK: u8 = 0b00111111; 410 | self.0 & MASK 411 | } 412 | } 413 | 414 | /// H265FragmentationUnitPacket represents a single Fragmentation Unit packet. 415 | /// 416 | /// 0 1 2 3 417 | /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 418 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 419 | /// | PayloadHdr (Type=49) | FU header | DONL (cond) | 420 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-| 421 | /// | DONL (cond) | | 422 | /// |-+-+-+-+-+-+-+-+ | 423 | /// | FU payload | 424 | /// | | 425 | /// | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 426 | /// | :...OPTIONAL RTP padding | 427 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 428 | /// 429 | /// Reference: https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.3 430 | #[derive(Default, Debug, Clone, PartialEq, Eq)] 431 | pub struct H265FragmentationUnitPacket { 432 | /// payload_header is the header of the H265 packet. 433 | payload_header: H265NALUHeader, 434 | /// fu_header is the header of the fragmentation unit 435 | fu_header: H265FragmentationUnitHeader, 436 | /// donl is a 16-bit field, that may or may not be present. 437 | donl: Option, 438 | /// payload of the fragmentation unit. 439 | payload: Bytes, 440 | 441 | might_need_donl: bool, 442 | } 443 | 444 | impl H265FragmentationUnitPacket { 445 | /// with_donl can be called to specify whether or not DONL might be parsed. 446 | /// DONL may need to be parsed if `sprop-max-don-diff` is greater than 0 on the RTP stream. 447 | pub fn with_donl(&mut self, value: bool) { 448 | self.might_need_donl = value; 449 | } 450 | 451 | /// depacketize parses the passed byte slice and stores the result in the H265FragmentationUnitPacket this method is called upon. 452 | fn depacketize(&mut self, payload: &Bytes) -> Result<()> { 453 | const TOTAL_HEADER_SIZE: usize = H265NALU_HEADER_SIZE + H265FRAGMENTATION_UNIT_HEADER_SIZE; 454 | if payload.len() <= TOTAL_HEADER_SIZE { 455 | return Err(Error::ErrShortPacket); 456 | } 457 | 458 | let payload_header = H265NALUHeader::new(payload[0], payload[1]); 459 | if payload_header.f() { 460 | return Err(Error::ErrH265CorruptedPacket); 461 | } 462 | if !payload_header.is_fragmentation_unit() { 463 | return Err(Error::ErrInvalidH265PacketType); 464 | } 465 | 466 | let fu_header = H265FragmentationUnitHeader(payload[2]); 467 | let mut payload = payload.slice(3..); 468 | 469 | if fu_header.s() && self.might_need_donl { 470 | if payload.len() <= 2 { 471 | return Err(Error::ErrShortPacket); 472 | } 473 | 474 | let donl = ((payload[0] as u16) << 8) | (payload[1] as u16); 475 | self.donl = Some(donl); 476 | payload = payload.slice(2..); 477 | } 478 | 479 | self.payload_header = payload_header; 480 | self.fu_header = fu_header; 481 | self.payload = payload; 482 | 483 | Ok(()) 484 | } 485 | 486 | /// payload_header returns the NALU header of the packet. 487 | pub fn payload_header(&self) -> H265NALUHeader { 488 | self.payload_header 489 | } 490 | 491 | /// fu_header returns the Fragmentation Unit Header of the packet. 492 | pub fn fu_header(&self) -> H265FragmentationUnitHeader { 493 | self.fu_header 494 | } 495 | 496 | /// donl returns the DONL of the packet. 497 | pub fn donl(&self) -> Option { 498 | self.donl 499 | } 500 | 501 | /// payload returns the Fragmentation Unit packet payload. 502 | pub fn payload(&self) -> Bytes { 503 | self.payload.clone() 504 | } 505 | } 506 | 507 | /// 508 | /// PACI implementation 509 | /// 510 | 511 | /// H265PACIPacket represents a single H265 PACI packet. 512 | /// 513 | /// 0 1 2 3 514 | /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 515 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 516 | /// | PayloadHdr (Type=50) |A| cType | phssize |F0..2|Y| 517 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 518 | /// | payload Header Extension Structure (phes) | 519 | /// |=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=| 520 | /// | | 521 | /// | PACI payload: NAL unit | 522 | /// | . . . | 523 | /// | | 524 | /// | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 525 | /// | :...OPTIONAL RTP padding | 526 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 527 | /// 528 | /// Reference: https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.4 529 | #[derive(Default, Debug, Clone, PartialEq, Eq)] 530 | pub struct H265PACIPacket { 531 | /// payload_header is the header of the H265 packet. 532 | payload_header: H265NALUHeader, 533 | 534 | /// Field which holds value for `A`, `cType`, `phssize`, `F0`, `F1`, `F2` and `Y` fields. 535 | paci_header_fields: u16, 536 | 537 | /// phes is a header extension, of byte length `phssize` 538 | phes: Bytes, 539 | 540 | /// payload contains NAL units & optional padding 541 | payload: Bytes, 542 | } 543 | 544 | impl H265PACIPacket { 545 | /// payload_header returns the NAL Unit Header. 546 | pub fn payload_header(&self) -> H265NALUHeader { 547 | self.payload_header 548 | } 549 | 550 | /// a copies the F bit of the PACI payload NALU. 551 | pub fn a(&self) -> bool { 552 | const MASK: u16 = 0b10000000 << 8; 553 | (self.paci_header_fields & MASK) != 0 554 | } 555 | 556 | /// ctype copies the Type field of the PACI payload NALU. 557 | pub fn ctype(&self) -> u8 { 558 | const MASK: u16 = 0b01111110 << 8; 559 | ((self.paci_header_fields & MASK) >> (8 + 1)) as u8 560 | } 561 | 562 | /// phs_size indicates the size of the phes field. 563 | pub fn phs_size(&self) -> u8 { 564 | const MASK: u16 = (0b00000001 << 8) | 0b11110000; 565 | ((self.paci_header_fields & MASK) >> 4) as u8 566 | } 567 | 568 | /// f0 indicates the presence of a Temporal Scalability support extension in the phes. 569 | pub fn f0(&self) -> bool { 570 | const MASK: u16 = 0b00001000; 571 | (self.paci_header_fields & MASK) != 0 572 | } 573 | 574 | /// f1 must be zero, reserved for future extensions. 575 | pub fn f1(&self) -> bool { 576 | const MASK: u16 = 0b00000100; 577 | (self.paci_header_fields & MASK) != 0 578 | } 579 | 580 | /// f2 must be zero, reserved for future extensions. 581 | pub fn f2(&self) -> bool { 582 | const MASK: u16 = 0b00000010; 583 | (self.paci_header_fields & MASK) != 0 584 | } 585 | 586 | /// y must be zero, reserved for future extensions. 587 | pub fn y(&self) -> bool { 588 | const MASK: u16 = 0b00000001; 589 | (self.paci_header_fields & MASK) != 0 590 | } 591 | 592 | /// phes contains header extensions. Its size is indicated by phssize. 593 | pub fn phes(&self) -> Bytes { 594 | self.phes.clone() 595 | } 596 | 597 | /// payload is a single NALU or NALU-like struct, not including the first two octets (header). 598 | pub fn payload(&self) -> Bytes { 599 | self.payload.clone() 600 | } 601 | 602 | /// tsci returns the Temporal Scalability Control Information extension, if present. 603 | pub fn tsci(&self) -> Option { 604 | if !self.f0() || self.phs_size() < 3 { 605 | return None; 606 | } 607 | 608 | Some(H265TSCI( 609 | ((self.phes[0] as u32) << 16) | ((self.phes[1] as u32) << 8) | self.phes[0] as u32, 610 | )) 611 | } 612 | 613 | /// depacketize parses the passed byte slice and stores the result in the H265PACIPacket this method is called upon. 614 | fn depacketize(&mut self, payload: &Bytes) -> Result<()> { 615 | const TOTAL_HEADER_SIZE: usize = H265NALU_HEADER_SIZE + 2; 616 | if payload.len() <= TOTAL_HEADER_SIZE { 617 | return Err(Error::ErrShortPacket); 618 | } 619 | 620 | let payload_header = H265NALUHeader::new(payload[0], payload[1]); 621 | if payload_header.f() { 622 | return Err(Error::ErrH265CorruptedPacket); 623 | } 624 | if !payload_header.is_paci_packet() { 625 | return Err(Error::ErrInvalidH265PacketType); 626 | } 627 | 628 | let paci_header_fields = ((payload[2] as u16) << 8) | (payload[3] as u16); 629 | let mut payload = payload.slice(4..); 630 | 631 | self.paci_header_fields = paci_header_fields; 632 | let header_extension_size = self.phs_size(); 633 | 634 | if payload.len() < header_extension_size as usize + 1 { 635 | self.paci_header_fields = 0; 636 | return Err(Error::ErrShortPacket); 637 | } 638 | 639 | self.payload_header = payload_header; 640 | 641 | if header_extension_size > 0 { 642 | self.phes = payload.slice(..header_extension_size as usize); 643 | } 644 | 645 | payload = payload.slice(header_extension_size as usize..); 646 | self.payload = payload; 647 | 648 | Ok(()) 649 | } 650 | } 651 | 652 | /// 653 | /// Temporal Scalability Control Information 654 | /// 655 | 656 | /// H265TSCI is a Temporal Scalability Control Information header extension. 657 | /// Reference: https://datatracker.ietf.org/doc/html/rfc7798#section-4.5 658 | #[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] 659 | pub struct H265TSCI(pub u32); 660 | 661 | impl H265TSCI { 662 | /// tl0picidx see RFC7798 for more details. 663 | pub fn tl0picidx(&self) -> u8 { 664 | const M1: u32 = 0xFFFF0000; 665 | const M2: u32 = 0xFF00; 666 | ((((self.0 & M1) >> 16) & M2) >> 8) as u8 667 | } 668 | 669 | /// irap_pic_id see RFC7798 for more details. 670 | pub fn irap_pic_id(&self) -> u8 { 671 | const M1: u32 = 0xFFFF0000; 672 | const M2: u32 = 0x00FF; 673 | (((self.0 & M1) >> 16) & M2) as u8 674 | } 675 | 676 | /// s see RFC7798 for more details. 677 | pub fn s(&self) -> bool { 678 | const M1: u32 = 0xFF00; 679 | const M2: u32 = 0b10000000; 680 | (((self.0 & M1) >> 8) & M2) != 0 681 | } 682 | 683 | /// e see RFC7798 for more details. 684 | pub fn e(&self) -> bool { 685 | const M1: u32 = 0xFF00; 686 | const M2: u32 = 0b01000000; 687 | (((self.0 & M1) >> 8) & M2) != 0 688 | } 689 | 690 | /// res see RFC7798 for more details. 691 | pub fn res(&self) -> u8 { 692 | const M1: u32 = 0xFF00; 693 | const M2: u32 = 0b00111111; 694 | (((self.0 & M1) >> 8) & M2) as u8 695 | } 696 | } 697 | 698 | /// 699 | /// H265 Payload Enum 700 | /// 701 | #[derive(Debug, Clone, PartialEq, Eq)] 702 | pub enum H265Payload { 703 | H265SingleNALUnitPacket(H265SingleNALUnitPacket), 704 | H265FragmentationUnitPacket(H265FragmentationUnitPacket), 705 | H265AggregationPacket(H265AggregationPacket), 706 | H265PACIPacket(H265PACIPacket), 707 | } 708 | 709 | impl Default for H265Payload { 710 | fn default() -> Self { 711 | H265Payload::H265SingleNALUnitPacket(H265SingleNALUnitPacket::default()) 712 | } 713 | } 714 | 715 | /// 716 | /// Packet implementation 717 | /// 718 | 719 | /// H265Packet represents a H265 packet, stored in the payload of an RTP packet. 720 | #[derive(Default, Debug, Clone, PartialEq, Eq)] 721 | pub struct H265Packet { 722 | payload: H265Payload, 723 | might_need_donl: bool, 724 | } 725 | 726 | impl H265Packet { 727 | /// with_donl can be called to specify whether or not DONL might be parsed. 728 | /// DONL may need to be parsed if `sprop-max-don-diff` is greater than 0 on the RTP stream. 729 | pub fn with_donl(&mut self, value: bool) { 730 | self.might_need_donl = value; 731 | } 732 | 733 | /// payload returns the populated payload. 734 | /// Must be casted to one of: 735 | /// - H265SingleNALUnitPacket 736 | /// - H265FragmentationUnitPacket 737 | /// - H265AggregationPacket 738 | /// - H265PACIPacket 739 | pub fn payload(&self) -> &H265Payload { 740 | &self.payload 741 | } 742 | } 743 | 744 | impl Depacketizer for H265Packet { 745 | /// depacketize parses the passed byte slice and stores the result in the H265Packet this method is called upon 746 | fn depacketize(&mut self, payload: &Bytes) -> Result { 747 | if payload.len() <= H265NALU_HEADER_SIZE { 748 | return Err(Error::ErrShortPacket); 749 | } 750 | 751 | let payload_header = H265NALUHeader::new(payload[0], payload[1]); 752 | if payload_header.f() { 753 | return Err(Error::ErrH265CorruptedPacket); 754 | } 755 | 756 | if payload_header.is_paci_packet() { 757 | let mut decoded = H265PACIPacket::default(); 758 | decoded.depacketize(payload)?; 759 | 760 | self.payload = H265Payload::H265PACIPacket(decoded); 761 | } else if payload_header.is_fragmentation_unit() { 762 | let mut decoded = H265FragmentationUnitPacket::default(); 763 | decoded.with_donl(self.might_need_donl); 764 | 765 | decoded.depacketize(payload)?; 766 | 767 | self.payload = H265Payload::H265FragmentationUnitPacket(decoded); 768 | } else if payload_header.is_aggregation_packet() { 769 | let mut decoded = H265AggregationPacket::default(); 770 | decoded.with_donl(self.might_need_donl); 771 | 772 | decoded.depacketize(payload)?; 773 | 774 | self.payload = H265Payload::H265AggregationPacket(decoded); 775 | } else { 776 | let mut decoded = H265SingleNALUnitPacket::default(); 777 | decoded.with_donl(self.might_need_donl); 778 | 779 | decoded.depacketize(payload)?; 780 | 781 | self.payload = H265Payload::H265SingleNALUnitPacket(decoded); 782 | } 783 | 784 | Ok(payload.clone()) 785 | } 786 | 787 | /// is_partition_head checks if this is the head of a packetized nalu stream. 788 | fn is_partition_head(&self, _payload: &Bytes) -> bool { 789 | //TODO: 790 | true 791 | } 792 | 793 | fn is_partition_tail(&self, marker: bool, _payload: &Bytes) -> bool { 794 | marker 795 | } 796 | } 797 | -------------------------------------------------------------------------------- /src/codecs/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod g7xx; 2 | pub mod h264; 3 | pub mod h265; 4 | pub mod opus; 5 | pub mod vp8; 6 | pub mod vp9; 7 | -------------------------------------------------------------------------------- /src/codecs/opus/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod opus_test; 3 | 4 | use crate::{ 5 | error::{Error, Result}, 6 | packetizer::{Depacketizer, Payloader}, 7 | }; 8 | 9 | use bytes::Bytes; 10 | 11 | #[derive(Default, Debug, Copy, Clone)] 12 | pub struct OpusPayloader; 13 | 14 | impl Payloader for OpusPayloader { 15 | fn payload(&mut self, mtu: usize, payload: &Bytes) -> Result> { 16 | if payload.is_empty() || mtu == 0 { 17 | return Ok(vec![]); 18 | } 19 | 20 | Ok(vec![payload.clone()]) 21 | } 22 | 23 | fn clone_to(&self) -> Box { 24 | Box::new(*self) 25 | } 26 | } 27 | 28 | /// OpusPacket represents the Opus header that is stored in the payload of an RTP Packet 29 | #[derive(PartialEq, Eq, Debug, Default, Clone)] 30 | pub struct OpusPacket; 31 | 32 | impl Depacketizer for OpusPacket { 33 | fn depacketize(&mut self, packet: &Bytes) -> Result { 34 | if packet.is_empty() { 35 | Err(Error::ErrShortPacket) 36 | } else { 37 | Ok(packet.clone()) 38 | } 39 | } 40 | 41 | fn is_partition_head(&self, _payload: &Bytes) -> bool { 42 | true 43 | } 44 | 45 | fn is_partition_tail(&self, _marker: bool, _payload: &Bytes) -> bool { 46 | true 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/codecs/opus/opus_test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[test] 4 | fn test_opus_unmarshal() -> Result<()> { 5 | let mut pck = OpusPacket::default(); 6 | 7 | // Empty packet 8 | let empty_bytes = Bytes::from_static(&[]); 9 | let result = pck.depacketize(&empty_bytes); 10 | assert!(result.is_err(), "Result should be err in case of error"); 11 | 12 | // Normal packet 13 | let raw_bytes = Bytes::from_static(&[0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x90]); 14 | let payload = pck.depacketize(&raw_bytes)?; 15 | assert_eq!(&raw_bytes, &payload, "Payload must be same"); 16 | 17 | Ok(()) 18 | } 19 | 20 | #[test] 21 | fn test_opus_payload() -> Result<()> { 22 | let mut pck = OpusPayloader::default(); 23 | let empty = Bytes::from_static(&[]); 24 | let payload = Bytes::from_static(&[0x90, 0x90, 0x90]); 25 | 26 | // Positive MTU, empty payload 27 | let result = pck.payload(1, &empty)?; 28 | assert!(result.is_empty(), "Generated payload should be empty"); 29 | 30 | // Positive MTU, small payload 31 | let result = pck.payload(1, &payload)?; 32 | assert_eq!(result.len(), 1, "Generated payload should be the 1"); 33 | 34 | // Positive MTU, small payload 35 | let result = pck.payload(2, &payload)?; 36 | assert_eq!(result.len(), 1, "Generated payload should be the 1"); 37 | 38 | Ok(()) 39 | } 40 | 41 | #[test] 42 | fn test_opus_is_partition_head() -> Result<()> { 43 | let opus = OpusPacket::default(); 44 | //"NormalPacket" 45 | assert!( 46 | opus.is_partition_head(&Bytes::from_static(&[0x00, 0x00])), 47 | "All OPUS RTP packet should be the head of a new partition" 48 | ); 49 | 50 | Ok(()) 51 | } 52 | -------------------------------------------------------------------------------- /src/codecs/vp8/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod vp8_test; 3 | 4 | use crate::{ 5 | error::{Error, Result}, 6 | packetizer::{Depacketizer, Payloader}, 7 | }; 8 | 9 | use bytes::{Buf, BufMut, Bytes, BytesMut}; 10 | 11 | pub const VP8_HEADER_SIZE: usize = 1; 12 | 13 | /// Vp8Payloader payloads VP8 packets 14 | #[derive(Default, Debug, Copy, Clone)] 15 | pub struct Vp8Payloader { 16 | pub enable_picture_id: bool, 17 | picture_id: u16, 18 | } 19 | 20 | impl Payloader for Vp8Payloader { 21 | /// Payload fragments a VP8 packet across one or more byte arrays 22 | fn payload(&mut self, mtu: usize, payload: &Bytes) -> Result> { 23 | if payload.is_empty() || mtu == 0 { 24 | return Ok(vec![]); 25 | } 26 | 27 | /* 28 | * https://tools.ietf.org/html/rfc7741#section-4.2 29 | * 30 | * 0 1 2 3 4 5 6 7 31 | * +-+-+-+-+-+-+-+-+ 32 | * |X|R|N|S|R| PID | (REQUIRED) 33 | * +-+-+-+-+-+-+-+-+ 34 | * X: |I|L|T|K| RSV | (OPTIONAL) 35 | * +-+-+-+-+-+-+-+-+ 36 | * I: |M| PictureID | (OPTIONAL) 37 | * +-+-+-+-+-+-+-+-+ 38 | * L: | tl0picidx | (OPTIONAL) 39 | * +-+-+-+-+-+-+-+-+ 40 | * T/K: |tid|Y| KEYIDX | (OPTIONAL) 41 | * +-+-+-+-+-+-+-+-+ 42 | * S: Start of VP8 partition. SHOULD be set to 1 when the first payload 43 | * octet of the RTP packet is the beginning of a new VP8 partition, 44 | * and MUST NOT be 1 otherwise. The S bit MUST be set to 1 for the 45 | * first packet of each encoded frame. 46 | */ 47 | let using_header_size = if self.enable_picture_id { 48 | if self.picture_id == 0 || self.picture_id < 128 { 49 | VP8_HEADER_SIZE + 2 50 | } else { 51 | VP8_HEADER_SIZE + 3 52 | } 53 | } else { 54 | VP8_HEADER_SIZE 55 | }; 56 | 57 | let max_fragment_size = mtu as isize - using_header_size as isize; 58 | let mut payload_data_remaining = payload.len() as isize; 59 | let mut payload_data_index: usize = 0; 60 | let mut payloads = vec![]; 61 | 62 | // Make sure the fragment/payload size is correct 63 | if std::cmp::min(max_fragment_size, payload_data_remaining) <= 0 { 64 | return Ok(payloads); 65 | } 66 | 67 | let mut first = true; 68 | while payload_data_remaining > 0 { 69 | let current_fragment_size = 70 | std::cmp::min(max_fragment_size, payload_data_remaining) as usize; 71 | let mut out = BytesMut::with_capacity(using_header_size + current_fragment_size); 72 | let mut buf = vec![0u8; 4]; 73 | if first { 74 | buf[0] = 0x10; 75 | first = false; 76 | } 77 | 78 | if self.enable_picture_id { 79 | if using_header_size == VP8_HEADER_SIZE + 2 { 80 | buf[0] |= 0x80; 81 | buf[1] |= 0x80; 82 | buf[2] |= (self.picture_id & 0x7F) as u8; 83 | } else if using_header_size == VP8_HEADER_SIZE + 3 { 84 | buf[0] |= 0x80; 85 | buf[1] |= 0x80; 86 | buf[2] |= 0x80 | ((self.picture_id >> 8) & 0x7F) as u8; 87 | buf[3] |= (self.picture_id & 0xFF) as u8; 88 | } 89 | } 90 | 91 | out.put(&buf[..using_header_size]); 92 | 93 | out.put( 94 | &*payload.slice(payload_data_index..payload_data_index + current_fragment_size), 95 | ); 96 | payloads.push(out.freeze()); 97 | 98 | payload_data_remaining -= current_fragment_size as isize; 99 | payload_data_index += current_fragment_size; 100 | } 101 | 102 | self.picture_id += 1; 103 | self.picture_id &= 0x7FFF; 104 | 105 | Ok(payloads) 106 | } 107 | 108 | fn clone_to(&self) -> Box { 109 | Box::new(*self) 110 | } 111 | } 112 | 113 | /// Vp8Packet represents the VP8 header that is stored in the payload of an RTP Packet 114 | #[derive(PartialEq, Eq, Debug, Default, Clone)] 115 | pub struct Vp8Packet { 116 | /// Required Header 117 | /// extended controlbits present 118 | pub x: u8, 119 | /// when set to 1 this frame can be discarded 120 | pub n: u8, 121 | /// start of VP8 partition 122 | pub s: u8, 123 | /// partition index 124 | pub pid: u8, 125 | 126 | /// Extended control bits 127 | /// 1 if PictureID is present 128 | pub i: u8, 129 | /// 1 if tl0picidx is present 130 | pub l: u8, 131 | /// 1 if tid is present 132 | pub t: u8, 133 | /// 1 if KEYIDX is present 134 | pub k: u8, 135 | 136 | /// Optional extension 137 | /// 8 or 16 bits, picture ID 138 | pub picture_id: u16, 139 | /// 8 bits temporal level zero index 140 | pub tl0_pic_idx: u8, 141 | /// 2 bits temporal layer index 142 | pub tid: u8, 143 | /// 1 bit layer sync bit 144 | pub y: u8, 145 | /// 5 bits temporal key frame index 146 | pub key_idx: u8, 147 | } 148 | 149 | impl Depacketizer for Vp8Packet { 150 | /// depacketize parses the passed byte slice and stores the result in the VP8Packet this method is called upon 151 | fn depacketize(&mut self, packet: &Bytes) -> Result { 152 | let payload_len = packet.len(); 153 | if payload_len < 4 { 154 | return Err(Error::ErrShortPacket); 155 | } 156 | // 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 157 | // +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ 158 | // |X|R|N|S|R| PID | (REQUIRED) |X|R|N|S|R| PID | (REQUIRED) 159 | // +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ 160 | // X: |I|L|T|K| RSV | (OPTIONAL) X: |I|L|T|K| RSV | (OPTIONAL) 161 | // +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ 162 | // I: |M| PictureID | (OPTIONAL) I: |M| PictureID | (OPTIONAL) 163 | // +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ 164 | // L: | tl0picidx | (OPTIONAL) | PictureID | 165 | // +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ 166 | //T/K:|tid|Y| KEYIDX | (OPTIONAL) L: | tl0picidx | (OPTIONAL) 167 | // +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ 168 | //T/K:|tid|Y| KEYIDX | (OPTIONAL) 169 | // +-+-+-+-+-+-+-+-+ 170 | 171 | let reader = &mut packet.clone(); 172 | let mut payload_index = 0; 173 | 174 | let mut b = reader.get_u8(); 175 | payload_index += 1; 176 | 177 | self.x = (b & 0x80) >> 7; 178 | self.n = (b & 0x20) >> 5; 179 | self.s = (b & 0x10) >> 4; 180 | self.pid = b & 0x07; 181 | 182 | if self.x == 1 { 183 | b = reader.get_u8(); 184 | payload_index += 1; 185 | self.i = (b & 0x80) >> 7; 186 | self.l = (b & 0x40) >> 6; 187 | self.t = (b & 0x20) >> 5; 188 | self.k = (b & 0x10) >> 4; 189 | } 190 | 191 | if self.i == 1 { 192 | b = reader.get_u8(); 193 | payload_index += 1; 194 | // PID present? 195 | if b & 0x80 > 0 { 196 | // M == 1, PID is 16bit 197 | self.picture_id = (((b & 0x7f) as u16) << 8) | (reader.get_u8() as u16); 198 | payload_index += 1; 199 | } else { 200 | self.picture_id = b as u16; 201 | } 202 | } 203 | 204 | if payload_index >= payload_len { 205 | return Err(Error::ErrShortPacket); 206 | } 207 | 208 | if self.l == 1 { 209 | self.tl0_pic_idx = reader.get_u8(); 210 | payload_index += 1; 211 | } 212 | 213 | if payload_index >= payload_len { 214 | return Err(Error::ErrShortPacket); 215 | } 216 | 217 | if self.t == 1 || self.k == 1 { 218 | let b = reader.get_u8(); 219 | if self.t == 1 { 220 | self.tid = b >> 6; 221 | self.y = (b >> 5) & 0x1; 222 | } 223 | if self.k == 1 { 224 | self.key_idx = b & 0x1F; 225 | } 226 | payload_index += 1; 227 | } 228 | 229 | if payload_index >= packet.len() { 230 | return Err(Error::ErrShortPacket); 231 | } 232 | 233 | Ok(packet.slice(payload_index..)) 234 | } 235 | 236 | /// is_partition_head checks whether if this is a head of the VP8 partition 237 | fn is_partition_head(&self, payload: &Bytes) -> bool { 238 | if payload.is_empty() { 239 | false 240 | } else { 241 | (payload[0] & 0x10) != 0 242 | } 243 | } 244 | 245 | fn is_partition_tail(&self, marker: bool, _payload: &Bytes) -> bool { 246 | marker 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /src/codecs/vp8/vp8_test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[test] 4 | fn test_vp8_unmarshal() -> Result<()> { 5 | let mut pck = Vp8Packet::default(); 6 | 7 | // Empty packet 8 | let empty_bytes = Bytes::from_static(&[]); 9 | let result = pck.depacketize(&empty_bytes); 10 | assert!(result.is_err(), "Result should be err in case of error"); 11 | 12 | // Payload smaller than header size 13 | let small_bytes = Bytes::from_static(&[0x00, 0x11, 0x22]); 14 | let result = pck.depacketize(&small_bytes); 15 | assert!(result.is_err(), "Result should be err in case of error"); 16 | 17 | // Payload smaller than header size 18 | let small_bytes = Bytes::from_static(&[0x00, 0x11]); 19 | let result = pck.depacketize(&small_bytes); 20 | assert!(result.is_err(), "Result should be err in case of error"); 21 | 22 | // Normal packet 23 | let raw_bytes = Bytes::from_static(&[0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x90]); 24 | let payload = pck.depacketize(&raw_bytes).expect("Normal packet"); 25 | assert!(!payload.is_empty(), "Payload must be not empty"); 26 | 27 | // Header size, only X 28 | let raw_bytes = Bytes::from_static(&[0x80, 0x00, 0x00, 0x00]); 29 | let payload = pck.depacketize(&raw_bytes).expect("Only X"); 30 | assert!(!payload.is_empty(), "Payload must be not empty"); 31 | assert_eq!(pck.x, 1, "X must be 1"); 32 | assert_eq!(pck.i, 0, "I must be 0"); 33 | assert_eq!(pck.l, 0, "L must be 0"); 34 | assert_eq!(pck.t, 0, "T must be 0"); 35 | assert_eq!(pck.k, 0, "K must be 0"); 36 | 37 | // Header size, X and I, PID 16bits 38 | let raw_bytes = Bytes::from_static(&[0x80, 0x80, 0x81, 0x00, 0x00]); 39 | let payload = pck.depacketize(&raw_bytes).expect("X and I, PID 16bits"); 40 | assert!(!payload.is_empty(), "Payload must be not empty"); 41 | assert_eq!(pck.x, 1, "X must be 1"); 42 | assert_eq!(pck.i, 1, "I must be 1"); 43 | assert_eq!(pck.l, 0, "L must be 0"); 44 | assert_eq!(pck.t, 0, "T must be 0"); 45 | assert_eq!(pck.k, 0, "K must be 0"); 46 | 47 | // Header size, X and L 48 | let raw_bytes = Bytes::from_static(&[0x80, 0x40, 0x00, 0x00]); 49 | let payload = pck.depacketize(&raw_bytes).expect("X and L"); 50 | assert!(!payload.is_empty(), "Payload must be not empty"); 51 | assert_eq!(pck.x, 1, "X must be 1"); 52 | assert_eq!(pck.i, 0, "I must be 0"); 53 | assert_eq!(pck.l, 1, "L must be 1"); 54 | assert_eq!(pck.t, 0, "T must be 0"); 55 | assert_eq!(pck.k, 0, "K must be 0"); 56 | 57 | // Header size, X and T 58 | let raw_bytes = Bytes::from_static(&[0x80, 0x20, 0x00, 0x00]); 59 | let payload = pck.depacketize(&raw_bytes).expect("X and T"); 60 | assert!(!payload.is_empty(), "Payload must be not empty"); 61 | assert_eq!(pck.x, 1, "X must be 1"); 62 | assert_eq!(pck.i, 0, "I must be 0"); 63 | assert_eq!(pck.l, 0, "L must be 0"); 64 | assert_eq!(pck.t, 1, "T must be 1"); 65 | assert_eq!(pck.k, 0, "K must be 0"); 66 | 67 | // Header size, X and K 68 | let raw_bytes = Bytes::from_static(&[0x80, 0x10, 0x00, 0x00]); 69 | let payload = pck.depacketize(&raw_bytes).expect("X and K"); 70 | assert!(!payload.is_empty(), "Payload must be not empty"); 71 | assert_eq!(pck.x, 1, "X must be 1"); 72 | assert_eq!(pck.i, 0, "I must be 0"); 73 | assert_eq!(pck.l, 0, "L must be 0"); 74 | assert_eq!(pck.t, 0, "T must be 0"); 75 | assert_eq!(pck.k, 1, "K must be 1"); 76 | 77 | // Header size, all flags and 8bit picture_id 78 | let raw_bytes = Bytes::from_static(&[0xff, 0xff, 0x00, 0x00, 0x00, 0x00]); 79 | let payload = pck 80 | .depacketize(&raw_bytes) 81 | .expect("all flags and 8bit picture_id"); 82 | assert!(!payload.is_empty(), "Payload must be not empty"); 83 | assert_eq!(pck.x, 1, "X must be 1"); 84 | assert_eq!(pck.i, 1, "I must be 1"); 85 | assert_eq!(pck.l, 1, "L must be 1"); 86 | assert_eq!(pck.t, 1, "T must be 1"); 87 | assert_eq!(pck.k, 1, "K must be 1"); 88 | 89 | // Header size, all flags and 16bit picture_id 90 | let raw_bytes = Bytes::from_static(&[0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00]); 91 | let payload = pck 92 | .depacketize(&raw_bytes) 93 | .expect("all flags and 16bit picture_id"); 94 | assert!(!payload.is_empty(), "Payload must be not empty"); 95 | assert_eq!(pck.x, 1, "X must be 1"); 96 | assert_eq!(pck.i, 1, "I must be 1"); 97 | assert_eq!(pck.l, 1, "L must be 1"); 98 | assert_eq!(pck.t, 1, "T must be 1"); 99 | assert_eq!(pck.k, 1, "K must be 1"); 100 | 101 | Ok(()) 102 | } 103 | 104 | #[test] 105 | fn test_vp8_payload() -> Result<()> { 106 | let tests = vec![ 107 | ( 108 | "WithoutPictureID", 109 | Vp8Payloader::default(), 110 | 2, 111 | vec![ 112 | Bytes::from_static(&[0x90, 0x90, 0x90]), 113 | Bytes::from_static(&[0x91, 0x91]), 114 | ], 115 | vec![ 116 | vec![ 117 | Bytes::from_static(&[0x10, 0x90]), 118 | Bytes::from_static(&[0x00, 0x90]), 119 | Bytes::from_static(&[0x00, 0x90]), 120 | ], 121 | vec![ 122 | Bytes::from_static(&[0x10, 0x91]), 123 | Bytes::from_static(&[0x00, 0x91]), 124 | ], 125 | ], 126 | ), 127 | ( 128 | "WithPictureID_1byte", 129 | Vp8Payloader { 130 | enable_picture_id: true, 131 | picture_id: 0x20, 132 | }, 133 | 5, 134 | vec![ 135 | Bytes::from_static(&[0x90, 0x90, 0x90]), 136 | Bytes::from_static(&[0x91, 0x91]), 137 | ], 138 | vec![ 139 | vec![ 140 | Bytes::from_static(&[0x90, 0x80, 0x20, 0x90, 0x90]), 141 | Bytes::from_static(&[0x80, 0x80, 0x20, 0x90]), 142 | ], 143 | vec![Bytes::from_static(&[0x90, 0x80, 0x21, 0x91, 0x91])], 144 | ], 145 | ), 146 | ( 147 | "WithPictureID_2bytes", 148 | Vp8Payloader { 149 | enable_picture_id: true, 150 | picture_id: 0x120, 151 | }, 152 | 6, 153 | vec![ 154 | Bytes::from_static(&[0x90, 0x90, 0x90]), 155 | Bytes::from_static(&[0x91, 0x91]), 156 | ], 157 | vec![ 158 | vec![ 159 | Bytes::from_static(&[0x90, 0x80, 0x81, 0x20, 0x90, 0x90]), 160 | Bytes::from_static(&[0x80, 0x80, 0x81, 0x20, 0x90]), 161 | ], 162 | vec![Bytes::from_static(&[0x90, 0x80, 0x81, 0x21, 0x91, 0x91])], 163 | ], 164 | ), 165 | ]; 166 | 167 | for (name, mut pck, mtu, payloads, expected) in tests { 168 | for (i, payload) in payloads.iter().enumerate() { 169 | let actual = pck.payload(mtu, payload)?; 170 | assert_eq!( 171 | expected[i], actual, 172 | "{}: Generated packet[{}] differs", 173 | name, i 174 | ); 175 | } 176 | } 177 | 178 | Ok(()) 179 | } 180 | 181 | #[test] 182 | fn test_vp8_payload_eror() -> Result<()> { 183 | let mut pck = Vp8Payloader::default(); 184 | let empty = Bytes::from_static(&[]); 185 | let payload = Bytes::from_static(&[0x90, 0x90, 0x90]); 186 | 187 | // Positive MTU, empty payload 188 | let result = pck.payload(1, &empty)?; 189 | assert!(result.is_empty(), "Generated payload should be empty"); 190 | 191 | // Positive MTU, small payload 192 | let result = pck.payload(1, &payload)?; 193 | assert_eq!(result.len(), 0, "Generated payload should be empty"); 194 | 195 | // Positive MTU, small payload 196 | let result = pck.payload(2, &payload)?; 197 | assert_eq!( 198 | result.len(), 199 | payload.len(), 200 | "Generated payload should be the same size as original payload size" 201 | ); 202 | 203 | Ok(()) 204 | } 205 | 206 | #[test] 207 | fn test_vp8_partition_head_checker_is_partition_head() -> Result<()> { 208 | let vp8 = Vp8Packet::default(); 209 | 210 | //"SmallPacket" 211 | assert!( 212 | !vp8.is_partition_head(&Bytes::from_static(&[0x00])), 213 | "Small packet should not be the head of a new partition" 214 | ); 215 | 216 | //"SFlagON", 217 | assert!( 218 | vp8.is_partition_head(&Bytes::from_static(&[0x10, 0x00, 0x00, 0x00])), 219 | "Packet with S flag should be the head of a new partition" 220 | ); 221 | 222 | //"SFlagOFF" 223 | assert!( 224 | !vp8.is_partition_head(&Bytes::from_static(&[0x00, 0x00, 0x00, 0x00])), 225 | "Packet without S flag should not be the head of a new partition" 226 | ); 227 | 228 | Ok(()) 229 | } 230 | -------------------------------------------------------------------------------- /src/codecs/vp9/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod vp9_test; 3 | 4 | use crate::{ 5 | error::{Error, Result}, 6 | packetizer::{Depacketizer, Payloader}, 7 | }; 8 | 9 | use bytes::{Buf, BufMut, Bytes, BytesMut}; 10 | use std::fmt; 11 | use std::sync::Arc; 12 | 13 | /// Flexible mode 15 bit picture ID 14 | const VP9HEADER_SIZE: usize = 3; 15 | const MAX_SPATIAL_LAYERS: u8 = 5; 16 | const MAX_VP9REF_PICS: usize = 3; 17 | 18 | /// InitialPictureIDFn is a function that returns random initial picture ID. 19 | pub type InitialPictureIDFn = Arc u16) + Send + Sync>; 20 | 21 | /// Vp9Payloader payloads VP9 packets 22 | #[derive(Default, Clone)] 23 | pub struct Vp9Payloader { 24 | picture_id: u16, 25 | initialized: bool, 26 | 27 | pub initial_picture_id_fn: Option, 28 | } 29 | 30 | impl fmt::Debug for Vp9Payloader { 31 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 32 | f.debug_struct("Vp9Payloader") 33 | .field("picture_id", &self.picture_id) 34 | .field("initialized", &self.initialized) 35 | .finish() 36 | } 37 | } 38 | 39 | impl Payloader for Vp9Payloader { 40 | /// Payload fragments an Vp9Payloader packet across one or more byte arrays 41 | fn payload(&mut self, mtu: usize, payload: &Bytes) -> Result> { 42 | /* 43 | * https://www.ietf.org/id/draft-ietf-payload-vp9-13.txt 44 | * 45 | * Flexible mode (F=1) 46 | * 0 1 2 3 4 5 6 7 47 | * +-+-+-+-+-+-+-+-+ 48 | * |I|P|L|F|B|E|V|Z| (REQUIRED) 49 | * +-+-+-+-+-+-+-+-+ 50 | * I: |M| PICTURE ID | (REQUIRED) 51 | * +-+-+-+-+-+-+-+-+ 52 | * M: | EXTENDED PID | (RECOMMENDED) 53 | * +-+-+-+-+-+-+-+-+ 54 | * L: | tid |U| SID |D| (CONDITIONALLY RECOMMENDED) 55 | * +-+-+-+-+-+-+-+-+ -\ 56 | * P,F: | P_DIFF |N| (CONDITIONALLY REQUIRED) - up to 3 times 57 | * +-+-+-+-+-+-+-+-+ -/ 58 | * V: | SS | 59 | * | .. | 60 | * +-+-+-+-+-+-+-+-+ 61 | * 62 | * Non-flexible mode (F=0) 63 | * 0 1 2 3 4 5 6 7 64 | * +-+-+-+-+-+-+-+-+ 65 | * |I|P|L|F|B|E|V|Z| (REQUIRED) 66 | * +-+-+-+-+-+-+-+-+ 67 | * I: |M| PICTURE ID | (RECOMMENDED) 68 | * +-+-+-+-+-+-+-+-+ 69 | * M: | EXTENDED PID | (RECOMMENDED) 70 | * +-+-+-+-+-+-+-+-+ 71 | * L: | tid |U| SID |D| (CONDITIONALLY RECOMMENDED) 72 | * +-+-+-+-+-+-+-+-+ 73 | * | tl0picidx | (CONDITIONALLY REQUIRED) 74 | * +-+-+-+-+-+-+-+-+ 75 | * V: | SS | 76 | * | .. | 77 | * +-+-+-+-+-+-+-+-+ 78 | */ 79 | 80 | if payload.is_empty() || mtu == 0 { 81 | return Ok(vec![]); 82 | } 83 | 84 | if !self.initialized { 85 | if self.initial_picture_id_fn.is_none() { 86 | self.initial_picture_id_fn = 87 | Some(Arc::new(|| -> u16 { rand::random::() & 0x7FFF })); 88 | } 89 | self.picture_id = if let Some(f) = &self.initial_picture_id_fn { 90 | f() 91 | } else { 92 | 0 93 | }; 94 | self.initialized = true; 95 | } 96 | 97 | let max_fragment_size = mtu as isize - VP9HEADER_SIZE as isize; 98 | let mut payloads = vec![]; 99 | let mut payload_data_remaining = payload.len(); 100 | let mut payload_data_index = 0; 101 | 102 | if std::cmp::min(max_fragment_size, payload_data_remaining as isize) <= 0 { 103 | return Ok(vec![]); 104 | } 105 | 106 | while payload_data_remaining > 0 { 107 | let current_fragment_size = 108 | std::cmp::min(max_fragment_size as usize, payload_data_remaining); 109 | let mut out = BytesMut::with_capacity(VP9HEADER_SIZE + current_fragment_size); 110 | let mut buf = vec![0u8; VP9HEADER_SIZE]; 111 | buf[0] = 0x90; // F=1 I=1 112 | if payload_data_index == 0 { 113 | buf[0] |= 0x08; // B=1 114 | } 115 | if payload_data_remaining == current_fragment_size { 116 | buf[0] |= 0x04; // E=1 117 | } 118 | buf[1] = (self.picture_id >> 8) as u8 | 0x80; 119 | buf[2] = (self.picture_id & 0xFF) as u8; 120 | 121 | out.put(&buf[..]); 122 | 123 | out.put( 124 | &*payload.slice(payload_data_index..payload_data_index + current_fragment_size), 125 | ); 126 | 127 | payloads.push(out.freeze()); 128 | 129 | payload_data_remaining -= current_fragment_size; 130 | payload_data_index += current_fragment_size; 131 | } 132 | 133 | self.picture_id += 1; 134 | self.picture_id &= 0x7FFF; 135 | 136 | Ok(payloads) 137 | } 138 | 139 | fn clone_to(&self) -> Box { 140 | Box::new(self.clone()) 141 | } 142 | } 143 | 144 | /// Vp9Packet represents the VP9 header that is stored in the payload of an RTP Packet 145 | #[derive(PartialEq, Eq, Debug, Default, Clone)] 146 | pub struct Vp9Packet { 147 | /// picture ID is present 148 | pub i: bool, 149 | /// inter-picture predicted frame. 150 | pub p: bool, 151 | /// layer indices present 152 | pub l: bool, 153 | /// flexible mode 154 | pub f: bool, 155 | /// start of frame. beginning of new vp9 frame 156 | pub b: bool, 157 | /// end of frame 158 | pub e: bool, 159 | /// scalability structure (SS) present 160 | pub v: bool, 161 | /// Not a reference frame for upper spatial layers 162 | pub z: bool, 163 | 164 | /// Recommended headers 165 | /// 7 or 16 bits, picture ID. 166 | pub picture_id: u16, 167 | 168 | /// Conditionally recommended headers 169 | /// Temporal layer ID 170 | pub tid: u8, 171 | /// Switching up point 172 | pub u: bool, 173 | /// Spatial layer ID 174 | pub sid: u8, 175 | /// Inter-layer dependency used 176 | pub d: bool, 177 | 178 | /// Conditionally required headers 179 | /// Reference index (F=1) 180 | pub pdiff: Vec, 181 | /// Temporal layer zero index (F=0) 182 | pub tl0picidx: u8, 183 | 184 | /// Scalability structure headers 185 | /// N_S + 1 indicates the number of spatial layers present in the VP9 stream 186 | pub ns: u8, 187 | /// Each spatial layer's frame resolution present 188 | pub y: bool, 189 | /// PG description present flag. 190 | pub g: bool, 191 | /// N_G indicates the number of pictures in a Picture Group (PG) 192 | pub ng: u8, 193 | pub width: Vec, 194 | pub height: Vec, 195 | /// Temporal layer ID of pictures in a Picture Group 196 | pub pgtid: Vec, 197 | /// Switching up point of pictures in a Picture Group 198 | pub pgu: Vec, 199 | /// Reference indecies of pictures in a Picture Group 200 | pub pgpdiff: Vec>, 201 | } 202 | 203 | impl Depacketizer for Vp9Packet { 204 | /// depacketize parses the passed byte slice and stores the result in the Vp9Packet this method is called upon 205 | fn depacketize(&mut self, packet: &Bytes) -> Result { 206 | if packet.is_empty() { 207 | return Err(Error::ErrShortPacket); 208 | } 209 | 210 | let reader = &mut packet.clone(); 211 | let b = reader.get_u8(); 212 | 213 | self.i = (b & 0x80) != 0; 214 | self.p = (b & 0x40) != 0; 215 | self.l = (b & 0x20) != 0; 216 | self.f = (b & 0x10) != 0; 217 | self.b = (b & 0x08) != 0; 218 | self.e = (b & 0x04) != 0; 219 | self.v = (b & 0x02) != 0; 220 | self.z = (b & 0x01) != 0; 221 | 222 | let mut payload_index = 1; 223 | 224 | if self.i { 225 | payload_index = self.parse_picture_id(reader, payload_index)?; 226 | } 227 | 228 | if self.l { 229 | payload_index = self.parse_layer_info(reader, payload_index)?; 230 | } 231 | 232 | if self.f && self.p { 233 | payload_index = self.parse_ref_indices(reader, payload_index)?; 234 | } 235 | 236 | if self.v { 237 | payload_index = self.parse_ssdata(reader, payload_index)?; 238 | } 239 | 240 | Ok(packet.slice(payload_index..)) 241 | } 242 | 243 | /// is_partition_head checks whether if this is a head of the VP9 partition 244 | fn is_partition_head(&self, payload: &Bytes) -> bool { 245 | if payload.is_empty() { 246 | false 247 | } else { 248 | (payload[0] & 0x08) != 0 249 | } 250 | } 251 | 252 | fn is_partition_tail(&self, marker: bool, _payload: &Bytes) -> bool { 253 | marker 254 | } 255 | } 256 | 257 | impl Vp9Packet { 258 | // Picture ID: 259 | // 260 | // +-+-+-+-+-+-+-+-+ 261 | // I: |M| PICTURE ID | M:0 => picture id is 7 bits. 262 | // +-+-+-+-+-+-+-+-+ M:1 => picture id is 15 bits. 263 | // M: | EXTENDED PID | 264 | // +-+-+-+-+-+-+-+-+ 265 | // 266 | fn parse_picture_id( 267 | &mut self, 268 | reader: &mut dyn Buf, 269 | mut payload_index: usize, 270 | ) -> Result { 271 | if reader.remaining() == 0 { 272 | return Err(Error::ErrShortPacket); 273 | } 274 | let b = reader.get_u8(); 275 | payload_index += 1; 276 | // PID present? 277 | if (b & 0x80) != 0 { 278 | if reader.remaining() == 0 { 279 | return Err(Error::ErrShortPacket); 280 | } 281 | // M == 1, PID is 15bit 282 | self.picture_id = (((b & 0x7f) as u16) << 8) | (reader.get_u8() as u16); 283 | payload_index += 1; 284 | } else { 285 | self.picture_id = (b & 0x7F) as u16; 286 | } 287 | 288 | Ok(payload_index) 289 | } 290 | 291 | fn parse_layer_info( 292 | &mut self, 293 | reader: &mut dyn Buf, 294 | mut payload_index: usize, 295 | ) -> Result { 296 | payload_index = self.parse_layer_info_common(reader, payload_index)?; 297 | 298 | if self.f { 299 | Ok(payload_index) 300 | } else { 301 | self.parse_layer_info_non_flexible_mode(reader, payload_index) 302 | } 303 | } 304 | 305 | // Layer indices (flexible mode): 306 | // 307 | // +-+-+-+-+-+-+-+-+ 308 | // L: | T |U| S |D| 309 | // +-+-+-+-+-+-+-+-+ 310 | // 311 | fn parse_layer_info_common( 312 | &mut self, 313 | reader: &mut dyn Buf, 314 | mut payload_index: usize, 315 | ) -> Result { 316 | if reader.remaining() == 0 { 317 | return Err(Error::ErrShortPacket); 318 | } 319 | let b = reader.get_u8(); 320 | payload_index += 1; 321 | 322 | self.tid = b >> 5; 323 | self.u = b & 0x10 != 0; 324 | self.sid = (b >> 1) & 0x7; 325 | self.d = b & 0x01 != 0; 326 | 327 | if self.sid >= MAX_SPATIAL_LAYERS { 328 | Err(Error::ErrTooManySpatialLayers) 329 | } else { 330 | Ok(payload_index) 331 | } 332 | } 333 | 334 | // Layer indices (non-flexible mode): 335 | // 336 | // +-+-+-+-+-+-+-+-+ 337 | // L: | T |U| S |D| 338 | // +-+-+-+-+-+-+-+-+ 339 | // | tl0picidx | 340 | // +-+-+-+-+-+-+-+-+ 341 | // 342 | fn parse_layer_info_non_flexible_mode( 343 | &mut self, 344 | reader: &mut dyn Buf, 345 | mut payload_index: usize, 346 | ) -> Result { 347 | if reader.remaining() == 0 { 348 | return Err(Error::ErrShortPacket); 349 | } 350 | self.tl0picidx = reader.get_u8(); 351 | payload_index += 1; 352 | Ok(payload_index) 353 | } 354 | 355 | // Reference indices: 356 | // 357 | // +-+-+-+-+-+-+-+-+ P=1,F=1: At least one reference index 358 | // P,F: | P_DIFF |N| up to 3 times has to be specified. 359 | // +-+-+-+-+-+-+-+-+ N=1: An additional P_DIFF follows 360 | // current P_DIFF. 361 | // 362 | fn parse_ref_indices( 363 | &mut self, 364 | reader: &mut dyn Buf, 365 | mut payload_index: usize, 366 | ) -> Result { 367 | let mut b = 1u8; 368 | while (b & 0x1) != 0 { 369 | if reader.remaining() == 0 { 370 | return Err(Error::ErrShortPacket); 371 | } 372 | b = reader.get_u8(); 373 | payload_index += 1; 374 | 375 | self.pdiff.push(b >> 1); 376 | if self.pdiff.len() >= MAX_VP9REF_PICS { 377 | return Err(Error::ErrTooManyPDiff); 378 | } 379 | } 380 | 381 | Ok(payload_index) 382 | } 383 | 384 | // Scalability structure (SS): 385 | // 386 | // +-+-+-+-+-+-+-+-+ 387 | // V: | N_S |Y|G|-|-|-| 388 | // +-+-+-+-+-+-+-+-+ -| 389 | // Y: | WIDTH | (OPTIONAL) . 390 | // + + . 391 | // | | (OPTIONAL) . 392 | // +-+-+-+-+-+-+-+-+ . N_S + 1 times 393 | // | HEIGHT | (OPTIONAL) . 394 | // + + . 395 | // | | (OPTIONAL) . 396 | // +-+-+-+-+-+-+-+-+ -| 397 | // G: | N_G | (OPTIONAL) 398 | // +-+-+-+-+-+-+-+-+ -| 399 | // N_G: | T |U| R |-|-| (OPTIONAL) . 400 | // +-+-+-+-+-+-+-+-+ -| . N_G times 401 | // | P_DIFF | (OPTIONAL) . R times . 402 | // +-+-+-+-+-+-+-+-+ -| -| 403 | // 404 | fn parse_ssdata(&mut self, reader: &mut dyn Buf, mut payload_index: usize) -> Result { 405 | if reader.remaining() == 0 { 406 | return Err(Error::ErrShortPacket); 407 | } 408 | 409 | let b = reader.get_u8(); 410 | payload_index += 1; 411 | 412 | self.ns = b >> 5; 413 | self.y = b & 0x10 != 0; 414 | self.g = (b >> 1) & 0x7 != 0; 415 | 416 | let ns = (self.ns + 1) as usize; 417 | self.ng = 0; 418 | 419 | if self.y { 420 | if reader.remaining() < 4 * ns { 421 | return Err(Error::ErrShortPacket); 422 | } 423 | 424 | self.width = vec![0u16; ns]; 425 | self.height = vec![0u16; ns]; 426 | for i in 0..ns { 427 | self.width[i] = reader.get_u16(); 428 | self.height[i] = reader.get_u16(); 429 | } 430 | payload_index += 4 * ns; 431 | } 432 | 433 | if self.g { 434 | if reader.remaining() == 0 { 435 | return Err(Error::ErrShortPacket); 436 | } 437 | 438 | self.ng = reader.get_u8(); 439 | payload_index += 1; 440 | } 441 | 442 | for i in 0..self.ng as usize { 443 | if reader.remaining() == 0 { 444 | return Err(Error::ErrShortPacket); 445 | } 446 | let b = reader.get_u8(); 447 | payload_index += 1; 448 | 449 | self.pgtid.push(b >> 5); 450 | self.pgu.push(b & 0x10 != 0); 451 | 452 | let r = ((b >> 2) & 0x3) as usize; 453 | if reader.remaining() < r { 454 | return Err(Error::ErrShortPacket); 455 | } 456 | 457 | self.pgpdiff.push(vec![]); 458 | for _ in 0..r { 459 | let b = reader.get_u8(); 460 | payload_index += 1; 461 | 462 | self.pgpdiff[i].push(b); 463 | } 464 | } 465 | 466 | Ok(payload_index) 467 | } 468 | } 469 | -------------------------------------------------------------------------------- /src/codecs/vp9/vp9_test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[test] 4 | fn test_vp9_packet_unmarshal() -> Result<()> { 5 | let tests = vec![ 6 | ( 7 | "Empty", 8 | Bytes::from_static(&[]), 9 | Vp9Packet::default(), 10 | Bytes::new(), 11 | Some(Error::ErrShortPacket), 12 | ), 13 | ( 14 | "NonFlexible", 15 | Bytes::from_static(&[0x00, 0xAA]), 16 | Vp9Packet::default(), 17 | Bytes::from_static(&[0xAA]), 18 | None, 19 | ), 20 | ( 21 | "NonFlexiblePictureID", 22 | Bytes::from_static(&[0x80, 0x02, 0xAA]), 23 | Vp9Packet { 24 | i: true, 25 | picture_id: 0x02, 26 | ..Default::default() 27 | }, 28 | Bytes::from_static(&[0xAA]), 29 | None, 30 | ), 31 | ( 32 | "NonFlexiblePictureIDExt", 33 | Bytes::from_static(&[0x80, 0x81, 0xFF, 0xAA]), 34 | Vp9Packet { 35 | i: true, 36 | picture_id: 0x01FF, 37 | ..Default::default() 38 | }, 39 | Bytes::from_static(&[0xAA]), 40 | None, 41 | ), 42 | ( 43 | "NonFlexiblePictureIDExt_ShortPacket0", 44 | Bytes::from_static(&[0x80, 0x81]), 45 | Vp9Packet::default(), 46 | Bytes::new(), 47 | Some(Error::ErrShortPacket), 48 | ), 49 | ( 50 | "NonFlexiblePictureIDExt_ShortPacket1", 51 | Bytes::from_static(&[0x80]), 52 | Vp9Packet::default(), 53 | Bytes::new(), 54 | Some(Error::ErrShortPacket), 55 | ), 56 | ( 57 | "NonFlexibleLayerIndicePictureID", 58 | Bytes::from_static(&[0xA0, 0x02, 0x23, 0x01, 0xAA]), 59 | Vp9Packet { 60 | i: true, 61 | l: true, 62 | picture_id: 0x02, 63 | tid: 0x01, 64 | sid: 0x01, 65 | d: true, 66 | tl0picidx: 0x01, 67 | ..Default::default() 68 | }, 69 | Bytes::from_static(&[0xAA]), 70 | None, 71 | ), 72 | ( 73 | "FlexibleLayerIndicePictureID", 74 | Bytes::from_static(&[0xB0, 0x02, 0x23, 0x01, 0xAA]), 75 | Vp9Packet { 76 | f: true, 77 | i: true, 78 | l: true, 79 | picture_id: 0x02, 80 | tid: 0x01, 81 | sid: 0x01, 82 | d: true, 83 | ..Default::default() 84 | }, 85 | Bytes::from_static(&[0x01, 0xAA]), 86 | None, 87 | ), 88 | ( 89 | "NonFlexibleLayerIndicePictureID_ShortPacket0", 90 | Bytes::from_static(&[0xA0, 0x02, 0x23]), 91 | Vp9Packet::default(), 92 | Bytes::new(), 93 | Some(Error::ErrShortPacket), 94 | ), 95 | ( 96 | "NonFlexibleLayerIndicePictureID_ShortPacket1", 97 | Bytes::from_static(&[0xA0, 0x02]), 98 | Vp9Packet::default(), 99 | Bytes::new(), 100 | Some(Error::ErrShortPacket), 101 | ), 102 | ( 103 | "FlexiblePictureIDRefIndex", 104 | Bytes::from_static(&[0xD0, 0x02, 0x03, 0x04, 0xAA]), 105 | Vp9Packet { 106 | i: true, 107 | p: true, 108 | f: true, 109 | picture_id: 0x02, 110 | pdiff: vec![0x01, 0x02], 111 | ..Default::default() 112 | }, 113 | Bytes::from_static(&[0xAA]), 114 | None, 115 | ), 116 | ( 117 | "FlexiblePictureIDRefIndex_TooManyPDiff", 118 | Bytes::from_static(&[0xD0, 0x02, 0x03, 0x05, 0x07, 0x09, 0x10, 0xAA]), 119 | Vp9Packet::default(), 120 | Bytes::new(), 121 | Some(Error::ErrTooManyPDiff), 122 | ), 123 | ( 124 | "FlexiblePictureIDRefIndexNoPayload", 125 | Bytes::from_static(&[0xD0, 0x02, 0x03, 0x04]), 126 | Vp9Packet { 127 | i: true, 128 | p: true, 129 | f: true, 130 | picture_id: 0x02, 131 | pdiff: vec![0x01, 0x02], 132 | ..Default::default() 133 | }, 134 | Bytes::from_static(&[]), 135 | None, 136 | ), 137 | ( 138 | "FlexiblePictureIDRefIndex_ShortPacket0", 139 | Bytes::from_static(&[0xD0, 0x02, 0x03]), 140 | Vp9Packet::default(), 141 | Bytes::new(), 142 | Some(Error::ErrShortPacket), 143 | ), 144 | ( 145 | "FlexiblePictureIDRefIndex_ShortPacket1", 146 | Bytes::from_static(&[0xD0, 0x02]), 147 | Vp9Packet::default(), 148 | Bytes::new(), 149 | Some(Error::ErrShortPacket), 150 | ), 151 | ( 152 | "FlexiblePictureIDRefIndex_ShortPacket2", 153 | Bytes::from_static(&[0xD0]), 154 | Vp9Packet::default(), 155 | Bytes::new(), 156 | Some(Error::ErrShortPacket), 157 | ), 158 | ( 159 | "ScalabilityStructureResolutionsNoPayload", 160 | Bytes::from_static(&[ 161 | 0x0A, 162 | (1 << 5) | (1 << 4), // NS:1 Y:1 G:0 163 | (640 >> 8) as u8, 164 | (640 & 0xff) as u8, 165 | (360 >> 8) as u8, 166 | (360 & 0xff) as u8, 167 | (1280 >> 8) as u8, 168 | (1280 & 0xff) as u8, 169 | (720 >> 8) as u8, 170 | (720 & 0xff) as u8, 171 | ]), 172 | Vp9Packet { 173 | b: true, 174 | v: true, 175 | ns: 1, 176 | y: true, 177 | g: false, 178 | ng: 0, 179 | width: vec![640, 1280], 180 | height: vec![360, 720], 181 | ..Default::default() 182 | }, 183 | Bytes::new(), 184 | None, 185 | ), 186 | ( 187 | "ScalabilityStructureNoPayload", 188 | Bytes::from_static(&[ 189 | 0x0A, 190 | (1 << 5) | (1 << 3), // NS:1 Y:0 G:1 191 | 2, 192 | (1 << 4), // T:0 U:1 R:0 - 193 | (2 << 5) | (1 << 2), // T:2 U:0 R:1 - 194 | 33, 195 | ]), 196 | Vp9Packet { 197 | b: true, 198 | v: true, 199 | ns: 1, 200 | y: false, 201 | g: true, 202 | ng: 2, 203 | pgtid: vec![0, 2], 204 | pgu: vec![true, false], 205 | pgpdiff: vec![vec![], vec![33]], 206 | ..Default::default() 207 | }, 208 | Bytes::new(), 209 | None, 210 | ), 211 | ]; 212 | 213 | for (name, b, pkt, expected, err) in tests { 214 | let mut p = Vp9Packet::default(); 215 | 216 | if let Some(expected) = err { 217 | if let Err(actual) = p.depacketize(&b) { 218 | assert_eq!( 219 | expected, actual, 220 | "{}: expected {}, but got {}", 221 | name, expected, actual 222 | ); 223 | } else { 224 | assert!(false, "{}: expected error, but got passed", name); 225 | } 226 | } else { 227 | let payload = p.depacketize(&b)?; 228 | assert_eq!(pkt, p, "{}: expected {:?}, but got {:?}", name, pkt, p); 229 | assert_eq!(payload, expected); 230 | } 231 | } 232 | 233 | Ok(()) 234 | } 235 | 236 | #[test] 237 | fn test_vp9_payloader_payload() -> Result<()> { 238 | let mut r0 = 8692; 239 | let mut rands = vec![]; 240 | for _ in 0..10 { 241 | rands.push(vec![(r0 >> 8) as u8 | 0x80, (r0 & 0xFF) as u8]); 242 | r0 += 1; 243 | } 244 | 245 | let tests = vec![ 246 | ("NilPayload", vec![Bytes::new()], 100, vec![]), 247 | ("SmallMTU", vec![Bytes::from(vec![0x00, 0x00])], 1, vec![]), 248 | ( 249 | "NegativeMTU", 250 | vec![Bytes::from(vec![0x00, 0x00])], 251 | 0, 252 | vec![], 253 | ), 254 | ( 255 | "OnePacket", 256 | vec![Bytes::from(vec![0x01, 0x02])], 257 | 10, 258 | vec![Bytes::from(vec![ 259 | 0x9C, 260 | rands[0][0], 261 | rands[0][1], 262 | 0x01, 263 | 0x02, 264 | ])], 265 | ), 266 | ( 267 | "TwoPackets", 268 | vec![Bytes::from(vec![0x01, 0x02])], 269 | 4, 270 | vec![ 271 | Bytes::from(vec![0x98, rands[0][0], rands[0][1], 0x01]), 272 | Bytes::from(vec![0x94, rands[0][0], rands[0][1], 0x02]), 273 | ], 274 | ), 275 | ( 276 | "ThreePackets", 277 | vec![Bytes::from(vec![0x01, 0x02, 0x03])], 278 | 4, 279 | vec![ 280 | Bytes::from(vec![0x98, rands[0][0], rands[0][1], 0x01]), 281 | Bytes::from(vec![0x90, rands[0][0], rands[0][1], 0x02]), 282 | Bytes::from(vec![0x94, rands[0][0], rands[0][1], 0x03]), 283 | ], 284 | ), 285 | ( 286 | "TwoFramesFourPackets", 287 | vec![Bytes::from(vec![0x01, 0x02, 0x03]), Bytes::from(vec![0x04])], 288 | 5, 289 | vec![ 290 | Bytes::from(vec![0x98, rands[0][0], rands[0][1], 0x01, 0x02]), 291 | Bytes::from(vec![0x94, rands[0][0], rands[0][1], 0x03]), 292 | Bytes::from(vec![0x9C, rands[1][0], rands[1][1], 0x04]), 293 | ], 294 | ), 295 | ]; 296 | 297 | for (name, bs, mtu, expected) in tests { 298 | let mut pck = Vp9Payloader { 299 | initial_picture_id_fn: Some(Arc::new(|| -> u16 { 8692 })), 300 | ..Default::default() 301 | }; 302 | 303 | let mut actual = vec![]; 304 | for b in &bs { 305 | actual.extend(pck.payload(mtu, b)?); 306 | } 307 | assert_eq!(expected, actual, "{}: Payloaded packet", name); 308 | } 309 | 310 | //"PictureIDOverflow" 311 | { 312 | let mut pck = Vp9Payloader { 313 | initial_picture_id_fn: Some(Arc::new(|| -> u16 { 8692 })), 314 | ..Default::default() 315 | }; 316 | let mut p_prev = Vp9Packet::default(); 317 | for i in 0..0x8000 { 318 | let res = pck.payload(4, &Bytes::from_static(&[0x01]))?; 319 | let mut p = Vp9Packet::default(); 320 | p.depacketize(&res[0])?; 321 | 322 | if i > 0 { 323 | if p_prev.picture_id == 0x7FFF { 324 | assert_eq!( 325 | p.picture_id, 0, 326 | "Picture ID next to 0x7FFF must be 0, got {}", 327 | p.picture_id 328 | ); 329 | } else if p_prev.picture_id + 1 != p.picture_id { 330 | assert!( 331 | false, 332 | "Picture ID next must be incremented by 1: {} -> {}", 333 | p_prev.picture_id, p.picture_id, 334 | ); 335 | } 336 | } 337 | 338 | p_prev = p; 339 | } 340 | } 341 | 342 | Ok(()) 343 | } 344 | 345 | #[test] 346 | fn test_vp9_partition_head_checker_is_partition_head() -> Result<()> { 347 | let vp9 = Vp9Packet::default(); 348 | 349 | //"SmallPacket" 350 | assert!( 351 | !vp9.is_partition_head(&Bytes::new()), 352 | "Small packet should not be the head of a new partition" 353 | ); 354 | 355 | //"NormalPacket" 356 | assert!( 357 | vp9.is_partition_head(&Bytes::from_static(&[0x18, 0x00, 0x00])), 358 | "VP9 RTP packet with B flag should be head of a new partition" 359 | ); 360 | assert!( 361 | !vp9.is_partition_head(&Bytes::from_static(&[0x10, 0x00, 0x00])), 362 | "VP9 RTP packet without B flag should not be head of a new partition" 363 | ); 364 | 365 | Ok(()) 366 | } 367 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | pub type Result = std::result::Result; 4 | 5 | #[derive(Error, Debug, PartialEq)] 6 | #[non_exhaustive] 7 | pub enum Error { 8 | #[error("RTP header size insufficient")] 9 | ErrHeaderSizeInsufficient, 10 | #[error("RTP header size insufficient for extension")] 11 | ErrHeaderSizeInsufficientForExtension, 12 | #[error("buffer too small")] 13 | ErrBufferTooSmall, 14 | #[error("extension not enabled")] 15 | ErrHeaderExtensionsNotEnabled, 16 | #[error("extension not found")] 17 | ErrHeaderExtensionNotFound, 18 | 19 | #[error("header extension id must be between 1 and 14 for RFC 5285 extensions")] 20 | ErrRfc8285oneByteHeaderIdrange, 21 | #[error("header extension payload must be 16bytes or less for RFC 5285 one byte extensions")] 22 | ErrRfc8285oneByteHeaderSize, 23 | 24 | #[error("header extension id must be between 1 and 255 for RFC 5285 extensions")] 25 | ErrRfc8285twoByteHeaderIdrange, 26 | #[error("header extension payload must be 255bytes or less for RFC 5285 two byte extensions")] 27 | ErrRfc8285twoByteHeaderSize, 28 | 29 | #[error("header extension id must be 0 for none RFC 5285 extensions")] 30 | ErrRfc3550headerIdrange, 31 | 32 | #[error("packet is not large enough")] 33 | ErrShortPacket, 34 | #[error("invalid nil packet")] 35 | ErrNilPacket, 36 | #[error("too many PDiff")] 37 | ErrTooManyPDiff, 38 | #[error("too many spatial layers")] 39 | ErrTooManySpatialLayers, 40 | #[error("NALU Type is unhandled")] 41 | ErrUnhandledNaluType, 42 | 43 | #[error("corrupted h265 packet")] 44 | ErrH265CorruptedPacket, 45 | #[error("invalid h265 packet type")] 46 | ErrInvalidH265PacketType, 47 | 48 | #[error("extension_payload must be in 32-bit words")] 49 | HeaderExtensionPayloadNot32BitWords, 50 | #[error("audio level overflow")] 51 | AudioLevelOverflow, 52 | #[error("payload is not large enough")] 53 | PayloadIsNotLargeEnough, 54 | #[error("STAP-A declared size({0}) is larger than buffer({1})")] 55 | StapASizeLargerThanBuffer(usize, usize), 56 | #[error("nalu type {0} is currently not handled")] 57 | NaluTypeIsNotHandled(u8), 58 | #[error("{0}")] 59 | Util(#[from] util::Error), 60 | 61 | #[error("{0}")] 62 | Other(String), 63 | } 64 | 65 | impl From for util::Error { 66 | fn from(e: Error) -> Self { 67 | util::Error::from_std(e) 68 | } 69 | } 70 | 71 | impl PartialEq for Error { 72 | fn eq(&self, other: &util::Error) -> bool { 73 | if let Some(down) = other.downcast_ref::() { 74 | self == down 75 | } else { 76 | false 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/extension/abs_send_time_extension/abs_send_time_extension_test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::error::Result; 3 | 4 | use bytes::BytesMut; 5 | use chrono::prelude::*; 6 | use std::time::Duration; 7 | 8 | const ABS_SEND_TIME_RESOLUTION: i128 = 1000; 9 | 10 | #[test] 11 | fn test_ntp_conversion() -> Result<()> { 12 | let loc = FixedOffset::west(5 * 60 * 60); // UTC-5 13 | let tests = vec![ 14 | ( 15 | loc.ymd(1985, 6, 23).and_hms_nano(4, 0, 0, 0), 16 | 0xa0c65b1000000000_u64, 17 | ), 18 | ( 19 | loc.ymd(1999, 12, 31).and_hms_nano(23, 59, 59, 500000), 20 | 0xbc18084f0020c49b_u64, 21 | ), 22 | ( 23 | loc.ymd(2019, 3, 27).and_hms_nano(13, 39, 30, 8675309), 24 | 0xe04641e202388b88_u64, 25 | ), 26 | ]; 27 | 28 | for (t, n) in &tests { 29 | let st = UNIX_EPOCH 30 | .checked_add(Duration::from_nanos(t.timestamp_nanos() as u64)) 31 | .unwrap_or(UNIX_EPOCH); 32 | let ntp = unix2ntp(st); 33 | 34 | if cfg!(target_os = "windows") { 35 | let actual = ntp as i128; 36 | let expected = *n as i128; 37 | let diff = actual - expected; 38 | if !(-ABS_SEND_TIME_RESOLUTION..=ABS_SEND_TIME_RESOLUTION).contains(&diff) { 39 | assert!(false, "unix2ntp error, expected: {:?}, got: {:?}", ntp, *n,); 40 | } 41 | } else { 42 | assert_eq!(ntp, *n, "unix2ntp error"); 43 | } 44 | } 45 | 46 | for (t, n) in &tests { 47 | let output = ntp2unix(*n); 48 | let input = UNIX_EPOCH 49 | .checked_add(Duration::from_nanos(t.timestamp_nanos() as u64)) 50 | .unwrap_or(UNIX_EPOCH); 51 | let diff = input.duration_since(output).unwrap().as_nanos() as i128; 52 | if !(-ABS_SEND_TIME_RESOLUTION..=ABS_SEND_TIME_RESOLUTION).contains(&diff) { 53 | assert!( 54 | false, 55 | "Converted time.Time from NTP time differs, expected: {:?}, got: {:?}", 56 | input, output, 57 | ); 58 | } 59 | } 60 | 61 | Ok(()) 62 | } 63 | 64 | #[test] 65 | fn test_abs_send_time_extension_roundtrip() -> Result<()> { 66 | let tests = vec![ 67 | AbsSendTimeExtension { timestamp: 123456 }, 68 | AbsSendTimeExtension { timestamp: 654321 }, 69 | ]; 70 | 71 | for test in &tests { 72 | let mut raw = BytesMut::with_capacity(test.marshal_size()); 73 | raw.resize(test.marshal_size(), 0); 74 | test.marshal_to(&mut raw)?; 75 | let raw = raw.freeze(); 76 | let buf = &mut raw.clone(); 77 | let out = AbsSendTimeExtension::unmarshal(buf)?; 78 | assert_eq!(test.timestamp, out.timestamp); 79 | } 80 | 81 | Ok(()) 82 | } 83 | 84 | #[test] 85 | fn test_abs_send_time_extension_estimate() -> Result<()> { 86 | let tests = vec![ 87 | //FFFFFFC000000000 mask of second 88 | (0xa0c65b1000100000, 0xa0c65b1001000000), // not carried 89 | (0xa0c65b3f00000000, 0xa0c65b4001000000), // carried during transmission 90 | ]; 91 | 92 | for (send_ntp, receive_ntp) in tests { 93 | let in_time = ntp2unix(send_ntp); 94 | let send = AbsSendTimeExtension { 95 | timestamp: send_ntp >> 14, 96 | }; 97 | let mut raw = BytesMut::with_capacity(send.marshal_size()); 98 | raw.resize(send.marshal_size(), 0); 99 | send.marshal_to(&mut raw)?; 100 | let raw = raw.freeze(); 101 | let buf = &mut raw.clone(); 102 | let receive = AbsSendTimeExtension::unmarshal(buf)?; 103 | 104 | let estimated = receive.estimate(ntp2unix(receive_ntp)); 105 | let diff = estimated.duration_since(in_time).unwrap().as_nanos() as i128; 106 | if !(-ABS_SEND_TIME_RESOLUTION..=ABS_SEND_TIME_RESOLUTION).contains(&diff) { 107 | assert!( 108 | false, 109 | "Converted time.Time from NTP time differs, expected: {:?}, got: {:?}", 110 | in_time, estimated, 111 | ); 112 | } 113 | } 114 | 115 | Ok(()) 116 | } 117 | -------------------------------------------------------------------------------- /src/extension/abs_send_time_extension/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod abs_send_time_extension_test; 3 | 4 | use crate::error::Error; 5 | use util::marshal::{Marshal, MarshalSize, Unmarshal}; 6 | 7 | use bytes::{Buf, BufMut}; 8 | use std::time::{Duration, SystemTime, UNIX_EPOCH}; 9 | 10 | pub const ABS_SEND_TIME_EXTENSION_SIZE: usize = 3; 11 | 12 | /// AbsSendTimeExtension is a extension payload format in 13 | /// http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time 14 | #[derive(PartialEq, Eq, Debug, Default, Copy, Clone)] 15 | pub struct AbsSendTimeExtension { 16 | pub timestamp: u64, 17 | } 18 | 19 | impl Unmarshal for AbsSendTimeExtension { 20 | /// Unmarshal parses the passed byte slice and stores the result in the members. 21 | fn unmarshal(raw_packet: &mut B) -> Result 22 | where 23 | Self: Sized, 24 | B: Buf, 25 | { 26 | if raw_packet.remaining() < ABS_SEND_TIME_EXTENSION_SIZE { 27 | return Err(Error::ErrBufferTooSmall.into()); 28 | } 29 | 30 | let b0 = raw_packet.get_u8(); 31 | let b1 = raw_packet.get_u8(); 32 | let b2 = raw_packet.get_u8(); 33 | let timestamp = (b0 as u64) << 16 | (b1 as u64) << 8 | b2 as u64; 34 | 35 | Ok(AbsSendTimeExtension { timestamp }) 36 | } 37 | } 38 | 39 | impl MarshalSize for AbsSendTimeExtension { 40 | /// MarshalSize returns the size of the AbsSendTimeExtension once marshaled. 41 | fn marshal_size(&self) -> usize { 42 | ABS_SEND_TIME_EXTENSION_SIZE 43 | } 44 | } 45 | 46 | impl Marshal for AbsSendTimeExtension { 47 | /// MarshalTo serializes the members to buffer. 48 | fn marshal_to(&self, mut buf: &mut [u8]) -> Result { 49 | if buf.remaining_mut() < ABS_SEND_TIME_EXTENSION_SIZE { 50 | return Err(Error::ErrBufferTooSmall.into()); 51 | } 52 | 53 | buf.put_u8(((self.timestamp & 0xFF0000) >> 16) as u8); 54 | buf.put_u8(((self.timestamp & 0xFF00) >> 8) as u8); 55 | buf.put_u8((self.timestamp & 0xFF) as u8); 56 | 57 | Ok(ABS_SEND_TIME_EXTENSION_SIZE) 58 | } 59 | } 60 | 61 | impl AbsSendTimeExtension { 62 | /// Estimate absolute send time according to the receive time. 63 | /// Note that if the transmission delay is larger than 64 seconds, estimated time will be wrong. 64 | pub fn estimate(&self, receive: SystemTime) -> SystemTime { 65 | let receive_ntp = unix2ntp(receive); 66 | let mut ntp = receive_ntp & 0xFFFFFFC000000000 | (self.timestamp & 0xFFFFFF) << 14; 67 | if receive_ntp < ntp { 68 | // Receive time must be always later than send time 69 | ntp -= 0x1000000 << 14; 70 | } 71 | 72 | ntp2unix(ntp) 73 | } 74 | 75 | /// NewAbsSendTimeExtension makes new AbsSendTimeExtension from time.Time. 76 | pub fn new(send_time: SystemTime) -> Self { 77 | AbsSendTimeExtension { 78 | timestamp: unix2ntp(send_time) >> 14, 79 | } 80 | } 81 | } 82 | 83 | pub fn unix2ntp(st: SystemTime) -> u64 { 84 | let u = st 85 | .duration_since(UNIX_EPOCH) 86 | .unwrap_or_else(|_| Duration::from_secs(0)) 87 | .as_nanos() as u64; 88 | let mut s = u / 1_000_000_000; 89 | s += 0x83AA7E80; //offset in seconds between unix epoch and ntp epoch 90 | let mut f = u % 1_000_000_000; 91 | f <<= 32; 92 | f /= 1_000_000_000; 93 | s <<= 32; 94 | 95 | s | f 96 | } 97 | 98 | pub fn ntp2unix(t: u64) -> SystemTime { 99 | let mut s = t >> 32; 100 | let mut f = t & 0xFFFFFFFF; 101 | f *= 1_000_000_000; 102 | f >>= 32; 103 | s -= 0x83AA7E80; 104 | let u = s * 1_000_000_000 + f; 105 | 106 | UNIX_EPOCH 107 | .checked_add(Duration::new(u / 1_000_000_000, (u % 1_000_000_000) as u32)) 108 | .unwrap_or(UNIX_EPOCH) 109 | } 110 | -------------------------------------------------------------------------------- /src/extension/audio_level_extension/audio_level_extension_test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::error::Result; 3 | use bytes::{Bytes, BytesMut}; 4 | 5 | #[test] 6 | fn test_audio_level_extension_too_small() -> Result<()> { 7 | let mut buf = &vec![0u8; 0][..]; 8 | let result = AudioLevelExtension::unmarshal(&mut buf); 9 | assert!(result.is_err()); 10 | 11 | Ok(()) 12 | } 13 | 14 | #[test] 15 | fn test_audio_level_extension_voice_true() -> Result<()> { 16 | let raw = Bytes::from_static(&[0x88]); 17 | let buf = &mut raw.clone(); 18 | let a1 = AudioLevelExtension::unmarshal(buf)?; 19 | let a2 = AudioLevelExtension { 20 | level: 8, 21 | voice: true, 22 | }; 23 | assert_eq!(a1, a2); 24 | 25 | let mut dst = BytesMut::with_capacity(a2.marshal_size()); 26 | dst.resize(a2.marshal_size(), 0); 27 | a2.marshal_to(&mut dst)?; 28 | assert_eq!(raw, dst.freeze()); 29 | 30 | Ok(()) 31 | } 32 | 33 | #[test] 34 | fn test_audio_level_extension_voice_false() -> Result<()> { 35 | let raw = Bytes::from_static(&[0x8]); 36 | let buf = &mut raw.clone(); 37 | let a1 = AudioLevelExtension::unmarshal(buf)?; 38 | let a2 = AudioLevelExtension { 39 | level: 8, 40 | voice: false, 41 | }; 42 | assert_eq!(a1, a2); 43 | 44 | let mut dst = BytesMut::with_capacity(a2.marshal_size()); 45 | dst.resize(a2.marshal_size(), 0); 46 | a2.marshal_to(&mut dst)?; 47 | assert_eq!(raw, dst.freeze()); 48 | 49 | Ok(()) 50 | } 51 | 52 | #[test] 53 | fn test_audio_level_extension_level_overflow() -> Result<()> { 54 | let a = AudioLevelExtension { 55 | level: 128, 56 | voice: false, 57 | }; 58 | 59 | let mut dst = BytesMut::with_capacity(a.marshal_size()); 60 | dst.resize(a.marshal_size(), 0); 61 | let result = a.marshal_to(&mut dst); 62 | assert!(result.is_err()); 63 | 64 | Ok(()) 65 | } 66 | -------------------------------------------------------------------------------- /src/extension/audio_level_extension/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod audio_level_extension_test; 3 | 4 | use crate::error::Error; 5 | use util::marshal::{Marshal, MarshalSize, Unmarshal}; 6 | 7 | use bytes::{Buf, BufMut}; 8 | 9 | // AUDIO_LEVEL_EXTENSION_SIZE One byte header size 10 | pub const AUDIO_LEVEL_EXTENSION_SIZE: usize = 1; 11 | 12 | /// AudioLevelExtension is a extension payload format described in 13 | /// https://tools.ietf.org/html/rfc6464 14 | /// 15 | /// Implementation based on: 16 | /// https://chromium.googlesource.com/external/webrtc/+/e2a017725570ead5946a4ca8235af27470ca0df9/webrtc/modules/rtp_rtcp/source/rtp_header_extensions.cc#49 17 | /// 18 | /// One byte format: 19 | /// 0 1 20 | /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 21 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 22 | /// | ID | len=0 |V| level | 23 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 24 | /// 25 | /// Two byte format: 26 | /// 0 1 2 3 27 | /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 28 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 29 | /// | ID | len=1 |V| level | 0 (pad) | 30 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 31 | #[derive(PartialEq, Eq, Debug, Default, Copy, Clone)] 32 | pub struct AudioLevelExtension { 33 | pub level: u8, 34 | pub voice: bool, 35 | } 36 | 37 | impl Unmarshal for AudioLevelExtension { 38 | /// Unmarshal parses the passed byte slice and stores the result in the members 39 | fn unmarshal(raw_packet: &mut B) -> Result 40 | where 41 | Self: Sized, 42 | B: Buf, 43 | { 44 | if raw_packet.remaining() < AUDIO_LEVEL_EXTENSION_SIZE { 45 | return Err(Error::ErrBufferTooSmall.into()); 46 | } 47 | 48 | let b = raw_packet.get_u8(); 49 | 50 | Ok(AudioLevelExtension { 51 | level: b & 0x7F, 52 | voice: (b & 0x80) != 0, 53 | }) 54 | } 55 | } 56 | 57 | impl MarshalSize for AudioLevelExtension { 58 | /// MarshalSize returns the size of the AudioLevelExtension once marshaled. 59 | fn marshal_size(&self) -> usize { 60 | AUDIO_LEVEL_EXTENSION_SIZE 61 | } 62 | } 63 | 64 | impl Marshal for AudioLevelExtension { 65 | /// MarshalTo serializes the members to buffer 66 | fn marshal_to(&self, mut buf: &mut [u8]) -> Result { 67 | if buf.remaining_mut() < AUDIO_LEVEL_EXTENSION_SIZE { 68 | return Err(Error::ErrBufferTooSmall.into()); 69 | } 70 | if self.level > 127 { 71 | return Err(Error::AudioLevelOverflow.into()); 72 | } 73 | let voice = if self.voice { 0x80u8 } else { 0u8 }; 74 | 75 | buf.put_u8(voice | self.level); 76 | 77 | Ok(AUDIO_LEVEL_EXTENSION_SIZE) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/extension/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod abs_send_time_extension; 2 | pub mod audio_level_extension; 3 | pub mod transport_cc_extension; 4 | -------------------------------------------------------------------------------- /src/extension/transport_cc_extension/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod transport_cc_extension_test; 3 | 4 | use crate::error::Error; 5 | use util::marshal::{Marshal, MarshalSize, Unmarshal}; 6 | 7 | use bytes::{Buf, BufMut}; 8 | 9 | // transport-wide sequence 10 | pub const TRANSPORT_CC_EXTENSION_SIZE: usize = 2; 11 | 12 | /// TransportCCExtension is a extension payload format in 13 | /// https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01 14 | /// 0 1 2 3 15 | /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 16 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 17 | /// | 0xBE | 0xDE | length=1 | 18 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 19 | /// | ID | L=1 |transport-wide sequence number | zero padding | 20 | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 21 | #[derive(PartialEq, Eq, Debug, Default, Copy, Clone)] 22 | pub struct TransportCcExtension { 23 | pub transport_sequence: u16, 24 | } 25 | 26 | impl Unmarshal for TransportCcExtension { 27 | /// Unmarshal parses the passed byte slice and stores the result in the members 28 | fn unmarshal(raw_packet: &mut B) -> Result 29 | where 30 | Self: Sized, 31 | B: Buf, 32 | { 33 | if raw_packet.remaining() < TRANSPORT_CC_EXTENSION_SIZE { 34 | return Err(Error::ErrBufferTooSmall.into()); 35 | } 36 | let b0 = raw_packet.get_u8(); 37 | let b1 = raw_packet.get_u8(); 38 | 39 | let transport_sequence = ((b0 as u16) << 8) | b1 as u16; 40 | Ok(TransportCcExtension { transport_sequence }) 41 | } 42 | } 43 | 44 | impl MarshalSize for TransportCcExtension { 45 | /// MarshalSize returns the size of the TransportCcExtension once marshaled. 46 | fn marshal_size(&self) -> usize { 47 | TRANSPORT_CC_EXTENSION_SIZE 48 | } 49 | } 50 | 51 | impl Marshal for TransportCcExtension { 52 | /// Marshal serializes the members to buffer 53 | fn marshal_to(&self, mut buf: &mut [u8]) -> Result { 54 | if buf.remaining_mut() < TRANSPORT_CC_EXTENSION_SIZE { 55 | return Err(Error::ErrBufferTooSmall.into()); 56 | } 57 | buf.put_u16(self.transport_sequence); 58 | Ok(TRANSPORT_CC_EXTENSION_SIZE) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/extension/transport_cc_extension/transport_cc_extension_test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::error::Result; 3 | use bytes::{Bytes, BytesMut}; 4 | 5 | #[test] 6 | fn test_transport_cc_extension_too_small() -> Result<()> { 7 | let mut buf = &vec![0u8; 0][..]; 8 | let result = TransportCcExtension::unmarshal(&mut buf); 9 | assert!(result.is_err()); 10 | 11 | Ok(()) 12 | } 13 | 14 | #[test] 15 | fn test_transport_cc_extension() -> Result<()> { 16 | let raw = Bytes::from_static(&[0x00, 0x02]); 17 | let buf = &mut raw.clone(); 18 | let t1 = TransportCcExtension::unmarshal(buf)?; 19 | let t2 = TransportCcExtension { 20 | transport_sequence: 2, 21 | }; 22 | assert_eq!(t1, t2); 23 | 24 | let mut dst = BytesMut::with_capacity(t2.marshal_size()); 25 | dst.resize(t2.marshal_size(), 0); 26 | t2.marshal_to(&mut dst)?; 27 | assert_eq!(raw, dst.freeze()); 28 | 29 | Ok(()) 30 | } 31 | 32 | #[test] 33 | fn test_transport_cc_extension_extra_bytes() -> Result<()> { 34 | let mut raw = Bytes::from_static(&[0x00, 0x02, 0x00, 0xff, 0xff]); 35 | let buf = &mut raw; 36 | let t1 = TransportCcExtension::unmarshal(buf)?; 37 | let t2 = TransportCcExtension { 38 | transport_sequence: 2, 39 | }; 40 | assert_eq!(t1, t2); 41 | 42 | Ok(()) 43 | } 44 | -------------------------------------------------------------------------------- /src/header.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use util::marshal::{Marshal, MarshalSize, Unmarshal}; 3 | 4 | use bytes::{Buf, BufMut, Bytes}; 5 | 6 | pub const HEADER_LENGTH: usize = 4; 7 | pub const VERSION_SHIFT: u8 = 6; 8 | pub const VERSION_MASK: u8 = 0x3; 9 | pub const PADDING_SHIFT: u8 = 5; 10 | pub const PADDING_MASK: u8 = 0x1; 11 | pub const EXTENSION_SHIFT: u8 = 4; 12 | pub const EXTENSION_MASK: u8 = 0x1; 13 | pub const EXTENSION_PROFILE_ONE_BYTE: u16 = 0xBEDE; 14 | pub const EXTENSION_PROFILE_TWO_BYTE: u16 = 0x1000; 15 | pub const EXTENSION_ID_RESERVED: u8 = 0xF; 16 | pub const CC_MASK: u8 = 0xF; 17 | pub const MARKER_SHIFT: u8 = 7; 18 | pub const MARKER_MASK: u8 = 0x1; 19 | pub const PT_MASK: u8 = 0x7F; 20 | pub const SEQ_NUM_OFFSET: usize = 2; 21 | pub const SEQ_NUM_LENGTH: usize = 2; 22 | pub const TIMESTAMP_OFFSET: usize = 4; 23 | pub const TIMESTAMP_LENGTH: usize = 4; 24 | pub const SSRC_OFFSET: usize = 8; 25 | pub const SSRC_LENGTH: usize = 4; 26 | pub const CSRC_OFFSET: usize = 12; 27 | pub const CSRC_LENGTH: usize = 4; 28 | 29 | #[derive(Debug, Eq, PartialEq, Default, Clone)] 30 | pub struct Extension { 31 | pub id: u8, 32 | pub payload: Bytes, 33 | } 34 | 35 | /// Header represents an RTP packet header 36 | /// NOTE: PayloadOffset is populated by Marshal/Unmarshal and should not be modified 37 | #[derive(Debug, Eq, PartialEq, Default, Clone)] 38 | pub struct Header { 39 | pub version: u8, 40 | pub padding: bool, 41 | pub extension: bool, 42 | pub marker: bool, 43 | pub payload_type: u8, 44 | pub sequence_number: u16, 45 | pub timestamp: u32, 46 | pub ssrc: u32, 47 | pub csrc: Vec, 48 | pub extension_profile: u16, 49 | pub extensions: Vec, 50 | } 51 | 52 | impl Unmarshal for Header { 53 | /// Unmarshal parses the passed byte slice and stores the result in the Header this method is called upon 54 | fn unmarshal(raw_packet: &mut B) -> Result 55 | where 56 | Self: Sized, 57 | B: Buf, 58 | { 59 | let raw_packet_len = raw_packet.remaining(); 60 | if raw_packet_len < HEADER_LENGTH { 61 | return Err(Error::ErrHeaderSizeInsufficient.into()); 62 | } 63 | /* 64 | * 0 1 2 3 65 | * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 66 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 67 | * |V=2|P|X| CC |M| PT | sequence number | 68 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 69 | * | timestamp | 70 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 71 | * | synchronization source (SSRC) identifier | 72 | * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ 73 | * | contributing source (CSRC) identifiers | 74 | * | .... | 75 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 76 | */ 77 | let b0 = raw_packet.get_u8(); 78 | let version = b0 >> VERSION_SHIFT & VERSION_MASK; 79 | let padding = (b0 >> PADDING_SHIFT & PADDING_MASK) > 0; 80 | let extension = (b0 >> EXTENSION_SHIFT & EXTENSION_MASK) > 0; 81 | let cc = (b0 & CC_MASK) as usize; 82 | 83 | let mut curr_offset = CSRC_OFFSET + (cc * CSRC_LENGTH); 84 | if raw_packet_len < curr_offset { 85 | return Err(Error::ErrHeaderSizeInsufficient.into()); 86 | } 87 | 88 | let b1 = raw_packet.get_u8(); 89 | let marker = (b1 >> MARKER_SHIFT & MARKER_MASK) > 0; 90 | let payload_type = b1 & PT_MASK; 91 | 92 | let sequence_number = raw_packet.get_u16(); 93 | let timestamp = raw_packet.get_u32(); 94 | let ssrc = raw_packet.get_u32(); 95 | 96 | let mut csrc = Vec::with_capacity(cc); 97 | for _ in 0..cc { 98 | csrc.push(raw_packet.get_u32()); 99 | } 100 | 101 | let (extension_profile, extensions) = if extension { 102 | let expected = curr_offset + 4; 103 | if raw_packet_len < expected { 104 | return Err(Error::ErrHeaderSizeInsufficientForExtension.into()); 105 | } 106 | let extension_profile = raw_packet.get_u16(); 107 | curr_offset += 2; 108 | let extension_length = raw_packet.get_u16() as usize * 4; 109 | curr_offset += 2; 110 | 111 | let expected = curr_offset + extension_length; 112 | if raw_packet_len < expected { 113 | return Err(Error::ErrHeaderSizeInsufficientForExtension.into()); 114 | } 115 | 116 | let mut extensions = vec![]; 117 | match extension_profile { 118 | // RFC 8285 RTP One Byte Header Extension 119 | EXTENSION_PROFILE_ONE_BYTE => { 120 | let end = curr_offset + extension_length; 121 | while curr_offset < end { 122 | let b = raw_packet.get_u8(); 123 | if b == 0x00 { 124 | // padding 125 | curr_offset += 1; 126 | continue; 127 | } 128 | 129 | let extid = b >> 4; 130 | let len = ((b & (0xFF ^ 0xF0)) + 1) as usize; 131 | curr_offset += 1; 132 | 133 | if extid == EXTENSION_ID_RESERVED { 134 | break; 135 | } 136 | 137 | extensions.push(Extension { 138 | id: extid, 139 | payload: raw_packet.copy_to_bytes(len), 140 | }); 141 | curr_offset += len; 142 | } 143 | } 144 | // RFC 8285 RTP Two Byte Header Extension 145 | EXTENSION_PROFILE_TWO_BYTE => { 146 | let end = curr_offset + extension_length; 147 | while curr_offset < end { 148 | let b = raw_packet.get_u8(); 149 | if b == 0x00 { 150 | // padding 151 | curr_offset += 1; 152 | continue; 153 | } 154 | 155 | let extid = b; 156 | curr_offset += 1; 157 | 158 | let len = raw_packet.get_u8() as usize; 159 | curr_offset += 1; 160 | 161 | extensions.push(Extension { 162 | id: extid, 163 | payload: raw_packet.copy_to_bytes(len), 164 | }); 165 | curr_offset += len; 166 | } 167 | } 168 | // RFC3550 Extension 169 | _ => { 170 | if raw_packet_len < curr_offset + extension_length { 171 | return Err(Error::ErrHeaderSizeInsufficientForExtension.into()); 172 | } 173 | extensions.push(Extension { 174 | id: 0, 175 | payload: raw_packet.copy_to_bytes(extension_length), 176 | }); 177 | } 178 | }; 179 | 180 | (extension_profile, extensions) 181 | } else { 182 | (0, vec![]) 183 | }; 184 | 185 | Ok(Header { 186 | version, 187 | padding, 188 | extension, 189 | marker, 190 | payload_type, 191 | sequence_number, 192 | timestamp, 193 | ssrc, 194 | csrc, 195 | extension_profile, 196 | extensions, 197 | }) 198 | } 199 | } 200 | 201 | impl MarshalSize for Header { 202 | /// MarshalSize returns the size of the packet once marshaled. 203 | fn marshal_size(&self) -> usize { 204 | let mut head_size = 12 + (self.csrc.len() * CSRC_LENGTH); 205 | if self.extension { 206 | let extension_payload_len = self.get_extension_payload_len(); 207 | let extension_payload_size = (extension_payload_len + 3) / 4; 208 | head_size += 4 + extension_payload_size * 4; 209 | } 210 | head_size 211 | } 212 | } 213 | 214 | impl Marshal for Header { 215 | /// Marshal serializes the header and writes to the buffer. 216 | fn marshal_to(&self, mut buf: &mut [u8]) -> Result { 217 | /* 218 | * 0 1 2 3 219 | * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 220 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 221 | * |V=2|P|X| CC |M| PT | sequence number | 222 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 223 | * | timestamp | 224 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 225 | * | synchronization source (SSRC) identifier | 226 | * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ 227 | * | contributing source (CSRC) identifiers | 228 | * | .... | 229 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 230 | */ 231 | let remaining_before = buf.remaining_mut(); 232 | if remaining_before < self.marshal_size() { 233 | return Err(Error::ErrBufferTooSmall.into()); 234 | } 235 | 236 | // The first byte contains the version, padding bit, extension bit, and csrc size 237 | let mut b0 = (self.version << VERSION_SHIFT) | self.csrc.len() as u8; 238 | if self.padding { 239 | b0 |= 1 << PADDING_SHIFT; 240 | } 241 | 242 | if self.extension { 243 | b0 |= 1 << EXTENSION_SHIFT; 244 | } 245 | buf.put_u8(b0); 246 | 247 | // The second byte contains the marker bit and payload type. 248 | let mut b1 = self.payload_type; 249 | if self.marker { 250 | b1 |= 1 << MARKER_SHIFT; 251 | } 252 | buf.put_u8(b1); 253 | 254 | buf.put_u16(self.sequence_number); 255 | buf.put_u32(self.timestamp); 256 | buf.put_u32(self.ssrc); 257 | 258 | for csrc in &self.csrc { 259 | buf.put_u32(*csrc); 260 | } 261 | 262 | if self.extension { 263 | buf.put_u16(self.extension_profile); 264 | 265 | // calculate extensions size and round to 4 bytes boundaries 266 | let extension_payload_len = self.get_extension_payload_len(); 267 | if self.extension_profile != EXTENSION_PROFILE_ONE_BYTE 268 | && self.extension_profile != EXTENSION_PROFILE_TWO_BYTE 269 | && extension_payload_len % 4 != 0 270 | { 271 | //the payload must be in 32-bit words. 272 | return Err(Error::HeaderExtensionPayloadNot32BitWords.into()); 273 | } 274 | let extension_payload_size = (extension_payload_len as u16 + 3) / 4; 275 | buf.put_u16(extension_payload_size); 276 | 277 | match self.extension_profile { 278 | // RFC 8285 RTP One Byte Header Extension 279 | EXTENSION_PROFILE_ONE_BYTE => { 280 | for extension in &self.extensions { 281 | buf.put_u8((extension.id << 4) | (extension.payload.len() as u8 - 1)); 282 | buf.put(&*extension.payload); 283 | } 284 | } 285 | // RFC 8285 RTP Two Byte Header Extension 286 | EXTENSION_PROFILE_TWO_BYTE => { 287 | for extension in &self.extensions { 288 | buf.put_u8(extension.id); 289 | buf.put_u8(extension.payload.len() as u8); 290 | buf.put(&*extension.payload); 291 | } 292 | } 293 | // RFC3550 Extension 294 | _ => { 295 | if self.extensions.len() != 1 { 296 | return Err(Error::ErrRfc3550headerIdrange.into()); 297 | } 298 | 299 | if let Some(extension) = self.extensions.first() { 300 | let ext_len = extension.payload.len(); 301 | if ext_len % 4 != 0 { 302 | return Err(Error::HeaderExtensionPayloadNot32BitWords.into()); 303 | } 304 | buf.put(&*extension.payload); 305 | } 306 | } 307 | }; 308 | 309 | // add padding to reach 4 bytes boundaries 310 | for _ in extension_payload_len..extension_payload_size as usize * 4 { 311 | buf.put_u8(0); 312 | } 313 | } 314 | 315 | let remaining_after = buf.remaining_mut(); 316 | Ok(remaining_before - remaining_after) 317 | } 318 | } 319 | 320 | impl Header { 321 | pub fn get_extension_payload_len(&self) -> usize { 322 | let payload_len: usize = self 323 | .extensions 324 | .iter() 325 | .map(|extension| extension.payload.len()) 326 | .sum(); 327 | 328 | let profile_len = self.extensions.len() 329 | * match self.extension_profile { 330 | EXTENSION_PROFILE_ONE_BYTE => 1, 331 | EXTENSION_PROFILE_TWO_BYTE => 2, 332 | _ => 0, 333 | }; 334 | 335 | payload_len + profile_len 336 | } 337 | 338 | /// SetExtension sets an RTP header extension 339 | pub fn set_extension(&mut self, id: u8, payload: Bytes) -> Result<(), Error> { 340 | if self.extension { 341 | match self.extension_profile { 342 | EXTENSION_PROFILE_ONE_BYTE => { 343 | if !(1..=14).contains(&id) { 344 | return Err(Error::ErrRfc8285oneByteHeaderIdrange); 345 | } 346 | if payload.len() > 16 { 347 | return Err(Error::ErrRfc8285oneByteHeaderSize); 348 | } 349 | } 350 | EXTENSION_PROFILE_TWO_BYTE => { 351 | if id < 1 { 352 | return Err(Error::ErrRfc8285twoByteHeaderIdrange); 353 | } 354 | if payload.len() > 255 { 355 | return Err(Error::ErrRfc8285twoByteHeaderSize); 356 | } 357 | } 358 | _ => { 359 | if id != 0 { 360 | return Err(Error::ErrRfc3550headerIdrange); 361 | } 362 | } 363 | }; 364 | 365 | // Update existing if it exists else add new extension 366 | if let Some(extension) = self 367 | .extensions 368 | .iter_mut() 369 | .find(|extension| extension.id == id) 370 | { 371 | extension.payload = payload; 372 | } else { 373 | self.extensions.push(Extension { id, payload }); 374 | } 375 | } else { 376 | // No existing header extensions 377 | self.extension = true; 378 | 379 | self.extension_profile = match payload.len() { 380 | 0..=16 => EXTENSION_PROFILE_ONE_BYTE, 381 | 17..=255 => EXTENSION_PROFILE_TWO_BYTE, 382 | _ => self.extension_profile, 383 | }; 384 | 385 | self.extensions.push(Extension { id, payload }); 386 | } 387 | Ok(()) 388 | } 389 | 390 | /// returns an extension id array 391 | pub fn get_extension_ids(&self) -> Vec { 392 | if self.extension { 393 | self.extensions.iter().map(|e| e.id).collect() 394 | } else { 395 | vec![] 396 | } 397 | } 398 | 399 | /// returns an RTP header extension 400 | pub fn get_extension(&self, id: u8) -> Option { 401 | if self.extension { 402 | self.extensions 403 | .iter() 404 | .find(|extension| extension.id == id) 405 | .map(|extension| extension.payload.clone()) 406 | } else { 407 | None 408 | } 409 | } 410 | 411 | /// Removes an RTP Header extension 412 | pub fn del_extension(&mut self, id: u8) -> Result<(), Error> { 413 | if self.extension { 414 | if let Some(index) = self 415 | .extensions 416 | .iter() 417 | .position(|extension| extension.id == id) 418 | { 419 | self.extensions.remove(index); 420 | Ok(()) 421 | } else { 422 | Err(Error::ErrHeaderExtensionNotFound) 423 | } 424 | } else { 425 | Err(Error::ErrHeaderExtensionsNotEnabled) 426 | } 427 | } 428 | } 429 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn(rust_2018_idioms)] 2 | #![allow(dead_code)] 3 | 4 | pub mod codecs; 5 | mod error; 6 | pub mod extension; 7 | pub mod header; 8 | pub mod packet; 9 | pub mod packetizer; 10 | pub mod sequence; 11 | 12 | pub use error::Error; 13 | -------------------------------------------------------------------------------- /src/packet/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod packet_test; 3 | 4 | use crate::{error::Error, header::*}; 5 | use util::marshal::{Marshal, MarshalSize, Unmarshal}; 6 | 7 | use bytes::{Buf, BufMut, Bytes}; 8 | use std::fmt; 9 | 10 | /// Packet represents an RTP Packet 11 | /// NOTE: Raw is populated by Marshal/Unmarshal and should not be modified 12 | #[derive(Debug, Eq, PartialEq, Default, Clone)] 13 | pub struct Packet { 14 | pub header: Header, 15 | pub payload: Bytes, 16 | } 17 | 18 | impl fmt::Display for Packet { 19 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 20 | let mut out = "RTP PACKET:\n".to_string(); 21 | 22 | out += format!("\tVersion: {}\n", self.header.version).as_str(); 23 | out += format!("\tMarker: {}\n", self.header.marker).as_str(); 24 | out += format!("\tPayload Type: {}\n", self.header.payload_type).as_str(); 25 | out += format!("\tSequence Number: {}\n", self.header.sequence_number).as_str(); 26 | out += format!("\tTimestamp: {}\n", self.header.timestamp).as_str(); 27 | out += format!("\tSSRC: {} ({:x})\n", self.header.ssrc, self.header.ssrc).as_str(); 28 | out += format!("\tPayload Length: {}\n", self.payload.len()).as_str(); 29 | 30 | write!(f, "{}", out) 31 | } 32 | } 33 | 34 | impl Unmarshal for Packet { 35 | /// Unmarshal parses the passed byte slice and stores the result in the Header this method is called upon 36 | fn unmarshal(raw_packet: &mut B) -> Result 37 | where 38 | Self: Sized, 39 | B: Buf, 40 | { 41 | let header = Header::unmarshal(raw_packet)?; 42 | let payload_len = raw_packet.remaining(); 43 | let payload = raw_packet.copy_to_bytes(payload_len); 44 | if header.padding { 45 | if payload_len > 0 { 46 | let padding_len = payload[payload_len - 1] as usize; 47 | if padding_len <= payload_len { 48 | Ok(Packet { 49 | header, 50 | payload: payload.slice(..payload_len - padding_len), 51 | }) 52 | } else { 53 | Err(Error::ErrShortPacket.into()) 54 | } 55 | } else { 56 | Err(Error::ErrShortPacket.into()) 57 | } 58 | } else { 59 | Ok(Packet { header, payload }) 60 | } 61 | } 62 | } 63 | 64 | impl MarshalSize for Packet { 65 | /// MarshalSize returns the size of the packet once marshaled. 66 | fn marshal_size(&self) -> usize { 67 | let payload_len = self.payload.len(); 68 | let padding_len = if self.header.padding { 69 | let padding_len = get_padding(payload_len); 70 | if padding_len == 0 { 71 | 4 72 | } else { 73 | padding_len 74 | } 75 | } else { 76 | 0 77 | }; 78 | self.header.marshal_size() + payload_len + padding_len 79 | } 80 | } 81 | 82 | impl Marshal for Packet { 83 | /// MarshalTo serializes the packet and writes to the buffer. 84 | fn marshal_to(&self, mut buf: &mut [u8]) -> Result { 85 | if buf.remaining_mut() < self.marshal_size() { 86 | return Err(Error::ErrBufferTooSmall.into()); 87 | } 88 | 89 | let n = self.header.marshal_to(buf)?; 90 | buf = &mut buf[n..]; 91 | buf.put(&*self.payload); 92 | let padding_len = if self.header.padding { 93 | let mut padding_len = get_padding(self.payload.len()); 94 | if padding_len == 0 { 95 | padding_len = 4; 96 | } 97 | for i in 0..padding_len { 98 | if i != padding_len - 1 { 99 | buf.put_u8(0); 100 | } else { 101 | buf.put_u8(padding_len as u8); 102 | } 103 | } 104 | padding_len 105 | } else { 106 | 0 107 | }; 108 | 109 | Ok(n + self.payload.len() + padding_len) 110 | } 111 | } 112 | 113 | /// getPadding Returns the padding required to make the length a multiple of 4 114 | fn get_padding(len: usize) -> usize { 115 | if len % 4 == 0 { 116 | 0 117 | } else { 118 | 4 - (len % 4) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/packetizer/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod packetizer_test; 3 | 4 | use crate::error::Result; 5 | use crate::{extension::abs_send_time_extension::*, header::*, packet::*, sequence::*}; 6 | use util::marshal::{Marshal, MarshalSize}; 7 | 8 | use async_trait::async_trait; 9 | use bytes::{Bytes, BytesMut}; 10 | use std::fmt; 11 | use std::future::Future; 12 | use std::pin::Pin; 13 | use std::sync::Arc; 14 | use std::time::SystemTime; 15 | 16 | /// Payloader payloads a byte array for use as rtp.Packet payloads 17 | pub trait Payloader: fmt::Debug { 18 | fn payload(&mut self, mtu: usize, b: &Bytes) -> Result>; 19 | fn clone_to(&self) -> Box; 20 | } 21 | 22 | impl Clone for Box { 23 | fn clone(&self) -> Box { 24 | self.clone_to() 25 | } 26 | } 27 | 28 | /// Packetizer packetizes a payload 29 | #[async_trait] 30 | pub trait Packetizer: fmt::Debug { 31 | fn enable_abs_send_time(&mut self, value: u8); 32 | async fn packetize(&mut self, payload: &Bytes, samples: u32) -> Result>; 33 | fn skip_samples(&mut self, skipped_samples: u32); 34 | fn clone_to(&self) -> Box; 35 | } 36 | 37 | impl Clone for Box { 38 | fn clone(&self) -> Box { 39 | self.clone_to() 40 | } 41 | } 42 | 43 | /// Depacketizer depacketizes a RTP payload, removing any RTP specific data from the payload 44 | pub trait Depacketizer { 45 | fn depacketize(&mut self, b: &Bytes) -> Result; 46 | 47 | /// Checks if the packet is at the beginning of a partition. This 48 | /// should return false if the result could not be determined, in 49 | /// which case the caller will detect timestamp discontinuities. 50 | fn is_partition_head(&self, payload: &Bytes) -> bool; 51 | 52 | /// Checks if the packet is at the end of a partition. This should 53 | /// return false if the result could not be determined. 54 | fn is_partition_tail(&self, marker: bool, payload: &Bytes) -> bool; 55 | } 56 | 57 | //TODO: SystemTime vs Instant? 58 | // non-monotonic clock vs monotonically non-decreasing clock 59 | /// FnTimeGen provides current SystemTime 60 | pub type FnTimeGen = 61 | Arc Pin + Send + 'static>>) + Send + Sync>; 62 | 63 | #[derive(Clone)] 64 | pub(crate) struct PacketizerImpl { 65 | pub(crate) mtu: usize, 66 | pub(crate) payload_type: u8, 67 | pub(crate) ssrc: u32, 68 | pub(crate) payloader: Box, 69 | pub(crate) sequencer: Box, 70 | pub(crate) timestamp: u32, 71 | pub(crate) clock_rate: u32, 72 | pub(crate) abs_send_time: u8, //http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time 73 | pub(crate) time_gen: Option, 74 | } 75 | 76 | impl fmt::Debug for PacketizerImpl { 77 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 78 | f.debug_struct("PacketizerImpl") 79 | .field("mtu", &self.mtu) 80 | .field("payload_type", &self.payload_type) 81 | .field("ssrc", &self.ssrc) 82 | .field("timestamp", &self.timestamp) 83 | .field("clock_rate", &self.clock_rate) 84 | .field("abs_send_time", &self.abs_send_time) 85 | .finish() 86 | } 87 | } 88 | 89 | pub fn new_packetizer( 90 | mtu: usize, 91 | payload_type: u8, 92 | ssrc: u32, 93 | payloader: Box, 94 | sequencer: Box, 95 | clock_rate: u32, 96 | ) -> impl Packetizer { 97 | PacketizerImpl { 98 | mtu, 99 | payload_type, 100 | ssrc, 101 | payloader, 102 | sequencer, 103 | timestamp: rand::random::(), 104 | clock_rate, 105 | abs_send_time: 0, 106 | time_gen: None, 107 | } 108 | } 109 | 110 | #[async_trait] 111 | impl Packetizer for PacketizerImpl { 112 | fn enable_abs_send_time(&mut self, value: u8) { 113 | self.abs_send_time = value 114 | } 115 | 116 | async fn packetize(&mut self, payload: &Bytes, samples: u32) -> Result> { 117 | let payloads = self.payloader.payload(self.mtu - 12, payload)?; 118 | let payloads_len = payloads.len(); 119 | let mut packets = Vec::with_capacity(payloads_len); 120 | for (i, payload) in payloads.into_iter().enumerate() { 121 | packets.push(Packet { 122 | header: Header { 123 | version: 2, 124 | padding: false, 125 | extension: false, 126 | marker: i == payloads_len - 1, 127 | payload_type: self.payload_type, 128 | sequence_number: self.sequencer.next_sequence_number(), 129 | timestamp: self.timestamp, //TODO: Figure out how to do timestamps 130 | ssrc: self.ssrc, 131 | ..Default::default() 132 | }, 133 | payload, 134 | }); 135 | } 136 | 137 | self.timestamp = self.timestamp.wrapping_add(samples); 138 | 139 | if payloads_len != 0 && self.abs_send_time != 0 { 140 | let st = if let Some(fn_time_gen) = &self.time_gen { 141 | fn_time_gen().await 142 | } else { 143 | SystemTime::now() 144 | }; 145 | let send_time = AbsSendTimeExtension::new(st); 146 | //apply http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time 147 | let mut raw = BytesMut::with_capacity(send_time.marshal_size()); 148 | raw.resize(send_time.marshal_size(), 0); 149 | let _ = send_time.marshal_to(&mut raw)?; 150 | packets[payloads_len - 1] 151 | .header 152 | .set_extension(self.abs_send_time, raw.freeze())?; 153 | } 154 | 155 | Ok(packets) 156 | } 157 | 158 | /// skip_samples causes a gap in sample count between Packetize requests so the 159 | /// RTP payloads produced have a gap in timestamps 160 | fn skip_samples(&mut self, skipped_samples: u32) { 161 | self.timestamp = self.timestamp.wrapping_add(skipped_samples); 162 | } 163 | 164 | fn clone_to(&self) -> Box { 165 | Box::new(self.clone()) 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/packetizer/packetizer_test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::codecs::*; 3 | use crate::error::Result; 4 | 5 | use chrono::prelude::*; 6 | use std::time::{Duration, UNIX_EPOCH}; 7 | 8 | #[tokio::test] 9 | async fn test_packetizer() -> Result<()> { 10 | let multiple_payload = Bytes::from_static(&[0; 128]); 11 | let g722 = Box::new(g7xx::G722Payloader {}); 12 | let seq = Box::new(new_random_sequencer()); 13 | 14 | //use the G722 payloader here, because it's very simple and all 0s is valid G722 data. 15 | let mut packetizer = new_packetizer(100, 98, 0x1234ABCD, g722, seq, 90000); 16 | let packets = packetizer.packetize(&multiple_payload, 2000).await?; 17 | 18 | if packets.len() != 2 { 19 | let mut packet_lengths = String::new(); 20 | for i in 0..packets.len() { 21 | packet_lengths += 22 | format!("Packet {} length {}\n", i, packets[i].payload.len()).as_str(); 23 | } 24 | assert!( 25 | false, 26 | "Generated {} packets instead of 2\n{}", 27 | packets.len(), 28 | packet_lengths, 29 | ); 30 | } 31 | Ok(()) 32 | } 33 | 34 | #[tokio::test] 35 | async fn test_packetizer_abs_send_time() -> Result<()> { 36 | let g722 = Box::new(g7xx::G722Payloader {}); 37 | let sequencer = Box::new(new_fixed_sequencer(1234)); 38 | 39 | let time_gen: Option = Some(Arc::new( 40 | || -> Pin + Send + 'static>> { 41 | Box::pin(async move { 42 | let loc = FixedOffset::west(5 * 60 * 60); // UTC-5 43 | let t = loc.ymd(1985, 6, 23).and_hms_nano(4, 0, 0, 0); 44 | UNIX_EPOCH 45 | .checked_add(Duration::from_nanos(t.timestamp_nanos() as u64)) 46 | .unwrap_or(UNIX_EPOCH) 47 | }) 48 | }, 49 | )); 50 | 51 | //use the G722 payloader here, because it's very simple and all 0s is valid G722 data. 52 | let mut pktizer = PacketizerImpl { 53 | mtu: 100, 54 | payload_type: 98, 55 | ssrc: 0x1234ABCD, 56 | payloader: g722, 57 | sequencer, 58 | timestamp: 45678, 59 | clock_rate: 90000, 60 | abs_send_time: 0, 61 | time_gen, 62 | }; 63 | pktizer.enable_abs_send_time(1); 64 | 65 | let payload = Bytes::from_static(&[0x11, 0x12, 0x13, 0x14]); 66 | let packets = pktizer.packetize(&payload, 2000).await?; 67 | 68 | let expected = Packet { 69 | header: Header { 70 | version: 2, 71 | padding: false, 72 | extension: true, 73 | marker: true, 74 | payload_type: 98, 75 | sequence_number: 1234, 76 | timestamp: 45678, 77 | ssrc: 0x1234ABCD, 78 | csrc: vec![], 79 | extension_profile: 0xBEDE, 80 | extensions: vec![Extension { 81 | id: 1, 82 | payload: Bytes::from_static(&[0x40, 0, 0]), 83 | }], 84 | }, 85 | payload: Bytes::from_static(&[0x11, 0x12, 0x13, 0x14]), 86 | }; 87 | 88 | if packets.len() != 1 { 89 | assert!(false, "Generated {} packets instead of 1", packets.len()) 90 | } 91 | 92 | assert_eq!(expected, packets[0]); 93 | 94 | Ok(()) 95 | } 96 | 97 | #[tokio::test] 98 | async fn test_packetizer_timestamp_rollover_does_not_panic() -> Result<()> { 99 | let g722 = Box::new(g7xx::G722Payloader {}); 100 | let seq = Box::new(new_random_sequencer()); 101 | 102 | let payload = Bytes::from_static(&[0; 128]); 103 | let mut packetizer = new_packetizer(100, 98, 0x1234ABCD, g722, seq, 90000); 104 | 105 | packetizer.packetize(&payload, 10).await?; 106 | 107 | packetizer.packetize(&payload, u32::MAX).await?; 108 | 109 | packetizer.skip_samples(u32::MAX); 110 | 111 | Ok(()) 112 | } 113 | -------------------------------------------------------------------------------- /src/sequence.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::sync::{Arc, Mutex}; 3 | 4 | /// Sequencer generates sequential sequence numbers for building RTP packets 5 | pub trait Sequencer: fmt::Debug { 6 | fn next_sequence_number(&self) -> u16; 7 | fn roll_over_count(&self) -> u64; 8 | fn clone_to(&self) -> Box; 9 | } 10 | 11 | impl Clone for Box { 12 | fn clone(&self) -> Box { 13 | self.clone_to() 14 | } 15 | } 16 | 17 | /// NewRandomSequencer returns a new sequencer starting from a random sequence 18 | /// number 19 | pub fn new_random_sequencer() -> impl Sequencer { 20 | let c = Counters { 21 | sequence_number: rand::random::(), 22 | roll_over_count: 0, 23 | }; 24 | SequencerImpl(Arc::new(Mutex::new(c))) 25 | } 26 | 27 | /// NewFixedSequencer returns a new sequencer starting from a specific 28 | /// sequence number 29 | pub fn new_fixed_sequencer(s: u16) -> impl Sequencer { 30 | let sequence_number = if s == 0 { u16::MAX } else { s - 1 }; 31 | 32 | let c = Counters { 33 | sequence_number, 34 | roll_over_count: 0, 35 | }; 36 | 37 | SequencerImpl(Arc::new(Mutex::new(c))) 38 | } 39 | 40 | #[derive(Debug, Clone)] 41 | struct SequencerImpl(Arc>); 42 | 43 | #[derive(Debug)] 44 | struct Counters { 45 | sequence_number: u16, 46 | roll_over_count: u64, 47 | } 48 | 49 | impl Sequencer for SequencerImpl { 50 | /// NextSequenceNumber increment and returns a new sequence number for 51 | /// building RTP packets 52 | fn next_sequence_number(&self) -> u16 { 53 | let mut lock = self.0.lock().unwrap(); 54 | 55 | if lock.sequence_number == u16::MAX { 56 | lock.roll_over_count += 1; 57 | lock.sequence_number = 0; 58 | } else { 59 | lock.sequence_number += 1; 60 | } 61 | 62 | lock.sequence_number 63 | } 64 | 65 | /// RollOverCount returns the amount of times the 16bit sequence number 66 | /// has wrapped 67 | fn roll_over_count(&self) -> u64 { 68 | self.0.lock().unwrap().roll_over_count 69 | } 70 | 71 | fn clone_to(&self) -> Box { 72 | Box::new(self.clone()) 73 | } 74 | } 75 | --------------------------------------------------------------------------------