1use 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#[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;
52const LIMIT: u32 = 2048;
54
55#[derive(Clone, Debug)]
57pub struct Config {
58 pub width: u32,
59 pub height: u32,
60 pub framerate: u32,
61 pub bitrate: u32,
63 pub gop: u32,
65 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#[derive(Copy, Clone)]
84struct FrameMeta {
85 poc: u16,
86 frame_num: u32,
87}
88
89pub struct Encoder {
91 config: Config,
92 sps: Rc<Sps>,
93 pps: Rc<Pps>,
94 width_mbs: u16,
95 height_mbs: u16,
96
97 input: Option<Surface<()>>,
100 recon: Vec<Surface<()>>,
101 reference: Option<(usize, FrameMeta)>,
103
104 counter: u32,
105
106 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 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 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 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 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, self.config.framerate.max(1) * 1000, 26, MIN_QP as u32,
242 0, RcFlags::default(),
244 0, MAX_QP as u32,
246 0, 0, );
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 let picture = picture
260 .sync()
261 .map_err(|(e, _)| anyhow::anyhow!("picture sync: {e:?}"))?;
262
263 self.input = Some(
265 picture
266 .take_surface()
267 .map_err(|_| anyhow::anyhow!("reclaim input surface (still referenced)"))?,
268 );
269
270 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
308fn 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
323fn 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 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 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
368fn 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
419fn 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
489fn 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, 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
535fn 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
596fn 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}