6

问题

我有一个旧应用程序,它使用 MRLE 编解码器在 .avi 容器中提供视频。该代码使用 Video for Windows API。多年来,这一直令人钦佩,但我刚刚发现我的代码在 Windows 8 上无法正常运行。

在 Windows 8 上,该程序会创建一个 .avi 文件,但是当在例如 Windows Media Player 中查看它时,视频会播放正确的持续时间,但会一直显示第一帧。

我制作了一个 SSCCE 来演示这个问题:

#include <Windows.h>
#include <vfw.h>
#include <cstdlib>
#include <iostream>

#pragma comment(lib, "vfw32.lib")

int main()
{
    RECT frame = { 0, 0, 120, 100 };

    AVIFileInit();

    IAVIFile *pFile;
    if (AVIFileOpenA(&pFile, "out.avi", OF_CREATE | OF_WRITE, NULL) != 0)
    {
        std::cout << "AVIFileOpen failed" << std::endl;
        return 1;
    }

    AVISTREAMINFO si = { 0 };
    si.fccType = streamtypeVIDEO;
    si.fccHandler = mmioFOURCC('M', 'R', 'L', 'E');
    si.dwScale = 1;
    si.dwRate = 25;
    si.dwQuality = (DWORD)-1;
    si.rcFrame = frame;
    IAVIStream *pStream;
    if (AVIFileCreateStream(pFile, &pStream, &si) != 0)
    {
        std::cout << "AVIFileCreateStream failed" << std::endl;
        return 1;
    }

    AVICOMPRESSOPTIONS co = { 0 };
    co.fccType = si.fccType;
    co.fccHandler = si.fccHandler;
    co.dwQuality = si.dwQuality;
    IAVIStream *pCompressedStream;
    if (AVIMakeCompressedStream(&pCompressedStream, pStream, &co, NULL) != 0)
    {
        std::cout << "AVIMakeCompressedStream failed" << std::endl;
        return 1;
    }

    size_t bmiSize = sizeof(BITMAPINFOHEADER) + 256*sizeof(RGBQUAD);
    BITMAPINFO *bmi = (BITMAPINFO*)std::malloc(bmiSize);
    ZeroMemory(bmi, bmiSize);
    bmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmi->bmiHeader.biWidth = si.rcFrame.right;
    bmi->bmiHeader.biHeight = si.rcFrame.bottom;
    bmi->bmiHeader.biPlanes = 1;
    bmi->bmiHeader.biBitCount = 8;
    bmi->bmiHeader.biCompression = BI_RGB;
    bmi->bmiHeader.biSizeImage = bmi->bmiHeader.biWidth*bmi->bmiHeader.biHeight;
    bmi->bmiHeader.biClrUsed = 256;
    for (int i = 0; i < 256; i++)
    {
        RGBQUAD col = { i, i, i, 0 };
        bmi->bmiColors[i] = col;
    }
    if (AVIStreamSetFormat(pCompressedStream, 0, bmi, bmiSize) != 0)
    {
        std::cout << "AVIStreamSetFormat failed" << std::endl;
        return 1;
    }

    unsigned char *bits = new unsigned char[bmi->bmiHeader.biSizeImage];
    for (int frame = 0; frame < 256; frame++)
    {
        std::memset(bits, 255-frame, bmi->bmiHeader.biSizeImage);
        if (AVIStreamWrite(pCompressedStream, frame, 1, bits, bmi->bmiHeader.biSizeImage, 0, NULL, NULL) != 0)
        {
            std::cout << "AVIStreamWrite failed" << std::endl;
            return 1;
        }
    }

    if (AVIStreamRelease(pCompressedStream) != 0 || AVIStreamRelease(pStream) != 0)
    {
        std::cout << "AVIStreamRelease failed" << std::endl;
        return 1;
    }
    if (AVIFileRelease(pFile) != 0)
    {
        std::cout << "AVIFileRelease failed" << std::endl;
        return 1;
    }

    std::cout << "Succeeded" << std::endl;
    return 0;
}

这将创建一个非常简单的视频。创建了一个灰色调的调色板。每一帧都会选择不同的灰色阴影。因此,所需的视频从白色过渡到黑色。这在 Win7 及更早版本上按预期发生。但在 Win8 上,视频只包含第一个白帧。

这似乎是 .avi 文件生成的问题。如果我在 Win8 上生成文件并在 Win7 上查看,则该文件无法正确播放。如果我在 Win7 上生成文件并在 Win8 上查看,则视频会根据需要显示。

我知道 Video for Windows 是一个古老的遗留 API。但是,我正在编码的帧非常适合游程编码。在我支持的所有 Windows 版本上,默认情况下都可以使用 MRLE 编解码器。因此,我有充分的理由不愿意尝试使用更现代的多媒体 API 之一,直到我可以确定 Win8 上的 Video for Windows with MRLE 是一个失败的原因。

问题

是否可以在 Win8 上使用 Video for Windows 创建 MRLE 编码的 .avi 文件?如果是这样,它是如何完成的?

4

1 回答 1

1

这似乎确实是 Windows 8 中的一个错误。感谢 Roman 提出了一些解决该问题的想法。我可以确认手动执行 RLE 编码很容易,而且效果很好。

从问题中的代码开始,我们需要保留所有内容,并替换写入帧的 for 循环的内部结构。我们仍然需要创建压缩流,但我们不再写入它。相反,我们将 RLE8 编码数据写入原始流。

unsigned char *bits = new unsigned char[bmi->bmiHeader.biHeight*4 + 2];
for (int frame = 0; frame < 256; frame++)
{
    size_t i = 0;
    for (size_t y = 0; y < bmi->bmiHeader.biHeight; y++)
    {
        bits[i++] = bmi->bmiHeader.biWidth;
        bits[i++] = 255-frame; // encoded run
        bits[i++] = 0;
        bits[i++] = 0; // EOL
    }
    bits[i++] = 0;
    bits[i++] = 1; // EOB
    if (AVIStreamWrite(pStream, frame, 1, bits, i, 0, NULL, NULL) != 0)
    {
        std::cout << "AVIStreamWrite failed" << std::endl;
        return 1;
    }
}

显然,在实际应用程序中,您需要编写一个真正的 RLE8 编码器,但这证明了这一点。

于 2014-04-01T20:50:10.830 回答