.net 如何为视频文件中的帧的呈现和提取计时?

dhxwm5r4  于 2023-05-19  发布在  .NET
关注(0)|答案(1)|浏览(155)

目标是控制BackGroundWorker的DoWork事件中的帧提取速度。
我尝试了Thread.Sleep(),但它抛出了一个异常。
这就是我想做的。上面和下面都有描述。

using Accord.Video;
using Accord.Video.FFMPEG;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Windows.Forms;

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();

        using (var vFReader = new VideoFileReader())
        {
            vFReader.Open(@"C:\Users\Chocolade 1972\Downloads\MyVid.mp4");
            trackBar1.Maximum = (int)vFReader.FrameCount;
        }
    }

    private void button1_Click(object sender, EventArgs e)
    {
        backgroundWorker1.RunWorkerAsync();
    }

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        using (var vFReader = new VideoFileReader())
        {
            vFReader.Open(@"C:\Users\Chocolade 1972\Downloads\MyVid.mp4");
            for (var i = 0; i < vFReader.FrameCount; i++)
            {
                backgroundWorker1.ReportProgress(0, vFReader.ReadVideoFrame());
            }

            // Not sure that this would be required as it might happen implicitly at the end of the 'using' block.
            vFReader.Close();
        }
    }

    private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        pictureBox1.Image?.Dispose();
        pictureBox1.Image = (Image)e.UserState;
    }

    private void Form1_Resize(object sender, EventArgs e)
    {
        label1.Text = this.Size.ToString();
    }
}

它工作正常,但太快了。我如何使用定时器,或其他可以控制帧提取速度的东西?

0md85ypi

0md85ypi1#

我建议对当前代码做一些修改(实际上是很多)。
要点:
1.创建一个异步方法来执行视频播放。VideoFileReader在ThreadPool线程(实际上是2)上工作,它不会导致Form冻结
1.使用IProgress委托(类型为Progress<Bitmap>,此处命名为videoProgress),将用于更新PictureBox控件的新数据封送到UI线程。委托方法名为Updater
1.使用单个位图对象,设置为PictureBox的Image属性
1.使用从此位图派生的Graphics对象绘制视频帧。这允许包含所使用的资源。PictureBox只是失效,以显示位图的当前内容
1.允许视频播放方法接受帧速率值,此处设置为每秒25帧。当然,它可以适应放慢或加快播放(注意,设置超过32~35帧每秒,你开始失去一些帧)
1.使用CancellationTokenSource向视频播放方法发出信号,以在播放处于活动状态时按下停止按钮或关闭窗体时停止播放并终止
重要提示:

  • VideoFileReader返回的Bitmap必须处理。如果不这样做,您将看到图形资源的消耗量不断增加,而且不会停止
  • 使用单个位图并使用派生的Graphics对象绘制每个新帧,可以保留图形资源。如果在播放视频时保持“诊断工具”窗格打开,您会注意到没有泄漏任何资源,并且内存使用量保持不变。

当然,当您打开这个窗体并创建容器Bitmap时,会有一点增加,但是当窗体关闭时,会回收少量的资源

  • 这也允许更平滑的过渡和更快的渲染速度(在视频播放时移动Form)。此外,尝试锚/停靠PictureBox,设置SizeMode = Zoom并最大化Form(设置PictureBox的Zoom模式会影响性能,您应该调整位图的大小)

buttonStart_ClickbuttonStop_ClickbuttonPause_Click是用于开始、停止和暂停播放的按钮的Click处理程序。
syncRoot对象在这里不是严格必需的,但是保留它,它可能在某些时候会变得有用。

using System.Drawing;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Accord.Video.FFMPEG;

public partial class Form1 : Form
{
    Bitmap frame = null;
    Graphics frameGraphics = null;
    bool isVideoRunning = false;
    IProgress<Bitmap> videoProgress = null;
    private CancellationTokenSource cts = null;
    private readonly object syncRoot = new object();
    private static long pause = 0;

    public Form1() => InitializeComponent(); 

    private async void buttonStart_Click(object sender, EventArgs e) {
        string fileName = "[The Video File Path]";

        if (isVideoRunning) return;
        isVideoRunning = true;

        using (var videoReader = new VideoFileReader()) {
            videoReader.Open(fileName);
            frame = new Bitmap(videoReader.Width + 2, videoReader.Height + 2);
            trackBar1.Maximum = (int)videoReader.FrameCount;
        }

        videoProgress = new Progress<Bitmap>(Updater);
        cts = new CancellationTokenSource();
        pictureBox1.Image = frame;
        try {
            frameGraphics = Graphics.FromImage(frame);
            // Set the frame rate to 25 frames per second
            int frameRate = 1000 / 25;
            await GetVideoFramesAsync(videoProgress, fileName, frameRate, cts.Token);
        }
        finally {
            StopPlayback(false);
            frameGraphics?.Dispose();
            pictureBox1.Image?.Dispose();
            pictureBox1.Image = null;
            buttonPause.Text = "Pause";
            pause = 0;
            isVideoRunning = false;
        }
    }

    private void buttonStop_Click(object sender, EventArgs e) => StopPlayback(true);

    private void buttonPause_Click(object sender, EventArgs e)
    {
        if (pause == 0) {
            buttonPause.Text = "Resume";
            Interlocked.Increment(ref pause);
        }
        else {
            Interlocked.Decrement(ref pause);
            buttonPause.Text = "Pause";
        }
    }

    private void StopPlayback(bool cancel) {
        lock (syncRoot) {
            if (cancel) cts?.Cancel();
            cts?.Dispose();
            cts = null;
        }
    }

    private void Updater(Bitmap videoFrame) {
        using (videoFrame) frameGraphics.DrawImage(videoFrame, Point.Empty);
        pictureBox1.Invalidate();
    }

    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);

            while (!token.IsCancellationRequested) {
                // Resumes on a ThreadPool Thread
                await Task.Delay(intervalMs, token).ConfigureAwait(false);

                if (Interlocked.Read(ref pause) == 0) {
                    var frame = videoReader.ReadVideoFrame();
                    if (frame is null) break;
                    updater.Report(frame);
                }
            }
        }
    }

    protected override void OnFormClosing(FormClosingEventArgs e) {
        if (isVideoRunning) StopPlayback(true);
        base.OnFormClosing(e);
    }
}

相关问题