/************************************************************************** * widRecorder_ReadHeaders [internal] */ static void widRecorder_ReadHeaders(WINE_WAVEDEV * wwi) { enum win_wm_message tmp_msg; DWORD_PTR tmp_param; HANDLE tmp_ev; WAVEHDR* lpWaveHdr; while (ALSA_RetrieveRingMessage(&wwi->msgRing, &tmp_msg, &tmp_param, &tmp_ev)) { if (tmp_msg == WINE_WM_HEADER) { LPWAVEHDR* wh; lpWaveHdr = (LPWAVEHDR)tmp_param; lpWaveHdr->lpNext = 0; if (wwi->lpQueuePtr == 0) wwi->lpQueuePtr = lpWaveHdr; else { for (wh = &(wwi->lpQueuePtr); *wh; wh = &((*wh)->lpNext)); *wh = lpWaveHdr; } } else { ERR("should only have headers left\n"); } } }
/************************************************************************** * widRecorder [internal] */ static DWORD CALLBACK widRecorder(LPVOID pmt) { WORD uDevID = (DWORD_PTR)pmt; WINE_WAVEDEV* wwi = &WInDev[uDevID]; WAVEHDR* lpWaveHdr; DWORD dwSleepTime; DWORD bytesRead; enum win_wm_message msg; DWORD_PTR param; HANDLE ev; DWORD frames_per_period; wwi->state = WINE_WS_STOPPED; InterlockedExchange((LONG*)&wwi->dwTotalRecorded, 0); wwi->lpQueuePtr = NULL; SetEvent(wwi->hStartUpEvent); /* make sleep time to be # of ms to output a period */ dwSleepTime = (wwi->dwPeriodSize * 1000) / wwi->format.Format.nAvgBytesPerSec; frames_per_period = snd_pcm_bytes_to_frames(wwi->pcm, wwi->dwPeriodSize); TRACE("sleeptime=%d ms, total buffer length=%d ms (%d bytes)\n", dwSleepTime, wwi->dwBufferSize * 1000 / wwi->format.Format.nAvgBytesPerSec, wwi->dwBufferSize); for (;;) { /* wait for dwSleepTime or an event in thread's queue */ if (wwi->lpQueuePtr != NULL && wwi->state == WINE_WS_PLAYING) { DWORD frames; DWORD bytes; DWORD read; lpWaveHdr = wwi->lpQueuePtr; /* read all the fragments accumulated so far */ frames = snd_pcm_avail_update(wwi->pcm); bytes = snd_pcm_frames_to_bytes(wwi->pcm, frames); TRACE("frames = %d bytes = %d state=%d\n", frames, bytes, snd_pcm_state(wwi->pcm)); if (snd_pcm_state(wwi->pcm) == SND_PCM_STATE_XRUN) { FIXME("Recovering from XRUN!\n"); snd_pcm_prepare(wwi->pcm); frames = snd_pcm_avail_update(wwi->pcm); bytes = snd_pcm_frames_to_bytes(wwi->pcm, frames); snd_pcm_start(wwi->pcm); snd_pcm_forward(wwi->pcm, frames - snd_pcm_bytes_to_frames(wwi->pcm, wwi->dwPeriodSize)); continue; } while (frames > 0 && wwi->lpQueuePtr) { TRACE("bytes = %d\n", bytes); if (lpWaveHdr->dwBufferLength - lpWaveHdr->dwBytesRecorded < bytes) { bytes = lpWaveHdr->dwBufferLength - lpWaveHdr->dwBytesRecorded; frames = snd_pcm_bytes_to_frames(wwi->pcm, bytes); } /* directly read fragment in wavehdr */ read = wwi->read(wwi->pcm, lpWaveHdr->lpData + lpWaveHdr->dwBytesRecorded, frames); bytesRead = snd_pcm_frames_to_bytes(wwi->pcm, read); TRACE("bytesRead=(%d(%d)/(%d)) -> (%d/%d)\n", bytesRead, read, frames, lpWaveHdr->dwBufferLength, lpWaveHdr->dwBufferLength - lpWaveHdr->dwBytesRecorded); if (read != (DWORD) -1) { /* update number of bytes recorded in current buffer and by this device */ lpWaveHdr->dwBytesRecorded += bytesRead; InterlockedExchangeAdd((LONG*)&wwi->dwTotalRecorded, bytesRead); frames -= read; bytes -= bytesRead; /* buffer is full. notify client */ if (!snd_pcm_bytes_to_frames(wwi->pcm, lpWaveHdr->dwBytesRecorded - lpWaveHdr->dwBufferLength)) { /* must copy the value of next waveHdr, because we have no idea of what * will be done with the content of lpWaveHdr in callback */ LPWAVEHDR lpNext = lpWaveHdr->lpNext; lpWaveHdr->dwFlags &= ~WHDR_INQUEUE; lpWaveHdr->dwFlags |= WHDR_DONE; wwi->lpQueuePtr = lpNext; widNotifyClient(wwi, WIM_DATA, (DWORD_PTR)lpWaveHdr, 0); lpWaveHdr = lpNext; } } else { WARN("read(%s, %p, %d) failed (%d/%s)\n", wwi->pcmname, lpWaveHdr->lpData + lpWaveHdr->dwBytesRecorded, frames, frames, snd_strerror(read)); } } } ALSA_WaitRingMessage(&wwi->msgRing, dwSleepTime); while (ALSA_RetrieveRingMessage(&wwi->msgRing, &msg, ¶m, &ev)) { TRACE("msg=%s param=0x%lx\n", ALSA_getCmdString(msg), param); switch (msg) { case WINE_WM_PAUSING: wwi->state = WINE_WS_PAUSED; /*FIXME("Device should stop recording\n");*/ SetEvent(ev); break; case WINE_WM_STARTING: wwi->state = WINE_WS_PLAYING; snd_pcm_start(wwi->pcm); SetEvent(ev); break; case WINE_WM_HEADER: lpWaveHdr = (LPWAVEHDR)param; lpWaveHdr->lpNext = 0; /* insert buffer at the end of queue */ { LPWAVEHDR* wh; for (wh = &(wwi->lpQueuePtr); *wh; wh = &((*wh)->lpNext)); *wh = lpWaveHdr; } break; case WINE_WM_STOPPING: if (wwi->state != WINE_WS_STOPPED) { snd_pcm_drain(wwi->pcm); /* read any headers in queue */ widRecorder_ReadHeaders(wwi); /* return current buffer to app */ lpWaveHdr = wwi->lpQueuePtr; if (lpWaveHdr) { LPWAVEHDR lpNext = lpWaveHdr->lpNext; TRACE("stop %p %p\n", lpWaveHdr, lpWaveHdr->lpNext); lpWaveHdr->dwFlags &= ~WHDR_INQUEUE; lpWaveHdr->dwFlags |= WHDR_DONE; wwi->lpQueuePtr = lpNext; widNotifyClient(wwi, WIM_DATA, (DWORD_PTR)lpWaveHdr, 0); } } wwi->state = WINE_WS_STOPPED; SetEvent(ev); break; case WINE_WM_RESETTING: if (wwi->state != WINE_WS_STOPPED) { snd_pcm_drain(wwi->pcm); } wwi->state = WINE_WS_STOPPED; wwi->dwTotalRecorded = 0; /* read any headers in queue */ widRecorder_ReadHeaders(wwi); /* return all buffers to the app */ while (wwi->lpQueuePtr) { lpWaveHdr = wwi->lpQueuePtr; TRACE("reset %p %p\n", lpWaveHdr, lpWaveHdr->lpNext); wwi->lpQueuePtr = lpWaveHdr->lpNext; lpWaveHdr->dwFlags &= ~WHDR_INQUEUE; lpWaveHdr->dwFlags |= WHDR_DONE; widNotifyClient(wwi, WIM_DATA, (DWORD_PTR)lpWaveHdr, 0); } SetEvent(ev); break; case WINE_WM_CLOSING: wwi->hThread = 0; wwi->state = WINE_WS_CLOSED; SetEvent(ev); ExitThread(0); /* shouldn't go here */ default: FIXME("unknown message %d\n", msg); break; } } } ExitThread(0); /* just for not generating compilation warnings... should never be executed */ return 0; }
/************************************************************************** * wodPlayer_ProcessMessages [internal] */ static void wodPlayer_ProcessMessages(WINE_WAVEDEV* wwo) { LPWAVEHDR lpWaveHdr; enum win_wm_message msg; DWORD_PTR param; HANDLE ev; int err; while (ALSA_RetrieveRingMessage(&wwo->msgRing, &msg, ¶m, &ev)) { TRACE("Received %s %lx\n", ALSA_getCmdString(msg), param); switch (msg) { case WINE_WM_PAUSING: if ( snd_pcm_state(wwo->pcm) == SND_PCM_STATE_RUNNING ) { if ( snd_pcm_hw_params_can_pause(wwo->hw_params) ) { err = snd_pcm_pause(wwo->pcm, 1); if ( err < 0 ) ERR("pcm_pause failed: %s\n", snd_strerror(err)); wwo->state = WINE_WS_PAUSED; } else { wodPlayer_Reset(wwo,FALSE); } } SetEvent(ev); break; case WINE_WM_RESTARTING: if (wwo->state == WINE_WS_PAUSED) { if ( snd_pcm_state(wwo->pcm) == SND_PCM_STATE_PAUSED ) { err = snd_pcm_pause(wwo->pcm, 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,TRUE); 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_Reset [internal] * * wodPlayer helper. Resets current output stream. */ static void wodPlayer_Reset(WINE_WAVEDEV* wwo, BOOL reset) { int err; TRACE("(%p)\n", wwo); wodUpdatePlayedTotal(wwo, NULL); /* updates current notify list */ wodPlayer_NotifyCompletions(wwo, FALSE); if ( (err = snd_pcm_drop(wwo->pcm)) < 0) { FIXME("flush: %s\n", snd_strerror(err)); wwo->hThread = 0; wwo->state = WINE_WS_STOPPED; ExitThread(-1); } if ( (err = snd_pcm_prepare(wwo->pcm)) < 0 ) ERR("pcm prepare failed: %s\n", snd_strerror(err)); if (reset) { enum win_wm_message msg; DWORD_PTR 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 (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); } ALSA_ResetRingMessage(&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, expect 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; } }