Hello, This patch to winejack adds wave-in support, and fixes some bugs in the wave-out code. I have tested the patch extensively with the xten sipphone client. I have also done some testing with sndrec32, winamp 2.8, and audacity (the windows port). I have tested both mono and stereo recording and playback. NOTE: If you are using jack 0.90.0 (and maybe even earlier versions), the winejack driver will not work (with or without this patch). It will seg-fault when it calls jack_activate(). The work-around is to edit the jack_activate() in libjack/client.c. At the top there is a line: #define BIG_ENOUGH_STACK 1048576 change it to: #define BIG_ENOUGH_STACK (1048576 / 2) then recompile and reinstall jack. Chris Morgan is looking into a real fix. As I mentioned, this bug is orthognal to my patch. Here is a description on the fixes to the wave-out portion of the winejack driver: in JACK_callback: if(wwo->format.wf.nChannels == 1) { sample_move_d16_d16((short*)wwo->sound_buffer +((jackBytesAvailableThisCallback - jackBytesLeft) / sizeof(short)), (short*)buffer, jackBytesToWrite, wwo->format.wf.nChannels); } else /* just copy the memory over */ { memcpy(wwo->sound_buffer + (jackBytesAvailableThisCallback - jackBytesLeft), buffer, jackBytesToWrite); } sample_move_d16_d16() takes the numbers of samples (which are 16 bit samples in this case), but here it is passed the number of bytes. As a result, twice as much data is written as should be. This can cause seg-faults since sample_move_d16_d16() will write past the end of wwo->sound_buffer. (The playback sounds fine, because the playback pointer is incremented correctly, so the data the gets copied past the end of the sound_buffer on one pass, will be copied into the beginning of sound_buffer the next time.) Also, I think if sizeof(sample_t) changes, this code will start acting badly. I rewrote the JACK_callback() to do its calculations based on frames instead of bytes -- I think this makes it easier to understand, and less error prone. in JACK_bufsize () there is a line: buffer_required = sizeof(sample_t) * nframes; I don't think this makes since wwo->buffer_space will always hold 16-bit stereo data. in JACK_OpenDevice(): I added an explicit call to JACK_bufsize because in newer versions of jack, it isn't ever being called, so the buffer_space is never being initialized. in wodOpen(): 'InitializeCriticalSection()' is called, but if JACK_OpenDevice() fails, DeleteCriticalSection() is not called. This means all future calls to wodOpen will fail. Also, if someone calls wodOpev with dwFlag WAVE_FORMAT_QUERY set, the code initializes wwo->access_crst, and then returns. Then when they try to open the device for real, it fails trying to initialize wwo->access_crst a second-time, because its already initialized. I changed the order of things so that the format checks come before the InitializeCriticalSection(). There were also a few other places where DeleteCriticalSection() needed to be added or moved. The rest of the changes are for adding wave-in support. --- orig/dlls/winmm/winejack/winejack.drv.spec +++ mod/dlls/winmm/winejack/winejack.drv.spec @@ -1,2 +1,3 @@ @ stdcall DriverProc(long long long long long) JACK_DriverProc @ stdcall wodMessage(long long long long long) JACK_wodMessage +@ stdcall widMessage(long long long long long) JACK_widMessage --- orig/dlls/winmm/winejack/audio.c +++ mod/dlls/winmm/winejack/audio.c @@ -1,4 +1,4 @@ -/* -*- tab-width: 8; c-basic-offset: 4 -*- */ +/* -*- tab-width: 8; c-basic-offset: 2 -*- */ /* * Wine Driver for jack Sound Server * http://jackit.sourceforge.net @@ -29,7 +29,6 @@ * right now we use the winmm layer to do resampling although it would * be nice to have a full set of algorithms to choose from based on cpu * time - * implement wave-in support with jack * * FIXME: * pause in waveOut during loop is not handled correctly @@ -83,6 +82,7 @@ MAKE_FUNCPTR(jack_port_get_buffer); MAKE_FUNCPTR(jack_get_ports); MAKE_FUNCPTR(jack_port_name); +MAKE_FUNCPTR(jack_get_buffer_size); #undef MAKE_FUNCPTR /* define the below to work around a bug in jack where closing a port */ @@ -96,7 +96,7 @@ /* only allow 10 output devices through this driver, this ought to be adequate */ #define MAX_WAVEOUTDRV (10) -#define MAX_WAVEINDRV (1) +#define MAX_WAVEINDRV (10) /* state diagram for waveOut writing: * @@ -170,6 +170,19 @@ DWORD dwTotalRecorded; WAVEINCAPSA caps; BOOL bTriggerSupport; + WORD wDevID; + + jack_port_t* in_port_l; /* ports for left and right channels */ + jack_port_t* in_port_r; + jack_client_t* client; + long sample_rate; /* jack server sample rate */ + +#if JACK_CLOSE_HACK + BOOL in_use; /* TRUE if this device is in use */ +#endif + + char* sound_buffer; + unsigned long buffer_size; /* synchronization stuff */ CRITICAL_SECTION access_crst; @@ -185,12 +198,19 @@ static LPWAVEHDR wodHelper_PlayPtrNext(WINE_WAVEOUT* wwo); static DWORD wodHelper_NotifyCompletions(WINE_WAVEOUT* wwo, BOOL force); -static int JACK_OpenDevice(WINE_WAVEOUT* wwo); +static int JACK_OpenWaveOutDevice(WINE_WAVEOUT* wwo); +static int JACK_OpenWaveInDevice(WINE_WAVEIN* wwi, WORD nChannels); + +#if JACK_CLOSE_HACK +static void JACK_CloseWaveOutDevice(WINE_WAVEOUT* wwo, BOOL close_client); +#else +static void JACK_CloseWaveOutDevice(WINE_WAVEOUT* wwo); +#endif #if JACK_CLOSE_HACK -static void JACK_CloseDevice(WINE_WAVEOUT* wwo, BOOL close_client); +static void JACK_CloseWaveInDevice(WINE_WAVEIN* wwi, BOOL close_client); #else -static void JACK_CloseDevice(WINE_WAVEOUT* wwo); +static void JACK_CloseWaveInDevice(WINE_WAVEIN* wwi); #endif @@ -253,6 +273,25 @@ } } +/* convert from floating point to 16 bit */ +/* allow for copying of a buffer that will hold a single channel stream */ +/* to stereo data with alternating left/right channels */ +/* nsamples is in terms of float samples */ +/* dst_skip is in terms of 16bit samples */ +void sample_move_s16_d16 (short *dst, sample_t *src, + unsigned long nsamples, unsigned long dst_skip) +{ + /* ALERT: signed sign-extension portability !!! */ + while (nsamples--) + { + *dst = (*src) * SAMPLE_MAX_16BIT; +/* TRACE("src=(%.8f,%p) dst=(%d,%p)\n",*src,src,*dst,dst); */ + dst += dst_skip; + src++; + } +} + + /* fill dst buffer with nsamples worth of silence */ void sample_silence_dS (sample_t *dst, unsigned long nsamples) { @@ -265,39 +304,30 @@ } /****************************************************************** - * JACK_callback + * JACK_callback_wwo */ /* everytime the jack server wants something from us it calls this function, so we either deliver it some sound to play or deliver it nothing to play */ -int JACK_callback (nframes_t nframes, void *arg) +int JACK_callback_wwo (nframes_t nframes, void *arg) { sample_t* out_l; sample_t* out_r; WINE_WAVEOUT* wwo = (WINE_WAVEOUT*)arg; - TRACE("wDevID: %d, nframes %ld\n", wwo->wDevID, nframes); + TRACE("wDevID: %u, nframes %u state=%u\n", wwo->wDevID, nframes,wwo->state); if(!wwo->client) ERR("client is closed, this is weird...\n"); - out_l = (sample_t *) fp_jack_port_get_buffer(wwo->out_port_l, - nframes); - out_r = (sample_t *) fp_jack_port_get_buffer(wwo->out_port_r, - nframes); - - EnterCriticalSection(&wwo->access_crst); + out_l = (sample_t *) fp_jack_port_get_buffer(wwo->out_port_l, nframes); + out_r = (sample_t *) fp_jack_port_get_buffer(wwo->out_port_r, nframes); if(wwo->state == WINE_WS_PLAYING) { - DWORD jackBytesAvailableThisCallback = sizeof(sample_t) * nframes; - DWORD jackBytesLeft = sizeof(sample_t) * nframes; - - DWORD inputBytesAvailable; /* number of bytes we have from the app, after conversion to 16bit stereo */ - DWORD jackBytesToWrite; /* number of bytes we are going to write out, after conversion */ - - DWORD bytesInput; /* the number of bytes from the app */ - DWORD appBytesToWrite; /* number of bytes from the app we are going to write */ + DWORD jackFramesAvailable = nframes; + DWORD outputFramesAvailable; + DWORD numFramesToWrite; long written = 0; char* buffer; @@ -315,65 +345,53 @@ TRACE("wwo.state == WINE_WS_PLAYING\n"); - /* see if our buffer is large enough for the data we are writing */ - /* ie. Buffer_size < (bytes we already wrote + bytes we are going to write in this loop) */ - if(wwo->buffer_size < jackBytesAvailableThisCallback) + /* see if our sound_buffer is large enough to hold the number of frames jack requested */ + /* Note: sound_buffer is always filled with 16-bit stereo data, even for mono mode */ + if(wwo->buffer_size < (nframes * sizeof(short) * 2)) { ERR("for some reason JACK_BufSize() didn't allocate enough memory\n"); - ERR("allocated %ld bytes, need %ld bytes\n", wwo->buffer_size, - jackBytesAvailableThisCallback); - LeaveCriticalSection(&wwo->access_crst); + ERR("allocated %ld bytes, need %d bytes\n", wwo->buffer_size, (nframes * sizeof(short) * 2)); return 0; } - /* while we have jackBytesLeft and a wave header to be played */ - while(jackBytesLeft && wwo->lpPlayPtr) + /* while we have jackFramesAvailable and a wave header to be played */ + while(jackFramesAvailable && wwo->lpPlayPtr) { /* find the amount of audio to be played at this time */ - bytesInput = wwo->lpPlayPtr->dwBufferLength - wwo->dwPartialOffset; - inputBytesAvailable = bytesInput; - - /* calculate inputBytesAvailable based on audio format conversion */ - if(wwo->format.wf.nChannels == 1) - inputBytesAvailable<<=1; /* multiply by two for mono->stereo conversion */ + outputFramesAvailable = (wwo->lpPlayPtr->dwBufferLength - wwo->dwPartialOffset) / wwo->format.wf.nBlockAlign; - /* find the minimum of the inputBytesAvailable and the space available */ - jackBytesToWrite = min(jackBytesLeft, inputBytesAvailable); - - /* calculate appBytesToWrite based on audio format conversion */ - appBytesToWrite = jackBytesToWrite; - if(wwo->format.wf.nChannels == 1) - appBytesToWrite>>=1; /* divide by two for stereo->mono conversion */ - - TRACE("jackBytesToWrite == %ld, appBytesToWrite == %ld\n", jackBytesToWrite, appBytesToWrite); + numFramesToWrite = min(jackFramesAvailable, outputFramesAvailable); + TRACE("dwBufferLength=(%ld) dwPartialOffset=(%ld)\n",wwo->lpPlayPtr->dwBufferLength,wwo->dwPartialOffset); + TRACE("outputFramesAvailable == %ld, jackFramesAvailable == %ld\n", outputFramesAvailable, jackFramesAvailable); buffer = wwo->lpPlayPtr->lpData + wwo->dwPartialOffset; /* convert from mono to stereo if necessary */ /* otherwise just memcpy to the output buffer */ + if(wwo->format.wf.nChannels == 1) { - sample_move_d16_d16((short*)wwo->sound_buffer +((jackBytesAvailableThisCallback - jackBytesLeft) / sizeof(short)), - (short*)buffer, jackBytesToWrite, wwo->format.wf.nChannels); + sample_move_d16_d16((short*)wwo->sound_buffer + ((nframes - jackFramesAvailable) * sizeof(short)), + (short*)buffer, numFramesToWrite, wwo->format.wf.nChannels); } else /* just copy the memory over */ { - memcpy(wwo->sound_buffer + (jackBytesAvailableThisCallback - jackBytesLeft), - buffer, jackBytesToWrite); + memcpy(wwo->sound_buffer + ((nframes - jackFramesAvailable) * wwo->format.wf.nBlockAlign), + buffer, numFramesToWrite * wwo->format.wf.nBlockAlign); } /* advance to the next wave header if possible, or advance pointer */ /* inside of the current header if we haven't completed it */ - if(appBytesToWrite == bytesInput) + if(numFramesToWrite == outputFramesAvailable) { wodHelper_PlayPtrNext(wwo); /* we wrote the whole waveheader, skip to the next one*/ } else { - wwo->dwPartialOffset+=appBytesToWrite; /* else advance by the bytes we took in to write */ + wwo->dwPartialOffset+=(numFramesToWrite * wwo->format.wf.nBlockAlign); /* else advance by the bytes we took in to write */ } - written+=appBytesToWrite; /* add on what we wrote */ - jackBytesLeft-=jackBytesToWrite; /* take away what was written in terms of output bytes */ + written+=(numFramesToWrite * wwo->format.wf.nBlockAlign); /* add on what we wrote */ + jackFramesAvailable-=numFramesToWrite; /* take away what was written in terms of output bytes */ } wwo->tickCountMS = GetTickCount(); /* record the current time */ @@ -386,25 +404,22 @@ /* the audio to the jack server */ /* apply volume to the buffer */ - /* NOTE: buffer_size >> 2 to convert from bytes to 16 bit stereo(32bit) samples */ - volume_effect32(wwo->sound_buffer, (jackBytesAvailableThisCallback - jackBytesLeft)>>2, wwo->volume_left, - wwo->volume_right); + volume_effect32(wwo->sound_buffer, (nframes - jackFramesAvailable), wwo->volume_left, wwo->volume_right); /* convert from stereo 16 bit to single channel 32 bit float */ /* for each jack server channel */ /* NOTE: we skip over two sample since we want to only get either the left or right channel */ - sample_move_d16_s16(out_l, (short*)wwo->sound_buffer, (jackBytesAvailableThisCallback - jackBytesLeft)>>2, 2); - sample_move_d16_s16(out_r, (short*)wwo->sound_buffer + 1, - (jackBytesAvailableThisCallback - jackBytesLeft)>>2, 2); + sample_move_d16_s16(out_l, (short*)wwo->sound_buffer, (nframes - jackFramesAvailable), 2); + sample_move_d16_s16(out_r, (short*)wwo->sound_buffer + 1, (nframes - jackFramesAvailable), 2); /* see if we still have jackBytesLeft here, if we do that means that we ran out of wave data to play and had a buffer underrun, fill in the rest of the space with zero bytes */ - if(jackBytesLeft) + if(jackFramesAvailable) { - ERR("buffer underrun of %ld bytes\n", jackBytesLeft); - sample_silence_dS(out_l + ((jackBytesAvailableThisCallback - jackBytesLeft) / sizeof(sample_t)), jackBytesLeft / sizeof(sample_t)); - sample_silence_dS(out_r + ((jackBytesAvailableThisCallback - jackBytesLeft) / sizeof(sample_t)), jackBytesLeft / sizeof(sample_t)); + ERR("buffer underrun of %ld frames\n", jackFramesAvailable); + sample_silence_dS(out_l + (nframes - jackFramesAvailable), jackFramesAvailable); + sample_silence_dS(out_r + (nframes - jackFramesAvailable), jackFramesAvailable); } } else if(wwo->state == WINE_WS_PAUSED || @@ -417,33 +432,34 @@ } /* notify the client of completed wave headers */ + EnterCriticalSection(&wwo->access_crst); wodHelper_NotifyCompletions(wwo, FALSE); - LeaveCriticalSection(&wwo->access_crst); - TRACE("ending\n"); - return 0; } /****************************************************************** - * JACK_bufsize + * JACK_bufsize_wwo * * Called whenever the jack server changes the the max number * of frames passed to JACK_callback */ -int JACK_bufsize (nframes_t nframes, void *arg) +int JACK_bufsize_wwo (nframes_t nframes, void *arg) { WINE_WAVEOUT* wwo = (WINE_WAVEOUT*)arg; DWORD buffer_required; - TRACE("the maximum buffer size is now %lu frames\n", nframes); + TRACE("wDevID=%d\n",wwo->wDevID); + TRACE("the maximum buffer size is now %u frames\n", nframes); /* make sure the callback routine has adequate memory */ /* see if our buffer is large enough for the data we are writing */ /* ie. Buffer_size < (bytes we already wrote + bytes we are going to write in this loop) */ EnterCriticalSection(&wwo->access_crst); - buffer_required = sizeof(sample_t) * nframes; + /* wwo->sound_buffer is always filled with 16-bit stereo data, even for mono streams */ + buffer_required = nframes * sizeof(short) * 2; + TRACE("wwo->buffer_size (%ld) buffer_required (%ld).\n", wwo->buffer_size,buffer_required); if(wwo->buffer_size < buffer_required) { TRACE("expanding buffer from wwo->buffer_size == %ld, to %ld\n", @@ -452,9 +468,9 @@ wwo->buffer_size = buffer_required; if (wwo->sound_buffer) - wwo->sound_buffer = HeapReAlloc(GetProcessHeap(), 0, wwo->sound_buffer, wwo->buffer_size); + wwo->sound_buffer = HeapReAlloc(GetProcessHeap(), 0, wwo->sound_buffer, wwo->buffer_size); else - wwo->sound_buffer = HeapAlloc(GetProcessHeap(), 0, wwo->buffer_size); + wwo->sound_buffer = HeapAlloc(GetProcessHeap(), 0, wwo->buffer_size); /* if we don't have a buffer then error out */ if(!wwo->sound_buffer) @@ -467,26 +483,37 @@ LeaveCriticalSection(&wwo->access_crst); - TRACE("called\n"); + TRACE("ending\n"); return 0; } +/****************************************************************** + * JACK_bufsize_wwi + * + * Called whenever the jack server changes the the max number + * of frames passed to JACK_callback + */ +int JACK_bufsize_wwi (nframes_t nframes, void *arg) +{ + TRACE("the maximum buffer size is now %u frames\n", nframes); + return 0; +} /****************************************************************** * JACK_srate */ int JACK_srate (nframes_t nframes, void *arg) { - TRACE("the sample rate is now %lu/sec\n", nframes); + TRACE("the sample rate is now %u/sec\n", nframes); return 0; } /****************************************************************** - * JACK_shutdown + * JACK_shutdown_wwo */ /* if this is called then jack shut down... handle this appropriately */ -void JACK_shutdown(void* arg) +void JACK_shutdown_wwo(void* arg) { WINE_WAVEOUT* wwo = (WINE_WAVEOUT*)arg; @@ -496,7 +523,27 @@ /* lets see if we can't reestablish the connection */ Sleep(750); /* pause for a short period of time */ - if(!JACK_OpenDevice(wwo)) + if(!JACK_OpenWaveOutDevice(wwo)) + { + ERR("unable to reconnect with jack...\n"); + } +} + +/****************************************************************** + * JACK_shutdown_wwi + */ +/* if this is called then jack shut down... handle this appropriately */ +void JACK_shutdown_wwi(void* arg) +{ + WINE_WAVEIN* wwi = (WINE_WAVEIN*)arg; + + wwi->client = 0; /* reset client */ + + TRACE("trying to reconnect after sleeping for a short while...\n"); + + /* lets see if we can't reestablish the connection */ + Sleep(750); /* pause for a short period of time */ + if(!JACK_OpenWaveInDevice(wwi,wwi->format.wf.nChannels)) { ERR("unable to reconnect with jack...\n"); } @@ -504,9 +551,9 @@ /****************************************************************** - * JACK_OpenDevice + * JACK_OpenWaveOutDevice */ -static int JACK_OpenDevice(WINE_WAVEOUT* wwo) +static int JACK_OpenWaveOutDevice(WINE_WAVEOUT* wwo) { const char** ports; int i; @@ -537,8 +584,8 @@ wwo->buffer_size = 0; /* try to become a client of the JACK server */ - snprintf(client_name, sizeof(client_name), "wine_jack_client %d", wwo->wDevID); - TRACE("client name '%s'\n", client_name); + snprintf(client_name, sizeof(client_name), "wine_jack_out_%d", wwo->wDevID); + TRACE("client name '%s'\n", client_name); if ((client = fp_jack_client_new (client_name)) == 0) { /* jack has problems with shutting down clients, so lets */ @@ -550,16 +597,16 @@ return 0; } } - - /* tell the JACK server to call `JACK_callback()' whenever + + /* tell the JACK server to call `JACK_callback_wwo()' whenever there is work to be done. */ - fp_jack_set_process_callback (client, JACK_callback, wwo); + fp_jack_set_process_callback (client, JACK_callback_wwo, wwo); - /* tell the JACK server to call `JACK_bufsize()' whenever + /* tell the JACK server to call `JACK_bufsize_wwo()' whenever the maximum number of frames that will be passed to `JACK_Callback()' changes */ - fp_jack_set_buffer_size_callback (client, JACK_bufsize, wwo); - + fp_jack_set_buffer_size_callback (client, JACK_bufsize_wwo, wwo); + /* tell the JACK server to call `srate()' whenever the sample rate of the system changes. */ fp_jack_set_sample_rate_callback (client, JACK_srate, wwo); @@ -567,7 +614,7 @@ /* tell the JACK server to call `jack_shutdown()' if it ever shuts down, either entirely, or if it just decides to stop calling us. */ - fp_jack_on_shutdown (client, JACK_shutdown, wwo); + fp_jack_on_shutdown (client, JACK_shutdown_wwo, wwo); /* display the current sample rate. once the client is activated (see below), you should rely on your own sample rate @@ -583,6 +630,8 @@ out_port_r = fp_jack_port_register (client, "out_r", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + TRACE("Created ports. (%p) (%p)\n",out_port_l, out_port_r); + /* save away important values to the WINE_WAVEOUT struct */ wwo->client = client; wwo->out_port_l = out_port_l; @@ -592,6 +641,9 @@ wwo->in_use = TRUE; /* mark this device as in use since it now is ;-) */ #endif + /* set initial buffer size */ + JACK_bufsize_wwo (fp_jack_get_buffer_size(client),wwo); + /* tell the JACK server that we are ready to roll */ if (fp_jack_activate (client)) { @@ -599,6 +651,7 @@ return 0; } + TRACE("jack activate.\n"); /* figure out what the ports that we want to output on are */ /* NOTE: we do this instead of using stuff like "alsa_pcm:playback_X" because */ /* this way works if names are changed */ @@ -637,7 +690,11 @@ /* if something failed we need to shut the client down and return 0 */ if(failed) { - JACK_CloseDevice(wwo, TRUE); +#if JACK_CLOSE_HACK + JACK_CloseWaveOutDevice(wwo, TRUE); +#else + JACK_CloseWaveOutDevice(wwo); +#endif return 0; } @@ -645,20 +702,20 @@ } /****************************************************************** - * JACK_CloseDevice + * JACK_CloseWaveOutDevice * * Close the connection to the server cleanly. * If close_client is TRUE we close the client for this device instead of * just marking the device as in_use(JACK_CLOSE_HACK only) */ #if JACK_CLOSE_HACK -static void JACK_CloseDevice(WINE_WAVEOUT* wwo, BOOL close_client) +static void JACK_CloseWaveOutDevice(WINE_WAVEOUT* wwo, BOOL close_client) #else -static void JACK_CloseDevice(WINE_WAVEOUT* wwo) +static void JACK_CloseWaveOutDevice(WINE_WAVEOUT* wwo) #endif { #if JACK_CLOSE_HACK - TRACE("wDevID: %d, close_client: %d\n", wwo->wDevID, close_client); + TRACE("wDevID: %d, close_client (wwo): %d\n", wwo->wDevID, close_client); #else TRACE("wDevID: %d\n", wwo->wDevID); #endif @@ -688,6 +745,49 @@ } /****************************************************************** + * JACK_CloseWaveInDevice + * + * Close the connection to the server cleanly. + * If close_client is TRUE we close the client for this device instead of + * just marking the device as in_use(JACK_CLOSE_HACK only) + */ +#if JACK_CLOSE_HACK +static void JACK_CloseWaveInDevice(WINE_WAVEIN* wwi, BOOL close_client) +#else +static void JACK_CloseWaveInDevice(WINE_WAVEIN* wwi) +#endif +{ +#if JACK_CLOSE_HACK + TRACE("wDevID: %d, close_client (wwi): %d\n", wwi->wDevID, close_client); +#else + TRACE("wDevID: %d\n", wwi->wDevID); +#endif + +#if JACK_CLOSE_HACK + if(close_client) + { +#endif + fp_jack_deactivate(wwi->client); /* supposed to help the jack_client_close() to succeed */ + fp_jack_client_close (wwi->client); + + EnterCriticalSection(&wwi->access_crst); + wwi->client = 0; /* reset client */ + HeapFree(GetProcessHeap(), 0, wwi->sound_buffer); /* free buffer memory */ + wwi->sound_buffer = 0; + wwi->buffer_size = 0; /* zero out size of the buffer */ + LeaveCriticalSection(&wwi->access_crst); +#if JACK_CLOSE_HACK + } else + { + EnterCriticalSection(&wwi->access_crst); + TRACE("setting in_use to FALSE\n"); + wwi->in_use = FALSE; + LeaveCriticalSection(&wwi->access_crst); + } +#endif +} + +/****************************************************************** * JACK_WaveRelease * * @@ -696,23 +796,40 @@ { int iDevice; - TRACE("closing all open devices\n"); + TRACE("closing all open waveout devices\n"); - /* close all open devices */ + /* close all open output devices */ for(iDevice = 0; iDevice < MAX_WAVEOUTDRV; iDevice++) { TRACE("iDevice == %d\n", iDevice); if(WOutDev[iDevice].client) { #if JACK_CLOSE_HACK - JACK_CloseDevice(&WOutDev[iDevice], TRUE); /* close the device, FORCE the client to close */ + JACK_CloseWaveOutDevice(&WOutDev[iDevice], TRUE); /* close the device, FORCE the client to close */ #else - JACK_CloseDevice(&WOutDev[iDevice]); /* close the device, FORCE the client to close */ + JACK_CloseWaveOutDevice(&WOutDev[iDevice]); /* close the device, FORCE the client to close */ #endif DeleteCriticalSection(&(WOutDev[iDevice].access_crst)); /* delete the critical section */ } } + TRACE("closing all open wavein devices\n"); + + /* close all open input devices */ + for(iDevice = 0; iDevice < MAX_WAVEINDRV; iDevice++) + { + TRACE("iDevice == %d\n", iDevice); + if(WInDev[iDevice].client) + { +#if JACK_CLOSE_HACK + JACK_CloseWaveInDevice(&WInDev[iDevice], TRUE); /* close the device, FORCE the client to close */ +#else + JACK_CloseWaveInDevice(&WInDev[iDevice]); /* close the device, FORCE the client to close */ +#endif + DeleteCriticalSection(&(WInDev[iDevice].access_crst)); /* delete the critical section */ + } + } + TRACE("returning 1\n"); return 1; @@ -745,6 +862,7 @@ LOAD_FUNCPTR(jack_port_get_buffer); LOAD_FUNCPTR(jack_get_ports); LOAD_FUNCPTR(jack_port_name); + LOAD_FUNCPTR(jack_get_buffer_size); #undef LOAD_FUNCPTR /* start with output device */ @@ -755,6 +873,7 @@ #if JACK_CLOSE_HACK WOutDev[i].in_use = FALSE; + WInDev[i].in_use = FALSE; #endif memset(&WOutDev[i].caps, 0, sizeof(WOutDev[i].caps)); @@ -800,6 +919,37 @@ { /* TODO: we should initialize read stuff here */ memset(&WInDev[0].caps, 0, sizeof(WInDev[0].caps)); + + /* 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.wChannels = 0x2; + /* NOTE: we don't support any 8 bit modes so note that */ +/* 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 1; /* return success */ @@ -958,11 +1108,13 @@ lpWaveHdr->dwFlags &= ~WHDR_INQUEUE; lpWaveHdr->dwFlags |= WHDR_DONE; - TRACE("calling notify client\n"); + TRACE("notifying client: lpWaveHdr=(%p) lpPlayPtr=(%p) dwFlags=(%ld)\n", + lpWaveHdr, wwo->lpPlayPtr, lpWaveHdr->dwFlags); wodNotifyClient(wwo, WOM_DONE, (DWORD)lpWaveHdr, 0); } - + TRACE("Not notifying client: lpWaveHdr=(%p) lpPlayPtr=(%p) lpLoopPtr=(%p)\n", + lpWaveHdr, wwo->lpPlayPtr, wwo->lpLoopPtr); retval = (lpWaveHdr && lpWaveHdr != wwo->lpPlayPtr && lpWaveHdr != wwo->lpLoopPtr) ? 0 : INFINITE; @@ -1042,12 +1194,16 @@ return MMSYSERR_BADDEVICEID; } + TRACE("dwSupport=(0x%lx), dwFormats=(0x%lx)\n", WOutDev[wDevID].caps.dwSupport, WOutDev[wDevID].caps.dwFormats); memcpy(lpCaps, &WOutDev[wDevID].caps, min(dwSize, sizeof(*lpCaps))); return MMSYSERR_NOERROR; } /************************************************************************** * wodOpen [internal] + * + * NOTE: doesn't it seem like there is a race condition if you try to open + * the same device twice? */ static DWORD wodOpen(WORD wDevID, LPWAVEOPENDESC lpDesc, DWORD dwFlags) { @@ -1082,27 +1238,6 @@ return WAVERR_BADFORMAT; } - wwo = &WOutDev[wDevID]; - wwo->wDevID = wDevID; - - /* Set things up before we call JACK_OpenDevice because */ - /* we will start getting callbacks before JACK_OpenDevice */ - /* even returns and we want to be initialized before then */ - wwo->state = WINE_WS_STOPPED; /* start in a stopped state */ - wwo->dwPlayedTotal = 0; /* zero out these totals */ - wwo->dwWrittenTotal = 0; - wwo->bytesInJack = 0; - wwo->tickCountMS = 0; - - InitializeCriticalSection(&wwo->access_crst); /* initialize the critical section */ - - /* open up jack ports for this device */ - if (!JACK_OpenDevice(&WOutDev[wDevID])) - { - ERR("JACK_OpenDevice(%d) failed\n", wDevID); - return MMSYSERR_ERROR; /* return unspecified error */ - } - /* only PCM format is supported so far... */ if (lpDesc->lpFormat->wFormatTag != WAVE_FORMAT_PCM || lpDesc->lpFormat->nChannels == 0 || @@ -1122,15 +1257,41 @@ return MMSYSERR_NOERROR; } - dwFlags &= ~WAVE_DIRECTSOUND; /* direct sound not supported, ignore the flag */ + wwo = &WOutDev[wDevID]; + wwo->wDevID = wDevID; + + /* Set things up before we call JACK_OpenWaveOutDevice because */ + /* we will start getting callbacks before JACK_OpenWaveOutDevice */ + /* even returns and we want to be initialized before then */ + wwo->state = WINE_WS_STOPPED; /* start in a stopped state */ + wwo->dwPlayedTotal = 0; /* zero out these totals */ + wwo->dwWrittenTotal = 0; + wwo->bytesInJack = 0; + wwo->tickCountMS = 0; + /* Initialize volume to full level */ + wwo->volume_left = 100; + wwo->volume_right = 100; + + InitializeCriticalSection(&wwo->access_crst); /* initialize the critical section */ EnterCriticalSection(&wwo->access_crst); + dwFlags &= ~WAVE_DIRECTSOUND; /* direct sound not supported, ignore the flag */ + wwo->wFlags = HIWORD(dwFlags & CALLBACK_TYPEMASK); memcpy(&wwo->waveDesc, lpDesc, sizeof(WAVEOPENDESC)); memcpy(&wwo->format, lpDesc->lpFormat, sizeof(PCMWAVEFORMAT)); + /* open up jack ports for this device */ + if (!JACK_OpenWaveOutDevice(&WOutDev[wDevID])) + { + ERR("JACK_OpenWaveOutDevice(%d) failed\n", wDevID); + LeaveCriticalSection(&wwo->access_crst); + DeleteCriticalSection(&wwo->access_crst); /* delete the critical section so we can initialize it again from wodOpen() */ + return MMSYSERR_ERROR; /* return unspecified error */ + } + LeaveCriticalSection(&wwo->access_crst); /* display the current wave format */ @@ -1147,10 +1308,11 @@ wwo->sample_rate, wwo->format.wf.nSamplesPerSec); #if JACK_CLOSE_HACK - JACK_CloseDevice(wwo, FALSE); /* close this device, don't force the client to close */ + JACK_CloseWaveOutDevice(wwo, FALSE); /* close this device, don't force the client to close */ #else - JACK_CloseDevice(wwo); /* close this device */ + JACK_CloseWaveOutDevice(wwo); /* close this device */ #endif + DeleteCriticalSection(&wwo->access_crst); /* delete the critical section so we can initialize it again from wodOpen() */ return WAVERR_BADFORMAT; } @@ -1200,11 +1362,11 @@ wwo->state = WINE_WS_CLOSED; /* mark the device as closed */ #if JACK_CLOSE_HACK - JACK_CloseDevice(wwo, FALSE); /* close the jack device, DO NOT force the client to close */ + JACK_CloseWaveOutDevice(wwo, FALSE); /* close the jack device, DO NOT force the client to close */ #else - JACK_CloseDevice(wwo); /* close the jack device */ - DeleteCriticalSection(&wwo->access_crst); /* delete the critical section so we can initialize it again from wodOpen() */ + JACK_CloseWaveOutDevice(wwo); /* close the jack device */ #endif + DeleteCriticalSection(&wwo->access_crst); /* delete the critical section so we can initialize it again from wodOpen() */ ret = wodNotifyClient(wwo, WOM_CLOSE, 0L, 0L); } @@ -1212,6 +1374,7 @@ return ret; } + /************************************************************************** * wodWrite [internal] * @@ -1254,9 +1417,6 @@ for (wh = &(wwo->lpQueuePtr); *wh; wh = &((*wh)->lpNext)); *wh = lpWaveHdr; - LeaveCriticalSection(&wwo->access_crst); - - EnterCriticalSection(&wwo->access_crst); if (!wwo->lpPlayPtr) wodHelper_BeginWaveHdr(wwo,lpWaveHdr); if (wwo->state == WINE_WS_STOPPED) @@ -1626,6 +1786,619 @@ *======================================================================*/ /************************************************************************** + * 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; +} + +/****************************************************************** + * JACK_callback_wwi + */ +/* everytime the jack server wants something from us it calls this + function */ +int JACK_callback_wwi (nframes_t nframes, void *arg) +{ + sample_t* in_l; + sample_t* in_r = 0; + WINE_WAVEIN* wwi = (WINE_WAVEIN*)arg; + + TRACE("wDevID: %u, nframes %u\n", wwi->wDevID, nframes); + + if(!wwi->client) + ERR("client is closed, this is weird...\n"); + + in_l = (sample_t *) fp_jack_port_get_buffer(wwi->in_port_l, nframes); + + if (wwi->in_port_r) + in_r = (sample_t *) fp_jack_port_get_buffer(wwi->in_port_r, nframes); + + EnterCriticalSection(&wwi->access_crst); + + if((wwi->lpQueuePtr != NULL) && (wwi->state == WINE_WS_PLAYING)) + { + LPWAVEHDR lpWaveHdr = wwi->lpQueuePtr; + nframes_t jackFramesLeft = nframes; + +#if JACK_CLOSE_HACK + if(wwi->in_use == FALSE) + { + /* do nothing if nothing is being recorded */ + return 0; + } +#endif + + TRACE("wwi.state == WINE_WS_PLAYING\n"); + + while (lpWaveHdr && jackFramesLeft) + { + DWORD waveHdrFramesLeft = (lpWaveHdr->dwBufferLength - lpWaveHdr->dwBytesRecorded) / (sizeof(short) * wwi->format.wf.nChannels); + DWORD numFrames = min (jackFramesLeft, waveHdrFramesLeft); + + TRACE ("dwBufferLength=(%lu) dwBytesRecorded=(%ld)\n", lpWaveHdr->dwBufferLength, lpWaveHdr->dwBytesRecorded); + TRACE ("jackFramesLeft=(%u) waveHdrFramesLeft=(%lu)\n", jackFramesLeft, waveHdrFramesLeft); + + if (!in_r) { + /* mono */ + sample_move_s16_d16((short *)((char *)lpWaveHdr->lpData + lpWaveHdr->dwBytesRecorded), in_l+(nframes-jackFramesLeft), numFrames, 1); + } else { + /* stereo */ + sample_move_s16_d16((short *)((char *)lpWaveHdr->lpData + lpWaveHdr->dwBytesRecorded), + in_l+(nframes-jackFramesLeft), numFrames, 2); + sample_move_s16_d16((short *)((char *)lpWaveHdr->lpData + lpWaveHdr->dwBytesRecorded + sizeof(short)), + in_r+(nframes-jackFramesLeft), numFrames, 2); + } + + lpWaveHdr->dwBytesRecorded += (numFrames * sizeof(short) * wwi->format.wf.nChannels ); + jackFramesLeft -= numFrames; + + 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; + + lpWaveHdr->dwFlags &= ~WHDR_INQUEUE; + lpWaveHdr->dwFlags |= WHDR_DONE; + + TRACE("WaveHdr full. dwBytesRecorded=(%lu) dwFlags=(0x%lx)\n",lpWaveHdr->dwBytesRecorded,lpWaveHdr->dwFlags); + + widNotifyClient(wwi, WIM_DATA, (DWORD)lpWaveHdr, 0); + + lpWaveHdr = wwi->lpQueuePtr = lpNext; + } + } + TRACE ("jackFramesLeft=(%u) lpWaveHdr=(%p)\n", jackFramesLeft, lpWaveHdr); + if (jackFramesLeft > 0) { WARN("Record buffer ran out of WaveHdrs\n"); } + } + + LeaveCriticalSection(&wwi->access_crst); + + return 0; +} + +/****************************************************************** + * JACK_OpenWaveInDevice + */ +static int JACK_OpenWaveInDevice(WINE_WAVEIN* wwi, WORD nChannels) +{ + const char** ports; + int i; + char client_name[64]; + jack_port_t* in_port_l; + jack_port_t* in_port_r = 0; + jack_client_t* client; + int failed = 0; + + TRACE("creating jack client and setting up callbacks\n"); + + if ((nChannels == 0) || (nChannels > 2)) { + ERR ("nChannels = (%d), but we only support mono or stereo.\n", nChannels); + return 0; + } + +#if JACK_CLOSE_HACK + /* see if this device is already open */ + if(wwi->client) + { + /* if this device is already in use then it is bad for us to be in here */ + if(wwi->in_use) + return 0; + + TRACE("using existing client\n"); + wwi->in_use = TRUE; + return 1; + } +#endif + + /* zero out the buffer pointer and the size of the buffer */ + wwi->sound_buffer = 0; + wwi->buffer_size = 0; + + /* try to become a client of the JACK server */ + snprintf(client_name, sizeof(client_name), "wine_jack_in_%d", wwi->wDevID); + TRACE("client name '%s'\n", client_name); + if ((client = fp_jack_client_new (client_name)) == 0) + { + /* jack has problems with shutting down clients, so lets */ + /* wait a short while and try once more before we give up */ + Sleep(250); + if ((client = fp_jack_client_new (client_name)) == 0) + { + ERR("jack server not running?\n"); + return 0; + } + } + wwi->client = client; + + /* tell the JACK server to call `JACK_wwi_callback()' whenever + there is work to be done. */ + fp_jack_set_process_callback (client, JACK_callback_wwi, wwi); + + /* tell the JACK server to call `JACK_bufsize_wwi()' whenever + the maximum number of frames that will be passed + to `JACK_Callback()' changes */ + fp_jack_set_buffer_size_callback (client, JACK_bufsize_wwi, wwi); + + /* tell the JACK server to call `srate()' whenever + the sample rate of the system changes. */ + fp_jack_set_sample_rate_callback (client, JACK_srate, wwi); + + /* tell the JACK server to call `jack_shutdown()' if + it ever shuts down, either entirely, or if it + just decides to stop calling us. */ + fp_jack_on_shutdown (client, JACK_shutdown_wwi, wwi); + + /* display the current sample rate. once the client is activated + (see below), you should rely on your own sample rate + callback (see above) for this value. */ + wwi->sample_rate = fp_jack_get_sample_rate(client); + TRACE("engine sample rate: %lu\n", wwi->sample_rate); + + /* create the left and right channel output ports */ + /* jack's ports are all mono so for stereo you need two */ + in_port_l = fp_jack_port_register (client, "in_l", + JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0); + wwi->in_port_l = in_port_l; + TRACE("Created port. (%p)\n", in_port_l); + + if (nChannels == 2) + { + in_port_r = fp_jack_port_register (client, "in_r", + JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0); + TRACE("Created port. (%p)\n", in_port_r); + } + wwi->in_port_r = in_port_r; + +#if JACK_CLOSE_HACK + wwi->in_use = TRUE; /* mark this device as in use since it now is ;-) */ +#endif + + TRACE("activating client.\n"); + /* tell the JACK server that we are ready to roll */ + if (fp_jack_activate (client)) + { + ERR( "cannot activate client\n"); + return 0; + } + TRACE("activated client.\n"); + /* figure out what the ports that we want to output on are */ + /* NOTE: we do this instead of using stuff like "alsa_pcm:playback_X" because */ + /* this way works if names are changed */ + ports = fp_jack_get_ports(client, NULL, NULL, JackPortIsPhysical|JackPortIsOutput); + + /* display a trace of the output ports we found */ + for(i = 0; ports[i]; i++) + { + TRACE("ports[%d] = '%s'\n", i, ports[i]); + } + + if(!ports) + { + ERR("jack_get_ports() failed to find 'JackPortIsPhysical|JackPortIsOutput'\n"); + } + + /* connect the ports. Note: you can't do this before + the client is activated (this may change in the future). + */ + /* we want to connect to two ports so we have stereo input ;-) */ + + if(fp_jack_connect(client, ports[0], fp_jack_port_name(in_port_l))) + { + ERR ("cannot connect to input port %d('%s')\n", 0, ports[0]); + failed = 1; + } + TRACE("Connected (%s)<->(%s)\n",ports[0],fp_jack_port_name(in_port_l)); + + if ((nChannels == 2) && in_port_r) { + if(fp_jack_connect(client, ports[1], fp_jack_port_name(in_port_r))) + { + ERR ("cannot connect to input port %d('%s')\n", 1, ports[1]); + failed = 1; + } + TRACE("Connected (%s)<->(%s)\n",ports[1],fp_jack_port_name(in_port_r)); + } + free(ports); /* free the returned array of ports */ + + /* if something failed we need to shut the client down and return 0 */ + if(failed) + { +#if JACK_CLOSE_HACK + JACK_CloseWaveInDevice(wwi, TRUE); +#else + JACK_CloseWaveInDevice(wwi); +#endif + return 0; + } + + TRACE("return success.\n"); + return 1; /* return success */ +} + +/************************************************************************** + * 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_WAVEINDRV reached !\n"); + return MMSYSERR_BADDEVICEID; + } + + memcpy(lpCaps, &WInDev[wDevID].caps, min(dwSize, sizeof(*lpCaps))); + return MMSYSERR_NOERROR; +} + +/************************************************************************** + * widOpen [internal] + */ +static DWORD widOpen(WORD wDevID, LPWAVEOPENDESC lpDesc, DWORD dwFlags) +{ + WINE_WAVEIN* wwi; + DWORD retval; + + TRACE("(%u, %p, %08lX);\n", wDevID, lpDesc, dwFlags); + if (lpDesc == NULL) + { + WARN("Invalid Parameter !\n"); + return MMSYSERR_INVALPARAM; + } + if (wDevID >= MAX_WAVEINDRV) { + TRACE ("MAX_WAVEINDRV reached !\n"); + return MMSYSERR_BADDEVICEID; + } + +#if JACK_CLOSE_HACK + if(WInDev[wDevID].client && WOutDev[wDevID].in_use) +#else + if(WInDev[wDevID].client) +#endif + { + TRACE("device %d already allocated\n", wDevID); + return MMSYSERR_ALLOCATED; + } + + /* make sure we aren't being opened in 8 bit mode */ + if(lpDesc->lpFormat->wBitsPerSample == 8) + { + TRACE("8bits per sample unsupported, returning WAVERR_BADFORMAT\n"); + return WAVERR_BADFORMAT; + } + + /* only PCM format is supported 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]; + wwi->wDevID = wDevID; + + /* Set things up before we call JACK_OpenWaveOutDevice because */ + /* we will start getting callbacks before JACK_OpenWaveOutDevice */ + /* even returns and we want to be initialized before then */ + wwi->state = WINE_WS_STOPPED; /* start in a stopped state */ + + InitializeCriticalSection(&wwi->access_crst); /* initialize the critical section */ + EnterCriticalSection(&wwi->access_crst); + + /* open up jack ports for this device */ + if (!JACK_OpenWaveInDevice(&WInDev[wDevID], lpDesc->lpFormat->nChannels)) + { + ERR("JACK_OpenWaveInDevice(%d) failed\n", wDevID); + LeaveCriticalSection(&wwi->access_crst); + DeleteCriticalSection(&wwi->access_crst); + return MMSYSERR_ERROR; /* return unspecified error */ + } + + dwFlags &= ~WAVE_DIRECTSOUND; /* direct sound not supported, ignore the flag */ + + wwi->wFlags = HIWORD(dwFlags & CALLBACK_TYPEMASK); + + memcpy(&wwi->waveDesc, lpDesc, sizeof(WAVEOPENDESC)); + memcpy(&wwi->format, lpDesc->lpFormat, sizeof(PCMWAVEFORMAT)); + + LeaveCriticalSection(&wwi->access_crst); + + /* display the current wave format */ + 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); + + /* make sure that we have the same sample rate in our audio stream */ + /* as we do in the jack server */ + if(wwi->format.wf.nSamplesPerSec != wwi->sample_rate) + { + TRACE("error: jack server sample rate is '%ld', wave sample rate is '%ld'\n", + wwi->sample_rate, wwi->format.wf.nSamplesPerSec); + +#if JACK_CLOSE_HACK + JACK_CloseWaveInDevice(wwi, FALSE); /* close this device, don't force the client to close */ +#else + JACK_CloseWaveInDevice(wwi); /* close this device */ +#endif + DeleteCriticalSection(&wwi->access_crst); + return WAVERR_BADFORMAT; + } + + /* check for an invalid number of bits per sample */ + if (wwi->format.wBitsPerSample == 0) + { + WARN("Resetting zeroed wBitsPerSample to 16\n"); + wwi->format.wBitsPerSample = 16 * + (wwi->format.wf.nAvgBytesPerSec / + wwi->format.wf.nSamplesPerSec) / + wwi->format.wf.nChannels; + } + + TRACE("notify client.\n"); + EnterCriticalSection(&wwi->access_crst); + retval = widNotifyClient(wwi, WIM_OPEN, 0L, 0L); + LeaveCriticalSection(&wwi->access_crst); + + return retval; +} +/************************************************************************** + * widClose [internal] + */ +static DWORD widClose(WORD wDevID) +{ + DWORD ret = MMSYSERR_NOERROR; + WINE_WAVEIN* wwi; + + TRACE("(%u);\n", wDevID); + + if (wDevID >= MAX_WAVEINDRV || !WInDev[wDevID].client) + { + WARN("bad device ID !\n"); + return MMSYSERR_BADDEVICEID; + } + + wwi = &WInDev[wDevID]; + if (wwi->lpQueuePtr) + { + WARN("buffers still playing !\n"); + ret = WAVERR_STILLPLAYING; + } else + { + /* sanity check: this should not happen since the device must have been reset before */ + if (wwi->lpQueuePtr) ERR("out of sync\n"); + + wwi->state = WINE_WS_CLOSED; /* mark the device as closed */ + +#if JACK_CLOSE_HACK + JACK_CloseWaveInDevice(wwi, FALSE); /* close the jack device, DO NOT force the client to close */ +#else + JACK_CloseWaveInDevice(wwi); /* close the jack device */ +#endif + DeleteCriticalSection(&wwi->access_crst); /* delete the critical section so we can initialize it again from wodOpen() */ + + ret = widNotifyClient(wwi, WIM_CLOSE, 0L, 0L); + } + + return ret; +} + +/************************************************************************** + * widAddBuffer [internal] + */ +static DWORD widAddBuffer(WORD wDevID, LPWAVEHDR lpWaveHdr, DWORD dwSize) +{ + WINE_WAVEIN* wwi = &WInDev[wDevID]; + + 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; + + EnterCriticalSection(&wwi->access_crst); + /* insert buffer at end of queue */ + { + LPWAVEHDR* wh; + for (wh = &(wwi->lpQueuePtr); *wh; wh = &((*wh)->lpNext)); + *wh=lpWaveHdr; + } + LeaveCriticalSection(&wwi->access_crst); + + 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; + lpWaveHdr->lpNext = NULL; + + 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; + } + + WInDev[wDevID].state = WINE_WS_PLAYING; + return MMSYSERR_NOERROR; +} + +/************************************************************************** + * widStop [internal] + */ +static DWORD widStop(WORD wDevID) +{ + WINE_WAVEIN* wwi = &WInDev[wDevID]; + + TRACE("(%u);\n", wDevID); + if (wDevID >= MAX_WAVEINDRV || WInDev[wDevID].state == WINE_WS_CLOSED) { + WARN("can't stop !\n"); + return MMSYSERR_INVALHANDLE; + } + + if (wwi->state != WINE_WS_STOPPED) + { + WAVEHDR* lpWaveHdr; + /* do something here to stop 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; + + return MMSYSERR_NOERROR; +} + +/************************************************************************** + * widReset [internal] + */ +static DWORD widReset(WORD wDevID) +{ + WINE_WAVEIN* wwi = &WInDev[wDevID]; + WAVEHDR* lpWaveHdr; + + TRACE("(%u);\n", wDevID); + if (wDevID >= MAX_WAVEINDRV || WInDev[wDevID].state == WINE_WS_CLOSED) { + WARN("can't reset !\n"); + return MMSYSERR_INVALHANDLE; + } + + wwi->state = WINE_WS_STOPPED; + + /* 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; + + return MMSYSERR_NOERROR; +} + +/************************************************************************** * widMessage (WINEJACK.6) */ DWORD WINAPI JACK_widMessage(WORD wDevID, WORD wMsg, DWORD dwUser, @@ -1634,10 +2407,38 @@ 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 MAX_WAVEINDRV; + 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; } #else /* !HAVE_JACK_JACK_H */ + +DWORD WINAPI JACK_widMessage(WORD wDevID, WORD wMsg, DWORD dwUser, + DWORD dwParam1, DWORD dwParam2) +{ + FIXME("(%u, %04X, %08lX, %08lX, %08lX):jack support not compiled into wine\n", wDevID, wMsg, dwUser, dwParam1, dwParam2); + return MMSYSERR_NOTENABLED; +} /************************************************************************** * wodMessage (WINEJACK.7)