use super::atom_info::{AtomIdent, AtomInfo};
use super::read::{AtomReader, find_child_atom, skip_atom};
use crate::config::ParsingMode;
use crate::macros::try_vec;
use crate::mp4::error::{AtomParseError, Mp4ParseError};
use crate::properties::FileProperties;
use crate::util::alloc::VecFallibleCapacity;
use crate::util::math::RoundedDivision;
use std::io::{Cursor, Read, Seek, SeekFrom};
use std::time::Duration;
use byteorder::{BigEndian, ReadBytesExt};
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Mp4Codec {
AAC,
ALAC,
MP3,
FLAC,
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
#[rustfmt::skip]
#[non_exhaustive]
pub enum AudioObjectType {
#[default]
NULL = 0,
AacMain = 1,
AacLowComplexity = 2,
AacScalableSampleRate = 3,
AacLongTermPrediction = 4,
SpectralBandReplication = 5,
AACScalable = 6,
TwinVQ = 7,
CodeExcitedLinearPrediction = 8,
HarmonicVectorExcitationCoding = 9,
TextToSpeechtInterface = 12,
MainSynthetic = 13,
WavetableSynthesis = 14,
GeneralMIDI = 15,
AlgorithmicSynthesis = 16,
ErrorResilientAacLowComplexity = 17,
ErrorResilientAacLongTermPrediction = 19,
ErrorResilientAacScalable = 20,
ErrorResilientAacTwinVQ = 21,
ErrorResilientAacBitSlicedArithmeticCoding = 22,
ErrorResilientAacLowDelay = 23,
ErrorResilientCodeExcitedLinearPrediction = 24,
ErrorResilientHarmonicVectorExcitationCoding = 25,
ErrorResilientHarmonicIndividualLinesNoise = 26,
ErrorResilientParametric = 27,
SinuSoidalCoding = 28,
ParametricStereo = 29,
MpegSurround = 30,
MpegLayer1 = 32,
MpegLayer2 = 33,
MpegLayer3 = 34,
DirectStreamTransfer = 35,
AudioLosslessCoding = 36,
ScalableLosslessCoding = 37,
ScalableLosslessCodingNoneCore = 38,
ErrorResilientAacEnhancedLowDelay = 39,
SymbolicMusicRepresentationSimple = 40,
SymbolicMusicRepresentationMain = 41,
UnifiedSpeechAudioCoding = 42,
SpatialAudioObjectCoding = 43,
LowDelayMpegSurround = 44,
SpatialAudioObjectCodingDialogueEnhancement = 45,
AudioSync = 46,
}
impl TryFrom<u8> for AudioObjectType {
type Error = ();
#[rustfmt::skip]
fn try_from(value: u8) -> std::result::Result<Self, Self::Error> {
match value {
1 => Ok(Self::AacMain),
2 => Ok(Self::AacLowComplexity),
3 => Ok(Self::AacScalableSampleRate),
4 => Ok(Self::AacLongTermPrediction),
5 => Ok(Self::SpectralBandReplication),
6 => Ok(Self::AACScalable),
7 => Ok(Self::TwinVQ),
8 => Ok(Self::CodeExcitedLinearPrediction),
9 => Ok(Self::HarmonicVectorExcitationCoding),
12 => Ok(Self::TextToSpeechtInterface),
13 => Ok(Self::MainSynthetic),
14 => Ok(Self::WavetableSynthesis),
15 => Ok(Self::GeneralMIDI),
16 => Ok(Self::AlgorithmicSynthesis),
17 => Ok(Self::ErrorResilientAacLowComplexity),
19 => Ok(Self::ErrorResilientAacLongTermPrediction),
20 => Ok(Self::ErrorResilientAacScalable),
21 => Ok(Self::ErrorResilientAacTwinVQ),
22 => Ok(Self::ErrorResilientAacBitSlicedArithmeticCoding),
23 => Ok(Self::ErrorResilientAacLowDelay),
24 => Ok(Self::ErrorResilientCodeExcitedLinearPrediction),
25 => Ok(Self::ErrorResilientHarmonicVectorExcitationCoding),
26 => Ok(Self::ErrorResilientHarmonicIndividualLinesNoise),
27 => Ok(Self::ErrorResilientParametric),
28 => Ok(Self::SinuSoidalCoding),
29 => Ok(Self::ParametricStereo),
30 => Ok(Self::MpegSurround),
32 => Ok(Self::MpegLayer1),
33 => Ok(Self::MpegLayer2),
34 => Ok(Self::MpegLayer3),
35 => Ok(Self::DirectStreamTransfer),
36 => Ok(Self::AudioLosslessCoding),
37 => Ok(Self::ScalableLosslessCoding),
38 => Ok(Self::ScalableLosslessCodingNoneCore),
39 => Ok(Self::ErrorResilientAacEnhancedLowDelay),
40 => Ok(Self::SymbolicMusicRepresentationSimple),
41 => Ok(Self::SymbolicMusicRepresentationMain),
42 => Ok(Self::UnifiedSpeechAudioCoding),
43 => Ok(Self::SpatialAudioObjectCoding),
44 => Ok(Self::LowDelayMpegSurround),
45 => Ok(Self::SpatialAudioObjectCodingDialogueEnhancement),
46 => Ok(Self::AudioSync),
_ => Err(()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[non_exhaustive]
pub struct Mp4Properties {
pub(crate) codec: Option<Mp4Codec>,
pub(crate) extended_audio_object_type: Option<AudioObjectType>,
pub(crate) duration: Duration,
pub(crate) overall_bitrate: Option<u32>,
pub(crate) audio_bitrate: Option<u32>,
pub(crate) sample_rate: Option<u32>,
pub(crate) bit_depth: Option<u8>,
pub(crate) channels: Option<u8>,
pub(crate) drm_protected: bool,
pub(crate) ftyp: String,
}
impl From<Mp4Properties> for FileProperties {
fn from(input: Mp4Properties) -> Self {
Self {
duration: input.duration,
overall_bitrate: input.overall_bitrate,
audio_bitrate: input.audio_bitrate,
sample_rate: input.sample_rate,
bit_depth: input.bit_depth,
channels: input.channels,
channel_mask: None,
}
}
}
impl Mp4Properties {
pub fn duration(&self) -> Duration {
self.duration
}
pub fn overall_bitrate(&self) -> Option<u32> {
self.overall_bitrate
}
pub fn audio_bitrate(&self) -> Option<u32> {
self.audio_bitrate
}
pub fn sample_rate(&self) -> Option<u32> {
self.sample_rate
}
pub fn bit_depth(&self) -> Option<u8> {
self.bit_depth
}
pub fn channels(&self) -> Option<u8> {
self.channels
}
pub fn codec(&self) -> Option<Mp4Codec> {
self.codec
}
pub fn audio_object_type(&self) -> Option<AudioObjectType> {
self.extended_audio_object_type
}
pub fn is_drm_protected(&self) -> bool {
self.drm_protected
}
pub fn ftyp(&self) -> &str {
self.ftyp.as_ref()
}
}
struct AudioTrak {
mdhd: AtomInfo,
minf: Option<AtomInfo>,
}
fn find_audio_trak<R>(
reader: &mut AtomReader<R>,
traks: &[AtomInfo],
) -> Result<AudioTrak, Mp4ParseError>
where
R: Read + Seek,
{
const IDENT: AtomIdent<'static> = AtomIdent::Fourcc(*b"mdia");
let mut audio_track = false;
let mut mdhd = None;
let mut minf = None;
for mdia in traks {
if audio_track {
break;
}
mdhd = None;
minf = None;
reader.seek(SeekFrom::Start(mdia.start + 8))?;
let mut read = 8;
while read < mdia.len {
let Some(atom) = reader.next()? else { break };
read += atom.len;
if let AtomIdent::Fourcc(fourcc) = atom.ident {
match &fourcc {
b"mdhd" => {
skip_atom(reader, atom.extended, atom.len)?;
mdhd = Some(atom)
},
b"hdlr" => {
if atom.len < 20 {
log::warn!("Incomplete 'hdlr' atom, skipping");
skip_atom(reader, atom.extended, atom.len)?;
continue;
}
reader.seek(SeekFrom::Current(8))?;
let mut handler_type = [0; 4];
reader.read_exact(&mut handler_type)?;
if &handler_type == b"soun" {
audio_track = true
}
skip_atom(reader, atom.extended, atom.len - 12)?;
},
b"minf" => minf = Some(atom),
_ => {
skip_atom(reader, atom.extended, atom.len)?;
},
}
continue;
}
skip_atom(reader, atom.extended, atom.len)?;
}
}
if !audio_track {
return Err(Mp4ParseError::message("file contains no audio tracks"));
}
let Some(mdhd) = mdhd else {
return Err(
AtomParseError::message(Some(IDENT), "audio track has no \"mdhd\" atom").into(),
);
};
Ok(AudioTrak { mdhd, minf })
}
struct Mdhd {
timescale: u32,
duration: u64,
}
impl Mdhd {
const IDENT: AtomIdent<'static> = AtomIdent::Fourcc(*b"mdhd");
fn parse<R>(reader: &mut AtomReader<R>) -> Result<Self, AtomParseError>
where
R: Read + Seek,
{
fn parse_inner<R>(reader: &mut AtomReader<R>) -> Result<Mdhd, AtomParseError>
where
R: Read + Seek,
{
let version = reader.read_u8()?;
let _flags = reader.read_uint(3)?;
let (timescale, duration) = if version == 1 {
let _creation_time = reader.read_u64()?;
let _modification_time = reader.read_u64()?;
let timescale = reader.read_u32()?;
let duration = reader.read_u64()?;
(timescale, duration)
} else {
let _creation_time = reader.read_u32()?;
let _modification_time = reader.read_u32()?;
let timescale = reader.read_u32()?;
let duration = reader.read_u32()?;
(timescale, u64::from(duration))
};
Ok(Mdhd {
timescale,
duration,
})
}
parse_inner(reader).map_err(|e| e.with_ident(Self::IDENT))
}
}
#[derive(Debug)]
struct SttsEntry {
_sample_count: u32,
sample_duration: u32,
}
#[derive(Debug)]
struct Stts {
entries: Vec<SttsEntry>,
}
impl Stts {
const IDENT: AtomIdent<'static> = AtomIdent::Fourcc(*b"stts");
fn parse<R>(reader: &mut R) -> Result<Self, AtomParseError>
where
R: Read,
{
fn parse_inner<R>(reader: &mut R) -> Result<Stts, AtomParseError>
where
R: Read,
{
let _version_and_flags = reader.read_uint::<BigEndian>(4)?;
let entry_count = reader.read_u32::<BigEndian>()?;
let mut entries = Vec::try_with_capacity_stable(entry_count as usize)?;
for _ in 0..entry_count {
let sample_count = reader.read_u32::<BigEndian>()?;
let sample_duration = reader.read_u32::<BigEndian>()?;
entries.push(SttsEntry {
_sample_count: sample_count,
sample_duration,
});
}
Ok(Stts { entries })
}
parse_inner(reader).map_err(|e| e.with_ident(Self::IDENT))
}
}
struct Minf {
stsd_data: Vec<u8>,
stts: Option<Stts>,
}
impl Minf {
const IDENT: AtomIdent<'static> = AtomIdent::Fourcc(*b"minf");
fn parse<R>(
reader: &mut AtomReader<R>,
len: u64,
parse_mode: ParsingMode,
) -> Result<Option<Self>, AtomParseError>
where
R: Read + Seek,
{
fn parse_inner<R>(
reader: &mut AtomReader<R>,
len: u64,
parse_mode: ParsingMode,
) -> Result<Option<Minf>, AtomParseError>
where
R: Read + Seek,
{
let Some(stbl) = find_child_atom(reader, len, *b"stbl", parse_mode)? else {
return Ok(None);
};
let mut stsd_data = None;
let mut stts = None;
let mut read = 8;
while read < stbl.len {
let Some(atom) = reader.next()? else { break };
read += atom.len;
if let AtomIdent::Fourcc(fourcc) = atom.ident {
match &fourcc {
b"stsd" => {
fn parse_stsd<R>(
reader: &mut AtomReader<R>,
atom: &AtomInfo,
) -> Result<Vec<u8>, AtomParseError>
where
R: Read + Seek,
{
let mut stsd = try_vec![0; (atom.len - 8) as usize]?;
reader.read_exact(&mut stsd)?;
Ok(stsd)
}
stsd_data = Some(
parse_stsd(reader, &atom).map_err(|e| e.with_ident(atom.ident))?,
);
},
b"stts" => stts = Some(Stts::parse(reader)?),
_ => {
skip_atom(reader, atom.extended, atom.len)?;
},
}
continue;
}
}
let Some(stsd_data) = stsd_data else {
return Ok(None);
};
Ok(Some(Minf { stsd_data, stts }))
}
parse_inner(reader, len, parse_mode).map_err(|e| e.with_ident_if_not_present(Self::IDENT))
}
}
const STSD_IDENT: AtomIdent<'static> = AtomIdent::Fourcc(*b"stsd");
fn read_stsd<R>(
reader: &mut AtomReader<R>,
properties: &mut Mp4Properties,
) -> Result<(), AtomParseError>
where
R: Read + Seek,
{
const MIN_SUPPORTED_ENTRY_VERSION: u16 = 0;
const MAX_SUPPORTED_ENTRY_VERSION: u16 = 2;
fn read_inner<R>(
reader: &mut AtomReader<R>,
properties: &mut Mp4Properties,
) -> Result<(), AtomParseError>
where
R: Read + Seek,
{
reader.seek(SeekFrom::Current(4))?;
let num_sample_entries = reader.read_u32()?;
for _ in 0..num_sample_entries {
let Some(atom) = reader.next()? else {
return Err(AtomParseError::message(None, "expected sample entry atom"));
};
if atom.extended {
return Err(AtomParseError::message(
None,
"extended atoms are not supported in `stsd`",
));
}
let AtomIdent::Fourcc(ref descriptor_format) = atom.ident else {
return Err(AtomParseError::message(None, "unexpected freeform atom"));
};
reader.seek(SeekFrom::Current(8))?;
let stsd_version = reader.read_u16()?;
if !(MIN_SUPPORTED_ENTRY_VERSION..=MAX_SUPPORTED_ENTRY_VERSION).contains(&stsd_version)
{
return Err(AtomParseError::message(None, "unsupported `stsd` version"));
}
reader.seek(SeekFrom::Current(6))?;
properties.channels = Some(reader.read_u16()? as u8);
reader.seek(SeekFrom::Current(6))?;
properties.sample_rate = Some(reader.read_u32()? >> 16);
let mut offset = reader.stream_position()?;
if stsd_version == 1 {
offset = reader.seek(SeekFrom::Current(16))?;
} else if stsd_version == 2 {
let extension_struct_size = reader.read_u32()?;
offset = reader.seek(SeekFrom::Current(32 + i64::from(extension_struct_size)))?;
}
match descriptor_format {
b"mp4a" => mp4a_properties(reader, properties)
.map_err(|e| e.with_ident_if_not_present(AtomIdent::Fourcc(*b"mp4a")))?,
b"alac" => alac_properties(reader, properties)
.map_err(|e| e.with_ident_if_not_present(AtomIdent::Fourcc(*b"alac")))?,
b"fLaC" => flac_properties(reader, properties)
.map_err(|e| e.with_ident_if_not_present(AtomIdent::Fourcc(*b"fLaC")))?,
b"drms" => {
properties.drm_protected = true;
skip_atom(reader, false, offset - atom.start)
.map_err(|e| e.with_ident(atom.ident))?;
continue;
},
_ => {
log::warn!(
"Found unsupported sample entry: {:?}",
descriptor_format.escape_ascii().to_string()
);
skip_atom(reader, false, offset - atom.start)?;
continue;
},
}
break;
}
Ok(())
}
read_inner(reader, properties).map_err(|e| e.with_ident_if_not_present(STSD_IDENT))
}
pub(super) fn read_properties<R>(
reader: &mut AtomReader<R>,
traks: &[AtomInfo],
file_length: u64,
parse_mode: ParsingMode,
) -> Result<Mp4Properties, Mp4ParseError>
where
R: Read + Seek,
{
let AudioTrak { mdhd, minf } = find_audio_trak(reader, traks)?;
reader.seek(SeekFrom::Start(mdhd.start + 8))?;
let Mdhd {
timescale,
duration,
} = Mdhd::parse(reader)?;
let mut properties = Mp4Properties::default();
if timescale > 0 {
let duration_millis = (duration * 1000).div_round(u64::from(timescale));
properties.duration = Duration::from_millis(duration_millis);
}
let Some(minf_info) = minf else {
return Ok(properties);
};
reader.seek(SeekFrom::Start(minf_info.start + 8))?;
let Some(Minf { stsd_data, stts }) = Minf::parse(reader, minf_info.len, parse_mode)? else {
return Ok(properties);
};
let mut cursor = Cursor::new(&*stsd_data);
let mut stsd_reader = AtomReader::new(&mut cursor, parse_mode)?;
read_stsd(&mut stsd_reader, &mut properties)?;
if duration > 0 {
let mdat_len;
match mdat_length(reader) {
Ok(len) => mdat_len = len,
Err(err) => {
if parse_mode == ParsingMode::Strict {
return Err(err);
}
log::warn!("No \"mdat\" atom found, any audio properties will be useless.");
return Ok(properties);
},
}
if let Some(stts) = stts {
let stts_specifies_duration =
!(stts.entries.len() == 1 && stts.entries[0].sample_duration == 1);
if stts_specifies_duration {
let audio_bitrate_bps = (((u128::from(mdat_len) * 8) * u128::from(timescale))
/ u128::from(duration)) as u32;
properties.audio_bitrate = Some(audio_bitrate_bps / 1000);
}
}
let duration_millis = properties.duration.as_millis();
if duration_millis == 0 {
log::warn!("Duration is 0, unable to calculate bitrate");
return Ok(properties);
}
let overall_bitrate = u128::from(file_length * 8) / duration_millis;
properties.overall_bitrate = Some(overall_bitrate as u32);
if matches!(properties.audio_bitrate, None | Some(0)) {
log::warn!("Estimating audio bitrate from 'mdat' size");
properties.audio_bitrate = Some((u128::from(mdat_len * 8) / duration_millis) as u32);
}
}
Ok(properties)
}
pub(crate) const SAMPLE_RATES: [u32; 15] = [
96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350, 0, 0,
];
fn mp4a_properties<R>(
stsd: &mut AtomReader<R>,
properties: &mut Mp4Properties,
) -> Result<(), AtomParseError>
where
R: Read + Seek,
{
const ELEMENTARY_DESCRIPTOR_TAG: u8 = 0x03;
const DECODER_CONFIG_TAG: u8 = 0x04;
const DECODER_SPECIFIC_DESCRIPTOR_TAG: u8 = 0x05;
properties.codec = Some(Mp4Codec::AAC);
let Ok(Some(esds)) = stsd.next() else {
return Ok(());
};
if esds.ident != AtomIdent::Fourcc(*b"esds") {
return Ok(());
}
if stsd.read_u32()? != 0 {
return Ok(());
}
let descriptor = Descriptor::read(stsd)?;
if descriptor.tag == ELEMENTARY_DESCRIPTOR_TAG {
stsd.seek(SeekFrom::Current(3))?;
let descriptor = Descriptor::read(stsd)?;
if descriptor.tag == DECODER_CONFIG_TAG {
let codec = stsd.read_u8()?;
properties.codec = match codec {
0x40 | 0x41 | 0x66 | 0x67 | 0x68 => Some(Mp4Codec::AAC),
0x69 | 0x6B => Some(Mp4Codec::MP3),
_ => None,
};
stsd.seek(SeekFrom::Current(8))?;
let average_bitrate = stsd.read_u32()?;
let descriptor = Descriptor::read(stsd)?;
if descriptor.tag == DECODER_SPECIFIC_DESCRIPTOR_TAG {
let byte_a = stsd.read_u8()?;
let byte_b = stsd.read_u8()?;
let mut object_type = byte_a >> 3;
let mut frequency_index = ((byte_a & 0x07) << 1) | (byte_b >> 7);
let mut channel_conf = (byte_b >> 3) & 0x0F;
let mut extended_object_type = false;
if object_type == 31 {
extended_object_type = true;
object_type = 32 + ((byte_a & 7) | (byte_b >> 5));
frequency_index = (byte_b >> 1) & 0x0F;
}
properties.extended_audio_object_type = Some(
AudioObjectType::try_from(object_type)
.map_err(|_| AtomParseError::message(None, "invalid audio object type"))?,
);
match frequency_index {
0x0F => {
let sample_rate;
let explicit_sample_rate = stsd.read_u24()?;
if extended_object_type {
sample_rate = explicit_sample_rate >> 1;
channel_conf = ((explicit_sample_rate >> 4) & 0x0F) as u8;
} else {
sample_rate = explicit_sample_rate << 1;
let byte_c = stsd.read_u8()?;
channel_conf =
((explicit_sample_rate & 0x80) as u8 | (byte_c >> 1)) & 0x0F;
}
if sample_rate > 0 {
properties.sample_rate = Some(sample_rate);
}
},
i if i < SAMPLE_RATES.len() as u8 => {
properties.sample_rate = Some(SAMPLE_RATES[i as usize]);
if extended_object_type {
let byte_c = stsd.read_u8()?;
channel_conf = (byte_b & 1) | (byte_c & 0xE0);
} else {
channel_conf = (byte_b >> 3) & 0x0F;
}
},
_ => {},
}
if channel_conf > 0 {
properties.channels = Some(channel_conf);
}
if object_type == 36 {
let mut ident = [0; 5];
stsd.read_exact(&mut ident)?;
if &ident == b"\0ALS\0" {
properties.sample_rate = Some(stsd.read_u32()?);
stsd.seek(SeekFrom::Current(4))?;
properties.channels = Some(stsd.read_u16()? as u8 + 1);
}
}
}
if average_bitrate > 0 || properties.duration.is_zero() {
properties.audio_bitrate = Some(average_bitrate / 1000);
}
}
}
Ok(())
}
fn alac_properties<R>(
stsd: &mut AtomReader<R>,
properties: &mut Mp4Properties,
) -> Result<(), AtomParseError>
where
R: Read + Seek,
{
if stsd.len() != 80 {
return Ok(());
}
let Ok(Some(alac)) = stsd.next() else {
return Ok(());
};
if alac.ident != AtomIdent::Fourcc(*b"alac") {
return Ok(());
}
properties.codec = Some(Mp4Codec::ALAC);
stsd.seek(SeekFrom::Current(9))?;
let sample_size = stsd.read_u8()?;
properties.bit_depth = Some(sample_size);
stsd.seek(SeekFrom::Current(3))?;
properties.channels = Some(stsd.read_u8()?);
stsd.seek(SeekFrom::Current(6))?;
properties.audio_bitrate = Some(stsd.read_u32()? / 1000);
properties.sample_rate = Some(stsd.read_u32()?);
Ok(())
}
fn flac_properties<R>(
stsd: &mut AtomReader<R>,
properties: &mut Mp4Properties,
) -> Result<(), AtomParseError>
where
R: Read + Seek,
{
properties.codec = Some(Mp4Codec::FLAC);
let Some(dfla) = stsd.next()? else {
return Ok(());
};
if dfla.ident != AtomIdent::Fourcc(*b"dfLa") {
return Ok(());
}
stsd.seek(SeekFrom::Current(4))?;
if dfla.len - 12 < 18 {
return Ok(());
}
let stream_info_block = crate::flac::block::Block::read(stsd, |_| true)
.map_err(|e| AtomParseError::new(STSD_IDENT, e.into()))?;
let flac_properties =
crate::flac::properties::read_properties(&mut &stream_info_block.content[..], 0, 0)?;
properties.sample_rate = Some(flac_properties.sample_rate);
properties.bit_depth = Some(flac_properties.bit_depth);
properties.channels = Some(flac_properties.channels);
Ok(())
}
fn mdat_length<R>(reader: &mut AtomReader<R>) -> Result<u64, Mp4ParseError>
where
R: Read + Seek,
{
reader.rewind()?;
while let Ok(Some(atom)) = reader.next() {
if atom.ident == AtomIdent::Fourcc(*b"mdat") {
return Ok(atom.len - 8);
}
skip_atom(reader, atom.extended, atom.len)?;
}
Err(Mp4ParseError::message(
"file does not contain an \"mdat\" atom",
))
}
struct Descriptor {
tag: u8,
_size: u32,
}
impl Descriptor {
fn read<R: Read>(reader: &mut R) -> Result<Descriptor, AtomParseError> {
let tag = reader.read_u8()?;
let mut size: u32 = 0;
for _ in 0..4 {
let b = reader.read_u8()?;
size = (size << 7) | u32::from(b & 0x7F);
if b & 0x80 == 0 {
break;
}
}
Ok(Descriptor { tag, _size: size })
}
}