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

如何在C#中用FFmpeg在PictureBox播放视频并解决Base64转换问题?

解决C#中FFmpeg流式播放视频到PictureBox的Base64转换错误

问题根源

你当前代码的核心错误是错误地将FFmpeg输出的原始二进制帧数据当成了Base64字符串处理。image2pipe输出的是未经编码的RGB24像素流,属于二进制数据,而非文本格式的Base64。用BeginOutputReadLine()读取文本流会破坏二进制数据结构,后续的Convert.FromBase64String()自然会抛出非法字符的错误。

正确实现方案

核心思路

  1. 直接读取FFmpeg的StandardOutput二进制流,而非文本行
  2. 提前获取视频的宽高,计算单帧RGB24数据的字节数(宽×高×3)
  3. 在后台线程中循环读取帧数据,转换为Bitmap后跨线程更新PictureBox
  4. 严格管理资源,避免内存泄漏

完整修正代码

全局变量定义

private Process _ffmpegProcess;
private Thread _frameReadThread;
private int _videoWidth;
private int _videoHeight;

播放按钮点击事件

private void button5_Click(object sender, EventArgs e)
{
    if (_ffmpegProcess != null) return;

    string videoPath = @"C:/Users/google/Desktop/New folder/video.mp4";
    string ffmpegPath = @"C:/Users/google/Desktop/ffmpeg.exe";

    // 获取视频分辨率,用于计算单帧字节数
    GetVideoResolution(ffmpegPath, videoPath, out _videoWidth, out _videoHeight);
    if (_videoWidth == 0 || _videoHeight == 0)
    {
        MessageBox.Show("无法获取视频分辨率");
        return;
    }

    _ffmpegProcess = new Process();
    _ffmpegProcess.StartInfo.FileName = ffmpegPath;
    // 指定输出RGB24原始帧,保持原视频帧率
    _ffmpegProcess.StartInfo.Arguments = $"-i \"{videoPath}\" -vf format=rgb24 -r 30 -f rawvideo -pix_fmt rgb24 -";
    _ffmpegProcess.StartInfo.RedirectStandardOutput = true;
    _ffmpegProcess.StartInfo.UseShellExecute = false;
    _ffmpegProcess.StartInfo.CreateNoWindow = true;

    _ffmpegProcess.Start();

    // 启动后台线程读取二进制帧
    _frameReadThread = new Thread(ReadFrames);
    _frameReadThread.IsBackground = true;
    _frameReadThread.Start();
}

获取视频分辨率方法

private void GetVideoResolution(string ffmpegPath, string videoPath, out int width, out int height)
{
    width = 0;
    height = 0;
    Process process = new Process();
    process.StartInfo.FileName = ffmpegPath;
    process.StartInfo.Arguments = $"-i \"{videoPath}\" -hide_banner";
    process.StartInfo.RedirectStandardError = true; // FFmpeg信息输出到stderr
    process.StartInfo.UseShellExecute = false;
    process.StartInfo.CreateNoWindow = true;
    process.Start();

    string output = process.StandardError.ReadToEnd();
    process.WaitForExit();

    // 正则解析宽高
    var regex = new System.Text.RegularExpressions.Regex(@"Stream #0:0.*Video:.* (\d+)x(\d+)");
    var match = regex.Match(output);
    if (match.Success)
    {
        width = int.Parse(match.Groups[1].Value);
        height = int.Parse(match.Groups[2].Value);
    }
}

后台线程读取帧并更新UI

private void ReadFrames()
{
    int frameSize = _videoWidth * _videoHeight * 3; // RGB24每个像素占3字节
    byte[] frameBuffer = new byte[frameSize];

    try
    {
        while (_ffmpegProcess != null && !_ffmpegProcess.HasExited)
        {
            // 读取完整一帧的字节数据
            int bytesRead = _ffmpegProcess.StandardOutput.Read(frameBuffer, 0, frameSize);
            if (bytesRead != frameSize) break; // 读取不完整,视频结束或出错

            // 将RGB24字节流转换为Bitmap
            using (Bitmap bitmap = new Bitmap(_videoWidth, _videoHeight, System.Drawing.Imaging.PixelFormat.Format24bppRgb))
            {
                var bmpData = bitmap.LockBits(
                    new System.Drawing.Rectangle(0, 0, _videoWidth, _videoHeight),
                    System.Drawing.Imaging.ImageLockMode.WriteOnly,
                    bitmap.PixelFormat);

                System.Runtime.InteropServices.Marshal.Copy(frameBuffer, 0, bmpData.Scan0, frameSize);
                bitmap.UnlockBits(bmpData);

                // 跨线程更新PictureBox,同时释放旧资源
                pictureBox1.Invoke((MethodInvoker)(() =>
                {
                    pictureBox1.Image?.Dispose();
                    pictureBox1.Image = new Bitmap(bitmap);
                }));
            }
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show($"读取视频帧出错:{ex.Message}");
    }
    finally
    {
        StopVideo();
    }
}

停止播放及资源释放

private void StopVideo()
{
    // 终止后台读取线程
    if (_frameReadThread != null && _frameReadThread.IsAlive)
    {
        _frameReadThread.Abort();
        _frameReadThread = null;
    }

    // 停止FFmpeg进程
    if (_ffmpegProcess == null) return;
    try
    {
        if (!_ffmpegProcess.HasExited) _ffmpegProcess.Kill();
    }
    catch { }
    finally
    {
        _ffmpegProcess.Dispose();
        _ffmpegProcess = null;
    }

    // 清空PictureBox并释放资源
    pictureBox1.Invoke((MethodInvoker)(() =>
    {
        pictureBox1.Image?.Dispose();
        pictureBox1.Image = null;
    }));
}

窗体关闭时清理

private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
    StopVideo();
}

关键注意事项

  • 禁用文本读取:绝对不能用OutputDataReceived或BeginOutputReadLine()处理二进制帧数据,必须直接读取StandardOutput的字节流
  • 资源释放:每次更新PictureBox时,务必释放旧的Image对象,否则会导致严重的内存泄漏
  • 线程安全:UI控件只能在UI线程更新,必须用Invoke或BeginInvoke切换线程上下文
  • 帧率控制:FFmpeg命令中-r 30指定输出帧率,可根据视频实际情况调整,避免丢帧或卡顿

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 18:25:00