/*
* Farstream - Farstream Multicast UDP Stream Transmitter
*
* Copyright 2007 Collabora Ltd.
* @author: Olivier Crete <olivier.crete@collabora.co.uk>
* Copyright 2007 Nokia Corp.
*
* fs-multicast-stream-transmitter.c - A Farstream Multiast UDP stream transmitter
*
* 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
*/
/**
* SECTION:fs-multicast-stream-transmitter
* @short_description: A stream transmitter object for Multicast UDP
* @see_also: #FsRawUdpStreamTransmitter
*
* The multicast transmitter allows data to be sent over and received from
* multicasted UDP on IPv4.
*
* This stream transmitter never emits local candidates. It will listen
* to the port specified in the remote candidate. And will also send to that
* port. It accepts only a single remote candidate per component, if a new one
* is given, it will replace the previous one for that component.
*
* The transmitter will only stop sending to a multicast group when all of its
* StreamTransmitters that have this multicast group as destination have their
* "sending" property set to false. Multiple stream transmitters can point to
* the same multicast groups from the same Transmitter (session), and only one
* copy of each packet will be received.
*
* It will only listen to and send from the IP specified in the
* prefered-local-candidates. There can be only one preferred candidate per
* component. Only the component_id and the ip will be used from the preferred
* local candidates, everything else is ignored.
*
* Packets sent will be looped back (so that other clients on the same session
* can be on the same machine.
*
* The name of this transmitter is "multicast".
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "fs-multicast-stream-transmitter.h"
#include "fs-multicast-transmitter.h"
#include <farstream/fs-candidate.h>
#include <farstream/fs-conference.h>
#include <gst/gst.h>
#include <string.h>
#include <sys/types.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#ifdef G_OS_WIN32
# include <winsock2.h>
#else /*G_OS_WIN32*/
# include <netdb.h>
# include <sys/socket.h>
# include <netinet/in.h>
# include <arpa/inet.h>
#endif /*G_OS_WIN32*/
GST_DEBUG_CATEGORY_EXTERN (fs_multicast_transmitter_debug);
#define GST_CAT_DEFAULT fs_multicast_transmitter_debug
/* Signals */
enum
{
LAST_SIGNAL
};
/* props */
enum
{
PROP_0,
PROP_SENDING,
PROP_PREFERRED_LOCAL_CANDIDATES
};
struct _FsMulticastStreamTransmitterPrivate
{
gboolean disposed;
/* We don't actually hold a ref to this,
* But since our parent FsStream can not exist without its parent
* FsSession, we should be safe
*/
FsMulticastTransmitter *transmitter;
GMutex mutex;
/* Protected by the mutex */
gboolean sending;
/*
* We have at most of those per component (index 0 is unused)
*/
FsCandidate **local_candidate;
/* Protected by the mutex */
FsCandidate **remote_candidate;
/* Protected by the mutex */
UdpSock **udpsocks;
GList *preferred_local_candidates;
};
#define FS_MULTICAST_STREAM_TRANSMITTER_GET_PRIVATE(o) \
(G_TYPE_INSTANCE_GET_PRIVATE ((o), FS_TYPE_MULTICAST_STREAM_TRANSMITTER, \
FsMulticastStreamTransmitterPrivate))
#define FS_MULTICAST_STREAM_TRANSMITTER_LOCK(s) \
g_mutex_lock (&(s)->priv->mutex)
#define FS_MULTICAST_STREAM_TRANSMITTER_UNLOCK(s) \
g_mutex_unlock (&(s)->priv->mutex)
static void fs_multicast_stream_transmitter_class_init (FsMulticastStreamTransmitterClass *klass);
static void fs_multicast_stream_transmitter_init (FsMulticastStreamTransmitter *self);
static void fs_multicast_stream_transmitter_dispose (GObject *object);
static void fs_multicast_stream_transmitter_finalize (GObject *object);
static void fs_multicast_stream_transmitter_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec);
static void fs_multicast_stream_transmitter_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec);
static gboolean fs_multicast_stream_transmitter_force_remote_candidates (
FsStreamTransmitter *streamtransmitter, GList *candidates,
GError **error);
static GObjectClass *parent_class = NULL;
// static guint signals[LAST_SIGNAL] = { 0 };
static GType type = 0;
GType
fs_multicast_stream_transmitter_get_type (void)
{
return type;
}
GType
fs_multicast_stream_transmitter_register_type (FsPlugin *module G_GNUC_UNUSED)
{
static const GTypeInfo info = {
sizeof (FsMulticastStreamTransmitterClass),
NULL,
NULL,
(GClassInitFunc) fs_multicast_stream_transmitter_class_init,
NULL,
NULL,
sizeof (FsMulticastStreamTransmitter),
0,
(GInstanceInitFunc) fs_multicast_stream_transmitter_init
};
type = g_type_register_static (FS_TYPE_STREAM_TRANSMITTER,
"FsMulticastStreamTransmitter", &info, 0);
return type;
}
static void
fs_multicast_stream_transmitter_class_init (FsMulticastStreamTransmitterClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
FsStreamTransmitterClass *streamtransmitterclass =
FS_STREAM_TRANSMITTER_CLASS (klass);
parent_class = g_type_class_peek_parent (klass);
gobject_class->set_property = fs_multicast_stream_transmitter_set_property;
gobject_class->get_property = fs_multicast_stream_transmitter_get_property;
streamtransmitterclass->force_remote_candidates =
fs_multicast_stream_transmitter_force_remote_candidates;
g_object_class_override_property (gobject_class, PROP_SENDING, "sending");
g_object_class_override_property (gobject_class,
PROP_PREFERRED_LOCAL_CANDIDATES, "preferred-local-candidates");
gobject_class->dispose = fs_multicast_stream_transmitter_dispose;
gobject_class->finalize = fs_multicast_stream_transmitter_finalize;
g_type_class_add_private (klass, sizeof (FsMulticastStreamTransmitterPrivate));
}
static void
fs_multicast_stream_transmitter_init (FsMulticastStreamTransmitter *self)
{
/* member init */
self->priv = FS_MULTICAST_STREAM_TRANSMITTER_GET_PRIVATE (self);
self->priv->disposed = FALSE;
self->priv->sending = TRUE;
g_mutex_init (&self->priv->mutex);
}
static void
fs_multicast_stream_transmitter_dispose (GObject *object)
{
FsMulticastStreamTransmitter *self = FS_MULTICAST_STREAM_TRANSMITTER (object);
if (self->priv->disposed)
/* If dispose did already run, return. */
return;
if (self->priv->udpsocks)
{
if (self->priv->udpsocks[1])
{
if (self->priv->sending)
fs_multicast_transmitter_udpsock_dec_sending (
self->priv->udpsocks[1]);
fs_multicast_transmitter_put_udpsock (self->priv->transmitter,
self->priv->udpsocks[1], self->priv->remote_candidate[1]->ttl);
self->priv->udpsocks[1] = NULL;
}
}
/* Make sure dispose does not run twice. */
self->priv->disposed = TRUE;
parent_class->dispose (object);
}
static void
fs_multicast_stream_transmitter_finalize (GObject *object)
{
FsMulticastStreamTransmitter *self = FS_MULTICAST_STREAM_TRANSMITTER (object);
gint c; /* component_id */
if (self->priv->preferred_local_candidates)
{
fs_candidate_list_destroy (self->priv->preferred_local_candidates);
self->priv->preferred_local_candidates = NULL;
}
if (self->priv->remote_candidate)
{
for (c = 1; c <= self->priv->transmitter->components; c++)
{
if (self->priv->remote_candidate[c])
fs_candidate_destroy (self->priv->remote_candidate[c]);
self->priv->remote_candidate[c] = NULL;
}
g_free (self->priv->remote_candidate);
self->priv->remote_candidate = NULL;
}
if (self->priv->local_candidate)
{
for (c = 1; c <= self->priv->transmitter->components; c++)
{
if (self->priv->local_candidate[c])
fs_candidate_destroy (self->priv->local_candidate[c]);
self->priv->local_candidate[c] = NULL;
}
g_free (self->priv->local_candidate);
self->priv->local_candidate = NULL;
}
g_free (self->priv->udpsocks);
self->priv->udpsocks = NULL;
g_mutex_clear (&self->priv->mutex);
parent_class->finalize (object);
}
static void
fs_multicast_stream_transmitter_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
FsMulticastStreamTransmitter *self = FS_MULTICAST_STREAM_TRANSMITTER (object);
switch (prop_id)
{
case PROP_SENDING:
FS_MULTICAST_STREAM_TRANSMITTER_LOCK (self);
g_value_set_boolean (value, self->priv->sending);
FS_MULTICAST_STREAM_TRANSMITTER_UNLOCK (self);
break;
case PROP_PREFERRED_LOCAL_CANDIDATES:
g_value_set_boxed (value, self->priv->preferred_local_candidates);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
fs_multicast_stream_transmitter_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
FsMulticastStreamTransmitter *self = FS_MULTICAST_STREAM_TRANSMITTER (object);
switch (prop_id) {
case PROP_SENDING:
{
gboolean old_sending = self->priv->sending;
gboolean sending = g_value_get_boolean (value);
FS_MULTICAST_STREAM_TRANSMITTER_LOCK (self);
self->priv->sending = sending;
if (sending != old_sending)
if (self->priv->udpsocks[1])
{
guint8 ttl = self->priv->remote_candidate[1]->ttl;
fs_multicast_transmitter_udpsock_ref (self->priv->transmitter,
self->priv->udpsocks[1], ttl);
FS_MULTICAST_STREAM_TRANSMITTER_UNLOCK (self);
if (sending)
fs_multicast_transmitter_udpsock_inc_sending (
self->priv->udpsocks[1]);
else
fs_multicast_transmitter_udpsock_dec_sending (
self->priv->udpsocks[1]);
fs_multicast_transmitter_put_udpsock (self->priv->transmitter,
self->priv->udpsocks[1], ttl);
FS_MULTICAST_STREAM_TRANSMITTER_LOCK (self);
}
FS_MULTICAST_STREAM_TRANSMITTER_UNLOCK (self);
}
break;
case PROP_PREFERRED_LOCAL_CANDIDATES:
self->priv->preferred_local_candidates = g_value_dup_boxed (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static gboolean
fs_multicast_stream_transmitter_build (FsMulticastStreamTransmitter *self,
GError **error)
{
GList *item;
gint c;
self->priv->udpsocks = g_new0 (UdpSock *,
self->priv->transmitter->components + 1);
self->priv->local_candidate = g_new0 (FsCandidate *,
self->priv->transmitter->components + 1);
self->priv->remote_candidate = g_new0 (FsCandidate *,
self->priv->transmitter->components + 1);
for (item = g_list_first (self->priv->preferred_local_candidates);
item;
item = g_list_next (item))
{
FsCandidate *candidate = item->data;
if (candidate->proto != FS_NETWORK_PROTOCOL_UDP)
{
g_set_error (error, FS_ERROR, FS_ERROR_INVALID_ARGUMENTS,
"You set preferred candidate of a type %d that is not"
" FS_NETWORK_PROTOCOL_UDP",
candidate->proto);
return FALSE;
}
if (candidate->component_id == 0)
{
g_set_error (error, FS_ERROR, FS_ERROR_INVALID_ARGUMENTS,
"Component id 0 is invalid");
return FALSE;
}
if (candidate->component_id > self->priv->transmitter->components)
{
g_set_error (error, FS_ERROR, FS_ERROR_INVALID_ARGUMENTS,
"You specified an invalid component id %d with is higher"
" than the maximum %d", candidate->component_id,
self->priv->transmitter->components);
return FALSE;
}
if (self->priv->local_candidate[candidate->component_id])
{
g_set_error (error, FS_ERROR,
FS_ERROR_INVALID_ARGUMENTS,
"You set more than one preferred local candidate for component %u",
candidate->component_id);
return FALSE;
}
if (candidate->ip == NULL)
{
g_set_error (error, FS_ERROR,
FS_ERROR_INVALID_ARGUMENTS,
"You have not set the local ip address for the preferred candidate"
" for this component");
return FALSE;
}
self->priv->local_candidate[candidate->component_id] =
fs_candidate_copy (candidate);
}
for (c = 1; c <= self->priv->transmitter->components; c++)
{
if (!self->priv->local_candidate[c])
{
self->priv->local_candidate[c] = fs_candidate_new (NULL, c,
FS_CANDIDATE_TYPE_MULTICAST, FS_NETWORK_PROTOCOL_UDP, NULL, 0);
}
}
return TRUE;
}
static gboolean
fs_multicast_stream_transmitter_add_remote_candidate (
FsMulticastStreamTransmitter *self, FsCandidate *candidate,
GError **error)
{
UdpSock *newudpsock = NULL;
FS_MULTICAST_STREAM_TRANSMITTER_LOCK (self);
if (self->priv->remote_candidate[candidate->component_id])
{
FsCandidate *old_candidate =
self->priv->remote_candidate[candidate->component_id];
if (old_candidate->port == candidate->port &&
old_candidate->ttl == candidate->ttl &&
!strcmp (old_candidate->ip, candidate->ip))
{
GST_DEBUG ("Re-set the same candidate, ignoring");
FS_MULTICAST_STREAM_TRANSMITTER_UNLOCK (self);
return TRUE;
}
}
FS_MULTICAST_STREAM_TRANSMITTER_UNLOCK (self);
/*
* IMPROVE ME: We should probably check that the candidate's IP
* has the format x.x.x.x where x is [0,255] using GRegex, etc
* We should also check if the address is in the multicast range
*/
newudpsock = fs_multicast_transmitter_get_udpsock (
self->priv->transmitter,
candidate->component_id,
self->priv->local_candidate[candidate->component_id]->ip,
candidate->ip,
candidate->port,
candidate->ttl,
candidate->component_id == 1 ? self->priv->sending : TRUE,
error);
if (!newudpsock)
return FALSE;
FS_MULTICAST_STREAM_TRANSMITTER_LOCK (self);
if (self->priv->udpsocks[candidate->component_id] &&
candidate->component_id == 1)
{
if (self->priv->sending)
fs_multicast_transmitter_udpsock_dec_sending (
self->priv->udpsocks[candidate->component_id]);
fs_multicast_transmitter_put_udpsock (self->priv->transmitter,
self->priv->udpsocks[candidate->component_id],
self->priv->remote_candidate[candidate->component_id]->ttl);
}
self->priv->udpsocks[candidate->component_id] = newudpsock;
fs_candidate_destroy (self->priv->remote_candidate[candidate->component_id]);
self->priv->remote_candidate[candidate->component_id] =
fs_candidate_copy (candidate);
FS_MULTICAST_STREAM_TRANSMITTER_UNLOCK (self);
g_signal_emit_by_name (self, "new-active-candidate-pair",
self->priv->local_candidate[candidate->component_id],
self->priv->remote_candidate[candidate->component_id]);
return TRUE;
}
static gboolean
fs_multicast_stream_transmitter_force_remote_candidates (
FsStreamTransmitter *streamtransmitter, GList *candidates,
GError **error)
{
GList *item = NULL;
FsMulticastStreamTransmitter *self =
FS_MULTICAST_STREAM_TRANSMITTER (streamtransmitter);
for (item = candidates; item; item = g_list_next (item))
{
FsCandidate *candidate = item->data;
if (candidate->proto != FS_NETWORK_PROTOCOL_UDP) {
g_set_error (error, FS_ERROR, FS_ERROR_INVALID_ARGUMENTS,
"You set a candidate of a type %d that is not"
" FS_NETWORK_PROTOCOL_UDP",
candidate->proto);
return FALSE;
}
if (candidate->type != FS_CANDIDATE_TYPE_MULTICAST)
{
g_set_error (error, FS_ERROR, FS_ERROR_INVALID_ARGUMENTS,
"The remote candidate is not of the right type, it should be"
" FS_CANDIDATE_TYPE_MULTICAST (%d), but it is %d",
FS_CANDIDATE_TYPE_MULTICAST,
candidate->type);
return FALSE;
}
if (!candidate->ip) {
g_set_error (error, FS_ERROR, FS_ERROR_INVALID_ARGUMENTS,
"The candidate passed does not contain a valid ip");
return FALSE;
}
if (candidate->component_id == 0 ||
candidate->component_id > self->priv->transmitter->components) {
g_set_error (error, FS_ERROR, FS_ERROR_INVALID_ARGUMENTS,
"The candidate passed has an invalid component id %u (not in [1,%u])",
candidate->component_id, self->priv->transmitter->components);
return FALSE;
}
if (candidate->ttl == 0)
{
g_set_error (error, FS_ERROR, FS_ERROR_INVALID_ARGUMENTS,
"The TTL for IPv4 multicast candidates must not be 0");
return FALSE;
}
}
for (item = candidates; item; item = g_list_next (item))
if (!fs_multicast_stream_transmitter_add_remote_candidate (self,
item->data, error))
return FALSE;
return TRUE;
}
FsMulticastStreamTransmitter *
fs_multicast_stream_transmitter_newv (FsMulticastTransmitter *transmitter,
guint n_parameters, GParameter *parameters, GError **error)
{
FsMulticastStreamTransmitter *streamtransmitter = NULL;
streamtransmitter = g_object_newv (FS_TYPE_MULTICAST_STREAM_TRANSMITTER,
n_parameters, parameters);
if (!streamtransmitter) {
g_set_error (error, FS_ERROR, FS_ERROR_CONSTRUCTION,
"Could not build the stream transmitter");
return NULL;
}
streamtransmitter->priv->transmitter = transmitter;
if (!fs_multicast_stream_transmitter_build (streamtransmitter, error)) {
g_object_unref (streamtransmitter);
return NULL;
}
return streamtransmitter;
}