📄 audio.c
字号:
/************************************************************************** * wodPlayer_NotifyCompletions [internal] * * Notifies and remove from queue all wavehdrs which have been played to * the speaker (ie. they have cleared the OSS 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 <= wwo->dwPlayedTotal))) { 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;}/************************************************************************** * wodPlayer_Reset [internal] * * wodPlayer helper. Resets current output stream. */static void wodPlayer_Reset(WINE_WAVEOUT* wwo, BOOL reset){ wodUpdatePlayedTotal(wwo, NULL); /* updates current notify list */ wodPlayer_NotifyCompletions(wwo, FALSE); /* flush all possible output */ if (OSS_ResetDevice(wwo->ossdev) != MMSYSERR_NOERROR) { wwo->hThread = 0; wwo->state = WINE_WS_STOPPED; ExitThread(-1); } if (reset) { enum win_wm_message msg; DWORD 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 (OSS_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); } RESET_OMR(&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, except 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_WAVEOUT* wwo){ LPWAVEHDR lpWaveHdr; enum win_wm_message msg; DWORD param; HANDLE ev; while (OSS_RetrieveRingMessage(&wwo->msgRing, &msg, ¶m, &ev)) { TRACE("Received %s %lx\n", wodPlayerCmdString[msg - WM_USER - 1], param); switch (msg) { case WINE_WM_PAUSING: wodPlayer_Reset(wwo, FALSE); SetEvent(ev); break; case WINE_WM_RESTARTING: if (wwo->state == WINE_WS_PAUSED) { 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_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){ audio_buf_info dspspace; DWORD availInQ; wodUpdatePlayedTotal(wwo, &dspspace); availInQ = dspspace.bytes; TRACE("fragments=%d/%d, fragsize=%d, bytes=%d\n", dspspace.fragments, dspspace.fragstotal, dspspace.fragsize, dspspace.bytes); /* input queue empty and output buffer with less than one fragment to play * actually some cards do not play the fragment before the last if this one is partially feed * so we need to test for full the availability of 2 fragments */ if (!wwo->lpPlayPtr && wwo->dwBufferSize < availInQ + 2 * wwo->dwFragmentSize && !wwo->bNeedPost) { TRACE("Run out of wavehdr:s...\n"); return INFINITE; } /* no more room... no need to try to feed */ if (dspspace.fragments != 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 %lu\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); } if (wwo->bNeedPost) { /* OSS doesn't start before it gets either 2 fragments or a SNDCTL_DSP_POST; * if it didn't get one, we give it the other */ if (wwo->dwBufferSize < availInQ + 2 * wwo->dwFragmentSize) ioctl(wwo->ossdev->fd, SNDCTL_DSP_POST, 0); wwo->bNeedPost = FALSE; } } 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); WAIT_OMR(&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 flushing, */ /* check that a notification didn't give us more */ wodPlayer_ProcessMessages(wwo); if (!wwo->lpPlayPtr) { TRACE("flushing\n"); ioctl(wwo->ossdev->fd, SNDCTL_DSP_SYNC, 0); wwo->dwPlayedTotal = wwo->dwWrittenTotal; dwNextNotifyTime = wodPlayer_NotifyCompletions(wwo, FALSE); } else { TRACE("recovering\n"); dwNextFeedTime = wodPlayer_FeedDSP(wwo); } } } 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 >= numOutDev) { TRACE("numOutDev reached !\n"); return MMSYSERR_BADDEVICEID; } memcpy(lpCaps, &WOutDev[wDevID].ossdev->out_caps, min(dwSize, sizeof(*lpCaps))); return MMSYSERR_NOERROR;}/************************************************************************** * wodOpen [internal] */static DWORD wodOpen(WORD wDevID, LPWAVEOPENDESC lpDesc, DWORD dwFlags){ int audio_fragment; WINE_WAVEOUT* wwo; audio_buf_info info; DWORD ret; TRACE("(%u, %p[cb=%08lx], %08lX);\n", wDevID, lpDesc, lpDesc->dwCallback, dwFlags); if (lpDesc == NULL) { WARN("Invalid Parameter !\n"); return MMSYSERR_INVALPARAM; } if (wDevID >= numOutDev) { 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->ossdev->out_caps.dwSupport & WAVECAPS_DIRECTSOUND)) /* not supported, ignore it */ dwFlags &= ~WAVE_DIRECTSOUND; if (dwFlags & WAVE_DIRECTSOUND) { if (wwo->ossdev->out_caps.dwSupport & WAVECAPS_SAMPLEACCURATE) /* we have realtime DirectSound, fragments just waste our time, * but a large buffer is good, so choose 64KB (32 * 2^11) */ audio_fragment = 0x0020000B; else /* to approximate realtime, we must use small fragments, * let's try to fragment the above 64KB (256 * 2^8) */ audio_fragment = 0x01000008; } else { /* shockwave player uses only 4 1k-fragments at a rate of 22050 bytes/sec * thus leading to 46ms per fragment, and a turnaround time of 185ms */ /* 16 fragments max, 2^10=1024 bytes per fragment */ audio_fragment = 0x000F000A; } if (wwo->state != WINE_WS_CLOSED) return MMSYSERR_ALLOCATED; /* we want to be able to mmap() the device, which means it must be opened readable, * otherwise mmap() will fail (at least under Linux) */ ret = OSS_OpenDevice(wwo->ossdev, (dwFlags & WAVE_DIRECTSOUND) ? O_RDWR : O_WRONLY, &audio_fragment, (dwFlags & WAVE_DIRECTSOUND) ? 0 : 1, lpDesc->lpFormat->nSamplesPerSec, (lpDesc->lpFormat->nChannels > 1) ? 1 : 0, (lpDesc->lpFormat->wBitsPerSample == 16) ? AFMT_S16_LE : AFMT_U8); if ((ret==MMSYSERR_NOERROR) && (dwFlags & WAVE_DIRECTSOUND)) { lpDesc->lpFormat->nSamplesPerSec=wwo->ossdev->sample_rate; lpDesc->lpFormat->nChannels=(wwo->ossdev->stereo ? 2 : 1); lpDesc->lpFormat->wBitsPerSample=(wwo->ossdev->format == AFMT_U8 ? 8 : 16); lpDesc->lpFormat->nBlockAlign=lpDesc->lpFormat->nChannels*lpDesc->lpFormat->wBitsPerSample/8; lpDesc->lpFormat->nAvgBytesPerSec=lpDesc->lpFormat->nSamplesPerSec*lpDesc->lpFormat->nBlockAlign; TRACE("OSS_OpenDevice returned this format: %ldx%dx%d\n", lpDesc->lpFormat->nSamplesPerSec, lpDesc->lpFormat->wBitsPerSample, lpDesc->lpFormat->nChannels); } if (ret != 0) return ret; wwo->state = WINE_WS_STOPPED; wwo->wFlags = HIWORD(dwFlags & CALLBACK_TYPEMASK); memcpy(&wwo->waveDesc, lpDesc, sizeof(WAVEOPENDESC)); memcpy(&wwo->format, lpDesc->lpFormat, sizeof(PCMWAVEFORMAT));
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -