Created
October 15, 2017 13:12
-
-
Save Arlorean/ac7168a86ae1daed53f41c900dd05252 to your computer and use it in GitHub Desktop.
C# port of [test.cc](https://github.com/ashumeow/webrtc4all/blob/master/gotham/MFT_WebRTC4All/test/test.cc) using [nuget MediaFoundation](https://www.nuget.org/packages/MediaFoundation/)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (C) 2013 Gotham City. All rights reserved. | |
* Copyright (C) 2017 Arlorean. All rights reserved. (C#/MediaFoundation.NET port) | |
*/ | |
using System; | |
using System.Diagnostics; | |
using MediaFoundation; | |
using MediaFoundation.Misc; | |
using static MediaFoundation.CLSID; | |
using static MediaFoundation.MFExtern; | |
using static MediaFoundation.MFAttributesClsid; | |
using BOOL = System.Boolean; | |
using DWORD = System.Int32; | |
using HWND = System.IntPtr; | |
namespace TestWMF { | |
class Program { | |
/// <summary> | |
/// Display the web camera output in a console window. | |
/// </summary> | |
/// <param name="args"></param> | |
static void Main(string[] args) { | |
testSource(); | |
testSink(); | |
} | |
static void testSource() { | |
HResult hr; | |
IMFMediaSource pIMFSource = null; | |
IMFMediaSession pIMFSession = null; | |
IMFTopology pIMFTopology = null; | |
HWND hVideoWnd = Process.GetCurrentProcess().MainWindowHandle; | |
IMFPresentationDescriptor pIMFPD = null; | |
// Create the Custom Source | |
CHECK_HR(hr = CreateVideoDeviceSource(out pIMFSource)); | |
// Create the Presentation Descriptor | |
CHECK_HR(hr = pIMFSource.CreatePresentationDescriptor(out pIMFPD)); | |
// Create the Media Session | |
CHECK_HR(hr = MFCreateMediaSession(null, out pIMFSession)); | |
// Create the Playback Topology | |
CHECK_HR(hr = CreatePlaybackTopology(pIMFSource, pIMFPD, hVideoWnd, out pIMFTopology)); | |
// Run Session | |
CHECK_HR(hr = RunSession(pIMFSession, pIMFTopology)); | |
{ | |
IMFMediaEvent pEvent = null; | |
while (true) { | |
HResult hrStatus = HResult.S_OK; | |
MediaEventType met; | |
CHECK_HR(hr = pIMFSession.GetEvent(0, out pEvent)); | |
CHECK_HR(hr = pEvent.GetStatus(out hrStatus)); | |
CHECK_HR(hr = pEvent.GetType(out met)); | |
if (FAILED(hrStatus)) { | |
switch (hrStatus) { | |
case HResult.MF_E_TOPO_CODEC_NOT_FOUND: printf("MF_E_TOPO_CODEC_NOT_FOUND"); break; | |
case HResult.MF_E_NO_SAMPLE_TIMESTAMP: printf("MF_E_NO_SAMPLE_TIMESTAMP"); break; | |
} | |
printf("Session error: 0x{0:x} (event id: {1:d})\n", hrStatus, met); | |
hr = hrStatus; | |
goto done; | |
} | |
if (met == MediaEventType.MESessionEnded) { | |
break; | |
} | |
SafeRelease(pEvent); | |
} | |
} | |
done: | |
if (pIMFSession != null) { | |
pIMFSession.Shutdown(); | |
} | |
if (pIMFSource != null) { | |
pIMFSource.Shutdown(); | |
} | |
SafeRelease(pIMFSource); | |
SafeRelease(pIMFSession); | |
SafeRelease(pIMFPD); | |
SafeRelease(pIMFTopology); | |
} | |
static void testSink() { | |
} | |
// Create a playback topology from a media source. | |
static HResult CreatePlaybackTopology( | |
IMFMediaSource pSource, // Media source. | |
IMFPresentationDescriptor pPD, // Presentation descriptor. | |
HWND hVideoWnd, // Video window. | |
out IMFTopology ppTopology) // Receives a pointer to the topology. | |
{ | |
ppTopology = null; | |
IMFTopology pTopology = null; | |
DWORD cSourceStreams = 0; | |
// Create a new topology. | |
HResult hr = MFCreateTopology(out pTopology); | |
if (FAILED(hr)) { | |
goto done; | |
} | |
// Get the number of streams in the media source. | |
hr = pPD.GetStreamDescriptorCount(out cSourceStreams); | |
if (FAILED(hr)) { | |
goto done; | |
} | |
// For each stream, create the topology nodes and add them to the topology. | |
for (DWORD i = 0; i < cSourceStreams; i++) { | |
hr = AddBranchToPartialTopology(pTopology, pSource, pPD, i, hVideoWnd); | |
if (FAILED(hr)) { | |
goto done; | |
} | |
} | |
// Return the IMFTopology pointer to the caller. | |
ppTopology = pTopology; | |
//(*ppTopology).AddRef(); | |
done: | |
//SafeRelease(pTopology); | |
return hr; | |
} | |
// Add a topology branch for one stream. | |
// | |
// For each stream, this function does the following: | |
// | |
// 1. Creates a source node associated with the stream. | |
// 2. Creates an output node for the renderer. | |
// 3. Connects the two nodes. | |
// | |
// The media session will add any decoders that are needed. | |
static HResult AddBranchToPartialTopology( | |
IMFTopology pTopology, // Topology. | |
IMFMediaSource pSource, // Media source. | |
IMFPresentationDescriptor pPD, // Presentation descriptor. | |
DWORD iStream, // Stream index. | |
HWND hVideoWnd) // Window for video playback. | |
{ | |
IMFStreamDescriptor pSD = null; | |
IMFActivate pSinkActivate = null; | |
IMFTopologyNode pSourceNode = null; | |
IMFTopologyNode pOutputNode = null; | |
BOOL fSelected = false; | |
HResult hr = pPD.GetStreamDescriptorByIndex(iStream, out fSelected, out pSD); | |
if (FAILED(hr)) { | |
goto done; | |
} | |
if (fSelected) { | |
// Create the media sink activation object. | |
hr = CreateMediaSinkActivate(pSD, hVideoWnd, out pSinkActivate); | |
if (FAILED(hr)) { | |
goto done; | |
} | |
// Add a source node for this stream. | |
hr = AddSourceNode(pTopology, pSource, pPD, pSD, out pSourceNode); | |
if (FAILED(hr)) { | |
goto done; | |
} | |
// Create the output node for the renderer. | |
hr = AddOutputNode(pTopology, pSinkActivate, 0, out pOutputNode); | |
if (FAILED(hr)) { | |
goto done; | |
} | |
// Connect the source node to the output node. | |
hr = pSourceNode.ConnectOutput(0, pOutputNode, 0); | |
} | |
// else: If not selected, don't add the branch. | |
done: | |
SafeRelease(pSD); | |
SafeRelease(pSinkActivate); | |
SafeRelease(pSourceNode); | |
SafeRelease(pOutputNode); | |
return hr; | |
} | |
// Create an activation object for a renderer, based on the stream media type. | |
static HResult CreateMediaSinkActivate( | |
IMFStreamDescriptor pSourceSD, // Pointer to the stream descriptor. | |
HWND hVideoWindow, // Handle to the video clipping window. | |
out IMFActivate ppActivate | |
) { | |
ppActivate = null; | |
IMFMediaTypeHandler pHandler = null; | |
IMFActivate pActivate = null; | |
// Get the media type handler for the stream. | |
HResult hr = pSourceSD.GetMediaTypeHandler(out pHandler); | |
if (FAILED(hr)) { | |
goto done; | |
} | |
// Get the major media type. | |
Guid guidMajorType; | |
hr = pHandler.GetMajorType(out guidMajorType); | |
if (FAILED(hr)) { | |
goto done; | |
} | |
// Create an IMFActivate object for the renderer, based on the media type. | |
if (MFMediaType.Audio == guidMajorType) { | |
// Create the audio renderer. | |
hr = MFCreateAudioRendererActivate(out pActivate); | |
} | |
else if (MFMediaType.Video == guidMajorType) { | |
// Create the video renderer. | |
hr = MFCreateVideoRendererActivate(hVideoWindow, out pActivate); | |
} | |
else { | |
// Unknown stream type. | |
hr = HResult.E_FAIL; | |
// Optionally, you could deselect this stream instead of failing. | |
} | |
if (FAILED(hr)) { | |
goto done; | |
} | |
// Return IMFActivate pointer to caller. | |
ppActivate = pActivate; | |
//(*ppActivate).AddRef(); | |
done: | |
SafeRelease(pHandler); | |
//SafeRelease(pActivate); | |
return hr; | |
} | |
// Add a source node to a topology. | |
static HResult AddSourceNode( | |
IMFTopology pTopology, // Topology. | |
IMFMediaSource pSource, // Media source. | |
IMFPresentationDescriptor pPD, // Presentation descriptor. | |
IMFStreamDescriptor pSD, // Stream descriptor. | |
out IMFTopologyNode ppNode) // Receives the node pointer. | |
{ | |
IMFTopologyNode pNode = null; | |
HResult hr = HResult.S_OK; | |
CHECK_HR(hr = MFCreateTopologyNode(MFTopologyType.SourcestreamNode, out pNode)); | |
CHECK_HR(hr = pNode.SetUnknown(MF_TOPONODE_SOURCE, pSource)); | |
CHECK_HR(hr = pNode.SetUnknown(MF_TOPONODE_PRESENTATION_DESCRIPTOR, pPD)); | |
CHECK_HR(hr = pNode.SetUnknown(MF_TOPONODE_STREAM_DESCRIPTOR, pSD)); | |
CHECK_HR(hr = pTopology.AddNode(pNode)); | |
// Return the pointer to the caller. | |
ppNode = pNode; | |
//(*ppNode).AddRef(); | |
done: | |
//SafeRelease(pNode); | |
return hr; | |
} | |
// Add an output node to a topology. | |
static HResult AddOutputNode( | |
IMFTopology pTopology, // Topology. | |
IMFActivate pActivate, // Media sink activation object. | |
DWORD dwId, // Identifier of the stream sink. | |
out IMFTopologyNode ppNode) // Receives the node pointer. | |
{ | |
IMFTopologyNode pNode = null; | |
HResult hr = HResult.S_OK; | |
CHECK_HR(hr = MFCreateTopologyNode(MFTopologyType.OutputNode, out pNode)); | |
CHECK_HR(hr = pNode.SetObject(pActivate)); | |
CHECK_HR(hr = pNode.SetUINT32(MF_TOPONODE_STREAMID, dwId)); | |
CHECK_HR(hr = pNode.SetUINT32(MF_TOPONODE_NOSHUTDOWN_ON_REMOVE, 0/*FALSE*/)); | |
CHECK_HR(hr = pTopology.AddNode(pNode)); | |
// Return the pointer to the caller. | |
ppNode = pNode; | |
//(*ppNode).AddRef(); | |
done: | |
//SafeRelease(pNode); | |
return hr; | |
} | |
// Run session | |
static HResult RunSession(IMFMediaSession pSession, IMFTopology pTopology) { | |
IMFMediaEvent pEvent = null; | |
PropVariant var = new PropVariant(); | |
MediaEventType met; | |
HResult hrStatus = HResult.S_OK; | |
HResult hr = HResult.S_OK; | |
CHECK_HR(hr = pSession.SetTopology(0, pTopology)); | |
CHECK_HR(hr = pSession.Start(Guid.Empty, var)); | |
// Check first event | |
hr = pSession.GetEvent(MFEventFlag.NoWait, out pEvent); | |
if (hr == HResult.MF_E_NO_EVENTS_AVAILABLE) { | |
hr = HResult.S_OK; | |
goto done; | |
} | |
CHECK_HR(hr = pEvent.GetStatus(out hrStatus)); | |
if (FAILED(hrStatus)) { | |
CHECK_HR(hr = pEvent.GetType(out met)); | |
printf("Session error: 0x{0:x} (event id: {1:d})\n", hrStatus, met); | |
hr = hrStatus; | |
goto done; | |
} | |
done: | |
SafeRelease(pEvent); | |
return hr; | |
} | |
static HResult CreateVideoDeviceSource(out IMFMediaSource ppSource) { | |
ppSource = null; | |
IMFMediaSource pSource = null; | |
IMFAttributes pAttributes = null; | |
IMFActivate[] ppDevices = null; | |
DWORD count = 0; | |
// Create an attribute store to specify the enumeration parameters. | |
HResult hr = MFCreateAttributes(out pAttributes, 1); | |
if (FAILED(hr)) { | |
goto done; | |
} | |
// Source type: video capture devices | |
hr = pAttributes.SetGUID( | |
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, | |
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID | |
); | |
if (FAILED(hr)) { | |
goto done; | |
} | |
// Enumerate devices. | |
hr = MFEnumDeviceSources(pAttributes, out ppDevices, out count); | |
if (FAILED(hr)) { | |
goto done; | |
} | |
if (count == 0) { | |
hr = HResult.E_FAIL; | |
goto done; | |
} | |
// Create the media source object. | |
hr = ppDevices[0].ActivateObject(out pSource); | |
if (FAILED(hr)) { | |
goto done; | |
} | |
ppSource = pSource; | |
//ppSource.AddRef(); | |
done: | |
SafeRelease(pAttributes); | |
for (var i = 0; i < count; i++) { | |
SafeRelease(ppDevices[i]); | |
} | |
//CoTaskMemFree(ppDevices); | |
//SafeRelease(pSource); | |
return hr; | |
} | |
static void printf(string s) { | |
Console.WriteLine(s); | |
} | |
static void printf(string format, params object[] args) { | |
Console.WriteLine(format, args); | |
} | |
static void CHECK_HR(HResult hr) { | |
MFError.ThrowExceptionForHR(hr); | |
} | |
static bool FAILED(HResult hr) { | |
return COMBase.Failed(hr); | |
} | |
static void SafeRelease(object obj) { | |
COMBase.SafeRelease(obj); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment