ffmpeg-rs-raw/src/encode.rs
2024-11-11 11:31:23 +00:00

242 lines
7.2 KiB
Rust

use std::collections::HashMap;
use std::{ptr, slice};
use crate::{bail_ffmpeg, get_ffmpeg_error_msg, options_to_dict};
use anyhow::{bail, Error};
use ffmpeg_sys_the_third::{
av_channel_layout_default, av_d2q, av_inv_q, av_packet_alloc, av_packet_free,
avcodec_alloc_context3, avcodec_find_encoder, avcodec_free_context,
avcodec_get_supported_config, avcodec_open2, avcodec_receive_packet, avcodec_send_frame,
AVChannelLayout, AVCodec, AVCodecConfig, AVCodecContext, AVCodecID, AVFrame, AVPacket,
AVPixelFormat, AVRational, AVSampleFormat, AVERROR, AVERROR_EOF,
};
use libc::EAGAIN;
pub struct Encoder {
ctx: *mut AVCodecContext,
codec: *const AVCodec,
dst_stream_index: Option<i32>,
}
impl Drop for Encoder {
fn drop(&mut self) {
unsafe {
if !self.ctx.is_null() {
avcodec_free_context(&mut self.ctx);
}
}
}
}
impl Encoder {
/// Create a new encoder with the specified codec
pub fn new(codec: AVCodecID) -> Result<Self, Error> {
unsafe {
let codec = avcodec_find_encoder(codec);
if codec.is_null() {
bail!("codec not found");
}
let ctx = avcodec_alloc_context3(codec);
if ctx.is_null() {
bail!("context allocation failed");
}
// set some defaults
(*ctx).time_base = AVRational { num: 1, den: 1 };
Ok(Self {
ctx,
codec,
dst_stream_index: None,
})
}
}
/// Get the codec
pub fn codec(&self) -> *const AVCodec {
self.codec
}
/// Get the codec context
pub fn codec_context(&self) -> *const AVCodecContext {
self.ctx
}
/// List supported configs (see [avcodec_get_supported_config])
pub unsafe fn list_configs<'a, T>(&mut self, cfg: AVCodecConfig) -> Result<&'a [T], Error> {
let mut dst = ptr::null_mut();
let mut num_dst = 0;
let ret = avcodec_get_supported_config(
self.ctx,
self.codec,
cfg,
0,
ptr::addr_of_mut!(dst) as _,
&mut num_dst,
);
bail_ffmpeg!(ret);
Ok(slice::from_raw_parts(dst, num_dst as usize))
}
/// Store the destination stream index along with the encoder
/// AVPacket's created by this encoder will have stream_index assigned to this value
pub unsafe fn with_stream_index(mut self, index: i32) -> Self {
self.dst_stream_index = Some(index);
self
}
/// Set the encoder bitrate
pub unsafe fn with_bitrate(self, bitrate: i64) -> Self {
(*self.ctx).bit_rate = bitrate;
self
}
/// Set the encoder sample rate (audio)
pub unsafe fn with_sample_rate(self, fmt: i32) -> Self {
(*self.ctx).sample_rate = fmt;
self
}
/// Set the encoder width in pixels
pub unsafe fn with_width(self, width: i32) -> Self {
(*self.ctx).width = width;
self
}
/// Set the encoder height in pixels
pub unsafe fn with_height(self, height: i32) -> Self {
(*self.ctx).height = height;
self
}
/// Set the encoder level (see AV_LEVEL_*)
pub unsafe fn with_level(self, level: i32) -> Self {
(*self.ctx).level = level;
self
}
/// Set the encoder profile (see AV_PROFILE_*)
pub unsafe fn with_profile(self, profile: i32) -> Self {
(*self.ctx).profile = profile;
self
}
/// Set the encoder framerate
pub unsafe fn with_framerate(self, fps: f32) -> Self {
let q = av_d2q(fps as f64, 90_000);
(*self.ctx).framerate = q;
(*self.ctx).time_base = av_inv_q(q);
self
}
/// Set the encoder pixel format
pub unsafe fn with_pix_fmt(self, fmt: AVPixelFormat) -> Self {
(*self.ctx).pix_fmt = fmt;
self
}
/// Set the encoder sample format (audio)
pub unsafe fn with_sample_format(self, fmt: AVSampleFormat) -> Self {
(*self.ctx).sample_fmt = fmt;
self
}
/// Set the encoder channel layout (audio)
pub unsafe fn with_channel_layout(self, layout: AVChannelLayout) -> Self {
(*self.ctx).ch_layout = layout;
self
}
/// Set the encoder channel layout using number of channels (audio)
pub unsafe fn with_default_channel_layout(self, channels: i32) -> Self {
let mut layout = AVChannelLayout::empty();
av_channel_layout_default(&mut layout, channels);
(*self.ctx).ch_layout = layout;
self
}
/// Apply options to context
pub unsafe fn with_options<F>(self, fx: F) -> Self
where
F: FnOnce(*mut AVCodecContext),
{
fx(self.ctx);
self
}
/// Open the encoder so that you can start encoding frames (see [avcodec_open2])
pub unsafe fn open(self, options: Option<HashMap<String, String>>) -> Result<Self, Error> {
assert!(!self.ctx.is_null());
let mut options = if let Some(options) = options {
options_to_dict(options)?
} else {
ptr::null_mut()
};
let ret = avcodec_open2(self.ctx, self.codec, &mut options);
bail_ffmpeg!(ret);
Ok(self)
}
/// Encode a frame, returning a number of [AVPacket]
pub unsafe fn encode_frame(
&mut self,
frame: *mut AVFrame,
) -> Result<Vec<*mut AVPacket>, Error> {
let mut pkgs = Vec::new();
let mut ret = avcodec_send_frame(self.ctx, frame);
if ret < 0 && ret != AVERROR(EAGAIN) {
bail!(get_ffmpeg_error_msg(ret));
}
while ret >= 0 || ret == AVERROR(EAGAIN) {
let mut pkt = av_packet_alloc();
ret = avcodec_receive_packet(self.ctx, pkt);
if ret != 0 {
av_packet_free(&mut pkt);
if ret == AVERROR(EAGAIN) || ret == AVERROR_EOF {
break;
}
bail!(get_ffmpeg_error_msg(ret));
}
(*pkt).time_base = (*self.ctx).time_base;
if let Some(idx) = self.dst_stream_index {
(*pkt).stream_index = idx;
}
pkgs.push(pkt);
}
Ok(pkgs)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::generate_test_frame;
use std::io::Write;
#[test]
fn test_encode_png() -> Result<(), Error> {
unsafe {
let frame = generate_test_frame();
let mut encoder = Encoder::new(AVCodecID::AV_CODEC_ID_PNG)?
.with_width((*frame).width)
.with_height((*frame).height);
let pix_fmts: &[AVPixelFormat] =
encoder.list_configs(AVCodecConfig::AV_CODEC_CONFIG_PIX_FORMAT)?;
encoder = encoder.with_pix_fmt(pix_fmts[0]).open(None)?;
std::fs::create_dir_all("test_output")?;
let mut test_file = std::fs::File::create("test_output/test.png")?;
let mut pkts = encoder.encode_frame(frame)?;
let flush_pkts = encoder.encode_frame(ptr::null_mut())?;
pkts.extend(flush_pkts);
for pkt in pkts {
test_file.write(slice::from_raw_parts((*pkt).data, (*pkt).size as usize))?;
}
}
Ok(())
}
}