6

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?

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • I'd say the problem is with the codec itself - it accepts new data, but delivers some stale buffer. – Roman R. Mar 31 '14 at 15:39
  • @RomanR. Thanks for taking an interest. I can see that this is a subject area in which you are an expert. Do you think there's any hope for me? I mean, RLE_8 seems pretty easy to implement. Perhaps all I need to do is feed RLE_8 encoded bitmaps into the uncompressed stream. Although something tells me it must be more complicated than that. – David Heffernan Mar 31 '14 at 15:56
  • I could repeat the problem on my system. Presumably, it is possible to either isolate the problem to the codec exactly opening it directly with `ICOpen` and feeding it with a couple of video frames to check the output. Or maybe some flag is missing there and affects the output. If this however works well, then you can obtain compressed data for further write and eliminate `AVIMakeCompressedStream` and write data directly bypassing compression. That is, another try with `ICOpen` might suggest a workaround and shed more light (though simply implementing RLE8 might end up being easier). – Roman R. Mar 31 '14 at 16:17
  • @Roman Thanks for those tips. I'll try them out. – David Heffernan Mar 31 '14 at 16:24
  • @RomanR. In the end I never got as far as `ICOpen` and just went straight for the jugular. I proved that manually encoded RLE8 was viable and then went on and implemented an encoder in my app. This works very nicely. Although for some reason my encoder does not compress as much as the MS encoder so I guess I need to study MS encoded bitmaps to see if there are any obvious improvements for me to make. Thanks a lot for your tips and advice, it was very helpful. – David Heffernan Apr 01 '14 at 20:52

1 Answers1

1

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.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490