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 "androidmediarecorder_p.h"
++ #include "androidcamera_p.h"
++ #include "androidsurfacetexture_p.h"
++ #include "androidsurfaceview_p.h"
++ #include "qandroidglobal_p.h"
++ #include "qandroidmultimediautils_p.h"
++
++ #include <qmap.h>
++ #include <QtCore/qcoreapplication.h>
++ #include <QtCore/qlogging.h>
++ #include <QtCore/qurl.h>
++
++ QT_BEGIN_NAMESPACE
++
++ Q_STATIC_LOGGING_CATEGORY(lcMediaRecorder, "qt.multimedia.mediarecorder.android");
++
++ Q_DECLARE_JNI_CLASS(QtAudioDeviceManager,
++ "org/qtproject/qt/android/multimedia/QtAudioDeviceManager");
++ Q_DECLARE_JNI_CLASS(AudioDeviceInfo, "android/media/AudioDeviceInfo");
++
++ typedef QMap<QString, QJniObject> CamcorderProfiles;
++ Q_GLOBAL_STATIC(CamcorderProfiles, g_camcorderProfiles)
++
++ static QString profileKey()
++ {
++ return QStringLiteral("%1-%2");
++ }
++
++ bool AndroidCamcorderProfile::hasProfile(jint cameraId, Quality quality)
++ {
++ if (g_camcorderProfiles->contains(profileKey().arg(cameraId).arg(quality)))
++ return true;
++
++ return QJniObject::callStaticMethod<jboolean>("android/media/CamcorderProfile",
++ "hasProfile",
++ "(II)Z",
++ cameraId,
++ quality);
++ }
++
++ AndroidCamcorderProfile AndroidCamcorderProfile::get(jint cameraId, Quality quality)
++ {
++ const QString key = profileKey().arg(cameraId).arg(quality);
++ QMap<QString, QJniObject>::const_iterator it = g_camcorderProfiles->constFind(key);
++
++ if (it != g_camcorderProfiles->constEnd())
++ return AndroidCamcorderProfile(*it);
++
++ QJniObject camProfile = QJniObject::callStaticObjectMethod("android/media/CamcorderProfile",
++ "get",
++ "(II)Landroid/media/CamcorderProfile;",
++ cameraId,
++ quality);
++
++ return AndroidCamcorderProfile((*g_camcorderProfiles)[key] = camProfile);
++ }
++
++ int AndroidCamcorderProfile::getValue(AndroidCamcorderProfile::Field field) const
++ {
++ switch (field) {
++ case audioBitRate:
++ return m_camcorderProfile.getField<jint>("audioBitRate");
++ case audioChannels:
++ return m_camcorderProfile.getField<jint>("audioChannels");
++ case audioCodec:
++ return m_camcorderProfile.getField<jint>("audioCodec");
++ case audioSampleRate:
++ return m_camcorderProfile.getField<jint>("audioSampleRate");
++ case duration:
++ return m_camcorderProfile.getField<jint>("duration");
++ case fileFormat:
++ return m_camcorderProfile.getField<jint>("fileFormat");
++ case quality:
++ return m_camcorderProfile.getField<jint>("quality");
++ case videoBitRate:
++ return m_camcorderProfile.getField<jint>("videoBitRate");
++ case videoCodec:
++ return m_camcorderProfile.getField<jint>("videoCodec");
++ case videoFrameHeight:
++ return m_camcorderProfile.getField<jint>("videoFrameHeight");
++ case videoFrameRate:
++ return m_camcorderProfile.getField<jint>("videoFrameRate");
++ case videoFrameWidth:
++ return m_camcorderProfile.getField<jint>("videoFrameWidth");
++ }
++
++ return 0;
++ }
++
++ AndroidCamcorderProfile::AndroidCamcorderProfile(const QJniObject &camcorderProfile)
++ {
++ m_camcorderProfile = camcorderProfile;
++ }
++
++ static const char QtMediaRecorderListenerClassName[] =
++ "org/qtproject/qt/android/multimedia/QtMediaRecorderListener";
++ typedef QMap<jlong, AndroidMediaRecorder*> MediaRecorderMap;
++ Q_GLOBAL_STATIC(MediaRecorderMap, mediaRecorders)
++
++ static void notifyError(JNIEnv* , jobject, jlong id, jint what, jint extra)
++ {
++ AndroidMediaRecorder *obj = mediaRecorders->value(id, 0);
++ if (obj)
++ Q_EMIT obj->error(what, extra);
++ }
++
++ static void notifyInfo(JNIEnv* , jobject, jlong id, jint what, jint extra)
++ {
++ AndroidMediaRecorder *obj = mediaRecorders->value(id, 0);
++ if (obj)
++ Q_EMIT obj->info(what, extra);
++ }
++
++ AndroidMediaRecorder::AndroidMediaRecorder()
++ : QObject()
++ , m_id(reinterpret_cast<jlong>(this))
++ {
++ m_mediaRecorder = QJniObject("android/media/MediaRecorder");
++ if (m_mediaRecorder.isValid()) {
++ QJniObject listener(QtMediaRecorderListenerClassName, "(J)V", m_id);
++ m_mediaRecorder.callMethod<void>("setOnErrorListener",
++ "(Landroid/media/MediaRecorder$OnErrorListener;)V",
++ listener.object());
++ m_mediaRecorder.callMethod<void>("setOnInfoListener",
++ "(Landroid/media/MediaRecorder$OnInfoListener;)V",
++ listener.object());
++ mediaRecorders->insert(m_id, this);
++ }
++ }
++
++ AndroidMediaRecorder::~AndroidMediaRecorder()
++ {
++ if (m_isVideoSourceSet || m_isAudioSourceSet)
++ reset();
++
++ release();
++ mediaRecorders->remove(m_id);
++ }
++
++ void AndroidMediaRecorder::release()
++ {
++ m_mediaRecorder.callMethod<void>("release");
++ }
++
++ bool AndroidMediaRecorder::prepare()
++ {
++ QJniEnvironment env;
++ auto methodId = env->GetMethodID(m_mediaRecorder.objectClass(), "prepare", "()V");
++ env->CallVoidMethod(m_mediaRecorder.object(), methodId);
++
++ if (env.checkAndClearExceptions())
++ return false;
++ return true;
++ }
++
++ void AndroidMediaRecorder::reset()
++ {
++ m_mediaRecorder.callMethod<void>("reset");
++ m_isAudioSourceSet = false; // Now setAudioSource can be used again.
++ m_isVideoSourceSet = false;
++ }
++
++ bool AndroidMediaRecorder::start()
++ {
++ QJniEnvironment env;
++ auto methodId = env->GetMethodID(m_mediaRecorder.objectClass(), "start", "()V");
++ env->CallVoidMethod(m_mediaRecorder.object(), methodId);
++
++ if (env.checkAndClearExceptions())
++ return false;
++ return true;
++ }
++
++ void AndroidMediaRecorder::stop()
++ {
++ m_mediaRecorder.callMethod<void>("stop");
++ }
++
++ void AndroidMediaRecorder::setAudioChannels(int numChannels)
++ {
++ m_mediaRecorder.callMethod<void>("setAudioChannels", "(I)V", numChannels);
++ }
++
++ void AndroidMediaRecorder::setAudioEncoder(AudioEncoder encoder)
++ {
++ QJniEnvironment env;
++ m_mediaRecorder.callMethod<void>("setAudioEncoder", "(I)V", int(encoder));
++ }
++
++ void AndroidMediaRecorder::setAudioEncodingBitRate(int bitRate)
++ {
++ m_mediaRecorder.callMethod<void>("setAudioEncodingBitRate", "(I)V", bitRate);
++ }
++
++ void AndroidMediaRecorder::setAudioSamplingRate(int samplingRate)
++ {
++ m_mediaRecorder.callMethod<void>("setAudioSamplingRate", "(I)V", samplingRate);
++ }
++
++ void AndroidMediaRecorder::setAudioSource(AudioSource source)
++ {
++ if (!m_isAudioSourceSet) {
++ QJniEnvironment env;
++ auto methodId = env->GetMethodID(m_mediaRecorder.objectClass(), "setAudioSource", "(I)V");
++ env->CallVoidMethod(m_mediaRecorder.object(), methodId, source);
++ if (!env.checkAndClearExceptions())
++ m_isAudioSourceSet = true;
++ } else {
++ qCWarning(lcMediaRecorder) << "Audio source already set. Not setting a new source.";
++ }
++ }
++
++ bool AndroidMediaRecorder::isAudioSourceSet() const
++ {
++ return m_isAudioSourceSet;
++ }
++
++ bool AndroidMediaRecorder::setAudioInput(const QByteArray &id)
++ {
++ using namespace QtJniTypes;
++
++ AudioDeviceInfo deviceInfo = QtAudioDeviceManager::callStaticMethod<AudioDeviceInfo>(
++ "getAudioInputDeviceInfo", id.toInt());
++
++ if (!m_mediaRecorder.callMethod<jboolean>("setPreferredDevice", deviceInfo))
++ return false;
++
++ const bool ret =
++ QtAudioDeviceManager::callStaticMethod<jboolean>("prepareAudioInput", id.toInt());
++
++ if (!ret)
++ qCWarning(lcMediaRecorder) << "No default input device was set.";
++
++ return ret;
++ }
++
++ void AndroidMediaRecorder::setCamera(AndroidCamera *camera)
++ {
++ QJniObject cam = camera->getCameraObject();
++ m_mediaRecorder.callMethod<void>("setCamera", "(Landroid/hardware/Camera;)V", cam.object());
++ }
++
++ void AndroidMediaRecorder::setVideoEncoder(VideoEncoder encoder)
++ {
++ m_mediaRecorder.callMethod<void>("setVideoEncoder", "(I)V", int(encoder));
++ }
++
++ void AndroidMediaRecorder::setVideoEncodingBitRate(int bitRate)
++ {
++ m_mediaRecorder.callMethod<void>("setVideoEncodingBitRate", "(I)V", bitRate);
++ }
++
++ void AndroidMediaRecorder::setVideoFrameRate(int rate)
++ {
++ m_mediaRecorder.callMethod<void>("setVideoFrameRate", "(I)V", rate);
++ }
++
++ void AndroidMediaRecorder::setVideoSize(const QSize &size)
++ {
++ m_mediaRecorder.callMethod<void>("setVideoSize", "(II)V", size.width(), size.height());
++ }
++
++ void AndroidMediaRecorder::setVideoSource(VideoSource source)
++ {
++ QJniEnvironment env;
++
++ auto methodId = env->GetMethodID(m_mediaRecorder.objectClass(), "setVideoSource", "(I)V");
++ env->CallVoidMethod(m_mediaRecorder.object(), methodId, source);
++
++ if (!env.checkAndClearExceptions())
++ m_isVideoSourceSet = true;
++ }
++
++ void AndroidMediaRecorder::setOrientationHint(int degrees)
++ {
++ m_mediaRecorder.callMethod<void>("setOrientationHint", "(I)V", degrees);
++ }
++
++ void AndroidMediaRecorder::setOutputFormat(OutputFormat format)
++ {
++ QJniEnvironment env;
++ auto methodId = env->GetMethodID(m_mediaRecorder.objectClass(), "setOutputFormat", "(I)V");
++ env->CallVoidMethod(m_mediaRecorder.object(), methodId, format);
++ // setAudioSource cannot be set after outputFormat is set.
++ if (!env.checkAndClearExceptions())
++ m_isAudioSourceSet = true;
++ }
++
++ void AndroidMediaRecorder::setOutputFile(const QString &path)
++ {
++ if (QUrl(path).scheme() == QLatin1String("content")) {
++ const QJniObject fileDescriptor = QJniObject::callStaticObjectMethod(
++ "org/qtproject/qt/android/QtNative",
++ "openFdObjectForContentUrl",
++ "(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;)Ljava/io/FileDescriptor;",
++ QNativeInterface::QAndroidApplication::context().object(),
++ QJniObject::fromString(path).object(),
++ QJniObject::fromString(QLatin1String("rw")).object());
++
++ m_mediaRecorder.callMethod<void>("setOutputFile",
++ "(Ljava/io/FileDescriptor;)V",
++ fileDescriptor.object());
++ } else {
++ m_mediaRecorder.callMethod<void>("setOutputFile",
++ "(Ljava/lang/String;)V",
++ QJniObject::fromString(path).object());
++ }
++ }
++
++ void AndroidMediaRecorder::setSurfaceTexture(AndroidSurfaceTexture *texture)
++ {
++ m_mediaRecorder.callMethod<void>("setPreviewDisplay",
++ "(Landroid/view/Surface;)V",
++ texture->surface());
++ }
++
++ void AndroidMediaRecorder::setSurfaceHolder(AndroidSurfaceHolder *holder)
++ {
++ QJniObject surfaceHolder(holder->surfaceHolder());
++ QJniObject surface = surfaceHolder.callObjectMethod("getSurface",
++ "()Landroid/view/Surface;");
++ if (!surface.isValid())
++ return;
++
++ m_mediaRecorder.callMethod<void>("setPreviewDisplay",
++ "(Landroid/view/Surface;)V",
++ surface.object());
++ }
++
++ bool AndroidMediaRecorder::registerNativeMethods()
++ {
++ static const JNINativeMethod methods[] = {
++ {"notifyError", "(JII)V", (void *)notifyError},
++ {"notifyInfo", "(JII)V", (void *)notifyInfo}
++ };
++
++ const int size = std::size(methods);
++ return QJniEnvironment().registerNativeMethods(QtMediaRecorderListenerClassName, methods, size);
++ }
++
++ QT_END_NAMESPACE
++
++ #include "moc_androidmediarecorder_p.cpp"