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