[C#] NAudio 庫的各種常見使用方式: 播放 錄制 轉碼 音頻可視化


概述

在 NAudio 中, 常用類型有 WaveIn, WaveOut, WaveStream, WaveFileWriter, WaveFileReader, AudioFileReader 以及接口: IWaveProvider, ISampleProvider, IWaveIn, IWavePlayer

  1. WaveIn 表示波形輸入, 繼承了 IWaveIn, 例如麥克風輸入, 或者計算機正在播放的音頻流.
  2. WaveOut 表示波形輸出, 繼承了 IWavePlayer, 用來播放波形音樂, 以 IWaveProvider 作為播放源播放音頻, 通過拓展方法也支持以 ISampleProvider 作為播放源播放音頻
  3. WaveStream 表示波形流, 它繼承了 IWaveProvider, 可以用來作為播放源.
  4. WaveFileReader 繼承了 WaveStream, 用來讀取波形文件
  5. WaveFileWriter 繼承了Stream, 用來寫入文件, 常用於保存音頻錄制的數據.
  6. AudioFileReader 通用的音頻文件讀取器, 可以讀取波形文件, 也可以讀取其他類型的音頻文件例如 Aiff, MP3
  7. IWaveProvider 波形提供者, 上面已經提到, 是音頻播放的提供者, 通過拓展方法可以轉換為 ISampleProvider
  8. ISampleProvider 采樣提供者, 上面已經提到, 通過拓展方法可以作為 WaveOut 的播放源

播放音頻

常用的播放音頻方式有兩種, 播放波形音樂, 以及播放 MP3 音樂

  1. 播放波形音樂:

    // NAudio 中, 通過 WaveFileReader 來讀取波形數據, 在實例化時, 你可以指定文件名或者是輸入流, 這意味着你可以讀取內存流中的音頻數據
    // 但是需要注意的是, 不可以讀取來自網絡流的音頻, 因為網絡流不可以進行 Seek 操作.
    
    // 此處, 假設 ms 為一個 MemoryStream, 內存有音頻數據.
    WaveFileReader reader = new WaveFileReader(ms);
    WaveOut wout = new WaveOut();
    wout.Init(reader);             // 通過 IWaveProvider 為音頻輸出初始化
    wout.Play();                   // 至此, wout 將從指定的 reader 中提供的數據進行播放
    
  2. 播放 MP3 音樂:

    // 播放 MP3 音樂其實與播放波形音樂沒有太大區別, 只不過將 WaveFileReader 換成了 Mp3FileReader 罷了
    // 另外, 也可以使用通用的 Reader, MediaFoundationReader, 它既可以讀取波形音樂, 也可以讀取 MP3
    
    // 此處, 假設 ms 為一個 MemoryStream, 內存有音頻數據.
    Mp3FileReader reader = new Mp3FileReader(ms);
    WaveOut wout = new WaveOut();
    wout.Init(reader);
    wout.Play();
    

音頻錄制

  1. 錄制麥克風輸入

    // 借助 WaveIn 類, 我們可以輕易的捕獲麥克風輸入, 在每一次錄制到數據時, 將數據寫入到文件或其他流, 這就實現了保存錄音
    // 在保存波形文件時需要借助 WaveFileWriter, 當然, 如果你想保存為其他格式, 也可以使用其它的 Writer, 例如 CurWaveFileWriter 以及
    // AiffFileWriter, 美中不足的是沒有直接寫入到 MP3 的 FileWriter
    // 需要注意的是, 如果你是用的桌面程序, 那么你可以直接使用 WaveIn, 其回調基於 Windows 消息, 所以無法在控制台應用中使用 WaveIn
    // 如果要在控制台應用中實現錄音, 只需要使用 WaveInEvent, 它的回調基於事件而不是 Windows 消息, 所以可以通用
    
    WaveIn cap = new WaveIn();   // cap, capture
    WaveFileWriter writer = new WaveFileWriter();
    cap.DataAvailable += (s, args) => writer.Write(args.Buffer, 0, args.BytesRecorded);    // 訂閱事件
    cap.StartRecording();   // 開始錄制
    
    // 結束錄制時:
    cap.StopRecording();    // 停止錄制
    writer.Close();         // 關閉 FileWriter, 保存數據
    
    // 另外, 除了使用 WaveIn, 你還可以使用 WasapiCapture, 它與 WaveIn 的使用方式是一致的, 可以用來錄制麥克風
    // Wasapi 全稱 Windows Audio Session Application Programming Interface (Windows音頻會話應用編程接口)
    // 具體 WaveIn, WaveInEvent, WasapiCapture 的性能, 筆者還沒有測試過, 但估計不會有太大差異.
    // 提示: WasapiCapture 和 WasapiLoopbackCapture 位於 NAudio.Wave 命名空間下
    
  2. 錄制聲卡輸出

    // 錄制聲卡輸出, 也就是錄制計算機正在播放的聲音, 借助 WasapiLoopbackCapture 即可簡單實現, 使用方式與 WasapiCapture 無異
    
    WasapiLoopbackCapture cap = new WasapiLoopbackCapture();
    WaveFileWriter writer = new WaveFileWriter();
    cap.DataAvailable += (s, args) => writer.Write(args.Buffer, 0, args.BytesRecorded);
    cap.StartRecording();
    

高級應用

  1. 獲取計算機實時播放音量大小

    // 既然我們已經知道了, 那些數據都是一個個的采樣, 自然也可以通過它們來繪制頻譜, 只需要進行快速傅里葉變換即可
    // 而且有意思的是, NAudio 也為我們准備好了快速傅里葉變換的方法, 位於 NAudio.Dsp 命名空間下
    // 提示: 進行傅里葉變換所需要的復數(Complex)類也位於 NAudio.Dsp 命名空間, 它有兩個字段, X(實部) 與 Y(虛部)
    // 下面給出在 IeeeFloat 格式下的音樂可視化的簡單示例:
    WasapiLoopbackCapture cap = new WasapiLoopbackCapture();
    cap.DataAvailable += (s, args) =>
    {
        float[] samples = Enumerable
                              .Range(0, args.BytesRecorded / 4)
                              .Select(i => BitConverter.ToSingle(args.Buffer, i * 4))
                              .ToArray();   // 獲取采樣
        
        int log = (int)Math.Ceiling(Math.Log(samples.Length, 2));
        float[] filledSamples = new float[(int)Math.Pow(2, log)];
        Array.Copy(samples, filledSamples, samples.Length);   // 填充數據
        
        int sampleRate = (s as WasapiLoopbackCapture).WaveFormat.SampleRate;    // 獲取采樣率
        Complex[] complexSrc = filledSamples.Select(v => new Complex(){ X = v }).ToArray();  // 轉換為復數
        FastFourierTransform.FFT(false, log, complexSrc);     // 進行傅里葉變換
        double[] result = complexSrc.Select(v => Math.Sqrt(v.X * v.X + v.Y * v.Y)).ToArray();    // 取得結果
    };
    
  2. 音頻格式轉換

    // 對於 Wave, CueWave, Aiff, 這些格式都有其對應的 FileWriter, 我們可以直接調用其 Writer 的 Create***File 來
    // 從 IWaveProvider 創建對應格式的文件. 對於 MP3 這類沒有 FileWriter 的格式, 可以調用 MediaFoundationEncoder
    
    // 例如一個文件, "./Disconnected.mp3", 我們要將它轉換為 wav 格式, 只需要使用下面的代碼, CurWave 與 Aiff 同理
    using (Mp3FileReader reader = new Mp3FileReader("./Disconnected.mp3"))
    	WaveFileWriter.CreateWaveFile("./Disconnected.wav", reader);
    
    // 從 IWaveProvider 創建 MP3 文件, 假如一個 WaveFileReader 為 src
    MediaFoundationEncoder.EncodeToMp3(src, "./NewMp3.mp3");
    

提示

對於剛剛所說的音頻錄制, 采樣的格式有一點需要注意, 將數據轉換為一個 float 數組后, 其中還需要區分音頻通道, 例如一般音樂是雙通道, WaveFormat 的 Channel 為 2, 那么在 float 數組中, 每兩個采樣為一組, 一組采樣中每一個采樣都是一個通道在當前時間內的采樣.

以雙通道距離, 下圖中, 采樣數據中每一個圓圈都表示一個 float 值, 那么每兩個采樣時間點相同, 而各個通道的采樣就是每一組中每一個采樣

image

所以對於我們剛剛進行的音樂可視化, 嚴格意義上來講, 還需要區分通道

對於采樣的大小, BitsPerSample, 可以是 8, 16, 32, 其中 8 和 16 都是整數存儲采樣, 32 是浮點數存儲采樣.

另外, 對於音樂可視化部分的傅里葉變換結果, 我們只需要其中一部分, 取前 256 個足矣. (我也不知道它這個運算結果是如何分布的)

示例

本文提到的部分內容在 github.com/SlimeNull/AudioTest 倉庫中有示例, 例如音頻可視化, 音頻錄制, 以及其他零星的示例


如有錯誤, 還請指出


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM