是否可以在Windows 8下使用Video for Windows上的MRLE编解码器进行编码?

Dav*_*nan 6 winapi vfw

问题

我有一个旧应用程序,使用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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

我知道Video for Windows是一个古老的传统API.但是,我编码的帧非常适合行程编码.默认情况下,MRLE编解码器可用于我支持的所有Windows版本.因此,有充分的理由说明为什么我不愿意尝试使用更现代的多媒体API之一,直到我能确定带有MRLE的Win8上的Video for Windows是一个失败的原因.

问题

是否可以在Win8上使用Video for Windows来创建MRLE编码的.avi文件?如果是这样怎么办?

Dav*_*nan 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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