简体   繁体   English

如何使用选定的输出设备上的本机API在Windows上播放声音(mp3 / WAV)

[英]How to play a sound (mp3 / wav) on windows using native API on selected output device

I simply want to be able to create options for my program, so that user can pick which output device will be used to play sounds, like this one in MS Lync: 我只是希望能够为我的程序创建选项,以便用户可以选择使用哪个输出设备来播放声音,例如MS Lync中的一个:

在此输入图像描述

I originally created my program in Qt and I asked similar (but not identical) question here Qt5+ How to set default audio device for QMediaPlayer 我最初是在Qt中创建程序的,我在这里问了类似(但不完全相同)的问题Qt5 +如何为QMediaPlayer设置默认音频设备

I figured out that Qt is too much bugged for this and this is impossible, so I lowered my requirements and I will use native windows API as these are probably only solution here. 我发现Qt对此很烦人,这是不可能的,所以我降低了要求,我将使用本机Windows API,因为这些可能只是这里的解决方案。 This unfortunately requires rewrite of some parts of my program, and now I am following this guide on msdn: https://msdn.microsoft.com/en-us/library/windows/desktop/dd371455%28v=vs.85%29.aspx 不幸的是,这需要重写程序的某些部分,现在我在msdn上遵循此指南: https : //msdn.microsoft.com/zh-cn/library/windows/desktop/dd371455%28v=vs.85%29的.aspx

I basically want to be able to do following: 我基本上希望能够做到以下几点:

  • List all available output devices and display them on preferences form - I already have a working code for that using IMMDeviceEnumerator 列出所有可用的输出设备并在首选项窗体上显示它们-我已经有了使用IMMDeviceEnumerator的工作代码
  • Let user pick a device they want to use for output of my program - I already have that part 让用户选择要用于我的程序输出的设备-我已经有了该部分
  • Create a function, let's call it PlaySound(string path) that if called with path of .wav or .mp3 file would use the preferred IMMDevice and play a file through it - this is what I need help with 创建一个函数,将其称为PlaySound(string path) ,如果使用.wav或.mp3文件的PlaySound(string path)调用该函数,则会使用首选的IMMDevice并通过它播放文件- 这是我需要的帮助

Because I was using Qt so far and I have pretty much no idea of MS windows internals, I have no idea how could one take a file stored somewhere on disk and play it using windows API's especially using that selected IMMDevice which user set in their preferences. 因为到目前为止我一直在使用Qt,而且我几乎IMMDevice MS Windows内部结构,所以我不知道一个人如何才能获取存储在磁盘上某个位置的文件并使用Windows API播放它,特别是使用用户在其首选项中设置的所选IMMDevice I was googling and searching through documentation, but I could only work extremely complex and weird solutions, such as https://msdn.microsoft.com/en-us/library/windows/desktop/dd316756%28v=vs.85%29.aspx 我正在使用Google搜索和搜索文档,但是只能使用极其复杂和奇怪的解决方案,例如https://msdn.microsoft.com/en-us/library/windows/desktop/dd316756%28v=vs.85%29的.aspx

I could even find some examples where you can play mp3 file using MCI device, but that didn't really explain how to alter preferred output device, so it isn't very useful for my use. 我什至可以找到一些示例,您可以在其中使用MCI设备播放mp3文件,但这并没有真正说明如何更改首选输出设备,因此它对我的使用不是很有用。

I understand that low-level API is probably not going to offer some simple "playmyfile" function, but it would be nice to have at least some example of super-simple solution or some tutorial that would play media files using selected output device on windows so that I could use that as a starting reference. 我知道低级API可能不会提供一些简单的“ playmyfile”功能,但是最好至少有一些超简单解决方案的示例或一些教程,这些教程可以在Windows上使用选定的输出设备来播放媒体文件以便我可以以此为起点。 I have a working active IMMDevice , now I just need to make it possible to play mp3 / wav files through it. 我有一个正在运行的活动IMMDevice ,现在我只需要使其可以播放mp3 / wav文件即可。

NOTE: This is not some generic "how to play a sound on windows" question. 注意:这不是一些通用的“如何在Windows上播放声音”问题。 I need to be able to play that sound on selected audio output device . 我需要能够在选定的音频输出设备上播放该声音。 For my program only (just like MS Lync, VLC media player or any other advanced audio program can). 仅对于我的程序(就像MS Lync,VLC媒体播放器或任何其他高级音频程序一样)。 I don't want to change system global preferences (default device etc). 我不想更改系统全局首选项(默认设备等)。

I managed to do that but surprisingly using windows native libraries called "DirectShow" which are primarily designed for video rendering, but can handle audio as well. 我设法做到了这一点,但是令人惊讶的是使用了Windows本机库“ DirectShow”,该库主要用于视频渲染,但也可以处理音频。

How to: 如何:

Enumerate output devices This functions iterates over all audio devices detected by OS and store them in a list. 枚举输出设备此功能遍历OS检测到的所有音频设备,并将它们存储在列表中。

void Options::Initialize()
{
#ifdef WIN
    HRESULT hr;
    ICreateDevEnum *pSysDevEnum = NULL;
    hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void **)&pSysDevEnum);
    if (FAILED(hr))
        return;

    IEnumMoniker *pEnumCat = NULL;
    hr = pSysDevEnum->CreateClassEnumerator(CLSID_AudioRendererCategory, &pEnumCat, 0);
    if (hr == S_OK)
    {
        // Enumerate the monikers.
        IMoniker *pMoniker = NULL;
        ULONG cFetched;
        while (pEnumCat->Next(1, &pMoniker, &cFetched) == S_OK)
        {
            IPropertyBag *pPropBag;
            hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pPropBag);
            if (SUCCEEDED(hr))
            {
                // To retrieve the filter's friendly name, do the following:
                VARIANT varName;
                VariantInit(&varName);
                hr = pPropBag->Read(L"FriendlyName", &varName, 0);
                if (SUCCEEDED(hr))
                {
                    OutputDevice device;
                    device.Name = QString((QChar*)varName.bstrVal, wcslen(varName.bstrVal));
                    Options::devices.append(device);
                }
                VariantClear(&varName);
                pPropBag->Release();
            }
            pMoniker->Release();
        }
        pEnumCat->Release();
    }
    pSysDevEnum->Release();
#endif
}

Create a filter for device that user selected Iterate over all devices once more and make a filter for that which was selected by user 为用户选择的设备创建一个过滤器,再次遍历所有设备,并为用户选择的过滤器做一个过滤器

HRESULT hr;
ICreateDevEnum *pSysDevEnum = NULL;
hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void **)&pSysDevEnum);
if (FAILED(hr))
{
    Error("Failed SystemDeviceEnum");
    return;
}

IEnumMoniker *pEnumCat = NULL;
QSettings s;
hr = pSysDevEnum->CreateClassEnumerator(CLSID_AudioRendererCategory, &pEnumCat, 0);
IBaseFilter *pFilter = NULL;
if (hr == S_OK)
{
    // Enumerate the monikers.
    IMoniker *pMoniker = NULL;
    ULONG cFetched;
    int i = 0;
    while (pEnumCat->Next(1, &pMoniker, &cFetched) == S_OK)
    {
        IPropertyBag *pPropBag;
        hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pPropBag);
        if (SUCCEEDED(hr))
        {
            // retrieve the filter's friendly name now
            VARIANT varName;
            VariantInit(&varName);
            hr = pPropBag->Read(L"FriendlyName", &varName, 0);
            if (SUCCEEDED(hr))
            {
                QString name = QString((QChar*)varName.bstrVal, wcslen(varName.bstrVal));
                if (s.value("d:" + name).toBool())
                {
                    hr = pMoniker->BindToObject(NULL, NULL, IID_IBaseFilter, (void**)&pFilter);
                    // now we got the filter in pFilter so we can play sound using that filter
                    PlayWin(pFilter, path);
                }
            }
            VariantClear(&varName);
            pPropBag->Release();
        }
        pMoniker->Release();
    }
    pEnumCat->Release();
}
pSysDevEnum->Release();

Play the sound using the filter for our device In this function device is pFilter from previous function 使用我们设备的过滤器播放声音。在此功能中, device为上一个功能的pFilter

HRESULT hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, __uuidof(IGraphBuilder), (void **)&x->pGraph);
if (FAILED(hr))
{
    Error("ERROR - Could not create the Filter Graph Manager.");
    return;
}

hr = x->pGraph->QueryInterface(IID_IBasicAudio, (void**)&x->pOutput);

if (FAILED(hr))
{
    Error("ERROR - Could not create the IBasicAudio.");
    return;
}

x->pFlx = device;
if (device)
    x->pGraph->AddFilter(device, L"fd");
hr = x->pGraph->QueryInterface(__uuidof(IMediaControl), (void **)&x->pControl);
hr = x->pGraph->QueryInterface(__uuidof(IMediaEvent), (void **)&x->pEvent);

// Build the graph.
hr = x->pGraph->RenderFile(path, NULL);
if (SUCCEEDED(hr))
{
    // Run the graph.
    hr = x->pControl->Run();
}
else
{
    Error("Unable to play: " + QString::fromWCharArray(path));
}

This code on itself is of course not going to compile out of box, but it gives you a clue how to do this, in nutshell: 这段代码本身当然不会立即进行编译,但是简而言之,它为您提供了执行此操作的线索:

  1. Retrieve list of all devices and store it somewhere, so that we can create dialog for user 检索所有设备的列表并将其存储在某处,以便我们可以为用户创建对话框
  2. Before we play a sound, we check which device user selected and create a filter for it 在播放声音之前,我们检查用户选择了哪个设备并为其创建过滤器
  3. We apply the filter to DirectShow BasicAudio which is itself able to play any media file supported by system codecs. 我们将过滤器应用于DirectShow BasicAudio,它本身可以播放系统编解码器支持的任何媒体文件。

Documentation on msdn: https://msdn.microsoft.com/en-us/library/windows/desktop/dd407292%28v=vs.85%29.aspx msdn上的文档: https : //msdn.microsoft.com/zh-cn/library/windows/desktop/dd407292%28v=vs.85%29.aspx

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM