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 {
437 pa_operation_unref(op);
438 }
439 else
440 {
441 LOG(VB_GENERAL, LOG_ERR, LOC + fn_log_tag +
442 QString("set stream volume operation failed, stream %1, "
443 "error %2 ")
444 .arg(stream_index)
445 .arg(pa_strerror(pa_context_errno(m_pcontext))));
446 }
447 }
448 else
449 {
450 uint32_t sink_index = pa_stream_get_device_index(m_pstream);
451 pa_threaded_mainloop_lock(m_mainloop);
452 pa_operation *op =
453 pa_context_set_sink_volume_by_index(m_pcontext, sink_index,
456 pa_threaded_mainloop_unlock(m_mainloop);
457 if (op)
458 {
459 pa_operation_unref(op);
460 }
461 else
462 {
463 LOG(VB_GENERAL, LOG_ERR, LOC + fn_log_tag +
464 QString("set sink volume operation failed, sink %1, "
465 "error %2 ")
466 .arg(sink_index)
467 .arg(pa_strerror(pa_context_errno(m_pcontext))));
468 }
469 }
470}
471
473{
475 pa_threaded_mainloop_lock(m_mainloop);
476 pa_operation *op = pa_stream_drain(m_pstream, nullptr, this);
477 pa_threaded_mainloop_unlock(m_mainloop);
478
479 if (op)
480 pa_operation_unref(op);
481 else
482 LOG(VB_GENERAL, LOG_ERR, LOC + "Drain, stream drain failed");
483}
484
486{
487 QString fn_log_tag = "ContextConnect, ";
488 if (m_pcontext)
489 {
490 LOG(VB_GENERAL, LOG_ERR, LOC + fn_log_tag + "context appears to exist, but shouldn't (yet)");
491 pa_context_unref(m_pcontext);
492 m_pcontext = nullptr;
493 return false;
494 }
495 m_ctproplist = pa_proplist_new();
496 if (!m_ctproplist)
497 {
498 LOG(VB_GENERAL, LOG_ERR, LOC + fn_log_tag + QString("failed to create new proplist"));
499 return false;
500 }
501 pa_proplist_sets(m_ctproplist, PA_PROP_APPLICATION_NAME, "MythTV");
502 pa_proplist_sets(m_ctproplist, PA_PROP_APPLICATION_ICON_NAME, "mythtv");
503 pa_proplist_sets(m_ctproplist, PA_PROP_MEDIA_ROLE, "video");
504 m_pcontext =
505 pa_context_new_with_proplist(pa_threaded_mainloop_get_api(m_mainloop),
506 "MythTV", m_ctproplist);
507 if (!m_pcontext)
508 {
509 LOG(VB_GENERAL, LOG_ERR, LOC + fn_log_tag + "failed to acquire new context");
510 pa_proplist_free(m_ctproplist);
511 m_ctproplist = nullptr;
512 return false;
513 }
514 pa_context_set_state_callback(m_pcontext, ContextStateCallback, this);
515
516 QString pulse_host = ChooseHost();
517 int chk = pa_context_connect(m_pcontext,
518 !pulse_host.isEmpty() ? qPrintable(pulse_host) : nullptr,
519 (pa_context_flags_t)0, nullptr);
520
521 if (chk < 0)
522 {
523 LOG(VB_GENERAL, LOG_ERR, LOC + fn_log_tag + QString("context connect failed: %1")
524 .arg(pa_strerror(pa_context_errno(m_pcontext))));
525 pa_proplist_free(m_ctproplist);
526 m_ctproplist = nullptr;
527 return false;
528 }
529 bool connected = false;
530 pa_context_state_t state = pa_context_get_state(m_pcontext);
531 for (; !connected; state = pa_context_get_state(m_pcontext))
532 {
533 switch(state)
534 {
535 case PA_CONTEXT_READY:
536 LOG(VB_AUDIO, LOG_INFO, LOC + fn_log_tag +"context connection ready");
537 connected = true;
538 continue;
539
540 case PA_CONTEXT_FAILED:
541 case PA_CONTEXT_TERMINATED:
542 LOG(VB_GENERAL, LOG_ERR, LOC + fn_log_tag +
543 QString("context connection failed or terminated: %1")
544 .arg(pa_strerror(pa_context_errno(m_pcontext))));
545 pa_proplist_free(m_ctproplist);
546 m_ctproplist = nullptr;
547 return false;
548
549 default:
550 LOG(VB_AUDIO, LOG_INFO, LOC + fn_log_tag + "waiting for context connection ready");
551 pa_threaded_mainloop_wait(m_mainloop);
552 break;
553 }
554 }
555
556 pa_operation *op =
557 pa_context_get_server_info(m_pcontext, ServerInfoCallback, this);
558
559 if (op)
560 pa_operation_unref(op);
561 else
562 LOG(VB_GENERAL, LOG_ERR, LOC + fn_log_tag + "failed to get PulseAudio server info");
563
564 return true;
565}
566
568{
569 QString fn_log_tag = "ChooseHost, ";
570 QStringList parts = m_mainDevice.split(':');
571 QString host = parts.size() > 1 ? parts[1] : QString();
572 QString pulse_host;
573
574 if (host != "default")
575 pulse_host = host;
576
577 if (pulse_host.isEmpty() && host != "default")
578 {
579 QString env_pulse_host = qEnvironmentVariable("PULSE_SERVER");
580 if (!env_pulse_host.isEmpty())
581 pulse_host = env_pulse_host;
582 }
583
584 LOG(VB_AUDIO, LOG_INFO, LOC + fn_log_tag + QString("chosen PulseAudio server: %1")
585 .arg((pulse_host != nullptr) ? pulse_host : "default"));
586
587 return pulse_host;
588}
589
591{
592 QString fn_log_tag = "ConnectPlaybackStream, ";
593 m_stproplist = pa_proplist_new();
594 if (!m_stproplist)
595 {
596 LOG(VB_GENERAL, LOG_ERR, LOC + fn_log_tag + QString("failed to create new proplist"));
597 return false;
598 }
599 pa_proplist_sets(m_stproplist, PA_PROP_MEDIA_ROLE, "video");
600 m_pstream =
601 pa_stream_new_with_proplist(m_pcontext, "MythTV playback", &m_sampleSpec,
603 if (!m_pstream)
604 {
605 LOG(VB_GENERAL, LOG_ERR, LOC + "failed to create new playback stream");
606 pa_proplist_free(m_stproplist);
607 m_stproplist = nullptr;
608 return false;
609 }
610 pa_stream_set_state_callback(m_pstream, StreamStateCallback, this);
611 pa_stream_set_write_callback(m_pstream, WriteCallback, this);
612 pa_stream_set_overflow_callback(m_pstream, BufferFlowCallback, (char*)"over");
613 pa_stream_set_underflow_callback(m_pstream, BufferFlowCallback,
614 (char*)"under");
615 if (m_setInitialVol)
616 {
617 int volume {80};
618 if (gCoreContext->GetSetting("MixerControl", "PCM") == "PCM")
619 {
620 volume = gCoreContext->GetNumSetting("PCMMixerVolume", 80);
621 }
622 else
623 {
624 volume = gCoreContext->GetNumSetting("MasterMixerVolume", 80);
625 }
626 pa_cvolume_set(&m_volumeControl, m_channels,
627 (float)volume * (float)PA_VOLUME_NORM / 100.0F);
628 }
629 else
630 {
631 pa_cvolume_reset(&m_volumeControl, m_channels);
632 }
633
635
636 m_bufferSettings.maxlength = UINT32_MAX;
637 m_bufferSettings.tlength = m_fragmentSize * 4;
638 m_bufferSettings.prebuf = UINT32_MAX;
639 m_bufferSettings.minreq = UINT32_MAX;
640 m_bufferSettings.fragsize = UINT32_MAX;
641
642 int flags = PA_STREAM_INTERPOLATE_TIMING
643 | PA_STREAM_ADJUST_LATENCY
644 | PA_STREAM_AUTO_TIMING_UPDATE
645 | PA_STREAM_NO_REMIX_CHANNELS;
646
647 pa_stream_connect_playback(m_pstream, nullptr, &m_bufferSettings,
648 (pa_stream_flags_t)flags, nullptr, nullptr);
649
650 pa_stream_state_t sstate = PA_STREAM_UNCONNECTED;
651 bool connected = false;
652 bool failed = false;
653
654 while (!(connected || failed))
655 {
656 pa_context_state_t cstate = pa_context_get_state(m_pcontext);
657 switch (cstate)
658 {
659 case PA_CONTEXT_FAILED:
660 case PA_CONTEXT_TERMINATED:
661 LOG(VB_GENERAL, LOG_ERR, LOC + QString("context is stuffed, %1")
662 .arg(pa_strerror(pa_context_errno(m_pcontext))));
663 pa_proplist_free(m_stproplist);
664 m_stproplist = nullptr;
665 failed = true;
666 break;
667 default:
668 switch (sstate = pa_stream_get_state(m_pstream))
669 {
670 case PA_STREAM_READY:
671 connected = true;
672 break;
673 case PA_STREAM_FAILED:
674 case PA_STREAM_TERMINATED:
675 LOG(VB_GENERAL, LOG_ERR, LOC + QString("stream failed or was terminated, "
676 "context state %1, stream state %2")
677 .arg(cstate).arg(sstate));
678 pa_proplist_free(m_stproplist);
679 m_stproplist = nullptr;
680 failed = true;
681 break;
682 default:
683 pa_threaded_mainloop_wait(m_mainloop);
684 break;
685 }
686 }
687 }
688
689 const pa_buffer_attr *buf_attr = pa_stream_get_buffer_attr(m_pstream);
690 m_fragmentSize = buf_attr->tlength >> 2;
691 m_soundcardBufferSize = buf_attr->maxlength;
692
693 LOG(VB_AUDIO, LOG_INFO, LOC + QString("fragment size %1, soundcard buffer size %2")
695
696 return (connected && !failed);
697}
698
700{
701 QString fn_log_tag = QString("FlushStream (%1), ").arg(caller);
702 pa_threaded_mainloop_lock(m_mainloop);
703 pa_operation *op = pa_stream_flush(m_pstream, nullptr, this);
704 pa_threaded_mainloop_unlock(m_mainloop);
705 if (op)
706 pa_operation_unref(op);
707 else
708 LOG(VB_GENERAL, LOG_ERR, LOC + fn_log_tag + "stream flush operation failed ");
709}
710
711void AudioOutputPulseAudio::ContextDrainCallback(pa_context */*c*/, void *arg)
712{
713 auto *mloop = (pa_threaded_mainloop *)arg;
714 pa_threaded_mainloop_signal(mloop, 0);
715}
716
718{
719 auto *audoutP = static_cast<AudioOutputPulseAudio*>(arg);
720 switch (pa_context_get_state(c))
721 {
722 case PA_CONTEXT_READY:
723 case PA_CONTEXT_TERMINATED:
724 case PA_CONTEXT_FAILED:
725 pa_threaded_mainloop_signal(audoutP->m_mainloop, 0);
726 break;
727 case PA_CONTEXT_CONNECTING:
728 case PA_CONTEXT_UNCONNECTED:
729 case PA_CONTEXT_AUTHORIZING:
730 case PA_CONTEXT_SETTING_NAME:
731 break;
732 }
733}
734
736{
737 auto *audoutP = static_cast<AudioOutputPulseAudio*>(arg);
738 switch (pa_stream_get_state(s))
739 {
740 case PA_STREAM_READY:
741 case PA_STREAM_TERMINATED:
742 case PA_STREAM_FAILED:
743 pa_threaded_mainloop_signal(audoutP->m_mainloop, 0);
744 break;
745 case PA_STREAM_UNCONNECTED:
746 case PA_STREAM_CREATING:
747 break;
748 }
749}
750
751void AudioOutputPulseAudio::WriteCallback(pa_stream */*s*/, size_t /*size*/, void *arg)
752{
753 auto *audoutP = static_cast<AudioOutputPulseAudio*>(arg);
754 pa_threaded_mainloop_signal(audoutP->m_mainloop, 0);
755}
756
757void AudioOutputPulseAudio::BufferFlowCallback(pa_stream */*s*/, void *tag)
758{
759 LOG(VB_GENERAL, LOG_ERR, LOC + QString("stream buffer %1 flow").arg((char*)tag));
760}
761
763 pa_context *c, int ok, void *arg)
764{
765 QString fn_log_tag = "OpCompletionCallback, ";
766 auto *audoutP = static_cast<AudioOutputPulseAudio*>(arg);
767 if (!ok)
768 {
769 LOG(VB_GENERAL, LOG_ERR, LOC + fn_log_tag + QString("bummer, an operation failed: %1")
770 .arg(pa_strerror(pa_context_errno(c))));
771 }
772 pa_threaded_mainloop_signal(audoutP->m_mainloop, 0);
773}
774
776 pa_context */*context*/, const pa_server_info *inf, void */*arg*/)
777{
778 QString fn_log_tag = "ServerInfoCallback, ";
779
780 LOG(VB_AUDIO, LOG_INFO, LOC + fn_log_tag +
781 QString("PulseAudio server info - host name: %1, server version: "
782 "%2, server name: %3, default sink: %4")
783 .arg(inf->host_name, inf->server_version,
784 inf->server_name, inf->default_sink_name));
785}
786
788 pa_context */*c*/, const pa_sink_info *info, int /*eol*/, void *arg)
789{
790 auto *audoutP = static_cast<AudioOutputPulseAudio*>(arg);
791
792 if (!info)
793 {
794 pa_threaded_mainloop_signal(audoutP->m_mainloop, 0);
795 return;
796 }
797
798 audoutP->m_aoSettings->AddSupportedRate(info->sample_spec.rate);
799
800 for (uint i = 2; i <= info->sample_spec.channels; i++)
801 audoutP->m_aoSettings->AddSupportedChannels(i);
802
803 pa_threaded_mainloop_signal(audoutP->m_mainloop, 0);
804}
805
806/* 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:60
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:306