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 {80};
614 if (gCoreContext->GetSetting("MixerControl", "PCM") == "PCM")
615 {
616 volume = gCoreContext->GetNumSetting("PCMMixerVolume", 80);
617 }
618 else
619 {
620 volume = gCoreContext->GetNumSetting("MasterMixerVolume", 80);
621 }
622 pa_cvolume_set(&m_volumeControl, m_channels,
623 (float)volume * (float)PA_VOLUME_NORM / 100.0F);
624 }
625 else
626 {
627 pa_cvolume_reset(&m_volumeControl, m_channels);
628 }
629
631
632 m_bufferSettings.maxlength = UINT32_MAX;
633 m_bufferSettings.tlength = m_fragmentSize * 4;
634 m_bufferSettings.prebuf = UINT32_MAX;
635 m_bufferSettings.minreq = UINT32_MAX;
636 m_bufferSettings.fragsize = UINT32_MAX;
637
638 int flags = PA_STREAM_INTERPOLATE_TIMING
639 | PA_STREAM_ADJUST_LATENCY
640 | PA_STREAM_AUTO_TIMING_UPDATE
641 | PA_STREAM_NO_REMIX_CHANNELS;
642
643 pa_stream_connect_playback(m_pstream, nullptr, &m_bufferSettings,
644 (pa_stream_flags_t)flags, nullptr, nullptr);
645
646 pa_stream_state_t sstate = PA_STREAM_UNCONNECTED;
647 bool connected = false;
648 bool failed = false;
649
650 while (!(connected || failed))
651 {
652 pa_context_state_t cstate = pa_context_get_state(m_pcontext);
653 switch (cstate)
654 {
655 case PA_CONTEXT_FAILED:
656 case PA_CONTEXT_TERMINATED:
657 LOG(VB_GENERAL, LOG_ERR, LOC + QString("context is stuffed, %1")
658 .arg(pa_strerror(pa_context_errno(m_pcontext))));
659 pa_proplist_free(m_stproplist);
660 m_stproplist = nullptr;
661 failed = true;
662 break;
663 default:
664 switch (sstate = pa_stream_get_state(m_pstream))
665 {
666 case PA_STREAM_READY:
667 connected = true;
668 break;
669 case PA_STREAM_FAILED:
670 case PA_STREAM_TERMINATED:
671 LOG(VB_GENERAL, LOG_ERR, LOC + QString("stream failed or was terminated, "
672 "context state %1, stream state %2")
673 .arg(cstate).arg(sstate));
674 pa_proplist_free(m_stproplist);
675 m_stproplist = nullptr;
676 failed = true;
677 break;
678 default:
679 pa_threaded_mainloop_wait(m_mainloop);
680 break;
681 }
682 }
683 }
684
685 const pa_buffer_attr *buf_attr = pa_stream_get_buffer_attr(m_pstream);
686 m_fragmentSize = buf_attr->tlength >> 2;
687 m_soundcardBufferSize = buf_attr->maxlength;
688
689 LOG(VB_AUDIO, LOG_INFO, LOC + QString("fragment size %1, soundcard buffer size %2")
691
692 return (connected && !failed);
693}
694
696{
697 QString fn_log_tag = QString("FlushStream (%1), ").arg(caller);
698 pa_threaded_mainloop_lock(m_mainloop);
699 pa_operation *op = pa_stream_flush(m_pstream, nullptr, this);
700 pa_threaded_mainloop_unlock(m_mainloop);
701 if (op)
702 pa_operation_unref(op);
703 else
704 LOG(VB_GENERAL, LOG_ERR, LOC + fn_log_tag + "stream flush operation failed ");
705}
706
707void AudioOutputPulseAudio::ContextDrainCallback(pa_context */*c*/, void *arg)
708{
709 auto *mloop = (pa_threaded_mainloop *)arg;
710 pa_threaded_mainloop_signal(mloop, 0);
711}
712
714{
715 auto *audoutP = static_cast<AudioOutputPulseAudio*>(arg);
716 switch (pa_context_get_state(c))
717 {
718 case PA_CONTEXT_READY:
719 case PA_CONTEXT_TERMINATED:
720 case PA_CONTEXT_FAILED:
721 pa_threaded_mainloop_signal(audoutP->m_mainloop, 0);
722 break;
723 case PA_CONTEXT_CONNECTING:
724 case PA_CONTEXT_UNCONNECTED:
725 case PA_CONTEXT_AUTHORIZING:
726 case PA_CONTEXT_SETTING_NAME:
727 break;
728 }
729}
730
732{
733 auto *audoutP = static_cast<AudioOutputPulseAudio*>(arg);
734 switch (pa_stream_get_state(s))
735 {
736 case PA_STREAM_READY:
737 case PA_STREAM_TERMINATED:
738 case PA_STREAM_FAILED:
739 pa_threaded_mainloop_signal(audoutP->m_mainloop, 0);
740 break;
741 case PA_STREAM_UNCONNECTED:
742 case PA_STREAM_CREATING:
743 break;
744 }
745}
746
747void AudioOutputPulseAudio::WriteCallback(pa_stream */*s*/, size_t /*size*/, void *arg)
748{
749 auto *audoutP = static_cast<AudioOutputPulseAudio*>(arg);
750 pa_threaded_mainloop_signal(audoutP->m_mainloop, 0);
751}
752
753void AudioOutputPulseAudio::BufferFlowCallback(pa_stream */*s*/, void *tag)
754{
755 LOG(VB_GENERAL, LOG_ERR, LOC + QString("stream buffer %1 flow").arg((char*)tag));
756}
757
759 pa_context *c, int ok, void *arg)
760{
761 QString fn_log_tag = "OpCompletionCallback, ";
762 auto *audoutP = static_cast<AudioOutputPulseAudio*>(arg);
763 if (!ok)
764 {
765 LOG(VB_GENERAL, LOG_ERR, LOC + fn_log_tag + QString("bummer, an operation failed: %1")
766 .arg(pa_strerror(pa_context_errno(c))));
767 }
768 pa_threaded_mainloop_signal(audoutP->m_mainloop, 0);
769}
770
772 pa_context */*context*/, const pa_server_info *inf, void */*arg*/)
773{
774 QString fn_log_tag = "ServerInfoCallback, ";
775
776 LOG(VB_AUDIO, LOG_INFO, LOC + fn_log_tag +
777 QString("PulseAudio server info - host name: %1, server version: "
778 "%2, server name: %3, default sink: %4")
779 .arg(inf->host_name, inf->server_version,
780 inf->server_name, inf->default_sink_name));
781}
782
784 pa_context */*c*/, const pa_sink_info *info, int /*eol*/, void *arg)
785{
786 auto *audoutP = static_cast<AudioOutputPulseAudio*>(arg);
787
788 if (!info)
789 {
790 pa_threaded_mainloop_signal(audoutP->m_mainloop, 0);
791 return;
792 }
793
794 audoutP->m_aoSettings->AddSupportedRate(info->sample_spec.rate);
795
796 for (uint i = 2; i <= info->sample_spec.channels; i++)
797 audoutP->m_aoSettings->AddSupportedChannels(i);
798
799 pa_threaded_mainloop_signal(audoutP->m_mainloop, 0);
800}
801
802/* 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