PulseOutputPlugin.cxx 18.8 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 "PulseOutputPlugin.hxx"
21
#include "lib/pulse/Error.hxx"
22
#include "lib/pulse/LogError.hxx"
23
#include "lib/pulse/LockGuard.hxx"
24
#include "../OutputAPI.hxx"
Max Kellermann's avatar
Max Kellermann committed
25 26
#include "mixer/MixerList.hxx"
#include "mixer/plugins/PulseMixerPlugin.hxx"
27
#include "util/ScopeExit.hxx"
28

29 30 31
#include <pulse/thread-mainloop.h>
#include <pulse/context.h>
#include <pulse/stream.h>
32 33
#include <pulse/introspect.h>
#include <pulse/subscribe.h>
34
#include <pulse/version.h>
35

36
#include <cassert>
37
#include <cstddef>
38 39
#include <stdexcept>

40
#include <stdlib.h>
41

42
#define MPD_PULSE_NAME "Music Player Daemon"
43

44
class PulseOutput final : AudioOutput {
45 46 47
	const char *name;
	const char *server;
	const char *sink;
48
	const char *const media_role;
49

50
	PulseMixer *mixer = nullptr;
51

52
	struct pa_threaded_mainloop *mainloop = nullptr;
53
	struct pa_context *context;
54
	struct pa_stream *stream = nullptr;
55 56

	size_t writable;
57

58 59
	bool pause;

60
	explicit PulseOutput(const ConfigBlock &block);
61

62 63 64
public:
	void SetMixer(PulseMixer &_mixer);

Rosen Penev's avatar
Rosen Penev committed
65
	void ClearMixer([[maybe_unused]] PulseMixer &old_mixer) {
66 67 68 69 70
		assert(mixer == &old_mixer);

		mixer = nullptr;
	}

71
	void SetVolume(const pa_cvolume &volume);
72

73 74
	struct pa_threaded_mainloop *GetMainloop() {
		return mainloop;
75 76 77 78 79 80 81 82 83 84 85
	}

	void OnContextStateChanged(pa_context_state_t new_state);
	void OnServerLayoutChanged(pa_subscription_event_type_t t,
				   uint32_t idx);
	void OnStreamSuspended(pa_stream *_stream);
	void OnStreamStateChanged(pa_stream *_stream,
				  pa_stream_state_t new_state);
	void OnStreamWrite(size_t nbytes);

	void OnStreamSuccess() {
86
		Signal();
87 88
	}

89 90
	static bool TestDefaultDevice();

91 92 93 94
	static AudioOutput *Create(EventLoop &,
				   const ConfigBlock &block) {
		return new PulseOutput(block);
	}
95

96 97
	void Enable() override;
	void Disable() noexcept override;
98

99 100
	void Open(AudioFormat &audio_format) override;
	void Close() noexcept override;
101

102
	[[nodiscard]] std::chrono::steady_clock::duration Delay() const noexcept override;
103 104
	size_t Play(const void *chunk, size_t size) override;
	void Cancel() noexcept override;
105
	bool Pause() override;
106 107 108 109 110

private:
	/**
	 * Attempt to connect asynchronously to the PulseAudio server.
	 *
111
	 * Throws on error.
112
	 */
113
	void Connect();
114 115 116 117 118 119

	/**
	 * Create, set up and connect a context.
	 *
	 * Caller must lock the main loop.
	 *
120
	 * Throws on error.
121
	 */
122
	void SetupContext();
123 124 125 126 127 128 129 130

	/**
	 * Frees and clears the context.
	 *
	 * Caller must lock the main loop.
	 */
	void DeleteContext();

131 132 133 134
	void Signal() {
		pa_threaded_mainloop_signal(mainloop, 0);
	}

135 136 137 138 139 140 141
	/**
	 * Check if the context is (already) connected, and waits if
	 * not.  If the context has been disconnected, retry to
	 * connect.
	 *
	 * Caller must lock the main loop.
	 *
142
	 * Throws on error.
143
	 */
144
	void WaitConnection();
145 146 147 148 149 150

	/**
	 * Create, set up and connect a context.
	 *
	 * Caller must lock the main loop.
	 *
151
	 * Throws on error.
152
	 */
153
	void SetupStream(const pa_sample_spec &ss);
154 155 156 157 158 159 160 161 162 163 164

	/**
	 * Frees and clears the stream.
	 */
	void DeleteStream();

	/**
	 * Check if the stream is (already) connected, and waits if
	 * not.  The mainloop must be locked before calling this
	 * function.
	 *
165
	 * Throws on error.
166
	 */
167
	void WaitStream();
168 169 170

	/**
	 * Sets cork mode on the stream.
171
	 *
172
	 * Throws on error.
173
	 */
174
	void StreamPause(bool pause);
175 176
};

177
PulseOutput::PulseOutput(const ConfigBlock &block)
178
	:AudioOutput(FLAG_ENABLE_DISABLE|FLAG_PAUSE),
179 180
	 name(block.GetBlockValue("name", "mpd_pulse")),
	 server(block.GetBlockValue("server")),
181 182
	 sink(block.GetBlockValue("sink")),
	 media_role(block.GetBlockValue("media_role"))
183 184 185 186 187
{
	setenv("PULSE_PROP_media.role", "music", true);
	setenv("PULSE_PROP_application.icon_name", "mpd", true);
}

188 189
struct pa_threaded_mainloop *
pulse_output_get_mainloop(PulseOutput &po)
190
{
191
	return po.GetMainloop();
192 193
}

194 195
inline void
PulseOutput::SetMixer(PulseMixer &_mixer)
196
{
197
	assert(mixer == nullptr);
198

199
	mixer = &_mixer;
200

201
	if (mainloop == nullptr)
202 203
		return;

204
	Pulse::LockGuard lock(mainloop);
205

206 207 208
	if (context != nullptr &&
	    pa_context_get_state(context) == PA_CONTEXT_READY) {
		pulse_mixer_on_connect(_mixer, context);
209

210 211 212
		if (stream != nullptr &&
		    pa_stream_get_state(stream) == PA_STREAM_READY)
			pulse_mixer_on_change(_mixer, context, stream);
213 214 215 216
	}
}

void
217
pulse_output_set_mixer(PulseOutput &po, PulseMixer &pm)
218
{
219
	po.SetMixer(pm);
220 221
}

222 223
void
pulse_output_clear_mixer(PulseOutput &po, PulseMixer &pm)
224
{
225 226
	po.ClearMixer(pm);
}
227

228 229
inline void
PulseOutput::SetVolume(const pa_cvolume &volume)
230 231
{
	if (context == nullptr || stream == nullptr ||
232 233
	    pa_stream_get_state(stream) != PA_STREAM_READY)
		throw std::runtime_error("disconnected");
234

235 236 237 238
	pa_operation *o =
		pa_context_set_sink_input_volume(context,
						 pa_stream_get_index(stream),
						 &volume, nullptr, nullptr);
239 240
	if (o == nullptr)
		throw std::runtime_error("failed to set PulseAudio volume");
241 242 243 244

	pa_operation_unref(o);
}

245 246
void
pulse_output_set_volume(PulseOutput &po, const pa_cvolume *volume)
247
{
248
	return po.SetVolume(*volume);
249 250
}

251 252 253 254 255 256 257 258 259 260 261
/**
 * \brief waits for a pulseaudio operation to finish, frees it and
 *     unlocks the mainloop
 * \param operation the operation to wait for
 * \return true if operation has finished normally (DONE state),
 *     false otherwise
 */
static bool
pulse_wait_for_operation(struct pa_threaded_mainloop *mainloop,
			 struct pa_operation *operation)
{
262 263
	assert(mainloop != nullptr);
	assert(operation != nullptr);
264

265 266 267
	pa_operation_state_t state;
	while ((state = pa_operation_get_state(operation))
	       == PA_OPERATION_RUNNING)
268 269 270 271 272 273 274 275 276 277 278 279
		pa_threaded_mainloop_wait(mainloop);

	pa_operation_unref(operation);

	return state == PA_OPERATION_DONE;
}

/**
 * Callback function for stream operation.  It just sends a signal to
 * the caller thread, to wake pulse_wait_for_operation() up.
 */
static void
Rosen Penev's avatar
Rosen Penev committed
280 281
pulse_output_stream_success_cb([[maybe_unused]] pa_stream *s,
			       [[maybe_unused]] int success, void *userdata)
282
{
283
	PulseOutput &po = *(PulseOutput *)userdata;
284

285
	po.OnStreamSuccess();
286 287
}

288 289
inline void
PulseOutput::OnContextStateChanged(pa_context_state_t new_state)
290
{
291
	switch (new_state) {
292
	case PA_CONTEXT_READY:
293 294
		if (mixer != nullptr)
			pulse_mixer_on_connect(*mixer, context);
295

296
		Signal();
297 298
		break;

299 300
	case PA_CONTEXT_TERMINATED:
	case PA_CONTEXT_FAILED:
301 302
		if (mixer != nullptr)
			pulse_mixer_on_disconnect(*mixer);
303

304 305
		/* the caller thread might be waiting for these
		   states */
306
		Signal();
307 308 309 310 311 312 313 314 315 316
		break;

	case PA_CONTEXT_UNCONNECTED:
	case PA_CONTEXT_CONNECTING:
	case PA_CONTEXT_AUTHORIZING:
	case PA_CONTEXT_SETTING_NAME:
		break;
	}
}

317
static void
318 319 320 321 322 323 324 325 326 327
pulse_output_context_state_cb(struct pa_context *context, void *userdata)
{
	PulseOutput &po = *(PulseOutput *)userdata;

	po.OnContextStateChanged(pa_context_get_state(context));
}

inline void
PulseOutput::OnServerLayoutChanged(pa_subscription_event_type_t t,
				   uint32_t idx)
328
{
Max Kellermann's avatar
Max Kellermann committed
329
	auto facility =
330
		pa_subscription_event_type_t(t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK);
Max Kellermann's avatar
Max Kellermann committed
331
	auto type =
332
		pa_subscription_event_type_t(t & PA_SUBSCRIPTION_EVENT_TYPE_MASK);
333

334
	if (mixer != nullptr &&
335
	    facility == PA_SUBSCRIPTION_EVENT_SINK_INPUT &&
336 337 338
	    stream != nullptr &&
	    pa_stream_get_state(stream) == PA_STREAM_READY &&
	    idx == pa_stream_get_index(stream) &&
339 340
	    (type == PA_SUBSCRIPTION_EVENT_NEW ||
	     type == PA_SUBSCRIPTION_EVENT_CHANGE))
341 342 343 344
		pulse_mixer_on_change(*mixer, context, stream);
}

static void
Rosen Penev's avatar
Rosen Penev committed
345
pulse_output_subscribe_cb([[maybe_unused]] pa_context *context,
346 347 348 349 350 351
			  pa_subscription_event_type_t t,
			  uint32_t idx, void *userdata)
{
	PulseOutput &po = *(PulseOutput *)userdata;

	po.OnServerLayoutChanged(t, idx);
352 353
}

354 355
inline void
PulseOutput::Connect()
356
{
357
	assert(context != nullptr);
358

359
	if (pa_context_connect(context, server,
360 361 362
			       (pa_context_flags_t)0, nullptr) < 0)
		throw MakePulseError(context,
				     "pa_context_connect() has failed");
363
}
364

365 366
void
PulseOutput::DeleteStream()
367
{
368
	assert(stream != nullptr);
369

370
	pa_stream_set_suspended_callback(stream, nullptr, nullptr);
371

372 373
	pa_stream_set_state_callback(stream, nullptr, nullptr);
	pa_stream_set_write_callback(stream, nullptr, nullptr);
374

375 376 377
	pa_stream_disconnect(stream);
	pa_stream_unref(stream);
	stream = nullptr;
378 379
}

380 381
void
PulseOutput::DeleteContext()
382
{
383
	assert(context != nullptr);
384

385 386
	pa_context_set_state_callback(context, nullptr, nullptr);
	pa_context_set_subscribe_callback(context, nullptr, nullptr);
387

388 389 390
	pa_context_disconnect(context);
	pa_context_unref(context);
	context = nullptr;
391 392
}

393 394
void
PulseOutput::SetupContext()
395
{
396
	assert(mainloop != nullptr);
397

398 399 400 401 402 403 404 405 406 407
	pa_proplist *proplist = pa_proplist_new();
	if (media_role)
		pa_proplist_sets(proplist, PA_PROP_MEDIA_ROLE, media_role);

	context = pa_context_new_with_proplist(pa_threaded_mainloop_get_api(mainloop),
					       MPD_PULSE_NAME,
					       proplist);

	pa_proplist_free(proplist);

408 409
	if (context == nullptr)
		throw std::runtime_error("pa_context_new() has failed");
410

411 412 413 414
	pa_context_set_state_callback(context,
				      pulse_output_context_state_cb, this);
	pa_context_set_subscribe_callback(context,
					  pulse_output_subscribe_cb, this);
415

416 417 418
	try {
		Connect();
	} catch (...) {
419
		DeleteContext();
420
		throw;
421
	}
422 423
}

424
void
425
PulseOutput::Enable()
426
{
427
	assert(mainloop == nullptr);
428

429 430
	/* create the libpulse mainloop and start the thread */

431
	mainloop = pa_threaded_mainloop_new();
432 433
	if (mainloop == nullptr)
		throw std::runtime_error("pa_threaded_mainloop_new() has failed");
434

435
	pa_threaded_mainloop_lock(mainloop);
436

437 438 439 440
	if (pa_threaded_mainloop_start(mainloop) < 0) {
		pa_threaded_mainloop_unlock(mainloop);
		pa_threaded_mainloop_free(mainloop);
		mainloop = nullptr;
441

442
		throw std::runtime_error("pa_threaded_mainloop_start() has failed");
443 444 445 446
	}

	/* create the libpulse context and connect it */

447 448 449
	try {
		SetupContext();
	} catch (...) {
450 451 452 453
		pa_threaded_mainloop_unlock(mainloop);
		pa_threaded_mainloop_stop(mainloop);
		pa_threaded_mainloop_free(mainloop);
		mainloop = nullptr;
454
		throw;
455 456
	}

457
	pa_threaded_mainloop_unlock(mainloop);
458 459
}

460 461
void
PulseOutput::Disable() noexcept
462
{
463
	assert(mainloop != nullptr);
464

465 466 467 468 469
	pa_threaded_mainloop_stop(mainloop);
	if (context != nullptr)
		DeleteContext();
	pa_threaded_mainloop_free(mainloop);
	mainloop = nullptr;
470 471
}

472 473
void
PulseOutput::WaitConnection()
474
{
475
	assert(mainloop != nullptr);
476

477
	pa_context_state_t state;
478

479 480
	if (context == nullptr)
		SetupContext();
481 482

	while (true) {
483
		state = pa_context_get_state(context);
484 485 486
		switch (state) {
		case PA_CONTEXT_READY:
			/* nothing to do */
487
			return;
488 489 490 491 492

		case PA_CONTEXT_UNCONNECTED:
		case PA_CONTEXT_TERMINATED:
		case PA_CONTEXT_FAILED:
			/* failure */
493 494 495 496 497 498
			{
				auto e = MakePulseError(context,
							"failed to connect");
				DeleteContext();
				throw e;
			}
499 500 501 502 503

		case PA_CONTEXT_CONNECTING:
		case PA_CONTEXT_AUTHORIZING:
		case PA_CONTEXT_SETTING_NAME:
			/* wait some more */
504
			pa_threaded_mainloop_wait(mainloop);
505 506
			break;
		}
507
	}
508
}
509

510
inline void
Rosen Penev's avatar
Rosen Penev committed
511
PulseOutput::OnStreamSuspended([[maybe_unused]] pa_stream *_stream)
512
{
513 514
	assert(_stream == stream || stream == nullptr);
	assert(mainloop != nullptr);
515 516 517

	/* wake up the main loop to break out of the loop in
	   pulse_output_play() */
518
	Signal();
519 520
}

521
static void
522
pulse_output_stream_suspended_cb(pa_stream *stream, void *userdata)
523
{
524
	PulseOutput &po = *(PulseOutput *)userdata;
525

526 527 528 529 530 531 532 533 534 535
	po.OnStreamSuspended(stream);
}

inline void
PulseOutput::OnStreamStateChanged(pa_stream *_stream,
				  pa_stream_state_t new_state)
{
	assert(_stream == stream || stream == nullptr);
	assert(mainloop != nullptr);
	assert(context != nullptr);
536

537
	switch (new_state) {
538
	case PA_STREAM_READY:
539 540
		if (mixer != nullptr)
			pulse_mixer_on_change(*mixer, context, _stream);
541

542
		Signal();
543 544
		break;

545 546
	case PA_STREAM_FAILED:
	case PA_STREAM_TERMINATED:
547 548
		if (mixer != nullptr)
			pulse_mixer_on_disconnect(*mixer);
549

550
		Signal();
551 552 553 554 555 556 557
		break;

	case PA_STREAM_UNCONNECTED:
	case PA_STREAM_CREATING:
		break;
	}
}
558

559 560 561 562 563 564 565 566 567 568 569 570 571 572
static void
pulse_output_stream_state_cb(pa_stream *stream, void *userdata)
{
	PulseOutput &po = *(PulseOutput *)userdata;

	return po.OnStreamStateChanged(stream, pa_stream_get_state(stream));
}

inline void
PulseOutput::OnStreamWrite(size_t nbytes)
{
	assert(mainloop != nullptr);

	writable = nbytes;
573
	Signal();
574 575
}

576
static void
Rosen Penev's avatar
Rosen Penev committed
577
pulse_output_stream_write_cb([[maybe_unused]] pa_stream *stream, size_t nbytes,
578 579
			     void *userdata)
{
580
	PulseOutput &po = *(PulseOutput *)userdata;
581

582
	return po.OnStreamWrite(nbytes);
583 584
}

585 586
inline void
PulseOutput::SetupStream(const pa_sample_spec &ss)
587
{
588
	assert(context != nullptr);
589

590 591
	/* WAVE-EX is been adopted as the speaker map for most media files */
	pa_channel_map chan_map;
592 593
	pa_channel_map_init_extend(&chan_map, ss.channels,
				   PA_CHANNEL_MAP_WAVEEX);
594
	stream = pa_stream_new(context, name, &ss, &chan_map);
595 596 597
	if (stream == nullptr)
		throw MakePulseError(context,
				     "pa_stream_new() has failed");
598

599 600 601
	pa_stream_set_suspended_callback(stream,
					 pulse_output_stream_suspended_cb,
					 this);
602

603 604 605 606
	pa_stream_set_state_callback(stream,
				     pulse_output_stream_state_cb, this);
	pa_stream_set_write_callback(stream,
				     pulse_output_stream_write_cb, this);
607 608
}

609
void
610
PulseOutput::Open(AudioFormat &audio_format)
611
{
612
	assert(mainloop != nullptr);
613

614
	Pulse::LockGuard lock(mainloop);
615

616 617
	if (context != nullptr) {
		switch (pa_context_get_state(context)) {
618 619 620 621 622 623
		case PA_CONTEXT_UNCONNECTED:
		case PA_CONTEXT_TERMINATED:
		case PA_CONTEXT_FAILED:
			/* the connection was closed meanwhile; delete
			   it, and pulse_output_wait_connection() will
			   reopen it */
624
			DeleteContext();
625 626 627 628 629 630 631 632 633 634
			break;

		case PA_CONTEXT_READY:
		case PA_CONTEXT_CONNECTING:
		case PA_CONTEXT_AUTHORIZING:
		case PA_CONTEXT_SETTING_NAME:
			break;
		}
	}

635
	WaitConnection();
636

637 638
	/* Use the sample formats that our version of PulseAudio and MPD
	   have in common, otherwise force MPD to send 16 bit */
639

640
	pa_sample_spec ss;
641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660

	switch (audio_format.format) {
	case SampleFormat::FLOAT:
		ss.format = PA_SAMPLE_FLOAT32NE;
		break;
	case SampleFormat::S32:
		ss.format = PA_SAMPLE_S32NE;
		break;
	case SampleFormat::S24_P32:
		ss.format = PA_SAMPLE_S24_32NE;
		break;
	case SampleFormat::S16:
		ss.format = PA_SAMPLE_S16NE;
		break;
	default:
		audio_format.format = SampleFormat::S16;
		ss.format = PA_SAMPLE_S16NE;
		break;
	}

661
	ss.rate = std::min(audio_format.sample_rate, PA_RATE_MAX);
662
	ss.channels = audio_format.channels;
663

664 665
	/* create a stream .. */

666
	SetupStream(ss);
667 668 669

	/* .. and connect it (asynchronously) */

670
	if (pa_stream_connect_playback(stream, sink,
671 672
				       nullptr, pa_stream_flags_t(0),
				       nullptr, nullptr) < 0) {
673
		DeleteStream();
674

675 676
		throw MakePulseError(context,
				     "pa_stream_connect_playback() has failed");
677
	}
678 679

	pause = false;
680 681
}

682 683
void
PulseOutput::Close() noexcept
684
{
685
	assert(mainloop != nullptr);
686

687
	Pulse::LockGuard lock(mainloop);
688

689 690 691 692
	if (pa_stream_get_state(stream) == PA_STREAM_READY) {
		pa_operation *o =
			pa_stream_drain(stream,
					pulse_output_stream_success_cb, this);
693
		if (o == nullptr) {
694
			LogPulseError(context,
695
				      "pa_stream_drain() has failed");
696
		} else
697
			pulse_wait_for_operation(mainloop, o);
698 699
	}

700
	DeleteStream();
701

702 703 704
	if (context != nullptr &&
	    pa_context_get_state(context) != PA_CONTEXT_READY)
		DeleteContext();
705 706
}

707 708
void
PulseOutput::WaitStream()
709
{
710
	while (true) {
711
		switch (pa_stream_get_state(stream)) {
712
		case PA_STREAM_READY:
713
			return;
714

715 716 717
		case PA_STREAM_FAILED:
		case PA_STREAM_TERMINATED:
		case PA_STREAM_UNCONNECTED:
718 719
			throw MakePulseError(context,
					     "failed to connect the stream");
720

721
		case PA_STREAM_CREATING:
722
			pa_threaded_mainloop_wait(mainloop);
723 724
			break;
		}
725
	}
726 727
}

728
void
729
PulseOutput::StreamPause(bool _pause)
730
{
731 732 733
	assert(mainloop != nullptr);
	assert(context != nullptr);
	assert(stream != nullptr);
734

735
	pa_operation *o = pa_stream_cork(stream, _pause,
736
					 pulse_output_stream_success_cb, this);
737 738 739
	if (o == nullptr)
		throw MakePulseError(context,
				     "pa_stream_cork() has failed");
740

741 742 743
	if (!pulse_wait_for_operation(mainloop, o))
		throw MakePulseError(context,
				     "pa_stream_cork() has failed");
744 745
}

746 747
std::chrono::steady_clock::duration
PulseOutput::Delay() const noexcept
748
{
749
	Pulse::LockGuard lock(mainloop);
750

751
	auto result = std::chrono::steady_clock::duration::zero();
752
	if (pause && pa_stream_is_corked(stream) &&
753
	    pa_stream_get_state(stream) == PA_STREAM_READY)
754
		/* idle while paused */
755
		result = std::chrono::seconds(1);
756 757 758 759

	return result;
}

760
size_t
761
PulseOutput::Play(const void *chunk, size_t size)
762
{
763 764
	assert(mainloop != nullptr);
	assert(stream != nullptr);
765

766
	Pulse::LockGuard lock(mainloop);
767

768 769
	pause = false;

770 771
	/* check if the stream is (already) connected */

772
	WaitStream();
773

774
	assert(context != nullptr);
775 776 777

	/* unpause if previously paused */

778 779
	if (pa_stream_is_corked(stream))
		StreamPause(false);
780 781 782

	/* wait until the server allows us to write */

783
	while (writable == 0) {
784 785
		if (pa_stream_is_suspended(stream))
			throw std::runtime_error("suspended");
786

787
		pa_threaded_mainloop_wait(mainloop);
788

789 790
		if (pa_stream_get_state(stream) != PA_STREAM_READY)
			throw std::runtime_error("disconnected");
791 792 793 794
	}

	/* now write */

795
	if (size > writable)
796
		/* don't send more than possible */
797
		size = writable;
798

799
	writable -= size;
800

801
	int result = pa_stream_write(stream, chunk, size, nullptr,
802
				     0, PA_SEEK_RELATIVE);
803 804
	if (result < 0)
		throw MakePulseError(context, "pa_stream_write() failed");
805

806
	return size;
807 808
}

809 810
void
PulseOutput::Cancel() noexcept
811
{
812 813
	assert(mainloop != nullptr);
	assert(stream != nullptr);
814

815
	Pulse::LockGuard lock(mainloop);
816

817
	if (pa_stream_get_state(stream) != PA_STREAM_READY) {
818 819 820 821 822
		/* no need to flush when the stream isn't connected
		   yet */
		return;
	}

823
	assert(context != nullptr);
824

825 826 827
	pa_operation *o = pa_stream_flush(stream,
					  pulse_output_stream_success_cb,
					  this);
828
	if (o == nullptr) {
829
		LogPulseError(context, "pa_stream_flush() has failed");
830 831 832
		return;
	}

833
	pulse_wait_for_operation(mainloop, o);
834 835
}

836
bool
837
PulseOutput::Pause()
838
{
839 840
	assert(mainloop != nullptr);
	assert(stream != nullptr);
841

842
	Pulse::LockGuard lock(mainloop);
843

844 845
	pause = true;

846 847
	/* check if the stream is (already/still) connected */

848
	WaitStream();
849

850
	assert(context != nullptr);
851 852 853

	/* cork the stream */

854 855
	if (!pa_stream_is_corked(stream))
		StreamPause(true);
856 857 858 859

	return true;
}

860 861
inline bool
PulseOutput::TestDefaultDevice()
862
try {
863
	const ConfigBlock empty;
864
	PulseOutput po(empty);
865 866
	po.Enable();
	AtScopeExit(&po) { po.Disable(); };
867
	po.WaitConnection();
868

869
	return true;
870
} catch (...) {
871
	return false;
872 873
}

874
static bool
875
pulse_output_test_default_device()
876 877 878 879
{
	return PulseOutput::TestDefaultDevice();
}

880
const struct AudioOutputPlugin pulse_output_plugin = {
881 882
	"pulse",
	pulse_output_test_default_device,
883
	PulseOutput::Create,
884
	&pulse_mixer_plugin,
885
};