mp4的moov前置可以支持视频渐进式播放
技术细节
依靠ffmpeg的功能实现,基础脚本
ffmpeg -i input.mp4 -movflags faststart -c copy output.mp4
如何使用java实现呢
需要在服务器事先安装ffmpeg组件,然后java执行脚本(保存到临时文件速度更快)
try {
// 定义 FFmpeg 命令参数(注意路径需根据实际情况调整)
String[] command = {
"ffmpeg",
"-i", "input.mp4",
"-movflags", "faststart",
"-c", "copy",
"output.mp4"
};
// 创建进程并执行命令
ProcessBuilder processBuilder = new ProcessBuilder(command);
Process process = processBuilder.start();
// 读取 FFmpeg 的输出流和错误流(避免进程阻塞)
InputStream inputStream = process.getInputStream();
InputStream errorStream = process.getErrorStream();
printStream(inputStream, "Output");
printStream(errorStream, "Error");
// 等待命令执行完成
int exitCode = process.waitFor();
System.out.println("Exit Code: " + exitCode);
} catch (Exception e) {
e.printStackTrace();
}
如果不想安装组件使用javacv怎么实现呢
FFmpegFrameGrabber grabber = null;
FFmpegFrameRecorder recorder = null;
try {
// 1. 初始化 Grabber(读取输入文件)
grabber = new FFmpegFrameGrabber("input.mp4");
grabber.start();
// 2. 初始化 Recorder(写入输出文件)
recorder = new FFmpegFrameRecorder(
"output.mp4",
grabber.getImageWidth(),
grabber.getImageHeight(),
grabber.getAudioChannels()
);
// 3. 设置关键参数
recorder.setFormat("mp4");
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264); // 或直接复制流
recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
recorder.setSampleRate(grabber.getSampleRate());
recorder.setFrameRate(grabber.getFrameRate());
// 4. 启用 faststart(核心参数)
recorder.setOption("movflags", "faststart");
// 5. 开始录制
recorder.start();
// 6. 逐帧复制(直接复制流)
while (true) {
// 抓取帧(视频或音频)
Object frame = grabber.grab();
if (frame == null) {
break; // 结束循环
}
// 录制帧
if (frame instanceof org.bytedeco.javacv.Frame) {
recorder.record((org.bytedeco.javacv.Frame) frame);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (recorder != null) {
recorder.close();
}
if (grabber != null) {
grabber.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
但经测试,速度不太理想,
如果既不想安装组件又希望速度更快呢?
直接从javacv的运行环境里拿出核心组件去执行脚本
try {
// 1. 获取 FFmpeg 可执行文件的路径(通过 JavaCPP 加载)
String ffmpeg = Loader.load(FFmpeg.class);
// 2. 定义 FFmpeg 命令行参数(与终端命令一致)
String[] cmd = {
ffmpeg,
"-i", "input.mp4",
"-movflags", "faststart",
"-c", "copy",
"output.mp4"
};
// 3. 将参数转换为 PointerPointer 类型
PointerPointer<String> command = new PointerPointer<>(cmd.length);
for (int i = 0; i < cmd.length; i++) {
command.put(i, cmd[i]);
}
// 4. 执行命令
int exitCode = FFmpeg.exec(command);
System.out.println("Exit Code: " + exitCode);
} catch (Exception e) {
e.printStackTrace();
}
好像不错,本地经测试完美,
但是线上崩溃各种找不到相关依赖,
因为是容器化部署不想折腾怎么办?
再看下纯java的javacv方式为什么速度很慢,毕竟原理是相同的?
经查果然"6. 逐帧复制" 这一步拖后腿
String inputFile = "input.mp4";
String outputFile = "output.mp4";
FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(inputFile);
grabber.start();
FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(outputFile, 0);
recorder.setFormat("mp4");
// 设置movflags为faststart,确保moov前置
recorder.setOption("movflags", "faststart");
// 复制视频和音频编解码参数
recorder.setVideoCodec(grabber.getVideoCodec());
recorder.setAudioCodec(grabber.getAudioCodec());
// 设置媒体参数,如分辨率、采样率等
recorder.setImageWidth(grabber.getImageWidth());
recorder.setImageHeight(grabber.getImageHeight());
recorder.setSampleRate(grabber.getSampleRate());
recorder.setAudioChannels(grabber.getAudioChannels());
recorder.setAudioBitrate(grabber.getAudioBitrate());
// 启动录制器
recorder.start(grabber.getFormatContext());
AVPacket packet;
while ((packet = grabber.grabPacket()) != null) {
// 直接写入数据包,避免编解码
recorder.recordPacket(packet);
avcodec.av_packet_unref(packet); // 释放数据包资源
}
// 关闭资源
recorder.close();
grabber.close();
效果完美,又省事又快速
总结
通过直接写入数据包 绕过解码/编码,直接操作已压缩的媒体流数据,使得处理速度接近单纯的“文件复制”,而 faststart 操作仅需调整元数据位置,因此速度极快。这是 FFmpeg/JavaCV 中优化媒体处理性能的经典方法。