Buffered audio streaming via MMIO/PB and WinMM/DirX Routines
Posted: Sat Jul 29, 2006 12:43 pm
An example of streaming PCM Wave data to WaveOut
The MMIO routines are just used for reading the information and the uncompressed audiodata out of a wave file. If you have other routines (DirectShow, Avisynth, Libavcodec, etc.) for decompressing to obtain uncompressed PCM audiodata from a given file (ac3, mp3, mp4, etc.) you can just replace the MMIO routines with the needed specific ones.
EDIT: Codeupdate. Now Refillingrequest is watched by a PB Windowcallback- makes it independand from user Windowaccess.
I hope the code isn't messed up by the forum engine while posting cause of its lenght.
The MMIO routines are just used for reading the information and the uncompressed audiodata out of a wave file. If you have other routines (DirectShow, Avisynth, Libavcodec, etc.) for decompressing to obtain uncompressed PCM audiodata from a given file (ac3, mp3, mp4, etc.) you can just replace the MMIO routines with the needed specific ones.
EDIT: Codeupdate. Now Refillingrequest is watched by a PB Windowcallback- makes it independand from user Windowaccess.
Code: Select all
; Mini PB example for audio streaming using MMIO/WINMM routines, 2006 by Inc.
; Based on an MMIO example from "API-Guide" at All-API(dot)net.
; EnableExplicit
#NUM_BUFFERS = 5;8 ; 8 should be enough.
#BUFFER_SECONDS = 0.1;0.5 ; the bigger the buffer, the higher the latency; the lower the buffer the bigger the risk of stuttering.
Structure WAVEFORMATEX Extends WAVEFORMAT
cbSize.w
EndStructure
Global rc.l ; Return code
Global hmmioIn.l ; file handle
Global DataOffset.l ; start of audio data in wave file
Global audioLength.l ; number of bytes in audio data
Global startPos.l ; sample where we started playback from
Global Format.WAVEFORMATEX ; waveformat structure
Global Dim hmem.l(#NUM_BUFFERS-1) ; memory handles
Global Dim pmem.l(#NUM_BUFFERS-1) ; memory pointers
Global Dim hdr.WAVEHDR(#NUM_BUFFERS-1) ; wave headers
Global bufferSize.l ; size of output buffers
Global fPlaying.b ; is file currently playing
Global fFileOpen.b ; is file currently open
Global hWaveOut.l ; waveout handle
Global msg.s=Space(250) ; message buffer
Global hwnd.l ; window handle
Procedure Position()
Protected tm.MMTIME, Position.l
tm\wType = #TIME_BYTES
rc = waveOutGetPosition_(hWaveOut, @tm, SizeOf(MMTIME))
If rc = #MMSYSERR_NOERROR
Position = (startPos + tm\u\cb) / Format\nBlockAlign
Else
Position = (mmioSeek_(hmmioIn, 0, #SEEK_CUR) - DataOffset + bufferSize * #NUM_BUFFERS) / Format\nBlockAlign
EndIf
ProcedureReturn Position
EndProcedure
Procedure ServiceBuffers(*wavhdr.WAVEHDR)
Protected dataRemaining.l, i.l
If fPlaying = #True
Debug "refilling buffers ... at second "+Str(Position()/Format\nSamplesPerSec)
dataRemaining = (DataOffset + audioLength - mmioSeek_(hmmioIn, 0, #SEEK_CUR))
If bufferSize < dataRemaining
rc = mmioRead_(hmmioIn, *wavhdr\lpData, bufferSize)
Else
rc = mmioRead_(hmmioIn, *wavhdr\lpData, dataRemaining)
fPlaying = #False
Debug "... rest remaining data: "+Str(dataRemaining)
EndIf
*wavhdr\dwBufferLength = rc
rc = waveOutWrite_(hWaveOut, *wavhdr, SizeOf(WAVEHDR))
Else
Debug "... finishing buffering ... at second "+Str(Position()/Format\nSamplesPerSec)
For i = 0 To #NUM_BUFFERS-1
waveOutUnprepareHeader_( hWaveOut, @hdr(i), SizeOf(WAVEHDR))
Next
waveOutClose_( hWaveOut)
EndIf
EndProcedure
Procedure CloseTheFile()
Protected i.l
For i = 0 To (#NUM_BUFFERS-1)
GlobalFree_(hmem(i))
;Debug "Memory "+Str(i)+" allocated at "+Str(pmem(i))+ " released"
Next
If fPlaying = #True
For i = 0 To #NUM_BUFFERS-1
waveOutUnprepareHeader_( hWaveOut, @hdr(i), SizeOf(WAVEHDR))
Next
waveOutClose_( hWaveOut)
EndIf
mmioClose_(hmmioIn, 0)
fFileOpen = #False
EndProcedure
Procedure OpenTheFile(soundfile.s, WinNo.l)
Protected total.f, i.l, mmckinfoParentIn.MMCKINFO, mmckinfoSubchunkIn.MMCKINFO, mmioinf.MMIOINFO
hwnd = WindowID(WinNo)
fPlaying = #False
startPos = 0
; checking if still playing
If fPlaying
fPlaying = #False
EndIf
; close previously open file (if any)
If fFileOpen = #True
CloseTheFile()
EndIf
If soundfile = ""
Debug "File not found"
ProcedureReturn #False
EndIf
; Open the input file
hmmioIn = mmioOpen_(@soundfile, @mmioinf, #MMIO_READ)
If hmmioIn = 0
MessageRequester("Error", "Error opening input File, rc = "+PeekS(@mmioinf\wErrorRet))
ProcedureReturn #False
EndIf
; Check if this is a wave file
mmckinfoParentIn\fccType = mmioStringToFOURCC_("WAVE", 0)
rc = mmioDescend_(hmmioIn, @mmckinfoParentIn, 0, #MMIO_FINDRIFF)
If rc <> #MMSYSERR_NOERROR
CloseTheFile()
MessageRequester("Error", "Not a wave File")
ProcedureReturn #False
EndIf
; Get format info
mmckinfoSubchunkIn\ckid = mmioStringToFOURCC_("fmt", 0)
rc = mmioDescend_(hmmioIn, @mmckinfoSubchunkIn, @mmckinfoParentIn, #MMIO_FINDCHUNK)
If rc <> #MMSYSERR_NOERROR
CloseTheFile()
MessageRequester("Error", "Couldn;t get format chunk")
ProcedureReturn #False
EndIf
rc = mmioRead_(hmmioIn, @Format, mmckinfoSubchunkIn\ckSize)
If rc = -1
CloseTheFile()
MessageRequester("Error", "Error reading format")
ProcedureReturn #False
EndIf
Debug " "
Debug "FormatTag: "+Str(Format\wFormatTag)
Debug "Channels: "+Str(Format\nChannels)
Debug "SamplesPerSec: "+Str(Format\nSamplesPerSec)+" Hz"
Debug "AvgBytesPerSec: "+Str(Format\nAvgBytesPerSec)
Debug "BlockAlign: "+Str(Format\nBlockAlign)+" bytes"
Debug "Resolution: "+Str(Format\cbSize)+" bits"
Debug " "
rc = mmioAscend_(hmmioIn, @mmckinfoSubchunkIn, 0)
; Find the data subchunk
mmckinfoSubchunkIn\ckid = mmioStringToFOURCC_("data", 0)
rc = mmioDescend_(hmmioIn, @mmckinfoSubchunkIn, @mmckinfoParentIn, #MMIO_FINDCHUNK)
If rc <> #MMSYSERR_NOERROR
CloseTheFile()
MessageRequester("Error", "Couldn't get data chunk")
ProcedureReturn #False
EndIf
DataOffset = mmioSeek_(hmmioIn, 0, #SEEK_CUR)
; Get the length of the audio
audioLength = mmckinfoSubchunkIn\ckSize
; Allocate audio buffers
bufferSize = Format\nSamplesPerSec * Format\nBlockAlign * Format\nChannels * #BUFFER_SECONDS
bufferSize = bufferSize - (bufferSize % Format\nBlockAlign)
For i = 0 To (#NUM_BUFFERS-1)
GlobalFree_(hmem(i))
hmem(i) = GlobalAlloc_(#GMEM_ZEROINIT|#GMEM_MOVEABLE, bufferSize)
pmem(i) = GlobalLock_(hmem(i))
;Debug Str(bufferSize/1024)+"kb allocated at Memory "+Str(i)+" at adress "+Str(pmem(i))
total+(bufferSize/1024/1024)
Next
Debug StrF(total,3)+" MBs in total for "+Str(#NUM_BUFFERS)+" Buffers used"
Debug " "
fFileOpen = #True
ProcedureReturn #True
EndProcedure
Procedure Play()
Protected i.l
If fPlaying
ProcedureReturn #True
EndIf
rc = waveOutOpen_(@hWaveOut, #WAVE_MAPPER, @Format, hwnd, #Null, #CALLBACK_WINDOW)
If rc <> #MMSYSERR_NOERROR
waveOutGetErrorText_(rc, @msg, Len(msg))
Debug msg
ProcedureReturn #False
EndIf
For i = 0 To #NUM_BUFFERS-1
hdr(i)\lpData = pmem(i)
hdr(i)\dwBufferLength = bufferSize
hdr(i)\dwFlags = 0
hdr(i)\dwLoops = 0
rc = waveOutPrepareHeader_(hWaveOut, @hdr(i), SizeOf(WAVEHDR))
If rc <> #MMSYSERR_NOERROR
waveOutGetErrorText_(rc, @msg, Len(msg))
Debug msg
ProcedureReturn #False
EndIf
Next
fPlaying = #True
startPos = mmioSeek_(hmmioIn, 0, #SEEK_CUR) - DataOffset
; send a MM_WOM_DONE message to the callback so the buffers get filled and playback starts
For i = 0 To #NUM_BUFFERS-1
PostMessage_(hwnd, #MM_WOM_DONE, 0, @hdr(i))
Next
ProcedureReturn #True
EndProcedure
Procedure FileSeekTo(Position.l)
Protected bytepos.l
bytepos = Position * Format\nBlockAlign
If fFileOpen = #False Or bytepos <0>= audioLength
ProcedureReturn #False
EndIf
rc = mmioSeek_(hmmioIn, bytepos + DataOffset, #SEEK_SET)
If rc = #MMSYSERR_NOERROR
startPos = rc
ProcedureReturn #True
EndIf
EndProcedure
Procedure StopPlay()
fPlaying = #False
FileSeekTo(Position())
waveOutReset_( hWaveOut)
EndProcedure
Procedure pause()
If fPlaying
fPlaying = #False
FileSeekTo(Position())
waveOutPause_(hWaveOut)
Else
fPlaying = #True
WaveOutRestart_(hWaveOut)
EndIf
EndProcedure
Procedure.f Length() ; ... in Audioblocks!
ProcedureReturn audioLength / Format\nBlockAlign
EndProcedure
Procedure Playing()
Protected tm.MMTIME
tm\wType = #TIME_BYTES
rc = waveOutGetPosition_(hWaveOut, @tm, SizeOf(MMTIME))
If rc = #MMSYSERR_NOERROR
ProcedureReturn #True
Else
ProcedureReturn #False
EndIf
EndProcedure
Procedure WaveCallback(WindowID, message, wParam, lParam)
Protected result = #PB_ProcessPureBasicEvents
If message = #MM_WOM_DONE ; Buffers need to be refilled?
ServiceBuffers(lParam)
EndIf
ProcedureReturn result
EndProcedure
;DisableExplicit
;------------------------------------- Example --------------------------------
CompilerIf #PB_Compiler_Debugger = #False
MessageRequester("Info","Please run this example using the debugger and see its output while choosing a media file")
End
CompilerEndIf
OpenWindow(0,20,20,400,100,"PB audio streaming example using MMIO/WINMM routines.", #PB_Window_ScreenCentered|#PB_Window_SystemMenu )
SetWindowCallback(@WaveCallback())
File.s = OpenFileRequester("Choose a PCM Wave file","","Wave files (*.wav)|*.wav",0)
If File
If OpenTheFile(File,0)
Debug "Lenght: "+StrF(Length()/Format\nSamplesPerSec,3)+" sec"
Debug""
If Play()
Repeat
event = WaitWindowEvent()
lprm = EventlParam()
wprm = EventwParam()
If event = #WM_KEYDOWN
If wprm = #VK_ESCAPE ; Quit appl.
StopPlay()
CloseTheFile()
End
ElseIf wprm = #VK_LEFT ; Seeking backwards in 5% steps
temp = fPlaying
pause()
FileSeekTo(Position()-Int(Length()/20))
If temp
Play()
EndIf
ElseIf wprm = #VK_RIGHT; Seeking forward in 5% steps
temp = fPlaying
pause()
FileSeekTo(Position()+Int(Length()/20))
If temp
Play()
EndIf
ElseIf wprm = #VK_P
Debug Position()
EndIf
EndIf
Until event = #PB_Event_CloseWindow
StopPlay()
CloseTheFile()
End
Else
CloseTheFile()
Debug "error playing"
EndIf
End
Else
Debug "error initializing"
EndIf
Else
Debug "error opening"
EndIf