497 lines
12 KiB
C++
497 lines
12 KiB
C++
#include <QGuiApplication>
|
|
#include <QQmlApplicationEngine>
|
|
|
|
#include <QQuickWindow>
|
|
#include <QQuickItem>
|
|
#include <QRunnable>
|
|
#include <QCommandLineParser>
|
|
#include <QTimer>
|
|
|
|
#include <gst/gst.h>
|
|
|
|
#include "QGCLoggingCategory.h"
|
|
|
|
QGC_LOGGING_CATEGORY(AppLog, "VideoReceiverApp")
|
|
|
|
#if defined(__android__)
|
|
#include <QtAndroidExtras>
|
|
|
|
#include <jni.h>
|
|
|
|
#include <android/log.h>
|
|
|
|
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<void *>(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<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
|
|
return -1;
|
|
}
|
|
|
|
setNativeMethods();
|
|
|
|
gst_amc_jni_set_java_vm(vm);
|
|
|
|
return JNI_VERSION_1_6;
|
|
}
|
|
#endif
|
|
|
|
#include <GStreamer.h>
|
|
#include <VideoReceiver.h>
|
|
|
|
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<void()> 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<QQuickWindow*>(engine.rootObjects().first());
|
|
Q_ASSERT(_window != nullptr);
|
|
|
|
_widget = _window->findChild<QQuickItem*>("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<VideoReceiver::FILE_FORMAT>(_fileFormat));
|
|
|
|
if (_stopRecordingAfter > 0) {
|
|
unsigned connect = _connect;
|
|
QTimer::singleShot(_stopRecordingAfter * 1000, [this, connect](){
|
|
if (connect != _connect) {
|
|
return;
|
|
}
|
|
_receiver->stopRecording();
|
|
});
|
|
}
|
|
}
|
|
|
|
void
|
|
VideoReceiverApp::_dispatch(std::function<void()> 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();
|
|
}
|
|
}
|