Logo Search packages:      
Sourcecode: farsight version File versions

rtpgstcodecs.c

/*
 * rtpgstcodecs.h - Headers for RTP gstreamer codec setting
 *
 * Farsight RTP/AVP/SAVP/AVPF Module
 * Copyright (C) 2005,2006 Collabora Ltd.
 * Copyright (C) 2005,2006 Nokia Corporation
 *   @author Rob Taylor <rob.taylor@collabora.co.uk>
 *   @author Philippe Kalaf <philippe.kalaf@collabora.co.uk>
 * Copyright (C) 2005 INdT 
 *   @author Andre Moreira Magalhaes <andre.magalhaes@indt.org.br>
 *
 * This program 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 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 Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "rtpgstcodecs.h"

#include <string.h>

#define GST_ELEMENTS_CONF_FILE "gstelements.conf"

#define MAX_MTU 100

static gboolean check_for_sink (GList *pipeline);
static gboolean check_for_src (GList *pipeline);

static GList *
codec_cap_list_intersect (GList *list1, GList *list2);

/* the filter function for selecting the elements we can use in
 * autoplugging */
typedef gboolean (FilterFunc) (GstElementFactory *factory);

typedef struct _CodecCap CodecCap;

static gboolean is_payloader (GstElementFactory *factory);
static gboolean is_depayloader (GstElementFactory *factory);
static gboolean is_encoder (GstElementFactory *factory);
static gboolean is_decoder (GstElementFactory *factory);

static void debug_pipeline (GList *pipeline);
#if 0
static void debug_codec_cap_list (GList *codec_cap_list);

static void debug_codec_cap (CodecCap *codec_cap);
#endif

static GList *get_plugins_filtered_from_caps (FilterFunc filter,
                                              GstCaps *caps,
                                              GstPadDirection direction);

static gboolean check_caps_compatibility (GstElementFactory *factory,
                                          GstCaps *caps,
                                          GstCaps **matched_caps);

static GList *create_codec_cap_list (GstElementFactory *factory,
                                      GstPadDirection direction,
                                      GList *list,
                                      GstCaps *rtp_caps);

static GList *detect_send_codecs (GstCaps *caps);
static GList *detect_recv_codecs (GstCaps *caps);

static void create_codec_lists (FarsightMediaType media_type,
                                GList *recv_list, GList *send_list);

static void parse_codec_cap_list (GList *list, FarsightMediaType media_type);

static gint compare_caps (gconstpointer a, gconstpointer b);

static gboolean extract_field_data (GQuark field_id,
                                    const GValue *value,
                                    gpointer user_data);

static gboolean match_dyn_pt (const gchar *key, CodecInternal *codec_internal,
    const FarsightCodec *codec_to_match);

static void set_codec_preference_order (GList **list_codecs,
                                        const FarsightCodec *codec,
                                        guint pos);

static void set_options_on_element (GstElementFactory *elem_factory, 
    GstElement *current_element);

static void create_ghost_pad (GstElement *current_element, const gchar *dir,
    GstElement *codec_bin);

static gboolean hash_find_func (const gchar *key, 
                                CodecInternal *codec_internal,
                                const FarsightCodec *codec_to_match);

static GList *list_codecs[FARSIGHT_MEDIA_TYPE_LAST+1] = { NULL };
static GHashTable *list_codecs_internal[FARSIGHT_MEDIA_TYPE_LAST+1] = { NULL };
static GKeyFile *elem_config = NULL;
static guint pt_count = 96;

struct _CodecCap
{
  GstCaps *caps; /* media caps */
  GstCaps *rtp_caps; /* RTP caps of given media caps */
  /* 2 list approach so we can have a separation after an intersection is
   * calculted */
  GList *element_list1; /* elements for media, enc, dec, pay, depay */
  GList *element_list2; /* elements for media, enc, dec, pay, depay */
};

static void
codec_cap_list_free (GList *list)
{
  GList *mwalk;
  for (mwalk = list; mwalk; mwalk = g_list_next (mwalk))
  {
    CodecCap *codec_cap = mwalk->data;
    if (codec_cap->caps)
    {
      gst_caps_unref (codec_cap->caps);
    }
    if (codec_cap->rtp_caps)
    {
      gst_caps_unref (codec_cap->rtp_caps);
    }

    GList *walk;
    for (walk = codec_cap->element_list1; walk; walk = g_list_next (walk))
    {
      if (walk->data)
      {
        gst_object_unref ((GstElementFactory *)walk->data);
      }
    }
    for (walk = codec_cap->element_list2; walk; walk = g_list_next (walk))
    {
      if (walk->data)
      {
        gst_object_unref ((GstElementFactory *)walk->data);
      }
    }

    if (codec_cap->element_list1)
    {
      g_list_free (codec_cap->element_list1);
    }

    if (codec_cap->element_list2)
    {
      g_list_free (codec_cap->element_list2);
    }
    g_free (codec_cap);
  }

  g_list_free (list);
}

GKeyFile *
load_config_file ()
{
  GKeyFile *key_file;
  gchar *user_conf;
  gboolean found = FALSE;

  key_file = g_key_file_new();

  /*user_conf = g_build_filename (g_get_user_config_dir(), "farsight",
      GST_ELEMENTS_CONF_FILE, NULL); */
  user_conf = g_build_filename (g_get_home_dir(), ".farsight",
      GST_ELEMENTS_CONF_FILE, NULL);
  g_debug ("looking for %s", user_conf);

  found = g_key_file_load_from_file
      (key_file, user_conf, G_KEY_FILE_NONE, NULL);

  g_free (user_conf);
  /* let's try the system paths */
  if (!found)
  {
    gchar **dirs;
    gchar *system_conf;
    int i = 0;

    /* dirs = g_get_system_config_dirs(); */
    dirs = g_malloc0 (2*sizeof (gchar *));
    dirs[0] = g_strdup (SYSCONFDIR);
    while (!found && dirs[i])
    {
      system_conf = g_build_filename (dirs[i], "farsight",
          GST_ELEMENTS_CONF_FILE, NULL);
      g_debug ("looking for %s", system_conf);

      found = g_key_file_load_from_file
        (key_file, system_conf, G_KEY_FILE_NONE, NULL);
      i++;
      g_free (system_conf);
    }
    g_strfreev (dirs);
  }

  if (!found)
  {
    g_warning ("Could not find %s config file", GST_ELEMENTS_CONF_FILE);
    g_key_file_free (key_file);
    return NULL;
  }

  return key_file;
}

/**
 * load_codecs:
 * @media_type: a #FarsightMediaType
 *
 * find all plugins that follow the pattern:
 * input (microphone) -> N* -> rtp payloader -> network
 * network  -> rtp depayloader -> N* -> output (soundcard) 
 * media_type defines if we want audio or video codecs
 *
 * Returns : a #GList of codecs
 */
const GList *
load_codecs (FarsightMediaType media_type)
{
  GstCaps *caps;
  GList *recv_list = NULL;
  GList *send_list = NULL;

  /* if already computed just return list */
  if (list_codecs[media_type])
  {
    return list_codecs[media_type];
  }

  /* let's not reload if already loaded */
  if (!elem_config)
  {
    elem_config = load_config_file();
  }

  /* caps used to find the payloaders and depayloaders based on media type */
  if (media_type == FARSIGHT_MEDIA_TYPE_AUDIO)
  {
    caps = gst_caps_new_simple ("application/x-rtp",
            "media", G_TYPE_STRING, "audio", NULL);
  }
  else if (media_type == FARSIGHT_MEDIA_TYPE_VIDEO)
  {
    caps = gst_caps_new_simple ("application/x-rtp",
            "media", G_TYPE_STRING, "video", NULL);
  }
  else
  {
    g_warning ("Invalid media type given to load_codecs");
    return NULL;
  }

  recv_list = detect_recv_codecs (caps);
  send_list = detect_send_codecs (caps);

  gst_caps_unref (caps);
  /* if we can't send or recv let's just stop here */
  if (!recv_list && !send_list)
  {
    return NULL;
  }

  create_codec_lists (media_type, recv_list, send_list);

  codec_cap_list_free (recv_list);
  codec_cap_list_free (send_list);

  return list_codecs[media_type];
}

static void
create_codec_lists (FarsightMediaType media_type,
    GList *recv_list, GList *send_list)
{
  GList *duplex_list = NULL;
  list_codecs_internal[media_type] = g_hash_table_new_full (g_direct_hash,
      g_direct_equal, NULL, codec_internal_destroy);

  /* TODO we should support non duplex as well, as in have some caps that are
   * only sendable or only receivable */
  duplex_list = codec_cap_list_intersect (recv_list, send_list);

  parse_codec_cap_list (duplex_list, media_type);

  codec_cap_list_free (duplex_list);
}

/* insert given codec_cap list into list_codecs and list_codecs_internal */
static void
parse_codec_cap_list (GList *list, FarsightMediaType media_type)
{
  GList *walk;
  CodecCap *codec_cap;
  FarsightCodec *codec;
  CodecInternal *codec_internal;
  gint i;

  /* go thru all common caps */
  for (walk = list; walk; walk = g_list_next (walk))
  {
    codec_cap = (CodecCap *)(walk->data);

    codec = g_new0 (FarsightCodec, 1);
    codec->id = -1;
    codec->clock_rate = -1;

    for (i = 0; i < gst_caps_get_size (codec_cap->rtp_caps); i++)
    {
      GstStructure *structure = gst_caps_get_structure (codec_cap->rtp_caps, i);

      gst_structure_foreach (structure, extract_field_data,
            (gpointer) codec);
    }

    if (codec->id == -1 || codec->clock_rate == -1 || !codec->encoding_name)
    {
      g_warning ("Not enough information in rtp caps");
      farsight_codec_destroy (codec);
      continue;
    }

    codec_internal = g_new0 (CodecInternal, 1);
    codec_internal->codec = codec;
    codec_internal->media_caps = gst_caps_copy (codec_cap->caps);

    GList *w;
    codec_internal->send_pipeline_factory =
      g_list_copy (codec_cap->element_list2);
    for (w = codec_internal->send_pipeline_factory; w; w = g_list_next (w))
    {
      gst_object_ref (GST_OBJECT (w->data));
    }
    codec_internal->receive_pipeline_factory =
      g_list_copy (codec_cap->element_list1);
    for (w = codec_internal->receive_pipeline_factory; w; w = g_list_next (w))
    {
      gst_object_ref (GST_OBJECT (w->data));
    }

    codec_internal->has_sink =
      check_for_sink (codec_internal->receive_pipeline_factory);
    codec_internal->has_src =
      check_for_src (codec_internal->send_pipeline_factory);

    /* insert new information into tables */
    g_hash_table_insert (list_codecs_internal[media_type],
        (gpointer) codec->id, (gpointer) codec_internal);
    g_debug ("adding codec with pt %d, send_pipeline %p, receive_pipeline %p",
        codec->id, codec_internal->send_pipeline_factory,
        codec_internal->receive_pipeline_factory);
    debug_pipeline (codec_internal->send_pipeline_factory);
    debug_pipeline (codec_internal->receive_pipeline_factory);
    list_codecs[media_type] = g_list_append (list_codecs[media_type], codec);
  }
}

/* find all encoder/payloader combos and build list for them */
static GList *
detect_send_codecs (GstCaps *caps)
{
  GList *payloaders, *encoders;
  GList *send_list = NULL;

  /* find all payloader caps. All payloaders should be from klass
   * Codec/Payloader/Network and have as output a data of the mimetype
   * application/x-rtp */
  payloaders = get_plugins_filtered_from_caps (is_payloader, caps, GST_PAD_SINK);

  /* no payloader found. giving up */
  if (!payloaders)
  {
    g_warning ("No RTP Payloaders found");
    return NULL;
  }

  /* find all encoders based on is_encoder filter */
  encoders = get_plugins_filtered_from_caps (is_encoder, NULL, GST_PAD_SRC);
  if (!encoders)
  {
    codec_cap_list_free (payloaders);
    g_warning ("No encoders found");
    return NULL;
  }

  /* create intersection list of codecs common
   * to encoders and payloaders lists */
  send_list = codec_cap_list_intersect (payloaders, encoders);

  if (!send_list)
  {
    g_warning ("No compatible encoder/payloader pairs found");
  }

  codec_cap_list_free (payloaders);
  codec_cap_list_free (encoders);

  return send_list;
}

/* find all decoder/depayloader combos and build list for them */
static GList *
detect_recv_codecs (GstCaps *caps)
{
  GList *depayloaders, *decoders;
  GList *recv_list = NULL;

  /* find all depayloader caps. All depayloaders should be from klass
   * Codec/Depayr/Network and have as input a data of the mimetype
   * application/x-rtp */
  depayloaders = get_plugins_filtered_from_caps (is_depayloader, caps,
      GST_PAD_SRC);

  /* no depayloader found. giving up */
  if (!depayloaders)
  {
    g_warning ("No RTP Depayloaders found");
    return NULL;
  }

  /* find all decoders based on is_decoder filter */
  decoders = get_plugins_filtered_from_caps (is_decoder, NULL, GST_PAD_SINK);

  if (!decoders)
  {
    codec_cap_list_free (depayloaders);
    g_warning ("No decoders found");
    return NULL;
  }

  /* create intersection list of codecs common
   * to decoders and depayloaders lists */
  recv_list = codec_cap_list_intersect (depayloaders, decoders);

  if (!recv_list)
  {
    g_warning ("No compatible decoder/depayloader pairs found");
  }

  codec_cap_list_free (depayloaders);
  codec_cap_list_free (decoders);

  return recv_list;
}

/* returns the intersection of two lists */
static GList *
codec_cap_list_intersect (GList *list1, GList *list2)
{
  GList *walk1, *walk2;
  CodecCap *codec_cap1, *codec_cap2;
  GstCaps *caps1, *caps2;
  GstCaps *rtp_caps1, *rtp_caps2;
  GList *intersection_list = NULL;

  for (walk1 = list1; walk1; walk1 = g_list_next (walk1))
  {
    codec_cap1 = (CodecCap *)(walk1->data);
    caps1 = codec_cap1->caps;
    rtp_caps1 = codec_cap1->rtp_caps;
    for (walk2 = list2; walk2; walk2 = g_list_next (walk2))
    {
      codec_cap2 = (CodecCap *)(walk2->data);
      caps2 = codec_cap2->caps;
      rtp_caps2 = codec_cap2->rtp_caps;

      //g_debug ("intersecting %s AND %s", gst_caps_to_string (caps1), gst_caps_to_string (caps2));
      GstCaps *intersection = gst_caps_intersect (caps1, caps2);
      //g_debug ("got %s", gst_caps_to_string (intersection));
      if (!gst_caps_is_empty (intersection))
      {
        CodecCap *item = g_new0 (CodecCap, 1);
        item->caps = intersection;
        /* unionize rtp caps */
        if (rtp_caps1 && rtp_caps2)
        {
          item->rtp_caps = gst_caps_copy (rtp_caps1);
          gst_caps_append (item->rtp_caps, gst_caps_copy (rtp_caps2));
        }
        else if (rtp_caps1)
        {
          item->rtp_caps = rtp_caps1;
          gst_caps_ref (rtp_caps1);
        }
        else if (rtp_caps2)
        {
          item->rtp_caps = rtp_caps2;
          gst_caps_ref (rtp_caps2);
        }

        /* during an intersect, we concat/copy previous lists together and put them
         * into 1 and 2 */
        GList *swalk;
        for (swalk = codec_cap1->element_list1; swalk;
            swalk = g_list_next(swalk))
        {
          item->element_list1 = g_list_prepend (item->element_list1,
              gst_object_ref(swalk->data));
        }
        for (swalk = codec_cap1->element_list2; swalk;
            swalk = g_list_next(swalk))
        {
          item->element_list1 = g_list_prepend (item->element_list1,
              gst_object_ref(swalk->data));
        }
        for (swalk = codec_cap2->element_list1; swalk;
            swalk = g_list_next(swalk))
        {
          item->element_list2 = g_list_prepend (item->element_list2,
              gst_object_ref(swalk->data));
        }
        for (swalk = codec_cap2->element_list2; swalk;
            swalk = g_list_next(swalk))
        {
          item->element_list2 = g_list_prepend (item->element_list2,
              gst_object_ref(swalk->data));
        }

        intersection_list = g_list_prepend (intersection_list, item);
        break;
      }
      gst_caps_unref (intersection);
    }
  }

  return intersection_list;
}

void
unload_codecs (FarsightMediaType media_type)
{
  if (list_codecs[media_type]) {
    farsight_codec_list_destroy (list_codecs[media_type]);
    list_codecs[media_type] = NULL;
  }
  if (list_codecs_internal[media_type])
  {
    g_hash_table_destroy (list_codecs_internal[media_type]);
    list_codecs_internal[media_type] = NULL;
  }
  pt_count = 96;
}

/**
 * sort_codecs:
 * @local_codecs: Codec list returned by #load_codecs
 * @codec_preference: An array of #FarsightCodecPreference for sorting
 * @size: Size of codec_preference array
 *
 * Sorts the given codec list based on the codec_preference array.
 */
void
sort_codecs (GList **list_codecs,
             FarsightCodecPreference codec_preference[],
             guint size)
{
  guint8 i = 0, pos = 0;
  GList *lc;
  FarsightCodec *codec;
  guint8 count;

  count = size;
  for (; i < count; ++i)
  {
    for (lc = *list_codecs; lc; lc = g_list_next (lc))
    {
      codec = (FarsightCodec *) lc->data;
      /* only match clock_rate if it is different than 0 */
      if ((g_strcasecmp (codec->encoding_name,
              codec_preference[i].encoding_name) == 0) &&
              (codec->clock_rate == 0 || 
               codec->clock_rate == codec_preference[i].clock_rate))
      {
        /* HACK ALERT this hack is to compensate for the fact that GTalk jingle
         * does not send us any clock-rates */
        if (codec->clock_rate == 0)
        {
          codec->clock_rate = codec_preference[i].clock_rate;
        }
        set_codec_preference_order (list_codecs, codec, pos++);
        break;
      }
    }
  }
}

/**
 * create_pt_caps_hashtable
 * @codecs: A list of #FarsightCodec elements
 *
 * Creates a pt capa hash table for use by rtpbin
 *
 * Returns: the new #GHashTable for the given codec list
 */
GHashTable *
create_pt_caps_hashtable(GList *codecs)
{
  FarsightCodec *codec = NULL;
  GList *lp = NULL;
  GstCaps *caps = NULL;

  GHashTable *pt_map = g_hash_table_new_full (g_direct_hash,
          g_direct_equal,
          NULL,
          (GDestroyNotify) gst_caps_unref);

  for(lp = codecs; lp; lp = g_list_next(lp))
  {
    codec = (FarsightCodec *)lp->data;
    if (codec->clock_rate == 0)
    {
      g_warning ("skipping pt %d because clock-rate is 0. "
          "We will not be able to receive this payload type", codec->id);
      continue;
    }
    caps = gst_caps_new_simple ("application/x-rtp",
        "clock-rate", G_TYPE_INT, codec->clock_rate, NULL);
    g_hash_table_insert(pt_map, GINT_TO_POINTER(codec->id), (gpointer) caps);
  }

  return pt_map;
}

gboolean
merge_hashtables (gpointer key, gpointer value,
    gpointer user_data)
{
  GHashTable *dest_hashtable = (GHashTable *)user_data;

  g_hash_table_insert (dest_hashtable,
      (gpointer) key, (gpointer) value);

  return TRUE;
}

/**
 * update_local_dynamic_codecs
 * @codecs: A list of #FarsightCodec elements (list of remote codecs)
 * 
 * Updates local dynamic codecs to match the given remote codecs
 *
 */
void
update_local_dynamic_codecs (FarsightMediaType media_type, 
    const GList *remote_codecs)
{
  FarsightCodec *codec = NULL;
  GList *lp = NULL;
  GHashTable *new_list_codecs_internal = NULL;

  for(lp = (GList *)remote_codecs; lp; lp = g_list_next(lp))
  {
    codec = (FarsightCodec *)lp->data;
    if (codec->id < 96)
    {
      continue;
    }

    if (!new_list_codecs_internal)
    {
      new_list_codecs_internal = g_hash_table_new_full (g_direct_hash,
          g_direct_equal, NULL, codec_internal_destroy);
    }

    /* let's find this dynamic codec's CodecInternal entry */
    CodecInternal *ci;
    if ((ci = g_hash_table_find (
            (GHashTable *) list_codecs_internal[media_type],
            (GHRFunc) match_dyn_pt, codec)))
    {
      if (codec->id != ci->codec->id)
      {
        /* let's remove the entry from the old hash table and insert it in the
         * new hash table with the new pt id */
        g_hash_table_steal (list_codecs_internal[media_type], 
            GINT_TO_POINTER (ci->codec->id));
        g_hash_table_insert (new_list_codecs_internal,
            (gpointer) codec->id, (gpointer) ci);

        g_message ("Replaced local codec %d %s with %d %s", ci->codec->id,
            ci->codec->encoding_name, codec->id, codec->encoding_name);

        /* let's replace our id with the new one */
        ci->codec->id = codec->id;
      }
    }
  }

  /* let's merge the new values back into the hash table */
  if (new_list_codecs_internal)
  {
    g_hash_table_foreach_steal (new_list_codecs_internal, merge_hashtables,
        list_codecs_internal[media_type]);

    g_assert (g_hash_table_size(new_list_codecs_internal) == 0);
    g_hash_table_destroy (new_list_codecs_internal);
  }
}

/**
 * create_dynamic_pt_table
 * @codecs: A list of #FarsightCodec elements (list of remote codecs)
 *
 * Creates a relation table between remote dynamic codecs and local dynamic
 * codecs
 *
 * Returns: the new #GHashTable for the given remote codec list
 */
GHashTable *
create_dynamic_pt_table (FarsightMediaType media_type, const GList
    *remote_codecs)
{
  FarsightCodec *codec = NULL;
  GList *lp = NULL;

  GHashTable *pt_map = g_hash_table_new (g_direct_hash,
      g_direct_equal);

  for(lp = (GList *)remote_codecs; lp; lp = g_list_next(lp))
  {
    codec = (FarsightCodec *)lp->data;
    if (codec->id < 96)
    {
      continue;
    }

    CodecInternal *ci;
    if ((ci = g_hash_table_find (
            (GHashTable *) list_codecs_internal[media_type],
            (GHRFunc) match_dyn_pt, codec))) 
    {
      g_hash_table_insert(pt_map, GINT_TO_POINTER(codec->id),
          GINT_TO_POINTER(ci->codec->id));
    }
  }

  return pt_map;
}

static gboolean
match_dyn_pt (const gchar *key, CodecInternal *codec_internal,
                const FarsightCodec *codec_to_match)
{
  /* check if its necessary compare more items */
  if (g_ascii_strcasecmp (codec_internal->codec->encoding_name,
        codec_to_match->encoding_name) != 0)
  {
    return FALSE;
  }
  if (codec_internal->codec->clock_rate != codec_to_match->clock_rate)
  {
    return FALSE;
  }

  return TRUE;
}

static void
set_codec_preference_order (GList **list_codecs,
                            const FarsightCodec *codec,
                            guint pos)
{
  gint index = 0;

  g_return_if_fail (codec != NULL);
  g_return_if_fail (pos <= g_list_length (*list_codecs));

  GList *lc;
  GList *codec_item = NULL;

  for (lc = *list_codecs; lc; lc = g_list_next (lc), ++index)
  {
    if (lc->data == codec) 
    {
      codec_item = lc;
    }
  }

  if (!codec_item)
  {
    g_print ("%s (%d): codec not supported\n", __FUNCTION__, __LINE__);
    return;
  }

  // do nothing, codec already sorted
  if (index == pos)
  {
    return;
  }

  *list_codecs = g_list_delete_link (*list_codecs, codec_item);
  *list_codecs = g_list_insert_before (*list_codecs,
          g_list_nth (*list_codecs, pos),
          (gpointer) codec);
}

static gboolean
is_payloader (GstElementFactory *factory)
{
  const gchar *klass = gst_element_factory_get_klass (factory);
  return (g_strrstr (klass, "Codec/Payloader/Network") != NULL);
}

static gboolean
is_depayloader (GstElementFactory *factory)
{
  const gchar *klass = gst_element_factory_get_klass (factory);
  return (g_strrstr (klass, "Codec/Depayr/Network") != NULL);
}

static gboolean
is_encoder (GstElementFactory *factory)
{
  const gchar *klass = gst_element_factory_get_klass (factory);
  /* we might have some sources that provide a non raw stream */
  return (g_strrstr (klass, "Encoder") != NULL ||
      g_strrstr (klass, "Source") != NULL);
}

static gboolean
is_decoder (GstElementFactory *factory)
{
  const gchar *klass = gst_element_factory_get_klass (factory);
  /* we might have some sinks that provide decoding */
  return (g_strrstr (klass, "Decoder") != NULL ||
      g_strrstr (klass, "Parser") != NULL ||
      g_strrstr (klass, "Sink") != NULL);
}

/* checks if there is a Source element in the give 
 * list of GstElementFactories */
static gboolean
check_for_src (GList *pipeline)
{
  GList *walk;
  const gchar *class;

  for (walk = pipeline; walk; walk = g_list_next (walk))
  {
    class = gst_element_factory_get_klass (GST_ELEMENT_FACTORY (walk->data));
    if (g_strrstr (class, "Source") != NULL)
    {
      return TRUE;
    }
  }
  return FALSE;
}

/* checks if there is a Sink element in the give 
 * list of GstElementFactories */
static gboolean
check_for_sink (GList *pipeline)
{
  GList *walk;
  const gchar *class;

  for (walk = pipeline; walk; walk = g_list_next (walk))
  {
      class = gst_element_factory_get_klass (GST_ELEMENT_FACTORY (walk->data));
      if (g_strrstr (class, "Sink") != NULL)
      {
          return TRUE;
      }
  }
  return FALSE;
}

static void
debug_pipeline (GList *pipeline)
{
  GList *walk;

  g_print ("pipeline: ");
  for (walk = pipeline; walk; walk = g_list_next (walk))
  {
    g_print ("%p:%d:%s ", walk->data,
        GST_OBJECT_REFCOUNT_VALUE(GST_OBJECT (walk->data)),
        gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (walk->data)));
  }
  g_print ("\n");
}

#if 0
static void
debug_codec_cap_list (GList *codec_cap_list)
{
  GList *walk;
  g_debug ("size of codec_cap list is %d", g_list_length (codec_cap_list));
  for (walk = codec_cap_list; walk; walk = g_list_next (walk))
  {
    debug_codec_cap ((CodecCap *)walk->data);
  }
}

static void
debug_codec_cap (CodecCap *codec_cap)
{
  gchar *caps;
  if (codec_cap->caps)
  {
    caps = gst_caps_to_string (codec_cap->caps);
    g_print ("%p:%d:media_caps %s\n", codec_cap->caps,
        GST_CAPS_REFCOUNT_VALUE(codec_cap->caps),
        caps);
    g_free (caps);
  }

  if (codec_cap->rtp_caps)
  {
    caps = gst_caps_to_string (codec_cap->rtp_caps);
    g_print ("%p:%d:rtp_caps %s\n", codec_cap->rtp_caps,
        GST_CAPS_REFCOUNT_VALUE(codec_cap->rtp_caps), caps);
    g_free (caps);
  }

  g_print ("element_list1 -> ");
  debug_pipeline (codec_cap->element_list1);
  g_print ("element_list2 -> ");
  debug_pipeline (codec_cap->element_list2);
}

#endif

/* creates/returns a list of CodecCap based on given filter function and caps */
static GList *
get_plugins_filtered_from_caps (FilterFunc filter,
                                GstCaps *caps,
                                GstPadDirection direction)
{
  GList *walk, *result;
  GstElementFactory *factory;
  GList *list = NULL;
  gboolean is_valid;
  GstCaps *matched_caps = NULL;

  result = gst_registry_get_feature_list (gst_registry_get_default (),
          GST_TYPE_ELEMENT_FACTORY);

  walk = result;
  while (walk)
  {
    factory = GST_ELEMENT_FACTORY (walk->data);
    is_valid = FALSE;

    if (!filter (factory))
    {
      goto next;
    }

    if (caps)
    {
      if (check_caps_compatibility (factory, caps, &matched_caps))
      {
        is_valid = TRUE;
      }
    }

    if (is_valid || !caps)
    {
      list = create_codec_cap_list (factory, direction, list, matched_caps);
      if (matched_caps)
      {
        gst_caps_unref (matched_caps);
      }
    }

next:
    walk = g_list_next (walk);
  }

  /*
  walk = result;
  while (walk)
  {
    factory = GST_ELEMENT_FACTORY (walk->data);
    g_debug ("new refcnt is %d", GST_OBJECT_REFCOUNT_VALUE (GST_OBJECT (factory)));
    walk = g_list_next (walk);
  }
  */

  gst_plugin_feature_list_free (result);

  return list;
}

/* check if caps are found on given element */
static gboolean
check_caps_compatibility (GstElementFactory *factory,
                          GstCaps *caps, GstCaps **matched_caps)
{
  const GList *pads;
  GstStaticPadTemplate *padtemplate;
  GstCaps *padtemplate_caps = NULL;

  if (!factory->numpadtemplates)
  {
    return FALSE;
  }

  pads = factory->staticpadtemplates;
  while (pads)
  {
    padtemplate = (GstStaticPadTemplate *) (pads->data);
    pads = g_list_next (pads);

    padtemplate_caps = gst_static_caps_get (&padtemplate->static_caps);
    if (gst_caps_is_any (padtemplate_caps))
    {
      goto next;
    }

    if (caps)
    {
      if (gst_caps_is_subset (padtemplate_caps, caps))
      {
        *matched_caps = padtemplate_caps;
        return TRUE;
      }
    }

next:
    if (padtemplate_caps)
    {
      gst_caps_unref (padtemplate_caps);
    }
  }

  *matched_caps = NULL;
  return FALSE;
}

/* adds the given element to a list of CodecCap */
/* if element has several caps, several CodecCap elements will be added */
/* if element caps already in list, will make sure Sink/Source elements have
 * priority and replace old ones */
static GList *
create_codec_cap_list (GstElementFactory *factory,
                       GstPadDirection direction,
                       GList *list,
                       GstCaps *rtp_caps)
{
  const GList *pads;
  GstStaticPadTemplate *padtemplate;
  GstCaps *caps = NULL;
  gint i;

  pads = factory->staticpadtemplates;
  while (pads)
  {
    padtemplate = (GstStaticPadTemplate *) (pads->data);
    pads = g_list_next (pads);

    if (padtemplate->direction == direction)
    {
      caps = gst_static_caps_get (&padtemplate->static_caps);
      break;
    }
  }

  /*
  g_debug ("%s caps are %s", gst_plugin_feature_get_name (GST_PLUGIN_FEATURE
        (factory)), gst_caps_to_string (caps));
        */

  /* skips caps ANY */
  if (caps && !gst_caps_is_any (caps))
  {
    /* let us add one entry to the list per media type */
    for (i = 0; i < gst_caps_get_size (caps); i++)
    {
      CodecCap *entry = NULL;
      GList *found_item = NULL;
      GstStructure *structure = gst_caps_get_structure (caps, i);
      GstCaps *cur_caps =
        gst_caps_new_full (gst_structure_copy (structure), NULL);

      /* let's check if this caps is already in the list, if so let's replace
       * that CodecCap list instead of creating a new one */
      found_item = g_list_find_custom (list, cur_caps, (GCompareFunc)compare_caps);
      if (found_item)
      {
        entry = (CodecCap *)found_item->data;
      }

      if (!entry)
      {
        entry = g_new0 (CodecCap, 1);

        entry->caps = cur_caps;
        if (rtp_caps)
        {
          entry->rtp_caps = rtp_caps;
          gst_caps_ref (rtp_caps);
        }
        list = g_list_prepend (list, entry);
        entry->element_list1 = g_list_prepend (NULL, factory);
        gst_object_ref (factory);
      }
      else
      {
        gst_caps_unref (cur_caps);
        /* FIXME Should we unionize RTP info from several elements even if they will
         * not be used? For now won't do it... */

        /* already exists, let's check if this element is in our config file, if
         * so give it priority */
        const gchar *name = gst_plugin_feature_get_name 
          (GST_PLUGIN_FEATURE (factory));
        /* FIXME has_group will loop and strcmp all the group names, this is not
         * optimal */
        if (elem_config && g_key_file_has_group (elem_config, name))
        {
          if (g_key_file_has_key (elem_config, name, "prioritize", NULL))
          {
            if (g_key_file_get_boolean (elem_config, name, "prioritize", NULL))
            {
              /* should be only one element, let's free it */
              if (entry->element_list1)
              {
                gst_object_unref (entry->element_list1->data);
                g_list_free (entry->element_list1);
              }
              entry->element_list1 = g_list_prepend (NULL, factory);
              gst_object_ref (factory);
            }
          }
        }
      }

    }
  }

  gst_caps_unref (caps);

  return list;
}

/* GCompareFunc for list_find_custom */
/* compares caps and returns 0 if they intersect */
static gint
compare_caps (gconstpointer a, gconstpointer b)
{
  CodecCap *element = (CodecCap *)a;
  GstCaps *c_caps = (GstCaps *)b;
  GstCaps *intersect = gst_caps_intersect (element->caps, c_caps);
  if (!gst_caps_is_empty (intersect))
  {
    /* found */
    gst_caps_unref (intersect);
    return 0;
  }
  else
  {
    /* not found */
    gst_caps_unref (intersect);
    return 1;
  }
}

/**
 *  fill FarsightCodec fields based on payloader capabilities 
 *  TODO: optimise using quarks
 */
static gboolean
extract_field_data (GQuark field_id,
                    const GValue *value, 
                    gpointer user_data)
{
  /* TODO : This can be called several times from different rtp caps for the
   * same codec, it would be good to make sure any duplicate values are the
   * same, if not then we have several rtp elements that are giving different
   * caps information, therefore they need to be fixed */

  FarsightCodec *codec = (FarsightCodec *) user_data;
  GType type = G_VALUE_TYPE (value);
  const gchar *field_name = g_quark_to_string (field_id);
  const gchar *tmp;

  if (0 == strcmp (field_name, "media"))
  {
    if (type != G_TYPE_STRING)
    {
      return FALSE;
    }
    tmp = g_value_get_string (value);
    if (strcmp (tmp, "audio") == 0)
    {
      codec->media_type = FARSIGHT_MEDIA_TYPE_AUDIO;
    }
    else if (strcmp (tmp, "video") == 0)
    {
      codec->media_type = FARSIGHT_MEDIA_TYPE_VIDEO;
    }

  }
  else if (0 == strcmp (field_name, "payload"))
  {
    if (type == GST_TYPE_INT_RANGE)
    {
      if (gst_value_get_int_range_min (value) < 96 ||
          gst_value_get_int_range_max (value) > 255 || pt_count == 255)
      {
        return FALSE;
      }

      codec->id = pt_count++;
    }
    else if (type == G_TYPE_INT)
    {
      codec->id = g_value_get_int (value);
    }
    else
    {
      return FALSE;
    }
  }
  else if (0 == strcmp (field_name, "clock-rate"))
  {
    if (type != G_TYPE_INT)
    {
      return FALSE;
    }
    codec->clock_rate = g_value_get_int (value);
  }
  else if (0 == strcmp (field_name, "ssrc") ||
      0 == strcmp (field_name, "clock-base") ||
      0 == strcmp (field_name, "seqnum-base"))
  {
    // ignore these fields for now
    ;
  }
  else if (0 == strcmp (field_name, "encoding-name"))
  {
    if (type != G_TYPE_STRING)
    {
      return FALSE;
    }
    if (!codec->encoding_name)
    {
      codec->encoding_name = g_value_dup_string (value);
    }
  }
  else if (0 == strcmp (field_name, "encoding-params"))
  {
    if (type != G_TYPE_STRING)
    {
      return FALSE;
    }
    codec->channels = (guint) g_ascii_strtoull (
                                       g_value_get_string (value), NULL, 10);
  }
  else
  {
    if (type == G_TYPE_STRING)
    {
      FarsightCodecParameter *optional_param =
        g_new (FarsightCodecParameter, 1);

      optional_param->name = g_strdup (field_name);
      optional_param->value = g_strdup (g_value_get_string (value));
      codec->optional_params = g_list_append (codec->optional_params,
          optional_param);
    }
  }

  return TRUE;
}

void
codec_internal_destroy (gpointer data)
{
  CodecInternal *codec_internal = data;
  /* we don't copy the codecs anymore, so no need to free them */
  /*
  if (codec_internal->codec)
  {
    farsight_codec_destroy (codec_internal->codec);
  }
  */

  if (codec_internal->media_caps)
  {
    gst_caps_unref (codec_internal->media_caps);
  }

  GList *walk;
  for (walk = codec_internal->send_pipeline_factory;
      walk; walk = g_list_next (walk))
  {
    if (walk->data)
    {
      gst_object_unref ((GstElementFactory *)walk->data);
    }
  }
  for (walk = codec_internal->receive_pipeline_factory;
      walk; walk = g_list_next (walk))
  {
    if (walk->data)
    {
      gst_object_unref ((GstElementFactory *)walk->data);
    }
  }
  g_list_free (codec_internal->send_pipeline_factory);
  g_list_free (codec_internal->receive_pipeline_factory);


  g_free (codec_internal);
}

/* Creates a codec bin with payloader/encoder or depayloader/decoder based on
 * dir */
GstElement *
create_codec_bin (FarsightMediaType media_type, gint codec_id, gboolean dir)
{
  GstElement *codec_bin = NULL;
  GstElement *current_element = NULL;
  GstElement *previous_element = NULL;
  GList *walk;
  CodecInternal *codec_internal;
  GList *pipeline_factory = NULL;
  gchar *name = NULL;

  codec_internal = g_hash_table_lookup (
          (GHashTable *) list_codecs_internal[media_type],
          (gpointer) ((gint) codec_id));
  if (!codec_internal)
  {
    return NULL;
  }

  if (dir == DIR_SEND)
  {
    pipeline_factory = codec_internal->send_pipeline_factory;
  }
  else
  {
    pipeline_factory = codec_internal->receive_pipeline_factory;
  }

  g_debug ("creating %s codec bin for id %d, pipeline_factory %p",
      (dir==DIR_SEND)?"send":"recv", codec_id, pipeline_factory);
  name = g_strdup_printf ((dir == DIR_SEND)?"send%d":"recv%d",
          codec_id);
  codec_bin = gst_bin_new (name);
  g_free(name);
  for (walk = pipeline_factory; walk; walk = g_list_next (walk))
  {
    current_element =
        gst_element_factory_create ((GstElementFactory *) walk->data, NULL);
    if (!current_element)
    {
      return NULL;
    }

    gst_bin_add (GST_BIN (codec_bin), current_element);

    set_options_on_element (walk->data, current_element);

    /* queue delay to 0 on all depayloaders until I remove that property
     * all-together */
    if (dir == DIR_RECV)
    {
      if (is_depayloader (walk->data))
      {
        g_debug ("setting queue-delay on depayloader");
        g_object_set (G_OBJECT (current_element), "queue-delay", 0, NULL);

        /* let's create our bin sink ghostpad and link it to the depayloader */
        create_ghost_pad (current_element, "sink", codec_bin);
      }
      else if (is_decoder (walk->data))
      {
        /* let's create our bin src ghostpad and link it to the decoder */
        create_ghost_pad (current_element, "src", codec_bin);
      }
    }
    else if (dir == DIR_SEND)
    {
      if (is_payloader (walk->data))
      {
        g_object_set (current_element, "pt", codec_internal->codec->id, NULL);
        /* TODO find a better way to set the MTU, RTCP perhaps? */
        /*g_object_set (current_element, "mtu", MAX_MTU, NULL);*/
        /* FIXME please don't hardcode me */
        g_object_set (current_element, "max_ptime", 20 * GST_MSECOND, NULL);

        /* let's create our bin src ghostpad and link it to the payloader */
        create_ghost_pad (current_element, "src", codec_bin);
      }
      else if (is_encoder (walk->data))
      {
        /* let's create our bin sink ghostpad and link it to the depayloader */
        create_ghost_pad (current_element, "sink", codec_bin);
      }
    }

    /* let's link them together using the specified media_caps if any
     * this will ensure that multi-codec encoders/decoders will select the
     * appropriate codec based on caps negotiation */
    if (previous_element)
    {
      GstElement *src_element;
      GstElement *sink_element;
      if (gst_element_factory_can_sink_caps (walk->data,
            codec_internal->media_caps))
      {
        src_element = previous_element;
        sink_element = current_element;
      }
      else
      {
        src_element = current_element;
        sink_element = previous_element;
      }

      g_debug ("linking %s and %s with caps %s", gst_element_get_name
          (src_element), gst_element_get_name (sink_element),
          gst_caps_to_string (codec_internal->media_caps));
      if (!gst_element_link_filtered (src_element, sink_element,
          codec_internal->media_caps))
      {
        gchar *src_element_name = gst_element_get_name (src_element);
        gchar *sink_element_name = gst_element_get_name (sink_element);
        gchar *caps_as_string = gst_caps_to_string (codec_internal->media_caps);
        g_debug ("Could not link %s and %s with caps %s", src_element_name,
            sink_element_name, caps_as_string);
        g_free (src_element_name);
        g_free (sink_element_name);
        g_free (caps_as_string);
      }
    }
    previous_element = current_element;
  }

  return codec_bin;
}

static void
set_options_on_element (GstElementFactory *elem_factory, GstElement
    *current_element)
{
  /* let's check if this plugin has any user defined parameters to set */
  const gchar *element_name = gst_plugin_feature_get_name 
    (GST_PLUGIN_FEATURE (elem_factory));
  g_debug ("checking if %s is in config list", element_name);
  if (elem_config && g_key_file_has_group (elem_config,
        element_name))
  {
    g_debug ("Found config for %s", element_name);
    gchar **keys;
    gint i;
    keys = g_key_file_get_keys (elem_config, element_name, NULL, NULL);

    for (i = 0; keys[i]; i++)
    {
      GParamSpec *param_spec;
      GValue key_value = { 0 };
      GValue prop_value = { 0 };

      g_debug ("getting %s", keys[i]);
      param_spec = g_object_class_find_property
        (G_OBJECT_GET_CLASS(current_element), keys[i]);
      if (!param_spec)
      {
        continue;
      }

      g_value_init (&prop_value, param_spec->value_type);

      gchar *str_key_value;
      gboolean bool_key_value;
      gint int_key_value;
      gdouble double_key_value;
      switch (param_spec->value_type)
      {
        case G_TYPE_STRING:
          str_key_value = g_key_file_get_value (elem_config, element_name,
              keys[i], NULL);
          g_value_init (&key_value, G_TYPE_STRING);
          g_value_set_string (&key_value, str_key_value);
          g_debug ("%s is a string", keys[i]);
          g_free (str_key_value);
          break;
        case G_TYPE_BOOLEAN:
          bool_key_value = g_key_file_get_boolean (elem_config, element_name,
              keys[i], NULL);
          g_value_init (&key_value, G_TYPE_BOOLEAN);
          g_value_set_boolean (&key_value, bool_key_value);
          g_debug ("%s is a boolean", keys[i]);
          break;
        case G_TYPE_UINT64:
        case G_TYPE_INT64:
        case G_TYPE_DOUBLE:
          /* FIXME it seems get_double is only in 2.12, so for now get a
           * string and convert it to double */
#if 1
          str_key_value = g_key_file_get_value (elem_config, element_name, keys[i],
              NULL);
          double_key_value = g_strtod(str_key_value, NULL);
#else
          double_key_value = g_key_file_get_double (elem_config, element_name,
              keys[i], NULL);
#endif
          g_value_init (&key_value, G_TYPE_DOUBLE);
          g_value_set_double (&key_value, double_key_value);
          g_debug ("%s is a uint64", keys[i]);
          g_debug ("%s is a int64", keys[i]);
          g_debug ("%s is a double", keys[i]);
          break;
        case G_TYPE_ULONG:
          g_debug ("%s is a ulong", keys[i]);
          break;
        case G_TYPE_LONG:
          g_debug ("%s is a long", keys[i]);
          break;
        case G_TYPE_INT:
        case G_TYPE_UINT:
        case G_TYPE_ENUM:
        default:
          int_key_value = g_key_file_get_integer (elem_config, element_name,
              keys[i], NULL);
          g_value_init (&key_value, G_TYPE_INT);
          g_value_set_int (&key_value, int_key_value);
          g_debug ("%s is a int", keys[i]);
          g_debug ("%s is a uint", keys[i]);
          g_debug ("%s is an enum", keys[i]);
          g_debug ("%s is something else, attempting to int conv", keys[i]);
          break;
      }

      if (!g_value_transform (&key_value, &prop_value))
      {
        g_debug ("Could not transform gvalue pair");
        continue;
      }

      g_debug ("Setting %s to on %s", keys[i], element_name);
      g_object_set_property (G_OBJECT(current_element), keys[i], &prop_value);
    }

    g_strfreev(keys);
  }
}

static void
create_ghost_pad (GstElement *current_element, const gchar *dir, GstElement
    *codec_bin)
{
  GstPad *ghostpad;
  GstPad *pad = gst_element_get_pad (current_element, dir);
  if (pad)
  {
    ghostpad = gst_ghost_pad_new (dir, pad);
    gst_element_add_pad (codec_bin, ghostpad);
    gst_object_unref (GST_OBJECT (pad));
  }
}

CodecInternal *
lookup_codec_by_pt (FarsightMediaType media_type, gint pt)
{
  return g_hash_table_lookup (
        (GHashTable *) list_codecs_internal[media_type],
        (gpointer) ((gint) pt));
}

CodecInternal *
match_codec (FarsightMediaType media_type, FarsightCodec *codec)
{
  return g_hash_table_find (
      (GHashTable *) list_codecs_internal[media_type],
      (GHRFunc) hash_find_func, codec);
}

static gboolean
hash_find_func (const gchar *key, CodecInternal *codec_internal,
                const FarsightCodec *codec_to_match)
{
  /* check if its necessary compare more items */
  /* dynamic payload types could be different from remote end */
  if (codec_internal->codec->id < 96 &&
      codec_internal->codec->id != codec_to_match->id)
    return FALSE;

  if (g_ascii_strcasecmp (codec_internal->codec->encoding_name,
        codec_to_match->encoding_name) != 0)
    return FALSE;

  if (codec_internal->codec->id >= 96 &&
      codec_internal->codec->clock_rate != codec_to_match->clock_rate)
    return FALSE;

  return TRUE;
}


Generated by  Doxygen 1.6.0   Back to index