/* GStreamer
 *
 * codec-select.c: sample application to dynamically select a codec
 *
 * Copyright (C) <2008> Wim Taymans <wim dot taymans at gmail dot com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

/*
 * This example sets up a pipeline to 'encode' an audiotestsrc into 3 different
 * formats. The format can be selected dynamically at runtime.
 *
 * Each of the encoders require the audio in a specific different format.
 *
 * This example uses identity as the encoder and enforces the caps on identity
 * with a capsfilter.
 *
 * This is a good example of input and output selector and how these elements
 * preserve segment and timing information while switching between streams.
 */

#include <string.h>
#include <gst/gst.h>

/* Create an encoder element.
 * We make a bin containing:
 *
 * audioresample ! <enccaps> ! identity
 *
 * The sinkpad of audioresample and source pad of identity are ghosted on the
 * bin.
 */
static GstElement *
make_encoder (const GstCaps * caps)
{
  GstElement *result;
  GstElement *audioresample;
  GstElement *capsfilter;
  GstElement *identity;
  GstPad *pad;

  /* create result bin */
  result = gst_bin_new (NULL);
  g_assert (result);

  /* create elements */
  audioresample = gst_element_factory_make ("audioresample", NULL);
  g_assert (audioresample);

  capsfilter = gst_element_factory_make ("capsfilter", NULL);
  g_assert (capsfilter);
  g_object_set (capsfilter, "caps", caps, NULL);

  identity = gst_element_factory_make ("identity", NULL);
  g_assert (identity);
  g_object_set (identity, "silent", TRUE, NULL);

  /* add elements to result bin */
  gst_bin_add (GST_BIN (result), audioresample);
  gst_bin_add (GST_BIN (result), capsfilter);
  gst_bin_add (GST_BIN (result), identity);

  /* link elements */
  gst_element_link_pads (audioresample, "src", capsfilter, "sink");
  gst_element_link_pads (capsfilter, "src", identity, "sink");

  /* ghost src and sink pads */
  pad = gst_element_get_static_pad (audioresample, "sink");
  gst_element_add_pad (result, gst_ghost_pad_new ("sink", pad));
  gst_object_unref (pad);

  pad = gst_element_get_static_pad (identity, "src");
  gst_element_add_pad (result, gst_ghost_pad_new ("src", pad));
  gst_object_unref (pad);

  return result;
}

/*
 * We generate:
 *
 * audiotestsrc ! <audiocaps> ! output-selector ! [enc1 .. enc3] ! input-selector
 * select-all = true ! fakesink
 *
 * <audiocaps> makes sure we only produce one format from the audiotestsrc.
 *
 * Each encX element consists of:
 *
 *  audioresample ! <enccaps> ! identity !
 *
 * This way we can simply switch encoders without having to renegotiate.
 */
static GstElement *
make_pipeline (void)
{
  GstElement *result;
  GstElement *audiotestsrc;
  GstElement *audiocaps;
  GstElement *outputselect;
  GstElement *inputselect;
  GstElement *sink;
  GstCaps *caps;
  GstCaps *capslist[3];
  gint i;

  /* create result pipeline */
  result = gst_pipeline_new (NULL);
  g_assert (result);

  /* create various elements */
  audiotestsrc = gst_element_factory_make ("audiotestsrc", NULL);
  g_object_set (audiotestsrc, "num-buffers", 1000, NULL);
  g_assert (audiotestsrc);

  audiocaps = gst_element_factory_make ("capsfilter", NULL);
  g_assert (audiocaps);

  caps =
      gst_caps_from_string ("audio/x-raw,format=S16LE,rate=48000,channels=1");
  g_object_set (audiocaps, "caps", caps, NULL);
  gst_caps_unref (caps);

  outputselect = gst_element_factory_make ("output-selector", "select");
  g_assert (outputselect);

  inputselect = gst_element_factory_make ("input-selector", NULL);
  g_assert (inputselect);
  g_object_set (inputselect, "select-all", TRUE, NULL);

  sink = gst_element_factory_make ("fakesink", NULL);
  g_object_set (sink, "sync", TRUE, NULL);
  g_object_set (sink, "silent", TRUE, NULL);
  g_assert (sink);

  /* add elements */
  gst_bin_add (GST_BIN (result), audiotestsrc);
  gst_bin_add (GST_BIN (result), audiocaps);
  gst_bin_add (GST_BIN (result), outputselect);
  gst_bin_add (GST_BIN (result), inputselect);
  gst_bin_add (GST_BIN (result), sink);

  /* link elements */
  gst_element_link_pads (audiotestsrc, "src", audiocaps, "sink");
  gst_element_link_pads (audiocaps, "src", outputselect, "sink");
  gst_element_link_pads (inputselect, "src", sink, "sink");

  /* make caps */
  capslist[0] =
      gst_caps_from_string ("audio/x-raw,format=S16LE,rate=48000,channels=1");
  capslist[1] =
      gst_caps_from_string ("audio/x-raw,format=S16LE,rate=16000,channels=1");
  capslist[2] =
      gst_caps_from_string ("audio/x-raw,format=S16LE,rate=8000,channels=1");

  /* create encoder elements */
  for (i = 0; i < 3; i++) {
    GstElement *encoder;
    GstPad *srcpad, *sinkpad;

    encoder = make_encoder (capslist[i]);
    g_assert (encoder);

    gst_bin_add (GST_BIN (result), encoder);

    srcpad = gst_element_get_request_pad (outputselect, "src_%u");
    sinkpad = gst_element_get_static_pad (encoder, "sink");
    gst_pad_link (srcpad, sinkpad);
    gst_object_unref (srcpad);
    gst_object_unref (sinkpad);

    srcpad = gst_element_get_static_pad (encoder, "src");
    sinkpad = gst_element_get_request_pad (inputselect, "sink_%u");
    gst_pad_link (srcpad, sinkpad);
    gst_object_unref (srcpad);
    gst_object_unref (sinkpad);
  }

  return result;
}

static gboolean
do_switch (GstElement * pipeline)
{
  gint rand;
  GstElement *select;
  gchar *name;
  GstPad *pad;

  rand = g_random_int_range (0, 3);

  g_print ("switching to %d\n", rand);

  /* find the selector */
  select = gst_bin_get_by_name (GST_BIN (pipeline), "select");

  /* get the named pad */
  name = g_strdup_printf ("src_%u", rand);
  pad = gst_element_get_static_pad (select, name);
  g_free (name);

  /* set the active pad */
  g_object_set (select, "active-pad", pad, NULL);

  return TRUE;
}

static gboolean
my_bus_callback (GstBus * bus, GstMessage * message, gpointer data)
{
  GstElement *sender = (GstElement *) GST_MESSAGE_SRC (message);
  const gchar *name = gst_element_get_name (sender);
  GMainLoop *loop = (GMainLoop *) data;

  g_print ("Got %s message from %s\n", GST_MESSAGE_TYPE_NAME (message), name);

  switch (GST_MESSAGE_TYPE (message)) {

    case GST_MESSAGE_ERROR:{
      GError *err;
      gchar *debug;

      gst_message_parse_error (message, &err, &debug);
      g_print ("Error: %s (%s)\n", err->message, debug);
      g_error_free (err);
      g_free (debug);

      g_main_loop_quit (loop);
      break;
    }
    case GST_MESSAGE_EOS:
      /* end-of-stream */
      g_main_loop_quit (loop);
      break;
    default:
      /* unhandled message */
      break;
  }

  return TRUE;
}

gint
main (gint argc, gchar * argv[])
{
  GstElement *pipeline;
  GstBus *bus;
  GMainLoop *loop;

  /* init GStreamer */
  gst_init (&argc, &argv);
  loop = g_main_loop_new (NULL, FALSE);

  /* set up */
  pipeline = make_pipeline ();

  g_signal_connect (pipeline, "deep_notify",
      G_CALLBACK (gst_object_default_deep_notify), NULL);

  bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
  gst_bus_add_watch (bus, my_bus_callback, loop);
  gst_object_unref (bus);

  g_print ("Starting pipeline\n");

  gst_element_set_state (pipeline, GST_STATE_PLAYING);

  /* add a timeout to cycle between the formats */
  g_timeout_add_seconds (1, (GSourceFunc) do_switch, pipeline);

  /* now run */
  g_main_loop_run (loop);

  g_print ("Nulling pipeline\n");

  /* also clean up */
  gst_element_set_state (pipeline, GST_STATE_NULL);
  gst_object_unref (pipeline);

  return 0;
}
