I've been Googling/StackOverflowing for days and can't figure this out.
I have a WPF project where I want to play videos on multiple screens simultaneously with separate audio systems. For playing video, we're using WPFMediaKit, which relies on DirectShow Lib .NET. For other systems, we'd like to use something a little more modern, such as the Core Audio API via NAudio. We control the environment and only have to support Windows 7 (may migrate to 8 in the future).
WPFMediaKit's MediaUriElement
allows you to set its AudioRenderer
but it exhibits issues because of the 31 character limit imposed by... DirectSound? I'm not entirely sure where the limitation comes from but it's a problem because all of our audio devices have very similar names (which most likely won't be unique within the space of 31 characters). So, the plan is to modify the source and allow it to take in a GUID representing a DirectSound device and change its behavior to look it up by that. The code that'll present the audio device endpoint list to the user will rely on NAudio's MMDeviceEnumerator
from the Core Audio API.
I've gotten this far:
private static void DemonstrateTheIssue() {
SpeechSynthesizer speech = new SpeechSynthesizer();
MemoryStream stream = new MemoryStream();
SpeechAudioFormatInfo synthFormat = new SpeechAudioFormatInfo(EncodingFormat.Pcm, 88200, 16, 1, 16000, 2, null);
speech.SetOutputToAudioStream(stream, synthFormat);
speech.Speak("This is a test. This is only a test.");
stream.Position = 0;
RawSourceWaveStream reader = new RawSourceWaveStream(stream, new WaveFormat());
MMDeviceEnumerator coreAudioDeviceEnumerator = new MMDeviceEnumerator();
MMDeviceCollection coreAudioAudioEndPoints = coreAudioDeviceEnumerator.EnumerateAudioEndPoints(DataFlow.Render, DeviceState.Active);
MMDevice coreAudioSpdif = coreAudioAudioEndPoints.First(ep => ep.FriendlyName.Contains("S/PDIF")); // just an example. In the real application, would be selected by user and the GUID would be stored instead of the FriendlyName
string spdifGuidValue = coreAudioSpdif.Properties[PropertyKeys.PKEY_AudioEndpoint_GUID].Value as string;
Guid spdifGuid = Guid.Parse(spdifGuidValue);
DirectSoundOut directSoundSpdif = new DirectSoundOut(spdifGuid);
directSoundSpdif.Init(reader);
directSoundSpdif.Play();
}
This code works. When I inspect NAudio's source of DirectSoundOut
, I can see it calls DirectSoundOut.DirectSoundCreate
from dsound.dll
, which initializes an IDirectSound
instance. I'm not entirely sure what I can do with that instance. So, going back to the other side of this puzzle...
Let's look at what WPFMediaKit does when I give it an AudioRenderer
of "Digital Audio (S/PDIF) (High De"
. Digging through the source, I eventually came across AddFilterByName(IGraphBuilder graphBuilder, Guid deviceCategory, string friendlyName)
which eventually calls AddFilterByDevice(IGraphBuilder graphBuilder, DsDevice device)
. Okay, so this uses its own wrapper class, DsDevice
... which wraps IMoniker
and a few other things, including some properties from an IPropertyBag
attached to the IMoniker
instance. The last line of C# code I see before my trail vanishes into COM calls this: filterGraph.AddSourceFilterForMoniker(device.Mon, null, device.Name, out filter)
. filterGraph
is an IFilterGraph2
instance and device.Name
returns "FriendlyName"
from the moniker's property bag.
I feel like I'm close but I just can't connect the dots. How can I take an IDirectSound
instance and tell the filterGraph
to play audio on it? Do I need to get a moniker for the IDirectSound
? If so, how? Is there another method I'm unaware of? Or... can I create/compose/get a reference to my own filter and tell the filterGraph
about it? If so, how would I go about doing that?
Please answer this question like I'm a child -- this stuff is really over my head.