I want to extract all the frames from an MP4 video file and display them on a PictureBox.
The original code comes from this Q&A: How can I time the presentation and extraction of frames from a video file?
The exception happens after clicking the start button on the line:
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
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at Extract_Frames.Form1.<buttonStart_Click>d__17.MoveNext() in D:\Csharp Projects\Extract Frames\Form1.cs:line 151
using Accord.IO;
using Accord.Video;
using Accord.Video.FFMPEG;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Extract_Frames
{
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;
private int frameRate = 0;
private List<Bitmap> frames = new List<Bitmap>();
string fileName;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void StopPlayback(bool cancel)
{
lock (syncRoot)
{
if (cancel) cts?.Cancel();
cts?.Dispose();
cts = null;
}
}
int counter =1;
private void Updater(Bitmap videoFrame)
{
frames.Add(videoFrame);
label1.Text = "Current Frame Number : " + counter;
trackBar1.Value = counter;
counter++;
//Size size = new Size(videoFrame.Width, videoFrame.Height);
//pictureBox1.ClientSize = size;
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);
videoReader.ReadVideoFrame(1);
trackBar1.Value = 1;
label1.Text = "Current Frame Number : " + counter.ToString();
while (true)
{
if (Interlocked.Read(ref pause) == 0)
{
var frame = videoReader.ReadVideoFrame();
if (token.IsCancellationRequested || frame is null) break;
updater.Report(frame);
}
await Task.Delay(frameRate).ConfigureAwait(false);
}
}
}
private void trackBar2_Scroll(object sender, EventArgs e)
{
frameRate = trackBar2.Value / 25;
}
private async void buttonStart_Click(object sender, EventArgs e)
{
string fileName = textBox1.Text;
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>((bitmap) => Updater(bitmap));
cts = new CancellationTokenSource();
pictureBox1.Image = frame;
try
{
frameGraphics = Graphics.FromImage(frame);
// Set the fame rate to 25 frames per second
//int frameRate = 1000 / 25;
await GetVideoFramesAsync(videoProgress, fileName, frameRate, cts.Token);
}
finally
{
frameGraphics?.Dispose();
StopPlayback(false);
isVideoRunning = false;
}
}
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 buttonStop_Click(object sender, EventArgs e)
{
StopPlayback(true);
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
if (isVideoRunning) StopPlayback(true);
pictureBox1.Image?.Dispose();
base.OnFormClosing(e);
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
ControlPaint.DrawBorder(e.Graphics, pictureBox1.ClientRectangle, Color.Red, ButtonBorderStyle.Solid);
}
private void trackBar1_Scroll(object sender, EventArgs e)
{
pictureBox1.Image = frames[trackBar1.Value];
}
private void button1_Click(object sender, EventArgs e)
{
using (OpenFileDialog openFileDialog = new OpenFileDialog())
{
openFileDialog.InitialDirectory = "c:\\";
openFileDialog.Filter = "video files (*.mp4)|*.mp4|All files (*.*)|*.*";
openFileDialog.FilterIndex = 2;
openFileDialog.RestoreDirectory = true;
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
// Get the path of specified file
textBox1.Text = openFileDialog.FileName;
}
}
}
}
}
You can use a library called Emgu CV to achieve this, but since Emgu CV uses a container called Mat to store the bitmap of an image, you will need to define a list of Mats and loop through the frames in the video and add them to the list. The first step is to install a NuGet package called Emgu.Cv.runtime.windows and then put the code below in the event handler that triggers the reading process.
// At the top of your Form1.cs file, import the Emgu CV library
using Emgu.CV;
using Emgu.CV.Structure;
// The code below goes to the button handler for reading the frames
// Declare the path to the video
string videoPath = "video.mp4";
// List to hold the frames read from the video
List<Mat> frames = new List<Mat>();
// Open the video with the Emgu CV video capture class
var video = new VideoCapture(videoPath);
// Useful line for reading the frame rate from the video
var frameRate = (int)new VideoCapture(videoPath).Get(Emgu.CV.CvEnum.CapProp.Fps);
while (video. Grab())
{
// Read a frame and add to the list
var mat = new Mat();
video.Read(mat);
// Add the frame to the list
frames.Add(mat);
}
// Do something with the frames, for example access the
// frame at index 0 and display in the picture box
var first_mat = frames[0];
if(first_mat!=null){
var outputFile = "1.png";
// There isn't any direct way to get a bitmap from
// a Mat, so save to a file and use the file as an
// image location
first_mat.Save(outputFile);
pictureBox.ImageLocation = outputFile;
}