- 題名: Win32APIのwaveInXXXXのトラブル
- 日時: 2004/08/03 16:42:43
- ID: 5315
- この記事の返信元:
- (なし)
- この記事への返信:
- [5316] Re[1]: Win32APIのwaveInXXXXのトラブル2004/08/03 16:44:47
- ツリーを表示
― 続き ―
'バッファ領域の確保(更新)
Private Sub Update_BufferSize()
mWaveBuffer1 = Marshal.AllocHGlobal(mBufferSize)
mWaveBuffer2 = Marshal.AllocHGlobal(mBufferSize)
ReDim mMyOutPut(mBufferSize)
End Sub
'waveInOpenが引数にとるWAVEFORMATEX構造体のメンバの設定(更新)
Private Sub Update_WaveFormat()
Const WAVE_FORMAT_PCM As Short = 1S
With mWaveFormat
.wFormatTag = WAVE_FORMAT_PCM
.nChannels = 1S
.nSamplePerSecond = mSampleRate
.nAvgBytesPerSec = .nSamplePerSecond
.wBitsPerSample = 8S
.nBlockAilgn = 1S
.cbSize = 0S
End With
End Sub
'音声データ取得開始
Public Sub OpenDevice()
If mIsWaveInDeviceOpened = True Then Exit Sub
With mWaveBufferHeader1
.lpData = mWaveBuffer1
.dwBufferLength = mBufferSize
.dwBytesRecorded = 0
.dwFlags = 0
.dwLoops = 1
.lpNext = IntPtr.Zero
.dwUser = IntPtr.Zero
.Reserved = IntPtr.Zero
End With
With mWaveBufferHeader2
.lpData = mWaveBuffer2
.dwBufferLength = mBufferSize
.dwBytesRecorded = 0
.dwFlags = 0
.dwLoops = 1
.lpNext = IntPtr.Zero
.dwUser = IntPtr.Zero
.Reserved = IntPtr.Zero
End With
Dim Result As Integer
Const CALLBACK_WINDOW As Integer = &H10000
Const WAVE_MAPPER As Integer = &HFFFFFFFF
Result = waveInOpen(mDeviceHandle, WAVE_MAPPER, mWaveFormat, Me.Handle, 0, CALLBACK_WINDOW)
Result = waveInPrepareHeader(mDeviceHandle, mWaveBufferHeader1, Marshal.SizeOf(mWaveBufferHeader1))
Result = waveInPrepareHeader(mDeviceHandle, mWaveBufferHeader2, Marshal.SizeOf(mWaveBufferHeader2))
End Sub
Public Sub CloseDevice()
If mIsWaveInDeviceOpened = False Then Exit Sub
Call waveInReset(mDeviceHandle)
Call waveInClose(mDeviceHandle)
End Sub
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
Const MM_WIM_OPEN As Integer = &H3BE
Const MM_WIM_CLOSE As Integer = &H3BF
Const MM_WIM_DATA As Integer = &H3C0
Const MMSYSERR_NOERROR As Integer = 0
Dim Dbg As Integer
Select Case m.Msg
Case MM_WIM_OPEN
Dim Result As Integer = 100
mIsWaveInDeviceOpened = True
Result = waveInAddBuffer(mDeviceHandle, mWaveBufferHeader1, Marshal.SizeOf(mWaveBufferHeader1))
Result = waveInAddBuffer(mDeviceHandle, mWaveBufferHeader2, Marshal.SizeOf(mWaveBufferHeader2))
Result = waveInStart(mDeviceHandle)
Case MM_WIM_DATA
Dim Temp As WAVEHDR
Dim Result As Integer
Static DataCount As Integer = 0
DataCount += 1 : Console.WriteLine("DataCount = " & DataCount.ToString)
Temp = m.GetLParam(GetType(WAVEHDR))
If Temp.lpData.ToInt32 = mWaveBufferHeader1.lpData.ToInt32 Then
'開放されたバッファはmWaveBuffer1
Marshal.Copy(mWaveBuffer1, mMyOutPut, 0, mBufferSize)
Result = waveInAddBuffer(mDeviceHandle, mWaveBufferHeader1, Marshal.SizeOf(mWaveBufferHeader1))
If Result = MMSYSERR_NOERROR Then
Console.WriteLine("Add Buffer1 OK!")
Else
Console.WriteLine("Add Buffer1 Falied ! = " & Result.ToString & "Count = " & DataCount.ToString)
End If
ElseIf Temp.lpData.ToInt32 = mWaveBufferHeader2.lpData.ToInt32 Then
'開放されたバッファはmWaveBuffer1
Marshal.Copy(mWaveBuffer2, mMyOutPut, 0, mBufferSize)
Result = waveInAddBuffer(mDeviceHandle, mWaveBufferHeader2, Marshal.SizeOf(mWaveBufferHeader2))
If Result = MMSYSERR_NOERROR Then
Console.WriteLine("Add Buffer2 OK!")
Else
Console.WriteLine("Add Buffer2 Falied ! = " & Result.ToString & "Count = " & DataCount.ToString)
End If
Else
'受信したメッセージが指示するバッファのポインタは不正
Console.WriteLine("Invalid Pointer !")
End If
RaiseEvent WaveDataArrive(mMyOutPut)
Case MM_WIM_CLOSE
mIsWaveInDeviceOpened = False
waveInUnprepareHeader(mDeviceHandle, mWaveBufferHeader1, Marshal.SizeOf(mWaveBufferHeader1))
waveInUnprepareHeader(mDeviceHandle, mWaveBufferHeader2, Marshal.SizeOf(mWaveBufferHeader2))
Case Else
End Select
MyBase.WndProc(m)
End Sub
Public Sub New()
MyBase.New()
' この呼び出しは Windows フォーム デザイナで必要です。
InitializeComponent()
' InitializeComponent() 呼び出しの後に初期化を追加します。
Call Update_BufferSize()
Call Update_WaveFormat()
End Sub
' Form は、コンポーネント一覧に後処理を実行するために dispose をオーバーライドします。
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
Marshal.FreeHGlobal(mWaveBuffer1)
Marshal.FreeHGlobal(mWaveBuffer2)
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub
#Region " Windows フォーム デザイナで生成されたコード "
〜 略 〜
#End Region
End Class
― 続き ― メインのフォームでcWaveInFormのインスタンスを作成したあと、 コマンドボタンなど適当なタイミングでOpenDeviceメソッドをコールすると、 音声データの取得を開始し、状況を出力ウィンドウに報告します。 それで、困っている点とは ● データの取得を開始してしばらくすると、MM_WIM_DATAメッセージで OSから返されたバッファを再びキューに追加するwaveInAddBufferが WAVERR_STILLPLAYING( = 33 )を返して失敗します。(ちなみにこのエラーコードは、 「バッファがまだキューにある」という意味らしいのですが、MSDNのwaveInAddBufferの 解説には、この関数が返す値として記述されていません。) ● メインのフォームなど他のオブジェクトでWaveDataArriveイベントを受信して 引数として渡されるデータを使って何らかの処理を行うと、これを行わなかった 時に比べてwaveInAddBufferが失敗するタイミングが目に見えて早くなります。 気づいた点などありましたらご教授よろしくお願いします。 なお、そのほか判明していることを書いてみます。 ○ 音声データ取得開始時にコールされるwaveInOpen、waveInPrepareHeader及び その直後のMM_WIM_OPEN受信時にコールされるwaveInAddBuffer、waveInStartは すべてMMSYSERR_NOERROR(成功)を返し、また取得されたデータを画面にオシロスコープ状に 描画するコードを書いてみると、正しい音声の波形になっているようなので、構造体の定義や APIの宣言が不適切である可能性は低いと思います。 ○ 定数RoughFrameRateにもっと小さな値(例えば2)を設定してもやはり同じ問題がおこるので、 キューに入れるバッファのサイズが小さすぎる可能性は低いと思います。 ○ サンプリングレート(mSampleRate)は、低い値を設定した時の方が、waveInAddBufferが 失敗するタイミングが遅れるようです。 ○ WndProcでRaiseEventしているのがまずいのかとも思ったのですが、 これを削除してもやはりそのうちwaveInAddBufferが失敗します。 ○ MM_WIM_DATA受信時、WndProcを抜ける際に参考にしたサイトのサンプル通り MyBase.WndProc(m)の代わりにm.Result = IntPtr.Zeroとしても結果は変わらず。
■No5319に返信(深山さんの記事) こんにちは、返信ありがとうございます。 > ・WndProc 内の各メッセージ処理が終わった後に Return していない(MyBase に流れている) > ・OpenDevice 内で設定している dwBufferLength の値が元のコードと異なる > > というのが気になりました。 > これらは特に問題ないのでしょうか? 前者については、実は私はいわゆる「Windowsプログラミング」の経験がないので 参考にさせてもらったオリジナルのCコードのWndProcの中のReturn 0の意味が あまりよくわからないのですが、.NETでこれに相当する処理をするには、おそらく WndProcを抜けるときにMyBase.WndProc(m)の代わりにm.Result = IntPtr.Zeroを 実行してやればいいのではないかと思います。 http://www.microsoft.com/japan/msdn/library/default.asp?url=/japan/msdn/library/ja/cpref/html/frlrfsystemwindowsformsmessageclassresulttopic.asp で、実際やってみると結果に違いはないようでした。 後者については、コードの書き方が違うだけで意味的には同じだと思いますので、 恐らく問題ないのではないかと思います。(いま確認できる環境が手元にないので......。)
■No5324に返信(深山さんの記事) > はい。戻り値に関する扱いについては仰る通り、 m.Result に設定してあげれば大丈夫です。 > 私が言ったのはそういうことではなくて‥‥えっと、 return 0 とすることで戻り値を > 設定するのと同時に、関数から抜け出すというのはご存知ですよね? WndProc内のSelect...CaseのMM_WIM_OPEN、MM_WIM_DATA、MM_WIM_CLOSEの各ブロックの末尾に、 m.Result = IntPtr.Zero Exit Sub を付け加えたコードで試していますが、やはり同じようにwaveInAddBufferが 失敗する問題が発生するようでした。 > 元コードでは SRATE ( 11025 ) を設定してる箇所で、 mBufferSize > ( mSampleRate / mBufferSize = 44100 / 20 = 2205 ) を設定してるように見受けられたので。 ああ、2つのWAVEHDR構造体のdwBufferLengthメンバに設定している値が、件のサイトの サンプルコードの値と違うというご指摘ですね。よく読まずに早とちりしたようですみません。 MSDNによると、単にバッファのサイズをバイト単位で指定するだけのようですので、 実際に確保した領域より大きな値を指定してさえいなければ、たぶん問題ないのでは ないでしょうか? http://msdn.microsoft.com/library/default.asp?url=/library/en-us/multimed/htm/_win32_wavehdr_str.asp 実際、件のサンプルコードと条件を全て同じにしたものも確認していますが、 やはり同じように問題が発生するようでした。
分類:[.NET]
PCのオーディオ入力を、オシロスコープのように描画するアプリを計画しています。 下のURLのサイトとMSDNを参考にプログラムを書いているのですが、音声データの取得が うまくいかなくて困っています。(具体的な現象は後述します) http://black.sakura.ne.jp/~third/system/winapi/mm7.html とりあえず、困っている現象が再現できる(と思われる)最低限のコードを提示します。 音声データを取得するクラスのコードです。ウィンドウコールバックを使うためダミーの フォームになっているので、メインになるフォームのほかに適当な名前のフォームをプロジェクトに 追加して、そこに記述してください。(下のサンプルでは、cWaveInFormという名前の フォームクラスになっています。) 少々長いですがお許しを。 Imports System.Runtime.InteropServices Imports System.Threading Public Class cWaveInForm Inherits System.Windows.Forms.Form <StructLayout(LayoutKind.Explicit)> Public Structure WAVEHDR <FieldOffset(0)> Public lpData As IntPtr <FieldOffset(4)> Public dwBufferLength As Integer <FieldOffset(8)> Public dwBytesRecorded As Integer <FieldOffset(12)> Public dwUser As IntPtr <FieldOffset(16)> Public dwFlags As Integer <FieldOffset(20)> Public dwLoops As Integer <FieldOffset(24)> Public lpNext As IntPtr <FieldOffset(28)> Public Reserved As IntPtr End Structure <StructLayout(LayoutKind.Explicit)> Public Structure WAVEFORMATEX <FieldOffset(0)> Public wFormatTag As Short <FieldOffset(2)> Public nChannels As Short <FieldOffset(4)> Public nSamplePerSecond As Integer <FieldOffset(8)> Public nAvgBytesPerSec As Integer <FieldOffset(12)> Public nBlockAilgn As Short <FieldOffset(14)> Public wBitsPerSample As Short <FieldOffset(16)> Public cbSize As Short End Structure Private Declare Function waveInOpen Lib "winmm" (ByRef phwi As IntPtr, _ ByVal uDeviceID As Integer, _ ByRef pwfx As WAVEFORMATEX, _ ByVal dwCallBack As IntPtr, _ ByVal dwCallbackInstance As Integer, _ ByVal fdwOpen As Integer) As Integer Private Declare Function waveInPrepareHeader Lib "winmm" (ByVal hwi As IntPtr, _ ByRef pwh As WAVEHDR, _ ByVal cbwh As Integer) As Integer Private Declare Function waveInUnprepareHeader Lib "winmm" (ByVal hwi As IntPtr, _ ByRef pwh As WAVEHDR, _ ByVal cbwh As Integer) As Integer Private Declare Function waveInAddBuffer Lib "winmm" (ByVal hwi As IntPtr, _ ByRef pwh As WAVEHDR, _ ByVal cbwh As Integer) As Integer Private Declare Function waveInStart Lib "winmm" (ByVal hwi As IntPtr) As Integer Private Declare Function waveInStop Lib "winmm" (ByVal hwi As IntPtr) As Integer Private Declare Function waveInReset Lib "winmm" (ByVal hwi As IntPtr) As Integer Private Declare Function waveInClose Lib "winmm" (ByVal hwi As IntPtr) As Integer Public Enum eSampleRate F44100 = 44100 F22050 = 22050 F11025 = 11025 End Enum Public mSampleRate As eSampleRate = eSampleRate.F44100 Public Const BitRate As Integer = 8 '音声データを他のオブジェクトに渡すイベントを発生させる一秒当たりの回数の目標値 Private Const RoughFrameRate As Single = 20 'RoughFrameRateに近い値レートで(バッファがいっぱいになって) 'MM_WIM_DATAメッセージがくるようなバッファのサイズを決定。 Private mBufferSize As Integer = CInt(mSampleRate / RoughFrameRate) Private mFrameRate As Single = mSampleRate / mBufferSize Private mWaveBufferHeader1, mWaveBufferHeader2 As WAVEHDR Private mWaveBuffer1, mWaveBuffer2 As IntPtr Private mDeviceHandle As IntPtr Private mWaveFormat As WAVEFORMATEX Private mIsWaveInDeviceOpened As Boolean = False '他のオブジェクトに渡される音声データのキャッシュ Private mMyOutPut As Byte() '音声データを他のオブジェクトに渡すイベント Public Event WaveDataArrive(ByVal Wave As Byte()) ― 続く ―