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
相关产品推荐
相关产品推荐

