Поделиться через


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

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

Аудиограмма — это набор взаимосвязанных звуковых узлов, через которые потоки звуковых данных.

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

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

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

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

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

Примечание.

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

Выбор среда выполнения Windows AudioGraph или 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, необходимо также задать свойство QuantumSizeSelectionMode значение "БлижайшийToDesired", или указанное значение игнорируется. Если это значение используется, система выберет квантовый размер как можно ближе к заданному. Чтобы определить фактический квантовый размер, проверьте примерыPerQuantum аудиографа после его создания.

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

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

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

  • Начиная с Windows 10 версии 1803, можно задать свойство AudioGraphSettings.MaxPlaybackSpeedFactor, чтобы задать максимальное значение, используемое для свойств AudioFileInputNode.PlaySpeedFactor, AudioFrameInputNode.PlaySpeedFactor и MediaSourceInputNode.PlaySpeedFactor. Если звуковой граф поддерживает коэффициент скорости воспроизведения, превышающий 1, система должна выделить дополнительную память для поддержания достаточного буфера звуковых данных. По этой причине установка MaxPlaybackSpeedFactor на наименьшее значение, необходимое для приложения, уменьшит потребление памяти приложения. Если ваше приложение будет воспроизводить содержимое только в обычной скорости, рекомендуется задать MaxPlaybackSpeedFactor значение 1.

  • КодировкаProperties определяет формат звука, используемый графом. Поддерживаются только 32-разрядные форматы с плавающей запятой.

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

Вы можете позволить звуковому графу использовать устройство отрисовки звука по умолчанию или использовать класс Windows.Devices.Enumeration.DeviceInformation, чтобы получить список доступных звуковых устройств отрисовки системы, вызвав FindAllAsync и передав селектор звукового отрисовки устройства, возвращаемый Windows.Media.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.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 .
  • Выполните поиск позиции в звуковом файле, вызвав поиск и указав смещение времени в файл, в который следует переместить положение воспроизведения. Указанное значение должно находиться в диапазоне StartTime и EndTime. Получение текущей позиции воспроизведения узла с помощью свойства Position только для чтения.
  • Включите циклирование звукового файла, задав свойство LoopCount . Если значение не равно null, это значение указывает количество раз, когда файл будет воспроизводиться после первоначального воспроизведения. Таким образом, например, установка LoopCount значение 1 приведет к тому, что файл будет воспроизводиться в 2 раза в общей сложности, и при установке этого параметра значение 5 приведет к тому, что файл будет воспроизводиться в 6 раз в общей сложности. Если параметр LoopCount имеет значение NULL, файл будет цикличен на неопределенный срок. Чтобы остановить цикл, задайте значение 0.
  • Настройте скорость воспроизведения звукового файла, задав значение AudioSpeedFactor. Значение 1 указывает исходную скорость файла, .5 — половина скорости, а 2 — двойная скорость.

Входной узел MediaSource

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

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

Входной узел звукового кадра

Входной узел звукового кадра позволяет отправлять звуковые данные, создаваемые в собственном коде, в звуковой граф. Это позволяет сценариям, таким как создание пользовательского синтезатора программного обеспечения. Создайте 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, заполненный звуковыми данными в граф.
  • В Windows 10 версии 1803 появилась новая набор API-интерфейсов для использования MediaFrameReader с звуковыми данными. Эти 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, который создает аудиофрейм и заполняет его звуковыми данными.

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

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

Узел вывода аудиокадров позволяет получать и обрабатывать выходные данные звука из звукового графа с помощью создаваемого пользовательского кода. Пример сценария для этого — анализ сигнала для выходных данных звука. Создайте 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 звуковой граф начинает обработку звуковых данных. Каждый тип узла предоставляет методы запуска и остановки , которые приводят к тому, что отдельный узел запускает или останавливает обработку данных. При вызове AudioGraph.Stop все звуковые данные во всех узлах остановлены независимо от состояния отдельных узлов, но состояние каждого узла можно задать во время остановки звукового графа. Например, можно вызвать stop на отдельном узле во время остановки графа, а затем вызвать AudioGraph.Start, а отдельный узел останется в остановленном состоянии.

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

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

Добавление звуковых эффектов

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 поддерживает пространственный звук, который позволяет указать расположение в трехмерном пространстве, из которого создается звук из любого узла ввода или подмиксов. Вы также можете указать фигуру и направление, в котором создается звук, скорость, которая будет использоваться для перемещения звука Doppler узла, и определить модель распада, описывающую, как звук затеняется расстоянием.

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

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, чтобы использовать простой метод стереосмешивания для имитации пространственного звука, который является менее точным, но требует меньше ресурсов ЦП и памяти.

См. также