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();
32  if (!this->isOpen()) { open(QIODevice::WriteOnly | QIODevice::Unbuffered); }
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  m_inputFormat = inputFormat;
100  m_audioInput.reset(new QAudioSource(selectedDevice, m_inputFormat));
101  if (!m_audioInputBuffer) { m_audioInputBuffer = new CAudioInputBuffer(this); }
102  else { m_audioInputBuffer->disconnect(); } // make sure disconnected in any case
103  m_audioInputBuffer->start(m_inputFormat);
104  const QString format = toQString(m_inputFormat);
105  CLogMessage(this).info(u"Starting: '%1' with: %2") << selectedDevice.description() << format;
106 
107 #ifdef Q_OS_MAC
108  this->initMicrophoneMacOS();
109 #else
110  this->initMicrophone();
111 #endif
112  }
113 
114 #ifdef Q_OS_MAC
115  void CInput::initMicrophoneMacOS()
116  {
117  CMacOSMicrophoneAccess::AuthorizationStatus status = m_micAccess.getAuthorizationStatus();
118  if (status == CMacOSMicrophoneAccess::Authorized) { this->initMicrophone(); }
119  else if (status == CMacOSMicrophoneAccess::NotDetermined)
120  {
121  connect(&m_micAccess, &CMacOSMicrophoneAccess::permissionRequestAnswered, this, &CInput::initMicrophone);
122  m_micAccess.requestAccess();
123  CLogMessage(this).info(u"Request macOS permission for microphone");
124  }
125  else { CLogMessage(this).error(u"Microphone access not granted. Voice input will not work."); }
126  }
127 #endif
128 
130  {
131  if (!m_started) { return; }
132  m_started = false;
133  if (m_audioInput) { m_audioInput->stop(); }
134  m_audioInput.reset();
135  if (m_audioInputBuffer)
136  {
137  m_audioInputBuffer->stop();
138  m_audioInputBuffer->deleteLater();
139  m_audioInputBuffer = nullptr;
140  }
141  }
142 
143  void CInput::audioInDataAvailable(const QByteArray &frame)
144  {
145  static_assert(Q_BYTE_ORDER == Q_LITTLE_ENDIAN);
146  QVector<qint16> samples = convertBytesTo16BitPCM(frame);
147 
148  if (m_inputFormat.channelCount() == 2) { samples = convertFromStereoToMono(samples); }
149 
150  const double volume = m_gainRatio;
151  for (qint16 &sample : samples)
152  {
153  int value = qRound(sample * volume);
154  if (value > std::numeric_limits<qint16>::max()) value = std::numeric_limits<qint16>::max();
155  if (value < std::numeric_limits<qint16>::min()) value = std::numeric_limits<qint16>::min();
156  sample = static_cast<qint16>(value);
157 
158  qint16 sampleInput = qAbs(sample);
159  m_maxSampleInput = qMax(qAbs(sampleInput), m_maxSampleInput);
160  }
161 
162  int length;
163  const QByteArray encodedBuffer = m_encoder.encode(samples, samples.size(), &length);
164  m_opusBytesEncoded += length;
165 
166  m_sampleCount += samples.size();
167  if (m_sampleCount >= SampleCountPerEvent)
168  {
169  InputVolumeStreamArgs inputVolumeStreamArgs;
170  qint16 maxInt = std::numeric_limits<qint16>::max();
171  inputVolumeStreamArgs.PeakRaw = static_cast<float>(m_maxSampleInput) / maxInt;
172  inputVolumeStreamArgs.PeakDB = static_cast<float>(20 * std::log10(inputVolumeStreamArgs.PeakRaw));
173  double db = qBound(minDb, inputVolumeStreamArgs.PeakDB, maxDb);
174  double ratio = (db - minDb) / (maxDb - minDb);
175  if (ratio < 0.30) { ratio = 0.0; }
176  if (ratio > 1.0) { ratio = 1.0; }
177  inputVolumeStreamArgs.PeakVU = ratio;
178  emit inputVolumeStream(inputVolumeStreamArgs);
179  m_sampleCount = 0;
180  m_maxSampleInput = 0;
181  }
182 
183  OpusDataAvailableArgs opusDataAvailableArgs = { m_audioSequenceCounter++, encodedBuffer };
184  emit opusDataAvailable(opusDataAvailableArgs);
185  }
186 
187  void CInput::initMicrophone()
188  {
189  m_audioInput->start(m_audioInputBuffer);
190  connect(m_audioInputBuffer, &CAudioInputBuffer::frameAvailable, this, &CInput::audioInDataAvailable);
191  m_started = true;
192  CLogMessage(this).info(u"Started input");
193  }
194 
195 } // namespace swift::core::afv::audio
void frameAvailable(const QByteArray &frame)
Frame is available.
virtual qint64 readData(char *data, qint64 maxlen)
Definition: input.cpp:37
void start(const QAudioFormat &format)
Start.
Definition: input.cpp:28
virtual 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?
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.
#define SWIFT_VERIFY_X(COND, WHERE, WHAT)
A weaker kind of assert.
Definition: verify.h:26