├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ ├── audit.yml │ ├── benchmark.yml │ ├── pr-benchmark-upload-from-main.yml │ ├── pr-benchmark.yml │ └── rust.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── benches ├── bench.rs └── ci_bench.rs ├── examples ├── dump-pcr.rs ├── simple.rs └── strip.rs ├── fuzz ├── .gitignore ├── Cargo.toml ├── README.md ├── corpus │ └── fuzz_target_1 │ │ └── 0.ts └── fuzz_targets │ └── fuzz_target_1.rs ├── release.toml ├── scripts └── fuzz.sh ├── shootout ├── Cargo.toml ├── benches │ └── bench.rs ├── report.py ├── report.sh ├── report.svg └── src │ ├── ffmpeg_timestamps.rs │ ├── lib.rs │ ├── mpeg2ts_reader_timestamps.rs │ └── mpeg2ts_timestamps.rs ├── src ├── demultiplex.rs ├── descriptor │ ├── avcvideo.rs │ ├── iso_639_language.rs │ ├── max_bitrate.rs │ ├── mod.rs │ └── registration.rs ├── lib.rs ├── mpegts_crc.rs ├── packet.rs ├── pes.rs └── psi │ ├── mod.rs │ ├── pat.rs │ └── pmt.rs └── testsrc.ts /.gitattributes: -------------------------------------------------------------------------------- 1 | testsrc.ts filter=lfs diff=lfs merge=lfs -text 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | ignore: 9 | - dependency-name: hex-literal 10 | versions: 11 | - "> 0.2.1, < 1" 12 | - dependency-name: hex-literal 13 | versions: 14 | - ">= 0.3.a, < 0.4" 15 | 16 | - package-ecosystem: github-actions 17 | directory: / 18 | schedule: 19 | interval: monthly 20 | -------------------------------------------------------------------------------- /.github/workflows/audit.yml: -------------------------------------------------------------------------------- 1 | name: Security audit 2 | on: 3 | schedule: 4 | # on the 1st of each month 5 | - cron: '0 0 1 * *' 6 | jobs: 7 | audit: 8 | runs-on: ubuntu-22.04 9 | steps: 10 | - uses: actions/checkout@v4 11 | - uses: rustsec/audit-check@v2.0.0 12 | with: 13 | token: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/benchmark.yml: -------------------------------------------------------------------------------- 1 | name: Benchmark 2 | on: 3 | push: 4 | branches: master 5 | jobs: 6 | benchmark_with_bencher: 7 | name: Benchmark with Bencher 8 | runs-on: ubuntu-22.04 9 | env: 10 | BENCHER_PROJECT: mpeg2ts-reader 11 | BENCHER_API_TOKEN: ${{ secrets.BENCHER_API_TOKEN }} 12 | steps: 13 | - uses: actions/checkout@v4 14 | with: 15 | lfs: true 16 | 17 | - name: Install toolchain 18 | uses: dtolnay/rust-toolchain@1.79.0 19 | 20 | - uses: bencherdev/bencher@main 21 | - uses: cargo-bins/cargo-binstall@main 22 | 23 | - run: sudo apt update && sudo apt install valgrind 24 | 25 | - name: Install iai-callgrind-runner 26 | run: | 27 | version=$(cargo metadata --format-version=1 |\ 28 | jq '.packages[] | select(.name == "iai-callgrind").version' |\ 29 | tr -d '"' 30 | ) 31 | cargo binstall iai-callgrind-runner --no-confirm --version $version 32 | 33 | - run: | 34 | IAI_CALLGRIND_COLOR=never cargo bench --bench ci_bench > perf.txt 35 | - run: | 36 | bencher run \ 37 | --branch "$GITHUB_REF_NAME" \ 38 | --err \ 39 | --adapter rust_iai_callgrind \ 40 | --hash "$GITHUB_SHA" \ 41 | --file "perf.txt" 42 | -------------------------------------------------------------------------------- /.github/workflows/pr-benchmark-upload-from-main.yml: -------------------------------------------------------------------------------- 1 | name: Track Benchmarks 2 | 3 | on: 4 | workflow_run: 5 | workflows: [Run and Cache Benchmarks] 6 | types: 7 | - completed 8 | 9 | jobs: 10 | track_with_bencher: 11 | if: github.event.workflow_run.conclusion == 'success' 12 | runs-on: ubuntu-22.04 13 | env: 14 | BENCHER_PROJECT: mpeg2ts-reader 15 | BENCHER_ADAPTER: rust_iai_callgrind 16 | BENCHMARK_RESULTS: benchmark_results.txt 17 | PR_EVENT: event.json 18 | steps: 19 | - name: Download Benchmark Results 20 | uses: dawidd6/action-download-artifact@v9 21 | with: 22 | name: ${{ env.BENCHMARK_RESULTS }} 23 | run_id: ${{ github.event.workflow_run.id }} 24 | - name: Download PR Event 25 | uses: dawidd6/action-download-artifact@v9 26 | with: 27 | name: ${{ env.PR_EVENT }} 28 | run_id: ${{ github.event.workflow_run.id }} 29 | - name: Export PR Event Data 30 | uses: actions/github-script@v7 31 | with: 32 | script: | 33 | let fs = require('fs'); 34 | let prEvent = JSON.parse(fs.readFileSync(process.env.PR_EVENT, {encoding: 'utf8'})); 35 | core.exportVariable("PR_HEAD", prEvent.pull_request.head.ref); 36 | core.exportVariable("PR_BASE", prEvent.pull_request.base.ref); 37 | core.exportVariable("PR_BASE_SHA", prEvent.pull_request.base.sha); 38 | core.exportVariable("PR_DEFAULT", prEvent.pull_request.base.repo.default_branch); 39 | core.exportVariable("PR_NUMBER", prEvent.number); 40 | - uses: bencherdev/bencher@main 41 | - name: Track Benchmarks with Bencher 42 | run: | 43 | bencher run \ 44 | --ci-only-thresholds \ 45 | --branch "$PR_HEAD" \ 46 | --start-point "$PR_BASE" \ 47 | --start-point-hash "$PR_BASE_SHA" \ 48 | --start-point-clone-thresholds \ 49 | --start-point-reset \ 50 | --ci-number "$PR_NUMBER" \ 51 | --github-actions "${{ secrets.GITHUB_TOKEN }}" \ 52 | --token "${{ secrets.BENCHER_API_TOKEN }}" \ 53 | --err \ 54 | --file "$BENCHMARK_RESULTS" 55 | -------------------------------------------------------------------------------- /.github/workflows/pr-benchmark.yml: -------------------------------------------------------------------------------- 1 | name: Run and Cache Benchmarks 2 | 3 | on: 4 | pull_request: 5 | types: [opened, reopened, synchronize] 6 | 7 | jobs: 8 | benchmark: 9 | name: Run Benchmarks 10 | runs-on: ubuntu-22.04 11 | steps: 12 | - uses: actions/checkout@v4 13 | with: 14 | lfs: true 15 | - uses: cargo-bins/cargo-binstall@main 16 | 17 | - run: sudo apt-get install valgrind 18 | 19 | - name: Install iai-callgrind-runner 20 | run: | 21 | version=$(cargo metadata --format-version=1 |\ 22 | jq '.packages[] | select(.name == "iai-callgrind").version' |\ 23 | tr -d '"' 24 | ) 25 | cargo binstall --no-confirm iai-callgrind-runner --version $version 26 | 27 | - run: | 28 | IAI_CALLGRIND_COLOR=never cargo bench --bench ci_bench > benchmark_results.txt 29 | - name: Upload Benchmark Results 30 | uses: actions/upload-artifact@v4 31 | with: 32 | name: benchmark_results.txt 33 | path: ./benchmark_results.txt 34 | - name: Upload GitHub Pull Request Event 35 | uses: actions/upload-artifact@v4 36 | with: 37 | name: event.json 38 | path: ${{ github.event_path }} -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-22.04 13 | 14 | steps: 15 | - uses: actions/checkout@v4 16 | 17 | - name: Install toolchain 18 | uses: dtolnay/rust-toolchain@1.79.0 19 | 20 | - name: Build 21 | run: cargo build --verbose --all-targets 22 | 23 | - name: Run tests 24 | # avoid running the bench_ci, for which there is a separate github 25 | # workflow 26 | run: cargo test --verbose --examples --tests --bench bench 27 | 28 | 29 | coverage: 30 | name: Coverage 31 | runs-on: ubuntu-22.04 32 | container: 33 | image: xd009642/tarpaulin:0.28.0-slim 34 | options: --security-opt seccomp=unconfined 35 | steps: 36 | - name: Checkout repository 37 | uses: actions/checkout@v4 38 | - name: Install toolchain 39 | uses: dtolnay/rust-toolchain@1.79.0 40 | 41 | - name: Generate code coverage 42 | run: | 43 | cargo tarpaulin --engine llvm --verbose --timeout 120 --exclude-files shootout --exclude-files benches --exclude-files fuzz --out Lcov -- --test-threads 1 44 | 45 | - name: upload to Coveralls 46 | uses: coverallsapp/github-action@v2.3.6 47 | with: 48 | github-token: ${{ secrets.GITHUB_TOKEN }} 49 | path-to-lcov: './lcov.info' 50 | 51 | 52 | clippy: 53 | name: Lint 📎 54 | runs-on: ubuntu-22.04 55 | steps: 56 | - name: Checkout repository 57 | uses: actions/checkout@v4 58 | - name: Install toolchain 59 | uses: dtolnay/rust-toolchain@1.79.0 60 | with: 61 | components: clippy 62 | 63 | - name: Run clippy 64 | uses: actions-rs-plus/clippy-check@v2.2.1 65 | with: 66 | args: --all-features 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target/ 3 | **/*.swp 4 | resources/ 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Unreleased 4 | 5 | ## 0.18.2 - 2025-01-29 6 | 7 | ### Fixed 8 | - Make `StreamType(u8)` constructor public so that it can be used in `const` contexts in other crates 9 | 10 | ## 0.18.1 - 2025-01-29 11 | 12 | ### Fixed 13 | - Added `StreamType::from(u8)` which was missing in release 1.18.0 14 | 15 | ## 0.18.0 - 2025-01-28 16 | 17 | ### Changed 18 | - The `pes::StreamType` and `StreamId` types changed from enums to structs. These changes reduce the risk of silent 19 | breakage in future releases of this crate, if support for additional `StreamType` or `StreamId` values are added. 20 | - `descriptor::iso_639_language::Code::code()` no longer takes any arguments, and now returns `Cow<'_, str>` rather 21 | than `Result` (due to the switch from the `encoding` crate to the `encoding_rs` crate). 22 | 23 | ### Fixed 24 | - Removed the dependency on the unmaintained `encoding` create and switched to `encoding_rs`. See 25 | [RUSTSEC-2021-0153](https://rustsec.org/advisories/RUSTSEC-2021-0153). 26 | 27 | ## 0.16.0 - 2024-02-20 28 | ### Changed 29 | - Updated to Rust 2021 edition 30 | - `packet::TransportScramblingControl` changed from an enum to a struct. New `is_scrambled()` and `scheme()` methods 31 | on `TransportScramblingControl` replace the functionality of the old enum variants. The changed type layout 32 | slightly improves performance. 33 | - `packet::AdaptationControl` changed from an enum to a struct. Its `has_payload()` method now takes `self` by 34 | reference, and a new `has_adaptation_field()` method has been added. The changed type layout slightly improves 35 | performance. 36 | 37 | ### Added 38 | - `FormatIdentifier` is now re-exported from the 39 | [smptera-format-identifiers-rust](https://crates.io/crates/smptera-format-identifiers-rust) crate so that calling 40 | code can more easily use the same version of it as the `mpeg2ts-reader` crate. 41 | 42 | ## 0.15.0 - 2021-04-17 43 | 44 | ### Changed 45 | - `Iso639LanguageDescriptor::languages()` now produces `Result` rather than just `Language` 46 | - Since we don't support decryption, scrambled TS packets (packets with values of `transport_scrambling_control` other 47 | than `0`) are now dropped, to prevent the application being passed bogus data 48 | - `AdaptationFieldExtension::new()` changed to return `Result` 49 | 50 | ### Fixed 51 | - Fixed a panic when parsing truncated PMT data 52 | - Fixed a panic when parsing a truncated descriptor value 53 | - Fixed a panic when parsing a truncated language code in `iso_639_language_descriptor` 54 | - Fixed a panic when reported PES header length does not fit within available space 55 | - Fixed a panic due to a bug parsing PES header `ESCR_base` field 56 | - Fixes a panic when `PES_header_data_length` is not long enough to accommodate all the headers actually present 57 | - Fixed so we accept very short but still syntactically valid PSI sections previously rejected in some cases 58 | - Fixed a panic on TS packet with `adaptation_field_extension_flag` set, but `adaptation_field_extension_length` is `0` 59 | 60 | ## 0.14.0 - 2021-04-11 61 | ### Changed 62 | - The `pes::ElementaryStreamConsumer` type and its methods are now parameterised to gain access to the context object 63 | you provided to the demultiplexer. Thanks @fkaa. 64 | - `RegistrationDescriptor::format_identifier()` return type changed from u32 to a value of the `FormatIdentifier` enum 65 | from the [smptera-format-identifiers-rust](https://crates.io/crates/smptera-format-identifiers-rust) crate. Also 66 | `RegistrationDescriptor` fields are no longer public. 67 | - `Pid::new()` is now a `const` function. Other crates can now define `Pid` constants. 68 | - Definitions of constants for 'PAT' and 'stuffing' PIDs have been relocated, now that don't have to be in the same 69 | module as the `Pid` type. 70 | 71 | ### Added 72 | - Implementation of `TryFrom` for `Pid` 73 | 74 | ## 0.13.0 75 | ### Changed 76 | - The `descriptor_enum!{}` macro no longer provides a default case (which used to produce `Error`), so callers which don't define 77 | mappings for all possible descriptor tag values (`0`-`255`) will now get a compiler error. 78 | ### Fixed 79 | - Fixed incorrect value of `Timestamp::MAX`. 80 | ### Added 81 | - Added `Timestamp::TIMEBASE` constant. 82 | 83 | ## 0.12.00 84 | ### Added 85 | - `AVC_video_descriptor()` parsing. 86 | - `maximum_bitrate_descriptor()` parsing. 87 | - `Timestamp::likely_wrapped_since()` utility for detecting wraparound, and supporting `Timestamp::MAX` constant. 88 | 89 | ## 0.11.0 90 | ### Added 91 | - Made `mpegts_crc::sum_32()` public. 92 | - Added types to `psi` module for handling 'compact syntax' sections (mirroring existing types for handling 'section 93 | syntax' sections). 94 | 95 | ## 0.10.0 96 | ### Added 97 | - `StreamType::is_pes()` util function to identify those StreamType values that the spec expects to carry PES content. 98 | 99 | ## 0.9.0 100 | ### Fixed 101 | - Made the methods of `descriptor::iso_639_language::Language` public (they were private by mistake) 102 | - Drop TS packets that have `transport_error_indicator` flag set, rather than passing known-bad data to the 103 | application. 104 | 105 | ### Added 106 | - Some more descriptor-tag values in `CoreDescriptors` (but not the descriptor definitions themselves yet). 107 | 108 | ### Changed 109 | - Removed the `StreamConstructor` trait, and merged its previous responsibilities into `DemuxContext`. This makes it 110 | much simpler for client code to gain access to any relevant `DemuxContext` state when the demuxer requests a handler 111 | for a newly discovered stream within the TS. 112 | - Added a `StreamId` enum to replace the `u8` previously used as the return value for `PesHeader::stream_id()` 113 | - Removed single usage of `hex-slice` crate; resulting Debug impl is not quite so nice, but now there's one less 114 | dependency. 115 | - Avoid calling `start_stream()` on `ElementaryStreamConsumer` after parsing errors. It was only intended to be 116 | called when the substream was first encountered in the multiplex. 117 | 118 | ## 0.8.0 119 | ### Fixed 120 | - Avoid panics due to 0-length `adaptation_field`, larger-than-expected `program_info_length` and too small 121 | `section_length`. All found through fuzz testing. 122 | 123 | ### Changed 124 | - All public API members now have at least minimal documentation 125 | - Removed the `demultiplex::UnhandledPid` type to try and simplify the API slightly. 126 | - Removed `PesPacketConsumer`. The types `PesPacketFilter` and `PesPacketConsumer` seem in hindsight to be redundant 127 | (the first just being a thin wrapper for the second). `PesPacketFilter` now implements the complete functionality. 128 | - Removed `PmtSection::program_info_length()` and `StreamInfo::es_info_length()` from public API. 129 | - Changed `PmtSection::pcr_pid()` to return a `Pid` (got missed when others changed from `u16` to `Pid`) in version 130 | 0.7.0. 131 | 132 | ## 0.7.0 133 | ### Fixed 134 | - Removed some unused dependencies 135 | 136 | ### Changed 137 | - Changed the representation of PID values in the API from plain `u16` to a new `Pid` wrapper, 138 | so that the API can't represent invalid PID values. 139 | - `FilterRequest::ByStream` changes from tuple to struct variant, and gains `program_id` of the 140 | program to which the stream belongs. 141 | - All uses of `println!()` have been replaced with use of `warn!()` from the `log` crate. 142 | 143 | ## 0.6.0 144 | ### Fixed 145 | - PES data is no longer truncated at the end of the TS packet with 146 | `payload_unit_start_indicator` 147 | 148 | ### Changed 149 | - Many methods previously taking a `Packet` by value now instead take it by reference. This breaks with the old API, 150 | but may deliver a small performance improvement for some workloads. 151 | - Many objects that previously offered a no-args `new()` method have had this replaced with a `default()` impl. 152 | - The `PCR` type has been renamed `ClockRef`, since it's used to represent the values of both 153 | _Prograem Clock Reference_ and _Elementry Stream Clock Reference_ fields. 154 | - `descriptor::RegistrationDescriptor` became `descriptor::registration::RegistrationDescriptor`. 155 | New descriptor implementations will be added each in their own source file. 156 | - Moved PAT/PMT types from `demultiplex` module into `psi::pat` and `psi::pmt` modules. 157 | - Refactored some methods returning custom `Iterator` types to instead return `impl Iterator`, so that the 158 | actual iterator types can be hidden from the public API 159 | - `pes_packet_length` is now represented as `enum PesLength`, rather than 160 | directly as a `u16`, so that the special status of the length-value 0 can be 161 | made explicit (it's now mapped to `PesLength::Unbounded`) 162 | 163 | ### Added 164 | 165 | - Added `iso_639_language_descriptor` support 166 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mpeg2ts-reader" 3 | version = "0.18.2" 4 | authors = ["David Holroyd "] 5 | description = "Parser for MPEG Transport Stream data" 6 | repository = "https://github.com/dholroyd/mpeg2ts-reader" 7 | license = "MIT/Apache-2.0" 8 | categories = [ "multimedia::video", "parser-implementations" ] 9 | keywords = [ "mpegts", "ISO-13818-1", "H-222-0" ] 10 | readme = "README.md" 11 | edition = "2021" 12 | 13 | exclude = [ "testsrc.ts" ] 14 | 15 | [dependencies] 16 | fixedbitset = "0.5.0" 17 | encoding_rs = "0.8" 18 | log = "0.4" 19 | smptera-format-identifiers-rust = "0.4.0" 20 | 21 | [dev-dependencies] 22 | assert_matches = "1.5.0" 23 | bitstream-io = "2.2" 24 | criterion = "0.5.1" 25 | hex-literal = "0.2.1" 26 | hex-slice = "0.1.4" 27 | env_logger = "0.11.2" 28 | iai-callgrind = "0.14.0" 29 | 30 | [[bench]] 31 | name = "bench" 32 | harness = false 33 | 34 | [[bench]] 35 | name = "ci_bench" 36 | harness = false 37 | 38 | [profile.bench] 39 | # for profiling, 40 | debug = true 41 | 42 | [lints.rust] 43 | # per https://github.com/rust-fuzz/cargo-fuzz/issues/372 44 | unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] } 45 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 David Holroyd 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | mpeg2ts-reader 2 | ============== 3 | 4 | Rust reader for MPEG2 Transport Stream data 5 | 6 | [![crates.io version](https://img.shields.io/crates/v/mpeg2ts-reader.svg)](https://crates.io/crates/mpeg2ts-reader) 7 | [![Documentation](https://docs.rs/mpeg2ts-reader/badge.svg)](https://docs.rs/mpeg2ts-reader) 8 | [![Coverage Status](https://coveralls.io/repos/github/dholroyd/mpeg2ts-reader/badge.svg)](https://coveralls.io/github/dholroyd/mpeg2ts-reader) 9 | ![Unstable API](https://img.shields.io/badge/API_stability-unstable-yellow.svg) 10 | 11 | Zero-copy access to payload data within an MPEG Transport Stream. 12 | 13 | This crate, 14 | - implements a low-level state machine that recognises the structural elements of Transport Stream syntax 15 | - provides traits that you should implement to define your application-specific processing of the contained data. 16 | 17 | # Example 18 | 19 | Dump H264 payload data as hex. 20 | 21 | ```rust 22 | #[macro_use] 23 | extern crate mpeg2ts_reader; 24 | extern crate hex_slice; 25 | 26 | use hex_slice::AsHex; 27 | use mpeg2ts_reader::demultiplex; 28 | use mpeg2ts_reader::packet; 29 | use mpeg2ts_reader::pes; 30 | use mpeg2ts_reader::psi; 31 | use mpeg2ts_reader::StreamType; 32 | use std::cmp; 33 | use std::env; 34 | use std::fs::File; 35 | use std::io::Read; 36 | 37 | // This macro invocation creates an enum called DumpFilterSwitch, encapsulating all possible ways 38 | // that this application may handle transport stream packets. Each enum variant is just a wrapper 39 | // around an implementation of the PacketFilter trait 40 | packet_filter_switch! { 41 | DumpFilterSwitch { 42 | // the DumpFilterSwitch::Pes variant will perform the logic actually specific to this 43 | // application, 44 | Pes: pes::PesPacketFilter, 45 | 46 | // these definitions are boilerplate required by the framework, 47 | Pat: demultiplex::PatPacketFilter, 48 | Pmt: demultiplex::PmtPacketFilter, 49 | 50 | // this variant will be used when we want to ignore data in the transport stream that this 51 | // application does not care about 52 | Null: demultiplex::NullPacketFilter, 53 | } 54 | } 55 | 56 | // This macro invocation creates a type called DumpDemuxContext, which is our application-specific 57 | // implementation of the DemuxContext trait. 58 | demux_context!(DumpDemuxContext, DumpFilterSwitch); 59 | 60 | // When the de-multiplexing process needs to create a PacketFilter instance to handle a particular 61 | // kind of data discovered within the Transport Stream being processed, it will send a 62 | // FilterRequest to our application-specific implementation of the do_construct() method 63 | impl DumpDemuxContext { 64 | fn do_construct(&mut self, req: demultiplex::FilterRequest<'_, '_>) -> DumpFilterSwitch { 65 | match req { 66 | // The 'Program Association Table' is is always on PID 0. We just use the standard 67 | // handling here, but an application could insert its own logic if required, 68 | demultiplex::FilterRequest::ByPid(packet::Pid::PAT) => { 69 | DumpFilterSwitch::Pat(demultiplex::PatPacketFilter::default()) 70 | } 71 | // 'Stuffing' data on PID 0x1fff may be used to pad-out parts of the transport stream 72 | // so that it has constant overall bitrate. This causes it to be ignored if present. 73 | demultiplex::FilterRequest::ByPid(packet::Pid::STUFFING) => { 74 | DumpFilterSwitch::Null(demultiplex::NullPacketFilter::default()) 75 | } 76 | // Some Transport Streams will contain data on 'well known' PIDs, which are not 77 | // announced in PAT / PMT metadata. This application does not process any of these 78 | // well known PIDs, so we register NullPacketFiltet such that they will be ignored 79 | demultiplex::FilterRequest::ByPid(_) => { 80 | DumpFilterSwitch::Null(demultiplex::NullPacketFilter::default()) 81 | } 82 | // This match-arm installs our application-specific handling for each H264 stream 83 | // discovered within the transport stream, 84 | demultiplex::FilterRequest::ByStream { 85 | stream_type: StreamType::H264, 86 | pmt, 87 | stream_info, 88 | .. 89 | } => PtsDumpElementaryStreamConsumer::construct(pmt, stream_info), 90 | // We need to have a match-arm to specify how to handle any other StreamType values 91 | // that might be present; we answer with NullPacketFilter so that anything other than 92 | // H264 (handled above) is ignored, 93 | demultiplex::FilterRequest::ByStream { .. } => { 94 | DumpFilterSwitch::Null(demultiplex::NullPacketFilter::default()) 95 | } 96 | // The 'Program Map Table' defines the sub-streams for a particular program within the 97 | // Transport Stream (it is common for Transport Streams to contain only one program). 98 | // We just use the standard handling here, but an application could insert its own 99 | // logic if required, 100 | demultiplex::FilterRequest::Pmt { 101 | pid, 102 | program_number, 103 | } => DumpFilterSwitch::Pmt(demultiplex::PmtPacketFilter::new(pid, program_number)), 104 | // Ignore 'Network Information Table', if present, 105 | demultiplex::FilterRequest::Nit { .. } => { 106 | DumpFilterSwitch::Null(demultiplex::NullPacketFilter::default()) 107 | } 108 | } 109 | } 110 | } 111 | 112 | // Implement the ElementaryStreamConsumer to just dump and PTS/DTS timestamps to stdout 113 | pub struct PtsDumpElementaryStreamConsumer { 114 | pid: packet::Pid, 115 | len: Option, 116 | } 117 | impl PtsDumpElementaryStreamConsumer { 118 | fn construct( 119 | _pmt_sect: &psi::pmt::PmtSection, 120 | stream_info: &psi::pmt::StreamInfo, 121 | ) -> DumpFilterSwitch { 122 | let filter = pes::PesPacketFilter::new(PtsDumpElementaryStreamConsumer { 123 | pid: stream_info.elementary_pid(), 124 | len: None, 125 | }); 126 | DumpFilterSwitch::Pes(filter) 127 | } 128 | } 129 | impl pes::ElementaryStreamConsumer for PtsDumpElementaryStreamConsumer { 130 | fn start_stream(&mut self) {} 131 | fn begin_packet(&mut self, header: pes::PesHeader) { 132 | match header.contents() { 133 | pes::PesContents::Parsed(Some(parsed)) => { 134 | match parsed.pts_dts() { 135 | Ok(pes::PtsDts::PtsOnly(Ok(pts))) => { 136 | print!("{:?}: pts {:#08x} ", self.pid, pts.value()) 137 | } 138 | Ok(pes::PtsDts::Both { 139 | pts: Ok(pts), 140 | dts: Ok(dts), 141 | }) => print!( 142 | "{:?}: pts {:#08x} dts {:#08x} ", 143 | self.pid, 144 | pts.value(), 145 | dts.value() 146 | ), 147 | _ => (), 148 | } 149 | let payload = parsed.payload(); 150 | self.len = Some(payload.len()); 151 | println!( 152 | "{:02x}", 153 | payload[..cmp::min(payload.len(), 16)].plain_hex(false) 154 | ) 155 | } 156 | pes::PesContents::Parsed(None) => (), 157 | pes::PesContents::Payload(payload) => { 158 | self.len = Some(payload.len()); 159 | println!( 160 | "{:?}: {:02x}", 161 | self.pid, 162 | payload[..cmp::min(payload.len(), 16)].plain_hex(false) 163 | ) 164 | } 165 | } 166 | } 167 | fn continue_packet(&mut self, data: &[u8]) { 168 | println!( 169 | "{:?}: continues {:02x}", 170 | self.pid, 171 | data[..cmp::min(data.len(), 16)].plain_hex(false) 172 | ); 173 | self.len = self.len.map(|l| l + data.len()); 174 | } 175 | fn end_packet(&mut self) { 176 | println!("{:?}: end of packet length={:?}", self.pid, self.len); 177 | } 178 | fn continuity_error(&mut self) {} 179 | } 180 | 181 | fn main() { 182 | // open input file named on command line, 183 | let name = env::args().nth(1).unwrap(); 184 | let mut f = File::open(&name).expect(&format!("file not found: {}", &name)); 185 | 186 | // create the context object that stores the state of the transport stream demultiplexing 187 | // process 188 | let mut ctx = DumpDemuxContext::new(); 189 | 190 | // create the demultiplexer, which will use the ctx to create a filter for pid 0 (PAT) 191 | let mut demux = demultiplex::Demultiplex::new(&mut ctx); 192 | 193 | // consume the input file, 194 | let mut buf = [0u8; 188 * 1024]; 195 | loop { 196 | match f.read(&mut buf[..]).expect("read failed") { 197 | 0 => break, 198 | n => demux.push(&mut ctx, &buf[0..n]), 199 | } 200 | } 201 | } 202 | ``` 203 | 204 | # Performance shoot-out 205 | 206 | Comparing this crate to a couple of others which you might use to read a Transport Stream -- 207 | [mpeg2ts](https://crates.io/crates/mpeg2ts) and [ffmpg-sys](https://crates.io/crates/ffmpeg-sys): 208 | 209 | ![Performance](https://github.com/dholroyd/mpeg2ts-reader/raw/master/shootout/report.svg?sanitize=true) 210 | 211 | The benchmarks producing the above chart data are in the [`shootout`](shootout) folder. (If the benchmarks are giving 212 | an unfair representation of relative performance, that's a mistake -- please raise a bug!) 213 | 214 | The conditions of the test are, 215 | * the data is already in memory (no network/disk access) 216 | * test dataset is larger than CPU cache 217 | * processing is happening on a single core (no multiprocessing of the stream). 218 | 219 | # Supported Transport Stream features 220 | 221 | Not all Transport Stream features are supported yet. Here's a summary of what's available, 222 | and what's yet to come: 223 | 224 | - Framing 225 | - [x] _ISO/IEC 13818-1_ 188-byte packets 226 | - [ ] m2ts 192-byte packets (would be nice if an external crate could support, at least) 227 | - [ ] recovery after loss of synchronisation 228 | - Transport Stream packet 229 | - [x] Fixed headers 230 | - [x] Adaptation field 231 | - [ ] TS-level scrambling (values of `transport_scrambling_control` other than `0`) not supported 232 | - Program Specific Information tables 233 | - [x] Section syntax 234 | - [ ] 'Multi-section' tables 235 | - [x] PAT - Program Association Table 236 | - [x] PMT - Program Mapping Table 237 | - [ ] TSDT - Transport Stream Description Table 238 | - Packetised Elementary Stream syntax 239 | - [x] PES_packet_data 240 | - [x] PTS/DTS 241 | - [x] ESCR 242 | - [x] ES_rate 243 | - [x] DSM_trick_mode 244 | - [x] additional_copy_info 245 | - [x] PES_CRC 246 | - [ ] PES_extension 247 | - Descriptors 248 | - [ ] video_stream_descriptor 249 | - [ ] audio_stream_descriptor 250 | - [ ] hierarchy_descriptor 251 | - [x] registration_descriptor 252 | - [ ] data_stream_alignment_descriptor 253 | - [ ] target_background_grid_descriptor 254 | - [ ] video_window_descriptor 255 | - [ ] ca_descriptor 256 | - [x] iso_639_language_descriptor 257 | - [ ] system_clock_descriptor 258 | - [ ] multiplex_buffer_utilization_descriptor 259 | - [ ] copyright_descriptor 260 | - [x] maximum_bitrate_descriptor 261 | - [ ] private_data_indicator_descriptor 262 | - [ ] smoothing_buffer_descriptor 263 | - [ ] std_descriptor 264 | - [ ] ibp_descriptor 265 | - [ ] mpeg4_video_descriptor 266 | - [ ] mpeg4_audio_descriptor 267 | - [ ] iod_descriptor 268 | - [ ] sl_descriptor 269 | - [ ] fmc_descriptor 270 | - [ ] external_es_id_descriptor 271 | - [ ] muxcode_descriptor 272 | - [ ] fmxbuffersize_descriptor 273 | - [ ] multiplexbuffer_descriptor 274 | - [x] AVC_video_descriptor 275 | 276 | # Related crates 277 | 278 | Since this crate only covers the core Transport Stream spec, related specs are to be covered by other crates. 279 | 280 | - [scte35-reader](https://crates.io/crates/scte35-reader) extends `mpeg2ts-reader` to support SCTE-35 data. 281 | - [en-300-468-reader](https://crates.io/crates/en-300-468-reader) extends `mpeg2ts-reader` to support reading the 282 | *Service Description Table* (SDT) defined in ETSI standard *EN 300 486*. -------------------------------------------------------------------------------- /benches/bench.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate criterion; 3 | #[macro_use] 4 | extern crate mpeg2ts_reader; 5 | 6 | use criterion::{Criterion, Throughput}; 7 | use mpeg2ts_reader::demultiplex; 8 | use mpeg2ts_reader::pes; 9 | use mpeg2ts_reader::psi; 10 | use std::fs::File; 11 | use std::io::Read; 12 | 13 | packet_filter_switch! { 14 | NullFilterSwitch { 15 | Pat: demultiplex::PatPacketFilter, 16 | Pmt: demultiplex::PmtPacketFilter, 17 | Null: demultiplex::NullPacketFilter, 18 | NullPes: pes::PesPacketFilter, 19 | } 20 | } 21 | demux_context!(NullDemuxContext, NullFilterSwitch); 22 | impl NullDemuxContext { 23 | fn do_construct(&mut self, req: demultiplex::FilterRequest<'_, '_>) -> NullFilterSwitch { 24 | match req { 25 | demultiplex::FilterRequest::ByPid(psi::pat::PAT_PID) => { 26 | NullFilterSwitch::Pat(demultiplex::PatPacketFilter::default()) 27 | } 28 | demultiplex::FilterRequest::ByPid(_) => { 29 | NullFilterSwitch::Null(demultiplex::NullPacketFilter::default()) 30 | } 31 | demultiplex::FilterRequest::ByStream { 32 | pmt, stream_info, .. 33 | } => NullElementaryStreamConsumer::construct(pmt, stream_info), 34 | demultiplex::FilterRequest::Pmt { 35 | pid, 36 | program_number, 37 | } => NullFilterSwitch::Pmt(demultiplex::PmtPacketFilter::new(pid, program_number)), 38 | demultiplex::FilterRequest::Nit { .. } => { 39 | NullFilterSwitch::Null(demultiplex::NullPacketFilter::default()) 40 | } 41 | } 42 | } 43 | } 44 | 45 | pub struct NullElementaryStreamConsumer {} 46 | impl NullElementaryStreamConsumer { 47 | fn construct( 48 | _pmt_sect: &psi::pmt::PmtSection, 49 | stream_info: &psi::pmt::StreamInfo, 50 | ) -> NullFilterSwitch { 51 | println!("stream info: {:?}", stream_info); 52 | let filter = pes::PesPacketFilter::new(NullElementaryStreamConsumer {}); 53 | NullFilterSwitch::NullPes(filter) 54 | } 55 | } 56 | impl pes::ElementaryStreamConsumer for NullElementaryStreamConsumer { 57 | fn start_stream(&mut self, _ctx: &mut Ctx) {} 58 | fn begin_packet(&mut self, _ctx: &mut Ctx, header: pes::PesHeader) { 59 | if let pes::PesContents::Parsed(Some(content)) = header.contents() { 60 | match content.pts_dts() { 61 | Ok(pes::PtsDts::PtsOnly(Ok(ts))) => { 62 | criterion::black_box(ts); 63 | } 64 | Ok(pes::PtsDts::Both { pts: Ok(ts), .. }) => { 65 | criterion::black_box(ts); 66 | } 67 | _ => (), 68 | }; 69 | } 70 | } 71 | fn continue_packet(&mut self, _ctx: &mut Ctx, _data: &[u8]) {} 72 | fn end_packet(&mut self, _ctx: &mut Ctx) {} 73 | fn continuity_error(&mut self, _ctx: &mut Ctx) {} 74 | } 75 | 76 | fn mpeg2ts_reader(c: &mut Criterion) { 77 | let buf = if std::env::var("GITHUB_ACTIONS").is_ok() || std::env::var("GIT_AUTHOR_DATE").is_ok() 78 | { 79 | // HACK 80 | // we don't run benchmarking on github, or in pre-commit hook, but we do test that the 81 | // benchmark code executes without errors, so we need an input buffer of some kind for the 82 | // code to consume 83 | vec![0; 188] 84 | } else { 85 | let mut f = File::open("testsrc.ts") 86 | .expect("Test file missing. To create, run: mkdir -p resources && ffmpeg -f lavfi -i testsrc=duration=20:size=640x360:rate=30,noise=alls=20:allf=t+u -f lavfi -i sine=duration=20:frequency=1:beep_factor=480:sample_rate=48000 -c:v libx264 -b:v 20M -map 0:v -c:a aac -b:a 128k -map 1:a -vf format=yuv420p -f mpegts testsrc.ts"); 87 | let l = f.metadata().unwrap().len() as usize; 88 | let size = l.min(188 * 200_000); 89 | let mut buf = vec![0; size]; 90 | f.read(&mut buf[..]).unwrap(); 91 | buf 92 | }; 93 | let mut ctx = NullDemuxContext::new(); 94 | let mut demux = demultiplex::Demultiplex::new(&mut ctx); 95 | let mut group = c.benchmark_group("parse"); 96 | group.throughput(Throughput::Bytes(buf.len() as _)); 97 | group.bench_function("parse", move |b| { 98 | b.iter(|| demux.push(&mut ctx, &buf[..])); 99 | }); 100 | group.finish(); 101 | } 102 | 103 | criterion_group!(benches, mpeg2ts_reader); 104 | criterion_main!(benches); 105 | -------------------------------------------------------------------------------- /benches/ci_bench.rs: -------------------------------------------------------------------------------- 1 | use iai_callgrind::{library_benchmark, library_benchmark_group, main}; 2 | use mpeg2ts_reader::demultiplex; 3 | use mpeg2ts_reader::demux_context; 4 | use mpeg2ts_reader::packet_filter_switch; 5 | use mpeg2ts_reader::pes; 6 | use mpeg2ts_reader::psi; 7 | use std::fs::File; 8 | use std::io::Read; 9 | 10 | packet_filter_switch! { 11 | NullFilterSwitch { 12 | Pat: demultiplex::PatPacketFilter, 13 | Pmt: demultiplex::PmtPacketFilter, 14 | Null: demultiplex::NullPacketFilter, 15 | NullPes: pes::PesPacketFilter, 16 | } 17 | } 18 | demux_context!(NullDemuxContext, NullFilterSwitch); 19 | impl NullDemuxContext { 20 | fn do_construct(&mut self, req: demultiplex::FilterRequest<'_, '_>) -> NullFilterSwitch { 21 | match req { 22 | demultiplex::FilterRequest::ByPid(psi::pat::PAT_PID) => { 23 | NullFilterSwitch::Pat(demultiplex::PatPacketFilter::default()) 24 | } 25 | demultiplex::FilterRequest::ByPid(_) => { 26 | NullFilterSwitch::Null(demultiplex::NullPacketFilter::default()) 27 | } 28 | demultiplex::FilterRequest::ByStream { 29 | pmt, stream_info, .. 30 | } => NullElementaryStreamConsumer::construct(pmt, stream_info), 31 | demultiplex::FilterRequest::Pmt { 32 | pid, 33 | program_number, 34 | } => NullFilterSwitch::Pmt(demultiplex::PmtPacketFilter::new(pid, program_number)), 35 | demultiplex::FilterRequest::Nit { .. } => { 36 | NullFilterSwitch::Null(demultiplex::NullPacketFilter::default()) 37 | } 38 | } 39 | } 40 | } 41 | 42 | pub struct NullElementaryStreamConsumer {} 43 | impl NullElementaryStreamConsumer { 44 | fn construct( 45 | _pmt_sect: &psi::pmt::PmtSection, 46 | stream_info: &psi::pmt::StreamInfo, 47 | ) -> NullFilterSwitch { 48 | println!("stream info: {:?}", stream_info); 49 | let filter = pes::PesPacketFilter::new(NullElementaryStreamConsumer {}); 50 | NullFilterSwitch::NullPes(filter) 51 | } 52 | } 53 | impl pes::ElementaryStreamConsumer for NullElementaryStreamConsumer { 54 | fn start_stream(&mut self, _ctx: &mut Ctx) {} 55 | fn begin_packet(&mut self, _ctx: &mut Ctx, header: pes::PesHeader) { 56 | if let pes::PesContents::Parsed(Some(content)) = header.contents() { 57 | match content.pts_dts() { 58 | Ok(pes::PtsDts::PtsOnly(Ok(ts))) => { 59 | criterion::black_box(ts); 60 | } 61 | Ok(pes::PtsDts::Both { pts: Ok(ts), .. }) => { 62 | criterion::black_box(ts); 63 | } 64 | _ => (), 65 | }; 66 | } 67 | } 68 | fn continue_packet(&mut self, _ctx: &mut Ctx, _data: &[u8]) {} 69 | fn end_packet(&mut self, _ctx: &mut Ctx) {} 70 | fn continuity_error(&mut self, _ctx: &mut Ctx) {} 71 | } 72 | 73 | #[library_benchmark] 74 | fn reader() { 75 | let mut f = File::open("testsrc.ts") 76 | .expect("Test file missing. To create, run: mkdir -p resources && ffmpeg -f lavfi -i testsrc=duration=20:size=640x360:rate=30,noise=alls=20:allf=t+u -f lavfi -i sine=duration=20:frequency=1:beep_factor=480:sample_rate=48000 -c:v libx264 -b:v 20M -map 0:v -c:a aac -b:a 128k -map 1:a -vf format=yuv420p -f mpegts testsrc.ts"); 77 | let l = f.metadata().unwrap().len() as usize; 78 | let size = l.min(188 * 200_000); 79 | let mut buf = vec![0; size]; 80 | f.read(&mut buf[..]).unwrap(); 81 | 82 | let mut ctx = NullDemuxContext::new(); 83 | let mut demux = demultiplex::Demultiplex::new(&mut ctx); 84 | demux.push(&mut ctx, &buf[..]); 85 | } 86 | 87 | library_benchmark_group!( 88 | name = ci; 89 | benchmarks = reader 90 | ); 91 | 92 | main!(library_benchmark_groups = ci); 93 | -------------------------------------------------------------------------------- /examples/dump-pcr.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate mpeg2ts_reader; 3 | 4 | use mpeg2ts_reader::demultiplex; 5 | use mpeg2ts_reader::demultiplex::DemuxContext; 6 | use mpeg2ts_reader::demultiplex::PacketFilter; 7 | use mpeg2ts_reader::packet::Packet; 8 | use mpeg2ts_reader::psi; 9 | use std::env; 10 | use std::fs::File; 11 | use std::io::Read; 12 | use std::marker; 13 | 14 | packet_filter_switch! { 15 | PcrDumpFilterSwitch { 16 | Pat: demultiplex::PatPacketFilter, 17 | Pmt: demultiplex::PmtPacketFilter, 18 | Null: demultiplex::NullPacketFilter, 19 | Pcr: PcrPacketFilter, 20 | } 21 | } 22 | demux_context!(PcrDumpDemuxContext, PcrDumpFilterSwitch); 23 | impl PcrDumpDemuxContext { 24 | fn do_construct(&mut self, req: demultiplex::FilterRequest<'_, '_>) -> PcrDumpFilterSwitch { 25 | match req { 26 | demultiplex::FilterRequest::ByPid(psi::pat::PAT_PID) => { 27 | PcrDumpFilterSwitch::Pat(demultiplex::PatPacketFilter::default()) 28 | } 29 | demultiplex::FilterRequest::Pmt { 30 | pid, 31 | program_number, 32 | } => PcrDumpFilterSwitch::Pmt(demultiplex::PmtPacketFilter::new(pid, program_number)), 33 | 34 | demultiplex::FilterRequest::ByStream { 35 | pmt, stream_info, .. 36 | } => { 37 | if stream_info.elementary_pid() == pmt.pcr_pid() { 38 | PcrDumpFilterSwitch::Pcr(PcrPacketFilter::construct(pmt, stream_info)) 39 | } else { 40 | PcrDumpFilterSwitch::Null(demultiplex::NullPacketFilter::default()) 41 | } 42 | } 43 | 44 | demultiplex::FilterRequest::ByPid(_) => { 45 | PcrDumpFilterSwitch::Null(demultiplex::NullPacketFilter::default()) 46 | } 47 | demultiplex::FilterRequest::Nit { .. } => { 48 | PcrDumpFilterSwitch::Null(demultiplex::NullPacketFilter::default()) 49 | } 50 | } 51 | } 52 | } 53 | 54 | pub struct PcrPacketFilter { 55 | phantom: marker::PhantomData, 56 | } 57 | impl PcrPacketFilter { 58 | pub fn construct( 59 | _pmt: &psi::pmt::PmtSection, 60 | _stream_info: &psi::pmt::StreamInfo, 61 | ) -> PcrPacketFilter { 62 | Self::new() 63 | } 64 | pub fn new() -> PcrPacketFilter { 65 | PcrPacketFilter { 66 | phantom: marker::PhantomData, 67 | } 68 | } 69 | } 70 | impl PacketFilter for PcrPacketFilter { 71 | type Ctx = Ctx; 72 | fn consume(&mut self, _ctx: &mut Self::Ctx, pk: &Packet) { 73 | if let Some(adaptation_field) = pk.adaptation_field() { 74 | if let Ok(pcr) = adaptation_field.pcr() { 75 | println!("{:?} pcr={}", pk.pid(), u64::from(pcr)); 76 | } 77 | } 78 | } 79 | } 80 | 81 | fn main() { 82 | env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init(); 83 | 84 | // open input file named on command line, 85 | let name = env::args().nth(1).unwrap(); 86 | let mut f = File::open(&name).unwrap_or_else(|_| panic!("file not found: {}", &name)); 87 | 88 | // create the context object that stores the state of the transport stream demultiplexing 89 | // process 90 | let mut ctx = PcrDumpDemuxContext::new(); 91 | 92 | // create the demultiplexer, which will use the ctx to create a filter for pid 0 (PAT) 93 | let mut demux = demultiplex::Demultiplex::new(&mut ctx); 94 | 95 | // consume the input file, 96 | let mut buf = [0u8; 188 * 1024]; 97 | loop { 98 | match f.read(&mut buf[..]).expect("read failed") { 99 | 0 => break, 100 | n => demux.push(&mut ctx, &buf[0..n]), 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /examples/simple.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate mpeg2ts_reader; 3 | extern crate hex_slice; 4 | 5 | use hex_slice::AsHex; 6 | use mpeg2ts_reader::demultiplex; 7 | use mpeg2ts_reader::packet; 8 | use mpeg2ts_reader::pes; 9 | use mpeg2ts_reader::psi; 10 | use mpeg2ts_reader::StreamType; 11 | use std::cmp; 12 | use std::env; 13 | use std::fs::File; 14 | use std::io::Read; 15 | 16 | // This macro invocation creates an enum called DumpFilterSwitch, encapsulating all possible ways 17 | // that this application may handle transport stream packets. Each enum variant is just a wrapper 18 | // around an implementation of the PacketFilter trait 19 | packet_filter_switch! { 20 | DumpFilterSwitch { 21 | // the DumpFilterSwitch::Pes variant will perform the logic actually specific to this 22 | // application, 23 | Pes: pes::PesPacketFilter, 24 | 25 | // these definitions are boilerplate required by the framework, 26 | Pat: demultiplex::PatPacketFilter, 27 | Pmt: demultiplex::PmtPacketFilter, 28 | 29 | // this variant will be used when we want to ignore data in the transport stream that this 30 | // application does not care about 31 | Null: demultiplex::NullPacketFilter, 32 | } 33 | } 34 | 35 | // This macro invocation creates a type called DumpDemuxContext, which is our application-specific 36 | // implementation of the DemuxContext trait. 37 | demux_context!(DumpDemuxContext, DumpFilterSwitch); 38 | 39 | // When the de-multiplexing process needs to create a PacketFilter instance to handle a particular 40 | // kind of data discovered within the Transport Stream being processed, it will send a 41 | // FilterRequest to our application-specific implementation of the do_construct() method 42 | impl DumpDemuxContext { 43 | fn do_construct(&mut self, req: demultiplex::FilterRequest<'_, '_>) -> DumpFilterSwitch { 44 | match req { 45 | // The 'Program Association Table' is is always on PID 0. We just use the standard 46 | // handling here, but an application could insert its own logic if required, 47 | demultiplex::FilterRequest::ByPid(psi::pat::PAT_PID) => { 48 | DumpFilterSwitch::Pat(demultiplex::PatPacketFilter::default()) 49 | } 50 | // 'Stuffing' data on PID 0x1fff may be used to pad-out parts of the transport stream 51 | // so that it has constant overall bitrate. This causes it to be ignored if present. 52 | demultiplex::FilterRequest::ByPid(mpeg2ts_reader::STUFFING_PID) => { 53 | DumpFilterSwitch::Null(demultiplex::NullPacketFilter::default()) 54 | } 55 | // Some Transport Streams will contain data on 'well known' PIDs, which are not 56 | // announced in PAT / PMT metadata. This application does not process any of these 57 | // well known PIDs, so we register NullPacketFiltet such that they will be ignored 58 | demultiplex::FilterRequest::ByPid(_) => { 59 | DumpFilterSwitch::Null(demultiplex::NullPacketFilter::default()) 60 | } 61 | // This match-arm installs our application-specific handling for each H264 stream 62 | // discovered within the transport stream, 63 | demultiplex::FilterRequest::ByStream { 64 | stream_type: StreamType::H264, 65 | pmt, 66 | stream_info, 67 | .. 68 | } => PtsDumpElementaryStreamConsumer::construct(pmt, stream_info), 69 | // We need to have a match-arm to specify how to handle any other StreamType values 70 | // that might be present; we answer with NullPacketFilter so that anything other than 71 | // H264 (handled above) is ignored, 72 | demultiplex::FilterRequest::ByStream { .. } => { 73 | DumpFilterSwitch::Null(demultiplex::NullPacketFilter::default()) 74 | } 75 | // The 'Program Map Table' defines the sub-streams for a particular program within the 76 | // Transport Stream (it is common for Transport Streams to contain only one program). 77 | // We just use the standard handling here, but an application could insert its own 78 | // logic if required, 79 | demultiplex::FilterRequest::Pmt { 80 | pid, 81 | program_number, 82 | } => DumpFilterSwitch::Pmt(demultiplex::PmtPacketFilter::new(pid, program_number)), 83 | // Ignore 'Network Information Table', if present, 84 | demultiplex::FilterRequest::Nit { .. } => { 85 | DumpFilterSwitch::Null(demultiplex::NullPacketFilter::default()) 86 | } 87 | } 88 | } 89 | } 90 | 91 | // Implement the ElementaryStreamConsumer to just dump and PTS/DTS timestamps to stdout 92 | pub struct PtsDumpElementaryStreamConsumer { 93 | pid: packet::Pid, 94 | len: Option, 95 | } 96 | impl PtsDumpElementaryStreamConsumer { 97 | fn construct( 98 | _pmt_sect: &psi::pmt::PmtSection, 99 | stream_info: &psi::pmt::StreamInfo, 100 | ) -> DumpFilterSwitch { 101 | let filter = pes::PesPacketFilter::new(PtsDumpElementaryStreamConsumer { 102 | pid: stream_info.elementary_pid(), 103 | len: None, 104 | }); 105 | DumpFilterSwitch::Pes(filter) 106 | } 107 | } 108 | impl pes::ElementaryStreamConsumer for PtsDumpElementaryStreamConsumer { 109 | fn start_stream(&mut self, _ctx: &mut DumpDemuxContext) {} 110 | fn begin_packet(&mut self, _ctx: &mut DumpDemuxContext, header: pes::PesHeader) { 111 | match header.contents() { 112 | pes::PesContents::Parsed(Some(parsed)) => { 113 | match parsed.pts_dts() { 114 | Ok(pes::PtsDts::PtsOnly(Ok(pts))) => { 115 | print!("{:?}: pts {:#08x} ", self.pid, pts.value()) 116 | } 117 | Ok(pes::PtsDts::Both { 118 | pts: Ok(pts), 119 | dts: Ok(dts), 120 | }) => print!( 121 | "{:?}: pts {:#08x} dts {:#08x} ", 122 | self.pid, 123 | pts.value(), 124 | dts.value() 125 | ), 126 | _ => (), 127 | } 128 | let payload = parsed.payload(); 129 | self.len = Some(payload.len()); 130 | println!( 131 | "{:02x}", 132 | payload[..cmp::min(payload.len(), 16)].plain_hex(false) 133 | ) 134 | } 135 | pes::PesContents::Parsed(None) => (), 136 | pes::PesContents::Payload(payload) => { 137 | self.len = Some(payload.len()); 138 | println!( 139 | "{:?}: {:02x}", 140 | self.pid, 141 | payload[..cmp::min(payload.len(), 16)].plain_hex(false) 142 | ) 143 | } 144 | } 145 | } 146 | fn continue_packet(&mut self, _ctx: &mut DumpDemuxContext, data: &[u8]) { 147 | println!( 148 | "{:?}: continues {:02x}", 149 | self.pid, 150 | data[..cmp::min(data.len(), 16)].plain_hex(false) 151 | ); 152 | self.len = self.len.map(|l| l + data.len()); 153 | } 154 | fn end_packet(&mut self, _ctx: &mut DumpDemuxContext) { 155 | println!("{:?}: end of packet length={:?}", self.pid, self.len); 156 | } 157 | fn continuity_error(&mut self, _ctx: &mut DumpDemuxContext) {} 158 | } 159 | 160 | fn main() { 161 | env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init(); 162 | 163 | // open input file named on command line, 164 | let name = env::args().nth(1).unwrap(); 165 | let mut f = File::open(&name).unwrap_or_else(|_| panic!("file not found: {}", &name)); 166 | 167 | // create the context object that stores the state of the transport stream demultiplexing 168 | // process 169 | let mut ctx = DumpDemuxContext::new(); 170 | 171 | // create the demultiplexer, which will use the ctx to create a filter for pid 0 (PAT) 172 | let mut demux = demultiplex::Demultiplex::new(&mut ctx); 173 | 174 | // consume the input file, 175 | let mut buf = [0u8; 188 * 1024]; 176 | loop { 177 | match f.read(&mut buf[..]).expect("read failed") { 178 | 0 => break, 179 | n => demux.push(&mut ctx, &buf[0..n]), 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /examples/strip.rs: -------------------------------------------------------------------------------- 1 | use mpeg2ts_reader::demultiplex; 2 | use mpeg2ts_reader::packet::Packet; 3 | use std::fs::File; 4 | use std::io::Read; 5 | use std::{env, io, marker}; 6 | 7 | pub enum StripFilterSwitch { 8 | Write(PacketWriter), 9 | Strip(PacketStripper), 10 | } 11 | impl demultiplex::PacketFilter for StripFilterSwitch { 12 | type Ctx = StripDemuxContext; 13 | #[inline(always)] 14 | fn consume(&mut self, ctx: &mut StripDemuxContext, pk: &Packet<'_>) { 15 | match self { 16 | &mut StripFilterSwitch::Write(ref mut f) => f.consume(ctx, pk), 17 | &mut StripFilterSwitch::Strip(ref mut f) => f.consume(ctx, pk), 18 | } 19 | } 20 | } 21 | 22 | pub struct StripDemuxContext { 23 | changeset: demultiplex::FilterChangeset>, 24 | out: W, 25 | written: u64, 26 | stripped: u64, 27 | } 28 | impl demultiplex::DemuxContext for StripDemuxContext { 29 | type F = StripFilterSwitch; 30 | 31 | fn filter_changeset(&mut self) -> &mut demultiplex::FilterChangeset { 32 | &mut self.changeset 33 | } 34 | fn construct(&mut self, req: demultiplex::FilterRequest<'_, '_>) -> Self::F { 35 | match req { 36 | // 'Stuffing' data on PID 0x1fff may be used to pad-out parts of the transport stream 37 | // so that it has constant overall bitrate. Since we are stripping these packets, 38 | // we use an implementation of PacketFilter that just ignores them 39 | demultiplex::FilterRequest::ByPid(mpeg2ts_reader::STUFFING_PID) => { 40 | StripFilterSwitch::Strip(PacketStripper::default()) 41 | } 42 | // for any other packet, we will use a PacketFilter implementation that writes the 43 | // packet to the output 44 | _ => StripFilterSwitch::Write(PacketWriter::default()), 45 | } 46 | } 47 | } 48 | impl StripDemuxContext { 49 | pub fn new(out: W) -> Self { 50 | StripDemuxContext { 51 | changeset: demultiplex::FilterChangeset::default(), 52 | out, 53 | written: 0, 54 | stripped: 0, 55 | } 56 | } 57 | } 58 | 59 | pub struct PacketWriter(marker::PhantomData); 60 | impl demultiplex::PacketFilter for PacketWriter { 61 | type Ctx = StripDemuxContext; 62 | 63 | fn consume(&mut self, ctx: &mut Self::Ctx, pk: &Packet<'_>) { 64 | ctx.out 65 | .write_all(pk.buffer()) 66 | .expect("writing to stdout failed"); 67 | ctx.written += 1; 68 | } 69 | } 70 | impl Default for PacketWriter { 71 | fn default() -> Self { 72 | PacketWriter(marker::PhantomData::default()) 73 | } 74 | } 75 | 76 | pub struct PacketStripper(marker::PhantomData); 77 | impl demultiplex::PacketFilter for PacketStripper { 78 | type Ctx = StripDemuxContext; 79 | 80 | fn consume(&mut self, ctx: &mut Self::Ctx, _pk: &Packet<'_>) { 81 | ctx.stripped += 1; 82 | } 83 | } 84 | impl Default for PacketStripper { 85 | fn default() -> Self { 86 | PacketStripper(marker::PhantomData::default()) 87 | } 88 | } 89 | 90 | fn main() { 91 | env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init(); 92 | 93 | // open input file named on command line, 94 | let name = env::args().nth(1).unwrap(); 95 | let mut f = File::open(&name).unwrap_or_else(|_| panic!("file not found: {}", &name)); 96 | 97 | let out = io::stdout(); 98 | let buf = io::BufWriter::new(out); 99 | 100 | // create the context object that stores the state of the transport stream demultiplexing 101 | // process 102 | let mut ctx = StripDemuxContext::new(buf); 103 | 104 | // create the demultiplexer, which will use the ctx to create a filter for pid 0 (PAT) 105 | let mut demux = demultiplex::Demultiplex::new(&mut ctx); 106 | 107 | // consume the input file, 108 | let mut buf = [0u8; 188 * 1024]; 109 | loop { 110 | match f.read(&mut buf[..]).expect("read failed") { 111 | 0 => break, 112 | n => demux.push(&mut ctx, &buf[0..n]), 113 | } 114 | } 115 | eprintln!( 116 | "Written {} TS packets, stripped {} TS packets", 117 | ctx.written, ctx.stripped 118 | ); 119 | } 120 | -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | target 3 | corpus 4 | artifacts 5 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | 2 | [package] 3 | name = "mpeg2ts-reader-fuzz" 4 | version = "0.0.1" 5 | authors = ["Automatically generated"] 6 | publish = false 7 | edition = "2018" 8 | 9 | [package.metadata] 10 | cargo-fuzz = true 11 | 12 | [dependencies] 13 | mpeg2ts-reader = { path = ".." } 14 | libfuzzer-sys = { git = "https://github.com/rust-fuzz/libfuzzer-sys.git" } 15 | 16 | # Prevent this from interfering with workspaces 17 | [workspace] 18 | members = ["."] 19 | 20 | [[bin]] 21 | name = "fuzz_target_1" 22 | path = "fuzz_targets/fuzz_target_1.rs" 23 | -------------------------------------------------------------------------------- /fuzz/README.md: -------------------------------------------------------------------------------- 1 | # Fuzz testing mpeg2ts-reader 2 | 3 | ## Fuzz corpus 4 | 5 | TODO: ❌ There isn't yet a mechanism for distributing the fuzz testing corpus data. (It is not recommended to commit 6 | the data along with the project source code, so options might be to set up a seperate repo, or find some other file / 7 | object store from which to download the corpus.) 8 | 9 | You can still run the fuzz test without a pre-existing corpus, but it will take the fuzzer several minutes to generate 10 | a corpus from scratch. 11 | 12 | ## Running the fuzz test locally 13 | 14 | From the repository root directory, 15 | 16 | ``` 17 | cargo +nightly fuzz coverage fuzz_target_1 18 | ``` 19 | 20 | (The fuzzer will continue testing random inputs until killed.) 21 | 22 | ## Checking the code coverage of the corpus 23 | 24 | Having run the fuzz test as above and generated a test corpus, from the repository **root** directory, generate coverage 25 | metadata for the codebase, 26 | 27 | ``` 28 | cargo +nightly fuzz coverage fuzz_target_1 29 | ``` 30 | 31 | Then, from within **this** directory (the `fuzz` subdirectory of the repository root), 32 | 33 | ``` 34 | cargo +nightly cov \ 35 | -- show \ 36 | target/x86_64-unknown-linux-gnu/release/fuzz_target_1 \ 37 | --ignore-filename-regex=.cargo/ \ 38 | --format=html \ 39 | --instr-profile=coverage/fuzz_target_1/coverage.profdata \ 40 | > target/fuzz-cov.html 41 | ``` 42 | 43 | Read the report in `fuzz-cov.html` to look for risky areas of the codebase that are not being touched by the fuzzer. -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_target_1/0.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dholroyd/mpeg2ts-reader/6930492448ed6f102e12a2f78751486b62e34354/fuzz/corpus/fuzz_target_1/0.ts -------------------------------------------------------------------------------- /fuzz/fuzz_targets/fuzz_target_1.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use libfuzzer_sys::fuzz_target; 4 | use mpeg2ts_reader::{demultiplex, pes, packet, packet_filter_switch, demux_context, descriptor}; 5 | 6 | pub struct FuzzElementaryStreamConsumer; 7 | impl pes::ElementaryStreamConsumer for FuzzElementaryStreamConsumer { 8 | fn start_stream(&mut self, _ctx: &mut FuzzDemuxContext) {} 9 | fn begin_packet(&mut self, _ctx: &mut FuzzDemuxContext, header: pes::PesHeader) { 10 | let _ = header.stream_id(); 11 | let _ = header.pes_packet_length(); 12 | match header.contents() { 13 | pes::PesContents::Parsed(Some(content)) => { 14 | let _ = content.pes_priority(); 15 | let _ = content.data_alignment_indicator(); 16 | let _ = content.copyright(); 17 | let _ = content.original_or_copy(); 18 | let _ = content.pts_dts(); 19 | let _ = content.escr(); 20 | let _ = content.es_rate(); 21 | let _ = content.dsm_trick_mode(); 22 | let _ = content.additional_copy_info(); 23 | let _ = content.previous_pes_packet_crc(); 24 | let _ = content.pes_extension(); 25 | let _ = content.payload(); 26 | }, 27 | pes::PesContents::Parsed(None) => {}, 28 | pes::PesContents::Payload(_data) => {}, 29 | } 30 | } 31 | fn continue_packet(&mut self, _ctx: &mut FuzzDemuxContext, _data: &[u8]) {} 32 | fn end_packet(&mut self, _ctx: &mut FuzzDemuxContext) {} 33 | fn continuity_error(&mut self, _ctx: &mut FuzzDemuxContext) {} 34 | } 35 | 36 | pub struct FuzzPacketFilter; 37 | impl demultiplex::PacketFilter for FuzzPacketFilter { 38 | type Ctx = FuzzDemuxContext; 39 | 40 | fn consume(&mut self, _ctx: &mut Self::Ctx, pk: &packet::Packet<'_>) { 41 | if let Some(af) = pk.adaptation_field() { 42 | format!("{:?}", af); 43 | } 44 | } 45 | } 46 | 47 | packet_filter_switch!{ 48 | FuzzFilterSwitch { 49 | Pat: demultiplex::PatPacketFilter, 50 | Pmt: demultiplex::PmtPacketFilter, 51 | Elem: pes::PesPacketFilter, 52 | Packet: FuzzPacketFilter, 53 | Null: demultiplex::NullPacketFilter, 54 | } 55 | } 56 | demux_context!(FuzzDemuxContext, FuzzFilterSwitch); 57 | impl FuzzDemuxContext { 58 | fn do_construct(&mut self, req: demultiplex::FilterRequest) -> FuzzFilterSwitch { 59 | match req { 60 | demultiplex::FilterRequest::ByPid(packet::Pid::PAT) => FuzzFilterSwitch::Pat(demultiplex::PatPacketFilter::default()), 61 | demultiplex::FilterRequest::ByPid(_) => FuzzFilterSwitch::Null(demultiplex::NullPacketFilter::default()), 62 | demultiplex::FilterRequest::ByStream { stream_type, pmt, stream_info, .. } => { 63 | // we make redundant calls to pmt.descriptors() for each stream, but this is the 64 | // simplest place to hook this call into the fuzz test right now, 65 | for desc in pmt.descriptors::() { 66 | format!("{:?}", desc); 67 | } 68 | for desc in stream_info.descriptors::() { 69 | format!("{:?}", desc); 70 | } 71 | if stream_type.is_pes() { 72 | FuzzFilterSwitch::Elem(pes::PesPacketFilter::new(FuzzElementaryStreamConsumer)) 73 | } else { 74 | FuzzFilterSwitch::Packet(FuzzPacketFilter) 75 | } 76 | }, 77 | demultiplex::FilterRequest::Pmt{pid, program_number} => FuzzFilterSwitch::Pmt(demultiplex::PmtPacketFilter::new(pid, program_number)), 78 | demultiplex::FilterRequest::Nit{pid: _} => FuzzFilterSwitch::Null(demultiplex::NullPacketFilter::default()), 79 | } 80 | } 81 | } 82 | fuzz_target!(|data: &[u8]| { 83 | let mut ctx = FuzzDemuxContext::new(); 84 | let mut demux = demultiplex::Demultiplex::new(&mut ctx); 85 | demux.push(&mut ctx, data); 86 | }); 87 | -------------------------------------------------------------------------------- /release.toml: -------------------------------------------------------------------------------- 1 | pre-release-replacements = [ 2 | {file="CHANGELOG.md", search="## Unreleased", replace="## Unreleased\n\n## {{version}} - {{date}}", exactly=1}, 3 | ] 4 | -------------------------------------------------------------------------------- /scripts/fuzz.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | RUST_BACKTRACE=1 cargo +nightly fuzz run fuzz_target_1 -- #-max_total_time=60 4 | -------------------------------------------------------------------------------- /shootout/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mpeg2ts-reader-shootout" 3 | version = "0.0.1" 4 | publish = false 5 | 6 | [dependencies] 7 | mpeg2ts = "0.1.1" 8 | mpeg2ts-reader = { path = ".." } 9 | ffmpeg-sys-next = "7.1.0" 10 | 11 | [dev-dependencies] 12 | criterion = "0.2" 13 | 14 | [[bench]] 15 | name = "bench" 16 | harness = false 17 | -------------------------------------------------------------------------------- /shootout/benches/bench.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate criterion; 3 | extern crate mpeg2ts_reader_shootout; 4 | 5 | use criterion::{Criterion,Benchmark,Throughput}; 6 | use std::fs::File; 7 | use std::io::Read; 8 | use mpeg2ts_reader_shootout::mpeg2ts_timestamps::Mpeg2ts; 9 | use mpeg2ts_reader_shootout::mpeg2ts_reader_timestamps::Mpeg2tsReader; 10 | use mpeg2ts_reader_shootout::ffmpeg_timestamps::Ffmpeg; 11 | 12 | fn load_sample_data() -> (Vec, usize) { 13 | let mut f = File::open("../testsrc.ts").expect("file not found"); 14 | let l = f.metadata().unwrap().len() as usize; 15 | let size = l.min(188*200_000); 16 | let mut buf = vec![0; size]; 17 | f.read(&mut buf[..]).unwrap(); 18 | (buf, size) 19 | } 20 | 21 | fn mpeg2ts(c: &mut Criterion) { 22 | let (buf, size_bytes) = load_sample_data(); 23 | c.bench("crate:mpeg2ts", Benchmark::new("dts-check", move |b| { 24 | b.iter(|| { 25 | Mpeg2ts::check_timestamps(&buf[..]); 26 | } ); 27 | }).throughput(Throughput::Bytes(size_bytes as u32))); 28 | } 29 | 30 | fn mpeg2ts_reader(c: &mut Criterion) { 31 | let (buf, size_bytes) = load_sample_data(); 32 | c.bench("crate:mpeg2ts-reader", Benchmark::new("dts-check", move |b| { 33 | b.iter(|| { 34 | let mut r = Mpeg2tsReader::new(); 35 | r.check_timestamps(&buf[..]); 36 | } ); 37 | }).throughput(Throughput::Bytes(size_bytes as u32))); 38 | } 39 | 40 | fn ffmpeg(c: &mut Criterion) { 41 | let (buf, size_bytes) = load_sample_data(); 42 | let f = Ffmpeg::new(); 43 | c.bench("crate:ffmpeg-sys", Benchmark::new("dts-check", move |b| { 44 | b.iter(|| { 45 | f.check_timestamps(&buf[..]); 46 | } ); 47 | }).throughput(Throughput::Bytes(size_bytes as u32))); 48 | } 49 | 50 | 51 | criterion_group!(benches, ffmpeg, mpeg2ts_reader, mpeg2ts); 52 | criterion_main!(benches); 53 | -------------------------------------------------------------------------------- /shootout/report.py: -------------------------------------------------------------------------------- 1 | # post-processes the output of 'cargo bench' to summarise throughput of each 2 | # implementation 3 | 4 | import json 5 | import matplotlib.pyplot as plt; plt.rcdefaults() 6 | import numpy as np 7 | import matplotlib.pyplot as plt 8 | 9 | GIGABYTE = 1024*1024*1024 10 | NANOSECONDS_PER_SECOND = 1000000000 11 | 12 | def into_gbytes_per_second(byte_count, nanos): 13 | return byte_count * NANOSECONDS_PER_SECOND / nanos / GIGABYTE 14 | 15 | def get_benchmark_data(bench): 16 | with open("target/criterion/{}/dts-check/new/estimates.json".format(bench), "r") as f: 17 | dat = json.load(f) 18 | point_estimate = dat["Mean"]["point_estimate"] 19 | lower_bound = dat["Mean"]["confidence_interval"]["lower_bound"] 20 | upper_bound = dat["Mean"]["confidence_interval"]["upper_bound"] 21 | with open("target/criterion/{}/dts-check/new/benchmark.json".format(bench), "r") as f: 22 | dat = json.load(f) 23 | throughput_bytes = dat["throughput"]["Bytes"] 24 | 25 | return { 26 | "point_estimate": into_gbytes_per_second(throughput_bytes, point_estimate), 27 | "lower_bound": into_gbytes_per_second(throughput_bytes, lower_bound), 28 | "upper_bound": into_gbytes_per_second(throughput_bytes, upper_bound), 29 | } 30 | 31 | bench_names = ["crate:ffmpeg-sys", "crate:mpeg2ts", "crate:mpeg2ts-reader"] 32 | benches = [get_benchmark_data(name) for name in bench_names] 33 | 34 | plt.figure(figsize=(8, 6), dpi=80) 35 | 36 | y_pos = np.arange(len(bench_names)) 37 | performance = [bench["point_estimate"] for bench in benches] 38 | 39 | fig, ax = plt.subplots() 40 | rects = plt.barh(y_pos, performance, align='center', color='black', alpha=0.5, height=0.5) 41 | (x_left, x_right) = ax.get_xlim() 42 | x_width = x_right - x_left 43 | ax.margins(x=0.1) # make space for labels right-of bars 44 | for rect in rects: 45 | width = rect.get_width() 46 | ax.text(width+(0.01*x_width),rect. get_y() + rect.get_height()/2., 47 | '%.1f' % width, 48 | ha='left', va='center') 49 | 50 | plt.yticks(y_pos, [b.split(":")[1] for b in bench_names]) 51 | plt.xlabel('Gigabytes per second') 52 | plt.ylabel('Rust crate') 53 | plt.title('Parsing throughput') 54 | plt.tight_layout() 55 | plt.subplots_adjust(left=0.25) # more space for our crate-name labels 56 | 57 | plt.savefig("report.svg") 58 | -------------------------------------------------------------------------------- /shootout/report.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | rm -f "target/report.dat" 4 | echo -n "'ffmpeg-sys', " >> "target/report.dat" 5 | jq '.Mean.point_estimate' < "target/criterion/crate:ffmpeg-sys/dts-check/new/estimates.json" >> "target/report.dat" 6 | echo -n "'mpeg2ts', " >> "target/report.dat" 7 | jq '.Mean.point_estimate' < "target/criterion/crate:mpeg2ts/dts-check/new/estimates.json" >> "target/report.dat" 8 | echo -n "'mpeg2ts-reader', " >> "target/report.dat" 9 | jq '.Mean.point_estimate' < "target/criterion/crate:mpeg2ts-reader/dts-check/new/estimates.json" >> "target/report.dat" 10 | 11 | gnuplot -e 'set terminal svg; 12 | set output "target/report.svg"; 13 | set style histogram; 14 | set style data histogram; 15 | plot "target/report.dat" using 2:xtic(1)' 16 | -------------------------------------------------------------------------------- /shootout/report.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | 19 | 20 | 21 | 22 | 28 | 29 | 30 | 36 | 37 | 38 | 44 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 322 | 333 | 366 | 398 | 423 | 439 | 459 | 483 | 514 | 515 | 541 | 557 | 578 | 599 | 618 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 704 | 734 | 740 | 741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | 753 | 754 | 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | 767 | 768 | 769 | 770 | 771 | 772 | 773 | 774 | 775 | 776 | 777 | 778 | 779 | 780 | 781 | 782 | 783 | 784 | 785 | 786 | 787 | 788 | 789 | 790 | 791 | 792 | 793 | 794 | 795 | 796 | 797 | 798 | 799 | 800 | 801 | 802 | 803 | 830 | 851 | 852 | 853 | 854 | 855 | 856 | 857 | 858 | 859 | 860 | 861 | 862 | 863 | 864 | 865 | 866 | 867 | 870 | 871 | 872 | 875 | 876 | 877 | 880 | 881 | 882 | 885 | 886 | 887 | 888 | 889 | 895 | 919 | 920 | 921 | 922 | 923 | 924 | 925 | 926 | 927 | 928 | 929 | 930 | 931 | 932 | 933 | 934 | 935 | 936 | 937 | 938 | 939 | 940 | 941 | 942 | 943 | 944 | 945 | 946 | 965 | 984 | 985 | 986 | 987 | 988 | 989 | 990 | 991 | 992 | 993 | 994 | 995 | 996 | 997 | 998 | 999 | 1000 | 1001 | 1002 | 1003 | 1004 | 1005 | 1006 | 1007 | 1008 | 1009 | 1010 | 1011 | 1012 | 1013 | 1014 | -------------------------------------------------------------------------------- /shootout/src/ffmpeg_timestamps.rs: -------------------------------------------------------------------------------- 1 | use ffmpeg_sys_next::*; 2 | use std::ptr; 3 | use std::mem; 4 | use std::ffi; 5 | 6 | pub struct Ffmpeg{ 7 | fmt: *const AVInputFormat, 8 | } 9 | impl Ffmpeg { 10 | pub fn new() -> Ffmpeg { 11 | unsafe { 12 | let fmt_name = ffi::CString::new("mpegts").unwrap(); 13 | let fmt = av_find_input_format(fmt_name.as_ptr()); 14 | if fmt.is_null() { 15 | panic!("could not find format"); 16 | } 17 | Ffmpeg { fmt } 18 | } 19 | } 20 | 21 | pub fn check_timestamps(&self, buf: &[u8]) { 22 | unsafe { 23 | let ioctx = avio_alloc_context(buf.as_ptr() as *mut u8, 24 | buf.len() as i32, 25 | 0, 26 | ptr::null_mut(), 27 | None, 28 | None, 29 | None); 30 | let mut ctx = avformat_alloc_context(); 31 | (*ctx).pb = ioctx; 32 | let ret = avformat_open_input(&mut ctx, 33 | ptr::null_mut(), 34 | self.fmt as *mut AVInputFormat, 35 | ptr::null_mut()); 36 | if ret < 0 { 37 | panic!("avformat_open_input() failed {}", ret); 38 | } 39 | 40 | let mut pk: AVPacket = mem::zeroed(); 41 | av_init_packet(&mut pk); 42 | let mut last_ts = None; 43 | while av_read_frame(ctx, &mut pk) >= 0 { 44 | if pk.stream_index == 0 { // the video stream, in our copy of big_buck_bunny 45 | let this_ts = if pk.dts != AV_NOPTS_VALUE { 46 | Some(pk.dts) 47 | } else if pk.pts != AV_NOPTS_VALUE { 48 | Some(pk.pts) 49 | } else { 50 | None 51 | }; 52 | if let (Some(this), Some(last)) = (this_ts, last_ts) { 53 | if this <= last { 54 | println!("Non-monotonically increasing timestamp, last={}, this={}", last, this); 55 | } 56 | } 57 | last_ts = this_ts; 58 | } 59 | av_packet_unref(&mut pk); 60 | } 61 | // prevent libav from trying to free our buffer, 62 | (*ioctx).buffer = ptr::null_mut(); 63 | avformat_close_input(&mut ctx); 64 | avformat_free_context(ctx); 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /shootout/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate mpeg2ts; 2 | #[macro_use] 3 | extern crate mpeg2ts_reader; 4 | extern crate ffmpeg_sys_next; 5 | 6 | pub mod mpeg2ts_reader_timestamps; 7 | pub mod mpeg2ts_timestamps; 8 | pub mod ffmpeg_timestamps; -------------------------------------------------------------------------------- /shootout/src/mpeg2ts_reader_timestamps.rs: -------------------------------------------------------------------------------- 1 | use mpeg2ts_reader::{ 2 | demultiplex, 3 | pes, 4 | psi, 5 | packet, 6 | }; 7 | use mpeg2ts_reader::StreamType; 8 | 9 | pub struct Mpeg2tsReader { 10 | demux: demultiplex::Demultiplex, 11 | ctx: NullDemuxContext, 12 | } 13 | impl Mpeg2tsReader { 14 | pub fn new() -> Mpeg2tsReader { 15 | let mut ctx = NullDemuxContext::new(); 16 | let demux = demultiplex::Demultiplex::new(&mut ctx); 17 | Mpeg2tsReader { 18 | ctx, 19 | demux, 20 | } 21 | } 22 | pub fn check_timestamps(&mut self, buf: &[u8]) { 23 | self.demux.push(&mut self.ctx, &buf[..]); 24 | } 25 | } 26 | 27 | packet_filter_switch!{ 28 | NullFilterSwitch { 29 | Pat: demultiplex::PatPacketFilter, 30 | Pmt: demultiplex::PmtPacketFilter, 31 | Null: demultiplex::NullPacketFilter, 32 | NullPes: pes::PesPacketFilter, 33 | } 34 | } 35 | demux_context!(NullDemuxContext, NullFilterSwitch); 36 | impl NullDemuxContext { 37 | fn do_construct(&mut self, req: demultiplex::FilterRequest) -> NullFilterSwitch { 38 | match req { 39 | demultiplex::FilterRequest::ByPid(packet::Pid::PAT) => NullFilterSwitch::Pat(demultiplex::PatPacketFilter::default()), 40 | demultiplex::FilterRequest::ByStream{ stream_type: StreamType::H264, pmt, stream_info, .. } => TimeCheckElementaryStreamConsumer::construct(pmt, stream_info), 41 | demultiplex::FilterRequest::Pmt{pid, program_number} => NullFilterSwitch::Pmt(demultiplex::PmtPacketFilter::new(pid, program_number)), 42 | _ => NullFilterSwitch::Null(demultiplex::NullPacketFilter::default()), 43 | } 44 | } 45 | } 46 | 47 | pub struct TimeCheckElementaryStreamConsumer { 48 | last_ts: Option, 49 | } 50 | impl TimeCheckElementaryStreamConsumer { 51 | fn construct(_pmt_sect: &psi::pmt::PmtSection, _stream_info: &psi::pmt::StreamInfo) -> NullFilterSwitch { 52 | let filter = pes::PesPacketFilter::new(TimeCheckElementaryStreamConsumer { last_ts: None }); 53 | NullFilterSwitch::NullPes(filter) 54 | } 55 | } 56 | impl pes::ElementaryStreamConsumer for TimeCheckElementaryStreamConsumer { 57 | fn start_stream(&mut self, _ctx: &mut Ctx) { } 58 | fn begin_packet(&mut self, _ctx: &mut Ctx, header: pes::PesHeader) { 59 | if let pes::PesContents::Parsed(Some(content)) = header.contents() { 60 | let this_ts = match content.pts_dts() { 61 | Ok(pes::PtsDts::PtsOnly(Ok(ts))) => Some(ts.value()), 62 | Ok(pes::PtsDts::Both{ dts: Ok(ts), .. }) => Some(ts.value()), 63 | _ => None, 64 | }; 65 | if let Some(this) = this_ts { 66 | if let Some(last) = self.last_ts { 67 | if this <= last { 68 | println!("Non-monotonically increasing timestamp, last={}, this={}", last, this); 69 | } 70 | } 71 | self.last_ts = Some(this); 72 | } 73 | } 74 | } 75 | fn continue_packet(&mut self, _ctx: &mut Ctx, _data: &[u8]) { } 76 | fn end_packet(&mut self, _ctx: &mut Ctx) { } 77 | fn continuity_error(&mut self, _ctx: &mut Ctx) { } 78 | } 79 | -------------------------------------------------------------------------------- /shootout/src/mpeg2ts_timestamps.rs: -------------------------------------------------------------------------------- 1 | use mpeg2ts::pes::{PesPacketReader, ReadPesPacket}; 2 | use mpeg2ts::ts::TsPacketReader; 3 | 4 | pub struct Mpeg2ts; 5 | impl Mpeg2ts { 6 | pub fn check_timestamps(buf: &[u8]) { 7 | let reader = TsPacketReader::new(buf); 8 | let mut pes_reader = PesPacketReader::new(reader); 9 | let mut last_ts = None; 10 | loop { 11 | match pes_reader.read_pes_packet() { 12 | Ok(Some(pes)) => { 13 | if pes.header.stream_id.as_u8()!=224 { 14 | continue; 15 | } 16 | let this_ts = if let Some(dts) = pes.header.dts { 17 | Some(dts.as_u64()) 18 | } else if let Some(pts) = pes.header.pts { 19 | Some(pts.as_u64()) 20 | } else { 21 | None 22 | }; 23 | if let Some(this) = this_ts { 24 | if let Some(last) = last_ts { 25 | if this <= last { 26 | println!("Non-monotonically increasing timestamp, last={}, this={}", last, this); 27 | } 28 | } 29 | last_ts = Some(this); 30 | } 31 | }, 32 | Ok(None) => break, 33 | Err(_) => (), 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/descriptor/avcvideo.rs: -------------------------------------------------------------------------------- 1 | //! Provides some metadata from the SPS/PPS within this AVC stream, and also some flags to indicate 2 | //! usage of certain AVC stream features. 3 | 4 | use super::DescriptorError; 5 | use crate::descriptor::descriptor_len; 6 | use std::fmt; 7 | 8 | /// Descriptor holding copies of properties from the AVC metadata such as AVC 'profile' and 'level'. 9 | pub struct AvcVideoDescriptor<'buf> { 10 | buf: &'buf [u8], 11 | } 12 | impl<'buf> AvcVideoDescriptor<'buf> { 13 | /// The descriptor tag value which identifies the descriptor as a `AvcVideoDescriptor`. 14 | pub const TAG: u8 = 40; 15 | /// Construct a `AvcVideoDescriptor` instance that will parse the data from the given 16 | /// slice. 17 | pub fn new(tag: u8, buf: &'buf [u8]) -> Result, DescriptorError> { 18 | assert_eq!(tag, Self::TAG); 19 | descriptor_len(buf, tag, 4)?; 20 | Ok(AvcVideoDescriptor { buf }) 21 | } 22 | 23 | /// The AVC _profile_ used in this stream will be equal to, or lower than, this value 24 | pub fn profile_idc(&self) -> u8 { 25 | self.buf[0] 26 | } 27 | /// Value of the same flag from this AVC stream's _Sequence Parameter Set_ 28 | pub fn constraint_set0_flag(&self) -> bool { 29 | self.buf[1] & 0b1000_0000 != 0 30 | } 31 | /// Value of the same flag from this AVC stream's _Sequence Parameter Set_ 32 | pub fn constraint_set1_flag(&self) -> bool { 33 | self.buf[1] & 0b0100_0000 != 0 34 | } 35 | /// Value of the same flag from this AVC stream's _Sequence Parameter Set_ 36 | pub fn constraint_set2_flag(&self) -> bool { 37 | self.buf[1] & 0b0010_0000 != 0 38 | } 39 | /// Value of the same flag from this AVC stream's _Sequence Parameter Set_ 40 | pub fn constraint_set3_flag(&self) -> bool { 41 | self.buf[1] & 0b0001_0000 != 0 42 | } 43 | /// Value of the same flag from this AVC stream's _Sequence Parameter Set_ 44 | pub fn constraint_set4_flag(&self) -> bool { 45 | self.buf[1] & 0b0000_1000 != 0 46 | } 47 | /// Value of the same flag from this AVC stream's _Sequence Parameter Set_ 48 | pub fn constraint_set5_flag(&self) -> bool { 49 | self.buf[1] & 0b0000_0100 != 0 50 | } 51 | /// Value of the same flags from this AVC stream's _Sequence Parameter Set_ 52 | pub fn avc_compatible_flags(&self) -> u8 { 53 | self.buf[1] & 0b0000_0011 54 | } 55 | /// The AVC _level_ used in this stream will be equal to, or lower than, this value 56 | pub fn level_idc(&self) -> u8 { 57 | self.buf[2] 58 | } 59 | /// Stream may include AVC still pictures 60 | pub fn avc_still_present(&self) -> bool { 61 | self.buf[3] & 0b1000_0000 != 0 62 | } 63 | /// Stream may contain AVC 24-hour pictures 64 | pub fn avc_24_hour_picture_flag(&self) -> bool { 65 | self.buf[3] & 0b0100_0000 != 0 66 | } 67 | /// If false, _frame packing arrangement_ or _stereo video information_ SEI message should be 68 | /// present 69 | pub fn frame_packing_sei_not_present_flag(&self) -> bool { 70 | self.buf[3] & 0b0010_0000 != 0 71 | } 72 | } 73 | 74 | impl fmt::Debug for AvcVideoDescriptor<'_> { 75 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { 76 | f.debug_struct("AvcVideoDescriptor") 77 | .field("profile_idc", &self.profile_idc()) 78 | .field("constraint_set0_flag", &self.constraint_set0_flag()) 79 | .field("constraint_set1_flag", &self.constraint_set1_flag()) 80 | .field("constraint_set2_flag", &self.constraint_set2_flag()) 81 | .field("constraint_set3_flag", &self.constraint_set3_flag()) 82 | .field("constraint_set4_flag", &self.constraint_set4_flag()) 83 | .field("constraint_set5_flag", &self.constraint_set5_flag()) 84 | .field("avc_compatible_flags", &self.avc_compatible_flags()) 85 | .field("level_idc", &self.level_idc()) 86 | .field("avc_still_present", &self.avc_still_present()) 87 | .field("avc_24_hour_picture_flag", &self.avc_24_hour_picture_flag()) 88 | .field( 89 | "frame_packing_sei_not_present_flag", 90 | &self.frame_packing_sei_not_present_flag(), 91 | ) 92 | .finish() 93 | } 94 | } 95 | 96 | #[cfg(test)] 97 | mod test { 98 | use super::super::{CoreDescriptors, Descriptor}; 99 | use assert_matches::assert_matches; 100 | use hex_literal::*; 101 | 102 | #[test] 103 | fn descriptor() { 104 | let data = hex!("280442c01e3f"); 105 | let desc = CoreDescriptors::from_bytes(&data[..]).unwrap(); 106 | assert_matches!(desc, CoreDescriptors::AvcVideo(avc_video) => { 107 | assert_eq!(avc_video.level_idc(), 30); 108 | assert_eq!(avc_video.constraint_set0_flag(), true); 109 | assert_eq!(avc_video.constraint_set1_flag(), true); 110 | assert_eq!(avc_video.constraint_set3_flag(), false); 111 | assert_eq!(avc_video.constraint_set4_flag(), false); 112 | assert_eq!(avc_video.constraint_set5_flag(), false); 113 | assert_eq!(avc_video.avc_compatible_flags(), 0); 114 | assert_eq!(avc_video.profile_idc(), 66); 115 | assert_eq!(avc_video.avc_still_present(), false); 116 | assert_eq!(avc_video.avc_24_hour_picture_flag(), false); 117 | assert_eq!(avc_video.frame_packing_sei_not_present_flag(), true); 118 | }) 119 | } 120 | 121 | #[test] 122 | fn debug() { 123 | let data = hex!("280442c01e3f"); 124 | let desc = CoreDescriptors::from_bytes(&data[..]).unwrap(); 125 | assert_matches!(desc, CoreDescriptors::AvcVideo(avc_video) => { 126 | assert!(!format!("{:?}", avc_video).is_empty()); 127 | }); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/descriptor/iso_639_language.rs: -------------------------------------------------------------------------------- 1 | //! Support for [ISO-639](https://en.wikipedia.org/wiki/ISO_639) language code metadata, and 2 | //! audio-type metadata. 3 | 4 | use super::DescriptorError; 5 | use std::borrow::Cow; 6 | use std::fmt; 7 | 8 | /// Descriptor which may be attached to Transport Stream metadata to indicate the language of the 9 | /// content. 10 | pub struct Iso639LanguageDescriptor<'buf> { 11 | buf: &'buf [u8], 12 | } 13 | impl<'buf> Iso639LanguageDescriptor<'buf> { 14 | /// The descriptor tag value which identifies the descriptor as an `Iso639LanguageDescriptor`. 15 | pub const TAG: u8 = 10; 16 | /// Construct a `Iso639LanguageDescriptor` instance that will parse the data from the given 17 | /// slice. 18 | pub fn new( 19 | _tag: u8, 20 | buf: &'buf [u8], 21 | ) -> Result, DescriptorError> { 22 | Ok(Iso639LanguageDescriptor { buf }) 23 | } 24 | 25 | /// Produce an iterator over the `Language` items in the provided buffer. 26 | pub fn languages(&self) -> impl Iterator, LangError>> { 27 | LanguageIterator::new(self.buf) 28 | } 29 | } 30 | 31 | struct LanguageIterator<'buf> { 32 | remaining_data: &'buf [u8], 33 | } 34 | impl<'buf> LanguageIterator<'buf> { 35 | pub fn new(data: &'buf [u8]) -> LanguageIterator<'buf> { 36 | LanguageIterator { 37 | remaining_data: data, 38 | } 39 | } 40 | } 41 | impl<'buf> Iterator for LanguageIterator<'buf> { 42 | type Item = Result, LangError>; 43 | 44 | fn next(&mut self) -> Option { 45 | if self.remaining_data.is_empty() { 46 | None 47 | } else if self.remaining_data.len() < 4 { 48 | let actual = self.remaining_data.len(); 49 | self.remaining_data = &self.remaining_data[0..0]; 50 | Some(Err(LangError::TooShort { actual })) 51 | } else { 52 | let (head, tail) = self.remaining_data.split_at(4); 53 | self.remaining_data = tail; 54 | Some(Ok(Language::new(head))) 55 | } 56 | } 57 | } 58 | 59 | /// An error reading a language code out of the descriptor 60 | #[derive(Debug)] 61 | pub enum LangError { 62 | /// there is not sufficient data in the descriptor to read out another complete language code 63 | TooShort { 64 | /// the actual buffer size in bytes (expected size is 4 bytes) 65 | actual: usize, 66 | }, 67 | } 68 | 69 | /// Metadata about the role of the audio elementary stream to which this descriptor is attached. 70 | #[derive(Debug, PartialEq, Eq)] 71 | pub enum AudioType { 72 | /// The audio has no particular role define 73 | Undefined, 74 | /// There is no language-specific content within the audio 75 | CleanEffects, 76 | /// The audio is prepared for the heading impaired 77 | HearingImpaired, 78 | /// The audio is prepared for the visually impaired 79 | VisualImpairedCommentary, 80 | /// Values `0x80` to `0xFF` are reserved for use in future versions of _ISO/IEC 13818-1_ 81 | Reserved(u8), 82 | } 83 | impl From for AudioType { 84 | fn from(v: u8) -> Self { 85 | match v { 86 | 0 => AudioType::Undefined, 87 | 1 => AudioType::CleanEffects, 88 | 2 => AudioType::HearingImpaired, 89 | 3 => AudioType::VisualImpairedCommentary, 90 | _ => AudioType::Reserved(v), 91 | } 92 | } 93 | } 94 | /// One of potentially many pieces of language metadata within an `Iso639LanguageDescriptor`. 95 | pub struct Language<'buf> { 96 | buf: &'buf [u8], 97 | } 98 | impl<'buf> Language<'buf> { 99 | fn new(buf: &'buf [u8]) -> Language<'buf> { 100 | assert_eq!(buf.len(), 4); 101 | Language { buf } 102 | } 103 | /// Returns a string containing the ISO-639 language code of the elementary stream to which 104 | /// this descriptor is attached. 105 | pub fn code(&self) -> Cow<'_, str> { 106 | encoding_rs::mem::decode_latin1(&self.buf[0..3]) 107 | } 108 | /// Returns an `AudioType` variant indicating what role this audio track plays within the 109 | /// program. 110 | pub fn audio_type(&self) -> AudioType { 111 | AudioType::from(self.buf[3]) 112 | } 113 | } 114 | impl<'buf> fmt::Debug for Language<'buf> { 115 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { 116 | f.debug_struct("Language") 117 | .field("code", &self.code()) 118 | .field("audio_type", &self.audio_type()) 119 | .finish() 120 | } 121 | } 122 | 123 | struct LangsDebug<'buf>(&'buf Iso639LanguageDescriptor<'buf>); 124 | impl<'buf> fmt::Debug for LangsDebug<'buf> { 125 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { 126 | f.debug_list().entries(self.0.languages()).finish() 127 | } 128 | } 129 | impl<'buf> fmt::Debug for Iso639LanguageDescriptor<'buf> { 130 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { 131 | f.debug_struct("Iso639LanguageDescriptor") 132 | .field("languages", &LangsDebug(self)) 133 | .finish() 134 | } 135 | } 136 | 137 | #[cfg(test)] 138 | mod test { 139 | use super::super::{CoreDescriptors, Descriptor}; 140 | use super::*; 141 | use assert_matches::assert_matches; 142 | use hex_literal::*; 143 | 144 | #[test] 145 | fn descriptor() { 146 | let data = hex!("0a04656e6700"); 147 | let desc = CoreDescriptors::from_bytes(&data).unwrap(); 148 | assert_matches!(desc, CoreDescriptors::ISO639Language(iso_639_language) => { 149 | let mut langs = iso_639_language.languages(); 150 | let first = langs.next().unwrap().unwrap(); 151 | assert_eq!("eng", first.code()); 152 | assert_eq!(AudioType::Undefined, first.audio_type()); 153 | assert_matches!(langs.next(), None); 154 | }); 155 | } 156 | 157 | #[test] 158 | fn debug() { 159 | let data = hex!("0a04656e6700"); 160 | let desc = CoreDescriptors::from_bytes(&data).unwrap(); 161 | assert_matches!(desc, CoreDescriptors::ISO639Language(iso_639_language) => { 162 | assert!(!format!("{:?}", iso_639_language).is_empty()); 163 | }); 164 | } 165 | 166 | #[test] 167 | fn too_short() { 168 | let data = hex!("0a03656e67"); 169 | let desc = CoreDescriptors::from_bytes(&data).unwrap(); 170 | if let CoreDescriptors::ISO639Language(iso_639_language) = desc { 171 | let mut langs = iso_639_language.languages(); 172 | langs.next(); 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/descriptor/max_bitrate.rs: -------------------------------------------------------------------------------- 1 | //! Describes the maximum bitrate of the stream to which this descriptor is attached, including 2 | //! transport overheads. 3 | //! 4 | //! May be attached an an elementary stream, indicating the max bitrate of that elementary stream, 5 | //! or to the program as a whole. In both cases it appears in the PMT. 6 | 7 | use super::descriptor_len; 8 | use super::DescriptorError; 9 | use std::fmt; 10 | 11 | /// Describes the max bitrate of an elementary stream or a whole program. 12 | pub struct MaximumBitrateDescriptor<'buf> { 13 | buf: &'buf [u8], 14 | } 15 | impl<'buf> MaximumBitrateDescriptor<'buf> { 16 | /// The descriptor tag value which identifies the descriptor as an `MaximumBitrateDescriptor`. 17 | pub const TAG: u8 = 14; 18 | /// Construct a `MaximumBitrateDescriptor` instance that will parse the data from the given 19 | /// slice. 20 | pub fn new( 21 | tag: u8, 22 | buf: &'buf [u8], 23 | ) -> Result, DescriptorError> { 24 | assert_eq!(tag, Self::TAG); 25 | descriptor_len(buf, tag, 3)?; 26 | Ok(MaximumBitrateDescriptor { buf }) 27 | } 28 | 29 | /// The maximum bitrate expressed in units of 50 bytes per second 30 | pub fn maximum_bitrate(&self) -> u32 { 31 | u32::from(self.buf[0] & 0b0011_1111) << 16 32 | | u32::from(self.buf[1]) << 8 33 | | u32::from(self.buf[2]) 34 | } 35 | 36 | /// Convenience method which converts the result of `maximum_bitrate()` into a bits-per-second 37 | /// value. 38 | pub fn maximum_bits_per_second(&self) -> u32 { 39 | self.maximum_bitrate() * 50 * 8 40 | } 41 | } 42 | 43 | impl fmt::Debug for MaximumBitrateDescriptor<'_> { 44 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 45 | f.debug_struct("MaximumBitrateDescriptor") 46 | .field("maximum_bitrate", &self.maximum_bitrate()) 47 | .finish() 48 | } 49 | } 50 | 51 | #[cfg(test)] 52 | mod test { 53 | use super::super::{CoreDescriptors, Descriptor}; 54 | use assert_matches::assert_matches; 55 | use hex_literal::*; 56 | 57 | #[test] 58 | fn descriptor() { 59 | let data = hex!("0e03c00184"); 60 | let desc = CoreDescriptors::from_bytes(&data[..]).unwrap(); 61 | assert_matches!(desc, CoreDescriptors::MaximumBitrate(max_bitrate) => { 62 | assert_eq!(max_bitrate.maximum_bitrate(), 388); 63 | assert_eq!(max_bitrate.maximum_bits_per_second(), 155200); 64 | assert!(!format!("{:?}", max_bitrate).is_empty()); 65 | }); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/descriptor/mod.rs: -------------------------------------------------------------------------------- 1 | //! Descriptors provide metadata about an element of a Transport Stream. 2 | //! 3 | //! For example, a descriptor may be used to specify the language of an audio track. Use of 4 | //! specific descriptors is often not mandatory (many streams do not describe the language of their 5 | //! audio). 6 | //! 7 | //! The syntax 8 | //! of specific PSI tables often allows descriptors to be attached to the table itself, or to 9 | //! entries within the table. 10 | //! 11 | //! # Extensions 12 | //! 13 | //! Descriptors are a point of extension, with a range of descriptor types defined by the core 14 | //! standard, and further descriptor types defined by standards based upon transport streams. In 15 | //! order to support this extension, while avoiding allocations (a la `dyn Trait`), 16 | //! descriptor-related types and methods within this crate have a type-parameter so that calling 17 | //! code which wants to use externally-defined descriptors can supply a type which supports them. 18 | //! 19 | //! So for example code using [`PmtSection`](..//demultiplex/struct.PmtSection.html) will need to 20 | //! specify the `Descriptor` implementation to be produced, 21 | //! 22 | //! ``` 23 | //! # use mpeg2ts_reader::psi::pmt::PmtSection; 24 | //! # use mpeg2ts_reader::descriptor::CoreDescriptors; 25 | //! # let data = [0; 4]; 26 | //! let pmt = PmtSection::from_bytes(&data).unwrap(); 27 | //! // type parameter to descriptors() is inferred from the use of CoreDescriptors below 28 | //! for d in pmt.descriptors() { 29 | //! if let Ok(CoreDescriptors::Registration(reg)) = d { 30 | //! println!("registration_descriptor {:?}", reg.format_identifier()); 31 | //! } 32 | //! } 33 | //! ``` 34 | 35 | pub mod avcvideo; 36 | pub mod iso_639_language; 37 | pub mod max_bitrate; 38 | pub mod registration; 39 | 40 | use self::avcvideo::AvcVideoDescriptor; 41 | use self::iso_639_language::Iso639LanguageDescriptor; 42 | use self::max_bitrate::MaximumBitrateDescriptor; 43 | use self::registration::RegistrationDescriptor; 44 | use std::fmt; 45 | use std::marker; 46 | 47 | /// Trait allowing users of this trait to supply their own implementation of descriptor parsing. 48 | /// 49 | /// The default implementation provided by this crate is 50 | /// [`CoreDescriptors`](enum.CoreDescriptors.html), which will only provide support for descriptor 51 | /// types directly defined by _ISO/IEC 13818-1_. To support descriptors from other standards, 52 | /// an alternative implementation of this trait may be passed as a type parameter to methods such as 53 | /// [`PmtSection::descriptors()`](..//demultiplex/struct.PmtSection.html#method.descriptors). 54 | /// 55 | /// The [`descriptor_enum!{}`](../macro.descriptor_enum.html) macro can be used to help create 56 | /// implementations of this trait. 57 | pub trait Descriptor<'buf>: Sized { 58 | /// Create an object that that can wrap and parse the type of descriptor at the start of the 59 | /// given slice. 60 | fn from_bytes(buf: &'buf [u8]) -> Result; 61 | } 62 | 63 | /// Builds an enum to encapsulate all possible implementations of 64 | /// [`Descriptor`](descriptor/trait.Descriptor.html) that you want to be able to handle in your 65 | /// application. 66 | /// 67 | /// The list of variants is supplied in the macro body together with the 'tag' values of each 68 | /// option. The macro will generate an implementation of `descriptor::Descriptor` whose 69 | /// `from_bytes()` function will return the variant for the tag value found within the input 70 | /// buffer. 71 | /// 72 | /// ``` 73 | /// # use mpeg2ts_reader::*; 74 | /// use mpeg2ts_reader::descriptor::*; 75 | /// 76 | /// struct MySpecialDescriptor<'buf> { 77 | /// # buf: &'buf [u8], 78 | /// // ... 79 | /// }; 80 | /// impl<'buf> MySpecialDescriptor<'buf> { 81 | /// pub fn new(tag: u8, payload: &'buf [u8]) -> Result { 82 | /// # Ok(MySpecialDescriptor { buf: payload }) 83 | /// // ... 84 | /// } 85 | /// } 86 | /// 87 | /// descriptor_enum! { 88 | /// // you could #[derive(Debug)] here if you wanted 89 | /// MyOwnDescriptors { 90 | /// /// descriptor tag values `0`, `1` and `57` to `62` inclusive are marked as reserved 91 | /// /// by _ISO/IEC 13818-1_. If any of these tag values are found, 92 | /// /// MyOwnDescriptors::from_bytes() will return MyOwnDescriptors::Reserved. 93 | /// Reserved 0|1|57..=62 => UnknownDescriptor, 94 | /// Other 2..=56|63..=254 => UnknownDescriptor, 95 | /// /// When MyOwnDescriptors::from_bytes() finds the value 255, it will return the 96 | /// /// MyOwnDescriptors::MySpecialPrivate variant, which will in turn wrap an instance 97 | /// /// of MySpecialDescriptor. 98 | /// MySpecialPrivate 255 => MySpecialDescriptor, 99 | /// } 100 | /// } 101 | /// ``` 102 | #[macro_export] 103 | macro_rules! descriptor_enum { 104 | ( 105 | $(#[$outer:meta])* 106 | $name:ident { 107 | $( 108 | $(#[$inner:ident $($args:tt)*])* 109 | $case_name:ident $($tags:pat_param)|* => $t:ident 110 | ),*, 111 | } 112 | ) => { 113 | $(#[$outer])* 114 | pub enum $name<'buf> { 115 | $( 116 | $(#[$inner $($args)*])* 117 | $case_name($t<'buf>), 118 | )* 119 | } 120 | impl<'buf> $crate::descriptor::Descriptor<'buf> for $name<'buf> { 121 | fn from_bytes(buf: &'buf[u8]) -> Result { 122 | if buf.len() < 2 { 123 | return Err($crate::descriptor::DescriptorError::BufferTooShort{ buflen: buf.len() }) 124 | } 125 | let tag = buf[0]; 126 | let len = buf[1] as usize; 127 | let tag_end = len + 2; 128 | if tag_end > buf.len() { 129 | return Err($crate::descriptor::DescriptorError::TagTooLongForBuffer{ taglen: len, buflen: buf.len() }) 130 | } 131 | let payload = &buf[2..tag_end]; 132 | match tag { 133 | $( $( $tags )|* => Ok($name::$case_name($t::new(tag, payload)?)), )* 134 | } 135 | } 136 | } 137 | } 138 | } 139 | 140 | /// Catch-all type for when there is no explicit handling for the given descriptor type. 141 | pub struct UnknownDescriptor<'buf> { 142 | /// the descriptor's identifying 'tag' value; different types of descriptors are assigned 143 | /// different tag values 144 | pub tag: u8, 145 | /// the descriptor's payload bytes 146 | pub payload: &'buf [u8], 147 | } 148 | impl<'buf> UnknownDescriptor<'buf> { 149 | /// Constructor, in the form required for use with the 150 | /// [`descriptor_enum!{}`](../macro.descriptor_enum.html) macro. 151 | pub fn new(tag: u8, payload: &'buf [u8]) -> Result, DescriptorError> { 152 | Ok(UnknownDescriptor { tag, payload }) 153 | } 154 | } 155 | impl<'buf> fmt::Debug for UnknownDescriptor<'buf> { 156 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { 157 | f.debug_struct("UnknownDescriptor") 158 | .field("tag", &self.tag) 159 | .field("len", &self.payload.len()) 160 | .finish() 161 | } 162 | } 163 | 164 | descriptor_enum! { 165 | /// Default implementation of [`Descriptor`](trait.Descriptor.html) covering descriptor types 166 | /// from _ISO/IEC 13818-1_. 167 | /// 168 | /// **NB** coverage of the range of descriptors from the spec with descriptor-type-specific 169 | /// Rust types is currently incomplete, with those variants currently containing 170 | /// `UnknownDescriptor` needing to be changed to have type-specific implementations in some 171 | /// future release of this crate. 172 | #[derive(Debug)] 173 | CoreDescriptors { 174 | /// descriptor tag values `0`, `1` and `57` to `62` inclusive are marked as reserved by _ISO/IEC 13818-1_. 175 | Reserved 0|1|57..=62 => UnknownDescriptor, 176 | /// The `video_stream_descriptor()` syntax element from _ISO/IEC 13818-1_. 177 | VideoStream 2 => UnknownDescriptor, 178 | /// The `audio_stream_descriptor()` syntax element from _ISO/IEC 13818-1_. 179 | AudioStream 3 => UnknownDescriptor, 180 | /// The `hierarchy_descriptor()` syntax element from _ISO/IEC 13818-1_. 181 | Hierarchy 4 => UnknownDescriptor, 182 | /// The `registration_descriptor()` syntax element from _ISO/IEC 13818-1_. 183 | Registration 5 => RegistrationDescriptor, 184 | /// The `data_stream_alignment_descriptor()` syntax element from _ISO/IEC 13818-1_. 185 | DataStreamAlignment 6 => UnknownDescriptor, 186 | /// The `target_background_grid_descriptor()` syntax element from _ISO/IEC 13818-1_. 187 | TargetBackgroundGrid 7 => UnknownDescriptor, 188 | /// The `video_window_descriptor()` syntax element from _ISO/IEC 13818-1_. 189 | VideoWindow 8 => UnknownDescriptor, 190 | /// The `CA_descriptor()` syntax element from _ISO/IEC 13818-1_ ("Conditional Access"). 191 | CA 9 => UnknownDescriptor, 192 | /// The `ISO_639_language_descriptor()` syntax element from _ISO/IEC 13818-1_. 193 | ISO639Language 10 => Iso639LanguageDescriptor, 194 | /// The `system_clock_descriptor()` syntax element from _ISO/IEC 13818-1_. 195 | SystemClock 11 => UnknownDescriptor, 196 | /// The `multiplex_buffer_utilization_descriptor()` syntax element from _ISO/IEC 13818-1_. 197 | MultiplexBufferUtilization 12 => UnknownDescriptor, 198 | /// The `copyright_descriptor()` syntax element from _ISO/IEC 13818-1_. 199 | Copyright 13 => UnknownDescriptor, 200 | /// The `maximum_bitrate_descriptor()` syntax element from _ISO/IEC 13818-1_. 201 | MaximumBitrate 14 => MaximumBitrateDescriptor, 202 | /// The `private_data_indicator_descriptor()` syntax element from _ISO/IEC 13818-1_. 203 | PrivateDataIndicator 15 => UnknownDescriptor, 204 | /// The `smoothing_buffer_descriptor()` syntax element from _ISO/IEC 13818-1_. 205 | SmoothingBuffer 16 => UnknownDescriptor, 206 | /// The `STD_descriptor()` syntax element from _ISO/IEC 13818-1_. 207 | STD 17 => UnknownDescriptor, 208 | /// The `ibp_descriptor()` syntax element from _ISO/IEC 13818-1_. 209 | IBP 18 => UnknownDescriptor, 210 | /// descriptor tag values `19` to `26` inclusive are marked as reserved by _ISO IEC 13818-6_ (NB a different standard than the one supported by this crate). 211 | IsoIec13818dash6 19..=26 => UnknownDescriptor, 212 | /// The `MPEG-4_video_descriptor()` syntax element from _ISO/IEC 13818-1_. 213 | MPEG4Video 27 => UnknownDescriptor, 214 | /// The `MPEG-4_audio_descriptor()` syntax element from _ISO/IEC 13818-1_. 215 | MPEG4Audio 28 => UnknownDescriptor, 216 | /// The `IOD_descriptor()` syntax element from _ISO/IEC 13818-1_ ("Initial Object Descriptor"). 217 | IOD 29 => UnknownDescriptor, 218 | /// The `SL_descriptor()` syntax element from _ISO/IEC 13818-1_ ("Synchronization Layer"). 219 | SL 30 => UnknownDescriptor, 220 | /// The `FMC_descriptor()` syntax element from _ISO/IEC 13818-1_ ("FlexMux Channel"). 221 | FMC 31 => UnknownDescriptor, 222 | /// The `External_ES_ID_descriptor()` syntax element from _ISO/IEC 13818-1_. 223 | ExternalESID 32 => UnknownDescriptor, 224 | /// The `Muxcode_descriptor()` syntax element from _ISO/IEC 13818-1_. 225 | MuxCode 33 => UnknownDescriptor, 226 | /// The `FmxBufferSize_descriptor()` syntax element from _ISO/IEC 13818-1_ ("FlexMux buffer"). 227 | FmxBufferSize 34 => UnknownDescriptor, 228 | /// The `MultiplexBuffer_descriptor()` syntax element from _ISO/IEC 13818-1_. 229 | MultiplexBuffer 35 => UnknownDescriptor, 230 | /// The `content_labeling_descriptor()` syntax element from _ISO/IEC 13818-1_. 231 | MontentLabeling 36 => UnknownDescriptor, 232 | /// The `metadata_pointer_descriptor()` syntax element from _ISO/IEC 13818-1_. 233 | MetadataPointer 37 => UnknownDescriptor, 234 | /// The `metadata_descriptor()` syntax element from _ISO/IEC 13818-1_. 235 | Metadata 38 => UnknownDescriptor, 236 | /// The `metadata_STD_descriptor()` syntax element from _ISO/IEC 13818-1_. 237 | MetadataStd 39 => UnknownDescriptor, 238 | /// The `AVC_video_descriptor()` syntax element from _ISO/IEC 13818-1_. 239 | AvcVideo 40 => AvcVideoDescriptor, 240 | /// The `IPMP_descriptor()` syntax element defined in _ISO/IEC 13818-11_. 241 | IPMP 41 => UnknownDescriptor, 242 | /// The `AVC_timing_and_HRD_descriptor()` syntax element from _ISO/IEC 13818-1_. 243 | AvcTimingAndHrd 42 => UnknownDescriptor, 244 | /// The `MPEG-2_AAC_audio_descriptor()` syntax element from _ISO/IEC 13818-1_. 245 | Mpeg2AacAudio 43 => UnknownDescriptor, 246 | /// The `FlexMuxTiming_descriptor()` syntax element from _ISO/IEC 13818-1_. 247 | FlexMuxTiming 44 => UnknownDescriptor, 248 | /// The `MPEG-4_text_descriptor()` syntax element from _ISO/IEC 13818-1_. 249 | Mpeg4Text 45 => UnknownDescriptor, 250 | /// The `MPEG-4_audio_extension_descriptor()` syntax element from _ISO/IEC 13818-1_. 251 | Mpeg4AudioExtension 46 => UnknownDescriptor, 252 | /// The `Auxiliary_video_stream_descriptor()` syntax element from _ISO/IEC 13818-1_. 253 | AuxiliaryVideoStream 47 => UnknownDescriptor, 254 | /// The `SVC extension descriptor()` syntax element from _ISO/IEC 13818-1_. 255 | SvcExtension 48 => UnknownDescriptor, 256 | /// The `MVC extension descriptor()` syntax element from _ISO/IEC 13818-1_. 257 | MvcExtension 49 => UnknownDescriptor, 258 | /// The `J2K video descriptor()` syntax element from _ISO/IEC 13818-1_. 259 | J2kVideo 50 => UnknownDescriptor, 260 | /// The `MVC operation point descriptor()` syntax element from _ISO/IEC 13818-1_. 261 | MvcOperationPoint 51 => UnknownDescriptor, 262 | /// The `MPEG2_stereoscopic_video_format_descriptor()` syntax element from _ISO/IEC 13818-1_. 263 | Mpeg2StereoscopicVideoFormat 52 => UnknownDescriptor, 264 | /// The `Stereoscopic_program_info_descriptor()` syntax element from _ISO/IEC 13818-1_. 265 | StereoscopicProgramInfo 53 => UnknownDescriptor, 266 | /// The `Stereoscopic_video_info_descriptor()` syntax element from _ISO/IEC 13818-1_. 267 | StereoscopicVideoInfo 54 => UnknownDescriptor, 268 | /// The `Transport_profile_descriptor()` syntax element from _ISO/IEC 13818-1_. 269 | TransportProfile 55 => UnknownDescriptor, 270 | /// The `HEVC video descriptor()` syntax element from _ISO/IEC 13818-1_. 271 | HevcVideo 56 => UnknownDescriptor, 272 | /// The `Extension_descriptor()` syntax element from _ISO/IEC 13818-1_. 273 | Extension 63 => UnknownDescriptor, 274 | /// descriptor tag values `64` to `255` inclusive are marked for 'use private' use by _ISO/IEC 13818-1_. 275 | UserPrivate 64..=255 => UnknownDescriptor, 276 | } 277 | } 278 | 279 | /// Iterator over the descriptor elements in a given byte slice. 280 | pub struct DescriptorIter<'buf, Desc> 281 | where 282 | Desc: Descriptor<'buf>, 283 | { 284 | buf: &'buf [u8], 285 | phantom: marker::PhantomData, 286 | } 287 | impl<'buf, Desc> DescriptorIter<'buf, Desc> 288 | where 289 | Desc: Descriptor<'buf>, 290 | { 291 | /// Create an iterator over all the descriptors in the given slice 292 | pub fn new(buf: &'buf [u8]) -> DescriptorIter<'buf, Desc> { 293 | DescriptorIter { 294 | buf, 295 | phantom: marker::PhantomData, 296 | } 297 | } 298 | } 299 | impl<'buf, Desc> Iterator for DescriptorIter<'buf, Desc> 300 | where 301 | Desc: Descriptor<'buf>, 302 | { 303 | type Item = Result; 304 | 305 | fn next(&mut self) -> Option { 306 | if self.buf.is_empty() { 307 | return None; 308 | } 309 | if self.buf.len() < 2 { 310 | let buflen = self.buf.len(); 311 | // ensure anther call to next() will yield None, 312 | self.buf = &self.buf[0..0]; 313 | Some(Err(DescriptorError::BufferTooShort { buflen })) 314 | } else { 315 | let tag = self.buf[0]; 316 | let len = self.buf[1] as usize; 317 | let remaining_size = self.buf.len() - 2; 318 | if len > remaining_size { 319 | // ensure anther call to next() will yield None, 320 | self.buf = &self.buf[0..0]; 321 | Some(Err(DescriptorError::NotEnoughData { 322 | tag, 323 | actual: remaining_size, 324 | expected: len, 325 | })) 326 | } else { 327 | let (desc, rest) = self.buf.split_at(len + 2); 328 | self.buf = rest; 329 | Some(Descriptor::from_bytes(desc)) 330 | } 331 | } 332 | } 333 | } 334 | 335 | /// An error during parsing of a descriptor 336 | #[derive(Debug, PartialEq, Eq)] 337 | pub enum DescriptorError { 338 | /// The amount of data available in the buffer is not enough to hold the descriptor's declared 339 | /// size. 340 | NotEnoughData { 341 | /// descriptor tag value 342 | tag: u8, 343 | /// actual buffer size 344 | actual: usize, 345 | /// expected buffer size 346 | expected: usize, 347 | }, 348 | /// TODO: replace with NotEnoughData 349 | TagTooLongForBuffer { 350 | /// actual length in descriptor header 351 | taglen: usize, 352 | /// remaining bytes in buffer (which is seen to be shorter than `taglen`) 353 | buflen: usize, 354 | }, 355 | /// The buffer is too short to even hold the two bytes of generic descriptor header data 356 | BufferTooShort { 357 | /// the actual buffer length 358 | buflen: usize, 359 | }, 360 | /// There is no mapping defined of the given descriptor tag value to a `Descriptor` value. 361 | UnhandledTagValue(u8), 362 | } 363 | 364 | pub(crate) fn descriptor_len(buf: &[u8], tag: u8, len: usize) -> Result<(), DescriptorError> { 365 | if buf.len() < len { 366 | Err(DescriptorError::NotEnoughData { 367 | tag, 368 | actual: buf.len(), 369 | expected: len, 370 | }) 371 | } else { 372 | Ok(()) 373 | } 374 | } 375 | 376 | #[cfg(test)] 377 | mod test { 378 | use crate::descriptor::{descriptor_len, CoreDescriptors, DescriptorError, DescriptorIter}; 379 | use assert_matches::assert_matches; 380 | use hex_literal::*; 381 | 382 | #[test] 383 | fn core() { 384 | let data = hex!("000100"); 385 | let mut iter = DescriptorIter::>::new(&data); 386 | let desc = iter.next().unwrap(); 387 | assert!(!format!("{:?}", desc).is_empty()); 388 | assert_matches!(desc, Ok(CoreDescriptors::Reserved(d)) => { 389 | assert_eq!(d.tag, 0); 390 | assert_eq!(d.payload, &[0]); 391 | }); 392 | } 393 | 394 | #[test] 395 | fn not_enough_data() { 396 | assert_matches!( 397 | descriptor_len(b"", 0, 1), 398 | Err(DescriptorError::NotEnoughData { 399 | tag: 0, 400 | actual: 0, 401 | expected: 1, 402 | }) 403 | ); 404 | } 405 | } 406 | -------------------------------------------------------------------------------- /src/descriptor/registration.rs: -------------------------------------------------------------------------------- 1 | //! Registration descriptor indicates which kind of syntax any 'private data' within the transport 2 | //! stream will be following 3 | 4 | use super::DescriptorError; 5 | use crate::descriptor::descriptor_len; 6 | use crate::smptera::FormatIdentifier; 7 | use std::fmt; 8 | 9 | /// Indicates which kind of syntax any 'private data' within the transport stream will be following 10 | pub struct RegistrationDescriptor<'buf> { 11 | /// the registration data bytes 12 | buf: &'buf [u8], 13 | } 14 | impl<'buf> RegistrationDescriptor<'buf> { 15 | /// The descriptor tag value which identifies the descriptor as a `RegistrationDescriptor`. 16 | pub const TAG: u8 = 5; 17 | /// Construct a `RegistrationDescriptor` instance that will parse the data from the given 18 | /// slice. 19 | pub fn new(tag: u8, buf: &'buf [u8]) -> Result, DescriptorError> { 20 | descriptor_len(buf, tag, 4)?; 21 | Ok(RegistrationDescriptor { buf }) 22 | } 23 | 24 | /// Format identifier value assigned by a _Registration Authority_. 25 | /// 26 | /// Note that the `FormatIdentifier` type defines numerous constants for identifiers registered 27 | /// with the SMPTE RA, which you can use in tests like so: 28 | /// 29 | /// ```rust 30 | /// # use mpeg2ts_reader::descriptor::registration::RegistrationDescriptor; 31 | /// use mpeg2ts_reader::smptera::FormatIdentifier; 32 | /// # let descriptor = RegistrationDescriptor::new(RegistrationDescriptor::TAG, b"CUEI") 33 | /// # .unwrap(); 34 | /// if descriptor.format_identifier() == FormatIdentifier::CUEI { 35 | /// // perform some interesting action 36 | /// } 37 | /// ``` 38 | pub fn format_identifier(&self) -> FormatIdentifier { 39 | FormatIdentifier::from(&self.buf[0..4]) 40 | } 41 | 42 | /// Returns true if the given identifier is equal to the value returned by `format_identifier()` 43 | pub fn is_format(&self, id: FormatIdentifier) -> bool { 44 | self.format_identifier() == id 45 | } 46 | 47 | /// borrows a slice of additional_identification_info bytes, whose meaning is defined by 48 | /// the identifier returned by `format_identifier()`. 49 | pub fn additional_identification_info(&self) -> &[u8] { 50 | &self.buf[4..] 51 | } 52 | } 53 | impl<'buf> fmt::Debug for RegistrationDescriptor<'buf> { 54 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { 55 | f.debug_struct("RegistrationDescriptor") 56 | .field("format_identifier", &self.format_identifier()) 57 | .field( 58 | "additional_identification_info", 59 | &format!("{:x?}", self.additional_identification_info()), 60 | ) 61 | .finish() 62 | } 63 | } 64 | 65 | #[cfg(test)] 66 | mod test { 67 | use super::super::{CoreDescriptors, Descriptor}; 68 | use assert_matches::assert_matches; 69 | use hex_literal::*; 70 | 71 | #[test] 72 | fn descriptor() { 73 | let data = hex!("050443554549"); 74 | let desc = CoreDescriptors::from_bytes(&data[..]).unwrap(); 75 | assert_matches!(desc, CoreDescriptors::Registration(reg) => { 76 | let expected = crate::smptera::FormatIdentifier::from(&b"CUEI"[..]); 77 | assert_eq!(reg.format_identifier(), expected); 78 | assert!(reg.is_format(expected)); 79 | assert!(!format!("{:?}", reg).is_empty()) 80 | }); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Structures for parsing MPEG2 Transport Stream data, per the _ISO/IEC 13818-1_ standard. 2 | //! 3 | //! # Design principals 4 | //! 5 | //! * *Avoid copying and allocating* if possible. Most of the implementation works by borrowing 6 | //! slices of the underlying byte buffer. The implementation tries to avoid buffering up 7 | //! intermediate data where practical. 8 | //! * *Non-blocking*. It should be possible to integrate this library into a system non-blocking 9 | //! event-loop. The caller has to 'push' data. 10 | //! * *Extensible*. The standard calls out a number of 'reserved values' and other points of 11 | //! extension. This library should make it possible for other crates to implement such 12 | //! extensions. 13 | //! * *Minimal*. Lots of commonly used Transport Stream functionality is specified in standards 14 | //! from by ISDB / DVB / ATSC / SCTE / etc. and not in 13818-1 itself. I hope support for these 15 | //! features can be added via external crates. (None implemented any yet!) 16 | //! * *Transport Neutral*. There is currently no code here supporting consuming from files or the 17 | //! network. The APIs accept `&[u8]`, and the caller handles providing the data from wherever. 18 | //! 19 | //! 20 | //! # Planned API changes 21 | //! 22 | //! - Add 'context' objects 23 | //! - Real usage will likely need to thread context objects through the API in order to 24 | //! track application-specific details. 25 | //! - Currently mutable state is stored in the instance for each type of syntax parser, and 26 | //! it would be nice to explore extracting this out into parser-specific context types 27 | //! - Event generation / remove `warn!()` 28 | //! - currently problems are reported to the `log` crate 29 | //! - I would much prefer a way to emit 'events' for interesting data that can't just be an error 30 | //! return value, and to not have any logging code mixed with parsing logic 31 | //! - General 32 | //! - lots of places return `Option` but should return `Result` and a descriptive error 33 | 34 | #![forbid(unsafe_code)] 35 | #![deny(rust_2018_idioms, future_incompatible, missing_docs)] 36 | 37 | pub mod packet; 38 | #[macro_use] 39 | pub mod demultiplex; 40 | pub mod descriptor; 41 | pub mod mpegts_crc; 42 | pub mod pes; 43 | pub mod psi; 44 | 45 | use std::fmt; 46 | use std::fmt::Formatter; 47 | // expose access to FormatIdentifier, which is part of the public API of 48 | // descriptor::registration::RegistrationDescriptor 49 | pub use smptera_format_identifiers_rust as smptera; 50 | 51 | /// The types of Elementary Stream specified in _ISO/IEC 13818-1_. 52 | /// 53 | /// As returned by 54 | /// [`StreamInfo::stream_type()`](psi/pmt/struct.StreamInfo.html#method.stream_type). 55 | #[derive(PartialEq, Eq, Hash, Clone, Copy)] 56 | pub struct StreamType(pub u8); 57 | 58 | impl StreamType { 59 | // 0x00 reserved 60 | /// ISO/IEC 11172 Video 61 | pub const ISO_11172_VIDEO: StreamType = StreamType(0x01); 62 | /// ITU-T Rec. H.262 | ISO/IEC 13818-2 Video or ISO/IEC 11172-2 constrained parameter video stream 63 | pub const H262: StreamType = StreamType(0x02); 64 | /// ISO/IEC 11172 Audio 65 | pub const ISO_11172_AUDIO: StreamType = StreamType(0x03); 66 | /// ISO/IEC 13818-3 Audio 67 | pub const ISO_138183_AUDIO: StreamType = StreamType(0x04); 68 | /// ITU-T Rec. H.222.0 | ISO/IEC 13818-1 private_sections 69 | pub const H222_0_PRIVATE_SECTIONS: StreamType = StreamType(0x05); 70 | /// ITU-T Rec. H.222.0 | ISO/IEC 13818-1 PES packets containing private data 71 | pub const H222_0_PES_PRIVATE_DATA: StreamType = StreamType(0x06); 72 | /// ISO/IEC 13522 MHEG 73 | pub const MHEG: StreamType = StreamType(0x07); 74 | /// ITU-T Rec. H.222.0 | ISO/IEC 13818-1 Annex A DSM-CC 75 | pub const H222_0_DSM_CC: StreamType = StreamType(0x08); 76 | /// ITU-T Rec. H.222.1 77 | pub const H2221: StreamType = StreamType(0x09); 78 | /// ISO/IEC 13818-6 DSM CC multiprotocol encapsulation 79 | pub const ISO_13818_6_MULTIPROTOCOL_ENCAPSULATION: StreamType = StreamType(0x0A); 80 | /// ISO/IEC 13818-6 DSM CC U-N messages 81 | pub const DSMCC_UN_MESSAGES: StreamType = StreamType(0x0B); 82 | /// ISO/IEC 13818-6 DSM CC stream descriptors 83 | pub const DSMCC_STREAM_DESCRIPTORS: StreamType = StreamType(0x0C); 84 | /// ISO/IEC 13818-6 DSM CC tabled data 85 | pub const DSMCC_SECTIONS: StreamType = StreamType(0x0D); 86 | /// ITU-T Rec. H.222.0 | ISO/IEC 13818-1 auxiliary 87 | pub const H222_0_AUXILIARY: StreamType = StreamType(0x0E); 88 | /// ISO/IEC 13818-7 Audio with ADTS transport syntax 89 | pub const ADTS: StreamType = StreamType(0x0F); 90 | /// ISO/IEC 14496-2 Visual 91 | pub const ISO_14496_2_VISUAL: StreamType = StreamType(0x10); 92 | /// ISO/IEC 14496-3 Audio with the LATM transport syntax as defined in ISO/IEC 14496-3 / AMD 1 93 | pub const LATM: StreamType = StreamType(0x11); 94 | /// ISO/IEC 14496-1 SL-packetized stream or FlexMux stream carried in PES packets 95 | pub const FLEX_MUX_PES: StreamType = StreamType(0x12); 96 | /// ISO/IEC 14496-1 SL-packetized stream or FlexMux stream carried in ISO/IEC14496_sections. 97 | pub const FLEX_MUX_ISO_14496_SECTIONS: StreamType = StreamType(0x13); 98 | /// ISO/IEC 13818-6 Synchronized Download Protocol 99 | pub const SYNCHRONIZED_DOWNLOAD_PROTOCOL: StreamType = StreamType(0x14); 100 | /// Metadata carried in PES packets 101 | pub const METADATA_IN_PES: StreamType = StreamType(0x15); 102 | /// Metadata carried in metadata_sections 103 | pub const METADATA_IN_METADATA_SECTIONS: StreamType = StreamType(0x16); 104 | /// Metadata carried in ISO/IEC 13818-6 Data Carousel 105 | pub const DSMCC_DATA_CAROUSEL_METADATA: StreamType = StreamType(0x17); 106 | /// Metadata carried in ISO/IEC 13818-6 Object Carousel 107 | pub const DSMCC_OBJECT_CAROUSEL_METADATA: StreamType = StreamType(0x18); 108 | /// Metadata carried in ISO/IEC 13818-6 Synchronized Download Protocol 109 | pub const SYNCHRONIZED_DOWNLOAD_PROTOCOL_METADATA: StreamType = StreamType(0x19); 110 | /// IPMP stream (defined in ISO/IEC 13818-11, MPEG-2 IPMP) 111 | pub const IPMP: StreamType = StreamType(0x1a); 112 | /// AVC video stream as defined in ITU-T Rec. H.264 | ISO/IEC 14496-10 Video 113 | pub const H264: StreamType = StreamType(0x1b); 114 | /// ISO/IEC 14496-3 Audio, without using any additional transport syntax, such as DST, ALS and SLS 115 | pub const AUDIO_WITHOUT_TRANSPORT_SYNTAX: StreamType = StreamType(0x1c); 116 | /// ISO/IEC 14496-17 Text 117 | pub const ISO_14496_17_TEXT: StreamType = StreamType(0x1d); 118 | // 0x1e-0x23 reserved 119 | /// ITU-T Rec. H.265 and ISO/IEC 23008-2 120 | pub const H265: StreamType = StreamType(0x24); 121 | // 0x26-0x41 reserved 122 | /// Chinese Video Standard 123 | pub const CHINESE_VIDEO_STANDARD: StreamType = StreamType(0x42); 124 | // 0x43-0x7f reserved 125 | // 0x80 privately defined 126 | /// Dolby Digital (AC-3) audio for ATSC 127 | pub const ATSC_DOLBY_DIGITAL_AUDIO: StreamType = StreamType(0x81); 128 | // 0x82-0x94 privately defined 129 | /// ATSC Data Service Table, Network Resources Table 130 | pub const ATSC_DSMCC_NETWORK_RESOURCES_TABLE: StreamType = StreamType(0x95); 131 | // 0x95-0xc1 privately defined 132 | /// PES packets containing ATSC streaming synchronous data 133 | pub const ATSC_DSMCC_SYNCHRONOUS_DATA: StreamType = StreamType(0xc2); 134 | // 0xc3-0xff privately defined, 135 | 136 | /// `true` if packets of a stream with this `stream_type` will carry data in Packetized 137 | /// Elementary Stream format. 138 | pub fn is_pes(self) -> bool { 139 | matches!( 140 | self, 141 | StreamType::ISO_11172_VIDEO 142 | | StreamType::H262 143 | | StreamType::ISO_11172_AUDIO 144 | | StreamType::ISO_138183_AUDIO 145 | | StreamType::H222_0_PES_PRIVATE_DATA 146 | | StreamType::MHEG 147 | | StreamType::H222_0_DSM_CC 148 | | StreamType::H2221 149 | | StreamType::ISO_13818_6_MULTIPROTOCOL_ENCAPSULATION 150 | | StreamType::DSMCC_UN_MESSAGES 151 | | StreamType::DSMCC_STREAM_DESCRIPTORS 152 | | StreamType::DSMCC_SECTIONS 153 | | StreamType::H222_0_AUXILIARY 154 | | StreamType::ADTS 155 | | StreamType::ISO_14496_2_VISUAL 156 | | StreamType::LATM 157 | | StreamType::FLEX_MUX_PES 158 | | StreamType::FLEX_MUX_ISO_14496_SECTIONS 159 | | StreamType::SYNCHRONIZED_DOWNLOAD_PROTOCOL 160 | | StreamType::METADATA_IN_PES 161 | | StreamType::METADATA_IN_METADATA_SECTIONS 162 | | StreamType::DSMCC_DATA_CAROUSEL_METADATA 163 | | StreamType::DSMCC_OBJECT_CAROUSEL_METADATA 164 | | StreamType::SYNCHRONIZED_DOWNLOAD_PROTOCOL_METADATA 165 | | StreamType::IPMP 166 | | StreamType::H264 167 | | StreamType::AUDIO_WITHOUT_TRANSPORT_SYNTAX 168 | | StreamType::ISO_14496_17_TEXT 169 | | StreamType::H265 170 | | StreamType::CHINESE_VIDEO_STANDARD 171 | | StreamType::ATSC_DOLBY_DIGITAL_AUDIO 172 | | StreamType::ATSC_DSMCC_NETWORK_RESOURCES_TABLE 173 | | StreamType::ATSC_DSMCC_SYNCHRONOUS_DATA 174 | ) 175 | } 176 | } 177 | impl fmt::Debug for StreamType { 178 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 179 | match *self { 180 | StreamType::ISO_11172_VIDEO => f.write_str("ISO_11172_VIDEO"), 181 | StreamType::H262 => f.write_str("H262"), 182 | StreamType::ISO_11172_AUDIO => f.write_str("ISO_11172_AUDIO"), 183 | StreamType::ISO_138183_AUDIO => f.write_str("ISO_138183_AUDIO"), 184 | StreamType::H222_0_PRIVATE_SECTIONS => f.write_str("H222_0_PRIVATE_SECTIONS"), 185 | StreamType::H222_0_PES_PRIVATE_DATA => f.write_str("H222_0_PES_PRIVATE_DATA"), 186 | StreamType::MHEG => f.write_str("MHEG"), 187 | StreamType::H222_0_DSM_CC => f.write_str("H222_0_DSM_CC"), 188 | StreamType::H2221 => f.write_str("H2221"), 189 | StreamType::ISO_13818_6_MULTIPROTOCOL_ENCAPSULATION => { 190 | f.write_str("ISO_13818_6_MULTIPROTOCOL_ENCAPSULATION") 191 | } 192 | StreamType::DSMCC_UN_MESSAGES => f.write_str("DSMCC_UN_MESSAGES"), 193 | StreamType::DSMCC_STREAM_DESCRIPTORS => f.write_str("DSMCC_STREAM_DESCRIPTORS"), 194 | StreamType::DSMCC_SECTIONS => f.write_str("DSMCC_SECTIONS"), 195 | StreamType::H222_0_AUXILIARY => f.write_str("H222_0_AUXILIARY"), 196 | StreamType::ADTS => f.write_str("ADTS"), 197 | StreamType::ISO_14496_2_VISUAL => f.write_str("ISO_14496_2_VISUAL"), 198 | StreamType::LATM => f.write_str("LATM"), 199 | StreamType::FLEX_MUX_PES => f.write_str("FLEX_MUX_PES"), 200 | StreamType::FLEX_MUX_ISO_14496_SECTIONS => f.write_str("FLEX_MUX_ISO_14496_SECTIONS"), 201 | StreamType::SYNCHRONIZED_DOWNLOAD_PROTOCOL => { 202 | f.write_str("SYNCHRONIZED_DOWNLOAD_PROTOCOL") 203 | } 204 | StreamType::METADATA_IN_PES => f.write_str("METADATA_IN_PES"), 205 | StreamType::METADATA_IN_METADATA_SECTIONS => { 206 | f.write_str("METADATA_IN_METADATA_SECTIONS") 207 | } 208 | StreamType::DSMCC_DATA_CAROUSEL_METADATA => f.write_str("DSMCC_DATA_CAROUSEL_METADATA"), 209 | StreamType::DSMCC_OBJECT_CAROUSEL_METADATA => { 210 | f.write_str("DSMCC_OBJECT_CAROUSEL_METADATA") 211 | } 212 | StreamType::SYNCHRONIZED_DOWNLOAD_PROTOCOL_METADATA => { 213 | f.write_str("SYNCHRONIZED_DOWNLOAD_PROTOCOL_METADATA") 214 | } 215 | StreamType::IPMP => f.write_str("IPMP"), 216 | StreamType::H264 => f.write_str("H264"), 217 | StreamType::AUDIO_WITHOUT_TRANSPORT_SYNTAX => { 218 | f.write_str("AUDIO_WITHOUT_TRANSPORT_SYNTAX") 219 | } 220 | StreamType::ISO_14496_17_TEXT => f.write_str("ISO_14496_17_TEXT"), 221 | StreamType::H265 => f.write_str("H265"), 222 | StreamType::CHINESE_VIDEO_STANDARD => f.write_str("CHINESE_VIDEO_STANDARD"), 223 | StreamType::ATSC_DOLBY_DIGITAL_AUDIO => f.write_str("ATSC_DOLBY_DIGITAL_AUDIO"), 224 | StreamType::ATSC_DSMCC_NETWORK_RESOURCES_TABLE => { 225 | f.write_str("ATSC_DSMCC_NETWORK_RESOURCES_TABLE") 226 | } 227 | StreamType::ATSC_DSMCC_SYNCHRONOUS_DATA => f.write_str("ATSC_DSMCC_SYNCHRONOUS_DATA"), 228 | _ => { 229 | if self.0 >= 0x80 { 230 | f.write_fmt(format_args!("PRIVATE({})", self.0)) 231 | } else { 232 | f.write_fmt(format_args!("RESERVED({})", self.0)) 233 | } 234 | } 235 | } 236 | } 237 | } 238 | 239 | impl From for u8 { 240 | fn from(val: StreamType) -> Self { 241 | val.0 242 | } 243 | } 244 | impl From for StreamType { 245 | fn from(val: u8) -> Self { 246 | StreamType(val) 247 | } 248 | } 249 | 250 | /// The identifier of TS Packets containing 'stuffing' data, with value `0x1fff` 251 | pub const STUFFING_PID: packet::Pid = packet::Pid::new(0x1fff); 252 | 253 | #[cfg(test)] 254 | mod test { 255 | use super::StreamType; 256 | 257 | fn is_reserved(st: &StreamType) -> bool { 258 | match st.0 { 259 | 0x00 | 0x1e..=0x23 | 0x25..=0x41 | 0x43..=0x7f => true, 260 | _ => false, 261 | } 262 | } 263 | 264 | fn is_private(st: &StreamType) -> bool { 265 | match st.0 { 266 | // TODO: 0x95 now public, or is ATSC_DSMCC_NETWORK_RESOURCES_TABLE incorrect? 267 | 0x80 | 0x82..=0x94 | 0x96..=0xc1 | 0xc3..=0xff => true, 268 | _ => false, 269 | } 270 | } 271 | 272 | #[test] 273 | fn pes() { 274 | for st in 0..=255 { 275 | let ty = StreamType(st); 276 | if ty == StreamType::H222_0_PRIVATE_SECTIONS || is_reserved(&ty) || is_private(&ty) { 277 | assert!(!ty.is_pes(), "{:?}", ty); 278 | } else { 279 | assert!(ty.is_pes(), "{:?}", ty); 280 | } 281 | assert_eq!(ty, StreamType::from(u8::from(ty))); 282 | let _ = format!("{:?}", ty); 283 | } 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /src/mpegts_crc.rs: -------------------------------------------------------------------------------- 1 | //! MPEG _Cyclic Redundancy Check_ calculation 2 | #[allow(clippy::unreadable_literal)] 3 | const CRC_TABLE: [u32; 256] = [ 4 | 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005, 5 | 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 6 | 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, 7 | 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd, 8 | 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 9 | 0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, 10 | 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, 11 | 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 12 | 0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, 13 | 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, 14 | 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 15 | 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, 16 | 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692, 17 | 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, 18 | 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, 19 | 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a, 20 | 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 21 | 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, 22 | 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b, 23 | 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 24 | 0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, 25 | 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, 26 | 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 27 | 0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, 28 | 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, 29 | 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 30 | 0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, 31 | 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, 32 | 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, 33 | 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, 34 | 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c, 35 | 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4, 36 | ]; 37 | 38 | /// Calculate the 32-bit _Cyclic Redundancy Check_ of the data, according to the CRC scheme from 39 | /// _ISO/IEC 13818-1, Annex A_. 40 | pub fn sum32(data: &[u8]) -> u32 { 41 | let mut crc: u32 = 0xffff_ffff; 42 | 43 | for &d in data { 44 | let index = ((crc >> 24) ^ u32::from(d)) & 0xff; 45 | crc = (crc << 8) ^ CRC_TABLE[index as usize]; 46 | } 47 | 48 | crc 49 | } 50 | -------------------------------------------------------------------------------- /src/packet.rs: -------------------------------------------------------------------------------- 1 | //! A [`Packet`](./struct.Packet.html) struct and associated infrastructure to read an MPEG Transport Stream packet 2 | 3 | use crate::pes; 4 | use log::warn; 5 | use std::cmp::Ordering; 6 | use std::convert::TryFrom; 7 | use std::fmt; 8 | use std::fmt::{Debug, Formatter}; 9 | use std::num::NonZeroU8; 10 | 11 | /// Representation of the `adaptation_field_control` field from the Transport Stream packet header. 12 | /// The methods on this type indicate whether a `Packet`'s `adaptation_field()` and `payload()` 13 | /// methods will return `Some` or `None`. 14 | #[derive(Eq, PartialEq, Debug)] 15 | pub struct AdaptationControl(u8); 16 | 17 | impl AdaptationControl { 18 | /// Construct a new `AdaptationControl` given the fourth byte from a Transport Stream packet's 19 | /// header. The `adaptation_field_control` field that this type represents is derived from 20 | /// just two bits within the given byte - all the other bits are ignored 21 | #[inline(always)] 22 | fn new(header_byte: u8) -> AdaptationControl { 23 | AdaptationControl(header_byte) 24 | } 25 | 26 | /// True if the `adaptation_field_control` indicates that the packet will have a payload 27 | #[inline(always)] 28 | pub fn has_payload(&self) -> bool { 29 | self.0 & 0b00010000 != 0 30 | } 31 | 32 | /// True if the `adaptation_field_control` field indicates that the packet will have an 33 | /// adaptation field. 34 | #[inline(always)] 35 | pub fn has_adaptation_field(&self) -> bool { 36 | self.0 & 0b00100000 != 0 37 | } 38 | } 39 | 40 | /// Indicates content scrambling in use, if any. 41 | /// 42 | /// Actual content scrambling schemes, are undefined in the main TS spec (left to be described 43 | /// by other specifications). 44 | #[derive(Eq, PartialEq)] 45 | pub struct TransportScramblingControl(u8); 46 | 47 | impl TransportScramblingControl { 48 | /// creates a new instance from the fourth byte of a TS packet's header 49 | #[inline] 50 | fn from_byte_four(val: u8) -> TransportScramblingControl { 51 | TransportScramblingControl(val) 52 | } 53 | 54 | /// If the `transport_scrambling_control` field in the Transport Stream data has the value 55 | /// `00`, then return `false`, returns true if `transport_scrambling_control` has any other 56 | /// values. 57 | #[inline] 58 | pub fn is_scrambled(&self) -> bool { 59 | self.0 & 0b11000000 != 0 60 | } 61 | 62 | /// Returns the value of the `transport_scrambling_control` field, or `None` if the field 63 | /// indicates this packet payload is not scrambled. 64 | pub fn scheme(&self) -> Option { 65 | let scheme = self.0 >> 6; 66 | NonZeroU8::new(scheme) 67 | } 68 | } 69 | impl Debug for TransportScramblingControl { 70 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 71 | f.debug_struct("TransportScramblingControl") 72 | .field("scheme", &self.scheme()) 73 | .finish() 74 | } 75 | } 76 | 77 | /// A _Clock Reference_ is used to represent the values of PCR and ESCR fields within the transport 78 | /// stream data. 79 | /// 80 | /// A _Clock Reference_ includes a 33-bit, 90kHz `base` component, together with another 9-bit, 81 | /// high-resolution `extension` component. 82 | /// 83 | /// Together these can be viewed as a 42-bit, 27MHz quantity (e.g. `let full_value = pcr as u64`). 84 | /// Since the clock reference is limited to 33-bits, at a rate of 90kHz a continuously increasing 85 | /// clock value will wrap-around approximately every 26.5 hours. 86 | #[derive(Copy, Clone)] 87 | pub struct ClockRef { 88 | base: u64, 89 | extension: u16, 90 | } 91 | 92 | impl PartialEq for ClockRef { 93 | fn eq(&self, other: &ClockRef) -> bool { 94 | self.base == other.base && self.extension == other.extension 95 | } 96 | } 97 | 98 | impl From for u64 { 99 | fn from(pcr: ClockRef) -> u64 { 100 | pcr.base * 300 + u64::from(pcr.extension) 101 | } 102 | } 103 | 104 | impl fmt::Debug for ClockRef { 105 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { 106 | write!(f, "PCR{{{:08x}:{:04x}}}", self.base, self.extension) 107 | } 108 | } 109 | impl ClockRef { 110 | /// Panics if `data` is shorter than 5 bytes 111 | pub fn from_slice(data: &[u8]) -> ClockRef { 112 | ClockRef { 113 | base: u64::from(data[0]) << 25 114 | | u64::from(data[1]) << 17 115 | | u64::from(data[2]) << 9 116 | | u64::from(data[3]) << 1 117 | | u64::from(data[4]) >> 7, 118 | //reserved: (data[4] >> 1) & 0b00111111, 119 | extension: (u16::from(data[4]) & 0b1) << 8 | u16::from(data[5]), 120 | } 121 | } 122 | /// Panics if the `base` is greater than 2^33-1 or the `extension` is greater than 2^9-1 123 | pub fn from_parts(base: u64, extension: u16) -> ClockRef { 124 | assert!(base < (1 << 33)); 125 | assert!(extension < (1 << 9)); 126 | ClockRef { base, extension } 127 | } 128 | 129 | /// get the 33-bit, 90kHz 'base' component of the timestamp 130 | pub fn base(&self) -> u64 { 131 | self.base 132 | } 133 | 134 | /// get the 9-bit 'extension' component of the timestamp, measured in 300ths of the 90kHz base 135 | /// clockrate (i.e. 27MHz) 136 | pub fn extension(&self) -> u16 { 137 | self.extension 138 | } 139 | } 140 | 141 | /// Some error encountered while parsing adaptation field syntax 142 | #[derive(Debug, PartialEq, Eq)] 143 | pub enum AdaptationFieldError { 144 | /// The an optional field's value was requested, but the field is not actually present 145 | FieldNotPresent, 146 | /// There is a syntactic problem in the adaptation field being parsed, and not enough data 147 | /// is present in the stream to hold the requested component which is supposed to be present. 148 | NotEnoughData, 149 | /// The `seamless_splice()` function found a syntax error is the adaptation field data holding 150 | /// the _seamless_splice_ field. 151 | SpliceTimestampError(pes::TimestampError), 152 | } 153 | 154 | /// A collection of fields that may optionally appear within the header of a transport stream 155 | /// `Packet`. 156 | /// 157 | /// As returned by [`Packet::adaptation_field()`](struct.Packet.html#method.adaptation_field) 158 | pub struct AdaptationField<'buf> { 159 | buf: &'buf [u8], 160 | } 161 | 162 | impl<'buf> AdaptationField<'buf> { 163 | // TODO: just eager-load all this stuff in new()? would be simpler! 164 | 165 | /// Create a new structure to parse the adaptation field data held within the given slice. 166 | /// 167 | /// Panics if the slice is empty. 168 | pub fn new(buf: &'buf [u8]) -> AdaptationField<'buf> { 169 | assert!(!buf.is_empty()); 170 | AdaptationField { buf } 171 | } 172 | 173 | /// Get the value of the _discontinuity_indicator_ field which might have been written into 174 | /// the transport stream by some 'upstream' processor on discovering that there was a break 175 | /// in the data. 176 | pub fn discontinuity_indicator(&self) -> bool { 177 | self.buf[0] & 0b1000_0000 != 0 178 | } 179 | /// Get the value of the _random_access_indicator_ field. 180 | pub fn random_access_indicator(&self) -> bool { 181 | self.buf[0] & 0b0100_0000 != 0 182 | } 183 | /// Get the value of the _elementary_stream_priority_indicator_ field. 184 | pub fn elementary_stream_priority_indicator(&self) -> u8 { 185 | (self.buf[0] & 0b10_0000) >> 5 186 | } 187 | fn pcr_flag(&self) -> bool { 188 | self.buf[0] & 0b1_0000 != 0 189 | } 190 | fn opcr_flag(&self) -> bool { 191 | self.buf[0] & 0b1000 != 0 192 | } 193 | fn splicing_point_flag(&self) -> bool { 194 | self.buf[0] & 0b100 != 0 195 | } 196 | fn transport_private_data_flag(&self) -> bool { 197 | self.buf[0] & 0b10 != 0 198 | } 199 | fn adaptation_field_extension_flag(&self) -> bool { 200 | self.buf[0] & 0b1 != 0 201 | } 202 | fn slice(&self, from: usize, to: usize) -> Result<&'buf [u8], AdaptationFieldError> { 203 | if to > self.buf.len() { 204 | Err(AdaptationFieldError::NotEnoughData) 205 | } else { 206 | Ok(&self.buf[from..to]) 207 | } 208 | } 209 | const PCR_SIZE: usize = 6; 210 | /// Get the _Program Clock Reference_ field, 211 | /// or `AdaptationFieldError::FieldNotPresent` if absent 212 | pub fn pcr(&self) -> Result { 213 | if self.pcr_flag() { 214 | Ok(ClockRef::from_slice(self.slice(1, 1 + Self::PCR_SIZE)?)) 215 | } else { 216 | Err(AdaptationFieldError::FieldNotPresent) 217 | } 218 | } 219 | fn opcr_offset(&self) -> usize { 220 | if self.pcr_flag() { 221 | 1 + Self::PCR_SIZE 222 | } else { 223 | 1 224 | } 225 | } 226 | /// Returns the 'Original Program Clock Reference' value, 227 | /// or `AdaptationFieldError::FieldNotPresent` if absent 228 | pub fn opcr(&self) -> Result { 229 | if self.opcr_flag() { 230 | let off = self.opcr_offset(); 231 | Ok(ClockRef::from_slice(self.slice(off, off + Self::PCR_SIZE)?)) 232 | } else { 233 | Err(AdaptationFieldError::FieldNotPresent) 234 | } 235 | } 236 | fn splice_countdown_offset(&self) -> usize { 237 | self.opcr_offset() + if self.opcr_flag() { Self::PCR_SIZE } else { 0 } 238 | } 239 | /// Get the value of the _splice_countdown_ field, 240 | /// or `AdaptationFieldError::FieldNotPresent` if absent 241 | pub fn splice_countdown(&self) -> Result { 242 | if self.splicing_point_flag() { 243 | let off = self.splice_countdown_offset(); 244 | Ok(self.slice(off, off + 1)?[0]) 245 | } else { 246 | Err(AdaptationFieldError::FieldNotPresent) 247 | } 248 | } 249 | fn transport_private_data_offset(&self) -> usize { 250 | self.splice_countdown_offset() + usize::from(self.splicing_point_flag()) 251 | } 252 | /// Borrow a slice of the underlying buffer containing private data, 253 | /// or `AdaptationFieldError::FieldNotPresent` if absent 254 | pub fn transport_private_data(&self) -> Result<&[u8], AdaptationFieldError> { 255 | if self.transport_private_data_flag() { 256 | let off = self.transport_private_data_offset(); 257 | let len = self.slice(off, off + 1)?[0] as usize; 258 | Ok(self.slice(off + 1, off + 1 + len)?) 259 | } else { 260 | Err(AdaptationFieldError::FieldNotPresent) 261 | } 262 | } 263 | fn adaptation_field_extension_offset(&self) -> Result { 264 | let off = self.transport_private_data_offset(); 265 | Ok(off 266 | + if self.transport_private_data_flag() { 267 | let len = self.slice(off, off + 1)?[0] as usize; 268 | len + 1 269 | } else { 270 | 0 271 | }) 272 | } 273 | /// Returns extended adaptation fields, or `AdaptationFieldError::FieldNotPresent` if absent 274 | pub fn adaptation_field_extension( 275 | &self, 276 | ) -> Result, AdaptationFieldError> { 277 | if self.adaptation_field_extension_flag() { 278 | let off = self.adaptation_field_extension_offset()?; 279 | let len = self.slice(off, off + 1)?[0] as usize; 280 | AdaptationFieldExtension::new(self.slice(off + 1, off + 1 + len)?) 281 | } else { 282 | Err(AdaptationFieldError::FieldNotPresent) 283 | } 284 | } 285 | } 286 | 287 | impl<'buf> fmt::Debug for AdaptationField<'buf> { 288 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 289 | let mut d = f.debug_struct("AdaptationField"); 290 | d.field("discontinuity_indicator", &self.discontinuity_indicator()); 291 | d.field("random_access_indicator", &self.random_access_indicator()); 292 | d.field( 293 | "elementary_stream_priority_indicator", 294 | &self.elementary_stream_priority_indicator(), 295 | ); 296 | d.field("pcr", &self.pcr()); 297 | d.field("opcr", &self.opcr()); 298 | d.field("splice_countdown", &self.splice_countdown()); 299 | d.field("transport_private_data", &self.transport_private_data()); 300 | d.field( 301 | "adaptation_field_extension", 302 | &self.adaptation_field_extension(), 303 | ); 304 | d.finish() 305 | } 306 | } 307 | 308 | /// Optional extensions within an [`AdaptationField`](struct.AdaptationField.html). 309 | /// 310 | /// As returned by 311 | /// [`AdaptationField::adaptation_field_extension()`](struct.AdaptationField.html#method.adaptation_field_extension). 312 | pub struct AdaptationFieldExtension<'buf> { 313 | buf: &'buf [u8], 314 | } 315 | impl<'buf> AdaptationFieldExtension<'buf> { 316 | /// Create a new structure to parse the adaptation field extension data held within the given 317 | /// slice. 318 | pub fn new(buf: &'buf [u8]) -> Result, AdaptationFieldError> { 319 | if buf.is_empty() { 320 | Err(AdaptationFieldError::NotEnoughData) 321 | } else { 322 | Ok(AdaptationFieldExtension { buf }) 323 | } 324 | } 325 | 326 | fn slice(&self, from: usize, to: usize) -> Result<&'buf [u8], AdaptationFieldError> { 327 | if to > self.buf.len() { 328 | Err(AdaptationFieldError::NotEnoughData) 329 | } else { 330 | Ok(&self.buf[from..to]) 331 | } 332 | } 333 | 334 | fn ltw_flag(&self) -> bool { 335 | self.buf[0] & 0b1000_0000 != 0 336 | } 337 | fn piecewise_rate_flag(&self) -> bool { 338 | self.buf[0] & 0b0100_0000 != 0 339 | } 340 | fn seamless_splice_flag(&self) -> bool { 341 | self.buf[0] & 0b0010_0000 != 0 342 | } 343 | /// Returns the 'Legal time window offset', if any. 344 | pub fn ltw_offset(&self) -> Result, AdaptationFieldError> { 345 | if self.ltw_flag() { 346 | let dat = self.slice(1, 3)?; 347 | let ltw_valid_flag = dat[0] & 0b1000_0000 != 0; 348 | Ok(if ltw_valid_flag { 349 | Some(u16::from(dat[0] & 0b0111_1111) << 8 | u16::from(dat[1])) 350 | } else { 351 | None 352 | }) 353 | } else { 354 | Err(AdaptationFieldError::FieldNotPresent) 355 | } 356 | } 357 | fn piecewise_rate_offset(&self) -> usize { 358 | 1 + if self.ltw_flag() { 2 } else { 0 } 359 | } 360 | /// Get the value of the _piecewise_rate_ field, 361 | /// or `AdaptationFieldError::FieldNotPresent` if absent 362 | pub fn piecewise_rate(&self) -> Result { 363 | if self.piecewise_rate_flag() { 364 | let off = self.piecewise_rate_offset(); 365 | let dat = self.slice(off, off + 3)?; 366 | Ok(u32::from(dat[0] & 0b0011_1111) << 16 | u32::from(dat[1]) << 8 | u32::from(dat[2])) 367 | } else { 368 | Err(AdaptationFieldError::FieldNotPresent) 369 | } 370 | } 371 | fn seamless_splice_offset(&self) -> usize { 372 | self.piecewise_rate_offset() + if self.piecewise_rate_flag() { 3 } else { 0 } 373 | } 374 | /// Get the value of the _seamless_splice_ field, 375 | /// or `AdaptationFieldError::FieldNotPresent` if absent 376 | pub fn seamless_splice(&self) -> Result { 377 | if self.seamless_splice_flag() { 378 | let off = self.seamless_splice_offset(); 379 | let dat = self.slice(off, off + 5)?; 380 | Ok(SeamlessSplice { 381 | splice_type: dat[0] >> 4, 382 | dts_next_au: pes::Timestamp::from_bytes(dat) 383 | .map_err(AdaptationFieldError::SpliceTimestampError)?, 384 | }) 385 | } else { 386 | Err(AdaptationFieldError::FieldNotPresent) 387 | } 388 | } 389 | } 390 | 391 | impl<'buf> fmt::Debug for AdaptationFieldExtension<'buf> { 392 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 393 | let mut d = f.debug_struct("AdaptationFieldExtension"); 394 | d.field("ltw_offset", &self.ltw_offset()); 395 | d.field("piecewise_rate", &self.piecewise_rate()); 396 | d.field("seamless_splice", &self.seamless_splice()); 397 | d.finish() 398 | } 399 | } 400 | 401 | /// Value of the _seamless_splice_ field, as returned by 402 | /// [`AdaptationFieldExtension::seamless_splice()`](struct.AdaptationFieldExtension.html#method.seamless_splice) 403 | /// method 404 | #[derive(Debug, PartialEq, Eq)] 405 | pub struct SeamlessSplice { 406 | /// see _ISO/IEC 13818-1 : 2000_, Table 2-7 through Table 2-16 407 | pub splice_type: u8, 408 | /// The DTS of the access unit after the splice-point. 409 | pub dts_next_au: pes::Timestamp, 410 | } 411 | 412 | /// A counter value used within a transport stream to detect discontinuities in a sequence of packets. 413 | /// The continuity counter should increase by one for each packet with a given PID for which 414 | /// `adaptation_control` indicates that a payload should be present. 415 | /// 416 | /// See [`Packet.continuity_counter()`](struct.Packet.html#method.continuity_counter) 417 | #[derive(PartialEq, Eq, Debug, Clone, Copy)] 418 | pub struct ContinuityCounter { 419 | val: u8, 420 | } 421 | 422 | impl From for ContinuityCounter { 423 | #[inline] 424 | fn from(count: u8) -> ContinuityCounter { 425 | ContinuityCounter::new(count) 426 | } 427 | } 428 | 429 | impl ContinuityCounter { 430 | /// Panics if the given value is greater than 15. 431 | #[inline] 432 | pub fn new(count: u8) -> ContinuityCounter { 433 | assert!(count < 0b10000); 434 | ContinuityCounter { val: count } 435 | } 436 | 437 | /// Returns this counter's value, which will be between 0 and 15 inclusive. 438 | #[inline] 439 | pub fn count(self) -> u8 { 440 | self.val 441 | } 442 | 443 | /// true iff the given `ContinuityCounter` value follows this one. Note that the maximum counter 444 | /// value is 15, and the counter 'wraps around': 445 | /// 446 | /// ```rust 447 | /// # use mpeg2ts_reader::packet::ContinuityCounter; 448 | /// let a = ContinuityCounter::new(0); 449 | /// let b = ContinuityCounter::new(15); 450 | /// assert!(a.follows(b)); // after 15, counter wraps around to 0 451 | /// ``` 452 | #[inline] 453 | pub fn follows(self, other: ContinuityCounter) -> bool { 454 | (other.val + 1) & 0b1111 == self.val 455 | } 456 | } 457 | 458 | /// A Packet Identifier value, between `0x0000` and `0x1fff`. 459 | /// 460 | /// PID values identify a particular sub-stream within the overall Transport Stream. 461 | /// 462 | /// As returned by the [`Packet::pid`](struct.Packet.html#method.pid) method for example. 463 | #[derive(Copy, Clone, PartialEq, Eq, Hash)] 464 | pub struct Pid(u16); 465 | impl Pid { 466 | /// The largest possible PID value, `0x1fff`. 467 | pub const MAX_VALUE: u16 = 0x1fff; 468 | 469 | /// The total number of distinct PID values, `0x2000` (equal to `MAX_VALUE` + 1) 470 | pub const PID_COUNT: usize = (Self::MAX_VALUE + 1) as usize; 471 | 472 | #[doc(hidden)] 473 | /// Use mpeg2ts_reader::psi::pat::PAT_PID instead of this 474 | pub const PAT: Pid = Pid::new(0); 475 | #[doc(hidden)] 476 | /// Use mpeg2ts_reader::STUFFING_PID instead of this 477 | pub const STUFFING: Pid = Pid::new(0x1fff); 478 | 479 | /// Panics if the given value is greater than `Pid::MAX_VALUE`. 480 | pub const fn new(pid: u16) -> Pid { 481 | assert!(pid <= 0x1fff); 482 | Pid(pid) 483 | } 484 | } 485 | impl TryFrom for Pid { 486 | type Error = (); 487 | 488 | fn try_from(value: u16) -> Result { 489 | if value <= Pid::MAX_VALUE { 490 | Ok(Pid(value)) 491 | } else { 492 | Err(()) 493 | } 494 | } 495 | } 496 | impl From for u16 { 497 | #[inline] 498 | fn from(pid: Pid) -> Self { 499 | pid.0 500 | } 501 | } 502 | impl From for usize { 503 | #[inline] 504 | fn from(pid: Pid) -> Self { 505 | pid.0 as usize 506 | } 507 | } 508 | impl fmt::Debug for Pid { 509 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { 510 | write!(f, "Pid({:04x})", self.0) 511 | } 512 | } 513 | 514 | /// A transport stream `Packet` is a wrapper around a byte slice which allows the bytes to be 515 | /// interpreted as a packet structure per _ISO/IEC 13818-1, Section 2.4.3.3_. 516 | pub struct Packet<'buf> { 517 | buf: &'buf [u8], 518 | } 519 | 520 | const FIXED_HEADER_SIZE: usize = 4; 521 | // when AF present, a 1-byte 'length' field precedes the content, 522 | const ADAPTATION_FIELD_OFFSET: usize = FIXED_HEADER_SIZE + 1; 523 | 524 | impl<'buf> Packet<'buf> { 525 | /// The value `0x47`, which must appear in the first byte of every transport stream packet. 526 | pub const SYNC_BYTE: u8 = 0x47; 527 | 528 | /// The fixed 188 byte size of a transport stream packet. 529 | pub const SIZE: usize = 188; 530 | 531 | /// returns `true` if the given value is a valid synchronisation byte, the value `Packet::SYNC_BYTE` (0x47), which 532 | /// must appear at the start of every transport stream packet. 533 | #[inline(always)] 534 | pub fn is_sync_byte(b: u8) -> bool { 535 | b == Self::SYNC_BYTE 536 | } 537 | 538 | /// Panics if the given buffer is less than 188 bytes, or if the initial sync-byte does not 539 | /// have the correct value (`0x47`). Calling code is expected to have already checked those 540 | /// conditions. 541 | /// 542 | /// Panics if the buffer size is not exactly `Packet::SIZE` (188) bytes, or if the first 543 | /// byte value is not equal to `Packet::SYNC_BYTE` (0x47). 544 | #[inline(always)] 545 | pub fn new(buf: &'buf [u8]) -> Packet<'buf> { 546 | assert_eq!(buf.len(), Self::SIZE); 547 | assert!(Packet::is_sync_byte(buf[0])); 548 | Packet { buf } 549 | } 550 | 551 | /// Like `new()`, but returns `None` if the sync-byte has incorrect value (still panics if the 552 | /// buffer size is not 188 bytes). 553 | #[inline(always)] 554 | pub fn try_new(buf: &'buf [u8]) -> Option> { 555 | assert_eq!(buf.len(), Self::SIZE); 556 | if Packet::is_sync_byte(buf[0]) { 557 | Some(Packet { buf }) 558 | } else { 559 | None 560 | } 561 | } 562 | 563 | /// *May* have been set if some previous processing of this TS data detected at least 564 | /// 1 uncorrectable bit error in this TS packet. 565 | #[inline] 566 | pub fn transport_error_indicator(&self) -> bool { 567 | self.buf[1] & 0b1000_0000 != 0 568 | } 569 | 570 | /// a structure larger than a single packet payload needs to be split across multiple packets, 571 | /// `payload_unit_start()` indicates if this packet payload contains the start of the 572 | /// structure. If `false`, this packets payload is a continuation of a structure which began 573 | /// in an earlier packet within the transport stream. 574 | #[inline] 575 | pub fn payload_unit_start_indicator(&self) -> bool { 576 | self.buf[1] & 0b0100_0000 != 0 577 | } 578 | 579 | /// When `1`, this TS packet has higher priority than other packets of the the same PID having 580 | /// PID `0`. 581 | pub fn transport_priority(&self) -> bool { 582 | self.buf[1] & 0b0010_0000 != 0 583 | } 584 | 585 | /// The sub-stream to which a particular packet belongs is indicated by this Packet Identifier 586 | /// value. 587 | #[inline] 588 | pub fn pid(&self) -> Pid { 589 | Pid(u16::from(self.buf[1] & 0b0001_1111) << 8 | u16::from(self.buf[2])) 590 | } 591 | 592 | /// Value of the _transport_scrambling_control_ field. 593 | #[inline] 594 | pub fn transport_scrambling_control(&self) -> TransportScramblingControl { 595 | TransportScramblingControl::from_byte_four(self.buf[3]) 596 | } 597 | 598 | /// The returned enum value indicates if `adaptation_field()`, `payload()` or both will return 599 | /// something. 600 | #[inline] 601 | pub fn adaptation_control(&self) -> AdaptationControl { 602 | AdaptationControl::new(self.buf[3]) 603 | } 604 | 605 | /// Each packet with a given `pid()` value within a transport stream should have a continuity 606 | /// counter value which increases by 1 from the last counter value seen. Unexpected continuity 607 | /// counter values allow the receiver of the transport stream to detect discontinuities in the 608 | /// stream (e.g. due to data loss during transmission). 609 | #[inline] 610 | pub fn continuity_counter(&self) -> ContinuityCounter { 611 | ContinuityCounter::new(self.buf[3] & 0b0000_1111) 612 | } 613 | 614 | fn adaptation_field_length(&self) -> usize { 615 | self.buf[4] as usize 616 | } 617 | 618 | /// An `AdaptationField` contains additional packet headers that may be present in the packet. 619 | pub fn adaptation_field(&self) -> Option> { 620 | let ac = self.adaptation_control(); 621 | if ac.has_adaptation_field() { 622 | if ac.has_payload() { 623 | let len = self.adaptation_field_length(); 624 | if len > 182 { 625 | warn!( 626 | "invalid adaptation_field_length for AdaptationFieldAndPayload: {}", 627 | len 628 | ); 629 | // TODO: Option> instead? 630 | return None; 631 | } 632 | if len == 0 { 633 | return None; 634 | } 635 | Some(self.mk_af(len)) 636 | } else { 637 | let len = self.adaptation_field_length(); 638 | if len != (Self::SIZE - ADAPTATION_FIELD_OFFSET) { 639 | warn!( 640 | "invalid adaptation_field_length for AdaptationFieldOnly: {}", 641 | len 642 | ); 643 | // TODO: Option> instead? 644 | return None; 645 | } 646 | Some(self.mk_af(len)) 647 | } 648 | } else { 649 | None 650 | } 651 | } 652 | 653 | fn mk_af(&self, len: usize) -> AdaptationField<'buf> { 654 | AdaptationField::new(&self.buf[ADAPTATION_FIELD_OFFSET..ADAPTATION_FIELD_OFFSET + len]) 655 | } 656 | 657 | /// The data contained within the packet, not including the packet headers. 658 | /// Not all packets have a payload, and `None` is returned if `adaptation_control()` indicates 659 | /// that no payload is present. None may also be returned if the packet is malformed. 660 | /// If `Some` payload is returned, it is guaranteed not to be an empty slice. 661 | #[inline(always)] 662 | pub fn payload(&self) -> Option<&'buf [u8]> { 663 | if self.adaptation_control().has_payload() { 664 | self.mk_payload() 665 | } else { 666 | None 667 | } 668 | } 669 | 670 | #[inline] 671 | fn mk_payload(&self) -> Option<&'buf [u8]> { 672 | let offset = self.content_offset(); 673 | let len = self.buf.len(); 674 | match offset.cmp(&len) { 675 | Ordering::Equal => { 676 | warn!("no payload data present"); 677 | None 678 | } 679 | Ordering::Greater => { 680 | warn!( 681 | "adaptation_field_length {} too large", 682 | self.adaptation_field_length() 683 | ); 684 | None 685 | } 686 | Ordering::Less => Some(&self.buf[offset..]), 687 | } 688 | } 689 | 690 | /// borrow a reference to the underlying buffer of this packet 691 | pub fn buffer(&self) -> &'buf [u8] { 692 | self.buf 693 | } 694 | 695 | #[inline] 696 | fn content_offset(&self) -> usize { 697 | if self.adaptation_control().has_adaptation_field() { 698 | ADAPTATION_FIELD_OFFSET + self.adaptation_field_length() 699 | } else { 700 | FIXED_HEADER_SIZE 701 | } 702 | } 703 | } 704 | 705 | #[cfg(test)] 706 | mod test { 707 | use crate::demultiplex::test::NullDemuxContext; 708 | use crate::demultiplex::{NullPacketFilter, PacketFilter}; 709 | use crate::packet::*; 710 | use crate::pes; 711 | 712 | #[test] 713 | fn pid() { 714 | assert!(Pid::try_from(0x2000).is_err()); 715 | } 716 | 717 | #[test] 718 | #[should_panic] 719 | fn zero_len() { 720 | let buf = [0u8; 0]; 721 | Packet::new(&buf[..]); 722 | } 723 | 724 | #[test] 725 | fn test_xmas_tree() { 726 | let mut buf = [0xffu8; Packet::SIZE]; 727 | buf[0] = Packet::SYNC_BYTE; 728 | buf[4] = 28; // adaptation_field_length 729 | buf[19] = 1; // transport_private_data_length 730 | buf[21] = 11; // adaptation_field_extension_length 731 | let pk = Packet::new(&buf[..]); 732 | assert_eq!(u16::from(pk.pid()), 0b1111111111111u16); 733 | assert!(pk.transport_error_indicator()); 734 | assert!(pk.payload_unit_start_indicator()); 735 | assert!(pk.transport_priority()); 736 | assert!(pk.transport_scrambling_control().is_scrambled()); 737 | assert_eq!( 738 | pk.transport_scrambling_control().scheme(), 739 | NonZeroU8::new(3) 740 | ); 741 | assert!(pk.adaptation_control().has_payload()); 742 | assert!(pk.adaptation_control().has_adaptation_field()); 743 | assert_eq!(pk.continuity_counter().count(), 0b1111); 744 | assert!(pk.adaptation_field().is_some()); 745 | let ad = pk.adaptation_field().unwrap(); 746 | assert!(ad.discontinuity_indicator()); 747 | assert_eq!( 748 | ad.pcr(), 749 | Ok(ClockRef::from_parts( 750 | 0b1_1111_1111_1111_1111_1111_1111_1111_1111, 751 | 0b1_1111_1111 752 | )) 753 | ); 754 | assert_eq!(1234 * 300 + 56, u64::from(ClockRef::from_parts(1234, 56))); 755 | assert_eq!( 756 | ad.opcr(), 757 | Ok(ClockRef::from_parts( 758 | 0b1_1111_1111_1111_1111_1111_1111_1111_1111, 759 | 0b1_1111_1111 760 | )) 761 | ); 762 | assert_eq!(ad.splice_countdown(), Ok(0b11111111)); 763 | let expected_data = [0xff]; 764 | assert_eq!(ad.transport_private_data(), Ok(&expected_data[..])); 765 | let ext = ad.adaptation_field_extension().unwrap(); 766 | assert_eq!(ext.ltw_offset(), Ok(Some(0b0111_1111_1111_1111))); 767 | assert_eq!(ext.piecewise_rate(), Ok(0b0011_1111_1111_1111_1111_1111)); 768 | assert_eq!( 769 | ext.seamless_splice(), 770 | Ok(SeamlessSplice { 771 | splice_type: 0b1111, 772 | dts_next_au: pes::Timestamp::from_u64(0b1_1111_1111_1111_1111_1111_1111_1111_1111) 773 | }) 774 | ); 775 | assert!(!format!("{:?}", pk.adaptation_field()).is_empty()) 776 | } 777 | 778 | #[test] 779 | fn empty_adaptation_field() { 780 | let mut buf = [0xffu8; Packet::SIZE]; 781 | buf[0] = Packet::SYNC_BYTE; 782 | buf[4] = 0; // adaptation_field_length 783 | let pk = Packet::new(&buf[..]); 784 | assert!(pk.adaptation_control().has_payload()); 785 | assert!(pk.adaptation_control().has_adaptation_field()); 786 | assert!(pk.adaptation_field().is_none()); 787 | } 788 | 789 | #[test] 790 | fn empty_adaptation_field_extension() { 791 | assert!(AdaptationFieldExtension::new(b"").is_err()); 792 | } 793 | 794 | #[test] 795 | fn should_do_nothing() { 796 | let mut ctx = NullDemuxContext::new(); 797 | let mut data = vec![0; 188]; 798 | data[0] = 0x47; 799 | let pkt = Packet::new(&data); 800 | NullPacketFilter::default().consume(&mut ctx, &pkt); 801 | } 802 | } 803 | -------------------------------------------------------------------------------- /src/psi/pat.rs: -------------------------------------------------------------------------------- 1 | //! Types related to the _Program Association Table_ 2 | 3 | use crate::packet; 4 | use log::warn; 5 | 6 | /// The identifier of TS Packets containing Program Association Table sections, with value `0`. 7 | pub const PAT_PID: packet::Pid = packet::Pid::new(0); 8 | 9 | /// Identifiers related to a specific program within the Transport Stream 10 | #[derive(Clone, Debug)] 11 | pub enum ProgramDescriptor { 12 | // TODO: consider renaming 'ProgramAssociationEntry', so as not to cause confusion with types 13 | // actually implementing the Descriptor trait (which this type should not do) 14 | /// this PAT section entry describes where the Network Information Table will be found 15 | Network { 16 | /// The PID of NIT section packets 17 | pid: packet::Pid, 18 | }, 19 | /// this PAT section entry gives the PID and Program Number of a program within this Transport 20 | /// Stream 21 | Program { 22 | /// The program number might represent the 'channel' of the program 23 | program_number: u16, 24 | /// The PID where PMT sections for this program can be found 25 | pid: packet::Pid, 26 | }, 27 | } 28 | 29 | impl ProgramDescriptor { 30 | /// panics if fewer than 4 bytes are provided 31 | pub fn from_bytes(data: &[u8]) -> ProgramDescriptor { 32 | let program_number = (u16::from(data[0]) << 8) | u16::from(data[1]); 33 | let pid = packet::Pid::new((u16::from(data[2]) & 0b0001_1111) << 8 | u16::from(data[3])); 34 | if program_number == 0 { 35 | ProgramDescriptor::Network { pid } 36 | } else { 37 | ProgramDescriptor::Program { 38 | program_number, 39 | pid, 40 | } 41 | } 42 | } 43 | 44 | /// produces the Pid of either the NIT or PMT, depending on which type of `ProgramDescriptor` 45 | /// this is 46 | pub fn pid(&self) -> packet::Pid { 47 | match *self { 48 | ProgramDescriptor::Network { pid } => pid, 49 | ProgramDescriptor::Program { pid, .. } => pid, 50 | } 51 | } 52 | } 53 | 54 | /// Sections of the _Program Association Table_ give details of the programs within a transport 55 | /// stream. There may be only one program, or in the case of a broadcast multiplex, there may 56 | /// be many. 57 | #[derive(Clone, Debug)] 58 | pub struct PatSection<'buf> { 59 | data: &'buf [u8], 60 | } 61 | impl<'buf> PatSection<'buf> { 62 | /// Create a `PatSection`, wrapping the given slice, whose methods can parse the section's 63 | /// fields 64 | pub fn new(data: &'buf [u8]) -> PatSection<'buf> { 65 | PatSection { data } 66 | } 67 | /// Returns an iterator over the entries in this program association table section. 68 | pub fn programs(&self) -> impl Iterator + 'buf { 69 | ProgramIter { buf: self.data } 70 | } 71 | } 72 | 73 | /// Iterate over the list of programs in a `PatSection`. 74 | struct ProgramIter<'buf> { 75 | buf: &'buf [u8], 76 | } 77 | impl<'buf> Iterator for ProgramIter<'buf> { 78 | type Item = ProgramDescriptor; 79 | 80 | fn next(&mut self) -> Option { 81 | if self.buf.is_empty() { 82 | return None; 83 | } 84 | if self.buf.len() < 4 { 85 | warn!( 86 | "too few bytes remaining for PAT descriptor: {}", 87 | self.buf.len() 88 | ); 89 | return None; 90 | } 91 | let (head, tail) = self.buf.split_at(4); 92 | self.buf = tail; 93 | Some(ProgramDescriptor::from_bytes(head)) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/psi/pmt.rs: -------------------------------------------------------------------------------- 1 | //! Types related to the _Program Map Table_ 2 | 3 | use crate::demultiplex::DemuxError; 4 | use crate::descriptor; 5 | use crate::packet; 6 | use crate::StreamType; 7 | use log::warn; 8 | use std::fmt; 9 | 10 | /// Sections of the _Program Map Table_ give details of the streams within a particular program 11 | pub struct PmtSection<'buf> { 12 | data: &'buf [u8], 13 | } 14 | impl<'buf> fmt::Debug for PmtSection<'buf> { 15 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { 16 | f.debug_struct("PmtSection") 17 | .field("pcr_pid", &self.pcr_pid()) 18 | .field("descriptors", &DescriptorsDebug(self)) 19 | .field("streams", &StreamsDebug(self)) 20 | .finish() 21 | } 22 | } 23 | struct StreamsDebug<'buf>(&'buf PmtSection<'buf>); 24 | impl<'buf> fmt::Debug for StreamsDebug<'buf> { 25 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { 26 | f.debug_list().entries(self.0.streams()).finish() 27 | } 28 | } 29 | struct DescriptorsDebug<'buf>(&'buf PmtSection<'buf>); 30 | impl<'buf> fmt::Debug for DescriptorsDebug<'buf> { 31 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { 32 | f.debug_list() 33 | .entries(self.0.descriptors::>()) 34 | .finish() 35 | } 36 | } 37 | 38 | impl<'buf> PmtSection<'buf> { 39 | /// Create a `PmtSection`, wrapping the given slice, whose methods can parse the section's 40 | /// fields 41 | pub fn from_bytes(data: &'buf [u8]) -> Result, DemuxError> { 42 | if data.len() < Self::HEADER_SIZE { 43 | Err(DemuxError::NotEnoughData { 44 | field: "program_map_section", 45 | expected: Self::HEADER_SIZE, 46 | actual: data.len(), 47 | }) 48 | } else { 49 | let sect = PmtSection { data }; 50 | let expected = sect.program_info_length() as usize + Self::HEADER_SIZE; 51 | if data.len() < expected { 52 | Err(DemuxError::NotEnoughData { 53 | field: "descriptor", 54 | expected, 55 | actual: data.len(), 56 | }) 57 | } else { 58 | Ok(sect) 59 | } 60 | } 61 | } 62 | 63 | /// Borrow a reference to the buffer containing the underlying PMT section data 64 | pub fn buffer(&self) -> &[u8] { 65 | self.data 66 | } 67 | 68 | const HEADER_SIZE: usize = 4; 69 | 70 | /// Returns the Pid of packets that will contain the Program Clock Reference for this program 71 | pub fn pcr_pid(&self) -> packet::Pid { 72 | packet::Pid::new(u16::from(self.data[0] & 0b0001_1111) << 8 | u16::from(self.data[1])) 73 | } 74 | fn program_info_length(&self) -> u16 { 75 | u16::from(self.data[2] & 0b0000_1111) << 8 | u16::from(self.data[3]) 76 | } 77 | /// Returns an iterator over the descriptors attached to this PMT section. 78 | pub fn descriptors + 'buf>( 79 | &self, 80 | ) -> impl Iterator> + 'buf { 81 | let descriptor_end = Self::HEADER_SIZE + self.program_info_length() as usize; 82 | let descriptor_data = &self.data[Self::HEADER_SIZE..descriptor_end]; 83 | descriptor::DescriptorIter::new(descriptor_data) 84 | } 85 | /// Returns an iterator over the streams of which this program is composed 86 | pub fn streams(&self) -> impl Iterator> { 87 | let descriptor_end = Self::HEADER_SIZE + self.program_info_length() as usize; 88 | if descriptor_end > self.data.len() { 89 | warn!( 90 | "program_info_length={} extends beyond end of PMT section (section_length={})", 91 | self.program_info_length(), 92 | self.data.len() 93 | ); 94 | // return an iterator that will produce no items, 95 | StreamInfoIter::new(&self.data[0..0]) 96 | } else { 97 | StreamInfoIter::new(&self.data[descriptor_end..]) 98 | } 99 | } 100 | } 101 | /// Iterator over the `StreamInfo` entries in a `PmtSection`. 102 | struct StreamInfoIter<'buf> { 103 | buf: &'buf [u8], 104 | } 105 | impl<'buf> StreamInfoIter<'buf> { 106 | fn new(buf: &'buf [u8]) -> StreamInfoIter<'buf> { 107 | StreamInfoIter { buf } 108 | } 109 | } 110 | impl<'buf> Iterator for StreamInfoIter<'buf> { 111 | type Item = StreamInfo<'buf>; 112 | 113 | fn next(&mut self) -> Option { 114 | if self.buf.is_empty() { 115 | return None; 116 | } 117 | if let Some((stream_info, info_len)) = StreamInfo::from_bytes(self.buf) { 118 | self.buf = &self.buf[info_len..]; 119 | Some(stream_info) 120 | } else { 121 | None 122 | } 123 | } 124 | } 125 | 126 | /// Details of a particular elementary stream within a program. 127 | /// 128 | /// - `stream_type` gives an indication of the kind of content carried within the stream 129 | /// - The `elementry_pid` property allows us to find Transport Stream packets that belong to the 130 | /// elementry stream 131 | /// - `descriptors` _may_ provide extra metadata describing some of the 132 | /// stream's properties (for example, the streams 'language' might be given in a descriptor; or 133 | /// it might not) 134 | pub struct StreamInfo<'buf> { 135 | data: &'buf [u8], 136 | } 137 | 138 | impl<'buf> StreamInfo<'buf> { 139 | const HEADER_SIZE: usize = 5; 140 | 141 | fn from_bytes(data: &'buf [u8]) -> Option<(StreamInfo<'buf>, usize)> { 142 | if data.len() < Self::HEADER_SIZE { 143 | warn!( 144 | "only {} bytes remaining for stream info, at least {} required {:?}", 145 | data.len(), 146 | Self::HEADER_SIZE, 147 | data 148 | ); 149 | return None; 150 | } 151 | let result = StreamInfo { data }; 152 | 153 | let descriptor_end = Self::HEADER_SIZE + result.es_info_length() as usize; 154 | if descriptor_end > data.len() { 155 | warn!( 156 | "PMT section of size {} is not large enough to contain es_info_length of {}", 157 | data.len(), 158 | result.es_info_length() 159 | ); 160 | return None; 161 | } 162 | Some((result, descriptor_end)) 163 | } 164 | 165 | /// The type of this stream 166 | pub fn stream_type(&self) -> StreamType { 167 | StreamType(self.data[0]) 168 | } 169 | /// The Pid that will be used for TS packets containing the data of this stream 170 | pub fn elementary_pid(&self) -> packet::Pid { 171 | packet::Pid::new(u16::from(self.data[1] & 0b0001_1111) << 8 | u16::from(self.data[2])) 172 | } 173 | fn es_info_length(&self) -> u16 { 174 | u16::from(self.data[3] & 0b0000_1111) << 8 | u16::from(self.data[4]) 175 | } 176 | 177 | /// Returns an iterator over the descriptors attached to this stream 178 | pub fn descriptors + 'buf>( 179 | &self, 180 | ) -> impl Iterator> + 'buf { 181 | let descriptor_end = Self::HEADER_SIZE + self.es_info_length() as usize; 182 | descriptor::DescriptorIter::new(&self.data[Self::HEADER_SIZE..descriptor_end]) 183 | } 184 | } 185 | impl<'buf> fmt::Debug for StreamInfo<'buf> { 186 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { 187 | f.debug_struct("StreamInfo") 188 | .field("stream_type", &self.stream_type()) 189 | .field("elementary_pid", &self.elementary_pid()) 190 | .field("descriptors", &StreamInfoDescriptorsDebug(self)) 191 | .finish() 192 | } 193 | } 194 | struct StreamInfoDescriptorsDebug<'buf>(&'buf StreamInfo<'buf>); 195 | impl<'buf> fmt::Debug for StreamInfoDescriptorsDebug<'buf> { 196 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { 197 | f.debug_list() 198 | .entries(self.0.descriptors::>()) 199 | .finish() 200 | } 201 | } 202 | 203 | #[cfg(test)] 204 | mod test { 205 | use crate::demultiplex::test::make_test_data; 206 | use crate::demultiplex::DemuxError; 207 | use crate::descriptor::CoreDescriptors; 208 | use crate::psi::pmt::PmtSection; 209 | use assert_matches::assert_matches; 210 | use bitstream_io::BitWrite; 211 | use hex_literal::hex; 212 | 213 | #[test] 214 | fn debug_does_not_panic() { 215 | let data = hex!("fd4df0001bfd4df00652010b70010411fd4ef01152010c0a04656e67007c025a007f02068211fd52f01252010d0a04656e67037c035880477f02060606fd51f00d5201055908656e671000010001"); 216 | let section = PmtSection::from_bytes(&data).unwrap(); 217 | // don't mind what it looks like, but it's embarrassing if it always panics! 218 | assert!(!format!("{:?}", section).is_empty()); 219 | } 220 | 221 | #[test] 222 | fn far_too_small() { 223 | let data = hex!("fd4df0"); 224 | assert_matches!( 225 | PmtSection::from_bytes(&data), 226 | Err(DemuxError::NotEnoughData { 227 | expected: 4, 228 | actual: 3, 229 | .. 230 | }) 231 | ); 232 | } 233 | 234 | #[test] 235 | fn descriptors_dont_fit() { 236 | let data = make_test_data(|w| { 237 | w.write(3, 0)?; // reserved 238 | w.write(13, 123)?; // PCR_PID 239 | w.write(4, 0)?; // reserved 240 | w.write(12, 1) // program_info_length 241 | }); 242 | // program_info_length claimed there is 1 byte of descriptor data, but there is no more 243 | // data in the buffer 244 | assert!(PmtSection::from_bytes(&data).is_err()); 245 | } 246 | 247 | #[test] 248 | fn truncated_descriptor() { 249 | let data = make_test_data(|w| { 250 | w.write(3, 0)?; // reserved 251 | w.write(13, 4)?; // PCR_PID 252 | w.write(4, 0)?; // reserved 253 | w.write(12, 0x1)?; // program_info_length 254 | w.write(8, 0x1) // descriptor_tag 255 | }); 256 | // a descriptor needs to be at least two bytes (descriptor_tag + descriptor_length) but 257 | // we only have one (descriptor_length missing) 258 | let sect = PmtSection::from_bytes(&data).unwrap(); 259 | let mut iter = sect.descriptors::>(); 260 | assert_matches!(iter.next(), Some(Err(_))); 261 | assert_matches!(iter.next(), None); 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /testsrc.ts: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:f6823b6c4491f3205b6e9d4e313e1f5127c257dc228128005c9294bd023eeaeb 3 | size 51776140 4 | --------------------------------------------------------------------------------