Revision control

++ // 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