Commit e3b53937 authored by Rafaël Carré's avatar Rafaël Carré
Browse files

* #6180 #6267

Implement preview widget using cairo
Exchange shared memory keys through DBus
parent b2f72b91
......@@ -18,12 +18,12 @@ libconfig_la_SOURCES = \
libconfig_la_LDFLAGS = $(DBUSGLIB_LDFLAGS) $(LIBNOTIFY_LDFLAGS) \
$(SFLGTK_LDFLAGS) $(SFLGLIB_LDFLAGS) $(WEBKIT_LDFLAGS) $(LIBGNOMEUI_LDFLAGS) \
$(GNOMEDOCUTILS_LDFLAGS)
$(GNOMEDOCUTILS_LDFLAGS) @CLUTTER_LIBS@
libconfig_la_LIBADD = $(DBUSGLIB_LIBS) $(LIBNOTIFY_LIBSI) \
$(SFLGTK_LIBS) $(SFLGLIB_LIBS) $(WEBKIT_LIBS) $(LIBGNOMEUI_LIBS) \
$(GNOMEDOCUTILS_LIBS)
$(GNOMEDOCUTILS_LIBS) @CLUTTER_LIBS@
libconfig_la_CFLAGS = $(DBUSGLIB_CFLAGS) $(LIBNOTIFY_CFLAGS) \
$(SFLGTK_CFLAGS) $(SFLGLIB_CFLAGS) $(WEBKIT_CFLAGS) $(LIBGNOMEUI_CFLAGS) \
$(GNOMEDOCUTILS_CFLAGS)
$(GNOMEDOCUTILS_CFLAGS) @CLUTTER_CFLAGS@
......@@ -36,6 +36,8 @@
#include "dbus.h"
#include "video/video_preview.h"
#include <clutter/clutter.h>
static GtkWidget *v4l2Device;
static GtkWidget *v4l2Input;
static GtkWidget *v4l2Size;
......@@ -110,12 +112,21 @@ preview_is_running_cb(GObject *obj, GParamSpec *pspec, gpointer user_data)
static VideoPreview *preview = NULL;
static GtkWidget *preview_button = NULL;
static GtkWidget *drawarea = NULL;
static int drawWidth = 20 * 16;
static int drawHeight = 20 * 9;
static const char *drawFormat;
void
video_started_cb()
video_started_cb(DBusGProxy *proxy, gint OUT_shmId, gint OUT_semId, gint OUT_videoBufferSize, GError *error, gpointer userdata)
{
(void)proxy;
(void)error;
(void)userdata;
DEBUG("Preview started");
if (preview == NULL) {
preview = video_preview_new();
preview = video_preview_new(drawarea, drawWidth, drawHeight, drawFormat, OUT_shmId, OUT_semId, OUT_videoBufferSize);
g_signal_connect (preview, "notify::running",
G_CALLBACK (preview_is_running_cb),
preview_button);
......@@ -138,7 +149,15 @@ preview_button_clicked(GtkButton *button, gpointer data UNUSED)
preview_button = GTK_WIDGET(button);
if (g_strcmp0(gtk_button_get_label(button), "_Start preview") == 0) {
gtk_button_set_label(button, "_Stop preview");
dbus_start_video_preview();
static const char *formats[2] = { "rgb24", "bgra" };
if (clutter_init(NULL, NULL) == CLUTTER_INIT_SUCCESS) {
drawFormat = formats[0];
} else {
drawFormat = formats[1];
}
dbus_start_video_preview(drawWidth, drawHeight, drawFormat);
}
else {
/* user clicked stop */
......@@ -147,7 +166,6 @@ preview_button_clicked(GtkButton *button, gpointer data UNUSED)
}
}
GtkWidget* create_video_configuration()
{
// Main widget
......@@ -171,6 +189,11 @@ GtkWidget* create_video_configuration()
g_signal_connect(G_OBJECT(preview_button), "clicked", G_CALLBACK(preview_button_clicked), NULL);
gtk_widget_show(GTK_WIDGET(preview_button));
drawarea = gtk_drawing_area_new();
gtk_widget_set_size_request (drawarea, drawWidth, drawHeight);
gtk_table_attach(GTK_TABLE(table), drawarea, 1, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 6);
gtk_widget_show(GTK_WIDGET(drawarea));
gnome_main_section_new_with_table (_ ("Video4Linux2"), &frame, &table, 1, 4);
gtk_box_pack_start (GTK_BOX (ret), frame, FALSE, FALSE, 0);
GtkWidget *v4l2box = v4l2_box();
......
......@@ -32,11 +32,12 @@
#define __VIDEO_CONF_H__
#include <gtk/gtk.h>
#include <dbus.h>
GtkWidget* create_video_configuration();
GtkWidget* videocodecs_box();
GtkWidget* v4l2_box();
void video_started_cb();
void video_started_cb(DBusGProxy *proxy, gint OUT_shmId, gint OUT_semId, gint OUT_videoBufferSize, GError *error, gpointer userdata);
void video_stopped_cb();
#endif // __VIDEO_CONF_H__
......@@ -1011,20 +1011,41 @@
<annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="MapStringString"/>
<tp:docstring>
</tp:docstring>
<arg type="a{ss}" name="shortcutsMap" direction="in">
<tp:docstring>
<arg type="a{ss}" name="shortcutsMap" direction="in"> <tp:docstring>
</tp:docstring>
</arg>
</method>
<method name="startVideoPreview" tp:name-for-bindings="startVideoPreview">
<arg type="i" name="width" direction="in">
<tp:docstring>
</tp:docstring>
</arg>
<arg type="i" name="height" direction="in">
<tp:docstring>
</tp:docstring>
</arg>
<arg type="s" name="format" direction="in">
<tp:docstring>
</tp:docstring>
</arg>
<arg type="i" name="shmId" direction="out">
<tp:docstring>
</tp:docstring>
</arg>
<arg type="i" name="semId" direction="out">
<tp:docstring>
</tp:docstring>
</arg>
<arg type="i" name="videoBufferSize" direction="out">
<tp:docstring>
</tp:docstring>
</arg>
</method>
<method name="stopVideoPreview" tp:name-for-bindings="stopVideoPreview">
</method>
<signal name="videoStarted" tp:name-for-bindings="videoStarted">
</signal>
<signal name="videoStopped" tp:name-for-bindings="videoStopped">
</signal>
......
......@@ -2729,11 +2729,11 @@ dbus_send_text_message (const gchar* callID, const gchar *message)
}
void
dbus_start_video_preview ()
dbus_start_video_preview (int width, int height, const char *format)
{
GError *error = NULL;
org_sflphone_SFLphone_ConfigurationManager_start_video_preview_async (
configurationManagerProxy, video_started_cb, &error);
configurationManagerProxy, width, height, format, video_started_cb, &error);
if (error) {
ERROR ("Failed to call start_video_preview () on ConfigurationManager: %s",
......
......@@ -38,7 +38,11 @@
#include <sys/sem.h> /* semaphore functions and structs. */
#include <sys/shm.h>
#include <assert.h>
#include <string.h>
#include <clutter/clutter.h>
#include <cairo.h>
#define VIDEO_PREVIEW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
VIDEO_PREVIEW_TYPE, VideoPreviewPrivate))
......@@ -51,6 +55,13 @@ enum
{
PROP_0,
PROP_RUNNING,
PROP_WIDTH,
PROP_HEIGHT,
PROP_FORMAT,
PROP_DRAWAREA,
PROP_SHMKEY,
PROP_SEMKEY,
PROP_VIDEO_BUFFER_SIZE,
PROP_LAST
};
......@@ -59,23 +70,30 @@ static GParamSpec *properties[PROP_LAST];
static void video_preview_finalize (GObject *gobject);
static void video_preview_get_property (GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec);
static void video_preview_set_property (GObject *object, guint prop_id,
const GValue *value, GParamSpec *pspec);
/* Our private member structure */
struct _VideoPreviewPrivate {
guint width;
guint height;
gchar *format;
gchar *shm_buffer;
gint sem_set_id;
gint sem_key;
gint shm_key;
gint videobuffersize;
gboolean using_clutter;
ClutterActor *texture;
gpointer drawarea;
cairo_t *cairo;
gboolean is_running;
};
typedef struct _FrameInfo {
unsigned size;
unsigned width;
unsigned height;
} FrameInfo;
/* See /bits/sem.h line 55 for why this is necessary */
#if _SEM_SEMUN_UNDEFINED
union semun
......@@ -87,40 +105,23 @@ union semun
};
#endif
#define TEMPFILE "/tmp/frame.txt"
/* FIXME: this will be replaced by a dbus call */
static FrameInfo *
getFrameInfo()
{
FrameInfo *info;
FILE *tmp = fopen(TEMPFILE, "r");
if (tmp == NULL) {
g_print("Error: Could not open file %s\n", TEMPFILE);
/* FIXME: this should error out gracefully */
return NULL;
}
info = (FrameInfo *) g_malloc(sizeof(FrameInfo));
if (fscanf(tmp, "%u\n%u\n%u\n", &info->size, &info->width, &info->height) <= 0)
g_print("Error: Could not read %s\n", TEMPFILE);
return info;
}
/* join and/or create a shared memory segment */
static int
getShm(unsigned numBytes)
getShm(unsigned numBytes, int shmKey)
{
key_t key;
key_t key = shmKey;
int shm_id;
/* connect to a segment with 600 permissions
(r--r--r--) */
key = ftok("/tmp", 'c');
shm_id = shmget(key, numBytes, 0644);
if (shm_id == -1)
perror("shmget");
return shm_id;
}
/* attach a shared memory segment */
static char *
attachShm(int shm_id)
......@@ -147,11 +148,11 @@ detachShm(char *data)
}
static int
get_sem_set()
get_sem_set(int semKey)
{
/* this variable will contain the semaphore set. */
int sem_set_id;
key_t key = ftok("/tmp", 'b');
key_t key = semKey;
/* semaphore value, for semctl(). */
union semun sem_val;
......@@ -168,28 +169,49 @@ get_sem_set()
return sem_set_id;
}
static void
video_preview_class_init (VideoPreviewClass *klass)
{
int i;
GObjectClass *gobject_class;
gobject_class = G_OBJECT_CLASS (klass);
g_type_class_add_private (klass, sizeof (VideoPreviewPrivate));
gobject_class->finalize = video_preview_finalize;
gobject_class->get_property = video_preview_get_property;
gobject_class->set_property = video_preview_set_property;
properties[PROP_RUNNING] = g_param_spec_boolean ("running", "Running",
"True if preview is running",
FALSE,
G_PARAM_READABLE);
g_object_class_install_property (gobject_class, PROP_RUNNING,
properties[PROP_RUNNING]);
properties[PROP_DRAWAREA] = g_param_spec_pointer ("drawarea", "DrawArea",
"Pointer to the drawing area",
G_PARAM_READABLE|G_PARAM_WRITABLE|G_PARAM_CONSTRUCT_ONLY);
properties[PROP_WIDTH] = g_param_spec_int ("width", "Width", "Width of preview video", G_MININT, G_MAXINT, -1,
G_PARAM_READABLE|G_PARAM_WRITABLE|G_PARAM_CONSTRUCT_ONLY);
properties[PROP_HEIGHT] = g_param_spec_int ("height", "Height", "Height of preview video", G_MININT, G_MAXINT, -1,
G_PARAM_READABLE|G_PARAM_WRITABLE|G_PARAM_CONSTRUCT_ONLY);
/* Initialize Clutter */
if (clutter_init (NULL, NULL) != CLUTTER_INIT_SUCCESS)
g_print("Error: could not initialize clutter!\n");
properties[PROP_FORMAT] = g_param_spec_pointer ("format", "Format", "Pixel format of preview video",
G_PARAM_READABLE|G_PARAM_WRITABLE|G_PARAM_CONSTRUCT_ONLY);
properties[PROP_SHMKEY] = g_param_spec_int ("shmkey", "ShmKey", "Unique key for shared memory identifier", G_MININT, G_MAXINT, -1,
G_PARAM_READABLE|G_PARAM_WRITABLE|G_PARAM_CONSTRUCT_ONLY);
properties[PROP_SEMKEY] = g_param_spec_int ("semkey", "SemKey", "Unique key for semaphore identifier", G_MININT, G_MAXINT, -1,
G_PARAM_READABLE|G_PARAM_WRITABLE|G_PARAM_CONSTRUCT_ONLY);
properties[PROP_VIDEO_BUFFER_SIZE] = g_param_spec_int ("vbsize", "VideoBufferSize", "Size of shared memory buffer", G_MININT, G_MAXINT, -1,
G_PARAM_READABLE|G_PARAM_WRITABLE|G_PARAM_CONSTRUCT_ONLY);
for (i = PROP_0 + 1; i < PROP_LAST; i++) {
g_object_class_install_property (gobject_class, i, properties[i]);
}
}
static void
......@@ -207,6 +229,68 @@ video_preview_get_property (GObject *object, guint prop_id,
case PROP_RUNNING:
g_value_set_boolean (value, priv->is_running);
break;
case PROP_DRAWAREA:
g_value_set_pointer(value, priv->drawarea);
break;
case PROP_WIDTH:
g_value_set_int(value, priv->width);
break;
case PROP_HEIGHT:
g_value_set_int(value, priv->height);
break;
case PROP_FORMAT:
g_value_set_pointer(value, priv->format);
break;
case PROP_SHMKEY:
g_value_set_int(value, priv->shm_key);
break;
case PROP_SEMKEY:
g_value_set_int(value, priv->sem_key);
break;
case PROP_VIDEO_BUFFER_SIZE:
g_value_set_int(value, priv->videobuffersize);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
video_preview_set_property (GObject *object, guint prop_id,
const GValue *value, GParamSpec *pspec)
{
VideoPreview *preview;
VideoPreviewPrivate *priv;
preview = VIDEO_PREVIEW(object);
priv = preview->priv;
switch (prop_id)
{
case PROP_DRAWAREA:
priv->drawarea = g_value_get_pointer(value);
break;
case PROP_WIDTH:
priv->width = g_value_get_int(value);
break;
case PROP_HEIGHT:
priv->height = g_value_get_int(value);
break;
case PROP_FORMAT:
priv->format = g_value_get_pointer(value);
break;
case PROP_SHMKEY:
priv->shm_key = g_value_get_int(value);
break;
case PROP_SEMKEY:
priv->sem_key = g_value_get_int(value);
break;
case PROP_VIDEO_BUFFER_SIZE:
priv->videobuffersize = g_value_get_int(value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
......@@ -221,25 +305,8 @@ video_preview_init (VideoPreview *self)
self->priv = priv = VIDEO_PREVIEW_GET_PRIVATE (self);
/* update the object state depending on constructor properties */
int shm_id = -1;
FrameInfo *info = getFrameInfo();
if (info)
{
priv->width = info->width;
priv->height = info->height;
shm_id = getShm(info->size);
g_free (info);
}
if (shm_id != -1) {
priv->shm_buffer = attachShm(shm_id);
priv->sem_set_id = get_sem_set();
}
else {
priv->shm_buffer = NULL;
priv->sem_set_id = -1;
}
priv->texture = NULL;
priv->cairo = NULL;
}
static void
......@@ -280,35 +347,68 @@ sem_wait(int sem_set_id)
return semop(sem_set_id, &sem_op, 1);
}
/* round integer value up to next multiple of 4 */
/* round int value up to next multiple of 4 */
static int
round_up_4(int value)
align(int value)
{
return (value + 3) &~ 3;
}
static void
readFrameFromShm(int width, int height, char *data, int sem_set_id,
ClutterActor *texture)
readFrameFromShm(VideoPreviewPrivate *priv)
{
if (sem_set_id != -1) {
if (sem_wait(sem_set_id) != -1) {
clutter_texture_set_from_rgb_data (CLUTTER_TEXTURE(texture),
(void*)data,
FALSE,
width,
height,
round_up_4(3 * width),
3,
0,
NULL);
int width = priv->width;
int height = priv->height;
void *data = priv->shm_buffer;
int sem_set_id = priv->sem_set_id;
ClutterActor *texture = priv->texture;
if (sem_set_id == -1)
return;
if (sem_wait(sem_set_id) == -1) {
if (errno != EAGAIN) {
g_print("Could not read from shared memory!\n");
perror("shm: ");
}
return;
}
if (priv->using_clutter) {
if (strcmp(priv->format, "rgb24")) {
g_print("clutter render: Unknown pixel format `%s'\n", priv->format);
}
clutter_texture_set_from_rgb_data (CLUTTER_TEXTURE(texture),
(void*)data,
FALSE,
width,
height,
align(3 /* bytes per pixel */ * width), // stride
3,
0,
NULL);
} else {
cairo_format_t format;
if (!strcmp(priv->format, "bgra"))
format = CAIRO_FORMAT_RGB24;
else {
g_print("cairo render: Unknown pixel format `%s'\n", priv->format);
return;
}
else
{
if (errno != EAGAIN) {
g_print("Could not read from shared memory!\n");
perror("shm: ");
}
int stride = cairo_format_stride_for_width (format, width);
assert(stride == align(4*width));
cairo_surface_t *surface = cairo_image_surface_create_for_data (data,
format,
width,
height,
stride);
if (surface) {
cairo_set_source_surface(priv->cairo, surface, 0, 0);
cairo_paint(priv->cairo);
cairo_surface_destroy(surface);
}
}
}
......@@ -318,9 +418,9 @@ updateTexture(gpointer data)
{
VideoPreview *preview = (VideoPreview *) data;
VideoPreviewPrivate *priv = VIDEO_PREVIEW_GET_PRIVATE(preview);
if (priv->shm_buffer != NULL) {
readFrameFromShm(priv->width, priv->height, priv->shm_buffer,
priv->sem_set_id, priv->texture);
readFrameFromShm(priv);
return TRUE;
}
else
......@@ -333,11 +433,19 @@ updateTexture(gpointer data)
* Create a new #VideoPreview instance.
*/
VideoPreview *
video_preview_new (void)
video_preview_new (GtkWidget *drawarea, int width, int height, const char *format, int semkey, int shmkey, int vbsize)
{
VideoPreview *result;
result = g_object_new (VIDEO_PREVIEW_TYPE, NULL);
result = g_object_new (VIDEO_PREVIEW_TYPE,
"drawarea", (gpointer)drawarea,
"width", (gint)width,
"height", (gint)height,
"format", (gpointer)format,
"semkey", (gint)semkey,
"shmkey", (gint)shmkey,
"vbsize", (gint)vbsize,
NULL);
return result;
}
......@@ -358,31 +466,48 @@ video_preview_run(VideoPreview *preview)
{
VideoPreviewPrivate * priv = VIDEO_PREVIEW_GET_PRIVATE(preview);
ClutterActor *stage;
int shm_id = getShm(priv->videobuffersize, priv->sem_key);
priv->shm_buffer = attachShm(shm_id);
priv->sem_set_id = get_sem_set(priv->shm_key);
/* Get a stage */
stage = clutter_stage_new ();
g_signal_connect (stage, "delete-event", G_CALLBACK(on_stage_delete),
preview);
clutter_actor_set_size(stage,
priv->width,
priv->height);
priv->using_clutter = !strcmp(priv->format, "rgb24");
g_print("Preview: using %s render\n", priv->using_clutter ? "clutter" : "cairo");
priv->texture = clutter_texture_new();
if (priv->using_clutter) {
ClutterActor *stage;
clutter_stage_set_title(CLUTTER_STAGE (stage), "Video Test");
/* Add ClutterTexture to the stage */
clutter_container_add(CLUTTER_CONTAINER (stage), priv->texture, NULL);
/* Get a stage */
stage = clutter_stage_new ();
g_signal_connect (stage, "delete-event", G_CALLBACK(on_stage_delete),
preview);
clutter_actor_set_size(stage,
priv->width,
priv->height);
priv->texture = clutter_texture_new();
clutter_stage_set_title(CLUTTER_STAGE (stage), "Video Test");
/* Add ClutterTexture to the stage */
clutter_container_add(CLUTTER_CONTAINER (stage), priv->texture, NULL);
clutter_actor_show_all(stage);
} else {
if (!priv->cairo) {