📄 audio.c
字号:
/* this will be used to check if the given wave header has been fully played or not... */ lpWaveHdr->reserved = lpWaveHdr->dwBufferLength; /* If we wrote all current wavehdr, skip to the next one */ wodPlayer_PlayPtrNext(wwo); } *frames -= written; wwo->dwPlayedTotal += snd_pcm_frames_to_bytes(wwo->p_handle, written); 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_WAVEOUT* 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 */ while ((lpWaveHdr = wwo->lpQueuePtr) && (force || (lpWaveHdr != wwo->lpPlayPtr && lpWaveHdr != wwo->lpLoopPtr && lpWaveHdr->reserved == lpWaveHdr->dwBufferLength))) { wwo->lpQueuePtr = lpWaveHdr->lpNext; lpWaveHdr->dwFlags &= ~WHDR_INQUEUE; lpWaveHdr->dwFlags |= WHDR_DONE; wodNotifyClient(wwo, WOM_DONE, (DWORD)lpWaveHdr, 0); } return (lpWaveHdr && lpWaveHdr != wwo->lpPlayPtr && lpWaveHdr != wwo->lpLoopPtr) ? wodPlayer_NotifyWait(wwo, lpWaveHdr) : INFINITE;}void wait_for_poll(snd_pcm_t *handle, struct pollfd *ufds, unsigned int count){ unsigned short revents; if (snd_pcm_state(handle) != SND_PCM_STATE_RUNNING) return; while (1) { poll(ufds, count, -1); snd_pcm_poll_descriptors_revents(handle, ufds, count, &revents); if (revents & POLLERR) return; /*if (revents & POLLOUT) return 0;*/ }}/************************************************************************** * wodPlayer_Reset [internal] * * wodPlayer helper. Resets current output stream. */static void wodPlayer_Reset(WINE_WAVEOUT* wwo){ enum win_wm_message msg; DWORD param; HANDLE ev; int err; /* flush all possible output */ wait_for_poll(wwo->p_handle, wwo->ufds, wwo->count); /* updates current notify list */ wodPlayer_NotifyCompletions(wwo, FALSE); if ( (err = snd_pcm_drop(wwo->p_handle)) < 0) { FIXME("flush: %s\n", snd_strerror(err)); wwo->hThread = 0; wwo->state = WINE_WS_STOPPED; ExitThread(-1); } if ( (err = snd_pcm_prepare(wwo->p_handle)) < 0 ) ERR("pcm prepare failed: %s\n", snd_strerror(err)); /* remove any buffer */ wodPlayer_NotifyCompletions(wwo, TRUE); wwo->lpPlayPtr = wwo->lpQueuePtr = wwo->lpLoopPtr = NULL; wwo->state = WINE_WS_STOPPED; /* remove any existing message in the ring */ EnterCriticalSection(&wwo->msgRing.msg_crst); /* return all pending headers in queue */ while (ALSA_RetrieveRingMessage(&wwo->msgRing, &msg, ¶m, &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); } ResetEvent(wwo->msgRing.msg_event); LeaveCriticalSection(&wwo->msgRing.msg_crst);}/************************************************************************** * wodPlayer_ProcessMessages [internal] */static void wodPlayer_ProcessMessages(WINE_WAVEOUT* wwo){ LPWAVEHDR lpWaveHdr; enum win_wm_message msg; DWORD param; HANDLE ev; int err; while (ALSA_RetrieveRingMessage(&wwo->msgRing, &msg, ¶m, &ev)) { /* TRACE("Received %s %lx\n", wodPlayerCmdString[msg - WM_USER - 1], param); */ switch (msg) { case WINE_WM_PAUSING: if ( snd_pcm_state(wwo->p_handle) == SND_PCM_STATE_RUNNING ) { err = snd_pcm_pause(wwo->p_handle, 1); if ( err < 0 ) ERR("pcm_pause failed: %s\n", snd_strerror(err)); } wwo->state = WINE_WS_PAUSED; SetEvent(ev); break; case WINE_WM_RESTARTING: if (wwo->state == WINE_WS_PAUSED) { if ( snd_pcm_state(wwo->p_handle) == SND_PCM_STATE_PAUSED ) { err = snd_pcm_pause(wwo->p_handle, 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); SetEvent(ev); break; case WINE_WM_UPDATE: wodUpdatePlayedTotal(wwo, NULL); 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_WAVEOUT* wwo){ DWORD availInQ = snd_pcm_avail_update(wwo->p_handle); /* no more room... no need to try to feed */ while (wwo->lpPlayPtr && availInQ > 0) if ( wodPlayer_WriteMaxFrags(wwo, &availInQ) < 0 ) break; return wodPlayer_DSPWait(wwo);}/************************************************************************** * wodPlayer [internal] */static DWORD CALLBACK wodPlayer(LPVOID pmt){ WORD uDevID = (DWORD)pmt; WINE_WAVEOUT* wwo = (WINE_WAVEOUT*)&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 %lums (%lu,%lu)\n", dwSleepTime, dwNextFeedTime, dwNextNotifyTime); WaitForSingleObject(wwo->msgRing.msg_event, dwSleepTime); wodPlayer_ProcessMessages(wwo); if (wwo->state == WINE_WS_PLAYING) { dwNextFeedTime = wodPlayer_FeedDSP(wwo); dwNextNotifyTime = wodPlayer_NotifyCompletions(wwo, FALSE); } else { dwNextFeedTime = dwNextNotifyTime = INFINITE; } }}/************************************************************************** * wodGetDevCaps [internal] */static DWORD wodGetDevCaps(WORD wDevID, LPWAVEOUTCAPSA lpCaps, DWORD dwSize){ TRACE("(%u, %p, %lu);\n", wDevID, lpCaps, dwSize); if (lpCaps == NULL) return MMSYSERR_NOTENABLED; if (wDevID >= MAX_WAVEOUTDRV) { TRACE("MAX_WAVOUTDRV reached !\n"); return MMSYSERR_BADDEVICEID; } memcpy(lpCaps, &WOutDev[wDevID].caps, min(dwSize, sizeof(*lpCaps))); return MMSYSERR_NOERROR;}/************************************************************************** * wodOpen [internal] */static DWORD wodOpen(WORD wDevID, LPWAVEOPENDESC lpDesc, DWORD dwFlags){ WINE_WAVEOUT* wwo; snd_pcm_hw_params_t * hw_params; snd_pcm_sw_params_t * sw_params; snd_pcm_access_t access; snd_pcm_format_t format; int rate; unsigned int buffer_time = 500000; unsigned int period_time = 10000; int buffer_size; snd_pcm_uframes_t period_size; int flags; snd_pcm_t * pcm; int err; snd_pcm_hw_params_alloca(&hw_params); snd_pcm_sw_params_alloca(&sw_params); TRACE("(%u, %p, %08lX);\n", wDevID, lpDesc, dwFlags); if (lpDesc == NULL) { WARN("Invalid Parameter !\n"); return MMSYSERR_INVALPARAM; } if (wDevID >= MAX_WAVEOUTDRV) { TRACE("MAX_WAVOUTDRV reached !\n"); return MMSYSERR_BADDEVICEID; } /* only PCM format is supported so far... */ if (lpDesc->lpFormat->wFormatTag != WAVE_FORMAT_PCM || lpDesc->lpFormat->nChannels == 0 || lpDesc->lpFormat->nSamplesPerSec == 0) { WARN("Bad format: tag=%04X nChannels=%d nSamplesPerSec=%ld !\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=%ld !\n", lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels, lpDesc->lpFormat->nSamplesPerSec); return MMSYSERR_NOERROR; } wwo = &WOutDev[wDevID]; if ((dwFlags & WAVE_DIRECTSOUND) && !(wwo->caps.dwSupport & WAVECAPS_DIRECTSOUND)) /* not supported, ignore it */ dwFlags &= ~WAVE_DIRECTSOUND; wwo->p_handle = 0; flags = SND_PCM_NONBLOCK; if ( dwFlags & WAVE_DIRECTSOUND ) flags |= SND_PCM_ASYNC; if (snd_pcm_open(&pcm, wwo->device, SND_PCM_STREAM_PLAYBACK, dwFlags)) { ERR("Error open: %s\n", snd_strerror(errno)); return MMSYSERR_NOTENABLED; } wwo->wFlags = HIWORD(dwFlags & CALLBACK_TYPEMASK); memcpy(&wwo->waveDesc, lpDesc, sizeof(WAVEOPENDESC)); memcpy(&wwo->format, lpDesc->lpFormat, sizeof(PCMWAVEFORMAT)); if (wwo->format.wBitsPerSample == 0) { WARN("Resetting zeroed wBitsPerSample\n"); wwo->format.wBitsPerSample = 8 * (wwo->format.wf.nAvgBytesPerSec / wwo->format.wf.nSamplesPerSec) / wwo->format.wf.nChannels; } snd_pcm_hw_params_any(pcm, hw_params);#define EXIT_ON_ERROR(f,e,txt) do \{ \ int err; \ if ( (err = (f) ) < 0) \ { \ ERR(txt ": %s\n", snd_strerror(err)); \ snd_pcm_close(pcm); \ return e; \ } \} 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"); wwo->write = snd_pcm_writei; } else wwo->write = snd_pcm_mmap_writei; EXIT_ON_ERROR( snd_pcm_hw_params_set_channels(pcm, hw_params, wwo->format.wf.nChannels), MMSYSERR_INVALPARAM, "unable to set required channels"); format = (wwo->format.wBitsPerSample == 16) ? SND_PCM_FORMAT_S16_LE : SND_PCM_FORMAT_U8; EXIT_ON_ERROR( snd_pcm_hw_params_set_format(pcm, hw_params, format), MMSYSERR_INVALPARAM, "unable to set required format"); rate = snd_pcm_hw_params_set_rate_near(pcm, hw_params, wwo->format.wf.nSamplesPerSec, 0); if (rate < 0) { ERR("Rate %ld Hz not available for playback: %s\n", wwo->format.wf.nSamplesPerSec, snd_strerror(rate)); snd_pcm_close(pcm); return WAVERR_BADFORMAT; } if (rate != wwo->format.wf.nSamplesPerSec) { ERR("Rate doesn't match (requested %ld Hz, got %d Hz)\n", wwo->format.wf.nSamplesPerSec, rate); snd_pcm_close(pcm); return WAVERR_BADFORMAT; } EXIT_ON_ERROR( snd_pcm_hw_params_set_buffer_time_near(pcm, hw_params, buffer_time, 0), MMSYSERR_INVALPARAM, "unable to set buffer time"); EXIT_ON_ERROR( snd_pcm_hw_params_set_period_time_near(pcm, hw_params, period_time, 0), 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"); period_size = snd_pcm_hw_params_get_period_size(hw_params, 0); buffer_size = snd_pcm_hw_params_get_buffer_size(hw_params); snd_pcm_sw_params_current(pcm, sw_params); EXIT_ON_ERROR( snd_pcm_sw_params_set_start_threshold(pcm, sw_params, dwFlags & WAVE_DIRECTSOUND ? INT_MAX : 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");
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -