Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to encode using the MRLE codec on Video for Windows under Windows 8?

Tags:

winapi

vfw

The Problem

I have an old application that provides videos inside .avi containers using the MRLE codec. The code uses the Video for Windows API. This has worked admirably for many years, but I've just discovered that my code does not behave correctly on Windows 8.

On Windows 8, the program creates a .avi file but when it is viewed in, for instance, Windows Media Player, the video plays for the correct duration but the first frame is shown the whole time.

I've made an SSCCE to demonstrate the problem:

#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;
}

This creates a very simple video. A palette of gray shades is created. And each frame selects a different one of those gray shades. So the desired video transitions from white to black. This happens as expected on Win7 and earlier. But on Win8 the video just contains the first white frame.

It seems to be an issue with the .avi file generation. If I generate a file on Win8 and view on Win7 then the file does not play correctly. If I generate the file on Win7 and view on Win8 then the video displays as desired.

I know that Video for Windows is an ancient legacy API. However, the frames that I am encoding are very amenable to run-length encoding. And the MRLE codec is available by default on all versions of Windows that I support. So there are good reasons why I am reluctant to try to use one of the more modern multimedia APIs until I can be sure that Video for Windows on Win8 with MRLE is a lost cause.

The Question

Is it possible to use Video for Windows on Win8 to create MRLE encoded .avi files? If so how is it done?

like image 212
David Heffernan Avatar asked Mar 31 '14 15:03

David Heffernan


1 Answers

It seems likely that this is indeed a bug in Windows 8. Thanks to Roman for suggesting some ideas to work around the problem. I can confirm that performing the RLE encoding manually is easy to do, and works well.

Starting from the code in the question, we need to keep it all, and replace the innards of the for loop that writes the frames. We do still need to create the compressed stream, but we no longer write to it. Instead we write RLE8 encoded data to the raw stream.

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;
    }
}

Obviously in a real application, you'd need to write a real RLE8 encoder, but this proves the point.

like image 149
David Heffernan Avatar answered Oct 09 '22 18:10

David Heffernan