ffmpeg-next库write_header疑似Bug致时间基准设为Rational(1/15360)
使用ffmpeg-next编码视频时time_base被错误重置的问题
我使用ffmpeg-next crate编码视频,虽能成功生成输出文件,但流的time_base被错误写入。已确认编码器和流的time_base均设置正确,但调用octx.write_header().unwrap();后,流的time_base会从Rational(1/30)被重置为Rational(1/15360),后续修改无法修正,错误值已写入文件头。
调试定位
我修改了ffmpeg-next的源码并重新编译,确认调用avformat_write_header前time_base值是正确的,修改后的write_header函数如下:
pub fn write_header(&mut self) -> Result<(), Error> { println!( "_________________ {:?}", self.stream(0).unwrap().time_base() ); unsafe { match avformat_write_header(self.as_mut_ptr(), ptr::null_mut()) { 0 => Ok(()), e => Err(Error::from(e)), } } }
问题分析
我推测这可能是crate的Bug,但因对FFmpeg了解有限不敢断言。GitHub仓库中的示例似乎无此问题,不过我未能运行transcode-x264示例,我的代码大多参考该示例。
相关代码片段中,我不确定set_parameters的影响,测试显示无作用,尝试在函数末尾重新设置time_base也无效:
let mut ost = octx.add_stream(codec)?; ost.set_time_base(Rational::new(1, FPS)); ost.set_parameters(&encoder); encoder.set_time_base(Rational::new(1, FPS)); ost.set_parameters(&opened_encoder);
默认及上述示例中流的time_base为0/0,手动设置此值也无效果。我还发现修改set_pts的值会影响输出帧率,但不影响time_base,这应该是副作用。
最小复现示例
抽象main函数
fn main() { let output_file = "output.mp4"; let x264_opts = parse_opts("preset=medium".to_string()).expect("invalid x264 options string"); ffmpeg_next::init().unwrap(); let mut octx = format::output(output_file).unwrap(); let mut encoder = Encoder::new(&mut octx, x264_opts).unwrap(); format::context::output::dump(&octx, 0, Some(&output_file)); //This line somehow clears the streams time base octx.write_header().unwrap(); // Without this line, the next logs returns Rational(1/30) Rational(1/15360) indicating streams timebase is wrong. even thought I set it above // this line changes it back but only for the print but not the actual output. Because the faulty data is written into the header // octx.stream_mut(0) // .unwrap() // .set_time_base(Rational::new(1, FPS)); println!( "---------------- {:?} {:?}", encoder.encoder.time_base(), octx.stream(0).unwrap().time_base(), ); for frame_num in 0..100 { let mut frame = encoder.create_frame(); frame.set_pts(Some(frame_num)); encoder.add_frame(&frame, &mut octx); } encoder.close(&mut octx); octx.write_trailer().unwrap(); }
包含实现逻辑的Encoder结构体
struct Encoder { encoder: encoder::Video, } impl Encoder { fn new( octx: &mut format::context::Output, x264_opts: Dictionary, ) -> Result<Self, ffmpeg_next::Error> { let set_header = octx .format() .flags() .contains(ffmpeg_next::format::flag::Flags::GLOBAL_HEADER); let codec = encoder::find(codec::Id::H264); let mut ost = octx.add_stream(codec)?; ost.set_time_base(Rational::new(1, FPS)); let mut encoder = codec::context::Context::new_with_codec( encoder::find(codec::Id::H264) .ok_or(ffmpeg_next::Error::InvalidData) .unwrap(), ) .encoder() .video() .unwrap(); ost.set_parameters(&encoder); encoder.set_width(WIDTH); encoder.set_height(HEIGHT); encoder.set_aspect_ratio(WIDTH as f64 / HEIGHT as f64); encoder.set_format(util::format::Pixel::YUV420P); encoder.set_frame_rate(Some(Rational::new(FPS, 1))); encoder.set_time_base(Rational::new(1, FPS)); if set_header { encoder.set_flags(ffmpeg_next::codec::flag::Flags::GLOBAL_HEADER); } let opened_encoder = encoder .open_with(x264_opts.to_owned()) .expect("error opening x264 with supplied settings"); ost.set_parameters(&opened_encoder); println!( "\nost time_base: {}; encoder time_base: {}; encoder frame_rate: {}\n", ost.time_base(), &opened_encoder.time_base(), &opened_encoder.frame_rate() ); Ok(Self { encoder: opened_encoder, }) } fn add_frame(&mut self, frame: &frame::Video, octx: &mut format::context::Output) { self.encoder.send_frame(frame).unwrap(); self.process_packets(octx); } fn close(&mut self, octx: &mut format::context::Output) { self.encoder.send_eof().unwrap(); self.process_packets(octx); } fn process_packets(&mut self, octx: &mut format::context::Output) { let mut encoded = Packet::empty(); while self.encoder.receive_packet(&mut encoded).is_ok() { encoded.set_stream(0); encoded.write_interleaved(octx).unwrap(); } } fn create_frame(&self) -> frame::Video { return frame::Video::new( self.encoder.format(), self.encoder.width(), self.encoder.height(), ); } }
其他工具代码
use ffmpeg_next::{ codec::{self}, encoder, format, frame, util, Dictionary, Packet, Rational, }; const FPS: i32 = 30; const WIDTH: u32 = 720; const HEIGHT: u32 = 1080; fn parse_opts<'a>(s: String) -> Option<Dictionary<'a>> { let mut dict = Dictionary::new(); for keyval in s.split_terminator(',') { let tokens: Vec<&str> = keyval.split('=').collect(); match tokens[..] { [key, val] => dict.set(key, val), _ => return None, } } Some(dict) }
内容的提问来源于stack exchange,提问作者Huhngut
相关产品推荐
相关产品推荐

