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

使用JavaCV将QuickTime转MP4遇编码与时序错误求助

使用JavaCV转换QuickTime到MP4的问题解决

问题背景

正在进行QuickTime转MP4的项目,使用的JavaCV Maven依赖如下:

<dependency>
    <groupId>org.bytedeco</groupId>
    <artifactId>javacv-platform</artifactId>
    <version>1.5.8</version>
</dependency>

问题1:MPEG4编码启动失败

采用MPEG4编码的转换代码:

public static ByteArrayOutputStream convertToMp4(String videoId, byte[] file) {
    classLogger.trace("request is received to convert video: " + videoId + " to mp4");
    FFmpegLogCallback.set();
    InputStream stream = new ByteArrayInputStream(file);
    Frame captured_frame = null;
    FFmpegFrameRecorder recorder = null;
    FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(stream);
    SeekableByteArrayOutputStream outputStream = new SeekableByteArrayOutputStream();
    try {
        grabber.start();
        System.out.println("grabber frame rate " + grabber.getFrameRate()); //value is 29.85882710114861
        recorder = new FFmpegFrameRecorder(outputStream, grabber.getImageWidth(), grabber.getImageHeight(), grabber.getAudioChannels());
        recorder.setVideoCodec(avcodec.AV_CODEC_ID_MPEG4);
        System.out.println("recorder frame rate b4 " + recorder.getFrameRate()); //value is 30.0
        recorder.setFormat("mp4");
        recorder.setFrameRate(grabber.getFrameRate());
        recorder.setAudioMetadata(grabber.getAudioMetadata());
        recorder.setAudioOption("crf", "0");
        recorder.setAudioQuality(0);
        recorder.setVideoBitrate(grabber.getVideoBitrate());
        recorder.setAudioBitrate(grabber.getAudioBitrate());
        recorder.setSampleRate(grabber.getSampleRate());
        recorder.setVideoMetadata(grabber.getVideoMetadata());
        recorder.start();
        while ((captured_frame = grabber.grabFrame()) != null) {
            try {
                recorder.setTimestamp(grabber.getTimestamp());
                recorder.record(captured_frame);
            } catch (Exception e) {
                classLogger.error(e.getMessage());
            }
        }
        recorder.stop();
        recorder.release();
        grabber.stop();
        grabber.release();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return outputStream;
}

执行时触发错误:

Error: [mpeg4 @ 0x7f61043a6e40] timebase 19763/590100 not supported by MPEG 4 standard, the maximum admitted value for the timebase denominator is 65535

org.bytedeco.javacv.FFmpegFrameRecorder$Exception: avcodec_open2() error -22: Could not open video codec. (For more details, make sure FFmpegLogCallback.set() has been called.)

尝试修改帧率调整timebase无效。

问题2:H264编码丢帧、画质失真

改用H264编码(替换recorder.setVideoCodec(avcodec.AV_CODEC_ID_MPEG4)为recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264))后转换成功,但出现大量丢帧、画质失真,同时日志报错:

Error: [mp4 @ 0x7fc2486d9b80] Application provided invalid, non monotonically increasing dts to muxer in stream 0: 2094878 >= 2094878
av_interleaved_write_frame() error -22 while writing interleaved video packet. (For more details, make sure FFmpegLogCallback.set() has been called.)

解决办法

针对MPEG4编码的Timebase报错

MPEG4标准限制timebase分母不能超过65535,原视频的timebase是19763/590100,分母远超限制导致编码启动失败,试试这两个方案:

  1. 手动指定合规的timebase
    在recorder.start()之前添加配置,选择符合标准的timebase:
    // 用30帧对应的timebase,分母30远小于65535
    recorder.setVideoOption("time_base", "1/30");
    // 或者用更接近原帧率的1001/30000(对应29.97帧)
    recorder.setVideoOption("time_base", "1001/30000");
    
  2. 强制固定帧率
    直接将recorder的帧率设为30,避免非标准帧率带来的异常timebase:
    recorder.setFrameRate(30);
    

针对H264的丢帧和DTS错误

DTS(解码时间戳)非单调递增是因为原视频时间戳存在重复或乱序,导致封装器报错,可通过以下方式修复:

  1. 移除手动设置timestamp
    删除recorder.setTimestamp(grabber.getTimestamp());,让JavaCV自动处理时间戳,避免手动设置引发的冲突。
  2. 同步合法的timebase
    确保recorder的timebase与grabber一致且符合标准:
    AVRational videoTb = grabber.getVideoTimebase();
    // 若原timebase分母超标,替换为帧率对应的合法值
    if (videoTb.den() > 65535) {
        recorder.setVideoTimebase(new AVRational(1, Math.round(grabber.getFrameRate())));
    } else {
        recorder.setVideoTimebase(videoTb);
    }
    
  3. 强制时间戳递增
    若原视频时间戳确实乱序,手动维护一个递增的时间戳:
    long lastTs = -1;
    while ((captured_frame = grabber.grabFrame()) != null) {
        try {
            long currentTs = grabber.getTimestamp();
            // 确保当前时间戳严格大于上一个
            if (currentTs <= lastTs) {
                currentTs = lastTs + 1;
            }
            recorder.setTimestamp(currentTs);
            recorder.record(captured_frame);
            lastTs = currentTs;
        } catch (Exception e) {
            classLogger.error(e.getMessage());
        }
    }
    
  4. 优化H264编码参数
    调整编码参数避免画质失真:
    recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
    recorder.setVideoOption("crf", "23"); // CRF23是画质与文件大小的平衡值,0为无损但文件过大
    recorder.setVideoOption("preset", "medium"); // 平衡编码速度与画质,slow预设画质更优
    recorder.setVideoBitrate(grabber.getVideoBitrate()); // 保持原视频码率,避免过度压缩
    

额外优化建议

  • 升级JavaCV版本:1.5.8版本较旧,升级到最新稳定版(如1.5.10+)可修复部分FFmpeg兼容性问题。
  • 避免用SeekableByteArrayOutputStream处理大视频:内存占用过高时,直接写入文件或使用更高效的流实现。
  • 收紧异常处理逻辑:当前代码捕获异常后继续执行易引发后续帧错误,严重异常时应终止转换流程。

内容的提问来源于stack exchange,提问作者Saurabh Sharma

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 01:52:37