/* clang-format off */ /* These need to be included after windows.h */ #include <mmreg.h> #include <mmsystem.h> /* clang-format on */
/* This is missing from the MinGW headers. Use a safe fallback. */ #if !defined(MEMORY_ALLOCATION_ALIGNMENT) #define MEMORY_ALLOCATION_ALIGNMENT 16 #endif
/**Taken from winbase.h, also not in MinGW.*/ #ifndef STACK_SIZE_PARAM_IS_A_RESERVATION #define STACK_SIZE_PARAM_IS_A_RESERVATION 0x00010000 // Threads only #endif
/* It is assumed that the caller is holding this lock. It must be dropped
during the callback to avoid deadlocks. */
LeaveCriticalSection(&stm->lock);
got = stm->data_callback(stm, stm->user_ptr, NULL, hdr->lpData, wanted);
EnterCriticalSection(&stm->lock); if (got < 0) {
stm->error = 1;
LeaveCriticalSection(&stm->lock);
SetEvent(stm->event);
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); return;
} elseif (got < wanted) {
stm->draining = 1;
}
stm->written += got;
r = WaitForSingleObject(ctx->event, INFINITE);
XASSERT(r == WAIT_OBJECT_0);
/* Process work items in batches so that a single stream can't starve the others by continuously adding new work to the top of
the work item stack. */
item = InterlockedFlushSList(ctx->work); while (item != NULL) {
PSLIST_ENTRY tmp = item;
winmm_refill_stream(((struct cubeb_stream_item *)tmp)->stream);
item = item->Next;
_aligned_free(tmp);
}
EnterCriticalSection(&context->lock); /* CUBEB_STREAM_MAX is a horrible hack to avoid a situation where, when many streams are active at once, a subset of them will not consume (via
playback) or release (via waveOutReset) their buffers. */ if (context->active_streams >= CUBEB_STREAM_MAX) {
LeaveCriticalSection(&context->lock); return CUBEB_ERROR;
}
context->active_streams += 1;
LeaveCriticalSection(&context->lock);
stm = calloc(1, sizeof(*stm));
XASSERT(stm);
stm->context = context;
stm->params = *output_stream_params;
// Data callback is set to the user-provided data callback after // the initialization and potential preroll callback calls are done, because // cubeb users don't expect the data callback to be called during // initialization.
stm->data_callback = preroll_callback;
stm->state_callback = state_callback;
stm->user_ptr = user_ptr;
stm->written = 0;
/* winmm_buffer_callback will be called during waveOutOpen, so all
other initialization must be complete before calling it. */
r = waveOutOpen(&stm->waveout, WAVE_MAPPER, &wfx.Format,
(DWORD_PTR)winmm_buffer_callback, (DWORD_PTR)stm,
CALLBACK_FUNCTION); if (r != MMSYSERR_NOERROR) {
winmm_stream_destroy(stm); return CUBEB_ERROR;
}
r = waveOutPause(stm->waveout); if (r != MMSYSERR_NOERROR) {
winmm_stream_destroy(stm); return CUBEB_ERROR;
}
for (i = 0; i < NBUFS; ++i) {
WAVEHDR * hdr = &stm->buffers[i];
r = waveOutPrepareHeader(stm->waveout, hdr, sizeof(*hdr)); if (r != MMSYSERR_NOERROR) {
winmm_stream_destroy(stm); return CUBEB_ERROR;
}
winmm_refill_stream(stm);
}
stm->frame_size = bytes_per_frame(stm->params);
stm->prev_pos_lo_dword = 0;
stm->pos_hi_dword = 0; // Set the user data callback now that preroll has finished.
stm->data_callback = data_callback;
stm->position_base = 0;
// Offset the position by the number of frames written during preroll.
stm->position_base = stm->written;
stm->written = 0;
*stream = stm;
LOG("winmm_stream_init OK");
return CUBEB_OK;
}
staticvoid
winmm_stream_destroy(cubeb_stream * stm)
{ int i;
if (stm->waveout) {
MMTIME time;
MMRESULT r; int device_valid; int enqueued;
/* Don't need this value, we just want the result to detect invalid
handle/no device errors than waveOutReset doesn't seem to report. */
time.wType = TIME_SAMPLES;
r = waveOutGetPosition(stm->waveout, &time, sizeof(time));
device_valid = !(r == MMSYSERR_INVALHANDLE || r == MMSYSERR_NODRIVER);
for (i = 0; i < NBUFS; ++i) { if (stm->buffers[i].dwFlags & WHDR_PREPARED) {
waveOutUnprepareHeader(stm->waveout, &stm->buffers[i], sizeof(stm->buffers[i]));
}
}
waveOutClose(stm->waveout);
LeaveCriticalSection(&stm->lock);
}
if (stm->event) {
CloseHandle(stm->event);
}
DeleteCriticalSection(&stm->lock);
for (i = 0; i < NBUFS; ++i) {
free(stm->buffers[i].lpData);
}
/* We don't support more than two channels in this backend. */
*max_channels = 2;
return CUBEB_OK;
}
staticint
winmm_get_min_latency(cubeb * ctx, cubeb_stream_params params,
uint32_t * latency)
{ // 100ms minimum, if we are not in a bizarre configuration.
*latency = ctx->minimum_latency_ms * params.rate / 1000;
r = waveOutGetDevCaps(WAVE_MAPPER, &woc, sizeof(WAVEOUTCAPS)); if (r != MMSYSERR_NOERROR) { return CUBEB_ERROR;
}
/* Check if we support 48kHz, but not 44.1kHz. */ if (!(woc.dwFormats & WAVE_FORMAT_4S16) &&
woc.dwFormats & WAVE_FORMAT_48S16) {
*rate = 48000; return CUBEB_OK;
} /* Prefer 44.1kHz between 44.1kHz and 48kHz. */
*rate = 44100;
/* Microsoft wave audio docs say "samples are the preferred time format in which to represent the current position", but relying on this causes problems on Windows XP, the only OS cubeb_winmm is used on.
While the wdmaud.sys driver internally tracks a 64-bit position and ensures no backward movement, the WinMM API limits the position returned from waveOutGetPosition() to a 32-bit DWORD (this applies equally to XP x64). The higher 32 bits are chopped off, and to an API consumer the position can appear to move backward.
In theory, even a 32-bit TIME_SAMPLES position should provide plenty of playback time for typical use cases before this pseudo wrap-around, e.g: (2^32 - 1)/48000 = ~24:51:18 for 48.0 kHz stereo; (2^32 - 1)/44100 = ~27:03:12 for 44.1 kHz stereo. In reality, wdmaud.sys doesn't provide a TIME_SAMPLES position at all, only a 32-bit TIME_BYTES position, from which wdmaud.drv derives TIME_SAMPLES: SamplePos = (BytePos * 8) / BitsPerFrame, where BitsPerFrame = Channels * BitsPerSample, Per dom\media\AudioSampleFormat.h, desktop builds always use 32-bit FLOAT32 samples, so the maximum for TIME_SAMPLES should be: (2^29 - 1)/48000 = ~03:06:25; (2^29 - 1)/44100 = ~03:22:54. This might still be OK for typical browser usage, but there's also a bug in the formula above: BytePos * 8 (BytePos << 3) is done on a 32-bit BytePos, without first casting it to 64 bits, so the highest 3 bits, if set, would get shifted out, and the maximum possible TIME_SAMPLES drops unacceptably low: (2^26 - 1)/48000 = ~00:23:18; (2^26 - 1)/44100 = ~00:25:22.
To work around these limitations, we just get the position in TIME_BYTES, recover the 64-bit value, and do our own conversion to samples.
*/
/* Convert chopped 32-bit waveOutGetPosition() into 64-bit true position. */ static uint64_t
update_64bit_position(cubeb_stream * stm, DWORD pos_lo_dword)
{ /* Caller should be holding stm->lock. */ if (pos_lo_dword < stm->prev_pos_lo_dword) {
stm->pos_hi_dword++;
LOG("waveOutGetPosition() has wrapped around: %#lx -> %#lx",
stm->prev_pos_lo_dword, pos_lo_dword);
LOG("Wrap-around count = %#lx", stm->pos_hi_dword);
LOG("Current 64-bit position = %#llx",
(((uint64_t)stm->pos_hi_dword) << 32) | ((uint64_t)pos_lo_dword));
}
stm->prev_pos_lo_dword = pos_lo_dword;
EnterCriticalSection(&stm->lock); /* See the long comment above for why not just use TIME_SAMPLES here. */
time.wType = TIME_BYTES;
r = waveOutGetPosition(stm->waveout, &time, sizeof(time));
// Subtract the number of frames that were written while prerolling, during // initialization. if (position_not_adjusted < stm->position_base) {
*position = 0;
} else {
*position = position_not_adjusted - stm->position_base;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.