From 91015363cadecc8fffa8d67c326db51ea3a1ca99 Mon Sep 17 00:00:00 2001 From: Troy Benson Date: Tue, 26 Aug 2025 17:50:05 -0400 Subject: [PATCH] try fix hevc transcoding --- crates/mp4/src/boxes/mod.rs | 3 +- crates/mp4/src/boxes/types/ftyp.rs | 2 + crates/mp4/src/boxes/types/hvc1.rs | 112 +++++++++++++++++++++++++++ crates/mp4/src/boxes/types/mod.rs | 1 + crates/transmuxer/src/codecs/hevc.rs | 4 +- crates/transmuxer/src/lib.rs | 2 +- crates/transmuxer/src/tests/mod.rs | 2 +- 7 files changed, 121 insertions(+), 5 deletions(-) create mode 100644 crates/mp4/src/boxes/types/hvc1.rs diff --git a/crates/mp4/src/boxes/mod.rs b/crates/mp4/src/boxes/mod.rs index 84c064d07..0d743abf6 100644 --- a/crates/mp4/src/boxes/mod.rs +++ b/crates/mp4/src/boxes/mod.rs @@ -33,6 +33,7 @@ use crate::boxes::types::ftyp::Ftyp; use crate::boxes::types::hdlr::Hdlr; use crate::boxes::types::hev1::Hev1; use crate::boxes::types::hmhd::Hmhd; +use crate::boxes::types::hvc1::Hvc1; use crate::boxes::types::hvcc::HvcC; use crate::boxes::types::mdat::Mdat; use crate::boxes::types::mdhd::Mdhd; @@ -84,5 +85,5 @@ impl_box!( Url, Avc1, Clap, Pasp, AvcC, Btrt, Mp4a, Esds, Moof, Mfhd, Traf, Tfhd, Tfdt, Trun, Mdat, Av01, Av1C, Colr, - Hev1, HvcC, Opus, + Hev1, Hvc1, HvcC, Opus, ); diff --git a/crates/mp4/src/boxes/types/ftyp.rs b/crates/mp4/src/boxes/types/ftyp.rs index 63fb71aae..becad9d3c 100644 --- a/crates/mp4/src/boxes/types/ftyp.rs +++ b/crates/mp4/src/boxes/types/ftyp.rs @@ -77,6 +77,7 @@ pub enum FourCC { Avc1, Av01, Hev1, + Hvc1, Unknown([u8; 4]), } @@ -89,6 +90,7 @@ impl FourCC { Self::Avc1 => *b"avc1", Self::Av01 => *b"av01", Self::Hev1 => *b"hev1", + Self::Hvc1 => *b"hvc1", Self::Unknown(bytes) => *bytes, } } diff --git a/crates/mp4/src/boxes/types/hvc1.rs b/crates/mp4/src/boxes/types/hvc1.rs new file mode 100644 index 000000000..e31697565 --- /dev/null +++ b/crates/mp4/src/boxes/types/hvc1.rs @@ -0,0 +1,112 @@ +use std::io; + +use bytes::{Buf, Bytes}; + +use super::btrt::Btrt; +use super::hvcc::HvcC; +use super::stsd::{SampleEntry, VisualSampleEntry}; +use crate::boxes::DynBox; +use crate::boxes::header::BoxHeader; +use crate::boxes::traits::BoxType; +use crate::codec::VideoCodec; + +#[derive(Debug, Clone, PartialEq)] +/// HEVC (H.265) Codec Box +/// ISO/IEC 14496-15:2022 - 8.4 +pub struct Hvc1 { + pub header: BoxHeader, + pub visual_sample_entry: SampleEntry, + pub hvcc: HvcC, + pub btrt: Option, + pub unknown: Vec, +} + +impl Hvc1 { + pub fn new(visual_sample_entry: SampleEntry, hvcc: HvcC, btrt: Option) -> Self { + Self { + header: BoxHeader::new(Self::NAME), + visual_sample_entry, + hvcc, + btrt, + unknown: Vec::new(), + } + } + + pub fn codec(&self) -> io::Result { + Ok(VideoCodec::Hevc { + constraint_indicator: self.hvcc.hevc_config.general_constraint_indicator_flags, + level: self.hvcc.hevc_config.general_level_idc, + profile: self.hvcc.hevc_config.general_profile_idc, + profile_compatibility: self.hvcc.hevc_config.general_profile_compatibility_flags, + tier: self.hvcc.hevc_config.general_tier_flag, + general_profile_space: self.hvcc.hevc_config.general_profile_space, + }) + } +} + +impl BoxType for Hvc1 { + const NAME: [u8; 4] = *b"hvc1"; + + fn demux(header: BoxHeader, data: Bytes) -> io::Result { + let mut reader = io::Cursor::new(data); + + let mut visual_sample_entry = SampleEntry::::demux(&mut reader)?; + + let mut hvcc = None; + let mut btrt = None; + let mut unknown = Vec::new(); + + while reader.has_remaining() { + let dyn_box = DynBox::demux(&mut reader)?; + match dyn_box { + DynBox::HvcC(b) => { + hvcc = Some(*b); + } + DynBox::Btrt(b) => { + btrt = Some(*b); + } + DynBox::Clap(b) => { + visual_sample_entry.extension.clap = Some(*b); + } + DynBox::Pasp(b) => { + visual_sample_entry.extension.pasp = Some(*b); + } + DynBox::Colr(b) => { + visual_sample_entry.extension.colr = Some(*b); + } + _ => { + unknown.push(dyn_box); + } + } + } + + let hvcc = hvcc.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "trak box is missing tkhd box"))?; + + Ok(Self { + header, + visual_sample_entry, + hvcc, + btrt, + unknown, + }) + } + + fn primitive_size(&self) -> u64 { + self.visual_sample_entry.size() + + self.hvcc.size() + + self.btrt.as_ref().map(|b| b.size()).unwrap_or(0) + + self.unknown.iter().map(|b| b.size()).sum::() + } + + fn primitive_mux(&self, writer: &mut T) -> io::Result<()> { + self.visual_sample_entry.mux(writer)?; + self.hvcc.mux(writer)?; + if let Some(btrt) = &self.btrt { + btrt.mux(writer)?; + } + for unknown in &self.unknown { + unknown.mux(writer)?; + } + Ok(()) + } +} diff --git a/crates/mp4/src/boxes/types/mod.rs b/crates/mp4/src/boxes/types/mod.rs index 267fc68aa..1b7bb0981 100644 --- a/crates/mp4/src/boxes/types/mod.rs +++ b/crates/mp4/src/boxes/types/mod.rs @@ -16,6 +16,7 @@ pub mod ftyp; pub mod hdlr; pub mod hev1; pub mod hmhd; +pub mod hvc1; pub mod hvcc; pub mod mdat; pub mod mdhd; diff --git a/crates/transmuxer/src/codecs/hevc.rs b/crates/transmuxer/src/codecs/hevc.rs index 8843e69c2..ed4c50e35 100644 --- a/crates/transmuxer/src/codecs/hevc.rs +++ b/crates/transmuxer/src/codecs/hevc.rs @@ -5,7 +5,7 @@ use scuffle_flv::video::header::VideoFrameType; use scuffle_h265::{HEVCDecoderConfigurationRecord, SpsRbsp}; use scuffle_mp4::DynBox; use scuffle_mp4::types::colr::{ColorType, Colr}; -use scuffle_mp4::types::hev1::Hev1; +use scuffle_mp4::types::hvc1::Hvc1; use scuffle_mp4::types::hvcc::HvcC; use scuffle_mp4::types::stsd::{SampleEntry, VisualSampleEntry}; use scuffle_mp4::types::trun::{TrunSample, TrunSampleFlag}; @@ -34,7 +34,7 @@ pub(crate) fn stsd_entry(config: HEVCDecoderConfigurationRecord) -> Result<(DynB }); Ok(( - Hev1::new( + Hvc1::new( SampleEntry::new(VisualSampleEntry::new( sps.cropped_width() as u16, sps.cropped_height() as u16, diff --git a/crates/transmuxer/src/lib.rs b/crates/transmuxer/src/lib.rs index 3032695ce..310462057 100644 --- a/crates/transmuxer/src/lib.rs +++ b/crates/transmuxer/src/lib.rs @@ -493,7 +493,7 @@ impl<'a> Transmuxer<'a> { entry } VideoSequenceHeader::Hevc(config) => { - compatable_brands.push(FourCC::Hev1); + compatable_brands.push(FourCC::Hvc1); video_codec = VideoCodec::Hevc { constraint_indicator: config.general_constraint_indicator_flags, level: config.general_level_idc, diff --git a/crates/transmuxer/src/tests/mod.rs b/crates/transmuxer/src/tests/mod.rs index ddbb3b0bd..836c38289 100644 --- a/crates/transmuxer/src/tests/mod.rs +++ b/crates/transmuxer/src/tests/mod.rs @@ -333,7 +333,7 @@ fn test_transmuxer_hevc_aac() { assert_eq!(json["format"]["duration"], "2.026667"); assert_eq!(json["format"]["tags"]["major_brand"], "iso5"); assert_eq!(json["format"]["tags"]["minor_version"], "512"); - assert_eq!(json["format"]["tags"]["compatible_brands"], "iso5iso6hev1mp41"); + assert_eq!(json["format"]["tags"]["compatible_brands"], "iso5iso6hvc1mp41"); assert_eq!(json["streams"][0]["codec_name"], "hevc"); assert_eq!(json["streams"][0]["codec_type"], "video");