使用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,分母远超限制导致编码启动失败,试试这两个方案:
- 手动指定合规的timebase
在recorder.start()之前添加配置,选择符合标准的timebase:// 用30帧对应的timebase,分母30远小于65535 recorder.setVideoOption("time_base", "1/30"); // 或者用更接近原帧率的1001/30000(对应29.97帧) recorder.setVideoOption("time_base", "1001/30000"); - 强制固定帧率
直接将recorder的帧率设为30,避免非标准帧率带来的异常timebase:recorder.setFrameRate(30);
针对H264的丢帧和DTS错误
DTS(解码时间戳)非单调递增是因为原视频时间戳存在重复或乱序,导致封装器报错,可通过以下方式修复:
- 移除手动设置timestamp
删除recorder.setTimestamp(grabber.getTimestamp());,让JavaCV自动处理时间戳,避免手动设置引发的冲突。 - 同步合法的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); } - 强制时间戳递增
若原视频时间戳确实乱序,手动维护一个递增的时间戳: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()); } } - 优化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
相关产品推荐
相关产品推荐

