OSXOutputPlugin.cxx 18.3 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2017 The Music Player Daemon Project
3
 * 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 "config.h"
21
#include "OSXOutputPlugin.hxx"
22
#include "../OutputAPI.hxx"
23
#include "util/ScopeExit.hxx"
24
#include "util/RuntimeError.hxx"
25
#include "util/Domain.hxx"
26 27
#include "thread/Mutex.hxx"
#include "thread/Cond.hxx"
28
#include "system/ByteOrder.hxx"
29
#include "Log.hxx"
30

31
#include <CoreAudio/CoreAudio.h>
32
#include <AudioUnit/AudioUnit.h>
33
#include <CoreServices/CoreServices.h>
Yue Wang's avatar
Yue Wang committed
34
#include <boost/lockfree/spsc_queue.hpp>
35

36 37
#include <memory>

38
struct OSXOutput {
39
	AudioOutput base;
40

41 42 43 44
	/* configuration settings */
	OSType component_subtype;
	/* only applicable with kAudioUnitSubType_HALOutput */
	const char *device_name;
45
	const char *channel_map;
46 47
	bool hog_device;
	bool sync_sample_rate;
48

49
	AudioDeviceID dev_id;
50
	AudioComponentInstance au;
51
	AudioStreamBasicDescription asbd;
52 53

	boost::lockfree::spsc_queue<uint8_t> *ring_buffer;
54

55
	OSXOutput(const ConfigBlock &block);
Max Kellermann's avatar
Max Kellermann committed
56
};
57

58
static constexpr Domain osx_output_domain("osx_output");
59

60 61 62 63 64 65 66 67 68 69 70 71 72 73
static void
osx_os_status_to_cstring(OSStatus status, char *str, size_t size) {
	CFErrorRef cferr = CFErrorCreate(nullptr, kCFErrorDomainOSStatus, status, nullptr);
	CFStringRef cfstr = CFErrorCopyDescription(cferr);
	if (!CFStringGetCString(cfstr, str, size, kCFStringEncodingUTF8)) {
		/* conversion failed, return empty string */
		*str = '\0';
	}
	if (cferr)
		CFRelease(cferr);
	if (cfstr)
		CFRelease(cfstr);
}

Max Kellermann's avatar
Max Kellermann committed
74 75
static bool
osx_output_test_default_device(void)
Avuton Olrich's avatar
Avuton Olrich committed
76
{
77 78
	/* on a Mac, this is always the default plugin, if nothing
	   else is configured */
79
	return true;
Warren Dukes's avatar
Warren Dukes committed
80 81
}

82 83
OSXOutput::OSXOutput(const ConfigBlock &block)
	:base(osx_output_plugin, block)
84
{
85
	const char *device = block.GetBlockValue("device");
86

87
	if (device == nullptr || 0 == strcmp(device, "default")) {
88 89
		component_subtype = kAudioUnitSubType_DefaultOutput;
		device_name = nullptr;
90 91
	}
	else if (0 == strcmp(device, "system")) {
92 93
		component_subtype = kAudioUnitSubType_SystemOutput;
		device_name = nullptr;
94 95
	}
	else {
96
		component_subtype = kAudioUnitSubType_HALOutput;
97
		/* XXX am I supposed to strdup() this? */
98
		device_name = device;
99
	}
100

101 102 103
	channel_map = block.GetBlockValue("channel_map");
	hog_device = block.GetBlockValue("hog_device", false);
	sync_sample_rate = block.GetBlockValue("sync_sample_rate", false);
104 105
}

106
static AudioOutput *
107
osx_output_init(const ConfigBlock &block)
Avuton Olrich's avatar
Avuton Olrich committed
108
{
109
	OSXOutput *oo = new OSXOutput(block);
Max Kellermann's avatar
Max Kellermann committed
110

111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
	AudioObjectPropertyAddress aopa = {
		kAudioHardwarePropertyDefaultOutputDevice,
		kAudioObjectPropertyScopeOutput,
		kAudioObjectPropertyElementMaster
	};

	AudioDeviceID dev_id = kAudioDeviceUnknown;
	UInt32 dev_id_size = sizeof(dev_id);
	AudioObjectGetPropertyData(kAudioObjectSystemObject,
				   &aopa,
				   0,
				   NULL,
				   &dev_id_size,
				   &dev_id);
	oo->dev_id = dev_id;

127
	return &oo->base;
128 129
}

130
static void
131
osx_output_finish(AudioOutput *ao)
Avuton Olrich's avatar
Avuton Olrich committed
132
{
133
	OSXOutput *oo = (OSXOutput *)ao;
Max Kellermann's avatar
Max Kellermann committed
134

135
	delete oo;
136 137
}

138
static void
139 140 141 142
osx_output_parse_channel_map(
	const char *device_name,
	const char *channel_map_str,
	SInt32 channel_map[],
143
	UInt32 num_channels)
144 145 146 147 148 149
{
	char *endptr;
	unsigned int inserted_channels = 0;
	bool want_number = true;

	while (*channel_map_str) {
150 151 152
		if (inserted_channels >= num_channels)
			throw FormatRuntimeError("%s: channel map contains more than %u entries or trailing garbage",
						 device_name, num_channels);
153 154 155 156 157 158 159 160 161 162 163

		if (!want_number && *channel_map_str == ',') {
			++channel_map_str;
			want_number = true;
			continue;
		}

		if (want_number &&
			(isdigit(*channel_map_str) || *channel_map_str == '-')
		) {
			channel_map[inserted_channels] = strtol(channel_map_str, &endptr, 10);
164 165
			if (channel_map[inserted_channels] < -1)
				throw FormatRuntimeError("%s: channel map value %d not allowed (must be -1 or greater)",
166
							 device_name, channel_map[inserted_channels]);
167

168 169 170 171 172 173 174 175 176
			channel_map_str = endptr;
			want_number = false;
			FormatDebug(osx_output_domain,
				"%s: channel_map[%u] = %d",
				device_name, inserted_channels, channel_map[inserted_channels]);
			++inserted_channels;
			continue;
		}

177 178
		throw FormatRuntimeError("%s: invalid character '%c' in channel map",
					 device_name, *channel_map_str);
179 180
	}

181 182 183
	if (inserted_channels < num_channels)
		throw FormatRuntimeError("%s: channel map contains less than %u entries",
					 device_name, num_channels);
184 185
}

186 187
static void
osx_output_set_channel_map(OSXOutput *oo)
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
{
	AudioStreamBasicDescription desc;
	OSStatus status;
	UInt32 size, num_channels;
	char errormsg[1024];

	size = sizeof(desc);
	memset(&desc, 0, size);
	status = AudioUnitGetProperty(oo->au,
		kAudioUnitProperty_StreamFormat,
		kAudioUnitScope_Output,
		0,
		&desc,
		&size);
	if (status != noErr) {
		osx_os_status_to_cstring(status, errormsg, sizeof(errormsg));
204 205
		throw FormatRuntimeError("%s: unable to get number of output device channels: %s",
					 oo->device_name, errormsg);
206 207 208
	}

	num_channels = desc.mChannelsPerFrame;
209
	std::unique_ptr<SInt32[]> channel_map(new SInt32[num_channels]);
210 211 212
	osx_output_parse_channel_map(oo->device_name,
				     oo->channel_map,
				     channel_map.get(),
213
				     num_channels);
214 215 216 217 218 219

	size = num_channels * sizeof(SInt32);
	status = AudioUnitSetProperty(oo->au,
		kAudioOutputUnitProperty_ChannelMap,
		kAudioUnitScope_Input,
		0,
220
		channel_map.get(),
221 222 223
		size);
	if (status != noErr) {
		osx_os_status_to_cstring(status, errormsg, sizeof(errormsg));
224
		throw FormatRuntimeError("%s: unable to set channel map: %s", oo->device_name, errormsg);
225 226 227
	}
}

228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294
static void
osx_output_sync_device_sample_rate(AudioDeviceID dev_id, AudioStreamBasicDescription desc)
{
	FormatDebug(osx_output_domain, "Syncing sample rate.");
	AudioObjectPropertyAddress aopa = {
		kAudioDevicePropertyAvailableNominalSampleRates,
		kAudioObjectPropertyScopeOutput,
		kAudioObjectPropertyElementMaster
	};

	UInt32 property_size;
	OSStatus err = AudioObjectGetPropertyDataSize(dev_id,
						      &aopa,
						      0,
						      NULL,
						      &property_size);

	int count = property_size/sizeof(AudioValueRange);
	AudioValueRange ranges[count];
	property_size = sizeof(ranges);
	err = AudioObjectGetPropertyData(dev_id,
					 &aopa,
					 0,
					 NULL,
					 &property_size,
					 &ranges);
	// Get the maximum sample rate as fallback.
	Float64 sample_rate = .0;
	for (int i = 0; i < count; i++) {
		if (ranges[i].mMaximum > sample_rate)
			sample_rate = ranges[i].mMaximum;
	}

	// Now try to see if the device support our format sample rate.
	// For some high quality media samples, the frame rate may exceed
	// device capability. In this case, we let CoreAudio downsample
	// by decimation with an integer factor ranging from 1 to 4.
	for (int f = 4; f > 0; f--) {
		Float64 rate = desc.mSampleRate / f;
		for (int i = 0; i < count; i++) {
			if (ranges[i].mMinimum <= rate
			   && rate <= ranges[i].mMaximum) {
				sample_rate = rate;
				break;
			}
		}
	}

	aopa.mSelector = kAudioDevicePropertyNominalSampleRate,

	err = AudioObjectSetPropertyData(dev_id,
					 &aopa,
					 0,
					 NULL,
					 sizeof(&desc.mSampleRate),
					 &sample_rate);
	if (err != noErr) {
                FormatWarning(osx_output_domain,
			      "Failed to synchronize the sample rate: %d",
			      err);
	} else {
		FormatDebug(osx_output_domain,
			    "Sample rate synced to %f Hz.",
			    sample_rate);
	}
}

295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344
static OSStatus
osx_output_set_buffer_size(AudioUnit au, AudioStreamBasicDescription desc, UInt32 *frame_size)
{
	AudioValueRange value_range = {0, 0};
	UInt32 property_size = sizeof(AudioValueRange);
	OSStatus err = AudioUnitGetProperty(au,
					    kAudioDevicePropertyBufferFrameSizeRange,
					    kAudioUnitScope_Global,
					    0,
					    &value_range,
					    &property_size);
	if (err != noErr)
		return err;

	UInt32 buffer_frame_size = value_range.mMaximum;
	err = AudioUnitSetProperty(au,
				   kAudioDevicePropertyBufferFrameSize,
				   kAudioUnitScope_Global,
				   0,
				   &buffer_frame_size,
				   sizeof(buffer_frame_size));
	if (err != noErr)
                FormatWarning(osx_output_domain,
			      "Failed to set maximum buffer size: %d",
			      err);

	property_size = sizeof(buffer_frame_size);
	err = AudioUnitGetProperty(au,
				   kAudioDevicePropertyBufferFrameSize,
				   kAudioUnitScope_Global,
				   0,
				   &buffer_frame_size,
				   &property_size);
	if (err != noErr) {
                FormatWarning(osx_output_domain,
			      "Cannot get the buffer frame size: %d",
			      err);
		return err;
	}

	buffer_frame_size *= desc.mBytesPerFrame;

	// We set the frame size to a power of two integer that
	// is larger than buffer_frame_size.
	while (*frame_size < buffer_frame_size + 1) {
		*frame_size <<= 1;
	}

	return noErr;
}
345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400

static void
osx_output_hog_device(AudioDeviceID dev_id, bool hog)
{
	pid_t hog_pid;
	AudioObjectPropertyAddress aopa = {
		kAudioDevicePropertyHogMode,
		kAudioObjectPropertyScopeOutput,
		kAudioObjectPropertyElementMaster
	};
	UInt32 size = sizeof(hog_pid);
	OSStatus err = AudioObjectGetPropertyData(dev_id,
						  &aopa,
						  0,
						  NULL,
						  &size,
						  &hog_pid);
	if (err != noErr) {
		FormatDebug(osx_output_domain,
			    "Cannot get hog information: %d",
			    err);
		return;
	}
	if (hog) {
		if (hog_pid != -1) {
		        FormatDebug(osx_output_domain,
				    "Device is already hogged.");
			return;
		}
	} else {
		if (hog_pid != getpid()) {
		        FormatDebug(osx_output_domain,
				    "Device is not owned by this process.");
			return;
		}
	}
	hog_pid = hog ? getpid() : -1;
	size = sizeof(hog_pid);
	err = AudioObjectSetPropertyData(dev_id,
					 &aopa,
					 0,
					 NULL,
					 size,
					 &hog_pid);
	if (err != noErr) {
		FormatDebug(osx_output_domain,
			    "Cannot hog the device: %d",
			    err);
	} else {
		FormatDebug(osx_output_domain,
			    hog_pid == -1 ? "Device is unhogged" 
					  : "Device is hogged");
	}
}


401 402
static void
osx_output_set_device(OSXOutput *oo)
403 404 405
{
	OSStatus status;
	UInt32 size, numdevices;
406
	AudioObjectPropertyAddress propaddr;
407
	CFStringRef cfname = nullptr;
408
	char errormsg[1024];
409 410 411
	char name[256];
	unsigned int i;

412 413 414 415 416
	AtScopeExit(&cfname) {
		if (cfname)
			CFRelease(cfname);
	};

417
	if (oo->component_subtype != kAudioUnitSubType_HALOutput)
418
		return;
419 420

	/* how many audio devices are there? */
421 422
	propaddr = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
	status = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propaddr, 0, nullptr, &size);
423
	if (status != noErr) {
424
		osx_os_status_to_cstring(status, errormsg, sizeof(errormsg));
425 426
		throw FormatRuntimeError("Unable to determine number of OS X audio devices: %s",
					 errormsg);
427 428 429 430
	}

	/* what are the available audio device IDs? */
	numdevices = size / sizeof(AudioDeviceID);
431 432
	std::unique_ptr<AudioDeviceID[]> deviceids(new AudioDeviceID[numdevices]);
	status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propaddr, 0, nullptr, &size, deviceids.get());
433
	if (status != noErr) {
434
		osx_os_status_to_cstring(status, errormsg, sizeof(errormsg));
435 436
		throw FormatRuntimeError("Unable to determine OS X audio device IDs: %s",
					 errormsg);
437 438 439
	}

	/* which audio device matches oo->device_name? */
440
	propaddr = { kAudioObjectPropertyName, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
441
	size = sizeof(CFStringRef);
442
	for (i = 0; i < numdevices; i++) {
443
		status = AudioObjectGetPropertyData(deviceids[i], &propaddr, 0, nullptr, &size, &cfname);
444
		if (status != noErr) {
445
			osx_os_status_to_cstring(status, errormsg, sizeof(errormsg));
446 447 448 449
			throw FormatRuntimeError("Unable to determine OS X device name "
						 "(device %u): %s",
						 (unsigned int) deviceids[i],
						 errormsg);
450
		}
451

452 453
		if (!CFStringGetCString(cfname, name, sizeof(name), kCFStringEncodingUTF8))
			throw std::runtime_error("Unable to convert device name from CFStringRef to char*");
454

455
		if (strcmp(oo->device_name, name) == 0) {
456 457 458
			FormatDebug(osx_output_domain,
				    "found matching device: ID=%u, name=%s",
				    (unsigned)deviceids[i], name);
459 460 461 462
			break;
		}
	}
	if (i == numdevices) {
463 464 465 466
		FormatWarning(osx_output_domain,
			      "Found no audio device with name '%s' "
			      "(will use default audio device)",
			      oo->device_name);
467
		return;
468 469 470 471 472 473 474 475 476
	}

	status = AudioUnitSetProperty(oo->au,
				      kAudioOutputUnitProperty_CurrentDevice,
				      kAudioUnitScope_Global,
				      0,
				      &(deviceids[i]),
				      sizeof(AudioDeviceID));
	if (status != noErr) {
477
		osx_os_status_to_cstring(status, errormsg, sizeof(errormsg));
478 479
		throw FormatRuntimeError("Unable to set OS X audio output device: %s",
					 errormsg);
480
	}
481

482
	oo->dev_id = deviceids[i];
483 484 485
	FormatDebug(osx_output_domain,
		    "set OS X audio output device ID=%u, name=%s",
		    (unsigned)deviceids[i], name);
486

487 488
	if (oo->channel_map)
		osx_output_set_channel_map(oo);
489 490
}

491 492 493 494 495 496 497 498

/*
	This function (the 'render callback' osx_render) is called by the
	OS X audio subsystem (CoreAudio) to request audio data that will be
	played by the audio hardware. This function has hard time constraints
	so it cannot do IO (debug statements) or memory allocations.
*/

499 500
static OSStatus
osx_render(void *vdata,
501 502 503
	   gcc_unused AudioUnitRenderActionFlags *io_action_flags,
	   gcc_unused const AudioTimeStamp *in_timestamp,
	   gcc_unused UInt32 in_bus_number,
504
	   UInt32 in_number_frames,
505 506
	   AudioBufferList *buffer_list)
{
507
	OSXOutput *od = (OSXOutput *) vdata;
508

509 510 511 512 513
	int count = in_number_frames * od->asbd.mBytesPerFrame;
	buffer_list->mBuffers[0].mDataByteSize =
		od->ring_buffer->pop((uint8_t *)buffer_list->mBuffers[0].mData,
				     count);
 	return noErr;
514 515
}

516 517
static void
osx_output_enable(AudioOutput *ao)
Avuton Olrich's avatar
Avuton Olrich committed
518
{
519
	char errormsg[1024];
520
	OSXOutput *oo = (OSXOutput *)ao;
521

522
	AudioComponentDescription desc;
523
	desc.componentType = kAudioUnitType_Output;
524
	desc.componentSubType = oo->component_subtype;
525 526 527 528
	desc.componentManufacturer = kAudioUnitManufacturer_Apple;
	desc.componentFlags = 0;
	desc.componentFlagsMask = 0;

529
	AudioComponent comp = AudioComponentFindNext(nullptr, &desc);
530 531
	if (comp == 0)
		throw std::runtime_error("Error finding OS X component");
Warren Dukes's avatar
Warren Dukes committed
532

533
	OSStatus status = AudioComponentInstanceNew(comp, &oo->au);
534
	if (status != noErr) {
535
		osx_os_status_to_cstring(status, errormsg, sizeof(errormsg));
536 537
		throw FormatRuntimeError("Unable to open OS X component: %s",
					 errormsg);
Warren Dukes's avatar
Warren Dukes committed
538 539
	}

540 541 542
	try {
		osx_output_set_device(oo);
	} catch (...) {
543
		AudioComponentInstanceDispose(oo->au);
544
		throw;
545 546
	}

547
	if (oo->hog_device) {
548
		osx_output_hog_device(oo->dev_id, true);
549
	}
550 551 552
}

static void
553
osx_output_disable(AudioOutput *ao)
554
{
555
	OSXOutput *oo = (OSXOutput *)ao;
556

557
	AudioComponentInstanceDispose(oo->au);
558

559
	if (oo->hog_device) {
560
		osx_output_hog_device(oo->dev_id, false);
561
	}
562 563 564
}

static void
565
osx_output_close(AudioOutput *ao)
566
{
567
	OSXOutput *od = (OSXOutput *)ao;
568 569 570

	AudioOutputUnitStop(od->au);
	AudioUnitUninitialize(od->au);
Max Kellermann's avatar
Max Kellermann committed
571

572
	delete od->ring_buffer;
573 574
}

575 576
static void
osx_output_open(AudioOutput *ao, AudioFormat &audio_format)
577
{
578
	char errormsg[1024];
579
	OSXOutput *od = (OSXOutput *)ao;
580

581 582 583 584
	memset(&od->asbd, 0, sizeof(od->asbd));
	od->asbd.mSampleRate = audio_format.sample_rate;
	od->asbd.mFormatID = kAudioFormatLinearPCM;
	od->asbd.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
585

586 587
	switch (audio_format.format) {
	case SampleFormat::S8:
588
		od->asbd.mBitsPerChannel = 8;
589 590
		break;

591
	case SampleFormat::S16:
592
		od->asbd.mBitsPerChannel = 16;
593 594
		break;

595
	case SampleFormat::S32:
596
		od->asbd.mBitsPerChannel = 32;
597 598
		break;

599
	default:
600
		audio_format.format = SampleFormat::S32;
601
		od->asbd.mBitsPerChannel = 32;
602 603
		break;
	}
604

605
	if (IsBigEndian())
606
		od->asbd.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
607

608 609 610 611
	od->asbd.mBytesPerPacket = audio_format.GetFrameSize();
	od->asbd.mFramesPerPacket = 1;
	od->asbd.mBytesPerFrame = od->asbd.mBytesPerPacket;
	od->asbd.mChannelsPerFrame = audio_format.channels;
612

613 614 615
	if (od->sync_sample_rate) {
		osx_output_sync_device_sample_rate(od->dev_id, od->asbd);
	}
616

617
	OSStatus status =
618 619
		AudioUnitSetProperty(od->au, kAudioUnitProperty_StreamFormat,
				     kAudioUnitScope_Input, 0,
620 621
				     &od->asbd,
				     sizeof(od->asbd));
622 623
	if (status != noErr)
		throw std::runtime_error("Unable to set format on OS X device");
624

625 626 627 628 629 630 631 632 633 634 635
	AURenderCallbackStruct callback;
	callback.inputProc = osx_render;
	callback.inputProcRefCon = od;

	status =
		AudioUnitSetProperty(od->au,
				     kAudioUnitProperty_SetRenderCallback,
				     kAudioUnitScope_Input, 0,
				     &callback, sizeof(callback));
	if (status != noErr) {
		AudioComponentInstanceDispose(od->au);
636
		throw std::runtime_error("unable to set callback for OS X audio unit");
637 638
	}

639
	status = AudioUnitInitialize(od->au);
640
	if (status != noErr) {
641
		osx_os_status_to_cstring(status, errormsg, sizeof(errormsg));
642 643
		throw FormatRuntimeError("Unable to initialize OS X audio unit: %s",
					 errormsg);
644 645
	}

646
	UInt32 buffer_frame_size = 1;
647 648 649
	status = osx_output_set_buffer_size(od->au, od->asbd, &buffer_frame_size);
	if (status != noErr) {
		osx_os_status_to_cstring(status, errormsg, sizeof(errormsg));
650 651
		throw FormatRuntimeError("Unable to set frame size: %s",
					 errormsg);
652
	}
653

654
	od->ring_buffer = new boost::lockfree::spsc_queue<uint8_t>(buffer_frame_size);
655

656 657
	status = AudioOutputUnitStart(od->au);
	if (status != 0) {
658
		AudioUnitUninitialize(od->au);
659
		osx_os_status_to_cstring(status, errormsg, sizeof(errormsg));
660 661
		throw FormatRuntimeError("unable to start audio output: %s",
					 errormsg);
662
	}
663 664
}

665
static size_t
666
osx_output_play(AudioOutput *ao, const void *chunk, size_t size)
Avuton Olrich's avatar
Avuton Olrich committed
667
{
668
	OSXOutput *od = (OSXOutput *)ao;
669
	return od->ring_buffer->push((uint8_t *)chunk, size);
670 671
}

672
static std::chrono::steady_clock::duration
673 674 675
osx_output_delay(AudioOutput *ao)
{
	OSXOutput *od = (OSXOutput *)ao;
676 677 678
	return od->ring_buffer->write_available()
		? std::chrono::steady_clock::duration::zero()
		: std::chrono::milliseconds(25);
679 680
}

681
const struct AudioOutputPlugin osx_output_plugin = {
682 683 684 685 686 687 688 689
	"osx",
	osx_output_test_default_device,
	osx_output_init,
	osx_output_finish,
	osx_output_enable,
	osx_output_disable,
	osx_output_open,
	osx_output_close,
690
	osx_output_delay,
691 692 693
	nullptr,
	osx_output_play,
	nullptr,
694
	nullptr,
695 696
	nullptr,
	nullptr,
697
};