diff options
author | Olivier CrĂȘte <olivier.crete@collabora.co.uk> | 2009-02-23 16:47:42 -0500 |
---|---|---|
committer | Olivier CrĂȘte <olivier.crete@collabora.co.uk> | 2009-02-23 16:49:13 -0500 |
commit | 7ebd18f5fa4c235107f35a76adf7c3262749b912 (patch) | |
tree | 0e2d1ce82161d6955d4c9e8f4b637b979eaecbe6 /examples | |
parent | 548f9520d56d7a0efd23482e1b02ff63d2a3e5be (diff) | |
download | farstream-7ebd18f5fa4c235107f35a76adf7c3262749b912.tar.gz |
Move examples to examples dir
Diffstat (limited to 'examples')
-rw-r--r-- | examples/Makefile.am | 2 | ||||
-rw-r--r-- | examples/commandline/Makefile.am | 14 | ||||
-rw-r--r-- | examples/commandline/simple-call.c | 339 | ||||
-rw-r--r-- | examples/gui/Makefile.am | 1 | ||||
-rw-r--r-- | examples/gui/fs2-gui.glade | 705 | ||||
-rwxr-xr-x | examples/gui/fs2-gui.py | 929 | ||||
-rw-r--r-- | examples/gui/fs2_gui_net.py | 456 |
7 files changed, 2446 insertions, 0 deletions
diff --git a/examples/Makefile.am b/examples/Makefile.am new file mode 100644 index 00000000..3099ae23 --- /dev/null +++ b/examples/Makefile.am @@ -0,0 +1,2 @@ + +SUBDIRS = gui commandline diff --git a/examples/commandline/Makefile.am b/examples/commandline/Makefile.am new file mode 100644 index 00000000..d374e674 --- /dev/null +++ b/examples/commandline/Makefile.am @@ -0,0 +1,14 @@ + +noinst_PROGRAMS = simple-call + + +AM_CFLAGS = \ + -I$(top_srcdir)/gst/fsrtpconference/ \ + $(FS2_INTERNAL_CFLAGS) \ + $(FS2_CFLAGS) \ + $(GST_CFLAGS) \ + $(CFLAGS) + +LDADD = \ + $(top_builddir)/gst-libs/gst/farsight/libgstfarsight-0.10.la \ + $(GST_LIBS) diff --git a/examples/commandline/simple-call.c b/examples/commandline/simple-call.c new file mode 100644 index 00000000..fbc56fbf --- /dev/null +++ b/examples/commandline/simple-call.c @@ -0,0 +1,339 @@ +/* Farsight 2 ad-hoc test for simple calls. + * + * Copyright (C) 2008 Collabora, Nokia + * @author: Olivier Crete <olivier.crete@collabora.co.uk> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * WARNING: + * + * Do not use this as an example of a proper use of farsight, it assumes that + * both ends have the EXACT same list of codec installed in the EXACT same order + */ + + +#include <stdlib.h> + +#include <glib.h> +#include <gst/gst.h> +#include <gst/farsight/fs-conference-iface.h> + +#define DEFAULT_AUDIOSRC "alsasrc" +#define DEFAULT_AUDIOSINK "audioconvert ! audioresample ! audioconvert ! alsasink" + +typedef struct _TestSession +{ + FsSession *session; + FsStream *stream; +} TestSession; + + +static void +print_error (GError *error) +{ + if (error) + { + g_error ("Error: %s:%d : %s", g_quark_to_string (error->domain), + error->code, error->message); + } +} + +static void +src_pad_added_cb (FsStream *stream, GstPad *pad, FsCodec *codec, + gpointer user_data) +{ + GstElement *pipeline = GST_ELEMENT_CAST (user_data); + GstElement *sink = NULL; + GError *error = NULL; + GstPad *pad2; + + g_print ("Adding receive pipeline\n"); + + if (g_getenv ("AUDIOSINK")) + sink = gst_parse_bin_from_description (g_getenv ("AUDIOSINK"), TRUE, + &error); + else + sink = gst_parse_bin_from_description (DEFAULT_AUDIOSINK, TRUE, + &error); + print_error (error); + g_assert (sink); + + g_assert (gst_bin_add (GST_BIN (pipeline), sink)); + + + pad2 = gst_element_get_static_pad (sink, "sink"); + g_assert (pad2); + + g_assert (GST_PAD_LINK_SUCCESSFUL (gst_pad_link (pad, pad2))); + + g_assert (gst_element_set_state (sink, GST_STATE_PLAYING) != + GST_STATE_CHANGE_FAILURE); + + gst_object_unref (pad2); +} + +static TestSession* +add_audio_session (GstElement *pipeline, FsConference *conf, guint id, + FsParticipant *part, guint localport, const gchar *remoteip, + guint remoteport) +{ + TestSession *ses = g_slice_new0 (TestSession); + GError *error = NULL; + GstPad *pad = NULL, *pad2 = NULL; + GstElement *src = NULL; + GList *cands = NULL; + GList *codecs = NULL; + GParameter param = {0}; + gboolean res; + + ses->session = fs_conference_new_session (conf, FS_MEDIA_TYPE_AUDIO, &error); + print_error (error); + g_assert (ses->session); + + g_object_get (ses->session, "sink-pad", &pad, NULL); + + if (g_getenv ("AUDIOSRC")) + src = gst_parse_bin_from_description (g_getenv ("AUDIOSRC"), TRUE, + &error); + else + src = gst_parse_bin_from_description (DEFAULT_AUDIOSRC, TRUE, + &error); + print_error (error); + g_assert (src); + + g_assert (gst_bin_add (GST_BIN (pipeline), src)); + + pad2 = gst_element_get_static_pad (src, "src"); + g_assert (pad2); + + g_assert (GST_PAD_LINK_SUCCESSFUL (gst_pad_link (pad2, pad))); + + gst_object_unref (pad2); + gst_object_unref (pad); + + + cands = g_list_prepend (NULL, fs_candidate_new ("", FS_COMPONENT_RTP, + FS_CANDIDATE_TYPE_HOST, FS_NETWORK_PROTOCOL_UDP, NULL, localport)); + + param.name = "preferred-local-candidates"; + g_value_init (¶m.value, FS_TYPE_CANDIDATE_LIST); + g_value_take_boxed (¶m.value, cands); + + ses->stream = fs_session_new_stream (ses->session, part, FS_DIRECTION_BOTH, + "rawudp", 1, ¶m, &error); + print_error (error); + g_assert (ses->stream); + + g_value_unset (¶m.value); + + g_signal_connect (ses->stream, "src-pad-added", + G_CALLBACK (src_pad_added_cb), pipeline); + + cands = g_list_prepend (NULL, fs_candidate_new ("", FS_COMPONENT_RTP, + FS_CANDIDATE_TYPE_HOST, FS_NETWORK_PROTOCOL_UDP, remoteip, + remoteport)); + + res = fs_stream_set_remote_candidates (ses->stream, cands, &error); + print_error (error); + g_assert (res); + + fs_candidate_list_destroy (cands); + + codecs = g_list_prepend (NULL, + fs_codec_new (FS_CODEC_ID_ANY, "PCMA", FS_MEDIA_TYPE_AUDIO, 0)); + codecs = g_list_prepend (codecs, + fs_codec_new (FS_CODEC_ID_ANY, "PCMU", FS_MEDIA_TYPE_AUDIO, 0)); + + res = fs_session_set_codec_preferences (ses->session, codecs, &error); + print_error (error); + fs_codec_list_destroy (codecs); + + + g_object_get (ses->session, "codecs", &codecs, NULL); + res = fs_stream_set_remote_codecs (ses->stream, codecs, &error); + print_error (error); + g_assert (res); + + return ses; +} + +static gboolean +async_bus_cb (GstBus *bus, GstMessage *message, gpointer user_data) +{ + switch (GST_MESSAGE_TYPE(message)) + { + case GST_MESSAGE_ERROR: + { + GError *error = NULL; + gchar *debug_str = NULL; + + gst_message_parse_error (message, &error, &debug_str); + g_error ("Got gst message: %s %s", error->message, debug_str); + } + break; + case GST_MESSAGE_WARNING: + { + GError *error = NULL; + gchar *debug_str = NULL; + + gst_message_parse_warning (message, &error, &debug_str); + g_warning ("Got gst message: %s %s", error->message, debug_str); + } + break; + case GST_MESSAGE_ELEMENT: + { + const GstStructure *s = gst_message_get_structure (message); + + if (gst_structure_has_name (s, "farsight-error")) + { + gint error; + const gchar *error_msg = gst_structure_get_string (s, "error-msg"); + const gchar *debug_msg = gst_structure_get_string (s, "debug-msg"); + + g_assert (gst_structure_get_enum (s, "error-no", FS_TYPE_ERROR, + &error)); + + if (FS_ERROR_IS_FATAL (error)) + g_error ("Farsight fatal error: %d %s %s", error, error_msg, + debug_msg); + else + g_warning ("Farsight non-fatal error: %d %s %s", error, error_msg, + debug_msg); + } + else if (gst_structure_has_name (s, "farsight-new-local-candidate")) + { + const GValue *val = gst_structure_get_value (s, "candidate"); + FsCandidate *cand = NULL; + + g_assert (val); + cand = g_value_get_boxed (val); + + g_print ("New candidate: %s %d\n", cand->ip, cand->port); + } + else if (gst_structure_has_name (s, + "farsight-local-candidates-prepared")) + { + g_print ("Local candidates prepared\n"); + } + else if (gst_structure_has_name (s, "farsight-recv-codecs-changed")) + { + const GValue *val = gst_structure_get_value (s, "codecs"); + GList *codecs = NULL; + + g_assert (val); + codecs = g_value_get_boxed (val); + + g_print ("Recv codecs changed:\n"); + for (; codecs; codecs = g_list_next (codecs)) + { + FsCodec *codec = codecs->data; + gchar *tmp = fs_codec_to_string (codec); + g_print ("%s\n", tmp); + g_free (tmp); + } + } + else if (gst_structure_has_name (s, "farsight-send-codec-changed")) + { + const GValue *val = gst_structure_get_value (s, "codec"); + FsCodec *codec = NULL; + gchar *tmp; + g_assert (val); + codec = g_value_get_boxed (val); + tmp = fs_codec_to_string (codec); + + g_print ("Send codec changed: %s\n", tmp); + g_free (tmp); + } + } + break; + default: + break; + } + + return TRUE; +} + +int main (int argc, char **argv) +{ + GMainLoop *loop = NULL; + GstElement *pipeline = NULL; + GstBus *bus = NULL; + const gchar *remoteip; + guint localport = 0; + guint remoteport = 0; + GstElement *conf = NULL; + FsParticipant *part = NULL; + GError *error = NULL; + + gst_init (&argc, &argv); + + if (argc != 4) + { + g_print ("Usage: %s <local port> <remoteip> <remoteport>\n", + argv[0]); + return 1; + } + + localport = atoi (argv[1]); + remoteip = argv[2]; + remoteport = atoi (argv[3]); + + if (!localport || !remoteip || !remoteport) + { + g_print ("Usage: %s <local port> <remoteip> <remoteport>\n", + argv[0]); + return 2; + } + + loop = g_main_loop_new (NULL, FALSE); + + pipeline = gst_pipeline_new (NULL); + + bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline)); + gst_bus_add_watch (bus, async_bus_cb, pipeline); + gst_object_unref (bus); + + conf = gst_element_factory_make ("fsrtpconference", NULL); + g_assert (conf); + + part = fs_conference_new_participant (FS_CONFERENCE (conf), "test@ignore", + &error); + print_error (error); + g_assert (part); + + g_assert (gst_bin_add (GST_BIN (pipeline), conf)); + + + add_audio_session (pipeline, FS_CONFERENCE (conf), 1, part, localport, + remoteip, remoteport); + + + g_assert (gst_element_set_state (pipeline, GST_STATE_PLAYING) != + GST_STATE_CHANGE_FAILURE); + + g_main_loop_run (loop); + + g_assert (gst_element_set_state (pipeline, GST_STATE_NULL) != + GST_STATE_CHANGE_FAILURE); + + g_object_unref (part); + + gst_object_unref (pipeline); + g_main_loop_unref (loop); + + return 0; +} diff --git a/examples/gui/Makefile.am b/examples/gui/Makefile.am new file mode 100644 index 00000000..10a8a396 --- /dev/null +++ b/examples/gui/Makefile.am @@ -0,0 +1 @@ +EXTRA_DIST = fs2-gui.glade fs2_gui_net.py fs2-gui.py diff --git a/examples/gui/fs2-gui.glade b/examples/gui/fs2-gui.glade new file mode 100644 index 00000000..b1b41517 --- /dev/null +++ b/examples/gui/fs2-gui.glade @@ -0,0 +1,705 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd"> +<!--Generated with glade3 3.4.0 on Tue Jun 17 16:01:07 2008 --> +<glade-interface> + <widget class="GtkWindow" id="main_window"> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="border_width">6</property> + <property name="resizable">False</property> + <signal name="destroy" handler="shutdown"/> + <child> + <widget class="GtkVBox" id="vbox1"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="spacing">12</property> + <child> + <widget class="GtkLabel" id="info_label"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes">You are now connected to</property> + <property name="use_markup">True</property> + <property name="use_underline">True</property> + <property name="justify">GTK_JUSTIFY_CENTER</property> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <widget class="GtkTable" id="users_table"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="n_rows">3</property> + <property name="n_columns">2</property> + <property name="column_spacing">6</property> + <property name="row_spacing">6</property> + <child> + <widget class="GtkLabel" id="label4"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes">Video:</property> + </widget> + <packing> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + <property name="x_options">GTK_FILL</property> + <property name="x_padding">6</property> + </packing> + </child> + <child> + <widget class="GtkComboBox" id="video_combobox"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <signal name="changed" handler="video_combobox_changed_cb"/> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + <property name="y_options">GTK_EXPAND</property> + </packing> + </child> + <child> + <widget class="GtkComboBox" id="audio_combobox"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <signal name="changed" handler="audio_combobox_changed_cb"/> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="y_options">GTK_EXPAND</property> + </packing> + </child> + <child> + <widget class="GtkLabel" id="label3"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes">Audio:</property> + </widget> + <packing> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="x_options">GTK_FILL</property> + <property name="x_padding">6</property> + </packing> + </child> + <child> + <widget class="GtkAspectFrame" id="preview_aspectframe"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="border_width">6</property> + <property name="label_xalign">0</property> + <property name="ratio">1.3300000429153442</property> + <child> + <widget class="GtkDrawingArea" id="preview_drawingarea"> + <property name="width_request">160</property> + <property name="height_request">120</property> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <signal name="expose_event" handler="exposed"/> + </widget> + </child> + <child> + <widget class="GtkLabel" id="frame_label"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes"><b>User</b></property> + <property name="use_markup">True</property> + </widget> + <packing> + <property name="type">label_item</property> + </packing> + </child> + </widget> + <packing> + <property name="right_attach">2</property> + </packing> + </child> + </widget> + <packing> + <property name="position">1</property> + </packing> + </child> + <child> + <widget class="GtkHButtonBox" id="hbuttonbox1"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="spacing">6</property> + <property name="layout_style">GTK_BUTTONBOX_EDGE</property> + <child> + <widget class="GtkButton" id="dtmf_button"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes">DTMF</property> + <property name="response_id">0</property> + <signal name="clicked" handler="show_dtmf"/> + </widget> + </child> + <child> + <widget class="GtkButton" id="button2"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="has_focus">True</property> + <property name="is_focus">True</property> + <property name="receives_default">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes">gtk-quit</property> + <property name="use_stock">True</property> + <property name="response_id">-4</property> + <signal name="clicked" handler="shutdown"/> + </widget> + <packing> + <property name="pack_type">GTK_PACK_END</property> + <property name="position">1</property> + </packing> + </child> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">2</property> + </packing> + </child> + </widget> + </child> + </widget> + <widget class="GtkWindow" id="window1"> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <child> + <widget class="GtkAspectFrame" id="user_frame"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="border_width">6</property> + <property name="label_xalign">0</property> + <property name="ratio">1.3300000429153442</property> + <child> + <widget class="GtkDrawingArea" id="user_drawingarea"> + <property name="width_request">160</property> + <property name="height_request">120</property> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <signal name="expose_event" handler="exposed"/> + </widget> + </child> + <child> + <widget class="GtkLabel" id="frame_label"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes"><b>User</b></property> + <property name="use_markup">True</property> + </widget> + <packing> + <property name="type">label_item</property> + </packing> + </child> + </widget> + </child> + </widget> + <widget class="GtkDialog" id="neworconnect_dialog"> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="border_width">5</property> + <property name="resizable">False</property> + <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property> + <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property> + <property name="has_separator">False</property> + <signal name="close" handler="quit"/> + <signal name="destroy" handler="quit"/> + <child internal-child="vbox"> + <widget class="GtkVBox" id="dialog-vbox2"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="spacing">2</property> + <child> + <widget class="GtkTable" id="table3"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="border_width">6</property> + <property name="n_rows">3</property> + <property name="n_columns">3</property> + <property name="column_spacing">12</property> + <property name="row_spacing">12</property> + <child> + <widget class="GtkLabel" id="label1"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + </widget> + <packing> + <property name="left_attach">2</property> + <property name="right_attach">3</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + </packing> + </child> + <child> + <widget class="GtkImage" id="image5"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="yalign">0</property> + <property name="icon_size">6</property> + <property name="icon_name">start-here</property> + </widget> + </child> + <child> + <widget class="GtkEntry" id="newip_entry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="text" translatable="yes">127.0.0.1</property> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">3</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="y_options">GTK_FILL</property> + </packing> + </child> + <child> + <widget class="GtkLabel" id="label9"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes">Port: </property> + </widget> + <packing> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + <property name="y_options">GTK_FILL</property> + </packing> + </child> + <child> + <widget class="GtkLabel" id="label10"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes">IP address: </property> + </widget> + <packing> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="y_options">GTK_FILL</property> + </packing> + </child> + <child> + <widget class="GtkLabel" id="label11"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes">Enter the IP address and the port of the server to connect to, or only the port if you want to start a new server</property> + <property name="wrap">True</property> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">3</property> + </packing> + </child> + <child> + <widget class="GtkSpinButton" id="newport_spinbutton"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="max_length">5</property> + <property name="width_chars">5</property> + <property name="adjustment">9392 0 65535 1 10 10</property> + <property name="snap_to_ticks">True</property> + <property name="numeric">True</property> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + <property name="x_options">GTK_SHRINK</property> + </packing> + </child> + </widget> + <packing> + <property name="position">1</property> + </packing> + </child> + <child internal-child="action_area"> + <widget class="GtkHButtonBox" id="dialog-action_area2"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="layout_style">GTK_BUTTONBOX_END</property> + <child> + <widget class="GtkButton" id="button7"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes">gtk-close</property> + <property name="use_stock">True</property> + <property name="response_id">-4</property> + <signal name="clicked" handler="quit"/> + </widget> + </child> + <child> + <widget class="GtkButton" id="button6"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="response_id">2</property> + <signal name="clicked" handler="new_server"/> + <child> + <widget class="GtkHBox" id="hbox3"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <child> + <widget class="GtkImage" id="image3"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="stock">gtk-new</property> + </widget> + <packing> + <property name="expand">False</property> + </packing> + </child> + <child> + <widget class="GtkLabel" id="label5"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes">_New server</property> + <property name="use_underline">True</property> + </widget> + <packing> + <property name="position">1</property> + </packing> + </child> + </widget> + </child> + </widget> + <packing> + <property name="position">1</property> + </packing> + </child> + <child> + <widget class="GtkButton" id="button5"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes">gtk-connect</property> + <property name="use_stock">True</property> + <property name="response_id">1</property> + <signal name="clicked" handler="connect"/> + </widget> + <packing> + <property name="position">2</property> + </packing> + </child> + </widget> + <packing> + <property name="expand">False</property> + <property name="pack_type">GTK_PACK_END</property> + </packing> + </child> + </widget> + </child> + </widget> + <widget class="GtkWindow" id="dtmf_window"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="title" translatable="yes">Send DTMF</property> + <child> + <widget class="GtkVBox" id="vbox2"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="spacing">12</property> + <child> + <widget class="GtkVBox" id="vbox3"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <child> + <widget class="GtkLabel" id="label2"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes">Send DTMF as:</property> + </widget> + </child> + <child> + <widget class="GtkRadioButton" id="dtmf_as_event"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes">RFC-4733 DTMF Event </property> + <property name="response_id">0</property> + <property name="active">True</property> + <property name="draw_indicator">True</property> + </widget> + <packing> + <property name="position">1</property> + </packing> + </child> + <child> + <widget class="GtkRadioButton" id="dtmf_as_sound"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes">Sound</property> + <property name="response_id">0</property> + <property name="draw_indicator">True</property> + <property name="group">dtmf_as_event</property> + </widget> + <packing> + <property name="position">2</property> + </packing> + </child> + </widget> + <packing> + <property name="padding">6</property> + </packing> + </child> + <child> + <widget class="GtkTable" id="table1"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="n_rows">4</property> + <property name="n_columns">3</property> + <property name="column_spacing">6</property> + <property name="row_spacing">6</property> + <child> + <widget class="GtkButton" id="dtmf_pound"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes">#</property> + <property name="response_id">0</property> + <signal name="pressed" handler="dtmf_start" object="#"/> + <signal name="released" handler="dtmf_stop" object="#"/> + </widget> + <packing> + <property name="left_attach">2</property> + <property name="right_attach">3</property> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> + </packing> + </child> + <child> + <widget class="GtkButton" id="dtmf_0"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes">0</property> + <property name="response_id">0</property> + <signal name="pressed" handler="dtmf_start" object="0"/> + <signal name="released" handler="dtmf_stop" object="0"/> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> + </packing> + </child> + <child> + <widget class="GtkButton" id="dtmf_star"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes">*</property> + <property name="response_id">0</property> + <signal name="pressed" handler="dtmf_start" object="*"/> + <signal name="released" handler="dtmf_stop" object="*"/> + </widget> + <packing> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> + </packing> + </child> + <child> + <widget class="GtkButton" id="dtmf_9"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes">9</property> + <property name="response_id">0</property> + <signal name="pressed" handler="dtmf_start" object="9"/> + <signal name="released" handler="dtmf_stop" object="9"/> + </widget> + <packing> + <property name="left_attach">2</property> + <property name="right_attach">3</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + </packing> + </child> + <child> + <widget class="GtkButton" id="dtmf_8"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes">8</property> + <property name="response_id">0</property> + <signal name="pressed" handler="dtmf_start" object="8"/> + <signal name="released" handler="dtmf_stop" object="8"/> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + </packing> + </child> + <child> + <widget class="GtkButton" id="dtmf_7"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes">7</property> + <property name="response_id">0</property> + <signal name="pressed" handler="dtmf_start" object="7"/> + <signal name="released" handler="dtmf_stop" object="7"/> + </widget> + <packing> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + </packing> + </child> + <child> + <widget class="GtkButton" id="dtmf_6"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes">6</property> + <property name="response_id">0</property> + <signal name="pressed" handler="dtmf_start" object="6"/> + <signal name="released" handler="dtmf_stop" object="6"/> + </widget> + <packing> + <property name="left_attach">2</property> + <property name="right_attach">3</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + </packing> + </child> + <child> + <widget class="GtkButton" id="dtmf_5"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes">5</property> + <property name="response_id">0</property> + <signal name="pressed" handler="dtmf_start" object="5"/> + <signal name="released" handler="dtmf_stop" object="5"/> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + </packing> + </child> + <child> + <widget class="GtkButton" id="dtmf_4"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes">4</property> + <property name="response_id">0</property> + <signal name="pressed" handler="dtmf_start" object="4"/> + <signal name="released" handler="dtmf_stop" object="4"/> + </widget> + <packing> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + </packing> + </child> + <child> + <widget class="GtkButton" id="dtmf_3"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes">3</property> + <property name="response_id">0</property> + <signal name="pressed" handler="dtmf_start" object="3"/> + <signal name="released" handler="dtmf_stop" object="3"/> + </widget> + <packing> + <property name="left_attach">2</property> + <property name="right_attach">3</property> + </packing> + </child> + <child> + <widget class="GtkButton" id="dtmf_2"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes">2</property> + <property name="response_id">0</property> + <signal name="pressed" handler="dtmf_start" object="2"/> + <signal name="released" handler="dtmf_stop" object="2"/> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + </packing> + </child> + <child> + <widget class="GtkButton" id="dtmf_1"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes">1</property> + <property name="response_id">0</property> + <signal name="pressed" handler="dtmf_start" object="1"/> + <signal name="released" handler="dtmf_stop" object="1"/> + </widget> + </child> + </widget> + <packing> + <property name="position">1</property> + </packing> + </child> + <child> + <widget class="GtkHBox" id="hbox1"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="spacing">6</property> + <child> + <widget class="GtkButton" id="button1"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes">gtk-close</property> + <property name="use_stock">True</property> + <property name="response_id">0</property> + <signal name="clicked" handler="dtmf_destroy"/> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="padding">6</property> + <property name="pack_type">GTK_PACK_END</property> + </packing> + </child> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="padding">6</property> + <property name="position">2</property> + </packing> + </child> + </widget> + </child> + </widget> +</glade-interface> diff --git a/examples/gui/fs2-gui.py b/examples/gui/fs2-gui.py new file mode 100755 index 00000000..f7446188 --- /dev/null +++ b/examples/gui/fs2-gui.py @@ -0,0 +1,929 @@ +#!/usr/bin/python + +# Farsight 2 demo GUI program +# +# Copyright (C) 2007 Collabora, Nokia +# @author: Olivier Crete <olivier.crete@collabora.co.uk> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# + +import sys, os, pwd, os.path +import socket +import threading +import weakref + +import signal + +try: + import pygtk + pygtk.require("2.0") + + import gtk, gtk.glade, gobject, gtk.gdk + import gobject +except ImportError, e: + raise SystemExit("PyGTK couldn't be found ! (%s)" % (e[0])) + +try: + import pygst + pygst.require('0.10') + + import gst +except ImportError, e: + raise SystemExit("Gst-Python couldn't be found! (%s)" % (e[0])) +try: + import farsight +except: + try: + sys.path.append(os.path.join(os.path.dirname(__file__), + '..', '..', 'python', '.libs')) + import farsight + except ImportError, e: + raise SystemExit("Farsight couldn't be found! (%s)" % (e[0])) + + + +from fs2_gui_net import FsUIClient, FsUIListener, FsUIServer + +CAMERA=False + +AUDIO=True +VIDEO=True + +CLIENT=1 +SERVER=2 + +TRANSMITTER="rawudp" + +mycname = "".join((pwd.getpwuid(os.getuid())[0], + "-" , + str(os.getpid()), + "@", + socket.gethostname())) + +gladefile = os.path.join(os.path.dirname(__file__),"fs2-gui.glade") + + +def make_video_sink(pipeline, xid, name, async=True): + "Make a bin with a video sink in it, that will be displayed on xid." + bin = gst.Bin("videosink_%d" % xid) + sink = gst.element_factory_make("ximagesink", name) + sink.set_property("sync", async) + sink.set_property("async", async) + bin.add(sink) + colorspace = gst.element_factory_make("ffmpegcolorspace") + bin.add(colorspace) + videoscale = gst.element_factory_make("videoscale") + bin.add(videoscale) + videoscale.link(colorspace) + colorspace.link(sink) + bin.add_pad(gst.GhostPad("sink", videoscale.get_pad("sink"))) + sink.set_data("xid", xid) + return bin + + +class FsUIPipeline: + "Object to wrap the GstPipeline" + + def int_handler(self, sig, frame): + try: + gst.DEBUG_BIN_TO_DOT_FILE(self.pipeline, 0, "pipelinedump") + except: + pass + sys.exit(2) + + def __init__(self, elementname="fsrtpconference"): + self.pipeline = gst.Pipeline() + signal.signal(signal.SIGINT, self.int_handler) + notifier = farsight.ElementAddedNotifier() + notifier.connect("element-added", self.element_added_cb) + notifier.add(self.pipeline) + self.pipeline.get_bus().set_sync_handler(self.sync_handler) + self.pipeline.get_bus().add_watch(self.async_handler) + self.conf = gst.element_factory_make(elementname) + # Sets lets our own cname + self.conf.set_property("sdes-cname", mycname) + self.pipeline.add(self.conf) + if VIDEO: + self.videosource = FsUIVideoSource(self.pipeline) + self.videosession = FsUISession(self.conf, self.videosource) + if AUDIO: + self.audiosource = FsUIAudioSource(self.pipeline) + self.audiosession = FsUISession(self.conf, self.audiosource) + self.adder = None + self.pipeline.set_state(gst.STATE_PLAYING) + + def __del__(self): + self.pipeline.set_state(gst.STATE_NULL) + + def sync_handler(self, bus, message): + "Message handler to get the prepare-xwindow-id event" + if message.type == gst.MESSAGE_ELEMENT and \ + message.structure.has_name("prepare-xwindow-id"): + xid = None + element = message.src + # We stored the XID on the element or its parent on the expose event + # Now lets look it up + while not xid and element: + xid = element.get_data("xid") + element = element.get_parent() + if xid: + message.src.set_xwindow_id(xid) + return gst.BUS_DROP + return gst.BUS_PASS + + def async_handler(self, bus, message): + "Async handler to print messages" + if message.type == gst.MESSAGE_ERROR: + print message.src.get_name(), ": ", message.parse_error() + elif message.type == gst.MESSAGE_WARNING: + print message.src.get_name(), ": ", message.parse_warning() + elif message.type == gst.MESSAGE_ELEMENT: + if message.structure.has_name("dtmf-event"): + print "dtmf-event: %d" % message.structure["number"] + elif message.structure.has_name("farsight-local-candidates-prepared"): + message.structure["stream"].uistream.local_candidates_prepared() + + elif message.structure.has_name("farsight-new-local-candidate"): + message.structure["stream"].uistream.new_local_candidate( + message.structure["candidate"]) + elif message.structure.has_name("farsight-codecs-changed"): + print message.src.get_name(), ": ", message.structure.get_name() + message.structure["session"].uisession.codecs_changed() + if AUDIO and message.structure["session"] == self.audiosession.fssession: + self.codecs_changed_audio() + if VIDEO and message.structure["session"] == self.videosession.fssession: + self.codecs_changed_video() + elif message.structure.has_name("farsight-send-codec-changed"): + print message.src.get_name(), ": ", message.structure.get_name() + print "send codec changed: " + message.structure["codec"].to_string() + if AUDIO and message.structure["session"] == self.audiosession.fssession: + self.codecs_changed_audio() + if VIDEO and message.structure["session"] == self.videosession.fssession: + self.codecs_changed_video() + elif message.structure.has_name("farsight-recv-codecs-changed"): + print message.src.get_name(), ": ", message.structure.get_name() + message.structure["stream"].uistream.recv_codecs_changed( \ + message.structure["codecs"]) + + + elif message.structure.has_name("farsight-error"): + print "Async error ("+ str(message.structure["error-no"]) +"): " + message.structure["error-msg"] +" --- "+ message.structure["debug-msg"] + else: + print message.src.get_name(), ": ", message.structure.get_name() + elif message.type != gst.MESSAGE_STATE_CHANGED \ + and message.type != gst.MESSAGE_ASYNC_DONE: + print message.type + + return True + + def make_video_preview(self, xid, newsize_callback): + "Creates the preview sink" + self.previewsink = make_video_sink(self.pipeline, xid, + "previewvideosink", False) + self.pipeline.add(self.previewsink) + #Add a probe to wait for the first buffer to find the image size + self.havesize = self.previewsink.get_pad("sink").add_buffer_probe(self.have_size, + newsize_callback) + + self.previewsink.set_state(gst.STATE_PLAYING) + self.videosource.tee.link(self.previewsink) + self.pipeline.set_state(gst.STATE_PLAYING) + return self.previewsink + + def have_size(self, pad, buffer, callback): + "Callback on the first buffer to know the drawingarea size" + x = buffer.caps[0]["width"] + y = buffer.caps[0]["height"] + callback(x,y) + self.previewsink.get_pad("sink").remove_buffer_probe(self.havesize) + return True + + def link_audio_sink(self, pad): + "Link the audio sink to the pad" + print >>sys.stderr, "LINKING AUDIO SINK" + if not self.adder: + audiosink = gst.element_factory_make("alsasink") + audiosink.set_property("buffer-time", 50000) + self.pipeline.add(audiosink) + + try: + self.adder = gst.element_factory_make("liveadder") + except gst.ElementNotFoundError: + audiosink.set_state(gst.STATE_PLAYING) + pad.link(audiosink.get_pad("sink")) + return + self.pipeline.add(self.adder) + audiosink.set_state(gst.STATE_PLAYING) + self.adder.link(audiosink) + self.adder.set_state(gst.STATE_PLAYING) + convert1 = gst.element_factory_make("audioconvert") + self.pipeline.add(convert1) + resample = gst.element_factory_make("audioresample") + self.pipeline.add(resample) + convert2 = gst.element_factory_make("audioconvert") + self.pipeline.add(convert2) + convert1.link(resample) + resample.link(convert2) + convert2.link(self.adder) + pad.link(convert1.get_pad("sink")) + convert2.set_state(gst.STATE_PLAYING) + resample.set_state(gst.STATE_PLAYING) + convert1.set_state(gst.STATE_PLAYING) + + def element_added_cb(self, notifier, bin, element): + if element.get_factory().get_name() == "x264enc": + element.set_property("byte-stream", True) + element.set_property("bitrate", 128) + elif element.get_factory().get_name() == "gstrtpbin": + element.set_property("latency", 100) + + +class FsUISource: + "An abstract generic class for media sources" + + def __init__(self, pipeline): + self.pipeline = pipeline + self.tee = gst.element_factory_make("tee") + pipeline.add(self.tee) + self.tee.set_state(gst.STATE_PLAYING) + + self.source = self.make_source() + pipeline.add(self.source) + self.source.link(self.tee) + self.playcount = 0 + + def __del__(self): + self.source.set_state(gst.STATE_NULL) + self.tee.set_state(gst.STATE_NULL) + self.pipeline.remove(self.source) + self.pipeline.remove(self.tee) + + + def make_source(self): + "Creates and returns the source GstElement" + raise NotImplementedError() + + + def get_type(self): + "Returns the FsMediaType of the source." + raise NotImplementedError() + + def get_src_pad(self, name="src%d"): + "Gets a source pad from the source" + queue = gst.element_factory_make("queue") + queue.set_property("leaky", 2) + queue.set_property("max-size-time", 50*gst.MSECOND) + requestpad = self.tee.get_request_pad(name) + self.pipeline.add(queue) + requestpad.link(queue.get_static_pad("sink")) + pad = queue.get_static_pad("src") + pad.set_data("requestpad", requestpad) + pad.set_data("queue", queue) + return pad + + def put_src_pad(self, pad): + "Puts the source pad from the source" + self.pipeline.remove(pad.get_data("queue")) + self.tee.release_request_pad(pad.get_data("requestpad")) + + +class FsUIVideoSource(FsUISource): + "A Video source" + + def get_type(self): + return farsight.MEDIA_TYPE_VIDEO + + def make_source(self): + bin = gst.Bin() + if CAMERA: + source = gst.element_factory_make("v4l2src") + source.set_property("device", CAMERA) + bin.add(source) + else: + source = gst.element_factory_make("videotestsrc") + source.set_property("is-live", 1) + bin.add(source) + overlay = gst.element_factory_make("timeoverlay") + overlay.set_property("font-desc", "Sans 32") + bin.add(overlay) + source.link(overlay) + source=overlay + + filter = gst.element_factory_make("capsfilter") + filter.set_property("caps", gst.Caps("video/x-raw-yuv , width=[300,500] , height=[200,500], framerate=[20/1,30/1]")) + bin.add(filter) + source.link(filter) + + videoscale = gst.element_factory_make("videoscale") + bin.add(videoscale) + filter.link(videoscale) + + bin.add_pad(gst.GhostPad("src", videoscale.get_pad("src"))) + return bin + + + +class FsUIAudioSource(FsUISource): + "An audio source" + + def get_type(self): + return farsight.MEDIA_TYPE_AUDIO + + def make_source(self): + source = gst.element_factory_make("audiotestsrc") + source.set_property("is-live", True) + source.set_property("wave", 5) + return source + #return gst.element_factory_make("alsasrc") + #return gst.element_factory_make("gconfaudiosrc") + + + +class FsUISession: + "This is one session (audio or video depending on the source)" + + def __init__(self, conference, source): + self.conference = conference + self.source = source + self.streams = [] + self.fssession = conference.new_session(source.get_type()) + self.fssession.uisession = self + if source.get_type() == farsight.MEDIA_TYPE_VIDEO: + # We prefer H263-1998 because we know it works + # We don't know if the others do work + # We know H264 doesn't work for now or anything else + # that needs to send config data + self.fssession.set_codec_preferences( [ \ + farsight.Codec(farsight.CODEC_ID_ANY, + "THEORA", + farsight.MEDIA_TYPE_VIDEO, + 90000), + farsight.Codec(farsight.CODEC_ID_ANY, + "H264", + farsight.MEDIA_TYPE_VIDEO, + 0), + farsight.Codec(farsight.CODEC_ID_ANY, + "H263-1998", + farsight.MEDIA_TYPE_VIDEO, + 0), + farsight.Codec(farsight.CODEC_ID_ANY, + "H263", + farsight.MEDIA_TYPE_VIDEO, + 0) + ]) + elif source.get_type() == farsight.MEDIA_TYPE_AUDIO: + self.fssession.set_codec_preferences( [ \ + farsight.Codec(farsight.CODEC_ID_ANY, + "PCMA", + farsight.MEDIA_TYPE_AUDIO, + 0), + farsight.Codec(farsight.CODEC_ID_ANY, + "PCMU", + farsight.MEDIA_TYPE_AUDIO, + 0), + # The gst speexenc element breaks timestamps + farsight.Codec(farsight.CODEC_ID_DISABLE, + "SPEEX", + farsight.MEDIA_TYPE_AUDIO, + 16000), + # Sadly, vorbis is not currently compatible with live streaming :-( + farsight.Codec(farsight.CODEC_ID_DISABLE, + "VORBIS", + farsight.MEDIA_TYPE_AUDIO, + 0), + ]) + + self.sourcepad = self.source.get_src_pad() + self.sourcepad.link(self.fssession.get_property("sink-pad")) + + def __del__(self): + self.sourcepad(unlink) + self.source.put_src_pad(self.sourcepad) + def __stream_finalized(self, s): + self.streams.remove(s) + + def new_stream(self, id, participant): + "Creates a new stream for a specific participant" + transmitter_params = {} + # If its video, we start at port 9078, to make it more easy + # to differentiate it in a tcpdump log + if self.source.get_type() == farsight.MEDIA_TYPE_VIDEO and \ + TRANSMITTER == "rawudp": + cand = farsight.Candidate() + cand.component_id = farsight.COMPONENT_RTP + cand.port = 9078 + transmitter_params["preferred-local-candidates"] = [cand] + realstream = self.fssession.new_stream(participant.fsparticipant, + farsight.DIRECTION_BOTH, + TRANSMITTER, transmitter_params) + stream = FsUIStream(id, self, participant, realstream) + self.streams.append(weakref.ref(stream, self.__stream_finalized)) + return stream + + def dtmf_start(self, event, method): + if (event == "*"): + event = farsight.DTMF_EVENT_STAR + elif (event == "#"): + event = farsight.DTMF_EVENT_POUND + else: + event = int(event) + self.fssession.start_telephony_event(event, 2, method) + + def dtmf_stop(self, method): + self.fssession.stop_telephony_event(method) + + def codecs_changed(self): + "Callback from FsSession" + for s in self.streams: + try: + s().codecs_changed() + except AttributeError: + pass + + def send_stream_codecs(self, codecs, sourcestream): + for s in self.streams: + stream = s() + if stream and stream is not sourcestream: + stream.connect.send_codecs(stream.participant.id, + sourcestream.id, + codecs, + sourcestream.participant.id) + +class FsUIStream: + "One participant in one session" + + def __init__(self, id, session, participant, fsstream): + self.id = id + self.session = session + self.participant = participant + self.fsstream = fsstream + self.connect = participant.connect + self.fsstream.uistream = self + self.fsstream.connect("src-pad-added", self.__src_pad_added) + self.send_codecs = False + self.last_codecs = None + self.last_stream_codecs = None + self.candidates = [] + + def local_candidates_prepared(self): + "Callback from FsStream" + self.connect.send_candidates_done(self.participant.id, self.id) + def new_local_candidate(self, candidate): + "Callback from FsStream" + self.connect.send_candidate(self.participant.id, self.id, candidate) + def __src_pad_added(self, stream, pad, codec): + "Callback from FsStream" + if self.session.source.get_type() == farsight.MEDIA_TYPE_VIDEO: + self.participant.link_video_sink(pad) + else: + self.participant.pipeline.link_audio_sink(pad) + + def candidate(self, candidate): + "Callback for the network object." + self.candidates.append(candidate) + def candidates_done(self): + "Callback for the network object." + self.fsstream.set_remote_candidates(self.candidates) + self.candidates = [] + def codecs(self, codecs): + "Callback for the network object. Set the codecs" + + print "Remote codecs" + for c in codecs: + print "Got remote codec from %s/%s %s" % \ + (self.participant.id, self.id, c.to_string()) + oldcodecs = self.fsstream.get_property("remote-codecs") + if oldcodecs == codecs: + return + try: + self.fsstream.set_remote_codecs(codecs) + except AttributeError: + print "Tried to set codecs with 0 codec" + self.send_local_codecs() + self.send_stream_codecs() + + + def send_local_codecs(self): + "Callback for the network object." + self.send_codecs = True + self.check_send_local_codecs() + + def codecs_changed(self): + self.check_send_local_codecs() + self.send_stream_codecs() + + def check_send_local_codecs(self): + "Internal function to send our local codecs when they're ready" + if not self.send_codecs: + return + if not self.session.fssession.get_property("codecs-ready"): + print "Codecs are not ready" + return + codecs = self.session.fssession.get_property("codecs") + assert(codecs is not None and len(codecs) > 0) + if (codecs == self.last_codecs): + return + self.last_codecs = codecs + print "sending local codecs" + self.connect.send_codecs(self.participant.id, self.id, codecs) + + def send_stream_codecs(self): + if not self.connect.is_server: + return + if not self.session.fssession.get_property("codecs-ready"): + return + codecs = self.fsstream.get_property("negotiated-codecs") + if codecs: + self.session.send_stream_codecs(codecs, self) + + def recv_codecs_changed(self, codecs): + self.participant.recv_codecs_changed() + + + def __remove_from_send_codecs_to(self, participant): + self.send_codecs_to.remote(participant) + + + def send_codecs_to(self, participant): + codecs = self.fsstream.get_property("negotiated-codecs") + print "sending stream %s codecs from %s to %s" % \ + (self.id, self.participant.id, participant.id) + if codecs: + participant.connect.send_codecs(participant.id, self.id, codecs, + self.participant.id) + + +class FsUIParticipant: + "Wraps one FsParticipant, is one user remote contact" + + def __init__(self, connect, id, cname, pipeline, mainui): + self.connect = connect + self.id = id + self.cname = cname + self.pipeline = pipeline + self.mainui = mainui + self.fsparticipant = pipeline.conf.new_participant(cname) + self.outcv = threading.Condition() + self.funnel = None + self.make_widget() + self.streams = {} + if VIDEO: + self.streams[int(farsight.MEDIA_TYPE_VIDEO)] = \ + pipeline.videosession.new_stream( + int(farsight.MEDIA_TYPE_VIDEO), self) + if AUDIO: + self.streams[int(farsight.MEDIA_TYPE_AUDIO)] = \ + pipeline.audiosession.new_stream( + int(farsight.MEDIA_TYPE_AUDIO), self) + + def candidate(self, media, candidate): + "Callback for the network object." + self.streams[media].candidate(candidate) + def candidates_done(self, media): + "Callback for the network object." + self.streams[media].candidates_done() + def codecs(self, media, codecs): + "Callback for the network object." + self.streams[media].codecs(codecs) + def send_local_codecs(self): + "Callback for the network object." + for id in self.streams: + self.streams[id].send_local_codecs() + + def make_widget(self): + "Make the widget of the participant's video stream." + gtk.gdk.threads_enter() + self.glade = gtk.glade.XML(gladefile, "user_frame") + self.userframe = self.glade.get_widget("user_frame") + self.glade.get_widget("frame_label").set_text(self.cname) + self.glade.signal_autoconnect(self) + self.label = gtk.Label() + self.label.set_alignment(0,0) + self.label.show() + self.mainui.hbox_add(self.userframe, self.label) + gtk.gdk.threads_leave() + + def exposed(self, widget, *args): + """From the exposed signal, used to create the video sink + The video sink will be created here, but will only be linked when the + pad arrives and link_video_sink() is called. + """ + if not VIDEO: + return + try: + self.videosink.get_by_interface(gst.interfaces.XOverlay).expose() + except AttributeError: + try: + self.outcv.acquire() + self.videosink = make_video_sink(self.pipeline.pipeline, + widget.window.xid, + "uservideosink") + self.pipeline.pipeline.add(self.videosink) + self.funnel = gst.element_factory_make("fsfunnel") + self.pipeline.pipeline.add(self.funnel) + self.funnel.link(self.videosink) + self.havesize = self.videosink.get_pad("sink").add_buffer_probe(self.have_size) + + self.videosink.set_state(gst.STATE_PLAYING) + self.funnel.set_state(gst.STATE_PLAYING) + self.outcv.notifyAll() + finally: + self.outcv.release() + + + def have_size(self, pad, buffer): + "Callback on the first buffer to know the drawingarea size" + x = buffer.caps[0]["width"] + y = buffer.caps[0]["height"] + gtk.gdk.threads_enter() + self.glade.get_widget("user_drawingarea").set_size_request(x,y) + gtk.gdk.threads_leave() + self.videosink.get_pad("sink").remove_buffer_probe(self.havesize) + del self.havesize + return True + + + + def link_video_sink(self, pad): + """Link the video sink + + Wait for the funnnel for the video sink to be created, when it has been + created, link it. + """ + try: + self.outcv.acquire() + while self.funnel is None: + self.outcv.wait() + print >>sys.stderr, "LINKING VIDEO SINK" + pad.link(self.funnel.get_pad("sink%d")) + finally: + self.outcv.release() + + def destroy(self): + if VIDEO: + try: + self.videosink.get_pad("sink").disconnect_handler(self.havesize) + pass + except AttributeError: + pass + self.glade.get_widget("user_drawingarea").disconnect_by_func(self.exposed) + self.streams = {} + self.outcv.acquire() + self.videosink.set_locked_state(True) + self.funnel.set_locked_state(True) + self.videosink.set_state(gst.STATE_NULL) + self.funnel.set_state(gst.STATE_NULL) + self.pipeline.pipeline.remove(self.videosink) + self.pipeline.pipeline.remove(self.funnel) + del self.videosink + del self.funnel + self.outcv.release() + gtk.gdk.threads_enter() + self.userframe.destroy() + self.label.destroy() + gtk.gdk.threads_leave() + + def error(self): + "Callback for the network object." + if self.id == 1: + self.mainui.fatal_error("<b>Disconnected from server</b>") + else: + print "ERROR ON %d" % (self.id) + + def recv_codecs_changed(self): + codecs = {} + for s in self.streams: + codec = self.streams[s].fsstream.get_property("current-recv-codecs") + mediatype = self.streams[s].session.fssession.get_property("media-type") + if len(codec): + if mediatype in codecs: + codecs[mediatype] += codec + else: + codecs[mediatype] = codec + str = "" + for mt in codecs: + str += "<big>" +mt.value_nick.title() + "</big>:\n" + for c in codecs[mt]: + str += " <b>%s</b>: %s %s\n" % (c.id, + c.encoding_name, + c.clock_rate) + self.label.set_markup(str) + + def send_codecs_to(self, participant): + for sid in self.streams: + self.streams[sid].send_codecs_to(participant) + + +class FsMainUI: + "The main UI and its different callbacks" + + def __init__(self, mode, ip, port): + self.mode = mode + self.pipeline = FsUIPipeline() + self.pipeline.codecs_changed_audio = self.reset_audio_codecs + self.pipeline.codecs_changed_video = self.reset_video_codecs + self.glade = gtk.glade.XML(gladefile, "main_window") + self.glade.signal_autoconnect(self) + self.mainwindow = self.glade.get_widget("main_window") + self.audio_combobox = self.glade.get_widget("audio_combobox") + self.video_combobox = self.glade.get_widget("video_combobox") + liststore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_PYOBJECT) + self.audio_combobox.set_model(liststore) + cell = gtk.CellRendererText() + self.audio_combobox.pack_start(cell, True) + self.audio_combobox.add_attribute(cell, 'text', 0) + self.reset_audio_codecs() + liststore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_PYOBJECT) + self.video_combobox.set_model(liststore) + cell = gtk.CellRendererText() + self.video_combobox.pack_start(cell, True) + self.video_combobox.add_attribute(cell, 'text', 0) + self.reset_video_codecs() + + if mode == CLIENT: + self.client = FsUIClient(ip, port, mycname, FsUIParticipant, + self.pipeline, self) + self.glade.get_widget("info_label").set_markup( + "<b>%s</b>\nConnected to %s:%s" % (mycname, ip, port)) + elif mode == SERVER: + self.server = FsUIListener(port, FsUIServer, mycname, + FsUIParticipant, self.pipeline, self) + self.glade.get_widget("info_label").set_markup( + "<b>%s</b>\nExpecting connections on port %s" % + (mycname, self.server.port)) + + + self.mainwindow.show() + + def reset_codecs(self, combobox, fssession): + liststore = combobox.get_model() + current = fssession.get_property("current-send-codec") + liststore.clear() + for c in fssession.get_property("codecs"): + str = ("%s: %s/%s %s" % (c.id, + c.media_type.value_nick, + c.encoding_name, + c.clock_rate)) + iter = liststore.append([str, c]) + if current and c and current.id == c.id: + combobox.set_active_iter(iter) + print "active: "+ c.to_string() + + def reset_audio_codecs(self): + if AUDIO: + self.reset_codecs(self.audio_combobox, + self.pipeline.audiosession.fssession) + + def reset_video_codecs(self): + if VIDEO: + self.reset_codecs(self.video_combobox, + self.pipeline.videosession.fssession) + + def combobox_changed_cb(self, combobox, fssession): + liststore = combobox.get_model() + iter = combobox.get_active_iter() + if iter: + codec = liststore.get_value(iter, 1) + fssession.set_send_codec(codec) + + def audio_combobox_changed_cb(self, combobox): + self.combobox_changed_cb(combobox, self.pipeline.audiosession.fssession) + + def video_combobox_changed_cb(self, combobox): + self.combobox_changed_cb(combobox, self.pipeline.videosession.fssession) + + + def exposed(self, widget, *args): + "Callback from the exposed event of the widget to make the preview sink" + if not VIDEO: + return + try: + self.preview.get_by_interface(gst.interfaces.XOverlay).expose() + except AttributeError: + self.preview = self.pipeline.make_video_preview(widget.window.xid, + self.newsize) + + def newsize (self, x, y): + self.glade.get_widget("preview_drawingarea").set_size_request(x,y) + + def shutdown(self, widget=None): + gtk.main_quit() + + def hbox_add(self, widget, label): + table = self.glade.get_widget("users_table") + x = table.get_properties("n-columns")[0] + table.attach(widget, x, x+1, 0, 1) + table.attach(label, x, x+1, 1, 3, xpadding=6) + + def __del__(self): + self.mainwindow.destroy() + + def fatal_error(self, errormsg): + gtk.gdk.threads_enter() + dialog = gtk.MessageDialog(self.mainwindow, + gtk.DIALOG_MODAL, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_OK) + dialog.set_markup(errormsg); + dialog.run() + dialog.destroy() + gtk.main_quit() + gtk.gdk.threads_leave() + + def show_dtmf(self, button): + try: + self.dtmf.present() + except AttributeError: + self.dtmf = gtk.glade.XML(gladefile, "dtmf_window") + self.dtmf.signal_autoconnect(self) + + def dtmf_start(self, button): + if (self.dtmf.get_widget("dtmf_as_event").get_active()): + self.dtmf_last_method = farsight.DTMF_METHOD_RTP_RFC4733 + elif (self.dtmf.get_widget("dtmf_as_sound").get_active()): + self.dtmf_last_method = farsight.DTMF_METHOD_IN_BAND + else: + print "Invalid DTMF Method" + return + self.pipeline.audiosession.dtmf_start(button.get_label(), \ + self.dtmf_last_method) + + def dtmf_stop(self, button): + try: + self.pipeline.audiosession.dtmf_stop(self.dtmf_last_method) + del self.dtmf_last_method + except AttributeError: + pass + def dtmf_destroy(self, button): + self.dtmf.get_widget("dtmf_window").destroy() + del self.dtmf + + + +class FsUIStartup: + "Displays the startup window and then creates the FsMainUI" + + def __init__(self): + self.glade = gtk.glade.XML(gladefile, "neworconnect_dialog") + self.dialog = self.glade.get_widget("neworconnect_dialog") + self.glade.get_widget("newport_spinbutton").set_value(9893) + self.glade.signal_autoconnect(self) + self.dialog.show() + self.acted = False + + def action(self, mode): + port = self.glade.get_widget("newport_spinbutton").get_value_as_int() + ip = self.glade.get_widget("newip_entry").get_text() + try: + self.ui = FsMainUI(mode, ip, port) + self.acted = True + self.dialog.destroy() + del self.glade + del self.dialog + except socket.error, e: + dialog = gtk.MessageDialog(self.dialog, + gtk.DIALOG_MODAL, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_OK) + dialog.set_markup("<b>Could not connect to %s %d</b>" % (ip,port)) + dialog.format_secondary_markup(e[1]) + dialog.run() + dialog.destroy() + + def new_server(self, widget): + self.action(SERVER) + + def connect(self, widget): + self.action(CLIENT) + + + def quit(self, widget): + if not self.acted: + gtk.main_quit() + + + + +if __name__ == "__main__": + if len(sys.argv) >= 2: + CAMERA = sys.argv[1] + else: + CAMERA = None + + gobject.threads_init() + gtk.gdk.threads_init() + startup = FsUIStartup() + gtk.main() diff --git a/examples/gui/fs2_gui_net.py b/examples/gui/fs2_gui_net.py new file mode 100644 index 00000000..c834d77a --- /dev/null +++ b/examples/gui/fs2_gui_net.py @@ -0,0 +1,456 @@ +#!/usr/bin/python + +# Farsight 2 simple network signalling library for the demo GUI +# +# Copyright (C) 2007 Collabora, Nokia +# @author: Olivier Crete <olivier.crete@collabora.co.uk> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# + +# +# This is the signalling code used by fs2-gui.py +# + +import sys, os, pwd, os.path +import socket, struct +import gc + + +try: + import pygst + pygst.require('0.10') + + import gst +except ImportError, e: + raise SystemExit("Gst-Python couldn't be found! (%s)" % (e[0])) + +try: + import farsight +except: + sys.path.append(os.path.join(os.path.dirname(__file__), + '..', '..', 'python', '.libs')) + import farsight + +import gobject + +class FsUIConnect: + ERROR = 0 + CODECS = 1 + CANDIDATE = 2 + CANDIDATES_DONE = 3 + INTRO = 4 + + def __reset(self): + self.type = None + self.media = None + self.size = struct.calcsize("!IIIIII") + self.data = "" + self.dest = -1 + self.src = -1 + + + def __init__(self, sock, callbacks, myid=0): + self.sock = sock + self.__reset() + self.callbacks = callbacks + self.myid = myid + self.partid = 1 + self.is_server = True + sock.setblocking(0) + gobject.io_add_watch(self.sock.fileno(), gobject.IO_IN, + self.__data_in) + gobject.io_add_watch(self.sock.fileno(), + gobject.IO_ERR | gobject.IO_HUP, + self.__error) + + def __error(self, source, condition): + print "have error" + if (self.src >= 0): + self.callbacks[self.ERROR](self.src) + else: + self.callbacks[self.ERROR](self.partid) + return False + + def __data_in(self, source, condition): + data = self.sock.recv(self.size-len(self.data)) + + if len(data) == 0: + print "received nothing" + if (self.src >= 0): + self.callbacks[self.ERROR](self.src) + else: + self.callbacks[self.ERROR](self.partid) + return False + + self.data += data + if len(self.data) == self.size: + if self.type is not None: + if self.type == self.CODECS: + data = self.__codecs_from_string(data) + elif self.type == self.CANDIDATE: + data = self.__candidate_from_string(data) + else: + data = self.data + self.callbacks[self.type](self.src, self.dest, + self.media, data) + self.__reset() + else: + (check, + self.src, + self.dest, + self.type, + self.media, + self.size) = struct.unpack("!IIIIII", self.data) + if check != 0xDEADBEEF: + print "CORRUPTION" + sys.exit(1) + if self.myid > 1 and self.dest != self.myid: + print "GOT MESSAGE FOR %d, but I am %d" % (self.dest, + self.myid) + sys.exit(1) + self.data="" + if self.size == 0: + self.callbacks[self.type](self.src, self.dest, + self.media, None) + self.__reset() + return True + + def __send_data(self, dest, type, media=0, data="", src=None): + if src is None: src = self.myid + if src == 0 and type != self.INTRO: raise Exception + try: + self.sock.sendall(struct.pack("!IIIIII", + 0xDEADBEEF, + int(src), + int(dest), + int(type), + int(media), + len(data))) + self.sock.sendall(data) + except socket.error: + print "have error" + self.callbacks[self.ERROR](self.partid) + + + def send_error(self, dest, src): + self.__send_data(dest, self.ERROR, src=src) + def send_intro(self, dest, cname, src=None): + self.__send_data(dest, self.INTRO, data=cname, src=src) + def send_codecs(self, dest, media, codecs, src=None): + self.__send_data(dest, self.CODECS, + media=media, + data=self.__codecs_to_string(codecs), src=src) + def send_candidate(self, dest, media, candidate, src=None): + self.__send_data(dest, self.CANDIDATE, media=media, + data=self.__candidate_to_string(candidate), src=src) + def send_candidates_done(self, dest, media, src=None): + self.__send_data(dest, self.CANDIDATES_DONE, media=media, src=src) + + def __del__(self): + try: + self.sock.close() + except AttributeError: + pass + + + def __candidate_to_string(self, candidate): + return "|".join(( + candidate.foundation, + str(candidate.component_id), + candidate.ip, + str(candidate.port), + candidate.base_ip, + str(candidate.base_port), + str(int(candidate.proto)), + str(candidate.priority), + str(int(candidate.type)), + candidate.username, + candidate.password)) + + def __candidate_from_string(self, string): + candidate = farsight.Candidate() + (candidate.foundation, + component_id, + candidate.ip, + port, + candidate.base_ip, + base_port, + proto, + priority, + type, + candidate.username, + candidate.password) = string.split("|") + candidate.component_id = int(component_id) + candidate.port = int(port) + candidate.base_port = int(base_port) + candidate.proto = int(proto) + candidate.priority = int(priority) + candidate.type = int(type) + return candidate + + def __codecs_to_string(self, codecs): + codec_strings = [] + for codec in codecs: + start = " ".join((str(codec.id), + codec.encoding_name, + str(int(codec.media_type)), + str(codec.clock_rate), + str(codec.channels))) + codec = "".join((start, + "|", + ";".join(["=".join(i) for i in codec.optional_params]))) + codec_strings.append(codec) + + return "\n".join(codec_strings) + + + def __codecs_from_string(self, string): + codecs = [] + for substring in string.split("\n"): + (start,end) = substring.split("|") + (id, encoding_name, media_type, clock_rate, channels) = start.split(" ") + codec = farsight.Codec(int(id), encoding_name, int(media_type), + int(clock_rate)) + codec.channels = int(channels) + if len(end): + codec.optional_params = \ + [tuple(x.split("=",1)) for x in end.split(";") if len(x) > 0] + codecs.append(codec) + return codecs + +class FsUIConnectClient (FsUIConnect): + def __init__(self, ip, port, callbacks): + sock = socket.socket() + sock.connect((ip, port)) + FsUIConnect.__init__(self, sock, callbacks) + self.is_server = False + +class FsUIListener: + def __init__(self, port, callback, *args): + self.sock = socket.socket() + self.callback = callback + self.args = args + bound = False + while not bound: + try: + self.sock.bind(("", port)) + bound = True + except socket.error, e: + port += 1 + self.port = port + print "Bound to port ", port + self.sock.setblocking(0) + gobject.io_add_watch(self.sock.fileno(), gobject.IO_IN, self.data_in) + gobject.io_add_watch(self.sock.fileno(), + gobject.IO_ERR | gobject.IO_HUP, + self.error) + self.sock.listen(3) + + def error(self, source, condition): + print "Error on listen" + sys.exit(1) + return False + + def data_in(self, source, condition): + (sock,addr) = self.sock.accept() + self.callback(sock, *self.args) + return True + +class FsUIClient: + def __init__(self, ip, port, cname, get_participant, *args): + self.participants = {} + self.get_participant = get_participant + self.args = args + self.cname = cname + self.connect = FsUIConnectClient(ip, port, (self.__error, + self.__codecs, + self.__candidate, + self.__candidate_done, + self.__intro)) + self.connect.send_intro(1, cname) + + def __codecs(self, src, dest, media, data): + print "Got codec Src:%d dest:%d data:%s" % (src, dest, data) + self.participants[src].codecs(media, data) + def __candidate(self, src, dest, media, data): + self.participants[src].candidate(media, data) + def __candidate_done(self, src, dest, media, data): + self.participants[src].candidates_done(media) + def __intro(self, src, dest, media, cname): + print "Got Intro from %s, I am %d" % (src, dest) + if src == 1: + self.connect.myid = dest + if not self.participants.has_key(src): + if src != 1: + self.connect.send_intro(src, self.cname) + self.participants[src] = self.get_participant(self.connect, src, + cname, + *self.args) + def __error(self, participantid, *arg): + print "Client Error", participantid + if participantid == 1: + # Communication error with server, its over + self.participants[participantid].error() + else: + self.participants[participantid].destroy() + del self.participants[participantid] + gc.collect() + + +class FsUIServer: + nextid = 2 + participants = {} + + def __init__(self, sock, cname, get_participant, *args): + self.cname = cname + self.get_participant = get_participant + self.args = args + self.connect = FsUIConnect(sock, (self.__error, + self.__codecs, + self.__candidate, + self.__candidate_done, + self.__intro), 1) + def __codecs(self, src, dest, media, data): + FsUIServer.participants[src].codecs(media, data) + def __candidate(self, src, dest, media, data): + if dest == 1: + FsUIServer.participants[src].candidate(media, data) + else: + print data + FsUIServer.participants[dest].connect.send_candidate(dest, + media, + data, + src) + def __candidate_done(self, src, dest, media, data): + if dest == 1: + FsUIServer.participants[src].candidates_done(media) + else: + FsUIServer.participants[dest].connect.send_candidates_done(dest, + media, + src) + def __intro(self, src, dest, media, cname): + print "Got Intro from %s to %s" % (src, dest) + if src == 0 and dest == 1: + newid = FsUIServer.nextid + # Forward the introduction to all other participants + for pid in FsUIServer.participants: + print "Sending from %d to %d" % (newid, pid) + FsUIServer.participants[pid].connect.send_intro(pid, cname, + newid) + self.connect.send_intro(newid, self.cname) + self.connect.partid = newid + FsUIServer.participants[newid] = self.get_participant(self.connect, + newid, + cname, + *self.args) + FsUIServer.participants[newid].send_local_codecs() + FsUIServer.nextid += 1 + elif dest != 1: + FsUIServer.participants[dest].connect.send_intro(dest, + cname, + src) + FsUIServer.participants[src].send_codecs_to( + FsUIServer.participants[dest]) + else: + print "ERROR SRC != 0" + + def __error(self, participantid, *args): + print "Server Error", participantid + FsUIServer.participants[participantid].destroy() + del FsUIServer.participants[participantid] + gc.collect() + for pid in FsUIServer.participants: + FsUIServer.participants[pid].connect.send_error(pid, participantid) + +if __name__ == "__main__": + class TestMedia: + def __init__(self, pid, id, connect): + self.pid = pid + self.id = id + self.connect = connect + candidate = farsight.Candidate() + candidate.component_id = 1 + connect.send_candidate(self.pid, self.id, candidate) + connect.send_candidates_done(self.pid, self.id) + def candidate(self, candidate): + print "Got candidate", candidate + def candidates_done(self): + print "Got candidate done" + def codecs(self, codecs): + if self.connect.myid != 1: + self.connect.send_codecs(1, self.id, + [farsight.Codec(self.connect.myid, + "codec-name", + self.pid, + self.id)]) + + def send_local_codecs(self): + print "Send local codecs to %s for media %s" % (self.pid, self.id) + self.connect.send_codecs(self.pid, self.id, + [farsight.Codec(self.connect.myid, + "local_codec", + self.pid, + self.id)]) + def get_codecs(self): + return [farsight.Codec(self.connect.myid, + "nego-codecs", + self.pid, + self.id)] + + + class TestParticipant: + def __init__(self, connect, id, cname, *args): + self.id = id + self.streams = {1: TestMedia(id, 1, connect), + 2: TestMedia(id, 2, connect)} + self.cname = cname + self.connect = connect + print "New Participant %s and cname %s" % (id,cname) + def candidate(self, media, candidate): + self.streams[media].candidate(candidate) + def candidates_done(self, media): + self.streams[media].candidates_done() + def codecs(self, media, codecs): + self.streams[media].codecs(codecs) + def send_local_codecs(self): + for id in self.streams: + self.streams[id].send_local_codecs() + def destroy(self): + pass + def send_codecs_to(self, participant): + for sid in self.streams: + print "to: %s from: %s" % (str(participant.id), (self.id)) + participant.connect.send_codecs(participant.id, + self.streams[sid].id, + self.streams[sid].get_codecs(), + self.id) + def error(self): + print "ERROR" + sys.exit(1) + def destroy(self): + passs + + + mycname = "test" + mainloop = gobject.MainLoop() + gobject.threads_init() + if len(sys.argv) > 1: + client = FsUIClient("127.0.0.1", int(sys.argv[1]), + "cname" + sys.argv[1], + TestParticipant) + else: + listener = FsUIListener(9893, FsUIServer, "cnameServ", TestParticipant) + mainloop.run() |