Adds a first draft of an FLV demuxer.

Original commit message from CVS:
2007-07-19  Julien MOUTTE  <julien@moutte.net>

* configure.ac:
* gst/flv/Makefile.am:
* gst/flv/gstflvdemux.c: (gst_flv_demux_flush),
(gst_flv_demux_cleanup), (gst_flv_demux_chain),
(gst_flv_demux_pull_tag), (gst_flv_demux_pull_header),
(gst_flv_demux_seek_to_prev_keyframe), (gst_flv_demux_loop),
(gst_flv_demux_sink_activate),
(gst_flv_demux_sink_activate_push),
(gst_flv_demux_sink_activate_pull), (gst_flv_demux_sink_event),
(gst_flv_demux_change_state), (gst_flv_demux_dispose),
(gst_flv_demux_base_init), (gst_flv_demux_class_init),
(gst_flv_demux_init), (plugin_init):
* gst/flv/gstflvdemux.h:
* gst/flv/gstflvparse.c: (FLV_GET_BEUI24), (FLV_GET_STRING),
(gst_flv_demux_query_types), (gst_flv_demux_query),
(gst_flv_parse_metadata_item), (gst_flv_parse_tag_script),
(gst_flv_parse_tag_audio), (gst_flv_parse_tag_video),
(gst_flv_parse_tag_type), (gst_flv_parse_header):
* gst/flv/gstflvparse.h: Adds a first draft of an FLV demuxer.
It does not do seeking yet, it supports pull and push mode so
YES
you can use it to play youtube videos directly from an HTTP uri.
Not so much testing done yet but it parses metadata, reply to
duration queries, etc...
This commit is contained in:
Julien Moutte 2007-07-19 15:05:30 +00:00
parent 6cbacb8a14
commit faea34bf98
7 changed files with 1660 additions and 0 deletions

View file

@ -1,3 +1,28 @@
2007-07-19 Julien MOUTTE <julien@moutte.net>
* configure.ac:
* gst/flv/Makefile.am:
* gst/flv/gstflvdemux.c: (gst_flv_demux_flush),
(gst_flv_demux_cleanup), (gst_flv_demux_chain),
(gst_flv_demux_pull_tag), (gst_flv_demux_pull_header),
(gst_flv_demux_seek_to_prev_keyframe), (gst_flv_demux_loop),
(gst_flv_demux_sink_activate), (gst_flv_demux_sink_activate_push),
(gst_flv_demux_sink_activate_pull), (gst_flv_demux_sink_event),
(gst_flv_demux_change_state), (gst_flv_demux_dispose),
(gst_flv_demux_base_init), (gst_flv_demux_class_init),
(gst_flv_demux_init), (plugin_init):
* gst/flv/gstflvdemux.h:
* gst/flv/gstflvparse.c: (FLV_GET_BEUI24), (FLV_GET_STRING),
(gst_flv_demux_query_types), (gst_flv_demux_query),
(gst_flv_parse_metadata_item), (gst_flv_parse_tag_script),
(gst_flv_parse_tag_audio), (gst_flv_parse_tag_video),
(gst_flv_parse_tag_type), (gst_flv_parse_header):
* gst/flv/gstflvparse.h: Adds a first draft of an FLV demuxer.
It does not do seeking yet, it supports pull and push mode so YES
you can use it to play youtube videos directly from an HTTP uri.
Not so much testing done yet but it parses metadata, reply to
duration queries, etc...
2007-07-19 Stefan Kost <ensonic@users.sf.net>
* tests/check/Makefile.am:

View file

@ -85,6 +85,7 @@ GST_PLUGINS_ALL="\
deinterlace \
equalizer \
filter \
flv \
freeze \
h264parse \
interleave \
@ -980,6 +981,7 @@ gst/cdxaparse/Makefile
gst/deinterlace/Makefile
gst/equalizer/Makefile
gst/filter/Makefile
gst/flv/Makefile
gst/freeze/Makefile
gst/h264parse/Makefile
gst/interleave/Makefile

9
gst/flv/Makefile.am Normal file
View file

@ -0,0 +1,9 @@
plugin_LTLIBRARIES = libgstflvdemux.la
libgstflvdemux_la_CFLAGS = ${GST_CFLAGS}
libgstflvdemux_la_LIBADD = $(GST_BASE_LIBS)
libgstflvdemux_la_LDFLAGS = ${GST_PLUGIN_LDFLAGS}
libgstflvdemux_la_SOURCES = gstflvdemux.c gstflvparse.c
noinst_HEADERS = gstflvdemux.h gstflvparse.h

675
gst/flv/gstflvdemux.c Normal file
View file

@ -0,0 +1,675 @@
/* GStreamer
* Copyright (C) <2007> Julien Moutte <julien@moutte.net>
*
* 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.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstflvdemux.h"
#include "gstflvparse.h"
#include <string.h>
static GstStaticPadTemplate flv_sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("video/x-flv")
);
static GstStaticPadTemplate audio_src_template =
GST_STATIC_PAD_TEMPLATE ("audio",
GST_PAD_SRC,
GST_PAD_SOMETIMES,
GST_STATIC_CAPS_ANY);
static GstStaticPadTemplate video_src_template =
GST_STATIC_PAD_TEMPLATE ("video",
GST_PAD_SRC,
GST_PAD_SOMETIMES,
GST_STATIC_CAPS_ANY);
static GstElementDetails flv_demux_details = {
"FLV Demuxer",
"Codec/Demuxer",
"Demux FLV feeds into digital streams",
"Julien Moutte <julien@moutte.net>"
};
GST_DEBUG_CATEGORY (flvdemux_debug);
#define GST_CAT_DEFAULT flvdemux_debug
GST_BOILERPLATE (GstFLVDemux, gst_flv_demux, GstElement, GST_TYPE_ELEMENT);
#define FLV_HEADER_SIZE 13
#define FLV_TAG_TYPE_SIZE 4
static void
gst_flv_demux_flush (GstFLVDemux * demux, gboolean discont)
{
GST_DEBUG_OBJECT (demux, "flushing queued data in the FLV demuxer");
gst_adapter_clear (demux->adapter);
demux->audio_need_discont = TRUE;
demux->video_need_discont = TRUE;
}
static void
gst_flv_demux_cleanup (GstFLVDemux * demux)
{
GST_DEBUG_OBJECT (demux, "cleaning up FLV demuxer");
}
static GstFlowReturn
gst_flv_demux_chain (GstPad * pad, GstBuffer * buffer)
{
GstFlowReturn ret = GST_FLOW_OK;
GstFLVDemux *demux = NULL;
demux = GST_FLV_DEMUX (gst_pad_get_parent (pad));
gst_adapter_push (demux->adapter, buffer);
parse:
switch (demux->state) {
case FLV_STATE_HEADER:
{
if (gst_adapter_available (demux->adapter) >= FLV_HEADER_SIZE) {
const guint8 *data;
data = gst_adapter_peek (demux->adapter, FLV_HEADER_SIZE);
ret = gst_flv_parse_header (demux, data, FLV_HEADER_SIZE);
gst_adapter_flush (demux->adapter, FLV_HEADER_SIZE);
demux->state = FLV_STATE_TAG_TYPE;
goto parse;
} else {
goto beach;
}
}
case FLV_STATE_TAG_TYPE:
{
if (gst_adapter_available (demux->adapter) >= FLV_TAG_TYPE_SIZE) {
const guint8 *data;
data = gst_adapter_peek (demux->adapter, FLV_TAG_TYPE_SIZE);
ret = gst_flv_parse_tag_type (demux, data, FLV_TAG_TYPE_SIZE);
gst_adapter_flush (demux->adapter, FLV_TAG_TYPE_SIZE);
goto parse;
} else {
goto beach;
}
}
case FLV_STATE_TAG_VIDEO:
{
if (gst_adapter_available (demux->adapter) >= demux->tag_size) {
const guint8 *data;
data = gst_adapter_peek (demux->adapter, demux->tag_size);
ret = gst_flv_parse_tag_video (demux, data, demux->tag_size);
gst_adapter_flush (demux->adapter, demux->tag_size);
demux->state = FLV_STATE_TAG_TYPE;
goto parse;
} else {
goto beach;
}
}
case FLV_STATE_TAG_AUDIO:
{
if (gst_adapter_available (demux->adapter) >= demux->tag_size) {
const guint8 *data;
data = gst_adapter_peek (demux->adapter, demux->tag_size);
ret = gst_flv_parse_tag_audio (demux, data, demux->tag_size);
gst_adapter_flush (demux->adapter, demux->tag_size);
demux->state = FLV_STATE_TAG_TYPE;
goto parse;
} else {
goto beach;
}
}
case FLV_STATE_TAG_SCRIPT:
{
if (gst_adapter_available (demux->adapter) >= demux->tag_size) {
const guint8 *data;
data = gst_adapter_peek (demux->adapter, demux->tag_size);
ret = gst_flv_parse_tag_script (demux, data, demux->tag_size);
gst_adapter_flush (demux->adapter, demux->tag_size);
demux->state = FLV_STATE_TAG_TYPE;
goto parse;
} else {
goto beach;
}
}
default:
GST_DEBUG_OBJECT (demux, "unexpected demuxer state");
}
beach:
gst_object_unref (demux);
return ret;
}
static GstFlowReturn
gst_flv_demux_pull_tag (GstPad * pad, GstFLVDemux * demux)
{
GstBuffer *buffer = NULL;
GstFlowReturn ret = GST_FLOW_OK;
/* Get the first 4 bytes to identify tag type and size */
ret = gst_pad_pull_range (pad, demux->offset, FLV_TAG_TYPE_SIZE, &buffer);
if (G_UNLIKELY (ret != GST_FLOW_OK)) {
GST_WARNING_OBJECT (demux, "failed when pulling %d bytes",
FLV_TAG_TYPE_SIZE);
goto beach;
}
if (G_UNLIKELY (buffer && GST_BUFFER_SIZE (buffer) != FLV_TAG_TYPE_SIZE)) {
GST_WARNING_OBJECT (demux, "partial pull got %d when expecting %d",
GST_BUFFER_SIZE (buffer), FLV_TAG_TYPE_SIZE);
gst_buffer_unref (buffer);
ret = GST_FLOW_UNEXPECTED;
goto beach;
}
/* Identify tag type */
ret = gst_flv_parse_tag_type (demux, GST_BUFFER_DATA (buffer),
GST_BUFFER_SIZE (buffer));
gst_buffer_unref (buffer);
/* Jump over tag type + size */
demux->offset += FLV_TAG_TYPE_SIZE;
/* Pull the whole tag */
ret = gst_pad_pull_range (pad, demux->offset, demux->tag_size, &buffer);
if (G_UNLIKELY (ret != GST_FLOW_OK)) {
GST_WARNING_OBJECT (demux, "failed when pulling %d bytes", demux->tag_size);
goto beach;
}
if (G_UNLIKELY (buffer && GST_BUFFER_SIZE (buffer) != demux->tag_size)) {
GST_WARNING_OBJECT (demux, "partial pull got %d when expecting %d",
GST_BUFFER_SIZE (buffer), demux->tag_size);
gst_buffer_unref (buffer);
ret = GST_FLOW_UNEXPECTED;
goto beach;
}
switch (demux->state) {
case FLV_STATE_TAG_VIDEO:
ret = gst_flv_parse_tag_video (demux, GST_BUFFER_DATA (buffer),
GST_BUFFER_SIZE (buffer));
break;
case FLV_STATE_TAG_AUDIO:
ret = gst_flv_parse_tag_audio (demux, GST_BUFFER_DATA (buffer),
GST_BUFFER_SIZE (buffer));
break;
case FLV_STATE_TAG_SCRIPT:
ret = gst_flv_parse_tag_script (demux, GST_BUFFER_DATA (buffer),
GST_BUFFER_SIZE (buffer));
break;
default:
GST_WARNING_OBJECT (demux, "unexpected state %d", demux->state);
}
gst_buffer_unref (buffer);
/* Jump over that part we've just parsed */
demux->offset += demux->tag_size;
/* Make sure we reinitialize the tag size */
demux->tag_size = 0;
/* Ready for the next tag */
demux->state = FLV_STATE_TAG_TYPE;
beach:
return ret;
}
static GstFlowReturn
gst_flv_demux_pull_header (GstPad * pad, GstFLVDemux * demux)
{
GstBuffer *buffer = NULL;
GstFlowReturn ret = GST_FLOW_OK;
/* Get the first 9 bytes */
ret = gst_pad_pull_range (pad, demux->offset, FLV_HEADER_SIZE, &buffer);
if (G_UNLIKELY (ret != GST_FLOW_OK)) {
GST_WARNING_OBJECT (demux, "failed when pulling %d bytes", FLV_HEADER_SIZE);
goto beach;
}
if (G_UNLIKELY (buffer && GST_BUFFER_SIZE (buffer) != FLV_HEADER_SIZE)) {
GST_WARNING_OBJECT (demux, "partial pull got %d when expecting %d",
GST_BUFFER_SIZE (buffer), FLV_HEADER_SIZE);
gst_buffer_unref (buffer);
ret = GST_FLOW_UNEXPECTED;
goto beach;
}
ret = gst_flv_parse_header (demux, GST_BUFFER_DATA (buffer),
GST_BUFFER_SIZE (buffer));
/* Jump over the header now */
demux->offset += FLV_HEADER_SIZE;
demux->state = FLV_STATE_TAG_TYPE;
beach:
return ret;
}
static GstFlowReturn
gst_flv_demux_seek_to_prev_keyframe (GstFLVDemux * demux)
{
return GST_FLOW_OK;
}
static void
gst_flv_demux_loop (GstPad * pad)
{
GstFLVDemux *demux = NULL;
GstFlowReturn ret = GST_FLOW_OK;
demux = GST_FLV_DEMUX (gst_pad_get_parent (pad));
if (demux->segment->rate >= 0) {
/* pull in data */
switch (demux->state) {
case FLV_STATE_TAG_TYPE:
ret = gst_flv_demux_pull_tag (pad, demux);
break;
case FLV_STATE_DONE:
ret = GST_FLOW_UNEXPECTED;
break;
default:
ret = gst_flv_demux_pull_header (pad, demux);
}
/* pause if something went wrong */
if (G_UNLIKELY (ret != GST_FLOW_OK))
goto pause;
/* check EOS condition */
if ((demux->segment->flags & GST_SEEK_FLAG_SEGMENT) &&
(demux->segment->stop != -1) &&
(demux->segment->last_stop >= demux->segment->stop)) {
ret = GST_FLOW_UNEXPECTED;
goto pause;
}
} else { /* Reverse playback */
/* pull in data */
switch (demux->state) {
case FLV_STATE_TAG_TYPE:
ret = gst_flv_demux_pull_tag (pad, demux);
/* When packet parsing returns UNEXPECTED that means we ve reached the
point where we want to go to the previous keyframe. This is either
the last FLV tag or the keyframe we used last time */
if (ret == GST_FLOW_UNEXPECTED) {
ret = gst_flv_demux_seek_to_prev_keyframe (demux);
demux->state = FLV_STATE_TAG_TYPE;
}
break;
default:
ret = gst_flv_demux_pull_header (pad, demux);
}
/* pause if something went wrong */
if (G_UNLIKELY (ret != GST_FLOW_OK))
goto pause;
/* check EOS condition */
if (demux->segment->last_stop <= demux->segment->start) {
ret = GST_FLOW_UNEXPECTED;
goto pause;
}
}
gst_object_unref (demux);
return;
pause:
{
const gchar *reason = gst_flow_get_name (ret);
GST_LOG_OBJECT (demux, "pausing task, reason %s", reason);
gst_pad_pause_task (pad);
if (GST_FLOW_IS_FATAL (ret) || ret == GST_FLOW_NOT_LINKED) {
if (ret == GST_FLOW_UNEXPECTED) {
/* perform EOS logic */
gst_element_no_more_pads (GST_ELEMENT_CAST (demux));
if (demux->segment->flags & GST_SEEK_FLAG_SEGMENT) {
gint64 stop;
/* for segment playback we need to post when (in stream time)
* we stopped, this is either stop (when set) or the duration. */
if ((stop = demux->segment->stop) == -1)
stop = demux->segment->duration;
if (demux->segment->rate >= 0) {
GST_LOG_OBJECT (demux, "Sending segment done, at end of segment");
gst_element_post_message (GST_ELEMENT_CAST (demux),
gst_message_new_segment_done (GST_OBJECT_CAST (demux),
GST_FORMAT_TIME, stop));
} else { /* Reverse playback */
GST_LOG_OBJECT (demux, "Sending segment done, at beginning of "
"segment");
gst_element_post_message (GST_ELEMENT_CAST (demux),
gst_message_new_segment_done (GST_OBJECT_CAST (demux),
GST_FORMAT_TIME, demux->segment->start));
}
} else {
/* normal playback, send EOS to all linked pads */
gst_element_no_more_pads (GST_ELEMENT (demux));
GST_LOG_OBJECT (demux, "Sending EOS, at end of stream");
if (!gst_pad_event_default (demux->sinkpad, gst_event_new_eos ())) {
GST_WARNING_OBJECT (demux, "failed pushing EOS on streams");
GST_ELEMENT_ERROR (demux, STREAM, FAILED,
("Internal data stream error."),
("Can't push EOS downstream (empty/invalid file "
"with no streams/tags ?)"));
}
}
} else {
GST_ELEMENT_ERROR (demux, STREAM, FAILED,
("Internal data stream error."),
("stream stopped, reason %s", reason));
gst_pad_event_default (demux->sinkpad, gst_event_new_eos ());
}
}
gst_object_unref (demux);
return;
}
}
/* If we can pull that's prefered */
static gboolean
gst_flv_demux_sink_activate (GstPad * sinkpad)
{
if (gst_pad_check_pull_range (sinkpad)) {
return gst_pad_activate_pull (sinkpad, TRUE);
} else {
return gst_pad_activate_push (sinkpad, TRUE);
}
}
/* This function gets called when we activate ourselves in push mode.
* We cannot seek (ourselves) in the stream */
static gboolean
gst_flv_demux_sink_activate_push (GstPad * sinkpad, gboolean active)
{
GstFLVDemux *demux;
demux = GST_FLV_DEMUX (gst_pad_get_parent (sinkpad));
demux->random_access = FALSE;
gst_object_unref (demux);
return TRUE;
}
/* this function gets called when we activate ourselves in pull mode.
* We can perform random access to the resource and we start a task
* to start reading */
static gboolean
gst_flv_demux_sink_activate_pull (GstPad * sinkpad, gboolean active)
{
GstFLVDemux *demux;
demux = GST_FLV_DEMUX (gst_pad_get_parent (sinkpad));
if (active) {
demux->random_access = TRUE;
gst_object_unref (demux);
return gst_pad_start_task (sinkpad, (GstTaskFunction) gst_flv_demux_loop,
sinkpad);
} else {
demux->random_access = FALSE;
gst_object_unref (demux);
return gst_pad_stop_task (sinkpad);
}
}
static gboolean
gst_flv_demux_sink_event (GstPad * pad, GstEvent * event)
{
GstFLVDemux *demux;
gboolean ret = FALSE;
demux = GST_FLV_DEMUX (gst_pad_get_parent (pad));
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_FLUSH_STOP:
GST_DEBUG_OBJECT (demux, "flushing FLV demuxer");
gst_flv_demux_flush (demux, TRUE);
gst_adapter_clear (demux->adapter);
gst_segment_init (demux->segment, GST_FORMAT_TIME);
ret = gst_pad_event_default (demux->sinkpad, event);
break;
case GST_EVENT_EOS:
GST_DEBUG_OBJECT (demux, "received EOS");
gst_element_no_more_pads (GST_ELEMENT (demux));
if (!gst_pad_event_default (demux->sinkpad, event)) {
GST_WARNING_OBJECT (demux, "failed pushing EOS on streams");
GST_ELEMENT_ERROR (demux, STREAM, FAILED,
("Internal data stream error."),
("Can't push EOS downstream (empty/invalid file "
"with no streams/tags ?)"));
}
ret = TRUE;
break;
case GST_EVENT_NEWSEGMENT:
{
GstFormat format;
gdouble rate;
gint64 start, stop, time;
gboolean update;
GST_DEBUG_OBJECT (demux, "received new segment");
gst_event_parse_new_segment (event, &update, &rate, &format, &start,
&stop, &time);
if (format == GST_FORMAT_TIME) {
/* time segment, this is perfect, copy over the values. */
gst_segment_set_newsegment (demux->segment, update, rate, format, start,
stop, time);
#ifdef POST_10_10
GST_DEBUG_OBJECT (demux, "NEWSEGMENT: %" GST_SEGMENT_FORMAT,
demux->segment);
#endif
/* and forward */
ret = gst_pad_event_default (demux->sinkpad, event);
} else {
/* non-time format */
demux->audio_need_segment = TRUE;
demux->video_need_segment = TRUE;
ret = TRUE;
gst_event_unref (event);
}
break;
}
default:
ret = gst_pad_event_default (demux->sinkpad, event);
break;
}
gst_object_unref (demux);
return ret;
}
static GstStateChangeReturn
gst_flv_demux_change_state (GstElement * element, GstStateChange transition)
{
GstFLVDemux *demux;
GstStateChangeReturn ret;
demux = GST_FLV_DEMUX (element);
switch (transition) {
case GST_STATE_CHANGE_READY_TO_PAUSED:
demux->state = FLV_STATE_HEADER;
demux->need_header = TRUE;
demux->audio_need_discont = TRUE;
demux->video_need_discont = TRUE;
break;
default:
break;
}
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
if (ret == GST_STATE_CHANGE_FAILURE)
return ret;
switch (transition) {
case GST_STATE_CHANGE_PAUSED_TO_READY:
gst_flv_demux_cleanup (demux);
break;
default:
break;
}
return ret;
}
static void
gst_flv_demux_dispose (GObject * object)
{
GstFLVDemux *demux = GST_FLV_DEMUX (object);
GST_DEBUG_OBJECT (demux, "disposing FLV demuxer");
if (demux->adapter) {
gst_adapter_clear (demux->adapter);
g_object_unref (demux->adapter);
demux->adapter = NULL;
}
if (demux->segment) {
gst_segment_free (demux->segment);
demux->segment = NULL;
}
if (demux->taglist) {
gst_tag_list_free (demux->taglist);
demux->taglist = NULL;
}
if (demux->new_seg_event) {
gst_event_unref (demux->new_seg_event);
demux->new_seg_event = NULL;
}
GST_CALL_PARENT (G_OBJECT_CLASS, dispose, (object));
}
static void
gst_flv_demux_base_init (gpointer g_class)
{
GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&flv_sink_template));
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&audio_src_template));
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&video_src_template));
gst_element_class_set_details (element_class, &flv_demux_details);
}
static void
gst_flv_demux_class_init (GstFLVDemuxClass * klass)
{
GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_flv_demux_dispose);
gstelement_class->change_state =
GST_DEBUG_FUNCPTR (gst_flv_demux_change_state);
}
static void
gst_flv_demux_init (GstFLVDemux * demux, GstFLVDemuxClass * g_class)
{
demux->sinkpad =
gst_pad_new_from_static_template (&flv_sink_template, "sink");
gst_pad_set_event_function (demux->sinkpad,
GST_DEBUG_FUNCPTR (gst_flv_demux_sink_event));
gst_pad_set_chain_function (demux->sinkpad,
GST_DEBUG_FUNCPTR (gst_flv_demux_chain));
gst_pad_set_activate_function (demux->sinkpad,
GST_DEBUG_FUNCPTR (gst_flv_demux_sink_activate));
gst_pad_set_activatepull_function (demux->sinkpad,
GST_DEBUG_FUNCPTR (gst_flv_demux_sink_activate_pull));
gst_pad_set_activatepush_function (demux->sinkpad,
GST_DEBUG_FUNCPTR (gst_flv_demux_sink_activate_push));
gst_element_add_pad (GST_ELEMENT (demux), demux->sinkpad);
demux->adapter = gst_adapter_new ();
demux->segment = gst_segment_new ();
demux->taglist = gst_tag_list_new ();
gst_segment_init (demux->segment, GST_FORMAT_TIME);
demux->offset = 0;
demux->strict = FALSE;
demux->push_tags = FALSE;
}
static gboolean
plugin_init (GstPlugin * plugin)
{
GST_DEBUG_CATEGORY_INIT (flvdemux_debug, "flvdemux", 0, "FLV demuxer");
if (!gst_element_register (plugin, "flvdemux", GST_RANK_PRIMARY,
gst_flv_demux_get_type ()))
return FALSE;
return TRUE;
}
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR,
"flvdemux", "Element demuxing FLV stream",
plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)

108
gst/flv/gstflvdemux.h Normal file
View file

@ -0,0 +1,108 @@
/* GStreamer
* Copyright (C) <2007> Julien Moutte <julien@moutte.net>
*
* 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.
*/
#ifndef __FLV_DEMUX_H__
#define __FLV_DEMUX_H__
#include <gst/gst.h>
#include <gst/base/gstadapter.h>
G_BEGIN_DECLS
#define GST_TYPE_FLV_DEMUX \
(gst_flv_demux_get_type())
#define GST_FLV_DEMUX(obj) \
(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_FLV_DEMUX,GstFLVDemux))
#define GST_FLV_DEMUX_CLASS(klass) \
(G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_FLV_DEMUX,GstFLVDemuxClass))
#define GST_IS_FLV_DEMUX(obj) \
(G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_FLV_DEMUX))
#define GST_IS_FLV_DEMUX_CLASS(klass) \
(G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_FLV_DEMUX))
typedef struct _GstFLVDemux GstFLVDemux;
typedef struct _GstFLVDemuxClass GstFLVDemuxClass;
typedef enum {
FLV_STATE_HEADER,
FLV_STATE_TAG_TYPE,
FLV_STATE_TAG_VIDEO,
FLV_STATE_TAG_AUDIO,
FLV_STATE_TAG_SCRIPT,
FLV_STATE_DONE,
FLV_STATE_NONE
} GstFLVDemuxState;
struct _GstFLVDemux {
GstElement element;
GstPad * sinkpad;
GstPad * audio_pad;
GstPad * video_pad;
GstAdapter * adapter;
GstSegment * segment;
GstEvent * new_seg_event;
GstTagList * taglist;
GstFLVDemuxState state;
guint64 offset;
GstClockTime duration;
guint64 tag_size;
guint64 tag_data_size;
/* Audio infos */
guint16 rate;
guint16 channels;
guint16 width;
guint16 audio_codec_tag;
guint64 audio_offset;
gboolean audio_need_discont;
gboolean audio_need_segment;
/* Video infos */
guint32 w;
guint32 h;
guint16 video_codec_tag;
guint64 video_offset;
gboolean video_need_discont;
gboolean video_need_segment;
gboolean random_access;
gboolean need_header;
gboolean has_audio;
gboolean has_video;
gboolean push_tags;
gboolean strict;
};
struct _GstFLVDemuxClass {
GstElementClass parent_class;
};
GType gst_flv_demux_get_type (void);
G_END_DECLS
#endif /* __FLV_DEMUX_H__ */

797
gst/flv/gstflvparse.c Normal file
View file

@ -0,0 +1,797 @@
/* GStreamer
* Copyright (C) <2007> Julien Moutte <julien@moutte.net>
*
* 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.
*/
#include "gstflvparse.h"
#include <string.h>
GST_DEBUG_CATEGORY_EXTERN (flvdemux_debug);
#define GST_CAT_DEFAULT flvdemux_debug
static guint32
FLV_GET_BEUI24 (const guint8 * data, size_t data_size)
{
guint32 ret = 0;
g_return_val_if_fail (data != NULL, 0);
g_return_val_if_fail (data_size >= 3, 0);
ret = GST_READ_UINT16_BE (data) << 8;
ret |= GST_READ_UINT8 (data + 2);
return ret;
}
static gchar *
FLV_GET_STRING (const guint8 * data, size_t data_size)
{
guint32 string_size = 0;
gchar *string = NULL;
g_return_val_if_fail (data != NULL, 0);
g_return_val_if_fail (data_size >= 3, 0);
string_size = GST_READ_UINT16_BE (data);
string = g_try_malloc0 (string_size + 1);
if (G_UNLIKELY (!string)) {
return NULL;
}
memcpy (string, data + 2, string_size);
return string;
}
static const GstQueryType *
gst_flv_demux_query_types (GstPad * pad)
{
static const GstQueryType query_types[] = {
GST_QUERY_DURATION,
0
};
return query_types;
}
static gboolean
gst_flv_demux_query (GstPad * pad, GstQuery * query)
{
gboolean res = TRUE;
GstFLVDemux *demux;
demux = GST_FLV_DEMUX (gst_pad_get_parent (pad));
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_DURATION:
{
GstFormat format;
gst_query_parse_duration (query, &format, NULL);
/* duration is time only */
if (format != GST_FORMAT_TIME) {
GST_DEBUG_OBJECT (demux, "duration query only supported for time "
"format");
res = FALSE;
goto beach;
}
GST_DEBUG_OBJECT (pad, "duration query, replying %" GST_TIME_FORMAT,
GST_TIME_ARGS (demux->duration));
gst_query_set_duration (query, GST_FORMAT_TIME, demux->duration);
break;
}
case GST_QUERY_LATENCY:
{
GstPad *peer;
if ((peer = gst_pad_get_peer (demux->sinkpad))) {
/* query latency on peer pad */
res = gst_pad_query (peer, query);
gst_object_unref (peer);
} else {
/* no peer, we don't know */
res = FALSE;
}
break;
}
default:
res = FALSE;
break;
}
beach:
gst_object_unref (demux);
return res;
}
static size_t
gst_flv_parse_metadata_item (GstFLVDemux * demux, const guint8 * data,
size_t data_size)
{
gchar *tag_name = NULL;
guint8 tag_type = 0;
size_t offset = 0;
/* Name of the tag */
tag_name = FLV_GET_STRING (data, data_size);
offset += strlen (tag_name) + 2;
/* What kind of object is that */
tag_type = GST_READ_UINT8 (data + offset);
offset++;
GST_DEBUG_OBJECT (demux, "tag name %s, tag type %d", tag_name, tag_type);
switch (tag_type) {
case 0: // Double
{ /* Use a union to read the uint64 and then as a double */
union
{
guint64 value_uint64;
gdouble value_double;
} value_union;
value_union.value_uint64 = GST_READ_UINT64_BE (data + offset);
offset += 8;
GST_DEBUG_OBJECT (demux, "%s => (double) %f", tag_name,
value_union.value_double);
if (!strcmp (tag_name, "duration")) {
demux->duration = value_union.value_double * GST_SECOND;
gst_tag_list_add (demux->taglist, GST_TAG_MERGE_REPLACE,
GST_TAG_DURATION, demux->duration, NULL);
} else {
if (tag_name) {
if (!gst_tag_exists (tag_name)) {
gst_tag_register (tag_name, GST_TAG_FLAG_META, G_TYPE_DOUBLE,
tag_name, tag_name, gst_tag_merge_use_first);
}
if (gst_tag_get_type (tag_name) == G_TYPE_DOUBLE) {
gst_tag_list_add (demux->taglist, GST_TAG_MERGE_REPLACE,
tag_name, value_union.value_double, NULL);
} else {
GST_WARNING_OBJECT (demux, "tag %s already registered with a "
"different type", tag_name);
}
}
}
break;
}
case 1: // Boolean
{
gboolean value = GST_READ_UINT8 (data + offset);
offset++;
GST_DEBUG_OBJECT (demux, "%s => (boolean) %d", tag_name, value);
if (tag_name) {
if (!gst_tag_exists (tag_name)) {
gst_tag_register (tag_name, GST_TAG_FLAG_META, G_TYPE_BOOLEAN,
tag_name, tag_name, gst_tag_merge_use_first);
}
if (gst_tag_get_type (tag_name) == G_TYPE_BOOLEAN) {
gst_tag_list_add (demux->taglist, GST_TAG_MERGE_REPLACE,
tag_name, value, NULL);
} else {
GST_WARNING_OBJECT (demux, "tag %s already registered with a "
"different type", tag_name);
}
}
break;
}
case 2: // String
{
gchar *value = NULL;
value = FLV_GET_STRING (data + offset, data_size - offset);
offset += strlen (value) + 2;
GST_DEBUG_OBJECT (demux, "%s => (string) %s", tag_name, value);
if (tag_name) {
if (!gst_tag_exists (tag_name)) {
gst_tag_register (tag_name, GST_TAG_FLAG_META, G_TYPE_STRING,
tag_name, tag_name, gst_tag_merge_strings_with_comma);
}
if (gst_tag_get_type (tag_name) == G_TYPE_STRING) {
gst_tag_list_add (demux->taglist, GST_TAG_MERGE_REPLACE,
tag_name, value, NULL);
} else {
GST_WARNING_OBJECT (demux, "tag %s already registered with a "
"different type", tag_name);
}
}
g_free (value);
break;
}
default:
GST_WARNING_OBJECT (demux, "unsupported tag type %d", tag_type);
}
g_free (tag_name);
return offset;
}
GstFlowReturn
gst_flv_parse_tag_script (GstFLVDemux * demux, const guint8 * data,
size_t data_size)
{
GstFlowReturn ret = GST_FLOW_OK;
size_t offset = 7;
GST_LOG_OBJECT (demux, "parsing a script tag");
if (GST_READ_UINT8 (data + offset++) == 2) {
gchar *function_name = FLV_GET_STRING (data + offset, data_size - offset);
GST_LOG_OBJECT (demux, "function name is %s", function_name);
if (!strcmp (function_name, "onMetaData")) {
guint32 nb_elems = 0;
GST_DEBUG_OBJECT (demux, "we have a metadata script object");
/* Jump over the onMetaData string and the array indicator */
offset += 13;
nb_elems = GST_READ_UINT32_BE (data + offset);
/* Jump over the number of elements */
offset += 4;
GST_DEBUG_OBJECT (demux, "there are %d elements in the array", nb_elems);
while (nb_elems--) {
offset += gst_flv_parse_metadata_item (demux, data + offset,
data_size - offset);
}
demux->push_tags = TRUE;
}
g_free (function_name);
}
return ret;
}
GstFlowReturn
gst_flv_parse_tag_audio (GstFLVDemux * demux, const guint8 * data,
size_t data_size)
{
GstFlowReturn ret = GST_FLOW_OK;
GstBuffer *buffer = NULL;
guint32 pts = 0, codec_tag = 0, rate = 0, width = 0, channels = 0;
guint32 codec_data = 0;
guint8 flags = 0;
GST_LOG_OBJECT (demux, "parsing an audio tag");
/* Grab information about audio tag */
pts = FLV_GET_BEUI24 (data, data_size);
flags = GST_READ_UINT8 (data + 7);
/* Channels */
if (flags & 0x01) {
channels = 2;
} else {
channels = 1;
}
/* Width */
if (flags & 0x02) {
width = 16;
} else {
width = 8;
}
/* Sampling rate */
if (flags & 0x0C) {
rate = 44100;
} else if (flags & 0x08) {
rate = 22050;
} else if (flags & 0x04) {
rate = 11025;
} else {
rate = 5512;
}
/* Codec tag */
if (flags & 0x10) {
codec_tag = 1;
codec_data = 1;
} else if (flags & 0x20) {
codec_tag = 2;
codec_data = 1;
} else if (flags & 0x30) {
codec_tag = 3;
codec_data = 1;
} else if (flags & 0x40) {
codec_tag = 4;
codec_data = 1;
} else if (flags & 0x50) {
codec_tag = 5;
codec_data = 1;
}
GST_LOG_OBJECT (demux, "audio tag with %d channels, %dHz sampling rate, "
"%d bits width, codec tag %d", channels, rate, width, codec_tag);
/* If we don't have our audio pad created, then create it. */
if (G_UNLIKELY (!demux->audio_pad)) {
GstCaps *caps = NULL;
demux->audio_pad = gst_pad_new ("audio", GST_PAD_SRC);
if (G_UNLIKELY (!demux->audio_pad)) {
GST_WARNING_OBJECT (demux, "failed creating audio pad");
ret = GST_FLOW_ERROR;
goto beach;
}
/* Make it active */
gst_pad_set_active (demux->audio_pad, TRUE);
switch (codec_tag) {
case 2:
caps = gst_caps_new_simple ("audio/mpeg",
"mpegversion", G_TYPE_INT, 1, "layer", G_TYPE_INT, 3, NULL);
break;
case 0:
case 3:
caps = gst_caps_new_simple ("audio/x-raw-int", NULL);
break;
default:
GST_WARNING_OBJECT (demux, "unsupported audio codec tag", codec_tag);
}
if (G_UNLIKELY (!caps)) {
GST_WARNING_OBJECT (demux, "failed creating caps for audio pad");
ret = GST_FLOW_ERROR;
gst_object_unref (demux->audio_pad);
demux->audio_pad = NULL;
goto beach;
}
gst_caps_set_simple (caps,
"rate", G_TYPE_INT, rate,
"channels", G_TYPE_INT, channels, "width", G_TYPE_INT, width, NULL);
gst_pad_set_caps (demux->audio_pad, caps);
gst_caps_unref (caps);
/* Store the caps we have set */
demux->audio_codec_tag = codec_tag;
demux->rate = rate;
demux->channels = channels;
demux->width = width;
/* Set functions on the pad */
gst_pad_set_query_type_function (demux->audio_pad,
GST_DEBUG_FUNCPTR (gst_flv_demux_query_types));
gst_pad_set_query_function (demux->audio_pad,
GST_DEBUG_FUNCPTR (gst_flv_demux_query));
/* We need to set caps before adding */
gst_element_add_pad (GST_ELEMENT (demux), demux->audio_pad);
}
/* Check if caps have changed */
if (G_UNLIKELY (rate != demux->rate || channels != demux->channels ||
codec_tag != demux->audio_codec_tag || width != demux->width)) {
GstCaps *caps = NULL;
GST_DEBUG_OBJECT (demux, "audio settings have changed, changing caps");
switch (codec_tag) {
case 2:
caps = gst_caps_new_simple ("audio/mpeg",
"mpegversion", G_TYPE_INT, 1, "layer", G_TYPE_INT, 3, NULL);
break;
case 0:
case 3:
caps = gst_caps_new_simple ("audio/x-raw-int", NULL);
break;
default:
GST_WARNING_OBJECT (demux, "unsupported audio codec tag", codec_tag);
}
if (G_UNLIKELY (!caps)) {
GST_WARNING_OBJECT (demux, "failed creating caps for audio pad");
ret = GST_FLOW_ERROR;
goto beach;
}
gst_caps_set_simple (caps,
"rate", G_TYPE_INT, rate,
"channels", G_TYPE_INT, channels, "width", G_TYPE_INT, width, NULL);
gst_pad_set_caps (demux->audio_pad, caps);
gst_caps_unref (caps);
/* Store the caps we have set */
demux->audio_codec_tag = codec_tag;
demux->rate = rate;
demux->channels = channels;
demux->width = width;
}
/* Push taglist if present */
if ((demux->has_audio & (demux->audio_pad != NULL)) &&
(demux->has_video & (demux->video_pad != NULL)) &&
demux->taglist && demux->push_tags) {
GST_DEBUG_OBJECT (demux, "pushing tags out");
gst_element_found_tags (GST_ELEMENT (demux), demux->taglist);
demux->taglist = gst_tag_list_new ();
demux->push_tags = FALSE;
}
/* Create buffer from pad */
ret = gst_pad_alloc_buffer (demux->audio_pad, GST_BUFFER_OFFSET_NONE,
demux->tag_data_size - codec_data, GST_PAD_CAPS (demux->audio_pad),
&buffer);
if (G_UNLIKELY (ret != GST_FLOW_OK)) {
GST_WARNING_OBJECT (demux, "failed allocating a %d bytes buffer",
demux->tag_data_size);
goto beach;
}
/* Fill buffer with data */
GST_BUFFER_TIMESTAMP (buffer) = pts * GST_MSECOND;
GST_BUFFER_DURATION (buffer) = GST_CLOCK_TIME_NONE;
GST_BUFFER_OFFSET (buffer) = demux->audio_offset++;
GST_BUFFER_OFFSET_END (buffer) = demux->audio_offset;
if (G_UNLIKELY (demux->audio_need_discont)) {
GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT);
demux->audio_need_discont = FALSE;
}
gst_segment_set_last_stop (demux->segment, GST_FORMAT_TIME,
GST_BUFFER_TIMESTAMP (buffer));
/* Do we need a newsegment event ? */
if (G_UNLIKELY (demux->audio_need_segment)) {
if (!demux->new_seg_event) {
GST_DEBUG_OBJECT (demux, "pushing newsegment from %"
GST_TIME_FORMAT " to %" GST_TIME_FORMAT,
GST_TIME_ARGS (demux->segment->last_stop), GST_TIME_ARGS (-1));
demux->new_seg_event = gst_event_new_new_segment (FALSE,
demux->segment->rate, demux->segment->format,
demux->segment->last_stop, -1, demux->segment->last_stop);
}
gst_pad_push_event (demux->audio_pad, gst_event_ref (demux->new_seg_event));
demux->audio_need_segment = FALSE;
}
memcpy (GST_BUFFER_DATA (buffer), data + 7 + codec_data,
demux->tag_data_size - codec_data);
GST_LOG_OBJECT (demux, "pushing %d bytes buffer at pts %" GST_TIME_FORMAT
" with duration %" GST_TIME_FORMAT ", offset %" G_GUINT64_FORMAT,
GST_BUFFER_SIZE (buffer), GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
GST_TIME_ARGS (GST_BUFFER_DURATION (buffer)), GST_BUFFER_OFFSET (buffer));
/* Push downstream */
ret = gst_pad_push (demux->audio_pad, buffer);
beach:
return ret;
}
GstFlowReturn
gst_flv_parse_tag_video (GstFLVDemux * demux, const guint8 * data,
size_t data_size)
{
GstFlowReturn ret = GST_FLOW_OK;
GstBuffer *buffer = NULL;
guint32 pts = 0, codec_tag = 0, codec_data = 0;
gboolean keyframe = FALSE;
guint8 flags = 0;
GST_LOG_OBJECT (demux, "parsing a video tag");
/* Grab information about audio tag */
pts = FLV_GET_BEUI24 (data, data_size);
flags = GST_READ_UINT8 (data + 7);
/* Keyframe */
if (flags & 0x10) {
keyframe = TRUE;
} else if (flags & 0x20) {
keyframe = FALSE;
}
/* Codec tag */
if (flags & 0x02) { // H263
codec_tag = 2;
codec_data = 1;
} else if (flags & 0x03) { // Sorenson Spark
codec_tag = 3;
codec_data = 1;
} else if (flags & 0x04) { // VP6
codec_tag = 4;
codec_data = 2;
} else if (flags & 0x05) { // VP6 Alpha
codec_tag = 5;
codec_data = 2;
}
GST_LOG_OBJECT (demux, "video tag with codec tag %d, keyframe (%d)",
codec_tag, keyframe);
/* If we don't have our video pad created, then create it. */
if (G_UNLIKELY (!demux->video_pad)) {
GstCaps *caps = NULL;
demux->video_pad = gst_pad_new ("video", GST_PAD_SRC);
if (G_UNLIKELY (!demux->video_pad)) {
GST_WARNING_OBJECT (demux, "failed creating video pad");
ret = GST_FLOW_ERROR;
goto beach;
}
/* Make it active */
gst_pad_set_active (demux->video_pad, TRUE);
/* Generate caps for that pad */
switch (codec_tag) {
case 2:
caps = gst_caps_new_simple ("video/x-flash-video", NULL);
break;
case 3:
caps = gst_caps_new_simple ("video/x-flash-video", NULL);
break;
case 4:
caps = gst_caps_new_simple ("video/x-vp6", NULL);
break;
case 5:
caps = gst_caps_new_simple ("video/x-vp6", NULL);
break;
default:
GST_WARNING_OBJECT (demux, "unsupported video codec tag %d", codec_tag);
}
if (G_UNLIKELY (!caps)) {
GST_WARNING_OBJECT (demux, "failed creating caps for video pad");
gst_object_unref (demux->video_pad);
demux->video_pad = NULL;
ret = GST_FLOW_ERROR;
goto beach;
}
gst_pad_set_caps (demux->video_pad, caps);
gst_caps_unref (caps);
/* Store the caps we have set */
demux->video_codec_tag = codec_tag;
/* Set functions on the pad */
gst_pad_set_query_type_function (demux->video_pad,
GST_DEBUG_FUNCPTR (gst_flv_demux_query_types));
gst_pad_set_query_function (demux->video_pad,
GST_DEBUG_FUNCPTR (gst_flv_demux_query));
/* We need to set caps before adding */
gst_element_add_pad (GST_ELEMENT (demux), demux->video_pad);
}
/* Check if caps have changed */
if (G_UNLIKELY (codec_tag != demux->video_codec_tag)) {
GstCaps *caps = NULL;
GST_DEBUG_OBJECT (demux, "video settings have changed, changing caps");
/* Generate caps for that pad */
switch (codec_tag) {
case 2:
caps = gst_caps_new_simple ("video/x-flash-video", NULL);
break;
case 3:
caps = gst_caps_new_simple ("video/x-flash-video", NULL);
break;
case 4:
caps = gst_caps_new_simple ("video/x-vp6", NULL);
break;
case 5:
caps = gst_caps_new_simple ("video/x-vp6", NULL);
break;
default:
GST_WARNING_OBJECT (demux, "unsupported video codec tag %d", codec_tag);
}
if (G_UNLIKELY (!caps)) {
GST_WARNING_OBJECT (demux, "failed creating caps for video pad");
ret = GST_FLOW_ERROR;
goto beach;
}
gst_pad_set_caps (demux->video_pad, caps);
gst_caps_unref (caps);
/* Store the caps we have set */
demux->video_codec_tag = codec_tag;
}
/* Push taglist if present */
if ((demux->has_audio & (demux->audio_pad != NULL)) &&
(demux->has_video & (demux->video_pad != NULL)) &&
demux->taglist && demux->push_tags) {
GST_DEBUG_OBJECT (demux, "pushing tags out");
gst_element_found_tags (GST_ELEMENT (demux), demux->taglist);
demux->taglist = gst_tag_list_new ();
demux->push_tags = FALSE;
}
/* Create buffer from pad */
ret = gst_pad_alloc_buffer (demux->video_pad, GST_BUFFER_OFFSET_NONE,
demux->tag_data_size - codec_data, GST_PAD_CAPS (demux->video_pad),
&buffer);
if (G_UNLIKELY (ret != GST_FLOW_OK)) {
GST_WARNING_OBJECT (demux, "failed allocating a %d bytes buffer",
demux->tag_data_size);
goto beach;
}
/* Fill buffer with data */
GST_BUFFER_TIMESTAMP (buffer) = pts * GST_MSECOND;
GST_BUFFER_DURATION (buffer) = GST_CLOCK_TIME_NONE;
GST_BUFFER_OFFSET (buffer) = demux->video_offset++;
GST_BUFFER_OFFSET_END (buffer) = demux->video_offset;
if (!keyframe) {
GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT);
}
if (G_UNLIKELY (demux->video_need_discont)) {
GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT);
demux->video_need_discont = FALSE;
}
gst_segment_set_last_stop (demux->segment, GST_FORMAT_TIME,
GST_BUFFER_TIMESTAMP (buffer));
/* Do we need a newsegment event ? */
if (G_UNLIKELY (demux->video_need_segment)) {
if (!demux->new_seg_event) {
GST_DEBUG_OBJECT (demux, "pushing newsegment from %"
GST_TIME_FORMAT " to %" GST_TIME_FORMAT,
GST_TIME_ARGS (demux->segment->last_stop), GST_TIME_ARGS (-1));
demux->new_seg_event = gst_event_new_new_segment (FALSE,
demux->segment->rate, demux->segment->format,
demux->segment->last_stop, -1, demux->segment->last_stop);
}
gst_pad_push_event (demux->video_pad, gst_event_ref (demux->new_seg_event));
demux->video_need_segment = FALSE;
}
/* FIXME: safety checks */
memcpy (GST_BUFFER_DATA (buffer), data + 7 + codec_data,
demux->tag_data_size - codec_data);
GST_LOG_OBJECT (demux, "pushing %d bytes buffer at pts %" GST_TIME_FORMAT
" with duration %" GST_TIME_FORMAT ", offset %" G_GUINT64_FORMAT
", keyframe (%d)", GST_BUFFER_SIZE (buffer),
GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
GST_TIME_ARGS (GST_BUFFER_DURATION (buffer)), GST_BUFFER_OFFSET (buffer),
keyframe);
/* Push downstream */
ret = gst_pad_push (demux->video_pad, buffer);
beach:
return ret;
}
GstFlowReturn
gst_flv_parse_tag_type (GstFLVDemux * demux, const guint8 * data,
size_t data_size)
{
GstFlowReturn ret = GST_FLOW_OK;
guint8 tag_type = 0;
tag_type = data[0];
switch (tag_type) {
case 9:
demux->state = FLV_STATE_TAG_VIDEO;
break;
case 8:
demux->state = FLV_STATE_TAG_AUDIO;
break;
case 18:
demux->state = FLV_STATE_TAG_SCRIPT;
break;
default:
GST_WARNING_OBJECT (demux, "unsupported tag type %u", tag_type);
}
/* Tag size is 1 byte of type + 3 bytes of size + 7 bytes + tag data size +
* 4 bytes of previous tag size */
demux->tag_data_size = FLV_GET_BEUI24 (data + 1, data_size - 1);
demux->tag_size = demux->tag_data_size + 11;
GST_LOG_OBJECT (demux, "tag data size is %d", demux->tag_data_size);
return ret;
}
GstFlowReturn
gst_flv_parse_header (GstFLVDemux * demux, const guint8 * data,
size_t data_size)
{
GstFlowReturn ret = GST_FLOW_OK;
/* Check for the FLV tag */
if (data[0] == 'F' && data[1] == 'L' && data[2] == 'V') {
GST_DEBUG_OBJECT (demux, "FLV header detected");
} else {
if (G_UNLIKELY (demux->strict)) {
GST_WARNING_OBJECT (demux, "invalid header tag detected");
ret = GST_FLOW_UNEXPECTED;
goto beach;
}
}
/* Jump over the 4 first bytes */
data += 4;
/* Now look at audio/video flags */
{
guint8 flags = data[0];
if (flags & 1) {
GST_DEBUG_OBJECT (demux, "there is a video stream");
demux->has_video = TRUE;
}
if (flags & 4) {
GST_DEBUG_OBJECT (demux, "there is an audio stream");
demux->has_audio = TRUE;
}
}
/* We don't care about the rest */
demux->need_header = FALSE;
beach:
return ret;
}

44
gst/flv/gstflvparse.h Normal file
View file

@ -0,0 +1,44 @@
/* GStreamer
* Copyright (C) <2007> Julien Moutte <julien@moutte.net>
*
* 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.
*/
#ifndef __FLV_PARSE_H__
#define __FLV_PARSE_H__
#include "gstflvdemux.h"
G_BEGIN_DECLS
GstFlowReturn gst_flv_parse_tag_script (GstFLVDemux * demux,
const guint8 * data, size_t data_size);
GstFlowReturn gst_flv_parse_tag_audio (GstFLVDemux * demux, const guint8 * data,
size_t data_size);
GstFlowReturn gst_flv_parse_tag_video (GstFLVDemux * demux, const guint8 * data,
size_t data_size);
GstFlowReturn gst_flv_parse_tag_type (GstFLVDemux * demux, const guint8 * data,
size_t data_size);
GstFlowReturn gst_flv_parse_header (GstFLVDemux * demux, const guint8 * data,
size_t data_size);
G_END_DECLS
#endif /* __FLV_PARSE_H__ */