You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 15:08:12