blob: 411c91212ae7b3ea698cb638ca97a396b9f85182 [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 Salenikovich36c025c2015-03-03 19:06:44 -050041struct _VideoWidgetClass {
42 GtkBinClass parent_class;
43};
44
45struct _VideoWidget {
46 GtkBin parent;
47};
48
49typedef struct _VideoWidgetPrivate VideoWidgetPrivate;
50
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040051typedef struct _VideoWidgetRenderer VideoWidgetRenderer;
52
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050053struct _VideoWidgetPrivate {
54 GtkWidget *clutter_widget;
55 ClutterActor *stage;
56 ClutterActor *video_container;
57
58 /* remote peer data */
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040059 VideoWidgetRenderer *remote;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050060
61 /* local peer data */
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040062 VideoWidgetRenderer *local;
63};
64
65struct _VideoWidgetRenderer {
66 gboolean show;
67 ClutterActor *actor;
68 Video::Renderer *renderer;
69 QMetaObject::Connection frame_update;
70 QMetaObject::Connection render_stop;
71 QMetaObject::Connection render_start;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050072};
73
74G_DEFINE_TYPE_WITH_PRIVATE(VideoWidget, video_widget, GTK_TYPE_BIN);
75
76#define VIDEO_WIDGET_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), VIDEO_WIDGET_TYPE, VideoWidgetPrivate))
77
78typedef struct _FrameInfo
79{
80 ClutterActor *image_actor;
81 ClutterContent *image;
82 guchar *data;
83 gint data_size;
84 gint width;
85 gint height;
86} FrameInfo;
87
88/*
89 * video_widget_dispose()
90 *
91 * The dispose function for the video_widget class.
92 */
93static void
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040094video_widget_dispose(GObject *object)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050095{
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040096 VideoWidget *self = VIDEO_WIDGET(object);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050097 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
98
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040099 QObject::disconnect(priv->remote->frame_update);
100 QObject::disconnect(priv->remote->render_stop);
101 QObject::disconnect(priv->remote->render_start);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500102
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400103 QObject::disconnect(priv->local->frame_update);
104 QObject::disconnect(priv->local->render_stop);
105 QObject::disconnect(priv->local->render_start);
106
107 G_OBJECT_CLASS(video_widget_parent_class)->dispose(object);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500108}
109
110
111/*
112 * video_widget_finalize()
113 *
114 * The finalize function for the video_widget class.
115 */
116static void
117video_widget_finalize(GObject *object)
118{
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400119 VideoWidget *self = VIDEO_WIDGET(object);
120 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
121
122 g_free(priv->remote);
123 g_free(priv->local);
124
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500125 G_OBJECT_CLASS(video_widget_parent_class)->finalize(object);
126}
127
128
129/*
130 * video_widget_class_init()
131 *
132 * This function init the video_widget_class.
133 */
134static void
135video_widget_class_init(VideoWidgetClass *klass)
136{
137 GObjectClass *object_class = G_OBJECT_CLASS(klass);
138
139 /* override method */
140 object_class->dispose = video_widget_dispose;
141 object_class->finalize = video_widget_finalize;
142}
143
144
145/*
146 * video_widget_init()
147 *
148 * This function init the video_widget.
149 * - init clutter
150 * - init all the widget members
151 */
152static void
153video_widget_init(VideoWidget *self)
154{
155 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
156
157 /* init clutter widget */
158 priv->clutter_widget = gtk_clutter_embed_new();
159 /* add it to the video_widget */
160 gtk_container_add(GTK_CONTAINER(self), priv->clutter_widget);
161 /* get the stage */
162 priv->stage = gtk_clutter_embed_get_stage(GTK_CLUTTER_EMBED(priv->clutter_widget));
163
164 /* layout manager is used to arrange children in space, here we ask clutter
165 * to align children to fill the space when resizing the window */
166 clutter_actor_set_layout_manager(priv->stage,
167 clutter_bin_layout_new(CLUTTER_BIN_ALIGNMENT_FILL, CLUTTER_BIN_ALIGNMENT_FILL));
168
169 /* add a scene container where we can add and remove our actors */
170 priv->video_container = clutter_actor_new();
171 clutter_actor_set_background_color(priv->video_container, CLUTTER_COLOR_Black);
172 clutter_actor_add_child(priv->stage, priv->video_container);
173
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400174 /* init the remote and local structs */
175 priv->remote = g_new0(VideoWidgetRenderer, 1);
176 priv->local = g_new0(VideoWidgetRenderer, 1);
177
178 /* arrange remote actors */
179 priv->remote->actor = clutter_actor_new();
180 clutter_actor_insert_child_below(priv->video_container, priv->remote->actor, NULL);
181 /* the remote camera must always fill the container size */
182 ClutterConstraint *constraint = clutter_bind_constraint_new(priv->video_container,
183 CLUTTER_BIND_SIZE, 0);
184 clutter_actor_add_constraint(priv->remote->actor, constraint);
185
186 /* arrange local actor */
187 priv->local->actor = clutter_actor_new();
188 clutter_actor_insert_child_above(priv->video_container, priv->local->actor, NULL);
189 /* set size to square, but it will stay the aspect ratio when the image is rendered */
190 clutter_actor_set_size(priv->local->actor, VIDEO_LOCAL_SIZE, VIDEO_LOCAL_SIZE);
191 /* set position constraint to right cornder */
192 constraint = clutter_align_constraint_new(priv->video_container,
193 CLUTTER_ALIGN_BOTH, 0.99);
194 clutter_actor_add_constraint(priv->local->actor, constraint);
195 clutter_actor_set_opacity(priv->local->actor,
196 VIDEO_LOCAL_OPACITY_DEFAULT);
197
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500198 /* TODO: handle button event in screen */
199 // g_signal_connect(screen, "button-press-event",
200 // G_CALLBACK(on_button_press_in_screen_event_cb),
201 // self);
202
203 /* TODO: init drag & drop images */
204 // gtk_drag_dest_set(GTK_WIDGET(self), GTK_DEST_DEFAULT_ALL, NULL, 0, (GdkDragAction)(GDK_ACTION_COPY | GDK_ACTION_PRIVATE));
205 // gtk_drag_dest_add_uri_targets(GTK_WIDGET(self));
206 // g_signal_connect(GTK_WIDGET(self), "drag-data-received",
207 // G_CALLBACK(on_drag_data_received_cb), NULL);
208}
209
210static FrameInfo *
Stepan Salenikovichcd1bf632015-03-09 16:24:08 -0400211prepare_framedata(Video::Renderer *renderer, ClutterActor* image_actor)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500212{
213 const QByteArray& data = renderer->currentFrame();
214 QSize res = renderer->size();
215
216 /* copy frame data */
217 gpointer frame_data = g_memdup((gconstpointer)data.constData(), data.size());
218
219 FrameInfo *frame = g_new0(FrameInfo, 1);
220
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500221 frame->image_actor = image_actor;
222 frame->data = (guchar *)frame_data;
223 frame->data_size = data.size();
224 frame->width = res.width();
225 frame->height = res.height();
226
227 return frame;
228}
229
230static void
231free_framedata(gpointer data)
232{
233 FrameInfo *frame = (FrameInfo *)data;
234 g_free(frame->data);
235 g_free(frame);
236}
237
238static gboolean
239clutter_render_image(FrameInfo *frame)
240{
241 g_return_val_if_fail(CLUTTER_IS_ACTOR(frame->image_actor), FALSE);
242
Stepan Salenikovichcd1bf632015-03-09 16:24:08 -0400243 ClutterContent * image_new = clutter_image_new();
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500244
245 const gint BPP = 4;
246 const gint ROW_STRIDE = BPP * frame->width;
247
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500248 GError *error = NULL;
249 clutter_image_set_data(
250 CLUTTER_IMAGE(image_new),
251 frame->data,
252 COGL_PIXEL_FORMAT_BGRA_8888,
253 frame->width,
254 frame->height,
255 ROW_STRIDE,
256 &error);
257 if (error) {
258 g_warning("error rendering image to clutter: %s", error->message);
259 g_error_free(error);
260 }
261
Stepan Salenikovichcd1bf632015-03-09 16:24:08 -0400262 clutter_actor_set_content(frame->image_actor, image_new);
263 g_object_unref (image_new);
264 /* note: we must set the content gravity be "resize aspect" after setting the image data to make sure
265 * that the aspect ratio is correct
266 */
267 clutter_actor_set_content_gravity(frame->image_actor, CLUTTER_CONTENT_GRAVITY_RESIZE_ASPECT);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500268
269 return FALSE; /* we do not want this function to be called again */
270}
271
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400272static void
273renderer_stop(VideoWidgetRenderer *renderer)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500274{
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400275 QObject::disconnect(renderer->frame_update);
276 renderer->show = FALSE;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500277}
278
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400279static void
280renderer_start(VideoWidgetRenderer *renderer)
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500281{
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400282 renderer->show = TRUE;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500283
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400284 renderer->frame_update = QObject::connect(
285 renderer->renderer,
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500286 &Video::Renderer::frameUpdated,
287 [=]() {
288
289 /* this callback comes from another thread;
290 * rendering must be done in the main loop;
291 * copy the frame and add it as a task on the main loop
292 */
293
294 /* for now use the video container for the remote image */
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400295 FrameInfo *frame = prepare_framedata(renderer->renderer,
296 renderer->actor);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500297
298 g_idle_add_full(G_PRIORITY_HIGH_IDLE,
299 (GSourceFunc)clutter_render_image,
300 frame,
301 (GDestroyNotify)free_framedata);
302 }
303 );
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500304}
305
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400306static void
307video_widget_set_renderer(VideoWidgetRenderer *renderer)
308{
309 if (renderer == NULL) return;
310
311 /* update the renderer */
312 QObject::disconnect(renderer->frame_update);
313 QObject::disconnect(renderer->render_stop);
314 QObject::disconnect(renderer->render_start);
315
316 renderer_start(renderer);
317
318 renderer->render_stop = QObject::connect(
319 renderer->renderer,
320 &Video::Renderer::stopped,
321 [=]() {
322 renderer_stop(renderer);
323 }
324 );
325
326 renderer->render_start = QObject::connect(
327 renderer->renderer,
328 &Video::Renderer::started,
329 [=]() {
330 renderer_start(renderer);
331 }
332 );
333}
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500334
335/*
336 * video_widget_new()
337 *
338 * The function use to create a new video_widget
339 */
340GtkWidget*
341video_widget_new(void)
342{
343 GtkWidget *self = (GtkWidget *)g_object_new(VIDEO_WIDGET_TYPE, NULL);
344 return self;
345}
346
347void
348video_widget_set_remote_renderer(VideoWidget *self, Video::Renderer *renderer_remote_new)
349{
350 g_return_if_fail(IS_VIDEO_WIDGET(self));
351 if (renderer_remote_new == NULL) return;
352
353 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
354
355 /* update the renderer */
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400356 priv->remote->renderer = renderer_remote_new;
357 video_widget_set_renderer(priv->remote);
358}
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500359
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400360void
361video_widget_set_local_renderer(VideoWidget *self, Video::Renderer *renderer_local_new)
362{
363 g_return_if_fail(IS_VIDEO_WIDGET(self));
364 if (renderer_local_new == NULL) return;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500365
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400366 VideoWidgetPrivate *priv = VIDEO_WIDGET_GET_PRIVATE(self);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500367
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400368 /* update the renderer */
369 priv->local->renderer = renderer_local_new;
370 video_widget_set_renderer(priv->local);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500371}