1 /*
2  * $Id: dsound_wrapper.c,v 1.1 2006/04/30 16:23:59 jcr13 Exp $
3  * Simplified DirectSound interface.
4  *
5  * Author: Phil Burk & Robert Marsanyi
6  *
7  * PortAudio Portable Real-Time Audio Library
8  * For more information see: http://www.softsynth.com/portaudio/
9  * DirectSound Implementation
10  * Copyright (c) 1999-2000 Phil Burk & Robert Marsanyi
11  *
12  * Permission is hereby granted, free of charge, to any person obtaining
13  * a copy of this software and associated documentation files
14  * (the "Software"), to deal in the Software without restriction,
15  * including without limitation the rights to use, copy, modify, merge,
16  * publish, distribute, sublicense, and/or sell copies of the Software,
17  * and to permit persons to whom the Software is furnished to do so,
18  * subject to the following conditions:
19  *
20  * The above copyright notice and this permission notice shall be
21  * included in all copies or substantial portions of the Software.
22  *
23  * Any person wishing to distribute modifications to the Software is
24  * requested to send the modifications to the original developer so that
25  * they can be incorporated into the canonical version.
26  *
27  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
28  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
29  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
30  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
31  * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
32  * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
33  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34  *
35  */
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <math.h>
39 #define INITGUID     // Needed to build IID_IDirectSoundNotify. See objbase.h for info.
40 #include <objbase.h>
41 #include <unknwn.h>
42 #include "dsound_wrapper.h"
43 #include "pa_trace.h"
44 
45 /************************************************************************************/
DSW_Term(DSoundWrapper * dsw)46 void DSW_Term( DSoundWrapper *dsw )
47 {
48     // Cleanup the sound buffers
49     if (dsw->dsw_OutputBuffer)
50     {
51         IDirectSoundBuffer_Stop( dsw->dsw_OutputBuffer );
52         IDirectSoundBuffer_Release( dsw->dsw_OutputBuffer );
53         dsw->dsw_OutputBuffer = NULL;
54     }
55 #if SUPPORT_AUDIO_CAPTURE
56     if (dsw->dsw_InputBuffer)
57     {
58         IDirectSoundCaptureBuffer_Stop( dsw->dsw_InputBuffer );
59         IDirectSoundCaptureBuffer_Release( dsw->dsw_InputBuffer );
60         dsw->dsw_InputBuffer = NULL;
61     }
62     if (dsw->dsw_pDirectSoundCapture)
63     {
64         IDirectSoundCapture_Release( dsw->dsw_pDirectSoundCapture );
65         dsw->dsw_pDirectSoundCapture = NULL;
66     }
67 #endif /* SUPPORT_AUDIO_CAPTURE */
68     if (dsw->dsw_pDirectSound)
69     {
70         IDirectSound_Release( dsw->dsw_pDirectSound );
71         dsw->dsw_pDirectSound = NULL;
72     }
73 }
74 /************************************************************************************/
DSW_Init(DSoundWrapper * dsw)75 HRESULT DSW_Init( DSoundWrapper *dsw )
76 {
77     memset( dsw, 0, sizeof(DSoundWrapper) );
78     return 0;
79 }
80 /************************************************************************************/
DSW_InitOutputDevice(DSoundWrapper * dsw,LPGUID lpGUID)81 HRESULT DSW_InitOutputDevice( DSoundWrapper *dsw, LPGUID lpGUID )
82 {
83     // Create the DS object
84     HRESULT hr = DirectSoundCreate( lpGUID, &dsw->dsw_pDirectSound, NULL );
85     if( hr != DS_OK ) return hr;
86     return hr;
87 }
88 
89 /************************************************************************************/
DSW_InitOutputBuffer(DSoundWrapper * dsw,unsigned long nFrameRate,int nChannels,int bytesPerBuffer)90 HRESULT DSW_InitOutputBuffer( DSoundWrapper *dsw, unsigned long nFrameRate, int nChannels, int bytesPerBuffer )
91 {
92     DWORD          dwDataLen;
93     DWORD          playCursor;
94     HRESULT        result;
95     LPDIRECTSOUNDBUFFER pPrimaryBuffer;
96     HWND           hWnd;
97     HRESULT        hr;
98     WAVEFORMATEX   wfFormat;
99     DSBUFFERDESC   primaryDesc;
100     DSBUFFERDESC   secondaryDesc;
101     unsigned char* pDSBuffData;
102     LARGE_INTEGER  counterFrequency;
103     dsw->dsw_OutputSize = bytesPerBuffer;
104     dsw->dsw_OutputRunning = FALSE;
105     dsw->dsw_OutputUnderflows = 0;
106     dsw->dsw_FramesWritten = 0;
107     dsw->dsw_BytesPerFrame = nChannels * sizeof(short);
108     // We were using getForegroundWindow() but sometimes the ForegroundWindow may not be the
109     // applications's window. Also if that window is closed before the Buffer is closed
110     // then DirectSound can crash. (Thanks for Scott Patterson for reporting this.)
111     // So we will use GetDesktopWindow() which was suggested by Miller Puckette.
112     // hWnd = GetForegroundWindow();
113     hWnd = GetDesktopWindow();
114     // Set cooperative level to DSSCL_EXCLUSIVE so that we can get 16 bit output, 44.1 KHz.
115     // Exclusize also prevents unexpected sounds from other apps during a performance.
116     if ((hr = IDirectSound_SetCooperativeLevel( dsw->dsw_pDirectSound,
117               hWnd, DSSCL_EXCLUSIVE)) != DS_OK)
118     {
119         return hr;
120     }
121     // -----------------------------------------------------------------------
122     // Create primary buffer and set format just so we can specify our custom format.
123     // Otherwise we would be stuck with the default which might be 8 bit or 22050 Hz.
124     // Setup the primary buffer description
125     ZeroMemory(&primaryDesc, sizeof(DSBUFFERDESC));
126     primaryDesc.dwSize        = sizeof(DSBUFFERDESC);
127     primaryDesc.dwFlags       = DSBCAPS_PRIMARYBUFFER; // all panning, mixing, etc done by synth
128     primaryDesc.dwBufferBytes = 0;
129     primaryDesc.lpwfxFormat   = NULL;
130     // Create the buffer
131     if ((result = IDirectSound_CreateSoundBuffer( dsw->dsw_pDirectSound,
132                   &primaryDesc, &pPrimaryBuffer, NULL)) != DS_OK) return result;
133     // Define the buffer format
134     wfFormat.wFormatTag = WAVE_FORMAT_PCM;
135     wfFormat.nChannels = nChannels;
136     wfFormat.nSamplesPerSec = nFrameRate;
137     wfFormat.wBitsPerSample = 8 * sizeof(short);
138     wfFormat.nBlockAlign = wfFormat.nChannels * wfFormat.wBitsPerSample / 8;
139     wfFormat.nAvgBytesPerSec = wfFormat.nSamplesPerSec * wfFormat.nBlockAlign;
140     wfFormat.cbSize = 0;  /* No extended format info. */
141     // Set the primary buffer's format
142     if((result = IDirectSoundBuffer_SetFormat( pPrimaryBuffer, &wfFormat)) != DS_OK) return result;
143     // ----------------------------------------------------------------------
144     // Setup the secondary buffer description
145     ZeroMemory(&secondaryDesc, sizeof(DSBUFFERDESC));
146     secondaryDesc.dwSize = sizeof(DSBUFFERDESC);
147     secondaryDesc.dwFlags =  DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2;
148     secondaryDesc.dwBufferBytes = bytesPerBuffer;
149     secondaryDesc.lpwfxFormat = &wfFormat;
150     // Create the secondary buffer
151     if ((result = IDirectSound_CreateSoundBuffer( dsw->dsw_pDirectSound,
152                   &secondaryDesc, &dsw->dsw_OutputBuffer, NULL)) != DS_OK) return result;
153     // Lock the DS buffer
154     if ((result = IDirectSoundBuffer_Lock( dsw->dsw_OutputBuffer, 0, dsw->dsw_OutputSize, (LPVOID*)&pDSBuffData,
155                                            &dwDataLen, NULL, 0, 0)) != DS_OK) return result;
156     // Zero the DS buffer
157     ZeroMemory(pDSBuffData, dwDataLen);
158     // Unlock the DS buffer
159     if ((result = IDirectSoundBuffer_Unlock( dsw->dsw_OutputBuffer, pDSBuffData, dwDataLen, NULL, 0)) != DS_OK) return result;
160     if( QueryPerformanceFrequency( &counterFrequency ) )
161     {
162         int framesInBuffer = bytesPerBuffer / (nChannels * sizeof(short));
163         dsw->dsw_CounterTicksPerBuffer.QuadPart = (counterFrequency.QuadPart * framesInBuffer) / nFrameRate;
164         AddTraceMessage("dsw_CounterTicksPerBuffer = %d\n", dsw->dsw_CounterTicksPerBuffer.LowPart );
165     }
166     else
167     {
168         dsw->dsw_CounterTicksPerBuffer.QuadPart = 0;
169     }
170     // Let DSound set the starting write position because if we set it to zero, it looks like the
171     // buffer is full to begin with. This causes a long pause before sound starts when using large buffers.
172     hr = IDirectSoundBuffer_GetCurrentPosition( dsw->dsw_OutputBuffer, &playCursor, &dsw->dsw_WriteOffset );
173     if( hr != DS_OK )
174     {
175         return hr;
176     }
177     dsw->dsw_FramesWritten = dsw->dsw_WriteOffset / dsw->dsw_BytesPerFrame;
178     /* printf("DSW_InitOutputBuffer: playCursor = %d, writeCursor = %d\n", playCursor, dsw->dsw_WriteOffset ); */
179     return DS_OK;
180 }
181 
182 /************************************************************************************/
DSW_StartOutput(DSoundWrapper * dsw)183 HRESULT DSW_StartOutput( DSoundWrapper *dsw )
184 {
185     HRESULT        hr;
186     QueryPerformanceCounter( &dsw->dsw_LastPlayTime );
187     dsw->dsw_LastPlayCursor = 0;
188     dsw->dsw_FramesPlayed = 0;
189     hr = IDirectSoundBuffer_SetCurrentPosition( dsw->dsw_OutputBuffer, 0 );
190     if( hr != DS_OK )
191     {
192         return hr;
193     }
194     // Start the buffer playback in a loop.
195     if( dsw->dsw_OutputBuffer != NULL )
196     {
197         hr = IDirectSoundBuffer_Play( dsw->dsw_OutputBuffer, 0, 0, DSBPLAY_LOOPING );
198         if( hr != DS_OK )
199         {
200             return hr;
201         }
202         dsw->dsw_OutputRunning = TRUE;
203     }
204 
205     return 0;
206 }
207 /************************************************************************************/
DSW_StopOutput(DSoundWrapper * dsw)208 HRESULT DSW_StopOutput( DSoundWrapper *dsw )
209 {
210     // Stop the buffer playback
211     if( dsw->dsw_OutputBuffer != NULL )
212     {
213         dsw->dsw_OutputRunning = FALSE;
214         return IDirectSoundBuffer_Stop( dsw->dsw_OutputBuffer );
215     }
216     else return 0;
217 }
218 
219 /************************************************************************************/
DSW_QueryOutputSpace(DSoundWrapper * dsw,long * bytesEmpty)220 HRESULT DSW_QueryOutputSpace( DSoundWrapper *dsw, long *bytesEmpty )
221 {
222     HRESULT hr;
223     DWORD   playCursor;
224     DWORD   writeCursor;
225     long    numBytesEmpty;
226     long    playWriteGap;
227     // Query to see how much room is in buffer.
228     // Note: Even though writeCursor is not used, it must be passed to prevent DirectSound from dieing
229     // under WinNT. The Microsoft documentation says we can pass NULL but apparently not.
230     // Thanks to Max Rheiner for the fix.
231     hr = IDirectSoundBuffer_GetCurrentPosition( dsw->dsw_OutputBuffer, &playCursor, &writeCursor );
232     if( hr != DS_OK )
233     {
234         return hr;
235     }
236     AddTraceMessage("playCursor", playCursor);
237     AddTraceMessage("dsw_WriteOffset", dsw->dsw_WriteOffset);
238     // Determine size of gap between playIndex and WriteIndex that we cannot write into.
239     playWriteGap = writeCursor - playCursor;
240     if( playWriteGap < 0 ) playWriteGap += dsw->dsw_OutputSize; // unwrap
241     /* DirectSound doesn't have a large enough playCursor so we cannot detect wrap-around. */
242     /* Attempt to detect playCursor wrap-around and correct it. */
243     if( dsw->dsw_OutputRunning && (dsw->dsw_CounterTicksPerBuffer.QuadPart != 0) )
244     {
245         /* How much time has elapsed since last check. */
246         LARGE_INTEGER   currentTime;
247         LARGE_INTEGER   elapsedTime;
248         long            bytesPlayed;
249         long            bytesExpected;
250         long            buffersWrapped;
251         QueryPerformanceCounter( &currentTime );
252         elapsedTime.QuadPart = currentTime.QuadPart - dsw->dsw_LastPlayTime.QuadPart;
253         dsw->dsw_LastPlayTime = currentTime;
254         /* How many bytes does DirectSound say have been played. */
255         bytesPlayed = playCursor - dsw->dsw_LastPlayCursor;
256         if( bytesPlayed < 0 ) bytesPlayed += dsw->dsw_OutputSize; // unwrap
257         dsw->dsw_LastPlayCursor = playCursor;
258         /* Calculate how many bytes we would have expected to been played by now. */
259         bytesExpected = (long) ((elapsedTime.QuadPart * dsw->dsw_OutputSize) / dsw->dsw_CounterTicksPerBuffer.QuadPart);
260         buffersWrapped = (bytesExpected - bytesPlayed) / dsw->dsw_OutputSize;
261         if( buffersWrapped > 0 )
262         {
263             AddTraceMessage("playCursor wrapped! bytesPlayed", bytesPlayed );
264             AddTraceMessage("playCursor wrapped! bytesExpected", bytesExpected );
265             playCursor += (buffersWrapped * dsw->dsw_OutputSize);
266             bytesPlayed += (buffersWrapped * dsw->dsw_OutputSize);
267         }
268         /* Maintain frame output cursor. */
269         dsw->dsw_FramesPlayed += (bytesPlayed / dsw->dsw_BytesPerFrame);
270     }
271     numBytesEmpty = playCursor - dsw->dsw_WriteOffset;
272     if( numBytesEmpty < 0 ) numBytesEmpty += dsw->dsw_OutputSize; // unwrap offset
273     /* Have we underflowed? */
274     if( numBytesEmpty > (dsw->dsw_OutputSize - playWriteGap) )
275     {
276         if( dsw->dsw_OutputRunning )
277         {
278             dsw->dsw_OutputUnderflows += 1;
279             AddTraceMessage("underflow detected! numBytesEmpty", numBytesEmpty );
280         }
281         dsw->dsw_WriteOffset = writeCursor;
282         numBytesEmpty = dsw->dsw_OutputSize - playWriteGap;
283     }
284     *bytesEmpty = numBytesEmpty;
285     return hr;
286 }
287 
288 /************************************************************************************/
DSW_ZeroEmptySpace(DSoundWrapper * dsw)289 HRESULT DSW_ZeroEmptySpace( DSoundWrapper *dsw )
290 {
291     HRESULT hr;
292     LPBYTE lpbuf1 = NULL;
293     LPBYTE lpbuf2 = NULL;
294     DWORD dwsize1 = 0;
295     DWORD dwsize2 = 0;
296     long  bytesEmpty;
297     hr = DSW_QueryOutputSpace( dsw, &bytesEmpty ); // updates dsw_FramesPlayed
298     if (hr != DS_OK) return hr;
299     if( bytesEmpty == 0 ) return DS_OK;
300     // Lock free space in the DS
301     hr = IDirectSoundBuffer_Lock( dsw->dsw_OutputBuffer, dsw->dsw_WriteOffset, bytesEmpty, (void **) &lpbuf1, &dwsize1,
302                                   (void **) &lpbuf2, &dwsize2, 0);
303     if (hr == DS_OK)
304     {
305         // Copy the buffer into the DS
306         ZeroMemory(lpbuf1, dwsize1);
307         if(lpbuf2 != NULL)
308         {
309             ZeroMemory(lpbuf2, dwsize2);
310         }
311         // Update our buffer offset and unlock sound buffer
312         dsw->dsw_WriteOffset = (dsw->dsw_WriteOffset + dwsize1 + dwsize2) % dsw->dsw_OutputSize;
313         IDirectSoundBuffer_Unlock( dsw->dsw_OutputBuffer, lpbuf1, dwsize1, lpbuf2, dwsize2);
314         dsw->dsw_FramesWritten += bytesEmpty / dsw->dsw_BytesPerFrame;
315     }
316     return hr;
317 }
318 
319 /************************************************************************************/
DSW_WriteBlock(DSoundWrapper * dsw,char * buf,long numBytes)320 HRESULT DSW_WriteBlock( DSoundWrapper *dsw, char *buf, long numBytes )
321 {
322     HRESULT hr;
323     LPBYTE lpbuf1 = NULL;
324     LPBYTE lpbuf2 = NULL;
325     DWORD dwsize1 = 0;
326     DWORD dwsize2 = 0;
327     // Lock free space in the DS
328     hr = IDirectSoundBuffer_Lock( dsw->dsw_OutputBuffer, dsw->dsw_WriteOffset, numBytes, (void **) &lpbuf1, &dwsize1,
329                                   (void **) &lpbuf2, &dwsize2, 0);
330     if (hr == DS_OK)
331     {
332         // Copy the buffer into the DS
333         CopyMemory(lpbuf1, buf, dwsize1);
334         if(lpbuf2 != NULL)
335         {
336             CopyMemory(lpbuf2, buf+dwsize1, dwsize2);
337         }
338         // Update our buffer offset and unlock sound buffer
339         dsw->dsw_WriteOffset = (dsw->dsw_WriteOffset + dwsize1 + dwsize2) % dsw->dsw_OutputSize;
340         IDirectSoundBuffer_Unlock( dsw->dsw_OutputBuffer, lpbuf1, dwsize1, lpbuf2, dwsize2);
341         dsw->dsw_FramesWritten += numBytes / dsw->dsw_BytesPerFrame;
342     }
343     return hr;
344 }
345 
346 /************************************************************************************/
DSW_GetOutputStatus(DSoundWrapper * dsw)347 DWORD DSW_GetOutputStatus( DSoundWrapper *dsw )
348 {
349     DWORD status;
350     if (IDirectSoundBuffer_GetStatus( dsw->dsw_OutputBuffer, &status ) != DS_OK)
351         return( DSERR_INVALIDPARAM );
352     else
353         return( status );
354 }
355 
356 #if SUPPORT_AUDIO_CAPTURE
357 /* These routines are used to support audio input.
358  * Do NOT compile these calls when using NT4 because it does
359  * not support the entry points.
360  */
361 /************************************************************************************/
DSW_InitInputDevice(DSoundWrapper * dsw,LPGUID lpGUID)362 HRESULT DSW_InitInputDevice( DSoundWrapper *dsw, LPGUID lpGUID )
363 {
364     HRESULT hr = DirectSoundCaptureCreate(  lpGUID, &dsw->dsw_pDirectSoundCapture,   NULL );
365     if( hr != DS_OK ) return hr;
366     return hr;
367 }
368 /************************************************************************************/
DSW_InitInputBuffer(DSoundWrapper * dsw,unsigned long nFrameRate,int nChannels,int bytesPerBuffer)369 HRESULT DSW_InitInputBuffer( DSoundWrapper *dsw, unsigned long nFrameRate, int nChannels, int bytesPerBuffer )
370 {
371     DSCBUFFERDESC  captureDesc;
372     WAVEFORMATEX   wfFormat;
373     HRESULT        result;
374     // Define the buffer format
375     wfFormat.wFormatTag      = WAVE_FORMAT_PCM;
376     wfFormat.nChannels       = nChannels;
377     wfFormat.nSamplesPerSec  = nFrameRate;
378     wfFormat.wBitsPerSample  = 8 * sizeof(short);
379     wfFormat.nBlockAlign     = wfFormat.nChannels * (wfFormat.wBitsPerSample / 8);
380     wfFormat.nAvgBytesPerSec = wfFormat.nSamplesPerSec * wfFormat.nBlockAlign;
381     wfFormat.cbSize          = 0;   /* No extended format info. */
382     dsw->dsw_InputSize = bytesPerBuffer;
383     // ----------------------------------------------------------------------
384     // Setup the secondary buffer description
385     ZeroMemory(&captureDesc, sizeof(DSCBUFFERDESC));
386     captureDesc.dwSize = sizeof(DSCBUFFERDESC);
387     captureDesc.dwFlags =  0;
388     captureDesc.dwBufferBytes = bytesPerBuffer;
389     captureDesc.lpwfxFormat = &wfFormat;
390     // Create the capture buffer
391     if ((result = IDirectSoundCapture_CreateCaptureBuffer( dsw->dsw_pDirectSoundCapture,
392                   &captureDesc, &dsw->dsw_InputBuffer, NULL)) != DS_OK) return result;
393     dsw->dsw_ReadOffset = 0;  // reset last read position to start of buffer
394     return DS_OK;
395 }
396 
397 /************************************************************************************/
DSW_StartInput(DSoundWrapper * dsw)398 HRESULT DSW_StartInput( DSoundWrapper *dsw )
399 {
400     // Start the buffer playback
401     if( dsw->dsw_InputBuffer != NULL )
402     {
403         return IDirectSoundCaptureBuffer_Start( dsw->dsw_InputBuffer, DSCBSTART_LOOPING );
404     }
405     else return 0;
406 }
407 
408 /************************************************************************************/
DSW_StopInput(DSoundWrapper * dsw)409 HRESULT DSW_StopInput( DSoundWrapper *dsw )
410 {
411     // Stop the buffer playback
412     if( dsw->dsw_InputBuffer != NULL )
413     {
414         return IDirectSoundCaptureBuffer_Stop( dsw->dsw_InputBuffer );
415     }
416     else return 0;
417 }
418 
419 /************************************************************************************/
DSW_QueryInputFilled(DSoundWrapper * dsw,long * bytesFilled)420 HRESULT DSW_QueryInputFilled( DSoundWrapper *dsw, long *bytesFilled )
421 {
422     HRESULT hr;
423     DWORD capturePos;
424     DWORD readPos;
425     long  filled;
426     // Query to see how much data is in buffer.
427     // We don't need the capture position but sometimes DirectSound doesn't handle NULLS correctly
428     // so let's pass a pointer just to be safe.
429     hr = IDirectSoundCaptureBuffer_GetCurrentPosition( dsw->dsw_InputBuffer, &capturePos, &readPos );
430     if( hr != DS_OK )
431     {
432         return hr;
433     }
434     filled = readPos - dsw->dsw_ReadOffset;
435     if( filled < 0 ) filled += dsw->dsw_InputSize; // unwrap offset
436     *bytesFilled = filled;
437     return hr;
438 }
439 
440 /************************************************************************************/
DSW_ReadBlock(DSoundWrapper * dsw,char * buf,long numBytes)441 HRESULT DSW_ReadBlock( DSoundWrapper *dsw, char *buf, long numBytes )
442 {
443     HRESULT hr;
444     LPBYTE lpbuf1 = NULL;
445     LPBYTE lpbuf2 = NULL;
446     DWORD dwsize1 = 0;
447     DWORD dwsize2 = 0;
448     // Lock free space in the DS
449     hr = IDirectSoundCaptureBuffer_Lock ( dsw->dsw_InputBuffer, dsw->dsw_ReadOffset, numBytes, (void **) &lpbuf1, &dwsize1,
450                                           (void **) &lpbuf2, &dwsize2, 0);
451     if (hr == DS_OK)
452     {
453         // Copy from DS to the buffer
454         CopyMemory( buf, lpbuf1, dwsize1);
455         if(lpbuf2 != NULL)
456         {
457             CopyMemory( buf+dwsize1, lpbuf2, dwsize2);
458         }
459         // Update our buffer offset and unlock sound buffer
460         dsw->dsw_ReadOffset = (dsw->dsw_ReadOffset + dwsize1 + dwsize2) % dsw->dsw_InputSize;
461         IDirectSoundCaptureBuffer_Unlock ( dsw->dsw_InputBuffer, lpbuf1, dwsize1, lpbuf2, dwsize2);
462     }
463     return hr;
464 }
465 
466 #endif /* SUPPORT_AUDIO_CAPTURE */
467