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
49 /** helper macro: returns the number of elements in an array */
50 #define ELEMENTS_OF(__array__) (sizeof(__array__) / sizeof(__array__[0]))
51
52 /**
53 * timeout for the device scan [ms]
54 * @see scanDevices()
55 */
56 #define TIMEOUT_WAIT_DEVICE_SCAN 10000
57
58 /**
59 * timeout to wait for the connection to the server [ms]
60 * @see connectToServer()
61 */
62 #define TIMEOUT_CONNECT_TO_SERVER 20000
63
64 /**
65 * timeout to wait for record [ms]
66 * @see open()
67 */
68 #define TIMEOUT_CONNECT_RECORD 10000
69
70 /**
71 * timeout to wait for disconnecting the recording stream [ms]
72 * @see close()
73 */
74 #define TIMEOUT_DISCONNECT_STREAM 10000
75
76 /**
77 * Global list of all known sample formats.
78 * @note this list should be sorted so that the most preferable formats
79 * come first in the list. When searching for a format that matches
80 * a given set of parameters, the first entry is taken.
81 *
82 * The sort order should be:
83 * - compression: none -> ulaw -> alaw -> adpcm -> mpeg ...
84 * - bits per sample: ascending
85 * - sample format: signed -> unsigned -> float -> double ...
86 * - endianness: cpu -> little -> big
87 * - bytes per sample: ascending
88 */
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 //***************************************************************************
117 /** find out the SampleFormat of an PulseAudio format */
sample_format_of(pa_sample_format_t fmt)118 static Kwave::SampleFormat::Format sample_format_of(pa_sample_format_t fmt)
119 {
120 Kwave::SampleFormat::Format sampleFormat = Kwave::SampleFormat::Unknown;
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 //***************************************************************************
137 /** find out the endianness of an PulseAudio format */
endian_of(pa_sample_format_t fmt)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 //***************************************************************************
compression_of(pa_sample_format_t fmt)148 static Kwave::Compression::Type compression_of(pa_sample_format_t fmt)
149 {
150 Kwave::Compression::Type compression = Kwave::Compression::NONE;
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 //***************************************************************************
bits_of(pa_sample_format_t fmt)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 //***************************************************************************
RecordPulseAudio()199 Kwave::RecordPulseAudio::RecordPulseAudio()
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 //***************************************************************************
~RecordPulseAudio()222 Kwave::RecordPulseAudio::~RecordPulseAudio()
223 {
224 disconnectFromServer();
225 m_device_list.clear();
226 }
227
228 //***************************************************************************
detectSupportedFormats(const QString & device)229 void Kwave::RecordPulseAudio::detectSupportedFormats(const QString &device)
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
252 Kwave::Compression t(compression_of(fmt));
253 Kwave::SampleFormat::Map sf;
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 //***************************************************************************
pa_read_cb(pa_stream * p,size_t nbytes,void * userdata)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 //***************************************************************************
notifyRead(pa_stream * stream,size_t nbytes)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 //***************************************************************************
pa_stream_state_cb(pa_stream * p,void * userdata)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 //***************************************************************************
notifyStreamState(pa_stream * stream)300 void Kwave::RecordPulseAudio::notifyStreamState(pa_stream* stream)
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 //***************************************************************************
pa_context_notify_cb(pa_context * c,void * userdata)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 //***************************************************************************
notifyContext(pa_context * c)345 void Kwave::RecordPulseAudio::notifyContext(pa_context *c)
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 //***************************************************************************
mode2format(int compression,int bits,Kwave::SampleFormat::Format sample_format)381 pa_sample_format_t Kwave::RecordPulseAudio::mode2format(
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 //***************************************************************************
endianness()404 Kwave::byte_order_t Kwave::RecordPulseAudio::endianness()
405 {
406 pa_sample_format_t fmt = mode2format(m_compression, m_bits_per_sample,
407 m_sample_format);
408 return (fmt != PA_SAMPLE_INVALID) ?
409 endian_of(fmt) : Kwave::UnknownEndian;
410 }
411
412 //***************************************************************************
sampleFormat()413 Kwave::SampleFormat::Format Kwave::RecordPulseAudio::sampleFormat()
414 {
415 return m_sample_format;
416 }
417
418 //***************************************************************************
setSampleFormat(Kwave::SampleFormat::Format new_format)419 int Kwave::RecordPulseAudio::setSampleFormat(
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 //***************************************************************************
detectSampleFormats()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;
442 if (bits_of(fmt) != Kwave::toInt(m_bits_per_sample))
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 //***************************************************************************
bitsPerSample()455 int Kwave::RecordPulseAudio::bitsPerSample()
456 {
457 return m_bits_per_sample;
458 }
459
460 //***************************************************************************
setBitsPerSample(unsigned int new_bits)461 int Kwave::RecordPulseAudio::setBitsPerSample(unsigned int new_bits)
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 //***************************************************************************
supportedBits()471 QList< unsigned int > Kwave::RecordPulseAudio::supportedBits()
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 //***************************************************************************
compression()496 Kwave::Compression::Type Kwave::RecordPulseAudio::compression()
497 {
498 return m_compression;
499 }
500
501 //***************************************************************************
setCompression(Kwave::Compression::Type new_compression)502 int Kwave::RecordPulseAudio::setCompression(
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 //***************************************************************************
detectCompressions()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 {
521 Kwave::Compression::Type compression = compression_of(fmt);
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 //***************************************************************************
sampleRate()534 double Kwave::RecordPulseAudio::sampleRate()
535 {
536 return m_rate;
537 }
538
539 //***************************************************************************
setSampleRate(double & new_rate)540 int Kwave::RecordPulseAudio::setSampleRate(double& new_rate)
541 {
542 if (qFuzzyCompare(new_rate, m_rate))
543 return 0;
544 close();
545 m_rate = new_rate;
546 return 0;
547 }
548
549 //***************************************************************************
detectSampleRates()550 QList< double > Kwave::RecordPulseAudio::detectSampleRates()
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 //***************************************************************************
tracks()603 int Kwave::RecordPulseAudio::tracks()
604 {
605 return m_tracks;
606 }
607
608 //***************************************************************************
setTracks(unsigned int & tracks)609 int Kwave::RecordPulseAudio::setTracks(unsigned int &tracks)
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 //***************************************************************************
detectTracks(unsigned int & min,unsigned int & max)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 //***************************************************************************
close()639 int Kwave::RecordPulseAudio::close()
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...");
647 m_mainloop_signal.wait(&m_mainloop_lock, TIMEOUT_DISCONNECT_STREAM);
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 //***************************************************************************
read(QByteArray & buffer,unsigned int offset)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 //***************************************************************************
initialize(uint32_t buffer_size)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,
724 m_sample_format);
725 if (fmt == PA_SAMPLE_INVALID) {
726 Kwave::SampleFormat::Map sf;
727
728 qWarning("format: no matching format for compression '%s', "
729 "%d bits/sample, format '%s'",
730 DBG(sf.description(sf.findFromData(m_sample_format), true)),
731 m_bits_per_sample,
732 DBG(Kwave::Compression(m_compression).name())
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)) {
743 Kwave::SampleFormat::Map sf;
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) {
794 m_mainloop_signal.wait(&m_mainloop_lock, TIMEOUT_CONNECT_RECORD);
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 //***************************************************************************
open(const QString & device)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 //***************************************************************************
supportedDevices()836 QStringList Kwave::RecordPulseAudio::supportedDevices()
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 //***************************************************************************
run_wrapper(const QVariant & params)852 void Kwave::RecordPulseAudio::run_wrapper(const QVariant ¶ms)
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 //***************************************************************************
poll_func(struct pollfd * ufds,unsigned long nfds,int timeout,void * userdata)862 static int poll_func(struct pollfd *ufds, unsigned long nfds,
863 int timeout, void *userdata)
864 {
865 Kwave::RecordPulseAudio *dev =
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 //***************************************************************************
mainloopPoll(struct pollfd * ufds,unsigned long int nfds,int timeout)874 int Kwave::RecordPulseAudio::mainloopPoll(struct pollfd *ufds,
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 //***************************************************************************
connectToServer()886 bool Kwave::RecordPulseAudio::connectToServer()
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",
927 m_pa_proplist
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();
950 m_mainloop_thread.start();
951
952 // wait until the context state is either connected or failed
953 failed = true;
954 if ( m_mainloop_signal.wait(&m_mainloop_lock,
955 TIMEOUT_CONNECT_TO_SERVER) )
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) {
971 disconnectFromServer();
972 }
973
974 QApplication::restoreOverrideCursor();
975
976 return !failed;
977 }
978
979 //***************************************************************************
disconnectFromServer()980 void Kwave::RecordPulseAudio::disconnectFromServer()
981 {
982 close();
983
984 // stop the main loop
985 m_mainloop_thread.isInterruptionRequested();
986 if (m_pa_mainloop) {
987 m_mainloop_lock.lock();
988 pa_mainloop_quit(m_pa_mainloop, 0);
989 m_mainloop_lock.unlock();
990 }
991 m_mainloop_thread.stop();
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 //***************************************************************************
pa_source_info_cb(pa_context * c,const pa_source_info * info,int eol,void * userdata)1015 void Kwave::RecordPulseAudio::pa_source_info_cb(pa_context *c,
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 //***************************************************************************
notifySourceInfo(pa_context * c,const pa_source_info * info,int eol)1026 void Kwave::RecordPulseAudio::notifySourceInfo(pa_context *c,
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 //***************************************************************************
scanDevices()1049 void Kwave::RecordPulseAudio::scanDevices()
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,
1059 pa_source_info_cb,
1060 this
1061 );
1062 if (op_source_info) {
1063 // set hourglass cursor, we have a long timeout...
1064 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1065 m_mainloop_signal.wait(&m_mainloop_lock, TIMEOUT_WAIT_DEVICE_SCAN);
1066 QApplication::restoreOverrideCursor();
1067 }
1068
1069 // create a list with final names
1070 QMap<QString, source_info_t> list;
1071 for (QMap<QString, source_info_t>::const_iterator it =
1072 m_device_list.constBegin();
1073 it != m_device_list.constEnd(); ++it)
1074 {
1075 const QString &source = it.key();
1076 const source_info_t &inf = it.value();
1077 const QString &name = inf.m_name;
1078 QString description = inf.m_description;
1079 QString driver = inf.m_driver;
1080
1081 // if the name is not unique, add the internal source name
1082 for (QMap<QString, source_info_t>::const_iterator it2 =
1083 m_device_list.constBegin();
1084 it2 != m_device_list.constEnd(); ++it2)
1085 {
1086 const QString &s = it2.key();
1087 const source_info_t &i = it2.value();
1088 if (s == source) continue;
1089 if ((i.m_description == description) &&
1090 (i.m_driver == driver))
1091 {
1092 // not unique
1093 description += _(" [") + name + _("]");
1094 break;
1095 }
1096 }
1097
1098 // mangle the driver name, e.g.
1099 // "module-alsa-sink.c" -> "alsa sink"
1100 QFileInfo f(driver);
1101 driver = f.baseName();
1102 driver.replace(_("-"), _(" "));
1103 driver.replace(_("_"), _(" "));
1104 if (driver.startsWith(_("module "), Qt::CaseInsensitive))
1105 driver.remove(0, 7);
1106 description.prepend(driver + _("|sound_card||"));
1107
1108 // add the leaf node
1109 if (inf.m_card != PA_INVALID_INDEX)
1110 description.append(_("|sound_device"));
1111 else
1112 description.append(_("|sound_note"));
1113
1114 list.insert(description, *it);
1115 }
1116
1117 m_device_list.clear();
1118 m_device_list = list;
1119 m_mainloop_lock.unlock();
1120 }
1121
1122 #endif /* HAVE_PULSEAUDIO_SUPPORT */
1123
1124 //***************************************************************************
1125 //***************************************************************************
1126