Skip to content

Commit

Permalink
Move encoders to av1an-encoders, redoing into factory pattern
Browse files Browse the repository at this point in the history
  • Loading branch information
master-of-zen committed Dec 30, 2024
1 parent 2943167 commit a0c92b9
Show file tree
Hide file tree
Showing 12 changed files with 1,045 additions and 6 deletions.
8 changes: 7 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/av1an-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ license = "GPL-3.0"
av1an-output = { path = "../av1an-output", version = "0.4.5" }
av1an-logging = { path = "../av1an-logging", version = "0.4.5" }
av1an-ffmpeg = { path = "../av1an-ffmpeg", version = "0.4.5" }
av1an-encoders = { path = "../av1an-encoders", version = "0.4.5" }
log = "0.4.14"
arrayvec = "0.7.2"
av-format = "0.7.0"
Expand Down
11 changes: 9 additions & 2 deletions crates/av1an-encoders/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
[package]
name = "av1an-encoders"
version = "0.1.0"
edition = "2024"
version.workspace = true
edition.workspace = true
authors.workspace = true

[dependencies]
thiserror = "1.0"
serde = { version = "1.0", features = ["derive"] }
tracing = { workspace = true }

[features]
default = []
168 changes: 168 additions & 0 deletions crates/av1an-encoders/src/aom.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
use crate::{
error::Error,
traits::{EncoderCapabilities, VideoEncoder},
};
use std::ffi::OsString;

const NULL: &str = if cfg!(windows) { "nul" } else { "/dev/null" };

#[derive(Default)]
pub struct AomEncoder;

impl VideoEncoder for AomEncoder {
fn name(&self) -> &'static str {
"aomenc"
}

fn format(&self) -> &'static str {
"av1"
}

fn output_extension(&self) -> &'static str {
"ivf"
}

fn binary_name(&self) -> &'static str {
"aomenc"
}

fn help_command(&self) -> [&'static str; 2] {
["aomenc", "--help"]
}

fn default_passes(&self) -> u8 {
2
}

fn get_default_arguments(&self, (cols, rows): (u32, u32)) -> Vec<String> {
let mut args = vec![
"--threads=8".into(),
"--cpu-used=6".into(),
"--end-usage=q".into(),
"--cq-level=30".into(),
];

if cols > 1 || rows > 1 {
let columns = (31 - cols.leading_zeros()) as usize;
let rows = (31 - rows.leading_zeros()) as usize;
args.extend_from_slice(&[
format!("--tile-columns={columns}"),
format!("--tile-rows={rows}"),
]);
}

args
}

fn compose_1_1_pass(
&self,
params: Vec<String>,
output: String,
_frame_count: usize,
) -> Vec<OsString> {
let mut cmd = vec!["aomenc".into(), "--passes=1".into()];
cmd.extend(params.into_iter().map(Into::into));
cmd.extend(["-o".into(), output.into(), "-".into()]);
cmd
}

fn compose_1_2_pass(
&self,
params: Vec<String>,
fpf: &str,
_frame_count: usize,
) -> Vec<OsString> {
let mut cmd =
vec!["aomenc".into(), "--passes=2".into(), "--pass=1".into()];
cmd.extend(params.into_iter().map(Into::into));
cmd.extend([
format!("--fpf={fpf}.log").into(),
"-o".into(),
NULL.into(),
"-".into(),
]);
cmd
}

fn compose_2_2_pass(
&self,
params: Vec<String>,
fpf: &str,
output: String,
_frame_count: usize,
) -> Vec<OsString> {
let mut cmd =
vec!["aomenc".into(), "--passes=2".into(), "--pass=2".into()];
cmd.extend(params.into_iter().map(Into::into));
cmd.extend([
format!("--fpf={fpf}.log").into(),
"-o".into(),
output.into(),
"-".into(),
]);
cmd
}

fn parse_encoded_frames(&self, s: &str) -> Option<u64> {
const PREFIX: &str = "Pass x/x frame x/";
if !s.starts_with(PREFIX) {
return None;
}

let after_prefix = s.get(PREFIX.len()..)?;
let first_digit_pos = after_prefix.find('/')?;
let first_space_pos =
after_prefix[first_digit_pos..].find(' ')? + first_digit_pos;

after_prefix
.get(first_digit_pos + 1..first_space_pos)?
.parse()
.ok()
}

fn get_format_bit_depth(&self, format: &str) -> Result<usize, Error> {
match format {
"yuv420p" | "yuv422p" | "yuv444p" | "gbrp" | "gray8" => Ok(8),
"yuv420p10le" | "yuv422p10le" | "yuv444p10le" | "gbrp10le"
| "gray10le" => Ok(10),
"yuv420p12le" | "yuv422p12le" | "yuv444p12le" | "gbrp12le"
| "gray12le" => Ok(12),
_ => Err(Error::UnsupportedFormat("aomenc".into(), format.into())),
}
}
}

impl EncoderCapabilities for AomEncoder {
fn supports_two_pass(&self) -> bool {
true
}

fn supports_constant_quality(&self) -> bool {
true
}

fn supports_bitrate(&self) -> bool {
true
}

fn supports_tile_parallel(&self) -> bool {
true
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_parse_encoded_frames() {
let encoder = AomEncoder::default();
assert_eq!(
encoder.parse_encoded_frames(
"Pass 1/2 frame 142/141 156465B 208875 us 679.83 fps"
),
Some(141)
);
assert_eq!(encoder.parse_encoded_frames("invalid"), None);
}
}
38 changes: 38 additions & 0 deletions crates/av1an-encoders/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use std::{fmt::Display, process::ExitStatus};
use thiserror::Error;

#[derive(Error, Debug)]
pub enum Error {
#[error("{0} does not support {1:?}")]
UnsupportedFormat(String, String),

#[error(
"Encoder crashed: {exit_status}\nstdout:\n{stdout}\nstderr:\n{stderr}"
)]
EncoderCrash {
exit_status: ExitStatus,
stdout: String,
stderr: String,
},

#[error("Invalid encoder parameters: {0}")]
InvalidParameters(String),

#[error("Encoder not found: {0}")]
EncoderNotFound(String),
}

#[derive(Debug, Clone)]
pub struct StringOrBytes {
pub inner: Vec<u8>,
}

impl Display for StringOrBytes {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Ok(s) = std::str::from_utf8(&self.inner) {
write!(f, "{}", s)
} else {
write!(f, "{:?}", self.inner)
}
}
}
47 changes: 47 additions & 0 deletions crates/av1an-encoders/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
pub mod error;
pub mod traits;

mod aom;
mod rav1e;
mod svt_av1;
mod x264;
mod x265;

use error::Error;
use std::str::FromStr;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Encoder {
Aom,
Rav1e,
SvtAv1,
X264,
X265,
}

impl FromStr for Encoder {
type Err = Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"aom" => Ok(Self::Aom),
"rav1e" => Ok(Self::Rav1e),
"svt-av1" => Ok(Self::SvtAv1),
"x264" => Ok(Self::X264),
"x265" => Ok(Self::X265),
_ => Err(Error::EncoderNotFound(s.to_string())),
}
}
}

impl Encoder {
pub fn get_instance(&self) -> Box<dyn crate::traits::VideoEncoder> {
match self {
Self::Aom => Box::new(aom::AomEncoder::default()),
Self::Rav1e => Box::new(rav1e::Rav1eEncoder::default()),
Self::SvtAv1 => Box::new(svt_av1::SvtAv1Encoder::default()),
Self::X264 => Box::new(x264::X264Encoder::default()),
Self::X265 => Box::new(x265::X265Encoder::default()),
}
}
}
3 changes: 0 additions & 3 deletions crates/av1an-encoders/src/main.rs

This file was deleted.

Loading

0 comments on commit a0c92b9

Please sign in to comment.