Search code examples
winapims-media-foundationgdi

Media Foundation capture reversed image


This is a bit weird, I'm capturing samples from my camera using SourceReader. I eventually get a HBITMAP which is upside down.

Here is the capturing code:

for (;;)
{
    DWORD streamIndex = 0, flags = 0;
    LONGLONG llTimeStamp = 0;
    CComPtr<IMFSample> pSample;

    hr = sr->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM,0,&streamIndex,&flags,&llTimeStamp,&pSample);
...
}

I get an IMFSample, which I'm trying to draw (not with Direct3D at the moment, but with Direct2D). The format captured is MFVideoFormat_NV12 and I want to convert it to MFVideoFormat_RGB32 so I can create a HBITMAP from it. So I create an IMFTransform with source type set to the media type returned from the capture and target the RGB type:

if (prvtrs)
{
    DWORD is = 0, os = 0;
    hr = prvtrs->GetStreamCount(&is, &os);
    if (is > 0 && os > 0)
    {
        hr = prvtrs->SetInputType(iids[0], fmt, 0);
        if (SUCCEEDED(hr))
        {
            PROPVARIANT pv;
            InitPropVariantFromCLSID(MFVideoFormat_RGB32, &pv);
            hr = prmt->SetItem(MF_MT_SUBTYPE, pv);
            if (SUCCEEDED(hr))
            {
                hr = prvtrs->SetOutputType(oods[0], prmt, 0);
                if (SUCCEEDED(hr))
                {
// OK
                }
            }
        }
    }
}

After the capture, I transform the IMFSample:

CComPtr<IMFSample> pSample2;
MFCreateSample(&pSample2);

if (si.cbSize == 0)
    prvtrs->GetOutputStreamInfo(oods[0], &si);

CComPtr<IMFMediaBuffer> bb;
MFCreateMemoryBuffer(si.cbSize, &bb);
pSample2->AddBuffer(bb);


hr = prvtrs->ProcessInput(iids[0], pSample, 0);
MFT_OUTPUT_DATA_BUFFER db = { 0 };
db.dwStreamID = oods[0];
db.pSample = pSample2;
DWORD st;
hr = prvtrs->ProcessOutput(0, 1, &db, &st);
if (db.pEvents)
    db.pEvents->Release();
if (SUCCEEDED(hr))
{
    // Show it
    CComPtr<IMFMediaBuffer> b4;
    pSample2->ConvertToContiguousBuffer(&b4);
    if (b4)
    {
        vector<char> o;
        auto bi = SaveSample(b4, wi, he);
....

And this is my SaveSample:

HBITMAP SaveSample(CComPtr<IMFMediaBuffer> mediaBuffer, int width32, int height32)
{
    HRESULT hr = 0;
    BYTE *pData = NULL;
    DWORD le = 0;
    mediaBuffer->GetCurrentLength(&le);
    hr = mediaBuffer->Lock(&pData, NULL, NULL);
    if (!pData)
        return 0;
    unsigned char*  pixels = pData;
    // at this point we have some input

    BITMAPINFOHEADER bmih;
    bmih.biSize = sizeof(BITMAPINFOHEADER);
    bmih.biWidth = width32;
    bmih.biHeight = height32;
    bmih.biPlanes = 1;
    bmih.biBitCount = 32;
    bmih.biCompression = BI_RGB;
    bmih.biSizeImage = 0;
    bmih.biXPelsPerMeter = 0;
    bmih.biYPelsPerMeter = 0;
    bmih.biClrUsed = 0;
    bmih.biClrImportant = 0;

    BITMAPINFO dbmi = { 0 };
    dbmi.bmiHeader = bmih;
    dbmi.bmiColors->rgbBlue = 0;
    dbmi.bmiColors->rgbGreen = 0;
    dbmi.bmiColors->rgbRed = 0;
    dbmi.bmiColors->rgbReserved = 0;

    HDC hdc = ::GetDC(NULL);
    HBITMAP hbmp = CreateDIBitmap(hdc, &bmih, CBM_INIT, pixels, &dbmi, DIB_RGB_COLORS);
    ::ReleaseDC(NULL, hdc);

    mediaBuffer->Unlock();
    return hbmp;
}

What I get is a bitmap upside down. What am I doing wrong?


Solution

  • You missed a crucial part of the specification of BMP files:

    The pixel array is a block of 32-bit DWORDs, that describes the image pixel by pixel. Usually pixels are stored "bottom-up", starting in the lower left corner, going from left to right, and then row by row from the bottom to the top of the image.[4] Unless BITMAPCOREHEADER is used, uncompressed Windows bitmaps also can be stored from the top to bottom, when the Image Height value is negative.

    Taking this into account, you can easily fix your error.