diff --git a/ChangeLog b/ChangeLog index 0008844122..9190419662 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,24 @@ +2005-03-14 Ronald S. Bultje + + * ext/ffmpeg/gstffmpeg.c: (plugin_init): + Enable muxers (only mp4 muxer for now). + * ext/ffmpeg/gstffmpegcodecmap.c: + (gst_ffmpeg_formatid_get_codecids), (gst_ffmpeg_caps_to_codecid): + * ext/ffmpeg/gstffmpegcodecmap.h: + Fix a bunch of typos in codec-id lookup (false/true return value + mixup), add a codec-id list retrieval function (because ffmpeg + does not provide one). With that, we can make valid pad templates. + * ext/ffmpeg/gstffmpegmux.c: (gst_ffmpegmux_init), + (gst_ffmpegmux_connect), (gst_ffmpegmux_loop), + (gst_ffmpegmux_change_state), (gst_ffmpegmux_get_id_caps), + (gst_ffmpegmux_register): + Fix for whatever changed since I last tried this. Works for MP4 + muxing. + * ext/ffmpeg/gstffmpegprotocol.c: (gst_ffmpegdata_open), + (gst_ffmpegdata_peek), (gst_ffmpegdata_write), + (gst_ffmpegdata_seek), (gst_ffmpegdata_close): + Update obviously-untested write code... + 2005-03-13 Ronald S. Bultje * ext/ffmpeg/gstffmpegenc.c: (gst_ffmpegenc_chain_video): diff --git a/ext/ffmpeg/gstffmpeg.c b/ext/ffmpeg/gstffmpeg.c index eb3b4299c7..16b18ff19b 100644 --- a/ext/ffmpeg/gstffmpeg.c +++ b/ext/ffmpeg/gstffmpeg.c @@ -80,7 +80,7 @@ plugin_init (GstPlugin * plugin) gst_ffmpegenc_register (plugin); gst_ffmpegdec_register (plugin); gst_ffmpegdemux_register (plugin); - /*gst_ffmpegmux_register (plugin); */ + gst_ffmpegmux_register (plugin); gst_ffmpegcsp_register (plugin); register_protocol (&gstreamer_protocol); diff --git a/ext/ffmpeg/gstffmpegcodecmap.c b/ext/ffmpeg/gstffmpegcodecmap.c index 4c7e758dc4..4be5dd49a8 100644 --- a/ext/ffmpeg/gstffmpegcodecmap.c +++ b/ext/ffmpeg/gstffmpegcodecmap.c @@ -107,6 +107,8 @@ gst_ffmpeg_set_palette (GstCaps *caps, AVCodecContext *context) __VA_ARGS__, NULL) \ : \ gst_caps_new_simple (mimetype, \ + "rate", GST_TYPE_INT_RANGE, 8000, 96000, \ + "channels", GST_TYPE_INT_RANGE, 1, 2, \ __VA_ARGS__, NULL) /* Convert a FFMPEG codec ID and optional AVCodecContext @@ -1325,6 +1327,24 @@ gst_ffmpeg_formatid_to_caps (const gchar * format_name) return caps; } +gboolean +gst_ffmpeg_formatid_get_codecids (const gchar *format_name, + enum CodecID ** video_codec_list, enum CodecID ** audio_codec_list) +{ + if (!strcmp (format_name, "mp4")) { + static enum CodecID mp4_video_list[] = { CODEC_ID_MPEG4, CODEC_ID_NONE }; + static enum CodecID mp4_audio_list[] = { CODEC_ID_AAC, CODEC_ID_NONE }; + + *video_codec_list = mp4_video_list; + *audio_codec_list = mp4_audio_list; + } else { + GST_WARNING ("Format %s not found", format_name); + return FALSE; + } + + return TRUE; +} + /* Convert a GstCaps to a FFMPEG codec ID. Size et all * are omitted, that can be queried by the user itself, * we're not eating the GstCaps or anything @@ -1398,7 +1418,7 @@ gst_ffmpeg_caps_to_codecid (const GstCaps * caps, AVCodecContext * context) } else if (!strcmp (mimetype, "video/x-dv")) { gboolean sys_strm; - if (!gst_structure_get_boolean (structure, "systemstream", &sys_strm) && + if (gst_structure_get_boolean (structure, "systemstream", &sys_strm) && !sys_strm) { id = CODEC_ID_DVVIDEO; video = TRUE; @@ -1419,8 +1439,8 @@ gst_ffmpeg_caps_to_codecid (const GstCaps * caps, AVCodecContext * context) gboolean sys_strm; gint mpegversion; - if (!gst_structure_get_boolean (structure, "systemstream", &sys_strm) && - !gst_structure_get_int (structure, "mpegversion", &mpegversion) && + if (gst_structure_get_boolean (structure, "systemstream", &sys_strm) && + gst_structure_get_int (structure, "mpegversion", &mpegversion) && !sys_strm) { switch (mpegversion) { case 1: @@ -1468,7 +1488,7 @@ gst_ffmpeg_caps_to_codecid (const GstCaps * caps, AVCodecContext * context) switch (mpegversion) { case 2: /* ffmpeg uses faad for both... */ case 4: - id = CODEC_ID_MPEG4AAC; + id = CODEC_ID_AAC; break; case 1: if (gst_structure_get_int (structure, "layer", &layer)) { diff --git a/ext/ffmpeg/gstffmpegcodecmap.h b/ext/ffmpeg/gstffmpegcodecmap.h index 7226bdaa76..4ba828742b 100644 --- a/ext/ffmpeg/gstffmpegcodecmap.h +++ b/ext/ffmpeg/gstffmpegcodecmap.h @@ -87,6 +87,17 @@ gst_ffmpeg_caps_with_codectype (enum CodecType type, GstCaps * gst_ffmpeg_formatid_to_caps (const gchar *format_name); +/* + * _formatid_get_codecids () can be used to get the codecIDs + * (CODEC_ID_NONE-terminated list) that fit that specific + * output format. + */ + +gboolean +gst_ffmpeg_formatid_get_codecids (const gchar *format_name, + enum CodecID ** video_codec_list, + enum CodecID ** audio_codec_list); + /* * Since FFMpeg has such really cool and useful descriptions * of its codecs, we use our own... @@ -118,7 +129,8 @@ gst_ffmpeg_avpicture_fill (AVPicture * picture, */ int gst_ffmpeg_img_convert (AVPicture * dst, int dst_pix_fmt, - const AVPicture * src, int src_pix_fmt, int src_width, int src_height); + const AVPicture * src, int src_pix_fmt, + int src_width, int src_height); #endif /* __GST_FFMPEG_CODECMAP_H__ */ diff --git a/ext/ffmpeg/gstffmpegmux.c b/ext/ffmpeg/gstffmpegmux.c index ae858f28a2..e8320cf6cb 100644 --- a/ext/ffmpeg/gstffmpegmux.c +++ b/ext/ffmpeg/gstffmpegmux.c @@ -45,6 +45,8 @@ struct _GstFFMpegMux AVFormatContext *context; gboolean opened; + GstTagList *tags; + GstPad *sinkpads[MAX_STREAMS]; gint videopads, audiopads; GstBuffer *bufferqueue[MAX_STREAMS]; @@ -189,6 +191,8 @@ gst_ffmpegmux_init (GstFFMpegMux * ffmpegmux) ffmpegmux->videopads = 0; ffmpegmux->audiopads = 0; + + ffmpegmux->tags = NULL; } static void @@ -269,7 +273,6 @@ gst_ffmpegmux_connect (GstPad * pad, const GstCaps * caps) /*g_return_val_if_fail (ffmpegmux->opened == FALSE, GST_PAD_LINK_REFUSED); */ - for (i = 0; i < ffmpegmux->context->nb_streams; i++) { if (pad == ffmpegmux->sinkpads[i]) { break; @@ -318,6 +321,16 @@ gst_ffmpegmux_loop (GstElement * element) ffmpegmux->eos[i] = TRUE; gst_event_unref (event); break; + case GST_EVENT_TAG: + if (ffmpegmux->tags) { + gst_tag_list_insert (ffmpegmux->tags, + gst_event_tag_get_list (event), GST_TAG_MERGE_PREPEND); + } else { + ffmpegmux->tags = + gst_tag_list_copy (gst_event_tag_get_list (event)); + } + gst_event_unref (event); + break; default: gst_pad_event_default (pad, event); break; @@ -330,6 +343,8 @@ gst_ffmpegmux_loop (GstElement * element) /* open "file" (gstreamer protocol to next element) */ if (!ffmpegmux->opened) { + const GstTagList *iface_tags; + /* we do need all streams to have started capsnego, * or things will go horribly wrong */ for (i = 0; i < ffmpegmux->context->nb_streams; i++) { @@ -343,6 +358,58 @@ gst_ffmpegmux_loop (GstElement * element) "video" : "audio")); return; } + if (st->codec.codec_type == CODEC_TYPE_AUDIO) { + st->codec.frame_size = + st->codec.sample_rate * + GST_BUFFER_DURATION (ffmpegmux->bufferqueue[i]) / GST_SECOND; + } + } + + /* tags */ + iface_tags = gst_tag_setter_get_list (GST_TAG_SETTER (ffmpegmux)); + if (ffmpegmux->tags || iface_tags) { + GstTagList *tags; + gint i; + gchar *s; + + if (iface_tags && ffmpegmux->tags) { + gst_tag_list_merge (iface_tags, ffmpegmux->tags, + GST_TAG_MERGE_APPEND); + } else if (iface_tags) { + tags = gst_tag_list_copy (iface_tags); + } else { + tags = gst_tag_list_copy (ffmpegmux->tags); + } + + /* get the interesting ones */ + if (gst_tag_list_get_string (tags, GST_TAG_TITLE, &s)) { + strncpy (ffmpegmux->context->title, s, + sizeof (ffmpegmux->context->title)); + } + if (gst_tag_list_get_string (tags, GST_TAG_ARTIST, &s)) { + strncpy (ffmpegmux->context->author, s, + sizeof (ffmpegmux->context->author)); + } + if (gst_tag_list_get_string (tags, GST_TAG_COPYRIGHT, &s)) { + strncpy (ffmpegmux->context->copyright, s, + sizeof (ffmpegmux->context->copyright)); + } + if (gst_tag_list_get_string (tags, GST_TAG_COMMENT, &s)) { + strncpy (ffmpegmux->context->comment, s, + sizeof (ffmpegmux->context->comment)); + } + if (gst_tag_list_get_string (tags, GST_TAG_ALBUM, &s)) { + strncpy (ffmpegmux->context->album, s, + sizeof (ffmpegmux->context->album)); + } + if (gst_tag_list_get_string (tags, GST_TAG_GENRE, &s)) { + strncpy (ffmpegmux->context->genre, s, + sizeof (ffmpegmux->context->genre)); + } + if (gst_tag_list_get_uint (tags, GST_TAG_TRACK_NUMBER, &i)) { + ffmpegmux->context->track = i; + } + gst_tag_list_free (tags); } if (url_fopen (&ffmpegmux->context->pb, @@ -352,7 +419,7 @@ gst_ffmpegmux_loop (GstElement * element) return; } - if (av_set_parameters (ffmpegmux->context, NULL)) { + if (av_set_parameters (ffmpegmux->context, NULL) < 0) { GST_ELEMENT_ERROR (element, LIBRARY, INIT, (NULL), ("Failed to initialize muxer")); return; @@ -362,7 +429,11 @@ gst_ffmpegmux_loop (GstElement * element) ffmpegmux->opened = TRUE; /* now open the mux format */ - av_write_header (ffmpegmux->context); + if (av_write_header (ffmpegmux->context) < 0) { + GST_ELEMENT_ERROR (element, LIBRARY, SETTINGS, (NULL), + ("Failed to write file header - check codec settings")); + return; + } } /* take the one with earliest timestamp, @@ -401,6 +472,7 @@ gst_ffmpegmux_loop (GstElement * element) /* set time */ pkt.pts = GST_BUFFER_TIMESTAMP (buf) * AV_TIME_BASE / GST_SECOND; + pkt.dts = pkt.pts; pkt.data = GST_BUFFER_DATA (buf); pkt.size = GST_BUFFER_SIZE (buf); pkt.stream_index = bufnum; @@ -430,6 +502,10 @@ gst_ffmpegmux_change_state (GstElement * element) switch (transition) { case GST_STATE_PAUSED_TO_READY: + if (ffmpegmux->tags) { + gst_tag_list_free (ffmpegmux->tags); + ffmpegmux->tags = NULL; + } if (ffmpegmux->opened) { url_fclose (&ffmpegmux->context->pb); ffmpegmux->opened = FALSE; @@ -443,6 +519,20 @@ gst_ffmpegmux_change_state (GstElement * element) return GST_STATE_SUCCESS; } +GstCaps * +gst_ffmpegmux_get_id_caps (enum CodecID * id_list) +{ + GstCaps *caps, *t; + gint i; + + caps = gst_caps_new_empty (); + for (i = 0; id_list[i] != CODEC_ID_NONE; i++) { + if ((t = gst_ffmpeg_codecid_to_caps (id_list[i], NULL, TRUE))) + gst_caps_append (caps, t); + } + + return caps; +} gboolean gst_ffmpegmux_register (GstPlugin * plugin) @@ -458,6 +548,9 @@ gst_ffmpegmux_register (GstPlugin * plugin) 0, (GInstanceInitFunc) gst_ffmpegmux_init, }; + static const GInterfaceInfo tag_setter_info = { + NULL, NULL, NULL + }; GType type; AVOutputFormat *in_plugin; GstFFMpegMuxClassParams *params; @@ -471,35 +564,20 @@ gst_ffmpegmux_register (GstPlugin * plugin) gchar *type_name; gchar *p; GstCaps *srccaps, *audiosinkcaps, *videosinkcaps; + enum CodecID *video_ids = NULL, *audio_ids = NULL; /* Try to find the caps that belongs here */ srccaps = gst_ffmpeg_formatid_to_caps (in_plugin->name); if (!srccaps) { 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 */ - audiosinkcaps = gst_caps_new_empty (); - videosinkcaps = gst_caps_new_empty (); - for (in_codec = first_avcodec; in_codec != NULL; in_codec = in_codec->next) { - GstCaps *temp = gst_ffmpeg_codecid_to_caps (in_codec->id, NULL, TRUE); - - if (!temp) { - continue; - } - switch (in_codec->type) { - case CODEC_TYPE_VIDEO: - gst_caps_append (videosinkcaps, temp); - break; - case CODEC_TYPE_AUDIO: - gst_caps_append (audiosinkcaps, temp); - break; - default: - gst_caps_free (temp); - break; - } + if (!gst_ffmpeg_formatid_get_codecids (in_plugin->name, + &video_ids, &audio_ids)) { + gst_caps_free (srccaps); + goto next; } + videosinkcaps = video_ids ? gst_ffmpegmux_get_id_caps (video_ids) : NULL; + audiosinkcaps = audio_ids ? gst_ffmpegmux_get_id_caps (audio_ids) : NULL; /* construct the type */ type_name = g_strdup_printf ("ffmux_%s", in_plugin->name); @@ -515,6 +593,11 @@ gst_ffmpegmux_register (GstPlugin * plugin) /* if it's already registered, drop it */ if (g_type_from_name (type_name)) { g_free (type_name); + gst_caps_free (srccaps); + if (audiosinkcaps) + gst_caps_free (audiosinkcaps); + if (videosinkcaps) + gst_caps_free (videosinkcaps); goto next; } @@ -530,8 +613,15 @@ gst_ffmpegmux_register (GstPlugin * plugin) /* create the type now */ type = g_type_register_static (GST_TYPE_ELEMENT, type_name, &typeinfo, 0); + g_type_add_interface_static (type, GST_TYPE_TAG_SETTER, + &tag_setter_info); if (!gst_element_register (plugin, type_name, GST_RANK_NONE, type)) { g_free (type_name); + gst_caps_free (srccaps); + if (audiosinkcaps) + gst_caps_free (audiosinkcaps); + if (videosinkcaps) + gst_caps_free (videosinkcaps); return FALSE; } diff --git a/ext/ffmpeg/gstffmpegprotocol.c b/ext/ffmpeg/gstffmpegprotocol.c index 69a6aea6e7..2772b5330e 100644 --- a/ext/ffmpeg/gstffmpegprotocol.c +++ b/ext/ffmpeg/gstffmpegprotocol.c @@ -39,7 +39,6 @@ struct _GstProtocolInfo { GstPad *pad; - int flags; GstByteStream *bs; gboolean eos; }; @@ -53,7 +52,6 @@ gst_ffmpegdata_open (URLContext * h, const char *filename, int flags) GST_LOG ("Opening %s", filename); info = g_new0 (GstProtocolInfo, 1); - info->flags = flags; /* we don't support R/W together */ if (flags != URL_RDONLY && flags != URL_WRONLY) { @@ -85,7 +83,6 @@ gst_ffmpegdata_open (URLContext * h, const char *filename, int flags) h->priv_data = (void *) info; h->is_streamed = FALSE; - h->flags = 0; h->max_packet_size = 0; return 0; @@ -102,7 +99,7 @@ gst_ffmpegdata_peek (URLContext * h, unsigned char *buf, int size) info = (GstProtocolInfo *) h->priv_data; - g_return_val_if_fail (info->flags == URL_RDONLY, AVERROR_IO); + g_return_val_if_fail (h->flags == URL_RDONLY, AVERROR_IO); bs = info->bs; @@ -202,7 +199,7 @@ gst_ffmpegdata_write (URLContext * h, unsigned char *buf, int size) info = (GstProtocolInfo *) h->priv_data; - g_return_val_if_fail (info->flags == URL_WRONLY, -EIO); + g_return_val_if_fail (h->flags == URL_WRONLY, -EIO); /* create buffer and push data further */ outbuf = gst_buffer_new_and_alloc (size); @@ -225,20 +222,22 @@ gst_ffmpegdata_seek (URLContext * h, offset_t pos, int whence) info = (GstProtocolInfo *) h->priv_data; - /* get data (typefind hack) */ - if (gst_bytestream_tell (info->bs) != gst_bytestream_length (info->bs)) { - gchar buf; - gst_ffmpegdata_peek (h, &buf, 1); - } + if (h->flags == URL_RDONLY) { + /* get data (typefind hack) */ + if (gst_bytestream_tell (info->bs) != gst_bytestream_length (info->bs)) { + gchar buf; + gst_ffmpegdata_peek (h, &buf, 1); + } - /* hack in ffmpeg to get filesize... */ - if (whence == SEEK_END && pos == -1) - return gst_bytestream_length (info->bs) - 1; - else if (whence == SEEK_END && pos == 0) - return gst_bytestream_length (info->bs); - /* another hack to get the current position... */ - else if (whence == SEEK_CUR && pos == 0) - return gst_bytestream_tell (info->bs); + /* hack in ffmpeg to get filesize... */ + if (whence == SEEK_END && pos == -1) + return gst_bytestream_length (info->bs) - 1; + else if (whence == SEEK_END && pos == 0) + return gst_bytestream_length (info->bs); + /* another hack to get the current position... */ + else if (whence == SEEK_CUR && pos == 0) + return gst_bytestream_tell (info->bs); + } switch (whence) { case SEEK_SET: @@ -255,7 +254,7 @@ gst_ffmpegdata_seek (URLContext * h, offset_t pos, int whence) break; } - switch (info->flags) { + switch (h->flags) { case URL_RDONLY: { GstEvent *event; guint8 *data; @@ -303,7 +302,8 @@ gst_ffmpegdata_seek (URLContext * h, offset_t pos, int whence) } case URL_WRONLY: - gst_pad_push (info->pad, GST_DATA (gst_event_new_seek (seek_type, pos))); + gst_pad_push (info->pad, + GST_DATA (gst_event_new_seek (seek_type | GST_FORMAT_BYTES, pos))); /* this is screwy because there might be queues or scheduler-queued * buffers... Argh! */ if (whence == SEEK_SET) { @@ -331,7 +331,7 @@ gst_ffmpegdata_close (URLContext * h) GST_LOG ("Closing file"); - switch (info->flags) { + switch (h->flags) { case URL_WRONLY:{ /* send EOS - that closes down the stream */ GstEvent *event = gst_event_new (GST_EVENT_EOS);