You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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仅输出第一帧相关日志,之后不再产生输出,仍无法正常获取解码图像。


问题成因
  1. 初始纯黑图像问题:最初指定输出格式为h264时,FFmpeg实际执行的是「解码H264→重新编码为H264」的流程,输出的是编码后的H264码流而非原始BGR像素数据,直接按原始像素读取自然得到错误内容。
  2. 调整为rawvideo后卡第一帧的问题由三点共同导致:
    • 管道缓冲阻塞:FFmpeg默认对管道输出启用块缓冲,缓冲区填满后才会将数据刷到输出流,读取端拿不到数据,FFmpeg自身也会因为输出缓冲区满暂停解码流程。
    • 读取逻辑缺陷:现有代码仅执行一次帧读取逻辑就会退出,且没有处理流读取的半包问题——单次Stream.Read调用不保证能读满请求的字节数,未读满时直接使用缓冲区会得到残缺的帧数据。
    • 输入流探测/参数缺失:FFmpeg默认会缓存一部分输入数据做格式探测,如果输入的H264码流没有在开头携带SPS/PPS参数集,或者探测长度设置过大,会导致FFmpeg长时间缓存数据不输出解码帧。
  3. 日志中出现的No accelerated colorspace conversion found from yuv420p to bgr24只是普通警告,仅说明当前色彩空间转换走软件计算,不影响功能正常运行。

修复方案
  1. 修正FFmpeg启动参数,关闭缓冲减少探测延迟
    将启动参数替换为以下内容,明确指定输入格式、缩短流探测时长、关闭IO缓冲:
    -f h264 -probesize 32 -analyzeduration 0 -i pipe: -avioflags direct -fflags nobuffer -f rawvideo -pix_fmt bgr24 -an -sn pipe:1
    
    进程启动后不要对标准输出流使用带默认缓冲的读取器,直接操作底层流对象。
  2. 重写帧读取逻辑,实现持续稳定的帧获取
    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; // 重置偏移,开始读取下一帧
        }
    }
    
  3. 检查输入流写入逻辑
    确认Socket接收的所有字节原封不动写入FFmpeg标准输入流,不要丢字节,尤其要确认码流开头携带SPS、PPS NAL单元。每次批量写入后调用inStream.Flush(),避免输入缓冲卡住解码流程。
  4. 按需添加丢帧逻辑(可选)
    如果不需要解码所有帧、只需要拿到最新帧,可以在FFmpeg参数中添加-vf fps=你需要的帧率做抽帧,也可以在读取端检测流中积压的未读数据长度,直接跳过旧帧字节读取最新帧,避免帧延迟累积。

内容的提问来源于stack exchange,提问作者massivemoisture

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.01 20:43:10