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