* Boston, MA 02111-1307, USA.
*/
-#include <string.h>
+#ifdef HAVE_CONFIG_H
#include "config.h"
+#endif
+
+#include <string.h>
#ifdef HAVE_FFMPEG_UNINSTALLED
#include <avformat.h>
#include <avi.h>
#include <gst/gst.h>
-extern GstCaps* gst_ffmpegcodec_codec_context_to_caps (AVCodecContext *context, int codec_id);
-
-typedef enum {
- STATE_OPEN,
- STATE_DEMUX,
-} DemuxState;
+#include "gstffmpegcodecmap.h"
typedef struct _GstFFMpegDemux GstFFMpegDemux;
GstPad *sinkpad;
AVFormatContext *context;
- DemuxState state;
+ gboolean opened;
GstPad *srcpads[MAX_STREAMS];
+ gint videopads, audiopads;
};
+typedef struct _GstFFMpegDemuxClassParams {
+ AVInputFormat *in_plugin;
+ GstPadTemplate *sinktempl;
+ GstPadTemplate *videosrctempl;
+ GstPadTemplate *audiosrctempl;
+ GstPluginFeature *typefind_feature;
+} GstFFMpegDemuxClassParams;
+
typedef struct _GstFFMpegDemuxClass GstFFMpegDemuxClass;
struct _GstFFMpegDemuxClass {
GstElementClass parent_class;
AVInputFormat *in_plugin;
+ GstPadTemplate *sinktempl;
+ GstPadTemplate *videosrctempl;
+ GstPadTemplate *audiosrctempl;
+ GstPluginFeature *typefind_feature;
};
#define GST_TYPE_FFMPEGDEC \
/* FILL ME */
};
-/* This factory is much simpler, and defines the source pad. */
-GST_PAD_TEMPLATE_FACTORY (gst_ffmpegdemux_sink_factory,
- "sink",
- GST_PAD_SINK,
- GST_PAD_ALWAYS,
- NULL
-)
-
-/* This factory is much simpler, and defines the source pad. */
-GST_PAD_TEMPLATE_FACTORY (gst_ffmpegdemux_audio_src_factory,
- "audio_%02d",
- GST_PAD_SRC,
- GST_PAD_SOMETIMES,
- NULL
-)
-
-/* This factory is much simpler, and defines the source pad. */
-GST_PAD_TEMPLATE_FACTORY (gst_ffmpegdemux_video_src_factory,
- "video_%02d",
- GST_PAD_SRC,
- GST_PAD_SOMETIMES,
- NULL
-)
-
-static GHashTable *global_plugins;
+static GHashTable *global_plugins, *typefind;
/* A number of functon prototypes are given so we can refer to them later. */
static void gst_ffmpegdemux_class_init (GstFFMpegDemuxClass *klass);
static void gst_ffmpegdemux_init (GstFFMpegDemux *ffmpegdemux);
+static void gst_ffmpegdemux_dispose (GObject *object);
static void gst_ffmpegdemux_loop (GstElement *element);
-static void gst_ffmpegdemux_set_property (GObject *object, guint prop_id, const GValue *value,
- GParamSpec *pspec);
-static void gst_ffmpegdemux_get_property (GObject *object, guint prop_id, GValue *value,
- GParamSpec *pspec);
+static GstElementStateReturn
+ gst_ffmpegdemux_change_state (GstElement *element);
static GstElementClass *parent_class = NULL;
{
GObjectClass *gobject_class;
GstElementClass *gstelement_class;
+ GstFFMpegDemuxClassParams *params;
gobject_class = (GObjectClass*)klass;
gstelement_class = (GstElementClass*)klass;
parent_class = g_type_class_ref(GST_TYPE_ELEMENT);
- klass->in_plugin = g_hash_table_lookup (global_plugins,
- GINT_TO_POINTER (G_OBJECT_CLASS_TYPE (gobject_class)));
+ params = g_hash_table_lookup (global_plugins,
+ GINT_TO_POINTER (G_OBJECT_CLASS_TYPE (gobject_class)));
- gobject_class->set_property = gst_ffmpegdemux_set_property;
- gobject_class->get_property = gst_ffmpegdemux_get_property;
+ klass->in_plugin = params->in_plugin;
+ klass->typefind_feature = params->typefind_feature;
+ klass->videosrctempl = params->videosrctempl;
+ klass->audiosrctempl = params->audiosrctempl;
+ klass->sinktempl = params->sinktempl;
+
+ gstelement_class->change_state = gst_ffmpegdemux_change_state;
+ gobject_class->dispose = gst_ffmpegdemux_dispose;
}
static void
gst_ffmpegdemux_init(GstFFMpegDemux *ffmpegdemux)
{
- //GstFFMpegDemuxClass *oclass = (GstFFMpegDemuxClass*)(G_OBJECT_GET_CLASS (ffmpegdemux));
+ GstFFMpegDemuxClass *oclass = (GstFFMpegDemuxClass*)(G_OBJECT_GET_CLASS (ffmpegdemux));
- ffmpegdemux->sinkpad = gst_pad_new_from_template (
- GST_PAD_TEMPLATE_GET (gst_ffmpegdemux_sink_factory), "sink");
+ ffmpegdemux->sinkpad = gst_pad_new_from_template (oclass->sinktempl,
+ "sink");
+ gst_element_add_pad (GST_ELEMENT (ffmpegdemux),
+ ffmpegdemux->sinkpad);
+ gst_element_set_loop_function (GST_ELEMENT (ffmpegdemux),
+ gst_ffmpegdemux_loop);
- gst_element_add_pad (GST_ELEMENT (ffmpegdemux), ffmpegdemux->sinkpad);
- gst_element_set_loop_function (GST_ELEMENT (ffmpegdemux), gst_ffmpegdemux_loop);
+ ffmpegdemux->opened = FALSE;
- ffmpegdemux->state = STATE_OPEN;
+ ffmpegdemux->videopads = 0;
+ ffmpegdemux->audiopads = 0;
}
static void
-gst_ffmpegdemux_loop (GstElement *element)
+gst_ffmpegdemux_dispose (GObject *object)
{
- GstFFMpegDemux *ffmpegdemux = (GstFFMpegDemux *)(element);
- GstFFMpegDemuxClass *oclass = (GstFFMpegDemuxClass*)(G_OBJECT_GET_CLASS (ffmpegdemux));
+ GstFFMpegDemux *ffmpegdemux = (GstFFMpegDemux *) object;
+
+ if (ffmpegdemux->opened) {
+ av_close_input_file (ffmpegdemux->context);
+ ffmpegdemux->opened = FALSE;
+ }
+}
+
+static GstCaps*
+gst_ffmpegdemux_typefind (GstBuffer *buffer,
+ gpointer priv)
+{
+ GstFFMpegDemuxClassParams *params;
+ AVInputFormat *in_plugin;
gint res = 0;
+ gint required = AVPROBE_SCORE_MAX * 0.8; /* 80% certainty enough? */
+
+ params = g_hash_table_lookup (typefind, priv);
- switch (ffmpegdemux->state) {
- case STATE_OPEN:
- {
- res = av_open_input_file (&ffmpegdemux->context,
- g_strdup_printf ("gstreamer://%p", ffmpegdemux->sinkpad),
- oclass->in_plugin,
- 0,
- NULL);
+ in_plugin = params->in_plugin;
- ffmpegdemux->state = STATE_DEMUX;
- break;
- }
- case STATE_DEMUX:
- {
- gint res;
- AVPacket pkt;
- AVFormatContext *ct = ffmpegdemux->context;
- AVStream *st;
- GstPad *pad;
-
- res = av_read_packet(ct, &pkt);
- if (res < 0) {
- if (url_feof (&ct->pb)) {
- gint i;
-
- for (i = 0; i < ct->nb_streams; i++) {
- GstPad *pad;
-
- pad = ffmpegdemux->srcpads[i];
-
- if (GST_PAD_IS_USABLE (pad)) {
- gst_pad_push (pad, GST_BUFFER (gst_event_new (GST_EVENT_EOS)));
- }
- }
- gst_element_set_eos (element);
- }
- return;
- }
+ if (in_plugin->read_probe) {
+ AVProbeData probe_data;
- st = ct->streams[pkt.stream_index];
+ probe_data.filename = "";
+ probe_data.buf = GST_BUFFER_DATA (buffer);
+ probe_data.buf_size = GST_BUFFER_SIZE (buffer);
- if (st->codec_info_state == 0) {
- gchar *templname = NULL;
+ res = in_plugin->read_probe (&probe_data);
+ if (res >= required) {
+ GstCaps *caps;
+ caps = GST_PAD_TEMPLATE_CAPS (params->sinktempl);
+ /* make sure we still hold a refcount to this caps */
+ gst_caps_ref (caps);
+ return caps;
+ }
+ }
- st->codec_info_state = 1;
-
- if (st->codec.codec_type == CODEC_TYPE_VIDEO) {
- templname = "video_%02d";
- }
- else if (st->codec.codec_type == CODEC_TYPE_AUDIO) {
- templname = "audio_%02d";
- }
-
- if (templname != NULL) {
- gchar *padname;
- GstCaps *caps;
- GstPadTemplate *templ;
-
- caps = gst_ffmpegcodec_codec_context_to_caps (&st->codec, st->codec.codec_id);
- templ = gst_pad_template_new (templname,
- GST_PAD_SRC,
- GST_PAD_SOMETIMES,
- caps, NULL);
-
- padname = g_strdup_printf (templname, pkt.stream_index);
- pad = gst_pad_new_from_template (templ, padname);
-
- ffmpegdemux->srcpads[pkt.stream_index] = pad;
- gst_element_add_pad (GST_ELEMENT (ffmpegdemux), pad);
- }
- else {
- g_warning ("unknown pad type %d", st->codec.codec_type);
- return;
- }
- }
- else {
- pad = ffmpegdemux->srcpads[pkt.stream_index];
- }
+ return NULL;
+}
- if (GST_PAD_IS_USABLE (pad)) {
- GstBuffer *outbuf;
+static void
+gst_ffmpegdemux_loop (GstElement *element)
+{
+ GstFFMpegDemux *ffmpegdemux = (GstFFMpegDemux *)(element);
+ GstFFMpegDemuxClass *oclass = (GstFFMpegDemuxClass*)(G_OBJECT_GET_CLASS (ffmpegdemux));
- outbuf = gst_buffer_new ();
- GST_BUFFER_DATA (outbuf) = pkt.data;
- GST_BUFFER_SIZE (outbuf) = pkt.size;
+ gint res;
+ AVPacket pkt;
+ AVFormatContext *ct;
+ AVStream *st;
+ GstPad *pad;
+
+ /* open file if we didn't so already */
+ if (!ffmpegdemux->opened) {
+ res = av_open_input_file (&ffmpegdemux->context,
+ g_strdup_printf ("gstreamer://i/%p",
+ ffmpegdemux->sinkpad),
+ oclass->in_plugin, 0, NULL);
+ if (res < 0) {
+ gst_element_error (GST_ELEMENT (ffmpegdemux),
+ "Failed to open demuxer/file context");
+ return;
+ }
- if (pkt.pts != AV_NOPTS_VALUE && ct->pts_den) {
- GST_BUFFER_TIMESTAMP (outbuf) = pkt.pts * GST_SECOND * ct->pts_num / ct->pts_den;
- }
- else {
- GST_BUFFER_TIMESTAMP (outbuf) = -1;
- }
+ ffmpegdemux->opened = TRUE;
+ }
- gst_pad_push (pad, outbuf);
+ /* shortcut to context */
+ ct = ffmpegdemux->context;
+
+ /* read a package */
+ res = av_read_packet (ct, &pkt);
+ if (res < 0) {
+ if (url_feof (&ct->pb)) {
+ int i;
+
+ /* we're at the end of file - send an EOS to
+ * each stream that we opened so far */
+ for (i = 0; i < ct->nb_streams; i++) {
+ GstPad *pad;
+ GstEvent *event = gst_event_new (GST_EVENT_EOS);
+
+ pad = ffmpegdemux->srcpads[i];
+ if (GST_PAD_IS_USABLE (pad)) {
+ gst_data_ref (GST_DATA (event));
+ gst_pad_push (pad, GST_BUFFER (event));
+ }
+ gst_data_unref (GST_DATA (event));
}
- break;
- }
- default:
gst_element_set_eos (element);
- break;
+
+ /* FIXME: should we go into
+ * should we close the context here?
+ * either way, a new media stream needs an
+ * event too */
+ }
+ return;
}
-}
-static void
-gst_ffmpegdemux_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
-{
- GstFFMpegDemux *ffmpegdemux;
+ /* shortcut to stream */
+ st = ct->streams[pkt.stream_index];
+
+ /* create the pad/stream if we didn't do so already */
+ if (st->codec_info_state == 0) {
+ GstPadTemplate *templ = NULL;
+ GstCaps *caps;
+ gchar *padname;
+ gint num;
+
+ /* mark as handled */
+ st->codec_info_state = 1;
+
+ /* find template */
+ switch (st->codec.codec_type) {
+ case CODEC_TYPE_VIDEO:
+ templ = oclass->videosrctempl;
+ num = ffmpegdemux->videopads++;
+ break;
+ case CODEC_TYPE_AUDIO:
+ templ = oclass->audiosrctempl;
+ num = ffmpegdemux->audiopads++;
+ break;
+ default:
+ g_warning ("Unknown pad type %d",
+ st->codec.codec_type);
+ return;
+ }
+
+ /* create new pad for this stream */
+ padname = g_strdup_printf (GST_PAD_TEMPLATE_NAME_TEMPLATE(templ),
+ num);
+ pad = gst_pad_new_from_template (templ, padname);
+ g_free (padname);
+
+ /* FIXME: convert() and query() functions for pad */
+
+ /* store pad internally */
+ ffmpegdemux->srcpads[pkt.stream_index] = pad;
+ gst_element_add_pad (GST_ELEMENT (ffmpegdemux), pad);
+
+ /* get caps that belongs to this stream */
+ caps = gst_ffmpeg_codecid_to_caps (st->codec.codec_id,
+ &st->codec);
+ if (gst_pad_try_set_caps (pad, caps) <= 0) {
+ GST_DEBUG (GST_CAT_PLUGIN_ERRORS,
+ "Failed to set caps from ffdemuxer on next element");
+ /* we continue here, in the next pad-is-usable check,
+ * we'll return nonetheless */
+ }
+ }
- /* Get a pointer of the right type. */
- ffmpegdemux = (GstFFMpegDemux *)(object);
+ /* shortcut to pad belonging to this stream */
+ pad = ffmpegdemux->srcpads[pkt.stream_index];
- /* Check the argument id to see which argument we're setting. */
- switch (prop_id) {
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
- break;
+ /* and handle the data by pushing it forward... */
+ if (GST_PAD_IS_USABLE (pad)) {
+ GstBuffer *outbuf;
+
+ outbuf = gst_buffer_new_and_alloc (pkt.size);
+ memcpy (GST_BUFFER_DATA (outbuf), pkt.data, pkt.size);
+ GST_BUFFER_SIZE (outbuf) = pkt.size;
+
+ if (pkt.pts != AV_NOPTS_VALUE && ct->pts_den) {
+ GST_BUFFER_TIMESTAMP (outbuf) = pkt.pts * GST_SECOND *
+ ct->pts_num / ct->pts_den;
+ }
+
+ if (pkt.flags & PKT_FLAG_KEY) {
+ GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_KEY_UNIT);
+ }
+
+ gst_pad_push (pad, outbuf);
+ pkt.destruct (&pkt);
}
}
-/* The set function is simply the inverse of the get fuction. */
-static void
-gst_ffmpegdemux_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+static GstElementStateReturn
+gst_ffmpegdemux_change_state (GstElement *element)
{
- GstFFMpegDemux *ffmpegdemux;
-
- /* It's not null if we got it, but it might not be ours */
- ffmpegdemux = (GstFFMpegDemux *)(object);
+ GstFFMpegDemux *ffmpegdemux = (GstFFMpegDemux *)(element);
+ gint transition = GST_STATE_TRANSITION (element);
- switch (prop_id) {
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ switch (transition) {
+ case GST_STATE_PAUSED_TO_READY:
+ if (ffmpegdemux->opened) {
+ av_close_input_file (ffmpegdemux->context);
+ ffmpegdemux->opened = FALSE;
+ }
break;
}
+
+ if (GST_ELEMENT_CLASS (parent_class)->change_state)
+ return GST_ELEMENT_CLASS (parent_class)->change_state (element);
+
+ return GST_STATE_SUCCESS;
}
gboolean
gst_ffmpegdemux_register (GstPlugin *plugin)
{
GstElementFactory *factory;
+ GstTypeFactory *type_factory;
+ GstTypeDefinition *type_definition;
GTypeInfo typeinfo = {
sizeof(GstFFMpegDemuxClass),
NULL,
GType type;
GstElementDetails *details;
AVInputFormat *in_plugin;
+ GstFFMpegDemuxClassParams *params;
+ AVCodec *in_codec;
in_plugin = first_iformat;
global_plugins = g_hash_table_new (NULL, NULL);
+ typefind = g_hash_table_new (NULL, NULL);
while (in_plugin) {
gchar *type_name;
gchar *p;
+ GstCaps *sinkcaps, *audiosrccaps, *videosrccaps;
+
+ /* Try to find the caps that belongs here */
+ sinkcaps = gst_ffmpeg_formatid_to_caps (in_plugin->name);
+ if (!sinkcaps) {
+ goto next;
+ }
+ /* This is a bit ugly, but we just take all formats
+ * for the pad template. We'll get an exact match
+ * when we open the stream */
+ audiosrccaps = NULL;
+ videosrccaps = NULL;
+ for (in_codec = first_avcodec; in_codec != NULL;
+ in_codec = in_codec->next) {
+ GstCaps *temp = gst_ffmpeg_codecid_to_caps (in_codec->id, NULL);
+ if (!temp) {
+ continue;
+ }
+ switch (in_codec->type) {
+ case CODEC_TYPE_VIDEO:
+ videosrccaps = gst_caps_append (videosrccaps, temp);
+ break;
+ case CODEC_TYPE_AUDIO:
+ audiosrccaps = gst_caps_append (audiosrccaps, temp);
+ break;
+ default:
+ gst_caps_unref (temp);
+ break;
+ }
+ }
/* construct the type */
type_name = g_strdup_printf("ffdemux_%s", in_plugin->name);
goto next;
}
- /* create the gtk type now */
+ /* create the type now */
type = g_type_register_static(GST_TYPE_ELEMENT, type_name , &typeinfo, 0);
/* construct the element details struct */
- details = g_new0 (GstElementDetails,1);
- details->longname = g_strdup (in_plugin->name);
- details->klass = "Codec/Demuxer/FFMpeg";
- details->license = "LGPL";
- details->description = g_strdup (in_plugin->name);
- details->version = g_strdup("1.0.0");
- details->author = g_strdup("The FFMPEG crew, GStreamer plugin by Wim Taymans <wim.taymans@chello.be>");
- details->copyright = g_strdup("(c) 2002");
-
- g_hash_table_insert (global_plugins,
- GINT_TO_POINTER (type),
- (gpointer) in_plugin);
+ details = g_new0 (GstElementDetails, 1);
+ details->longname = g_strdup (in_plugin->long_name);
+ details->klass = g_strdup ("Codec/Demuxer");
+ details->license = g_strdup ("LGPL");
+ details->description = g_strdup_printf ("FFMPEG %s demuxer",
+ in_plugin->name);
+ details->version = g_strdup (VERSION);
+ details->author = g_strdup ("The FFMPEG crew\n"
+ "Wim Taymans <wim.taymans@chello.be>\n"
+ "Ronald Bultje <rbultje@ronald.bitfreak.net>");
+ details->copyright = g_strdup ("(c) 2002-2003");
/* register the plugin with gstreamer */
factory = gst_element_factory_new(type_name,type,details);
g_return_val_if_fail(factory != NULL, FALSE);
- gst_element_factory_set_rank (factory, GST_ELEMENT_RANK_NONE);
+ /* typefind info */
+ type_definition = g_new0 (GstTypeDefinition, 1);
+ type_definition->name = g_strdup_printf ("fftype_%s",
+ in_plugin->name);
+ type_definition->mime = g_strdup (gst_caps_get_mime (sinkcaps));
+ type_definition->exts = g_strdup (in_plugin->extensions);
+ type_definition->typefindfunc = gst_ffmpegdemux_typefind;
+
+ type_factory = gst_type_factory_new (type_definition);
+
+ /* create a cache for these properties */
+ params = g_new0 (GstFFMpegDemuxClassParams, 1);
+ params->in_plugin = in_plugin;
+ params->typefind_feature = GST_PLUGIN_FEATURE (type_factory);
+ params->sinktempl = gst_pad_template_new ("sink", GST_PAD_SINK,
+ GST_PAD_ALWAYS,
+ sinkcaps, NULL);
+ gst_element_factory_add_pad_template (factory,
+ params->sinktempl);
+ params->audiosrctempl = gst_pad_template_new ("audio_%02d",
+ GST_PAD_SRC,
+ GST_PAD_SOMETIMES,
+ audiosrccaps, NULL);
+ gst_element_factory_add_pad_template (factory,
+ params->audiosrctempl);
+ params->videosrctempl = gst_pad_template_new ("video_%02d",
+ GST_PAD_SRC,
+ GST_PAD_SOMETIMES,
+ videosrccaps, NULL);
+ gst_element_factory_add_pad_template (factory,
+ params->videosrctempl);
+
+ g_hash_table_insert (global_plugins,
+ GINT_TO_POINTER (type),
+ (gpointer) params);
- gst_element_factory_add_pad_template (factory,
- GST_PAD_TEMPLATE_GET (gst_ffmpegdemux_sink_factory));
+ g_hash_table_insert (typefind,
+ (gpointer) type_factory,
+ (gpointer) params);
- gst_element_factory_add_pad_template (factory,
- GST_PAD_TEMPLATE_GET (gst_ffmpegdemux_video_src_factory));
- gst_element_factory_add_pad_template (factory,
- GST_PAD_TEMPLATE_GET (gst_ffmpegdemux_audio_src_factory));
+ gst_element_factory_set_rank (factory, GST_ELEMENT_RANK_MARGINAL);
/* The very last thing is to register the elementfactory with the plugin. */
gst_plugin_add_feature (plugin, GST_PLUGIN_FEATURE (factory));
+ gst_plugin_add_feature (plugin, GST_PLUGIN_FEATURE (type_factory));
next:
in_plugin = in_plugin->next;