convert WAV files with ACM (win32)

Post your FreeBASIC source, examples, tips and tricks here. Please don’t post code without including an explanation.
Post Reply
DamageX
Posts: 130
Joined: Nov 21, 2009 8:42

convert WAV files with ACM (win32)

Post by DamageX »

Here is a program that converts a WAV file containing uncompressed PCM to one containing MP3 audio. This requires the LAME MP3 ACM codec being installed on your system. Specify the input file and output file names on the command line.

Code: Select all

' convert PCM to LAME 160Kbps, CBR, 44KHz stereo
' compiled with FreeBASIC 0.23 Win32

extern "windows" lib "msacm32"
declare function acmStreamOpen (byval as any ptr,byval as integer, _
        byval as any ptr,byval as any ptr,byval as any ptr, _
        byval as any ptr,byval as integer,byval as integer) as integer
declare function acmStreamPrepareHeader (byval as integer, _
        byval as any ptr,byval as integer) as integer        
declare function acmStreamConvert (byval as integer,byval as any ptr, _
        byval as integer) as integer
declare function acmStreamClose (byval as integer,byval as integer) as integer
end extern

#lang "fblite"
option gosub
option explicit

dim as integer fact=0

dim as any ptr format1=any,format2=any
dim as integer x=any,y=any
dim as integer len1=any,len2=any,doffset=any,ooffset=any
dim as integer hstr=any,olength=any,ff1=any,ff2=any,cmode=any

dim shared wformatLAME(0 to 14) as ushort => _
 {&H55,2,&HAC44,0,&H4E20,0,1,0,&H0C,1,4,0,&H020A,1,0}
dim shared wformatCD(0 to 8) as ushort => {1,2,&HAC44,0,&HB110,2,4,16,0}

dim shared strheader(0 to 20) as integer
strheader(0)=84


' open files
ff1=freefile
open command$(1) for binary as #ff1
if err<>0 then goto a101
ff2=freefile
open command$(2) for binary as #ff2
if err<>0 then goto a101


' set output buffer to same size as input buffer (why not?)
len1=lof(ff1):len2=len1
if len1=0 then ? "empty file":end
redim shared srcbuf(0 to len1-1) as ubyte
redim shared destbuf(0 to len2-1) as ubyte


' read the entire input file, parse headers
get #ff1,,srcbuf()
close #ff1
x=*cast(integer ptr,@srcbuf(16))+20
a100:
if *cast(integer ptr,@srcbuf(x))<>&H61746164 then
x+=*cast(integer ptr,@srcbuf(x+4))+8
if x>2048 then ? "can't find data chunk :(":end
goto a100
end if
doffset=x+8


' check whether input file is already CD-quality format
for x=0 to 7
if wformatCD(x)<>*cast(ushort ptr,@srcbuf(20+x+x)) then
? "converting to 44KHz, 16-bit, stereo PCM..."
format1=@srcbuf(20)
format2=@wformatCD(0)
gosub convertstuff
' copy the converted PCM back to source buffer
if olength>len1-doffset then    ' make sure it is big enough
len1=doffset+olength
redim srcbuf(0 to len1-1) as ubyte
end if
for x=0 to olength-1:srcbuf(x+doffset)=destbuf(x):next x
fact=(olength shr 2)
goto a104
end if
next x
a104:


' calculate # of samples, if we didn't already
if fact=0 then fact=((len1-doffset) shr 2)


? "converting from PCM to LAME..."
format1=@wformatCD(0)
format2=@wformatLAME(0)
gosub convertstuff


' write output file
? "saving output file..."
y=wformatLAME(8)+18
x=&H46464952:put #ff2,,x
x=20+y+12+olength:put #ff2,,x
x=&H45564157:put #ff2,,x
x=&H20746D66:put #ff2,,x
put #ff2,,y
put #ff2,,wformatLAME(0),(y shr 1)
x=&H74636166:put #ff2,,x
x=4:put #ff2,,x
put #ff2,,fact
x=&H61746164:put #ff2,,x
put #ff2,,olength
put #ff2,,destbuf(0),olength

close #ff2
? "done."
end


a101:
? "error opening file :("
end


convertstuff:
x=acmStreamOpen(@hstr,0,format1,format2,0,0,0,4)
if x<>0 then ? "ACM error: ";x:end

strheader(3)=@srcbuf(doffset):strheader(7)=@destbuf(0)
strheader(4)=len1-doffset:strheader(8)=len2
ooffset=0
cmode=20

a102:
strheader(1)=0

x=acmStreamPrepareHeader(hstr,@strheader(0),0)
if x<>0 then ? "unknown error":end

x=acmStreamConvert(hstr,@strheader(0),cmode)
if x<>0 then
? "conversion error ";x
for x=0 to 9:? strheader(x):next x
end
end if

' check whether all of the data was processed
if strheader(5)<strheader(4) then 
' enlarge the buffer and prepare to make another pass
cmode=4
ooffset+=strheader(9)
len2+=strheader(9)
redim preserve destbuf(0 to len2-1) as ubyte
strheader(7)=@destbuf(ooffset)
strheader(3)+=strheader(5)
strheader(4)=strheader(4)-strheader(5)
goto a102
end if

olength=strheader(9)+ooffset
x=acmStreamClose(hstr,0)
return
If the source format is already 44KHz/16bit/stereo "CD quality" then the conversion happens in one step. If it is different then it must first be converted to CD quality before being encoded with LAME. If the source format is not uncompressed PCM then this program will fail, as I believe this would necessitate a third conversion (and when I tried to uncompress eg. MS ADPCM, the program simply crashed for unknown reasons)

The target format is hardcoded in the array wformatLAME(). If you were wondering what the format definition would look like for another compressor/bitrate/etc then check out this program which brings up the selection dialog and prints the output.

Code: Select all

' acm formats...

extern "windows" lib "msacm32"
declare function acmFormatChooseA (byval as any ptr) as integer
end extern

dim as integer x=any,y=any

dim shared wformat(0 to 24) as ushort
dim shared fchoose(0 to 57) as integer

fchoose(0)=232
fchoose(3)=@wformat(0)
fchoose(4)=50

x=acmFormatChooseA(@fchoose(0))

for y=0 to 24
? hex$(wformat(y));" ";
next y
Post Reply