From 5516fb7c6685a1d6854892795a31cd0313574e7e Mon Sep 17 00:00:00 2001 From: PolyMeilex Date: Wed, 25 Sep 2024 22:32:07 +0200 Subject: [PATCH] mpeg_encoder: Start cleanup of the mpeg code --- Cargo.lock | 22 +- mpeg_encoder/Cargo.toml | 2 +- mpeg_encoder/src/lib.rs | 663 ++++++++++++++++++-------------------- neothesia-cli/src/main.rs | 4 +- 4 files changed, 331 insertions(+), 360 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 61217fd7..808e5d69 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -417,22 +417,22 @@ dependencies = [ [[package]] name = "bindgen" -version = "0.64.0" +version = "0.69.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4" +checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", "cexpr", "clang-sys", + "itertools 0.10.5", "lazy_static", "lazycell", - "peeking_take_while", "proc-macro2", "quote", "regex", "rustc-hash 1.1.0", "shlex", - "syn 1.0.109", + "syn 2.0.77", ] [[package]] @@ -1243,11 +1243,11 @@ dependencies = [ [[package]] name = "ffmpeg-sys-next" -version = "6.1.0" +version = "7.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2529ad916d08c3562c754c21bc9b17a26c7882c0f5706cc2cd69472175f1620" +checksum = "dc33ac996bad6d164a08069a1e9b3370045860dba3d315acda09420f1febef9e" dependencies = [ - "bindgen 0.64.0", + "bindgen 0.69.4", "cc", "libc", "num_cpus", @@ -2848,12 +2848,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" -[[package]] -name = "peeking_take_while" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" - [[package]] name = "percent-encoding" version = "2.3.1" diff --git a/mpeg_encoder/Cargo.toml b/mpeg_encoder/Cargo.toml index 8c7f6649..37d6233b 100644 --- a/mpeg_encoder/Cargo.toml +++ b/mpeg_encoder/Cargo.toml @@ -16,6 +16,6 @@ build=["ffmpeg-sys/build"] [dependencies.ffmpeg-sys] package = "ffmpeg-sys-next" -version = "6.0.1" +version = "7.0.3" default-features = false features = [ "avformat", "swscale" ] diff --git a/mpeg_encoder/src/lib.rs b/mpeg_encoder/src/lib.rs index bad6ffed..83f9bdec 100644 --- a/mpeg_encoder/src/lib.rs +++ b/mpeg_encoder/src/lib.rs @@ -7,12 +7,10 @@ use ffmpeg_sys::{ AVCodec, AVCodecContext, AVCodecID, AVFormatContext, AVFrame, AVPacket, AVPixelFormat, AVRational, AVStream, SwsContext, }; -use std::ffi::CString; -use std::iter; -use std::iter::FromIterator; +use std::ffi::{CStr, CString}; use std::mem; -use std::path::{Path, PathBuf}; -use std::ptr; +use std::path::Path; +use std::ptr::{self, NonNull}; #[derive(PartialEq)] enum ColorFormat { @@ -33,93 +31,278 @@ impl From<&ColorFormat> for AVPixelFormat { } } +/// Initializes the recorder if needed. +#[allow(clippy::too_many_arguments)] +fn init_context( + path_str: &CStr, + format_context: &NonNull, + video_st: &NonNull, + time_base: AVRational, + gop_size: i32, + max_b_frames: i32, + pix_fmt: AVPixelFormat, + crf: Option, + preset: Option<&str>, + target_width: usize, + target_height: usize, +) -> NonNull { + unsafe { + let video_codec = (*(*format_context.as_ptr()).oformat).video_codec; + + if video_codec == AVCodecID::AV_CODEC_ID_NONE { + panic!("The selected output container does not support video encoding.") + } + + let codec: *const AVCodec = ffmpeg_sys::avcodec_find_encoder(video_codec); + + if codec.is_null() { + panic!("Codec not found."); + } + + let context = NonNull::new(ffmpeg_sys::avcodec_alloc_context3(codec)) + .expect("Could not allocate video codec context."); + + if let Some(crf) = crf { + let val = CString::new(crf.to_string()).unwrap(); + let _ = ffmpeg_sys::av_opt_set( + (*context.as_ptr()).priv_data, + c"crf".as_ptr(), + val.as_ptr(), + 0, + ); + } + + if let Some(preset) = preset { + let val = CString::new(preset).unwrap(); + let _ = ffmpeg_sys::av_opt_set( + (*context.as_ptr()).priv_data, + c"preset".as_ptr(), + val.as_ptr(), + 0, + ); + } + + (*context.as_ptr()).codec_id = video_codec; + + // Resolution must be a multiple of two. + (*context.as_ptr()).width = target_width as i32; + (*context.as_ptr()).height = target_height as i32; + + // frames per second. + (*context.as_ptr()).time_base = time_base; + (*context.as_ptr()).gop_size = gop_size; + (*context.as_ptr()).max_b_frames = max_b_frames; + (*context.as_ptr()).pix_fmt = pix_fmt; + + if (*context.as_ptr()).codec_id == AVCodecID::AV_CODEC_ID_MPEG1VIDEO { + // Needed to avoid using macroblocks in which some coeffs overflow. + // This does not happen with normal video, it just happens here as + // the motion of the chroma plane does not match the luma plane. + (*context.as_ptr()).mb_decision = 2; + } + + // Open the codec. + if ffmpeg_sys::avcodec_open2(context.as_ptr(), codec, ptr::null_mut()) < 0 { + panic!("Could not open the codec."); + } + + if ffmpeg_sys::avcodec_parameters_from_context( + (*video_st.as_ptr()).codecpar, + context.as_ptr(), + ) < 0 + { + panic!("Failed to set codec parameters."); + } + + // Open the output file. + if ffmpeg_sys::avio_open( + &mut (*format_context.as_ptr()).pb, + path_str.as_ptr(), + ffmpeg_sys::AVIO_FLAG_WRITE, + ) < 0 + { + panic!("Failed to open the output file."); + } + + if ffmpeg_sys::avformat_write_header(format_context.as_ptr(), ptr::null_mut()) < 0 { + panic!("Failed to open the output file."); + } + + context + } +} + /// MPEG video recorder. pub struct Encoder { tmp_frame_buf: Vec, - frame_buf: Vec, + _frame_buf: Vec, curr_frame_index: usize, - initialized: bool, - bit_rate: usize, target_width: usize, target_height: usize, - time_base: (usize, usize), - gop_size: usize, - max_b_frames: usize, - pix_fmt: AVPixelFormat, - tmp_frame: *mut AVFrame, - frame: *mut AVFrame, - context: *mut AVCodecContext, - format_context: *mut AVFormatContext, - video_st: *mut AVStream, - scale_context: *mut SwsContext, - path: PathBuf, + tmp_frame: NonNull, + frame: NonNull, + context: NonNull, + format_context: NonNull, + video_st: NonNull, + scale_context: NonNull, } impl Encoder { - /// Creates a new video recorder. - /// - /// # Arguments: - /// * `path` - path to the output file. - /// * `width` - width of the recorded video. - /// * `height` - height of the recorded video. - pub fn new>(path: P, width: usize, height: usize) -> Encoder { - Encoder::new_with_params(path, width, height, None, None, None, None, None) - } - - /// Creates a new video recorder with custom recording parameters. - /// - /// # Arguments: - /// * `path` - path to the output file. - /// * `width` - width of the recorded video. - /// * `height` - height of the recorded video. - /// * `bit_rate` - the average bit rate. Default value: 400000. - /// * `time_base` - this is the fundamental unit of time (in seconds) in terms of which - /// frame timestamps are represented. Default value: (1, 60), i-e, 60fps. - /// * `gop_size` - the number of pictures in a group of pictures. Default value: 10. - /// * `max_b_frames` - maximum number of B-frames between non-B-frames. Default value: 1. - /// * `pix_fmt` - pixel format. Default value: `AVPixelFormat::PIX_FMT_YUV420P`. - #[allow(clippy::too_many_arguments)] - pub fn new_with_params>( - path: P, + pub fn new( + path: impl AsRef, width: usize, height: usize, - bit_rate: Option, - time_base: Option<(usize, usize)>, - gop_size: Option, - max_b_frames: Option, - pix_fmt: Option, - ) -> Encoder { - let bit_rate = bit_rate.unwrap_or(40_0000); // FIXME - let time_base = time_base.unwrap_or((1, 60)); - let gop_size = gop_size.unwrap_or(10); - let max_b_frames = max_b_frames.unwrap_or(1); - let pix_fmt = pix_fmt.unwrap_or(AVPixelFormat::AV_PIX_FMT_YUV420P); + crf: Option, + preset: Option<&str>, + ) -> Self { + let path = path.as_ref().to_str().unwrap(); + let path_str = CString::new(path).unwrap(); + + let time_base = AVRational { num: 1, den: 60 }; + + let gop_size = 10; + let max_b_frames = 1; + let pix_fmt = AVPixelFormat::AV_PIX_FMT_YUV420P; + // width and height must be a multiple of two. - let width = if width % 2 == 0 { width } else { width + 1 }; - let height = if height % 2 == 0 { height } else { height + 1 }; + let target_width = if width % 2 == 0 { width } else { width + 1 }; + let target_height = if height % 2 == 0 { height } else { height + 1 }; + + // sws scaling context + let scale_context = unsafe { + NonNull::new(ffmpeg_sys::sws_getContext( + target_width as i32, + target_height as i32, + AVPixelFormat::AV_PIX_FMT_RGB24, + target_width as i32, + target_height as i32, + pix_fmt, + ffmpeg_sys::SWS_BICUBIC, + ptr::null_mut(), + ptr::null_mut(), + ptr::null(), + )) + .expect("Failed to create scale context") + }; - let mut pathbuf = PathBuf::new(); - pathbuf.push(path); + // Init the temporary video frame. + let tmp_frame = unsafe { + let frame = NonNull::new(ffmpeg_sys::av_frame_alloc()) + .expect("Could not allocate the video frame."); - Encoder { - initialized: false, - curr_frame_index: 0, - bit_rate, - target_width: width, - target_height: height, + (*frame.as_ptr()).format = pix_fmt as i32; + // the rest (width, height, data, linesize) are set at the moment of the snapshot. + + frame + }; + + // Init the destination video frame. + let (frame, frame_buf) = unsafe { + let frame = NonNull::new(ffmpeg_sys::av_frame_alloc()) + .expect("Could not allocate the video frame."); + + (*frame.as_ptr()).format = pix_fmt as i32; + (*frame.as_ptr()).width = target_width as i32; + (*frame.as_ptr()).height = target_height as i32; + (*frame.as_ptr()).pts = 0; + + // alloc the buffer + let nframe_bytes = ffmpeg_sys::av_image_get_buffer_size( + pix_fmt, + target_width as i32, + target_height as i32, + 16, + ); + + let frame_buf = vec![0u8; nframe_bytes as usize]; + + let _ = ffmpeg_sys::av_image_fill_arrays( + (*frame.as_ptr()).data.as_mut_ptr(), + (*frame.as_ptr()).linesize.as_mut_ptr(), + frame_buf.as_ptr(), + pix_fmt, + target_width as i32, + target_height as i32, + 1, + ); + + (frame, frame_buf) + }; + + // try to guess the container type from the path. + let format_context = unsafe { + let mut fmt = ptr::null_mut(); + + let _ = ffmpeg_sys::avformat_alloc_output_context2( + &mut fmt, + ptr::null_mut(), + ptr::null(), + path_str.as_ptr(), + ); + + NonNull::new(fmt) + .or_else(|| { + // could not guess, default to MPEG + let mpeg = CString::new(&b"mpeg"[..]).unwrap(); + + let _ = ffmpeg_sys::avformat_alloc_output_context2( + &mut fmt, + ptr::null_mut(), + mpeg.as_ptr(), + path_str.as_ptr(), + ); + + NonNull::new(fmt) + }) + .expect("Unable to create the output context.") + }; + + let video_st = unsafe { + let stream = NonNull::new(ffmpeg_sys::avformat_new_stream( + format_context.as_ptr(), + ptr::null(), + )) + .expect("Failed to allocate the video stream."); + + (*stream.as_ptr()).id = ((*format_context.as_ptr()).nb_streams - 1) as i32; + (*stream.as_ptr()).time_base = time_base; + + stream + }; + + let context = init_context( + &path_str, + &format_context, + &video_st, time_base, gop_size, max_b_frames, pix_fmt, - frame: ptr::null_mut(), - tmp_frame: ptr::null_mut(), - context: ptr::null_mut(), - scale_context: ptr::null_mut(), - format_context: ptr::null_mut(), - video_st: ptr::null_mut(), - path: pathbuf, - frame_buf: Vec::new(), + crf, + preset, + target_width, + target_height, + ); + + // Print detailed information about the input or output format, such as duration, bitrate, streams, container, programs, metadata, side data, codec and time base + unsafe { + ffmpeg_sys::av_dump_format(format_context.as_ptr(), 0, path_str.as_ptr(), 1); + } + + Self { tmp_frame_buf: Vec::new(), + curr_frame_index: 0, + target_width, + target_height, + tmp_frame, + + frame, + _frame_buf: frame_buf, + + context, + format_context, + video_st, + scale_context, } } @@ -162,8 +345,6 @@ impl Encoder { || (!has_alpha && data.len() == width * height * 3) ); - self.init(None, None); - let mut pkt = unsafe { let mut pkt: mem::MaybeUninit = mem::MaybeUninit::uninit(); ffmpeg_sys::av_init_packet(pkt.as_mut_ptr()); @@ -186,12 +367,12 @@ impl Encoder { } unsafe { - (*self.tmp_frame).width = width as i32; - (*self.tmp_frame).height = height as i32; + (*self.tmp_frame.as_ptr()).width = width as i32; + (*self.tmp_frame.as_ptr()).height = height as i32; let _ = ffmpeg_sys::av_image_fill_arrays( - (*self.tmp_frame).data.as_mut_ptr(), - (*self.tmp_frame).linesize.as_mut_ptr(), + (*self.tmp_frame.as_ptr()).data.as_mut_ptr(), + (*self.tmp_frame.as_ptr()).linesize.as_mut_ptr(), self.tmp_frame_buf.as_ptr(), (&color_format).into(), width as i32, @@ -203,8 +384,8 @@ impl Encoder { // Convert the snapshot frame to the right format for the destination frame. // unsafe { - self.scale_context = ffmpeg_sys::sws_getCachedContext( - self.scale_context, + self.scale_context = NonNull::new(ffmpeg_sys::sws_getCachedContext( + self.scale_context.as_ptr(), width as i32, height as i32, (&color_format).into(), @@ -215,306 +396,102 @@ impl Encoder { ptr::null_mut(), ptr::null_mut(), ptr::null(), - ); + )) + .unwrap(); let _ = ffmpeg_sys::sws_scale( - self.scale_context, - &(*self.tmp_frame).data[0] as *const *mut u8 as *const *const u8, - &(*self.tmp_frame).linesize[0], + self.scale_context.as_ptr(), + &(*self.tmp_frame.as_ptr()).data[0] as *const *mut u8 as *const *const u8, + &(*self.tmp_frame.as_ptr()).linesize[0], 0, height as i32, - &(*self.frame).data[0] as *const *mut u8, - &(*self.frame).linesize[0], + &(*self.frame.as_ptr()).data[0] as *const *mut u8, + &(*self.frame.as_ptr()).linesize[0], ); } // Encode the image. - let ret = unsafe { ffmpeg_sys::avcodec_send_frame(self.context, self.frame) }; + let ret = + unsafe { ffmpeg_sys::avcodec_send_frame(self.context.as_ptr(), self.frame.as_ptr()) }; if ret < 0 { panic!("Error encoding frame."); } unsafe { - if ffmpeg_sys::avcodec_receive_packet(self.context, &mut pkt) == 0 { - let _ = ffmpeg_sys::av_interleaved_write_frame(self.format_context, &mut pkt); + if ffmpeg_sys::avcodec_receive_packet(self.context.as_ptr(), &mut pkt) == 0 { + let _ = + ffmpeg_sys::av_interleaved_write_frame(self.format_context.as_ptr(), &mut pkt); ffmpeg_sys::av_packet_unref(&mut pkt); } } unsafe { - (*self.frame).pts += - ffmpeg_sys::av_rescale_q(1, (*self.context).time_base, (*self.video_st).time_base); - self.curr_frame_index += self.curr_frame_index; - } - } - - /// Initializes the recorder if needed. - /// - /// This is automatically called when the first snapshot is made. Call this explicitly if you - /// do not want the extra time overhead when the first snapshot is made. - pub fn init(&mut self, crf: Option, preset: Option<&str>) { - if self.initialized { - return; - } - - let path_str = CString::new(self.path.to_str().unwrap()).unwrap(); - - unsafe { - // try to guess the container type from the path. - let mut fmt = ptr::null_mut(); - - let _ = ffmpeg_sys::avformat_alloc_output_context2( - &mut fmt, - ptr::null_mut(), - ptr::null(), - path_str.as_ptr(), - ); - - if fmt.is_null() { - // could not guess, default to MPEG - let mpeg = CString::new(&b"mpeg"[..]).unwrap(); - - let _ = ffmpeg_sys::avformat_alloc_output_context2( - &mut fmt, - ptr::null_mut(), - mpeg.as_ptr(), - path_str.as_ptr(), - ); - } - - self.format_context = fmt; - - if self.format_context.is_null() { - panic!("Unable to create the output context."); - } - - let fmt = (*self.format_context).oformat; - - if (*fmt).video_codec == AVCodecID::AV_CODEC_ID_NONE { - panic!("The selected output container does not support video encoding.") - } - - let codec: *const AVCodec = ffmpeg_sys::avcodec_find_encoder((*fmt).video_codec); - - if codec.is_null() { - panic!("Codec not found."); - } - - self.video_st = ffmpeg_sys::avformat_new_stream(self.format_context, ptr::null()); - - if self.video_st.is_null() { - panic!("Failed to allocate the video stream."); - } - - (*self.video_st).id = ((*self.format_context).nb_streams - 1) as i32; - - self.context = ffmpeg_sys::avcodec_alloc_context3(codec); - - if self.context.is_null() { - panic!("Could not allocate video codec context."); - } - - { - if let Some(crf) = crf { - let name = CString::new("crf").unwrap(); - let val = CString::new(crf.to_string()).unwrap(); - let _ = ffmpeg_sys::av_opt_set( - (*self.context).priv_data, - name.as_ptr(), - val.as_ptr(), - 0, - ); - } - if let Some(preset) = preset { - let name = CString::new("preset").unwrap(); - let val = CString::new(preset).unwrap(); - let _ = ffmpeg_sys::av_opt_set( - (*self.context).priv_data, - name.as_ptr(), - val.as_ptr(), - 0, - ); - } - } - - // sws scaling context - self.scale_context = ffmpeg_sys::sws_getContext( - self.target_width as i32, - self.target_height as i32, - AVPixelFormat::AV_PIX_FMT_RGB24, - self.target_width as i32, - self.target_height as i32, - self.pix_fmt, - ffmpeg_sys::SWS_BICUBIC, - ptr::null_mut(), - ptr::null_mut(), - ptr::null(), - ); - - (*self.context).codec_id = (*fmt).video_codec; - - // Put sample parameters. - (*self.context).bit_rate = self.bit_rate as i64; - - // Resolution must be a multiple of two. - (*self.context).width = self.target_width as i32; - (*self.context).height = self.target_height as i32; - - // frames per second. - let (tnum, tdenum) = self.time_base; - (*self.context).time_base = AVRational { - num: tnum as i32, - den: tdenum as i32, - }; - (*self.video_st).time_base = (*self.context).time_base; - (*self.context).gop_size = self.gop_size as i32; - (*self.context).max_b_frames = self.max_b_frames as i32; - (*self.context).pix_fmt = self.pix_fmt; - - if (*self.context).codec_id == AVCodecID::AV_CODEC_ID_MPEG1VIDEO { - // Needed to avoid using macroblocks in which some coeffs overflow. - // This does not happen with normal video, it just happens here as - // the motion of the chroma plane does not match the luma plane. - (*self.context).mb_decision = 2; - } - - // Open the codec. - if ffmpeg_sys::avcodec_open2(self.context, codec, ptr::null_mut()) < 0 { - panic!("Could not open the codec."); - } - - /* - * Init the destination video frame. - */ - self.frame = ffmpeg_sys::av_frame_alloc(); - - if self.frame.is_null() { - panic!("Could not allocate the video frame."); - } - - (*self.frame).format = (*self.context).pix_fmt as i32; - (*self.frame).width = (*self.context).width; - (*self.frame).height = (*self.context).height; - (*self.frame).pts = 0; - - // alloc the buffer - let nframe_bytes = ffmpeg_sys::av_image_get_buffer_size( - self.pix_fmt, - self.target_width as i32, - self.target_height as i32, - 16, - ); - - let reps = iter::repeat(0u8).take(nframe_bytes as usize); - self.frame_buf = Vec::::from_iter(reps); - - let _ = ffmpeg_sys::av_image_fill_arrays( - (*self.frame).data.as_mut_ptr(), - (*self.frame).linesize.as_mut_ptr(), - self.frame_buf.as_ptr(), - self.pix_fmt, - self.target_width as i32, - self.target_height as i32, + (*self.frame.as_ptr()).pts += ffmpeg_sys::av_rescale_q( 1, + (*self.context.as_ptr()).time_base, + (*self.video_st.as_ptr()).time_base, ); - - /* - * Init the temporary video frame. - */ - self.tmp_frame = ffmpeg_sys::av_frame_alloc(); - - if self.tmp_frame.is_null() { - panic!("Could not allocate the video frame."); - } - - (*self.tmp_frame).format = (*self.context).pix_fmt as i32; - // the rest (width, height, data, linesize) are set at the moment of the snapshot. - - if ffmpeg_sys::avcodec_parameters_from_context((*self.video_st).codecpar, self.context) - < 0 - { - panic!("Failed to set codec parameters."); - } - - ffmpeg_sys::av_dump_format(self.format_context, 0, path_str.as_ptr(), 1); - - // Open the output file. - if ffmpeg_sys::avio_open( - &mut (*self.format_context).pb, - path_str.as_ptr(), - ffmpeg_sys::AVIO_FLAG_WRITE, - ) < 0 - { - panic!("Failed to open the output file."); - } - - if ffmpeg_sys::avformat_write_header(self.format_context, ptr::null_mut()) < 0 { - panic!("Failed to open the output file."); - } + self.curr_frame_index += self.curr_frame_index; } - - self.initialized = true; } } impl Drop for Encoder { fn drop(&mut self) { - if self.initialized { - // Get the delayed frames. + // Get the delayed frames. - let ret = unsafe { ffmpeg_sys::avcodec_send_frame(self.context, ptr::null()) }; + let ret = unsafe { ffmpeg_sys::avcodec_send_frame(self.context.as_ptr(), ptr::null()) }; - if ret < 0 { - panic!("Error encoding frame."); - } + if ret < 0 { + panic!("Error encoding frame."); + } - loop { - let mut pkt = unsafe { - let mut pkt: mem::MaybeUninit = mem::MaybeUninit::uninit(); - ffmpeg_sys::av_init_packet(pkt.as_mut_ptr()); - pkt.assume_init() - }; - - pkt.data = ptr::null_mut(); // packet data will be allocated by the encoder - pkt.size = 0; - - unsafe { - match ffmpeg_sys::avcodec_receive_packet(self.context, &mut pkt) { - 0 => { - let _ = ffmpeg_sys::av_interleaved_write_frame( - self.format_context, - &mut pkt, - ); - ffmpeg_sys::av_packet_unref(&mut pkt); - } - ffmpeg_sys::AVERROR_EOF => { - break; - } - _ => {} + loop { + let mut pkt = unsafe { + let mut pkt: mem::MaybeUninit = mem::MaybeUninit::uninit(); + ffmpeg_sys::av_init_packet(pkt.as_mut_ptr()); + pkt.assume_init() + }; + + pkt.data = ptr::null_mut(); // packet data will be allocated by the encoder + pkt.size = 0; + + unsafe { + match ffmpeg_sys::avcodec_receive_packet(self.context.as_ptr(), &mut pkt) { + 0 => { + let _ = ffmpeg_sys::av_interleaved_write_frame( + self.format_context.as_ptr(), + &mut pkt, + ); + ffmpeg_sys::av_packet_unref(&mut pkt); } + ffmpeg_sys::AVERROR_EOF => { + break; + } + _ => {} } } + } - // Write trailer - unsafe { - if ffmpeg_sys::av_write_trailer(self.format_context) < 0 { - panic!("Error writing trailer."); - } + // Write trailer + unsafe { + if ffmpeg_sys::av_write_trailer(self.format_context.as_ptr()) < 0 { + panic!("Error writing trailer."); } + } - // Free things and stuffs. - unsafe { - ffmpeg_sys::avcodec_free_context(&mut self.context); - ffmpeg_sys::av_frame_free(&mut self.frame); - ffmpeg_sys::av_frame_free(&mut self.tmp_frame); - ffmpeg_sys::sws_freeContext(self.scale_context); - if ffmpeg_sys::avio_closep(&mut (*self.format_context).pb) < 0 { - println!("Warning: failed closing output file"); - } - ffmpeg_sys::avformat_free_context(self.format_context); + // Free things and stuffs. + unsafe { + ffmpeg_sys::avcodec_free_context(&mut self.context.as_ptr()); + ffmpeg_sys::av_frame_free(&mut self.frame.as_ptr()); + ffmpeg_sys::av_frame_free(&mut self.tmp_frame.as_ptr()); + ffmpeg_sys::sws_freeContext(self.scale_context.as_ptr()); + if ffmpeg_sys::avio_closep(&mut (*self.format_context.as_ptr()).pb) < 0 { + println!("Warning: failed closing output file"); } + ffmpeg_sys::avformat_free_context(self.format_context.as_ptr()); } } } diff --git a/neothesia-cli/src/main.rs b/neothesia-cli/src/main.rs index f2f68534..902cec43 100644 --- a/neothesia-cli/src/main.rs +++ b/neothesia-cli/src/main.rs @@ -262,10 +262,10 @@ fn main() { "./out/video.mp4", recorder.width as usize, recorder.height as usize, + Some(0.0), + Some("medium"), ); - encoder.init(Some(0.0), Some("medium")); - let start = std::time::Instant::now(); println!("Encoding started:");