CSource 过滤器上的 SetMediaTime 使输出 AVI 变得毫无意义 - 知道为什么吗?

SetMediaTime on a CSource filter makes output AVI nonsense - any idea why?

更新:我最初发布的代码并没有实际重现问题;对于没有验证它,我深表歉意。奇怪行为的关键是一帧结束和下一帧开始之间的小增量(300 UNITS = 30 微秒)。出于某种原因,我使用的捕获硬件报告的帧速率与其在提供捕获帧及其时间戳时实际显示的帧速率不同。我已经更新了下面的源代码,以举例说明如何模仿这种行为。

我为 directshow 写了一个简单的 "fake" 图像源过滤器,源自 CSource。它运作良好。但我注意到一些我无法解释的奇怪现象。我的 FillBuffer 看起来像:

const REFERENCE_TIME TIME_PER_FRAME = 166000;

HRESULT MyFilterOutputPin::FillBuffer(IMediaSample *pms)
{
    //fill the bytes of the image media sample
    static REFERENCE_TIME currentTime = 0;
    REFERENCE_TIME startTime = currentTime;
    REFERENCE_TIME endTime = currentTime + TIME_PER_FRAME; //60Hz video
    // The +300 below is an update not in the original question, and is the
    // key to reproducing the behavior.
    currentTime += TIME_PER_FRAME + 300;
    pms->SetTime(&startTime, &endTime);
    pms->SetMediaTime(&startTime, &endTime);
    return S_OK;
}

我的 CMediaType 是通过调用

设置的
SetCMediaTypeForBitmap(1920,1080,TIME_PER_FRAME,&cmt);

该函数的实现方式为

void SetCMediaTypeForBitmap(unsigned long width, unsigned long height, REFERENCE_TIME averageTimePerFrame, CMediaType *pmt)
{
    CMediaType mt;
    mt.SetType(&MEDIATYPE_Video);
    mt.SetSubtype(&MEDIASUBTYPE_RGB24);
    mt.SetFormatType(&FORMAT_VideoInfo);
    mt.SetSampleSize(GetBitmapBufferSize(width, height, BIT_COUNT));
    auto pvi = (VIDEOINFOHEADER*)mt.AllocFormatBuffer(sizeof(VIDEOINFOHEADER));
    pvi->rcSource.left = pvi->rcSource.top = 0;
    pvi->rcSource.right = width;
    pvi->rcSource.bottom = height;
    pvi->rcTarget = pvi->rcSource;
    pvi->dwBitErrorRate = 0;
    pvi->AvgTimePerFrame = averageTimePerFrame;
    pvi->bmiHeader.biSize = 40;
    pvi->bmiHeader.biWidth = width;
    pvi->bmiHeader.biHeight = height;
    pvi->bmiHeader.biPlanes = 1;
    pvi->bmiHeader.biBitCount = BIT_COUNT;
    pvi->bmiHeader.biCompression = 0;
    pvi->bmiHeader.biSizeImage = mt.lSampleSize;
    pvi->dwBitRate = (DWORD)(((uint64_t)mt.lSampleSize) * 8 / pvi->AvgTimePerFrame * UNITS);
    pvi->bmiHeader.biXPelsPerMeter = pvi->bmiHeader.biYPelsPerMeter = pvi->bmiHeader.biClrUsed = pvi->bmiHeader.biClrImportant = 0;
    *pmt = mt;
}

如果我尝试在 MyFilterOutputPin::FillBuffer 覆盖中设置样本的媒体时间,然后将输出写入 AVI 文件,根据 VirtualDub,AVI 文件的帧数将增加 300 倍这应该。它将大多数帧列为丢弃帧,并定期有一个真实帧。

如果我简单地去掉SetMediaTime,输出的AVI就完全正常了。

我尝试了多种设置媒体时间的方法。我可以设置相对于过滤器 m_pStart 的时间、参考时钟上的时间等。这似乎无关紧要 - 只是 MediaTime 的存在会破坏 AVI。

我已经看到适当的 directshow 捕获过滤器可以很好地设置 MediaTime,所以我猜我没有做某事。任何 thoughts/ideas?

这是我的文件属性的屏幕截图,大约捕获了 2 秒。实际输出了 138 帧,但 AVI 认为它有大约 40000 帧,或真实数字的 290 倍。如果我 运行 没有 SetMediaTime 的相同代码,AVI 是 2 秒长的 138 帧和。没有 "dropped" 帧。

未丢失的帧位于 0、326、552、878、1104、1430、1756、1982。它们之间的增量为 326、226、326、226、226、326、326、226。它是绝对让我挠头...

AVI 帧索引将包含流 header 中定义的固定帧速率下的每个帧的条目。例如,您创建 300 fps 轨道,然后您的源时间戳样本具有 1 fps 频率。生成的文件将包含您的帧和它们之间的 299 个丢弃(零长度)帧。这就是你应该得到的。

也就是说你的时间戳代码片段是正确的(你用一种简单的方式来做)。然而重要的是什么速率适用于流本身,这是从媒体类型派生的,你没有包括在问题中,你应该检查它。

媒体类型速率和时间戳的匹配是获得准确输出AVI文件的关键。

我今天偶然发现了 this bit of documentation,我认为它实际上在某种程度上解释了事情。从中,

Optionally, the filter can also specify a media time for the sample. In a video stream, media time represents the frame number.

因此,多路复用器需要媒体时间(如果存在),例如 0-1,1-2,2-3。当媒体时间设置为连续块时,例如 0-100000、100000-200000,我猜 mux 可以应对。但是当存在差距时,根据 Microsoft 提供的文档,我可以理解事情是如何崩溃的。

但是知道这一点实际上是非常强大的。由于 AVI 文件是恒定帧率格式,因此您可以在需要时使用 mediatimes 来传达帧丢失。为此,我已经开始成功地使用它们。

仅供参考,几天前我尝试在一个项目中再次包含基于实际时间的媒体时间,而不是有趣的结果,directshow 图只会停止在 E_FAIL。

tl;dr 仅使用媒体时间来传达帧数,至少与 AVI mux 是这样。