Revision control

Copy as Markdown

// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include <QtMultimedia/private/qandroidaudiodevices_p.h>
#include <QtMultimedia/private/qandroidaudiodevice_p.h>
#include <QtMultimedia/private/qandroidaudiojnitypes_p.h>
#include <QtMultimedia/private/qandroidaudiosink_p.h>
#include <QtMultimedia/private/qandroidaudiosource_p.h>
#include <QtMultimedia/private/qplatformmediaintegration_p.h>
#include <QtCore/qjniobject.h>
QT_BEGIN_NAMESPACE
using namespace QtJniTypes;
namespace {
QAudioFormat preferredFormatForDevice(const QtJniTypes::AudioDeviceInfo &deviceInfo)
{
QAudioFormat preferredFormat;
// Set preferred channel count based on what device reports, with default set to stereo (2)
QJniArray<jint> channelCounts = deviceInfo.callMethod<QJniArray<jint>>("getChannelCounts");
if (channelCounts.isEmpty()) {
preferredFormat.setChannelConfig(QAudioFormat::ChannelConfigStereo);
} else {
const auto [minIt, maxIt] = std::minmax_element(channelCounts.begin(), channelCounts.end());
const int channelCount = std::clamp(2, *minIt, *maxIt);
preferredFormat.setChannelConfig(
QAudioFormat::defaultChannelConfigForChannelCount(channelCount));
}
// Get optimal sample rate from AudioManager
preferredFormat.setSampleRate(
QtAudioDeviceManager::callStaticMethod<jint>("getDefaultSampleRate"));
// Using Float avoids conversions for processing, so we should prefer that instead of whatever
// the device uses natively
preferredFormat.setSampleFormat(QAudioFormat::Float);
return preferredFormat;
};
QList<QAudioDevice> availableDevices(QAudioDevice::Mode mode)
{
if (mode == QAudioDevice::Null)
return {};
QList<QAudioDevice> devices;
const char *getMethod =
mode == QAudioDevice::Input ? "getAudioInputDevices" : "getAudioOutputDevices";
auto deviceInfos =
QtAudioDeviceManager::callStaticMethod<QJniArray<AudioDeviceInfo>>(getMethod);
if (!deviceInfos.isValid())
return {};
for (int i = 0; i < deviceInfos.size(); ++i) {
AudioDeviceInfo deviceInfo = deviceInfos.at(i);
int id = deviceInfo.callMethod<jint>("getId");
jint deviceType = deviceInfo.callMethod<jint>("getType");
auto description = QtAudioDeviceManager::callStaticMethod<QString>(
"audioDeviceTypeToString", deviceType);
bool isBluetoothDevice =
QtAudioDeviceManager::callStaticMethod<jboolean>("isBluetoothDevice", deviceInfo);
devices << QAudioDevicePrivate::createQAudioDevice(std::make_unique<QAndroidAudioDevice>(
QString::number(id).toUtf8(), description, mode,
preferredFormatForDevice(deviceInfo), isBluetoothDevice, i == 0));
}
return devices;
}
} // namespace
// Called by any C++ thread
QAndroidAudioDevices::QAndroidAudioDevices() : QPlatformAudioDevices()
{
QtAudioDeviceManager::callStaticMethod<void>(
"qAndroidAudioDevicesConstructed",
static_cast<jlong>(reinterpret_cast<size_t>(this)));
}
QAndroidAudioDevices::~QAndroidAudioDevices()
{
// Performs a blocking call to unregister QAndroidAudioDevices from receiving
// any more callbacks, and flushes remaining callbacks.
QtAudioDeviceManager::callStaticMethod<void>("qAndroidAudioDevicesDestroyed");
}
// Called by any C++ thread
QList<QAudioDevice> QAndroidAudioDevices::findAudioInputs() const
{
return availableDevices(QAudioDevice::Input);
}
// Called by any C++ thread
QList<QAudioDevice> QAndroidAudioDevices::findAudioOutputs() const
{
return availableDevices(QAudioDevice::Output);
}
// Called by any C++ thread
QPlatformAudioSource *QAndroidAudioDevices::createAudioSource(const QAudioDevice &deviceInfo,
const QAudioFormat &fmt,
QObject *parent)
{
return new QtAAudio::QAndroidAudioSource(deviceInfo, fmt, parent);
}
// Called by any C++ thread
QPlatformAudioSink *QAndroidAudioDevices::createAudioSink(const QAudioDevice &deviceInfo,
const QAudioFormat &fmt, QObject *parent)
{
return new QtAAudio::QAndroidAudioSink(deviceInfo, fmt, parent);
}
// Invoked by background Java Handler thread
static void onAudioInputDevicesUpdated(
JNIEnv * /*env*/,
jobject /*thiz*/,
jlong nativePtr)
{
auto *audioDevices = reinterpret_cast<QAndroidAudioDevices*>(static_cast<size_t>(nativePtr));
Q_ASSERT(!audioDevices->thread()->isCurrentThread());
audioDevices->onAudioInputsChanged();
}
Q_DECLARE_JNI_NATIVE_METHOD(onAudioInputDevicesUpdated)
// Invoked by background Java Handler thread
static void onAudioOutputDevicesUpdated(
JNIEnv * /*env*/,
jobject /*thiz*/,
jlong nativePtr)
{
auto *audioDevices = reinterpret_cast<QAndroidAudioDevices*>(static_cast<size_t>(nativePtr));
Q_ASSERT(!audioDevices->thread()->isCurrentThread());
audioDevices->onAudioOutputsChanged();
}
Q_DECLARE_JNI_NATIVE_METHOD(onAudioOutputDevicesUpdated)
bool QAndroidAudioDevices::registerNativeMethods()
{
static const bool registered = []{
const auto context = QNativeInterface::QAndroidApplication::context();
QtAudioDeviceManager::callStaticMethod<void>("setContext", context);
return QtJniTypes::QtAudioDeviceManager::registerNativeMethods({
Q_JNI_NATIVE_METHOD(onAudioInputDevicesUpdated),
Q_JNI_NATIVE_METHOD(onAudioOutputDevicesUpdated),
});
}();
return registered;
}
QT_END_NAMESPACE
extern "C" Q_DECL_EXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void * /*reserved*/)
{
static bool initialized = false;
if (initialized)
return JNI_VERSION_1_6;
initialized = true;
typedef union {
JNIEnv *nativeEnvironment;
void *venv;
} UnionJNIEnvToVoid;
UnionJNIEnvToVoid uenv;
uenv.venv = NULL;
if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_6) != JNI_OK)
return JNI_ERR;
if (!QAndroidAudioDevices::registerNativeMethods())
return JNI_ERR;
return JNI_VERSION_1_6;
}