move back and make the context setup shared

(this is in preparation for making it possible to do HW encoding)
This commit is contained in:
Lily Tsuru 2024-09-09 22:34:56 -04:00
parent fa929641e5
commit 2da9b38974
5 changed files with 149 additions and 120 deletions

View file

@ -18,6 +18,8 @@ futures-util = { version = "0.3", default-features = false, features = ["sink",
# ffmpeg # ffmpeg
ffmpeg-the-third = "2.0.1" ffmpeg-the-third = "2.0.1"
# misc stuff
rand = "0.8.5" rand = "0.8.5"
serde = "1.0.209" serde = "1.0.209"
serde_json = "1.0.128" serde_json = "1.0.128"

View file

@ -5,7 +5,7 @@ use std::{
use tokio::sync::mpsc::{self, error::TryRecvError}; use tokio::sync::mpsc::{self, error::TryRecvError};
use super::ffmpeg; use super::ffmpeg;
use super::h264_encoder_sw::H264EncoderSW; use super::h264_encoder::H264Encoder;
pub enum EncodeThreadInput { pub enum EncodeThreadInput {
Init { size: crate::types::Size }, Init { size: crate::types::Size },
@ -40,7 +40,7 @@ fn encoder_thread_main(
) { ) {
let mut packet = ffmpeg::Packet::empty(); let mut packet = ffmpeg::Packet::empty();
let mut encoder: Option<H264EncoderSW> = None; let mut encoder: Option<H264Encoder> = None;
let mut sws = None; let mut sws = None;
let mut yuv_frame = None; let mut yuv_frame = None;
@ -73,8 +73,9 @@ fn encoder_thread_main(
.expect("Failed to create SWS conversion context"), .expect("Failed to create SWS conversion context"),
); );
// TODO: HW support!
encoder = Some( encoder = Some(
H264EncoderSW::new(size, 60, 3 * (1000 * 1000)) H264Encoder::new_software(size, 60, 3 * (1000 * 1000))
.expect("Failed to create encoder"), .expect("Failed to create encoder"),
); );
} }

View file

@ -0,0 +1,142 @@
use super::ffmpeg;
use anyhow::Context;
use ffmpeg::error::EAGAIN;
use ffmpeg::codec as lavc; // lavc
use crate::types::Size;
/// this is required for libx264 to like. Work
pub fn create_context_from_codec(codec: ffmpeg::Codec) -> Result<lavc::Context, ffmpeg::Error> {
unsafe {
let context = ffmpeg::sys::avcodec_alloc_context3(codec.as_ptr());
if context.is_null() {
return Err(ffmpeg::Error::Unknown);
}
let context = lavc::Context::wrap(context, None);
Ok(context)
}
}
fn create_context_and_set_common_parameters(
codec: &str,
size: Size,
max_framerate: u32,
bitrate: usize,
) -> anyhow::Result<(ffmpeg::Codec, ffmpeg::encoder::video::Video)> {
let encoder = ffmpeg::encoder::find_by_name(codec)
.expect(&format!("could not find the codec \"{codec}\""));
let mut video_encoder_context = create_context_from_codec(encoder)?.encoder().video()?;
// TODO: Either no GOP, or a fairly large one.
// idk
let gop = /*if max_framerate / 2 != 0 {
max_framerate / 2
} else {
max_framerate
} */
i32::MAX as u32;
video_encoder_context.set_width(size.width);
video_encoder_context.set_height(size.height);
video_encoder_context.set_frame_rate(Some(ffmpeg::Rational(1, max_framerate as i32)));
video_encoder_context.set_bit_rate(bitrate);
//video_encoder_context.set_max_bit_rate(bitrate);
// qp TODO:
//video_encoder_context.set_qmax(30);
//video_encoder_context.set_qmin(35);
video_encoder_context.set_time_base(ffmpeg::Rational(1, max_framerate as i32).invert());
video_encoder_context.set_format(ffmpeg::format::Pixel::YUV420P);
video_encoder_context.set_gop(gop);
video_encoder_context.set_max_b_frames(0);
unsafe {
(*video_encoder_context.as_mut_ptr()).delay = 0;
(*video_encoder_context.as_mut_ptr()).refs = 0;
}
Ok((encoder, video_encoder_context))
}
/// A simple H.264 encoder. Currently software only, however
/// pieces are being put in place to eventually allow HW encoding.
pub struct H264Encoder {
encoder: ffmpeg::encoder::video::Encoder,
}
impl H264Encoder {
pub fn new_software(size: Size, max_framerate: u32, bitrate: usize) -> anyhow::Result<Self> {
// Create the libx264 context
let (encoder, mut video_encoder_context) =
create_context_and_set_common_parameters("libx264", size, max_framerate, bitrate)?;
video_encoder_context.set_format(ffmpeg::format::Pixel::YUV420P);
let threads = std::thread::available_parallelism().expect("ggg").get() / 8;
// FIXME: tracing please.
println!("H264Encoder::new_software(): Using {threads} threads to encode");
// Frame-level threading causes [N] frames of latency
// so we use slice-level threading to reduce the latency
// as much as possible while still allowing threading
video_encoder_context.set_threading(ffmpeg::threading::Config {
kind: ffmpeg::threading::Type::Slice,
count: threads,
});
// Set libx264 applicable dictionary options
let mut dict = ffmpeg::Dictionary::new();
dict.set("tune", "zerolatency");
dict.set("preset", "veryfast");
// This could probably be moved but then it would mean returning the dictionary too
// which is fine I guess it just seems a bit rickity
dict.set("profile", "main");
// TODO:
dict.set("crf", "43");
dict.set("crf_max", "48");
let encoder = video_encoder_context
.open_as_with(encoder, dict)
.with_context(|| "While opening x264 video codec")?;
Ok(Self { encoder: encoder })
}
pub fn send_frame(&mut self, frame: &ffmpeg::Frame) {
self.encoder.send_frame(frame).unwrap();
}
pub fn send_eof(&mut self) {
self.encoder.send_eof().unwrap();
}
// Shuold this return a Result<ControlFlow> so we can make it easier to know when to continue?
pub fn receive_packet(&mut self, packet: &mut ffmpeg::Packet) -> anyhow::Result<()> {
loop {
match self.encoder.receive_packet(packet) {
Ok(_) => break,
Err(ffmpeg::Error::Other { errno }) => {
if errno != EAGAIN {
return Err(ffmpeg::Error::Other { errno: errno }.into());
} else {
// EAGAIN is not fatal, and simply means
// we should just try again
break;
}
}
Err(e) => return Err(e.into()),
}
}
Ok(())
}
}

View file

@ -1,116 +0,0 @@
use super::ffmpeg;
use anyhow::Context;
use ffmpeg::error::EAGAIN;
use ffmpeg::codec as lavc; // lavc
use crate::types::Size;
/// this is required for libx264 to like. Work
pub fn create_context_from_codec(codec: ffmpeg::Codec) -> Result<lavc::Context, ffmpeg::Error> {
unsafe {
let context = ffmpeg::sys::avcodec_alloc_context3(codec.as_ptr());
if context.is_null() {
return Err(ffmpeg::Error::Unknown);
}
let context = lavc::Context::wrap(context, None);
Ok(context)
}
}
/// A simple software H.264 encoder.
pub struct H264EncoderSW {
encoder: ffmpeg::encoder::video::Encoder,
}
impl H264EncoderSW {
pub fn new(size: Size, max_framerate: u32, bitrate: usize) -> anyhow::Result<Self> {
let encoder = ffmpeg::encoder::find(lavc::Id::H264).expect("could not find libx264");
let mut video_encoder_context = create_context_from_codec(encoder)?.encoder().video()?;
let gop = /*if max_framerate / 2 != 0 {
max_framerate / 2
} else {
max_framerate
} */
i32::MAX as u32;
video_encoder_context.set_width(size.width);
video_encoder_context.set_height(size.height);
video_encoder_context.set_frame_rate(Some(ffmpeg::Rational(1, max_framerate as i32)));
video_encoder_context.set_bit_rate(bitrate);
//video_encoder_context.set_max_bit_rate(bitrate);
// qp TODO:
//video_encoder_context.set_qmax(30);
//video_encoder_context.set_qmin(35);
video_encoder_context.set_time_base(ffmpeg::Rational(1, max_framerate as i32).invert());
video_encoder_context.set_format(ffmpeg::format::Pixel::YUV420P);
video_encoder_context.set_gop(gop);
video_encoder_context.set_max_b_frames(0);
unsafe {
(*video_encoder_context.as_mut_ptr()).delay = 0;
(*video_encoder_context.as_mut_ptr()).refs = 0;
}
let threads = 4;
println!("using {threads} threads to encode");
// frame-level threading causes [N] frames of latency
// so we use slice-level threading to reduce the latency
// as much as possible while still using it
video_encoder_context.set_threading(ffmpeg::threading::Config {
kind: ffmpeg::threading::Type::Slice,
count: threads,
});
let mut dict = ffmpeg::Dictionary::new();
dict.set("tune", "zerolatency");
dict.set("preset", "veryfast");
dict.set("profile", "main");
// TODO:
dict.set("crf", "43");
dict.set("crf_max", "48");
let encoder = video_encoder_context
.open_as_with(encoder, dict)
.with_context(|| "While opening x264 video codec")?;
Ok(Self { encoder: encoder })
}
pub fn send_frame(&mut self, frame: &ffmpeg::Frame) {
self.encoder.send_frame(frame).unwrap();
}
pub fn send_eof(&mut self) {
self.encoder.send_eof().unwrap();
}
// Shuold this return a Result<ControlFlow> so we can make it easier to know when to continue?
pub fn receive_packet(&mut self, packet: &mut ffmpeg::Packet) -> anyhow::Result<()> {
loop {
match self.encoder.receive_packet(packet) {
Ok(_) => break,
Err(ffmpeg::Error::Other { errno }) => {
if errno != EAGAIN {
return Err(ffmpeg::Error::Other { errno: errno }.into());
} else {
// EAGAIN is not fatal, and simply means
// we should just try again
break;
}
}
Err(e) => return Err(e.into()),
}
}
Ok(())
}
}

View file

@ -1,4 +1,4 @@
pub mod h264_encoder_sw; pub mod h264_encoder;
pub mod encoder_thread; pub mod encoder_thread;
pub use ffmpeg_the_third as ffmpeg; pub use ffmpeg_the_third as ffmpeg;