From 5afcb1a83200e4c2a6e46b9077d26e3a4634cf02 Mon Sep 17 00:00:00 2001 From: sscobici Date: Thu, 3 Oct 2024 23:41:13 +0300 Subject: [PATCH] isomp4: Add HEVC basic detection --- symphonia-format-isomp4/src/atoms/avcc.rs | 2 - symphonia-format-isomp4/src/atoms/hvcc.rs | 72 +++++++++++++++++++++++ symphonia-format-isomp4/src/atoms/mod.rs | 10 ++++ symphonia-format-isomp4/src/atoms/stsd.rs | 43 +++++++++++++- 4 files changed, 122 insertions(+), 5 deletions(-) create mode 100644 symphonia-format-isomp4/src/atoms/hvcc.rs diff --git a/symphonia-format-isomp4/src/atoms/avcc.rs b/symphonia-format-isomp4/src/atoms/avcc.rs index 303eb79b..3b1c9f9a 100644 --- a/symphonia-format-isomp4/src/atoms/avcc.rs +++ b/symphonia-format-isomp4/src/atoms/avcc.rs @@ -32,8 +32,6 @@ impl Atom for AvcCAtom { let extra_data = reader.read_boxed_slice_exact(len as usize)?; - dbg!(extra_data.len()); - // Parse the AVCDecoderConfigurationRecord to get the profile and level. Defined in // ISO/IEC 14496-15 section 5.3.3.1. let mut br = BitReaderLtr::new(&extra_data); diff --git a/symphonia-format-isomp4/src/atoms/hvcc.rs b/symphonia-format-isomp4/src/atoms/hvcc.rs new file mode 100644 index 00000000..21caf05f --- /dev/null +++ b/symphonia-format-isomp4/src/atoms/hvcc.rs @@ -0,0 +1,72 @@ +// Symphonia +// Copyright (c) 2019-2022 The Project Symphonia Developers. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use symphonia_core::codecs::video::well_known::CODEC_ID_HEVC; +use symphonia_core::codecs::video::VideoCodecParameters; +use symphonia_core::codecs::CodecProfile; +use symphonia_core::errors::{decode_error, Error, Result}; +use symphonia_core::io::{BitReaderLtr, ReadBitsLtr, ReadBytes}; + +use crate::atoms::{Atom, AtomHeader}; + +#[allow(dead_code)] +#[derive(Debug)] +pub struct HvcCAtom { + /// HEVC extra data (HEVCDecoderConfigurationRecord). + extra_data: Box<[u8]>, + profile: CodecProfile, + level: u32, +} + +impl Atom for HvcCAtom { + fn read(reader: &mut B, header: AtomHeader) -> Result { + // The HEVCConfiguration atom payload is a single HEVCDecoderConfigurationRecord. This record + // forms the defacto codec extra data. + let len = header + .data_len() + .ok_or_else(|| Error::DecodeError("isomp4 (hvcC): expected atom size to be known"))?; + + let extra_data = reader.read_boxed_slice_exact(len as usize)?; + + // Parse the HEVCDecoderConfigurationRecord to get the profile and level. Defined in + // ISO/IEC 14496-15 section 8.3.3.1.2 + let mut br = BitReaderLtr::new(&extra_data); + + // Configuration version is always 1. + let configuration_version = br.read_bits_leq32(8)?; + + if configuration_version != 1 { + return decode_error( + "isomp4 (hvcC): unexpected hevc decoder configuration record version", + ); + } + + // HEVC profile as defined in ISO/IEC 23008-2. + let _general_profile_space = br.read_bits_leq32(2)?; + let _general_tier_flag = br.read_bits_leq32(1)?; + let general_profile_idc = br.read_bits_leq32(5)?; + let _general_profile_compatibility_flags = br.read_bits_leq32(32)?; + let _general_constraint_indicator_flags = br.read_bits_leq64(48)?; + let general_level_idc = br.read_bits_leq32(8)?; + + Ok(Self { + extra_data, + profile: CodecProfile::new(general_profile_idc), + level: general_level_idc, + }) + } +} + +impl HvcCAtom { + pub fn fill_codec_params(&self, codec_params: &mut VideoCodecParameters) { + codec_params + .for_codec(CODEC_ID_HEVC) + .with_profile(self.profile) + .with_level(self.level) + .with_extra_data(self.extra_data.clone()); + } +} diff --git a/symphonia-format-isomp4/src/atoms/mod.rs b/symphonia-format-isomp4/src/atoms/mod.rs index 58276ac7..2c1b7d42 100644 --- a/symphonia-format-isomp4/src/atoms/mod.rs +++ b/symphonia-format-isomp4/src/atoms/mod.rs @@ -20,6 +20,7 @@ pub(crate) mod esds; pub(crate) mod flac; pub(crate) mod ftyp; pub(crate) mod hdlr; +pub(crate) mod hvcc; pub(crate) mod ilst; pub(crate) mod mdhd; pub(crate) mod mdia; @@ -62,6 +63,7 @@ pub use esds::EsdsAtom; pub use flac::FlacAtom; pub use ftyp::FtypAtom; pub use hdlr::HdlrAtom; +pub use hvcc::HvcCAtom; pub use ilst::IlstAtom; pub use mdhd::MdhdAtom; pub use mdia::MdiaAtom; @@ -147,6 +149,7 @@ pub enum AtomType { GroupingTag, Handler, HdVideoTag, + HevcConfiguration, IdentPodcastTag, KeywordTag, LongDescriptionTag, @@ -209,6 +212,9 @@ pub enum AtomType { Uuid, VisualSampleEntryAv1, VisualSampleEntryAvc1, + VisualSampleEntryDvh1, + VisualSampleEntryDvhe, + VisualSampleEntryHev1, VisualSampleEntryHvc1, VisualSampleEntryMp4v, VisualSampleEntryVp8, @@ -233,6 +239,8 @@ impl From<[u8; 4]> for AtomType { b"data" => AtomType::MetaTagData, b"dfLa" => AtomType::FlacDsConfig, b"dOps" => AtomType::OpusDsConfig, + b"dvh1" => AtomType::VisualSampleEntryDvh1, + b"dvhe" => AtomType::VisualSampleEntryDvhe, b"edts" => AtomType::Edit, b"elst" => AtomType::EditList, b"esds" => AtomType::Esds, @@ -242,7 +250,9 @@ impl From<[u8; 4]> for AtomType { b"free" => AtomType::Free, b"ftyp" => AtomType::FileType, b"hdlr" => AtomType::Handler, + b"hev1" => AtomType::VisualSampleEntryHev1, b"hvc1" => AtomType::VisualSampleEntryHvc1, + b"hvcC" => AtomType::HevcConfiguration, b"ilst" => AtomType::MetaList, b"in24" => AtomType::AudioSampleEntryS24, b"in32" => AtomType::AudioSampleEntryS32, diff --git a/symphonia-format-isomp4/src/atoms/stsd.rs b/symphonia-format-isomp4/src/atoms/stsd.rs index 61e2534d..8224f566 100644 --- a/symphonia-format-isomp4/src/atoms/stsd.rs +++ b/symphonia-format-isomp4/src/atoms/stsd.rs @@ -29,7 +29,7 @@ use symphonia_core::io::ReadBytes; use crate::atoms::{AlacAtom, Atom, AtomHeader, AtomType, EsdsAtom, FlacAtom, OpusAtom, WaveAtom}; use crate::fp::FpU16; -use super::{AtomIterator, AvcCAtom}; +use super::{AtomIterator, AvcCAtom, HvcCAtom}; /// Sample description atom. #[allow(dead_code)] @@ -76,6 +76,9 @@ impl Atom for StsdAtom { } AtomType::VisualSampleEntryAv1 | AtomType::VisualSampleEntryAvc1 + | AtomType::VisualSampleEntryDvh1 + | AtomType::VisualSampleEntryDvhe + | AtomType::VisualSampleEntryHev1 | AtomType::VisualSampleEntryHvc1 | AtomType::VisualSampleEntryMp4v | AtomType::VisualSampleEntryVp8 @@ -148,6 +151,12 @@ impl StsdAtom { Some(VisualCodecSpecific::Avc1(ref avc)) => { avc.fill_codec_params(&mut codec_params); } + Some(VisualCodecSpecific::Dvh1(ref hevc)) + | Some(VisualCodecSpecific::Dvhe(ref hevc)) + | Some(VisualCodecSpecific::Hev1(ref hevc)) + | Some(VisualCodecSpecific::Hvc1(ref hevc)) => { + hevc.fill_codec_params(&mut codec_params); + } _ => (), } @@ -551,7 +560,10 @@ pub struct VisualSampleEntry { pub enum VisualCodecSpecific { Av1, Avc1(AvcCAtom), - Hvc1, + Dvhe(HvcCAtom), + Dvh1(HvcCAtom), + Hev1(HvcCAtom), + Hvc1(HvcCAtom), Mp4v, Vp8, Vp9, @@ -610,11 +622,36 @@ fn read_visual_sample_entry( AtomType::AvcConfiguration => { // AVC if header.atom_type != AtomType::VisualSampleEntryAvc1 || codec_specific.is_some() { - return decode_error("isomp4: invalid sample entry"); + return decode_error("isomp4: invalid avc configuration sample entry"); } codec_specific = Some(VisualCodecSpecific::Avc1(iter.read_atom::()?)); } + AtomType::HevcConfiguration => { + // HEVC + if (header.atom_type != AtomType::VisualSampleEntryDvhe + && header.atom_type != AtomType::VisualSampleEntryDvh1 + && header.atom_type != AtomType::VisualSampleEntryHev1 + && header.atom_type != AtomType::VisualSampleEntryHvc1) || codec_specific.is_some() { + return decode_error("isomp4: invalid hevc configuration sample entry"); + } + + match header.atom_type { + AtomType::VisualSampleEntryDvhe => { + codec_specific = Some(VisualCodecSpecific::Dvhe(iter.read_atom::()?)); + } + AtomType::VisualSampleEntryDvh1 => { + codec_specific = Some(VisualCodecSpecific::Dvh1(iter.read_atom::()?)); + } + AtomType::VisualSampleEntryHev1 => { + codec_specific = Some(VisualCodecSpecific::Hev1(iter.read_atom::()?)); + } + AtomType::VisualSampleEntryHvc1 => { + codec_specific = Some(VisualCodecSpecific::Hvc1(iter.read_atom::()?)); + } + _ => {} + } + } _ => { debug!("unknown visual sample entry sub-atom: {:?}.", entry_header.atom_type()); }