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) : 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 | |
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_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 | |
92 | private 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 | |
121 | private: |
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 | |
149 | QFFmpegAudioInput::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 | |
160 | QFFmpegAudioInput::~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 | |
169 | void QFFmpegAudioInput::setAudioDevice(const QAudioDevice &device) |
170 | { |
171 | audioIO->setDevice(device); |
172 | } |
173 | |
174 | void QFFmpegAudioInput::setMuted(bool muted) |
175 | { |
176 | audioIO->setMuted(muted); |
177 | } |
178 | |
179 | void QFFmpegAudioInput::setVolume(float volume) |
180 | { |
181 | audioIO->setVolume(volume); |
182 | } |
183 | |
184 | void QFFmpegAudioInput::setFrameSize(int s) |
185 | { |
186 | audioIO->setFrameSize(s); |
187 | } |
188 | |
189 | void QFFmpegAudioInput::setRunning(bool b) |
190 | { |
191 | audioIO->setRunning(b); |
192 | } |
193 | |
194 | int QFFmpegAudioInput::bufferSize() const |
195 | { |
196 | return audioIO->bufferSize(); |
197 | } |
198 | |
199 | QT_END_NAMESPACE |
200 | |
201 | #include "moc_qffmpegaudioinput_p.cpp" |
202 | |
203 | #include "qffmpegaudioinput.moc" |
204 | |