kwave  18.07.70
PlayBack-Qt.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  PlayBack-Qt.cpp - playback device for Qt Multimedia
3  -------------------
4  begin : Thu Nov 12 2015
5  copyright : (C) 2015 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 <errno.h>
22 #include <algorithm>
23 #include <limits>
24 
25 #include <QApplication>
26 #include <QAudioDeviceInfo>
27 #include <QAudioFormat>
28 #include <QAudioOutput>
29 #include <QObject>
30 #include <QSysInfo>
31 
32 #include <KLocalizedString>
33 
35 #include "libkwave/String.h"
36 #include "libkwave/Utils.h"
37 
38 #include "PlayBack-Qt.h"
39 
41 #define DEFAULT_DEVICE (i18n("Default device") + _("|sound_note"))
42 
43 //***************************************************************************
45  :QObject(), Kwave::PlayBackDevice(),
46  m_lock(QMutex::Recursive),
47  m_device_name_map(),
48  m_available_devices(),
49  m_output(Q_NULLPTR),
50  m_buffer_size(0),
51  m_encoder(Q_NULLPTR)
52 {
53 }
54 
55 //***************************************************************************
57 {
58  close();
59 }
60 
61 //***************************************************************************
62 void Kwave::PlayBackQt::createEncoder(const QAudioFormat &format)
63 {
64  // discard the old encoder
65  delete m_encoder;
66  m_encoder = Q_NULLPTR;
67 
68  // get the sample format
70  switch (format.sampleType()) {
71  case QAudioFormat::SignedInt:
72  sample_format = Kwave::SampleFormat::Signed;
73  break;
74  case QAudioFormat::UnSignedInt:
75  sample_format = Kwave::SampleFormat::Unsigned;
76  break;
77  default:
78  sample_format = Kwave::SampleFormat::Unknown;
79  break;
80  }
81  if (sample_format == Kwave::SampleFormat::Unknown) {
82  qWarning("PlayBackQt: unsupported sample format %d",
83  static_cast<int>(format.sampleType()));
84  return;
85  }
86 
87  unsigned int bits = 0;
88  switch (format.sampleSize()) {
89  case 8: bits = 8; break;
90  case 16: bits = 16; break;
91  case 24: bits = 24; break;
92  case 32: bits = 32; break;
93  default: bits = 0; break;
94  }
95  if (bits == 0) {
96  qWarning("PlayBackQt: unsupported bits per sample: %d",
97  static_cast<int>(format.sampleSize()));
98  return;
99  }
100 
102  switch (format.byteOrder()) {
103  case QAudioFormat::BigEndian: endian = Kwave::BigEndian; break;
104  case QAudioFormat::LittleEndian: endian = Kwave::LittleEndian; break;
105  default: endian = Kwave::UnknownEndian; break;
106  }
107  if (endian == Kwave::UnknownEndian) {
108  qWarning("PlayBackQt: unsupported byte order in audio format: %d",
109  static_cast<int>(format.byteOrder()));
110  return;
111  }
112 
113  // create the sample encoder
114  m_encoder = new Kwave::SampleEncoderLinear(sample_format, bits, endian);
115 }
116 
117 //***************************************************************************
118 QString Kwave::PlayBackQt::open(const QString &device, double rate,
119  unsigned int channels, unsigned int bits,
120  unsigned int bufbase)
121 {
122  qDebug("PlayBackQt::open(device='%s', rate=%0.1f,channels=%u, bits=%u, "
123  "bufbase=%u)", DBG(device), rate, channels, bits, bufbase);
124 
125  if ((rate < double(1.0f)) || !channels || !bits || !bufbase)
126  return i18n("One or more invalid/out of range arguments.");
127 
128  // close the previous device
129  close();
130 
131  QMutexLocker _lock(&m_lock); // context: main thread
132 
133  // make sure we have a valid list of devices
134  scanDevices();
135 
136  const QAudioDeviceInfo info(deviceInfo(device));
137  if (info.isNull()) {
138  return i18n("The audio device '%1' is unknown or no longer connected",
139  device.section(QLatin1Char('|'), 0, 0));
140  }
141 
142  // find a supported sample format
143  const QAudioFormat preferred_format(info.preferredFormat());
144  QAudioFormat format(preferred_format);
145  format.setSampleSize(Kwave::toInt(bits));
146  format.setChannelCount(Kwave::toInt(channels));
147  format.setSampleRate(Kwave::toInt(rate));
148 
149  // find a replacement format with matching codec, channels, bits and rate
150  if (!format.isValid() || !info.isFormatSupported(format))
151  format = info.nearestFormat(format);
152 
153  if (format.codec() != _("audio/pcm"))
154  return i18n("PCM encoding is not supported");
155 
156  if (format.sampleSize() != Kwave::toInt(bits))
157  return i18n("%1 bits per sample are not supported", bits);
158 
159  if (format.channelCount() != Kwave::toInt(channels))
160  return i18n("playback with %1 channels is not supported", channels);
161 
162  if (format.sampleRate() != Kwave::toInt(rate))
163  return i18n("sample rate %1 Hz is not supported", Kwave::toInt(rate));
164 
165  if ( (format.sampleType() != QAudioFormat::SignedInt) &&
166  (format.sampleType() != QAudioFormat::UnSignedInt) )
167  return i18n("integer sample format is not supported");
168 
169  // create a sample encoder
170  createEncoder(format);
171  Q_ASSERT(m_encoder);
172  if (!m_encoder) return i18n("Out of memory");
173 
174  // create a new Qt output device
175  m_output = new QAudioOutput(format, this);
176  Q_ASSERT(m_output);
177  if (!m_output) return i18n("Out of memory");
178 
179  // connect the state machine and the notification engine
180  connect(m_output, SIGNAL(stateChanged(QAudio::State)),
181  this, SLOT(stateChanged(QAudio::State)));
182 
183  // calculate the buffer size in bytes
184  if (bufbase < 8)
185  bufbase = 8;
186  m_buffer_size = (1U << bufbase);
187  qDebug(" buffer size = %u", m_buffer_size);
188 
189  // in the rare case that out backend already gives us a period size,
190  // check out buffer size against it
191  m_buffer_size = qMax(m_buffer_size, Kwave::toUint(m_output->periodSize()));
192 
194 
195  // open the output device for writing
196  m_output->start(&m_buffer);
197 
198  // calculate an appropriate timeout, based on the period and buffer sizes
199  const int period_size = m_output->periodSize();
200  qDebug(" period_size = %d", period_size);
201  unsigned int bytes_per_frame = m_encoder->rawBytesPerSample() * channels;
202  unsigned int buffer_size = qMax(qMax<int>(period_size, m_buffer_size),
203  m_output->bufferSize());
204  unsigned int buffer_frames =
205  ((buffer_size * 2) + (bytes_per_frame - 1)) / bytes_per_frame;
206  int timeout = qMax(Kwave::toInt((1000 * buffer_frames) / rate), 100);
207  qDebug(" timeout = %d ms", timeout);
208  m_buffer.setTimeout(timeout);
209 
210  if (m_output->error() != QAudio::NoError) {
211  qDebug("error no: %d", int(m_output->error()));
212  return i18n("Opening the Qt Multimedia device '%1' failed", device);
213  }
214 
215  return QString();
216 }
217 
218 //***************************************************************************
220 {
221  QByteArray frame;
222 
223  {
224  QMutexLocker _lock(&m_lock); // context: worker thread
225 
226  if (!m_encoder || !m_output) return -EIO;
227 
228  int bytes_per_sample = m_encoder->rawBytesPerSample();
229  int bytes_raw = samples.size() * bytes_per_sample;
230 
231  frame.resize(bytes_raw);
232  frame.fill(char(0));
233  m_encoder->encode(samples, samples.size(), frame);
234  }
235 
236  qint64 written = m_buffer.writeData(frame.constData(), frame.size());
237  if (written != frame.size()) {
238  qDebug("WARNING: Kwave::PlayBackQt::write: written=%lld/%d",
239  written, frame.size());
240  return -EIO;
241  }
242 
243  return 0;
244 }
245 
246 //***************************************************************************
247 void Kwave::PlayBackQt::stateChanged(QAudio::State state)
248 {
249  Q_ASSERT(m_output);
250  if (!m_output) return;
251 
252  if (m_output->error() != QAudio::NoError) {
253  qDebug("PlaybBackQt::stateChanged(%d), ERROR=%d, "
254  "buffer free=%d",
255  static_cast<int>(state),
256  static_cast<int>(m_output->error()),
257  m_output->bytesFree()
258  );
259  }
260  switch (state) {
261  case QAudio::ActiveState:
262  qDebug("PlaybBackQt::stateChanged(ActiveState)");
263  break;
264  case QAudio::SuspendedState:
265  qDebug("PlaybBackQt::stateChanged(SuspendedState)");
266  break;
267  case QAudio::StoppedState: {
268  qDebug("PlaybBackQt::stateChanged(StoppedState)");
269  break;
270  }
271  case QAudio::IdleState:
272  qDebug("PlaybBackQt::stateChanged(IdleState)");
273  break;
274  default:
275  qWarning("PlaybBackQt::stateChanged(%d)",
276  static_cast<int>(state));
277  break;
278  }
279 }
280 
281 //***************************************************************************
282 QAudioDeviceInfo Kwave::PlayBackQt::deviceInfo(const QString &device) const
283 {
284  // check for default device
285  if (!device.length() || (device == DEFAULT_DEVICE))
286  return QAudioDeviceInfo::defaultOutputDevice();
287 
288  // check if the device name is known
289  if (m_device_name_map.isEmpty() || !m_device_name_map.contains(device))
290  return QAudioDeviceInfo();
291 
292  // translate the path into a Qt audio output device name
293  // iterate over all available devices
294  const QString dev_name = m_device_name_map[device];
295  foreach (const QAudioDeviceInfo &dev, m_available_devices) {
296  if (dev.deviceName() == dev_name)
297  return QAudioDeviceInfo(dev);
298  }
299 
300  // fallen through: return empty info
301  return QAudioDeviceInfo();
302 }
303 
304 //***************************************************************************
306 {
307  qDebug("Kwave::PlayBackQt::close()");
308 
309  QMutexLocker _lock(&m_lock); // context: main thread
310 
311  if (m_output && m_encoder) {
312  unsigned int pad_bytes_cnt = m_output->periodSize();
313  unsigned int bytes_per_frame = m_output->format().bytesPerFrame();
314  unsigned int pad_samples_cnt = pad_bytes_cnt / bytes_per_frame;
315  Kwave::SampleArray pad_samples(pad_samples_cnt);
316  QByteArray pad_bytes(pad_bytes_cnt, char(0));
317  m_encoder->encode(pad_samples, pad_samples_cnt, pad_bytes);
318 
319  m_buffer.drain(pad_bytes);
320 
321  // stopping the engine might block, so we need to do this unlocked
322  qDebug("Kwave::PlayBackQt::close() - flushing..., state=%d",
323  m_output->state());
324  while (
325  m_output &&
326  (m_output->state() == QAudio::ActiveState) &&
328  ) {
329  m_lock.unlock();
330  qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
331  m_lock.lock();
332  }
333  qDebug("Kwave::PlayBackQt::close() - flushing done.");
334  m_lock.unlock();
335  m_output->stop();
336  m_buffer.stop();
337  m_lock.lock();
338  }
339 
340  delete m_output;
341  m_output = Q_NULLPTR;
342 
343  delete m_encoder;
344  m_encoder = Q_NULLPTR;
345 
346  m_device_name_map.clear();
347  m_available_devices.clear();
348 
349  qDebug("Kwave::PlayBackQt::close() - DONE");
350  return 0;
351 }
352 
353 //***************************************************************************
355 {
356  QMutexLocker _lock(&m_lock); // context: main thread
357 
358  // re-validate the list if necessary
359  if (m_device_name_map.isEmpty() || m_available_devices.isEmpty())
360  scanDevices();
361 
362  QStringList list = m_device_name_map.keys();
363 
364  // move the "default" device to the start of the list
365  if (list.contains(DEFAULT_DEVICE))
366  list.move(list.indexOf(DEFAULT_DEVICE), 0);
367 
368  if (!list.isEmpty()) list.append(_("#TREE#"));
369 
370  return list;
371 }
372 
373 //***************************************************************************
375 {
376  m_available_devices.clear();
377  m_device_name_map.clear();
378 
379  // get the list of available audio output devices from Qt
380  foreach (const QAudioDeviceInfo &device,
381  QAudioDeviceInfo::availableDevices(QAudio::AudioOutput))
382  {
383  QString qt_name = device.deviceName();
384 
385  // for debugging: list all devices
386 // qDebug("name='%s'", DBG(qt_name));
387 
388  // device name not available ?
389  if (!qt_name.length()) {
390  qWarning("PlayBackQt::supportedDevices() "
391  "=> BUG: device with no name?");
392  continue;
393  }
394 
395  QString gui_name = qt_name + _("|sound_note");
396  if (m_device_name_map.contains(gui_name)) {
397  qWarning("PlayBackQt::supportedDevices() "
398  "=> BUG: duplicate device name: '%s'", DBG(gui_name));
399  continue;
400  }
401 
402  m_available_devices.append(device);
403  m_device_name_map[gui_name] = qt_name;
404  }
405 }
406 
407 //***************************************************************************
409 {
410  return _("");
411 }
412 
413 //***************************************************************************
414 QList<unsigned int> Kwave::PlayBackQt::supportedBits(const QString &device)
415 {
416  QMutexLocker _lock(&m_lock); // context: main thread
417 
418  QList<unsigned int> list;
419  const QAudioDeviceInfo info(deviceInfo(device));
420 
421  // no devices at all -> empty list
422  if (info.isNull()) return list;
423 
424  // iterate over all supported sample sizes
425  foreach (int bits, info.supportedSampleSizes()) {
426  if (!list.contains(bits) && (bits > 0))
427  list << Kwave::toUint(bits);
428  }
429 
430  std::sort(list.begin(), list.end(), std::greater<unsigned int>());
431  return list;
432 }
433 
434 //***************************************************************************
435 int Kwave::PlayBackQt::detectChannels(const QString &device,
436  unsigned int &min, unsigned int &max)
437 {
438  QMutexLocker _lock(&m_lock); // context: main thread
439 
440  const QAudioDeviceInfo info(deviceInfo(device));
441 
442  max = std::numeric_limits<unsigned int>::min();
443  min = std::numeric_limits<unsigned int>::max();
444 
445  // no devices at all -> empty
446  if (info.isNull()) return -1;
447 
448  // iterate over all supported sample sizes
449  foreach (int channels, info.supportedChannelCounts()) {
450  if (channels <= 0) continue;
451  unsigned int c = Kwave::toUint(channels);
452  if (c < 1) continue;
453  if (c < min) min = c;
454  if (c > max) max = c;
455  }
456 
457  return (max > 0) ? max : -1;
458 }
459 
460 //***************************************************************************
461 //***************************************************************************
462 
463 //***************************************************************************
465  :QIODevice(),
466  m_lock(QMutex::Recursive),
467  m_sem_free(0),
468  m_sem_filled(0),
469  m_raw_buffer(),
470  m_timeout(1000),
471  m_pad_data(),
472  m_pad_ofs(0)
473 {
474 }
475 
476 //***************************************************************************
478 {
479 }
480 
481 //***************************************************************************
482 void Kwave::PlayBackQt::Buffer::start(unsigned int buf_size, int timeout)
483 {
484  m_raw_buffer.clear();
485  m_sem_filled.acquire(m_sem_filled.available());
486  m_sem_free.acquire(m_sem_free.available());
487  m_sem_free.release(buf_size);
488  m_timeout = timeout;
489  m_pad_data.clear();
490  m_pad_ofs = 0;
491 
492  open(QIODevice::ReadOnly);
493 }
494 
495 //***************************************************************************
497 {
498  QMutexLocker _lock(&m_lock); // context: main thread
499  m_timeout = timeout;
500  qDebug("Kwave::PlayBackQt::Buffer::setTimeout(%d)", timeout);
501 }
502 
503 //***************************************************************************
504 void Kwave::PlayBackQt::Buffer::drain(QByteArray &padding)
505 {
506  m_pad_data = padding;
507  m_pad_ofs = 0;
508 }
509 
510 //***************************************************************************
512 {
513  close();
514 }
515 
516 //***************************************************************************
517 qint64 Kwave::PlayBackQt::Buffer::readData(char *data, qint64 len)
518 {
519  qint64 read_bytes = -1;
520  qint64 requested = len;
521 
522 // qDebug("Kwave::PlayBackQt::Buffer::readData(..., len=%lld", requested);
523  if (len == 0) return 0;
524  if (len < 0) return -1;
525 
526  while (len > 0) {
527  int count = qMin<int>(
528  qMax(m_sem_filled.available(), 1),
529  Kwave::toInt(len)
530  );
531  if (Q_LIKELY(m_sem_filled.tryAcquire(count, m_timeout))) {
532 // qDebug(" read: locking...");
533  QMutexLocker _lock(&m_lock); // context: qt streaming engine
534 // qDebug(" read: locked, count=%lld", len);
535  m_sem_free.release(count);
536  if (read_bytes < 0) read_bytes = 0;
537  read_bytes += count;
538  len -= count;
539  while (count--)
540  *(data++) = m_raw_buffer.dequeue();
541  } else break;
542  }
543 
544  // if we are at the end of the stream: do some padding to satisfy Qt
545  while ( (read_bytes < requested) &&
546  !m_pad_data.isEmpty() && (m_pad_ofs < m_pad_data.size()) )
547  {
548  *(data++) = 0;
549  read_bytes++;
550  m_pad_ofs++;
551  }
552 
553  if (read_bytes != requested)
554  qDebug("Kwave::PlayBackQt::Buffer::readData(...) -> read=%lld/%lld",
555  read_bytes, requested);
556 
557  return read_bytes;
558 }
559 
560 //***************************************************************************
561 qint64 Kwave::PlayBackQt::Buffer::writeData(const char *data, qint64 len)
562 {
563 
564  qint64 written_bytes = 0;
565  while (len) {
566  int count = qMin<int>(
567  qMax(m_sem_free.available(), 1),
568  Kwave::toInt(len)
569  );
570  if (Q_LIKELY(m_sem_free.tryAcquire(count, m_timeout * 10))) {
571  QMutexLocker _lock(&m_lock); // context: kwave worker thread
572  m_sem_filled.release(count);
573  written_bytes += count;
574  len -= count;
575  while (count--)
576  m_raw_buffer.enqueue(*(data++));
577  } else break;
578  }
579 
580  return written_bytes;
581 }
582 
583 //***************************************************************************
585 {
586  return QIODevice::bytesAvailable() +
587  m_sem_filled.available() +
588  m_pad_data.size() -
589  m_pad_ofs;
590 }
591 
592 #endif /* HAVE_QT_AUDIO_SUPPORT */
593 
594 //***************************************************************************
595 //***************************************************************************
#define DEFAULT_DEVICE
Definition: PlayBack-Qt.cpp:41
byte_order_t
Definition: ByteOrder.h:25
Definition: App.h:33
virtual QList< unsigned int > supportedBits(const QString &device) Q_DECL_OVERRIDE
virtual unsigned int rawBytesPerSample()=0
QList< QAudioDeviceInfo > m_available_devices
Definition: PlayBack-Qt.h:231
Kwave::SampleEncoder * m_encoder
Definition: PlayBack-Qt.h:240
virtual qint64 bytesAvailable() const Q_DECL_OVERRIDE
virtual qint64 readData(char *data, qint64 len) Q_DECL_OVERRIDE
virtual int detectChannels(const QString &device, unsigned int &min, unsigned int &max) Q_DECL_OVERRIDE
virtual QString fileFilter() Q_DECL_OVERRIDE
void stateChanged(QAudio::State state)
QMap< QString, QString > m_device_name_map
Definition: PlayBack-Qt.h:228
QAudioDeviceInfo deviceInfo(const QString &device) const
bool connect(Kwave::StreamObject &source, const char *output, Kwave::StreamObject &sink, const char *input)
Definition: Connect.cpp:48
void start(unsigned int buf_size, int timeout)
virtual qint64 writeData(const char *data, qint64 len) Q_DECL_OVERRIDE
int toInt(T x)
Definition: Utils.h:127
virtual int close() Q_DECL_OVERRIDE
void createEncoder(const QAudioFormat &format)
Definition: PlayBack-Qt.cpp:62
virtual QStringList supportedDevices() Q_DECL_OVERRIDE
virtual int write(const Kwave::SampleArray &samples) Q_DECL_OVERRIDE
void setTimeout(int timeout)
Kwave::PlayBackQt::Buffer m_buffer
Definition: PlayBack-Qt.h:242
#define _(m)
Definition: memcpy.c:66
unsigned int size() const
#define DBG(qs)
Definition: String.h:55
unsigned int m_buffer_size
Definition: PlayBack-Qt.h:237
virtual ~PlayBackQt() Q_DECL_OVERRIDE
Definition: PlayBack-Qt.cpp:56
QAudioOutput * m_output
Definition: PlayBack-Qt.h:234
virtual ~Buffer() Q_DECL_OVERRIDE
unsigned int toUint(T x)
Definition: Utils.h:109
virtual QString open(const QString &device, double rate, unsigned int channels, unsigned int bits, unsigned int bufbase) Q_DECL_OVERRIDE
QQueue< char > m_raw_buffer
Definition: PlayBack-Qt.h:206
virtual void encode(const Kwave::SampleArray &samples, unsigned int count, QByteArray &raw_data)=0
void drain(QByteArray &padding)