OSXOutputPlugin.cxx 26.4 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2019 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"
Matthew Leon's avatar
Matthew Leon committed
23
#include "mixer/MixerList.hxx"
24
#include "util/ScopeExit.hxx"
25
#include "util/RuntimeError.hxx"
26
#include "util/Domain.hxx"
27 28
#include "util/Manual.hxx"
#include "util/ConstBuffer.hxx"
29
#include "pcm/Export.hxx"
30 31
#include "thread/Mutex.hxx"
#include "thread/Cond.hxx"
32
#include "util/ByteOrder.hxx"
33 34
#include "util/StringBuffer.hxx"
#include "util/StringFormat.hxx"
35
#include "Log.hxx"
36

37
#include <CoreAudio/CoreAudio.h>
38
#include <AudioUnit/AudioUnit.h>
Yue Wang's avatar
Yue Wang committed
39
#include <AudioToolbox/AudioToolbox.h>
40
#include <CoreServices/CoreServices.h>
Yue Wang's avatar
Yue Wang committed
41
#include <boost/lockfree/spsc_queue.hpp>
42

43 44
#include <memory>

45 46
static constexpr unsigned MPD_OSX_BUFFER_TIME_MS = 100;

47
static StringBuffer<64>
48 49
StreamDescriptionToString(const AudioStreamBasicDescription desc)
{
50 51
	// Only convert the lpcm formats (nothing else supported / used by MPD)
	assert(desc.mFormatID == kAudioFormatLinearPCM);
52

53
	return StringFormat<64>("%u channel %s %sinterleaved %u-bit %s %s (%uHz)",
54 55 56 57 58 59 60
				desc.mChannelsPerFrame,
				(desc.mFormatFlags & kAudioFormatFlagIsNonMixable) ? "" : "mixable",
				(desc.mFormatFlags & kAudioFormatFlagIsNonInterleaved) ? "non-" : "",
				desc.mBitsPerChannel,
				(desc.mFormatFlags & kAudioFormatFlagIsFloat) ? "Float" : "SInt",
				(desc.mFormatFlags & kAudioFormatFlagIsBigEndian) ? "BE" : "LE",
				(UInt32)desc.mSampleRate);
61 62 63
}


64
struct OSXOutput final : AudioOutput {
65 66 67 68
	/* configuration settings */
	OSType component_subtype;
	/* only applicable with kAudioUnitSubType_HALOutput */
	const char *device_name;
69
	const char *channel_map;
70
	bool hog_device;
71
	bool pause;
72 73 74 75 76 77 78
#ifdef ENABLE_DSD
	/**
	 * Enable DSD over PCM according to the DoP standard?
	 *
	 * @see http://dsd-guide.com/dop-open-standard
	 */
	bool dop_setting;
79
	bool dop_enabled;
80
	Manual<PcmExport> pcm_export;
81
#endif
82

83
	AudioDeviceID dev_id;
84
	AudioComponentInstance au;
85
	AudioStreamBasicDescription asbd;
86 87

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

89
	OSXOutput(const ConfigBlock &block);
90 91

	static AudioOutput *Create(EventLoop &, const ConfigBlock &block);
Matthew Leon's avatar
Matthew Leon committed
92 93
	int GetVolume();
	void SetVolume(unsigned new_volume);
94 95 96 97 98 99 100 101 102 103

private:
	void Enable() override;
	void Disable() noexcept override;

	void Open(AudioFormat &audio_format) override;
	void Close() noexcept override;

	std::chrono::steady_clock::duration Delay() const noexcept override;
	size_t Play(const void *chunk, size_t size) override;
104
	bool Pause() override;
105
	void Cancel() noexcept override;
Max Kellermann's avatar
Max Kellermann committed
106
};
107

108
static constexpr Domain osx_output_domain("osx_output");
109

110
static void
111 112
osx_os_status_to_cstring(OSStatus status, char *str, size_t size)
{
113 114 115 116 117 118 119 120 121 122 123 124
	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
125
static bool
126
osx_output_test_default_device()
Avuton Olrich's avatar
Avuton Olrich committed
127
{
128 129
	/* on a Mac, this is always the default plugin, if nothing
	   else is configured */
130
	return true;
Warren Dukes's avatar
Warren Dukes committed
131 132
}

133
OSXOutput::OSXOutput(const ConfigBlock &block)
134
	:AudioOutput(FLAG_ENABLE_DISABLE|FLAG_PAUSE)
135
{
136
	const char *device = block.GetBlockValue("device");
137

138
	if (device == nullptr || 0 == strcmp(device, "default")) {
139 140
		component_subtype = kAudioUnitSubType_DefaultOutput;
		device_name = nullptr;
141 142
	}
	else if (0 == strcmp(device, "system")) {
143 144
		component_subtype = kAudioUnitSubType_SystemOutput;
		device_name = nullptr;
145 146
	}
	else {
147
		component_subtype = kAudioUnitSubType_HALOutput;
148
		/* XXX am I supposed to strdup() this? */
149
		device_name = device;
150
	}
151

152 153
	channel_map = block.GetBlockValue("channel_map");
	hog_device = block.GetBlockValue("hog_device", false);
154 155 156
#ifdef ENABLE_DSD
	dop_setting = block.GetBlockValue("dop", false);
#endif
157 158
}

159 160
AudioOutput *
OSXOutput::Create(EventLoop &, const ConfigBlock &block)
Avuton Olrich's avatar
Avuton Olrich committed
161
{
162
	OSXOutput *oo = new OSXOutput(block);
163
	AudioObjectPropertyAddress aopa;
164 165
	AudioDeviceID dev_id = kAudioDeviceUnknown;
	UInt32 dev_id_size = sizeof(dev_id);
166

167 168 169 170 171 172 173 174
	if (oo->component_subtype == kAudioUnitSubType_SystemOutput)
		// get system output dev_id if configured
		aopa = {
			kAudioHardwarePropertyDefaultSystemOutputDevice,
			kAudioObjectPropertyScopeOutput,
			kAudioObjectPropertyElementMaster
		};
	else
175 176
		/* fallback to default device initially (can still be
		   changed by osx_output_set_device) */
177 178 179 180 181 182
		aopa = {
			kAudioHardwarePropertyDefaultOutputDevice,
			kAudioObjectPropertyScopeOutput,
			kAudioObjectPropertyElementMaster
		};

183 184 185 186 187 188 189 190
	AudioObjectGetPropertyData(kAudioObjectSystemObject,
				   &aopa,
				   0,
				   NULL,
				   &dev_id_size,
				   &dev_id);
	oo->dev_id = dev_id;

191
	return oo;
192 193
}

194

Matthew Leon's avatar
Matthew Leon committed
195 196 197
int
OSXOutput::GetVolume()
{
198 199
	Float32 vol;
	AudioObjectPropertyAddress aopa = {
Yue Wang's avatar
Yue Wang committed
200
		.mSelector	= kAudioHardwareServiceDeviceProperty_VirtualMasterVolume,
201 202 203 204 205 206 207 208 209 210
		.mScope		= kAudioObjectPropertyScopeOutput,
		.mElement	= kAudioObjectPropertyElementMaster,
	};
	UInt32 size = sizeof(vol);
	OSStatus status = AudioObjectGetPropertyData(dev_id,
						     &aopa,
						     0,
						     NULL,
						     &size,
						     &vol);
Matthew Leon's avatar
Matthew Leon committed
211 212

	if (status != noErr) {
213
		char errormsg[1024];
Matthew Leon's avatar
Matthew Leon committed
214 215 216 217
		osx_os_status_to_cstring(status, errormsg, sizeof(errormsg));
		throw FormatRuntimeError("unable to get volume: %s", errormsg);
	}

218
	return static_cast<int>(vol * 100.0);
Matthew Leon's avatar
Matthew Leon committed
219 220 221
}

void
222 223
OSXOutput::SetVolume(unsigned new_volume)
{
224 225
	Float32 vol = new_volume / 100.0;
	AudioObjectPropertyAddress aopa = {
Yue Wang's avatar
Yue Wang committed
226
		.mSelector	= kAudioHardwareServiceDeviceProperty_VirtualMasterVolume,
227 228 229 230 231 232 233 234 235 236
		.mScope		= kAudioObjectPropertyScopeOutput,
		.mElement	= kAudioObjectPropertyElementMaster
	};
	UInt32 size = sizeof(vol);
	OSStatus status = AudioObjectSetPropertyData(dev_id,
						     &aopa,
						     0,
						     NULL,
						     size,
						     &vol);
Matthew Leon's avatar
Matthew Leon committed
237 238

	if (status != noErr) {
239
		char errormsg[1024];
Matthew Leon's avatar
Matthew Leon committed
240 241 242 243 244 245
		osx_os_status_to_cstring(status, errormsg, sizeof(errormsg));
		throw FormatRuntimeError( "unable to set new volume %u: %s",
				new_volume, errormsg);
	}
}

246
static void
247 248 249 250
osx_output_parse_channel_map(const char *device_name,
			     const char *channel_map_str,
			     SInt32 channel_map[],
			     UInt32 num_channels)
251 252 253 254 255 256
{
	char *endptr;
	unsigned int inserted_channels = 0;
	bool want_number = true;

	while (*channel_map_str) {
257 258 259
		if (inserted_channels >= num_channels)
			throw FormatRuntimeError("%s: channel map contains more than %u entries or trailing garbage",
						 device_name, num_channels);
260 261 262 263 264 265 266 267 268 269 270

		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);
271 272
			if (channel_map[inserted_channels] < -1)
				throw FormatRuntimeError("%s: channel map value %d not allowed (must be -1 or greater)",
273
							 device_name, channel_map[inserted_channels]);
274

275 276 277 278 279 280 281 282 283
			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;
		}

284 285
		throw FormatRuntimeError("%s: invalid character '%c' in channel map",
					 device_name, *channel_map_str);
286 287
	}

288 289 290
	if (inserted_channels < num_channels)
		throw FormatRuntimeError("%s: channel map contains less than %u entries",
					 device_name, num_channels);
291 292
}

293 294
static void
osx_output_set_channel_map(OSXOutput *oo)
295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310
{
	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));
311 312
		throw FormatRuntimeError("%s: unable to get number of output device channels: %s",
					 oo->device_name, errormsg);
313 314 315
	}

	num_channels = desc.mChannelsPerFrame;
316
	std::unique_ptr<SInt32[]> channel_map(new SInt32[num_channels]);
317 318 319
	osx_output_parse_channel_map(oo->device_name,
				     oo->channel_map,
				     channel_map.get(),
320
				     num_channels);
321 322 323 324 325 326

	size = num_channels * sizeof(SInt32);
	status = AudioUnitSetProperty(oo->au,
		kAudioOutputUnitProperty_ChannelMap,
		kAudioUnitScope_Input,
		0,
327
		channel_map.get(),
328 329 330
		size);
	if (status != noErr) {
		osx_os_status_to_cstring(status, errormsg, sizeof(errormsg));
331
		throw FormatRuntimeError("%s: unable to set channel map: %s", oo->device_name, errormsg);
332 333 334
	}
}

335 336

static float
337 338
osx_output_score_sample_rate(Float64 destination_rate, unsigned source_rate)
{
339 340 341 342 343 344 345
	float score = 0;
	double int_portion;
	double frac_portion = modf(source_rate / destination_rate, &int_portion);
	// prefer sample rates that are multiples of the source sample rate
	score += (1 - frac_portion) * 1000;
	// prefer exact matches over other multiples
	score += (int_portion == 1.0) ? 500 : 0;
346 347
	if (source_rate == destination_rate)
		score += 1000;
348
	else if (source_rate > destination_rate)
349 350 351
		score += (int_portion > 1 && int_portion < 100) ? (100 - int_portion) / 100 * 100 : 0;
	else
		score += (int_portion > 1 && int_portion < 100) ? (100 + int_portion) / 100 * 100 : 0;
352

353 354 355 356
	return score;
}

static float
357 358 359
osx_output_score_format(const AudioStreamBasicDescription &format_desc,
			const AudioStreamBasicDescription &target_format)
{
360 361 362
	float score = 0;
	// Score only linear PCM formats (everything else MPD cannot use)
	if (format_desc.mFormatID == kAudioFormatLinearPCM) {
363 364 365
		score += osx_output_score_sample_rate(format_desc.mSampleRate,
						      target_format.mSampleRate);

366 367
		// Just choose the stream / format with the highest number of output channels
		score += format_desc.mChannelsPerFrame * 5;
368

369
		if (target_format.mFormatFlags == kLinearPCMFormatFlagIsFloat) {
370 371 372 373
			// for float, prefer the highest bitdepth we have
			if (format_desc.mBitsPerChannel >= 16)
				score += (format_desc.mBitsPerChannel / 8);
		} else {
374
			if (format_desc.mBitsPerChannel == target_format.mBitsPerChannel)
375
				score += 5;
376
			else if (format_desc.mBitsPerChannel > target_format.mBitsPerChannel)
377
				score += 1;
378

379 380
		}
	}
381

382 383 384
	return score;
}

385
static Float64
386 387
osx_output_set_device_format(AudioDeviceID dev_id,
			     const AudioStreamBasicDescription &target_format)
388 389
{
	AudioObjectPropertyAddress aopa = {
390
		kAudioDevicePropertyStreams,
391 392 393 394 395
		kAudioObjectPropertyScopeOutput,
		kAudioObjectPropertyElementMaster
	};

	UInt32 property_size;
396 397 398
	OSStatus err = AudioObjectGetPropertyDataSize(dev_id, &aopa, 0, NULL,
						      &property_size);
	if (err != noErr)
399
		throw FormatRuntimeError("Cannot get number of streams: %d", err);
400

401
	const size_t n_streams = property_size / sizeof(AudioStreamID);
402 403 404 405 406
	static constexpr size_t MAX_STREAMS = 64;
	if (n_streams > MAX_STREAMS)
		throw std::runtime_error("Too many streams");

	AudioStreamID streams[MAX_STREAMS];
407 408 409
	err = AudioObjectGetPropertyData(dev_id, &aopa, 0, NULL,
					 &property_size, streams);
	if (err != noErr)
410
		throw FormatRuntimeError("Cannot get streams: %d", err);
411

412 413 414 415
	bool format_found = false;
	int output_stream;
	AudioStreamBasicDescription output_format;

416
	for (size_t i = 0; i < n_streams; i++) {
417
		UInt32 direction;
418
		AudioStreamID stream = streams[i];
419 420 421 422 423 424 425 426 427
		aopa.mSelector = kAudioStreamPropertyDirection;
		property_size = sizeof(direction);
		err = AudioObjectGetPropertyData(stream,
						 &aopa,
						 0,
						 NULL,
						 &property_size,
						 &direction);
		if (err != noErr)
428
			throw FormatRuntimeError("Cannot get streams direction: %d",
429 430 431
						 err);

		if (direction != 0)
432 433 434
			continue;

		aopa.mSelector = kAudioStreamPropertyAvailablePhysicalFormats;
435 436
		err = AudioObjectGetPropertyDataSize(stream, &aopa, 0, NULL,
						     &property_size);
437
		if (err != noErr)
438 439
			throw FormatRuntimeError("Unable to get format size s for stream %d. Error = %s",
						 streams[i], err);
440

441
		const size_t format_count = property_size / sizeof(AudioStreamRangedDescription);
442 443 444 445 446
		static constexpr size_t MAX_FORMATS = 256;
		if (format_count > MAX_FORMATS)
			throw std::runtime_error("Too many formats");

		AudioStreamRangedDescription format_list[MAX_FORMATS];
447 448
		err = AudioObjectGetPropertyData(stream, &aopa, 0, NULL,
						 &property_size, format_list);
449
		if (err != noErr)
450 451
			throw FormatRuntimeError("Unable to get available formats for stream %d. Error = %s",
						 streams[i], err);
452 453

		float output_score = 0;
454
		for (size_t j = 0; j < format_count; j++) {
455 456
			AudioStreamBasicDescription format_desc = format_list[j].mFormat;
			std::string format_string;
457

458 459 460
			// for devices with kAudioStreamAnyRate
			// we use the requested samplerate here
			if (format_desc.mSampleRate == kAudioStreamAnyRate)
461 462
				format_desc.mSampleRate = target_format.mSampleRate;
			float score = osx_output_score_format(format_desc, target_format);
463

464
			// print all (linear pcm) formats and their rating
465 466 467 468 469
			if (score > 0.0)
				FormatDebug(osx_output_domain,
					    "Format: %s rated %f",
					    StreamDescriptionToString(format_desc).c_str(), score);

470 471 472 473 474
			if (score > output_score) {
				output_score  = score;
				output_format = format_desc;
				output_stream = stream; // set the idx of the stream in the device
				format_found = true;
475 476 477 478
			}
		}
	}

479
	if (format_found) {
480
		aopa.mSelector = kAudioStreamPropertyPhysicalFormat;
481
		err = AudioObjectSetPropertyData(output_stream,
482 483 484 485 486 487
						 &aopa,
						 0,
						 NULL,
						 sizeof(output_format),
						 &output_format);
		if (err != noErr)
488
			throw FormatRuntimeError("Failed to change the stream format: %d",
489 490
						 err);
	}
491 492

	return output_format.mSampleRate;
493 494
}

495
static OSStatus
496 497
osx_output_set_buffer_size(AudioUnit au, AudioStreamBasicDescription desc,
			   UInt32 *frame_size)
498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517
{
	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)
518
		FormatWarning(osx_output_domain,
519 520 521 522 523 524 525 526 527 528 529
			      "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) {
530
		FormatWarning(osx_output_domain,
531 532 533 534 535 536 537 538 539 540 541 542 543 544 545
			      "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;
}
546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568

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;
	}
569

570 571
	if (hog) {
		if (hog_pid != -1) {
572
			FormatDebug(osx_output_domain,
573 574 575 576 577
				    "Device is already hogged.");
			return;
		}
	} else {
		if (hog_pid != getpid()) {
578
			FormatDebug(osx_output_domain,
579 580 581 582
				    "Device is not owned by this process.");
			return;
		}
	}
583

584 585 586 587 588 589 590 591 592 593 594 595 596
	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 {
597 598 599 600
		LogDebug(osx_output_domain,
			 hog_pid == -1
			 ? "Device is unhogged"
			 : "Device is hogged");
601 602 603 604
	}
}


605 606
static void
osx_output_set_device(OSXOutput *oo)
607 608 609
{
	OSStatus status;
	UInt32 size, numdevices;
610
	AudioObjectPropertyAddress propaddr;
611
	CFStringRef cfname = nullptr;
612
	char errormsg[1024];
613 614 615
	char name[256];
	unsigned int i;

616 617 618 619 620
	AtScopeExit(&cfname) {
		if (cfname)
			CFRelease(cfname);
	};

621
	if (oo->component_subtype != kAudioUnitSubType_HALOutput)
622
		return;
623 624

	/* how many audio devices are there? */
625 626 627 628 629
	propaddr = { kAudioHardwarePropertyDevices,
		     kAudioObjectPropertyScopeGlobal,
		     kAudioObjectPropertyElementMaster };
	status = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject,
						&propaddr, 0, nullptr, &size);
630
	if (status != noErr) {
631
		osx_os_status_to_cstring(status, errormsg, sizeof(errormsg));
632 633
		throw FormatRuntimeError("Unable to determine number of OS X audio devices: %s",
					 errormsg);
634 635 636 637
	}

	/* what are the available audio device IDs? */
	numdevices = size / sizeof(AudioDeviceID);
638
	std::unique_ptr<AudioDeviceID[]> deviceids(new AudioDeviceID[numdevices]);
639 640 641
	status = AudioObjectGetPropertyData(kAudioObjectSystemObject,
					    &propaddr, 0, nullptr,
					    &size, deviceids.get());
642
	if (status != noErr) {
643
		osx_os_status_to_cstring(status, errormsg, sizeof(errormsg));
644 645
		throw FormatRuntimeError("Unable to determine OS X audio device IDs: %s",
					 errormsg);
646 647 648
	}

	/* which audio device matches oo->device_name? */
649 650 651
	propaddr = { kAudioObjectPropertyName,
		     kAudioObjectPropertyScopeGlobal,
		     kAudioObjectPropertyElementMaster };
652
	size = sizeof(CFStringRef);
653
	for (i = 0; i < numdevices; i++) {
654 655 656
		status = AudioObjectGetPropertyData(deviceids[i], &propaddr,
						    0, nullptr,
						    &size, &cfname);
657
		if (status != noErr) {
658
			osx_os_status_to_cstring(status, errormsg, sizeof(errormsg));
659 660 661 662
			throw FormatRuntimeError("Unable to determine OS X device name "
						 "(device %u): %s",
						 (unsigned int) deviceids[i],
						 errormsg);
663
		}
664

665 666
		if (!CFStringGetCString(cfname, name, sizeof(name),
					kCFStringEncodingUTF8))
667
			throw std::runtime_error("Unable to convert device name from CFStringRef to char*");
668

669
		if (strcmp(oo->device_name, name) == 0) {
670 671 672
			FormatDebug(osx_output_domain,
				    "found matching device: ID=%u, name=%s",
				    (unsigned)deviceids[i], name);
673 674 675
			break;
		}
	}
676 677 678

	if (i == numdevices)
		throw FormatRuntimeError("Found no audio device with name '%s' ",
679
			      oo->device_name);
680 681 682 683 684 685 686 687

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

693
	oo->dev_id = deviceids[i];
694 695 696
	FormatDebug(osx_output_domain,
		    "set OS X audio output device ID=%u, name=%s",
		    (unsigned)deviceids[i], name);
697

698 699
	if (oo->channel_map)
		osx_output_set_channel_map(oo);
700 701
}

702

703 704 705 706 707 708 709
/**
 * 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.
 */
710 711
static OSStatus
osx_render(void *vdata,
712 713 714
	   gcc_unused AudioUnitRenderActionFlags *io_action_flags,
	   gcc_unused const AudioTimeStamp *in_timestamp,
	   gcc_unused UInt32 in_bus_number,
715
	   UInt32 in_number_frames,
716 717
	   AudioBufferList *buffer_list)
{
718
	OSXOutput *od = (OSXOutput *) vdata;
719

720 721 722 723
	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);
724
	return noErr;
725 726
}

727 728
void
OSXOutput::Enable()
Avuton Olrich's avatar
Avuton Olrich committed
729
{
730
	char errormsg[1024];
731

732
	AudioComponentDescription desc;
733
	desc.componentType = kAudioUnitType_Output;
734
	desc.componentSubType = component_subtype;
735 736 737 738
	desc.componentManufacturer = kAudioUnitManufacturer_Apple;
	desc.componentFlags = 0;
	desc.componentFlagsMask = 0;

739
	AudioComponent comp = AudioComponentFindNext(nullptr, &desc);
740 741
	if (comp == 0)
		throw std::runtime_error("Error finding OS X component");
Warren Dukes's avatar
Warren Dukes committed
742

743
	OSStatus status = AudioComponentInstanceNew(comp, &au);
744
	if (status != noErr) {
745
		osx_os_status_to_cstring(status, errormsg, sizeof(errormsg));
746 747
		throw FormatRuntimeError("Unable to open OS X component: %s",
					 errormsg);
Warren Dukes's avatar
Warren Dukes committed
748
	}
749
#ifdef ENABLE_DSD
750
	pcm_export.Construct();
751
#endif
Warren Dukes's avatar
Warren Dukes committed
752

753
	try {
754
		osx_output_set_device(this);
755
	} catch (...) {
756
		AudioComponentInstanceDispose(au);
757
#ifdef ENABLE_DSD
758
		pcm_export.Destruct();
759
#endif
760
		throw;
761
	}
762

763 764
	if (hog_device)
		osx_output_hog_device(dev_id, true);
765 766
}

767 768
void
OSXOutput::Disable() noexcept
769
{
770
	AudioComponentInstanceDispose(au);
771
#ifdef ENABLE_DSD
772
	pcm_export.Destruct();
773
#endif
774

775 776
	if (hog_device)
		osx_output_hog_device(dev_id, false);
777 778
}

779 780
void
OSXOutput::Close() noexcept
781
{
782 783 784
	AudioOutputUnitStop(au);
	AudioUnitUninitialize(au);
	delete ring_buffer;
785 786
}

787 788
void
OSXOutput::Open(AudioFormat &audio_format)
789
{
790
	char errormsg[1024];
791
#ifdef ENABLE_DSD
792 793
	PcmExport::Params params;
	params.alsa_channel_order = true;
794 795
	bool dop = dop_setting;
#endif
796

797 798
	memset(&asbd, 0, sizeof(asbd));
	asbd.mFormatID = kAudioFormatLinearPCM;
799 800 801 802
	if (audio_format.format == SampleFormat::FLOAT) {
		asbd.mFormatFlags = kLinearPCMFormatFlagIsFloat;
	} else {
		asbd.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
803
	}
804

805
	if (IsBigEndian())
806
		asbd.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
807

808 809 810 811 812
	if (audio_format.format == SampleFormat::S24_P32) {
		asbd.mBitsPerChannel = 24;
	} else {
		asbd.mBitsPerChannel = audio_format.GetSampleSize() * 8;
	}
813
	asbd.mBytesPerPacket = audio_format.GetFrameSize();
814
	asbd.mSampleRate = audio_format.sample_rate;
815

816
#ifdef ENABLE_DSD
817 818
	if (dop && audio_format.format == SampleFormat::DSD) {
		asbd.mBitsPerChannel = 24;
819
		params.dsd_mode = PcmExport::DsdMode::DOP;
820
		asbd.mSampleRate = params.CalcOutputSampleRate(audio_format.sample_rate);
821
		asbd.mBytesPerPacket = 4 * audio_format.channels;
822

823
	}
824
#endif
825

826 827 828
	asbd.mFramesPerPacket = 1;
	asbd.mBytesPerFrame = asbd.mBytesPerPacket;
	asbd.mChannelsPerFrame = audio_format.channels;
829

830
	Float64 sample_rate = osx_output_set_device_format(dev_id, asbd);
831 832

#ifdef ENABLE_DSD
833 834 835
	if (audio_format.format == SampleFormat::DSD &&
	    sample_rate != asbd.mSampleRate) {
		// fall back to PCM in case sample_rate cannot be synchronized
836
		params.dsd_mode = PcmExport::DsdMode::NONE;
837 838 839 840 841 842
		audio_format.format = SampleFormat::S32;
		asbd.mBitsPerChannel = 32;
		asbd.mBytesPerPacket = audio_format.GetFrameSize();
		asbd.mSampleRate = params.CalcOutputSampleRate(audio_format.sample_rate);
		asbd.mBytesPerFrame = asbd.mBytesPerPacket;
	}
843
	dop_enabled = params.dsd_mode == PcmExport::DsdMode::DOP;
844
#endif
845

846
	OSStatus status =
847
		AudioUnitSetProperty(au, kAudioUnitProperty_StreamFormat,
848
				     kAudioUnitScope_Input, 0,
849 850
				     &asbd,
				     sizeof(asbd));
851 852
	if (status != noErr)
		throw std::runtime_error("Unable to set format on OS X device");
853

854 855
	AURenderCallbackStruct callback;
	callback.inputProc = osx_render;
856
	callback.inputProcRefCon = this;
857 858

	status =
859
		AudioUnitSetProperty(au,
860 861 862 863
				     kAudioUnitProperty_SetRenderCallback,
				     kAudioUnitScope_Input, 0,
				     &callback, sizeof(callback));
	if (status != noErr) {
864
		AudioComponentInstanceDispose(au);
865
		throw std::runtime_error("Unable to set callback for OS X audio unit");
866 867
	}

868
	status = AudioUnitInitialize(au);
869
	if (status != noErr) {
870
		osx_os_status_to_cstring(status, errormsg, sizeof(errormsg));
871 872
		throw FormatRuntimeError("Unable to initialize OS X audio unit: %s",
					 errormsg);
873 874
	}

875
	UInt32 buffer_frame_size = 1;
876
	status = osx_output_set_buffer_size(au, asbd, &buffer_frame_size);
877 878
	if (status != noErr) {
		osx_os_status_to_cstring(status, errormsg, sizeof(errormsg));
879 880
		throw FormatRuntimeError("Unable to set frame size: %s",
					 errormsg);
881
	}
882

883
	size_t ring_buffer_size = std::max<size_t>(buffer_frame_size,
884
						   MPD_OSX_BUFFER_TIME_MS * audio_format.GetFrameSize() * audio_format.sample_rate / 1000);
885 886

#ifdef ENABLE_DSD
887
	if (dop_enabled) {
888 889
		pcm_export->Open(audio_format.format, audio_format.channels, params);
		ring_buffer_size = std::max<size_t>(buffer_frame_size,
890
						   MPD_OSX_BUFFER_TIME_MS * pcm_export->GetOutputFrameSize() * asbd.mSampleRate / 1000);
891
	}
892
#endif
893
	ring_buffer = new boost::lockfree::spsc_queue<uint8_t>(ring_buffer_size);
894

895
	status = AudioOutputUnitStart(au);
896
	if (status != 0) {
897
		AudioUnitUninitialize(au);
898
		osx_os_status_to_cstring(status, errormsg, sizeof(errormsg));
899
		throw FormatRuntimeError("Unable to start audio output: %s",
900
					 errormsg);
901
	}
902
	pause = false;
903 904
}

905 906
size_t
OSXOutput::Play(const void *chunk, size_t size)
Avuton Olrich's avatar
Avuton Olrich committed
907
{
908
	assert(size > 0);
909
	if (pause) {
910 911 912 913 914 915 916
		pause = false;
		OSStatus status = AudioOutputUnitStart(au);
		if (status != 0) {
			AudioUnitUninitialize(au);
			throw std::runtime_error("Unable to restart audio output after pause");
		}
	}
917
#ifdef ENABLE_DSD
918
	if (dop_enabled) {
919
		const auto e = pcm_export->Export({chunk, size});
920
		if (e.empty())
921
			return size;
922

923
		size_t bytes_written = ring_buffer->push((const uint8_t *)e.data, e.size);
924
		return pcm_export->CalcInputSize(bytes_written);
925
	}
926 927
#endif
	return ring_buffer->push((const uint8_t *)chunk, size);
928 929
}

930 931
std::chrono::steady_clock::duration
OSXOutput::Delay() const noexcept
932
{
Yue Wang's avatar
Yue Wang committed
933
	return ring_buffer->write_available() && !pause
934
		? std::chrono::steady_clock::duration::zero()
935
		: std::chrono::milliseconds(MPD_OSX_BUFFER_TIME_MS / 4);
936
}
937 938 939 940

bool OSXOutput::Pause()
{
	if (!pause) {
941 942 943 944 945
		pause = true;
		AudioOutputUnitStop(au);
	}
	return true;
}
946 947 948 949 950 951 952 953 954 955 956 957

void
OSXOutput::Cancel() noexcept
{
	AudioOutputUnitStop(au);
	ring_buffer->reset();
#ifdef ENABLE_DSD
	pcm_export->Reset();
#endif
	AudioOutputUnitStart(au);
}

Matthew Leon's avatar
Matthew Leon committed
958 959 960 961 962 963 964 965 966 967 968 969
int
osx_output_get_volume(OSXOutput &output)
{
	return output.GetVolume();
}

void
osx_output_set_volume(OSXOutput &output, unsigned new_volume)
{
	return output.SetVolume(new_volume);
}

970
const struct AudioOutputPlugin osx_output_plugin = {
971 972
	"osx",
	osx_output_test_default_device,
973
	&OSXOutput::Create,
974
	&osx_mixer_plugin,
975
};