音视频音频处理【免费下载链接】NAudioAudio and MIDI library for .NET项目地址https://gitcode.com/gh_mirrors/na/NAudio点击查看免费下载本文基于 NAudio 官方文档 Docs/EnumerateAcmDrivers.md 展开并结合 src/NAudio.WinMM/Compression 下的源码实现与 tests/NAudio.Windows.Tests/Acm/AcmDriverTests.cs 测试用例进行纵深解析。读完本文你将掌握什么是 ACM 编解码器、NAudio 中哪个类依赖 ACM 做格式转换、如何用AcmDriver.EnumerateAcmDrivers()三级遍历枚举出系统上所有驱动、标签与格式以及如何解读枚举输出包括 PCM 与非 PCM 格式的差异从而为WaveFormatConversionStream挑选出真实可用的目标WaveFormat。一、ACMMedia Foundation 之前的 Windows 音频压缩 APIACMAudio Compression Manager音频压缩管理器是 Windows 上一套处理压缩音频的老式 API历史早于 Media Foundation。它由msacm32.dll提供核心实现通过“驱动Driver”的形式把各种音频编解码器codec暴露给系统。从某种意义上讲这套体系在今天已经不那么重要了——大部分新开发的代码会优先使用 Media Foundation TransformsMFT或 DMO 来处理压缩音频。但实际项目中你仍会遇到这样的情况某些编解码器以 ACM 形式提供时反而更容易获取而在 Media Foundation 中未必能找到对应的 Transform。此时了解并善用 ACM 驱动枚举就是一项很有实用价值的排查手段。在 NAudio 中真正使用 ACM 编解码器的类主要是WaveFormatConversionStream位于 src/NAudio.WinMM/WaveFormatConversionStream.cs。当你构造它时需要同时提供一个源WaveFormat和一个目标WaveFormat二者之间的转换有两种典型方向从压缩音频到 PCM —— 这是解码Decoder从 PCM 到压缩音频 —— 这是编码Encoder。需要特别强调你不能随便挑两个WaveFormat定义就指望转换一定成功。ACM 只支持受驱动支持的转换你只能执行系统上真实存在的受支持变换supported transforms。这就是为什么提前枚举系统上安装的 ACM 驱动、看清每个驱动支持的格式具有不可替代的价值——它能直接告诉你该用什么样的WaveFormat才能成功调用某个编解码器。二、三级枚举的整体思路NAudio 将 ACM 驱动体系的枚举设计为清晰的三级结构对应三个核心类层级枚举入口返回类型含义第一级AcmDriver.EnumerateAcmDrivers()IEnumerableAcmDriver系统上安装的全部 ACM 驱动第二级driver.FormatTagsIEnumerableAcmFormatTag某个驱动支持的所有“格式标签”如 PCM、GSM、DviAdpcm 等编码类型第三级driver.GetFormats(formatTag)IEnumerableAcmFormat某个标签下所有具体的格式采样率、位深、声道数的组合对应源码中AcmDriver.cs 是入口类内部通过 P/Invoke 调用acmDriverEnum、acmFormatTagEnum、acmFormatEnum三个 Windows ACM API 完成遍历public static IEnumerableAcmDriver EnumerateAcmDrivers() { drivers new ListAcmDriver(); MmException.Try(AcmInterop.acmDriverEnum(new AcmInterop.AcmDriverEnumCallback(DriverEnumCallback), IntPtr.Zero, 0), acmDriverEnum); return drivers; }回调中每收到一个驱动句柄就包装成一个AcmDriver对象并加入列表private static bool DriverEnumCallback(IntPtr hAcmDriver, IntPtr dwInstance, AcmDriverDetailsSupportFlags flags) { drivers.Add(new AcmDriver(hAcmDriver)); return true; }这个过程初看有些绕但由此获得的信息对定位“到底该用哪个WaveFormat才能成功使用某个 codec”几乎是决定性的。三、完整枚举代码示例下面这段代码会遍历系统上所有 ACM 驱动并打印出它们各自的格式详情。它完整演示了上面提到的三级遍历是本文的核心实操样本来自 Docs/EnumerateAcmDrivers.mdforeach (var driver in AcmDriver.EnumerateAcmDrivers()) { StringBuilder builder new StringBuilder(); builder.AppendFormat(Long Name: {0}\r\n, driver.LongName); builder.AppendFormat(Short Name: {0}\r\n, driver.ShortName); builder.AppendFormat(Driver ID: {0}\r\n, driver.DriverId); driver.Open(); builder.AppendFormat(FormatTags:\r\n); foreach (AcmFormatTag formatTag in driver.FormatTags) { builder.AppendFormat(\r\n); builder.AppendFormat(Format Tag {0}: {1}\r\n, formatTag.FormatTagIndex, formatTag.FormatDescription); builder.AppendFormat( Standard Format Count: {0}\r\n, formatTag.StandardFormatsCount); builder.AppendFormat( Support Flags: {0}\r\n, formatTag.SupportFlags); builder.AppendFormat( Format Tag: {0}, Format Size: {1}\r\n, formatTag.FormatTag, formatTag.FormatSize); builder.AppendFormat( Formats:\r\n); foreach (AcmFormat format in driver.GetFormats(formatTag)) { builder.AppendFormat( \r\n); builder.AppendFormat( Format {0}: {1}\r\n, format.FormatIndex, format.FormatDescription); builder.AppendFormat( FormatTag: {0}, Support Flags: {1}\r\n, format.FormatTag, format.SupportFlags); builder.AppendFormat( WaveFormat: {0} {1}Hz Channels: {2} Bits: {3} Block Align: {4}, AverageBytesPerSecond: {5} ({6:0.0} kbps), Extra Size: {7}\r\n, format.WaveFormat.Encoding, format.WaveFormat.SampleRate, format.WaveFormat.Channels, format.WaveFormat.BitsPerSample, format.WaveFormat.BlockAlign, format.WaveFormat.AverageBytesPerSecond, (format.WaveFormat.AverageBytesPerSecond * 8) / 1000.0, format.WaveFormat.ExtraSize); if (format.WaveFormat is WaveFormatExtraData format.WaveFormat.ExtraSize 0) { WaveFormatExtraData wfed (WaveFormatExtraData)format.WaveFormat; builder.Append( Extra Bytes:\r\n ); for (int n 0; n format.WaveFormat.ExtraSize; n) { builder.AppendFormat({0:X2} , wfed.ExtraData[n]); } builder.Append(\r\n); } } } driver.Close(); Console.WriteLine(builder.ToString()); }对这段代码结合源码还有几个实操细节值得强调先Open()再访问FormatTags从 AcmDriver.cs 可以看到FormatTags属性在内部执行acmFormatTagEnum前会先检查driverHandle如果驱动尚未Open()会直接抛出InvalidOperationException(Driver must be opened first)。GetFormats(formatTag)也有同样的检查AcmDriver.cs。枚举后记得Close()Close()内部调用acmDriverClose并置空句柄而AcmDriver实现了IDisposableDispose()最终也会调用Close()见 AcmDriver.cs。更稳妥的写法是把driver包在using里。GetFormats内部会为WAVEFORMATEX分配 1024 字节缓冲区源码注释说明formatTag.FormatSize不可靠某些 codec 的MaxFormatSize也不可靠因此枚举格式时统一分配 1024 字节以保证容纳AcmDriver.cs。位速率换算AverageBytesPerSecond * 8 / 1000.0将字节/秒换算为 kbps例如 8000 字节/秒对应 64.0 kbps。四、解读输出以 GSM 6.10 为例枚举的输出通常非常冗长——如果你在系统上额外安装了一些编解码器输出规模还会进一步膨胀。下面是 GSM 6.10 编解码器输出的一个片段Long Name: Microsoft GSM 6.10 Audio CODEC Short Name: Microsoft GSM 6.10 Driver ID: 48141232 FormatTags: Format Tag 0: PCM Standard Format Count: 8 Support Flags: Codec Format Tag: Pcm, Format Size: 16 Formats: Format 0: 8.000 kHz, 8 Bit, Mono FormatTag: Pcm, Support Flags: Codec WaveFormat: Pcm 8000Hz Channels: 1 Bits: 8 Block Align: 1, AverageBytesPerSecond: 8000 (64.0 kbps), Extra Size: 0 Format 1: 8.000 kHz, 16 Bit, Mono FormatTag: Pcm, Support Flags: Codec WaveFormat: Pcm 8000Hz Channels: 1 Bits: 16 Block Align: 2, AverageBytesPerSecond: 16000 (128.0 kbps), Extra Size: 0 Format 2: 11.025 kHz, 8 Bit, Mono FormatTag: Pcm, Support Flags: Codec WaveFormat: Pcm 11025Hz Channels: 1 Bits: 8 Block Align: 1, AverageBytesPerSecond: 11025 (88.2 kbps), Extra Size: 0这个片段告诉我们几个要点Long Name/Short Name前者是驱动完整名称Microsoft GSM 6.10 Audio CODEC后者是简短名称Microsoft GSM 6.10。在 AcmDriverDetails.cs 中短名称缓冲区为 32 字符、长名称为 128 字符二者由acmDriverDetails填充。Driver ID驱动句柄IntPtr也是后续打开驱动、建立转换流的凭据。Format Tag与Format SizePcm标签的格式大小为 16 字节即标准WAVEFORMATEX头无扩展数据的尺寸。Block Align与AverageBytesPerSecond例如 8 kHz/16 位/单声道Block Align 为 2 字节每秒平均 16000 字节换算即 128.0 kbps。这些字段正是构造WaveFormat时需要用到的关键参数。Support Flags: Codec表示该驱动作为编解码器提供服务详见下文 Flags 一节。五、非 PCM 格式与 WaveFormatExtraData对于非 PCM 格式WaveFormat结构往往需要在标准 16 字节WAVEFORMATEX之后追加额外的编码参数Extra Bytes此时format.WaveFormat的实际类型是WaveFormatExtraDataExtraSize大于 0ExtraData数组中保存这些额外的字节。枚举代码中的最后一个分支正是负责把这段扩展数据以十六进制打印出来。下面是一个DviAdpcm即 IMA ADPCM 类格式的例子。可以看到该格式的WaveFormat结构需要两个额外的字节值分别是 0xF9 和 0x01 Format 1: 8.000 kHz, 4 Bit, Stereo FormatTag: DviAdpcm, Support Flags: Codec WaveFormat: DviAdpcm 8000Hz Channels: 2 Bits: 4 Block Align: 512, AverageBytesPerSecond: 8110 (64.9 kbps), Extra Size: 2 Extra Bytes: F9 01这条输出极具实战价值如果你打算用WaveFormatConversionStream把某段音频解码或编码为 DviAdpcm就必须构造出带相同 Extra Bytes0xF9、0x01的WaveFormatExtraData否则驱动无法正确理解你提交的格式转换可能直接失败。这也再次印证了文档的核心论断——枚举出来的信息对“构造正确的WaveFormat”是决定性的。六、核心 API 的源码级速查围绕枚举流程NAudio 在 src/NAudio.WinMM/Compression 下提供了如下关键类型理解它们的属性有助于你更精准地筛选驱动与格式AcmDriverAcmDriver.cs成员说明EnumerateAcmDrivers()静态枚举全部已安装 ACM 驱动底层调用acmDriverEnumFindByShortName(shortName)静态按短名称查找驱动找不到返回nullIsCodecInstalled(shortName)静态判断某个 codec 是否已安装按短名称匹配AddLocalDriver(driverFile)静态从.acm/DLL 文件加载本地驱动底层依次调用NativeLibrary.TryLoad与acmDriverAddRemoveLocalDriver(localDriver)静态卸载之前用AddLocalDriver添加的本地驱动ShowFormatChooseDialog(...)静态弹出系统 ACM 格式选择对话框ShortName/LongName驱动短/长名称来自acmDriverDetailsDriverId驱动 IDIntPtr句柄FormatTags该驱动的格式标签集合要求先Open()GetFormats(formatTag)某标签下的全部格式要求先Open()Open()/Close()打开/关闭驱动句柄对应acmDriverOpen/acmDriverCloseMaxFormatSize通过acmMetrics(MaxSizeFormat)查询 ACM 互操作所需的最大格式大小AcmFormatTagAcmFormatTag.cs属性说明FormatTagIndex标签序号FormatTag标签对应的WaveFormatEncoding如Pcm、DviAdpcmFormatSize该标签下格式结构的大小含扩展部分源码注释提示它并不可靠StandardFormatsCount该标签下的标准格式数量SupportFlags驱动的支持标志见下文枚举FormatDescription标签的人类可读描述缓冲区 48 字符见 AcmFormatTagDetails.csAcmFormatAcmFormat.cs属性说明FormatIndex格式序号FormatTag格式所属的WaveFormatEncodingSupportFlags该格式的支持标志WaveFormat构造出的完整WaveFormat对象内部从WAVEFORMATEX指针反序列化而来WaveFormatByteSizeWaveFormat结构的总字节数FormatDescription格式描述例如 8.000 kHz, 16 Bit, Mono缓冲区 128 字符见 AcmFormatDetails.csSupport Flags 枚举AcmDriverDetailsSupportFlags.csSupportFlags是一个[Flags]枚举可能同时包含多个位。核心取值如下标志值含义Codec0x00000001该驱动是编解码器Converter0x00000002该驱动是格式转换器Filter0x00000004该驱动是滤波器Hardware0x00000008支持硬件Async0x00000010支持异步操作Local0x40000000本地驱动Disabled0x80000000驱动当前被禁用筛选时例如只关心编解码能力可判断(flags AcmDriverDetailsSupportFlags.Codec) ! 0。七、配套便捷方法与典型应用除了三级枚举AcmDriver.cs 还提供两个高频使用的便捷静态方法它们同样以枚举为基础AcmDriver.IsCodecInstalled(shortName)遍历所有驱动、比对ShortName判断某个 codec 是否已安装。例如在测试 AcmDriverTests.cs 中IsCodecInstalled(MS-ADPCM)在标准 Windows 上应返回true而一个不存在的名字则返回false。AcmDriver.FindByShortName(shortName)返回第一个短名称匹配的驱动对象常用于拿到特定 codec 后直接Open()并枚举其格式。测试CanEnumerateFormats正是以AcmDriver.FindByShortName(MS-ADPCM)开头见 AcmDriverTests.cs。一个典型的实战链路是先FindByShortName或IsCodecInstalled确认目标 codec 存在 → 枚举其FormatTags与GetFormats找到合适的格式 → 用枚举得到的WaveFormat含必要的WaveFormatExtraData构造WaveFormatConversionStream完成编解码。八、线程安全msacm32 的进程级锁值得特别留意的是 AcmInterop.cs 类注释中记录的一个重要实现事实NAudio 曾观察到在多线程并发调用msacm32.dll时会出现访问冲突access violations详见源码中引用的 NAudio 历史 issue #355、#629。因此 NAudio 采取防御性措施——用一个进程级的可重入锁AcmInterop.AcmLock串行化所有 msacm32 的 P/Invoke 调用。这意味着驱动枚举、格式枚举等操作在执行期间会阻塞进程内其他 ACM 相关工作ACM 操作在进程内实际上是单线程化的AcmStream.cs 的类注释进一步建议对高吞吐场景优先考虑 Media Foundation 或基于 DMO 的解码/编码方案。这一点在写并发音频处理代码时非常关键——不要在多个线程中同时发起 ACM 调用也不要因为枚举操作短暂阻塞而误以为死锁。九、测试验证与结论NAudio 仓库的集成测试 AcmDriverTests.cs标注[Category(IntegrationTest)]需要在真实 Windows 环境运行覆盖了与本文直接相关的全部环节可作为你验证环境与理解的参照CanEnumerateDrivers验证EnumerateAcmDrivers()返回非空且每个驱动的DriverId非零、ShortName非空CanOpenAndCloseDriver对每个驱动执行Open()/Close()CanEnumerateFormatTags对每个驱动枚举FormatTags并输出标签详情CanEnumerateFormats用FindByShortName(MS-ADPCM)找到驱动后遍历其全部格式FindsStandardCodec/DoesntFindNonexistentCodec验证IsCodecInstalled的命中与不命中路径。总结来说虽然 ACM 是 Windows 上偏“古老”的音频压缩体系但在某些 codec 只有 ACM 版本可用的场景下掌握AcmDriver.EnumerateAcmDrivers()驱动的三级枚举就能准确回答“系统上到底有哪些编解码器、每个编解码器支持哪些精确的WaveFormat含扩展字节”从而为WaveFormatConversionStream的编解码调用构造出真正可用、可成功的格式参数。赞分享音视频音频处理【免费下载链接】NAudioAudio and MIDI library for .NET项目地址https://gitcode.com/gh_mirrors/na/NAudio点击查看免费下载相关推荐Fallout 1 CE音乐系统与ACM格式解码深度解析Fallout 1 CE音乐系统与ACM格式解码深度解析 引言后核战世界的音频复兴 在辐射Fallout系列的经典游戏中音频系统不仅是营造沉浸式后核战世游戏开发NodeGui 中 SelectionFlag 枚举详解驱动 QItemSelectionModel 选择行为的位标志体系NodeGui 中 SelectionFlag 枚举详解驱动 QItemSelectionModel 选择行为的位标志体系 SelectionFlag 是 N桌面应用跨平台上一篇OptiScaler跨GPU游戏超分辨率优化工具使用指南下一篇约定式提交Conventional Commits1.0.0-beta.1 规范详解以结构化提交信息驱动版本管理与 CHANGELOG 自动化创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考