📄 audio_05.c
字号:
/* -*- tab-width: 8; c-basic-offset: 4 -*- *//* * Sample Wine Driver for Advanced Linux Sound System (ALSA) * Based on version 0.5 of the ALSA API * * Copyright 2002 Eric Pouech * 2002 David Hammerton * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */#include "config.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 <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 "dsound.h"#include "dsdriver.h"#include "alsa.h"#include "wine/debug.h"WINE_DEFAULT_DEBUG_CHANNEL(wave);#if defined(HAVE_ALSA) && (SND_LIB_MAJOR == 0) && (SND_LIB_MINOR == 5)#define MAX_WAVEOUTDRV (1)#define MAX_WAVEINDRV (1)/* 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};typedef struct { enum win_wm_message msg; /* message identifier */ DWORD 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 { /* FIXME: this could be made a dynamically growing array (if needed) */#define ALSA_RING_BUFFER_SIZE 30 ALSA_MSG messages[ALSA_RING_BUFFER_SIZE]; int msg_tosave; int msg_toget; HANDLE msg_event; CRITICAL_SECTION msg_crst;} ALSA_MSG_RING;typedef struct { /* Windows information */ volatile int state; /* one of the WINE_WS_ manifest constants */ WAVEOPENDESC waveDesc; WORD wFlags; PCMWAVEFORMAT format; WAVEOUTCAPSA caps; /* ALSA information */ snd_pcm_t* handle; /* handle to ALSA device */ DWORD dwFragmentSize; /* size of ALSA buffer fragment */ 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 */ DWORD dwPartialOffset; /* Offset of not yet written bytes in lpPlayPtr */ LPWAVEHDR lpLoopPtr; /* pointer of first buffer in loop, if any */ DWORD dwLoops; /* private copy of loop counter */ DWORD dwPlayedTotal; /* number of bytes actually played since opening */ DWORD dwWrittenTotal; /* number of bytes written to ALSA buffer since opening */ /* synchronization stuff */ HANDLE hStartUpEvent; HANDLE hThread; DWORD dwThreadID; ALSA_MSG_RING msgRing; /* DirectSound stuff */ void* mmap_buffer; snd_pcm_mmap_control_t* mmap_control; unsigned mmap_block_size; unsigned mmap_block_number;} WINE_WAVEOUT;static WINE_WAVEOUT WOutDev [MAX_WAVEOUTDRV];static DWORD ALSA_WodNumDevs;static DWORD wodDsCreate(UINT wDevID, PIDSDRIVER* drv);/* These strings used only for tracing */static const char *wodPlayerCmdString[] = { "WINE_WM_PAUSING", "WINE_WM_RESTARTING", "WINE_WM_RESETTING", "WINE_WM_HEADER", "WINE_WM_UPDATE", "WINE_WM_BREAKLOOP", "WINE_WM_CLOSING",};/*======================================================================* * Low level WAVE implementation * *======================================================================*//****************************************************************** * ALSA_WaveInit * * Initialize internal structures from ALSA information */LONG ALSA_WaveInit(void){ snd_pcm_t* h; snd_pcm_info_t info; snd_pcm_channel_info_t chn_info; TRACE("There are %d cards\n", snd_cards()); ALSA_WodNumDevs = 0; if (snd_pcm_open(&h, 0, 0, SND_PCM_OPEN_DUPLEX|SND_PCM_OPEN_NONBLOCK)) { ERR("Error open: %s\n", snd_strerror(errno)); return -1; } if (snd_pcm_info(h, &info)) { ERR("Error info: %s\n", snd_strerror(errno)); return -1; } ALSA_WodNumDevs++; TRACE("type=%u, flags=%s%s%s name=%s #pb=%d cp=%d\n", info.type, (info.flags & SND_PCM_INFO_PLAYBACK) ? "playback " : "", (info.flags & SND_PCM_INFO_PLAYBACK) ? "capture " : "", (info.flags & SND_PCM_INFO_DUPLEX) ? "duplex " : "", info.name, info.playback, info.capture); memset(&chn_info, 0, sizeof(chn_info)); if (snd_pcm_channel_info(h, &chn_info)) { ERR("Error chn info: %s\n", snd_strerror(errno)); return -1; }#define X(f,s) ((chn_info.flags & (f)) ? #s " " : "")#define Y(f,s) ((chn_info.rates & (f)) ? #s " " : "") TRACE("subdevice=%d name=%s chn=%d mode=%d\n" "\tflags=%s%s%s%s%s%s%s%s%s%s%s\n" "\tfmts=%u rates=%s%s%s%s%s%s%s%s%s%s%s%s%s\n" "\trates=[%d,%d] voices=[%d,%d] buf_size=%d fg_size=[%d,%d] fg_align=%u\n", chn_info.subdevice, chn_info.subname, chn_info.channel, chn_info.mode, X(SND_PCM_CHNINFO_MMAP,MMAP), X(SND_PCM_CHNINFO_STREAM,STREAM), X(SND_PCM_CHNINFO_BLOCK,BLOCK), X(SND_PCM_CHNINFO_BATCH,BATCH), X(SND_PCM_CHNINFO_INTERLEAVE,INTERLEAVE), X(SND_PCM_CHNINFO_NONINTERLEAVE,NONINTERLEAVE), X(SND_PCM_CHNINFO_BLOCK_TRANSFER,BLOCK_TRANSFER), X(SND_PCM_CHNINFO_OVERRANGE,OVERRANGE), X(SND_PCM_CHNINFO_MMAP_VALID,MMAP_VALID), X(SND_PCM_CHNINFO_PAUSE,PAUSE), X(SND_PCM_CHNINFO_GLOBAL_PARAMS,GLOBAL_PARAMS), chn_info.formats, Y(SND_PCM_RATE_CONTINUOUS,CONTINUOUS), Y(SND_PCM_RATE_KNOT,KNOT), Y(SND_PCM_RATE_8000,8000), Y(SND_PCM_RATE_11025,11025), Y(SND_PCM_RATE_16000,16000), Y(SND_PCM_RATE_22050,22050), Y(SND_PCM_RATE_32000,32000), Y(SND_PCM_RATE_44100,44100), Y(SND_PCM_RATE_48000,48000), Y(SND_PCM_RATE_88200,88200), Y(SND_PCM_RATE_96000,96000), Y(SND_PCM_RATE_176400,176400), Y(SND_PCM_RATE_192000,192000), chn_info.min_rate, chn_info.max_rate, chn_info.min_voices, chn_info.max_voices, chn_info.buffer_size, chn_info.min_fragment_size, chn_info.max_fragment_size, chn_info.fragment_align);#undef X#undef Y /* FIXME: use better values */ WOutDev[0].caps.wMid = 0x0002; WOutDev[0].caps.wPid = 0x0104; strcpy(WOutDev[0].caps.szPname, "SB16 Wave Out"); WOutDev[0].caps.vDriverVersion = 0x0100; WOutDev[0].caps.dwFormats = 0x00000000; WOutDev[0].caps.dwSupport = WAVECAPS_VOLUME;#define X(r,v) \ if (chn_info.rates & SND_PCM_RATE_##r) \ { \ if (chn_info.formats & SND_PCM_FMT_U8) \ { \ if (chn_info.min_voices <= 1 && 1 <= chn_info.max_voices) \ WOutDev[0].caps.dwFormats |= WAVE_FORMAT_##v##S08; \ if (chn_info.min_voices <= 2 && 2 <= chn_info.max_voices) \ WOutDev[0].caps.dwFormats |= WAVE_FORMAT_##v##S08; \ } \ if (chn_info.formats & SND_PCM_FMT_S16_LE) \ { \ if (chn_info.min_voices <= 1 && 1 <= chn_info.max_voices) \ WOutDev[0].caps.dwFormats |= WAVE_FORMAT_##v##S16; \ if (chn_info.min_voices <= 2 && 2 <= chn_info.max_voices) \ WOutDev[0].caps.dwFormats |= WAVE_FORMAT_##v##S16; \ } \ } X(11025,1); X(22050,2); X(44100,4);#undef X if (chn_info.min_voices > 1) FIXME("-"); WOutDev[0].caps.wChannels = (chn_info.max_voices >= 2) ? 2 : 1; if (chn_info.min_voices <= 2 && 2 <= chn_info.max_voices) WOutDev[0].caps.dwSupport |= WAVECAPS_LRVOLUME; /* FIXME: always true ? */ WOutDev[0].caps.dwSupport |= WAVECAPS_SAMPLEACCURATE; /* FIXME: is test sufficient ? */ if (chn_info.flags & SND_PCM_CHNINFO_MMAP) WOutDev[0].caps.dwSupport |= WAVECAPS_DIRECTSOUND; TRACE("Configured with dwFmts=%08lx dwSupport=%08lx\n", WOutDev[0].caps.dwFormats, WOutDev[0].caps.dwSupport); snd_pcm_close(h); return 0;}/****************************************************************** * ALSA_InitRingMessage * * Initialize the ring of messages for passing between driver's caller and playback/record * thread */static int ALSA_InitRingMessage(ALSA_MSG_RING* omr){ omr->msg_toget = 0; omr->msg_tosave = 0; omr->msg_event = CreateEventA(NULL, FALSE, FALSE, NULL); memset(omr->messages, 0, sizeof(ALSA_MSG) * ALSA_RING_BUFFER_SIZE); InitializeCriticalSection(&omr->msg_crst); return 0;}/****************************************************************** * ALSA_DestroyRingMessage * */static int ALSA_DestroyRingMessage(ALSA_MSG_RING* omr){ CloseHandle(omr->msg_event); DeleteCriticalSection(&omr->msg_crst); return 0;}/****************************************************************** * ALSA_AddRingMessage * * Inserts a new message into the ring (should be called from DriverProc derivated routines) */static int ALSA_AddRingMessage(ALSA_MSG_RING* omr, enum win_wm_message msg, DWORD param, BOOL wait){ HANDLE hEvent = INVALID_HANDLE_VALUE; EnterCriticalSection(&omr->msg_crst); if ((omr->msg_toget == ((omr->msg_tosave + 1) % ALSA_RING_BUFFER_SIZE))) /* buffer overflow ? */ { ERR("buffer overflow !?\n"); LeaveCriticalSection(&omr->msg_crst); return 0; } if (wait) { hEvent = CreateEventA(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!!!!\n"); /* fast messages have to be added at the start of the queue */ omr->msg_toget = (omr->msg_toget + ALSA_RING_BUFFER_SIZE - 1) % ALSA_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) % ALSA_RING_BUFFER_SIZE; } LeaveCriticalSection(&omr->msg_crst); /* signal a new message */ SetEvent(omr->msg_event); 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. */static int ALSA_RetrieveRingMessage(ALSA_MSG_RING* omr, enum win_wm_message *msg, DWORD *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) % ALSA_RING_BUFFER_SIZE; LeaveCriticalSection(&omr->msg_crst); return 1;}/*======================================================================* * Low level WAVE OUT implementation * *======================================================================*//************************************************************************** * wodNotifyClient [internal] */static DWORD wodNotifyClient(WINE_WAVEOUT* wwo, WORD wMsg, DWORD dwParam1, DWORD dwParam2){ TRACE("wMsg = 0x%04x dwParm1 = %04lX dwParam2 = %04lX\n", wMsg, dwParam1, dwParam2); switch (wMsg) { case WOM_OPEN: case WOM_CLOSE: case WOM_DONE: if (wwo->wFlags != DCB_NULL && !DriverCallback(wwo->waveDesc.dwCallback, wwo->wFlags, wwo->waveDesc.hWave, wMsg, wwo->waveDesc.dwInstance, dwParam1, dwParam2)) { WARN("can't notify client !\n"); return MMSYSERR_ERROR; } break; default: FIXME("Unknown callback message %u\n", wMsg); return MMSYSERR_INVALPARAM; } return MMSYSERR_NOERROR;}/************************************************************************** * wodUpdatePlayedTotal [internal] * */static BOOL wodUpdatePlayedTotal(WINE_WAVEOUT* wwo, snd_pcm_channel_status_t* ps){ snd_pcm_channel_status_t s; snd_pcm_channel_status_t* status = (ps) ? ps : &s; if (snd_pcm_channel_status(wwo->handle, status)) { ERR("Can't get channel status: %s\n", snd_strerror(errno)); return FALSE; } wwo->dwPlayedTotal = wwo->dwWrittenTotal - (wwo->dwBufferSize - status->count); if (wwo->dwPlayedTotal != status->scount) { FIXME("Ooch: %u played by ALSA, %lu counted by driver\n", status->scount, wwo->dwPlayedTotal); if (wwo->dwPlayedTotal & 0x8000000) wwo->dwPlayedTotal = 0;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -