/* * Wine Driver for jack Sound Server * http://jackit.sourceforge.net * * Copyright 1994 Martin Ayotte * Copyright 1999 Eric Pouech (async playing in waveOut/waveIn) * Copyright 2000 Eric Pouech (loops in waveOut) * Copyright 2002 Chris Morgan (jack version of this file) * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* * TODO: * implement audio stream resampling for any arbitrary frequenty * 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 * * FIXME: * pause in waveOut during loop is not handled correctly */ #include "config.h" #include #include #include #include #ifdef HAVE_UNISTD_H # include #endif #include #include "windef.h" #include "winbase.h" #include "winnls.h" #include "wingdi.h" #include "winerror.h" #include "mmddk.h" #include "dsound.h" #include "dsdriver.h" #include "jack.h" #include "wine/unicode.h" #include "wine/library.h" #include "wine/debug.h" #ifdef HAVE_JACK_JACK_H #include #endif WINE_DEFAULT_DEBUG_CHANNEL(wave); #ifdef HAVE_JACK_JACK_H #define MAKE_FUNCPTR(f) static typeof(f) * fp_##f = NULL; /* Function pointers for dynamic loading of libjack */ /* these are prefixed with "fp_", ie. "fp_jack_client_new" */ MAKE_FUNCPTR(jack_activate); MAKE_FUNCPTR(jack_connect); MAKE_FUNCPTR(jack_client_new); MAKE_FUNCPTR(jack_client_close); MAKE_FUNCPTR(jack_deactivate); MAKE_FUNCPTR(jack_set_process_callback); MAKE_FUNCPTR(jack_set_buffer_size_callback); MAKE_FUNCPTR(jack_set_sample_rate_callback); MAKE_FUNCPTR(jack_on_shutdown); MAKE_FUNCPTR(jack_get_sample_rate); MAKE_FUNCPTR(jack_port_register); 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 */ /* takes a very long time, so to get around this we actually don't */ /* close the port when the device is closed but instead mark the */ /* corresponding device as unused */ #define JACK_CLOSE_HACK 1 typedef jack_default_audio_sample_t sample_t; typedef jack_nframes_t nframes_t; /* only allow 10 output devices through this driver, this ought to be adequate */ #define MAX_WAVEOUTDRV (10) #define MAX_WAVEINDRV (10) /* state diagram for waveOut writing: * * +---------+-------------+---------------+---------------------------------+ * | state | function | event | new state | * +---------+-------------+---------------+---------------------------------+ * | | open() | | STOPPED | * | PAUSED | write() | | PAUSED | * | STOPPED | write() | | PLAYING | * | PLAYING | write() | HEADER | PLAYING | * | (other) | write() | | | * | (any) | pause() | PAUSING | PAUSED | * | PAUSED | restart() | RESTARTING | PLAYING (if no thrd => STOPPED) | * | (any) | reset() | RESETTING | STOPPED | * | (any) | close() | CLOSING | CLOSED | * +---------+-------------+---------------+---------------------------------+ */ /* states of the playing device */ #define WINE_WS_PLAYING 0 #define WINE_WS_PAUSED 1 #define WINE_WS_STOPPED 2 #define WINE_WS_CLOSED 3 typedef struct { volatile int state; /* one of the WINE_WS_ manifest constants */ WAVEOPENDESC waveDesc; WORD wFlags; PCMWAVEFORMAT format; WAVEOUTCAPSW caps; WORD wDevID; char interface_name[32]; jack_port_t* out_port_l; /* ports for left and right channels */ jack_port_t* out_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; DWORD volume_left; DWORD volume_right; LPWAVEHDR lpQueuePtr; /* start of queued WAVEHDRs (waiting to be notified) */ LPWAVEHDR lpPlayPtr; /* start of not yet fully played buffers */ DWORD dwPartialOffset; /* Offset of not yet written bytes in lpPlayPtr */ LPWAVEHDR lpLoopPtr; /* pointer of first buffer in loop, if any */ DWORD dwLoops; /* private copy of loop counter */ DWORD dwPlayedTotal; /* number of bytes actually played since opening */ DWORD dwWrittenTotal; /* number of bytes written to jack since opening */ DWORD bytesInJack; /* bytes that we wrote during the previous JACK_Callback() */ DWORD tickCountMS; /* time in MS of last JACK_Callback() */ /* synchronization stuff */ CRITICAL_SECTION access_crst; } WINE_WAVEOUT; typedef struct { volatile int state; WAVEOPENDESC waveDesc; WORD wFlags; PCMWAVEFORMAT format; LPWAVEHDR lpQueuePtr; DWORD dwTotalRecorded; WAVEINCAPSW caps; BOOL bTriggerSupport; WORD wDevID; char interface_name[32]; 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; } 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); static LPWAVEHDR wodHelper_PlayPtrNext(WINE_WAVEOUT* wwo); static DWORD wodHelper_NotifyCompletions(WINE_WAVEOUT* wwo, BOOL force); 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_CloseWaveInDevice(WINE_WAVEIN* wwi, BOOL close_client); #else static void JACK_CloseWaveInDevice(WINE_WAVEIN* wwi); #endif static DWORD bytes_to_mmtime(LPMMTIME lpTime, DWORD position, PCMWAVEFORMAT* format) { TRACE("wType=%04X wBitsPerSample=%u nSamplesPerSec=%lu nChannels=%u nAvgBytesPerSec=%lu\n", lpTime->wType, format->wBitsPerSample, format->wf.nSamplesPerSec, format->wf.nChannels, format->wf.nAvgBytesPerSec); TRACE("Position in bytes=%lu\n", position); switch (lpTime->wType) { case TIME_SAMPLES: lpTime->u.sample = position / (format->wBitsPerSample / 8 * format->wf.nChannels); TRACE("TIME_SAMPLES=%lu\n", lpTime->u.sample); break; case TIME_MS: lpTime->u.ms = 1000.0 * position / (format->wBitsPerSample / 8 * format->wf.nChannels * format->wf.nSamplesPerSec); TRACE("TIME_MS=%lu\n", lpTime->u.ms); break; case TIME_SMPTE: lpTime->u.smpte.fps = 30; position = position / (format->wBitsPerSample / 8 * format->wf.nChannels); position += (format->wf.nSamplesPerSec / lpTime->u.smpte.fps) - 1; /* round up */ lpTime->u.smpte.sec = position / format->wf.nSamplesPerSec; position -= lpTime->u.smpte.sec * format->wf.nSamplesPerSec; lpTime->u.smpte.min = lpTime->u.smpte.sec / 60; lpTime->u.smpte.sec -= 60 * lpTime->u.smpte.min; lpTime->u.smpte.hour = lpTime->u.smpte.min / 60; lpTime->u.smpte.min -= 60 * lpTime->u.smpte.hour; lpTime->u.smpte.fps = 30; lpTime->u.smpte.frame = position * lpTime->u.smpte.fps / format->wf.nSamplesPerSec; TRACE("TIME_SMPTE=%02u:%02u:%02u:%02u\n", lpTime->u.smpte.hour, lpTime->u.smpte.min, lpTime->u.smpte.sec, lpTime->u.smpte.frame); break; default: WARN("Format %d not supported, using TIME_BYTES !\n", lpTime->wType); lpTime->wType = TIME_BYTES; /* fall through */ case TIME_BYTES: lpTime->u.cb = position; TRACE("TIME_BYTES=%lu\n", lpTime->u.cb); break; } return MMSYSERR_NOERROR; } /*======================================================================* * Low level WAVE implementation * *======================================================================*/ #define SAMPLE_MAX_16BIT 32767.0f /* Alsaplayer function that applies volume changes to a buffer */ /* (C) Andy Lo A Foe */ /* Length is in terms of 32 bit samples */ void volume_effect32(void *buffer, int length, int left, int right) { short *data = (short *)buffer; int i, v; if (right == -1) right = left; for(i = 0; i < length; i++) { v = (int) ((*(data) * left) / 100); *(data++) = (v>32767) ? 32767 : ((v<-32768) ? -32768 : v); v = (int) ((*(data) * right) / 100); *(data++) = (v>32767) ? 32767 : ((v<-32768) ? -32768 : v); } } /* move 16 bit mono/stereo to 16 bit stereo */ void sample_move_d16_d16(short *dst, short *src, unsigned long nsamples, int nChannels) { while(nsamples--) { *dst = *src; dst++; if(nChannels == 2) src++; *dst = *src; dst++; src++; } } /* convert from 16 bit to floating point */ /* allow for copying of stereo data with alternating left/right */ /* channels to a buffer that will hold a single channel stream */ /* nsamples is in terms of 16bit samples */ /* src_skip is in terms of 16bit samples */ void sample_move_d16_s16 (sample_t *dst, short *src, unsigned long nsamples, unsigned long src_skip) { /* ALERT: signed sign-extension portability !!! */ while (nsamples--) { *dst = (*src) / SAMPLE_MAX_16BIT; dst++; src += src_skip; } } /* 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) { /* ALERT: signed sign-extension portability !!! */ while (nsamples--) { *dst = 0; dst++; } } /****************************************************************** * 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_wwo (nframes_t nframes, void *arg) { sample_t* out_l; sample_t* out_r; WINE_WAVEOUT* wwo = (WINE_WAVEOUT*)arg; 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); if(wwo->state == WINE_WS_PLAYING) { DWORD jackFramesAvailable = nframes; DWORD outputFramesAvailable; DWORD numFramesToWrite; long written = 0; char* buffer; #if JACK_CLOSE_HACK if(wwo->in_use == FALSE) { /* output silence if nothing is being outputted */ sample_silence_dS(out_l, nframes); sample_silence_dS(out_r, nframes); return 0; } #endif TRACE("wwo.state == WINE_WS_PLAYING\n"); /* 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 %d bytes\n", wwo->buffer_size, (nframes * sizeof(short) * 2)); return 0; } /* 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 */ outputFramesAvailable = (wwo->lpPlayPtr->dwBufferLength - wwo->dwPartialOffset) / wwo->format.wf.nBlockAlign; 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 + ((nframes - jackFramesAvailable) * sizeof(short)), (short*)buffer, numFramesToWrite, wwo->format.wf.nChannels); } else /* just copy the memory over */ { 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(numFramesToWrite == outputFramesAvailable) { wodHelper_PlayPtrNext(wwo); /* we wrote the whole waveheader, skip to the next one*/ } else { wwo->dwPartialOffset+=(numFramesToWrite * wwo->format.wf.nBlockAlign); /* else advance by the bytes we took in to write */ } 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 */ wwo->dwWrittenTotal+=written; /* update states on wave device */ wwo->dwPlayedTotal+=wwo->bytesInJack; /* we must have finished with the last bytes or we wouldn't be back inside of this callback again... */ wwo->bytesInJack = written; /* record the bytes inside of jack */ /* Now that we have finished filling the buffer either until it is full or until */ /* we have run out of application sound data to process, apply volume and output */ /* the audio to the jack server */ /* apply volume to the buffer */ 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, (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(jackFramesAvailable) { 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 || wwo->state == WINE_WS_STOPPED || wwo->state == WINE_WS_CLOSED) { /* output silence if nothing is being outputted */ sample_silence_dS(out_l, nframes); sample_silence_dS(out_r, nframes); } /* notify the client of completed wave headers */ EnterCriticalSection(&wwo->access_crst); wodHelper_NotifyCompletions(wwo, FALSE); LeaveCriticalSection(&wwo->access_crst); return 0; } /****************************************************************** * JACK_bufsize_wwo * * Called whenever the jack server changes the the max number * of frames passed to JACK_callback */ int JACK_bufsize_wwo (nframes_t nframes, void *arg) { WINE_WAVEOUT* wwo = (WINE_WAVEOUT*)arg; DWORD buffer_required; 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); /* 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", wwo->buffer_size, buffer_required); TRACE("GetProcessHeap() == %p\n", GetProcessHeap()); wwo->buffer_size = buffer_required; if (wwo->sound_buffer) wwo->sound_buffer = HeapReAlloc(GetProcessHeap(), 0, wwo->sound_buffer, wwo->buffer_size); else wwo->sound_buffer = HeapAlloc(GetProcessHeap(), 0, wwo->buffer_size); /* if we don't have a buffer then error out */ if(!wwo->sound_buffer) { ERR("error allocating sound_buffer memory\n"); LeaveCriticalSection(&wwo->access_crst); return 0; } } LeaveCriticalSection(&wwo->access_crst); 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 %u/sec\n", nframes); return 0; } /****************************************************************** * JACK_shutdown_wwo */ /* if this is called then jack shut down... handle this appropriately */ void JACK_shutdown_wwo(void* arg) { WINE_WAVEOUT* wwo = (WINE_WAVEOUT*)arg; wwo->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_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"); } } /****************************************************************** * JACK_OpenWaveOutDevice */ static int JACK_OpenWaveOutDevice(WINE_WAVEOUT* wwo) { const char** ports; int i; char client_name[64]; jack_port_t* out_port_l; jack_port_t* out_port_r; jack_client_t* client; int failed = 0; TRACE("creating jack client and setting up callbacks\n"); #if JACK_CLOSE_HACK /* see if this device is already open */ if(wwo->client) { /* if this device is already in use then it is bad for us to be in here */ if(wwo->in_use) return 0; TRACE("using existing client\n"); wwo->in_use = TRUE; return 1; } #endif /* zero out the buffer pointer and the size of the buffer */ wwo->sound_buffer = 0; wwo->buffer_size = 0; /* try to become a client of the JACK server */ 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 */ /* 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; } } /* 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, wwo); /* 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, 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); /* 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, wwo); /* 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. */ wwo->sample_rate = fp_jack_get_sample_rate(client); TRACE("engine sample rate: %lu\n", wwo->sample_rate); /* create the left and right channel output ports */ /* jack's ports are all mono so for stereo you need two */ out_port_l = fp_jack_port_register (client, "out_l", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); 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; wwo->out_port_r = out_port_r; #if JACK_CLOSE_HACK 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)) { ERR( "cannot activate client\n"); 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 */ ports = fp_jack_get_ports(client, NULL, NULL, JackPortIsPhysical|JackPortIsInput); /* 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|JackPortIsInput'\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 output ;-) */ if(fp_jack_connect(client, fp_jack_port_name(out_port_l), ports[0])) { ERR ("cannot connect to output port %d('%s')\n", 0, ports[0]); failed = 1; } if(fp_jack_connect(client, fp_jack_port_name(out_port_r), ports[1])) { ERR ("cannot connect to output port %d('%s')\n", 1, ports[1]); failed = 1; } 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_CloseWaveOutDevice(wwo, TRUE); #else JACK_CloseWaveOutDevice(wwo); #endif return 0; } return 1; /* return success */ } /****************************************************************** * 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_CloseWaveOutDevice(WINE_WAVEOUT* wwo, BOOL close_client) #else static void JACK_CloseWaveOutDevice(WINE_WAVEOUT* wwo) #endif { #if JACK_CLOSE_HACK TRACE("wDevID: %d, close_client (wwo): %d\n", wwo->wDevID, close_client); #else TRACE("wDevID: %d\n", wwo->wDevID); #endif #if JACK_CLOSE_HACK if(close_client) { #endif fp_jack_deactivate(wwo->client); /* supposed to help the jack_client_close() to succeed */ fp_jack_client_close (wwo->client); EnterCriticalSection(&wwo->access_crst); wwo->client = 0; /* reset client */ HeapFree(GetProcessHeap(), 0, wwo->sound_buffer); /* free buffer memory */ wwo->sound_buffer = 0; wwo->buffer_size = 0; /* zero out size of the buffer */ LeaveCriticalSection(&wwo->access_crst); #if JACK_CLOSE_HACK } else { EnterCriticalSection(&wwo->access_crst); TRACE("setting in_use to FALSE\n"); wwo->in_use = FALSE; LeaveCriticalSection(&wwo->access_crst); } #endif } /****************************************************************** * 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 * * */ LONG JACK_WaveRelease(void) { int iDevice; TRACE("closing all open waveout devices\n"); /* 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_CloseWaveOutDevice(&WOutDev[iDevice], TRUE); /* close the device, FORCE the client to close */ #else 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; } /****************************************************************** * JACK_WaveInit * * Initialize internal structures from JACK server info */ LONG JACK_WaveInit(void) { int i; static const WCHAR ini_out[] = {'J','A','C','K',' ','W','a','v','e','O','u','t',' ','D','r','i','v','e','r',0}; static const WCHAR ini_in [] = {'J','A','C','K',' ','W','a','v','e','I','n',' ',' ','D','r','i','v','e','r',0}; TRACE("called\n"); /* setup function pointers */ #define LOAD_FUNCPTR(f) if((fp_##f = wine_dlsym(jackhandle, #f, NULL, 0)) == NULL) goto sym_not_found; LOAD_FUNCPTR(jack_activate); LOAD_FUNCPTR(jack_connect); LOAD_FUNCPTR(jack_client_new); LOAD_FUNCPTR(jack_client_close); LOAD_FUNCPTR(jack_deactivate); LOAD_FUNCPTR(jack_set_process_callback); LOAD_FUNCPTR(jack_set_buffer_size_callback); LOAD_FUNCPTR(jack_set_sample_rate_callback); LOAD_FUNCPTR(jack_on_shutdown); LOAD_FUNCPTR(jack_get_sample_rate); LOAD_FUNCPTR(jack_port_register); 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 */ for (i = 0; i < MAX_WAVEOUTDRV; ++i) { WOutDev[i].client = 0; /* initialize the client to 0 */ #if JACK_CLOSE_HACK WOutDev[i].in_use = FALSE; WInDev[i].in_use = FALSE; #endif memset(&WOutDev[i].caps, 0, sizeof(WOutDev[i].caps)); WOutDev[i].caps.wMid = 0x00FF; /* Manufac ID */ WOutDev[i].caps.wPid = 0x0001; /* Product ID */ strcpyW(WOutDev[i].caps.szPname, ini_out); snprintf(WOutDev[i].interface_name, sizeof(WOutDev[i].interface_name), "winejack: %d", i); WOutDev[i].caps.vDriverVersion = 0x0100; WOutDev[i].caps.dwFormats = 0x00000000; WOutDev[i].caps.dwSupport = WAVECAPS_VOLUME; WOutDev[i].caps.wChannels = 2; WOutDev[i].caps.dwSupport |= WAVECAPS_LRVOLUME; /* NOTE: we don't support any 8 bit modes so note that */ /* WOutDev[i].caps.dwFormats |= WAVE_FORMAT_4M08; WOutDev[i].caps.dwFormats |= WAVE_FORMAT_4S08; */ WOutDev[i].caps.dwFormats |= WAVE_FORMAT_4S16; WOutDev[i].caps.dwFormats |= WAVE_FORMAT_4M16; /* WOutDev[i].caps.dwFormats |= WAVE_FORMAT_2M08; WOutDev[i].caps.dwFormats |= WAVE_FORMAT_2S08; */ WOutDev[i].caps.dwFormats |= WAVE_FORMAT_2M16; WOutDev[i].caps.dwFormats |= WAVE_FORMAT_2S16; /* WOutDev[i].caps.dwFormats |= WAVE_FORMAT_1M08; WOutDev[i].caps.dwFormats |= WAVE_FORMAT_1S08;*/ WOutDev[i].caps.dwFormats |= WAVE_FORMAT_1M16; WOutDev[i].caps.dwFormats |= WAVE_FORMAT_1S16; } /* then do input device */ for (i = 0; i < MAX_WAVEINDRV; ++i) { /* TODO: we should initialize read stuff here */ memset(&WInDev[0].caps, 0, sizeof(WInDev[0].caps)); WInDev[i].caps.wMid = 0x00FF; WInDev[i].caps.wPid = 0x0001; strcpyW(WInDev[i].caps.szPname, ini_in); snprintf(WInDev[i].interface_name, sizeof(WInDev[i].interface_name), "winejack: %d", i); 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 */ /* error path for function pointer loading errors */ sym_not_found: WINE_MESSAGE( "Wine cannot find certain functions that it needs inside the jack" "library. To enable Wine to use the jack audio server please " "install libjack\n"); wine_dlclose(jackhandle, NULL, 0); jackhandle = NULL; return FALSE; } /*======================================================================* * Low level WAVE OUT implementation * *======================================================================*/ /************************************************************************** * wodNotifyClient [internal] */ static DWORD wodNotifyClient(WINE_WAVEOUT* wwo, WORD wMsg, DWORD dwParam1, DWORD dwParam2) { TRACE("wMsg = 0x%04x dwParm1 = %04lX dwParam2 = %04lX\n", wMsg, dwParam1, dwParam2); switch (wMsg) { case WOM_OPEN: case WOM_CLOSE: case WOM_DONE: if (wwo->wFlags != DCB_NULL && !DriverCallback(wwo->waveDesc.dwCallback, wwo->wFlags, (HDRVR)wwo->waveDesc.hWave, wMsg, wwo->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; } /************************************************************************** * wodHelper_BeginWaveHdr [internal] * * Makes the specified lpWaveHdr the currently playing wave header. * If the specified wave header is a begin loop and we're not already in * a loop, setup the loop. */ static void wodHelper_BeginWaveHdr(WINE_WAVEOUT* wwo, LPWAVEHDR lpWaveHdr) { EnterCriticalSection(&wwo->access_crst); wwo->lpPlayPtr = lpWaveHdr; if (!lpWaveHdr) { LeaveCriticalSection(&wwo->access_crst); return; } if (lpWaveHdr->dwFlags & WHDR_BEGINLOOP) { if (wwo->lpLoopPtr) { WARN("Already in a loop. Discarding loop on this header (%p)\n", lpWaveHdr); TRACE("Already in a loop. Discarding loop on this header (%p)\n", lpWaveHdr); } else { TRACE("Starting loop (%ldx) with %p\n", lpWaveHdr->dwLoops, lpWaveHdr); wwo->lpLoopPtr = lpWaveHdr; /* Windows does not touch WAVEHDR.dwLoops, * so we need to make an internal copy */ wwo->dwLoops = lpWaveHdr->dwLoops; } } wwo->dwPartialOffset = 0; LeaveCriticalSection(&wwo->access_crst); } /************************************************************************** * wodHelper_PlayPtrNext [internal] * * Advance the play pointer to the next waveheader, looping if required. */ static LPWAVEHDR wodHelper_PlayPtrNext(WINE_WAVEOUT* wwo) { LPWAVEHDR lpWaveHdr; EnterCriticalSection(&wwo->access_crst); lpWaveHdr = wwo->lpPlayPtr; wwo->dwPartialOffset = 0; if ((lpWaveHdr->dwFlags & WHDR_ENDLOOP) && wwo->lpLoopPtr) { /* We're at the end of a loop, loop if required */ if (--wwo->dwLoops > 0) { wwo->lpPlayPtr = wwo->lpLoopPtr; } else { /* Handle overlapping loops correctly */ if (wwo->lpLoopPtr != lpWaveHdr && (lpWaveHdr->dwFlags & WHDR_BEGINLOOP)) { FIXME("Correctly handled case ? (ending loop buffer also starts a new loop)\n"); /* shall we consider the END flag for the closing loop or for * the opening one or for both ??? * code assumes for closing loop only */ } else { lpWaveHdr = lpWaveHdr->lpNext; } wwo->lpLoopPtr = NULL; wodHelper_BeginWaveHdr(wwo, lpWaveHdr); } } else { /* We're not in a loop. Advance to the next wave header */ TRACE("not inside of a loop, advancing to next wave header\n"); wodHelper_BeginWaveHdr(wwo, lpWaveHdr = lpWaveHdr->lpNext); } LeaveCriticalSection(&wwo->access_crst); return lpWaveHdr; } /* if force is TRUE then notify the client that all the headers were completed */ static DWORD wodHelper_NotifyCompletions(WINE_WAVEOUT* wwo, BOOL force) { LPWAVEHDR lpWaveHdr; DWORD retval; TRACE("called\n"); EnterCriticalSection(&wwo->access_crst); /* Start from lpQueuePtr and keep notifying until: * - we hit an unwritten wavehdr * - we hit the beginning of a running loop * - we hit a wavehdr which hasn't finished playing */ while ((lpWaveHdr = wwo->lpQueuePtr) && (force || (lpWaveHdr != wwo->lpPlayPtr && lpWaveHdr != wwo->lpLoopPtr))) { wwo->lpQueuePtr = lpWaveHdr->lpNext; lpWaveHdr->dwFlags &= ~WHDR_INQUEUE; lpWaveHdr->dwFlags |= WHDR_DONE; 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; LeaveCriticalSection(&wwo->access_crst); return retval; } /************************************************************************** * wodHelper_Reset [internal] * * Resets current output stream. */ static void wodHelper_Reset(WINE_WAVEOUT* wwo, BOOL reset) { EnterCriticalSection(&wwo->access_crst); /* updates current notify list */ wodHelper_NotifyCompletions(wwo, FALSE); if (reset) { /* remove all wave headers and notify client that all headers were completed */ wodHelper_NotifyCompletions(wwo, TRUE); wwo->lpPlayPtr = wwo->lpQueuePtr = wwo->lpLoopPtr = NULL; wwo->state = WINE_WS_STOPPED; wwo->dwPlayedTotal = wwo->dwWrittenTotal = wwo->bytesInJack = 0; wwo->dwPartialOffset = 0; /* Clear partial wavehdr */ } else { if (wwo->lpLoopPtr) { /* complicated case, not handled yet (could imply modifying the loop counter) */ FIXME("Pausing while in loop isn't correctly handled yet, except strange results\n"); wwo->lpPlayPtr = wwo->lpLoopPtr; wwo->dwPartialOffset = 0; wwo->dwWrittenTotal = wwo->dwPlayedTotal; /* this is wrong !!! */ } else { LPWAVEHDR ptr; DWORD sz = wwo->dwPartialOffset; /* reset all the data as if we had written only up to lpPlayedTotal bytes */ /* compute the max size playable from lpQueuePtr */ for (ptr = wwo->lpQueuePtr; ptr != wwo->lpPlayPtr; ptr = ptr->lpNext) { sz += ptr->dwBufferLength; } /* because the reset lpPlayPtr will be lpQueuePtr */ if (wwo->dwWrittenTotal > wwo->dwPlayedTotal + sz) ERR("doh\n"); wwo->dwPartialOffset = sz - (wwo->dwWrittenTotal - wwo->dwPlayedTotal); wwo->dwWrittenTotal = wwo->dwPlayedTotal; wwo->lpPlayPtr = wwo->lpQueuePtr; } wwo->state = WINE_WS_PAUSED; } LeaveCriticalSection(&wwo->access_crst); } /************************************************************************** * wodGetDevCaps [internal] */ static DWORD wodGetDevCaps(WORD wDevID, LPWAVEOUTCAPSW lpCaps, DWORD dwSize) { TRACE("(%u, %p, %lu);\n", wDevID, lpCaps, dwSize); if (lpCaps == NULL) return MMSYSERR_NOTENABLED; if (wDevID >= MAX_WAVEOUTDRV) { TRACE("MAX_WAVOUTDRV reached !\n"); 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) { WINE_WAVEOUT* wwo; DWORD retval; TRACE("(%u, %p, %08lX);\n", wDevID, lpDesc, dwFlags); if (lpDesc == NULL) { WARN("Invalid Parameter !\n"); return MMSYSERR_INVALPARAM; } if (wDevID >= MAX_WAVEOUTDRV) { TRACE("MAX_WAVOUTDRV reached !\n"); return MMSYSERR_BADDEVICEID; } #if JACK_CLOSE_HACK if(WOutDev[wDevID].client && WOutDev[wDevID].in_use) #else if(WOutDev[wDevID].client) #endif { TRACE("device %d already allocated\n", wDevID); return MMSYSERR_ALLOCATED; } /* Only the PCM format is supported so far... * Also we only support 16 bit mode. */ if (lpDesc->lpFormat->wFormatTag != WAVE_FORMAT_PCM || lpDesc->lpFormat->nChannels == 0 || lpDesc->lpFormat->nSamplesPerSec == 0 || lpDesc->lpFormat->wBitsPerSample != 16) { WARN("Bad format: tag=%04X nChannels=%d nSamplesPerSec=%ld wBitsPerSample=%d !\n", lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels, lpDesc->lpFormat->nSamplesPerSec, lpDesc->lpFormat->wBitsPerSample); 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; } 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 */ TRACE("wBitsPerSample=%u, nAvgBytesPerSec=%lu, nSamplesPerSec=%lu, nChannels=%u nBlockAlign=%u!\n", wwo->format.wBitsPerSample, wwo->format.wf.nAvgBytesPerSec, wwo->format.wf.nSamplesPerSec, wwo->format.wf.nChannels, wwo->format.wf.nBlockAlign); /* make sure that we have the same sample rate in our audio stream */ /* as we do in the jack server */ if(wwo->format.wf.nSamplesPerSec != wwo->sample_rate) { TRACE("error: jack server sample rate is '%ld', wave sample rate is '%ld'\n", wwo->sample_rate, wwo->format.wf.nSamplesPerSec); #if JACK_CLOSE_HACK JACK_CloseWaveOutDevice(wwo, FALSE); /* close this device, don't force the client to close */ #else 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; } /* check for an invalid number of bits per sample */ if (wwo->format.wBitsPerSample == 0) { WARN("Resetting zeroed wBitsPerSample\n"); wwo->format.wBitsPerSample = 8 * (wwo->format.wf.nAvgBytesPerSec / wwo->format.wf.nSamplesPerSec) / wwo->format.wf.nChannels; } EnterCriticalSection(&wwo->access_crst); retval = wodNotifyClient(wwo, WOM_OPEN, 0L, 0L); LeaveCriticalSection(&wwo->access_crst); return retval; } /************************************************************************** * wodClose [internal] */ static DWORD wodClose(WORD wDevID) { DWORD ret = MMSYSERR_NOERROR; WINE_WAVEOUT* wwo; TRACE("(%u);\n", wDevID); if (wDevID >= MAX_WAVEOUTDRV || !WOutDev[wDevID].client) { WARN("bad device ID !\n"); return MMSYSERR_BADDEVICEID; } wwo = &WOutDev[wDevID]; if (wwo->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 (wwo->lpQueuePtr || wwo->lpPlayPtr) ERR("out of sync\n"); wwo->state = WINE_WS_CLOSED; /* mark the device as closed */ #if JACK_CLOSE_HACK JACK_CloseWaveOutDevice(wwo, FALSE); /* close the jack device, DO NOT force the client to close */ #else 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); } return ret; } /************************************************************************** * wodWrite [internal] * */ static DWORD wodWrite(WORD wDevID, LPWAVEHDR lpWaveHdr, DWORD dwSize) { LPWAVEHDR*wh; WINE_WAVEOUT *wwo; TRACE("(%u, %p, %08lX);\n", wDevID, lpWaveHdr, dwSize); /* first, do the sanity checks... */ if (wDevID >= MAX_WAVEOUTDRV || !WOutDev[wDevID].client) { WARN("bad dev ID !\n"); return MMSYSERR_BADDEVICEID; } wwo = &WOutDev[wDevID]; if (lpWaveHdr->lpData == NULL || !(lpWaveHdr->dwFlags & WHDR_PREPARED)) { TRACE("unprepared\n"); return WAVERR_UNPREPARED; } if (lpWaveHdr->dwFlags & WHDR_INQUEUE) { TRACE("still playing\n"); return WAVERR_STILLPLAYING; } lpWaveHdr->dwFlags &= ~WHDR_DONE; lpWaveHdr->dwFlags |= WHDR_INQUEUE; lpWaveHdr->lpNext = 0; EnterCriticalSection(&wwo->access_crst); /* insert buffer at the end of queue */ for (wh = &(wwo->lpQueuePtr); *wh; wh = &((*wh)->lpNext)); *wh = lpWaveHdr; if (!wwo->lpPlayPtr) wodHelper_BeginWaveHdr(wwo,lpWaveHdr); if (wwo->state == WINE_WS_STOPPED) wwo->state = WINE_WS_PLAYING; LeaveCriticalSection(&wwo->access_crst); return MMSYSERR_NOERROR; } /************************************************************************** * wodPrepare [internal] */ static DWORD wodPrepare(WORD wDevID, LPWAVEHDR lpWaveHdr, DWORD dwSize) { TRACE("(%u, %p, %08lX);\n", wDevID, lpWaveHdr, dwSize); if (wDevID >= MAX_WAVEOUTDRV) { WARN("bad device ID !\n"); return MMSYSERR_BADDEVICEID; } if (lpWaveHdr->dwFlags & WHDR_INQUEUE) return WAVERR_STILLPLAYING; lpWaveHdr->dwFlags |= WHDR_PREPARED; lpWaveHdr->dwFlags &= ~WHDR_DONE; return MMSYSERR_NOERROR; } /************************************************************************** * wodUnprepare [internal] */ static DWORD wodUnprepare(WORD wDevID, LPWAVEHDR lpWaveHdr, DWORD dwSize) { TRACE("(%u, %p, %08lX);\n", wDevID, lpWaveHdr, dwSize); if (wDevID >= MAX_WAVEOUTDRV) { WARN("bad device ID !\n"); return MMSYSERR_BADDEVICEID; } if (lpWaveHdr->dwFlags & WHDR_INQUEUE) return WAVERR_STILLPLAYING; lpWaveHdr->dwFlags &= ~WHDR_PREPARED; lpWaveHdr->dwFlags |= WHDR_DONE; return MMSYSERR_NOERROR; } /************************************************************************** * wodPause [internal] */ static DWORD wodPause(WORD wDevID) { TRACE("(%u);!\n", wDevID); if (wDevID >= MAX_WAVEOUTDRV || !WOutDev[wDevID].client) { WARN("bad device ID !\n"); return MMSYSERR_BADDEVICEID; } TRACE("[3-PAUSING]\n"); EnterCriticalSection(&(WOutDev[wDevID].access_crst)); wodHelper_Reset(&WOutDev[wDevID], FALSE); LeaveCriticalSection(&(WOutDev[wDevID].access_crst)); return MMSYSERR_NOERROR; } /************************************************************************** * wodRestart [internal] */ static DWORD wodRestart(WORD wDevID) { TRACE("(%u);\n", wDevID); if (wDevID >= MAX_WAVEOUTDRV || !WOutDev[wDevID].client) { WARN("bad device ID !\n"); return MMSYSERR_BADDEVICEID; } if (WOutDev[wDevID].state == WINE_WS_PAUSED) { EnterCriticalSection(&(WOutDev[wDevID].access_crst)); WOutDev[wDevID].state = WINE_WS_PLAYING; LeaveCriticalSection(&(WOutDev[wDevID].access_crst)); } return MMSYSERR_NOERROR; } /************************************************************************** * wodReset [internal] */ static DWORD wodReset(WORD wDevID) { TRACE("(%u);\n", wDevID); if (wDevID >= MAX_WAVEOUTDRV || !WOutDev[wDevID].client) { WARN("bad device ID !\n"); return MMSYSERR_BADDEVICEID; } EnterCriticalSection(&(WOutDev[wDevID].access_crst)); wodHelper_Reset(&WOutDev[wDevID], TRUE); LeaveCriticalSection(&(WOutDev[wDevID].access_crst)); return MMSYSERR_NOERROR; } /************************************************************************** * wodGetPosition [internal] */ static DWORD wodGetPosition(WORD wDevID, LPMMTIME lpTime, DWORD uSize) { DWORD val; WINE_WAVEOUT* wwo; DWORD elapsedMS; TRACE("(%u, %p, %lu);\n", wDevID, lpTime, uSize); if (wDevID >= MAX_WAVEOUTDRV || !WOutDev[wDevID].client) { WARN("bad device ID !\n"); return MMSYSERR_BADDEVICEID; } /* if null pointer to time structure return error */ if (lpTime == NULL) return MMSYSERR_INVALPARAM; wwo = &WOutDev[wDevID]; EnterCriticalSection(&(WOutDev[wDevID].access_crst)); val = wwo->dwPlayedTotal; elapsedMS = GetTickCount() - wwo->tickCountMS; LeaveCriticalSection(&(WOutDev[wDevID].access_crst)); /* account for the bytes played since the last JACK_Callback() */ val+=((elapsedMS * wwo->format.wf.nAvgBytesPerSec) / 1000); return bytes_to_mmtime(lpTime, val, &wwo->format); } /************************************************************************** * wodBreakLoop [internal] */ static DWORD wodBreakLoop(WORD wDevID) { TRACE("(%u);\n", wDevID); if (wDevID >= MAX_WAVEOUTDRV || !WOutDev[wDevID].client) { WARN("bad device ID !\n"); return MMSYSERR_BADDEVICEID; } EnterCriticalSection(&(WOutDev[wDevID].access_crst)); if (WOutDev[wDevID].state == WINE_WS_PLAYING && WOutDev[wDevID].lpLoopPtr != NULL) { /* ensure exit at end of current loop */ WOutDev[wDevID].dwLoops = 1; } LeaveCriticalSection(&(WOutDev[wDevID].access_crst)); return MMSYSERR_NOERROR; } /************************************************************************** * wodGetVolume [internal] */ static DWORD wodGetVolume(WORD wDevID, LPDWORD lpdwVol) { DWORD left, right; left = WOutDev[wDevID].volume_left; right = WOutDev[wDevID].volume_right; TRACE("(%u, %p);\n", wDevID, lpdwVol); *lpdwVol = ((left * 0xFFFFl) / 100) + (((right * 0xFFFFl) / 100) << 16); return MMSYSERR_NOERROR; } /************************************************************************** * wodSetVolume [internal] */ static DWORD wodSetVolume(WORD wDevID, DWORD dwParam) { DWORD left, right; left = (LOWORD(dwParam) * 100) / 0xFFFFl; right = (HIWORD(dwParam) * 100) / 0xFFFFl; TRACE("(%u, %08lX);\n", wDevID, dwParam); EnterCriticalSection(&(WOutDev[wDevID].access_crst)); WOutDev[wDevID].volume_left = left; WOutDev[wDevID].volume_right = right; LeaveCriticalSection(&(WOutDev[wDevID].access_crst)); return MMSYSERR_NOERROR; } /************************************************************************** * wodGetNumDevs [internal] */ static DWORD wodGetNumDevs(void) { return MAX_WAVEOUTDRV; } /************************************************************************** * wodDevInterfaceSize [internal] */ static DWORD wodDevInterfaceSize(UINT wDevID, LPDWORD dwParam1) { TRACE("(%u, %p)\n", wDevID, dwParam1); *dwParam1 = MultiByteToWideChar(CP_ACP, 0, WOutDev[wDevID].interface_name, -1, NULL, 0 ) * sizeof(WCHAR); return MMSYSERR_NOERROR; } /************************************************************************** * wodDevInterface [internal] */ static DWORD wodDevInterface(UINT wDevID, PWCHAR dwParam1, DWORD dwParam2) { if (dwParam2 >= MultiByteToWideChar(CP_ACP, 0, WOutDev[wDevID].interface_name, -1, NULL, 0 ) * sizeof(WCHAR)) { MultiByteToWideChar(CP_ACP, 0, WOutDev[wDevID].interface_name, -1, dwParam1, dwParam2 / sizeof(WCHAR)); return MMSYSERR_NOERROR; } return MMSYSERR_INVALPARAM; } /************************************************************************** * wodMessage (WINEJACK.7) */ DWORD WINAPI JACK_wodMessage(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: TRACE("DRVM_INIT\n"); return JACK_WaveInit(); case DRVM_EXIT: TRACE("DRVM_EXIT\n"); return JACK_WaveRelease(); case DRVM_ENABLE: /* FIXME: Pretend this is supported */ TRACE("DRVM_ENABLE\n"); return 0; case DRVM_DISABLE: /* FIXME: Pretend this is supported */ TRACE("DRVM_DISABLE\n"); return 0; case WODM_OPEN: return wodOpen(wDevID, (LPWAVEOPENDESC)dwParam1, dwParam2); case WODM_CLOSE: return wodClose(wDevID); case WODM_WRITE: return wodWrite(wDevID, (LPWAVEHDR)dwParam1, dwParam2); case WODM_PAUSE: return wodPause(wDevID); case WODM_GETPOS: return wodGetPosition(wDevID, (LPMMTIME)dwParam1, dwParam2); case WODM_BREAKLOOP: return wodBreakLoop(wDevID); case WODM_PREPARE: return wodPrepare(wDevID, (LPWAVEHDR)dwParam1, dwParam2); case WODM_UNPREPARE: return wodUnprepare(wDevID, (LPWAVEHDR)dwParam1, dwParam2); case WODM_GETDEVCAPS: return wodGetDevCaps(wDevID, (LPWAVEOUTCAPSW)dwParam1, dwParam2); case WODM_GETNUMDEVS: return wodGetNumDevs(); case WODM_GETPITCH: return MMSYSERR_NOTSUPPORTED; case WODM_SETPITCH: return MMSYSERR_NOTSUPPORTED; case WODM_GETPLAYBACKRATE: return MMSYSERR_NOTSUPPORTED; case WODM_SETPLAYBACKRATE: return MMSYSERR_NOTSUPPORTED; case WODM_GETVOLUME: return wodGetVolume(wDevID, (LPDWORD)dwParam1); case WODM_SETVOLUME: return wodSetVolume(wDevID, dwParam1); case WODM_RESTART: return wodRestart(wDevID); case WODM_RESET: return wodReset(wDevID); case DRV_QUERYDEVICEINTERFACESIZE: return wodDevInterfaceSize (wDevID, (LPDWORD)dwParam1); case DRV_QUERYDEVICEINTERFACE: return wodDevInterface (wDevID, (PWCHAR)dwParam1, dwParam2); case DRV_QUERYDSOUNDIFACE: return wodDsCreate(wDevID, (PIDSDRIVER*)dwParam1); case DRV_QUERYDSOUNDDESC: return wodDsDesc(wDevID, (PDSDRIVERDESC)dwParam1); default: FIXME("unknown message %d!\n", wMsg); } return MMSYSERR_NOTSUPPORTED; } /*======================================================================* * Low level DSOUND implementation * *======================================================================*/ typedef struct IDsDriverImpl IDsDriverImpl; typedef struct IDsDriverBufferImpl IDsDriverBufferImpl; struct IDsDriverImpl { /* IUnknown fields */ IDsDriverVtbl *lpVtbl; DWORD ref; /* IDsDriverImpl fields */ UINT wDevID; IDsDriverBufferImpl*primary; }; struct IDsDriverBufferImpl { /* IUnknown fields */ IDsDriverBufferVtbl *lpVtbl; DWORD ref; /* IDsDriverBufferImpl fields */ IDsDriverImpl* drv; DWORD buflen; }; static DWORD wodDsCreate(UINT wDevID, PIDSDRIVER* drv) { /* we can't perform memory mapping as we don't have a file stream interface with jack like we do with oss */ MESSAGE("This sound card's driver does not support direct access\n"); MESSAGE("The (slower) DirectSound HEL mode will be used instead.\n"); return MMSYSERR_NOTSUPPORTED; } static DWORD wodDsDesc(UINT wDevID, PDSDRIVERDESC desc) { memset(desc, 0, sizeof(*desc)); strcpy(desc->szDesc, "Wine jack DirectSound Driver"); strcpy(desc->szDrvname, "winejack.drv"); return MMSYSERR_NOERROR; } /*======================================================================* * Low level WAVE IN implementation * *======================================================================*/ /************************************************************************** * 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, LPWAVEINCAPSW 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; } /* Only the PCM format is supported so far... * Also we only support 16 bit mode. */ if (lpDesc->lpFormat->wFormatTag != WAVE_FORMAT_PCM || lpDesc->lpFormat->nChannels == 0 || lpDesc->lpFormat->nSamplesPerSec == 0 || lpDesc->lpFormat->wBitsPerSample!=16) { WARN("Bad format: tag=%04X nChannels=%d nSamplesPerSec=%ld wBitsPerSample=%d !\n", lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels, lpDesc->lpFormat->nSamplesPerSec, lpDesc->lpFormat->wBitsPerSample); 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\n"); wwi->format.wBitsPerSample = 8 * (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; } /************************************************************************** * widGetNumDevs [internal] */ static DWORD widGetNumDevs(void) { return MAX_WAVEINDRV; } /************************************************************************** * widDevInterfaceSize [internal] */ static DWORD widDevInterfaceSize(UINT wDevID, LPDWORD dwParam1) { TRACE("(%u, %p)\n", wDevID, dwParam1); *dwParam1 = MultiByteToWideChar(CP_ACP, 0, WInDev[wDevID].interface_name, -1, NULL, 0 ) * sizeof(WCHAR); return MMSYSERR_NOERROR; } /************************************************************************** * widDevInterface [internal] */ static DWORD widDevInterface(UINT wDevID, PWCHAR dwParam1, DWORD dwParam2) { if (dwParam2 >= MultiByteToWideChar(CP_ACP, 0, WInDev[wDevID].interface_name, -1, NULL, 0 ) * sizeof(WCHAR)) { MultiByteToWideChar(CP_ACP, 0, WInDev[wDevID].interface_name, -1, dwParam1, dwParam2 / sizeof(WCHAR)); return MMSYSERR_NOERROR; } return MMSYSERR_INVALPARAM; } /************************************************************************** * widMessage (WINEJACK.6) */ DWORD WINAPI JACK_widMessage(WORD wDevID, WORD 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, (LPWAVEINCAPSW)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); case DRV_QUERYDEVICEINTERFACESIZE: return widDevInterfaceSize (wDevID, (LPDWORD)dwParam1); case DRV_QUERYDEVICEINTERFACE: return widDevInterface (wDevID, (PWCHAR)dwParam1, dwParam2); default: FIXME("unknown message %d!\n", wMsg); } return MMSYSERR_NOTSUPPORTED; } #else /* !HAVE_JACK_JACK_H */ /************************************************************************** * widMessage (WINEJACK.6) */ 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) */ DWORD WINAPI JACK_wodMessage(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; } #endif /* HAVE_JACK_JACK_H */