diff options
Diffstat (limited to 'gst/flv/gstflvparse.c')
-rw-r--r-- | gst/flv/gstflvparse.c | 797 |
1 files changed, 797 insertions, 0 deletions
diff --git a/gst/flv/gstflvparse.c b/gst/flv/gstflvparse.c new file mode 100644 index 00000000..023df66c --- /dev/null +++ b/gst/flv/gstflvparse.c @@ -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; +} |