Processar quadros de áudio com MediaFrameReader

Este artigo mostra-lhe como usar um MediaFrameReader com MediaCapture para obter dados de áudio de uma fonte de media frame. Para aprender sobre o uso de um MediaFrameReader para obter dados de imagem, como de uma câmara a cores, infravermelhos ou de profundidade, consulte Processar fotogramas de media com o MediaFrameReader. Esse artigo fornece uma visão geral do padrão de utilização do leitor de frames e discute algumas funcionalidades adicionais da classe MediaFrameReader , como o uso do MediaFrameSourceGroup para recuperar frames de múltiplas fontes ao mesmo tempo.

Nota

As funcionalidades discutidas neste artigo só estão disponíveis a partir do Windows 10, versão 1803.

Configurar o seu projeto

O processo de aquisição de frames de áudio é, em grande medida, o mesmo que o de adquirir outros tipos de frames multimédia. Tal como em qualquer aplicação que utilize MediaCapture, deve declarar que a sua aplicação utiliza a capacidade de webcam antes de tentar aceder a qualquer dispositivo com câmara. Se a sua aplicação captar a partir de um dispositivo de áudio, deve também declarar a capacidade do dispositivo de microfone .

Selecionar fontes de quadros e grupos de fontes

O primeiro passo na captura de frames de áudio é inicializar um MediaFrameSource que represente a fonte dos dados de áudio, como um microfone ou outro dispositivo de captura de áudio. Para isso, deve criar uma nova instância do objeto MediaCapture . Neste exemplo, a única definição de inicialização para o MediaCapture é definir o StreamingCaptureMode para indicar que queremos transmitir áudio do dispositivo de captura.

Depois de chamar MediaCapture.InitializeAsync, pode obter a lista de origens de fotogramas de multimédia acessíveis através da propriedade FrameSources. Este exemplo seleciona todas as origens de fotogramas cujo MediaFrameSourceInfo, que descreve a origem do fotograma, tem um MediaStreamType de Audio, indicando que a origem multimédia produz dados de áudio.

Se a consulta devolver uma ou mais origens de frames, pode verificar a propriedade CurrentFormat para ver se a origem suporta o formato de áudio pretendido - neste exemplo, dados de áudio em vírgula flutuante. Verifique as AudioEncodingProperties para garantir que a codificação de áudio que deseja é suportada pela fonte.

m_mediaCapture = new MediaCapture();
MediaCaptureInitializationSettings settings = new MediaCaptureInitializationSettings()
{
    StreamingCaptureMode = StreamingCaptureMode.Audio,
};
await m_mediaCapture.InitializeAsync(settings);

var audioFrameSources = m_mediaCapture.FrameSources.Where(x => x.Value.Info.MediaStreamType == MediaStreamType.Audio);

if (audioFrameSources.Count() == 0)
{
    Debug.WriteLine("No audio frame source was found.");
    return;
}

MediaFrameSource frameSource = audioFrameSources.FirstOrDefault().Value;

MediaFrameFormat format = frameSource.CurrentFormat;
if (format.Subtype != MediaEncodingSubtypes.Float)
{
    return;
}

if (format.AudioEncodingProperties.ChannelCount != 2
    || format.AudioEncodingProperties.SampleRate != 48000)
{
    return;
}

Crie e inicie o MediaFrameReader

Obtenha uma nova instância de MediaFrameReader chamando MediaCapture.CreateFrameReaderAsync, passando o objeto MediaFrameSource que selecionou no passo anterior. Por predefinição, as tramas de áudio são obtidas no modo com buffer, o que torna menos provável a sua perda, embora isto ainda possa acontecer se não processar as tramas de áudio com rapidez suficiente e encher o buffer de memória alocado ao sistema.

Registar um handler para o evento MediaFrameReader.FrameArrived , que é ativado pelo sistema quando um novo frame de dados de áudio está disponível. Chame StartAsync para iniciar a aquisição de frames de áudio. Se o leitor de frames não arrancar, o valor de estado devolvido pela chamada terá um valor diferente de Sucesso.

m_mediaFrameReader = await m_mediaCapture.CreateFrameReaderAsync(frameSource);

// Optionally set acquisition mode. Buffered is the default mode for audio.
m_mediaFrameReader.AcquisitionMode = MediaFrameReaderAcquisitionMode.Buffered;

m_mediaFrameReader.FrameArrived += MediaFrameReader_AudioFrameArrived;

var status = await m_mediaFrameReader.StartAsync();

if (status != MediaFrameReaderStartStatus.Success)
{
    Debug.WriteLine("The MediaFrameReader couldn't start.");
}

No processador do evento FrameArrived, chame TryAcquireLatestFrame no objeto MediaFrameReader passado ao processador como remetente para tentar obter uma referência para o fotograma multimédia mais recente. Note que este objeto pode ser nulo, por isso deve sempre verificar antes de usar o objeto. Os tipos de fotogramas multimédia contidos no MediaFrameReference devolvido por TryAcquireLatestFrame dependem do tipo de origem ou origens de fotogramas que configurou para o leitor de fotogramas adquirir. Como o leitor de frames neste exemplo foi configurado para adquirir frames de áudio, ele obtém o frame subjacente usando a propriedade AudioMediaFrame .

Este método auxiliar ProcessAudioFrame no exemplo abaixo mostra como obter uma AudioFrame, que fornece informações como a marca temporal do frame e se este é descontínuo, a partir do objeto AudioMediaFrame. Para ler ou processar os dados da amostra áudio, terá de obter o objeto AudioBuffer do objeto AudioMediaFrame , criar um IMemoryBufferReference e depois chamar o método COM IMemoryBufferByteAccess::GetBuffer para recuperar os dados. Consulte a nota abaixo da lista de códigos para mais informações sobre o acesso a buffers nativos.

O formato dos dados depende da fonte do frame. Neste exemplo, ao selecionar uma fonte de frame media, garantimos explicitamente que a fonte de frame selecionada usava dois canais de dados float. O restante do código de exemplo mostra como determinar a duração e a contagem de amostras para os dados de áudio no frame.

private void MediaFrameReader_AudioFrameArrived(MediaFrameReader sender, MediaFrameArrivedEventArgs args)
{
    using (MediaFrameReference reference = sender.TryAcquireLatestFrame())
    {
        if (reference != null)
        {
            ProcessAudioFrame(reference.AudioMediaFrame);
        }
    }
}
unsafe private void ProcessAudioFrame(AudioMediaFrame audioMediaFrame)
{

    using (AudioFrame audioFrame = audioMediaFrame.GetAudioFrame())
    using (AudioBuffer buffer = audioFrame.LockBuffer(AudioBufferAccessMode.Read))
    using (IMemoryBufferReference reference = buffer.CreateReference())
    {
        byte* dataInBytes;
        uint capacityInBytes;
        float* dataInFloat;

        var memoryBuffer = reference.As<IMemoryBufferByteAccess>();
        memoryBuffer.GetBuffer(out dataInBytes, out capacityInBytes);


        // The requested format was float
        dataInFloat = (float*)dataInBytes;

        // Get the number of samples by multiplying the duration by sampling rate: 
        // duration [s] x sampling rate [samples/s] = # samples 

        // Duration can be gotten off the frame reference OR the audioFrame
        TimeSpan duration = audioMediaFrame.FrameReference.Duration;

        // frameDurMs is in milliseconds, while SampleRate is given per second.
        uint frameDurMs = (uint)duration.TotalMilliseconds;
        uint sampleRate = audioMediaFrame.AudioEncodingProperties.SampleRate;
        uint sampleCount = (frameDurMs * sampleRate) / 1000;

    }
}

Nota

Para poder operar nos dados de áudio, deve aceder a um buffer de memória nativo. Para isso, deve usar a interface COM IMemoryBufferByteAccess incluindo a lista de códigos abaixo. As operações no buffer nativo devem ser realizadas num método que utilize a palavra-chave insegura . Também tens de assinalar a opção para permitir código inseguro no separador Build do diálogo Project -> Properties.

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

Informações adicionais sobre o uso do MediaFrameReader com dados de áudio

Pode recuperar o AudioDeviceController associado à fonte de frames de áudio acedendo à propriedade MediaFrameSource.Controller . Este objeto pode ser usado para obter ou definir as propriedades do fluxo do dispositivo de captura ou para controlar o nível de captura. O exemplo seguinte silencia o dispositivo de áudio, de modo que os frames continuam a ser adquiridos pelo leitor de frames, mas todas as amostras têm valor de 0.

m_audioDeviceController.Muted = true;

Pode usar um objeto AudioFrame para passar dados de áudio capturados por uma fonte de media frame para um AudioGraph. Passe o frame para o método AddFrame de um AudioFrameInputNode. Para mais informações sobre o uso de gráficos de áudio para capturar, processar e misturar sinais de áudio, consulte Gráficos de áudio.