.NET 音頻采集
當(dāng)前位置:點(diǎn)晴教程→知識(shí)管理交流
→『 技術(shù)文檔交流 』
本文介紹Windows下聲音數(shù)據(jù)的采集,用于本地錄音、視訊會(huì)議、投屏等場(chǎng)景 聲音錄制有麥克風(fēng)、揚(yáng)聲器以及混合錄制三類(lèi)方式,麥克風(fēng)和揚(yáng)聲器單獨(dú)錄制的場(chǎng)景更多點(diǎn),混合錄制更多的是用于本地錄音 我們基于NAudio實(shí)現(xiàn),開(kāi)源組件NAudio已經(jīng)很穩(wěn)定的實(shí)現(xiàn)了各類(lèi)播放、錄制、轉(zhuǎn)碼等功能,WaveIn,WaveInEvent,WasapiCapture,WasapiLoopbackCapture, WaveOut, WaveStream, WaveFileWriter, WaveFileReader, AudioFileReader都是比較常見(jiàn)的類(lèi),下面詳細(xì)介紹下錄制模塊的實(shí)現(xiàn) 麥克風(fēng)錄制1.WaveInEvent 通過(guò)WaveInEvent類(lèi),我們可以捕獲麥克風(fēng)輸入: 1 private WaveInEvent _waveIn; 2 private WaveFileWriter _writer; 3 private void MainWindow_Loaded(object sender, RoutedEventArgs e) 4 { 5 _waveIn = new WaveInEvent(); 6 //441采樣率,單通道 7 _waveIn.WaveFormat = new WaveFormat(44100, 1); 8 _writer = new WaveFileWriter("recordedAudio.wav", _waveIn.WaveFormat); 9 _waveIn.DataAvailable += (s, a) => 10 { 11 _writer.Write(a.Buffer, 0, a.BytesRecorded); 12 }; 13 // 列出所有可用的錄音設(shè)備 14 for (int i = 0; i < WaveIn.DeviceCount; i++) 15 { 16 var deviceInfo = WaveIn.GetCapabilities(i); 17 OutputTextBlock.Text += $"Device {i}: {deviceInfo.ProductName}\r\n"; 18 } 19 } 20 private void StartRecordButton_OnClick(object sender, RoutedEventArgs e) 21 { 22 _waveIn.StartRecording(); 23 } 24 private void StopRecordButton_OnClick(object sender, RoutedEventArgs e) 25 { 26 _waveIn.StopRecording(); 27 _waveIn.Dispose(); 28 _writer.Close(); 29 } 在每次錄制到數(shù)據(jù)時(shí),將數(shù)據(jù)寫(xiě)入文件。上面是實(shí)現(xiàn)保存錄音的DEMO 2.WaveIn 還有WaveIn,和WaveInEvent是一樣接口IWaveIn。如果是Windows窗口應(yīng)用,可以直接使用WaveIn,但需要傳入窗口句柄??刂婆_(tái)應(yīng)用是無(wú)法支持WaveIn的 WaveIn構(gòu)造參數(shù)需要傳入窗口句柄,默認(rèn)不傳的話NAudio會(huì)創(chuàng)建一個(gè)窗口: 1 internal void Connect(WaveInterop.WaveCallback callback) 2 { 3 if (this.Strategy == WaveCallbackStrategy.NewWindow) 4 { 5 this.waveOutWindow = new WaveWindow(callback); 6 this.waveOutWindow.CreateControl(); 7 this.Handle = this.waveOutWindow.Handle; 8 } 9 else 10 { 11 ......... 12 } 13 } 另外這里的WaveWindow是winform窗口,internal class WaveWindow : Form WaveIn 使用回調(diào)函數(shù)(Callback)來(lái)處理音頻數(shù)據(jù),這種回調(diào)函數(shù)會(huì)在 Windows 收到音頻數(shù)據(jù)時(shí)通過(guò)消息機(jī)制調(diào)度。這通常意味著你需要管理并處理這些回調(diào)函數(shù),以確保音頻數(shù)據(jù)的正確捕捉和處理。然而這也意味著需要更多的底層工作和線程安全控制。 而在控制臺(tái)這類(lèi)非GUI應(yīng)用,就建議使用WaveInEvent了,它未使用窗口消息,而是通過(guò)while循環(huán)監(jiān)聽(tīng)buffers數(shù)據(jù),通過(guò)判斷buffer.Done是否完成來(lái)觸發(fā)輸出buffer數(shù)據(jù)事件DataAvailable。 所以性能來(lái)說(shuō)WaveIn從線程處理上會(huì)占優(yōu)很多,未做過(guò)對(duì)比測(cè)試(待補(bǔ)充) 3.WasapiCapture 除了WaveIn API,還可以使用WasapiCapture, 它與WaveIn的使用方式是一致的, 可以用來(lái)錄制麥克風(fēng)WaveInAPI雖然沒(méi)有獨(dú)占、共享功能,但也需要處理并發(fā)問(wèn)題,即多個(gè)錄音實(shí)例訪問(wèn)同一個(gè)麥克風(fēng)設(shè)備的話會(huì)存在并發(fā)訪問(wèn)問(wèn)題。 WasapiCapture是WASAPI About WASAPI - Win32 apps | Microsoft Learn,全稱(chēng)Windows Audio Session Application Programming Interface (Windows音頻會(huì)話應(yīng)用編程接口) ,它在Windows Vista引入 、提供了一些關(guān)鍵的改進(jìn) 比如,提供更低的音頻延遲和高性能音頻處理,可以提供共享模式和獨(dú)占模式 在共享模式下,可以與多個(gè)應(yīng)用程序共享一個(gè)音頻設(shè)備;WasapiCapture.ShareMode = AudioClientShareMode.Shared; 在獨(dú)占模式下,應(yīng)用程序可以完全控制音頻設(shè)備,降低延遲 AudioClientShareMode.Exclusive 看看WasapiCapture DEMO,都是基于IWaveIn接口實(shí)現(xiàn),所以代碼無(wú)差別: 1 private WaveFileWriter _writer; 2 private WasapiCapture _capture; 3 private void MainWindow_Loaded(object sender, RoutedEventArgs e) 4 { 5 _capture = new WasapiCapture(); 6 _writer = new WaveFileWriter("recordedAudio.wav", _capture.WaveFormat); 7 _capture.DataAvailable += (s, a) => 8 { 9 _writer.Write(a.Buffer, 0, a.BytesRecorded); 10 }; 11 // 列出所有可用的錄音設(shè)備 12 for (int i = 0; i < WaveIn.DeviceCount; i++) 13 { 14 var deviceInfo = WaveIn.GetCapabilities(i); 15 OutputTextBlock.Text += $"Device {i}: {deviceInfo.ProductName}\r\n"; 16 } 17 } 錄制麥克風(fēng)音頻,WasapiCapture 是最佳選擇,專(zhuān)為低延遲、高性能設(shè)計(jì) 另外,如果音頻采集時(shí)需要重采樣,可以使用BufferedWaveProvider緩存DataAvailable事件過(guò)來(lái)的原始音頻數(shù)據(jù), 1 //創(chuàng)建BufferedWaveProvider,緩存原始音頻數(shù)據(jù) 2 var bufferedProvider = new BufferedWaveProvider(provider.NAudioWaveFormat) 3 { 4 DiscardOnBufferOverflow = true, 5 ReadFully = false 6 }; 7 provider.WaveIn.DataAvailable += (s, e) => 8 { 9 //將音頻數(shù)據(jù)寫(xiě)入 BufferedWaveProvider 10 bufferedProvider.AddSamples(e.Buffer, 0, e.BytesRecorded); 11 }; 12 //獲取采樣接口 13 var sampleProvider = bufferedProvider.ToSampleProvider(); 14 sampleProvider = new WdlResamplingSampleProvider(sampleProvider, TargetFormat.SampleRate); 15 //重采樣后的音頻數(shù)據(jù) 16 _waveProvider = sampleProvider.ToWaveProvider16(); BufferedWaveProvider、SampleToWaveProvider16均是實(shí)現(xiàn)IWaveProvider通用接口,可提供音頻格式以及獲取數(shù)據(jù)接口 1 public interface IWaveProvider 2 { 3 /// <summary>Gets the WaveFormat of this WaveProvider.</summary> 4 /// <value>The wave format.</value> 5 WaveFormat WaveFormat { get; } 6 7 /// <summary>Fill the specified buffer with wave data.</summary> 8 /// <param name="buffer">The buffer to fill of wave data.</param> 9 /// <param name="offset">Offset into buffer</param> 10 /// <param name="count">The number of bytes to read</param> 11 /// <returns>the number of bytes written to the buffer.</returns> 12 int Read(byte[] buffer, int offset, int count); 13 } 將重采樣的數(shù)據(jù)寫(xiě)入本地文件保存: 1 /// <summary> 2 /// 目標(biāo)音頻格式 3 /// </summary> 4 public WaveFormat TargetFormat { get; } 5 public void Save() 6 { 7 using var writer = new WaveFileWriter("recordedAudio.wav", TargetFormat); 8 // 將重采樣后的數(shù)據(jù)寫(xiě)入文件 9 byte[] buffer = new byte[TargetFormat.AverageBytesPerSecond]; 10 int bytesRead; 11 while ((bytesRead = _waveProvider.Read(buffer, 0, buffer.Length)) > 0) 12 { 13 writer.Write(buffer, 0, bytesRead); 14 } 15 } 這樣,我們使用 WasapiCapture 捕獲音頻數(shù)據(jù),并將這些數(shù)據(jù)實(shí)時(shí)重采樣到指定采樣率如44.1kHz(常見(jiàn)的采樣率有441和480),單聲道格式。錄音結(jié)束后,重采樣后的音頻數(shù)據(jù)再被保存到一個(gè)WAV文件中。 另外如果是單通道聲音,可以轉(zhuǎn)換成多通道即立體聲: 1 // Mono to Stereo 2 if (simpleFormat.Channels == 1) 3 { 4 sampleProvider = sampleProvider.ToStereo(); 5 } ToStereo返回的MonoToStereoSampleProvider,會(huì)將單通道聲音數(shù)據(jù),轉(zhuǎn)換為雙通道的音頻格式。但實(shí)際上,采樣器MonoToStereoSampleProvider內(nèi)部只有一份source數(shù)據(jù),在Read時(shí)外部參數(shù)Samples直接除以2即變成了1,左右聲道均輸出此音頻數(shù)據(jù)。 麥克風(fēng)錄制,推薦首選WasapiCapture。 揚(yáng)聲器錄制錄制揚(yáng)聲器聲音即聲卡輸出,借助WasapiLoopbackCapture可簡(jiǎn)單實(shí)現(xiàn),使用方式與WasapiCapture沒(méi)區(qū)別。部分代碼: 1 var capture = new WasapiLoopbackCapture(); 2 var writer = new WaveFileWriter("recordedAudio.wav", capture.WaveFormat); 3 capture.DataAvailable += (s, a) => 4 { 5 writer.Write(a.Buffer, 0, a.BytesRecorded); 6 }; 7 capture.StartRecording(); 8 // 列出所有可用的揚(yáng)聲器設(shè)備 9 for (int i = 0; i < WaveOut.DeviceCount; i++) 10 { 11 var deviceInfo = WaveOut.GetCapabilities(i); 12 OutputTextBlock.Text += $"Device {i}: {deviceInfo.ProductName}\r\n"; 13 } 1. 音頻可視化 值得另外說(shuō)的,揚(yáng)聲器錄制有一類(lèi)廠測(cè)場(chǎng)景,上位機(jī)工廠測(cè)試軟件測(cè)試揚(yáng)聲器,需要顯示聲道的音頻曲線 音頻波形圖或者頻譜圖,可以通過(guò)DataAvailable拿到的字節(jié)數(shù)組,根據(jù)可視化圖X坐標(biāo)需要顯示的點(diǎn)列數(shù)量,在數(shù)組中獲取數(shù)據(jù)然后映射到可視化圖表坐標(biāo)Y值上。詳細(xì)的可參考這篇 [C#] 使用 NAudio 實(shí)現(xiàn)音頻可視化_c#聲音頻譜-CSDN博客,它實(shí)現(xiàn)的是曲線,也可以另外換成柱狀圖。 錄制揚(yáng)聲器,有些場(chǎng)景需要關(guān)閉本地?fù)P聲器外放。投屏軟件有這個(gè)場(chǎng)景,會(huì)將當(dāng)前設(shè)備A的聲卡音頻數(shù)據(jù)傳輸?shù)狡渌O(shè)備B上播放,但設(shè)備A不想重復(fù)播放聲音。因?yàn)樵O(shè)備A播放聲音的話,會(huì)議室會(huì)有混音,并且投屏設(shè)備A一般是筆記本、設(shè)備B是會(huì)議大屏,揚(yáng)聲器質(zhì)量和功率是不如專(zhuān)業(yè)的交互大屏的,大屏揚(yáng)聲器價(jià)格會(huì)貴點(diǎn)。 1 var volume = playbackDevice.AudioEndpointVolume; 2 // 記錄原音量,用于結(jié)束錄制時(shí)恢復(fù)音量 3 float originalVolume = volume.MasterVolumeLevelScalar; 4 // 靜音播放設(shè)備 5 volume.MasterVolumeLevelScalar = 0; 2.保持揚(yáng)聲器活躍 同時(shí),錄制揚(yáng)聲器是一個(gè)持續(xù)活動(dòng),為避免因無(wú)音頻信號(hào)導(dǎo)致設(shè)備自動(dòng)關(guān)閉或進(jìn)入低功耗狀態(tài),在不想關(guān)閉音頻設(shè)備而又沒(méi)有實(shí)際音頻播放任務(wù)時(shí),會(huì)用沉默音頻保持設(shè)備活躍??梢园慈缦虏僮?/p> 1).創(chuàng)建一個(gè)WasapiOut實(shí)例,指定使用共享模式:var wasapiOut = new WasapiOut(device, AudioClientShareMode.Shared, true, 50); 2).獲取音頻設(shè)備MMDevice的AudioClient對(duì)象 using var audioClient = device.AudioClient; 3).在啟動(dòng)WasapiLoopbackCapture錄制時(shí),將此靜音波形播放對(duì)象啟動(dòng),持續(xù)生成靜音信號(hào) 混音錄制也有必要介紹下混音錄制,雖然場(chǎng)景較少。 初始化多個(gè)麥克風(fēng)、揚(yáng)聲器錄制器,然后同上面重采樣操作,創(chuàng)建一個(gè) BufferedWaveProvider (bufferedProvider),用于存儲(chǔ)輸入的音頻數(shù)據(jù)。 訂閱訂閱 IWaveIn 的 DataAvailable 事件,將數(shù)據(jù)都塞進(jìn)緩存音頻緩存器 最后返回16位浮點(diǎn)波形數(shù)據(jù)存儲(chǔ)器,IWaveProvider數(shù)據(jù)獲取方式同上面重采樣操作。 1 public MixAudioCapture(params IWaveIn[] audioWaveCaptures) 2 { 3 _audioWaveCaptures = audioWaveCaptures; 4 var sampleProviders = new List<ISampleProvider>(); 5 foreach (var waveIn in audioWaveCaptures) 6 { 7 var bufferedProvider = new BufferedWaveProvider(waveIn.WaveFormat) 8 { 9 DiscardOnBufferOverflow = true, 10 ReadFully = false 11 }; 12 waveIn.DataAvailable += (s, e) => 13 { 14 bufferedProvider.AddSamples(e.Buffer, 0, e.BytesRecorded); 15 }; 16 var sampleProvider = bufferedProvider.ToSampleProvider(); 17 sampleProviders.Add(sampleProvider); 18 } 19 var waveProviders = sampleProviders.Select(m => m.ToWaveProvider()); 20 // 混音后的音頻數(shù)據(jù) 21 _waveProvider = new MixingWaveProvider32(waveProviders).ToSampleProvider().ToWaveProvider16(); 22 } 一般混音的同時(shí),也會(huì)重采樣。看具體場(chǎng)景操作吧。 上方示例代碼詳見(jiàn)Demo kybs00/AudioCaptureDemo: 音頻采集DEMO (github.com) 參考: 簡(jiǎn)要介紹WASAPI播放音頻的方法 - PeacoorZomboss - 博客園 (cnblogs.com) ?轉(zhuǎn)自https://www.cnblogs.com/kybs0/p/18375991 該文章在 2024/11/18 10:46:14 編輯過(guò) |
關(guān)鍵字查詢(xún)
相關(guān)文章
正在查詢(xún)... |