音频和视频数据是大多数多媒体应用程序向用户提供信息的主要方式,这些数据一般具有较高的采样速率,如果不经过压缩的话,保存它们需要消耗大量的存贮空间,在网络上进行传输的效率也很低,因此音频视频数字压缩编码在多媒体技术中占有很重要的地位。就音频数据而言,目前常用的压缩方法有很多种,不同的方法具有不同的压缩比和还原音质,编码的格式和算法也各不相同,其中某些压缩算法相当复杂,普通程序不可能去实现其编解码算法。所幸的是,与Windows3.x相比,Windows95/NT4.0为多媒体应用程序提供了更强的支持,引入了ACM(Audio Compression Manager,音频压缩管理器)和VCM(Video Compressio nManager,视频压缩管理器),它们负责管理系统中所有音频和视频编解码器(Coder-Decoder,简称CODEC,是实现音频视频数据编解码的驱动程序),应用程序可以通过ACM或VCM提供的编程接口调用这些系统中现成的编解码器来实现音频或视频数据的压缩和解压缩。95/NT4.0系统自带的音频CODECs支持一些早期的音频数据压缩标准,如ADPCM等,InternetExplorer4.0等应用程序包含的音频CODECs支持一些比较新的压缩标准,如MPEGLayer3等。在控制面板的多媒体组件中选择“高级”,打开“音频压缩的编码解码器”,就可列出系统中安装的所有音频CODECs。本文所要介绍的就是ACM音频压缩接口的编程方法,所用编程工具为VC++5.0。
获取CODECs的信息
----ACM的API函数定义在头文件msacm.h中,除此之外,对ACM编程还必须包含头文件mmsystem.h,mmreg.h,这两个头文件定义了多媒体编程中最基本的常量和数据结构。为了避免有些高版本ACM才提供的函数和功能在较低版本的ACM中上不可用,程序中应调用acmGetVersion函数查询用户机器中ACM的版本信息。
----前面提到,在控制面板中可以查看系统中CODECs的信息,而在应用程序中也常常需要知道某种音频CODECs是否存在,并获取其编解码参数等信息,这一点可以通过调用下面两个函数来实现。
---- MMRESULT mmr=acmMetrics(NULL, ACM_METRIC_COUNT_CODECS, &dwCodecs);
---- mmr = acmDriverEnum(CodecsEnumProc, 0, 0);
----acmMetrics()函数可以获取许多ACM对象的有用信息,例如向其中传递ACM_METRIC_COUNT_CODECS可以查询系统中安装的音频CODECs总数。函数acmDriverEnum()的功能是枚举所有的音频CODECs,在acmDriverEnum()的参数中指定回调函数CodecsEnumProc()可以进一步查询每个CODEC的信息。Windows编程中经常要用到回调函数,下面是枚举音频CODECs的一个回调函数的示例。
BOOL CALLBACK CodecsEnumProc(HACMDRIVERID
hadid, DWORD dwInstance, DWORD fdwSupport) {
DWORD dwSize = 0;
if (fdwSupport & ACMDRIVERDETAILS_SUPPORTF_CODEC)
printf("多格式转换\n");
ACMDRIVERDETAILS add;
acmdd.cbStruct = sizeof(acmdd);
MMRESULT mmr = acmDriverDetails(hadid, &acmdd, 0);
if (mmr) error_msg(mmr);
else {
printf(" 全称: %s\n", acmdd.szLongName);
printf(" 描述: %s\n", acmdd.szFeatures);
}
HACMDRIVER had = NULL;
mmr = acmDriverOpen(&had, hadid, 0); //打开驱动程序
if (mmr) error_msg(mmr);
else {
mmr = acmMetrics(had, ACM_METRIC_
MAX_SIZE_FORMAT, &dwSize);
WAVEFORMATEX* pwf = (WAVEFORMATEX*) malloc(dwSize);
memset(pwf, 0, dwSize);
pwf->cbSize = LOWORD(dwSize) - sizeof(WAVEFORMATEX);
pwf->wFormatTag = WAVE_FORMAT_UNKNOWN;
ACMFORMATDETAILS fd;
memset(&fd, 0, sizeof(fd));
fd.cbStruct = sizeof(fd); fd.pwfx = pwf; fd.cbwfx = dwSize;
fd.dwFormatTag = WAVE_FORMAT_UNKNOWN;
mmr = acmFormatEnum(had, &fd, FormatEnumProc, 0, 0);
if (mmr) error_msg(mmr);
free(pwf);
acmDriverClose(had, 0);
}
return TRUE;
}
----CodecsEnumProc()共有三个参数。第一个参数是驱动程序的ID值;第二个参数是实例数据,本文例子中未使用;第三个参数描述该驱动程序所支持的功能,它由一组标识进行或运算构成,例如,如果设置了标识ACMDRIVERDETAILS_SUPPORTF_CODEC,则说明该驱动程序可以将一种编码格式的音频信号转换成另一种编码格式。通过acmDriverDetails()函数可以获得对该驱动程序进一步的信息,如CODEC的名称、简单描述等。以上信息实际上是由ACM收集,并保存在ACM内部,所以查询以上信息时并未真正将驱动程序加载至内存。而要获得每一种驱动程序支持的音频格式信息,则必须将驱动程序加载至内存,这是通过acmDriverOpen()完成的,在退出CodecsEnumProc()前,还要用acmDriverClose()来关闭已打开的驱动程序。在使用音频格式枚举函数前,需要先分配一块缓冲区存置格式信息,缓冲区的大小可通过调用acmMetrics()查询ACM_METRIC_MAX_SIZE_FORMAT获得,格式信息中的音频格式标识设为WAVE_FORMAT_UNKNOWN。在音频格式枚举中同样使用了回调函数,此回调函数只是列出了该音频格式的名称和标识值。
BOOL CALLBACK FormatEnumProc
(HACMDRIVERID hadid, LPACMFORMATDETAILS
pafd, DWORD dwInstance, DWORD fdwSupport) {
printf("%4.4lXH, %s\n", pafd- >dwFormatTag, pafd- >szFormat);
return TRUE;
}
----上面介绍了浏览系统中所有音频CODECs及每种CODEC所支持的音频格式的方法,某些典型的应用程序可能需要列出系统中所有可以选用的CODECs,并由用户来选择使用哪一种CODEC进行压缩,此时就需要利用上面的编程方法来获取CODECs的信息。
音频数据的压缩
----下面说明使用某一CODEC实现音频压缩的过程,读者朋友只需稍加改动就可编写出相应的解压程序。假设源信号为8K采样、16bitsPCM编码、单声道、长度为1秒的音频信号。驱动程序采用Windows95自带的TrueSpeech音频CODEC,它能实现大约10:1的压缩。在此例中,TrueSpeechCODEC支持从源音频格式到目标格式的转换,而在实际应用中,可能某种CODEC不支持直接将源音频格式转换成目标格式,这时可以采取两步转换法,即先将源格式转换成一种中间格式,再将此中间格式转换成目标格式,因为线性PCM编码最为简单,且为绝大多数CODEC所支持,所以一般中间格式都选为线性PCM格式的一种。
----在进行压缩之前首先需要确定TrueSpeech驱动程序的ID值。为此需要用到acmDriverEnum()函数,对枚举到的每一个驱动程序,由acmDriverEnum()指定的回调函数将检查其支持的所有音频格式,若其中包括wFormatTag值为WAVE_FORMAT_DSPGROUP_TRUESPEECH的音频格式,则此驱动程序就是要寻找的TrueSpeechCODEC,它所支持的第一种WAVE_FORMAT_DSPGROUP_TRUESPEECH音频格式即为目标音频压缩格式。查询所需的CODEC及其支持的音频格式的方法见前一小节的介绍。
----根据查询的结果,设hadID为TrueSpeechCODEC的ID值,pwfDrv为指向目标WAVEFORMATEX结构的指针,接下来利用获得的ID值打开相应的驱动程序。
HACMDRIVER had = NULL;
mmr = acmDriverOpen(&had, hadID, 0);
if(mmr) { printf(" 打开驱动程序失败\n"); exit(1); }
----压缩和解压缩一样,都是将音频信号从一种音频格式转换成另一种格式,要完成这一过程,首先要打开转换流。在用acmStreamOpen打开转换流时,我们指定了ACM_STREAMOPENF_NONREALTIME标志,它表示转换无需实时进行。因为很多压缩算法的计算量是相当大的,实时完成几乎是不可能的,例如在本例中,如果不指定此标志,TrueSpeechCODEC就会返回“无法完成”的错误。
HACMSTREAM hstr = NULL;
DWORD dwSrcBytes = dwSrcSamples * wfSrc.wBitsPerSample / 8;
mmr = acmStreamOpen(&hstr,had, //驱动程序句柄
pwfSrc, //指向源音频格式的指针
pwfDrv, //指向目标音频格式的指针
NULL, //无过滤器
NULL, //无回调函数
0,ACM_STREAMOPENF_NONREALTIME);
----在真正进行转换之前,还必须准备转换流的信息头。下面一段代码中,先利用源数据的大小以及目标格式的平均数据率估算目标数据的缓存区大小,然后调用acmStreamPrepareHeader为转换准备信息头。
---- DWORD dwDstBytes=pwfDrv->nAvgBytesPerSec*dwSrcSamples/wfSrc.nSamplesPerSec;
---- dwDstBytes = dwDstBytes*3/2; // 计 算 压 缩 后 音 频 数 据 大 小, 并 依 此 适 当 增 加 输 出 缓 冲 区 的 大 小。
BYTE* pDstData = new BYTE [dwDstBytes];
ACMSTREAMHEADER shdr;
memset(&strhdr, 0, sizeof(shdr));
shdr.cbStruct = sizeof(shdr);
shdr.pbSrc = pSrcData; //源音频数据区
shdr.cbSrcLength = dwSrcBytes;
shdr.pbDst = pDstData; //压缩后音频数据缓冲区
shdr.cbDstLength = dwDstBytes;
mmr = acmStreamPrepareHeader(hstr, &shdr, 0);
----语音数据真正的压缩过程是由函数acmStreamConvert()完成的。在调用acmStreamConvert()时可以指定回调函数,以便在转换过程中显示进度信息等。在本例中,未指定回调函数,只是简单地等待压缩的结束。
----mmr=acmStreamConvert(hstr,&shdr,0);
----数据压缩完毕后,应用程序就可以把缓冲区中的数据写入目标文件中。
----最后,必须关闭转换流和驱动程序。
mmr=acmStreamClose(hstr,0);
mmr=acmDriverClose(had,0);
----本文介绍了利用ACM获取音频CODEC的信息以及实现音频压缩的一般方法和过程,对ACM编程感兴趣的读者可以进一步参考VC++5的联机帮助中关于ACM的信息。
延伸阅读
文章来源于领测软件测试网 https://www.ltesting.net/