如何在BackgroundWorker的DoWork事件中控制视频帧提取速度?
控制BackgroundWorker中视频帧提取与显示的速度
你的问题核心是后台线程提取帧的速度远快于正常播放节奏,直接用Thread.Sleep()容易出异常,下面给两种可靠的解决方案:
方案1:基于视频帧率的精确时间间隔控制
利用视频本身的帧率计算每帧的等待时间,避免固定Sleep带来的误差,同时加入任务取消检查,符合BackgroundWorker的使用规范:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { using (var vFReader = new VideoFileReader()) { vFReader.Open(@"C:\Users\Chocolade 1972\Downloads\MyVid.mp4"); double frameRate = vFReader.FrameRate; // 计算每帧的理论间隔毫秒数(比如30帧就是约33ms) int frameInterval = (int)(1000 / frameRate); long lastFrameTick = DateTime.Now.Ticks; for (var i = 0; i < vFReader.FrameCount; i++) { // 读取当前帧并上报给UI var currentFrame = vFReader.ReadVideoFrame(); backgroundWorker1.ReportProgress(i, currentFrame); // 计算已消耗时间,动态调整等待时长 long currentTick = DateTime.Now.Ticks; long elapsedMs = (currentTick - lastFrameTick) / TimeSpan.TicksPerMillisecond; int waitMs = frameInterval - (int)elapsedMs; // 仅当需要等待且任务未取消时执行Sleep if (waitMs > 0 && !backgroundWorker1.CancellationPending) { Thread.Sleep(waitMs); } lastFrameTick = DateTime.Now.Ticks; // 检查是否需要终止任务 if (backgroundWorker1.CancellationPending) { e.Cancel = true; break; } } } }
方案2:用线程定时器调度帧读取
如果不想在循环里阻塞线程,可以用System.Threading.Timer来定时触发帧读取操作,更灵活且易于调整速度:
首先在Form类里添加字段:
private VideoFileReader _videoReader; private int _currentFrameIndex; private System.Threading.Timer _frameTimer; private int _frameInterval; // 自定义帧间隔毫秒数
修改启动按钮的点击事件:
private void button1_Click(object sender, EventArgs e) { // 初始化视频阅读器 _videoReader = new VideoFileReader(); _videoReader.Open(@"C:\Users\Chocolade 1972\Downloads\MyVid.mp4"); _currentFrameIndex = 0; // 按原帧率设置间隔,也可换成TrackBar的值自定义速度 _frameInterval = (int)(1000 / _videoReader.FrameRate); // 启动定时器:立即执行第一次,之后每隔_frameInterval毫秒执行一次 _frameTimer = new System.Threading.Timer(ReadFrameCallback, null, 0, _frameInterval); }
添加定时器回调方法:
private void ReadFrameCallback(object state) { // 终止条件:帧读完或任务被取消 if (_currentFrameIndex >= _videoReader.FrameCount || backgroundWorker1.CancellationPending) { _frameTimer.Change(Timeout.Infinite, Timeout.Infinite); _videoReader.Close(); _videoReader.Dispose(); return; } // 读取帧并上报 var frame = _videoReader.ReadVideoFrame(); backgroundWorker1.ReportProgress(_currentFrameIndex, frame); _currentFrameIndex++; }
最后添加窗体关闭时的资源清理:
protected override void OnFormClosing(FormClosingEventArgs e) { _frameTimer?.Change(Timeout.Infinite, Timeout.Infinite); _frameTimer?.Dispose(); _videoReader?.Close(); _videoReader?.Dispose(); base.OnFormClosing(e); }
关于Thread.Sleep()触发异常的说明
你之前遇到的异常大概率不是Sleep本身导致的,而是缺少任务取消检查、资源提前释放,或者Sleep期间UI线程的操作和后台线程产生冲突。上面两种方案都加入了资源管理和取消逻辑,能避免这类问题。
内容的提问来源于stack exchange,提问作者Daniel Lip
相关产品推荐
相关产品推荐

