OSXOutputPlugin.cxx 26.3 KB
Newer Older
1
/*
2
 * Copyright 2003-2018 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 29
#include "util/Manual.hxx"
#include "util/ConstBuffer.hxx"
#include "pcm/PcmExport.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 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
static StringBuffer<64>
StreamDescriptionToString(const AudioStreamBasicDescription desc) {
	// Only convert the lpcm formats (nothing else supported / used by MPD)
	assert(desc.mFormatID == kAudioFormatLinearPCM);
	
	return StringFormat<64>("%u channel %s %sinterleaved %u-bit %s %s (%uHz)",
							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);
}


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

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

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

88
	OSXOutput(const ConfigBlock &block);
89 90

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

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;
103
	bool Pause() override;
104
	void Cancel() noexcept override;
Max Kellermann's avatar
Max Kellermann committed
105
};
106

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

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

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

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

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

157 158
AudioOutput *
OSXOutput::Create(EventLoop &, const ConfigBlock &block)
Avuton Olrich's avatar
Avuton Olrich committed
159
{
160
	OSXOutput *oo = new OSXOutput(block);
161
	AudioObjectPropertyAddress aopa;
162 163
	AudioDeviceID dev_id = kAudioDeviceUnknown;
	UInt32 dev_id_size = sizeof(dev_id);
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
	
	if (oo->component_subtype == kAudioUnitSubType_SystemOutput)
		// get system output dev_id if configured
		aopa = {
			kAudioHardwarePropertyDefaultSystemOutputDevice,
			kAudioObjectPropertyScopeOutput,
			kAudioObjectPropertyElementMaster
		};
	else
		// fallback to default device initially (can still be changed by osx_output_set_device)
		aopa = {
			kAudioHardwarePropertyDefaultOutputDevice,
			kAudioObjectPropertyScopeOutput,
			kAudioObjectPropertyElementMaster
		};

180 181 182 183 184 185 186 187
	AudioObjectGetPropertyData(kAudioObjectSystemObject,
				   &aopa,
				   0,
				   NULL,
				   &dev_id_size,
				   &dev_id);
	oo->dev_id = dev_id;

188
	return oo;
189 190
}

191

Matthew Leon's avatar
Matthew Leon committed
192 193 194
int
OSXOutput::GetVolume()
{
195 196
	Float32 vol;
	AudioObjectPropertyAddress aopa = {
Yue Wang's avatar
Yue Wang committed
197
		.mSelector	= kAudioHardwareServiceDeviceProperty_VirtualMasterVolume,
198 199 200 201 202 203 204 205 206 207
		.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
208 209

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

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

void
OSXOutput::SetVolume(unsigned new_volume) {
220 221
	Float32 vol = new_volume / 100.0;
	AudioObjectPropertyAddress aopa = {
Yue Wang's avatar
Yue Wang committed
222
		.mSelector	= kAudioHardwareServiceDeviceProperty_VirtualMasterVolume,
223 224 225 226 227 228 229 230 231 232
		.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
233 234

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

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

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

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

272 273 274 275 276 277 278 279 280
			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;
		}

281 282
		throw FormatRuntimeError("%s: invalid character '%c' in channel map",
					 device_name, *channel_map_str);
283 284
	}

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

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

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

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

332 333 334 335 336 337 338 339 340 341

static float
osx_output_score_sample_rate(Float64 destination_rate, unsigned int source_rate) {
	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;
342 343 344
	if (source_rate == destination_rate)
		score += 1000;
	else if(source_rate > destination_rate)
345 346 347 348 349 350 351 352
		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;
	
	return score;
}

static float
353
osx_output_score_format(const AudioStreamBasicDescription &format_desc, const AudioStreamBasicDescription &target_format) {
354 355 356
	float score = 0;
	// Score only linear PCM formats (everything else MPD cannot use)
	if (format_desc.mFormatID == kAudioFormatLinearPCM) {
357
		score += osx_output_score_sample_rate(format_desc.mSampleRate, target_format.mSampleRate);
358 359 360 361
		
		// Just choose the stream / format with the highest number of output channels
		score += format_desc.mChannelsPerFrame * 5;
		
362
		if (target_format.mFormatFlags == kLinearPCMFormatFlagIsFloat) {
363 364 365 366
			// for float, prefer the highest bitdepth we have
			if (format_desc.mBitsPerChannel >= 16)
				score += (format_desc.mBitsPerChannel / 8);
		} else {
367
			if (format_desc.mBitsPerChannel == target_format.mBitsPerChannel)
368
				score += 5;
369
			else if (format_desc.mBitsPerChannel > target_format.mBitsPerChannel)
370 371 372 373 374 375 376
				score += 1;
			
		}
	}
	return score;
}

377
static Float64
378
osx_output_set_device_format(AudioDeviceID dev_id, const AudioStreamBasicDescription &target_format)
379 380
{
	AudioObjectPropertyAddress aopa = {
381
		kAudioDevicePropertyStreams,
382 383 384 385 386
		kAudioObjectPropertyScopeOutput,
		kAudioObjectPropertyElementMaster
	};

	UInt32 property_size;
387 388 389 390
	OSStatus err = AudioObjectGetPropertyDataSize(dev_id, &aopa, 0, NULL, &property_size);	
	if (err != noErr) {	
		throw FormatRuntimeError("Cannot get number of streams: %d\n", err);	
	}	
391
	
392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415
	int n_streams = property_size / sizeof(AudioStreamID);	
	AudioStreamID streams[n_streams];	
	err = AudioObjectGetPropertyData(dev_id, &aopa, 0, NULL, &property_size, streams);	
	if (err != noErr) {	
		throw FormatRuntimeError("Cannot get streams: %d\n", err);	
	}	
	
	bool format_found = false;
	int output_stream;
	AudioStreamBasicDescription output_format;

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

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

		int format_count = property_size / sizeof(AudioStreamRangedDescription);
		AudioStreamRangedDescription format_list[format_count];
		err = AudioObjectGetPropertyData(stream, &aopa, 0, NULL, &property_size, format_list);
		if (err != noErr)
			throw FormatRuntimeError("Unable to get available formats for stream %d. Error = %s", streams[i], err);

		float output_score = 0;
		for (int j = 0; j < format_count; j++) {
			AudioStreamBasicDescription format_desc = format_list[j].mFormat;
			std::string format_string;
			
			// for devices with kAudioStreamAnyRate
			// we use the requested samplerate here
			if (format_desc.mSampleRate == kAudioStreamAnyRate)
440 441
				format_desc.mSampleRate = target_format.mSampleRate;
			float score = osx_output_score_format(format_desc, target_format);
442 443 444 445 446 447 448 449 450 451
			
			// print all (linear pcm) formats and their rating
			if(score > 0.0)
				FormatDebug(osx_output_domain, "Format: %s rated %f", StreamDescriptionToString(format_desc).c_str(), score);
			
			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;
452 453 454 455
			}
		}
	}

456 457 458 459 460 461 462 463 464 465 466 467 468 469
	if (format_found) {
		aopa.mSelector = kAudioStreamPropertyPhysicalFormat;	
		err = AudioObjectSetPropertyData(output_stream,
						 &aopa,	
						 0,	
						 NULL,	
						 sizeof(output_format),	
						 &output_format);	
		if (err != noErr) {	
			throw FormatRuntimeError("Failed to change the stream format: %d\n", err);	
		}	
	}	

	return output_format.mSampleRate;
470 471
}

472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521
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;
}
522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577

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");
	}
}


578 579
static void
osx_output_set_device(OSXOutput *oo)
580 581 582
{
	OSStatus status;
	UInt32 size, numdevices;
583
	AudioObjectPropertyAddress propaddr;
584
	CFStringRef cfname = nullptr;
585
	char errormsg[1024];
586 587 588
	char name[256];
	unsigned int i;

589 590 591 592 593
	AtScopeExit(&cfname) {
		if (cfname)
			CFRelease(cfname);
	};

594
	if (oo->component_subtype != kAudioUnitSubType_HALOutput)
595
		return;
596 597

	/* how many audio devices are there? */
598 599
	propaddr = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
	status = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propaddr, 0, nullptr, &size);
600
	if (status != noErr) {
601
		osx_os_status_to_cstring(status, errormsg, sizeof(errormsg));
602 603
		throw FormatRuntimeError("Unable to determine number of OS X audio devices: %s",
					 errormsg);
604 605 606 607
	}

	/* what are the available audio device IDs? */
	numdevices = size / sizeof(AudioDeviceID);
608 609
	std::unique_ptr<AudioDeviceID[]> deviceids(new AudioDeviceID[numdevices]);
	status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propaddr, 0, nullptr, &size, deviceids.get());
610
	if (status != noErr) {
611
		osx_os_status_to_cstring(status, errormsg, sizeof(errormsg));
612 613
		throw FormatRuntimeError("Unable to determine OS X audio device IDs: %s",
					 errormsg);
614 615 616
	}

	/* which audio device matches oo->device_name? */
617
	propaddr = { kAudioObjectPropertyName, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
618
	size = sizeof(CFStringRef);
619
	for (i = 0; i < numdevices; i++) {
620
		status = AudioObjectGetPropertyData(deviceids[i], &propaddr, 0, nullptr, &size, &cfname);
621
		if (status != noErr) {
622
			osx_os_status_to_cstring(status, errormsg, sizeof(errormsg));
623 624 625 626
			throw FormatRuntimeError("Unable to determine OS X device name "
						 "(device %u): %s",
						 (unsigned int) deviceids[i],
						 errormsg);
627
		}
628

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

632
		if (strcmp(oo->device_name, name) == 0) {
633 634 635
			FormatDebug(osx_output_domain,
				    "found matching device: ID=%u, name=%s",
				    (unsigned)deviceids[i], name);
636 637 638 639
			break;
		}
	}
	if (i == numdevices) {
640
                throw FormatRuntimeError("Found no audio device with name '%s' ",
641
			      oo->device_name);
642 643 644 645 646 647 648 649 650
	}

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

656
	oo->dev_id = deviceids[i];
657 658 659
	FormatDebug(osx_output_domain,
		    "set OS X audio output device ID=%u, name=%s",
		    (unsigned)deviceids[i], name);
660

661 662
	if (oo->channel_map)
		osx_output_set_channel_map(oo);
663 664
}

665 666 667 668 669 670 671 672

/*
	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.
*/

673 674
static OSStatus
osx_render(void *vdata,
675 676 677
	   gcc_unused AudioUnitRenderActionFlags *io_action_flags,
	   gcc_unused const AudioTimeStamp *in_timestamp,
	   gcc_unused UInt32 in_bus_number,
678
	   UInt32 in_number_frames,
679 680
	   AudioBufferList *buffer_list)
{
681
	OSXOutput *od = (OSXOutput *) vdata;
682

683 684 685 686 687
	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;
688 689
}

690 691
void
OSXOutput::Enable()
Avuton Olrich's avatar
Avuton Olrich committed
692
{
693
	char errormsg[1024];
694

695
	AudioComponentDescription desc;
696
	desc.componentType = kAudioUnitType_Output;
697
	desc.componentSubType = component_subtype;
698 699 700 701
	desc.componentManufacturer = kAudioUnitManufacturer_Apple;
	desc.componentFlags = 0;
	desc.componentFlagsMask = 0;

702
	AudioComponent comp = AudioComponentFindNext(nullptr, &desc);
703 704
	if (comp == 0)
		throw std::runtime_error("Error finding OS X component");
Warren Dukes's avatar
Warren Dukes committed
705

706
	OSStatus status = AudioComponentInstanceNew(comp, &au);
707
	if (status != noErr) {
708
		osx_os_status_to_cstring(status, errormsg, sizeof(errormsg));
709 710
		throw FormatRuntimeError("Unable to open OS X component: %s",
					 errormsg);
Warren Dukes's avatar
Warren Dukes committed
711
	}
712
#ifdef ENABLE_DSD
713
	pcm_export.Construct();
714
#endif
Warren Dukes's avatar
Warren Dukes committed
715

716
	try {
717
		osx_output_set_device(this);
718
	} catch (...) {
719
		AudioComponentInstanceDispose(au);
720
#ifdef ENABLE_DSD
721
		pcm_export.Destruct();
722
#endif
723
		throw;
724
	}
725
	
726 727
	if (hog_device)
		osx_output_hog_device(dev_id, true);
728 729
}

730 731
void
OSXOutput::Disable() noexcept
732
{
733
	AudioComponentInstanceDispose(au);
734
#ifdef ENABLE_DSD
735
	pcm_export.Destruct();
736
#endif
737

738 739
	if (hog_device)
		osx_output_hog_device(dev_id, false);
740 741
}

742 743
void
OSXOutput::Close() noexcept
744
{
745 746 747
	AudioOutputUnitStop(au);
	AudioUnitUninitialize(au);
	delete ring_buffer;
748 749
}

750 751
void
OSXOutput::Open(AudioFormat &audio_format)
752
{
753
	char errormsg[1024];
754
#ifdef ENABLE_DSD
755 756
	PcmExport::Params params;
	params.alsa_channel_order = true;
757
	bool dop = dop_setting;
758
	params.dop = false;
759
#endif
760

761 762
	memset(&asbd, 0, sizeof(asbd));
	asbd.mFormatID = kAudioFormatLinearPCM;
763 764 765 766
	if (audio_format.format == SampleFormat::FLOAT) {
		asbd.mFormatFlags = kLinearPCMFormatFlagIsFloat;
	} else {
		asbd.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
767
	}
768

769
	if (IsBigEndian())
770
		asbd.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
771

772 773 774 775 776
	if (audio_format.format == SampleFormat::S24_P32) {
		asbd.mBitsPerChannel = 24;
	} else {
		asbd.mBitsPerChannel = audio_format.GetSampleSize() * 8;
	}
777
	asbd.mBytesPerPacket = audio_format.GetFrameSize();
778
	asbd.mSampleRate = audio_format.sample_rate;
779

780
#ifdef ENABLE_DSD
781 782 783
	if (dop && audio_format.format == SampleFormat::DSD) {
		asbd.mBitsPerChannel = 24;
		params.dop = true;
784
		asbd.mSampleRate = params.CalcOutputSampleRate(audio_format.sample_rate);
785
		asbd.mBytesPerPacket = 4 * audio_format.channels;
786

787
	}
788
#endif
789

790 791 792
	asbd.mFramesPerPacket = 1;
	asbd.mBytesPerFrame = asbd.mBytesPerPacket;
	asbd.mChannelsPerFrame = audio_format.channels;
793

794
	Float64 sample_rate = osx_output_set_device_format(dev_id, asbd);
795 796

#ifdef ENABLE_DSD
797
	if(audio_format.format == SampleFormat::DSD && sample_rate != asbd.mSampleRate) { // fall back to PCM in case sample_rate cannot be synchronized
798 799 800 801 802 803 804
		params.dop = false;
		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;
	}
805
	dop_enabled = params.dop;
806
#endif
807

808
	OSStatus status =
809
		AudioUnitSetProperty(au, kAudioUnitProperty_StreamFormat,
810
				     kAudioUnitScope_Input, 0,
811 812
				     &asbd,
				     sizeof(asbd));
813 814
	if (status != noErr)
		throw std::runtime_error("Unable to set format on OS X device");
815

816 817
	AURenderCallbackStruct callback;
	callback.inputProc = osx_render;
818
	callback.inputProcRefCon = this;
819 820

	status =
821
		AudioUnitSetProperty(au,
822 823 824 825
				     kAudioUnitProperty_SetRenderCallback,
				     kAudioUnitScope_Input, 0,
				     &callback, sizeof(callback));
	if (status != noErr) {
826
		AudioComponentInstanceDispose(au);
827
		throw std::runtime_error("Unable to set callback for OS X audio unit");
828 829
	}

830
	status = AudioUnitInitialize(au);
831
	if (status != noErr) {
832
		osx_os_status_to_cstring(status, errormsg, sizeof(errormsg));
833 834
		throw FormatRuntimeError("Unable to initialize OS X audio unit: %s",
					 errormsg);
835 836
	}

837
	UInt32 buffer_frame_size = 1;
838
	status = osx_output_set_buffer_size(au, asbd, &buffer_frame_size);
839 840
	if (status != noErr) {
		osx_os_status_to_cstring(status, errormsg, sizeof(errormsg));
841 842
		throw FormatRuntimeError("Unable to set frame size: %s",
					 errormsg);
843
	}
844

845
	size_t ring_buffer_size = std::max<size_t>(buffer_frame_size,
846
						   MPD_OSX_BUFFER_TIME_MS * audio_format.GetFrameSize() * audio_format.sample_rate / 1000);
847 848 849 850 851 852 853

#ifdef ENABLE_DSD
        if (dop_enabled) {
		pcm_export->Open(audio_format.format, audio_format.channels, params);
		ring_buffer_size = std::max<size_t>(buffer_frame_size,
						   MPD_OSX_BUFFER_TIME_MS * pcm_export->GetFrameSize(audio_format) * asbd.mSampleRate / 1000);
	}
854
#endif
855
	ring_buffer = new boost::lockfree::spsc_queue<uint8_t>(ring_buffer_size);
856

857
	status = AudioOutputUnitStart(au);
858
	if (status != 0) {
859
		AudioUnitUninitialize(au);
860
		osx_os_status_to_cstring(status, errormsg, sizeof(errormsg));
861
		throw FormatRuntimeError("Unable to start audio output: %s",
862
					 errormsg);
863
	}
864
	pause = false;
865 866
}

867 868
size_t
OSXOutput::Play(const void *chunk, size_t size)
Avuton Olrich's avatar
Avuton Olrich committed
869
{
870
	assert(size > 0);
871 872 873 874 875 876 877 878
	if(pause) {
		pause = false;
		OSStatus status = AudioOutputUnitStart(au);
		if (status != 0) {
			AudioUnitUninitialize(au);
			throw std::runtime_error("Unable to restart audio output after pause");
		}
	}
879
#ifdef ENABLE_DSD
880 881
        if (dop_enabled) {
		const auto e = pcm_export->Export({chunk, size});
882 883 884 885 886 887
		/* the DoP (DSD over PCM) filter converts two frames
		   at a time and ignores the last odd frame; if there
		   was only one frame (e.g. the last frame in the
		   file), the result is empty; to avoid an endless
		   loop, bail out here, and pretend the one frame has
		   been played */
888 889
		if (e.size == 0)
			return size;
890

891 892 893
		size_t bytes_written = ring_buffer->push((const uint8_t *)e.data, e.size);
		return pcm_export->CalcSourceSize(bytes_written);
	}
894 895
#endif
	return ring_buffer->push((const uint8_t *)chunk, size);
896 897
}

898 899
std::chrono::steady_clock::duration
OSXOutput::Delay() const noexcept
900
{
Yue Wang's avatar
Yue Wang committed
901
	return ring_buffer->write_available() && !pause
902
		? std::chrono::steady_clock::duration::zero()
903
		: std::chrono::milliseconds(MPD_OSX_BUFFER_TIME_MS / 4);
904
}
905 906 907 908 909 910 911 912
	
bool OSXOutput::Pause() {
	if(!pause) {
		pause = true;
		AudioOutputUnitStop(au);
	}
	return true;
}
913 914 915 916 917 918 919 920 921 922 923 924

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
925 926 927 928 929 930 931 932 933 934 935 936
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);
}

937
const struct AudioOutputPlugin osx_output_plugin = {
938 939
	"osx",
	osx_output_test_default_device,
940
	&OSXOutput::Create,
941
	&osx_mixer_plugin,
942
};