osx_plugin.c 7.38 KB
Newer Older
1 2 3
/*
 * Copyright (C) 2003-2009 The Music Player Daemon Project
 * http://www.musicpd.org
4 5 6 7 8 9 10 11 12 13
 *
 * 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.
14 15 16 17
 *
 * 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.
18 19
 */

20
#include "../output_api.h"
21

22
#include <glib.h>
23 24
#include <AudioUnit/AudioUnit.h>

25 26 27
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "osx"

Max Kellermann's avatar
Max Kellermann committed
28
struct osx_output {
29
	AudioUnit au;
30 31
	GMutex *mutex;
	GCond *condition;
Avuton Olrich's avatar
Avuton Olrich committed
32
	char *buffer;
Max Kellermann's avatar
Max Kellermann committed
33
	size_t buffer_size;
34 35
	size_t pos;
	size_t len;
Max Kellermann's avatar
Max Kellermann committed
36
};
37

38 39 40 41 42 43 44 45 46
/**
 * The quark used for GError.domain.
 */
static inline GQuark
osx_output_quark(void)
{
	return g_quark_from_static_string("osx_output");
}

Max Kellermann's avatar
Max Kellermann committed
47 48
static bool
osx_output_test_default_device(void)
Avuton Olrich's avatar
Avuton Olrich committed
49
{
50 51
	/* on a Mac, this is always the default plugin, if nothing
	   else is configured */
52
	return true;
Warren Dukes's avatar
Warren Dukes committed
53 54
}

55
static void *
Max Kellermann's avatar
Max Kellermann committed
56
osx_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
57 58
		G_GNUC_UNUSED const struct config_param *param,
		G_GNUC_UNUSED GError **error)
Avuton Olrich's avatar
Avuton Olrich committed
59
{
Max Kellermann's avatar
Max Kellermann committed
60 61 62 63 64 65 66 67 68 69 70
	struct osx_output *oo = g_new(struct osx_output, 1);

	oo->mutex = g_mutex_new();
	oo->condition = g_cond_new();

	oo->pos = 0;
	oo->len = 0;
	oo->buffer = NULL;
	oo->buffer_size = 0;

	return oo;
71 72
}

Max Kellermann's avatar
Max Kellermann committed
73
static void osx_output_finish(void *data)
Avuton Olrich's avatar
Avuton Olrich committed
74
{
Max Kellermann's avatar
Max Kellermann committed
75 76
	struct osx_output *od = data;

77
	g_free(od->buffer);
78 79
	g_mutex_free(od->mutex);
	g_cond_free(od->condition);
80
	g_free(od);
81 82
}

Max Kellermann's avatar
Max Kellermann committed
83
static void osx_output_cancel(void *data)
Avuton Olrich's avatar
Avuton Olrich committed
84
{
Max Kellermann's avatar
Max Kellermann committed
85
	struct osx_output *od = data;
86

87
	g_mutex_lock(od->mutex);
88
	od->len = 0;
89
	g_mutex_unlock(od->mutex);
90 91
}

Max Kellermann's avatar
Max Kellermann committed
92
static void osx_output_close(void *data)
Avuton Olrich's avatar
Avuton Olrich committed
93
{
Max Kellermann's avatar
Max Kellermann committed
94
	struct osx_output *od = data;
95

96
	g_mutex_lock(od->mutex);
Avuton Olrich's avatar
Avuton Olrich committed
97
	while (od->len) {
98
		g_cond_wait(od->condition, od->mutex);
99
	}
100
	g_mutex_unlock(od->mutex);
101

102
	AudioOutputUnitStop(od->au);
103
	AudioUnitUninitialize(od->au);
104
	CloseComponent(od->au);
105 106
}

Max Kellermann's avatar
Max Kellermann committed
107 108
static OSStatus
osx_render(void *vdata,
Max Kellermann's avatar
Max Kellermann committed
109 110 111 112 113
	   G_GNUC_UNUSED AudioUnitRenderActionFlags *io_action_flags,
	   G_GNUC_UNUSED const AudioTimeStamp *in_timestamp,
	   G_GNUC_UNUSED UInt32 in_bus_number,
	   G_GNUC_UNUSED UInt32 in_number_frames,
	   AudioBufferList *buffer_list)
Warren Dukes's avatar
Warren Dukes committed
114
{
Max Kellermann's avatar
Max Kellermann committed
115 116 117 118 119 120
	struct osx_output *od = (struct osx_output *) vdata;
	AudioBuffer *buffer = &buffer_list->mBuffers[0];
	size_t buffer_size = buffer->mDataByteSize;
	size_t bytes_to_copy;
	size_t trailer_length;
	size_t dest_pos = 0;
Warren Dukes's avatar
Warren Dukes committed
121

122
	g_mutex_lock(od->mutex);
Avuton Olrich's avatar
Avuton Olrich committed
123

Max Kellermann's avatar
Max Kellermann committed
124 125 126
	bytes_to_copy = MIN(od->len, buffer_size);
	buffer_size = bytes_to_copy;
	od->len -= bytes_to_copy;
Avuton Olrich's avatar
Avuton Olrich committed
127

Max Kellermann's avatar
Max Kellermann committed
128 129 130 131
	trailer_length = od->buffer_size - od->pos;
	if (bytes_to_copy > trailer_length) {
		memcpy((unsigned char*)buffer->mData + dest_pos,
		       od->buffer + od->pos, trailer_length);
Avuton Olrich's avatar
Avuton Olrich committed
132
		od->pos = 0;
Max Kellermann's avatar
Max Kellermann committed
133 134
		dest_pos += trailer_length;
		bytes_to_copy -= trailer_length;
Avuton Olrich's avatar
Avuton Olrich committed
135
	}
Warren Dukes's avatar
Warren Dukes committed
136

Max Kellermann's avatar
Max Kellermann committed
137 138 139
	memcpy((unsigned char*)buffer->mData + dest_pos,
	       od->buffer + od->pos, bytes_to_copy);
	od->pos += bytes_to_copy;
140

Max Kellermann's avatar
Max Kellermann committed
141
	if (od->pos >= od->buffer_size)
Avuton Olrich's avatar
Avuton Olrich committed
142
		od->pos = 0;
143

144 145
	g_mutex_unlock(od->mutex);
	g_cond_signal(od->condition);
Warren Dukes's avatar
Warren Dukes committed
146

Max Kellermann's avatar
Max Kellermann committed
147
	buffer->mDataByteSize = buffer_size;
Warren Dukes's avatar
Warren Dukes committed
148

Max Kellermann's avatar
Max Kellermann committed
149
	if (!buffer_size) {
150
		g_usleep(1000);
Warren Dukes's avatar
Warren Dukes committed
151
	}
Warren Dukes's avatar
Warren Dukes committed
152 153 154 155

	return 0;
}

156
static bool
157
osx_output_open(void *data, struct audio_format *audio_format, GError **error)
Avuton Olrich's avatar
Avuton Olrich committed
158
{
Max Kellermann's avatar
Max Kellermann committed
159
	struct osx_output *od = data;
160 161 162
	ComponentDescription desc;
	Component comp;
	AURenderCallbackStruct callback;
Max Kellermann's avatar
Max Kellermann committed
163
	AudioStreamBasicDescription stream_description;
164 165
	OSStatus status;
	ComponentResult result;
166

Max Kellermann's avatar
Max Kellermann committed
167 168
	if (audio_format->bits > 16)
		audio_format->bits = 16;
169

170 171 172 173 174 175 176
	desc.componentType = kAudioUnitType_Output;
	desc.componentSubType = kAudioUnitSubType_DefaultOutput;
	desc.componentManufacturer = kAudioUnitManufacturer_Apple;
	desc.componentFlags = 0;
	desc.componentFlagsMask = 0;

	comp = FindNextComponent(NULL, &desc);
Avuton Olrich's avatar
Avuton Olrich committed
177
	if (comp == 0) {
178 179
		g_set_error(error, osx_output_quark(), 0,
			    "Error finding OS X component");
180
		return false;
Warren Dukes's avatar
Warren Dukes committed
181 182
	}

183 184
	status = OpenAComponent(comp, &od->au);
	if (status != noErr) {
185 186 187
		g_set_error(error, osx_output_quark(), 0,
			    "Unable to open OS X component: %s",
			    GetMacOSStatusCommentString(status));
188
		return false;
Warren Dukes's avatar
Warren Dukes committed
189 190
	}

191 192
	status = AudioUnitInitialize(od->au);
	if (status != noErr) {
193
		CloseComponent(od->au);
194 195 196
		g_set_error(error, osx_output_quark(), 0,
			    "Unable to initialize OS X audio unit: %s",
			    GetMacOSStatusCommentString(status));
197
		return false;
198 199 200 201 202
	}

	callback.inputProc = osx_render;
	callback.inputProcRefCon = od;

203 204 205 206 207
	result = AudioUnitSetProperty(od->au,
				      kAudioUnitProperty_SetRenderCallback,
				      kAudioUnitScope_Input, 0,
				      &callback, sizeof(callback));
	if (result != noErr) {
208 209
		AudioUnitUninitialize(od->au);
		CloseComponent(od->au);
210 211
		g_set_error(error, osx_output_quark(), 0,
			    "unable to set callback for OS X audio unit");
212
		return false;
213
	}
Warren Dukes's avatar
Warren Dukes committed
214

Max Kellermann's avatar
Max Kellermann committed
215 216 217
	stream_description.mSampleRate = audio_format->sample_rate;
	stream_description.mFormatID = kAudioFormatLinearPCM;
	stream_description.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
Max Kellermann's avatar
Max Kellermann committed
218
#if G_BYTE_ORDER == G_BIG_ENDIAN
Max Kellermann's avatar
Max Kellermann committed
219
	stream_description.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
220 221
#endif

Max Kellermann's avatar
Max Kellermann committed
222 223 224 225 226 227
	stream_description.mBytesPerPacket =
		audio_format_frame_size(audio_format);
	stream_description.mFramesPerPacket = 1;
	stream_description.mBytesPerFrame = stream_description.mBytesPerPacket;
	stream_description.mChannelsPerFrame = audio_format->channels;
	stream_description.mBitsPerChannel = audio_format->bits;
228

229 230 231 232 233
	result = AudioUnitSetProperty(od->au, kAudioUnitProperty_StreamFormat,
				      kAudioUnitScope_Input, 0,
				      &stream_description,
				      sizeof(stream_description));
	if (result != noErr) {
234 235
		AudioUnitUninitialize(od->au);
		CloseComponent(od->au);
236 237
		g_set_error(error, osx_output_quark(), 0,
			    "Unable to set format on OS X device");
238
		return false;
239 240
	}

241
	/* create a buffer of 1s */
Max Kellermann's avatar
Max Kellermann committed
242 243 244
	od->buffer_size = (audio_format->sample_rate) *
		audio_format_frame_size(audio_format);
	od->buffer = g_realloc(od->buffer, od->buffer_size);
245

Warren Dukes's avatar
Warren Dukes committed
246 247
	od->pos = 0;
	od->len = 0;
248

249 250
	status = AudioOutputUnitStart(od->au);
	if (status != 0) {
251 252 253
		g_set_error(error, osx_output_quark(), 0,
			    "unable to start audio output: %s",
			    GetMacOSStatusCommentString(status));
254 255 256
		return false;
	}

257
	return true;
258 259
}

260
static size_t
261 262
osx_output_play(void *data, const void *chunk, size_t size,
		G_GNUC_UNUSED GError **error)
Avuton Olrich's avatar
Avuton Olrich committed
263
{
Max Kellermann's avatar
Max Kellermann committed
264
	struct osx_output *od = data;
265
	size_t start, nbytes;
Warren Dukes's avatar
Warren Dukes committed
266

267
	g_mutex_lock(od->mutex);
Warren Dukes's avatar
Warren Dukes committed
268

Max Kellermann's avatar
Max Kellermann committed
269
	while (od->len >= od->buffer_size)
270 271
		/* wait for some free space in the buffer */
		g_cond_wait(od->condition, od->mutex);
Warren Dukes's avatar
Warren Dukes committed
272

273
	start = od->pos + od->len;
Max Kellermann's avatar
Max Kellermann committed
274 275
	if (start >= od->buffer_size)
		start -= od->buffer_size;
Warren Dukes's avatar
Warren Dukes committed
276

277 278
	nbytes = start < od->pos
		? od->pos - start
Max Kellermann's avatar
Max Kellermann committed
279
		: od->buffer_size - start;
Warren Dukes's avatar
Warren Dukes committed
280

281
	assert(nbytes > 0);
282

283 284
	if (nbytes > size)
		nbytes = size;
285

286
	memcpy(od->buffer + start, chunk, nbytes);
287
	od->len += nbytes;
288

289
	g_mutex_unlock(od->mutex);
290

291
	return nbytes;
292 293
}

294
const struct audio_output_plugin osxPlugin = {
295
	.name = "osx",
Max Kellermann's avatar
Max Kellermann committed
296 297 298 299 300 301 302
	.test_default_device = osx_output_test_default_device,
	.init = osx_output_init,
	.finish = osx_output_finish,
	.open = osx_output_open,
	.close = osx_output_close,
	.play = osx_output_play,
	.cancel = osx_output_cancel,
303
};