📄 audio.c
字号:
if (mr->msg_toget == mr->msg_tosave) /* buffer empty ? */ { LeaveCriticalSection(&mr->msg_crst); return 0; } *msg = mr->messages[mr->msg_toget].msg; mr->messages[mr->msg_toget].msg = 0; *param = mr->messages[mr->msg_toget].param; *hEvent = mr->messages[mr->msg_toget].hEvent; mr->msg_toget = (mr->msg_toget + 1) % mr->ring_buffer_size; LeaveCriticalSection(&mr->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){ wwo->PlayedTotal = wwo->WrittenTotal; 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; if (lpWaveHdr->dwFlags & WHDR_BEGINLOOP) { if (wwo->lpLoopPtr) { WARN("Already in a loop. Discarding loop on this header (%p)\n", lpWaveHdr); TRACE("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; } 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_NotifyCompletions [internal] * * Notifies and remove from queue all wavehdrs which have been played to * the speaker (ie. they have cleared the audio device). 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 */ wodUpdatePlayedTotal(wwo); while ((lpWaveHdr = wwo->lpQueuePtr) && (force || (lpWaveHdr != wwo->lpPlayPtr && lpWaveHdr != wwo->lpLoopPtr && lpWaveHdr->reserved <= wwo->PlayedTotal))) { 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) ? 1 : 1;}/************************************************************************** * wodPlayer_Reset [internal] * * wodPlayer helper. Resets current output stream. */static void wodPlayer_Reset(WINE_WAVEOUT* wwo, BOOL reset){ wodUpdatePlayedTotal(wwo); wodPlayer_NotifyCompletions(wwo, FALSE); /* updates current notify list */ /* we aren't able to flush any data that has already been written */ /* to nas, otherwise we would do the flushing here */ nas_free(wwo); 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->PlayedTotal = wwo->WrittenTotal = 0; /* remove any existing message in the ring */ EnterCriticalSection(&wwo->msgRing.msg_crst); /* return all pending headers in queue */ while (NAS_RetrieveRingMessage(&wwo->msgRing, &msg, ¶m, &ev)) { TRACE("flushing msg\n"); 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); } 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->WrittenTotal = wwo->PlayedTotal; /* this is wrong !!! */ } else { /* the data already written is going to be played, so take */ /* this fact into account here */ wwo->PlayedTotal = wwo->WrittenTotal; } 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 (NAS_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: 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); 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 [internal] */static DWORD CALLBACK wodPlayer(LPVOID pmt){ WORD uDevID = (DWORD)pmt; WINE_WAVEOUT* wwo = (WINE_WAVEOUT*)&WOutDev[uDevID]; wwo->state = WINE_WS_STOPPED; SetEvent(wwo->hStartUpEvent); for (;;) { if (wwo->FlowStarted) { AuHandleEvents(wwo->AuServ); if (wwo->state == WINE_WS_PLAYING && wwo->freeBytes && wwo->BufferUsed) nas_send_buffer(wwo); } if (wwo->BufferUsed <= FRAG_SIZE && wwo->writeBytes > 0) wodPlayer_NotifyCompletions(wwo, FALSE); WaitForSingleObject(wwo->msgRing.msg_event, 20); wodPlayer_ProcessMessages(wwo); while(wwo->lpPlayPtr) { wwo->lpPlayPtr->reserved = wwo->WrittenTotal + wwo->lpPlayPtr->dwBufferLength; nas_add_buffer(wwo); wodPlayer_PlayPtrNext(wwo); } }}/************************************************************************** * 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; TRACE("wodOpen (%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; } /* if this device is already open tell the app that it is allocated */ wwo = &WOutDev[wDevID]; if(wwo->open) { TRACE("device already allocated\n"); return MMSYSERR_ALLOCATED; } /* 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; } /* direct sound not supported, ignore the flag */ dwFlags &= ~WAVE_DIRECTSOUND; 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; } if (!nas_open(wwo)) return MMSYSERR_ALLOCATED; NAS_InitRingMessage(&wwo->msgRing); /* create player thread */ if (!(dwFlags & WAVE_DIRECTSOUND)) { wwo->hStartUpEvent = CreateEventA(NULL, FALSE, FALSE, NULL); wwo->hThread = CreateThread(NULL, 0, wodPlayer, (LPVOID)(DWORD)wDevID, 0, &(wwo->dwThreadID)); WaitForSingleObject(wwo->hStartUpEvent, INFINITE); CloseHandle(wwo->hStartUpEvent); } else { wwo->hThread = INVALID_HANDLE_VALUE; wwo->dwThreadID = 0; } wwo->hStartUpEvent = INVALID_HANDLE_VALUE; TRACE("stream=0x%lx, BufferSize=%ld\n", (long)wwo->AuServ, wwo->BufferSize); TRACE("wBitsPerSample=%u nAvgBytesPerSec=%lu nSamplesPerSec=%lu nChannels=%u nBlockAlign=%u\n", wwo->format.wBitsPerSample, wwo->format.wf.nAvgBytesPerSec, wwo->format.wf.nSamplesPerSec, wwo->format.wf.nChannels, wwo->format.wf.nBlockAlign); return wodNotifyClient(wwo, WOM_OPEN, 0L, 0L);}/************************************************************************** * wodClose [internal] */static DWORD wodClose(WORD wDevID){ DWORD ret = MMSYSERR_NOERROR; WINE_WAVEOUT* wwo; TRACE("(%u);\n", wDevID); if (wDevID >= MAX_WAVEOUTDRV || AuServ == NULL) { WARN("bad device ID !\n"); return MMSYSERR_BADDEVICEID; } wwo = &WOutDev[wDevID]; if (wwo->lpQueuePtr) { WARN("buffers still playing !\n"); ret = WAVERR_STILLPLAYING; } else { TRACE("imhere[3-close]\n"); if (wwo->hThread != INVALID_HANDLE_VALUE) { NAS_AddRingMessage(&wwo->msgRing, WINE_WM_CLOSING, 0, TRUE); } NAS_DestroyRingMessage(&wwo->msgRing); NAS_CloseDevice(wwo); /* close the stream and clean things up */ ret = wodNotifyClient(wwo, WOM_CLOSE, 0L, 0L); } return ret;}/************************************************************************** * wodWrite [internal] * */static DWORD wodWrite(WORD wDevID, LPWAVEHDR lpWaveHdr, DWORD dwSize){ TRACE("(%u, %p, %08lX);\n", wDevID, lpWaveHdr, dwSize); /* first, do the sanity checks... */ if (wDevID >= MAX_WAVEOUTDRV || AuServ == NULL) { WARN("bad dev ID !\n"); return MMSYSERR_BADDEVICEID; } if (lpWaveHdr->lpData == NULL || !(lpWaveHdr->dwFlags & WHDR_PREPARED)) { TRACE("unprepared\n"); return WAVERR_UNPREPARED; } if (lpWaveHdr->dwFlags & WHDR_INQUEUE) { TRACE("still playing\n"); return WAVERR_STILLPLAYING; } lpWaveHdr->dwFlags &= ~WHDR_DONE; lpWaveHdr->dwFlags |= WHDR_INQUEUE; lpWaveHdr->lpNext = 0; TRACE("adding ring message\n"); NAS_AddRingMessage(&WOutDev[wDevID].msgRing, WINE_WM_HEADER, (DWORD)lpWaveHdr, FALSE); return MMSYSERR_NOERROR;}/************************************************************************** * wodPrepare [internal] */static DWORD wodPrepare(WORD wDevID, LPWAVEHDR lpWaveHdr, DWORD dwSize){ TRACE("(%u, %p, %08lX);\n", wDevID, lpWaveHdr, dwSize); if (wDevID >= MAX_WAVEOUTDRV) { WARN("bad device ID !\n"); return MMSYSERR_BADDEVICEID; } if (lpWaveHdr->dwFlags & WHDR_INQUEUE) return WAVERR_STILLPLAYING; lpWaveHdr->dwFlags |= WHDR_PREPARED; lpWaveHdr->dwFlags &= ~WHDR_DONE; return MMSYSERR_NOERROR;}/************************************************************************** * wodUnprepare [internal]
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -