C# NAudio 庫(kù)的各種常見(jiàn)使用方式之播放 錄制 轉(zhuǎn)碼 音頻可視化
概述
在 NAudio 中, 常用類(lèi)型有 WaveIn, WaveOut, WaveStream, WaveFileWriter, WaveFileReader, AudioFileReader 以及接口: IWaveProvider, ISampleProvider, IWaveIn, IWavePlayer
- WaveIn 表示波形輸入, 繼承了 IWaveIn, 例如麥克風(fēng)輸入, 或者計(jì)算機(jī)正在播放的音頻流.
- WaveOut 表示波形輸出, 繼承了 IWavePlayer, 用來(lái)播放波形音樂(lè), 以 IWaveProvider 作為播放源播放音頻, 通過(guò)拓展方法也支持以 ISampleProvider 作為播放源播放音頻
- WaveStream 表示波形流, 它繼承了 IWaveProvider, 可以用來(lái)作為播放源.
- WaveFileReader 繼承了 WaveStream, 用來(lái)讀取波形文件
- WaveFileWriter 繼承了Stream, 用來(lái)寫(xiě)入文件, 常用于保存音頻錄制的數(shù)據(jù).
- AudioFileReader 通用的音頻文件讀取器, 可以讀取波形文件, 也可以讀取其他類(lèi)型的音頻文件例如 Aiff, MP3
- IWaveProvider 波形提供者, 上面已經(jīng)提到, 是音頻播放的提供者, 通過(guò)拓展方法可以轉(zhuǎn)換為 ISampleProvider
- ISampleProvider 采樣提供者, 上面已經(jīng)提到, 通過(guò)拓展方法可以作為 WaveOut 的播放源
播放音頻
常用的播放音頻方式有兩種, 播放波形音樂(lè), 以及播放 MP3 音樂(lè)
播放波形音樂(lè):
// NAudio 中, 通過(guò) WaveFileReader 來(lái)讀取波形數(shù)據(jù), 在實(shí)例化時(shí), 你可以指定文件名或者是輸入流, 這意味著你可以讀取內(nèi)存流中的音頻數(shù)據(jù) // 但是需要注意的是, 不可以讀取來(lái)自網(wǎng)絡(luò)流的音頻, 因?yàn)榫W(wǎng)絡(luò)流不可以進(jìn)行 Seek 操作. // 此處, 假設(shè) ms 為一個(gè) MemoryStream, 內(nèi)存有音頻數(shù)據(jù). WaveFileReader reader = new WaveFileReader(ms); WaveOut wout = new WaveOut(); wout.Init(reader); // 通過(guò) IWaveProvider 為音頻輸出初始化 wout.Play(); // 至此, wout 將從指定的 reader 中提供的數(shù)據(jù)進(jìn)行播放
播放 MP3 音樂(lè):
// 播放 MP3 音樂(lè)其實(shí)與播放波形音樂(lè)沒(méi)有太大區(qū)別, 只不過(guò)將 WaveFileReader 換成了 Mp3FileReader 罷了 // 另外, 也可以使用通用的 Reader, MediaFoundationReader, 它既可以讀取波形音樂(lè), 也可以讀取 MP3 // 此處, 假設(shè) ms 為一個(gè) MemoryStream, 內(nèi)存有音頻數(shù)據(jù). Mp3FileReader reader = new Mp3FileReader(ms); WaveOut wout = new WaveOut(); wout.Init(reader); wout.Play();
音頻錄制
錄制麥克風(fēng)輸入
// 借助 WaveIn 類(lèi), 我們可以輕易的捕獲麥克風(fēng)輸入, 在每一次錄制到數(shù)據(jù)時(shí), 將數(shù)據(jù)寫(xiě)入到文件或其他流, 這就實(shí)現(xiàn)了保存錄音 // 在保存波形文件時(shí)需要借助 WaveFileWriter, 當(dāng)然, 如果你想保存為其他格式, 也可以使用其它的 Writer, 例如 CurWaveFileWriter 以及 // AiffFileWriter, 美中不足的是沒(méi)有直接寫(xiě)入到 MP3 的 FileWriter // 需要注意的是, 如果你是用的桌面程序, 那么你可以直接使用 WaveIn, 其回調(diào)基于 Windows 消息, 所以無(wú)法在控制臺(tái)應(yīng)用中使用 WaveIn // 如果要在控制臺(tái)應(yīng)用中實(shí)現(xiàn)錄音, 只需要使用 WaveInEvent, 它的回調(diào)基于事件而不是 Windows 消息, 所以可以通用 WaveIn cap = new WaveIn(); // cap, capture WaveFileWriter writer = new WaveFileWriter(); cap.DataAvailable += (s, args) => writer.Write(args.Buffer, 0, args.BytesRecorded); // 訂閱事件 cap.StartRecording(); // 開(kāi)始錄制 // 結(jié)束錄制時(shí): cap.StopRecording(); // 停止錄制 writer.Close(); // 關(guān)閉 FileWriter, 保存數(shù)據(jù) // 另外, 除了使用 WaveIn, 你還可以使用 WasapiCapture, 它與 WaveIn 的使用方式是一致的, 可以用來(lái)錄制麥克風(fēng) // Wasapi 全稱(chēng) Windows Audio Session Application Programming Interface (Windows音頻會(huì)話應(yīng)用編程接口) // 具體 WaveIn, WaveInEvent, WasapiCapture 的性能, 筆者還沒(méi)有測(cè)試過(guò), 但估計(jì)不會(huì)有太大差異. // 提示: WasapiCapture 和 WasapiLoopbackCapture 位于 NAudio.Wave 命名空間下
錄制聲卡輸出
// 錄制聲卡輸出, 也就是錄制計(jì)算機(jī)正在播放的聲音, 借助 WasapiLoopbackCapture 即可簡(jiǎn)單實(shí)現(xiàn), 使用方式與 WasapiCapture 無(wú)異 WasapiLoopbackCapture cap = new WasapiLoopbackCapture(); WaveFileWriter writer = new WaveFileWriter(); cap.DataAvailable += (s, args) => writer.Write(args.Buffer, 0, args.BytesRecorded); cap.StartRecording();
高級(jí)應(yīng)用
獲取計(jì)算機(jī)實(shí)時(shí)播放音量大小
// 既然我們已經(jīng)知道了, 那些數(shù)據(jù)都是一個(gè)個(gè)的采樣, 自然也可以通過(guò)它們來(lái)繪制頻譜, 只需要進(jìn)行快速傅里葉變換即可
// 而且有意思的是, NAudio 也為我們準(zhǔn)備好了快速傅里葉變換的方法, 位于 NAudio.Dsp 命名空間下
// 提示: 進(jìn)行傅里葉變換所需要的復(fù)數(shù)(Complex)類(lèi)也位于 NAudio.Dsp 命名空間, 它有兩個(gè)字段, X(實(shí)部) 與 Y(虛部)
// 下面給出在 IeeeFloat 格式下的音樂(lè)可視化的簡(jiǎn)單示例:
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); // 填充數(shù)據(jù)
int sampleRate = (s as WasapiLoopbackCapture).WaveFormat.SampleRate; // 獲取采樣率
Complex[] complexSrc = filledSamples.Select(v => new Complex(){ X = v }).ToArray(); // 轉(zhuǎn)換為復(fù)數(shù)
FastFourierTransform.FFT(false, log, complexSrc); // 進(jìn)行傅里葉變換
double[] result = complexSrc.Select(v => Math.Sqrt(v.X * v.X + v.Y * v.Y)).ToArray(); // 取得結(jié)果
};
音頻格式轉(zhuǎn)換
// 對(duì)于 Wave, CueWave, Aiff, 這些格式都有其對(duì)應(yīng)的 FileWriter, 我們可以直接調(diào)用其 Writer 的 Create***File 來(lái)
// 從 IWaveProvider 創(chuàng)建對(duì)應(yīng)格式的文件. 對(duì)于 MP3 這類(lèi)沒(méi)有 FileWriter 的格式, 可以調(diào)用 MediaFoundationEncoder
// 例如一個(gè)文件, "./Disconnected.mp3", 我們要將它轉(zhuǎn)換為 wav 格式, 只需要使用下面的代碼, CurWave 與 Aiff 同理
using (Mp3FileReader reader = new Mp3FileReader("./Disconnected.mp3"))
WaveFileWriter.CreateWaveFile("./Disconnected.wav", reader);
// 從 IWaveProvider 創(chuàng)建 MP3 文件, 假如一個(gè) WaveFileReader 為 src
MediaFoundationEncoder.EncodeToMp3(src, "./NewMp3.mp3");
提示
對(duì)于剛剛所說(shuō)的音頻錄制, 采樣的格式有一點(diǎn)需要注意, 將數(shù)據(jù)轉(zhuǎn)換為一個(gè) float 數(shù)組后, 其中還需要區(qū)分音頻通道, 例如一般音樂(lè)是雙通道, WaveFormat 的 Channel 為 2, 那么在 float 數(shù)組中, 每?jī)蓚€(gè)采樣為一組, 一組采樣中每一個(gè)采樣都是一個(gè)通道在當(dāng)前時(shí)間內(nèi)的采樣.
以雙通道距離, 下圖中, 采樣數(shù)據(jù)中每一個(gè)圓圈都表示一個(gè) float 值, 那么每?jī)蓚€(gè)采樣時(shí)間點(diǎn)相同, 而各個(gè)通道的采樣就是每一組中每一個(gè)采樣

所以對(duì)于我們剛剛進(jìn)行的音樂(lè)可視化, 嚴(yán)格意義上來(lái)講, 還需要區(qū)分通道
對(duì)于采樣的大小, BitsPerSample, 可以是 8, 16, 32, 其中 8 和 16 都是整數(shù)存儲(chǔ)采樣, 32 是浮點(diǎn)數(shù)存儲(chǔ)采樣.
另外, 對(duì)于音樂(lè)可視化部分的傅里葉變換結(jié)果, 我們只需要其中一部分, 取前 256 個(gè)足矣. (我也不知道它這個(gè)運(yùn)算結(jié)果是如何分布的)
示例
本文提到的部分內(nèi)容在 github.com/SlimeNull/AudioTest 倉(cāng)庫(kù)中有示例, 例如音頻可視化, 音頻錄制, 以及其他零星的示例
到此這篇關(guān)于C# NAudio 庫(kù)的各種常見(jiàn)使用方式之播放 錄制 轉(zhuǎn)碼 音頻可視化的文章就介紹到這了,更多相關(guān)C# NAudio 庫(kù)用法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Unity的AssetPostprocessor?Model動(dòng)畫(huà)函數(shù)使用案例深究
這篇文章主要介紹了Unity的AssetPostprocessor?Model動(dòng)畫(huà)函數(shù)使用案例的深入解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08
C#調(diào)用dll報(bào)錯(cuò):無(wú)法加載dll,找不到指定模塊的解決
這篇文章主要介紹了C#調(diào)用dll報(bào)錯(cuò):無(wú)法加載dll,找不到指定模塊的解決問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01
C#使用selenium實(shí)現(xiàn)操作瀏覽器并且截圖
這篇文章主要為大家詳細(xì)介紹了C#如何使用selenium組件實(shí)現(xiàn)操作瀏覽器并且截圖,文中的示例代碼簡(jiǎn)潔易懂,有需要的小伙伴可以參考一下2024-01-01
Unity UGUI的Scrollbar滾動(dòng)條組件使用詳解
這篇文章主要介紹了Unity UGUI的Scrollbar(滾動(dòng)條)組件的介紹及使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07
C#遠(yuǎn)程發(fā)送和接收數(shù)據(jù)流生成圖片的方法
這篇文章主要介紹了C#遠(yuǎn)程發(fā)送和接收數(shù)據(jù)流生成圖片的方法,涉及C#通過(guò)數(shù)據(jù)流傳輸圖片的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-07-07
C#利用WinForm實(shí)現(xiàn)查看指定目錄下所有圖片功能
Windows 窗體是用于生成 Windows 桌面應(yīng)用的 UI 框架, 它提供了一種基于 Visual Studio 中提供的可視化設(shè)計(jì)器創(chuàng)建桌面應(yīng)用的高效方法,本文介紹了C#利用WinForm實(shí)現(xiàn)可以查看指定目錄文件下所有圖片功能,需要的朋友可以參考下2024-05-05
C#實(shí)現(xiàn)目錄跳轉(zhuǎn)(TreeView和SplitContainer)的示例代碼
本文主要介紹了C#實(shí)現(xiàn)目錄跳轉(zhuǎn)(TreeView和SplitContainer)的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07

