Wykresy audio

W tym artykule pokazano, jak używać interfejsów API w przestrzeni nazw Windows.Media.Audio do tworzenia grafów audio na potrzeby routingu, miksowania i przetwarzania dźwięku.

Wykres dźwiękowy to zestaw połączonych węzłów audio, za pośrednictwem których przepływa dane audio.

  • Węzły wejściowe audio dostarczają dane audio do grafu z urządzeń wejściowych audio, plików audio lub z kodu niestandardowego.

  • Węzły wyjściowe audio są miejscem docelowym dźwięku przetwarzanego przez graf. Dźwięk może być kierowany z grafu do urządzeń wyjściowych audio, plików audio lub kodu niestandardowego.

  • Węzły submiksu pobierają dźwięk z jednego lub większej liczby węzłów i łączą go w jeden sygnał wyjściowy, który można kierować do innych węzłów w grafie.

Po utworzeniu wszystkich węzłów i skonfigurowaniu połączeń między nimi wystarczy uruchomić graf audio, a dane audio przepływają z węzłów wejściowych przez dowolne węzły podmiksu do węzłów wyjściowych. Ten model sprawia, że scenariusze takie jak nagrywanie z mikrofonu urządzenia do pliku audio, odtwarzanie dźwięku z pliku do głośnika urządzenia lub mieszanie dźwięku z wielu źródeł szybkie i łatwe do zaimplementowania.

Dodatkowe scenariusze stają się dostępne dzięki dodaniu efektów audio do grafu audio. Każdy węzeł w grafie audio może zawierać zero lub więcej efektów audio, które przetwarzają dźwięk przechodzący przez węzeł. Istnieje kilka wbudowanych efektów, takich jak echo, korektor, ograniczanie i odgłos, które można dołączyć do węzła audio z zaledwie kilkoma wierszami kodu. Możesz również utworzyć własne niestandardowe efekty dźwiękowe, które działają dokładnie tak samo jak wbudowane efekty.

Wybór między środowisko wykonawcze systemu Windows AudioGraph a XAudio2

Interfejsy API grafu audio środowiska środowisko wykonawcze systemu Windows udostępniają funkcje, które można również zrealizować przy użyciu opartych na modelu COM interfejsów API XAudio2 APIs. Poniżej przedstawiono funkcje platformy środowisko wykonawcze systemu Windows audio graph, które różnią się od XAudio2.

Interfejsy API grafu dźwięku środowiska środowisko wykonawcze systemu Windows:

  • Jest znacznie łatwiejsze w użyciu niż XAudio2.
  • Oprócz obsługi języka C++, można go używać z poziomu języka C#.
  • Może używać plików audio, w tym skompresowanych formatów plików, bezpośrednio. XAudio2 działa wyłącznie na buforach audio i nie zapewnia obsługi wejścia/wyjścia plików.
  • Można używać ścieżki audio o niskich opóźnieniach w systemie Windows.
  • Obsługa automatycznego przełączania punktów końcowych, gdy są używane domyślne parametry punktu końcowego. Jeśli na przykład użytkownik przełączy się z głośnika urządzenia na zestaw słuchawkowy, dźwięk zostanie automatycznie przekierowany na nowe wyjście.

klasa AudioGraph

Klasa AudioGraph jest elementem nadrzędnym wszystkich węzłów tworzących graf. Ten obiekt służy do tworzenia wystąpień wszystkich typów węzłów audio. Utwórz instancję klasy AudioGraph, inicjalizując obiekt klasy AudioGraphSettings zawierający ustawienia konfiguracji grafu, a następnie wywołując metodę AudioGraph.CreateAsync. Zwrócony CreateAudioGraphResult daje dostęp do utworzonego grafu audio lub zapewnia wartość błędu, jeśli tworzenie grafu audio zakończy się niepowodzeniem.

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;
}
  • Wszystkie typy węzłów audio są tworzone przy użyciu metod Create* klasy AudioGraph .

  • Metoda AudioGraph.Start powoduje, że grafu audio zacznie przetwarzać dane audio. Metoda AudioGraph.Stop zatrzymuje przetwarzanie dźwięku. Każdy węzeł w grafie można uruchomić i zatrzymać niezależnie podczas działania grafu, ale żadne węzły nie są aktywne po zatrzymaniu grafu. ResetAllNodes powoduje, że wszystkie węzły w grafie usuwają wszelkie dane znajdujące się aktualnie w ich buforach audio.

  • Zdarzenie QuantumStarted występuje, gdy graf rozpoczyna przetwarzanie nowego kwantu danych audio. Zdarzenie QuantumProcessed występuje po zakończeniu przetwarzania kwantowego.

  • Jedyna właściwość AudioGraphSettings wymagana jest AudioRenderCategory. Określenie tej wartości umożliwia systemowi zoptymalizowanie toru audio dla określonej kategorii.

  • Rozmiar kwantowy grafu audio określa liczbę próbek przetwarzanych jednocześnie. Domyślnie rozmiar kwantowy wynosi 10 ms na podstawie domyślnego współczynnika próbkowania. Jeśli określisz niestandardowy rozmiar kwantowy, ustawiając właściwość DesiredSamplesPerQuantum , musisz również ustawić właściwość QuantumSizeSelectionMode na Wartość ClosestToDesired lub podana wartość jest ignorowana. Jeśli ta wartość jest używana, system wybierze rozmiar kwantowy jak najbliżej określonego rozmiaru. Aby określić rzeczywisty rozmiar kwantu, sprawdź wartość SamplesPerQuantum w obiekcie AudioGraph po jego utworzeniu.

  • Jeśli planujesz używać grafu audio tylko z plikami i nie planujesz wyprowadzania danych wyjściowych do urządzenia audio, zaleca się użycie domyślnego rozmiaru kwantowego, nie ustawiając właściwości DesiredSamplesPerQuantum .

  • Właściwość DesiredRenderDeviceAudioProcessing określa ilość przetwarzania podstawowego urządzenia renderowania wykonywanego na danych wyjściowych grafu audio. Ustawienie Domyślne umożliwia systemowi używanie domyślnego przetwarzania dźwięku dla określonej kategorii renderowania audio. Takie przetwarzanie może znacząco poprawić dźwięk dźwięku na niektórych urządzeniach, szczególnie na urządzeniach przenośnych z małymi głośnikami. Ustawienie Nieprzetworzone może poprawić wydajność, minimalizując ilość wykonywanego przetwarzania sygnału, ale może spowodować niższą jakość dźwięku na niektórych urządzeniach.

  • Jeśli parametr QuantumSizeSelectionMode jest ustawiony na LowestLatency, graf audio automatycznie użyje wartości Raw dla DesiredRenderDeviceAudioProcessing.

  • Można ustawić właściwość AudioGraphSettings.MaxPlaybackSpeedFactor , aby ustawić maksymalną wartość używaną dla właściwości AudioFileInputNode.PlaybackSpeedFactor, AudioFrameInputNode.PlaybackSpeedFactor i MediaSourceInputNode.PlaybackSpeedFactor . Gdy wykres dźwiękowy obsługuje współczynnik szybkości odtwarzania większy niż 1, system musi przydzielić dodatkową pamięć, aby zachować wystarczający bufor danych dźwiękowych. Z tego powodu ustawienie parametru MaxPlaybackSpeedFactor na najniższą wartość wymaganą przez aplikację zmniejszy zużycie pamięci aplikacji. Jeśli aplikacja będzie odtwarzać zawartość tylko z normalną szybkością, zaleca się ustawienie parametru MaxPlaybackSpeedFactor na wartość 1.

  • EncodingProperties określa format audio używany przez graf. Obsługiwane są tylko 32-bitowe formaty zmiennoprzecinkowe.

  • PrimaryRenderDevice ustawia podstawowe urządzenie renderowania dla grafu audio. Jeśli nie ustawisz tego ustawienia, zostanie użyte domyślne urządzenie systemowe. Podstawowe urządzenie renderowania służy do obliczania rozmiarów kwantowych dla innych węzłów na grafie. Jeśli w systemie nie ma żadnych urządzeń renderowanych audio, tworzenie grafu audio zakończy się niepowodzeniem.

Możesz pozwolić grafowi audio używać domyślnego urządzenia renderującego dźwięk albo użyć klasy Windows.Devices.Enumeration.DeviceInformation, aby pobrać listę dostępnych w systemie urządzeń renderujących dźwięk, wywołując metodę FindAllAsync i przekazując do niej selektor urządzeń renderujących dźwięk zwrócony przez Windows.Media.Devices.MediaDevice.GetAudioRenderSelector. Możesz wybrać jeden z zwróconych obiektów DeviceInformation programowo lub pokazać interfejs użytkownika, aby umożliwić użytkownikowi wybranie urządzenia, a następnie użycie go do ustawienia właściwości 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;

Węzeł wejściowy urządzenia

Węzeł wejściowy urządzenia pobiera dźwięk do grafu z urządzenia przechwytywania dźwięku podłączonego do systemu, takiego jak mikrofon. Utwórz obiekt DeviceInputNode, który używa domyślnego urządzenia przechwytywania dźwięku systemu, wywołując CreateDeviceInputNodeAsync. Określ MediaCategory, aby umożliwić systemowi zoptymalizowanie toru audio dla określonej kategorii.

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;
}

Jeśli chcesz określić konkretne urządzenie do przechwytywania dźwięku dla węzła wejściowego urządzenia, możesz użyć klasy Windows.Devices.Enumeration.DeviceInformation, aby uzyskać listę dostępnych w systemie urządzeń do przechwytywania dźwięku, wywołując metodę FindAllAsync i przekazując selektor urządzenia do przechwytywania dźwięku zwrócony przez Windows.Media.Devices.MediaDevice.GetAudioCaptureSelector. Możesz wybrać jeden z zwróconych obiektów DeviceInformation programowo lub pokazać interfejs użytkownika, aby umożliwić użytkownikowi wybranie urządzenia, a następnie przekazanie go do polecenia 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);

Węzeł wyjściowy urządzenia

Węzeł wyjściowy urządzenia wypycha dźwięk z grafu do urządzenia renderowania audio, takiego jak głośniki lub zestaw słuchawkowy. Utwórz DeviceOutputNode wywołując CreateDeviceOutputNodeAsync. Węzeł wyjściowy używa elementu PrimaryRenderDevice grafu audio.

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;
}

Węzeł wejściowy pliku

Węzeł wejściowy pliku umożliwia przekazywanie danych z pliku audio do grafu. Utwórz AudioFileInputNode wywołując 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;
}
  • Węzły wejściowe plików obsługują następujące formaty plików: mp3, wav, wma, m4a.
  • Ustaw właściwość StartTime , aby określić przesunięcie czasu do pliku, w którym powinno rozpocząć się odtwarzanie. Jeśli ta właściwość ma wartość null, zostanie użyty początek pliku. Ustaw właściwość EndTime , aby określić przesunięcie czasu do pliku, w którym powinno zakończyć się odtwarzanie. Jeśli ta właściwość ma wartość null, zostanie użyty koniec pliku. Wartość czasu rozpoczęcia musi być niższa niż wartość czasu zakończenia, a wartość czasu zakończenia musi być mniejsza lub równa czasowi trwania pliku audio, który można określić, sprawdzając wartość właściwości Czas trwania .
  • Poszukaj pozycji w pliku audio, wywołując funkcję Wyszukiwania i określając przesunięcie czasu do pliku, do którego ma zostać przeniesiona pozycja odtwarzania. Określona wartość musi należeć do zakresu StartTime i EndTime . Pobierz bieżącą pozycję odtwarzania węzła za pomocą właściwości tylko do odczytu Position.
  • Włącz pętlę pliku audio, ustawiając właściwość LoopCount . Jeśli wartość nie ma wartości null, ta wartość wskazuje, ile razy plik będzie odtwarzany po początkowym odtwarzaniu. Na przykład ustawienie wartości LoopCount na 1 spowoduje, że plik będzie odtwarzany 2 razy w sumie, a ustawienie go na 5 spowoduje, że plik będzie odtwarzany 6 razy w sumie. Ustawienie wartości LoopCount na wartość null powoduje, że plik jest w pętli na czas nieokreślony. Aby zatrzymać pętlę, ustaw wartość 0.
  • Dostosuj szybkość odtwarzania pliku audio, ustawiając element PlaybackSpeedFactor. Wartość 1 wskazuje oryginalną prędkość pliku, .5 jest pół prędkości, a 2 jest podwójna szybkość.

Węzeł wejściowy MediaSource

Klasa MediaSource zapewnia typowy sposób odwoływania się do nośnika z różnych źródeł i uwidacznia wspólny model uzyskiwania dostępu do danych multimedialnych niezależnie od bazowego formatu multimediów, który może być plikiem na dysku, strumieniem lub adaptacyjnym źródłem sieci przesyłania strumieniowego. Węzeł MediaSourceAudioInputNode umożliwia kierowanie danych audio z usługi MediaSource do grafu audio. Utwórz obiekt MediaSourceAudioInputNode , wywołując metodę CreateMediaSourceAudioInputNodeAsync, przekazując obiekt MediaSource reprezentujący zawartość, którą chcesz odtworzyć. Zwracany jest element CreateMediaSourceAudioInputNodeResult , którego można użyć do określenia stanu operacji, sprawdzając właściwość Status . Jeśli stan to Powodzenie, możesz pobrać utworzony obiekt MediaSourceAudioInputNode , korzystając z właściwości Node . W poniższym przykładzie pokazano tworzenie węzła z obiektu AdaptiveMediaSource reprezentującego przesyłanie strumieniowe zawartości za pośrednictwem sieci.

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;
}

Aby otrzymać powiadomienie, gdy odtwarzanie dotrze do końca zawartości MediaSource, zarejestruj procedurę obsługi dla zdarzenia MediaSourceCompleted.

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

Podczas odtwarzania pliku z dysku wszystko niemal zawsze zakończy się pomyślnie, ale w przypadku multimediów przesyłanych strumieniowo ze źródła sieciowego podczas odtwarzania może wystąpić błąd z powodu zmiany połączenia sieciowego lub innych problemów pozostających poza kontrolą grafu audio. Jeśli podczas odtwarzania element MediaSource stanie się niemożliwy do odtworzenia, graf dźwiękowy zgłosi zdarzenie UnrecoverableErrorOccurred . Możesz użyć procedury obsługi tego zdarzenia, aby zatrzymać i usunąć graf audio, a następnie ponownie zainicjować graf.

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();
    }
}

Węzeł wyjściowy pliku

Węzeł wyjściowy pliku umożliwia kierowanie danych dźwiękowych z grafu do pliku audio. Utwórz AudioFileOutputNode wywołując 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;
}

Węzeł wejścia ramki audio

Węzeł wejściowy ramki audio umożliwia przesyłanie do grafu audio danych audio generowanych we własnym kodzie. Umożliwia to scenariusze, takie jak tworzenie niestandardowego syntetyzatora oprogramowania. Utwórz AudioFrameInputNode wywołując 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;
}

Zdarzenie FrameInputNode.QuantumStarted jest wywoływane , gdy wykres dźwiękowy jest gotowy do rozpoczęcia przetwarzania następnego kwantu danych audio. Do tego zdarzenia przekazujesz własne wygenerowane dane audio z poziomu procedury obsługi.

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);
    }
}
  • Obiekt FrameInputNodeQuantumStartedEventArgs przekazany do procedury obsługi zdarzenia QuantumStarted udostępnia właściwość RequiredSamples, która wskazuje, ile próbek graf audio potrzebuje do wypełnienia kwantu przeznaczonego do przetworzenia.
  • Wywołaj AudioFrameInputNode.AddFrame aby przekazać AudioFrame obiekt wypełniony danymi audio do grafu.
  • Za pomocą elementu MediaFrameReader z danymi audio można uzyskać obiekty AudioFrame ze źródła ramki multimedialnej, które można przekazać do elementu FrameInputNode przy użyciu metody AddFrame .
  • Poniżej przedstawiono przykładową implementację metody pomocnika GenerateAudioData .

Aby wypełnić AudioFrame przy użyciu danych audio, musisz uzyskać dostęp do bazowego buforu pamięci ramki audio. W tym celu zainicjuj interfejs IMemoryBufferByteAccess COM, jak pokazano poniżej.

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

Poniższy kod przedstawia przykładową implementację GenerateAudioData metody pomocniczej, która tworzy metodę pomocnika AudioFrame i wypełnia ją danymi audio.

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;
}
  • Ponieważ ta metoda uzyskuje dostęp do nieprzetworzonego buforu bazowego typów środowisko wykonawcze systemu Windows, musi zostać zadeklarowana przy użyciu słowa kluczowego unsafe. Należy również skonfigurować projekt w programie Microsoft Visual Studio, aby zezwolić na kompilowanie kodu niebezpiecznego, otwierając stronę Properties projektu, klikając stronę właściwości Build i zaznaczając pole wyboru Allow Unsafe Code.
  • Zainicjuj nowe wystąpienie AudioFrame w przestrzeni nazw Windows.Media, przekazując do konstruktora żądany rozmiar buforu. Rozmiar buforu to liczba próbek pomnożonych przez rozmiar każdej próbki.
  • Uzyskaj AudioBuffer ramki audio, wywołując LockBuffer.
  • Pobierz wystąpienie interfejsu IMemoryBufferByteAccess COM z buforu audio, wywołując polecenie CreateReference.
  • Pobierz wskaźnik do nieprzetworzonych danych buforu audio, wywołując metodę IMemoryBufferByteAccess.GetBuffer, a następnie rzutuj go na typ danych próbek audio.
  • Wypełnij bufor danymi i zwróć obiekt AudioFrame w celu przesłania do grafu audio.

Węzeł wyjściowy ramki audio

Węzeł wyjściowy ramki audio umożliwia odbieranie i przetwarzanie danych dźwiękowych z grafu audio za pomocą utworzonego kodu niestandardowego. Przykładem takiego scenariusza jest przeprowadzenie analizy sygnału na wyjściu audio. Utwórz AudioFrameOutputNode wywołując CreateFrameOutputNode.

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

Zdarzenie AudioGraph.QuantumStarted jest wywoływane, gdy graf audio rozpoczyna przetwarzanie kwantu danych dźwiękowych. Dostęp do danych dźwiękowych można uzyskać z poziomu programu obsługi dla tego zdarzenia.

Note

Jeśli chcesz pobierać ramki audio w regularnych odstępach, zsynchronizowane z grafem audio, wywołaj metodę AudioFrameOutputNode.GetFrame w synchronicznym programie obsługi zdarzenia QuantumStarted. Zdarzenie QuantumProcessed jest wywoływane asynchronicznie po zakończeniu przetwarzania dźwięku przez aparat audio, co oznacza, że jego cykl może być nieregularny. W związku z tym nie należy używać zdarzenia QuantumProcessed do zsynchronizowanego przetwarzania danych ramki audio.

private void AudioGraph_QuantumStarted(AudioGraph sender, object args)
{
    AudioFrame frame = frameOutputNode.GetFrame();
    ProcessFrameOutput(frame);
}
  • Wywołaj GetFrame, aby uzyskać obiekt AudioFrame wypełniony danymi audio z grafu.
  • Poniżej przedstawiono przykładową implementację metody pomocnika 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;
    }
}
  • Podobnie jak w powyższym przykładzie węzła wejściowego ramki audio, należy zadeklarować interfejs IMemoryBufferByteAccess COM i skonfigurować projekt, aby zezwolić na niebezpieczny kod w celu uzyskania dostępu do bazowego buforu audio.
  • Uzyskaj AudioBuffer ramki audio, wywołując LockBuffer.
  • Pobierz instancję interfejsu COM IMemoryBufferByteAccess z buforu audio, wywołując CreateReference.
  • Pobierz wskaźnik do nieprzetworzonych danych buforu audio, wywołując funkcję IMemoryBufferByteAccess.GetBuffer i oddając ją do przykładowego typu danych audio.

Połączenia węzłów i węzły submiksu

Wszystkie typy węzłów wejściowych uwidaczniają metodę AddOutgoingConnection , która kieruje dźwięk generowany przez węzeł do węzła przekazanego do metody . Poniższy przykład łączy AudioFileInputNode z AudioDeviceOutputNode który jest prostą konfiguracją odtwarzania pliku audio na głośniku urządzenia.

fileInputNode.AddOutgoingConnection(deviceOutputNode);

Możesz utworzyć więcej niż jedno połączenie z węzła wejściowego do innych węzłów. Poniższy przykład dodaje kolejne połączenie z AudioFileInputNode do AudioFileOutputNode. Teraz dźwięk z pliku audio jest odtwarzany do głośnika urządzenia i jest również zapisywany w pliku audio.

fileInputNode.AddOutgoingConnection(fileOutputNode);

Węzły wyjściowe mogą również odbierać więcej niż jedno połączenie z innych węzłów. W poniższym przykładzie jest wykonywane połączenie z AudioDeviceInputNode do węzła AudioDeviceOutput. Ponieważ węzeł wyjściowy ma połączenia z węzła wejściowego pliku i węzła wejściowego urządzenia, dane wyjściowe będą zawierać kombinację dźwięku z obu źródeł. AddOutgoingConnection ma wersję przeciążoną, która umożliwia określenie wartości wzmocnienia dla sygnału przechodzącego przez połączenie.

deviceInputNode.AddOutgoingConnection(deviceOutputNode, .5);

Mimo że węzły wyjściowe mogą przyjmować połączenia z wielu węzłów, możesz chcieć utworzyć pośredni miks sygnałów z jednego lub większej liczby węzłów przed przekazaniem tego miksu do węzła wyjściowego. Na przykład możesz ustawić poziom lub zastosować efekty do podzbioru sygnałów dźwiękowych na wykresie. W tym celu użyj AudioSubmixNode. Możesz połączyć się z węzłem podmiksu z jednego lub większej liczby węzłów wejściowych albo innych węzłów podmiksu. W poniższym przykładzie tworzony jest nowy węzeł podmiksu za pomocą AudioGraph.CreateSubmixNode. Następnie dodaje się połączenia z węzła wejściowego pliku i węzła wejścia ramki do węzła submiksu. Na koniec węzeł submiksu jest połączony z węzłem wyjścia pliku.

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

Uruchamianie i zatrzymywanie węzłów grafu audio

Gdy zostaje wywołana metoda AudioGraph.Start, graf audio rozpoczyna przetwarzanie danych audio. Każdy typ węzła udostępnia metody uruchamiania i zatrzymywania , które powodują uruchomienie lub zatrzymanie przetwarzania danych przez poszczególne węzły. Po wywołaniu elementu AudioGraph.Stop wszystkie operacje przetwarzania audio we wszystkich węzłach są zatrzymywane niezależnie od stanu poszczególnych węzłów, ale stan każdego węzła można ustawić podczas zatrzymywania grafu audio. Można na przykład wywołać metodę Stop w pojedynczym węźle, gdy graf jest zatrzymany, a następnie wywołać metodę AudioGraph.Start, a pojedynczy węzeł pozostanie w stanie zatrzymanym.

Wszystkie typy węzłów uwidaczniają właściwość ConsumeInput , która po ustawieniu wartości false umożliwia węzłowi kontynuowanie przetwarzania dźwięku, ale uniemożliwia korzystanie z danych audio wejściowych z innych węzłów.

Wszystkie typy węzłów uwidaczniają metodę Reset , która powoduje, że węzeł odrzuci wszystkie dane audio aktualnie w buforze.

Dodawanie efektów dźwiękowych

Interfejs API grafu audio umożliwia dodawanie efektów dźwiękowych do każdego typu węzła na grafie. Węzły wyjściowe, węzły wejściowe i węzły podrzędne mogą mieć nieograniczoną liczbę efektów dźwiękowych, ograniczonych tylko przez możliwości sprzętu. Poniższy przykład przedstawia dodanie wbudowanego efektu echa do węzła submix.

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

submixNode.EffectDefinitions.Add(echoEffect);
  • Wszystkie efekty dźwiękowe implementują IAudioEffectDefinition. Każdy węzeł uwidacznia właściwość EffectDefinitions reprezentującą listę efektów zastosowanych do tego węzła. Dodaj efekt, dodając do listy obiekt definicji.
  • Istnieje kilka klas definicji efektów dostępnych w Windows. Media.Audio przestrzeni nazw. Należą do nich:
  • Możesz utworzyć własne efekty dźwiękowe implementujące IAudioEffectDefinition i zastosować je do dowolnego węzła na grafie audio.
  • Każdy typ węzła uwidacznia metodę DisableEffectsByDefinition , która wyłącza wszystkie efekty na liście EffectDefinitions węzła, które zostały dodane przy użyciu określonej definicji. EnableEffectsByDefinition umożliwia efekty z określoną definicją.

Dźwięk przestrzenny

AudioGraph obsługuje dźwięk przestrzenny, który umożliwia określenie położenia w przestrzeni 3D, z którego emitowany jest dźwięk z dowolnego węzła wejściowego lub węzła sumowania. Można również określić kształt i kierunek, w którym jest emitowany dźwięk, szybkość, która będzie używana do zmiany dźwięku węzła dopplera, i zdefiniować model rozkładu, który opisuje sposób tłumienie dźwięku z odległością.

Aby utworzyć emiter, można najpierw utworzyć kształt, w którym dźwięk jest rzutowany z emitera, który może być stożkiem lub wielokierunkowym. Klasa AudioNodeEmitterShape udostępnia metody statyczne do tworzenia każdego z tych kształtów. Następnie utwórz model rozkładu. Określa to, jak głośność dźwięku z emitera zmniejsza się wraz ze wzrostem odległości od odbiornika. Metoda CreateNatural tworzy model zanikania, który odwzorowuje naturalne zanikanie dźwięku przy użyciu modelu spadku natężenia odwrotnie proporcjonalnego do kwadratu odległości. Na koniec utwórz obiekt AudioNodeEmitterSettings. Obecnie ten obiekt jest używany wyłącznie do włączania i wyłączania tłumienia dopplerowskiego dźwięku emitera zależnego od prędkości. Wywołaj konstruktor AudioNodeEmitter, przekazując obiekty inicjalizacji, które właśnie utworzono. Domyślnie emiter jest umieszczany na początku, ale można ustawić położenie emitera z właściwością Position .

Note

Emitery węzłów dźwiękowych mogą przetwarzać tylko dźwięk sformatowany w formacie mono z częstotliwością próbkowania 48kHz. Próba użycia dźwięku stereo lub dźwięku z inną częstotliwością próbkowania spowoduje wyjątek.

Przypisujesz emiter do węzła audio podczas tworzenia, używając przeciążonej metody tworzenia dla typu węzła, który chcesz utworzyć. W tym przykładzie CreateFileInputNodeAsync służy do tworzenia węzła wejściowego pliku z określonego pliku i AudioNodeEmitter obiektu, który chcesz skojarzyć z węzłem.

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;

Obiekt AudioDeviceOutputNode, który wyprowadza dźwięk z grafu do użytkownika, ma obiekt nasłuchu, dostępny za pomocą właściwości Listener, który reprezentuje położenie, orientację i prędkość użytkownika w przestrzeni 3D. Pozycje wszystkich emiterów na wykresie są określane względem położenia i orientacji obiektu słuchacza. Domyślnie odbiornik znajduje się w punkcie początkowym (0,0,0) skierowanym do przodu wzdłuż osi Z, ale można ustawić jego położenie i orientację z właściwościami Położenie i Orientacja .

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

Możesz zaktualizować lokalizację, szybkość i kierunek emiterów w czasie wykonywania, aby symulować ruch źródła audio przez przestrzeń 3D.

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

Możesz również zaktualizować położenie, prędkość i orientację obiektu nasłuchującego podczas działania, aby symulować ruch użytkownika w przestrzeni 3D.

deviceOutputNode.Listener.Position = newUserPosition;

Domyślnie dźwięk przestrzenny jest wyliczany przy użyciu algorytmu funkcji przenoszenia związanej z położeniem głowy (HRTF) firmy Microsoft w celu tłumienia dźwięku na podstawie jego kształtu, prędkości i położenia względem słuchacza. Właściwość SpatialAudioModel można ustawić na FoldDown, aby użyć prostej metody stereofonicznego miksowania do symulowania dźwięku przestrzennego, która jest mniej dokładna, ale wymaga mniej zasobów procesora i pamięci.

Zobacz także