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