1use crate::{Codec, CodecFamily, EncoderBackend, Hdr10Metadata, StreamInfo};
4
5#[derive(Debug, Clone, Default, PartialEq, Eq)]
7pub struct SourceFormat {
8 pub pix_fmt: String,
10 pub bit_depth: u8,
12 pub color_primaries: String,
14 pub color_transfer: String,
16 pub color_space: String,
18 pub is_hdr: bool,
20 pub hdr10: Option<Hdr10Metadata>,
22}
23
24impl SourceFormat {
25 pub fn from_stream(stream: &StreamInfo) -> Self {
27 let bit_depth = bit_depth(stream);
28 let pix_fmt = if stream.pix_fmt.is_empty() {
29 yuv420p_for_depth(bit_depth).to_string()
30 } else {
31 stream.pix_fmt.clone()
32 };
33 Self {
34 pix_fmt,
35 bit_depth,
36 color_primaries: stream.color_primaries.clone(),
37 color_transfer: stream.color_transfer.clone(),
38 color_space: stream.color_space.clone(),
39 is_hdr: stream.is_hdr(),
40 hdr10: None,
41 }
42 }
43
44 pub fn is_high_bit_depth(&self) -> bool {
46 self.bit_depth > 8
47 }
48
49 pub async fn enrich_hdr10(mut self, path: &str) -> Self {
56 if self.is_hdr {
57 if let Ok(Some(md)) = crate::probe_hdr10_metadata(path).await {
58 self.hdr10 = Some(md);
59 }
60 }
61 self
62 }
63}
64
65pub fn bit_depth(stream: &StreamInfo) -> u8 {
67 if stream.bits_per_raw_sample >= 10 {
68 return stream.bits_per_raw_sample.clamp(8, 16) as u8;
69 }
70 if stream.pix_fmt.contains("16") {
71 return 16;
72 }
73 if stream.pix_fmt.contains("12") {
74 return 12;
75 }
76 if stream.pix_fmt.contains("10") {
77 return 10;
78 }
79 8
80}
81
82pub fn yuv420p_for_depth(depth: u8) -> &'static str {
84 match depth {
85 10 => "yuv420p10le",
86 12 => "yuv420p12le",
87 16 => "yuv420p16le",
88 _ => "yuv420p",
89 }
90}
91
92pub fn psnr_peak(depth: u8) -> f64 {
94 match depth {
95 10 => 1023.0,
96 12 => 4095.0,
97 16 => 65535.0,
98 _ => 255.0,
99 }
100}
101
102pub fn codec_supports_bit_depth(codec: Codec, depth: u8) -> bool {
108 if depth <= 8 {
109 return true;
110 }
111 match codec {
112 Codec::X264 | Codec::X265 | Codec::SvtAv1 | Codec::Vp9 => true,
114 Codec::NvencH265 | Codec::QsvH265 | Codec::AmfH265 | Codec::VideoToolboxH265 => true,
116 Codec::NvencAv1 | Codec::QsvAv1 | Codec::AmfAv1 => true,
117 Codec::VaapiH265 | Codec::VaapiAv1 => true,
118 Codec::NvencH264
120 | Codec::QsvH264
121 | Codec::AmfH264
122 | Codec::VideoToolboxH264
123 | Codec::VaapiH264 => false,
124 }
125}
126
127fn hw_pix_fmt(format: &SourceFormat, codec: Codec) -> Option<String> {
131 if !format.is_high_bit_depth() {
132 return None;
133 }
134 match codec.backend() {
138 EncoderBackend::Nvenc
139 | EncoderBackend::Qsv
140 | EncoderBackend::Amf
141 | EncoderBackend::VideoToolbox => {
142 if format.bit_depth == 10 {
143 Some("p010le".into())
144 } else {
145 None
146 }
147 }
148 EncoderBackend::Vaapi => None, EncoderBackend::Software => None,
150 }
151}
152
153pub fn encode_color_args(codec: Codec, format: &SourceFormat) -> Vec<String> {
155 let mut args = Vec::new();
156
157 if format.is_high_bit_depth() && codec_supports_bit_depth(codec, format.bit_depth) {
158 if codec.is_software() {
159 args.extend(["-pix_fmt".into(), format.pix_fmt.clone()]);
160 match codec {
161 Codec::X264 => args.extend(["-profile:v".into(), "high10".into()]),
162 Codec::X265 => args.extend(["-x265-params".into(), x265_params(format)]),
163 Codec::SvtAv1 => {}
164 _ => {}
165 }
166 } else if let Some(pix) = hw_pix_fmt(format, codec) {
167 args.extend(["-pix_fmt".into(), pix]);
168 if matches!(codec.family(), CodecFamily::H265 | CodecFamily::Av1) {
170 args.extend(["-profile:v".into(), "main10".into()]);
171 }
172 }
173 }
174
175 if format.is_hdr {
176 append_color_metadata(&mut args, format);
177 match codec {
178 Codec::X265 => merge_x265_color_params(&mut args, format),
179 Codec::SvtAv1 => {
180 let params = svtav1_hdr_params(format);
181 if !params.is_empty() {
182 args.extend(["-svtav1-params".into(), params]);
186 }
187 }
188 _ => add_hdr_bsf(&mut args, codec.family(), format),
189 }
190 }
191
192 args
193}
194
195fn add_hdr_bsf(args: &mut Vec<String>, family: CodecFamily, format: &SourceFormat) {
205 let Some(hdr10) = &format.hdr10 else {
206 return;
207 };
208 if hdr10.is_empty() {
209 return;
210 }
211
212 let bsf_name = match family {
213 CodecFamily::H265 => "hevc_metadata",
214 CodecFamily::H264 => "h264_metadata",
215 _ => return,
218 };
219
220 let mut params = Vec::new();
221 if let Some(display) = &hdr10.mastering_display {
222 params.push(format!("mastering_display=\"{}\"", display.to_x265_string()));
223 }
224 if let Some(max_cll) = hdr10.max_cll {
225 let max_fall = hdr10.max_fall.unwrap_or(0);
226 params.push(format!("max_cll={max_cll},{max_fall}"));
227 }
228 if !params.is_empty() {
229 args.extend(["-bsf".into(), format!("{}={}", bsf_name, params.join(":"))]);
230 }
231}
232
233fn append_color_metadata(args: &mut Vec<String>, format: &SourceFormat) {
234 if !format.color_primaries.is_empty() {
235 args.extend(["-color_primaries".into(), format.color_primaries.clone()]);
236 }
237 if !format.color_transfer.is_empty() {
238 args.extend(["-color_trc".into(), format.color_transfer.clone()]);
239 }
240 if !format.color_space.is_empty() {
241 args.extend(["-colorspace".into(), format.color_space.clone()]);
242 }
243}
244
245fn x265_params(format: &SourceFormat) -> String {
246 let mut parts = Vec::new();
247 if format.bit_depth > 8 {
248 parts.push("profile=main10".into());
249 }
250 if format.is_hdr {
251 if !format.color_primaries.is_empty() {
252 parts.push(format!("colorprim={}", format.color_primaries));
253 }
254 if !format.color_transfer.is_empty() {
255 parts.push(format!("transfer={}", format.color_transfer));
256 }
257 if !format.color_space.is_empty() {
258 parts.push(format!("colormatrix={}", format.color_space));
259 }
260 if let Some(hdr10) = &format.hdr10 {
261 if let Some(display) = &hdr10.mastering_display {
262 parts.push(format!("master-display={}", display.to_x265_string()));
263 }
264 if let Some(max_cll) = hdr10.max_cll {
265 parts.push(format!("max-cll={},{}", max_cll, hdr10.max_fall.unwrap_or(0)));
267 }
268 }
269 }
270 parts.join(":")
271}
272
273fn svtav1_hdr_params(format: &SourceFormat) -> String {
279 let mut parts = Vec::new();
280 if let Some(hdr10) = &format.hdr10 {
281 if let Some(display) = &hdr10.mastering_display {
282 parts.push(format!("mastering-display={}", display.to_svtav1_string()));
283 }
284 if let Some(max_cll) = hdr10.max_cll {
285 parts.push(format!("content-light={},{}", max_cll, hdr10.max_fall.unwrap_or(0)));
286 }
287 }
288 parts.join(":")
289}
290
291fn merge_x265_color_params(args: &mut Vec<String>, format: &SourceFormat) {
292 let color = x265_params(format);
293 if color.is_empty() {
294 return;
295 }
296 if let Some(idx) = args.iter().position(|a| a == "-x265-params") {
297 let existing = args.get(idx + 1).cloned().unwrap_or_default();
298 let merged = if existing.is_empty() { color } else { format!("{existing}:{color}") };
299 args[idx + 1] = merged;
300 } else {
301 args.extend(["-x265-params".into(), color]);
302 }
303}
304
305#[cfg(test)]
306mod tests {
307 use super::*;
308 use crate::StreamInfo;
309
310 fn base_stream() -> StreamInfo {
311 StreamInfo {
312 index: 0,
313 codec_name: "h264".into(),
314 codec_long_name: String::new(),
315 codec_type: "video".into(),
316 profile: String::new(),
317 width: 1920,
318 height: 1080,
319 pix_fmt: "yuv420p".into(),
320 level: 0,
321 field_order: String::new(),
322 color_range: String::new(),
323 color_space: "bt709".into(),
324 color_transfer: "bt709".into(),
325 color_primaries: "bt709".into(),
326 duration: 0.0,
327 bit_rate: 0,
328 nb_frames: 0,
329 r_frame_rate: "24/1".into(),
330 avg_frame_rate: "24/1".into(),
331 sample_rate: 0,
332 channels: 0,
333 channel_layout: String::new(),
334 bits_per_raw_sample: 8,
335 }
336 }
337
338 #[test]
339 fn test_bit_depth_from_pix_fmt() {
340 let mut stream = base_stream();
341 stream.pix_fmt = "yuv420p10le".into();
342 assert_eq!(bit_depth(&stream), 10);
343 }
344
345 #[test]
346 fn test_source_format_high_bit_depth() {
347 let mut stream = base_stream();
348 stream.pix_fmt = "yuv420p10le".into();
349 let format = SourceFormat::from_stream(&stream);
350 assert_eq!(format.bit_depth, 10);
351 assert_eq!(format.pix_fmt, "yuv420p10le");
352 assert!(format.is_high_bit_depth());
353 }
354
355 #[test]
356 fn test_encode_color_args_x265_10bit_hdr() {
357 let mut stream = base_stream();
358 stream.pix_fmt = "yuv420p10le".into();
359 stream.color_transfer = "smpte2084".into();
360 stream.color_primaries = "bt2020".into();
361 stream.color_space = "bt2020nc".into();
362 let format = SourceFormat::from_stream(&stream);
363 let args = encode_color_args(Codec::X265, &format);
364 assert!(args.windows(2).any(|w| w[0] == "-pix_fmt" && w[1] == "yuv420p10le"));
365 assert!(args.iter().any(|a| a.contains("profile=main10")));
366 assert!(args.iter().any(|a| a.contains("transfer=smpte2084")));
367 }
368
369 #[test]
370 fn test_psnr_peak_scaling() {
371 assert_eq!(psnr_peak(8), 255.0);
372 assert_eq!(psnr_peak(10), 1023.0);
373 }
374
375 #[test]
376 fn test_encode_color_args_emits_hdr10_metadata() {
377 use crate::{Hdr10Metadata, MasteringDisplay};
378 let mut stream = base_stream();
379 stream.pix_fmt = "yuv420p10le".into();
380 stream.color_transfer = "smpte2084".into();
381 stream.color_primaries = "bt2020".into();
382 stream.color_space = "bt2020nc".into();
383 let mut format = SourceFormat::from_stream(&stream);
384 format.hdr10 = Some(Hdr10Metadata {
385 mastering_display: Some(MasteringDisplay {
386 green_x: 13250,
387 green_y: 34500,
388 blue_x: 7500,
389 blue_y: 3000,
390 red_x: 34000,
391 red_y: 16000,
392 white_x: 15635,
393 white_y: 16450,
394 max_luminance: 10_000_000,
395 min_luminance: 50,
396 }),
397 max_cll: Some(1000),
398 max_fall: Some(400),
399 });
400 let args = encode_color_args(Codec::X265, &format);
401 let params = args
402 .windows(2)
403 .find(|w| w[0] == "-x265-params")
404 .map(|w| w[1].clone())
405 .expect("x265-params present");
406 assert!(
407 params.contains("master-display=G(13250,34500)B(7500,3000)R(34000,16000)WP(15635,16450)L(10000000,50)"),
408 "got: {params}"
409 );
410 assert!(params.contains("max-cll=1000,400"), "got: {params}");
411 }
412
413 #[test]
414 fn test_encode_color_args_svtav1_hdr10_metadata() {
415 use crate::{Hdr10Metadata, MasteringDisplay};
416 let mut stream = base_stream();
417 stream.pix_fmt = "yuv420p10le".into();
418 stream.color_transfer = "smpte2084".into();
419 stream.color_primaries = "bt2020".into();
420 stream.color_space = "bt2020nc".into();
421 let mut format = SourceFormat::from_stream(&stream);
422 format.hdr10 = Some(Hdr10Metadata {
423 mastering_display: Some(MasteringDisplay {
424 green_x: 13250,
425 green_y: 34500,
426 blue_x: 7500,
427 blue_y: 3000,
428 red_x: 34000,
429 red_y: 16000,
430 white_x: 15635,
431 white_y: 16450,
432 max_luminance: 10_000_000,
433 min_luminance: 50,
434 }),
435 max_cll: Some(1000),
436 max_fall: Some(400),
437 });
438 let args = encode_color_args(Codec::SvtAv1, &format);
439 let params = args
440 .windows(2)
441 .find(|w| w[0] == "-svtav1-params")
442 .map(|w| w[1].clone())
443 .expect("svtav1-params present");
444 assert!(
445 params.contains("mastering-display=G(0.265,0.69)B(0.15,0.06)R(0.68,0.32)WP(0.3127,0.329)L(1000,0.005)"),
446 "got: {params}"
447 );
448 assert!(params.contains("content-light=1000,400"), "got: {params}");
449 assert!(args.windows(2).any(|w| w[0] == "-color_trc" && w[1] == "smpte2084"));
451 }
452
453 #[test]
454 fn test_encode_color_args_no_hdr10_when_sdr() {
455 let mut stream = base_stream();
456 stream.pix_fmt = "yuv420p10le".into();
457 let format = SourceFormat::from_stream(&stream);
458 let args = encode_color_args(Codec::X265, &format);
459 assert!(!args.iter().any(|a| a.contains("master-display")));
460 assert!(!args.iter().any(|a| a.contains("max-cll")));
461 }
462
463 #[test]
466 fn test_hw_pix_fmt_nvenc_10bit() {
467 let mut stream = base_stream();
468 stream.pix_fmt = "yuv420p10le".into();
469 stream.bits_per_raw_sample = 10;
470 let format = SourceFormat::from_stream(&stream);
471 assert_eq!(hw_pix_fmt(&format, Codec::NvencH265), Some("p010le".into()));
472 assert_eq!(hw_pix_fmt(&format, Codec::NvencAv1), Some("p010le".into()));
473 }
474
475 #[test]
476 fn test_hw_pix_fmt_8bit_shows_none() {
477 let format = SourceFormat::from_stream(&base_stream());
478 assert_eq!(hw_pix_fmt(&format, Codec::NvencH265), None);
479 assert_eq!(hw_pix_fmt(&format, Codec::QsvH265), None);
480 assert_eq!(hw_pix_fmt(&format, Codec::VaapiH265), None);
481 }
482
483 #[test]
484 fn test_hw_pix_fmt_vaapi_returns_none() {
485 let mut stream = base_stream();
486 stream.pix_fmt = "yuv420p10le".into();
487 stream.bits_per_raw_sample = 10;
488 let format = SourceFormat::from_stream(&stream);
489 assert_eq!(hw_pix_fmt(&format, Codec::VaapiH265), None);
490 }
491
492 #[test]
493 fn test_hw_pix_fmt_amf_10bit() {
494 let mut stream = base_stream();
495 stream.pix_fmt = "yuv420p10le".into();
496 stream.bits_per_raw_sample = 10;
497 let format = SourceFormat::from_stream(&stream);
498 assert_eq!(hw_pix_fmt(&format, Codec::AmfH265), Some("p010le".into()));
499 }
500
501 #[test]
502 fn test_hw_pix_fmt_qsv_10bit() {
503 let mut stream = base_stream();
504 stream.pix_fmt = "yuv420p10le".into();
505 stream.bits_per_raw_sample = 10;
506 let format = SourceFormat::from_stream(&stream);
507 assert_eq!(hw_pix_fmt(&format, Codec::QsvH265), Some("p010le".into()));
508 }
509
510 #[test]
511 fn test_hw_pix_fmt_videotoolbox_10bit() {
512 let mut stream = base_stream();
513 stream.pix_fmt = "yuv420p10le".into();
514 stream.bits_per_raw_sample = 10;
515 let format = SourceFormat::from_stream(&stream);
516 assert_eq!(hw_pix_fmt(&format, Codec::VideoToolboxH265), Some("p010le".into()));
517 }
518
519 #[test]
522 fn test_encode_color_args_nvenc_high_bit_depth_pix_fmt() {
523 let mut stream = base_stream();
524 stream.pix_fmt = "yuv420p10le".into();
525 stream.bits_per_raw_sample = 10;
526 let format = SourceFormat::from_stream(&stream);
527 let args = encode_color_args(Codec::NvencH265, &format);
528 assert!(
529 args.windows(2).any(|w| w[0] == "-pix_fmt" && w[1] == "p010le"),
530 "expected -pix_fmt p010le for NVENC 10-bit, got {args:?}"
531 );
532 }
533
534 #[test]
535 fn test_encode_color_args_nvenc_hdr10_sets_color_and_bsf() {
536 use crate::{Hdr10Metadata, MasteringDisplay};
537 let mut stream = base_stream();
538 stream.pix_fmt = "yuv420p10le".into();
539 stream.bits_per_raw_sample = 10;
540 stream.color_transfer = "smpte2084".into();
541 stream.color_primaries = "bt2020".into();
542 stream.color_space = "bt2020nc".into();
543 let mut format = SourceFormat::from_stream(&stream);
544 format.hdr10 = Some(Hdr10Metadata {
545 mastering_display: Some(MasteringDisplay {
546 green_x: 13250,
547 green_y: 34500,
548 blue_x: 7500,
549 blue_y: 3000,
550 red_x: 34000,
551 red_y: 16000,
552 white_x: 15635,
553 white_y: 16450,
554 max_luminance: 10_000_000,
555 min_luminance: 50,
556 }),
557 max_cll: Some(1000),
558 max_fall: Some(400),
559 });
560 let args = encode_color_args(Codec::NvencH265, &format);
561 assert!(args.windows(2).any(|w| w[0] == "-color_trc" && w[1] == "smpte2084"));
563 assert!(args.windows(2).any(|w| w[0] == "-pix_fmt" && w[1] == "p010le"));
565 let bsf_idx = args.iter().position(|a| a == "-bsf").expect("missing -bsf");
567 let bsf_val = &args[bsf_idx + 1];
568 assert!(
569 bsf_val.starts_with("hevc_metadata="),
570 "expected hevc_metadata BSF, got: {bsf_val}"
571 );
572 assert!(bsf_val.contains("mastering_display="));
573 assert!(bsf_val.contains("max_cll=1000,400"));
574 }
575
576 #[test]
577 fn test_encode_color_args_amf_high_bit_depth_pix_fmt() {
578 let mut stream = base_stream();
579 stream.pix_fmt = "yuv420p10le".into();
580 stream.bits_per_raw_sample = 10;
581 let format = SourceFormat::from_stream(&stream);
582 let args = encode_color_args(Codec::AmfH265, &format);
583 assert!(
584 args.windows(2).any(|w| w[0] == "-pix_fmt" && w[1] == "p010le"),
585 "expected -pix_fmt p010le for AMF 10-bit, got {args:?}"
586 );
587 }
588
589 #[test]
590 fn test_encode_color_args_qsv_high_bit_depth_pix_fmt() {
591 let mut stream = base_stream();
592 stream.pix_fmt = "yuv420p10le".into();
593 stream.bits_per_raw_sample = 10;
594 let format = SourceFormat::from_stream(&stream);
595 let args = encode_color_args(Codec::QsvH265, &format);
596 assert!(
597 args.windows(2).any(|w| w[0] == "-pix_fmt" && w[1] == "p010le"),
598 "expected -pix_fmt p010le for QSV 10-bit, got {args:?}"
599 );
600 }
601
602 #[test]
603 fn test_encode_color_args_videotoolbox_high_bit_depth_pix_fmt() {
604 let mut stream = base_stream();
605 stream.pix_fmt = "yuv420p10le".into();
606 stream.bits_per_raw_sample = 10;
607 let format = SourceFormat::from_stream(&stream);
608 let args = encode_color_args(Codec::VideoToolboxH265, &format);
609 assert!(
610 args.windows(2).any(|w| w[0] == "-pix_fmt" && w[1] == "p010le"),
611 "expected -pix_fmt p010le for VideoToolbox 10-bit, got {args:?}"
612 );
613 }
614
615 #[test]
616 fn test_encode_color_args_nvenc_h264_8bit_does_not_set_p010() {
617 let mut stream = base_stream();
619 stream.pix_fmt = "yuv420p10le".into();
620 stream.bits_per_raw_sample = 10;
621 let format = SourceFormat::from_stream(&stream);
622 let args = encode_color_args(Codec::NvencH264, &format);
623 assert!(!args.windows(2).any(|w| w == ["-pix_fmt", "p010le"]));
624 assert!(args.is_empty(), "expected no color args for H.264 NVENC 10-bit: {args:?}");
625 }
626
627 #[test]
628 fn test_encode_color_args_adds_profile_for_hw_hevc_10bit() {
629 let mut stream = base_stream();
630 stream.pix_fmt = "yuv420p10le".into();
631 stream.bits_per_raw_sample = 10;
632 let format = SourceFormat::from_stream(&stream);
633 for codec in &[Codec::NvencH265, Codec::QsvH265, Codec::AmfH265, Codec::VideoToolboxH265] {
634 let args = encode_color_args(*codec, &format);
635 assert!(
636 args.windows(2).any(|w| w == ["-profile:v", "main10"]),
637 "{codec:?}: expected -profile:v main10, got {args:?}"
638 );
639 }
640 }
641
642 #[test]
643 fn test_encode_color_args_svtav1_hdr10_uses_svtav1_params_not_bsf() {
644 use crate::{Hdr10Metadata, MasteringDisplay};
645 let mut stream = base_stream();
646 stream.pix_fmt = "yuv420p10le".into();
647 stream.bits_per_raw_sample = 10;
648 stream.color_transfer = "smpte2084".into();
649 stream.color_primaries = "bt2020".into();
650 stream.color_space = "bt2020nc".into();
651 let mut format = SourceFormat::from_stream(&stream);
652 format.hdr10 = Some(Hdr10Metadata {
653 mastering_display: Some(MasteringDisplay {
654 green_x: 13250,
655 green_y: 34500,
656 blue_x: 7500,
657 blue_y: 3000,
658 red_x: 34000,
659 red_y: 16000,
660 white_x: 15635,
661 white_y: 16450,
662 max_luminance: 10_000_000,
663 min_luminance: 50,
664 }),
665 max_cll: Some(1000),
666 max_fall: Some(400),
667 });
668 let args = encode_color_args(Codec::SvtAv1, &format);
669 assert!(!args.iter().any(|a| a == "-bsf"), "SVT-AV1 should not use BSF: {args:?}");
671 assert!(
672 args.windows(2).any(|w| w[0] == "-svtav1-params"),
673 "SVT-AV1 should use -svtav1-params: {args:?}"
674 );
675 }
676
677 #[test]
678 fn test_encode_color_args_x265_hdr10_uses_x265_params_not_bsf() {
679 use crate::{Hdr10Metadata, MasteringDisplay};
680 let mut stream = base_stream();
681 stream.pix_fmt = "yuv420p10le".into();
682 stream.bits_per_raw_sample = 10;
683 stream.color_transfer = "smpte2084".into();
684 stream.color_primaries = "bt2020".into();
685 stream.color_space = "bt2020nc".into();
686 let mut format = SourceFormat::from_stream(&stream);
687 format.hdr10 = Some(Hdr10Metadata {
688 mastering_display: Some(MasteringDisplay {
689 green_x: 13250,
690 green_y: 34500,
691 blue_x: 7500,
692 blue_y: 3000,
693 red_x: 34000,
694 red_y: 16000,
695 white_x: 15635,
696 white_y: 16450,
697 max_luminance: 10_000_000,
698 min_luminance: 50,
699 }),
700 max_cll: Some(1000),
701 max_fall: Some(400),
702 });
703 let args = encode_color_args(Codec::X265, &format);
704 assert!(!args.iter().any(|a| a == "-bsf"), "x265 should not use BSF: {args:?}");
705 assert!(
706 args.windows(2).any(|w| w[0] == "-x265-params"),
707 "x265 should use -x265-params: {args:?}"
708 );
709 }
710
711 #[test]
712 fn test_encode_color_args_nvenc_av1_no_bsf() {
713 let mut stream = base_stream();
716 stream.pix_fmt = "yuv420p10le".into();
717 stream.bits_per_raw_sample = 10;
718 stream.color_transfer = "smpte2084".into();
719 stream.color_primaries = "bt2020".into();
720 stream.color_space = "bt2020nc".into();
721 let format = SourceFormat::from_stream(&stream);
722 let args = encode_color_args(Codec::NvencAv1, &format);
723 assert!(!args.iter().any(|a| a == "-bsf"), "AV1 should not use BSF: {args:?}");
724 assert!(args.windows(2).any(|w| w[0] == "-color_trc" && w[1] == "smpte2084"));
726 }
727
728 #[test]
731 fn test_codec_supports_bit_depth_10bit_hw() {
732 assert!(codec_supports_bit_depth(Codec::NvencH265, 10));
733 assert!(codec_supports_bit_depth(Codec::QsvH265, 10));
734 assert!(codec_supports_bit_depth(Codec::AmfH265, 10));
735 assert!(codec_supports_bit_depth(Codec::VideoToolboxH265, 10));
736 assert!(codec_supports_bit_depth(Codec::VaapiH265, 10));
737 assert!(codec_supports_bit_depth(Codec::NvencAv1, 10));
738 assert!(codec_supports_bit_depth(Codec::QsvAv1, 10));
739 assert!(codec_supports_bit_depth(Codec::AmfAv1, 10));
740 assert!(codec_supports_bit_depth(Codec::VaapiAv1, 10));
741 }
742
743 #[test]
744 fn test_codec_supports_bit_depth_h264_hw_8bit_only() {
745 assert!(!codec_supports_bit_depth(Codec::NvencH264, 10));
746 assert!(!codec_supports_bit_depth(Codec::QsvH264, 10));
747 assert!(!codec_supports_bit_depth(Codec::AmfH264, 10));
748 assert!(!codec_supports_bit_depth(Codec::VideoToolboxH264, 10));
749 assert!(!codec_supports_bit_depth(Codec::VaapiH264, 10));
750 }
751}