Date

Windows Vista に搭載されている Windows Core Audio を使って、サウンドデバイスからキャプチャ(録音)をする。Windows Core Audioの特徴を生かして、排他モードで行い、低レイテンシを目指す。

はじめにデバイスの「エンドポイント」を取得する手順を説明する。通常、サウンドデバイスには入出力端子が複数搭載されている。入力系ではMicrophone や Line In 、出力系では前面と背面それぞれの Line Out などである。エンドポイントはデバイス上にあるこれらの端子1つを表す。録音するには、どのエンドポイントから録音するのか決める。

あるデバイス上のエンドポイントから録音するクラスをCoreAudioCaptorとする。

1
2
3
4
5
6
7
class CoreAudioCaptor {
    CComPtr<IMMDeviceEnumerator> pEnum;
    CComPtr<IMMDeviceCollection> pEndCollect;
    CComPtr<IMMDevice> pCapDevice;
    CComPtr<IAudioClient> pAudClient;
    CComPtr<IAudioCaptureClient> pCapClient;
    CComPtr<IAudioClock> pClock;

MMDeviceEnumeratorのインスタンスを作成し、IMMDeviceEnumerator::EnumAudioEndpointsを呼び出すと、エンドポイントのコレクションIMMDeviceCollectionを取得できる。このとき引数にDEVICE_STATE_ACTIVEを渡すと、アクティブなエンドポイントのみ取得する。アクティブなエンドポイントとは、エンドポイントに対応する端子にケーブルが差し込まれて使用可能な状態のことを言います。Windows Vistaではケーブルが端子に差し込まれたことを検出し、その段階でエンドポイントが使用可能になる機能がある。検出が不可能なデバイスもある。

IMMDeviceCollection::GetCountで数を取得できる。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
HRESULT SetEndPoint() {
    HRESULT hr;
    // アクティブなキャプチャ用のエンドポイントを列挙
    if(FAILED(pEnum.CreateInstance<MMDeviceEnumerator>
        (__uuidof(IMMDeviceEnumerator))))
    {
        return E_FAIL;
    }
    pEnum->EnumAudioEndpoints(eCapture, DEVICE_STATE_ACTIVE, &pEndCollect);
    UINT count;
    pEndCollect->GetCount(&count);
    DebugMsg(_T("%S count=%d"), __FUNCTION__, count);

エンドポイントのコレクションから、任意にエンドポイントを取得する。取得したエンドポイントはIMMDeviceを持つ。IMMDevice::Activateを呼び出してエンドポイントをアクティベートする。アクティベートするとIAudioClientを取得できる。録音・再生の制御はIAudioClientで行う。

一般的なアプリケーションであればここでエンドポイントをすべて取得し、ユーザに対してどのエンドポイントから録音するか選択させれば良いだろう。その際、ユーザフレンドリな名前を表示できると良い。そこで IMMDevice::OpenPropertyStoreを使って取得 したIPropertyStoreIPropertyStore::GetValueを使って名前を取得 する。取得した名前のバッファはPROPVARIANT型である。バッファは ComTaskMemFreeで解放する。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
for(UINT i=0;i<count;++i) {
    CComPtr<IMMDevice> pItem;
    LPWSTR id_str;
    CComPtr<IPropertyStore> pPropStore;
    PROPVARIANT varName;
    DWORD state;
    pEndCollect->Item(i, &pItem);
    pItem->GetId(&id_str);
    pItem->OpenPropertyStore(STGM_READ, &pPropStore);
    pItem->GetState(&state);
    PropVariantInit(&varName);
    pPropStore->GetValue(PKEY_Device_FriendlyName, &varName);
    DebugMsg(_T("%S #%d %s %s %X"), __FUNCTION__, i, id_str, varName.pwszVal, state);
    PropVariantClear(&varName);
    CoTaskMemFree(id_str);
    //フローは必ずキャプチャであるはず
    CComPtr<IMMEndpoint> pEndPoint;
    pItem.QueryInterface<IMMEndpoint>(&pEndPoint);
    EDataFlow flow;
    pEndPoint->GetDataFlow(&flow);
    assert(flow==eCapture);
    // 2番目のエンドポイントを選択
    if(i == 1) {
        pCapDevice=pItem;
        if(FAILED(pCapDevice->Activate(
            __uuidof(IAudioClient), CLSCTX_ALL,NULL, (void**)&pAudClient)))
        {
            return E_FAIL;
        }
    }
}

Comments

comments powered by Disqus