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 | |
10 | QT_BEGIN_NAMESPACE |
11 | |
12 | namespace QFFmpeg { |
13 | |
14 | class AudioSourceIO : public QIODevice |
15 | { |
16 | Q_OBJECT |
17 | public: |
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 | |
69 | protected: |
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 | |
103 | private 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 | |
132 | private: |
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 | |
159 | QFFmpegAudioInput::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 | |
170 | QFFmpegAudioInput::~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 | |
179 | void QFFmpegAudioInput::setAudioDevice(const QAudioDevice &device) |
180 | { |
181 | m_audioIO->setDevice(device); |
182 | } |
183 | |
184 | void QFFmpegAudioInput::setMuted(bool muted) |
185 | { |
186 | m_audioIO->setMuted(muted); |
187 | } |
188 | |
189 | void QFFmpegAudioInput::setVolume(float volume) |
190 | { |
191 | m_audioIO->setVolume(volume); |
192 | } |
193 | |
194 | void QFFmpegAudioInput::setBufferSize(int bufferSize) |
195 | { |
196 | m_audioIO->setBufferSize(bufferSize); |
197 | } |
198 | |
199 | void QFFmpegAudioInput::setRunning(bool b) |
200 | { |
201 | m_audioIO->setRunning(b); |
202 | } |
203 | |
204 | int QFFmpegAudioInput::bufferSize() const |
205 | { |
206 | return m_audioIO->bufferSize(); |
207 | } |
208 | |
209 | QT_END_NAMESPACE |
210 | |
211 | #include "moc_qffmpegaudioinput_p.cpp" |
212 | |
213 | #include "qffmpegaudioinput.moc" |
214 |
Definitions
Learn Advanced QML with KDAB
Find out more