webrtc/nice: Support domain name as connection-address of ICE candidate
[platform/upstream/gstreamer.git] / subprojects / gst-plugins-good / ext / shout2 / gstshout2.c
1 /* GStreamer
2  * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
3  * Copyright (C) <2006> Tim-Philipp Müller <tim centricular net>
4  * Copyright (C) <2012> Ralph Giles <giles@mozilla.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21
22 /**
23  * SECTION:element-shout2send
24  * @title: shout2send
25  *
26  * shout2send pushes a media stream to an Icecast server
27  *
28  * ## Example launch line
29  * |[
30  * gst-launch-1.0 uridecodebin uri=file:///path/to/audiofile ! audioconvert ! vorbisenc ! oggmux ! shout2send mount=/stream.ogg port=8000 username=source password=somepassword ip=server_IP_address_or_hostname
31  * ]| This pipeline demuxes, decodes, re-encodes and re-muxes an audio
32  * media file into oggvorbis and sends the resulting stream to an Icecast
33  * server. Properties mount, port, username and password are all server-config
34  * dependent.
35  *
36  */
37
38 #ifdef HAVE_CONFIG_H
39 #include "config.h"
40 #endif
41
42 #include "gstshout2.h"
43 #include <stdlib.h>
44 #include <string.h>
45
46 #include <glib/gi18n-lib.h>
47
48 #ifndef HAVE_SHOUT_2_4_6_OR_NEWER
49 #define shout_set_metadata_utf8 shout_set_metadata
50 #endif
51
52 GST_DEBUG_CATEGORY_STATIC (shout2_debug);
53 #define GST_CAT_DEFAULT shout2_debug
54
55 enum
56 {
57   SIGNAL_CONNECTION_PROBLEM,    /* FIXME 2.0: remove this */
58   LAST_SIGNAL
59 };
60
61 enum
62 {
63   ARG_0,
64   ARG_IP,                       /* the IP address or hostname of the server */
65   ARG_PORT,                     /* the encoder port number on the server */
66   ARG_PASSWORD,                 /* the encoder password on the server */
67   ARG_USERNAME,                 /* the encoder username on the server */
68   ARG_PUBLIC,                   /* is this stream public? */
69   ARG_STREAMNAME,               /* Name of the stream */
70   ARG_DESCRIPTION,              /* Description of the stream */
71   ARG_GENRE,                    /* Genre of the stream */
72
73   ARG_PROTOCOL,                 /* Protocol to connect with */
74
75   ARG_MOUNT,                    /* mountpoint of stream (icecast only) */
76   ARG_URL,                      /* the stream's homepage URL */
77
78   ARG_TIMEOUT,                  /* The max amount of time to wait for
79                                    network activity */
80   ARG_SEND_TITLE_INFO,          /* If stream song title updates should be made */
81   ARG_USERAGENT                 /* User-Agent setting */
82 };
83
84 #define DEFAULT_IP           "127.0.0.1"
85 #define DEFAULT_PORT         8000
86 #define DEFAULT_PASSWORD     "hackme"
87 #define DEFAULT_USERNAME     "source"
88 #define DEFAULT_PUBLIC       FALSE
89 #define DEFAULT_STREAMNAME   ""
90 #define DEFAULT_DESCRIPTION  ""
91 #define DEFAULT_USERAGENT    "GStreamer " PACKAGE_VERSION
92 #define DEFAULT_GENRE        ""
93 #define DEFAULT_MOUNT        ""
94 #define DEFAULT_URL          ""
95 #define DEFAULT_PROTOCOL     SHOUT2SEND_PROTOCOL_HTTP
96 #define DEFAULT_TIMEOUT      10000
97 #define DEFAULT_SEND_TITLE_INFO TRUE
98
99 #define SHOUT2SEND_CAPS "application/ogg; audio/ogg; video/ogg; "\
100     "audio/mpeg, mpegversion = (int) 1, layer = (int) [ 1, 3 ]; " \
101     "video/webm; audio/webm"
102
103 static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
104     GST_PAD_SINK,
105     GST_PAD_ALWAYS,
106     GST_STATIC_CAPS (SHOUT2SEND_CAPS));
107
108 static void gst_shout2send_finalize (GstShout2send * shout2send);
109
110 static gboolean gst_shout2send_event (GstBaseSink * sink, GstEvent * event);
111 static gboolean gst_shout2send_unlock (GstBaseSink * basesink);
112 static gboolean gst_shout2send_unlock_stop (GstBaseSink * basesink);
113 static GstFlowReturn gst_shout2send_render (GstBaseSink * sink,
114     GstBuffer * buffer);
115 static gboolean gst_shout2send_start (GstBaseSink * basesink);
116 static gboolean gst_shout2send_stop (GstBaseSink * basesink);
117
118 static void gst_shout2send_set_property (GObject * object, guint prop_id,
119     const GValue * value, GParamSpec * pspec);
120 static void gst_shout2send_get_property (GObject * object, guint prop_id,
121     GValue * value, GParamSpec * pspec);
122
123 static gboolean gst_shout2send_setcaps (GstBaseSink * basesink, GstCaps * caps);
124
125 static guint gst_shout2send_signals[LAST_SIGNAL] = { 0 };
126
127 #define GST_TYPE_SHOUT_PROTOCOL (gst_shout2send_protocol_get_type())
128 static GType
129 gst_shout2send_protocol_get_type (void)
130 {
131   static GType shout2send_protocol_type = 0;
132   static const GEnumValue shout2send_protocol[] = {
133     {SHOUT2SEND_PROTOCOL_XAUDIOCAST,
134         "Xaudiocast Protocol (icecast 1.3.x)", "xaudiocast"},
135     {SHOUT2SEND_PROTOCOL_ICY, "Icy Protocol (ShoutCast)", "icy"},
136     {SHOUT2SEND_PROTOCOL_HTTP, "Http Protocol (icecast 2.x)", "http"},
137     {0, NULL, NULL},
138   };
139
140   if (!shout2send_protocol_type) {
141     shout2send_protocol_type =
142         g_enum_register_static ("GstShout2SendProtocol", shout2send_protocol);
143   }
144
145
146   return shout2send_protocol_type;
147 }
148
149 #define gst_shout2send_parent_class parent_class
150 G_DEFINE_TYPE_WITH_CODE (GstShout2send, gst_shout2send, GST_TYPE_BASE_SINK,
151     G_IMPLEMENT_INTERFACE (GST_TYPE_TAG_SETTER, NULL));
152 #ifdef ENABLE_NLS
153 #define _do_init \
154   bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);\
155   bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
156 #else /* ENABLE_NLS */
157 #define _do_init
158 #endif
159 GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (shout2send, "shout2send", GST_RANK_NONE,
160     GST_TYPE_SHOUT2SEND, _do_init);
161
162 static void
163 gst_shout2send_class_init (GstShout2sendClass * klass)
164 {
165   GObjectClass *gobject_class;
166   GstElementClass *gstelement_class;
167   GstBaseSinkClass *gstbasesink_class;
168   GstPadTemplate *tmpl;
169
170   gobject_class = (GObjectClass *) klass;
171   gstelement_class = (GstElementClass *) klass;
172   gstbasesink_class = (GstBaseSinkClass *) klass;
173
174   parent_class = g_type_class_peek_parent (klass);
175
176   gobject_class->set_property = gst_shout2send_set_property;
177   gobject_class->get_property = gst_shout2send_get_property;
178   gobject_class->finalize = (GObjectFinalizeFunc) gst_shout2send_finalize;
179
180   /* FIXME: 2.0 Should probably change this prop name to "server" */
181   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_IP,
182       g_param_spec_string ("ip", "ip", "IP address or hostname", DEFAULT_IP,
183           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
184   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_PORT,
185       g_param_spec_int ("port", "port", "port", 1, G_MAXUSHORT, DEFAULT_PORT,
186           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
187
188   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_PASSWORD,
189       g_param_spec_string ("password", "password", "password", DEFAULT_PASSWORD,
190           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
191
192   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_USERNAME,
193       g_param_spec_string ("username", "username", "username", DEFAULT_USERNAME,
194           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
195
196   /* metadata */
197   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_PUBLIC,
198       g_param_spec_boolean ("public", "public",
199           "If the stream should be listed on the server's stream directory",
200           DEFAULT_PUBLIC, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
201
202   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_STREAMNAME,
203       g_param_spec_string ("streamname", "streamname", "name of the stream",
204           DEFAULT_STREAMNAME, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
205
206   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_DESCRIPTION,
207       g_param_spec_string ("description", "description", "description",
208           DEFAULT_DESCRIPTION, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
209
210   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_GENRE,
211       g_param_spec_string ("genre", "genre", "genre", DEFAULT_GENRE,
212           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
213
214   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_PROTOCOL,
215       g_param_spec_enum ("protocol", "protocol", "Connection Protocol to use",
216           GST_TYPE_SHOUT_PROTOCOL, DEFAULT_PROTOCOL,
217           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
218
219   /**
220    * GstShout2send:send-title-info
221    *
222    * Update stream metadata with song title and artist information
223    *
224    * Since: 1.22
225    **/
226
227   g_object_class_install_property (G_OBJECT_CLASS (klass),
228       ARG_SEND_TITLE_INFO,
229       g_param_spec_boolean ("send-title-info", "send-title-info",
230           "Update stream metadata with song title and artist information",
231           DEFAULT_SEND_TITLE_INFO, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
232
233   /**
234    * GstShout2send:user-agent
235    *
236    * User agent of the source
237    *
238    * Since: 1.22
239    **/
240
241   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_USERAGENT,
242       g_param_spec_string ("user-agent", "user-agent",
243           "User agent of the source", DEFAULT_USERAGENT,
244           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
245
246   /* icecast only */
247   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_MOUNT,
248       g_param_spec_string ("mount", "mount", "mount", DEFAULT_MOUNT,
249           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
250
251   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_URL,
252       g_param_spec_string ("url", "url", "the stream's homepage URL",
253           DEFAULT_URL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
254
255   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_TIMEOUT,
256       g_param_spec_uint ("timeout", "timeout",
257           "Max amount of time to wait for network activity, in milliseconds",
258           1, G_MAXUINT, DEFAULT_TIMEOUT,
259           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
260
261   /* signals */
262   gst_shout2send_signals[SIGNAL_CONNECTION_PROBLEM] =
263       g_signal_new ("connection-problem", G_TYPE_FROM_CLASS (klass),
264       0, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_INT);
265
266   gstbasesink_class->start = GST_DEBUG_FUNCPTR (gst_shout2send_start);
267   gstbasesink_class->stop = GST_DEBUG_FUNCPTR (gst_shout2send_stop);
268   gstbasesink_class->unlock = GST_DEBUG_FUNCPTR (gst_shout2send_unlock);
269   gstbasesink_class->unlock_stop =
270       GST_DEBUG_FUNCPTR (gst_shout2send_unlock_stop);
271   gstbasesink_class->render = GST_DEBUG_FUNCPTR (gst_shout2send_render);
272   gstbasesink_class->event = GST_DEBUG_FUNCPTR (gst_shout2send_event);
273   gstbasesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_shout2send_setcaps);
274
275   tmpl = gst_static_pad_template_get (&sink_template);
276   gst_element_class_add_pad_template (gstelement_class, tmpl);
277
278   gst_element_class_set_static_metadata (gstelement_class,
279       "Icecast network sink",
280       "Sink/Network", "Sends data to an icecast server",
281       "Wim Taymans <wim.taymans@chello.be>, "
282       "Pedro Corte-Real <typo@netcabo.pt>, "
283       "Zaheer Abbas Merali <zaheerabbas at merali dot org>");
284
285   GST_DEBUG_CATEGORY_INIT (shout2_debug, "shout2", 0, "shout2send element");
286
287   gst_type_mark_as_plugin_api (GST_TYPE_SHOUT_PROTOCOL, 0);
288 }
289
290 static void
291 gst_shout2send_init (GstShout2send * shout2send)
292 {
293   gst_base_sink_set_sync (GST_BASE_SINK (shout2send), FALSE);
294
295   shout2send->timer = gst_poll_new (TRUE);
296
297   shout2send->ip = g_strdup (DEFAULT_IP);
298   shout2send->port = DEFAULT_PORT;
299   shout2send->password = g_strdup (DEFAULT_PASSWORD);
300   shout2send->username = g_strdup (DEFAULT_USERNAME);
301   shout2send->streamname = g_strdup (DEFAULT_STREAMNAME);
302   shout2send->description = g_strdup (DEFAULT_DESCRIPTION);
303   shout2send->genre = g_strdup (DEFAULT_GENRE);
304   shout2send->mount = g_strdup (DEFAULT_MOUNT);
305   shout2send->url = g_strdup (DEFAULT_URL);
306   shout2send->protocol = DEFAULT_PROTOCOL;
307   shout2send->ispublic = DEFAULT_PUBLIC;
308   shout2send->timeout = DEFAULT_TIMEOUT;
309
310   shout2send->format = -1;
311   shout2send->usage = SHOUT_USAGE_UNKNOWN;
312   shout2send->tags = gst_tag_list_new_empty ();
313   shout2send->conn = NULL;
314   shout2send->connected = FALSE;
315   shout2send->songmetadata = NULL;
316   shout2send->songartist = NULL;
317   shout2send->songtitle = NULL;
318   shout2send->send_title_info = DEFAULT_SEND_TITLE_INFO;
319   shout2send->user_agent = g_strdup (DEFAULT_USERAGENT);
320 }
321
322 static void
323 gst_shout2send_finalize (GstShout2send * shout2send)
324 {
325   g_free (shout2send->ip);
326   g_free (shout2send->password);
327   g_free (shout2send->username);
328   g_free (shout2send->streamname);
329   g_free (shout2send->description);
330   g_free (shout2send->genre);
331   g_free (shout2send->mount);
332   g_free (shout2send->url);
333   g_free (shout2send->user_agent);
334
335   gst_tag_list_unref (shout2send->tags);
336
337   gst_poll_free (shout2send->timer);
338
339   G_OBJECT_CLASS (parent_class)->finalize ((GObject *) (shout2send));
340 }
341
342 static void
343 set_shout_metadata (const GstTagList * list, const gchar * tag,
344     gpointer user_data)
345 {
346   GstShout2send *shout2send = (GstShout2send *) user_data;
347   char **shout_metadata = &(shout2send->songmetadata);
348   char **song_artist = &(shout2send->songartist);
349   char **song_title = &(shout2send->songtitle);
350
351   gchar *value;
352
353   GST_DEBUG ("tag: %s being added", tag);
354   if (strcmp (tag, GST_TAG_ARTIST) == 0) {
355     if (gst_tag_get_type (tag) == G_TYPE_STRING) {
356       if (!gst_tag_list_get_string (list, tag, &value)) {
357         GST_DEBUG ("Error reading \"%s\" tag value", tag);
358         return;
359       }
360
361       if (*song_artist != NULL)
362         g_free (*song_artist);
363
364       *song_artist = g_strdup (value);
365     }
366   } else if (strcmp (tag, GST_TAG_TITLE) == 0) {
367     if (gst_tag_get_type (tag) == G_TYPE_STRING) {
368       if (!gst_tag_list_get_string (list, tag, &value)) {
369         GST_DEBUG ("Error reading \"%s\" tag value", tag);
370         return;
371       }
372
373       if (*song_title != NULL)
374         g_free (*song_title);
375
376       *song_title = g_strdup (value);
377     }
378   }
379
380   if (*shout_metadata != NULL)
381     g_free (*shout_metadata);
382
383   if (!shout2send->send_title_info) {
384     *shout_metadata = NULL;
385     return;
386   }
387
388   if (*song_title && *song_artist) {
389     *shout_metadata = g_strdup_printf ("%s - %s", *song_artist, *song_title);
390   } else if (*song_title && *song_artist == NULL) {
391     *shout_metadata = g_strdup_printf ("Unknown - %s", *song_title);
392   } else if (*song_title == NULL && *song_artist) {
393     *shout_metadata = g_strdup_printf ("%s - Unknown", *song_artist);
394   } else {
395     *shout_metadata = g_strdup_printf ("Unknown - Unknown");
396   }
397
398   GST_LOG ("shout metadata is now: %s", *shout_metadata);
399 }
400
401 #if 0
402 static void
403 gst_shout2send_set_metadata (GstShout2send * shout2send)
404 {
405   const GstTagList *user_tags;
406   GstTagList *copy;
407   char *tempmetadata;
408   shout_metadata_t *pmetadata;
409
410   g_return_if_fail (shout2send != NULL);
411   user_tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (shout2send));
412   if ((shout2send->tags == NULL) && (user_tags == NULL)) {
413     return;
414   }
415   copy = gst_tag_list_merge (user_tags, shout2send->tags,
416       gst_tag_setter_get_tag_merge_mode (GST_TAG_SETTER (shout2send)));
417   /* lets get the artist and song tags */
418   tempmetadata = NULL;
419   gst_tag_list_foreach ((GstTagList *) copy, set_shout_metadata,
420       (gpointer) & tempmetadata);
421   if (tempmetadata) {
422     pmetadata = shout_metadata_new ();
423     shout_metadata_add (pmetadata, "song", tempmetadata);
424     shout_set_metadata (shout2send->conn, pmetadata);
425     shout_metadata_free (pmetadata);
426   }
427
428   gst_tag_list_unref (copy);
429 }
430 #endif
431
432
433 static gboolean
434 gst_shout2send_event (GstBaseSink * sink, GstEvent * event)
435 {
436   GstShout2send *shout2send;
437   gboolean ret = TRUE;
438
439   shout2send = GST_SHOUT2SEND (sink);
440
441   GST_LOG_OBJECT (shout2send, "got %s event", GST_EVENT_TYPE_NAME (event));
442
443   switch (GST_EVENT_TYPE (event)) {
444     case GST_EVENT_TAG:{
445       /* vorbis audio doesn't need metadata setting on the icecast level, only mp3 */
446       if (shout2send->tags && shout2send->format == SHOUT_FORMAT_MP3) {
447         GstTagList *list;
448
449         gst_event_parse_tag (event, &list);
450         GST_DEBUG_OBJECT (shout2send, "tags=%" GST_PTR_FORMAT, list);
451         gst_tag_list_insert (shout2send->tags,
452             list,
453             gst_tag_setter_get_tag_merge_mode (GST_TAG_SETTER (shout2send)));
454         /* lets get the artist and song tags */
455         gst_tag_list_foreach ((GstTagList *) list,
456             set_shout_metadata, shout2send);
457         if (shout2send->songmetadata && shout2send->connected) {
458           shout_metadata_t *pmetadata;
459           int shout_ret;
460
461           GST_DEBUG_OBJECT (shout2send, "metadata now: %s",
462               shout2send->songmetadata);
463
464           pmetadata = shout_metadata_new ();
465           shout_ret =
466               shout_metadata_add (pmetadata, "song", shout2send->songmetadata);
467           if (shout_ret == SHOUTERR_SUCCESS) {
468             shout_ret = shout_set_metadata_utf8 (shout2send->conn, pmetadata);
469             if (shout_ret != SHOUTERR_SUCCESS) {
470               GST_WARNING_OBJECT (shout2send, "Failed to set metadata: %s",
471                   shout_get_error (shout2send->conn));
472             }
473           }
474           shout_metadata_free (pmetadata);
475         }
476       }
477       break;
478     }
479     default:{
480       GST_LOG_OBJECT (shout2send, "let base class handle event");
481       if (GST_BASE_SINK_CLASS (parent_class)->event) {
482         event = gst_event_ref (event);
483         ret = GST_BASE_SINK_CLASS (parent_class)->event (sink, event);
484       }
485       break;
486     }
487   }
488
489   return ret;
490 }
491
492 static gboolean
493 gst_shout2send_set_meta (GstShout2send * sink, const char *meta,
494     const char *val)
495 {
496   GST_DEBUG_OBJECT (sink, "setting %s: %s", meta, val);
497
498   if (shout_set_meta (sink->conn, meta, val) == SHOUTERR_SUCCESS)
499     return TRUE;
500
501   GST_ELEMENT_ERROR (sink, LIBRARY, SETTINGS, (NULL),
502       ("Error setting %s: %s", meta, shout_get_error (sink->conn)));
503
504   return FALSE;
505 }
506
507 static gboolean
508 gst_shout2send_start (GstBaseSink * basesink)
509 {
510   GstShout2send *sink = GST_SHOUT2SEND (basesink);
511   const gchar *cur_prop;
512   gshort proto = 3;
513
514   GST_DEBUG_OBJECT (sink, "starting");
515
516   sink->conn = shout_new ();
517
518   switch (sink->protocol) {
519     case SHOUT2SEND_PROTOCOL_XAUDIOCAST:
520       proto = SHOUT_PROTOCOL_XAUDIOCAST;
521       break;
522     case SHOUT2SEND_PROTOCOL_ICY:
523       proto = SHOUT_PROTOCOL_ICY;
524       break;
525     case SHOUT2SEND_PROTOCOL_HTTP:
526       proto = SHOUT_PROTOCOL_HTTP;
527       break;
528   }
529
530   cur_prop = "protocol";
531   GST_DEBUG_OBJECT (sink, "setting protocol: %d", sink->protocol);
532   if (shout_set_protocol (sink->conn, proto) != SHOUTERR_SUCCESS)
533     goto set_failed;
534
535   cur_prop = "ip";
536   GST_DEBUG_OBJECT (sink, "setting IP/hostname: %s", sink->ip);
537   if (shout_set_host (sink->conn, sink->ip) != SHOUTERR_SUCCESS)
538     goto set_failed;
539
540   cur_prop = "port";
541   GST_DEBUG_OBJECT (sink, "setting port: %u", sink->port);
542   if (shout_set_port (sink->conn, sink->port) != SHOUTERR_SUCCESS)
543     goto set_failed;
544
545   cur_prop = "password";
546   GST_DEBUG_OBJECT (sink, "setting password: %s", sink->password);
547   if (shout_set_password (sink->conn, sink->password) != SHOUTERR_SUCCESS)
548     goto set_failed;
549
550   cur_prop = "public";
551   GST_DEBUG_OBJECT (sink, "setting %s: %u", cur_prop, sink->ispublic);
552   if (shout_set_public (sink->conn,
553           (sink->ispublic ? 1 : 0)) != SHOUTERR_SUCCESS)
554     goto set_failed;
555
556   if (!gst_shout2send_set_meta (sink, SHOUT_META_NAME, sink->streamname))
557     goto set_meta_failed;
558
559   if (!gst_shout2send_set_meta (sink, SHOUT_META_DESCRIPTION,
560           sink->description))
561     goto set_meta_failed;
562
563   if (!gst_shout2send_set_meta (sink, SHOUT_META_GENRE, sink->genre))
564     goto set_meta_failed;
565
566   cur_prop = "mount";
567   GST_DEBUG_OBJECT (sink, "setting %s: %s", cur_prop, sink->mount);
568   if (shout_set_mount (sink->conn, sink->mount) != SHOUTERR_SUCCESS)
569     goto set_failed;
570
571   cur_prop = "username";
572   GST_DEBUG_OBJECT (sink, "setting %s: %s", cur_prop, sink->username);
573   if (shout_set_user (sink->conn, sink->username) != SHOUTERR_SUCCESS)
574     goto set_failed;
575
576   cur_prop = "agent";
577   GST_DEBUG_OBJECT (sink, "setting %s: %s", cur_prop, sink->user_agent);
578   if (shout_set_agent (sink->conn, sink->user_agent) != SHOUTERR_SUCCESS) {
579     goto set_failed;
580   }
581
582   return TRUE;
583
584 /* ERROR */
585 set_failed:
586   {
587     GST_ELEMENT_ERROR (sink, LIBRARY, SETTINGS, (NULL),
588         ("Error setting %s: %s", cur_prop, shout_get_error (sink->conn)));
589     /* fallthrough */
590   }
591 set_meta_failed:
592   {
593     shout_free (sink->conn);
594     sink->conn = NULL;
595     return FALSE;
596   }
597 }
598
599 static GstFlowReturn
600 gst_shout2send_connect (GstShout2send * sink)
601 {
602   GstFlowReturn fret = GST_FLOW_OK;
603   gint ret;
604   GstClockTime start_ts;
605
606   GST_DEBUG_OBJECT (sink, "Connection format is: %d", sink->format);
607
608   if (sink->format == -1)
609     goto no_caps;
610
611   if (shout_set_nonblocking (sink->conn, 1) != SHOUTERR_SUCCESS)
612     goto could_not_set_nonblocking;
613
614   {
615 #ifdef HAVE_SHOUT_2_4_6_OR_NEWER
616     ret =
617         shout_set_content_format (sink->conn, sink->format, sink->usage, NULL);
618 #else
619     ret = shout_set_format (sink->conn, sink->format);
620 #endif
621
622     if (ret != SHOUTERR_SUCCESS)
623       goto could_not_set_format;
624   }
625
626   GST_DEBUG_OBJECT (sink, "connecting");
627
628   start_ts = gst_util_get_timestamp ();
629   ret = shout_open (sink->conn);
630
631   /* wait for connection or timeout */
632   /* starting with libshout 2.4.2, shout_open() has broken API + ABI and
633    * can also return SHOUTERR_RETRY (a new define) to mean "try again" */
634   while (ret == SHOUTERR_BUSY || ret == SHOUTERR_RETRY) {
635     if (gst_util_get_timestamp () - start_ts > sink->timeout * GST_MSECOND) {
636       goto connection_timeout;
637     }
638     if (gst_poll_wait (sink->timer, 10 * GST_MSECOND) == -1) {
639       GST_LOG_OBJECT (sink, "unlocked");
640
641       fret = gst_base_sink_wait_preroll (GST_BASE_SINK (sink));
642       if (fret != GST_FLOW_OK)
643         goto done;
644     }
645     ret = shout_get_connected (sink->conn);
646   }
647
648   if (ret != SHOUTERR_CONNECTED && ret != SHOUTERR_SUCCESS)
649     goto could_not_connect;
650
651   GST_DEBUG_OBJECT (sink, "connected to server");
652   sink->connected = TRUE;
653
654   /* initialize sending rate monitoring */
655   sink->prev_queuelen = 0;
656   sink->data_sent = 0;
657   sink->stalled = TRUE;
658   sink->datasent_reset_ts = sink->stalled_ts = gst_util_get_timestamp ();
659
660   /* let's set metadata */
661   if (sink->songmetadata) {
662     shout_metadata_t *pmetadata;
663
664     GST_DEBUG_OBJECT (sink, "shout metadata now: %s", sink->songmetadata);
665     pmetadata = shout_metadata_new ();
666     ret = shout_metadata_add (pmetadata, "song", sink->songmetadata);
667     if (ret == SHOUTERR_SUCCESS) {
668       ret = shout_set_metadata_utf8 (sink->conn, pmetadata);
669       if (ret != SHOUTERR_SUCCESS) {
670         GST_WARNING_OBJECT (sink, "Failed to set metadata: %s",
671             shout_get_error (sink->conn));
672       }
673     }
674     shout_metadata_free (pmetadata);
675   }
676
677 done:
678   return fret;
679
680 /* ERRORS */
681 no_caps:
682   {
683     GST_ELEMENT_ERROR (sink, CORE, NEGOTIATION, (NULL),
684         ("No input caps received."));
685     return GST_FLOW_NOT_NEGOTIATED;
686   }
687
688 could_not_set_nonblocking:
689   {
690     GST_ELEMENT_ERROR (sink, LIBRARY, SETTINGS, (NULL),
691         ("Error configuring libshout to use non-blocking i/o: %s",
692             shout_get_error (sink->conn)));
693     return GST_FLOW_ERROR;
694   }
695
696 could_not_set_format:
697   {
698     GST_ELEMENT_ERROR (sink, LIBRARY, SETTINGS, (NULL),
699         ("Error setting connection format: %s", shout_get_error (sink->conn)));
700     return GST_FLOW_ERROR;
701   }
702
703 could_not_connect:
704   {
705     GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
706         (_("Could not connect to server")),
707         ("shout_open() failed: err=%s", shout_get_error (sink->conn)));
708     g_signal_emit (sink, gst_shout2send_signals[SIGNAL_CONNECTION_PROBLEM], 0,
709         shout_get_errno (sink->conn));
710     return GST_FLOW_ERROR;
711   }
712
713 connection_timeout:
714   {
715     GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
716         (_("Could not connect to server")), ("connection timed out"));
717     g_signal_emit (sink, gst_shout2send_signals[SIGNAL_CONNECTION_PROBLEM], 0,
718         shout_get_errno (sink->conn));
719     return GST_FLOW_ERROR;
720   }
721 }
722
723 static gboolean
724 gst_shout2send_stop (GstBaseSink * basesink)
725 {
726   GstShout2send *sink = GST_SHOUT2SEND (basesink);
727
728   if (sink->conn) {
729     if (sink->connected)
730       shout_close (sink->conn);
731     shout_free (sink->conn);
732     sink->conn = NULL;
733   }
734
735   if (sink->songmetadata) {
736     g_free (sink->songmetadata);
737     sink->songmetadata = NULL;
738   }
739
740   sink->connected = FALSE;
741   sink->format = -1;
742   sink->usage = SHOUT_USAGE_UNKNOWN;
743
744   return TRUE;
745 }
746
747 static gboolean
748 gst_shout2send_unlock (GstBaseSink * basesink)
749 {
750   GstShout2send *sink;
751
752   sink = GST_SHOUT2SEND (basesink);
753
754   GST_DEBUG_OBJECT (basesink, "unlock");
755   gst_poll_set_flushing (sink->timer, TRUE);
756
757   return TRUE;
758 }
759
760 static gboolean
761 gst_shout2send_unlock_stop (GstBaseSink * basesink)
762 {
763   GstShout2send *sink;
764
765   sink = GST_SHOUT2SEND (basesink);
766
767   GST_DEBUG_OBJECT (basesink, "unlock_stop");
768   gst_poll_set_flushing (sink->timer, FALSE);
769
770   return TRUE;
771 }
772
773 static GstFlowReturn
774 gst_shout2send_render (GstBaseSink * basesink, GstBuffer * buf)
775 {
776   GstShout2send *sink;
777   glong ret;
778   gint delay;
779   GstFlowReturn fret = GST_FLOW_OK;
780   GstMapInfo map;
781   GstClockTime now;
782   ssize_t queuelen;
783
784   sink = GST_SHOUT2SEND (basesink);
785
786   /* we connect here because we need to know the format before we can set up
787    * the connection, which we don't know yet in _start(), and also because we
788    * don't want to block the application thread */
789   if (!sink->connected) {
790     fret = gst_shout2send_connect (sink);
791     if (fret != GST_FLOW_OK)
792       goto done;
793   }
794
795   delay = shout_delay (sink->conn);
796
797   if (delay > 0) {
798     GST_LOG_OBJECT (sink, "waiting %d msec", delay);
799     if (gst_poll_wait (sink->timer, GST_MSECOND * delay) == -1) {
800       GST_LOG_OBJECT (sink, "unlocked");
801
802       fret = gst_base_sink_wait_preroll (basesink);
803       if (fret != GST_FLOW_OK)
804         goto done;
805     }
806   } else {
807     GST_LOG_OBJECT (sink, "we're %d msec late", -delay);
808   }
809
810   /* accumulate how much data have actually been sent
811    * to the network since the last call to shout_send() */
812   queuelen = shout_queuelen (sink->conn);
813   if (sink->prev_queuelen > 0)
814     sink->data_sent += sink->prev_queuelen - queuelen;
815
816   gst_buffer_map (buf, &map, GST_MAP_READ);
817
818   /* add map.size instead of re-reading the queue length because
819    * the data may actually be sent immediately */
820   sink->prev_queuelen = queuelen + map.size;
821
822   GST_LOG_OBJECT (sink, "sending %u bytes of data, queue length now is %"
823       G_GUINT64_FORMAT, (guint) map.size, sink->prev_queuelen);
824
825   ret = shout_send (sink->conn, map.data, map.size);
826
827   gst_buffer_unmap (buf, &map);
828   if (ret != SHOUTERR_SUCCESS)
829     goto send_error;
830
831   now = gst_util_get_timestamp ();
832   if (now - sink->datasent_reset_ts >= 500 * GST_MSECOND) {
833     guint64 send_rate;
834
835     send_rate = gst_util_uint64_scale (sink->data_sent, GST_SECOND,
836         now - sink->datasent_reset_ts);
837
838     if (send_rate == 0 && !sink->stalled) {
839       sink->stalled = TRUE;
840       sink->stalled_ts = now;
841     } else if (send_rate > 0 && sink->stalled) {
842       sink->stalled = FALSE;
843     }
844
845     sink->data_sent = 0;
846     sink->datasent_reset_ts = now;
847
848     GST_DEBUG_OBJECT (sink, "sending rate is %" G_GUINT64_FORMAT " bps, "
849         "stalled %d, stalled_ts %" GST_TIME_FORMAT, send_rate, sink->stalled,
850         GST_TIME_ARGS (sink->stalled_ts));
851
852     if (sink->stalled && now - sink->stalled_ts >= sink->timeout * GST_MSECOND) {
853       GST_WARNING_OBJECT (sink, "network send queue is stalled for too long");
854       goto network_error;
855     }
856   }
857
858 done:
859
860   return fret;
861
862 /* ERRORS */
863 send_error:
864   {
865     GST_ELEMENT_ERROR (sink, RESOURCE, WRITE, (NULL),
866         ("shout_send() failed: %s", shout_get_error (sink->conn)));
867     g_signal_emit (sink, gst_shout2send_signals[SIGNAL_CONNECTION_PROBLEM], 0,
868         shout_get_errno (sink->conn));
869     return GST_FLOW_ERROR;
870   }
871
872 network_error:
873   {
874     GST_ELEMENT_ERROR (sink, RESOURCE, WRITE, (NULL),
875         ("network timeout reached"));
876     g_signal_emit (sink, gst_shout2send_signals[SIGNAL_CONNECTION_PROBLEM], 0,
877         SHOUTERR_BUSY);
878     return GST_FLOW_ERROR;
879   }
880 }
881
882 static void
883 gst_shout2send_set_property (GObject * object, guint prop_id,
884     const GValue * value, GParamSpec * pspec)
885 {
886   GstShout2send *shout2send;
887
888   shout2send = GST_SHOUT2SEND (object);
889   switch (prop_id) {
890
891     case ARG_IP:
892       g_free (shout2send->ip);
893       shout2send->ip = g_value_dup_string (value);
894       break;
895     case ARG_PORT:
896       shout2send->port = g_value_get_int (value);
897       break;
898     case ARG_PASSWORD:
899       g_free (shout2send->password);
900       shout2send->password = g_value_dup_string (value);
901       break;
902     case ARG_USERNAME:
903       g_free (shout2send->username);
904       shout2send->username = g_value_dup_string (value);
905       break;
906     case ARG_PUBLIC:
907       shout2send->ispublic = g_value_get_boolean (value);
908       break;
909     case ARG_STREAMNAME:       /* Name of the stream */
910       g_free (shout2send->streamname);
911       shout2send->streamname = g_value_dup_string (value);
912       break;
913     case ARG_DESCRIPTION:      /* Description of the stream */
914       g_free (shout2send->description);
915       shout2send->description = g_value_dup_string (value);
916       break;
917     case ARG_GENRE:            /* Genre of the stream */
918       g_free (shout2send->genre);
919       shout2send->genre = g_value_dup_string (value);
920       break;
921     case ARG_PROTOCOL:         /* protocol to connect with */
922       shout2send->protocol = g_value_get_enum (value);
923       break;
924     case ARG_MOUNT:            /* mountpoint of stream (icecast only) */
925       g_free (shout2send->mount);
926       shout2send->mount = g_value_dup_string (value);
927       break;
928     case ARG_URL:              /* the stream's homepage URL */
929       g_free (shout2send->url);
930       shout2send->url = g_value_dup_string (value);
931       break;
932     case ARG_TIMEOUT:
933       shout2send->timeout = g_value_get_uint (value);
934       break;
935     case ARG_SEND_TITLE_INFO:
936       shout2send->send_title_info = g_value_get_boolean (value);
937       break;
938     case ARG_USERAGENT:
939       g_free (shout2send->user_agent);
940       shout2send->user_agent = g_value_dup_string (value);
941       break;
942     default:
943       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
944       break;
945   }
946 }
947
948 static void
949 gst_shout2send_get_property (GObject * object, guint prop_id,
950     GValue * value, GParamSpec * pspec)
951 {
952   GstShout2send *shout2send;
953
954   shout2send = GST_SHOUT2SEND (object);
955   switch (prop_id) {
956
957     case ARG_IP:
958       g_value_set_string (value, shout2send->ip);
959       break;
960     case ARG_PORT:
961       g_value_set_int (value, shout2send->port);
962       break;
963     case ARG_PASSWORD:
964       g_value_set_string (value, shout2send->password);
965       break;
966     case ARG_USERNAME:
967       g_value_set_string (value, shout2send->username);
968       break;
969     case ARG_PUBLIC:
970       g_value_set_boolean (value, shout2send->ispublic);
971       break;
972     case ARG_STREAMNAME:       /* Name of the stream */
973       g_value_set_string (value, shout2send->streamname);
974       break;
975     case ARG_DESCRIPTION:      /* Description of the stream */
976       g_value_set_string (value, shout2send->description);
977       break;
978     case ARG_GENRE:            /* Genre of the stream */
979       g_value_set_string (value, shout2send->genre);
980       break;
981     case ARG_PROTOCOL:         /* protocol to connect with */
982       g_value_set_enum (value, shout2send->protocol);
983       break;
984     case ARG_MOUNT:            /* mountpoint of stream (icecast only) */
985       g_value_set_string (value, shout2send->mount);
986       break;
987     case ARG_URL:              /* the stream's homepage URL */
988       g_value_set_string (value, shout2send->url);
989       break;
990     case ARG_TIMEOUT:
991       g_value_set_uint (value, shout2send->timeout);
992       break;
993     case ARG_SEND_TITLE_INFO:
994       g_value_set_boolean (value, shout2send->send_title_info);
995       break;
996     case ARG_USERAGENT:
997       g_value_set_string (value, shout2send->user_agent);
998       break;
999     default:
1000       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1001       break;
1002   }
1003 }
1004
1005 static gboolean
1006 gst_shout2send_setcaps (GstBaseSink * basesink, GstCaps * caps)
1007 {
1008   const gchar *mimetype;
1009   GstShout2send *shout2send;
1010   gboolean ret = TRUE;
1011
1012   shout2send = GST_SHOUT2SEND (basesink);
1013
1014   mimetype = gst_structure_get_name (gst_caps_get_structure (caps, 0));
1015
1016   GST_DEBUG_OBJECT (shout2send, "mimetype of caps given is: %s", mimetype);
1017
1018   if (!strcmp (mimetype, "audio/mpeg")) {
1019     shout2send->format = SHOUT_FORMAT_MP3;
1020     shout2send->usage = SHOUT_USAGE_AUDIO;
1021   } else if (g_str_has_suffix (mimetype, "/ogg")) {
1022     shout2send->format = SHOUT_FORMAT_OGG;
1023     if (g_str_has_prefix (mimetype, "audio/"))
1024       shout2send->usage = SHOUT_USAGE_AUDIO;
1025     else if (g_str_has_prefix (mimetype, "video/"))
1026       shout2send->usage = SHOUT_USAGE_VISUAL | SHOUT_USAGE_AUDIO;
1027     else
1028       shout2send->usage = SHOUT_USAGE_UNKNOWN;
1029   } else if (g_str_has_suffix (mimetype, "/webm")) {
1030     shout2send->format = SHOUT_FORMAT_WEBM;
1031     if (g_str_has_prefix (mimetype, "audio/"))
1032       shout2send->usage = SHOUT_USAGE_AUDIO;
1033     else if (g_str_has_prefix (mimetype, "video/"))
1034       shout2send->usage = SHOUT_USAGE_VISUAL | SHOUT_USAGE_AUDIO;
1035     else
1036       shout2send->usage = SHOUT_USAGE_UNKNOWN;
1037   } else {
1038     ret = FALSE;
1039   }
1040
1041   return ret;
1042 }
1043
1044 static gboolean
1045 plugin_init (GstPlugin * plugin)
1046 {
1047   return GST_ELEMENT_REGISTER (shout2send, plugin);
1048 }
1049
1050 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
1051     GST_VERSION_MINOR,
1052     shout2,
1053     "Sends data to an icecast server using libshout2",
1054     plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)