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

C# WinForms如何读取FFmpeg管道流并实时预览音视频录制画面

解决方案

方案1:基于FFmpeg管道输出的实时解码实现

步骤1:修改FFmpeg启动参数

直接调整FFmpeg命令,同时输出原始视频帧到管道用于界面显示,以及输出MP4文件落地存储,省去二次编码的性能损耗:

Process ffmpegProcess = new Process();
ffmpegProcess.StartInfo = new ProcessStartInfo
{
    FileName = "ffmpeg",
    Arguments = $"-f dshow -video_size 1280x720 -framerate 30 -vcodec mjpeg -i video=\"{video设备名}\":audio=\"{audio设备名}\" -pix_fmt bgr24 -f rawvideo -r 30 pipe:1 -pix_fmt yuv420p -b:v 4M -f mp4 -movflags frag_keyframe+empty_moov {输出文件路径}",
    RedirectStandardOutput = true,
    RedirectStandardInput = true,
    RedirectStandardError = true,
    UseShellExecute = false,
    CreateNoWindow = true
};

参数说明:输出到pipe:1的是BGR24格式的原始视频帧,和WinForms原生Bitmap的像素格式完全匹配,不需要额外转色,性能最优。

步骤2:单独线程读取管道流并渲染

开后台线程读取FFmpeg的标准输出,拿到完整帧之后转成Bitmap更新UI:

// 全局变量定义
private bool isRecording = false;
private Thread readFrameThread;
private const int FrameWidth = 1280;
private const int FrameHeight = 720;
private const int FrameByteSize = FrameWidth * FrameHeight * 3; // BGR24单帧字节数

// 启动录制后开启读帧线程
ffmpegProcess.Start();
isRecording = true;
readFrameThread = new Thread(ReadFrameLoop);
readFrameThread.IsBackground = true;
readFrameThread.Start();

// 读帧循环逻辑
private void ReadFrameLoop()
{
    byte[] frameBuffer = new byte[FrameByteSize];
    while (isRecording && !ffmpegProcess.HasExited)
    {
        int totalRead = 0;
        // 读满一帧数据再处理
        while (totalRead < FrameByteSize)
        {
            int readCount = ffmpegProcess.StandardOutput.BaseStream.Read(frameBuffer, totalRead, FrameByteSize - totalRead);
            if (readCount == 0) break;
            totalRead += readCount;
        }
        if (totalRead != FrameByteSize) continue;

        // 转Bitmap并更新界面
        using (var bmp = new Bitmap(FrameWidth, FrameHeight, System.Drawing.Imaging.PixelFormat.Format24bppRgb))
        {
            var bmpData = bmp.LockBits(new Rectangle(0, 0, FrameWidth, FrameHeight), System.Drawing.Imaging.ImageLockMode.WriteOnly, bmp.PixelFormat);
            System.Runtime.InteropServices.Marshal.Copy(frameBuffer, 0, bmpData.Scan0, FrameByteSize);
            bmp.UnlockBits(bmpData);

            // 跨线程更新UI控件
            pictureBox1.Invoke(new Action(() =>
            {
                pictureBox1.Image?.Dispose();
                pictureBox1.Image = (Bitmap)bmp.Clone();
            }));
        }
    }
}

步骤3:安全退出逻辑

录制停止时必须正常关闭FFmpeg进程,避免MP4文件损坏:

private void StopRecord()
{
    isRecording = false;
    readFrameThread?.Join(1000);
    if (!ffmpegProcess.HasExited)
    {
        // 给FFmpeg发送退出指令,正常收尾文件
        ffmpegProcess.StandardInput.Write('q');
        ffmpegProcess.WaitForExit(2000);
        if (!ffmpegProcess.HasExited) ffmpegProcess.Kill();
    }
    ffmpegProcess?.Dispose();
}

注意事项:

  • 读帧线程必须设为后台线程,避免程序退出时卡住进程
  • 所有UI控件操作必须通过Invoke/BeginInvoke调度到UI线程执行
  • 调整视频分辨率时要同步修改FrameWidth/FrameHeight/FrameByteSize三个参数,否则会出现花屏

方案2:其他可行实现方案

  • 用FFmpeg.AutoGen库直接绑定FFmpeg原生API,不需要启动外部进程,对流的控制更灵活,延迟更低,可直接实现采集、编码、存文件、显示全流程逻辑
  • 用OpenCVSharp的VideoCapture接口读取摄像头流,自带渲染逻辑,同时配合NAudio采集音频,最后用FFmpeg混流生成MP4,代码实现更简单
  • 之前用Aforge.DirectShow画质差是因为默认编码器参数过低,直接调整编码比特率、关键帧间隔参数即可获得高清画质,不需要替换整个技术栈

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 08:15:04