kwave  18.07.70
Record-PulseAudio.cpp
Go to the documentation of this file.
1 /*************************************************************************
2  Record-PulseAudio.cpp - device for audio recording via PulesAudio
3  -------------------
4  begin : Sun Okt 20 2013
5  copyright : (C) 2014 by Joerg-Christian Boehme
6  email : joerg@chaosdorf.de
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
18 #include "config.h"
19 #ifdef HAVE_PULSEAUDIO_SUPPORT
20 
21 #include <errno.h>
22 #include <signal.h>
23 #include <unistd.h>
24 
25 #include <limits>
26 
27 #include <pulse/thread-mainloop.h>
28 
29 #include <QApplication>
30 #include <QCursor>
31 #include <QFileInfo>
32 #include <QLatin1Char>
33 #include <QLocale>
34 #include <QString>
35 #include <QVariant>
36 #include <QtGlobal>
37 
38 #include <KLocalizedString>
39 #include <KUser>
40 
41 #include "libkwave/Compression.h"
42 #include "libkwave/SampleFormat.h"
43 #include "libkwave/String.h"
44 #include "libkwave/Utils.h"
45 #include "libkwave/memcpy.h"
46 
47 #include "Record-PulseAudio.h"
48 
50 #define ELEMENTS_OF(__array__) (sizeof(__array__) / sizeof(__array__[0]))
51 
56 #define TIMEOUT_WAIT_DEVICE_SCAN 10000
57 
62 #define TIMEOUT_CONNECT_TO_SERVER 20000
63 
68 #define TIMEOUT_CONNECT_RECORD 10000
69 
74 #define TIMEOUT_DISCONNECT_STREAM 10000
75 
89 static const pa_sample_format_t _known_formats[] =
90 {
91  /* 8 bit */
92  PA_SAMPLE_U8,
93 
94  /* 16 bit */
95  PA_SAMPLE_S16LE, PA_SAMPLE_S16BE,
96 
97  /* 24 bit */
98  PA_SAMPLE_S24LE, PA_SAMPLE_S24BE,
99 
100  /* 24 bit in LSB of 32 bit */
101  PA_SAMPLE_S24_32LE, PA_SAMPLE_S24_32BE,
102 
103  /* 32 bit */
104  PA_SAMPLE_S32LE, PA_SAMPLE_S32BE,
105 
106  /* float 32 bit */
107  PA_SAMPLE_FLOAT32LE, PA_SAMPLE_FLOAT32BE,
108 
109  /* ULAW */
110  PA_SAMPLE_ULAW,
111 
112  /* ALAW */
113  PA_SAMPLE_ALAW
114 };
115 
116 //***************************************************************************
118 static Kwave::SampleFormat::Format sample_format_of(pa_sample_format_t fmt)
119 {
121  switch (fmt) {
122  case PA_SAMPLE_FLOAT32LE: /* FALLTHROUGH */
123  case PA_SAMPLE_FLOAT32BE:
124  sampleFormat = Kwave::SampleFormat::Float;
125  break;
126  case PA_SAMPLE_U8:
127  sampleFormat = Kwave::SampleFormat::Unsigned;
128  break;
129  default:
130  sampleFormat = Kwave::SampleFormat::Signed;
131  break;
132  }
133  return sampleFormat;
134 }
135 
136 //***************************************************************************
138 static Kwave::byte_order_t endian_of(pa_sample_format_t fmt)
139 {
140  if (pa_sample_format_is_le(fmt) == 1)
141  return Kwave::LittleEndian;
142  if (pa_sample_format_is_be(fmt) == 1)
143  return Kwave::BigEndian;
144  return Kwave::CpuEndian;
145 }
146 
147 //***************************************************************************
148 static Kwave::Compression::Type compression_of(pa_sample_format_t fmt)
149 {
151  switch (fmt) {
152  case PA_SAMPLE_ULAW:
153  compression = Kwave::Compression::G711_ULAW;
154  break;
155  case PA_SAMPLE_ALAW:
156  compression = Kwave::Compression::G711_ALAW;
157  break;
158  default:
159  compression = Kwave::Compression::NONE;
160  break;
161  }
162  return compression;
163 }
164 
165 //***************************************************************************
166 static int bits_of(pa_sample_format_t fmt)
167 {
168  int bits = 0;
169  switch (fmt) {
170  case PA_SAMPLE_ULAW: /* FALLTHROUGH */
171  case PA_SAMPLE_ALAW: /* FALLTHROUGH */
172  case PA_SAMPLE_U8:
173  bits = 8;
174  break;
175  case PA_SAMPLE_S16LE: /* FALLTHROUGH */
176  case PA_SAMPLE_S16BE:
177  bits = 16;
178  break;
179  case PA_SAMPLE_S24LE: /* FALLTHROUGH */
180  case PA_SAMPLE_S24BE: /* FALLTHROUGH */
181  case PA_SAMPLE_S24_32LE: /* FALLTHROUGH */
182  case PA_SAMPLE_S24_32BE:
183  bits = 24;
184  break;
185  case PA_SAMPLE_S32LE: /* FALLTHROUGH */
186  case PA_SAMPLE_S32BE: /* FALLTHROUGH */
187  case PA_SAMPLE_FLOAT32LE: /* FALLTHROUGH */
188  case PA_SAMPLE_FLOAT32BE:
189  bits = 32;
190  break;
191  default:
192  bits = 0;
193  break;
194  }
195  return bits;
196 }
197 
198 //***************************************************************************
200  :Kwave::RecordDevice(),
201  m_mainloop_thread(this, QVariant()),
202  m_mainloop_lock(),
203  m_mainloop_signal(),
204  m_sample_format(Kwave::SampleFormat::Unknown),
205  m_tracks(0),
206  m_rate(0.0),
207  m_compression(Kwave::Compression::NONE),
208  m_bits_per_sample(0),
209  m_supported_formats(),
210  m_initialized(false),
211  m_pa_proplist(Q_NULLPTR),
212  m_pa_mainloop(Q_NULLPTR),
213  m_pa_context(Q_NULLPTR),
214  m_pa_stream(Q_NULLPTR),
215  m_pa_device(),
216  m_name(i18n("Kwave record")),
217  m_device_list()
218 {
219 }
220 
221 //***************************************************************************
223 {
225  m_device_list.clear();
226 }
227 
228 //***************************************************************************
230 {
231  // start with an empty list
232  m_supported_formats.clear();
233 
234  // lookup in the device list
235  if (!m_device_list.contains(device))
236  return;
237 
238  const pa_sample_spec &sampleSpec = m_device_list[device].m_sample_spec;
239  const pa_sample_format_t &formatSpec = sampleSpec.format;
240 
241  // try all known formats
242  qDebug("--- list of supported formats --- ");
243  for(unsigned int i = 0; i < ELEMENTS_OF(_known_formats); ++i) {
244  const pa_sample_format_t &fmt = _known_formats[i];
245 
246  if (formatSpec < _known_formats[i])
247  continue;
248 
249  // TODO: avoid duplicate entries that differ only in endianness,
250  // prefer our own (native) endianness
251 
254  qDebug("#%2u, %2u bit [%u byte], %s, '%s', '%s'",
255  i,
256  bits_of(fmt),
257  (bits_of(fmt) + 7) >> 3,
258  endian_of(fmt) == Kwave::CpuEndian ? "CPU" :
259  (endian_of(fmt) == Kwave::LittleEndian ? "LE " : "BE "),
260  DBG(sf.description(sf.findFromData(sample_format_of(fmt)), true)),
261  DBG(t.name())
262  );
263 
264  m_supported_formats.append(fmt);
265  }
266  qDebug("--------------------------------- ");
267 }
268 
269 //***************************************************************************
270 void Kwave::RecordPulseAudio::pa_read_cb(pa_stream *p, size_t nbytes,
271  void *userdata)
272 {
273  Kwave::RecordPulseAudio *record_plugin =
274  reinterpret_cast<Kwave::RecordPulseAudio *>(userdata);
275  Q_ASSERT(record_plugin);
276  if (record_plugin) record_plugin->notifyRead(p, nbytes);
277 }
278 
279 //***************************************************************************
280 void Kwave::RecordPulseAudio::notifyRead(pa_stream *stream, size_t nbytes)
281 {
282  Q_UNUSED(nbytes);
283  Q_ASSERT(stream);
284 
285  if (!stream || (stream != m_pa_stream)) return;
286 
287  m_mainloop_signal.wakeAll();
288 }
289 
290 //***************************************************************************
291 void Kwave::RecordPulseAudio::pa_stream_state_cb(pa_stream *p, void *userdata)
292 {
293  Kwave::RecordPulseAudio *record_plugin =
294  reinterpret_cast<Kwave::RecordPulseAudio *>(userdata);
295  Q_ASSERT(record_plugin);
296  if (record_plugin) record_plugin->notifyStreamState(p);
297 }
298 
299 //***************************************************************************
301 {
302  Q_ASSERT(stream);
303  if (!stream || (stream != m_pa_stream)) return;
304 
305  pa_stream_state_t state = pa_stream_get_state(stream);
306 
307 #ifdef DEBUG
308  #define DBG_CASE(x) case x: qDebug("RecordPulseAudio -> " #x ); break
309  switch (state)
310  {
311  DBG_CASE(PA_STREAM_CREATING);
312  DBG_CASE(PA_STREAM_UNCONNECTED);
313  DBG_CASE(PA_STREAM_FAILED);
314  DBG_CASE(PA_STREAM_TERMINATED);
315  DBG_CASE(PA_STREAM_READY);
316  }
317  #undef DBG_CASE
318 #endif /* DEBUG */
319 
320  switch (state) {
321  case PA_STREAM_CREATING:
322  break;
323  case PA_STREAM_UNCONNECTED: /* FALLTHROUGH */
324  case PA_STREAM_FAILED: /* FALLTHROUGH */
325  case PA_STREAM_TERMINATED: /* FALLTHROUGH */
326  case PA_STREAM_READY:
327  m_mainloop_signal.wakeAll();
328  break;
329  default:
330  Q_ASSERT(0 && "?");
331  break;
332  }
333 }
334 
335 //***************************************************************************
336 void Kwave::RecordPulseAudio::pa_context_notify_cb(pa_context* c, void* userdata)
337 {
338  Kwave::RecordPulseAudio *record_plugin =
339  reinterpret_cast<Kwave::RecordPulseAudio *>(userdata);
340  Q_ASSERT(record_plugin);
341  if (record_plugin) record_plugin->notifyContext(c);
342 }
343 
344 //***************************************************************************
346 {
347  Q_ASSERT(c == m_pa_context);
348  const pa_context_state_t state = pa_context_get_state(c);
349 
350 #ifdef DEBUG
351  #define DBG_CASE(x) case x: qDebug("RecordPulseAudio -> " #x ); break
352  switch (state)
353  {
354  DBG_CASE(PA_CONTEXT_UNCONNECTED);
355  DBG_CASE(PA_CONTEXT_CONNECTING);
356  DBG_CASE(PA_CONTEXT_AUTHORIZING);
357  DBG_CASE(PA_CONTEXT_SETTING_NAME);
358  DBG_CASE(PA_CONTEXT_READY);
359  DBG_CASE(PA_CONTEXT_TERMINATED);
360  DBG_CASE(PA_CONTEXT_FAILED);
361  }
362  #undef DBG_CASE
363 #endif /* DEBUG */
364 
365  switch (state)
366  {
367  case PA_CONTEXT_UNCONNECTED: /* FALLTHROUGH */
368  case PA_CONTEXT_CONNECTING: /* FALLTHROUGH */
369  case PA_CONTEXT_AUTHORIZING: /* FALLTHROUGH */
370  case PA_CONTEXT_SETTING_NAME:
371  break;
372  case PA_CONTEXT_READY: /* FALLTHROUGH */
373  case PA_CONTEXT_TERMINATED: /* FALLTHROUGH */
374  case PA_CONTEXT_FAILED:
375  m_mainloop_signal.wakeAll();
376  break;
377  }
378 }
379 
380 //***************************************************************************
382  int compression, int bits, Kwave::SampleFormat::Format sample_format)
383 {
384  // loop over all supported formats and keep only those that are
385  // compatible with the given compression, bits and sample format
386  foreach (const pa_sample_format_t &fmt, m_supported_formats)
387  {
388  if (compression_of(fmt) != compression) continue;
389  if (bits_of(fmt) != bits) continue;
390  if (!(sample_format_of(fmt) == sample_format)) continue;
391 
392  // mode is compatible
393  // As the list of known formats is already sorted so that
394  // the simplest formats come first, we don't have a lot
395  // of work -> just take the first entry ;-)
396  return fmt;
397  }
398 
399  qWarning("RecordPulesAudio::mode2format -> no match found !?");
400  return PA_SAMPLE_INVALID;
401 }
402 
403 //***************************************************************************
405 {
406  pa_sample_format_t fmt = mode2format(m_compression, m_bits_per_sample,
408  return (fmt != PA_SAMPLE_INVALID) ?
410 }
411 
412 //***************************************************************************
414 {
415  return m_sample_format;
416 }
417 
418 //***************************************************************************
420  Kwave::SampleFormat::Format new_format)
421 {
422  if (m_sample_format == new_format)
423  return 0;
424  close();
425  m_sample_format = new_format;
426  return 0;
427 }
428 
429 //***************************************************************************
430 QList<Kwave::SampleFormat::Format> Kwave::RecordPulseAudio::detectSampleFormats()
431 {
432  QList<Kwave::SampleFormat::Format> list;
433 
434  // try all known sample formats
435  foreach (const pa_sample_format_t &fmt, m_supported_formats)
436  {
437  const Kwave::SampleFormat::Format sample_format = sample_format_of(fmt);
438 
439  // only accept bits/sample if compression types
440  // and bits per sample match
441  if (compression_of(fmt) != m_compression) continue;
443  continue;
444 
445  // do not produce duplicates
446  if (list.contains(sample_format)) continue;
447 
448  list.append(sample_format);
449  }
450 
451  return list;
452 }
453 
454 //***************************************************************************
456 {
457  return m_bits_per_sample;
458 }
459 
460 //***************************************************************************
462 {
463  if (m_bits_per_sample == new_bits)
464  return 0;
465  close();
466  m_bits_per_sample = new_bits;
467  return 0;
468 }
469 
470 //***************************************************************************
472 {
473  QList<unsigned int> list;
474 
475  // try all known sample formats
476  foreach(const pa_sample_format_t &fmt, m_supported_formats)
477  {
478  const unsigned int bits = bits_of(fmt);
479 
480  // 0 bits means invalid/does not apply
481  if (!bits) continue;
482 
483  // only accept bits/sample if compression matches
484  if (compression_of(fmt) != m_compression) continue;
485 
486  // do not produce duplicates
487  if (list.contains(bits)) continue;
488 
489  list.append(bits);
490  }
491 
492  return list;
493 }
494 
495 //***************************************************************************
497 {
498  return m_compression;
499 }
500 
501 //***************************************************************************
503  Kwave::Compression::Type new_compression
504 )
505 {
506  if (m_compression != new_compression) {
507  close();
508  m_compression = new_compression;
509  }
510  return 0;
511 }
512 
513 //***************************************************************************
514 QList<Kwave::Compression::Type> Kwave::RecordPulseAudio::detectCompressions()
515 {
516  QList<Kwave::Compression::Type> list;
517 
518  // try all known sample formats
519  foreach (const pa_sample_format_t &fmt, m_supported_formats)
520  {
522 
523  // do not produce duplicates
524  if (list.contains(compression)) continue;
525 
526  Kwave::Compression t(compression);
527  list.append(compression);
528  }
529 
530  return list;
531 }
532 
533 //***************************************************************************
535 {
536  return m_rate;
537 }
538 
539 //***************************************************************************
541 {
542  if (qFuzzyCompare(new_rate, m_rate))
543  return 0;
544  close();
545  m_rate = new_rate;
546  return 0;
547 }
548 
549 //***************************************************************************
551 {
552  QList<double> list;
553 
554  static const unsigned int known_rates[] = {
555  1,
556  1000, // (just for testing)
557  2000, // (just for testing)
558  4000, // standard OSS
559  5125, // seen in Harmony driver (HP712, 715/new)
560  5510, // seen in AD1848 driver
561  5512, // seen in ES1370 driver
562  6215, // seen in ES188X driver
563  6615, // seen in Harmony driver (HP712, 715/new)
564  6620, // seen in AD1848 driver
565  7350, // seen in AWACS and Burgundy sound driver
566  8000, // standard OSS
567  8820, // seen in AWACS and Burgundy sound driver
568  9600, // seen in AD1848 driver
569  11025, // soundblaster
570  14700, // seen in AWACS and Burgundy sound driver
571  16000, // standard OSS
572  17640, // seen in AWACS and Burgundy sound driver
573  18900, // seen in Harmony driver (HP712, 715/new)
574  22050, // soundblaster
575  24000, // seen in NM256 driver
576  27428, // seen in Harmony driver (HP712, 715/new)
577  29400, // seen in AWACS and Burgundy sound driver
578  32000, // standard OSS
579  32768, // seen in CS4299 driver
580  33075, // seen in Harmony driver (HP712, 715/new)
581  37800, // seen in Harmony driver (HP712, 715/new)
582  44100, // soundblaster
583  48000, // AC97
584  64000, // AC97
585  88200, // seen in RME96XX driver
586  96000, // AC97
587  128000, // (just for testing)
588  192000 // AC97
589  };
590 
591  pa_sample_spec sampleSpec = m_device_list[m_device].m_sample_spec;
592  uint32_t rate = sampleSpec.rate;
593  for (unsigned int i = 0; i < ELEMENTS_OF(known_rates); i++) {
594  if(known_rates[i] <= rate) {
595  list.append(known_rates[i]);
596  }
597  }
598 
599  return list;
600 }
601 
602 //***************************************************************************
604 {
605  return m_tracks;
606 }
607 
608 //***************************************************************************
610 {
611  const quint8 max_tracks = std::numeric_limits<quint8>::max();
612 
613  if (tracks > max_tracks) {
614  tracks = max_tracks;
615  return -1;
616  }
617 
618  if (tracks == m_tracks)
619  return 0;
620 
621  close();
622  m_tracks = static_cast<quint8>(tracks);
623 
624  return 0;
625 }
626 
627 //***************************************************************************
628 int Kwave::RecordPulseAudio::detectTracks(unsigned int &min, unsigned int &max)
629 {
630  pa_sample_spec sampleSpec = m_device_list[m_device].m_sample_spec;
631  unsigned int channels = sampleSpec.channels;
632 
633  min = 1;
634  max = qBound<unsigned int>(min, channels, PA_CHANNELS_MAX);
635  return 0;
636 }
637 
638 //***************************************************************************
640 {
641  if (m_pa_stream) {
642  pa_stream_drop(m_pa_stream);
643 
644  m_mainloop_lock.lock();
645  pa_stream_disconnect(m_pa_stream);
646  qDebug("RecordPulseAudio::close() - waiting for stream disconnect...");
648  m_mainloop_lock.unlock();
649  qDebug("RecordPulseAudio::close() - stream disconnect DONE");
650 
651  pa_stream_unref(m_pa_stream);
652  }
653  m_pa_stream = Q_NULLPTR;
654 
655  // we need to re-initialize the next time
656  m_initialized = false;
657  return 0;
658 }
659 
660 //***************************************************************************
661 int Kwave::RecordPulseAudio::read(QByteArray& buffer, unsigned int offset)
662 {
663  if (buffer.isNull() || buffer.isEmpty())
664  return 0; // no buffer, nothing to do
665 
666  unsigned int length = buffer.size();
667 
668  // we configure our device at a late stage, not on the fly like in OSS
669  if (!m_initialized) {
670  int err = initialize(length);
671  if (err < 0) return err;
672  }
673 
674  m_mainloop_lock.lock();
675 
676  size_t freeBytes = length - offset;
677  size_t readableSize = pa_stream_readable_size(m_pa_stream);
678  if (readableSize > freeBytes) {
679  size_t additional_size = readableSize - freeBytes;
680  buffer.resize(static_cast<int>(length + additional_size));
681  }
682 
683  size_t readLength = 0;
684  if (readableSize > 0) {
685  const void *audioBuffer = Q_NULLPTR;
686  pa_stream_peek(m_pa_stream, &audioBuffer, &readLength);
687 
688  if (offset + readLength > Kwave::toUint(buffer.length())) {
689  pa_stream_drop(m_pa_stream);
690  m_mainloop_lock.unlock();
691  return -EIO; // peek returned invalid length
692  }
693 
694  char *data = buffer.data() + offset;
695  if (audioBuffer) {
696  MEMCPY(data, audioBuffer, readLength); // real data
697  } else {
698  memset(data, 0x00, readLength); // there was a gap
699  }
700 
701  pa_stream_drop(m_pa_stream);
702 
703  } else {
704  m_mainloop_lock.unlock();
705  return -EAGAIN;
706  }
707  m_mainloop_lock.unlock();
708 
709  return Kwave::toInt(readLength);
710 }
711 
712 //***************************************************************************
713 int Kwave::RecordPulseAudio::initialize(uint32_t buffer_size)
714 {
715  Q_ASSERT(!m_initialized);
716 
717  // make sure that we are connected to the sound server
718  if (!connectToServer()) {
719  qWarning("Connecting to the PulseAudio server failed!");
720  return -1;
721  }
722 
723  pa_sample_format_t fmt = mode2format(m_compression, m_bits_per_sample,
725  if (fmt == PA_SAMPLE_INVALID) {
727 
728  qWarning("format: no matching format for compression '%s', "
729  "%d bits/sample, format '%s'",
733  );
734  return -EINVAL;
735  }
736 
737  pa_sample_spec sample_spec;
738  sample_spec.channels = m_tracks;
739  sample_spec.format = fmt;
740  sample_spec.rate = static_cast<quint32>(m_rate);
741 
742  if(!pa_sample_spec_valid(&sample_spec)) {
744 
745  qWarning("no valid pulse audio format: %d, rate: %0.3g, channels: %d",
746  static_cast<int>(fmt), m_rate, m_tracks);
747  return -EINVAL;
748  }
749 
750  // run with mainloop locked from here on...
751  m_mainloop_lock.lock();
752 
753  pa_channel_map channel_map;
754  pa_channel_map_init_extend(&channel_map, sample_spec.channels,
755  PA_CHANNEL_MAP_DEFAULT);
756 
757  if (!pa_channel_map_compatible(&channel_map, &sample_spec)) {
758  qWarning("Channel map doesn't match sample specification!");
759  }
760 
761  // create a new stream
762  m_pa_stream = pa_stream_new(
763  m_pa_context,
764  m_name.toUtf8().constData(),
765  &sample_spec,
766  &channel_map);
767 
768  if (!m_pa_stream) {
769  m_mainloop_lock.unlock();
770  qWarning("Failed to create a PulseAudio stream %s",
771  pa_strerror(pa_context_errno(m_pa_context)));
772  return -1;
773  }
774 
775  pa_stream_set_state_callback(m_pa_stream, pa_stream_state_cb, this);
776  pa_stream_set_read_callback(m_pa_stream, pa_read_cb, this);
777 
778  pa_buffer_attr attr;
779  attr.fragsize = buffer_size;
780  attr.maxlength = static_cast<uint32_t>(-1);
781  attr.minreq = static_cast<uint32_t>(-1);
782  attr.prebuf = static_cast<uint32_t>(-1);
783  attr.tlength = static_cast<uint32_t>(-1);
784  int flags = PA_STREAM_ADJUST_LATENCY;
785 
786  // connect the stream in record mode
787  int result = pa_stream_connect_record(
788  m_pa_stream,
789  m_pa_device.toUtf8().constData(),
790  &attr,
791  static_cast<pa_stream_flags_t>(flags));
792 
793  if (result >= 0) {
795  if (pa_stream_get_state(m_pa_stream) != PA_STREAM_READY)
796  result = -1;
797  }
798 
799  m_mainloop_lock.unlock();
800 
801  if (result < 0) {
802  pa_stream_unref(m_pa_stream);
803  m_pa_stream = Q_NULLPTR;
804  qWarning("Failed to open a PulseAudio stream for record %s",
805  pa_strerror(pa_context_errno(m_pa_context)));
806  return -1;
807  }
808 
809  m_initialized = true;
810  return 0;
811 }
812 
813 //***************************************************************************
814 QString Kwave::RecordPulseAudio::open(const QString& device)
815 {
816  // close the previous device
817  if (m_pa_stream) close();
818 
819  QString pa_device;
820  if (m_device_list.contains(device))
821  pa_device = m_device_list[device].m_name;
822 
823  if (!pa_device.length())
824  return QString::number(ENODEV);
825 
826  m_pa_device = pa_device;
827  m_device = device;
828 
829  // detect all formats the device knows
830  detectSupportedFormats(device);
831 
832  return QString();
833 }
834 
835 //***************************************************************************
837 {
838  QStringList list;
839 
840  // re-validate the list if necessary
841  scanDevices();
842 
843  if (!m_pa_mainloop || !m_pa_context) return list;
844 
845  list = m_device_list.keys();
846  if (!list.isEmpty()) list.prepend(_("#TREE#"));
847 
848  return list;
849 }
850 
851 //***************************************************************************
852 void Kwave::RecordPulseAudio::run_wrapper(const QVariant &params)
853 {
854  Q_UNUSED(params);
855  m_mainloop_lock.lock();
856  pa_mainloop_run(m_pa_mainloop, Q_NULLPTR);
857  m_mainloop_lock.unlock();
858  qDebug("RecordPulseAudio::run_wrapper - done.");
859 }
860 
861 //***************************************************************************
862 static int poll_func(struct pollfd *ufds, unsigned long nfds,
863  int timeout, void *userdata)
864 {
866  static_cast<Kwave::RecordPulseAudio *>(userdata);
867  Q_ASSERT(dev);
868  if (!dev) return -1;
869 
870  return dev->mainloopPoll(ufds, nfds, timeout);
871 }
872 
873 //***************************************************************************
875  unsigned long int nfds,
876  int timeout)
877 {
878  m_mainloop_lock.unlock();
879  int retval = poll(ufds, nfds, timeout);
880  m_mainloop_lock.lock();
881 
882  return retval;
883 }
884 
885 //***************************************************************************
887 {
888  if (m_pa_context) return true; // already connected
889 
890  // set hourglass cursor, we are waiting...
891  QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
892 
893  // create a property list for this application
894  m_pa_proplist = pa_proplist_new();
895  Q_ASSERT(m_pa_proplist);
896 
897  pa_proplist_sets(m_pa_proplist, PA_PROP_APPLICATION_LANGUAGE,
898  UTF8(QLocale::system().name()));
899  pa_proplist_sets(m_pa_proplist, PA_PROP_APPLICATION_NAME,
900  UTF8(qApp->applicationName()));
901  pa_proplist_sets(m_pa_proplist, PA_PROP_APPLICATION_ICON_NAME,
902  "kwave");
903  pa_proplist_sets(m_pa_proplist, PA_PROP_APPLICATION_PROCESS_BINARY,
904  "kwave");
905  pa_proplist_setf(m_pa_proplist, PA_PROP_APPLICATION_PROCESS_ID,
906  "%ld", static_cast<long int>(qApp->applicationPid()));
907  KUser user;
908  pa_proplist_sets(m_pa_proplist, PA_PROP_APPLICATION_PROCESS_USER,
909  UTF8(user.loginName()));
910  pa_proplist_sets(m_pa_proplist, PA_PROP_APPLICATION_VERSION,
911  UTF8(qApp->applicationVersion()));
912 
913  pa_proplist_sets(m_pa_proplist, PA_PROP_MEDIA_ROLE, "production");
914 
915  // ignore SIGPIPE in this context
916 #ifdef HAVE_SIGNAL_H
917  signal(SIGPIPE, SIG_IGN);
918 #endif
919 
920  m_pa_mainloop = pa_mainloop_new();
921  Q_ASSERT(m_pa_mainloop);
922  pa_mainloop_set_poll_func(m_pa_mainloop, poll_func, this);
923 
924  m_pa_context = pa_context_new_with_proplist(
925  pa_mainloop_get_api(m_pa_mainloop),
926  "Kwave",
928  );
929 
930  // set the callback for getting informed about the context state
931  pa_context_set_state_callback(m_pa_context, pa_context_notify_cb, this);
932 
933  // connect to the pulse audio server server
934  bool failed = false;
935  int error = pa_context_connect(
936  m_pa_context, // context
937  Q_NULLPTR, // server
938  static_cast<pa_context_flags_t>(0), // flags
939  Q_NULLPTR // API
940  );
941  if (error < 0)
942  {
943  qWarning("RecordPulseAudio: pa_contect_connect failed (%s)",
944  pa_strerror(pa_context_errno(m_pa_context)));
945  failed = true;
946  }
947 
948  if (!failed) {
949  m_mainloop_lock.lock();
951 
952  // wait until the context state is either connected or failed
953  failed = true;
956  {
957  if (pa_context_get_state(m_pa_context) == PA_CONTEXT_READY) {
958  failed = false;
959  }
960  }
961  m_mainloop_lock.unlock();
962 
963  if (failed) {
964  qWarning("RecordPulseAudio: context FAILED (%s):-(",
965  pa_strerror(pa_context_errno(m_pa_context)));
966  }
967  }
968 
969  // if the connection failed, clean up
970  if (failed) {
972  }
973 
974  QApplication::restoreOverrideCursor();
975 
976  return !failed;
977 }
978 
979 //***************************************************************************
981 {
982  close();
983 
984  // stop the main loop
986  if (m_pa_mainloop) {
987  m_mainloop_lock.lock();
988  pa_mainloop_quit(m_pa_mainloop, 0);
989  m_mainloop_lock.unlock();
990  }
992 
993  // disconnect the pulse context
994  if (m_pa_context) {
995  pa_context_disconnect(m_pa_context);
996  pa_context_unref(m_pa_context);
997  m_pa_context = Q_NULLPTR;
998  }
999 
1000  // stop and free the main loop
1001  if (m_pa_mainloop) {
1002  pa_mainloop_free(m_pa_mainloop);
1003  m_pa_mainloop = Q_NULLPTR;
1004  }
1005 
1006  // release the property list
1007  if (m_pa_proplist) {
1008  pa_proplist_free(m_pa_proplist);
1009  m_pa_proplist = Q_NULLPTR;
1010  }
1011 
1012 }
1013 
1014 //***************************************************************************
1016  const pa_source_info *info,
1017  int eol, void *userdata)
1018 {
1019  Kwave::RecordPulseAudio *record_plugin =
1020  reinterpret_cast<Kwave::RecordPulseAudio *>(userdata);
1021  Q_ASSERT(record_plugin);
1022  if (record_plugin) record_plugin->notifySourceInfo(c, info, eol);
1023 }
1024 
1025 //***************************************************************************
1027  const pa_source_info *info,
1028  int eol)
1029 {
1030  Q_UNUSED(c);
1031  Q_ASSERT(c == m_pa_context);
1032 
1033  if (eol == 0) {
1034  source_info_t i;
1035  i.m_name = QString::fromUtf8(info->name);
1036  i.m_description = QString::fromUtf8(info->description);
1037  i.m_driver = QString::fromUtf8(info->driver);
1038  i.m_card = info->card;
1039  i.m_sample_spec = info->sample_spec;
1040 
1041  QString name = QString::number(m_device_list.count());
1042  m_device_list[name] = i;
1043  } else {
1044  m_mainloop_signal.wakeAll();
1045  }
1046 }
1047 
1048 //***************************************************************************
1050 {
1051  if (!m_pa_context) connectToServer();
1052  if (!m_pa_context) return;
1053 
1054  // fetch the device list from the PulseAudio server
1055  m_mainloop_lock.lock();
1056  m_device_list.clear();
1057  pa_operation *op_source_info = pa_context_get_source_info_list(
1058  m_pa_context,
1060  this
1061  );
1062  if (op_source_info) {
1063  // set hourglass cursor, we have a long timeout...
1064  QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1066  QApplication::restoreOverrideCursor();
1067  }
1068 
1069  // create a list with final names
1070  QMap<QString, source_info_t> list;
1071 
1072  foreach (QString source, m_device_list.keys()) {
1073  QString name = m_device_list[source].m_name;
1074  QString description = m_device_list[source].m_description;
1075  QString driver = m_device_list[source].m_driver;
1076 
1077  // if the name is not unique, add the internal source name
1078  int unique = true;
1079  foreach (QString s, m_device_list.keys()) {
1080  if (s == source) continue;
1081  if ((m_device_list[s].m_description == description) &&
1082  (m_device_list[s].m_driver == driver))
1083  {
1084  unique = false;
1085  break;
1086  }
1087  }
1088  if (!unique) description += _(" [") + name + _("]");
1089 
1090  // mangle the driver name, e.g.
1091  // "module-alsa-sink.c" -> "alsa sink"
1092  QFileInfo f(driver);
1093  driver = f.baseName();
1094  driver.replace(_("-"), _(" "));
1095  driver.replace(_("_"), _(" "));
1096  if (driver.toLower().startsWith(_("module ")))
1097  driver.remove(0, 7);
1098  description.prepend(driver + _("|sound_card||"));
1099 
1100  // add the leaf node
1101  if (m_device_list[source].m_card != PA_INVALID_INDEX)
1102  description.append(_("|sound_device"));
1103  else
1104  description.append(_("|sound_note"));
1105 
1106  list.insert(description, m_device_list[source]);
1107  }
1108 
1109  m_device_list.clear();
1110  m_device_list = list;
1111  m_mainloop_lock.unlock();
1112 }
1113 
1114 #endif /* HAVE_PULSEAUDIO_SUPPORT */
1115 
1116 //***************************************************************************
1117 //***************************************************************************
byte_order_t
Definition: ByteOrder.h:25
pa_sample_format_t mode2format(int compression, int bits, Kwave::SampleFormat::Format sample_format)
Definition: App.h:33
QList< pa_sample_format_t > m_supported_formats
void notifyRead(pa_stream *stream, size_t nbytes)
static const pa_sample_format_t _known_formats[]
virtual int setSampleFormat(Kwave::SampleFormat::Format new_format) Q_DECL_OVERRIDE
QString description(IDX type, bool localized) const
Definition: TypesMap.h:128
virtual void run_wrapper(const QVariant &params) Q_DECL_OVERRIDE
QString name() const
Definition: Compression.cpp:45
void detectSupportedFormats(const QString &device)
#define ELEMENTS_OF(__array__)
void notifyStreamState(pa_stream *stream)
static Kwave::Compression::Type compression_of(pa_sample_format_t fmt)
virtual int bitsPerSample() Q_DECL_OVERRIDE
virtual QStringList supportedDevices() Q_DECL_OVERRIDE
virtual Kwave::Compression::Type compression() Q_DECL_OVERRIDE
#define TIMEOUT_DISCONNECT_STREAM
virtual QList< double > detectSampleRates() Q_DECL_OVERRIDE
const char name[16]
Definition: memcpy.c:510
QWaitCondition m_mainloop_signal
#define TIMEOUT_CONNECT_TO_SERVER
virtual int setCompression(Kwave::Compression::Type new_compression) Q_DECL_OVERRIDE
static void pa_source_info_cb(pa_context *c, const pa_source_info *info, int eol, void *userdata)
static Kwave::byte_order_t endian_of(pa_sample_format_t fmt)
Kwave::WorkerThread m_mainloop_thread
int toInt(T x)
Definition: Utils.h:127
static int poll_func(struct pollfd *ufds, unsigned long nfds, int timeout, void *userdata)
virtual void cancel()
#define MEMCPY
Definition: memcpy.h:37
int initialize(uint32_t buffer_size)
virtual ~RecordPulseAudio() Q_DECL_OVERRIDE
#define TIMEOUT_CONNECT_RECORD
virtual QList< unsigned int > supportedBits() Q_DECL_OVERRIDE
virtual int close() Q_DECL_OVERRIDE
virtual QList< Kwave::SampleFormat::Format > detectSampleFormats() Q_DECL_OVERRIDE
QMap< QString, source_info_t > m_device_list
static Kwave::SampleFormat::Format sample_format_of(pa_sample_format_t fmt)
#define TIMEOUT_WAIT_DEVICE_SCAN
#define _(m)
Definition: memcpy.c:66
virtual Kwave::byte_order_t endianness() Q_DECL_OVERRIDE
#define DBG(qs)
Definition: String.h:55
static void pa_read_cb(pa_stream *p, size_t nbytes, void *userdata)
virtual int stop(unsigned int timeout=10000)
void notifySourceInfo(pa_context *c, const pa_source_info *info, int eol)
static void pa_context_notify_cb(pa_context *c, void *userdata)
virtual int detectTracks(unsigned int &min, unsigned int &max) Q_DECL_OVERRIDE
virtual int setSampleRate(double &new_rate) Q_DECL_OVERRIDE
virtual void start()
unsigned int toUint(T x)
Definition: Utils.h:109
virtual int read(QByteArray &buffer, unsigned int offset) Q_DECL_OVERRIDE
virtual QList< Kwave::Compression::Type > detectCompressions() Q_DECL_OVERRIDE
virtual Kwave::SampleFormat::Format sampleFormat() Q_DECL_OVERRIDE
#define DBG_CASE(x)
void notifyContext(pa_context *c)
virtual QString open(const QString &dev) Q_DECL_OVERRIDE
int mainloopPoll(struct pollfd *ufds, unsigned long int nfds, int timeout)
static void pa_stream_state_cb(pa_stream *p, void *userdata)
virtual double sampleRate() Q_DECL_OVERRIDE
static int bits_of(pa_sample_format_t fmt)
Kwave::SampleFormat::Format m_sample_format
IDX findFromData(const DATA &data) const
Definition: TypesMap.h:89
#define UTF8(qs)
Definition: String.h:48
virtual int setBitsPerSample(unsigned int new_bits) Q_DECL_OVERRIDE
virtual int setTracks(unsigned int &tracks) Q_DECL_OVERRIDE
virtual int tracks() Q_DECL_OVERRIDE
Kwave::Compression::Type m_compression