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