問題描述
如何使用 c# 編寫一個簡單的視頻播放器,以準確的 fps 播放視頻? (How to use c# to write a simple video player which plays the video with accurate fps?)
I'm write a simple video play with Emgu, which is a c# wrapper of opencv.
Now I can play a video, but I found the fps is not accurate, a little faster than normal.
In my code, I use Thread.sleep()
to wait a while between 2 frames. The code is:
int fps = getFromFile(file); // which is 30
while (true) {
var frame = CvInvoke.cvQueryFrame(capture);
if (frame.ToInt32() == 0) break;
Image<Bgr, byte> dest = new Image<Bgr, byte>(Convert.ToInt32(width), Convert.ToInt32(height));
CvInvoke.cvCopy(frame, dest, IntPtr.Zero);
box.Image = dest;
Thread.Sleep(1000 / Convert.ToInt32(fps));
}
Where is wrong, and how to fix it?
update
The box
in the code is Emgu.UI.ImageBox
, which is thread‑safety, and I can access it from other thread directly: box.Image = dest
‑‑‑‑‑
參考解法
方法 1:
You need to keep a queue of frame objects 'topped up' and render them using a timer of some sort. Usually, I would use a pool of frame objects that are continually recycled.
There is no simple video ‑ vlc.exe: 13 threads, realplay.exe: 19 threads, divX player: 23 threads.
方法 2:
My recommendation would be to use a timer
_capture = new Capture(ofd.FileName);
_videoTimer = new Timer();
double fps = _capture.GetCaptureProperty(CAP_PROP.CV_CAP_PROP_FPS);
_videoTimer.Interval = Convert.ToInt16(1000 / fps); //the timer interval
_videoTimer.Tick += new EventHandler(_videoTimer_Tick);
_videoTimer.Start();
and now show this in an image box
private void _videoTimer_Tick(object sender, EventArgs e)
{
Image<Bgr, Byte> img = _capture.QueryFrame();
if (img == null) return;
box.Image = img;
}
方法 3:
Timing an event at very small and accurate intervals is no trivial task, and Thread.Sleep certainly isn't what you want here.
First, Thread.Sleep doesn't guarantee your thread will resume in the specified amount of time, it just guarantees you'll be suspended for at least that amount.
Secondly, most timers are not accurate at the millisecond level you need here. These include System.Threading.Timer and System.Timers.Timer. You'll get jerky framerate at best using these. My solution of choice would be to write my own event loop with lag compensation using System.Diagostics.Stopwatch. This is fairly difficult but a good example is given in OpenTK's GameWindow class. Another solution might the Multimedia Timers, but those are not directly available from .NET and I have no experience using these.
Thirdly, don't use the same thread to post rendering events every 33ms and to actually render the frames; have a separate thread render frames continuously into a queue, and have a the timer thread simply pick the front frame. That way you free up the timer thread and allow it to be as accurate as it needs to be.
(by Freewind、Martin James、Mikos、Asik)