Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /*
- * Copyright © 2011 Mozilla Foundation
- *
- * This program is made available under an ISC-style license. See the
- * accompanying file LICENSE for details.
- */
- #define __MSVCRT_VERSION__ 0x0700
- #undef WINVER
- #define WINVER 0x0501
- #undef WIN32_LEAN_AND_MEAN
- #include <malloc.h>
- #include <windows.h>
- #include <mmreg.h>
- #include <mmsystem.h>
- #include <process.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <math.h>
- #include <IntSafe.h>
- #include "cubeb/cubeb.h"
- #include "cubeb-internal.h"
- /* This is missing from the MinGW headers. Use a safe fallback. */
- #if !defined(MEMORY_ALLOCATION_ALIGNMENT)
- #define MEMORY_ALLOCATION_ALIGNMENT 16
- #endif
- /**This is also missing from the MinGW headers. It also appears to be undocumented by Microsoft.*/
- #ifndef WAVE_FORMAT_48M08
- #define WAVE_FORMAT_48M08 0x00001000 /* 48 kHz, Mono, 8-bit */
- #endif
- #ifndef WAVE_FORMAT_48M16
- #define WAVE_FORMAT_48M16 0x00002000 /* 48 kHz, Mono, 16-bit */
- #endif
- #ifndef WAVE_FORMAT_48S08
- #define WAVE_FORMAT_48S08 0x00004000 /* 48 kHz, Stereo, 8-bit */
- #endif
- #ifndef WAVE_FORMAT_48S16
- #define WAVE_FORMAT_48S16 0x00008000 /* 48 kHz, Stereo, 16-bit */
- #endif
- #ifndef WAVE_FORMAT_96M08
- #define WAVE_FORMAT_96M08 0x00010000 /* 96 kHz, Mono, 8-bit */
- #endif
- #ifndef WAVE_FORMAT_96M16
- #define WAVE_FORMAT_96M16 0x00020000 /* 96 kHz, Mono, 16-bit */
- #endif
- #ifndef WAVE_FORMAT_96S08
- #define WAVE_FORMAT_96S08 0x00040000 /* 96 kHz, Stereo, 8-bit */
- #endif
- #ifndef WAVE_FORMAT_96S16
- #define WAVE_FORMAT_96S16 0x00080000 /* 96 kHz, Stereo, 16-bit */
- #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
- #ifndef DRVM_MAPPER
- #define DRVM_MAPPER (0x2000)
- #endif
- #ifndef DRVM_MAPPER_PREFERRED_GET
- #define DRVM_MAPPER_PREFERRED_GET (DRVM_MAPPER+21)
- #endif
- #ifndef DRVM_MAPPER_CONSOLEVOICECOM_GET
- #define DRVM_MAPPER_CONSOLEVOICECOM_GET (DRVM_MAPPER+23)
- #endif
- #define CUBEB_STREAM_MAX 32
- #define NBUFS 4
- const GUID KSDATAFORMAT_SUBTYPE_PCM =
- { 0x00000001, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
- const GUID KSDATAFORMAT_SUBTYPE_IEEE_FLOAT =
- { 0x00000003, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
- struct cubeb_stream_item {
- SLIST_ENTRY head;
- cubeb_stream * stream;
- };
- static struct cubeb_ops const winmm_ops;
- struct cubeb {
- struct cubeb_ops const * ops;
- HANDLE event;
- HANDLE thread;
- int shutdown;
- PSLIST_HEADER work;
- CRITICAL_SECTION lock;
- unsigned int active_streams;
- unsigned int minimum_latency_ms;
- };
- struct cubeb_stream {
- cubeb * context;
- cubeb_stream_params params;
- cubeb_data_callback data_callback;
- cubeb_state_callback state_callback;
- void * user_ptr;
- WAVEHDR buffers[NBUFS];
- size_t buffer_size;
- int next_buffer;
- int free_buffers;
- int shutdown;
- int draining;
- HANDLE event;
- HWAVEOUT waveout;
- CRITICAL_SECTION lock;
- uint64_t written;
- float soft_volume;
- /* For position wrap-around handling: */
- size_t frame_size;
- DWORD prev_pos_lo_dword;
- DWORD pos_hi_dword;
- };
- static size_t
- bytes_per_frame(cubeb_stream_params params)
- {
- size_t bytes;
- switch (params.format) {
- case CUBEB_SAMPLE_S16LE:
- bytes = sizeof(signed short);
- break;
- case CUBEB_SAMPLE_FLOAT32LE:
- bytes = sizeof(float);
- break;
- default:
- XASSERT(0);
- }
- return bytes * params.channels;
- }
- static WAVEHDR *
- winmm_get_next_buffer(cubeb_stream * stm)
- {
- WAVEHDR * hdr = NULL;
- XASSERT(stm->free_buffers > 0 && stm->free_buffers <= NBUFS);
- hdr = &stm->buffers[stm->next_buffer];
- XASSERT(hdr->dwFlags & WHDR_PREPARED ||
- (hdr->dwFlags & WHDR_DONE && !(hdr->dwFlags & WHDR_INQUEUE)));
- stm->next_buffer = (stm->next_buffer + 1) % NBUFS;
- stm->free_buffers -= 1;
- return hdr;
- }
- static void
- winmm_refill_stream(cubeb_stream * stm)
- {
- WAVEHDR * hdr;
- long got;
- long wanted;
- MMRESULT r;
- EnterCriticalSection(&stm->lock);
- stm->free_buffers += 1;
- XASSERT(stm->free_buffers > 0 && stm->free_buffers <= NBUFS);
- if (stm->draining) {
- LeaveCriticalSection(&stm->lock);
- if (stm->free_buffers == NBUFS) {
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
- }
- SetEvent(stm->event);
- return;
- }
- if (stm->shutdown) {
- LeaveCriticalSection(&stm->lock);
- SetEvent(stm->event);
- return;
- }
- hdr = winmm_get_next_buffer(stm);
- wanted = (DWORD) stm->buffer_size / bytes_per_frame(stm->params);
- /* 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) {
- LeaveCriticalSection(&stm->lock);
- /* XXX handle this case */
- XASSERT(0);
- return;
- } else if (got < wanted) {
- stm->draining = 1;
- }
- stm->written += got;
- XASSERT(hdr->dwFlags & WHDR_PREPARED);
- hdr->dwBufferLength = got * bytes_per_frame(stm->params);
- XASSERT(hdr->dwBufferLength <= stm->buffer_size);
- if (stm->soft_volume != -1.0) {
- if (stm->params.format == CUBEB_SAMPLE_FLOAT32NE) {
- float * b = (float *) hdr->lpData;
- uint32_t i;
- for (i = 0; i < got * stm->params.channels; i++) {
- b[i] *= stm->soft_volume;
- }
- } else {
- short * b = (short *) hdr->lpData;
- uint32_t i;
- for (i = 0; i < got * stm->params.channels; i++) {
- b[i] = (short) (b[i] * stm->soft_volume);
- }
- }
- }
- r = waveOutWrite(stm->waveout, hdr, sizeof(*hdr));
- if (r != MMSYSERR_NOERROR) {
- LeaveCriticalSection(&stm->lock);
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
- return;
- }
- LeaveCriticalSection(&stm->lock);
- }
- static unsigned __stdcall
- winmm_buffer_thread(void * user_ptr)
- {
- cubeb * ctx = (cubeb *) user_ptr;
- XASSERT(ctx);
- for (;;) {
- DWORD r;
- PSLIST_ENTRY item;
- 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);
- }
- if (ctx->shutdown) {
- break;
- }
- }
- return 0;
- }
- static void CALLBACK
- winmm_buffer_callback(HWAVEOUT waveout, UINT msg, DWORD_PTR user_ptr, DWORD_PTR p1, DWORD_PTR p2)
- {
- cubeb_stream * stm = (cubeb_stream *) user_ptr;
- struct cubeb_stream_item * item;
- if (msg != WOM_DONE) {
- return;
- }
- item = _aligned_malloc(sizeof(struct cubeb_stream_item), MEMORY_ALLOCATION_ALIGNMENT);
- XASSERT(item);
- item->stream = stm;
- InterlockedPushEntrySList(stm->context->work, &item->head);
- SetEvent(stm->context->event);
- }
- static unsigned int
- calculate_minimum_latency(void)
- {
- OSVERSIONINFOEX osvi;
- DWORDLONG mask;
- /* Running under Terminal Services results in underruns with low latency. */
- if (GetSystemMetrics(SM_REMOTESESSION) == TRUE) {
- return 500;
- }
- /* Vista's WinMM implementation underruns when less than 200ms of audio is buffered. */
- memset(&osvi, 0, sizeof(OSVERSIONINFOEX));
- osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
- osvi.dwMajorVersion = 6;
- osvi.dwMinorVersion = 0;
- mask = 0;
- VER_SET_CONDITION(mask, VER_MAJORVERSION, VER_EQUAL);
- VER_SET_CONDITION(mask, VER_MINORVERSION, VER_EQUAL);
- if (VerifyVersionInfo(&osvi, VER_MAJORVERSION | VER_MINORVERSION, mask) != 0) {
- return 200;
- }
- return 100;
- }
- static void winmm_destroy(cubeb * ctx);
- /*static*/ int
- winmm_init(cubeb ** context, char const * context_name)
- {
- cubeb * ctx;
- XASSERT(context);
- *context = NULL;
- /* Don't initialize a context if there are no devices available. */
- if (waveOutGetNumDevs() == 0) {
- return CUBEB_ERROR;
- }
- ctx = calloc(1, sizeof(*ctx));
- XASSERT(ctx);
- ctx->ops = &winmm_ops;
- ctx->work = _aligned_malloc(sizeof(*ctx->work), MEMORY_ALLOCATION_ALIGNMENT);
- XASSERT(ctx->work);
- InitializeSListHead(ctx->work);
- ctx->event = CreateEvent(NULL, FALSE, FALSE, NULL);
- if (!ctx->event) {
- winmm_destroy(ctx);
- return CUBEB_ERROR;
- }
- ctx->thread = (HANDLE) _beginthreadex(NULL, 256 * 1024, winmm_buffer_thread, ctx, STACK_SIZE_PARAM_IS_A_RESERVATION, NULL);
- if (!ctx->thread) {
- winmm_destroy(ctx);
- return CUBEB_ERROR;
- }
- SetThreadPriority(ctx->thread, THREAD_PRIORITY_TIME_CRITICAL);
- InitializeCriticalSection(&ctx->lock);
- ctx->active_streams = 0;
- ctx->minimum_latency_ms = calculate_minimum_latency();
- *context = ctx;
- return CUBEB_OK;
- }
- static char const *
- winmm_get_backend_id(cubeb * ctx)
- {
- return "winmm";
- }
- static void
- winmm_destroy(cubeb * ctx)
- {
- DWORD r;
- XASSERT(ctx->active_streams == 0);
- XASSERT(!InterlockedPopEntrySList(ctx->work));
- DeleteCriticalSection(&ctx->lock);
- if (ctx->thread) {
- ctx->shutdown = 1;
- SetEvent(ctx->event);
- r = WaitForSingleObject(ctx->thread, INFINITE);
- XASSERT(r == WAIT_OBJECT_0);
- CloseHandle(ctx->thread);
- }
- if (ctx->event) {
- CloseHandle(ctx->event);
- }
- _aligned_free(ctx->work);
- free(ctx);
- }
- static void winmm_stream_destroy(cubeb_stream * stm);
- static int
- winmm_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name,
- cubeb_devid input_device,
- cubeb_stream_params * input_stream_params,
- cubeb_devid output_device,
- cubeb_stream_params * output_stream_params,
- unsigned int latency_frames,
- cubeb_data_callback data_callback,
- cubeb_state_callback state_callback,
- void * user_ptr)
- {
- MMRESULT r;
- WAVEFORMATEXTENSIBLE wfx;
- cubeb_stream * stm;
- int i;
- size_t bufsz;
- XASSERT(context);
- XASSERT(stream);
- if (input_stream_params) {
- /* Capture support not yet implemented. */
- return CUBEB_ERROR_NOT_SUPPORTED;
- }
- if (input_device || output_device) {
- /* Device selection not yet implemented. */
- return CUBEB_ERROR_DEVICE_UNAVAILABLE;
- }
- *stream = NULL;
- memset(&wfx, 0, sizeof(wfx));
- if (output_stream_params->channels > 2) {
- wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
- wfx.Format.cbSize = sizeof(wfx) - sizeof(wfx.Format);
- } else {
- wfx.Format.wFormatTag = WAVE_FORMAT_PCM;
- if (output_stream_params->format == CUBEB_SAMPLE_FLOAT32LE) {
- wfx.Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
- }
- wfx.Format.cbSize = 0;
- }
- wfx.Format.nChannels = output_stream_params->channels;
- wfx.Format.nSamplesPerSec = output_stream_params->rate;
- /* XXX fix channel mappings */
- wfx.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
- switch (output_stream_params->format) {
- case CUBEB_SAMPLE_S16LE:
- wfx.Format.wBitsPerSample = 16;
- wfx.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
- break;
- case CUBEB_SAMPLE_FLOAT32LE:
- wfx.Format.wBitsPerSample = 32;
- wfx.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
- break;
- default:
- return CUBEB_ERROR_INVALID_FORMAT;
- }
- wfx.Format.nBlockAlign = (wfx.Format.wBitsPerSample * wfx.Format.nChannels) / 8;
- wfx.Format.nAvgBytesPerSec = wfx.Format.nSamplesPerSec * wfx.Format.nBlockAlign;
- wfx.Samples.wValidBitsPerSample = wfx.Format.wBitsPerSample;
- 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;
- stm->data_callback = data_callback;
- stm->state_callback = state_callback;
- stm->user_ptr = user_ptr;
- stm->written = 0;
- uint32_t latency_ms = latency_frames * 1000 / output_stream_params->rate;
- if (latency_ms < context->minimum_latency_ms) {
- latency_ms = context->minimum_latency_ms;
- }
- bufsz = (size_t) (stm->params.rate / 1000.0 * latency_ms * bytes_per_frame(stm->params) / NBUFS);
- if (bufsz % bytes_per_frame(stm->params) != 0) {
- bufsz += bytes_per_frame(stm->params) - (bufsz % bytes_per_frame(stm->params));
- }
- XASSERT(bufsz % bytes_per_frame(stm->params) == 0);
- stm->buffer_size = bufsz;
- InitializeCriticalSection(&stm->lock);
- stm->event = CreateEvent(NULL, FALSE, FALSE, NULL);
- if (!stm->event) {
- winmm_stream_destroy(stm);
- return CUBEB_ERROR;
- }
- stm->soft_volume = -1.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];
- hdr->lpData = calloc(1, bufsz);
- XASSERT(hdr->lpData);
- hdr->dwBufferLength = bufsz;
- hdr->dwFlags = 0;
- 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;
- *stream = stm;
- return CUBEB_OK;
- }
- static void
- winmm_stream_destroy(cubeb_stream * stm)
- {
- int i;
- if (stm->waveout) {
- MMTIME time;
- MMRESULT r;
- int device_valid;
- int enqueued;
- EnterCriticalSection(&stm->lock);
- stm->shutdown = 1;
- waveOutReset(stm->waveout);
- /* 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);
- enqueued = NBUFS - stm->free_buffers;
- LeaveCriticalSection(&stm->lock);
- /* Wait for all blocks to complete. */
- while (device_valid && enqueued > 0) {
- DWORD rv = WaitForSingleObject(stm->event, INFINITE);
- XASSERT(rv == WAIT_OBJECT_0);
- EnterCriticalSection(&stm->lock);
- enqueued = NBUFS - stm->free_buffers;
- LeaveCriticalSection(&stm->lock);
- }
- EnterCriticalSection(&stm->lock);
- 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);
- }
- EnterCriticalSection(&stm->context->lock);
- XASSERT(stm->context->active_streams >= 1);
- stm->context->active_streams -= 1;
- LeaveCriticalSection(&stm->context->lock);
- free(stm);
- }
- static int
- winmm_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
- {
- XASSERT(ctx && max_channels);
- /* We don't support more than two channels in this backend. */
- *max_channels = 2;
- return CUBEB_OK;
- }
- static int
- 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;
- return CUBEB_OK;
- }
- static int
- winmm_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
- {
- WAVEOUTCAPS woc;
- MMRESULT r;
- 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;
- return CUBEB_OK;
- }
- static int
- winmm_stream_start(cubeb_stream * stm)
- {
- MMRESULT r;
- EnterCriticalSection(&stm->lock);
- r = waveOutRestart(stm->waveout);
- LeaveCriticalSection(&stm->lock);
- if (r != MMSYSERR_NOERROR) {
- return CUBEB_ERROR;
- }
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
- return CUBEB_OK;
- }
- static int
- winmm_stream_stop(cubeb_stream * stm)
- {
- MMRESULT r;
- EnterCriticalSection(&stm->lock);
- r = waveOutPause(stm->waveout);
- LeaveCriticalSection(&stm->lock);
- if (r != MMSYSERR_NOERROR) {
- return CUBEB_ERROR;
- }
- stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
- return CUBEB_OK;
- }
- /*
- 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;
- return (((uint64_t) stm->pos_hi_dword)<<32) | ((uint64_t) pos_lo_dword);
- }
- static int
- winmm_stream_get_position(cubeb_stream * stm, uint64_t * position)
- {
- MMRESULT r;
- MMTIME time;
- 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));
- if (r != MMSYSERR_NOERROR || time.wType != TIME_BYTES) {
- LeaveCriticalSection(&stm->lock);
- return CUBEB_ERROR;
- }
- *position = update_64bit_position(stm, time.u.cb) / stm->frame_size;
- LeaveCriticalSection(&stm->lock);
- return CUBEB_OK;
- }
- static int
- winmm_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
- {
- MMRESULT r;
- MMTIME time;
- 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));
- if (r != MMSYSERR_NOERROR || time.wType != TIME_BYTES) {
- LeaveCriticalSection(&stm->lock);
- return CUBEB_ERROR;
- }
- uint64_t position = update_64bit_position(stm, time.u.cb);
- uint64_t written = stm->written;
- LeaveCriticalSection(&stm->lock);
- XASSERT((written - (position / stm->frame_size)) <= UINT32_MAX);
- *latency = (uint32_t) (written - (position / stm->frame_size));
- return CUBEB_OK;
- }
- static int
- winmm_stream_set_volume(cubeb_stream * stm, float volume)
- {
- EnterCriticalSection(&stm->lock);
- stm->soft_volume = volume;
- LeaveCriticalSection(&stm->lock);
- return CUBEB_OK;
- }
- #define MM_11025HZ_MASK (WAVE_FORMAT_1M08 | WAVE_FORMAT_1M16 | WAVE_FORMAT_1S08 | WAVE_FORMAT_1S16)
- #define MM_22050HZ_MASK (WAVE_FORMAT_2M08 | WAVE_FORMAT_2M16 | WAVE_FORMAT_2S08 | WAVE_FORMAT_2S16)
- #define MM_44100HZ_MASK (WAVE_FORMAT_4M08 | WAVE_FORMAT_4M16 | WAVE_FORMAT_4S08 | WAVE_FORMAT_4S16)
- #define MM_48000HZ_MASK (WAVE_FORMAT_48M08 | WAVE_FORMAT_48M16 | WAVE_FORMAT_48S08 | WAVE_FORMAT_48S16)
- #define MM_96000HZ_MASK (WAVE_FORMAT_96M08 | WAVE_FORMAT_96M16 | WAVE_FORMAT_96S08 | WAVE_FORMAT_96S16)
- static void
- winmm_calculate_device_rate(cubeb_device_info * info, DWORD formats)
- {
- if (formats & MM_11025HZ_MASK) {
- info->min_rate = 11025;
- info->default_rate = 11025;
- info->max_rate = 11025;
- }
- if (formats & MM_22050HZ_MASK) {
- if (info->min_rate == 0) info->min_rate = 22050;
- info->max_rate = 22050;
- info->default_rate = 22050;
- }
- if (formats & MM_44100HZ_MASK) {
- if (info->min_rate == 0) info->min_rate = 44100;
- info->max_rate = 44100;
- info->default_rate = 44100;
- }
- if (formats & MM_48000HZ_MASK) {
- if (info->min_rate == 0) info->min_rate = 48000;
- info->max_rate = 48000;
- info->default_rate = 48000;
- }
- if (formats & MM_96000HZ_MASK) {
- if (info->min_rate == 0) {
- info->min_rate = 96000;
- info->default_rate = 96000;
- }
- info->max_rate = 96000;
- }
- }
- #define MM_S16_MASK (WAVE_FORMAT_1M16 | WAVE_FORMAT_1S16 | WAVE_FORMAT_2M16 | WAVE_FORMAT_2S16 | WAVE_FORMAT_4M16 | \
- WAVE_FORMAT_4S16 | WAVE_FORMAT_48M16 | WAVE_FORMAT_48S16 | WAVE_FORMAT_96M16 | WAVE_FORMAT_96S16)
- static int
- winmm_query_supported_formats(UINT devid, DWORD formats,
- cubeb_device_fmt * supfmt, cubeb_device_fmt * deffmt)
- {
- WAVEFORMATEXTENSIBLE wfx;
- if (formats & MM_S16_MASK)
- *deffmt = *supfmt = CUBEB_DEVICE_FMT_S16LE;
- else
- *deffmt = *supfmt = 0;
- ZeroMemory(&wfx, sizeof(WAVEFORMATEXTENSIBLE));
- wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
- wfx.Format.nChannels = 2;
- wfx.Format.nSamplesPerSec = 44100;
- wfx.Format.wBitsPerSample = 32;
- wfx.Format.nBlockAlign = (wfx.Format.wBitsPerSample * wfx.Format.nChannels) / 8;
- wfx.Format.nAvgBytesPerSec = wfx.Format.nSamplesPerSec * wfx.Format.nBlockAlign;
- wfx.Format.cbSize = 22;
- wfx.Samples.wValidBitsPerSample = wfx.Format.wBitsPerSample;
- wfx.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
- wfx.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
- if (waveOutOpen(NULL, devid, &wfx.Format, 0, 0, WAVE_FORMAT_QUERY) == MMSYSERR_NOERROR)
- *supfmt = (cubeb_device_fmt)(*supfmt | CUBEB_DEVICE_FMT_F32LE);
- return (*deffmt != 0) ? CUBEB_OK : CUBEB_ERROR;
- }
- static char *
- guid_to_cstr(LPGUID guid)
- {
- char * ret = malloc(sizeof(char) * 40);
- if (!ret) {
- return NULL;
- }
- _snprintf_s(ret, sizeof(char) * 40, _TRUNCATE,
- "{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}",
- guid->Data1, guid->Data2, guid->Data3,
- guid->Data4[0], guid->Data4[1], guid->Data4[2], guid->Data4[3],
- guid->Data4[4], guid->Data4[5], guid->Data4[6], guid->Data4[7]);
- return ret;
- }
- static cubeb_device_pref
- winmm_query_preferred_out_device(UINT devid)
- {
- DWORD mmpref = WAVE_MAPPER, compref = WAVE_MAPPER, status;
- cubeb_device_pref ret = CUBEB_DEVICE_PREF_NONE;
- if (waveOutMessage((HWAVEOUT)(size_t)WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET,
- (DWORD_PTR)&mmpref, (DWORD_PTR)&status) == MMSYSERR_NOERROR &&
- devid == mmpref)
- ret |= CUBEB_DEVICE_PREF_MULTIMEDIA | CUBEB_DEVICE_PREF_NOTIFICATION;
- if (waveOutMessage((HWAVEOUT)(size_t)WAVE_MAPPER, DRVM_MAPPER_CONSOLEVOICECOM_GET,
- (DWORD_PTR)&compref, (DWORD_PTR)&status) == MMSYSERR_NOERROR &&
- devid == compref)
- ret |= CUBEB_DEVICE_PREF_VOICE;
- return ret;
- }
- static char *
- device_id_idx(UINT devid)
- {
- char * ret = (char *)malloc(sizeof(char)*16);
- if (!ret) {
- return NULL;
- }
- _snprintf_s(ret, 16, _TRUNCATE, "%u", devid);
- return ret;
- }
- static cubeb_device_info *
- winmm_create_device_from_outcaps2(LPWAVEOUTCAPS2A caps, UINT devid)
- {
- cubeb_device_info * ret;
- ret = calloc(1, sizeof(cubeb_device_info));
- if (!ret) {
- return NULL;
- }
- ret->devid = (cubeb_devid)(size_t)devid;
- ret->device_id = device_id_idx(devid);
- ret->friendly_name = _strdup(caps->szPname);
- ret->group_id = guid_to_cstr(&caps->ProductGuid);
- ret->vendor_name = guid_to_cstr(&caps->ManufacturerGuid);
- ret->type = CUBEB_DEVICE_TYPE_OUTPUT;
- ret->state = CUBEB_DEVICE_STATE_ENABLED;
- ret->preferred = winmm_query_preferred_out_device(devid);
- ret->max_channels = caps->wChannels;
- winmm_calculate_device_rate(ret, caps->dwFormats);
- winmm_query_supported_formats(devid, caps->dwFormats,
- &ret->format, &ret->default_format);
- /* Hardcoed latency estimates... */
- ret->latency_lo = 100 * ret->default_rate / 1000;
- ret->latency_hi = 200 * ret->default_rate / 1000;
- return ret;
- }
- static cubeb_device_info *
- winmm_create_device_from_outcaps(LPWAVEOUTCAPSA caps, UINT devid)
- {
- cubeb_device_info * ret;
- ret = calloc(1, sizeof(cubeb_device_info));
- if (!ret) {
- return NULL;
- }
- ret->devid = (cubeb_devid)(size_t)devid;
- ret->device_id = device_id_idx(devid);
- ret->friendly_name = _strdup(caps->szPname);
- ret->group_id = NULL;
- ret->vendor_name = NULL;
- ret->type = CUBEB_DEVICE_TYPE_OUTPUT;
- ret->state = CUBEB_DEVICE_STATE_ENABLED;
- ret->preferred = winmm_query_preferred_out_device(devid);
- ret->max_channels = caps->wChannels;
- winmm_calculate_device_rate(ret, caps->dwFormats);
- winmm_query_supported_formats(devid, caps->dwFormats,
- &ret->format, &ret->default_format);
- /* Hardcoed latency estimates... */
- ret->latency_lo = 100 * ret->default_rate / 1000;
- ret->latency_hi = 200 * ret->default_rate / 1000;
- return ret;
- }
- static cubeb_device_pref
- winmm_query_preferred_in_device(UINT devid)
- {
- DWORD mmpref = WAVE_MAPPER, compref = WAVE_MAPPER, status;
- cubeb_device_pref ret = CUBEB_DEVICE_PREF_NONE;
- if (waveInMessage((HWAVEIN)(size_t)WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET,
- (DWORD_PTR)&mmpref, (DWORD_PTR)&status) == MMSYSERR_NOERROR &&
- devid == mmpref)
- ret |= CUBEB_DEVICE_PREF_MULTIMEDIA | CUBEB_DEVICE_PREF_NOTIFICATION;
- if (waveInMessage((HWAVEIN)(size_t)WAVE_MAPPER, DRVM_MAPPER_CONSOLEVOICECOM_GET,
- (DWORD_PTR)&compref, (DWORD_PTR)&status) == MMSYSERR_NOERROR &&
- devid == compref)
- ret |= CUBEB_DEVICE_PREF_VOICE;
- return ret;
- }
- static cubeb_device_info *
- winmm_create_device_from_incaps2(LPWAVEINCAPS2A caps, UINT devid)
- {
- cubeb_device_info * ret;
- ret = calloc(1, sizeof(cubeb_device_info));
- if (!ret) {
- return NULL;
- }
- ret->devid = (cubeb_devid)(size_t)devid;
- ret->device_id = device_id_idx(devid);
- ret->friendly_name = _strdup(caps->szPname);
- ret->group_id = guid_to_cstr(&caps->ProductGuid);
- ret->vendor_name = guid_to_cstr(&caps->ManufacturerGuid);
- ret->type = CUBEB_DEVICE_TYPE_INPUT;
- ret->state = CUBEB_DEVICE_STATE_ENABLED;
- ret->preferred = winmm_query_preferred_in_device(devid);
- ret->max_channels = caps->wChannels;
- winmm_calculate_device_rate(ret, caps->dwFormats);
- winmm_query_supported_formats(devid, caps->dwFormats,
- &ret->format, &ret->default_format);
- /* Hardcoed latency estimates... */
- ret->latency_lo = 100 * ret->default_rate / 1000;
- ret->latency_hi = 200 * ret->default_rate / 1000;
- return ret;
- }
- static cubeb_device_info *
- winmm_create_device_from_incaps(LPWAVEINCAPSA caps, UINT devid)
- {
- cubeb_device_info * ret;
- ret = calloc(1, sizeof(cubeb_device_info));
- if (!ret) {
- return NULL;
- }
- ret->devid = (cubeb_devid)(size_t)devid;
- ret->device_id = device_id_idx(devid);
- ret->friendly_name = _strdup(caps->szPname);
- ret->group_id = NULL;
- ret->vendor_name = NULL;
- ret->type = CUBEB_DEVICE_TYPE_INPUT;
- ret->state = CUBEB_DEVICE_STATE_ENABLED;
- ret->preferred = winmm_query_preferred_in_device(devid);
- ret->max_channels = caps->wChannels;
- winmm_calculate_device_rate(ret, caps->dwFormats);
- winmm_query_supported_formats(devid, caps->dwFormats,
- &ret->format, &ret->default_format);
- /* Hardcoed latency estimates... */
- ret->latency_lo = 100 * ret->default_rate / 1000;
- ret->latency_hi = 200 * ret->default_rate / 1000;
- return ret;
- }
- static int
- winmm_enumerate_devices(cubeb * context, cubeb_device_type type,
- cubeb_device_collection ** collection)
- {
- UINT i, incount, outcount, total;
- cubeb_device_info * cur;
- outcount = waveOutGetNumDevs();
- incount = waveInGetNumDevs();
- total = outcount + incount;
- if (total > 0) {
- total -= 1;
- }
- *collection = malloc(sizeof(cubeb_device_collection) +
- sizeof(cubeb_device_info*) * total);
- (*collection)->count = 0;
- if (type & CUBEB_DEVICE_TYPE_OUTPUT) {
- WAVEOUTCAPSA woc;
- WAVEOUTCAPS2A woc2;
- ZeroMemory(&woc, sizeof(woc));
- ZeroMemory(&woc2, sizeof(woc2));
- for (i = 0; i < outcount; i++) {
- if ((waveOutGetDevCapsA(i, (LPWAVEOUTCAPSA)&woc2, sizeof(woc2)) == MMSYSERR_NOERROR &&
- (cur = winmm_create_device_from_outcaps2(&woc2, i)) != NULL) ||
- (waveOutGetDevCapsA(i, &woc, sizeof(woc)) == MMSYSERR_NOERROR &&
- (cur = winmm_create_device_from_outcaps(&woc, i)) != NULL)
- ) {
- (*collection)->device[(*collection)->count++] = cur;
- }
- }
- }
- if (type & CUBEB_DEVICE_TYPE_INPUT) {
- WAVEINCAPSA wic;
- WAVEINCAPS2A wic2;
- ZeroMemory(&wic, sizeof(wic));
- ZeroMemory(&wic2, sizeof(wic2));
- for (i = 0; i < incount; i++) {
- if ((waveInGetDevCapsA(i, (LPWAVEINCAPSA)&wic2, sizeof(wic2)) == MMSYSERR_NOERROR &&
- (cur = winmm_create_device_from_incaps2(&wic2, i)) != NULL) ||
- (waveInGetDevCapsA(i, &wic, sizeof(wic)) == MMSYSERR_NOERROR &&
- (cur = winmm_create_device_from_incaps(&wic, i)) != NULL)
- ) {
- (*collection)->device[(*collection)->count++] = cur;
- }
- }
- }
- return CUBEB_OK;
- }
- static struct cubeb_ops const winmm_ops = {
- /*.init =*/ winmm_init,
- /*.get_backend_id =*/ winmm_get_backend_id,
- /*.get_max_channel_count=*/ winmm_get_max_channel_count,
- /*.get_min_latency=*/ winmm_get_min_latency,
- /*.get_preferred_sample_rate =*/ winmm_get_preferred_sample_rate,
- /*.enumerate_devices =*/ winmm_enumerate_devices,
- /*.destroy =*/ winmm_destroy,
- /*.stream_init =*/ winmm_stream_init,
- /*.stream_destroy =*/ winmm_stream_destroy,
- /*.stream_start =*/ winmm_stream_start,
- /*.stream_stop =*/ winmm_stream_stop,
- /*.stream_get_position =*/ winmm_stream_get_position,
- /*.stream_get_latency = */ winmm_stream_get_latency,
- /*.stream_set_volume =*/ winmm_stream_set_volume,
- /*.stream_set_panning =*/ NULL,
- /*.stream_get_current_device =*/ NULL,
- /*.stream_device_destroy =*/ NULL,
- /*.stream_register_device_changed_callback=*/ NULL,
- /*.register_device_collection_changed =*/ NULL
- };
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement