Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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;
22static Q_LOGGING_CATEGORY(adLogger, "QAndroidAudioDecoder")
23
25 : m_format(AMediaFormat_new())
26{}
27
29{
30 if (m_codec) {
31 AMediaCodec_delete(m_codec);
32 m_codec = nullptr;
33 }
34
35 if (m_extractor) {
36 AMediaExtractor_delete(m_extractor);
37 m_extractor = nullptr;
38 }
39
40 if (m_format) {
41 AMediaFormat_delete(m_format);
42 m_format = nullptr;
43 }
44}
45
47{
48 if (!m_codec)
49 return;
50
51 const media_status_t err = AMediaCodec_stop(m_codec);
52 if (err != AMEDIA_OK)
53 qCWarning(adLogger) << "stop() error: " << err;
54}
55
57{
58 const QJniObject path = QJniObject::callStaticObjectMethod(
59 "org/qtproject/qt/android/multimedia/QtMultimediaUtils",
60 "getMimeType",
61 "(Landroid/content/Context;Ljava/lang/String;)Ljava/lang/String;",
62 QNativeInterface::QAndroidApplication::context().object(),
63 QJniObject::fromString(source.path()).object());
64
65 const QString mime = path.isValid() ? path.toString() : "";
66
67 if (!mime.isEmpty() && !mime.contains("audio", Qt::CaseInsensitive)) {
68 m_formatError = tr("Cannot set source, invalid mime type for the source provided.");
69 return;
70 }
71
72 if (!m_extractor)
73 m_extractor = AMediaExtractor_new();
74
75 QFile file(source.path());
77 emit error(QAudioDecoder::ResourceError, tr("Cannot open the file"));
78 return;
79 }
80
81 const int fd = file.handle();
82
83 if (fd < 0) {
84 emit error(QAudioDecoder::ResourceError, tr("Invalid fileDescriptor for source."));
85 return;
86 }
87 const int size = file.size();
88 media_status_t status = AMediaExtractor_setDataSourceFd(m_extractor, fd, 0,
89 size > 0 ? size : LONG_MAX);
90 close(fd);
91
92 if (status != AMEDIA_OK) {
93 if (m_extractor) {
94 AMediaExtractor_delete(m_extractor);
95 m_extractor = nullptr;
96 }
97 m_formatError = tr("Setting source for Audio Decoder failed.");
98 }
99}
100
101void Decoder::createDecoder()
102{
103 // get encoded format for decoder
104 m_format = AMediaExtractor_getTrackFormat(m_extractor, 0);
105
106 const char *mime;
107 if (!AMediaFormat_getString(m_format, AMEDIAFORMAT_KEY_MIME, &mime)) {
108 if (m_extractor) {
109 AMediaExtractor_delete(m_extractor);
110 m_extractor = nullptr;
111 }
112 emit error(QAudioDecoder::FormatError, tr("Format not supported by Audio Decoder."));
113
114 return;
115 }
116
117 // get audio duration from source
118 int64_t durationUs;
119 AMediaFormat_getInt64(m_format, AMEDIAFORMAT_KEY_DURATION, &durationUs);
120 emit durationChanged(durationUs / 1000);
121
122 // set default output audio format from input file
123 if (!m_outputFormat.isValid()) {
124 int32_t sampleRate;
125 AMediaFormat_getInt32(m_format, AMEDIAFORMAT_KEY_SAMPLE_RATE, &sampleRate);
126 m_outputFormat.setSampleRate(sampleRate);
127 int32_t channelCount;
128 AMediaFormat_getInt32(m_format, AMEDIAFORMAT_KEY_CHANNEL_COUNT, &channelCount);
129 m_outputFormat.setChannelCount(channelCount);
130 m_outputFormat.setSampleFormat(QAudioFormat::Int16);
131 }
132
133 m_codec = AMediaCodec_createDecoderByType(mime);
134}
135
137{
138 if (!m_formatError.isEmpty()) {
139 emit error(QAudioDecoder::FormatError, m_formatError);
140 return;
141 }
142
143 if (!m_extractor) {
144 emit error(QAudioDecoder::ResourceError, tr("Cannot decode, source not set."));
145 return;
146 }
147
148 createDecoder();
149
150 if (!m_codec) {
151 emit error(QAudioDecoder::ResourceError, tr("Audio Decoder could not be created."));
152 return;
153 }
154
155 media_status_t status = AMediaCodec_configure(m_codec, m_format, nullptr /* surface */,
156 nullptr /* crypto */, 0);
157
158 if (status != AMEDIA_OK) {
159 emit error(QAudioDecoder::ResourceError, tr("Audio Decoder failed configuration."));
160 return;
161 }
162
163 status = AMediaCodec_start(m_codec);
164 if (status != AMEDIA_OK) {
165 emit error(QAudioDecoder::ResourceError, tr("Audio Decoder failed to start."));
166 return;
167 }
168
169 AMediaExtractor_selectTrack(m_extractor, 0);
170
171 emit decodingChanged(true);
172 m_inputEOS = false;
173 while (!m_inputEOS) {
174 // handle input buffer
175 const ssize_t bufferIdx = AMediaCodec_dequeueInputBuffer(m_codec, dequeueTimeout);
176
177 if (bufferIdx >= 0) {
178 size_t bufferSize = {};
179 uint8_t *buffer = AMediaCodec_getInputBuffer(m_codec, bufferIdx, &bufferSize);
180 const int sample = AMediaExtractor_readSampleData(m_extractor, buffer, bufferSize);
181 if (sample < 0) {
182 m_inputEOS = true;
183 break;
184 }
185
186 const int64_t presentationTimeUs = AMediaExtractor_getSampleTime(m_extractor);
187 AMediaCodec_queueInputBuffer(m_codec, bufferIdx, 0, sample, presentationTimeUs,
188 m_inputEOS ? AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM : 0);
189 AMediaExtractor_advance(m_extractor);
190
191 // handle output buffer
192 AMediaCodecBufferInfo info;
193 ssize_t idx = AMediaCodec_dequeueOutputBuffer(m_codec, &info, dequeueTimeout);
194
195 while (idx == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED
196 || idx == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
197 if (idx == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED)
198 qCWarning(adLogger) << "dequeueOutputBuffer() status: outputFormat changed";
199
200 idx = AMediaCodec_dequeueOutputBuffer(m_codec, &info, dequeueTimeout);
201 }
202
203 if (idx >= 0) {
204 if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM)
205 break;
206
207 if (info.size > 0) {
208 size_t bufferSize;
209 const uint8_t *bufferData = AMediaCodec_getOutputBuffer(m_codec, idx,
210 &bufferSize);
211 const QByteArray data((const char*)(bufferData + info.offset), info.size);
212 auto audioBuffer = QAudioBuffer(data, m_outputFormat, presentationTimeUs);
213 if (presentationTimeUs >= 0)
214 emit positionChanged(std::move(audioBuffer), presentationTimeUs / 1000);
215
216 AMediaCodec_releaseOutputBuffer(m_codec, idx, false);
217 }
218 } else if (idx == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
219 qCWarning(adLogger) << "dequeueOutputBuffer() status: try again later";
220 break;
221 } else {
222 qCWarning(adLogger) <<
223 "AMediaCodec_dequeueOutputBuffer() status: invalid buffer idx " << idx;
224 }
225 } else {
226 qCWarning(adLogger) << "dequeueInputBuffer() status: invalid buffer idx " << bufferIdx;
227 }
228 }
229 emit finished();
230}
231
233 : QPlatformAudioDecoder(parent),
234 m_decoder(new Decoder())
235{
236 connect(m_decoder, &Decoder::positionChanged, this, &QAndroidAudioDecoder::positionChanged);
237 connect(m_decoder, &Decoder::durationChanged, this, &QAndroidAudioDecoder::durationChanged);
239 connect(m_decoder, &Decoder::finished, this, &QAndroidAudioDecoder::finished);
242}
243
245{
246 m_decoder->thread()->quit();
247 m_decoder->thread()->wait();
248 delete m_threadDecoder;
249 delete m_decoder;
250}
251
253{
254 if (!requestPermissions())
255 return;
256
257 if (isDecoding())
258 return;
259
260 m_device = nullptr;
262
263 if (m_source != fileName) {
264 m_source = fileName;
265 emit setSourceUrl(m_source);
267 }
268}
269
271{
272 if (isDecoding())
273 return;
274
275 m_source.clear();
276 if (m_device != device) {
277 m_device = device;
278
279 if (!requestPermissions())
280 return;
281
283 }
284}
285
287{
288 if (isDecoding())
289 return;
290
291 m_position = -1;
292
293 if (m_device && (!m_device->isOpen() || !m_device->isReadable())) {
295 QString::fromUtf8("Unable to read from the specified device"));
296 return;
297 }
298
299 if (!m_threadDecoder) {
300 m_threadDecoder = new QThread(this);
301 m_decoder->moveToThread(m_threadDecoder);
302 m_threadDecoder->start();
303 }
304
305 decode();
306}
307
309{
310 if (!isDecoding() && m_position < 0 && m_duration < 0)
311 return;
312
313 m_decoder->stop();
314 m_audioBuffer.clear();
315 m_position = -1;
316 m_duration = -1;
317 setIsDecoding(false);
318
321}
322
324{
325 if (!m_audioBuffer.isEmpty()) {
326 QPair<QAudioBuffer, int> buffer = m_audioBuffer.takeFirst();
327 m_position = buffer.second;
329 return buffer.first;
330 }
331
332 // no buffers available
333 return {};
334}
335
337{
338 return m_audioBuffer.size() > 0;
339}
340
342{
343 return m_position;
344}
345
347{
348 return m_duration;
349}
350
351void QAndroidAudioDecoder::positionChanged(QAudioBuffer audioBuffer, qint64 position)
352{
353 m_audioBuffer.append(QPair<QAudioBuffer, int>(audioBuffer, position));
354 m_position = position;
356}
357
358void QAndroidAudioDecoder::durationChanged(qint64 duration)
359{
360 m_duration = duration;
362}
363
364void QAndroidAudioDecoder::error(const QAudioDecoder::Error err, const QString &errorString)
365{
366 stop();
368}
369
370void QAndroidAudioDecoder::finished()
371{
372 emit bufferAvailableChanged(m_audioBuffer.size() > 0);
373
374 if (m_duration != -1)
375 emit durationChanged(m_duration);
376
377 // remove temp file when decoding is finished
380}
381
382bool QAndroidAudioDecoder::requestPermissions()
383{
384 const auto writeRes = QtAndroidPrivate::requestPermission(QStringLiteral("android.permission.WRITE_EXTERNAL_STORAGE"));
385 if (writeRes.result() == QtAndroidPrivate::Authorized)
386 return true;
387
388 return false;
389}
390
391void QAndroidAudioDecoder::decode()
392{
393 if (m_device) {
394 connect(m_device, &QIODevice::readyRead, this, &QAndroidAudioDecoder::readDevice);
395 if (m_device->bytesAvailable())
396 readDevice();
397 } else {
399 }
400}
401
402bool QAndroidAudioDecoder::createTempFile()
403{
405
406 bool success = file.open(QIODevice::QIODevice::ReadWrite);
407 if (!success)
408 emit error(QAudioDecoder::ResourceError, tr("Error opening temporary file: %1").arg(file.errorString()));
409
410 success &= (file.write(m_deviceBuffer) == m_deviceBuffer.size());
411 if (!success)
412 emit error(QAudioDecoder::ResourceError, tr("Error while writing data to temporary file"));
413
414 file.close();
415 m_deviceBuffer.clear();
416 if (success)
417 m_decoder->setSource(file.fileName());
418
419 return success;
420}
421
422void QAndroidAudioDecoder::readDevice() {
423 m_deviceBuffer.append(m_device->readAll());
424 if (m_device->atEnd()) {
425 disconnect(m_device, &QIODevice::readyRead, this, &QAndroidAudioDecoder::readDevice);
426 if (!createTempFile()) {
427 m_deviceBuffer.clear();
428 stop();
429 return;
430 }
432 }
433}
434
436
437#include "moc_qandroidaudiodecoder_p.cpp"
IOBluetoothDevice * device
void error(const QAudioDecoder::Error error, const QString &errorString)
void decodingChanged(bool decoding)
void finished()
void positionChanged(const QAudioBuffer &buffer, qint64 position)
void setSource(const QUrl &source)
void durationChanged(const qint64 duration)
bool bufferAvailable() const override
void setSourceUrl(const QUrl &source)
QAudioBuffer read() override
qint64 duration() const override
qint64 position() const override
void setSourceDevice(QIODevice *device) override
void setSource(const QUrl &fileName) override
QAndroidAudioDecoder(QAudioDecoder *parent)
\inmodule QtMultimedia
The QAudioDecoder class implements decoding audio.
Error
Defines a media player error condition.
constexpr void setSampleRate(int sampleRate) noexcept
Sets the sample rate to samplerate in Hertz.
constexpr bool isValid() const noexcept
Returns true if all of the parameters are valid.
constexpr void setSampleFormat(SampleFormat f) noexcept
Sets the sample format to format.
constexpr void setChannelCount(int channelCount) noexcept
Sets the channel count to channels.
\inmodule QtCore
Definition qbytearray.h:57
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
Definition qbytearray.h:494
void clear()
Clears the contents of the byte array and makes it null.
QByteArray & append(char c)
This is an overloaded member function, provided for convenience. It differs from the above function o...
static QString tempPath()
Returns the absolute canonical path of the system's temporary directory.
Definition qdir.cpp:2133
int handle() const
Returns the file handle of the file.
void close() override
Calls QFileDevice::flush() and closes the file.
\inmodule QtCore
Definition qfile.h:93
QFILE_MAYBE_NODISCARD bool open(OpenMode flags) override
Opens the file using OpenMode mode, returning true if successful; otherwise false.
Definition qfile.cpp:904
bool remove()
Removes the file specified by fileName().
Definition qfile.cpp:419
QString fileName() const override
Returns the name set by setFileName() or to the QFile constructors.
Definition qfile.cpp:277
qint64 size() const override
\reimp
Definition qfile.cpp:1179
\inmodule QtCore \reentrant
Definition qiodevice.h:34
void readyRead()
This signal is emitted once every time new data is available for reading from the device's current re...
bool isOpen() const
Returns true if the device is open; otherwise returns false.
QByteArray readAll()
Reads all remaining data from the device, and returns it as a byte array.
bool isReadable() const
Returns true if data can be read from the device; otherwise returns false.
qint64 write(const char *data, qint64 len)
Writes at most maxSize bytes of data from data to the device.
virtual qint64 bytesAvailable() const
Returns the number of bytes that are available for reading.
QString errorString() const
Returns a human-readable description of the last device error that occurred.
virtual bool atEnd() const
Returns true if the current read and write position is at the end of the device (i....
\inmodule QtCore
qsizetype size() const noexcept
Definition qlist.h:397
bool isEmpty() const noexcept
Definition qlist.h:401
value_type takeFirst()
Definition qlist.h:566
void append(parameter_type t)
Definition qlist.h:458
void clear()
Definition qlist.h:434
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
QThread * thread() const
Returns the thread in which the object lives.
Definition qobject.cpp:1598
bool moveToThread(QThread *thread QT6_DECL_NEW_OVERLOAD_TAIL)
Changes the thread affinity for this object and its children and returns true on success.
Definition qobject.cpp:1643
void durationChanged(qint64 duration)
void positionChanged(qint64 position)
void bufferAvailableChanged(bool available)
QAudioDecoder::Error error() const
void setIsDecoding(bool running=true)
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
bool isEmpty() const noexcept
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:192
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:6018
void start(Priority=InheritPriority)
Definition qthread.cpp:996
bool wait(QDeadlineTimer deadline=QDeadlineTimer(QDeadlineTimer::Forever))
Definition qthread.cpp:1023
void quit()
Definition qthread.cpp:1008
bool singleShot
whether the timer is a single-shot timer
Definition qtimer.h:22
\inmodule QtCore
Definition qurl.h:94
void clear()
Resets the content of the QUrl.
Definition qurl.cpp:1909
list append(new Employee("Blackpool", "Stephen"))
Combined button and popup list for selecting options.
@ CaseInsensitive
static QT_BEGIN_NAMESPACE const char tempFile[]
constexpr int dequeueTimeout
DBusConnection const char DBusError * error
#define Q_LOGGING_CATEGORY(name,...)
#define qCWarning(category,...)
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum GLuint buffer
GLuint64 GLenum GLint fd
GLsizei GLsizei GLchar * source
GLsizei const GLchar *const * path
static qreal position(const QQuickItem *item, QQuickAnchors::Anchor anchorLine)
SSL_CTX int void * arg
#define QStringLiteral(str)
#define tr(X)
#define emit
long long qint64
Definition qtypes.h:60
QFile file
[0]
application x qt windows mime
[2]
myObject disconnect()
[26]
QHostInfo info
[0]