📄 audio.c
字号:
*/static BOOL wodUpdatePlayedTotal(WINE_WAVEOUT* wwo){ /* total played is the bytes written less the bytes to write ;-) */ wwo->dwPlayedTotal = wwo->dwWrittenTotal - (wwo->dwBufferSize - arts_stream_get(wwo->play_stream, ARTS_P_BUFFER_SPACE)); 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; } } wwo->dwPartialOffset = 0;}/************************************************************************** * 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; wwo->dwPartialOffset = 0; 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_DSPWait [internal] * Returns the number of milliseconds to wait for the DSP buffer to clear. * This is based on the number of fragments we want to be clear before * writing and the number of free fragments we already have. */static DWORD wodPlayer_DSPWait(const WINE_WAVEOUT *wwo){ int waitvalue = (wwo->dwBufferSize - arts_stream_get(wwo->play_stream, ARTS_P_BUFFER_SPACE)) / ((wwo->format.wf.nSamplesPerSec * wwo->format.wBitsPerSample * wwo->format.wf.nChannels) /1000); TRACE("wait value of %d\n", waitvalue); /* return the time left to play the buffer */ return waitvalue;}/************************************************************************** * wodPlayer_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; if(lpWaveHdr->reserved < wwo->dwPlayedTotal) { dwMillis = 1; } else { dwMillis = (lpWaveHdr->reserved - wwo->dwPlayedTotal) * 1000 / wwo->format.wf.nAvgBytesPerSec; if(!dwMillis) dwMillis = 1; } TRACE("dwMillis = %ld\n", dwMillis); return dwMillis;}/************************************************************************** * wodPlayer_WriteMaxFrags [internal] * Writes the maximum number of bytes possible to the DSP and returns * the number of bytes written. */static int wodPlayer_WriteMaxFrags(WINE_WAVEOUT* wwo, DWORD* bytes){ /* Only attempt to write to free bytes */ DWORD dwLength = wwo->lpPlayPtr->dwBufferLength - wwo->dwPartialOffset; int toWrite = min(dwLength, *bytes); int written; TRACE("Writing wavehdr %p.%lu[%lu]\n", wwo->lpPlayPtr, wwo->dwPartialOffset, wwo->lpPlayPtr->dwBufferLength); /* see if our buffer isn't large enough for the data we are writing */ if(wwo->buffer_size < toWrite) { if(wwo->sound_buffer) HeapFree(GetProcessHeap(), 0, wwo->sound_buffer); } /* if we don't have a buffer then get one */ if(!wwo->sound_buffer) { /* allocate some memory for the buffer */ wwo->sound_buffer = HeapAlloc(GetProcessHeap(), 0, toWrite); wwo->buffer_size = toWrite; } /* if we don't have a buffer then error out */ if(!wwo->sound_buffer) { ERR("error allocating sound_buffer memory\n"); return 0; } TRACE("toWrite == %d\n", toWrite); /* apply volume to the bits */ /* for single channel audio streams we only use the LEFT volume */ if(wwo->format.wBitsPerSample == 16) { /* apply volume to the buffer we are about to send */ /* divide toWrite(bytes) by 2 as volume processes by 16 bits */ volume_effect16(wwo->lpPlayPtr->lpData + wwo->dwPartialOffset, wwo->sound_buffer, toWrite>>1, wwo->volume_left, wwo->volume_right, wwo->format.wf.nChannels); } else if(wwo->format.wBitsPerSample == 8) { /* apply volume to the buffer we are about to send */ volume_effect8(wwo->lpPlayPtr->lpData + wwo->dwPartialOffset, wwo->sound_buffer, toWrite, wwo->volume_left, wwo->volume_right, wwo->format.wf.nChannels); } else { FIXME("unsupported wwo->format.wBitsPerSample of %d\n", wwo->format.wBitsPerSample); } /* send the audio data to arts for playing */ written = arts_write(wwo->play_stream, wwo->sound_buffer, toWrite); TRACE("written = %d\n", written); if (written <= 0) return written; /* if we wrote nothing just return */ if (written >= dwLength) wodPlayer_PlayPtrNext(wwo); /* If we wrote all current wavehdr, skip to the next one */ else wwo->dwPartialOffset += written; /* Remove the amount written */ *bytes -= written; wwo->dwWrittenTotal += written; /* update stats on this wave device */ return written; /* return the number of bytes written */}/************************************************************************** * 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 */ 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); wodPlayer_NotifyCompletions(wwo, FALSE); /* updates current notify list */ /* we aren't able to flush any data that has already been written */ /* to arts, otherwise we would do the flushing here */ 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; wwo->dwPartialOffset = 0; /* Clear partial wavehdr */ /* remove any existing message in the ring */ EnterCriticalSection(&wwo->msgRing.msg_crst); /* return all pending headers in queue */ while (ARTS_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->dwPartialOffset = 0; wwo->dwWrittenTotal = wwo->dwPlayedTotal; /* this is wrong !!! */ } else { /* the data already written is going to be played, so take */ /* this fact into account here */ wwo->dwPlayedTotal = wwo->dwWrittenTotal; } 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 (ARTS_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_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; wodUpdatePlayedTotal(wwo); availInQ = arts_stream_get(wwo->play_stream, ARTS_P_BUFFER_SPACE); TRACE("availInQ = %ld\n", availInQ); /* input queue empty and output buffer with no space */ if (!wwo->lpPlayPtr && availInQ) { TRACE("Run out of wavehdr:s... flushing\n"); wwo->dwPlayedTotal = wwo->dwWrittenTotal; return INFINITE; } /* no more room... no need to try to feed */ if(!availInQ) { TRACE("no more room, no need to try to feed\n"); return wodPlayer_DSPWait(wwo); } /* Feed from partial wavehdr */ if (wwo->lpPlayPtr && wwo->dwPartialOffset != 0) { TRACE("feeding from partial wavehdr\n"); wodPlayer_WriteMaxFrags(wwo, &availInQ); } /* Feed wavehdrs until we run out of wavehdrs or DSP space */ if (!wwo->dwPartialOffset) { while(wwo->lpPlayPtr && availInQ > SPACE_THRESHOLD) { TRACE("feeding waveheaders until we run out of space\n"); /* note the value that dwPlayedTotal will return when this wave finishes playing */ wwo->lpPlayPtr->reserved = wwo->dwWrittenTotal + wwo->lpPlayPtr->dwBufferLength; wodPlayer_WriteMaxFrags(wwo, &availInQ); } } 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; 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; } /* if this device is already open tell the app that it is allocated */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -