mirror of
git://source.winehq.org/git/wine.git
synced 2024-11-05 18:01:34 +00:00
2423 lines
78 KiB
C
2423 lines
78 KiB
C
/*
|
|
* 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, 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 <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#ifdef HAVE_UNISTD_H
|
|
# include <unistd.h>
|
|
#endif
|
|
#include <fcntl.h>
|
|
#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 <jack/jack.h>
|
|
#endif
|
|
|
|
|
|
WINE_DEFAULT_DEBUG_CHANNEL(wave);
|
|
|
|
#ifdef SONAME_LIBJACK
|
|
|
|
#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_open" */
|
|
MAKE_FUNCPTR(jack_activate);
|
|
MAKE_FUNCPTR(jack_connect);
|
|
MAKE_FUNCPTR(jack_client_open);
|
|
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() | <thrd create> | PLAYING |
|
|
* | PLAYING | write() | HEADER | PLAYING |
|
|
* | (other) | write() | <error> | |
|
|
* | (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=%u nChannels=%u nAvgBytesPerSec=%u\n",
|
|
lpTime->wType, format->wBitsPerSample, format->wf.nSamplesPerSec,
|
|
format->wf.nChannels, format->wf.nAvgBytesPerSec);
|
|
TRACE("Position in bytes=%u\n", position);
|
|
|
|
switch (lpTime->wType) {
|
|
case TIME_SAMPLES:
|
|
lpTime->u.sample = position / (format->wBitsPerSample / 8 * format->wf.nChannels);
|
|
TRACE("TIME_SAMPLES=%u\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=%u\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=%u\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 */
|
|
static void volume_effect32(void *buffer, int length, int left, int right)
|
|
{
|
|
short *data = 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 */
|
|
static 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 */
|
|
static 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 */
|
|
static 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 */
|
|
static 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 */
|
|
static int JACK_callback_wwo (nframes_t nframes, void *arg)
|
|
{
|
|
sample_t* out_l;
|
|
sample_t* out_r;
|
|
WINE_WAVEOUT* wwo = 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 = fp_jack_port_get_buffer(wwo->out_port_l, nframes);
|
|
out_r = 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 * (unsigned)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=(%d) dwPartialOffset=(%d)\n",wwo->lpPlayPtr->dwBufferLength,wwo->dwPartialOffset);
|
|
TRACE("outputFramesAvailable == %d, jackFramesAvailable == %d\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 %d 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 max number
|
|
* of frames passed to JACK_callback
|
|
*/
|
|
static int JACK_bufsize_wwo (nframes_t nframes, void *arg)
|
|
{
|
|
WINE_WAVEOUT* wwo = 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 (%d).\n", wwo->buffer_size,buffer_required);
|
|
if(wwo->buffer_size < buffer_required)
|
|
{
|
|
TRACE("expanding buffer from wwo->buffer_size == %ld, to %d\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 max number
|
|
* of frames passed to JACK_callback
|
|
*/
|
|
static int JACK_bufsize_wwi (nframes_t nframes, void *arg)
|
|
{
|
|
TRACE("the maximum buffer size is now %u frames\n", nframes);
|
|
return 0;
|
|
}
|
|
|
|
/******************************************************************
|
|
* JACK_srate
|
|
*/
|
|
static 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 */
|
|
static void JACK_shutdown_wwo(void* arg)
|
|
{
|
|
WINE_WAVEOUT* wwo = 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 */
|
|
static void JACK_shutdown_wwi(void* arg)
|
|
{
|
|
WINE_WAVEIN* wwi = 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_open (client_name, JackUseExactName, NULL)) == 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_open (client_name, JackUseExactName, NULL)) == 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
|
|
}
|
|
|
|
static int WAVE_loadcount;
|
|
|
|
/******************************************************************
|
|
* JACK_WaveRelease
|
|
*
|
|
*
|
|
*/
|
|
static LONG JACK_WaveRelease(void)
|
|
{
|
|
int iDevice;
|
|
|
|
if (--WAVE_loadcount)
|
|
return 1;
|
|
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
|
|
*/
|
|
static LONG JACK_WaveInit(void)
|
|
{
|
|
int i;
|
|
CHAR szPname[MAXPNAMELEN];
|
|
|
|
TRACE("called\n");
|
|
if (WAVE_loadcount++)
|
|
return 1;
|
|
|
|
/* 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_open);
|
|
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 */
|
|
snprintf(szPname, sizeof(szPname), "JACK WaveOut %d", i);
|
|
MultiByteToWideChar(CP_ACP, 0, szPname, -1, WOutDev[i].caps.szPname, sizeof(WOutDev[i].caps.szPname)/sizeof(WCHAR));
|
|
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[i].caps, 0, sizeof(WInDev[i].caps));
|
|
|
|
WInDev[i].caps.wMid = 0x00FF;
|
|
WInDev[i].caps.wPid = 0x0001;
|
|
snprintf(szPname, sizeof(szPname), "JACK WaveIn %d", i);
|
|
MultiByteToWideChar(CP_ACP, 0, szPname, -1, WInDev[i].caps.szPname, sizeof(WInDev[i].caps.szPname)/sizeof(WCHAR));
|
|
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_PTR dwParam1,
|
|
DWORD_PTR dwParam2)
|
|
{
|
|
TRACE("wMsg = %04X dwParm1 = %08lX dwParam2 = %08lX\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 (%dx) 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=(%d)\n",
|
|
lpWaveHdr, wwo->lpPlayPtr, lpWaveHdr->dwFlags);
|
|
|
|
wodNotifyClient(wwo, WOM_DONE, (DWORD_PTR)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, expect 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, %u);\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%x), dwFormats=(0x%x)\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, %08X);\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=%d 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=%d !\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);
|
|
|
|
wwo->waveDesc = *lpDesc;
|
|
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=%u, nSamplesPerSec=%u, 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 '%d'\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, 0, 0);
|
|
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, 0, 0);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* wodWrite [internal]
|
|
*
|
|
*/
|
|
static DWORD wodWrite(WORD wDevID, LPWAVEHDR lpWaveHdr, DWORD dwSize)
|
|
{
|
|
LPWAVEHDR*wh;
|
|
WINE_WAVEOUT *wwo;
|
|
|
|
TRACE("(%u, %p, %08X);\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;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* 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, %u);\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, %08X);\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_PTR dwParam1, DWORD_PTR dwParam2)
|
|
{
|
|
TRACE("(%u, %04X, %08X, %08lX, %08lX);\n", wDevID, wMsg, dwUser, dwParam1, dwParam2);
|
|
|
|
switch (wMsg) {
|
|
case DRVM_INIT:
|
|
return JACK_WaveInit();
|
|
case DRVM_EXIT:
|
|
return JACK_WaveRelease();
|
|
case DRVM_ENABLE:
|
|
case DRVM_DISABLE:
|
|
/* FIXME: Pretend this is supported */
|
|
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 MMSYSERR_NOTSUPPORTED;
|
|
case WODM_UNPREPARE: return MMSYSERR_NOTSUPPORTED;
|
|
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 */
|
|
const IDsDriverVtbl *lpVtbl;
|
|
DWORD ref;
|
|
/* IDsDriverImpl fields */
|
|
UINT wDevID;
|
|
IDsDriverBufferImpl*primary;
|
|
};
|
|
|
|
struct IDsDriverBufferImpl
|
|
{
|
|
/* IUnknown fields */
|
|
const 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_PTR dwParam1,
|
|
DWORD_PTR dwParam2)
|
|
{
|
|
TRACE("wMsg = %04X dwParm1 = %08lX dwParam2 = %08lX\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 */
|
|
static int JACK_callback_wwi (nframes_t nframes, void *arg)
|
|
{
|
|
sample_t* in_l;
|
|
sample_t* in_r = 0;
|
|
WINE_WAVEIN* wwi = arg;
|
|
|
|
TRACE("wDevID: %u, nframes %u\n", wwi->wDevID, nframes);
|
|
|
|
if(!wwi->client)
|
|
ERR("client is closed, this is weird...\n");
|
|
|
|
in_l = fp_jack_port_get_buffer(wwi->in_port_l, nframes);
|
|
|
|
if (wwi->in_port_r)
|
|
in_r = 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 */
|
|
LeaveCriticalSection(&wwi->access_crst);
|
|
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=(%u) dwBytesRecorded=(%d)\n", lpWaveHdr->dwBufferLength, lpWaveHdr->dwBytesRecorded);
|
|
TRACE ("jackFramesLeft=(%u) waveHdrFramesLeft=(%u)\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=(%u) dwFlags=(0x%x)\n",lpWaveHdr->dwBytesRecorded,lpWaveHdr->dwFlags);
|
|
|
|
widNotifyClient(wwi, WIM_DATA, (DWORD_PTR)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_open (client_name, JackUseExactName, NULL)) == 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_open (client_name, JackUseExactName, NULL)) == 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, %u);\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, %08X);\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=%d 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=%d !\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);
|
|
|
|
wwi->waveDesc = *lpDesc;
|
|
memcpy(&wwi->format, lpDesc->lpFormat, sizeof(PCMWAVEFORMAT));
|
|
|
|
LeaveCriticalSection(&wwi->access_crst);
|
|
|
|
/* display the current wave format */
|
|
TRACE("wBitsPerSample=%u, nAvgBytesPerSec=%u, nSamplesPerSec=%u, 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 '%d'\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, 0, 0);
|
|
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, 0, 0);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* widAddBuffer [internal]
|
|
*/
|
|
static DWORD widAddBuffer(WORD wDevID, LPWAVEHDR lpWaveHdr, DWORD dwSize)
|
|
{
|
|
WINE_WAVEIN* wwi = &WInDev[wDevID];
|
|
|
|
TRACE("(%u, %p, %08X);\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;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* 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_PTR)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_PTR)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_PTR dwParam1, DWORD_PTR dwParam2)
|
|
{
|
|
TRACE("(%u, %04X, %08X, %08lX, %08lX);\n", wDevID, wMsg, dwUser, dwParam1, dwParam2);
|
|
|
|
switch (wMsg) {
|
|
case DRVM_INIT:
|
|
return JACK_WaveInit();
|
|
case DRVM_EXIT:
|
|
return JACK_WaveRelease();
|
|
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 MMSYSERR_NOTSUPPORTED;
|
|
case WIDM_UNPREPARE: return MMSYSERR_NOTSUPPORTED;
|
|
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 /* !SONAME_LIBJACK */
|
|
|
|
/**************************************************************************
|
|
* widMessage (WINEJACK.6)
|
|
*/
|
|
DWORD WINAPI JACK_widMessage(WORD wDevID, WORD wMsg, DWORD dwUser,
|
|
DWORD_PTR dwParam1, DWORD_PTR dwParam2)
|
|
{
|
|
FIXME("(%u, %04X, %08X, %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_PTR dwParam1, DWORD_PTR dwParam2)
|
|
{
|
|
FIXME("(%u, %04X, %08X, %08lX, %08lX):jack support not compiled into wine\n",
|
|
wDevID, wMsg, dwUser, dwParam1, dwParam2);
|
|
return MMSYSERR_NOTENABLED;
|
|
}
|
|
|
|
#endif /* SONAME_LIBJACK */
|