none
Media Foundation SinkWriter gives vertically flipped H264 encoded video RRS feed

  • Question

  • Hi 

    I am using Media Foundation SinkWriter to convert some bitmap images into H264 encoded video. I use the exact same example provided in the microsoft documentation link, where it encodes a static input buffer to video, i simply replaced that buffer with my bitmapdata.Scan0 buffer and my input type is RGB 24. 

    The output video is fine when the output media type is WMV1, WMV2 or WMV3 but gets vertically flipped when output type is H264. 

    Given below is the sample test application:

    // MFTSinkWriter.cpp : Defines the entry point for the console application.
    //

    #include "stdafx.h"
    #include <Windows.h>
    #include <mfapi.h>
    #include <mfidl.h>
    #include <Mfreadwrite.h>
    #include <mferror.h>
    #include <GdiPlus.h>
    #include <iostream>
    #include <sstream>

    using namespace Gdiplus;

    #pragma comment(lib, "Gdiplus.lib")
    #pragma comment(lib, "mfreadwrite")
    #pragma comment(lib, "mfplat")
    #pragma comment(lib, "mfuuid")

    template <class T> void SafeRelease(T **ppT)
    {
        if (*ppT)
        {
            (*ppT)->Release();
            *ppT = NULL;
        }
    }

    // Format constants

    UINT32 VIDEO_WIDTH = 0;
    UINT32 VIDEO_HEIGHT = 0;
    UINT32 VIDEO_STRIDE = 0;
    const GUID   VIDEO_ENCODING_FORMAT = MFVideoFormat_H264;
    const UINT32 VIDEO_BIT_RATE = 800000;
    const UINT32 VIDEO_FPS = 30;
    const GUID   VIDEO_INPUT_FORMAT = MFVideoFormat_RGB24;
    const UINT32 VIDEO_FRAME_COUNT = 20 * VIDEO_FPS;
    const UINT64 VIDEO_FRAME_DURATION = 10 * 1000 * 1000 / VIDEO_FPS;



    HRESULT InitializeSinkWriter(IMFSinkWriter **ppWriter, DWORD *pStreamIndex)
    {
        *ppWriter = NULL;
        *pStreamIndex = NULL;

        IMFSinkWriter   *pSinkWriter = NULL;
        IMFMediaType    *pMediaTypeOut = NULL;   
        IMFMediaType    *pMediaTypeIn = NULL;    
        DWORD           streamIndex;     

    IMFAttributes *attributes;
    MFCreateAttributes(&attributes, TRUE);
    HRESULT hr = attributes->SetUINT32(MF_READWRITE_ENABLE_HARDWARE_TRANSFORMS, TRUE);
    hr = attributes->SetUINT32(MF_SINK_WRITER_DISABLE_THROTTLING, TRUE);
    hr = attributes->SetUINT32(MF_LOW_LATENCY, TRUE);
    hr = attributes->SetGUID(MF_TRANSCODE_CONTAINERTYPE, MFTranscodeContainerType_MPEG4);

    hr = MFCreateSinkWriterFromURL(L"output.wmv", NULL, attributes, &pSinkWriter);


        // Set the output media type.
        if (SUCCEEDED(hr))
        {
            hr = MFCreateMediaType(&pMediaTypeOut);   
        }

    if (SUCCEEDED(hr))
        {
            hr = pMediaTypeOut->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);     
        }
        if (SUCCEEDED(hr))
        {
            hr = pMediaTypeOut->SetGUID(MF_MT_SUBTYPE, VIDEO_ENCODING_FORMAT);   
        }

        if (SUCCEEDED(hr))
        {
            hr = pMediaTypeOut->SetUINT32(MF_MT_AVG_BITRATE, VIDEO_BIT_RATE);   
        }

        if (SUCCEEDED(hr))
        {
            hr = pMediaTypeOut->SetUINT32(MF_MT_INTERLACE_MODE, 2);   
        }
        if (SUCCEEDED(hr))
        {
            hr = MFSetAttributeSize(pMediaTypeOut, MF_MT_FRAME_SIZE, VIDEO_WIDTH, VIDEO_HEIGHT);   
        }
        if (SUCCEEDED(hr))
        {
            hr = MFSetAttributeRatio(pMediaTypeOut, MF_MT_FRAME_RATE, VIDEO_FPS, 1);   
        }
        if (SUCCEEDED(hr))
        {
            hr = MFSetAttributeRatio(pMediaTypeOut, MF_MT_PIXEL_ASPECT_RATIO, 1, 1);   
        }
        if (SUCCEEDED(hr))
        {
            hr = pSinkWriter->AddStream(pMediaTypeOut, &streamIndex);   
        }

        // Set the input media type.
        if (SUCCEEDED(hr))
        {
            hr = MFCreateMediaType(&pMediaTypeIn);   
        }
        if (SUCCEEDED(hr))
        {
            hr = pMediaTypeIn->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);   
        }
        if (SUCCEEDED(hr))
        {
            hr = pMediaTypeIn->SetGUID(MF_MT_SUBTYPE, VIDEO_INPUT_FORMAT);     
        }
        if (SUCCEEDED(hr))
        {
            hr = pMediaTypeIn->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive);   
        }
        if (SUCCEEDED(hr))
        {
            hr = MFSetAttributeSize(pMediaTypeIn, MF_MT_FRAME_SIZE, VIDEO_WIDTH, VIDEO_HEIGHT);   
        }
        if (SUCCEEDED(hr))
        {
            hr = MFSetAttributeRatio(pMediaTypeIn, MF_MT_FRAME_RATE, VIDEO_FPS, 1);   
        }
        if (SUCCEEDED(hr))
        {
            hr = MFSetAttributeRatio(pMediaTypeIn, MF_MT_PIXEL_ASPECT_RATIO, 1, 1);   
        }
        if (SUCCEEDED(hr))
        {
            hr = pSinkWriter->SetInputMediaType(streamIndex, pMediaTypeIn, NULL);   
        }

        // Tell the sink writer to start accepting data.
        if (SUCCEEDED(hr))
        {
            hr = pSinkWriter->BeginWriting();
        }

        // Return the pointer to the caller.
        if (SUCCEEDED(hr))
        {
            *ppWriter = pSinkWriter;
            (*ppWriter)->AddRef();
            *pStreamIndex = streamIndex;
        }

        SafeRelease(&pSinkWriter);
        SafeRelease(&pMediaTypeOut);
        SafeRelease(&pMediaTypeIn);
        return hr;
    }


    HRESULT WriteFrame(
    Bitmap *b2,
        IMFSinkWriter *pWriter, 
        DWORD streamIndex, 
        const LONGLONG& rtStart,        // Time stamp.
    DWORD counter
        )
    {

    BitmapData bmpData;
    b2->LockBits(new Rect(0,0,b2->GetWidth(),b2->GetHeight()), ImageLockMode::ImageLockModeRead,  b2->GetPixelFormat(), &bmpData);
        
        IMFSample *pSample = NULL;
        IMFMediaBuffer *pBuffer = NULL;

        const LONG cbWidth = 4 * VIDEO_WIDTH;//bmpData.Stride;
        const DWORD cbBuffer = cbWidth * VIDEO_HEIGHT;

        BYTE *pData = NULL;

        // Create a new memory buffer.
        HRESULT hr = MFCreateMemoryBuffer(cbBuffer, &pBuffer);

        // Lock the buffer and copy the video frame to the buffer.
        if (SUCCEEDED(hr))
        {
            hr = pBuffer->Lock(&pData, NULL, NULL);
        }
        if (SUCCEEDED(hr))
        {
            hr = MFCopyImage(
                pData,                      // Destination buffer.
                bmpData.Stride,                    // Destination stride.
                (BYTE*)bmpData.Scan0,    // First row in source image.
                bmpData.Stride,                    // Source stride.
                cbWidth,                    // Image width in bytes.
                VIDEO_HEIGHT                // Image height in pixels.
                );
        }



        if (pBuffer)
        {
            pBuffer->Unlock();
        }

        // Set the data length of the buffer.
        if (SUCCEEDED(hr))
        {
            hr = pBuffer->SetCurrentLength(cbBuffer);
        }

        // Create a media sample and add the buffer to the sample.
        if (SUCCEEDED(hr))
        {
            hr = MFCreateSample(&pSample);
        }
        if (SUCCEEDED(hr))
        {
            hr = pSample->AddBuffer(pBuffer);
        }

        // Set the time stamp and the duration.
        if (SUCCEEDED(hr))
        {
            hr = pSample->SetSampleTime(rtStart);
        }
        if (SUCCEEDED(hr))
        {
            hr = pSample->SetSampleDuration(VIDEO_FRAME_DURATION);
        }

        // Send the sample to the Sink Writer.
        if (SUCCEEDED(hr))
        {
            hr = pWriter->WriteSample(streamIndex, pSample);
        }

        SafeRelease(&pSample);
        SafeRelease(&pBuffer);
    // Unlock the bits.
    b2->UnlockBits( &bmpData );
    //delete b;
        return hr;
    }


    void main()
    {
    GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR           gdiplusToken;

    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

    Gdiplus::Bitmap * image = Gdiplus::Bitmap::FromFile(L"F:\\test.bmp");
    VIDEO_WIDTH = image->GetWidth();
    VIDEO_HEIGHT = image->GetHeight();

    HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
        if (SUCCEEDED(hr))
        {
            hr = MFStartup(MF_VERSION);
            if (SUCCEEDED(hr))
            {
                IMFSinkWriter *pSinkWriter = NULL;
                DWORD stream;

                hr = InitializeSinkWriter(&pSinkWriter, &stream);
                if (SUCCEEDED(hr))
                {
                    // Send frames to the sink writer.
                    LONGLONG rtStart = 0;


                    for (DWORD i = 0; i < VIDEO_FRAME_COUNT; ++i)
                    {
                        hr = WriteFrame(image, pSinkWriter, stream, rtStart, i);
                        if (FAILED(hr))
                        {
                            break;
                        }
                        rtStart += VIDEO_FRAME_DURATION;
                    }
                }
                if (SUCCEEDED(hr))
                {
                    hr = pSinkWriter->Finalize();
                }
                SafeRelease(&pSinkWriter);
                MFShutdown();
            }
            CoUninitialize();

        }
    GdiplusShutdown(gdiplusToken);
    }


    Wednesday, June 19, 2019 8:45 PM

Answers

  • Hi,

    Thanks you for your reply, but isn't it odd that the code logic should differ for H264 and other output video formats? 

    Regards

    SK

    • Marked as answer by SUJIT2405 Thursday, June 20, 2019 12:16 PM
    Wednesday, June 19, 2019 9:15 PM
  • Thanks for your reply, 

    I chnaged my logic to following and it worked like a charm :

    hr = MFCopyImage(
                pData,                      // Destination buffer.
                bmpData.Stride,                    // Destination stride.
                (BYTE*)bmpData.Scan0 + (VIDEO_HEIGHT - 1) * bmpData.Stride,//top,    // First row in source image.
                bmpData.Stride * -1,                    // Source stride.
                cbWidth,                    // Image width in bytes.
                VIDEO_HEIGHT                // Image height in pixels.
                );

    but the point is SinkWriter works differently internally for different types of encoders, especially H264.

    • Marked as answer by SUJIT2405 Thursday, June 20, 2019 12:16 PM
    Wednesday, June 19, 2019 9:33 PM
  • No, it is not odd.

    Your efforts here address proper formatting of media sample in uncompressed domain (you transfer top to bottom RGB image from GDI+ as opposed to normally bottom to top images with video APIs, so you have to deal with indication of row order).


    http://alax.info/blog/tag/directshow

    • Marked as answer by SUJIT2405 Thursday, June 20, 2019 12:16 PM
    Thursday, June 20, 2019 9:53 AM

All replies

  • Indicate negative stride on media type or replace MFCopyImage with row by row copy in reverse order.

    http://alax.info/blog/tag/directshow

    Wednesday, June 19, 2019 9:04 PM
  • Hi,

    Thanks you for your reply, but isn't it odd that the code logic should differ for H264 and other output video formats? 

    Regards

    SK

    • Marked as answer by SUJIT2405 Thursday, June 20, 2019 12:16 PM
    Wednesday, June 19, 2019 9:15 PM
  • Thanks for your reply, 

    I chnaged my logic to following and it worked like a charm :

    hr = MFCopyImage(
                pData,                      // Destination buffer.
                bmpData.Stride,                    // Destination stride.
                (BYTE*)bmpData.Scan0 + (VIDEO_HEIGHT - 1) * bmpData.Stride,//top,    // First row in source image.
                bmpData.Stride * -1,                    // Source stride.
                cbWidth,                    // Image width in bytes.
                VIDEO_HEIGHT                // Image height in pixels.
                );

    but the point is SinkWriter works differently internally for different types of encoders, especially H264.

    • Marked as answer by SUJIT2405 Thursday, June 20, 2019 12:16 PM
    Wednesday, June 19, 2019 9:33 PM
  • Hello SUJIT2405,

    Mark the replies if you think it helps you solve the issue and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread.  Thanks for your cooperation.

    Best regards,

    Rita


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Thursday, June 20, 2019 8:20 AM
  • No, it is not odd.

    Your efforts here address proper formatting of media sample in uncompressed domain (you transfer top to bottom RGB image from GDI+ as opposed to normally bottom to top images with video APIs, so you have to deal with indication of row order).


    http://alax.info/blog/tag/directshow

    • Marked as answer by SUJIT2405 Thursday, June 20, 2019 12:16 PM
    Thursday, June 20, 2019 9:53 AM