pidgin/pidgin

Use Meson summary() function.

2021-07-27, Elliott Sales de Andrade
cb640ea0f315
Use Meson summary() function.

Now that we require at least 0.52, we can use Meson's builtin summary printing to display the results of configuration.

Testing Done:
Configured with defaults, and with pixmaps disabled to trigger the warning: https://asciinema.org/a/mV2oxOoVCJNdmrPwgqqUJ3mkU?t=17

Reviewed at https://reviews.imfreedom.org/r/848/
/* purple
*
* Purple is the legal property of its developers, whose names are too numerous
* to list here. Please refer to the COPYRIGHT file distributed with this
* source distribution.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
*/
#include <glib/gi18n-lib.h>
#include "internal.h"
#include "backend-fs2.h"
#ifdef USE_VV
#include "backend-iface.h"
#include "debug.h"
#include "network.h"
#include "media-gst.h"
#include <purplekeyvaluepair.h>
#include <farstream/fs-conference.h>
#include <farstream/fs-element-added-notifier.h>
#include <farstream/fs-utils.h>
#include <gst/gststructure.h>
/** @copydoc _PurpleMediaBackendFs2Session */
typedef struct _PurpleMediaBackendFs2Session PurpleMediaBackendFs2Session;
/** @copydoc _PurpleMediaBackendFs2Stream */
typedef struct _PurpleMediaBackendFs2Stream PurpleMediaBackendFs2Stream;
static void purple_media_backend_iface_init(PurpleMediaBackendInterface *iface);
static gboolean
gst_bus_cb(GstBus *bus, GstMessage *msg, PurpleMediaBackendFs2 *self);
static void
state_changed_cb(PurpleMedia *media, PurpleMediaState state,
gchar *sid, gchar *name, PurpleMediaBackendFs2 *self);
static void
stream_info_cb(PurpleMedia *media, PurpleMediaInfoType type,
gchar *sid, gchar *name, gboolean local,
PurpleMediaBackendFs2 *self);
static gboolean purple_media_backend_fs2_add_stream(PurpleMediaBackend *self,
const gchar *sess_id, const gchar *who,
PurpleMediaSessionType type, gboolean initiator,
const gchar *transmitter,
guint num_params, GParameter *params);
static void purple_media_backend_fs2_add_remote_candidates(
PurpleMediaBackend *self,
const gchar *sess_id, const gchar *participant,
GList *remote_candidates);
static gboolean purple_media_backend_fs2_codecs_ready(PurpleMediaBackend *self,
const gchar *sess_id);
static GList *purple_media_backend_fs2_get_codecs(PurpleMediaBackend *self,
const gchar *sess_id);
static GList *purple_media_backend_fs2_get_local_candidates(
PurpleMediaBackend *self,
const gchar *sess_id, const gchar *participant);
static gboolean purple_media_backend_fs2_set_encryption_parameters (
PurpleMediaBackend *self, const gchar *sess_id, const gchar *cipher,
const gchar *auth, const gchar *key, gsize key_len);
static gboolean purple_media_backend_fs2_set_decryption_parameters(
PurpleMediaBackend *self, const gchar *sess_id,
const gchar *participant, const gchar *cipher,
const gchar *auth, const gchar *key, gsize key_len);
static gboolean purple_media_backend_fs2_set_require_encryption(
PurpleMediaBackend *self, const gchar *sess_id,
const gchar *participant, gboolean require_encryption);
static gboolean purple_media_backend_fs2_set_remote_codecs(
PurpleMediaBackend *self,
const gchar *sess_id, const gchar *participant,
GList *codecs);
static gboolean purple_media_backend_fs2_set_send_codec(
PurpleMediaBackend *self, const gchar *sess_id,
PurpleMediaCodec *codec);
static void purple_media_backend_fs2_set_params(PurpleMediaBackend *self,
guint num_params, GParameter *params);
static const gchar **purple_media_backend_fs2_get_available_params(void);
static gboolean purple_media_backend_fs2_send_dtmf(
PurpleMediaBackend *self, const gchar *sess_id,
gchar dtmf, guint8 volume, guint16 duration);
static gboolean purple_media_backend_fs2_set_send_rtcp_mux(
PurpleMediaBackend *self,
const gchar *sess_id, const gchar *participant,
gboolean send_rtcp_mux);
static void free_stream(PurpleMediaBackendFs2Stream *stream);
static void free_session(PurpleMediaBackendFs2Session *session);
/**
* PurpleMediaBackendFs2:
*
* An opaque structure representing the Farstream media backend.
*/
struct _PurpleMediaBackendFs2
{
GObject parent;
};
struct _PurpleMediaBackendFs2Stream
{
PurpleMediaBackendFs2Session *session;
gchar *participant;
FsStream *stream;
gboolean supports_add;
GstElement *src;
GstElement *tee;
GstElement *volume;
GstElement *level;
GstElement *fakesink;
GstElement *queue;
GList *local_candidates;
GList *remote_candidates;
guint connected_cb_id;
};
struct _PurpleMediaBackendFs2Session
{
PurpleMediaBackendFs2 *backend;
gchar *id;
FsSession *session;
GstElement *src;
GstElement *tee;
GstElement *srcvalve;
GstPad *srcpad;
PurpleMediaSessionType type;
};
typedef struct
{
PurpleMedia *media;
GstElement *confbin;
FsConference *conference;
gchar *conference_type;
FsElementAddedNotifier *notifier;
GHashTable *sessions;
GHashTable *participants;
GList *streams;
gdouble silence_threshold;
} PurpleMediaBackendFs2Private;
enum {
PROP_0,
PROP_CONFERENCE_TYPE,
PROP_MEDIA,
};
G_DEFINE_TYPE_WITH_CODE(PurpleMediaBackendFs2, purple_media_backend_fs2,
G_TYPE_OBJECT,
G_ADD_PRIVATE(PurpleMediaBackendFs2)
G_IMPLEMENT_INTERFACE(PURPLE_MEDIA_TYPE_BACKEND,
purple_media_backend_iface_init));
static void
purple_media_backend_fs2_init(PurpleMediaBackendFs2 *self)
{
}
static FsCandidateType
purple_media_candidate_type_to_fs(PurpleMediaCandidateType type)
{
switch (type) {
case PURPLE_MEDIA_CANDIDATE_TYPE_HOST:
return FS_CANDIDATE_TYPE_HOST;
case PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX:
return FS_CANDIDATE_TYPE_SRFLX;
case PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX:
return FS_CANDIDATE_TYPE_PRFLX;
case PURPLE_MEDIA_CANDIDATE_TYPE_RELAY:
return FS_CANDIDATE_TYPE_RELAY;
case PURPLE_MEDIA_CANDIDATE_TYPE_MULTICAST:
return FS_CANDIDATE_TYPE_MULTICAST;
}
g_return_val_if_reached(FS_CANDIDATE_TYPE_HOST);
}
static PurpleMediaCandidateType
purple_media_candidate_type_from_fs(FsCandidateType type)
{
switch (type) {
case FS_CANDIDATE_TYPE_HOST:
return PURPLE_MEDIA_CANDIDATE_TYPE_HOST;
case FS_CANDIDATE_TYPE_SRFLX:
return PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX;
case FS_CANDIDATE_TYPE_PRFLX:
return PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX;
case FS_CANDIDATE_TYPE_RELAY:
return PURPLE_MEDIA_CANDIDATE_TYPE_RELAY;
case FS_CANDIDATE_TYPE_MULTICAST:
return PURPLE_MEDIA_CANDIDATE_TYPE_MULTICAST;
}
g_return_val_if_reached(PURPLE_MEDIA_CANDIDATE_TYPE_HOST);
}
static FsNetworkProtocol
purple_media_network_protocol_to_fs(PurpleMediaNetworkProtocol protocol)
{
switch (protocol) {
case PURPLE_MEDIA_NETWORK_PROTOCOL_UDP:
return FS_NETWORK_PROTOCOL_UDP;
case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_PASSIVE:
return FS_NETWORK_PROTOCOL_TCP_PASSIVE;
case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_ACTIVE:
return FS_NETWORK_PROTOCOL_TCP_ACTIVE;
case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_SO:
return FS_NETWORK_PROTOCOL_TCP_SO;
default:
g_return_val_if_reached(FS_NETWORK_PROTOCOL_TCP);
}
}
static PurpleMediaNetworkProtocol
purple_media_network_protocol_from_fs(FsNetworkProtocol protocol)
{
switch (protocol) {
case FS_NETWORK_PROTOCOL_UDP:
return PURPLE_MEDIA_NETWORK_PROTOCOL_UDP;
case FS_NETWORK_PROTOCOL_TCP_PASSIVE:
return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_PASSIVE;
case FS_NETWORK_PROTOCOL_TCP_ACTIVE:
return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_ACTIVE;
case FS_NETWORK_PROTOCOL_TCP_SO:
return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_SO;
default:
g_return_val_if_reached(PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_PASSIVE);
}
}
static GstPadProbeReturn
event_probe_cb(GstPad *srcpad, GstPadProbeInfo *info, gpointer unused)
{
GstEvent *event = GST_PAD_PROBE_INFO_EVENT(info);
if (GST_EVENT_TYPE(event) == GST_EVENT_CUSTOM_DOWNSTREAM
&& gst_event_has_name(event, "purple-unlink-tee")) {
const GstStructure *s = gst_event_get_structure(event);
gst_pad_unlink(srcpad, gst_pad_get_peer(srcpad));
gst_pad_remove_probe(srcpad,
g_value_get_ulong(gst_structure_get_value(s, "handler-id")));
if (g_value_get_boolean(gst_structure_get_value(s, "release-pad")))
gst_element_release_request_pad(GST_ELEMENT_PARENT(srcpad), srcpad);
return GST_PAD_PROBE_DROP;
}
return GST_PAD_PROBE_OK;
}
static void
unlink_teepad_dynamic(GstPad *srcpad, gboolean release_pad)
{
gulong id = gst_pad_add_probe(srcpad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM,
event_probe_cb, NULL, NULL);
if (GST_IS_GHOST_PAD(srcpad))
srcpad = gst_ghost_pad_get_target(GST_GHOST_PAD(srcpad));
gst_element_send_event(gst_pad_get_parent_element(srcpad),
gst_event_new_custom(GST_EVENT_CUSTOM_DOWNSTREAM,
gst_structure_new("purple-unlink-tee",
"release-pad", G_TYPE_BOOLEAN, release_pad,
"handler-id", G_TYPE_ULONG, id,
NULL)));
}
static void
purple_media_backend_fs2_dispose(GObject *obj)
{
PurpleMediaBackendFs2Private *priv =
purple_media_backend_fs2_get_instance_private(
PURPLE_MEDIA_BACKEND_FS2(obj));
GList *iter = NULL;
purple_debug_info("backend-fs2", "purple_media_backend_fs2_dispose\n");
if (priv->notifier) {
g_object_unref(priv->notifier);
priv->notifier = NULL;
}
if (priv->confbin) {
GstElement *pipeline;
pipeline = purple_media_manager_get_pipeline(
purple_media_get_manager(priv->media));
/* All connections to media sources should be blocked before confbin is
* removed, to prevent freezing of any other simultaneously running
* media calls. */
if (priv->sessions) {
GList *sessions = g_hash_table_get_values(priv->sessions);
for (; sessions; sessions =
g_list_delete_link(sessions, sessions)) {
PurpleMediaBackendFs2Session *session = sessions->data;
if (session->srcpad) {
unlink_teepad_dynamic(session->srcpad, FALSE);
gst_object_unref(session->srcpad);
session->srcpad = NULL;
}
}
}
gst_element_set_locked_state(priv->confbin, TRUE);
gst_element_set_state(GST_ELEMENT(priv->confbin),
GST_STATE_NULL);
if (pipeline) {
GstBus *bus;
gst_bin_remove(GST_BIN(pipeline), priv->confbin);
bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
g_signal_handlers_disconnect_matched(G_OBJECT(bus),
G_SIGNAL_MATCH_FUNC |
G_SIGNAL_MATCH_DATA,
0, 0, 0, gst_bus_cb, obj);
gst_object_unref(bus);
} else {
purple_debug_warning("backend-fs2", "Unable to "
"properly dispose the conference. "
"Couldn't get the pipeline.\n");
}
priv->confbin = NULL;
priv->conference = NULL;
}
if (priv->sessions) {
GList *sessions = g_hash_table_get_values(priv->sessions);
for (; sessions; sessions =
g_list_delete_link(sessions, sessions)) {
PurpleMediaBackendFs2Session *session =
sessions->data;
if (session->session) {
g_object_unref(session->session);
session->session = NULL;
}
}
}
if (priv->participants) {
g_hash_table_destroy(priv->participants);
priv->participants = NULL;
}
for (iter = priv->streams; iter; iter = g_list_next(iter)) {
PurpleMediaBackendFs2Stream *stream = iter->data;
if (stream->stream) {
g_object_unref(stream->stream);
stream->stream = NULL;
}
}
if (priv->media) {
g_object_remove_weak_pointer(G_OBJECT(priv->media),
(gpointer*)&priv->media);
priv->media = NULL;
}
G_OBJECT_CLASS(purple_media_backend_fs2_parent_class)->dispose(obj);
}
static void
purple_media_backend_fs2_finalize(GObject *obj)
{
PurpleMediaBackendFs2Private *priv =
purple_media_backend_fs2_get_instance_private(
PURPLE_MEDIA_BACKEND_FS2(obj));
purple_debug_info("backend-fs2", "purple_media_backend_fs2_finalize\n");
g_free(priv->conference_type);
g_list_free_full(priv->streams, (GDestroyNotify)free_stream);
if (priv->sessions) {
GList *sessions = g_hash_table_get_values(priv->sessions);
g_list_free_full(sessions, (GDestroyNotify)free_session);
g_hash_table_destroy(priv->sessions);
}
G_OBJECT_CLASS(purple_media_backend_fs2_parent_class)->finalize(obj);
}
static void
purple_media_backend_fs2_set_property(GObject *object, guint prop_id,
const GValue *value, GParamSpec *pspec)
{
PurpleMediaBackendFs2Private *priv;
g_return_if_fail(PURPLE_MEDIA_IS_BACKEND_FS2(object));
priv = purple_media_backend_fs2_get_instance_private(
PURPLE_MEDIA_BACKEND_FS2(object));
switch (prop_id) {
case PROP_CONFERENCE_TYPE:
priv->conference_type = g_value_dup_string(value);
break;
case PROP_MEDIA:
priv->media = g_value_get_object(value);
if (priv->media == NULL)
break;
g_object_add_weak_pointer(G_OBJECT(priv->media),
(gpointer*)&priv->media);
g_signal_connect(G_OBJECT(priv->media),
"state-changed",
G_CALLBACK(state_changed_cb),
PURPLE_MEDIA_BACKEND_FS2(object));
g_signal_connect(G_OBJECT(priv->media), "stream-info",
G_CALLBACK(stream_info_cb),
PURPLE_MEDIA_BACKEND_FS2(object));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(
object, prop_id, pspec);
break;
}
}
static void
purple_media_backend_fs2_get_property(GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec)
{
PurpleMediaBackendFs2Private *priv;
g_return_if_fail(PURPLE_MEDIA_IS_BACKEND_FS2(object));
priv = purple_media_backend_fs2_get_instance_private(
PURPLE_MEDIA_BACKEND_FS2(object));
switch (prop_id) {
case PROP_CONFERENCE_TYPE:
g_value_set_string(value, priv->conference_type);
break;
case PROP_MEDIA:
g_value_set_object(value, priv->media);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(
object, prop_id, pspec);
break;
}
}
static void
purple_media_backend_fs2_class_init(PurpleMediaBackendFs2Class *klass)
{
GObjectClass *gobject_class = (GObjectClass*)klass;
GList *features;
gobject_class->dispose = purple_media_backend_fs2_dispose;
gobject_class->finalize = purple_media_backend_fs2_finalize;
gobject_class->set_property = purple_media_backend_fs2_set_property;
gobject_class->get_property = purple_media_backend_fs2_get_property;
g_object_class_override_property(gobject_class, PROP_CONFERENCE_TYPE,
"conference-type");
g_object_class_override_property(gobject_class, PROP_MEDIA, "media");
/* VA-API elements aren't well supported in Farstream. Ignore them. */
features = gst_registry_get_feature_list_by_plugin(gst_registry_get(),
"vaapi");
g_list_foreach(features, (GFunc)gst_plugin_feature_set_rank, GINT_TO_POINTER(GST_RANK_NONE));
gst_plugin_feature_list_free(features);
}
static void
purple_media_backend_iface_init(PurpleMediaBackendInterface *iface)
{
iface->add_stream = purple_media_backend_fs2_add_stream;
iface->add_remote_candidates =
purple_media_backend_fs2_add_remote_candidates;
iface->codecs_ready = purple_media_backend_fs2_codecs_ready;
iface->get_codecs = purple_media_backend_fs2_get_codecs;
iface->get_local_candidates =
purple_media_backend_fs2_get_local_candidates;
iface->set_remote_codecs = purple_media_backend_fs2_set_remote_codecs;
iface->set_send_codec = purple_media_backend_fs2_set_send_codec;
iface->set_encryption_parameters =
purple_media_backend_fs2_set_encryption_parameters;
iface->set_decryption_parameters =
purple_media_backend_fs2_set_decryption_parameters;
iface->set_require_encryption =
purple_media_backend_fs2_set_require_encryption;
iface->set_params = purple_media_backend_fs2_set_params;
iface->get_available_params = purple_media_backend_fs2_get_available_params;
iface->send_dtmf = purple_media_backend_fs2_send_dtmf;
iface->set_send_rtcp_mux = purple_media_backend_fs2_set_send_rtcp_mux;
}
static FsMediaType
session_type_to_fs_media_type(PurpleMediaSessionType type)
{
if (type & PURPLE_MEDIA_AUDIO)
return FS_MEDIA_TYPE_AUDIO;
else if (type & PURPLE_MEDIA_VIDEO)
return FS_MEDIA_TYPE_VIDEO;
#ifdef HAVE_MEDIA_APPLICATION
else if (type & PURPLE_MEDIA_APPLICATION)
return FS_MEDIA_TYPE_APPLICATION;
#endif
else
return 0;
}
static FsStreamDirection
session_type_to_fs_stream_direction(PurpleMediaSessionType type)
{
if ((type & PURPLE_MEDIA_AUDIO) == PURPLE_MEDIA_AUDIO ||
(type & PURPLE_MEDIA_VIDEO) == PURPLE_MEDIA_VIDEO)
return FS_DIRECTION_BOTH;
else if ((type & PURPLE_MEDIA_SEND_AUDIO) ||
(type & PURPLE_MEDIA_SEND_VIDEO))
return FS_DIRECTION_SEND;
else if ((type & PURPLE_MEDIA_RECV_AUDIO) ||
(type & PURPLE_MEDIA_RECV_VIDEO))
return FS_DIRECTION_RECV;
#ifdef HAVE_MEDIA_APPLICATION
else if ((type & PURPLE_MEDIA_APPLICATION) == PURPLE_MEDIA_APPLICATION)
return FS_DIRECTION_BOTH;
else if (type & PURPLE_MEDIA_SEND_APPLICATION)
return FS_DIRECTION_SEND;
else if (type & PURPLE_MEDIA_RECV_APPLICATION)
return FS_DIRECTION_RECV;
#endif
else
return FS_DIRECTION_NONE;
}
static PurpleMediaSessionType
session_type_from_fs(FsMediaType type, FsStreamDirection direction)
{
PurpleMediaSessionType result = PURPLE_MEDIA_NONE;
if (type == FS_MEDIA_TYPE_AUDIO) {
if (direction & FS_DIRECTION_SEND)
result |= PURPLE_MEDIA_SEND_AUDIO;
if (direction & FS_DIRECTION_RECV)
result |= PURPLE_MEDIA_RECV_AUDIO;
} else if (type == FS_MEDIA_TYPE_VIDEO) {
if (direction & FS_DIRECTION_SEND)
result |= PURPLE_MEDIA_SEND_VIDEO;
if (direction & FS_DIRECTION_RECV)
result |= PURPLE_MEDIA_RECV_VIDEO;
#ifdef HAVE_MEDIA_APPLICATION
} else if (type == FS_MEDIA_TYPE_APPLICATION) {
if (direction & FS_DIRECTION_SEND)
result |= PURPLE_MEDIA_SEND_APPLICATION;
if (direction & FS_DIRECTION_RECV)
result |= PURPLE_MEDIA_RECV_APPLICATION;
#endif
}
return result;
}
static FsCandidate *
candidate_to_fs(PurpleMediaCandidate *candidate)
{
FsCandidate *fscandidate;
gchar *foundation;
guint component_id;
gchar *ip;
guint port;
gchar *base_ip;
guint base_port;
PurpleMediaNetworkProtocol proto;
guint32 priority;
PurpleMediaCandidateType type;
gchar *username;
gchar *password;
guint ttl;
if (candidate == NULL)
return NULL;
g_object_get(G_OBJECT(candidate),
"foundation", &foundation,
"component-id", &component_id,
"ip", &ip,
"port", &port,
"base-ip", &base_ip,
"base-port", &base_port,
"protocol", &proto,
"priority", &priority,
"type", &type,
"username", &username,
"password", &password,
"ttl", &ttl,
NULL);
fscandidate = fs_candidate_new(foundation,
component_id, purple_media_candidate_type_to_fs(type),
purple_media_network_protocol_to_fs(proto), ip, port);
fscandidate->base_ip = base_ip;
fscandidate->base_port = base_port;
fscandidate->priority = priority;
fscandidate->username = username;
fscandidate->password = password;
fscandidate->ttl = ttl;
g_free(foundation);
g_free(ip);
return fscandidate;
}
static PurpleMediaCandidate *
candidate_from_fs(FsCandidate *fscandidate)
{
PurpleMediaCandidate *candidate;
if (fscandidate == NULL)
return NULL;
candidate = purple_media_candidate_new(fscandidate->foundation,
fscandidate->component_id,
purple_media_candidate_type_from_fs(fscandidate->type),
purple_media_network_protocol_from_fs(fscandidate->proto),
fscandidate->ip, fscandidate->port);
g_object_set(candidate,
"base-ip", fscandidate->base_ip,
"base-port", fscandidate->base_port,
"priority", fscandidate->priority,
"username", fscandidate->username,
"password", fscandidate->password,
"ttl", fscandidate->ttl, NULL);
return candidate;
}
static FsCodec *
codec_to_fs(const PurpleMediaCodec *codec)
{
FsCodec *new_codec;
gint id;
char *encoding_name;
PurpleMediaSessionType media_type;
guint clock_rate;
guint channels;
GList *iter;
if (codec == NULL)
return NULL;
g_object_get(G_OBJECT(codec),
"id", &id,
"encoding-name", &encoding_name,
"media-type", &media_type,
"clock-rate", &clock_rate,
"channels", &channels,
"optional-params", &iter,
NULL);
new_codec = fs_codec_new(id, encoding_name,
session_type_to_fs_media_type(media_type),
clock_rate);
new_codec->channels = channels;
for (; iter; iter = g_list_next(iter)) {
PurpleKeyValuePair *param = (PurpleKeyValuePair*)iter->data;
fs_codec_add_optional_parameter(new_codec,
param->key, param->value);
}
g_free(encoding_name);
return new_codec;
}
static PurpleMediaCodec *
codec_from_fs(const FsCodec *codec)
{
PurpleMediaCodec *new_codec;
GList *iter;
if (codec == NULL)
return NULL;
new_codec = purple_media_codec_new(codec->id, codec->encoding_name,
session_type_from_fs(codec->media_type,
FS_DIRECTION_BOTH), codec->clock_rate);
g_object_set(new_codec, "channels", codec->channels, NULL);
for (iter = codec->optional_params; iter; iter = g_list_next(iter)) {
FsCodecParameter *param = (FsCodecParameter*)iter->data;
purple_media_codec_add_optional_parameter(new_codec,
param->name, param->value);
}
return new_codec;
}
static PurpleMediaBackendFs2Session *
get_session(PurpleMediaBackendFs2 *self, const gchar *sess_id)
{
PurpleMediaBackendFs2Private *priv;
PurpleMediaBackendFs2Session *session = NULL;
g_return_val_if_fail(PURPLE_MEDIA_IS_BACKEND_FS2(self), NULL);
priv = purple_media_backend_fs2_get_instance_private(self);
if (priv->sessions != NULL)
session = g_hash_table_lookup(priv->sessions, sess_id);
return session;
}
static FsParticipant *
get_participant(PurpleMediaBackendFs2 *self, const gchar *name)
{
PurpleMediaBackendFs2Private *priv;
FsParticipant *participant = NULL;
g_return_val_if_fail(PURPLE_MEDIA_IS_BACKEND_FS2(self), NULL);
priv = purple_media_backend_fs2_get_instance_private(self);
if (priv->participants != NULL)
participant = g_hash_table_lookup(priv->participants, name);
return participant;
}
static PurpleMediaBackendFs2Stream *
get_stream(PurpleMediaBackendFs2 *self,
const gchar *sess_id, const gchar *name)
{
PurpleMediaBackendFs2Private *priv;
GList *streams;
g_return_val_if_fail(PURPLE_MEDIA_IS_BACKEND_FS2(self), NULL);
priv = purple_media_backend_fs2_get_instance_private(self);
streams = priv->streams;
for (; streams; streams = g_list_next(streams)) {
PurpleMediaBackendFs2Stream *stream = streams->data;
if (purple_strequal(stream->session->id, sess_id) &&
purple_strequal(stream->participant, name))
return stream;
}
return NULL;
}
static GList *
get_streams(PurpleMediaBackendFs2 *self,
const gchar *sess_id, const gchar *name)
{
PurpleMediaBackendFs2Private *priv;
GList *streams, *ret = NULL;
g_return_val_if_fail(PURPLE_MEDIA_IS_BACKEND_FS2(self), NULL);
priv = purple_media_backend_fs2_get_instance_private(self);
streams = priv->streams;
for (; streams; streams = g_list_next(streams)) {
PurpleMediaBackendFs2Stream *stream = streams->data;
if (sess_id != NULL && !purple_strequal(stream->session->id, sess_id))
continue;
else if (name != NULL && !purple_strequal(stream->participant, name))
continue;
else
ret = g_list_prepend(ret, stream);
}
ret = g_list_reverse(ret);
return ret;
}
static PurpleMediaBackendFs2Session *
get_session_from_fs_stream(PurpleMediaBackendFs2 *self, FsStream *stream)
{
PurpleMediaBackendFs2Private *priv =
purple_media_backend_fs2_get_instance_private(self);
FsSession *fssession;
GList *values;
g_return_val_if_fail(PURPLE_MEDIA_IS_BACKEND_FS2(self), NULL);
g_return_val_if_fail(FS_IS_STREAM(stream), NULL);
g_object_get(stream, "session", &fssession, NULL);
values = g_hash_table_get_values(priv->sessions);
for (; values; values = g_list_delete_link(values, values)) {
PurpleMediaBackendFs2Session *session = values->data;
if (session->session == fssession) {
g_list_free(values);
g_object_unref(fssession);
return session;
}
}
g_object_unref(fssession);
return NULL;
}
static gdouble
gst_msg_db_to_percent(GstMessage *msg, gchar *value_name)
{
const GValue *list;
const GValue *value;
gdouble value_db;
gdouble percent;
list = gst_structure_get_value(gst_message_get_structure(msg), value_name);
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
value = g_value_array_get_nth(g_value_get_boxed(list), 0);
G_GNUC_END_IGNORE_DEPRECATIONS
value_db = g_value_get_double(value);
percent = pow(10, value_db / 20);
return (percent > 1.0) ? 1.0 : percent;
}
static void
purple_media_error_fs(PurpleMedia *media, const gchar *error,
const GstStructure *fs_error)
{
const gchar *error_msg = gst_structure_get_string(fs_error, "error-msg");
purple_media_error(media, "%s%s%s", error,
error_msg ? _("\n\nMessage from Farstream: ") : "",
error_msg ? error_msg : "");
}
static void
gst_handle_message_element(GstBus *bus, GstMessage *msg,
PurpleMediaBackendFs2 *self)
{
PurpleMediaBackendFs2Private *priv =
purple_media_backend_fs2_get_instance_private(self);
GstElement *src = GST_ELEMENT(GST_MESSAGE_SRC(msg));
static guint level_id = 0;
const GstStructure *structure = gst_message_get_structure(msg);
if (level_id == 0)
level_id = g_signal_lookup("level", PURPLE_TYPE_MEDIA);
if (gst_structure_has_name(structure, "level")) {
GstElement *src = GST_ELEMENT(GST_MESSAGE_SRC(msg));
gchar *name;
gchar *participant = NULL;
PurpleMediaBackendFs2Session *session = NULL;
gdouble percent;
if (!PURPLE_IS_MEDIA(priv->media) ||
GST_ELEMENT_PARENT(src) != priv->confbin)
return;
name = gst_element_get_name(src);
if (!strncmp(name, "sendlevel_", 10)) {
session = get_session(self, name+10);
if (priv->silence_threshold > 0) {
percent = gst_msg_db_to_percent(msg, "decay");
g_object_set(session->srcvalve,
"drop", (percent < priv->silence_threshold), NULL);
}
}
g_free(name);
if (!g_signal_has_handler_pending(priv->media, level_id, 0, FALSE))
return;
if (!session) {
GList *iter = priv->streams;
PurpleMediaBackendFs2Stream *stream;
for (; iter; iter = g_list_next(iter)) {
stream = iter->data;
if (stream->level == src) {
session = stream->session;
participant = stream->participant;
break;
}
}
}
if (!session)
return;
percent = gst_msg_db_to_percent(msg, "rms");
g_signal_emit(priv->media, level_id, 0,
session->id, participant, percent);
return;
}
if (!FS_IS_CONFERENCE(src) || !PURPLE_MEDIA_IS_BACKEND(self) ||
priv->conference != FS_CONFERENCE(src))
return;
if (gst_structure_has_name(structure, "farstream-error")) {
FsError error_no;
gboolean error_emitted = FALSE;
gst_structure_get_enum(structure, "error-no",
FS_TYPE_ERROR, (gint*)&error_no);
switch (error_no) {
case FS_ERROR_CONSTRUCTION:
purple_media_error_fs(priv->media,
_("Error initializing the call. "
"This probably denotes problem in "
"installation of GStreamer or Farstream."),
structure);
error_emitted = TRUE;
break;
case FS_ERROR_NETWORK:
purple_media_error_fs(priv->media, _("Network error."),
structure);
error_emitted = TRUE;
purple_media_end(priv->media, NULL, NULL);
break;
case FS_ERROR_NEGOTIATION_FAILED:
purple_media_error_fs(priv->media,
_("Codec negotiation failed. "
"This problem might be resolved by installing "
"more GStreamer codecs."),
structure);
error_emitted = TRUE;
purple_media_end(priv->media, NULL, NULL);
break;
case FS_ERROR_NO_CODECS:
purple_media_error(priv->media,
_("No codecs found. "
"Install some GStreamer codecs found "
"in GStreamer plugins packages."));
error_emitted = TRUE;
purple_media_end(priv->media, NULL, NULL);
break;
default:
purple_debug_error("backend-fs2",
"farstream-error: %i: %s\n",
error_no,
gst_structure_get_string(structure, "error-msg"));
break;
}
if (FS_ERROR_IS_FATAL(error_no)) {
if (!error_emitted)
purple_media_error(priv->media,
_("A non-recoverable Farstream error has occurred."));
purple_media_end(priv->media, NULL, NULL);
}
} else if (gst_structure_has_name(structure,
"farstream-new-local-candidate")) {
const GValue *value;
FsStream *stream;
FsCandidate *local_candidate;
PurpleMediaCandidate *candidate;
FsParticipant *participant;
PurpleMediaBackendFs2Session *session;
PurpleMediaBackendFs2Stream *media_stream;
const gchar *name;
value = gst_structure_get_value(structure, "stream");
stream = g_value_get_object(value);
value = gst_structure_get_value(structure, "candidate");
local_candidate = g_value_get_boxed(value);
session = get_session_from_fs_stream(self, stream);
purple_debug_info("backend-fs2",
"got new local candidate: %s\n",
local_candidate->foundation);
g_object_get(stream, "participant", &participant, NULL);
name = g_object_get_data(G_OBJECT(participant), "purple-name");
media_stream = get_stream(self, session->id, name);
media_stream->local_candidates = g_list_append(
media_stream->local_candidates,
fs_candidate_copy(local_candidate));
candidate = candidate_from_fs(local_candidate);
g_signal_emit_by_name(self, "new-candidate",
session->id, name, candidate);
g_object_unref(candidate);
g_object_unref(participant);
} else if (gst_structure_has_name(structure,
"farstream-local-candidates-prepared")) {
const GValue *value;
FsStream *stream;
FsParticipant *participant;
PurpleMediaBackendFs2Session *session;
value = gst_structure_get_value(structure, "stream");
stream = g_value_get_object(value);
session = get_session_from_fs_stream(self, stream);
g_object_get(stream, "participant", &participant, NULL);
g_signal_emit_by_name(self, "candidates-prepared",
session->id,
g_object_get_data(G_OBJECT(participant), "purple-name"));
g_object_unref(participant);
} else if (gst_structure_has_name(structure,
"farstream-new-active-candidate-pair")) {
const GValue *value;
FsStream *stream;
FsCandidate *local_candidate;
FsCandidate *remote_candidate;
FsParticipant *participant;
PurpleMediaBackendFs2Session *session;
PurpleMediaCandidate *lcandidate, *rcandidate;
value = gst_structure_get_value(structure, "stream");
stream = g_value_get_object(value);
value = gst_structure_get_value(structure, "local-candidate");
local_candidate = g_value_get_boxed(value);
value = gst_structure_get_value(structure, "remote-candidate");
remote_candidate = g_value_get_boxed(value);
g_object_get(stream, "participant", &participant, NULL);
session = get_session_from_fs_stream(self, stream);
lcandidate = candidate_from_fs(local_candidate);
rcandidate = candidate_from_fs(remote_candidate);
g_signal_emit_by_name(self, "active-candidate-pair",
session->id,
g_object_get_data(G_OBJECT(participant), "purple-name"),
lcandidate, rcandidate);
g_object_unref(participant);
g_object_unref(lcandidate);
g_object_unref(rcandidate);
} else if (gst_structure_has_name(structure,
"farstream-recv-codecs-changed")) {
const GValue *value;
GList *codecs;
FsCodec *codec;
value = gst_structure_get_value(structure, "codecs");
codecs = g_value_get_boxed(value);
codec = codecs->data;
purple_debug_info("backend-fs2",
"farstream-recv-codecs-changed: %s\n",
codec->encoding_name);
} else if (gst_structure_has_name(structure,
"farstream-component-state-changed")) {
const GValue *value;
FsStreamState fsstate;
guint component;
const gchar *state;
value = gst_structure_get_value(structure, "state");
fsstate = g_value_get_enum(value);
value = gst_structure_get_value(structure, "component");
component = g_value_get_uint(value);
switch (fsstate) {
case FS_STREAM_STATE_FAILED:
state = "FAILED";
break;
case FS_STREAM_STATE_DISCONNECTED:
state = "DISCONNECTED";
break;
case FS_STREAM_STATE_GATHERING:
state = "GATHERING";
break;
case FS_STREAM_STATE_CONNECTING:
state = "CONNECTING";
break;
case FS_STREAM_STATE_CONNECTED:
state = "CONNECTED";
break;
case FS_STREAM_STATE_READY:
state = "READY";
break;
default:
state = "UNKNOWN";
break;
}
purple_debug_info("backend-fs2",
"farstream-component-state-changed: "
"component: %u state: %s\n",
component, state);
} else if (gst_structure_has_name(structure,
"farstream-send-codec-changed")) {
const GValue *value;
FsCodec *codec;
gchar *codec_str;
value = gst_structure_get_value(structure, "codec");
codec = g_value_get_boxed(value);
codec_str = fs_codec_to_string(codec);
purple_debug_info("backend-fs2",
"farstream-send-codec-changed: codec: %s\n",
codec_str);
g_free(codec_str);
} else if (gst_structure_has_name(structure,
"farstream-codecs-changed")) {
const GValue *value;
FsSession *fssession;
GList *sessions;
value = gst_structure_get_value(structure, "session");
fssession = g_value_get_object(value);
sessions = g_hash_table_get_values(priv->sessions);
for (; sessions; sessions =
g_list_delete_link(sessions, sessions)) {
PurpleMediaBackendFs2Session *session = sessions->data;
gchar *session_id;
if (session->session != fssession)
continue;
session_id = g_strdup(session->id);
g_signal_emit_by_name(self, "codecs-changed",
session_id);
g_free(session_id);
g_list_free(sessions);
break;
}
}
}
static void
gst_handle_message_error(GstBus *bus, GstMessage *msg,
PurpleMediaBackendFs2 *self)
{
PurpleMediaBackendFs2Private *priv =
purple_media_backend_fs2_get_instance_private(self);
GstElement *element = GST_ELEMENT(GST_MESSAGE_SRC(msg));
GstElement *lastElement = NULL;
GList *sessions;
GError *error = NULL;
gchar *debug_msg = NULL;
gst_message_parse_error(msg, &error, &debug_msg);
purple_debug_error("backend-fs2", "gst error %s\ndebugging: %s\n",
error->message, debug_msg);
g_error_free(error);
g_free(debug_msg);
while (element && !GST_IS_PIPELINE(element)) {
if (element == priv->confbin)
break;
lastElement = element;
element = GST_ELEMENT_PARENT(element);
}
if (!element || !GST_IS_PIPELINE(element))
return;
sessions = purple_media_get_session_ids(priv->media);
for (; sessions; sessions = g_list_delete_link(sessions, sessions)) {
if (purple_media_get_src(priv->media, sessions->data)
!= lastElement)
continue;
if (purple_media_get_session_type(priv->media, sessions->data)
& PURPLE_MEDIA_AUDIO)
purple_media_error(priv->media,
_("Error with your microphone"));
else if (purple_media_get_session_type(priv->media,
sessions->data) & PURPLE_MEDIA_VIDEO)
purple_media_error(priv->media,
_("Error with your webcam"));
break;
}
g_list_free(sessions);
purple_media_error(priv->media, _("Conference error"));
purple_media_end(priv->media, NULL, NULL);
}
static gboolean
gst_bus_cb(GstBus *bus, GstMessage *msg, PurpleMediaBackendFs2 *self)
{
switch(GST_MESSAGE_TYPE(msg)) {
case GST_MESSAGE_ELEMENT:
gst_handle_message_element(bus, msg, self);
break;
case GST_MESSAGE_ERROR:
gst_handle_message_error(bus, msg, self);
break;
default:
break;
}
return TRUE;
}
static void
remove_element(GstElement *element)
{
if (element) {
gst_element_set_locked_state(element, TRUE);
gst_element_set_state(element, GST_STATE_NULL);
gst_bin_remove(GST_BIN(GST_ELEMENT_PARENT(element)), element);
}
}
static void
state_changed_cb(PurpleMedia *media, PurpleMediaState state,
gchar *sid, gchar *name, PurpleMediaBackendFs2 *self)
{
if (state == PURPLE_MEDIA_STATE_END) {
PurpleMediaBackendFs2Private *priv =
purple_media_backend_fs2_get_instance_private(
self);
if (sid && name) {
PurpleMediaBackendFs2Stream *stream = get_stream(self, sid, name);
gst_object_unref(stream->stream);
priv->streams = g_list_remove(priv->streams, stream);
remove_element(stream->src);
remove_element(stream->tee);
remove_element(stream->volume);
remove_element(stream->level);
remove_element(stream->fakesink);
remove_element(stream->queue);
free_stream(stream);
} else if (sid && !name) {
PurpleMediaBackendFs2Session *session = get_session(self, sid);
GstPad *pad;
g_object_get(session->session, "sink-pad", &pad, NULL);
gst_pad_unlink(GST_PAD_PEER(pad), pad);
gst_object_unref(pad);
gst_object_unref(session->session);
g_hash_table_remove(priv->sessions, session->id);
pad = gst_pad_get_peer(session->srcpad);
gst_element_remove_pad(GST_ELEMENT_PARENT(pad), pad);
gst_object_unref(pad);
gst_object_unref(session->srcpad);
remove_element(session->srcvalve);
remove_element(session->tee);
free_session(session);
}
purple_media_manager_remove_output_windows(
purple_media_get_manager(media), media, sid, name);
}
}
static void
stream_info_cb(PurpleMedia *media, PurpleMediaInfoType type,
gchar *sid, gchar *name, gboolean local,
PurpleMediaBackendFs2 *self)
{
if (type == PURPLE_MEDIA_INFO_ACCEPT && sid != NULL && name != NULL) {
PurpleMediaBackendFs2Stream *stream =
get_stream(self, sid, name);
GError *err = NULL;
g_object_set(G_OBJECT(stream->stream), "direction",
session_type_to_fs_stream_direction(
stream->session->type), NULL);
if (stream->remote_candidates == NULL ||
purple_media_is_initiator(media, sid, name))
return;
if (stream->supports_add)
fs_stream_add_remote_candidates(stream->stream,
stream->remote_candidates, &err);
else
fs_stream_force_remote_candidates(stream->stream,
stream->remote_candidates, &err);
if (err == NULL)
return;
purple_debug_error("backend-fs2", "Error adding "
"remote candidates: %s\n",
err->message);
g_error_free(err);
} else if (local == TRUE && (type == PURPLE_MEDIA_INFO_MUTE ||
type == PURPLE_MEDIA_INFO_UNMUTE)) {
PurpleMediaBackendFs2Private *priv =
purple_media_backend_fs2_get_instance_private(
self);
gboolean active = (type == PURPLE_MEDIA_INFO_MUTE);
GList *sessions;
if (sid == NULL)
sessions = g_hash_table_get_values(priv->sessions);
else
sessions = g_list_prepend(NULL,
get_session(self, sid));
purple_debug_info("media", "Turning mute %s\n",
active ? "on" : "off");
for (; sessions; sessions = g_list_delete_link(
sessions, sessions)) {
PurpleMediaBackendFs2Session *session =
sessions->data;
if (session->type & PURPLE_MEDIA_SEND_AUDIO) {
gchar *name = g_strdup_printf("volume_%s",
session->id);
GstElement *volume = gst_bin_get_by_name(
GST_BIN(priv->confbin), name);
g_free(name);
g_object_set(volume, "mute", active, NULL);
}
}
} else if (local == TRUE && (type == PURPLE_MEDIA_INFO_HOLD ||
type == PURPLE_MEDIA_INFO_UNHOLD)) {
gboolean active = (type == PURPLE_MEDIA_INFO_HOLD);
GList *streams = get_streams(self, sid, name);
for (; streams; streams =
g_list_delete_link(streams, streams)) {
PurpleMediaBackendFs2Stream *stream = streams->data;
if (stream->session->type & PURPLE_MEDIA_SEND_AUDIO) {
g_object_set(stream->stream, "direction",
session_type_to_fs_stream_direction(
stream->session->type & ((active) ?
~PURPLE_MEDIA_SEND_AUDIO :
PURPLE_MEDIA_AUDIO)), NULL);
}
}
} else if (local == TRUE && (type == PURPLE_MEDIA_INFO_PAUSE ||
type == PURPLE_MEDIA_INFO_UNPAUSE)) {
gboolean active = (type == PURPLE_MEDIA_INFO_PAUSE);
GList *streams = get_streams(self, sid, name);
for (; streams; streams =
g_list_delete_link(streams, streams)) {
PurpleMediaBackendFs2Stream *stream = streams->data;
if (stream->session->type & PURPLE_MEDIA_SEND_VIDEO) {
g_object_set(stream->stream, "direction",
session_type_to_fs_stream_direction(
stream->session->type & ((active) ?
~PURPLE_MEDIA_SEND_VIDEO :
PURPLE_MEDIA_VIDEO)), NULL);
}
}
}
}
static gboolean
init_conference(PurpleMediaBackendFs2 *self)
{
PurpleMediaBackendFs2Private *priv =
purple_media_backend_fs2_get_instance_private(self);
GstElement *pipeline;
GstBus *bus;
gchar *name;
GKeyFile *default_props;
PurpleAccount *account;
priv->conference = FS_CONFERENCE(
gst_element_factory_make(priv->conference_type, NULL));
if (priv->conference == NULL) {
purple_debug_error("backend-fs2", "Conference == NULL\n");
return FALSE;
}
account = purple_media_get_account(priv->media);
if (purple_account_get_silence_suppression(account))
priv->silence_threshold = purple_prefs_get_int(
"/purple/media/audio/silence_threshold") / 100.0;
else
priv->silence_threshold = 0;
g_object_unref(account);
pipeline = purple_media_manager_get_pipeline(
purple_media_get_manager(priv->media));
if (pipeline == NULL) {
purple_debug_error("backend-fs2",
"Couldn't retrieve pipeline.\n");
return FALSE;
}
name = g_strdup_printf("conf_%p", priv->conference);
priv->confbin = gst_bin_new(name);
if (priv->confbin == NULL) {
purple_debug_error("backend-fs2",
"Couldn't create confbin.\n");
return FALSE;
}
g_free(name);
bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
if (bus == NULL) {
purple_debug_error("backend-fs2",
"Couldn't get the pipeline's bus.\n");
return FALSE;
}
default_props = fs_utils_get_default_element_properties(GST_ELEMENT(priv->conference));
if (default_props != NULL) {
priv->notifier = fs_element_added_notifier_new();
fs_element_added_notifier_add(priv->notifier,
GST_BIN(priv->confbin));
fs_element_added_notifier_set_properties_from_keyfile(priv->notifier, default_props);
}
g_signal_connect(G_OBJECT(bus), "message",
G_CALLBACK(gst_bus_cb), self);
gst_object_unref(bus);
if (!gst_bin_add(GST_BIN(pipeline),
GST_ELEMENT(priv->confbin))) {
purple_debug_error("backend-fs2", "Couldn't add confbin "
"element to the pipeline\n");
return FALSE;
}
if (!gst_bin_add(GST_BIN(priv->confbin),
GST_ELEMENT(priv->conference))) {
purple_debug_error("backend-fs2", "Couldn't add conference "
"element to the confbin\n");
return FALSE;
}
if (gst_element_set_state(GST_ELEMENT(priv->confbin),
GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
purple_debug_error("backend-fs2",
"Failed to start conference.\n");
return FALSE;
}
return TRUE;
}
static gboolean
create_src(PurpleMediaBackendFs2 *self, const gchar *sess_id,
PurpleMediaSessionType type)
{
PurpleMediaBackendFs2Private *priv =
purple_media_backend_fs2_get_instance_private(self);
PurpleMediaBackendFs2Session *session;
PurpleMediaSessionType session_type;
FsMediaType media_type = session_type_to_fs_media_type(type);
FsStreamDirection type_direction =
session_type_to_fs_stream_direction(type);
GstElement *src;
GstPad *sinkpad, *srcpad;
GstPad *ghost = NULL;
if ((type_direction & FS_DIRECTION_SEND) == 0)
return TRUE;
session_type = session_type_from_fs(
media_type, FS_DIRECTION_SEND);
src = purple_media_manager_get_element(
purple_media_get_manager(priv->media),
session_type, priv->media, sess_id, NULL);
if (!GST_IS_ELEMENT(src)) {
purple_debug_error("backend-fs2",
"Error creating src for session %s\n",
sess_id);
return FALSE;
}
session = get_session(self, sess_id);
if (session == NULL) {
purple_debug_warning("backend-fs2",
"purple_media_set_src: trying to set"
" src on non-existent session\n");
return FALSE;
}
if (session->src)
gst_object_unref(session->src);
session->src = src;
gst_element_set_locked_state(session->src, TRUE);
session->tee = gst_element_factory_make("tee", NULL);
gst_bin_add(GST_BIN(priv->confbin), session->tee);
/* This supposedly isn't necessary, but it silences some warnings */
if (GST_ELEMENT_PARENT(priv->confbin)
== GST_ELEMENT_PARENT(session->src)) {
GstPad *pad = gst_element_get_static_pad(session->tee, "sink");
ghost = gst_ghost_pad_new(NULL, pad);
gst_object_unref(pad);
gst_pad_set_active(ghost, TRUE);
gst_element_add_pad(priv->confbin, ghost);
}
gst_element_set_state(session->tee, GST_STATE_PLAYING);
gst_element_link(session->src, priv->confbin);
if (ghost)
session->srcpad = gst_pad_get_peer(ghost);
g_object_get(session->session, "sink-pad", &sinkpad, NULL);
if (session->type & PURPLE_MEDIA_SEND_AUDIO) {
gchar *name = g_strdup_printf("volume_%s", session->id);
GstElement *level;
GstElement *volume = gst_element_factory_make("volume", name);
double input_volume = purple_prefs_get_int(
"/purple/media/audio/volume/input")/10.0;
g_free(name);
name = g_strdup_printf("sendlevel_%s", session->id);
level = gst_element_factory_make("level", name);
g_free(name);
session->srcvalve = gst_element_factory_make("valve", NULL);
gst_bin_add(GST_BIN(priv->confbin), volume);
gst_bin_add(GST_BIN(priv->confbin), level);
gst_bin_add(GST_BIN(priv->confbin), session->srcvalve);
gst_element_set_state(level, GST_STATE_PLAYING);
gst_element_set_state(volume, GST_STATE_PLAYING);
gst_element_set_state(session->srcvalve, GST_STATE_PLAYING);
gst_element_link(level, session->srcvalve);
gst_element_link(volume, level);
gst_element_link(session->tee, volume);
srcpad = gst_element_get_static_pad(session->srcvalve, "src");
g_object_set(volume, "volume", input_volume, NULL);
} else {
srcpad = gst_element_get_request_pad(session->tee, "src_%u");
}
purple_debug_info("backend-fs2", "connecting pad: %s\n",
gst_pad_link(srcpad, sinkpad) == GST_PAD_LINK_OK
? "success" : "failure");
gst_element_set_locked_state(session->src, FALSE);
gst_object_unref(session->src);
gst_object_unref(sinkpad);
gst_object_unref(srcpad);
purple_media_manager_create_output_window(purple_media_get_manager(
priv->media), priv->media, sess_id, NULL);
purple_debug_info("backend-fs2", "create_src: setting source "
"state to GST_STATE_PLAYING - it may hang here on win32\n");
gst_element_set_state(session->src, GST_STATE_PLAYING);
purple_debug_info("backend-fs2", "create_src: state set\n");
return TRUE;
}
static gboolean
create_session(PurpleMediaBackendFs2 *self, const gchar *sess_id,
PurpleMediaSessionType type, gboolean initiator,
const gchar *transmitter)
{
PurpleMediaBackendFs2Private *priv =
purple_media_backend_fs2_get_instance_private(self);
PurpleMediaBackendFs2Session *session;
GError *err = NULL;
GList *codec_conf = NULL;
gchar *filename = NULL;
session = g_new0(PurpleMediaBackendFs2Session, 1);
session->session = fs_conference_new_session(priv->conference,
session_type_to_fs_media_type(type), &err);
#ifdef HAVE_MEDIA_APPLICATION
if (type == PURPLE_MEDIA_APPLICATION) {
GstCaps *caps;
GObject *rtpsession = NULL;
caps = gst_caps_new_empty_simple ("application/octet-stream");
fs_session_set_allowed_caps (session->session, caps, caps, NULL);
gst_caps_unref (caps);
g_object_get (session->session, "internal-session", &rtpsession, NULL);
if (rtpsession) {
g_object_set (rtpsession, "probation", 0, NULL);
g_object_unref (rtpsession);
}
}
#endif
if (err != NULL) {
purple_media_error(priv->media,
_("Error creating session: %s"),
err->message);
g_error_free(err);
g_free(session);
return FALSE;
}
filename = g_build_filename(purple_config_dir(), "fs-codec.conf", NULL);
codec_conf = fs_codec_list_from_keyfile(filename, &err);
g_free(filename);
if (err != NULL) {
if (err->code == G_KEY_FILE_ERROR_NOT_FOUND)
purple_debug_info("backend-fs2", "Couldn't read "
"fs-codec.conf: %s\n",
err->message);
else
purple_debug_error("backend-fs2", "Error reading "
"fs-codec.conf: %s\n",
err->message);
g_error_free(err);
purple_debug_info("backend-fs2",
"Loading default codec conf instead\n");
codec_conf = fs_utils_get_default_codec_preferences(
GST_ELEMENT(priv->conference));
}
fs_session_set_codec_preferences(session->session, codec_conf, NULL);
fs_codec_list_destroy(codec_conf);
/*
* Removes a 5-7 second delay before
* receiving the src-pad-added signal.
* Only works for non-multicast FsRtpSessions.
*/
if (!purple_strequal(transmitter, "multicast"))
g_object_set(G_OBJECT(session->session),
"no-rtcp-timeout", 0, NULL);
session->id = g_strdup(sess_id);
session->backend = self;
session->type = type;
if (!priv->sessions) {
purple_debug_info("backend-fs2",
"Creating hash table for sessions\n");
priv->sessions = g_hash_table_new_full(g_str_hash, g_str_equal,
g_free, NULL);
}
g_hash_table_insert(priv->sessions, g_strdup(session->id), session);
if (!create_src(self, sess_id, type)) {
purple_debug_info("backend-fs2", "Error creating the src\n");
return FALSE;
}
return TRUE;
}
static void
free_session(PurpleMediaBackendFs2Session *session)
{
g_free(session->id);
g_free(session);
}
static gboolean
create_participant(PurpleMediaBackendFs2 *self, const gchar *name)
{
PurpleMediaBackendFs2Private *priv =
purple_media_backend_fs2_get_instance_private(self);
FsParticipant *participant;
GError *err = NULL;
participant = fs_conference_new_participant(
priv->conference, &err);
if (err) {
purple_debug_error("backend-fs2",
"Error creating participant: %s\n",
err->message);
g_error_free(err);
return FALSE;
}
g_object_set_data_full(G_OBJECT(participant), "purple-name",
g_strdup(name), g_free);
if (g_object_class_find_property(G_OBJECT_GET_CLASS(participant),
"cname")) {
g_object_set(participant, "cname", name, NULL);
}
if (!priv->participants) {
purple_debug_info("backend-fs2",
"Creating hash table for participants\n");
priv->participants = g_hash_table_new_full(g_str_hash,
g_str_equal, g_free, g_object_unref);
}
g_hash_table_insert(priv->participants, g_strdup(name), participant);
return TRUE;
}
static gboolean
src_pad_added_cb_cb(PurpleMediaBackendFs2Stream *stream)
{
PurpleMediaBackendFs2Private *priv;
g_return_val_if_fail(stream != NULL, FALSE);
priv = purple_media_backend_fs2_get_instance_private(
stream->session->backend);
stream->connected_cb_id = 0;
purple_media_manager_create_output_window(
purple_media_get_manager(priv->media), priv->media,
stream->session->id, stream->participant);
g_signal_emit_by_name(priv->media, "state-changed",
PURPLE_MEDIA_STATE_CONNECTED,
stream->session->id, stream->participant);
return FALSE;
}
static void
src_pad_added_cb(FsStream *fsstream, GstPad *srcpad,
FsCodec *codec, PurpleMediaBackendFs2Stream *stream)
{
PurpleMediaBackendFs2Private *priv;
GstPad *sinkpad;
g_return_if_fail(FS_IS_STREAM(fsstream));
g_return_if_fail(stream != NULL);
priv = purple_media_backend_fs2_get_instance_private(
stream->session->backend);
if (stream->src == NULL) {
GstElement *sink = NULL;
if (codec->media_type == FS_MEDIA_TYPE_AUDIO) {
double output_volume = purple_prefs_get_int(
"/purple/media/audio/volume/output")/10.0;
stream->queue = gst_element_factory_make("queue", NULL);
stream->volume = gst_element_factory_make("volume", NULL);
g_object_set(stream->volume, "volume", output_volume, NULL);
stream->level = gst_element_factory_make("level", NULL);
stream->src = gst_element_factory_make("audiomixer", NULL);
sink = purple_media_manager_get_element(
purple_media_get_manager(priv->media),
PURPLE_MEDIA_RECV_AUDIO, priv->media,
stream->session->id,
stream->participant);
gst_bin_add(GST_BIN(priv->confbin), stream->queue);
gst_bin_add(GST_BIN(priv->confbin), stream->volume);
gst_bin_add(GST_BIN(priv->confbin), stream->level);
gst_bin_add(GST_BIN(priv->confbin), sink);
gst_element_set_state(sink, GST_STATE_PLAYING);
gst_element_set_state(stream->level, GST_STATE_PLAYING);
gst_element_set_state(stream->volume, GST_STATE_PLAYING);
gst_element_set_state(stream->queue, GST_STATE_PLAYING);
gst_element_link(stream->level, sink);
gst_element_link(stream->volume, stream->level);
gst_element_link(stream->queue, stream->volume);
sink = stream->queue;
} else if (codec->media_type == FS_MEDIA_TYPE_VIDEO) {
stream->src = gst_element_factory_make("funnel", NULL);
sink = gst_element_factory_make("fakesink", NULL);
g_object_set(G_OBJECT(sink), "async", FALSE, NULL);
gst_bin_add(GST_BIN(priv->confbin), sink);
gst_element_set_state(sink, GST_STATE_PLAYING);
stream->fakesink = sink;
#ifdef HAVE_MEDIA_APPLICATION
} else if (codec->media_type == FS_MEDIA_TYPE_APPLICATION) {
stream->src = gst_element_factory_make("funnel", NULL);
sink = purple_media_manager_get_element(
purple_media_get_manager(priv->media),
PURPLE_MEDIA_RECV_APPLICATION, priv->media,
stream->session->id,
stream->participant);
gst_bin_add(GST_BIN(priv->confbin), sink);
gst_element_set_state(sink, GST_STATE_PLAYING);
#endif
}
stream->tee = gst_element_factory_make("tee", NULL);
gst_bin_add_many(GST_BIN(priv->confbin),
stream->src, stream->tee, NULL);
gst_element_set_state(stream->tee, GST_STATE_PLAYING);
gst_element_set_state(stream->src, GST_STATE_PLAYING);
gst_element_link_many(stream->src, stream->tee, sink, NULL);
} else {
if (codec->media_type == FS_MEDIA_TYPE_AUDIO) {
GstElement *convert, *resample, *capsfilter;
GstPad *mixer_srcpad;
GstCaps *caps;
/* The audiomixer element requires that all input
* streams have the same rate, so resample if
* needed
*/
mixer_srcpad = gst_element_get_static_pad(stream->src, "src");
caps = gst_pad_get_current_caps(mixer_srcpad);
if (caps) {
convert = gst_element_factory_make("audioconvert", NULL);
resample = gst_element_factory_make("audioresample", NULL);
capsfilter = gst_element_factory_make("capsfilter", NULL);
gst_bin_add_many(GST_BIN(priv->confbin), convert,
resample, capsfilter, NULL);
gst_element_link_many(gst_pad_get_parent_element(srcpad),
convert, resample, capsfilter, NULL);
g_object_set(capsfilter, "caps", caps, NULL);
gst_element_set_state(convert, GST_STATE_PLAYING);
gst_element_set_state(resample, GST_STATE_PLAYING);
gst_element_set_state(capsfilter, GST_STATE_PLAYING);
srcpad = gst_element_get_static_pad(capsfilter, "src");
gst_object_unref(caps);
}
gst_object_unref(mixer_srcpad);
}
}
sinkpad = gst_element_get_request_pad(stream->src, "sink_%u");
gst_pad_link(srcpad, sinkpad);
gst_object_unref(sinkpad);
stream->connected_cb_id = g_timeout_add(0,
(GSourceFunc)src_pad_added_cb_cb, stream);
}
static gboolean
create_stream(PurpleMediaBackendFs2 *self,
const gchar *sess_id, const gchar *who,
PurpleMediaSessionType type, gboolean initiator,
const gchar *transmitter,
guint num_params, GParameter *params)
{
PurpleMediaBackendFs2Private *priv =
purple_media_backend_fs2_get_instance_private(self);
GError *err = NULL;
FsStream *fsstream = NULL;
const gchar *stun_ip = purple_network_get_stun_ip();
const gchar *turn_ip = purple_network_get_turn_ip();
guint _num_params = num_params;
GParameter *_params;
FsStreamDirection type_direction =
session_type_to_fs_stream_direction(type);
PurpleMediaBackendFs2Session *session;
PurpleMediaBackendFs2Stream *stream;
FsParticipant *participant;
/* check if the protocol has already specified a relay-info
we need to do this to allow them to override when using non-standard
TURN modes, like Google f.ex. */
gboolean got_turn_from_protocol = FALSE;
guint i;
GPtrArray *relay_info = g_ptr_array_new_full (1, (GDestroyNotify) gst_structure_free);
gboolean ret;
session = get_session(self, sess_id);
if (session == NULL) {
purple_debug_error("backend-fs2",
"Couldn't find session to create stream.\n");
return FALSE;
}
participant = get_participant(self, who);
if (participant == NULL) {
purple_debug_error("backend-fs2", "Couldn't find "
"participant to create stream.\n");
return FALSE;
}
fsstream = fs_session_new_stream(session->session, participant,
initiator == TRUE ? type_direction :
(type_direction & FS_DIRECTION_RECV), &err);
if (fsstream == NULL) {
if (err) {
purple_debug_error("backend-fs2", "Error creating stream: %s\n",
err->message ? err->message : "NULL");
g_error_free(err);
} else
purple_debug_error("backend-fs2",
"Error creating stream\n");
return FALSE;
}
for (i = 0 ; i < num_params ; i++) {
if (purple_strequal(params[i].name, "relay-info")) {
got_turn_from_protocol = TRUE;
break;
}
}
_params = g_new0(GParameter, num_params + 3);
memcpy(_params, params, sizeof(GParameter) * num_params);
/* set the controlling mode parameter */
_params[_num_params].name = "controlling-mode";
g_value_init(&_params[_num_params].value, G_TYPE_BOOLEAN);
g_value_set_boolean(&_params[_num_params].value, initiator);
++_num_params;
if (stun_ip) {
purple_debug_info("backend-fs2",
"Setting stun-ip on new stream: %s\n", stun_ip);
_params[_num_params].name = "stun-ip";
g_value_init(&_params[_num_params].value, G_TYPE_STRING);
g_value_set_string(&_params[_num_params].value, stun_ip);
++_num_params;
}
if (turn_ip && purple_strequal("nice", transmitter) && !got_turn_from_protocol) {
gint port;
const gchar *username = purple_prefs_get_string(
"/purple/network/turn_username");
const gchar *password = purple_prefs_get_string(
"/purple/network/turn_password");
/* UDP */
port = purple_prefs_get_int("/purple/network/turn_port");
if (port > 0) {
g_ptr_array_add (relay_info,
gst_structure_new ("relay-info",
"ip", G_TYPE_STRING, turn_ip,
"port", G_TYPE_UINT, port,
"username", G_TYPE_STRING, username,
"password", G_TYPE_STRING, password,
"relay-type", G_TYPE_STRING, "udp",
NULL));
}
/* TCP */
port = purple_prefs_get_int("/purple/network/turn_port_tcp");
if (port > 0) {
g_ptr_array_add (relay_info,
gst_structure_new ("relay-info",
"ip", G_TYPE_STRING, turn_ip,
"port", G_TYPE_UINT, port,
"username", G_TYPE_STRING, username,
"password", G_TYPE_STRING, password,
"relay-type", G_TYPE_STRING, "tcp",
NULL));
}
/* TURN over SSL is only supported by libnice for Google's "psuedo" SSL mode
at this time */
purple_debug_info("backend-fs2",
"Setting relay-info on new stream\n");
_params[_num_params].name = "relay-info";
g_value_init(&_params[_num_params].value, G_TYPE_PTR_ARRAY);
g_value_set_boxed(&_params[_num_params].value, relay_info);
_num_params++;
}
ret = fs_stream_set_transmitter(fsstream, transmitter,
_params, _num_params, &err);
for (i = 0 ; i < _num_params ; i++)
g_value_unset (&_params[i].value);
g_free(_params);
if (relay_info)
g_ptr_array_unref (relay_info);
if (ret == FALSE) {
purple_debug_error("backend-fs2",
"Could not set transmitter %s: %s.\n",
transmitter, err ? err->message : NULL);
g_clear_error(&err);
return FALSE;
}
stream = g_new0(PurpleMediaBackendFs2Stream, 1);
stream->participant = g_strdup(who);
stream->session = session;
stream->stream = fsstream;
stream->supports_add = purple_strequal(transmitter, "nice");
priv->streams = g_list_append(priv->streams, stream);
g_signal_connect(G_OBJECT(fsstream), "src-pad-added",
G_CALLBACK(src_pad_added_cb), stream);
return TRUE;
}
static void
free_stream(PurpleMediaBackendFs2Stream *stream)
{
/* Remove the connected_cb timeout */
if (stream->connected_cb_id != 0)
g_source_remove(stream->connected_cb_id);
g_free(stream->participant);
if (stream->local_candidates)
fs_candidate_list_destroy(stream->local_candidates);
if (stream->remote_candidates)
fs_candidate_list_destroy(stream->remote_candidates);
g_free(stream);
}
static gboolean
purple_media_backend_fs2_add_stream(PurpleMediaBackend *self,
const gchar *sess_id, const gchar *who,
PurpleMediaSessionType type, gboolean initiator,
const gchar *transmitter,
guint num_params, GParameter *params)
{
PurpleMediaBackendFs2 *backend = PURPLE_MEDIA_BACKEND_FS2(self);
PurpleMediaBackendFs2Private *priv =
purple_media_backend_fs2_get_instance_private(backend);
PurpleMediaBackendFs2Stream *stream;
if (priv->conference == NULL && !init_conference(backend)) {
purple_debug_error("backend-fs2",
"Error initializing the conference.\n");
return FALSE;
}
if (get_session(backend, sess_id) == NULL &&
!create_session(backend, sess_id, type,
initiator, transmitter)) {
purple_debug_error("backend-fs2",
"Error creating the session.\n");
return FALSE;
}
if (get_participant(backend, who) == NULL &&
!create_participant(backend, who)) {
purple_debug_error("backend-fs2",
"Error creating the participant.\n");
return FALSE;
}
stream = get_stream(backend, sess_id, who);
if (stream != NULL) {
FsStreamDirection type_direction =
session_type_to_fs_stream_direction(type);
if (session_type_to_fs_stream_direction(
stream->session->type) != type_direction) {
/* change direction */
g_object_set(stream->stream, "direction",
type_direction, NULL);
}
} else if (!create_stream(backend, sess_id, who, type,
initiator, transmitter, num_params, params)) {
purple_debug_error("backend-fs2",
"Error creating the stream.\n");
return FALSE;
}
return TRUE;
}
static void
purple_media_backend_fs2_add_remote_candidates(PurpleMediaBackend *self,
const gchar *sess_id, const gchar *participant,
GList *remote_candidates)
{
PurpleMediaBackendFs2Private *priv;
PurpleMediaBackendFs2Stream *stream;
GError *err = NULL;
g_return_if_fail(PURPLE_MEDIA_IS_BACKEND_FS2(self));
priv = purple_media_backend_fs2_get_instance_private(
PURPLE_MEDIA_BACKEND_FS2(self));
stream = get_stream(PURPLE_MEDIA_BACKEND_FS2(self),
sess_id, participant);
if (stream == NULL) {
purple_debug_error("backend-fs2",
"purple_media_add_remote_candidates: "
"couldn't find stream %s %s.\n",
sess_id ? sess_id : "(null)",
participant ? participant : "(null)");
return;
}
stream->remote_candidates = g_list_concat(stream->remote_candidates,
g_list_copy_deep(remote_candidates, (GCopyFunc)candidate_to_fs, NULL));
if (purple_media_is_initiator(priv->media, sess_id, participant) ||
purple_media_accepted(
priv->media, sess_id, participant)) {
if (stream->supports_add)
fs_stream_add_remote_candidates(stream->stream,
stream->remote_candidates, &err);
else
fs_stream_force_remote_candidates(stream->stream,
stream->remote_candidates, &err);
if (err) {
purple_debug_error("backend-fs2", "Error adding remote"
" candidates: %s\n", err->message);
g_error_free(err);
}
}
}
static gboolean
purple_media_backend_fs2_codecs_ready(PurpleMediaBackend *self,
const gchar *sess_id)
{
PurpleMediaBackendFs2Private *priv;
gboolean ret = FALSE;
g_return_val_if_fail(PURPLE_MEDIA_IS_BACKEND_FS2(self), FALSE);
priv = purple_media_backend_fs2_get_instance_private(
PURPLE_MEDIA_BACKEND_FS2(self));
if (sess_id != NULL) {
PurpleMediaBackendFs2Session *session = get_session(
PURPLE_MEDIA_BACKEND_FS2(self), sess_id);
if (session == NULL)
return FALSE;
if (session->type & (PURPLE_MEDIA_SEND_AUDIO |
#ifdef HAVE_MEDIA_APPLICATION
PURPLE_MEDIA_SEND_APPLICATION |
#endif
PURPLE_MEDIA_SEND_VIDEO)) {
GList *codecs = NULL;
g_object_get(session->session,
"codecs", &codecs, NULL);
if (codecs) {
fs_codec_list_destroy (codecs);
ret = TRUE;
}
} else
ret = TRUE;
} else {
GList *values = g_hash_table_get_values(priv->sessions);
for (; values; values = g_list_delete_link(values, values)) {
PurpleMediaBackendFs2Session *session = values->data;
if (session->type & (PURPLE_MEDIA_SEND_AUDIO |
#ifdef HAVE_MEDIA_APPLICATION
PURPLE_MEDIA_SEND_APPLICATION |
#endif
PURPLE_MEDIA_SEND_VIDEO)) {
GList *codecs = NULL;
g_object_get(session->session,
"codecs", &codecs, NULL);
if (codecs) {
fs_codec_list_destroy (codecs);
ret = TRUE;
} else {
ret = FALSE;
break;
}
} else
ret = TRUE;
}
if (values != NULL)
g_list_free(values);
}
return ret;
}
static GList *
purple_media_backend_fs2_get_codecs(PurpleMediaBackend *self,
const gchar *sess_id)
{
PurpleMediaBackendFs2Session *session;
GList *fscodecs;
GList *codecs;
g_return_val_if_fail(PURPLE_MEDIA_IS_BACKEND_FS2(self), NULL);
session = get_session(PURPLE_MEDIA_BACKEND_FS2(self), sess_id);
if (session == NULL)
return NULL;
g_object_get(G_OBJECT(session->session),
"codecs", &fscodecs, NULL);
codecs = g_list_copy_deep(fscodecs, (GCopyFunc)codec_from_fs, NULL);
fs_codec_list_destroy(fscodecs);
return codecs;
}
static GList *
purple_media_backend_fs2_get_local_candidates(PurpleMediaBackend *self,
const gchar *sess_id, const gchar *participant)
{
PurpleMediaBackendFs2Stream *stream;
GList *candidates = NULL;
g_return_val_if_fail(PURPLE_MEDIA_IS_BACKEND_FS2(self), NULL);
stream = get_stream(PURPLE_MEDIA_BACKEND_FS2(self),
sess_id, participant);
if (stream != NULL) {
candidates = g_list_copy_deep(stream->local_candidates,
(GCopyFunc)candidate_from_fs, NULL);
}
return candidates;
}
static gboolean
purple_media_backend_fs2_set_remote_codecs(PurpleMediaBackend *self,
const gchar *sess_id, const gchar *participant,
GList *codecs)
{
PurpleMediaBackendFs2Stream *stream;
GList *fscodecs;
GError *err = NULL;
g_return_val_if_fail(PURPLE_MEDIA_IS_BACKEND_FS2(self), FALSE);
stream = get_stream(PURPLE_MEDIA_BACKEND_FS2(self),
sess_id, participant);
if (stream == NULL)
return FALSE;
fscodecs = g_list_copy_deep(codecs, (GCopyFunc)codec_to_fs, NULL);
fs_stream_set_remote_codecs(stream->stream, fscodecs, &err);
fs_codec_list_destroy(fscodecs);
if (err) {
purple_debug_error("backend-fs2",
"Error setting remote codecs: %s\n",
err->message);
g_error_free(err);
return FALSE;
}
return TRUE;
}
static GstStructure *
create_fs2_srtp_structure(const gchar *cipher, const gchar *auth,
const gchar *key, gsize key_len)
{
GstStructure *result;
GstBuffer *buffer;
GstMapInfo info;
buffer = gst_buffer_new_allocate(NULL, key_len, NULL);
gst_buffer_map(buffer, &info, GST_MAP_WRITE);
memcpy(info.data, key, key_len);
gst_buffer_unmap(buffer, &info);
result = gst_structure_new("FarstreamSRTP",
"cipher", G_TYPE_STRING, cipher,
"auth", G_TYPE_STRING, auth,
"key", GST_TYPE_BUFFER, buffer,
NULL);
gst_buffer_unref(buffer);
return result;
}
static gboolean
purple_media_backend_fs2_set_encryption_parameters (PurpleMediaBackend *self,
const gchar *sess_id, const gchar *cipher, const gchar *auth,
const gchar *key, gsize key_len)
{
PurpleMediaBackendFs2Session *session;
GstStructure *srtp;
GError *err = NULL;
gboolean result;
g_return_val_if_fail(PURPLE_MEDIA_IS_BACKEND_FS2(self), FALSE);
session = get_session(PURPLE_MEDIA_BACKEND_FS2(self), sess_id);
if (!session)
return FALSE;
srtp = create_fs2_srtp_structure(cipher, auth, key, key_len);
if (!srtp)
return FALSE;
result = fs_session_set_encryption_parameters(session->session, srtp,
&err);
if (!result) {
purple_debug_error("backend-fs2",
"Error setting encryption parameters: %s\n", err->message);
g_error_free(err);
}
gst_structure_free(srtp);
return result;
}
static gboolean
purple_media_backend_fs2_set_decryption_parameters (PurpleMediaBackend *self,
const gchar *sess_id, const gchar *participant,
const gchar *cipher, const gchar *auth,
const gchar *key, gsize key_len)
{
PurpleMediaBackendFs2Stream *stream;
GstStructure *srtp;
GError *err = NULL;
gboolean result;
g_return_val_if_fail(PURPLE_MEDIA_IS_BACKEND_FS2(self), FALSE);
stream = get_stream(PURPLE_MEDIA_BACKEND_FS2(self), sess_id,
participant);
if (!stream)
return FALSE;
srtp = create_fs2_srtp_structure(cipher, auth, key, key_len);
if (!srtp)
return FALSE;
result = fs_stream_set_decryption_parameters(stream->stream, srtp,
&err);
if (!result) {
purple_debug_error("backend-fs2",
"Error setting decryption parameters: %s\n", err->message);
g_error_free(err);
}
gst_structure_free(srtp);
return result;
}
static gboolean
purple_media_backend_fs2_set_require_encryption(PurpleMediaBackend *self,
const gchar *sess_id, const gchar *participant,
gboolean require_encryption)
{
PurpleMediaBackendFs2Stream *stream;
stream = get_stream(PURPLE_MEDIA_BACKEND_FS2(self), sess_id,
participant);
if (!stream) {
return FALSE;
}
g_object_set(stream->stream, "require-encryption",
require_encryption, NULL);
return TRUE;
}
static gboolean
purple_media_backend_fs2_set_send_codec(PurpleMediaBackend *self,
const gchar *sess_id, PurpleMediaCodec *codec)
{
PurpleMediaBackendFs2Session *session;
FsCodec *fscodec;
GError *err = NULL;
g_return_val_if_fail(PURPLE_MEDIA_IS_BACKEND_FS2(self), FALSE);
session = get_session(PURPLE_MEDIA_BACKEND_FS2(self), sess_id);
if (session == NULL)
return FALSE;
fscodec = codec_to_fs(codec);
fs_session_set_send_codec(session->session, fscodec, &err);
fs_codec_destroy(fscodec);
if (err) {
purple_debug_error("media", "Error setting send codec\n");
g_error_free(err);
return FALSE;
}
return TRUE;
}
static const gchar **
purple_media_backend_fs2_get_available_params(void)
{
static const gchar *supported_params[] = {
"sdes-cname", "sdes-email", "sdes-location", "sdes-name", "sdes-note",
"sdes-phone", "sdes-tool", NULL
};
return supported_params;
}
static const gchar*
param_to_sdes_type(const gchar *param)
{
const gchar **supported = purple_media_backend_fs2_get_available_params();
static const gchar *sdes_types[] = {
"cname", "email", "location", "name", "note", "phone", "tool", NULL
};
guint i;
for (i = 0; supported[i] != NULL; ++i) {
if (purple_strequal(param, supported[i])) {
return sdes_types[i];
}
}
return NULL;
}
static void
purple_media_backend_fs2_set_params(PurpleMediaBackend *self,
guint num_params, GParameter *params)
{
PurpleMediaBackendFs2Private *priv;
guint i;
GstStructure *sdes;
g_return_if_fail(PURPLE_MEDIA_IS_BACKEND_FS2(self));
priv = purple_media_backend_fs2_get_instance_private(
PURPLE_MEDIA_BACKEND_FS2(self));
if (priv->conference == NULL &&
!init_conference(PURPLE_MEDIA_BACKEND_FS2(self))) {
purple_debug_error("backend-fs2",
"Error initializing the conference.\n");
return;
}
g_object_get(G_OBJECT(priv->conference), "sdes", &sdes, NULL);
for (i = 0; i != num_params; ++i) {
const gchar *sdes_type = param_to_sdes_type(params[i].name);
if (!sdes_type)
continue;
gst_structure_set(sdes, sdes_type,
G_TYPE_STRING, g_value_get_string(&params[i].value),
NULL);
}
g_object_set(G_OBJECT(priv->conference), "sdes", sdes, NULL);
gst_structure_free(sdes);
}
static gboolean
send_dtmf_callback(gpointer userdata)
{
FsSession *session = userdata;
fs_session_stop_telephony_event(session);
return FALSE;
}
static gboolean
purple_media_backend_fs2_send_dtmf(PurpleMediaBackend *self,
const gchar *sess_id, gchar dtmf, guint8 volume,
guint16 duration)
{
PurpleMediaBackendFs2Session *session;
FsDTMFEvent event;
g_return_val_if_fail(PURPLE_MEDIA_IS_BACKEND_FS2(self), FALSE);
session = get_session(PURPLE_MEDIA_BACKEND_FS2(self), sess_id);
if (session == NULL)
return FALSE;
/* Convert DTMF char into FsDTMFEvent enum */
switch(dtmf) {
case '0': event = FS_DTMF_EVENT_0; break;
case '1': event = FS_DTMF_EVENT_1; break;
case '2': event = FS_DTMF_EVENT_2; break;
case '3': event = FS_DTMF_EVENT_3; break;
case '4': event = FS_DTMF_EVENT_4; break;
case '5': event = FS_DTMF_EVENT_5; break;
case '6': event = FS_DTMF_EVENT_6; break;
case '7': event = FS_DTMF_EVENT_7; break;
case '8': event = FS_DTMF_EVENT_8; break;
case '9': event = FS_DTMF_EVENT_9; break;
case '*': event = FS_DTMF_EVENT_STAR; break;
case '#': event = FS_DTMF_EVENT_POUND; break;
case 'A': event = FS_DTMF_EVENT_A; break;
case 'B': event = FS_DTMF_EVENT_B; break;
case 'C': event = FS_DTMF_EVENT_C; break;
case 'D': event = FS_DTMF_EVENT_D; break;
default:
return FALSE;
}
if (!fs_session_start_telephony_event(session->session,
event, volume)) {
return FALSE;
}
if (duration <= 50) {
fs_session_stop_telephony_event(session->session);
} else {
g_timeout_add(duration, send_dtmf_callback,
session->session);
}
return TRUE;
}
#else
GType
purple_media_backend_fs2_get_type(void)
{
return G_TYPE_NONE;
}
#endif /* USE_VV */
GstElement *
purple_media_backend_fs2_get_src(PurpleMediaBackendFs2 *self,
const gchar *sess_id)
{
#ifdef USE_VV
PurpleMediaBackendFs2Session *session = get_session(self, sess_id);
return session != NULL ? session->src : NULL;
#else
return NULL;
#endif
}
GstElement *
purple_media_backend_fs2_get_tee(PurpleMediaBackendFs2 *self,
const gchar *sess_id, const gchar *who)
{
#ifdef USE_VV
if (sess_id != NULL && who == NULL) {
PurpleMediaBackendFs2Session *session =
get_session(self, sess_id);
return (session != NULL) ? session->tee : NULL;
} else if (sess_id != NULL && who != NULL) {
PurpleMediaBackendFs2Stream *stream =
get_stream(self, sess_id, who);
return (stream != NULL) ? stream->tee : NULL;
}
#endif /* USE_VV */
g_return_val_if_reached(NULL);
}
void
purple_media_backend_fs2_set_input_volume(PurpleMediaBackendFs2 *self,
const gchar *sess_id, double level)
{
#ifdef USE_VV
PurpleMediaBackendFs2Private *priv;
GList *sessions;
g_return_if_fail(PURPLE_MEDIA_IS_BACKEND_FS2(self));
priv = purple_media_backend_fs2_get_instance_private(self);
purple_prefs_set_int("/purple/media/audio/volume/input", level);
if (sess_id == NULL)
sessions = g_hash_table_get_values(priv->sessions);
else
sessions = g_list_append(NULL, get_session(self, sess_id));
for (; sessions; sessions = g_list_delete_link(sessions, sessions)) {
PurpleMediaBackendFs2Session *session = sessions->data;
if (session->type & PURPLE_MEDIA_SEND_AUDIO) {
gchar *name = g_strdup_printf("volume_%s",
session->id);
GstElement *volume = gst_bin_get_by_name(
GST_BIN(priv->confbin), name);
g_free(name);
g_object_set(volume, "volume", level/10.0, NULL);
}
}
#endif /* USE_VV */
}
void
purple_media_backend_fs2_set_output_volume(PurpleMediaBackendFs2 *self,
const gchar *sess_id, const gchar *who, double level)
{
#ifdef USE_VV
GList *streams;
g_return_if_fail(PURPLE_MEDIA_IS_BACKEND_FS2(self));
purple_prefs_set_int("/purple/media/audio/volume/output", level);
streams = get_streams(self, sess_id, who);
for (; streams; streams = g_list_delete_link(streams, streams)) {
PurpleMediaBackendFs2Stream *stream = streams->data;
if (stream->session->type & PURPLE_MEDIA_RECV_AUDIO
&& GST_IS_ELEMENT(stream->volume)) {
g_object_set(stream->volume, "volume",
level/10.0, NULL);
}
}
#endif /* USE_VV */
}
#ifdef USE_VV
static gboolean
purple_media_backend_fs2_set_send_rtcp_mux(PurpleMediaBackend *self,
const gchar *sess_id, const gchar *participant,
gboolean send_rtcp_mux)
{
PurpleMediaBackendFs2Stream *stream;
g_return_val_if_fail(PURPLE_MEDIA_IS_BACKEND_FS2(self), FALSE);
stream = get_stream(PURPLE_MEDIA_BACKEND_FS2(self),
sess_id, participant);
if (stream != NULL &&
g_object_class_find_property (G_OBJECT_GET_CLASS (stream->stream),
"send-rtcp-mux") != NULL) {
g_object_set (stream->stream, "send-rtcp-mux", send_rtcp_mux, NULL);
return TRUE;
}
return FALSE;
}
#endif /* USE_VV */