diff options
author | Marius Vlad <marius.vlad@collabora.com> | 2020-06-20 23:16:15 +0300 |
---|---|---|
committer | Marius Vlad <marius.vlad@collabora.com> | 2020-07-10 00:35:28 +0300 |
commit | 3ac1a25bcfa27594ffd44f5255017322d517cb40 (patch) | |
tree | 030c89af09347bf65cef230fb54318771b201f33 /app/main.cpp | |
parent | 6f05f0d2fb3619e85bee71a18c353908edf8bfe3 (diff) |
Conversion to using agl-compositor
- Use Qt/QtWayland to set-up an app_id for the main surface. We use that
surface and pass it to waylandsink. Waylandsink create a sub-surface
with the parent the Qt/QtWayland one.
- Install a handler for handling bus messages With it, it allows to pass
the wl_display with the help of a context and the surface backing up
the Widget.
- Installs a timer to wait for Qt to create/get the xdg_surface and
attach a buffer to it. Without we won't be able receive the commit
for the parent event in order to handle it.
Bug-AGL: SPEC-3382
Signed-off-by: Marius Vlad <marius.vlad@collabora.com>
Change-Id: I4237b7dad110ce36e3a01226e67cb508b6d9a6d9
Diffstat (limited to 'app/main.cpp')
-rw-r--r-- | app/main.cpp | 241 |
1 files changed, 210 insertions, 31 deletions
diff --git a/app/main.cpp b/app/main.cpp index 0494b5f..796bcdb 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -13,20 +13,167 @@ * 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" -#undef DEBUG +#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]); @@ -53,54 +200,86 @@ int main(int argc, char *argv[]) // 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 ! "; - GstPlugin *plugin = gst_registry_find_plugin(gst_registry_get (), "vaapisink"); - if(plugin) { + "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 ! "; + //"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"; + GstPlugin *plugin = gst_registry_find_plugin(gst_registry_get(), "vaapisink"); + + if (plugin) { + std::cout << "Using va api sink" << std::endl; pipeline_str += "vaapisink"; gst_object_unref(plugin); } else { + std::cout << "Using wayland sink" << std::endl; pipeline_str += "waylandsink"; } -#ifdef DEBUG - std::cout << "Using pipeline: " << pipeline_str << std::endl; -#endif - // Initialize GStreamer -#ifdef DEBUG - int gargc = 2; - char **gargv = (char**) malloc(2 * sizeof(char*)); - gargv[0] = strdup(argv[0]); - gargv[1] = strdup("--gst-debug-level=2"); gst_init(&gargc, &gargv); -#else - gst_init(NULL, NULL); -#endif - // Create our IVI surface handler + 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) { + if (!pipeline) { std::cerr << "gstreamer pipeline construction failed!" << std::endl; exit(1); } + receiver_data.pipeline = pipeline; - // Start pipeline - gst_element_set_state(pipeline, GST_STATE_PLAYING); - std::cout << "gstreamer pipeline running" << std::endl; - - // Wait until error or EOS GstBus *bus = gst_element_get_bus(pipeline); - GstMessage *msg = gst_bus_timed_pop_filtered(bus, - GST_CLOCK_TIME_NONE, - (GstMessageType) (GST_MESSAGE_ERROR | GST_MESSAGE_EOS)); + gst_bus_add_signal_watch(bus); - // Free resources - if(msg != NULL) - gst_message_unref(msg); + 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 0; + return ret; } |