Using Codecs To Compress Wave Audio
Using Codecs To Compress Wave Audio
Using Codecs To Compress Wave Audio
Abstract
Windows 95 and Windows NT both include CO !Cs which can compress and decompress wa"e audio streams# $a"ing your wa"e audio data in compressed %orm can help with data storage re&uirements and reduce data transmission times when audio is sent o"er a networ'# This article and its accompanying sample code shows how to compress wa"e audio pac'ets using any o% CO !Cs installed on a Windows system# By altering the code "ery slightly it can also be used to decompress compressed data or per%orm data %ormat con"ersions# The sample code was de"eloped using (isual C)) "ersion 5#* and tested under Windows 95 and Windows NT 4#*
Introduction
Windows 95 and more recently Windows NT both include the ability to handle compressed wa"e%orm audio and "ideo data streams using installable CO !Cs# A CO !C is a small piece o% code used to COmpress or !Compress a data stream +hence CO, !C-# The data can represent anything such as audio or "ideo# .ost CO !Cs handle both compression and decompression but some are designed only to decompress so that proprietary data can be played on a system but the data %ormat cannot be created on that system# Although a CO !C can be used in principal to compress or decompress any stream o% data/ "arious CO !Cs ha"e been designed to compress certain data types with either higher compression ratios/ better %idelity or real,time per%ormance# 0or e1ample/ the best way to get a high degree o% "ideo data compression may not gi"e ade&uate results when applied to audio data and "ise "ersa# This article %ocuses on how to use a CO !C %rom your own code to compress audio data into one o% the %ormats supported by the CO !Cs on your system# The primary reason %or compressing audio data is to reduce the "olume o% data re&uired to store a sound se&uence# $maller data "olumes mean less dis' space is occupied by the sounds and also that they can be transmitted %aster o"er a modem or networ' lin'# 2% the data is compressed into one o% the common %ormats supported by Windows systems then it can be played bac' directly without the need to decompress it manually 3 the system will use its own CO !Cs to decompress the data %or playbac'#
6age 5
<windows.h> <mmsystem.h> <mmreg.h> // Multimedia registration <msacm.h> // Audio Compression Manager <stdio.h>
The mmsystem#h header de%ines most o% the multimedia support %or Windows but not the AC. A62 set or any o% the manu%acturer speci%ic de%ines# .mreg#h contains de%initions o% wa"e %ormat tags %or "arious wa"e data types as designed by di%%erent manu%acturers# 2t also contains de%initions o% structures +based on WA(!0O=.AT!>- that are used to manipulate the di%%erent wa"e data types# The msacm#h %ile contains the A62s/ %lags and so on %or the Audio Compression .anager +AC.-# The %irst thing we can do is per%orm some general &ueries o% the AC. to get its "ersion number and in%ormation such as how many dri"ers it is currently managing# ?ere@s part o% the code that &ueries the AC.<
// get the ACM version DWO D dwACM!er " acm#et!ersion$%& print'$(ACM version )u.).*+u ,uild )u(./WO D$dwACM!er% >> 0./WO D$dwACM!er% 1 *2**334OWO D$dwACM!er%%& i' $4OWO D$dwACM!er% "" *% print'$( $ etail%(%& print'$(5n(%& // show some ACM metrics print'$(ACM metrics65n(%& DWO D dwCodecs " *&
6age A
MM 7894: mmr " acmMetrics$;944- ACM<M7: /C<CO9;:<COD7C8- 1dwCodecs%& i' $mmr% = show<error$mmr%& > else = print'$()lu codecs installed5n(- dwCodecs%& > The CA6$ sample &ueries the AC. %or a %ew more metrics# 9ou can loo' at the code in detail and run the sample to see the results %or yoursel%# ?a"ing loo'ed at the AC./ we can now as it to enumerate all o% the dri"ers currently in the system# As is common practice in Windows programming the enumeration %unction we call uses a callbac' %unction in our code to report data %or each enumerated de"ice# ?ere8s the call that begins the enumeration o% all the de"ices currently managed by the AC.<
// enumerate the set o' ena,led drivers print'$(7na,led drivers65n(%& mmr " acmDriver7num$Driver7num?roc- *- *%& i' $mmr% show<error$mmr%& ;i'e many other multimedia %unctions/ most o% the AC. %unction calls return an ..=!$B;T "alue which indicates any error that might occur# A Cero "alue indicates that the %unction call completed success%ully# Now let8s see the enumeration callbac' %unction DriverEnumProc which is called %or each dri"er in the system<
@OO4 CA44@ACA Driver7num?roc$.ACMD /!7 /D hadid- DWO D dw/nstance- DWO D 'dw8upport% = print'$( id6 )0.0l2.(- hadid%& print'$( supports65n(%& i' $'dw8upport 1 ACMD /!7 D7:A/48<89??O :3<A8B;C% print'$( async conversions5n(%& i' $'dw8upport 1 ACMD /!7 D7:A/48<89??O :3<COD7C% print'$( di''erent 'ormat conversions5n(%& i' $'dw8upport 1 ACMD /!7 D7:A/48<89??O :3<CO;!7 :7 % print'$( same 'ormat conversions5n(%& i' $'dw8upport 1 ACMD /!7 D7:A/48<89??O :3<3/4:7 % print'$( 'iltering5n(%& // get some details ACMD /!7 D7:A/48 dd& dd.c,8truct " siCeo'$dd%& MM 7894: mmr " acmDriverDetails$hadid- 1dd- *%& i' $mmr% = print'$( (%& show<error$mmr%& > else = print'$( 8hort name6 )s5n(- dd.sC8hort;ame%& print'$( 4ong name6 )s5n(- dd.sC4ong;ame%& print'$( Copyright6 )s5n(- dd.sCCopyright%& print'$( 4icensing6 )s5n(- dd.sC4icensing%& print'$( 3eatures6 )s5n(- dd.sC3eatures%& print'$( 8upports )u 'ormats5n(- dd.c3ormat:ags%& print'$( 8upports )u 'ilter 'ormats5n(- dd.c3ilter:ags%&
6age D
> // open the driver .ACMD /!7 had " ;944& mmr " acmDriverOpen$1had- hadid- *%& i' $mmr% = print'$( (%& show<error$mmr%& > else = DWO D dw8iCe " *& mmr " acmMetrics$had- ACM<M7: /C<MAD<8/E7<3O MA:- 1dw8iCe%& i' $dw8iCe < siCeo'$WA!73O MA:7D%% dw8iCe " siCeo'$WA!73O MA:7D%& // 'or M8F?CM WA!73O MA:7DG pw' " $WA!73O MA:7DG% malloc$dw8iCe%& memset$pw'- *- dw8iCe%& pw'F>c,8iCe " 4OWO D$dw8iCe% F siCeo'$WA!73O MA:7D%& pw'F>w3ormat:ag " WA!7<3O MA:<9;A;OW;& ACM3O MA:D7:A/48 'd& memset$1'd- *- siCeo'$'d%%& 'd.c,8truct " siCeo'$'d%& 'd.pw'2 " pw'& 'd.c,w'2 " dw8iCe& 'd.dw3ormat:ag " WA!7<3O MA:<9;A;OW;& mmr " acm3ormat7num$had- 1'd- 3ormat7num?roc- *- *%& i' $mmr% = print'$( (%& show<error$mmr%& > 'ree$pw'%& acmDriverClose$had- *%& > > return : 97& // continue enumeration
The callbac' %unction is passed a set o% %lags which describe what 'ind o% support the dri"er has# $ome dri"ers can operate asynchronously while other s do not# $ome dri"ers can con"ert one wa"e data %ormat to another +these are CO !Cs- and other dri"ers can only per%orm %iltering operations where the input and output %ormats are the same# Note that the AC. maintains this data along with the te1t name o% the dri"er/ copyright in%ormation and so on so that we can loo' at this data without the need to load or open a speci%ic dri"er# This is con"enient when we want to present a list bo1 to the user +%or e1ample- to select a speci%ic dri"er to use# To obtain more detailed in%ormation about the capabilities o% a dri"er/ we must load the dri"er and open it which is done by calling acmOpenDriver# Once the dri"er is open/ we can as' it to enumerate the wa"e data %ormats it supports# There is one minor complication here because although all wa"e %ormat description structures are based on WAVEFO !A"E# many %ormats use an e1tended %orm o% the structure to hold in%ormation speci%ic to the structure# 2% we want to enumerate all the %ormats we need some idea o% how big a structure to allocate so the dri"er can %ill in the details# We can %ind the siCe o% the largest structure re&uired by calling acm!etrics and passing the AC!$!E" IC$!A#$%I&E$FO !A" %lag# 2% you loo' at the code abo"e you8ll see 2 simply cast the result o% the allocation to be a WAVEFO !A"E# pointer# 28m not interested in any type,speci%ic data here :ust the common in%ormation so this pointer does all 2 need#
6age 4
?a"ing allocated the structure/ 2 can now call acmFormatEnum to enumerate the supported %ormats# Once again we use a callbac' %unction to recei"e the enumerated %ormat data<
@OO4 CA44@ACA 3ormat7num?roc$.ACMD /!7 /D hadid- 4?ACM3O MA:D7:A/48 pa'd- DWO D dw/nstance- DWO D 'dw8upport% = print'$( )H.HlD.- )s5n(- pa'dF>dw3ormat:ag- pa'dF>sC3ormat%& return : 97& // continue enumerating
>
As you can see/ this one8s tri"ial and :ust prints out some o% the in%ormation about the %ormat# $o with the code abo"e you can &uery the AC. %or all its dri"ers and %ind what %ormats are supported by each# 2 suggest you run the CA6$ program now and see what your system currently has installed#
6age 5
There is howe"er one %urther problem which 2 chose to ignore in my sample code and 28ll lea"e to you to resol"e# 2% we ha"e a CO !C that will create the compressed %ormat we want but supports se"eral input %ormats/ how do we choose the best intermediate %ormat to useI 0ollowing Nigel8s ma1im which states< FAlways do the least amount o% wor' possibleF 2 chose to simply use the %irst 6C. %ormat the CO !C supports as the intermediate %orm# While this is "ery easy to implement it can lead to some loss o% data %idelity# Consider that the CO !C we want to use has some algorithm %or almost lossless compression and can accept G or 5H bit 6C. data at 55#*A5 or AA#*5*# ;et8s say we want to con"ert a high %idelity sample recorded at 44#5 '?C/ 5H bit stereo# What we are trying to do is reduce the data "olume but not at the e1pense o% &uality# 2% we simply enumerate the %ormats the CO !C supports/ the %irst one we %ind might well be 55#*A5 '?C/ G bit# .ono# 2% we con"ert to this %ormat %irst and then compress it we will certainly ha"e lost some &uality because the intermediate %ormat we chose was not good enough# 2% we8d ha"e used 5H bit and AA '?C/ we would ha"e done much better# ?a"ing warned you o% this pit%all/ let8s loo' now at the CON( sample and see how it wor's#
// // // // //
3irst we create a wave that might have ,een Iust recorded. :he 'ormat is JJ.*+K L.C- 0 ,it mono ?CM which is a recording 'ormat availa,le on all machines. our sample wave will ,e J second long and will ,e a sine wave o' JL.C which is e2actly J-*** cycles
WA!73O MA:7D w'8rc& memset$1w'8rc- *- siCeo'$w'8rc%%& w'8rc.c,8iCe " *& w'8rc.w3ormat:ag " WA!7<3O MA:<?CM& // pcm w'8rc.nChannels " J& // mono w'8rc.n8amples?er8ec " JJ*+K& // JJ.*+K L.C w'8rc.w@its?er8ample " 0& // 0 ,it w'8rc.n@locLAlign " w'8rc.nChannels G w'8rc.w@its?er8ample / 0& w'8rc.nAvg@ytes?er8ec " w'8rc.n8amples?er8ec G w'8rc.n@locLAlign& DWO D dw8rc8amples " w'8rc.n8amples?er8ec& @B:7G p8rcData " new @B:7 Mdw8rc8amplesN& // J second duration @B:7G pData " p8rcData& dou,le ' " J***.*& dou,le pi " H.* G atan$J.*%& dou,le w " +.* G pi G '& 'or $DWO D dw " *& dw < dw8rc8amples& dwOO% = dou,le t " $dou,le% dw / $dou,le% w'8rc.n8amples?er8ec& GpDataOO " J+0 O $@B:7%$J+P.* G sin$w G t%%& > A WAVEFO !A"E# structure is created to describe the source data %ormat and a wa"e o% one second duration 55#*A5 '?C/ mono/ G bit 6C. is generated with some simple math#
6age H
The ne1t step is to choose a %ormat we8d li'e to con"ert the data to and locate a suitable CO !C#
WO D w3ormat:ag " WA!7<3O MA:<D8?# O9?<: 978?77C.& // ;ow we locate a COD7C that supports the destination 'ormat tag .ACMD /!7 /D hadid " 'ind<driver$w3ormat:ag%& i' $hadid "" ;944% = print'$(;o driver 'ound5n(%& e2it$J%& > print'$(Driver 'ound $hadid6 )H.HlD.%5n(- hadid%& The 'ind$driver %unction enumerates all the dri"ers until it %inds one that supports the gi"en tag "alue +int his case WA(!J0O=.ATJ $67=OB6JT=B!$6!!C?-# 2 won8t show the details since it8s "ery similar to the enumeration code we loo'ed at earlier# 9ou can e1amine how it wor's %or yoursel% later# ?a"ing located the dri"er we now need to construct a WAVEFO !A"E# structure %or the %inal compressed data %ormat that the dri"er will generate and also %or the intermediate 6C. %ormat the dri"er needs as input<
// get the details o' the 'ormat // ;ote6 this is Iust the 'irst o' one or more possi,le 'ormats 'or the given tag WA!73O MA:7DG pw'Drv " get<driver<'ormat$hadid- w3ormat:ag%& i' $pw'Drv "" ;944% = print'$(7rror getting 'ormat in'o5n(%& e2it$J%& > print'$(Driver 'ormat6 )u ,its- )lu samples per second5n(pw'DrvF>w@its?er8ample- pw'DrvF>n8amples?er8ec%& // get a ?CM 'ormat tag the driver supports // ;ote6 we Iust picL the 'irst supported ?CM 'ormat which might not really // ,e the ,est choice. WA!73O MA:7DG pw'?CM " get<driver<'ormat$hadid- WA!7<3O MA:<?CM%& i' $pw'?CM "" ;944% = print'$(7rror getting ?CM 'ormat in'o5n(%& e2it$J%& > print'$(?CM 'ormat6 )u ,its- )lu samples per second5n(pw'?CMF>w@its?er8ample- pw'?CMF>n8amples?er8ec%& At the ris' o% repeating mysel%/ beware that the get$driver$'ormat %unction :ust enumerates %or the %irst matching %ormat and this might not be optimal i% you want the best possible &uality# Now we ha"e WAVEFO !A"E# structures built to describe the source %ormat/ the intermediate 6C. %ormat and the %inal compressed %ormat# 2t8s time to start con"erting the data# Con"ersion is done by using what the AC. calls a stream# We open the stream passing descriptions o% the source and destination %ormats and then as' the stream to con"ert them#
6age 7
2n the case we8ll loo' at here the con"ersion is done synchronously and may ta'e &uite some time i% the CO !C algorithm is comple1# $ome CO !Cs can wor' asynchronously noti%ying you as things progress "ia a message to a window/ a call to a callbac' %unction or setting an e"ent# The code here :ust gets the :ob done with the least %uss 3 but you do get to wait until it8s complete# There is one other important point# As you8ll see/ when we open the con"ersion streams we speci%y the AC.J$T=!A.O6!N0JNON=!A;T2.! %lag# This is "ery important# 2% you omit this %lag then some dri"ers +%or e1ample the True$peech dri"er- will report error 55A +not possible-# This error is telling you that the con"ersion you as'ed %or cannot be done in real time# This isn8t an issue in my sample but it would be i% you were trying to con"ert a lot o% data at the same time you were playing it# $o let8s loo' now at the %irst con"ersion step which con"erts the source %ormat to the intermediate %ormat<
///////////////////////////////////////////////////////////////////////////// // convert the source wave to the ?CM 'ormat supported ,y the COD7C // we use any driver that can do the ?CM to ?CM conversion .ACM8: 7AM hstr " ;944& mmr " acm8treamOpen$1hstr;944- // any driver 1w'8rc- // source 'ormat pw'?CM- // destination 'ormat ;944- // no 'ilter ;944- // no call,acL *- // instance data $not used% ACM<8: 7AMO?7;3<;O; 7A4:/M7%& // 'lags i' $mmr% = print'$(3ailed to open a stream to do ?CM to ?CM conversion5n(%& e2it$J%& > // allocate a ,u''er 'or the result o' the conversion. DWO D dw8rc@ytes " dw8rc8amples G w'8rc.w@its?er8ample / 0& DWO D dwDstJ8amples " dw8rc8amples G pw'?CMF>n8amples?er8ec / w'8rc.n8amples?er8ec& DWO D dwDstJ@ytes " dwDstJ8amples G pw'?CMF>w@its?er8ample / 0& @B:7G pDstJData " new @B:7 MdwDstJ@ytesN& // 'ill in the conversion in'o ACM8: 7AM.7AD7 strhdr& memset$1strhdr- *- siCeo'$strhdr%%& strhdr.c,8truct " siCeo'$strhdr%& strhdr.p,8rc " p8rcData& // the source data to convert strhdr.c,8rc4ength " dw8rc@ytes& strhdr.p,Dst " pDstJData& strhdr.c,Dst4ength " dwDstJ@ytes& // prep the header mmr " acm8tream?repare.eader$hstr- 1strhdr- *%& // convert the data print'$(Converting to intermediate ?CM 'ormat...5n(%& mmr " acm8treamConvert$hstr- 1strhdr- *%& i' $mmr% =
6age G
print'$(3ailed to do ?CM to ?CM conversion5n(%& e2it$J%& > print'$(Converted OA5n(%& // close the stream acm8treamClose$hstr- *%& When the stream is opened the second parameter is set to NB;;/ indicating that we will accept any dri"er to per%orm this con"ersion# The only comple1ity is computing how much bu%%er space we8ll need %or the output data# $ince a 6C. to 6C. con"ersion in"ol"es no compression or decompression the computation is straight %orward# 9ou might note the call to acm%treamPrepare,eader which actually is a con"enience %or the dri"er and allows it to loc' the memory be%ore con"ersion begins# The %inal step is to con"ert the intermediate %ormat to the %inal compressed %ormat<
//////////////////////////////////////////////////////////////////////////////// /// // convert the intermediate ?CM 'ormat to the 'inal 'ormat // open the driver .ACMD /!7 had " ;944& mmr " acmDriverOpen$1had- hadid- *%& i' $mmr% = print'$(3ailed to open driver5n(%& e2it$J%& > // open the conversion stream // ;ote the use o' the ACM<8: 7AMO?7;3<;O; 7A4:/M7 'lag. Without this // some so'tware compressors will report error KJ+ F not possi,le mmr " acm8treamOpen$1hstrhad- // driver handle pw'?CM- // source 'ormat pw'Drv- // destination 'ormat ;944- // no 'ilter ;944- // no call,acL *- // instance data $not used% ACM<8: 7AMO?7;3<;O; 7A4:/M7%& // 'lags i' $mmr% = print'$(3ailed to open a stream to do ?CM to driver 'ormat conversion5n(%& e2it$J%& > // allocate a ,u''er 'or the result o' the conversion. // compute the output ,u''er siCe ,ased on the average ,yte rate // and add a ,it 'or randomness // the /MA<AD?CM driver 'ails the conversion without this e2tra space DWO D dwDst+@ytes " pw'DrvF>nAvg@ytes?er8ec G dwDstJ8amples / pw'?CMF>n8amples?er8ec& dwDst+@ytes " dwDst+@ytes G Q / +& // add a little room
6age 9
@B:7G pDst+Data " new @B:7 MdwDst+@ytesN& // 'ill in the conversion in'o ACM8: 7AM.7AD7 strhdr+& memset$1strhdr+- *- siCeo'$strhdr+%%& strhdr+.c,8truct " siCeo'$strhdr+%& strhdr+.p,8rc " pDstJData& // the source data to convert strhdr+.c,8rc4ength " dwDstJ@ytes& strhdr+.p,Dst " pDst+Data& strhdr+.c,Dst4ength " dwDst+@ytes& // prep the header mmr " acm8tream?repare.eader$hstr- 1strhdr+- *%& // convert the data print'$(Converting to 'inal 'ormat...5n(%& mmr " acm8treamConvert$hstr- 1strhdr+- *%& i' $mmr% = print'$(3ailed to do ?CM to driver 'ormat conversion5n(%& e2it$J%& > print'$(Converted OA5n(%& // close the stream and driver mmr " acm8treamClose$hstr- *%& mmr " acmDriverClose$had- *%& This is "ery similar to the 6C. to 6C. con"ersion but in this case we supply the handle to the dri"er we want to use when we open the stream# Actually we could supply NB;; here too since we already ascertained that the dri"er e1ists/ but supplying the handle a"oids the system wasting time %inding the dri"er %or us# Computing the bu%%er siCe %or the compressed data is a little tric' and re&uires some slight guesswor'# The nAvg-ytesPer%ec %ield o% the WAVEFO !A"E# structure indicates the a"erage rate at which bytes are read during playbac'# We can use this to estimate how much data we need to store the compressed wa"e# $ome dri"ers gi"e data that is truly a"erage and not the worst case so 2 chose to add 5*K more to the bu%%er which wor's well in practice e"en i% it is a little waste%ul# Once the con"ersion is complete the cbDst.engthUsed %ield o% the AC!%" EA!,EADE structure contains the actual number o% bytes used in the bu%%er and 2 used this to compute the compression ratio<
// show the conversion stats print'$(8ource wave had )lu ,ytes5n(- dw8rc@ytes%& print'$(Converted wave has )lu ,ytes5n(- strhdr+.c,Dst4ength9sed%& print'$(Compression ratio is )'5n(- $dou,le% dw8rc@ytes / $dou,le% strhdr+.c,Dst4ength9sed%&
%ummary
6age 5*
Compressing wa"e%orm data using Windows8 built,in CO !Cs is easy to do and results in data which occupies less dis' space and ta'es less time to transmit# 2% you ha"e a proprietary compression %ormat you can create you own CO !C to install and use it in the same way 28"e shown here# As usual/ 28m happy to answer &uestions regarding this article# 2 can be contacted by email< nigel,tLmsn#com
6age 55