mux/mkv: signal Dolby Vision via dvcC BlockAdditionMapping
With the EL now carried (mpls PID fix), the MKV writer tags the Dolby Vision layer so players/mediainfo recognise it: a BlockAdditionMapping (0x41E4) with BlockAddIDType 'dvcC' and a DOVIDecoderConfigurationRecord (profile 7, bl+el+rpu present) on the DV-layer video track. Disc Profile 7 dual-layer is preserved losslessly as a second video track (archival-correct, MakeMKV-equivalent); M2TS carries the EL as a native second video PID with no extra signaling needed. Adds dolby_vision_config() + a profile-7 dvcC unit test. End-to-end DV recognition (mediainfo / playback) to be confirmed on a full rip.
This commit is contained in:
@@ -362,6 +362,12 @@ pub const MATRIX_COEFFICIENTS: u32 = 0x55B1;
|
|||||||
pub const PRIMARIES: u32 = 0x55BB;
|
pub const PRIMARIES: u32 = 0x55BB;
|
||||||
pub const RANGE: u32 = 0x55B9;
|
pub const RANGE: u32 = 0x55B9;
|
||||||
|
|
||||||
|
// Dolby Vision — BlockAdditionMapping carries the DOVIDecoderConfigurationRecord
|
||||||
|
// (dvcC) so players / mediainfo recognise the track as Dolby Vision.
|
||||||
|
pub const BLOCK_ADDITION_MAPPING: u32 = 0x41E4;
|
||||||
|
pub const BLOCK_ADD_ID_TYPE: u32 = 0x41E7;
|
||||||
|
pub const BLOCK_ADD_ID_EXTRA_DATA: u32 = 0x41ED;
|
||||||
|
|
||||||
// Audio
|
// Audio
|
||||||
pub const AUDIO: u32 = 0xE1;
|
pub const AUDIO: u32 = 0xE1;
|
||||||
pub const SAMPLING_FREQUENCY: u32 = 0xB5;
|
pub const SAMPLING_FREQUENCY: u32 = 0xB5;
|
||||||
|
|||||||
@@ -34,6 +34,24 @@ pub struct MkvTrack {
|
|||||||
pub sample_rate: f64,
|
pub sample_rate: f64,
|
||||||
pub channels: u8,
|
pub channels: u8,
|
||||||
pub bit_depth: u8,
|
pub bit_depth: u8,
|
||||||
|
// Dolby Vision: the dvcC (DOVIDecoderConfigurationRecord) for the DV layer,
|
||||||
|
// emitted as a BlockAdditionMapping. `None` for non-DV tracks.
|
||||||
|
pub dv_config: Option<Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build a DOVIDecoderConfigurationRecord (dvcC) — 24 bytes — for the Matroska
|
||||||
|
/// BlockAdditionMapping. For disc Profile 7 dual-layer the base, enhancement,
|
||||||
|
/// and RPU are all present (lossless FEL/MEL preserved as a second track).
|
||||||
|
pub fn dolby_vision_config(profile: u8, level: u8, bl_compat_id: u8) -> Vec<u8> {
|
||||||
|
let mut v = vec![0u8; 24];
|
||||||
|
v[0] = 1; // dv_version_major
|
||||||
|
v[1] = 0; // dv_version_minor
|
||||||
|
// profile(7) | level(6) | rpu_present(1) | el_present(1) | bl_present(1)
|
||||||
|
v[2] = ((profile & 0x7F) << 1) | ((level >> 5) & 0x01);
|
||||||
|
v[3] = ((level & 0x1F) << 3) | (1 << 2) | (1 << 1) | 1; // rpu = el = bl = 1
|
||||||
|
v[4] = (bl_compat_id & 0x0F) << 4;
|
||||||
|
// v[5..24] reserved = 0
|
||||||
|
v
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MkvTrack {
|
impl MkvTrack {
|
||||||
@@ -83,6 +101,13 @@ impl MkvTrack {
|
|||||||
sample_rate: 0.0,
|
sample_rate: 0.0,
|
||||||
channels: 0,
|
channels: 0,
|
||||||
bit_depth: 0,
|
bit_depth: 0,
|
||||||
|
// The DV layer (hdr=DolbyVision) carries the dvcC so the track is
|
||||||
|
// recognised as Dolby Vision (disc Profile 7 dual-layer).
|
||||||
|
dv_config: if matches!(v.hdr, HdrFormat::DolbyVision) {
|
||||||
|
Some(dolby_vision_config(7, 6, 0))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,6 +153,7 @@ impl MkvTrack {
|
|||||||
sample_rate: sr,
|
sample_rate: sr,
|
||||||
channels: ch,
|
channels: ch,
|
||||||
bit_depth: 0,
|
bit_depth: 0,
|
||||||
|
dv_config: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,6 +182,7 @@ impl MkvTrack {
|
|||||||
sample_rate: 0.0,
|
sample_rate: 0.0,
|
||||||
channels: 0,
|
channels: 0,
|
||||||
bit_depth: 0,
|
bit_depth: 0,
|
||||||
|
dv_config: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -333,6 +360,17 @@ impl<W: Write + Seek> MkvMuxer<W> {
|
|||||||
ebml::end_master(&mut writer, vid_pos)?;
|
ebml::end_master(&mut writer, vid_pos)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dolby Vision signaling — BlockAdditionMapping is a child of the
|
||||||
|
// TrackEntry (sibling of Video). Carries the dvcC so players /
|
||||||
|
// mediainfo recognise the track as Dolby Vision.
|
||||||
|
if let Some(ref dvcc) = track.dv_config {
|
||||||
|
let map_pos = ebml::start_master(&mut writer, ebml::BLOCK_ADDITION_MAPPING)?;
|
||||||
|
// BlockAddIDType = "dvcC" fourcc (DOVIDecoderConfigurationRecord).
|
||||||
|
ebml::write_uint(&mut writer, ebml::BLOCK_ADD_ID_TYPE, 0x6476_6343)?;
|
||||||
|
ebml::write_binary(&mut writer, ebml::BLOCK_ADD_ID_EXTRA_DATA, dvcc)?;
|
||||||
|
ebml::end_master(&mut writer, map_pos)?;
|
||||||
|
}
|
||||||
|
|
||||||
// Audio-specific
|
// Audio-specific
|
||||||
if track.track_type == ebml::TRACK_TYPE_AUDIO && track.sample_rate > 0.0 {
|
if track.track_type == ebml::TRACK_TYPE_AUDIO && track.sample_rate > 0.0 {
|
||||||
let aud_pos = ebml::start_master(&mut writer, ebml::AUDIO)?;
|
let aud_pos = ebml::start_master(&mut writer, ebml::AUDIO)?;
|
||||||
@@ -612,6 +650,7 @@ mod tests {
|
|||||||
sample_rate: 0.0,
|
sample_rate: 0.0,
|
||||||
channels: 0,
|
channels: 0,
|
||||||
bit_depth: 0,
|
bit_depth: 0,
|
||||||
|
dv_config: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -636,9 +675,24 @@ mod tests {
|
|||||||
sample_rate: 48000.0,
|
sample_rate: 48000.0,
|
||||||
channels: 6,
|
channels: 6,
|
||||||
bit_depth: 0,
|
bit_depth: 0,
|
||||||
|
dv_config: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dolby_vision_config_profile7() {
|
||||||
|
// dvcC for disc Profile 7 dual-layer: version 1.0, profile 7, all of
|
||||||
|
// bl/el/rpu present. 24 bytes.
|
||||||
|
let c = dolby_vision_config(7, 6, 0);
|
||||||
|
assert_eq!(c.len(), 24);
|
||||||
|
assert_eq!(c[0], 1); // dv_version_major
|
||||||
|
assert_eq!(c[1], 0); // dv_version_minor
|
||||||
|
// profile in the top 7 bits of byte 2
|
||||||
|
assert_eq!(c[2] >> 1, 7, "dv_profile must be 7");
|
||||||
|
// rpu/el/bl present flags in byte 3 (low 3 bits after level)
|
||||||
|
assert_eq!(c[3] & 0b0000_0111, 0b0000_0111, "rpu+el+bl all present");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn mkv_writes_ebml_header() {
|
fn mkv_writes_ebml_header() {
|
||||||
let buf = Cursor::new(Vec::new());
|
let buf = Cursor::new(Vec::new());
|
||||||
|
|||||||
Reference in New Issue
Block a user