Skip to main content

moq_vaapi/
decode.rs

1// Copyright 2022 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4//
5// Adapted from discord/cros-codecs (`decoder/stateless/h264.rs` and
6// `decoder/stateless/h264/vaapi.rs`), itself BSD-3-Clause / Copyright The
7// ChromiumOS Authors. See LICENSE.cros-codecs.
8
9//! Thin VA-API H.264 decoder built on the vendored libva binding.
10//!
11//! The mirror of [`crate::encode`]. It reuses the backend-agnostic bitstream
12//! layer ([`crate::codec::h264`]) from discord/cros-codecs (BSD-3-Clause) for
13//! SPS/PPS/slice parsing, reference marking, and DPB bumping, then drives libva
14//! directly rather than vendoring cros-codecs's generic multi-backend decoder
15//! framework. The per-picture and per-slice VA buffer population is ported from
16//! cros-codecs's `decoder/stateless/h264/vaapi.rs`.
17//!
18//! Feed [`Decoder::decode`] one Annex-B access unit at a time. Its picture is
19//! submitted and completed before the call returns, rather than when the next
20//! unit's first slice arrives, so the hardware is never left holding a
21//! half-submitted picture between calls.
22//!
23//! Output trails input, and by more than the stream's reorder depth: C.4.5.3
24//! bumps the DPB only when a new picture needs the slot, so the delay follows
25//! the sequence's reference and reorder limits. A stream coded with three
26//! reference frames trails by three pictures whether or not it uses B-frames.
27//! [`Frame`] therefore carries the timestamp of the access unit it was coded in,
28//! and [`Decoder::flush`] is what releases the tail at the end of a stream.
29//!
30//! Progressive 8-bit 4:2:0 only: a sequence that is interlaced, deeper than 8
31//! bits, or not 4:2:0 is rejected rather than decoded wrongly. That covers every
32//! stream this crate's encoder, WebRTC, or a browser's `VideoEncoder` produces.
33//! Left and top cropping are rejected; right and bottom cropping are supported.
34
35use std::cell::Cell;
36use std::io::Cursor;
37use std::path::PathBuf;
38use std::rc::Rc;
39use std::sync::{Arc, Mutex, Weak};
40
41use anyhow::{anyhow, bail, Context as _};
42
43use crate::codec::h264::dpb::{Dpb, DpbEntry, DpbPicRefList, MmcoError, ReferencePicLists};
44use crate::codec::h264::parser::{
45	Level, MaxLongTermFrameIdx, Nalu, NaluType, Parser, Pps, Profile, RefPicListModification, Slice, SliceHeader,
46	SliceType, Sps,
47};
48use crate::codec::h264::picture::{Field, IsIdr, PictureData, Reference};
49use crate::{
50	bindings, Buffer, BufferType, Config as VaConfig, Context, Display, DrmPrimeSurfaceDescriptor, H264PicFields,
51	H264SeqFields, IQMatrix, IQMatrixBufferH264, Image, Picture, PictureH264, PictureParameter,
52	PictureParameterBufferH264, SliceParameter, SliceParameterBufferH264, Surface, UsageHint, VAConfigAttrib,
53	VAConfigAttribType, VAEntrypoint, VAProfile, VA_FOURCC_NV12, VA_INVALID_ID, VA_PICTURE_H264_INVALID,
54	VA_PICTURE_H264_LONG_TERM_REFERENCE, VA_PICTURE_H264_SHORT_TERM_REFERENCE, VA_RT_FORMAT_YUV420,
55	VA_SLICE_DATA_FLAG_ALL,
56};
57
58/// H.264 decoder configuration.
59#[derive(Clone, Debug)]
60pub struct Config {
61	/// DRM render node to open (e.g. `/dev/dri/renderD128`).
62	///
63	/// `None`, the default, opens the first node whose driver can decode H.264.
64	pub device: Option<PathBuf>,
65}
66
67impl Config {
68	/// Returns a configuration that opens the first render node whose driver can decode H.264.
69	pub fn new() -> Self {
70		Self { device: None }
71	}
72}
73
74impl Default for Config {
75	fn default() -> Self {
76		Self::new()
77	}
78}
79
80/// One decoded picture, downloaded to client memory as tightly packed NV12.
81#[derive(Clone)]
82pub struct Frame {
83	/// Timestamp of the access unit this picture was coded in, as handed to
84	/// [`Decoder::decode`].
85	pub timestamp: u64,
86	/// Visible width in pixels, i.e. after the SPS cropping rectangle.
87	pub width: u32,
88	/// Visible height in pixels.
89	pub height: u32,
90	/// NV12 planes with no row padding: `width * height` luma bytes followed by
91	/// `width * height / 2` interleaved chroma bytes.
92	pub data: Vec<u8>,
93}
94
95/// One decoded picture, left in the surface the hardware wrote it into.
96///
97/// The GPU counterpart of [`Frame`]: same picture, same timestamp, but a DRM
98/// PRIME descriptor for the surface rather than a copy of its pixels. The
99/// descriptor owns the exported file descriptors and the surface travels with
100/// it, so both the allocation and libva's handle on it outlive the decoder.
101///
102/// The export is NV12 in one object holding both planes, which is what the
103/// Intel and AMD drivers produce. Read the modifier off the object before
104/// assuming anything about the layout: a decode target is tiled, and the plane
105/// pitches carry padding the visible size does not imply. That is also why
106/// [`ExportedFrame::download`] goes back through the surface instead of reading
107/// the file descriptor as rows.
108///
109/// Dropping the frame hands its surface back to the decoder, which decodes a
110/// later picture into it. So keep the frame for as long as anything reads the
111/// descriptor or an import of it: a GPU consumer holds it until the work that
112/// samples it has finished, not merely until that work is submitted. A frame
113/// that outlives its decoder, or the sequence it was decoded in, releases its
114/// surface to the driver instead.
115///
116/// This is [`Send`] and [`Sync`] even though [`Decoder`] is neither: a surface
117/// is a display and an id, both of which libva serializes internally, and none
118/// of the decoder's `Rc`s come along.
119pub struct ExportedFrame {
120	/// Timestamp of the access unit this picture was coded in, as handed to
121	/// [`Decoder::decode_exported`].
122	pub timestamp: u64,
123	/// Visible width in pixels, i.e. after the SPS cropping rectangle.
124	pub width: u32,
125	/// Visible height in pixels.
126	pub height: u32,
127	/// The surface's export: format, modifier, and the offset and pitch of each
128	/// plane.
129	pub descriptor: DrmPrimeSurfaceDescriptor,
130	/// The surface the picture was decoded into, held here so the decoder does
131	/// not decode into it again until this drops, and so
132	/// [`ExportedFrame::download`] has something to map.
133	surface: Arc<PooledSurface>,
134}
135
136impl ExportedFrame {
137	/// Exports `surface`, an NV12 picture from somewhere other than the decoder, the way the decoder exports its own.
138	///
139	/// A picture the video processor produced, such as a scaled copy of a
140	/// captured frame, then reaches its consumer with the same descriptor and
141	/// the same [`download`](Self::download) fallback as a decoded one, so one
142	/// consumer handles both. `timestamp` is carried through untouched.
143	///
144	/// # Errors
145	///
146	/// Fails when the surface is not NV12, cannot be synced, or the driver will
147	/// not export it.
148	pub fn from_surface(surface: Surface<()>, timestamp: u64) -> anyhow::Result<Self> {
149		surface.sync().map_err(|e| anyhow!("surface sync: {e:?}"))?;
150		let (width, height) = surface.size();
151		let descriptor = surface
152			.export_prime()
153			.map_err(|e| anyhow!("export the surface: {e:?}"))?;
154		if descriptor.fourcc != VA_FOURCC_NV12 {
155			anyhow::bail!("exported fourcc {:#010x}, expected NV12", descriptor.fourcc);
156		}
157		Ok(Self {
158			timestamp,
159			width,
160			height,
161			descriptor,
162			surface: Arc::new(PooledSurface::unpooled(surface)),
163		})
164	}
165
166	/// Reads this picture back to client memory as tightly packed NV12.
167	///
168	/// The way back for a caller that took a descriptor and then found itself
169	/// with a consumer that wants bytes. It costs the read [`Decoder::decode`]
170	/// would have done up front, so prefer that entry point when nothing on the
171	/// path draws on the GPU.
172	///
173	/// Same layout as [`Frame::data`], with the driver's row padding dropped.
174	pub fn download(&self) -> anyhow::Result<Frame> {
175		read_back(self.surface.get(), self.timestamp, self.width, self.height)
176	}
177}
178
179impl std::fmt::Debug for ExportedFrame {
180	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
181		f.debug_struct("ExportedFrame")
182			.field("timestamp", &self.timestamp)
183			.field("width", &self.width)
184			.field("height", &self.height)
185			.field("fourcc", &self.descriptor.fourcc)
186			.finish()
187	}
188}
189
190impl std::fmt::Debug for Frame {
191	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
192		f.debug_struct("Frame")
193			.field("timestamp", &self.timestamp)
194			.field("width", &self.width)
195			.field("height", &self.height)
196			.field("bytes", &self.data.len())
197			.finish()
198	}
199}
200
201/// A VA-API H.264 decoder. Built once, fed Annex-B access units, emits NV12.
202///
203/// # Examples
204///
205/// ```no_run
206/// use moq_vaapi::decode::{Config, Decoder};
207///
208/// # fn main() -> anyhow::Result<()> {
209/// # let access_units: Vec<Vec<u8>> = Vec::new();
210/// let mut decoder = Decoder::new(Config::new())?;
211/// let mut pictures = Vec::new();
212///
213/// for (index, access_unit) in access_units.iter().enumerate() {
214///     pictures.extend(decoder.decode(access_unit, index as u64)?);
215/// }
216///
217/// // Output trails input, so the tail of the stream only arrives here.
218/// pictures.extend(decoder.flush()?);
219/// # Ok(())
220/// # }
221/// ```
222pub struct Decoder {
223	parser: Parser,
224	dpb: Dpb<Handle>,
225	/// Cached variables from the previous reference picture.
226	prev_ref_pic_info: PrevReferencePicInfo,
227	/// Cached variables from the previous picture.
228	prev_pic_info: PrevPicInfo,
229	max_long_term_frame_idx: MaxLongTermFrameIdx,
230	/// VA state for the active sequence, rebuilt when the SPS changes something
231	/// the config, context, or surface pool depends on.
232	sequence: Option<Sequence>,
233	/// Pictures bumped out of the DPB, in output order, awaiting download.
234	ready: Vec<Handle>,
235	/// Drop order: everything above borrows the display.
236	display: Arc<Display>,
237}
238
239impl Decoder {
240	/// Opens [`Config::device`], or the first render node that decodes H.264,
241	/// and checks that the driver exposes an H.264 decode entrypoint.
242	///
243	/// The VA config, context, and surface pool are built later, from the first
244	/// SPS, since their profile and size come from the stream. This still fails
245	/// early enough for a caller to fall back to another decoder when libva is
246	/// missing, the node cannot be opened, or the driver decodes no H.264. A
247	/// named node is never swapped for another.
248	pub fn new(config: Config) -> anyhow::Result<Self> {
249		let (device, display) = match config.device {
250			Some(device) => {
251				let display =
252					Display::open_drm_display(&device).map_err(|e| anyhow!("open DRM display {device:?}: {e:?}"))?;
253				probe(&display)?;
254				(device, display)
255			}
256			None => {
257				crate::display::open_first(probe).map_err(|e| e.context("find a render node that decodes H.264"))?
258			}
259		};
260
261		log::info!("opened VA-API H.264 decoder on {device:?}");
262		Ok(Self {
263			parser: Parser::default(),
264			dpb: Dpb::default(),
265			prev_ref_pic_info: PrevReferencePicInfo::default(),
266			prev_pic_info: PrevPicInfo::default(),
267			max_long_term_frame_idx: MaxLongTermFrameIdx::default(),
268			sequence: None,
269			ready: Vec::new(),
270			display,
271		})
272	}
273
274	/// Decodes one Annex-B access unit, returning the pictures that came due, in
275	/// output order.
276	///
277	/// `timestamp` is opaque and travels with the picture coded in this access
278	/// unit, so it survives DPB reordering. Parameter sets are read from the
279	/// stream, so an access unit carrying only an SPS and a PPS decodes to no
280	/// frames.
281	pub fn decode(&mut self, access_unit: &[u8], timestamp: u64) -> anyhow::Result<Vec<Frame>> {
282		self.submit(access_unit, timestamp)?;
283		self.take_frames()
284	}
285
286	/// Decodes one Annex-B access unit, leaving the pictures that came due on
287	/// the GPU.
288	///
289	/// The same decode as [`Decoder::decode`], differing only in what the caller
290	/// gets back: a DRM PRIME descriptor for each picture's surface instead of a
291	/// copy of its pixels. For a consumer that draws on the GPU that is the
292	/// whole picture never touching system memory.
293	///
294	/// An exported surface stays out of the recycling pool until the
295	/// [`ExportedFrame`] drops, since the next picture would otherwise be decoded
296	/// over pixels the consumer still holds a descriptor for. A consumer that
297	/// lets each picture go once it has drawn it therefore costs no allocation
298	/// per picture; one that holds on to pictures costs one surface per picture
299	/// held.
300	///
301	/// A consumer that turns out to want bytes after all is not stuck: the
302	/// surface travels with the descriptor, and [`ExportedFrame::download`] reads
303	/// it back.
304	pub fn decode_exported(&mut self, access_unit: &[u8], timestamp: u64) -> anyhow::Result<Vec<ExportedFrame>> {
305		self.submit(access_unit, timestamp)?;
306		self.take_exported()
307	}
308
309	/// Decodes one access unit, leaving the pictures that came due in the ready
310	/// queue for whichever of [`Decoder::take_frames`] or
311	/// [`Decoder::take_exported`] the caller asked for.
312	fn submit(&mut self, access_unit: &[u8], timestamp: u64) -> anyhow::Result<()> {
313		let mut cursor = Cursor::new(access_unit);
314		let mut current: Option<CurrentPicture> = None;
315		let mut nalus = 0usize;
316
317		// `Nalu::next` reports the end of the buffer as an error, so a truncated
318		// unit is indistinguishable from a complete one and simply stops us.
319		while let Ok(nalu) = Nalu::next(&mut cursor) {
320			nalus += 1;
321			match nalu.header.type_ {
322				NaluType::Sps => {
323					self.parser.parse_sps(&nalu).map_err(|e| anyhow!("parse SPS: {e}"))?;
324				}
325				NaluType::Pps => {
326					self.parser.parse_pps(&nalu).map_err(|e| anyhow!("parse PPS: {e}"))?;
327				}
328				NaluType::Slice
329				| NaluType::SliceDpa
330				| NaluType::SliceDpb
331				| NaluType::SliceDpc
332				| NaluType::SliceIdr
333				| NaluType::SliceExt => {
334					let slice = self
335						.parser
336						.parse_slice_header(nalu)
337						.map_err(|e| anyhow!("parse slice header: {e}"))?;
338
339					let mut picture = match current.take() {
340						// An access unit normally holds one picture, but nothing
341						// forces that: `first_mb_in_slice == 0` starts a new one,
342						// so the picture in flight is done.
343						Some(picture) if slice.header.first_mb_in_slice == 0 => {
344							self.finish_picture(picture)?;
345							self.begin_picture(&slice, timestamp)?
346						}
347						Some(picture) => picture,
348						None => self.begin_picture(&slice, timestamp)?,
349					};
350
351					self.decode_slice(&mut picture, &slice)?;
352					current = Some(picture);
353				}
354				other => log::trace!("ignoring NAL unit of type {other:?}"),
355			}
356		}
357
358		// Since the loop cannot tell a truncated unit from a complete one, a
359		// caller that hands us the wrong container gets silence. Say so once per
360		// unit rather than leaving them to wonder where their pictures went.
361		if nalus == 0 && !access_unit.is_empty() {
362			log::warn!(
363				"access unit of {} bytes holds no Annex-B NAL unit; length-prefixed input has to be converted first",
364				access_unit.len()
365			);
366		}
367
368		// Finishing here rather than on the next unit's first slice keeps the
369		// hardware from holding a half-submitted picture between calls.
370		if let Some(picture) = current.take() {
371			self.finish_picture(picture)?;
372		}
373
374		Ok(())
375	}
376
377	/// Returns every picture still held in the DPB, in output order.
378	///
379	/// C.4.5.3 bumps the DPB only when a new picture needs the slot, so a stream
380	/// that simply stops leaves its tail sitting there. This releases that tail
381	/// and empties the DPB, so a second call returns nothing.
382	///
383	/// The parsed parameter sets survive, and nothing here waits for an IDR: a
384	/// stream picked up part way through decodes against an empty DPB, which
385	/// gives distorted pictures until its next IDR rather than an error.
386	pub fn flush(&mut self) -> anyhow::Result<Vec<Frame>> {
387		self.reset();
388		self.take_frames()
389	}
390
391	/// [`Decoder::flush`] for a caller taking its pictures on the GPU.
392	pub fn flush_exported(&mut self) -> anyhow::Result<Vec<ExportedFrame>> {
393		self.reset();
394		self.take_exported()
395	}
396
397	/// Moves the DPB to the ready queue and forgets the order count state the
398	/// next picture would otherwise be derived against.
399	fn reset(&mut self) {
400		self.drain();
401		self.prev_ref_pic_info = PrevReferencePicInfo::default();
402		self.prev_pic_info = PrevPicInfo::default();
403		self.max_long_term_frame_idx = MaxLongTermFrameIdx::default();
404	}
405
406	/// Downloads every picture waiting in the ready queue.
407	fn take_frames(&mut self) -> anyhow::Result<Vec<Frame>> {
408		std::mem::take(&mut self.ready).iter().map(download).collect()
409	}
410
411	/// Exports every picture waiting in the ready queue, leaving the pixels
412	/// where the hardware wrote them.
413	fn take_exported(&mut self) -> anyhow::Result<Vec<ExportedFrame>> {
414		std::mem::take(&mut self.ready).iter().map(export).collect()
415	}
416
417	/// Moves everything left in the DPB to the ready queue.
418	fn drain(&mut self) {
419		self.ready.extend(self.dpb.drain().into_iter().flatten());
420	}
421
422	/// Applies `sps` to the DPB limits, rebuilding the VA config, context, and
423	/// surface pool if it changes anything they depend on.
424	fn apply_sps(&mut self, sps: &Sps) -> anyhow::Result<()> {
425		let info = SequenceInfo::new(sps)?;
426		// C.4.5.3: the DPB limits follow the active SPS whether or not the VA
427		// state has to be rebuilt.
428		let max_dpb_frames = sps.max_dpb_frames();
429		let max_num_order_frames = sps.max_num_order_frames() as usize;
430		let max_num_reorder_frames = if max_num_order_frames > max_dpb_frames {
431			0
432		} else {
433			max_num_order_frames
434		};
435		self.dpb.set_limits(max_dpb_frames, max_num_reorder_frames);
436
437		if self.sequence.as_ref().map(|sequence| &sequence.info) == Some(&info) {
438			return Ok(());
439		}
440
441		// Surfaces are about to change shape, so let everything decoded against
442		// the old sequence out first. Those handles keep their own surfaces (and
443		// through them the old pool) alive until they are downloaded.
444		self.drain();
445
446		self.sequence = Some(Sequence::new(&self.display, sps, info)?);
447		Ok(())
448	}
449
450	/// Starts a picture: computes its order count, updates the DPB's picture
451	/// numbers, and hands the driver the picture parameters.
452	fn begin_picture(&mut self, slice: &Slice, timestamp: u64) -> anyhow::Result<CurrentPicture> {
453		let pps = Rc::clone(
454			self.parser
455				.get_pps(slice.header.pic_parameter_set_id)
456				.context("slice refers to an unknown PPS")?,
457		);
458		let sps = Rc::clone(&pps.sps);
459		self.apply_sps(&sps)?;
460
461		if slice.nalu.header.idr_pic_flag {
462			self.prev_ref_pic_info.frame_num = 0;
463		}
464
465		let frame_num = u32::from(slice.header.frame_num);
466		if frame_num != self.prev_ref_pic_info.frame_num
467			&& frame_num != (self.prev_ref_pic_info.frame_num + 1) % sps.max_frame_num()
468		{
469			self.handle_frame_num_gap(&sps, frame_num, timestamp)?;
470		}
471
472		let pic = self.init_current_pic(slice, &sps, timestamp)?;
473		let ref_pic_lists = self.dpb.build_ref_pic_lists(&pic);
474
475		// `apply_sps` above either kept the sequence or built a new one.
476		let sequence = self.sequence.as_ref().expect("sequence built by apply_sps");
477		let context = Rc::clone(&sequence.context);
478		let handle = Handle {
479			surface: Arc::new(sequence.pool.alloc()?),
480			timestamp,
481			width: sequence.width,
482			height: sequence.height,
483		};
484
485		let mut picture = Picture::new(timestamp, Rc::clone(&context), handle.clone());
486		let pic_param = build_pic_param(&slice.header, &pic, handle.surface.id(), &self.dpb, &sps, &pps);
487		picture.add_buffer(create_buffer(&context, pic_param)?);
488		picture.add_buffer(create_buffer(&context, build_iq_matrix(&pps))?);
489
490		log::trace!("starting picture POC {}", pic.pic_order_cnt);
491		Ok(CurrentPicture {
492			pic,
493			pps,
494			handle,
495			picture,
496			ref_pic_lists,
497		})
498	}
499
500	/// Hands the driver one slice: its parameters, its reference lists, and the
501	/// slice NAL itself.
502	fn decode_slice(&mut self, current: &mut CurrentPicture, slice: &Slice) -> anyhow::Result<()> {
503		// A VA slice parameter carries both of these in 16 bits, so refuse what
504		// would otherwise be truncated into a plausible wrong offset. Only a
505		// picture past 65536 macroblocks, which is beyond 8K, gets close.
506		if slice.header.first_mb_in_slice > u32::from(u16::MAX) {
507			bail!(
508				"slice starts at macroblock {}, past what a VA slice parameter addresses",
509				slice.header.first_mb_in_slice
510			);
511		}
512		if slice.header.header_bit_size > usize::from(u16::MAX) {
513			bail!(
514				"slice header is {} bits long, past what a VA slice parameter addresses",
515				slice.header.header_bit_size
516			);
517		}
518
519		// A slice may refer to a different PPS than the one that started the
520		// picture, as long as it names the same SPS.
521		let pps = Rc::clone(
522			self.parser
523				.get_pps(slice.header.pic_parameter_set_id)
524				.context("slice refers to an unknown PPS")?,
525		);
526		if SequenceInfo::new(&pps.sps)? != SequenceInfo::new(&current.pps.sps)? {
527			bail!("invalid stream: the sequence changed between slices of one picture");
528		}
529		current.pps = Rc::clone(&pps);
530
531		let (list0, list1) = self.create_ref_pic_lists(&current.pic, &slice.header, &current.ref_pic_lists)?;
532		let context = &self.sequence.as_ref().expect("sequence built by apply_sps").context;
533
534		let slice_param = build_slice_param(&slice.header, slice.nalu.size, &list0, &list1, &pps.sps, &pps);
535		current.picture.add_buffer(create_buffer(context, slice_param)?);
536		current.picture.add_buffer(create_buffer(
537			context,
538			BufferType::SliceData(slice.nalu.as_ref().to_vec()),
539		)?);
540
541		Ok(())
542	}
543
544	/// Submits the picture to the hardware, then runs the reference marking and
545	/// bumping the specification calls for once a picture is decoded.
546	fn finish_picture(&mut self, current: CurrentPicture) -> anyhow::Result<()> {
547		let CurrentPicture {
548			mut pic,
549			pps,
550			handle,
551			picture,
552			..
553		} = current;
554
555		let picture = picture.begin::<()>().map_err(|e| anyhow!("picture begin: {e:?}"))?;
556		let picture = picture.render().map_err(|e| anyhow!("picture render: {e:?}"))?;
557		let picture = picture.end().map_err(|e| anyhow!("picture end: {e:?}"))?;
558		// Sync before the surface enters the DPB: it is about to be read as a
559		// reference by later pictures, and the caller may never ask for its
560		// pixels. Dropping the synced picture frees the VA buffers with it.
561		drop(picture.sync::<()>().map_err(|(e, _)| anyhow!("picture sync: {e:?}"))?);
562		log::trace!("decoded picture POC {}", pic.pic_order_cnt);
563
564		if pic.is_ref() {
565			self.reference_pic_marking(&mut pic, &pps.sps)?;
566			self.prev_ref_pic_info.fill(&pic);
567		}
568		self.prev_pic_info.fill(&pic);
569
570		// C.4.5.3 clause 3: a picture with memory_management_control_operation
571		// equal to 5 empties the DPB.
572		if pic.has_mmco_5 {
573			self.drain();
574		}
575
576		// C.4.5.3 clauses 1, 4, 5, and 6.
577		self.ready.extend(self.dpb.bump_as_needed(&pic).into_iter().flatten());
578
579		// C.4.5.1: a reference picture is bumped until a frame buffer is free,
580		// then stored. C.4.5.2: a non-reference picture is stored if a buffer is
581		// free after bumping, and output directly otherwise.
582		if pic.is_ref() || self.dpb.has_empty_frame_buffer() {
583			self.dpb
584				.store_picture(pic.into_rc(), Some(handle))
585				.map_err(|e| anyhow!("store picture in DPB: {e}"))?;
586		} else {
587			self.ready.push(handle);
588		}
589
590		Ok(())
591	}
592
593	/// Builds the picture for `slice` and makes room for it in the DPB.
594	fn init_current_pic(&mut self, slice: &Slice, sps: &Sps, timestamp: u64) -> anyhow::Result<PictureData> {
595		// Progressive only, so a picture is never the second field of a frame.
596		let mut pic = PictureData::new_from_slice(slice, sps, timestamp, None);
597		self.compute_pic_order_count(&mut pic, sps)?;
598
599		if matches!(pic.is_idr, IsIdr::Yes { .. }) {
600			// C.4.5.3 clause 2: an IDR bumps the whole DPB, unless
601			// no_output_of_prior_pics_flag says to discard it instead (C.4.4).
602			if pic.ref_pic_marking.no_output_of_prior_pics_flag {
603				self.dpb.clear();
604			} else {
605				self.drain();
606			}
607		}
608
609		self.dpb
610			.update_pic_nums(u32::from(slice.header.frame_num), sps.max_frame_num(), &pic);
611
612		Ok(pic)
613	}
614
615	/// 8.2.5.2: invents the pictures for the frame numbers a lossy stream skipped,
616	/// so reference marking keeps working.
617	fn handle_frame_num_gap(&mut self, sps: &Sps, frame_num: u32, timestamp: u64) -> anyhow::Result<()> {
618		if self.dpb.is_empty() {
619			return Ok(());
620		}
621
622		if !sps.gaps_in_frame_num_value_allowed_flag {
623			bail!("invalid frame_num {frame_num}: assuming unintentional loss of pictures");
624		}
625		log::debug!("frame_num gap up to {frame_num}");
626
627		let max_frame_num = sps.max_frame_num();
628		let mut unused = (self.prev_ref_pic_info.frame_num + 1) % max_frame_num;
629		while unused != frame_num {
630			let mut pic = PictureData::new_non_existing(unused, timestamp);
631			self.compute_pic_order_count(&mut pic, sps)?;
632
633			self.dpb.update_pic_nums(unused, max_frame_num, &pic);
634			self.dpb.sliding_window_marking(&mut pic, sps);
635			self.ready.extend(self.dpb.bump_as_needed(&pic).into_iter().flatten());
636			self.dpb
637				.store_picture(pic.into_rc(), None)
638				.map_err(|e| anyhow!("store non-existing picture in DPB: {e}"))?;
639
640			unused = (unused + 1) % max_frame_num;
641		}
642
643		Ok(())
644	}
645
646	/// 8.2.1: derives the picture order count, which drives output ordering.
647	fn compute_pic_order_count(&mut self, pic: &mut PictureData, sps: &Sps) -> anyhow::Result<()> {
648		match pic.pic_order_cnt_type {
649			// 8.2.1.1
650			0 => {
651				let (prev_pic_order_cnt_msb, prev_pic_order_cnt_lsb) = if matches!(pic.is_idr, IsIdr::Yes { .. }) {
652					(0, 0)
653				} else if self.prev_ref_pic_info.has_mmco_5 {
654					if matches!(self.prev_ref_pic_info.field, Field::Bottom) {
655						(0, 0)
656					} else {
657						(0, self.prev_ref_pic_info.top_field_order_cnt)
658					}
659				} else {
660					(
661						self.prev_ref_pic_info.pic_order_cnt_msb,
662						self.prev_ref_pic_info.pic_order_cnt_lsb,
663					)
664				};
665
666				let max_pic_order_cnt_lsb = 1 << (sps.log2_max_pic_order_cnt_lsb_minus4 + 4);
667
668				pic.pic_order_cnt_msb = if (pic.pic_order_cnt_lsb < prev_pic_order_cnt_lsb)
669					&& (prev_pic_order_cnt_lsb - pic.pic_order_cnt_lsb >= max_pic_order_cnt_lsb / 2)
670				{
671					prev_pic_order_cnt_msb + max_pic_order_cnt_lsb
672				} else if (pic.pic_order_cnt_lsb > prev_pic_order_cnt_lsb)
673					&& (pic.pic_order_cnt_lsb - prev_pic_order_cnt_lsb > max_pic_order_cnt_lsb / 2)
674				{
675					prev_pic_order_cnt_msb - max_pic_order_cnt_lsb
676				} else {
677					prev_pic_order_cnt_msb
678				};
679
680				if !matches!(pic.field, Field::Bottom) {
681					pic.top_field_order_cnt = pic.pic_order_cnt_msb + pic.pic_order_cnt_lsb;
682				}
683
684				if !matches!(pic.field, Field::Top) {
685					pic.bottom_field_order_cnt = if matches!(pic.field, Field::Frame) {
686						pic.top_field_order_cnt + pic.delta_pic_order_cnt_bottom
687					} else {
688						pic.pic_order_cnt_msb + pic.pic_order_cnt_lsb
689					};
690				}
691			}
692
693			// 8.2.1.2
694			1 => {
695				if self.prev_pic_info.has_mmco_5 {
696					self.prev_pic_info.frame_num_offset = 0;
697				}
698
699				pic.frame_num_offset = if matches!(pic.is_idr, IsIdr::Yes { .. }) {
700					0
701				} else if self.prev_pic_info.frame_num > pic.frame_num {
702					self.prev_pic_info.frame_num_offset + sps.max_frame_num()
703				} else {
704					self.prev_pic_info.frame_num_offset
705				};
706
707				let mut abs_frame_num = if sps.num_ref_frames_in_pic_order_cnt_cycle != 0 {
708					pic.frame_num_offset + pic.frame_num
709				} else {
710					0
711				};
712
713				if pic.nal_ref_idc == 0 && abs_frame_num > 0 {
714					abs_frame_num -= 1;
715				}
716
717				let mut expected_pic_order_cnt = 0;
718
719				if abs_frame_num > 0 {
720					if sps.num_ref_frames_in_pic_order_cnt_cycle == 0 {
721						bail!("invalid num_ref_frames_in_pic_order_cnt_cycle");
722					}
723
724					let cycle_len = u32::from(sps.num_ref_frames_in_pic_order_cnt_cycle);
725					let cycles = (abs_frame_num - 1) / cycle_len;
726					// The cycle is only summed up to the position this frame sits
727					// at within it, not all the way round.
728					let position_in_cycle = (abs_frame_num - 1) % cycle_len;
729					expected_pic_order_cnt = cycles as i32 * sps.expected_delta_per_pic_order_cnt_cycle;
730
731					for i in 0..=position_in_cycle {
732						expected_pic_order_cnt += sps.offset_for_ref_frame[i as usize];
733					}
734				}
735
736				if pic.nal_ref_idc == 0 {
737					expected_pic_order_cnt += sps.offset_for_non_ref_pic;
738				}
739
740				match pic.field {
741					Field::Frame => {
742						pic.top_field_order_cnt = expected_pic_order_cnt + pic.delta_pic_order_cnt0;
743						pic.bottom_field_order_cnt =
744							pic.top_field_order_cnt + sps.offset_for_top_to_bottom_field + pic.delta_pic_order_cnt1;
745					}
746					Field::Top => {
747						pic.top_field_order_cnt = expected_pic_order_cnt + pic.delta_pic_order_cnt0;
748					}
749					Field::Bottom => {
750						pic.bottom_field_order_cnt =
751							expected_pic_order_cnt + sps.offset_for_top_to_bottom_field + pic.delta_pic_order_cnt0;
752					}
753				}
754			}
755
756			// 8.2.1.3
757			2 => {
758				if self.prev_pic_info.has_mmco_5 {
759					self.prev_pic_info.frame_num_offset = 0;
760				}
761
762				pic.frame_num_offset = if matches!(pic.is_idr, IsIdr::Yes { .. }) {
763					0
764				} else if self.prev_pic_info.frame_num > pic.frame_num {
765					self.prev_pic_info.frame_num_offset + sps.max_frame_num()
766				} else {
767					self.prev_pic_info.frame_num_offset
768				};
769
770				let pic_order_cnt = if matches!(pic.is_idr, IsIdr::Yes { .. }) {
771					0
772				} else if pic.nal_ref_idc == 0 {
773					2 * (pic.frame_num_offset + pic.frame_num) as i32 - 1
774				} else {
775					2 * (pic.frame_num_offset + pic.frame_num) as i32
776				};
777
778				if matches!(pic.field, Field::Frame | Field::Top) {
779					pic.top_field_order_cnt = pic_order_cnt;
780				}
781				if matches!(pic.field, Field::Frame | Field::Bottom) {
782					pic.bottom_field_order_cnt = pic_order_cnt;
783				}
784			}
785
786			other => bail!("invalid pic_order_cnt_type {other}"),
787		}
788
789		pic.pic_order_cnt = match pic.field {
790			Field::Frame => std::cmp::min(pic.top_field_order_cnt, pic.bottom_field_order_cnt),
791			Field::Top => pic.top_field_order_cnt,
792			Field::Bottom => pic.bottom_field_order_cnt,
793		};
794
795		Ok(())
796	}
797
798	/// 8.2.5.1: marks the decoded picture as used or unused for reference.
799	fn reference_pic_marking(&mut self, pic: &mut PictureData, sps: &Sps) -> anyhow::Result<()> {
800		if matches!(pic.is_idr, IsIdr::Yes { .. }) {
801			self.dpb.mark_all_as_unused_for_ref();
802
803			if pic.ref_pic_marking.long_term_reference_flag {
804				pic.set_reference(Reference::LongTerm, false);
805				pic.long_term_frame_idx = 0;
806				self.max_long_term_frame_idx = MaxLongTermFrameIdx::Idx(0);
807			} else {
808				pic.set_reference(Reference::ShortTerm, false);
809				self.max_long_term_frame_idx = MaxLongTermFrameIdx::NoLongTermFrameIndices;
810			}
811
812			return Ok(());
813		}
814
815		if pic.ref_pic_marking.adaptive_ref_pic_marking_mode_flag {
816			self.handle_memory_management_ops(pic)?;
817		} else {
818			self.dpb.sliding_window_marking(pic, sps);
819		}
820
821		Ok(())
822	}
823
824	/// 8.2.5.4: runs the picture's adaptive memory management control operations.
825	fn handle_memory_management_ops(&mut self, pic: &mut PictureData) -> Result<(), MmcoError> {
826		let markings = pic.ref_pic_marking.clone();
827
828		for marking in &markings.inner {
829			match marking.memory_management_control_operation {
830				0 => break,
831				1 => self.dpb.mmco_op_1(pic, marking)?,
832				2 => self.dpb.mmco_op_2(pic, marking)?,
833				3 => self.dpb.mmco_op_3(pic, marking)?,
834				4 => self.max_long_term_frame_idx = self.dpb.mmco_op_4(marking),
835				5 => self.max_long_term_frame_idx = self.dpb.mmco_op_5(pic),
836				6 => self.dpb.mmco_op_6(pic, marking),
837				other => return Err(MmcoError::UnknownMmco(other)),
838			}
839		}
840
841		Ok(())
842	}
843
844	/// 8.2.4: derives RefPicList0 and RefPicList1 for one slice, from the
845	/// per-picture lists the DPB built.
846	fn create_ref_pic_lists<'a>(
847		&'a self,
848		pic: &PictureData,
849		hdr: &SliceHeader,
850		lists: &ReferencePicLists,
851	) -> anyhow::Result<(DpbPicRefList<'a, Handle>, DpbPicRefList<'a, Handle>)> {
852		let list0 = match hdr.slice_type {
853			SliceType::P | SliceType::Sp => self.modify_ref_pic_list(pic, hdr, false, &lists.ref_pic_list_p0)?,
854			SliceType::B => self.modify_ref_pic_list(pic, hdr, false, &lists.ref_pic_list_b0)?,
855			_ => Vec::new(),
856		};
857
858		let list1 = match hdr.slice_type {
859			SliceType::B => self.modify_ref_pic_list(pic, hdr, true, &lists.ref_pic_list_b1)?,
860			_ => Vec::new(),
861		};
862
863		Ok((list0, list1))
864	}
865
866	/// 8.2.4.3: applies the slice header's reference picture list modifications.
867	fn modify_ref_pic_list<'a>(
868		&'a self,
869		pic: &PictureData,
870		hdr: &SliceHeader,
871		list1: bool,
872		indices: &[usize],
873	) -> anyhow::Result<DpbPicRefList<'a, Handle>> {
874		let (modify, num_ref_idx_active_minus1, modifications) = if list1 {
875			(
876				hdr.ref_pic_list_modification_flag_l1,
877				hdr.num_ref_idx_l1_active_minus1,
878				&hdr.ref_pic_list_modification_l1,
879			)
880		} else {
881			(
882				hdr.ref_pic_list_modification_flag_l0,
883				hdr.num_ref_idx_l0_active_minus1,
884				&hdr.ref_pic_list_modification_l0,
885			)
886		};
887
888		let mut list: DpbPicRefList<'a, Handle> = indices
889			.iter()
890			.map(|&i| &self.dpb.entries()[i])
891			.take(usize::from(num_ref_idx_active_minus1) + 1)
892			.collect();
893
894		if !modify {
895			return Ok(list);
896		}
897
898		let mut pic_num_pred = pic.pic_num;
899		let mut ref_idx = 0;
900
901		for modification in modifications {
902			match modification.modification_of_pic_nums_idc {
903				idc @ (0 | 1) => self.short_term_modification(
904					pic,
905					&mut list,
906					num_ref_idx_active_minus1,
907					hdr.max_pic_num as i32,
908					idc,
909					modification,
910					&mut pic_num_pred,
911					&mut ref_idx,
912				)?,
913				2 => self.long_term_modification(&mut list, num_ref_idx_active_minus1, modification, &mut ref_idx)?,
914				3 => break,
915				other => bail!("unexpected modification_of_pic_nums_idc {other}"),
916			}
917		}
918
919		Ok(list)
920	}
921
922	/// 8.2.4.3.1: modification for short-term reference pictures.
923	#[allow(clippy::too_many_arguments)]
924	fn short_term_modification<'a>(
925		&'a self,
926		pic: &PictureData,
927		list: &mut DpbPicRefList<'a, Handle>,
928		num_ref_idx_active_minus1: u8,
929		max_pic_num: i32,
930		idc: u8,
931		modification: &RefPicListModification,
932		pic_num_pred: &mut i32,
933		ref_idx: &mut usize,
934	) -> anyhow::Result<()> {
935		let abs_diff_pic_num = modification.abs_diff_pic_num_minus1 as i32 + 1;
936
937		let pic_num_no_wrap = if idc == 0 {
938			if *pic_num_pred - abs_diff_pic_num < 0 {
939				*pic_num_pred - abs_diff_pic_num + max_pic_num
940			} else {
941				*pic_num_pred - abs_diff_pic_num
942			}
943		} else if *pic_num_pred + abs_diff_pic_num >= max_pic_num {
944			*pic_num_pred + abs_diff_pic_num - max_pic_num
945		} else {
946			*pic_num_pred + abs_diff_pic_num
947		};
948
949		*pic_num_pred = pic_num_no_wrap;
950
951		let pic_num = if pic_num_no_wrap > pic.pic_num {
952			pic_num_no_wrap - max_pic_num
953		} else {
954			pic_num_no_wrap
955		};
956
957		let entry = self
958			.dpb
959			.find_short_term_with_pic_num(pic_num)
960			.with_context(|| format!("no short-term reference with pic_num {pic_num}"))?;
961
962		Self::insert_modification(list, entry, num_ref_idx_active_minus1, ref_idx, |target| {
963			target.pic_num_f(max_pic_num) != pic_num
964		});
965
966		Ok(())
967	}
968
969	/// 8.2.4.3.2: modification for long-term reference pictures.
970	fn long_term_modification<'a>(
971		&'a self,
972		list: &mut DpbPicRefList<'a, Handle>,
973		num_ref_idx_active_minus1: u8,
974		modification: &RefPicListModification,
975		ref_idx: &mut usize,
976	) -> anyhow::Result<()> {
977		let long_term_pic_num = modification.long_term_pic_num;
978		let max_long_term_frame_idx = self.max_long_term_frame_idx;
979
980		let entry = self
981			.dpb
982			.find_long_term_with_long_term_pic_num(long_term_pic_num)
983			.with_context(|| format!("no long-term reference with long_term_pic_num {long_term_pic_num}"))?;
984
985		Self::insert_modification(list, entry, num_ref_idx_active_minus1, ref_idx, |target| {
986			target.long_term_pic_num_f(max_long_term_frame_idx) != long_term_pic_num
987		});
988
989		Ok(())
990	}
991
992	/// The shared tail of 8.2.4.3.1 and 8.2.4.3.2: insert `entry` at `ref_idx`,
993	/// then shuffle out the duplicate `keep` identifies and re-truncate the list.
994	fn insert_modification<'a>(
995		list: &mut DpbPicRefList<'a, Handle>,
996		entry: &'a DpbEntry<Handle>,
997		num_ref_idx_active_minus1: u8,
998		ref_idx: &mut usize,
999		keep: impl Fn(&PictureData) -> bool,
1000	) {
1001		// A modification past the end of the list is a stream error; the spec
1002		// only defines indices within num_ref_idx_lX_active_minus1 + 1.
1003		let at = (*ref_idx).min(list.len());
1004		list.insert(at, entry);
1005		*ref_idx = at + 1;
1006
1007		let mut next = *ref_idx;
1008		for current in *ref_idx..=usize::from(num_ref_idx_active_minus1) + 1 {
1009			if current >= list.len() {
1010				break;
1011			}
1012
1013			if keep(&list[current].pic.borrow()) {
1014				list[next] = list[current];
1015				next += 1;
1016			}
1017		}
1018
1019		list.truncate(usize::from(num_ref_idx_active_minus1) + 1);
1020	}
1021}
1022
1023/// The picture currently being assembled, held across the slices that make it up.
1024struct CurrentPicture {
1025	pic: PictureData,
1026	/// PPS active for the last slice seen.
1027	pps: Rc<Pps>,
1028	/// Handle to the surface the driver renders into.
1029	handle: Handle,
1030	picture: Picture<crate::PictureNew, Handle>,
1031	/// Per-picture reference lists, modified per slice.
1032	ref_pic_lists: ReferencePicLists,
1033}
1034
1035/// A decoded picture: the surface holding it plus what the caller needs to read
1036/// it back. Cloned into the DPB, so the surface is not recycled while it is still
1037/// a reference or still waiting to be output.
1038#[derive(Clone)]
1039struct Handle {
1040	surface: Arc<PooledSurface>,
1041	timestamp: u64,
1042	width: u32,
1043	height: u32,
1044}
1045
1046impl std::borrow::Borrow<Surface<()>> for Handle {
1047	fn borrow(&self) -> &Surface<()> {
1048		self.surface.get()
1049	}
1050}
1051
1052/// The properties of an SPS that the VA config, context, and surface pool are
1053/// built from. A change in any of them forces them to be rebuilt.
1054#[derive(Clone, Debug, PartialEq, Eq)]
1055struct SequenceInfo {
1056	visible: (u32, u32),
1057	coded: (u32, u32),
1058	profile_idc: u8,
1059	bit_depth_luma_minus8: u8,
1060	bit_depth_chroma_minus8: u8,
1061	chroma_format_idc: u8,
1062	frame_mbs_only_flag: bool,
1063	max_dpb_frames: usize,
1064}
1065
1066impl SequenceInfo {
1067	fn new(sps: &Sps) -> anyhow::Result<Self> {
1068		if !sps.frame_mbs_only_flag {
1069			bail!("interlaced H.264 is not supported");
1070		}
1071		if sps.chroma_format_idc != 1 {
1072			bail!(
1073				"only 4:2:0 chroma is supported, got chroma_format_idc {}",
1074				sps.chroma_format_idc
1075			);
1076		}
1077		if sps.bit_depth_luma_minus8 != 0 || sps.bit_depth_chroma_minus8 != 0 {
1078			bail!(
1079				"only 8-bit samples are supported, got {}-bit luma and {}-bit chroma",
1080				sps.bit_depth_luma_minus8 + 8,
1081				sps.bit_depth_chroma_minus8 + 8
1082			);
1083		}
1084
1085		if sps.frame_cropping_flag && (sps.frame_crop_left_offset != 0 || sps.frame_crop_top_offset != 0) {
1086			bail!("left and top H.264 cropping is not supported");
1087		}
1088		let (crop_right, crop_bottom) = if sps.frame_cropping_flag {
1089			(sps.frame_crop_right_offset, sps.frame_crop_bottom_offset)
1090		} else {
1091			(0, 0)
1092		};
1093		let width = crop_right
1094			.checked_mul(2)
1095			.and_then(|crop| sps.width().checked_sub(crop))
1096			.context("invalid horizontal H.264 crop")?;
1097		let height = crop_bottom
1098			.checked_mul(2)
1099			.and_then(|crop| sps.height().checked_sub(crop))
1100			.context("invalid vertical H.264 crop")?;
1101		if width == 0 || height == 0 || width % 2 != 0 || height % 2 != 0 {
1102			bail!("visible size {width}x{height} is not a non-zero even 4:2:0 size");
1103		}
1104
1105		Ok(Self {
1106			visible: (width, height),
1107			coded: (sps.width(), sps.height()),
1108			profile_idc: sps.profile_idc,
1109			bit_depth_luma_minus8: sps.bit_depth_luma_minus8,
1110			bit_depth_chroma_minus8: sps.bit_depth_chroma_minus8,
1111			chroma_format_idc: sps.chroma_format_idc,
1112			frame_mbs_only_flag: sps.frame_mbs_only_flag,
1113			max_dpb_frames: sps.max_dpb_frames(),
1114		})
1115	}
1116}
1117
1118/// The VA state for one coded video sequence.
1119struct Sequence {
1120	info: SequenceInfo,
1121	/// Visible width after cropping, which is what a [`Frame`] reports.
1122	width: u32,
1123	/// Visible height after cropping.
1124	height: u32,
1125	pool: SurfacePool,
1126	// Drop order: the context must go before the config it was created from.
1127	context: Rc<Context>,
1128	_config: VaConfig,
1129}
1130
1131impl Sequence {
1132	fn new(display: &Arc<Display>, sps: &Sps, info: SequenceInfo) -> anyhow::Result<Self> {
1133		let (width, height) = info.visible;
1134
1135		let attrs = vec![VAConfigAttrib {
1136			type_: VAConfigAttribType::VAConfigAttribRTFormat,
1137			value: VA_RT_FORMAT_YUV420,
1138		}];
1139		let config = display
1140			.create_config(attrs, va_profile(sps)?, VAEntrypoint::VAEntrypointVLD)
1141			.map_err(|e| anyhow!("create VA config: {e:?}"))?;
1142
1143		let (coded_width, coded_height) = info.coded;
1144		let context = display
1145			.create_context::<()>(&config, coded_width, coded_height, None, true)
1146			.map_err(|e| anyhow!("create VA context: {e:?}"))?;
1147
1148		log::info!(
1149			"VA-API H.264 sequence: {width}x{height} visible, {coded_width}x{coded_height} coded, \
1150			 profile_idc {}, DPB {} frames",
1151			sps.profile_idc,
1152			info.max_dpb_frames
1153		);
1154
1155		// What the decoder itself can need at once: a full DPB plus the picture
1156		// being decoded. Anything free beyond that was let go by a consumer that
1157		// had been holding pictures, and is memory the stream will not ask for
1158		// again.
1159		let capacity = info.max_dpb_frames + 1;
1160		Ok(Self {
1161			info,
1162			width,
1163			height,
1164			pool: SurfacePool {
1165				display: Arc::clone(display),
1166				width: coded_width,
1167				height: coded_height,
1168				free: Arc::new(FreeList {
1169					surfaces: Mutex::new(Vec::new()),
1170					capacity,
1171				}),
1172				allocated: Cell::new(0),
1173			},
1174			context,
1175			_config: config,
1176		})
1177	}
1178}
1179
1180/// A recycling pool of decode target surfaces.
1181///
1182/// Surfaces are allocated on demand and come back when the last reference to
1183/// one drops: the DPB's, and an [`ExportedFrame`]'s if the picture was handed
1184/// out. So a picture a consumer holds is never decoded over, and one it has let
1185/// go of is decoded into again rather than replaced by a fresh allocation.
1186///
1187/// The pool keeps at most [`FreeList::capacity`] surfaces free and destroys
1188/// any returned past that, so a consumer that held many pictures and then let
1189/// them all go does not leave that many parked here for the rest of the
1190/// sequence.
1191struct SurfacePool {
1192	display: Arc<Display>,
1193	width: u32,
1194	height: u32,
1195	/// Shared with every surface on loan, which puts itself back from whichever
1196	/// thread drops it last.
1197	free: Arc<FreeList>,
1198	/// Surfaces allocated over the pool's life.
1199	allocated: Cell<usize>,
1200}
1201
1202/// The surfaces a [`SurfacePool`] has free to decode into.
1203struct FreeList {
1204	surfaces: Mutex<Vec<Surface<()>>>,
1205	/// The most surfaces kept; one returned past this is destroyed.
1206	capacity: usize,
1207}
1208
1209impl SurfacePool {
1210	fn alloc(&self) -> anyhow::Result<PooledSurface> {
1211		let recycled = self.free.surfaces.lock().expect("poisoned").pop();
1212		let surface = match recycled {
1213			Some(surface) => surface,
1214			None => {
1215				let surface = self
1216					.display
1217					.create_surfaces::<()>(
1218						VA_RT_FORMAT_YUV420,
1219						Some(VA_FOURCC_NV12),
1220						self.width,
1221						self.height,
1222						Some(UsageHint::USAGE_HINT_DECODER),
1223						vec![()],
1224					)
1225					.map_err(|e| anyhow!("create decode surface: {e:?}"))?
1226					.pop()
1227					.context("VA-API returned an empty surface list")?;
1228				self.allocated.set(self.allocated.get() + 1);
1229				log::debug!(
1230					"allocated decode surface {} at {}x{}",
1231					self.allocated.get(),
1232					self.width,
1233					self.height
1234				);
1235				surface
1236			}
1237		};
1238
1239		Ok(PooledSurface {
1240			surface: Some(surface),
1241			free: Arc::downgrade(&self.free),
1242		})
1243	}
1244}
1245
1246/// A surface on loan from a [`SurfacePool`], returned to it on drop.
1247///
1248/// Shared behind an [`Arc`] by the DPB and by an [`ExportedFrame`], so it goes
1249/// back only once neither refers to it. The pool is held weakly: a surface that
1250/// outlives its sequence is destroyed rather than kept for a size nobody
1251/// decodes at any more.
1252struct PooledSurface {
1253	/// Always `Some` until dropped; the `Option` is only there so [`Drop`] can
1254	/// move the surface back into the pool.
1255	surface: Option<Surface<()>>,
1256	/// Where to return the surface. Empty for a surface that came from
1257	/// somewhere other than a pool, which is destroyed on drop.
1258	free: Weak<FreeList>,
1259}
1260
1261impl PooledSurface {
1262	/// Wraps a surface that belongs to no pool.
1263	fn unpooled(surface: Surface<()>) -> Self {
1264		Self {
1265			surface: Some(surface),
1266			free: Weak::new(),
1267		}
1268	}
1269
1270	fn get(&self) -> &Surface<()> {
1271		self.surface.as_ref().expect("surface is taken only on drop")
1272	}
1273
1274	fn id(&self) -> bindings::VASurfaceID {
1275		self.get().id()
1276	}
1277}
1278
1279impl Drop for PooledSurface {
1280	fn drop(&mut self) {
1281		let (Some(surface), Some(free)) = (self.surface.take(), self.free.upgrade()) else {
1282			return;
1283		};
1284		let mut surfaces = free.surfaces.lock().expect("poisoned");
1285		if surfaces.len() < free.capacity {
1286			surfaces.push(surface);
1287		}
1288	}
1289}
1290
1291/// Exports a decoded surface as a DRM PRIME descriptor, copying nothing.
1292///
1293/// The frame holds a reference to the surface, so the pool does not hand it out
1294/// again while the caller holds the descriptor, and [`ExportedFrame::download`]
1295/// can still map it.
1296fn export(handle: &Handle) -> anyhow::Result<ExportedFrame> {
1297	let surface = handle.surface.get();
1298	surface.sync().map_err(|e| anyhow!("surface sync: {e:?}"))?;
1299
1300	let descriptor = surface
1301		.export_prime()
1302		.map_err(|e| anyhow!("export the decode surface: {e:?}"))?;
1303
1304	Ok(ExportedFrame {
1305		timestamp: handle.timestamp,
1306		width: handle.width,
1307		height: handle.height,
1308		descriptor,
1309		surface: Arc::clone(&handle.surface),
1310	})
1311}
1312
1313/// Reads a decoded surface back to client memory as tightly packed NV12.
1314fn download(handle: &Handle) -> anyhow::Result<Frame> {
1315	read_back(handle.surface.get(), handle.timestamp, handle.width, handle.height)
1316}
1317
1318/// Reads `surface` back to client memory as tightly packed NV12, cropped to
1319/// `width` by `height`.
1320///
1321/// The one read-back path, shared by the picture a decoder downloads itself and
1322/// the one a caller took as a descriptor and later wants on the CPU after all.
1323fn read_back(surface: &Surface<()>, timestamp: u64, width: u32, height: u32) -> anyhow::Result<Frame> {
1324	surface.sync().map_err(|e| anyhow!("surface sync: {e:?}"))?;
1325
1326	let visible = (width, height);
1327
1328	// vaDeriveImage exposes the surface's own buffer, so the copy below is the
1329	// only one. Not every driver can derive every surface; fall back to
1330	// vaCreateImage + vaGetImage, which always works but copies inside the driver
1331	// first.
1332	match Image::derive_from(surface, visible) {
1333		Ok(image) if image.image().format.fourcc == VA_FOURCC_NV12 => {
1334			return pack_nv12(&image, timestamp, width, height);
1335		}
1336		Ok(_) => log::debug!("derived image is not NV12, falling back to vaGetImage"),
1337		Err(e) => log::debug!("vaDeriveImage failed ({e:?}), falling back to vaGetImage"),
1338	}
1339
1340	let format = surface
1341		.display()
1342		.query_image_formats()
1343		.map_err(|e| anyhow!("query image formats: {e:?}"))?
1344		.into_iter()
1345		.find(|format| format.fourcc == VA_FOURCC_NV12)
1346		.context("driver has no NV12 image format")?;
1347	let image = Image::create_from(surface, format, surface.size(), visible)
1348		.map_err(|e| anyhow!("vaGetImage into an NV12 image: {e:?}"))?;
1349
1350	pack_nv12(&image, timestamp, width, height)
1351}
1352
1353/// Copies a mapped NV12 image into a tightly packed buffer, dropping the row
1354/// padding the driver's pitch may carry.
1355fn pack_nv12(image: &Image<'_>, timestamp: u64, frame_width: u32, frame_height: u32) -> anyhow::Result<Frame> {
1356	let va_image = image.image();
1357	let src: &[u8] = image.as_ref();
1358	let (width, height) = (frame_width as usize, frame_height as usize);
1359	let chroma_rows = height / 2;
1360
1361	let mut data = vec![0u8; width * (height + chroma_rows)];
1362	let (luma, chroma) = data.split_at_mut(width * height);
1363	copy_plane(
1364		luma,
1365		src,
1366		va_image.offsets[0] as usize,
1367		va_image.pitches[0] as usize,
1368		width,
1369		height,
1370	)?;
1371	copy_plane(
1372		chroma,
1373		src,
1374		va_image.offsets[1] as usize,
1375		va_image.pitches[1] as usize,
1376		width,
1377		chroma_rows,
1378	)?;
1379
1380	Ok(Frame {
1381		timestamp,
1382		width: frame_width,
1383		height: frame_height,
1384		data,
1385	})
1386}
1387
1388/// Copies `rows` rows of `width` bytes out of a strided plane.
1389fn copy_plane(
1390	dst: &mut [u8],
1391	src: &[u8],
1392	offset: usize,
1393	pitch: usize,
1394	width: usize,
1395	rows: usize,
1396) -> anyhow::Result<()> {
1397	if rows == 0 {
1398		return Ok(());
1399	}
1400	let end = offset + pitch * (rows - 1) + width;
1401	if pitch < width || src.len() < end {
1402		bail!("VA image plane (offset {offset}, pitch {pitch}) does not hold {rows} rows of {width} bytes");
1403	}
1404
1405	for row in 0..rows {
1406		let from = offset + row * pitch;
1407		dst[row * width..][..width].copy_from_slice(&src[from..from + width]);
1408	}
1409
1410	Ok(())
1411}
1412
1413/// Checks that `display`'s driver can decode H.264, which is what [`Decoder::new`] needs of a render node.
1414///
1415/// # Errors
1416///
1417/// Fails unless the driver offers `VAEntrypointVLD` for a Constrained
1418/// Baseline, Main, or High profile, or when it cannot be asked.
1419pub fn probe(display: &Display) -> anyhow::Result<()> {
1420	let profiles = display
1421		.query_config_profiles()
1422		.map_err(|e| anyhow!("query VA profiles: {e:?}"))?;
1423
1424	let supported = profiles
1425		.into_iter()
1426		.filter(|profile| {
1427			matches!(
1428				*profile,
1429				VAProfile::VAProfileH264ConstrainedBaseline
1430					| VAProfile::VAProfileH264Main
1431					| VAProfile::VAProfileH264High
1432			)
1433		})
1434		.any(|profile| {
1435			display
1436				.query_config_entrypoints(profile)
1437				.is_ok_and(|entrypoints| entrypoints.contains(&VAEntrypoint::VAEntrypointVLD))
1438		});
1439
1440	if !supported {
1441		bail!("driver exposes no H.264 decode entrypoint");
1442	}
1443
1444	Ok(())
1445}
1446
1447/// Maps the SPS profile onto the VA profile to open the config with.
1448fn va_profile(sps: &Sps) -> anyhow::Result<VAProfile::Type> {
1449	let profile = Profile::try_from(sps.profile_idc).map_err(|e| anyhow!("{e}"))?;
1450
1451	Ok(match profile {
1452		// VA-API has no plain Baseline profile, and the picture parameter buffer
1453		// has no way to describe the FMO and ASO tools that are all that separate
1454		// it from Constrained Baseline (num_slice_groups_minus1 is pinned to 0
1455		// below), so both map here. A stream that really uses them decodes wrong
1456		// rather than not at all, which no encoder in this decade emits.
1457		Profile::Baseline => VAProfile::VAProfileH264ConstrainedBaseline,
1458		Profile::Main | Profile::Extended => VAProfile::VAProfileH264Main,
1459		// The bit depth and chroma format are checked separately, so a High10 or
1460		// High422 SPS only reaches here when its samples are 8-bit 4:2:0.
1461		Profile::High | Profile::High10 | Profile::High422P => VAProfile::VAProfileH264High,
1462	})
1463}
1464
1465fn create_buffer(context: &Rc<Context>, buffer: BufferType) -> anyhow::Result<Buffer> {
1466	context
1467		.create_buffer(buffer)
1468		.map_err(|e| anyhow!("create VA buffer: {e:?}"))
1469}
1470
1471/// Cached variables from the previous reference picture, needed by 8.2.1.
1472#[derive(Debug)]
1473struct PrevReferencePicInfo {
1474	frame_num: u32,
1475	has_mmco_5: bool,
1476	top_field_order_cnt: i32,
1477	pic_order_cnt_msb: i32,
1478	pic_order_cnt_lsb: i32,
1479	field: Field,
1480}
1481
1482impl Default for PrevReferencePicInfo {
1483	fn default() -> Self {
1484		Self {
1485			frame_num: 0,
1486			has_mmco_5: false,
1487			top_field_order_cnt: 0,
1488			pic_order_cnt_msb: 0,
1489			pic_order_cnt_lsb: 0,
1490			field: Field::Frame,
1491		}
1492	}
1493}
1494
1495impl PrevReferencePicInfo {
1496	fn fill(&mut self, pic: &PictureData) {
1497		self.has_mmco_5 = pic.has_mmco_5;
1498		self.top_field_order_cnt = pic.top_field_order_cnt;
1499		self.pic_order_cnt_msb = pic.pic_order_cnt_msb;
1500		self.pic_order_cnt_lsb = pic.pic_order_cnt_lsb;
1501		self.field = pic.field;
1502		self.frame_num = pic.frame_num;
1503	}
1504}
1505
1506/// Cached variables from the previous picture, needed by 8.2.1.
1507#[derive(Debug, Default)]
1508struct PrevPicInfo {
1509	frame_num: u32,
1510	frame_num_offset: u32,
1511	has_mmco_5: bool,
1512}
1513
1514impl PrevPicInfo {
1515	fn fill(&mut self, pic: &PictureData) {
1516		self.frame_num = pic.frame_num;
1517		self.has_mmco_5 = pic.has_mmco_5;
1518		self.frame_num_offset = pic.frame_num_offset;
1519	}
1520}
1521
1522/// The surface a DPB entry decoded into, or `VA_INVALID_ID` for the non-existing
1523/// pictures invented for a frame_num gap.
1524fn va_surface_id(handle: &Option<Handle>) -> bindings::VASurfaceID {
1525	match handle {
1526		Some(handle) => handle.surface.id(),
1527		None => VA_INVALID_ID,
1528	}
1529}
1530
1531/// Ported from cros-codecs `fill_va_h264_pic` (BSD-3-Clause). Progressive only,
1532/// so the field cases collapse to the frame one.
1533fn fill_va_h264_pic(pic: &PictureData, surface_id: bindings::VASurfaceID) -> PictureH264 {
1534	let mut flags = 0;
1535	let frame_idx = match pic.reference() {
1536		Reference::LongTerm => {
1537			flags |= VA_PICTURE_H264_LONG_TERM_REFERENCE;
1538			pic.long_term_frame_idx
1539		}
1540		Reference::ShortTerm => {
1541			flags |= VA_PICTURE_H264_SHORT_TERM_REFERENCE;
1542			pic.frame_num
1543		}
1544		Reference::None => pic.frame_num,
1545	};
1546
1547	PictureH264::new(
1548		surface_id,
1549		frame_idx,
1550		flags,
1551		pic.top_field_order_cnt,
1552		pic.bottom_field_order_cnt,
1553	)
1554}
1555
1556/// Builds the picture used to fill the array slots there is no reference for.
1557fn build_invalid_va_h264_pic() -> PictureH264 {
1558	PictureH264::new(VA_INVALID_ID, 0, VA_PICTURE_H264_INVALID, 0, 0)
1559}
1560
1561/// Ported from cros-codecs `build_iq_matrix` (BSD-3-Clause). VA-API wants the
1562/// scaling lists in raster order, the PPS carries them zig-zag.
1563fn build_iq_matrix(pps: &Pps) -> BufferType {
1564	const ZIGZAG_4X4: [usize; 16] = [0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15];
1565	#[rustfmt::skip]
1566	const ZIGZAG_8X8: [usize; 64] = [
1567		0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27,
1568		20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51,
1569		58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63,
1570	];
1571
1572	let mut scaling_list4x4 = [[0u8; 16]; 6];
1573	for (list, src) in scaling_list4x4.iter_mut().zip(pps.scaling_lists_4x4.iter()) {
1574		for (i, &value) in src.iter().enumerate() {
1575			list[ZIGZAG_4X4[i]] = value;
1576		}
1577	}
1578
1579	let mut scaling_list8x8 = [[0u8; 64]; 2];
1580	for (list, src) in scaling_list8x8.iter_mut().zip(pps.scaling_lists_8x8.iter()) {
1581		for (i, &value) in src.iter().enumerate() {
1582			list[ZIGZAG_8X8[i]] = value;
1583		}
1584	}
1585
1586	BufferType::IQMatrix(IQMatrix::H264(IQMatrixBufferH264::new(
1587		scaling_list4x4,
1588		scaling_list8x8,
1589	)))
1590}
1591
1592/// Ported from cros-codecs `build_pic_param` (BSD-3-Clause).
1593fn build_pic_param(
1594	hdr: &SliceHeader,
1595	pic: &PictureData,
1596	surface_id: bindings::VASurfaceID,
1597	dpb: &Dpb<Handle>,
1598	sps: &Sps,
1599	pps: &Pps,
1600) -> BufferType {
1601	let curr_pic = fill_va_h264_pic(pic, surface_id);
1602
1603	let short_term = dpb
1604		.short_term_refs_iter()
1605		.filter(|entry| !entry.pic.borrow().nonexisting);
1606	let mut refs: Vec<PictureH264> = short_term
1607		.chain(dpb.long_term_refs_iter())
1608		.take(16)
1609		.map(|entry| fill_va_h264_pic(&entry.pic.borrow(), va_surface_id(&entry.reference)))
1610		.collect();
1611	refs.resize_with(16, build_invalid_va_h264_pic);
1612	let refs: [PictureH264; 16] = refs.try_into().unwrap_or_else(|_| unreachable!("resized to 16"));
1613
1614	let seq_fields = H264SeqFields::new(
1615		sps.chroma_format_idc as u32,
1616		sps.separate_colour_plane_flag as u32,
1617		sps.gaps_in_frame_num_value_allowed_flag as u32,
1618		sps.frame_mbs_only_flag as u32,
1619		sps.mb_adaptive_frame_field_flag as u32,
1620		sps.direct_8x8_inference_flag as u32,
1621		// See A.3.3.2.
1622		(sps.level_idc >= Level::L3_1) as u32,
1623		sps.log2_max_frame_num_minus4 as u32,
1624		sps.pic_order_cnt_type as u32,
1625		sps.log2_max_pic_order_cnt_lsb_minus4 as u32,
1626		sps.delta_pic_order_always_zero_flag as u32,
1627	);
1628
1629	let pic_fields = H264PicFields::new(
1630		pps.entropy_coding_mode_flag as u32,
1631		pps.weighted_pred_flag as u32,
1632		pps.weighted_bipred_idc as u32,
1633		pps.transform_8x8_mode_flag as u32,
1634		hdr.field_pic_flag as u32,
1635		pps.constrained_intra_pred_flag as u32,
1636		pps.bottom_field_pic_order_in_frame_present_flag as u32,
1637		pps.deblocking_filter_control_present_flag as u32,
1638		pps.redundant_pic_cnt_present_flag as u32,
1639		(pic.nal_ref_idc != 0) as u32,
1640	);
1641
1642	// SequenceInfo rejects interlaced input, so map units are frame macroblocks.
1643	let picture_height_in_mbs_minus1 = sps.pic_height_in_map_units_minus1;
1644
1645	BufferType::PictureParameter(PictureParameter::H264(PictureParameterBufferH264::new(
1646		curr_pic,
1647		refs,
1648		sps.pic_width_in_mbs_minus1,
1649		picture_height_in_mbs_minus1,
1650		sps.bit_depth_luma_minus8,
1651		sps.bit_depth_chroma_minus8,
1652		sps.max_num_ref_frames,
1653		&seq_fields,
1654		// FMO is not expressible in VA-API.
1655		0,
1656		0,
1657		0,
1658		pps.pic_init_qp_minus26,
1659		pps.pic_init_qs_minus26,
1660		pps.chroma_qp_index_offset,
1661		pps.second_chroma_qp_index_offset,
1662		&pic_fields,
1663		hdr.frame_num,
1664	)))
1665}
1666
1667/// Ported from cros-codecs `fill_ref_pic_list` (BSD-3-Clause).
1668fn fill_ref_pic_list(list: &[&DpbEntry<Handle>]) -> [PictureH264; 32] {
1669	let mut pics: Vec<PictureH264> = list
1670		.iter()
1671		.take(32)
1672		.map(|entry| fill_va_h264_pic(&entry.pic.borrow(), va_surface_id(&entry.reference)))
1673		.collect();
1674	pics.resize_with(32, build_invalid_va_h264_pic);
1675
1676	pics.try_into().unwrap_or_else(|_| unreachable!("resized to 32"))
1677}
1678
1679/// Ported from cros-codecs `build_slice_param` (BSD-3-Clause).
1680fn build_slice_param(
1681	hdr: &SliceHeader,
1682	slice_size: usize,
1683	list0: &[&DpbEntry<Handle>],
1684	list1: &[&DpbEntry<Handle>],
1685	sps: &Sps,
1686	pps: &Pps,
1687) -> BufferType {
1688	let pwt = &hdr.pred_weight_table;
1689
1690	let mut luma_weight_l0 = [0i16; 32];
1691	let mut luma_offset_l0 = [0i16; 32];
1692	let mut chroma_weight_l0 = [[0i16; 2]; 32];
1693	let mut chroma_offset_l0 = [[0i16; 2]; 32];
1694
1695	let mut luma_weight_l1 = [0i16; 32];
1696	let mut luma_offset_l1 = [0i16; 32];
1697	let mut chroma_weight_l1 = [[0i16; 2]; 32];
1698	let mut chroma_offset_l1 = [[0i16; 2]; 32];
1699
1700	// Explicit weighted prediction: P and SP slices weight list 0 only, B slices
1701	// weight both. Implicit (weighted_bipred_idc 2) is derived by the hardware.
1702	let fill_l0 = (pps.weighted_pred_flag && (hdr.slice_type.is_p() || hdr.slice_type.is_sp()))
1703		|| (pps.weighted_bipred_idc == 1 && hdr.slice_type.is_b());
1704	let fill_l1 = pps.weighted_bipred_idc == 1 && hdr.slice_type.is_b();
1705	let chroma = sps.chroma_array_type() != 0;
1706
1707	if fill_l0 {
1708		for i in 0..=usize::from(hdr.num_ref_idx_l0_active_minus1) {
1709			luma_weight_l0[i] = pwt.luma_weight_l0[i];
1710			luma_offset_l0[i] = i16::from(pwt.luma_offset_l0[i]);
1711			if chroma {
1712				for j in 0..2 {
1713					chroma_weight_l0[i][j] = pwt.chroma_weight_l0[i][j];
1714					chroma_offset_l0[i][j] = i16::from(pwt.chroma_offset_l0[i][j]);
1715				}
1716			}
1717		}
1718	}
1719
1720	if fill_l1 {
1721		for i in 0..=usize::from(hdr.num_ref_idx_l1_active_minus1) {
1722			luma_weight_l1[i] = pwt.luma_weight_l1[i];
1723			luma_offset_l1[i] = i16::from(pwt.luma_offset_l1[i]);
1724			if chroma {
1725				for j in 0..2 {
1726					chroma_weight_l1[i][j] = pwt.chroma_weight_l1[i][j];
1727					chroma_offset_l1[i][j] = i16::from(pwt.chroma_offset_l1[i][j]);
1728				}
1729			}
1730		}
1731	}
1732
1733	BufferType::SliceParameter(SliceParameter::H264(SliceParameterBufferH264::new(
1734		slice_size as u32,
1735		0,
1736		VA_SLICE_DATA_FLAG_ALL,
1737		hdr.header_bit_size as u16,
1738		hdr.first_mb_in_slice as u16,
1739		hdr.slice_type as u8,
1740		hdr.direct_spatial_mv_pred_flag as u8,
1741		hdr.num_ref_idx_l0_active_minus1,
1742		hdr.num_ref_idx_l1_active_minus1,
1743		hdr.cabac_init_idc,
1744		hdr.slice_qp_delta,
1745		hdr.disable_deblocking_filter_idc,
1746		hdr.slice_alpha_c0_offset_div2,
1747		hdr.slice_beta_offset_div2,
1748		fill_ref_pic_list(list0),
1749		fill_ref_pic_list(list1),
1750		pwt.luma_log2_weight_denom,
1751		pwt.chroma_log2_weight_denom,
1752		fill_l0 as u8,
1753		luma_weight_l0,
1754		luma_offset_l0,
1755		(fill_l0 && chroma) as u8,
1756		chroma_weight_l0,
1757		chroma_offset_l0,
1758		fill_l1 as u8,
1759		luma_weight_l1,
1760		luma_offset_l1,
1761		(fill_l1 && chroma) as u8,
1762		chroma_weight_l1,
1763		chroma_offset_l1,
1764	)))
1765}
1766
1767#[cfg(test)]
1768mod tests {
1769	use std::os::fd::OwnedFd;
1770	use std::process::Command;
1771
1772	use super::*;
1773
1774	fn progressive_sps() -> Sps {
1775		Sps {
1776			frame_mbs_only_flag: true,
1777			chroma_format_idc: 1,
1778			pic_width_in_mbs_minus1: 19,
1779			pic_height_in_map_units_minus1: 14,
1780			..Default::default()
1781		}
1782	}
1783
1784	#[test]
1785	fn rejects_nonzero_crop_origins() {
1786		for (left, top) in [(1, 0), (0, 1)] {
1787			let sps = Sps {
1788				frame_cropping_flag: true,
1789				frame_crop_left_offset: left,
1790				frame_crop_top_offset: top,
1791				..progressive_sps()
1792			};
1793			assert!(SequenceInfo::new(&sps)
1794				.unwrap_err()
1795				.to_string()
1796				.contains("left and top"));
1797		}
1798	}
1799
1800	#[test]
1801	fn crop_only_updates_change_sequence_identity() {
1802		let original = SequenceInfo::new(&progressive_sps()).unwrap();
1803		for (right, bottom, expected) in [(1, 0, (318, 240)), (0, 1, (320, 238))] {
1804			let cropped = SequenceInfo::new(&Sps {
1805				frame_cropping_flag: true,
1806				frame_crop_right_offset: right,
1807				frame_crop_bottom_offset: bottom,
1808				..progressive_sps()
1809			})
1810			.unwrap();
1811			assert_eq!(original.coded, cropped.coded);
1812			assert_eq!(cropped.visible, expected);
1813			assert_ne!(original, cropped);
1814		}
1815	}
1816
1817	#[test]
1818	fn rejects_invalid_crop_dimensions() {
1819		for crop in [160, 161, u32::MAX] {
1820			assert!(SequenceInfo::new(&Sps {
1821				frame_cropping_flag: true,
1822				frame_crop_right_offset: crop,
1823				..progressive_sps()
1824			})
1825			.is_err());
1826		}
1827	}
1828
1829	#[test]
1830	fn maximum_progressive_height_does_not_overflow_va_parameters() {
1831		let sps = Rc::new(Sps {
1832			pic_height_in_map_units_minus1: u16::MAX,
1833			..progressive_sps()
1834		});
1835		let params = build_pic_param(
1836			&SliceHeader::default(),
1837			&PictureData::new_non_existing(0, 0),
1838			VA_INVALID_ID,
1839			&Dpb::default(),
1840			&sps,
1841			&crate::codec::h264::parser::PpsBuilder::new(Rc::clone(&sps)).build(),
1842		);
1843		let BufferType::PictureParameter(PictureParameter::H264(params)) = params else {
1844			panic!("expected H.264 picture parameters");
1845		};
1846		assert_eq!(params.inner().picture_height_in_mbs_minus1, u16::MAX);
1847		assert!(SequenceInfo::new(&Sps {
1848			frame_mbs_only_flag: false,
1849			..progressive_sps()
1850		})
1851		.is_err());
1852	}
1853
1854	/// Encoder settings shared by the tests that need a stream rather than a
1855	/// particular picture.
1856	fn encoder_config(width: u32, height: u32) -> crate::encode::Config {
1857		crate::encode::Config::new(width, height, 30, 2_000_000, 30)
1858	}
1859
1860	/// A gray field with a moving block, so a picture that decoded into the
1861	/// wrong surface is visible as the block being in the wrong place.
1862	fn nv12_frame(width: u32, height: u32, step: u32) -> Vec<u8> {
1863		let (w, h) = (width as usize, height as usize);
1864		let mut data = vec![128u8; w * h * 3 / 2];
1865		let x0 = (step * 16) as usize % (w / 2);
1866		for y in h / 4..h / 2 {
1867			for x in x0..x0 + w / 4 {
1868				data[y * w + x] = 235;
1869			}
1870		}
1871		data
1872	}
1873
1874	/// Exported pictures the caller still holds never share a surface.
1875	///
1876	/// The invariant `export` exists to keep: a held surface stays out of the
1877	/// pool, because recycling it would have a later picture decoded over pixels
1878	/// the caller still holds a descriptor for. Two pictures coming
1879	/// back with the same modifier and layout is expected; two coming back on
1880	/// the same allocation is the bug. The modifier itself is hardware policy,
1881	/// so it is printed rather than asserted.
1882	#[test]
1883	fn exported_pictures_do_not_share_a_surface() {
1884		let (width, height) = (320u32, 240u32);
1885		let Ok(mut encoder) = crate::encode::Encoder::new(encoder_config(width, height)) else {
1886			eprintln!("skipping: no VA-API H.264 encoder");
1887			return;
1888		};
1889		let Ok(mut decoder) = Decoder::new(Config::new()) else {
1890			eprintln!("skipping: no VA-API H.264 decoder");
1891			return;
1892		};
1893
1894		let mut exported = Vec::new();
1895		for step in 0..8 {
1896			let unit = encoder
1897				.encode_nv12(&nv12_frame(width, height, step), step == 0)
1898				.expect("encode a picture");
1899			exported.extend(decoder.decode_exported(&unit, step as u64).expect("decode a picture"));
1900		}
1901		exported.extend(decoder.flush_exported().expect("flush the decoder"));
1902		assert!(!exported.is_empty(), "the decoder produced no pictures");
1903
1904		let first = &exported[0];
1905		eprintln!(
1906			"exported {} pictures: {}x{} fourcc {:#x} modifier {:#x}",
1907			exported.len(),
1908			first.width,
1909			first.height,
1910			first.descriptor.fourcc,
1911			first.descriptor.objects[0].drm_format_modifier
1912		);
1913
1914		let mut seen = Vec::new();
1915		for (index, frame) in exported.iter().enumerate() {
1916			assert_eq!((frame.width, frame.height), (width, height));
1917			assert_eq!(frame.descriptor.fourcc, VA_FOURCC_NV12);
1918			assert_eq!(frame.descriptor.objects.len(), 1, "a composed export is one object");
1919			assert_eq!(frame.descriptor.layers[0].num_planes, 2, "NV12 is two planes");
1920
1921			// Two live descriptors for one allocation would mean the pool handed
1922			// a held surface back out. The inode behind a DMA-BUF names the
1923			// buffer, and is what identifies one allocation from another.
1924			let inode = inode(&frame.descriptor.objects[0].fd);
1925			assert!(
1926				!seen.contains(&inode),
1927				"picture {index} was decoded into an allocation an earlier one still holds"
1928			);
1929			seen.push(inode);
1930		}
1931	}
1932
1933	/// With no node named, the decoder opens one that decodes H.264. A named
1934	/// node is used or refused, never swapped for another.
1935	#[test]
1936	fn the_render_node_is_found_or_named() {
1937		if let Err(err) = Decoder::new(Config::new()) {
1938			eprintln!("skipping: no VA-API H.264 decoder: {err:#}");
1939			return;
1940		}
1941		let named = Config {
1942			device: Some(PathBuf::from("/dev/null")),
1943		};
1944		assert!(
1945			Decoder::new(named).is_err(),
1946			"a named node that is not a render node was swapped for one that is"
1947		);
1948	}
1949
1950	/// Every picture fed in comes back out, the last of them only on flush.
1951	///
1952	/// C.4.5.3 bumps the DPB when a new picture needs a slot, so a stream that
1953	/// simply stops leaves its tail sitting there: one picture for this crate's
1954	/// IPPP encoder, three for a stream coded the way x264 does by default. The
1955	/// first assertion keeps the test honest, since a decoder that held nothing
1956	/// back would satisfy the rest without a flush ever running.
1957	#[test]
1958	fn flush_returns_the_pictures_the_dpb_still_holds() {
1959		const PICTURES: u64 = 5;
1960		let (width, height) = (320u32, 240u32);
1961
1962		let Ok(mut encoder) = crate::encode::Encoder::new(encoder_config(width, height)) else {
1963			eprintln!("skipping: no VA-API H.264 encoder");
1964			return;
1965		};
1966		let Ok(mut decoder) = Decoder::new(Config::new()) else {
1967			eprintln!("skipping: no VA-API H.264 decoder");
1968			return;
1969		};
1970
1971		let mut streamed = Vec::new();
1972		for step in 0..PICTURES {
1973			let unit = encoder
1974				.encode_nv12(&nv12_frame(width, height, step as u32), step == 0)
1975				.expect("encode a picture");
1976			streamed.extend(decoder.decode(&unit, step).expect("decode a picture"));
1977		}
1978		assert!(
1979			(streamed.len() as u64) < PICTURES,
1980			"the DPB held nothing back, so this test proves nothing"
1981		);
1982
1983		let flushed = decoder.flush().expect("flush the decoder");
1984		let timestamps: Vec<u64> = streamed
1985			.iter()
1986			.chain(&flushed)
1987			.map(|picture| picture.timestamp)
1988			.collect();
1989		assert_eq!(
1990			timestamps,
1991			(0..PICTURES).collect::<Vec<_>>(),
1992			"the stream lost pictures at its end"
1993		);
1994
1995		// The bumping process marks what it hands out, so a second flush finds
1996		// nothing and a caller that flushes twice never sees a picture twice.
1997		assert!(decoder.flush().expect("flush an empty decoder").is_empty());
1998	}
1999
2000	/// A picture the consumer has let go of is decoded into again rather than
2001	/// replaced by a new allocation, and the recycled surfaces still carry the
2002	/// right pictures.
2003	///
2004	/// Each picture is read back and dropped before the next access unit goes
2005	/// in, the way a renderer that draws a picture and moves on behaves. Without
2006	/// recycling this allocated one surface per picture.
2007	#[test]
2008	fn a_dropped_export_is_decoded_into_again() {
2009		const PICTURES: u32 = 60;
2010		let (width, height) = (320u32, 240u32);
2011		let Ok(mut encoder) = crate::encode::Encoder::new(encoder_config(width, height)) else {
2012			eprintln!("skipping: no VA-API H.264 encoder");
2013			return;
2014		};
2015		let Ok(mut exporting) = Decoder::new(Config::new()) else {
2016			eprintln!("skipping: no VA-API H.264 decoder");
2017			return;
2018		};
2019		let mut downloading = Decoder::new(Config::new()).expect("a second decoder");
2020
2021		let mut compared = 0;
2022		for step in 0..PICTURES {
2023			let unit = encoder
2024				.encode_nv12(&nv12_frame(width, height, step), step == 0)
2025				.expect("encode a picture");
2026			let exported = exporting
2027				.decode_exported(&unit, step as u64)
2028				.expect("decode to a descriptor");
2029			let downloaded = downloading.decode(&unit, step as u64).expect("decode to client memory");
2030			assert_eq!(exported.len(), downloaded.len(), "the two decoders disagreed");
2031			for (gpu, cpu) in exported.iter().zip(&downloaded) {
2032				let read_back = gpu.download().expect("read the exported surface back");
2033				assert!(read_back.data == cpu.data, "picture {} came back wrong", gpu.timestamp);
2034				compared += 1;
2035			}
2036		}
2037		assert!(compared > 0, "the decoder produced no pictures");
2038
2039		let pool = &exporting.sequence.as_ref().expect("a sequence").pool;
2040		let allocated = pool.allocated.get();
2041		eprintln!("{allocated} surfaces allocated for {PICTURES} exported pictures");
2042		assert!(
2043			allocated <= pool.free.capacity,
2044			"{allocated} surfaces for {PICTURES} pictures; a full DPB and the picture in flight need {}",
2045			pool.free.capacity
2046		);
2047	}
2048
2049	/// A consumer that holds many pictures and then lets them all go leaves at
2050	/// most the pool's capacity behind, and the next pictures reuse those.
2051	#[test]
2052	fn the_pool_keeps_a_bounded_number_of_free_surfaces() {
2053		const HELD: u32 = 40;
2054		let (width, height) = (320u32, 240u32);
2055		let Ok(mut encoder) = crate::encode::Encoder::new(encoder_config(width, height)) else {
2056			eprintln!("skipping: no VA-API H.264 encoder");
2057			return;
2058		};
2059		let Ok(mut decoder) = Decoder::new(Config::new()) else {
2060			eprintln!("skipping: no VA-API H.264 decoder");
2061			return;
2062		};
2063
2064		let mut step = 0;
2065		let mut decode = |decoder: &mut Decoder| {
2066			let unit = encoder
2067				.encode_nv12(&nv12_frame(width, height, step), step == 0)
2068				.expect("encode a picture");
2069			step += 1;
2070			decoder.decode_exported(&unit, step as u64).expect("decode a picture")
2071		};
2072
2073		let mut held = Vec::new();
2074		for _ in 0..HELD {
2075			held.extend(decode(&mut decoder));
2076		}
2077		let pool = &decoder.sequence.as_ref().expect("a sequence").pool;
2078		let allocated = pool.allocated.get();
2079		assert!(
2080			allocated >= held.len(),
2081			"{} pictures held on {allocated} surfaces",
2082			held.len()
2083		);
2084
2085		drop(held);
2086		let free = pool.free.surfaces.lock().unwrap().len();
2087		assert!(
2088			free <= pool.free.capacity,
2089			"{free} surfaces left free, more than the capacity of {}",
2090			pool.free.capacity
2091		);
2092
2093		for _ in 0..10 {
2094			drop(decode(&mut decoder));
2095		}
2096		let pool = &decoder.sequence.as_ref().expect("a sequence").pool;
2097		assert_eq!(
2098			pool.allocated.get(),
2099			allocated,
2100			"pictures after the release did not reuse the free surfaces"
2101		);
2102	}
2103
2104	/// An exported picture crosses threads, which is the point of handing one to
2105	/// a renderer: the decoder runs on its own thread and the frame outlives it.
2106	/// [`Decoder`] is neither [`Send`] nor [`Sync`], so this pins that the export
2107	/// does not inherit that.
2108	#[test]
2109	fn an_exported_frame_is_send_and_sync() {
2110		const fn assert_send_sync<T: Send + Sync>() {}
2111		assert_send_sync::<ExportedFrame>();
2112	}
2113
2114	/// An exported picture reads back to the same pixels the ordinary path
2115	/// downloads.
2116	///
2117	/// The invariant that lets a GPU-resident frame keep a CPU fallback: the
2118	/// surface travels with the descriptor, so `vaDeriveImage` still
2119	/// reaches the picture the descriptor points at. One stream through two
2120	/// decoders, so both sides see the same pictures in the same order and the
2121	/// comparison is exact rather than approximate.
2122	#[test]
2123	fn an_exported_picture_downloads_to_the_same_pixels() {
2124		let (width, height) = (320u32, 240u32);
2125		let Ok(mut encoder) = crate::encode::Encoder::new(encoder_config(width, height)) else {
2126			eprintln!("skipping: no VA-API H.264 encoder");
2127			return;
2128		};
2129		let Ok(mut exporting) = Decoder::new(Config::new()) else {
2130			eprintln!("skipping: no VA-API H.264 decoder");
2131			return;
2132		};
2133		let mut downloading = Decoder::new(Config::new()).expect("a second decoder");
2134
2135		let mut exported = Vec::new();
2136		let mut downloaded = Vec::new();
2137		for step in 0..8 {
2138			let unit = encoder
2139				.encode_nv12(&nv12_frame(width, height, step), step == 0)
2140				.expect("encode a picture");
2141			exported.extend(
2142				exporting
2143					.decode_exported(&unit, step as u64)
2144					.expect("decode to a descriptor"),
2145			);
2146			downloaded.extend(downloading.decode(&unit, step as u64).expect("decode to client memory"));
2147		}
2148		exported.extend(exporting.flush_exported().expect("flush the exporting decoder"));
2149		downloaded.extend(downloading.flush().expect("flush the downloading decoder"));
2150
2151		assert!(!exported.is_empty(), "the decoder produced no pictures");
2152		assert_eq!(exported.len(), downloaded.len(), "the two decoders disagreed");
2153
2154		for (index, (gpu, cpu)) in exported.iter().zip(&downloaded).enumerate() {
2155			let read_back = gpu.download().expect("read the exported surface back");
2156			assert_eq!(read_back.timestamp, cpu.timestamp, "picture {index} lost its timestamp");
2157			assert_eq!((read_back.width, read_back.height), (cpu.width, cpu.height));
2158			assert!(
2159				read_back.data == cpu.data,
2160				"picture {index} read back differently than it downloaded"
2161			);
2162		}
2163	}
2164
2165	/// Decoded pictures match ffmpeg's software decoder byte for byte, in the
2166	/// order both put them out.
2167	///
2168	/// The test that says this is a decoder rather than plumbing. The stream is
2169	/// coded with B-frames and three reference frames, so matching the reference
2170	/// means the reference lists, the picture order counts, and the DPB bumping
2171	/// are all right, not just the surfaces. Skips without ffmpeg or a device.
2172	#[test]
2173	fn decoded_pictures_match_a_software_decoder() {
2174		const WIDTH: u32 = 320;
2175		const HEIGHT: u32 = 240;
2176		const PICTURES: usize = 30;
2177
2178		let Some(stream) = ffmpeg(&[
2179			"-f",
2180			"lavfi",
2181			"-i",
2182			&format!("testsrc2=size={WIDTH}x{HEIGHT}:rate=30"),
2183			"-frames:v",
2184			&PICTURES.to_string(),
2185			"-c:v",
2186			"libx264",
2187			"-profile:v",
2188			"main",
2189			"-bf",
2190			"2",
2191			"-refs",
2192			"3",
2193			"-g",
2194			"15",
2195			"-pix_fmt",
2196			"yuv420p",
2197			"-f",
2198			"h264",
2199			"-",
2200		]) else {
2201			eprintln!("skipping: no ffmpeg with libx264");
2202			return;
2203		};
2204
2205		// ffmpeg reads the elementary stream from a file rather than a pipe, so
2206		// the test never has to drain its output while still writing its input.
2207		let path = std::env::temp_dir().join(format!("moq-vaapi-decode-{}.264", std::process::id()));
2208		std::fs::write(&path, &stream).expect("write the elementary stream");
2209		let reference = ffmpeg(&[
2210			"-i",
2211			path.to_str().expect("a UTF-8 temporary path"),
2212			"-pix_fmt",
2213			"nv12",
2214			"-f",
2215			"rawvideo",
2216			"-",
2217		]);
2218		let _ = std::fs::remove_file(&path);
2219		let reference = reference.expect("ffmpeg decodes what it just encoded");
2220
2221		let picture_len = (WIDTH * HEIGHT * 3 / 2) as usize;
2222		assert_eq!(
2223			reference.len(),
2224			PICTURES * picture_len,
2225			"ffmpeg decoded a different count"
2226		);
2227
2228		let Ok(mut decoder) = Decoder::new(Config::new()) else {
2229			eprintln!("skipping: no VA-API H.264 decoder");
2230			return;
2231		};
2232
2233		let units = split_access_units(&stream);
2234		let mut decoded = Vec::new();
2235		for (index, unit) in units.iter().enumerate() {
2236			decoded.extend(decoder.decode(unit, index as u64).expect("decode an access unit"));
2237		}
2238		decoded.extend(decoder.flush().expect("flush the decoder"));
2239		assert_eq!(decoded.len(), PICTURES, "the decoder lost or invented pictures");
2240
2241		let mut timestamps: Vec<u64> = decoded.iter().map(|picture| picture.timestamp).collect();
2242		assert!(
2243			timestamps.windows(2).any(|pair| pair[0] > pair[1]),
2244			"the stream came out in decode order, so nothing here tests reordering"
2245		);
2246		timestamps.sort_unstable();
2247		assert_eq!(
2248			timestamps,
2249			(0..PICTURES as u64).collect::<Vec<_>>(),
2250			"a picture's timestamp did not survive reordering"
2251		);
2252
2253		for (index, picture) in decoded.iter().enumerate() {
2254			assert_eq!((picture.width, picture.height), (WIDTH, HEIGHT));
2255			assert!(
2256				picture.data == reference[index * picture_len..][..picture_len],
2257				"picture {index} of {PICTURES} differs from the software decoder"
2258			);
2259		}
2260	}
2261
2262	/// A second sequence at another size decodes at that size, and the first
2263	/// sequence's tail comes out ahead of it.
2264	///
2265	/// What `apply_sps` promises: an SPS that changes the coded size rebuilds the
2266	/// VA context and the surface pool, and everything decoded against the old
2267	/// one is let out first so no picture is read back at the wrong size.
2268	#[test]
2269	fn a_new_sequence_decodes_at_its_own_size() {
2270		const PER_SEQUENCE: u32 = 6;
2271		let sizes = [(320u32, 240u32), (192u32, 144u32)];
2272
2273		let Ok(mut decoder) = Decoder::new(Config::new()) else {
2274			eprintln!("skipping: no VA-API H.264 decoder");
2275			return;
2276		};
2277
2278		let mut decoded = Vec::new();
2279		let mut timestamp = 0u64;
2280		for &(width, height) in &sizes {
2281			let Ok(mut encoder) = crate::encode::Encoder::new(encoder_config(width, height)) else {
2282				eprintln!("skipping: no VA-API H.264 encoder");
2283				return;
2284			};
2285			for step in 0..PER_SEQUENCE {
2286				let unit = encoder
2287					.encode_nv12(&nv12_frame(width, height, step), step == 0)
2288					.expect("encode a picture");
2289				decoded.extend(decoder.decode(&unit, timestamp).expect("decode a picture"));
2290				timestamp += 1;
2291			}
2292		}
2293		decoded.extend(decoder.flush().expect("flush the decoder"));
2294
2295		let expected: Vec<(u32, u32)> = sizes
2296			.iter()
2297			.flat_map(|&size| std::iter::repeat_n(size, PER_SEQUENCE as usize))
2298			.collect();
2299		let actual: Vec<(u32, u32)> = decoded.iter().map(|picture| (picture.width, picture.height)).collect();
2300		assert_eq!(actual, expected, "a picture came out at the wrong size or out of order");
2301
2302		let timestamps: Vec<u64> = decoded.iter().map(|picture| picture.timestamp).collect();
2303		assert_eq!(timestamps, (0..timestamp).collect::<Vec<_>>());
2304
2305		for picture in &decoded {
2306			let expected_len = (picture.width * picture.height * 3 / 2) as usize;
2307			assert_eq!(
2308				picture.data.len(),
2309				expected_len,
2310				"a picture was read back at the wrong size"
2311			);
2312		}
2313	}
2314
2315	/// Runs ffmpeg with `args`, returning its standard output, or `None` when
2316	/// ffmpeg is missing or refused the arguments.
2317	fn ffmpeg(args: &[&str]) -> Option<Vec<u8>> {
2318		let output = Command::new("ffmpeg").arg("-v").arg("error").args(args).output().ok()?;
2319		if !output.status.success() {
2320			eprintln!("ffmpeg {args:?} failed: {}", String::from_utf8_lossy(&output.stderr));
2321			return None;
2322		}
2323		Some(output.stdout)
2324	}
2325
2326	/// Splits an Annex-B elementary stream into access units.
2327	///
2328	/// Each picture here is a single slice, so a unit ends at the next VCL NAL
2329	/// unit, or at the next parameter set or SEI that follows one.
2330	fn split_access_units(stream: &[u8]) -> Vec<Vec<u8>> {
2331		let starts: Vec<usize> = (0..stream.len().saturating_sub(3))
2332			.filter(|&i| stream[i..i + 3] == [0, 0, 1])
2333			.collect();
2334
2335		let mut units = Vec::new();
2336		let mut current: Vec<u8> = Vec::new();
2337		let mut current_has_picture = false;
2338
2339		for (index, &start) in starts.iter().enumerate() {
2340			let end = starts.get(index + 1).copied().unwrap_or(stream.len());
2341			let nal_type = stream[start + 3] & 0x1f;
2342			let is_slice = (1..=5).contains(&nal_type);
2343
2344			if current_has_picture && (is_slice || matches!(nal_type, 6..=9)) {
2345				units.push(std::mem::take(&mut current));
2346				current_has_picture = false;
2347			}
2348
2349			current.extend_from_slice(&stream[start..end]);
2350			current_has_picture |= is_slice;
2351		}
2352
2353		if !current.is_empty() {
2354			units.push(current);
2355		}
2356		units
2357	}
2358
2359	/// The inode of a descriptor, which for a DMA-BUF names the allocation
2360	/// behind it: every buffer gets its own on the anonymous dma-buf filesystem.
2361	fn inode(fd: &OwnedFd) -> u64 {
2362		use std::os::unix::fs::MetadataExt as _;
2363
2364		// Stat a duplicate, so the descriptor the caller holds is untouched and
2365		// the `File` wrapper closes only its own copy.
2366		let file = std::fs::File::from(fd.try_clone().expect("duplicate the descriptor"));
2367		file.metadata().expect("stat the descriptor").ino()
2368	}
2369}