diff --git a/ChangeLog b/ChangeLog index cacb9b4a8a..3b21a0e209 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,15 @@ +2006-02-12 Edward Hervey + + * ext/ffmpeg/gstffmpegdemux.c: (gst_ffmpegdemux_init), + (gst_ffmpegdemux_close), (gst_ffmpegdemux_handle_seek), + (gst_ffmpegdemux_add), (my_safe_copy), (gst_ffmpegdemux_read_tags), + (gst_ffmpegdemux_open), (gst_ffmpegdemux_loop): + Add Tag support and keyframe seeking (for those formats where ffmpeg + actually fills in the index). + * ext/ffmpeg/gstffmpegprotocol.c: (gst_ffmpegdata_peek), + (gst_ffmpegdata_read), (gst_ffmpegdata_seek): + Add support for size querying. + 2006-02-11 Thomas Vander Stichele * ext/ffmpeg/gstffmpeg.c: diff --git a/ext/ffmpeg/gstffmpegdemux.c b/ext/ffmpeg/gstffmpegdemux.c index 98f76d1cf3..e31d542a21 100644 --- a/ext/ffmpeg/gstffmpegdemux.c +++ b/ext/ffmpeg/gstffmpegdemux.c @@ -53,6 +53,12 @@ struct _GstFFMpegDemux guint64 last_ts[MAX_STREAMS]; gint videopads, audiopads; + /* Id of the first video stream */ + gint videostreamid; + + /* time of the first media frame */ + gint64 timeoffset; + /* segment stuff */ /* TODO : replace with GstSegment */ gdouble segment_rate; @@ -60,7 +66,9 @@ struct _GstFFMpegDemux /* GST_FORMAT_TIME */ gint64 segment_start; gint64 segment_stop; + GstEvent *seek_event; + gint64 seek_start; }; typedef struct _GstFFMpegDemuxClassParams @@ -220,11 +228,15 @@ gst_ffmpegdemux_init (GstFFMpegDemux * demux) demux->videopads = 0; demux->audiopads = 0; + demux->videostreamid = -1; + demux->timeoffset = 0; + demux->segment_rate = 1.0; demux->segment_flags = 0; demux->segment_start = -1; demux->segment_stop = -1; demux->seek_event = NULL; + demux->seek_start = 0; } static void @@ -247,6 +259,16 @@ gst_ffmpegdemux_close (GstFFMpegDemux * demux) demux->videopads = 0; demux->audiopads = 0; + demux->videostreamid = -1; + demux->timeoffset = 0; + + demux->segment_rate = 1.0; + demux->segment_flags = 0; + demux->segment_start = -1; + demux->segment_stop = -1; + demux->seek_event = NULL; + demux->seek_start = 0; + /* close demuxer context from ffmpeg */ av_close_input_file (demux->context); demux->context = NULL; @@ -284,16 +306,47 @@ gst_ffmpegdemux_handle_seek (GstFFMpegDemux * demux, gboolean update) keyframe = demux->segment_flags & GST_SEEK_FLAG_KEY_UNIT; if (flush) { + GST_LOG_OBJECT (demux, "sending flush_start"); for (stream = 0; stream < demux->context->nb_streams; stream++) { gst_pad_push_event (demux->srcpads[stream], gst_event_new_flush_start()); } gst_pad_push_event (demux->sinkpad, gst_event_new_flush_start ()); - } else + } else { + GST_LOG_OBJECT (demux, "pausing task"); gst_pad_pause_task (demux->sinkpad); + } GST_PAD_STREAM_LOCK (demux->sinkpad); + GST_DEBUG_OBJECT (demux, "after PAD_STREAM_LOCK"); + + /* by default, the seek position is the segment_start */ + demux->seek_start = demux->segment_start; + + /* if index is available, find previous keyframe */ + if ((demux->videostreamid != -1) && (demux->context->index_built)) { + gint keyframeidx; + + GST_LOG_OBJECT (demux, "looking for keyframe in ffmpeg for time %lld", + demux->segment_start / (GST_SECOND / AV_TIME_BASE)); + keyframeidx = av_index_search_timestamp + (demux->context->streams[demux->videostreamid], + demux->segment_start / (GST_SECOND / AV_TIME_BASE), + AVSEEK_FLAG_BACKWARD); + GST_LOG_OBJECT (demux, "keyframeidx:%d", keyframeidx); + if (keyframeidx >= 0) { + gint64 idxtimestamp = demux->context->streams[demux->videostreamid]->index_entries[keyframeidx].timestamp; + GST_LOG_OBJECT (demux, "Found a keyframe at ffmpeg idx:%d timestamp :%lld", + keyframeidx, idxtimestamp); + demux->seek_start = idxtimestamp * (GST_SECOND / AV_TIME_BASE); + } + } else { + GST_LOG_OBJECT (demux, "no videostream or index not built"); + } + if (keyframe) + demux->segment_start = demux->seek_start; + GST_DEBUG_OBJECT (demux, "Creating new segment (%"GST_TIME_FORMAT" / %"GST_TIME_FORMAT, GST_TIME_ARGS (demux->segment_start), GST_TIME_ARGS (demux->segment_stop)); @@ -568,6 +621,7 @@ gst_ffmpegdemux_add (GstFFMpegDemux * demux, AVStream * stream) case CODEC_TYPE_VIDEO: templ = oclass->videosrctempl; num = demux->videopads++; + demux->videostreamid = stream->index; break; case CODEC_TYPE_AUDIO: templ = oclass->audiosrctempl; @@ -614,6 +668,92 @@ gst_ffmpegdemux_add (GstFFMpegDemux * demux, AVStream * stream) return TRUE; } +static gchar * +my_safe_copy (gchar * input) +{ + gchar * output; + + if (!(g_utf8_validate (input, -1, NULL))) { + output = g_convert (input, strlen(input), + "UTF-8", "ISO-8859-1", + NULL, NULL, NULL); + } else { + output = g_strdup (input); + } +} + +static GstTagList * +gst_ffmpegdemux_read_tags (GstFFMpegDemux * demux) +{ + GstTagList *tlist; + gboolean hastag = FALSE; + + tlist = gst_tag_list_new (); + + if (*demux->context->title) { + gst_tag_list_add (tlist, GST_TAG_MERGE_REPLACE, + GST_TAG_TITLE, + my_safe_copy (demux->context->title), + NULL); + hastag = TRUE; + } + if (*demux->context->author) { + gst_tag_list_add (tlist, GST_TAG_MERGE_REPLACE, + GST_TAG_ARTIST, + my_safe_copy (demux->context->author), + NULL); + hastag = TRUE; + } + if (*demux->context->copyright) { + gst_tag_list_add (tlist, GST_TAG_MERGE_REPLACE, + GST_TAG_COPYRIGHT, + my_safe_copy (demux->context->copyright), + NULL); + hastag = TRUE; + } + if (*demux->context->comment) { + gst_tag_list_add (tlist, GST_TAG_MERGE_REPLACE, + GST_TAG_COMMENT, + my_safe_copy (demux->context->comment), + NULL); + hastag = TRUE; + } + if (*demux->context->album) { + gst_tag_list_add (tlist, GST_TAG_MERGE_REPLACE, + GST_TAG_ALBUM, + my_safe_copy (demux->context->album), + NULL); + hastag = TRUE; + } + if (demux->context->track) { + gst_tag_list_add (tlist, GST_TAG_MERGE_REPLACE, + GST_TAG_TRACK_NUMBER, + demux->context->track, + NULL); + hastag = TRUE; + } + if (*demux->context->genre) { + gst_tag_list_add (tlist, GST_TAG_MERGE_REPLACE, + GST_TAG_GENRE, + my_safe_copy (demux->context->genre), + NULL); + hastag = TRUE; + } + if (demux->context->year) { + gst_tag_list_add (tlist, GST_TAG_MERGE_REPLACE, + GST_TAG_DATE, + g_date_new_dmy(1, 1, demux->context->year), + NULL); + hastag = TRUE; + } + + if (!hastag) { + gst_tag_list_free (tlist); + tlist = NULL; + } + return tlist; +} + static gboolean gst_ffmpegdemux_open (GstFFMpegDemux * demux) { @@ -621,6 +761,7 @@ gst_ffmpegdemux_open (GstFFMpegDemux * demux) (GstFFMpegDemuxClass *) G_OBJECT_GET_CLASS (demux); gchar *location; gint res; + GstTagList *tags; /* to be sure... */ gst_ffmpegdemux_close (demux); @@ -648,6 +789,36 @@ gst_ffmpegdemux_open (GstFFMpegDemux * demux) demux->handled[res] = TRUE; } + gst_element_no_more_pads (GST_ELEMENT (demux)); + + + /* grab the tags */ + tags = gst_ffmpegdemux_read_tags(demux); + if (tags) { + gst_element_post_message (GST_ELEMENT (demux), + gst_message_new_tag (GST_OBJECT (demux), + tags)); + } + + /* remember initial start position and shift start/stop */ + demux->timeoffset = demux->context->start_time * (GST_SECOND / AV_TIME_BASE ); + demux->segment_start = 0; + demux->segment_stop = demux->context->duration * (GST_SECOND / AV_TIME_BASE ); + + /* Send newsegment on all src pads */ + for (res = 0; res < demux->context->nb_streams; res++) { + AVStream *stream; + + GST_DEBUG_OBJECT (demux, "sending newsegment start:%"GST_TIME_FORMAT" duration:%"GST_TIME_FORMAT, + GST_TIME_ARGS (demux->segment_start), + GST_TIME_ARGS (demux->segment_stop)); + + gst_pad_push_event(demux->srcpads[res], + gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_TIME, + demux->segment_start, demux->segment_stop, + demux->segment_start)); + } + demux->opened = TRUE; return TRUE; @@ -694,52 +865,20 @@ gst_ffmpegdemux_loop (GstPad * pad) ret = GST_FLOW_ERROR; goto pause; } - gst_element_no_more_pads (GST_ELEMENT (demux)); - - /* Calculate min start and maximum end */ - for (res = 0; res < demux->context->nb_streams; res++) { - gint64 start_time, duration, end; - AVStream *stream; - - stream = demux->context->streams[res]; - - start_time = gst_ffmpeg_time_ff_to_gst (stream->start_time, stream->time_base); - duration = gst_ffmpeg_time_ff_to_gst (stream->duration, stream->time_base); - if (start_time == GST_CLOCK_TIME_NONE) - start_time = 0; - if (duration == GST_CLOCK_TIME_NONE) - end = GST_CLOCK_TIME_NONE; - else - end = start_time + duration; - - if ((start_time < demux->segment_start) || (demux->segment_start == -1)) - demux->segment_start = start_time; - - if ((end > demux->segment_stop) || (demux->segment_stop == -1)) - demux->segment_stop = end; - } - /* Send newsegment on all src pads */ - for (res = 0; res < demux->context->nb_streams; res++) { - AVStream *stream; - - GST_DEBUG_OBJECT (demux, "sending newsegment start:%"GST_TIME_FORMAT" duration:%"GST_TIME_FORMAT, - GST_TIME_ARGS (demux->segment_start), - GST_TIME_ARGS (demux->segment_stop)); - - gst_pad_push_event(demux->srcpads[res], - gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_TIME, - demux->segment_start, demux->segment_stop, - demux->segment_start)); - } } + /* pending seek */ if (demux->seek_event) { - /* pending seek */ - GST_DEBUG_OBJECT (demux, "About to call av_seek_frame (context, -1, %lld, 0)", - gst_ffmpeg_time_gst_to_ff (demux->segment_start, demux->context->streams[0]->time_base)); - if ((av_seek_frame (demux->context, -1, - gst_ffmpeg_time_gst_to_ff (demux->segment_start, demux->context->streams[0]->time_base), 0)) < 0) { - GST_WARNING_OBJECT (demux, "Call to av_seek_frame failed"); + gint seekret; + + GST_DEBUG_OBJECT (demux, "About to call av_seek_frame (context, %d, %lld, 0) for time %"GST_TIME_FORMAT, + -1, + gst_ffmpeg_time_gst_to_ff (demux->seek_start + demux->timeoffset, demux->context->streams[0]->time_base), + GST_TIME_ARGS (demux->seek_start + demux->timeoffset)); + if (((seekret = av_seek_frame + (demux->context, -1, + gst_ffmpeg_time_gst_to_ff (demux->seek_start + demux->timeoffset, demux->context->streams[0]->time_base), 0))) < 0) { + GST_WARNING_OBJECT (demux, "Call to av_seek_frame failed : %d", seekret); ret = GST_FLOW_ERROR; goto pause; } @@ -795,7 +934,7 @@ gst_ffmpegdemux_loop (GstPad * pad) memcpy (GST_BUFFER_DATA (outbuf), pkt.data, pkt.size); GST_BUFFER_TIMESTAMP (outbuf) = gst_ffmpeg_time_ff_to_gst (pkt.pts, - demux->context->streams[pkt.stream_index]->time_base); + demux->context->streams[pkt.stream_index]->time_base) - demux->timeoffset; if (GST_BUFFER_TIMESTAMP_IS_VALID (outbuf)) demux->last_ts[stream->index] = GST_BUFFER_TIMESTAMP (outbuf); if (!(pkt.flags & PKT_FLAG_KEY)) @@ -814,7 +953,7 @@ gst_ffmpegdemux_loop (GstPad * pad) GST_WARNING_OBJECT (demux, "No pad from stream %d", pkt.stream_index); } - + pkt.destruct (&pkt); return; diff --git a/ext/ffmpeg/gstffmpegprotocol.c b/ext/ffmpeg/gstffmpegprotocol.c index 2897bef313..656d280b91 100644 --- a/ext/ffmpeg/gstffmpegprotocol.c +++ b/ext/ffmpeg/gstffmpegprotocol.c @@ -97,17 +97,22 @@ gst_ffmpegdata_peek (URLContext * h, unsigned char *buf, int size) { GstProtocolInfo *info; GstBuffer *inbuf = NULL; - int total; + GstFlowReturn ret; + int total = 0; g_return_val_if_fail (h->flags == URL_RDONLY, AVERROR_IO); info = (GstProtocolInfo *) h->priv_data; - if (gst_pad_pull_range(info->pad, info->offset, (guint) size, &inbuf) != GST_FLOW_OK) { - total = 0; + ret = gst_pad_pull_range(info->pad, info->offset, (guint) size, &inbuf); + + if ((ret == GST_FLOW_OK) || (ret == GST_FLOW_UNEXPECTED)) { + if (inbuf) { + total = (gint) GST_BUFFER_SIZE (inbuf); + memcpy (buf, GST_BUFFER_DATA (inbuf), total); + gst_buffer_unref (inbuf); + } } else { - total = (gint) GST_BUFFER_SIZE (inbuf); - memcpy (buf, GST_BUFFER_DATA (inbuf), total); - gst_buffer_unref (inbuf); + return -1; } return total; @@ -124,7 +129,8 @@ gst_ffmpegdata_read (URLContext * h, unsigned char *buf, int size) GST_DEBUG ("Reading %d bytes of data at position %lld", size, info->offset); res = gst_ffmpegdata_peek(h, buf, size); - info->offset += res; + if (res >= 0) + info->offset += res; GST_DEBUG ("Returning %d bytes", res); @@ -169,6 +175,8 @@ gst_ffmpegdata_seek (URLContext * h, offset_t pos, int whence) info = (GstProtocolInfo *) h->priv_data; + /* TODO : if we are push-based, we need to return sensible info */ + switch (h->flags) { case URL_RDONLY: { @@ -181,7 +189,16 @@ gst_ffmpegdata_seek (URLContext * h, offset_t pos, int whence) info->offset += pos; break; case SEEK_END: - GST_WARNING ("Can't handle SEEK_END yet"); + /* ffmpeg wants to know the current end position in bytes ! */ + { + GstFormat format = GST_FORMAT_BYTES; + gint64 duration; + + if (gst_pad_is_linked (info->pad)) + if (gst_pad_query_duration (GST_PAD_PEER (info->pad), &format, &duration)) + info->offset = ((guint64) duration) + pos; + } + break; default: break; }