summaryrefslogtreecommitdiffstats
path: root/src/plugins/opensles
diff options
context:
space:
mode:
authorChristian Strømme <christian.stromme@theqtcompany.com>2015-11-17 16:05:09 +0100
committerChristian Stromme <christian.stromme@theqtcompany.com>2016-01-08 00:04:12 +0000
commit3bd9da9aba63b51114a3602313e51085954be194 (patch)
tree9ddf571092e5793c26e4c4009a510799db7c5fe4 /src/plugins/opensles
parentddaacc147e1df96ac49d4276dc2cd623bb084bed (diff)
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 <yoann.lopes@theqtcompany.com>
Diffstat (limited to 'src/plugins/opensles')
-rw-r--r--src/plugins/opensles/opensles.pro2
-rw-r--r--src/plugins/opensles/qopenslesaudiooutput.cpp31
-rw-r--r--src/plugins/opensles/qopenslesdeviceinfo.cpp2
-rw-r--r--src/plugins/opensles/qopenslesengine.cpp150
-rw-r--r--src/plugins/opensles/qopenslesengine.h8
5 files changed, 184 insertions, 9 deletions
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<QString, qint32> 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 <SLES/OpenSLES_Android.h>
+#include <QtCore/private/qjnihelpers_p.h>
+#include <QtCore/private/qjni_p.h>
#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<int> 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<jint>("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<jboolean>("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<int>() << 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<int> supportedChannelCounts(QAudio::Mode mode) const;
QList<int> 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);