/** * Copy data into a buffer, and prepare the wave header. */ static bool winmm_set_buffer(struct winmm_output *wo, struct winmm_buffer *buffer, const void *data, size_t size, GError **error_r) { void *dest = pcm_buffer_get(&buffer->buffer, size); if (dest == NULL) { g_set_error(error_r, winmm_output_quark(), 0, "Out of memory"); return false; } memcpy(dest, data, size); memset(&buffer->hdr, 0, sizeof(buffer->hdr)); buffer->hdr.lpData = dest; buffer->hdr.dwBufferLength = size; MMRESULT result = waveOutPrepareHeader(wo->handle, &buffer->hdr, sizeof(buffer->hdr)); if (result != MMSYSERR_NOERROR) { g_set_error(error_r, winmm_output_quark(), result, "waveOutPrepareHeader() failed"); return false; } return true; }
static bool winmm_output_open(void *data, struct audio_format *audio_format, GError **error_r) { struct winmm_output *wo = data; wo->event = CreateEvent(NULL, false, false, NULL); if (wo->event == NULL) { g_set_error(error_r, winmm_output_quark(), 0, "CreateEvent() failed"); return false; } switch (audio_format->format) { case SAMPLE_FORMAT_S8: case SAMPLE_FORMAT_S16: break; case SAMPLE_FORMAT_S24: case SAMPLE_FORMAT_S24_P32: case SAMPLE_FORMAT_S32: case SAMPLE_FORMAT_UNDEFINED: /* we havn't tested formats other than S16 */ audio_format->format = SAMPLE_FORMAT_S16; break; } if (audio_format->channels > 2) /* same here: more than stereo was not tested */ audio_format->channels = 2; WAVEFORMATEX format; format.wFormatTag = WAVE_FORMAT_PCM; format.nChannels = audio_format->channels; format.nSamplesPerSec = audio_format->sample_rate; format.nBlockAlign = audio_format_frame_size(audio_format); format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; format.wBitsPerSample = audio_format_sample_size(audio_format) * 8; format.cbSize = 0; MMRESULT result = waveOutOpen(&wo->handle, wo->device_id, &format, (DWORD_PTR)wo->event, 0, CALLBACK_EVENT); if (result != MMSYSERR_NOERROR) { CloseHandle(wo->event); g_set_error(error_r, winmm_output_quark(), result, "waveOutOpen() failed"); return false; } for (unsigned i = 0; i < G_N_ELEMENTS(wo->buffers); ++i) { pcm_buffer_init(&wo->buffers[i].buffer); memset(&wo->buffers[i].hdr, 0, sizeof(wo->buffers[i].hdr)); } wo->next_buffer = 0; return true; }
static size_t winmm_output_play(void *data, const void *chunk, size_t size, GError **error_r) { struct winmm_output *wo = data; /* get the next buffer from the ring and prepare it */ struct winmm_buffer *buffer = &wo->buffers[wo->next_buffer]; if (!winmm_drain_buffer(wo, buffer, error_r) || !winmm_set_buffer(wo, buffer, chunk, size, error_r)) return 0; /* enqueue the buffer */ MMRESULT result = waveOutWrite(wo->handle, &buffer->hdr, sizeof(buffer->hdr)); if (result != MMSYSERR_NOERROR) { waveOutUnprepareHeader(wo->handle, &buffer->hdr, sizeof(buffer->hdr)); g_set_error(error_r, winmm_output_quark(), result, "waveOutWrite() failed"); return 0; } /* mark our buffer as "used" */ wo->next_buffer = (wo->next_buffer + 1) % G_N_ELEMENTS(wo->buffers); return size; }
static bool get_device_id(const char *device_name, UINT *device_id, GError **error_r) { /* if device is not specified use wave mapper */ if (device_name == NULL) { *device_id = WAVE_MAPPER; return true; } UINT numdevs = waveOutGetNumDevs(); /* check for device id */ char *endptr; UINT id = strtoul(device_name, &endptr, 0); if (endptr > device_name && *endptr == 0) { if (id >= numdevs) goto fail; *device_id = id; return true; } /* check for device name */ for (UINT i = 0; i < numdevs; i++) { WAVEOUTCAPS caps; MMRESULT result = waveOutGetDevCaps(i, &caps, sizeof(caps)); if (result != MMSYSERR_NOERROR) continue; /* szPname is only 32 chars long, so it is often truncated. Use partial match to work around this. */ if (strstr(device_name, caps.szPname) == device_name) { *device_id = i; return true; } } fail: g_set_error(error_r, winmm_output_quark(), 0, "device \"%s\" is not found", device_name); return false; }
/** * Wait until the buffer is finished. */ static bool winmm_drain_buffer(struct winmm_output *wo, struct winmm_buffer *buffer, GError **error_r) { if ((buffer->hdr.dwFlags & WHDR_DONE) == WHDR_DONE) /* already finished */ return true; while (true) { MMRESULT result = waveOutUnprepareHeader(wo->handle, &buffer->hdr, sizeof(buffer->hdr)); if (result == MMSYSERR_NOERROR) return true; else if (result != WAVERR_STILLPLAYING) { g_set_error(error_r, winmm_output_quark(), result, "waveOutUnprepareHeader() failed"); return false; } /* wait some more */ WaitForSingleObject(wo->event, INFINITE); } }