问题
This code works to output a piano tone for 2 seconds using winmm.dll via platform invocation services, it seems to work fine on XP but waveoutopen fails in windows 7 rc
updated based on feedback from John Knoeller
Imports System.Runtime.InteropServices Public Class AudioStream
<StructLayout(LayoutKind.Sequential)> _
Private Structure WAVEHDR
Public lpData As Integer
Public dwBufferLength As Integer
Public dwBytesRecorded As Integer
Public dwUser As Integer
Public dwFlags As Integer
Public dwLoops As Integer
Public lpNext As Integer
Public Reserved As Integer
End Structure
<StructLayout(LayoutKind.Sequential)> _
Private Structure WAVEFORMATEX
Public wFormatTag As Int16
Public nChannels As Int16
Public nSamplesPerSec As Int32
Public nAvgBytesPerSec As Int32
Public nBlockAlign As Int16
Public wBitsPerSample As Int16
Public cbSize As Int16
End Structure
Private Declare Function waveOutOpen Lib "winmm.dll" (ByRef lphWaveOut As Int32, ByVal uDeviceID As Int32, ByRef lpFormat As WAVEFORMATEX, ByVal dwCallback As WaveDelegate, ByVal dwInstance As Int32, ByVal dwFlags As Int32) As Int32
Private Declare Function waveOutClose Lib "winmm.dll" (ByVal hWaveOut As Int32) As Int32
Private Declare Function waveOutPrepareHeader Lib "winmm.dll" (ByVal hWaveOut As Int32, ByRef lpWaveOutHdr As WAVEHDR, ByVal uSize As Int32) As Int32
Private Declare Function waveOutUnprepareHeader Lib "winmm.dll" (ByVal hWaveOut As Int32, ByRef lpWaveOutHdr As WAVEHDR, ByVal uSize As Int32) As Int32
Private Declare Function waveOutWrite Lib "winmm.dll" (ByVal hWaveOut As Int32, ByRef lpWaveOutHdr As WAVEHDR, ByVal uSize As Int32) As Int32
Private Delegate Sub WaveDelegate(ByVal hwo As IntPtr, ByVal uMsg As Integer, ByVal dwInstance As Integer, ByRef wavhdr As WAVEHDR, ByVal dwParam2 As Integer)
Private Const WAVE_MAPPER = -1&
Private Const WAVE_FORMAT_PCM = 1
Private Const CALLBACK_FUNCTION = &H30000 ' to set up a callback to a function
Private Const WHDR_DONE = &H1 ' done bit
Private Const WHDR_PREPARED = &H2 ' set if this header has been prepared
Private Const WHDR_BEGINLOOP = &H4 ' loop start block
Private Const WHDR_ENDLOOP = &H8 ' loop end block
Private Const WHDR_INQUEUE = &H10 ' reserved for driver
Private Const MM_WOM_OPEN = &H3BB ' waveform output
Private Const MM_WOM_CLOSE = &H3BC
Private Const MM_WOM_DONE = &H3BD
Private Const WOM_OPEN = MM_WOM_OPEN
Private Const WOM_CLOSE = MM_WOM_CLOSE
Private Const WOM_DONE = MM_WOM_DONE
Private Const MMSYSERR_BASE = 0
Private Const MMSYSERR_NOERROR = 0 ' no error
Private Const MMSYSERR_ERROR = (MMSYSERR_BASE + 1) ' unspecified error
Private Const MMSYSERR_BADDEVICEID = (MMSYSERR_BASE + 2) ' device ID out of range
Private Const MMSYSERR_NOTENABLED = (MMSYSERR_BASE + 3) ' driver failed enable
Private Const MMSYSERR_ALLOCATED = (MMSYSERR_BASE + 4) ' device already allocated
Private Const MMSYSERR_INVALHANDLE = (MMSYSERR_BASE + 5) ' device handle is invalid
Private Const MMSYSERR_NODRIVER = (MMSYSERR_BASE + 6) ' no device driver present
Private Const MMSYSERR_NOMEM = (MMSYSERR_BASE + 7) ' memory allocation error
Private Const MMSYSERR_NOTSUPPORTED = (MMSYSERR_BASE + 8) ' function isn't supported
Private Const MMSYSERR_BADERRNUM = (MMSYSERR_BASE + 9) ' error value out of range
Private Const MMSYSERR_INVALFLAG = (MMSYSERR_BASE + 10) ' invalid flag passed
Private Const MMSYSERR_INVALPARAM = (MMSYSERR_BASE + 11) ' invalid parameter passed
Private Const MMSYSERR_HANDLEBUSY = (MMSYSERR_BASE + 12) ' handle being used simultaneously on another thread (eg callback) */
Private Const MMSYSERR_INVALIDALIAS = (MMSYSERR_BASE + 13) ' specified alias not found
Private Const MMSYSERR_BADDB = (MMSYSERR_BASE + 14) ' bad registry database
Private Const MMSYSERR_KEYNOTFOUND = (MMSYSERR_BASE + 15) ' registry key not found
Private Const MMSYSERR_READERROR = (MMSYSERR_BASE + 16) ' registry read error
Private Const MMSYSERR_WRITEERROR = (MMSYSERR_BASE + 17) ' registry write error
Private Const MMSYSERR_DELETEERROR = (MMSYSERR_BASE + 18) ' registry delete error
Private Const MMSYSERR_VALNOTFOUND = (MMSYSERR_BASE + 19) ' registry value not found
Private Const MMSYSERR_NODRIVERCB = (MMSYSERR_BASE + 20) ' driver does not call DriverCallback
Private Const MMSYSERR_MOREDATA = (MMSYSERR_BASE + 21) ' more data to be returned
Private Const MMSYSERR_LASTERROR = (MMSYSERR_BASE + 21) ' last error in range
Private Const WAVERR_BASE = 32
Private Const WAVERR_BADFORMAT = (WAVERR_BASE + 0) ' unsupported wave format
Private Const WAVERR_STILLPLAYING = (WAVERR_BASE + 1) ' still something playing
Private Const WAVERR_UNPREPARED = (WAVERR_BASE + 2) ' header not prepared
Private Const WAVERR_SYNC = (WAVERR_BASE + 3) ' device is synchronous
Private Const WAVERR_LASTERROR = (WAVERR_BASE + 3) ' last error in range
Private FinishedPlaying As Boolean ' local flag to track playback status
Private mCallBack As WaveDelegate = AddressOf WaveCallBack ' function pointer to our callback function
Private pmem As IntPtr ' heap memory pointer
''' <summary>
''' Play a tone of a specified hz frequency for a specified duration in seconds
''' </summary>
Public Sub Play(ByVal Frequency As Single, ByVal Seconds As Single)
Dim wavFormat As WAVEFORMATEX
Dim wavHead As WAVEHDR
Dim hWaveOut As Int32
With wavFormat
.wFormatTag = WAVE_FORMAT_PCM
.nChannels = 2
.wBitsPerSample = 16
.nSamplesPerSec = 44100
.nBlockAlign = .nChannels * .wBitsPerSample / 8
.nAvgBytesPerSec = .nBlockAlign * .nSamplesPerSec
End With
Dim BufferSamples As Integer = wavFormat.nSamplesPerSec * Seconds
Dim BufferBytes As Integer = BufferSamples * wavFormat.nBlockAlign
'allocate memory on the heap
pmem = Marshal.AllocHGlobal(BufferBytes)
With wavHead
.lpData = pmem.ToInt32
.dwBufferLength = BufferBytes
End With
waveOutOpen(hWaveOut, WAVE_MAPPER, wavFormat, mCallBack, 0, CALLBACK_FUNCTION)
waveOutPrepareHeader(hWaveOut, wavHead, Len(wavHead))
FinishedPlaying = False
' fill buffer with specific frequency
Dim SamplesPerCycle As Double = wavFormat.nSamplesPerSec / Frequency
For i As Integer = 0 To BufferSamples - 1
' 16-bit samples are stored as 2's-complement signed integers, ranging from -32768 to 32767
Dim RotationPercent As Double = (i Mod SamplesPerCycle) / SamplesPerCycle
Dim RotationRadians As Double = RotationPercent * Math.PI * 2
Dim SampleValue As Int16 = Math.Sin(RotationRadians) * Int16.MaxValue
' blocks are 4 bytes - 2 bytes for left channel then 2 bytes for right channel
' left channel
Marshal.WriteInt16(pmem, i * wavFormat.nBlockAlign, SampleValue)
' right channel
Marshal.WriteInt16(pmem, (i * wavFormat.nBlockAlign) + 2, SampleValue)
Next
' play buffer
waveOutWrite(hWaveOut, wavHead, Len(wavHead))
Do While (Not FinishedPlaying)
Application.DoEvents()
Loop
waveOutUnprepareHeader(hWaveOut, wavHead, Len(wavHead))
waveOutClose(hWaveOut)
'free memory we allocated on the heap
Marshal.FreeHGlobal(pmem)
End Sub
''' <summary>
''' This is our handler for the waveout API callback
''' </summary>
Private Sub WaveCallBack(ByVal hwo As IntPtr, ByVal uMsg As Integer, ByVal dwInstance As Integer, ByRef wavhdr As WAVEHDR, ByVal dwParam2 As Integer)
Select Case uMsg
Case MM_WOM_OPEN
Debug.WriteLine("Open")
Case WOM_DONE
FinishedPlaying = True
Case Else
Debug.WriteLine(uMsg)
End Select
End Sub
''' <summary>
''' This is a convienient entry point to allow the class to be executed standalone (by configuring project properties)
''' </summary>
Public Shared Sub Main()
Dim BeatsPerMinute As Double = 120
Dim BeatsPerSecond As Double = BeatsPerMinute / 60
Dim ScaleSteps() As Integer = {0, 2, 2, 1, 2, 2, 2, 1} ' tone steps for major scale
Dim MyAudioStream As New AudioStream
Dim ToneFrequency As Double = 261.626 ' 261.626hz middle c piano tone
For t As Integer = 0 To ScaleSteps.Length - 1
For s As Integer = 1 To ScaleSteps(t)
ToneFrequency *= 1.05946309435929 ' Twelfth root of two for next tone
Next
MyAudioStream.Play(ToneFrequency, 1 / BeatsPerSecond) ' play tone for one second
Next
End Sub
End Class
回答1:
Your buffer is too small, change this
Dim BufferSize As Integer = 44100 * Length - 1
to this
Dim BufferSize As Integer = 44100 * Length * 2 * 2
...
For i As Integer = 0 To (BufferSize/(2*2)) - 1
Edit: I don't see how this works at all, you declare the audio as 44khz, 16 bit stereo.
but then you fill the audio buffer with 32bit mono. So the size of the data works out, but the audio data itself is wrong.
Also, nBlockAlign should be number of bytes per sample * number of channels.
.nBlockAlign = 2*2
Edit2: I keep finding more problems. I'm not a VB guy, but I'll try and write out what the code should look like, you may have to fix syntax errors.
With wavFormat
.wFormatTag = WAVE_FORMAT_PCM
.nChannels = 2
.wBitsPerSample = 16
.nSamplesPerSec = 44100
.nBlockAlign = .nChannels * .wBitsPerSample/8
.nAvgBytesPerSec = .nBlockAlign * .nSamplesPerSec
End With
Dim BufferSamples As Integer = 44100 * Length
Dim BufferBytes As Integer = BufferSamples * wavFormat.nBlockAlign
pmem = Marshal.AllocHGlobal(BufferBytes)
With wavHead
.lpData = pmem.ToInt32
.dwBufferLength = BufferBytes
End With
waveOutOpen(hWaveOut, WAVE_MAPPER, wavFormat, mCallBack, 0, CALLBACK_FUNCTION)
waveOutPrepareHeader(hWaveOut, wavHead, Len(wavHead))
FinishedPlaying = False
' fill buffer
' Specific frequency:
FreqConst = 44100 / (Math.PI * 2) / Freq
For i As Integer = 0 To BufferSamples - 1
Dim IntValue As Int16 = Math.Sin((i Mod 44100) / FreqConst)
Marshal.WriteInt16(pmem, i*4, IntValue)
Marshal.WriteInt16(pmem, i*4+2, IntValue)
Next
来源:https://stackoverflow.com/questions/2320860/waveout-sometimes-memory-issue