Hello, This patch to winearts adds wave-in support and fixes a few bugs in the wave-out code. I have made a few minor changes since I posted this patch on wine-devel@xxxxxxxxxx, so definately use this version. The following comments are cut and pasted from my previous email to wine-devel@xxxxxxxxxx and are provided here in case someone reads this list in the future. Here is a brief description of the changes I made: (1) In ARTS_CloseDevice() (now named ARTS_CloseWaveOutDevice()) and wodPlayer_NotifyWait() I added 'wwo->sound_buffer = 0' after the HeapFree(). Without it, HeapFree() was being called twice on the same buffer, and would crash the second time. (2) ARTS_AddRingMessage() Added the same fix I submitted earlier for wineoss in regards to ring buffer resizing. (3) wodPlayer_WriteMaxFrags() DWORD *bytes, points to the number of bytes the winearts driver thinks it should be able to write. However, arts sometimes lies, and says it has (for example) 512 bytes available, but when you call arts_write() it returns zero bytes written. In that case, I update *bytes to point to 0, otherwise wodPlayer_WriteMaxFrags() will keep being called in vain. (4a) wodPlayer_FeedDSP() The old code had the following checks: /* input queue empty and output buffer with no space */ if (!wwo->lpPlayPtr && availInQ) { TRACE("Run out of wavehdr:s... flushing\n"); wwo->dwPlayedTotal = wwo->dwWrittenTotal; return INFINITE; } if(!availInQ) { TRACE("no more room, no need to try to feed\n"); return wodPlayer_DSPWait(wwo); } I could not understand the purpose of the '&& availInQ' in the first check. It seems that if we are out of wavehdrs, then it does not matter if there is space in the availInQ or not, because we don't have anything to write. So I removed the && availInQ part. (4b) wodPlayer_FeedDSP() In the while loop, I could not figure out the purpose of 'availInQ > SPACE_THRESHOLD', so I changed it to 'availInQ'. In reality, the SPACE_THRESHOLD check could probably be left in, I am not sure what the purpose was in the first place. (4c) wodPlayer_FeedDSP() the while loop in wodPlayer_FeedDSP() can terminate for two reasons: + availInQ == 0 + ran out of waveHdrs to write. In the first case, wodPlayer_FeedDSP() should return the number of milliseconds before the buffer needs more data (which it currently does). In the second case, it should return INFINTE, since it can't feed anymore data until it gets some more wave headers. I added a bit of code to handle the second case. Notice also, that DSPWait() will only be called when the availInQ == 0. This means that DSPWait() is always calculating the same value which leads us to 4d. (4d) Removed wodPlayer_DSPWait() I just calculate the wait value once in wodOpen() and store it in wwo->dwSleepTime. (5) wodOpen () In wodOpen I changed the code from setting the ARTS_P_BUFFER_SIZE, to setting the ARTS_P_PACKET_SETTINGS. This allows for finer control which I needed in order to achieve lower latency. The rest of the changes are for adding wave in support. --- orig/dlls/winmm/winearts/winearts.drv.spec +++ mod/dlls/winmm/winearts/winearts.drv.spec @@ -1,2 +1,3 @@ @ stdcall DriverProc(long long long long long) ARTS_DriverProc @ stdcall wodMessage(long long long long long) ARTS_wodMessage +@ stdcall widMessage(long long long long long) ARTS_widMessage --- orig/dlls/winmm/winearts/audio.c +++ mod/dlls/winmm/winearts/audio.c @@ -32,8 +32,7 @@ * FIXME: * pause in waveOut does not work correctly in loop mode * - * TODO: - * implement wave-in support with artsc + * does something need to be done in for WaveIn DirectSound? */ /*#define EMULATE_SB16*/ @@ -65,10 +64,32 @@ #include <artsc.h> -#define BUFFER_SIZE 16 * 1024 -#define SPACE_THRESHOLD 5 * 1024 +/* The following four #defines allow you to fine-tune the packet + * settings in arts for better low-latency support. You must also + * adjust the latency in the KDE arts control panel. I recommend 4 + * fragments, 1024 bytes. + * + * The following is from the ARTS documentation and explains what CCCC + * and SSSS mean: + * + * @li ARTS_P_PACKET_SETTINGS (rw) This is a way to configure packet + * size & packet count at the same time. The format is 0xCCCCSSSS, + * where 2^SSSS is the packet size, and CCCC is the packet count. Note + * that when writing this, you don't necessarily get the settings you + * requested. + */ +#define WAVEOUT_PACKET_CCCC 0x000C +#define WAVEOUT_PACKET_SSSS 0x0008 +#define WAVEIN_PACKET_CCCC 0x000C +#define WAVEIN_PACKET_SSSS 0x0008 + +#define BUFFER_REFILL_THRESHOLD 4 + +#define WAVEOUT_PACKET_SETTINGS ((WAVEOUT_PACKET_CCCC << 16) | (WAVEOUT_PACKET_SSSS)) +#define WAVEIN_PACKET_SETTINGS ((WAVEIN_PACKET_CCCC << 16) | (WAVEIN_PACKET_SSSS)) #define MAX_WAVEOUTDRV (10) +#define MAX_WAVEINDRV (10) /* state diagram for waveOut writing: * @@ -96,7 +117,7 @@ /* events to be send to device */ enum win_wm_message { WINE_WM_PAUSING = WM_USER + 1, WINE_WM_RESTARTING, WINE_WM_RESETTING, WINE_WM_HEADER, - WINE_WM_UPDATE, WINE_WM_BREAKLOOP, WINE_WM_CLOSING + WINE_WM_UPDATE, WINE_WM_BREAKLOOP, WINE_WM_CLOSING, WINE_WM_STARTING, WINE_WM_STOPPING }; typedef struct { @@ -126,14 +147,16 @@ PCMWAVEFORMAT format; WAVEOUTCAPSA caps; + DWORD dwSleepTime; /* Num of milliseconds to sleep between filling the dsp buffers */ + /* arts information */ arts_stream_t play_stream; /* the stream structure we get from arts when opening a stream for playing */ DWORD dwBufferSize; /* size of whole buffer in bytes */ + int packetSettings; char* sound_buffer; long buffer_size; - DWORD volume_left; /* volume control information */ DWORD volume_right; @@ -154,7 +177,29 @@ ARTS_MSG_RING msgRing; } WINE_WAVEOUT; +typedef struct { + volatile int state; /* one of the WINE_WS_ manifest constants */ + WAVEOPENDESC waveDesc; + WORD wFlags; + PCMWAVEFORMAT format; + WAVEINCAPSA caps; + + /* arts information */ + arts_stream_t record_stream; /* the stream structure we get from arts when opening a stream for recording */ + int packetSettings; + + LPWAVEHDR lpQueuePtr; + DWORD dwRecordedTotal; + + /* synchronization stuff */ + HANDLE hStartUpEvent; + HANDLE hThread; + DWORD dwThreadID; + ARTS_MSG_RING msgRing; +} WINE_WAVEIN; + static WINE_WAVEOUT WOutDev [MAX_WAVEOUTDRV]; +static WINE_WAVEIN WInDev [MAX_WAVEINDRV]; static DWORD wodDsCreate(UINT wDevID, PIDSDRIVER* drv); static DWORD wodDsDesc(UINT wDevID, PDSDRIVERDESC desc); @@ -169,6 +214,8 @@ "WINE_WM_UPDATE", "WINE_WM_BREAKLOOP", "WINE_WM_CLOSING", + "WINE_WM_STARTING", + "WINE_WM_STOPPING", }; /*======================================================================* @@ -229,22 +276,35 @@ } /****************************************************************** - * ARTS_CloseDevice + * ARTS_CloseWaveOutDevice * */ -void ARTS_CloseDevice(WINE_WAVEOUT* wwo) +void ARTS_CloseWaveOutDevice(WINE_WAVEOUT* wwo) { arts_close_stream(wwo->play_stream); /* close the arts stream */ wwo->play_stream = (arts_stream_t*)-1; /* free up the buffer we use for volume and reset the size */ if(wwo->sound_buffer) + { HeapFree(GetProcessHeap(), 0, wwo->sound_buffer); + wwo->sound_buffer = 0; + } wwo->buffer_size = 0; } /****************************************************************** + * ARTS_CloseWaveInDevice + * + */ +void ARTS_CloseWaveInDevice(WINE_WAVEIN* wwi) +{ + arts_close_stream(wwi->record_stream); /* close the arts stream */ + wwi->record_stream = (arts_stream_t*)-1; +} + +/****************************************************************** * ARTS_Init */ static int ARTS_Init(void) @@ -264,7 +324,15 @@ { if(WOutDev[iDevice].play_stream != (arts_stream_t*)-1) { - ARTS_CloseDevice(&WOutDev[iDevice]); + ARTS_CloseWaveOutDevice(&WOutDev[iDevice]); + } + } + + for(iDevice = 0; iDevice < MAX_WAVEINDRV; iDevice++) + { + if(WInDev[iDevice].record_stream != (arts_stream_t*)-1) + { + ARTS_CloseWaveInDevice(&WInDev[iDevice]); } } @@ -331,7 +399,44 @@ WOutDev[i].caps.dwFormats |= WAVE_FORMAT_1S16; } + for (i = 0; i < MAX_WAVEINDRV; ++i) + { + WInDev[i].record_stream = (arts_stream_t*)-1; + memset(&WInDev[i].caps, 0, sizeof(WInDev[i].caps)); /* zero out + caps values */ + /* FIXME: some programs compare this string against the content of the registry + * for MM drivers. The names have to match in order for the program to work + * (e.g. MS win9x mplayer.exe) + */ +#ifdef EMULATE_SB16 + WInDev[i].caps.wMid = 0x0002; + WInDev[i].caps.wPid = 0x0104; + strcpy(WInDev[i].caps.szPname, "SB16 Wave In"); +#else + WInDev[i].caps.wMid = 0x00FF; + WInDev[i].caps.wPid = 0x0001; + strcpy(WInDev[i].caps.szPname,"CS4236/37/38"); +#endif + WInDev[i].caps.vDriverVersion = 0x0100; + WInDev[i].caps.dwFormats = 0x00000000; + + WInDev[i].caps.wChannels = 2; + + WInDev[i].caps.dwFormats |= WAVE_FORMAT_4M08; + WInDev[i].caps.dwFormats |= WAVE_FORMAT_4S08; + WInDev[i].caps.dwFormats |= WAVE_FORMAT_4S16; + WInDev[i].caps.dwFormats |= WAVE_FORMAT_4M16; + WInDev[i].caps.dwFormats |= WAVE_FORMAT_2M08; + WInDev[i].caps.dwFormats |= WAVE_FORMAT_2S08; + WInDev[i].caps.dwFormats |= WAVE_FORMAT_2M16; + WInDev[i].caps.dwFormats |= WAVE_FORMAT_2S16; + WInDev[i].caps.dwFormats |= WAVE_FORMAT_1M08; + WInDev[i].caps.dwFormats |= WAVE_FORMAT_1S08; + WInDev[i].caps.dwFormats |= WAVE_FORMAT_1M16; + WInDev[i].caps.dwFormats |= WAVE_FORMAT_1S16; + WInDev[i].caps.wReserved1 = 0; + } return 0; } @@ -376,9 +481,22 @@ EnterCriticalSection(&mr->msg_crst); if ((mr->msg_toget == ((mr->msg_tosave + 1) % mr->ring_buffer_size))) { + int old_ring_buffer_size = mr->ring_buffer_size; mr->ring_buffer_size += ARTS_RING_BUFFER_INCREMENT; TRACE("mr->ring_buffer_size=%d\n",mr->ring_buffer_size); mr->messages = HeapReAlloc(GetProcessHeap(),0,mr->messages, mr->ring_buffer_size * sizeof(RING_MSG)); + /* Now we need to rearrange the ring buffer so that the new + buffers just allocated are in between mr->msg_tosave and + mr->msg_toget. + */ + if (mr->msg_tosave < mr->msg_toget) + { + memmove(&(mr->messages[mr->msg_toget + ARTS_RING_BUFFER_INCREMENT]), + &(mr->messages[mr->msg_toget]), + sizeof(RING_MSG)*(old_ring_buffer_size - mr->msg_toget) + ); + mr->msg_toget += ARTS_RING_BUFFER_INCREMENT; + } } if (wait) { @@ -554,25 +672,6 @@ } /************************************************************************** - * wodPlayer_DSPWait [internal] - * Returns the number of milliseconds to wait for the DSP buffer to clear. - * This is based on the number of fragments we want to be clear before - * writing and the number of free fragments we already have. - */ -static DWORD wodPlayer_DSPWait(const WINE_WAVEOUT *wwo) -{ - int waitvalue = (wwo->dwBufferSize - arts_stream_get(wwo->play_stream, - ARTS_P_BUFFER_SPACE)) / ((wwo->format.wf.nSamplesPerSec * - wwo->format.wBitsPerSample * wwo->format.wf.nChannels) - /1000); - - TRACE("wait value of %d\n", waitvalue); - - /* return the time left to play the buffer */ - return waitvalue; -} - -/************************************************************************** * wodPlayer_NotifyWait [internal] * Returns the number of milliseconds to wait before attempting to notify * completion of the specified wavehdr. @@ -618,7 +717,10 @@ if(wwo->buffer_size < toWrite) { if(wwo->sound_buffer) - HeapFree(GetProcessHeap(), 0, wwo->sound_buffer); + { + HeapFree(GetProcessHeap(), 0, wwo->sound_buffer); + wwo->sound_buffer = 0; + } } /* if we don't have a buffer then get one */ @@ -664,7 +766,11 @@ TRACE("written = %d\n", written); - if (written <= 0) return written; /* if we wrote nothing just return */ + if (written <= 0) + { + *bytes = 0; /* apparently arts is actually full */ + return written; /* if we wrote nothing just return */ + } if (written >= dwLength) wodPlayer_PlayPtrNext(wwo); /* If we wrote all current wavehdr, skip to the next one */ @@ -690,6 +796,23 @@ { LPWAVEHDR lpWaveHdr; + if (wwo->lpQueuePtr) { + TRACE("lpWaveHdr=(%p), lpPlayPtr=(%p), lpLoopPtr=(%p), reserved=(%ld), dwWrittenTotal=(%ld), force=(%d)\n", + wwo->lpQueuePtr, + wwo->lpPlayPtr, + wwo->lpLoopPtr, + wwo->lpQueuePtr->reserved, + wwo->dwWrittenTotal, + force); + } else { + TRACE("lpWaveHdr=(%p), lpPlayPtr=(%p), lpLoopPtr=(%p), dwWrittenTotal=(%ld), force=(%d)\n", + wwo->lpQueuePtr, + wwo->lpPlayPtr, + wwo->lpLoopPtr, + wwo->dwWrittenTotal, + force); + } + /* Start from lpQueuePtr and keep notifying until: * - we hit an unwritten wavehdr * - we hit the beginning of a running loop @@ -699,7 +822,7 @@ (force || (lpWaveHdr != wwo->lpPlayPtr && lpWaveHdr != wwo->lpLoopPtr && - lpWaveHdr->reserved <= wwo->dwPlayedTotal))) { + lpWaveHdr->reserved <= wwo->dwWrittenTotal))) { wwo->lpQueuePtr = lpWaveHdr->lpNext; @@ -854,10 +977,9 @@ availInQ = arts_stream_get(wwo->play_stream, ARTS_P_BUFFER_SPACE); TRACE("availInQ = %ld\n", availInQ); - /* input queue empty and output buffer with no space */ - if (!wwo->lpPlayPtr && availInQ) { + /* input queue empty */ + if (!wwo->lpPlayPtr) { TRACE("Run out of wavehdr:s... flushing\n"); - wwo->dwPlayedTotal = wwo->dwWrittenTotal; return INFINITE; } @@ -865,7 +987,7 @@ if(!availInQ) { TRACE("no more room, no need to try to feed\n"); - return wodPlayer_DSPWait(wwo); + return wwo->dwSleepTime; } /* Feed from partial wavehdr */ @@ -878,16 +1000,26 @@ /* Feed wavehdrs until we run out of wavehdrs or DSP space */ if (!wwo->dwPartialOffset) { - while(wwo->lpPlayPtr && availInQ > SPACE_THRESHOLD) - { - TRACE("feeding waveheaders until we run out of space\n"); - /* note the value that dwPlayedTotal will return when this wave finishes playing */ - wwo->lpPlayPtr->reserved = wwo->dwWrittenTotal + wwo->lpPlayPtr->dwBufferLength; - wodPlayer_WriteMaxFrags(wwo, &availInQ); - } + while(wwo->lpPlayPtr && availInQ) + { + TRACE("feeding waveheaders until we run out of space\n"); + /* note the value that dwPlayedTotal will return when this wave finishes playing */ + wwo->lpPlayPtr->reserved = wwo->dwWrittenTotal + wwo->lpPlayPtr->dwBufferLength; + TRACE("reserved=(%ld) dwWrittenTotal=(%ld) dwBufferLength=(%ld)\n", + wwo->lpPlayPtr->reserved, + wwo->dwWrittenTotal, + wwo->lpPlayPtr->dwBufferLength + ); + wodPlayer_WriteMaxFrags(wwo, &availInQ); + } + } + + if (!wwo->lpPlayPtr) { + TRACE("Ran out of wavehdrs\n"); + return INFINITE; } - return wodPlayer_DSPWait(wwo); + return wwo->dwSleepTime; } @@ -1010,14 +1142,19 @@ if(!wwo->play_stream) return MMSYSERR_ALLOCATED; - /* Try to set buffer size from constant and store the value that it - was set to for future use */ - wwo->dwBufferSize = arts_stream_set(wwo->play_stream, - ARTS_P_BUFFER_SIZE, BUFFER_SIZE); - TRACE("Tried to set BUFFER_SIZE of %d, wwo->dwBufferSize is actually %ld\n", BUFFER_SIZE, wwo->dwBufferSize); + /* Try to set the packet settings from constant and store the value that it + was actually set to for future use */ + wwo->packetSettings = arts_stream_set(wwo->play_stream, ARTS_P_PACKET_SETTINGS, WAVEOUT_PACKET_SETTINGS); + TRACE("Tried to set ARTS_P_PACKET_SETTINGS to (%x), actually set to (%x)\n", WAVEOUT_PACKET_SETTINGS, wwo->packetSettings); + + wwo->dwBufferSize = arts_stream_get(wwo->play_stream, ARTS_P_BUFFER_SIZE); + TRACE("Buffer size is now (%ld)\n",wwo->dwBufferSize); + wwo->dwPlayedTotal = 0; wwo->dwWrittenTotal = 0; + wwo->dwSleepTime = ((1 << (wwo->packetSettings & 0xFFFF)) * 1000 * BUFFER_REFILL_THRESHOLD) / wwo->format.wf.nAvgBytesPerSec; + /* Initialize volume to full level */ wwo->volume_left = 100; wwo->volume_right = 100; @@ -1076,7 +1213,7 @@ ARTS_DestroyRingMessage(&wwo->msgRing); - ARTS_CloseDevice(wwo); /* close the stream and clean things up */ + ARTS_CloseWaveOutDevice(wwo); /* close the stream and clean things up */ ret = wodNotifyClient(wwo, WOM_CLOSE, 0L, 0L); } @@ -1399,6 +1536,499 @@ } /*======================================================================* + * Low level WAVE IN implementation * + *======================================================================*/ + +/************************************************************************** + * widGetNumDevs [internal] + */ +static DWORD widGetNumDevs(void) +{ + TRACE("%d \n",MAX_WAVEINDRV); + return MAX_WAVEINDRV; +} + +/************************************************************************** + * widNotifyClient [internal] + */ +static DWORD widNotifyClient(WINE_WAVEIN* wwi, WORD wMsg, DWORD dwParam1, DWORD dwParam2) +{ + TRACE("wMsg = 0x%04x dwParm1 = %04lX dwParam2 = %04lX\n", wMsg, dwParam1, dwParam2); + + switch (wMsg) { + case WIM_OPEN: + case WIM_CLOSE: + case WIM_DATA: + if (wwi->wFlags != DCB_NULL && + !DriverCallback(wwi->waveDesc.dwCallback, wwi->wFlags, + (HDRVR)wwi->waveDesc.hWave, wMsg, + wwi->waveDesc.dwInstance, dwParam1, dwParam2)) { + WARN("can't notify client !\n"); + return MMSYSERR_ERROR; + } + break; + default: + FIXME("Unknown callback message %u\n", wMsg); + return MMSYSERR_INVALPARAM; + } + return MMSYSERR_NOERROR; +} + +/************************************************************************** + * widGetDevCaps [internal] + */ +static DWORD widGetDevCaps(WORD wDevID, LPWAVEINCAPSA lpCaps, DWORD dwSize) +{ + TRACE("(%u, %p, %lu);\n", wDevID, lpCaps, dwSize); + + if (lpCaps == NULL) return MMSYSERR_NOTENABLED; + + if (wDevID >= MAX_WAVEINDRV) { + TRACE("MAX_WAVINDRV reached !\n"); + return MMSYSERR_BADDEVICEID; + } + + memcpy(lpCaps, &WInDev[wDevID].caps, min(dwSize, sizeof(*lpCaps))); + return MMSYSERR_NOERROR; +} + +/************************************************************************** + * widRecorder [internal] + */ +static DWORD CALLBACK widRecorder(LPVOID pmt) +{ + WORD uDevID = (DWORD)pmt; + WINE_WAVEIN* wwi = (WINE_WAVEIN*)&WInDev[uDevID]; + WAVEHDR* lpWaveHdr; + DWORD dwSleepTime; + DWORD bytesRead; + int dwBufferSpace; + enum win_wm_message msg; + DWORD param; + HANDLE ev; + + SetEvent(wwi->hStartUpEvent); + + /* make sleep time to be # of ms to record one packet */ + dwSleepTime = ((1 << (wwi->packetSettings & 0xFFFF)) * 1000) / wwi->format.wf.nAvgBytesPerSec; + TRACE("sleeptime=%ld ms\n", dwSleepTime); + + for(;;) { + /* Oddly enough, dwBufferSpace is sometimes negative.... + * + * NOTE: If you remove this call to arts_stream_get() and + * remove the && (dwBufferSpace > 0) the code will still + * function correctly. I don't know which way is + * faster/better. + */ + dwBufferSpace = arts_stream_get(wwi->record_stream, ARTS_P_BUFFER_SPACE); + TRACE("wwi->lpQueuePtr=(%p), wwi->state=(%d), dwBufferSpace=(%d)\n",wwi->lpQueuePtr,wwi->state,dwBufferSpace); + + /* read all data is arts input buffer. */ + if ((wwi->lpQueuePtr != NULL) && (wwi->state == WINE_WS_PLAYING) && (dwBufferSpace > 0)) + { + lpWaveHdr = wwi->lpQueuePtr; + + TRACE("read as much as we can\n"); + while(wwi->lpQueuePtr) + { + TRACE("attempt to read %ld bytes\n",lpWaveHdr->dwBufferLength - lpWaveHdr->dwBytesRecorded); + bytesRead = arts_read(wwi->record_stream, + lpWaveHdr->lpData + lpWaveHdr->dwBytesRecorded, + lpWaveHdr->dwBufferLength - lpWaveHdr->dwBytesRecorded); + TRACE("bytesRead=%ld\n",bytesRead); + if (bytesRead==0) break; + + lpWaveHdr->dwBytesRecorded += bytesRead; + wwi->dwRecordedTotal += bytesRead; + + /* buffer full. notify client */ + if (lpWaveHdr->dwBytesRecorded >= lpWaveHdr->dwBufferLength) + { + /* must copy the value of next waveHdr, because we have no idea of what + * will be done with the content of lpWaveHdr in callback + */ + LPWAVEHDR lpNext = lpWaveHdr->lpNext; + + TRACE("waveHdr full.\n"); + + lpWaveHdr->dwFlags &= ~WHDR_INQUEUE; + lpWaveHdr->dwFlags |= WHDR_DONE; + + widNotifyClient(wwi, WIM_DATA, (DWORD)lpWaveHdr, 0); + lpWaveHdr = wwi->lpQueuePtr = lpNext; + } + } + } + + /* wait for dwSleepTime or an event in thread's queue */ + WaitForSingleObject(wwi->msgRing.msg_event, dwSleepTime); + + while (ARTS_RetrieveRingMessage(&wwi->msgRing, &msg, ¶m, &ev)) + { + TRACE("msg=%s param=0x%lx\n",wodPlayerCmdString[msg - WM_USER - 1], param); + switch(msg) { + case WINE_WM_PAUSING: + wwi->state = WINE_WS_PAUSED; + + /* Put code here to "pause" arts recording + */ + + SetEvent(ev); + break; + case WINE_WM_STARTING: + wwi->state = WINE_WS_PLAYING; + + /* Put code here to "start" arts recording + */ + + SetEvent(ev); + break; + case WINE_WM_HEADER: + lpWaveHdr = (LPWAVEHDR)param; + /* insert buffer at end of queue */ + { + LPWAVEHDR* wh; + int num_headers = 0; + for (wh = &(wwi->lpQueuePtr); *wh; wh = &((*wh)->lpNext)) + { + num_headers++; + + } + *wh=lpWaveHdr; + } + break; + case WINE_WM_STOPPING: + if (wwi->state != WINE_WS_STOPPED) + { + + /* Put code here to "stop" arts recording + */ + + /* return current buffer to app */ + lpWaveHdr = wwi->lpQueuePtr; + if (lpWaveHdr) + { + LPWAVEHDR lpNext = lpWaveHdr->lpNext; + TRACE("stop %p %p\n", lpWaveHdr, lpWaveHdr->lpNext); + lpWaveHdr->dwFlags &= ~WHDR_INQUEUE; + lpWaveHdr->dwFlags |= WHDR_DONE; + widNotifyClient(wwi, WIM_DATA, (DWORD)lpWaveHdr, 0); + wwi->lpQueuePtr = lpNext; + } + } + wwi->state = WINE_WS_STOPPED; + SetEvent(ev); + break; + case WINE_WM_RESETTING: + wwi->state = WINE_WS_STOPPED; + wwi->dwRecordedTotal = 0; + + /* return all buffers to the app */ + for (lpWaveHdr = wwi->lpQueuePtr; lpWaveHdr; lpWaveHdr = lpWaveHdr->lpNext) { + TRACE("reset %p %p\n", lpWaveHdr, lpWaveHdr->lpNext); + lpWaveHdr->dwFlags &= ~WHDR_INQUEUE; + lpWaveHdr->dwFlags |= WHDR_DONE; + + widNotifyClient(wwi, WIM_DATA, (DWORD)lpWaveHdr, 0); + } + wwi->lpQueuePtr = NULL; + SetEvent(ev); + break; + case WINE_WM_CLOSING: + wwi->hThread = 0; + wwi->state = WINE_WS_CLOSED; + SetEvent(ev); + ExitThread(0); + /* shouldn't go here */ + default: + FIXME("unknown message %d\n", msg); + break; + } + } + } + ExitThread(0); + /* just for not generating compilation warnings... should never be executed */ + return 0; +} + +/************************************************************************** + * widOpen [internal] + */ +static DWORD widOpen(WORD wDevID, LPWAVEOPENDESC lpDesc, DWORD dwFlags) +{ + WINE_WAVEIN* wwi; + + TRACE("(%u, %p %08lX);\n",wDevID, lpDesc, dwFlags); + if (lpDesc == NULL) { + WARN("Invalid Parametr (lpDesc == NULL)!\n"); + return MMSYSERR_INVALPARAM; + } + + if (wDevID >= MAX_WAVEINDRV) { + TRACE ("MAX_WAVEINDRV reached !\n"); + return MMSYSERR_BADDEVICEID; + } + + /* if this device is already open tell the app that it is allocated */ + if(WInDev[wDevID].record_stream != (arts_stream_t*)-1) + { + TRACE("device already allocated\n"); + return MMSYSERR_ALLOCATED; + } + + /* only PCM format is support so far... */ + if (lpDesc->lpFormat->wFormatTag != WAVE_FORMAT_PCM || + lpDesc->lpFormat->nChannels == 0 || + lpDesc->lpFormat->nSamplesPerSec == 0) { + WARN("Bad format: tag=%04X nChannels=%d nSamplesPerSec=%ld !\n", + lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels, + lpDesc->lpFormat->nSamplesPerSec); + return WAVERR_BADFORMAT; + } + + if (dwFlags & WAVE_FORMAT_QUERY) { + TRACE("Query format: tag=%04X nChannels=%d nSamplesPerSec=%ld !\n", + lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels, + lpDesc->lpFormat->nSamplesPerSec); + return MMSYSERR_NOERROR; + } + + wwi = &WInDev[wDevID]; + + /* direct sound not supported, ignore the flag */ + dwFlags &= ~WAVE_DIRECTSOUND; + + wwi->wFlags = HIWORD(dwFlags & CALLBACK_TYPEMASK); + + memcpy(&wwi->waveDesc, lpDesc, sizeof(WAVEOPENDESC)); + memcpy(&wwi->format, lpDesc->lpFormat, sizeof(PCMWAVEFORMAT)); + + if (wwi->format.wBitsPerSample == 0) { + WARN("Resetting zerod wBitsPerSample\n"); + wwi->format.wBitsPerSample = 8 * + (wwi->format.wf.nAvgBytesPerSec / + wwi->format.wf.nSamplesPerSec) / + wwi->format.wf.nChannels; + } + + wwi->record_stream = arts_record_stream(wwi->format.wf.nSamplesPerSec, + wwi->format.wBitsPerSample, + wwi->format.wf.nChannels, + "winearts"); + TRACE("(wwi->record_stream=%p)\n",wwi->record_stream); + wwi->state = WINE_WS_STOPPED; + + wwi->packetSettings = arts_stream_set(wwi->record_stream, ARTS_P_PACKET_SETTINGS, WAVEIN_PACKET_SETTINGS); + TRACE("Tried to set ARTS_P_PACKET_SETTINGS to (%x), actually set to (%x)\n", WAVEIN_PACKET_SETTINGS, wwi->packetSettings); + TRACE("Buffer size is now (%d)\n", arts_stream_get(wwi->record_stream, ARTS_P_BUFFER_SIZE)); + + if (wwi->lpQueuePtr) { + WARN("Should have an empty queue (%p)\n", wwi->lpQueuePtr); + wwi->lpQueuePtr = NULL; + } + arts_stream_set(wwi->record_stream, ARTS_P_BLOCKING, 0); /* disable blocking on this stream */ + + if(!wwi->record_stream) return MMSYSERR_ALLOCATED; + + wwi->dwRecordedTotal = 0; + wwi->wFlags = HIWORD(dwFlags & CALLBACK_TYPEMASK); + + ARTS_InitRingMessage(&wwi->msgRing); + + /* create recorder thread */ + if (!(dwFlags & WAVE_DIRECTSOUND)) { + wwi->hStartUpEvent = CreateEventA(NULL, FALSE, FALSE, NULL); + wwi->hThread = CreateThread(NULL, 0, widRecorder, (LPVOID)(DWORD)wDevID, 0, &(wwi->dwThreadID)); + WaitForSingleObject(wwi->hStartUpEvent, INFINITE); + CloseHandle(wwi->hStartUpEvent); + } else { + wwi->hThread = INVALID_HANDLE_VALUE; + wwi->dwThreadID = 0; + } + wwi->hStartUpEvent = INVALID_HANDLE_VALUE; + + TRACE("wBitsPerSample=%u, nAvgBytesPerSec=%lu, nSamplesPerSec=%lu, nChannels=%u nBlockAlign=%u!\n", + wwi->format.wBitsPerSample, wwi->format.wf.nAvgBytesPerSec, + wwi->format.wf.nSamplesPerSec, wwi->format.wf.nChannels, + wwi->format.wf.nBlockAlign); + return widNotifyClient(wwi, WIM_OPEN, 0L, 0L); +} + +/************************************************************************** + * widClose [internal] + */ +static DWORD widClose(WORD wDevID) +{ + WINE_WAVEIN* wwi; + + TRACE("(%u);\n", wDevID); + if (wDevID >= MAX_WAVEINDRV || WInDev[wDevID].state == WINE_WS_CLOSED) { + WARN("can't close !\n"); + return MMSYSERR_INVALHANDLE; + } + + wwi = &WInDev[wDevID]; + + if (wwi->lpQueuePtr != NULL) { + WARN("still buffers open !\n"); + return WAVERR_STILLPLAYING; + } + + ARTS_AddRingMessage(&wwi->msgRing, WINE_WM_CLOSING, 0, TRUE); + ARTS_CloseWaveInDevice(wwi); + wwi->state = WINE_WS_CLOSED; + ARTS_DestroyRingMessage(&wwi->msgRing); + return widNotifyClient(wwi, WIM_CLOSE, 0L, 0L); +} + +/************************************************************************** + * widAddBuffer [internal] + */ +static DWORD widAddBuffer(WORD wDevID, LPWAVEHDR lpWaveHdr, DWORD dwSize) +{ + TRACE("(%u, %p, %08lX);\n", wDevID, lpWaveHdr, dwSize); + + if (wDevID >= MAX_WAVEINDRV || WInDev[wDevID].state == WINE_WS_CLOSED) { + WARN("can't do it !\n"); + return MMSYSERR_INVALHANDLE; + } + if (!(lpWaveHdr->dwFlags & WHDR_PREPARED)) { + TRACE("never been prepared !\n"); + return WAVERR_UNPREPARED; + } + if (lpWaveHdr->dwFlags & WHDR_INQUEUE) { + TRACE("header already in use !\n"); + return WAVERR_STILLPLAYING; + } + + lpWaveHdr->dwFlags |= WHDR_INQUEUE; + lpWaveHdr->dwFlags &= ~WHDR_DONE; + lpWaveHdr->dwBytesRecorded = 0; + lpWaveHdr->lpNext = NULL; + + ARTS_AddRingMessage(&WInDev[wDevID].msgRing, WINE_WM_HEADER, (DWORD)lpWaveHdr, FALSE); + return MMSYSERR_NOERROR; +} + +/************************************************************************** + * widPrepare [internal] + */ +static DWORD widPrepare(WORD wDevID, LPWAVEHDR lpWaveHdr, DWORD dwSize) +{ + TRACE("(%u, %p, %08lX);\n", wDevID, lpWaveHdr, dwSize); + + if (wDevID >= MAX_WAVEINDRV) return MMSYSERR_INVALHANDLE; + + if (lpWaveHdr->dwFlags & WHDR_INQUEUE) + return WAVERR_STILLPLAYING; + + lpWaveHdr->dwFlags |= WHDR_PREPARED; + lpWaveHdr->dwFlags &= ~WHDR_DONE; + lpWaveHdr->dwBytesRecorded = 0; + + return MMSYSERR_NOERROR; +} + +/************************************************************************** + * widUnprepare [internal] + */ +static DWORD widUnprepare(WORD wDevID, LPWAVEHDR lpWaveHdr, DWORD dwSize) +{ + TRACE("(%u, %p, %08lX);\n", wDevID, lpWaveHdr, dwSize); + if (wDevID >= MAX_WAVEINDRV) { + WARN("bad device ID !\n"); + return MMSYSERR_INVALHANDLE; + } + + if (lpWaveHdr->dwFlags & WHDR_INQUEUE) { + TRACE("Still playing...\n"); + return WAVERR_STILLPLAYING; + } + + lpWaveHdr->dwFlags &= ~WHDR_PREPARED; + lpWaveHdr->dwFlags |= WHDR_DONE; + + return MMSYSERR_NOERROR; +} + +/************************************************************************** + * widStart [internal] + */ +static DWORD widStart(WORD wDevID) +{ + TRACE("(%u);\n", wDevID); + if (wDevID >= MAX_WAVEINDRV || WInDev[wDevID].state == WINE_WS_CLOSED) { + WARN("can't start recording !\n"); + return MMSYSERR_INVALHANDLE; + } + + ARTS_AddRingMessage(&WInDev[wDevID].msgRing, WINE_WM_STARTING, 0, TRUE); + return MMSYSERR_NOERROR; +} + +/************************************************************************** + * widStop [internal] + */ +static DWORD widStop(WORD wDevID) +{ + TRACE("(%u);\n", wDevID); + if (wDevID >= MAX_WAVEINDRV || WInDev[wDevID].state == WINE_WS_CLOSED) { + WARN("can't stop !\n"); + return MMSYSERR_INVALHANDLE; + } + + ARTS_AddRingMessage(&WInDev[wDevID].msgRing, WINE_WM_STOPPING, 0, TRUE); + + return MMSYSERR_NOERROR; +} + +/************************************************************************** + * widReset [internal] + */ +static DWORD widReset(WORD wDevID) +{ + TRACE("(%u);\n", wDevID); + if (wDevID >= MAX_WAVEINDRV || WInDev[wDevID].state == WINE_WS_CLOSED) { + WARN("can't reset !\n"); + return MMSYSERR_INVALHANDLE; + } + ARTS_AddRingMessage(&WInDev[wDevID].msgRing, WINE_WM_RESETTING, 0, TRUE); + return MMSYSERR_NOERROR; +} + +/************************************************************************** + * widMessage (WINEARTS.6) + */ +DWORD WINAPI ARTS_widMessage(UINT wDevID, UINT wMsg, DWORD dwUser, + DWORD dwParam1, DWORD dwParam2) +{ + TRACE("(%u, %04X, %08lX, %08lX, %08lX);\n", + wDevID, wMsg, dwUser, dwParam1, dwParam2); + switch (wMsg) { + case DRVM_INIT: + case DRVM_EXIT: + case DRVM_ENABLE: + case DRVM_DISABLE: + /* FIXME: Pretend this is supported */ + return 0; + case WIDM_OPEN: return widOpen (wDevID, (LPWAVEOPENDESC)dwParam1, dwParam2); + case WIDM_CLOSE: return widClose (wDevID); + case WIDM_ADDBUFFER: return widAddBuffer (wDevID, (LPWAVEHDR)dwParam1, dwParam2); + case WIDM_PREPARE: return widPrepare (wDevID, (LPWAVEHDR)dwParam1, dwParam2); + case WIDM_UNPREPARE: return widUnprepare (wDevID, (LPWAVEHDR)dwParam1, dwParam2); + case WIDM_GETDEVCAPS: return widGetDevCaps (wDevID, (LPWAVEINCAPSA)dwParam1, dwParam2); + case WIDM_GETNUMDEVS: return widGetNumDevs (); + case WIDM_RESET: return widReset (wDevID); + case WIDM_START: return widStart (wDevID); + case WIDM_STOP: return widStop (wDevID); + default: + FIXME("unknown message %d!\n", wMsg); + } + return MMSYSERR_NOTSUPPORTED; +} + +/*======================================================================* * Low level DSOUND implementation * *======================================================================*/ static DWORD wodDsCreate(UINT wDevID, PIDSDRIVER* drv) @@ -1430,6 +2060,16 @@ * wodMessage (WINEARTS.@) */ DWORD WINAPI ARTS_wodMessage(WORD wDevID, WORD wMsg, DWORD dwUser, + DWORD dwParam1, DWORD dwParam2) +{ + FIXME("(%u, %04X, %08lX, %08lX, %08lX):stub\n", wDevID, wMsg, dwUser, dwParam1, dwParam2); + return MMSYSERR_NOTENABLED; +} + +/************************************************************************** + * widMessage (WINEARTS.6) + */ +DWORD WINAPI ARTS_widMessage(UINT wDevID, UINT wMsg, DWORD dwUser, DWORD dwParam1, DWORD dwParam2) { FIXME("(%u, %04X, %08lX, %08lX, %08lX):stub\n", wDevID, wMsg, dwUser, dwParam1, dwParam2);