swift
input.cpp
Go to the documentation of this file.
1 // SPDX-FileCopyrightText: Copyright (C) 2019 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
5 
6 #include "core/afv/audio/input.h"
7 
8 #include <cmath>
9 
10 #include <QAudioDevice>
11 #include <QtGlobal>
12 
13 #include "misc/logmessage.h"
14 #include "misc/verify.h"
15 #include "sound/audioutilities.h"
16 
17 using namespace swift::misc;
18 using namespace swift::misc::audio;
19 using namespace swift::sound;
20 
21 namespace swift::core::afv::audio
22 {
23  CAudioInputBuffer::CAudioInputBuffer(QObject *parent) : QIODevice(parent)
24  {
25  this->setObjectName("CAudioInputBuffer");
26  }
27 
28  void CAudioInputBuffer::start(const QAudioFormat &format)
29  {
30  m_format = format;
31  m_buffer.clear();
33  }
34 
35  void CAudioInputBuffer::stop() { this->close(); }
36 
37  qint64 CAudioInputBuffer::readData(char *data, qint64 maxlen)
38  {
39  Q_UNUSED(data)
40  Q_UNUSED(maxlen)
41  return 0;
42  }
43 
44  qint64 CAudioInputBuffer::writeData(const char *data, qint64 len)
45  {
46  m_buffer.append(data, static_cast<int>(len));
47  const int byteCount = 1920 * m_format.channelCount();
48  while (m_buffer.size() > byteCount)
49  {
50  emit frameAvailable(m_buffer.left(byteCount));
51  m_buffer.remove(0, byteCount);
52  }
53  return len;
54  }
55 
56  CInput::CInput(int sampleRate, QObject *parent)
57  : QObject(parent), m_sampleRate(sampleRate), m_encoder(sampleRate, 1, OPUS_APPLICATION_VOIP)
58  {
59  this->setObjectName("CInput");
60  m_encoder.setBitRate(16 * 1024);
61  }
62 
63  bool CInput::setGainRatio(double gainRatio)
64  {
65  if (qFuzzyCompare(m_gainRatio, gainRatio)) { return false; }
66  m_gainRatio = gainRatio;
67  return true;
68  }
69 
70  /*
71  double CInput::getDeviceInputVolume() const
72  {
73  if (m_audioInput) { return static_cast<double>(m_audioInput->volume()); }
74  return 0.0;
75  }
76 
77  bool CInput::setDeviceInputVolume(double volume)
78  {
79  if (!m_audioInput && m_started) { return false; }
80  const qreal v = normalize0to100qr(volume);
81  m_audioInput->setVolume(v);
82  return true;
83  }
84  */
85 
86  void CInput::start(const CAudioDeviceInfo &inputDevice)
87  {
88  if (m_started) { return; }
89 
90  SWIFT_VERIFY_X(inputDevice.isValid() && inputDevice.isInputDevice(), Q_FUNC_INFO, "Wrong input device");
91  m_device = inputDevice;
92 
93  QAudioFormat inputFormat;
94  inputFormat.setSampleRate(m_sampleRate); // normally 48000
95  inputFormat.setChannelCount(1);
96  inputFormat.setSampleFormat(QAudioFormat::Int16);
97 
98  QAudioDevice selectedDevice = getLowestLatencyDevice(inputDevice, inputFormat);
99 
100  if (selectedDevice.isNull())
101  {
102  CLogMessage(this).error(u"No suitable audio input device found for '%1'. Required format: %2")
103  << inputDevice.getName() << toQString(inputFormat);
104  return;
105  }
106 
107  if (!selectedDevice.isFormatSupported(inputFormat))
108  {
109  CLogMessage(this).error(u"Selected device '%1' does not support required format: %2")
110  << selectedDevice.description() << toQString(inputFormat);
111  return;
112  }
113 
114  m_inputFormat = inputFormat;
115  m_audioInput.reset(new QAudioSource(selectedDevice, m_inputFormat));
116  if (!m_audioInputBuffer) { m_audioInputBuffer = new CAudioInputBuffer(this); }
117  else { m_audioInputBuffer->disconnect(); } // make sure disconnected in any case
118  m_audioInputBuffer->start(m_inputFormat);
119  const QString format = toQString(m_inputFormat);
120  CLogMessage(this).info(u"Starting: '%1' with: %2") << selectedDevice.description() << format;
121 
122 #ifdef Q_OS_MAC
123  this->initMicrophoneMacOS();
124 #else
125  this->initMicrophone();
126 #endif
127  }
128 
129 #ifdef Q_OS_MAC
130  void CInput::initMicrophoneMacOS()
131  {
132  CMacOSMicrophoneAccess::AuthorizationStatus status = m_micAccess.getAuthorizationStatus();
133  if (status == CMacOSMicrophoneAccess::Authorized) { this->initMicrophone(); }
134  else if (status == CMacOSMicrophoneAccess::NotDetermined)
135  {
136  connect(&m_micAccess, &CMacOSMicrophoneAccess::permissionRequestAnswered, this, &CInput::initMicrophone);
137  m_micAccess.requestAccess();
138  CLogMessage(this).info(u"Request macOS permission for microphone");
139  }
140  else { CLogMessage(this).error(u"Microphone access not granted. Voice input will not work."); }
141  }
142 #endif
143 
145  {
146  if (!m_started) { return; }
147  m_started = false;
148  if (m_audioInput) { m_audioInput->stop(); }
149  m_audioInput.reset();
150  if (m_audioInputBuffer)
151  {
152  m_audioInputBuffer->stop();
153  m_audioInputBuffer->deleteLater();
154  m_audioInputBuffer = nullptr;
155  }
156  }
157 
158  void CInput::audioInDataAvailable(const QByteArray &frame)
159  {
160  static_assert(Q_BYTE_ORDER == Q_LITTLE_ENDIAN);
161  QVector<qint16> samples = convertBytesTo16BitPCM(frame);
162 
163  if (m_inputFormat.channelCount() == 2) { samples = convertFromStereoToMono(samples); }
164 
165  const double volume = m_gainRatio;
166  for (qint16 &sample : samples)
167  {
168  int value = qRound(sample * volume);
169  if (value > std::numeric_limits<qint16>::max()) value = std::numeric_limits<qint16>::max();
170  if (value < std::numeric_limits<qint16>::min()) value = std::numeric_limits<qint16>::min();
171  sample = static_cast<qint16>(value);
172 
173  // qAbs(qint16::min()) would assert because +32768 cannot be represented
174  // as a qint16. For peak detection, saturate this edge case to qint16::max().
175  const qint16 sampleInput =
176  sample != std::numeric_limits<qint16>::min() ? qAbs(sample) : std::numeric_limits<qint16>::max();
177  m_maxSampleInput = qMax(sampleInput, m_maxSampleInput);
178  }
179 
180  int length {};
181  const QByteArray encodedBuffer = m_encoder.encode(samples, samples.size(), &length);
182  m_opusBytesEncoded += length;
183 
184  m_sampleCount += samples.size();
185  if (m_sampleCount >= SampleCountPerEvent)
186  {
187  InputVolumeStreamArgs inputVolumeStreamArgs;
188  qint16 maxInt = std::numeric_limits<qint16>::max();
189  inputVolumeStreamArgs.PeakRaw = static_cast<float>(m_maxSampleInput) / maxInt;
190  inputVolumeStreamArgs.PeakDB = static_cast<float>(20 * std::log10(inputVolumeStreamArgs.PeakRaw));
191  double db = qBound(minDb, inputVolumeStreamArgs.PeakDB, maxDb);
192  double ratio = (db - minDb) / (maxDb - minDb);
193  if (ratio < 0.30) { ratio = 0.0; }
194  ratio = std::min(ratio, 1.0);
195  inputVolumeStreamArgs.PeakVU = ratio;
196  emit inputVolumeStream(inputVolumeStreamArgs);
197  m_sampleCount = 0;
198  m_maxSampleInput = 0;
199  }
200 
201  OpusDataAvailableArgs opusDataAvailableArgs = { m_audioSequenceCounter++, encodedBuffer };
202  emit opusDataAvailable(opusDataAvailableArgs);
203  }
204 
205  void CInput::initMicrophone()
206  {
207  m_audioInput->start(m_audioInputBuffer);
208  connect(m_audioInputBuffer, &CAudioInputBuffer::frameAvailable, this, &CInput::audioInDataAvailable);
209  m_started = true;
210  CLogMessage(this).info(u"Started input");
211  }
212 
213 } // namespace swift::core::afv::audio
void frameAvailable(const QByteArray &frame)
Frame is available.
qint64 readData(char *data, qint64 maxlen)
Definition: input.cpp:37
void start(const QAudioFormat &format)
Start.
Definition: input.cpp:28
qint64 writeData(const char *data, qint64 len)
Definition: input.cpp:44
CInput(int sampleRate, QObject *parent=nullptr)
Ctor.
Definition: input.cpp:56
void start(const swift::misc::audio::CAudioDeviceInfo &inputDevice)
Start.
Definition: input.cpp:86
void opusDataAvailable(const OpusDataAvailableArgs &args)
OPUS data.
void inputVolumeStream(const InputVolumeStreamArgs &args)
Volume stream data.
bool setGainRatio(double gainRatio)
Gain ratio, value a amplitude need to be multiplied with.
Definition: input.cpp:63
Class for emitting a log message.
Definition: logmessage.h:27
void permissionRequestAnswered(bool granted)
User has answered the permission request popup.
AuthorizationStatus
Authorization status.
Derived & error(const char16_t(&format)[N])
Set the severity to error, providing a format string.
Derived & info(const char16_t(&format)[N])
Set the severity to info, providing a format string.
Value object encapsulating information of a audio device.
bool isInputDevice() const
Input device.
bool isValid() const
Valid audio device object?
const QString & getName() const
Get the device name.
QByteArray encode(const QVector< qint16 > &pcmSamples, int samplesLength, int *encodedLength)
Encode.
Definition: opusencoder.cpp:18
void setBitRate(int bitRate)
Bit rate.
Definition: opusencoder.cpp:16
Free functions in swift::misc.
QByteArray & append(QByteArrayView data)
void clear()
QByteArray left(qsizetype len) &&
QByteArray & remove(qsizetype pos, qsizetype len)
qsizetype size() const const
virtual void close()
bool isOpen() const const
virtual bool open(QIODeviceBase::OpenMode mode)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void deleteLater()
bool disconnect(const QMetaObject::Connection &connection)
void setObjectName(QAnyStringView name)
void reset(T *other)
#define SWIFT_VERIFY_X(COND, WHERE, WHAT)
A weaker kind of assert.
Definition: verify.h:26