Звуковые графы

В этой статье показано, как использовать API в пространстве имен Windows.Media.Audio для создания звуковых графов для сценариев маршрутизации, микширования и обработки звука.

Звуковой граф — это набор связанных друг с другом звуковых узлов, по которым передаются звуковые данные.

  • Узлы аудиовхода поставляют звуковые данные из входных аудиоустройств, звуковых файлов или пользовательского кода в граф. lat

  • Звуковые данные, обработанные с помощью графа, доставляются на узлы аудиовыхода. Звук можно направить из графа на выходные аудиоустройства, а также в звуковые файлы и пользовательский код.

  • Узлы субмикширования принимают звуковые данные из одного или нескольких узлов и объединяют их в единый вывод, который можно передать в другие узлы графа.

После создания всех узлов и настройки соединений между ними вам просто нужно запустить звуковой граф, после чего звуковые данные перемещаются из узлов ввода через узлы субмикширования на узлы вывода. Эта модель позволяет быстро и легко реализовать ряд сценариев, например записать звуковой файл с помощью микрофона устройства, воспроизвести звук из файла через динамик устройства или смикшировать аудиоданные из нескольких источников.

При добавлении к звуковому графу звуковых эффектов реализуются дополнительные сценарии. Каждый узел звукового графа можно при необходимости заполнить звуковыми эффектами, которые отвечают за обработку звука, проходящего через узел. Написав всего несколько строк кода, вы можете подключить к звуковому узлу несколько встроенных эффектов, таких как эхо, эквалайзер, ограничение и реверберация. Кроме того, вы можете создать собственные пользовательские звуковые эффекты, которые работают точно так же, как и встроенные эффекты.

Примечание

В примере UWP AudioGraph реализован код, описанный в этом обзоре. Скачайте этот пример, чтобы просмотреть код в контексте или использовать пример как отправную точку для настройки вашего приложения.

Выбор между звуковым графом среды выполнения Windows и интерфейсом XAudio2

API звуковых графов среды выполнения Windows предоставляют возможности, которые также можно реализовать с помощью API XAudio2 на основе модели COM. Ниже перечислены возможности инфраструктуры звукового графа среды выполнения Windows, отличные от интерфейса XAudio2.

API звуковых графов среды выполнения Windows:

  • значительно проще в использовании, чем XAudio2;
  • помимо C++, могут использоваться и в C#;
  • могут использовать звуковые файлы, в том числе в сжатых форматах, напрямую. Интерфейс XAudio2 работает только в звуковых буферах и не обеспечивает никаких возможностей ввода-вывода файлов;
  • могут использовать звуковой конвейер с низкой задержкой в 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;

}
  • Все типы звуковых узлов создаются с помощью методов Create* класса AudioGraph .

  • Метод AudioGraph.Start позволяет начать обработку звуковых данных с помощью звукового графа. Метод AudioGraph.Stop позволяет остановить звуковую обработку. Каждый узел графа можно запустить и остановить независимо от того, выполняется ли граф, однако при остановке графа все узлы отключаются. Метод ResetAllNodes позволяет удалить из всех узлов графа любые данные, находящиеся в их звуковых буферах.

  • Событие QuantumStarted возникает, когда граф приступает к обработке нового кванта времени звуковых данных. Событие QuantumProcessed возникает, когда обработка кванта времени завершается.

  • Единственное необходимое свойство AudioGraphSettings — это AudioRenderCategory. Если задать это значение, система оптимизирует звуковой конвейер для указанной категории.

  • Размер кванта времени звукового графа определяет количество примеров, которые обрабатываются одновременно. По умолчанию размер кванта времени составляет 10 мс на основе частоты дискретизации по умолчанию. Если указать пользовательский размер кванта времени, установив свойство DesiredSamplesPerQuantum, необходимо также задать значение ClosestToDesired для свойства QuantumSizeSelectionMode. В противном случае указанное значение будет проигнорировано. Если используется это значение, система выберет размер кванта времени, максимально соответствующий указанному вами. Чтобы определить фактический размер кванта времени, проверьте свойство SamplesPerQuantum класса AudioGraph после его создания.

  • Если вы планируете всего лишь использовать звуковой граф с файлами без вывода на звуковое устройство, мы рекомендуем использовать размер кванта времени по умолчанию, не задавая свойство DesiredSamplesPerQuantum.

  • Свойство DesiredRenderDeviceAudioProcessing определяет объем выходных данных звукового графа, обрабатываемых первичным устройством рендеринга. Параметр Default позволяет системе использовать обработку звука по умолчанию для определенной категории рендеринга звуковых данных. С помощью такой обработки вы можете значительно улучшить качество звука на некоторых устройствах, особенно мобильных устройствах с небольшими динамиками. Параметр Raw позволяет повысить производительность благодаря уменьшению количества обрабатываемых сигналов, но также может привести к ухудшению качества звука на некоторых устройствах.

  • Если для режима QuantumSizeSelectionMode задано значение LowestLatency, звуковой граф автоматически будет использовать параметр Raw для DesiredRenderDeviceAudioProcessing.

  • Начиная с 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 программным путем или отобразить пользовательский интерфейс и выбрать с его помощью устройство для задания свойства 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 программным путем или отобразить пользовательский интерфейс, с помощью которого пользователь сможет выбрать устройство для последующей передачи методу 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 или 5, файл будет воспроизведен 2 и 6 раз соответственно. Если указать для свойства LoopCount значение Null, файл будет воспроизведен неограниченное количество раз. Чтобы остановить повторное воспроизведение, задайте значение 0.
  • Регулируйте скорость воспроизведения звукового файла путем установки свойства PlaybackSpeedFactor. Значение 1 указывает на первоначальную скорость передачи файла, а значения 0,5 и 2 — на скорость, меньшую и большую в два раза соответственно.

Узел ввода MediaSource

Класс MediaSource используется для указания данных мультимедиа из других источников. Класс предоставляет общую модель для обращения к данным мультимедиа независимо от используемого медиаформата — это может быть файл на диске, потоковая передача или сетевой источник адаптивной потоковой передачи. Узел **MediaSourceAudioInputNode применяется для направления аудиоданных из в MediaSource в звуковой граф. Создайте MediaSourceAudioInputNode, вызвав метод CreateMediaSourceAudioInputNodeAsync и передав объект MediaSource, представляющий воспроизводимое содержимое. Метод возвращает результат ** CreateMediaSourceAudioInputNodeResult, с помощью которого можно выяснить состояние операции, проверив его свойство Status. Если статус — Success, то вы можете получить созданный MediaSourceAudioInputNode, обратившись к свойству Node. В следующем примере показано создание узла из объекта 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.Stop, чтобы остановить обработку узла, перед вызовом AudioFileOutputNode.FinalizeAsync. В противном случае будет создано исключение.

Узел ввода аудиокадра

Узел ввода аудиокадра позволяет отправить в звуковой граф звуковые данные, созданные с помощью собственного кода. Это делает возможными различные сценарии, такие как создание синтезатор пользовательского программного обеспечения. Создайте узел 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, которое указывает, сколько примеров необходимо заполнить звуковому графу для обработки кванта времени.
  • Вызовите метод AudioFrameInputNode.AddFrame, чтобы передать в граф объект AudioFrame, заполненный звуковыми данными.
  • Новый набор API-интерфейсов для использования MediaFrameReader со звуковыми данными появился в Windows 10 версии 1803. Эти API позволяют получить объекты AudioFrame из источника кадров мультимедиа, которые можно передать в FrameInputNode с помощью метода AddFrame. Дополнительные сведения см. в разделе Обработка аудиокадров с помощью 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);
}

Следующий код представляет собой пример реализации вспомогательного метода 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 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, его необходимо объявить с помощью ключевого слова unsafe. Необходимо также настроить проект в Microsoft Visual Studio, чтобы разрешить компиляцию небезопасного кода. Для этого откройте страницу проекта Свойства, щелкните страницу свойств Сборка и установите флажок Разрешить небезопасный код.
  • Инициализируйте новый экземпляр класса AudioFrame в пространстве имен Windows.Media, передав нужный размер буфера в конструктор. Размер буфера — это число примеров, умноженное на размер каждого примера.
  • Получите AudioBuffer аудиокадра, вызвав LockBuffer.
  • Получите экземпляр COM-интерфейса IMemoryBufferByteAccess из звукового буфера, вызвав CreateReference.
  • Получите указатель на необработанные данные звукового буфера, вызвав метод IMemoryBufferByteAccess.GetBuffer и приведите его к типу данных образца звуковых данных.
  • Заполните буфер данными и верните класс AudioFrame для отправки в звуковой граф.

Узел вывода аудиокадра

Узел вывода аудиокадра позволяет получать и обрабатывать выходные звуковые данных из звукового графа с помощью пользовательского кода, который вы создали. Пример такого сценария — анализ сигналов аудиовыхода. Создайте узел AudioFrameOutputNode, вызвав CreateFrameOutputNode.

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

Событие AudioGraph.QuantumStarted возникает, когда звуковой граф начинает обработку кванта времени звуковых данных. Вы можете получить доступ к звуковым данным в обработчике для этого события.

Примечание

Если вы хотите получать аудиокадры на регулярной основе в синхронизации со звуковым графом, вызовите AudioFrameOutputNode.GetFrame из синхронного обработчика событий QuantumStarted. Событие QuantumProcessed возникает асинхронно после того, как обработчик звука завершает аудиообработку. Это означает, что событие может возникать нерегулярно. Таким образом, событие QuantumProcessed не следует использовать для синхронизированной обработки данных аудиокадров.

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

}
  • Вызовите метод GetFrame, чтобы получить объект AudioFrame, заполненный звуковыми данными из графа.
  • Ниже приведен пример реализации вспомогательного метода 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;
    }
}
  • Как и в приведенном выше примере узла ввода аудиокадра, потребуется объявить COM-интерфейс IMemoryBufferByteAccess и настроить проект на разрешение небезопасного кода, чтобы получить доступ к базовому звуковому буферу.
  • Получите AudioBuffer аудиокадра, вызвав LockBuffer.
  • Получите экземпляр COM-интерфейса IMemoryBufferByteAccess из звукового буфера путем вызова метода CreateReference.
  • Получите указатель на необработанные данные звукового буфера, вызвав метод IMemoryBufferByteAccess.GetBuffer, и приведите его к типу примеров звуковых данных.

Соединения узлов и узлы субмикширования

Узлы ввода всех типов обеспечивают метод AddOutgoingConnection, направляющий созданные узлом звуковые данные на узел, который передается в метод. В следующем примере класс AudioFileInputNode подключается к классу AudioDeviceOutputNode, что представляет собой простой сценарий воспроизведения звукового файла на динамике устройства.

fileInputNode.AddOutgoingConnection(deviceOutputNode);

Вы можете создать несколько соединений между узлом ввода и другими узлами. В следующем примере добавляется другое соединение между классами AudioFileInputNode и AudioFileOutputNode. Теперь звук из звукового файла воспроизводится на динамике устройства, а также записывается в звуковой файл.

fileInputNode.AddOutgoingConnection(fileOutputNode);

Узлы вывода также могут получать несколько соединений с других узлов. В следующем примере создается соединение между классом AudioDeviceInputNode и узлом AudioDeviceOutput. Так как на узле вывода есть соединения от узлов ввода файла и устройства, выходные данные будут включать содержимое из обоих источников. Метод AddOutgoingConnection предоставляет перегрузку, которая позволяет задать значение усиления для сигнала, проходящего через соединение.

deviceInputNode.AddOutgoingConnection(deviceOutputNode, .5);

Хотя узлы вывода могут принимать соединения из нескольких узлов, вы можете создать промежуточный набор сигналов из одного или нескольких узлов, прежде чем передать конечный набор для вывода. Например, вы можете установить уровень или применить эффекты к подмножеству звуковых сигналов в графе. Для этого можно воспользоваться элементом AudioSubmixNode. Вы можете подключиться к узлу субмикширования из одно или нескольких узлов ввода или других узлов субмикширования. В следующем примере новый узел субмикширования создается с помощью метода 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 поддерживает пространственный звук, который позволяет указать расположение источника звука (любого узла ввода или субмикширования) в трехмерном пространстве. Можно также указать форму и направление, в котором испущен звук, скорость, которая будет использоваться для доплеровского сдвига звука узла, и определить модель затухания, которая описывает ослабление звука с увеличением расстояния.

Для создания излучателя вы можете сначала создать форму, с которой звук проецируется из излучателя — коническую или всенаправленную. Класс AudioNodeEmitterShape предоставляет статические методы создания каждой из этих форм. Далее создайте модель затухания. Она определяет, как громкость звука из излучателя снижается при увеличении расстояния от слушателя. Метод CreateNatural создает модель затухания, которая имитирует естественное ослабление звука с помощью модели ослабления звука пропорционально квадрату расстояния. Наконец, создайте объект AudioNodeEmitterSettings. Сейчас он используется только для включения и отключения допплеровского ослабления звука излучателя в зависимости от скорости. Вызовите конструктор AudioNodeEmitter, передав созданные объекты инициализации. По умолчанию излучатель помещается в начале координат, но вы можете изменить его положение с помощью свойства Position.

Примечание

Излучатели звуковых узлов могут обрабатывать только монофонический звук с частотой дискретизации 48 кГц. Попытка использовать стереофонический звук или звук с другой частотой дискретизации вызовет исключение.

Вы назначаете излучатель звуковому узлу при его создании с помощью перегруженного метода создания нужного типа узла. В этом примере для создания узла ввода из указанного файла используются метод 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. Он представляет расположение, ориентацию и скорость пользователя в трехмерном пространстве. Положение всех излучателей в графе определяется относительно положения и ориентации объекта прослушивателя. По умолчанию слушатель расположен в начале координат (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);

Вы можете изменить расположение, скорость и направление излучателей во время выполнения, чтобы имитировать движение источника звука в трехмерном пространстве.

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

Вы также можете изменить расположение, скорость и ориентацию объекта слушателя, чтобы имитировать движение пользователя в трехмерном пространстве.

deviceOutputNode.Listener.Position = newUserPosition;

По умолчанию пространственный звук рассчитывается с помощью алгоритма передаточной функции слухового аппарата человека (HRTF) Майкрософт, чтобы ослаблять звук в зависимости от формы, скорости и положения относительно слушателя. Вы можете присвоить свойству SpatialAudioModel значение FoldDown, чтобы использовать простой стереометод микширования имитации пространственного звука, который будет менее точным, но требует меньше ресурсов ЦП и памяти.

См. также раздел