blob: 96b541e7cdc12b7677fae8b8a82b975eebda6ee7 [file] [log] [blame]
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001/*
2 * Copyright (C) 2004-2015 Savoir-Faire Linux Inc.
3 * Author: Sebastien Bourdelin <sebastien.bourdelin@savoirfairelinux.com>
4 * Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 *
20 * Additional permission under GNU GPL version 3 section 7:
21 *
22 * If you modify this program, or any covered work, by linking or
23 * combining it with the OpenSSL project's OpenSSL library (or a
24 * modified version of that library), containing parts covered by the
25 * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
26 * grants you additional permission to convey the resulting work.
27 * Corresponding Source for a non-source form of such a combination
28 * shall include the source code for the parts of OpenSSL used as well
29 * as that of the covered work.
30 */
31
32#include "video_widget.h"
33
34#include <clutter/clutter.h>
35#include <clutter-gtk/clutter-gtk.h>
36#include <video/renderer.h>
37
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040038#define VIDEO_LOCAL_SIZE 150
39#define VIDEO_LOCAL_OPACITY_DEFAULT 150 /* out of 255 */
40
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -040041/* check video frame queues at this rate;
42 * use 30 ms (about 30 fps) since we don't expect to
43 * receive video frames faster than that */
44#define FRAME_RATE_PERIOD 30
45
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050046struct _VideoWidgetClass {
47 GtkBinClass parent_class;
48};
49
50struct _VideoWidget {
51 GtkBin parent;
52};
53
54typedef struct _VideoWidgetPrivate VideoWidgetPrivate;
55
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040056typedef struct _VideoWidgetRenderer VideoWidgetRenderer;
57
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050058struct _VideoWidgetPrivate {
59 GtkWidget *clutter_widget;
60 ClutterActor *stage;
61 ClutterActor *video_container;
62
63 /* remote peer data */
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040064 VideoWidgetRenderer *remote;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050065
66 /* local peer data */
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040067 VideoWidgetRenderer *local;
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -040068
69 guint frame_timeout_source;
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040070};
71
72struct _VideoWidgetRenderer {
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -040073 GAsyncQueue *frame_queue;
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040074 ClutterActor *actor;
75 Video::Renderer *renderer;
76 QMetaObject::Connection frame_update;
77 QMetaObject::Connection render_stop;
78 QMetaObject::Connection render_start;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050079};
80
81G_DEFINE_TYPE_WITH_PRIVATE(VideoWidget, video_widget, GTK_TYPE_BIN);
82
83#define VIDEO_WIDGET_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), VIDEO_WIDGET_TYPE, VideoWidgetPrivate))
84
85typedef struct _FrameInfo
86{
87 ClutterActor *image_actor;
88 ClutterContent *image;
89 guchar *data;
90 gint data_size;
91 gint width;
92 gint height;
93} FrameInfo;
94
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -040095/* static prototypes */
96static FrameInfo *prepare_framedata(Video::Renderer *renderer, ClutterActor* image_actor);
97static void free_framedata(gpointer data);
98static void clutter_render_image(FrameInfo *frame);
99static gboolean check_frame_queue(VideoWidget *self);
100static void renderer_stop(VideoWidgetRenderer *renderer);
101static void renderer_start(VideoWidgetRenderer *renderer);
102static void video_widget_set_renderer(VideoWidgetRenderer *renderer);
103
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500104/*
105 * video_widget_dispose()
106 *
107 * The dispose function for the video_widget class.
108 */
109static void
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400110video_widget_dispose(GObject *object)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500111{
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400112 VideoWidget *self = VIDEO_WIDGET(object);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500113 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
114
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400115 QObject::disconnect(priv->remote->frame_update);
116 QObject::disconnect(priv->remote->render_stop);
117 QObject::disconnect(priv->remote->render_start);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500118
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400119 QObject::disconnect(priv->local->frame_update);
120 QObject::disconnect(priv->local->render_stop);
121 QObject::disconnect(priv->local->render_start);
122
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400123 g_source_remove(priv->frame_timeout_source);
124
125 g_async_queue_unref(priv->remote->frame_queue);
126 g_async_queue_unref(priv->local->frame_queue);
127
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400128 G_OBJECT_CLASS(video_widget_parent_class)->dispose(object);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500129}
130
131
132/*
133 * video_widget_finalize()
134 *
135 * The finalize function for the video_widget class.
136 */
137static void
138video_widget_finalize(GObject *object)
139{
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400140 VideoWidget *self = VIDEO_WIDGET(object);
141 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
142
143 g_free(priv->remote);
144 g_free(priv->local);
145
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500146 G_OBJECT_CLASS(video_widget_parent_class)->finalize(object);
147}
148
149
150/*
151 * video_widget_class_init()
152 *
153 * This function init the video_widget_class.
154 */
155static void
156video_widget_class_init(VideoWidgetClass *klass)
157{
158 GObjectClass *object_class = G_OBJECT_CLASS(klass);
159
160 /* override method */
161 object_class->dispose = video_widget_dispose;
162 object_class->finalize = video_widget_finalize;
163}
164
165
166/*
167 * video_widget_init()
168 *
169 * This function init the video_widget.
170 * - init clutter
171 * - init all the widget members
172 */
173static void
174video_widget_init(VideoWidget *self)
175{
176 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
177
178 /* init clutter widget */
179 priv->clutter_widget = gtk_clutter_embed_new();
180 /* add it to the video_widget */
181 gtk_container_add(GTK_CONTAINER(self), priv->clutter_widget);
182 /* get the stage */
183 priv->stage = gtk_clutter_embed_get_stage(GTK_CLUTTER_EMBED(priv->clutter_widget));
184
185 /* layout manager is used to arrange children in space, here we ask clutter
186 * to align children to fill the space when resizing the window */
187 clutter_actor_set_layout_manager(priv->stage,
188 clutter_bin_layout_new(CLUTTER_BIN_ALIGNMENT_FILL, CLUTTER_BIN_ALIGNMENT_FILL));
189
190 /* add a scene container where we can add and remove our actors */
191 priv->video_container = clutter_actor_new();
192 clutter_actor_set_background_color(priv->video_container, CLUTTER_COLOR_Black);
193 clutter_actor_add_child(priv->stage, priv->video_container);
194
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400195 /* init the remote and local structs */
196 priv->remote = g_new0(VideoWidgetRenderer, 1);
197 priv->local = g_new0(VideoWidgetRenderer, 1);
198
199 /* arrange remote actors */
200 priv->remote->actor = clutter_actor_new();
201 clutter_actor_insert_child_below(priv->video_container, priv->remote->actor, NULL);
202 /* the remote camera must always fill the container size */
203 ClutterConstraint *constraint = clutter_bind_constraint_new(priv->video_container,
204 CLUTTER_BIND_SIZE, 0);
205 clutter_actor_add_constraint(priv->remote->actor, constraint);
206
207 /* arrange local actor */
208 priv->local->actor = clutter_actor_new();
209 clutter_actor_insert_child_above(priv->video_container, priv->local->actor, NULL);
210 /* set size to square, but it will stay the aspect ratio when the image is rendered */
211 clutter_actor_set_size(priv->local->actor, VIDEO_LOCAL_SIZE, VIDEO_LOCAL_SIZE);
212 /* set position constraint to right cornder */
213 constraint = clutter_align_constraint_new(priv->video_container,
214 CLUTTER_ALIGN_BOTH, 0.99);
215 clutter_actor_add_constraint(priv->local->actor, constraint);
216 clutter_actor_set_opacity(priv->local->actor,
217 VIDEO_LOCAL_OPACITY_DEFAULT);
218
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400219 /* init frame queues and the timeout sources to check them */
220 priv->remote->frame_queue = g_async_queue_new_full((GDestroyNotify)free_framedata);
221 priv->local->frame_queue = g_async_queue_new_full((GDestroyNotify)free_framedata);
222 priv->frame_timeout_source = g_timeout_add(FRAME_RATE_PERIOD, (GSourceFunc)check_frame_queue, self);
223
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500224 /* TODO: handle button event in screen */
225 // g_signal_connect(screen, "button-press-event",
226 // G_CALLBACK(on_button_press_in_screen_event_cb),
227 // self);
228
229 /* TODO: init drag & drop images */
230 // gtk_drag_dest_set(GTK_WIDGET(self), GTK_DEST_DEFAULT_ALL, NULL, 0, (GdkDragAction)(GDK_ACTION_COPY | GDK_ACTION_PRIVATE));
231 // gtk_drag_dest_add_uri_targets(GTK_WIDGET(self));
232 // g_signal_connect(GTK_WIDGET(self), "drag-data-received",
233 // G_CALLBACK(on_drag_data_received_cb), NULL);
234}
235
236static FrameInfo *
Stepan Salenikovichcd1bf632015-03-09 16:24:08 -0400237prepare_framedata(Video::Renderer *renderer, ClutterActor* image_actor)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500238{
239 const QByteArray& data = renderer->currentFrame();
240 QSize res = renderer->size();
241
242 /* copy frame data */
243 gpointer frame_data = g_memdup((gconstpointer)data.constData(), data.size());
244
245 FrameInfo *frame = g_new0(FrameInfo, 1);
246
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500247 frame->image_actor = image_actor;
248 frame->data = (guchar *)frame_data;
249 frame->data_size = data.size();
250 frame->width = res.width();
251 frame->height = res.height();
252
253 return frame;
254}
255
256static void
257free_framedata(gpointer data)
258{
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400259 if (data == NULL) return;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500260 FrameInfo *frame = (FrameInfo *)data;
261 g_free(frame->data);
262 g_free(frame);
263}
264
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400265static void
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500266clutter_render_image(FrameInfo *frame)
267{
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400268 if (frame == NULL) return;
269 g_return_if_fail(CLUTTER_IS_ACTOR(frame->image_actor));
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500270
Stepan Salenikovichcd1bf632015-03-09 16:24:08 -0400271 ClutterContent * image_new = clutter_image_new();
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500272
273 const gint BPP = 4;
274 const gint ROW_STRIDE = BPP * frame->width;
275
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500276 GError *error = NULL;
277 clutter_image_set_data(
278 CLUTTER_IMAGE(image_new),
279 frame->data,
280 COGL_PIXEL_FORMAT_BGRA_8888,
281 frame->width,
282 frame->height,
283 ROW_STRIDE,
284 &error);
285 if (error) {
286 g_warning("error rendering image to clutter: %s", error->message);
287 g_error_free(error);
288 }
289
Stepan Salenikovichcd1bf632015-03-09 16:24:08 -0400290 clutter_actor_set_content(frame->image_actor, image_new);
291 g_object_unref (image_new);
292 /* note: we must set the content gravity be "resize aspect" after setting the image data to make sure
293 * that the aspect ratio is correct
294 */
295 clutter_actor_set_content_gravity(frame->image_actor, CLUTTER_CONTENT_GRAVITY_RESIZE_ASPECT);
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400296}
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500297
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400298static gboolean
299check_frame_queue(VideoWidget *self)
300{
301 g_return_val_if_fail(IS_VIDEO_WIDGET(self), FALSE);
302 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
303
304 /* get the latest frame in the queue */
305 gpointer local_data_last = NULL;
306 gpointer local_data_next = g_async_queue_try_pop(priv->local->frame_queue);
307 while(local_data_next != NULL) {
308 // if (local_data_last != NULL) g_debug("skipping local frame");
309 /* make sure to free the frame we're skipping */
310 free_framedata(local_data_last);
311 local_data_last = local_data_next;
312 local_data_next = g_async_queue_try_pop(priv->local->frame_queue);
313 }
314
315 /* display the frame */
316 clutter_render_image((FrameInfo *)local_data_last);
317 free_framedata(local_data_last);
318
319 /* get the latest frame in the queue */
320 gpointer remote_data_last = NULL;
321 gpointer remote_data_next = g_async_queue_try_pop(priv->remote->frame_queue);
322 while(remote_data_next != NULL) {
323 // if (remote_data_last != NULL) g_debug("skipping remote frame");
324 /* make sure to free the frame we're skipping */
325 free_framedata(remote_data_last);
326 remote_data_last = remote_data_next;
327 remote_data_next = g_async_queue_try_pop(priv->remote->frame_queue);
328 }
329
330 /* display the frame */
331 clutter_render_image((FrameInfo *)remote_data_last);
332 free_framedata(remote_data_last);
333
334 return TRUE; /* keep going */
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500335}
336
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400337static void
338renderer_stop(VideoWidgetRenderer *renderer)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500339{
Stepan Salenikovichbfe9ac62015-03-11 12:49:20 -0400340 g_return_if_fail(CLUTTER_IS_ACTOR(renderer->actor));
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400341 QObject::disconnect(renderer->frame_update);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500342}
343
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400344static void
345renderer_start(VideoWidgetRenderer *renderer)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500346{
Stepan Salenikovichbfe9ac62015-03-11 12:49:20 -0400347 g_return_if_fail(CLUTTER_IS_ACTOR(renderer->actor));
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400348 renderer->frame_update = QObject::connect(
349 renderer->renderer,
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500350 &Video::Renderer::frameUpdated,
351 [=]() {
352
353 /* this callback comes from another thread;
354 * rendering must be done in the main loop;
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400355 * copy the frame and add it to the frame queue
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500356 */
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400357 FrameInfo *frame = prepare_framedata(renderer->renderer,
358 renderer->actor);
Stepan Salenikovich5e6a0b72015-03-12 14:55:22 -0400359 g_async_queue_push(renderer->frame_queue, frame);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500360 }
361 );
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500362}
363
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400364static void
365video_widget_set_renderer(VideoWidgetRenderer *renderer)
366{
367 if (renderer == NULL) return;
368
369 /* update the renderer */
370 QObject::disconnect(renderer->frame_update);
371 QObject::disconnect(renderer->render_stop);
372 QObject::disconnect(renderer->render_start);
373
374 renderer_start(renderer);
375
376 renderer->render_stop = QObject::connect(
377 renderer->renderer,
378 &Video::Renderer::stopped,
379 [=]() {
380 renderer_stop(renderer);
381 }
382 );
383
384 renderer->render_start = QObject::connect(
385 renderer->renderer,
386 &Video::Renderer::started,
387 [=]() {
388 renderer_start(renderer);
389 }
390 );
391}
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500392
393/*
394 * video_widget_new()
395 *
396 * The function use to create a new video_widget
397 */
398GtkWidget*
399video_widget_new(void)
400{
401 GtkWidget *self = (GtkWidget *)g_object_new(VIDEO_WIDGET_TYPE, NULL);
402 return self;
403}
404
405void
406video_widget_set_remote_renderer(VideoWidget *self, Video::Renderer *renderer_remote_new)
407{
408 g_return_if_fail(IS_VIDEO_WIDGET(self));
409 if (renderer_remote_new == NULL) return;
410
411 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
412
413 /* update the renderer */
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400414 priv->remote->renderer = renderer_remote_new;
415 video_widget_set_renderer(priv->remote);
416}
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500417
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400418void
419video_widget_set_local_renderer(VideoWidget *self, Video::Renderer *renderer_local_new)
420{
421 g_return_if_fail(IS_VIDEO_WIDGET(self));
422 if (renderer_local_new == NULL) return;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500423
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400424 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500425
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400426 /* update the renderer */
427 priv->local->renderer = renderer_local_new;
428 video_widget_set_renderer(priv->local);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500429}