gstreamer/ext/gnomevfs/gstgnomevfssrc.c
Wim Taymans 1dae961cbf Plugin port to 0.9, ogg/theora playback should work in the seek example now.
Original commit message from CVS:
Plugin port to 0.9, ogg/theora playback should work in the seek
example now.
Removed old examples.
Removed old oggvorbisenc, renamed rawvorbisenc to vorbisenc as
explained in 0.9 TODO doc.
2005-03-31 09:43:49 +00:00

1437 lines
40 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* GStreamer
* Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
* 2000 Wim Taymans <wtay@chello.be>
* 2001 Bastien Nocera <hadess@hadess.net>
* 2002 Kristian Rietveld <kris@gtk.org>
* 2002,2003 Colin Walters <walters@gnu.org>
*
* gnomevfssrc.c:
*
* 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., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#define BROKEN_SIG 1
/*#undef BROKEN_SIG */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gst/gst-i18n-plugin.h"
#include "gstgnomevfs.h"
#include "gstgnomevfsuri.h"
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <errno.h>
#include <string.h>
#include <gst/gst.h>
#include <libgnomevfs/gnome-vfs.h>
/* gnome-vfs.h doesn't include the following header, which we need: */
#include <libgnomevfs/gnome-vfs-standard-callbacks.h>
#define GST_TYPE_GNOMEVFSSRC \
(gst_gnomevfssrc_get_type())
#define GST_GNOMEVFSSRC(obj) \
(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GNOMEVFSSRC,GstGnomeVFSSrc))
#define GST_GNOMEVFSSRC_CLASS(klass) \
(G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_GNOMEVFSSRC,GstGnomeVFSSrcClass))
#define GST_IS_GNOMEVFSSRC(obj) \
(G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GNOMEVFSSRC))
#define GST_IS_GNOMEVFSSRC_CLASS(obj) \
(G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_GNOMEVFSSRC))
static GStaticMutex count_lock = G_STATIC_MUTEX_INIT;
static gint ref_count = 0;
static gboolean vfs_owner = FALSE;
typedef enum
{
GST_GNOMEVFSSRC_OPEN = GST_ELEMENT_FLAG_LAST,
GST_GNOMEVFSSRC_FLAG_LAST = GST_ELEMENT_FLAG_LAST + 2
}
GstGnomeVFSSrcFlags;
typedef struct _GstGnomeVFSSrc
{
GstElement element;
/* pads */
GstPad *srcpad;
/* uri, file, ... */
GnomeVFSURI *uri;
gchar *uri_name;
GnomeVFSHandle *handle;
gboolean own_handle;
GnomeVFSFileSize size; /* -1 = unknown */
GnomeVFSFileOffset curoffset; /* current offset in file */
gboolean seekable;
gulong bytes_per_read; /* bytes per read */
/* Seek stuff */
gboolean need_flush, need_discont;
GnomeVFSFileOffset reqoffset; /* wanted offset for next buf */
/* icecast/audiocast metadata extraction handling */
gboolean iradio_mode;
gboolean http_callbacks_pushed;
gint icy_metaint;
GnomeVFSFileSize icy_count;
gchar *iradio_name;
gchar *iradio_genre;
gchar *iradio_url;
gchar *iradio_title;
GThread *audiocast_thread;
GList *audiocast_notify_queue;
GMutex *audiocast_queue_mutex;
GMutex *audiocast_udpdata_mutex;
gint audiocast_thread_die_infd;
gint audiocast_thread_die_outfd;
gint audiocast_port;
gint audiocast_fd;
} GstGnomeVFSSrc;
typedef struct _GstGnomeVFSSrcClass
{
GstElementClass parent_class;
} GstGnomeVFSSrcClass;
static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS_ANY);
static const GstFormat *
gst_gnomevfssrc_get_formats (GstPad * pad)
{
static const GstFormat formats[] = {
GST_FORMAT_BYTES,
0,
};
return formats;
}
static const GstQueryType *
gst_gnomevfssrc_get_query_types (GstPad * pad)
{
static const GstQueryType types[] = {
GST_QUERY_TOTAL,
GST_QUERY_POSITION,
0,
};
return types;
}
static const GstEventMask *
gst_gnomevfssrc_get_event_mask (GstPad * pad)
{
static const GstEventMask masks[] = {
{GST_EVENT_SEEK, GST_SEEK_METHOD_CUR |
GST_SEEK_METHOD_SET | GST_SEEK_METHOD_END | GST_SEEK_FLAG_FLUSH},
{GST_EVENT_FLUSH, 0},
{GST_EVENT_SIZE, 0},
{0, 0},
};
return masks;
}
enum
{
ARG_0,
ARG_HANDLE,
ARG_LOCATION,
ARG_BYTESPERREAD,
ARG_IRADIO_MODE,
ARG_IRADIO_NAME,
ARG_IRADIO_GENRE,
ARG_IRADIO_URL,
ARG_IRADIO_TITLE
};
static void gst_gnomevfssrc_base_init (gpointer g_class);
static void gst_gnomevfssrc_class_init (GstGnomeVFSSrcClass * klass);
static void gst_gnomevfssrc_init (GstGnomeVFSSrc * gnomevfssrc);
static void gst_gnomevfssrc_finalize (GObject * object);
static void gst_gnomevfssrc_uri_handler_init (gpointer g_iface,
gpointer iface_data);
static void gst_gnomevfssrc_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec);
static void gst_gnomevfssrc_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec);
static GstFlowReturn gst_gnomevfssrc_getrange (GstPad * pad,
guint64 offset, guint size, GstBuffer ** buffer);
static gboolean gst_gnomevfssrc_activate (GstPad * pad, GstActivateMode mode);
static GstElementStateReturn
gst_gnomevfssrc_change_state (GstElement * element);
static void gst_gnomevfssrc_close_file (GstGnomeVFSSrc * src);
static gboolean gst_gnomevfssrc_open_file (GstGnomeVFSSrc * src);
static gboolean gst_gnomevfssrc_srcpad_event (GstPad * pad, GstEvent * event);
static gboolean gst_gnomevfssrc_srcpad_query (GstPad * pad, GstQueryType type,
GstFormat * format, gint64 * value);
static int audiocast_init (GstGnomeVFSSrc * src);
static int audiocast_register_listener (gint * port, gint * fd);
static void audiocast_do_notifications (GstGnomeVFSSrc * src);
static gpointer audiocast_thread_run (GstGnomeVFSSrc * src);
static void audiocast_thread_kill (GstGnomeVFSSrc * src);
static GstElementClass *parent_class = NULL;
GType
gst_gnomevfssrc_get_type (void)
{
static GType gnomevfssrc_type = 0;
if (!gnomevfssrc_type) {
static const GTypeInfo gnomevfssrc_info = {
sizeof (GstGnomeVFSSrcClass),
gst_gnomevfssrc_base_init,
NULL,
(GClassInitFunc) gst_gnomevfssrc_class_init,
NULL,
NULL,
sizeof (GstGnomeVFSSrc),
0,
(GInstanceInitFunc) gst_gnomevfssrc_init,
};
static const GInterfaceInfo urihandler_info = {
gst_gnomevfssrc_uri_handler_init,
NULL,
NULL
};
gnomevfssrc_type =
g_type_register_static (GST_TYPE_ELEMENT,
"GstGnomeVFSSrc", &gnomevfssrc_info, 0);
g_type_add_interface_static (gnomevfssrc_type, GST_TYPE_URI_HANDLER,
&urihandler_info);
}
return gnomevfssrc_type;
}
static void
gst_gnomevfssrc_base_init (gpointer g_class)
{
GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
static GstElementDetails gst_gnomevfssrc_details =
GST_ELEMENT_DETAILS ("GnomeVFS Source",
"Source/File",
"Read from any GnomeVFS-supported file",
"Bastien Nocera <hadess@hadess.net>\n"
"Ronald S. Bultje <rbultje@ronald.bitfreak.net>");
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&srctemplate));
gst_element_class_set_details (element_class, &gst_gnomevfssrc_details);
}
static void
gst_gnomevfssrc_class_init (GstGnomeVFSSrcClass * klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
parent_class = g_type_class_ref (GST_TYPE_ELEMENT);
gobject_class->finalize = gst_gnomevfssrc_finalize;
gobject_class->set_property = gst_gnomevfssrc_set_property;
gobject_class->get_property = gst_gnomevfssrc_get_property;
gstelement_class->change_state = gst_gnomevfssrc_change_state;
/* properties */
gst_element_class_install_std_props (GST_ELEMENT_CLASS (klass),
"bytesperread", ARG_BYTESPERREAD, G_PARAM_READWRITE,
"location", ARG_LOCATION, G_PARAM_READWRITE, NULL);
g_object_class_install_property (gobject_class,
ARG_HANDLE,
g_param_spec_pointer ("handle",
"GnomeVFSHandle", "Handle for GnomeVFS", G_PARAM_READWRITE));
/* icecast stuff */
g_object_class_install_property (gobject_class,
ARG_IRADIO_MODE,
g_param_spec_boolean ("iradio-mode",
"iradio-mode",
"Enable internet radio mode (extraction of icecast/audiocast metadata)",
FALSE, G_PARAM_READWRITE));
g_object_class_install_property (gobject_class,
ARG_IRADIO_NAME,
g_param_spec_string ("iradio-name",
"iradio-name", "Name of the stream", NULL, G_PARAM_READABLE));
g_object_class_install_property (gobject_class,
ARG_IRADIO_GENRE,
g_param_spec_string ("iradio-genre",
"iradio-genre", "Genre of the stream", NULL, G_PARAM_READABLE));
g_object_class_install_property (gobject_class,
ARG_IRADIO_URL,
g_param_spec_string ("iradio-url",
"iradio-url",
"Homepage URL for radio stream", NULL, G_PARAM_READABLE));
g_object_class_install_property (gobject_class,
ARG_IRADIO_TITLE,
g_param_spec_string ("iradio-title",
"iradio-title",
"Name of currently playing song", NULL, G_PARAM_READABLE));
}
static void
gst_gnomevfssrc_init (GstGnomeVFSSrc * gnomevfssrc)
{
gnomevfssrc->srcpad =
gst_pad_new_from_template (gst_static_pad_template_get (&srctemplate),
"src");
gst_pad_set_getrange_function (gnomevfssrc->srcpad, gst_gnomevfssrc_getrange);
gst_pad_set_activate_function (gnomevfssrc->srcpad, gst_gnomevfssrc_activate);
gst_pad_set_event_mask_function (gnomevfssrc->srcpad,
gst_gnomevfssrc_get_event_mask);
gst_pad_set_event_function (gnomevfssrc->srcpad,
gst_gnomevfssrc_srcpad_event);
gst_pad_set_query_type_function (gnomevfssrc->srcpad,
gst_gnomevfssrc_get_query_types);
gst_pad_set_query_function (gnomevfssrc->srcpad,
gst_gnomevfssrc_srcpad_query);
gst_pad_set_formats_function (gnomevfssrc->srcpad,
gst_gnomevfssrc_get_formats);
gst_element_add_pad (GST_ELEMENT (gnomevfssrc), gnomevfssrc->srcpad);
gnomevfssrc->uri = NULL;
gnomevfssrc->uri_name = NULL;
gnomevfssrc->handle = NULL;
gnomevfssrc->curoffset = gnomevfssrc->reqoffset = 0;
gnomevfssrc->bytes_per_read = 4096;
gnomevfssrc->need_discont = gnomevfssrc->need_flush = FALSE;
gnomevfssrc->seekable = FALSE;
gnomevfssrc->size = (GnomeVFSFileSize) - 1;
gnomevfssrc->icy_metaint = 0;
gnomevfssrc->iradio_mode = FALSE;
gnomevfssrc->http_callbacks_pushed = FALSE;
gnomevfssrc->icy_count = 0;
gnomevfssrc->iradio_name = NULL;
gnomevfssrc->iradio_genre = NULL;
gnomevfssrc->iradio_url = NULL;
gnomevfssrc->iradio_title = NULL;
gnomevfssrc->audiocast_udpdata_mutex = g_mutex_new ();
gnomevfssrc->audiocast_queue_mutex = g_mutex_new ();
gnomevfssrc->audiocast_notify_queue = NULL;
gnomevfssrc->audiocast_thread = NULL;
g_static_mutex_lock (&count_lock);
if (ref_count == 0) {
/* gnome vfs engine init */
if (gnome_vfs_initialized () == FALSE) {
gnome_vfs_init ();
vfs_owner = TRUE;
}
}
ref_count++;
g_static_mutex_unlock (&count_lock);
}
static void
gst_gnomevfssrc_finalize (GObject * object)
{
GstGnomeVFSSrc *src = GST_GNOMEVFSSRC (object);
g_static_mutex_lock (&count_lock);
ref_count--;
if (ref_count == 0 && vfs_owner) {
if (gnome_vfs_initialized () == TRUE) {
gnome_vfs_shutdown ();
}
}
g_static_mutex_unlock (&count_lock);
if (src->uri) {
gnome_vfs_uri_unref (src->uri);
src->uri = NULL;
}
if (src->uri_name) {
g_free (src->uri_name);
src->uri_name = NULL;
}
g_mutex_free (src->audiocast_udpdata_mutex);
g_mutex_free (src->audiocast_queue_mutex);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
/*
* URI interface support.
*/
static guint
gst_gnomevfssrc_uri_get_type (void)
{
return GST_URI_SRC;
}
static gchar **
gst_gnomevfssrc_uri_get_protocols (void)
{
static gchar **protocols = NULL;
if (!protocols)
protocols = gst_gnomevfs_get_supported_uris ();
return protocols;
}
static const gchar *
gst_gnomevfssrc_uri_get_uri (GstURIHandler * handler)
{
GstGnomeVFSSrc *src = GST_GNOMEVFSSRC (handler);
return src->uri_name;
}
static gboolean
gst_gnomevfssrc_uri_set_uri (GstURIHandler * handler, const gchar * uri)
{
GstGnomeVFSSrc *src = GST_GNOMEVFSSRC (handler);
if (GST_STATE (src) == GST_STATE_PLAYING ||
GST_STATE (src) == GST_STATE_PAUSED)
return FALSE;
g_object_set (G_OBJECT (src), "location", uri, NULL);
return TRUE;
}
static void
gst_gnomevfssrc_uri_handler_init (gpointer g_iface, gpointer iface_data)
{
GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
iface->get_type = gst_gnomevfssrc_uri_get_type;
iface->get_protocols = gst_gnomevfssrc_uri_get_protocols;
iface->get_uri = gst_gnomevfssrc_uri_get_uri;
iface->set_uri = gst_gnomevfssrc_uri_set_uri;
}
static void
gst_gnomevfssrc_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstGnomeVFSSrc *src;
gchar cwd[PATH_MAX];
/* it's not null if we got it, but it might not be ours */
g_return_if_fail (GST_IS_GNOMEVFSSRC (object));
src = GST_GNOMEVFSSRC (object);
switch (prop_id) {
case ARG_LOCATION:
/* the element must be stopped or paused in order to do this */
if (GST_STATE (src) == GST_STATE_PLAYING ||
GST_STATE (src) == GST_STATE_PAUSED)
break;
if (src->uri) {
gnome_vfs_uri_unref (src->uri);
src->uri = NULL;
}
if (src->uri_name) {
g_free (src->uri_name);
src->uri_name = NULL;
}
if (g_value_get_string (value)) {
const gchar *location = g_value_get_string (value);
if (!strchr (location, ':')) {
gchar *newloc = gnome_vfs_escape_path_string (location);
if (*newloc == '/')
src->uri_name = g_strdup_printf ("file://%s", newloc);
else
src->uri_name =
g_strdup_printf ("file://%s/%s", getcwd (cwd, PATH_MAX),
newloc);
g_free (newloc);
} else
src->uri_name = g_strdup (location);
src->uri = gnome_vfs_uri_new (src->uri_name);
}
break;
case ARG_HANDLE:
if (GST_STATE (src) == GST_STATE_NULL ||
GST_STATE (src) == GST_STATE_READY) {
if (src->uri) {
gnome_vfs_uri_unref (src->uri);
src->uri = NULL;
}
if (src->uri_name) {
g_free (src->uri_name);
src->uri_name = NULL;
}
src->handle = g_value_get_pointer (value);
}
break;
case ARG_BYTESPERREAD:
src->bytes_per_read = g_value_get_int (value);
break;
case ARG_IRADIO_MODE:
src->iradio_mode = g_value_get_boolean (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_gnomevfssrc_get_property (GObject * object, guint prop_id, GValue * value,
GParamSpec * pspec)
{
GstGnomeVFSSrc *src;
/* it's not null if we got it, but it might not be ours */
g_return_if_fail (GST_IS_GNOMEVFSSRC (object));
src = GST_GNOMEVFSSRC (object);
switch (prop_id) {
case ARG_LOCATION:
g_value_set_string (value, src->uri_name);
break;
case ARG_BYTESPERREAD:
g_value_set_int (value, src->bytes_per_read);
break;
case ARG_HANDLE:
g_value_set_pointer (value, src->handle);
break;
case ARG_IRADIO_MODE:
g_value_set_boolean (value, src->iradio_mode);
break;
case ARG_IRADIO_NAME:
g_value_set_string (value, src->iradio_name);
break;
case ARG_IRADIO_GENRE:
g_value_set_string (value, src->iradio_genre);
break;
case ARG_IRADIO_URL:
g_value_set_string (value, src->iradio_url);
break;
case ARG_IRADIO_TITLE:
g_mutex_lock (src->audiocast_udpdata_mutex);
g_value_set_string (value, src->iradio_title);
g_mutex_unlock (src->audiocast_udpdata_mutex);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static char *
unicodify (const char *str, int len, ...)
{
char *ret = NULL, *cset;
va_list args;
gsize bytes_read, bytes_written;
if (g_utf8_validate (str, len, NULL))
return g_strndup (str, len >= 0 ? len : strlen (str));
va_start (args, len);
while ((cset = va_arg (args, char *)) != NULL)
{
if (!strcmp (cset, "locale"))
ret = g_locale_to_utf8 (str, len, &bytes_read, &bytes_written, NULL);
else
ret = g_convert (str, len, "UTF-8", cset,
&bytes_read, &bytes_written, NULL);
if (ret)
break;
}
va_end (args);
return ret;
}
static char *
gst_gnomevfssrc_unicodify (const char *str)
{
return unicodify (str, -1, "locale", "ISO-8859-1", NULL);
}
/*
* icecast/audiocast metadata extraction support code
*/
static int
audiocast_init (GstGnomeVFSSrc * src)
{
int pipefds[2];
GError *error = NULL;
if (!src->iradio_mode)
return TRUE;
GST_DEBUG ("audiocast: registering listener");
if (audiocast_register_listener (&src->audiocast_port,
&src->audiocast_fd) < 0) {
GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, (NULL),
("Unable to listen on UDP port %d", src->audiocast_port));
close (src->audiocast_fd);
return FALSE;
}
GST_DEBUG ("audiocast: creating pipe");
src->audiocast_notify_queue = NULL;
if (pipe (pipefds) < 0) {
close (src->audiocast_fd);
return FALSE;
}
src->audiocast_thread_die_infd = pipefds[0];
src->audiocast_thread_die_outfd = pipefds[1];
GST_DEBUG ("audiocast: creating audiocast thread");
src->audiocast_thread =
g_thread_create ((GThreadFunc) audiocast_thread_run, src, TRUE, &error);
if (error != NULL) {
GST_ELEMENT_ERROR (src, RESOURCE, TOO_LAZY, (NULL),
("Unable to create thread: %s", error->message));
close (src->audiocast_fd);
return FALSE;
}
return TRUE;
}
static int
audiocast_register_listener (gint * port, gint * fd)
{
struct sockaddr_in sin;
int sock;
socklen_t sinlen = sizeof (struct sockaddr_in);
GST_DEBUG ("audiocast: estabilishing UDP listener");
if ((sock = socket (AF_INET, SOCK_DGRAM, 0)) < 0)
goto lose;
memset (&sin, 0, sinlen);
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = g_htonl (INADDR_ANY);
if (bind (sock, (struct sockaddr *) &sin, sinlen) < 0)
goto lose_and_close;
memset (&sin, 0, sinlen);
if (getsockname (sock, (struct sockaddr *) &sin, &sinlen) < 0)
goto lose_and_close;
GST_DEBUG ("audiocast: listening on local %s:%d", inet_ntoa (sin.sin_addr),
g_ntohs (sin.sin_port));
*port = g_ntohs (sin.sin_port);
*fd = sock;
return 0;
lose_and_close:
close (sock);
lose:
return -1;
}
static void
audiocast_do_notifications (GstGnomeVFSSrc * src)
{
/* Send any pending notifications we got from the UDP thread. */
if (src->iradio_mode) {
GList *entry;
g_mutex_lock (src->audiocast_queue_mutex);
for (entry = src->audiocast_notify_queue; entry; entry = entry->next)
g_object_notify (G_OBJECT (src), (const gchar *) entry->data);
g_list_free (src->audiocast_notify_queue);
src->audiocast_notify_queue = NULL;
g_mutex_unlock (src->audiocast_queue_mutex);
}
}
static gpointer
audiocast_thread_run (GstGnomeVFSSrc * src)
{
char buf[1025], **lines;
gsize len;
fd_set fdset, readset;
struct sockaddr_in from;
socklen_t fromlen = sizeof (struct sockaddr_in);
FD_ZERO (&fdset);
FD_SET (src->audiocast_fd, &fdset);
FD_SET (src->audiocast_thread_die_infd, &fdset);
while (1) {
GST_DEBUG ("audiocast thread: dropping into select");
readset = fdset;
if (select (FD_SETSIZE, &readset, NULL, NULL, NULL) < 0) {
perror ("select");
return NULL;
}
if (FD_ISSET (src->audiocast_thread_die_infd, &readset)) {
char buf[1];
GST_DEBUG ("audiocast thread: got die character");
read (src->audiocast_thread_die_infd, buf, 1);
close (src->audiocast_thread_die_infd);
close (src->audiocast_fd);
return NULL;
}
GST_DEBUG ("audiocast thread: reading data");
len =
recvfrom (src->audiocast_fd, buf, sizeof (buf) - 1, 0,
(struct sockaddr *) &from, &fromlen);
if (len < 0 && errno == EAGAIN)
continue;
else if (len >= 0) {
int i;
char *valptr, *value;
buf[len] = '\0';
lines = g_strsplit (buf, "\n", 0);
if (!lines)
continue;
for (i = 0; lines[i]; i++) {
while ((lines[i][strlen (lines[i]) - 1] == '\n') ||
(lines[i][strlen (lines[i]) - 1] == '\r'))
lines[i][strlen (lines[i]) - 1] = '\0';
valptr = strchr (lines[i], ':');
if (!valptr)
continue;
else
valptr++;
g_strstrip (valptr);
if (!strlen (valptr))
continue;
value = gst_gnomevfssrc_unicodify (valptr);
if (!value) {
g_print ("Unable to convert \"%s\" to UTF-8!\n", valptr);
continue;
}
if (!strncmp (lines[i], "x-audiocast-streamtitle", 23)) {
g_mutex_lock (src->audiocast_udpdata_mutex);
g_free (src->iradio_title);
src->iradio_title = value;
g_mutex_unlock (src->audiocast_udpdata_mutex);
g_mutex_lock (src->audiocast_queue_mutex);
src->audiocast_notify_queue =
g_list_append (src->audiocast_notify_queue, "iradio-title");
GST_DEBUG ("audiocast title: %s\n", src->iradio_title);
g_mutex_unlock (src->audiocast_queue_mutex);
} else if (!strncmp (lines[i], "x-audiocast-streamurl", 21)) {
g_mutex_lock (src->audiocast_udpdata_mutex);
g_free (src->iradio_url);
src->iradio_url = value;
g_mutex_unlock (src->audiocast_udpdata_mutex);
g_mutex_lock (src->audiocast_queue_mutex);
src->audiocast_notify_queue =
g_list_append (src->audiocast_notify_queue, "iradio-url");
GST_DEBUG ("audiocast url: %s\n", src->iradio_title);
g_mutex_unlock (src->audiocast_queue_mutex);
} else if (!strncmp (lines[i], "x-audiocast-udpseqnr", 20)) {
gchar outbuf[120];
sprintf (outbuf, "x-audiocast-ack: %ld \r\n", atol (value));
g_free (value);
if (sendto (src->audiocast_fd, outbuf, strlen (outbuf), 0,
(struct sockaddr *) &from, fromlen) <= 0) {
g_print ("Error sending response to server: %s\n",
strerror (errno));
continue;
}
GST_DEBUG ("sent audiocast ack: %s\n", outbuf);
}
}
g_strfreev (lines);
}
}
return NULL;
}
static void
audiocast_thread_kill (GstGnomeVFSSrc * src)
{
if (!src->audiocast_thread)
return;
/*
We rely on this hack to kill the
audiocast thread. If we get icecast
metadata, then we don't need the
audiocast metadata too.
*/
GST_DEBUG ("audiocast: writing die character");
write (src->audiocast_thread_die_outfd, "q", 1);
close (src->audiocast_thread_die_outfd);
GST_DEBUG ("audiocast: joining thread");
g_thread_join (src->audiocast_thread);
src->audiocast_thread = NULL;
}
static void
gst_gnomevfssrc_send_additional_headers_callback (gconstpointer in,
gsize in_size, gpointer out, gsize out_size, gpointer callback_data)
{
GstGnomeVFSSrc *src = GST_GNOMEVFSSRC (callback_data);
GnomeVFSModuleCallbackAdditionalHeadersOut *out_args =
(GnomeVFSModuleCallbackAdditionalHeadersOut *) out;
if (!src->iradio_mode)
return;
GST_DEBUG ("sending headers\n");
out_args->headers = g_list_append (out_args->headers,
g_strdup ("icy-metadata:1\r\n"));
out_args->headers = g_list_append (out_args->headers,
g_strdup_printf ("x-audiocast-udpport: %d\r\n", src->audiocast_port));
}
static void
gst_gnomevfssrc_received_headers_callback (gconstpointer in,
gsize in_size, gpointer out, gsize out_size, gpointer callback_data)
{
GList *i;
gint icy_metaint;
GstGnomeVFSSrc *src = GST_GNOMEVFSSRC (callback_data);
GnomeVFSModuleCallbackReceivedHeadersIn *in_args =
(GnomeVFSModuleCallbackReceivedHeadersIn *) in;
/* This is only used for internet radio stuff right now */
if (!src->iradio_mode)
return;
for (i = in_args->headers; i; i = i->next) {
char *data = (char *) i->data;
char *key = data;
char *value = strchr (data, ':');
if (!value)
continue;
value++;
g_strstrip (value);
if (!strlen (value))
continue;
/* Icecast stuff */
if (!strncmp (data, "icy-metaint:", 12)) { /* ugh */
sscanf (data + 12, "%d", &icy_metaint);
src->icy_metaint = icy_metaint;
GST_DEBUG ("got icy-metaint %d, killing audiocast thread",
src->icy_metaint);
audiocast_thread_kill (src);
continue;
}
if (!strncmp (data, "icy-", 4))
key = data + 4;
else if (!strncmp (data, "x-audiocast-", 12))
key = data + 12;
else
continue;
GST_DEBUG ("key: %s", key);
if (!strncmp (key, "name", 4)) {
g_free (src->iradio_name);
src->iradio_name = gst_gnomevfssrc_unicodify (value);
if (src->iradio_name)
g_object_notify (G_OBJECT (src), "iradio-name");
} else if (!strncmp (key, "genre", 5)) {
g_free (src->iradio_genre);
src->iradio_genre = gst_gnomevfssrc_unicodify (value);
if (src->iradio_genre)
g_object_notify (G_OBJECT (src), "iradio-genre");
} else if (!strncmp (key, "url", 3)) {
g_free (src->iradio_url);
src->iradio_url = gst_gnomevfssrc_unicodify (value);
if (src->iradio_url)
g_object_notify (G_OBJECT (src), "iradio-url");
}
}
}
static void
gst_gnomevfssrc_push_callbacks (GstGnomeVFSSrc * gnomevfssrc)
{
if (gnomevfssrc->http_callbacks_pushed)
return;
GST_DEBUG ("pushing callbacks");
gnome_vfs_module_callback_push
(GNOME_VFS_MODULE_CALLBACK_HTTP_SEND_ADDITIONAL_HEADERS,
gst_gnomevfssrc_send_additional_headers_callback, gnomevfssrc, NULL);
gnome_vfs_module_callback_push
(GNOME_VFS_MODULE_CALLBACK_HTTP_RECEIVED_HEADERS,
gst_gnomevfssrc_received_headers_callback, gnomevfssrc, NULL);
gnomevfssrc->http_callbacks_pushed = TRUE;
}
static void
gst_gnomevfssrc_pop_callbacks (GstGnomeVFSSrc * gnomevfssrc)
{
if (!gnomevfssrc->http_callbacks_pushed)
return;
GST_DEBUG ("popping callbacks");
gnome_vfs_module_callback_pop
(GNOME_VFS_MODULE_CALLBACK_HTTP_SEND_ADDITIONAL_HEADERS);
gnome_vfs_module_callback_pop
(GNOME_VFS_MODULE_CALLBACK_HTTP_RECEIVED_HEADERS);
}
static void
gst_gnomevfssrc_get_icy_metadata (GstGnomeVFSSrc * src)
{
GnomeVFSFileSize length = 0;
GnomeVFSResult res;
gint metadata_length;
guchar foobyte;
guchar *data;
guchar *pos;
gchar **tags;
int i;
GST_DEBUG ("reading icecast metadata");
while (length == 0) {
res = gnome_vfs_read (src->handle, &foobyte, 1, &length);
if (res != GNOME_VFS_OK)
return;
}
metadata_length = foobyte * 16;
if (metadata_length == 0)
return;
data = g_new (gchar, metadata_length + 1);
pos = data;
while (pos - data < metadata_length) {
res = gnome_vfs_read (src->handle, pos,
metadata_length - (pos - data), &length);
/* FIXME: better error handling here? */
if (res != GNOME_VFS_OK) {
g_free (data);
return;
}
pos += length;
}
data[metadata_length] = 0;
tags = g_strsplit (data, "';", 0);
for (i = 0; tags[i]; i++) {
if (!g_ascii_strncasecmp (tags[i], "StreamTitle=", 12)) {
g_free (src->iradio_title);
src->iradio_title = gst_gnomevfssrc_unicodify (tags[i] + 13);
if (src->iradio_title) {
GST_DEBUG ("sending notification on icecast title");
g_object_notify (G_OBJECT (src), "iradio-title");
} else
g_print ("Unable to convert icecast title \"%s\" to UTF-8!\n",
tags[i] + 13);
}
if (!g_ascii_strncasecmp (tags[i], "StreamUrl=", 10)) {
g_free (src->iradio_url);
src->iradio_url = gst_gnomevfssrc_unicodify (tags[i] + 11);
if (src->iradio_url) {
GST_DEBUG ("sending notification on icecast url");
g_object_notify (G_OBJECT (src), "iradio-url");
} else
g_print ("Unable to convert icecast url \"%s\" to UTF-8!\n",
tags[i] + 11);
}
}
g_strfreev (tags);
}
/* end of icecast/audiocast metadata extraction support code */
/*
* Read a new buffer from src->reqoffset, takes care of events
* and seeking and such.
*/
static GstFlowReturn
gst_gnomevfssrc_get (GstGnomeVFSSrc * src, GstBuffer ** buffer)
{
GnomeVFSResult res;
GstBuffer *buf;
GnomeVFSFileSize readbytes;
guint8 *data;
g_return_val_if_fail (GST_FLAG_IS_SET (src, GST_GNOMEVFSSRC_OPEN),
GST_FLOW_ERROR);
/* if the requested offset is outside the file boundary, we´re EOF */
if (src->size != (GnomeVFSFileSize) - 1 && src->reqoffset >= src->size) {
GST_LOG_OBJECT (src, "Requested offset %lld is outside filesize %llu",
src->reqoffset, src->size);
gst_pad_push_event (src->srcpad, gst_event_new (GST_EVENT_EOS));
return GST_FLOW_WRONG_STATE;
}
/* seek if required */
if (src->curoffset != src->reqoffset) {
if (src->seekable) {
if ((res = gnome_vfs_seek (src->handle,
GNOME_VFS_SEEK_START, src->reqoffset)) != GNOME_VFS_OK) {
GST_ERROR_OBJECT (src,
"Failed to seek to requested position %lld: %s",
src->reqoffset, gnome_vfs_result_to_string (res));
return GST_FLOW_ERROR;
}
src->curoffset = src->reqoffset;
} else {
GST_ERROR_OBJECT (src,
"Requested seek from %lld to %lld on non-seekable stream",
src->curoffset, src->reqoffset);
return GST_FLOW_ERROR;
}
}
/* create the buffer */
/* FIXME: should eventually use a bufferpool for this */
buf = gst_buffer_new ();
audiocast_do_notifications (src);
if (src->iradio_mode && src->icy_metaint > 0) {
data = g_malloc (src->icy_metaint);
/* try to read */
GST_DEBUG ("doing read: icy_count: %" G_GINT64_FORMAT, src->icy_count);
if ((res = gnome_vfs_read (src->handle, data,
src->icy_metaint - src->icy_count,
&readbytes)) != GNOME_VFS_OK) {
GST_ERROR_OBJECT (src, "Failed to read iradio data: %s",
gnome_vfs_result_to_string (res));
return GST_FLOW_ERROR;
} else if (readbytes == 0) {
gst_buffer_unref (buf);
GST_LOG_OBJECT (src, "Reading iradio data gave EOS");
gst_pad_push_event (src->srcpad, gst_event_new (GST_EVENT_EOS));
return GST_FLOW_WRONG_STATE;
}
src->icy_count += readbytes;
GST_BUFFER_OFFSET (buf) = src->curoffset;
GST_BUFFER_SIZE (buf) += readbytes;
GST_BUFFER_DATA (buf) = data;
src->curoffset += readbytes;
src->reqoffset += readbytes;
if (src->icy_count == src->icy_metaint) {
gst_gnomevfssrc_get_icy_metadata (src);
src->icy_count = 0;
}
} else {
data = g_malloc (src->bytes_per_read);
if (src->need_flush) {
src->need_flush = FALSE;
GST_LOG_OBJECT (src, "sending flush");
gst_pad_push_event (src->srcpad, gst_event_new (GST_EVENT_FLUSH));
}
if (src->need_discont) {
src->need_discont = FALSE;
GST_LOG_OBJECT (src, "sending discont");
gst_pad_push_event (src->srcpad, gst_event_new_discontinuous (FALSE,
GST_FORMAT_BYTES, src->reqoffset, GST_FORMAT_UNDEFINED));
}
if ((res = gnome_vfs_read (src->handle, data,
src->bytes_per_read, &readbytes)) != GNOME_VFS_OK) {
GST_ERROR_OBJECT (src, "Failed to read data: %s",
gnome_vfs_result_to_string (res));
return GST_FLOW_ERROR;
} else if (readbytes == 0) {
gst_buffer_unref (buf);
GST_LOG_OBJECT (src, "Reading data gave EOS");
gst_pad_push_event (src->srcpad, gst_event_new (GST_EVENT_EOS));
return GST_FLOW_WRONG_STATE;
}
GST_BUFFER_OFFSET (buf) = src->curoffset;
GST_BUFFER_SIZE (buf) = readbytes;
GST_BUFFER_DATA (buf) = data;
src->curoffset += readbytes;
src->reqoffset += readbytes;
}
/* we're done, return the buffer */
*buffer = buf;
return GST_FLOW_OK;
}
/* open the file, do stuff necessary to go to READY state */
static gboolean
gst_gnomevfssrc_open_file (GstGnomeVFSSrc * src)
{
GnomeVFSResult res;
GnomeVFSFileInfo *info;
g_return_val_if_fail (!GST_FLAG_IS_SET (src, GST_GNOMEVFSSRC_OPEN), FALSE);
if (!audiocast_init (src))
return FALSE;
gst_gnomevfssrc_push_callbacks (src);
if (src->uri != NULL) {
if ((res = gnome_vfs_open_uri (&src->handle, src->uri,
GNOME_VFS_OPEN_READ)) != GNOME_VFS_OK) {
gchar *filename = gnome_vfs_uri_to_string (src->uri,
GNOME_VFS_URI_HIDE_PASSWORD);
gst_gnomevfssrc_pop_callbacks (src);
audiocast_thread_kill (src);
GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, (NULL),
("Could not open vfs file \"%s\" for reading: %s",
filename, gnome_vfs_result_to_string (res)));
g_free (filename);
return FALSE;
}
src->own_handle = TRUE;
} else if (!src->handle) {
GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, (NULL), ("No filename given"));
return FALSE;
} else {
src->own_handle = FALSE;
}
src->size = (GnomeVFSFileSize) - 1;
info = gnome_vfs_file_info_new ();
if ((res = gnome_vfs_get_file_info_from_handle (src->handle,
info, GNOME_VFS_FILE_INFO_DEFAULT)) == GNOME_VFS_OK) {
if (info->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_SIZE) {
src->size = info->size;
GST_DEBUG_OBJECT (src, "size: %llu bytes", src->size);
} else
GST_LOG_OBJECT (src, "filesize not known");
} else {
GST_WARNING_OBJECT (src, "getting info failed: %s",
gnome_vfs_result_to_string (res));
}
gnome_vfs_file_info_unref (info);
audiocast_do_notifications (src);
if (gnome_vfs_seek (src->handle, GNOME_VFS_SEEK_CURRENT, 0)
== GNOME_VFS_OK) {
src->seekable = TRUE;
} else {
src->seekable = FALSE;
}
GST_FLAG_SET (src, GST_GNOMEVFSSRC_OPEN);
return TRUE;
}
static void
gst_gnomevfssrc_close_file (GstGnomeVFSSrc * src)
{
g_return_if_fail (GST_FLAG_IS_SET (src, GST_GNOMEVFSSRC_OPEN));
gst_gnomevfssrc_pop_callbacks (src);
audiocast_thread_kill (src);
if (src->own_handle) {
gnome_vfs_close (src->handle);
src->handle = NULL;
}
src->size = (GnomeVFSFileSize) - 1;
src->curoffset = src->reqoffset = 0;
src->need_flush = src->need_discont = FALSE;
GST_FLAG_UNSET (src, GST_GNOMEVFSSRC_OPEN);
}
/*
* Called to get random access data.
*/
static GstFlowReturn
gst_gnomevfssrc_getrange (GstPad * pad,
guint64 offset, guint size, GstBuffer ** buffer)
{
GstGnomeVFSSrc *src = GST_GNOMEVFSSRC (gst_pad_get_parent (pad));
/* get ready */
src->reqoffset = offset;
src->bytes_per_read = size;
/* get data */
return gst_gnomevfssrc_get (src, buffer);
}
/*
* Used if we´re loopbased.
*/
static void
gst_gnomevfssrc_loop (GstPad * pad)
{
GstGnomeVFSSrc *src = GST_GNOMEVFSSRC (gst_pad_get_parent (pad));
GstFlowReturn res;
GstBuffer *buffer;
if ((res = gst_gnomevfssrc_get (src, &buffer)) != GST_FLOW_OK ||
(res = gst_pad_push (pad, buffer)) != GST_FLOW_OK) {
gst_task_pause (GST_RPAD_TASK (pad));
}
}
/*
* Called to notify us of scheduling mode.
*/
static gboolean
gst_gnomevfssrc_activate (GstPad * pad, GstActivateMode mode)
{
gboolean res = FALSE;
GstGnomeVFSSrc *src = GST_GNOMEVFSSRC (gst_pad_get_parent (pad));
switch (mode) {
/* we're the loop function */
case GST_ACTIVATE_PUSH:
/* if we have a scheduler we can start the task */
if (GST_ELEMENT_SCHEDULER (src)) {
GST_STREAM_LOCK (pad);
if (!GST_FLAG_IS_SET (src, GST_GNOMEVFSSRC_OPEN)) {
if (!gst_gnomevfssrc_open_file (src)) {
GST_STREAM_UNLOCK (pad);
goto fail_open;
}
}
GST_RPAD_TASK (pad) =
gst_scheduler_create_task (GST_ELEMENT_SCHEDULER (src),
(GstTaskFunction) gst_gnomevfssrc_loop, pad);
gst_task_start (GST_RPAD_TASK (pad));
res = TRUE;
GST_STREAM_UNLOCK (pad);
}
break;
/* random access mode */
case GST_ACTIVATE_PULL:
if (!GST_FLAG_IS_SET (src, GST_GNOMEVFSSRC_OPEN)) {
if (!gst_gnomevfssrc_open_file (src)) {
goto fail_open;
}
}
res = TRUE;
break;
case GST_ACTIVATE_NONE:
/* step 1, unblock clock sync (if any) */
/* step 2, make sure streaming finishes */
GST_STREAM_LOCK (pad);
/* step 3, stop the task */
if (GST_RPAD_TASK (pad)) {
gst_task_stop (GST_RPAD_TASK (pad));
}
if (GST_FLAG_IS_SET (src, GST_GNOMEVFSSRC_OPEN))
gst_gnomevfssrc_close_file (src);
GST_STREAM_UNLOCK (pad);
res = TRUE;
break;
default:
break;
}
return res;
fail_open:
{
return FALSE;
}
}
static GstElementStateReturn
gst_gnomevfssrc_change_state (GstElement * element)
{
g_return_val_if_fail (GST_IS_GNOMEVFSSRC (element), GST_STATE_FAILURE);
switch (GST_STATE_TRANSITION (element)) {
case GST_STATE_READY_TO_PAUSED:
break;
case GST_STATE_PAUSED_TO_READY:
default:
break;
}
if (GST_ELEMENT_CLASS (parent_class)->change_state)
return GST_ELEMENT_CLASS (parent_class)->change_state (element);
return GST_STATE_SUCCESS;
}
static gboolean
gst_gnomevfssrc_srcpad_query (GstPad * pad, GstQueryType type,
GstFormat * format, gint64 * value)
{
GstGnomeVFSSrc *src = GST_GNOMEVFSSRC (gst_pad_get_parent (pad));
gboolean res = FALSE;
switch (type) {
case GST_QUERY_TOTAL:
if (*format == GST_FORMAT_BYTES || src->size != (GnomeVFSFileSize) - 1) {
res = TRUE;
*value = src->size;
}
break;
case GST_QUERY_POSITION:
switch (*format) {
case GST_FORMAT_BYTES:
*value = src->curoffset;
res = TRUE;
break;
case GST_FORMAT_PERCENT:
if (src->size != (GnomeVFSFileSize) - 1) {
res = TRUE;
*value = src->curoffset * GST_FORMAT_PERCENT_MAX / src->size;
}
break;
default:
break;
}
break;
default:
break;
}
return res;
}
static gboolean
gst_gnomevfssrc_srcpad_event (GstPad * pad, GstEvent * event)
{
GstGnomeVFSSrc *src = GST_GNOMEVFSSRC (GST_PAD_PARENT (pad));
gboolean res = FALSE;
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_SEEK:
{
gint64 desired_offset;
GnomeVFSResult res;
if (GST_EVENT_SEEK_FORMAT (event) != GST_FORMAT_BYTES) {
break;
} else if (!src->seekable) {
GST_WARNING_OBJECT (src, "Seek on non-seekable stream");
break;
}
switch (GST_EVENT_SEEK_METHOD (event)) {
case GST_SEEK_METHOD_SET:
desired_offset = GST_EVENT_SEEK_OFFSET (event);
break;
case GST_SEEK_METHOD_CUR:
desired_offset = src->curoffset + GST_EVENT_SEEK_OFFSET (event);
break;
case GST_SEEK_METHOD_END:
if (src->size == (GnomeVFSFileSize) - 1) {
goto done;
}
desired_offset = src->size - ABS (GST_EVENT_SEEK_OFFSET (event));
break;
default:
goto done;
}
/* check boundaries */
if (desired_offset < 0 || desired_offset >= src->size) {
GST_WARNING_OBJECT (src, "Seek to %lld is outside filebounds 0-%llu",
desired_offset, src->size);
break;
}
/* prepare for seek */
src->reqoffset = desired_offset;
GST_DEBUG_OBJECT (src, "new_seek to %lld: %s", desired_offset);
src->need_discont = TRUE;
src->need_flush = GST_EVENT_SEEK_FLAGS (event) & GST_SEEK_FLAG_FLUSH;
res = TRUE;
break;
}
case GST_EVENT_SIZE:
if (GST_EVENT_SIZE_FORMAT (event) != GST_FORMAT_BYTES) {
break;
}
src->bytes_per_read = GST_EVENT_SIZE_VALUE (event);
g_object_notify (G_OBJECT (src), "bytesperread");
break;
default:
break;
}
done:
gst_event_unref (event);
return res;
}