レガシ オーディオ アプリケーションのオーディオ イベント

DirectSound、DirectShow、waveOutXxx 関数などのレガシ オーディオ API を使用すると、アプリケーションはオーディオ ストリームのボリューム レベルを取得および設定できます。 アプリケーションでは、これらの API のボリューム コントロール機能を使用して、アプリケーション ウィンドウにボリューム スライダーを表示できます。

Windows Vista では、システム ボリューム コントロール プログラム Sndvol により、ユーザーが個々のアプリケーションのオーディオ ボリューム レベルを制御可能になります。 アプリケーションによって表示されるボリューム スライダーは、Sndvol の対応するボリューム スライダーにリンクされる必要があります。 ユーザーがアプリケーション ウィンドウのボリューム スライダーを使用してアプリケーションの音量を調整した場合、Sndvol の対応するボリューム スライダーがすぐに移動して、新しいボリューム レベルを示します。 逆に、ユーザーが Sndvol を使用してアプリケーションのボリュームを調整した場合、アプリケーション ウィンドウのボリューム スライダーが、新しいボリューム レベルを示すように移動する必要があります。

Windows Vista では、アプリケーションが IDirectSoundBuffer::SetVolume メソッドまたは waveOutSetVolume 関数を呼び出して行ったボリューム変更がすぐに Sndvol に反映されます。 ただし、DirectSound や waveOutXxx 関数などのレガシ オーディオ API では、ユーザーが Sndvol を通じてアプリケーション ボリュームを変更したときにアプリケーションに通知する手段はありません。 アプリケーションがボリューム スライダーを表示しても、ボリュームの変更の通知を受け取らない場合、スライダーは Sndvol でユーザーが行った変更を反映することができません。 適切な動作を実装するには、レガシ オーディオ API による通知の欠如をアプリケーション デザイナーが何らかの形で補う必要があります。

1 つのソリューションとして、アプリケーションが定期的にタイマーを設定して、ボリューム レベルをチェックするように通知し、変更されたかどうかを確認する方法があります。

よりスマートなソリューションとして、アプリケーションがコア オーディオ API のイベント通知機能を使用できます。 特に、アプリケーションは IAudioSessionEvents インターフェイスを登録して、ボリュームの変更やその他の種類のオーディオ イベントが発生したときにコールバックを受信できます。 ボリュームが変更されると、ボリューム変更コールバック ルーチンは、変更を反映するようアプリケーションのボリューム スライダーを直ちに更新することができます。

次のコード例は、ボリューム変更や他のオーディオ イベントの通知を受け取るためにアプリケーションを登録する方法を示しています。

//-----------------------------------------------------------
// Register the application to receive notifications when the
// volume level changes on the default process-specific audio
// session (with session GUID value GUID_NULL) on the audio
// endpoint device with the specified data-flow direction
// (eRender or eCapture) and device role.
//-----------------------------------------------------------
#define EXIT_ON_ERROR(hr)  \
              if (FAILED(hr)) { goto Exit; }
#define SAFE_RELEASE(punk)  \
              if ((punk) != NULL)  \
                { (punk)->Release(); (punk) = NULL; }

class AudioVolumeEvents
{
    HRESULT _hrStatus;
    IAudioSessionManager *_pManager;
    IAudioSessionControl *_pControl;
    IAudioSessionEvents *_pAudioEvents;
public:
    AudioVolumeEvents(EDataFlow, ERole, IAudioSessionEvents*);
    ~AudioVolumeEvents();
    HRESULT GetStatus() { return _hrStatus; };
};

// Constructor
AudioVolumeEvents::AudioVolumeEvents(EDataFlow flow, ERole role,
                                     IAudioSessionEvents *pAudioEvents)
{
    IMMDeviceEnumerator *pEnumerator = NULL;
    IMMDevice *pDevice = NULL;

    _hrStatus = S_OK;
    _pManager = NULL;
    _pControl = NULL;
    _pAudioEvents = pAudioEvents;

    if (_pAudioEvents == NULL)
    {
        _hrStatus = E_POINTER;
        return;
    }

    _pAudioEvents->AddRef();

    // Get the enumerator for the audio endpoint devices
    // on this system.
    _hrStatus = CoCreateInstance(__uuidof(MMDeviceEnumerator),
                                 NULL, CLSCTX_INPROC_SERVER,
                                 __uuidof(IMMDeviceEnumerator),
                                 (void**)&pEnumerator);
    EXIT_ON_ERROR(_hrStatus)

    // Get the audio endpoint device with the specified data-flow
    // direction (eRender or eCapture) and device role.
    _hrStatus = pEnumerator->GetDefaultAudioEndpoint(flow, role,
                                                     &pDevice);
    EXIT_ON_ERROR(_hrStatus)

    // Get the session manager for the endpoint device.
    _hrStatus = pDevice->Activate(__uuidof(IAudioSessionManager),
                                  CLSCTX_INPROC_SERVER, NULL,
                                  (void**)&_pManager);
    EXIT_ON_ERROR(_hrStatus)

    // Get the control interface for the process-specific audio
    // session with session GUID = GUID_NULL. This is the session
    // that an audio stream for a DirectSound, DirectShow, waveOut,
    // or PlaySound application stream belongs to by default.
    _hrStatus = _pManager->GetAudioSessionControl(NULL, 0, &_pControl);
    EXIT_ON_ERROR(_hrStatus)

    _hrStatus = _pControl->RegisterAudioSessionNotification(_pAudioEvents);
    EXIT_ON_ERROR(_hrStatus)

Exit:
    SAFE_RELEASE(pEnumerator)
    SAFE_RELEASE(pDevice)
}

// Destructor
AudioVolumeEvents::~AudioVolumeEvents()
{
    if (_pControl != NULL)
    {
        _pControl->UnregisterAudioSessionNotification(_pAudioEvents);
    }
    SAFE_RELEASE(_pManager)
    SAFE_RELEASE(_pControl)
    SAFE_RELEASE(_pAudioEvents)
};

前のコード例では、AudioVolumeEvents という名前のクラスを実装しています。 プログラムの初期化時、オーディオ アプリケーションは AudioVolumeEvents オブジェクトを作成することによりオーディオ イベント通知を有効にします。 このクラスのコンストラクターは、次の 3 つの入力パラメーターを使用します。

コンストラクターは、フローとロールの値を入力パラメーターとして IMMDeviceEnumerator::GetDefaultAudioEndpoint メソッドに提供します。 このメソッドは、指定されたデータ フロー方向とデバイス ロールを持つオーディオ エンドポイント デバイスをカプセル化する IMMDevice オブジェクトを作成します。

アプリケーションは、pAudioEvents によりポイントされるオブジェクトを実装します。 (実装は前のコード例では示されていません。IAudioSessionEvents インターフェイスを実装するコード例については、「オーディオ セッション イベント」をご覧ください)。このインターフェイスの各メソッドは、特定の種類のオーディオ イベントの通知を受け取ります。 アプリケーションが特定のイベントの種類に関与しない場合、そのイベントの種類のメソッドは何もせずに S_OK を返す必要があります。

IAudioSessionEvents::OnSimpleVolumeChanged メソッドは、ボリューム変更の通知を受け取ります。 通常、このメソッドはアプリケーションのボリューム スライダーを更新します。

前のコード例では、AudioVolumeEvents クラスのコンストラクターは、セッション GUID 値 GUID_NULL によって識別されるプロセス固有のオーディオ セッションにおける通知を登録します。 既定では、DirectSound、DirectShow、waveOutXxx 関数などのレガシ オーディオ API は、ストリームをこのセッションに割り当てます。 ただし、DirectSound または DirectShow アプリケーションでは、オプションとして既定の動作をオーバーライドし、そのストリームをプロセス間セッションまたは GUID_NULL 以外の GUID 値で識別されるセッションに割り当てることができます。 (現在のところ、waveOutXxx アプリケーションが同様の方法で既定の動作をオーバーライドするためのメカニズムは用意されていません)。この動作を持つ DirectShow アプリケーションのコード例については、「DirectShow アプリケーションのデバイス ロール」をご覧ください。 このようなアプリケーションに対応するため、前のコード例のコンストラクターを変更して、2 つの追加の入力パラメーター (監視対象のセッションがプロセス間セッションとプロセス固有のセッションのどちらであるかを示すフラグ) を受け入れることができます。 これらのパラメーターをコンストラクターにおける IAudioSessionManager::GetAudioSessionControl メソッドの呼び出しに渡します。

コンストラクターが IAudioSessionControl::RegisterAudioSessionNotification メソッドを呼び出して通知を登録した後、IAudioSessionControl または IAudioSessionManager インターフェイスが存在する限り、アプリケーションは引き続き通知を受信します。 前のコード例の AudioVolumeEvents オブジェクトは、デストラクターが呼び出されるまでこれらのインターフェイスへの参照を保持します。 この動作により、AudioVolumeEvents オブジェクトの有効期間中、アプリケーションは引き続き通知を受け取ります。

DirectSound またはレガシ Windows マルチメディア アプリケーションでは、デバイス ロールに基づいてオーディオ デバイスを暗黙的に選択する代わりに、フレンドリ名によって識別される使用可能なデバイスの一覧からデバイスをユーザーが明示的に選択できることがあります。 この動作をサポートするには、前のコード例を変更し、選択したデバイスのオーディオ イベント通知を生成する必要があります。 2 つの変更が必要です。 まず、(コード例の flow パラメーターと role パラメーターの代わりに) 入力パラメーターとしてエンドポイント ID 文字列を受け入れるようコンストラクター定義を変更します。 この文字列は、選択した DirectSound またはレガシ波形デバイスに対応するオーディオ エンドポイント デバイスを識別します。 次に、IMMDeviceEnumerator::GetDefaultAudioEndpoint メソッドの呼び出しを、IMMDeviceEnumerator::GetDevice メソッドの呼び出しに置き換えます。 GetDevice 呼び出しは、エンドポイント ID 文字列を入力パラメーターとして受け取り、文字列によって識別されるエンドポイント デバイスのインスタンスを作成します。

DirectSound デバイスまたはレガシ波形デバイスのエンドポイント ID 文字列を取得する方法は、次のとおりです。

まず、デバイスの列挙時、DirectSound は列挙された各デバイスのエンドポイント ID 文字列を提供します。 デバイスの列挙を開始するため、アプリケーションはコールバック関数ポインターを入力パラメーター として DirectSoundCreate または DirectSoundCaptureCreate 関数に渡します。 コールバック関数の定義は、次のとおりです。

BOOL DSEnumCallback(
  LPGUID  lpGuid,
  LPCSTR  lpcstrDescription,
  LPCSTR  lpcstrModule,
  LPVOID  lpContext
);

Windows Vista では、lpcstrModule パラメーターはエンドポイント ID 文字列をポイントします。 (Windows Server 2003、Windows XP、Windows 2000 を含む以前のバージョンの Windows では、lpcstrModule パラメーターはデバイスのドライバー モジュールの名前をポイントします)。lpcstrDescription パラメーターは、デバイスのフレンドリ名を含む文字列をポイントします。 DirectSound デバイス列挙について詳しくは、Windows SDK のドキュメントをご覧ください。

次に、レガシ波形デバイスのエンドポイント ID 文字列を取得するには、waveOutMessage または waveInMessage 関数を使用して、波形デバイス ドライバーに DRV_QUERYFUNCTIONINSTANCEID メッセージを送信します。 このメッセージの使用方法を示すコード例については、「レガシ Windows マルチメディア アプリケーションのデバイス ロール」をご覧ください。

レガシ オーディオ API との相互運用性