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 
31  {
32  // We can always generate output; report a fixed period's worth
33  // so audio backends that gate readData() calls on bytesAvailable()
34  // pull data periodically rather than treating us as starved (see #406)
35  constexpr qint64 bufferLengthMs = 20;
36  const qint64 bufferSize = m_outputFormat.bytesForDuration(bufferLengthMs * 1000); // microseconds
37  return bufferSize + QIODevice::bytesAvailable();
38  }
39 
40  qint64 CAudioOutputBuffer::readData(char *data, qint64 maxlen)
41  {
42  const int sampleBytes = m_outputFormat.bytesPerSample();
43  const int channelCount = m_outputFormat.channelCount();
44  const qint64 count = maxlen / (sampleBytes * channelCount);
45  QVector<float> buffer;
46  m_sampleProvider->readSamples(buffer, count);
47 
48  for (float sample : std::as_const(buffer))
49  {
50  const float absSample = qAbs(sample);
51  m_maxSampleOutput = std::max(absSample, m_maxSampleOutput);
52  }
53 
54  m_sampleCount += buffer.size();
55  if (m_sampleCount >= SampleCountPerEvent)
56  {
57  OutputVolumeStreamArgs outputVolumeStreamArgs;
58  outputVolumeStreamArgs.PeakRaw = m_maxSampleOutput / 1.0;
59  outputVolumeStreamArgs.PeakDb = static_cast<float>(20 * std::log10(outputVolumeStreamArgs.PeakRaw));
60  const double db = qBound(m_minDb, outputVolumeStreamArgs.PeakDb, m_maxDb);
61  double ratio = (db - m_minDb) / (m_maxDb - m_minDb);
62  if (ratio < 0.30) { ratio = 0.0; }
63  ratio = std::min(ratio, 1.0);
64  outputVolumeStreamArgs.PeakVU = ratio;
65  emit outputVolumeStream(outputVolumeStreamArgs);
66  m_sampleCount = 0;
67  m_maxSampleOutput = 0;
68  }
69 
70  if (channelCount == 2) { buffer = convertFromMonoToStereo(buffer); }
71 
72  memcpy(data, buffer.constData(), static_cast<size_t>(maxlen));
73  return maxlen;
74  }
75 
76  qint64 CAudioOutputBuffer::writeData(const char *data, qint64 len)
77  {
78  Q_UNUSED(data)
79  Q_UNUSED(len)
80  return -1;
81  }
82 
83  COutput::COutput(QObject *parent) : QObject(parent) { this->setObjectName(classNameShort(this)); }
84 
85  void COutput::start(const CAudioDeviceInfo &outputDevice, ISampleProvider *sampleProvider)
86  {
87  if (m_started) { return; }
88 
89  SWIFT_VERIFY_X(outputDevice.isValid() && outputDevice.isOutputDevice(), Q_FUNC_INFO, "Wrong output device");
90 
91  if (m_audioOutputBuffer) { m_audioOutputBuffer->deleteLater(); }
92  m_audioOutputBuffer = new CAudioOutputBuffer(sampleProvider, this);
94 
95  m_device = outputDevice;
96 
97  QAudioFormat outputFormat;
98  outputFormat.setSampleRate(48000);
99  outputFormat.setChannelCount(1);
100  outputFormat.setChannelConfig(QAudioFormat::ChannelConfigMono);
101  outputFormat.setSampleFormat(QAudioFormat::Float);
102  static_assert(Q_BYTE_ORDER == Q_LITTLE_ENDIAN);
103 
104  const QString format = toQString(outputFormat);
105  const QAudioDevice selectedDevice = getLowestLatencyDevice(outputDevice, outputFormat);
106  CLogMessage(this).info(u"Starting: '%1' with: %2") << selectedDevice.description() << format;
107 
108  m_audioOutput.reset(new QAudioSink(selectedDevice, outputFormat));
109  m_audioOutputBuffer->open(QIODevice::ReadWrite | QIODevice::Unbuffered);
110  m_audioOutputBuffer->setAudioFormat(outputFormat);
111  m_audioOutput->start(m_audioOutputBuffer);
112 
113  m_started = true;
114  }
115 
117  {
118  if (!m_started) { return; }
119  m_started = false;
120  m_audioOutput->stop();
121  m_audioOutput.reset();
122  if (m_audioOutputBuffer)
123  {
124  m_audioOutputBuffer->deleteLater();
125  m_audioOutputBuffer = nullptr;
126  }
127  }
128 
129  /*
130  double COutput::getDeviceOutputVolume() const
131  {
132  if (m_audioOutput && m_started) { return static_cast<double>(m_audioOutput->volume()); }
133  return 0.0;
134  }
135 
136  bool COutput::setDeviceOutputVolume(double volume)
137  {
138  if (!m_audioOutput) { return false; }
139  const qreal v = normalize0to100qr(volume);
140  m_audioOutput->setVolume(v);
141  return true;
142  }
143  */
144 
145 } // namespace swift::core::afv::audio
qint64 writeData(const char *data, qint64 len)
Definition: output.cpp:76
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:40
void stop()
Stop output.
Definition: output.cpp:116
void outputVolumeStream(const OutputVolumeStreamArgs &args)
Streaming data.
COutput(QObject *parent=nullptr)
Ctor.
Definition: output.cpp:83
void start(const swift::misc::audio::CAudioDeviceInfo &outputDevice, swift::sound::sample_provider::ISampleProvider *sampleProvider)
Start output.
Definition: output.cpp:85
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.
virtual qint64 bytesAvailable() const const
virtual bool open(QIODeviceBase::OpenMode mode)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void deleteLater()
void setObjectName(QAnyStringView name)
void reset(T *other)
QString arg(Args &&... args) const const
#define SWIFT_VERIFY_X(COND, WHERE, WHAT)
A weaker kind of assert.
Definition: verify.h:26