summaryrefslogtreecommitdiffstats
path: root/app/main.cpp
diff options
context:
space:
mode:
authorMarius Vlad <marius.vlad@collabora.com>2020-07-14 17:46:10 +0300
committerMarius Vlad <marius.vlad@collabora.com>2020-07-14 19:19:23 +0300
commit9eb4b3fb1fec65c411a4e0257cc63ed4a15425fa (patch)
tree88c3ad89ceb84988cdff3129984c9c80f547f5ba /app/main.cpp
parent26799d1b074f31ca703aea196c98374e5eb3bf04 (diff)
Migrate qt cluster-receiver to its own repositoryjellyfish_9.99.2jellyfish/9.99.29.99.2
This has the run-by-default already removed. Signed-off-by: Marius Vlad <marius.vlad@collabora.com> Change-Id: Id0a102812d588777ff8641a6961532afd26cb440
Diffstat (limited to 'app/main.cpp')
-rw-r--r--app/main.cpp264
1 files changed, 264 insertions, 0 deletions
diff --git a/app/main.cpp b/app/main.cpp
new file mode 100644
index 0000000..b68aeac
--- /dev/null
+++ b/app/main.cpp
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2018 Konsulko Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <QGuiApplication>
+#include <QApplication>
+#include <QtCore/QDebug>
+#include <QtCore/QCommandLineParser>
+#include <QtCore/QUrlQuery>
+#include <QtGui/QGuiApplication>
+#include <QtQml/QQmlApplicationEngine>
+#include <QtQml/QQmlContext>
+#include <QtQml/qqml.h>
+#include <QQuickWindow>
+#include <QtQuickControls2/QQuickStyle>
+#include <QtGui/QGuiApplication>
+#include <qpa/qplatformnativeinterface.h>
+#include <QTimer>
+
+#define GST_USE_UNSTABLE_API
+
+#include <QWidget>
+
+#include <string>
+#include <iostream>
+#include <cstring>
+
+#include <gst/gst.h>
+
+#include <gst/video/videooverlay.h>
+#include <gst/wayland/wayland.h>
+
+#include "surface.hpp"
+
+#define WINDOW_WIDTH_SIZE 640
+#define WINDOW_HEIGHT_SIZE 720
+
+#define WINDOW_WIDTH_POS_X 640
+#define WINDOW_WIDTH_POS_Y 180
+
+struct cluster_window_data {
+ int x; int y;
+ int width; int height;
+};
+
+struct cluster_receiver_data {
+ QWidget *widget;
+ QPlatformNativeInterface *native;
+
+ GstElement *pipeline;
+ GstWaylandVideo *wl_video;
+ GstVideoOverlay *overlay;
+
+ bool widget_has_buffer_mapped;
+ struct cluster_window_data window_data;
+};
+
+static struct wl_surface *
+getWlSurface(QPlatformNativeInterface *native, QWindow *window)
+{
+ void *surf = native->nativeResourceForWindow("surface", window);
+ return static_cast<struct ::wl_surface *>(surf);
+}
+
+
+static void
+error_cb(GstBus *bus, GstMessage *msg, gpointer user_data)
+{
+ struct cluster_receiver_data *d = static_cast<struct cluster_receiver_data *>(user_data);
+
+ gchar *debug = NULL;
+ GError *err = NULL;
+
+ gst_message_parse_error(msg, &err, &debug);
+
+ g_print("Error: %s\n", err->message);
+ g_error_free(err);
+
+ if (debug) {
+ g_print("Debug details: %s\n", debug);
+ g_free(debug);
+ }
+
+ gst_element_set_state(d->pipeline, GST_STATE_NULL);
+}
+
+static GstBusSyncReply
+bus_sync_handler(GstBus *bus, GstMessage *message, gpointer user_data)
+{
+ struct cluster_receiver_data *d = static_cast<struct cluster_receiver_data *>(user_data);
+
+ if (gst_is_wayland_display_handle_need_context_message(message)) {
+ GstContext *context;
+ struct wl_display *display_handle;
+
+ display_handle =
+ static_cast<struct wl_display *>(
+ d->native->nativeResourceForIntegration("display")
+ );
+
+ context = gst_wayland_display_handle_context_new(display_handle);
+
+ d->wl_video = GST_WAYLAND_VIDEO(GST_MESSAGE_SRC(message));
+ gst_element_set_context(GST_ELEMENT(GST_MESSAGE_SRC(message)), context);
+
+ goto drop;
+ } else if (gst_is_video_overlay_prepare_window_handle_message(message)) {
+ struct wl_surface *window_handle;
+ QWindow *win = d->widget->windowHandle();
+
+ if (!d->widget_has_buffer_mapped) {
+ d->widget->setVisible(true);
+ d->widget_has_buffer_mapped = true;
+ }
+
+ /* GST_MESSAGE_SRC(message) will be the overlay object that we
+ * have to use. This may be waylandsink, but it may also be
+ * playbin. In the latter case, we must make sure to use
+ * playbin instead of waylandsink, because playbin resets the
+ * window handle and render_rectangle after restarting playback
+ * and the actual window size is lost */
+ d->overlay = GST_VIDEO_OVERLAY(GST_MESSAGE_SRC(message));
+ window_handle = getWlSurface(d->native, win);
+
+ g_print("setting window handle and size (%d x %d) w %d, h %d\n",
+ d->window_data.x, d->window_data.y,
+ d->window_data.width, d->window_data.height);
+
+ gst_video_overlay_set_window_handle(d->overlay, (guintptr) window_handle);
+ gst_video_overlay_set_render_rectangle(d->overlay,
+ d->window_data.x, d->window_data.y,
+ d->window_data.width, d->window_data.height);
+
+ goto drop;
+ }
+
+ return GST_BUS_PASS;
+
+drop:
+ gst_message_unref(message);
+ return GST_BUS_DROP;
+}
+
+int main(int argc, char *argv[])
+{
+ int port = 0;
+ std::string token;
+ std::string role = "receiver";
+ QString my_role = "receiver";
+
+ struct cluster_receiver_data receiver_data = {};
+ struct cluster_window_data window_data = {};
+
+ window_data.x = 0;
+ window_data.y = 0;
+
+ window_data.width = WINDOW_WIDTH_SIZE;
+ window_data.height = WINDOW_HEIGHT_SIZE;
+
+ receiver_data.window_data = window_data;
+
+ int gargc = 2;
+ char **gargv = (char**) malloc(2 * sizeof(char*));
+ gargv[0] = strdup(argv[0]);
+ gargv[1] = strdup("--gst-debug-level=2");
+
+ try {
+ port = std::stol(argv[1]);
+ token = argv[2];
+ } catch (const std::invalid_argument& e) {
+ std::cerr << "Invalid argument" << std::endl;
+ exit(1);
+ } catch (const std::out_of_range& e) {
+ std::cerr << "Port out of range" << std::endl;
+ exit(1);
+ }
+
+ std::string pipeline_str = \
+ "rtpbin name=rtpbin udpsrc "
+ "caps=\"application/x-rtp,media=(string)video,clock-rate=(int)90000,encoding-name=JPEG,payload=26\" "
+ "port=5005 ! rtpbin.recv_rtp_sink_0 rtpbin. ! "
+ "rtpjpegdepay ! jpegdec ! waylandsink";
+
+ gst_init(&gargc, &gargv);
+
+ std::cout << "Using pipeline: " << pipeline_str << std::endl;
+
+ QApplication app(argc, argv);
+ QPlatformNativeInterface *native = qApp->platformNativeInterface();
+ receiver_data.native = native;
+
+ // mark the application id -> my_role
+ app.setDesktopFileName(my_role);
+
+ SurfaceHandler handler(port, token, role);
+
+ // we already have the app_id -> role this will set-up the x and y
+ // position and a bounding box that will be used to clip out the
+ // surface. Note, that in this example, the surface area is the same as
+ // the bounding box
+ handler.set_bounding_box(WINDOW_WIDTH_POS_X, WINDOW_WIDTH_POS_Y,
+ 0, 0, WINDOW_WIDTH_SIZE, WINDOW_HEIGHT_SIZE);
+
+ QWidget *widget = new QWidget;
+ widget->resize(WINDOW_WIDTH_SIZE,
+ WINDOW_HEIGHT_SIZE);
+ widget->setVisible(false);
+ // this is necessary to trigger a buffer attach on the xdg surface;
+ // waylandsink will use that surface to create a sub-surface and will
+ // perform a commit on the parent one; without doing a show() here
+ // QtWayland will not attach the buffer to the surface, and the parent
+ // surface will not be 'committed'; most likely this require more
+ // fiddling as to keep the buffer attached but not displayed or use
+ // some kind of local alpha to display that cluster is showing
+ widget->show();
+
+ receiver_data.widget = widget;
+ receiver_data.widget_has_buffer_mapped = true;
+
+ GstElement *pipeline = gst_parse_launch(pipeline_str.c_str(), NULL);
+ if (!pipeline) {
+ std::cerr << "gstreamer pipeline construction failed!" << std::endl;
+ exit(1);
+ }
+ receiver_data.pipeline = pipeline;
+
+ GstBus *bus = gst_element_get_bus(pipeline);
+ gst_bus_add_signal_watch(bus);
+
+ g_signal_connect(bus, "message::error", G_CALLBACK(error_cb), &receiver_data);
+ gst_bus_set_sync_handler(bus, bus_sync_handler, &receiver_data, NULL);
+ gst_object_unref(bus);
+
+ // Start the pipeline, giving Qt the time to create everything; not
+ // waiting here will cause both Qt and Gstreamer to race, running as
+ // different threads. So this gives Qt sufficient time to create the
+ // Widget, the surface the buffer and attach it to the surface. The
+ // drawback is that it will displayed until the remote side start
+ // sending frames to decode.
+ std::cout << "wainting for Qt to be ready..." << std::endl;
+ QTimer::singleShot(500, [pipeline](){
+ gst_element_set_state(pipeline, GST_STATE_PLAYING);
+ std::cout << "gstreamer pipeline running" << std::endl;
+ });
+
+ // run the application
+ int ret = app.exec();
+ widget->hide();
+
+ gst_element_set_state(pipeline, GST_STATE_NULL);
+ gst_object_unref(pipeline);
+ return ret;
+}