次の方法で共有


Exclusive-Mode ストリーム

前に説明したように、アプリケーションが排他モードでストリームを開いた場合、アプリケーションはストリームを再生または記録するオーディオ エンドポイント デバイスを排他的に使用します。 これに対し、複数のアプリケーションは、デバイスで共有モード ストリームを開くことで、オーディオ エンドポイント デバイスを共有できます。

オーディオ デバイスへの排他モード アクセスは、重要なシステム サウンドをブロックしたり、他のアプリケーションとの相互運用性を防いだり、ユーザー エクスペリエンスを低下させる可能性があります。 これらの問題を軽減するために、排他モード ストリームを使用するアプリケーションは、通常、アプリケーションがフォアグラウンド プロセスではない場合、またはアクティブにストリーミングしていない場合に、オーディオ デバイスの制御を放棄します。

ストリーム待機時間は、アプリケーションのエンドポイント バッファーをオーディオ エンドポイント デバイスに接続するデータ パスに固有の遅延です。 レンダリング ストリームの場合、待機時間は、アプリケーションがエンドポイント バッファーにサンプルを書き込む時間から、サンプルがスピーカーを介して聞こえるまでの最大遅延です。 キャプチャ ストリームの場合、待機時間は、サウンドがマイクに入った時間から、アプリケーションがそのサウンドのサンプルをエンドポイント バッファーから読み取ることができる時間までの最大遅延です。

排他モード ストリームを使用するアプリケーションは、多くの場合、オーディオ エンドポイント デバイスとエンドポイント バッファーにアクセスするアプリケーション スレッド間のデータ パスで待機時間が短い必要があるため、これを行います。 通常、これらのスレッドは比較的高い優先度で実行され、オーディオ ハードウェアによって連続する処理パスを分離する定期的な間隔に近い、または同じ間隔で実行されるようにスケジュールします。 各パスの間に、オーディオ ハードウェアはエンドポイント バッファー内の新しいデータを処理します。

最小のストリーム待機時間を実現するには、アプリケーションで特殊なオーディオ ハードウェアと、軽く読み込まれるコンピューター システムの両方が必要になる場合があります。 タイミング制限を超えてオーディオ ハードウェアを駆動したり、競合する優先度の高いタスクでシステムを読み込んだりすると、低遅延のオーディオ ストリームで障害が発生する可能性があります。 たとえば、レンダリング ストリームの場合、オーディオ ハードウェアがバッファーを読み取る前にアプリケーションがエンドポイント バッファーへの書き込みに失敗した場合や、バッファーの再生がスケジュールされる前にハードウェアがバッファーの読み取りに失敗した場合に、障害が発生する可能性があります。 通常、さまざまなオーディオ ハードウェアおよび幅広いシステムで実行することを目的としたアプリケーションでは、すべてのターゲット環境で障害が発生しないように十分なタイミング要件を緩和する必要があります。

Windows Vista には、待機時間の短いオーディオ ストリームを必要とするアプリケーションをサポートするいくつかの機能があります。 User-Mode オーディオ コンポーネントで説明したように、タイム クリティカルな操作を実行するアプリケーションでは、マルチ メディア クラス スケジューラ サービス (MMCSS) 関数を呼び出して、CPU リソースを低優先度のアプリケーションに拒否することなくスレッドの優先度を高めることができます。 さらに、IAudioClient::Initialize メソッドは、アプリケーションのバッファー サービス スレッドがオーディオ デバイスから新しいバッファーが使用可能になったときに実行をスケジュールできるようにするAUDCLNT_STREAMFLAGS_EVENTCALLBACK フラグをサポートしています。 これらの機能を使用することで、アプリケーション スレッドは、実行されるタイミングに関する不確実性を減らし、待機時間の短いオーディオ ストリームで障害が発生するリスクを軽減できます。

古いオーディオ アダプターのドライバーは、WaveCyclic または WavePci デバイス ドライバー インターフェイス (DDI) を使用する可能性が高くなりますが、新しいオーディオ アダプターのドライバーは WaveRT DDI をサポートする可能性が高くなります。 排他モード アプリケーションの場合、WaveRT ドライバーは WaveCyclic ドライバーまたは WavePci ドライバーよりも優れたパフォーマンスを提供できますが、WaveRT ドライバーには追加のハードウェア機能が必要です。 これらの機能には、ハードウェア バッファーをアプリケーションと直接共有する機能が含まれます。 直接共有を使用すると、排他モード アプリケーションとオーディオ ハードウェアの間でデータを転送するために、システムの介入は必要ありません。 これに対し、WaveCyclic ドライバーと WavePci ドライバーは、古くて能力の低いオーディオ アダプターに適しています。 これらのアダプターは、アプリケーション バッファーとハードウェア バッファーの間でデータ ブロック (システム I/O 要求パケットまたは IRP に接続) を転送するためにシステム ソフトウェアに依存します。 さらに、USB オーディオ デバイスは、アプリケーション バッファーとハードウェア バッファーの間でデータを転送するシステム ソフトウェアに依存します。 データ転送のためにシステムに依存するオーディオ デバイスに接続する排他モード アプリケーションのパフォーマンスを向上させるために、WASAPI は、アプリケーションとハードウェアの間でデータを転送するシステム スレッドの優先順位を自動的に高めます。 WASAPI は MMCSS を使用してスレッドの優先度を高めます。 Windows Vista では、システム スレッドが PCM 形式でデバイス期間が 10 ミリ秒未満の排他的モードオーディオ再生ストリームのデータ転送を管理する場合、WASAPI は MMCSS タスク名 "Pro Audio" をスレッドに割り当てます。 ストリームのデバイス期間が 10 ミリ秒以上の場合、WASAPI は MMCSS タスク名 "Audio" をスレッドに割り当てます。 WaveCyclic、WavePci、WaveRT DDI の詳細については、Windows DDK のドキュメントを参照してください。 適切なデバイス期間の選択については、「IAudioClient::GetDevicePeriodを参照してください。

セッション ボリューム制御で説明されているように、WASAPI は、ISimpleAudioVolume、IChannelAudioVolume、および IAudioStreamVolumeインターフェイスを提供し、共有モードオーディオ ストリームのボリューム レベルを制御します。 ただし、これらのインターフェイスのコントロールは、排他モード ストリームには影響しません。 代わりに、排他モード ストリームを管理するアプリケーションでは、通常、EndpointVolume APIIAudioEndpointVolume インターフェイスを使用して、これらのストリームのボリューム レベルを制御します。 このインターフェイスの詳細については、「エンドポイント ボリューム コントロールの 」を参照してください。

システム内の再生デバイスとキャプチャ デバイスごとに、ユーザーはデバイスを排他モードで使用できるかどうかを制御できます。 ユーザーがデバイスの排他モードの使用を無効にした場合、デバイスを使用して共有モード ストリームのみを再生または記録できます。

ユーザーがデバイスの排他モードの使用を有効にした場合、ユーザーは、排他モードでデバイスを使用するアプリケーションによる要求が、現在デバイスを介して共有モード ストリームを再生または記録している可能性があるアプリケーションによるデバイスの使用を優先するかどうかを制御することもできます。 プリエンプションが有効になっている場合、デバイスが現在使用されていない場合、またはデバイスが共有モードで使用されている場合、アプリケーションによるデバイスの排他制御の要求は成功しますが、別のアプリケーションが既にデバイスを排他的に制御している場合、要求は失敗します。 プリエンプションが無効になっている場合、デバイスが現在使用されていない場合、アプリケーションによるデバイスの排他制御の要求は成功しますが、デバイスが既に共有モードまたは排他モードで使用されている場合、要求は失敗します。

Windows Vista では、オーディオ エンドポイント デバイスの既定の設定は次のとおりです。

  • デバイスを使用して、排他モードストリームを再生または記録できます。
  • デバイスを使用して排他モード ストリームの再生または記録を行う要求は、デバイスを介して現在再生または記録されている共有モード ストリームを優先します。

再生または記録デバイスの排他モード設定を変更するには

  1. タスク バーの右側にある通知領域のスピーカー アイコンを右クリックし、[再生デバイス] または [録音デバイスの ] を選択します。 (別の方法として、コマンド プロンプト ウィンドウから Windows マルチメディア コントロール パネル Mmsys.cplを実行します。詳細については、「DEVICE_STATE_XXX 定数の解説」を参照してください。
  2. サウンド ウィンドウが表示されたら、再生 または 録音 選択します。 次に、デバイス名の一覧でエントリを選択し、[プロパティ] クリックします。
  3. プロパティ ウィンドウが表示されたら、[詳細 ] をクリックします。
  4. アプリケーションが排他モードでデバイスを使用できるようにするには、[アプリケーションがこのデバイスを排他的に制御することを許可する] ラベルが付チェック ボックスをオンにします。 デバイスの排他モードの使用を無効にするには、チェック ボックスをオフにします。
  5. デバイスの排他モードの使用が有効になっている場合、デバイスが現在再生中の場合、または共有モード ストリームを記録している場合に、デバイスの排他制御の要求が成功するかどうかを指定できます。 排他モード アプリケーションを共有モード アプリケーションよりも優先するには、[排他モード アプリケーションの優先度を のチェック ボックスをオンにします。 共有モード アプリケーションよりも排他モード アプリケーションの優先度を拒否するには、チェック ボックスをオフにします。

次のコード例は、排他モードで使用するように構成されたオーディオ レンダリング デバイスで、待機時間の短いオーディオ ストリームを再生する方法を示しています。

//-----------------------------------------------------------
// Play an exclusive-mode stream on the default audio
// rendering device. The PlayExclusiveStream function uses
// event-driven buffering and MMCSS to play the stream at
// the minimum latency supported by the device.
//-----------------------------------------------------------

// REFERENCE_TIME time units per second and per millisecond
#define REFTIMES_PER_SEC  10000000
#define REFTIMES_PER_MILLISEC  10000

#define EXIT_ON_ERROR(hres)  \
              if (FAILED(hres)) { goto Exit; }
#define SAFE_RELEASE(punk)  \
              if ((punk) != NULL)  \
                { (punk)->Release(); (punk) = NULL; }

const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
const IID IID_IAudioClient = __uuidof(IAudioClient);
const IID IID_IAudioRenderClient = __uuidof(IAudioRenderClient);

HRESULT PlayExclusiveStream(MyAudioSource *pMySource)
{
    HRESULT hr;
    REFERENCE_TIME hnsRequestedDuration = 0;
    IMMDeviceEnumerator *pEnumerator = NULL;
    IMMDevice *pDevice = NULL;
    IAudioClient *pAudioClient = NULL;
    IAudioRenderClient *pRenderClient = NULL;
    WAVEFORMATEX *pwfx = NULL;
    HANDLE hEvent = NULL;
    HANDLE hTask = NULL;
    UINT32 bufferFrameCount;
    BYTE *pData;
    DWORD flags = 0;
    DWORD taskIndex = 0;
    
    hr = CoCreateInstance(
           CLSID_MMDeviceEnumerator, NULL,
           CLSCTX_ALL, IID_IMMDeviceEnumerator,
           (void**)&pEnumerator);
    EXIT_ON_ERROR(hr)

    hr = pEnumerator->GetDefaultAudioEndpoint(
                        eRender, eConsole, &pDevice);
    EXIT_ON_ERROR(hr)

    hr = pDevice->Activate(
                    IID_IAudioClient, CLSCTX_ALL,
                    NULL, (void**)&pAudioClient);
    EXIT_ON_ERROR(hr)

    // Call a helper function to negotiate with the audio
    // device for an exclusive-mode stream format.
    hr = GetStreamFormat(pAudioClient, &pwfx);
    EXIT_ON_ERROR(hr)

    // Initialize the stream to play at the minimum latency.
    hr = pAudioClient->GetDevicePeriod(NULL, &hnsRequestedDuration);
    EXIT_ON_ERROR(hr)

    hr = pAudioClient->Initialize(
                         AUDCLNT_SHAREMODE_EXCLUSIVE,
                         AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
                         hnsRequestedDuration,
                         hnsRequestedDuration,
                         pwfx,
                         NULL);
    if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) {
        // Align the buffer if needed, see IAudioClient::Initialize() documentation
        UINT32 nFrames = 0;
        hr = pAudioClient->GetBufferSize(&nFrames);
        EXIT_ON_ERROR(hr)
        hnsRequestedDuration = (REFERENCE_TIME)((double)REFTIMES_PER_SEC / pwfx->nSamplesPerSec * nFrames + 0.5);
        hr = pAudioClient->Initialize(
            AUDCLNT_SHAREMODE_EXCLUSIVE,
            AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
            hnsRequestedDuration,
            hnsRequestedDuration,
            pwfx,
            NULL);
    }
    EXIT_ON_ERROR(hr)

    // Tell the audio source which format to use.
    hr = pMySource->SetFormat(pwfx);
    EXIT_ON_ERROR(hr)

    // Create an event handle and register it for
    // buffer-event notifications.
    hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    if (hEvent == NULL)
    {
        hr = E_FAIL;
        goto Exit;
    }

    hr = pAudioClient->SetEventHandle(hEvent);
    EXIT_ON_ERROR(hr);

    // Get the actual size of the two allocated buffers.
    hr = pAudioClient->GetBufferSize(&bufferFrameCount);
    EXIT_ON_ERROR(hr)

    hr = pAudioClient->GetService(
                         IID_IAudioRenderClient,
                         (void**)&pRenderClient);
    EXIT_ON_ERROR(hr)

    // To reduce latency, load the first buffer with data
    // from the audio source before starting the stream.
    hr = pRenderClient->GetBuffer(bufferFrameCount, &pData);
    EXIT_ON_ERROR(hr)

    hr = pMySource->LoadData(bufferFrameCount, pData, &flags);
    EXIT_ON_ERROR(hr)

    hr = pRenderClient->ReleaseBuffer(bufferFrameCount, flags);
    EXIT_ON_ERROR(hr)

    // Ask MMCSS to temporarily boost the thread priority
    // to reduce glitches while the low-latency stream plays.
    hTask = AvSetMmThreadCharacteristics(TEXT("Pro Audio"), &taskIndex);
    if (hTask == NULL)
    {
        hr = E_FAIL;
        EXIT_ON_ERROR(hr)
    }

    hr = pAudioClient->Start();  // Start playing.
    EXIT_ON_ERROR(hr)

    // Each loop fills one of the two buffers.
    while (flags != AUDCLNT_BUFFERFLAGS_SILENT)
    {
        // Wait for next buffer event to be signaled.
        DWORD retval = WaitForSingleObject(hEvent, 2000);
        if (retval != WAIT_OBJECT_0)
        {
            // Event handle timed out after a 2-second wait.
            pAudioClient->Stop();
            hr = ERROR_TIMEOUT;
            goto Exit;
        }

        // Grab the next empty buffer from the audio device.
        hr = pRenderClient->GetBuffer(bufferFrameCount, &pData);
        EXIT_ON_ERROR(hr)

        // Load the buffer with data from the audio source.
        hr = pMySource->LoadData(bufferFrameCount, pData, &flags);
        EXIT_ON_ERROR(hr)

        hr = pRenderClient->ReleaseBuffer(bufferFrameCount, flags);
        EXIT_ON_ERROR(hr)
    }

    // Wait for the last buffer to play before stopping.
    Sleep((DWORD)(hnsRequestedDuration/REFTIMES_PER_MILLISEC));

    hr = pAudioClient->Stop();  // Stop playing.
    EXIT_ON_ERROR(hr)

Exit:
    if (hEvent != NULL)
    {
        CloseHandle(hEvent);
    }
    if (hTask != NULL)
    {
        AvRevertMmThreadCharacteristics(hTask);
    }
    CoTaskMemFree(pwfx);
    SAFE_RELEASE(pEnumerator)
    SAFE_RELEASE(pDevice)
    SAFE_RELEASE(pAudioClient)
    SAFE_RELEASE(pRenderClient)

    return hr;
}

前のコード例では、PlayExclusiveStream 関数は、レンダリング ストリームの再生中にエンドポイント バッファーを処理するアプリケーション スレッドで実行されます。 この関数は、クライアント定義クラス MyAudioSource に属するオブジェクトへのポインターである 1 つのパラメーター pMySource を受け取ります。 このクラスには、コード例で呼び出される 2 つのメンバー関数 LoadData と SetFormat があります。 MyAudioSource については、「ストリーム のレンダリングに関するページを参照してください。

PlayExclusiveStream 関数は、既定のレンダリング デバイスとネゴシエートするヘルパー関数 GetStreamFormat を呼び出して、アプリケーションで使用するのに適した排他モードストリーム形式をデバイスがサポートしているかどうかを判断します。 GetStreamFormat 関数のコードは、コード例には表示されません。これは、実装の詳細がアプリケーションの要件に完全に依存するためです。 ただし、GetStreamFormat 関数の操作は簡単に記述できます。IAudioClient::IsFormatSupported メソッドを 1 回以上呼び出して、デバイスが適切な形式をサポートしているかどうかを判断します。 アプリケーションの要件によって、GetStreamFormat が IsFormatSupported メソッドに表示される形式と、その形式を提示する順序が決まります。 IsFormatSupported の詳細については、「デバイス形式の」を参照してください。

GetStreamFormat 呼び出しの後、PlayExclusiveStream 関数は IAudioClient::GetDevicePeriod メソッドを呼び出して、オーディオ ハードウェアでサポートされている最小デバイス期間を取得します。 次に、この関数は IAudioClient::Initialize メソッドを呼び出して、最小期間と等しいバッファー期間を要求します。 呼び出しが成功した場合、Initialize メソッドは 2 つのエンドポイント バッファーを割り当てます。各バッファーは、期間が最小期間に等しくなります。 その後、オーディオ ストリームの実行が開始されると、アプリケーションとオーディオ ハードウェアは"ping-pong" の方法で 2 つのバッファーを共有します。つまり、アプリケーションが 1 つのバッファーに書き込む間、ハードウェアはもう一方のバッファーから読み取ります。

ストリームを開始する前に、PlayExclusiveStream 関数は次の処理を行います。

  • バッファーを埋める準備ができたときに通知を受け取るイベント ハンドルを作成して登録します。
  • ストリームの実行が開始されてから最初のサウンドが聞こえるまでの遅延を減らすために、オーディオ ソースのデータを最初のバッファーに入力します。
  • AvSetMmThreadCharacteristics 関数を呼び出して、MMCSS が PlayExclusiveStream を実行するスレッドの優先度を上げることを要求します。 (ストリームの実行が停止すると、AvRevertMmThreadCharacteristics 関数呼び出しによって元のスレッドの優先度が復元されます)。

AvRevertMmThreadCharacteristics AvRevertMmThreadCharacteristics の詳細については、Windows SDK のドキュメントを参照してください。

ストリームの実行中に、前のコード例の ループ中のの各反復処理によって、1 つのエンドポイント バッファーが埋まります。 反復処理の間、WaitForSingleObject 関数呼び出しは、イベント ハンドルが通知されるまで待機します。 ハンドルが通知されると、ループ本体は次の処理を行います。

  1. IAudioRenderClient::GetBuffer メソッドを呼び出して、次のバッファーを取得します。
  2. バッファーを塗りつぶします。
  3. IAudioRenderClient::ReleaseBuffer メソッドを呼び出してバッファーを解放します。

WaitForSingleObject の詳細については、Windows SDK のドキュメントを参照してください。

オーディオ アダプターが WaveRT ドライバーによって制御されている場合、イベント ハンドルのシグナリングは、オーディオ ハードウェアからの DMA 転送通知に関連付けられます。 USB オーディオ デバイスの場合、または WaveCyclic ドライバーまたは WavePci ドライバーによって制御されるオーディオ デバイスの場合、イベント ハンドルのシグナル通知は、アプリケーション バッファーからハードウェア バッファーにデータを転送する IRP の完了に関連付けられます。

前のコード例では、オーディオ ハードウェアとコンピューター システムをパフォーマンスの制限にプッシュします。 まず、ストリームの待機時間を短縮するために、アプリケーションは、オーディオ ハードウェアがサポートする最小デバイス期間を使用するようにバッファー サービス スレッドをスケジュールします。 次に、各デバイス期間内にスレッドが確実に実行されるように、AvSetMmThreadCharacteristics 関数呼び出しによって TaskName パラメーターが "Pro Audio" に設定されます。これは、Windows Vista では、最も優先度の高い既定のタスク名です。 アプリケーションのタイミング要件が、その有用性を損なうことなく緩和される可能性があるかどうかを検討します。 たとえば、アプリケーションでは、最小よりも長い期間を使用するようにバッファー サービス スレッドをスケジュールできます。 より長い期間は、より低いスレッド優先度の使用を安全に許可する可能性があります。

Stream Management