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 (uint & value : m_volumeControl.values)
38  value = 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  {
267  VBERROR(fn_log_tag + QString("stream write failed: %1")
268  .arg(write_status == PA_ERR_BADSTATE
269  ? "PA_ERR_BADSTATE"
270  : "PA_ERR_INVALID"));
271  }
272 
273  VBERROR(fn_log_tag + QString("short write, %1 of %2")
274  .arg(size - to_write).arg(size));
275  }
276  }
277  else
278  VBERROR(fn_log_tag + QString("stream state not good: %1")
279  .arg(sstate,0,16));
280 }
281 
283 {
284  pa_usec_t latency = 0;
285  size_t buffered = 0;
286 
287  if (!m_pcontext || pa_context_get_state(m_pcontext) != PA_CONTEXT_READY)
288  return 0;
289 
290  if (!m_pstream || pa_stream_get_state(m_pstream) != PA_STREAM_READY)
291  return 0;
292 
293  const pa_buffer_attr *buf_attr = pa_stream_get_buffer_attr(m_pstream);
294  size_t bfree = pa_stream_writable_size(m_pstream);
295  buffered = buf_attr->tlength - bfree;
296 
297  pa_threaded_mainloop_lock(m_mainloop);
298 
299  while (pa_stream_get_latency(m_pstream, &latency, nullptr) < 0)
300  {
301  if (pa_context_errno(m_pcontext) != PA_ERR_NODATA)
302  {
303  latency = 0;
304  break;
305  }
306  pa_threaded_mainloop_wait(m_mainloop);
307  }
308 
309  pa_threaded_mainloop_unlock(m_mainloop);
310 
311  return (latency * m_sampleRate *
312  m_outputBytesPerFrame / 1000000) + buffered;
313 }
314 
316 {
317  return (float)m_volumeControl.values[channel] /
318  (float)PA_VOLUME_NORM * 100.0F;
319 }
320 
321 void AudioOutputPulseAudio::SetVolumeChannel(int channel, int volume)
322 {
323  QString fn_log_tag = "SetVolumeChannel, ";
324 
325  if (channel < 0 || channel > PULSE_MAX_CHANNELS || volume < 0)
326  {
327  VBERROR(fn_log_tag + QString("bad volume params, channel %1, volume %2")
328  .arg(channel).arg(volume));
329  return;
330  }
331 
332  m_volumeControl.values[channel] =
333  (float)volume / 100.0F * (float)PA_VOLUME_NORM;
334 
335 // FIXME: This code did nothing at all so has been commented out for now
336 // until it's decided whether it was ever required
337 // volume = min(100, volume);
338 // volume = max(0, volume);
339 
340  if (gCoreContext->GetSetting("MixerControl", "PCM").toLower() == "pcm")
341  {
342  uint32_t stream_index = pa_stream_get_index(m_pstream);
343  pa_threaded_mainloop_lock(m_mainloop);
344  pa_operation *op =
345  pa_context_set_sink_input_volume(m_pcontext, stream_index,
347  OpCompletionCallback, this);
348  pa_threaded_mainloop_unlock(m_mainloop);
349  if (op)
350  pa_operation_unref(op);
351  else
352  {
353  VBERROR(fn_log_tag +
354  QString("set stream volume operation failed, stream %1, "
355  "error %2 ")
356  .arg(stream_index)
357  .arg(pa_strerror(pa_context_errno(m_pcontext))));
358  }
359  }
360  else
361  {
362  uint32_t sink_index = pa_stream_get_device_index(m_pstream);
363  pa_threaded_mainloop_lock(m_mainloop);
364  pa_operation *op =
365  pa_context_set_sink_volume_by_index(m_pcontext, sink_index,
367  OpCompletionCallback, this);
368  pa_threaded_mainloop_unlock(m_mainloop);
369  if (op)
370  pa_operation_unref(op);
371  else
372  {
373  VBERROR(fn_log_tag +
374  QString("set sink volume operation failed, sink %1, "
375  "error %2 ")
376  .arg(sink_index)
377  .arg(pa_strerror(pa_context_errno(m_pcontext))));
378  }
379  }
380 }
381 
383 {
385  pa_threaded_mainloop_lock(m_mainloop);
386  pa_operation *op = pa_stream_drain(m_pstream, nullptr, this);
387  pa_threaded_mainloop_unlock(m_mainloop);
388 
389  if (op)
390  pa_operation_unref(op);
391  else
392  VBERROR("Drain, stream drain failed");
393 }
394 
396 {
397  QString fn_log_tag = "ContextConnect, ";
398  if (m_pcontext)
399  {
400  VBERROR(fn_log_tag + "context appears to exist, but shouldn't (yet)");
401  pa_context_unref(m_pcontext);
402  m_pcontext = nullptr;
403  return false;
404  }
405  pa_proplist *proplist = pa_proplist_new();
406  if (!proplist)
407  {
408  VBERROR(fn_log_tag + QString("failed to create new proplist"));
409  return false;
410  }
411  pa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, "MythTV");
412  pa_proplist_sets(proplist, PA_PROP_APPLICATION_ICON_NAME, "mythtv");
413  pa_proplist_sets(proplist, PA_PROP_MEDIA_ROLE, "video");
414  m_pcontext =
415  pa_context_new_with_proplist(pa_threaded_mainloop_get_api(m_mainloop),
416  "MythTV", proplist);
417  if (!m_pcontext)
418  {
419  VBERROR(fn_log_tag + "failed to acquire new context");
420  return false;
421  }
422  pa_context_set_state_callback(m_pcontext, ContextStateCallback, this);
423 
424  char *pulse_host = ChooseHost();
425  int chk = pa_context_connect(
426  m_pcontext, pulse_host, (pa_context_flags_t)0, nullptr);
427 
428  delete[] pulse_host;
429 
430  if (chk < 0)
431  {
432  VBERROR(fn_log_tag + QString("context connect failed: %1")
433  .arg(pa_strerror(pa_context_errno(m_pcontext))));
434  return false;
435  }
436  bool connected = false;
437  pa_context_state_t state = pa_context_get_state(m_pcontext);
438  for (; !connected; state = pa_context_get_state(m_pcontext))
439  {
440  switch(state)
441  {
442  case PA_CONTEXT_READY:
443  VBAUDIO(fn_log_tag +"context connection ready");
444  connected = true;
445  continue;
446 
447  case PA_CONTEXT_FAILED:
448  case PA_CONTEXT_TERMINATED:
449  VBERROR(fn_log_tag +
450  QString("context connection failed or terminated: %1")
451  .arg(pa_strerror(pa_context_errno(m_pcontext))));
452  return false;
453 
454  default:
455  VBAUDIO(fn_log_tag + "waiting for context connection ready");
456  pa_threaded_mainloop_wait(m_mainloop);
457  break;
458  }
459  }
460 
461  pa_operation *op =
462  pa_context_get_server_info(m_pcontext, ServerInfoCallback, this);
463 
464  if (op)
465  pa_operation_unref(op);
466  else
467  VBERROR(fn_log_tag + "failed to get PulseAudio server info");
468 
469  return true;
470 }
471 
473 {
474  QString fn_log_tag = "ChooseHost, ";
475  char *pulse_host = nullptr;
476  char *device = strdup(m_mainDevice.toLatin1().constData());
477  const char *host = nullptr;
478 
479  for (host=device; host && *host != ':' && *host != 0; host++);
480 
481  if (host && *host != 0)
482  host++;
483 
484  if (host && *host != 0 && strcmp(host,"default") != 0)
485  {
486  if ((pulse_host = new char[strlen(host) + 1]))
487  strcpy(pulse_host, host);
488  else
489  {
490  VBERROR(fn_log_tag +
491  QString("allocation of pulse host '%1' char[%2] failed")
492  .arg(host).arg(strlen(host) + 1));
493  }
494  }
495 
496  if (!pulse_host && host && strcmp(host,"default") != 0)
497  {
498  char *env_pulse_host = getenv("PULSE_SERVER");
499  if (env_pulse_host && (*env_pulse_host != '\0'))
500  {
501  int host_len = strlen(env_pulse_host) + 1;
502 
503  if ((pulse_host = new char[host_len]))
504  strcpy(pulse_host, env_pulse_host);
505  else
506  {
507  VBERROR(fn_log_tag +
508  QString("allocation of pulse host '%1' char[%2] failed")
509  .arg(env_pulse_host).arg(host_len));
510  }
511  }
512  }
513 
514  VBAUDIO(fn_log_tag + QString("chosen PulseAudio server: %1")
515  .arg((pulse_host != nullptr) ? pulse_host : "default"));
516 
517  free(device);
518 
519  return pulse_host;
520 }
521 
523 {
524  QString fn_log_tag = "ConnectPlaybackStream, ";
525  pa_proplist *proplist = pa_proplist_new();
526  if (!proplist)
527  {
528  VBERROR(fn_log_tag + QString("failed to create new proplist"));
529  return false;
530  }
531  pa_proplist_sets(proplist, PA_PROP_MEDIA_ROLE, "video");
532  m_pstream =
533  pa_stream_new_with_proplist(m_pcontext, "MythTV playback", &m_sampleSpec,
534  &m_channelMap, proplist);
535  if (!m_pstream)
536  {
537  VBERROR("failed to create new playback stream");
538  return false;
539  }
540  pa_stream_set_state_callback(m_pstream, StreamStateCallback, this);
541  pa_stream_set_write_callback(m_pstream, WriteCallback, this);
542  pa_stream_set_overflow_callback(m_pstream, BufferFlowCallback, (char*)"over");
543  pa_stream_set_underflow_callback(m_pstream, BufferFlowCallback,
544  (char*)"under");
545  if (m_setInitialVol)
546  {
547  int volume = gCoreContext->GetNumSetting("MasterMixerVolume", 80);
548  pa_cvolume_set(&m_volumeControl, m_channels,
549  (float)volume * (float)PA_VOLUME_NORM / 100.0F);
550  }
551  else
552  pa_cvolume_reset(&m_volumeControl, m_channels);
553 
555 
556  m_bufferSettings.maxlength = (uint32_t)-1;
557  m_bufferSettings.tlength = m_fragmentSize * 4;
558  m_bufferSettings.prebuf = (uint32_t)-1;
559  m_bufferSettings.minreq = (uint32_t)-1;
560  m_bufferSettings.fragsize = (uint32_t) -1;
561 
562  int flags = PA_STREAM_INTERPOLATE_TIMING
563  | PA_STREAM_ADJUST_LATENCY
564  | PA_STREAM_AUTO_TIMING_UPDATE
565  | PA_STREAM_NO_REMIX_CHANNELS;
566 
567  pa_stream_connect_playback(m_pstream, nullptr, &m_bufferSettings,
568  (pa_stream_flags_t)flags, nullptr, nullptr);
569 
570  pa_context_state_t cstate = PA_CONTEXT_UNCONNECTED;
571  pa_stream_state_t sstate = PA_STREAM_UNCONNECTED;
572  bool connected = false;
573  bool failed = false;
574 
575  while (!(connected || failed))
576  {
577  switch (cstate = pa_context_get_state(m_pcontext))
578  {
579  case PA_CONTEXT_FAILED:
580  case PA_CONTEXT_TERMINATED:
581  VBERROR(QString("context is stuffed, %1")
582  .arg(pa_strerror(pa_context_errno(m_pcontext))));
583  failed = true;
584  break;
585  default:
586  switch (sstate = pa_stream_get_state(m_pstream))
587  {
588  case PA_STREAM_READY:
589  connected = true;
590  break;
591  case PA_STREAM_FAILED:
592  case PA_STREAM_TERMINATED:
593  VBERROR(QString("stream failed or was terminated, "
594  "context state %1, stream state %2")
595  .arg(cstate).arg(sstate));
596  failed = true;
597  break;
598  default:
599  pa_threaded_mainloop_wait(m_mainloop);
600  break;
601  }
602  }
603  }
604 
605  const pa_buffer_attr *buf_attr = pa_stream_get_buffer_attr(m_pstream);
606  m_fragmentSize = buf_attr->tlength >> 2;
607  m_soundcardBufferSize = buf_attr->maxlength;
608 
609  VBAUDIO(QString("fragment size %1, soundcard buffer size %2")
611 
612  return (connected && !failed);
613 }
614 
615 void AudioOutputPulseAudio::FlushStream(const char *caller)
616 {
617  QString fn_log_tag = QString("FlushStream (%1), ").arg(caller);
618  pa_threaded_mainloop_lock(m_mainloop);
619  pa_operation *op = pa_stream_flush(m_pstream, nullptr, this);
620  pa_threaded_mainloop_unlock(m_mainloop);
621  if (op)
622  pa_operation_unref(op);
623  else
624  VBERROR(fn_log_tag + "stream flush operation failed ");
625 }
626 
627 void AudioOutputPulseAudio::ContextStateCallback(pa_context *c, void *arg)
628 {
629  QString fn_log_tag = "_ContextStateCallback, ";
630  auto *audoutP = static_cast<AudioOutputPulseAudio*>(arg);
631  switch (pa_context_get_state(c))
632  {
633  case PA_CONTEXT_READY:
634  case PA_CONTEXT_TERMINATED:
635  case PA_CONTEXT_FAILED:
636  pa_threaded_mainloop_signal(audoutP->m_mainloop, 0);
637  break;
638  case PA_CONTEXT_CONNECTING:
639  case PA_CONTEXT_UNCONNECTED:
640  case PA_CONTEXT_AUTHORIZING:
641  case PA_CONTEXT_SETTING_NAME:
642  break;
643  }
644 }
645 
646 void AudioOutputPulseAudio::StreamStateCallback(pa_stream *s, void *arg)
647 {
648  QString fn_log_tag = "StreamStateCallback, ";
649  auto *audoutP = static_cast<AudioOutputPulseAudio*>(arg);
650  switch (pa_stream_get_state(s))
651  {
652  case PA_STREAM_READY:
653  case PA_STREAM_TERMINATED:
654  case PA_STREAM_FAILED:
655  pa_threaded_mainloop_signal(audoutP->m_mainloop, 0);
656  break;
657  case PA_STREAM_UNCONNECTED:
658  case PA_STREAM_CREATING:
659  break;
660  }
661 }
662 
663 void AudioOutputPulseAudio::WriteCallback(pa_stream */*s*/, size_t /*size*/, void *arg)
664 {
665  auto *audoutP = static_cast<AudioOutputPulseAudio*>(arg);
666  pa_threaded_mainloop_signal(audoutP->m_mainloop, 0);
667 }
668 
669 void AudioOutputPulseAudio::BufferFlowCallback(pa_stream */*s*/, void *tag)
670 {
671  VBERROR(QString("stream buffer %1 flow").arg((char*)tag));
672 }
673 
675  pa_context *c, int ok, void *arg)
676 {
677  QString fn_log_tag = "OpCompletionCallback, ";
678  auto *audoutP = static_cast<AudioOutputPulseAudio*>(arg);
679  if (!ok)
680  {
681  VBERROR(fn_log_tag + QString("bummer, an operation failed: %1")
682  .arg(pa_strerror(pa_context_errno(c))));
683  }
684  pa_threaded_mainloop_signal(audoutP->m_mainloop, 0);
685 }
686 
688  pa_context */*context*/, const pa_server_info *inf, void */*arg*/)
689 {
690  QString fn_log_tag = "ServerInfoCallback, ";
691 
692  VBAUDIO(fn_log_tag +
693  QString("PulseAudio server info - host name: %1, server version: "
694  "%2, server name: %3, default sink: %4")
695  .arg(inf->host_name).arg(inf->server_version)
696  .arg(inf->server_name).arg(inf->default_sink_name));
697 }
698 
700  pa_context */*c*/, const pa_sink_info *info, int /*eol*/, void *arg)
701 {
702  auto *audoutP = static_cast<AudioOutputPulseAudio*>(arg);
703 
704  if (!info)
705  {
706  pa_threaded_mainloop_signal(audoutP->m_mainloop, 0);
707  return;
708  }
709 
710  audoutP->m_aoSettings->AddSupportedRate(info->sample_spec.rate);
711 
712  for (uint i = 2; i <= info->sample_spec.channels; i++)
713  audoutP->m_aoSettings->AddSupportedChannels(i);
714 
715  pa_threaded_mainloop_signal(audoutP->m_mainloop, 0);
716 }
717 
718 /* 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:308
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:84
#define VBAUDIOTS(str)
#define VBAUDIO(str)