winforms 如何定时演示和从视频文件中提取帧?

eit6fx6z  于 2022-11-16  发布在  其他
关注(0)|答案(1)|浏览(234)

目标是控制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();
    }
}

我想使用一个计时器,或者其他可以控制帧提取速度的东西。

raogr8fs

raogr8fs1#

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

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

当然,当您打开此窗体并创建容器Bitmap时,资源会略有增加,但当窗体关闭时,会回收少量资源

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

buttonStart_ClickbuttonStop_ClickbuttonPause_Click是用于开始、停止和暂停播放的Button的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 (true) {
                if (token.IsCancellationRequested) break;
                // Resumes on a ThreadPool Thread
                await Task.Delay(intervalMs).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);
    }
}

相关问题