質問

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.

役に立ちましたか?

解決

DirectSound and DirectShow are two different APIs. There is no direct adding of DirectSound objects into DirectShow filter graphs and hence your inability to do impossible.

There are two ways to address the problem: you either insist that you already hold an IDirectSound interface pointer and you want to use it within DirectShow. In this case you have to implement a custom audio renderer filter, which will be backed by this DirectSound interface and your renderer will forward all audio to the device of your interest. You don't really want to go this way. In particular, you won't be able to implement this in C# only.

And the second way is to find an existing DirectShow audio renderer, which is using the device of your interest. Then use it and it will play through the same DirectSound device, just not exactly the pointer you had on your hands in first place.

More details on the second approach. DirectShow has a special category where all audio renderers are listed: CLSID_AudioRendererCategory. Enumerating filters there, you enumerate instances of DirectSound Renderer Filter created for every existing device.

This filter acts as a wrapper for an audio device. To enumerate the audio devices available on the user's system, use the ICreateDevEnum interface with the audio renderer category (CLSID_AudioRendererCategory). For each audio device, the audio renderer category contains two filter instances. One of these corresponds to the DirectSound Renderer, and the other corresponds to the Audio Renderer (WaveOut) filter. The DirectSound instance has the friendly name "DirectSound: DeviceName," where DeviceName is the name of the device. The WaveOut instance has the friendly name DeviceName.

You can find the right device there, and you then you can use this filter as a regular audio renderer (that is, you add it to your graph, you connect it there). The filter does not however expose IDirectSound pointer it is using internally.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top