|
本文介绍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 }<br>
复制代码 录制麦克风音频,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,左右声道均输出此音频数据。
扬声器录制
录制扬声器声音即声卡输出,借助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;
wasapiOut.Init(new SilenceProvider(audioClient.MixFormat));
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 }
复制代码 一般混音的同时,也会重采样。看具体场景操作吧
参考:
简要介绍WASAPI播放音频的方法 - PeacoorZomboss - 博客园 (cnblogs.com)
[C#] 使用 NAudio 实现音频可视化_c#声音频谱-CSDN博客
naudio/NAudio: Audio and MIDI library for .NET (github.com)
关键字:音频采集、麦克风/扬声器/混音采集
出处:http://www.cnblogs.com/kybs0/本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须在文章页面给出原文连接,否则保留追究法律责任的权利。
来源:https://www.cnblogs.com/kybs0/p/18375991
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作! |
|