C#调用FFmpeg解码H264流提取帧保存为JPEG始终为黑图
问题背景
我开发了一款C#应用程序,通过Socket接收H264码流,需要持续从该码流中获取最新帧图像。
实现基于FFmpeg 5.0.1,最初的实现逻辑如下:
- 启动FFmpeg进程的代码:
var ffmpegInfo = new ProcessStartInfo(FFMPEG_PATH); ffmpegInfo.RedirectStandardInput = true; ffmpegInfo.RedirectStandardOutput = true; ffmpegInfo.RedirectStandardError = true; ffmpegInfo.UseShellExecute = false; ffmpegInfo.Arguments = "-i pipe: -f h264 -pix_fmt bgr24 -an -sn pipe:"; ffmpegInfo.UseShellExecute = false; ffmpegInfo.CreateNoWindow = true; Process myFFmpeg = new Process(); myFFmpeg.StartInfo = ffmpegInfo; myFFmpeg.EnableRaisingEvents = true; myFFmpeg.Start(); var inStream = myFFmpeg.StandardInput.BaseStream; FileStream baseStream = myFFmpeg.StandardOutput.BaseStream as FileStream; myFFmpeg.BeginErrorReadLine();
- 启动独立线程,从Socket接收码流写入FFmpeg标准输入流:
// inStream 为上一步获取的 myFFmpeg.StandardInput.BaseStream var t = Task.Run(() => ReceiveStream(inStream));
- 从FFmpeg标准输出流读取解码结果:
byte[] decoded = new byte[Width * Height * 3]; int numBytesToRead = Width * Height * 3; int numBytesRead = 0; while (numBytesToRead > 0) { int n = baseStream.Read(decoded, 0, decoded.Length); Console.WriteLine($"Read {n} bytes"); if (n == 0) { break; } numBytesRead += n; numBytesToRead -= n; }
- 最后用ImageSharp库将读取到的
decoded字节数组保存为JPEG文件:
image.Save("test.jpeg", encoder);
故障现象
初始故障
保存得到的test.jpeg始终为纯黑图像,初始FFmpeg stderr日志如下:
Duration: N/A, bitrate: N/A Stream #0:0: Video: h264 (High), yuv420p(tv, smpte170m/bt470bg/smpte170m, progressive), 1080x2256, 25 fps, 25 tbr, 1200k tbn Stream mapping: Stream #0:0 -> #0:0 (h264 (native) -> h264 (libx264)) Incompatible pixel format 'bgr24' for codec 'libx264', auto-selecting format 'yuv444p' [libx264 @ 0x11b9068f0] using cpu capabilities: ARMv8 NEON [libx264 @ 0x11b9068f0] profile High 4:4:4 Predictive, level 5.0, 4:4:4, 8-bit Output #0, h264, to 'pipe:': Metadata: encoder : Lavf59.16.100 Stream #0:0: Video: h264, yuv444p(tv, smpte170m/bt470bg/smpte170m, progressive), 1080x2256, q=2-31, 25 fps, 25 tbn Metadata: encoder : Lavc59.18.100 libx264 Side data: cpb: bitrate max/min/avg: 0/0/0 buffer size: 0 vbv_delay: N/A frame= 1 fps=0.0 q=0.0 size= 0kB time=00:00:00.00 bitrate=N/A speed= 0x frame= 56 fps=0.0 q=0.0 size= 0kB time=00:00:00.00 bitrate=N/A speed= 0x frame= 87 fps= 83 q=28.0 size= 370kB time=00:00:01.16 bitrate=2610.6kbits/s speed=1.11x frame= 118 fps= 75 q=28.0 size= 698kB time=00:00:02.40 bitrate=2381.4kbits/s speed=1.54x frame= 154 fps= 75 q=28.0 size= 1083kB time=00:00:03.84 bitrate=2311.1kbits/s speed=1.86x ...
参数调整后的故障
将输出格式参数从h264修改为rawvideo,调整后的启动参数为:-i pipe: -f rawvideo -pix_fmt bgr24 -an -sn pipe:
调整后FFmpeg日志如下:
Input #0, h264, from 'pipe:': Duration: N/A, bitrate: N/A Stream #0:0: Video: h264 (High), yuv420p(tv, smpte170m/bt470bg/smpte170m, progressive), 1080x2256, 25 fps, 25 tbr, 1200k tbn Stream mapping: Stream #0:0 -> #0:0 (h264 (native) -> rawvideo (native)) // 约9条"No accelerated colorspace..."提示 [swscaler @ 0x128690000] [swscaler @ 0x1286a0000] No accelerated colorspace conversion found from yuv420p to bgr24. Output #0, rawvideo, to 'pipe:': Metadata: encoder : Lavf59.16.100 Stream #0:0: Video: rawvideo (BGR[24] / 0x18524742), bgr24(pc, gbr/bt470bg/smpte170m, progressive), 1080x2256, q=2-31, 1461888 kb/s, 25 fps, 25 tbn Metadata: encoder : Lavc59.18.100 rawvideo frame= 1 fps=0.0 q=0.0 size= 0kB time=00:00:00.00 bitrate=N/A speed= 0x // 此后FFmpeg无任何日志输出
调整参数后FFmpeg仅输出第一帧相关日志,之后不再产生输出,仍无法正常获取解码图像。
问题成因
- 初始纯黑图像问题:最初指定输出格式为
h264时,FFmpeg实际执行的是「解码H264→重新编码为H264」的流程,输出的是编码后的H264码流而非原始BGR像素数据,直接按原始像素读取自然得到错误内容。 - 调整为rawvideo后卡第一帧的问题由三点共同导致:
- 管道缓冲阻塞:FFmpeg默认对管道输出启用块缓冲,缓冲区填满后才会将数据刷到输出流,读取端拿不到数据,FFmpeg自身也会因为输出缓冲区满暂停解码流程。
- 读取逻辑缺陷:现有代码仅执行一次帧读取逻辑就会退出,且没有处理流读取的半包问题——单次
Stream.Read调用不保证能读满请求的字节数,未读满时直接使用缓冲区会得到残缺的帧数据。 - 输入流探测/参数缺失:FFmpeg默认会缓存一部分输入数据做格式探测,如果输入的H264码流没有在开头携带SPS/PPS参数集,或者探测长度设置过大,会导致FFmpeg长时间缓存数据不输出解码帧。
- 日志中出现的
No accelerated colorspace conversion found from yuv420p to bgr24只是普通警告,仅说明当前色彩空间转换走软件计算,不影响功能正常运行。
修复方案
- 修正FFmpeg启动参数,关闭缓冲减少探测延迟
将启动参数替换为以下内容,明确指定输入格式、缩短流探测时长、关闭IO缓冲:
进程启动后不要对标准输出流使用带默认缓冲的读取器,直接操作底层流对象。-f h264 -probesize 32 -analyzeduration 0 -i pipe: -avioflags direct -fflags nobuffer -f rawvideo -pix_fmt bgr24 -an -sn pipe:1 - 重写帧读取逻辑,实现持续稳定的帧获取
rawvideo每帧大小固定为宽*高*3字节,需要循环读取凑够整帧后再处理,不要读满一次就退出。参考实现:int frameSize = Width * Height * 3; byte[] frameBuffer = new byte[frameSize]; int readOffset = 0; while (true) { int readLen = baseStream.Read(frameBuffer, readOffset, frameSize - readOffset); if (readLen == 0) break; // 流断开,退出循环 readOffset += readLen; if (readOffset == frameSize) { // 凑够完整一帧,在这里处理图像:保存、推送到业务层都可以 using (var image = Image.LoadPixelData<Bgr24>(frameBuffer, Width, Height)) { image.Save("latest.jpeg", encoder); } readOffset = 0; // 重置偏移,开始读取下一帧 } } - 检查输入流写入逻辑
确认Socket接收的所有字节原封不动写入FFmpeg标准输入流,不要丢字节,尤其要确认码流开头携带SPS、PPS NAL单元。每次批量写入后调用inStream.Flush(),避免输入缓冲卡住解码流程。 - 按需添加丢帧逻辑(可选)
如果不需要解码所有帧、只需要拿到最新帧,可以在FFmpeg参数中添加-vf fps=你需要的帧率做抽帧,也可以在读取端检测流中积压的未读数据长度,直接跳过旧帧字节读取最新帧,避免帧延迟累积。
内容的提问来源于stack exchange,提问作者massivemoisture
相关产品推荐
相关产品推荐

