potisanのプログラミングメモ

趣味のプログラマーがプログラミング関係で気になったことや調べたことをいつでも忘れられるようにメモするブログです。はてなブログ無料版なので記事の上の方はたぶん広告です。記事中にも広告挿入されるみたいです。

C++11 STL&WRL&MMDevice APIでオーディオエンドポイントを列挙する

はじめに

前回の投稿ではWRLとMMDevice APIを用いてデバイスとそのプロパティの取得を行いました。今回はそのコードを改変してデバイスの列挙を行います。

サンプルコード

以下にコードを示します。

#pragma comment(lib, "propsys.lib")

#include <memory>
#include <string>
#include <sstream>
#include <vector>

#define STRICT
#include <Windows.h>
#include <propvarutil.h>

#include <wrl.h>

#include <mmdeviceapi.h>
#include <endpointvolume.h>
#include <functiondiscoverykeys_devpkey.h>

using namespace std;
using namespace Microsoft::WRL;
using namespace Microsoft::WRL::Details;

// スコープ範囲でのCOMの初期化・解放を自動化します。
// ref. http://msdn.microsoft.com/ja-jp/library/jj822931(v=vs.110).aspx
class CoInitializeWrapper
{
private:
    HRESULT m_hr;
public:
    CoInitializeWrapper(DWORD dwCoInit = 0)
    {
        m_hr = CoInitializeEx(nullptr, dwCoInit);
    }

    ~CoInitializeWrapper()
    {
        if (SUCCEEDED(m_hr))
        {
            CoUninitialize();
        }
    }

    operator HRESULT() const { return m_hr; }
};

template <typename T> struct CoTaskMem_deleter
{
    CoTaskMem_deleter() {}
    void operator() (T* p) { CoTaskMemFree(p); }
};

HRESULT PropertyStoreReadStringValueAlloc(
    IN IPropertyStore* pPropStore,
    IN const PROPERTYKEY& key,
    OUT LPWSTR* ppsz);

LPWSTR PropertyStoreReadStringValueAllocAutoException(
    IN IPropertyStore* pPropStore,
    IN const PROPERTYKEY& key);

int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int)
{
    // COMの初期化
    CoInitializeWrapper initialize;
    if (FAILED(initialize)) RaiseException(initialize);

    // MMDeviceEnumeratorの作成
    ComPtr<IMMDeviceEnumerator> deviceEnumerator;
    HRESULT hr = CoCreateInstance(
        __uuidof(MMDeviceEnumerator),
        nullptr,
        CLSCTX_INPROC_SERVER,
        IID_PPV_ARGS(&deviceEnumerator));
    if (FAILED(hr)) RaiseException(hr);

    // 全ての状態の出力オーディオエンドポイントを列挙
    ComPtr<IMMDeviceCollection> devices;
    hr = deviceEnumerator->EnumAudioEndpoints(
        EDataFlow::eAll,
        DEVICE_STATE_ACTIVE | DEVICE_STATE_DISABLED | DEVICE_STATE_NOTPRESENT | DEVICE_STATE_UNPLUGGED,
        &devices);
    if (FAILED(hr)) RaiseException(hr);

    // デバイスを列挙してそれぞれの説明を取得
    vector<wstring> device_descriptions;
    UINT uNumDevices;
    hr = devices->GetCount(&uNumDevices);
    if (FAILED(hr)) RaiseException(hr);
    for (UINT i = 0; i < uNumDevices; i++)
    {
        ComPtr<IMMDevice> device;
        hr = devices->Item(i, &device);

        ComPtr<IPropertyStore> propStore;
        hr = device->OpenPropertyStore(STGM_READ, &propStore);
        if (FAILED(hr)) RaiseException(hr);
        unique_ptr<WCHAR, CoTaskMem_deleter<WCHAR>> device_description(
            PropertyStoreReadStringValueAllocAutoException(propStore.Get(),
            PKEY_Device_DeviceDesc));

        device_descriptions.push_back(device_description.get());
    }

    // ','で区切って表示
    wostringstream s;
    copy(device_descriptions.begin(), device_descriptions.end(),
        ostream_iterator<wstring, WCHAR>(s, L","));
    MessageBox(HWND_DESKTOP, s.str().c_str(), nullptr, MB_OK);

    return 0;
}

// IPropertyStoreから文字列プロパティを読み込んで返します。
// 取得した文字列はCoTaskMemFreeで解放して下さい。
HRESULT PropertyStoreReadStringValueAlloc(
    IN IPropertyStore* pPropStore,
    IN const PROPERTYKEY& key,
    OUT LPWSTR* ppsz)
{
    PROPVARIANT propvar;
    PropVariantInit(&propvar);
    HRESULT hr = pPropStore->GetValue(key, &propvar);
    if (SUCCEEDED(hr))
        hr = PropVariantToStringAlloc(propvar, ppsz);
    PropVariantClear(&propvar);
    return hr;
}

// IPropertyStoreから文字列プロパティの読み込んで返します。
// 取得した文字列はCoTaskMemFreeで解放して下さい。
// 内部で関数が失敗した場合はエラーコードを引数にMicrosoft::WRL::Details::RaiseException関数を呼び出します。
LPWSTR PropertyStoreReadStringValueAllocAutoException(
    IN IPropertyStore* pPropStore,
    IN const PROPERTYKEY& key)
{
    LPTSTR buffer = nullptr;
    HRESULT hr = PropertyStoreReadStringValueAlloc(
        pPropStore, key, &buffer);
    if (FAILED(hr))
        RaiseException(hr);
    return buffer;
}

実行結果

スピーカー,マイク,ステレオ ミキサー,

IMMDeviceEnumerator.EnumAudioEndpointsメソッド

サンプルコードにあるように引数としてオーディオエンドポイントの種類(ERole列挙型のメンバ)とステータスを要求します。

種類に関してはERole::eRender, ERole::eCapture, ERole::eAllの何れも使用することができます。対して、ステータスは省略したつもりで0を指定すると失敗するので注意して下さい。全てのステータスのエンドポイントを列挙するにはサンプルコードのようにDEVICE_STATE_*を論理和演算|でつなぎます。