Files
deckcraft/crates/av1/src/lib.rs
T
Brandon ThomasandClaude Opus 5.5 4375fe7b89 Media playback: slidecraft-media (probe, audio decode, H.264/HEVC/VP9/AV1 video, mixer and clock), cpal output, editor control bar, slide show playback, media.* commands
- crates/media: probe (duration, rate/channels, size/fps; WMA/WMV recognised), audio decode to
  f32 PCM (MP3, AAC M4A/ADTS, ALAC, WAV, AIFF, CAF, FLAC, Vorbis via symphonia; Opus with our
  decoder in Ogg/WebM/MKV), video decode (MP4/MOV/WebM/MKV) with YUV->RGBA, poster frames,
  trim/fade/volume, Player mixer + per-voice clock (device-clocked or wall-clocked), VideoFeed.
- Codec crates ported from FilmCraft: bitstream, h264, hevc, vp9, av1, isobmff, matroska, ogg, opus.
- insert.audio/insert.video probe and store duration/size and a poster frame; unplayable media
  is embedded with a warning. New media.play/pause/toggle/stop/seek/info/posterFrame.
- UI: MediaHost (cpal output injected by the desktop app), control bar under media, video frames
  on canvas and in the show; autoplay, click to play, loop, rewind, across slides, hide, full screen.
- Tests with in-code WAV/AIFF and CC0 self-generated fixtures (make.sh, ATTRIBUTION.md rows).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 01:35:09 -04:00

124 lines
3.9 KiB
Rust

//! Clean-room AV1 decoder, implemented from the AV1 Bitstream & Decoding Process Specification
//! v1.0.0 with Errata 1 (AOMediaCodec/av1-spec).
//!
//! See the crate README for the conformance status of each decoding stage.
// Items used only by stages still being written (inter prediction, post filters).
// Spec-style indexed loops (FilmCraft allows this lint workspace-wide).
#![allow(clippy::needless_range_loop)]
#![allow(dead_code)]
#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable))]
mod bits;
mod cdef;
mod cdf;
mod decoder;
mod frame;
mod grain;
pub mod header;
mod inter;
mod inter_info;
mod intra;
mod mvpred;
mod par;
mod postfilter;
mod restoration;
mod spec_tables;
mod state;
mod stats;
mod symbol;
mod tile;
mod transform;
pub use decoder::Decoder;
pub use header::{ColorConfig, FrameHeader, SequenceHeader};
pub use stats::{DecodeStats, Stage};
/// Decoder errors.
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum Error {
#[error("truncated AV1 data")]
Truncated,
#[error("invalid AV1 data: {0}")]
Invalid(&'static str),
#[error("unsupported AV1 feature: {0}")]
Unsupported(&'static str),
}
pub type Result<T> = std::result::Result<T, Error>;
/// A decoded (shown) frame: planar samples without row padding, `bit_depth` significant bits in
/// `u16`. Chroma planes are `((width + ssx) >> ssx) x ((height + ssy) >> ssy)`; they are empty
/// for monochrome streams.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Picture {
pub width: u32,
pub height: u32,
pub bit_depth: u8,
pub subsampling_x: u8,
pub subsampling_y: u8,
pub mono_chrome: bool,
pub planes: [Vec<u16>; 3],
pub color_primaries: u8,
pub transfer_characteristics: u8,
pub matrix_coefficients: u8,
pub full_range: bool,
/// Timestamp of the temporal unit that showed the picture ([`Decoder::decode_pts`]).
pub pts: i64,
/// Decoded in draft mode without deblocking / CDEF / loop restoration
/// ([`Decoder::set_draft`]): approximate, for reduced-resolution playback only.
pub draft: bool,
}
impl Picture {
pub fn plane_width(&self, plane: usize) -> u32 {
if plane == 0 { self.width } else { (self.width + self.subsampling_x as u32) >> self.subsampling_x }
}
pub fn plane_height(&self, plane: usize) -> u32 {
if plane == 0 { self.height } else { (self.height + self.subsampling_y as u32) >> self.subsampling_y }
}
}
/// Whether a temporal unit starts a random access point: its first frame is a shown key frame
/// (used for keyframe detection: containers may flag samples as sync that are not).
pub fn is_key_frame_unit(data: &[u8]) -> bool {
let mut pos = 0;
let mut seq_seen = false;
while pos < data.len() {
let h = data[pos];
let obu_type = (h >> 3) & 0xf;
let ext = h & 4 != 0;
let has_size = h & 2 != 0;
let mut p = pos + 1 + ext as usize;
let size = if has_size {
match bits::leb128(data.get(p..).unwrap_or(&[])) {
Ok((v, n)) => {
p += n;
v as usize
}
Err(_) => return false,
}
} else {
data.len().saturating_sub(p)
};
if p > data.len() {
return false;
}
match obu_type {
1 => seq_seen = true,
3 | 6 => {
let b = data.get(p).copied().unwrap_or(0);
// show_existing_frame = 0, frame_type = KEY_FRAME (0), show_frame = 1
let show_existing = b >> 7;
let frame_type = (b >> 5) & 3;
let show_frame = (b >> 4) & 1;
let _ = seq_seen;
return show_existing == 0 && frame_type == 0 && show_frame == 1;
}
_ => {}
}
pos = p.saturating_add(size);
}
false
}