From 3bd9da9aba63b51114a3602313e51085954be194 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Str=C3=B8mme?= Date: Tue, 17 Nov 2015 16:05:09 +0100 Subject: Android: Improve detection of optimal settings for QAudioOutput. This change will try to detect the optimal buffer size and sample-rate on Android (requires API level 17+). If the device supports low-latency playback, then it's recommended that application developers check the preferred sample-rate through QAudioDeviceInfo::preferredFormat(). On most devices the preferred sample rate seems to be 48 KHz, so this is now the default instead of 44.1 KHz. Note that not all devices supports "proper" low-latency playback, and there are no APIs to retrieve information about how many devices that can be active at the same time; The only remotely quantitative value I've found is "a few"... Change-Id: I0708738b4a31f6bf9e88e9a816679cb688e023f3 Reviewed-by: Yoann Lopes --- src/plugins/opensles/opensles.pro | 2 +- src/plugins/opensles/qopenslesaudiooutput.cpp | 31 ++++-- src/plugins/opensles/qopenslesdeviceinfo.cpp | 2 +- src/plugins/opensles/qopenslesengine.cpp | 150 ++++++++++++++++++++++++++ src/plugins/opensles/qopenslesengine.h | 8 ++ 5 files changed, 184 insertions(+), 9 deletions(-) (limited to 'src/plugins') diff --git a/src/plugins/opensles/opensles.pro b/src/plugins/opensles/opensles.pro index 53c5a120b..aa8e05444 100644 --- a/src/plugins/opensles/opensles.pro +++ b/src/plugins/opensles/opensles.pro @@ -1,5 +1,5 @@ TARGET = qtaudio_opensles -QT += multimedia-private +QT += multimedia-private core-private PLUGIN_TYPE = audio PLUGIN_CLASS_NAME = QOpenSLESPlugin diff --git a/src/plugins/opensles/qopenslesaudiooutput.cpp b/src/plugins/opensles/qopenslesaudiooutput.cpp index d17363d20..3af4e8bb2 100644 --- a/src/plugins/opensles/qopenslesaudiooutput.cpp +++ b/src/plugins/opensles/qopenslesaudiooutput.cpp @@ -42,13 +42,23 @@ #endif // ANDROID #define BUFFER_COUNT 2 -#define DEFAULT_PERIOD_TIME_MS 50 -#define MINIMUM_PERIOD_TIME_MS 5 #define EBASE 2.302585093 #define LOG10(x) qLn(x)/qreal(EBASE) QT_BEGIN_NAMESPACE +static inline void openSlDebugInfo() +{ + const QAudioFormat &format = QAudioDeviceInfo::defaultOutputDevice().preferredFormat(); + qDebug() << "======= OpenSL ES Device info =======" + << "\nSupports low-latency playback: " << (QOpenSLESEngine::supportsLowLatency() ? "YES" : "NO") + << "\nPreferred sample rate: " << QOpenSLESEngine::getOutputValue(QOpenSLESEngine::SampleRate, -1) + << "\nFrames per buffer: " << QOpenSLESEngine::getOutputValue(QOpenSLESEngine::FramesPerBuffer, -1) + << "\nPreferred Format: " << format + << "\nLow-latency buffer size: " << QOpenSLESEngine::getLowLatencyBufferSize(format) + << "\nDefault buffer size: " << QOpenSLESEngine::getDefaultBufferSize(format); +} + QMap QOpenSLESAudioOutput::m_categories; QOpenSLESAudioOutput::QOpenSLESAudioOutput(const QByteArray &device) @@ -531,13 +541,17 @@ bool QOpenSLESAudioOutput::preparePlayer() setVolume(m_volume); + const int lowLatencyBufferSize = QOpenSLESEngine::getLowLatencyBufferSize(m_format); + const int defaultBufferSize = QOpenSLESEngine::getDefaultBufferSize(m_format); + // Buffer size if (m_bufferSize <= 0) { - m_bufferSize = m_format.bytesForDuration(DEFAULT_PERIOD_TIME_MS * 1000); - } else { - const int minimumBufSize = m_format.bytesForDuration(MINIMUM_PERIOD_TIME_MS * 1000); - if (m_bufferSize < minimumBufSize) - m_bufferSize = minimumBufSize; + m_bufferSize = defaultBufferSize; + } else if (QOpenSLESEngine::supportsLowLatency()) { + if (m_bufferSize < lowLatencyBufferSize) + m_bufferSize = lowLatencyBufferSize; + } else if (m_bufferSize < defaultBufferSize) { + m_bufferSize = defaultBufferSize; } m_periodSize = m_bufferSize; @@ -598,6 +612,9 @@ void QOpenSLESAudioOutput::stopPlayer() void QOpenSLESAudioOutput::startPlayer() { + if (QOpenSLESEngine::printDebugInfo()) + openSlDebugInfo(); + if (SL_RESULT_SUCCESS != (*m_playItf)->SetPlayState(m_playItf, SL_PLAYSTATE_PLAYING)) { setError(QAudio::FatalError); destroyPlayer(); diff --git a/src/plugins/opensles/qopenslesdeviceinfo.cpp b/src/plugins/opensles/qopenslesdeviceinfo.cpp index c93a66e1e..0dd8183ef 100644 --- a/src/plugins/opensles/qopenslesdeviceinfo.cpp +++ b/src/plugins/opensles/qopenslesdeviceinfo.cpp @@ -61,7 +61,7 @@ QAudioFormat QOpenSLESDeviceInfo::preferredFormat() const format.setCodec(QStringLiteral("audio/pcm")); format.setSampleSize(16); format.setSampleType(QAudioFormat::SignedInt); - format.setSampleRate(44100); + format.setSampleRate(QOpenSLESEngine::getOutputValue(QOpenSLESEngine::SampleRate, 48000)); format.setChannelCount(m_mode == QAudio::AudioInput ? 1 : 2); return format; } diff --git a/src/plugins/opensles/qopenslesengine.cpp b/src/plugins/opensles/qopenslesengine.cpp index 7689533e6..a30c90d84 100644 --- a/src/plugins/opensles/qopenslesengine.cpp +++ b/src/plugins/opensles/qopenslesengine.cpp @@ -38,8 +38,13 @@ #ifdef ANDROID #include +#include +#include #endif +#define MINIMUM_PERIOD_TIME_MS 5 +#define DEFAULT_PERIOD_TIME_MS 50 + #define CheckError(message) if (result != SL_RESULT_SUCCESS) { qWarning(message); return; } Q_GLOBAL_STATIC(QOpenSLESEngine, openslesEngine); @@ -130,6 +135,151 @@ QList QOpenSLESEngine::supportedSampleRates(QAudio::Mode mode) const } } +int QOpenSLESEngine::getOutputValue(QOpenSLESEngine::OutputValue type, int defaultValue) +{ +#if defined(Q_OS_ANDROID) && !defined(Q_OS_ANDROID_NO_SDK) + static int sampleRate = 0; + static int framesPerBuffer = 0; + static const int sdkVersion = QtAndroidPrivate::androidSdkVersion(); + + if (sdkVersion < 17) // getProperty() was added in API level 17... + return defaultValue; + + if (type == FramesPerBuffer && framesPerBuffer != 0) + return framesPerBuffer; + + if (type == SampleRate && sampleRate != 0) + return sampleRate; + + QJNIObjectPrivate ctx(QtAndroidPrivate::activity()); + if (!ctx.isValid()) + return defaultValue; + + + QJNIObjectPrivate audioServiceString = ctx.getStaticObjectField("android/content/Context", + "AUDIO_SERVICE", + "Ljava/lang/String;"); + QJNIObjectPrivate am = ctx.callObjectMethod("getSystemService", + "(Ljava/lang/String;)Ljava/lang/Object;", + audioServiceString.object()); + if (!am.isValid()) + return defaultValue; + + QJNIObjectPrivate sampleRateField = QJNIObjectPrivate::getStaticObjectField("android/media/AudioManager", + "PROPERTY_OUTPUT_SAMPLE_RATE", + "Ljava/lang/String;"); + QJNIObjectPrivate framesPerBufferField = QJNIObjectPrivate::getStaticObjectField("android/media/AudioManager", + "PROPERTY_OUTPUT_FRAMES_PER_BUFFER", + "Ljava/lang/String;"); + + QJNIObjectPrivate sampleRateString = am.callObjectMethod("getProperty", + "(Ljava/lang/String;)Ljava/lang/String;", + sampleRateField.object()); + QJNIObjectPrivate framesPerBufferString = am.callObjectMethod("getProperty", + "(Ljava/lang/String;)Ljava/lang/String;", + framesPerBufferField.object()); + + if (!sampleRateString.isValid() || !framesPerBufferString.isValid()) + return defaultValue; + + framesPerBuffer = framesPerBufferString.toString().toInt(); + sampleRate = sampleRateString.toString().toInt(); + + if (type == FramesPerBuffer) + return framesPerBuffer; + + if (type == SampleRate) + return sampleRate; + +#endif // Q_OS_ANDROID + + return defaultValue; +} + +int QOpenSLESEngine::getDefaultBufferSize(const QAudioFormat &format) +{ +#if defined(Q_OS_ANDROID) && !defined(Q_OS_ANDROID_NO_SDK) + if (!format.isValid()) + return 0; + + const int channelConfig = [&format]() -> int + { + if (format.channelCount() == 1) + return 4; /* MONO */ + else if (format.channelCount() == 2) + return 12; /* STEREO */ + else if (format.channelCount() > 2) + return 1052; /* SURROUND */ + else + return 1; /* DEFAULT */ + }(); + + const int audioFormat = [&format]() -> int + { + if (format.sampleType() == QAudioFormat::Float && QtAndroidPrivate::androidSdkVersion() >= 21) + return 4; /* PCM_FLOAT */ + else if (format.sampleSize() == 8) + return 3; /* PCM_8BIT */ + else if (format.sampleSize() == 16) + return 2; /* PCM_16BIT*/ + else + return 1; /* DEFAULT */ + }(); + + const int sampleRate = format.sampleRate(); + return QJNIObjectPrivate::callStaticMethod("android/media/AudioTrack", + "getMinBufferSize", + "(III)I", + sampleRate, + channelConfig, + audioFormat); +#else + return format.bytesForDuration(DEFAULT_PERIOD_TIME_MS); +#endif // Q_OS_ANDROID +} + +int QOpenSLESEngine::getLowLatencyBufferSize(const QAudioFormat &format) +{ + return format.bytesForFrames(QOpenSLESEngine::getOutputValue(QOpenSLESEngine::FramesPerBuffer, + format.framesForDuration(MINIMUM_PERIOD_TIME_MS))); +} + +bool QOpenSLESEngine::supportsLowLatency() +{ +#if defined(Q_OS_ANDROID) && !defined(Q_OS_ANDROID_NO_SDK) + static int isSupported = -1; + + if (isSupported != -1) + return (isSupported == 1); + + QJNIObjectPrivate ctx(QtAndroidPrivate::activity()); + if (!ctx.isValid()) + return false; + + QJNIObjectPrivate pm = ctx.callObjectMethod("getPackageManager", "()Landroid/content/pm/PackageManager;"); + if (!pm.isValid()) + return false; + + QJNIObjectPrivate audioFeatureField = QJNIObjectPrivate::getStaticObjectField("android/content/pm/PackageManager", + "FEATURE_AUDIO_LOW_LATENCY", + "Ljava/lang/String;"); + if (!audioFeatureField.isValid()) + return false; + + isSupported = pm.callMethod("hasSystemFeature", + "(Ljava/lang/String;)Z", + audioFeatureField.object()); + return (isSupported == 1); +#else + return true; +#endif // Q_OS_ANDROID +} + +bool QOpenSLESEngine::printDebugInfo() +{ + return qEnvironmentVariableIsSet("QT_OPENSL_INFO"); +} + void QOpenSLESEngine::checkSupportedInputFormats() { m_supportedInputChannelCounts = QList() << 1; diff --git a/src/plugins/opensles/qopenslesengine.h b/src/plugins/opensles/qopenslesengine.h index 0c2042f50..cbc3ca115 100644 --- a/src/plugins/opensles/qopenslesengine.h +++ b/src/plugins/opensles/qopenslesengine.h @@ -45,6 +45,8 @@ QT_BEGIN_NAMESPACE class QOpenSLESEngine { public: + enum OutputValue { FramesPerBuffer, SampleRate }; + QOpenSLESEngine(); ~QOpenSLESEngine(); @@ -58,6 +60,12 @@ public: QList supportedChannelCounts(QAudio::Mode mode) const; QList supportedSampleRates(QAudio::Mode mode) const; + static int getOutputValue(OutputValue type, int defaultValue = 0); + static int getDefaultBufferSize(const QAudioFormat &format); + static int getLowLatencyBufferSize(const QAudioFormat &format); + static bool supportsLowLatency(); + static bool printDebugInfo(); + private: void checkSupportedInputFormats(); bool inputFormatIsSupported(SLDataFormat_PCM format); -- cgit v1.2.3