mirror of
https://github.com/storytold/filmcraft.git
synced 2026-10-14 19:26:59 -04:00
Stateless VA-API decoding with FilmCraft's own H.264 host side: the software decoder's SPS/PPS/slice-header parsers and its DPB (now generic over the picture type in filmcraft-h264) drive the GPU, so reference marking, reference lists and output order are shared and the pictures are bit-exact with the software decoder. libva is loaded at run time; no libva or driver means software decoding as before. frame_num gaps (open-GOP seeks) get the software decoder's stand-in frames. HybridDecoder keeps the transparent software fallback. Tests: hardware parity on 7 H.264 streams incl. 1080p/2160p, seeks, flushes, forced failures, fuzzing, concurrency; front end against a recording stand-in on every OS; ABI checks against va.h. Docs: platform README, ADR 0001 addendum, architecture, performance (H.264 2160p CPU per frame 417 -> 11 ms on Iris Xe), roadmap. Co-Authored-By: Claude Opus 5.5 <[email protected]>
625 lines
24 KiB
Rust
625 lines
24 KiB
Rust
//! Top-level decoder: NAL dispatch, picture boundaries, POC, DPB and output.
|
|
//!
|
|
//! The calling thread parses headers and does all DPB / reference list bookkeeping (which only needs
|
|
//! header information). Macroblock decoding of each picture runs as a job — on a thread pool with the
|
|
//! `threads` feature — that publishes finished macroblock rows into the picture's shared [`Frame`];
|
|
//! jobs of later pictures block per row on the reference data they need, so several pictures decode
|
|
//! concurrently (frame-level parallelism).
|
|
|
|
use crate::dpb::{Dpb, Output, OutputMeta, RefMark};
|
|
use crate::error::{Error, Result, ensure, invalid, unsupported};
|
|
use crate::params::{Pps, Sps};
|
|
use crate::picture::{Frame, FrameRef, MbKind, MbState, Planes, RefPic};
|
|
use crate::slice::{NalHeader, Poc, PocState, SliceHeader, SliceType, is_new_picture, nal_type};
|
|
use crate::slicedec::{PicState, SliceDecoder};
|
|
use crate::transform::LevelScale;
|
|
use crate::{ColorInfo, Picture};
|
|
use filmcraft_bitstream::{BitReader, annexb_nals, length_prefixed_nals, unescape_rbsp};
|
|
use std::collections::VecDeque;
|
|
use std::sync::{Arc, Mutex};
|
|
|
|
/// One slice of a picture job.
|
|
struct SliceJob {
|
|
sh: SliceHeader,
|
|
pps: Arc<Pps>,
|
|
sps: Arc<Sps>,
|
|
rbsp: Vec<u8>,
|
|
refs: [Vec<RefPic>; 2],
|
|
ls: Arc<LevelScale>,
|
|
}
|
|
|
|
/// A picture whose slices are being collected.
|
|
struct PendingPic {
|
|
frame: FrameRef,
|
|
sps: Arc<Sps>,
|
|
first: SliceHeader,
|
|
poc: Poc,
|
|
pts: i64,
|
|
key: bool,
|
|
slices: Vec<SliceJob>,
|
|
}
|
|
|
|
/// Counters describing what the decoder has seen (useful to check test coverage).
|
|
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
|
pub struct DecodeStats {
|
|
pub pictures: u64,
|
|
pub slices_cavlc: u64,
|
|
pub slices_cabac: u64,
|
|
pub slices_i: u64,
|
|
pub slices_p: u64,
|
|
pub slices_b: u64,
|
|
pub mb_i4x4: u64,
|
|
pub mb_i8x8: u64,
|
|
pub mb_i16x16: u64,
|
|
pub mb_pcm: u64,
|
|
pub mb_p_skip: u64,
|
|
pub mb_b_skip: u64,
|
|
pub mb_b_direct16x16: u64,
|
|
pub mb_inter: u64,
|
|
/// Inter macroblocks using the 8x8 transform.
|
|
pub mb_inter_8x8_transform: u64,
|
|
pub mmco_ops: u64,
|
|
pub long_term_marks: u64,
|
|
pub frame_num_gaps: u64,
|
|
pub weighted_slices: u64,
|
|
pub temporal_direct_slices: u64,
|
|
/// Non-reference pictures decoded in draft mode (deblocking skipped).
|
|
pub draft_pictures: u64,
|
|
}
|
|
|
|
/// State shared with decoding jobs.
|
|
#[derive(Default)]
|
|
struct Shared {
|
|
error: Mutex<Option<Error>>,
|
|
stats: Mutex<DecodeStats>,
|
|
/// Recycled picture buffers.
|
|
pool: Mutex<Vec<PicState>>,
|
|
}
|
|
|
|
/// H.264 decoder.
|
|
pub struct Decoder {
|
|
spss: Vec<Option<Arc<Sps>>>,
|
|
ppss: Vec<Option<Arc<Pps>>>,
|
|
nal_length_size: Option<usize>,
|
|
dpb: Dpb<FrameRef>,
|
|
poc_state: PocState,
|
|
prev_ref_frame_num: u32,
|
|
pending: Option<PendingPic>,
|
|
next_id: u32,
|
|
active_sps: Option<Arc<Sps>>,
|
|
ls_cache: Vec<(Arc<Pps>, Arc<LevelScale>)>,
|
|
/// Pictures leaving the DPB, converted once their decoding job has finished.
|
|
out_queue: VecDeque<Output<FrameRef>>,
|
|
shared: Arc<Shared>,
|
|
#[cfg(feature = "threads")]
|
|
pool: Option<rayon::ThreadPool>,
|
|
in_flight: VecDeque<FrameRef>,
|
|
max_in_flight: usize,
|
|
threads: usize,
|
|
/// Draft mode: skip deblocking of non-reference pictures ([`Decoder::set_draft`]).
|
|
draft: bool,
|
|
}
|
|
|
|
impl Default for Decoder {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
/// The worker threads [`Decoder::new`] uses: the available cores (at most 16) with the `threads`
|
|
/// feature on native targets, 1 otherwise.
|
|
pub fn default_threads() -> usize {
|
|
#[cfg(all(feature = "threads", not(target_arch = "wasm32")))]
|
|
{
|
|
std::thread::available_parallelism().map(|n| n.get()).unwrap_or(1).min(16)
|
|
}
|
|
#[cfg(not(all(feature = "threads", not(target_arch = "wasm32"))))]
|
|
{
|
|
1
|
|
}
|
|
}
|
|
|
|
impl Decoder {
|
|
/// A decoder using all available cores (with the `threads` feature).
|
|
pub fn new() -> Self {
|
|
Self::with_threads(default_threads())
|
|
}
|
|
|
|
/// A decoder using up to `threads` worker threads (1 = decode on the calling thread).
|
|
pub fn with_threads(threads: usize) -> Self {
|
|
crate::cavlc::init_tables();
|
|
#[cfg(feature = "threads")]
|
|
let pool = if threads > 1 && cfg!(not(target_arch = "wasm32")) {
|
|
rayon::ThreadPoolBuilder::new().num_threads(threads).thread_name(|i| format!("h264-{i}")).build().ok()
|
|
} else {
|
|
None
|
|
};
|
|
#[cfg(feature = "threads")]
|
|
let workers = if pool.is_some() { threads } else { 1 };
|
|
#[cfg(not(feature = "threads"))]
|
|
let workers = 1;
|
|
Decoder {
|
|
spss: vec![None; 32],
|
|
ppss: vec![None; 256],
|
|
nal_length_size: None,
|
|
dpb: Dpb::new(),
|
|
poc_state: PocState::default(),
|
|
prev_ref_frame_num: 0,
|
|
pending: None,
|
|
next_id: 1,
|
|
active_sps: None,
|
|
ls_cache: Vec::new(),
|
|
out_queue: VecDeque::new(),
|
|
shared: Arc::new(Shared::default()),
|
|
#[cfg(feature = "threads")]
|
|
pool,
|
|
in_flight: VecDeque::new(),
|
|
max_in_flight: threads.max(1) + 2,
|
|
threads: workers,
|
|
draft: false,
|
|
}
|
|
}
|
|
|
|
/// Worker threads decoding pictures (1: on the calling thread).
|
|
pub fn threads(&self) -> usize {
|
|
self.threads
|
|
}
|
|
|
|
/// Draft mode (off by default) for reduced-resolution playback: pictures submitted from now
|
|
/// on that are non-reference pictures (`nal_ref_idc` 0) skip the deblocking filter (8.7).
|
|
/// Nothing predicts from a non-reference picture (and deblocking never changes the motion
|
|
/// data used for direct prediction), so every other picture stays bit-exact; the draft
|
|
/// pictures themselves are approximate and flagged [`Picture::draft`]. Never use it for
|
|
/// export or for a frame shown while paused.
|
|
pub fn set_draft(&mut self, on: bool) {
|
|
self.draft = on;
|
|
}
|
|
|
|
pub fn draft(&self) -> bool {
|
|
self.draft
|
|
}
|
|
|
|
/// Configure from an `avcC` (AVCDecoderConfigurationRecord) box payload.
|
|
pub fn from_avcc(avcc: &[u8]) -> Result<Self> {
|
|
let mut d = Decoder::new();
|
|
d.configure_avcc(avcc)?;
|
|
Ok(d)
|
|
}
|
|
|
|
/// Parse an `avcC` record: SPS/PPS and the NAL length size used by [`Decoder::decode`].
|
|
pub fn configure_avcc(&mut self, avcc: &[u8]) -> Result<()> {
|
|
ensure!(avcc.len() >= 7, "avcC too short");
|
|
ensure!(avcc[0] == 1, "unsupported avcC version {}", avcc[0]);
|
|
self.nal_length_size = Some((avcc[4] & 3) as usize + 1);
|
|
let mut pos = 5;
|
|
let read_sets = |pos: &mut usize, count: usize, d: &mut Decoder| -> Result<()> {
|
|
for _ in 0..count {
|
|
ensure!(*pos + 2 <= avcc.len(), "avcC truncated");
|
|
let len = u16::from_be_bytes([avcc[*pos], avcc[*pos + 1]]) as usize;
|
|
*pos += 2;
|
|
ensure!(*pos + len <= avcc.len(), "avcC truncated");
|
|
d.handle_nal(&avcc[*pos..*pos + len], 0)?;
|
|
*pos += len;
|
|
}
|
|
Ok(())
|
|
};
|
|
let nsps = (avcc[pos] & 0x1f) as usize;
|
|
pos += 1;
|
|
read_sets(&mut pos, nsps, self)?;
|
|
ensure!(pos < avcc.len(), "avcC truncated");
|
|
let npps = avcc[pos] as usize;
|
|
pos += 1;
|
|
read_sets(&mut pos, npps, self)
|
|
}
|
|
|
|
/// Length of NAL length prefixes (from avcC), or None for Annex-B input.
|
|
pub fn nal_length_size(&self) -> Option<usize> {
|
|
self.nal_length_size
|
|
}
|
|
|
|
/// Decode one access unit (all NAL units of one picture, Annex-B or length-prefixed per
|
|
/// configuration; a byte stream with several complete access units is accepted too). Returns
|
|
/// pictures that became ready for output, in output order; `pts` travels with the picture of
|
|
/// this access unit through reordering.
|
|
pub fn decode(&mut self, data: &[u8], pts: i64) -> Result<Vec<Picture>> {
|
|
let nals = match self.nal_length_size {
|
|
Some(n) => length_prefixed_nals(data, n)?,
|
|
None => annexb_nals(data),
|
|
};
|
|
let mut result = Ok(());
|
|
for nal in nals {
|
|
if let Err(e) = self.handle_nal(nal, pts) {
|
|
result = Err(e);
|
|
break;
|
|
}
|
|
}
|
|
// The access unit is complete: hand its picture to a decoding job.
|
|
self.submit_pending();
|
|
if let Some(e) = self.take_error() {
|
|
result = result.and(Err(e));
|
|
}
|
|
result?;
|
|
// Return finished pictures without blocking on frames still being decoded (bounded latency).
|
|
let max_queue = self.max_in_flight * 2;
|
|
let mut pics = Vec::new();
|
|
while let Some(o) = self.out_queue.front() {
|
|
if !o.frame.is_published(o.frame.mb_h() - 1) && self.out_queue.len() <= max_queue {
|
|
break;
|
|
}
|
|
let Some(o) = self.out_queue.pop_front() else { break };
|
|
pics.push(make_picture(&o));
|
|
}
|
|
Ok(pics)
|
|
}
|
|
|
|
/// Output all remaining pictures (end of stream).
|
|
pub fn flush(&mut self) -> Vec<Picture> {
|
|
self.submit_pending();
|
|
let mut outs = Vec::new();
|
|
self.dpb.flush(&mut outs);
|
|
self.emit(outs);
|
|
let pics = self.out_queue.drain(..).map(|o| make_picture(&o)).collect();
|
|
while let Some(f) = self.in_flight.pop_front() {
|
|
f.wait_complete();
|
|
}
|
|
pics
|
|
}
|
|
|
|
/// Statistics accumulated so far (macroblock counts include finished pictures only).
|
|
pub fn stats(&self) -> DecodeStats {
|
|
self.shared.stats.lock().map(|s| s.clone()).unwrap_or_default()
|
|
}
|
|
|
|
fn stat(&self, f: impl FnOnce(&mut DecodeStats)) {
|
|
if let Ok(mut s) = self.shared.stats.lock() {
|
|
f(&mut s);
|
|
}
|
|
}
|
|
|
|
/// First error reported by a decoding job since the last call (also returned by
|
|
/// [`Decoder::decode`]); useful after [`Decoder::flush`].
|
|
pub fn take_error(&mut self) -> Option<Error> {
|
|
self.shared.error.lock().ok().and_then(|mut e| e.take())
|
|
}
|
|
|
|
fn handle_nal(&mut self, nal: &[u8], pts: i64) -> Result<()> {
|
|
if nal.is_empty() {
|
|
return Ok(());
|
|
}
|
|
let hdr = NalHeader::parse(nal[0])?;
|
|
match hdr.nal_unit_type {
|
|
nal_type::SPS => {
|
|
let sps = Sps::parse(&unescape_rbsp(&nal[1..]))?;
|
|
let id = sps.id as usize;
|
|
self.spss[id] = Some(Arc::new(sps));
|
|
}
|
|
nal_type::PPS => {
|
|
let rbsp = unescape_rbsp(&nal[1..]);
|
|
let spss: Vec<Option<Sps>> = self.spss.iter().map(|s| s.as_ref().map(|s| (**s).clone())).collect();
|
|
let pps = Pps::parse(&rbsp, &spss)?;
|
|
let id = pps.id as usize;
|
|
self.ppss[id] = Some(Arc::new(pps));
|
|
}
|
|
nal_type::SLICE | nal_type::IDR => self.handle_slice(nal, hdr, pts)?,
|
|
nal_type::SLICE_DPA | nal_type::SLICE_DPB | nal_type::SLICE_DPC => {
|
|
return unsupported("data partitioning (Extended profile)");
|
|
}
|
|
nal_type::END_SEQ | nal_type::END_STREAM => self.submit_pending(),
|
|
// SEI, AUD, filler, SPS extension, prefix NAL, subset SPS, auxiliary and extension slices: skipped.
|
|
_ => {}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn level_scale(&mut self, pps: &Arc<Pps>) -> Arc<LevelScale> {
|
|
if let Some((_, ls)) = self.ls_cache.iter().find(|(p, _)| Arc::ptr_eq(p, pps)) {
|
|
return ls.clone();
|
|
}
|
|
let ls: Arc<LevelScale> = Arc::from(LevelScale::new(&pps.scaling));
|
|
self.ls_cache.retain(|(p, _)| self.ppss.iter().flatten().any(|q| Arc::ptr_eq(p, q)));
|
|
self.ls_cache.push((pps.clone(), ls.clone()));
|
|
ls
|
|
}
|
|
|
|
fn handle_slice(&mut self, nal: &[u8], hdr: NalHeader, pts: i64) -> Result<()> {
|
|
let rbsp = unescape_rbsp(&nal[1..]);
|
|
let ppss = &self.ppss;
|
|
let spss = &self.spss;
|
|
let mut found: Option<(Arc<Pps>, Arc<Sps>)> = None;
|
|
let (sh, _, _) = SliceHeader::parse(&rbsp, hdr, |id| {
|
|
let pps = ppss[id as usize].as_ref().ok_or_else(|| Error::MissingParameterSet(format!("PPS {id}")))?;
|
|
let sps = spss[pps.sps_id as usize].as_ref().ok_or_else(|| Error::MissingParameterSet(format!("SPS {}", pps.sps_id)))?;
|
|
found = Some((pps.clone(), sps.clone()));
|
|
Ok((&**pps, &**sps))
|
|
})?;
|
|
let Some((pps, sps)) = found else {
|
|
return Err(Error::MissingParameterSet("PPS".into()));
|
|
};
|
|
sps.check_supported()?;
|
|
if pps.num_slice_groups > 1 {
|
|
return unsupported("slice groups (FMO)");
|
|
}
|
|
if sh.field_pic {
|
|
return unsupported("field pictures");
|
|
}
|
|
if matches!(sh.slice_type, SliceType::Sp | SliceType::Si) {
|
|
return unsupported("SP/SI slices");
|
|
}
|
|
if sh.redundant_pic_cnt > 0 {
|
|
return Ok(()); // redundant slices are ignored
|
|
}
|
|
let new_pic = match &self.pending {
|
|
None => true,
|
|
Some(p) => is_new_picture(&p.first, &sh, &sps) || sh.first_mb_in_slice == 0 || !Arc::ptr_eq(&p.sps, &sps),
|
|
};
|
|
if new_pic {
|
|
self.submit_pending();
|
|
if sh.first_mb_in_slice != 0 && self.active_sps.is_none() {
|
|
return invalid("stream does not start with the first slice of a picture");
|
|
}
|
|
self.start_picture(&sh, &sps, pts)?;
|
|
}
|
|
let ls = self.level_scale(&pps);
|
|
let Some(pending) = self.pending.as_mut() else {
|
|
return invalid("slice without a started picture");
|
|
};
|
|
let refs = self
|
|
.dpb
|
|
.build_ref_lists(&sh, pending.poc.frame(), sps.max_frame_num(), |e| RefPic { frame: e.frame.clone(), long_term: e.mark == RefMark::Long })?;
|
|
if !sh.slice_type.is_intra() {
|
|
ensure!(!refs[0].is_empty(), "no reference pictures available for inter slice");
|
|
if sh.slice_type.is_b() {
|
|
ensure!(!refs[1].is_empty(), "empty RefPicList1 in B slice");
|
|
}
|
|
}
|
|
let weighted = (pps.weighted_pred && sh.slice_type.is_p()) || (pps.weighted_bipred_idc != 0 && sh.slice_type.is_b());
|
|
let temporal = sh.slice_type.is_b() && !sh.direct_spatial_mv_pred;
|
|
let (cabac, st, mmcos, lt) = (
|
|
pps.entropy_coding_mode,
|
|
sh.slice_type,
|
|
sh.mmcos.len() as u64,
|
|
sh.long_term_reference as u64 + sh.mmcos.iter().filter(|m| m.op == 3 || m.op == 6).count() as u64,
|
|
);
|
|
pending.slices.push(SliceJob { sh, pps, sps, rbsp, refs, ls });
|
|
self.stat(|s| {
|
|
if cabac {
|
|
s.slices_cabac += 1;
|
|
} else {
|
|
s.slices_cavlc += 1;
|
|
}
|
|
match st {
|
|
SliceType::I | SliceType::Si => s.slices_i += 1,
|
|
SliceType::P | SliceType::Sp => s.slices_p += 1,
|
|
SliceType::B => s.slices_b += 1,
|
|
}
|
|
s.weighted_slices += weighted as u64;
|
|
s.temporal_direct_slices += temporal as u64;
|
|
s.mmco_ops += mmcos;
|
|
s.long_term_marks += lt;
|
|
});
|
|
Ok(())
|
|
}
|
|
|
|
fn start_picture(&mut self, sh: &SliceHeader, sps: &Arc<Sps>, pts: i64) -> Result<()> {
|
|
// Activate SPS; a resolution change flushes the DPB.
|
|
let changed = match &self.active_sps {
|
|
None => true,
|
|
Some(a) => a.width() != sps.width() || a.height() != sps.height() || a.max_dpb_frames() != sps.max_dpb_frames(),
|
|
};
|
|
if changed && self.active_sps.is_some() {
|
|
let mut outs = Vec::new();
|
|
self.dpb.flush(&mut outs);
|
|
self.emit(outs);
|
|
self.dpb.entries.clear();
|
|
}
|
|
self.active_sps = Some(sps.clone());
|
|
self.dpb.capacity = sps.max_dpb_frames();
|
|
self.dpb.max_reorder = sps.max_num_reorder_frames().min(self.dpb.capacity);
|
|
let mb_w = sps.pic_width_in_mbs as usize;
|
|
let mb_h = sps.frame_height_in_mbs() as usize;
|
|
if sh.idr {
|
|
let mut outs = Vec::new();
|
|
self.dpb.idr(sh.no_output_of_prior_pics, &mut outs);
|
|
self.emit(outs);
|
|
self.prev_ref_frame_num = 0;
|
|
} else {
|
|
let max = sps.max_frame_num();
|
|
if sh.frame_num != self.prev_ref_frame_num && sh.frame_num != (self.prev_ref_frame_num + 1) % max {
|
|
// frame_num gap (8.2.5.2): insert "non-existing" frames. Their samples are never used by
|
|
// conforming streams; copy the latest decoded frame for concealment.
|
|
let meta = Arc::new(output_meta(sps, pts, false));
|
|
let next_id = &mut self.next_id;
|
|
let poc_state = &mut self.poc_state;
|
|
let last = self.dpb.entries.iter().filter(|e| !e.non_existing).max_by_key(|e| e.frame.id).map(|e| e.frame.clone());
|
|
let mut make = |_fnum: u32| {
|
|
let id = *next_id;
|
|
*next_id += 1;
|
|
let planes = match &last {
|
|
Some(f) if f.mb_w == mb_w && f.mb_h() == mb_h => {
|
|
let mut p = Planes::new(mb_w * 16, mb_h * 16);
|
|
let (y, u, v) = f.copy_cropped((0, 0, mb_w * 16, mb_h * 16));
|
|
p.y = y;
|
|
p.cb = u;
|
|
p.cr = v;
|
|
p
|
|
}
|
|
_ => Planes::gray(mb_w * 16, mb_h * 16),
|
|
};
|
|
(Arc::new(Frame::from_planes(id, 0, &planes)), 0, meta.clone())
|
|
};
|
|
let mut upd = |fnum: u32| poc_state.update_gap_frame(fnum, sps);
|
|
self.shared.stats.lock().map(|mut s| s.frame_num_gaps += 1).ok();
|
|
self.dpb.fill_frame_num_gap(self.prev_ref_frame_num, sh.frame_num, max, sps.max_num_ref_frames as usize, &mut make, &mut upd);
|
|
self.prev_ref_frame_num = (sh.frame_num + max - 1) % max;
|
|
}
|
|
}
|
|
let poc = self.poc_state.compute(sh, sps);
|
|
// MMCO5: the picture's POC becomes relative to itself (tempPicOrderCnt, 8.2.1).
|
|
let frame_poc = if sh.has_mmco5() { 0 } else { poc.frame() };
|
|
let id = self.next_id;
|
|
self.next_id += 1;
|
|
let frame = Arc::new(Frame::new(id, frame_poc, mb_w, mb_h));
|
|
self.pending = Some(PendingPic { frame, sps: sps.clone(), first: sh.clone(), poc, pts, key: sh.idr, slices: Vec::new() });
|
|
Ok(())
|
|
}
|
|
|
|
/// Start decoding the pending picture and do its reference marking / DPB insertion.
|
|
fn submit_pending(&mut self) {
|
|
let Some(p) = self.pending.take() else { return };
|
|
let PendingPic { frame, sps, first, poc, pts, key, slices } = p;
|
|
let draft = self.draft && first.nal_ref_idc == 0;
|
|
self.dispatch(frame.clone(), slices, draft);
|
|
self.poc_state.update(&first, &poc);
|
|
if first.nal_ref_idc != 0 {
|
|
self.prev_ref_frame_num = if first.has_mmco5() { 0 } else { first.frame_num };
|
|
}
|
|
let meta = Arc::new(OutputMeta { draft, ..output_meta(&sps, pts, key) });
|
|
let mut outs = Vec::new();
|
|
let fpoc = frame.poc;
|
|
self.dpb.store_picture(&first, frame, fpoc, sps.max_frame_num(), sps.max_num_ref_frames as usize, meta, &mut outs);
|
|
self.emit(outs);
|
|
}
|
|
|
|
fn dispatch(&mut self, frame: FrameRef, slices: Vec<SliceJob>, draft: bool) {
|
|
let shared = self.shared.clone();
|
|
#[cfg(feature = "threads")]
|
|
if let Some(pool) = &self.pool {
|
|
self.in_flight.retain(|f| !f.is_published(f.mb_h() - 1));
|
|
while self.in_flight.len() >= self.max_in_flight {
|
|
if let Some(f) = self.in_flight.pop_front() {
|
|
f.wait_complete();
|
|
}
|
|
}
|
|
self.in_flight.push_back(frame.clone());
|
|
pool.spawn_fifo(move || run_job(frame, slices, &shared, draft));
|
|
return;
|
|
}
|
|
run_job(frame, slices, &shared, draft);
|
|
}
|
|
|
|
fn emit(&mut self, outs: Vec<Output<FrameRef>>) {
|
|
self.out_queue.extend(outs);
|
|
}
|
|
}
|
|
|
|
/// Decode all slices of one picture and publish its rows.
|
|
fn run_job(frame: FrameRef, slices: Vec<SliceJob>, shared: &Shared, draft: bool) {
|
|
let reuse = shared.pool.lock().ok().and_then(|mut p| p.pop());
|
|
let mut pic = match reuse {
|
|
Some(mut p) if p.mb_w == frame.mb_w && p.mb_h == frame.mb_h() => {
|
|
p.reset(frame.clone());
|
|
p
|
|
}
|
|
_ => PicState::new(frame.clone()),
|
|
};
|
|
pic.skip_deblock = draft;
|
|
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
|
let mut first_err = None;
|
|
for s in &slices {
|
|
if let Err(e) = decode_slice(s, &mut pic) {
|
|
first_err.get_or_insert(e);
|
|
}
|
|
}
|
|
pic.flush_rows(true);
|
|
first_err
|
|
}));
|
|
let err = match result {
|
|
Ok(e) => e,
|
|
Err(_) => {
|
|
// Make sure readers never block on this frame.
|
|
let fallback = Planes::gray(frame.width, frame.height);
|
|
let blank = vec![MbState::default(); frame.mb_w * frame.mb_h()];
|
|
for r in 0..frame.mb_h() {
|
|
if !frame.is_published(r) {
|
|
frame.publish(r, Frame::make_row(&fallback, r, &blank, &|_, _, _| u32::MAX));
|
|
}
|
|
}
|
|
pic = PicState::new(frame.clone());
|
|
Some(Error::Invalid("internal error while decoding a picture".into()))
|
|
}
|
|
};
|
|
if let Ok(mut s) = shared.stats.lock() {
|
|
s.pictures += 1;
|
|
s.draft_pictures += draft as u64;
|
|
for st in &pic.mbs {
|
|
match st.kind {
|
|
MbKind::I4x4 => s.mb_i4x4 += 1,
|
|
MbKind::I8x8 => s.mb_i8x8 += 1,
|
|
MbKind::I16x16 => s.mb_i16x16 += 1,
|
|
MbKind::IPcm => s.mb_pcm += 1,
|
|
MbKind::PSkip => s.mb_p_skip += 1,
|
|
MbKind::BSkip => s.mb_b_skip += 1,
|
|
MbKind::BDirect16x16 => s.mb_b_direct16x16 += 1,
|
|
MbKind::Inter => s.mb_inter += 1,
|
|
MbKind::None => {}
|
|
}
|
|
if st.transform_8x8 && !st.kind.is_intra() {
|
|
s.mb_inter_8x8_transform += 1;
|
|
}
|
|
}
|
|
}
|
|
if let (Some(e), Ok(mut slot)) = (err, shared.error.lock()) {
|
|
slot.get_or_insert(e);
|
|
}
|
|
if let Ok(mut p) = shared.pool.lock()
|
|
&& p.len() < 4
|
|
{
|
|
p.push(pic);
|
|
}
|
|
}
|
|
|
|
fn decode_slice(s: &SliceJob, pic: &mut PicState) -> Result<()> {
|
|
let mut sd = SliceDecoder::new(&s.sh, &s.pps, &s.sps, pic, &s.refs, &s.ls)?;
|
|
if s.pps.entropy_coding_mode {
|
|
sd.decode_cabac(&s.rbsp)
|
|
} else {
|
|
let mut r = BitReader::new(&s.rbsp);
|
|
r.seek_bits(s.sh.header_bits);
|
|
sd.decode_cavlc(&mut r)
|
|
}
|
|
}
|
|
|
|
fn output_meta(sps: &Sps, pts: i64, key: bool) -> OutputMeta {
|
|
let vui = sps.vui.clone().unwrap_or_default();
|
|
OutputMeta {
|
|
pts,
|
|
key,
|
|
crop: sps.crop_rect(),
|
|
full_range: vui.full_range,
|
|
colour_primaries: vui.colour_primaries,
|
|
transfer_characteristics: vui.transfer_characteristics,
|
|
matrix_coefficients: vui.matrix_coefficients,
|
|
sar: vui.sar,
|
|
draft: false,
|
|
}
|
|
}
|
|
|
|
fn make_picture(o: &Output<FrameRef>) -> Picture {
|
|
let (cx, cy, cw, ch) = o.meta.crop;
|
|
let (cx, cy, cw, ch) = (cx as usize, cy as usize, cw as usize, ch as usize);
|
|
let (y, u, v) = o.frame.copy_cropped((cx, cy, cw, ch));
|
|
let (ccw, cch) = (cw.div_ceil(2), ch.div_ceil(2));
|
|
Picture {
|
|
width: cw as u32,
|
|
height: ch as u32,
|
|
chroma_width: ccw as u32,
|
|
chroma_height: cch as u32,
|
|
y,
|
|
u,
|
|
v,
|
|
y_stride: cw,
|
|
uv_stride: ccw,
|
|
pts: o.meta.pts,
|
|
poc: o.poc,
|
|
key: o.meta.key,
|
|
color: ColorInfo {
|
|
full_range: o.meta.full_range,
|
|
primaries: o.meta.colour_primaries,
|
|
transfer: o.meta.transfer_characteristics,
|
|
matrix: o.meta.matrix_coefficients,
|
|
},
|
|
sar: o.meta.sar,
|
|
draft: o.meta.draft,
|
|
}
|
|
}
|