使用 MediaFrameReader 處理音訊畫面

本文將教你如何使用 MediaFrameReader 搭配 MediaCapture 從媒體框架來源取得音訊資料。 想了解如何使用 MediaFrameReader 獲取影像資料,例如彩色、紅外線或深度相機,請參閱 使用 MediaFrameReader 處理媒體畫面。 該文章概述畫面格讀取器的使用方式,並討論 MediaFrameReader 類別的一些其他功能,例如使用 MediaFrameSourceGroup 同時從多個來源擷取畫面格。

Note

本文所討論的功能僅從 Windows 10 版本 1803 開始提供。

設定您的專案

取得音訊幀的過程與其他類型的媒體幀大致相同。 和任何使用 MediaCapture 的應用程式一樣,你必須先聲明你的應用程式使用了 網路攝影機 功能,才能嘗試存取任何攝影機裝置。 如果你的應用程式能從音訊裝置擷取,也應該宣告 麥克風 裝置的功能。

選取畫面來源和畫面來源群組

擷取音訊框架的第一步是初始化一個代表音訊資料來源的 MediaFrameSource ,例如麥克風或其他音訊擷取裝置。 為此,您必須建立一個新的 MediaCapture 物件實例。 在這個例子中, MediaCapture 唯一的初始化設定是設定 StreamingCaptureMode 來表示我們想從擷取裝置串流音訊。

呼叫 MediaCapture.InitializeAsync 後,你可以用 FrameSources 屬性取得可存取的媒體畫面來源清單。 此範例選取所有其描述該影格來源的 MediaFrameSourceInfo 之 MediaStreamType 為 Audio 的影格來源,表示該媒體來源會產生音訊資料。

如果查詢回傳一個或多個影格來源,你可以檢查 CurrentFormat 屬性,看看該來源是否支援你想要的音訊格式——在此範例中,是浮點音訊資料。 請檢查 AudioEncodingProperties 以確保你想要的音訊編碼是來源所支援的。

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

建立並啟動 MediaFrameReader

透過呼叫 MediaCapture.CreateFrameReaderAsync,傳遞你在前一步選擇的 MediaFrameSource 物件,取得新的 MediaFrameReader 實例。 預設情況下,音訊影格會以緩衝模式取得,因此較不容易發生掉幀;不過,如果您處理音訊影格的速度不夠快,導致系統分配的記憶體緩衝區被填滿,仍然可能發生掉幀。

註冊一個處理程序,用於 MediaFrameReader.FrameArrived 事件,當有新的音訊資料影格可用時,系統會觸發此事件。 請致電 StartAsync 開始擷取音訊幀。 若框架讀取器未能啟動,呼叫回傳的狀態值將有非 成功值。

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.");
}

在 FrameArrived 事件處理常式中,對作為 sender 傳遞給處理常式的 MediaFrameReader 物件呼叫 TryAcquireLatestFrame,以嘗試擷取最新媒體畫面格的參考。 請注意,這個物件可以是空的,所以使用前應該先檢查。 包裝在 MediaFrameReference 中、由 TryAcquireLatestFrame 傳回的媒體畫格類型,取決於你將畫格讀取器設定為擷取哪一種畫格來源或哪些畫格來源。 由於本範例中的影格讀取器是為了擷取音訊影格而設,因此它是利用 AudioMediaFrame 特性取得底層影格。

以下範例中的 ProcessAudioFrame 輔助方法說明如何從 AudioMediaFrame 物件取得 AudioFrame,其中提供了影格的時間戳記及是否發生不連續情形等資訊。 要讀取或處理音訊取樣資料,你需要從 AudioMediaFrame 物件取得 AudioBuffer 物件,建立 IMemoryBufferReference,然後呼叫 COM 方法 IMemoryBufferByteAccess::GetBuffer 來取得資料。 請參閱下方程式碼清單下的說明,了解更多關於存取原生緩衝區的資訊。

資料格式取決於畫面來源。 在此範例中,選擇媒體畫面來源時,我們明確確保所選畫面來源使用兩個浮點資料通道。 範例程式碼的其餘部分展示了如何決定影格中音訊資料的時長與取樣數。

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;

    }
}

Note

要操作音訊資料,必須存取原生記憶體緩衝區。 為此,您必須使用 IMemoryBufferByteAccess COM 介面,並附上下方的程式碼清單。 對原生緩衝區的操作必須使用使用 不安全 關鍵字的方法執行。 你也需要在 >對話框的建置標籤中勾選允許不安全程式碼的方框。

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

關於使用 MediaFrameReader 搭配音訊資料的額外資訊

你可以透過存取 MediaFrameSource.Controller 屬性,擷取與音訊幀來源相關聯的 AudioDeviceController。 此物件可用於取得或設定擷取裝置的串流屬性,或控制擷取等級。 以下範例會將音訊裝置設為靜音,讓影格讀取器仍持續擷取影格,但所有取樣值都為 0。

m_audioDeviceController.Muted = true;

你可以使用 AudioFrame 物件,將媒體框架來源擷取的音訊資料傳入 AudioGraph。 將影格傳送到 AudioFrameInputNode 的 AddFrame 方法中。 欲了解更多使用音訊圖表捕捉、處理及混合音訊訊號的資訊,請參閱音訊圖表。