Skip to main content

moq_vaapi/
encode.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 (encoder/stateless/h264/vaapi.rs), itself
6// BSD-3-Clause / Copyright The ChromiumOS Authors. See LICENSE.cros-codecs.
7
8//! Thin VA-API H.264 encoder built on the vendored libva binding.
9//!
10//! Reuses the backend-agnostic bitstream layer ([`crate::codec::h264`]) from
11//! discord/cros-codecs (BSD-3-Clause) for SPS/PPS/slice synthesis, and drives
12//! libva directly for surface upload + slice submission, rather than vendoring
13//! cros-codecs's generic multi-backend encoder framework. The per-frame VA
14//! buffer population is ported from cros-codecs's `encoder/stateless/h264/vaapi.rs`.
15//!
16//! Low-latency only: IPPP (no B-frames), one reference frame, matching the
17//! VideoToolbox / Media Foundation / NVENC backends in moq-video.
18//!
19//! NOTE: compile-verified against libva 1.23, but the emitted bitstream has not
20//! been validated at playback. The per-frame param population, the reconstructed
21//! surface / reference rotation, and NV12 stride handling are the spots most
22//! likely to need hardware tuning.
23
24use std::path::PathBuf;
25use std::rc::Rc;
26use std::sync::Arc;
27
28use crate::codec::h264::parser::SpsBuilder;
29use crate::codec::h264::parser::{Level, Pps, PpsBuilder, Profile, SliceHeader, SliceHeaderBuilder, SliceType, Sps};
30use crate::codec::h264::synthesizer::Synthesizer;
31use crate::{
32	BufferType, Config as VaConfig, Context, Display, EncCodedBuffer, EncMiscParameter, EncMiscParameterFrameRate,
33	EncMiscParameterRateControl, EncPackedHeaderParameter, EncPackedHeaderType, EncPictureParameter,
34	EncPictureParameterBufferH264, EncSequenceParameter, EncSequenceParameterBufferH264, EncSliceParameter,
35	EncSliceParameterBufferH264, H264EncFrameCropOffsets, H264EncPicFields, H264EncSeqFields, H264VuiFields, Image,
36	MappedCodedBuffer, Picture, PictureH264, RcFlags, Surface, UsageHint, VAConfigAttrib, VAConfigAttribType,
37	VAEntrypoint, VAProfile, VA_FOURCC_NV12, VA_INVALID_ID, VA_PICTURE_H264_INVALID,
38	VA_PICTURE_H264_SHORT_TERM_REFERENCE, VA_RC_CBR, VA_RT_FORMAT_YUV420,
39};
40
41/// Whether a frame is used as a reference, and for how long.
42/// (Vendored from discord/cros-codecs, BSD-3-Clause; used by the slice synthesizer.)
43#[derive(Copy, Clone, PartialEq, Eq, Debug)]
44pub enum IsReference {
45	No,
46	ShortTerm,
47	LongTerm,
48}
49
50const MIN_QP: u8 = 1;
51const MAX_QP: u8 = 51;
52/// `max_frame_num` upper bound for the low-delay GOP; matches cros-codecs.
53const LIMIT: u32 = 2048;
54
55/// H.264 encoder configuration. Dimensions must be even (4:2:0 chroma).
56#[derive(Clone, Debug)]
57pub struct Config {
58	pub width: u32,
59	pub height: u32,
60	pub framerate: u32,
61	/// Target bitrate in bits per second (CBR).
62	pub bitrate: u32,
63	/// Keyframe interval in frames.
64	pub gop: u32,
65	/// DRM render node to open (e.g. `/dev/dri/renderD128`).
66	pub device: PathBuf,
67}
68
69impl Config {
70	pub fn new(width: u32, height: u32, framerate: u32, bitrate: u32, gop: u32) -> Self {
71		Self {
72			width,
73			height,
74			framerate,
75			bitrate,
76			gop,
77			device: PathBuf::from("/dev/dri/renderD128"),
78		}
79	}
80}
81
82/// Per-frame reference metadata. (Mirrors cros-codecs `DpbEntryMeta`.)
83#[derive(Copy, Clone)]
84struct FrameMeta {
85	poc: u16,
86	frame_num: u32,
87}
88
89/// A VA-API H.264 encoder. Built once, fed NV12 frames, emits Annex-B H.264.
90pub struct Encoder {
91	config: Config,
92	sps: Rc<Sps>,
93	pps: Rc<Pps>,
94	width_mbs: u16,
95	height_mbs: u16,
96
97	// Source surface (reused; NV12 re-uploaded per frame) and a 2-deep
98	// reconstructed surface ring (current + previous reference for IPPP).
99	input: Option<Surface<()>>,
100	recon: Vec<Surface<()>>,
101	/// Reference from the previous encoded frame: (recon index, metadata).
102	reference: Option<(usize, FrameMeta)>,
103
104	counter: u32,
105
106	// Drop order: encode resources before the display they belong to.
107	coded: EncCodedBuffer,
108	context: Rc<Context>,
109	_va_config: VaConfig,
110	display: Arc<Display>,
111}
112
113impl Encoder {
114	pub fn new(config: Config) -> anyhow::Result<Self> {
115		let display = Display::open_drm_display(&config.device)
116			.map_err(|e| anyhow::anyhow!("open DRM display {:?}: {e:?}", config.device))?;
117
118		let attrs = vec![
119			VAConfigAttrib {
120				type_: VAConfigAttribType::VAConfigAttribRTFormat,
121				value: VA_RT_FORMAT_YUV420,
122			},
123			VAConfigAttrib {
124				type_: VAConfigAttribType::VAConfigAttribRateControl,
125				value: VA_RC_CBR,
126			},
127		];
128		let va_config = display
129			.create_config(attrs, VAProfile::VAProfileH264Main, VAEntrypoint::VAEntrypointEncSlice)
130			.map_err(|e| anyhow::anyhow!("create VA config: {e:?}"))?;
131
132		let context = display
133			.create_context::<()>(&va_config, config.width, config.height, None, true)
134			.map_err(|e| anyhow::anyhow!("create VA context: {e:?}"))?;
135
136		let coded_size = (config.bitrate as usize / 4).max(1_500_000);
137		let coded = context
138			.create_enc_coded(coded_size)
139			.map_err(|e| anyhow::anyhow!("create coded buffer: {e:?}"))?;
140
141		let input = make_surface(&display, &config)?;
142		let recon = vec![make_surface(&display, &config)?, make_surface(&display, &config)?];
143
144		let (sps, pps) = build_sps_pps(&config);
145		let width_mbs = sps.pic_width_in_mbs_minus1 + 1;
146		let height_mbs = sps.pic_height_in_map_units_minus1 + 1;
147
148		log::info!(
149			"opened VA-API H.264 encoder: {}x{} @ {}fps, {} bps",
150			config.width,
151			config.height,
152			config.framerate,
153			config.bitrate
154		);
155		Ok(Self {
156			config,
157			sps,
158			pps,
159			width_mbs,
160			height_mbs,
161			input: Some(input),
162			recon,
163			reference: None,
164			counter: 0,
165			coded,
166			context,
167			_va_config: va_config,
168			display,
169		})
170	}
171
172	/// Encode one tightly-packed NV12 frame, returning Annex-B H.264 (with inline
173	/// SPS/PPS on each IDR). `keyframe` forces an IDR.
174	pub fn encode_nv12(&mut self, nv12: &[u8], keyframe: bool) -> anyhow::Result<Vec<u8>> {
175		let is_idr = keyframe || self.counter % self.config.gop == 0;
176		if is_idr {
177			// IDR resets the H.264 frame numbering and the reference list.
178			self.counter = 0;
179			self.reference = None;
180		}
181
182		let input = self.input.take().expect("input surface present");
183		upload_nv12(&self.display, &input, self.config.width, self.config.height, nv12)?;
184
185		let meta = FrameMeta {
186			poc: ((self.counter * 2) & 0xffff) as u16,
187			frame_num: self.counter,
188		};
189		let recon_idx = (self.counter as usize) % self.recon.len();
190
191		let slice_type = if is_idr { SliceType::I } else { SliceType::P };
192		let header = SliceHeaderBuilder::new(&self.pps)
193			.slice_type(slice_type)
194			.first_mb_in_slice(0)
195			.frame_num(meta.frame_num as u16)
196			.pic_order_cnt_lsb(meta.poc)
197			.build();
198
199		let num_macroblocks = self.width_mbs as u32 * self.height_mbs as u32;
200		let bits_per_second = self.config.bitrate;
201
202		// Reference (previous reconstructed frame) for P slices: surface id + meta.
203		let reference = match (is_idr, self.reference) {
204			(false, Some((ref_idx, ref_meta))) => Some((self.recon[ref_idx].id(), ref_meta)),
205			_ => None,
206		};
207
208		let seq_param = build_enc_seq_param(&self.sps, bits_per_second, LIMIT, 0);
209		let pic_param = build_enc_pic_param(
210			&self.pps,
211			&self.coded,
212			self.recon[recon_idx].id(),
213			meta,
214			is_idr,
215			reference,
216		);
217		let slice_param = build_enc_slice_param(&self.pps, &header, reference, num_macroblocks);
218
219		let mut picture = Picture::new(meta.frame_num as u64, Rc::clone(&self.context), input);
220
221		// VA-API spec buffer order: sequence, picture, slice, then packed headers,
222		// then rate-control misc params.
223		picture.add_buffer(self.create(seq_param)?);
224		picture.add_buffer(self.create(pic_param)?);
225		picture.add_buffer(self.create(slice_param)?);
226
227		if is_idr {
228			let (sps_param, sps_data) = packed_header(EncPackedHeaderType::Sequence, &self.packed_sps()?);
229			let (pps_param, pps_data) = packed_header(EncPackedHeaderType::Picture, &self.packed_pps()?);
230			picture.add_buffer(self.create(sps_param)?);
231			picture.add_buffer(self.create(sps_data)?);
232			picture.add_buffer(self.create(pps_param)?);
233			picture.add_buffer(self.create(pps_data)?);
234		}
235
236		let rc = EncMiscParameterRateControl::new(
237			bits_per_second,
238			100,                                 // target_percentage (CBR)
239			self.config.framerate.max(1) * 1000, // window_size (ms)
240			26,                                  // initial_qp
241			MIN_QP as u32,
242			0, // basic_unit_size
243			RcFlags::default(),
244			0, // icq_quality_factor
245			MAX_QP as u32,
246			0, // quality_factor
247			0, // target_frame_size
248		);
249		picture.add_buffer(self.create(BufferType::EncMiscParameter(EncMiscParameter::RateControl(rc)))?);
250
251		let framerate = EncMiscParameterFrameRate::new(self.config.framerate, 0);
252		picture.add_buffer(self.create(BufferType::EncMiscParameter(EncMiscParameter::FrameRate(framerate)))?);
253
254		let picture = picture.begin().map_err(|e| anyhow::anyhow!("picture begin: {e:?}"))?;
255		let picture = picture.render().map_err(|e| anyhow::anyhow!("picture render: {e:?}"))?;
256		let picture = picture.end().map_err(|e| anyhow::anyhow!("picture end: {e:?}"))?;
257		// Sync (PictureEnd -> PictureSync) so the surface is reclaimable and the
258		// coded buffer is ready to read.
259		let picture = picture
260			.sync()
261			.map_err(|(e, _)| anyhow::anyhow!("picture sync: {e:?}"))?;
262
263		// Reclaim the source surface for the next frame.
264		self.input = Some(
265			picture
266				.take_surface()
267				.map_err(|_| anyhow::anyhow!("reclaim input surface (still referenced)"))?,
268		);
269
270		// The reconstructed surface syncs implicitly; read the coded bitstream.
271		let bitstream = self.read_coded()?;
272
273		self.reference = Some((recon_idx, meta));
274		self.counter += 1;
275		Ok(bitstream)
276	}
277
278	fn create(&self, buffer: BufferType) -> anyhow::Result<crate::Buffer> {
279		self.context
280			.create_buffer(buffer)
281			.map_err(|e| anyhow::anyhow!("create VA buffer: {e:?}"))
282	}
283
284	fn packed_sps(&self) -> anyhow::Result<Vec<u8>> {
285		let mut buf = Vec::new();
286		Synthesizer::<'_, Sps, _>::synthesize(3, &self.sps, &mut buf, true)
287			.map_err(|e| anyhow::anyhow!("synthesize SPS: {e:?}"))?;
288		Ok(buf)
289	}
290
291	fn packed_pps(&self) -> anyhow::Result<Vec<u8>> {
292		let mut buf = Vec::new();
293		Synthesizer::<'_, Pps, _>::synthesize(3, &self.pps, &mut buf, true)
294			.map_err(|e| anyhow::anyhow!("synthesize PPS: {e:?}"))?;
295		Ok(buf)
296	}
297
298	fn read_coded(&self) -> anyhow::Result<Vec<u8>> {
299		let mapped = MappedCodedBuffer::new(&self.coded).map_err(|e| anyhow::anyhow!("map coded buffer: {e:?}"))?;
300		let mut out = Vec::new();
301		for segment in mapped.iter() {
302			out.extend_from_slice(segment.buf);
303		}
304		Ok(out)
305	}
306}
307
308/// Allocate one NV12 encode surface.
309fn make_surface(display: &Arc<Display>, config: &Config) -> anyhow::Result<Surface<()>> {
310	let mut surfaces = display
311		.create_surfaces::<()>(
312			VA_RT_FORMAT_YUV420,
313			Some(VA_FOURCC_NV12),
314			config.width,
315			config.height,
316			Some(UsageHint::USAGE_HINT_ENCODER),
317			vec![()],
318		)
319		.map_err(|e| anyhow::anyhow!("create surface: {e:?}"))?;
320	surfaces.pop().ok_or_else(|| anyhow::anyhow!("no surface created"))
321}
322
323/// Upload tightly-packed NV12 into a surface. Ported from cros-codecs
324/// `upload_nv12_img` (honors the image's per-plane offsets + pitches).
325fn upload_nv12(
326	display: &Arc<Display>,
327	surface: &Surface<()>,
328	width: u32,
329	height: u32,
330	data: &[u8],
331) -> anyhow::Result<()> {
332	let formats = display
333		.query_image_formats()
334		.map_err(|e| anyhow::anyhow!("query image formats: {e:?}"))?;
335	let format = formats
336		.into_iter()
337		.find(|f| f.fourcc == VA_FOURCC_NV12)
338		.ok_or_else(|| anyhow::anyhow!("driver has no NV12 image format"))?;
339
340	let mut image = Image::create_from(surface, format, surface.size(), surface.size())
341		.map_err(|e| anyhow::anyhow!("create image: {e:?}"))?;
342	let va_image = *image.image();
343	let dest: &mut [u8] = image.as_mut();
344	let (w, h) = (width as usize, height as usize);
345
346	// Luma plane.
347	let mut src = data;
348	let mut dst = &mut dest[va_image.offsets[0] as usize..];
349	for _ in 0..h {
350		dst[..w].copy_from_slice(&src[..w]);
351		dst = &mut dst[va_image.pitches[0] as usize..];
352		src = &src[w..];
353	}
354	// Interleaved chroma plane (h/2 rows of w bytes).
355	let mut src = &data[w * h..];
356	let mut dst = &mut dest[va_image.offsets[1] as usize..];
357	for _ in 0..h / 2 {
358		dst[..w].copy_from_slice(&src[..w]);
359		dst = &mut dst[va_image.pitches[1] as usize..];
360		src = &src[w..];
361	}
362
363	drop(image);
364	surface.sync().map_err(|e| anyhow::anyhow!("surface sync: {e:?}"))?;
365	Ok(())
366}
367
368/// Build the SPS/PPS for a low-latency IPPP stream. Ported from
369/// discord/cros-codecs `LowDelayH264Delegate::new_sequence` (BSD-3-Clause).
370fn build_sps_pps(config: &Config) -> (Rc<Sps>, Rc<Pps>) {
371	let level = Level::L4;
372	let sps = SpsBuilder::new()
373		.seq_parameter_set_id(0)
374		.profile_idc(Profile::Main)
375		.chroma_format_idc(1)
376		.level_idc(level)
377		.max_frame_num(LIMIT)
378		.pic_order_cnt_type(0)
379		.max_pic_order_cnt_lsb(LIMIT * 2)
380		.max_num_ref_frames(1)
381		.frame_mbs_only_flag(true)
382		.direct_8x8_inference_flag(level >= Level::L3)
383		.resolution(config.width, config.height)
384		.bit_depth_luma(8)
385		.bit_depth_chroma(8)
386		.aspect_ratio(1, 1)
387		.timing_info(1, config.framerate * 2, false)
388		.max_num_reorder_frames(0)
389		.max_dec_frame_buffering(1)
390		.build();
391
392	let pps = PpsBuilder::new(Rc::clone(&sps))
393		.pic_parameter_set_id(0)
394		.pic_init_qp(26)
395		.entropy_coding_mode_flag(true)
396		.transform_8x8_mode_flag(false)
397		.deblocking_filter_control_present_flag(true)
398		.num_ref_idx_l0_default_active(1)
399		.num_ref_idx_l1_default_active_minus1(0)
400		.build();
401
402	(sps, pps)
403}
404
405fn build_invalid_pic() -> PictureH264 {
406	PictureH264::new(VA_INVALID_ID, 0, VA_PICTURE_H264_INVALID, 0, 0)
407}
408
409fn build_h264_pic(surface_id: u32, meta: FrameMeta) -> PictureH264 {
410	PictureH264::new(
411		surface_id,
412		meta.frame_num,
413		VA_PICTURE_H264_SHORT_TERM_REFERENCE,
414		meta.poc as i32,
415		meta.poc as i32,
416	)
417}
418
419/// Ported from cros-codecs `build_enc_seq_param` (BSD-3-Clause).
420fn build_enc_seq_param(sps: &Sps, bits_per_second: u32, intra_period: u32, ip_period: u32) -> BufferType {
421	let seq_fields = H264EncSeqFields::new(
422		sps.chroma_format_idc as u32,
423		sps.frame_mbs_only_flag as u32,
424		sps.mb_adaptive_frame_field_flag as u32,
425		sps.seq_scaling_matrix_present_flag as u32,
426		sps.direct_8x8_inference_flag as u32,
427		sps.log2_max_frame_num_minus4 as u32,
428		sps.pic_order_cnt_type as u32,
429		sps.log2_max_pic_order_cnt_lsb_minus4 as u32,
430		sps.delta_pic_order_always_zero_flag as u32,
431	);
432
433	let frame_crop = if sps.frame_cropping_flag {
434		Some(H264EncFrameCropOffsets::new(
435			sps.frame_crop_left_offset,
436			sps.frame_crop_right_offset,
437			sps.frame_crop_top_offset,
438			sps.frame_crop_bottom_offset,
439		))
440	} else {
441		None
442	};
443
444	let vui_fields = if sps.vui_parameters_present_flag {
445		Some(H264VuiFields::new(
446			sps.vui_parameters.aspect_ratio_idc as u32,
447			sps.vui_parameters.timing_info_present_flag as u32,
448			sps.vui_parameters.bitstream_restriction_flag as u32,
449			sps.vui_parameters.log2_max_mv_length_horizontal,
450			sps.vui_parameters.log2_max_mv_length_vertical,
451			sps.vui_parameters.fixed_frame_rate_flag as u32,
452			sps.vui_parameters.low_delay_hrd_flag as u32,
453			sps.vui_parameters.motion_vectors_over_pic_boundaries_flag as u32,
454		))
455	} else {
456		None
457	};
458
459	let mut offset_for_ref_frame = [0i32; 256];
460	offset_for_ref_frame[..255].copy_from_slice(&sps.offset_for_ref_frame[..]);
461
462	BufferType::EncSequenceParameter(EncSequenceParameter::H264(EncSequenceParameterBufferH264::new(
463		sps.seq_parameter_set_id,
464		sps.level_idc as u8,
465		intra_period,
466		intra_period,
467		ip_period,
468		bits_per_second,
469		sps.max_num_ref_frames as u32,
470		sps.pic_width_in_mbs_minus1 + 1,
471		sps.pic_height_in_map_units_minus1 + 1,
472		&seq_fields,
473		sps.bit_depth_luma_minus8,
474		sps.bit_depth_chroma_minus8,
475		sps.num_ref_frames_in_pic_order_cnt_cycle,
476		sps.offset_for_non_ref_pic,
477		sps.offset_for_top_to_bottom_field,
478		offset_for_ref_frame,
479		frame_crop,
480		vui_fields,
481		sps.vui_parameters.aspect_ratio_idc,
482		sps.vui_parameters.sar_width as u32,
483		sps.vui_parameters.sar_height as u32,
484		sps.vui_parameters.num_units_in_tick,
485		sps.vui_parameters.time_scale,
486	)))
487}
488
489/// Ported from cros-codecs `build_enc_pic_param` (BSD-3-Clause).
490fn build_enc_pic_param(
491	pps: &Pps,
492	coded: &EncCodedBuffer,
493	recon_id: u32,
494	meta: FrameMeta,
495	is_idr: bool,
496	reference: Option<(u32, FrameMeta)>,
497) -> BufferType {
498	let pic_fields = H264EncPicFields::new(
499		is_idr as u32,
500		1, // reference_pic_flag: this frame is a short-term reference
501		pps.entropy_coding_mode_flag as u32,
502		pps.weighted_pred_flag as u32,
503		pps.weighted_bipred_idc as u32,
504		pps.constrained_intra_pred_flag as u32,
505		pps.transform_8x8_mode_flag as u32,
506		pps.deblocking_filter_control_present_flag as u32,
507		pps.redundant_pic_cnt_present_flag as u32,
508		0,
509		pps.pic_scaling_matrix_present_flag as u32,
510	);
511
512	let curr_pic = build_h264_pic(recon_id, meta);
513	let mut reference_frames: [PictureH264; 16] = std::array::from_fn(|_| build_invalid_pic());
514	if let Some((id, m)) = reference {
515		reference_frames[0] = build_h264_pic(id, m);
516	}
517
518	BufferType::EncPictureParameter(EncPictureParameter::H264(EncPictureParameterBufferH264::new(
519		curr_pic,
520		reference_frames,
521		coded.id(),
522		pps.pic_parameter_set_id,
523		pps.seq_parameter_set_id,
524		0,
525		meta.frame_num as u16,
526		(pps.pic_init_qp_minus26 + 26) as u8,
527		pps.num_ref_idx_l0_default_active_minus1,
528		pps.num_ref_idx_l1_default_active_minus1,
529		pps.chroma_qp_index_offset,
530		pps.second_chroma_qp_index_offset,
531		&pic_fields,
532	)))
533}
534
535/// Ported from cros-codecs `build_enc_slice_param` (BSD-3-Clause), simplified for
536/// IPPP (no weighted prediction, single reference in list 0).
537fn build_enc_slice_param(
538	pps: &Pps,
539	header: &SliceHeader,
540	reference: Option<(u32, FrameMeta)>,
541	num_macroblocks: u32,
542) -> BufferType {
543	let mut ref_pic_list_0: [PictureH264; 32] = std::array::from_fn(|_| build_invalid_pic());
544	if let Some((id, m)) = reference {
545		ref_pic_list_0[0] = build_h264_pic(id, m);
546	}
547	let ref_pic_list_1: [PictureH264; 32] = std::array::from_fn(|_| build_invalid_pic());
548
549	let (num_ref_idx_l0_active_minus1, num_ref_idx_l1_active_minus1) = if header.num_ref_idx_active_override_flag {
550		(header.num_ref_idx_l0_active_minus1, header.num_ref_idx_l1_active_minus1)
551	} else {
552		(
553			pps.num_ref_idx_l0_default_active_minus1,
554			pps.num_ref_idx_l1_default_active_minus1,
555		)
556	};
557
558	BufferType::EncSliceParameter(EncSliceParameter::H264(EncSliceParameterBufferH264::new(
559		header.first_mb_in_slice,
560		num_macroblocks,
561		VA_INVALID_ID,
562		header.slice_type as u8,
563		pps.pic_parameter_set_id,
564		header.idr_pic_id,
565		header.pic_order_cnt_lsb,
566		header.delta_pic_order_cnt_bottom,
567		header.delta_pic_order_cnt,
568		header.direct_spatial_mv_pred_flag as u8,
569		header.num_ref_idx_active_override_flag as u8,
570		num_ref_idx_l0_active_minus1,
571		num_ref_idx_l1_active_minus1,
572		ref_pic_list_0,
573		ref_pic_list_1,
574		header.pred_weight_table.luma_log2_weight_denom,
575		header.pred_weight_table.chroma_log2_weight_denom,
576		0,
577		header.pred_weight_table.luma_weight_l0,
578		[0i16; 32],
579		0,
580		header.pred_weight_table.chroma_weight_l0,
581		[[0i16; 2]; 32],
582		0,
583		header.pred_weight_table.luma_weight_l1,
584		[0i16; 32],
585		0,
586		header.pred_weight_table.chroma_weight_l1,
587		[[0i16; 2]; 32],
588		header.cabac_init_idc,
589		header.slice_qp_delta,
590		header.disable_deblocking_filter_idc,
591		header.slice_alpha_c0_offset_div2,
592		header.slice_beta_offset_div2,
593	)))
594}
595
596/// A packed-header parameter + data buffer pair for a synthesized NAL.
597fn packed_header(kind: EncPackedHeaderType, data: &[u8]) -> (BufferType, BufferType) {
598	let param =
599		BufferType::EncPackedHeaderParameter(EncPackedHeaderParameter::new(kind, (data.len() * 8) as u32, true));
600	let payload = BufferType::EncPackedHeaderData(data.to_vec());
601	(param, payload)
602}