swift
output.cpp
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 <cmath>
7 
8 #include <QStringBuilder>
9 
10 #include "misc/logmessage.h"
11 #include "misc/metadatautils.h"
12 #include "misc/verify.h"
13 #include "sound/audioutilities.h"
14 
15 using namespace swift::misc;
16 using namespace swift::misc::audio;
17 using namespace swift::sound;
18 using namespace swift::sound::sample_provider;
19 
20 namespace swift::core::afv::audio
21 {
22  CAudioOutputBuffer::CAudioOutputBuffer(ISampleProvider *sampleProvider, QObject *parent)
23  : QIODevice(parent), m_sampleProvider(sampleProvider)
24  {
25  Q_ASSERT_X(sampleProvider, Q_FUNC_INFO, "need sample provide");
26  const QString on = QStringLiteral("%1 for %2").arg(classNameShort(this), sampleProvider->objectName());
27  this->setObjectName(on);
28  }
29 
30 #ifdef Q_OS_WIN
31  qint64 CAudioOutputBuffer::bytesAvailable() const
32  {
33  // Workaround to mimic the pre-Qt6 behavior.
34  // With Qt6, the QAudioSink on Windows uses the bytesAvailable function to trigger
35  // a call to readData() only when data is available. Other platforms still use a
36  // pull procedure that automatically calls readData() afer a specific period. Until
37  // a proper solution for the bytesAvailable() is implemented, this uses a fixed number.
38  // readData() will handle it itself if actually no data is available.
39  return 3840 + QIODevice::bytesAvailable();
40  }
41 #endif
42 
43  qint64 CAudioOutputBuffer::readData(char *data, qint64 maxlen)
44  {
45  const int sampleBytes = m_outputFormat.bytesPerSample();
46  const int channelCount = m_outputFormat.channelCount();
47  const qint64 count = maxlen / (sampleBytes * channelCount);
48  QVector<float> buffer;
49  m_sampleProvider->readSamples(buffer, count);
50 
51  for (float sample : std::as_const(buffer))
52  {
53  const float absSample = qAbs(sample);
54  if (absSample > m_maxSampleOutput) { m_maxSampleOutput = absSample; }
55  }
56 
57  m_sampleCount += buffer.size();
58  if (m_sampleCount >= SampleCountPerEvent)
59  {
60  OutputVolumeStreamArgs outputVolumeStreamArgs;
61  outputVolumeStreamArgs.PeakRaw = m_maxSampleOutput / 1.0;
62  outputVolumeStreamArgs.PeakDb = static_cast<float>(20 * std::log10(outputVolumeStreamArgs.PeakRaw));
63  const double db = qBound(m_minDb, outputVolumeStreamArgs.PeakDb, m_maxDb);
64  double ratio = (db - m_minDb) / (m_maxDb - m_minDb);
65  if (ratio < 0.30) { ratio = 0.0; }
66  if (ratio > 1.0) { ratio = 1.0; }
67  outputVolumeStreamArgs.PeakVU = ratio;
68  emit outputVolumeStream(outputVolumeStreamArgs);
69  m_sampleCount = 0;
70  m_maxSampleOutput = 0;
71  }
72 
73  if (channelCount == 2) { buffer = convertFromMonoToStereo(buffer); }
74 
75  memcpy(data, buffer.constData(), static_cast<size_t>(maxlen));
76  return maxlen;
77  }
78 
79  qint64 CAudioOutputBuffer::writeData(const char *data, qint64 len)
80  {
81  Q_UNUSED(data)
82  Q_UNUSED(len)
83  return -1;
84  }
85 
86  COutput::COutput(QObject *parent) : QObject(parent) { this->setObjectName(classNameShort(this)); }
87 
88  void COutput::start(const CAudioDeviceInfo &outputDevice, ISampleProvider *sampleProvider)
89  {
90  if (m_started) { return; }
91 
92  SWIFT_VERIFY_X(outputDevice.isValid() && outputDevice.isOutputDevice(), Q_FUNC_INFO, "Wrong output device");
93 
94  if (m_audioOutputBuffer) { m_audioOutputBuffer->deleteLater(); }
95  m_audioOutputBuffer = new CAudioOutputBuffer(sampleProvider, this);
96  connect(m_audioOutputBuffer, &CAudioOutputBuffer::outputVolumeStream, this, &COutput::outputVolumeStream);
97 
98  m_device = outputDevice;
99 
100  QAudioFormat outputFormat;
101  outputFormat.setSampleRate(48000);
102  outputFormat.setChannelCount(1);
103  outputFormat.setSampleFormat(QAudioFormat::Float);
104  static_assert(Q_BYTE_ORDER == Q_LITTLE_ENDIAN);
105 
106  const QString format = toQString(outputFormat);
107  const QAudioDevice selectedDevice = getLowestLatencyDevice(outputDevice, outputFormat);
108  CLogMessage(this).info(u"Starting: '%1' with: %2") << selectedDevice.description() << format;
109 
110  m_audioOutput.reset(new QAudioSink(selectedDevice, outputFormat));
111  m_audioOutputBuffer->open(QIODevice::ReadWrite | QIODevice::Unbuffered);
112  m_audioOutputBuffer->setAudioFormat(outputFormat);
113  m_audioOutput->start(m_audioOutputBuffer);
114 
115  m_started = true;
116  }
117 
119  {
120  if (!m_started) { return; }
121  m_started = false;
122  m_audioOutput->stop();
123  m_audioOutput.reset();
124  if (m_audioOutputBuffer)
125  {
126  m_audioOutputBuffer->deleteLater();
127  m_audioOutputBuffer = nullptr;
128  }
129  }
130 
131  /*
132  double COutput::getDeviceOutputVolume() const
133  {
134  if (m_audioOutput && m_started) { return static_cast<double>(m_audioOutput->volume()); }
135  return 0.0;
136  }
137 
138  bool COutput::setDeviceOutputVolume(double volume)
139  {
140  if (!m_audioOutput) { return false; }
141  const qreal v = normalize0to100qr(volume);
142  m_audioOutput->setVolume(v);
143  return true;
144  }
145  */
146 
147 } // namespace swift::core::afv::audio
qint64 writeData(const char *data, qint64 len)
Definition: output.cpp:79
void outputVolumeStream(const OutputVolumeStreamArgs &args)
Volume stream.
void setAudioFormat(const QAudioFormat &format)
Set the format.
Definition: output.h:35
qint64 readData(char *data, qint64 maxlen)
Definition: output.cpp:43
void stop()
Stop output.
Definition: output.cpp:118
void outputVolumeStream(const OutputVolumeStreamArgs &args)
Streaming data.
COutput(QObject *parent=nullptr)
Ctor.
Definition: output.cpp:86
void start(const swift::misc::audio::CAudioDeviceInfo &outputDevice, swift::sound::sample_provider::ISampleProvider *sampleProvider)
Start output.
Definition: output.cpp:88
Class for emitting a log message.
Definition: logmessage.h:27
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 isOutputDevice() const
Output device.
bool isValid() const
Valid audio device object?
virtual int readSamples(QVector< float > &samples, qint64 count)=0
Read samples.
Free functions in swift::misc.
QString classNameShort(const QObject *object)
Class name as from QMetaObject::className without namespace.
#define SWIFT_VERIFY_X(COND, WHERE, WHAT)
A weaker kind of assert.
Definition: verify.h:26