|  | /* | 
|  | * 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 "mmddk.h" | 
|  |  | 
|  | #include "ks.h" | 
|  | #include "guiddef.h" | 
|  | #include "ksmedia.h" | 
|  |  | 
|  | #include "alsa.h" | 
|  |  | 
|  | #ifdef HAVE_ALSA | 
|  |  | 
|  | #include "wine/library.h" | 
|  | #include "wine/unicode.h" | 
|  | #include "wine/debug.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 = INVALID_HANDLE_VALUE; | 
|  |  | 
|  | 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 == INVALID_HANDLE_VALUE) | 
|  | { | 
|  | 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 = INVALID_HANDLE_VALUE; | 
|  | 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; | 
|  | if (iLength > sizeof(WAVEFORMATPCMEX)) { | 
|  | ERR("calculated %u bytes, capping\n", iLength); | 
|  | iLength = sizeof(WAVEFORMATPCMEX); | 
|  | } | 
|  | 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 if (wf->wFormatTag == WAVE_FORMAT_MULAW || wf->wFormatTag == WAVE_FORMAT_ALAW) { | 
|  | if (wf->wBitsPerSample==8) | 
|  | return TRUE; | 
|  | else | 
|  | ERR("WAVE_FORMAT_MULAW and WAVE_FORMAT_ALAW wBitsPerSample must = 8\n"); | 
|  |  | 
|  | } else if (wf->wFormatTag == WAVE_FORMAT_ADPCM) { | 
|  | if (wf->wBitsPerSample==4) | 
|  | return TRUE; | 
|  | else | 
|  | ERR("WAVE_FORMAT_ADPCM wBitsPerSample must = 4\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; | 
|  |  | 
|  |  | 
|  | #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 */ | 
|  | snd_ctl_elem_id_set_name(elemidp, "PCM Playback Volume"); | 
|  | 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); | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | ERR("Could not find 'PCM 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; | 
|  | } | 
|  |  | 
|  | #endif | 
|  |  | 
|  | /************************************************************************** | 
|  | * 				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) { | 
|  | #ifdef HAVE_ALSA | 
|  | 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; | 
|  | #endif | 
|  | default: | 
|  | return 0; | 
|  | } | 
|  | } |