C# MP3 声音捕获/录制组件






4.75/5 (44投票s)
一个 .NET 组件,可以从声卡捕获 WAVE 或 MP3 声音。使用 LAME 进行 MP3 压缩。

引言
令人惊讶的是,.NET Framework 3.5 中没有声音捕获组件。就连 WPF 和 Silverlight 2.0 的设计者们也如此专注于图形,以至于他们忘记了从用户麦克风录制声音的应用程序。据说下一版本的 Silverlight 将提供这样的功能。
然而,您通常希望将录制的声音存储为 MP3 文件(或将其作为 MP3 流发送)。由于 MP3 专利限制,这在法律上很复杂。出于同样的法律原因,我们可以假定 MP3 功能不会很快出现在 Microsoft 技术中(有 WMA 可用)。
这里有一个易于使用的 .NET 3.5 组件,为 Windows 应用程序提供声音捕获功能。它输出原始 PCM 样本或常规 WAV 文件。或者,您可以设置一个布尔属性来使用 LAME DLL 并实时进行 MP3 压缩。
本文使用了 Idel Cardoso 编写的 C# MP3 Compressor 库的一部分,该库又部分基于 Ianier Munoz 编写的 A low level audio player in C#。有关 LAME 项目的技术和版权信息,请参阅 此网站。
背景
我选择 **Managed DirectX (MDX) 1.1** 来捕获声音。MDX 项目目前已冻结,因为 Microsoft 已转向 **XNA Game Studio Express**(一个对于仅捕获少量声音而言相当不合适的解决方案)。MDX 是 **SlimDX** 开源项目的后继者,SlimDX 暴露了大致相似的接口,并将调用委托给原生的 **DirectSound** 库。
MDX 包含 .NET 程序集,它将调用委托给 2006 年的原生 DirectX DLL。我们期望在几乎所有 Windows 计算机上都安装与 2006 年接口向后兼容的 DirectX 版本(是的,也适用于 Vista)。
该组件通过 MDX 从声卡捕获声音,格式为原始 PCM。PCM 格式是声音样本值的简单序列。样本可以是 8 位(0..255)或 16 位(-32768..32767)。立体声音频是样本对的序列(左、右、左、右…)。PCM 是原始数据流的合适格式。流式传输意味着以小块传递数据,而数据的总量可能未知。原始 PCM 是 Mp3SoundCapture
组件最基本的输出类型。如果您将此格式直接流式传输到 WAV 文件,您将**无法播放它**,因为 WAV 文件还必须包含 RIFF 头部。
WAV (RIFF) 格式要求原始 PCM 数据前加上 RIFF 头部,该头部除了格式信息外,还包含文件中 PCM 数据总长度的信息。这就是为什么您实际上无法流式传输 RIFF 数据,因为流的总大小通常是未知的。WAV (RIFF) 格式是 Mp3SoundCapture
组件的第二种输出类型。您可以将此格式直接流式传输到 WAV 文件。
Mp3SoundCapture
组件的第三种输出类型是 MP3。比特率(kbit/s)决定了 MP3 声音的质量。除此之外,声音质量还取决于要压缩的 PCM 数据的格式。并非所有比特率和采样参数的组合都是允许的。您可以将 MP3 流引导到 MP3 文件。
Using the Code
设置
从您的应用程序中,添加对 Istrib.Sound.Mp3.dll 程序集的引用(请参阅 Istrib.Sound.Example.WinForms
示例)。MP3 压缩还需要 lame_enc.dll 库位于二进制目录中。Istrib.Sound.Mp3.dll 程序集引用了 Managed DirectX 程序集(位于 DirectX 子目录中)。DirectX 程序集也可以通过 Microsoft 网站上的 **DirectX End-User Runtimes** 可再发行包安装到 GAC 中(此处:2008 年 11 月版本)。
如果您在调试应用程序时遇到“Loader Lock”警告,请参阅此处获取解决方法。
Istrib.Sound.Mp3.dll 程序集包含一个组件:Mp3SoundCapture
。您可以将此组件视为一个声音录像机。在调用 Start(...)
和 Stop()
方法(“录像机按钮”)之前,您需要设置其属性。您向 Start
方法的每次调用提供一个可写流或文件路径。该组件的单个实例可以逐个捕获声音到多个流/文件中。
组件构造
您可以使用 Visual Studio Component Designer 将 Mp3SoundCapture
组件从 Visual Studio 工具箱拖放到组件表面,或手动创建该组件。
mp3SoundCapture = new Mp3SoundCapture();
构造完成后,该组件即可使用。默认输出为 MP3 128kbit/s,采样率为 22kHz,16 位,单声道。通过设置组件属性来指定采样参数和输出格式。
捕获设备(例如 麦克风)
您可以使用默认的 Windows 录音设备
mp3SoundCapture.CaptureDevice = SoundCaptureDevice.Default;
或者选择一个已安装的系统声音捕获设备
mp3SoundCapture.CaptureDevice = SoundCaptureDevice.AllAvailable.First();
输出格式
您需要设置 3 种输出类型之一
Mp3SoundCapture.Outputs.Mp3
- MP3 格式Mp3SoundCapture.Outputs.RawPcm
- 原始样本数据(无 RIFF 头部)Mp3SoundCapture.Outputs.Wav
- WAV 文件数据(包含 RIFF 头部)
mp3SoundCapture.OutputType = Mp3SoundCapture.Outputs.Mp3;
采样参数
对于 PCM 或 WAV 输出,您可以选择声卡支持的任何可用采样参数(PcmSoundFormat.StandardFormats
)
mp3SoundCapture.WaveFormat = PcmSoundFormat.StandardFormats.First();
… 或者如果您想硬编码
mp3SoundCapture.WaveFormat = PcmSoundFormat.Pcm22kHz16bitMono;
MP3 格式的采样参数限制为 Mp3SoundFormat.AllSourceFormats
返回的值。并非所有采样参数和比特率的组合都是允许的。如果您在采样参数之前选择比特率,那么您可以使用 Mp3BitRate.CompatibleSourceFormats
属性列出兼容的值。
mp3SoundCapture.WaveFormat = myMp3BitRate.CompatibleSourceFormats.First();
//Or: mp3SoundCapture.WaveFormat = Mp3SoundFormat.AllSourceFormats.First();
MP3 比特率
对于 MP3 输出格式,您指定一个可用的比特率。同样,并非所有比特率都可以与所有采样参数配对。如果您在比特率之前选择采样参数,那么您可以使用 PcmSoundFormat.GetCompatibleMp3BitRates()
扩展方法来枚举兼容的 MP3 比特率。
mp3SoundCapture.Mp3BitRate = myPcmSoundFormat.GetCompatibleMp3BitRates().First();
//Or mp3SoundCapture.Mp3BitRate = Mp3BitRate.AllValues.First();
… 或者如果您想硬编码
mp3SoundCapture.Mp3BitRate = Mp3BitRate.BitRate128;
音量标准化选项
当应用程序录制和存储许多声音片段时,通常需要调整它们的音量,使所有片段都处于相似的音量水平。Mp3SoundCapture
提供了 NormalizeVolume
属性供您执行此转换。将其设置为 true 会导致所有录制的声音片段都被标准化,即,最响亮的片段的音量将被调至最高可能水平,而所有其他片段将按比例调高。
mp3SoundCapture.NormalizeVolume = true;
请注意,标准化算法必须读取整个流以找到最响亮的位置,然后重写整个流以调整每个样本的音量。这意味着整个流必须在输出到输出之前被缓冲。Mp3SoundCapture
在标准化时使用临时文件来缓冲数据。如果应用了 MP3 压缩,则在标准化之后进行。当您录制了一个相当大的声音片段时,大部分处理发生在**调用 Stop()** 方法之后,而不是实时进行(就像 NormalizeVolume
为 false
时一样)。这可能需要一些时间。在这里,Mp3SoundCapture
提供了异步停止。
捕获
要开始捕获,只需调用 Start(Stream)
方法,传入一个已打开的可写流(您必须在捕获停止后自己关闭它——在使用异步停止时并不明显)。您也可以调用 Start(string)
方法,传入一个输出文件名。
要停止捕获,只需调用 Stop()
方法。
异步 Stop()
如上文所述,在标准化时,调用 Stop()
后可能需要一些时间才能将所有捕获的数据写入输出流。Mp3SoundCapture
提供了一个选项,可以立即离开 Capturing
状态,并将所有缓冲区处理委托给一个单独的线程。您可以在不等待上一个录制会话的最后字节的情况下开始下一个录制会话。默认情况下,异步行为是禁用的。要启用它,请设置
mp3SoundCapture.WaitOnStop = false;
请注意,您不能在调用 Start(Stream)
方法的输出缓冲区被关闭之前关闭它,直到 Mp3SoundCapture.Stopped
事件被触发。使用 Stopped
事件参数来获取已准备好关闭的流的引用,或者——如果您使用了 Start(string filePath)
——则获取已由 Mp3SoundCapture
关闭的文件路径。
private void mp3SoundCapture_Stopped(object sender, Mp3SoundCapture.StoppedEventArgs e)
{
//Now the e.OutputFileName file is ready and contains all captured data
dataAvailableLbl.Text = "Data available in " + e.OutputFileName;
dataAvailableLbl.Visible = true;
}
关注点
在某些开发环境配置中,您可能会在调试器下启动应用程序时遇到 “Loader Lock”错误(实际上是一个警告)。这是 Managed DirectX 中一个众所周知的设计问题。您可以在 Visual Studio 调试器设置中禁用此错误(大多数人这样做而没有可观察到的后果)。我更倾向于不这样做。相反,我找到了一种解决方法:如果引用 Istrib.Sound.Mp3.dll 的项目还显式引用 Managed DirectX 程序集(Microsoft.DirectX
和 Microsoft.DirectX.DirectSound
),那么就不会发出警告。否则,调试器会在每次将任何引用 Managed DirectX 库的程序集加载到应用程序域时显示警告。
然而,根据我的经验,**您不能使用 _Visual Studio Add Reference..._ 向导** 来添加 DirectX **GAC** 程序集的引用。当您引用 DirectX 程序集的**本地副本**(如示例中的 _DirectX_ 子目录)时,这不是问题。
GAC 问题的解决方法是通过文本编辑器手动编辑您的 _csproj_ 文件来添加对 GAC 程序集的引用。
<ItemGroup>
...
<Reference Include="Microsoft.DirectX">
<Name>Microsoft.DirectX</Name>
</Reference>
<Reference Include="Microsoft.DirectX.DirectSound">
<Name>Microsoft.DirectX.DirectSound</Name>
</Reference>
...
</ItemGroup>
**Visual Studio Add Reference...** 向导会生成相同的 XML,除了它包含了完整的程序集版本信息。这也应该有效…但它没有,至少在我的机器上是这样。
历史
- 2008 年 11 月 29 日:初次发布
- 2008 年 12 月 1 日:增加了更多 MP3 格式
- 2009 年 10 月 19 日:更新了源代码和演示项目