Recording sound using the API and mciSendString with Visual Basic 6
A method that really works (completely)

Getting mciSendString to work properly can be a tremendous headache. While writing a program, I needed to use the API to access the win.mm.dll library (mciSendString) to record sound in a format that Microsoft's Media player would play without errors. After struggling with conflicting advice from various web sites, and practically no useful information from Microsoft's site, I took it upon myself to dig a bit deeper.

My problem was that after I had figured out the correct information to make up the wave file header, I could not get Microsoft's Media player to play the file. I tried different orders for sending the information, different formulas, different coding techniques. I tried various versions of WMP without success (except for a really early version that has no version number attached to it). WinAmp and others played them without problems. Why couldn't I get WMP to play these files? What was missing?

In this tutorial I will attempt to explain what needs to be done to record wave files that will play correctly, regardless of which media player plays it. I never did find the solution on the net as I had hoped. I read the advice of many who suggested not using the API method at all, since it doesn't work correcly. I wanted to use it because it gives the user much more control. If you follow this tutorial, you will have to look no further.

The Problem

After a wave file is saved to your drive using VB's mciSendString, it does not play in Microsoft's Media Players. I have found that this is because the bytes-per-sec value is always written to the file incorrectly.
The byte depiction on the left represents a bad file. Note the yellow highlighted bytes. In the bad file, the hex values are 11 2B 00 which translates to 11025 decimal (these bytes are read in reverse order). The decimal value for this should be 176400 because this was recorded at 2 channels, 16 bit, 44100 samples: 44100 *( (2 * 16) / 8) However mciSendString always puts 11025 as the value. My solution is to directly change the file after it has been recorded. The byte depiction on the right depicts the file after it has been corrected. The yellow highlighted hex values (in reverse order - 02 B1 10) now equal the correct value of 176400 and plays perfectly in Windows Media Player.

Here is the complete VB code (written for VB6) that codes all the steps for recording a wave file, and my solution for fixing that wave file so it is playable with all media players.

The code written here can be copied and pasted into your program. It is activated by command buttons on the main form. This code has been modified from the original, taking out features that interact with my full program to leave behind the essence of recording and fixing a wave file. Here's hoping it functions with a simple copy and paste. If you have any questions or problems, feel free to e-mail me.

' module modRecordWav

Option Explicit
Dim lBytes As Long ' I'm using this as a global variable since several procedures need it
' declare the functions used in the winmm.dll library
Private Declare Function mciSendString Lib "winmm.dll" _
Alias "mciSendStringA" _
(ByVal lpstrCommand As String, _
ByVal lpstrReturnString As String, _
ByVal uReturnLength As Long, _
ByVal hwndCallback As Long) As Long

Private Declare Function mciGetErrorString Lib "winmm.dll" _
Alias "mciGetErrorStringA" _
(ByVal dwError As Long, _
ByVal lpstrBuffer As String, _
ByVal uLength As Long) As Long
'________________________________________________

Public Function StartRecord(Soundfile As String, sChannels As String, sBits As String, sSamples As String) As Boolean
' this function gets called from a click of a button on the main form
' it passes the name of the wave file including the path,
' the number of channels (1 or 2),
' the number of bits (8 or 16),
' and the number of samples per second desired (standards are 11025, 22050, and 44100)
' the higher the sample number, the better the sound quality
' these are all passed as strings

' declare variables
Dim Result as long
Dim errormsg as integer
Dim ReturnString As String * 1024
Dim ErrorString As String * 1024
Dim mssg As String * 255
Dim i As Long
Dim BlockAlign As Integer
Dim sBytes As String
' make sure all working files in memory are closed
mciSendString "close all", 0, 0, 0
' open a ned working file in memory - recsound is my name for this working file
Result= mciSendString("open new Type waveaudio Alias recsound", ReturnString, Len(ReturnString), 0)
' this result check will be performed after EVERY mciSendString to make sure it does not
' generate any errors

If Not Result= 0 Then
errormsg = mciGetErrorString(Result, ErrorString, 1024)
MsgBox ErrorString, 0, "Error"
End If
' set time format to milliseconds
Result= mciSendString("set recsound time format ms", ReturnString, 1024, 0)
If Not Result= 0 Then
errormsg = mciGetErrorString(Result, ErrorString, 1024)
MsgBox ErrorString, 0, "Error (time format)"
End If
' set to pcm type wave file (Microsoft standard)
Result= mciSendString("set recsound format tag pcm", ReturnString, 1024, 0)
If Not Result= 0 Then
errormsg = mciGetErrorString(Result, ErrorString, 1024)
MsgBox ErrorString, 0, "Error (format tag)"
End If

' in these next settings make sure there is a space between the setting and the setting variable
' set number of channels (1 or 2) - variable sChannels passed from calling procedure
Result= mciSendString("set recsound channels " & sChannels, ReturnString, 1024, 0)
If Not Result= 0 Then
errormsg = mciGetErrorString(Result, ErrorString, 1024)
MsgBox ErrorString, 0, "Error (channels)"
End If

' set number of samples - variable sSamples passed from calling procedure
Result& = mciSendString("set recsound samplespersec " & sSamples", ReturnString, 1024, 0)
If Not Result= 0 Then
errormsg = mciGetErrorString(Result, ErrorString, 1024)
MsgBox ErrorString, 0, "Error (samples/sec)"
End If

' set number of bits (8 or 16) - - variable sBits passed from calling procedure
Result= mciSendString("set recsound bitspersample " & sBits, ReturnString, 1024, 0)
If Not Result= 0 Then
errormsg = mciGetErrorString(Result, ErrorString, 1024)
MsgBox ErrorString, 0, "Error (bits/sample)"
End If

' set the block allignment - I don't think this is necessary but
' is included just in case, and for completeness

' formula to find block allignment
BlockAlign = CInt((CLng(sBits) / 8) * CLng(sChannels))
Result= mciSendString("set recsound alignment " & Str$(BlockAlign), ReturnString, 1024, 0)
If Not Result= 0 Then
errormsg = mciGetErrorString(Result, ErrorString, 1024)
MsgBox ErrorString, 0, "Error (block align)"
End If

' calculates OK but mciSendString does not put it in the wav file properly
' will always put 11025 regardless of what it is supposed to be
' this value is ONLY good for 1 channel, 8 bits, 11025 samples
' this is where WMP stumbles - we'll address this further on

' we'll include this anyway so you can see where the error actually lies

lBytes = CLng(sSamples) * ((CLng(sChannels) * CLng(sBits)) / 8)
sBytes = Str$(lBytes)

' doesn't work correctly due to suspected bug in winmm.dll
' make sure this part of the code is commented out or you will get errors
' ************************************************
' Result= mciSendString("set recsound bytespersec " & sBytes, ReturnString, 1024, 0)
' If Not Result= 0 Then
' errormsg = mciGetErrorString(Result, ErrorString, 1024)
' MsgBox ErrorString, 0, "Error (bytes/sec)"
' End If


' start recording
Result= mciSendString("record recsound", ReturnString, Len(ReturnString), 0)
If Not Result= 0 Then
errormsg = mciGetErrorString(Result, ErrorString, 1024)
MsgBox ErrorString, 0, "Error (record)"
End If
End Function
' __________________________________________________

Public Function StopRecord() As Boolean
' this function activated by stop command button on main form
Dim Result as Long
Dim errormsg as Integer
Dim ReturnString As String * 1024
Dim ErrorString As String * 1024
Dim mssg As String * 255
Dim i As Long

' stop the recording
Result = mciSendString("stop recsound", ReturnString, Len(ReturnString), 0)
If Not Result = 0 Then
errormsg = mciGetErrorString(Result, ErrorString, 1024)
MsgBox ErrorString, 0, "Error (stop)"
End If

' save the wave file that is in memory
Result= mciSendString("save recsound C:\NewWav.wav", ReturnString, Len(ReturnString), 0)
If Not Result= 0 Then
errormsg = mciGetErrorString(Result, ErrorString, 1024)
MsgBox ErrorString, 0, "Error (save)"
End If

' calls the procedure that will read back the values just recorded
' see these results to make the error show
Call GetRecStatus

' close the file in memory
Result= mciSendString("close recsound", ReturnString, 1024, 0)
If Not Result= 0 Then
errormsg = mciGetErrorString(Result, ErrorString, 1024)
MsgBox ErrorString, 0, "Error (close)"
End If

' make sure all files in memory are closed
mciSendString "close all", 0, 0, 0

' now call the procedure that fixes the file on your drive
Call FixWaveFile

End Function
' _______________________________________________________

Private Sub GetRecStatus()
' read the status of the file in memory
' declare variables
Dim i As Long
Dim MSchan As String
Dim MSbits As String
Dim MSsamples As String
Dim MSbytes As String
Dim lChan As Long
Dim lBits As Long
Dim lSamples As Long
'Dim lBytes As Long declared as global
Dim sChan As String
Dim mssg As String * 255

' channel status
i = mciSendString("status recsound channels", mssg, 255, 0)
If Str(mssg) = "1" Then
MSchan = "mono"
sChan = "1"
Else
MSchan = "stereo"
sChan = "2"
End If
Msgbox "channel =" & MSchan

' bits per sample
i = mciSendString("status recsound bitspersample", mssg, 255, 0)
MSbits = Str(mssg)
Msgbox "bits per sample =" & MSbits

'samples
i = mciSendString("status recsound samplespersec", mssg, 255, 0)
MSsamples = Str(mssg)
Msgbox "samples =" & MSsamples

' this reports 11025 regardless of how it was written! wrong! - fixed later
i = mciSendString("status recsound bytespersec", mssg, 255, 0)
MSbytes = Str(mssg)
Msgbox "bytes per sec=" & MSbytes

' calculate the real bytes per sec value
' this value will be used to fix the file after it has been saved
lBytes = CLng(MSsamples) * ((CLng(sChan) * CLng(MSbits)) / 8)

End Sub

' ____________________________________________________

Public Sub FixWaveFile()
' this will fix the file so it is playable with WMP
' declare integers
Dim Indexnum As Integer
Dim x As Integer
Dim HexCode As String
Dim Hex1 As String
Dim Hex2 As String
Dim Hex3 As String
Dim lByteNum As Long ' byte number (29,30, & 31) in the wave file
Dim bByte As Byte ' will be hex byte to write

' get the hexadecimal for the lBytes value
HexCode = Hex(lBytes) ' lBytes calculated from previous formula
Do While Len(HexCode) < 6 ' make sure the hex code is 6 chars long
HexCode = "0" & HexCode ' if not, add a zero
Loop

' note: this value had to be written to the file in reverse order!
Hex1 = Right$(HexCode, 2) ' Endian small - reverse order - get last hex byte first
Hex2 = Mid$(HexCode, 3, 2) ' get middle hex byte
Hex3 = Left$(HexCode, 2) ' get first hex byte

'open the file
Indexnum = FreeFile ' get a free file number
Open "C:\NewWav.wav" For Binary Access Write As #Indexnum ' binary open file
lByteNum = 29 ' first byte to write is 29
bByte = CInt("&H" & Hex1) ' bByte = integer of hex Hex1
Put #Indexnum, lByteNum, bByte 'write bByte value to byte position lByteNum in file
bByte = CInt("&H" & Hex2) ' proceed to write remaining two bytes to consecutive positions
lByteNum = lByteNum + 1
Put #Indexnum, lByteNum, bByte ' note the Put command for writing bites to binary files
bByte = CInt("&H" & Hex3)
lByteNum = lByteNum + 1
Put #Indexnum, lByteNum, bByte
Close #1
End Sub

_________________________________________________________________________

tutorial & VB code by S Clarke

Questions or comments can be sent to: Rediware

Thanks for visiting!

26940