Commit 8258a518 authored by Andrew Eikum's avatar Andrew Eikum Committed by Alexandre Julliard

winealsa.drv: Remove wave, mixer, and dsound driver code.

parent 55542281
MODULE = winealsa.drv
IMPORTS = dxguid uuid winmm ole32 user32 advapi32
IMPORTS = uuid winmm ole32
EXTRALIBS = @ALSALIBS@
C_SRCS = \
alsa.c \
dscapture.c \
dsoutput.c \
midi.c \
mixer.c \
mmdevdrv.c \
wavein.c \
waveinit.c \
waveout.c
mmdevdrv.c
@MAKE_DLL_RULES@
/*
* Wine Driver for ALSA
*
* Copyright 2002 Eric Pouech
* Copyright 2006 Jaroslav Kysela
* Copyright 2007 Maarten Lankhorst
*
* This file has a few shared generic subroutines shared among the alsa
* implementation.
*
* 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
*/
#include "config.h"
#include <stdarg.h>
#include "windef.h"
#include "winbase.h"
#include "wingdi.h"
#include "winuser.h"
#include "winnls.h"
#include "winerror.h"
#include "mmddk.h"
#include "mmreg.h"
#include "dsound.h"
#include "dsdriver.h"
#include "ks.h"
#include "wine/library.h"
#include "wine/unicode.h"
#include "wine/debug.h"
#include "alsa.h"
#include "initguid.h"
#include "ksmedia.h"
WINE_DEFAULT_DEBUG_CHANNEL(alsa);
/* unless someone makes a wineserver kernel module, Unix pipes are faster than win32 events */
#define USE_PIPE_SYNC
#ifdef USE_PIPE_SYNC
#define INIT_OMR(omr) do { if (pipe(omr->msg_pipe) < 0) { omr->msg_pipe[0] = omr->msg_pipe[1] = -1; } } while (0)
#define CLOSE_OMR(omr) do { close(omr->msg_pipe[0]); close(omr->msg_pipe[1]); } while (0)
#define SIGNAL_OMR(omr) do { int x = 0; write((omr)->msg_pipe[1], &x, sizeof(x)); } while (0)
#define CLEAR_OMR(omr) do { int x = 0; read((omr)->msg_pipe[0], &x, sizeof(x)); } while (0)
#define RESET_OMR(omr) do { } while (0)
#define WAIT_OMR(omr, sleep) \
do { struct pollfd pfd; pfd.fd = (omr)->msg_pipe[0]; \
pfd.events = POLLIN; poll(&pfd, 1, sleep); } while (0)
#else
#define INIT_OMR(omr) do { omr->msg_event = CreateEventW(NULL, FALSE, FALSE, NULL); } while (0)
#define CLOSE_OMR(omr) do { CloseHandle(omr->msg_event); } while (0)
#define SIGNAL_OMR(omr) do { SetEvent((omr)->msg_event); } while (0)
#define CLEAR_OMR(omr) do { } while (0)
#define RESET_OMR(omr) do { ResetEvent((omr)->msg_event); } while (0)
#define WAIT_OMR(omr, sleep) \
do { WaitForSingleObject((omr)->msg_event, sleep); } while (0)
#endif
#define ALSA_RING_BUFFER_INCREMENT 64
/******************************************************************
* ALSA_InitRingMessage
*
* Initialize the ring of messages for passing between driver's caller and playback/record
* thread
*/
int ALSA_InitRingMessage(ALSA_MSG_RING* omr)
{
omr->msg_toget = 0;
omr->msg_tosave = 0;
INIT_OMR(omr);
omr->ring_buffer_size = ALSA_RING_BUFFER_INCREMENT;
omr->messages = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,omr->ring_buffer_size * sizeof(ALSA_MSG));
InitializeCriticalSection(&omr->msg_crst);
omr->msg_crst.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": ALSA_MSG_RING.msg_crst");
return 0;
}
/******************************************************************
* ALSA_DestroyRingMessage
*
*/
int ALSA_DestroyRingMessage(ALSA_MSG_RING* omr)
{
CLOSE_OMR(omr);
HeapFree(GetProcessHeap(),0,omr->messages);
omr->ring_buffer_size = 0;
omr->msg_crst.DebugInfo->Spare[0] = 0;
DeleteCriticalSection(&omr->msg_crst);
return 0;
}
/******************************************************************
* ALSA_ResetRingMessage
*
*/
void ALSA_ResetRingMessage(ALSA_MSG_RING* omr)
{
RESET_OMR(omr);
}
/******************************************************************
* ALSA_WaitRingMessage
*
*/
void ALSA_WaitRingMessage(ALSA_MSG_RING* omr, DWORD sleep)
{
WAIT_OMR(omr, sleep);
}
/******************************************************************
* ALSA_AddRingMessage
*
* Inserts a new message into the ring (should be called from DriverProc derived routines)
*/
int ALSA_AddRingMessage(ALSA_MSG_RING* omr, enum win_wm_message msg, DWORD_PTR param, BOOL wait)
{
HANDLE hEvent = NULL;
EnterCriticalSection(&omr->msg_crst);
if (omr->msg_toget == ((omr->msg_tosave + 1) % omr->ring_buffer_size))
{
int old_ring_buffer_size = omr->ring_buffer_size;
omr->ring_buffer_size += ALSA_RING_BUFFER_INCREMENT;
omr->messages = HeapReAlloc(GetProcessHeap(),0,omr->messages, omr->ring_buffer_size * sizeof(ALSA_MSG));
/* Now we need to rearrange the ring buffer so that the new
buffers just allocated are in between omr->msg_tosave and
omr->msg_toget.
*/
if (omr->msg_tosave < omr->msg_toget)
{
memmove(&(omr->messages[omr->msg_toget + ALSA_RING_BUFFER_INCREMENT]),
&(omr->messages[omr->msg_toget]),
sizeof(ALSA_MSG)*(old_ring_buffer_size - omr->msg_toget)
);
omr->msg_toget += ALSA_RING_BUFFER_INCREMENT;
}
}
if (wait)
{
hEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
if (!hEvent)
{
ERR("can't create event !?\n");
LeaveCriticalSection(&omr->msg_crst);
return 0;
}
if (omr->msg_toget != omr->msg_tosave && omr->messages[omr->msg_toget].msg != WINE_WM_HEADER)
FIXME("two fast messages in the queue!!!! toget = %d(%s), tosave=%d(%s)\n",
omr->msg_toget,ALSA_getCmdString(omr->messages[omr->msg_toget].msg),
omr->msg_tosave,ALSA_getCmdString(omr->messages[omr->msg_tosave].msg));
/* fast messages have to be added at the start of the queue */
omr->msg_toget = (omr->msg_toget + omr->ring_buffer_size - 1) % omr->ring_buffer_size;
omr->messages[omr->msg_toget].msg = msg;
omr->messages[omr->msg_toget].param = param;
omr->messages[omr->msg_toget].hEvent = hEvent;
}
else
{
omr->messages[omr->msg_tosave].msg = msg;
omr->messages[omr->msg_tosave].param = param;
omr->messages[omr->msg_tosave].hEvent = NULL;
omr->msg_tosave = (omr->msg_tosave + 1) % omr->ring_buffer_size;
}
LeaveCriticalSection(&omr->msg_crst);
/* signal a new message */
SIGNAL_OMR(omr);
if (wait)
{
/* wait for playback/record thread to have processed the message */
WaitForSingleObject(hEvent, INFINITE);
CloseHandle(hEvent);
}
return 1;
}
/******************************************************************
* ALSA_RetrieveRingMessage
*
* Get a message from the ring. Should be called by the playback/record thread.
*/
int ALSA_RetrieveRingMessage(ALSA_MSG_RING* omr, enum win_wm_message *msg,
DWORD_PTR *param, HANDLE *hEvent)
{
EnterCriticalSection(&omr->msg_crst);
if (omr->msg_toget == omr->msg_tosave) /* buffer empty ? */
{
LeaveCriticalSection(&omr->msg_crst);
return 0;
}
*msg = omr->messages[omr->msg_toget].msg;
omr->messages[omr->msg_toget].msg = 0;
*param = omr->messages[omr->msg_toget].param;
*hEvent = omr->messages[omr->msg_toget].hEvent;
omr->msg_toget = (omr->msg_toget + 1) % omr->ring_buffer_size;
CLEAR_OMR(omr);
LeaveCriticalSection(&omr->msg_crst);
return 1;
}
/*======================================================================*
* Utility functions *
*======================================================================*/
/* These strings used only for tracing */
const char * ALSA_getCmdString(enum win_wm_message msg)
{
#define MSG_TO_STR(x) case x: return #x
switch(msg) {
MSG_TO_STR(WINE_WM_PAUSING);
MSG_TO_STR(WINE_WM_RESTARTING);
MSG_TO_STR(WINE_WM_RESETTING);
MSG_TO_STR(WINE_WM_HEADER);
MSG_TO_STR(WINE_WM_UPDATE);
MSG_TO_STR(WINE_WM_BREAKLOOP);
MSG_TO_STR(WINE_WM_CLOSING);
MSG_TO_STR(WINE_WM_STARTING);
MSG_TO_STR(WINE_WM_STOPPING);
}
#undef MSG_TO_STR
return wine_dbg_sprintf("UNKNOWN(0x%08x)", msg);
}
const char * ALSA_getMessage(UINT msg)
{
#define MSG_TO_STR(x) case x: return #x
switch(msg) {
MSG_TO_STR(DRVM_INIT);
MSG_TO_STR(DRVM_EXIT);
MSG_TO_STR(DRVM_ENABLE);
MSG_TO_STR(DRVM_DISABLE);
MSG_TO_STR(WIDM_OPEN);
MSG_TO_STR(WIDM_CLOSE);
MSG_TO_STR(WIDM_ADDBUFFER);
MSG_TO_STR(WIDM_PREPARE);
MSG_TO_STR(WIDM_UNPREPARE);
MSG_TO_STR(WIDM_GETDEVCAPS);
MSG_TO_STR(WIDM_GETNUMDEVS);
MSG_TO_STR(WIDM_GETPOS);
MSG_TO_STR(WIDM_RESET);
MSG_TO_STR(WIDM_START);
MSG_TO_STR(WIDM_STOP);
MSG_TO_STR(WODM_OPEN);
MSG_TO_STR(WODM_CLOSE);
MSG_TO_STR(WODM_WRITE);
MSG_TO_STR(WODM_PAUSE);
MSG_TO_STR(WODM_GETPOS);
MSG_TO_STR(WODM_BREAKLOOP);
MSG_TO_STR(WODM_PREPARE);
MSG_TO_STR(WODM_UNPREPARE);
MSG_TO_STR(WODM_GETDEVCAPS);
MSG_TO_STR(WODM_GETNUMDEVS);
MSG_TO_STR(WODM_GETPITCH);
MSG_TO_STR(WODM_SETPITCH);
MSG_TO_STR(WODM_GETPLAYBACKRATE);
MSG_TO_STR(WODM_SETPLAYBACKRATE);
MSG_TO_STR(WODM_GETVOLUME);
MSG_TO_STR(WODM_SETVOLUME);
MSG_TO_STR(WODM_RESTART);
MSG_TO_STR(WODM_RESET);
MSG_TO_STR(DRV_QUERYDEVICEINTERFACESIZE);
MSG_TO_STR(DRV_QUERYDEVICEINTERFACE);
MSG_TO_STR(DRV_QUERYDSOUNDIFACE);
MSG_TO_STR(DRV_QUERYDSOUNDDESC);
}
#undef MSG_TO_STR
return wine_dbg_sprintf("UNKNOWN(0x%04x)", msg);
}
const char * ALSA_getFormat(WORD wFormatTag)
{
#define FMT_TO_STR(x) case x: return #x
switch(wFormatTag) {
FMT_TO_STR(WAVE_FORMAT_PCM);
FMT_TO_STR(WAVE_FORMAT_EXTENSIBLE);
FMT_TO_STR(WAVE_FORMAT_MULAW);
FMT_TO_STR(WAVE_FORMAT_ALAW);
FMT_TO_STR(WAVE_FORMAT_ADPCM);
}
#undef FMT_TO_STR
return wine_dbg_sprintf("UNKNOWN(0x%04x)", wFormatTag);
}
/* Allow 1% deviation for sample rates (some ES137x cards) */
BOOL ALSA_NearMatch(int rate1, int rate2)
{
return (((100 * (rate1 - rate2)) / rate1) == 0);
}
DWORD ALSA_bytes_to_mmtime(LPMMTIME lpTime, DWORD position, WAVEFORMATPCMEX* format)
{
TRACE("wType=%04X wBitsPerSample=%u nSamplesPerSec=%u nChannels=%u nAvgBytesPerSec=%u\n",
lpTime->wType, format->Format.wBitsPerSample, format->Format.nSamplesPerSec,
format->Format.nChannels, format->Format.nAvgBytesPerSec);
TRACE("Position in bytes=%u\n", position);
switch (lpTime->wType) {
case TIME_SAMPLES:
lpTime->u.sample = position / (format->Format.wBitsPerSample / 8 * format->Format.nChannels);
TRACE("TIME_SAMPLES=%u\n", lpTime->u.sample);
break;
case TIME_MS:
lpTime->u.ms = 1000.0 * position / (format->Format.wBitsPerSample / 8 * format->Format.nChannels * format->Format.nSamplesPerSec);
TRACE("TIME_MS=%u\n", lpTime->u.ms);
break;
case TIME_SMPTE:
lpTime->u.smpte.fps = 30;
position = position / (format->Format.wBitsPerSample / 8 * format->Format.nChannels);
position += (format->Format.nSamplesPerSec / lpTime->u.smpte.fps) - 1; /* round up */
lpTime->u.smpte.sec = position / format->Format.nSamplesPerSec;
position -= lpTime->u.smpte.sec * format->Format.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->Format.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;
}
void ALSA_copyFormat(LPWAVEFORMATEX wf1, LPWAVEFORMATPCMEX wf2)
{
unsigned int iLength;
ZeroMemory(wf2, sizeof(*wf2));
if (wf1->wFormatTag == WAVE_FORMAT_PCM)
iLength = sizeof(PCMWAVEFORMAT);
else if (wf1->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
iLength = sizeof(WAVEFORMATPCMEX);
else
iLength = sizeof(WAVEFORMATEX) + wf1->cbSize;
memcpy(wf2, wf1, iLength);
}
BOOL ALSA_supportedFormat(LPWAVEFORMATEX wf)
{
TRACE("(%p)\n",wf);
if (wf->nSamplesPerSec<DSBFREQUENCY_MIN||wf->nSamplesPerSec>DSBFREQUENCY_MAX)
return FALSE;
if (wf->wFormatTag == WAVE_FORMAT_PCM) {
if (wf->nChannels==1||wf->nChannels==2) {
if (wf->wBitsPerSample==8||wf->wBitsPerSample==16)
return TRUE;
}
} else if (wf->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
WAVEFORMATEXTENSIBLE * wfex = (WAVEFORMATEXTENSIBLE *)wf;
if (wf->cbSize == 22 &&
(IsEqualGUID(&wfex->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM) ||
IsEqualGUID(&wfex->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))) {
if (wf->nChannels>=1 && wf->nChannels<=6) {
if (wf->wBitsPerSample==wfex->Samples.wValidBitsPerSample) {
if (wf->wBitsPerSample==8||wf->wBitsPerSample==16||
wf->wBitsPerSample==24||wf->wBitsPerSample==32) {
return TRUE;
}
} else
WARN("wBitsPerSample != wValidBitsPerSample not supported yet\n");
}
} else
WARN("only KSDATAFORMAT_SUBTYPE_PCM and KSDATAFORMAT_SUBTYPE_IEEE_FLOAT "
"supported\n");
} else
WARN("only WAVE_FORMAT_PCM and WAVE_FORMAT_EXTENSIBLE supported\n");
return FALSE;
}
/*======================================================================*
* Low level WAVE implementation *
*======================================================================*/
/**************************************************************************
* ALSA_CheckSetVolume [internal]
*
* Helper function for Alsa volume queries. This tries to simplify
* the process of managing the volume. All parameters are optional
* (pass NULL to ignore or not use).
* Return values are MMSYSERR_NOERROR on success, or !0 on failure;
* error codes are normalized into the possible documented return
* values from waveOutGetVolume.
*/
int ALSA_CheckSetVolume(snd_hctl_t *hctl, int *out_left, int *out_right,
int *out_min, int *out_max, int *out_step,
int *new_left, int *new_right)
{
int rc = MMSYSERR_NOERROR;
int value_count = 0;
snd_hctl_elem_t * elem = NULL;
snd_ctl_elem_info_t * eleminfop = NULL;
snd_ctl_elem_value_t * elemvaluep = NULL;
snd_ctl_elem_id_t * elemidp = NULL;
const char *names[] = {"PCM Playback Volume", "Line Playback Volume", NULL};
const char **name;
#define EXIT_ON_ERROR(f,txt,exitcode) do \
{ \
int err; \
if ( (err = (f) ) < 0) \
{ \
ERR(txt " failed: %s\n", snd_strerror(err)); \
rc = exitcode; \
goto out; \
} \
} while(0)
if (! hctl)
return MMSYSERR_NOTSUPPORTED;
/* Allocate areas to return information about the volume */
EXIT_ON_ERROR(snd_ctl_elem_id_malloc(&elemidp), "snd_ctl_elem_id_malloc", MMSYSERR_NOMEM);
EXIT_ON_ERROR(snd_ctl_elem_value_malloc (&elemvaluep), "snd_ctl_elem_value_malloc", MMSYSERR_NOMEM);
EXIT_ON_ERROR(snd_ctl_elem_info_malloc (&eleminfop), "snd_ctl_elem_info_malloc", MMSYSERR_NOMEM);
snd_ctl_elem_id_clear(elemidp);
snd_ctl_elem_value_clear(elemvaluep);
snd_ctl_elem_info_clear(eleminfop);
/* Setup and find an element id that exactly matches the characteristic we want
** FIXME: It is probably short sighted to hard code and fixate on PCM Playback Volume */
for( name = names; *name; name++ )
{
snd_ctl_elem_id_set_name(elemidp, *name);
snd_ctl_elem_id_set_interface(elemidp, SND_CTL_ELEM_IFACE_MIXER);
elem = snd_hctl_find_elem(hctl, elemidp);
if (elem)
{
/* Read and return volume information */
EXIT_ON_ERROR(snd_hctl_elem_info(elem, eleminfop), "snd_hctl_elem_info", MMSYSERR_NOTSUPPORTED);
value_count = snd_ctl_elem_info_get_count(eleminfop);
if (out_min || out_max || out_step)
{
if (!snd_ctl_elem_info_is_readable(eleminfop))
{
ERR("snd_ctl_elem_info_is_readable returned false; cannot return info\n");
rc = MMSYSERR_NOTSUPPORTED;
goto out;
}
if (out_min)
*out_min = snd_ctl_elem_info_get_min(eleminfop);
if (out_max)
*out_max = snd_ctl_elem_info_get_max(eleminfop);
if (out_step)
*out_step = snd_ctl_elem_info_get_step(eleminfop);
}
if (out_left || out_right)
{
EXIT_ON_ERROR(snd_hctl_elem_read(elem, elemvaluep), "snd_hctl_elem_read", MMSYSERR_NOTSUPPORTED);
if (out_left)
*out_left = snd_ctl_elem_value_get_integer(elemvaluep, 0);
if (out_right)
{
if (value_count == 1)
*out_right = snd_ctl_elem_value_get_integer(elemvaluep, 0);
else if (value_count == 2)
*out_right = snd_ctl_elem_value_get_integer(elemvaluep, 1);
else
{
ERR("Unexpected value count %d from snd_ctl_elem_info_get_count while getting volume info\n", value_count);
rc = -1;
goto out;
}
}
}
/* Set the volume */
if (new_left || new_right)
{
EXIT_ON_ERROR(snd_hctl_elem_read(elem, elemvaluep), "snd_hctl_elem_read", MMSYSERR_NOTSUPPORTED);
if (new_left)
snd_ctl_elem_value_set_integer(elemvaluep, 0, *new_left);
if (new_right)
{
if (value_count == 1)
snd_ctl_elem_value_set_integer(elemvaluep, 0, *new_right);
else if (value_count == 2)
snd_ctl_elem_value_set_integer(elemvaluep, 1, *new_right);
else
{
ERR("Unexpected value count %d from snd_ctl_elem_info_get_count while setting volume info\n", value_count);
rc = -1;
goto out;
}
}
EXIT_ON_ERROR(snd_hctl_elem_write(elem, elemvaluep), "snd_hctl_elem_write", MMSYSERR_NOTSUPPORTED);
}
break;
}
}
if( !*name )
{
ERR("Could not find '{PCM,Line} Playback Volume' element\n");
rc = MMSYSERR_NOTSUPPORTED;
}
#undef EXIT_ON_ERROR
out:
if (elemvaluep)
snd_ctl_elem_value_free(elemvaluep);
if (eleminfop)
snd_ctl_elem_info_free(eleminfop);
if (elemidp)
snd_ctl_elem_id_free(elemidp);
return rc;
}
/**************************************************************************
* wine_snd_pcm_recover [internal]
*
* Code slightly modified from alsa-lib v1.0.23 snd_pcm_recover implementation.
* used to recover from XRUN errors (buffer underflow/overflow)
*/
int wine_snd_pcm_recover(snd_pcm_t *pcm, int err, int silent)
{
if (err > 0)
err = -err;
if (err == -EINTR) /* nothing to do, continue */
return 0;
if (err == -EPIPE) {
const char *s;
if (snd_pcm_stream(pcm) == SND_PCM_STREAM_PLAYBACK)
s = "underrun";
else
s = "overrun";
if (!silent)
ERR("%s occurred\n", s);
err = snd_pcm_prepare(pcm);
if (err < 0) {
ERR("cannot recover from %s, prepare failed: %s\n", s, snd_strerror(err));
return err;
}
return 0;
}
if (err == -ESTRPIPE) {
while ((err = snd_pcm_resume(pcm)) == -EAGAIN)
/* wait until suspend flag is released */
poll(NULL, 0, 1000);
if (err < 0) {
err = snd_pcm_prepare(pcm);
if (err < 0) {
ERR("cannot recover from suspend, prepare failed: %s\n", snd_strerror(err));
return err;
}
}
return 0;
}
return err;
}
/**************************************************************************
* ALSA_TraceParameters [internal]
*
* used to trace format changes, hw and sw parameters
*/
void ALSA_TraceParameters(snd_pcm_hw_params_t * hw_params, snd_pcm_sw_params_t * sw, int full)
{
int err;
snd_pcm_format_t format;
snd_pcm_access_t access;
#define X(x) ((x)? "true" : "false")
if (full)
TRACE("FLAGS: sampleres=%s overrng=%s pause=%s resume=%s syncstart=%s batch=%s block=%s double=%s "
"halfd=%s joint=%s\n",
X(snd_pcm_hw_params_can_mmap_sample_resolution(hw_params)),
X(snd_pcm_hw_params_can_overrange(hw_params)),
X(snd_pcm_hw_params_can_pause(hw_params)),
X(snd_pcm_hw_params_can_resume(hw_params)),
X(snd_pcm_hw_params_can_sync_start(hw_params)),
X(snd_pcm_hw_params_is_batch(hw_params)),
X(snd_pcm_hw_params_is_block_transfer(hw_params)),
X(snd_pcm_hw_params_is_double(hw_params)),
X(snd_pcm_hw_params_is_half_duplex(hw_params)),
X(snd_pcm_hw_params_is_joint_duplex(hw_params)));
#undef X
err = snd_pcm_hw_params_get_access(hw_params, &access);
if (err >= 0)
{
TRACE("access=%s\n", snd_pcm_access_name(access));
}
else
{
snd_pcm_access_mask_t * acmask;
acmask = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, snd_pcm_access_mask_sizeof());
snd_pcm_hw_params_get_access_mask(hw_params, acmask);
for ( access = SND_PCM_ACCESS_MMAP_INTERLEAVED; access <= SND_PCM_ACCESS_LAST; access++)
if (snd_pcm_access_mask_test(acmask, access))
TRACE("access=%s\n", snd_pcm_access_name(access));
HeapFree( GetProcessHeap(), 0, acmask );
}
err = snd_pcm_hw_params_get_format(hw_params, &format);
if (err >= 0)
{
TRACE("format=%s\n", snd_pcm_format_name(format));
}
else
{
snd_pcm_format_mask_t * fmask;
fmask = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, snd_pcm_format_mask_sizeof());
snd_pcm_hw_params_get_format_mask(hw_params, fmask);
for ( format = SND_PCM_FORMAT_S8; format <= SND_PCM_FORMAT_LAST ; format++)
if ( snd_pcm_format_mask_test(fmask, format) )
TRACE("format=%s\n", snd_pcm_format_name(format));
HeapFree( GetProcessHeap(), 0, fmask );
}
do {
int err=0;
unsigned int val=0;
err = snd_pcm_hw_params_get_channels(hw_params, &val);
if (err<0) {
unsigned int min = 0;
unsigned int max = 0;
err = snd_pcm_hw_params_get_channels_min(hw_params, &min),
err = snd_pcm_hw_params_get_channels_max(hw_params, &max);
TRACE("channels_min=%u, channels_min_max=%u\n", min, max);
} else {
TRACE("channels=%d\n", val);
}
} while(0);
do {
int err=0;
snd_pcm_uframes_t val=0;
err = snd_pcm_hw_params_get_buffer_size(hw_params, &val);
if (err<0) {
snd_pcm_uframes_t min = 0;
snd_pcm_uframes_t max = 0;
err = snd_pcm_hw_params_get_buffer_size_min(hw_params, &min),
err = snd_pcm_hw_params_get_buffer_size_max(hw_params, &max);
TRACE("buffer_size_min=%lu, buffer_size_min_max=%lu\n", min, max);
} else {
TRACE("buffer_size=%lu\n", val);
}
} while(0);
#define X(x) do { \
int err=0; \
int dir=0; \
unsigned int val=0; \
err = snd_pcm_hw_params_get_##x(hw_params,&val, &dir); \
if (err<0) { \
unsigned int min = 0; \
unsigned int max = 0; \
err = snd_pcm_hw_params_get_##x##_min(hw_params, &min, &dir); \
err = snd_pcm_hw_params_get_##x##_max(hw_params, &max, &dir); \
TRACE(#x "_min=%u " #x "_max=%u\n", min, max); \
} else \
TRACE(#x "=%d\n", val); \
} while(0)
X(rate);
X(buffer_time);
X(periods);
do {
int err=0;
int dir=0;
snd_pcm_uframes_t val=0;
err = snd_pcm_hw_params_get_period_size(hw_params, &val, &dir);
if (err<0) {
snd_pcm_uframes_t min = 0;
snd_pcm_uframes_t max = 0;
err = snd_pcm_hw_params_get_period_size_min(hw_params, &min, &dir),
err = snd_pcm_hw_params_get_period_size_max(hw_params, &max, &dir);
TRACE("period_size_min=%lu, period_size_min_max=%lu\n", min, max);
} else {
TRACE("period_size=%lu\n", val);
}
} while(0);
X(period_time);
#undef X
if (!sw)
return;
}
/**************************************************************************
* DriverProc (WINEALSA.@)
*/
LRESULT CALLBACK ALSA_DriverProc(DWORD_PTR dwDevID, HDRVR hDriv, UINT wMsg,
LPARAM dwParam1, LPARAM dwParam2)
{
/* EPP TRACE("(%08lX, %04X, %08lX, %08lX, %08lX)\n", */
/* EPP dwDevID, hDriv, wMsg, dwParam1, dwParam2); */
switch(wMsg) {
case DRV_LOAD:
case DRV_FREE:
case DRV_OPEN:
case DRV_CLOSE:
case DRV_ENABLE:
case DRV_DISABLE:
case DRV_QUERYCONFIGURE:
return 1;
case DRV_CONFIGURE: MessageBoxA(0, "ALSA MultiMedia Driver !", "ALSA Driver", MB_OK); return 1;
case DRV_INSTALL:
case DRV_REMOVE:
return DRV_SUCCESS;
default:
return 0;
}
}
/* Definition for ALSA drivers : wine multimedia system
*
* Copyright (C) 2002 Erich Pouech
* Copyright (C) 2002 Marco Pietrobono
* Copyright (C) 2003 Christian Costa
* Copyright (C) 2007 Maarten Lankhorst
*
* 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
*/
#ifndef __WINE_CONFIG_H
# error You must include config.h to use this header
#endif
#ifndef __ALSA_H
#define __ALSA_H
#ifdef interface
#undef interface
#endif
#define ALSA_PCM_NEW_HW_PARAMS_API
#define ALSA_PCM_NEW_SW_PARAMS_API
#ifdef HAVE_ALSA_ASOUNDLIB_H
#include <alsa/asoundlib.h>
#elif defined(HAVE_SYS_ASOUNDLIB_H)
#include <sys/asoundlib.h>
#endif
#ifdef HAVE_SYS_ERRNO_H
#include <sys/errno.h>
#endif
/* 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
/* events to be send to device */
enum win_wm_message {
WINE_WM_PAUSING = WM_USER + 1, WINE_WM_RESTARTING, WINE_WM_RESETTING, WINE_WM_HEADER,
WINE_WM_UPDATE, WINE_WM_BREAKLOOP, WINE_WM_CLOSING, WINE_WM_STARTING, WINE_WM_STOPPING
};
typedef struct {
enum win_wm_message msg; /* message identifier */
DWORD_PTR param; /* parameter for this message */
HANDLE hEvent; /* if message is synchronous, handle of event for synchro */
} ALSA_MSG;
/* implement an in-process message ring for better performance
* (compared to passing thru the server)
* this ring will be used by the input (resp output) record (resp playback) routine
*/
typedef struct {
ALSA_MSG * messages;
int ring_buffer_size;
int msg_tosave;
int msg_toget;
/* Either pipe or event is used, but that is defined in alsa.c,
* since this is a global header we define both here */
int msg_pipe[2];
HANDLE msg_event;
CRITICAL_SECTION msg_crst;
} ALSA_MSG_RING;
typedef struct {
volatile int state; /* one of the WINE_WS_ manifest constants */
WAVEOPENDESC waveDesc;
WORD wFlags;
WAVEFORMATPCMEX format;
char* pcmname; /* string name of alsa PCM device */
char* ctlname; /* string name of alsa control device */
char interface_name[MAXPNAMELEN * 2];
snd_pcm_t* pcm; /* handle to ALSA playback device */
snd_pcm_hw_params_t * hw_params;
DWORD dwBufferSize; /* size of whole ALSA buffer in bytes */
LPWAVEHDR lpQueuePtr; /* start of queued WAVEHDRs (waiting to be notified) */
LPWAVEHDR lpPlayPtr; /* start of not yet fully played buffers */
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 ALSA buffer since opening */
/* synchronization stuff */
HANDLE hStartUpEvent;
HANDLE hThread;
DWORD dwThreadID;
ALSA_MSG_RING msgRing;
/* DirectSound stuff */
DSDRIVERDESC ds_desc;
DSDRIVERCAPS ds_caps;
/* Waveout only fields */
WAVEOUTCAPSW outcaps;
snd_hctl_t * hctl; /* control handle for the playback volume */
snd_pcm_sframes_t (*write)(snd_pcm_t *, const void *, snd_pcm_uframes_t );
DWORD dwPartialOffset; /* Offset of not yet written bytes in lpPlayPtr */
/* Wavein only fields */
WAVEINCAPSW incaps;
DWORD dwSupport;
snd_pcm_sframes_t (*read)(snd_pcm_t *, void *, snd_pcm_uframes_t );
DWORD dwPeriodSize; /* size of OSS buffer period */
DWORD dwTotalRecorded;
} WINE_WAVEDEV;
/*----------------------------------------------------------------------------
** Global array of output and input devices, initialized via ALSA_WaveInit
*/
#define WAVEDEV_ALLOC_EXTENT_SIZE 10
/* wavein.c */
extern WINE_WAVEDEV *WInDev DECLSPEC_HIDDEN;
extern DWORD ALSA_WidNumMallocedDevs DECLSPEC_HIDDEN;
extern DWORD ALSA_WidNumDevs DECLSPEC_HIDDEN;
/* waveout.c */
extern WINE_WAVEDEV *WOutDev DECLSPEC_HIDDEN;
extern DWORD ALSA_WodNumMallocedDevs DECLSPEC_HIDDEN;
extern DWORD ALSA_WodNumDevs DECLSPEC_HIDDEN;
/* alsa.c */
int ALSA_InitRingMessage(ALSA_MSG_RING* omr) DECLSPEC_HIDDEN;
int ALSA_DestroyRingMessage(ALSA_MSG_RING* omr) DECLSPEC_HIDDEN;
void ALSA_ResetRingMessage(ALSA_MSG_RING* omr) DECLSPEC_HIDDEN;
void ALSA_WaitRingMessage(ALSA_MSG_RING* omr, DWORD sleep) DECLSPEC_HIDDEN;
int ALSA_AddRingMessage(ALSA_MSG_RING* omr, enum win_wm_message msg, DWORD_PTR param, BOOL wait) DECLSPEC_HIDDEN;
int ALSA_RetrieveRingMessage(ALSA_MSG_RING* omr, enum win_wm_message *msg, DWORD_PTR *param, HANDLE *hEvent) DECLSPEC_HIDDEN;
int ALSA_CheckSetVolume(snd_hctl_t *hctl, int *out_left, int *out_right, int *out_min, int *out_max, int *out_step, int *new_left, int *new_right) DECLSPEC_HIDDEN;
const char * ALSA_getCmdString(enum win_wm_message msg) DECLSPEC_HIDDEN;
const char * ALSA_getMessage(UINT msg) DECLSPEC_HIDDEN;
const char * ALSA_getFormat(WORD wFormatTag) DECLSPEC_HIDDEN;
BOOL ALSA_NearMatch(int rate1, int rate2) DECLSPEC_HIDDEN;
DWORD ALSA_bytes_to_mmtime(LPMMTIME lpTime, DWORD position, WAVEFORMATPCMEX* format) DECLSPEC_HIDDEN;
void ALSA_TraceParameters(snd_pcm_hw_params_t * hw_params, snd_pcm_sw_params_t * sw, int full) DECLSPEC_HIDDEN;
int wine_snd_pcm_recover(snd_pcm_t *pcm, int err, int silent) DECLSPEC_HIDDEN;
void ALSA_copyFormat(LPWAVEFORMATEX wf1, LPWAVEFORMATPCMEX wf2) DECLSPEC_HIDDEN;
BOOL ALSA_supportedFormat(LPWAVEFORMATEX wf) DECLSPEC_HIDDEN;
/* dscapture.c */
DWORD widDsCreate(UINT wDevID, PIDSCDRIVER* drv) DECLSPEC_HIDDEN;
DWORD widDsDesc(UINT wDevID, PDSDRIVERDESC desc) DECLSPEC_HIDDEN;
/* dsoutput.c */
DWORD wodDsCreate(UINT wDevID, PIDSDRIVER* drv) DECLSPEC_HIDDEN;
DWORD wodDsDesc(UINT wDevID, PDSDRIVERDESC desc) DECLSPEC_HIDDEN;
/* waveinit.c */
extern void ALSA_WaveInit(void) DECLSPEC_HIDDEN;
#endif /* __ALSA_H */
/*
* Sample Wine Driver for Advanced Linux Sound System (ALSA)
* Based on version <final> of the ALSA API
*
* Copyright 2007 - Maarten Lankhorst
*
* 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
*/
/*======================================================================*
* Low level dsound input implementation *
*======================================================================*/
#include "config.h"
#include "wine/port.h"
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#include <errno.h>
#include <limits.h>
#include <fcntl.h>
#ifdef HAVE_SYS_IOCTL_H
# include <sys/ioctl.h>
#endif
#ifdef HAVE_SYS_MMAN_H
# include <sys/mman.h>
#endif
#include "windef.h"
#include "winbase.h"
#include "wingdi.h"
#include "winerror.h"
#include "winuser.h"
#include "mmddk.h"
#include "mmreg.h"
#include "dsound.h"
#include "dsdriver.h"
#include "alsa.h"
#include "wine/library.h"
#include "wine/unicode.h"
#include "wine/debug.h"
/* Notify timer checks every 10 ms with a resolution of 2 ms */
#define DS_TIME_DEL 10
#define DS_TIME_RES 2
WINE_DEFAULT_DEBUG_CHANNEL(dsalsa);
typedef struct IDsCaptureDriverBufferImpl IDsCaptureDriverBufferImpl;
typedef struct IDsCaptureDriverImpl
{
const IDsCaptureDriverVtbl *lpVtbl;
LONG ref;
IDsCaptureDriverBufferImpl* capture_buffer;
UINT wDevID;
} IDsCaptureDriverImpl;
typedef struct IDsCaptureDriverNotifyImpl
{
const IDsDriverNotifyVtbl *lpVtbl;
LONG ref;
IDsCaptureDriverBufferImpl *buffer;
DSBPOSITIONNOTIFY *notifies;
DWORD nrofnotifies, playpos;
UINT timerID;
} IDsCaptureDriverNotifyImpl;
struct IDsCaptureDriverBufferImpl
{
const IDsCaptureDriverBufferVtbl *lpVtbl;
LONG ref;
IDsCaptureDriverImpl *drv;
IDsCaptureDriverNotifyImpl *notify;
CRITICAL_SECTION pcm_crst;
LPBYTE mmap_buffer, presented_buffer;
DWORD mmap_buflen_bytes, play_looping, mmap_ofs_bytes;
BOOL mmap;
/* Note: snd_pcm_frames_to_bytes(This->pcm, mmap_buflen_frames) != mmap_buflen_bytes */
/* The actual buffer may differ in size from the wanted buffer size */
snd_pcm_t *pcm;
snd_pcm_hw_params_t *hw_params;
snd_pcm_sw_params_t *sw_params;
snd_pcm_uframes_t mmap_buflen_frames, mmap_pos;
};
static void Capture_CheckNotify(IDsCaptureDriverNotifyImpl *This, DWORD from, DWORD len)
{
unsigned i;
for (i = 0; i < This->nrofnotifies; ++i) {
LPDSBPOSITIONNOTIFY event = This->notifies + i;
DWORD offset = event->dwOffset;
TRACE("checking %d, position %d, event = %p\n", i, offset, event->hEventNotify);
if (offset == DSBPN_OFFSETSTOP) {
if (!from && !len) {
SetEvent(event->hEventNotify);
TRACE("signalled event %p (%d)\n", event->hEventNotify, i);
return;
}
else return;
}
if (offset >= from && offset < (from + len))
{
TRACE("signalled event %p (%d)\n", event->hEventNotify, i);
SetEvent(event->hEventNotify);
}
}
}
static void CALLBACK Capture_Notify(UINT timerID, UINT msg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2)
{
IDsCaptureDriverBufferImpl *This = (IDsCaptureDriverBufferImpl *)dwUser;
DWORD last_playpos, playpos;
PIDSCDRIVERBUFFER iface = (PIDSCDRIVERBUFFER)This;
/* **** */
/* Don't deadlock since there is a critical section can be held by the timer api itself while running this code */
if (!TryEnterCriticalSection(&This->pcm_crst)) return;
IDsDriverBuffer_GetPosition(iface, &playpos, NULL);
last_playpos = This->notify->playpos;
This->notify->playpos = playpos;
if (snd_pcm_state(This->pcm) != SND_PCM_STATE_RUNNING || last_playpos == playpos || !This->notify->nrofnotifies || !This->notify->notifies)
goto done;
if (playpos < last_playpos)
{
Capture_CheckNotify(This->notify, last_playpos, This->mmap_buflen_bytes);
if (playpos)
Capture_CheckNotify(This->notify, 0, playpos);
}
else Capture_CheckNotify(This->notify, last_playpos, playpos - last_playpos);
done:
LeaveCriticalSection(&This->pcm_crst);
/* **** */
}
static HRESULT WINAPI IDsCaptureDriverNotifyImpl_QueryInterface(PIDSDRIVERNOTIFY iface, REFIID riid, LPVOID *ppobj)
{
IDsCaptureDriverNotifyImpl *This = (IDsCaptureDriverNotifyImpl *)iface;
TRACE("(%p,%s,%p)\n",This,debugstr_guid(riid),ppobj);
if ( IsEqualGUID(riid, &IID_IUnknown) ||
IsEqualGUID(riid, &IID_IDsDriverNotify) ) {
IDsDriverNotify_AddRef(iface);
*ppobj = This;
return DS_OK;
}
FIXME( "Unknown IID %s\n", debugstr_guid(riid));
*ppobj = 0;
return E_NOINTERFACE;
}
static ULONG WINAPI IDsCaptureDriverNotifyImpl_AddRef(PIDSDRIVERNOTIFY iface)
{
IDsCaptureDriverNotifyImpl *This = (IDsCaptureDriverNotifyImpl *)iface;
ULONG refCount = InterlockedIncrement(&This->ref);
TRACE("(%p) ref was %d\n", This, refCount - 1);
return refCount;
}
static ULONG WINAPI IDsCaptureDriverNotifyImpl_Release(PIDSDRIVERNOTIFY iface)
{
IDsCaptureDriverNotifyImpl *This = (IDsCaptureDriverNotifyImpl *)iface;
ULONG refCount = InterlockedDecrement(&This->ref);
TRACE("(%p) ref was %d\n", This, refCount + 1);
if (!refCount) {
This->buffer->notify = NULL;
if (This->timerID)
{
timeKillEvent(This->timerID);
timeEndPeriod(DS_TIME_RES);
}
HeapFree(GetProcessHeap(), 0, This->notifies);
HeapFree(GetProcessHeap(), 0, This);
TRACE("(%p) released\n", This);
}
return refCount;
}
static HRESULT WINAPI IDsCaptureDriverNotifyImpl_SetNotificationPositions(PIDSDRIVERNOTIFY iface, DWORD howmuch, LPCDSBPOSITIONNOTIFY notify)
{
DWORD len = howmuch * sizeof(DSBPOSITIONNOTIFY);
unsigned i;
LPVOID notifies;
IDsCaptureDriverNotifyImpl *This = (IDsCaptureDriverNotifyImpl *)iface;
TRACE("(%p,0x%08x,%p)\n",This,howmuch,notify);
if (!notify) {
WARN("invalid parameter\n");
return DSERR_INVALIDPARAM;
}
if (TRACE_ON(dsalsa))
for (i=0;i<howmuch; ++i)
TRACE("notify at %d to %p\n", notify[i].dwOffset, notify[i].hEventNotify);
/* **** */
EnterCriticalSection(&This->buffer->pcm_crst);
/* Make an internal copy of the caller-supplied array.
* Replace the existing copy if one is already present. */
if (This->notifies)
notifies = HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, This->notifies, len);
else
notifies = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, len);
if (!notifies)
{
LeaveCriticalSection(&This->buffer->pcm_crst);
/* **** */
return DSERR_OUTOFMEMORY;
}
This->notifies = notifies;
memcpy(This->notifies, notify, len);
This->nrofnotifies = howmuch;
IDsDriverBuffer_GetPosition((PIDSCDRIVERBUFFER)This->buffer, &This->playpos, NULL);
if (!This->timerID)
{
timeBeginPeriod(DS_TIME_RES);
This->timerID = timeSetEvent(DS_TIME_DEL, DS_TIME_RES, Capture_Notify, (DWORD_PTR)This->buffer, TIME_PERIODIC | TIME_KILL_SYNCHRONOUS);
}
LeaveCriticalSection(&This->buffer->pcm_crst);
/* **** */
return S_OK;
}
static const IDsDriverNotifyVtbl dscdnvt =
{
IDsCaptureDriverNotifyImpl_QueryInterface,
IDsCaptureDriverNotifyImpl_AddRef,
IDsCaptureDriverNotifyImpl_Release,
IDsCaptureDriverNotifyImpl_SetNotificationPositions,
};
#if 0
/** Convert the position an application sees into a position ALSA sees */
static snd_pcm_uframes_t fakepos_to_realpos(const IDsCaptureDriverBufferImpl* This, DWORD fakepos)
{
snd_pcm_uframes_t realpos;
if (fakepos < This->mmap_ofs_bytes)
realpos = This->mmap_buflen_bytes + fakepos - This->mmap_ofs_bytes;
else realpos = fakepos - This->mmap_ofs_bytes;
return snd_pcm_bytes_to_frames(This->pcm, realpos) % This->mmap_buflen_frames;
}
#endif
/** Convert the position ALSA sees into a position an application sees */
static DWORD realpos_to_fakepos(const IDsCaptureDriverBufferImpl* This, snd_pcm_uframes_t realpos)
{
DWORD realposb = snd_pcm_frames_to_bytes(This->pcm, realpos);
return (realposb + This->mmap_ofs_bytes) % This->mmap_buflen_bytes;
}
/** Raw copy data, with buffer wrap around */
static void CopyDataWrap(const IDsCaptureDriverBufferImpl* This, LPBYTE dest, DWORD fromwhere, DWORD copylen, DWORD buflen)
{
DWORD remainder = buflen - fromwhere;
if (remainder >= copylen)
{
CopyMemory(dest, This->mmap_buffer + fromwhere, copylen);
}
else
{
CopyMemory(dest, This->mmap_buffer + fromwhere, remainder);
copylen -= remainder;
CopyMemory(dest, This->mmap_buffer, copylen);
}
}
/** Copy data from the mmap buffer to backbuffer, taking into account all wraparounds that may occur */
static void CopyData(const IDsCaptureDriverBufferImpl* This, snd_pcm_uframes_t fromwhere, snd_pcm_uframes_t len)
{
DWORD dlen = snd_pcm_frames_to_bytes(This->pcm, len) % This->mmap_buflen_bytes;
/* Backbuffer */
DWORD ofs = realpos_to_fakepos(This, fromwhere);
DWORD remainder = This->mmap_buflen_bytes - ofs;
/* MMAP buffer */
DWORD realbuflen = snd_pcm_frames_to_bytes(This->pcm, This->mmap_buflen_frames);
DWORD realofs = snd_pcm_frames_to_bytes(This->pcm, fromwhere);
if (remainder >= dlen)
{
CopyDataWrap(This, This->presented_buffer + ofs, realofs, dlen, realbuflen);
}
else
{
CopyDataWrap(This, This->presented_buffer + ofs, realofs, remainder, realbuflen);
dlen -= remainder;
CopyDataWrap(This, This->presented_buffer, (realofs+remainder)%realbuflen, dlen, realbuflen);
}
}
/** Fill buffers, for starting and stopping
* Alsa won't start playing until everything is filled up
* This also updates mmap_pos
*
* Returns: Amount of periods in use so snd_pcm_avail_update
* doesn't have to be called up to 4x in GetPosition()
*/
static snd_pcm_uframes_t CommitAll(IDsCaptureDriverBufferImpl *This, DWORD forced)
{
const snd_pcm_channel_area_t *areas;
snd_pcm_uframes_t used;
const snd_pcm_uframes_t commitahead = This->mmap_buflen_frames;
used = This->mmap_buflen_frames - snd_pcm_avail_update(This->pcm);
TRACE("%p needs to commit to %lu, used: %lu\n", This, commitahead, used);
if (used < commitahead && (forced || This->play_looping))
{
snd_pcm_uframes_t done, putin = commitahead - used;
if (This->mmap)
{
snd_pcm_mmap_begin(This->pcm, &areas, &This->mmap_pos, &putin);
CopyData(This, This->mmap_pos, putin);
done = snd_pcm_mmap_commit(This->pcm, This->mmap_pos, putin);
This->mmap_pos += done;
used += done;
putin = commitahead - used;
if (This->mmap_pos == This->mmap_buflen_frames && (snd_pcm_sframes_t)putin > 0 && This->play_looping)
{
This->mmap_ofs_bytes += snd_pcm_frames_to_bytes(This->pcm, This->mmap_buflen_frames);
This->mmap_ofs_bytes %= This->mmap_buflen_bytes;
snd_pcm_mmap_begin(This->pcm, &areas, &This->mmap_pos, &putin);
CopyData(This, This->mmap_pos, putin);
done = snd_pcm_mmap_commit(This->pcm, This->mmap_pos, putin);
This->mmap_pos += done;
used += done;
}
}
else
{
DWORD pos;
snd_pcm_sframes_t ret;
snd_pcm_uframes_t cap = snd_pcm_bytes_to_frames(This->pcm, This->mmap_buflen_bytes);
pos = realpos_to_fakepos(This, This->mmap_pos);
if (This->mmap_pos + putin > cap)
putin = cap - This->mmap_pos;
ret = snd_pcm_readi(This->pcm, This->presented_buffer + pos, putin);
if (ret == -EPIPE)
{
WARN("Underrun occurred\n");
snd_pcm_prepare(This->pcm);
ret = snd_pcm_readi(This->pcm, This->presented_buffer + pos, putin);
snd_pcm_start(This->pcm);
}
if (ret < 0)
{
WARN("Committing data: %ld / %s (%ld)\n", ret, snd_strerror(ret), putin);
ret = 0;
}
This->mmap_pos += ret;
used += ret;
/* At this point mmap_pos may be >= This->mmap_pos this is harmless
* realpos_to_fakepos handles it well, and below it is truncated
*/
putin = commitahead - used;
if (putin > 0)
{
pos = realpos_to_fakepos(This, This->mmap_pos);
ret = snd_pcm_readi(This->pcm, This->presented_buffer + pos, putin);
if (ret > 0)
{
This->mmap_pos += ret;
used += ret;
}
}
}
}
if (This->mmap_pos >= This->mmap_buflen_frames)
{
This->mmap_ofs_bytes += snd_pcm_frames_to_bytes(This->pcm, This->mmap_buflen_frames);
This->mmap_ofs_bytes %= This->mmap_buflen_bytes;
This->mmap_pos -= This->mmap_buflen_frames;
}
return used;
}
static void CheckXRUN(IDsCaptureDriverBufferImpl* This)
{
snd_pcm_state_t state = snd_pcm_state(This->pcm);
int err;
if ( state == SND_PCM_STATE_XRUN )
{
err = snd_pcm_prepare(This->pcm);
CommitAll(This, FALSE);
snd_pcm_start(This->pcm);
WARN("xrun occurred\n");
if ( err < 0 )
ERR("recovery from xrun failed, prepare failed: %s\n", snd_strerror(err));
}
else if ( state == SND_PCM_STATE_SUSPENDED )
{
int err = snd_pcm_resume(This->pcm);
TRACE("recovery from suspension occurred\n");
if (err < 0 && err != -EAGAIN){
err = snd_pcm_prepare(This->pcm);
if (err < 0)
ERR("recovery from suspend failed, prepare failed: %s\n", snd_strerror(err));
}
}
else if ( state != SND_PCM_STATE_RUNNING)
{
WARN("Unhandled state: %d\n", state);
}
}
/**
* Allocate the memory-mapped buffer for direct sound, and set up the
* callback.
*/
static int CreateMMAP(IDsCaptureDriverBufferImpl* pdbi)
{
snd_pcm_t *pcm = pdbi->pcm;
snd_pcm_format_t format;
snd_pcm_uframes_t frames, ofs, avail, psize, boundary;
unsigned int channels, bits_per_sample, bits_per_frame;
int err, mmap_mode;
const snd_pcm_channel_area_t *areas;
snd_pcm_hw_params_t *hw_params = pdbi->hw_params;
snd_pcm_sw_params_t *sw_params = pdbi->sw_params;
mmap_mode = snd_pcm_type(pcm);
if (mmap_mode == SND_PCM_TYPE_HW)
TRACE("mmap'd buffer is a direct hardware buffer.\n");
else if (mmap_mode == SND_PCM_TYPE_DMIX)
TRACE("mmap'd buffer is an ALSA dmix buffer\n");
else
TRACE("mmap'd buffer is an ALSA type %d buffer\n", mmap_mode);
err = snd_pcm_hw_params_get_period_size(hw_params, &psize, NULL);
err = snd_pcm_hw_params_get_format(hw_params, &format);
err = snd_pcm_hw_params_get_buffer_size(hw_params, &frames);
err = snd_pcm_hw_params_get_channels(hw_params, &channels);
bits_per_sample = snd_pcm_format_physical_width(format);
bits_per_frame = bits_per_sample * channels;
if (TRACE_ON(dsalsa))
ALSA_TraceParameters(hw_params, NULL, FALSE);
TRACE("format=%s frames=%ld channels=%d bits_per_sample=%d bits_per_frame=%d\n",
snd_pcm_format_name(format), frames, channels, bits_per_sample, bits_per_frame);
pdbi->mmap_buflen_frames = frames;
snd_pcm_sw_params_current(pcm, sw_params);
snd_pcm_sw_params_set_start_threshold(pcm, sw_params, 0);
snd_pcm_sw_params_get_boundary(sw_params, &boundary);
snd_pcm_sw_params_set_stop_threshold(pcm, sw_params, boundary);
snd_pcm_sw_params_set_silence_threshold(pcm, sw_params, INT_MAX);
snd_pcm_sw_params_set_silence_size(pcm, sw_params, 0);
snd_pcm_sw_params_set_avail_min(pcm, sw_params, 0);
err = snd_pcm_sw_params(pcm, sw_params);
pdbi->mmap_ofs_bytes = 0;
if (!pdbi->mmap)
{
pdbi->mmap_buffer = NULL;
frames = snd_pcm_bytes_to_frames(pdbi->pcm, pdbi->mmap_buflen_bytes);
snd_pcm_format_set_silence(format, pdbi->presented_buffer, frames);
pdbi->mmap_pos = 0;
}
else
{
err = snd_pcm_mmap_begin(pcm, &areas, &ofs, &avail);
if ( err < 0 )
{
ERR("Can't map sound device for direct access: %s/%d\n", snd_strerror(err), err);
return DSERR_GENERIC;
}
snd_pcm_format_set_silence(format, areas->addr, pdbi->mmap_buflen_frames);
pdbi->mmap_pos = ofs + snd_pcm_mmap_commit(pcm, ofs, 0);
pdbi->mmap_buffer = areas->addr;
}
TRACE("created mmap buffer of %ld frames (%d bytes) at %p\n",
pdbi->mmap_buflen_frames, pdbi->mmap_buflen_bytes, pdbi->mmap_buffer);
return DS_OK;
}
static HRESULT WINAPI IDsCaptureDriverBufferImpl_QueryInterface(PIDSCDRIVERBUFFER iface, REFIID riid, LPVOID *ppobj)
{
IDsCaptureDriverBufferImpl *This = (IDsCaptureDriverBufferImpl *)iface;
if ( IsEqualGUID(riid, &IID_IUnknown) ||
IsEqualGUID(riid, &IID_IDsCaptureDriverBuffer) ) {
IDsCaptureDriverBuffer_AddRef(iface);
*ppobj = iface;
return DS_OK;
}
if ( IsEqualGUID( &IID_IDsDriverNotify, riid ) ) {
if (!This->notify)
{
This->notify = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(IDsCaptureDriverNotifyImpl));
if (!This->notify)
return DSERR_OUTOFMEMORY;
This->notify->lpVtbl = &dscdnvt;
This->notify->buffer = This;
/* Keep a lock on IDsDriverNotify for ourself, so it is destroyed when the buffer is */
IDsDriverNotify_AddRef((PIDSDRIVERNOTIFY)This->notify);
}
IDsDriverNotify_AddRef((PIDSDRIVERNOTIFY)This->notify);
*ppobj = This->notify;
return DS_OK;
}
if ( IsEqualGUID( &IID_IDsDriverPropertySet, riid ) ) {
FIXME("Unsupported interface IID_IDsDriverPropertySet\n");
return E_FAIL;
}
FIXME("(): Unknown interface %s\n", debugstr_guid(riid));
return DSERR_UNSUPPORTED;
}
static ULONG WINAPI IDsCaptureDriverBufferImpl_AddRef(PIDSCDRIVERBUFFER iface)
{
IDsCaptureDriverBufferImpl *This = (IDsCaptureDriverBufferImpl *)iface;
ULONG refCount = InterlockedIncrement(&This->ref);
TRACE("(%p)->(ref before=%u)\n",This, refCount - 1);
return refCount;
}
static ULONG WINAPI IDsCaptureDriverBufferImpl_Release(PIDSCDRIVERBUFFER iface)
{
IDsCaptureDriverBufferImpl *This = (IDsCaptureDriverBufferImpl *)iface;
ULONG refCount = InterlockedDecrement(&This->ref);
TRACE("(%p)->(ref before=%u)\n",This, refCount + 1);
if (refCount)
return refCount;
EnterCriticalSection(&This->pcm_crst);
if (This->notify)
IDsDriverNotify_Release((PIDSDRIVERNOTIFY)This->notify);
TRACE("mmap buffer %p destroyed\n", This->mmap_buffer);
This->drv->capture_buffer = NULL;
LeaveCriticalSection(&This->pcm_crst);
This->pcm_crst.DebugInfo->Spare[0] = 0;
DeleteCriticalSection(&This->pcm_crst);
snd_pcm_drop(This->pcm);
snd_pcm_close(This->pcm);
This->pcm = NULL;
HeapFree(GetProcessHeap(), 0, This->presented_buffer);
HeapFree(GetProcessHeap(), 0, This->sw_params);
HeapFree(GetProcessHeap(), 0, This->hw_params);
HeapFree(GetProcessHeap(), 0, This);
return 0;
}
static HRESULT WINAPI IDsCaptureDriverBufferImpl_Lock(PIDSCDRIVERBUFFER iface, LPVOID*ppvAudio1,LPDWORD pdwLen1,LPVOID*ppvAudio2,LPDWORD pdwLen2, DWORD dwWritePosition,DWORD dwWriteLen, DWORD dwFlags)
{
IDsCaptureDriverBufferImpl *This = (IDsCaptureDriverBufferImpl *)iface;
TRACE("(%p,%p,%p,%p,%p,%d,%d,0x%08x)\n", This, ppvAudio1, pdwLen1, ppvAudio2, pdwLen2, dwWritePosition, dwWriteLen, dwFlags);
if (ppvAudio1)
*ppvAudio1 = (LPBYTE)This->presented_buffer + dwWritePosition;
if (dwWritePosition + dwWriteLen <= This->mmap_buflen_bytes) {
if (pdwLen1)
*pdwLen1 = dwWriteLen;
if (ppvAudio2)
*ppvAudio2 = 0;
if (pdwLen2)
*pdwLen2 = 0;
} else {
if (pdwLen1)
*pdwLen1 = This->mmap_buflen_bytes - dwWritePosition;
if (ppvAudio2)
*ppvAudio2 = This->presented_buffer;
if (pdwLen2)
*pdwLen2 = dwWriteLen - (This->mmap_buflen_bytes - dwWritePosition);
}
return DS_OK;
}
static HRESULT WINAPI IDsCaptureDriverBufferImpl_Unlock(PIDSCDRIVERBUFFER iface, LPVOID pvAudio1,DWORD dwLen1, LPVOID pvAudio2,DWORD dwLen2)
{
IDsCaptureDriverBufferImpl *This = (IDsCaptureDriverBufferImpl *)iface;
TRACE("(%p,%p,%d,%p,%d)\n",This,pvAudio1,dwLen1,pvAudio2,dwLen2);
return DS_OK;
}
static HRESULT WINAPI IDsCaptureDriverBufferImpl_SetFormat(PIDSCDRIVERBUFFER iface, LPWAVEFORMATEX pwfx)
{
IDsCaptureDriverBufferImpl *This = (IDsCaptureDriverBufferImpl *)iface;
WINE_WAVEDEV *wwi = &WInDev[This->drv->wDevID];
snd_pcm_t *pcm = NULL;
snd_pcm_hw_params_t *hw_params = This->hw_params;
snd_pcm_format_t format = -1;
snd_pcm_uframes_t buffer_size;
DWORD rate = pwfx->nSamplesPerSec;
int err=0;
BOOL mmap;
TRACE("(%p, %p)\n", iface, pwfx);
switch (pwfx->wBitsPerSample)
{
case 8: format = SND_PCM_FORMAT_U8; break;
case 16: format = SND_PCM_FORMAT_S16_LE; break;
case 24: format = SND_PCM_FORMAT_S24_3LE; break;
case 32: format = SND_PCM_FORMAT_S32_LE; break;
default: FIXME("Unsupported bpp: %d\n", pwfx->wBitsPerSample); return DSERR_GENERIC;
}
/* **** */
EnterCriticalSection(&This->pcm_crst);
err = snd_pcm_open(&pcm, wwi->pcmname, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK);
if (err < 0)
{
if (errno != EBUSY || !This->pcm)
{
/* **** */
LeaveCriticalSection(&This->pcm_crst);
WARN("Cannot open sound device: %s\n", snd_strerror(err));
return DSERR_GENERIC;
}
snd_pcm_drop(This->pcm);
snd_pcm_close(This->pcm);
This->pcm = NULL;
err = snd_pcm_open(&pcm, wwi->pcmname, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK);
if (err < 0)
{
/* **** */
LeaveCriticalSection(&This->pcm_crst);
WARN("Cannot open sound device: %s\n", snd_strerror(err));
return DSERR_BUFFERLOST;
}
}
/* Set some defaults */
snd_pcm_hw_params_any(pcm, hw_params);
err = snd_pcm_hw_params_set_channels(pcm, hw_params, pwfx->nChannels);
if (err < 0) { WARN("Could not set channels to %d\n", pwfx->nChannels); goto err; }
err = snd_pcm_hw_params_set_format(pcm, hw_params, format);
if (err < 0) { WARN("Could not set format to %d bpp\n", pwfx->wBitsPerSample); goto err; }
err = snd_pcm_hw_params_set_rate_near(pcm, hw_params, &rate, NULL);
if (err < 0) { rate = pwfx->nSamplesPerSec; WARN("Could not set rate\n"); goto err; }
if (!ALSA_NearMatch(rate, pwfx->nSamplesPerSec))
{
WARN("Could not set sound rate to %d, but instead to %d\n", pwfx->nSamplesPerSec, rate);
pwfx->nSamplesPerSec = rate;
pwfx->nAvgBytesPerSec = rate * pwfx->nBlockAlign;
/* Let DirectSound detect this */
}
snd_pcm_hw_params_set_periods_integer(pcm, hw_params);
buffer_size = This->mmap_buflen_bytes / pwfx->nBlockAlign;
snd_pcm_hw_params_set_buffer_size_near(pcm, hw_params, &buffer_size);
buffer_size = 5000;
snd_pcm_hw_params_set_period_time_near(pcm, hw_params, (unsigned int*)&buffer_size, NULL);
err = snd_pcm_hw_params_set_access (pcm, hw_params, SND_PCM_ACCESS_MMAP_INTERLEAVED);
if (err < 0)
{
err = snd_pcm_hw_params_set_access (pcm, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
if (err < 0) { WARN("Could not set access\n"); goto err; }
mmap = 0;
}
else
mmap = 1;
err = snd_pcm_hw_params(pcm, hw_params);
if (err < 0) { WARN("Could not set hw parameters\n"); goto err; }
if (This->pcm)
{
snd_pcm_drop(This->pcm);
snd_pcm_close(This->pcm);
}
This->pcm = pcm;
This->mmap = mmap;
snd_pcm_prepare(This->pcm);
CreateMMAP(This);
/* **** */
LeaveCriticalSection(&This->pcm_crst);
return S_OK;
err:
if (err < 0)
WARN("Failed to apply changes: %s\n", snd_strerror(err));
if (!This->pcm)
This->pcm = pcm;
else
snd_pcm_close(pcm);
if (This->pcm)
snd_pcm_hw_params_current(This->pcm, This->hw_params);
/* **** */
LeaveCriticalSection(&This->pcm_crst);
return DSERR_BADFORMAT;
}
static HRESULT WINAPI IDsCaptureDriverBufferImpl_GetPosition(PIDSCDRIVERBUFFER iface, LPDWORD lpdwCappos, LPDWORD lpdwReadpos)
{
IDsCaptureDriverBufferImpl *This = (IDsCaptureDriverBufferImpl *)iface;
snd_pcm_uframes_t hw_pptr, hw_wptr;
EnterCriticalSection(&This->pcm_crst);
if (!This->pcm)
{
FIXME("Bad pointer for pcm: %p\n", This->pcm);
LeaveCriticalSection(&This->pcm_crst);
return DSERR_GENERIC;
}
if (snd_pcm_state(This->pcm) != SND_PCM_STATE_RUNNING)
{
CheckXRUN(This);
hw_pptr = This->mmap_pos;
}
else
{
/* FIXME: Unused at the moment */
snd_pcm_uframes_t used = CommitAll(This, FALSE);
if (This->mmap_pos > used)
hw_pptr = This->mmap_pos - used;
else
hw_pptr = This->mmap_buflen_frames - used + This->mmap_pos;
}
hw_wptr = This->mmap_pos;
if (lpdwCappos)
*lpdwCappos = realpos_to_fakepos(This, hw_pptr);
if (lpdwReadpos)
*lpdwReadpos = realpos_to_fakepos(This, hw_wptr);
LeaveCriticalSection(&This->pcm_crst);
TRACE("hw_pptr=%u, hw_wptr=%u playpos=%u(%p), writepos=%u(%p)\n", (unsigned int)hw_pptr, (unsigned int)hw_wptr, realpos_to_fakepos(This, hw_pptr), lpdwCappos, realpos_to_fakepos(This, hw_wptr), lpdwReadpos);
return DS_OK;
}
static HRESULT WINAPI IDsCaptureDriverBufferImpl_GetStatus(PIDSCDRIVERBUFFER iface, LPDWORD lpdwStatus)
{
IDsCaptureDriverBufferImpl *This = (IDsCaptureDriverBufferImpl *)iface;
snd_pcm_state_t state;
TRACE("(%p,%p)\n",iface,lpdwStatus);
state = snd_pcm_state(This->pcm);
switch (state)
{
case SND_PCM_STATE_XRUN:
case SND_PCM_STATE_SUSPENDED:
case SND_PCM_STATE_RUNNING:
*lpdwStatus = DSCBSTATUS_CAPTURING | (This->play_looping ? DSCBSTATUS_LOOPING : 0);
break;
default:
*lpdwStatus = 0;
break;
}
TRACE("State: %d, flags: 0x%08x\n", state, *lpdwStatus);
return DS_OK;
}
static HRESULT WINAPI IDsCaptureDriverBufferImpl_Start(PIDSCDRIVERBUFFER iface, DWORD dwFlags)
{
IDsCaptureDriverBufferImpl *This = (IDsCaptureDriverBufferImpl *)iface;
TRACE("(%p,%x)\n",iface,dwFlags);
/* **** */
EnterCriticalSection(&This->pcm_crst);
snd_pcm_start(This->pcm);
This->play_looping = !!(dwFlags & DSCBSTART_LOOPING);
if (!This->play_looping)
/* Not well supported because of the difference in ALSA size and DSOUND's notion of size
* what it does right now is fill the buffer once.. ALSA size */
FIXME("Non-looping buffers are not properly supported!\n");
CommitAll(This, TRUE);
if (This->notify && This->notify->nrofnotifies && This->notify->notifies)
{
DWORD playpos = realpos_to_fakepos(This, This->mmap_pos);
if (playpos)
Capture_CheckNotify(This->notify, 0, playpos);
This->notify->playpos = playpos;
}
/* **** */
LeaveCriticalSection(&This->pcm_crst);
return DS_OK;
}
static HRESULT WINAPI IDsCaptureDriverBufferImpl_Stop(PIDSCDRIVERBUFFER iface)
{
IDsCaptureDriverBufferImpl *This = (IDsCaptureDriverBufferImpl *)iface;
TRACE("(%p)\n",iface);
/* **** */
EnterCriticalSection(&This->pcm_crst);
This->play_looping = FALSE;
snd_pcm_drop(This->pcm);
snd_pcm_prepare(This->pcm);
if (This->notify && This->notify->notifies && This->notify->nrofnotifies)
Capture_CheckNotify(This->notify, 0, 0);
/* **** */
LeaveCriticalSection(&This->pcm_crst);
return DS_OK;
}
static const IDsCaptureDriverBufferVtbl dsdbvt =
{
IDsCaptureDriverBufferImpl_QueryInterface,
IDsCaptureDriverBufferImpl_AddRef,
IDsCaptureDriverBufferImpl_Release,
IDsCaptureDriverBufferImpl_Lock,
IDsCaptureDriverBufferImpl_Unlock,
IDsCaptureDriverBufferImpl_SetFormat,
IDsCaptureDriverBufferImpl_GetPosition,
IDsCaptureDriverBufferImpl_GetStatus,
IDsCaptureDriverBufferImpl_Start,
IDsCaptureDriverBufferImpl_Stop
};
static HRESULT WINAPI IDsCaptureDriverImpl_QueryInterface(PIDSCDRIVER iface, REFIID riid, LPVOID *ppobj)
{
/* IDsCaptureDriverImpl *This = (IDsCaptureDriverImpl *)iface; */
FIXME("(%p): stub!\n",iface);
return DSERR_UNSUPPORTED;
}
static ULONG WINAPI IDsCaptureDriverImpl_AddRef(PIDSCDRIVER iface)
{
IDsCaptureDriverImpl *This = (IDsCaptureDriverImpl *)iface;
ULONG refCount = InterlockedIncrement(&This->ref);
TRACE("(%p)->(ref before=%u)\n",This, refCount - 1);
return refCount;
}
static ULONG WINAPI IDsCaptureDriverImpl_Release(PIDSCDRIVER iface)
{
IDsCaptureDriverImpl *This = (IDsCaptureDriverImpl *)iface;
ULONG refCount = InterlockedDecrement(&This->ref);
TRACE("(%p)->(ref before=%u)\n",This, refCount + 1);
if (refCount)
return refCount;
HeapFree(GetProcessHeap(), 0, This);
return 0;
}
static HRESULT WINAPI IDsCaptureDriverImpl_GetDriverDesc(PIDSCDRIVER iface, PDSDRIVERDESC pDesc)
{
IDsCaptureDriverImpl *This = (IDsCaptureDriverImpl *)iface;
TRACE("(%p,%p)\n",iface,pDesc);
*pDesc = WInDev[This->wDevID].ds_desc;
pDesc->dwFlags = 0;
pDesc->dnDevNode = WInDev[This->wDevID].waveDesc.dnDevNode;
pDesc->wVxdId = 0;
pDesc->wReserved = 0;
pDesc->ulDeviceNum = This->wDevID;
pDesc->dwHeapType = DSDHEAP_NOHEAP;
pDesc->pvDirectDrawHeap = NULL;
pDesc->dwMemStartAddress = 0xDEAD0000;
pDesc->dwMemEndAddress = 0xDEAF0000;
pDesc->dwMemAllocExtra = 0;
pDesc->pvReserved1 = NULL;
pDesc->pvReserved2 = NULL;
return DS_OK;
}
static HRESULT WINAPI IDsCaptureDriverImpl_Open(PIDSCDRIVER iface)
{
HRESULT hr = S_OK;
IDsCaptureDriverImpl *This = (IDsCaptureDriverImpl *)iface;
int err=0;
snd_pcm_t *pcm = NULL;
snd_pcm_hw_params_t *hw_params;
/* While this is not really needed, it is a good idea to do this,
* to see if sound can be initialized */
hw_params = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, snd_pcm_hw_params_sizeof());
if (!hw_params)
{
hr = DSERR_OUTOFMEMORY;
WARN("--> %08x\n", hr);
return hr;
}
err = snd_pcm_open(&pcm, WInDev[This->wDevID].pcmname, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK);
if (err < 0) goto err;
err = snd_pcm_hw_params_any(pcm, hw_params);
if (err < 0) goto err;
err = snd_pcm_hw_params_set_access (pcm, hw_params, SND_PCM_ACCESS_MMAP_INTERLEAVED);
if (err < 0)
{
err = snd_pcm_hw_params_set_access (pcm, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
if (err < 0) goto err;
}
TRACE("Success\n");
snd_pcm_close(pcm);
HeapFree(GetProcessHeap(), 0, hw_params);
return hr;
err:
hr = DSERR_GENERIC;
WARN("Failed to open device: %s\n", snd_strerror(err));
if (pcm)
snd_pcm_close(pcm);
HeapFree(GetProcessHeap(), 0, hw_params);
WARN("--> %08x\n", hr);
return hr;
}
static HRESULT WINAPI IDsCaptureDriverImpl_Close(PIDSCDRIVER iface)
{
IDsCaptureDriverImpl *This = (IDsCaptureDriverImpl *)iface;
TRACE("(%p) stub, harmless\n",This);
return DS_OK;
}
static HRESULT WINAPI IDsCaptureDriverImpl_GetCaps(PIDSCDRIVER iface, PDSCDRIVERCAPS pCaps)
{
IDsCaptureDriverImpl *This = (IDsCaptureDriverImpl *)iface;
WINE_WAVEDEV *wwi = &WInDev[This->wDevID];
TRACE("(%p,%p)\n",iface,pCaps);
pCaps->dwSize = sizeof(DSCDRIVERCAPS);
pCaps->dwFlags = wwi->ds_caps.dwFlags;
pCaps->dwFormats = wwi->incaps.dwFormats;
pCaps->dwChannels = wwi->incaps.wChannels;
return DS_OK;
}
static HRESULT WINAPI IDsCaptureDriverImpl_CreateCaptureBuffer(PIDSCDRIVER iface,
LPWAVEFORMATEX pwfx,
DWORD dwFlags, DWORD dwCardAddress,
LPDWORD pdwcbBufferSize,
LPBYTE *ppbBuffer,
LPVOID *ppvObj)
{
IDsCaptureDriverImpl *This = (IDsCaptureDriverImpl *)iface;
IDsCaptureDriverBufferImpl** ippdsdb = (IDsCaptureDriverBufferImpl**)ppvObj;
HRESULT err;
TRACE("(%p,%p,%x,%x)\n",iface,pwfx,dwFlags,dwCardAddress);
if (This->capture_buffer)
return DSERR_ALLOCATED;
This->capture_buffer = *ippdsdb = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(IDsCaptureDriverBufferImpl));
if (*ippdsdb == NULL)
return DSERR_OUTOFMEMORY;
(*ippdsdb)->hw_params = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, snd_pcm_hw_params_sizeof());
(*ippdsdb)->sw_params = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, snd_pcm_sw_params_sizeof());
(*ippdsdb)->presented_buffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, *pdwcbBufferSize);
if (!(*ippdsdb)->hw_params || !(*ippdsdb)->sw_params || !(*ippdsdb)->presented_buffer)
{
HeapFree(GetProcessHeap(), 0, (*ippdsdb)->sw_params);
HeapFree(GetProcessHeap(), 0, (*ippdsdb)->hw_params);
HeapFree(GetProcessHeap(), 0, (*ippdsdb)->presented_buffer);
return DSERR_OUTOFMEMORY;
}
(*ippdsdb)->lpVtbl = &dsdbvt;
(*ippdsdb)->ref = 1;
(*ippdsdb)->drv = This;
(*ippdsdb)->mmap_buflen_bytes = *pdwcbBufferSize;
InitializeCriticalSection(&(*ippdsdb)->pcm_crst);
(*ippdsdb)->pcm_crst.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": ALSA_DSCAPTURE.pcm_crst");
/* SetFormat initialises pcm */
err = IDsDriverBuffer_SetFormat((IDsDriverBuffer*)*ppvObj, pwfx);
if (FAILED(err))
{
WARN("Error occurred: %08x\n", err);
goto err;
}
*ppbBuffer = (*ippdsdb)->presented_buffer;
/* buffer is ready to go */
TRACE("buffer created at %p\n", *ippdsdb);
return err;
err:
HeapFree(GetProcessHeap(), 0, (*ippdsdb)->presented_buffer);
HeapFree(GetProcessHeap(), 0, (*ippdsdb)->sw_params);
HeapFree(GetProcessHeap(), 0, (*ippdsdb)->hw_params);
HeapFree(GetProcessHeap(), 0, *ippdsdb);
*ippdsdb = NULL;
return err;
}
static const IDsCaptureDriverVtbl dscdvt =
{
IDsCaptureDriverImpl_QueryInterface,
IDsCaptureDriverImpl_AddRef,
IDsCaptureDriverImpl_Release,
IDsCaptureDriverImpl_GetDriverDesc,
IDsCaptureDriverImpl_Open,
IDsCaptureDriverImpl_Close,
IDsCaptureDriverImpl_GetCaps,
IDsCaptureDriverImpl_CreateCaptureBuffer
};
/**************************************************************************
* widDsCreate [internal]
*/
DWORD widDsCreate(UINT wDevID, PIDSCDRIVER* drv)
{
IDsCaptureDriverImpl** idrv = (IDsCaptureDriverImpl**)drv;
TRACE("(%d,%p)\n",wDevID,drv);
*idrv = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(IDsCaptureDriverImpl));
if (!*idrv)
return MMSYSERR_NOMEM;
(*idrv)->lpVtbl = &dscdvt;
(*idrv)->ref = 1;
(*idrv)->wDevID = wDevID;
return MMSYSERR_NOERROR;
}
/**************************************************************************
* widDsDesc [internal]
*/
DWORD widDsDesc(UINT wDevID, PDSDRIVERDESC desc)
{
*desc = WInDev[wDevID].ds_desc;
return MMSYSERR_NOERROR;
}
/*
* Sample Wine Driver for Advanced Linux Sound System (ALSA)
* Based on version <final> of the ALSA API
*
* Copyright 2002 Eric Pouech
* 2002 Marco Pietrobono
* 2003 Christian Costa : WaveIn support
* 2006-2007 Maarten Lankhorst
*
* 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
*/
/*======================================================================*
* Low level dsound output implementation *
*======================================================================*/
#include "config.h"
#include "wine/port.h"
#include <stdlib.h>
#include <assert.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#include <errno.h>
#include <limits.h>
#include <fcntl.h>
#ifdef HAVE_SYS_IOCTL_H
# include <sys/ioctl.h>
#endif
#ifdef HAVE_SYS_MMAN_H
# include <sys/mman.h>
#endif
#include "windef.h"
#include "winbase.h"
#include "wingdi.h"
#include "winerror.h"
#include "winuser.h"
#include "mmddk.h"
#include "mmreg.h"
#include "dsound.h"
#include "dsdriver.h"
#include "alsa.h"
#include "wine/library.h"
#include "wine/unicode.h"
#include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(dsalsa);
typedef struct IDsDriverImpl IDsDriverImpl;
typedef struct IDsDriverBufferImpl IDsDriverBufferImpl;
struct IDsDriverImpl
{
/* IUnknown fields */
IDsDriver IDsDriver_iface;
LONG ref;
/* IDsDriverImpl fields */
IDsDriverBufferImpl* primary;
UINT wDevID;
};
struct IDsDriverBufferImpl
{
IDsDriverBuffer IDsDriverBuffer_iface;
LONG ref;
IDsDriverImpl* drv;
CRITICAL_SECTION pcm_crst;
BYTE *mmap_buffer;
DWORD mmap_buflen_bytes;
BOOL mmap;
snd_pcm_t *pcm;
snd_pcm_hw_params_t *hw_params;
snd_pcm_sw_params_t *sw_params;
snd_pcm_uframes_t mmap_buflen_frames, mmap_pos, mmap_commitahead;
};
static inline IDsDriverImpl *impl_from_IDsDriver(IDsDriver *iface)
{
return CONTAINING_RECORD(iface, IDsDriverImpl, IDsDriver_iface);
}
static inline IDsDriverBufferImpl *impl_from_IDsDriverBuffer(IDsDriverBuffer *iface)
{
return CONTAINING_RECORD(iface, IDsDriverBufferImpl, IDsDriverBuffer_iface);
}
/** Fill buffers, for starting and stopping
* Alsa won't start playing until everything is filled up
* This also updates mmap_pos
*
* Returns: Amount of periods in use so snd_pcm_avail_update
* doesn't have to be called up to 4x in GetPosition()
*/
static snd_pcm_uframes_t CommitAll(IDsDriverBufferImpl *This)
{
const snd_pcm_channel_area_t *areas;
snd_pcm_sframes_t used;
const snd_pcm_uframes_t commitahead = This->mmap_commitahead;
used = This->mmap_buflen_frames - snd_pcm_avail_update(This->pcm);
if (used < 0) used = 0;
TRACE("%p needs to commit to %lu, used: %ld\n", This, commitahead, used);
if (used < commitahead)
{
snd_pcm_sframes_t done;
snd_pcm_uframes_t putin = commitahead - used;
if (This->mmap)
{
snd_pcm_mmap_begin(This->pcm, &areas, &This->mmap_pos, &putin);
done = snd_pcm_mmap_commit(This->pcm, This->mmap_pos, putin);
}
else
{
if (putin + This->mmap_pos > This->mmap_buflen_frames)
putin = This->mmap_buflen_frames - This->mmap_pos;
done = snd_pcm_writei(This->pcm, This->mmap_buffer + snd_pcm_frames_to_bytes(This->pcm, This->mmap_pos), putin);
if (done < putin) WARN("Short write %ld/%ld\n", putin, done);
}
if (done < 0) done = 0;
This->mmap_pos += done;
used += done;
putin = commitahead - used;
if (This->mmap_pos == This->mmap_buflen_frames && (snd_pcm_sframes_t)putin > 0)
{
if (This->mmap)
{
snd_pcm_mmap_begin(This->pcm, &areas, &This->mmap_pos, &putin);
done = snd_pcm_mmap_commit(This->pcm, This->mmap_pos, putin);
This->mmap_pos += done;
}
else
{
done = snd_pcm_writei(This->pcm, This->mmap_buffer, putin);
if (done < putin) WARN("Short write %ld/%ld\n", putin, done);
if (done < 0) done = 0;
This->mmap_pos = done;
}
used += done;
}
}
if (This->mmap_pos == This->mmap_buflen_frames)
This->mmap_pos = 0;
return used;
}
static void CheckXRUN(IDsDriverBufferImpl* This)
{
snd_pcm_state_t state = snd_pcm_state(This->pcm);
int err;
if ( state == SND_PCM_STATE_XRUN )
{
err = snd_pcm_prepare(This->pcm);
CommitAll(This);
snd_pcm_start(This->pcm);
WARN("xrun occurred\n");
if ( err < 0 )
ERR("recovery from xrun failed, prepare failed: %s\n", snd_strerror(err));
}
else if ( state == SND_PCM_STATE_SUSPENDED )
{
int err = snd_pcm_resume(This->pcm);
TRACE("recovery from suspension occurred\n");
if (err < 0 && err != -EAGAIN){
err = snd_pcm_prepare(This->pcm);
if (err < 0)
ERR("recovery from suspend failed, prepare failed: %s\n", snd_strerror(err));
}
} else if ( state != SND_PCM_STATE_RUNNING ) {
FIXME("Unhandled state: %d\n", state);
}
}
/**
* Allocate the memory-mapped buffer for direct sound, and set up the
* callback.
*/
static int DSDB_CreateMMAP(IDsDriverBufferImpl* pdbi)
{
snd_pcm_t *pcm = pdbi->pcm;
snd_pcm_format_t format;
snd_pcm_uframes_t frames, ofs, avail, psize, boundary;
unsigned int channels, bits_per_sample, bits_per_frame;
int err, mmap_mode;
const snd_pcm_channel_area_t *areas;
snd_pcm_hw_params_t *hw_params = pdbi->hw_params;
snd_pcm_sw_params_t *sw_params = pdbi->sw_params;
void *buf;
mmap_mode = snd_pcm_type(pcm);
if (mmap_mode == SND_PCM_TYPE_HW)
TRACE("mmap'd buffer is a direct hardware buffer.\n");
else if (mmap_mode == SND_PCM_TYPE_DMIX)
TRACE("mmap'd buffer is an ALSA dmix buffer\n");
else
TRACE("mmap'd buffer is an ALSA type %d buffer\n", mmap_mode);
err = snd_pcm_hw_params_get_period_size(hw_params, &psize, NULL);
err = snd_pcm_hw_params_get_format(hw_params, &format);
err = snd_pcm_hw_params_get_buffer_size(hw_params, &frames);
err = snd_pcm_hw_params_get_channels(hw_params, &channels);
bits_per_sample = snd_pcm_format_physical_width(format);
bits_per_frame = bits_per_sample * channels;
if (TRACE_ON(dsalsa))
ALSA_TraceParameters(hw_params, NULL, FALSE);
TRACE("format=%s frames=%ld channels=%d bits_per_sample=%d bits_per_frame=%d\n",
snd_pcm_format_name(format), frames, channels, bits_per_sample, bits_per_frame);
pdbi->mmap_buflen_frames = frames;
pdbi->mmap_buflen_bytes = snd_pcm_frames_to_bytes( pcm, frames );
snd_pcm_sw_params_current(pcm, sw_params);
snd_pcm_sw_params_set_start_threshold(pcm, sw_params, 0);
snd_pcm_sw_params_get_boundary(sw_params, &boundary);
snd_pcm_sw_params_set_stop_threshold(pcm, sw_params, boundary);
snd_pcm_sw_params_set_silence_threshold(pcm, sw_params, boundary);
snd_pcm_sw_params_set_silence_size(pcm, sw_params, 0);
snd_pcm_sw_params_set_avail_min(pcm, sw_params, 0);
err = snd_pcm_sw_params(pcm, sw_params);
avail = snd_pcm_avail_update(pcm);
if ((snd_pcm_sframes_t)avail < 0)
{
ERR("No buffer is available: %s.\n", snd_strerror(avail));
return DSERR_GENERIC;
}
if (!pdbi->mmap)
{
buf = pdbi->mmap_buffer = HeapAlloc(GetProcessHeap(), 0, pdbi->mmap_buflen_bytes);
if (!buf)
return DSERR_OUTOFMEMORY;
snd_pcm_format_set_silence(format, buf, pdbi->mmap_buflen_frames);
pdbi->mmap_pos = 0;
}
else
{
err = snd_pcm_mmap_begin(pcm, &areas, &ofs, &avail);
if ( err < 0 )
{
ERR("Can't map sound device for direct access: %s/%d\n", snd_strerror(err), err);
return DSERR_GENERIC;
}
snd_pcm_format_set_silence(format, areas->addr, pdbi->mmap_buflen_frames);
pdbi->mmap_pos = ofs + snd_pcm_mmap_commit(pcm, ofs, 0);
pdbi->mmap_buffer = areas->addr;
}
TRACE("created mmap buffer of %ld frames (%d bytes) at %p\n",
frames, pdbi->mmap_buflen_bytes, pdbi->mmap_buffer);
return DS_OK;
}
static HRESULT WINAPI IDsDriverBufferImpl_QueryInterface(PIDSDRIVERBUFFER iface, REFIID riid, LPVOID *ppobj)
{
/* IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; */
FIXME("(): stub!\n");
return DSERR_UNSUPPORTED;
}
static ULONG WINAPI IDsDriverBufferImpl_AddRef(PIDSDRIVERBUFFER iface)
{
IDsDriverBufferImpl *This = impl_from_IDsDriverBuffer(iface);
ULONG refCount = InterlockedIncrement(&This->ref);
TRACE("(%p)->(ref before=%u)\n",This, refCount - 1);
return refCount;
}
static ULONG WINAPI IDsDriverBufferImpl_Release(PIDSDRIVERBUFFER iface)
{
IDsDriverBufferImpl *This = impl_from_IDsDriverBuffer(iface);
ULONG refCount = InterlockedDecrement(&This->ref);
TRACE("(%p)->(ref before=%u)\n",This, refCount + 1);
if (refCount)
return refCount;
TRACE("mmap buffer %p destroyed\n", This->mmap_buffer);
if (This == This->drv->primary)
This->drv->primary = NULL;
This->pcm_crst.DebugInfo->Spare[0] = 0;
DeleteCriticalSection(&This->pcm_crst);
snd_pcm_drop(This->pcm);
snd_pcm_close(This->pcm);
This->pcm = NULL;
HeapFree(GetProcessHeap(), 0, This->sw_params);
HeapFree(GetProcessHeap(), 0, This->hw_params);
if (!This->mmap)
HeapFree(GetProcessHeap(), 0, This->mmap_buffer);
HeapFree(GetProcessHeap(), 0, This);
return 0;
}
static HRESULT WINAPI IDsDriverBufferImpl_Lock(PIDSDRIVERBUFFER iface,
LPVOID*ppvAudio1,LPDWORD pdwLen1,
LPVOID*ppvAudio2,LPDWORD pdwLen2,
DWORD dwWritePosition,DWORD dwWriteLen,
DWORD dwFlags)
{
IDsDriverBufferImpl *This = impl_from_IDsDriverBuffer(iface);
snd_pcm_uframes_t writepos;
TRACE("%d bytes from %d\n", dwWriteLen, dwWritePosition);
/* **** */
EnterCriticalSection(&This->pcm_crst);
if (dwFlags & DSBLOCK_ENTIREBUFFER)
dwWriteLen = This->mmap_buflen_bytes;
if (dwWriteLen > This->mmap_buflen_bytes || dwWritePosition >= This->mmap_buflen_bytes)
{
/* **** */
LeaveCriticalSection(&This->pcm_crst);
return DSERR_INVALIDPARAM;
}
if (ppvAudio2) *ppvAudio2 = NULL;
if (pdwLen2) *pdwLen2 = 0;
*ppvAudio1 = This->mmap_buffer + dwWritePosition;
*pdwLen1 = dwWriteLen;
if (dwWritePosition+dwWriteLen > This->mmap_buflen_bytes)
{
DWORD remainder = This->mmap_buflen_bytes - dwWritePosition;
*pdwLen1 = remainder;
if (ppvAudio2 && pdwLen2)
{
*ppvAudio2 = This->mmap_buffer;
*pdwLen2 = dwWriteLen - remainder;
}
else dwWriteLen = remainder;
}
writepos = snd_pcm_bytes_to_frames(This->pcm, dwWritePosition);
if (writepos == This->mmap_pos)
{
const snd_pcm_channel_area_t *areas;
snd_pcm_uframes_t writelen = snd_pcm_bytes_to_frames(This->pcm, dwWriteLen), putin = writelen;
TRACE("Hit mmap_pos, locking data!\n");
if (This->mmap)
snd_pcm_mmap_begin(This->pcm, &areas, &This->mmap_pos, &putin);
}
else
WARN("mmap_pos (%lu) != writepos (%lu) not locking data!\n", This->mmap_pos, writepos);
LeaveCriticalSection(&This->pcm_crst);
/* **** */
return DS_OK;
}
static HRESULT WINAPI IDsDriverBufferImpl_Unlock(PIDSDRIVERBUFFER iface,
LPVOID pvAudio1,DWORD dwLen1,
LPVOID pvAudio2,DWORD dwLen2)
{
IDsDriverBufferImpl *This = impl_from_IDsDriverBuffer(iface);
snd_pcm_uframes_t writepos;
if (!dwLen1)
return DS_OK;
/* **** */
EnterCriticalSection(&This->pcm_crst);
writepos = snd_pcm_bytes_to_frames(This->pcm, (DWORD_PTR)pvAudio1 - (DWORD_PTR)This->mmap_buffer);
if (writepos == This->mmap_pos)
{
const snd_pcm_channel_area_t *areas;
snd_pcm_uframes_t writelen = snd_pcm_bytes_to_frames(This->pcm, dwLen1);
TRACE("Committing data\n");
if (This->mmap)
This->mmap_pos += snd_pcm_mmap_commit(This->pcm, This->mmap_pos, writelen);
else
{
int ret;
ret = snd_pcm_writei(This->pcm, pvAudio1, writelen);
if (ret == -EPIPE)
{
WARN("Underrun occurred\n");
wine_snd_pcm_recover(This->pcm, -EPIPE, 1);
ret = snd_pcm_writei(This->pcm, pvAudio1, writelen);
/* Advance mmap pointer a little to make dsound notice the underrun and respond to it */
if (ret < writelen) WARN("Short write %ld/%d\n", writelen, ret);
This->mmap_pos += This->mmap_commitahead + ret;
This->mmap_pos %= This->mmap_buflen_frames;
}
else if (ret > 0)
This->mmap_pos += ret;
if (ret < 0)
WARN("Committing data: %d / %s (%p %ld)\n", ret, snd_strerror(ret), pvAudio1, writelen);
}
if (This->mmap_pos == This->mmap_buflen_frames)
This->mmap_pos = 0;
if (dwLen2)
{
writelen = snd_pcm_bytes_to_frames(This->pcm, dwLen2);
if (This->mmap)
{
snd_pcm_mmap_begin(This->pcm, &areas, &This->mmap_pos, &writelen);
This->mmap_pos += snd_pcm_mmap_commit(This->pcm, This->mmap_pos, writelen);
}
else
{
int ret;
ret = snd_pcm_writei(This->pcm, pvAudio2, writelen);
if (ret < writelen) WARN("Short write %ld/%d\n", writelen, ret);
This->mmap_pos = ret > 0 ? ret : 0;
}
assert(This->mmap_pos < This->mmap_buflen_frames);
}
}
LeaveCriticalSection(&This->pcm_crst);
/* **** */
return DS_OK;
}
static HRESULT SetFormat(IDsDriverBufferImpl *This, LPWAVEFORMATEX pwfx)
{
snd_pcm_t *pcm = NULL;
snd_pcm_hw_params_t *hw_params = This->hw_params;
unsigned int buffer_time = 500000;
snd_pcm_format_t format = -1;
snd_pcm_uframes_t psize;
DWORD rate = pwfx->nSamplesPerSec;
int err=0;
switch (pwfx->wBitsPerSample)
{
case 8: format = SND_PCM_FORMAT_U8; break;
case 16: format = SND_PCM_FORMAT_S16_LE; break;
case 24: format = SND_PCM_FORMAT_S24_3LE; break;
case 32: format = SND_PCM_FORMAT_S32_LE; break;
default: FIXME("Unsupported bpp: %d\n", pwfx->wBitsPerSample); return DSERR_GENERIC;
}
err = snd_pcm_open(&pcm, WOutDev[This->drv->wDevID].pcmname, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
if (err < 0)
{
if (errno != EBUSY || !This->pcm)
{
WARN("Cannot open sound device: %s\n", snd_strerror(err));
return DSERR_GENERIC;
}
snd_pcm_drop(This->pcm);
snd_pcm_close(This->pcm);
This->pcm = NULL;
err = snd_pcm_open(&pcm, WOutDev[This->drv->wDevID].pcmname, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
if (err < 0)
{
WARN("Cannot open sound device: %s\n", snd_strerror(err));
return DSERR_BUFFERLOST;
}
}
/* Set some defaults */
snd_pcm_hw_params_any(pcm, hw_params);
err = snd_pcm_hw_params_set_channels(pcm, hw_params, pwfx->nChannels);
if (err < 0) { WARN("Could not set channels to %d\n", pwfx->nChannels); goto err; }
err = snd_pcm_hw_params_set_format(pcm, hw_params, format);
if (err < 0) { WARN("Could not set format to %d bpp\n", pwfx->wBitsPerSample); goto err; }
/* Alsa's rate resampling is only used if the application specifically requests
* a buffer at a certain frequency, else it is better to disable it due to unwanted
* side effects, which may include: Less granular pointer, changing buffer sizes, etc
*/
#if SND_LIB_VERSION >= 0x010009
snd_pcm_hw_params_set_rate_resample(pcm, hw_params, 0);
#endif
err = snd_pcm_hw_params_set_rate_near(pcm, hw_params, &rate, NULL);
if (err < 0) { rate = pwfx->nSamplesPerSec; WARN("Could not set rate\n"); goto err; }
if (!ALSA_NearMatch(rate, pwfx->nSamplesPerSec))
{
WARN("Could not set sound rate to %d, but instead to %d\n", pwfx->nSamplesPerSec, rate);
pwfx->nSamplesPerSec = rate;
pwfx->nAvgBytesPerSec = rate * pwfx->nBlockAlign;
/* Let DirectSound detect this */
}
snd_pcm_hw_params_set_periods_integer(pcm, hw_params);
snd_pcm_hw_params_set_buffer_time_near(pcm, hw_params, &buffer_time, NULL);
buffer_time = 10000;
snd_pcm_hw_params_set_period_time_near(pcm, hw_params, &buffer_time, NULL);
err = snd_pcm_hw_params_get_period_size(hw_params, &psize, NULL);
buffer_time = 16;
snd_pcm_hw_params_set_periods_near(pcm, hw_params, &buffer_time, NULL);
if (!This->mmap)
{
HeapFree(GetProcessHeap(), 0, This->mmap_buffer);
This->mmap_buffer = NULL;
}
err = snd_pcm_hw_params_set_access (pcm, hw_params, SND_PCM_ACCESS_MMAP_INTERLEAVED);
if (err >= 0)
This->mmap = 1;
else
{
This->mmap = 0;
err = snd_pcm_hw_params_set_access (pcm, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
}
err = snd_pcm_hw_params(pcm, hw_params);
/* ALSA needs at least 3 buffers to work successfully */
This->mmap_commitahead = 3 * psize;
while (This->mmap_commitahead <= 512)
This->mmap_commitahead += psize;
if (This->pcm)
{
snd_pcm_drop(This->pcm);
snd_pcm_close(This->pcm);
}
This->pcm = pcm;
snd_pcm_prepare(This->pcm);
DSDB_CreateMMAP(This);
return S_OK;
err:
if (err < 0)
WARN("Failed to apply changes: %s\n", snd_strerror(err));
if (!This->pcm)
This->pcm = pcm;
else
snd_pcm_close(pcm);
if (This->pcm)
snd_pcm_hw_params_current(This->pcm, This->hw_params);
return DSERR_BADFORMAT;
}
static HRESULT WINAPI IDsDriverBufferImpl_SetFormat(PIDSDRIVERBUFFER iface, LPWAVEFORMATEX pwfx)
{
IDsDriverBufferImpl *This = impl_from_IDsDriverBuffer(iface);
HRESULT hr = S_OK;
TRACE("(%p, %p)\n", iface, pwfx);
/* **** */
EnterCriticalSection(&This->pcm_crst);
hr = SetFormat(This, pwfx);
/* **** */
LeaveCriticalSection(&This->pcm_crst);
if (hr == DS_OK)
return S_FALSE;
return hr;
}
static HRESULT WINAPI IDsDriverBufferImpl_SetFrequency(PIDSDRIVERBUFFER iface, DWORD dwFreq)
{
/* IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; */
FIXME("(%p,%d): stub\n",iface,dwFreq);
return S_OK;
}
static HRESULT WINAPI IDsDriverBufferImpl_SetVolumePan(PIDSDRIVERBUFFER iface, PDSVOLUMEPAN pVolPan)
{
IDsDriverBufferImpl *This = impl_from_IDsDriverBuffer(iface);
FIXME("(%p,%p): stub\n",This,pVolPan);
/* TODO: Bring volume control back */
return DS_OK;
}
static HRESULT WINAPI IDsDriverBufferImpl_SetPosition(PIDSDRIVERBUFFER iface, DWORD dwNewPos)
{
/* IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; */
/* I don't even think alsa allows this */
FIXME("(%p,%d): stub\n",iface,dwNewPos);
return DSERR_UNSUPPORTED;
}
static HRESULT WINAPI IDsDriverBufferImpl_GetPosition(PIDSDRIVERBUFFER iface,
LPDWORD lpdwPlay, LPDWORD lpdwWrite)
{
IDsDriverBufferImpl *This = impl_from_IDsDriverBuffer(iface);
snd_pcm_uframes_t hw_pptr, hw_wptr;
snd_pcm_state_t state;
/* **** */
EnterCriticalSection(&This->pcm_crst);
if (!This->pcm)
{
FIXME("Bad pointer for pcm: %p\n", This->pcm);
LeaveCriticalSection(&This->pcm_crst);
return DSERR_GENERIC;
}
if (!lpdwPlay && !lpdwWrite)
CommitAll(This);
state = snd_pcm_state(This->pcm);
if (state != SND_PCM_STATE_PREPARED && state != SND_PCM_STATE_RUNNING)
{
CheckXRUN(This);
state = snd_pcm_state(This->pcm);
}
if (state == SND_PCM_STATE_RUNNING)
{
snd_pcm_sframes_t used = This->mmap_buflen_frames - snd_pcm_avail_update(This->pcm);
if (used < 0)
{
WARN("Underrun: %ld / %ld\n", used, snd_pcm_avail_update(This->pcm));
if (This->mmap)
{
snd_pcm_forward(This->pcm, -used);
This->mmap_pos += -used;
This->mmap_pos %= This->mmap_buflen_frames;
}
used = 0;
}
if (This->mmap_pos > used)
hw_pptr = This->mmap_pos - used;
else
hw_pptr = This->mmap_buflen_frames + This->mmap_pos - used;
hw_pptr %= This->mmap_buflen_frames;
TRACE("At position: %ld (%ld) - Used %ld\n", hw_pptr, This->mmap_pos, used);
}
else hw_pptr = This->mmap_pos;
hw_wptr = This->mmap_pos;
LeaveCriticalSection(&This->pcm_crst);
/* **** */
if (lpdwPlay)
*lpdwPlay = snd_pcm_frames_to_bytes(This->pcm, hw_pptr);
if (lpdwWrite)
*lpdwWrite = snd_pcm_frames_to_bytes(This->pcm, hw_wptr);
TRACE("hw_pptr=0x%08x, hw_wptr=0x%08x playpos=%d, writepos=%d\n", (unsigned int)hw_pptr, (unsigned int)hw_wptr, lpdwPlay?*lpdwPlay:-1, lpdwWrite?*lpdwWrite:-1);
return DS_OK;
}
static HRESULT WINAPI IDsDriverBufferImpl_Play(PIDSDRIVERBUFFER iface, DWORD dwRes1, DWORD dwRes2, DWORD dwFlags)
{
IDsDriverBufferImpl *This = impl_from_IDsDriverBuffer(iface);
TRACE("(%p,%x,%x,%x)\n",iface,dwRes1,dwRes2,dwFlags);
/* **** */
EnterCriticalSection(&This->pcm_crst);
snd_pcm_start(This->pcm);
/* **** */
LeaveCriticalSection(&This->pcm_crst);
return DS_OK;
}
static HRESULT WINAPI IDsDriverBufferImpl_Stop(PIDSDRIVERBUFFER iface)
{
const snd_pcm_channel_area_t *areas;
snd_pcm_uframes_t avail;
snd_pcm_format_t format;
IDsDriverBufferImpl *This = impl_from_IDsDriverBuffer(iface);
TRACE("(%p)\n",iface);
/* **** */
EnterCriticalSection(&This->pcm_crst);
avail = This->mmap_buflen_frames;
snd_pcm_drop(This->pcm);
snd_pcm_prepare(This->pcm);
avail = snd_pcm_avail_update(This->pcm);
snd_pcm_hw_params_get_format(This->hw_params, &format);
if (This->mmap)
{
snd_pcm_mmap_begin(This->pcm, &areas, &This->mmap_pos, &avail);
snd_pcm_format_set_silence(format, areas->addr, This->mmap_buflen_frames);
snd_pcm_mmap_commit(This->pcm, This->mmap_pos, 0);
}
else
{
snd_pcm_format_set_silence(format, This->mmap_buffer, This->mmap_buflen_frames);
snd_pcm_writei(This->pcm, This->mmap_buffer, This->mmap_buflen_frames);
This->mmap_pos = 0;
}
/* **** */
LeaveCriticalSection(&This->pcm_crst);
return DS_OK;
}
static const IDsDriverBufferVtbl dsdbvt =
{
IDsDriverBufferImpl_QueryInterface,
IDsDriverBufferImpl_AddRef,
IDsDriverBufferImpl_Release,
IDsDriverBufferImpl_Lock,
IDsDriverBufferImpl_Unlock,
IDsDriverBufferImpl_SetFormat,
IDsDriverBufferImpl_SetFrequency,
IDsDriverBufferImpl_SetVolumePan,
IDsDriverBufferImpl_SetPosition,
IDsDriverBufferImpl_GetPosition,
IDsDriverBufferImpl_Play,
IDsDriverBufferImpl_Stop
};
static HRESULT WINAPI IDsDriverImpl_QueryInterface(PIDSDRIVER iface, REFIID riid, LPVOID *ppobj)
{
/* IDsDriverImpl *This = (IDsDriverImpl *)iface; */
FIXME("(%p): stub!\n",iface);
return DSERR_UNSUPPORTED;
}
static ULONG WINAPI IDsDriverImpl_AddRef(PIDSDRIVER iface)
{
IDsDriverImpl *This = impl_from_IDsDriver(iface);
ULONG refCount = InterlockedIncrement(&This->ref);
TRACE("(%p)->(ref before=%u)\n",This, refCount - 1);
return refCount;
}
static ULONG WINAPI IDsDriverImpl_Release(PIDSDRIVER iface)
{
IDsDriverImpl *This = impl_from_IDsDriver(iface);
ULONG refCount = InterlockedDecrement(&This->ref);
TRACE("(%p)->(ref before=%u)\n",This, refCount + 1);
if (refCount)
return refCount;
HeapFree(GetProcessHeap(), 0, This);
return 0;
}
static HRESULT WINAPI IDsDriverImpl_GetDriverDesc(PIDSDRIVER iface, PDSDRIVERDESC pDesc)
{
IDsDriverImpl *This = impl_from_IDsDriver(iface);
TRACE("(%p,%p)\n",iface,pDesc);
*pDesc = WOutDev[This->wDevID].ds_desc;
pDesc->dwFlags = DSDDESC_DONTNEEDSECONDARYLOCK | DSDDESC_DONTNEEDWRITELEAD;
pDesc->dnDevNode = WOutDev[This->wDevID].waveDesc.dnDevNode;
pDesc->wVxdId = 0;
pDesc->wReserved = 0;
pDesc->ulDeviceNum = This->wDevID;
pDesc->dwHeapType = DSDHEAP_NOHEAP;
pDesc->pvDirectDrawHeap = NULL;
pDesc->dwMemStartAddress = 0xDEAD0000;
pDesc->dwMemEndAddress = 0xDEAF0000;
pDesc->dwMemAllocExtra = 0;
pDesc->pvReserved1 = NULL;
pDesc->pvReserved2 = NULL;
return DS_OK;
}
static HRESULT WINAPI IDsDriverImpl_Open(PIDSDRIVER iface)
{
HRESULT hr = S_OK;
IDsDriverImpl *This = impl_from_IDsDriver(iface);
int err=0;
snd_pcm_t *pcm = NULL;
snd_pcm_hw_params_t *hw_params;
/* While this is not really needed, it is a good idea to do this,
* to see if sound can be initialized */
hw_params = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, snd_pcm_hw_params_sizeof());
if (!hw_params)
{
hr = DSERR_OUTOFMEMORY;
goto unalloc;
}
err = snd_pcm_open(&pcm, WOutDev[This->wDevID].pcmname, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
if (err < 0) goto err;
err = snd_pcm_hw_params_any(pcm, hw_params);
if (err < 0) goto err;
err = snd_pcm_hw_params_set_access (pcm, hw_params, SND_PCM_ACCESS_MMAP_INTERLEAVED);
if (err < 0)
err = snd_pcm_hw_params_set_access (pcm, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
if (err < 0) goto err;
TRACE("Success\n");
snd_pcm_close(pcm);
goto unalloc;
err:
hr = DSERR_GENERIC;
FIXME("Failed to open device: %s\n", snd_strerror(err));
if (pcm)
snd_pcm_close(pcm);
unalloc:
HeapFree(GetProcessHeap(), 0, hw_params);
if (hr != S_OK)
WARN("--> %08x\n", hr);
return hr;
}
static HRESULT WINAPI IDsDriverImpl_Close(PIDSDRIVER iface)
{
IDsDriverImpl *This = impl_from_IDsDriver(iface);
TRACE("(%p) stub, harmless\n",This);
return DS_OK;
}
static HRESULT WINAPI IDsDriverImpl_GetCaps(PIDSDRIVER iface, PDSDRIVERCAPS pCaps)
{
IDsDriverImpl *This = impl_from_IDsDriver(iface);
TRACE("(%p,%p)\n",iface,pCaps);
*pCaps = WOutDev[This->wDevID].ds_caps;
return DS_OK;
}
static HRESULT WINAPI IDsDriverImpl_CreateSoundBuffer(PIDSDRIVER iface,
LPWAVEFORMATEX pwfx,
DWORD dwFlags, DWORD dwCardAddress,
LPDWORD pdwcbBufferSize,
LPBYTE *ppbBuffer,
LPVOID *ppvObj)
{
IDsDriverImpl *This = impl_from_IDsDriver(iface);
IDsDriverBufferImpl** ippdsdb = (IDsDriverBufferImpl**)ppvObj;
HRESULT err;
TRACE("(%p,%p,%x,%x)\n",iface,pwfx,dwFlags,dwCardAddress);
/* we only support primary buffers... for now */
if (!(dwFlags & DSBCAPS_PRIMARYBUFFER))
return DSERR_UNSUPPORTED;
if (This->primary)
return DSERR_ALLOCATED;
*ippdsdb = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(IDsDriverBufferImpl));
if (*ippdsdb == NULL)
return DSERR_OUTOFMEMORY;
(*ippdsdb)->hw_params = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, snd_pcm_hw_params_sizeof());
(*ippdsdb)->sw_params = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, snd_pcm_sw_params_sizeof());
if (!(*ippdsdb)->hw_params || !(*ippdsdb)->sw_params)
{
HeapFree(GetProcessHeap(), 0, (*ippdsdb)->sw_params);
HeapFree(GetProcessHeap(), 0, (*ippdsdb)->hw_params);
return DSERR_OUTOFMEMORY;
}
(*ippdsdb)->IDsDriverBuffer_iface.lpVtbl = &dsdbvt;
(*ippdsdb)->ref = 1;
(*ippdsdb)->drv = This;
InitializeCriticalSection(&(*ippdsdb)->pcm_crst);
(*ippdsdb)->pcm_crst.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": ALSA_DSOUTPUT.pcm_crst");
/* SetFormat has to re-initialize pcm here anyway */
err = SetFormat(*ippdsdb, pwfx);
if (FAILED(err))
{
WARN("Error occurred: %08x\n", err);
goto err;
}
if (dwFlags & DSBCAPS_PRIMARYBUFFER)
This->primary = *ippdsdb;
*pdwcbBufferSize = (*ippdsdb)->mmap_buflen_bytes;
*ppbBuffer = (*ippdsdb)->mmap_buffer;
/* buffer is ready to go */
TRACE("buffer created at %p\n", *ippdsdb);
return err;
err:
HeapFree(GetProcessHeap(), 0, (*ippdsdb)->sw_params);
HeapFree(GetProcessHeap(), 0, (*ippdsdb)->hw_params);
HeapFree(GetProcessHeap(), 0, *ippdsdb);
*ippdsdb = NULL;
return err;
}
static HRESULT WINAPI IDsDriverImpl_DuplicateSoundBuffer(PIDSDRIVER iface,
PIDSDRIVERBUFFER pBuffer,
LPVOID *ppvObj)
{
IDsDriverImpl *This = impl_from_IDsDriver(iface);
FIXME("(%p,%p): stub\n",This,pBuffer);
return DSERR_INVALIDCALL;
}
static const IDsDriverVtbl dsdvt =
{
IDsDriverImpl_QueryInterface,
IDsDriverImpl_AddRef,
IDsDriverImpl_Release,
IDsDriverImpl_GetDriverDesc,
IDsDriverImpl_Open,
IDsDriverImpl_Close,
IDsDriverImpl_GetCaps,
IDsDriverImpl_CreateSoundBuffer,
IDsDriverImpl_DuplicateSoundBuffer
};
DWORD wodDsCreate(UINT wDevID, PIDSDRIVER* drv)
{
IDsDriverImpl** idrv = (IDsDriverImpl**)drv;
TRACE("driver created\n");
*idrv = HeapAlloc(GetProcessHeap(),0,sizeof(IDsDriverImpl));
if (!*idrv)
return MMSYSERR_NOMEM;
(*idrv)->IDsDriver_iface.lpVtbl = &dsdvt;
(*idrv)->ref = 1;
(*idrv)->wDevID = wDevID;
(*idrv)->primary = NULL;
return MMSYSERR_NOERROR;
}
DWORD wodDsDesc(UINT wDevID, PDSDRIVERDESC desc)
{
*desc = WOutDev[wDevID].ds_desc;
return MMSYSERR_NOERROR;
}
......@@ -51,9 +51,10 @@
#include "mmreg.h"
#include "dsound.h"
#include "dsdriver.h"
#include "alsa.h"
#include "wine/debug.h"
#include <alsa/asoundlib.h>
WINE_DEFAULT_DEBUG_CHANNEL(midi);
#ifndef SND_SEQ_PORT_TYPE_PORT
......@@ -1390,4 +1391,29 @@ DWORD WINAPI ALSA_modMessage(UINT wDevID, UINT wMsg, DWORD_PTR dwUser,
return MMSYSERR_NOTSUPPORTED;
}
/*-----------------------------------------------------------------------*/
/**************************************************************************
* DriverProc (WINEALSA.@)
*/
LRESULT CALLBACK ALSA_DriverProc(DWORD_PTR dwDevID, HDRVR hDriv, UINT wMsg,
LPARAM dwParam1, LPARAM dwParam2)
{
/* EPP TRACE("(%08lX, %04X, %08lX, %08lX, %08lX)\n", */
/* EPP dwDevID, hDriv, wMsg, dwParam1, dwParam2); */
switch(wMsg) {
case DRV_LOAD:
case DRV_FREE:
case DRV_OPEN:
case DRV_CLOSE:
case DRV_ENABLE:
case DRV_DISABLE:
case DRV_QUERYCONFIGURE:
case DRV_CONFIGURE:
return 1;
case DRV_INSTALL:
case DRV_REMOVE:
return DRV_SUCCESS;
default:
return 0;
}
}
/*
* Alsa MIXER Wine Driver for Linux
* Very loosely based on wineoss mixer driver
*
* Copyright 2007 Maarten Lankhorst
*
* 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
*/
#include "config.h"
#include "wine/port.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 <errno.h>
#include <assert.h>
#ifdef HAVE_SYS_IOCTL_H
# include <sys/ioctl.h>
#endif
#define NONAMELESSUNION
#define NONAMELESSSTRUCT
#include "windef.h"
#include "winbase.h"
#include "wingdi.h"
#include "winuser.h"
#include "winnls.h"
#include "mmddk.h"
#include "mmreg.h"
#include "dsound.h"
#include "dsdriver.h"
#include "mmsystem.h"
#include "alsa.h"
#include "wine/unicode.h"
#include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(mixer);
#define WINE_MIXER_MANUF_ID 0xAA
#define WINE_MIXER_PRODUCT_ID 0x55
#define WINE_MIXER_VERSION 0x0100
/* Generic notes:
* In windows it seems to be required for all controls to have a volume switch
* In alsa that's optional
*
* I assume for playback controls, that there is always a playback volume switch available
* Mute is optional
*
* For capture controls, it is needed that there is a capture switch and a volume switch,
* It doesn't matter whether it is a playback volume switch or a capture volume switch.
* The code will first try to get/adjust capture volume, if that fails it tries playback volume
* It is not pretty, but under my 3 test cards it seems that there is no other choice:
* Most capture controls don't have a capture volume setting
*
* MUX means that only capture source can be exclusively selected,
* MIXER means that multiple sources can be selected simultaneously.
*/
static const char * getMessage(UINT uMsg)
{
#define MSG_TO_STR(x) case x: return #x;
switch (uMsg){
MSG_TO_STR(DRVM_INIT);
MSG_TO_STR(DRVM_EXIT);
MSG_TO_STR(DRVM_ENABLE);
MSG_TO_STR(DRVM_DISABLE);
MSG_TO_STR(MXDM_GETDEVCAPS);
MSG_TO_STR(MXDM_GETLINEINFO);
MSG_TO_STR(MXDM_GETNUMDEVS);
MSG_TO_STR(MXDM_OPEN);
MSG_TO_STR(MXDM_CLOSE);
MSG_TO_STR(MXDM_GETLINECONTROLS);
MSG_TO_STR(MXDM_GETCONTROLDETAILS);
MSG_TO_STR(MXDM_SETCONTROLDETAILS);
default: break;
}
#undef MSG_TO_STR
return wine_dbg_sprintf("UNKNOWN(%08x)", uMsg);
}
static const char * getControlType(DWORD dwControlType)
{
#define TYPE_TO_STR(x) case x: return #x;
switch (dwControlType) {
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_CUSTOM);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BOOLEANMETER);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SIGNEDMETER);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_PEAKMETER);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_UNSIGNEDMETER);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BOOLEAN);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_ONOFF);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MUTE);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MONO);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_LOUDNESS);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_STEREOENH);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BASS_BOOST);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BUTTON);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_DECIBELS);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SIGNED);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_UNSIGNED);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_PERCENT);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SLIDER);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_PAN);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_QSOUNDPAN);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_FADER);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_VOLUME);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BASS);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_TREBLE);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_EQUALIZER);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SINGLESELECT);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MUX);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MULTIPLESELECT);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MIXER);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MICROTIME);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MILLITIME);
}
#undef TYPE_TO_STR
return wine_dbg_sprintf("UNKNOWN(%08x)", dwControlType);
}
/* A simple declaration of a line control
* These are each of the channels that show up
*/
typedef struct line {
/* Name we present to outside world */
WCHAR name[MAXPNAMELEN];
DWORD component;
DWORD dst;
DWORD capt;
DWORD chans;
snd_mixer_elem_t *elem;
} line;
/* A control structure, with toggle enabled switch
* Control structures control volume, muted, which capture source
*/
typedef struct control {
BOOL enabled;
MIXERCONTROLW c;
} control;
/* Mixer device */
typedef struct mixer
{
snd_mixer_t *mix;
WCHAR mixername[MAXPNAMELEN];
int chans, dests;
LPDRVCALLBACK callback;
DWORD_PTR callbackpriv;
HDRVR hmx;
line *lines;
control *controls;
} mixer;
#define MAX_MIXERS 32
#define CONTROLSPERLINE 3
#define OFS_MUTE 2
#define OFS_MUX 1
static int cards = 0;
static mixer mixdev[MAX_MIXERS];
static HANDLE thread;
static int elem_callback(snd_mixer_elem_t *elem, unsigned int mask);
static DWORD WINAPI ALSA_MixerPollThread(LPVOID lParam);
static CRITICAL_SECTION elem_crst;
static int msg_pipe[2];
static LONG refcnt;
/* found channel names in alsa lib, alsa api doesn't have another way for this
* map name -> componenttype, worst case we get a wrong componenttype which is
* mostly harmless
*/
static const struct mixerlinetype {
const char *name; DWORD cmpt;
} converttable[] = {
{ "Master", MIXERLINE_COMPONENTTYPE_DST_SPEAKERS, },
{ "Capture", MIXERLINE_COMPONENTTYPE_DST_WAVEIN, },
{ "PCM", MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT, },
{ "PC Speaker", MIXERLINE_COMPONENTTYPE_SRC_PCSPEAKER, },
{ "Synth", MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER, },
{ "Headphone", MIXERLINE_COMPONENTTYPE_DST_HEADPHONES, },
{ "Mic", MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE, },
{ "Aux", MIXERLINE_COMPONENTTYPE_SRC_UNDEFINED, },
{ "CD", MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC, },
{ "Line", MIXERLINE_COMPONENTTYPE_SRC_LINE, },
{ "Phone", MIXERLINE_COMPONENTTYPE_SRC_TELEPHONE, },
{ "Digital", MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE, },
{ "Front Mic", MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE, },
};
/* Map name to MIXERLINE_COMPONENTTYPE_XXX */
static int getcomponenttype(const char *name)
{
int x;
for (x=0; x< sizeof(converttable)/sizeof(converttable[0]); ++x)
if (!strcasecmp(name, converttable[x].name))
{
TRACE("%d -> %s\n", x, name);
return converttable[x].cmpt;
}
WARN("Unknown mixer name %s, probably harmless\n", name);
return MIXERLINE_COMPONENTTYPE_SRC_UNDEFINED;
}
/* Is this control suited for showing up? */
static int blacklisted(snd_mixer_elem_t *elem)
{
const char *name = snd_mixer_selem_get_name(elem);
BOOL blisted = 0;
if (!snd_mixer_selem_has_playback_volume(elem) &&
!snd_mixer_selem_has_capture_volume(elem))
blisted = 1;
TRACE("%s: %x\n", name, blisted);
return blisted;
}
static void fillcontrols(mixer *mmixer)
{
int id;
for (id = 0; id < mmixer->chans; ++id)
{
line *mline = &mmixer->lines[id];
int ofs = CONTROLSPERLINE * id;
int x;
long min, max;
TRACE("Filling control %d\n", id);
if (!mline->elem)
break;
if (id == 1 && !mline->elem)
continue;
if (mline->capt && snd_mixer_selem_has_capture_volume(mline->elem))
snd_mixer_selem_get_capture_volume_range(mline->elem, &min, &max);
else
snd_mixer_selem_get_playback_volume_range(mline->elem, &min, &max);
/* (!snd_mixer_selem_has_playback_volume(elem) || snd_mixer_selem_has_capture_volume(elem)) */
/* Volume, always enabled by definition of blacklisted channels */
mmixer->controls[ofs].enabled = 1;
mmixer->controls[ofs].c.cbStruct = sizeof(mmixer->controls[ofs].c);
mmixer->controls[ofs].c.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
mmixer->controls[ofs].c.dwControlID = ofs;
mmixer->controls[ofs].c.Bounds.s1.dwMinimum = 0;
mmixer->controls[ofs].c.Bounds.s1.dwMaximum = 65535;
mmixer->controls[ofs].c.Metrics.cSteps = 65536/(max-min);
if ((id == 1 && snd_mixer_selem_has_capture_switch(mline->elem)) ||
(!mline->capt && snd_mixer_selem_has_playback_switch(mline->elem)))
{ /* MUTE button optional, main capture channel should have one too */
mmixer->controls[ofs+OFS_MUTE].enabled = 1;
mmixer->controls[ofs+OFS_MUTE].c.cbStruct = sizeof(mmixer->controls[ofs].c);
mmixer->controls[ofs+OFS_MUTE].c.dwControlType = MIXERCONTROL_CONTROLTYPE_MUTE;
mmixer->controls[ofs+OFS_MUTE].c.dwControlID = ofs+OFS_MUTE;
mmixer->controls[ofs+OFS_MUTE].c.Bounds.s1.dwMaximum = 1;
}
if (mline->capt && snd_mixer_selem_has_capture_switch_exclusive(mline->elem))
mmixer->controls[CONTROLSPERLINE+OFS_MUX].c.dwControlType = MIXERCONTROL_CONTROLTYPE_MUX;
if (id == 1)
{ /* Capture select, in case cMultipleItems is 0, it means capture is disabled anyway */
mmixer->controls[ofs+OFS_MUX].enabled = 1;
mmixer->controls[ofs+OFS_MUX].c.cbStruct = sizeof(mmixer->controls[ofs].c);
mmixer->controls[ofs+OFS_MUX].c.dwControlType = MIXERCONTROL_CONTROLTYPE_MIXER;
mmixer->controls[ofs+OFS_MUX].c.dwControlID = ofs+OFS_MUX;
mmixer->controls[ofs+OFS_MUX].c.fdwControl = MIXERCONTROL_CONTROLF_MULTIPLE;
for (x = 0; x<mmixer->chans; ++x)
if (x != id && mmixer->lines[x].dst == id)
++(mmixer->controls[ofs+OFS_MUX].c.cMultipleItems);
if (!mmixer->controls[ofs+OFS_MUX].c.cMultipleItems)
mmixer->controls[ofs+OFS_MUX].enabled = 0;
mmixer->controls[ofs+OFS_MUX].c.Bounds.s1.dwMaximum = mmixer->controls[ofs+OFS_MUX].c.cMultipleItems - 1;
mmixer->controls[ofs+OFS_MUX].c.Metrics.cSteps = mmixer->controls[ofs+OFS_MUX].c.cMultipleItems;
}
for (x=0; x<CONTROLSPERLINE; ++x)
{
lstrcpynW(mmixer->controls[ofs+x].c.szShortName, mline->name, sizeof(mmixer->controls[ofs+x].c.szShortName)/sizeof(WCHAR));
lstrcpynW(mmixer->controls[ofs+x].c.szName, mline->name, sizeof(mmixer->controls[ofs+x].c.szName)/sizeof(WCHAR));
}
}
}
/* get amount of channels for elem */
/* Officially we should keep capture/playback separated,
* but that's not going to work in the alsa api */
static int chans(mixer *mmixer, snd_mixer_elem_t * elem, DWORD capt)
{
int ret=0, chn;
if (capt && snd_mixer_selem_has_capture_volume(elem)) {
for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
if (snd_mixer_selem_has_capture_channel(elem, chn))
++ret;
} else {
for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
if (snd_mixer_selem_has_playback_channel(elem, chn))
++ret;
}
if (!ret)
FIXME("Mixer channel %s was found for %s, but no channels were found? Wrong selection!\n", snd_mixer_selem_get_name(elem), (snd_mixer_selem_has_playback_volume(elem) ? "playback" : "capture"));
return ret;
}
static void filllines(mixer *mmixer, snd_mixer_elem_t *mastelem, snd_mixer_elem_t *captelem, int capt)
{
snd_mixer_elem_t *elem;
line *mline = mmixer->lines;
if (mastelem) {
/* Master control */
MultiByteToWideChar(CP_UNIXCP, 0, snd_mixer_selem_get_name(mastelem), -1, mline->name, sizeof(mline->name)/sizeof(WCHAR));
mline->component = getcomponenttype(snd_mixer_selem_get_name(mastelem));
mline->dst = 0;
mline->capt = 0;
mline->elem = mastelem;
mline->chans = chans(mmixer, mastelem, 0);
snd_mixer_elem_set_callback(mastelem, elem_callback);
snd_mixer_elem_set_callback_private(mastelem, mmixer);
} else {
MultiByteToWideChar(CP_UNIXCP, 0, "Empty Master Element", -1, mline->name, sizeof(mline->name)/sizeof(WCHAR));
}
/* Capture control
* Note: since mmixer->dests = 1, it means only playback control is visible
* This makes sense, because if there are no capture sources capture control
* can't do anything and should be invisible */
/* Control 1 is reserved for capture even when not enabled */
++mline;
if (capt)
{
MultiByteToWideChar(CP_UNIXCP, 0, snd_mixer_selem_get_name(captelem), -1, mline->name, sizeof(mline->name)/sizeof(WCHAR));
mline->component = getcomponenttype(snd_mixer_selem_get_name(captelem));
mline->dst = 1;
mline->capt = 1;
mline->elem = captelem;
mline->chans = chans(mmixer, captelem, 1);
snd_mixer_elem_set_callback(captelem, elem_callback);
snd_mixer_elem_set_callback_private(captelem, mmixer);
}
for (elem = snd_mixer_first_elem(mmixer->mix); elem; elem = snd_mixer_elem_next(elem))
if (elem != mastelem && elem != captelem && !blacklisted(elem))
{
const char * name = snd_mixer_selem_get_name(elem);
DWORD comp = getcomponenttype(name);
if (snd_mixer_selem_has_playback_volume(elem) &&
(snd_mixer_selem_has_capture_volume(elem) || comp != MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE))
{
(++mline)->component = comp;
MultiByteToWideChar(CP_UNIXCP, 0, name, -1, mline->name, MAXPNAMELEN);
mline->capt = mline->dst = 0;
mline->elem = elem;
mline->chans = chans(mmixer, elem, 0);
}
else if (!capt)
continue;
if (capt && (snd_mixer_selem_has_capture_volume(elem) || comp == MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE))
{
(++mline)->component = comp;
MultiByteToWideChar(CP_UNIXCP, 0, name, -1, mline->name, MAXPNAMELEN);
mline->capt = mline->dst = 1;
mline->elem = elem;
mline->chans = chans(mmixer, elem, 1);
}
snd_mixer_elem_set_callback(elem, elem_callback);
snd_mixer_elem_set_callback_private(elem, mmixer);
}
}
static void filllines_no_master(mixer *mmixer, snd_mixer_elem_t *captelem, int capt)
{
line *mline = mmixer->lines;
MultiByteToWideChar(CP_UNIXCP, 0, snd_mixer_selem_get_name(captelem), -1, mline->name, sizeof(mline->name)/sizeof(WCHAR));
mline->component = getcomponenttype(snd_mixer_selem_get_name(captelem));
mline->dst = 0;
mline->capt = 1;
mline->elem = captelem;
mline->chans = chans(mmixer, captelem, 1);
snd_mixer_elem_set_callback(captelem, elem_callback);
snd_mixer_elem_set_callback_private(captelem, mmixer);
}
/* Windows api wants to have a 'master' device to which all slaves are attached
* There are 2 ones in this code:
* - 'Master', fall back to 'Headphone' if unavailable, and if that's not available 'PCM'
* - 'Capture'
* Capture might not always be available, so should be prepared to be without if needed
*/
static void ALSA_MixerInit(void)
{
int x, mixnum = 0;
snd_ctl_card_info_t *info;
info = HeapAlloc( GetProcessHeap(), 0, snd_ctl_card_info_sizeof());
for (x = 0; x < MAX_MIXERS; ++x)
{
int card, err, capcontrols = 0, total_elems = 0;
char cardind[6], cardname[10];
snd_ctl_t *ctl;
snd_mixer_elem_t *elem, *mastelem = NULL, *headelem = NULL, *captelem = NULL, *pcmelem = NULL, *lineelem = NULL, *micelem = NULL;
memset(info, 0, snd_ctl_card_info_sizeof());
memset(&mixdev[mixnum], 0, sizeof(*mixdev));
snprintf(cardind, sizeof(cardind), "%d", x);
card = snd_card_get_index(cardind);
if (card < 0)
continue;
snprintf(cardname, sizeof(cardname), "hw:%d", card);
err = snd_ctl_open(&ctl, cardname, 0);
if (err < 0)
{
WARN("Cannot open card: %s\n", snd_strerror(err));
continue;
}
err = snd_ctl_card_info(ctl, info);
if (err < 0)
{
WARN("Cannot get card info: %s\n", snd_strerror(err));
snd_ctl_close(ctl);
continue;
}
MultiByteToWideChar(CP_UNIXCP, 0, snd_ctl_card_info_get_name(info), -1, mixdev[mixnum].mixername, sizeof(mixdev[mixnum].mixername)/sizeof(WCHAR));
snd_ctl_close(ctl);
err = snd_mixer_open(&mixdev[mixnum].mix, 0);
if (err < 0)
{
WARN("Error occurred opening mixer: %s\n", snd_strerror(err));
continue;
}
err = snd_mixer_attach(mixdev[mixnum].mix, cardname);
if (err < 0)
goto eclose;
err = snd_mixer_selem_register(mixdev[mixnum].mix, NULL, NULL);
if (err < 0)
goto eclose;
err = snd_mixer_load(mixdev[mixnum].mix);
if (err < 0)
goto eclose;
/* First, lets see what's available..
* If there are multiple Master or Captures, all except 1 will be added as slaves
*/
total_elems = snd_mixer_get_count(mixdev[mixnum].mix);
TRACE("Total elems: %d\n", total_elems);
for (elem = snd_mixer_first_elem(mixdev[mixnum].mix); elem; elem = snd_mixer_elem_next(elem))
if (!strcasecmp(snd_mixer_selem_get_name(elem), "Master") && !mastelem)
{
mastelem = elem;
++(mixdev[mixnum].chans);
}
else if (!strcasecmp(snd_mixer_selem_get_name(elem), "Capture") && !captelem)
captelem = elem;
else if (!strcasecmp(snd_mixer_selem_get_name(elem), "Mic") && !micelem && !mastelem && total_elems == 1)
/* this is what snd-usb-audio mics look like; just a Mic control and that's it.*/
micelem = elem;
else if (!blacklisted(elem))
{
DWORD comp = getcomponenttype(snd_mixer_selem_get_name(elem));
DWORD skip = 0;
/* Work around buggy drivers: Make this a capture control if the name is recognised as a microphone */
if (snd_mixer_selem_has_capture_volume(elem))
++capcontrols;
else if (comp == MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE)
{
++capcontrols;
skip = 1;
}
if (!skip && snd_mixer_selem_has_playback_volume(elem))
{
if (!strcasecmp(snd_mixer_selem_get_name(elem), "Headphone") && !headelem)
headelem = elem;
else if (!strcasecmp(snd_mixer_selem_get_name(elem), "PCM") && !pcmelem)
pcmelem = elem;
else if (!strcasecmp(snd_mixer_selem_get_name(elem), "Line") && !lineelem)
lineelem = elem;
++(mixdev[mixnum].chans);
}
}
/* Add dummy capture channel, wanted by Windows */
mixdev[mixnum].chans += 1;
/* If there is only 'Capture' and 'Master', this device is not worth it */
if (mixdev[mixnum].chans == 2)
{
WARN("No channels found, skipping device!\n");
goto close;
}
/* Master element can't have a capture control in this code, so
* if Headphone or PCM is promoted to master, unset its capture control */
if (headelem && !mastelem)
{
/* Using 'Headphone' as master device */
mastelem = headelem;
capcontrols -= !!snd_mixer_selem_has_capture_switch(mastelem);
}
else if (pcmelem && !mastelem)
{
/* Use 'PCM' as master device */
mastelem = pcmelem;
capcontrols -= !!snd_mixer_selem_has_capture_switch(mastelem);
}
else if (lineelem && !mastelem)
{
/* Use 'Line' as master device */
mastelem = lineelem;
capcontrols -= !!snd_mixer_selem_has_capture_switch(mastelem);
}
else if (!mastelem && !captelem && !micelem)
{
/* If there is nothing sensible that can act as 'Master' control, something is wrong */
FIXME("No master control found on %s, disabling mixer\n", snd_ctl_card_info_get_name(info));
goto close;
}
if (!captelem || !capcontrols)
{
/* Can't enable capture, so disabling it
* Note: capture control will still exist because
* dwLineID 0 and 1 are reserved for Master and Capture
*/
WARN("No use enabling capture part of mixer, capture control found: %s, amount of capture controls: %d\n",
(!captelem ? "no" : "yes"), capcontrols);
capcontrols = 0;
mixdev[mixnum].dests = 1;
}
else
{
mixdev[mixnum].chans += capcontrols;
mixdev[mixnum].dests = 2;
}
mixdev[mixnum].lines = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(line) * mixdev[mixnum].chans);
mixdev[mixnum].controls = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(control) * CONTROLSPERLINE*mixdev[mixnum].chans);
err = -ENOMEM;
if (!mixdev[mixnum].lines || !mixdev[mixnum].controls)
goto close;
if (mastelem)
filllines(&mixdev[mixnum], mastelem, captelem, capcontrols);
else if (micelem)
filllines_no_master(&mixdev[mixnum], micelem, 1);
fillcontrols(&mixdev[mixnum]);
TRACE("%s: Amount of controls: %i/%i, name: %s\n", cardname, mixdev[mixnum].dests, mixdev[mixnum].chans, debugstr_w(mixdev[mixnum].mixername));
mixnum++;
continue;
eclose:
WARN("Error occurred initialising mixer: %s\n", snd_strerror(err));
close:
HeapFree(GetProcessHeap(), 0, mixdev[mixnum].lines);
HeapFree(GetProcessHeap(), 0, mixdev[mixnum].controls);
snd_mixer_close(mixdev[mixnum].mix);
}
cards = mixnum;
HeapFree( GetProcessHeap(), 0, info );
/* There is no trouble with already assigning callbacks without initialising critsect:
* Callbacks only occur when snd_mixer_handle_events is called (only happens in thread)
*/
InitializeCriticalSection(&elem_crst);
elem_crst.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": ALSA_MIXER.elem_crst");
TRACE("\n");
}
static void ALSA_MixerExit(void)
{
int x;
if (refcnt)
{
WARN("Callback thread still alive, terminating uncleanly, refcnt: %d\n", refcnt);
/* Least we can do is making sure we're not in 'foreign' code */
EnterCriticalSection(&elem_crst);
TerminateThread(thread, 1);
refcnt = 0;
LeaveCriticalSection(&elem_crst);
}
TRACE("Cleaning up\n");
elem_crst.DebugInfo->Spare[0] = 0;
DeleteCriticalSection(&elem_crst);
for (x = 0; x < cards; ++x)
{
snd_mixer_close(mixdev[x].mix);
HeapFree(GetProcessHeap(), 0, mixdev[x].lines);
HeapFree(GetProcessHeap(), 0, mixdev[x].controls);
}
cards = 0;
}
static mixer* MIX_GetMix(UINT wDevID)
{
mixer *mmixer;
if (wDevID >= cards)
{
WARN("Invalid mixer id: %d\n", wDevID);
return NULL;
}
mmixer = &mixdev[wDevID];
return mmixer;
}
/* Since alsa doesn't tell what exactly changed, just assume all affected controls changed */
static int elem_callback(snd_mixer_elem_t *elem, unsigned int type)
{
mixer *mmixer = snd_mixer_elem_get_callback_private(elem);
int x;
BOOL captchanged = 0;
if (type != SND_CTL_EVENT_MASK_VALUE)
return 0;
assert(mmixer);
EnterCriticalSection(&elem_crst);
if (!mmixer->callback)
goto out;
for (x=0; x<mmixer->chans; ++x)
{
const int ofs = CONTROLSPERLINE*x;
if (elem != mmixer->lines[x].elem)
continue;
if (mmixer->lines[x].capt)
++captchanged;
TRACE("Found changed control %s\n", debugstr_w(mmixer->lines[x].name));
mmixer->callback(mmixer->hmx, MM_MIXM_LINE_CHANGE, mmixer->callbackpriv, x, 0);
mmixer->callback(mmixer->hmx, MM_MIXM_CONTROL_CHANGE, mmixer->callbackpriv, ofs, 0);
if (mmixer->controls[ofs+OFS_MUTE].enabled)
mmixer->callback(mmixer->hmx, MM_MIXM_CONTROL_CHANGE, mmixer->callbackpriv, ofs+OFS_MUTE, 0);
}
if (captchanged)
mmixer->callback(mmixer->hmx, MM_MIXM_CONTROL_CHANGE, mmixer->callbackpriv, CONTROLSPERLINE+OFS_MUX, 0);
out:
LeaveCriticalSection(&elem_crst);
return 0;
}
static DWORD WINAPI ALSA_MixerPollThread(LPVOID lParam)
{
struct pollfd *pfds = NULL;
int x, y, err, mcnt, count = 1;
TRACE("%p\n", lParam);
for (x = 0; x < cards; ++x)
count += snd_mixer_poll_descriptors_count(mixdev[x].mix);
TRACE("Counted %d descriptors\n", count);
pfds = HeapAlloc(GetProcessHeap(), 0, count * sizeof(struct pollfd));
if (!pfds)
{
WARN("Out of memory\n");
goto die;
}
pfds[0].fd = msg_pipe[0];
pfds[0].events = POLLIN;
y = 1;
for (x = 0; x < cards; ++x)
y += snd_mixer_poll_descriptors(mixdev[x].mix, &pfds[y], count - y);
while ((err = poll(pfds, (unsigned int) count, -1)) >= 0 || errno == EINTR || errno == EAGAIN)
{
if (pfds[0].revents & POLLIN)
break;
mcnt = 1;
for (x = y = 0; x < cards; ++x)
{
int j, max = snd_mixer_poll_descriptors_count(mixdev[x].mix);
for (j = 0; j < max; ++j)
if (pfds[mcnt+j].revents)
{
y += snd_mixer_handle_events(mixdev[x].mix);
break;
}
mcnt += max;
}
if (y)
TRACE("Handled %d events\n", y);
}
die:
TRACE("Shutting down\n");
HeapFree(GetProcessHeap(), 0, pfds);
y = read(msg_pipe[0], &x, sizeof(x));
close(msg_pipe[1]);
close(msg_pipe[0]);
return 0;
}
static DWORD MIX_Open(UINT wDevID, LPMIXEROPENDESC desc, DWORD_PTR flags)
{
mixer *mmixer = MIX_GetMix(wDevID);
if (!mmixer)
return MMSYSERR_BADDEVICEID;
flags &= CALLBACK_TYPEMASK;
switch (flags)
{
case CALLBACK_NULL:
goto done;
case CALLBACK_FUNCTION:
break;
default:
FIXME("Unhandled callback type: %08lx\n", flags & CALLBACK_TYPEMASK);
return MIXERR_INVALVALUE;
}
mmixer->callback = (LPDRVCALLBACK)desc->dwCallback;
mmixer->callbackpriv = desc->dwInstance;
mmixer->hmx = (HDRVR)desc->hmx;
done:
if (InterlockedIncrement(&refcnt) == 1)
{
if (pipe(msg_pipe) >= 0)
{
thread = CreateThread(NULL, 0, ALSA_MixerPollThread, NULL, 0, NULL);
if (!thread)
{
close(msg_pipe[0]);
close(msg_pipe[1]);
msg_pipe[0] = msg_pipe[1] = -1;
}
}
else
msg_pipe[0] = msg_pipe[1] = -1;
}
return MMSYSERR_NOERROR;
}
static DWORD MIX_Close(UINT wDevID)
{
int x = 0;
mixer *mmixer = MIX_GetMix(wDevID);
if (!mmixer)
return MMSYSERR_BADDEVICEID;
EnterCriticalSection(&elem_crst);
mmixer->callback = 0;
LeaveCriticalSection(&elem_crst);
if (!InterlockedDecrement(&refcnt))
{
if (write(msg_pipe[1], &x, sizeof(x)) > 0)
{
TRACE("Shutting down thread...\n");
WaitForSingleObject(thread, INFINITE);
TRACE("Done\n");
}
}
return MMSYSERR_NOERROR;
}
static DWORD MIX_GetDevCaps(UINT wDevID, LPMIXERCAPS2W caps, DWORD_PTR parm2)
{
mixer *mmixer = MIX_GetMix(wDevID);
MIXERCAPS2W capsW;
if (!caps)
return MMSYSERR_INVALPARAM;
if (!mmixer)
return MMSYSERR_BADDEVICEID;
memset(&capsW, 0, sizeof(MIXERCAPS2W));
capsW.wMid = WINE_MIXER_MANUF_ID;
capsW.wPid = WINE_MIXER_PRODUCT_ID;
capsW.vDriverVersion = WINE_MIXER_VERSION;
lstrcpynW(capsW.szPname, mmixer->mixername, sizeof(capsW.szPname)/sizeof(WCHAR));
capsW.cDestinations = mmixer->dests;
memcpy(caps, &capsW, min(parm2, sizeof(capsW)));
return MMSYSERR_NOERROR;
}
/* convert win32 volume to alsa volume, and vice versa */
static INT normalized(INT value, INT prevmax, INT nextmax)
{
int ret = MulDiv(value, nextmax, prevmax);
/* Have to stay in range */
TRACE("%d/%d -> %d/%d\n", value, prevmax, ret, nextmax);
if (ret > nextmax)
ret = nextmax;
else if (ret < 0)
ret = 0;
return ret;
}
/* get amount of sources for dest */
static int getsrccntfromchan(mixer *mmixer, int dad)
{
int i, j=0;
for (i=0; i<mmixer->chans; ++i)
if (i != dad && mmixer->lines[i].dst == dad)
{
++j;
}
if (!j)
FIXME("No src found for %i (%s)?\n", dad, debugstr_w(mmixer->lines[dad].name));
return j;
}
/* find lineid for source 'num' with dest 'dad' */
static int getsrclinefromchan(mixer *mmixer, int dad, int num)
{
int i, j=0;
for (i=0; i<mmixer->chans; ++i)
if (i != dad && mmixer->lines[i].dst == dad)
{
if (num == j)
return i;
++j;
}
WARN("No src found for src %i from dest %i\n", num, dad);
return 0;
}
/* get the source number belonging to line */
static int getsrcfromline(mixer *mmixer, int line)
{
int i, j=0, dad = mmixer->lines[line].dst;
for (i=0; i<mmixer->chans; ++i)
if (i != dad && mmixer->lines[i].dst == dad)
{
if (line == i)
return j;
++j;
}
WARN("No src found for line %i with dad %i\n", line, dad);
return 0;
}
/* Get volume/muted/capture channel */
static DWORD MIX_GetControlDetails(UINT wDevID, LPMIXERCONTROLDETAILS mctrld, DWORD_PTR flags)
{
mixer *mmixer = MIX_GetMix(wDevID);
DWORD ctrl;
DWORD line;
control *ct;
if (!mctrld)
return MMSYSERR_INVALPARAM;
ctrl = mctrld->dwControlID;
line = ctrl/CONTROLSPERLINE;
if (mctrld->cbStruct != sizeof(*mctrld))
return MMSYSERR_INVALPARAM;
if (!mmixer)
return MMSYSERR_BADDEVICEID;
if (line >= mmixer->chans || !mmixer->controls[ctrl].enabled)
return MIXERR_INVALCONTROL;
ct = &mmixer->controls[ctrl];
flags &= MIXER_GETCONTROLDETAILSF_QUERYMASK;
switch (flags) {
case MIXER_GETCONTROLDETAILSF_VALUE:
TRACE("MIXER_GETCONTROLDETAILSF_VALUE (%d/%d)\n", ctrl, line);
switch (ct->c.dwControlType)
{
case MIXERCONTROL_CONTROLTYPE_VOLUME:
{
long min = 0, max = 0, vol = 0;
int chn;
LPMIXERCONTROLDETAILS_UNSIGNED mcdu;
snd_mixer_elem_t * elem = mmixer->lines[line].elem;
if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_UNSIGNED))
{
WARN("invalid parameter: cbDetails %d\n", mctrld->cbDetails);
return MMSYSERR_INVALPARAM;
}
TRACE("%s MIXERCONTROLDETAILS_UNSIGNED[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
mcdu = mctrld->paDetails;
if (mctrld->cChannels != 1 && mmixer->lines[line].chans != mctrld->cChannels)
{
WARN("Unsupported cChannels (%d instead of %d)\n", mctrld->cChannels, mmixer->lines[line].chans);
return MMSYSERR_INVALPARAM;
}
if (mmixer->lines[line].capt && snd_mixer_selem_has_capture_volume(elem)) {
snd_mixer_selem_get_capture_volume_range(elem, &min, &max);
for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
if (snd_mixer_selem_has_capture_channel(elem, chn))
{
snd_mixer_selem_get_capture_volume(elem, chn, &vol);
mcdu->dwValue = normalized(vol - min, max, 65535);
if (mctrld->cChannels == 1)
break;
++mcdu;
}
} else {
snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
if (snd_mixer_selem_has_playback_channel(elem, chn))
{
snd_mixer_selem_get_playback_volume(elem, chn, &vol);
mcdu->dwValue = normalized(vol - min, max, 65535);
if (mctrld->cChannels == 1)
break;
++mcdu;
}
}
return MMSYSERR_NOERROR;
}
case MIXERCONTROL_CONTROLTYPE_ONOFF:
case MIXERCONTROL_CONTROLTYPE_MUTE:
{
LPMIXERCONTROLDETAILS_BOOLEAN mcdb;
int chn, ival;
snd_mixer_elem_t * elem = mmixer->lines[line].elem;
if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_BOOLEAN))
{
WARN("invalid parameter: cbDetails %d\n", mctrld->cbDetails);
return MMSYSERR_INVALPARAM;
}
TRACE("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
mcdb = mctrld->paDetails;
if (line == 1)
for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
{
if (!snd_mixer_selem_has_capture_channel(elem, chn))
continue;
snd_mixer_selem_get_capture_switch(elem, chn, &ival);
break;
}
else
for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
{
if (!snd_mixer_selem_has_playback_channel(elem, chn))
continue;
snd_mixer_selem_get_playback_switch(elem, chn, &ival);
break;
}
if (chn > SND_MIXER_SCHN_LAST)
{
TRACE("can't find active channel\n");
return MMSYSERR_INVALPARAM; /* fixme: what's right error? */
}
mcdb->fValue = !ival;
TRACE("=> %s\n", mcdb->fValue ? "on" : "off");
return MMSYSERR_NOERROR;
}
case MIXERCONTROL_CONTROLTYPE_MIXER:
case MIXERCONTROL_CONTROLTYPE_MUX:
{
LPMIXERCONTROLDETAILS_BOOLEAN mcdb;
int x, i=0, ival = 0, chn;
if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_BOOLEAN))
{
WARN("invalid parameter: cbDetails %d\n", mctrld->cbDetails);
return MMSYSERR_INVALPARAM;
}
TRACE("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
mcdb = mctrld->paDetails;
for (x = 0; x<mmixer->chans; ++x)
if (line != x && mmixer->lines[x].dst == line)
{
ival = 0;
for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
{
if (!snd_mixer_selem_has_capture_channel(mmixer->lines[x].elem, chn))
continue;
snd_mixer_selem_get_capture_switch(mmixer->lines[x].elem, chn, &ival);
if (ival)
break;
}
if (i >= mctrld->u.cMultipleItems)
{
TRACE("overflow\n");
return MMSYSERR_INVALPARAM;
}
TRACE("fVal[%i] = %sselected\n", i, (!ival ? "un" : ""));
mcdb[i++].fValue = ival;
}
break;
}
default:
FIXME("Unhandled controltype %s\n", getControlType(ct->c.dwControlType));
return MMSYSERR_INVALPARAM;
}
return MMSYSERR_NOERROR;
case MIXER_GETCONTROLDETAILSF_LISTTEXT:
TRACE("MIXER_GETCONTROLDETAILSF_LISTTEXT (%d)\n", ctrl);
if (ct->c.dwControlType == MIXERCONTROL_CONTROLTYPE_MUX || ct->c.dwControlType == MIXERCONTROL_CONTROLTYPE_MIXER)
{
LPMIXERCONTROLDETAILS_LISTTEXTW mcdlt = mctrld->paDetails;
int i, j;
for (i = j = 0; j < mmixer->chans; ++j)
if (j != line && mmixer->lines[j].dst == line)
{
if (i > mctrld->u.cMultipleItems)
return MMSYSERR_INVALPARAM;
mcdlt->dwParam1 = j;
mcdlt->dwParam2 = mmixer->lines[j].component;
lstrcpynW(mcdlt->szName, mmixer->lines[j].name, sizeof(mcdlt->szName) / sizeof(WCHAR));
TRACE("Adding %i as %s\n", j, debugstr_w(mcdlt->szName));
++i; ++mcdlt;
}
if (i < mctrld->u.cMultipleItems)
return MMSYSERR_INVALPARAM;
return MMSYSERR_NOERROR;
}
FIXME ("Imagine this code being horribly broken and incomplete, introducing: reality\n");
return MMSYSERR_INVALPARAM;
default:
WARN("Unknown flag (%08lx)\n", flags);
return MMSYSERR_INVALPARAM;
}
}
/* Set volume/capture channel/muted for control */
static DWORD MIX_SetControlDetails(UINT wDevID, LPMIXERCONTROLDETAILS mctrld, DWORD_PTR flags)
{
mixer *mmixer = MIX_GetMix(wDevID);
DWORD ctrl, line, i;
control *ct;
snd_mixer_elem_t * elem;
if (!mctrld)
return MMSYSERR_INVALPARAM;
ctrl = mctrld->dwControlID;
line = ctrl/CONTROLSPERLINE;
if (mctrld->cbStruct != sizeof(*mctrld))
{
WARN("Invalid size of mctrld %d\n", mctrld->cbStruct);
return MMSYSERR_INVALPARAM;
}
if (!mmixer)
return MMSYSERR_BADDEVICEID;
if (line >= mmixer->chans)
{
WARN("Invalid line id: %d not in range of 0-%d\n", line, mmixer->chans-1);
return MMSYSERR_INVALPARAM;
}
if (!mmixer->controls[ctrl].enabled)
{
WARN("Control %d not enabled\n", ctrl);
return MIXERR_INVALCONTROL;
}
ct = &mmixer->controls[ctrl];
elem = mmixer->lines[line].elem;
flags &= MIXER_SETCONTROLDETAILSF_QUERYMASK;
switch (flags) {
case MIXER_SETCONTROLDETAILSF_VALUE:
TRACE("MIXER_SETCONTROLDETAILSF_VALUE (%d)\n", ctrl);
break;
default:
WARN("Unknown flag (%08lx)\n", flags);
return MMSYSERR_INVALPARAM;
}
switch (ct->c.dwControlType)
{
case MIXERCONTROL_CONTROLTYPE_VOLUME:
{
long min = 0, max = 0;
int chn;
LPMIXERCONTROLDETAILS_UNSIGNED mcdu;
snd_mixer_elem_t * elem = mmixer->lines[line].elem;
if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_UNSIGNED))
{
WARN("invalid parameter: cbDetails %d\n", mctrld->cbDetails);
return MMSYSERR_INVALPARAM;
}
if (mctrld->cChannels != 1 && mmixer->lines[line].chans != mctrld->cChannels)
{
WARN("Unsupported cChannels (%d instead of %d)\n", mctrld->cChannels, mmixer->lines[line].chans);
return MMSYSERR_INVALPARAM;
}
TRACE("%s MIXERCONTROLDETAILS_UNSIGNED[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
mcdu = mctrld->paDetails;
for (chn=0; chn<mctrld->cChannels;++chn)
{
TRACE("Chan %d value %d\n", chn, mcdu[chn].dwValue);
}
/* There isn't always a capture volume, so in that case change playback volume */
if (mmixer->lines[line].capt && snd_mixer_selem_has_capture_volume(elem))
{
snd_mixer_selem_get_capture_volume_range(elem, &min, &max);
for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
if (snd_mixer_selem_has_capture_channel(elem, chn))
{
snd_mixer_selem_set_capture_volume(elem, chn, min + normalized(mcdu->dwValue, 65535, max));
if (mctrld->cChannels != 1)
mcdu++;
}
}
else
{
snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
if (snd_mixer_selem_has_playback_channel(elem, chn))
{
snd_mixer_selem_set_playback_volume(elem, chn, min + normalized(mcdu->dwValue, 65535, max));
if (mctrld->cChannels != 1)
mcdu++;
}
}
break;
}
case MIXERCONTROL_CONTROLTYPE_MUTE:
case MIXERCONTROL_CONTROLTYPE_ONOFF:
{
LPMIXERCONTROLDETAILS_BOOLEAN mcdb;
if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_BOOLEAN))
{
WARN("invalid parameter: cbDetails %d\n", mctrld->cbDetails);
return MMSYSERR_INVALPARAM;
}
TRACE("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
mcdb = mctrld->paDetails;
if (line == 1) /* Mute/unmute capturing */
for (i = 0; i <= SND_MIXER_SCHN_LAST; ++i)
{
if (snd_mixer_selem_has_capture_channel(elem, i))
snd_mixer_selem_set_capture_switch(elem, i, !mcdb->fValue);
}
else
for (i = 0; i <= SND_MIXER_SCHN_LAST; ++i)
if (snd_mixer_selem_has_playback_channel(elem, i))
snd_mixer_selem_set_playback_switch(elem, i, !mcdb->fValue);
break;
}
case MIXERCONTROL_CONTROLTYPE_MIXER:
case MIXERCONTROL_CONTROLTYPE_MUX:
{
LPMIXERCONTROLDETAILS_BOOLEAN mcdb;
int x, i=0, chn;
int didone = 0, canone = (ct->c.dwControlType == MIXERCONTROL_CONTROLTYPE_MUX);
if (mctrld->cbDetails != sizeof(MIXERCONTROLDETAILS_BOOLEAN))
{
WARN("invalid parameter: cbDetails %d\n", mctrld->cbDetails);
return MMSYSERR_INVALPARAM;
}
TRACE("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(ct->c.dwControlType), mctrld->cChannels);
mcdb = mctrld->paDetails;
for (x=i=0; x < mmixer->chans; ++x)
if (line != x && mmixer->lines[x].dst == line)
{
TRACE("fVal[%i] (%s) = %i\n", i, debugstr_w(mmixer->lines[x].name), mcdb[i].fValue);
if (i >= mctrld->u.cMultipleItems)
{
TRACE("Too many items to fit, overflowing\n");
return MIXERR_INVALVALUE;
}
if (mcdb[i].fValue && canone && didone)
{
TRACE("Nice try, but it's not going to work\n");
elem_callback(mmixer->lines[1].elem, SND_CTL_EVENT_MASK_VALUE);
return MIXERR_INVALVALUE;
}
if (mcdb[i].fValue)
didone = 1;
++i;
}
if (canone && !didone)
{
TRACE("Nice try, this is not going to work either\n");
elem_callback(mmixer->lines[1].elem, SND_CTL_EVENT_MASK_VALUE);
return MIXERR_INVALVALUE;
}
for (x = i = 0; x<mmixer->chans; ++x)
if (line != x && mmixer->lines[x].dst == line)
{
if (mcdb[i].fValue)
for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
{
if (!snd_mixer_selem_has_capture_channel(mmixer->lines[x].elem, chn))
continue;
snd_mixer_selem_set_capture_switch(mmixer->lines[x].elem, chn, mcdb[i].fValue);
}
++i;
}
/* If it's a MUX, it means that only 1 channel can be selected
* and the other channels are unselected
*
* For MIXER multiple sources are allowed, so unselect here
*/
if (canone)
break;
for (x = i = 0; x<mmixer->chans; ++x)
if (line != x && mmixer->lines[x].dst == line)
{
if (!mcdb[i].fValue)
for (chn = 0; chn <= SND_MIXER_SCHN_LAST; ++chn)
{
if (!snd_mixer_selem_has_capture_channel(mmixer->lines[x].elem, chn))
continue;
snd_mixer_selem_set_capture_switch(mmixer->lines[x].elem, chn, mcdb[i].fValue);
}
++i;
}
break;
}
default:
FIXME("Unhandled type %s\n", getControlType(ct->c.dwControlType));
return MMSYSERR_INVALPARAM;
}
return MMSYSERR_NOERROR;
}
/* Here we give info over the source/dest line given by dwSource+dwDest or dwDest, respectively
* It is also possible that a line is found by componenttype or target type, latter is not implemented yet
* Most important values returned in struct:
* dwLineID
* sz(Short)Name
* line control count
* amount of channels
*/
static DWORD MIX_GetLineInfo(UINT wDevID, LPMIXERLINEW Ml, DWORD_PTR flags)
{
DWORD_PTR qf = flags & MIXER_GETLINEINFOF_QUERYMASK;
mixer *mmixer = MIX_GetMix(wDevID);
line *mline;
int idx, i;
if (!Ml)
{
WARN("No Ml\n");
return MMSYSERR_INVALPARAM;
}
if (!mmixer)
{
WARN("Device %u not found\n", wDevID);
return MMSYSERR_BADDEVICEID;
}
if (Ml->cbStruct != sizeof(*Ml))
{
WARN("invalid parameter: Ml->cbStruct = %d\n", Ml->cbStruct);
return MMSYSERR_INVALPARAM;
}
Ml->dwUser = 0;
Ml->fdwLine = MIXERLINE_LINEF_DISCONNECTED;
switch (qf)
{
case MIXER_GETLINEINFOF_COMPONENTTYPE:
{
Ml->dwLineID = 0xFFFF;
TRACE("Looking for componenttype %d/%x\n", Ml->dwComponentType, Ml->dwComponentType);
for (idx = 0; idx < mmixer->chans; ++idx)
if (mmixer->lines[idx].component == Ml->dwComponentType)
{
Ml->dwLineID = idx;
break;
}
if (Ml->dwLineID == 0xFFFF)
return MMSYSERR_KEYNOTFOUND;
/* Now that we have lineid, fallback to lineid*/
}
case MIXER_GETLINEINFOF_LINEID:
if (Ml->dwLineID >= mmixer->chans)
return MIXERR_INVALLINE;
TRACE("MIXER_GETLINEINFOF_LINEID %d\n", Ml->dwLineID);
Ml->dwDestination = mmixer->lines[Ml->dwLineID].dst;
if (Ml->dwDestination != Ml->dwLineID)
{
Ml->dwSource = getsrcfromline(mmixer, Ml->dwLineID);
Ml->cConnections = 1;
}
else
{
Ml->cConnections = getsrccntfromchan(mmixer, Ml->dwLineID);
Ml->dwSource = 0xFFFFFFFF;
}
TRACE("Connections %d, source %d\n", Ml->cConnections, Ml->dwSource);
break;
case MIXER_GETLINEINFOF_DESTINATION:
if (Ml->dwDestination >= mmixer->dests)
{
WARN("dest %d out of bounds\n", Ml->dwDestination);
return MIXERR_INVALLINE;
}
Ml->dwLineID = Ml->dwDestination;
Ml->cConnections = getsrccntfromchan(mmixer, Ml->dwLineID);
Ml->dwSource = 0xFFFFFFFF;
break;
case MIXER_GETLINEINFOF_SOURCE:
if (Ml->dwDestination >= mmixer->dests)
{
WARN("dest %d for source out of bounds\n", Ml->dwDestination);
return MIXERR_INVALLINE;
}
if (Ml->dwSource >= getsrccntfromchan(mmixer, Ml->dwDestination))
{
WARN("src %d out of bounds\n", Ml->dwSource);
return MIXERR_INVALLINE;
}
Ml->dwLineID = getsrclinefromchan(mmixer, Ml->dwDestination, Ml->dwSource);
Ml->cConnections = 1;
break;
case MIXER_GETLINEINFOF_TARGETTYPE:
FIXME("TODO: TARGETTYPE, stub\n");
return MMSYSERR_INVALPARAM;
default:
FIXME("Unknown query flag: %08lx\n", qf);
return MMSYSERR_INVALPARAM;
}
Ml->fdwLine &= ~MIXERLINE_LINEF_DISCONNECTED;
Ml->fdwLine |= MIXERLINE_LINEF_ACTIVE;
if (Ml->dwLineID >= mmixer->dests)
Ml->fdwLine |= MIXERLINE_LINEF_SOURCE;
mline = &mmixer->lines[Ml->dwLineID];
Ml->dwComponentType = mline->component;
Ml->cChannels = mmixer->lines[Ml->dwLineID].chans;
Ml->cControls = 0;
for (i=CONTROLSPERLINE*Ml->dwLineID;i<CONTROLSPERLINE*(Ml->dwLineID+1); ++i)
if (mmixer->controls[i].enabled)
++(Ml->cControls);
lstrcpynW(Ml->szShortName, mmixer->lines[Ml->dwLineID].name, sizeof(Ml->szShortName)/sizeof(WCHAR));
lstrcpynW(Ml->szName, mmixer->lines[Ml->dwLineID].name, sizeof(Ml->szName)/sizeof(WCHAR));
if (mline->capt)
Ml->Target.dwType = MIXERLINE_TARGETTYPE_WAVEIN;
else
Ml->Target.dwType = MIXERLINE_TARGETTYPE_WAVEOUT;
Ml->Target.dwDeviceID = 0xFFFFFFFF;
Ml->Target.wMid = WINE_MIXER_MANUF_ID;
Ml->Target.wPid = WINE_MIXER_PRODUCT_ID;
Ml->Target.vDriverVersion = WINE_MIXER_VERSION;
lstrcpynW(Ml->Target.szPname, mmixer->mixername, sizeof(Ml->Target.szPname)/sizeof(WCHAR));
return MMSYSERR_NOERROR;
}
/* Get the controls that belong to a certain line, either all or 1 */
static DWORD MIX_GetLineControls(UINT wDevID, LPMIXERLINECONTROLSW mlc, DWORD_PTR flags)
{
mixer *mmixer = MIX_GetMix(wDevID);
int i,j = 0;
DWORD ct;
if (!mlc || mlc->cbStruct != sizeof(*mlc))
{
WARN("Invalid mlc %p, cbStruct: %d\n", mlc, (!mlc ? -1 : mlc->cbStruct));
return MMSYSERR_INVALPARAM;
}
if (mlc->cbmxctrl != sizeof(MIXERCONTROLW))
{
WARN("cbmxctrl %d\n", mlc->cbmxctrl);
return MMSYSERR_INVALPARAM;
}
if (!mmixer)
return MMSYSERR_BADDEVICEID;
flags &= MIXER_GETLINECONTROLSF_QUERYMASK;
if (flags == MIXER_GETLINECONTROLSF_ONEBYID)
mlc->dwLineID = mlc->u.dwControlID / CONTROLSPERLINE;
if (mlc->dwLineID >= mmixer->chans)
{
TRACE("Invalid dwLineID %d\n", mlc->dwLineID);
return MIXERR_INVALLINE;
}
switch (flags)
{
case MIXER_GETLINECONTROLSF_ALL:
TRACE("line=%08x MIXER_GETLINECONTROLSF_ALL (%d)\n", mlc->dwLineID, mlc->cControls);
for (i = 0; i < CONTROLSPERLINE; ++i)
if (mmixer->controls[i+mlc->dwLineID * CONTROLSPERLINE].enabled)
{
memcpy(&mlc->pamxctrl[j], &mmixer->controls[i+mlc->dwLineID * CONTROLSPERLINE].c, sizeof(MIXERCONTROLW));
TRACE("Added %s (%s)\n", debugstr_w(mlc->pamxctrl[j].szShortName), debugstr_w(mlc->pamxctrl[j].szName));
++j;
if (j > mlc->cControls)
{
WARN("invalid parameter\n");
return MMSYSERR_INVALPARAM;
}
}
if (!j || mlc->cControls > j)
{
WARN("invalid parameter\n");
return MMSYSERR_INVALPARAM;
}
break;
case MIXER_GETLINECONTROLSF_ONEBYID:
TRACE("line=%08x MIXER_GETLINECONTROLSF_ONEBYID (%x)\n", mlc->dwLineID, mlc->u.dwControlID);
if (!mmixer->controls[mlc->u.dwControlID].enabled)
return MIXERR_INVALCONTROL;
mlc->pamxctrl[0] = mmixer->controls[mlc->u.dwControlID].c;
break;
case MIXER_GETLINECONTROLSF_ONEBYTYPE:
TRACE("line=%08x MIXER_GETLINECONTROLSF_ONEBYTYPE (%s)\n", mlc->dwLineID, getControlType(mlc->u.dwControlType));
ct = mlc->u.dwControlType & MIXERCONTROL_CT_CLASS_MASK;
for (i = 0; i <= CONTROLSPERLINE; ++i)
{
const int ofs = i+mlc->dwLineID*CONTROLSPERLINE;
if (i == CONTROLSPERLINE)
{
WARN("invalid parameter: control %s not found\n", getControlType(mlc->u.dwControlType));
return MIXERR_INVALCONTROL;
}
if (mmixer->controls[ofs].enabled && (mmixer->controls[ofs].c.dwControlType & MIXERCONTROL_CT_CLASS_MASK) == ct)
{
mlc->pamxctrl[0] = mmixer->controls[ofs].c;
break;
}
}
break;
default:
FIXME("Unknown flag %08lx\n", flags & MIXER_GETLINECONTROLSF_QUERYMASK);
return MMSYSERR_INVALPARAM;
}
return MMSYSERR_NOERROR;
}
/**************************************************************************
* mxdMessage (WINEALSA.3)
*/
DWORD WINAPI ALSA_mxdMessage(UINT wDevID, UINT wMsg, DWORD_PTR dwUser,
DWORD_PTR dwParam1, DWORD_PTR dwParam2)
{
DWORD ret;
TRACE("(%04X, %s, %08lX, %08lX, %08lX);\n", wDevID, getMessage(wMsg),
dwUser, dwParam1, dwParam2);
switch (wMsg)
{
case DRVM_INIT: ALSA_MixerInit(); ret = MMSYSERR_NOERROR; break;
case DRVM_EXIT: ALSA_MixerExit(); ret = MMSYSERR_NOERROR; break;
/* All taken care of by driver initialisation */
/* Unimplemented, and not needed */
case DRVM_ENABLE:
case DRVM_DISABLE:
ret = MMSYSERR_NOERROR; break;
case MXDM_OPEN:
ret = MIX_Open(wDevID, (LPMIXEROPENDESC) dwParam1, dwParam2); break;
case MXDM_CLOSE:
ret = MIX_Close(wDevID); break;
case MXDM_GETDEVCAPS:
ret = MIX_GetDevCaps(wDevID, (LPMIXERCAPS2W)dwParam1, dwParam2); break;
case MXDM_GETLINEINFO:
ret = MIX_GetLineInfo(wDevID, (LPMIXERLINEW)dwParam1, dwParam2); break;
case MXDM_GETLINECONTROLS:
ret = MIX_GetLineControls(wDevID, (LPMIXERLINECONTROLSW)dwParam1, dwParam2); break;
case MXDM_GETCONTROLDETAILS:
ret = MIX_GetControlDetails(wDevID, (LPMIXERCONTROLDETAILS)dwParam1, dwParam2); break;
case MXDM_SETCONTROLDETAILS:
ret = MIX_SetControlDetails(wDevID, (LPMIXERCONTROLDETAILS)dwParam1, dwParam2); break;
case MXDM_GETNUMDEVS:
ret = cards; break;
default:
WARN("unknown message %s!\n", getMessage(wMsg));
return MMSYSERR_NOTSUPPORTED;
}
TRACE("Returning %08X\n", ret);
return ret;
}
......@@ -37,9 +37,9 @@
#include "devpkey.h"
#include "dshow.h"
#include "dsound.h"
#include "endpointvolume.h"
#include "initguid.h"
#include "endpointvolume.h"
#include "audioclient.h"
#include "audiopolicy.h"
#include "dsdriver.h"
......@@ -158,7 +158,6 @@ static const IAudioStreamVolumeVtbl AudioStreamVolume_Vtbl;
static const IChannelAudioVolumeVtbl ChannelAudioVolume_Vtbl;
static const IAudioSessionManager2Vtbl AudioSessionManager2_Vtbl;
int wine_snd_pcm_recover(snd_pcm_t *pcm, int err, int silent);
static AudioSessionWrapper *AudioSessionWrapper_Create(ACImpl *client);
static inline ACImpl *impl_from_IAudioClient(IAudioClient *iface)
......@@ -237,6 +236,49 @@ int WINAPI AUDDRV_GetPriority(void)
return Priority_Neutral;
}
/**************************************************************************
* wine_snd_pcm_recover [internal]
*
* Code slightly modified from alsa-lib v1.0.23 snd_pcm_recover implementation.
* used to recover from XRUN errors (buffer underflow/overflow)
*/
static int wine_snd_pcm_recover(snd_pcm_t *pcm, int err, int silent)
{
if (err > 0)
err = -err;
if (err == -EINTR) /* nothing to do, continue */
return 0;
if (err == -EPIPE) {
const char *s;
if (snd_pcm_stream(pcm) == SND_PCM_STREAM_PLAYBACK)
s = "underrun";
else
s = "overrun";
if (!silent)
ERR("%s occurred\n", s);
err = snd_pcm_prepare(pcm);
if (err < 0) {
ERR("cannot recover from %s, prepare failed: %s\n", s, snd_strerror(err));
return err;
}
return 0;
}
if (err == -ESTRPIPE) {
while ((err = snd_pcm_resume(pcm)) == -EAGAIN)
/* wait until suspend flag is released */
poll(NULL, 0, 1000);
if (err < 0) {
err = snd_pcm_prepare(pcm);
if (err < 0) {
ERR("cannot recover from suspend, prepare failed: %s\n", snd_strerror(err));
return err;
}
}
return 0;
}
return err;
}
static BOOL alsa_try_open(const char *devnode, snd_pcm_stream_t stream)
{
snd_pcm_t *handle;
......
/*
* Sample Wine Driver for Advanced Linux Sound System (ALSA)
* Based on version <final> of the ALSA API
*
* Copyright 2002 Eric Pouech
* 2002 Marco Pietrobono
* 2003 Christian Costa : WaveIn support
* 2006-2007 Maarten Lankhorst
*
* 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
*/
/*======================================================================*
* Low level WAVE IN implementation *
*======================================================================*/
#include "config.h"
#include "wine/port.h"
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#include <errno.h>
#include <limits.h>
#include <fcntl.h>
#ifdef HAVE_SYS_IOCTL_H
# include <sys/ioctl.h>
#endif
#ifdef HAVE_SYS_MMAN_H
# include <sys/mman.h>
#endif
#include "windef.h"
#include "winbase.h"
#include "wingdi.h"
#include "winuser.h"
#include "winnls.h"
#include "mmddk.h"
#include "mmreg.h"
#include "dsound.h"
#include "dsdriver.h"
#include "ks.h"
#include "ksmedia.h"
#include "alsa.h"
#include "wine/library.h"
#include "wine/unicode.h"
#include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(wave);
WINE_WAVEDEV *WInDev;
DWORD ALSA_WidNumMallocedDevs;
DWORD ALSA_WidNumDevs;
/**************************************************************************
* widNotifyClient [internal]
*/
static void widNotifyClient(WINE_WAVEDEV* wwi, WORD wMsg, DWORD_PTR dwParam1, DWORD_PTR dwParam2)
{
TRACE("wMsg = 0x%04x dwParm1 = %04lX dwParam2 = %04lX\n", wMsg, dwParam1, dwParam2);
switch (wMsg) {
case WIM_OPEN:
case WIM_CLOSE:
case WIM_DATA:
DriverCallback(wwi->waveDesc.dwCallback, wwi->wFlags, (HDRVR)wwi->waveDesc.hWave,
wMsg, wwi->waveDesc.dwInstance, dwParam1, dwParam2);
break;
default:
FIXME("Unknown callback message %u\n", wMsg);
}
}
/**************************************************************************
* 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 >= ALSA_WidNumDevs) {
TRACE("Requested device %d, but only %d are known!\n", wDevID, ALSA_WidNumDevs);
return MMSYSERR_BADDEVICEID;
}
memcpy(lpCaps, &WInDev[wDevID].incaps, min(dwSize, sizeof(*lpCaps)));
return MMSYSERR_NOERROR;
}
/**************************************************************************
* widRecorder_ReadHeaders [internal]
*/
static void widRecorder_ReadHeaders(WINE_WAVEDEV * wwi)
{
enum win_wm_message tmp_msg;
DWORD_PTR tmp_param;
HANDLE tmp_ev;
WAVEHDR* lpWaveHdr;
while (ALSA_RetrieveRingMessage(&wwi->msgRing, &tmp_msg, &tmp_param, &tmp_ev)) {
if (tmp_msg == WINE_WM_HEADER) {
LPWAVEHDR* wh;
lpWaveHdr = (LPWAVEHDR)tmp_param;
lpWaveHdr->lpNext = 0;
if (wwi->lpQueuePtr == 0)
wwi->lpQueuePtr = lpWaveHdr;
else {
for (wh = &(wwi->lpQueuePtr); *wh; wh = &((*wh)->lpNext));
*wh = lpWaveHdr;
}
} else {
ERR("should only have headers left\n");
}
}
}
/**************************************************************************
* widRecorder [internal]
*/
static DWORD CALLBACK widRecorder(LPVOID pmt)
{
WORD uDevID = (DWORD_PTR)pmt;
WINE_WAVEDEV* wwi = &WInDev[uDevID];
WAVEHDR* lpWaveHdr;
DWORD dwSleepTime;
DWORD bytesRead;
enum win_wm_message msg;
DWORD_PTR param;
HANDLE ev;
wwi->state = WINE_WS_STOPPED;
InterlockedExchange((LONG*)&wwi->dwTotalRecorded, 0);
wwi->lpQueuePtr = NULL;
SetEvent(wwi->hStartUpEvent);
/* make sleep time to be # of ms to output a period */
dwSleepTime = (wwi->dwPeriodSize * 1000) / wwi->format.Format.nAvgBytesPerSec;
TRACE("sleeptime=%d ms, total buffer length=%d ms (%d bytes)\n", dwSleepTime, wwi->dwBufferSize * 1000 / wwi->format.Format.nAvgBytesPerSec, wwi->dwBufferSize);
for (;;) {
/* wait for dwSleepTime or an event in thread's queue */
if (wwi->lpQueuePtr != NULL && wwi->state == WINE_WS_PLAYING)
{
DWORD frames;
DWORD bytes;
DWORD read;
lpWaveHdr = wwi->lpQueuePtr;
/* read all the fragments accumulated so far */
frames = snd_pcm_avail_update(wwi->pcm);
bytes = snd_pcm_frames_to_bytes(wwi->pcm, frames);
TRACE("frames = %d bytes = %d state=%d\n", frames, bytes, snd_pcm_state(wwi->pcm));
if (snd_pcm_state(wwi->pcm) == SND_PCM_STATE_XRUN)
{
FIXME("Recovering from XRUN!\n");
snd_pcm_prepare(wwi->pcm);
frames = snd_pcm_avail_update(wwi->pcm);
bytes = snd_pcm_frames_to_bytes(wwi->pcm, frames);
snd_pcm_start(wwi->pcm);
snd_pcm_forward(wwi->pcm, frames - snd_pcm_bytes_to_frames(wwi->pcm, wwi->dwPeriodSize));
continue;
}
while (frames > 0 && wwi->lpQueuePtr)
{
TRACE("bytes = %d\n", bytes);
if (lpWaveHdr->dwBufferLength - lpWaveHdr->dwBytesRecorded < bytes)
{
bytes = lpWaveHdr->dwBufferLength - lpWaveHdr->dwBytesRecorded;
frames = snd_pcm_bytes_to_frames(wwi->pcm, bytes);
}
/* directly read fragment in wavehdr */
read = wwi->read(wwi->pcm, lpWaveHdr->lpData + lpWaveHdr->dwBytesRecorded, frames);
bytesRead = snd_pcm_frames_to_bytes(wwi->pcm, read);
TRACE("bytesRead=(%d(%d)/(%d)) -> (%d/%d)\n", bytesRead, read, frames, lpWaveHdr->dwBufferLength, lpWaveHdr->dwBufferLength - lpWaveHdr->dwBytesRecorded);
if (read != (DWORD) -1)
{
/* update number of bytes recorded in current buffer and by this device */
lpWaveHdr->dwBytesRecorded += bytesRead;
InterlockedExchangeAdd((LONG*)&wwi->dwTotalRecorded, bytesRead);
frames -= read;
bytes -= bytesRead;
/* buffer is full. notify client */
if (!snd_pcm_bytes_to_frames(wwi->pcm, 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;
wwi->lpQueuePtr = lpNext;
widNotifyClient(wwi, WIM_DATA, (DWORD_PTR)lpWaveHdr, 0);
lpWaveHdr = lpNext;
}
} else {
WARN("read(%s, %p, %d) failed (%d/%s)\n", wwi->pcmname,
lpWaveHdr->lpData + lpWaveHdr->dwBytesRecorded,
frames, frames, snd_strerror(read));
}
}
}
ALSA_WaitRingMessage(&wwi->msgRing, dwSleepTime);
while (ALSA_RetrieveRingMessage(&wwi->msgRing, &msg, &param, &ev))
{
TRACE("msg=%s param=0x%lx\n", ALSA_getCmdString(msg), param);
switch (msg) {
case WINE_WM_PAUSING:
wwi->state = WINE_WS_PAUSED;
/*FIXME("Device should stop recording\n");*/
SetEvent(ev);
break;
case WINE_WM_STARTING:
wwi->state = WINE_WS_PLAYING;
snd_pcm_start(wwi->pcm);
SetEvent(ev);
break;
case WINE_WM_HEADER:
lpWaveHdr = (LPWAVEHDR)param;
lpWaveHdr->lpNext = 0;
/* insert buffer at the end of queue */
{
LPWAVEHDR* wh;
for (wh = &(wwi->lpQueuePtr); *wh; wh = &((*wh)->lpNext));
*wh = lpWaveHdr;
}
break;
case WINE_WM_STOPPING:
if (wwi->state != WINE_WS_STOPPED)
{
snd_pcm_drain(wwi->pcm);
/* read any headers in queue */
widRecorder_ReadHeaders(wwi);
/* 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;
wwi->lpQueuePtr = lpNext;
widNotifyClient(wwi, WIM_DATA, (DWORD_PTR)lpWaveHdr, 0);
}
}
wwi->state = WINE_WS_STOPPED;
SetEvent(ev);
break;
case WINE_WM_RESETTING:
if (wwi->state != WINE_WS_STOPPED)
{
snd_pcm_drain(wwi->pcm);
}
wwi->state = WINE_WS_STOPPED;
wwi->dwTotalRecorded = 0;
/* read any headers in queue */
widRecorder_ReadHeaders(wwi);
/* return all buffers to the app */
while (wwi->lpQueuePtr) {
lpWaveHdr = wwi->lpQueuePtr;
TRACE("reset %p %p\n", lpWaveHdr, lpWaveHdr->lpNext);
wwi->lpQueuePtr = lpWaveHdr->lpNext;
lpWaveHdr->dwFlags &= ~WHDR_INQUEUE;
lpWaveHdr->dwFlags |= WHDR_DONE;
widNotifyClient(wwi, WIM_DATA, (DWORD_PTR)lpWaveHdr, 0);
}
SetEvent(ev);
break;
case WINE_WM_CLOSING:
wwi->hThread = 0;
wwi->state = WINE_WS_CLOSED;
SetEvent(ev);
ExitThread(0);
/* shouldn't go here */
default:
FIXME("unknown message %d\n", msg);
break;
}
}
}
ExitThread(0);
/* just for not generating compilation warnings... should never be executed */
return 0;
}
/**************************************************************************
* widOpen [internal]
*/
static DWORD widOpen(WORD wDevID, LPWAVEOPENDESC lpDesc, DWORD dwFlags)
{
WINE_WAVEDEV* wwi;
snd_pcm_hw_params_t * hw_params;
snd_pcm_sw_params_t * sw_params;
snd_pcm_access_t access;
snd_pcm_format_t format;
unsigned int rate;
unsigned int buffer_time = 500000;
unsigned int period_time = 10000;
snd_pcm_uframes_t buffer_size;
snd_pcm_uframes_t period_size;
int flags;
snd_pcm_t * pcm;
int err;
int dir;
DWORD ret;
/* JPW TODO - review this code */
TRACE("(%u, %p, %08X);\n", wDevID, lpDesc, dwFlags);
if (lpDesc == NULL) {
WARN("Invalid Parameter !\n");
return MMSYSERR_INVALPARAM;
}
if (wDevID >= ALSA_WidNumDevs) {
TRACE("Requested device %d, but only %d are known!\n", wDevID, ALSA_WidNumDevs);
return MMSYSERR_BADDEVICEID;
}
/* only PCM format is supported so far... */
if (!ALSA_supportedFormat(lpDesc->lpFormat)) {
WARN("Bad format: tag=%04X nChannels=%d nSamplesPerSec=%d !\n",
lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels,
lpDesc->lpFormat->nSamplesPerSec);
return WAVERR_BADFORMAT;
}
if (dwFlags & WAVE_FORMAT_QUERY) {
TRACE("Query format: tag=%04X nChannels=%d nSamplesPerSec=%d !\n",
lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels,
lpDesc->lpFormat->nSamplesPerSec);
return MMSYSERR_NOERROR;
}
wwi = &WInDev[wDevID];
if (wwi->pcm != NULL) {
WARN("already allocated\n");
return MMSYSERR_ALLOCATED;
}
flags = SND_PCM_NONBLOCK;
if ( (err=snd_pcm_open(&pcm, wwi->pcmname, SND_PCM_STREAM_CAPTURE, flags)) < 0 )
{
ERR("Error open: %s\n", snd_strerror(err));
return MMSYSERR_NOTENABLED;
}
wwi->wFlags = HIWORD(dwFlags & CALLBACK_TYPEMASK);
wwi->waveDesc = *lpDesc;
ALSA_copyFormat(lpDesc->lpFormat, &wwi->format);
if (wwi->format.Format.wBitsPerSample == 0) {
WARN("Resetting zeroed wBitsPerSample\n");
wwi->format.Format.wBitsPerSample = 8 *
(wwi->format.Format.nAvgBytesPerSec /
wwi->format.Format.nSamplesPerSec) /
wwi->format.Format.nChannels;
}
hw_params = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, snd_pcm_hw_params_sizeof() );
sw_params = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, snd_pcm_sw_params_sizeof() );
if (!hw_params || !sw_params)
{
ret = MMSYSERR_NOMEM;
goto error;
}
snd_pcm_hw_params_any(pcm, hw_params);
#define EXIT_ON_ERROR(f,e,txt) do \
{ \
int err; \
if ( (err = (f) ) < 0) \
{ \
WARN(txt ": %s\n", snd_strerror(err)); \
ret = (e); \
goto error; \
} \
} while(0)
access = SND_PCM_ACCESS_MMAP_INTERLEAVED;
if ( ( err = snd_pcm_hw_params_set_access(pcm, hw_params, access ) ) < 0) {
WARN("mmap not available. switching to standard write.\n");
access = SND_PCM_ACCESS_RW_INTERLEAVED;
EXIT_ON_ERROR( snd_pcm_hw_params_set_access(pcm, hw_params, access ), MMSYSERR_INVALPARAM, "unable to set access for playback");
wwi->read = snd_pcm_readi;
}
else
wwi->read = snd_pcm_mmap_readi;
EXIT_ON_ERROR( snd_pcm_hw_params_set_channels(pcm, hw_params, wwi->format.Format.nChannels), WAVERR_BADFORMAT, "unable to set required channels");
if ((wwi->format.Format.wFormatTag == WAVE_FORMAT_PCM) ||
((wwi->format.Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE) &&
IsEqualGUID(&wwi->format.SubFormat, &KSDATAFORMAT_SUBTYPE_PCM))) {
format = (wwi->format.Format.wBitsPerSample == 8) ? SND_PCM_FORMAT_U8 :
(wwi->format.Format.wBitsPerSample == 16) ? SND_PCM_FORMAT_S16_LE :
(wwi->format.Format.wBitsPerSample == 24) ? SND_PCM_FORMAT_S24_3LE :
(wwi->format.Format.wBitsPerSample == 32) ? SND_PCM_FORMAT_S32_LE : -1;
} else if ((wwi->format.Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE) &&
IsEqualGUID(&wwi->format.SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)){
format = (wwi->format.Format.wBitsPerSample == 32) ? SND_PCM_FORMAT_FLOAT_LE : -1;
} else if (wwi->format.Format.wFormatTag == WAVE_FORMAT_MULAW) {
FIXME("unimplemented format: WAVE_FORMAT_MULAW\n");
ret = WAVERR_BADFORMAT;
goto error;
} else if (wwi->format.Format.wFormatTag == WAVE_FORMAT_ALAW) {
FIXME("unimplemented format: WAVE_FORMAT_ALAW\n");
ret = WAVERR_BADFORMAT;
goto error;
} else if (wwi->format.Format.wFormatTag == WAVE_FORMAT_ADPCM) {
FIXME("unimplemented format: WAVE_FORMAT_ADPCM\n");
ret = WAVERR_BADFORMAT;
goto error;
} else {
ERR("invalid format: %0x04x\n", wwi->format.Format.wFormatTag);
ret = WAVERR_BADFORMAT;
goto error;
}
EXIT_ON_ERROR( snd_pcm_hw_params_set_format(pcm, hw_params, format), WAVERR_BADFORMAT, "unable to set required format");
rate = wwi->format.Format.nSamplesPerSec;
dir = 0;
err = snd_pcm_hw_params_set_rate_near(pcm, hw_params, &rate, &dir);
if (err < 0) {
WARN("Rate %d Hz not available for playback: %s\n", wwi->format.Format.nSamplesPerSec, snd_strerror(rate));
ret = WAVERR_BADFORMAT;
goto error;
}
if (!ALSA_NearMatch(rate, wwi->format.Format.nSamplesPerSec)) {
WARN("Rate doesn't match (requested %d Hz, got %d Hz)\n", wwi->format.Format.nSamplesPerSec, rate);
ret = WAVERR_BADFORMAT;
goto error;
}
dir=0;
EXIT_ON_ERROR( snd_pcm_hw_params_set_buffer_time_near(pcm, hw_params, &buffer_time, &dir), MMSYSERR_INVALPARAM, "unable to set buffer time");
dir=0;
EXIT_ON_ERROR( snd_pcm_hw_params_set_period_time_near(pcm, hw_params, &period_time, &dir), MMSYSERR_INVALPARAM, "unable to set period time");
EXIT_ON_ERROR( snd_pcm_hw_params(pcm, hw_params), MMSYSERR_INVALPARAM, "unable to set hw params for playback");
dir=0;
err = snd_pcm_hw_params_get_period_size(hw_params, &period_size, &dir);
err = snd_pcm_hw_params_get_buffer_size(hw_params, &buffer_size);
snd_pcm_sw_params_current(pcm, sw_params);
EXIT_ON_ERROR( snd_pcm_sw_params_set_start_threshold(pcm, sw_params, 1), MMSYSERR_ERROR, "unable to set start threshold");
EXIT_ON_ERROR( snd_pcm_sw_params_set_silence_size(pcm, sw_params, 0), MMSYSERR_ERROR, "unable to set silence size");
EXIT_ON_ERROR( snd_pcm_sw_params_set_avail_min(pcm, sw_params, period_size), MMSYSERR_ERROR, "unable to set avail min");
EXIT_ON_ERROR( snd_pcm_sw_params_set_silence_threshold(pcm, sw_params, 0), MMSYSERR_ERROR, "unable to set silence threshold");
EXIT_ON_ERROR( snd_pcm_sw_params(pcm, sw_params), MMSYSERR_ERROR, "unable to set sw params for playback");
#undef EXIT_ON_ERROR
snd_pcm_prepare(pcm);
if (TRACE_ON(wave))
ALSA_TraceParameters(hw_params, sw_params, FALSE);
/* now, we can save all required data for later use... */
wwi->dwBufferSize = snd_pcm_frames_to_bytes(pcm, buffer_size);
wwi->lpQueuePtr = wwi->lpPlayPtr = wwi->lpLoopPtr = NULL;
ALSA_InitRingMessage(&wwi->msgRing);
wwi->dwPeriodSize = snd_pcm_frames_to_bytes(pcm, period_size);
TRACE("dwPeriodSize=%u\n", wwi->dwPeriodSize);
TRACE("wBitsPerSample=%u, nAvgBytesPerSec=%u, nSamplesPerSec=%u, nChannels=%u nBlockAlign=%u!\n",
wwi->format.Format.wBitsPerSample, wwi->format.Format.nAvgBytesPerSec,
wwi->format.Format.nSamplesPerSec, wwi->format.Format.nChannels,
wwi->format.Format.nBlockAlign);
wwi->hStartUpEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
wwi->hThread = CreateThread(NULL, 0, widRecorder, (LPVOID)(DWORD_PTR)wDevID, 0, &(wwi->dwThreadID));
if (!wwi->hThread) {
ERR("Thread creation for the widRecorder failed!\n");
CloseHandle(wwi->hStartUpEvent);
ret = MMSYSERR_NOMEM;
goto error;
}
SetThreadPriority(wwi->hThread, THREAD_PRIORITY_TIME_CRITICAL);
WaitForSingleObject(wwi->hStartUpEvent, INFINITE);
CloseHandle(wwi->hStartUpEvent);
wwi->hStartUpEvent = NULL;
HeapFree( GetProcessHeap(), 0, sw_params );
wwi->hw_params = hw_params;
wwi->pcm = pcm;
widNotifyClient(wwi, WIM_OPEN, 0L, 0L);
return MMSYSERR_NOERROR;
error:
snd_pcm_close(pcm);
HeapFree( GetProcessHeap(), 0, hw_params );
HeapFree( GetProcessHeap(), 0, sw_params );
if (wwi->msgRing.ring_buffer_size > 0)
ALSA_DestroyRingMessage(&wwi->msgRing);
return ret;
}
/**************************************************************************
* widClose [internal]
*/
static DWORD widClose(WORD wDevID)
{
WINE_WAVEDEV* wwi;
TRACE("(%u);\n", wDevID);
if (wDevID >= ALSA_WidNumDevs) {
TRACE("Requested device %d, but only %d are known!\n", wDevID, ALSA_WidNumDevs);
return MMSYSERR_BADDEVICEID;
}
wwi = &WInDev[wDevID];
if (wwi->pcm == NULL) {
WARN("Requested to close already closed device %d!\n", wDevID);
return MMSYSERR_BADDEVICEID;
}
if (wwi->lpQueuePtr) {
WARN("buffers still playing !\n");
return WAVERR_STILLPLAYING;
} else {
if (wwi->hThread) {
ALSA_AddRingMessage(&wwi->msgRing, WINE_WM_CLOSING, 0, TRUE);
}
ALSA_DestroyRingMessage(&wwi->msgRing);
HeapFree( GetProcessHeap(), 0, wwi->hw_params );
wwi->hw_params = NULL;
snd_pcm_close(wwi->pcm);
wwi->pcm = NULL;
widNotifyClient(wwi, WIM_CLOSE, 0L, 0L);
}
return MMSYSERR_NOERROR;
}
/**************************************************************************
* widAddBuffer [internal]
*
*/
static DWORD widAddBuffer(WORD wDevID, LPWAVEHDR lpWaveHdr, DWORD dwSize)
{
TRACE("(%u, %p, %08X);\n", wDevID, lpWaveHdr, dwSize);
/* first, do the sanity checks... */
if (wDevID >= ALSA_WidNumDevs) {
TRACE("Requested device %d, but only %d are known!\n", wDevID, ALSA_WidNumDevs);
return MMSYSERR_BADDEVICEID;
}
if (WInDev[wDevID].pcm == NULL) {
WARN("Requested to add buffer to already closed device %d!\n", wDevID);
return MMSYSERR_BADDEVICEID;
}
if (lpWaveHdr->lpData == NULL || !(lpWaveHdr->dwFlags & WHDR_PREPARED))
return WAVERR_UNPREPARED;
if (lpWaveHdr->dwFlags & WHDR_INQUEUE)
return WAVERR_STILLPLAYING;
lpWaveHdr->dwFlags &= ~WHDR_DONE;
lpWaveHdr->dwFlags |= WHDR_INQUEUE;
lpWaveHdr->dwBytesRecorded = 0;
lpWaveHdr->lpNext = 0;
ALSA_AddRingMessage(&WInDev[wDevID].msgRing, WINE_WM_HEADER, (DWORD_PTR)lpWaveHdr, FALSE);
return MMSYSERR_NOERROR;
}
/**************************************************************************
* widStart [internal]
*
*/
static DWORD widStart(WORD wDevID, LPWAVEHDR lpWaveHdr, DWORD dwSize)
{
TRACE("(%u, %p, %08X);\n", wDevID, lpWaveHdr, dwSize);
/* first, do the sanity checks... */
if (wDevID >= ALSA_WidNumDevs) {
TRACE("Requested device %d, but only %d are known!\n", wDevID, ALSA_WidNumDevs);
return MMSYSERR_BADDEVICEID;
}
if (WInDev[wDevID].pcm == NULL) {
WARN("Requested to start closed device %d!\n", wDevID);
return MMSYSERR_BADDEVICEID;
}
ALSA_AddRingMessage(&WInDev[wDevID].msgRing, WINE_WM_STARTING, 0, TRUE);
return MMSYSERR_NOERROR;
}
/**************************************************************************
* widStop [internal]
*
*/
static DWORD widStop(WORD wDevID, LPWAVEHDR lpWaveHdr, DWORD dwSize)
{
TRACE("(%u, %p, %08X);\n", wDevID, lpWaveHdr, dwSize);
/* first, do the sanity checks... */
if (wDevID >= ALSA_WidNumDevs) {
TRACE("Requested device %d, but only %d are known!\n", wDevID, ALSA_WidNumDevs);
return MMSYSERR_BADDEVICEID;
}
if (WInDev[wDevID].pcm == NULL) {
WARN("Requested to stop closed device %d!\n", wDevID);
return MMSYSERR_BADDEVICEID;
}
ALSA_AddRingMessage(&WInDev[wDevID].msgRing, WINE_WM_STOPPING, 0, TRUE);
return MMSYSERR_NOERROR;
}
/**************************************************************************
* widReset [internal]
*/
static DWORD widReset(WORD wDevID)
{
TRACE("(%u);\n", wDevID);
if (wDevID >= ALSA_WidNumDevs) {
TRACE("Requested device %d, but only %d are known!\n", wDevID, ALSA_WidNumDevs);
return MMSYSERR_BADDEVICEID;
}
if (WInDev[wDevID].pcm == NULL) {
WARN("Requested to reset closed device %d!\n", wDevID);
return MMSYSERR_BADDEVICEID;
}
ALSA_AddRingMessage(&WInDev[wDevID].msgRing, WINE_WM_RESETTING, 0, TRUE);
return MMSYSERR_NOERROR;
}
/**************************************************************************
* widGetPosition [internal]
*/
static DWORD widGetPosition(WORD wDevID, LPMMTIME lpTime, DWORD uSize)
{
WINE_WAVEDEV* wwi;
TRACE("(%u, %p, %u);\n", wDevID, lpTime, uSize);
if (wDevID >= ALSA_WidNumDevs) {
TRACE("Requested device %d, but only %d are known!\n", wDevID, ALSA_WidNumDevs);
return MMSYSERR_BADDEVICEID;
}
if (WInDev[wDevID].state == WINE_WS_CLOSED) {
WARN("Requested position of closed device %d!\n", wDevID);
return MMSYSERR_BADDEVICEID;
}
if (lpTime == NULL) {
WARN("invalid parameter: lpTime = NULL\n");
return MMSYSERR_INVALPARAM;
}
wwi = &WInDev[wDevID];
return ALSA_bytes_to_mmtime(lpTime, wwi->dwTotalRecorded, &wwi->format);
}
/**************************************************************************
* widGetNumDevs [internal]
*/
static DWORD widGetNumDevs(void)
{
return ALSA_WidNumDevs;
}
/**************************************************************************
* widDevInterfaceSize [internal]
*/
static DWORD widDevInterfaceSize(UINT wDevID, LPDWORD dwParam1)
{
TRACE("(%u, %p)\n", wDevID, dwParam1);
*dwParam1 = MultiByteToWideChar(CP_UNIXCP, 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_UNIXCP, 0, WInDev[wDevID].interface_name, -1,
NULL, 0 ) * sizeof(WCHAR))
{
MultiByteToWideChar(CP_UNIXCP, 0, WInDev[wDevID].interface_name, -1,
dwParam1, dwParam2 / sizeof(WCHAR));
return MMSYSERR_NOERROR;
}
return MMSYSERR_INVALPARAM;
}
/**************************************************************************
* widMessage (WINEALSA.@)
*/
DWORD WINAPI ALSA_widMessage(UINT wDevID, UINT wMsg, DWORD_PTR dwUser,
DWORD_PTR dwParam1, DWORD_PTR dwParam2)
{
TRACE("(%u, %s, %08lX, %08lX, %08lX);\n",
wDevID, ALSA_getMessage(wMsg), dwUser, dwParam1, dwParam2);
switch (wMsg) {
case DRVM_INIT:
ALSA_WaveInit();
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 MMSYSERR_NOTSUPPORTED;
case WIDM_UNPREPARE: return MMSYSERR_NOTSUPPORTED;
case WIDM_GETDEVCAPS: return widGetDevCaps (wDevID, (LPWAVEINCAPSW)dwParam1, dwParam2);
case WIDM_GETNUMDEVS: return widGetNumDevs ();
case WIDM_GETPOS: return widGetPosition (wDevID, (LPMMTIME)dwParam1, dwParam2);
case WIDM_RESET: return widReset (wDevID);
case WIDM_START: return widStart (wDevID, (LPWAVEHDR)dwParam1, dwParam2);
case WIDM_STOP: return widStop (wDevID, (LPWAVEHDR)dwParam1, dwParam2);
case DRV_QUERYDEVICEINTERFACESIZE: return widDevInterfaceSize (wDevID, (LPDWORD)dwParam1);
case DRV_QUERYDEVICEINTERFACE: return widDevInterface (wDevID, (PWCHAR)dwParam1, dwParam2);
case DRV_QUERYDSOUNDIFACE: return widDsCreate (wDevID, (PIDSCDRIVER*)dwParam1);
case DRV_QUERYDSOUNDDESC: return widDsDesc (wDevID, (PDSDRIVERDESC)dwParam1);
default:
FIXME("unknown message %d!\n", wMsg);
}
return MMSYSERR_NOTSUPPORTED;
}
/*
* Sample Wine Driver for Advanced Linux Sound System (ALSA)
* Based on version <final> of the ALSA API
*
* This file performs the initialisation and scanning of the sound subsystem.
*
* Copyright 2002 Eric Pouech
* 2002 Marco Pietrobono
* 2003 Christian Costa : WaveIn support
* 2006-2007 Maarten Lankhorst
*
* 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
*/
#include "config.h"
#include "wine/port.h"
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#include <errno.h>
#include <limits.h>
#include <fcntl.h>
#ifdef HAVE_SYS_IOCTL_H
# include <sys/ioctl.h>
#endif
#ifdef HAVE_SYS_MMAN_H
# include <sys/mman.h>
#endif
#include "windef.h"
#include "winbase.h"
#include "wingdi.h"
#include "winerror.h"
#include "winuser.h"
#include "winnls.h"
#include "winreg.h"
#include "mmddk.h"
#include "mmreg.h"
#include "dsound.h"
#include "dsdriver.h"
#include "alsa.h"
#include "wine/library.h"
#include "wine/unicode.h"
#include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(wave);
/*----------------------------------------------------------------------------
** ALSA_TestDeviceForWine
**
** Test to see if a given device is sufficient for Wine.
*/
static int ALSA_TestDeviceForWine(int card, int device, snd_pcm_stream_t streamtype)
{
snd_pcm_t *pcm = NULL;
char pcmname[256];
int retcode;
snd_pcm_hw_params_t *hwparams;
const char *reason = NULL;
unsigned int rrate;
hwparams = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, snd_pcm_hw_params_sizeof() );
/* Note that the plug: device masks out a lot of info, we want to avoid that */
sprintf(pcmname, "hw:%d,%d", card, device);
retcode = snd_pcm_open(&pcm, pcmname, streamtype, SND_PCM_NONBLOCK);
if (retcode < 0)
{
/* Note that a busy device isn't automatically disqualified */
if (retcode == (-1 * EBUSY))
retcode = 0;
goto exit;
}
retcode = snd_pcm_hw_params_any(pcm, hwparams);
if (retcode < 0)
{
reason = "Could not retrieve hw_params";
goto exit;
}
/* set the count of channels */
retcode = snd_pcm_hw_params_set_channels(pcm, hwparams, 2);
if (retcode < 0)
{
retcode = snd_pcm_hw_params_set_channels(pcm, hwparams, 1); /* If we can't open stereo, try mono; this is vital for snd_usb_audio microphones */
}
if (retcode < 0)
{
reason = "Could not set channels";
goto exit;
}
rrate = 44100;
retcode = snd_pcm_hw_params_set_rate_near(pcm, hwparams, &rrate, 0);
if (retcode < 0)
{
reason = "Could not set rate";
goto exit;
}
if (rrate == 0)
{
reason = "Rate came back as 0";
goto exit;
}
/* write the parameters to device */
retcode = snd_pcm_hw_params(pcm, hwparams);
if (retcode < 0)
{
reason = "Could not set hwparams";
goto exit;
}
retcode = 0;
exit:
if (pcm)
snd_pcm_close(pcm);
HeapFree( GetProcessHeap(), 0, hwparams );
if (retcode != 0 && retcode != (-1 * ENOENT))
TRACE("Discarding card %d/device %d: %s [%d(%s)]\n", card, device, reason, retcode, snd_strerror(retcode));
return retcode;
}
/*----------------------------------------------------------------------------
** ALSA_RegGetString
** Retrieve a string from a registry key
*/
static int ALSA_RegGetString(HKEY key, const char *value, char **bufp)
{
DWORD rc;
DWORD type;
DWORD bufsize;
*bufp = NULL;
rc = RegQueryValueExA(key, value, NULL, &type, NULL, &bufsize);
if (rc != ERROR_SUCCESS)
return(rc);
if (type != REG_SZ)
return 1;
*bufp = HeapAlloc(GetProcessHeap(), 0, bufsize);
if (! *bufp)
return 1;
rc = RegQueryValueExA(key, value, NULL, NULL, (LPBYTE)*bufp, &bufsize);
return rc;
}
/*----------------------------------------------------------------------------
** ALSA_RegGetBoolean
** Get a string and interpret it as a boolean
*/
/* Possible truths:
Y(es), T(rue), 1, E(nabled) */
#define IS_OPTION_TRUE(ch) ((ch) == 'y' || (ch) == 'Y' || (ch) == 't' || (ch) == 'T' || (ch) == '1' || (ch) == 'e' || (ch) == 'E')
static int ALSA_RegGetBoolean(HKEY key, const char *value, BOOL *answer)
{
DWORD rc;
char *buf = NULL;
rc = ALSA_RegGetString(key, value, &buf);
if (buf)
{
*answer = FALSE;
if (IS_OPTION_TRUE(*buf))
*answer = TRUE;
HeapFree(GetProcessHeap(), 0, buf);
}
return rc;
}
/*----------------------------------------------------------------------------
** ALSA_RegGetInt
** Get a string and interpret it as a DWORD
*/
static int ALSA_RegGetInt(HKEY key, const char *value, DWORD *answer)
{
DWORD rc;
char *buf = NULL;
rc = ALSA_RegGetString(key, value, &buf);
if (buf)
{
*answer = atoi(buf);
HeapFree(GetProcessHeap(), 0, buf);
}
return rc;
}
/* return a string duplicated on the win32 process heap, free with HeapFree */
static char* ALSA_strdup(const char *s) {
char *result = HeapAlloc(GetProcessHeap(), 0, strlen(s)+1);
if (!result)
return NULL;
strcpy(result, s);
return result;
}
#define ALSA_RETURN_ONFAIL(mycall) \
{ \
int rc; \
{rc = mycall;} \
if ((rc) < 0) \
{ \
ERR("%s failed: %s(%d)\n", #mycall, snd_strerror(rc), rc); \
return(rc); \
} \
}
/*----------------------------------------------------------------------------
** ALSA_ComputeCaps
**
** Given an ALSA PCM, figure out our HW CAPS structure info.
** ctl can be null, pcm is required, as is all output parms.
**
*/
static int ALSA_ComputeCaps(snd_ctl_t *ctl, snd_pcm_t *pcm,
WORD *channels, DWORD *flags, DWORD *formats, DWORD *supports)
{
snd_pcm_hw_params_t *hw_params;
snd_pcm_format_mask_t *fmask;
snd_pcm_access_mask_t *acmask;
unsigned int ratemin = 0;
unsigned int ratemax = 0;
unsigned int chmin = 0;
unsigned int chmax = 0;
int rc, dir = 0;
hw_params = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, snd_pcm_hw_params_sizeof() );
fmask = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, snd_pcm_format_mask_sizeof() );
acmask = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, snd_pcm_access_mask_sizeof() );
if ((rc = snd_pcm_hw_params_any(pcm, hw_params)) < 0) goto done;
snd_pcm_hw_params_get_format_mask(hw_params, fmask);
if ((rc = snd_pcm_hw_params_get_access_mask(hw_params, acmask)) < 0) goto done;
if ((rc = snd_pcm_hw_params_get_rate_min(hw_params, &ratemin, &dir)) < 0) goto done;
if ((rc = snd_pcm_hw_params_get_rate_max(hw_params, &ratemax, &dir)) < 0) goto done;
if ((rc = snd_pcm_hw_params_get_channels_min(hw_params, &chmin)) < 0) goto done;
if ((rc = snd_pcm_hw_params_get_channels_max(hw_params, &chmax)) < 0) goto done;
#define X(r,v) \
if ( (r) >= ratemin && ( (r) <= ratemax || ratemax == -1) ) \
{ \
if (snd_pcm_format_mask_test( fmask, SND_PCM_FORMAT_U8)) \
{ \
if (chmin <= 1 && 1 <= chmax) \
*formats |= WAVE_FORMAT_##v##M08; \
if (chmin <= 2 && 2 <= chmax) \
*formats |= WAVE_FORMAT_##v##S08; \
} \
if (snd_pcm_format_mask_test( fmask, SND_PCM_FORMAT_S16_LE)) \
{ \
if (chmin <= 1 && 1 <= chmax) \
*formats |= WAVE_FORMAT_##v##M16; \
if (chmin <= 2 && 2 <= chmax) \
*formats |= WAVE_FORMAT_##v##S16; \
} \
}
X(11025,1);
X(22050,2);
X(44100,4);
X(48000,48);
X(96000,96);
#undef X
if (chmin > 1)
FIXME("Device has a minimum of %d channels\n", chmin);
*channels = chmax;
/* FIXME: is sample accurate always true ?
** Can we do WAVECAPS_PITCH, WAVECAPS_SYNC, or WAVECAPS_PLAYBACKRATE? */
*supports |= WAVECAPS_SAMPLEACCURATE;
*supports |= WAVECAPS_DIRECTSOUND;
/* check for volume control support */
if (ctl) {
if (snd_ctl_name(ctl))
{
snd_hctl_t *hctl;
if (snd_hctl_open(&hctl, snd_ctl_name(ctl), 0) >= 0)
{
snd_hctl_load(hctl);
if (!ALSA_CheckSetVolume( hctl, NULL, NULL, NULL, NULL, NULL, NULL, NULL ))
{
*supports |= WAVECAPS_VOLUME;
if (chmin <= 2 && 2 <= chmax)
*supports |= WAVECAPS_LRVOLUME;
}
snd_hctl_free(hctl);
snd_hctl_close(hctl);
}
}
}
*flags = DSCAPS_CERTIFIED | DSCAPS_CONTINUOUSRATE;
*flags |= DSCAPS_SECONDARYMONO | DSCAPS_SECONDARYSTEREO;
*flags |= DSCAPS_SECONDARY8BIT | DSCAPS_SECONDARY16BIT;
if (*formats & (WAVE_FORMAT_1M08 | WAVE_FORMAT_2M08 |
WAVE_FORMAT_4M08 | WAVE_FORMAT_48M08 |
WAVE_FORMAT_96M08 | WAVE_FORMAT_1M16 |
WAVE_FORMAT_2M16 | WAVE_FORMAT_4M16 |
WAVE_FORMAT_48M16 | WAVE_FORMAT_96M16) )
*flags |= DSCAPS_PRIMARYMONO;
if (*formats & (WAVE_FORMAT_1S08 | WAVE_FORMAT_2S08 |
WAVE_FORMAT_4S08 | WAVE_FORMAT_48S08 |
WAVE_FORMAT_96S08 | WAVE_FORMAT_1S16 |
WAVE_FORMAT_2S16 | WAVE_FORMAT_4S16 |
WAVE_FORMAT_48S16 | WAVE_FORMAT_96S16) )
*flags |= DSCAPS_PRIMARYSTEREO;
if (*formats & (WAVE_FORMAT_1M08 | WAVE_FORMAT_2M08 |
WAVE_FORMAT_4M08 | WAVE_FORMAT_48M08 |
WAVE_FORMAT_96M08 | WAVE_FORMAT_1S08 |
WAVE_FORMAT_2S08 | WAVE_FORMAT_4S08 |
WAVE_FORMAT_48S08 | WAVE_FORMAT_96S08) )
*flags |= DSCAPS_PRIMARY8BIT;
if (*formats & (WAVE_FORMAT_1M16 | WAVE_FORMAT_2M16 |
WAVE_FORMAT_4M16 | WAVE_FORMAT_48M16 |
WAVE_FORMAT_96M16 | WAVE_FORMAT_1S16 |
WAVE_FORMAT_2S16 | WAVE_FORMAT_4S16 |
WAVE_FORMAT_48S16 | WAVE_FORMAT_96S16) )
*flags |= DSCAPS_PRIMARY16BIT;
rc = 0;
done:
if (rc < 0) ERR("failed: %s(%d)\n", snd_strerror(rc), rc);
HeapFree( GetProcessHeap(), 0, hw_params );
HeapFree( GetProcessHeap(), 0, fmask );
HeapFree( GetProcessHeap(), 0, acmask );
return rc;
}
/*----------------------------------------------------------------------------
** ALSA_AddCommonDevice
**
** Perform Alsa initialization common to both capture and playback
**
** Side Effect: ww->pcname and ww->ctlname may need to be freed.
**
** Note: this was originally coded by using snd_pcm_name(pcm), until
** I discovered that with at least one version of alsa lib,
** the use of a pcm named default:0 would cause snd_pcm_name() to fail.
** So passing the name in is logically extraneous. Sigh.
*/
static int ALSA_AddCommonDevice(snd_ctl_t *ctl, snd_pcm_t *pcm, const char *pcmname, WINE_WAVEDEV *ww)
{
snd_pcm_info_t *infop;
int rc;
infop = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, snd_pcm_info_sizeof() );
if ((rc = snd_pcm_info(pcm, infop)) < 0)
{
HeapFree( GetProcessHeap(), 0, infop );
return rc;
}
if (pcm && pcmname)
ww->pcmname = ALSA_strdup(pcmname);
else
{
HeapFree( GetProcessHeap(), 0, infop );
return -1;
}
if (ctl && snd_ctl_name(ctl))
ww->ctlname = ALSA_strdup(snd_ctl_name(ctl));
strcpy(ww->interface_name, "winealsa: ");
memcpy(ww->interface_name + strlen(ww->interface_name),
ww->pcmname,
min(strlen(ww->pcmname), sizeof(ww->interface_name) - strlen("winealsa: ")));
strcpy(ww->ds_desc.szDrvname, "winealsa.drv");
memcpy(ww->ds_desc.szDesc, snd_pcm_info_get_name(infop),
min( (sizeof(ww->ds_desc.szDesc) - 1), strlen(snd_pcm_info_get_name(infop))) );
ww->ds_caps.dwMinSecondarySampleRate = DSBFREQUENCY_MIN;
ww->ds_caps.dwMaxSecondarySampleRate = DSBFREQUENCY_MAX;
ww->ds_caps.dwPrimaryBuffers = 1;
HeapFree( GetProcessHeap(), 0, infop );
return 0;
}
/*----------------------------------------------------------------------------
** ALSA_FreeDevice
*/
static void ALSA_FreeDevice(WINE_WAVEDEV *ww)
{
HeapFree(GetProcessHeap(), 0, ww->pcmname);
ww->pcmname = NULL;
HeapFree(GetProcessHeap(), 0, ww->ctlname);
ww->ctlname = NULL;
}
/*----------------------------------------------------------------------------
** ALSA_AddDeviceToArray
**
** Dynamically size one of the wavein or waveout arrays of devices,
** and add a fully configured device node to the array.
**
*/
static int ALSA_AddDeviceToArray(WINE_WAVEDEV *ww, WINE_WAVEDEV **array,
DWORD *count, DWORD *alloced, int isdefault)
{
int i = *count;
if (*count >= *alloced)
{
(*alloced) += WAVEDEV_ALLOC_EXTENT_SIZE;
if (! (*array))
*array = HeapAlloc(GetProcessHeap(), 0, sizeof(*ww) * (*alloced));
else
*array = HeapReAlloc(GetProcessHeap(), 0, *array, sizeof(*ww) * (*alloced));
if (!*array)
{
return -1;
}
}
/* If this is the default, arrange for it to be the first element */
if (isdefault && i > 0)
{
(*array)[*count] = (*array)[0];
i = 0;
}
(*array)[i] = *ww;
(*count)++;
return 0;
}
/*----------------------------------------------------------------------------
** ALSA_AddPlaybackDevice
**
** Add a given Alsa device to Wine's internal list of Playback
** devices.
*/
static int ALSA_AddPlaybackDevice(snd_ctl_t *ctl, snd_pcm_t *pcm, const char *pcmname, int isdefault)
{
WINE_WAVEDEV wwo;
int rc;
memset(&wwo, '\0', sizeof(wwo));
rc = ALSA_AddCommonDevice(ctl, pcm, pcmname, &wwo);
if (rc)
return(rc);
MultiByteToWideChar(CP_UNIXCP, 0, wwo.ds_desc.szDesc, -1,
wwo.outcaps.szPname, sizeof(wwo.outcaps.szPname)/sizeof(WCHAR));
wwo.outcaps.szPname[sizeof(wwo.outcaps.szPname)/sizeof(WCHAR) - 1] = '\0';
wwo.outcaps.wMid = MM_CREATIVE;
wwo.outcaps.wPid = MM_CREATIVE_SBP16_WAVEOUT;
wwo.outcaps.vDriverVersion = 0x0100;
rc = ALSA_ComputeCaps(ctl, pcm, &wwo.outcaps.wChannels, &wwo.ds_caps.dwFlags,
&wwo.outcaps.dwFormats, &wwo.outcaps.dwSupport);
if (rc)
{
WARN("Error calculating device caps for pcm [%s]\n", wwo.pcmname);
ALSA_FreeDevice(&wwo);
return(rc);
}
rc = ALSA_AddDeviceToArray(&wwo, &WOutDev, &ALSA_WodNumDevs, &ALSA_WodNumMallocedDevs, isdefault);
if (rc)
ALSA_FreeDevice(&wwo);
return (rc);
}
/*----------------------------------------------------------------------------
** ALSA_AddCaptureDevice
**
** Add a given Alsa device to Wine's internal list of Capture
** devices.
*/
static int ALSA_AddCaptureDevice(snd_ctl_t *ctl, snd_pcm_t *pcm, const char *pcmname, int isdefault)
{
WINE_WAVEDEV wwi;
int rc;
memset(&wwi, '\0', sizeof(wwi));
rc = ALSA_AddCommonDevice(ctl, pcm, pcmname, &wwi);
if (rc)
return(rc);
MultiByteToWideChar(CP_UNIXCP, 0, wwi.ds_desc.szDesc, -1,
wwi.incaps.szPname, sizeof(wwi.incaps.szPname) / sizeof(WCHAR));
wwi.incaps.szPname[sizeof(wwi.incaps.szPname)/sizeof(WCHAR) - 1] = '\0';
wwi.incaps.wMid = MM_CREATIVE;
wwi.incaps.wPid = MM_CREATIVE_SBP16_WAVEOUT;
wwi.incaps.vDriverVersion = 0x0100;
rc = ALSA_ComputeCaps(ctl, pcm, &wwi.incaps.wChannels, &wwi.ds_caps.dwFlags,
&wwi.incaps.dwFormats, &wwi.dwSupport);
if (rc)
{
WARN("Error calculating device caps for pcm [%s]\n", wwi.pcmname);
ALSA_FreeDevice(&wwi);
return(rc);
}
rc = ALSA_AddDeviceToArray(&wwi, &WInDev, &ALSA_WidNumDevs, &ALSA_WidNumMallocedDevs, isdefault);
if (rc)
ALSA_FreeDevice(&wwi);
return(rc);
}
/*----------------------------------------------------------------------------
** ALSA_CheckEnvironment
**
** Given an Alsa style configuration node, scan its subitems
** for environment variable names, and use them to find an override,
** if appropriate.
** This is essentially a long and convoluted way of doing:
** getenv("ALSA_CARD")
** getenv("ALSA_CTL_CARD")
** getenv("ALSA_PCM_CARD")
** getenv("ALSA_PCM_DEVICE")
**
** The output value is set with the atoi() of the first environment
** variable found to be set, if any; otherwise, it is left alone
*/
static void ALSA_CheckEnvironment(snd_config_t *node, int *outvalue)
{
snd_config_iterator_t iter;
for (iter = snd_config_iterator_first(node);
iter != snd_config_iterator_end(node);
iter = snd_config_iterator_next(iter))
{
snd_config_t *leaf = snd_config_iterator_entry(iter);
if (snd_config_get_type(leaf) == SND_CONFIG_TYPE_STRING)
{
const char *value;
if (snd_config_get_string(leaf, &value) >= 0)
{
char *p = getenv(value);
if (p)
{
*outvalue = atoi(p);
return;
}
}
}
}
}
/*----------------------------------------------------------------------------
** ALSA_DefaultDevices
**
** Jump through Alsa style hoops to (hopefully) properly determine
** Alsa defaults for CTL Card #, as well as for PCM Card + Device #.
** We'll also find out if the user has set any of the environment
** variables that specify we're to use a specific card or device.
**
** Parameters:
** directhw Whether to use a direct hardware device or not;
** essentially switches the pcm device name from
** one of 'default:X' or 'plughw:X' to "hw:X"
** defctlcard If !NULL, will hold the ctl card number given
** by the ALSA config as the default
** defpcmcard If !NULL, default pcm card #
** defpcmdev If !NULL, default pcm device #
** fixedctlcard If !NULL, and the user set the appropriate
** environment variable, we'll set to the
** card the user specified.
** fixedpcmcard If !NULL, and the user set the appropriate
** environment variable, we'll set to the
** card the user specified.
** fixedpcmdev If !NULL, and the user set the appropriate
** environment variable, we'll set to the
** device the user specified.
**
** Returns: 0 on success, < 0 on failure
*/
static int ALSA_DefaultDevices(int directhw,
long *defctlcard,
long *defpcmcard, long *defpcmdev,
int *fixedctlcard,
int *fixedpcmcard, int *fixedpcmdev)
{
snd_config_t *configp;
char pcmsearch[256];
ALSA_RETURN_ONFAIL(snd_config_update());
if (defctlcard)
if (snd_config_search(snd_config, "defaults.ctl.card", &configp) >= 0)
snd_config_get_integer(configp, defctlcard);
if (defpcmcard)
if (snd_config_search(snd_config, "defaults.pcm.card", &configp) >= 0)
snd_config_get_integer(configp, defpcmcard);
if (defpcmdev)
if (snd_config_search(snd_config, "defaults.pcm.device", &configp) >= 0)
snd_config_get_integer(configp, defpcmdev);
if (fixedctlcard)
{
if (snd_config_search(snd_config, "ctl.hw.@args.CARD.default.vars", &configp) >= 0)
ALSA_CheckEnvironment(configp, fixedctlcard);
}
if (fixedpcmcard)
{
sprintf(pcmsearch, "pcm.%s.@args.CARD.default.vars", directhw ? "hw" : "plughw");
if (snd_config_search(snd_config, pcmsearch, &configp) >= 0)
ALSA_CheckEnvironment(configp, fixedpcmcard);
}
if (fixedpcmdev)
{
sprintf(pcmsearch, "pcm.%s.@args.DEV.default.vars", directhw ? "hw" : "plughw");
if (snd_config_search(snd_config, pcmsearch, &configp) >= 0)
ALSA_CheckEnvironment(configp, fixedpcmdev);
}
return 0;
}
/*----------------------------------------------------------------------------
** ALSA_ScanDevices
**
** Iterate through all discoverable ALSA cards, searching
** for usable PCM devices.
**
** Parameters:
** directhw Whether to use a direct hardware device or not;
** essentially switches the pcm device name from
** one of 'default:X' or 'plughw:X' to "hw:X"
** defctlcard Alsa's notion of the default ctl card.
** defpcmcard . pcm card
** defpcmdev . pcm device
** fixedctlcard If not -1, then gives the value of ALSA_CTL_CARD
** or equivalent environment variable
** fixedpcmcard If not -1, then gives the value of ALSA_PCM_CARD
** or equivalent environment variable
** fixedpcmdev If not -1, then gives the value of ALSA_PCM_DEVICE
** or equivalent environment variable
**
** Returns: 0 on success, < 0 on failure
*/
static int ALSA_ScanDevices(int directhw,
long defctlcard, long defpcmcard, long defpcmdev,
int fixedctlcard, int fixedpcmcard, int fixedpcmdev)
{
int card = fixedpcmcard;
int scan_devices = (fixedpcmdev == -1);
/*------------------------------------------------------------------------
** Loop through all available cards
**----------------------------------------------------------------------*/
if (card == -1)
snd_card_next(&card);
for (; card != -1; snd_card_next(&card))
{
char ctlname[256];
snd_ctl_t *ctl;
int rc;
int device;
/*--------------------------------------------------------------------
** Try to open a ctl handle; Wine doesn't absolutely require one,
** but it does allow for volume control and for device scanning
**------------------------------------------------------------------*/
sprintf(ctlname, "hw:%d", fixedctlcard == -1 ? card : fixedctlcard);
rc = snd_ctl_open(&ctl, ctlname, SND_CTL_NONBLOCK);
if (rc < 0)
{
ctl = NULL;
WARN("Unable to open an alsa ctl for [%s] (pcm card %d): %s; not scanning devices\n",
ctlname, card, snd_strerror(rc));
if (fixedpcmdev == -1)
fixedpcmdev = 0;
}
/*--------------------------------------------------------------------
** Loop through all available devices on this card
**------------------------------------------------------------------*/
device = fixedpcmdev;
if (device == -1)
snd_ctl_pcm_next_device(ctl, &device);
for (; device != -1; snd_ctl_pcm_next_device(ctl, &device))
{
char defaultpcmname[256];
char plugpcmname[256];
char hwpcmname[256];
char *pcmname = NULL;
snd_pcm_t *pcm;
sprintf(defaultpcmname, "default");
sprintf(plugpcmname, "plughw:%d,%d", card, device);
sprintf(hwpcmname, "hw:%d,%d", card, device);
/*----------------------------------------------------------------
** See if it's a valid playback device
**--------------------------------------------------------------*/
if (ALSA_TestDeviceForWine(card, device, SND_PCM_STREAM_PLAYBACK) == 0)
{
/* If we can, try the default:X device name first */
if (! scan_devices && ! directhw)
{
pcmname = defaultpcmname;
rc = snd_pcm_open(&pcm, pcmname, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
}
else
rc = -1;
if (rc < 0)
{
pcmname = directhw ? hwpcmname : plugpcmname;
rc = snd_pcm_open(&pcm, pcmname, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
}
if (rc >= 0)
{
if (defctlcard == card && defpcmcard == card && defpcmdev == device)
ALSA_AddPlaybackDevice(ctl, pcm, pcmname, TRUE);
else
ALSA_AddPlaybackDevice(ctl, pcm, pcmname, FALSE);
snd_pcm_close(pcm);
}
else
{
TRACE("Device [%s/%s] failed to open for playback: %s\n",
directhw || scan_devices ? "(N/A)" : defaultpcmname,
directhw ? hwpcmname : plugpcmname,
snd_strerror(rc));
}
}
/*----------------------------------------------------------------
** See if it's a valid capture device
**--------------------------------------------------------------*/
if (ALSA_TestDeviceForWine(card, device, SND_PCM_STREAM_CAPTURE) == 0)
{
/* If we can, try the default:X device name first */
if (! scan_devices && ! directhw)
{
pcmname = defaultpcmname;
rc = snd_pcm_open(&pcm, pcmname, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK);
}
else
rc = -1;
if (rc < 0)
{
pcmname = directhw ? hwpcmname : plugpcmname;
rc = snd_pcm_open(&pcm, pcmname, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK);
}
if (rc >= 0)
{
if (defctlcard == card && defpcmcard == card && defpcmdev == device)
ALSA_AddCaptureDevice(ctl, pcm, pcmname, TRUE);
else
ALSA_AddCaptureDevice(ctl, pcm, pcmname, FALSE);
snd_pcm_close(pcm);
}
else
{
TRACE("Device [%s/%s] failed to open for capture: %s\n",
directhw || scan_devices ? "(N/A)" : defaultpcmname,
directhw ? hwpcmname : plugpcmname,
snd_strerror(rc));
}
}
if (! scan_devices)
break;
}
if (ctl)
snd_ctl_close(ctl);
/*--------------------------------------------------------------------
** If the user has set env variables such that we're pegged to
** a specific card, then break after we've examined it
**------------------------------------------------------------------*/
if (fixedpcmcard != -1)
break;
}
return 0;
}
/*----------------------------------------------------------------------------
** ALSA_PerformDefaultScan
** Perform the basic default scanning for devices within ALSA.
** The hope is that this routine implements a 'correct'
** scanning algorithm from the Alsalib point of view.
**
** Note that Wine, overall, has other mechanisms to
** override and specify exact CTL and PCM device names,
** but this routine is imagined as the default that
** 99% of users will use.
**
** The basic algorithm is simple:
** Use snd_card_next to iterate cards; within cards, use
** snd_ctl_pcm_next_device to iterate through devices.
**
** We add a little complexity by taking into consideration
** environment variables such as ALSA_CARD (et all), and by
** detecting when a given device matches the default specified
** by Alsa.
**
** Parameters:
** directhw If !0, indicates we should use the hw:X
** PCM interface, rather than first try
** the 'default' device followed by the plughw
** device. (default and plughw do fancy mixing
** and audio scaling, if they are available).
** devscan If TRUE, we should scan all devices, not
** juse use device 0 on each card
**
** Returns:
** 0 on success
**
** Effects:
** Invokes the ALSA_AddXXXDevice functions on valid
** looking devices
*/
static int ALSA_PerformDefaultScan(int directhw, BOOL devscan)
{
long defctlcard = -1, defpcmcard = -1, defpcmdev = -1;
int fixedctlcard = -1, fixedpcmcard = -1, fixedpcmdev = -1;
int rc;
/* FIXME: We should dlsym the new snd_names_list/snd_names_list_free 1.0.9 apis,
** and use them instead of this scan mechanism if they are present */
rc = ALSA_DefaultDevices(directhw, &defctlcard, &defpcmcard, &defpcmdev,
&fixedctlcard, &fixedpcmcard, &fixedpcmdev);
if (rc)
return(rc);
if (fixedpcmdev == -1 && ! devscan)
fixedpcmdev = 0;
return(ALSA_ScanDevices(directhw, defctlcard, defpcmcard, defpcmdev, fixedctlcard, fixedpcmcard, fixedpcmdev));
}
/*----------------------------------------------------------------------------
** ALSA_AddUserSpecifiedDevice
** Add a device given from the registry
*/
static int ALSA_AddUserSpecifiedDevice(const char *ctlname, const char *pcmname)
{
int rc;
int okay = 0;
snd_ctl_t *ctl = NULL;
snd_pcm_t *pcm = NULL;
if (ctlname)
{
rc = snd_ctl_open(&ctl, ctlname, SND_CTL_NONBLOCK);
if (rc < 0)
ctl = NULL;
}
rc = snd_pcm_open(&pcm, pcmname, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
if (rc >= 0)
{
ALSA_AddPlaybackDevice(ctl, pcm, pcmname, FALSE);
okay++;
snd_pcm_close(pcm);
}
rc = snd_pcm_open(&pcm, pcmname, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK);
if (rc >= 0)
{
ALSA_AddCaptureDevice(ctl, pcm, pcmname, FALSE);
okay++;
snd_pcm_close(pcm);
}
if (ctl)
snd_ctl_close(ctl);
return (okay == 0);
}
/*----------------------------------------------------------------------------
** ALSA_WaveInit
** Initialize the Wine Alsa sub system.
** The main task is to probe for and store a list of all appropriate playback
** and capture devices.
** Key control points are from the registry key:
** [Software\Wine\Alsa Driver]
** AutoScanCards Whether or not to scan all known sound cards
** and add them to Wine's list (default yes)
** AutoScanDevices Whether or not to scan all known PCM devices
** on each card (default no)
** UseDirectHW Whether or not to use the hw:X device,
** instead of the fancy default:X or plughw:X device.
** The hw:X device goes straight to the hardware
** without any fancy mixing or audio scaling in between.
** DeviceCount If present, specifies the number of hard coded
** Alsa devices to add to Wine's list; default 0
** DevicePCMn Specifies the Alsa PCM devices to open for
** Device n (where n goes from 1 to DeviceCount)
** DeviceCTLn Specifies the Alsa control devices to open for
** Device n (where n goes from 1 to DeviceCount)
**
** Using AutoScanCards no, and then Devicexxx info
** is a way to exactly specify the devices used by Wine.
**
*/
void ALSA_WaveInit(void)
{
DWORD rc;
BOOL AutoScanCards = TRUE;
BOOL AutoScanDevices = FALSE;
BOOL UseDirectHW = FALSE;
DWORD DeviceCount = 0;
HKEY key = 0;
int i;
static int loaded;
if (loaded++)
return;
/* @@ Wine registry key: HKCU\Software\Wine\Alsa Driver */
rc = RegOpenKeyExA(HKEY_CURRENT_USER, "Software\\Wine\\Alsa Driver", 0, KEY_QUERY_VALUE, &key);
if (rc == ERROR_SUCCESS)
{
ALSA_RegGetBoolean(key, "AutoScanCards", &AutoScanCards);
ALSA_RegGetBoolean(key, "AutoScanDevices", &AutoScanDevices);
ALSA_RegGetBoolean(key, "UseDirectHW", &UseDirectHW);
ALSA_RegGetInt(key, "DeviceCount", &DeviceCount);
}
if (AutoScanCards)
ALSA_PerformDefaultScan(UseDirectHW, AutoScanDevices);
for (i = 0; i < DeviceCount; i++)
{
char *ctl_name = NULL;
char *pcm_name = NULL;
char value[30];
sprintf(value, "DevicePCM%d", i + 1);
if (ALSA_RegGetString(key, value, &pcm_name) == ERROR_SUCCESS)
{
sprintf(value, "DeviceCTL%d", i + 1);
ALSA_RegGetString(key, value, &ctl_name);
ALSA_AddUserSpecifiedDevice(ctl_name, pcm_name);
}
HeapFree(GetProcessHeap(), 0, ctl_name);
HeapFree(GetProcessHeap(), 0, pcm_name);
}
if (key)
RegCloseKey(key);
}
/*
* Sample Wine Driver for Advanced Linux Sound System (ALSA)
* Based on version <final> of the ALSA API
*
* Copyright 2002 Eric Pouech
* 2002 Marco Pietrobono
* 2003 Christian Costa : WaveIn support
* 2006-2007 Maarten Lankhorst
*
* 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
*/
/*======================================================================*
* Low level WAVE OUT implementation *
*======================================================================*/
#include "config.h"
#include "wine/port.h"
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#include <errno.h>
#include <limits.h>
#include <fcntl.h>
#ifdef HAVE_SYS_IOCTL_H
# include <sys/ioctl.h>
#endif
#ifdef HAVE_SYS_MMAN_H
# include <sys/mman.h>
#endif
#include "windef.h"
#include "winbase.h"
#include "wingdi.h"
#include "winuser.h"
#include "winnls.h"
#include "mmddk.h"
#include "mmreg.h"
#include "dsound.h"
#include "dsdriver.h"
#include "ks.h"
#include "ksmedia.h"
#include "alsa.h"
#include "wine/library.h"
#include "wine/unicode.h"
#include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(wave);
WINE_WAVEDEV *WOutDev;
DWORD ALSA_WodNumMallocedDevs;
DWORD ALSA_WodNumDevs;
/**************************************************************************
* wodNotifyClient [internal]
*/
static void wodNotifyClient(WINE_WAVEDEV* wwo, WORD wMsg, DWORD_PTR dwParam1, DWORD_PTR dwParam2)
{
TRACE("wMsg = 0x%04x dwParm1 = %lx dwParam2 = %lx\n", wMsg, dwParam1, dwParam2);
switch (wMsg) {
case WOM_OPEN:
case WOM_CLOSE:
case WOM_DONE:
DriverCallback(wwo->waveDesc.dwCallback, wwo->wFlags, (HDRVR)wwo->waveDesc.hWave,
wMsg, wwo->waveDesc.dwInstance, dwParam1, dwParam2);
break;
default:
FIXME("Unknown callback message %u\n", wMsg);
}
}
/**************************************************************************
* wodUpdatePlayedTotal [internal]
*
*/
static BOOL wodUpdatePlayedTotal(WINE_WAVEDEV* wwo, snd_pcm_status_t* ps)
{
snd_pcm_sframes_t delay;
snd_pcm_sframes_t avail;
snd_pcm_uframes_t buf_size = 0;
snd_pcm_state_t state;
state = snd_pcm_state(wwo->pcm);
avail = snd_pcm_avail_update(wwo->pcm);
snd_pcm_hw_params_get_buffer_size(wwo->hw_params, &buf_size);
delay = buf_size - avail;
if (state != SND_PCM_STATE_RUNNING && state != SND_PCM_STATE_PREPARED)
{
WARN("Unexpected state (%d) while updating Total Played, resetting\n", state);
wine_snd_pcm_recover(wwo->pcm, -EPIPE, 0);
delay=0;
}
/* A delay < 0 indicates an underrun; for our purposes that's 0. */
if (delay < 0)
{
WARN("Unexpected delay (%ld) while updating Total Played, resetting\n", delay);
delay=0;
}
InterlockedExchange((LONG*)&wwo->dwPlayedTotal, wwo->dwWrittenTotal - snd_pcm_frames_to_bytes(wwo->pcm, delay));
return TRUE;
}
/**************************************************************************
* wodPlayer_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 wodPlayer_BeginWaveHdr(WINE_WAVEDEV* wwo, LPWAVEHDR lpWaveHdr)
{
wwo->lpPlayPtr = lpWaveHdr;
if (!lpWaveHdr) return;
if (lpWaveHdr->dwFlags & WHDR_BEGINLOOP) {
if (wwo->lpLoopPtr) {
WARN("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;
}
/**************************************************************************
* wodPlayer_PlayPtrNext [internal]
*
* Advance the play pointer to the next waveheader, looping if required.
*/
static LPWAVEHDR wodPlayer_PlayPtrNext(WINE_WAVEDEV* wwo)
{
LPWAVEHDR 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;
wodPlayer_BeginWaveHdr(wwo, lpWaveHdr);
}
} else {
/* We're not in a loop. Advance to the next wave header */
wodPlayer_BeginWaveHdr(wwo, lpWaveHdr = lpWaveHdr->lpNext);
}
return lpWaveHdr;
}
/**************************************************************************
* wodPlayer_DSPWait [internal]
* Returns the number of milliseconds to wait for the DSP buffer to play a
* period
*/
static DWORD wodPlayer_DSPWait(const WINE_WAVEDEV *wwo)
{
/* time for one period to be played */
unsigned int val=0;
int dir=0;
int err=0;
err = snd_pcm_hw_params_get_period_time(wwo->hw_params, &val, &dir);
return val / 1000;
}
/**************************************************************************
* wodPlayer_NotifyWait [internal]
* Returns the number of milliseconds to wait before attempting to notify
* completion of the specified wavehdr.
* This is based on the number of bytes remaining to be written in the
* wave.
*/
static DWORD wodPlayer_NotifyWait(const WINE_WAVEDEV* wwo, LPWAVEHDR lpWaveHdr)
{
DWORD dwMillis;
if (lpWaveHdr->reserved < wwo->dwPlayedTotal) {
dwMillis = 1;
} else {
dwMillis = (lpWaveHdr->reserved - wwo->dwPlayedTotal) * 1000 / wwo->format.Format.nAvgBytesPerSec;
if (!dwMillis) dwMillis = 1;
}
return dwMillis;
}
/**************************************************************************
* wodPlayer_WriteMaxFrags [internal]
* Writes the maximum number of frames possible to the DSP and returns
* the number of frames written.
*/
static int wodPlayer_WriteMaxFrags(WINE_WAVEDEV* wwo, DWORD* frames)
{
/* Only attempt to write to free frames */
LPWAVEHDR lpWaveHdr = wwo->lpPlayPtr;
DWORD dwLength = snd_pcm_bytes_to_frames(wwo->pcm, lpWaveHdr->dwBufferLength - wwo->dwPartialOffset);
int toWrite = min(dwLength, *frames);
int written;
TRACE("Writing wavehdr %p.%u[%u]\n", lpWaveHdr, wwo->dwPartialOffset, lpWaveHdr->dwBufferLength);
if (toWrite > 0) {
written = (wwo->write)(wwo->pcm, lpWaveHdr->lpData + wwo->dwPartialOffset, toWrite);
if ( written < 0) {
/* XRUN occurred. let's try to recover */
wine_snd_pcm_recover(wwo->pcm, written, 0);
written = (wwo->write)(wwo->pcm, lpWaveHdr->lpData + wwo->dwPartialOffset, toWrite);
}
if (written <= 0) {
/* still in error */
ERR("Error in writing wavehdr. Reason: %s\n", snd_strerror(written));
return written;
}
} else
written = 0;
wwo->dwPartialOffset += snd_pcm_frames_to_bytes(wwo->pcm, written);
if (wwo->dwPartialOffset + wwo->format.Format.nBlockAlign - 1 >= lpWaveHdr->dwBufferLength) {
/* this will be used to check if the given wave header has been fully played or not... */
wwo->dwPartialOffset = lpWaveHdr->dwBufferLength;
/* If we wrote all current wavehdr, skip to the next one */
wodPlayer_PlayPtrNext(wwo);
}
*frames -= written;
wwo->dwWrittenTotal += snd_pcm_frames_to_bytes(wwo->pcm, written);
TRACE("dwWrittenTotal=%u\n", wwo->dwWrittenTotal);
return written;
}
/**************************************************************************
* wodPlayer_NotifyCompletions [internal]
*
* Notifies and remove from queue all wavehdrs which have been played to
* the speaker (ie. they have cleared the ALSA buffer). If force is true,
* we notify all wavehdrs and remove them all from the queue even if they
* are unplayed or part of a loop.
*/
static DWORD wodPlayer_NotifyCompletions(WINE_WAVEDEV* wwo, BOOL force)
{
LPWAVEHDR lpWaveHdr;
/* 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
*/
for (;;)
{
lpWaveHdr = wwo->lpQueuePtr;
if (!lpWaveHdr) {TRACE("Empty queue\n"); break;}
if (!force)
{
snd_pcm_uframes_t frames;
snd_pcm_hw_params_get_period_size(wwo->hw_params, &frames, NULL);
if (lpWaveHdr == wwo->lpPlayPtr) {TRACE("play %p\n", lpWaveHdr); break;}
if (lpWaveHdr == wwo->lpLoopPtr) {TRACE("loop %p\n", lpWaveHdr); break;}
if (lpWaveHdr->reserved > wwo->dwPlayedTotal + frames) {TRACE("still playing %p (%lu/%u)\n", lpWaveHdr, lpWaveHdr->reserved, wwo->dwPlayedTotal);break;}
}
wwo->dwPlayedTotal += lpWaveHdr->reserved - wwo->dwPlayedTotal;
wwo->lpQueuePtr = lpWaveHdr->lpNext;
lpWaveHdr->dwFlags &= ~WHDR_INQUEUE;
lpWaveHdr->dwFlags |= WHDR_DONE;
wodNotifyClient(wwo, WOM_DONE, (DWORD_PTR)lpWaveHdr, 0);
}
return (lpWaveHdr && lpWaveHdr != wwo->lpPlayPtr && lpWaveHdr != wwo->lpLoopPtr) ?
wodPlayer_NotifyWait(wwo, lpWaveHdr) : INFINITE;
}
/**************************************************************************
* wodPlayer_Reset [internal]
*
* wodPlayer helper. Resets current output stream.
*/
static void wodPlayer_Reset(WINE_WAVEDEV* wwo, BOOL reset)
{
int err;
TRACE("(%p)\n", wwo);
wodUpdatePlayedTotal(wwo, NULL);
/* updates current notify list */
wodPlayer_NotifyCompletions(wwo, FALSE);
if ( (err = snd_pcm_drop(wwo->pcm)) < 0) {
FIXME("flush: %s\n", snd_strerror(err));
wwo->hThread = 0;
wwo->state = WINE_WS_STOPPED;
ExitThread(-1);
}
if ( (err = snd_pcm_prepare(wwo->pcm)) < 0 )
ERR("pcm prepare failed: %s\n", snd_strerror(err));
if (reset) {
enum win_wm_message msg;
DWORD_PTR param;
HANDLE ev;
/* remove any buffer */
wodPlayer_NotifyCompletions(wwo, TRUE);
wwo->lpPlayPtr = wwo->lpQueuePtr = wwo->lpLoopPtr = NULL;
wwo->state = WINE_WS_STOPPED;
wwo->dwPlayedTotal = wwo->dwWrittenTotal = 0;
/* Clear partial wavehdr */
wwo->dwPartialOffset = 0;
/* remove any existing message in the ring */
EnterCriticalSection(&wwo->msgRing.msg_crst);
/* return all pending headers in queue */
while (ALSA_RetrieveRingMessage(&wwo->msgRing, &msg, &param, &ev))
{
if (msg != WINE_WM_HEADER)
{
FIXME("shouldn't have headers left\n");
SetEvent(ev);
continue;
}
((LPWAVEHDR)param)->dwFlags &= ~WHDR_INQUEUE;
((LPWAVEHDR)param)->dwFlags |= WHDR_DONE;
wodNotifyClient(wwo, WOM_DONE, param, 0);
}
ALSA_ResetRingMessage(&wwo->msgRing);
LeaveCriticalSection(&wwo->msgRing.msg_crst);
} 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("grin\n");
wwo->dwPartialOffset = sz - (wwo->dwWrittenTotal - wwo->dwPlayedTotal);
wwo->dwWrittenTotal = wwo->dwPlayedTotal;
wwo->lpPlayPtr = wwo->lpQueuePtr;
}
wwo->state = WINE_WS_PAUSED;
}
}
/**************************************************************************
* wodPlayer_ProcessMessages [internal]
*/
static void wodPlayer_ProcessMessages(WINE_WAVEDEV* wwo)
{
LPWAVEHDR lpWaveHdr;
enum win_wm_message msg;
DWORD_PTR param;
HANDLE ev;
int err;
while (ALSA_RetrieveRingMessage(&wwo->msgRing, &msg, &param, &ev)) {
TRACE("Received %s %lx\n", ALSA_getCmdString(msg), param);
switch (msg) {
case WINE_WM_PAUSING:
if ( snd_pcm_state(wwo->pcm) == SND_PCM_STATE_RUNNING )
{
if ( snd_pcm_hw_params_can_pause(wwo->hw_params) )
{
err = snd_pcm_pause(wwo->pcm, 1);
if ( err < 0 )
ERR("pcm_pause failed: %s\n", snd_strerror(err));
wwo->state = WINE_WS_PAUSED;
}
else
{
wodPlayer_Reset(wwo,FALSE);
}
}
SetEvent(ev);
break;
case WINE_WM_RESTARTING:
if (wwo->state == WINE_WS_PAUSED)
{
if ( snd_pcm_state(wwo->pcm) == SND_PCM_STATE_PAUSED )
{
err = snd_pcm_pause(wwo->pcm, 0);
if ( err < 0 )
ERR("pcm_pause failed: %s\n", snd_strerror(err));
}
wwo->state = WINE_WS_PLAYING;
}
SetEvent(ev);
break;
case WINE_WM_HEADER:
lpWaveHdr = (LPWAVEHDR)param;
/* insert buffer at the end of queue */
{
LPWAVEHDR* wh;
for (wh = &(wwo->lpQueuePtr); *wh; wh = &((*wh)->lpNext));
*wh = lpWaveHdr;
}
if (!wwo->lpPlayPtr)
wodPlayer_BeginWaveHdr(wwo,lpWaveHdr);
if (wwo->state == WINE_WS_STOPPED)
wwo->state = WINE_WS_PLAYING;
break;
case WINE_WM_RESETTING:
wodPlayer_Reset(wwo,TRUE);
SetEvent(ev);
break;
case WINE_WM_BREAKLOOP:
if (wwo->state == WINE_WS_PLAYING && wwo->lpLoopPtr != NULL) {
/* ensure exit at end of current loop */
wwo->dwLoops = 1;
}
SetEvent(ev);
break;
case WINE_WM_CLOSING:
/* 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->hThread = 0;
wwo->state = WINE_WS_CLOSED;
SetEvent(ev);
ExitThread(0);
/* shouldn't go here */
default:
FIXME("unknown message %d\n", msg);
break;
}
}
}
/**************************************************************************
* wodPlayer_FeedDSP [internal]
* Feed as much sound data as we can into the DSP and return the number of
* milliseconds before it will be necessary to feed the DSP again.
*/
static DWORD wodPlayer_FeedDSP(WINE_WAVEDEV* wwo)
{
DWORD availInQ;
wodUpdatePlayedTotal(wwo, NULL);
availInQ = snd_pcm_avail_update(wwo->pcm);
/* no more room... no need to try to feed */
if (availInQ > 0) {
/* Feed from partial wavehdr */
if (wwo->lpPlayPtr && wwo->dwPartialOffset != 0) {
wodPlayer_WriteMaxFrags(wwo, &availInQ);
}
/* Feed wavehdrs until we run out of wavehdrs or DSP space */
if (wwo->dwPartialOffset == 0 && wwo->lpPlayPtr) {
do {
TRACE("Setting time to elapse for %p to %u\n",
wwo->lpPlayPtr, wwo->dwWrittenTotal + wwo->lpPlayPtr->dwBufferLength);
/* note the value that dwPlayedTotal will return when this wave finishes playing */
wwo->lpPlayPtr->reserved = wwo->dwWrittenTotal + wwo->lpPlayPtr->dwBufferLength;
} while (wodPlayer_WriteMaxFrags(wwo, &availInQ) && wwo->lpPlayPtr && availInQ > 0);
}
}
return wodPlayer_DSPWait(wwo);
}
/**************************************************************************
* wodPlayer [internal]
*/
static DWORD CALLBACK wodPlayer(LPVOID pmt)
{
WORD uDevID = (DWORD_PTR)pmt;
WINE_WAVEDEV* wwo = &WOutDev[uDevID];
DWORD dwNextFeedTime = INFINITE; /* Time before DSP needs feeding */
DWORD dwNextNotifyTime = INFINITE; /* Time before next wave completion */
DWORD dwSleepTime;
wwo->state = WINE_WS_STOPPED;
SetEvent(wwo->hStartUpEvent);
for (;;) {
/** Wait for the shortest time before an action is required. If there
* are no pending actions, wait forever for a command.
*/
dwSleepTime = min(dwNextFeedTime, dwNextNotifyTime);
TRACE("waiting %ums (%u,%u)\n", dwSleepTime, dwNextFeedTime, dwNextNotifyTime);
ALSA_WaitRingMessage(&wwo->msgRing, dwSleepTime);
wodPlayer_ProcessMessages(wwo);
if (wwo->state == WINE_WS_PLAYING) {
dwNextFeedTime = wodPlayer_FeedDSP(wwo);
dwNextNotifyTime = wodPlayer_NotifyCompletions(wwo, FALSE);
if (dwNextFeedTime == INFINITE) {
/* FeedDSP ran out of data, but before giving up, */
/* check that a notification didn't give us more */
wodPlayer_ProcessMessages(wwo);
if (wwo->lpPlayPtr) {
TRACE("recovering\n");
dwNextFeedTime = wodPlayer_FeedDSP(wwo);
}
}
} else {
dwNextFeedTime = dwNextNotifyTime = INFINITE;
}
}
return 0;
}
/**************************************************************************
* 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 >= ALSA_WodNumDevs) {
TRACE("Asked for device %d, but only %d known!\n", wDevID, ALSA_WodNumDevs);
return MMSYSERR_BADDEVICEID;
}
memcpy(lpCaps, &WOutDev[wDevID].outcaps, min(dwSize, sizeof(*lpCaps)));
return MMSYSERR_NOERROR;
}
/**************************************************************************
* wodOpen [internal]
*/
static DWORD wodOpen(WORD wDevID, LPWAVEOPENDESC lpDesc, DWORD dwFlags)
{
WINE_WAVEDEV* wwo;
snd_pcm_t * pcm = NULL;
snd_hctl_t * hctl = NULL;
snd_pcm_hw_params_t * hw_params;
snd_pcm_sw_params_t * sw_params;
snd_pcm_access_t access;
snd_pcm_format_t format = -1;
unsigned int rate;
unsigned int buffer_time = 120000;
unsigned int period_time = 22000;
snd_pcm_uframes_t buffer_size;
snd_pcm_uframes_t period_size;
int flags;
int err=0;
int dir=0;
DWORD retcode = 0;
TRACE("(%u, %p, %08X);\n", wDevID, lpDesc, dwFlags);
if (lpDesc == NULL) {
WARN("Invalid Parameter !\n");
return MMSYSERR_INVALPARAM;
}
if (wDevID >= ALSA_WodNumDevs) {
TRACE("Asked for device %d, but only %d known!\n", wDevID, ALSA_WodNumDevs);
return MMSYSERR_BADDEVICEID;
}
/* only PCM format is supported so far... */
if (!ALSA_supportedFormat(lpDesc->lpFormat)) {
WARN("Bad format: tag=%04X nChannels=%d nSamplesPerSec=%d !\n",
lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels,
lpDesc->lpFormat->nSamplesPerSec);
return WAVERR_BADFORMAT;
}
if (dwFlags & WAVE_FORMAT_QUERY) {
TRACE("Query format: tag=%04X nChannels=%d nSamplesPerSec=%d !\n",
lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels,
lpDesc->lpFormat->nSamplesPerSec);
return MMSYSERR_NOERROR;
}
wwo = &WOutDev[wDevID];
if (wwo->pcm != NULL) {
WARN("%d already allocated\n", wDevID);
return MMSYSERR_ALLOCATED;
}
if (dwFlags & WAVE_DIRECTSOUND)
FIXME("Why are we called with DirectSound flag? It doesn't use MMSYSTEM any more\n");
/* not supported, ignore it */
dwFlags &= ~WAVE_DIRECTSOUND;
flags = SND_PCM_NONBLOCK;
if ( (err = snd_pcm_open(&pcm, wwo->pcmname, SND_PCM_STREAM_PLAYBACK, flags)) < 0)
{
ERR("Error open: %s\n", snd_strerror(err));
return MMSYSERR_NOTENABLED;
}
if (wwo->ctlname)
{
err = snd_hctl_open(&hctl, wwo->ctlname, 0);
if (err >= 0)
{
snd_hctl_load(hctl);
}
else
{
WARN("Could not open hctl for [%s]: %s\n", wwo->ctlname, snd_strerror(err));
hctl = NULL;
}
}
wwo->wFlags = HIWORD(dwFlags & CALLBACK_TYPEMASK);
wwo->waveDesc = *lpDesc;
ALSA_copyFormat(lpDesc->lpFormat, &wwo->format);
TRACE("Requested this format: %dx%dx%d %s\n",
wwo->format.Format.nSamplesPerSec,
wwo->format.Format.wBitsPerSample,
wwo->format.Format.nChannels,
ALSA_getFormat(wwo->format.Format.wFormatTag));
if (wwo->format.Format.wBitsPerSample == 0) {
WARN("Resetting zeroed wBitsPerSample\n");
wwo->format.Format.wBitsPerSample = 8 *
(wwo->format.Format.nAvgBytesPerSec /
wwo->format.Format.nSamplesPerSec) /
wwo->format.Format.nChannels;
}
#define EXIT_ON_ERROR(f,e,txt) do \
{ \
int err; \
if ( (err = (f) ) < 0) \
{ \
WARN(txt ": %s\n", snd_strerror(err)); \
retcode=e; \
goto errexit; \
} \
} while(0)
sw_params = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, snd_pcm_sw_params_sizeof() );
hw_params = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, snd_pcm_hw_params_sizeof() );
if (!hw_params || !sw_params)
{
retcode = MMSYSERR_NOMEM;
goto errexit;
}
snd_pcm_hw_params_any(pcm, hw_params);
access = SND_PCM_ACCESS_MMAP_INTERLEAVED;
if ( ( err = snd_pcm_hw_params_set_access(pcm, hw_params, access ) ) < 0) {
WARN("mmap not available. switching to standard write.\n");
access = SND_PCM_ACCESS_RW_INTERLEAVED;
EXIT_ON_ERROR( snd_pcm_hw_params_set_access(pcm, hw_params, access ), MMSYSERR_INVALPARAM, "unable to set access for playback");
wwo->write = snd_pcm_writei;
}
else
wwo->write = snd_pcm_mmap_writei;
if ((err = snd_pcm_hw_params_set_channels(pcm, hw_params, wwo->format.Format.nChannels)) < 0) {
WARN("unable to set required channels: %d\n", wwo->format.Format.nChannels);
EXIT_ON_ERROR( snd_pcm_hw_params_set_channels(pcm, hw_params, wwo->format.Format.nChannels ), WAVERR_BADFORMAT, "unable to set required channels" );
}
if ((wwo->format.Format.wFormatTag == WAVE_FORMAT_PCM) ||
((wwo->format.Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE) &&
IsEqualGUID(&wwo->format.SubFormat, &KSDATAFORMAT_SUBTYPE_PCM))) {
format = (wwo->format.Format.wBitsPerSample == 8) ? SND_PCM_FORMAT_U8 :
(wwo->format.Format.wBitsPerSample == 16) ? SND_PCM_FORMAT_S16_LE :
(wwo->format.Format.wBitsPerSample == 24) ? SND_PCM_FORMAT_S24_3LE :
(wwo->format.Format.wBitsPerSample == 32) ? SND_PCM_FORMAT_S32_LE : -1;
} else if ((wwo->format.Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE) &&
IsEqualGUID(&wwo->format.SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)){
format = (wwo->format.Format.wBitsPerSample == 32) ? SND_PCM_FORMAT_FLOAT_LE : -1;
} else {
ERR("invalid format: %0x04x\n", wwo->format.Format.wFormatTag);
retcode = WAVERR_BADFORMAT;
goto errexit;
}
if ((err = snd_pcm_hw_params_set_format(pcm, hw_params, format)) < 0) {
WARN("unable to set required format: %s\n", snd_pcm_format_name(format));
EXIT_ON_ERROR( snd_pcm_hw_params_set_format(pcm, hw_params, format), WAVERR_BADFORMAT, "unable to set required format" );
}
rate = wwo->format.Format.nSamplesPerSec;
dir=0;
err = snd_pcm_hw_params_set_rate_near(pcm, hw_params, &rate, &dir);
if (err < 0) {
WARN("Rate %d Hz not available for playback: %s\n", wwo->format.Format.nSamplesPerSec, snd_strerror(rate));
retcode = WAVERR_BADFORMAT;
goto errexit;
}
if (!ALSA_NearMatch(rate, wwo->format.Format.nSamplesPerSec)) {
WARN("Rate doesn't match (requested %d Hz, got %d Hz)\n", wwo->format.Format.nSamplesPerSec, rate);
retcode = WAVERR_BADFORMAT;
goto errexit;
}
TRACE("Got this format: %dx%dx%d %s\n",
wwo->format.Format.nSamplesPerSec,
wwo->format.Format.wBitsPerSample,
wwo->format.Format.nChannels,
ALSA_getFormat(wwo->format.Format.wFormatTag));
dir=0;
EXIT_ON_ERROR( snd_pcm_hw_params_set_buffer_time_near(pcm, hw_params, &buffer_time, &dir), MMSYSERR_INVALPARAM, "unable to set buffer time");
dir=0;
EXIT_ON_ERROR( snd_pcm_hw_params_set_period_time_near(pcm, hw_params, &period_time, &dir), MMSYSERR_INVALPARAM, "unable to set period time");
EXIT_ON_ERROR( snd_pcm_hw_params(pcm, hw_params), MMSYSERR_INVALPARAM, "unable to set hw params for playback");
err = snd_pcm_hw_params_get_period_size(hw_params, &period_size, &dir);
err = snd_pcm_hw_params_get_buffer_size(hw_params, &buffer_size);
snd_pcm_sw_params_current(pcm, sw_params);
EXIT_ON_ERROR( snd_pcm_sw_params_set_start_threshold(pcm, sw_params, 1), MMSYSERR_ERROR, "unable to set start threshold");
EXIT_ON_ERROR( snd_pcm_sw_params_set_silence_size(pcm, sw_params, 0), MMSYSERR_ERROR, "unable to set silence size");
EXIT_ON_ERROR( snd_pcm_sw_params_set_avail_min(pcm, sw_params, period_size), MMSYSERR_ERROR, "unable to set avail min");
EXIT_ON_ERROR( snd_pcm_sw_params_set_silence_threshold(pcm, sw_params, 0), MMSYSERR_ERROR, "unable to set silence threshold");
EXIT_ON_ERROR( snd_pcm_sw_params(pcm, sw_params), MMSYSERR_ERROR, "unable to set sw params for playback");
#undef EXIT_ON_ERROR
snd_pcm_prepare(pcm);
if (TRACE_ON(wave))
ALSA_TraceParameters(hw_params, sw_params, FALSE);
/* now, we can save all required data for later use... */
wwo->dwBufferSize = snd_pcm_frames_to_bytes(pcm, buffer_size);
wwo->lpQueuePtr = wwo->lpPlayPtr = wwo->lpLoopPtr = NULL;
wwo->dwPlayedTotal = wwo->dwWrittenTotal = 0;
wwo->dwPartialOffset = 0;
ALSA_InitRingMessage(&wwo->msgRing);
wwo->hStartUpEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
wwo->hThread = CreateThread(NULL, 0, wodPlayer, (LPVOID)(DWORD_PTR)wDevID, 0, &(wwo->dwThreadID));
if (!wwo->hThread) {
ERR("Thread creation for the wodPlayer failed!\n");
CloseHandle(wwo->hStartUpEvent);
retcode = MMSYSERR_NOMEM;
goto errexit;
}
SetThreadPriority(wwo->hThread, THREAD_PRIORITY_TIME_CRITICAL);
WaitForSingleObject(wwo->hStartUpEvent, INFINITE);
CloseHandle(wwo->hStartUpEvent);
wwo->hStartUpEvent = NULL;
TRACE("handle=%p\n", pcm);
TRACE("wBitsPerSample=%u, nAvgBytesPerSec=%u, nSamplesPerSec=%u, nChannels=%u nBlockAlign=%u!\n",
wwo->format.Format.wBitsPerSample, wwo->format.Format.nAvgBytesPerSec,
wwo->format.Format.nSamplesPerSec, wwo->format.Format.nChannels,
wwo->format.Format.nBlockAlign);
HeapFree( GetProcessHeap(), 0, sw_params );
wwo->hw_params = hw_params;
wwo->hctl = hctl;
wwo->pcm = pcm;
wodNotifyClient(wwo, WOM_OPEN, 0L, 0L);
return MMSYSERR_NOERROR;
errexit:
if (pcm)
snd_pcm_close(pcm);
if (hctl)
{
snd_hctl_free(hctl);
snd_hctl_close(hctl);
}
HeapFree( GetProcessHeap(), 0, hw_params );
HeapFree( GetProcessHeap(), 0, sw_params );
if (wwo->msgRing.ring_buffer_size > 0)
ALSA_DestroyRingMessage(&wwo->msgRing);
return retcode;
}
/**************************************************************************
* wodClose [internal]
*/
static DWORD wodClose(WORD wDevID)
{
WINE_WAVEDEV* wwo;
TRACE("(%u);\n", wDevID);
if (wDevID >= ALSA_WodNumDevs) {
TRACE("Asked for device %d, but only %d known!\n", wDevID, ALSA_WodNumDevs);
return MMSYSERR_BADDEVICEID;
}
wwo = &WOutDev[wDevID];
if (wwo->pcm == NULL) {
WARN("Requested to close already closed device %d!\n", wDevID);
return MMSYSERR_BADDEVICEID;
}
if (wwo->lpQueuePtr) {
WARN("buffers still playing !\n");
return WAVERR_STILLPLAYING;
} else {
if (wwo->hThread) {
ALSA_AddRingMessage(&wwo->msgRing, WINE_WM_CLOSING, 0, TRUE);
}
ALSA_DestroyRingMessage(&wwo->msgRing);
HeapFree( GetProcessHeap(), 0, wwo->hw_params );
wwo->hw_params = NULL;
if (wwo->hctl)
{
snd_hctl_free(wwo->hctl);
snd_hctl_close(wwo->hctl);
wwo->hctl = NULL;
}
snd_pcm_close(wwo->pcm);
wwo->pcm = NULL;
wodNotifyClient(wwo, WOM_CLOSE, 0L, 0L);
}
return MMSYSERR_NOERROR;
}
/**************************************************************************
* wodWrite [internal]
*
*/
static DWORD wodWrite(WORD wDevID, LPWAVEHDR lpWaveHdr, DWORD dwSize)
{
TRACE("(%u, %p, %08X);\n", wDevID, lpWaveHdr, dwSize);
if (wDevID >= ALSA_WodNumDevs) {
TRACE("Asked for device %d, but only %d known!\n", wDevID, ALSA_WodNumDevs);
return MMSYSERR_BADDEVICEID;
}
if (WOutDev[wDevID].pcm == NULL) {
WARN("Requested to write to closed device %d!\n", wDevID);
return MMSYSERR_BADDEVICEID;
}
if (lpWaveHdr->lpData == NULL || !(lpWaveHdr->dwFlags & WHDR_PREPARED))
return WAVERR_UNPREPARED;
if (lpWaveHdr->dwFlags & WHDR_INQUEUE)
return WAVERR_STILLPLAYING;
lpWaveHdr->dwFlags &= ~WHDR_DONE;
lpWaveHdr->dwFlags |= WHDR_INQUEUE;
lpWaveHdr->lpNext = 0;
ALSA_AddRingMessage(&WOutDev[wDevID].msgRing, WINE_WM_HEADER, (DWORD_PTR)lpWaveHdr, FALSE);
return MMSYSERR_NOERROR;
}
/**************************************************************************
* wodPause [internal]
*/
static DWORD wodPause(WORD wDevID)
{
TRACE("(%u);!\n", wDevID);
if (wDevID >= ALSA_WodNumDevs) {
TRACE("Asked for device %d, but only %d known!\n", wDevID, ALSA_WodNumDevs);
return MMSYSERR_BADDEVICEID;
}
if (WOutDev[wDevID].pcm == NULL) {
WARN("Requested to pause closed device %d!\n", wDevID);
return MMSYSERR_BADDEVICEID;
}
ALSA_AddRingMessage(&WOutDev[wDevID].msgRing, WINE_WM_PAUSING, 0, TRUE);
return MMSYSERR_NOERROR;
}
/**************************************************************************
* wodRestart [internal]
*/
static DWORD wodRestart(WORD wDevID)
{
TRACE("(%u);\n", wDevID);
if (wDevID >= ALSA_WodNumDevs) {
TRACE("Asked for device %d, but only %d known!\n", wDevID, ALSA_WodNumDevs);
return MMSYSERR_BADDEVICEID;
}
if (WOutDev[wDevID].pcm == NULL) {
WARN("Requested to restart closed device %d!\n", wDevID);
return MMSYSERR_BADDEVICEID;
}
if (WOutDev[wDevID].state == WINE_WS_PAUSED) {
ALSA_AddRingMessage(&WOutDev[wDevID].msgRing, WINE_WM_RESTARTING, 0, TRUE);
}
/* FIXME: is NotifyClient with WOM_DONE right ? (Comet Busters 1.3.3 needs this notification) */
/* FIXME: Myst crashes with this ... hmm -MM
return wodNotifyClient(wwo, WOM_DONE, 0L, 0L);
*/
return MMSYSERR_NOERROR;
}
/**************************************************************************
* wodReset [internal]
*/
static DWORD wodReset(WORD wDevID)
{
TRACE("(%u);\n", wDevID);
if (wDevID >= ALSA_WodNumDevs) {
TRACE("Asked for device %d, but only %d known!\n", wDevID, ALSA_WodNumDevs);
return MMSYSERR_BADDEVICEID;
}
if (WOutDev[wDevID].pcm == NULL) {
WARN("Requested to reset closed device %d!\n", wDevID);
return MMSYSERR_BADDEVICEID;
}
ALSA_AddRingMessage(&WOutDev[wDevID].msgRing, WINE_WM_RESETTING, 0, TRUE);
return MMSYSERR_NOERROR;
}
/**************************************************************************
* wodGetPosition [internal]
*/
static DWORD wodGetPosition(WORD wDevID, LPMMTIME lpTime, DWORD uSize)
{
WINE_WAVEDEV* wwo;
TRACE("(%u, %p, %u);\n", wDevID, lpTime, uSize);
if (wDevID >= ALSA_WodNumDevs) {
TRACE("Asked for device %d, but only %d known!\n", wDevID, ALSA_WodNumDevs);
return MMSYSERR_BADDEVICEID;
}
if (WOutDev[wDevID].pcm == NULL) {
WARN("Requested to get position of closed device %d!\n", wDevID);
return MMSYSERR_BADDEVICEID;
}
if (lpTime == NULL) return MMSYSERR_INVALPARAM;
wwo = &WOutDev[wDevID];
return ALSA_bytes_to_mmtime(lpTime, wwo->dwPlayedTotal, &wwo->format);
}
/**************************************************************************
* wodBreakLoop [internal]
*/
static DWORD wodBreakLoop(WORD wDevID)
{
TRACE("(%u);\n", wDevID);
if (wDevID >= ALSA_WodNumDevs) {
TRACE("Asked for device %d, but only %d known!\n", wDevID, ALSA_WodNumDevs);
return MMSYSERR_BADDEVICEID;
}
if (WOutDev[wDevID].pcm == NULL) {
WARN("Requested to breakloop of closed device %d!\n", wDevID);
return MMSYSERR_BADDEVICEID;
}
ALSA_AddRingMessage(&WOutDev[wDevID].msgRing, WINE_WM_BREAKLOOP, 0, TRUE);
return MMSYSERR_NOERROR;
}
/**************************************************************************
* wodGetVolume [internal]
*/
static DWORD wodGetVolume(WORD wDevID, LPDWORD lpdwVol)
{
WORD wleft, wright;
WINE_WAVEDEV* wwo;
int min, max;
int left, right;
DWORD rc;
TRACE("(%u, %p);\n", wDevID, lpdwVol);
if (wDevID >= ALSA_WodNumDevs) {
TRACE("Asked for device %d, but only %d known!\n", wDevID, ALSA_WodNumDevs);
return MMSYSERR_BADDEVICEID;
}
if (lpdwVol == NULL)
return MMSYSERR_NOTENABLED;
wwo = &WOutDev[wDevID];
rc = ALSA_CheckSetVolume(wwo->hctl, &left, &right, &min, &max, NULL, NULL, NULL);
if (rc == MMSYSERR_NOERROR)
{
#define VOLUME_ALSA_TO_WIN(x) ( ( (((x)-min) * 65535) + (max-min)/2 ) /(max-min))
wleft = VOLUME_ALSA_TO_WIN(left);
wright = VOLUME_ALSA_TO_WIN(right);
#undef VOLUME_ALSA_TO_WIN
TRACE("left=%d,right=%d,converted to windows left %d, right %d\n", left, right, wleft, wright);
*lpdwVol = MAKELONG( wleft, wright );
}
else
TRACE("CheckSetVolume failed; rc %d\n", rc);
return rc;
}
/**************************************************************************
* wodSetVolume [internal]
*/
static DWORD wodSetVolume(WORD wDevID, DWORD dwParam)
{
WORD wleft, wright;
WINE_WAVEDEV* wwo;
int min, max;
int left, right;
DWORD rc;
TRACE("(%u, %08X);\n", wDevID, dwParam);
if (wDevID >= ALSA_WodNumDevs) {
TRACE("Asked for device %d, but only %d known!\n", wDevID, ALSA_WodNumDevs);
return MMSYSERR_BADDEVICEID;
}
wwo = &WOutDev[wDevID];
rc = ALSA_CheckSetVolume(wwo->hctl, NULL, NULL, &min, &max, NULL, NULL, NULL);
if (rc == MMSYSERR_NOERROR)
{
wleft = LOWORD(dwParam);
wright = HIWORD(dwParam);
#define VOLUME_WIN_TO_ALSA(x) ( ( ( ((x) * (max-min)) + 32767) / 65535) + min )
left = VOLUME_WIN_TO_ALSA(wleft);
right = VOLUME_WIN_TO_ALSA(wright);
#undef VOLUME_WIN_TO_ALSA
rc = ALSA_CheckSetVolume(wwo->hctl, NULL, NULL, NULL, NULL, NULL, &left, &right);
if (rc == MMSYSERR_NOERROR)
TRACE("set volume: wleft=%d, wright=%d, converted to alsa left %d, right %d\n", wleft, wright, left, right);
else
TRACE("SetVolume failed; rc %d\n", rc);
}
return rc;
}
/**************************************************************************
* wodGetNumDevs [internal]
*/
static DWORD wodGetNumDevs(void)
{
return ALSA_WodNumDevs;
}
/**************************************************************************
* wodDevInterfaceSize [internal]
*/
static DWORD wodDevInterfaceSize(UINT wDevID, LPDWORD dwParam1)
{
TRACE("(%u, %p)\n", wDevID, dwParam1);
*dwParam1 = MultiByteToWideChar(CP_UNIXCP, 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_UNIXCP, 0, WOutDev[wDevID].interface_name, -1,
NULL, 0 ) * sizeof(WCHAR))
{
MultiByteToWideChar(CP_UNIXCP, 0, WOutDev[wDevID].interface_name, -1,
dwParam1, dwParam2 / sizeof(WCHAR));
return MMSYSERR_NOERROR;
}
return MMSYSERR_INVALPARAM;
}
/**************************************************************************
* wodMessage (WINEALSA.@)
*/
DWORD WINAPI ALSA_wodMessage(UINT wDevID, UINT wMsg, DWORD_PTR dwUser,
DWORD_PTR dwParam1, DWORD_PTR dwParam2)
{
TRACE("(%u, %s, %08lX, %08lX, %08lX);\n",
wDevID, ALSA_getMessage(wMsg), dwUser, dwParam1, dwParam2);
switch (wMsg) {
case DRVM_INIT:
ALSA_WaveInit();
case DRVM_EXIT:
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_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_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_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;
}
......@@ -2,9 +2,6 @@
@ stdcall -private DriverProc(long long long long long) ALSA_DriverProc
@ stdcall -private midMessage(long long long long long) ALSA_midMessage
@ stdcall -private modMessage(long long long long long) ALSA_modMessage
@ stdcall -private mxdMessage(long long long long long) ALSA_mxdMessage
@ stdcall -private widMessage(long long long long long) ALSA_widMessage
@ stdcall -private wodMessage(long long long long long) ALSA_wodMessage
# MMDevAPI driver functions
@ stdcall -private GetPriority() AUDDRV_GetPriority
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment