簡體   English   中英

如何使用選定的輸出設備上的本機API在Windows上播放聲音(mp3 / WAV)

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

我只是希望能夠為我的程序創建選項,以便用戶可以選擇使用哪個輸出設備來播放聲音,例如MS Lync中的一個:

在此輸入圖像描述

我最初是在Qt中創建程序的,我在這里問了類似(但不完全相同)的問題Qt5 +如何為QMediaPlayer設置默認音頻設備

我發現Qt對此很煩人,這是不可能的,所以我降低了要求,我將使用本機Windows API,因為這些可能只是這里的解決方案。 不幸的是,這需要重寫程序的某些部分,現在我在msdn上遵循此指南: https : //msdn.microsoft.com/zh-cn/library/windows/desktop/dd371455%28v=vs.85%29的.aspx

我基本上希望能夠做到以下幾點:

  • 列出所有可用的輸出設備並在首選項窗體上顯示它們-我已經有了使用IMMDeviceEnumerator的工作代碼
  • 讓用戶選擇要用於我的程序輸出的設備-我已經有了該部分
  • 創建一個函數,將其稱為PlaySound(string path) ,如果使用.wav或.mp3文件的PlaySound(string path)調用該函數,則會使用首選的IMMDevice並通過它播放文件- 這是我需要的幫助

因為到目前為止我一直在使用Qt,而且我幾乎IMMDevice MS Windows內部結構,所以我不知道一個人如何才能獲取存儲在磁盤上某個位置的文件並使用Windows API播放它,特別是使用用戶在其首選項中設置的所選IMMDevice 我正在使用Google搜索和搜索文檔,但是只能使用極其復雜和奇怪的解決方案,例如https://msdn.microsoft.com/en-us/library/windows/desktop/dd316756%28v=vs.85%29的.aspx

我什至可以找到一些示例,您可以在其中使用MCI設備播放mp3文件,但這並沒有真正說明如何更改首選輸出設備,因此它對我的使用不是很有用。

我知道低級API可能不會提供一些簡單的“ playmyfile”功能,但是最好至少有一些超簡單解決方案的示例或一些教程,這些教程可以在Windows上使用選定的輸出設備來播放媒體文件以便我可以以此為起點。 我有一個正在運行的活動IMMDevice ,現在我只需要使其可以播放mp3 / wav文件即可。

注意:這不是一些通用的“如何在Windows上播放聲音”問題。 我需要能夠在選定的音頻輸出設備上播放該聲音。 僅對於我的程序(就像MS Lync,VLC媒體播放器或任何其他高級音頻程序一樣)。 我不想更改系統全局首選項(默認設備等)。

我設法做到了這一點,但是令人驚訝的是使用了Windows本機庫“ DirectShow”,該庫主要用於視頻渲染,但也可以處理音頻。

如何:

枚舉輸出設備此功能遍歷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
}

為用戶選擇的設備創建一個過濾器,再次遍歷所有設備,並為用戶選擇的過濾器做一個過濾器

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();

使用我們設備的過濾器播放聲音。在此功能中, 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));
}

這段代碼本身當然不會立即進行編譯,但是簡而言之,它為您提供了執行此操作的線索:

  1. 檢索所有設備的列表並將其存儲在某處,以便我們可以為用戶創建對話框
  2. 在播放聲音之前,我們檢查用戶選擇了哪個設備並為其創建過濾器
  3. 我們將過濾器應用於DirectShow BasicAudio,它本身可以播放系統編解碼器支持的任何媒體文件。

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