将视频渲染到帧速率可能不一致的文件

Rendering video to file that may have an inconsistent frame rate

我正在以不一致的速率从源(可视为黑盒)获取原始视频帧。我正在尝试将视频源录制到磁盘。我正在使用 AForge 的 VideoRecorder 这样做,并且正在写入 MP4 文件。

但是,我接收帧的速率不一致导致视频显示速度加快。似乎我只能创建具有固定帧率的视频文件,即使源没有固定帧率。

渲染到屏幕时这不是问题,因为我们可以尽可能快地渲染。写入文件时无法执行此操作,因为播放文件时会以固定帧速率播放。

有哪些解决方案?输出不必是相同的视频格式,只要有一些合理的方法可以稍后转换它(不一定是实时的)。视频源可能会很长,所以我不能将所有内容都存储在内存中然后再编码。

我的代码目前看起来是这样的:

VideoFileWriter writer = new VideoFileWriter();
Stopwatch stopwatch = new Stopwatch();

public override void Start() {
    writer.Open("output.mp4", videoWidth, videoHeight, frameRate, AForge.Video.FFMPEG.VideoCodec.MPEG4);
    stopwatch.Start();
}

public override void End() {
    writer.Close();
}

public override void Draw(Frame frame) {
    double elapsedTimeInSeconds = stopwatch.ElapsedTicks / (double) Stopwatch.Frequency;
    double timeBetweenFramesInSeconds = 1.0 / FrameRate;
    if (elapsedTimeInSeconds >= timeBetweenFramesInSeconds) {
        stopwatch.Restart();
        writer.WriteVideoFrame(frame.ToBitmap());
    }
}

我们的黑盒调用 StartEndDraw 方法的位置。我在 Draw 中进行的当前检查可防止我们绘制得太快,但不会采取任何措施来处理绘制太慢的情况。

事实证明 WriteVideoFrame 被重载,函数的一种变体是 WriteVideoFrame(Bitmap frame, TimeSpan timestamp)。你可以猜到,时间戳是用来让一帧出现在视频的某个时间点。

因此,通过跟踪实时,我们可以将每一帧设置为使用它应该在视频中出现的时间。当然,如果渲染速度不够快,视频质量会变差,但这解决了手头的问题。

这是我用于 Draw 函数的代码:

// We can provide a frame offset so that each frame has a time that it's supposed to be
// seen at. This ensures that the video looks correct if the render rate is lower than
// the frame rate, since these times will be used (it'll be choppy, but at least it'll
// be paced correctly -- necessary so that sound won't go out of sync).
long currentTick = DateTime.Now.Ticks;
StartTick = StartTick ?? currentTick;
var frameOffset = new TimeSpan(currentTick - StartTick.Value);

// Figure out if we need to render this frame to the file (ie, has enough time passed
// that this frame will be in the file?). This prevents us from going over the
// desired frame rate and improves performance (assuming that we can go over the frame
// rate).
double elapsedTimeInSeconds = stopwatch.ElapsedTicks / (double) Stopwatch.Frequency;
double timeBetweenFramesInSeconds = 1.0 / FrameRate;
if (elapsedTimeInSeconds >= timeBetweenFramesInSeconds)
{
    stopwatch.Restart();

    Writer.WriteVideoFrame(frame.ToBitmap(), frameOffset);
}

其中 StartTick 是对象的 long? 成员。

我也遇到过这个问题。就我而言,我正在使用 Aforge 模仿闭路电视系统。 CCTV应该在录制的时间上是准确的,所以我面临着一个很大的两难境地。这是我在这方面使用的解决方法。

首先,声明一个时间跨度,它将作为录制的基础。你需要在开始录音的时候设置这个意思。这个值就是你开始录音的时间。为了这个答案,我们称之为 tmspStartRecording

然后在您的捕获设备的新帧事件中:

var currentTime = DateTime.Now.TimeOfDay;
// this will get the elapse time between
// the current time from the time you start your recording
TimeSpan elapse = currentTime - tmspStartRecording;
writer.WriteVideoFrame((Bitmap)image.Clone(),elapse);

不要忘记设置起始时间跨度的值,好吗?