Magic-number/taxonomy pass: central wire-format + sector + unit consts
- libfreemkv::consts: coding_type::* (ES coding-type bytes), pes_stream_id::* + PAYLOAD_RANGE, SECTOR_BYTES (usize) + SECTOR_BYTES_U64 (offset math) - replace bare wire-code/sector literals across disc, mpls, clpi, labels, m2ts_mux, ps, tsmux, file_sector_source, extract - remove two unreachable secondary-stream match arms in mpls parse_stream_entry
This commit is contained in:
+14
-14
@@ -6,7 +6,7 @@
|
|||||||
//!
|
//!
|
||||||
//! Reference: https://github.com/lw/BluRay/wiki/CLPI
|
//! Reference: https://github.com/lw/BluRay/wiki/CLPI
|
||||||
|
|
||||||
use crate::consts::{BD_SOURCE_PACKET_BYTES, SECTOR_BYTES};
|
use crate::consts::{BD_SOURCE_PACKET_BYTES, SECTOR_BYTES_U64};
|
||||||
use crate::disc::Extent;
|
use crate::disc::Extent;
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
|
|
||||||
@@ -175,8 +175,8 @@ impl ClipInfo {
|
|||||||
// a sub-sector-aligned range still spans every sector it touches.
|
// a sub-sector-aligned range still spans every sector it touches.
|
||||||
let start_byte = start_spn as u64 * BD_SOURCE_PACKET_BYTES as u64;
|
let start_byte = start_spn as u64 * BD_SOURCE_PACKET_BYTES as u64;
|
||||||
let end_byte = end_spn as u64 * BD_SOURCE_PACKET_BYTES as u64;
|
let end_byte = end_spn as u64 * BD_SOURCE_PACKET_BYTES as u64;
|
||||||
let start_sector = (start_byte / SECTOR_BYTES as u64) as u32;
|
let start_sector = (start_byte / SECTOR_BYTES_U64) as u32;
|
||||||
let end_sector = end_byte.div_ceil(SECTOR_BYTES as u64) as u32;
|
let end_sector = end_byte.div_ceil(SECTOR_BYTES_U64) as u32;
|
||||||
|
|
||||||
vec![Extent {
|
vec![Extent {
|
||||||
start_lba: start_sector, // relative to m2ts file start
|
start_lba: start_sector, // relative to m2ts file start
|
||||||
@@ -261,6 +261,7 @@ pub fn parse(data: &[u8]) -> Result<ClipInfo> {
|
|||||||
/// errors because the EP map is the primary CLPI output, and a corrupt
|
/// errors because the EP map is the primary CLPI output, and a corrupt
|
||||||
/// program_info shouldn't break sector-range lookups.
|
/// program_info shouldn't break sector-range lookups.
|
||||||
fn parse_program_info(data: &[u8]) -> Vec<ClpiStream> {
|
fn parse_program_info(data: &[u8]) -> Vec<ClpiStream> {
|
||||||
|
use crate::consts::coding_type as c;
|
||||||
let mut out = Vec::new();
|
let mut out = Vec::new();
|
||||||
if data.len() < 6 {
|
if data.len() < 6 {
|
||||||
return out;
|
return out;
|
||||||
@@ -299,16 +300,15 @@ fn parse_program_info(data: &[u8]) -> Vec<ClpiStream> {
|
|||||||
let mut language = String::new();
|
let mut language = String::new();
|
||||||
|
|
||||||
match coding_type {
|
match coding_type {
|
||||||
// Video — MPEG-2 (0x02), H.264 (0x1B), HEVC (0x24)
|
// Video — MPEG-2, H.264, HEVC
|
||||||
0x02 | 0x1B | 0x24 => {
|
c::MPEG2_VIDEO | c::H264 | c::HEVC => {
|
||||||
if sci.len() >= 2 {
|
if sci.len() >= 2 {
|
||||||
video_format = (sci[1] >> 4) & 0x0F;
|
video_format = (sci[1] >> 4) & 0x0F;
|
||||||
video_rate = sci[1] & 0x0F;
|
video_rate = sci[1] & 0x0F;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Primary audio — LPCM(0x80), AC-3(0x81), DTS(0x82),
|
// Primary audio — LPCM, AC-3, DTS, TrueHD, AC-3+, DTS-HD HR, DTS-HD MA
|
||||||
// TrueHD(0x83), AC-3+(0x84), DTS-HD(0x85), DTS-HD MA(0x86)
|
c::LPCM..=c::DTS_HD_MA => {
|
||||||
0x80..=0x86 => {
|
|
||||||
if sci.len() >= 2 {
|
if sci.len() >= 2 {
|
||||||
audio_format = (sci[1] >> 4) & 0x0F;
|
audio_format = (sci[1] >> 4) & 0x0F;
|
||||||
audio_rate = sci[1] & 0x0F;
|
audio_rate = sci[1] & 0x0F;
|
||||||
@@ -317,8 +317,8 @@ fn parse_program_info(data: &[u8]) -> Vec<ClpiStream> {
|
|||||||
language = String::from_utf8_lossy(&sci[2..5]).to_string();
|
language = String::from_utf8_lossy(&sci[2..5]).to_string();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Secondary audio (0xA1 AC-3+, 0xA2 DTS-HD)
|
// Secondary audio (AC-3+ secondary, DTS-HD secondary)
|
||||||
0xA1 | 0xA2 => {
|
c::AC3_PLUS_SECONDARY | c::DTS_HD_SECONDARY => {
|
||||||
if sci.len() >= 2 {
|
if sci.len() >= 2 {
|
||||||
audio_format = (sci[1] >> 4) & 0x0F;
|
audio_format = (sci[1] >> 4) & 0x0F;
|
||||||
audio_rate = sci[1] & 0x0F;
|
audio_rate = sci[1] & 0x0F;
|
||||||
@@ -327,8 +327,8 @@ fn parse_program_info(data: &[u8]) -> Vec<ClpiStream> {
|
|||||||
language = String::from_utf8_lossy(&sci[2..5]).to_string();
|
language = String::from_utf8_lossy(&sci[2..5]).to_string();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// PG (0x90), IG (0x91): coding_type + 3-byte language [+ char_code for PG]
|
// PG, IG: coding_type + 3-byte language [+ char_code for PG]
|
||||||
0x90 | 0x91 => {
|
c::PG | c::IG => {
|
||||||
if sci.len() >= 4 {
|
if sci.len() >= 4 {
|
||||||
language = String::from_utf8_lossy(&sci[1..4]).to_string();
|
language = String::from_utf8_lossy(&sci[1..4]).to_string();
|
||||||
}
|
}
|
||||||
@@ -1125,8 +1125,8 @@ mod tests {
|
|||||||
let end_spn = big_spn as u64;
|
let end_spn = big_spn as u64;
|
||||||
let start_byte = start_spn * BD_SOURCE_PACKET_BYTES as u64;
|
let start_byte = start_spn * BD_SOURCE_PACKET_BYTES as u64;
|
||||||
let end_byte = end_spn * BD_SOURCE_PACKET_BYTES as u64;
|
let end_byte = end_spn * BD_SOURCE_PACKET_BYTES as u64;
|
||||||
let start_sector = (start_byte / SECTOR_BYTES as u64) as u32;
|
let start_sector = (start_byte / SECTOR_BYTES_U64) as u32;
|
||||||
let end_sector = end_byte.div_ceil(SECTOR_BYTES as u64) as u32;
|
let end_sector = end_byte.div_ceil(SECTOR_BYTES_U64) as u32;
|
||||||
assert_eq!(extents[0].start_lba, start_sector);
|
assert_eq!(extents[0].start_lba, start_sector);
|
||||||
assert_eq!(extents[0].sector_count, end_sector - start_sector);
|
assert_eq!(extents[0].sector_count, end_sector - start_sector);
|
||||||
// Concretely: 0x20000 × 192 / 2048 = 12288 sectors.
|
// Concretely: 0x20000 × 192 / 2048 = 12288 sectors.
|
||||||
|
|||||||
@@ -8,8 +8,18 @@
|
|||||||
|
|
||||||
/// Bytes per logical sector on every optical medium freemkv reads
|
/// Bytes per logical sector on every optical medium freemkv reads
|
||||||
/// (Blu-ray, DVD-Video, CD-ROM Mode 1). Universal — hence unprefixed.
|
/// (Blu-ray, DVD-Video, CD-ROM Mode 1). Universal — hence unprefixed.
|
||||||
|
///
|
||||||
|
/// `usize` because its dominant use is buffer sizing and slice indexing, where
|
||||||
|
/// Rust *requires* `usize` (`vec![0u8; SECTOR_BYTES]`, `buf.len() < SECTOR_BYTES`).
|
||||||
|
/// For byte-offset / capacity arithmetic — which is `u64` because a disc can
|
||||||
|
/// exceed 4 GiB — use [`SECTOR_BYTES_U64`] instead of casting at each site.
|
||||||
pub const SECTOR_BYTES: usize = 2048;
|
pub const SECTOR_BYTES: usize = 2048;
|
||||||
|
|
||||||
|
/// [`SECTOR_BYTES`] as `u64`, for byte-offset and capacity arithmetic. The
|
||||||
|
/// single `usize → u64` boundary cast lives here, once, so offset math across
|
||||||
|
/// the workspace reads as `sectors * SECTOR_BYTES_U64` with no per-site cast.
|
||||||
|
pub const SECTOR_BYTES_U64: u64 = SECTOR_BYTES as u64;
|
||||||
|
|
||||||
/// Bytes per MPEG-2 transport-stream packet. Common to all MPEG-TS, not just
|
/// Bytes per MPEG-2 transport-stream packet. Common to all MPEG-TS, not just
|
||||||
/// Blu-ray — prefixed by the format, not a disc type.
|
/// Blu-ray — prefixed by the format, not a disc type.
|
||||||
pub const TS_PACKET_BYTES: usize = 188;
|
pub const TS_PACKET_BYTES: usize = 188;
|
||||||
@@ -31,3 +41,80 @@ pub const TS_PAYLOAD_BYTES: usize = TS_PACKET_BYTES - TS_HEADER_BYTES;
|
|||||||
/// prefixed with the [`BD_TIMESTAMP_PREFIX_BYTES`] arrival-timestamp header.
|
/// prefixed with the [`BD_TIMESTAMP_PREFIX_BYTES`] arrival-timestamp header.
|
||||||
/// A BDAV/M2TS construct only — DVD VOBs have no source packets — hence `BD_`.
|
/// A BDAV/M2TS construct only — DVD VOBs have no source packets — hence `BD_`.
|
||||||
pub const BD_SOURCE_PACKET_BYTES: usize = TS_PACKET_BYTES + BD_TIMESTAMP_PREFIX_BYTES;
|
pub const BD_SOURCE_PACKET_BYTES: usize = TS_PACKET_BYTES + BD_TIMESTAMP_PREFIX_BYTES;
|
||||||
|
|
||||||
|
/// Elementary-stream coding-type codes — the single source of truth for the
|
||||||
|
/// byte that identifies a stream's codec.
|
||||||
|
///
|
||||||
|
/// This is one registry used in two places that share the same value space:
|
||||||
|
/// the MPEG-TS PMT `stream_type` (ISO/IEC 13818-1 Table 2-34) and the Blu-ray
|
||||||
|
/// STN/CLPI `stream_coding_type` (BD-ROM Part 3). The standardized video codes
|
||||||
|
/// (`0x02`, `0x1B`, `0x24`, `0xEA`) are ISO assignments; the `0x80..=0xA2`
|
||||||
|
/// audio/graphics codes sit in the ISO user-private range and follow the
|
||||||
|
/// Blu-ray Disc Association / ATSC A/52 convention. Because every consumer
|
||||||
|
/// reads or writes this single byte, the family is unprefixed — the scope is
|
||||||
|
/// "any elementary stream freemkv parses or muxes".
|
||||||
|
///
|
||||||
|
/// Each constant is `u8`: the spec defines an 8-bit field and the code compares
|
||||||
|
/// it directly against a byte read from the buffer, so no casts are needed.
|
||||||
|
pub mod coding_type {
|
||||||
|
/// MPEG-2 video (ISO/IEC 13818-1 Table 2-34).
|
||||||
|
pub const MPEG2_VIDEO: u8 = 0x02;
|
||||||
|
/// H.264 / AVC video (ISO/IEC 13818-1 Table 2-34).
|
||||||
|
pub const H264: u8 = 0x1B;
|
||||||
|
/// HEVC / H.265 video (ISO/IEC 13818-1 Table 2-34, 2015 amendment).
|
||||||
|
pub const HEVC: u8 = 0x24;
|
||||||
|
/// SMPTE VC-1 video (BD-ROM convention, ISO user-private range).
|
||||||
|
pub const VC1: u8 = 0xEA;
|
||||||
|
|
||||||
|
/// LPCM audio (BD-ROM convention).
|
||||||
|
pub const LPCM: u8 = 0x80;
|
||||||
|
/// Dolby Digital (AC-3) audio (BD-ROM / ATSC A/52 convention).
|
||||||
|
pub const AC3: u8 = 0x81;
|
||||||
|
/// DTS audio (BD-ROM convention).
|
||||||
|
pub const DTS: u8 = 0x82;
|
||||||
|
/// Dolby TrueHD audio (BD-ROM convention).
|
||||||
|
pub const TRUEHD: u8 = 0x83;
|
||||||
|
/// Dolby Digital Plus (E-AC-3 / AC-3+) audio (BD-ROM convention).
|
||||||
|
pub const AC3_PLUS: u8 = 0x84;
|
||||||
|
/// DTS-HD High Resolution audio (BD-ROM Part 3-1).
|
||||||
|
pub const DTS_HD_HR: u8 = 0x85;
|
||||||
|
/// DTS-HD Master Audio (lossless) (BD-ROM Part 3-1).
|
||||||
|
pub const DTS_HD_MA: u8 = 0x86;
|
||||||
|
|
||||||
|
/// Presentation Graphics — PG subtitle stream (BD-ROM HDMV).
|
||||||
|
pub const PG: u8 = 0x90;
|
||||||
|
/// Interactive Graphics — IG / BD-J menu overlay, NOT a subtitle (BD-ROM HDMV).
|
||||||
|
pub const IG: u8 = 0x91;
|
||||||
|
/// Text subtitle stream (BD-ROM HDMV).
|
||||||
|
pub const TEXT_SUBTITLE: u8 = 0x92;
|
||||||
|
|
||||||
|
/// Secondary Dolby Digital Plus audio (BD-ROM convention).
|
||||||
|
pub const AC3_PLUS_SECONDARY: u8 = 0xA1;
|
||||||
|
/// Secondary DTS-HD audio (lossless MA, not lossy HR) (BD-ROM convention).
|
||||||
|
pub const DTS_HD_SECONDARY: u8 = 0xA2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// MPEG PES `stream_id` codes — the byte after the `00 00 01` start-code prefix
|
||||||
|
/// that identifies an elementary stream's role in a PES packet (ISO/IEC
|
||||||
|
/// 13818-1 Table 2-22). Shared by the program-stream demuxer and the TS/M2TS
|
||||||
|
/// muxers, so defined here once. Each is `u8` (matches the byte on the wire).
|
||||||
|
pub mod pes_stream_id {
|
||||||
|
/// Video stream (`110x xxxx`; freemkv emits the base id `0xE0`).
|
||||||
|
pub const VIDEO: u8 = 0xE0;
|
||||||
|
/// private_stream_1 — AC-3 / DTS / LPCM / PGS subtitle payloads.
|
||||||
|
pub const PRIVATE_STREAM_1: u8 = 0xBD;
|
||||||
|
/// padding_stream — stuffing bytes only, no payload to demux.
|
||||||
|
pub const PADDING_STREAM: u8 = 0xBE;
|
||||||
|
/// private_stream_2 — DVD navigation (PCI/DSI); carries no muxable ES.
|
||||||
|
pub const PRIVATE_STREAM_2: u8 = 0xBF;
|
||||||
|
|
||||||
|
/// Highest video stream_id — the `110x xxxx` video range tops out at 0xEF.
|
||||||
|
pub const VIDEO_MAX: u8 = 0xEF;
|
||||||
|
|
||||||
|
/// Inclusive range of every PES `stream_id` that carries demuxable payload:
|
||||||
|
/// [`PRIVATE_STREAM_1`] (0xBD) through [`VIDEO_MAX`] (0xEF) — i.e. private
|
||||||
|
/// stream 1/2, padding, MPEG audio (0xC0-0xDF) and video (0xE0-0xEF). The
|
||||||
|
/// pack (0xBA), system-header (0xBB) and program-end (0xB9) codes sit below
|
||||||
|
/// this range and are deliberately excluded: they're structural, not ES.
|
||||||
|
pub const PAYLOAD_RANGE: core::ops::RangeInclusive<u8> = PRIVATE_STREAM_1..=VIDEO_MAX;
|
||||||
|
}
|
||||||
|
|||||||
+3
-3
@@ -24,7 +24,7 @@ use std::io::Write;
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
use crate::consts::SECTOR_BYTES;
|
use crate::consts::{SECTOR_BYTES, SECTOR_BYTES_U64};
|
||||||
/// AACS aligned unit = 3 sectors / 6144 bytes. Content reads are issued in
|
/// AACS aligned unit = 3 sectors / 6144 bytes. Content reads are issued in
|
||||||
/// multiples of this so the decrypt step always sees whole units.
|
/// multiples of this so the decrypt step always sees whole units.
|
||||||
const AACS_UNIT_SECTORS: u32 = 3;
|
const AACS_UNIT_SECTORS: u32 = 3;
|
||||||
@@ -282,7 +282,7 @@ impl Disc {
|
|||||||
for &(abs_lba, byte_len) in &pf.extents {
|
for &(abs_lba, byte_len) in &pf.extents {
|
||||||
extents.push(crate::disc::Extent {
|
extents.push(crate::disc::Extent {
|
||||||
start_lba: abs_lba,
|
start_lba: abs_lba,
|
||||||
sector_count: (byte_len as u64).div_ceil(SECTOR_BYTES as u64) as u32,
|
sector_count: (byte_len as u64).div_ceil(SECTOR_BYTES_U64) as u32,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -467,7 +467,7 @@ fn extract_one_file<S: SectorSource>(
|
|||||||
// the per-extent re-anchoring in the mux read paths
|
// the per-extent re-anchoring in the mux read paths
|
||||||
// (`mux/disc.rs`, `sector/prefetched.rs`). No-op for CSS / None.
|
// (`mux/disc.rs`, `sector/prefetched.rs`). No-op for CSS / None.
|
||||||
dec.set_unit_base(abs_lba);
|
dec.set_unit_base(abs_lba);
|
||||||
let sectors = (byte_len as u64).div_ceil(SECTOR_BYTES as u64) as u32;
|
let sectors = (byte_len as u64).div_ceil(SECTOR_BYTES_U64) as u32;
|
||||||
let mut sector_off: u32 = 0;
|
let mut sector_off: u32 = 0;
|
||||||
while sector_off < sectors {
|
while sector_off < sectors {
|
||||||
let mut batch = (sectors - sector_off).min(READ_BATCH_SECTORS);
|
let mut batch = (sectors - sector_off).min(READ_BATCH_SECTORS);
|
||||||
|
|||||||
+24
-23
@@ -627,28 +627,29 @@ impl Codec {
|
|||||||
];
|
];
|
||||||
|
|
||||||
pub(crate) fn from_coding_type(ct: u8) -> Self {
|
pub(crate) fn from_coding_type(ct: u8) -> Self {
|
||||||
|
use crate::consts::coding_type as c;
|
||||||
match ct {
|
match ct {
|
||||||
0x24 => Codec::Hevc,
|
c::HEVC => Codec::Hevc,
|
||||||
0x1B => Codec::H264,
|
c::H264 => Codec::H264,
|
||||||
0xEA => Codec::Vc1,
|
c::VC1 => Codec::Vc1,
|
||||||
0x02 => Codec::Mpeg2,
|
c::MPEG2_VIDEO => Codec::Mpeg2,
|
||||||
0x83 => Codec::TrueHd,
|
c::TRUEHD => Codec::TrueHd,
|
||||||
0x86 => Codec::DtsHdMa,
|
c::DTS_HD_MA => Codec::DtsHdMa,
|
||||||
0x85 => Codec::DtsHdHr,
|
c::DTS_HD_HR => Codec::DtsHdHr,
|
||||||
0x82 => Codec::Dts,
|
c::DTS => Codec::Dts,
|
||||||
0x81 => Codec::Ac3,
|
c::AC3 => Codec::Ac3,
|
||||||
0x84 | 0xA1 => Codec::Ac3Plus,
|
c::AC3_PLUS | c::AC3_PLUS_SECONDARY => Codec::Ac3Plus,
|
||||||
0x80 => Codec::Lpcm,
|
c::LPCM => Codec::Lpcm,
|
||||||
// 0x86 (primary) / 0xA2 (secondary) are the DTS-HD MA
|
// DTS_HD_MA (primary 0x86) / DTS_HD_SECONDARY (0xA2) are the
|
||||||
// lossless pair, parallel to 0x81/0xA1 for AC-3. 0xA2 is
|
// DTS-HD MA lossless pair, parallel to AC3/AC3_PLUS_SECONDARY for
|
||||||
// lossless MA, not lossy HR.
|
// AC-3. The secondary code is lossless MA, not lossy HR.
|
||||||
0xA2 => Codec::DtsHdMa,
|
c::DTS_HD_SECONDARY => Codec::DtsHdMa,
|
||||||
// 0x90 = Presentation Graphics (PG / subtitles). 0x91 = Interactive
|
// PG (0x90) = Presentation Graphics (subtitles). IG (0x91, menus)
|
||||||
// Graphics (IG / menus) and 0x92 = Text subtitles are distinct HDMV
|
// and TEXT_SUBTITLE (0x92) are distinct HDMV coding types and are
|
||||||
// coding types and are NOT PG subtitle streams; only 0x90 maps to
|
// NOT PG subtitle streams; only PG maps to Pgs. IG falls through to
|
||||||
// Pgs. IG (0x91) falls through to Unknown so the PMT/STN walker drops
|
// Unknown so the PMT/STN walker drops it rather than surfacing a
|
||||||
// it rather than surfacing a bogus PGS subtitle track for a menu ES.
|
// bogus PGS subtitle track for a menu ES.
|
||||||
0x90 => Codec::Pgs,
|
c::PG => Codec::Pgs,
|
||||||
ct => Codec::Unknown(ct),
|
ct => Codec::Unknown(ct),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5068,8 +5069,8 @@ mod tests {
|
|||||||
let mf = Mapfile::load(&disc.mapfile_for(&iso_path)).expect("load mapfile");
|
let mf = Mapfile::load(&disc.mapfile_for(&iso_path)).expect("load mapfile");
|
||||||
let good = mf.ranges_with(&[SectorStatus::Finished]);
|
let good = mf.ranges_with(&[SectorStatus::Finished]);
|
||||||
let bad_ranges = mf.ranges_with(&[SectorStatus::NonTrimmed]);
|
let bad_ranges = mf.ranges_with(&[SectorStatus::NonTrimmed]);
|
||||||
let disc_bytes = sectors as u64 * 2048;
|
const SEC: u64 = crate::consts::SECTOR_BYTES_U64;
|
||||||
const SEC: u64 = crate::consts::SECTOR_BYTES as u64;
|
let disc_bytes = sectors as u64 * SEC;
|
||||||
|
|
||||||
// The first failing batch starts at LBA 320; everything before it read
|
// The first failing batch starts at LBA 320; everything before it read
|
||||||
// cleanly and must be Finished.
|
// cleanly and must be Finished.
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ use std::path::Path;
|
|||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use crate::sector::SectorSource;
|
use crate::sector::SectorSource;
|
||||||
|
|
||||||
use crate::consts::SECTOR_BYTES;
|
use crate::consts::{SECTOR_BYTES, SECTOR_BYTES_U64};
|
||||||
|
|
||||||
/// Bytes-read threshold per `posix_fadvise(DONTNEED)` drop on the
|
/// Bytes-read threshold per `posix_fadvise(DONTNEED)` drop on the
|
||||||
/// read side. Mirrors `WRITEBACK_CHUNK_BYTES` so the read-side page
|
/// read side. Mirrors `WRITEBACK_CHUNK_BYTES` so the read-side page
|
||||||
@@ -134,7 +134,7 @@ impl FileSectorSource {
|
|||||||
.metadata()
|
.metadata()
|
||||||
.map_err(|e| Error::IoError { source: e })?
|
.map_err(|e| Error::IoError { source: e })?
|
||||||
.len();
|
.len();
|
||||||
let sectors = len / SECTOR_BYTES as u64;
|
let sectors = len / SECTOR_BYTES_U64;
|
||||||
if sectors > u32::MAX as u64 {
|
if sectors > u32::MAX as u64 {
|
||||||
return Err(Error::IsoTooLarge {
|
return Err(Error::IsoTooLarge {
|
||||||
path: path.to_string_lossy().into_owned(),
|
path: path.to_string_lossy().into_owned(),
|
||||||
@@ -180,7 +180,7 @@ impl SectorSource for FileSectorSource {
|
|||||||
if count == 0 {
|
if count == 0 {
|
||||||
return Ok(0);
|
return Ok(0);
|
||||||
}
|
}
|
||||||
let offset = lba as u64 * SECTOR_BYTES as u64;
|
let offset = lba as u64 * SECTOR_BYTES_U64;
|
||||||
self.file
|
self.file
|
||||||
.seek(SeekFrom::Start(offset))
|
.seek(SeekFrom::Start(offset))
|
||||||
.map_err(|e| Error::IoError { source: e })?;
|
.map_err(|e| Error::IoError { source: e })?;
|
||||||
@@ -494,7 +494,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn dontneed_eviction_does_not_affect_data() {
|
fn dontneed_eviction_does_not_affect_data() {
|
||||||
// 32 MiB default chunk = 16384 sectors; read a bit past it.
|
// 32 MiB default chunk = 16384 sectors; read a bit past it.
|
||||||
let total = (READ_DROP_CHUNK_BYTES_DEFAULT / SECTOR_BYTES as u64) as u32 + 64;
|
let total = (READ_DROP_CHUNK_BYTES_DEFAULT / SECTOR_BYTES_U64) as u32 + 64;
|
||||||
let dir = tempdir().unwrap();
|
let dir = tempdir().unwrap();
|
||||||
let path = dir.path().join("drop.iso");
|
let path = dir.path().join("drop.iso");
|
||||||
make_iso(&path, total);
|
make_iso(&path, total);
|
||||||
|
|||||||
+6
-3
@@ -634,6 +634,7 @@ fn append_clpi_orphans(
|
|||||||
reader: &mut dyn SectorSource,
|
reader: &mut dyn SectorSource,
|
||||||
udf: &UdfFs,
|
udf: &UdfFs,
|
||||||
) -> usize {
|
) -> usize {
|
||||||
|
use crate::consts::coding_type as c;
|
||||||
// Index existing labels by PID — but StreamLabel doesn't carry
|
// Index existing labels by PID — but StreamLabel doesn't carry
|
||||||
// PID. Index by (type, language, codec_hint) tuple instead; this
|
// PID. Index by (type, language, codec_hint) tuple instead; this
|
||||||
// is fuzzier than PID matching but the only signal available
|
// is fuzzier than PID matching but the only signal available
|
||||||
@@ -679,9 +680,11 @@ fn append_clpi_orphans(
|
|||||||
// Interactive Graphics (BD-J menu overlay), NOT a user-facing
|
// Interactive Graphics (BD-J menu overlay), NOT a user-facing
|
||||||
// subtitle — skip it, matching the MPLS path which drops IG.
|
// subtitle — skip it, matching the MPLS path which drops IG.
|
||||||
let stype = match s.coding_type {
|
let stype = match s.coding_type {
|
||||||
0x80..=0x86 | 0xA1 | 0xA2 => StreamLabelType::Audio,
|
c::LPCM..=c::DTS_HD_MA | c::AC3_PLUS_SECONDARY | c::DTS_HD_SECONDARY => {
|
||||||
0x90 => StreamLabelType::Subtitle,
|
StreamLabelType::Audio
|
||||||
_ => continue, // 0x91 IG / video / unknown — skip
|
}
|
||||||
|
c::PG => StreamLabelType::Subtitle,
|
||||||
|
_ => continue, // IG / video / unknown — skip
|
||||||
};
|
};
|
||||||
// Same dedup logic as MPLS: normalize language, build codec
|
// Same dedup logic as MPLS: normalize language, build codec
|
||||||
// hint, check against existing label set.
|
// hint, check against existing label set.
|
||||||
|
|||||||
@@ -227,21 +227,22 @@ pub(crate) fn language_display_name(iso: &str) -> String {
|
|||||||
/// bytes (the table covers everything the spec defines, but unknown
|
/// bytes (the table covers everything the spec defines, but unknown
|
||||||
/// values are still possible on malformed discs).
|
/// values are still possible on malformed discs).
|
||||||
pub(crate) fn codec_name(coding_type: u8) -> &'static str {
|
pub(crate) fn codec_name(coding_type: u8) -> &'static str {
|
||||||
|
use crate::consts::coding_type as c;
|
||||||
match coding_type {
|
match coding_type {
|
||||||
0x02 => "MPEG-2",
|
c::MPEG2_VIDEO => "MPEG-2",
|
||||||
0x1B => "H.264",
|
c::H264 => "H.264",
|
||||||
0x24 => "HEVC",
|
c::HEVC => "HEVC",
|
||||||
0x80 => "LPCM",
|
c::LPCM => "LPCM",
|
||||||
0x81 => "AC-3",
|
c::AC3 => "AC-3",
|
||||||
0x82 => "DTS",
|
c::DTS => "DTS",
|
||||||
0x83 => "TrueHD",
|
c::TRUEHD => "TrueHD",
|
||||||
0x84 => "AC-3+",
|
c::AC3_PLUS => "AC-3+",
|
||||||
0x85 => "DTS-HD HR", // BD-ROM Part 3-1: 0x85 = DTS-HD High Resolution
|
c::DTS_HD_HR => "DTS-HD HR", // BD-ROM Part 3-1: 0x85 = DTS-HD High Resolution
|
||||||
0x86 => "DTS-HD MA",
|
c::DTS_HD_MA => "DTS-HD MA",
|
||||||
0x90 => "PG",
|
c::PG => "PG",
|
||||||
0x91 => "IG",
|
c::IG => "IG",
|
||||||
0xA1 => "AC-3+ Secondary",
|
c::AC3_PLUS_SECONDARY => "AC-3+ Secondary",
|
||||||
0xA2 => "DTS-HD Secondary",
|
c::DTS_HD_SECONDARY => "DTS-HD Secondary",
|
||||||
_ => "",
|
_ => "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-34
@@ -179,7 +179,9 @@ pub fn parse(data: &[u8]) -> Result<Playlist> {
|
|||||||
}
|
}
|
||||||
// PG subtitles
|
// PG subtitles
|
||||||
for _ in 0..n_pg {
|
for _ in 0..n_pg {
|
||||||
if let Some((entry, next)) = parse_stream_entry(item, spos, STREAM_CATEGORY_PG_SUBTITLE) {
|
if let Some((entry, next)) =
|
||||||
|
parse_stream_entry(item, spos, STREAM_CATEGORY_PG_SUBTITLE)
|
||||||
|
{
|
||||||
streams.push(entry);
|
streams.push(entry);
|
||||||
spos = next;
|
spos = next;
|
||||||
} else {
|
} else {
|
||||||
@@ -196,7 +198,9 @@ pub fn parse(data: &[u8]) -> Result<Playlist> {
|
|||||||
}
|
}
|
||||||
// Secondary audio
|
// Secondary audio
|
||||||
for _ in 0..n_sec_audio {
|
for _ in 0..n_sec_audio {
|
||||||
if let Some((mut entry, next)) = parse_stream_entry(item, spos, STREAM_CATEGORY_AUDIO) {
|
if let Some((mut entry, next)) =
|
||||||
|
parse_stream_entry(item, spos, STREAM_CATEGORY_AUDIO)
|
||||||
|
{
|
||||||
entry.stream_type = 5;
|
entry.stream_type = 5;
|
||||||
entry.secondary = true;
|
entry.secondary = true;
|
||||||
streams.push(entry);
|
streams.push(entry);
|
||||||
@@ -213,7 +217,9 @@ pub fn parse(data: &[u8]) -> Result<Playlist> {
|
|||||||
}
|
}
|
||||||
// Secondary video (PiP)
|
// Secondary video (PiP)
|
||||||
for _ in 0..n_sec_video {
|
for _ in 0..n_sec_video {
|
||||||
if let Some((mut entry, next)) = parse_stream_entry(item, spos, STREAM_CATEGORY_VIDEO) {
|
if let Some((mut entry, next)) =
|
||||||
|
parse_stream_entry(item, spos, STREAM_CATEGORY_VIDEO)
|
||||||
|
{
|
||||||
entry.stream_type = 6;
|
entry.stream_type = 6;
|
||||||
entry.secondary = true;
|
entry.secondary = true;
|
||||||
streams.push(entry);
|
streams.push(entry);
|
||||||
@@ -240,7 +246,9 @@ pub fn parse(data: &[u8]) -> Result<Playlist> {
|
|||||||
}
|
}
|
||||||
// Secondary PG (PiP subtitles) — must consume to keep spos aligned
|
// Secondary PG (PiP subtitles) — must consume to keep spos aligned
|
||||||
for _ in 0..n_pip_pg {
|
for _ in 0..n_pip_pg {
|
||||||
if let Some((mut entry, next)) = parse_stream_entry(item, spos, STREAM_CATEGORY_PG_SUBTITLE) {
|
if let Some((mut entry, next)) =
|
||||||
|
parse_stream_entry(item, spos, STREAM_CATEGORY_PG_SUBTITLE)
|
||||||
|
{
|
||||||
entry.secondary = true;
|
entry.secondary = true;
|
||||||
streams.push(entry);
|
streams.push(entry);
|
||||||
// Skip reference data: num_refs(1) + reserved(1) + refs + padding
|
// Skip reference data: num_refs(1) + reserved(1) + refs + padding
|
||||||
@@ -256,7 +264,9 @@ pub fn parse(data: &[u8]) -> Result<Playlist> {
|
|||||||
}
|
}
|
||||||
// Dolby Vision enhancement layer
|
// Dolby Vision enhancement layer
|
||||||
for _ in 0..n_dv {
|
for _ in 0..n_dv {
|
||||||
if let Some((mut entry, next)) = parse_stream_entry(item, spos, STREAM_CATEGORY_VIDEO) {
|
if let Some((mut entry, next)) =
|
||||||
|
parse_stream_entry(item, spos, STREAM_CATEGORY_VIDEO)
|
||||||
|
{
|
||||||
entry.stream_type = 7;
|
entry.stream_type = 7;
|
||||||
entry.secondary = true;
|
entry.secondary = true;
|
||||||
streams.push(entry);
|
streams.push(entry);
|
||||||
@@ -333,6 +343,7 @@ const STREAM_CATEGORY_PG_SUBTITLE: u8 = 3;
|
|||||||
const STREAM_CATEGORY_IG: u8 = 4;
|
const STREAM_CATEGORY_IG: u8 = 4;
|
||||||
|
|
||||||
fn parse_stream_entry(item: &[u8], pos: usize, stream_type: u8) -> Option<(StreamEntry, usize)> {
|
fn parse_stream_entry(item: &[u8], pos: usize, stream_type: u8) -> Option<(StreamEntry, usize)> {
|
||||||
|
use crate::consts::coding_type as c;
|
||||||
if pos + 2 > item.len() {
|
if pos + 2 > item.len() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@@ -387,22 +398,27 @@ fn parse_stream_entry(item: &[u8], pos: usize, stream_type: u8) -> Option<(Strea
|
|||||||
let mut color_space_val = 0u8;
|
let mut color_space_val = 0u8;
|
||||||
let mut language = String::new();
|
let mut language = String::new();
|
||||||
|
|
||||||
|
// `stream_type` here is the STN category passed by the caller, which is
|
||||||
|
// only ever a primary category (VIDEO/AUDIO/PG_SUBTITLE/IG). Secondary
|
||||||
|
// audio/video and the DV enhancement layer are parsed through their
|
||||||
|
// matching primary category (identical attribute layout) and re-tagged by
|
||||||
|
// the caller after this returns, so there are no secondary arms here.
|
||||||
match stream_type {
|
match stream_type {
|
||||||
1 => {
|
STREAM_CATEGORY_VIDEO => {
|
||||||
// Video: coding_type(1) + format_rate(1) + [hdr_info(1) if HEVC]
|
// Video: coding_type(1) + format_rate(1) + [hdr_info(1) if HEVC]
|
||||||
if sa.len() >= 2 {
|
if sa.len() >= 2 {
|
||||||
video_format = (sa[1] >> 4) & 0x0F;
|
video_format = (sa[1] >> 4) & 0x0F;
|
||||||
video_rate = sa[1] & 0x0F;
|
video_rate = sa[1] & 0x0F;
|
||||||
}
|
}
|
||||||
if coding_type == 0x24 && sa.len() > 2 {
|
if coding_type == c::HEVC && sa.len() > 2 {
|
||||||
dynamic_range = (sa[2] >> 4) & 0x0F;
|
dynamic_range = (sa[2] >> 4) & 0x0F;
|
||||||
color_space_val = sa[2] & 0x0F;
|
color_space_val = sa[2] & 0x0F;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
2 => {
|
STREAM_CATEGORY_AUDIO => {
|
||||||
// Audio: coding_type(1) + format_rate(1) + language(3)
|
// Audio: coding_type(1) + format_rate(1) + language(3)
|
||||||
// Exception: PGS (0x90/0x91) in audio slot uses PG layout: coding_type(1) + language(3)
|
// Exception: PG/IG in an audio slot uses PG layout: coding_type(1) + language(3)
|
||||||
if coding_type == 0x90 || coding_type == 0x91 {
|
if coding_type == c::PG || coding_type == c::IG {
|
||||||
if sa.len() >= 4 {
|
if sa.len() >= 4 {
|
||||||
language = String::from_utf8_lossy(&sa[1..4]).to_string();
|
language = String::from_utf8_lossy(&sa[1..4]).to_string();
|
||||||
}
|
}
|
||||||
@@ -416,35 +432,14 @@ fn parse_stream_entry(item: &[u8], pos: usize, stream_type: u8) -> Option<(Strea
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
3 => {
|
STREAM_CATEGORY_PG_SUBTITLE => {
|
||||||
// PG: coding_type(1) + language(3).
|
// PG: coding_type(1) + language(3).
|
||||||
// IG (type 4) is parsed only to advance spos and is then
|
// IG is parsed only to advance spos and is then discarded by the
|
||||||
// discarded by the caller, so it deliberately has no arm here.
|
// caller, so it deliberately has no arm here.
|
||||||
if sa.len() >= 4 {
|
if sa.len() >= 4 {
|
||||||
language = String::from_utf8_lossy(&sa[1..4]).to_string();
|
language = String::from_utf8_lossy(&sa[1..4]).to_string();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
5 => {
|
|
||||||
// Secondary audio: same as primary audio
|
|
||||||
if sa.len() >= 2 {
|
|
||||||
audio_format = (sa[1] >> 4) & 0x0F;
|
|
||||||
audio_rate = sa[1] & 0x0F;
|
|
||||||
}
|
|
||||||
if sa.len() >= 5 {
|
|
||||||
language = String::from_utf8_lossy(&sa[2..5]).to_string();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
6 | 7 => {
|
|
||||||
// Secondary video: same as primary video
|
|
||||||
if sa.len() >= 2 {
|
|
||||||
video_format = (sa[1] >> 4) & 0x0F;
|
|
||||||
video_rate = sa[1] & 0x0F;
|
|
||||||
}
|
|
||||||
if coding_type == 0x24 && sa.len() > 2 {
|
|
||||||
dynamic_range = (sa[2] >> 4) & 0x0F;
|
|
||||||
color_space_val = sa[2] & 0x0F;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+18
-5
@@ -76,15 +76,18 @@ const PCR_INTERVAL_PACKETS: u64 = 40;
|
|||||||
/// the picture it timestamps. 200 ms in 90 kHz ticks.
|
/// the picture it timestamps. 200 ms in 90 kHz ticks.
|
||||||
const PCR_LEAD_90KHZ: u64 = 90_000 / 5;
|
const PCR_LEAD_90KHZ: u64 = 90_000 / 5;
|
||||||
|
|
||||||
|
// PMT stream-type codes. These are the same elementary-stream coding-type
|
||||||
|
// registry as the parse side; the single source of truth is `consts::coding_type`.
|
||||||
|
use crate::consts::coding_type;
|
||||||
/// HEVC stream-type code, ISO/IEC 13818-1 Table 2-34 (2015 amendment).
|
/// HEVC stream-type code, ISO/IEC 13818-1 Table 2-34 (2015 amendment).
|
||||||
const STREAM_TYPE_HEVC: u8 = 0x24;
|
const STREAM_TYPE_HEVC: u8 = coding_type::HEVC;
|
||||||
/// AC-3 / E-AC-3. Not an ISO assignment — sits in the user-private
|
/// AC-3 / E-AC-3. Not an ISO assignment — sits in the user-private
|
||||||
/// 0x80-0xFF range and is the Blu-ray Disc Association / ATSC A/52
|
/// 0x80-0xFF range and is the Blu-ray Disc Association / ATSC A/52
|
||||||
/// convention.
|
/// convention.
|
||||||
const STREAM_TYPE_AC3: u8 = 0x81;
|
const STREAM_TYPE_AC3: u8 = coding_type::AC3;
|
||||||
/// Dolby TrueHD. Also a private/BD-conventional value in the
|
/// Dolby TrueHD. Also a private/BD-conventional value in the
|
||||||
/// user-private 0x80-0xFF range, not an ISO assignment.
|
/// user-private 0x80-0xFF range, not an ISO assignment.
|
||||||
const STREAM_TYPE_TRUEHD: u8 = 0x83;
|
const STREAM_TYPE_TRUEHD: u8 = coding_type::TRUEHD;
|
||||||
|
|
||||||
/// Audio codec hint for [`M2tsMux::new`] / [`M2tsMux::set_audio`]. The
|
/// Audio codec hint for [`M2tsMux::new`] / [`M2tsMux::set_audio`]. The
|
||||||
/// muxer needs to know the codec to pick the right PMT `stream_type`
|
/// muxer needs to know the codec to pick the right PMT `stream_type`
|
||||||
@@ -420,7 +423,12 @@ impl<W: Write> M2tsMux<W> {
|
|||||||
|
|
||||||
/// Build a PES packet for a video access unit.
|
/// Build a PES packet for a video access unit.
|
||||||
fn build_video_pes(pts_90k: u64, es: &[u8]) -> Vec<u8> {
|
fn build_video_pes(pts_90k: u64, es: &[u8]) -> Vec<u8> {
|
||||||
build_pes_packet(0xE0, pts_90k, es, /* length_in_header */ false)
|
build_pes_packet(
|
||||||
|
crate::consts::pes_stream_id::VIDEO,
|
||||||
|
pts_90k,
|
||||||
|
es,
|
||||||
|
/* length_in_header */ false,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build a PES packet for an audio access unit.
|
/// Build a PES packet for an audio access unit.
|
||||||
@@ -430,7 +438,12 @@ fn build_audio_pes(pts_90k: u64, es: &[u8]) -> Vec<u8> {
|
|||||||
// start code. For an access unit larger than ~64 KiB (rare — e.g. a
|
// start code. For an access unit larger than ~64 KiB (rare — e.g. a
|
||||||
// large TrueHD frame) the length field falls back to the unbounded
|
// large TrueHD frame) the length field falls back to the unbounded
|
||||||
// (0x0000) form, which most demuxers tolerate for private_stream_1.
|
// (0x0000) form, which most demuxers tolerate for private_stream_1.
|
||||||
build_pes_packet(0xBD, pts_90k, es, /* length_in_header */ true)
|
build_pes_packet(
|
||||||
|
crate::consts::pes_stream_id::PRIVATE_STREAM_1,
|
||||||
|
pts_90k,
|
||||||
|
es,
|
||||||
|
/* length_in_header */ true,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_pes_packet(stream_id: u8, pts_90k: u64, es: &[u8], length_in_header: bool) -> Vec<u8> {
|
fn build_pes_packet(stream_id: u8, pts_90k: u64, es: &[u8], length_in_header: bool) -> Vec<u8> {
|
||||||
|
|||||||
+8
-8
@@ -23,10 +23,10 @@ const SYSTEM_HEADER_ID: u8 = 0xBB;
|
|||||||
const PROGRAM_END_ID: u8 = 0xB9;
|
const PROGRAM_END_ID: u8 = 0xB9;
|
||||||
|
|
||||||
/// Private stream 1 (AC3, DTS, LPCM, subtitles).
|
/// Private stream 1 (AC3, DTS, LPCM, subtitles).
|
||||||
const PRIVATE_STREAM_1: u8 = 0xBD;
|
const PRIVATE_STREAM_1: u8 = crate::consts::pes_stream_id::PRIVATE_STREAM_1;
|
||||||
/// Private stream 2 (0xBF) — DVD navigation (PCI/DSI). Carries no muxable
|
/// Private stream 2 (0xBF) — DVD navigation (PCI/DSI). Carries no muxable
|
||||||
/// elementary stream; expected to be dropped on every disc.
|
/// elementary stream; expected to be dropped on every disc.
|
||||||
const PRIVATE_STREAM_2: u8 = 0xBF;
|
const PRIVATE_STREAM_2: u8 = crate::consts::pes_stream_id::PRIVATE_STREAM_2;
|
||||||
|
|
||||||
/// Hard cap on the demuxer's reassembly buffer. A length-0 (unbounded) video
|
/// Hard cap on the demuxer's reassembly buffer. A length-0 (unbounded) video
|
||||||
/// PES is delimited by the next PS-layer boundary; if a corrupt stream declares
|
/// PES is delimited by the next PS-layer boundary; if a corrupt stream declares
|
||||||
@@ -107,8 +107,8 @@ impl PsPacket {
|
|||||||
/// mis-routing the packet.
|
/// mis-routing the packet.
|
||||||
pub fn dvd_pid(&self) -> Option<u16> {
|
pub fn dvd_pid(&self) -> Option<u16> {
|
||||||
match self.stream_id {
|
match self.stream_id {
|
||||||
0xE0..=0xEF => Some(DVD_VIDEO_PID),
|
crate::consts::pes_stream_id::VIDEO..=0xEF => Some(DVD_VIDEO_PID),
|
||||||
0xBD => {
|
PRIVATE_STREAM_1 => {
|
||||||
let sub = self.sub_stream_id?;
|
let sub = self.sub_stream_id?;
|
||||||
dvd_audio_pid(sub).or_else(|| dvd_subtitle_pid(sub))
|
dvd_audio_pid(sub).or_else(|| dvd_subtitle_pid(sub))
|
||||||
}
|
}
|
||||||
@@ -348,8 +348,8 @@ fn find_ps_boundary(data: &[u8], from: usize) -> Option<usize> {
|
|||||||
fn is_pes_stream_id(id: u8) -> bool {
|
fn is_pes_stream_id(id: u8) -> bool {
|
||||||
// Video: 0xE0-0xEF, MPEG audio: 0xC0-0xDF, private stream 1: 0xBD,
|
// Video: 0xE0-0xEF, MPEG audio: 0xC0-0xDF, private stream 1: 0xBD,
|
||||||
// private stream 2: 0xBF, padding: 0xBE, ECM/EMM etc.
|
// private stream 2: 0xBF, padding: 0xBE, ECM/EMM etc.
|
||||||
// We parse anything in the PES range.
|
// We parse anything in the payload-bearing PES range.
|
||||||
matches!(id, 0xBD..=0xEF)
|
crate::consts::pes_stream_id::PAYLOAD_RANGE.contains(&id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a single PES packet from a byte slice that starts at the start code.
|
/// Parse a single PES packet from a byte slice that starts at the start code.
|
||||||
@@ -365,12 +365,12 @@ fn parse_pes_packet(data: &[u8]) -> Option<PsPacket> {
|
|||||||
let stream_id = data[3];
|
let stream_id = data[3];
|
||||||
|
|
||||||
// Padding stream — skip entirely.
|
// Padding stream — skip entirely.
|
||||||
if stream_id == 0xBE {
|
if stream_id == crate::consts::pes_stream_id::PADDING_STREAM {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Streams without standard PES header extension.
|
// Streams without standard PES header extension.
|
||||||
if stream_id == 0xBF {
|
if stream_id == PRIVATE_STREAM_2 {
|
||||||
let payload = if data.len() > 6 { &data[6..] } else { &[] };
|
let payload = if data.len() > 6 { &data[6..] } else { &[] };
|
||||||
return Some(PsPacket {
|
return Some(PsPacket {
|
||||||
stream_id,
|
stream_id,
|
||||||
|
|||||||
+4
-3
@@ -312,11 +312,12 @@ impl<W: Write> TsMuxer<W> {
|
|||||||
|
|
||||||
/// Build a PES packet header for a BD stream.
|
/// Build a PES packet header for a BD stream.
|
||||||
fn build_pes_header(pid: u16, pts_90k: u64, data_len: usize) -> Vec<u8> {
|
fn build_pes_header(pid: u16, pts_90k: u64, data_len: usize) -> Vec<u8> {
|
||||||
|
use crate::consts::pes_stream_id;
|
||||||
// Determine stream_id from PID range
|
// Determine stream_id from PID range
|
||||||
let stream_id: u8 = if is_video_pid(pid) {
|
let stream_id: u8 = if is_video_pid(pid) {
|
||||||
0xE0 // video
|
pes_stream_id::VIDEO
|
||||||
} else {
|
} else {
|
||||||
0xBD // audio, PGS subtitle, or default (private stream 1)
|
pes_stream_id::PRIVATE_STREAM_1 // audio, PGS subtitle, or default
|
||||||
};
|
};
|
||||||
|
|
||||||
let pes_data_len = data_len + 8; // 3 header bytes + 5 PTS bytes + data
|
let pes_data_len = data_len + 8; // 3 header bytes + 5 PTS bytes + data
|
||||||
@@ -332,7 +333,7 @@ fn build_pes_header(pid: u16, pts_90k: u64, data_len: usize) -> Vec<u8> {
|
|||||||
// video; `write_frame` splits oversized 0xBD access units so a private
|
// video; `write_frame` splits oversized 0xBD access units so a private
|
||||||
// stream always fits a bounded u16 length here. The `> 65535` arm
|
// stream always fits a bounded u16 length here. The `> 65535` arm
|
||||||
// remains a defensive fallback for video only.
|
// remains a defensive fallback for video only.
|
||||||
if stream_id == 0xE0 || pes_data_len > 65535 {
|
if stream_id == pes_stream_id::VIDEO || pes_data_len > u16::MAX as usize {
|
||||||
header.push(0x00);
|
header.push(0x00);
|
||||||
header.push(0x00);
|
header.push(0x00);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user