1 | // Copyright (C) 2020 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 "qffmpegaudiodecoder_p.h" |
4 | #include "qffmpegresampler_p.h" |
5 | #include "qaudiobuffer.h" |
6 | |
7 | #include "qffmpegplaybackengine_p.h" |
8 | #include "playbackengine/qffmpegrenderer_p.h" |
9 | |
10 | #include <qloggingcategory.h> |
11 | |
12 | static Q_LOGGING_CATEGORY(qLcAudioDecoder, "qt.multimedia.ffmpeg.audioDecoder" ) |
13 | |
14 | QT_BEGIN_NAMESPACE |
15 | |
16 | namespace QFFmpeg |
17 | { |
18 | |
19 | class SteppingAudioRenderer : public Renderer |
20 | { |
21 | Q_OBJECT |
22 | public: |
23 | SteppingAudioRenderer(const QAudioFormat &format) : Renderer({}), m_format(format) { } |
24 | |
25 | RenderingResult renderInternal(Frame frame) override |
26 | { |
27 | if (!frame.isValid()) |
28 | return {}; |
29 | |
30 | if (!m_resampler) |
31 | m_resampler = std::make_unique<QFFmpegResampler>(args: frame.codecContext(), args&: m_format); |
32 | |
33 | emit newAudioBuffer(m_resampler->resample(frame: frame.avFrame())); |
34 | |
35 | return {}; |
36 | } |
37 | |
38 | signals: |
39 | void newAudioBuffer(QAudioBuffer); |
40 | |
41 | private: |
42 | QAudioFormat m_format; |
43 | std::unique_ptr<QFFmpegResampler> m_resampler; |
44 | }; |
45 | |
46 | class AudioDecoder : public PlaybackEngine |
47 | { |
48 | Q_OBJECT |
49 | public: |
50 | explicit AudioDecoder(const QAudioFormat &format) : m_format(format) { } |
51 | |
52 | RendererPtr createRenderer(QPlatformMediaPlayer::TrackType trackType) override |
53 | { |
54 | if (trackType != QPlatformMediaPlayer::AudioStream) |
55 | return RendererPtr{ {}, {} }; |
56 | |
57 | auto result = createPlaybackEngineObject<SteppingAudioRenderer>(args&: m_format); |
58 | m_audioRenderer = result.get(); |
59 | |
60 | connect(sender: result.get(), signal: &SteppingAudioRenderer::newAudioBuffer, context: this, |
61 | slot: &AudioDecoder::newAudioBuffer); |
62 | |
63 | return result; |
64 | } |
65 | |
66 | void nextBuffer() |
67 | { |
68 | Q_ASSERT(m_audioRenderer); |
69 | Q_ASSERT(!m_audioRenderer->isStepForced()); |
70 | |
71 | m_audioRenderer->doForceStep(); |
72 | // updateObjectsPausedState(); |
73 | } |
74 | |
75 | signals: |
76 | void newAudioBuffer(QAudioBuffer); |
77 | |
78 | private: |
79 | QPointer<Renderer> m_audioRenderer; |
80 | QAudioFormat m_format; |
81 | }; |
82 | } // namespace QFFmpeg |
83 | |
84 | QFFmpegAudioDecoder::QFFmpegAudioDecoder(QAudioDecoder *parent) |
85 | : QPlatformAudioDecoder(parent) |
86 | { |
87 | } |
88 | |
89 | QFFmpegAudioDecoder::~QFFmpegAudioDecoder() = default; |
90 | |
91 | QUrl QFFmpegAudioDecoder::source() const |
92 | { |
93 | return m_url; |
94 | } |
95 | |
96 | void QFFmpegAudioDecoder::setSource(const QUrl &fileName) |
97 | { |
98 | stop(); |
99 | m_sourceDevice = nullptr; |
100 | |
101 | if (std::exchange(obj&: m_url, new_val: fileName) != fileName) |
102 | sourceChanged(); |
103 | } |
104 | |
105 | QIODevice *QFFmpegAudioDecoder::sourceDevice() const |
106 | { |
107 | return m_sourceDevice; |
108 | } |
109 | |
110 | void QFFmpegAudioDecoder::setSourceDevice(QIODevice *device) |
111 | { |
112 | stop(); |
113 | m_url.clear(); |
114 | if (std::exchange(obj&: m_sourceDevice, new_val&: device) != device) |
115 | sourceChanged(); |
116 | } |
117 | |
118 | void QFFmpegAudioDecoder::start() |
119 | { |
120 | qCDebug(qLcAudioDecoder) << "start" ; |
121 | auto checkNoError = [this]() { |
122 | if (error() == QAudioDecoder::NoError) |
123 | return true; |
124 | |
125 | durationChanged(duration: -1); |
126 | positionChanged(position: -1); |
127 | |
128 | m_decoder.reset(); |
129 | |
130 | return false; |
131 | }; |
132 | |
133 | m_decoder = std::make_unique<AudioDecoder>(args&: m_audioFormat); |
134 | connect(sender: m_decoder.get(), signal: &AudioDecoder::errorOccured, context: this, slot: &QFFmpegAudioDecoder::errorSignal); |
135 | connect(sender: m_decoder.get(), signal: &AudioDecoder::endOfStream, context: this, slot: &QFFmpegAudioDecoder::done); |
136 | connect(sender: m_decoder.get(), signal: &AudioDecoder::newAudioBuffer, context: this, |
137 | slot: &QFFmpegAudioDecoder::newAudioBuffer); |
138 | |
139 | QFFmpeg::MediaDataHolder::Maybe media = QFFmpeg::MediaDataHolder::create(url: m_url, stream: m_sourceDevice, cancelToken: nullptr); |
140 | |
141 | if (media) { |
142 | Q_ASSERT(media.value()); |
143 | if (media.value()->streamInfo(trackType: QPlatformMediaPlayer::AudioStream).isEmpty()) |
144 | error(error: QAudioDecoder::FormatError, |
145 | errorString: QLatin1String("The media doesn't contain an audio stream" )); |
146 | else |
147 | m_decoder->setMedia(std::move(*media.value())); |
148 | } else { |
149 | auto [code, description] = media.error(); |
150 | errorSignal(err: code, errorString: description); |
151 | } |
152 | |
153 | if (!checkNoError()) |
154 | return; |
155 | |
156 | m_decoder->setState(QMediaPlayer::PausedState); |
157 | if (!checkNoError()) |
158 | return; |
159 | |
160 | m_decoder->nextBuffer(); |
161 | if (!checkNoError()) |
162 | return; |
163 | |
164 | durationChanged(duration: QFFmpeg::toUserDuration(trackDuration: m_decoder->duration()).get()); |
165 | setIsDecoding(true); |
166 | } |
167 | |
168 | void QFFmpegAudioDecoder::stop() |
169 | { |
170 | qCDebug(qLcAudioDecoder) << ">>>>> stop" ; |
171 | if (m_decoder) { |
172 | m_decoder.reset(); |
173 | done(); |
174 | } |
175 | } |
176 | |
177 | QAudioFormat QFFmpegAudioDecoder::audioFormat() const |
178 | { |
179 | return m_audioFormat; |
180 | } |
181 | |
182 | void QFFmpegAudioDecoder::setAudioFormat(const QAudioFormat &format) |
183 | { |
184 | if (std::exchange(obj&: m_audioFormat, new_val: format) != format) |
185 | formatChanged(format: m_audioFormat); |
186 | } |
187 | |
188 | QAudioBuffer QFFmpegAudioDecoder::read() |
189 | { |
190 | auto buffer = std::exchange(obj&: m_audioBuffer, new_val: QAudioBuffer{}); |
191 | if (!buffer.isValid()) |
192 | return buffer; |
193 | qCDebug(qLcAudioDecoder) << "reading buffer" << buffer.startTime(); |
194 | bufferAvailableChanged(available: false); |
195 | if (m_decoder) |
196 | m_decoder->nextBuffer(); |
197 | return buffer; |
198 | } |
199 | |
200 | void QFFmpegAudioDecoder::newAudioBuffer(const QAudioBuffer &b) |
201 | { |
202 | Q_ASSERT(b.isValid()); |
203 | Q_ASSERT(!m_audioBuffer.isValid()); |
204 | Q_ASSERT(!bufferAvailable()); |
205 | |
206 | qCDebug(qLcAudioDecoder) << "new audio buffer" << b.startTime(); |
207 | m_audioBuffer = b; |
208 | const qint64 pos = b.startTime(); |
209 | positionChanged(position: pos/1000); |
210 | bufferAvailableChanged(available: b.isValid()); |
211 | bufferReady(); |
212 | } |
213 | |
214 | void QFFmpegAudioDecoder::done() |
215 | { |
216 | qCDebug(qLcAudioDecoder) << ">>>>> DONE!" ; |
217 | finished(); |
218 | } |
219 | |
220 | void QFFmpegAudioDecoder::errorSignal(int err, const QString &errorString) |
221 | { |
222 | // unfortunately the error enums for QAudioDecoder and QMediaPlayer aren't identical. |
223 | // Map them. |
224 | switch (QMediaPlayer::Error(err)) { |
225 | case QMediaPlayer::NoError: |
226 | error(error: QAudioDecoder::NoError, errorString); |
227 | break; |
228 | case QMediaPlayer::ResourceError: |
229 | error(error: QAudioDecoder::ResourceError, errorString); |
230 | break; |
231 | case QMediaPlayer::FormatError: |
232 | error(error: QAudioDecoder::FormatError, errorString); |
233 | break; |
234 | case QMediaPlayer::NetworkError: |
235 | // fall through, Network error doesn't exist in QAudioDecoder |
236 | case QMediaPlayer::AccessDeniedError: |
237 | error(error: QAudioDecoder::AccessDeniedError, errorString); |
238 | break; |
239 | } |
240 | } |
241 | |
242 | QT_END_NAMESPACE |
243 | |
244 | #include "moc_qffmpegaudiodecoder_p.cpp" |
245 | |
246 | #include "qffmpegaudiodecoder.moc" |
247 | |