1 /* GStreamer PNG Parser
2  * Copyright (C) <2013> Collabora Ltd
3  *  @author Olivier Crete <olivier.crete@collabora.com>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20 
21 #ifdef HAVE_CONFIG_H
22 #  include "config.h"
23 #endif
24 
25 #include "gstpngparse.h"
26 
27 #include <gst/base/base.h>
28 #include <gst/pbutils/pbutils.h>
29 
30 #define PNG_SIGNATURE G_GUINT64_CONSTANT (0x89504E470D0A1A0A)
31 
32 GST_DEBUG_CATEGORY (png_parse_debug);
33 #define GST_CAT_DEFAULT png_parse_debug
34 
35 static GstStaticPadTemplate srctemplate =
36 GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC,
37     GST_PAD_ALWAYS,
38     GST_STATIC_CAPS ("image/png, width = (int)[1, MAX], height = (int)[1, MAX],"
39         "parsed = (boolean) true")
40     );
41 
42 static GstStaticPadTemplate sinktemplate =
43 GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK,
44     GST_PAD_ALWAYS,
45     GST_STATIC_CAPS ("image/png")
46     );
47 
48 #define parent_class gst_png_parse_parent_class
49 G_DEFINE_TYPE (GstPngParse, gst_png_parse, GST_TYPE_BASE_PARSE);
50 
51 static gboolean gst_png_parse_start (GstBaseParse * parse);
52 static gboolean gst_png_parse_event (GstBaseParse * parse, GstEvent * event);
53 static GstFlowReturn gst_png_parse_handle_frame (GstBaseParse * parse,
54     GstBaseParseFrame * frame, gint * skipsize);
55 static GstFlowReturn gst_png_parse_pre_push_frame (GstBaseParse * parse,
56     GstBaseParseFrame * frame);
57 
58 static void
gst_png_parse_class_init(GstPngParseClass * klass)59 gst_png_parse_class_init (GstPngParseClass * klass)
60 {
61   GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
62   GstBaseParseClass *parse_class = GST_BASE_PARSE_CLASS (klass);
63 
64   GST_DEBUG_CATEGORY_INIT (png_parse_debug, "pngparse", 0, "png parser");
65 
66   gst_element_class_add_static_pad_template (gstelement_class, &srctemplate);
67   gst_element_class_add_static_pad_template (gstelement_class, &sinktemplate);
68   gst_element_class_set_static_metadata (gstelement_class, "PNG parser",
69       "Codec/Parser/Video/Image",
70       "Parses PNG files", "Olivier Crete <olivier.crete@collabora.com>");
71 
72   /* Override BaseParse vfuncs */
73   parse_class->start = GST_DEBUG_FUNCPTR (gst_png_parse_start);
74   parse_class->sink_event = GST_DEBUG_FUNCPTR (gst_png_parse_event);
75   parse_class->handle_frame = GST_DEBUG_FUNCPTR (gst_png_parse_handle_frame);
76   parse_class->pre_push_frame =
77       GST_DEBUG_FUNCPTR (gst_png_parse_pre_push_frame);
78 }
79 
80 static void
gst_png_parse_init(GstPngParse * pngparse)81 gst_png_parse_init (GstPngParse * pngparse)
82 {
83   GST_PAD_SET_ACCEPT_INTERSECT (GST_BASE_PARSE_SINK_PAD (pngparse));
84   GST_PAD_SET_ACCEPT_TEMPLATE (GST_BASE_PARSE_SINK_PAD (pngparse));
85 }
86 
87 static gboolean
gst_png_parse_start(GstBaseParse * parse)88 gst_png_parse_start (GstBaseParse * parse)
89 {
90   GstPngParse *pngparse = GST_PNG_PARSE (parse);
91 
92   GST_DEBUG_OBJECT (pngparse, "start");
93 
94   /* the start code and at least 2 empty frames (IHDR and IEND) */
95   gst_base_parse_set_min_frame_size (parse, 8 + 12 + 12);
96 
97   pngparse->width = 0;
98   pngparse->height = 0;
99 
100   pngparse->sent_codec_tag = FALSE;
101 
102   return TRUE;
103 }
104 
105 static gboolean
gst_png_parse_event(GstBaseParse * parse,GstEvent * event)106 gst_png_parse_event (GstBaseParse * parse, GstEvent * event)
107 {
108   gboolean res;
109 
110   res = GST_BASE_PARSE_CLASS (parent_class)->sink_event (parse, event);
111 
112   switch (GST_EVENT_TYPE (event)) {
113     case GST_EVENT_FLUSH_STOP:
114       /* the start code and at least 2 empty frames (IHDR and IEND) */
115       gst_base_parse_set_min_frame_size (parse, 8 + 12 + 12);
116       break;
117     default:
118       break;
119   }
120 
121   return res;
122 }
123 
124 static GstFlowReturn
gst_png_parse_handle_frame(GstBaseParse * parse,GstBaseParseFrame * frame,gint * skipsize)125 gst_png_parse_handle_frame (GstBaseParse * parse,
126     GstBaseParseFrame * frame, gint * skipsize)
127 {
128   GstPngParse *pngparse = GST_PNG_PARSE (parse);
129   GstMapInfo map;
130   GstByteReader reader;
131   GstFlowReturn ret = GST_FLOW_OK;
132   guint64 signature;
133   guint width = 0, height = 0;
134 
135   gst_buffer_map (frame->buffer, &map, GST_MAP_READ);
136   gst_byte_reader_init (&reader, map.data, map.size);
137 
138   if (!gst_byte_reader_peek_uint64_be (&reader, &signature))
139     goto beach;
140 
141   if (signature != PNG_SIGNATURE) {
142     for (;;) {
143       guint offset;
144 
145       offset = gst_byte_reader_masked_scan_uint32 (&reader, 0xffffffff,
146           0x89504E47, 0, gst_byte_reader_get_remaining (&reader));
147 
148       if (offset == -1) {
149         *skipsize = gst_byte_reader_get_remaining (&reader) - 4;
150         goto beach;
151       }
152 
153       gst_byte_reader_skip (&reader, offset);
154 
155       if (!gst_byte_reader_peek_uint64_be (&reader, &signature))
156         goto beach;
157 
158       if (signature == PNG_SIGNATURE) {
159         /* We're skipping, go out, we'll be back */
160         *skipsize = gst_byte_reader_get_pos (&reader);
161         goto beach;
162       }
163       gst_byte_reader_skip (&reader, 4);
164     }
165   }
166 
167   gst_byte_reader_skip (&reader, 8);
168 
169   for (;;) {
170     guint32 length;
171     guint32 code;
172 
173     if (!gst_byte_reader_get_uint32_be (&reader, &length))
174       goto beach;
175     if (!gst_byte_reader_get_uint32_le (&reader, &code))
176       goto beach;
177 
178     GST_TRACE_OBJECT (parse, "%" GST_FOURCC_FORMAT " chunk, %u bytes",
179         GST_FOURCC_ARGS (code), length);
180 
181     if (code == GST_MAKE_FOURCC ('I', 'H', 'D', 'R')) {
182       if (!gst_byte_reader_get_uint32_be (&reader, &width))
183         goto beach;
184       if (!gst_byte_reader_get_uint32_be (&reader, &height))
185         goto beach;
186       length -= 8;
187     } else if (code == GST_MAKE_FOURCC ('I', 'D', 'A', 'T')) {
188       gst_base_parse_set_min_frame_size (parse,
189           gst_byte_reader_get_pos (&reader) + 4 + length + 12);
190     }
191 
192     if (!gst_byte_reader_skip (&reader, length + 4))
193       goto beach;
194 
195     if (code == GST_MAKE_FOURCC ('I', 'E', 'N', 'D')) {
196       /* the start code and at least 2 empty frames (IHDR and IEND) */
197       gst_base_parse_set_min_frame_size (parse, 8 + 12 + 12);
198 
199       if (pngparse->width != width || pngparse->height != height) {
200         GstCaps *caps, *sink_caps;
201 
202         pngparse->height = height;
203         pngparse->width = width;
204 
205         caps = gst_caps_new_simple ("image/png",
206             "width", G_TYPE_INT, width, "height", G_TYPE_INT, height, NULL);
207 
208         sink_caps =
209             gst_pad_get_current_caps (GST_BASE_PARSE_SINK_PAD (pngparse));
210 
211         if (sink_caps) {
212           GstStructure *st;
213           gint fr_num, fr_denom;
214 
215           st = gst_caps_get_structure (sink_caps, 0);
216           if (st
217               && gst_structure_get_fraction (st, "framerate", &fr_num,
218                   &fr_denom)) {
219             gst_caps_set_simple (caps,
220                 "framerate", GST_TYPE_FRACTION, fr_num, fr_denom, NULL);
221           } else {
222             GST_WARNING_OBJECT (pngparse, "No framerate set");
223           }
224 
225           gst_caps_unref (sink_caps);
226         }
227 
228         if (!gst_pad_set_caps (GST_BASE_PARSE_SRC_PAD (parse), caps))
229           ret = GST_FLOW_NOT_NEGOTIATED;
230 
231         gst_caps_unref (caps);
232 
233         if (ret != GST_FLOW_OK)
234           goto beach;
235       }
236 
237 
238       gst_buffer_unmap (frame->buffer, &map);
239       return gst_base_parse_finish_frame (parse, frame,
240           gst_byte_reader_get_pos (&reader));
241     }
242   }
243 
244 beach:
245 
246   gst_buffer_unmap (frame->buffer, &map);
247 
248   return ret;
249 }
250 
251 static GstFlowReturn
gst_png_parse_pre_push_frame(GstBaseParse * parse,GstBaseParseFrame * frame)252 gst_png_parse_pre_push_frame (GstBaseParse * parse, GstBaseParseFrame * frame)
253 {
254   GstPngParse *pngparse = GST_PNG_PARSE (parse);
255 
256   if (!pngparse->sent_codec_tag) {
257     GstTagList *taglist;
258     GstCaps *caps;
259 
260     /* codec tag */
261     caps = gst_pad_get_current_caps (GST_BASE_PARSE_SRC_PAD (parse));
262     if (G_UNLIKELY (caps == NULL)) {
263       if (GST_PAD_IS_FLUSHING (GST_BASE_PARSE_SRC_PAD (parse))) {
264         GST_INFO_OBJECT (parse, "Src pad is flushing");
265         return GST_FLOW_FLUSHING;
266       } else {
267         GST_INFO_OBJECT (parse, "Src pad is not negotiated!");
268         return GST_FLOW_NOT_NEGOTIATED;
269       }
270     }
271 
272     taglist = gst_tag_list_new_empty ();
273     gst_pb_utils_add_codec_description_to_tag_list (taglist,
274         GST_TAG_VIDEO_CODEC, caps);
275     gst_caps_unref (caps);
276 
277     gst_base_parse_merge_tags (parse, taglist, GST_TAG_MERGE_REPLACE);
278     gst_tag_list_unref (taglist);
279 
280     /* also signals the end of first-frame processing */
281     pngparse->sent_codec_tag = TRUE;
282   }
283 
284   return GST_FLOW_OK;
285 }
286