C#中FFmpeg编码解码串行执行异常问题排查
问题分析:FFmpeg编码解码进程阻塞问题
我在C#中创建了FFmpeg编码和解码进程,尝试执行以下流程:
- 获取测试原始数据
- 将数据流式传输至FFmpeg进程进行编码
- 读取编码后的数据并流式传输至FFmpeg进程进行解码
- 向控制台输出字节大小
为实现1/30秒的执行间隔,我还添加了
Thread.Sleep(1000/30)。但实际运行时,控制台仅输出一次原始帧和编码帧的字节大小后便无任何反应,以下是我的代码:
internal byte[] GetFrame(int width, int height) { int frameSize = width * height * 3; byte[] frameData = new byte[frameSize]; Random random = new Random(); for(int i = 0; i < frameData.Length; i++) { frameData[i] = (byte)random.Next(256); } return frameData; } internal void EncodeAndDecode(int width, int height, int fps, int bitrate, string codec, string preset, string tune) { Process ffmpegEncodeProcess = new Process { StartInfo = new ProcessStartInfo { FileName = "ffmpeg", Arguments = $"-f rawvideo -pixel_format rgb24 -s 540x720 -r 30 " + $"-i - -c:v libx264 -preset ultrafast -tune zerolatency -maxrate 1500k -b:v 1500k " + $"-f h264 pipe:1", RedirectStandardInput = true, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true } }; Process ffmpegDecodeProcess = new Process { StartInfo = new ProcessStartInfo { FileName = "ffmpeg", Arguments = $"-f h264 -i pipe:0 " + $"-f rawvideo -pixel_format rgb24 -s 540x720 pipe:1", RedirectStandardInput = true, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true } }; //start process ffmpegEncodeProcess.Start(); ffmpegDecodeProcess.Start(); //input stream var ffmpegEncodeInput = ffmpegEncodeProcess.StandardInput.BaseStream; var ffmpegDecodeInput = ffmpegDecodeProcess.StandardInput.BaseStream; //output stream var ffmpegEncodeOutput = ffmpegEncodeProcess.StandardOutput.BaseStream; var ffmpegDecodeOutput = ffmpegDecodeProcess.StandardOutput.BaseStream; new Thread(async () => { try { while(true) { byte[] encodedChunk = new byte[width * height * 3]; byte[] decodedChunk = new byte[width * height * 3]; byte[] frameData = GetFrame(width, height); Console.WriteLine($"[INFO] Original frame size: {frameData.Length}bytes."); if(frameData.Length == 0) { break; } await ffmpegEncodeInput.WriteAsync(frameData, 0, frameData.Length); await ffmpegEncodeInput.FlushAsync(); int encodedBytesRead = await ffmpegEncodeOutput.ReadAsync(encodedChunk, 0, encodedChunk.Length); Console.WriteLine($"[INFO] Encoded frame size: {encodedBytesRead}bytes."); if(encodedBytesRead > 0) { await ffmpegDecodeInput.WriteAsync(encodedChunk, 0, encodedBytesRead); await ffmpegDecodeInput.FlushAsync(); int decodedBytesRead = await ffmpegDecodeOutput.ReadAsync(decodedChunk, 0, decodedChunk.Length); Console.WriteLine($"[INFO] Decoded frame size: {decodedBytesRead}bytes."); if(decodedBytesRead > 0) { //do something... } } Thread.Sleep(1000 / fps); //1/30s } } catch(Exception e) { Console.WriteLine(e); } finally { ffmpegEncodeInput.Close(); ffmpegDecodeInput.Close(); if(!ffmpegEncodeProcess.HasExited) { ffmpegEncodeProcess.Kill(); } if(!ffmpegDecodeProcess.HasExited) { ffmpegDecodeProcess.Kill(); } Console.WriteLine("[INFO]: FFmpeg process clean up."); } }).Start(); }
核心问题与修复方案
1. FFmpeg错误流未读取导致进程阻塞
FFmpeg会持续向StandardError输出日志,若不读取该流,缓冲区会被填满,直接导致FFmpeg进程挂起,无法处理后续输入输出。必须后台读取错误流释放缓冲区:
// 启动进程后立即后台读取错误流 _ = Task.Run(() => ffmpegEncodeProcess.StandardError.ReadToEnd()); _ = Task.Run(() => ffmpegDecodeProcess.StandardError.ReadToEnd());
2. 编码输出读取逻辑错误
H.264编码后的帧大小远小于原始RGB帧,你用width*height*3作为编码缓冲区时,ReadAsync会等待缓冲区填满才返回,而编码数据不足以填满缓冲区,导致线程卡死。修复方式:无需修改缓冲区大小,但要接受ReadAsync返回的实际读取字节数(即使未填满缓冲区)。
3. 异步线程中误用Thread.Sleep
Thread.Sleep会阻塞整个线程,在异步lambda中应改用await Task.Delay(1000/fps),避免阻塞异步执行流。
4. 随机数生成器重复初始化
每次调用GetFrame新建Random实例,短时间内会生成重复随机数(默认种子基于系统时钟),应将Random作为类成员复用:
private readonly Random _random = new Random(); internal byte[] GetFrame(int width, int height) { int frameSize = width * height * 3; byte[] frameData = new byte[frameSize]; _random.NextBytes(frameData); // 用内置方法更高效 return frameData; }
5. 解码帧读取不完整
解码后的原始帧大小固定,单次ReadAsync可能无法读取完整帧,需循环读取直到获取全部数据:
int totalDecodedBytes = 0; while (totalDecodedBytes < decodedChunk.Length) { int bytesRead = await ffmpegDecodeOutput.ReadAsync(decodedChunk, totalDecodedBytes, decodedChunk.Length - totalDecodedBytes); if (bytesRead == 0) break; totalDecodedBytes += bytesRead; } Console.WriteLine($"[INFO] Decoded frame size: {totalDecodedBytes}bytes.");
修复后的完整代码
private readonly Random _random = new Random(); internal byte[] GetFrame(int width, int height) { int frameSize = width * height * 3; byte[] frameData = new byte[frameSize]; _random.NextBytes(frameData); return frameData; } internal void EncodeAndDecode(int width, int height, int fps, int bitrate, string codec, string preset, string tune) { Process ffmpegEncodeProcess = new Process { StartInfo = new ProcessStartInfo { FileName = "ffmpeg", Arguments = $"-f rawvideo -pixel_format rgb24 -s {width}x{height} -r {fps} " + $"-i - -c:v libx264 -preset {preset} -tune {tune} -maxrate {bitrate}k -b:v {bitrate}k " + $"-f h264 pipe:1", RedirectStandardInput = true, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true } }; Process ffmpegDecodeProcess = new Process { StartInfo = new ProcessStartInfo { FileName = "ffmpeg", Arguments = $"-f h264 -i pipe:0 " + $"-f rawvideo -pixel_format rgb24 -s {width}x{height} pipe:1", RedirectStandardInput = true, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true } }; ffmpegEncodeProcess.Start(); ffmpegDecodeProcess.Start(); // 后台读取错误流防止阻塞 _ = Task.Run(() => ffmpegEncodeProcess.StandardError.ReadToEnd()); _ = Task.Run(() => ffmpegDecodeProcess.StandardError.ReadToEnd()); var ffmpegEncodeInput = ffmpegEncodeProcess.StandardInput.BaseStream; var ffmpegDecodeInput = ffmpegDecodeProcess.StandardInput.BaseStream; var ffmpegEncodeOutput = ffmpegEncodeProcess.StandardOutput.BaseStream; var ffmpegDecodeOutput = ffmpegDecodeProcess.StandardOutput.BaseStream; new Thread(async () => { try { while (true) { byte[] encodedChunk = new byte[width * height * 3]; byte[] decodedChunk = new byte[width * height * 3]; byte[] frameData = GetFrame(width, height); Console.WriteLine($"[INFO] Original frame size: {frameData.Length}bytes."); await ffmpegEncodeInput.WriteAsync(frameData, 0, frameData.Length); await ffmpegEncodeInput.FlushAsync(); int encodedBytesRead = await ffmpegEncodeOutput.ReadAsync(encodedChunk, 0, encodedChunk.Length); Console.WriteLine($"[INFO] Encoded frame size: {encodedBytesRead}bytes."); if (encodedBytesRead > 0) { await ffmpegDecodeInput.WriteAsync(encodedChunk, 0, encodedBytesRead); await ffmpegDecodeInput.FlushAsync(); // 循环读取完整解码帧 int totalDecodedBytes = 0; while (totalDecodedBytes < decodedChunk.Length) { int bytesRead = await ffmpegDecodeOutput.ReadAsync(decodedChunk, totalDecodedBytes, decodedChunk.Length - totalDecodedBytes); if (bytesRead == 0) break; totalDecodedBytes += bytesRead; } Console.WriteLine($"[INFO] Decoded frame size: {totalDecodedBytes}bytes."); } // 异步延迟替代Thread.Sleep await Task.Delay(1000 / fps); } } catch (Exception e) { Console.WriteLine($"[ERROR] {e.Message}"); } finally { ffmpegEncodeInput.Close(); ffmpegDecodeInput.Close(); if (!ffmpegEncodeProcess.HasExited) ffmpegEncodeProcess.Kill(); if (!ffmpegDecodeProcess.HasExited) ffmpegDecodeProcess.Kill(); Console.WriteLine("[INFO]: FFmpeg process cleaned up."); } }).Start(); }
额外优化建议
- 给编码进程添加
-g 1参数,强制每帧为独立I帧,避免解码依赖前序帧导致的阻塞。 - 监听进程
Exited事件,及时处理FFmpeg异常退出的情况。
内容的提问来源于stack exchange,提问作者Minwoo Kim
相关产品推荐
相关产品推荐

