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