kwave  18.07.70
Record-Qt.cpp
Go to the documentation of this file.
1 /*************************************************************************
2  Record-Qt.cpp - device for audio recording via Qt Multimedia
3  -------------------
4  begin : Sun Mar 20 2016
5  copyright : (C) 2016 by Thomas Eschenbacher
6  email : Thomas.Eschenbacher@gmx.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_QT_AUDIO_SUPPORT
20 
21 #include <algorithm>
22 #include <limits>
23 
24 #include <QApplication>
25 #include <QAudioDeviceInfo>
26 #include <QAudioFormat>
27 #include <QAudioInput>
28 #include <QCursor>
29 #include <QFileInfo>
30 #include <QIODevice>
31 #include <QLatin1Char>
32 #include <QLocale>
33 #include <QString>
34 #include <QVariant>
35 #include <QtGlobal>
36 
37 #include <KLocalizedString>
38 
39 #include "libkwave/Compression.h"
40 #include "libkwave/SampleFormat.h"
41 #include "libkwave/String.h"
42 #include "libkwave/Utils.h"
43 #include "libkwave/memcpy.h"
44 
45 #include "Record-Qt.h"
46 
48 #define DEFAULT_DEVICE (i18n("Default device") + _("|sound_note"))
49 
51 #define RECORD_POLL_TIMEOUT 200
52 
57 #define BUFFER_SIZE_OVERCOMMIT 2
58 
59 //***************************************************************************
61  :QObject(Q_NULLPTR),
63  m_lock(QMutex::Recursive),
64  m_device_name_map(),
65  m_available_devices(),
66  m_input(Q_NULLPTR),
67  m_source(Q_NULLPTR),
68  m_sample_format(Kwave::SampleFormat::Unknown),
69  m_tracks(0),
70  m_rate(0.0),
71  m_compression(Kwave::Compression::NONE),
72  m_bits_per_sample(0),
73  m_device(),
74  m_initialized(false),
75  m_sem(0)
76 {
77  connect(this, SIGNAL(sigCreateRequested(QAudioFormat&,uint)),
78  this, SLOT(createInMainThread(QAudioFormat&,uint)),
79  Qt::BlockingQueuedConnection);
80  connect(this, SIGNAL(sigCloseRequested()),
81  this, SLOT(closeInMainThread()),
82  Qt::BlockingQueuedConnection);
83 }
84 
85 //***************************************************************************
87 {
88  close();
89 }
90 
91 //***************************************************************************
93 {
94  return m_sample_format;
95 }
96 
97 //***************************************************************************
99 {
100  if (m_sample_format == new_format) return 0;
101  close();
102  m_sample_format = new_format;
103  return 0;
104 }
105 
106 //***************************************************************************
107 QList<Kwave::SampleFormat::Format> Kwave::RecordQt::detectSampleFormats()
108 {
109  QList<Kwave::SampleFormat::Format> list;
110  QMutexLocker _lock(&m_lock); // context: main thread
111 
112  const QAudioDeviceInfo info(deviceInfo(m_device));
113 
114  // no devices at all -> empty
115  if (info.isNull()) return list;
116 
117  // iterate over all supported bits per sample
118  foreach (QAudioFormat::SampleType t, info.supportedSampleTypes()) {
119  switch (t) {
120  case QAudioFormat::SignedInt:
121  list.append(Kwave::SampleFormat::Signed);
122  break;
123  case QAudioFormat::UnSignedInt:
124  list.append(Kwave::SampleFormat::Unsigned);
125  break;
126  case QAudioFormat::Float:
127  list.append(Kwave::SampleFormat::Float);
128  break;
129  default:
130  break;
131  }
132  }
133 
134  return list;
135 }
136 
137 //***************************************************************************
139 {
141  QMutexLocker _lock(&m_lock); // context: main thread
142 
143  const QAudioDeviceInfo info(deviceInfo(m_device));
144 
145  // no devices at all -> empty
146  if (info.isNull()) return byte_order;
147 
148  // iterate over all supported bits per sample
149  switch (info.preferredFormat().byteOrder()) {
151  byte_order = Kwave::LittleEndian;
152  break;
154  byte_order = Kwave::BigEndian;
155  break;
156  default:
157  break;
158  }
159 
160  return byte_order;
161 }
162 
163 //***************************************************************************
165 {
166  return m_bits_per_sample;
167 }
168 
169 //***************************************************************************
170 int Kwave::RecordQt::setBitsPerSample(unsigned int new_bits)
171 {
172  if (new_bits == m_bits_per_sample) return 0;
173 
174  close();
175  m_bits_per_sample = new_bits;
176  return 0;
177 }
178 
179 //***************************************************************************
180 QList< unsigned int > Kwave::RecordQt::supportedBits()
181 {
182  QList<unsigned int> list;
183  QMutexLocker _lock(&m_lock); // context: main thread
184 
185  const QAudioDeviceInfo info(deviceInfo(m_device));
186 
187  // no devices at all -> empty
188  if (info.isNull()) return list;
189 
190  // iterate over all supported bits per sample
191  foreach (int bits, info.supportedSampleSizes()) {
192  if (bits <= 0) continue;
193  list.append(Kwave::toUint(bits));
194  }
195 
196  std::sort(list.begin(), list.end(), std::less<unsigned int>());
197  return list;
198 }
199 
200 //***************************************************************************
202 {
203  return m_compression;
204 }
205 
206 //***************************************************************************
208 {
209  if (new_compression != m_compression) {
210  close();
211  m_compression = new_compression;
212  }
213  return 0;
214 }
215 
216 //***************************************************************************
217 QList<Kwave::Compression::Type> Kwave::RecordQt::detectCompressions()
218 {
219  QList<Kwave::Compression::Type> list;
220  list.append(Kwave::Compression::NONE);
221  return list;
222 }
223 
224 //***************************************************************************
226 {
227  return m_rate;
228 }
229 
230 //***************************************************************************
231 int Kwave::RecordQt::setSampleRate(double& new_rate)
232 {
233  if (qFuzzyCompare(new_rate, m_rate)) return 0;
234 
235  close();
236  m_rate = new_rate;
237  return 0;
238 }
239 
240 //***************************************************************************
242 {
243  QList<double> list;
244  QMutexLocker _lock(&m_lock); // context: main thread
245 
246  const QAudioDeviceInfo info(deviceInfo(m_device));
247 
248  // no devices at all -> empty
249  if (info.isNull()) return list;
250 
251  // iterate over all supported sample sizes
252  foreach (int rate, info.supportedSampleRates()) {
253  if (rate <= 0) continue;
254  list.append(static_cast<double>(rate));
255  }
256 
257  std::sort(list.begin(), list.end(), std::less<double>());
258  return list;
259 }
260 
261 //***************************************************************************
263 {
264  return m_tracks;
265 }
266 
267 //***************************************************************************
269 {
270  if (tracks == m_tracks) return 0;
271  if (tracks > 255) tracks = 255;
272 
273  close();
274  m_tracks = static_cast<quint8>(tracks);
275  return 0;
276 }
277 
278 //***************************************************************************
279 int Kwave::RecordQt::detectTracks(unsigned int &min, unsigned int &max)
280 {
281  QMutexLocker _lock(&m_lock); // context: main thread
282 
283  const QAudioDeviceInfo info(deviceInfo(m_device));
284 
285  max = std::numeric_limits<unsigned int>::min();
286  min = std::numeric_limits<unsigned int>::max();
287 
288  // no devices at all -> empty
289  if (info.isNull()) return -1;
290 
291  // iterate over all supported sample sizes
292  foreach (int channels, info.supportedChannelCounts()) {
293  if (channels <= 0) continue;
294  unsigned int c = Kwave::toUint(channels);
295  if (c < 1) continue;
296  if (c < min) min = c;
297  if (c > max) max = c;
298  }
299 
300  return (max > 0) ? max : -1;
301 }
302 
303 //***************************************************************************
304 QAudioDeviceInfo Kwave::RecordQt::deviceInfo(const QString &device) const
305 {
306  // check for default device
307  if (!device.length() || (device == DEFAULT_DEVICE))
308  return QAudioDeviceInfo::defaultInputDevice();
309 
310  // check if the device name is known
311  if (m_device_name_map.isEmpty() || !m_device_name_map.contains(device))
312  return QAudioDeviceInfo();
313 
314  // translate the path into a Qt audio output device name
315  // iterate over all available devices
316  const QString dev_name = m_device_name_map[device];
317  foreach (const QAudioDeviceInfo &dev, m_available_devices) {
318  if (dev.deviceName() == dev_name)
319  return QAudioDeviceInfo(dev);
320  }
321 
322  // fallen through: return empty info
323  return QAudioDeviceInfo();
324 }
325 
326 //***************************************************************************
328 {
329  if (m_source) {
330  m_source->close();
331  m_source = Q_NULLPTR;
332  }
333  if (m_input) {
334  m_input->stop();
335  m_input->reset();
336  delete m_input;
337  m_input = Q_NULLPTR;
338  }
339 
340  m_initialized = false;
341 }
342 
343 //***************************************************************************
345 {
346  QMutexLocker _lock(&m_lock);
347 
348  if (QThread::currentThread() == qApp->thread())
350  else
351  emit sigCloseRequested();
352 
353  return 0;
354 }
355 
356 //***************************************************************************
357 int Kwave::RecordQt::read(QByteArray& buffer, unsigned int offset)
358 {
359  if (buffer.isNull() || buffer.isEmpty())
360  return 0; // no buffer, nothing to do
361 
362  int buffer_size = buffer.size();
363 
364  // we configure our device at a late stage, otherwise we would not
365  // know the internal buffer size
366  if (!m_initialized) {
367  int err = initialize(buffer_size);
368  if (err < 0) return -EAGAIN;
369  m_initialized = true;
370  }
371  Q_ASSERT(m_source);
372  Q_ASSERT(m_input);
373  if (!m_source || !m_input)
374  return -ENODEV;
375 
376  // adjust the buffer size if is has been changed in the plugin
377  if ((buffer_size > 0) && (m_input->bufferSize() != buffer_size))
378  m_input->setBufferSize(buffer_size * BUFFER_SIZE_OVERCOMMIT);
379 
380  // wait until some data gets available (with timeout)
381  m_sem.tryAcquire(1, RECORD_POLL_TIMEOUT);
382 
383  char *p = buffer.data() + offset;
384  unsigned int len = buffer.length() - offset;
385  qint64 length = m_source->read(p, len);
386 
387  return (length < 1) ? -EAGAIN : Kwave::toInt(length);
388 }
389 
390 //***************************************************************************
391 QString Kwave::RecordQt::open(const QString& device)
392 {
393 
394  // close the previous device
395  close();
396 
397  QMutexLocker _lock(&m_lock);
398 
399  // make sure we have a valid list of devices
400  scanDevices();
401 
402  const QAudioDeviceInfo info(deviceInfo(device));
403  if (info.isNull()) {
404  return QString::number(ENODEV);
405  }
406 
407  m_device = device;
408  return QString();
409 }
410 
411 //***************************************************************************
413 {
414  QMutexLocker _lock(&m_lock); // context: main thread
415 
416  // re-validate the list if necessary
417  if (m_device_name_map.isEmpty() || m_available_devices.isEmpty())
418  scanDevices();
419 
420  QStringList list = m_device_name_map.keys();
421 
422  // move the "default" device to the start of the list
423  if (list.contains(DEFAULT_DEVICE))
424  list.move(list.indexOf(DEFAULT_DEVICE), 0);
425 
426  if (!list.isEmpty()) list.append(_("#TREE#"));
427 
428  return list;
429 }
430 
431 //***************************************************************************
432 int Kwave::RecordQt::initialize(unsigned int buffer_size)
433 {
434  // do sanity checks of the current parameters, otherwise Qt crashes
435  // with floating point errors or similar
436  if (m_rate < 1.0) return -EINVAL;
437  if (m_bits_per_sample < 1) return -EINVAL;
438  if (m_tracks < 1) return -EINVAL;
439  if (!m_device.length()) return -EINVAL;
440 
441  const QAudioDeviceInfo info(deviceInfo(m_device));
442 
443  // find a supported sample format
444  const QAudioFormat preferred_format(info.preferredFormat());
445  QAudioFormat format(preferred_format);
446  format.setSampleSize(Kwave::toInt(m_bits_per_sample));
447  format.setChannelCount(Kwave::toInt(m_tracks));
448  format.setSampleRate(Kwave::toInt(m_rate));
449  format.setCodec(_("audio/pcm"));
450 
451  // find a replacement format with matching codec, tracks, bits and rate
452  if (!format.isValid() || !info.isFormatSupported(format))
453  format = info.nearestFormat(format);
454 
455  if (format.codec() != _("audio/pcm")) {
456  qWarning("PCM encoding is not supported");
457  return -EIO;
458  }
459 
460  if (format.sampleSize() != Kwave::toInt(m_bits_per_sample)) {
461  qWarning("%d bits per sample are not supported", m_bits_per_sample);
462  return -EIO;
463  }
464 
465  if (format.channelCount() != Kwave::toInt(m_tracks)) {
466  qWarning("recording with %d channels is not supported", m_tracks);
467  return -EIO;
468  }
469 
470  if (format.sampleRate() != Kwave::toInt(m_rate)) {
471  qWarning("sample rate %d Hz is not supported", Kwave::toInt(m_rate));
472  return -EIO;
473  }
474 
475  if ( (format.sampleType() != QAudioFormat::SignedInt) &&
476  (format.sampleType() != QAudioFormat::UnSignedInt) )
477  {
478  qWarning("integer sample format is not supported");
479  return -EIO;
480  }
481 
482  // create a new Qt output device
483  if (QThread::currentThread() == qApp->thread())
484  createInMainThread(format, buffer_size);
485  else
486  emit sigCreateRequested(format, buffer_size);
487 
488  return 0;
489 }
490 
491 //***************************************************************************
492 void Kwave::RecordQt::createInMainThread(QAudioFormat &format,
493  unsigned int buffer_size)
494 {
495  QMutexLocker _lock(&m_lock);
496 
497  // reset the semaphore to zero
498  m_sem.acquire(m_sem.available());
499 
500  // create a new audio device for the selected format
501  m_input = new QAudioInput(format, this);
502  Q_ASSERT(m_input);
503  if (!m_input) return;
504  connect(m_input, SIGNAL(notify()), this, SLOT(notified()));
505 
506  // set the buffer size, before starting to record
507  m_input->setBufferSize(buffer_size * BUFFER_SIZE_OVERCOMMIT);
508 
509  // start recording engine
510  m_source = m_input->start();
511 }
512 
513 //***************************************************************************
515 {
516  m_sem.release();
517 }
518 
519 //***************************************************************************
521 {
522  m_available_devices.clear();
523  m_device_name_map.clear();
524 
525  // get the list of available audio output devices from Qt
526  foreach (const QAudioDeviceInfo &device,
527  QAudioDeviceInfo::availableDevices(QAudio::AudioInput))
528  {
529  QString qt_name = device.deviceName();
530 
531  // for debugging: list all devices
532 // qDebug(" name='%s'", DBG(qt_name));
533 
534  // device name not available ?
535  if (!qt_name.length()) {
536  qWarning("RecordQt::supportedDevices() "
537  "=> BUG: device with no name?");
538  continue;
539  }
540 
541  QString gui_name = qt_name + _("|sound_note");
542  if (m_device_name_map.contains(gui_name)) {
543  qWarning("RecordQt::supportedDevices() "
544  "=> BUG: duplicate device name: '%s'", DBG(gui_name));
545  continue;
546  }
547 
548  m_available_devices.append(device);
549  m_device_name_map[gui_name] = qt_name;
550  }
551 }
552 
553 #endif /* HAVE_QT_AUDIO_SUPPORT */
554 
555 //***************************************************************************
556 //***************************************************************************
QIODevice * m_source
Definition: Record-Qt.h:256
virtual Kwave::SampleFormat::Format sampleFormat() Q_DECL_OVERRIDE
Definition: Record-Qt.cpp:92
QList< QAudioDeviceInfo > m_available_devices
Definition: Record-Qt.h:250
void createInMainThread(QAudioFormat &format, unsigned int buffer_size)
Definition: Record-Qt.cpp:492
virtual ~RecordQt() Q_DECL_OVERRIDE
Definition: Record-Qt.cpp:86
bool m_initialized
Definition: Record-Qt.h:277
byte_order_t
Definition: ByteOrder.h:25
Definition: App.h:33
virtual QList< Kwave::SampleFormat::Format > detectSampleFormats() Q_DECL_OVERRIDE
Definition: Record-Qt.cpp:107
void sigCloseRequested()
#define DEFAULT_DEVICE
Definition: Record-Qt.cpp:48
virtual int close() Q_DECL_OVERRIDE
Definition: Record-Qt.cpp:344
void closeInMainThread()
Definition: Record-Qt.cpp:327
QSemaphore m_sem
Definition: Record-Qt.h:280
bool connect(Kwave::StreamObject &source, const char *output, Kwave::StreamObject &sink, const char *input)
Definition: Connect.cpp:48
virtual QList< Kwave::Compression::Type > detectCompressions() Q_DECL_OVERRIDE
Definition: Record-Qt.cpp:217
Kwave::Compression::Type m_compression
Definition: Record-Qt.h:268
virtual int setCompression(Kwave::Compression::Type new_compression) Q_DECL_OVERRIDE
Definition: Record-Qt.cpp:207
void scanDevices()
Definition: Record-Qt.cpp:520
unsigned int m_bits_per_sample
Definition: Record-Qt.h:271
Kwave::SampleFormat::Format m_sample_format
Definition: Record-Qt.h:259
int toInt(T x)
Definition: Utils.h:127
void sigCreateRequested(QAudioFormat &format, unsigned int buffer_size)
quint8 m_tracks
Definition: Record-Qt.h:262
virtual int detectTracks(unsigned int &min, unsigned int &max) Q_DECL_OVERRIDE
Definition: Record-Qt.cpp:279
int initialize(unsigned int buffer_size)
Definition: Record-Qt.cpp:432
virtual QList< unsigned int > supportedBits() Q_DECL_OVERRIDE
Definition: Record-Qt.cpp:180
virtual int setBitsPerSample(unsigned int new_bits) Q_DECL_OVERRIDE
Definition: Record-Qt.cpp:170
virtual int setSampleFormat(Kwave::SampleFormat::Format new_format) Q_DECL_OVERRIDE
Definition: Record-Qt.cpp:98
virtual QStringList supportedDevices() Q_DECL_OVERRIDE
Definition: Record-Qt.cpp:412
virtual double sampleRate() Q_DECL_OVERRIDE
Definition: Record-Qt.cpp:225
virtual int setTracks(unsigned int &tracks) Q_DECL_OVERRIDE
Definition: Record-Qt.cpp:268
virtual Kwave::Compression::Type compression() Q_DECL_OVERRIDE
Definition: Record-Qt.cpp:201
#define _(m)
Definition: memcpy.c:66
virtual int read(QByteArray &buffer, unsigned int offset) Q_DECL_OVERRIDE
Definition: Record-Qt.cpp:357
#define DBG(qs)
Definition: String.h:55
virtual int setSampleRate(double &new_rate) Q_DECL_OVERRIDE
Definition: Record-Qt.cpp:231
unsigned int toUint(T x)
Definition: Utils.h:109
virtual Kwave::byte_order_t endianness() Q_DECL_OVERRIDE
Definition: Record-Qt.cpp:138
QAudioInput * m_input
Definition: Record-Qt.h:253
#define RECORD_POLL_TIMEOUT
Definition: Record-Qt.cpp:51
QString m_device
Definition: Record-Qt.h:274
QAudioDeviceInfo deviceInfo(const QString &device) const
Definition: Record-Qt.cpp:304
virtual int bitsPerSample() Q_DECL_OVERRIDE
Definition: Record-Qt.cpp:164
virtual QString open(const QString &dev) Q_DECL_OVERRIDE
Definition: Record-Qt.cpp:391
QMap< QString, QString > m_device_name_map
Definition: Record-Qt.h:247
virtual int tracks() Q_DECL_OVERRIDE
Definition: Record-Qt.cpp:262
virtual QList< double > detectSampleRates() Q_DECL_OVERRIDE
Definition: Record-Qt.cpp:241
#define BUFFER_SIZE_OVERCOMMIT
Definition: Record-Qt.cpp:57