microsoft / media-foundation

Repository for Windows Media Foundation related tools and samples
MIT License
144 stars 31 forks source link

IMFContentDecryptionModuleSession::GenerateRequest failed with code 0x8004c3e8 #37

Closed cjw1115 closed 2 years ago

cjw1115 commented 2 years ago

Hi, I tried to create a AppContainer and use the sample code from MediaEngineEMEUWPSample, but I got below errors:

  1. When I try to create CDM object, below infomation is printed in OUTPUT windows: Exception thrown at 0x00007FF89C2D474C (KernelBase.dll) in MediaEngineEMEConsoleSample.exe: WinRT originate error - 0xC00D36BA : 'The object does not support the specified service.'. however, the hresult is S_OK. Then I can continue other steps.
  2. When generate a request in founction IMFContentDecryptionModuleSession::GenerateRequest, it failed with error code ‘0x8004c3e8’

Since there is no any docs on internet about this, could you help to help me from Microsoft side. Thanks!

BTW, I found MSEdge.exe also have #1 problem when I attach its process, but it can play Playready content by MediaFoundation successfully.

cjrog commented 2 years ago

When generate a request in founction IMFContentDecryptionModuleSession::GenerateRequest, it failed with error code ‘0x8004c3e8’

Which version of Windows are you using? If you are on Win11, please use mftracelog to capture a trace when the error code occurs and add it to this issue.

cjw1115 commented 2 years ago

Hi cjrog,

here is a simple full trace: simple_full_trace.zip

I use same CDM config in a UWP app, it works! After GenerateRequest, callback KeyMessage is called.

the only difference is storePath

//My AppContianer without package identify, so I create a LocalCache folder manually
configProperties.storagePath = L"C:\\Users\\xxx\\AppData\\Local\\Packages\\com-cq-test-container\\LocalCache\\";
// UWP with it's own LocalCache folder
configProperties.storagePath = winrt::Windows::Storage::ApplicationData::Current().LocalCacheFolder().Path().c_str();

And I found Edge browser use same API, but looks it work! Im not if there any special config.

Thanks

cjw1115 commented 2 years ago

Now I obseved another error code“MSPR_E_REDIRECT" , a thread exited with that code, then a rpc timeout message printed in Output window.

swenkeratmicrosoft commented 2 years ago

When running outside a UWP application, you must invoke the following method which requires a number of pre-steps.

https://docs.microsoft.com/en-us/windows/win32/api/mfcontentdecryptionmodule/nf-mfcontentdecryptionmodule-imfcontentdecryptionmodule-setpmphostapp

Most of the code you'll need to obtain an IMFPMPHostApp can be found in the storecdm sample in cdm.cpp inside Cdm_clearkey_IMFContentDecryptionModule::RuntimeClassInitialize and ::GetService.

You have to create a couple of property stores (with MediaProtectionSystemId set to the PlayReady system ID [which you can obtain from PlayReady statics, see link below] and key system string of "com.microsoft.playready.recommendation").

From there, create a IMediaProtectionPMPServerFactory, create a IMediaProtectionPMPServer, and from that obtain the IMFPMPHostApp which you then pass to SetPMPHostApp.

https://docs.microsoft.com/en-us/uwp/api/windows.media.protection.playready.playreadystatics.mediaprotectionsystemid?view=winrt-22621

I hope this helps, -Sam Wenker Microsoft PlayReady Architect

cjw1115 commented 2 years ago

Hi @swenkeratmicrosoft Thanks for your helpful information.

the sample code in storeCdm use IMFPMPHostApp, if I use it too in my code, the line

hr = spIMFGetService->GetService(MF_PMP_SERVICE, IID_PPV_ARGS(pmpHostApp.put()));

will failed with message "0xc00d36ba : The object does not support the specified service.", but when I change IMFPMPHostApp to IMFPMPHost, it will be okay. However, SetPMPHostApp require interface IMFPMPHostApp, I still can not get a IMFPMPHostApp.

Here is code to create pmpserver:

winrt::Windows::Foundation::Collections::PropertySet playreadyProp;
playreadyProp.Insert(systemId, winrt::box_value(L"com.microsoft.playready.recommendation"));

winrt::Windows::Foundation::Collections::PropertySet pmpServerProp;
pmpServerProp.Insert(L"Windows.Media.Protection.MediaProtectionSystemId", winrt::box_value(systemId));
pmpServerProp.Insert(L"Windows.Media.Protection.MediaProtectionSystemIdMapping", playreadyProp);
//pmpServerProp.Insert(L"Windows.Media.Protection.PMPStoreContext", winrt::box_value(L"com-cq-test-container"));

// This line will create a subproecss "MFPMP.EXE"
winrt::Windows::Media::Protection::MediaProtectionPMPServer pmpServer{ pmpServerProp };

//ComPtr<IMFPMPHostApp> pmpHostApp;
winrt::com_ptr<IMFPMPHost> pmpHostApp;
winrt::com_ptr<IMFGetService> spIMFGetService = pmpServer.as<IMFGetService>();
hr = spIMFGetService->GetService(MF_PMP_SERVICE, IID_PPV_ARGS(pmpHostApp.put()));

This works in a Win32 App. but it will not work in an AppContianer. My final target is to wrap a win32 process in an AppContainer, like edge browser.

Do you have any other suggestion about getting a IMFPMPHostApp?

Thanks again!

swenkeratmicrosoft commented 2 years ago

I think that whether you can obtain an IMFPMPHostApp (using GetService above) actually depends on whether or not you are running in an app container. In other words, I think your application will most likely only function after you've done the work to wrap it in an app container.

Since you're planning to do that anyway, can you front-load the work to run your current win32 process inside an app container and see if obtaining IMFPMPHostAPp works in that scenario?

I hope this helps, -Sam Wenker Microsoft PlayReady Architect

cjw1115 commented 2 years ago

I tried in three cases to GetService for IMFPMPHostApp

  1. AppContainer, created by interface CreateAppContainerProfile, has no package identity
  2. UWP APP, normal uwp app,
  3. Desktop App, a win32 console app.

For 1 and 3, always get error "WinRT originate error - 0xC00D36BA : 'The object does not support the specified service.'." For 2, it works as expected.

Look like that mfpmp server create IMFPMPHostApp or IMFPMPHost based on different app model. I'm confused why I created AppContainer by code can not get IMFPMPHostApp.

Do you know mfpmp server's internal behavior about this? how it judge app model? And edge browser also create AppContainer by code(found in chromium code base), curious why it works

swenkeratmicrosoft commented 2 years ago

From looking at the source code in the OS itself, the following is the check to determine whether IMFPMPHostApp can be returned. However, I don't know what actually causes this API to return "InboxOnly" versus "All". It's obviously something that the browser is doing - perhaps you can figure out what?

AppPolicyMediaFoundationCodecLoading policy;
AppPolicyGetMediaFoundationCodecLoading(GetCurrentThreadEffectiveToken(), &policy);

switch (policy)
{
case AppPolicyMediaFoundationCodecLoading_InboxOnly:
    // this case allows IMFPMPHostApp
default:
    // this case does not

-Sam Wenker Microsoft PlayReady Architect

cjw1115 commented 2 years ago

Since edge also have this error:"0xC00D36BA : 'The object does not support the specified service.'" looks their AppPolicyMediaFoundationCodecLoading is all too. I also verified it in Chromium.

If I don't set SetPMPHostApp, just go to step GenerateRequest, it failed with code 0x8004c3e8. And in this step, I found it will access mspr.hds file multiple times and access Playready web service.

In the VS Output Panel, a code "0x8004b839(MSPR_E_REDIRECT)" is printed too. no sure which internal step failed!

Is their any chance to find out reason in my previous trace file. simple_full_trace.zip

Thanks.

swenkeratmicrosoft commented 2 years ago

Are you saying that when you debug Edge/Chrome, when it invokes the following code specifically to obtain the IMFPMPHostApp interface, it also gets 0xC00D36BA?

winrt::com_ptr pmpHostApp; winrt::com_ptr spIMFGetService = pmpServer.as(); hr = spIMFGetService->GetService(MF_PMP_SERVICE, IID_PPV_ARGS(pmpHostApp.put()));

I ask because the error 0xC00D36BA is returned in dozens of places and 99% of them are expected and handled, so if you're just seeing that error code in the VS Output Panel when debugging Edge/Chrome, that's totally meaningless.

MSPR_E_REDIRECT is also expected and handled internally. It is benign and can be ignored.

Looking at the VS Output Panel for error codes in the OS is generally useless. Code like the following is extremely common in the OS. You'd see "E_FOO" in your VS Output Panel even though no end-user would ever be impacted by it.

hr = doStuff(); if( hr == S_OK ) { doOk(); } else if( hr == E_FOO ) { hr = S_OK; doFoo(); } else { fail... }

As far as I know, GenerateRequest is expected to fail with 0x8004c3e8 unless SetPMPHostApp is called in both the browser and in your scenario.

-Sam Wenker Microsoft PlayReady Architect

swenkeratmicrosoft commented 2 years ago

I've gotten some more information from another developer that will hopefully help us here.

I assume you already have the following pointer initialized to something.

winrt::com_ptr spIMFContentDecryptionModule;


First, try the following pseudocode. Note the use of MF_CDM_SERVICE and not MF_PMP_SERVICE.

winrt::com_ptr<IMFGetService> spIMFGetService = spIMFContentDecryptionModule.as<IMFGetService>();
winrt::com_ptr<IMFPMPHostApp> spIMFPMPHostApp;
hr = spIMFGetService->GetService(MF_CDM_SERVICE, IID_PPV_ARGS(spIMFPMPHostApp.put()));

// If hr succeeds, you've got your spIMFPMPHostApp and you can call SetPMPHostApp.

If that doesn't work, my next reply will have alternative (longer) pseudo-code to try.

-Sam Wenker Microsoft PlayReady Architect

swenkeratmicrosoft commented 2 years ago
//
// First, define a class that implements IMFPMPHostApp yourself
// It's implemented on top of IMFPMPHost itself
// This is obviously pseudocode, for example "CHK" isn't defined but it's where you'd do error handling for failed HRESULTs
//

class PMPHostWrapper : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::RuntimeClassType::ClassicCom>, IMFPMPHostApp> {
 public:
  PMPHostWrapper() {}
  ~PMPHostWrapper() {}

  HRESULT RuntimeClassInitialize(winrt::com_ptr<IMFPMPHost>& host) {
    m_spIMFPMPHost = host.as(&m_spIMFPMPHost);
    return S_OK;
  }

  STDMETHODIMP LockProcess() override {
    return m_spIMFPMPHost->LockProcess();
  }

  STDMETHODIMP UnlockProcess() override {
    return m_spIMFPMPHost->UnlockProcess();
  }

  STDMETHODIMP ActivateClassById(LPCWSTR id, IStream* stream, REFIID riid, void** activated_class) override {
    HRESULT hr = S_OK;
    wchar_t guid[MAX_PATH] = {};
    StringFromGUID2(riid, guid, std::size(guid));

    ComPtr<IMFAttributes> creation_attributes;
    CHK(MFCreateAttributes(&creation_attributes, 3));
    CHK(creation_attributes->SetString(GUID_ClassName, id));

    if (stream) {
      STATSTG statstg;
      CHK(stream->Stat(&statstg, STATFLAG_NOOPEN | STATFLAG_NONAME));

      std::vector<uint8_t> stream_blob(statstg.cbSize.LowPart);
      unsigned long read_size = 0;
      CHK(stream->Read(&stream_blob[0], stream_blob.size(), &read_size));
      CHK(creation_attributes->SetBlob(GUID_ObjectStream, &stream_blob[0],
                                       read_size));
    }

    // Serialize attributes
    ComPtr<IStream> output_stream;
    CHK(CreateStreamOnHGlobal(nullptr, TRUE, &output_stream));
    CHK(MFSerializeAttributesToStream(creation_attributes.Get(), 0,
                                      output_stream.Get()));
    CHK(output_stream->Seek({}, STREAM_SEEK_SET, nullptr));

    ComPtr<IMFActivate> activator;
    CHK(m_spIMFPMPHost->CreateObjectByCLSID(
        CLSID_EMEStoreActivate, output_stream.Get(), IID_PPV_ARGS(&activator)));
    CHK(activator->ActivateObject(riid, activated_class));

    return hr;
  }

private:
  winrt::com_ptr<IMFPMPHost> m_spIMFPMPHost;
};

// Again, note the use of MF_CDM_SERVICE and *not* MF_PMP_SERVICE.

winrt::com_ptr<IMFGetService> spIMFGetService = spIMFContentDecryptionModule.as<IMFGetService>();
winrt::com_ptr<IMFPMPHost> spIMFPMPHost;
hr = spIMFGetService->GetService(MF_CDM_SERVICE, IID_PPV_ARGS(spIMFPMPHost.put()));

// Assuming hr succeeds, you've got your spIMFPMPHost, so now create your wrapper...

winrt::com_ptr<IMFPMPHost> spIMFPMPHostApp;
hr = MakeAndInitialize<PMPHostWrapper>(&spIMFPMPHostApp, spIMFPMPHost);

// Assuming hr succeeds, you've got your spIMFPMPHostApp and you can call SetPMPHostApp.

Do either my previous reply or this reply get GenerateRequest working?

-Sam Wenker Microsoft PlayReady Architect

cjw1115 commented 2 years ago

I can create a CMD(PMP Server)by interface IMFContentDecryptionModuleAccess::CreateContentDecryptionModule,

then, with below code, I can get a IMFPMPHost object from the CDM, but it can not be used to get object IMFPMPHostApp in AppContainer.

winrt::com_ptr<IMFPMPHost> pmpHost;
winrt::com_ptr<IMFGetService> cdmService = cdm.as<IMFGetService>();
hr = cdmService->GetService(MF_CONTENTDECRYPTIONMODULE_SERVICE, IID_PPV_ARGS(pmpHost.put()));

BTW, I can not find definition of MF_CDM_SERVICE, but I found MF_CONTENTDECRYPTIONMODULE_SERVICE, looks they are same thing.

However, this is a big progress since I don't need to use interface Windows::Media::Protection::MediaProtectionPMPServer to create PMP server again to obtain a IMFPMPHost object

cjw1115 commented 2 years ago

After I can get a IMFPMPHost object from CDM. I tried to use PMPHostWrapper you provided. It works!!!

  1. call SetPMPHostApp with wrapper, ActivateClassById is called with IID CLSID_IMFPMPHost
  2. call generateRequest, ActivateClassById is called again with IID CLSID_IMFPMPHost.
  3. then KeyMessage callback is called.

BTW, below three GUID are used in this wrapper. I can not find them in any header files until I google them.

I found two of them in https://gist.github.com/stevemk14ebr/af8053c506ef895cd520f8017a81f913, but for GUID_ObjectStream, I didn't find it.(Luckly, IStream is null in my case, so I don't need to use it now)

are they from Microsoft internal code? could you share them too?

Thanks again! The blocker here is removed! This issues can be closed!

swenkeratmicrosoft commented 2 years ago

Great! I'm so glad we were able to assist you and will close this issue with one last comment.

You are correct - those three required guids are not currently published in any public header, and that's a bug in the Windows SDK. I will follow-up internally here at Microsoft to get that fixed for a future version of the Windows SDK; most likely they will be placed in mfidl.idl / mfidl.h (but don't quote me). In the meantime, here they are.

EXTERN_GUID(GUID_ObjectStream, 0x3e73735c, 0xe6c0, 0x481d, 0x82, 0x60, 0xee, 0x5d, 0xb1, 0x34, 0x3b, 0x5f); EXTERN_GUID(GUID_ClassName, 0x77631a31, 0xe5e7, 0x4785, 0xbf, 0x17, 0x20, 0xf5, 0x7b, 0x22, 0x48, 0x02); EXTERN_GUID(CLSID_EMEStoreActivate, 0x2df7b51e, 0x797b, 0x4d06, 0xbe, 0x71, 0xd1, 0x4a, 0x52, 0xcf, 0x84, 0x21);

You are also correct that MF_CONTENTDECRYPTIONMODULE_SERVICE is the public version of MF_CDM_SERVICE. (They have the same underlying value.)

I really appreciate your patience while we worked through this with you. :)

-Sam Wenker Microsoft PlayReady Architect