audioinput.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  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 <stdlib.h>
  52. #include <math.h>
  53. #include <QDateTime>
  54. #include <QDebug>
  55. #include <QPainter>
  56. #include <QVBoxLayout>
  57. #include <QAudioDeviceInfo>
  58. #include <QAudioInput>
  59. #include <qendian.h>
  60. AudioInfo::AudioInfo(const QAudioFormat &format)
  61. : m_format(format)
  62. {
  63. switch (m_format.sampleSize()) {
  64. case 8:
  65. switch (m_format.sampleType()) {
  66. case QAudioFormat::UnSignedInt:
  67. m_maxAmplitude = 255;
  68. break;
  69. case QAudioFormat::SignedInt:
  70. m_maxAmplitude = 127;
  71. break;
  72. default:
  73. break;
  74. }
  75. break;
  76. case 16:
  77. switch (m_format.sampleType()) {
  78. case QAudioFormat::UnSignedInt:
  79. m_maxAmplitude = 65535;
  80. break;
  81. case QAudioFormat::SignedInt:
  82. m_maxAmplitude = 32767;
  83. break;
  84. default:
  85. break;
  86. }
  87. break;
  88. case 32:
  89. switch (m_format.sampleType()) {
  90. case QAudioFormat::UnSignedInt:
  91. m_maxAmplitude = 0xffffffff;
  92. break;
  93. case QAudioFormat::SignedInt:
  94. m_maxAmplitude = 0x7fffffff;
  95. break;
  96. case QAudioFormat::Float:
  97. m_maxAmplitude = 0x7fffffff; // Kind of
  98. default:
  99. break;
  100. }
  101. break;
  102. default:
  103. break;
  104. }
  105. }
  106. void AudioInfo::start()
  107. {
  108. open(QIODevice::WriteOnly);
  109. }
  110. void AudioInfo::stop()
  111. {
  112. close();
  113. }
  114. qint64 AudioInfo::readData(char *data, qint64 maxlen)
  115. {
  116. Q_UNUSED(data)
  117. Q_UNUSED(maxlen)
  118. return 0;
  119. }
  120. qint64 AudioInfo::writeData(const char *data, qint64 len)
  121. {
  122. if (m_maxAmplitude) {
  123. Q_ASSERT(m_format.sampleSize() % 8 == 0);
  124. const int channelBytes = m_format.sampleSize() / 8;
  125. const int sampleBytes = m_format.channelCount() * channelBytes;
  126. Q_ASSERT(len % sampleBytes == 0);
  127. const int numSamples = len / sampleBytes;
  128. quint32 maxValue = 0;
  129. const unsigned char *ptr = reinterpret_cast<const unsigned char *>(data);
  130. for (int i = 0; i < numSamples; ++i) {
  131. for (int j = 0; j < m_format.channelCount(); ++j) {
  132. quint32 value = 0;
  133. if (m_format.sampleSize() == 8 && m_format.sampleType() == QAudioFormat::UnSignedInt) {
  134. value = *reinterpret_cast<const quint8*>(ptr);
  135. } else if (m_format.sampleSize() == 8 && m_format.sampleType() == QAudioFormat::SignedInt) {
  136. value = qAbs(*reinterpret_cast<const qint8*>(ptr));
  137. } else if (m_format.sampleSize() == 16 && m_format.sampleType() == QAudioFormat::UnSignedInt) {
  138. if (m_format.byteOrder() == QAudioFormat::LittleEndian)
  139. value = qFromLittleEndian<quint16>(ptr);
  140. else
  141. value = qFromBigEndian<quint16>(ptr);
  142. } else if (m_format.sampleSize() == 16 && m_format.sampleType() == QAudioFormat::SignedInt) {
  143. if (m_format.byteOrder() == QAudioFormat::LittleEndian)
  144. value = qAbs(qFromLittleEndian<qint16>(ptr));
  145. else
  146. value = qAbs(qFromBigEndian<qint16>(ptr));
  147. } else if (m_format.sampleSize() == 32 && m_format.sampleType() == QAudioFormat::UnSignedInt) {
  148. if (m_format.byteOrder() == QAudioFormat::LittleEndian)
  149. value = qFromLittleEndian<quint32>(ptr);
  150. else
  151. value = qFromBigEndian<quint32>(ptr);
  152. } else if (m_format.sampleSize() == 32 && m_format.sampleType() == QAudioFormat::SignedInt) {
  153. if (m_format.byteOrder() == QAudioFormat::LittleEndian)
  154. value = qAbs(qFromLittleEndian<qint32>(ptr));
  155. else
  156. value = qAbs(qFromBigEndian<qint32>(ptr));
  157. } else if (m_format.sampleSize() == 32 && m_format.sampleType() == QAudioFormat::Float) {
  158. value = qAbs(*reinterpret_cast<const float*>(ptr) * 0x7fffffff); // assumes 0-1.0
  159. }
  160. maxValue = qMax(value, maxValue);
  161. ptr += channelBytes;
  162. }
  163. }
  164. maxValue = qMin(maxValue, m_maxAmplitude);
  165. m_level = qreal(maxValue) / m_maxAmplitude;
  166. }
  167. emit update();
  168. return len;
  169. }
  170. RenderArea::RenderArea(QWidget *parent)
  171. : QWidget(parent)
  172. {
  173. setBackgroundRole(QPalette::Base);
  174. setAutoFillBackground(true);
  175. setMinimumHeight(30);
  176. setMinimumWidth(200);
  177. }
  178. void RenderArea::paintEvent(QPaintEvent * /* event */)
  179. {
  180. QPainter painter(this);
  181. painter.setPen(Qt::black);
  182. painter.drawRect(QRect(painter.viewport().left()+10,
  183. painter.viewport().top()+10,
  184. painter.viewport().right()-20,
  185. painter.viewport().bottom()-20));
  186. if (m_level == 0.0)
  187. return;
  188. int pos = ((painter.viewport().right()-20)-(painter.viewport().left()+11))*m_level;
  189. painter.fillRect(painter.viewport().left()+11,
  190. painter.viewport().top()+10,
  191. pos,
  192. painter.viewport().height()-21,
  193. Qt::red);
  194. }
  195. void RenderArea::setLevel(qreal value)
  196. {
  197. m_level = value;
  198. update();
  199. }
  200. InputTest::InputTest()
  201. {
  202. initializeWindow();
  203. initializeAudio(QAudioDeviceInfo::defaultInputDevice());
  204. }
  205. void InputTest::initializeWindow()
  206. {
  207. QWidget *window = new QWidget;
  208. QVBoxLayout *layout = new QVBoxLayout;
  209. m_canvas = new RenderArea(this);
  210. layout->addWidget(m_canvas);
  211. m_deviceBox = new QComboBox(this);
  212. const QAudioDeviceInfo &defaultDeviceInfo = QAudioDeviceInfo::defaultInputDevice();
  213. m_deviceBox->addItem(defaultDeviceInfo.deviceName(), QVariant::fromValue(defaultDeviceInfo));
  214. for (auto &deviceInfo: QAudioDeviceInfo::availableDevices(QAudio::AudioInput)) {
  215. if (deviceInfo != defaultDeviceInfo)
  216. m_deviceBox->addItem(deviceInfo.deviceName(), QVariant::fromValue(deviceInfo));
  217. }
  218. connect(m_deviceBox, QOverload<int>::of(&QComboBox::activated), this, &InputTest::deviceChanged);
  219. layout->addWidget(m_deviceBox);
  220. m_volumeSlider = new QSlider(Qt::Horizontal, this);
  221. m_volumeSlider->setRange(0, 100);
  222. m_volumeSlider->setValue(100);
  223. connect(m_volumeSlider, &QSlider::valueChanged, this, &InputTest::sliderChanged);
  224. layout->addWidget(m_volumeSlider);
  225. m_modeButton = new QPushButton(this);
  226. connect(m_modeButton, &QPushButton::clicked, this, &InputTest::toggleMode);
  227. layout->addWidget(m_modeButton);
  228. m_suspendResumeButton = new QPushButton(this);
  229. connect(m_suspendResumeButton, &QPushButton::clicked, this, &InputTest::toggleSuspend);
  230. layout->addWidget(m_suspendResumeButton);
  231. window->setLayout(layout);
  232. setCentralWidget(window);
  233. window->show();
  234. }
  235. void InputTest::initializeAudio(const QAudioDeviceInfo &deviceInfo)
  236. {
  237. QAudioFormat format;
  238. format.setSampleRate(8000);
  239. format.setChannelCount(1);
  240. format.setSampleSize(16);
  241. format.setSampleType(QAudioFormat::SignedInt);
  242. format.setByteOrder(QAudioFormat::LittleEndian);
  243. format.setCodec("audio/pcm");
  244. if (!deviceInfo.isFormatSupported(format)) {
  245. qWarning() << "Default format not supported - trying to use nearest";
  246. format = deviceInfo.nearestFormat(format);
  247. }
  248. m_audioInfo.reset(new AudioInfo(format));
  249. connect(m_audioInfo.data(), &AudioInfo::update, [this]() {
  250. m_canvas->setLevel(m_audioInfo->level());
  251. });
  252. m_audioInput.reset(new QAudioInput(deviceInfo, format));
  253. qreal initialVolume = QAudio::convertVolume(m_audioInput->volume(),
  254. QAudio::LinearVolumeScale,
  255. QAudio::LogarithmicVolumeScale);
  256. m_volumeSlider->setValue(qRound(initialVolume * 100));
  257. m_audioInfo->start();
  258. toggleMode();
  259. }
  260. void InputTest::toggleMode()
  261. {
  262. m_audioInput->stop();
  263. toggleSuspend();
  264. // Change bewteen pull and push modes
  265. if (m_pullMode) {
  266. m_modeButton->setText(tr("Enable push mode"));
  267. m_audioInput->start(m_audioInfo.data());
  268. } else {
  269. m_modeButton->setText(tr("Enable pull mode"));
  270. auto io = m_audioInput->start();
  271. connect(io, &QIODevice::readyRead,
  272. [&, io]() {
  273. qint64 len = m_audioInput->bytesReady();
  274. const int BufferSize = 4096;
  275. if (len > BufferSize)
  276. len = BufferSize;
  277. QByteArray buffer(len, 0);
  278. qint64 l = io->read(buffer.data(), len);
  279. if (l > 0)
  280. m_audioInfo->write(buffer.constData(), l);
  281. });
  282. }
  283. m_pullMode = !m_pullMode;
  284. }
  285. void InputTest::toggleSuspend()
  286. {
  287. // toggle suspend/resume
  288. if (m_audioInput->state() == QAudio::SuspendedState || m_audioInput->state() == QAudio::StoppedState) {
  289. m_audioInput->resume();
  290. m_suspendResumeButton->setText(tr("Suspend recording"));
  291. } else if (m_audioInput->state() == QAudio::ActiveState) {
  292. m_audioInput->suspend();
  293. m_suspendResumeButton->setText(tr("Resume recording"));
  294. } else if (m_audioInput->state() == QAudio::IdleState) {
  295. // no-op
  296. }
  297. }
  298. void InputTest::deviceChanged(int index)
  299. {
  300. m_audioInfo->stop();
  301. m_audioInput->stop();
  302. m_audioInput->disconnect(this);
  303. initializeAudio(m_deviceBox->itemData(index).value<QAudioDeviceInfo>());
  304. }
  305. void InputTest::sliderChanged(int value)
  306. {
  307. qreal linearVolume = QAudio::convertVolume(value / qreal(100),
  308. QAudio::LogarithmicVolumeScale,
  309. QAudio::LinearVolumeScale);
  310. m_audioInput->setVolume(linearVolume);
  311. }