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 <QtFFmpegMediaPluginImpl/private/qandroidvideodevices_p.h>
#include <QtCore/private/qandroidextras_p.h>
#include <QtCore/qcoreapplication.h>
#include <QtCore/qcoreapplication_platform.h>
#include <QtCore/qjnienvironment.h>
#include <QtCore/qloggingcategory.h>
#include <QtFFmpegMediaPluginImpl/private/qandroidvideoframebuffer_p.h>
#include <QtMultimedia/private/qcameradevice_p.h>
#include <jni.h>
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
namespace QFFmpeg {
Q_STATIC_LOGGING_CATEGORY(qLCAndroidVideoDevices, "qt.multimedia.ffmpeg.android.videoDevices");
// Can be called from any thread
QAndroidVideoDevices::QAndroidVideoDevices(QPlatformMediaIntegration *integration)
: QPlatformVideoDevices(integration)
{
m_javaCameraAvailabilityListener = QJavaCameraAvailabilityListener(
QtAndroidPrivate::activity(),
static_cast<jlong>(reinterpret_cast<size_t>(this)));
}
QAndroidVideoDevices::~QAndroidVideoDevices()
{
m_javaCameraAvailabilityListener.callMethod<void>("cleanup");
}
QCameraFormat createCameraFormat(int width, int height, int fpsMin, int fpsMax)
{
QCameraFormatPrivate *format = new QCameraFormatPrivate();
format->resolution = { width, height };
format->minFrameRate = fpsMin;
format->maxFrameRate = fpsMax;
format->pixelFormat = QVideoFrameFormat::PixelFormat::Format_YUV420P;
return format->create();
}
// Can be called from any thread
QList<QCameraDevice> QAndroidVideoDevices::findVideoInputs() const
{
QList<QCameraDevice> devices;
QtJniTypes::QtVideoDeviceManager deviceManager {
QNativeInterface::QAndroidApplication::context() };
if (!deviceManager.isValid()) {
qCWarning(qLCAndroidVideoDevices) << "Failed to connect to Qt Video Device Manager.";
return devices;
}
const QJniArray cameraIdList = deviceManager.callMethod<QtJniTypes::String[]>("getCameraIdList");
if (!cameraIdList.isValid())
return devices;
int cameraIndex = 0;
for (const auto &cameraId : cameraIdList) {
if (!cameraId.isValid())
continue;
auto info = std::make_unique<QCameraDevicePrivate>();
info->id = cameraId.toString().toUtf8();
info->orientation = deviceManager.callMethod<jint>("getSensorOrientation", cameraId);
// Will be set to -1 if facing can not be determined.
const int facing = deviceManager.callMethod<jint>("getLensFacing", cameraId);
// Values grabbed from Android docs CameraCharacteristics.LENS_FACING
constexpr int LENS_FACING_FRONT = 0;
constexpr int LENS_FACING_BACK = 1;
constexpr int LENS_FACING_EXTERNAL = 2;
switch (facing) {
case LENS_FACING_EXTERNAL:
info->position = QCameraDevice::Position::UnspecifiedPosition;
info->description = QStringLiteral(u"External Camera: %1").arg(cameraIndex);
break;
case LENS_FACING_BACK:
info->position = QCameraDevice::Position::BackFace;
info->description = QStringLiteral(u"Rear Camera: %1").arg(cameraIndex);
break;
case LENS_FACING_FRONT:
info->position = QCameraDevice::Position::FrontFace;
info->description = QStringLiteral(u"Front Camera: %1").arg(cameraIndex);
break;
default:
info->position = QCameraDevice::Position::UnspecifiedPosition;
info->description = QStringLiteral(u"Camera: %1").arg(cameraIndex);
break;
}
++cameraIndex;
const auto fpsRanges = deviceManager.callMethod<QStringList>("getFpsRange", cameraId);
int maxFps = 0, minFps = 0;
for (auto range : fpsRanges) {
range = range.remove(u"["_s);
range = range.remove(u"]"_s);
const auto split = range.split(u","_s);
int min = split.at(0).toInt();
int max = split.at(1).toInt();
if (max > maxFps) {
maxFps = max;
minFps = min;
}
}
const static int yuvFormat = QtJniTypes::ImageFormat::getStaticField<jint>("YUV_420_888");
const QStringList videoSizes = deviceManager.callMethod<QStringList>(
"getStreamConfigurationsSizes", cameraId, yuvFormat);
const static int jpegFormat = QtJniTypes::ImageFormat::getStaticField<jint>("JPEG");
const QStringList photoSizes = deviceManager.callMethod<QStringList>(
"getStreamConfigurationsSizes", cameraId, jpegFormat);
if (videoSizes.isEmpty() && photoSizes.isEmpty())
continue;
auto sizeFromString = [](const QString &sizeString) -> QSize {
const auto split = sizeString.split(u"x"_s);
int width = split.at(0).toInt();
int height = split.at(1).toInt();
return { width, height };
};
for (const auto &sizeString : videoSizes) {
auto size = sizeFromString(sizeString);
info->videoFormats.append(
createCameraFormat(size.width(), size.height(), minFps, maxFps));
}
for (const auto &sizeString : photoSizes)
info->photoResolutions.append(sizeFromString(sizeString));
devices.push_back(info.release()->create());
}
return devices;
}
} // namespace QFFmpeg
// Called from background looper thread in Android
static void onCameraAvailableNative(
JNIEnv*,
jobject,
jlong nativePtr)
{
using namespace QFFmpeg;
auto *videoDevices = reinterpret_cast<QAndroidVideoDevices*>(static_cast<size_t>(nativePtr));
Q_ASSERT(!videoDevices->thread()->isCurrentThread());
videoDevices->onVideoInputsChanged();
}
Q_DECLARE_JNI_NATIVE_METHOD(onCameraAvailableNative)
// Called from background looper thread in Android
static void onCameraUnavailableNative(
JNIEnv*,
jobject,
jlong nativePtr)
{
using namespace QFFmpeg;
auto *videoDevices = reinterpret_cast<QAndroidVideoDevices*>(static_cast<size_t>(nativePtr));
Q_ASSERT(!videoDevices->thread()->isCurrentThread());
videoDevices->onVideoInputsChanged();
}
Q_DECLARE_JNI_NATIVE_METHOD(onCameraUnavailableNative)
bool QFFmpeg::QAndroidVideoDevices::registerNativeMethods() {
return QJavaCameraAvailabilityListener::registerNativeMethods({
Q_JNI_NATIVE_METHOD(onCameraAvailableNative),
Q_JNI_NATIVE_METHOD(onCameraUnavailableNative),
});
}
QT_END_NAMESPACE