diff options
author | Marius Vlad <marius.vlad@collabora.com> | 2020-07-14 17:46:10 +0300 |
---|---|---|
committer | Marius Vlad <marius.vlad@collabora.com> | 2020-07-14 19:19:23 +0300 |
commit | 9eb4b3fb1fec65c411a4e0257cc63ed4a15425fa (patch) | |
tree | 88c3ad89ceb84988cdff3129984c9c80f547f5ba /app/main.cpp | |
parent | 26799d1b074f31ca703aea196c98374e5eb3bf04 (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.cpp | 264 |
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; +} |