481 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			481 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* GStreamer
 | |
|  * Copyright (C) 2010 David Schleef <ds@schleef.org>
 | |
|  *
 | |
|  * 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., 51 Franklin St, Fifth Floor,
 | |
|  * Boston, MA 02110-1301, USA.
 | |
|  */
 | |
| /**
 | |
|  * SECTION:element-diracparse
 | |
|  * @title: gstdiracparse
 | |
|  *
 | |
|  * The gstdiracparse element does FIXME stuff.
 | |
|  *
 | |
|  * ## Example launch line
 | |
|  * |[
 | |
|  * gst-launch-1.0 -v fakesrc ! gstdiracparse ! FIXME ! fakesink
 | |
|  * ]|
 | |
|  * FIXME Describe what the pipeline does.
 | |
|  *
 | |
|  */
 | |
| 
 | |
| #ifdef HAVE_CONFIG_H
 | |
| #include "config.h"
 | |
| #endif
 | |
| 
 | |
| #include <gst/gst.h>
 | |
| #include <gst/base/base.h>
 | |
| #include <gst/pbutils/pbutils.h>
 | |
| #include <string.h>
 | |
| #include "gstvideoparserselements.h"
 | |
| #include "gstdiracparse.h"
 | |
| #include "dirac_parse.h"
 | |
| 
 | |
| /* prototypes */
 | |
| 
 | |
| 
 | |
| static void gst_dirac_parse_set_property (GObject * object,
 | |
|     guint property_id, const GValue * value, GParamSpec * pspec);
 | |
| static void gst_dirac_parse_get_property (GObject * object,
 | |
|     guint property_id, GValue * value, GParamSpec * pspec);
 | |
| static void gst_dirac_parse_dispose (GObject * object);
 | |
| static void gst_dirac_parse_finalize (GObject * object);
 | |
| 
 | |
| static gboolean gst_dirac_parse_start (GstBaseParse * parse);
 | |
| static gboolean gst_dirac_parse_stop (GstBaseParse * parse);
 | |
| static gboolean gst_dirac_parse_set_sink_caps (GstBaseParse * parse,
 | |
|     GstCaps * caps);
 | |
| static GstCaps *gst_dirac_parse_get_sink_caps (GstBaseParse * parse,
 | |
|     GstCaps * filter);
 | |
| static GstFlowReturn gst_dirac_parse_handle_frame (GstBaseParse * parse,
 | |
|     GstBaseParseFrame * frame, gint * skipsize);
 | |
| static gboolean gst_dirac_parse_convert (GstBaseParse * parse,
 | |
|     GstFormat src_format, gint64 src_value, GstFormat dest_format,
 | |
|     gint64 * dest_value);
 | |
| static GstFlowReturn gst_dirac_parse_pre_push_frame (GstBaseParse * parse,
 | |
|     GstBaseParseFrame * frame);
 | |
| 
 | |
| enum
 | |
| {
 | |
|   PROP_0
 | |
| };
 | |
| 
 | |
| /* pad templates */
 | |
| 
 | |
| static GstStaticPadTemplate gst_dirac_parse_sink_template =
 | |
| GST_STATIC_PAD_TEMPLATE ("sink",
 | |
|     GST_PAD_SINK,
 | |
|     GST_PAD_ALWAYS,
 | |
|     GST_STATIC_CAPS ("video/x-dirac")
 | |
|     );
 | |
| 
 | |
| static GstStaticPadTemplate gst_dirac_parse_src_template =
 | |
| GST_STATIC_PAD_TEMPLATE ("src",
 | |
|     GST_PAD_SRC,
 | |
|     GST_PAD_ALWAYS,
 | |
|     GST_STATIC_CAPS ("video/x-dirac, parsed=(boolean)TRUE, "
 | |
|         "width=(int)[1,MAX], height=(int)[1,MAX], "
 | |
|         "framerate=(fraction)[0/1,MAX], "
 | |
|         "pixel-aspect-ratio=(fraction)[0/1,MAX], "
 | |
|         "interlace-mode=(string) { progressive, interleaved }, "
 | |
|         "profile=(string){ vc2-low-delay, vc2-simple, vc2-main, main }, "
 | |
|         "level=(string) { 0, 1, 128}")
 | |
|     );
 | |
| 
 | |
| /* class initialization */
 | |
| 
 | |
| #define parent_class gst_dirac_parse_parent_class
 | |
| G_DEFINE_TYPE (GstDiracParse, gst_dirac_parse, GST_TYPE_BASE_PARSE);
 | |
| GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (diracparse, "diracparse", GST_RANK_NONE,
 | |
|     GST_TYPE_DIRAC_PARSE, videoparsers_element_init (plugin));
 | |
| 
 | |
| static void
 | |
| gst_dirac_parse_class_init (GstDiracParseClass * klass)
 | |
| {
 | |
|   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
 | |
|   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
 | |
|   GstBaseParseClass *base_parse_class = GST_BASE_PARSE_CLASS (klass);
 | |
| 
 | |
|   gobject_class->set_property = gst_dirac_parse_set_property;
 | |
|   gobject_class->get_property = gst_dirac_parse_get_property;
 | |
|   gobject_class->dispose = gst_dirac_parse_dispose;
 | |
|   gobject_class->finalize = gst_dirac_parse_finalize;
 | |
| 
 | |
|   gst_element_class_add_static_pad_template (element_class,
 | |
|       &gst_dirac_parse_src_template);
 | |
|   gst_element_class_add_static_pad_template (element_class,
 | |
|       &gst_dirac_parse_sink_template);
 | |
| 
 | |
|   gst_element_class_set_static_metadata (element_class, "Dirac parser",
 | |
|       "Codec/Parser/Video", "Parses Dirac streams",
 | |
|       "David Schleef <ds@schleef.org>");
 | |
| 
 | |
|   base_parse_class->start = GST_DEBUG_FUNCPTR (gst_dirac_parse_start);
 | |
|   base_parse_class->stop = GST_DEBUG_FUNCPTR (gst_dirac_parse_stop);
 | |
|   base_parse_class->set_sink_caps =
 | |
|       GST_DEBUG_FUNCPTR (gst_dirac_parse_set_sink_caps);
 | |
|   base_parse_class->get_sink_caps =
 | |
|       GST_DEBUG_FUNCPTR (gst_dirac_parse_get_sink_caps);
 | |
|   base_parse_class->handle_frame =
 | |
|       GST_DEBUG_FUNCPTR (gst_dirac_parse_handle_frame);
 | |
|   base_parse_class->convert = GST_DEBUG_FUNCPTR (gst_dirac_parse_convert);
 | |
|   base_parse_class->pre_push_frame =
 | |
|       GST_DEBUG_FUNCPTR (gst_dirac_parse_pre_push_frame);
 | |
| 
 | |
| }
 | |
| 
 | |
| static void
 | |
| gst_dirac_parse_init (GstDiracParse * diracparse)
 | |
| {
 | |
|   gst_base_parse_set_min_frame_size (GST_BASE_PARSE (diracparse), 13);
 | |
|   gst_base_parse_set_pts_interpolation (GST_BASE_PARSE (diracparse), FALSE);
 | |
|   GST_PAD_SET_ACCEPT_INTERSECT (GST_BASE_PARSE_SINK_PAD (diracparse));
 | |
|   GST_PAD_SET_ACCEPT_TEMPLATE (GST_BASE_PARSE_SINK_PAD (diracparse));
 | |
| }
 | |
| 
 | |
| void
 | |
| gst_dirac_parse_set_property (GObject * object, guint property_id,
 | |
|     const GValue * value, GParamSpec * pspec)
 | |
| {
 | |
|   g_return_if_fail (GST_IS_DIRAC_PARSE (object));
 | |
| 
 | |
|   switch (property_id) {
 | |
|     default:
 | |
|       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
 | |
|       break;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void
 | |
| gst_dirac_parse_get_property (GObject * object, guint property_id,
 | |
|     GValue * value, GParamSpec * pspec)
 | |
| {
 | |
|   g_return_if_fail (GST_IS_DIRAC_PARSE (object));
 | |
| 
 | |
|   switch (property_id) {
 | |
|     default:
 | |
|       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
 | |
|       break;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void
 | |
| gst_dirac_parse_dispose (GObject * object)
 | |
| {
 | |
|   g_return_if_fail (GST_IS_DIRAC_PARSE (object));
 | |
| 
 | |
|   /* clean up as possible.  may be called multiple times */
 | |
| 
 | |
|   G_OBJECT_CLASS (parent_class)->dispose (object);
 | |
| }
 | |
| 
 | |
| void
 | |
| gst_dirac_parse_finalize (GObject * object)
 | |
| {
 | |
|   g_return_if_fail (GST_IS_DIRAC_PARSE (object));
 | |
| 
 | |
|   /* clean up object here */
 | |
| 
 | |
|   G_OBJECT_CLASS (parent_class)->finalize (object);
 | |
| }
 | |
| 
 | |
| 
 | |
| static gboolean
 | |
| gst_dirac_parse_start (GstBaseParse * parse)
 | |
| {
 | |
|   GstDiracParse *diracparse = GST_DIRAC_PARSE (parse);
 | |
| 
 | |
|   gst_base_parse_set_min_frame_size (parse, 13);
 | |
| 
 | |
|   diracparse->sent_codec_tag = FALSE;
 | |
| 
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| gst_dirac_parse_stop (GstBaseParse * parse)
 | |
| {
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| gst_dirac_parse_set_sink_caps (GstBaseParse * parse, GstCaps * caps)
 | |
| {
 | |
|   /* Called when sink caps are set */
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| static const gchar *
 | |
| get_profile_name (int profile)
 | |
| {
 | |
|   switch (profile) {
 | |
|     case 0:
 | |
|       return "vc2-low-delay";
 | |
|     case 1:
 | |
|       return "vc2-simple";
 | |
|     case 2:
 | |
|       return "vc2-main";
 | |
|     case 8:
 | |
|       return "main";
 | |
|     default:
 | |
|       break;
 | |
|   }
 | |
|   return "unknown";
 | |
| }
 | |
| 
 | |
| static const gchar *
 | |
| get_level_name (int level)
 | |
| {
 | |
|   switch (level) {
 | |
|     case 0:
 | |
|       return "0";
 | |
|     case 1:
 | |
|       return "1";
 | |
|     case 128:
 | |
|       return "128";
 | |
|     default:
 | |
|       break;
 | |
|   }
 | |
|   /* need to add it to template caps, so return 0 for now */
 | |
|   GST_WARNING ("unhandled dirac level %u", level);
 | |
|   return "0";
 | |
| }
 | |
| 
 | |
| static GstFlowReturn
 | |
| gst_dirac_parse_handle_frame (GstBaseParse * parse,
 | |
|     GstBaseParseFrame * frame, gint * skipsize)
 | |
| {
 | |
|   int off;
 | |
|   guint32 next_header;
 | |
|   GstMapInfo map;
 | |
|   guint8 *data;
 | |
|   gsize size;
 | |
|   gboolean have_picture = FALSE;
 | |
|   int offset;
 | |
|   guint framesize = 0;
 | |
| 
 | |
|   gst_buffer_map (frame->buffer, &map, GST_MAP_READ);
 | |
|   data = map.data;
 | |
|   size = map.size;
 | |
| 
 | |
|   if (G_UNLIKELY (size < 13)) {
 | |
|     *skipsize = 1;
 | |
|     goto out;
 | |
|   }
 | |
| 
 | |
|   GST_DEBUG ("%" G_GSIZE_FORMAT ": %02x %02x %02x %02x", size, data[0], data[1],
 | |
|       data[2], data[3]);
 | |
| 
 | |
|   if (GST_READ_UINT32_BE (data) != 0x42424344) {
 | |
|     GstByteReader reader;
 | |
| 
 | |
|     gst_byte_reader_init (&reader, data, size);
 | |
|     off = gst_byte_reader_masked_scan_uint32 (&reader, 0xffffffff,
 | |
|         0x42424344, 0, size);
 | |
| 
 | |
|     if (off < 0) {
 | |
|       *skipsize = size - 3;
 | |
|       goto out;
 | |
|     }
 | |
| 
 | |
|     GST_LOG_OBJECT (parse, "possible sync at buffer offset %d", off);
 | |
| 
 | |
|     GST_DEBUG ("skipping %d", off);
 | |
|     *skipsize = off;
 | |
|     goto out;
 | |
|   }
 | |
| 
 | |
|   /* have sync, parse chunks */
 | |
| 
 | |
|   offset = 0;
 | |
|   while (!have_picture) {
 | |
|     GST_DEBUG ("offset %d:", offset);
 | |
| 
 | |
|     if (offset + 13 >= size) {
 | |
|       framesize = offset + 13;
 | |
|       goto out;
 | |
|     }
 | |
| 
 | |
|     GST_DEBUG ("chunk type %02x", data[offset + 4]);
 | |
| 
 | |
|     if (GST_READ_UINT32_BE (data + offset) != 0x42424344) {
 | |
|       GST_DEBUG ("bad header");
 | |
|       *skipsize = 3;
 | |
|       goto out;
 | |
|     }
 | |
| 
 | |
|     next_header = GST_READ_UINT32_BE (data + offset + 5);
 | |
|     GST_DEBUG ("next_header %d", next_header);
 | |
|     if (next_header == 0)
 | |
|       next_header = 13;
 | |
| 
 | |
|     if (SCHRO_PARSE_CODE_IS_PICTURE (data[offset + 4])) {
 | |
|       have_picture = TRUE;
 | |
|     }
 | |
| 
 | |
|     offset += next_header;
 | |
|     if (offset >= size) {
 | |
|       framesize = offset;
 | |
|       goto out;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   gst_buffer_unmap (frame->buffer, &map);
 | |
| 
 | |
|   framesize = offset;
 | |
|   GST_DEBUG ("framesize %d", framesize);
 | |
| 
 | |
|   g_assert (framesize <= size);
 | |
| 
 | |
|   if (data[4] == SCHRO_PARSE_CODE_SEQUENCE_HEADER) {
 | |
|     GstCaps *caps;
 | |
|     GstDiracParse *diracparse = GST_DIRAC_PARSE (parse);
 | |
|     DiracSequenceHeader sequence_header;
 | |
|     int ret;
 | |
| 
 | |
|     ret = dirac_sequence_header_parse (&sequence_header, data + 13, size - 13);
 | |
|     if (ret) {
 | |
|       memcpy (&diracparse->sequence_header, &sequence_header,
 | |
|           sizeof (sequence_header));
 | |
|       caps = gst_caps_new_simple ("video/x-dirac",
 | |
|           "width", G_TYPE_INT, sequence_header.width,
 | |
|           "height", G_TYPE_INT, sequence_header.height,
 | |
|           "framerate", GST_TYPE_FRACTION,
 | |
|           sequence_header.frame_rate_numerator,
 | |
|           sequence_header.frame_rate_denominator,
 | |
|           "pixel-aspect-ratio", GST_TYPE_FRACTION,
 | |
|           sequence_header.aspect_ratio_numerator,
 | |
|           sequence_header.aspect_ratio_denominator,
 | |
|           "interlace-mode", G_TYPE_STRING,
 | |
|           sequence_header.interlaced ? "interleaved" : "progressive",
 | |
|           "profile", G_TYPE_STRING, get_profile_name (sequence_header.profile),
 | |
|           "level", G_TYPE_STRING, get_level_name (sequence_header.level), NULL);
 | |
|       gst_pad_set_caps (GST_BASE_PARSE_SRC_PAD (parse), caps);
 | |
|       gst_caps_unref (caps);
 | |
| 
 | |
|       gst_base_parse_set_frame_rate (parse,
 | |
|           sequence_header.frame_rate_numerator,
 | |
|           sequence_header.frame_rate_denominator, 0, 0);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   gst_base_parse_set_min_frame_size (parse, 13);
 | |
| 
 | |
|   return gst_base_parse_finish_frame (parse, frame, framesize);
 | |
| 
 | |
| out:
 | |
|   gst_buffer_unmap (frame->buffer, &map);
 | |
|   if (framesize)
 | |
|     gst_base_parse_set_min_frame_size (parse, framesize);
 | |
|   return GST_FLOW_OK;
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| gst_dirac_parse_convert (GstBaseParse * parse, GstFormat src_format,
 | |
|     gint64 src_value, GstFormat dest_format, gint64 * dest_value)
 | |
| {
 | |
|   /* Convert between formats */
 | |
| 
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| static GstFlowReturn
 | |
| gst_dirac_parse_pre_push_frame (GstBaseParse * parse, GstBaseParseFrame * frame)
 | |
| {
 | |
|   GstDiracParse *diracparse = GST_DIRAC_PARSE (parse);
 | |
| 
 | |
|   if (!diracparse->sent_codec_tag) {
 | |
|     GstTagList *taglist;
 | |
|     GstCaps *caps;
 | |
| 
 | |
|     /* codec tag */
 | |
|     caps = gst_pad_get_current_caps (GST_BASE_PARSE_SRC_PAD (parse));
 | |
|     if (G_UNLIKELY (caps == NULL)) {
 | |
|       if (GST_PAD_IS_FLUSHING (GST_BASE_PARSE_SRC_PAD (parse))) {
 | |
|         GST_INFO_OBJECT (parse, "Src pad is flushing");
 | |
|         return GST_FLOW_FLUSHING;
 | |
|       } else {
 | |
|         GST_INFO_OBJECT (parse, "Src pad is not negotiated!");
 | |
|         return GST_FLOW_NOT_NEGOTIATED;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     taglist = gst_tag_list_new_empty ();
 | |
|     gst_pb_utils_add_codec_description_to_tag_list (taglist,
 | |
|         GST_TAG_VIDEO_CODEC, caps);
 | |
|     gst_caps_unref (caps);
 | |
| 
 | |
|     gst_base_parse_merge_tags (parse, taglist, GST_TAG_MERGE_REPLACE);
 | |
|     gst_tag_list_unref (taglist);
 | |
| 
 | |
|     /* also signals the end of first-frame processing */
 | |
|     diracparse->sent_codec_tag = TRUE;
 | |
|   }
 | |
| 
 | |
|   return GST_FLOW_OK;
 | |
| }
 | |
| 
 | |
| static void
 | |
| remove_fields (GstCaps * caps)
 | |
| {
 | |
|   guint i, n;
 | |
| 
 | |
|   n = gst_caps_get_size (caps);
 | |
|   for (i = 0; i < n; i++) {
 | |
|     GstStructure *s = gst_caps_get_structure (caps, i);
 | |
| 
 | |
|     gst_structure_remove_field (s, "parsed");
 | |
|   }
 | |
| }
 | |
| 
 | |
| static GstCaps *
 | |
| gst_dirac_parse_get_sink_caps (GstBaseParse * parse, GstCaps * filter)
 | |
| {
 | |
|   GstCaps *peercaps, *templ;
 | |
|   GstCaps *res;
 | |
| 
 | |
|   templ = gst_pad_get_pad_template_caps (GST_BASE_PARSE_SINK_PAD (parse));
 | |
|   if (filter) {
 | |
|     GstCaps *fcopy = gst_caps_copy (filter);
 | |
|     /* Remove the fields we convert */
 | |
|     remove_fields (fcopy);
 | |
|     peercaps = gst_pad_peer_query_caps (GST_BASE_PARSE_SRC_PAD (parse), fcopy);
 | |
|     gst_caps_unref (fcopy);
 | |
|   } else
 | |
|     peercaps = gst_pad_peer_query_caps (GST_BASE_PARSE_SRC_PAD (parse), NULL);
 | |
| 
 | |
|   if (peercaps) {
 | |
|     /* Remove the parsed field */
 | |
|     peercaps = gst_caps_make_writable (peercaps);
 | |
|     remove_fields (peercaps);
 | |
| 
 | |
|     res = gst_caps_intersect_full (peercaps, templ, GST_CAPS_INTERSECT_FIRST);
 | |
|     gst_caps_unref (peercaps);
 | |
|     gst_caps_unref (templ);
 | |
|   } else {
 | |
|     res = templ;
 | |
|   }
 | |
| 
 | |
|   if (filter) {
 | |
|     GstCaps *intersection;
 | |
| 
 | |
|     intersection =
 | |
|         gst_caps_intersect_full (filter, res, GST_CAPS_INTERSECT_FIRST);
 | |
|     gst_caps_unref (res);
 | |
|     res = intersection;
 | |
|   }
 | |
| 
 | |
|   return res;
 | |
| }
 |