[VB6] Play MP3s from either file or byte array w/ PlaySound (sndPlaySound)
.wav is actually a container format, and if you put MP3 audio into a WAV container, it turns out PlaySound (or sndPlaySound) is able to play it.
This allows playing MP3s from a file, or byte array, with just that simple API instead of more complex methods or additional dependencies.
Credit to autoitscript.com's Melba23, this is based off their autoitscript implementation of this idea.
---
First, the main function plays from memory with an MP3 loaded into a byte array, then placed into the WAV container, so you'd be able to play one from a resource or anywhere else where you have an mp3 without an actual file.
Code:
Private btPlay() As Byte 'The full structure passed to PlaySound. It must be module-level, otherwise it will go out of scope and crash.
'Alternative, you could use GlobalAlloc and play from that, but then you need a module-level anyway for GlobalFree.
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
Private Declare Function PlaySoundW Lib "winmm.dll" (ByVal pszSound As Long, ByVal hModule As Long, ByVal dwFlags As SND_FLAGS) As Long
Private Enum SND_FLAGS
SND_SYNC = &H0 ' play synchronously (default)
SND_ASYNC = &H1 ' play asynchronously
SND_NODEFAULT = &H2 ' silence not default, if soundnot found
SND_MEMORY = &H4 ' lpszSoundName points to a memoryFile
SND_LOOP = &H8 ' loop the sound until nextsndPlaySound
SND_NOSTOP = &H10 ' don't stop any currently playingsound
SND_PURGE = &H40 ' purge non-static events forTask. Not supported.
SND_APPLICATION = &H80 ' look for applicationspecific association
SND_NOWAIT = &H2000 'Not supported
SND_ALIAS = &H10000 ' name is a WIN.INI [sounds] entry
SND_FILENAME = &H20000 ' name is a file name
SND_SENTRY = &H80000 ' /* Generate a SoundSentry event with this sound */
SND_RING = &H100000 ' /* Treat this as a "ring" from a communications app - don't duck me */
SND_SYSTEM = &H200000 ' /* Treat this as a system sound */
SND_ALIAS_ID = &H110000 ' name is a WIN.INI [sounds]Entry identifier
SND_ALIAS_START = 0 ' must be > 4096 to keep strings insame section of resource file
SND_RESERVED = &HFF000000 ' In particular these flags areReserved
SND_RESOURCE = &H40004 ' name is a resource name or atom
SND_TYPE_MASK = &H170007
SND_VALID = &H1F ' valid flags / ;Internal /
SND_VALIDFLAGS = &H17201F ' Set of valid flag bits.
End Enum
Private Sub PlayMp3Hackertastically(btMp3() As Byte, Optional dwFlags As SND_FLAGS = 0&)
'Since WAV is a container format, we can force MP3 audio into a WAV container, which PlaySound
'then is able to play.
'The structure is built as follows:
'1) Initial part of the WAV format header,
'2) WAV header size: The file size plus both parts of the header,
'3) The remainder of the WAV container header
'4) The size of the MP3 data, and then finally
'5) The bytes of the MP3 file.
Dim btHdr1(3) As Byte 'First part of header
btHdr1(0) = &H52: btHdr1(1) = &H49: btHdr1(2) = &H46: btHdr1(3) = &H46
Dim btHdr2(57) As Byte 'Remainder of WAV container header
btHdr2(0) = &H57: btHdr2(1) = &H41: btHdr2(2) = &H56: btHdr2(3) = &H45: btHdr2(4) = &H66: btHdr2(5) = &H6D: btHdr2(6) = &H74: btHdr2(7) = &H20: btHdr2(8) = &H1E: btHdr2(9) = &H0: btHdr2(10) = &H0: btHdr2(11) = &H0: btHdr2(12) = &H55: btHdr2(13) = &H0: btHdr2(14) = &H2: btHdr2(15) = &H0: btHdr2(16) = &H44: btHdr2(17) = &HAC: btHdr2(18) = &H0: btHdr2(19) = &H0: btHdr2(20) = &H58: btHdr2(21) = &H1B: btHdr2(22) = &H0: btHdr2(23) = &H0: btHdr2(24) = &H1: btHdr2(25) = &H0: btHdr2(26) = &H0: btHdr2(27) = &H0: btHdr2(28) = &HC: btHdr2(29) = &H0: btHdr2(30) = &H1: btHdr2(31) = &H0
btHdr2(32) = &H2: btHdr2(33) = &H0: btHdr2(34) = &H0: btHdr2(35) = &H0: btHdr2(36) = &HB6: btHdr2(37) = &H0: btHdr2(38) = &H1: btHdr2(39) = &H0: btHdr2(40) = &H71: btHdr2(41) = &H5: btHdr2(42) = &H66: btHdr2(43) = &H61: btHdr2(44) = &H63: btHdr2(45) = &H74: btHdr2(46) = &H4: btHdr2(47) = &H0: btHdr2(48) = &H0: btHdr2(49) = &H0: btHdr2(50) = &H64: btHdr2(51) = &HE: btHdr2(52) = &H6: btHdr2(53) = &H0: btHdr2(54) = &H64: btHdr2(55) = &H61: btHdr2(56) = &H74: btHdr2(57) = &H61
'Sizes: Normally a Long, a Long is 4 bytes, so we'll use 4-byte arrays to add each size
Dim iFileSize As Long
Dim iWaveSize As Long
Dim btFS(3) As Byte
Dim btWS(3) As Byte
iFileSize = UBound(btMp3) + 1
CopyMemory btFS(0), ByVal VarPtr(iFileSize), 4&
iWaveSize = iFileSize + 63
CopyMemory btWS(0), ByVal VarPtr(iWaveSize), 4&
ReDim btPlay((4 + 58 + 4 + 4 + (UBound(btMp3) + 2))) 'The sizes of all the arrays we're about to combine.
'The extra byte will ensure a null-terminated structure.
Dim lOff As Long 'Copying offset
CopyMemory btPlay(0), btHdr1(0), 4&: lOff = 4
CopyMemory btPlay(lOff), btWS(0), 4&: lOff = 8
CopyMemory btPlay(lOff), btHdr2(0), 58&: lOff = 66
CopyMemory btPlay(lOff), btFS(0), 4&: lOff = 70
CopyMemory btPlay(lOff), btMp3(0), UBound(btMp3) + 1
'Add mandatory flags
dwFlags = dwFlags Or SND_MEMORY Or SND_NODEFAULT
PlaySoundW VarPtr(btPlay(0)), 0&, dwFlags
End Sub
What the code is doing here, it first takes the main header for the WAV container, then the total header size which includes the variable file size, then the rest of the header, the size of the MP3 data, and finally the MP3 data itself; combining them all into a single byte array. I've broken the copying and array size calculations down so it's easier to see what's going on.
Finally, copying into globally allocated memory was the trick here, you'd get a crash with an access violation if you tried to just play from the local byte array, which for normal .wav data you can do. Note that the issue here is the data going out of scope. If you moved btPlay to a module-level variable, that would work too.
Now, if you did want to play from a .mp3 file, you can just use a standard method to read it into a byte array, then use the play from byte array code above:
Code:
Private Declare Function CreateFileW Lib "kernel32" (ByVal lpFileName As Long, ByVal dwDesiredAccess As Long, ByVal dwShareMode As Long, ByVal lpSecurityAttributes As Any, ByVal dwCreationDisposition As Long, ByVal dwFlagsAndAttributes As Long, ByVal hTemplateFile As Long) As Long
Private Const FILE_READ_DATA = &H1
Private Const FILE_SHARE_READ = &H1&
Private Const OPEN_EXISTING = 3&
Private Declare Function GetFileSize Lib "kernel32" (ByVal hFile As Long, lpFileSizeHigh As Long) As Long
Private Declare Function ReadFile Lib "kernel32" (ByVal hFile As Long, lpBuffer As Any, ByVal nNumberOfBytesToRead As Long, lpNumberOfBytesRead As Long, ByVal lpOverlapped As Any) As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Private Sub PlayMP3HackertasticallyFromFile(sPath As String)
Dim hFile As Long
Dim bWave() As Byte
Dim lSize As Long
Dim mf2 As Long
hFile = CreateFileW(StrPtr(sPath), FILE_READ_DATA, FILE_SHARE_READ, ByVal 0&, OPEN_EXISTING, 0, 0)
lSize = GetFileSize(hFile, mf2)
ReDim bWave(lSize - 1)
ReadFile hFile, bWave(0), lSize, mf2, 0&
CloseHandle hFile
PlayMp3Hackertastically bWave
End Sub
That's all there is to it, but I'll attach a project anyway:
sndPlaySound is the older and more common API; it's a subset of PlaySound though. If you wanted to, you could switch them out if you wanted to.
Update: Forgot to mention, if you're playing Async, you can stop playback at any time by calling PlaySoundW 0&, 0&, 0&
The attached project has been changed to add a 'Stop' button, options for Loop and Async, and automatically stopping when exiting. Also cleaned up and commented code.
Update: Using GlobalAlloc would probably leak memory since GlobalFree was never called; the issue was the data going out of scope, so I just switched to putting btPlay as a module-level var.
Last edited by fafalone; Mar 28th, 2020 at 07:13 PM.
Reason: Changed method