audioinput.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. /****************************************************************************
  2. **
  3. ** Copyright (C) 2017 The Qt Company Ltd.
  4. ** Contact: https://www.qt.io/licensing/
  5. **
  6. ** This file is part of the examples of the Qt Toolkit.
  7. **
  8. ** $QT_BEGIN_LICENSE:BSD$
  9. ** Commercial License Usage
  10. ** Licensees holding valid commercial Qt licenses may use this file in
  11. ** accordance with the commercial license agreement provided with the
  12. ** Software or, alternatively, in accordance with the terms contained in
  13. ** a written agreement between you and The Qt Company. For licensing terms
  14. ** and conditions see https://www.qt.io/terms-conditions. For further
  15. ** information use the contact form at https://www.qt.io/contact-us.
  16. **
  17. ** BSD License Usage
  18. ** Alternatively, you may use this file under the terms of the BSD license
  19. ** as follows:
  20. **
  21. ** "Redistribution and use in source and binary forms, with or without
  22. ** modification, are permitted provided that the following conditions are
  23. ** met:
  24. ** * Redistributions of source code must retain the above copyright
  25. ** notice, this list of conditions and the following disclaimer.
  26. ** * Redistributions in binary form must reproduce the above copyright
  27. ** notice, this list of conditions and the following disclaimer in
  28. ** the documentation and/or other materials provided with the
  29. ** distribution.
  30. ** * Neither the name of The Qt Company Ltd nor the names of its
  31. ** contributors may be used to endorse or promote products derived
  32. ** from this software without specific prior written permission.
  33. **
  34. **
  35. ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  36. ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  37. ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  38. ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  39. ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  40. ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  41. ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  42. ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  43. ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  44. ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  45. ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
  46. **
  47. ** $QT_END_LICENSE$
  48. **
  49. ****************************************************************************/
  50. #include "audioinput.h"
  51. #include "ui/audio_render.h"
  52. #include <stdlib.h>
  53. #include <math.h>
  54. #include <QDateTime>
  55. #include <QDebug>
  56. #include <QPainter>
  57. #include <QVBoxLayout>
  58. #include <QAudioDeviceInfo>
  59. #include <QAudioInput>
  60. #include <qendian.h>
  61. AudioInfo::AudioInfo(const QAudioFormat &format)
  62. : m_format(format)
  63. {
  64. switch (m_format.sampleSize()) {
  65. case 8:
  66. switch (m_format.sampleType()) {
  67. case QAudioFormat::UnSignedInt:
  68. m_maxAmplitude = 255;
  69. break;
  70. case QAudioFormat::SignedInt:
  71. m_maxAmplitude = 127;
  72. break;
  73. default:
  74. break;
  75. }
  76. break;
  77. case 16:
  78. switch (m_format.sampleType()) {
  79. case QAudioFormat::UnSignedInt:
  80. m_maxAmplitude = 65535;
  81. break;
  82. case QAudioFormat::SignedInt:
  83. m_maxAmplitude = 32767;
  84. break;
  85. default:
  86. break;
  87. }
  88. break;
  89. case 32:
  90. switch (m_format.sampleType()) {
  91. case QAudioFormat::UnSignedInt:
  92. m_maxAmplitude = 0xffffffff;
  93. break;
  94. case QAudioFormat::SignedInt:
  95. m_maxAmplitude = 0x7fffffff;
  96. break;
  97. case QAudioFormat::Float:
  98. m_maxAmplitude = 0x7fffffff; // Kind of
  99. default:
  100. break;
  101. }
  102. break;
  103. default:
  104. break;
  105. }
  106. }
  107. void AudioInfo::start()
  108. {
  109. open(QIODevice::WriteOnly);
  110. }
  111. void AudioInfo::stop()
  112. {
  113. close();
  114. }
  115. qint64 AudioInfo::readData(char *data, qint64 maxlen)
  116. {
  117. Q_UNUSED(data)
  118. Q_UNUSED(maxlen)
  119. return 0;
  120. }
  121. qint64 AudioInfo::writeData(const char *data, qint64 len)
  122. {
  123. if (m_maxAmplitude) {
  124. Q_ASSERT(m_format.sampleSize() % 8 == 0);
  125. const int channelBytes = m_format.sampleSize() / 8;
  126. const int sampleBytes = m_format.channelCount() * channelBytes;
  127. Q_ASSERT(len % sampleBytes == 0);
  128. const int numSamples = len / sampleBytes;
  129. quint32 maxValue = 0;
  130. const unsigned char *ptr = reinterpret_cast<const unsigned char *>(data);
  131. for (int i = 0; i < numSamples; ++i) {
  132. for (int j = 0; j < m_format.channelCount(); ++j) {
  133. quint32 value = 0;
  134. if (m_format.sampleSize() == 8 && m_format.sampleType() == QAudioFormat::UnSignedInt) {
  135. value = *reinterpret_cast<const quint8*>(ptr);
  136. } else if (m_format.sampleSize() == 8 && m_format.sampleType() == QAudioFormat::SignedInt) {
  137. value = qAbs(*reinterpret_cast<const qint8*>(ptr));
  138. } else if (m_format.sampleSize() == 16 && m_format.sampleType() == QAudioFormat::UnSignedInt) {
  139. if (m_format.byteOrder() == QAudioFormat::LittleEndian)
  140. value = qFromLittleEndian<quint16>(ptr);
  141. else
  142. value = qFromBigEndian<quint16>(ptr);
  143. } else if (m_format.sampleSize() == 16 && m_format.sampleType() == QAudioFormat::SignedInt) {
  144. if (m_format.byteOrder() == QAudioFormat::LittleEndian)
  145. value = qAbs(qFromLittleEndian<qint16>(ptr));
  146. else
  147. value = qAbs(qFromBigEndian<qint16>(ptr));
  148. } else if (m_format.sampleSize() == 32 && m_format.sampleType() == QAudioFormat::UnSignedInt) {
  149. if (m_format.byteOrder() == QAudioFormat::LittleEndian)
  150. value = qFromLittleEndian<quint32>(ptr);
  151. else
  152. value = qFromBigEndian<quint32>(ptr);
  153. } else if (m_format.sampleSize() == 32 && m_format.sampleType() == QAudioFormat::SignedInt) {
  154. if (m_format.byteOrder() == QAudioFormat::LittleEndian)
  155. value = qAbs(qFromLittleEndian<qint32>(ptr));
  156. else
  157. value = qAbs(qFromBigEndian<qint32>(ptr));
  158. } else if (m_format.sampleSize() == 32 && m_format.sampleType() == QAudioFormat::Float) {
  159. value = qAbs(*reinterpret_cast<const float*>(ptr) * 0x7fffffff); // assumes 0-1.0
  160. }
  161. maxValue = qMax(value, maxValue);
  162. ptr += channelBytes;
  163. }
  164. }
  165. maxValue = qMin(maxValue, m_maxAmplitude);
  166. m_level = qreal(maxValue) / m_maxAmplitude;
  167. }
  168. emit update();
  169. return len;
  170. }
  171. InputTest::InputTest()
  172. {
  173. initializeWindow();
  174. initializeAudio(QAudioDeviceInfo::defaultInputDevice());
  175. }
  176. void InputTest::initializeWindow()
  177. {
  178. QWidget *window = new QWidget;
  179. QVBoxLayout *layout = new QVBoxLayout;
  180. m_canvas = new RenderArea(this);
  181. layout->addWidget(m_canvas);
  182. m_deviceBox = new QComboBox(this);
  183. const QAudioDeviceInfo &defaultDeviceInfo = QAudioDeviceInfo::defaultInputDevice();
  184. m_deviceBox->addItem(defaultDeviceInfo.deviceName(), QVariant::fromValue(defaultDeviceInfo));
  185. for (auto &deviceInfo: QAudioDeviceInfo::availableDevices(QAudio::AudioInput)) {
  186. if (deviceInfo != defaultDeviceInfo)
  187. m_deviceBox->addItem(deviceInfo.deviceName(), QVariant::fromValue(deviceInfo));
  188. }
  189. connect(m_deviceBox, QOverload<int>::of(&QComboBox::activated), this, &InputTest::deviceChanged);
  190. layout->addWidget(m_deviceBox);
  191. m_volumeSlider = new QSlider(Qt::Horizontal, this);
  192. m_volumeSlider->setRange(0, 100);
  193. m_volumeSlider->setValue(100);
  194. connect(m_volumeSlider, &QSlider::valueChanged, this, &InputTest::sliderChanged);
  195. layout->addWidget(m_volumeSlider);
  196. m_modeButton = new QPushButton(this);
  197. connect(m_modeButton, &QPushButton::clicked, this, &InputTest::toggleMode);
  198. layout->addWidget(m_modeButton);
  199. m_suspendResumeButton = new QPushButton(this);
  200. connect(m_suspendResumeButton, &QPushButton::clicked, this, &InputTest::toggleSuspend);
  201. layout->addWidget(m_suspendResumeButton);
  202. window->setLayout(layout);
  203. setCentralWidget(window);
  204. window->show();
  205. }
  206. void InputTest::initializeAudio(const QAudioDeviceInfo &deviceInfo)
  207. {
  208. QAudioFormat format;
  209. format.setSampleRate(8000);
  210. format.setChannelCount(1);
  211. format.setSampleSize(16);
  212. format.setSampleType(QAudioFormat::SignedInt);
  213. format.setByteOrder(QAudioFormat::LittleEndian);
  214. format.setCodec("audio/pcm");
  215. if (!deviceInfo.isFormatSupported(format)) {
  216. qWarning() << "Default format not supported - trying to use nearest";
  217. format = deviceInfo.nearestFormat(format);
  218. }
  219. m_audioInfo.reset(new AudioInfo(format));
  220. connect(m_audioInfo.data(), &AudioInfo::update, [this]() {
  221. m_canvas->setLevel(m_audioInfo->level());
  222. });
  223. m_audioInput.reset(new QAudioInput(deviceInfo, format));
  224. qreal initialVolume = QAudio::convertVolume(m_audioInput->volume(),
  225. QAudio::LinearVolumeScale,
  226. QAudio::LogarithmicVolumeScale);
  227. m_volumeSlider->setValue(qRound(initialVolume * 100));
  228. m_audioInfo->start();
  229. toggleMode();
  230. }
  231. void InputTest::toggleMode()
  232. {
  233. m_audioInput->stop();
  234. toggleSuspend();
  235. // Change bewteen pull and push modes
  236. if (m_pullMode) {
  237. m_modeButton->setText(tr("Enable push mode"));
  238. m_audioInput->start(m_audioInfo.data());
  239. } else {
  240. m_modeButton->setText(tr("Enable pull mode"));
  241. auto io = m_audioInput->start();
  242. connect(io, &QIODevice::readyRead,
  243. [&, io]() {
  244. qint64 len = m_audioInput->bytesReady();
  245. const int BufferSize = 4096;
  246. if (len > BufferSize)
  247. len = BufferSize;
  248. QByteArray buffer(len, 0);
  249. qint64 l = io->read(buffer.data(), len);
  250. if (l > 0)
  251. m_audioInfo->write(buffer.constData(), l);
  252. });
  253. }
  254. m_pullMode = !m_pullMode;
  255. }
  256. void InputTest::toggleSuspend()
  257. {
  258. // toggle suspend/resume
  259. if (m_audioInput->state() == QAudio::SuspendedState || m_audioInput->state() == QAudio::StoppedState) {
  260. m_audioInput->resume();
  261. m_suspendResumeButton->setText(tr("Suspend recording"));
  262. } else if (m_audioInput->state() == QAudio::ActiveState) {
  263. m_audioInput->suspend();
  264. m_suspendResumeButton->setText(tr("Resume recording"));
  265. } else if (m_audioInput->state() == QAudio::IdleState) {
  266. // no-op
  267. }
  268. }
  269. void InputTest::deviceChanged(int index)
  270. {
  271. m_audioInfo->stop();
  272. m_audioInput->stop();
  273. m_audioInput->disconnect(this);
  274. initializeAudio(m_deviceBox->itemData(index).value<QAudioDeviceInfo>());
  275. }
  276. void InputTest::sliderChanged(int value)
  277. {
  278. qreal linearVolume = QAudio::convertVolume(value / qreal(100),
  279. QAudio::LogarithmicVolumeScale,
  280. QAudio::LinearVolumeScale);
  281. m_audioInput->setVolume(linearVolume);
  282. }