首页 > 其他分享 >.NET 音频采集及多种方案对比

.NET 音频采集及多种方案对比

时间:2024-08-30 15:48:19浏览次数:9  
标签:音频 录制 WaveIn 采集 扬声器 var new 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;
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     }
复制代码

一般混音的同时,也会重采样。看具体场景操作吧。

上方示例代码详见Demo kybs00/AudioCaptureDemo: 音频采集DEMO (github.com)

 

参考:

简要介绍WASAPI播放音频的方法 - PeacoorZomboss - 博客园 (cnblogs.com)

[C#] 使用 NAudio 实现音频可视化_c#声音频谱-CSDN博客

naudio/NAudio: Audio and MIDI library for .NET (github.com)

关键字:音频采集、麦克风/扬声器/混音采集

 

2024-08-30 15:46:10【出处】:https://www.cnblogs.com/kybs0/p/18375991

=======================================================================================

标签:音频,录制,WaveIn,采集,扬声器,var,new,NET
From: https://www.cnblogs.com/mq0036/p/18388877

相关文章

  • .NET 8 Moq mock GetRequiredKeyedService Setup报错
    .NET8MoqmockGetRequiredKeyedServiceSetup报错项目代码里有地方用到IServiceProvider.GetRequiredKeyedService<T>来解析服务,在写单元测试时需要Mock它,本以为像下面这样写就可以了:varserviceProvider=newMock<IServiceProvider>();serviceProvider.Setup(x=>x.GetR......
  • .NET 摄像头采集
    本文主要介绍摄像头(相机)如何采集数据,用于类似摄像头本地显示软件,以及流媒体数据传输场景如传屏、视讯会议等。摄像头采集有多种方案,如AForge.NET、WPFMediaKit、OpenCvSharp、EmguCv、DirectShow.NET、MediaCaptre(UWP),网上一些文章以及github已经有很多介绍,这里总结、确认技术选型......
  • 磁场发生装置-电磁铁系列 Electromagnet
    功能简介上海天端实业有限公司电磁铁系列产品,可提供可调均匀强磁场,稳定性高,可控性强。适用于科研单位,高等院校及工厂做物质磁性实验,具有多种用途可配用于磁性材料测量装置、振动样品磁强计、霍尔效应研究、磁光效应研究、磁电阻效应研究、磁致伸缩研究、转矩磁强计、力法磁强......
  • ASP.NET Core6.0-wwwroot文件夹无法访问解决方法
    默认情况下,ASP.NETCore项目中的wwwroot文件夹被视为Web根文件夹。静态文件可以存储在Web根目录下的任何文件夹中,并可以使用该根目录的相对路径进行访问。在ASP.NET应用程序中,可以从应用程序的根文件夹或其下的任何其他文件夹提供静态文件。这已在ASP.NETCore中更改。现在,只有Web......
  • NS4248 3.0Wx2 双声道 D 类音频功率放大器附加立体声耳机功能
    1特性●工作电压范围:3.0V-5.25V●输出功率3W@ClassD/Load=3ohm●THD+N=0.1%@VDD=5V/Po=0.5W●立体声耳机放大模式●优异的全带宽EMI抑制能力●优异的“上电,掉电”噪声抑制●过流保护、欠压保护●提供SOP16封装2应用范围●手提电脑●台式电脑●......
  • .NET 开源报表神器 Seal-Report
    前言Seal-Report是一款.NET开源报表工具,拥有1.4KStar。它提供了一个完整的框架,使用C#编写,最新的版本采用的是.NET8.0。它能够高效地从各种数据库或NoSQL数据源生成日常报表,并支持执行复杂的报表任务。其简单易用的安装过程和直观的设计界面,我们能够在几分钟内创建并......
  • .net8 aot 发布
    AOT是AheadOfTime的缩写,指运行前编译,是两种程序的编译方式之一,与JIT(Just-in-time)相对.net 对操作系统的支持情况操作系统.NET8(体系结构).NET6(体系结构)Windows11✔️x64、x86、Arm64✔️x64、Arm64WindowsServer2022✔️x64、x86✔️x64、x86Windows10版本......
  • PageOffice6国产Linux系统最简集成代码(Asp.Net)
    本文描述了PageOffice产品在普通的Asp.Net项目中如何集成调用。PageOffice国产版:支持信创系统,支持银河麒麟V10和统信UOS,支持X86(intel、兆芯、海光等)、ARM(飞腾、鲲鹏、麒麟等)、longarch芯片架构。新建Asp.Net项目:PageOffice6-Net-Simple在您的web项目的“依赖项-包-管理NuGet......
  • .NET 开源实时监控系统 - WatchDog
    目录前言项目介绍功能特点项目技术栈工作原理1、支持.NET版本2、下载源码安装与配置1、WatchDog安装2、WatchDog服务注册3、添加异常记录器4、设置自动清除日志(可选)5、设置日志记录到外部数据库(可选)6、设置访问日志的账号密码7、配置说明和示例8、记录消息/......
  • .NET 摄像头采集
    本文主要介绍摄像头(相机)如何采集数据,用于类似摄像头本地显示软件,以及流媒体数据传输场景如传屏、视讯会议等。摄像头采集有多种方案,如AForge.NET、WPFMediaKit、OpenCvSharp、EmguCv、DirectShow.NET、MediaCaptre(UWP),网上一些文章以及github已经有很多介绍,这里总结下1. AForge.......