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) : 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_audioSource)
31 m_audioSource->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 setBufferSize(int bufferSize)
43 {
44 m_bufferSize.storeRelease(newValue: (bufferSize > 0 && m_format.isValid())
45 ? m_format.bytesForFrames(frameCount: bufferSize)
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_audioSource);
77
78 int l = len;
79 while (len > 0) {
80 const auto bufferSize = m_bufferSize.loadAcquire();
81
82 while (m_pcm.size() > bufferSize) {
83 // bufferSize has been reduced. Send data until m_pcm
84 // can hold more data.
85 sendBuffer(pcmData: m_pcm.first(n: bufferSize));
86 m_pcm.remove(index: 0, len: bufferSize);
87 }
88
89 // Size of m_pcm is always <= bufferSize
90 int toAppend = qMin(a: len, b: bufferSize - m_pcm.size());
91 m_pcm.append(s: data, len: toAppend);
92 data += toAppend;
93 len -= toAppend;
94 if (m_pcm.size() == bufferSize) {
95 sendBuffer(pcmData: m_pcm);
96 m_pcm.clear();
97 }
98 }
99
100 return l;
101 }
102
103private Q_SLOTS:
104 void updateSource() {
105 QMutexLocker locker(&m_mutex);
106 m_format = m_device.preferredFormat();
107 if (std::exchange(obj&: m_audioSource, new_val: nullptr))
108 m_pcm.clear();
109
110 m_audioSource = std::make_unique<QAudioSource>(args&: m_device, args&: m_format);
111 updateVolume();
112 if (m_running)
113 m_audioSource->start(device: this);
114 }
115 void updateVolume()
116 {
117 if (m_audioSource)
118 m_audioSource->setVolume(m_muted ? 0. : m_volume);
119 }
120 void updateRunning()
121 {
122 QMutexLocker locker(&m_mutex);
123 if (m_running) {
124 if (!m_audioSource)
125 updateSource();
126 m_audioSource->start(device: this);
127 } else {
128 m_audioSource->stop();
129 }
130 }
131
132private:
133
134 void sendBuffer(const QByteArray &pcmData)
135 {
136 QAudioFormat fmt = m_audioSource->format();
137 qint64 time = fmt.durationForBytes(byteCount: m_processed);
138 QAudioBuffer buffer(pcmData, fmt, time);
139 emit m_input->newAudioBuffer(buffer);
140 m_processed += pcmData.size();
141 }
142
143 QMutex m_mutex;
144 QAudioDevice m_device;
145 float m_volume = 1.;
146 bool m_muted = false;
147 bool m_running = false;
148
149 QFFmpegAudioInput *m_input = nullptr;
150 std::unique_ptr<QAudioSource> m_audioSource;
151 QAudioFormat m_format;
152 QAtomicInt m_bufferSize = DefaultAudioInputBufferSize;
153 qint64 m_processed = 0;
154 QByteArray m_pcm;
155};
156
157} // namespace QFFmpeg
158
159QFFmpegAudioInput::QFFmpegAudioInput(QAudioInput *qq)
160 : QPlatformAudioInput(qq)
161{
162 qRegisterMetaType<QAudioBuffer>();
163
164 m_inputThread = std::make_unique<QThread>();
165 m_audioIO = new QFFmpeg::AudioSourceIO(this);
166 m_audioIO->moveToThread(thread: m_inputThread.get());
167 m_inputThread->start();
168}
169
170QFFmpegAudioInput::~QFFmpegAudioInput()
171{
172 // Ensure that COM is uninitialized by nested QWindowsResampler
173 // on the same thread that initialized it.
174 m_audioIO->deleteLater();
175 m_inputThread->exit();
176 m_inputThread->wait();
177}
178
179void QFFmpegAudioInput::setAudioDevice(const QAudioDevice &device)
180{
181 m_audioIO->setDevice(device);
182}
183
184void QFFmpegAudioInput::setMuted(bool muted)
185{
186 m_audioIO->setMuted(muted);
187}
188
189void QFFmpegAudioInput::setVolume(float volume)
190{
191 m_audioIO->setVolume(volume);
192}
193
194void QFFmpegAudioInput::setBufferSize(int bufferSize)
195{
196 m_audioIO->setBufferSize(bufferSize);
197}
198
199void QFFmpegAudioInput::setRunning(bool b)
200{
201 m_audioIO->setRunning(b);
202}
203
204int QFFmpegAudioInput::bufferSize() const
205{
206 return m_audioIO->bufferSize();
207}
208
209QT_END_NAMESPACE
210
211#include "moc_qffmpegaudioinput_p.cpp"
212
213#include "qffmpegaudioinput.moc"
214

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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