/*
 * Unit tests for capture functions
 *
 * Copyright (c) 2002 Francois Gouget
 * Copyright (c) 2003 Robert Reif
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#define COBJMACROS
#include <stdio.h>
#include "initguid.h"
#include "windows.h"
#include "wine/test.h"
#include "mmsystem.h"
#include "dsound.h"
#include "dsconf.h"

#include "dsound_test.h"

#define TIMESTAMP_NOTIFICATIONS     5
#define TOTAL_NOTIFICATIONS         (TIMESTAMP_NOTIFICATIONS + 1)

static const char * get_format_str(WORD format)
{
    static char msg[32];
#define WAVE_FORMAT(f) case f: return #f
    switch (format) {
    WAVE_FORMAT(WAVE_FORMAT_PCM);
    WAVE_FORMAT(WAVE_FORMAT_ADPCM);
    WAVE_FORMAT(WAVE_FORMAT_IBM_CVSD);
    WAVE_FORMAT(WAVE_FORMAT_ALAW);
    WAVE_FORMAT(WAVE_FORMAT_MULAW);
    WAVE_FORMAT(WAVE_FORMAT_OKI_ADPCM);
    WAVE_FORMAT(WAVE_FORMAT_IMA_ADPCM);
    WAVE_FORMAT(WAVE_FORMAT_MEDIASPACE_ADPCM);
    WAVE_FORMAT(WAVE_FORMAT_SIERRA_ADPCM);
    WAVE_FORMAT(WAVE_FORMAT_G723_ADPCM);
    WAVE_FORMAT(WAVE_FORMAT_DIGISTD);
    WAVE_FORMAT(WAVE_FORMAT_DIGIFIX);
    WAVE_FORMAT(WAVE_FORMAT_DIALOGIC_OKI_ADPCM);
    WAVE_FORMAT(WAVE_FORMAT_YAMAHA_ADPCM);
    WAVE_FORMAT(WAVE_FORMAT_SONARC);
    WAVE_FORMAT(WAVE_FORMAT_DSPGROUP_TRUESPEECH);
    WAVE_FORMAT(WAVE_FORMAT_ECHOSC1);
    WAVE_FORMAT(WAVE_FORMAT_AUDIOFILE_AF36);
    WAVE_FORMAT(WAVE_FORMAT_APTX);
    WAVE_FORMAT(WAVE_FORMAT_AUDIOFILE_AF10);
    WAVE_FORMAT(WAVE_FORMAT_DOLBY_AC2);
    WAVE_FORMAT(WAVE_FORMAT_GSM610);
    WAVE_FORMAT(WAVE_FORMAT_ANTEX_ADPCME);
    WAVE_FORMAT(WAVE_FORMAT_CONTROL_RES_VQLPC);
    WAVE_FORMAT(WAVE_FORMAT_DIGIREAL);
    WAVE_FORMAT(WAVE_FORMAT_DIGIADPCM);
    WAVE_FORMAT(WAVE_FORMAT_CONTROL_RES_CR10);
    WAVE_FORMAT(WAVE_FORMAT_NMS_VBXADPCM);
    WAVE_FORMAT(WAVE_FORMAT_G721_ADPCM);
    WAVE_FORMAT(WAVE_FORMAT_MPEG);
    WAVE_FORMAT(WAVE_FORMAT_MPEGLAYER3);
    WAVE_FORMAT(WAVE_FORMAT_CREATIVE_ADPCM);
    WAVE_FORMAT(WAVE_FORMAT_CREATIVE_FASTSPEECH8);
    WAVE_FORMAT(WAVE_FORMAT_CREATIVE_FASTSPEECH10);
    WAVE_FORMAT(WAVE_FORMAT_FM_TOWNS_SND);
    WAVE_FORMAT(WAVE_FORMAT_OLIGSM);
    WAVE_FORMAT(WAVE_FORMAT_OLIADPCM);
    WAVE_FORMAT(WAVE_FORMAT_OLICELP);
    WAVE_FORMAT(WAVE_FORMAT_OLISBC);
    WAVE_FORMAT(WAVE_FORMAT_OLIOPR);
    WAVE_FORMAT(WAVE_FORMAT_DEVELOPMENT);
    WAVE_FORMAT(WAVE_FORMAT_EXTENSIBLE);
    }
#undef WAVE_FORMAT
    sprintf(msg, "Unknown(0x%04x)", format);
    return msg;
}

const char * format_string(const WAVEFORMATEX* wfx)
{
    static char str[64];

    sprintf(str, "%5ldx%2dx%d %s",
	wfx->nSamplesPerSec, wfx->wBitsPerSample, wfx->nChannels,
        get_format_str(wfx->wFormatTag));

    return str;
}

static void IDirectSoundCapture_test(LPDIRECTSOUNDCAPTURE dsco,
                                     BOOL initialized, LPCGUID lpGuid)
{
    HRESULT rc;
    DSCCAPS dsccaps;
    int ref;
    IUnknown * unknown;
    IDirectSoundCapture * dsc;

    /* Try to Query for objects */
    rc=IDirectSoundCapture_QueryInterface(dsco, &IID_IUnknown,
                                          (LPVOID*)&unknown);
    ok(rc==DS_OK, "IDirectSoundCapture_QueryInterface(IID_IUnknown) "
       "failed: %08lx\n", rc);
    if (rc==DS_OK)
        IUnknown_Release(unknown);

    rc=IDirectSoundCapture_QueryInterface(dsco, &IID_IDirectSoundCapture,
                                          (LPVOID*)&dsc);
    ok(rc==DS_OK, "IDirectSoundCapture_QueryInterface(IID_IDirectSoundCapture) "
       "failed: %08lx\n", rc);
    if (rc==DS_OK)
        IDirectSoundCapture_Release(dsc);

    if (initialized == FALSE) {
        /* try uninitialized object */
        rc=IDirectSoundCapture_GetCaps(dsco,0);
        ok(rc==DSERR_UNINITIALIZED||rc==E_INVALIDARG,
           "IDirectSoundCapture_GetCaps(NULL) should have returned "
           "DSERR_UNINITIALIZED or E_INVALIDARG, returned: %08lx\n", rc);

        rc=IDirectSoundCapture_GetCaps(dsco, &dsccaps);
        ok(rc==DSERR_UNINITIALIZED,"IDirectSoundCapture_GetCaps() "
           "should have returned DSERR_UNINITIALIZED, returned: %08lx\n", rc);

        rc=IDirectSoundCapture_Initialize(dsco, lpGuid);
        ok(rc==DS_OK||rc==DSERR_NODRIVER||rc==DSERR_ALLOCATED||
           rc==E_FAIL||rc==E_INVALIDARG,
           "IDirectSoundCapture_Initialize() failed: %08lx\n", rc);
        if (rc==DSERR_NODRIVER||rc==E_INVALIDARG) {
            trace("  No Driver\n");
            goto EXIT;
        } else if (rc==E_FAIL) {
            trace("  No Device\n");
            goto EXIT;
        } else if (rc==DSERR_ALLOCATED) {
            trace("  Already In Use\n");
            goto EXIT;
        }
    }

    rc=IDirectSoundCapture_Initialize(dsco, lpGuid);
    ok(rc==DSERR_ALREADYINITIALIZED, "IDirectSoundCapture_Initialize() "
       "should have returned DSERR_ALREADYINITIALIZED: %08lx\n", rc);

    /* DSOUND: Error: Invalid caps buffer */
    rc=IDirectSoundCapture_GetCaps(dsco, 0);
    ok(rc==DSERR_INVALIDPARAM, "IDirectSoundCapture_GetCaps(NULL) "
       "should have returned DSERR_INVALIDPARAM, returned: %08lx\n", rc);

    ZeroMemory(&dsccaps, sizeof(dsccaps));

    /* DSOUND: Error: Invalid caps buffer */
    rc=IDirectSoundCapture_GetCaps(dsco, &dsccaps);
    ok(rc==DSERR_INVALIDPARAM, "IDirectSound_GetCaps() "
       "should have returned DSERR_INVALIDPARAM, returned: %08lx\n", rc);

    dsccaps.dwSize=sizeof(dsccaps);

    /* DSOUND: Running on a certified driver */
    rc=IDirectSoundCapture_GetCaps(dsco, &dsccaps);
    ok(rc==DS_OK, "IDirectSoundCapture_GetCaps() failed: %08lx\n", rc);

EXIT:
    ref=IDirectSoundCapture_Release(dsco);
    ok(ref==0, "IDirectSoundCapture_Release() has %d references, "
       "should have 0\n", ref);
}

static void test_capture(void)
{
    HRESULT rc;
    LPDIRECTSOUNDCAPTURE dsco=NULL;
    LPCLASSFACTORY cf=NULL;

    trace("Testing IDirectSoundCapture\n");

    rc=CoGetClassObject(&CLSID_DirectSoundCapture, CLSCTX_INPROC_SERVER, NULL,
                        &IID_IClassFactory, (void**)&cf);
    ok(rc==S_OK,"CoGetClassObject(CLSID_DirectSoundCapture, IID_IClassFactory) "
       "failed: %08lx\n", rc);

    rc=CoGetClassObject(&CLSID_DirectSoundCapture, CLSCTX_INPROC_SERVER, NULL,
                        &IID_IUnknown, (void**)&cf);
    ok(rc==S_OK,"CoGetClassObject(CLSID_DirectSoundCapture, IID_IUnknown) "
       "failed: %08lx\n", rc);

    /* try the COM class factory method of creation with no device specified */
    rc=CoCreateInstance(&CLSID_DirectSoundCapture, NULL, CLSCTX_INPROC_SERVER,
                        &IID_IDirectSoundCapture, (void**)&dsco);
    ok(rc==S_OK||rc==REGDB_E_CLASSNOTREG,"CoCreateInstance(CLSID_DirectSoundCapture) failed: %08lx\n", rc);
    if (rc==REGDB_E_CLASSNOTREG) {
        trace("  Class Not Registered\n");
        return;
    }
    if (dsco)
        IDirectSoundCapture_test(dsco, FALSE, NULL);

    /* try the COM class factory method of creation with default capture
     * device specified */
    rc=CoCreateInstance(&CLSID_DirectSoundCapture, NULL, CLSCTX_INPROC_SERVER,
                        &IID_IDirectSoundCapture, (void**)&dsco);
    ok(rc==S_OK,"CoCreateInstance(CLSID_DirectSoundCapture) failed: %08lx\n", rc);
    if (dsco)
        IDirectSoundCapture_test(dsco, FALSE, &DSDEVID_DefaultCapture);

    /* try the COM class factory method of creation with default voice
     * capture device specified */
    rc=CoCreateInstance(&CLSID_DirectSoundCapture, NULL, CLSCTX_INPROC_SERVER,
                        &IID_IDirectSoundCapture, (void**)&dsco);
    ok(rc==S_OK,"CoCreateInstance(CLSID_DirectSoundCapture) failed: %08lx\n", rc);
    if (dsco)
        IDirectSoundCapture_test(dsco, FALSE, &DSDEVID_DefaultVoiceCapture);

    /* try the COM class factory method of creation with a bad
     * IID specified */
    rc=CoCreateInstance(&CLSID_DirectSoundCapture, NULL, CLSCTX_INPROC_SERVER,
                        &CLSID_DirectSoundPrivate, (void**)&dsco);
    ok(rc==E_NOINTERFACE,
       "CoCreateInstance(CLSID_DirectSoundCapture,CLSID_DirectSoundPrivate) "
       "should have failed: %08lx\n",rc);

    /* try with no device specified */
    rc = DirectSoundCaptureCreate(NULL, &dsco, NULL);
    ok(rc==DS_OK||rc==DSERR_NODRIVER||rc==DSERR_ALLOCATED||rc==E_FAIL,
       "DirectSoundCaptureCreate(NULL) failed: %08lx\n",rc);
    if (rc==S_OK && dsco)
        IDirectSoundCapture_test(dsco, TRUE, NULL);

    /* try with default capture device specified */
    rc = DirectSoundCaptureCreate(&DSDEVID_DefaultCapture, &dsco, NULL);
    ok(rc==DS_OK||rc==DSERR_NODRIVER||rc==DSERR_ALLOCATED||rc==E_FAIL,
       "DirectSoundCaptureCreate(DSDEVID_DefaultCapture) failed: %08lx\n", rc);
    if (rc==DS_OK && dsco)
        IDirectSoundCapture_test(dsco, TRUE, NULL);

    /* try with default voice capture device specified */
    rc = DirectSoundCaptureCreate(&DSDEVID_DefaultVoiceCapture, &dsco, NULL);
    ok(rc==DS_OK||rc==DSERR_NODRIVER||rc==DSERR_ALLOCATED||rc==E_FAIL,
       "DirectSoundCaptureCreate(DSDEVID_DefaultVoiceCapture) failed: %08lx\n", rc);
    if (rc==DS_OK && dsco)
        IDirectSoundCapture_test(dsco, TRUE, NULL);

    /* try with a bad device specified */
    rc = DirectSoundCaptureCreate(&DSDEVID_DefaultVoicePlayback, &dsco, NULL);
    ok(rc==DSERR_NODRIVER,
       "DirectSoundCaptureCreate(DSDEVID_DefaultVoicePlatback) "
       "should have failed: %08lx\n",rc);
    if (rc==DS_OK && dsco)
        IDirectSoundCapture_Release(dsco);
}

typedef struct {
    char* wave;
    DWORD wave_len;

    LPDIRECTSOUNDCAPTUREBUFFER dscbo;
    LPWAVEFORMATEX wfx;
    DSBPOSITIONNOTIFY posnotify[TOTAL_NOTIFICATIONS];
    HANDLE event[TOTAL_NOTIFICATIONS];
    LPDIRECTSOUNDNOTIFY notify;

    DWORD buffer_size;
    DWORD read;
    DWORD offset;
    DWORD size;

    DWORD last_pos;
} capture_state_t;

static BOOL capture_buffer_service(capture_state_t* state)
{
    HRESULT rc;
    LPVOID ptr1,ptr2;
    DWORD len1,len2;
    DWORD capture_pos,read_pos;

    rc=IDirectSoundCaptureBuffer_GetCurrentPosition(state->dscbo,&capture_pos,
                                                    &read_pos);
    ok(rc==DS_OK,"IDirectSoundCaptureBuffer_GetCurrentPosition() failed: %08lx\n", rc);
    if (rc!=DS_OK)
        return FALSE;

    ptr1 = ptr2 = (void *)0xdeadbeef;
    len1 = len2 = 0xdeadbeef;
    rc=IDirectSoundCaptureBuffer_Lock(state->dscbo,(DWORD)-32,0,&ptr1,&len1,&ptr2,&len2,0);
    ok(rc==DSERR_INVALIDPARAM,"IDirectSoundCaptureBuffer_Lock() failed: %08lx\n", rc);
    ok(!ptr1 && !ptr2 && !len1 && !len2, "got %p, %lu, %p, %lu.\n", ptr1, len1, ptr2, len2);

    ptr1 = ptr2 = (void *)0xdeadbeef;
    len1 = len2 = 0xdeadbeef;
    rc=IDirectSoundCaptureBuffer_Lock(state->dscbo,(DWORD)-32,64,&ptr1,&len1,&ptr2,&len2,0);
    ok(rc==DSERR_INVALIDPARAM,"IDirectSoundCaptureBuffer_Lock() returned: %08lx\n", rc);
    ok(!ptr1 && !ptr2 && !len1 && !len2, "got %p, %lu, %p, %lu.\n", ptr1, len1, ptr2, len2);

    ptr1 = ptr2 = (void *)0xdeadbeef;
    len1 = len2 = 0xdeadbeef;
    rc=IDirectSoundCaptureBuffer_Lock(state->dscbo,0,0,&ptr1,&len1,&ptr2,&len2,0);
    ok(rc==DSERR_INVALIDPARAM,"IDirectSoundCaptureBuffer_Lock() returned: %08lx\n", rc);
    ok(!ptr1 && !ptr2 && !len1 && !len2, "got %p, %lu, %p, %lu.\n", ptr1, len1, ptr2, len2);

    ptr1 = ptr2 = (void *)0xdeadbeef;
    len1 = len2 = 0xdeadbeef;
    rc=IDirectSoundCaptureBuffer_Lock(state->dscbo,0,0,&ptr1,&len1,NULL,NULL,0);
    ok(rc==DSERR_INVALIDPARAM,"IDirectSoundCaptureBuffer_Lock() returned: %08lx\n", rc);
    ok(!ptr1 && !len1, "got %p, %lu.\n", ptr1, len1);

    ptr1 = ptr2 = (void *)0xdeadbeef;
    len1 = len2 = 0xdeadbeef;
    rc=IDirectSoundCaptureBuffer_Lock(state->dscbo,state->buffer_size,1,&ptr1,&len1,&ptr2,&len2,0);
    ok(rc==DSERR_INVALIDPARAM,"IDirectSoundCaptureBuffer_Lock() returned: %08lx\n", rc);
    ok(!ptr1 && !ptr2 && !len1 && !len2, "got %p, %lu, %p, %lu.\n", ptr1, len1, ptr2, len2);

    ptr1 = ptr2 = (void *)0xdeadbeef;
    len1 = len2 = 0xdeadbeef;
    rc=IDirectSoundCaptureBuffer_Lock(state->dscbo,0,state->buffer_size + 1,&ptr1,&len1,&ptr2,&len2,0);
    ok(rc==DSERR_INVALIDPARAM,"IDirectSoundCaptureBuffer_Lock() returned: %08lx\n", rc);
    ok(!ptr1 && !ptr2 && !len1 && !len2, "got %p, %lu, %p, %lu.\n", ptr1, len1, ptr2, len2);

    ptr1 = ptr2 = (void *)0xdeadbeef;
    len1 = len2 = 0xdeadbeef;
    rc=IDirectSoundCaptureBuffer_Lock(state->dscbo,state->offset,state->buffer_size,
                                      &ptr1,&len1,&ptr2,&len2,0);
    ok(!rc,"IDirectSoundCaptureBuffer_Lock() returned: %08lx\n", rc);
    if (state->offset)
        ok(ptr1 == (char *)ptr2 + state->offset, "got %p, %p, buffer_size %ld, offset %ld.\n",
           ptr1, ptr2, state->buffer_size, state->offset);
    else
        ok(!ptr2, "got %p.\n", ptr2);
    ok(len1 == state->buffer_size - state->offset, "got %ld, buffer_size %ld, offset %ld.\n",
       len1, state->buffer_size, state->offset);
    ok(len2 == state->offset, "got %ld, buffer_size %ld, offset %ld.\n",
       len2, state->buffer_size, state->offset);
    rc=IDirectSoundCaptureBuffer_Unlock(state->dscbo,ptr1,len1,ptr2,len2);
    ok(rc==DS_OK,"IDirectSoundCaptureBuffer_Unlock() returned: %08lx\n", rc);

    rc=IDirectSoundCaptureBuffer_Lock(state->dscbo,state->offset,state->size,
                                      &ptr1,&len1,&ptr2,&len2,0);
    ok(rc==DS_OK,"IDirectSoundCaptureBuffer_Lock() failed: %08lx\n", rc);
    if (rc!=DS_OK)
        return FALSE;

    rc=IDirectSoundCaptureBuffer_Unlock(state->dscbo,ptr1,len1,ptr2,len2);
    ok(rc==DS_OK,"IDirectSoundCaptureBuffer_Unlock() failed: %08lx\n", rc);
    if (rc!=DS_OK)
        return FALSE;

    state->offset = (state->offset + state->size) % state->buffer_size;

    return TRUE;
}

static void test_capture_buffer(LPDIRECTSOUNDCAPTURE dsco,
				LPDIRECTSOUNDCAPTUREBUFFER dscbo, int record)
{
    HRESULT rc;
    DSCBCAPS dscbcaps;
    WAVEFORMATEX wfx;
    DWORD size,status;
    capture_state_t state;
    int i, ref;

    /* Private dsound.dll: Error: Invalid caps pointer */
    rc=IDirectSoundCaptureBuffer_GetCaps(dscbo,0);
    ok(rc==DSERR_INVALIDPARAM,"IDirectSoundCaptureBuffer_GetCaps() should "
       "have returned DSERR_INVALIDPARAM, returned: %08lx\n", rc);

    /* Private dsound.dll: Error: Invalid caps pointer */
    dscbcaps.dwSize=0;
    rc=IDirectSoundCaptureBuffer_GetCaps(dscbo,&dscbcaps);
    ok(rc==DSERR_INVALIDPARAM,"IDirectSoundCaptureBuffer_GetCaps() should "
       "have returned DSERR_INVALIDPARAM, returned: %08lx\n", rc);

    dscbcaps.dwSize=sizeof(dscbcaps);
    rc=IDirectSoundCaptureBuffer_GetCaps(dscbo,&dscbcaps);
    ok(rc==DS_OK,"IDirectSoundCaptureBuffer_GetCaps() failed: %08lx\n", rc);
    if (rc==DS_OK && winetest_debug > 1) {
	trace("    Caps: size = %ld flags=0x%08lx buffer size=%ld\n",
	    dscbcaps.dwSize,dscbcaps.dwFlags,dscbcaps.dwBufferBytes);
    }

    /* Query the format size. Note that it may not match sizeof(wfx) */
    /* Private dsound.dll: Error: Either pwfxFormat or pdwSizeWritten must
     * be non-NULL */
    rc=IDirectSoundCaptureBuffer_GetFormat(dscbo,NULL,0,NULL);
    ok(rc==DSERR_INVALIDPARAM,"IDirectSoundCaptureBuffer_GetFormat() should "
       "have returned DSERR_INVALIDPARAM, returned: %08lx\n", rc);

    size=0;
    rc=IDirectSoundCaptureBuffer_GetFormat(dscbo,NULL,0,&size);
    ok(rc==DS_OK && size!=0,"IDirectSoundCaptureBuffer_GetFormat() should "
       "have returned the needed size: rc=%08lx, size=%ld\n", rc,size);

    rc=IDirectSoundCaptureBuffer_GetFormat(dscbo,&wfx,sizeof(wfx),NULL);
    ok(rc==DS_OK,"IDirectSoundCaptureBuffer_GetFormat() failed: %08lx\n", rc);
    if (rc==DS_OK && winetest_debug > 1) {
	trace("    Format: tag=0x%04x %ldx%dx%d avg.B/s=%ld align=%d\n",
	      wfx.wFormatTag,wfx.nSamplesPerSec,wfx.wBitsPerSample,
	      wfx.nChannels,wfx.nAvgBytesPerSec,wfx.nBlockAlign);
    }

    /* Private dsound.dll: Error: Invalid status pointer */
    rc=IDirectSoundCaptureBuffer_GetStatus(dscbo,0);
    ok(rc==DSERR_INVALIDPARAM,"IDirectSoundCaptureBuffer_GetStatus() should "
       "have returned DSERR_INVALIDPARAM, returned: %08lx\n", rc);

    rc=IDirectSoundCaptureBuffer_GetStatus(dscbo,&status);
    ok(rc==DS_OK,"IDirectSoundCaptureBuffer_GetStatus() failed: %08lx\n", rc);
    if (rc==DS_OK && winetest_debug > 1) {
	trace("    Status=0x%04lx\n",status);
    }

    ZeroMemory(&state, sizeof(state));
    state.dscbo=dscbo;
    state.wfx=&wfx;
    state.buffer_size = dscbcaps.dwBufferBytes;
    for (i = 0; i < TOTAL_NOTIFICATIONS; i++)
        state.event[i] = CreateEventW(NULL, FALSE, FALSE, NULL);
    state.size = dscbcaps.dwBufferBytes / TIMESTAMP_NOTIFICATIONS;

    rc=IDirectSoundCaptureBuffer_QueryInterface(dscbo,&IID_IDirectSoundNotify,
                                                (void **)&(state.notify));
    ok((rc==DS_OK)&&(state.notify!=NULL),
       "IDirectSoundCaptureBuffer_QueryInterface() failed: %08lx\n", rc);

    for (i = 0; i < TIMESTAMP_NOTIFICATIONS; i++) {
	state.posnotify[i].dwOffset = (i * state.size) + state.size - 1;
	state.posnotify[i].hEventNotify = state.event[i];
    }

    state.posnotify[i].dwOffset = DSBPN_OFFSETSTOP;
    state.posnotify[i].hEventNotify = state.event[i];

    rc = IDirectSoundNotify_SetNotificationPositions(state.notify, TOTAL_NOTIFICATIONS,
                                                   state.posnotify);
    ok(rc==DS_OK,"IDirectSoundNotify_SetNotificationPositions() failed: %08lx\n", rc);

    ref=IDirectSoundNotify_Release(state.notify);
    ok(ref==0,"IDirectSoundNotify_Release(): has %d references, should have "
       "0\n",ref);

    rc=IDirectSoundCaptureBuffer_Start(dscbo,DSCBSTART_LOOPING);
    ok(rc==DS_OK,"IDirectSoundCaptureBuffer_Start() failed: %08lx\n", rc);

    rc=IDirectSoundCaptureBuffer_Start(dscbo,0);
    ok(rc==DS_OK,"IDirectSoundCaptureBuffer_Start() failed: %08lx\n", rc);

    rc=IDirectSoundCaptureBuffer_GetStatus(dscbo,&status);
    ok(rc==DS_OK,"IDirectSoundCaptureBuffer_GetStatus() failed: %08lx\n", rc);
    ok(status==(DSCBSTATUS_CAPTURING|DSCBSTATUS_LOOPING) || broken(status==DSCBSTATUS_CAPTURING),
       "GetStatus: bad status: %lx\n",status);

    if (record) {
	/* wait for timestamp notifications */
	for (i = 0; i < (TIMESTAMP_NOTIFICATIONS * 2); i++) {
	    rc = WaitForMultipleObjects(TOTAL_NOTIFICATIONS, state.event, FALSE, 3000);
	    ok(rc == (WAIT_OBJECT_0 + (i % TIMESTAMP_NOTIFICATIONS)),
               "WaitForMultipleObjects failed: 0x%lx\n",rc);
	    if (rc != (WAIT_OBJECT_0 + (i % TIMESTAMP_NOTIFICATIONS))) {
		ok((rc==WAIT_TIMEOUT)||(rc==WAIT_FAILED),
                   "Wrong notification: should be %d, got %ld\n",
		    i % TIMESTAMP_NOTIFICATIONS, rc - WAIT_OBJECT_0);
	    }
	    if (!capture_buffer_service(&state))
		break;
	}

    }
    rc=IDirectSoundCaptureBuffer_Stop(dscbo);
    ok(rc==DS_OK,"IDirectSoundCaptureBuffer_Stop() failed: %08lx\n", rc);

    /* wait for stop notification */
    rc = WaitForSingleObject(state.event[TIMESTAMP_NOTIFICATIONS], 3000);
    ok(rc == WAIT_OBJECT_0, "WaitForSingleObject failed: 0x%lx\n", rc);

    rc=IDirectSoundCaptureBuffer_Stop(dscbo);
    ok(rc==DS_OK,"IDirectSoundCaptureBuffer_Stop() failed: %08lx\n", rc);
}

static BOOL WINAPI dscenum_callback(LPGUID lpGuid, LPCSTR lpcstrDescription,
				    LPCSTR lpcstrModule, LPVOID lpContext)
{
    HRESULT rc;
    LPDIRECTSOUNDCAPTURE dsco=NULL;
    LPDIRECTSOUNDCAPTUREBUFFER dscbo=NULL;
    DSCBUFFERDESC bufdesc;
    WAVEFORMATEX wfx;
    DSCCAPS dsccaps;
    DWORD f;
    int ref;

    /* Private dsound.dll: Error: Invalid interface buffer */
    trace("*** Testing %s - %s ***\n",lpcstrDescription,lpcstrModule);
    rc = DirectSoundCaptureCreate(lpGuid, NULL, NULL);
    ok(rc==DSERR_INVALIDPARAM,"DirectSoundCaptureCreate() should have "
       "returned DSERR_INVALIDPARAM, returned: %08lx\n",rc);

    rc = DirectSoundCaptureCreate(lpGuid, &dsco, NULL);
    ok((rc==DS_OK)||(rc==DSERR_NODRIVER)||(rc==E_FAIL)||(rc==DSERR_ALLOCATED),
       "DirectSoundCaptureCreate() failed: %08lx\n",rc);
    if (rc!=DS_OK) {
        if (rc==DSERR_NODRIVER)
            trace("  No Driver\n");
        else if (rc==E_FAIL)
            trace("  No Device\n");
        else if (rc==DSERR_ALLOCATED)
            trace("  Already In Use\n");
	goto EXIT;
    }

    /* Private dsound.dll: Error: Invalid caps buffer */
    rc=IDirectSoundCapture_GetCaps(dsco,NULL);
    ok(rc==DSERR_INVALIDPARAM,"IDirectSoundCapture_GetCaps() should have "
       "returned DSERR_INVALIDPARAM, returned: %08lx\n",rc);

    /* Private dsound.dll: Error: Invalid caps buffer */
    dsccaps.dwSize=0;
    rc=IDirectSoundCapture_GetCaps(dsco,&dsccaps);
    ok(rc==DSERR_INVALIDPARAM,"IDirectSoundCapture_GetCaps() should have "
       "returned DSERR_INVALIDPARAM, returned: %08lx\n",rc);

    dsccaps.dwSize=sizeof(dsccaps);
    rc=IDirectSoundCapture_GetCaps(dsco,&dsccaps);
    ok(rc==DS_OK,"IDirectSoundCapture_GetCaps() failed: %08lx\n", rc);
    if (rc==DS_OK && winetest_debug > 1) {
	trace("  Caps: size=%ld flags=0x%08lx formats=%05lx channels=%ld\n",
	      dsccaps.dwSize,dsccaps.dwFlags,dsccaps.dwFormats,
              dsccaps.dwChannels);
    }

    /* Private dsound.dll: Error: Invalid size */
    /* Private dsound.dll: Error: Invalid capture buffer description */
    ZeroMemory(&bufdesc, sizeof(bufdesc));
    bufdesc.dwSize=0;
    bufdesc.dwFlags=0;
    bufdesc.dwBufferBytes=0;
    bufdesc.dwReserved=0;
    bufdesc.lpwfxFormat=NULL;
    rc=IDirectSoundCapture_CreateCaptureBuffer(dsco,&bufdesc,&dscbo,NULL);
    ok(rc==DSERR_INVALIDPARAM,"IDirectSoundCapture_CreateCaptureBuffer() "
       "should have returned DSERR_INVALIDPARAM, returned: %08lx\n", rc);
    if (rc==DS_OK) {
	ref=IDirectSoundCaptureBuffer_Release(dscbo);
	ok(ref==0,"IDirectSoundCaptureBuffer_Release() has %d references, "
           "should have 0\n",ref);
    }

    /* Private dsound.dll: Error: Invalid buffer size */
    /* Private dsound.dll: Error: Invalid capture buffer description */
    ZeroMemory(&bufdesc, sizeof(bufdesc));
    bufdesc.dwSize=sizeof(bufdesc);
    bufdesc.dwFlags=0;
    bufdesc.dwBufferBytes=0;
    bufdesc.dwReserved=0;
    bufdesc.lpwfxFormat=NULL;
    rc=IDirectSoundCapture_CreateCaptureBuffer(dsco,&bufdesc,&dscbo,NULL);
    ok(rc==DSERR_INVALIDPARAM,"IDirectSoundCapture_CreateCaptureBuffer() "
       "should have returned DSERR_INVALIDPARAM, returned %08lx\n", rc);
    if (rc==DS_OK) {
	ref=IDirectSoundCaptureBuffer_Release(dscbo);
	ok(ref==0,"IDirectSoundCaptureBuffer_Release() has %d references, "
           "should have 0\n",ref);
    }

    /* Private dsound.dll: Error: Invalid buffer size */
    /* Private dsound.dll: Error: Invalid capture buffer description */
    ZeroMemory(&bufdesc, sizeof(bufdesc));
    ZeroMemory(&wfx, sizeof(wfx));
    bufdesc.dwSize=sizeof(bufdesc);
    bufdesc.dwFlags=0;
    bufdesc.dwBufferBytes=0;
    bufdesc.dwReserved=0;
    bufdesc.lpwfxFormat=&wfx;
    rc=IDirectSoundCapture_CreateCaptureBuffer(dsco,&bufdesc,&dscbo,NULL);
    ok(rc==DSERR_INVALIDPARAM,"IDirectSoundCapture_CreateCaptureBuffer() "
       "should have returned DSERR_INVALIDPARAM, returned: %08lx\n", rc);
    if (rc==DS_OK) {
	ref=IDirectSoundCaptureBuffer_Release(dscbo);
	ok(ref==0,"IDirectSoundCaptureBuffer_Release() has %d references, "
           "should have 0\n",ref);
    }

    /* Private dsound.dll: Error: Invalid buffer size */
    /* Private dsound.dll: Error: Invalid capture buffer description */
    init_format(&wfx,WAVE_FORMAT_PCM,11025,8,1);
    ZeroMemory(&bufdesc, sizeof(bufdesc));
    bufdesc.dwSize=sizeof(bufdesc);
    bufdesc.dwFlags=0;
    bufdesc.dwBufferBytes=0;
    bufdesc.dwReserved=0;
    bufdesc.lpwfxFormat=&wfx;
    rc=IDirectSoundCapture_CreateCaptureBuffer(dsco,&bufdesc,&dscbo,NULL);
    ok(rc==DSERR_INVALIDPARAM,"IDirectSoundCapture_CreateCaptureBuffer() "
       "should have returned DSERR_INVALIDPARAM, returned: %08lx\n", rc);
    if (rc==DS_OK) {
	ref=IDirectSoundCaptureBuffer_Release(dscbo);
	ok(ref==0,"IDirectSoundCaptureBuffer_Release() has %d references, "
           "should have 0\n",ref);
    }

    for (f = 0; f < ARRAY_SIZE(formats); f++) {
	dscbo=NULL;
	init_format(&wfx,WAVE_FORMAT_PCM,formats[f][0],formats[f][1],
                    formats[f][2]);
	ZeroMemory(&bufdesc, sizeof(bufdesc));
	bufdesc.dwSize=sizeof(bufdesc);
	bufdesc.dwFlags=0;
	bufdesc.dwBufferBytes=wfx.nAvgBytesPerSec;
	bufdesc.dwReserved=0;
	bufdesc.lpwfxFormat=&wfx;
        if (winetest_interactive)
	    trace("  Testing the capture buffer at %s\n", format_string(&wfx));
	rc=IDirectSoundCapture_CreateCaptureBuffer(dsco,&bufdesc,&dscbo,NULL);
	ok(((rc==DS_OK)&&(dscbo!=NULL))
           || rc==DSERR_BADFORMAT || rc==DSERR_INVALIDCALL || rc==DSERR_NODRIVER
           || rc==DSERR_ALLOCATED || rc==E_INVALIDARG || rc==E_FAIL,
           "IDirectSoundCapture_CreateCaptureBuffer() failed to create a "
           "%s capture buffer: %08lx\n",format_string(&wfx),rc);
	if (rc==DS_OK) {
	    test_capture_buffer(dsco, dscbo, winetest_interactive);
	    ref=IDirectSoundCaptureBuffer_Release(dscbo);
	    ok(ref==0,"IDirectSoundCaptureBuffer_Release() has %d references, "
               "should have 0\n",ref);
	} else if (rc==DSERR_BADFORMAT) {
            ok(!(dsccaps.dwFormats & formats[f][3]),
               "IDirectSoundCapture_CreateCaptureBuffer() failed to create a "
               "capture buffer: format listed as supported but using it failed\n");
            if (!(dsccaps.dwFormats & formats[f][3]))
                trace("  Format not supported: %s\n", format_string(&wfx));
        } else if (rc==DSERR_NODRIVER) {
            trace("  No Driver\n");
        } else if (rc==DSERR_ALLOCATED) {
            trace("  Already In Use\n");
        } else if (rc==E_INVALIDARG) { /* try the old version struct */
            DSCBUFFERDESC1 bufdesc1;
	    ZeroMemory(&bufdesc1, sizeof(bufdesc1));
	    bufdesc1.dwSize=sizeof(bufdesc1);
	    bufdesc1.dwFlags=0;
	    bufdesc1.dwBufferBytes=wfx.nAvgBytesPerSec;
	    bufdesc1.dwReserved=0;
	    bufdesc1.lpwfxFormat=&wfx;
	    rc=IDirectSoundCapture_CreateCaptureBuffer(dsco,
                (DSCBUFFERDESC*)&bufdesc1,&dscbo,NULL);
            ok(rc==DS_OK || broken(rc==DSERR_INVALIDPARAM),
               "IDirectSoundCapture_CreateCaptureBuffer() failed to create a "
               "%s capture buffer: %08lx\n",format_string(&wfx), rc);
            if (rc==DSERR_INVALIDPARAM) {
                skip("broken driver\n");
                goto EXIT;
            }
            if (rc==DS_OK) {
	        test_capture_buffer(dsco, dscbo, winetest_interactive);
	        ref=IDirectSoundCaptureBuffer_Release(dscbo);
	        ok(ref==0,"IDirectSoundCaptureBuffer_Release() has %d "
                   "references, should have 0\n",ref);
            }
        } else if (rc==E_FAIL) {
            /* WAVE_FORMAT_PCM only allows 8 and 16 bits per sample, so only
             * report a failure if the bits per sample is 8 or 16
             */
            if (wfx.wBitsPerSample == 8 || wfx.wBitsPerSample == 16)
                ok(FALSE,"Should not fail for 8 or 16 bits per sample\n");
        }
    }

    /* try a non PCM format */
    if (0)
    {
    /* FIXME: Why is this commented out? */
    init_format(&wfx,WAVE_FORMAT_MULAW,8000,8,1);
    ZeroMemory(&bufdesc, sizeof(bufdesc));
    bufdesc.dwSize=sizeof(bufdesc);
    bufdesc.dwFlags=DSCBCAPS_WAVEMAPPED;
    bufdesc.dwBufferBytes=wfx.nAvgBytesPerSec;
    bufdesc.dwReserved=0;
    bufdesc.lpwfxFormat=&wfx;
    if (winetest_interactive)
        trace("  Testing the capture buffer at %s\n", format_string(&wfx));
    rc=IDirectSoundCapture_CreateCaptureBuffer(dsco,&bufdesc,&dscbo,NULL);
    ok((rc==DS_OK)&&(dscbo!=NULL),"IDirectSoundCapture_CreateCaptureBuffer() "
       "failed to create a capture buffer: %08lx\n",rc);
    if ((rc==DS_OK)&&(dscbo!=NULL)) {
	test_capture_buffer(dsco, dscbo, winetest_interactive);
	ref=IDirectSoundCaptureBuffer_Release(dscbo);
	ok(ref==0,"IDirectSoundCaptureBuffer_Release() has %d references, "
           "should have 0\n",ref);
    }
    }

    /* Try an invalid format to test error handling */
    if (0)
    {
    /* FIXME: Remove this test altogether? */
    init_format(&wfx,WAVE_FORMAT_PCM,2000000,16,2);
    ZeroMemory(&bufdesc, sizeof(bufdesc));
    bufdesc.dwSize=sizeof(bufdesc);
    bufdesc.dwFlags=DSCBCAPS_WAVEMAPPED;
    bufdesc.dwBufferBytes=wfx.nAvgBytesPerSec;
    bufdesc.dwReserved=0;
    bufdesc.lpwfxFormat=&wfx;
    if (winetest_interactive)
        trace("  Testing the capture buffer at %s\n", format_string(&wfx));
    rc=IDirectSoundCapture_CreateCaptureBuffer(dsco,&bufdesc,&dscbo,NULL);
    ok(rc!=DS_OK,"IDirectSoundCapture_CreateCaptureBuffer() should have failed "
       "at 2 MHz %08lx\n",rc);
    }

EXIT:
    if (dsco!=NULL) {
	ref=IDirectSoundCapture_Release(dsco);
	ok(ref==0,"IDirectSoundCapture_Release() has %d references, should "
           "have 0\n",ref);
    }

    return TRUE;
}

static void test_enumerate(void)
{
    HRESULT rc;
    rc = DirectSoundCaptureEnumerateA(dscenum_callback, NULL);
    ok(rc==DS_OK,"DirectSoundCaptureEnumerateA() failed: %08lx\n", rc);
}

static void test_COM(void)
{
    IDirectSoundCapture *dsc = (IDirectSoundCapture*)0xdeadbeef;
    IDirectSoundCaptureBuffer *buffer = (IDirectSoundCaptureBuffer*)0xdeadbeef;
    IDirectSoundNotify *notify;
    IUnknown *unk;
    DSCBUFFERDESC bufdesc;
    WAVEFORMATEX wfx;
    HRESULT hr;
    ULONG refcount;

    hr = DirectSoundCaptureCreate(NULL, &dsc, (IUnknown *)0xdeadbeef);
    ok(hr == DSERR_NOAGGREGATION,
       "DirectSoundCaptureCreate failed: %08lx, expected DSERR_NOAGGREGATION\n", hr);
    ok(dsc == (IDirectSoundCapture*)0xdeadbeef, "dsc = %p\n", dsc);

    hr = DirectSoundCaptureCreate(NULL, &dsc, NULL);
    if (hr == DSERR_NODRIVER) {
        skip("No driver\n");
        return;
    }
    ok(hr == DS_OK, "DirectSoundCaptureCreate failed: %08lx, expected DS_OK\n", hr);

    /* Different refcount for IDirectSoundCapture and for IUnknown */
    refcount = IDirectSoundCapture_AddRef(dsc);
    ok(refcount == 2, "refcount == %lu, expected 2\n", refcount);
    hr = IDirectSoundCapture_QueryInterface(dsc, &IID_IUnknown, (void**)&unk);
    ok(hr == S_OK, "QueryInterface for IID_IUnknown failed: %08lx\n", hr);
    refcount = IUnknown_AddRef(unk);
    ok(refcount == 2, "refcount == %lu, expected 2\n", refcount);
    IUnknown_Release(unk);
    IUnknown_Release(unk);
    IDirectSoundCapture_Release(dsc);

    init_format(&wfx, WAVE_FORMAT_PCM, 44100, 16, 1);
    ZeroMemory(&bufdesc, sizeof(bufdesc));
    bufdesc.dwSize = sizeof(bufdesc);
    bufdesc.dwBufferBytes = wfx.nAvgBytesPerSec;
    bufdesc.lpwfxFormat = &wfx;

    hr = IDirectSoundCapture_CreateCaptureBuffer(dsc, &bufdesc, &buffer, (IUnknown*)0xdeadbeef);
    if (hr == E_INVALIDARG) {
        /* Old DirectX has only the 1st version of the DSCBUFFERDESC struct */
        bufdesc.dwSize = sizeof(DSCBUFFERDESC1);
        hr = IDirectSoundCapture_CreateCaptureBuffer(dsc, &bufdesc, &buffer, (IUnknown*)0xdeadbeef);
    }
    ok(hr == DSERR_NOAGGREGATION,
       "IDirectSoundCapture_CreateCaptureBuffer failed: %08lx, expected DSERR_NOAGGREGATION\n", hr);
    ok(buffer == (IDirectSoundCaptureBuffer*)0xdeadbeef || !buffer /* Win2k without DirectX9 */,
       "buffer = %p\n", buffer);

    hr = IDirectSoundCapture_CreateCaptureBuffer(dsc, &bufdesc, &buffer, NULL);
    ok(hr == DS_OK, "IDirectSoundCapture_CreateCaptureBuffer failed: %08lx, expected DS_OK\n", hr);

    /* IDirectSoundCaptureBuffer and IDirectSoundNotify have separate refcounts */
    IDirectSoundCaptureBuffer_AddRef(buffer);
    refcount = IDirectSoundCaptureBuffer_AddRef(buffer);
    ok(refcount == 3, "IDirectSoundCaptureBuffer refcount is %lu, expected 3\n", refcount);
    hr = IDirectSoundCaptureBuffer_QueryInterface(buffer, &IID_IDirectSoundNotify, (void**)&notify);
    ok(hr == DS_OK, "IDirectSoundCapture_QueryInterface failed: %08lx, expected DS_OK\n", hr);
    refcount = IDirectSoundNotify_AddRef(notify);
    ok(refcount == 2, "IDirectSoundNotify refcount is %lu, expected 2\n", refcount);
    IDirectSoundCaptureBuffer_AddRef(buffer);
    refcount = IDirectSoundCaptureBuffer_Release(buffer);
    ok(refcount == 3, "IDirectSoundCaptureBuffer refcount is %lu, expected 3\n", refcount);

    /* Release IDirectSoundCaptureBuffer while keeping IDirectSoundNotify alive */
    while (IDirectSoundCaptureBuffer_Release(buffer) > 0);
    refcount = IDirectSoundNotify_AddRef(notify);
    ok(refcount == 3, "IDirectSoundNotify refcount is %lu, expected 3\n", refcount);
    refcount = IDirectSoundCaptureBuffer_AddRef(buffer);
    ok(refcount == 1, "IDirectSoundCaptureBuffer refcount is %lu, expected 1\n", refcount);

    while (IDirectSoundNotify_Release(notify) > 0);
    refcount = IDirectSoundCaptureBuffer_Release(buffer);
    ok(refcount == 0, "IDirectSoundCaptureBuffer refcount is %lu, expected 0\n", refcount);
    refcount = IDirectSoundCapture_Release(dsc);
    ok(refcount == 0, "IDirectSoundCapture refcount is %lu, expected 0\n", refcount);
}

START_TEST(capture)
{
    CoInitialize(NULL);

    test_COM();
    test_capture();
    test_enumerate();

    CoUninitialize();
}
