オーディオ グラフ

この記事では、Windows.Media.Audio 名前空間の API を使ってオーディオのルーティング、ミキシング、処理のシナリオでオーディオ グラフを作成する方法について説明します。

オーディオ グラフは、相互接続されたオーディオ ノードのセットです。オーディオ データは、ここを通って流れます。

  • オーディオ入力ノードは、オーディオ入力デバイス、オーディオ ファイル、またはカスタム コードから、オーディオ データをグラフに提供します。 lat

  • オーディオ出力ノードは、グラフで処理されたオーディオの目的地です。 オーディオは、グラフからオーディオ出力デバイス、オーディオ ファイル、またはカスタム コードにルーティングできます。

  • サブミックス ノードは、1 つまたは複数のノードからのオーディオを結合して 1 つの出力にします。この出力は、グラフ内の他のノードにルーティングすることができます。

すべてのノードが作成され、ノード間の接続が設定された後、オーディオ グラフを開始すると、オーディオ データが入力ノードからサブミックス ノードを通って出力ノードまで流れます。 このモデルでは、デバイスのマイクからオーディオ ファイルへの録音、ファイルからデバイスのスピーカーへのオーディオ再生、複数ソースからのオーディオ ミキシングなどのシナリオが、すばやく簡単に実装できるようになります。

オーディオ エフェクトをオーディオ グラフに追加することで、その他のシナリオも有効になります。 オーディオ グラフ内の各ノードには、ノードを通過するオーディオに対してオーディオ処理を実行するオーディオ エフェクトを 0 個以上設定できます。 エコー、イコライザー、リミッティング、リバーブなど、いくつかの組み込みエフェクトがあり、これらはわずか数行のコードでオーディオ ノードにアタッチできます。 組み込みエフェクトとまったく同様に動作するカスタム オーディオ エフェクトを独自に作成することもできます。

注意

AudioGraph UWP サンプルは、この概要で説明するコードを実装します。 サンプルをダウンロードすると、コンテキスト内のコードを確認できます。独自のアプリの出発点として使うこともできます。

Windows ランタイム AudioGraph または XAudio2 の選択

Windows ランタイム オーディオ グラフ API で提供される機能は、COM ベースの XAudio2 API を使って実装することもできます。 XAudio2 とは異なる Windows ランタイム オーディオ グラフ フレームワークの特徴を次に示します。

Windows ランタイム オーディオ グラフ API:

  • XAudio2 よりもずっと簡単です。
  • C++ 用にサポートされていますが、C# からも使用できます。
  • オーディオ ファイル (圧縮ファイル形式など) を使うことができます。 XAudio2 はオーディオ バッファーのみで動作します。ファイル I/O 機能はありません。
  • Windows 10 では、低待機時間オーディオ パイプラインを使うことができます。
  • 既定のエンドポイント パラメーターの使用時にエンドポイントの自動切り替えをサポートします。 たとえば、ユーザーがデバイスのスピーカーからヘッドホンに切り替えると、オーディオが自動的に新しい入力にリダイレクトされます。

AudioGraph クラス

AudioGraph クラスは、グラフを構成するすべてのノードの親です。 すべての種類のオーディオ ノードのインスタンス作成に、このオブジェクトを使います。 AudioGraph クラスのインスタンスを作成するには、AudioGraphSettings オブジェクトを初期化し、グラフの構成設定を含めて、AudioGraph.CreateAsync を呼び出します。 返された CreateAudioGraphResult により、作成されたオーディオ グラフへのアクセスが可能になります。オーディオ グラフの作成に失敗すると、エラー値が返されます。

AudioGraph audioGraph;
private async Task InitAudioGraph()
{

    AudioGraphSettings settings = new AudioGraphSettings(Windows.Media.Render.AudioRenderCategory.Media);

    CreateAudioGraphResult result = await AudioGraph.CreateAsync(settings);
    if (result.Status != AudioGraphCreationStatus.Success)
    {
        ShowErrorMessage("AudioGraph creation error: " + result.Status.ToString());
    }

    audioGraph = result.Graph;

}
  • オーディオ ノードの種類はすべて、AudioGraph クラスの Create* メソッドを使用して作成されます。

  • AudioGraph.Start メソッドを呼び出すと、オーディオ グラフによってオーディオ データの処理が開始されます。 AudioGraph.Stop メソッドは、オーディオ処理を停止します。 グラフの実行中、グラフ内の各ノードは個別に開始および停止できますが、グラフが停止すると、すべてのノードが非アクティブになります。 ResetAllNodes を呼び出すと、グラフ内のすべてのノードで、現在のオーディオ バッファー内にあるすべてのデータが破棄されます。

  • グラフで、オーディオ データの新しいクォンタムの処理が開始されると、QuantumStarted イベントが発生します。 クォンタムの処理が完了すると、QuantumProcessed イベントが発生します。

  • AudioGraphSettings プロパティのうち、必須であるのは AudioRenderCategory のみです。 この値を指定することにより、システムは指定されたカテゴリについてオーディオ パイプラインを最適化します。

  • オーディオ グラフのクォンタム サイズにより、同時に処理されるサンプルの数が決定します。 既定では、既定のサンプル レートのクォンタム サイズは 10 ミリ秒ベースです。 DesiredSamplesPerQuantum プロパティを設定することでカスタムのクォンタム サイズを指定する場合は、QuantumSizeSelectionMode プロパティを ClosestToDesired に設定しないと、指定した値が無視されます。 この値を使うと、指定した値にできる限り近いクォンタム サイズがシステムによって選択されます。 実際のクォンタム サイズを確認するには、AudioGraphSamplesPerQuantum を作成後にチェックします。

  • オーディオ グラフの使用対象がファイルのみであり、オーディオ デバイスに出力する予定がない場合は、DesiredSamplesPerQuantum プロパティを設定せずに、既定のクォンタム サイズを使うことをお勧めします。

  • DesiredRenderDeviceAudioProcessing プロパティは、オーディオ グラフの出力に対してプライマリ レンダリング デバイスで実行される処理の量を決定します。 Default 設定を使うと、指定されたオーディオ レンダリング カテゴリに対してシステムが既定のオーディオ処理を使用できるようになります。 この処理により、一部のデバイス (特に、小型スピーカーが搭載されているモバイル デバイス) ではオーディオのサウンドが大幅に改善される場合があります。 Raw 設定を使うと、実行する信号処理の量を最小化してパフォーマンスを向上できることがありますが、一部のデバイスでは音質が低下する場合があります。

  • QuantumSizeSelectionModeLowestLatency に設定されていると、オーディオ グラフは DesiredRenderDeviceAudioProcessing に対して自動的に Raw を使います。

  • Windows 10、バージョン 1803 では、AudioGraphSettings.MaxPlaybackSpeedFactor プロパティを設定することで、AudioFileInputNode.PlaybackSpeedFactor プロパティ、AudioFrameInputNode.PlaybackSpeedFactor プロパティ、MediaSourceInputNode.PlaybackSpeedFactor プロパティに使用する最大値を設定できます。 オーディオ グラフが、1 より大きい再生速度係数をサポートしている場合、システムは、オーディオ データの十分なバッファーを維持するために、追加のメモリを割り当てる必要があります。 このため、MaxPlaybackSpeedFactor をアプリで必要な最小値に設定すると、アプリによるメモリ消費量が減少します。 アプリが、通常の速度でのみコンテンツを再生する場合は、MaxPlaybackSpeedFactor を 1 に設定することをお勧めします。

  • EncodingProperties は、グラフで使用されるオーディオ形式を決定します。 サポートされているのは 32 ビットの浮動小数点形式のみです。

  • PrimaryRenderDevice は、オーディオ グラフのプライマリ レンダリング デバイスを設定します。 このプロパティを設定しなかった場合は、既定のシステム デバイスが使われます。 プライマリ レンダリング デバイスは、グラフの他のノードのクォンタム サイズの計算に使われます。 システムにオーディオ レンダリング デバイスが存在しない場合、オーディオ グラフの作成は失敗します。

オーディオ グラフでは、既定のオーディオ レンダリング デバイスを使うことも、Windows.Devices.Enumeration.DeviceInformation クラスを使ってシステムで利用可能なオーディオ レンダリング デバイスの一覧を取得することもできます。これには、FindAllAsync を呼び出して、Windows.Media.Devices.MediaDevice.GetAudioRenderSelector から返されるオーディオ レンダリング デバイス セレクターを渡します。 返された DeviceInformation オブジェクトのうちいずれかをプログラムで選択するか、ユーザーがデバイスを選択できるように UI を表示して、選択されたデバイスを PrimaryRenderDevice プロパティに設定します。

Windows.Devices.Enumeration.DeviceInformationCollection devices =
 await Windows.Devices.Enumeration.DeviceInformation.FindAllAsync(Windows.Media.Devices.MediaDevice.GetAudioRenderSelector());

// Show UI to allow the user to select a device
Windows.Devices.Enumeration.DeviceInformation selectedDevice = ShowMyDeviceSelectionUI(devices);


settings.PrimaryRenderDevice = selectedDevice;

デバイス入力ノード

デバイス入力ノードは、システムに接続されているオーディオ キャプチャ デバイス (マイクなど) からオーディオを取得し、グラフに渡します。 システムの既定オーディオ キャプチャ デバイスを使う DeviceInputNode オブジェクトを作成するには、CreateDeviceInputNodeAsync を呼び出します。 AudioRenderCategory を指定すると、指定されたカテゴリのオーディオ パイプラインがシステムによって最適化されます。

AudioDeviceInputNode deviceInputNode;
private async Task CreateDeviceInputNode()
{
    // Create a device output node
    CreateAudioDeviceInputNodeResult result = await audioGraph.CreateDeviceInputNodeAsync(Windows.Media.Capture.MediaCategory.Media);

    if (result.Status != AudioDeviceNodeCreationStatus.Success)
    {
        // Cannot create device output node
        ShowErrorMessage(result.Status.ToString());
        return;
    }

    deviceInputNode = result.DeviceInputNode;
}

デバイス入力ノードとして特定のオーディオ キャプチャ デバイスを指定する場合は、Windows.Devices.Enumeration.DeviceInformation クラスを使用して、FindAllAsync を呼び出し、Windows.Media.Devices.MediaDevice.GetAudioCaptureSelector によって返されるオーディオ レンダリング デバイス セレクターを渡すことによって、システムの使用可能なオーディオ キャプチャ デバイスの一覧を取得できます。 返された DeviceInformation オブジェクトのうちいずれかをプログラムで選択するか、ユーザーがデバイスを選択できるように UI を表示して、選択されたデバイスを CreateDeviceInputNodeAsync に渡します。

Windows.Devices.Enumeration.DeviceInformationCollection devices =
 await Windows.Devices.Enumeration.DeviceInformation.FindAllAsync(Windows.Media.Devices.MediaDevice.GetAudioCaptureSelector());

// Show UI to allow the user to select a device
Windows.Devices.Enumeration.DeviceInformation selectedDevice = ShowMyDeviceSelectionUI(devices);

CreateAudioDeviceInputNodeResult result =
    await audioGraph.CreateDeviceInputNodeAsync(Windows.Media.Capture.MediaCategory.Media, audioGraph.EncodingProperties, selectedDevice);

デバイス出力ノード

デバイス出力ノードは、オーディオをグラフからスピーカーやヘッドセットなどのオーディオ レンダリング デバイスにプッシュします。 DeviceOutputNode を作成するには、CreateDeviceOutputNodeAsync を呼び出します。 出力ノードでは、オーディオ グラフの PrimaryRenderDevice が使われます。

AudioDeviceOutputNode deviceOutputNode;
private async Task CreateDeviceOutputNode()
{
    // Create a device output node
    CreateAudioDeviceOutputNodeResult result = await audioGraph.CreateDeviceOutputNodeAsync();

    if (result.Status != AudioDeviceNodeCreationStatus.Success)
    {
        // Cannot create device output node
        ShowErrorMessage(result.Status.ToString());
        return;
    }

    deviceOutputNode = result.DeviceOutputNode;
}

ファイル入力ノード

ファイル入力ノードを使うと、データをオーディオ ファイルからグラフに渡すことができます。 AudioFileInputNode を作成するには、CreateFileInputNodeAsync を呼び出します。

AudioFileInputNode fileInputNode;
private async Task CreateFileInputNode()
{
    if (audioGraph == null)
        return;

    FileOpenPicker filePicker = new FileOpenPicker();
    filePicker.SuggestedStartLocation = PickerLocationId.MusicLibrary;
    filePicker.FileTypeFilter.Add(".mp3");
    filePicker.FileTypeFilter.Add(".wav");
    filePicker.FileTypeFilter.Add(".wma");
    filePicker.FileTypeFilter.Add(".m4a");
    filePicker.ViewMode = PickerViewMode.Thumbnail;
    StorageFile file = await filePicker.PickSingleFileAsync();

    // File can be null if cancel is hit in the file picker
    if (file == null)
    {
        return;
    }
    CreateAudioFileInputNodeResult result = await audioGraph.CreateFileInputNodeAsync(file);

    if (result.Status != AudioFileNodeCreationStatus.Success)
    {
        ShowErrorMessage(result.Status.ToString());
    }

    fileInputNode = result.FileInputNode;
}
  • ファイル入力ノードでは、ファイル形式として mp3、wav、wma、m4a がサポートされています。
  • ファイル内の再生開始位置にタイム オフセットを指定するには、StartTime プロパティを設定します。 このプロパティが null の場合は、ファイルの先頭が使用されます。 ファイル内の再生終了位置にタイム オフセットを指定するには、EndTime プロパティを設定します。 このプロパティが null の場合は、ファイルの末尾が使用されます。 開始時刻の値は、終了時刻の値より小さくする必要があります。また、終了時刻の値はオーディオ ファイルの長さを超えないように設定する必要があります。オーディオ ファイルの長さを確認するには、Duration プロパティの値をチェックします。
  • オーディオ ファイル内の位置をシークするには、Seek を呼び出し、ファイル内の再生位置の移動先にタイム オフセットを指定します。 指定された値は、StartTime から EndTime の範囲内である必要があります。 ノードの現在の再生位置を取得するには、読み取り専用の Position プロパティを使います。
  • オーディオ ファイルのループ処理を有効にするには、LoopCount プロパティを設定します。 この値は、null 以外であれば、初回の再生後にファイルが再生される回数を示します。 たとえば、LoopCount を 1 に設定すると、このファイルは合計 2 回再生されます。値を 5 に設定すると、ファイルは合計 6 回再生されます。 LoopCount を null に設定すると、ファイルが無限にループされます。 ループを停止するには、値を 0 に設定します。
  • オーディオ ファイルの再生速度を調整するには、PlaybackSpeedFactor を設定します。 値 1 は、ファイルの元の速度を示します、0.5 は半分の速度、2 は 2 倍の速度を示します。

MediaSource 入力ノード

MediaSource クラスは、さまざまなソースのメディアを参照するための一般的な方法を提供し、基になるメディア形式 (ディスク上のファイル、ストリーム、アダプティブ ストリーミング ネットワーク ソースなど) に関係なく、メディア データにアクセスするための一般的なモデルを公開します。 **MediaSourceAudioInputNode ノードを使用すると、MediaSource からオーディオ グラフにオーディオ データを渡すことができます。 MediaSourceAudioInputNode を作成するには、CreateMediaSourceAudioInputNodeAsync を呼び出して、再生するコンテンツを表す MediaSource オブジェクトを渡します。 * * CreateMediaSourceAudioInputNodeResult が返され、その Status プロパティを確認すると、操作の状態を判断できます。 状態が Success の場合は、Node プロパティにアクセスして、作成された MediaSourceAudioInputNode を取得できます。 以下では、ネットワーク経由のコンテンツのストリーミングを表す AdaptiveMediaSource オブジェクトからノードを作成する例を示します。 MediaSource の操作について詳しくは、「メディア項目、プレイリスト、およびトラック」を参照してください。 インターネット経由のストリーミング メディア コンテンツについて詳しくは、「アダプティブ ストリーミング」をご覧ください。

MediaSourceAudioInputNode mediaSourceInputNode;
private async Task CreateMediaSourceInputNode(System.Uri contentUri)
{
    if (audioGraph == null)
        return;

    var adaptiveMediaSourceResult = await AdaptiveMediaSource.CreateFromUriAsync(contentUri);
    if(adaptiveMediaSourceResult.Status != AdaptiveMediaSourceCreationStatus.Success)
    {
        Debug.WriteLine("Failed to create AdaptiveMediaSource");
        return;
    }

    var mediaSource = MediaSource.CreateFromAdaptiveMediaSource(adaptiveMediaSourceResult.MediaSource);
    CreateMediaSourceAudioInputNodeResult mediaSourceAudioInputNodeResult =
        await audioGraph.CreateMediaSourceAudioInputNodeAsync(mediaSource);

    if (mediaSourceAudioInputNodeResult.Status != MediaSourceAudioInputNodeCreationStatus.Success)
    {
        switch (mediaSourceAudioInputNodeResult.Status)
        {
            case MediaSourceAudioInputNodeCreationStatus.FormatNotSupported:
                Debug.WriteLine("The MediaSource uses an unsupported format");
                break;
            case MediaSourceAudioInputNodeCreationStatus.NetworkError:
                Debug.WriteLine("The MediaSource requires a network connection and a network-related error occurred");
                break;
            case MediaSourceAudioInputNodeCreationStatus.UnknownFailure:
            default:
                Debug.WriteLine("An unknown error occurred while opening the MediaSource");
                break;
        }
        return;
    }

    mediaSourceInputNode = mediaSourceAudioInputNodeResult.Node;
}

再生が MediaSource コンテンツの最後に達したときに通知を受け取るには、MediaSourceCompleted イベントのハンドラーを登録します。

mediaSourceInputNode.MediaSourceCompleted += MediaSourceInputNode_MediaSourceCompleted;
private void MediaSourceInputNode_MediaSourceCompleted(MediaSourceAudioInputNode sender, object args)
{
    audioGraph.Stop();
}

ディスクからファイルを再生した場合は、ほとんどが正常に完了しますが、ネットワーク ソースからストリームされたメディアは、ネットワーク接続状況の変化など、オーディオ グラフの制御の及ばない問題によって再生中に失敗することがあります。 MediaSource が再生中に再生できなくなった場合、オーディオ グラフで UnrecoverableErrorOccurred イベントが発生します。 このイベントのハンドラーを使用して、オーディオ グラフを停止および破棄すると、グラフを再初期化できるようになります。

audioGraph.UnrecoverableErrorOccurred += AudioGraph_UnrecoverableErrorOccurred;
private void AudioGraph_UnrecoverableErrorOccurred(AudioGraph sender, AudioGraphUnrecoverableErrorOccurredEventArgs args)
{
    if (sender == audioGraph && args.Error != AudioGraphUnrecoverableError.None)
    {
        Debug.WriteLine("The audio graph encountered and unrecoverable error.");
        audioGraph.Stop();
        audioGraph.Dispose();
        InitAudioGraph();
    }
}

ファイル出力ノード

ファイル出力ノードを使用すると、オーディオ データをグラフからオーディオ ファイルに渡すことができます。 AudioFileOutputNode を作成するには、CreateFileOutputNodeAsync を呼び出します。

AudioFileOutputNode fileOutputNode;
private async Task CreateFileOutputNode()
{
    FileSavePicker saveFilePicker = new FileSavePicker();
    saveFilePicker.FileTypeChoices.Add("Pulse Code Modulation", new List<string>() { ".wav" });
    saveFilePicker.FileTypeChoices.Add("Windows Media Audio", new List<string>() { ".wma" });
    saveFilePicker.FileTypeChoices.Add("MPEG Audio Layer-3", new List<string>() { ".mp3" });
    saveFilePicker.SuggestedFileName = "New Audio Track";
    StorageFile file = await saveFilePicker.PickSaveFileAsync();

    // File can be null if cancel is hit in the file picker
    if (file == null)
    {
        return;
    }

    Windows.Media.MediaProperties.MediaEncodingProfile mediaEncodingProfile;
    switch (file.FileType.ToString().ToLowerInvariant())
    {
        case ".wma":
            mediaEncodingProfile = MediaEncodingProfile.CreateWma(AudioEncodingQuality.High);
            break;
        case ".mp3":
            mediaEncodingProfile = MediaEncodingProfile.CreateMp3(AudioEncodingQuality.High);
            break;
        case ".wav":
            mediaEncodingProfile = MediaEncodingProfile.CreateWav(AudioEncodingQuality.High);
            break;
        default:
            throw new ArgumentException();
    }


    // Operate node at the graph format, but save file at the specified format
    CreateAudioFileOutputNodeResult result = await audioGraph.CreateFileOutputNodeAsync(file, mediaEncodingProfile);

    if (result.Status != AudioFileNodeCreationStatus.Success)
    {
        // FileOutputNode creation failed
        ShowErrorMessage(result.Status.ToString());
        return;
    }

    fileOutputNode = result.FileOutputNode;
}
  • ファイル出力ノードでは、ファイル形式として mp3、wav、wma、m4a がサポートされています。
  • AudioFileOutputNode.FinalizeAsync を呼び出す前に、AudioFileOutputNode.Stop を呼び出して、ノードの処理を停止する必要があります。そうしないと例外がスローされます。

オーディオ フレーム入力ノード

オーディオ フレーム入力ノードでは、独自のコードで生成したオーディオ データをオーディオ グラフにプッシュすることができます。 これにより、カスタムのソフトウェア シンセサイザーを作成するなどのシナリオが可能になります。 AudioFrameInputNode を作成するには、CreateFrameInputNode を呼び出します。

AudioFrameInputNode frameInputNode;
private void CreateFrameInputNode()
{
    // Create the FrameInputNode at the same format as the graph, except explicitly set mono.
    AudioEncodingProperties nodeEncodingProperties = audioGraph.EncodingProperties;
    nodeEncodingProperties.ChannelCount = 1;
    frameInputNode = audioGraph.CreateFrameInputNode(nodeEncodingProperties);

    // Initialize the Frame Input Node in the stopped state
    frameInputNode.Stop();

    // Hook up an event handler so we can start generating samples when needed
    // This event is triggered when the node is required to provide data
    frameInputNode.QuantumStarted += node_QuantumStarted;
}

オーディオ グラフでオーディオ データの次のクォンタムの処理を開始する準備ができると、FrameInputNode.QuantumStarted イベントが発生します。 カスタム生成したオーディオ データは、このイベントに対するハンドラー内で指定します。

private void node_QuantumStarted(AudioFrameInputNode sender, FrameInputNodeQuantumStartedEventArgs args)
{
    // GenerateAudioData can provide PCM audio data by directly synthesizing it or reading from a file.
    // Need to know how many samples are required. In this case, the node is running at the same rate as the rest of the graph
    // For minimum latency, only provide the required amount of samples. Extra samples will introduce additional latency.
    uint numSamplesNeeded = (uint)args.RequiredSamples;

    if (numSamplesNeeded != 0)
    {
        AudioFrame audioData = GenerateAudioData(numSamplesNeeded);
        frameInputNode.AddFrame(audioData);
    }
}
  • QuantumStarted イベント ハンドラーに渡された FrameInputNodeQuantumStartedEventArgs オブジェクトには RequiredSamples プロパティがあります。このプロパティは、クォンタムを処理するためにオーディオ グラフが必要とするサンプル数を示します。
  • オーディオ データを設定した AudioFrame オブジェクトをグラフに渡すには、AudioFrameInputNode.AddFrame を呼び出します。
  • Windows 10、バージョン 1803 では、オーディオ データで MediaFrameReader を使用するための新しい API セットが導入されました。 これらの API を使用すると、メディア フレーム ソースから AudioFrame オブジェクトを取得し、それを AddFrame メソッドを使用して FrameInputNode に渡すことができます。 詳しくは、「MediaFrameReader を使ったオーディオ フレームの処理」をご覧ください。
  • GenerateAudioData ヘルパー メソッドの実装例を下に示します。

AudioFrame にオーディオ データを設定するには、オーディオ フレームの基になるメモリ バッファーにアクセスできる必要があります。 これには、該当する名前空間に以下のコードを追加して、COM インターフェイス IMemoryBufferByteAccess を初期化する必要があります。

[ComImport]
[Guid("5B0D3235-4DBA-4D44-865E-8F1D0E4FD04D")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
unsafe interface IMemoryBufferByteAccess
{
    void GetBuffer(out byte* buffer, out uint capacity);
}

次のコードは、AudioFrame を作成し、オーディオ データを設定する GenerateAudioData ヘルパー メソッドの実装例を示しています。

private double audioWaveTheta = 0;

unsafe private AudioFrame GenerateAudioData(uint samples)
{
    // Buffer size is (number of samples) * (size of each sample)
    // We choose to generate single channel (mono) audio. For multi-channel, multiply by number of channels
    uint bufferSize = samples * sizeof(float);
    AudioFrame frame = new Windows.Media.AudioFrame(bufferSize);

    using (AudioBuffer buffer = frame.LockBuffer(AudioBufferAccessMode.Write))
    using (IMemoryBufferReference reference = buffer.CreateReference())
    {
        byte* dataInBytes;
        uint capacityInBytes;
        float* dataInFloat;

        // Get the buffer from the AudioFrame
        ((IMemoryBufferByteAccess)reference).GetBuffer(out dataInBytes, out capacityInBytes);

        // Cast to float since the data we are generating is float
        dataInFloat = (float*)dataInBytes;

        float freq = 1000; // choosing to generate frequency of 1kHz
        float amplitude = 0.3f;
        int sampleRate = (int)audioGraph.EncodingProperties.SampleRate;
        double sampleIncrement = (freq * (Math.PI * 2)) / sampleRate;

        // Generate a 1kHz sine wave and populate the values in the memory buffer
        for (int i = 0; i < samples; i++)
        {
            double sinValue = amplitude * Math.Sin(audioWaveTheta);
            dataInFloat[i] = (float)sinValue;
            audioWaveTheta += sampleIncrement;
        }
    }

    return frame;
}
  • このメソッドは、Windows ランタイム型よりも低いレベルの RAW バッファーにアクセスするため、unsafe キーワードを使って宣言する必要があります。 また、Microsoft Visual Studio でアンセーフ コードのコンパイルを許可するようにプロジェクトを構成する必要があります。プロジェクトの [プロパティ] ページを開き、[ビルド] プロパティ ページをクリックして、[アンセーフ コードの許可] チェック ボックスをオンにしてください。
  • Windows.Media 名前空間で AudioFrame の新しいインスタンスを初期化するには、必要なバッファー サイズをコンストラクターに渡します。 バッファー サイズとは、サンプル数に各サンプルのサイズを掛けた値です。
  • オーディオ フレームの AudioBuffer を取得するには、LockBuffer を呼び出します。
  • オーディオ バッファーから IMemoryBufferByteAccess COM インターフェイスのインスタンスを取得するには、CreateReference を呼び出します。
  • 生のオーディオ バッファー データへのポインターを取得するには、IMemoryBufferByteAccess.GetBuffer を呼び出してオーディオ データのサンプル データ型にキャストします。
  • オーディオ グラフへの提出用に、バッファーにデータを設定して AudioFrame を返します。

オーディオ フレーム出力ノード

オーディオ フレーム出力ノードでは、独自に作成したカスタム コードを使い、オーディオ グラフからオーディオ データ出力を受信し、処理することができます。 サンプル シナリオでは、オーディオ出力に対して信号分析を実行します。 AudioFrameOutputNode を作成するには、CreateFrameOutputNode を呼び出します。

AudioFrameOutputNode frameOutputNode;
private void CreateFrameOutputNode()
{
    frameOutputNode = audioGraph.CreateFrameOutputNode();
    audioGraph.QuantumStarted += AudioGraph_QuantumStarted;
}

オーディオ グラフでオーディオ データのクォンタムの処理が開始すると、AudioGraph.QuantumStarted イベントが発生します。 オーディオ データには、このイベントのハンドラー内からアクセスすることができます。

注意

オーディオ フレームを一定間隔で、オーディオ グラフと同期させて取得する場合は、同期 QuantumStarted イベント ハンドラー内から AudioFrameOutputNode.GetFrame を呼び出します。 QuantumProcessed イベントは、オーディオ エンジンがオーディオ処理を完了した後、非同期的に発生するため、一定間隔でない可能性があります。 したがって、オーディオ フレーム データの同期処理に QuantumProcessed イベントを使用することはできません。

private void AudioGraph_QuantumStarted(AudioGraph sender, object args)
{
    AudioFrame frame = frameOutputNode.GetFrame();
    ProcessFrameOutput(frame);

}
  • オーディオ データを設定した AudioFrame オブジェクトをグラフから取得するには、GetFrame を呼び出します。
  • ProcessFrameOutput ヘルパー メソッドの実装例を下に示します。
unsafe private void ProcessFrameOutput(AudioFrame frame)
{
    using (AudioBuffer buffer = frame.LockBuffer(AudioBufferAccessMode.Write))
    using (IMemoryBufferReference reference = buffer.CreateReference())
    {
        byte* dataInBytes;
        uint capacityInBytes;
        float* dataInFloat;

        // Get the buffer from the AudioFrame
        ((IMemoryBufferByteAccess)reference).GetBuffer(out dataInBytes, out capacityInBytes);

        dataInFloat = (float*)dataInBytes;
    }
}
  • 上に示したオーディオ フレーム入力ノードの例と同様、基になっているオーディオ バッファーにアクセスするために、IMemoryBufferByteAccess COM インターフェイスを宣言して、アンセーフ コードが許可されるようにプロジェクトを構成する必要があります。
  • オーディオ フレームの AudioBuffer を取得するには、LockBuffer を呼び出します。
  • オーディオ バッファーから IMemoryBufferByteAccess COM インターフェイスのインスタンスを取得するには、CreateReference を呼び出します。
  • 生のオーディオ バッファー データへのポインターを取得するには、IMemoryBufferByteAccess.GetBuffer を呼び出してオーディオ データのサンプル データ型にキャストします。

ノード接続とサブミックス ノード

すべての種類の入力ノードには AddOutgoingConnection メソッドがあります。このメソッドでは、そのノードで生成されたオーディオをメソッドに渡されたノードにルーティングします。 次の例では、AudioFileInputNodeAudioDeviceOutputNode に接続します。これは、デバイスのスピーカーでオーディオ ファイルを再生するための単純な設定です。

fileInputNode.AddOutgoingConnection(deviceOutputNode);

入力ノードから別ノードへは、複数の接続を作成できます。 次の例では、AudioFileInputNode から AudioFileOutputNode への接続を追加しています。 ここで、オーディオ ファイルからのオーディオがデバイスのスピーカーで再生され、オーディオ ファイルにも出力されます。

fileInputNode.AddOutgoingConnection(fileOutputNode);

出力ノードでも、他のノードから複数の接続を受け取ることができます。 次の例では、AudioDeviceInputNode から AudioDeviceOutput ノードへの接続が作成されています。 この出力ノードには、ファイル入力ノードとデバイス入力ノードからの接続があるため、出力には両方のソースからのオーディオのミックスが含まれます。 AddOutgoingConnection には、接続を通過する信号のゲイン値を指定するためのオーバーロードが用意されています。

deviceInputNode.AddOutgoingConnection(deviceOutputNode, .5);

出力ノードでは複数ノードからの接続を許容できますが、ミックスを出力に渡す前に、1 つ以上のノードからの信号の中間ミックスを作成することも検討してください。 たとえば、グラフ内のオーディオ信号のサブセットに対し、レベルの設定やエフェクトの適用を行うことができます。 そのためには、AudioSubmixNode を使用します。 サブミックス ノードには、1 つ以上の入力ノードまたは他のサブミックス ノードから接続することができます。 次の例では、AudioGraph.CreateSubmixNode で新しいサブミックス ノードを作成しています。 次に、ファイル入力ノードとフレーム出力ノードからサブミックス ノードへの接続が追加されています。 最後に、サブミックス ノードがファイル出力ノードに接続されています。

private void CreateSubmixNode()
{
    AudioSubmixNode submixNode = audioGraph.CreateSubmixNode();
    fileInputNode.AddOutgoingConnection(submixNode);
    frameInputNode.AddOutgoingConnection(submixNode);
    submixNode.AddOutgoingConnection(fileOutputNode);
}

オーディオ グラフ ノードの開始と停止

AudioGraph.Start が呼び出されると、オーディオ グラフはオーディオ データの処理を開始します。 すべてのノードの種類に、個々のノードでデータの処理を開始または停止する Start メソッドと Stop メソッドが用意されています。 AudioGraph.Stop が呼び出されると、個々のノードの状態に関係なく、すべてのノードでのすべてのオーディオ処理が停止しますが、オーディオ グラフが停止している間も各ノードの状態が設定されることは考えられます。 たとえば、グラフの停止中に各ノードで Stop を呼び出してから AudioGraph.Start を呼び出した場合、個々のノードは停止状態のままです。

すべてのノードの種類には ConsumeInput プロパティが用意されています。これが false に設定されると、ノードではオーディオ処理を続行できますが、他のノードから入力されているオーディオ データの使用が停止されます。

すべてのノードの種類には Reset メソッドが用意されています。このメソッドが呼び出されると、ノードのバッファーにある現在のオーディオ データがすべて破棄されます。

オーディオ エフェクトの追加

オーディオ グラフ API を使うと、グラフ内のすべての種類のノードに、オーディオ エフェクトを追加することができます。 個々の出力ノード、入力ノード、およびサブミックス ノードに追加できるオーディオ エフェクトの数に制限はありません (ハードウェア性能によってのみ制限されます)。次の例では、組み込みのエコー エフェクトをサブミックス ノードに追加する方法を示しています。

EchoEffectDefinition echoEffect = new EchoEffectDefinition(audioGraph);
echoEffect.Delay = 1000.0;
echoEffect.Feedback = .2;
echoEffect.WetDryMix = .5;

submixNode.EffectDefinitions.Add(echoEffect);
  • すべてのオーディオ エフェクトには、IAudioEffectDefinition が実装されています。 すべてのノードには、そのノードに適用されたエフェクトの一覧を表す EffectDefinitions プロパティが用意されています。 エフェクトを追加するには、エフェクトの定義オブジェクトをこの一覧に追加します。
  • Windows.Media.Audio 名前空間には、複数のエフェクト定義クラスがあります。 これには以下が含まれます。
  • IAudioEffectDefinition を実装する独自のオーディオ エフェクトを作成し、オーディオ グラフ内の任意のノードに適用することができます。
  • すべてのノードの種類には、DisableEffectsByDefinition メソッドが用意されています。このメソッドは、ノードの EffectDefinitions リストに含まれる、指定の定義を使って追加されたすべてのエフェクトを無効にします。 EnableEffectsByDefinition は、指定の定義を持つエフェクトを有効にします。

空間オーディオ

Windows 10 バージョン 1607 以降、AudioGraph は空間オーディオをサポートしています。これにより、任意の入力またはサブミックス ノードからのオーディオが出力される 3D 空間内の場所を指定することができます。 また、オーディオ出力の形状や方向、ノードのオーディオのドップラー偏移に使用される速度を指定することや、距離に応じたオーディオの減衰を記述する減衰モデルを定義することもできます。

エミッターを作成するには、最初に、エミッターから出力されるサウンドの形状を作成します。これには、円すい状または無指向性を指定できます。 AudioNodeEmitterShape クラスは、これらの各形状を作成するための静的メソッドを提供します。 次に、減衰モデルを作成します。 これは、リスナーからの距離が増加するにつれて、エミッターからのオーディオの音量をどのように減少させるかを定義します。 CreateNatural メソッドは、距離二乗減衰モデルを使用してサウンドの自然減衰をエミュレートする減衰モデルを作成します。 最後に、AudioNodeEmitterSettings オブジェクトを作成します。 現時点では、このオブジェクトは、エミッターのオーディオの速度ベースのドップラー減衰を有効または無効にするためにのみ使用されます。 AudioNodeEmitter コンストラクターを呼び出して、作成した初期化オブジェクトを渡します。 既定では、エミッターは原点に置かれますが、エミッターの位置は Position プロパティで設定できます。

注意

オーディオ ノード エミッターは、48 kHz のサンプル レートとモノラルでフォーマットされているオーディオのみを処理できます。 ステレオ オーディオや別のサンプル レートのオーディオを使用しようとすると、例外が発生します。

目的のノードの種類の作成メソッドをオーバーロードした作成メソッドを使用してオーディオ ノードを作成する場合、オーディオ ノードにエミッターを割り当てます。 この例では、CreateFileInputNodeAsync を使用して、指定されたファイルとノードに関連付ける AudioNodeEmitter オブジェクトから、ファイル入力ノードを作成しています。

var emitterShape = AudioNodeEmitterShape.CreateOmnidirectional();
var decayModel = AudioNodeEmitterDecayModel.CreateNatural(.1, 1, 10, 100);
var settings = AudioNodeEmitterSettings.None;

var emitter = new AudioNodeEmitter(emitterShape, decayModel, settings);
emitter.Position = new System.Numerics.Vector3(10, 0, 5);

CreateAudioFileInputNodeResult result = await audioGraph.CreateFileInputNodeAsync(file, emitter);

if (result.Status != AudioFileNodeCreationStatus.Success)
{
    ShowErrorMessage(result.Status.ToString());
}

fileInputNode = result.FileInputNode;

グラフからのオーディオをユーザーに対して出力する AudioDeviceOutputNode には、Listener プロパティでアクセスできるリスナー オブジェクトがあります。このオブジェクトは、3D 空間でのユーザーの位置、向き、速度を表します。 グラフ内のすべてのエミッタの位置は、リスナー オブジェクトの位置と向きを基準にしています。 既定では、リスナーは原点 (0,0,0) に位置し、Z 軸に沿って前向きですが、リスナーの位置と向きは、Position プロパティと Orientation プロパティを使って設定できます。

deviceOutputNode.Listener.Position = new System.Numerics.Vector3(100, 0, 0);
deviceOutputNode.Listener.Orientation = System.Numerics.Quaternion.CreateFromYawPitchRoll(0, (float)Math.PI, 0);

実行時にエミッターの位置、速度、方向を更新することで、3D 空間でのオーディオ ソースの動きをシミュレートすることができます。

var emitter = fileInputNode.Emitter;
emitter.Position = newObjectPosition;
emitter.DopplerVelocity = newObjectPosition - oldObjectPosition;

また、実行時にリスナー オブジェクトの位置、速度、向きを更新することで、3D 空間でのユーザーの動きをシミュレートすることができます。

deviceOutputNode.Listener.Position = newUserPosition;

既定では、空間オーディオは、リスナーを基準とするオーディオの形状、速度、位置に基づいてオーディオを減衰する、Microsoft の頭部伝達関数 (HRTF) アルゴリズムを使用して計算されます。 SpatialAudioModel プロパティを FoldDown に設定することにより、単純なステレオ ミックス メソッドを使用して、正確性は下がるが、必要な CPU とメモリ リソースが少ない空間オーディオをシミュレートすることができます。

こちらもご覧ください