1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3#include "qffmpegaudioinput_p.h"
4#include <qiodevice.h>
5#include <qaudiosource.h>
6#include <qaudiobuffer.h>
7#include <qatomic.h>
8#include <qdebug.h>
9
10QT_BEGIN_NAMESPACE
11
12namespace QFFmpeg {
13
14class AudioSourceIO : public QIODevice
15{
16 Q_OBJECT
17public:
18 AudioSourceIO(QFFmpegAudioInput *audioInput) : QIODevice(), m_input(audioInput)
19 {
20 m_muted = m_input->muted;
21 m_volume = m_input->volume;
22 updateVolume();
23 open(mode: QIODevice::WriteOnly);
24 }
25
26 ~AudioSourceIO() override
27 {
28 // QAudioSource may invoke QIODevice::writeData in the destructor.
29 // Let's reset the audio source to get around the case.
30 if (m_src)
31 m_src->reset();
32 }
33
34 void setDevice(const QAudioDevice &device)
35 {
36 QMutexLocker locker(&m_mutex);
37 if (m_device == device)
38 return;
39 m_device = device;
40 QMetaObject::invokeMethod(obj: this, member: "updateSource");
41 }
42 void setFrameSize(int frameSize)
43 {
44 m_bufferSize.storeRelease(newValue: (frameSize > 0 && m_format.isValid())
45 ? m_format.bytesForFrames(frameCount: frameSize)
46 : DefaultAudioInputBufferSize);
47 }
48 void setRunning(bool r) {
49 QMutexLocker locker(&m_mutex);
50 if (m_running == r)
51 return;
52 m_running = r;
53 QMetaObject::invokeMethod(obj: this, member: "updateRunning");
54 }
55
56 void setVolume(float vol) {
57 QMutexLocker locker(&m_mutex);
58 m_volume = vol;
59 QMetaObject::invokeMethod(obj: this, member: "updateVolume");
60 }
61 void setMuted(bool muted) {
62 QMutexLocker locker(&m_mutex);
63 m_muted = muted;
64 QMetaObject::invokeMethod(obj: this, member: "updateVolume");
65 }
66
67 int bufferSize() const { return m_bufferSize.loadAcquire(); }
68
69protected:
70 qint64 readData(char *, qint64) override
71 {
72 return 0;
73 }
74 qint64 writeData(const char *data, qint64 len) override
75 {
76 Q_ASSERT(m_src);
77
78 int l = len;
79 while (len > 0) {
80 const auto bufferSize = m_bufferSize.loadAcquire();
81 int toAppend = qMin(a: len, b: bufferSize - m_pcm.size());
82 m_pcm.append(s: data, len: toAppend);
83 data += toAppend;
84 len -= toAppend;
85 if (m_pcm.size() == bufferSize)
86 sendBuffer();
87 }
88
89 return l;
90 }
91
92private Q_SLOTS:
93 void updateSource() {
94 QMutexLocker locker(&m_mutex);
95 m_format = m_device.preferredFormat();
96 if (std::exchange(obj&: m_src, new_val: nullptr))
97 m_pcm.clear();
98
99 m_src = std::make_unique<QAudioSource>(args&: m_device, args&: m_format);
100 updateVolume();
101 if (m_running)
102 m_src->start(device: this);
103 }
104 void updateVolume()
105 {
106 if (m_src)
107 m_src->setVolume(m_muted ? 0. : m_volume);
108 }
109 void updateRunning()
110 {
111 QMutexLocker locker(&m_mutex);
112 if (m_running) {
113 if (!m_src)
114 updateSource();
115 m_src->start(device: this);
116 } else {
117 m_src->stop();
118 }
119 }
120
121private:
122
123 void sendBuffer()
124 {
125 QAudioFormat fmt = m_src->format();
126 qint64 time = fmt.durationForBytes(byteCount: m_processed);
127 QAudioBuffer buffer(m_pcm, fmt, time);
128 emit m_input->newAudioBuffer(buffer);
129 m_processed += m_pcm.size();
130 m_pcm.clear();
131 }
132
133 QMutex m_mutex;
134 QAudioDevice m_device;
135 float m_volume = 1.;
136 bool m_muted = false;
137 bool m_running = false;
138
139 QFFmpegAudioInput *m_input = nullptr;
140 std::unique_ptr<QAudioSource> m_src;
141 QAudioFormat m_format;
142 QAtomicInt m_bufferSize = DefaultAudioInputBufferSize;
143 qint64 m_processed = 0;
144 QByteArray m_pcm;
145};
146
147}
148
149QFFmpegAudioInput::QFFmpegAudioInput(QAudioInput *qq)
150 : QPlatformAudioInput(qq)
151{
152 qRegisterMetaType<QAudioBuffer>();
153
154 inputThread = std::make_unique<QThread>();
155 audioIO = new QFFmpeg::AudioSourceIO(this);
156 audioIO->moveToThread(thread: inputThread.get());
157 inputThread->start();
158}
159
160QFFmpegAudioInput::~QFFmpegAudioInput()
161{
162 // Ensure that COM is uninitialized by nested QWindowsResampler
163 // on the same thread that initialized it.
164 audioIO->deleteLater();
165 inputThread->exit();
166 inputThread->wait();
167}
168
169void QFFmpegAudioInput::setAudioDevice(const QAudioDevice &device)
170{
171 audioIO->setDevice(device);
172}
173
174void QFFmpegAudioInput::setMuted(bool muted)
175{
176 audioIO->setMuted(muted);
177}
178
179void QFFmpegAudioInput::setVolume(float volume)
180{
181 audioIO->setVolume(volume);
182}
183
184void QFFmpegAudioInput::setFrameSize(int s)
185{
186 audioIO->setFrameSize(s);
187}
188
189void QFFmpegAudioInput::setRunning(bool b)
190{
191 audioIO->setRunning(b);
192}
193
194int QFFmpegAudioInput::bufferSize() const
195{
196 return audioIO->bufferSize();
197}
198
199QT_END_NAMESPACE
200
201#include "moc_qffmpegaudioinput_p.cpp"
202
203#include "qffmpegaudioinput.moc"
204

source code of qtmultimedia/src/plugins/multimedia/ffmpeg/qffmpegaudioinput.cpp