///|
/// File containers carrying AV1 low-overhead temporal units.
pub(all) enum Av1ContainerFormat {
Ivf
Mp4
WebM
} derive(Eq, Debug)
///|
/// One encoded temporal unit in container/decode order. Timestamps and
/// duration use the enclosing track's timescale. Unknown timing/sync fields
/// remain None instead of being inferred from presentation order.
/// The payload is owned and may be passed to the existing AV1 video decoder.
pub struct Av1Packet {
data : Array[Byte]
presentation_timestamp : Int64?
decode_timestamp : Int64?
duration : Int64?
sync_sample : Bool?
}
///|
/// An ISO BMFF movie edit. duration uses movie_timescale; media_time uses
/// the track timescale and -1 denotes an empty edit. media_rate is signed
/// 16.16 fixed-point (65536 means normal playback). Demuxing retains edits
/// separately so that encoded preroll/reference packets are never discarded.
pub struct Av1TrackEdit {
duration : Int64
media_time : Int64
media_rate : Int
}
///|
/// Selected AV1 video track. codec_initialization contains low-overhead OBUs,
/// without the four-byte av1C record header; prepend it to the first packet
/// when initializing a fresh AV1 decoder. Buffers do not alias the input.
/// max_frames in the demux policy limits packet count, not decoded pictures.
pub struct Av1DemuxedTrack {
format : Av1ContainerFormat
track_id : Int64
width : Int
height : Int
timescale : Int64
movie_timescale : Int64?
edits : Array[Av1TrackEdit]
codec_initialization : Array[Byte]
packets : Array[Av1Packet]
}
///|
fn av1_demux_validate_input(
data : Array[Byte],
limits : DecodeLimits,
) -> Result[Unit, DecodeError] {
if !decode_input_allowed(0, Some(limits)) {
return Err(decode_error(InvalidData, "demux", "Invalid resource limits"))
}
if data.length() > limits.max_input_bytes {
return Err(
decode_error(LimitExceeded, "demux", "Container exceeds max_input_bytes"),
)
}
Ok(())
}
///|
/// Shared MP4/WebM av1C configuration validation. The returned bytes are
/// initialization OBUs, not an Annex B length-prefixed stream.
fn av1_demux_av1c(data : Array[Byte]) -> Result[Array[Byte], DecodeError] {
if data.length() < 4 {
return Err(decode_error(TruncatedInput, "av1C", "Incomplete configuration"))
}
let marker_version = data[0].to_int()
if (marker_version & 128) == 0 {
return Err(decode_error(InvalidData, "av1C", "Missing marker bit"))
}
if (marker_version & 127) != 1 {
return Err(decode_error(Unsupported, "av1C", "Unsupported record version"))
}
let profile = data[1].to_int() >> 5
let level = data[1].to_int() & 31
let flags = data[2].to_int()
let high = (flags & 64) != 0
let twelve = (flags & 32) != 0
let mono = (flags & 16) != 0
let sx = (flags & 8) != 0
let sy = (flags & 4) != 0
let position = flags & 3
let delay = data[3].to_int()
if profile > 2 {
return Err(decode_error(Unsupported, "av1C", "Unsupported AV1 profile"))
}
if (level > 23 && level != 31) ||
(level <= 7 && (flags & 128) != 0) ||
(delay & 224) != 0 ||
((delay & 16) == 0 && (delay & 15) != 0) ||
(twelve && (!high || profile != 2)) ||
(sy && !sx) ||
position == 3 ||
((!sx || !sy) && position != 0) ||
(mono && (!sx || !sy || position != 0 || profile == 1)) ||
(!mono && profile == 0 && (!sx || !sy)) ||
(profile == 1 && (sx || sy)) ||
(!mono && profile == 2 && !twelve && (!sx || sy)) {
return Err(
decode_error(InvalidData, "av1C", "Inconsistent configuration fields"),
)
}
let obus = Array::makei(data.length() - 4, i => data[i + 4])
let mut pos = 0
let mut sequence_seen = false
while pos < obus.length() {
if (obus[pos].to_int() & 2) == 0 {
return Err(
decode_error(
InvalidData,
"av1C",
"Configuration OBU requires a size field",
),
)
}
let span = match av1_read_obu_span(obus, pos, true, obus.length()) {
Ok(Some(value)) => value
Err(error) =>
return Err({
..error,
context: "av1C/" + error.context,
byte_offset: error.byte_offset.map(at => at + 4L),
})
Ok(None) =>
return Err(decode_error(TruncatedInput, "av1C", "Incomplete OBU"))
}
if span.kind == 1 {
if sequence_seen || pos != 0 {
return Err(
decode_error(
InvalidData,
"av1C",
"Sequence header must occur once and first",
),
)
}
sequence_seen = true
let payload = Array::makei(span.end - span.payload_offset, i => {
obus[span.payload_offset + i]
})
let details : Av1SequenceDetails = {
level: 0,
tier: 0,
chroma_sample_position: 0,
}
let sequence = match
av1_parse_sequence_payload_details(payload, Some(details)) {
Some(value) => value
None =>
return Err(
decode_error(InvalidData, "av1C", "Invalid sequence header"),
)
}
let depth = if twelve { 12 } else if high { 10 } else { 8 }
if details.level != level ||
details.tier != flags >> 7 ||
details.chroma_sample_position != position ||
sequence.profile != profile ||
sequence.bit_depth != depth ||
sequence.monochrome != mono ||
sequence.subsampling_x != sx ||
sequence.subsampling_y != sy {
return Err(
decode_error(
InvalidData,
"av1C",
"Record and sequence color configuration differ",
),
)
}
} else if span.kind != 5 && span.kind != 15 {
return Err(
decode_error(
InvalidData,
"av1C",
"Configuration contains a non-configuration OBU",
),
)
}
pos = span.end
}
Ok(obus)
}
///|
/// Detect IVF, ISO BMFF or EBML from their file headers, or use an explicit
/// format. Only AV1 video tracks are selected. Parsing does not decode pixels.
pub fn av1_demux(
data : Array[Byte],
format? : Av1ContainerFormat? = None,
track_id? : Int64? = None,
limits? : DecodeLimits = DecodeLimits::default(),
) -> Result[Av1DemuxedTrack, DecodeError] {
match av1_demux_validate_input(data, limits) {
Err(error) => return Err(error)
Ok(_) => ()
}
let selected = match format {
Some(value) => value
None => {
if data.length() < 4 {
return Err(
decode_error(TruncatedInput, "demux", "Incomplete file signature"),
)
}
if data[0] == b'D' &&
data[1] == b'K' &&
data[2] == b'I' &&
data[3] == b'F' {
Ivf
} else if data[0] == 0x1A &&
data[1] == 0x45 &&
data[2] == 0xDF &&
data[3] == 0xA3 {
WebM
} else {
if data.length() < 8 {
return Err(
decode_error(TruncatedInput, "demux", "Incomplete container header"),
)
}
let kind = (data[4].to_int() << 24) |
(data[5].to_int() << 16) |
(data[6].to_int() << 8) |
data[7].to_int()
match kind {
// ftyp, styp, moov, moof, mdat, free, skip, wide, uuid
0x66747970
| 0x73747970
| 0x6D6F6F76
| 0x6D6F6F66
| 0x6D646174
| 0x66726565
| 0x736B6970
| 0x77696465
| 0x75756964 => Mp4
_ =>
return Err(
decode_error(Unsupported, "demux", "Unrecognized video container"),
)
}
}
}
}
match selected {
Ivf => {
if track_id is Some(id) && id != 0L {
return Err(
decode_error(InvalidData, "ivf.track", "IVF has only track zero"),
)
}
av1_demux_ivf(data, limits~)
}
Mp4 => av1_demux_mp4(data, track_id~, limits~)
WebM => av1_demux_webm(data, track_id~, limits~)
}
}