#include #include #include #include #include #include #include #include #include "QGCLoggingCategory.h" QGC_LOGGING_CATEGORY(AppLog, "VideoReceiverApp") #if defined(__android__) #include #include #include static jobject _class_loader = nullptr; static jobject _context = nullptr; extern "C" { void gst_amc_jni_set_java_vm(JavaVM *java_vm); jobject gst_android_get_application_class_loader(void) { return _class_loader; } } static void gst_android_init(JNIEnv* env, jobject context) { jobject class_loader = nullptr; jclass context_cls = env->GetObjectClass(context); if (!context_cls) { return; } jmethodID get_class_loader_id = env->GetMethodID(context_cls, "getClassLoader", "()Ljava/lang/ClassLoader;"); if (env->ExceptionCheck()) { env->ExceptionDescribe(); env->ExceptionClear(); return; } class_loader = env->CallObjectMethod(context, get_class_loader_id); if (env->ExceptionCheck()) { env->ExceptionDescribe(); env->ExceptionClear(); return; } _context = env->NewGlobalRef(context); _class_loader = env->NewGlobalRef(class_loader); } static const char kJniClassName[] {"labs/mavlink/VideoReceiverApp/QGLSinkActivity"}; static void setNativeMethods(void) { JNINativeMethod javaMethods[] { {"nativeInit", "()V", reinterpret_cast(gst_android_init)} }; QAndroidJniEnvironment jniEnv; if (jniEnv->ExceptionCheck()) { jniEnv->ExceptionDescribe(); jniEnv->ExceptionClear(); } jclass objectClass = jniEnv->FindClass(kJniClassName); if (!objectClass) { qWarning() << "Couldn't find class:" << kJniClassName; return; } jint val = jniEnv->RegisterNatives(objectClass, javaMethods, sizeof(javaMethods) / sizeof(javaMethods[0])); if (val < 0) { qWarning() << "Error registering methods: " << val; } else { qDebug() << "Main Native Functions Registered"; } if (jniEnv->ExceptionCheck()) { jniEnv->ExceptionDescribe(); jniEnv->ExceptionClear(); } } jint JNI_OnLoad(JavaVM* vm, void* reserved) { Q_UNUSED(reserved); JNIEnv* env; if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) { return -1; } setNativeMethods(); gst_amc_jni_set_java_vm(vm); return JNI_VERSION_1_6; } #endif #include #include class VideoReceiverApp : public QRunnable { public: VideoReceiverApp(QCoreApplication& app, bool qmlAllowed) : _app(app) , _qmlAllowed(qmlAllowed) {} void run(); int exec(); void startStreaming(); void startDecoding(); void startRecording(); protected: void _dispatch(std::function code); private: QCoreApplication& _app; bool _qmlAllowed; VideoReceiver* _receiver = nullptr; QQuickWindow* _window = nullptr; QQuickItem* _widget = nullptr; void* _videoSink = nullptr; QString _url; unsigned _timeout = 5; unsigned _connect = 1; bool _decode = true; unsigned _stopDecodingAfter = 0; bool _record = false; QString _videoFile; unsigned int _fileFormat = VideoReceiver::FILE_FORMAT_MIN; unsigned _stopRecordingAfter = 15; bool _useFakeSink = false; bool _streaming = false; bool _decoding = false; bool _recording = false; }; void VideoReceiverApp::run() { if((_videoSink = GStreamer::createVideoSink(nullptr, _widget)) == nullptr) { qCDebug(AppLog) << "createVideoSink failed"; return; } _receiver->startDecoding(_videoSink); } int VideoReceiverApp::exec() { QCommandLineParser parser; parser.addHelpOption(); parser.addPositionalArgument("url", QCoreApplication::translate("main", "Source URL.")); QCommandLineOption timeoutOption(QStringList() << "t" << "timeout", QCoreApplication::translate("main", "Source timeout."), QCoreApplication::translate("main", "seconds")); parser.addOption(timeoutOption); QCommandLineOption connectOption(QStringList() << "c" << "connect", QCoreApplication::translate("main", "Number of connection attempts."), QCoreApplication::translate("main", "attempts")); parser.addOption(connectOption); QCommandLineOption decodeOption(QStringList() << "d" << "decode", QCoreApplication::translate("main", "Decode and render video.")); parser.addOption(decodeOption); QCommandLineOption noDecodeOption("no-decode", QCoreApplication::translate("main", "Don't decode and render video.")); parser.addOption(noDecodeOption); QCommandLineOption stopDecodingOption("stop-decoding", QCoreApplication::translate("main", "Stop decoding after time."), QCoreApplication::translate("main", "seconds")); parser.addOption(stopDecodingOption); QCommandLineOption recordOption(QStringList() << "r" << "record", QCoreApplication::translate("main", "Record video."), QGuiApplication::translate("main", "file")); parser.addOption(recordOption); QCommandLineOption formatOption(QStringList() << "f" << "format", QCoreApplication::translate("main", "File format."), QCoreApplication::translate("main", "format")); parser.addOption(formatOption); QCommandLineOption stopRecordingOption("stop-recording", QCoreApplication::translate("main", "Stop recording after time."), QCoreApplication::translate("main", "seconds")); parser.addOption(stopRecordingOption); QCommandLineOption videoSinkOption("video-sink", QCoreApplication::translate("main", "Use video sink: 0 - autovideosink, 1 - fakesink"), QCoreApplication::translate("main", "sink")); if (!_qmlAllowed) { parser.addOption(videoSinkOption); } parser.process(_app); const QStringList args = parser.positionalArguments(); if (args.size() != 1) { parser.showHelp(0); } _url = args.at(0); if (parser.isSet(timeoutOption)) { _timeout = parser.value(timeoutOption).toUInt(); } if (parser.isSet(connectOption)) { _connect = parser.value(connectOption).toUInt(); } if (parser.isSet(decodeOption) && parser.isSet(noDecodeOption)) { parser.showHelp(0); } if (parser.isSet(decodeOption)) { _decode = true; } if (parser.isSet(noDecodeOption)) { _decode = false; } if (_decode && parser.isSet(stopDecodingOption)) { _stopDecodingAfter = parser.value(stopDecodingOption).toUInt(); } if (parser.isSet(recordOption)) { _record = true; _videoFile = parser.value(recordOption); } if (parser.isSet(formatOption)) { _fileFormat += parser.value(formatOption).toUInt(); } if (_record && parser.isSet(stopRecordingOption)) { _stopRecordingAfter = parser.value(stopRecordingOption).toUInt(); } if (parser.isSet(videoSinkOption)) { _useFakeSink = parser.value(videoSinkOption).toUInt() > 0; } _receiver = GStreamer::createVideoReceiver(nullptr); QQmlApplicationEngine engine; if (_decode && _qmlAllowed) { engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); _window = static_cast(engine.rootObjects().first()); Q_ASSERT(_window != nullptr); _widget = _window->findChild("videoItem"); Q_ASSERT(_widget != nullptr); } startStreaming(); QObject::connect(_receiver, &VideoReceiver::timeout, [](){ qCDebug(AppLog) << "Streaming timeout"; }); QObject::connect(_receiver, &VideoReceiver::streamingChanged, [this](bool active){ _streaming = active; if (_streaming) { qCDebug(AppLog) << "Streaming started"; } else { qCDebug(AppLog) << "Streaming stopped"; } }); QObject::connect(_receiver, &VideoReceiver::decodingChanged, [this](bool active){ _decoding = active; if (_decoding) { qCDebug(AppLog) << "Decoding started"; } else { qCDebug(AppLog) << "Decoding stopped"; if (_streaming) { if (!_recording) { _dispatch([this](){ _receiver->stop(); }); } } } }); QObject::connect(_receiver, &VideoReceiver::recordingChanged, [this](bool active){ _recording = active; if (_recording) { qCDebug(AppLog) << "Recording started"; } else { qCDebug(AppLog) << "Recording stopped"; if (_streaming) { if (!_decoding) { _dispatch([this](){ _receiver->stop(); }); } } } }); QObject::connect(_receiver, &VideoReceiver::onStartComplete, [this](VideoReceiver::STATUS status){ if (status != VideoReceiver::STATUS_OK) { qCDebug(AppLog) << "Video receiver start failed"; _dispatch([this](){ if (--_connect > 0) { qCDebug(AppLog) << "Restarting ..."; _dispatch([this](){ startStreaming(); }); } else { qCDebug(AppLog) << "Closing..."; delete _receiver; _app.exit(); } }); } else { qCDebug(AppLog) << "Video receiver started"; } }); QObject::connect(_receiver, &VideoReceiver::onStopComplete, [this](VideoReceiver::STATUS ){ qCDebug(AppLog) << "Video receiver stopped"; _dispatch([this](){ if (--_connect > 0) { qCDebug(AppLog) << "Restarting ..."; _dispatch([this](){ startStreaming(); }); } else { qCDebug(AppLog) << "Closing..."; delete _receiver; _app.exit(); } }); }); return _app.exec(); } void VideoReceiverApp::startStreaming() { _receiver->start(_url, _timeout); if (_decode) { startDecoding(); } if (_record) { startRecording(); } } void VideoReceiverApp::startDecoding() { if (_qmlAllowed) { _window->scheduleRenderJob(this, QQuickWindow::BeforeSynchronizingStage); } else { if (_videoSink == nullptr) { if ((_videoSink = gst_element_factory_make(_useFakeSink ? "fakesink" : "autovideosink", nullptr)) == nullptr) { qCDebug(AppLog) << "Failed to create video sink"; return; } } _receiver->startDecoding(_videoSink); } if (_stopDecodingAfter > 0) { unsigned connect = _connect; QTimer::singleShot(_stopDecodingAfter * 1000, Qt::PreciseTimer, [this, connect](){ if (connect != _connect) { return; } _receiver->stopDecoding(); }); } } void VideoReceiverApp::startRecording() { _receiver->startRecording(_videoFile, static_cast(_fileFormat)); if (_stopRecordingAfter > 0) { unsigned connect = _connect; QTimer::singleShot(_stopRecordingAfter * 1000, [this, connect](){ if (connect != _connect) { return; } _receiver->stopRecording(); }); } } void VideoReceiverApp::_dispatch(std::function code) { QTimer* timer = new QTimer(); timer->moveToThread(qApp->thread()); timer->setSingleShot(true); QObject::connect(timer, &QTimer::timeout, [=](){ code(); timer->deleteLater(); }); QMetaObject::invokeMethod(timer, "start", Qt::QueuedConnection, Q_ARG(int, 0)); } static bool isQtApp(const char* app) { const char* s; #if defined(Q_OS_WIN) if ((s = strrchr(app, '\\')) != nullptr) { #else if ((s = strrchr(app, '/')) != nullptr) { #endif s += 1; } else { s = app; } return s[0] == 'Q' || s[0] == 'q'; } int main(int argc, char *argv[]) { if (argc < 1) { return 0; } GStreamer::initialize(argc, argv, 3); if (isQtApp(argv[0])) { QGuiApplication app(argc, argv); VideoReceiverApp videoApp(app, true); return videoApp.exec(); } else { QCoreApplication app(argc, argv); VideoReceiverApp videoApp(app, false); return videoApp.exec(); } }