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

使用Accord.Video.FFMPEG提取MP4帧时遇参数无效异常的解决方法

解决MP4帧提取时的“参数无效”ArgumentException异常

问题场景

从MP4视频提取所有帧并在PictureBox中显示,代码参考Stack Overflow问答实现,点击开始按钮后,在var frame = videoReader.ReadVideoFrame();行抛出以下异常:

System.ArgumentException
  HResult=0x80070057
  Message=Parameter is not valid.
  Source=System.Drawing
  StackTrace:
   at System.Drawing.Bitmap..ctor(Int32 width, Int32 height, PixelFormat format)
   at Accord.Video.FFMPEG.VideoFileReader.DecodeVideoFrame(BitmapData bitmapData)
   at Accord.Video.FFMPEG.VideoFileReader.readVideoFrame(Int32 frameIndex, BitmapData output)
   at Accord.Video.FFMPEG.VideoFileReader.ReadVideoFrame()
   at Extract_Frames.Form1.<GetVideoFramesAsync>d__15.MoveNext() in D:\Csharp Projects\Extract Frames\Form1.cs:line 114
   ...

问题根源

  1. 重复打开视频文件:buttonStart_Click和GetVideoFramesAsync中分别实例化VideoFileReader并打开同一文件,导致文件读取状态冲突。
  2. 错误的帧索引调用:videoReader.ReadVideoFrame(1)指定索引读取帧,不仅可能因视频帧索引从0开始导致读取异常,还会打乱后续逐帧读取的状态。
  3. Bitmap生命周期管理错误:Updater方法中用using (videoFrame)释放帧对象,但同时将该对象加入frames列表,后续滚动trackBar时访问的是已释放的Bitmap,引发参数无效异常。
  4. frameRate初始值异常:trackBar2未调整时初始值为0,计算后frameRate=0,Task.Delay(0)会导致高频循环,加剧视频读取的资源冲突。

修复方案及代码修改

1. 统一视频读取逻辑,避免重复打开

移除buttonStart_Click中额外的VideoFileReader实例,将视频信息读取逻辑移到GetVideoFramesAsync中:

private async void buttonStart_Click(object sender, EventArgs e)
{
    string fileName = textBox1.Text;
    if (string.IsNullOrEmpty(fileName) || isVideoRunning) return;

    isVideoRunning = true;
    counter = 1;
    frames.Clear(); // 清空之前的帧列表

    videoProgress = new Progress<Bitmap>((bitmap) => Updater(bitmap));
    cts = new CancellationTokenSource();

    try
    {
        // 初始化frameRate默认值为40ms(对应25帧/秒)
        frameRate = frameRate == 0 ? 40 : frameRate;
        await GetVideoFramesAsync(videoProgress, fileName, frameRate, cts.Token);
    }
    finally
    {
        frameGraphics?.Dispose();
        frame?.Dispose();
        StopPlayback(false);
        isVideoRunning = false;
        buttonPause.Text = "Pause";
        Interlocked.Exchange(ref pause, 0);
    }
}

2. 修正帧读取逻辑

移除错误的指定索引读取,改为正常逐帧读取,并在读取前初始化显示用的Bitmap:

private async Task GetVideoFramesAsync(IProgress<Bitmap> updater, string fileName, int intervalMs, CancellationToken token = default)
{
    using (var videoReader = new VideoFileReader())
    {
        if (token.IsCancellationRequested) return;
        videoReader.Open(fileName);

        // 初始化显示用的Bitmap
        if (frame == null)
        {
            frame = new Bitmap(videoReader.Width, videoReader.Height);
            frameGraphics = Graphics.FromImage(frame);
            pictureBox1.Image = frame;
            trackBar1.Maximum = (int)videoReader.FrameCount;
        }

        label1.Text = "Current Frame Number : 1";
        trackBar1.Value = 1;

        while (true)
        {
            if (token.IsCancellationRequested) break;
            
            if (Interlocked.Read(ref pause) == 0)
            {
                var videoFrame = videoReader.ReadVideoFrame();
                if (videoFrame == null) break;

                updater.Report(videoFrame);
            }
            await Task.Delay(intervalMs).ConfigureAwait(false);
        }
    }
}

3. 修正Bitmap生命周期管理

移除Updater中的using (videoFrame),改为在停止播放或程序退出时统一清理帧列表:

private void Updater(Bitmap videoFrame)
{
    frames.Add(videoFrame);

    label1.Text = "Current Frame Number : " + counter;
    trackBar1.Value = counter;
    counter++;

    frameGraphics.DrawImage(videoFrame, Point.Empty);
    pictureBox1.Invalidate();
}

// 补充清理逻辑到StopPlayback
private void StopPlayback(bool cancel)
{
    lock (syncRoot)
    {
        if (cancel) cts?.Cancel();
        cts?.Dispose();
        cts = null;
    }

    // 清理帧列表
    foreach (var bmp in frames)
    {
        bmp.Dispose();
    }
    frames.Clear();
    counter = 1;
}

// 补充清理逻辑到窗体关闭事件
protected override void OnFormClosing(FormClosingEventArgs e)
{
    if (isVideoRunning) StopPlayback(true);
    pictureBox1.Image?.Dispose();
    frame?.Dispose();
    frameGraphics?.Dispose();
    // 清理剩余帧
    foreach (var bmp in frames)
    {
        bmp.Dispose();
    }
    frames.Clear();
    base.OnFormClosing(e);
}

4. 修正trackBar滚动事件,避免越界

修改trackBar1_Scroll方法,添加边界检查:

private void trackBar1_Scroll(object sender, EventArgs e)
{
    int index = trackBar1.Value - 1; // 列表索引从0开始,计数器从1开始
    if (index >= 0 && index < frames.Count)
    {
        frameGraphics.DrawImage(frames[index], Point.Empty);
        pictureBox1.Invalidate();
        label1.Text = "Current Frame Number : " + trackBar1.Value;
    }
}

5. 修正frameRate计算逻辑

确保frameRate不会为0,避免Task.Delay(0)导致的高频循环:

private void trackBar2_Scroll(object sender, EventArgs e)
{
    // 确保最小为1ms
    frameRate = Math.Max(1, trackBar2.Value / 25);
}

关键注意事项

  • 始终用using块包裹VideoFileReader,避免资源泄漏。
  • 严格管理Bitmap生命周期,禁止访问已释放的对象。
  • 多线程更新UI必须通过IProgress<T>确保在UI线程执行。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 08:58:12