1use std::path::PathBuf;
35use std::rc::Rc;
36use std::sync::Arc;
37
38use crate::codec::h264::parser::SpsBuilder;
39use crate::codec::h264::parser::{Level, Pps, PpsBuilder, Profile, SliceHeader, SliceHeaderBuilder, SliceType, Sps};
40use crate::codec::h264::synthesizer::Synthesizer;
41use crate::dmabuf::DmaBuf;
42use crate::vpp::Processor;
43use crate::{
44 BufferType, Color, Config as VaConfig, Context, Display, EncCodedBuffer, EncMiscParameter,
45 EncMiscParameterFrameRate, EncMiscParameterRateControl, EncPackedHeaderParameter, EncPackedHeaderType,
46 EncPictureParameter, EncPictureParameterBufferH264, EncSequenceParameter, EncSequenceParameterBufferH264,
47 EncSliceParameter, EncSliceParameterBufferH264, H264EncFrameCropOffsets, H264EncPicFields, H264EncSeqFields,
48 H264VuiFields, Image, MappedCodedBuffer, Picture, PictureH264, RcFlags, Surface, SurfaceMemoryDescriptor,
49 UsageHint, VAConfigAttrib, VAConfigAttribType, VAEntrypoint, VAProfile, VA_ATTRIB_NOT_SUPPORTED,
50 VA_ENC_PACKED_HEADER_PICTURE, VA_ENC_PACKED_HEADER_SEQUENCE, VA_FOURCC_ARGB, VA_FOURCC_BGRA, VA_FOURCC_BGRX,
51 VA_FOURCC_NV12, VA_FOURCC_RGBA, VA_FOURCC_RGBX, VA_FOURCC_XRGB, VA_INVALID_ID, VA_PICTURE_H264_INVALID,
52 VA_PICTURE_H264_SHORT_TERM_REFERENCE, VA_RC_CBR, VA_RT_FORMAT_YUV420,
53};
54
55#[derive(Copy, Clone, PartialEq, Eq, Debug)]
58pub enum IsReference {
59 No,
60 ShortTerm,
61 LongTerm,
62}
63
64const MIN_QP: u8 = 18;
71const MAX_QP: u8 = 36;
72pub const RATE_CONTROL_WINDOW_MS: u32 = 1_500;
77const LIMIT: u32 = 2048;
79
80#[derive(Clone, Debug)]
82pub struct Config {
83 pub width: u32,
84 pub height: u32,
85 pub framerate: u32,
86 pub bitrate: u32,
88 pub gop: u32,
90 pub device: Option<PathBuf>,
95 pub color: Color,
100}
101
102impl Config {
103 pub fn new(width: u32, height: u32, framerate: u32, bitrate: u32, gop: u32) -> Self {
104 Self {
105 width,
106 height,
107 framerate,
108 bitrate,
109 gop,
110 device: None,
111 color: Color::infer(height),
112 }
113 }
114}
115
116#[derive(Copy, Clone)]
118struct FrameMeta {
119 poc: u32,
120 frame_num: u32,
121}
122
123pub struct Encoder {
125 config: Config,
126 sps: Rc<Sps>,
127 pps: Rc<Pps>,
128 width_mbs: u16,
129 height_mbs: u16,
130
131 input: Option<Surface<()>>,
134 recon: Vec<Surface<()>>,
135 reference: Option<(usize, FrameMeta)>,
137
138 counter: u32,
139
140 processor: Option<Processor>,
143 reset_rate_control: bool,
146
147 coded: EncCodedBuffer,
149 coded_size: usize,
150 context: Rc<Context>,
151 _va_config: VaConfig,
152 display: Arc<Display>,
153}
154
155impl Encoder {
156 pub fn new(mut config: Config) -> anyhow::Result<Self> {
164 let (device, display) = match config.device.take() {
165 Some(device) => {
166 let display = Display::open_drm_display(&device)
167 .map_err(|e| anyhow::anyhow!("open DRM display {device:?}: {e:?}"))?;
168 (device, display)
169 }
170 None => {
171 crate::display::open_first(probe).map_err(|e| e.context("find a render node that encodes H.264"))?
172 }
173 };
174
175 let entrypoint = encode_entrypoint(&display)?;
176 let mut attrs = vec![
177 VAConfigAttrib {
178 type_: VAConfigAttribType::VAConfigAttribRTFormat,
179 value: VA_RT_FORMAT_YUV420,
180 },
181 VAConfigAttrib {
182 type_: VAConfigAttribType::VAConfigAttribRateControl,
183 value: VA_RC_CBR,
184 },
185 ];
186 let packed = packed_headers(&display, entrypoint);
190 if packed != 0 {
191 attrs.push(VAConfigAttrib {
192 type_: VAConfigAttribType::VAConfigAttribEncPackedHeaders,
193 value: packed,
194 });
195 } else {
196 log::warn!("the driver takes no packed SPS/PPS; the stream carries the driver's own headers");
197 }
198 let va_config = display
199 .create_config(attrs, VAProfile::VAProfileH264Main, entrypoint)
200 .map_err(|e| anyhow::anyhow!("create VA config: {e:?}"))?;
201
202 let context = display
203 .create_context::<()>(&va_config, config.width, config.height, None, true)
204 .map_err(|e| anyhow::anyhow!("create VA context: {e:?}"))?;
205
206 let coded_size = coded_size(config.bitrate);
207 let coded = context
208 .create_enc_coded(coded_size)
209 .map_err(|e| anyhow::anyhow!("create coded buffer: {e:?}"))?;
210
211 let input = make_surface(&display, &config)?;
212 let recon = vec![make_surface(&display, &config)?, make_surface(&display, &config)?];
213
214 let (sps, pps) = build_sps_pps(&config);
215 let width_mbs = sps.pic_width_in_mbs_minus1 + 1;
216 let height_mbs = sps.pic_height_in_map_units_minus1 + 1;
217
218 log::info!(
219 "opened VA-API H.264 encoder on {:?}: {}x{} @ {}fps, {} bps, low power: {}",
220 device,
221 config.width,
222 config.height,
223 config.framerate,
224 config.bitrate,
225 entrypoint == VAEntrypoint::VAEntrypointEncSliceLP,
226 );
227 config.device = Some(device);
228 Ok(Self {
229 config,
230 sps,
231 pps,
232 width_mbs,
233 height_mbs,
234 input: Some(input),
235 recon,
236 reference: None,
237 counter: 0,
238 processor: None,
239 reset_rate_control: false,
240 coded,
241 coded_size,
242 context,
243 _va_config: va_config,
244 display,
245 })
246 }
247
248 pub fn encode_nv12(&mut self, nv12: &[u8], keyframe: bool) -> anyhow::Result<Vec<u8>> {
256 let (width, height) = (self.config.width as usize, self.config.height as usize);
257 let expected = width * height + width * height.div_ceil(2);
258 if nv12.len() < expected {
259 anyhow::bail!("NV12 frame is {} bytes, {width}x{height} needs {expected}", nv12.len());
260 }
261 let input = self.input.take().expect("input surface present");
262 let result = upload_nv12(&self.display, &input, self.config.width, self.config.height, nv12)
263 .and_then(|()| self.encode_surface(&input, keyframe));
264 self.input = Some(input);
265 result
266 }
267
268 pub fn encode_dmabuf(&mut self, buffer: DmaBuf, keyframe: bool) -> anyhow::Result<Vec<u8>> {
295 let fourcc = buffer
296 .fourcc()
297 .ok_or_else(|| anyhow::anyhow!("no VA-API format for DRM format {:#010x}", buffer.drm_format))?;
298 let color = self.config.color;
299 let in_place = fourcc == VA_FOURCC_NV12
300 && (buffer.width, buffer.height) == (self.config.width, self.config.height)
301 && buffer.width % 16 == 0
302 && buffer.height % 16 == 0
303 && buffer.color.is_none_or(|own| own == color);
304 if in_place {
305 let source = buffer.import(&self.display, Some(UsageHint::USAGE_HINT_ENCODER))?;
306 return self.encode_surface(&source, keyframe);
307 }
308
309 let input_color = match is_rgb(fourcc) {
310 true => None,
311 false => Some(buffer.color.unwrap_or(color)),
312 };
313 let source = buffer.import(&self.display, Some(UsageHint::USAGE_HINT_VPP_READ))?;
314 let input = self.input.take().expect("input surface present");
315 let result = self
316 .processor()
317 .and_then(|processor| processor.blit(&source, &input, input_color, Some(color)))
318 .and_then(|()| self.encode_surface(&input, keyframe));
319 self.input = Some(input);
320 result
321 }
322
323 pub fn set_bitrate(&mut self, bitrate: u32) -> anyhow::Result<()> {
338 if bitrate == 0 {
339 anyhow::bail!("a bitrate of zero");
340 }
341 let size = coded_size(bitrate);
342 if size > self.coded_size {
343 self.coded = self
346 .context
347 .create_enc_coded(size)
348 .map_err(|e| anyhow::anyhow!("create coded buffer: {e:?}"))?;
349 self.coded_size = size;
350 }
351 if bitrate != self.config.bitrate {
352 self.reset_rate_control = true;
353 }
354 self.config.bitrate = bitrate;
355 Ok(())
356 }
357
358 pub fn config(&self) -> &Config {
360 &self.config
361 }
362
363 fn processor(&mut self) -> anyhow::Result<&Processor> {
365 if self.processor.is_none() {
366 self.processor = Some(Processor::with_display(Arc::clone(&self.display))?);
367 }
368 Ok(self.processor.as_ref().expect("opened above"))
369 }
370
371 fn encode_surface<D: SurfaceMemoryDescriptor>(
373 &mut self,
374 source: &Surface<D>,
375 keyframe: bool,
376 ) -> anyhow::Result<Vec<u8>> {
377 let is_idr = keyframe || self.counter % self.config.gop == 0;
378 if is_idr {
379 self.counter = 0;
381 self.reference = None;
382 }
383
384 let meta = FrameMeta {
388 poc: self.counter.wrapping_mul(2),
389 frame_num: self.counter % LIMIT,
390 };
391 let recon_idx = (self.counter as usize) % self.recon.len();
392
393 let slice_type = if is_idr { SliceType::I } else { SliceType::P };
394 let header = SliceHeaderBuilder::new(&self.pps)
395 .slice_type(slice_type)
396 .first_mb_in_slice(0)
397 .frame_num(meta.frame_num as u16)
398 .pic_order_cnt_lsb((meta.poc % (LIMIT * 2)) as u16)
399 .build();
400
401 let num_macroblocks = self.width_mbs as u32 * self.height_mbs as u32;
402 let bits_per_second = self.config.bitrate;
403
404 let reference = match (is_idr, self.reference) {
406 (false, Some((ref_idx, ref_meta))) => Some((self.recon[ref_idx].id(), ref_meta)),
407 _ => None,
408 };
409
410 let seq_param = build_enc_seq_param(&self.sps, bits_per_second, LIMIT, 0);
411 let pic_param = build_enc_pic_param(
412 &self.pps,
413 &self.coded,
414 self.recon[recon_idx].id(),
415 meta,
416 is_idr,
417 reference,
418 );
419 let slice_param = build_enc_slice_param(&self.pps, &header, reference, num_macroblocks);
420
421 let mut picture = Picture::new(meta.frame_num as u64, Rc::clone(&self.context), source);
422
423 picture.add_buffer(self.create(seq_param)?);
426 picture.add_buffer(self.create(pic_param)?);
427 picture.add_buffer(self.create(slice_param)?);
428
429 if is_idr {
430 let (sps_param, sps_data) = packed_header(EncPackedHeaderType::Sequence, &self.packed_sps()?);
431 let (pps_param, pps_data) = packed_header(EncPackedHeaderType::Picture, &self.packed_pps()?);
432 picture.add_buffer(self.create(sps_param)?);
433 picture.add_buffer(self.create(sps_data)?);
434 picture.add_buffer(self.create(pps_param)?);
435 picture.add_buffer(self.create(pps_data)?);
436 }
437
438 let rc = EncMiscParameterRateControl::new(
439 bits_per_second,
440 100, RATE_CONTROL_WINDOW_MS, u32::from((MIN_QP + MAX_QP) / 2),
443 MIN_QP as u32,
444 0, RcFlags::new(u32::from(self.reset_rate_control), 1, 0, 0, 0, 0, 0, 0, 0),
448 0, MAX_QP as u32,
450 0, 0, );
453 picture.add_buffer(self.create(BufferType::EncMiscParameter(EncMiscParameter::RateControl(rc)))?);
454
455 let framerate = EncMiscParameterFrameRate::new(self.config.framerate, 0);
456 picture.add_buffer(self.create(BufferType::EncMiscParameter(EncMiscParameter::FrameRate(framerate)))?);
457
458 let picture = picture.begin().map_err(|e| anyhow::anyhow!("picture begin: {e:?}"))?;
459 let picture = picture.render().map_err(|e| anyhow::anyhow!("picture render: {e:?}"))?;
460 let picture = picture.end().map_err(|e| anyhow::anyhow!("picture end: {e:?}"))?;
461 picture
464 .sync()
465 .map_err(|(e, _)| anyhow::anyhow!("picture sync: {e:?}"))?;
466
467 let bitstream = self.read_coded()?;
469
470 self.reference = Some((recon_idx, meta));
471 self.counter += 1;
472 self.reset_rate_control = false;
473 Ok(bitstream)
474 }
475
476 fn create(&self, buffer: BufferType) -> anyhow::Result<crate::Buffer> {
477 self.context
478 .create_buffer(buffer)
479 .map_err(|e| anyhow::anyhow!("create VA buffer: {e:?}"))
480 }
481
482 fn packed_sps(&self) -> anyhow::Result<Vec<u8>> {
483 let mut buf = Vec::new();
484 Synthesizer::<'_, Sps, _>::synthesize(3, &self.sps, &mut buf, true)
485 .map_err(|e| anyhow::anyhow!("synthesize SPS: {e:?}"))?;
486 Ok(buf)
487 }
488
489 fn packed_pps(&self) -> anyhow::Result<Vec<u8>> {
490 let mut buf = Vec::new();
491 Synthesizer::<'_, Pps, _>::synthesize(3, &self.pps, &mut buf, true)
492 .map_err(|e| anyhow::anyhow!("synthesize PPS: {e:?}"))?;
493 Ok(buf)
494 }
495
496 fn read_coded(&self) -> anyhow::Result<Vec<u8>> {
497 let mapped = MappedCodedBuffer::new(&self.coded).map_err(|e| anyhow::anyhow!("map coded buffer: {e:?}"))?;
498 let mut out = Vec::new();
499 for segment in mapped.iter() {
500 out.extend_from_slice(segment.buf);
501 }
502 Ok(out)
503 }
504}
505
506pub fn probe(display: &Display) -> anyhow::Result<()> {
513 encode_entrypoint(display).map(drop)
514}
515
516fn encode_entrypoint(display: &Display) -> anyhow::Result<VAEntrypoint::Type> {
520 let entrypoints = display
521 .query_config_entrypoints(VAProfile::VAProfileH264Main)
522 .map_err(|e| anyhow::anyhow!("query H.264 Main entrypoints: {e:?}"))?;
523 pick_entrypoint(&entrypoints).ok_or_else(|| anyhow::anyhow!("the device has no H.264 Main encode entrypoint"))
524}
525
526fn pick_entrypoint(offered: &[VAEntrypoint::Type]) -> Option<VAEntrypoint::Type> {
528 [VAEntrypoint::VAEntrypointEncSlice, VAEntrypoint::VAEntrypointEncSliceLP]
529 .into_iter()
530 .find(|entrypoint| offered.contains(entrypoint))
531}
532
533fn packed_headers(display: &Display, entrypoint: VAEntrypoint::Type) -> u32 {
537 let mut attrs = [VAConfigAttrib {
538 type_: VAConfigAttribType::VAConfigAttribEncPackedHeaders,
539 value: 0,
540 }];
541 if display
542 .get_config_attributes(VAProfile::VAProfileH264Main, entrypoint, &mut attrs)
543 .is_err()
544 || attrs[0].value & VA_ATTRIB_NOT_SUPPORTED != 0
545 {
546 return 0;
547 }
548 attrs[0].value & (VA_ENC_PACKED_HEADER_SEQUENCE | VA_ENC_PACKED_HEADER_PICTURE)
549}
550
551fn level(width: u32, height: u32, framerate: u32) -> Level {
560 const LEVELS: [(Level, u64, u64); 8] = [
562 (Level::L4, 245_760, 8_192),
563 (Level::L4_2, 522_240, 8_704),
564 (Level::L5, 589_824, 22_080),
565 (Level::L5_1, 983_040, 36_864),
566 (Level::L5_2, 2_073_600, 36_864),
567 (Level::L6, 4_177_920, 139_264),
568 (Level::L6_1, 8_355_840, 139_264),
569 (Level::L6_2, 16_711_680, 139_264),
570 ];
571 let frame = u64::from(width.div_ceil(16)) * u64::from(height.div_ceil(16));
572 let rate = frame * u64::from(framerate.max(1));
573 LEVELS
574 .into_iter()
575 .find(|&(_, max_rate, max_frame)| frame <= max_frame && rate <= max_rate)
576 .map(|(level, _, _)| level)
577 .unwrap_or(Level::L6_2)
578}
579
580fn coded_size(bitrate: u32) -> usize {
582 (bitrate as usize / 4).max(1_500_000)
583}
584
585fn is_rgb(fourcc: u32) -> bool {
587 matches!(
588 fourcc,
589 VA_FOURCC_BGRX | VA_FOURCC_BGRA | VA_FOURCC_RGBX | VA_FOURCC_RGBA | VA_FOURCC_XRGB | VA_FOURCC_ARGB
590 )
591}
592
593fn make_surface(display: &Arc<Display>, config: &Config) -> anyhow::Result<Surface<()>> {
595 let mut surfaces = display
596 .create_surfaces::<()>(
597 VA_RT_FORMAT_YUV420,
598 Some(VA_FOURCC_NV12),
599 config.width,
600 config.height,
601 Some(UsageHint::USAGE_HINT_ENCODER),
602 vec![()],
603 )
604 .map_err(|e| anyhow::anyhow!("create surface: {e:?}"))?;
605 surfaces.pop().ok_or_else(|| anyhow::anyhow!("no surface created"))
606}
607
608fn upload_nv12(
611 display: &Arc<Display>,
612 surface: &Surface<()>,
613 width: u32,
614 height: u32,
615 data: &[u8],
616) -> anyhow::Result<()> {
617 let formats = display
618 .query_image_formats()
619 .map_err(|e| anyhow::anyhow!("query image formats: {e:?}"))?;
620 let format = formats
621 .into_iter()
622 .find(|f| f.fourcc == VA_FOURCC_NV12)
623 .ok_or_else(|| anyhow::anyhow!("driver has no NV12 image format"))?;
624
625 let mut image = Image::create_from(surface, format, surface.size(), surface.size())
626 .map_err(|e| anyhow::anyhow!("create image: {e:?}"))?;
627 let va_image = *image.image();
628 let dest: &mut [u8] = image.as_mut();
629 let (w, h) = (width as usize, height as usize);
630
631 let mut src = data;
633 let mut dst = &mut dest[va_image.offsets[0] as usize..];
634 for _ in 0..h {
635 dst[..w].copy_from_slice(&src[..w]);
636 dst = &mut dst[va_image.pitches[0] as usize..];
637 src = &src[w..];
638 }
639 let mut src = &data[w * h..];
641 let mut dst = &mut dest[va_image.offsets[1] as usize..];
642 for _ in 0..h / 2 {
643 dst[..w].copy_from_slice(&src[..w]);
644 dst = &mut dst[va_image.pitches[1] as usize..];
645 src = &src[w..];
646 }
647
648 drop(image);
649 surface.sync().map_err(|e| anyhow::anyhow!("surface sync: {e:?}"))?;
650 Ok(())
651}
652
653fn build_sps_pps(config: &Config) -> (Rc<Sps>, Rc<Pps>) {
656 let level = level(config.width, config.height, config.framerate);
657 let (primaries, transfer, matrix) = config.color.vui();
658 let sps = SpsBuilder::new()
659 .seq_parameter_set_id(0)
660 .profile_idc(Profile::Main)
661 .chroma_format_idc(1)
662 .level_idc(level)
663 .max_frame_num(LIMIT)
664 .pic_order_cnt_type(0)
665 .max_pic_order_cnt_lsb(LIMIT * 2)
666 .max_num_ref_frames(1)
667 .frame_mbs_only_flag(true)
668 .direct_8x8_inference_flag(level >= Level::L3)
669 .resolution(config.width, config.height)
670 .bit_depth_luma(8)
671 .bit_depth_chroma(8)
672 .aspect_ratio(1, 1)
673 .timing_info(1, config.framerate * 2, false)
674 .video_signal_type(config.color.full_range, primaries, transfer, matrix)
675 .max_num_reorder_frames(0)
676 .max_dec_frame_buffering(1)
677 .build();
678
679 let pps = PpsBuilder::new(Rc::clone(&sps))
680 .pic_parameter_set_id(0)
681 .pic_init_qp(26)
682 .entropy_coding_mode_flag(true)
683 .transform_8x8_mode_flag(false)
684 .deblocking_filter_control_present_flag(true)
685 .num_ref_idx_l0_default_active(1)
686 .num_ref_idx_l1_default_active_minus1(0)
687 .build();
688
689 (sps, pps)
690}
691
692fn build_invalid_pic() -> PictureH264 {
693 PictureH264::new(VA_INVALID_ID, 0, VA_PICTURE_H264_INVALID, 0, 0)
694}
695
696fn build_h264_pic(surface_id: u32, meta: FrameMeta) -> PictureH264 {
697 PictureH264::new(
698 surface_id,
699 meta.frame_num,
700 VA_PICTURE_H264_SHORT_TERM_REFERENCE,
701 meta.poc as i32,
702 meta.poc as i32,
703 )
704}
705
706fn build_enc_seq_param(sps: &Sps, bits_per_second: u32, intra_period: u32, ip_period: u32) -> BufferType {
708 let seq_fields = H264EncSeqFields::new(
709 sps.chroma_format_idc as u32,
710 sps.frame_mbs_only_flag as u32,
711 sps.mb_adaptive_frame_field_flag as u32,
712 sps.seq_scaling_matrix_present_flag as u32,
713 sps.direct_8x8_inference_flag as u32,
714 sps.log2_max_frame_num_minus4 as u32,
715 sps.pic_order_cnt_type as u32,
716 sps.log2_max_pic_order_cnt_lsb_minus4 as u32,
717 sps.delta_pic_order_always_zero_flag as u32,
718 );
719
720 let frame_crop = if sps.frame_cropping_flag {
721 Some(H264EncFrameCropOffsets::new(
722 sps.frame_crop_left_offset,
723 sps.frame_crop_right_offset,
724 sps.frame_crop_top_offset,
725 sps.frame_crop_bottom_offset,
726 ))
727 } else {
728 None
729 };
730
731 let vui_fields = if sps.vui_parameters_present_flag {
732 Some(H264VuiFields::new(
733 sps.vui_parameters.aspect_ratio_idc as u32,
734 sps.vui_parameters.timing_info_present_flag as u32,
735 sps.vui_parameters.bitstream_restriction_flag as u32,
736 sps.vui_parameters.log2_max_mv_length_horizontal,
737 sps.vui_parameters.log2_max_mv_length_vertical,
738 sps.vui_parameters.fixed_frame_rate_flag as u32,
739 sps.vui_parameters.low_delay_hrd_flag as u32,
740 sps.vui_parameters.motion_vectors_over_pic_boundaries_flag as u32,
741 ))
742 } else {
743 None
744 };
745
746 let mut offset_for_ref_frame = [0i32; 256];
747 offset_for_ref_frame[..255].copy_from_slice(&sps.offset_for_ref_frame[..]);
748
749 BufferType::EncSequenceParameter(EncSequenceParameter::H264(EncSequenceParameterBufferH264::new(
750 sps.seq_parameter_set_id,
751 sps.level_idc as u8,
752 intra_period,
753 intra_period,
754 ip_period,
755 bits_per_second,
756 sps.max_num_ref_frames as u32,
757 sps.pic_width_in_mbs_minus1 + 1,
758 sps.pic_height_in_map_units_minus1 + 1,
759 &seq_fields,
760 sps.bit_depth_luma_minus8,
761 sps.bit_depth_chroma_minus8,
762 sps.num_ref_frames_in_pic_order_cnt_cycle,
763 sps.offset_for_non_ref_pic,
764 sps.offset_for_top_to_bottom_field,
765 offset_for_ref_frame,
766 frame_crop,
767 vui_fields,
768 sps.vui_parameters.aspect_ratio_idc,
769 sps.vui_parameters.sar_width as u32,
770 sps.vui_parameters.sar_height as u32,
771 sps.vui_parameters.num_units_in_tick,
772 sps.vui_parameters.time_scale,
773 )))
774}
775
776fn build_enc_pic_param(
778 pps: &Pps,
779 coded: &EncCodedBuffer,
780 recon_id: u32,
781 meta: FrameMeta,
782 is_idr: bool,
783 reference: Option<(u32, FrameMeta)>,
784) -> BufferType {
785 let pic_fields = H264EncPicFields::new(
786 is_idr as u32,
787 1, pps.entropy_coding_mode_flag as u32,
789 pps.weighted_pred_flag as u32,
790 pps.weighted_bipred_idc as u32,
791 pps.constrained_intra_pred_flag as u32,
792 pps.transform_8x8_mode_flag as u32,
793 pps.deblocking_filter_control_present_flag as u32,
794 pps.redundant_pic_cnt_present_flag as u32,
795 0,
796 pps.pic_scaling_matrix_present_flag as u32,
797 );
798
799 let curr_pic = build_h264_pic(recon_id, meta);
800 let mut reference_frames: [PictureH264; 16] = std::array::from_fn(|_| build_invalid_pic());
801 if let Some((id, m)) = reference {
802 reference_frames[0] = build_h264_pic(id, m);
803 }
804
805 BufferType::EncPictureParameter(EncPictureParameter::H264(EncPictureParameterBufferH264::new(
806 curr_pic,
807 reference_frames,
808 coded.id(),
809 pps.pic_parameter_set_id,
810 pps.seq_parameter_set_id,
811 0,
812 meta.frame_num as u16,
813 (pps.pic_init_qp_minus26 + 26) as u8,
814 pps.num_ref_idx_l0_default_active_minus1,
815 pps.num_ref_idx_l1_default_active_minus1,
816 pps.chroma_qp_index_offset,
817 pps.second_chroma_qp_index_offset,
818 &pic_fields,
819 )))
820}
821
822fn build_enc_slice_param(
825 pps: &Pps,
826 header: &SliceHeader,
827 reference: Option<(u32, FrameMeta)>,
828 num_macroblocks: u32,
829) -> BufferType {
830 let mut ref_pic_list_0: [PictureH264; 32] = std::array::from_fn(|_| build_invalid_pic());
831 if let Some((id, m)) = reference {
832 ref_pic_list_0[0] = build_h264_pic(id, m);
833 }
834 let ref_pic_list_1: [PictureH264; 32] = std::array::from_fn(|_| build_invalid_pic());
835
836 let (num_ref_idx_l0_active_minus1, num_ref_idx_l1_active_minus1) = if header.num_ref_idx_active_override_flag {
837 (header.num_ref_idx_l0_active_minus1, header.num_ref_idx_l1_active_minus1)
838 } else {
839 (
840 pps.num_ref_idx_l0_default_active_minus1,
841 pps.num_ref_idx_l1_default_active_minus1,
842 )
843 };
844
845 BufferType::EncSliceParameter(EncSliceParameter::H264(EncSliceParameterBufferH264::new(
846 header.first_mb_in_slice,
847 num_macroblocks,
848 VA_INVALID_ID,
849 header.slice_type as u8,
850 pps.pic_parameter_set_id,
851 header.idr_pic_id,
852 header.pic_order_cnt_lsb,
853 header.delta_pic_order_cnt_bottom,
854 header.delta_pic_order_cnt,
855 header.direct_spatial_mv_pred_flag as u8,
856 header.num_ref_idx_active_override_flag as u8,
857 num_ref_idx_l0_active_minus1,
858 num_ref_idx_l1_active_minus1,
859 ref_pic_list_0,
860 ref_pic_list_1,
861 header.pred_weight_table.luma_log2_weight_denom,
862 header.pred_weight_table.chroma_log2_weight_denom,
863 0,
864 header.pred_weight_table.luma_weight_l0,
865 [0i16; 32],
866 0,
867 header.pred_weight_table.chroma_weight_l0,
868 [[0i16; 2]; 32],
869 0,
870 header.pred_weight_table.luma_weight_l1,
871 [0i16; 32],
872 0,
873 header.pred_weight_table.chroma_weight_l1,
874 [[0i16; 2]; 32],
875 header.cabac_init_idc,
876 header.slice_qp_delta,
877 header.disable_deblocking_filter_idc,
878 header.slice_alpha_c0_offset_div2,
879 header.slice_beta_offset_div2,
880 )))
881}
882
883fn packed_header(kind: EncPackedHeaderType, data: &[u8]) -> (BufferType, BufferType) {
885 let param =
886 BufferType::EncPackedHeaderParameter(EncPackedHeaderParameter::new(kind, (data.len() * 8) as u32, true));
887 let payload = BufferType::EncPackedHeaderData(data.to_vec());
888 (param, payload)
889}
890
891#[cfg(test)]
892mod tests {
893 use std::path::PathBuf;
894 use std::process::Command;
895 use std::sync::atomic::{AtomicU32, Ordering};
896
897 use super::*;
898 use crate::dmabuf::DmaBuf;
899 use crate::{VA_RT_FORMAT_RGB32, VA_RT_FORMAT_YUV420};
900
901 const WIDTH: u32 = 320;
902 const HEIGHT: u32 = 240;
903
904 fn config() -> Config {
905 Config::new(WIDTH, HEIGHT, 30, 2_000_000, 30)
906 }
907
908 fn encoder(config: Config) -> Option<Encoder> {
910 match Encoder::new(config) {
911 Ok(encoder) => Some(encoder),
912 Err(err) => {
913 eprintln!("skipping: no VA-API H.264 encoder: {err:#}");
914 None
915 }
916 }
917 }
918
919 fn nv12_frame(width: u32, height: u32, step: u32) -> Vec<u8> {
922 let (w, h) = (width as usize, height as usize);
923 let mut data = vec![128u8; w * h + w * h.div_ceil(2)];
924 for y in 0..h {
925 for x in 0..w {
926 data[y * w + x] = (32 + (x + y) * 160 / (w + h)) as u8;
927 }
928 }
929 let x0 = (step as usize * 8) % (w / 2);
930 for y in h / 4..h / 2 {
931 for x in x0..x0 + w / 4 {
932 data[y * w + x] = 220;
933 }
934 }
935 data
936 }
937
938 fn temp_path(label: &str) -> PathBuf {
943 static NEXT: AtomicU32 = AtomicU32::new(0);
944 let index = NEXT.fetch_add(1, Ordering::Relaxed);
945 std::env::temp_dir().join(format!("moq-vaapi-{label}-{}-{index}.264", std::process::id()))
946 }
947
948 fn ffmpeg_decode(stream: &[u8], name: &str) -> Option<Vec<u8>> {
951 let path = temp_path(name);
954 std::fs::write(&path, stream).expect("write the elementary stream");
955 let output = Command::new("ffmpeg")
956 .args(["-v", "error", "-i"])
957 .arg(&path)
958 .args(["-pix_fmt", "nv12", "-f", "rawvideo", "-"])
959 .output();
960 let _ = std::fs::remove_file(&path);
961 match output {
962 Ok(output) if output.status.success() => Some(output.stdout),
963 Ok(output) => panic!("ffmpeg refused the stream: {}", String::from_utf8_lossy(&output.stderr)),
964 Err(_) => {
965 eprintln!("skipping: no ffmpeg");
966 None
967 }
968 }
969 }
970
971 fn ffprobe_color(stream: &[u8]) -> Option<String> {
973 let path = temp_path("probe");
974 std::fs::write(&path, stream).expect("write the elementary stream");
975 let output = Command::new("ffprobe")
976 .args([
977 "-v",
978 "error",
979 "-show_entries",
980 "stream=color_range,color_space",
981 "-of",
982 "csv=p=0",
983 ])
984 .arg(&path)
985 .output()
986 .ok();
987 let _ = std::fs::remove_file(&path);
988 Some(String::from_utf8(output?.stdout).ok()?.trim().to_string())
989 }
990
991 fn luma_psnr(a: &[u8], b: &[u8], width: u32, height: u32) -> f64 {
993 let len = (width * height) as usize;
994 let mse = a[..len]
995 .iter()
996 .zip(&b[..len])
997 .map(|(&x, &y)| (x as f64 - y as f64).powi(2))
998 .sum::<f64>()
999 / len as f64;
1000 10.0 * (255.0f64.powi(2) / mse.max(1e-9)).log10()
1001 }
1002
1003 fn mean(
1005 plane: &[u8],
1006 stride: usize,
1007 (x0, y0, x1, y1): (usize, usize, usize, usize),
1008 step: usize,
1009 at: usize,
1010 ) -> f64 {
1011 let mut sum = 0.0;
1012 let mut count = 0.0;
1013 for y in y0..y1 {
1014 for x in x0..x1 {
1015 sum += plane[y * stride + x * step + at] as f64;
1016 count += 1.0;
1017 }
1018 }
1019 sum / count
1020 }
1021
1022 fn nal_types(unit: &[u8]) -> Vec<u8> {
1024 let mut types = Vec::new();
1025 let mut i = 0;
1026 while i + 3 < unit.len() {
1027 if unit[i..i + 3] == [0, 0, 1] {
1028 types.push(unit[i + 3] & 0x1f);
1029 i += 3;
1030 }
1031 i += 1;
1032 }
1033 types
1034 }
1035
1036 fn surface_with(
1038 display: &Arc<Display>,
1039 fourcc: u32,
1040 rt_format: u32,
1041 (width, height): (u32, u32),
1042 fill: impl FnOnce(&mut [u8], &crate::bindings::VAImage),
1043 ) -> Surface<()> {
1044 let surface = display
1045 .create_surfaces(
1046 rt_format,
1047 Some(fourcc),
1048 width,
1049 height,
1050 Some(UsageHint::USAGE_HINT_EXPORT),
1051 vec![()],
1052 )
1053 .expect("allocate a surface")
1054 .pop()
1055 .expect("one surface");
1056 let format = display
1057 .query_image_formats()
1058 .expect("query image formats")
1059 .into_iter()
1060 .find(|f| f.fourcc == fourcc)
1061 .expect("the driver has an image format for the surface format");
1062 let mut image =
1063 Image::create_from(&surface, format, (width, height), (width, height)).expect("create an image");
1064 let va_image = *image.image();
1065 fill(image.as_mut(), &va_image);
1066 drop(image);
1067 surface.sync().expect("sync the filled surface");
1068 surface
1069 }
1070
1071 fn nv12_dmabuf(display: &Arc<Display>, (width, height): (u32, u32), step: u32) -> DmaBuf {
1073 let surface = surface_with(display, VA_FOURCC_NV12, VA_RT_FORMAT_YUV420, (width, height), |_, _| {});
1074 upload_nv12(display, &surface, width, height, &nv12_frame(width, height, step)).expect("upload");
1075 DmaBuf::from_prime(surface.export_prime().expect("export the surface")).expect("one object")
1076 }
1077
1078 #[test]
1081 fn the_render_node_is_found_or_named() {
1082 let Some(encoder) = encoder(config()) else { return };
1083 let device = encoder.config().device.clone().expect("the picked node is recorded");
1084 let display = Display::open_drm_display(&device).expect("reopen the picked node");
1085 probe(&display).expect("the picked node encodes H.264");
1086
1087 let named = Config {
1088 device: Some(PathBuf::from("/dev/null")),
1089 ..config()
1090 };
1091 assert!(
1092 Encoder::new(named).is_err(),
1093 "a named node that is not a render node was swapped for one that is"
1094 );
1095 }
1096
1097 #[test]
1099 fn nv12_round_trips_through_a_software_decoder() {
1100 let Some(mut encoder) = encoder(config()) else { return };
1101 let mut stream = Vec::new();
1102 let mut frames = Vec::new();
1103 for step in 0..10 {
1104 let frame = nv12_frame(WIDTH, HEIGHT, step);
1105 stream.extend(encoder.encode_nv12(&frame, false).expect("encode"));
1106 frames.push(frame);
1107 }
1108 let Some(decoded) = ffmpeg_decode(&stream, "nv12") else {
1109 return;
1110 };
1111 let len = frames[0].len();
1112 assert_eq!(decoded.len(), len * frames.len(), "ffmpeg decoded a different count");
1113 for (index, frame) in frames.iter().enumerate() {
1114 let psnr = luma_psnr(frame, &decoded[index * len..], WIDTH, HEIGHT);
1115 assert!(psnr > 35.0, "picture {index} decoded at {psnr:.1} dB");
1116 }
1117 }
1118
1119 #[test]
1121 fn the_stream_is_labelled_with_its_color_space() {
1122 for (config, expected) in [
1123 (config(), "tv,smpte170m"),
1124 (
1125 Config {
1126 color: Color::BT709,
1127 ..config()
1128 },
1129 "tv,bt709",
1130 ),
1131 ] {
1132 let Some(mut encoder) = encoder(config) else { return };
1133 let unit = encoder
1134 .encode_nv12(&nv12_frame(WIDTH, HEIGHT, 0), true)
1135 .expect("encode");
1136 let Some(color) = ffprobe_color(&unit) else {
1137 eprintln!("skipping: no ffprobe");
1138 return;
1139 };
1140 assert_eq!(color, expected);
1141 }
1142 }
1143
1144 #[test]
1146 fn keyframes_follow_the_group_length() {
1147 let Some(mut encoder) = encoder(Config { gop: 10, ..config() }) else {
1148 return;
1149 };
1150 for step in 0..25 {
1151 let unit = encoder
1152 .encode_nv12(&nv12_frame(WIDTH, HEIGHT, step), false)
1153 .expect("encode");
1154 let types = nal_types(&unit);
1155 let idr = types.contains(&5);
1156 assert_eq!(idr, step % 10 == 0, "frame {step} has NAL types {types:?}");
1157 if idr {
1158 assert!(
1159 types.contains(&7) && types.contains(&8),
1160 "an IDR carries its SPS and PPS"
1161 );
1162 }
1163 }
1164 for step in 0..=10 {
1166 let unit = encoder
1167 .encode_nv12(&nv12_frame(WIDTH, HEIGHT, step), step == 0)
1168 .expect("encode");
1169 assert_eq!(
1170 nal_types(&unit).contains(&5),
1171 step % 10 == 0,
1172 "frame {step} after the forced keyframe"
1173 );
1174 }
1175 }
1176
1177 #[test]
1178 fn the_full_entrypoint_wins_and_low_power_is_the_fallback() {
1179 use VAEntrypoint::{
1180 VAEntrypointEncSlice as Full, VAEntrypointEncSliceLP as LowPower, VAEntrypointVLD as Decode,
1181 };
1182 assert_eq!(pick_entrypoint(&[Decode, LowPower, Full]), Some(Full));
1183 assert_eq!(pick_entrypoint(&[Decode, LowPower]), Some(LowPower));
1184 assert_eq!(pick_entrypoint(&[Decode]), None);
1185 }
1186
1187 #[test]
1190 fn a_refused_import_leaves_the_encoder_usable() {
1191 let Some(mut encoder) = encoder(config()) else { return };
1192 let Some(display) = Display::open() else {
1193 eprintln!("skipping: no VA-API display");
1194 return;
1195 };
1196 let unmapped = DmaBuf {
1198 drm_format: u32::from_le_bytes(*b"ZZZZ"),
1199 ..nv12_dmabuf(&display, (WIDTH, HEIGHT), 0)
1200 };
1201 assert!(encoder.encode_dmabuf(unmapped, true).is_err());
1202 let unknown_modifier = DmaBuf {
1203 modifier: 0x00ff_ffff_ffff_fffe,
1204 ..nv12_dmabuf(&display, (WIDTH, HEIGHT), 0)
1205 };
1206 assert!(encoder.encode_dmabuf(unknown_modifier, true).is_err());
1207
1208 let unit = encoder
1209 .encode_nv12(&nv12_frame(WIDTH, HEIGHT, 0), true)
1210 .expect("the CPU path still encodes");
1211 let Some(decoded) = ffmpeg_decode(&unit, "refused") else {
1212 return;
1213 };
1214 let psnr = luma_psnr(&nv12_frame(WIDTH, HEIGHT, 0), &decoded, WIDTH, HEIGHT);
1215 assert!(psnr > 35.0, "decoded at {psnr:.1} dB");
1216 }
1217
1218 #[test]
1221 fn a_buffer_short_of_whole_macroblocks_is_blitted() {
1222 let (width, height) = (1920, 1080);
1223 let Some(mut encoder) = encoder(Config::new(width, height, 30, 4_000_000, 30)) else {
1224 return;
1225 };
1226 let Some(display) = Display::open() else {
1227 eprintln!("skipping: no VA-API display");
1228 return;
1229 };
1230 let unit = encoder
1231 .encode_dmabuf(nv12_dmabuf(&display, (width, height), 0), true)
1232 .expect("encode the DMA-BUF");
1233 let Some(decoded) = ffmpeg_decode(&unit, "1080") else {
1234 return;
1235 };
1236 let psnr = luma_psnr(&nv12_frame(width, height, 0), &decoded, width, height);
1237 assert!(psnr > 35.0, "decoded at {psnr:.1} dB");
1238 }
1239
1240 #[test]
1243 fn a_yuv_dmabuf_in_another_space_is_converted() {
1244 let Some(mut encoder) = encoder(Config {
1245 color: Color::BT601,
1246 ..config()
1247 }) else {
1248 return;
1249 };
1250 let Some(display) = Display::open() else {
1251 eprintln!("skipping: no VA-API display");
1252 return;
1253 };
1254 let [y, u, v] = limited([255, 0, 0], Color::BT709).map(|value| value.round() as u8);
1256 let surface = surface_with(
1257 &display,
1258 VA_FOURCC_NV12,
1259 VA_RT_FORMAT_YUV420,
1260 (WIDTH, HEIGHT),
1261 |data, image| {
1262 for row in 0..HEIGHT as usize {
1263 let at = image.offsets[0] as usize + row * image.pitches[0] as usize;
1264 data[at..at + WIDTH as usize].fill(y);
1265 }
1266 for row in 0..HEIGHT as usize / 2 {
1267 let at = image.offsets[1] as usize + row * image.pitches[1] as usize;
1268 for pair in data[at..at + WIDTH as usize].chunks_exact_mut(2) {
1269 pair.copy_from_slice(&[u, v]);
1270 }
1271 }
1272 },
1273 );
1274 let buffer = DmaBuf {
1275 color: Some(Color::BT709),
1276 ..DmaBuf::from_prime(surface.export_prime().expect("export")).expect("one object")
1277 };
1278 let unit = encoder.encode_dmabuf(buffer, true).expect("encode");
1279 let Some(decoded) = ffmpeg_decode(&unit, "yuv-color") else {
1280 return;
1281 };
1282
1283 let (w, h) = (WIDTH as usize, HEIGHT as usize);
1284 let (luma, chroma) = decoded.split_at(w * h);
1285 let actual = [
1286 mean(luma, w, (w / 4, h / 4, w * 3 / 4, h * 3 / 4), 1, 0),
1287 mean(chroma, w, (w / 8, h / 8, w * 3 / 8, h * 3 / 8), 2, 0),
1288 mean(chroma, w, (w / 8, h / 8, w * 3 / 8, h * 3 / 8), 2, 1),
1289 ];
1290 let expected = limited([255, 0, 0], Color::BT601);
1291 for (channel, (a, e)) in actual.iter().zip(expected).enumerate() {
1292 assert!(
1293 (a - e).abs() < 6.0,
1294 "channel {channel}: decoded {a:.1}, expected BT.601 {e:.1}"
1295 );
1296 }
1297 }
1298
1299 #[test]
1301 fn an_nv12_dmabuf_encodes_in_place() {
1302 let Some(mut encoder) = encoder(config()) else { return };
1303 let Some(display) = Display::open() else {
1304 eprintln!("skipping: no VA-API display");
1305 return;
1306 };
1307 let mut stream = Vec::new();
1308 for step in 0..5 {
1309 let buffer = nv12_dmabuf(&display, (WIDTH, HEIGHT), step);
1310 stream.extend(encoder.encode_dmabuf(buffer, false).expect("encode the DMA-BUF"));
1311 }
1312 let Some(decoded) = ffmpeg_decode(&stream, "dmabuf") else {
1313 return;
1314 };
1315 let len = nv12_frame(WIDTH, HEIGHT, 0).len();
1316 assert_eq!(decoded.len(), 5 * len);
1317 for step in 0..5 {
1318 let psnr = luma_psnr(
1319 &nv12_frame(WIDTH, HEIGHT, step),
1320 &decoded[step as usize * len..],
1321 WIDTH,
1322 HEIGHT,
1323 );
1324 assert!(psnr > 35.0, "picture {step} decoded at {psnr:.1} dB");
1325 }
1326 }
1327
1328 #[test]
1330 fn a_dmabuf_at_another_size_is_scaled() {
1331 let Some(mut encoder) = encoder(config()) else { return };
1332 let Some(display) = Display::open() else {
1333 eprintln!("skipping: no VA-API display");
1334 return;
1335 };
1336 let mut stream = Vec::new();
1337 for step in 0..3 {
1338 let buffer = nv12_dmabuf(&display, (WIDTH * 2, HEIGHT * 2), step);
1339 stream.extend(encoder.encode_dmabuf(buffer, false).expect("encode the DMA-BUF"));
1340 }
1341 let Some(decoded) = ffmpeg_decode(&stream, "scaled") else {
1342 return;
1343 };
1344 let len = nv12_frame(WIDTH, HEIGHT, 0).len();
1345 assert_eq!(decoded.len(), 3 * len, "the output is at the encoder's size");
1346 for step in 0..3u32 {
1350 let source = nv12_frame(WIDTH * 2, HEIGHT * 2, step);
1351 let mut expected = vec![0u8; (WIDTH * HEIGHT) as usize];
1352 for y in 0..HEIGHT as usize {
1353 for x in 0..WIDTH as usize {
1354 expected[y * WIDTH as usize + x] = source[(y * 2) * (WIDTH as usize * 2) + x * 2];
1355 }
1356 }
1357 let psnr = luma_psnr(&expected, &decoded[step as usize * len..], WIDTH, HEIGHT);
1358 assert!(psnr > 25.0, "picture {step} decoded at {psnr:.1} dB");
1359 }
1360 }
1361
1362 #[test]
1364 fn an_rgb_dmabuf_is_converted_to_the_configured_space() {
1365 const QUADRANTS: [[u8; 3]; 4] = [[255, 0, 0], [0, 255, 0], [0, 0, 255], [255, 255, 255]];
1367 for color in [Color::BT601, Color::BT709] {
1368 let Some(mut encoder) = encoder(Config { color, ..config() }) else {
1369 return;
1370 };
1371 let Some(display) = Display::open() else {
1372 eprintln!("skipping: no VA-API display");
1373 return;
1374 };
1375 let surface = surface_with(
1376 &display,
1377 VA_FOURCC_BGRX,
1378 VA_RT_FORMAT_RGB32,
1379 (WIDTH, HEIGHT),
1380 |data, image| {
1381 let pitch = image.pitches[0] as usize;
1382 let offset = image.offsets[0] as usize;
1383 for y in 0..HEIGHT as usize {
1384 for x in 0..WIDTH as usize {
1385 let quadrant = (y >= HEIGHT as usize / 2) as usize * 2 + (x >= WIDTH as usize / 2) as usize;
1386 let [r, g, b] = QUADRANTS[quadrant];
1387 let at = offset + y * pitch + x * 4;
1388 data[at..at + 4].copy_from_slice(&[b, g, r, 255]);
1389 }
1390 }
1391 },
1392 );
1393 let exported = surface.export_prime().expect("export");
1394 let buffer = DmaBuf::from_prime(exported).expect("one object");
1395 assert_eq!(buffer.drm_format, crate::dmabuf::drm::XRGB8888);
1396 let unit = encoder.encode_dmabuf(buffer, false).expect("encode the RGB DMA-BUF");
1397 let Some(decoded) = ffmpeg_decode(&unit, "rgb") else {
1398 return;
1399 };
1400
1401 let (w, h) = (WIDTH as usize, HEIGHT as usize);
1402 let (luma, chroma) = decoded.split_at(w * h);
1403 for (quadrant, rgb) in QUADRANTS.iter().enumerate() {
1404 let (qx, qy) = (quadrant % 2, quadrant / 2);
1405 let area = |scale: usize| {
1407 let (qw, qh) = (w / 2 / scale, h / 2 / scale);
1408 (
1409 qx * qw + qw / 4,
1410 qy * qh + qh / 4,
1411 qx * qw + qw * 3 / 4,
1412 qy * qh + qh * 3 / 4,
1413 )
1414 };
1415 let actual = [
1416 mean(luma, w, area(1), 1, 0),
1417 mean(chroma, w, area(2), 2, 0),
1418 mean(chroma, w, area(2), 2, 1),
1419 ];
1420 let expected = limited(*rgb, color);
1421 for (channel, (a, e)) in actual.iter().zip(expected).enumerate() {
1422 assert!(
1423 (a - e).abs() < 6.0,
1424 "{color:?} quadrant {quadrant} channel {channel}: decoded {a:.1}, expected {e:.1}"
1425 );
1426 }
1427 }
1428 }
1429 }
1430
1431 #[test]
1432 fn the_level_covers_the_frame_size_and_rate() {
1433 assert_eq!(level(320, 240, 30), Level::L4);
1434 assert_eq!(level(1920, 1080, 30), Level::L4);
1435 assert_eq!(level(1920, 1080, 60), Level::L4_2);
1436 assert_eq!(level(2560, 1440, 30), Level::L5);
1437 assert_eq!(level(3840, 2160, 30), Level::L5_1);
1438 assert_eq!(level(3840, 2160, 60), Level::L5_2);
1439 }
1440
1441 fn limited([r, g, b]: [u8; 3], color: Color) -> [f64; 3] {
1443 let (kr, kb) = match color.matrix {
1444 crate::Matrix::Bt601 => (0.299, 0.114),
1445 crate::Matrix::Bt709 => (0.2126, 0.0722),
1446 };
1447 let (r, g, b) = (r as f64, g as f64, b as f64);
1448 let y = kr * r + (1.0 - kr - kb) * g + kb * b;
1449 [
1450 16.0 + y * 219.0 / 255.0,
1451 128.0 + (b - y) / (2.0 * (1.0 - kb)) * 224.0 / 255.0,
1452 128.0 + (r - y) / (2.0 * (1.0 - kr)) * 224.0 / 255.0,
1453 ]
1454 }
1455
1456 #[test]
1458 fn a_bitrate_change_takes_effect_without_a_keyframe() {
1459 let Some(mut encoder) = encoder(Config::new(WIDTH, HEIGHT, 30, 2_000_000, 1_000)) else {
1460 return;
1461 };
1462 let mut state = 0x2545_f491_u32;
1466 let mut frame = |step: u32| {
1467 let mut frame = nv12_frame(WIDTH, HEIGHT, step);
1468 for sample in &mut frame[..(WIDTH * HEIGHT) as usize] {
1469 state ^= state << 13;
1470 state ^= state >> 17;
1471 state ^= state << 5;
1472 *sample = sample.saturating_add((state >> 28) as u8);
1473 }
1474 frame
1475 };
1476 let mut average = |encoder: &mut Encoder, from: u32| {
1480 let sizes: Vec<usize> = (from..from + 60)
1481 .map(|step| {
1482 let unit = encoder.encode_nv12(&frame(step), false).expect("encode");
1483 if step > 0 {
1484 assert!(!nal_types(&unit).contains(&5), "picture {step} is an IDR");
1485 }
1486 unit.len()
1487 })
1488 .collect();
1489 sizes[30..].iter().sum::<usize>() / 30
1490 };
1491
1492 let high = average(&mut encoder, 0);
1493 encoder.set_bitrate(250_000).expect("set the bitrate");
1494 assert_eq!(encoder.config().bitrate, 250_000);
1495 assert!(encoder.set_bitrate(0).is_err(), "a zero bitrate is refused");
1496 let low = average(&mut encoder, 60);
1497 eprintln!("mean picture at 2 Mbps: {high} bytes, at 250 kbps: {low} bytes");
1498 assert!(
1499 low * 3 < high,
1500 "the rate did not drop: {high} -> {low} bytes per picture"
1501 );
1502 }
1503}