Qt 6.x
The Qt SDK
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(),
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 int fd = -1;
76 if (source.path().contains(QLatin1String("content"))) {
77 fd = QJniObject::callStaticMethod<jint>("org/qtproject/qt/android/QtNative",
78 "openFdForContentUrl",
79 "(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;)I",
80 QNativeInterface::QAndroidApplication::context(),
81 QJniObject::fromString(source.path()).object(),
82 QJniObject::fromString(QLatin1String("r")).object());
83 } else {
84 fd = open(source.path().toStdString().c_str(), O_RDONLY);
85 }
86
87 if (fd < 0) {
88 emit error(QAudioDecoder::ResourceError, tr("Invalid fileDescriptor for source."));
89 return;
90 }
91 const int size = QFile(source.toString()).size();
92 media_status_t status = AMediaExtractor_setDataSourceFd(m_extractor, fd, 0,
93 size > 0 ? size : LONG_MAX);
94 close(fd);
95
96 if (status != AMEDIA_OK) {
97 if (m_extractor) {
98 AMediaExtractor_delete(m_extractor);
99 m_extractor = nullptr;
100 }
101 m_formatError = tr("Setting source for Audio Decoder failed.");
102 }
103}
104
105void Decoder::createDecoder()
106{
107 // get encoded format for decoder
108 m_format = AMediaExtractor_getTrackFormat(m_extractor, 0);
109
110 const char *mime;
111 if (!AMediaFormat_getString(m_format, AMEDIAFORMAT_KEY_MIME, &mime)) {
112 if (m_extractor) {
113 AMediaExtractor_delete(m_extractor);
114 m_extractor = nullptr;
115 }
116 emit error(QAudioDecoder::FormatError, tr("Format not supported by Audio Decoder."));
117
118 return;
119 }
120
121 // get audio duration from source
122 int64_t durationUs;
123 AMediaFormat_getInt64(m_format, AMEDIAFORMAT_KEY_DURATION, &durationUs);
124 emit durationChanged(durationUs / 1000);
125
126 // set default output audio format from input file
127 if (!m_outputFormat.isValid()) {
128 int32_t sampleRate;
129 AMediaFormat_getInt32(m_format, AMEDIAFORMAT_KEY_SAMPLE_RATE, &sampleRate);
130 m_outputFormat.setSampleRate(sampleRate);
131 int32_t channelCount;
132 AMediaFormat_getInt32(m_format, AMEDIAFORMAT_KEY_CHANNEL_COUNT, &channelCount);
133 m_outputFormat.setChannelCount(channelCount);
134 m_outputFormat.setSampleFormat(QAudioFormat::Int16);
135 }
136
137 m_codec = AMediaCodec_createDecoderByType(mime);
138}
139
141{
142 if (!m_formatError.isEmpty()) {
143 emit error(QAudioDecoder::FormatError, m_formatError);
144 return;
145 }
146
147 if (!m_extractor) {
148 emit error(QAudioDecoder::ResourceError, tr("Cannot decode, source not set."));
149 return;
150 }
151
152 createDecoder();
153
154 if (!m_codec) {
155 emit error(QAudioDecoder::ResourceError, tr("Audio Decoder could not be created."));
156 return;
157 }
158
159 media_status_t status = AMediaCodec_configure(m_codec, m_format, nullptr /* surface */,
160 nullptr /* crypto */, 0);
161
162 if (status != AMEDIA_OK) {
163 emit error(QAudioDecoder::ResourceError, tr("Audio Decoder failed configuration."));
164 return;
165 }
166
167 status = AMediaCodec_start(m_codec);
168 if (status != AMEDIA_OK) {
169 emit error(QAudioDecoder::ResourceError, tr("Audio Decoder failed to start."));
170 return;
171 }
172
173 AMediaExtractor_selectTrack(m_extractor, 0);
174
175 emit decodingChanged(true);
176 m_inputEOS = false;
177 while (!m_inputEOS) {
178 // handle input buffer
179 const ssize_t bufferIdx = AMediaCodec_dequeueInputBuffer(m_codec, dequeueTimeout);
180
181 if (bufferIdx >= 0) {
182 size_t bufferSize = {};
183 uint8_t *buffer = AMediaCodec_getInputBuffer(m_codec, bufferIdx, &bufferSize);
184 const int sample = AMediaExtractor_readSampleData(m_extractor, buffer, bufferSize);
185 if (sample < 0) {
186 m_inputEOS = true;
187 break;
188 }
189
190 const int64_t presentationTimeUs = AMediaExtractor_getSampleTime(m_extractor);
191 AMediaCodec_queueInputBuffer(m_codec, bufferIdx, 0, sample, presentationTimeUs,
192 m_inputEOS ? AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM : 0);
193 AMediaExtractor_advance(m_extractor);
194
195 // handle output buffer
196 AMediaCodecBufferInfo info;
197 ssize_t idx = AMediaCodec_dequeueOutputBuffer(m_codec, &info, dequeueTimeout);
198
199 while (idx == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED
200 || idx == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
201 if (idx == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED)
202 qCWarning(adLogger) << "dequeueOutputBuffer() status: outputFormat changed";
203
204 idx = AMediaCodec_dequeueOutputBuffer(m_codec, &info, dequeueTimeout);
205 }
206
207 if (idx >= 0) {
208 if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM)
209 break;
210
211 if (info.size > 0) {
212 size_t bufferSize;
213 const uint8_t *bufferData = AMediaCodec_getOutputBuffer(m_codec, idx,
214 &bufferSize);
215 const QByteArray data((const char*)(bufferData + info.offset), info.size);
216 auto audioBuffer = QAudioBuffer(data, m_outputFormat, presentationTimeUs);
217 if (presentationTimeUs >= 0)
218 emit positionChanged(std::move(audioBuffer), presentationTimeUs / 1000);
219
220 AMediaCodec_releaseOutputBuffer(m_codec, idx, false);
221 }
222 } else if (idx == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
223 qCWarning(adLogger) << "dequeueOutputBuffer() status: try again later";
224 break;
225 } else {
226 qCWarning(adLogger) <<
227 "AMediaCodec_dequeueOutputBuffer() status: invalid buffer idx " << idx;
228 }
229 } else {
230 qCWarning(adLogger) << "dequeueInputBuffer() status: invalid buffer idx " << bufferIdx;
231 }
232 }
233 emit finished();
234}
235
238 m_decoder(new Decoder())
239{
240 connect(m_decoder, &Decoder::positionChanged, this, &QAndroidAudioDecoder::positionChanged);
241 connect(m_decoder, &Decoder::durationChanged, this, &QAndroidAudioDecoder::durationChanged);
242 connect(m_decoder, &Decoder::error, this, &QAndroidAudioDecoder::error);
243 connect(m_decoder, &Decoder::finished, this, &QAndroidAudioDecoder::finished);
246}
247
249{
250 m_decoder->thread()->quit();
251 m_decoder->thread()->wait();
252 delete m_threadDecoder;
253 delete m_decoder;
254}
255
257{
258 if (!requestPermissions())
259 return;
260
261 if (isDecoding())
262 return;
263
264 m_device = nullptr;
266
267 if (m_source != fileName) {
268 m_source = fileName;
269 emit setSourceUrl(m_source);
271 }
272}
273
275{
276 if (isDecoding())
277 return;
278
279 m_source.clear();
280 if (m_device != device) {
281 m_device = device;
282
283 if (!requestPermissions())
284 return;
285
287 }
288}
289
291{
292 if (isDecoding())
293 return;
294
295 m_position = -1;
296
297 if (m_device && (!m_device->isOpen() || !m_device->isReadable())) {
299 QString::fromUtf8("Unable to read from the specified device"));
300 return;
301 }
302
303 if (!m_threadDecoder) {
304 m_threadDecoder = new QThread(this);
305 m_decoder->moveToThread(m_threadDecoder);
306 m_threadDecoder->start();
307 }
308
309 decode();
310}
311
313{
314 if (!isDecoding() && m_position < 0 && m_duration < 0)
315 return;
316
317 m_decoder->stop();
318 m_audioBuffer.clear();
319 m_position = -1;
320 m_duration = -1;
321 setIsDecoding(false);
322
325}
326
328{
329 if (!m_audioBuffer.isEmpty()) {
330 QPair<QAudioBuffer, int> buffer = m_audioBuffer.takeFirst();
331 m_position = buffer.second;
333 return buffer.first;
334 }
335
336 // no buffers available
337 return {};
338}
339
341{
342 return m_audioBuffer.size() > 0;
343}
344
346{
347 return m_position;
348}
349
351{
352 return m_duration;
353}
354
355void QAndroidAudioDecoder::positionChanged(QAudioBuffer audioBuffer, qint64 position)
356{
357 m_audioBuffer.append(QPair<QAudioBuffer, int>(audioBuffer, position));
358 m_position = position;
360}
361
362void QAndroidAudioDecoder::durationChanged(qint64 duration)
363{
364 m_duration = duration;
366}
367
368void QAndroidAudioDecoder::error(const QAudioDecoder::Error err, const QString &errorString)
369{
370 stop();
372}
373
374void QAndroidAudioDecoder::finished()
375{
376 emit bufferAvailableChanged(m_audioBuffer.size() > 0);
377
378 if (m_duration != -1)
379 emit durationChanged(m_duration);
380
381 // remove temp file when decoding is finished
384}
385
386bool QAndroidAudioDecoder::requestPermissions()
387{
388 const auto writeRes = QtAndroidPrivate::requestPermission(QStringLiteral("android.permission.WRITE_EXTERNAL_STORAGE"));
389 if (writeRes.result() == QtAndroidPrivate::Authorized)
390 return true;
391
392 return false;
393}
394
395void QAndroidAudioDecoder::decode()
396{
397 if (m_device) {
398 connect(m_device, &QIODevice::readyRead, this, &QAndroidAudioDecoder::readDevice);
399 if (m_device->bytesAvailable())
400 readDevice();
401 } else {
403 }
404}
405
406bool QAndroidAudioDecoder::createTempFile()
407{
409
410 bool success = file.open(QIODevice::QIODevice::ReadWrite);
411 if (!success)
412 emit error(QAudioDecoder::ResourceError, tr("Error opening temporary file: %1").arg(file.errorString()));
413
414 success &= (file.write(m_deviceBuffer) == m_deviceBuffer.size());
415 if (!success)
416 emit error(QAudioDecoder::ResourceError, tr("Error while writing data to temporary file"));
417
418 file.close();
419 m_deviceBuffer.clear();
420 if (success)
421 m_decoder->setSource(file.fileName());
422
423 return success;
424}
425
426void QAndroidAudioDecoder::readDevice() {
427 m_deviceBuffer.append(m_device->readAll());
428 if (m_device->atEnd()) {
429 disconnect(m_device, &QIODevice::readyRead, this, &QAndroidAudioDecoder::readDevice);
430 if (!createTempFile()) {
431 m_deviceBuffer.clear();
432 stop();
433 return;
434 }
436 }
437}
438
440
441#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:474
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:2130
void close() override
Calls QFileDevice::flush() and closes the file.
qint64 size() const
Returns the file size in bytes.
\inmodule QtCore
Definition qfile.h:93
bool open(OpenMode flags) override
Opens the file using OpenMode mode, returning true if successful; otherwise false.
Definition qfile.cpp:881
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:1156
\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:386
bool isEmpty() const noexcept
Definition qlist.h:390
value_type takeFirst()
Definition qlist.h:549
void append(parameter_type t)
Definition qlist.h:441
void clear()
Definition qlist.h:417
void moveToThread(QThread *thread)
Changes the thread affinity for this object and its children.
Definition qobject.cpp:1606
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2823
QThread * thread() const
Returns the thread in which the object lives.
Definition qobject.cpp:1561
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:127
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5857
bool isEmpty() const
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:1083
void start(Priority=InheritPriority)
Definition qthread.cpp:923
bool wait(QDeadlineTimer deadline=QDeadlineTimer(QDeadlineTimer::Forever))
Definition qthread.cpp:950
void quit()
Definition qthread.cpp:935
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:1901
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
std::pair< T1, T2 > QPair
#define Q_LOGGING_CATEGORY(name,...)
#define qCWarning(category,...)
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLenum GLuint buffer
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
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:55
QFile file
[0]
file open(QIODevice::ReadOnly)
QFileInfo info(fileName)
[8]
application x qt windows mime
[2]
myObject disconnect()
[26]
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent