OSXOutputPlugin.cxx 22.1 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2020 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 "apple/AudioObject.hxx"
23
#include "apple/AudioUnit.hxx"
24
#include "apple/StringRef.hxx"
25
#include "apple/Throw.hxx"
26
#include "../OutputAPI.hxx"
Matthew Leon's avatar
Matthew Leon committed
27
#include "mixer/MixerList.hxx"
28
#include "util/RuntimeError.hxx"
29
#include "util/Domain.hxx"
30 31
#include "util/Manual.hxx"
#include "util/ConstBuffer.hxx"
32
#include "pcm/Export.hxx"
33 34
#include "thread/Mutex.hxx"
#include "thread/Cond.hxx"
35
#include "util/ByteOrder.hxx"
36
#include "util/CharUtil.hxx"
37
#include "util/StringAPI.hxx"
38 39
#include "util/StringBuffer.hxx"
#include "util/StringFormat.hxx"
40
#include "Log.hxx"
41

42
#include <CoreAudio/CoreAudio.h>
43
#include <AudioUnit/AudioUnit.h>
Yue Wang's avatar
Yue Wang committed
44
#include <AudioToolbox/AudioToolbox.h>
45
#include <CoreServices/CoreServices.h>
Yue Wang's avatar
Yue Wang committed
46
#include <boost/lockfree/spsc_queue.hpp>
47

48 49
#include <memory>

50 51
static constexpr unsigned MPD_OSX_BUFFER_TIME_MS = 100;

52
static StringBuffer<64>
53 54
StreamDescriptionToString(const AudioStreamBasicDescription desc)
{
55 56
	// Only convert the lpcm formats (nothing else supported / used by MPD)
	assert(desc.mFormatID == kAudioFormatLinearPCM);
57

58
	return StringFormat<64>("%u channel %s %sinterleaved %u-bit %s %s (%uHz)",
59 60 61 62 63 64 65
				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);
66 67 68
}


69
struct OSXOutput final : AudioOutput {
70 71 72 73
	/* configuration settings */
	OSType component_subtype;
	/* only applicable with kAudioUnitSubType_HALOutput */
	const char *device_name;
74 75
	const char *const channel_map;
	const bool hog_device;
76

77
	bool pause;
78 79 80 81 82 83

	/**
	 * Is the audio unit "started", i.e. was AudioOutputUnitStart() called?
	 */
	bool started;

84 85 86 87 88 89
#ifdef ENABLE_DSD
	/**
	 * Enable DSD over PCM according to the DoP standard?
	 *
	 * @see http://dsd-guide.com/dop-open-standard
	 */
90
	const bool dop_setting;
91
	bool dop_enabled;
92
	Manual<PcmExport> pcm_export;
93
#endif
94

95
	AudioDeviceID dev_id;
96
	AudioComponentInstance au;
97
	AudioStreamBasicDescription asbd;
98 99

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

101
	OSXOutput(const ConfigBlock &block);
102 103

	static AudioOutput *Create(EventLoop &, const ConfigBlock &block);
Matthew Leon's avatar
Matthew Leon committed
104 105
	int GetVolume();
	void SetVolume(unsigned new_volume);
106 107 108 109 110 111 112 113 114 115

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;
116
	bool Pause() override;
117
	void Cancel() noexcept override;
Max Kellermann's avatar
Max Kellermann committed
118
};
119

120
static constexpr Domain osx_output_domain("osx_output");
121

Max Kellermann's avatar
Max Kellermann committed
122
static bool
123
osx_output_test_default_device()
Avuton Olrich's avatar
Avuton Olrich committed
124
{
125 126
	/* on a Mac, this is always the default plugin, if nothing
	   else is configured */
127
	return true;
Warren Dukes's avatar
Warren Dukes committed
128 129
}

130
OSXOutput::OSXOutput(const ConfigBlock &block)
131 132 133 134 135 136
	:AudioOutput(FLAG_ENABLE_DISABLE|FLAG_PAUSE),
	 channel_map(block.GetBlockValue("channel_map")),
	 hog_device(block.GetBlockValue("hog_device", false))
#ifdef ENABLE_DSD
	, dop_setting(block.GetBlockValue("dop", false))
#endif
137
{
138
	const char *device = block.GetBlockValue("device");
139

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

155 156
AudioOutput *
OSXOutput::Create(EventLoop &, const ConfigBlock &block)
Avuton Olrich's avatar
Avuton Olrich committed
157
{
158
	OSXOutput *oo = new OSXOutput(block);
159

160 161 162 163 164 165 166 167 168 169 170 171 172 173
	static constexpr AudioObjectPropertyAddress default_system_output_device{
		kAudioHardwarePropertyDefaultSystemOutputDevice,
		kAudioObjectPropertyScopeOutput,
		kAudioObjectPropertyElementMaster,
	};

	static constexpr AudioObjectPropertyAddress default_output_device{
		kAudioHardwarePropertyDefaultOutputDevice,
		kAudioObjectPropertyScopeOutput,
		kAudioObjectPropertyElementMaster
	};

	const auto &aopa =
		oo->component_subtype == kAudioUnitSubType_SystemOutput
174
		// get system output dev_id if configured
175
		? default_system_output_device
176 177
		/* fallback to default device initially (can still be
		   changed by osx_output_set_device) */
178
		: default_output_device;
179

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

190
	return oo;
191 192
}

193

Matthew Leon's avatar
Matthew Leon committed
194 195 196
int
OSXOutput::GetVolume()
{
197
	static constexpr AudioObjectPropertyAddress aopa = {
198 199 200
		kAudioHardwareServiceDeviceProperty_VirtualMasterVolume,
		kAudioObjectPropertyScopeOutput,
		kAudioObjectPropertyElementMaster,
201
	};
Matthew Leon's avatar
Matthew Leon committed
202

203 204
	const auto vol = AudioObjectGetPropertyDataT<Float32>(dev_id,
							      aopa);
Matthew Leon's avatar
Matthew Leon committed
205

206
	return static_cast<int>(vol * 100.0);
Matthew Leon's avatar
Matthew Leon committed
207 208 209
}

void
210 211
OSXOutput::SetVolume(unsigned new_volume)
{
212
	Float32 vol = new_volume / 100.0;
213
	static constexpr AudioObjectPropertyAddress aopa = {
214 215 216
		kAudioHardwareServiceDeviceProperty_VirtualMasterVolume,
		kAudioObjectPropertyScopeOutput,
		kAudioObjectPropertyElementMaster
217 218 219 220 221 222 223 224
	};
	UInt32 size = sizeof(vol);
	OSStatus status = AudioObjectSetPropertyData(dev_id,
						     &aopa,
						     0,
						     NULL,
						     size,
						     &vol);
Matthew Leon's avatar
Matthew Leon committed
225

226 227
	if (status != noErr)
		Apple::ThrowOSStatus(status);
Matthew Leon's avatar
Matthew Leon committed
228 229
}

230
static void
231 232 233 234
osx_output_parse_channel_map(const char *device_name,
			     const char *channel_map_str,
			     SInt32 channel_map[],
			     UInt32 num_channels)
235 236 237 238 239
{
	unsigned int inserted_channels = 0;
	bool want_number = true;

	while (*channel_map_str) {
240 241 242
		if (inserted_channels >= num_channels)
			throw FormatRuntimeError("%s: channel map contains more than %u entries or trailing garbage",
						 device_name, num_channels);
243 244 245 246 247 248 249 250

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

		if (want_number &&
251
			(IsDigitASCII(*channel_map_str) || *channel_map_str == '-')
252
		) {
253
			char *endptr;
254
			channel_map[inserted_channels] = strtol(channel_map_str, &endptr, 10);
255 256
			if (channel_map[inserted_channels] < -1)
				throw FormatRuntimeError("%s: channel map value %d not allowed (must be -1 or greater)",
257
							 device_name, channel_map[inserted_channels]);
258

259 260 261 262 263 264 265 266 267
			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;
		}

268 269
		throw FormatRuntimeError("%s: invalid character '%c' in channel map",
					 device_name, *channel_map_str);
270 271
	}

272 273 274
	if (inserted_channels < num_channels)
		throw FormatRuntimeError("%s: channel map contains less than %u entries",
					 device_name, num_channels);
275 276
}

277 278 279 280 281 282 283 284 285 286
static UInt32
AudioUnitGetChannelsPerFrame(AudioUnit inUnit)
{
	const auto desc = AudioUnitGetPropertyT<AudioStreamBasicDescription>(inUnit,
									     kAudioUnitProperty_StreamFormat,
									     kAudioUnitScope_Output,
									     0);
	return desc.mChannelsPerFrame;
}

287 288
static void
osx_output_set_channel_map(OSXOutput *oo)
289 290 291
{
	OSStatus status;

292
	const UInt32 num_channels = AudioUnitGetChannelsPerFrame(oo->au);
293
	std::unique_ptr<SInt32[]> channel_map(new SInt32[num_channels]);
294 295 296
	osx_output_parse_channel_map(oo->device_name,
				     oo->channel_map,
				     channel_map.get(),
297
				     num_channels);
298

299
	UInt32 size = num_channels * sizeof(SInt32);
300 301 302 303
	status = AudioUnitSetProperty(oo->au,
		kAudioOutputUnitProperty_ChannelMap,
		kAudioUnitScope_Input,
		0,
304
		channel_map.get(),
305
		size);
306 307
	if (status != noErr)
		Apple::ThrowOSStatus(status, "unable to set channel map");
308 309
}

310 311

static float
312 313
osx_output_score_sample_rate(Float64 destination_rate, unsigned source_rate)
{
314 315 316 317
	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
318 319
	if (frac_portion < 0.01 || frac_portion >= 0.99)
		score += 1000;
320 321
	// prefer exact matches over other multiples
	score += (int_portion == 1.0) ? 500 : 0;
322 323
	if (source_rate == destination_rate)
		score += 1000;
324
	else if (source_rate > destination_rate)
325 326 327
		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;
328

329 330 331 332
	return score;
}

static float
333 334 335
osx_output_score_format(const AudioStreamBasicDescription &format_desc,
			const AudioStreamBasicDescription &target_format)
{
336 337 338
	float score = 0;
	// Score only linear PCM formats (everything else MPD cannot use)
	if (format_desc.mFormatID == kAudioFormatLinearPCM) {
339 340 341
		score += osx_output_score_sample_rate(format_desc.mSampleRate,
						      target_format.mSampleRate);

342 343
		// Just choose the stream / format with the highest number of output channels
		score += format_desc.mChannelsPerFrame * 5;
344

345
		if (target_format.mFormatFlags == kLinearPCMFormatFlagIsFloat) {
346 347 348 349
			// for float, prefer the highest bitdepth we have
			if (format_desc.mBitsPerChannel >= 16)
				score += (format_desc.mBitsPerChannel / 8);
		} else {
350
			if (format_desc.mBitsPerChannel == target_format.mBitsPerChannel)
351
				score += 5;
352
			else if (format_desc.mBitsPerChannel > target_format.mBitsPerChannel)
353
				score += 1;
354

355 356
		}
	}
357

358 359 360
	return score;
}

361
static Float64
362 363
osx_output_set_device_format(AudioDeviceID dev_id,
			     const AudioStreamBasicDescription &target_format)
364
{
365
	static constexpr AudioObjectPropertyAddress aopa_device_streams = {
366
		kAudioDevicePropertyStreams,
367 368 369 370
		kAudioObjectPropertyScopeOutput,
		kAudioObjectPropertyElementMaster
	};

371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388
	static constexpr AudioObjectPropertyAddress aopa_stream_direction = {
		kAudioStreamPropertyDirection,
		kAudioObjectPropertyScopeOutput,
		kAudioObjectPropertyElementMaster
	};

	static constexpr AudioObjectPropertyAddress aopa_stream_phys_formats = {
		kAudioStreamPropertyAvailablePhysicalFormats,
		kAudioObjectPropertyScopeOutput,
		kAudioObjectPropertyElementMaster
	};

	static constexpr AudioObjectPropertyAddress aopa_stream_phys_format = {
		kAudioStreamPropertyPhysicalFormat,
		kAudioObjectPropertyScopeOutput,
		kAudioObjectPropertyElementMaster
	};

389
	OSStatus err;
390

391 392 393
	const auto streams =
		AudioObjectGetPropertyDataArray<AudioStreamID>(dev_id,
							       aopa_device_streams);
394

395 396 397 398
	bool format_found = false;
	int output_stream;
	AudioStreamBasicDescription output_format;

399 400 401 402
	for (const auto stream : streams) {
		const auto direction =
			AudioObjectGetPropertyDataT<UInt32>(stream,
							    aopa_stream_direction);
403
		if (direction != 0)
404 405
			continue;

406 407 408
		const auto format_list =
			AudioObjectGetPropertyDataArray<AudioStreamRangedDescription>(stream,
										      aopa_stream_phys_formats);
409 410

		float output_score = 0;
411 412 413

		for (const auto &format : format_list) {
			AudioStreamBasicDescription format_desc = format.mFormat;
414
			std::string format_string;
415

416 417 418
			// for devices with kAudioStreamAnyRate
			// we use the requested samplerate here
			if (format_desc.mSampleRate == kAudioStreamAnyRate)
419 420
				format_desc.mSampleRate = target_format.mSampleRate;
			float score = osx_output_score_format(format_desc, target_format);
421

422
			// print all (linear pcm) formats and their rating
423 424 425 426 427
			if (score > 0.0)
				FormatDebug(osx_output_domain,
					    "Format: %s rated %f",
					    StreamDescriptionToString(format_desc).c_str(), score);

428 429 430 431 432
			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;
433 434 435 436
			}
		}
	}

437 438
	if (format_found) {
		err = AudioObjectSetPropertyData(output_stream,
439
						 &aopa_stream_phys_format,
440 441 442 443 444
						 0,
						 NULL,
						 sizeof(output_format),
						 &output_format);
		if (err != noErr)
445
			throw FormatRuntimeError("Failed to change the stream format: %d",
446 447
						 err);
	}
448 449

	return output_format.mSampleRate;
450 451
}

452 453
static UInt32
osx_output_set_buffer_size(AudioUnit au, AudioStreamBasicDescription desc)
454
{
455 456 457 458
	const auto value_range = AudioUnitGetPropertyT<AudioValueRange>(au,
									kAudioDevicePropertyBufferFrameSizeRange,
									kAudioUnitScope_Global,
									0);
459

460 461 462 463 464 465
	try {
		AudioUnitSetBufferFrameSize(au, value_range.mMaximum);
	} catch (...) {
		LogError(std::current_exception(),
			 "Failed to set maximum buffer size");
	}
466

467
	auto buffer_frame_size = AudioUnitGetBufferFrameSize(au);
468 469 470 471
	buffer_frame_size *= desc.mBytesPerFrame;

	// We set the frame size to a power of two integer that
	// is larger than buffer_frame_size.
472 473 474
	UInt32 frame_size = 1;
	while (frame_size < buffer_frame_size + 1)
		frame_size <<= 1;
475

476
	return frame_size;
477
}
478 479

static void
480
osx_output_hog_device(AudioDeviceID dev_id, bool hog) noexcept
481
{
482
	static constexpr AudioObjectPropertyAddress aopa = {
483 484 485 486
		kAudioDevicePropertyHogMode,
		kAudioObjectPropertyScopeOutput,
		kAudioObjectPropertyElementMaster
	};
487

488 489 490 491 492 493 494 495 496 497
	pid_t hog_pid;

	try {
		hog_pid = AudioObjectGetPropertyDataT<pid_t>(dev_id, aopa);
	} catch (...) {
		Log(LogLevel::DEBUG, std::current_exception(),
		    "Failed to query HogMode");
		return;
	}

498 499
	if (hog) {
		if (hog_pid != -1) {
500
			FormatDebug(osx_output_domain,
501 502 503 504 505
				    "Device is already hogged.");
			return;
		}
	} else {
		if (hog_pid != getpid()) {
506
			FormatDebug(osx_output_domain,
507 508 509 510
				    "Device is not owned by this process.");
			return;
		}
	}
511

512
	hog_pid = hog ? getpid() : -1;
513 514
	UInt32 size = sizeof(hog_pid);
	OSStatus err;
515 516 517 518 519 520 521 522 523 524 525
	err = AudioObjectSetPropertyData(dev_id,
					 &aopa,
					 0,
					 NULL,
					 size,
					 &hog_pid);
	if (err != noErr) {
		FormatDebug(osx_output_domain,
			    "Cannot hog the device: %d",
			    err);
	} else {
526 527 528 529
		LogDebug(osx_output_domain,
			 hog_pid == -1
			 ? "Device is unhogged"
			 : "Device is hogged");
530 531 532
	}
}

533 534 535
gcc_pure
static bool
IsAudioDeviceName(AudioDeviceID id, const char *expected_name) noexcept
536
{
537 538 539 540 541
	static constexpr AudioObjectPropertyAddress aopa_name{
		kAudioObjectPropertyName,
		kAudioObjectPropertyScopeGlobal,
		kAudioObjectPropertyElementMaster,
	};
542

543
	char actual_name[256];
544 545 546 547 548 549

	try {
		auto cfname = AudioObjectGetStringProperty(id, aopa_name);
		if (!cfname.GetCString(actual_name, sizeof(actual_name)))
			return false;
	} catch (...) {
550
		return false;
551
	}
552

553 554 555
	return StringIsEqual(actual_name, expected_name);
}

556
static AudioDeviceID
557 558
FindAudioDeviceByName(const char *name)
{
559
	/* what are the available audio device IDs? */
560 561 562 563 564 565
	static constexpr AudioObjectPropertyAddress aopa_hw_devices{
		kAudioHardwarePropertyDevices,
		kAudioObjectPropertyScopeGlobal,
		kAudioObjectPropertyElementMaster,
	};

566
	const auto ids =
567 568
		AudioObjectGetPropertyDataArray<AudioDeviceID>(kAudioObjectSystemObject,
							       aopa_hw_devices);
569

570 571 572 573
	for (const auto id : ids) {
		if (IsAudioDeviceName(id, name))
			return id;
	}
574

575 576
	throw FormatRuntimeError("Found no audio device with name '%s' ",
				 name);
577
}
578

579 580 581 582 583
static void
osx_output_set_device(OSXOutput *oo)
{
	if (oo->component_subtype != kAudioUnitSubType_HALOutput)
		return;
584

585
	const auto id = FindAudioDeviceByName(oo->device_name);
586

587 588
	FormatDebug(osx_output_domain,
		    "found matching device: ID=%u, name=%s",
589
		    (unsigned)id, oo->device_name);
590

591
	AudioUnitSetCurrentDevice(oo->au, id);
592

593
	oo->dev_id = id;
594 595
	FormatDebug(osx_output_domain,
		    "set OS X audio output device ID=%u, name=%s",
596
		    (unsigned)id, oo->device_name);
597

598 599
	if (oo->channel_map)
		osx_output_set_channel_map(oo);
600 601
}

602

603 604 605 606 607 608 609
/**
 * 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.
 */
610 611
static OSStatus
osx_render(void *vdata,
Rosen Penev's avatar
Rosen Penev committed
612 613 614
	   [[maybe_unused]] AudioUnitRenderActionFlags *io_action_flags,
	   [[maybe_unused]] const AudioTimeStamp *in_timestamp,
	   [[maybe_unused]] UInt32 in_bus_number,
615
	   UInt32 in_number_frames,
616 617
	   AudioBufferList *buffer_list)
{
618
	OSXOutput *od = (OSXOutput *) vdata;
619

620 621 622 623
	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);
624
	return noErr;
625 626
}

627 628
void
OSXOutput::Enable()
Avuton Olrich's avatar
Avuton Olrich committed
629
{
630
	AudioComponentDescription desc;
631
	desc.componentType = kAudioUnitType_Output;
632
	desc.componentSubType = component_subtype;
633 634 635 636
	desc.componentManufacturer = kAudioUnitManufacturer_Apple;
	desc.componentFlags = 0;
	desc.componentFlagsMask = 0;

637
	AudioComponent comp = AudioComponentFindNext(nullptr, &desc);
638 639
	if (comp == 0)
		throw std::runtime_error("Error finding OS X component");
Warren Dukes's avatar
Warren Dukes committed
640

641
	OSStatus status = AudioComponentInstanceNew(comp, &au);
642 643 644
	if (status != noErr)
		Apple::ThrowOSStatus(status, "Unable to open OS X component");

645
#ifdef ENABLE_DSD
646
	pcm_export.Construct();
647
#endif
Warren Dukes's avatar
Warren Dukes committed
648

649
	try {
650
		osx_output_set_device(this);
651
	} catch (...) {
652
		AudioComponentInstanceDispose(au);
653
#ifdef ENABLE_DSD
654
		pcm_export.Destruct();
655
#endif
656
		throw;
657
	}
658

659 660
	if (hog_device)
		osx_output_hog_device(dev_id, true);
661 662
}

663 664
void
OSXOutput::Disable() noexcept
665
{
666
	AudioComponentInstanceDispose(au);
667
#ifdef ENABLE_DSD
668
	pcm_export.Destruct();
669
#endif
670

671 672
	if (hog_device)
		osx_output_hog_device(dev_id, false);
673 674
}

675 676
void
OSXOutput::Close() noexcept
677
{
678 679
	if (started)
		AudioOutputUnitStop(au);
680 681
	AudioUnitUninitialize(au);
	delete ring_buffer;
682 683
}

684 685
void
OSXOutput::Open(AudioFormat &audio_format)
686
{
687
#ifdef ENABLE_DSD
688 689
	PcmExport::Params params;
	params.alsa_channel_order = true;
690 691
	bool dop = dop_setting;
#endif
692

693 694
	memset(&asbd, 0, sizeof(asbd));
	asbd.mFormatID = kAudioFormatLinearPCM;
695 696 697 698
	if (audio_format.format == SampleFormat::FLOAT) {
		asbd.mFormatFlags = kLinearPCMFormatFlagIsFloat;
	} else {
		asbd.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
699
	}
700

701
	if (IsBigEndian())
702
		asbd.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
703

704 705 706 707 708
	if (audio_format.format == SampleFormat::S24_P32) {
		asbd.mBitsPerChannel = 24;
	} else {
		asbd.mBitsPerChannel = audio_format.GetSampleSize() * 8;
	}
709
	asbd.mBytesPerPacket = audio_format.GetFrameSize();
710
	asbd.mSampleRate = audio_format.sample_rate;
711

712
#ifdef ENABLE_DSD
713 714
	if (dop && audio_format.format == SampleFormat::DSD) {
		asbd.mBitsPerChannel = 24;
715
		params.dsd_mode = PcmExport::DsdMode::DOP;
716
		asbd.mSampleRate = params.CalcOutputSampleRate(audio_format.sample_rate);
717
		asbd.mBytesPerPacket = 4 * audio_format.channels;
718

719
	}
720
#endif
721

722 723 724
	asbd.mFramesPerPacket = 1;
	asbd.mBytesPerFrame = asbd.mBytesPerPacket;
	asbd.mChannelsPerFrame = audio_format.channels;
725

726
	Float64 sample_rate = osx_output_set_device_format(dev_id, asbd);
727 728

#ifdef ENABLE_DSD
729 730 731
	if (audio_format.format == SampleFormat::DSD &&
	    sample_rate != asbd.mSampleRate) {
		// fall back to PCM in case sample_rate cannot be synchronized
732
		params.dsd_mode = PcmExport::DsdMode::NONE;
733 734 735 736 737 738
		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;
	}
739
	dop_enabled = params.dsd_mode == PcmExport::DsdMode::DOP;
740
#endif
741

742
	AudioUnitSetInputStreamFormat(au, asbd);
743

744 745
	AURenderCallbackStruct callback;
	callback.inputProc = osx_render;
746
	callback.inputProcRefCon = this;
747

748
	AudioUnitSetInputRenderCallback(au, callback);
749

750
	OSStatus status = AudioUnitInitialize(au);
751 752
	if (status != noErr)
		Apple::ThrowOSStatus(status, "Unable to initialize OS X audio unit");
753

754
	UInt32 buffer_frame_size = osx_output_set_buffer_size(au, asbd);
755

756
	size_t ring_buffer_size = std::max<size_t>(buffer_frame_size,
757
						   MPD_OSX_BUFFER_TIME_MS * audio_format.GetFrameSize() * audio_format.sample_rate / 1000);
758 759

#ifdef ENABLE_DSD
760
	if (dop_enabled) {
761 762
		pcm_export->Open(audio_format.format, audio_format.channels, params);
		ring_buffer_size = std::max<size_t>(buffer_frame_size,
763
						   MPD_OSX_BUFFER_TIME_MS * pcm_export->GetOutputFrameSize() * asbd.mSampleRate / 1000);
764
	}
765
#endif
766
	ring_buffer = new boost::lockfree::spsc_queue<uint8_t>(ring_buffer_size);
767

768
	pause = false;
769
	started = false;
770 771
}

772 773
size_t
OSXOutput::Play(const void *chunk, size_t size)
Avuton Olrich's avatar
Avuton Olrich committed
774
{
775
	assert(size > 0);
776 777 778

	pause = false;

779 780
	ConstBuffer<uint8_t> input((const uint8_t *)chunk, size);

781
#ifdef ENABLE_DSD
782
	if (dop_enabled) {
783 784
		input = ConstBuffer<uint8_t>::FromVoid(pcm_export->Export(input.ToVoid()));
		if (input.empty())
785 786
			return size;
	}
787
#endif
788 789

	size_t bytes_written = ring_buffer->push(input.data, input.size);
790

791 792 793 794 795 796
	if (!started) {
		OSStatus status = AudioOutputUnitStart(au);
		if (status != noErr)
			throw std::runtime_error("Unable to restart audio output after pause");

		started = true;
797
	}
798

799 800
#ifdef ENABLE_DSD
	if (dop_enabled)
801
		bytes_written = pcm_export->CalcInputSize(bytes_written);
802
#endif
803 804

	return bytes_written;
805 806
}

807 808
std::chrono::steady_clock::duration
OSXOutput::Delay() const noexcept
809
{
Yue Wang's avatar
Yue Wang committed
810
	return ring_buffer->write_available() && !pause
811
		? std::chrono::steady_clock::duration::zero()
812
		: std::chrono::milliseconds(MPD_OSX_BUFFER_TIME_MS / 4);
813
}
814 815 816

bool OSXOutput::Pause()
{
817 818 819
	pause = true;

	if (started) {
820
		AudioOutputUnitStop(au);
821
		started = false;
822
	}
823

824 825
	return true;
}
826 827 828 829

void
OSXOutput::Cancel() noexcept
{
830 831 832 833 834
	if (started) {
		AudioOutputUnitStop(au);
		started = false;
	}

835 836 837 838
	ring_buffer->reset();
#ifdef ENABLE_DSD
	pcm_export->Reset();
#endif
839 840

	/* the AudioUnit will be restarted by the next Play() call */
841 842
}

Matthew Leon's avatar
Matthew Leon committed
843 844 845 846 847 848 849 850 851 852 853 854
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);
}

855
const struct AudioOutputPlugin osx_output_plugin = {
856 857
	"osx",
	osx_output_test_default_device,
858
	&OSXOutput::Create,
859
	&osx_mixer_plugin,
860
};