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>
This commit is contained in:
committed by
Christian Stromme
parent
ddaacc147e
commit
3bd9da9aba
@@ -1,5 +1,5 @@
|
||||
TARGET = qtaudio_opensles
|
||||
QT += multimedia-private
|
||||
QT += multimedia-private core-private
|
||||
|
||||
PLUGIN_TYPE = audio
|
||||
PLUGIN_CLASS_NAME = QOpenSLESPlugin
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user