blob: 4f07f1ca2b0c7cee43758df754776a5896f3bc11 [file] [log] [blame]
#include <gst/gst.h>
#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include <gst/video/video.h>
#define WINDOW_GLADE "window.glade"
#define INT_PROPERTY_GLADE "int_property.glade"
#define ENUM_PROPERTY_GLADE "enum_property.glade"
#define BOOL_PROPERTY_GLADE "boolean_property.glade"
#define PROPERTY_TO_VBOX \
properties[i].dynamic ? GTK_BOX (dynamic_vbox) : GTK_BOX (static_vbox)
#define GET_WIDGET(object, type, name) \
type (gtk_builder_get_object ((object)->builder, name))
#define GET_PROP_WIDGET(type, name) GET_WIDGET (&(properties[i]), type, name)
static guint h264_xid, preview_xid;
typedef struct
{
GtkBuilder *builder;
GstElement *src;
enum
{ NONE, INT, ENUM, BOOL } type;
const gchar *property_name;
gboolean readonly;
gboolean dynamic;
} Prop;
typedef struct
{
GtkBuilder *builder;
GstElement *bin;
GstElement *src;
GstElement *identity;
GstElement *vid_capsfilter;
GstElement *vf_capsfilter;
} Main;
Prop properties[] = {
{NULL, NULL, INT, "initial-bitrate", FALSE, FALSE},
{NULL, NULL, INT, "slice-units", FALSE, FALSE},
{NULL, NULL, ENUM, "slice-mode", FALSE, FALSE},
{NULL, NULL, INT, "iframe-period", FALSE, FALSE},
{NULL, NULL, ENUM, "usage-type", FALSE, FALSE},
{NULL, NULL, ENUM, "entropy", FALSE, FALSE},
{NULL, NULL, BOOL, "enable-sei", FALSE, FALSE},
{NULL, NULL, INT, "num-reorder-frames", FALSE, FALSE},
{NULL, NULL, BOOL, "preview-flipped", FALSE, FALSE},
{NULL, NULL, INT, "leaky-bucket-size", FALSE, FALSE},
{NULL, NULL, INT, "num-clock-samples", FALSE, TRUE},
{NULL, NULL, ENUM, "rate-control", FALSE, TRUE},
{NULL, NULL, BOOL, "fixed-framerate", FALSE, TRUE},
{NULL, NULL, INT, "max-mbps", TRUE, TRUE},
{NULL, NULL, INT, "level-idc", FALSE, TRUE},
{NULL, NULL, INT, "peak-bitrate", FALSE, TRUE},
{NULL, NULL, INT, "average-bitrate", FALSE, TRUE},
{NULL, NULL, INT, "min-iframe-qp", FALSE, TRUE},
{NULL, NULL, INT, "max-iframe-qp", FALSE, TRUE},
{NULL, NULL, INT, "min-pframe-qp", FALSE, TRUE},
{NULL, NULL, INT, "max-pframe-qp", FALSE, TRUE},
{NULL, NULL, INT, "min-bframe-qp", FALSE, TRUE},
{NULL, NULL, INT, "max-bframe-qp", FALSE, TRUE},
{NULL, NULL, INT, "ltr-buffer-size", FALSE, TRUE},
{NULL, NULL, INT, "ltr-encoder-control", FALSE, TRUE},
};
static void set_drop_probability (Main * self);
static void get_all_properties (void);
static void probe_all_properties (gboolean playing);
/* Callbacks */
void on_button_toggled (GtkToggleButton * button, gpointer user_data);
void on_get_button_clicked (GtkButton * button, gpointer user_data);
void on_set_button_clicked (GtkButton * button, gpointer user_data);
void on_button_ready_clicked (GtkButton * button, gpointer user_data);
void on_button_null_clicked (GtkButton * button, gpointer user_data);
void on_button_playing_clicked (GtkButton * button, gpointer user_data);
void on_iframe_button_clicked (GtkButton * button, gpointer user_data);
void on_renegotiate_button_clicked (GtkButton * button, gpointer user_data);
void on_start_capture_button_clicked (GtkButton * button, gpointer user_data);
void on_stop_capture_button_clicked (GtkButton * button, gpointer user_data);
void on_window_destroyed (GtkWindow * window, gpointer user_data);
static GstEvent *
new_upstream_force_key_unit (GstClockTime running_time,
gboolean all_headers, guint count)
{
GstEvent *force_key_unit_event;
GstStructure *s;
s = gst_structure_new ("GstForceKeyUnit",
"running-time", GST_TYPE_CLOCK_TIME, running_time,
"all-headers", G_TYPE_BOOLEAN, all_headers,
"count", G_TYPE_UINT, count, NULL);
force_key_unit_event = gst_event_new_custom (GST_EVENT_CUSTOM_UPSTREAM, s);
return force_key_unit_event;
}
void
on_get_button_clicked (GtkButton * button, gpointer user_data)
{
Prop *property = user_data;
switch (property->type) {
case INT:
{
gchar *val;
gint val_int;
g_object_get (property->src, property->property_name, &val_int, NULL);
val = g_strdup_printf ("%d", val_int);
gtk_entry_set_text (GET_WIDGET (property, GTK_ENTRY, "value"), val);
g_free (val);
}
break;
case ENUM:
{
GParamSpec *param;
gint val;
g_object_get (property->src, property->property_name, &val, NULL);
param = g_object_class_find_property (G_OBJECT_GET_CLASS (property->src),
property->property_name);
if (G_IS_PARAM_SPEC_ENUM (param)) {
GEnumValue *values;
guint i = 0;
values = G_ENUM_CLASS (g_type_class_ref (param->value_type))->values;
while (values[i].value_name) {
if (values[i].value == val) {
gtk_combo_box_set_active (GET_WIDGET (property,
(GtkComboBox *), "value"), i);
break;
}
i++;
}
}
}
break;
case BOOL:
{
gboolean val;
g_object_get (property->src, property->property_name, &val, NULL);
gtk_toggle_button_set_active (GET_WIDGET (property,
(GtkToggleButton *), "value"), val);
}
break;
case NONE:
default:
break;
}
}
void
on_set_button_clicked (GtkButton * button, gpointer user_data)
{
Prop *property = user_data;
switch (property->type) {
case INT:
{
int val_int;
const gchar *val;
val = gtk_entry_get_text (GET_WIDGET (property, GTK_ENTRY, "value"));
val_int = (int) g_ascii_strtoll (val, NULL, 0);
g_object_set (property->src, property->property_name, val_int, NULL);
}
break;
case ENUM:
{
GParamSpec *param;
param = g_object_class_find_property (G_OBJECT_GET_CLASS (property->src),
property->property_name);
if (G_IS_PARAM_SPEC_ENUM (param)) {
GEnumValue *values;
guint val = 0;
values = G_ENUM_CLASS (g_type_class_ref (param->value_type))->values;
val = gtk_combo_box_get_active (GET_WIDGET (property,
(GtkComboBox *), "value"));
g_object_set (property->src, property->property_name,
values[val].value, NULL);
}
}
break;
case BOOL:
{
gboolean val;
val = gtk_toggle_button_get_active (GET_WIDGET (property,
(GtkToggleButton *), "value"));
g_object_set (property->src, property->property_name, val, NULL);
}
break;
case NONE:
default:
break;
}
get_all_properties ();
}
void
on_button_toggled (GtkToggleButton * button, gpointer user_data)
{
if (gtk_toggle_button_get_active (button))
gtk_button_set_label (GTK_BUTTON (button), " Enabled ");
else
gtk_button_set_label (GTK_BUTTON (button), " Disabled ");
}
static gboolean
set_caps (Main * self, gboolean send_event)
{
const gchar *h264_filter;
const gchar *raw_filter;
GstCaps *h264_caps = NULL;
GstCaps *raw_caps = NULL;
gboolean ret = TRUE;
h264_filter = gtk_entry_get_text (GET_WIDGET (self, GTK_ENTRY, "h264_caps"));
raw_filter =
gtk_entry_get_text (GET_WIDGET (self, GTK_ENTRY, "preview_caps"));
if (h264_filter)
h264_caps = gst_caps_from_string (h264_filter);
if (raw_filter)
raw_caps = gst_caps_from_string (raw_filter);
g_debug ("H264 caps : %s", gst_caps_to_string (h264_caps));
g_debug ("Preview caps : %s", gst_caps_to_string (raw_caps));
if (!h264_caps || !raw_caps) {
g_debug ("Invalid caps");
ret = FALSE;
goto end;
}
g_object_set (self->vid_capsfilter, "caps", h264_caps, NULL);
g_object_set (self->vf_capsfilter, "caps", raw_caps, NULL);
if (send_event) {
gst_element_send_event (GST_ELEMENT (self->src),
gst_event_new_reconfigure ());
}
end:
if (h264_caps)
gst_caps_unref (h264_caps);
if (raw_caps)
gst_caps_unref (raw_caps);
return ret;
}
void
on_button_ready_clicked (GtkButton * button, gpointer user_data)
{
Main *self = user_data;
set_caps (self, FALSE);
gst_element_set_state (self->bin, GST_STATE_READY);
probe_all_properties (FALSE);
get_all_properties ();
}
void
on_button_null_clicked (GtkButton * button, gpointer user_data)
{
Main *self = user_data;
gst_element_set_state (self->bin, GST_STATE_NULL);
probe_all_properties (FALSE);
get_all_properties ();
}
void
on_button_playing_clicked (GtkButton * button, gpointer user_data)
{
Main *self = user_data;
if (gst_element_set_state (self->bin, GST_STATE_PLAYING) ==
GST_STATE_CHANGE_FAILURE) {
g_debug ("Unable to go to state PLAYING");
}
set_caps (self, FALSE);
probe_all_properties (TRUE);
get_all_properties ();
set_drop_probability (self);
}
void
on_iframe_button_clicked (GtkButton * button, gpointer user_data)
{
Main *self = user_data;
GstEvent *event;
gboolean pps_sps;
set_drop_probability (self);
pps_sps = gtk_toggle_button_get_active (GET_WIDGET (self, (GtkToggleButton *),
"pps_sps"));
event = new_upstream_force_key_unit (GST_CLOCK_TIME_NONE, pps_sps, 0);
gst_element_send_event (GST_ELEMENT (self->src), event);
}
void
on_renegotiate_button_clicked (GtkButton * button, gpointer user_data)
{
Main *self = user_data;
set_caps (self, TRUE);
probe_all_properties (GST_STATE (self->bin) >= GST_STATE_PAUSED);
get_all_properties ();
}
void
on_start_capture_button_clicked (GtkButton * button, gpointer user_data)
{
Main *self = user_data;
set_caps (self, FALSE);
g_signal_emit_by_name (G_OBJECT (self->src), "start-capture", NULL);
probe_all_properties (GST_STATE (self->bin) >= GST_STATE_PAUSED);
get_all_properties ();
}
void
on_stop_capture_button_clicked (GtkButton * button, gpointer user_data)
{
Main *self = user_data;
set_caps (self, FALSE);
g_signal_emit_by_name (G_OBJECT (self->src), "stop-capture", NULL);
probe_all_properties (GST_STATE (self->bin) >= GST_STATE_PAUSED);
get_all_properties ();
}
void
on_window_destroyed (GtkWindow * window, gpointer user_data)
{
gtk_main_quit ();
}
static gboolean
_bus_callback (GstBus * bus, GstMessage * message, gpointer user_data)
{
const GstStructure *s = gst_message_get_structure (message);
GstObject *source = NULL;
if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ELEMENT &&
gst_structure_has_name (s, "prepare-window-handle")) {
source = GST_MESSAGE_SRC (message);
if (!g_strcmp0 (gst_object_get_name (source), "h264_sink"))
gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (source),
h264_xid);
else
gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (source),
preview_xid);
}
return TRUE;
}
static void
set_drop_probability (Main * self)
{
const gchar *drop;
gdouble drop_probability = 0.0;
drop = gtk_entry_get_text (GET_WIDGET (self, GTK_ENTRY, "drop"));
drop_probability = g_ascii_strtod (drop, NULL);
g_debug ("Setting drop probability to : %f", drop_probability);
g_object_set (self->identity, "drop-probability", drop_probability, NULL);
}
static void
get_all_properties (void)
{
int i;
for (i = 0; i < G_N_ELEMENTS (properties); i++)
on_get_button_clicked (NULL, &properties[i]);
}
static void
probe_all_properties (gboolean playing)
{
int i;
for (i = 0; i < G_N_ELEMENTS (properties); i++) {
gboolean return_value, changeable, default_bool;
guint mask, minimum, maximum, default_int;
GParamSpec *param;
/* When playing, ignore static controls */
if (playing && !properties[i].dynamic)
continue;
switch (properties[i].type) {
case INT:
g_signal_emit_by_name (G_OBJECT (properties[i].src), "get-int-setting",
properties[i].property_name, &minimum, &default_int, &maximum,
&return_value, NULL);
if (return_value) {
gchar *min, *def, *max;
min = g_strdup_printf ("%d", minimum);
def = g_strdup_printf ("%d", default_int);
max = g_strdup_printf ("%d", maximum);
gtk_entry_set_text (GET_PROP_WIDGET (GTK_ENTRY, "minimum"), min);
gtk_entry_set_text (GET_PROP_WIDGET (GTK_ENTRY, "default"), def);
gtk_entry_set_text (GET_PROP_WIDGET (GTK_ENTRY, "maximum"), max);
g_free (min);
g_free (def);
g_free (max);
} else {
gtk_entry_set_text (GET_PROP_WIDGET (GTK_ENTRY, "minimum"), "");
gtk_entry_set_text (GET_PROP_WIDGET (GTK_ENTRY, "default"), "");
gtk_entry_set_text (GET_PROP_WIDGET (GTK_ENTRY, "maximum"), "");
}
break;
case ENUM:
g_signal_emit_by_name (G_OBJECT (properties[i].src), "get-enum-setting",
properties[i].property_name, &mask, &default_int, &return_value,
NULL);
param =
g_object_class_find_property (G_OBJECT_GET_CLASS (properties
[i].src), properties[i].property_name);
if (G_IS_PARAM_SPEC_ENUM (param)) {
GEnumValue *values;
guint j = 0;
values = G_ENUM_CLASS (g_type_class_ref (param->value_type))->values;
if (return_value) {
while (values[j].value_name) {
if (values[j].value == default_int) {
gtk_entry_set_text (GET_PROP_WIDGET (GTK_ENTRY, "default"),
values[j].value_name);
break;
}
j++;
}
} else {
gtk_entry_set_text (GET_PROP_WIDGET (GTK_ENTRY, "default"), "");
}
j = 0;
while (values[j].value_name) {
#if !GTK_CHECK_VERSION (2, 24, 0)
gtk_combo_box_remove_text (GET_PROP_WIDGET ((GtkComboBox *),
"value"), 0);
#else
gtk_combo_box_text_remove (GET_PROP_WIDGET ((GtkComboBoxText *),
"value"), 0);
#endif
j++;
}
j = 0;
while (values[j].value_name) {
gchar *val;
if (return_value && (mask & (1 << values[j].value)) != 0)
val = g_strdup_printf ("**%s**", values[j].value_name);
else
val = g_strdup (values[j].value_name);
#if !GTK_CHECK_VERSION (2, 24, 0)
gtk_combo_box_append_text (GET_PROP_WIDGET ((GtkComboBox *),
"value"), val);
#else
gtk_combo_box_text_append_text (GET_PROP_WIDGET ((GtkComboBoxText
*), "value"), val);
#endif
g_free (val);
j++;
}
}
break;
case BOOL:
g_signal_emit_by_name (G_OBJECT (properties[i].src),
"get-boolean-setting", properties[i].property_name,
&changeable, &default_bool, &return_value, NULL);
if (return_value) {
gtk_widget_set_sensitive (GET_PROP_WIDGET (GTK_WIDGET, "value"),
changeable);
gtk_widget_set_sensitive (GET_PROP_WIDGET (GTK_WIDGET, "get"),
changeable);
gtk_widget_set_sensitive (GET_PROP_WIDGET (GTK_WIDGET, "set"),
changeable);
gtk_toggle_button_set_active (GET_PROP_WIDGET ((GtkToggleButton *),
"default"), default_bool);
}
break;
case NONE:
default:
break;
}
}
}
int
main (int argc, char *argv[])
{
Main self = { NULL, NULL, NULL, NULL };
GstBus *bus = NULL;
GtkWidget *window, *static_vbox, *dynamic_vbox, *da;
gchar *drop;
gdouble drop_probability;
GdkWindow *gdk_win = NULL;
const char *device = "/dev/video0";
GError *error = NULL;
int i;
gtk_init (&argc, &argv);
gst_init (&argc, &argv);
if (argc > 1)
device = argv[1];
else
g_print ("Usage : %s [device]\nUsing default device : %s\n",
argv[0], device);
self.bin = gst_parse_launch ("uvch264src name=src src.vidsrc ! queue ! "
"capsfilter name=vid_cf ! identity name=identity ! decodebin ! "
"xvimagesink name=h264_sink async=false "
"src.vfsrc ! queue ! capsfilter name=vf_cf ! "
"xvimagesink name=preview_sink async=false", NULL);
if (!self.bin)
return -1;
/* Listen to the bus for messages */
bus = gst_element_get_bus (self.bin);
gst_bus_add_watch (bus, _bus_callback, self.bin);
gst_object_unref (bus);
self.src = gst_bin_get_by_name (GST_BIN (self.bin), "src");
self.identity = gst_bin_get_by_name (GST_BIN (self.bin), "identity");
self.vid_capsfilter = gst_bin_get_by_name (GST_BIN (self.bin), "vid_cf");
self.vf_capsfilter = gst_bin_get_by_name (GST_BIN (self.bin), "vf_cf");
self.builder = gtk_builder_new ();
gtk_builder_add_from_file (self.builder, WINDOW_GLADE, &error);
if (error) {
g_debug ("Unable to load glade file : %s", error->message);
goto end;
}
gtk_builder_connect_signals (self.builder, &self);
g_object_get (self.identity, "drop-probability", &drop_probability, NULL);
drop = g_strdup_printf ("%f", drop_probability);
gtk_entry_set_text (GET_WIDGET (&self, GTK_ENTRY, "drop"), drop);
g_free (drop);
window = GET_WIDGET (&self, GTK_WIDGET, "window");
static_vbox = GET_WIDGET (&self, GTK_WIDGET, "static");
dynamic_vbox = GET_WIDGET (&self, GTK_WIDGET, "dynamic");
da = GET_WIDGET (&self, GTK_WIDGET, "h264");
gtk_widget_realize (da);
gdk_win = gtk_widget_get_window (da);
h264_xid = GDK_WINDOW_XID (gdk_win);
da = GET_WIDGET (&self, GTK_WIDGET, "preview");
gtk_widget_realize (da);
gdk_win = gtk_widget_get_window (da);
preview_xid = GDK_WINDOW_XID (gdk_win);
set_caps (&self, FALSE);
g_object_set (self.src, "device", device, NULL);
if (gst_element_set_state (self.bin, GST_STATE_READY) ==
GST_STATE_CHANGE_FAILURE) {
g_debug ("Unable to go to state READY");
goto end;
}
for (i = 0; i < G_N_ELEMENTS (properties); i++) {
switch (properties[i].type) {
case INT:
properties[i].src = self.src;
properties[i].builder = gtk_builder_new ();
gtk_builder_add_from_file (properties[i].builder, INT_PROPERTY_GLADE,
NULL);
gtk_builder_connect_signals (properties[i].builder, &properties[i]);
gtk_box_pack_start (PROPERTY_TO_VBOX,
GET_PROP_WIDGET (GTK_WIDGET, "int-property"), TRUE, TRUE, 2);
gtk_label_set_label (GET_PROP_WIDGET (GTK_LABEL, "label"),
properties[i].property_name);
if (properties[i].readonly)
gtk_widget_set_sensitive (GET_PROP_WIDGET (GTK_WIDGET, "set"), FALSE);
break;
case ENUM:
properties[i].src = self.src;
properties[i].builder = gtk_builder_new ();
#if !GTK_CHECK_VERSION (2, 24, 0)
gtk_builder_add_from_file (properties[i].builder,
"enum_property_gtk2.glade", NULL);
#else
gtk_builder_add_from_file (properties[i].builder, ENUM_PROPERTY_GLADE,
NULL);
#endif
gtk_builder_connect_signals (properties[i].builder, &properties[i]);
gtk_box_pack_start (PROPERTY_TO_VBOX,
GET_PROP_WIDGET (GTK_WIDGET, "enum-property"), TRUE, TRUE, 2);
gtk_label_set_label (GET_PROP_WIDGET (GTK_LABEL, "label"),
properties[i].property_name);
#if !GTK_CHECK_VERSION (2, 24, 0)
{
GtkComboBox *combo_box;
GtkCellRenderer *cell;
GtkListStore *store;
combo_box = GET_PROP_WIDGET ((GtkComboBox *), "value");
store = gtk_list_store_new (1, G_TYPE_STRING);
gtk_combo_box_set_model (combo_box, GTK_TREE_MODEL (store));
g_object_unref (store);
cell = gtk_cell_renderer_text_new ();
gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box), cell, TRUE);
gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo_box), cell,
"text", 0, NULL);
}
#endif
if (properties[i].readonly)
gtk_widget_set_sensitive (GET_PROP_WIDGET (GTK_WIDGET, "set"), FALSE);
break;
case BOOL:
properties[i].src = self.src;
properties[i].builder = gtk_builder_new ();
gtk_builder_add_from_file (properties[i].builder, BOOL_PROPERTY_GLADE,
NULL);
gtk_builder_connect_signals (properties[i].builder, &properties[i]);
gtk_box_pack_start (PROPERTY_TO_VBOX,
GET_PROP_WIDGET (GTK_WIDGET, "boolean-property"), TRUE, TRUE, 2);
gtk_label_set_label (GET_PROP_WIDGET (GTK_LABEL, "label"),
properties[i].property_name);
if (properties[i].readonly)
gtk_widget_set_sensitive (GET_PROP_WIDGET (GTK_WIDGET, "set"), FALSE);
break;
case NONE:
default:
break;
}
}
probe_all_properties (FALSE);
get_all_properties ();
gtk_widget_show (window);
gtk_main ();
end:
g_object_unref (G_OBJECT (self.builder));
for (i = 0; i < G_N_ELEMENTS (properties); i++) {
if (properties[i].builder)
g_object_unref (G_OBJECT (properties[i].builder));
}
gst_element_set_state (self.bin, GST_STATE_NULL);
gst_object_unref (self.src);
gst_object_unref (self.identity);
gst_object_unref (self.vid_capsfilter);
gst_object_unref (self.vf_capsfilter);
gst_object_unref (self.bin);
return 0;
}