none
Win7 + H.264 Encoder + IMFSinkWriter Can't use Quality VBR encoding?

    Question

  • I'm trying to alter the encoder quality property eAVEncCommonRateControlMode_Quality via ICodecAPI.

    However the setting is ignored as started in the documentation which says the property must be set before IMFTransform::SetOutputType is called.

    Now here is the problem: the sink writer seems to call IMFTransform::SetOutputType when we call SetInputMediaType on the sink writer, however if we don't call SetInputMediaType we can't retrieve the ICodecAPI interface via sinkWriter.GetServiceForStream (throws exception) to change the quality setting..seems like a catch 22. I'm hoping it's me and not just a design flaw in the APIs.

    Setting the quality property works on Win8, as Win8 does not ignore it when called after IMFTransform::SetOutputType is called.

    Help!!

     


    • Edited by _mms_ Wednesday, March 22, 2017 11:45 PM
    Sunday, October 14, 2012 4:49 AM

All replies

  • This is unfortunately a design flaw with the way the sink writer and the H264 encoder interact.  You could potentially work around it by locally registering a wrapper MFT for H264.  The wrapper would delegate all calls to the real H264 encoder, except it would hold off on SetOutputType until the codec properties were configured.
    Thursday, November 8, 2012 10:21 PM
    Moderator
  • Hello. I am having the same problem, and I have created a wrapper for the H264 encoder.

    Now I am trying to register it locally, and getting the SinkWriter to use my wrapper instead of the H264 encoder.

    My questions are:

    1. Do I need to create a new project for a new DLL, based on the mft_grayscale sample? Can't I just use the class in the project I am using? I don't need to make the encoder (wrapper) available to other applications.

    2. If I can create the encoder in my .exe application, how do I call MFTRegisterLocal? I made up a CLSID for my wrapper, but how do I link my CLSID to the wrapper class?

    3. How do I make the SinkWriter use my wrapper instead of the real H264 encoder?

    Friday, November 9, 2012 10:12 AM
  • After a few hours of trying, I finally succeeded in constructing the H264 codec before the SinkWriter gets hold of it and calling SetOutputType. Here is how I did it.

    I didn't make a wrapper for the H264 video encoder. I created a class factory and registered it in Media Foundation. The SinkWriter uses MFTEnumEx to enumerate all the video codecs and select the one that matches your input and output media types. In my case, it's the H264 video encoder. If I register a class factory for a video encoder, the SinkWriter will call my class factory in order to construct the video encoder. And because I control the class factory, I can create the H264 video encoder, set whatever properties I want on it (CODECAPI_AVEncCommonQuality) and then pass it on to the SinkWriter. I didn't need to use a GUID and I didn't need to create a new DLL project.

    The ClassFactory is a regular class in my project, which implements the IClassFactory interface. I based my class on the ClassFactory class in the Windows SDK 7.1 samples (c:\Program Files\Microsoft SDKs\Windows\v7.1\Samples\multimedia\mediafoundation\common\ClassFactory.h).

    After I create my class factory, I need to register it in Media Foundation. In order to do this, I use MFTRegisterLocal. When I'm done, I need to unregister it using MFTUnregisterLocal. Here is how I call MFTRegisterLocal:

    MyClassFactory *cf = new MyClassFactory();
    MFT_REGISTER_TYPE_INFO infoInput = { MFMediaType_Video, MFVideoFormat_RGB32 };
    MFT_REGISTER_TYPE_INFO infoOutput = { MFMediaType_Video, MFVideoFormat_H264 };
    MFTRegisterLocal(cf, MFT_CATEGORY_VIDEO_ENCODER, L"ClassFactory", 0, 1, &infoInput, 1, &infoOutput);
    // rest of application....
    MFTUnregisterLocal(cf);
    delete cf;

    After I register the class factory, the SinkWriter will call it when it needs an encoder that takes the specified inputs and outputs. More specifically, it calls the CreateInstance method in the class factory. So in the CreateInstance method, I create a new H264 encoder, initialize it however I want, and return it via the ppv parameter. And in this way, the SinkWriter gets the new encoder that was initialized by me.

    Here is the CreateInstance method that is in my class factory:

    STDMETHODIMP MyClassFactory::CreateInstance( IUnknown *pUnkOuter, REFIID riid, void **ppv ) { HRESULT hr = S_OK; // If the caller is aggregating the object, the caller may only request // IUknown. (See MSDN documenation for IClassFactory::CreateInstance.) if (pUnkOuter != NULL) { if (riid != __uuidof(IUnknown)) { return E_NOINTERFACE; } } IMFTransform* pEncoder = NULL; hr = FindEncoderEx(MFVideoFormat_H264, &pEncoder); ICodecAPI* pCodecApi = NULL; hr = pEncoder->QueryInterface<ICodecAPI>(&pCodecApi); VARIANT var; VariantInit(&var); var.vt = VT_UI4; var.ulVal = eAVEncCommonRateControlMode_UnconstrainedVBR; hr = pCodecApi->SetValue(&CODECAPI_AVEncCommonRateControlMode, &var); VariantClear(&var); VariantInit(&var); var.vt = VT_UI4; var.ulVal = 100; hr = pCodecApi->SetValue(&CODECAPI_AVEncCommonQuality, &var); VariantClear(&var); hr = pEncoder->QueryInterface(riid, ppv);

    pEncoder->Release();

    pCodecApi->Release(); return hr; }


    I use a helper function FindEncoderEx in order to create the H264 encoder. It is a modified version of the function I found here: http://msdn.microsoft.com/en-us/library/windows/desktop/dd388652(v=vs.85).aspx . I removed the MFT_ENUM_FLAG_LOCALMFT flag because I don't want to get the locally registered MFTs (I don't want to get my class factory, I want the built-in H264 encoder). Here is the function:

    HRESULT MyClassFactory::FindEncoderEx( const GUID& subtype, IMFTransform **ppEncoder )
    {
    	HRESULT hr = S_OK;
    	UINT32 count = 0;
    
    	IMFActivate **ppActivate = NULL;
    
    	MFT_REGISTER_TYPE_INFO info = { 0 };
    
    	info.guidMajorType = MFMediaType_Video;
    	info.guidSubtype = subtype;
    
    	hr = MFTEnumEx(
    		MFT_CATEGORY_VIDEO_ENCODER,
    		MFT_ENUM_FLAG_SYNCMFT | MFT_ENUM_FLAG_SORTANDFILTER,
    		NULL,       // Input type
    		&info,      // Output type
    		&ppActivate,
    		&count
    		);
    
    	if (SUCCEEDED(hr) && count == 0)
    	{
    		hr = MF_E_TOPO_CODEC_NOT_FOUND;
    	}
    
    	// Create the first encoder in the list.
    	if (SUCCEEDED(hr))
    	{
    		hr = ppActivate[0]->ActivateObject(IID_PPV_ARGS(ppEncoder));
    	}
    
    	for (UINT32 i = 0; i < count; i++)
    	{
    		ppActivate[i]->Release();
    	}
    	CoTaskMemFree(ppActivate);
    
    	return hr;
    }

    So, this is the trick I used in order to get around the problem on Windows 7. I hope it will benefit the people who will run into the same problem.


    • Proposed as answer by Francis Grave Friday, October 11, 2013 4:08 PM
    • Edited by oviradoi Saturday, January 4, 2014 5:37 PM Forgot to call ->Release()
    Friday, November 9, 2012 9:38 PM
  • Hi oviradoi,

    interessting solution you have there.

    Before your post i was struggling with custom codec settings for WMV and H264 under Windows 7. In Windows 8 there is no Problem at all and you can simply use

    MF_SINK_WRITER_ENCODER_CONFIG

    But it is only working from Windows 8 upwards, for Windows 7 your solution fits perfectly.

    regards

    coOKie 




    • Proposed as answer by Francis Grave Friday, October 11, 2013 4:08 PM
    • Edited by Francis Grave Saturday, October 12, 2013 4:13 PM Edit
    Friday, October 11, 2013 3:34 PM
  • Hi oviradoi,

    Thanks for the solution, going to be trying this soon.

    -mms

    Thursday, November 7, 2013 8:00 AM
  • Hello cookie,

    Any idea how to actually do this? I just tried to implement the steps outlined for MF_SINK_WRITER_ENCODER_CONFIG but the property store seems to be ignored by MFCreateSinkWriterFromUrl.

    I'm setting the AVEncCommonRateControlMode and AVEncCommonQuality properties to eAVEncCommonRateControlMode_Quality and 100 respectievly, no effect on output, no errors either.

    Any snippets you can provide would be appreciated.

    -mms


    • Edited by _mms_ Thursday, November 7, 2013 8:12 AM
    Thursday, November 7, 2013 8:11 AM
  • Hi _mms_,

    like with a lot of other documents on MSDN, there are some errors in the description.

    You dont need to set this attribute as creation parameter for the sink writer as its no MFT, just set it on IMFSinkWriter->SetInputMediaType as the third parameter. Here is a snippet how you set the parameters for a wmv encoder :

    	PROPVARIANT prop;
    	IPropertyStore *pPropertyStore = NULL;
    	IMFAttributes *pEncoderParameters = NULL;
    
    	HRESULT hr = PSCreateMemoryPropertyStore(__uuidof(IPropertyStore), (void**)&pPropertyStore);
    	if (SUCCEEDED(hr))
    	{
    		prop.vt = VT_BOOL;
    		prop.boolVal = VARIANT_TRUE;
    		hr = pPropertyStore->SetValue(MFPKEY_VBRENABLED, prop);
    	}
    	if (SUCCEEDED(hr))
    	{
    		prop.vt = VT_I4;
    		prop.lVal = 100;
    		hr = pPropertyStore->SetValue(MFPKEY_VBRQUALITY, prop);
    	}
    	if (SUCCEEDED(hr))hr = MFCreateAttributes(&pEncoderParameters, 1);
    	if (SUCCEEDED(hr))hr = pEncoderParameters->SetUnknown(MF_SINK_WRITER_ENCODER_CONFIG, pPropertyStore);

    When you set a media type for an input on a stream you call :

    HRESULT hr = pSinkWriter->SetInputMediaType(videoStream, pMediaTypeIn, pEncoderParameters);

    The SetInputMediaType function uses the parameters for instantiating the encoder object. Please have in mind that you can have a lot of different streams on a sink writer using the same type of MFT but with different encoder settings, so you have to set the the encoding parameters for each MFT separately.

    Finaly dont forget to clean up with :

    SAFE_RELEASE(pEncoderParameters);
    PropVariantClear(&prop);

    Of course you have to modify this example for configuring the H264 encoder. You could use the sample code oviradoi has posted.

    I hope it helps you

    regards

    coOKie











    Tuesday, November 12, 2013 6:24 AM
  • Hi oviradoi,

    Im trying to use VBR while encoding WMV3 using SinkWriter. It seems that you found the solution to set the encoding setting for SinkWriter in Windows 7. But I could not applied it correctly. Below is my current work:

    - Encoding Wmv3 with SinkWriter from tutorial here: http://msdn.microsoft.com/en-us/library/windows/desktop/ff819477(v=vs.85).aspx

    - Using ClassFactory.h, modified for WMV3 encoding similar to your solution

    - Register the class factory before calling MFCreateSinkWriterFromURL() method as below:

    ClassFactory *cf = new ClassFactory();
    MFT_REGISTER_TYPE_INFO infoInput = { MFMediaType_Video, MFVideoFormat_RGB24 };
    MFT_REGISTER_TYPE_INFO infoOutput = { MFMediaType_Video, MFVideoFormat_WMV3 };
    MFTRegisterLocal(cf, MFT_CATEGORY_VIDEO_ENCODER, L"ClassFactory", MFT_ENUM_FLAG_ALL, 1, &infoInput, 1, &infoOutput);

    However it seems that ClassFactory::CreateInstance() was never be called.

    Any idea about where was I wrong ?

    Regards,

    Khaind

    Thursday, July 3, 2014 3:51 AM
  • mark
    Wednesday, August 13, 2014 11:23 PM
  • Remove MFT_ENUM_FLAG_ALL

    MFTRegisterLocal(cf, MFT_CATEGORY_VIDEO_ENCODER, L"ClassFactory", 0, 1, &infoInput, 1, &infoOutput);

    Wednesday, March 30, 2016 9:31 AM