英文:
MediaFoundation: Registering custom ClassFactory doesn't work
问题
背景:
我正在使用SinkWriter将NV12缓冲区编码为封装在MPEG4容器中的h264视频流。一切都正常工作,但有一个问题,由于SinkWriter抽象了底层编码器配置,我无法控制GOP大小、B图片数量、CODECAPI_AVEncCommonRateControlMode等属性。
问题在于SinkWriter仅在SetInputMediaType调用后实例化编码器转换,而我们只能在此之后获得CodecAPI实例。因此,我们无法在所有这些发生之前控制编码器并配置所需的属性,它也不会通过CodecAPI实例继续遵守对编码器的进一步更改。
实验:
我尝试了PropertyStore(MF_SINK_WRITER_ENCODER_CONFIG)方法,但似乎什么都没有改变(这可能是平台/编码器特定的行为),我还看到很多人抱怨这些API的行为不可预测。然后,我在这个MSDN线程(将近7年前的帖子)中找到了一个用户描述了他如何在Windows7机器上本地注册自定义类工厂来解决这个问题。
问题:
以MSDN线程为参考,我尝试实现IClassFactory并通过MFTRegisterLocal注册它,但对于我来说(Windows 10机器),CreateInstance函数从未被调用过。我只得到了对IID_IClassFactory和IID_IMFAttributes接口的QueryInterface方法调用。而且,SinkWriter似乎正在继续获取MFT。
我明白我可能做错了什么,而且我不是COM的专家。是否有其他方法可以实现这一目标?
<!-- language-all: c++ -->
**自定义类工厂实现:**
class MyClassFactory : public IClassFactory {
public:
MyClassFactory() : _cRef(1) {}
~MyClassFactory() {}
// 仅调用此方法
STDMETHODIMP QueryInterface(REFIID riid, void** ppv) {
HRESULT hr = E_NOTIMPL;
// 仅下面这两种情况(IID_IClassFactory 和 IID_IMFAttributes)被调用
if (IID_IClassFactory == riid)
{
*ppv = static_cast<IClassFactory*>(this);
if (*ppv) {
reinterpret_cast<IUnknown*>(*ppv)->AddRef();
}
hr = S_OK;
}
else if (IID_IMFAttributes == riid)
{
if (!pEncoder) {
hr = FindEncoderEx(&pEncoder);
}
IMFAttributes* attributes;
hr = pEncoder->GetAttributes(&attributes);
*ppv = attributes;
}
else
{
// 这种情况从未被触发
}
return hr;
}
// 这个从未被调用
STDMETHODIMP CreateInstance(IUnknown* pUnkOuter, REFIID riid, void** ppv)
{
HRESULT hr = S_OK;
if (pUnkOuter != NULL)
{
if (riid != __uuidof(IUnknown))
{
return E_NOINTERFACE;
}
}
if (!pEncoder) {
hr = FindEncoderEx(&pEncoder);
}
hr = pEncoder->QueryInterface(riid, ppv);
return hr;
}
IFACEMETHODIMP_(ULONG) AddRef()
{
return InterlockedIncrement(&_cRef);
}
IFACEMETHODIMP_(ULONG) Release()
{
assert(_cRef > 0);
LONG cRef = InterlockedDecrement(&_cRef);
if (!cRef)
delete this;
return cRef;
}
STDMETHODIMP LockServer(BOOL fLock)
{
if (fLock)
{
AddRef();
}
else {
Release();
}
return S_OK;
}
HRESULT FindEncoderEx(IMFTransform** ppEncoder)
{
...
}
protected:
LONG _cRef;
CComPtr<IMFTransform> pEncoder = NULL;
};
注册自定义类工厂:
MyClassFactory* cf = new MyClassFactory();
MFT_REGISTER_TYPE_INFO infoInput = { MFMediaType_Video, MFVideoFormat_NV12 };
MFT_REGISTER_TYPE_INFO infoOutput = { MFMediaType_Video, MFVideoFormat_H264 };
MFTRegisterLocal(cf, MFT_CATEGORY_VIDEO_ENCODER, L"MyClassFactory", 0, 1, &infoInput, 1, &infoOutput);
任何帮助将不胜感激。
英文:
Background:
I'm encoding NV12 buffers into h264 video stream wrapped in MPEG4 container using SinkWriter. Everything works fine but one problem though, as the SinkWriter abstracts the low-level encoder configurations I'm unable to control the properties like GOP size, B-picture count, CODECAPI_AVEncCommonRateControlMode, etc.
The problem is due to the fact that the SinkWriter instantiates the encoder transform only after the SetInputMediaType call, and we will be able to get the CodecAPI instance only after that point. So, we have no way to control the encoder and configure necessary props before all this happens, it also never honours further changes to the encoder through CodecAPI instance.
Experiments:
I tried the PropertyStore(MF_SINK_WRITER_ENCODER_CONFIG) approach but nothing seemed to be changing (It could be a platform/encoder specific behaviour), I could also see a lot of people complaining about unpredictable behaviours with these APIs. Then, I came across this MSDN thread (Almost 7 years old post) in which the user described how he dealt with this problem by locally registering a custom class factory on Windows7 machine.
Problem:
Having the MSDN thread as a reference, I tried implementing IClassFactory and registering it through MFTRegisterLocal but the CreateInstance function is never getting called for me (Windows 10 machine). I'm only getting the QueryInterface method called for IID_IClassFactory and IID_IMFAttributes interfaces instead. And, the SinkWriter seems to be proceeding with fetching the MFT on its own.
I understand I might be doing something wrong, and I'm not an expert in COM. Is there any other way I could achieve this?
<!-- language-all: c++ -->
Custom class factory implementation:
class MyClassFactory : public IClassFactory {
public:
MyClassFactory () : _cRef(1) {}
~MyClassFactory() {}
// Only this method is getting called
STDMETHODIMP QueryInterface(REFIID riid, void** ppv)
{
HRESULT hr = E_NOTIMPL;
// Only the below 2 cases (IID_IClassFactory and IID_IMFAttributes) are getting hit
if (IID_IClassFactory == riid)
{
*ppv = static_cast<IClassFactory*>(this);
if (*ppv) {
reinterpret_cast<IUnknown*>(*ppv)->AddRef();
}
hr = S_OK;
}
else if (IID_IMFAttributes == riid)
{
if (!pEncoder) {
hr = FindEncoderEx(&pEncoder);
}
IMFAttributes *attributes;
hr = pEncoder->GetAttributes(&attributes);
*ppv = attributes;
}
else
{
//This case has never been reached
}
return hr;
}
//This is never called
STDMETHODIMP CreateInstance(IUnknown* pUnkOuter, REFIID riid, void** ppv)
{
HRESULT hr = S_OK;
if (pUnkOuter != NULL)
{
if (riid != __uuidof(IUnknown))
{
return E_NOINTERFACE;
}
}
if (!pEncoder) {
hr = FindEncoderEx(&pEncoder);
}
hr = pEncoder->QueryInterface(riid, ppv);
return hr;
}
IFACEMETHODIMP_(ULONG) AddRef()
{
return InterlockedIncrement(&_cRef);
}
IFACEMETHODIMP_(ULONG) Release()
{
assert(_cRef > 0);
LONG cRef = InterlockedDecrement(&_cRef);
if (!cRef)
delete this;
return cRef;
}
STDMETHODIMP LockServer(BOOL fLock)
{
if (fLock)
{
AddRef();
}
else {
Release();
}
return S_OK;
}
HRESULT FindEncoderEx(IMFTransform** ppEncoder)
{
...
}
protected:
LONG _cRef;
CComPtr<IMFTransform> pEncoder = NULL;
};
Register custom class factory:
MyClassFactory* cf = new MyClassFactory();
MFT_REGISTER_TYPE_INFO infoInput = { MFMediaType_Video, MFVideoFormat_NV12 };
MFT_REGISTER_TYPE_INFO infoOutput = { MFMediaType_Video, MFVideoFormat_H264 };
MFTRegisterLocal(cf, MFT_CATEGORY_VIDEO_ENCODER, L"MyClassFactory", 0, 1, &infoInput, 1, &infoOutput);
Any help would be appreciated.
答案1
得分: 2
你正在走在一片薄冰上,因为你试图做的事情不应该起作用。你可以注册本地变换,但API通常更喜欢其他现有的MFT(因为它们具有更高的内部价值,而且有硬件支持),所以不应该期望覆盖现有的行为。
你的真正选择是:
- 使用
MF_SINK_WRITER_ENCODER_CONFIG
属性 传递编码器特定的配置。 - 使用COM注册类工厂而不是使用MF注册现有编码器的CLSID,以便COM实例化MFT按照你的方式进行;你需要弄清楚有关COM的细节来实现这一点,但一般情况下,我不建议在没有充分理由的情况下干预COM注册/实例化的标准行为。
- 分开运行编码MFT(或其等效物 - 在这种情况下,你不必严格使用MFT),在Sink Writer API之外单独运行,然后将已经压缩的数据馈送给Sink Writer。
英文:
You are walking on thin ice here because what you are trying to do is not supposed to work. You do (or at least can) register a local transform but API would typically prefer other existing MFTs to your one because they are of higher internal merit (and with hardware assistance support), so you are not expected to override existing behavior.
Your real options are:
- to use
MF_SINK_WRITER_ENCODER_CONFIG
attribute to pass encoder specific configuration - register class factory with COM rather than with MF for existing CLSID of encoder so that COM instantiation of MFT went your way; you would have to figure out details about COM to implement this and I would, in general, not recommend to interfere with standard behavior of COM registration/instantiation without good reason
- run encoding MFT (or its equivalent - you don't have to use MFT exactly in this case) separately, outside of Sink Writer API, and feed Sink Writer with already compressed data
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论