/* * Copyright © 2011 Mozilla Foundation * * This program is made available under an ISC-style license. See the * accompanying file LICENSE for details. */ #undef WINVER #define WINVER 0x0501 #undef WIN32_LEAN_AND_MEAN #include #include #include #include #include #include #include #include #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 { /* Note: Must match cubeb_stream layout in cubeb.c. */ cubeb * context; void * user_ptr; /**/ cubeb_stream_params params; cubeb_data_callback data_callback; cubeb_state_callback state_callback; 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; }; 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); XASSERT(output_stream_params); 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; } if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) { /* Loopback is not supported */ return CUBEB_ERROR_NOT_SUPPORTED; } *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); } *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; } static int winmm_stream_get_position(cubeb_stream * stm, uint64_t * position) { MMRESULT r; MMTIME time; EnterCriticalSection(&stm->lock); time.wType = TIME_SAMPLES; r = waveOutGetPosition(stm->waveout, &time, sizeof(time)); LeaveCriticalSection(&stm->lock); if (r != MMSYSERR_NOERROR || time.wType != TIME_SAMPLES) { return CUBEB_ERROR; } *position = time.u.sample; return CUBEB_OK; } static int winmm_stream_get_latency(cubeb_stream * stm, uint32_t * latency) { MMRESULT r; MMTIME time; uint64_t written; EnterCriticalSection(&stm->lock); time.wType = TIME_SAMPLES; r = waveOutGetPosition(stm->waveout, &time, sizeof(time)); written = stm->written; LeaveCriticalSection(&stm->lock); if (r != MMSYSERR_NOERROR || time.wType != TIME_SAMPLES) { return CUBEB_ERROR; } XASSERT(written - time.u.sample <= UINT32_MAX); *latency = (uint32_t) (written - time.u.sample); 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(40); if (!ret) { return NULL; } _snprintf_s(ret, 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) 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) 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 = malloc(16); if (!ret) { return NULL; } _snprintf_s(ret, 16, _TRUNCATE, "%u", devid); return ret; } static void winmm_create_device_from_outcaps2(cubeb_device_info * ret, LPWAVEOUTCAPS2A caps, UINT devid) { XASSERT(ret); ret->devid = (cubeb_devid) 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); /* Hardcoded latency estimates... */ ret->latency_lo = 100 * ret->default_rate / 1000; ret->latency_hi = 200 * ret->default_rate / 1000; } static void winmm_create_device_from_outcaps(cubeb_device_info * ret, LPWAVEOUTCAPSA caps, UINT devid) { XASSERT(ret); ret->devid = (cubeb_devid) 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); /* Hardcoded latency estimates... */ ret->latency_lo = 100 * ret->default_rate / 1000; ret->latency_hi = 200 * ret->default_rate / 1000; } 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) 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) WAVE_MAPPER, DRVM_MAPPER_CONSOLEVOICECOM_GET, (DWORD_PTR)&compref, (DWORD_PTR)&status) == MMSYSERR_NOERROR && devid == compref) ret |= CUBEB_DEVICE_PREF_VOICE; return ret; } static void winmm_create_device_from_incaps2(cubeb_device_info * ret, LPWAVEINCAPS2A caps, UINT devid) { XASSERT(ret); ret->devid = (cubeb_devid) 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); /* Hardcoded latency estimates... */ ret->latency_lo = 100 * ret->default_rate / 1000; ret->latency_hi = 200 * ret->default_rate / 1000; } static void winmm_create_device_from_incaps(cubeb_device_info * ret, LPWAVEINCAPSA caps, UINT devid) { XASSERT(ret); ret->devid = (cubeb_devid) 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); /* Hardcoded latency estimates... */ ret->latency_lo = 100 * ret->default_rate / 1000; ret->latency_hi = 200 * ret->default_rate / 1000; } static int winmm_enumerate_devices(cubeb * context, cubeb_device_type type, cubeb_device_collection * collection) { UINT i, incount, outcount, total; cubeb_device_info * devices; cubeb_device_info * dev; outcount = waveOutGetNumDevs(); incount = waveInGetNumDevs(); total = outcount + incount; devices = calloc(total, sizeof(cubeb_device_info)); 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++) { dev = &devices[collection->count]; if (waveOutGetDevCapsA(i, (LPWAVEOUTCAPSA)&woc2, sizeof(woc2)) == MMSYSERR_NOERROR) { winmm_create_device_from_outcaps2(dev, &woc2, i); collection->count += 1; } else if (waveOutGetDevCapsA(i, &woc, sizeof(woc)) == MMSYSERR_NOERROR) { winmm_create_device_from_outcaps(dev, &woc, i); collection->count += 1; } } } if (type & CUBEB_DEVICE_TYPE_INPUT) { WAVEINCAPSA wic; WAVEINCAPS2A wic2; ZeroMemory(&wic, sizeof(wic)); ZeroMemory(&wic2, sizeof(wic2)); for (i = 0; i < incount; i++) { dev = &devices[collection->count]; if (waveInGetDevCapsA(i, (LPWAVEINCAPSA)&wic2, sizeof(wic2)) == MMSYSERR_NOERROR) { winmm_create_device_from_incaps2(dev, &wic2, i); collection->count += 1; } else if (waveInGetDevCapsA(i, &wic, sizeof(wic)) == MMSYSERR_NOERROR) { winmm_create_device_from_incaps(dev, &wic, i); collection->count += 1; } } } collection->device = devices; return CUBEB_OK; } static int winmm_device_collection_destroy(cubeb * ctx, cubeb_device_collection * collection) { uint32_t i; XASSERT(collection); (void) ctx; for (i = 0; i < collection->count; i++) { free((void *) collection->device[i].device_id); free((void *) collection->device[i].friendly_name); free((void *) collection->device[i].group_id); free((void *) collection->device[i].vendor_name); } free(collection->device); 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, /*.device_collection_destroy =*/ winmm_device_collection_destroy, /*.destroy =*/ winmm_destroy, /*.stream_init =*/ winmm_stream_init, /*.stream_destroy =*/ winmm_stream_destroy, /*.stream_start =*/ winmm_stream_start, /*.stream_stop =*/ winmm_stream_stop, /*.stream_reset_default_device =*/ NULL, /*.stream_get_position =*/ winmm_stream_get_position, /*.stream_get_latency = */ winmm_stream_get_latency, /*.stream_get_input_latency = */ NULL, /*.stream_set_volume =*/ winmm_stream_set_volume, /*.stream_set_name =*/ NULL, /*.stream_get_current_device =*/ NULL, /*.stream_device_destroy =*/ NULL, /*.stream_register_device_changed_callback=*/ NULL, /*.register_device_collection_changed =*/ NULL };