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