使用CODECs压缩Wave音频.docx
《使用CODECs压缩Wave音频.docx》由会员分享,可在线阅读,更多相关《使用CODECs压缩Wave音频.docx(19页珍藏版)》请在冰豆网上搜索。
![使用CODECs压缩Wave音频.docx](https://file1.bdocx.com/fileroot1/2023-2/21/432bc628-a534-41cb-8c54-34c5e5eb12d2/432bc628-a534-41cb-8c54-34c5e5eb12d21.gif)
使用CODECs压缩Wave音频
使用CODECs压缩Wave音频
概要
微软的Win95和WinNT操作系统都包含有能够压缩解压缩Wave音频流的
CODECs。
将你的wave音频以压缩形式保存不但能够减少对存储空间的
需求,在网络上传送时也能减少数据传输的时间。
本文及其附带的实例代码告诉你怎样使用安装在Windows系统中的CODECs
来压缩和解压缩音频。
稍稍改变这些代码就可以用作解压缩经过压缩的
数据,执行数据格式转换。
所附实例代码是用MicrosoftVisualC++5.0
版本开发的,并在Win95和WinNT4.0操作系统上测试过。
简介
Win95及最近的WinNT都具有能过安装的CODECs处理压缩的wave格式的音
频和视频数据流的能力。
一个CODEC是一小段用于压缩(COmpress)及解压缩(DECompress)数
据流的代码(因此,得名CO-DEC)。
许多CODECs即能压缩又能解压缩。
而一些CODECs仅能用于解压缩,这样私有数据可以在系统上播放,但
数据格式不能在系统上创建。
尽管一个CODEC原则上能够用于压缩解压缩任一种数据流,还是设计有
各种各样的CODECs以实现以高的压缩比率,更好的保真度或实时压缩
性能来压缩某种数据类型。
例如,获取高的视频压缩数据压缩率的最
好方法应用于音频数据时未必就能得到相同的效果,反之也然。
本文着重于怎样在自己的代码中使用CODEC将音频数据以你的系统中CODECs
所支持的方式进行压缩。
压缩音频数据的一个主要原因是降低存储某
一声音序列所需数据量。
少的数据量意味着声音所占有的空间更少,
并且能够以更快的速度在MODEM和网络上传递。
如果数据以Windows系
统所支持的某一通用格式压缩的话,则可以不必手工解压缩就直接播放
--系统将使用它自己的CODECs解压缩数据并播放。
我的系统中有什么CODECs?
Win95和WinNT本身附带有几种标准的CODECs,也可由系统中所安装的应
用程序安装其他的CODECs。
例如,DSPGroup,Inc.TrueSpeechCODEC
随Win95发送,因此你写的任何应用于Win95的程序都可应用此CODEC
(假如用户没有在控制面板中删除它或禁止它的话)。
以后可能要安装
的CODEC的一个例子是微软网络(MSN)软件自已所用的音频数据。
所有安装的CODECs由音频压缩管理器(ACM)管理。
我们可以从一小程序
中查询ACM来查到安装了哪些CODECs,它们都支持什么格式。
你也可双
击控制面板中的多媒体选项,选择高级标签,就能看到系统中所安装的CODECs。
介绍应用ACM,得知它所管理每一个CODEC都可以做些什么的一个好方法
是写一个简单的查询ACM的命令行应用程序。
本文所附带的CAPS程序完
成的就是这个功能--让我们看看它的代码,我将给你一起分析此程序,
解释每一步完成的什么功能。
首先从调用ACM编程接口所需的包含的头文件开始:
#include
#include
#include//多媒体注册
#include//音频压缩管理器
#include
mmsystem.h头文件定了Windows支持大部分的多媒体功能,但不包含ACM
接口及任何厂商定义。
mmreg.h包含了对不同厂商设计的各种wave数据
类型的格式标签的定义。
它也包含了用于处理不同的wave数据类型的结
构(基于WAVEFORAMTEX)的定义。
msacm.h包含了ACM所需的API,标志
等等。
我们要做的第一件事就是执行一些常见的ACM查询来判断版本号,获取诸
如它当前管理了多少个驱动程序的的信息。
下面是查询ACM的部分代码:
//取得ACM版本号
DWORDdwACMVer=acmGetVersion();
printf("ACMversion%u.%.02ubuild%u",
HIWORD(dwACMVer)>>8,
HIWORD(dwACMVer)&0x00FF,
LOWORD(dwACMVer));
if(LOWORD(dwACMVer)==0)printf("(Retail)");
printf("\n");
//显示一些ACM的信息
printf("ACMmetrics:
\n");
DWORDdwCodecs=0;
MMRESULTmmr=acmMetrics(NULL,ACM_METRIC_COUNT_CODECS,&dwCodecs);
if(mmr){
show_error(mmr);
}
else{
printf("%lucodecsinstalled\n",dwCodecs);
}
CAPS实例查询了更多的ACM信息。
你可以仔细查看它的代码,运行程序得知结果。
对ACM有了简单了解后,现在可以要求它枚举出系统中当前所有的驱动
程序。
我们在程序中所调用的枚举函数使用回调函数来汇报每个设备
的数据,这在Windows编程是一种很普遍的方法。
下面的调用就是枚举
当前ACM所管理的所有设备:
//枚举所有允许的驱动程序
printf("Enableddrivers:
\n");
mmr=acmDriverEnum(DriverEnumProc,0,0);
if(mmr)show_error(mmr);
如同其它多媒体函数,许多ACM函数调用返回一MMRESULT值,指出了可
能发生的错误。
此值为0表示函数成功执行。
现在,让我们看看枚举回
调函数DriverEnumProc,它由系统中的每一个驱动程序调用:
BOOLCALLBACKDriverEnumProc(HACMDRIVERIDhadid,DWORDdwInstance,DWORDfdwSupport)
{
printf("id:
%8.8lxH",hadid);
printf("supports:
\n");
if(fdwSupport&ACMDRIVERDETAILS_SUPPORTF_ASYNC)printf("asyncconversions\n");
if(fdwSupport&ACMDRIVERDETAILS_SUPPORTF_CODEC)printf("differentformatconversions\n");
if(fdwSupport&ACMDRIVERDETAILS_SUPPORTF_CONVERTER)printf("sameformatconversions\n");
if(fdwSupport&ACMDRIVERDETAILS_SUPPORTF_FILTER)printf("filtering\n");
//获得一些具体信息
ACMDRIVERDETAILSdd;
dd.cbStruct=sizeof(dd);
MMRESULTmmr=acmDriverDetails(hadid,&dd,0);
if(mmr){
printf("");show_error(mmr);
}
else{
printf("Shortname:
%s\n",dd.szShortName);
printf("Longname:
%s\n",dd.szLongName);
printf("Copyright:
%s\n",dd.szCopyright);
printf("Licensing:
%s\n",dd.szLicensing);
printf("Features:
%s\n",dd.szFeatures);
printf("Supports%uformats\n",dd.cFormatTags);
printf("Supports%ufilterformats\n",dd.cFilterTags);
}
//打开驱动程序
HACMDRIVERhad=NULL;
mmr=acmDriverOpen(&had,hadid,0);
if(mmr){
printf("");show_error(mmr);
}
else{
DWORDdwSize=0;
mmr=acmMetrics(had,ACM_METRIC_MAX_SIZE_FORMAT,&dwSize);
if(dwSizecbSize=LOWORD(dwSize)-sizeof(WAVEFORMATEX);
pwf->wFormatTag=WAVE_FORMAT_UNKNOWN;
ACMFORMATDETAILSfd;
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){
printf("");
show_error(mmr);
}
free(pwf);
acmDriverClose(had,0);
}
returnTRUE;//继续枚举
}
驱动程序向回调函数传递了描述驱动程序所支持类型的一组标志。
一些驱动程序可以异步操作,而另一些驱动程序则不能。
一些驱动程序能够将一种wave数据格式转换成另一种格式(称作CODECs),而另一些驱动程序仅能完成过滤操作,其输入输出格式是一样的。
注意ACM维护着这类数据及驱动程序的名字,版权信息等等,这样我们可以不必装载或打开指定的驱动程序就可以得到这些数据。
这样很方便,譬如当需将数据放在列表框中由用户选择时。
要获得有关某一驱动程序能力更多的详细信息,必须装载驱动程序并打开它,可通过调用acmOpenDriver实现。
一旦驱动程序打开,可请求枚举它所支持的wave数据格式。
同时有一个小问题--尽管所有wave格式描述结构基于WAVEFORAMTEX,许多格式使用此结构的扩展形式来保存其特定的信息。
如果我们想枚举所有格式,需要知道为此结构分配多少供驱动程序填写详细信息的空间。
可通过向acmMetrics函数传递ACM_METRIC_MAX_SIZE_FORMAT标志得到所需的最大的结构的尺寸。
如果你读过上面的代码,你会发现我只是简单的将分配的空间强制转换为WAVEFORMATEX指针。
我只对通用信息而不是任一特定类型的数据感兴趣,因此这个指针符合我的要求。
为结构分配了空间后,现在我可以acmFormatEnum来枚举所支持的格式。
这次又用到一个回调函数来取得枚举出的格式的相关数据:
BOOLCALLBACKFormatEnumProc(HACMDRIVERIDhadid,LPACMFORMATDETAILSpafd,
DWORDdwInstance,DWORDfdwSupport)
{
printf("%4.4lXH,%s\n",pafd->dwFormatTag,pafd->szFormat);
returnTRUE;//继续枚举
}
如你所看到的,这是一次尝试并仅打印出格式的某些信息。
这样,通过上面的代码,你能够查询ACM所有的驱动程序,查找每一
个驱动程序所支持的格式。
我建议你现在运行CAPS程序,看看你的
系统上安装了些什么。
使用特定的CODEC
好了,我们已得知你的系统上安装了什么CODECs--现在来看看怎样查
找某一特定的CODEC并使用它压缩音频数据。
让我们看看CONV实例,
它使用一种有效的CODEC压缩一个简单的wave数据包。
为了使代码更
趋简单,我以控制台应用程序的形式的实现它,也没有尝试去播放压
缩过的数据或将其存入文件。
这个实例的代码仅向你展示怎样找到你所
需要的驱动程序并使用它将数据转换为压缩格式。
剩下的就靠你了。
两步实现压缩
在理想的情况下,压缩一些数据可能只不过是向系统说:
“这有一些数据,
请压缩成这种格式。
”不幸的是,Windows编程与理想相去甚远,象通常一
样,我们得自已做许多琐碎的工作。
要解决的第一个也是最重要的问题是给
定的CODEC可能不能压缩你所使用的数据格式。
例如,我们录入了一些11025Hz,8位,
单声道的PCM数据(或许是用户向麦克风说的话),此种格式几乎所有的
多媒体PC都能录制。
我们可能要将数据通过MODEM传递,因此我们想尽可能
的压缩数据,使数据量减少。
我们选择了TrucSpeechCODEC,它安装在
Windows中,能够获得大约10:
1的压缩率。
我们所要碰到的问题就是
TrueSpeechCODEC不能处理11025Hz,8位,单声道的PCM数据。
它只能处
理8000Hz,16位,单声道的数据(某些情况下是8位)。
因此我们必须先将
源数据转换为TrueSpeechCODEC所支持的中间PCM格式,然后在使用它将中
间数据转换为最终所需的格式。
可使用随Windows分发的某种不同的CODEC将一种PCM格式转换为另一种格式,
因此你需使用某种CODEC将数据转换为其它CODEC能够处理的格式。
我们已知道
怎样去枚举CODECs及其所支持的格式,因此这样做是可以实现的。
但还有一个问题,我在实例代码中忽略了,留给你们解决。
如果某一CODEC能
够创建我们所想要的压缩格式,但支持几种不同的输入格式,我们怎样选择
最佳的中间格式呢?
按照Nigel的准则,那就是“总是做最少量的工作”,我
选择使用CODEC所支持的枚举出的第一种PCM格式。
由于很容易实现,可能会导
致数据失真。
假设我们要使用的某一CODEC有一些近乎无失真的压缩算法,能
够接收8位或16位的11025Hz或22050Hz的PCM数据。
我们要转换以441000Hz,
16位立体声录制的高保真的样本。
我们试图降低数据量,而不在乎损失质量。
如果我们枚举此CODEC所支持的格式,第一个得到的可能是11025Hz,8位单声
道的格式。
我们先将数据转换为此格式,然后进行压缩,这其间肯定要损失一
些质量,因为这种中间格式不够好。
如果使用16位22050Hz的话会好一些。
已
告诉过你这种缺憾啦,让我们瞧瞧CONV实例,看它是怎样工作的。
CONV实例程序
CONV实例分四个阶段:
它创建一些wave格式数据的样本,找到一个合适的CODEC,
将数据转换为此CODEC可处理的中间格式,最后将数据转换成所需的格式。
为了
简单其间,源数据用程序创建而不是录入或是从wave文件中读取:
//首先我们创建一个可能是刚刚才录制的wave其格式为11.025kHz,
//8位单声道PCM,这是一个所有机器上都可用的录入格式,我们的例
//子是1秒长的1kHz的正弦波wave,刚好1000个周期
WAVEFORMATEXwfSrc;
memset(&wfSrc,0,sizeof(wfSrc));
wfSrc.cbSize=0;
wfSrc.wFormatTag=WAVE_FORMAT_PCM;//PCM
wfSrc.nChannels=1;//Mono
wfSrc.nSamplesPerSec=11025;//11.025kHz
wfSrc.wBitsPerSample=8;//8bit
wfSrc.nBlockAlign=wfSrc.nChannels*wfSrc.wBitsPerSample/8;
wfSrc.nAvgBytesPerSec=wfSrc.nSamplesPerSec*wfSrc.nBlockAlign;
DWORDdwSrcSamples=wfSrc.nSamplesPerSec;
BYTE*pSrcData=newBYTE[dwSrcSamples];//1秒种的长度
BYTE*pData=pSrcData;
doublef=1000.0;
doublepi=4.0*atan(1.0);
doublew=2.0*pi*f;
for(DWORDdw=0;dw
上面的代码创建了一个WAVEFORMATEX结构用来描述源数据格式,并用简单的
数学方法生成了1
秒钟长的11.025kHz,8位单声道的PCM的wave数据。
下一步就是选择要将数据转换成什么格式及选定一个合适的CODEC。
WORDwFormatTag=WAVE_FORMAT_DSPGROUP_TRUESPEECH;
//现在我们选定一个支持目标格式标签的CODEC
HACMDRIVERIDhadid=find_driver(wFormatTag);
if(hadid==NULL){
printf("Nodriverfound\n");
exit
(1);
}
printf("Driverfound(hadid:
%4.4lXH)\n",hadid);
find_driver函数枚举所有的驱动程序直到找到一个支持给定标签值的驱动程序(本例为
WAVE_FORMAT_DSPGROUP_TRUESPEECH)。
我没有在此给出代码是因为它与前面的枚举代
码非常相象。
随后你可以查看它是怎样工作的。
选定了驱动程序,现在要为最终驱动程序将产生的压缩数据格式创建一个WAVEFORMATEX
结构,并为驱动程序用于输入的中间PCM格式产生一个WAVEFORMATEX结构。
//获得格式的详情
//注意:
这只是一个给定格式签的第一种或是最可能的格式
WAVEFORMATEX*pwfDrv=get_driver_format(hadid,wFormatTag);
if(pwfDrv==NULL){
printf("Errorgettingformatinfo\n");
exit
(1);
}
printf("Driverformat:
%ubits,%lusamplespersecond\n",
pwfDrv->wBitsPerSample,pwfDrv->nSamplesPerSec);
//获取驱动程序所支持的PCM格式标签
//注意:
我们只是选取第一支持的PCM格式但不一定是最好的选择
WAVEFORMATEX*pwfPCM=get_driver_format(hadid,WAVE_FORMAT_PCM);
if(pwfPCM==NULL){
printf("ErrorgettingPCMformatinfo\n");
exit
(1);
}
printf("PCMformat:
%ubits,%lusamplespersecond\n",
pwfPCM->wBitsPerSample,pwfPCM->nSamplesPerSec);
有点重复了,要注意的是get_driver_format函数仅仅枚举出第一种匹配的格式--也许不能获得
可能的最好的质量。
现在我们有了WAVEFORMATEX结构描述源格式,中间PCM格式,以及最终的压缩格式。
可以开始转
换数据了。
转换由被ACM称作流的对象来实现。
我们可以打开流,将源格式、目标格式传递给它,
要求它进行转换。
在此要注意如果CODEC的算法复杂的话同步转换是很耗时的。
一些CODEC可以异步工作,通过向
窗口发送一个消息,或调用一个回调函数,或设置一个事件告知你转换进程。
下面的代码是以
最少麻烦准则完成任务的--你必须等待它直到完成。
还有另外一点,很重要。
如你所知,我们打
开转换流,指明ACM_STREAMOPENF_NONREALTIME标志。
这非常重要。
若省略此标志,那么一些
驱动程序(例如TrueSpeech驱动程序)将会报告错误512(意思是不可能)。
此错误告诉你所要
求的转换不能实时进行。
我的实例代码中没有这个问题,但如果你试图在播放数据的同时转换
大量数据,就要注意这点了。
让我们看看第一步的转换,它完成的是将源数据转换为中间格式:
/////////////////////////////////////////////////////////////////////////////
//将源wave转换为CODEC所支持的PCM格式
//我们使用任一种能实现PCM格式间转换驱动程序
HACMSTREAMhstr=NULL;
mmr=acmStreamOpen(&hstr,
NULL,//任一驱动程序
&wfSrc,//源格式
pwfPCM,//目标格式
NULL,//无过滤
NULL,//没回调
0,//实例数据(未使用)
ACM_STREAMOPENF_NONREALTIME);//标志
if(mmr){
printf("FailedtoopenastreamtodoPCMtoPCMconversion\n");
exit
(1);
}
//为转换结果开辟一个缓冲区
DWORDdwSrcBytes=dwSrcSamples*wfSrc.wBitsPerSample/8;
DWORDdwDst1Samples=dwSrcSamples*pwfPCM->nSamplesPerSec/wfSrc.nSamplesPerSec;
DWORDdwDst1Bytes=dwDst1Samples*pwfPCM->wBitsPerSample/8;
BYTE*pDst1Data=newBYTE[dwDst1Bytes];//填写转换信息
ACMSTREAMHEADERstrhdr;
memset(&strhdr,0,sizeof(strhdr));
strhdr.cbStruct=sizeof(strhdr);
strhdr.pbSrc=pSrcData;//要转换的源数据
strhdr.cbSrcLength=dwSrcBytes;
strhdr.pbDst=pDst1Data;
strhdr.cbDstLength=dwDst1Bytes;//准备好头
mmr=acmStreamPrepareHeader(hstr,&strhdr,0);//转换数据
printf("ConvertingtointermediatePCMformat...\n");
mmr=acmStreamConvert(hstr,&strhdr,0);
if(mmr){
printf("FailedtodoPCMtoPCMconversion\n");
exit
(1);
}
printf("Conver