kwave  18.07.70
PlayBack-PulseAudio.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  PlayBack-PulseAudio.cpp - playback device for PulseAudio
3  -------------------
4  begin : Tue Sep 29 2009
5  copyright : (C) 2009 by Thomas Eschenbacher
6  email : Thomas.Eschenbacher@gmx.de
7 
8  ***************************************************************************/
9 
10 /***************************************************************************
11  * *
12  * This program is free software; you can redistribute it and/or modify *
13  * it under the terms of the GNU General Public License as published by *
14  * the Free Software Foundation; either version 2 of the License, or *
15  * (at your option) any later version. *
16  * *
17  ***************************************************************************/
18 
19 #include "config.h"
20 #ifdef HAVE_PULSEAUDIO_SUPPORT
21 
22 #include <limits>
23 
24 #include <errno.h>
25 #include <math.h>
26 #include <signal.h>
27 #include <stdlib.h>
28 #include <unistd.h>
29 
30 #include <QApplication>
31 #include <QCursor>
32 #include <QFileInfo>
33 #include <QLatin1Char>
34 #include <QLocale>
35 #include <QString>
36 #include <QtGlobal>
37 
38 #include <KLocalizedString>
39 #include <KUser>
40 
41 #include "libkwave/FileInfo.h"
42 #include "libkwave/String.h"
43 #include "libkwave/Utils.h"
44 #include "libkwave/memcpy.h"
45 
46 #include "PlayBack-PulseAudio.h"
47 
52 #define TIMEOUT_WAIT_DEVICE_SCAN 10000
53 
58 #define TIMEOUT_CONNECT_TO_SERVER 20000
59 
64 #define TIMEOUT_CONNECT_PLAYBACK 10000
65 
70 #define TIMEOUT_MIN_FLUSH 1000
71 
76 #define TIMEOUT_MIN_DRAIN 3000
77 
78 //***************************************************************************
81  m_mainloop_thread(this, QVariant()),
82  m_mainloop_lock(),
83  m_mainloop_signal(),
84  m_info(info),
85  m_rate(0),
86  m_bytes_per_sample(0),
87  m_buffer(Q_NULLPTR),
88  m_buffer_size(0),
89  m_buffer_used(0),
90  m_bufbase(10),
91  m_pa_proplist(Q_NULLPTR),
92  m_pa_mainloop(Q_NULLPTR),
93  m_pa_context(Q_NULLPTR),
94  m_pa_stream(Q_NULLPTR),
95  m_device_list()
96 {
97 }
98 
99 //***************************************************************************
101 {
102  close();
103 }
104 
105 //***************************************************************************
106 void Kwave::PlayBackPulseAudio::pa_context_notify_cb(pa_context *c, void *data)
107 {
108  Kwave::PlayBackPulseAudio *playback_plugin =
109  reinterpret_cast<Kwave::PlayBackPulseAudio *>(data);
110  Q_ASSERT(playback_plugin);
111  if (playback_plugin) playback_plugin->notifyContext(c);
112 }
113 
114 //***************************************************************************
116  const pa_sink_info *info,
117  int eol, void *userdata)
118 {
119  Kwave::PlayBackPulseAudio *playback_plugin =
120  reinterpret_cast<Kwave::PlayBackPulseAudio *>(userdata);
121  Q_ASSERT(playback_plugin);
122  if (playback_plugin) playback_plugin->notifySinkInfo(c, info, eol);
123 }
124 
125 //***************************************************************************
126 void Kwave::PlayBackPulseAudio::pa_stream_state_cb(pa_stream *p, void *userdata)
127 {
128  Kwave::PlayBackPulseAudio *playback_plugin =
129  reinterpret_cast<Kwave::PlayBackPulseAudio *>(userdata);
130  Q_ASSERT(playback_plugin);
131  if (playback_plugin) playback_plugin->notifyStreamState(p);
132 }
133 
134 //***************************************************************************
135 void Kwave::PlayBackPulseAudio::pa_write_cb(pa_stream *p, size_t nbytes,
136  void *userdata)
137 {
138  Kwave::PlayBackPulseAudio *playback_plugin =
139  reinterpret_cast<Kwave::PlayBackPulseAudio *>(userdata);
140  Q_ASSERT(playback_plugin);
141  if (playback_plugin) playback_plugin->notifyWrite(p, nbytes);
142 }
143 
144 //***************************************************************************
146  int success,
147  void *userdata)
148 {
149  Kwave::PlayBackPulseAudio *playback_plugin =
150  reinterpret_cast<Kwave::PlayBackPulseAudio *>(userdata);
151  Q_ASSERT(playback_plugin);
152  if (playback_plugin) playback_plugin->notifySuccess(s, success);
153 }
154 
155 //***************************************************************************
157 {
158  Q_ASSERT(c == m_pa_context);
159  switch (pa_context_get_state(c))
160  {
161  case PA_CONTEXT_UNCONNECTED:
162 // qDebug("PlayBackPulseAudio: PA_CONTEXT_UNCONNECTED!?");
163  break;
164  case PA_CONTEXT_CONNECTING:
165 // qDebug("PlayBackPulseAudio: PA_CONTEXT_CONNECTING...");
166  break;
167  case PA_CONTEXT_AUTHORIZING:
168 // qDebug("PlayBackPulseAudio: PA_CONTEXT_AUTHORIZING...");
169  break;
170  case PA_CONTEXT_SETTING_NAME:
171 // qDebug("PlayBackPulseAudio: PA_CONTEXT_SETTING_NAME...");
172  break;
173  case PA_CONTEXT_READY:
174 // qDebug("PlayBackPulseAudio: PA_CONTEXT_READY.");
175  m_mainloop_signal.wakeAll();
176  break;
177  case PA_CONTEXT_TERMINATED:
178  qWarning("PlayBackPulseAudio: PA_CONTEXT_TERMINATED");
179  m_mainloop_signal.wakeAll();
180  break;
181  case PA_CONTEXT_FAILED:
182  qWarning("PlayBackPulseAudio: PA_CONTEXT_FAILED");
183  m_mainloop_signal.wakeAll();
184  break;
185  }
186 }
187 
188 //***************************************************************************
190  const pa_sink_info *info,
191  int eol)
192 {
193  Q_UNUSED(c);
194  Q_ASSERT(c == m_pa_context);
195  if (eol == 0) {
196 #if 0
197  qDebug("PlayBackPulseAudio: [%d] sink='%s' (%s) driver='%s'"\
198  "card=%d, ports=%d",
199  info->index,
200  info->name,
201  info->description,
202  info->driver,
203  info->card,
204  info->n_ports
205  );
206  for (int p = 0; p < info->n_ports; p++) {
207  qDebug(" [%2d] - '%s' (%s), prio=%d%s",
208  p,
209  info->ports[p]->name,
210  info->ports[p]->description,
211  info->ports[p]->priority,
212  (info->ports[p] == info->active_port) ? " <*>" : ""
213  );
214  }
215 #endif
216  sink_info_t i;
217  i.m_name = QString::fromUtf8(info->name);
218  i.m_description = QString::fromUtf8(info->description);
219  i.m_driver = QString::fromUtf8(info->driver);
220  i.m_card = info->card;
221  i.m_sample_spec = info->sample_spec;
222 
223  QString name = QString::number(m_device_list.count());
224  m_device_list[name] = i;
225 
226  } else {
227  m_mainloop_signal.wakeAll();
228  }
229 }
230 
231 //***************************************************************************
233 {
234  Q_ASSERT(stream);
235  if (!stream || (stream != m_pa_stream)) return;
236 
237  pa_stream_state_t state = pa_stream_get_state(stream);
238 // switch (state) {
239 // case PA_STREAM_UNCONNECTED:
240 // qDebug(" -> UNCONNECTED"); break;
241 // case PA_STREAM_CREATING:
242 // qDebug(" -> CREATING"); break;
243 // case PA_STREAM_READY:
244 // qDebug(" -> READY"); break;
245 // case PA_STREAM_FAILED:
246 // qDebug(" -> FAILED"); break;
247 // case PA_STREAM_TERMINATED:
248 // qDebug(" -> TERMINATED"); break;
249 // default:
250 // Q_ASSERT(0 && "?");
251 // qDebug(" -> ???"); break;
252 // }
253  switch (state) {
254  case PA_STREAM_UNCONNECTED:
255  case PA_STREAM_CREATING:
256  break;
257  case PA_STREAM_READY:
258  case PA_STREAM_FAILED:
259  case PA_STREAM_TERMINATED:
260  m_mainloop_signal.wakeAll();
261  break;
262  }
263 }
264 
265 //***************************************************************************
266 void Kwave::PlayBackPulseAudio::notifyWrite(pa_stream *stream, size_t nbytes)
267 {
268 // qDebug("PlayBackPulseAudio::notifyWrite(stream=%p, nbytes=%u)",
269 // static_cast<void *>(stream), nbytes);
270  Q_UNUSED(nbytes);
271 
272  Q_ASSERT(stream);
273  Q_ASSERT(stream == m_pa_stream);
274  if (!stream || (stream != m_pa_stream)) return;
275 
276  m_mainloop_signal.wakeAll();
277 }
278 
279 //***************************************************************************
280 void Kwave::PlayBackPulseAudio::notifySuccess(pa_stream* stream, int success)
281 {
282 // qDebug("PlayBackPulseAudio::notifySuccess(stream=%p, success=%d)",
283 // static_cast<void *>(stream), success);
284  Q_UNUSED(success);
285 
286  Q_ASSERT(stream);
287  Q_ASSERT(stream == m_pa_stream);
288  if (!stream || (stream != m_pa_stream)) return;
289 
290  m_mainloop_signal.wakeAll();
291 }
292 
293 //***************************************************************************
294 static int poll_func(struct pollfd *ufds, unsigned long nfds,
295  int timeout, void *userdata)
296 {
298  static_cast<Kwave::PlayBackPulseAudio *>(userdata);
299  Q_ASSERT(dev);
300  if (!dev) return -1;
301 
302  return dev->mainloopPoll(ufds, nfds, timeout);
303 }
304 
305 //***************************************************************************
307  unsigned long int nfds,
308  int timeout)
309 {
310  m_mainloop_lock.unlock();
311  int retval = poll(ufds, nfds, timeout);
312  m_mainloop_lock.lock();
313 
314  return retval;
315 }
316 
317 //***************************************************************************
319 {
320  if (m_pa_context) return true; // already connected
321 
322  // set hourglass cursor, we are waiting...
323  QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
324 
325  // create a property list for this application
326  m_pa_proplist = pa_proplist_new();
327  Q_ASSERT(m_pa_proplist);
328 
329  pa_proplist_sets(m_pa_proplist, PA_PROP_APPLICATION_LANGUAGE,
330  UTF8(QLocale::system().name()));
331  pa_proplist_sets(m_pa_proplist, PA_PROP_APPLICATION_NAME,
332  UTF8(qApp->applicationName()));
333  pa_proplist_sets(m_pa_proplist, PA_PROP_APPLICATION_ICON_NAME,
334  "kwave");
335  pa_proplist_sets(m_pa_proplist, PA_PROP_APPLICATION_PROCESS_BINARY,
336  "kwave");
337  pa_proplist_setf(m_pa_proplist, PA_PROP_APPLICATION_PROCESS_ID,
338  "%ld", static_cast<long int>(qApp->applicationPid()));
339  KUser user;
340  pa_proplist_sets(m_pa_proplist, PA_PROP_APPLICATION_PROCESS_USER,
341  UTF8(user.loginName()));
342  pa_proplist_sets(m_pa_proplist, PA_PROP_APPLICATION_VERSION,
343  UTF8(qApp->applicationVersion()));
344 
345  pa_proplist_sets(m_pa_proplist, PA_PROP_MEDIA_ROLE, "production");
346 
347  // ignore SIGPIPE in this context
348 #ifdef HAVE_SIGNAL_H
349  signal(SIGPIPE, SIG_IGN);
350 #endif
351 
352  m_pa_mainloop = pa_mainloop_new();
353  Q_ASSERT(m_pa_mainloop);
354  pa_mainloop_set_poll_func(m_pa_mainloop, poll_func, this);
355 
356  m_pa_context = pa_context_new_with_proplist(
357  pa_mainloop_get_api(m_pa_mainloop),
358  "Kwave",
360  );
361 
362  // set the callback for getting informed about the context state
363  pa_context_set_state_callback(m_pa_context, pa_context_notify_cb, this);
364 
365  // connect to the pulse audio server server
366  bool failed = false;
367  int error = pa_context_connect(
368  m_pa_context, // context
369  Q_NULLPTR, // server
370  static_cast<pa_context_flags_t>(0), // flags
371  Q_NULLPTR // API
372  );
373  if (error < 0)
374  {
375  qWarning("PlayBackPulseAudio: pa_contect_connect failed (%s)",
376  pa_strerror(pa_context_errno(m_pa_context)));
377  failed = true;
378  }
379 
380  if (!failed) {
381  m_mainloop_lock.lock();
383 
384  // wait until the context state is either connected or failed
385  failed = true;
388  {
389  if (pa_context_get_state(m_pa_context) == PA_CONTEXT_READY) {
390  qDebug("PlayBackPulseAudio: context is ready :-)");
391  failed = false;
392  }
393  }
394  m_mainloop_lock.unlock();
395 
396  if (failed) {
397  qWarning("PlayBackPulseAudio: context FAILED (%s):-(",
398  pa_strerror(pa_context_errno(m_pa_context)));
399  }
400  }
401 
402  // if the connection failed, clean up...
403  if (failed) {
405  }
406 
407  QApplication::restoreOverrideCursor();
408 
409  return !failed;
410 }
411 
412 //***************************************************************************
414 {
415  // stop the main loop
417  if (m_pa_mainloop) {
418  m_mainloop_lock.lock();
419  pa_mainloop_quit(m_pa_mainloop, 0);
420  m_mainloop_lock.unlock();
421  }
423 
424  // disconnect the pulse context
425  if (m_pa_context) {
426  pa_context_disconnect(m_pa_context);
427  pa_context_unref(m_pa_context);
428  m_pa_context = Q_NULLPTR;
429  }
430 
431  // stop and free the main loop
432  if (m_pa_mainloop) {
433  pa_mainloop_free(m_pa_mainloop);
434  m_pa_mainloop = Q_NULLPTR;
435  qDebug("PlayBackPulseAudio: mainloop freed");
436  }
437 
438  // release the property list
439  if (m_pa_proplist) {
440  pa_proplist_free(m_pa_proplist);
441  m_pa_proplist = Q_NULLPTR;
442  }
443 
444 }
445 
446 //***************************************************************************
447 QString Kwave::PlayBackPulseAudio::open(const QString &device, double rate,
448  unsigned int channels,
449  unsigned int bits,
450  unsigned int bufbase)
451 {
452  #define SET_PROPERTY(__property__,__info__) \
453  if (m_info.contains(__info__)) \
454  pa_proplist_sets(_proplist, __property__, \
455  m_info.get(__info__).toString().toUtf8().data())
456 
457  qDebug("PlayBackPulseAudio::open(device=%s,rate=%0.1f,channels=%u,"\
458  "bits=%u, bufbase=%u)",
459  DBG(device.split(_("|")).at(0)), rate, channels,
460  bits, bufbase);
461 
462  m_rate = rate;
463 
464  if (channels > 255)
465  return i18n("%1 channels are not supported, maximum is 255", channels);
466 
467  // close the previous device
468  if (m_pa_stream) close();
469 
470  // make sure that we are connected to the sound server
471  if (!connectToServer()) {
472  return i18n("Connecting to the PulseAudio server failed.");
473  }
474 
475  if (!m_device_list.contains(device)) scanDevices();
476  if (!m_device_list.contains(device)) {
477  return i18n(
478  "The PulseAudio device '%1' is unknown or no longer connected",
479  device.section(QLatin1Char('|'), 0, 0));
480  }
481  QString pa_device = m_device_list[device].m_name;
482 
483  // determine the buffer size
484  m_bytes_per_sample = sizeof(sample_t) * channels;
485  m_buffer_size = 0;
486  m_buffer = Q_NULLPTR;
487  m_bufbase = bufbase;
488 
489  // build a property list for the stream
490  pa_proplist *_proplist = pa_proplist_copy(m_pa_proplist);
491  Q_ASSERT(_proplist);
492  SET_PROPERTY(PA_PROP_MEDIA_TITLE, Kwave::INF_NAME);
493  SET_PROPERTY(PA_PROP_MEDIA_ARTIST, Kwave::INF_AUTHOR);
494 #ifdef PA_PROP_MEDIA_COPYRIGHT
495  SET_PROPERTY(PA_PROP_MEDIA_COPYRIGHT, Kwave::INF_COPYRIGHT);
496 #endif
497 #ifdef PA_PROP_MEDIA_SOFTWARE
498  SET_PROPERTY(PA_PROP_MEDIA_SOFTWARE, Kwave::INF_SOFTWARE);
499 #endif
500 // SET_PROPERTY(PA_PROP_MEDIA_LANGUAGE, Kwave::INF_...);
501  SET_PROPERTY(PA_PROP_MEDIA_FILENAME, Kwave::INF_FILENAME);
502 // SET_PROPERTY(PA_PROP_MEDIA_ICON_NAME, Kwave::INF_...);
503 
504  // use Kwave's internal sample format as output
505  pa_sample_spec sample_spec;
506 #if (Q_BYTE_ORDER == Q_BIG_ENDIAN)
507  sample_spec.format = PA_SAMPLE_S24_32BE;
508 #else
509  sample_spec.format = PA_SAMPLE_S24_32LE;
510 #endif
511  sample_spec.channels = static_cast<uint8_t>(channels);
512  sample_spec.rate = static_cast<uint32_t>(m_rate);
513 
514  // use the current title / filename or fixed string as stream name
515  QString name;
516  if (m_info.contains(Kwave::INF_NAME)) // first choice: title
517  name = m_info.get(Kwave::INF_NAME).toString();
518  // fallback: filename
519  if (!name.length() && m_info.contains(Kwave::INF_FILENAME))
520  name = m_info.get(Kwave::INF_FILENAME).toString();
521  if (!name.length()) // last resort: fixed string
522  name = i18n("playback...");
523 
524  // run with mainloop locked from here on...
525  m_mainloop_lock.lock();
526 
527  // create a new stream
528  m_pa_stream = pa_stream_new_with_proplist(
529  m_pa_context,
530  name.toUtf8().data(),
531  &sample_spec,
532  Q_NULLPTR /* const pa_channel_map *map */,
533  _proplist);
534  pa_proplist_free(_proplist);
535 
536  if (!m_pa_stream) {
537  m_mainloop_lock.unlock();
538  return i18n("Failed to create a PulseAudio stream (%1).",
539  QString::fromLocal8Bit(
540  pa_strerror(pa_context_errno(m_pa_context))));
541  }
542  qDebug("PlayBackPulseAudio::open(...) - stream created as %p",
543  static_cast<void *>(m_pa_stream));
544 
545  // register callbacks for changes in stream state and write events
546  pa_stream_set_state_callback(m_pa_stream, pa_stream_state_cb, this);
547  pa_stream_set_write_callback(m_pa_stream, pa_write_cb, this);
548 
549  // set buffer attributes
550  if (m_bufbase < 10) m_bufbase = 10;
551  const int s = ((1 << m_bufbase) * m_bytes_per_sample) / m_bytes_per_sample;
552  pa_buffer_attr attr;
553  attr.fragsize = -1;
554  attr.maxlength = s;
555  attr.minreq = -1;
556  attr.prebuf = -1;
557  attr.tlength = -1;
558 
559  // connect the stream in playback mode
560  int result = pa_stream_connect_playback(
561  m_pa_stream,
562  pa_device.length() ? pa_device.toUtf8().data() : Q_NULLPTR,
563  &attr /* buffer attributes */,
564  static_cast<pa_stream_flags_t>(
565  PA_STREAM_INTERPOLATE_TIMING |
566  PA_STREAM_AUTO_TIMING_UPDATE),
567  Q_NULLPTR /* volume */,
568  Q_NULLPTR /* sync stream */ );
569 
570  if (result >= 0) {
572  if (pa_stream_get_state(m_pa_stream) != PA_STREAM_READY)
573  result = -1;
574  }
575  m_mainloop_lock.unlock();
576 
577  if (result < 0) {
578  pa_stream_unref(m_pa_stream);
579  m_pa_stream = Q_NULLPTR;
580  return i18n("Failed to open a PulseAudio stream for playback (%1).",
581  QString::fromLocal8Bit(
582  pa_strerror(pa_context_errno(m_pa_context))));
583  }
584 
585  return QString();
586 }
587 
588 //***************************************************************************
590 {
591  unsigned int bytes = m_bytes_per_sample;
592 
593  // abort if byte per sample is unknown
594  Q_ASSERT(m_bytes_per_sample);
595  Q_ASSERT(m_pa_mainloop);
597  return -EINVAL;
598 
599  // start with a new/empty buffer from PulseAudio
600  if (!m_buffer) {
601  m_mainloop_lock.lock();
602 
603  // estimate buffer size and round to whole samples
604  size_t size = -1;
606 
607  // get a buffer from PulseAudio
608  int result = pa_stream_begin_write(m_pa_stream, &m_buffer, &size);
609  size /= m_bytes_per_sample;
610  size *= m_bytes_per_sample;
611 
612  // we don't use all of it to reduce latency, use only the
613  // minimum of our configured size and pulse audio's offer
614  if (size < m_buffer_size)
615  m_buffer_size = size;
616 
617  m_mainloop_lock.unlock();
618 
619  if (result < 0) {
620  qWarning("PlayBackPulseAudio: pa_stream_begin_write failed");
621  return -EIO;
622  }
623 
624 // qDebug("PlayBackPulseAudio::write(): got buffer %p, size=%u bytes",
625 // m_buffer, m_buffer_size);
626  }
627 
628  // abort with out-of-memory if failed
629  Q_ASSERT(m_buffer);
630  if (!m_buffer || !m_buffer_size)
631  return -ENOMEM;
632 
633  Q_ASSERT (m_buffer_used + bytes <= m_buffer_size);
634  if (m_buffer_used + bytes > m_buffer_size) {
635  qWarning("PlayBackPulseAudio::write(): buffer overflow ?! (%u/%u)",
638  m_buffer_used = 0;
639  return -EIO;
640  }
641 
642  // copy the samples
643  MEMCPY(reinterpret_cast<quint8 *>(m_buffer) + m_buffer_used,
644  samples.constData(), bytes);
645  m_buffer_used += bytes;
646 
647  // write the buffer if it is full
648  if (m_buffer_used >= m_buffer_size) return flush();
649  return 0;
650 }
651 
652 //***************************************************************************
654 {
656  return 0;
657 // qDebug("PlayBackPulseAudio::flush(): using buffer %p (%u bytes)",
658 // m_buffer, m_buffer_size);
659 
660  // calculate a reasonable time for the timeout (16 buffers)
661  int samples_per_buffer = Kwave::toInt(
663  );
664  int ms = (!qFuzzyIsNull(m_rate)) ?
665  Kwave::toInt((samples_per_buffer * 1000.0) / m_rate) : 0;
666  int timeout = (ms + 1) * 16;
667  if (timeout < TIMEOUT_MIN_FLUSH)
668  timeout = TIMEOUT_MIN_FLUSH;
669 
670  // write out the buffer allocated before in "write"
671  int result = 0;
672 
673  while (m_buffer_used) {
674  size_t len;
675 
676  m_mainloop_lock.lock();
677  while (!(len = pa_stream_writable_size(m_pa_stream))) {
678  if (!PA_CONTEXT_IS_GOOD(pa_context_get_state(m_pa_context)) ||
679  !PA_STREAM_IS_GOOD(pa_stream_get_state(m_pa_stream)) ||
680  (static_cast<ssize_t>(len) == -1) )
681  {
682  qWarning("PlayBackPulseAudio::flush(): bad stream state");
683  result = -1;
684  break;
685  }
686  if (!m_mainloop_signal.wait(&m_mainloop_lock, timeout)) {
687  qWarning("PlayBackPulseAudio::flush(): timed out after %u ms",
688  timeout);
689  result = -1;
690  break;
691  }
692  }
693  m_mainloop_lock.unlock();
694  if (result < 0) break;
695 
696  if (len > m_buffer_used) len = m_buffer_used;
697 
698 // qDebug("PlayBackPulseAudio::flush(): writing %u bytes...", len);
699  m_mainloop_lock.lock();
700  result = pa_stream_write(
701  m_pa_stream,
702  m_buffer,
703  len,
704  Q_NULLPTR,
705  0,
706  PA_SEEK_RELATIVE
707  );
708  m_mainloop_lock.unlock();
709 
710  if (result < 0) {
711  qWarning("PlayBackPulseAudio::flush(): pa_stream_write failed");
712  return -EIO;
713  }
714 
715  m_buffer = reinterpret_cast<quint8 *>(m_buffer) + len;
716  m_buffer_used -= len;
717  }
718 
719 // qDebug("PlayBackPulseAudio::flush(): flush done.");
720 
721  // buffer is written out now
722  m_buffer_used = 0;
723  m_buffer = Q_NULLPTR;
724  return result;
725 }
726 
727 //***************************************************************************
728 void Kwave::PlayBackPulseAudio::run_wrapper(const QVariant &params)
729 {
730  Q_UNUSED(params);
731  m_mainloop_lock.lock();
732  pa_mainloop_run(m_pa_mainloop, Q_NULLPTR);
733  m_mainloop_lock.unlock();
734 }
735 
736 //***************************************************************************
738 {
739  // set hourglass cursor, we are waiting...
740  QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
741 
742  if (m_buffer_used) flush();
743 
744  if (m_pa_mainloop && m_pa_stream) {
745 
746  pa_operation *op = Q_NULLPTR;
747  m_mainloop_lock.lock();
748  op = pa_stream_drain(m_pa_stream, pa_stream_success_cb, this);
749  Q_ASSERT(op);
750  if (!op) qWarning("pa_stream_drain() failed: '%s'", pa_strerror(
751  pa_context_errno(m_pa_context)));
752 
753  // calculate a reasonable time for the timeout (16 buffers)
754  int samples_per_buffer = Kwave::toInt(
756  );
757  int ms = (!qFuzzyIsNull(m_rate)) ?
758  Kwave::toInt((samples_per_buffer * 1000.0) / m_rate) : 0;
759  int timeout = (ms + 1) * 4;
760  if (timeout < TIMEOUT_MIN_DRAIN)
761  timeout = TIMEOUT_MIN_DRAIN;
762 
763  qDebug("PlayBackPulseAudio::flush(): waiting for drain to finish...");
764  while (op && (pa_operation_get_state(op) != PA_OPERATION_DONE)) {
765  if (!PA_CONTEXT_IS_GOOD(pa_context_get_state(m_pa_context)) ||
766  !PA_STREAM_IS_GOOD(pa_stream_get_state(m_pa_stream))) {
767  qWarning("PlayBackPulseAudio::close(): bad stream state");
768  break;
769  }
770  if (!m_mainloop_signal.wait(&m_mainloop_lock, timeout)) {
771  qWarning("PlayBackPulseAudio::flush(): timed out after %u ms",
772  timeout);
773  break;
774  }
775  }
776  m_mainloop_lock.unlock();
777 
778  if (m_pa_stream) {
779  pa_stream_disconnect(m_pa_stream);
780  pa_stream_unref(m_pa_stream);
781  m_pa_stream = Q_NULLPTR;
782  }
783  }
784 
786  m_device_list.clear();
787 
788  QApplication::restoreOverrideCursor();
789  return 0;
790 }
791 
792 //***************************************************************************
794 {
796  if (!m_pa_context) return;
797 
798  // fetch the device list from the PulseAudio server
799  m_mainloop_lock.lock();
800  m_device_list.clear();
801  pa_operation *op_sink_info = pa_context_get_sink_info_list(
802  m_pa_context,
804  this
805  );
806  if (op_sink_info) {
807  // set hourglass cursor, we have a long timeout...
808  QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
810  QApplication::restoreOverrideCursor();
811  }
812 
813  // create a list with final names
814 // qDebug("----------------------------------------");
815  QMap<QString, sink_info_t> list;
816 
817  // first entry == default device
818  sink_info_t i;
819  pa_sample_spec s;
820  s.format = PA_SAMPLE_INVALID;
821  s.rate = 0;
822  s.channels = 0;
823  i.m_name = QString();
824  i.m_description = _("(server default)");
825  i.m_driver = QString();
826  i.m_card = -1;
827  i.m_sample_spec = s;
828  list[i18n("(Use server default)") + _("|sound_note")] = i;
829 
830  foreach (QString sink, m_device_list.keys()) {
831  QString name = m_device_list[sink].m_name;
832  QString description = m_device_list[sink].m_description;
833  QString driver = m_device_list[sink].m_driver;
834 
835  // if the name is not unique, add the internal sink name
836  int unique = true;
837  foreach (QString sn, m_device_list.keys()) {
838  if (sn == sink) continue;
839  if ((m_device_list[sn].m_description == description) &&
840  (m_device_list[sn].m_driver == driver))
841  {
842  unique = false;
843  break;
844  }
845  }
846  if (!unique) description += _(" [") + name + _("]");
847 
848  // mangle the driver name, e.g.
849  // "module-alsa-sink.c" -> "alsa sink"
850  QFileInfo f(driver);
851  driver = f.baseName();
852  driver.replace(_("-"), _(" "));
853  driver.replace(_("_"), _(" "));
854  if (driver.toLower().startsWith(_("module ")))
855  driver.remove(0, 7);
856  description.prepend(driver + _("|sound_card||"));
857 
858  // add the leaf node
859  if (m_device_list[sink].m_card != PA_INVALID_INDEX)
860  description.append(_("|sound_device"));
861  else
862  description.append(_("|sound_note"));
863 
864 // qDebug("supported device: '%s'", DBG(description));
865  list.insert(description, m_device_list[sink]);
866  }
867 // qDebug("----------------------------------------");
868 
869  m_device_list = list;
870  m_mainloop_lock.unlock();
871 }
872 
873 //***************************************************************************
875 {
876  QStringList list;
877 
878  // re-validate the list if necessary
879  scanDevices();
880 
881  if (!m_pa_mainloop || !m_pa_context) return list;
882 
883  list = m_device_list.keys();
884  if (!list.isEmpty()) list.prepend(_("#TREE#"));
885 
886  return list;
887 }
888 
889 //***************************************************************************
891 {
892  return _("");
893 }
894 
895 //***************************************************************************
897 (
898  const QString &device
899 )
900 {
901  QList<unsigned int> list;
902 
903  if ( m_device_list.isEmpty() || !m_device_list.contains(device) ||
904  !pa_sample_spec_valid(&m_device_list[device].m_sample_spec) )
905  return list;
906 
907  list.append(Kwave::toUint(
908  pa_sample_size(&m_device_list[device].m_sample_spec) * 8)
909  );
910 
911  return list;
912 }
913 
914 //***************************************************************************
916  unsigned int &min,
917  unsigned int &max)
918 {
919  min = max = 0;
920 
921  if (m_device_list.isEmpty() || !m_device_list.contains(device))
922  return -1;
923 
924  min = max = m_device_list[device].m_sample_spec.channels;
925  return 0;
926 }
927 
928 #endif /* HAVE_PULSEAUDIO_SUPPORT */
929 
930 //***************************************************************************
931 //***************************************************************************
bool contains(const FileProperty property) const
Definition: FileInfo.cpp:354
static void pa_context_notify_cb(pa_context *c, void *data)
virtual int close() Q_DECL_OVERRIDE
#define SET_PROPERTY(__property__, __info__)
Definition: App.h:33
virtual QStringList supportedDevices() Q_DECL_OVERRIDE
Kwave::WorkerThread m_mainloop_thread
QVariant get(FileProperty key) const
Definition: FileInfo.cpp:372
#define TIMEOUT_WAIT_DEVICE_SCAN
#define TIMEOUT_MIN_DRAIN
void notifyStreamState(pa_stream *stream)
const char name[16]
Definition: memcpy.c:510
static int poll_func(struct pollfd *ufds, unsigned long nfds, int timeout, void *userdata)
#define TIMEOUT_CONNECT_PLAYBACK
virtual ~PlayBackPulseAudio() Q_DECL_OVERRIDE
#define TIMEOUT_MIN_FLUSH
int toInt(T x)
Definition: Utils.h:127
void notifyContext(pa_context *c)
virtual void cancel()
PlayBackPulseAudio(const Kwave::FileInfo &info)
static void pa_sink_info_cb(pa_context *c, const pa_sink_info *info, int eol, void *userdata)
#define MEMCPY
Definition: memcpy.h:37
#define TIMEOUT_CONNECT_TO_SERVER
virtual QString open(const QString &device, double rate, unsigned int channels, unsigned int bits, unsigned int bufbase) Q_DECL_OVERRIDE
virtual QString fileFilter() Q_DECL_OVERRIDE
static void pa_stream_state_cb(pa_stream *p, void *userdata)
const sample_t * constData() const
Definition: SampleArray.h:54
virtual int write(const Kwave::SampleArray &samples) Q_DECL_OVERRIDE
static void pa_write_cb(pa_stream *p, size_t nbytes, void *userdata)
virtual QList< unsigned int > supportedBits(const QString &device) Q_DECL_OVERRIDE
#define _(m)
Definition: memcpy.c:66
#define DBG(qs)
Definition: String.h:55
virtual int stop(unsigned int timeout=10000)
QMap< QString, sink_info_t > m_device_list
void notifySuccess(pa_stream *stream, int success)
virtual void start()
unsigned int toUint(T x)
Definition: Utils.h:109
virtual void run_wrapper(const QVariant &params) Q_DECL_OVERRIDE
void notifyWrite(pa_stream *stream, size_t nbytes)
static void pa_stream_success_cb(pa_stream *s, int success, void *userdata)
int mainloopPoll(struct pollfd *ufds, unsigned long int nfds, int timeout)
#define UTF8(qs)
Definition: String.h:48
virtual int detectChannels(const QString &device, unsigned int &min, unsigned int &max) Q_DECL_OVERRIDE
void notifySinkInfo(pa_context *c, const pa_sink_info *info, int eol)
qint32 sample_t
Definition: Sample.h:37