オーディオ グラフ

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

オーディオ グラフは、オーディオ データが流れる相互接続されたオーディオ ノードのセットです。

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

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

  • サブミックス ノード は、1 つ以上のノードからオーディオを取得し、それらを 1 つの出力に結合して、グラフ内の他のノードにルーティングできます。

すべてのノードが作成され、それらの間の接続が設定されたら、オーディオ グラフを開始するだけで、オーディオ データは入力ノードから任意のサブミックス ノードを経由して出力ノードに流れます。 このモデルでは、デバイスのマイクからオーディオ ファイルへの録音、ファイルからデバイスのスピーカーへのオーディオの再生、複数のソースからのオーディオのミキシングなどのシナリオを迅速かつ簡単に実装できます。

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

Windows ランタイム AudioGraph または XAudio2 を選ぶ

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

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

  • XAudio2 よりも大幅に使いやすいです。
  • C++ でサポートされているだけでなく、C# から使用できます。
  • 圧縮ファイル形式を含むオーディオ ファイルを直接使用できます。 XAudio2 はオーディオ バッファーでのみ動作し、ファイル I/O 機能は提供しません。
  • Windowsで待機時間の短いオーディオ パイプラインを使用できます。
  • 既定のエンドポイント パラメーターが使用されている場合は、エンドポイントの自動切り替えをサポートします。 たとえば、ユーザーがデバイスのスピーカーからヘッドセットに切り替えた場合、オーディオは自動的に新しい出力にリダイレクトされます。

AudioGraph クラス

AudioGraph クラスは、グラフを構成するすべてのノードの親です。 このオブジェクトを使用して、すべてのオーディオ ノード タイプのインスタンスを作成します。 グラフの構成設定を含む AudioGraphAudioGraphSettings オブジェクトを初期化して、クラスのインスタンスを作成します。 次に、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);
        return;
    }

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

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

  • QuantumStarted イベントは、グラフがオーディオ データの新しい量子の処理を開始するときに発生します。 QuantumProcessed イベントは、量子の処理が完了したときに発生します。

  • 必須の AudioGraphSettings プロパティは AudioRenderCategory のみです。 この値を指定すると、システムは指定されたカテゴリのオーディオ パイプラインを最適化できます。

  • オーディオ グラフの量子サイズによって、一度に処理されるサンプルの数が決まります。 既定では、量子サイズは既定のサンプル レートに基づいて 10 ミリ秒です。 DesiredSamplesPerQuantum プロパティを設定してカスタム量子サイズを指定する場合は、QuantumSizeSelectionMode プロパティを ClosestToDesired に設定するか、指定された値を無視する必要があります。 この値を使用すると、指定した量子サイズにできるだけ近い量子サイズが選択されます。 実際の量子サイズを決定するには、作成後に AudioGraphSamplesPerQuantum を確認します。

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

  • DesiredRenderDeviceAudioProcessing プロパティは、オーディオ グラフの出力に対してプライマリ レンダー デバイスが実行する処理の量を決定します。 既定の設定を使用すると、システムは、指定されたオーディオ レンダリング カテゴリの既定のオーディオ処理を使用できます。 この処理により、一部のデバイス、特に小型スピーカーを搭載したモバイル デバイスの音声を大幅に向上させることができます。 Raw 設定では、実行される信号処理の量を最小限に抑えることでパフォーマンスを向上させることができますが、一部のデバイスでは音質が低下する可能性があります。

  • QuantumSizeSelectionModeLowestLatency に設定されている場合、オーディオ グラフは DesiredRenderDeviceAudioProcessingRaw を自動的に使用します。

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

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

  • PrimaryRenderDevice は、オーディオ グラフのプライマリ レンダー デバイスを設定します。 これを設定しない場合は、既定のシステム デバイスが使用されます。 プライマリ レンダー デバイスは、グラフ内の他のノードの量子サイズを計算するために使用されます。 システムにオーディオ レンダリング デバイスが存在しない場合、オーディオ グラフの作成は失敗します。

オーディオ グラフで既定のオーディオ レンダー デバイスを使用することも、Windows.Devices.Enumeration.DeviceInformation クラスを使用して、Windows.Media.Devices.MediaDevice.GetAudioRenderSelector によって返されるオーディオ レンダー デバイス セレクターを FindAllAsync に渡すことで、システムで使用可能なオーディオ レンダー デバイスの一覧を取得することもできます。 返された 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;

デバイス入力ノード

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

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;
    WinRT.Interop.InitializeWithWindow.Initialize(filePicker, _hwnd);
    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());
        return;
    }

    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 はファイルの元の速度を示し、.5 は半分の速度、2 は 2 倍の速度を示します。

MediaSource 入力ノード

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

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

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

    MediaSource 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 イベントを発生させます。 このイベントのハンドラーを使用して、オーディオ グラフを停止して破棄し、グラフを再初期化できます。

if (audioGraph != null)
{
    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 System.Collections.Generic.List<string>() { ".wav" });
    saveFilePicker.FileTypeChoices.Add("Windows Media Audio", new System.Collections.Generic.List<string>() { ".wma" });
    saveFilePicker.FileTypeChoices.Add("MPEG Audio Layer-3", new System.Collections.Generic.List<string>() { ".mp3" });
    saveFilePicker.SuggestedFileName = "New Audio Track";
    WinRT.Interop.InitializeWithWindow.Initialize(saveFilePicker, _hwnd);
    StorageFile file = await saveFilePicker.PickSaveFileAsync();

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

    MediaEncodingProfile mediaEncodingProfile;
    switch (file.FileType.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);
    }
}
  • FrameInputNodeQuantumStartedEventArgs オブジェクトは QuantumStarted イベント ハンドラーに渡され、このオブジェクトが備える RequiredSamples プロパティは、処理対象の quantum を満たすためにオーディオ グラフが必要とするサンプル数を示します。
  • AudioFrameInputNode.AddFrame を呼び出して、オーディオ データが格納された AudioFrame オブジェクトをグラフに渡します。
  • MediaFrameReader をオーディオ データと共に使用して、メディア フレーム ソースから AudioFrame オブジェクトを取得できます。これは、AddFrame メソッドを使用して FrameInputNode に渡すことができます。
  • GenerateAudioData ヘルパー メソッドの実装例を次に示します。

オーディオ データを AudioFrame に設定するには、オーディオ フレームの基になるメモリ バッファーにアクセスする必要があります。 これを行うには、次に示すように IMemoryBufferByteAccess COM インターフェイスを初期化します。

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

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

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 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 ランタイム型の基になる生バッファーにアクセスするため、unsafe キーワードを使用して宣言する必要があります。 また、プロジェクトの Properties ページを開き、Build プロパティ ページをクリックし、Allow Unsafe Code チェックボックスを選択して、安全でないコードのコンパイルを許可するようにMicrosoft Visual Studioでプロジェクトを構成する必要があります。
  • Windows.Media 名前空間の AudioFrame の新しいインスタンスを初期化するには、目的のバッファー サイズをコンストラクターに渡します。 バッファー サイズは、サンプルの数に各サンプルのサイズを乗算したものです。
  • オーディオ フレームの AudioBuffer を取得するには、LockBuffer を呼び出します。
  • CreateReference を呼び出して、オーディオ バッファーから IMemoryBufferByteAccess COM インターフェイスのインスタンスを取得します。
  • IMemoryBufferByteAccess.GetBuffer を呼び出して生のオーディオ バッファー データへのポインターを取得し、オーディオ データのサンプル データ型にキャストします。
  • バッファーにデータを入力し、オーディオ グラフに送信するために AudioFrame を返します。

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

オーディオ フレーム出力ノードを使用すると、作成したカスタム コードを使用して、オーディオ グラフからのオーディオ データ出力を受信および処理できます。 このシナリオの例として、オーディオ出力で信号分析を実行します。 AudioFrameOutputNode を作成するには、CreateFrameOutputNode を呼び出します。

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

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

Note

オーディオ グラフと同期された通常の周期でオーディオ フレームを取得する場合は、同期 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 を呼び出します。
  • CreateReference を呼び出して、オーディオ バッファーから IMemoryBufferByteAccess COM インターフェイスのインスタンスを取得します。
  • IMemoryBufferByteAccess.GetBuffer を呼び出して生のオーディオ バッファー データへのポインターを取得し、オーディオ データのサンプル データ型にキャストします。

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

すべての入力ノードの種類は、ノードによって生成されたオーディオをメソッドに渡されるノードにルーティングする AddOutgoingConnection メソッドを公開します。 次の例ではAudioFileInputNode をデバイスのスピーカーでオーディオ ファイルを再生するための簡単なセットアップである AudioDeviceOutputNode に接続します。

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を実装する独自のオーディオ効果を作成し、オーディオ グラフ内の任意のノードに適用できます。
  • すべてのノードの種類は、指定した定義を使用して追加されたノードの EffectDefinitions リスト内のすべての効果を無効にする DisableEffectsByDefinition メソッドを公開します。 EnableEffectsByDefinition を使用すると、指定した定義で効果が有効になります。

空間オーディオ

AudioGraph では、空間オーディオがサポートされています。これにより、入力ノードまたはサブミックス ノードからのオーディオの出力元となる 3D 空間内の場所を指定できます。 また、オーディオが出力される図形と方向、ノードのオーディオをドップラーシフトするために使用される速度を指定したり、オーディオを距離で減衰させる方法を記述する減衰モデルを定義したりすることもできます。

エミッタを作成するには、まず、サウンドがエミッタから投影されるシェイプを作成できます。これは、円錐または全方向にすることができます。 AudioNodeEmitterShape クラスは、これらの各図形を作成するための静的メソッドを提供します。 次に、減衰モデルを作成します。 これにより、リスナーからの距離が長くなるにつれて、エミッターからのオーディオの音量がどのように減少するかを定義します。 CreateNatural メソッドは、距離 2 乗フォールオフ モデルを使用して、サウンドの自然な減衰をエミュレートする減衰モデルを作成します。 最後に、AudioNodeEmitterSettings オブジェクトを作成します。 現在、このオブジェクトは、エミッタのオーディオの速度ベースのドップラー減衰を有効および無効にするためにのみ使用されます。 AudioNodeEmitter コンストラクターを呼び出し、先ほど作成した初期化オブジェクトを渡します。 デフォルトでは、エミッタは原点に配置されますが、 Position プロパティを使用してエミッタの位置を設定できます。

Note

オーディオ ノード エミッターは、サンプル レートが 48kHz のモノラル形式のオーディオのみを処理できます。 異なるサンプル レートでステレオ オーディオまたはオーディオを使用しようとすると、例外が発生します。

目的のノードの種類に対応するオーバーロードされた作成メソッドを使用して作成時に、エミッターをオーディオ ノードに割り当てます。 この例では、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 Vector3(10, 0, 5);

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

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

fileInputNode = result.FileInputNode;

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

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

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

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

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

deviceOutputNode.Listener.Position = newUserPosition;

既定では、空間オーディオはMicrosoftのヘッド相対転送関数 (HRTF) アルゴリズムを使用して計算され、リスナーに対する形状、速度、位置に基づいてオーディオを減衰します。 SpatialAudioModel プロパティを FoldDown に設定すると、単純なステレオ ミックス方式を使用して、精度は低いが、必要な CPU とメモリのリソースが少ない空間オーディオをシミュレートできます。

こちらも参照ください