news 2026/9/7 6:50:43

WPF录音与播放实战:用NAudio轻松实现音频采集与播放

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
WPF录音与播放实战:用NAudio轻松实现音频采集与播放

简介:面向需要在Windows桌面应用中集成录音、播放以及简单音频分析功能的开发者,这份示例工程完整展示了基于.NET Framework 4.5和Visual Studio 2017的WPF实现方案。工程借助NAudio库中的WasapiLoopbackCapture进行声卡数据捕获,再通过MediaPlayer完成音频的加载、播放、暂停、继续与停止,并将这些操作封装为界面按钮事件,让读者看清从底层采集到UI交互的完整链路。资源共175个文件,主要包含45个C#源码、XAML界面布局、编译生成的程序集与界面资源,以及用于测试的wav音频和必要配置文件,压缩包整体仅3.18MB,结构完整、目录清晰,适合直接打开学习或作为项目模板快速改造。目前已有926人学习下载。通过该工程,读者可以掌握录音数据写入、播放器控制、本地音频文件时长与比特率读取等关键代码写法,同时了解NAudio与WPF整合时的常见处理方式,节省从零搭建和反复调试的时间,对开发聊天软件、教育工具、语音笔记等场景有直接参考价值。 做WPF上位机或者工具类应用的时候,录音和播放音频是特别容易撞上的需求。现场采集一段语音、做语音对讲前的试听、给测试程序加个声音提示,甚至是把麦克风输入存成样本数据,核心要解决的问题就两个:怎么录,怎么放。WPF本身并没有提供一个开箱即用的录音控件,但把NAudio这个库引进来之后,录音、播放、暂停、进度控制都能在C#里直接搞定,十几分钟就能跑通一条完整流程。这篇总结适合已经会写基本WPF页面、但没碰过音频采集的开发者,也适合想快速给现有项目加语音模块的同行参考。

1. 方案选型:先弄明白WPF里能做音频的几套路子

1.1 录音不是WPF的强项,选对库是关键

很多人一开始会去找WPF自带的MediaElement或者MediaPlayer,但这两个类主要负责播放,录音能力很弱。WPF的定位是界面框架,底层没有直接暴露麦克风采集API,硬要用也不是不行,得绕到WinRT的Windows.Media.Capture,但那种做法既要处理异步API,还要面对桌面应用和WinRT组件之间的类型转换问题,写起来并不顺手。

实际项目里我更推荐直接用NAudio。这是一个开源跨平台的音频处理库,封装了Windows底层waveIn、waveOut、DirectSound、WASAPI等一系列音频API,核心功能覆盖录音、播放、格式转换、音频处理、混音等。对WPF开发者来说,NAudio最大的好处是API设计非常直白:录音就是拉数据,播放就是喂数据,中间没有太多概念门槛。而且它支持.NET Framework和.NET 6/8,老项目新项目都能用。

市面上还有个常见选择是Windows.Media.Playback和MediaPlayer,但这个组合主要是面向UWP以及WinUI的,在纯WPF里使用多多少少会遇到兼容性摩擦。如果是做轻量级工具,不想引入第三方依赖,也可以只用系统自带的SoundPlayer,不过SoundPlayer只支持WAV格式,播放MP3之类的压缩格式会很尴尬。所以在项目里我一般优先用NAudio,它既能做录音,又能做播放,一套API走完,避免同时维护两套音频处理逻辑。

1.2 环境准备:新建项目并引入NAudio

先说环境,Visual Studio 2022,创建WPF应用程序,目标框架选.NET 8或.NET Framework 4.7.2都可以。NAudio 2.2.1针对这两种框架都有兼容包,直接NuGet安装就行。

在包管理器控制台里执行:

Install-Package NAudio

或者在“管理NuGet程序包”界面搜索NAudio,选择最新稳定版安装。装完之后,你会看到依赖里多了NAudio.Core、NAudio.WinForms等子包,没关系,那是NAudio模块化之后的结果,我们直接用命名空间NAudio.WaveNAudio.Wave.SampleProviders就够了。

这里提醒一句:如果项目最终要部署到32位系统,或者要兼容某些老声卡驱动,建议把目标平台显式设置成x86或x64,不要让“Any CPU”乱选。因为NAudio底层会加载本机音频模块,位数不一致时偶尔会出现莫名其妙的初始化失败。我在一个工控机上就踩过这个坑,IntPtr指针在64位进程里访问32位驱动返回的句柄,直接抛异常,后来统一改成x64才稳定。

2. 录音:把麦克风声音落到WAV文件

2.1 录音链路上的两个核心类

NAudio里做录音,最核心的配合是WaveInEventWaveFileWriter

WaveInEvent负责从声卡采集PCM音频数据,它按照设定的格式持续产生数据块,每次采集完一批数据就会触发DataAvailable事件。这个事件发生在后台线程,所以绝对不能在事件里直接操作WPF控件。WaveFileWriter则负责把收到的原始音频数据按WAV容器格式写入文件,WAV文件本质上就是在PCM数据前面加一个44字节的头,这个库已经帮我封装好了。

整个录音过程可以理解成一根水管:声卡裸数据往WaveInEvent里流,DataAvailable事件是水龙头,WaveFileWriter是接水的桶。只要开着水龙头,数据就不断装进桶里,关掉水龙头(调用StopRecording),桶里就是完整的录音文件。

用这两个类组合,还有一个隐藏好处:录制过程中如果程序崩溃,已经写进文件里的数据仍然是一个合法WAV的骨架,只是没有文件头,可以用其他工具修复,不至于全部丢失。

2.2 一个能直接用的录音封装

我先贴一段实际项目里常用的录音类,放到WPF的普通类库里就能跑。

using NAudio.Wave; public class AudioRecorder : IDisposable { private WaveInEvent _capture; private WaveFileWriter _writer; public AudioRecorder(string filePath) { _capture = new WaveInEvent { WaveFormat = new WaveFormat(44100, 16, 1), BufferMilliseconds = 50 }; _writer = new WaveFileWriter(filePath, _capture.WaveFormat); _capture.DataAvailable += OnDataAvailable; _capture.RecordingStopped += OnRecordingStopped; } private void OnDataAvailable(object sender, WaveInEventArgs e) { // e.Buffer里就是麦克风采集到的原始PCM数据 _writer.Write(e.Buffer, 0, e.BytesRecorded); } private void OnRecordingStopped(object sender, StoppedEventArgs e) { _writer.Dispose(); _writer = null; _capture.Dispose(); _capture = null; } public void Start() { _capture.StartRecording(); } public void Stop() { if (_capture != null && _capture.CaptureState == CaptureState.Capturing) { _capture.StopRecording(); } } public void Dispose() { Stop(); _capture?.Dispose(); _writer?.Dispose(); } }

这里有个细节值得注意:WaveFormat(44100, 16, 1)的三个参数分别是采样率、位深、声道数,在绝大多数Windows声卡上都能正常工作。BufferMilliseconds = 50表示每个音频缓冲区的大小是50毫秒,数值越小,录音延迟越低,但CPU占用会升高,系统负载高时还容易爆音。50毫秒对语音录音来说是兼顾延迟和稳定性的起步值。

RecordingStopped事件里释放资源是比较稳妥的做法,因为StopRecording()是异步的,真正停止并完成所有事件回调需要一点时间。如果在调用StopRecording后立刻释放_writer,有概率出现数据还没写完就被释放的“半截文件”。

2.3 采样参数选择和文件大小估算

录音参数不是随便拍的,要结合应用场景来定。

  • 语音通话、语音指令:采样率8000Hz或16000Hz就够,位深16bit,单声道。
  • 普通语音记录、会议录音:44100Hz,16bit,单声道或双声道,音质接近CD,文件体积中等。
  • 需要做后期处理、频谱分析的音频:建议44100Hz或48000Hz,16bit以上,双声道。

文件大小可以直接算,公式很简单:

文件大小(字节) = 采样率 × (位深 / 8) × 声道数 × 录音秒数

比如用44100Hz、16bit、单声道录一分钟:

44100 × 2 × 1 × 60 = 5292000字节 ≈ 5.29MB

如果换成双声道,则翻倍到10.6MB。这也是为什么短语音用WAV问题不大,但长时间录音最好另存成MP3或AAC,NAudio里可以配合MediaFoundationEncoder或者引入LAME编码器做MP3压缩。核心流程还是先采集成PCM,再转码,不复杂,但需要单独写一段转换逻辑。

2.4 录制时的界面状态更新

因为DataAvailable事件在后台线程触发,若要更新UI上的录音时长、电平指示等状态,必须借助Dispatcher跳回UI线程。直接写控件会抛“调用线程无法访问此对象”的异常。

我习惯做一个定时刷新,不要在每次DataAvailable都调用Dispatcher。50毫秒一个缓冲,相当于每秒20次跨线程切换,太浪费。可以用System.Windows.Threading.DispatcherTimer,每500毫秒拉一次录音时长,更新界面。

private void OnTimerTick(object? sender, EventArgs e) { if (_recorder != null && _captureState) { RecordingTimeText.Text = DateTime.Now.Subtract(_startTime).ToString(@"hh\:mm\:ss"); } }

这样做的好处是界面刷新频率稳定,CPU占用很低,同时也能避免频繁Dispatcher调用导致的卡顿。

3. 播放:把录好的音频再放出来

3.1 用WaveOutEvent播放WAV/MP3

录音存成WAV之后,播放就简单多了。我推荐用WaveOutEvent配合AudioFileReader

AudioFileReader是NAudio里一个很实用的读取器,既能读WAV,也能读MP3,还会自动完成格式转换。WaveOutEvent负责把解码后的PCM数据送到声卡输出。它们两个配合,基本覆盖了日常所有本地音频播放需求。

using NAudio.Wave; public class AudioPlayer : IDisposable { private AudioFileReader _reader; private WaveOutEvent _output; public void Play(string filePath) { Stop(); _reader = new AudioFileReader(filePath); _output = new WaveOutEvent { DesiredLatency = 200 }; _output.Init(_reader); _output.Play(); } public void Stop() { _output?.Stop(); _output?.Dispose(); _reader?.Dispose(); _output = null; _reader = null; } public void Dispose() { Stop(); } }

这里的关键是_output_reader必须保存为类字段,不能声明成局部变量。因为WaveOutEvent底层依赖声卡的播放线程,一旦方法结束后对象被垃圾回收,音频马上会静默,甚至程序直接崩溃。我见过不少新手写完后点击播放没声音,十有八九就是因为这个生命周期问题。

3.2 暂停、继续和播放进度

WaveOutEvent提供了Pause()Play(),可以实现暂停和继续。暂停时播放位置会保留在AudioFileReader.CurrentTime里,继续时从当前位置继续。

进度条显示可以这样做:

public TimeSpan CurrentTime => _reader?.CurrentTime ?? TimeSpan.Zero; public TimeSpan TotalTime => _reader?.TotalTime ?? TimeSpan.Zero; public float Progress => (float)(_reader.CurrentTime.TotalSeconds / _reader.TotalTime.TotalSeconds);

界面上的ProgressBar用一个DispatcherTimer定期读取这两个属性即可。停止播放时,把进度归零。

有几个播放状态需要单独标记:_output.PlaybackState会返回StoppedPlayingPaused,可以用它判断当前是否在播放中,避免按钮状态混乱。另外,WaveOutEvent.PlaybackStopped事件在停止、播放结束、设备拔出等情况下都会触发,不要在这个事件里做太重的清理,容易和主动调用Stop产生重复释放。

3.3 把录音和播放接到按钮上

简单写个界面交互,几个按钮就够了。XAML里放三个按钮:开始录音停止录音播放录音

<Button Content="开始录音" Click="OnStartRecording" /> <Button Content="停止录音" Click="OnStopRecording" /> <Button Content="播放录音" Click="OnPlayRecording" /> <TextBlock x:Name="StateText" />

后端逻辑:

private AudioRecorder _recorder; private AudioPlayer _player; private readonly string _tempFile = "record.wav"; private void OnStartRecording(object sender, RoutedEventArgs e) { _recorder = new AudioRecorder(_tempFile); _recorder.Start(); StateText.Text = "录音中"; } private void OnStopRecording(object sender, RoutedEventArgs e) { _recorder.Stop(); StateText.Text = "录音完成"; } private void OnPlayRecording(object sender, RoutedEventArgs e) { _player = new AudioPlayer(); _player.Play(_tempFile); }

这套交互虽然简单,但已经能覆盖最基础的“录完放一下听效果”场景。实际产品里,建议把录音状态和播放状态拆成枚举,配合MVVM的绑定来驱动按钮可用性,避免出现录着音又去点播放这种并发操作。

4. 常见问题与排查记录

4.1 录音相关的高频问题

症状是点击开始录音后,界面不报错,但文件里一点声音都没有,或者播放出来是刺耳的杂音。

先检查设备选择。默认WaveInEvent用的是系统默认麦克风。如果电脑同时插了USB麦克风、耳机麦克风和内置麦克风,默认设备很可能不是你想要的。可以用WaveInEvent.DeviceCount枚举所有录音设备,把设备名列到下拉框里:

for (int i = 0; i < WaveInEvent.DeviceCount; i++) { var caps = WaveInEvent.GetCapabilities(i); Debug.WriteLine($"{i}: {caps.ProductName}"); }

然后创建WaveInEvent时指定DeviceNumber属性。

还有一种是系统设置里麦克风隐私权限被关了。Windows 10/11默认对桌面应用访问麦克风有隐私开关,位置在“设置-隐私-麦克风”。如果程序跑起来后系统提示麦克风被禁用,去这里把权限打开。

4.2 播放相关的高频问题

播放最常见的是点击播放后无声,但录音文件用其他播放器打开又是正常的。

先判断_reader是否还在,代码里是不是把读取器声明成了局部变量,导致播放前就被回收了。然后看声卡输出设备有没有选对,多声卡环境下WaveOutEvent默认输出到系统默认播放设备,如果默认设备是个虚拟声卡,真实音箱不出声也正常。

还有一种情况是录出来的WAV采样率太高,老声卡不支持直接播放。比如用48000Hz录音,有些旧声卡只支持44100Hz,播放时就会有“变调”或者没声音。解决方法是播放前让AudioFileReader自动转换采样率,或者录音时直接用44100Hz,兼容性最好。

4.3 录音播放同时用时的资源冲突

WPF应用里有时候需要边录音边播放提示音,比如对讲系统的“滴”声。这时要特别注意声卡的独占模式。

WaveInEventWaveOutEvent默认走共享模式,多数声卡支持录音和播放同时进行。但如果程序里有人设置了底层的独占WASAPI,或者其他软件(如某些语音聊天工具)抢占了设备,你这边再录音或播放就会失败,报DeviceInUse类型的异常。

遇到这种问题,排查顺序是:先确认没有其他软件占用麦克风;再检查代码里是否同时创建了多个WaveInEvent实例,如果有,先停掉旧的再启动新的;最后考虑用WasapiCapture替代WaveInEvent获得更好的兼容性。WasapiCapture是WASAPI回调,延迟更低,但代码逻辑略有差异。

5. 升级一点:把录音播放做成MVVM服务

5.1 定义服务接口,方便后续替换

项目如果用了MVVM框架,比如Prism或者CommunityToolkit.Mvvm,建议把录音播放封装成服务接口,而不是在ViewModel里直接new一个具体类。这样以后换底层实现、加音频处理逻辑都方便。

public interface IAudioRecordingService { void StartRecording(string filePath); void StopRecording(); event EventHandler<TimeSpan>? RecordingTimeChanged; } public interface IAudioPlaybackService { void Play(string filePath); void Pause(); void Stop(); event EventHandler<bool>? PlayStateChanged; }

这样ViewModel只依赖两个接口,具体是NAudio实现还是系统API实现,都跟界面层无关。

5.2 在ViewModel里调用录音播放

ViewModel里使用服务后,按钮命令变得非常干净。

public class VoiceViewModel { private readonly IAudioRecordingService _recordingService; private readonly IAudioPlaybackService _playbackService; public VoiceViewModel(IAudioRecordingService recordingService, IAudioPlaybackService playbackService) { _recordingService = recordingService; _playbackService = playbackService; } public ICommand StartRecordCommand => new RelayCommand(() => _recordingService.StartRecording("voice.wav")); public ICommand StopRecordCommand => new RelayCommand(_recordingService.StopRecording); public ICommand PlayCommand => new RelayCommand(() => _playbackService.Play("voice.wav")); }

录音服务在实现时,把DataAvailable里产生的事件时间抛出来,ViewModel订阅后更新界面。这样主界面和音频逻辑彻底解耦,后续要做音频波形可视化、音量仪表,直接在服务里加事件就行,不会污染视图层。

实际项目里我建议把_tempFile路径放到配置里,并且启动时检测磁盘空间,因为长时间录音会在几分钟内产生几十甚至上百MB的WAV文件,如果没做控制,用户容易录到一半把C盘塞满。这个坑在我第一次做语音采集功能时遇到过,后来一直记着:录音不是“按下就完事”,内存、磁盘、线程、设备状态都要盯着。

NAudio这套方案在Windows平台上已经足够成熟,录音、播放、暂停、进度控制都能覆盖。如果你后面要做更复杂的实时音频处理,比如降噪、回声消除、频谱显示,也可以在这条技术路线上继续延伸。希望这次分享能帮正在做WPF录音播放功能的朋友少走几步弯路。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 6:45:45

机器学习线性代数Python代码实战:环境配置与例程跑通指南

简介&#xff1a;《机器学习线性代数基础&#xff08;Python语言描述&#xff09;》张雨萌版配套代码&#xff0c;是为系统学习该书各章示例与习题量身整理的资源包。面向机器学习初学者、复习线性代数的数据科学爱好者&#xff0c;以及需要动手验证公式推导的读者。资源共53个…

作者头像 李华
网站建设 2026/9/7 6:44:57

从原理图到FOC算法:EP100伺服驱动器资料包实战解析

简介&#xff1a;EP100伺服系统完整开发资料包&#xff0c;面向电机控制、自动化及机器人领域的工程师和爱好者&#xff0c;涵盖从硬件原理图、PCB工程到嵌入式C代码的全链路设计。压缩包内共352个文件&#xff0c;大小41.9MB&#xff0c;主要包括h/c源代码文件、原理图与PCB设…

作者头像 李华
网站建设 2026/9/7 6:44:25

基于SpringBoot的泥人制作短视频管理系统设计与实现

摘要 随着短视频行业的快速发展&#xff0c;传统泥人制作技艺的传承与推广面临新的机遇与挑战。本文设计并实现了一个基于SpringBoot的泥人制作短视频管理系统&#xff0c;旨在为泥人制作爱好者、非遗传承人和文化机构提供一个集视频上传、分类管理、在线观看、互动评论于一体…

作者头像 李华
网站建设 2026/9/7 6:43:51

ZEMAX 2008光学设计入门:老版本价值、破解版风险与双胶合透镜实操

简介&#xff1a;ZEMAX2008破解版资源&#xff0c;面向光学设计工程师、科研人员及相关专业学生&#xff0c;旨在解决正版软件授权费用高、安装激活流程复杂等实际问题&#xff0c;可服务于镜头设计、光学系统优化、照明模拟与像差分析等典型场景。资源以RAR压缩包形式打包&…

作者头像 李华
网站建设 2026/9/7 6:42:50

基于SpringBoot的农商交流平台系统(源代码+文档+PPT+调试+讲解)

温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台…

作者头像 李华