mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-24 10:09:32 -06:00
M13.4: hevcenc / av1enc crate skeletons (API contract, layers)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
f713eb9bfd
commit
3a8b401e81
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "effectcraft-hevcenc"
|
||||
description = "Clean-room pure-Rust HEVC (H.265) encoder (Main / Main 10, I and P slices, CABAC) for MP4 export, from ITU-T H.265"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
|
||||
[dev-dependencies]
|
||||
filmcraft-hevc = { workspace = true }
|
||||
@@ -0,0 +1,128 @@
|
||||
//! Clean-room pure-Rust HEVC (H.265) encoder — API skeleton (implementation in progress).
|
||||
//!
|
||||
//! The public API below is the contract `crates/export` codes against; keep it stable.
|
||||
|
||||
/// Profile written in the parameter sets.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub enum Profile {
|
||||
/// Main: 8-bit 4:2:0.
|
||||
#[default]
|
||||
Main,
|
||||
/// Main 10: 10-bit 4:2:0.
|
||||
Main10,
|
||||
}
|
||||
|
||||
impl Profile {
|
||||
pub fn bit_depth(self) -> u8 {
|
||||
match self {
|
||||
Profile::Main => 8,
|
||||
Profile::Main10 => 10,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Rate control.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum RateControl {
|
||||
/// Constant QP (0–51; lower is better).
|
||||
ConstantQp(u8),
|
||||
/// Average bitrate target in kbit/s (frame-level QP adaptation).
|
||||
Bitrate { kbps: u32 },
|
||||
}
|
||||
|
||||
/// Encoder settings.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct EncoderConfig {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
/// Frame rate numerator / denominator (VUI timing and rate control).
|
||||
pub fps_num: u32,
|
||||
pub fps_den: u32,
|
||||
pub profile: Profile,
|
||||
/// `general_level_idc` (30 × level, e.g. 93 = level 3.1); `None` picks the lowest level that fits.
|
||||
pub level_idc: Option<u8>,
|
||||
pub rate: RateControl,
|
||||
/// Frames between IDR pictures (1 = all intra).
|
||||
pub keyint: u32,
|
||||
/// BT.709 limited range is signalled in the VUI unless this is set.
|
||||
pub full_range: bool,
|
||||
}
|
||||
|
||||
impl EncoderConfig {
|
||||
pub fn new(width: u32, height: u32, fps_num: u32, fps_den: u32) -> Self {
|
||||
EncoderConfig {
|
||||
width,
|
||||
height,
|
||||
fps_num,
|
||||
fps_den,
|
||||
profile: Profile::Main,
|
||||
level_idc: None,
|
||||
rate: RateControl::ConstantQp(28),
|
||||
keyint: 60,
|
||||
full_range: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// One input picture, 4:2:0, samples at the profile's bit depth (8-bit values 0–255 for Main).
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Frame<'a> {
|
||||
pub y: &'a [u16],
|
||||
pub u: &'a [u16],
|
||||
pub v: &'a [u16],
|
||||
pub y_stride: usize,
|
||||
pub uv_stride: usize,
|
||||
}
|
||||
|
||||
/// One coded picture (an access unit): NAL units, each prefixed by a 4-byte big-endian length
|
||||
/// (ISO/IEC 14496-15 sample format; parameter sets are only in [`Encoder::hvcc`]).
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Packet {
|
||||
pub data: Vec<u8>,
|
||||
/// IDR picture.
|
||||
pub keyframe: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum Error {
|
||||
InvalidConfig(String),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Error::InvalidConfig(s) => write!(f, "invalid HEVC encoder config: {s}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
/// HEVC encoder. Pictures come out in input order (no reordering: I and P only).
|
||||
pub struct Encoder {
|
||||
cfg: EncoderConfig,
|
||||
}
|
||||
|
||||
impl Encoder {
|
||||
pub fn new(cfg: EncoderConfig) -> Result<Encoder, Error> {
|
||||
if cfg.width == 0 || cfg.height == 0 {
|
||||
return Err(Error::InvalidConfig("empty frame".into()));
|
||||
}
|
||||
Ok(Encoder { cfg })
|
||||
}
|
||||
pub fn config(&self) -> &EncoderConfig {
|
||||
&self.cfg
|
||||
}
|
||||
/// VPS, SPS, PPS NAL units (without start codes or length prefixes).
|
||||
pub fn parameter_sets(&self) -> [Vec<u8>; 3] {
|
||||
[vec![], vec![], vec![]]
|
||||
}
|
||||
/// HEVCDecoderConfigurationRecord (`hvcC` payload) with the parameter sets.
|
||||
pub fn hvcc(&self) -> Vec<u8> {
|
||||
vec![]
|
||||
}
|
||||
/// Encodes one picture.
|
||||
pub fn encode(&mut self, _frame: &Frame) -> Packet {
|
||||
Packet { data: vec![], keyframe: true }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user