1#[doc(hidden)]
20pub mod bitwriter;
21mod aq;
23#[doc(hidden)]
24pub mod cabac;
25pub mod ctu;
26pub mod hrd;
27pub mod inter;
28pub mod intra;
29pub mod loopfilter;
30pub mod pyramid;
31#[cfg(test)]
33mod ccp_streams;
34#[doc(hidden)]
35pub mod nal;
36#[cfg(test)]
37mod palette_streams;
38pub mod pcm;
39pub mod rate;
40#[cfg(test)]
41mod rdpcm_streams;
42#[cfg(test)]
43mod scc_streams;
44#[doc(hidden)]
46pub mod residual;
47
48#[derive(Debug)]
50enum EncodeMode {
51 Pcm,
54 Intra {
60 qp: i32,
62 tree: Option<ctu::TreeCfg>,
65 ctu_rc: bool,
67 lf: loopfilter::LoopFilterCfg,
69 rc: Option<rate::RateController>,
71 aq: u8,
74 timing: Option<(u32, u32)>,
76 hrd: Option<(hrd::HrdSignalCfg, hrd::HrdClock)>,
80 },
81 Inter(inter::LowDelayPEncoder),
86 Pyramid {
91 enc: pyramid::PyramidEncoder,
93 delay: i64,
95 pts_by_display: Vec<Option<i64>>,
98 decode_count: i64,
100 },
101}
102
103use std::collections::VecDeque;
104
105use oxideav_core::{
106 CodecId, CodecParameters, Encoder, Error, Frame, Packet, PixelFormat, Result, TimeBase,
107};
108
109pub struct H265Encoder {
179 codec_id: CodecId,
180 output_params: CodecParameters,
181 width: usize,
182 height: usize,
183 mode: EncodeMode,
184 ready: VecDeque<Packet>,
185 frame_index: i64,
186 time_base: TimeBase,
189}
190
191pub type H265PcmEncoder = H265Encoder;
194
195impl std::fmt::Debug for H265Encoder {
196 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
197 f.debug_struct("H265Encoder")
198 .field("width", &self.width)
199 .field("height", &self.height)
200 .field("mode", &self.mode)
201 .field("ready", &self.ready.len())
202 .finish()
203 }
204}
205
206pub fn make_encoder(params: &CodecParameters) -> Result<Box<dyn Encoder>> {
223 let width = params
224 .width
225 .ok_or_else(|| Error::InvalidData("h265 encode: width is required".into()))?
226 as usize;
227 let height = params
228 .height
229 .ok_or_else(|| Error::InvalidData("h265 encode: height is required".into()))?
230 as usize;
231 if width == 0 || height == 0 || width % 16 != 0 || height % 16 != 0 {
232 return Err(Error::InvalidData(format!(
233 "h265 encode: dimensions must be nonzero multiples of 16, got {width}x{height}"
234 )));
235 }
236 if let Some(pf) = params.pixel_format {
237 if pf != PixelFormat::Yuv420P {
238 return Err(Error::InvalidData(format!(
239 "h265 encode: only yuv420p input is supported, got {pf:?}"
240 )));
241 }
242 }
243 let parse_qp = |params: &CodecParameters| -> Result<i32> {
244 match params.options.get("qp") {
245 None => Ok(26),
246 Some(v) => v
247 .parse::<i32>()
248 .ok()
249 .filter(|q| (0..=51).contains(q))
250 .ok_or_else(|| {
251 Error::InvalidData(format!("h265 encode: qp must be 0..=51, got {v:?}"))
252 }),
253 }
254 };
255 let parse_flag = |params: &CodecParameters, key: &str| -> Result<bool> {
256 match params.options.get(key) {
257 None | Some("0") | Some("false") => Ok(false),
258 Some("1") | Some("true") => Ok(true),
259 Some(v) => Err(Error::InvalidData(format!(
260 "h265 encode: {key} must be 0/1/true/false, got {v:?}"
261 ))),
262 }
263 };
264 let parse_lf = |params: &CodecParameters| -> Result<loopfilter::LoopFilterCfg> {
265 let sao = parse_flag(params, "sao")?;
266 Ok(loopfilter::LoopFilterCfg {
267 deblocking: parse_flag(params, "deblock")?,
268 sao_luma: sao,
269 sao_chroma: sao,
270 })
271 };
272 let parse_bitrate = |params: &CodecParameters| -> Result<Option<u64>> {
274 let Some(v) = params.options.get("bitrate") else {
275 return Ok(None);
276 };
277 let (digits, mult) = match v.as_bytes().last() {
278 Some(b'k' | b'K') => (&v[..v.len() - 1], 1_000u64),
279 Some(b'M') => (&v[..v.len() - 1], 1_000_000),
280 _ => (v, 1),
281 };
282 digits
283 .parse::<u64>()
284 .ok()
285 .map(|n| n.saturating_mul(mult))
286 .filter(|&b| b >= 1_000)
287 .map(Some)
288 .ok_or_else(|| {
289 Error::InvalidData(format!(
290 "h265 encode: bitrate must be an integer >= 1000 bits/s \
291 (optional k/M suffix), got {v:?}"
292 ))
293 })
294 };
295 let parse_fps = |params: &CodecParameters| -> Result<(u32, u32)> {
297 let Some(v) = params.options.get("fps") else {
298 return Ok((25, 1));
299 };
300 let parsed = match v.split_once('/') {
301 None => v.parse::<u32>().ok().map(|n| (n, 1)),
302 Some((n, d)) => n.parse::<u32>().ok().zip(d.parse::<u32>().ok()),
303 };
304 parsed.filter(|&(n, d)| n > 0 && d > 0).ok_or_else(|| {
305 Error::InvalidData(format!(
306 "h265 encode: fps must be a positive integer or num/den ratio, got {v:?}"
307 ))
308 })
309 };
310 let parse_aq = |params: &CodecParameters| -> Result<u8> {
312 match params.options.get("aq") {
313 None => Ok(0),
314 Some(v) => v.parse::<u8>().ok().filter(|&a| a <= 3).ok_or_else(|| {
315 Error::InvalidData(format!("h265 encode: aq must be 0..=3, got {v:?}"))
316 }),
317 }
318 };
319 let parse_ctb = |params: &CodecParameters| -> Result<Option<ctu::TreeCfg>> {
323 match params.options.get("ctb") {
324 None => Ok(None),
325 Some(v) => v
326 .parse::<usize>()
327 .ok()
328 .and_then(ctu::TreeCfg::new)
329 .map(Some)
330 .ok_or_else(|| {
331 Error::InvalidData(format!("h265 encode: ctb must be 16, 32 or 64, got {v:?}"))
332 }),
333 }
334 };
335 let tree = parse_ctb(params)?;
336 let ctu_rc = parse_flag(params, "cturc")?;
338 if ctu_rc && (params.options.get("bitrate").is_none() || tree.is_none()) {
339 return Err(Error::InvalidData(
340 "h265 encode: cturc requires the bitrate and ctb options".into(),
341 ));
342 }
343 let tmvp = parse_flag(params, "tmvp")?;
345 let refs = match params.options.get("refs") {
347 None => None,
348 Some(v) => Some(
349 v.parse::<usize>()
350 .ok()
351 .filter(|n| (1..=4).contains(n))
352 .ok_or_else(|| {
353 Error::InvalidData(format!("h265 encode: refs must be 1..=4, got {v:?}"))
354 })?,
355 ),
356 };
357 if (tmvp || refs.is_some()) && params.options.get("mode") != Some("inter") {
358 return Err(Error::InvalidData(
359 "h265 encode: the tmvp / refs options require mode \"inter\"".into(),
360 ));
361 }
362 let bitrate = parse_bitrate(params)?;
363 let bufsize = match params.options.get("bufsize") {
366 None => None,
367 Some(v) => {
368 if bitrate.is_none() {
369 return Err(Error::InvalidData(
370 "h265 encode: bufsize requires the bitrate option".into(),
371 ));
372 }
373 let (digits, mult) = match v.as_bytes().last() {
374 Some(b'k' | b'K') => (&v[..v.len() - 1], 1_000u64),
375 Some(b'M') => (&v[..v.len() - 1], 1_000_000),
376 _ => (v, 1),
377 };
378 Some(
379 digits
380 .parse::<u64>()
381 .ok()
382 .map(|n| n.saturating_mul(mult))
383 .filter(|&b| b >= 1_000)
384 .ok_or_else(|| {
385 Error::InvalidData(format!(
386 "h265 encode: bufsize must be an integer >= 1000 bits (optional k/M suffix), got {v:?}"
387 ))
388 })?,
389 )
390 }
391 };
392 let fps = parse_fps(params)?;
393 let fps_declared = params.options.get("fps").is_some();
396 let hrd_on = parse_flag(params, "hrd")?;
399 if hrd_on && (bitrate.is_none() || bufsize.is_none() || !fps_declared) {
400 return Err(Error::InvalidData(
401 "h265 encode: hrd requires the bitrate, bufsize and fps options".into(),
402 ));
403 }
404 let cbr_on = parse_flag(params, "cbr")?;
406 if cbr_on && !hrd_on {
407 return Err(Error::InvalidData(
408 "h265 encode: cbr requires the hrd option".into(),
409 ));
410 }
411 if params.options.get("pyramidstep").is_some() && params.options.get("pyramid").is_none() {
412 return Err(Error::InvalidData(
413 "h265 encode: pyramidstep requires the pyramid option".into(),
414 ));
415 }
416 let adaptive_gop = parse_flag(params, "adaptivegop")?;
418 if adaptive_gop && params.options.get("pyramid").is_none() {
419 return Err(Error::InvalidData(
420 "h265 encode: adaptivegop requires the pyramid option".into(),
421 ));
422 }
423 let rc_cfg = |params: &CodecParameters, qp: i32| -> rate::RateControlCfg {
426 let mut cfg = rate::RateControlCfg::new(bitrate.unwrap_or(0), fps.0, fps.1);
427 if params.options.get("qp").is_some() {
428 cfg.initial_qp = Some(qp);
429 }
430 cfg.vbv_buffer_bits = bufsize;
431 cfg
432 };
433 let mode = match params.options.get("mode") {
434 None | Some("pcm") => {
435 if parse_lf(params)?.any() {
436 return Err(Error::InvalidData(
437 "h265 encode: deblock/sao options require mode \"intra\" or \"inter\"".into(),
438 ));
439 }
440 if parse_flag(params, "amp")? {
441 return Err(Error::InvalidData(
442 "h265 encode: the amp option requires mode \"inter\"".into(),
443 ));
444 }
445 if bitrate.is_some() {
446 return Err(Error::InvalidData(
447 "h265 encode: the bitrate option requires mode \"intra\" or \"inter\" \
448 (PCM coding is lossless fixed-rate)"
449 .into(),
450 ));
451 }
452 if parse_aq(params)? != 0 {
453 return Err(Error::InvalidData(
454 "h265 encode: the aq option requires mode \"intra\"".into(),
455 ));
456 }
457 if tree.is_some() {
458 return Err(Error::InvalidData(
459 "h265 encode: the ctb option requires mode \"intra\" or \"inter\"".into(),
460 ));
461 }
462 EncodeMode::Pcm
463 }
464 Some("intra") => {
465 if parse_flag(params, "amp")? {
466 return Err(Error::InvalidData(
467 "h265 encode: the amp option requires mode \"inter\"".into(),
468 ));
469 }
470 let qp = parse_qp(params)?;
471 EncodeMode::Intra {
472 qp,
473 tree,
474 ctu_rc,
475 lf: parse_lf(params)?,
476 rc: bitrate.map(|_| rate::RateController::new(&rc_cfg(params, qp), width, height)),
477 aq: parse_aq(params)?,
478 timing: fps_declared.then_some((fps.1, fps.0)),
479 hrd: match hrd_on {
480 false => None,
481 true => {
482 let signal = hrd::HrdSignalCfg::for_rate(
483 bitrate.unwrap_or(64),
484 bufsize.unwrap_or(16),
485 )
486 .with_cbr(cbr_on);
487 if cbr_on {
488 let tick_bits = signal.bit_rate.saturating_mul(u64::from(fps.1))
489 / u64::from(fps.0.max(1));
490 if signal.cpb_size < tick_bits.saturating_mul(2) {
491 return Err(Error::InvalidData(format!(
492 "h265 encode: {}",
493 intra::IntraEncodeError::CbrCpbTooSmall
494 )));
495 }
496 }
497 Some((signal, hrd::HrdClock::new(signal, fps.0, fps.1)))
498 }
499 },
500 }
501 }
502 Some("inter") => {
503 let qp = parse_qp(params)?;
504 if let Some(v) = params.options.get("pyramid") {
505 if params.options.get("gop").is_some() || params.options.get("bslices").is_some() {
506 return Err(Error::InvalidData(
507 "h265 encode: pyramid excludes the gop / bslices options".into(),
508 ));
509 }
510 let g = v
511 .parse::<usize>()
512 .ok()
513 .filter(|g| (2..=16).contains(g))
514 .ok_or_else(|| {
515 Error::InvalidData(format!(
516 "h265 encode: pyramid must be in 2..=16, got {v:?}"
517 ))
518 })?;
519 let step = match params.options.get("pyramidstep") {
523 None => 1i32,
524 Some(v) => v
525 .parse::<i32>()
526 .ok()
527 .filter(|s| (0..=6).contains(s))
528 .ok_or_else(|| {
529 Error::InvalidData(format!(
530 "h265 encode: pyramidstep must be 0..=6, got {v:?}"
531 ))
532 })?,
533 };
534 let mut enc = pyramid::PyramidEncoder::new(width, height, qp, g)
535 .map_err(|e| Error::InvalidData(format!("h265 encode: {e}")))?
536 .with_layer_qp_step(step)
537 .with_amp(parse_flag(params, "amp")?)
538 .with_loop_filters(parse_lf(params)?)
539 .with_aq(parse_aq(params)?);
540 if let Some(t) = tree {
541 enc = enc.with_tree(t);
542 }
543 if let Some(n) = refs {
544 enc = enc.with_refs(n);
545 }
546 enc = enc
547 .with_temporal_mvp(tmvp)
548 .with_adaptive_gop(adaptive_gop)
549 .with_ctu_rate_control(ctu_rc);
550 if fps_declared {
551 enc = enc.with_frame_rate(fps.0, fps.1);
552 }
553 if bitrate.is_some() {
554 enc = enc.with_rate_control(&rc_cfg(params, qp));
555 }
556 enc = enc.with_hrd(hrd_on).with_cbr(cbr_on);
557 let delay = i64::from(enc.reorder_delay());
558 EncodeMode::Pyramid {
559 enc,
560 delay,
561 pts_by_display: Vec::new(),
562 decode_count: 0,
563 }
564 } else {
565 let gop = match params.options.get("gop") {
566 None => 0usize,
567 Some(v) => v.parse::<usize>().map_err(|_| {
568 Error::InvalidData(format!(
569 "h265 encode: gop must be a non-negative integer, got {v:?}"
570 ))
571 })?,
572 };
573 let b_slices = parse_flag(params, "bslices")?;
574 let mut enc = inter::LowDelayPEncoder::new(width, height, qp, gop)
575 .map_err(|e| Error::InvalidData(format!("h265 encode: {e}")))?
576 .with_b_slices(b_slices)
577 .with_amp(parse_flag(params, "amp")?)
578 .with_loop_filters(parse_lf(params)?)
579 .with_aq(parse_aq(params)?);
580 if let Some(t) = tree {
581 enc = enc.with_tree(t);
582 }
583 if let Some(n) = refs {
584 enc = enc.with_refs(n);
585 }
586 enc = enc.with_temporal_mvp(tmvp).with_ctu_rate_control(ctu_rc);
587 if fps_declared {
588 enc = enc.with_frame_rate(fps.0, fps.1);
589 }
590 if bitrate.is_some() {
591 enc = enc.with_rate_control(&rc_cfg(params, qp));
592 }
593 enc = enc.with_hrd(hrd_on).with_cbr(cbr_on);
594 EncodeMode::Inter(enc)
595 }
596 }
597 Some(other) => {
598 return Err(Error::InvalidData(format!(
599 "h265 encode: unknown mode {other:?} (expected \"pcm\", \"intra\" or \"inter\")"
600 )))
601 }
602 };
603 let mut output_params = params.clone();
604 output_params.media_type = oxideav_core::MediaType::Video;
605 output_params.pixel_format = Some(PixelFormat::Yuv420P);
606 output_params.extradata.clear();
608 Ok(Box::new(H265Encoder {
609 codec_id: params.codec_id.clone(),
610 output_params,
611 width,
612 height,
613 mode,
614 ready: VecDeque::new(),
615 frame_index: 0,
616 time_base: TimeBase::new(i64::from(fps.1), i64::from(fps.0)),
617 }))
618}
619
620impl Encoder for H265Encoder {
621 fn codec_id(&self) -> &CodecId {
622 &self.codec_id
623 }
624
625 fn output_params(&self) -> &CodecParameters {
626 &self.output_params
627 }
628
629 fn send_frame(&mut self, frame: &Frame) -> Result<()> {
630 let v = match frame {
631 Frame::Video(v) => v,
632 _ => return Err(Error::InvalidData("h265 encode: video frames only".into())),
633 };
634 if v.planes.len() != 3 {
635 return Err(Error::InvalidData(format!(
636 "h265 encode: expected 3 planes (yuv420p), got {}",
637 v.planes.len()
638 )));
639 }
640 let pack = |idx: usize, w: usize, h: usize| -> Result<Vec<u8>> {
642 let plane = &v.planes[idx];
643 if plane.stride < w || plane.data.len() < plane.stride * h {
644 return Err(Error::InvalidData(format!(
645 "h265 encode: plane {idx} too small (stride {}, len {})",
646 plane.stride,
647 plane.data.len()
648 )));
649 }
650 let mut out = Vec::with_capacity(w * h);
651 for row in 0..h {
652 out.extend_from_slice(&plane.data[row * plane.stride..row * plane.stride + w]);
653 }
654 Ok(out)
655 };
656 let y = pack(0, self.width, self.height)?;
657 let cb = pack(1, self.width / 2, self.height / 2)?;
658 let cr = pack(2, self.width / 2, self.height / 2)?;
659
660 let (au, keyframe) = match &mut self.mode {
661 EncodeMode::Pcm => (
662 pcm::encode_idr_pcm_au(&y, &cb, &cr, self.width, self.height)
663 .map_err(|e| Error::InvalidData(format!("h265 encode: {e}")))?,
664 true,
665 ),
666 EncodeMode::Intra {
667 qp,
668 tree,
669 ctu_rc,
670 lf,
671 rc,
672 aq,
673 timing,
674 hrd,
675 } => {
676 let mut frame_qp = match rc {
677 Some(rc) => rc.pick_qp(rate::FrameClass::Intra),
678 None => *qp,
679 };
680 let budget = if *ctu_rc && tree.is_some() {
681 rc.as_ref().and_then(|r| r.last_budget_bits())
682 } else {
683 None
684 };
685 let cfg = intra::SpsCfg {
686 cu_qp_delta: *aq > 0 || budget.is_some(),
687 timing: *timing,
688 hrd: hrd.as_ref().map(|(signal, _)| *signal),
689 min_cb_log2: if tree.is_some() { 3 } else { 4 },
690 tree: *tree,
691 ..intra::SpsCfg::legacy(1)
692 };
693 let sei = hrd.as_mut().map(|(_, clock)| {
698 let (delay, offset) = clock.begin_buffering_period();
699 let payloads = [
700 (
701 hrd::SEI_BUFFERING_PERIOD,
702 hrd::buffering_period_payload(delay, offset),
703 ),
704 (
705 hrd::SEI_PIC_TIMING,
706 hrd::pic_timing_payload(clock.au_cpb_removal_delay_minus1(), 0),
707 ),
708 ];
709 let mut framed = vec![0, 0, 0, 1];
710 framed.extend(hrd::sei_prefix_nal(&payloads));
711 framed
712 });
713 let sei_bits = sei.as_ref().map_or(0, |s| s.len() as u64 * 8);
714 let code = |frame_qp: i32| -> Result<Vec<u8>> {
715 Ok(intra::encode_idr_intra_au_full(
716 &y,
717 &cb,
718 &cr,
719 self.width,
720 self.height,
721 frame_qp,
722 &cfg,
723 lf,
724 *aq,
725 budget,
726 )
727 .map_err(|e| Error::InvalidData(format!("h265 encode: {e}")))?
728 .au)
729 };
730 let mut au = code(frame_qp)?;
731 let cbr = hrd.as_ref().is_some_and(|(signal, _)| signal.cbr);
736 let cap = match (
737 rc.as_ref().and_then(|r| r.vbv_frame_cap()),
738 hrd.as_ref().map(|(_, clock)| clock.frame_cap()),
739 ) {
740 (Some(v), Some(h)) => Some(v.min(h)),
741 (v, h) => v.or(h),
742 }
743 .map(|c| if cbr { c.saturating_sub(56) } else { c });
745 if let Some(cap) = cap {
746 let ceiling = rc.as_ref().map_or(51, |r| r.max_qp());
747 while au.len() as u64 * 8 + sei_bits > cap && frame_qp < ceiling {
748 frame_qp = (frame_qp + 3).min(ceiling);
749 au = code(frame_qp)?;
750 }
751 }
752 if let Some(sei) = &sei {
753 hrd::splice_sei_before_vcl(&mut au, sei);
754 }
755 if let Some((_, clock)) = &*hrd {
756 let pad_bits = clock.cbr_filler_bits(au.len() as u64 * 8);
758 if pad_bits > 0 {
759 au.extend(hrd::filler_data_nal_framed((pad_bits as usize).div_ceil(8)));
760 }
761 }
762 if let Some(rc) = rc {
763 rc.update(rate::FrameClass::Intra, frame_qp, au.len() as u64 * 8);
764 }
765 if let Some((_, clock)) = hrd {
766 clock.push_au(au.len() as u64 * 8);
767 }
768 (au, true)
769 }
770 EncodeMode::Inter(enc) => {
771 let f = enc
772 .encode_frame(&inter::YuvFrame {
773 y: &y,
774 cb: &cb,
775 cr: &cr,
776 })
777 .map_err(|e| Error::InvalidData(format!("h265 encode: {e}")))?;
778 (f.au, f.keyframe)
779 }
780 EncodeMode::Pyramid {
781 enc,
782 delay,
783 pts_by_display,
784 decode_count,
785 } => {
786 pts_by_display.push(v.pts);
787 let aus = enc
788 .encode_frame(&inter::YuvFrame {
789 y: &y,
790 cb: &cb,
791 cr: &cr,
792 })
793 .map_err(|e| Error::InvalidData(format!("h265 encode: {e}")))?;
794 self.frame_index += 1;
795 let (delay, decode_count) = (*delay, decode_count);
796 let time_base = self.time_base;
797 let mut packets =
798 Self::pyramid_packets(aus, pts_by_display, delay, decode_count, time_base);
799 for pkt in packets.drain(..) {
800 self.ready.push_back(pkt);
801 }
802 return Ok(());
803 }
804 };
805 let mut pkt = Packet::new(0, self.time_base, au);
806 pkt.pts = v.pts.or(Some(self.frame_index));
807 pkt.dts = pkt.pts;
808 pkt.flags.keyframe = keyframe;
809 self.frame_index += 1;
810 self.ready.push_back(pkt);
811 Ok(())
812 }
813
814 fn receive_packet(&mut self) -> Result<Packet> {
815 self.ready.pop_front().ok_or(Error::NeedMore)
816 }
817
818 fn flush(&mut self) -> Result<()> {
819 if let EncodeMode::Pyramid {
821 enc,
822 delay,
823 pts_by_display,
824 decode_count,
825 } = &mut self.mode
826 {
827 let aus = enc.flush();
828 let (delay, decode_count) = (*delay, decode_count);
829 let time_base = self.time_base;
830 let mut packets =
831 Self::pyramid_packets(aus, pts_by_display, delay, decode_count, time_base);
832 for pkt in packets.drain(..) {
833 self.ready.push_back(pkt);
834 }
835 }
836 Ok(())
837 }
838}
839
840impl H265Encoder {
841 fn pyramid_packets(
846 aus: Vec<pyramid::PyramidAu>,
847 pts_by_display: &[Option<i64>],
848 delay: i64,
849 decode_count: &mut i64,
850 time_base: TimeBase,
851 ) -> Vec<Packet> {
852 aus.into_iter()
853 .map(|au| {
854 let mut pkt = Packet::new(0, time_base, au.au);
855 pkt.pts = Some(
856 pts_by_display
857 .get(au.display_order)
858 .copied()
859 .flatten()
860 .unwrap_or(au.display_order as i64),
861 );
862 pkt.dts = Some(*decode_count - delay);
863 *decode_count += 1;
864 pkt.flags.keyframe = au.keyframe;
865 pkt
866 })
867 .collect()
868 }
869}