Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qandroidaudiodecoder.cpp
Go to the documentation of this file.
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
4
5#include <QtCore/qcoreapplication.h>
6#include <QtCore/qjniobject.h>
7#include <QtCore/qjnienvironment.h>
8#include <QtCore/private/qandroidextras_p.h>
9#include <qloggingcategory.h>
10#include <QTimer>
11#include <QFile>
12#include <QDir>
13
14#include <sys/stat.h>
15#include <fcntl.h>
16#include <unistd.h>
17
19
20static const char tempFile[] = "encoded.wav";
21constexpr int dequeueTimeout = 5000;
22Q_STATIC_LOGGING_CATEGORY(adLogger, "QAndroidAudioDecoder");
23
24using namespace Qt::Literals;
25
26Decoder::Decoder()
27 : m_format(AMediaFormat_new())
28{}
29
30Decoder::~Decoder()
31{
32 if (m_codec) {
33 AMediaCodec_delete(m_codec);
34 m_codec = nullptr;
35 }
36
37 if (m_extractor) {
38 AMediaExtractor_delete(m_extractor);
39 m_extractor = nullptr;
40 }
41
42 if (m_format) {
43 AMediaFormat_delete(m_format);
44 m_format = nullptr;
45 }
46}
47
48void Decoder::stop()
49{
50 if (!m_codec)
51 return;
52
53 const media_status_t err = AMediaCodec_stop(m_codec);
54 if (err != AMEDIA_OK)
55 qCWarning(adLogger) << "stop() error: " << err;
56}
57
58void Decoder::setSource(const QUrl &source)
59{
60 const QJniObject path = QJniObject::callStaticObjectMethod(
61 "org/qtproject/qt/android/multimedia/QtMultimediaUtils",
62 "getMimeType",
63 "(Landroid/content/Context;Ljava/lang/String;)Ljava/lang/String;",
64 QNativeInterface::QAndroidApplication::context().object(),
65 QJniObject::fromString(source.path()).object());
66
67 const QString mime = path.isValid() ? path.toString() : u""_s;
68
69 if (!mime.isEmpty() && !mime.contains(u"audio"_s, Qt::CaseInsensitive)) {
70 m_formatError = tr("Cannot set source, invalid mime type for the source provided.");
71 return;
72 }
73
74 if (!m_extractor)
75 m_extractor = AMediaExtractor_new();
76
77 QFile file(source.path());
78 if (!file.open(QFile::ReadOnly)) {
79 emit error(QAudioDecoder::ResourceError, tr("Cannot open the file"));
80 return;
81 }
82
83 const int fd = file.handle();
84
85 if (fd < 0) {
86 emit error(QAudioDecoder::ResourceError, tr("Invalid fileDescriptor for source."));
87 return;
88 }
89 const int size = file.size();
90 media_status_t status = AMediaExtractor_setDataSourceFd(m_extractor, fd, 0,
91 size > 0 ? size : LONG_MAX);
92 close(fd);
93
94 if (status != AMEDIA_OK) {
95 if (m_extractor) {
96 AMediaExtractor_delete(m_extractor);
97 m_extractor = nullptr;
98 }
99 m_formatError = tr("Setting source for Audio Decoder failed.");
100 }
101}
102
103void Decoder::createDecoder()
104{
105 // get encoded format for decoder
106 m_format = AMediaExtractor_getTrackFormat(m_extractor, 0);
107
108 const char *mime;
109 if (!AMediaFormat_getString(m_format, AMEDIAFORMAT_KEY_MIME, &mime)) {
110 if (m_extractor) {
111 AMediaExtractor_delete(m_extractor);
112 m_extractor = nullptr;
113 }
114 emit error(QAudioDecoder::FormatError, tr("Format not supported by Audio Decoder."));
115
116 return;
117 }
118
119 // get audio duration from source
120 int64_t durationUs;
121 AMediaFormat_getInt64(m_format, AMEDIAFORMAT_KEY_DURATION, &durationUs);
122 emit durationChanged(durationUs / 1000);
123
124 // set default output audio format from input file
125 if (!m_outputFormat.isValid()) {
126 int32_t sampleRate;
127 AMediaFormat_getInt32(m_format, AMEDIAFORMAT_KEY_SAMPLE_RATE, &sampleRate);
128 m_outputFormat.setSampleRate(sampleRate);
129 int32_t channelCount;
130 AMediaFormat_getInt32(m_format, AMEDIAFORMAT_KEY_CHANNEL_COUNT, &channelCount);
131 m_outputFormat.setChannelCount(channelCount);
132 m_outputFormat.setSampleFormat(QAudioFormat::Int16);
133 }
134
135 m_codec = AMediaCodec_createDecoderByType(mime);
136}
137
138void Decoder::doDecode()
139{
140 if (!m_formatError.isEmpty()) {
141 emit error(QAudioDecoder::FormatError, m_formatError);
142 return;
143 }
144
145 if (!m_extractor) {
146 emit error(QAudioDecoder::ResourceError, tr("Cannot decode, source not set."));
147 return;
148 }
149
150 createDecoder();
151
152 if (!m_codec) {
153 emit error(QAudioDecoder::ResourceError, tr("Audio Decoder could not be created."));
154 return;
155 }
156
157 media_status_t status = AMediaCodec_configure(m_codec, m_format, nullptr /* surface */,
158 nullptr /* crypto */, 0);
159
160 if (status != AMEDIA_OK) {
161 emit error(QAudioDecoder::ResourceError, tr("Audio Decoder failed configuration."));
162 return;
163 }
164
165 status = AMediaCodec_start(m_codec);
166 if (status != AMEDIA_OK) {
167 emit error(QAudioDecoder::ResourceError, tr("Audio Decoder failed to start."));
168 return;
169 }
170
171 AMediaExtractor_selectTrack(m_extractor, 0);
172
173 emit decodingChanged(true);
174 m_inputEOS = false;
175 while (!m_inputEOS) {
176 // handle input buffer
177 const ssize_t bufferIdx = AMediaCodec_dequeueInputBuffer(m_codec, dequeueTimeout);
178
179 if (bufferIdx >= 0) {
180 size_t bufferSize = {};
181 uint8_t *buffer = AMediaCodec_getInputBuffer(m_codec, bufferIdx, &bufferSize);
182 const int sample = AMediaExtractor_readSampleData(m_extractor, buffer, bufferSize);
183 if (sample < 0) {
184 m_inputEOS = true;
185 break;
186 }
187
188 const int64_t presentationTimeUs = AMediaExtractor_getSampleTime(m_extractor);
189 AMediaCodec_queueInputBuffer(m_codec, bufferIdx, 0, sample, presentationTimeUs,
190 m_inputEOS ? AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM : 0);
191 AMediaExtractor_advance(m_extractor);
192
193 // handle output buffer
194 AMediaCodecBufferInfo info;
195 ssize_t idx = AMediaCodec_dequeueOutputBuffer(m_codec, &info, dequeueTimeout);
196
197 while (idx == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED
198 || idx == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
199 if (idx == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED)
200 qCWarning(adLogger) << "dequeueOutputBuffer() status: outputFormat changed";
201
202 idx = AMediaCodec_dequeueOutputBuffer(m_codec, &info, dequeueTimeout);
203 }
204
205 if (idx >= 0) {
206 if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM)
207 break;
208
209 if (info.size > 0) {
210 size_t bufferSize;
211 const uint8_t *bufferData = AMediaCodec_getOutputBuffer(m_codec, idx,
212 &bufferSize);
213 const QByteArray data((const char*)(bufferData + info.offset), info.size);
214 auto audioBuffer = QAudioBuffer(data, m_outputFormat, presentationTimeUs);
215 if (presentationTimeUs >= 0)
216 emit positionChanged(std::move(audioBuffer), presentationTimeUs / 1000);
217
218 AMediaCodec_releaseOutputBuffer(m_codec, idx, false);
219 }
220 } else if (idx == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
221 qCWarning(adLogger) << "dequeueOutputBuffer() status: try again later";
222 break;
223 } else {
224 qCWarning(adLogger) <<
225 "AMediaCodec_dequeueOutputBuffer() status: invalid buffer idx " << idx;
226 }
227 } else {
228 qCWarning(adLogger) << "dequeueInputBuffer() status: invalid buffer idx " << bufferIdx;
229 }
230 }
231 emit finished();
232}
233
234QAndroidAudioDecoder::QAndroidAudioDecoder(QAudioDecoder *parent)
235 : QPlatformAudioDecoder(parent),
236 m_decoder(new Decoder())
237{
238 connect(m_decoder, &Decoder::positionChanged, this, &QAndroidAudioDecoder::positionChanged);
239 connect(m_decoder, &Decoder::durationChanged, this, &QAndroidAudioDecoder::durationChanged);
240 connect(m_decoder, &Decoder::error, this, &QAndroidAudioDecoder::error);
241 connect(m_decoder, &Decoder::finished, this, &QAndroidAudioDecoder::finished);
242 connect(m_decoder, &Decoder::decodingChanged, this, &QPlatformAudioDecoder::setIsDecoding);
243 connect(this, &QAndroidAudioDecoder::setSourceUrl, m_decoder, & Decoder::setSource);
244}
245
247{
248 m_decoder->thread()->quit();
249 m_decoder->thread()->wait();
250 delete m_threadDecoder;
251 delete m_decoder;
252}
253
254void QAndroidAudioDecoder::setSource(const QUrl &fileName)
255{
256 if (!requestPermissions())
257 return;
258
259 if (isDecoding())
260 return;
261
262 m_device = nullptr;
263 error(QAudioDecoder::NoError, QStringLiteral(""));
264
265 if (m_source != fileName) {
266 m_source = fileName;
267 emit setSourceUrl(m_source);
268 sourceChanged();
269 }
270}
271
272void QAndroidAudioDecoder::setSourceDevice(QIODevice *device)
273{
274 if (isDecoding())
275 return;
276
277 m_source.clear();
278 if (m_device != device) {
279 m_device = device;
280
281 if (!requestPermissions())
282 return;
283
284 sourceChanged();
285 }
286}
287
289{
290 if (isDecoding())
291 return;
292
293 m_position = -1;
294
295 if (m_device && (!m_device->isOpen() || !m_device->isReadable())) {
296 emit error(QAudioDecoder::ResourceError,
297 QString::fromUtf8("Unable to read from the specified device"));
298 return;
299 }
300
301 if (!m_threadDecoder) {
302 m_threadDecoder = new QThread(this);
303 m_decoder->moveToThread(m_threadDecoder);
304 m_threadDecoder->start();
305 }
306
307 decode();
308}
309
311{
312 if (!isDecoding() && m_position < 0 && m_duration < 0)
313 return;
314
315 m_decoder->stop();
316 m_audioBuffer.clear();
317 m_position = -1;
318 m_duration = -1;
319 setIsDecoding(false);
320
321 emit bufferAvailableChanged(false);
322 emit QPlatformAudioDecoder::positionChanged(m_position);
323}
324
326{
327 if (!m_audioBuffer.isEmpty()) {
328 std::pair<QAudioBuffer, int> buffer = m_audioBuffer.takeFirst();
329 m_position = buffer.second;
330 emit QPlatformAudioDecoder::positionChanged(buffer.second);
331 return buffer.first;
332 }
333
334 // no buffers available
335 return {};
336}
337
339{
340 return m_audioBuffer.size() > 0;
341}
342
344{
345 return m_position;
346}
347
349{
350 return m_duration;
351}
352
353void QAndroidAudioDecoder::positionChanged(QAudioBuffer audioBuffer, qint64 position)
354{
355 m_audioBuffer.append(std::pair<QAudioBuffer, int>(audioBuffer, position));
356 m_position = position;
357 emit bufferReady();
358}
359
360void QAndroidAudioDecoder::durationChanged(qint64 duration)
361{
362 m_duration = duration;
363 emit QPlatformAudioDecoder::durationChanged(duration);
364}
365
366void QAndroidAudioDecoder::error(const QAudioDecoder::Error err, const QString &errorString)
367{
368 stop();
369 emit QPlatformAudioDecoder::error(err, errorString);
370}
371
372void QAndroidAudioDecoder::finished()
373{
374 emit bufferAvailableChanged(m_audioBuffer.size() > 0);
375
376 if (m_duration != -1)
377 emit durationChanged(m_duration);
378
379 // remove temp file when decoding is finished
380 QFile(QString(QDir::tempPath()).append(QString::fromUtf8(tempFile))).remove();
381 emit QPlatformAudioDecoder::finished();
382}
383
384bool QAndroidAudioDecoder::requestPermissions()
385{
386 const auto writeRes = QtAndroidPrivate::requestPermission(QStringLiteral("android.permission.WRITE_EXTERNAL_STORAGE"));
387 if (writeRes.result() == QtAndroidPrivate::Authorized)
388 return true;
389
390 return false;
391}
392
393void QAndroidAudioDecoder::decode()
394{
395 if (m_device) {
396 connect(m_device, &QIODevice::readyRead, this, &QAndroidAudioDecoder::readDevice);
397 if (m_device->bytesAvailable())
398 readDevice();
399 } else {
400 QTimer::singleShot(0, m_decoder, &Decoder::doDecode);
401 }
402}
403
404bool QAndroidAudioDecoder::createTempFile()
405{
406 QFile file = QFile(QDir::tempPath().append(QString::fromUtf8(tempFile)), this);
407
408 bool success = file.open(QIODevice::QIODevice::ReadWrite);
409 if (!success)
410 emit error(QAudioDecoder::ResourceError, tr("Error opening temporary file: %1").arg(file.errorString()));
411
412 success &= (file.write(m_deviceBuffer) == m_deviceBuffer.size());
413 if (!success)
414 emit error(QAudioDecoder::ResourceError, tr("Error while writing data to temporary file"));
415
416 file.close();
417 m_deviceBuffer.clear();
418 if (success)
419 m_decoder->setSource(file.fileName());
420
421 return success;
422}
423
424void QAndroidAudioDecoder::readDevice() {
425 m_deviceBuffer.append(m_device->readAll());
426 if (m_device->atEnd()) {
427 disconnect(m_device, &QIODevice::readyRead, this, &QAndroidAudioDecoder::readDevice);
428 if (!createTempFile()) {
429 m_deviceBuffer.clear();
430 stop();
431 return;
432 }
433 QTimer::singleShot(0, m_decoder, &Decoder::doDecode);
434 }
435}
436
437QT_END_NAMESPACE
438
439#include "moc_qandroidaudiodecoder_p.cpp"
bool bufferAvailable() const override
QAudioBuffer read() override
qint64 duration() const override
qint64 position() const override
void setSourceDevice(QIODevice *device) override
void setSource(const QUrl &fileName) override
static QT_BEGIN_NAMESPACE const char tempFile[]
constexpr int dequeueTimeout
#define qCWarning(category,...)
#define Q_STATIC_LOGGING_CATEGORY(name,...)