I'm trying to playback mp4 files using Media Foundation. I am using this documentation as a reference. Using that logic I can playback unprotected files but cannot playback protected files. In order to playback protected files, you need to do some additional work as documented here. I am using the first two mp4 test files published here, because surely Media Foundation works with Microsoft's own DRM system. The problem is that no matter what I do, any protected mp4 file fails playback with MF_E_UNSUPPORTED_BYTESTREAM_TYPE. The failure occurs during the MESessionTopologySet event, and my implementation of IMFContentProtectionManager is not invoked for anything other than QueryInterface(). This is all on Windows 10. So how do I playback protected mp4 files using Media Foundation?
// initialization
HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
hr = MFStartup(MF_VERSION);
// load source
IMFSourceResolver* SourceResolver = NULL;
hr = MFCreateSourceResolver(&SourceResolver);
MF_OBJECT_TYPE ObjectType = MF_OBJECT_INVALID;
IUnknown* Object = NULL;
hr = SourceResolver->CreateObjectFromURL(L"tearsofsteel_1080p_60s_24fps.6000kbps.1920x1080.h264-8b.2ch.128kbps.aac.avsep.cenc.mp4", MF_RESOLUTION_MEDIASOURCE | MF_RESOLUTION_READ, NULL, &ObjectType, &Object);
IMFMediaSource* MediaSource = NULL;
hr = Object->QueryInterface(&MediaSource);
IMFPresentationDescriptor* PresentationDescriptor = NULL;
hr = MediaSource->CreatePresentationDescriptor(&PresentationDescriptor);
hr = MFRequireProtectedEnvironment(PresentationDescriptor);
wprintf_s(L"MFRequireProtectedEnvironment() hr=0x%.08X\n", hr);
// get first stream
IMFStreamDescriptor* StreamDescriptor = NULL;
BOOL Selected = FALSE;
hr = PresentationDescriptor->GetStreamDescriptorByIndex(0, &Selected, &StreamDescriptor);
IMFMediaTypeHandler* MediaTypeHandler = NULL;
hr = StreamDescriptor->GetMediaTypeHandler(&MediaTypeHandler);
GUID MajorType = GUID_NULL;
hr = MediaTypeHandler->GetMajorType(&MajorType);
// create appropriate renderer
IMFActivate* Activate = NULL;
if (MajorType == MFMediaType_Video)
{
hr = MFCreateVideoRendererActivate(NULL, &Activate);
}
else if (MajorType == MFMediaType_Audio)
{
hr = MFCreateAudioRendererActivate(&Activate);
}
// build the topology
IMFTopology* Topology = NULL;
hr = MFCreateTopology(&Topology);
// build the source node
IMFTopologyNode* SourceTopologyNode = NULL;
hr = MFCreateTopologyNode(MF_TOPOLOGY_SOURCESTREAM_NODE, &SourceTopologyNode);
hr = SourceTopologyNode->SetUnknown(MF_TOPONODE_SOURCE, MediaSource);
hr = SourceTopologyNode->SetUnknown(MF_TOPONODE_PRESENTATION_DESCRIPTOR, PresentationDescriptor);
hr = SourceTopologyNode->SetUnknown(MF_TOPONODE_STREAM_DESCRIPTOR, StreamDescriptor);
hr = Topology->AddNode(SourceTopologyNode);
// build the output node
IMFTopologyNode* OutputTopologyNode = NULL;
hr = MFCreateTopologyNode(MF_TOPOLOGY_OUTPUT_NODE, &OutputTopologyNode);
hr = OutputTopologyNode->SetObject(Activate);
hr = OutputTopologyNode->SetUINT32(MF_TOPONODE_STREAMID, 0);
hr = OutputTopologyNode->SetUINT32(MF_TOPONODE_NOSHUTDOWN_ON_REMOVE, FALSE);
hr = Topology->AddNode(OutputTopologyNode);
hr = SourceTopologyNode->ConnectOutput(0, OutputTopologyNode, 0);
// create the protected session
IMFMediaSession* MediaSession = NULL;
IMFAttributes* Configuration = NULL;
hr = MFCreateAttributes(&Configuration, 1);
IUnknown* ContentProtectionManager = new ContentProtectionManagerImpl();
Configuration->SetUnknown(MF_SESSION_CONTENT_PROTECTION_MANAGER, ContentProtectionManager);
IMFActivate* EnablerActivate = NULL;
hr = MFCreatePMPMediaSession(0, Configuration, &MediaSession, &EnablerActivate);
// set the topology
hr = MediaSession->SetTopology(0, Topology);
// get the event status
IMFMediaEvent* Event = NULL;
hr = MediaSession->GetEvent(0, &Event);
MediaEventType Type = MEUnknown;
hr = Event->GetType(&Type);
HRESULT Status = S_OK;
hr = Event->GetStatus(&Status);
wprintf_s(L"Event Type=%d Status=0x%.08X\n", Type, Status);
I managed to get this working but it's kind of a mess. Rather than post a complete code example, I'll break it down into different components. This is based on the UWP sample code. Error handling and cleanup is omitted for brevity.
First, you need to create and configure an instance of an
IMFContentDecryptionModuleobject:Ok, we now have a content decryption module that we can use to playback PlayReady content. Next, because we're not running in a UWP app we need to provide our own instance of a
IMFPMPHostAppobject as described here.The methods you need to implement are as follows:
Now we need to create a content decryption session and generate a license request:
InitDataBuffer and InitDataSize contain the PlayReady PSSH box from the MP4 file. ContentDecryptionModuleSessionCallbacksImpl needs to implement the following methods:
Now we create our media source:
Then we create our media engine extension, which is required to load our media source (otherwise we'll get
MF_E_UNSUPPORTED_BYTESTREAM_TYPE):Methods we need to implement:
Then we can create the media engine object:
Now we can configure the content protection manager:
The only method we need to implement:
Then we can create a wrapper for the media source to deal with content protection:
Where we need to implement:
Then finally we can set the media source via our extension:
And assuming all of that works, the media playback will start.
Yes, it is that simple!
I'm sure I either glossed over something or forgot to include something. Let me know if anything's missing and I'll update accordingly.