Revision control

++ // Copyright (C) 2024 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/qandroidscreencapture_p.h>
++
++ #include <QtCore/private/qjnihelpers_p.h>
++ #include <QtCore/qreadwritelock.h>
++ #include <QtMultimedia/private/qsurfacecapturegrabber_p.h>
++
++ #include <QtFFmpegMediaPluginImpl/private/qandroidvideoframefactory_p.h>
++ #include <QtFFmpegMediaPluginImpl/private/qffmpegvideobuffer_p.h>
++
++ QT_BEGIN_NAMESPACE
++
++ Q_DECLARE_JNI_CLASS(QtScreenGrabber,
++ "org/qtproject/qt/android/multimedia/qffmpeg/QtScreenGrabber")
++ Q_DECLARE_JNI_CLASS(QtScreenCaptureService,
++ "org/qtproject/qt/android/multimedia/qffmpeg/QtScreenCaptureService")
++ Q_DECLARE_JNI_CLASS(Size, "android/util/Size")
++
++
++ namespace {
++ QAtomicInteger<int> idCounter = 0;
++ constexpr int REQUEST_CODE_MEDIA_PROJECTION = 24680; // Arbitrary
++ constexpr int RESULT_CANCEL = 0;
++ constexpr int RESULT_OK = -1;
++
++ }
++
++ class QAndroidScreenCapture::Grabber : public QtAndroidPrivate::ActivityResultListener,
++ public QSurfaceCaptureGrabber
++ {
++ public:
++ Grabber(QAndroidScreenCapture & screenCapture)
++ : m_activityRequestCode(REQUEST_CODE_MEDIA_PROJECTION + idCounter.fetchAndAddRelaxed(1))
++ , m_screenCapture(screenCapture)
++ {
++ injectContextToGrabbingThread(&m_resourceContext);
++
++ addFrameCallback(&screenCapture, &QAndroidScreenCapture::newVideoFrame);
++
++ using namespace QtJniTypes;
++ const auto sizeObj = QtScreenGrabber::callStaticMethod<Size>(
++ "getScreenCaptureSize", QtAndroidPrivate::activity());
++ const QSize size = QSize(sizeObj.callMethod<int>("getWidth"),
++ sizeObj.callMethod<int>("getHeight"));
++ m_format = QVideoFrameFormat(size, QVideoFrameFormat::Format_RGBA8888);
++
++ if (m_format.frameHeight() > 0 && m_format.frameWidth() > 0) {
++ QtAndroidPrivate::registerActivityResultListener(this);
++ m_jniGrabber = QtScreenGrabber(QtAndroidPrivate::activity(), m_activityRequestCode);
++ } else {
++ updateError(QStringLiteral("Invalid Screen size: %1x%2. Screen capture not started")
++ .arg(m_format.frameHeight())
++ .arg(m_format.frameWidth()));
++ }
++
++ setFrameRate(screenCapture.frameRate().value_or(DefaultScreenCaptureFrameRate));
++ m_format.setStreamFrameRate(frameRate());
++ }
++
++ QVideoFrame grabFrame() override
++ {
++ return m_resourceContext.latestFrame();
++ }
++
++ bool handleActivityResult(jint requestCode, jint resultCode, jobject data) override
++ {
++ if (requestCode != m_activityRequestCode || m_jniGrabber == nullptr)
++ return false;
++
++ if (resultCode == RESULT_OK) {
++ const QtJniTypes::Intent intent(data);
++ const bool screenCaptureServiceStarted = m_jniGrabber.callMethod<bool>(
++ "startScreenCaptureService",
++ resultCode,
++ reinterpret_cast<jlong>(&m_screenCapture),
++ m_format.frameWidth(),
++ m_format.frameHeight(),
++ intent);
++ if (!screenCaptureServiceStarted)
++ updateError(QStringLiteral("Cannot start screen capture service"));
++ } else if (resultCode == RESULT_CANCEL) {
++ updateError(QStringLiteral("Screen capture canceled"));
++ }
++ return true;
++ }
++
++ ~Grabber() override
++ {
++ stop();
++ QtAndroidPrivate::unregisterActivityResultListener(this);
++ m_jniGrabber.callMethod<bool>("stopScreenCaptureService");
++ }
++
++ QVideoFrameFormat format() const { return m_format; }
++
++ void onNewFrameReceived(QtJniTypes::Image image)
++ {
++ QMetaObject::invokeMethod(&m_resourceContext, &ResourceContext::updateLatestImageRef,
++ image);
++ }
++
++ private:
++ void updateError(const QString &errorString)
++ {
++ QMetaObject::invokeMethod(&m_screenCapture,
++ &QPlatformSurfaceCapture::updateError,
++ Qt::QueuedConnection,
++ QPlatformSurfaceCapture::Error::InternalError,
++ errorString);
++ }
++
++ QtJniTypes::QtScreenGrabber m_jniGrabber;
++ const int m_activityRequestCode;
++ QAndroidScreenCapture & m_screenCapture;
++ QVideoFrameFormat m_format;
++
++ class ResourceContext : public QObject
++ {
++ public:
++ ResourceContext() : m_frameFactory(QAndroidVideoFrameFactory::create()) { }
++ ~ResourceContext() override { updateLatestImageRef({ }); }
++ Q_DISABLE_COPY_MOVE(ResourceContext)
++
++ void updateLatestImageRef(QJniObject newImage)
++ {
++ auto oldImage = std::exchange(m_latestImage, newImage);
++ if (oldImage.isValid())
++ oldImage.callMethod<void>("close");
++ }
++
++ QVideoFrame latestFrame()
++ {
++ Q_ASSERT(m_frameFactory);
++ return m_latestImage.isValid()
++ ? m_frameFactory->createVideoFrame(std::move(m_latestImage))
++ : QVideoFrame();
++ }
++
++ private:
++ QJniObject m_latestImage;
++ std::shared_ptr<QAndroidVideoFrameFactory> m_frameFactory;
++ } m_resourceContext;
++ };
++
++ QAndroidScreenCapture::QAndroidScreenCapture()
++ : QPlatformSurfaceCapture(ScreenSource{})
++ {
++ }
++
++ QAndroidScreenCapture::~QAndroidScreenCapture()
++ {
++ }
++
++ QVideoFrameFormat QAndroidScreenCapture::frameFormat() const
++ {
++ return m_grabber ? m_grabber->format() : QVideoFrameFormat();
++ }
++
++ bool QAndroidScreenCapture::setActiveInternal(bool active)
++ {
++ if (active == static_cast<bool>(m_grabber))
++ return true;
++
++ if (m_grabber) {
++ m_grabber.reset();
++ } else {
++ m_grabber = std::make_unique<Grabber>(*this);
++ m_grabber->start();
++ }
++
++ return bool(m_grabber) == active;
++ }
++
++ void QAndroidScreenCapture::onNewFrameReceived(QtJniTypes::Image image)
++ {
++ if (m_grabber)
++ m_grabber->onNewFrameReceived(image);
++ else if (image.isValid())
++ image.callMethod<void>("close");
++ }
++
++ static void onScreenFrameAvailable(JNIEnv *env, jobject obj, QtJniTypes::Image image, jlong id)
++ {
++ Q_UNUSED(env);
++ Q_UNUSED(obj);
++ auto *cppObj = reinterpret_cast<QAndroidScreenCapture*>(id);
++ cppObj->onNewFrameReceived(image);
++ }
++ Q_DECLARE_JNI_NATIVE_METHOD(onScreenFrameAvailable)
++
++ static void onErrorUpdate(JNIEnv *env, jobject obj, QString errorString, jlong id)
++ {
++ Q_UNUSED(env);
++ Q_UNUSED(obj);
++ auto cppObj = reinterpret_cast<QAndroidScreenCapture*>(id);
++ QMetaObject::invokeMethod(cppObj,
++ &QPlatformSurfaceCapture::updateError,
++ Qt::QueuedConnection,
++ QPlatformSurfaceCapture::Error::InternalError,
++ errorString);
++ }
++ Q_DECLARE_JNI_NATIVE_METHOD(onErrorUpdate)
++
++
++ bool QAndroidScreenCapture::registerNativeMethods()
++ {
++ using namespace QtJniTypes;
++ static const bool registered = []() {
++ return QtScreenCaptureService::registerNativeMethods(
++ { Q_JNI_NATIVE_METHOD(onScreenFrameAvailable),
++ Q_JNI_NATIVE_METHOD(onErrorUpdate)});
++ }();
++ return registered;
++ }
++
++ QT_END_NAMESPACE