MythTV master
audiooutputpulse.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2008 Alan Calvert, 2010 foobum@gmail.com
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 * 02110-1301, USA.
18 */
19
20#include "audiooutputpulse.h"
21
22// QT headers
23#include <QString>
24
25// C++ headers
26#include <algorithm>
27
30
31#define LOC QString("PulseAudio: ")
32
33static constexpr int8_t PULSE_MAX_CHANNELS { 8 };
34
36 AudioOutputBase(settings)
37{
38 m_volumeControl.channels = 0;
39 for (uint & value : m_volumeControl.values)
40 value = PA_VOLUME_MUTED;
41
42 InitSettings(settings);
43 if (settings.m_init)
44 Reconfigure(settings);
45}
46
48{
49 KillAudio();
50 if (m_pcontext)
51 {
52 pa_context_unref(m_pcontext);
53 m_pcontext = nullptr;
54 }
55 if (m_ctproplist)
56 {
57 pa_proplist_free(m_ctproplist);
58 m_ctproplist = nullptr;
59 }
60 if (m_stproplist)
61 {
62 pa_proplist_free(m_stproplist);
63 m_stproplist = nullptr;
64 }
65}
66
68{
70 QString fn_log_tag = "OpenDevice, ";
71
72 /* Start the mainloop and connect a context so we can retrieve the
73 parameters of the default sink */
74 m_mainloop = pa_threaded_mainloop_new();
75 if (!m_mainloop)
76 {
77 LOG(VB_GENERAL, LOG_ERR, LOC + fn_log_tag + "Failed to get new threaded mainloop");
78 delete m_aoSettings;
79 return nullptr;
80 }
81
82 pa_threaded_mainloop_start(m_mainloop);
83 pa_threaded_mainloop_lock(m_mainloop);
84
85 if (!ContextConnect())
86 {
87 pa_threaded_mainloop_unlock(m_mainloop);
88 pa_threaded_mainloop_stop(m_mainloop);
89 pa_threaded_mainloop_free(m_mainloop);
90 m_mainloop = nullptr;
91 if (m_ctproplist)
92 {
93 pa_proplist_free(m_ctproplist);
94 m_ctproplist = nullptr;
95 }
96 if (m_stproplist)
97 {
98 pa_proplist_free(m_stproplist);
99 m_stproplist = nullptr;
100 }
101 delete m_aoSettings;
102 return nullptr;
103 }
104
105 /* Get the samplerate and channel count of the default sink, supported rate
106 and channels are added in SinkInfoCallback */
107 /* We should in theory be able to feed pulse any samplerate but allowing it
108 to resample results in weird behaviour (odd channel maps, static) post
109 pause / reset */
110 pa_operation *op = pa_context_get_sink_info_by_index(m_pcontext, 0,
112 this);
113 if (op)
114 {
115 pa_operation_unref(op);
116 pa_threaded_mainloop_wait(m_mainloop);
117 }
118 else
119 {
120 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to determine default sink samplerate");
121 }
122
123 pa_threaded_mainloop_unlock(m_mainloop);
124
125 // All formats except S24 (pulse wants S24LSB)
127 while ((fmt = m_aoSettings->GetNextFormat()))
128 {
129 if (fmt == FORMAT_S24
130// define from PA 0.9.15 only
131#ifndef PA_MAJOR
132 || fmt == FORMAT_S24LSB
133#endif
134 )
135 continue;
137 }
138
139 pa_context_disconnect(m_pcontext);
140 pa_context_unref(m_pcontext);
141 m_pcontext = nullptr;
142 pa_threaded_mainloop_stop(m_mainloop);
143 pa_threaded_mainloop_free(m_mainloop);
144 m_mainloop = nullptr;
145 if (m_ctproplist)
146 {
147 pa_proplist_free(m_ctproplist);
148 m_ctproplist = nullptr;
149 }
150 if (m_stproplist)
151 {
152 pa_proplist_free(m_stproplist);
153 m_stproplist = nullptr;
154 }
155
156 return m_aoSettings;
157}
158
160{
161 QString fn_log_tag = "OpenDevice, ";
163 {
164 LOG(VB_GENERAL, LOG_ERR, LOC + fn_log_tag + QString("audio channel limit %1, but %2 requested")
165 .arg(PULSE_MAX_CHANNELS).arg(m_channels));
166 return false;
167 }
168
170 m_sampleSpec.channels = m_volumeControl.channels = m_channels;
171 switch (m_outputFormat)
172 {
173 case FORMAT_U8: m_sampleSpec.format = PA_SAMPLE_U8; break;
174 case FORMAT_S16: m_sampleSpec.format = PA_SAMPLE_S16NE; break;
175// define from PA 0.9.15 only
176#ifdef PA_MAJOR
177 case FORMAT_S24LSB: m_sampleSpec.format = PA_SAMPLE_S24_32NE; break;
178#endif
179 case FORMAT_S32: m_sampleSpec.format = PA_SAMPLE_S32NE; break;
180 case FORMAT_FLT: m_sampleSpec.format = PA_SAMPLE_FLOAT32NE; break;
181 default:
182 LOG(VB_GENERAL, LOG_ERR, LOC + fn_log_tag + QString("unsupported sample format %1")
183 .arg(m_outputFormat));
184 return false;
185 }
186
187 if (!pa_sample_spec_valid(&m_sampleSpec))
188 {
189 LOG(VB_GENERAL, LOG_ERR, LOC + fn_log_tag + "invalid sample spec");
190 return false;
191 }
192 std::string spec(PA_SAMPLE_SPEC_SNPRINT_MAX,'\0');
193 pa_sample_spec_snprint(spec.data(), spec.size(), &m_sampleSpec);
194 LOG(VB_AUDIO, LOG_INFO, LOC + fn_log_tag + "using sample spec " + spec.data());
195
196 if(!pa_channel_map_init_auto(&m_channelMap, m_channels, PA_CHANNEL_MAP_WAVEEX))
197 {
198 LOG(VB_GENERAL, LOG_ERR, LOC + fn_log_tag + "failed to init channel map");
199 return false;
200 }
201
202 m_mainloop = pa_threaded_mainloop_new();
203 if (!m_mainloop)
204 {
205 LOG(VB_GENERAL, LOG_ERR, LOC + fn_log_tag + "failed to get new threaded mainloop");
206 return false;
207 }
208
209 pa_threaded_mainloop_start(m_mainloop);
210 pa_threaded_mainloop_lock(m_mainloop);
211
212 if (!ContextConnect())
213 {
214 pa_threaded_mainloop_unlock(m_mainloop);
215 pa_threaded_mainloop_stop(m_mainloop);
216 pa_threaded_mainloop_free(m_mainloop);
217 m_mainloop = nullptr;
218 if (m_ctproplist)
219 {
220 pa_proplist_free(m_ctproplist);
221 m_ctproplist = nullptr;
222 }
223 if (m_stproplist)
224 {
225 pa_proplist_free(m_stproplist);
226 m_stproplist = nullptr;
227 }
228 return false;
229 }
230
232 {
233 pa_threaded_mainloop_unlock(m_mainloop);
234 pa_threaded_mainloop_stop(m_mainloop);
235 pa_threaded_mainloop_free(m_mainloop);
236 m_mainloop = nullptr;
237 if (m_ctproplist)
238 {
239 pa_proplist_free(m_ctproplist);
240 m_ctproplist = nullptr;
241 }
242 if (m_stproplist)
243 {
244 pa_proplist_free(m_stproplist);
245 m_stproplist = nullptr;
246 }
247 return false;
248 }
249
250 pa_threaded_mainloop_unlock(m_mainloop);
251 return true;
252}
253
255{
256 if (m_mainloop)
257 pa_threaded_mainloop_lock(m_mainloop);
258
259 if (m_pstream)
260 {
261 FlushStream("CloseDevice");
262 pa_stream_disconnect(m_pstream);
263 pa_stream_unref(m_pstream);
264 m_pstream = nullptr;
265 }
266
267 if (m_pcontext)
268 {
269 if (m_mainloop)
270 {
271 pa_operation *op = pa_context_drain(m_pcontext, ContextDrainCallback, m_mainloop);
272 if (op)
273 {
274 // Wait for the asynchronous draining to complete
275 while (pa_operation_get_state(op) == PA_OPERATION_RUNNING)
276 {
277 pa_threaded_mainloop_wait(m_mainloop);
278 }
279 pa_operation_unref(op);
280 }
281 }
282 pa_context_disconnect(m_pcontext);
283 pa_context_unref(m_pcontext);
284 m_pcontext = nullptr;
285 }
286
287 if (m_mainloop)
288 {
289 pa_threaded_mainloop_unlock(m_mainloop);
290 pa_threaded_mainloop_stop(m_mainloop);
291 pa_threaded_mainloop_free(m_mainloop);
292 m_mainloop = nullptr;
293 }
294
295 if (m_ctproplist)
296 {
297 pa_proplist_free(m_ctproplist);
298 m_ctproplist = nullptr;
299 }
300 if (m_stproplist)
301 {
302 pa_proplist_free(m_stproplist);
303 m_stproplist = nullptr;
304 }
305}
306
307void AudioOutputPulseAudio::WriteAudio(uchar *aubuf, int size)
308{
309 QString fn_log_tag = "WriteAudio, ";
310 pa_stream_state_t sstate = pa_stream_get_state(m_pstream);
311
312 LOG(VB_AUDIO | VB_TIMESTAMP, LOG_INFO, LOC + fn_log_tag + QString("writing %1 bytes").arg(size));
313
314 /* NB This "if" check can be replaced with PA_STREAM_IS_GOOD() in
315 PulseAudio API from 0.9.11. As 0.9.10 is still widely used
316 we use the more verbose version for now */
317
318 if (sstate == PA_STREAM_CREATING || sstate == PA_STREAM_READY)
319 {
320 int write_status = PA_ERR_INVALID;
321 size_t to_write = size;
322 unsigned char *buf_ptr = aubuf;
323
324 pa_threaded_mainloop_lock(m_mainloop);
325 while (to_write > 0)
326 {
327 write_status = 0;
328 size_t writable = pa_stream_writable_size(m_pstream);
329 if (writable > 0)
330 {
331 size_t write = std::min(to_write, writable);
332 write_status = pa_stream_write(m_pstream, buf_ptr, write,
333 nullptr, 0, PA_SEEK_RELATIVE);
334
335 if (0 != write_status)
336 break;
337
338 buf_ptr += write;
339 to_write -= write;
340 }
341 else
342 {
343 pa_threaded_mainloop_wait(m_mainloop);
344 }
345 }
346 pa_threaded_mainloop_unlock(m_mainloop);
347
348 if (to_write > 0)
349 {
350 if (write_status != 0)
351 {
352 LOG(VB_GENERAL, LOG_ERR, LOC + fn_log_tag + QString("stream write failed: %1")
353 .arg(write_status == PA_ERR_BADSTATE
354 ? "PA_ERR_BADSTATE"
355 : "PA_ERR_INVALID"));
356 }
357
358 LOG(VB_GENERAL, LOG_ERR, LOC + fn_log_tag + QString("short write, %1 of %2")
359 .arg(size - to_write).arg(size));
360 }
361 }
362 else
363 {
364 LOG(VB_GENERAL, LOG_ERR, LOC + fn_log_tag + QString("stream state not good: %1")
365 .arg(sstate,0,16));
366 }
367}
368
370{
371 pa_usec_t latency = 0;
372 size_t buffered = 0;
373
374 if (!m_pcontext || pa_context_get_state(m_pcontext) != PA_CONTEXT_READY)
375 return 0;
376
377 if (!m_pstream || pa_stream_get_state(m_pstream) != PA_STREAM_READY)
378 return 0;
379
380 const pa_buffer_attr *buf_attr = pa_stream_get_buffer_attr(m_pstream);
381 size_t bfree = pa_stream_writable_size(m_pstream);
382 buffered = buf_attr->tlength - bfree;
383
384 pa_threaded_mainloop_lock(m_mainloop);
385
386 while (pa_stream_get_latency(m_pstream, &latency, nullptr) < 0)
387 {
388 if (pa_context_errno(m_pcontext) != PA_ERR_NODATA)
389 {
390 latency = 0;
391 break;
392 }
393 pa_threaded_mainloop_wait(m_mainloop);
394 }
395
396 pa_threaded_mainloop_unlock(m_mainloop);
397
398 return (latency * m_sampleRate *
399 m_outputBytesPerFrame / 1000000) + buffered;
400}
401
403{
404 return (float)m_volumeControl.values[channel] /
405 (float)PA_VOLUME_NORM * 100.0F;
406}
407
408void AudioOutputPulseAudio::SetVolumeChannel(int channel, int volume)
409{
410 QString fn_log_tag = "SetVolumeChannel, ";
411
412 if (channel < 0 || channel > PULSE_MAX_CHANNELS || volume < 0)
413 {
414 LOG(VB_GENERAL, LOG_ERR, LOC + fn_log_tag + QString("bad volume params, channel %1, volume %2")
415 .arg(channel).arg(volume));
416 return;
417 }
418
419 m_volumeControl.values[channel] =
420 (float)volume / 100.0F * (float)PA_VOLUME_NORM;
421
422// FIXME: This code did nothing at all so has been commented out for now
423// until it's decided whether it was ever required
424// volume = std::clamp(volume, 0, 100);
425
426 if (gCoreContext->GetSetting("MixerControl", "PCM").toLower() == "pcm")
427 {
428 uint32_t stream_index = pa_stream_get_index(m_pstream);
429 pa_threaded_mainloop_lock(m_mainloop);
430 pa_operation *op =
431 pa_context_set_sink_input_volume(m_pcontext, stream_index,
434 pa_threaded_mainloop_unlock(m_mainloop);
435 if (op)
436 pa_operation_unref(op);
437 else
438 {
439 LOG(VB_GENERAL, LOG_ERR, LOC + fn_log_tag +
440 QString("set stream volume operation failed, stream %1, "
441 "error %2 ")
442 .arg(stream_index)
443 .arg(pa_strerror(pa_context_errno(m_pcontext))));
444 }
445 }
446 else
447 {
448 uint32_t sink_index = pa_stream_get_device_index(m_pstream);
449 pa_threaded_mainloop_lock(m_mainloop);
450 pa_operation *op =
451 pa_context_set_sink_volume_by_index(m_pcontext, sink_index,
454 pa_threaded_mainloop_unlock(m_mainloop);
455 if (op)
456 pa_operation_unref(op);
457 else
458 {
459 LOG(VB_GENERAL, LOG_ERR, LOC + fn_log_tag +
460 QString("set sink volume operation failed, sink %1, "
461 "error %2 ")
462 .arg(sink_index)
463 .arg(pa_strerror(pa_context_errno(m_pcontext))));
464 }
465 }
466}
467
469{
471 pa_threaded_mainloop_lock(m_mainloop);
472 pa_operation *op = pa_stream_drain(m_pstream, nullptr, this);
473 pa_threaded_mainloop_unlock(m_mainloop);
474
475 if (op)
476 pa_operation_unref(op);
477 else
478 LOG(VB_GENERAL, LOG_ERR, LOC + "Drain, stream drain failed");
479}
480
482{
483 QString fn_log_tag = "ContextConnect, ";
484 if (m_pcontext)
485 {
486 LOG(VB_GENERAL, LOG_ERR, LOC + fn_log_tag + "context appears to exist, but shouldn't (yet)");
487 pa_context_unref(m_pcontext);
488 m_pcontext = nullptr;
489 return false;
490 }
491 m_ctproplist = pa_proplist_new();
492 if (!m_ctproplist)
493 {
494 LOG(VB_GENERAL, LOG_ERR, LOC + fn_log_tag + QString("failed to create new proplist"));
495 return false;
496 }
497 pa_proplist_sets(m_ctproplist, PA_PROP_APPLICATION_NAME, "MythTV");
498 pa_proplist_sets(m_ctproplist, PA_PROP_APPLICATION_ICON_NAME, "mythtv");
499 pa_proplist_sets(m_ctproplist, PA_PROP_MEDIA_ROLE, "video");
500 m_pcontext =
501 pa_context_new_with_proplist(pa_threaded_mainloop_get_api(m_mainloop),
502 "MythTV", m_ctproplist);
503 if (!m_pcontext)
504 {
505 LOG(VB_GENERAL, LOG_ERR, LOC + fn_log_tag + "failed to acquire new context");
506 pa_proplist_free(m_ctproplist);
507 m_ctproplist = nullptr;
508 return false;
509 }
510 pa_context_set_state_callback(m_pcontext, ContextStateCallback, this);
511
512 QString pulse_host = ChooseHost();
513 int chk = pa_context_connect(m_pcontext,
514 !pulse_host.isEmpty() ? qPrintable(pulse_host) : nullptr,
515 (pa_context_flags_t)0, nullptr);
516
517 if (chk < 0)
518 {
519 LOG(VB_GENERAL, LOG_ERR, LOC + fn_log_tag + QString("context connect failed: %1")
520 .arg(pa_strerror(pa_context_errno(m_pcontext))));
521 pa_proplist_free(m_ctproplist);
522 m_ctproplist = nullptr;
523 return false;
524 }
525 bool connected = false;
526 pa_context_state_t state = pa_context_get_state(m_pcontext);
527 for (; !connected; state = pa_context_get_state(m_pcontext))
528 {
529 switch(state)
530 {
531 case PA_CONTEXT_READY:
532 LOG(VB_AUDIO, LOG_INFO, LOC + fn_log_tag +"context connection ready");
533 connected = true;
534 continue;
535
536 case PA_CONTEXT_FAILED:
537 case PA_CONTEXT_TERMINATED:
538 LOG(VB_GENERAL, LOG_ERR, LOC + fn_log_tag +
539 QString("context connection failed or terminated: %1")
540 .arg(pa_strerror(pa_context_errno(m_pcontext))));
541 pa_proplist_free(m_ctproplist);
542 m_ctproplist = nullptr;
543 return false;
544
545 default:
546 LOG(VB_AUDIO, LOG_INFO, LOC + fn_log_tag + "waiting for context connection ready");
547 pa_threaded_mainloop_wait(m_mainloop);
548 break;
549 }
550 }
551
552 pa_operation *op =
553 pa_context_get_server_info(m_pcontext, ServerInfoCallback, this);
554
555 if (op)
556 pa_operation_unref(op);
557 else
558 LOG(VB_GENERAL, LOG_ERR, LOC + fn_log_tag + "failed to get PulseAudio server info");
559
560 return true;
561}
562
564{
565 QString fn_log_tag = "ChooseHost, ";
566 QStringList parts = m_mainDevice.split(':');
567 QString host = parts.size() > 1 ? parts[1] : QString();
568 QString pulse_host;
569
570 if (host != "default")
571 pulse_host = host;
572
573 if (pulse_host.isEmpty() && host != "default")
574 {
575 QString env_pulse_host = qEnvironmentVariable("PULSE_SERVER");
576 if (!env_pulse_host.isEmpty())
577 pulse_host = env_pulse_host;
578 }
579
580 LOG(VB_AUDIO, LOG_INFO, LOC + fn_log_tag + QString("chosen PulseAudio server: %1")
581 .arg((pulse_host != nullptr) ? pulse_host : "default"));
582
583 return pulse_host;
584}
585
587{
588 QString fn_log_tag = "ConnectPlaybackStream, ";
589 m_stproplist = pa_proplist_new();
590 if (!m_stproplist)
591 {
592 LOG(VB_GENERAL, LOG_ERR, LOC + fn_log_tag + QString("failed to create new proplist"));
593 return false;
594 }
595 pa_proplist_sets(m_stproplist, PA_PROP_MEDIA_ROLE, "video");
596 m_pstream =
597 pa_stream_new_with_proplist(m_pcontext, "MythTV playback", &m_sampleSpec,
599 if (!m_pstream)
600 {
601 LOG(VB_GENERAL, LOG_ERR, LOC + "failed to create new playback stream");
602 pa_proplist_free(m_stproplist);
603 m_stproplist = nullptr;
604 return false;
605 }
606 pa_stream_set_state_callback(m_pstream, StreamStateCallback, this);
607 pa_stream_set_write_callback(m_pstream, WriteCallback, this);
608 pa_stream_set_overflow_callback(m_pstream, BufferFlowCallback, (char*)"over");
609 pa_stream_set_underflow_callback(m_pstream, BufferFlowCallback,
610 (char*)"under");
611 if (m_setInitialVol)
612 {
613 int volume = gCoreContext->GetNumSetting("MasterMixerVolume", 80);
614 pa_cvolume_set(&m_volumeControl, m_channels,
615 (float)volume * (float)PA_VOLUME_NORM / 100.0F);
616 }
617 else
618 {
619 pa_cvolume_reset(&m_volumeControl, m_channels);
620 }
621
623
624 m_bufferSettings.maxlength = UINT32_MAX;
625 m_bufferSettings.tlength = m_fragmentSize * 4;
626 m_bufferSettings.prebuf = UINT32_MAX;
627 m_bufferSettings.minreq = UINT32_MAX;
628 m_bufferSettings.fragsize = UINT32_MAX;
629
630 int flags = PA_STREAM_INTERPOLATE_TIMING
631 | PA_STREAM_ADJUST_LATENCY
632 | PA_STREAM_AUTO_TIMING_UPDATE
633 | PA_STREAM_NO_REMIX_CHANNELS;
634
635 pa_stream_connect_playback(m_pstream, nullptr, &m_bufferSettings,
636 (pa_stream_flags_t)flags, nullptr, nullptr);
637
638 pa_stream_state_t sstate = PA_STREAM_UNCONNECTED;
639 bool connected = false;
640 bool failed = false;
641
642 while (!(connected || failed))
643 {
644 pa_context_state_t cstate = pa_context_get_state(m_pcontext);
645 switch (cstate)
646 {
647 case PA_CONTEXT_FAILED:
648 case PA_CONTEXT_TERMINATED:
649 LOG(VB_GENERAL, LOG_ERR, LOC + QString("context is stuffed, %1")
650 .arg(pa_strerror(pa_context_errno(m_pcontext))));
651 pa_proplist_free(m_stproplist);
652 m_stproplist = nullptr;
653 failed = true;
654 break;
655 default:
656 switch (sstate = pa_stream_get_state(m_pstream))
657 {
658 case PA_STREAM_READY:
659 connected = true;
660 break;
661 case PA_STREAM_FAILED:
662 case PA_STREAM_TERMINATED:
663 LOG(VB_GENERAL, LOG_ERR, LOC + QString("stream failed or was terminated, "
664 "context state %1, stream state %2")
665 .arg(cstate).arg(sstate));
666 pa_proplist_free(m_stproplist);
667 m_stproplist = nullptr;
668 failed = true;
669 break;
670 default:
671 pa_threaded_mainloop_wait(m_mainloop);
672 break;
673 }
674 }
675 }
676
677 const pa_buffer_attr *buf_attr = pa_stream_get_buffer_attr(m_pstream);
678 m_fragmentSize = buf_attr->tlength >> 2;
679 m_soundcardBufferSize = buf_attr->maxlength;
680
681 LOG(VB_AUDIO, LOG_INFO, LOC + QString("fragment size %1, soundcard buffer size %2")
683
684 return (connected && !failed);
685}
686
688{
689 QString fn_log_tag = QString("FlushStream (%1), ").arg(caller);
690 pa_threaded_mainloop_lock(m_mainloop);
691 pa_operation *op = pa_stream_flush(m_pstream, nullptr, this);
692 pa_threaded_mainloop_unlock(m_mainloop);
693 if (op)
694 pa_operation_unref(op);
695 else
696 LOG(VB_GENERAL, LOG_ERR, LOC + fn_log_tag + "stream flush operation failed ");
697}
698
699void AudioOutputPulseAudio::ContextDrainCallback(pa_context */*c*/, void *arg)
700{
701 auto *mloop = (pa_threaded_mainloop *)arg;
702 pa_threaded_mainloop_signal(mloop, 0);
703}
704
706{
707 auto *audoutP = static_cast<AudioOutputPulseAudio*>(arg);
708 switch (pa_context_get_state(c))
709 {
710 case PA_CONTEXT_READY:
711 case PA_CONTEXT_TERMINATED:
712 case PA_CONTEXT_FAILED:
713 pa_threaded_mainloop_signal(audoutP->m_mainloop, 0);
714 break;
715 case PA_CONTEXT_CONNECTING:
716 case PA_CONTEXT_UNCONNECTED:
717 case PA_CONTEXT_AUTHORIZING:
718 case PA_CONTEXT_SETTING_NAME:
719 break;
720 }
721}
722
724{
725 auto *audoutP = static_cast<AudioOutputPulseAudio*>(arg);
726 switch (pa_stream_get_state(s))
727 {
728 case PA_STREAM_READY:
729 case PA_STREAM_TERMINATED:
730 case PA_STREAM_FAILED:
731 pa_threaded_mainloop_signal(audoutP->m_mainloop, 0);
732 break;
733 case PA_STREAM_UNCONNECTED:
734 case PA_STREAM_CREATING:
735 break;
736 }
737}
738
739void AudioOutputPulseAudio::WriteCallback(pa_stream */*s*/, size_t /*size*/, void *arg)
740{
741 auto *audoutP = static_cast<AudioOutputPulseAudio*>(arg);
742 pa_threaded_mainloop_signal(audoutP->m_mainloop, 0);
743}
744
745void AudioOutputPulseAudio::BufferFlowCallback(pa_stream */*s*/, void *tag)
746{
747 LOG(VB_GENERAL, LOG_ERR, LOC + QString("stream buffer %1 flow").arg((char*)tag));
748}
749
751 pa_context *c, int ok, void *arg)
752{
753 QString fn_log_tag = "OpCompletionCallback, ";
754 auto *audoutP = static_cast<AudioOutputPulseAudio*>(arg);
755 if (!ok)
756 {
757 LOG(VB_GENERAL, LOG_ERR, LOC + fn_log_tag + QString("bummer, an operation failed: %1")
758 .arg(pa_strerror(pa_context_errno(c))));
759 }
760 pa_threaded_mainloop_signal(audoutP->m_mainloop, 0);
761}
762
764 pa_context */*context*/, const pa_server_info *inf, void */*arg*/)
765{
766 QString fn_log_tag = "ServerInfoCallback, ";
767
768 LOG(VB_AUDIO, LOG_INFO, LOC + fn_log_tag +
769 QString("PulseAudio server info - host name: %1, server version: "
770 "%2, server name: %3, default sink: %4")
771 .arg(inf->host_name, inf->server_version,
772 inf->server_name, inf->default_sink_name));
773}
774
776 pa_context */*c*/, const pa_sink_info *info, int /*eol*/, void *arg)
777{
778 auto *audoutP = static_cast<AudioOutputPulseAudio*>(arg);
779
780 if (!info)
781 {
782 pa_threaded_mainloop_signal(audoutP->m_mainloop, 0);
783 return;
784 }
785
786 audoutP->m_aoSettings->AddSupportedRate(info->sample_spec.rate);
787
788 for (uint i = 2; i <= info->sample_spec.channels; i++)
789 audoutP->m_aoSettings->AddSupportedChannels(i);
790
791 pa_threaded_mainloop_signal(audoutP->m_mainloop, 0);
792}
793
794/* vim: set expandtab tabstop=4 shiftwidth=4: */
#define LOC
static constexpr int8_t PULSE_MAX_CHANNELS
@ FORMAT_U8
@ FORMAT_S32
@ FORMAT_S24
@ FORMAT_NONE
@ FORMAT_FLT
@ FORMAT_S16
@ FORMAT_S24LSB
void KillAudio(void)
Kill the output thread and cleanup.
void Reconfigure(const AudioSettings &settings) override
(Re)Configure AudioOutputBase
AudioFormat m_outputFormat
void InitSettings(const AudioSettings &settings)
void Drain(void) override
Block until all available frames have been written to the device.
int GetBufferedOnSoundcard(void) const override
Return the size in bytes of frames currently in the audio buffer adjusted with the audio playback lat...
void SetVolumeChannel(int channel, int volume) override
void WriteAudio(unsigned char *aubuf, int size) override
pa_proplist * m_ctproplist
pa_channel_map m_channelMap
pa_sample_spec m_sampleSpec
static void BufferFlowCallback(pa_stream *s, void *tag)
void Drain(void) override
Block until all available frames have been written to the device.
static void SinkInfoCallback(pa_context *c, const pa_sink_info *info, int eol, void *arg)
static void ServerInfoCallback(pa_context *context, const pa_server_info *inf, void *arg)
static void ContextDrainCallback(pa_context *c, void *arg)
void FlushStream(const char *caller)
AudioOutputPulseAudio(const AudioSettings &settings)
pa_proplist * m_stproplist
AudioOutputSettings * m_aoSettings
static void WriteCallback(pa_stream *s, size_t size, void *arg)
static void OpCompletionCallback(pa_context *c, int ok, void *arg)
static void StreamStateCallback(pa_stream *s, void *arg)
AudioOutputSettings * GetOutputSettings(bool digital) override
pa_threaded_mainloop * m_mainloop
int GetVolumeChannel(int channel) const override
static void ContextStateCallback(pa_context *c, void *arg)
bool OpenDevice(void) override
pa_buffer_attr m_bufferSettings
void CloseDevice(void) override
void AddSupportedFormat(AudioFormat format)
bool m_init
If set to false, AudioOutput instance will not try to initially open the audio device.
Definition: audiosettings.h:85
QString GetSetting(const QString &key, const QString &defaultval="")
int GetNumSetting(const QString &key, int defaultval=0)
unsigned int uint
Definition: compat.h:68
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
dictionary info
Definition: azlyrics.py:7
def write(text, progress=True)
Definition: mythburn.py:307