FFmpeg录屏命令在Windows CMD可用但Java类调用生成文件不可读问题
问题:Java调用FFmpeg录制屏幕生成的文件无法播放(错误码0xc10100be)
我有一个JUnit测试,通过ScreenRecordingWatcher类在测试失败时调用FFmpeg录制屏幕。CMD中直接执行命令能生成正常可播放的文件,但Java调用生成的文件无法被Windows播放器读取,报错错误码0xc10100be。
正常工作的CMD命令
C:\outils\ffmpeg-master-latest-win64-gpl\bin\ffmpeg.exe -f gdigrab -framerate 30 -i desktop -c:v h264_nvenc -qp 0 C:\outils\video\output.mkv
Java实现代码(原版本)
public class ScreenRecordingWatcher extends TestWatcher { private static final String FFMPEG_PATH = "C:\\outils\\ffmpeg-master-latest-win64-gpl\\bin\\ffmpeg.exe"; private static final String OUTPUT_FILE = "C:\\outils\\video\\fichier.mkv"; private boolean testFailed = false; @Override protected void failed(Throwable e, Description description) { super.failed(e, description); testFailed = true; } @Override protected void finished(Description description) { super.finished(description); if (testFailed) { startRecording(); try { Thread.sleep(5000); } catch (InterruptedException e) { throw new RuntimeException(e); } stopRecording(); } } private void startRecording() { String[] command = { FFMPEG_PATH, "-f", "gdigrab", "-framerate", "30", "-i", "desktop", "-c:v", "h264_nvenc", "-qp", "0", OUTPUT_FILE }; System.out.println(Arrays.toString(command)); ProcessBuilder builder = new ProcessBuilder(command); try { builder.start(); } catch (IOException e) { e.printStackTrace(); } } private void stopRecording() { String[] command = { "tskill", "ffmpeg" }; ProcessBuilder builder = new ProcessBuilder(command); try { builder.start(); } catch (IOException e) { throw new RuntimeException(e); } } }
原因与解决方法
核心原因:强制终止FFmpeg进程导致文件结构损坏。原代码用
tskill ffmpeg直接杀死进程,FFmpeg来不及完成视频文件的封装收尾(比如写入MKV索引、完善文件头),导致播放器无法识别。而CMD中是手动输入q让FFmpeg优雅退出,文件能正常完成写入。修复后的代码:
import java.io.File; import java.io.IOException; import java.util.concurrent.TimeUnit; public class ScreenRecordingWatcher extends TestWatcher { private static final String FFMPEG_PATH = "C:\\outils\\ffmpeg-master-latest-win64-gpl\\bin\\ffmpeg.exe"; private static final String OUTPUT_FILE = "C:\\outils\\video\\fichier.mkv"; private boolean testFailed = false; private Process ffmpegProcess; // 保存FFmpeg进程引用 @Override protected void failed(Throwable e, Description description) { super.failed(e, description); testFailed = true; } @Override protected void finished(Description description) { super.finished(description); if (testFailed) { startRecording(); try { Thread.sleep(5000); } catch (InterruptedException e) { throw new RuntimeException(e); } stopRecording(); } } private void startRecording() { String[] command = { FFMPEG_PATH, "-f", "gdigrab", "-framerate", "30", "-i", "desktop", "-c:v", "h264_nvenc", "-qp", "0", OUTPUT_FILE }; System.out.println(Arrays.toString(command)); ProcessBuilder builder = new ProcessBuilder(command); // 重定向日志到文件,方便排查问题 builder.redirectErrorStream(true); builder.redirectOutput(new File("C:\\outils\\video\\ffmpeg_recording.log")); try { ffmpegProcess = builder.start(); // 保存进程实例 } catch (IOException e) { e.printStackTrace(); } } private void stopRecording() { if (ffmpegProcess == null || !ffmpegProcess.isAlive()) { return; } try { // 向FFmpeg发送退出指令,触发优雅收尾 ffmpegProcess.getOutputStream().write('q'); ffmpegProcess.getOutputStream().flush(); // 等待进程正常退出,最多等3秒 if (!ffmpegProcess.waitFor(3, TimeUnit.SECONDS)) { // 超时未退出则强制终止 ffmpegProcess.destroyForcibly(); } } catch (IOException | InterruptedException e) { e.printStackTrace(); ffmpegProcess.destroyForcibly(); } } }
- 关键优化点:
- 保存FFmpeg进程的直接引用,避免通过进程名杀死(可能误杀其他FFmpeg实例)。
- 发送
q字符让FFmpeg优雅退出,确保文件完成封装。 - 添加日志重定向,便于排查FFmpeg执行中的隐藏错误。
- 设置超时等待,超时后再强制终止,兼顾可靠性和效率。
内容的提问来源于stack exchange,提问作者Youssef Hammouma
相关产品推荐
相关产品推荐

