.NET 音頻采集
當前位置:點晴教程→知識管理交流
→『 技術文檔交流 』
本文介紹Windows下聲音數據的采集,用于本地錄音、視訊會議、投屏等場景 聲音錄制有麥克風、揚聲器以及混合錄制三類方式,麥克風和揚聲器單獨錄制的場景更多點,混合錄制更多的是用于本地錄音 我們基于NAudio實現,開源組件NAudio已經很穩定的實現了各類播放、錄制、轉碼等功能,WaveIn,WaveInEvent,WasapiCapture,WasapiLoopbackCapture, WaveOut, WaveStream, WaveFileWriter, WaveFileReader, AudioFileReader都是比較常見的類,下面詳細介紹下錄制模塊的實現 麥克風錄制1.WaveInEvent 通過WaveInEvent類,我們可以捕獲麥克風輸入: 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 // 列出所有可用的錄音設備 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 } 在每次錄制到數據時,將數據寫入文件。上面是實現保存錄音的DEMO 2.WaveIn 還有WaveIn,和WaveInEvent是一樣接口IWaveIn。如果是Windows窗口應用,可以直接使用WaveIn,但需要傳入窗口句柄。控制臺應用是無法支持WaveIn的 WaveIn構造參數需要傳入窗口句柄,默認不傳的話NAudio會創建一個窗口: 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 使用回調函數(Callback)來處理音頻數據,這種回調函數會在 Windows 收到音頻數據時通過消息機制調度。這通常意味著你需要管理并處理這些回調函數,以確保音頻數據的正確捕捉和處理。然而這也意味著需要更多的底層工作和線程安全控制。 而在控制臺這類非GUI應用,就建議使用WaveInEvent了,它未使用窗口消息,而是通過while循環監聽buffers數據,通過判斷buffer.Done是否完成來觸發輸出buffer數據事件DataAvailable。 所以性能來說WaveIn從線程處理上會占優很多,未做過對比測試(待補充) 3.WasapiCapture 除了WaveIn API,還可以使用WasapiCapture, 它與WaveIn的使用方式是一致的, 可以用來錄制麥克風WaveInAPI雖然沒有獨占、共享功能,但也需要處理并發問題,即多個錄音實例訪問同一個麥克風設備的話會存在并發訪問問題。 WasapiCapture是WASAPI About WASAPI - Win32 apps | Microsoft Learn,全稱Windows Audio Session Application Programming Interface (Windows音頻會話應用編程接口) ,它在Windows Vista引入 、提供了一些關鍵的改進 比如,提供更低的音頻延遲和高性能音頻處理,可以提供共享模式和獨占模式 在共享模式下,可以與多個應用程序共享一個音頻設備;WasapiCapture.ShareMode = AudioClientShareMode.Shared; 在獨占模式下,應用程序可以完全控制音頻設備,降低延遲 AudioClientShareMode.Exclusive 看看WasapiCapture DEMO,都是基于IWaveIn接口實現,所以代碼無差別: 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 // 列出所有可用的錄音設備 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 } 錄制麥克風音頻,WasapiCapture 是最佳選擇,專為低延遲、高性能設計 另外,如果音頻采集時需要重采樣,可以使用BufferedWaveProvider緩存DataAvailable事件過來的原始音頻數據, 1 //創建BufferedWaveProvider,緩存原始音頻數據 2 var bufferedProvider = new BufferedWaveProvider(provider.NAudioWaveFormat) 3 { 4 DiscardOnBufferOverflow = true, 5 ReadFully = false 6 }; 7 provider.WaveIn.DataAvailable += (s, e) => 8 { 9 //將音頻數據寫入 BufferedWaveProvider 10 bufferedProvider.AddSamples(e.Buffer, 0, e.BytesRecorded); 11 }; 12 //獲取采樣接口 13 var sampleProvider = bufferedProvider.ToSampleProvider(); 14 sampleProvider = new WdlResamplingSampleProvider(sampleProvider, TargetFormat.SampleRate); 15 //重采樣后的音頻數據 16 _waveProvider = sampleProvider.ToWaveProvider16(); BufferedWaveProvider、SampleToWaveProvider16均是實現IWaveProvider通用接口,可提供音頻格式以及獲取數據接口 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 } 將重采樣的數據寫入本地文件保存: 1 /// <summary> 2 /// 目標音頻格式 3 /// </summary> 4 public WaveFormat TargetFormat { get; } 5 public void Save() 6 { 7 using var writer = new WaveFileWriter("recordedAudio.wav", TargetFormat); 8 // 將重采樣后的數據寫入文件 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 捕獲音頻數據,并將這些數據實時重采樣到指定采樣率如44.1kHz(常見的采樣率有441和480),單聲道格式。錄音結束后,重采樣后的音頻數據再被保存到一個WAV文件中。 另外如果是單通道聲音,可以轉換成多通道即立體聲: 1 // Mono to Stereo 2 if (simpleFormat.Channels == 1) 3 { 4 sampleProvider = sampleProvider.ToStereo(); 5 } ToStereo返回的MonoToStereoSampleProvider,會將單通道聲音數據,轉換為雙通道的音頻格式。但實際上,采樣器MonoToStereoSampleProvider內部只有一份source數據,在Read時外部參數Samples直接除以2即變成了1,左右聲道均輸出此音頻數據。 麥克風錄制,推薦首選WasapiCapture。 揚聲器錄制錄制揚聲器聲音即聲卡輸出,借助WasapiLoopbackCapture可簡單實現,使用方式與WasapiCapture沒區別。部分代碼: 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 // 列出所有可用的揚聲器設備 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. 音頻可視化 值得另外說的,揚聲器錄制有一類廠測場景,上位機工廠測試軟件測試揚聲器,需要顯示聲道的音頻曲線 音頻波形圖或者頻譜圖,可以通過DataAvailable拿到的字節數組,根據可視化圖X坐標需要顯示的點列數量,在數組中獲取數據然后映射到可視化圖表坐標Y值上。詳細的可參考這篇 [C#] 使用 NAudio 實現音頻可視化_c#聲音頻譜-CSDN博客,它實現的是曲線,也可以另外換成柱狀圖。 錄制揚聲器,有些場景需要關閉本地揚聲器外放。投屏軟件有這個場景,會將當前設備A的聲卡音頻數據傳輸到其它設備B上播放,但設備A不想重復播放聲音。因為設備A播放聲音的話,會議室會有混音,并且投屏設備A一般是筆記本、設備B是會議大屏,揚聲器質量和功率是不如專業的交互大屏的,大屏揚聲器價格會貴點。 1 var volume = playbackDevice.AudioEndpointVolume; 2 // 記錄原音量,用于結束錄制時恢復音量 3 float originalVolume = volume.MasterVolumeLevelScalar; 4 // 靜音播放設備 5 volume.MasterVolumeLevelScalar = 0; 2.保持揚聲器活躍 同時,錄制揚聲器是一個持續活動,為避免因無音頻信號導致設備自動關閉或進入低功耗狀態,在不想關閉音頻設備而又沒有實際音頻播放任務時,會用沉默音頻保持設備活躍。可以按如下操作 1).創建一個WasapiOut實例,指定使用共享模式:var wasapiOut = new WasapiOut(device, AudioClientShareMode.Shared, true, 50); 2).獲取音頻設備MMDevice的AudioClient對象 using var audioClient = device.AudioClient; 3).在啟動WasapiLoopbackCapture錄制時,將此靜音波形播放對象啟動,持續生成靜音信號 混音錄制也有必要介紹下混音錄制,雖然場景較少。 初始化多個麥克風、揚聲器錄制器,然后同上面重采樣操作,創建一個 BufferedWaveProvider (bufferedProvider),用于存儲輸入的音頻數據。 訂閱訂閱 IWaveIn 的 DataAvailable 事件,將數據都塞進緩存音頻緩存器 最后返回16位浮點波形數據存儲器,IWaveProvider數據獲取方式同上面重采樣操作。 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 // 混音后的音頻數據 21 _waveProvider = new MixingWaveProvider32(waveProviders).ToSampleProvider().ToWaveProvider16(); 22 } 一般混音的同時,也會重采樣。看具體場景操作吧。 上方示例代碼詳見Demo kybs00/AudioCaptureDemo: 音頻采集DEMO (github.com) 參考: 簡要介紹WASAPI播放音頻的方法 - PeacoorZomboss - 博客園 (cnblogs.com) ?轉自https://www.cnblogs.com/kybs0/p/18375991 該文章在 2024/11/18 10:46:14 編輯過 |
關鍵字查詢
相關文章
正在查詢... |