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>
26using namespace Qt::Literals;
27using namespace std::chrono_literals;
30 : m_format(AMediaFormat_new())
36 AMediaCodec_delete(m_codec);
41 AMediaExtractor_delete(m_extractor);
42 m_extractor =
nullptr;
46 AMediaFormat_delete(m_format);
56 const media_status_t err = AMediaCodec_stop(m_codec);
58 qCWarning(adLogger) <<
"stop() error: " << err;
61void Decoder::setSource(
const QUrl &source)
63 const QJniObject path = QJniObject::callStaticObjectMethod(
64 "org/qtproject/qt/android/multimedia/QtMultimediaUtils",
66 "(Landroid/content/Context;Ljava/lang/String;)Ljava/lang/String;",
67 QNativeInterface::QAndroidApplication::context().object(),
68 QJniObject::fromString(source.path()).object());
70 const QString mime = path.isValid() ? path.toString() : u""_s;
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.");
78 m_extractor = AMediaExtractor_new();
80 QFile file(source.path());
81 if (!file.open(QFile::ReadOnly)) {
82 emit error(QAudioDecoder::ResourceError, tr(
"Cannot open the file"));
86 const int fd = file.handle();
89 emit error(QAudioDecoder::ResourceError, tr(
"Invalid fileDescriptor for source."));
92 const int size = file.size();
93 media_status_t status = AMediaExtractor_setDataSourceFd(m_extractor, fd, 0,
94 size > 0 ? size : LONG_MAX);
97 if (status != AMEDIA_OK) {
99 AMediaExtractor_delete(m_extractor);
100 m_extractor =
nullptr;
102 m_formatError = tr(
"Setting source for Audio Decoder failed.");
106void Decoder::createDecoder()
108 using namespace std::chrono;
111 m_format = AMediaExtractor_getTrackFormat(m_extractor, 0);
114 if (!AMediaFormat_getString(m_format, AMEDIAFORMAT_KEY_MIME, &mime)) {
116 AMediaExtractor_delete(m_extractor);
117 m_extractor =
nullptr;
119 emit error(QAudioDecoder::FormatError, tr(
"Format not supported by Audio Decoder."));
126 AMediaFormat_getInt64(m_format, AMEDIAFORMAT_KEY_DURATION, &durationUs);
127 emit durationChanged(round<milliseconds>(microseconds{ durationUs }));
130 if (!m_outputFormat.isValid()) {
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);
140 m_codec = AMediaCodec_createDecoderByType(mime);
143void Decoder::doDecode()
145 using namespace std::chrono;
146 if (!m_formatError.isEmpty()) {
147 emit error(QAudioDecoder::FormatError, m_formatError);
152 emit error(QAudioDecoder::ResourceError, tr(
"Cannot decode, source not set."));
159 emit error(QAudioDecoder::ResourceError, tr(
"Audio Decoder could not be created."));
163 media_status_t status = AMediaCodec_configure(m_codec, m_format,
nullptr ,
166 if (status != AMEDIA_OK) {
167 emit error(QAudioDecoder::ResourceError, tr(
"Audio Decoder failed configuration."));
171 status = AMediaCodec_start(m_codec);
172 if (status != AMEDIA_OK) {
173 emit error(QAudioDecoder::ResourceError, tr(
"Audio Decoder failed to start."));
177 AMediaExtractor_selectTrack(m_extractor, 0);
179 emit decodingChanged(
true);
181 while (!m_inputEOS) {
183 const ssize_t bufferIdx = AMediaCodec_dequeueInputBuffer(m_codec,
dequeueTimeout);
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);
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);
200 AMediaCodecBufferInfo info;
201 ssize_t idx = AMediaCodec_dequeueOutputBuffer(m_codec, &info,
dequeueTimeout);
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";
208 idx = AMediaCodec_dequeueOutputBuffer(m_codec, &info,
dequeueTimeout);
212 if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM)
217 const uint8_t *bufferData = AMediaCodec_getOutputBuffer(m_codec, idx,
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 }));
225 AMediaCodec_releaseOutputBuffer(m_codec, idx,
false);
227 }
else if (idx == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
228 qCWarning(adLogger) <<
"dequeueOutputBuffer() status: try again later";
232 "AMediaCodec_dequeueOutputBuffer() status: invalid buffer idx " << idx;
235 qCWarning(adLogger) <<
"dequeueInputBuffer() status: invalid buffer idx " << bufferIdx;
242 : QPlatformAudioDecoder(parent),
243 m_decoder(
new Decoder())
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);
249 connect(m_decoder, &Decoder::decodingChanged,
this, &QPlatformAudioDecoder::setIsDecoding);
250 connect(
this, &QAndroidAudioDecoder::setSourceUrl, m_decoder, & Decoder::setSource);
255 m_decoder->thread()->quit();
256 m_decoder->thread()->wait();
257 delete m_threadDecoder;
263 if (!requestPermissions())
270 error(QAudioDecoder::NoError, QStringLiteral(
""));
272 if (m_source != fileName) {
274 emit setSourceUrl(m_source);
285 if (m_device != device) {
288 if (!requestPermissions())
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"));
306 if (!m_threadDecoder) {
307 m_threadDecoder =
new QThread(
this);
308 m_decoder->moveToThread(m_threadDecoder);
309 m_threadDecoder->start();
317 if (!isDecoding() && position() < 0ms && duration() < 0ms)
321 m_audioBuffer.clear();
322 setIsDecoding(
false);
323 QPlatformAudioDecoder::positionChanged(kInvalidPosition);
324 durationChanged(kInvalidDuration);
326 emit bufferAvailableChanged(
false);
331 if (!m_audioBuffer.isEmpty()) {
332 auto buffer = m_audioBuffer.takeFirst();
333 QPlatformAudioDecoder::positionChanged(buffer.second);
342 return m_audioBuffer.size() > 0;
347 m_audioBuffer.append({ audioBuffer, position });
348 QPlatformAudioDecoder::positionChanged(position);
354 QPlatformAudioDecoder::durationChanged(duration);
360 emit QPlatformAudioDecoder::error(err, errorString);
365 emit bufferAvailableChanged(m_audioBuffer.size() > 0);
367 if (QPlatformAudioDecoder::duration() >= 0ms)
368 QPlatformAudioDecoder::durationChanged(QPlatformAudioDecoder::duration());
371 QFile(QString(QDir::tempPath()).append(QString::fromUtf8(tempFile))).remove();
372 emit QPlatformAudioDecoder::finished();
377 const auto writeRes = QtAndroidPrivate::requestPermission(QStringLiteral(
"android.permission.WRITE_EXTERNAL_STORAGE"));
378 if (writeRes.result() == QtAndroidPrivate::Authorized)
388 if (m_device->bytesAvailable())
391 QTimer::singleShot(0, m_decoder, &Decoder::doDecode);
397 QFile file = QFile(QDir::tempPath().append(QString::fromUtf8(tempFile)),
this);
399 bool success = file.open(QIODevice::QIODevice::ReadWrite);
401 emit error(QAudioDecoder::ResourceError, tr(
"Error opening temporary file: %1").arg(file.errorString()));
403 success &= (file.write(m_deviceBuffer) == m_deviceBuffer.size());
405 emit error(QAudioDecoder::ResourceError, tr(
"Error while writing data to temporary file"));
408 m_deviceBuffer.clear();
410 m_decoder->setSource(QUrl::fromLocalFile(file.fileName()));
416 m_deviceBuffer.append(m_device->readAll());
417 if (m_device->atEnd()) {
419 if (!createTempFile()) {
420 m_deviceBuffer.clear();
424 QTimer::singleShot(0, m_decoder, &Decoder::doDecode);
430#include "moc_qandroidaudiodecoder_p.cpp"
bool bufferAvailable() const override
virtual ~QAndroidAudioDecoder()
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,...)