📄 audio.c
字号:
/****************************************************************** * ALSA_WaveInit * * Initialize internal structures from ALSA information */LONG ALSA_WaveInit(void){ snd_pcm_t* h = NULL; snd_pcm_info_t * info; snd_pcm_hw_params_t * hw_params; WINE_WAVEOUT* wwo; wwo = &WOutDev[0]; /* FIXME: use better values */ wwo->device = FAKE_CHARPTR("hw"); wwo->caps.wMid = 0x0002; wwo->caps.wPid = 0x0104; strcpy(wwo->caps.szPname, "SB16 Wave Out"); wwo->caps.vDriverVersion = 0x0100; wwo->caps.dwFormats = 0x00000000; wwo->caps.dwSupport = WAVECAPS_VOLUME; strcpy(wwo->ds_desc.szDesc, "WineALSA DirectSound Driver"); strcpy(wwo->ds_desc.szDrvName, "winealsa.drv"); wwo->ds_guid = DSDEVID_DefaultPlayback; if (!wine_dlopen("libasound.so.2", RTLD_LAZY|RTLD_GLOBAL, NULL, 0)) { ERR("Error: ALSA lib needs to be loaded with flags RTLD_LAZY and RTLD_GLOBAL.\n"); return -1; } snd_pcm_info_alloca(&info); snd_pcm_hw_params_alloca(&hw_params);#define EXIT_ON_ERROR(f,txt) do { int err; if ( (err = (f) ) < 0) { ERR(txt ": %s\n", snd_strerror(err)); if (h) snd_pcm_close(h); return -1; } } while(0) ALSA_WodNumDevs = 0; EXIT_ON_ERROR( snd_pcm_open(&h, wwo->device, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK) , "open pcm" ); if (!h) return -1; ALSA_WodNumDevs++; EXIT_ON_ERROR( snd_pcm_info(h, info) , "pcm info" ); TRACE("dev=%d id=%s name=%s subdev=%d subdev_name=%s subdev_avail=%d subdev_num=%d stream=%s subclass=%s \n", snd_pcm_info_get_device(info), snd_pcm_info_get_id(info), snd_pcm_info_get_name(info), snd_pcm_info_get_subdevice(info), snd_pcm_info_get_subdevice_name(info), snd_pcm_info_get_subdevices_avail(info), snd_pcm_info_get_subdevices_count(info), snd_pcm_stream_name(snd_pcm_info_get_stream(info)), (snd_pcm_info_get_subclass(info) == SND_PCM_SUBCLASS_GENERIC_MIX ? "GENERIC MIX": "MULTI MIX")); EXIT_ON_ERROR( snd_pcm_hw_params_any(h, hw_params) , "pcm hw params" );#undef EXIT_ON_ERROR if (TRACE_ON(wave)) ALSA_TraceParameters(hw_params, NULL, TRUE); { snd_pcm_format_mask_t * fmask; int ratemin = snd_pcm_hw_params_get_rate_min(hw_params, 0); int ratemax = snd_pcm_hw_params_get_rate_max(hw_params, 0); int chmin = snd_pcm_hw_params_get_channels_min(hw_params); \ int chmax = snd_pcm_hw_params_get_channels_max(hw_params); \ snd_pcm_format_mask_alloca(&fmask); snd_pcm_hw_params_get_format_mask(hw_params, fmask);#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) \ wwo->caps.dwFormats |= WAVE_FORMAT_##v##S08; \ if (chmin <= 2 && 2 <= chmax) \ wwo->caps.dwFormats |= WAVE_FORMAT_##v##S08; \ } \ if (snd_pcm_format_mask_test( fmask, SND_PCM_FORMAT_S16_LE)) \ { \ if (chmin <= 1 && 1 <= chmax) \ wwo->caps.dwFormats |= WAVE_FORMAT_##v##S16; \ if (chmin <= 2 && 2 <= chmax) \ wwo->caps.dwFormats |= WAVE_FORMAT_##v##S16; \ } \ } X(11025,1); X(22050,2); X(44100,4);#undef X } if ( snd_pcm_hw_params_get_channels_min(hw_params) > 1) FIXME("-\n"); wwo->caps.wChannels = (snd_pcm_hw_params_get_channels_max(hw_params) >= 2) ? 2 : 1; if (snd_pcm_hw_params_get_channels_min(hw_params) <= 2 && 2 <= snd_pcm_hw_params_get_channels_max(hw_params)) wwo->caps.dwSupport |= WAVECAPS_LRVOLUME; /* FIXME: always true ? */ wwo->caps.dwSupport |= WAVECAPS_SAMPLEACCURATE; { snd_pcm_access_mask_t * acmask; snd_pcm_access_mask_alloca(&acmask); snd_pcm_hw_params_get_access_mask(hw_params, acmask); /* FIXME: NONITERLEAVED and COMPLEX are not supported right now */ if ( snd_pcm_access_mask_test( acmask, SND_PCM_ACCESS_MMAP_INTERLEAVED ) ) wwo->caps.dwSupport |= WAVECAPS_DIRECTSOUND; } TRACE("Configured with dwFmts=%08lx dwSupport=%08lx\n", wwo->caps.dwFormats, wwo->caps.dwSupport); snd_pcm_close(h); ALSA_InitializeVolumeCtl(wwo); 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); 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); return 0;}/****************************************************************** * ALSA_DestroyRingMessage * */static int ALSA_DestroyRingMessage(ALSA_MSG_RING* omr){ CloseHandle(omr->msg_event); HeapFree(GetProcessHeap(),0,omr->messages); 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) % omr->ring_buffer_size))) { omr->ring_buffer_size += ALSA_RING_BUFFER_INCREMENT; TRACE("omr->ring_buffer_size=%d\n",omr->ring_buffer_size); omr->messages = HeapReAlloc(GetProcessHeap(),0,omr->messages, omr->ring_buffer_size * sizeof(ALSA_MSG)); } 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 + 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 */ 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) % omr->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, (HDRVR)wwo->waveDesc.hWave, wMsg, wwo->waveDesc.dwInstance, dwParam1, dwParam2)) { WARN("can't notify client !\n"); return MMSYSERR_ERROR; } break; default: FIXME("Unknown callback message %u\n", wMsg); return MMSYSERR_INVALPARAM; } return MMSYSERR_NOERROR;}/************************************************************************** * wodUpdatePlayedTotal [internal] * */static BOOL wodUpdatePlayedTotal(WINE_WAVEOUT* wwo, snd_pcm_status_t* ps){ 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_WAVEOUT* wwo, LPWAVEHDR lpWaveHdr){ wwo->lpPlayPtr = lpWaveHdr; if (!lpWaveHdr) return; wwo->lpPlayPtr->reserved = 0; 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 (%ldx) with %p\n", lpWaveHdr->dwLoops, lpWaveHdr); wwo->lpLoopPtr = lpWaveHdr; /* Windows does not touch WAVEHDR.dwLoops, * so we need to make an internal copy */ wwo->dwLoops = lpWaveHdr->dwLoops; } }}/************************************************************************** * wodPlayer_PlayPtrNext [internal] * * Advance the play pointer to the next waveheader, looping if required. */static LPWAVEHDR wodPlayer_PlayPtrNext(WINE_WAVEOUT* wwo){ LPWAVEHDR lpWaveHdr = wwo->lpPlayPtr; 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; wwo->lpPlayPtr->reserved = 0; } 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_WAVEOUT *wwo){ /* time for one period to be played */ return snd_pcm_hw_params_get_period_time(wwo->hw_params, 0) / 1000;}/************************************************************************** * wodPllayer_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_WAVEOUT* wwo, LPWAVEHDR lpWaveHdr){ DWORD dwMillis; dwMillis = (lpWaveHdr->dwBufferLength - lpWaveHdr->reserved) * 1000 / wwo->format.wf.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_WAVEOUT* wwo, DWORD* frames){ /* Only attempt to write to free frames */ LPWAVEHDR lpWaveHdr = wwo->lpPlayPtr; DWORD dwLength = snd_pcm_bytes_to_frames(wwo->p_handle, lpWaveHdr->dwBufferLength - lpWaveHdr->reserved); int toWrite = min(dwLength, *frames); int written; TRACE("Writing wavehdr %p.%lu[%lu]\n", lpWaveHdr, lpWaveHdr->reserved, lpWaveHdr->dwBufferLength); written = (wwo->write)(wwo->p_handle, lpWaveHdr->lpData + lpWaveHdr->reserved, toWrite); if ( written < 0) { /* XRUN occurred. let's try to recover */ ALSA_XRUNRecovery(wwo, written); written = (wwo->write)(wwo->p_handle, lpWaveHdr->lpData + lpWaveHdr->reserved, toWrite); } if (written <= 0) { /* still in error */ ERR("Error in writing wavehdr. Reason: %s\n", snd_strerror(written)); return written; } lpWaveHdr->reserved += snd_pcm_frames_to_bytes(wwo->p_handle, written); if ( lpWaveHdr->reserved >= lpWaveHdr->dwBufferLength) {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -