WinmmOutputPlugin.cxx 8.04 KB
Newer Older
1
/*
2
 * Copyright (C) 2003-2013 The Music Player Daemon Project
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
 * http://www.musicpd.org
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include "config.h"
21
#include "WinmmOutputPlugin.hxx"
22
#include "OutputAPI.hxx"
23
#include "pcm/PcmBuffer.hxx"
24
#include "MixerList.hxx"
25 26
#include "util/Error.hxx"
#include "util/Domain.hxx"
27
#include "util/Macros.hxx"
28

29 30
#include <glib.h>

31 32
#include <stdlib.h>
#include <string.h>
33

34
struct WinmmBuffer {
35
	PcmBuffer buffer;
36 37 38 39

	WAVEHDR hdr;
};

40
struct WinmmOutput {
41 42
	struct audio_output base;

43
	UINT device_id;
44 45 46 47 48 49 50 51
	HWAVEOUT handle;

	/**
	 * This event is triggered by Windows when a buffer is
	 * finished.
	 */
	HANDLE event;

52
	WinmmBuffer buffers[8];
53 54 55
	unsigned next_buffer;
};

56
static constexpr Domain winmm_output_domain("winmm_output");
57

58
HWAVEOUT
59
winmm_output_get_handle(WinmmOutput *output)
60 61 62 63
{
	return output->handle;
}

64
static bool
65
winmm_output_test_default_device(void)
66
{
67
	return waveOutGetNumDevs() > 0;
68 69
}

70
static bool
71
get_device_id(const char *device_name, UINT *device_id, Error &error)
72 73
{
	/* if device is not specified use wave mapper */
74
	if (device_name == nullptr) {
75 76 77 78 79
		*device_id = WAVE_MAPPER;
		return true;
	}

	UINT numdevs = waveOutGetNumDevs();
80 81 82 83

	/* check for device id */
	char *endptr;
	UINT id = strtoul(device_name, &endptr, 0);
84 85 86 87 88 89
	if (endptr > device_name && *endptr == 0) {
		if (id >= numdevs)
			goto fail;
		*device_id = id;
		return true;
	}
90 91

	/* check for device name */
92
	for (UINT i = 0; i < numdevs; i++) {
93 94 95 96 97 98
		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. */
99 100 101 102
		if (strstr(device_name, caps.szPname) == device_name) {
			*device_id = i;
			return true;
		}
103 104
	}

105
fail:
106 107
	error.Format(winmm_output_domain,
		     "device \"%s\" is not found", device_name);
108
	return false;
109 110
}

111
static struct audio_output *
112
winmm_output_init(const config_param &param, Error &error)
113
{
114
	WinmmOutput *wo = new WinmmOutput();
115
	if (!ao_base_init(&wo->base, &winmm_output_plugin, param, error)) {
116
		delete wo;
117
		return nullptr;
118 119
	}

120
	const char *device = param.GetBlockValue("device");
121
	if (!get_device_id(device, &wo->device_id, error)) {
122
		ao_base_finish(&wo->base);
123
		delete wo;
124
		return nullptr;
125 126
	}

127
	return &wo->base;
128 129 130
}

static void
131
winmm_output_finish(struct audio_output *ao)
132
{
133
	WinmmOutput *wo = (WinmmOutput *)ao;
134

135
	ao_base_finish(&wo->base);
136
	delete wo;
137 138 139
}

static bool
140
winmm_output_open(struct audio_output *ao, AudioFormat &audio_format,
141
		  Error &error)
142
{
143
	WinmmOutput *wo = (WinmmOutput *)ao;
144

145 146
	wo->event = CreateEvent(nullptr, false, false, nullptr);
	if (wo->event == nullptr) {
147
		error.Set(winmm_output_domain, "CreateEvent() failed");
148 149 150
		return false;
	}

151 152 153
	switch (audio_format.format) {
	case SampleFormat::S8:
	case SampleFormat::S16:
154 155
		break;

156 157 158 159 160
	case SampleFormat::S24_P32:
	case SampleFormat::S32:
	case SampleFormat::FLOAT:
	case SampleFormat::DSD:
	case SampleFormat::UNDEFINED:
161
		/* we havn't tested formats other than S16 */
162
		audio_format.format = SampleFormat::S16;
163 164 165
		break;
	}

166
	if (audio_format.channels > 2)
167
		/* same here: more than stereo was not tested */
168
		audio_format.channels = 2;
169 170 171

	WAVEFORMATEX format;
	format.wFormatTag = WAVE_FORMAT_PCM;
172 173 174
	format.nChannels = audio_format.channels;
	format.nSamplesPerSec = audio_format.sample_rate;
	format.nBlockAlign = audio_format.GetFrameSize();
175
	format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
176
	format.wBitsPerSample = audio_format.GetSampleSize() * 8;
177 178
	format.cbSize = 0;

179
	MMRESULT result = waveOutOpen(&wo->handle, wo->device_id, &format,
180 181 182
				      (DWORD_PTR)wo->event, 0, CALLBACK_EVENT);
	if (result != MMSYSERR_NOERROR) {
		CloseHandle(wo->event);
183
		error.Set(winmm_output_domain, "waveOutOpen() failed");
184 185 186
		return false;
	}

187
	for (unsigned i = 0; i < ARRAY_SIZE(wo->buffers); ++i) {
188 189 190 191 192 193 194 195 196
		memset(&wo->buffers[i].hdr, 0, sizeof(wo->buffers[i].hdr));
	}

	wo->next_buffer = 0;

	return true;
}

static void
197
winmm_output_close(struct audio_output *ao)
198
{
199
	WinmmOutput *wo = (WinmmOutput *)ao;
200

201
	for (unsigned i = 0; i < ARRAY_SIZE(wo->buffers); ++i)
202
		wo->buffers[i].buffer.Clear();
203 204 205 206 207 208 209 210 211 212

	waveOutClose(wo->handle);

	CloseHandle(wo->event);
}

/**
 * Copy data into a buffer, and prepare the wave header.
 */
static bool
213
winmm_set_buffer(WinmmOutput *wo, WinmmBuffer *buffer,
214
		 const void *data, size_t size,
215
		 Error &error)
216
{
217
	void *dest = buffer->buffer.Get(size);
218
	assert(dest != nullptr);
219 220 221 222

	memcpy(dest, data, size);

	memset(&buffer->hdr, 0, sizeof(buffer->hdr));
223
	buffer->hdr.lpData = (LPSTR)dest;
224 225 226 227 228
	buffer->hdr.dwBufferLength = size;

	MMRESULT result = waveOutPrepareHeader(wo->handle, &buffer->hdr,
					       sizeof(buffer->hdr));
	if (result != MMSYSERR_NOERROR) {
229 230
		error.Set(winmm_output_domain, result,
			  "waveOutPrepareHeader() failed");
231 232 233 234 235 236 237 238 239 240
		return false;
	}

	return true;
}

/**
 * Wait until the buffer is finished.
 */
static bool
241
winmm_drain_buffer(WinmmOutput *wo, WinmmBuffer *buffer,
242
		   Error &error)
243 244 245 246 247 248 249 250 251 252 253 254
{
	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) {
255 256
			error.Set(winmm_output_domain, result,
				  "waveOutUnprepareHeader() failed");
257 258 259 260 261 262 263 264 265
			return false;
		}

		/* wait some more */
		WaitForSingleObject(wo->event, INFINITE);
	}
}

static size_t
266
winmm_output_play(struct audio_output *ao, const void *chunk, size_t size, Error &error)
267
{
268
	WinmmOutput *wo = (WinmmOutput *)ao;
269 270

	/* get the next buffer from the ring and prepare it */
271
	WinmmBuffer *buffer = &wo->buffers[wo->next_buffer];
272 273
	if (!winmm_drain_buffer(wo, buffer, error) ||
	    !winmm_set_buffer(wo, buffer, chunk, size, error))
274 275 276 277 278 279 280 281
		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));
282 283
		error.Set(winmm_output_domain, result,
			  "waveOutWrite() failed");
284 285 286 287 288
		return 0;
	}

	/* mark our buffer as "used" */
	wo->next_buffer = (wo->next_buffer + 1) %
289
		ARRAY_SIZE(wo->buffers);
290 291 292 293 294

	return size;
}

static bool
295
winmm_drain_all_buffers(WinmmOutput *wo, Error &error)
296
{
297
	for (unsigned i = wo->next_buffer; i < ARRAY_SIZE(wo->buffers); ++i)
298
		if (!winmm_drain_buffer(wo, &wo->buffers[i], error))
299 300 301
			return false;

	for (unsigned i = 0; i < wo->next_buffer; ++i)
302
		if (!winmm_drain_buffer(wo, &wo->buffers[i], error))
303 304 305 306 307 308
			return false;

	return true;
}

static void
309
winmm_stop(WinmmOutput *wo)
310 311 312
{
	waveOutReset(wo->handle);

313
	for (unsigned i = 0; i < ARRAY_SIZE(wo->buffers); ++i) {
314
		WinmmBuffer *buffer = &wo->buffers[i];
315 316 317 318 319 320
		waveOutUnprepareHeader(wo->handle, &buffer->hdr,
				       sizeof(buffer->hdr));
	}
}

static void
321
winmm_output_drain(struct audio_output *ao)
322
{
323
	WinmmOutput *wo = (WinmmOutput *)ao;
324

325
	if (!winmm_drain_all_buffers(wo, IgnoreError()))
326
		winmm_stop(wo);
327 328 329
}

static void
330
winmm_output_cancel(struct audio_output *ao)
331
{
332
	WinmmOutput *wo = (WinmmOutput *)ao;
333

334
	winmm_stop(wo);
335 336
}

337
const struct audio_output_plugin winmm_output_plugin = {
338 339 340 341 342 343 344 345 346 347 348 349 350 351 352
	"winmm",
	winmm_output_test_default_device,
	winmm_output_init,
	winmm_output_finish,
	nullptr,
	nullptr,
	winmm_output_open,
	winmm_output_close,
	nullptr,
	nullptr,
	winmm_output_play,
	winmm_output_drain,
	winmm_output_cancel,
	nullptr,
	&winmm_mixer_plugin,
353
};