我正在用Emgu编写一个简单的视频播放,这是opencv的c#包装器。
现在我可以播放视频了,但是我发现fps不准确,比平时快一点。
在我的代码中,我使用 Thread.sleep()
在 2 帧之间等待一段时间。代码为:
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));
}
哪里出了问题,如何解决?
更新
代码中的box
是Emgu.UI.ImageBox
,这是线程安全的,我可以直接从其他线程访问它:box.Image = dest
您需要保持帧对象的队列"充值"并使用某种计时器渲染它们。 通常,我会使用不断回收的框架对象池。
没有简单的视频 - vlc.exe:13 个线程,realplay.exe:19 个线程,divX 播放器:23 个线程。
我的建议是使用计时器
_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();
现在在图像框中显示它
private void _videoTimer_Tick(object sender, EventArgs e)
{
Image<Bgr, Byte> img = _capture.QueryFrame();
if (img == null) return;
box.Image = img;
}
以非常小而准确的间隔对事件进行计时并非易事,Thread.Sleep 当然不是您想要的。
首先,Thread.Sleep 不保证您的线程会在指定的时间内恢复,它只是保证您至少会在该时间内被暂停。
其次,大多数计时器在您需要的毫秒级别上都不准确。其中包括System.Threading.Timer和System.Timers.Timer。使用这些最多只能获得生涩的帧率。我选择的解决方案是使用 System.Diagostics.Stopwatch 编写我自己的带有滞后补偿的事件循环。这是相当困难的,但在OpenTK的GameWindow类中给出了一个很好的例子。另一种解决方案可能是多媒体计时器,但这些不能直接从 .NET 获得,我没有使用这些计时器的经验。
第三,不要使用相同的线程每 33 毫秒发布一次渲染事件并实际渲染帧;让一个单独的线程连续地将帧渲染到队列中,并且让计时器线程简单地选择前帧。这样,您可以释放计时器线程并使其尽可能准确。