概述

ACM是Audio Compression Manager或Audio Codec Manager的缩写,是音频压缩管理器或音频编解码器管理器。

音频数据一般都具有较高的采样率,经过压缩的原始数据才具有实用价值,否则不仅要占用大量存储空间而且在播放或进行网络传输时效率也是非常低下的,所以音频数字压缩编码在多媒体应用中有着广泛而又重要的用途。

音频的编码压缩方式有许多种,这些不同的压缩方式有着不同的数据压缩比和还原音质,具体的编码格式和算法更是大相径庭。多数协议都比较复杂,普通程序难以实现其加、解压算法,而为多媒体提供了较强支持的Windows 98操作系统引入了ACM和VCM(Video Codec Manager)技术,用来管理系统中存在的所有的音频和视频编、解码器(Coder-Decoder,即CODECs,用来实现音频、视频数据编解码的驱动程序)。可以通过它们提供的编程接口调用系统中存在的现成的编解码器来实现音频数据的加、解压。本文所要介绍的就是ACM音频压缩接口的编程方法,所使用的编程工具为Microsoft Visual C++ 6.0。

实现思路

尽管一个CODEC在理论上能够用于压缩、解压缩任一种数据流,但还是设计有各种各样的CODECs 以实现更高的压缩比、更高的保真度或实时压缩性能来压缩某种特定的数据类型。例如,把获取很高的视频压缩数据压缩率的最好方法应用到音频数据时未必就能得到相同的效果。

压缩音频数据的主要原理是降低存储某一声音序列所需的数据量。少的数据量就意味着声音所占有的空间更少,就能够以更快的速度通过MODEM在网络上传递。如果数据以Windows系统所支持的某种通用格式压缩的话,就可不经手工解压缩而直接播放--系 统将使用它自己的CODECs解压缩数据并播放。Windows 98本身附带有几种标准的CODECs,如DSP Group,Inc. TrueSpeech CODEC等。因此我们写的任何应用于 Windows 98下的程序都可应用这些CODEC,具体系统中都存在有哪些CODECs可以在控制面版的"多媒体"选项的"设备"标签页中查到。

CODEC 支持从源音频格式到目标格式的转换,而在实际应用中, 可能某种CODEC 不支持直接将源音频格式转换成目标格式,比如我们通过麦克风向多媒体计算机录入了一些频率为11025Hz、8位数据、单声道的PCM(Pulse Code Modulation,脉冲编码调制)数据,如果选用系统的TrueSpeech CODEC进行处理,就会引起失败,因为这种CODEC只能处理频率为8KHz,16位单声道的数据。所以转换时要采取两步转换法,即先将源格式转换成一种中间格式,再将此中间格式转换成目标格式,因为线性PCM 编码最为简单,且为绝大多数CODEC 所支持,所以一般中间格式都选为线性PCM 格式的一种,比如就可以先将原始数据转换成TrueSpeech CODEC所支持的中间PCM格式,然后再将其通过TrueSpeech CODEC转换成最终的压缩格式;

程序的设计实现

  有关ACM的API函数定义在头文件 msacm.h中, 除了在工程中加入对此头文件的引用之外, 对ACM编程还必须包含头文件mmsystem.h和mmreg.h,这两个头文件定义了多媒体编程中最基本 的常量和数据结构。为了避免有些高版本ACM才提供的函数和功能在较低版本的ACM中上不可用,程序中应调用acmGetVersion函 数查询用户机器中ACM 的版本信息。

  虽然可以根据控制面版手工得到关于某种音频CODECs的信息,但在应用程序中也常常需要知道某种音频CODECs是否存在,并获取其编解码参数等信息,可以通过回调函数find_format_enum来枚举系统中的音频压缩格式:

BOOL CALLBACK find_format_enum(HACMDRIVERID hadid, LPACMFORMATDETAILS pafd, DWORD dwInstance, DWORD fdwSupport)
{FIND_DRIVER_INFO* pdi = (FIND_DRIVER_INFO*) dwInstance;if (pafd->dwFormatTag == (DWORD)pdi->wFormatTag) {pdi->hadid = hadid;return FALSE; //停止枚举}return TRUE; //继续枚举
}

  在该回调函数中用到的FIND_DRIVER_INFO是自定义的数据结构,其两个成员变量分别用来保存ACM驱动器号的句柄和要转换的数据格式:

typedef struct {HACMDRIVERID hadid;WORD wFormatTag;
} FIND_DRIVER_INFO;

   现在可以枚举出系统中当前所有的驱动程序。我们在程序中所调用的枚举函数使用回调函数来汇报每个设备的数据,这在Windows编程是一种很普遍的方 法。要获得有关某一驱动程序能力更多的详细信息,必须装载驱动程序并打开它,可通过调用 acmOpenDriver实现。一旦驱动程序打开,可请求枚举它所支持的wave数据格式。但这就存在一个问题:所有wave格式描述结构都基于 WAVEFORAMTEX,许多格式使用此结构的扩展形式来保存其特定的信息。如果我们想枚举所有格式,需要知道为此结构分配多少供驱动程序填写详细信息的空间。可以通过向acmMetrics函数传递ACM_METRIC_MAX_SIZE_FORMAT标 志得到所需的最大的结构的尺寸。打开驱动程序后要通过acmMetrics函数枚举到所支持的格式,该函数可以获取到许多ACM对象的有用信息。实现该过 程的主要代码如下:

BOOL CALLBACK find_driver_enum (HACMDRIVERID hadid, DWORD dwInstance, DWORD fdwSupport)
{……MMRESULT mmr = acmDriverOpen(&had, hadid, 0);//枚举所支持的格式……mmr = acmMetrics((HACMOBJ)had, ACM_METRIC_MAX_SIZE_FORMAT, &dwSize);if (dwSize < sizeof(WAVEFORMATEX)) dwSize = sizeof(WAVEFORMATEX);WAVEFORMATEX* pwf = (WAVEFORMATEX*) malloc(dwSize);……pwf->cbSize = LOWORD(dwSize) - sizeof(WAVEFORMATEX);pwf->wFormatTag = pdi->wFormatTag;ACMFORMATDETAILS fd;……fd.cbStruct = sizeof(fd);fd.pwfx = pwf;fd.cbwfx = dwSize;fd.dwFormatTag = pdi->wFormatTag;mmr=acmFormatEnum(had, &fd, find_format_enum, (DWORD)(VOID*)pdi, 0); //枚举格式……acmDriverClose(had, 0); //关闭驱动器……
}

   根据指定的格式要找到其所对应的ACM驱动器号可以用枚举所有音频CODECs的ACM API函数acmDriverEnum来实现,在acmDriverEnum() 的参数中指定了在前面描述过的回调函数find_driver_enum,可以 进 一 步查询每个CODEC的信息,最终可以获取到ACM驱动器号的句柄。实现此功能的回调函数名为find_driver,本文后面将会用到。

  在把原始Wave音频数据转换到中间PCM格式数据之前,需要做些前期准备工作,填充一些相关的结构信息,具体有:WAVEFORMATEX结构描述源格式、中间PCM格式、以及最终的压缩格式等。下面先填充一个用来描述源数据格式的WAVEFORMATEX结构:

WAVEFORMATEX wfSrc;
memset(&wfSrc, 0, sizeof(wfSrc));
wfSrc.cbSize = 0;
wfSrc.wFormatTag = WAVE_FORMAT_PCM; //PCM脉冲编码调制
wfSrc.nChannels = 1; //单声道
wfSrc.nSamplesPerSec = 11025; //11.025kHz
wfSrc.wBitsPerSample = 8; //8 bit
wfSrc.nBlockAlign = wfSrc.nChannels * wfSrc.wBitsPerSample / 8;
wfSrc.nAvgBytesPerSec = wfSrc.nSamplesPerSec * wfSrc.nBlockAlign;

   然后通过前面提到的回调函数find_driver来获取由wFormatTag指定的中间数据格式所对应的驱动程序的ACM驱动器号,在此设定的是由 WAVE_FORMAT_DSPGROUP_TRUESPEECH指定的有Windows 98系统自带的TrueSpeech CODEC:

WORD wFormatTag = WAVE_FORMAT_DSPGROUP_TRUESPEECH;
HACMDRIVERID hadid = find_driver(wFormatTag);

  选定了驱动程序,现在要为最终驱动程序将产生的压缩数据格式创建一个WAVEFORMATEX结构,并为驱动程序用于输入的中间PCM格式产生一个WAVEFORMATEX结构:

WAVEFORMATEX* pwfDrv = get_driver_format(hadid, wFormatTag); // 获得格式的详情

  在结构pwfDrv的成员变量wBitsPerSample里存放着驱动格式的位数,在nSamplesPerSec里存放着驱动格式的采样率。然后可以用非常类似的方法获取驱动程序所支持的PCM格式标签:

WAVEFORMATEX* pwfPCM = get_driver_format(hadid, WAVE_FORMAT_PCM);

  当以上所需信息都以获取到后就可以开始转换数据了。转换由被ACM称作流的对象来实现。我们可以打开流,将源格式、目标格式传递给它,要求它进行转换。先将其转换成中间PCM格式。

将Wave音频转换为CODEC所支持的PCM格式

   通过CODEC将源Wave音频转换成CODEC所支持的PCM格式,可以使用任何可以做PCM间转换的驱动器。另外还有一点很重要:我们打开转换流 时,要指明ACM_STREAMOPENF_NONREALTIME标志。若省略此标志,那么一些驱动程序(例如TrueSpeech CODEC)将会报告发生第512号"不可能发生的"错误。该错误指明所要求的转换不能实时进行,如果在试图播放数据的同时转换大量数据,就必须注意这 点。下面是该步转换过程的简要描述:

mmr = acmStreamOpen(&hstr,
NULL, //任意驱动器
&wfSrc, //源格式
pwfPCM, //目标格式
NULL, //无过滤
NULL, //无回调
0, //初始数据
ACM_STREAMOPENF_NONREALTIME);

  根据以字节计的平均速率计算出输出缓冲区的大小,并加上一机动位(bit)如果没有此额外的空间IMA_ADPCM驱动程序将不能转换。中间的转换结果将存放在pDst1Data中:

DWORD dwSrcBytes = dwSrcSamples * wfSrc.wBitsPerSample / 8;
DWORD dwDst1Samples = dwSrcSamples * pwfPCM->nSamplesPerSec / wfSrc.nSamplesPerSec;
DWORD dwDst1Bytes = dwDst1Samples * pwfPCM->wBitsPerSample / 8;
unsigned char * pDst1Data = new unsigned char[dwDst1Bytes];
……
ACMSTREAMHEADER strhdr; //填充转换信息
memset(&strhdr, 0, sizeof(strhdr));
strhdr.cbStruct = sizeof(strhdr);
strhdr.pbSrc = cpBuf; //指定要转换的源Wave音频数据为cpBuf中的数据
strhdr.cbSrcLength = dwSrcBytes;
strhdr.pbDst = pDst1Data;
strhdr.cbDstLength = dwDst1Bytes;
mmr = acmStreamPrepareHeader(hstr, &strhdr, 0);
mmr = acmStreamConvert(hstr, &strhdr, 0); //转换数据
……
acmStreamClose(hstr, 0);

   当流打开时,第二个参数为NULL,表示接受任何驱动程序进行转换。复杂的只是计算需要给输出数据分配多大的缓冲区。PCM格式间的转换不牵扯压缩和解 压缩,缓冲区大小直接就计算出来了。至于调用acmStreamPrepareHeader这个ACM API函数,是由于它可以为驱动程序安排好一切并允许驱动程序在转换前锁定内存。

  生成最终的压缩格式

  向最终压缩格式的转换过程与前面的PCM格式间转换非常相似,只不过此次转换我们提供了打开流时想要使用的驱动程序的句柄。实际上,此处仍可使用NULL,因为已预知此驱动程序存在,但提供句柄避免了系统浪费时间为我们查找此驱动程序:

mmr = acmStreamOpen(&hstr,
had, //驱动器句柄
pwfPCM, //源格式
pwfDrv, //目标格式
NULL, //无过滤
NULL, //无回调
0, //实例化数据
ACM_STREAMOPENF_NONREALTIME);

   另外,计算用于压缩数据的缓冲区的尺寸有点难办,需要凭猜测。WAVEFORMATEX结构的nAvgBytesPerSec 域表示回放期间读取字节的平均速率。我们可使用它来估计存储压缩的wave需要多大空间。一 些驱动程序给出的数据确实是平均的,而不是最差场合下的值,因此我选择多增加50%的空间。 此方法在实践中虽然有些浪费但很有效:

DWORD dwDst2Bytes = pwfDrv->nAvgBytesPerSec * dwDst1Samples / pwfPCM->nSamplesPerSec;
dwDst2Bytes = dwDst2Bytes * 3 / 2;
unsigned char * pDst2Data = new unsigned char [dwDst2Bytes];

   其中,无符号字符型指针pDst2Data用于存储压缩的最终Wave音频数据,其大小经上式估算后存到dwDst2Bytes中。一旦转换完成, ACMSTREAMHEADER的结构的cbDstLengthUsed 域指出缓冲区实际用了多少字节。可以通过它来计算出压缩比:

double result= (double) dwSrcBytes / (double) strhdr2.cbDstLengthUsed;

  当源信号为8K采样、16bits PCM编码、单声道、长度为1秒的Wave音频信号, 驱动程序采用Windows 98自带的TrueSpeech 音频CODEC,它能实现大约10:1的压缩,这样高的压缩率还是比较另人满意的。

  小结:

   本文以TrueSpeech CODEC为例对使用ACM音频压缩编程接口实现Wave音频压缩的过程作了介绍。如果有自已的压缩格式,也可创建并安装自已的CODEC,实现的方法与 之基本类似。在理解了上述编程思想的前提下,对代码稍加改动就可编写出相 应的解压程序。本程序在Windows 2000 Professional下,由Microsoft Visual C++ 6.0编译通过。

ACM接口

驱动程序

  • acmDriverAdd
  • acmDriverClose
  • ACMDRIVERDETAILS
  • acmDriverEnum
  • acmDriverEnumCallback
  • acmDriverID
  • acmDriverMessage
  • acmDriverOpen
  • acmDriverPriority
  • acmDriverProc
  • acmDriverRemove

筛选器

  • ACMFILTERCHOOSE
  • acmFilterChooseHookProc
  • ACMFILTERDETAILS
  • acmFilterEnum
  • acmFilterEnumCallback
  • ACMFILTERTAGDETAILS
  • acmFilterTagEnum
  • acmFilterTagEnumCallback

格式

  • ACMFORMATCHOOSE
  • acmFormatChooseHookProc
  • ACMFORMATDETAILS
  • acmFormatEnum
  • acmFormatEnumCallback
  • acmFormatSuggest
  • ACMFORMATTAGDETAILS
  • acmFormatTagEnum
  • acmFormatTagEnumCallback

消息

  • MM _ ACM _ FILTERCHOOSE
  • MM _ ACM _ FORMATCHOOSE

  • acmStreamClose
  • acmStreamConvert
  • acmStreamConvertCallback
  • ACMSTREAMHEADER
  • acmStreamMessage
  • acmStreamOpen
  • acmStreamPrepareHeader
  • acmStreamReset
  • acmStreamSize
  • acmStreamUnprepareHeader

其他

  • acmGetVersion
  • acmMetrics

参考:

基于VC的ACM音频编程接口压缩Wave音频(一) - 走看看

音频压缩管理器参考 - Win32 apps | Microsoft Docs

ACM-音频编解码器管理器相关推荐

  1. Unity的声音(音频)管理器

    public class MusicManager : MonoBehaviour {#region 单例private static MusicManager instance = null;pub ...

  2. win7系统怎样打开音频服务器,win7系统打开音频管理器的具体教程

    win7系统使用久了,好多网友反馈说win7系统打开音频管理器的问题,非常不方便.有什么办法可以永久解决win7系统打开音频管理器的问题,面对win7系统打开音频管理器的图文步骤非常简单,只需要1.点 ...

  3. Android之AudioManager(音频管理器)详解

    AudioManager简介: AudioManager类提供了访问音量和振铃器mode控制.使用Context.getSystemService(Context.AUDIO_SERVICE)来得到这 ...

  4. [翻译] SoundManager 音频管理器

    SoundManager 音频管理器 https://github.com/nicklockwood/SoundManager Purpose SoundManager is a simple cla ...

  5. WIN10插入耳机没声音,Realtek音频管理器打不开

    装完显卡之后电脑突然就没声音了,百度了好多方法也没解决,其中打开 Realtek音频管理器 的方法是比较靠谱的,C:\Program Files\Realtek\Audio\HDA,在这个目录下打开 ...

  6. 耳机插入电脑没反应 控制面板也找不到realtek音频管理器的解决方案

    运行框(win+R)输入msconfig,回车 将[立体音频组合器服务]前面的这个[√]去掉下,确定,可能需要重启计算机.我的是有提醒重启计算机.控制面板还是看不到找不到realtek音频管理器,但耳 ...

  7. unity全局总的音频管理器

    这篇博客介绍一个unity的总的音频管理器 可以有效避免音频错乱的问题 我们把它挂在场景中 可以使用跨场景保存的方法来实现场景跳转之后的存在 首先 这篇博客会用到我之前介绍的知识 我下面把链接先给大家 ...

  8. U3D游戏开发框架(四)——音频管理器

    一:目的 游戏音频的播放在任何游戏中都占据非常重要的地位,音频的播放可以分为两种,一种为游戏音乐,另一种为游戏音效.前者适用于较长的音乐,如游戏背景音乐.第二种适用于比较短的游戏音乐,如开枪瞬间的音效 ...

  9. win10系统,主机箱的前置耳麦插孔用不了,“设置——声音”麦克风或者耳机已拔出未修复,“输入设备”无插座信息,更新驱动也无效,控制面板——小图标里查不到realtek高清晰音频管理器——一招解决

    win10系统,有一天忽然固态硬盘坏了,拆了重装系统之后主机箱的前置耳麦插孔里,插了耳机只能听到声音,麦克风无效,微信语音电话打不出去了. 查了一圈,"设置--声音"显示:麦克风或 ...

最新文章

  1. ftp windows无法访问此文件夹请确保输入的文件名_企业实战|企业FTP搭建
  2. 当钢铁直男去应聘...... | 每日趣闻
  3. VC2010不能将参数从“CString”转换为“const char *”
  4. python为什么中文要encoding-python 中文编码问题如何解决?
  5. C语言代码规范(三)if语句
  6. mysql 建表时建立索引_mysql 分享建表和索引的几点规范
  7. 注意儿童补钙有误区,汤臣倍健牛初乳加钙咀嚼片要用好
  8. DSP定点与浮点计算
  9. SSLOJ 1338.逃亡路径
  10. 春眠不觉晓,Kubernetes知多少
  11. process.cwd()与__dirname的区别
  12. python核心编程第13章答案
  13. 数据库优化相关面试题
  14. [CEOI2017]Mousetrap
  15. 《生物信息学:导论与方法》--本体论、分子通路鉴定--听课笔记(十八)
  16. Chart.xkcd图表库
  17. 消息中间件第一讲:RocketMQ从入门到精通
  18. NBOOT分析-S3C244xInit.s(1)
  19. 可穿戴式柔性电子应变传感器基底材料
  20. python用三重引号_python三引号

热门文章

  1. NUMA与英特尔下一代Xeon处理器学习心得
  2. 在LINUX-DEBIAN系统下挂载新硬盘的方法
  3. 数据结构C语言顺序表入门简单题目你会了吗?
  4. 三分钟手写RPC调用 (三)
  5. c++bitset用法详解(超简单)——蒟蒻函数
  6. oracle 数据库如何建立索引 如何用索引?
  7. Java画UML类图
  8. Jackson JsonNode
  9. 杭电OJ 1181(C++)
  10. SQL Server修改表结构,添加约束