1 /* GStreamer PNM decoder
2 * Copyright (C) 2009 Lutz Mueller <lutz@users.sourceforge.net>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20 /**
21 * SECTION:element-pnmdec
22 * @title: pnmdec
23 *
24 * Decodes pnm images.
25 *
26 * ## Example launch line
27 * |[
28 * gst-launch-1.0 filesrc location=test.pnm ! pnmdec ! videoconvert ! autovideosink
29 * ]| The above pipeline reads a pnm file and renders it to the screen.
30 *
31 */
32
33 #ifdef HAVE_CONFIG_H
34 #include "config.h"
35 #endif
36
37 #include "gstpnmdec.h"
38 #include "gstpnmutils.h"
39
40 #include <gst/gstutils.h>
41 #include <gst/video/video.h>
42 #include <gst/base/gstbytereader.h>
43 #include <string.h>
44 #include <stdio.h>
45
46 static gboolean gst_pnmdec_start (GstVideoDecoder * decoder);
47 static GstFlowReturn gst_pnmdec_finish (GstVideoDecoder * decoder);
48 static gboolean gst_pnmdec_set_format (GstVideoDecoder * decoder,
49 GstVideoCodecState * state);
50 static gboolean gst_pnmdec_stop (GstVideoDecoder * decoder);
51 static GstFlowReturn gst_pnmdec_parse (GstVideoDecoder * decoder,
52 GstVideoCodecFrame * frame, GstAdapter * adapter, gboolean at_eos);
53 static GstFlowReturn gst_pnmdec_handle_frame (GstVideoDecoder * decoder,
54 GstVideoCodecFrame * frame);
55 static GstFlowReturn
56 gst_pnmdec_parse_ascii (GstPnmdec * s, const guint8 * b, guint bs);
57
58 G_DEFINE_TYPE (GstPnmdec, gst_pnmdec, GST_TYPE_VIDEO_DECODER);
59
60 static GstStaticPadTemplate gst_pnmdec_src_pad_template =
61 GST_STATIC_PAD_TEMPLATE ("src",
62 GST_PAD_SRC,
63 GST_PAD_ALWAYS,
64 GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE
65 ("{ RGB, GRAY8, GRAY16_BE, GRAY16_LE }")));
66
67 static GstStaticCaps gst_pnmdec_gray16_caps =
68 GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{ GRAY16_BE, GRAY16_LE }"));
69
70 static GstStaticPadTemplate gst_pnmdec_sink_pad_template =
71 GST_STATIC_PAD_TEMPLATE ("sink",
72 GST_PAD_SINK,
73 GST_PAD_ALWAYS,
74 GST_STATIC_CAPS (MIME_ALL));
75
76 GST_DEBUG_CATEGORY (pnmdecoder_debug);
77 #define GST_CAT_DEFAULT pnmdecoder_debug
78
79 static void
gst_pnmdec_class_init(GstPnmdecClass * klass)80 gst_pnmdec_class_init (GstPnmdecClass * klass)
81 {
82 GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
83 GstVideoDecoderClass *vdec_class = (GstVideoDecoderClass *) klass;
84
85 GST_DEBUG_CATEGORY_INIT (pnmdecoder_debug, "pnmdec", 0, "PNM Video Decoder");
86
87 gst_element_class_add_static_pad_template (element_class,
88 &gst_pnmdec_src_pad_template);
89 gst_element_class_add_static_pad_template (element_class,
90 &gst_pnmdec_sink_pad_template);
91
92 gst_element_class_set_static_metadata (element_class, "PNM image decoder",
93 "Codec/Decoder/Image",
94 "Decodes images in portable pixmap/graymap/bitmap/anymamp (PNM) format",
95 "Lutz Mueller <lutz@users.sourceforge.net>");
96
97 vdec_class->start = gst_pnmdec_start;
98 vdec_class->finish = gst_pnmdec_finish;
99 vdec_class->stop = gst_pnmdec_stop;
100 vdec_class->parse = gst_pnmdec_parse;
101 vdec_class->handle_frame = gst_pnmdec_handle_frame;
102 vdec_class->set_format = gst_pnmdec_set_format;
103 }
104
105 static void
gst_pnmdec_flush(GstPnmdec * s)106 gst_pnmdec_flush (GstPnmdec * s)
107 {
108 memset (&s->mngr, 0, sizeof (s->mngr));
109 s->size = 0;
110 s->current_size = 0;
111 if (s->buf) {
112 gst_buffer_unref (s->buf);
113 s->buf = NULL;
114 }
115 }
116
117 static void
gst_pnmdec_init(GstPnmdec * s)118 gst_pnmdec_init (GstPnmdec * s)
119 {
120 /* Initialize decoder */
121 s->buf = NULL;
122 gst_pnmdec_flush (s);
123
124 gst_video_decoder_set_use_default_pad_acceptcaps (GST_VIDEO_DECODER_CAST
125 (s), TRUE);
126 GST_PAD_SET_ACCEPT_TEMPLATE (GST_VIDEO_DECODER_SINK_PAD (s));
127 }
128
129 static GstFlowReturn
gst_pnmdec_negotiate(GstVideoDecoder * decoder)130 gst_pnmdec_negotiate (GstVideoDecoder * decoder)
131 {
132 GstPnmdec *pnmdec = (GstPnmdec *) decoder;
133 GstVideoFormat fmt = GST_VIDEO_FORMAT_UNKNOWN;
134 GstVideoCodecState *output_state;
135
136 switch (pnmdec->mngr.info.type) {
137 case GST_PNM_TYPE_BITMAP:
138 if (pnmdec->mngr.info.encoding == GST_PNM_ENCODING_ASCII) {
139 return GST_FLOW_ERROR;
140 }
141 pnmdec->size = pnmdec->mngr.info.width * pnmdec->mngr.info.height * 1;
142 fmt = GST_VIDEO_FORMAT_GRAY8;
143 break;
144 case GST_PNM_TYPE_GRAYMAP:
145 if (pnmdec->mngr.info.max > 255) {
146 GstCaps *gray16_caps = gst_static_caps_get (&gst_pnmdec_gray16_caps);
147 GstCaps *peercaps;
148 GstStructure *peerstruct;
149 const gchar *fmtstr;
150
151 pnmdec->size = pnmdec->mngr.info.width * pnmdec->mngr.info.height * 2;
152 /* perform some basic negotiation to resolve which endianess,
153 * if any, is supported by the component downstream. Query
154 * the peer caps, intersecting with our preferred caps
155 */
156 peercaps =
157 gst_pad_peer_query_caps (GST_VIDEO_DECODER_SRC_PAD (decoder),
158 gray16_caps);
159 gst_caps_unref (gray16_caps);
160
161 GST_DEBUG ("Received caps from peer: %" GST_PTR_FORMAT, peercaps);
162 if (gst_caps_is_empty (peercaps)) {
163 gst_caps_unref (peercaps);
164 return FALSE;
165 }
166
167 if (!gst_caps_is_fixed (peercaps))
168 peercaps = gst_caps_fixate (peercaps);
169
170 peerstruct = gst_caps_get_structure (peercaps, 0);
171 fmtstr = gst_structure_get_string (peerstruct, "format");
172 if (fmtstr) {
173 if (g_str_equal (fmtstr, "GRAY16_BE")) {
174 fmt = GST_VIDEO_FORMAT_GRAY16_BE;
175 } else if (g_str_equal (fmtstr, "GRAY16_LE")) {
176 fmt = GST_VIDEO_FORMAT_GRAY16_LE;
177 }
178 }
179 gst_caps_unref (peercaps);
180 } else {
181 pnmdec->size = pnmdec->mngr.info.width * pnmdec->mngr.info.height * 1;
182 fmt = GST_VIDEO_FORMAT_GRAY8;
183 }
184 break;
185 case GST_PNM_TYPE_PIXMAP:
186 pnmdec->size = pnmdec->mngr.info.width * pnmdec->mngr.info.height * 3;
187 fmt = GST_VIDEO_FORMAT_RGB;
188 break;
189 }
190
191 if (fmt == GST_VIDEO_FORMAT_UNKNOWN)
192 return GST_FLOW_NOT_NEGOTIATED;
193
194 pnmdec->out_format = fmt;
195
196 output_state =
197 gst_video_decoder_set_output_state (decoder, fmt,
198 pnmdec->mngr.info.width, pnmdec->mngr.info.height, pnmdec->input_state);
199 gst_video_codec_state_unref (output_state);
200
201 if (gst_video_decoder_negotiate (decoder) == FALSE)
202 return GST_FLOW_NOT_NEGOTIATED;
203
204 return GST_FLOW_OK;
205 }
206
207 static gboolean
gst_pnmdec_set_format(GstVideoDecoder * decoder,GstVideoCodecState * state)208 gst_pnmdec_set_format (GstVideoDecoder * decoder, GstVideoCodecState * state)
209 {
210 GstPnmdec *pnmdec = (GstPnmdec *) decoder;
211
212 gst_pnmdec_negotiate (decoder);
213
214 if (pnmdec->input_state)
215 gst_video_codec_state_unref (pnmdec->input_state);
216 pnmdec->input_state = gst_video_codec_state_ref (state);
217
218 return TRUE;
219 }
220
221 static gboolean
gst_pnmdec_stop(GstVideoDecoder * decoder)222 gst_pnmdec_stop (GstVideoDecoder * decoder)
223 {
224 GstPnmdec *pnmdec = (GstPnmdec *) decoder;
225
226 if (pnmdec->input_state) {
227 gst_video_codec_state_unref (pnmdec->input_state);
228 pnmdec->input_state = NULL;
229 }
230
231 if (pnmdec->buf) {
232 gst_buffer_unref (pnmdec->buf);
233 pnmdec->buf = NULL;
234 }
235 return TRUE;
236 }
237
238 static GstFlowReturn
gst_pnmdec_parse_ascii(GstPnmdec * s,const guint8 * b,guint bs)239 gst_pnmdec_parse_ascii (GstPnmdec * s, const guint8 * b, guint bs)
240 {
241 GScanner *scanner;
242 guint i = 0;
243 guint target, last_val = 0;
244 GstMapInfo map;
245 guint8 *outdata;
246
247 if (!s->buf)
248 return GST_FLOW_OK;
249
250 target = s->size - s->current_size;
251
252 gst_buffer_map (s->buf, &map, GST_MAP_WRITE);
253
254 if (bs) {
255 GST_MEMDUMP_OBJECT (s, "Starting parse:", b, MIN (16, bs));
256 }
257
258 /* leave the number of bytes already parsed */
259 outdata = map.data + s->current_size;
260
261 if (s->have_last_val) {
262 while (bs && *b >= '0' && *b <= '9') {
263 s->last_val = 10 * s->last_val + *b - '0';
264 b++;
265 if (!--bs) {
266 goto drop_error;
267 }
268 }
269 if (s->last_val > s->mngr.info.max) {
270 GST_DEBUG_OBJECT (s, "Corrupt ASCII encoded PNM file.");
271 goto drop_error;
272 }
273
274 GST_LOG_OBJECT (s, "Collected partial value from previous parse - %u",
275 s->last_val);
276 if (s->mngr.info.max > 255) {
277 if (i + 1 >= target) {
278 GST_DEBUG_OBJECT (s, "PNM file contains too much data.");
279 goto drop_error;
280 }
281 if (s->out_format == GST_VIDEO_FORMAT_GRAY16_BE)
282 GST_WRITE_UINT16_BE (outdata + i, s->last_val);
283 else
284 GST_WRITE_UINT16_LE (outdata + i, s->last_val);
285 i += 2;
286 } else {
287 outdata[i++] = s->last_val;
288 }
289 last_val = s->last_val;
290 s->have_last_val = FALSE;
291 }
292
293 /* Might be no data if we're at EOS */
294 if (!bs)
295 goto done;
296
297 scanner = g_scanner_new (NULL);
298 g_scanner_input_text (scanner, (gchar *) b, bs);
299 while (!g_scanner_eof (scanner)) {
300 switch (g_scanner_get_next_token (scanner)) {
301 case G_TOKEN_INT:
302 if (s->mngr.info.max > 255) {
303 if (i + 1 >= target) {
304 GST_DEBUG_OBJECT (s,
305 "PNM file contains too much data after line %u col %u.",
306 scanner->line, scanner->position);
307 g_scanner_destroy (scanner);
308 goto done; // drop_error;
309 }
310 if (s->out_format == GST_VIDEO_FORMAT_GRAY16_BE)
311 GST_WRITE_UINT16_BE (outdata + i, scanner->value.v_int);
312 else
313 GST_WRITE_UINT16_LE (outdata + i, scanner->value.v_int);
314 i += 2;
315 } else {
316 if (i == target) {
317 GST_DEBUG_OBJECT (s,
318 "PNM file contains too much data after line %u col %u.",
319 scanner->line, scanner->position);
320 g_scanner_destroy (scanner);
321 goto drop_error;
322 }
323 outdata[i++] = scanner->value.v_int;
324 }
325 last_val = scanner->value.v_int;
326 break;
327 default:
328 /* Should we care? */ ;
329 }
330 }
331 g_scanner_destroy (scanner);
332
333 /* If we didn't get the whole image, handle the last byte with care. */
334 if (i && i < target && b[bs - 1] >= '0' && b[bs - 1] <= '9') {
335 s->last_val = last_val;
336 s->have_last_val = TRUE;
337 if (s->mngr.info.max > 255)
338 i -= 2;
339 else
340 i--;
341 GST_LOG_OBJECT (s, "Stored last value %u for next parse cycle",
342 s->last_val);
343 }
344
345 done:
346 /* Update the number of output bytes parsed in this scan */
347 s->current_size += i;
348 GST_LOG_OBJECT (s, "Parsed %u bytes, now have %u bytes of %u output",
349 i, s->current_size, s->size);
350 gst_buffer_unmap (s->buf, &map);
351
352 return GST_FLOW_OK;
353
354 drop_error:
355 gst_buffer_unmap (s->buf, &map);
356
357 return GST_FLOW_ERROR;
358 }
359
360 static GstFlowReturn
gst_pnmdec_handle_frame(GstVideoDecoder * decoder,GstVideoCodecFrame * frame)361 gst_pnmdec_handle_frame (GstVideoDecoder * decoder, GstVideoCodecFrame * frame)
362 {
363 GstPnmdec *s = (GstPnmdec *) decoder;
364 GstMapInfo imap, omap;
365 guint i_rowstride;
366 guint o_rowstride;
367 GstFlowReturn r = GST_FLOW_OK;
368 gint bytes, i, total_bytes = 0;
369
370 r = gst_video_decoder_allocate_output_frame (decoder, frame);
371 if (r != GST_FLOW_OK) {
372 gst_video_decoder_drop_frame (GST_VIDEO_DECODER (s), frame);
373 goto out;
374 }
375
376 if (s->mngr.info.encoding == GST_PNM_ENCODING_ASCII) {
377 /* In case of ASCII parsed data is stored in buf, so input needs to be
378 taken from here for frame processing */
379 gst_buffer_map (s->buf, &imap, GST_MAP_READ);
380 } else {
381 gst_buffer_map (frame->input_buffer, &imap, GST_MAP_READ);
382 }
383 gst_buffer_map (frame->output_buffer, &omap, GST_MAP_WRITE);
384
385 gst_buffer_copy_into (frame->output_buffer, frame->input_buffer,
386 GST_BUFFER_COPY_METADATA, 0, 0);
387
388 if (s->mngr.info.type == GST_PNM_TYPE_BITMAP) {
389 bytes = (s->mngr.info.width * s->mngr.info.height + 7) / 8;
390 for (i = 0; i < bytes; i++) {
391 omap.data[i * 8] = (imap.data[i] & 0x80) ? 0 : 255;
392 omap.data[i * 8 + 1] = (imap.data[i] & 0x40) ? 0 : 255;
393 omap.data[i * 8 + 2] = (imap.data[i] & 0x20) ? 0 : 255;
394 omap.data[i * 8 + 3] = (imap.data[i] & 0x10) ? 0 : 255;
395 omap.data[i * 8 + 4] = (imap.data[i] & 0x08) ? 0 : 255;
396 omap.data[i * 8 + 5] = (imap.data[i] & 0x04) ? 0 : 255;
397 omap.data[i * 8 + 6] = (imap.data[i] & 0x02) ? 0 : 255;
398 omap.data[i * 8 + 7] = (imap.data[i] & 0x01) ? 0 : 255;
399 }
400 total_bytes = bytes * 8;
401 } else
402 /* Need to convert from PNM rowstride to GStreamer rowstride */
403 if (s->mngr.info.width % 4 != 0) {
404 if (s->mngr.info.type == GST_PNM_TYPE_PIXMAP) {
405 i_rowstride = 3 * s->mngr.info.width;
406 o_rowstride = GST_ROUND_UP_4 (i_rowstride);
407 } else {
408 if (s->mngr.info.max > 255)
409 i_rowstride = s->mngr.info.width * 2;
410 else
411 i_rowstride = s->mngr.info.width;
412 o_rowstride = GST_ROUND_UP_4 (i_rowstride);
413 }
414
415 for (i = 0; i < s->mngr.info.height; i++)
416 memcpy (omap.data + i * o_rowstride, imap.data + i * i_rowstride,
417 i_rowstride);
418 total_bytes = o_rowstride * s->mngr.info.height;
419 } else {
420 memcpy (omap.data, imap.data, s->size);
421 total_bytes = s->size;
422 }
423
424 if (s->mngr.info.type != GST_PNM_TYPE_BITMAP) {
425 if (s->mngr.info.max > 255 && s->mngr.info.max < 65535) {
426 /* Convert the pixels from 0 - max range to 0 - 65535 range
427 * and appropriate endianness (input is always BE) */
428 guint8 *data = omap.data;
429 gint max = s->mngr.info.max;
430 if (s->out_format == GST_VIDEO_FORMAT_GRAY16_BE) {
431 for (i = 0; i < total_bytes; i += 2) {
432 /* Clamp to 65535 */
433 guint16 val = GST_READ_UINT16_BE (data + i);
434 val = (val > max) ? 65535 : 65535 * val / max;
435 GST_WRITE_UINT16_BE (data + i, val);
436 }
437 } else {
438 for (i = 0; i < total_bytes; i += 2) {
439 /* Clamp to 65535 */
440 guint16 val = GST_READ_UINT16_BE (data + i);
441 val = (val > max) ? 65535 : 65535 * val / max;
442 GST_WRITE_UINT16_LE (data + i, val);
443 }
444 }
445 } else if (s->mngr.info.max < 255) {
446 /* Convert the pixels from 0 - max range to 0 - 255 range */
447 gint max = s->mngr.info.max;
448 for (i = 0; i < total_bytes; i++) {
449 if (omap.data[i] <= max) {
450 omap.data[i] = 255 * omap.data[i] / max;
451 } else {
452 /* This is an error case, wherein value in the data stream is
453 more than max. Clamp such values to 255 */
454 omap.data[i] = 255;
455 }
456 }
457 }
458 }
459
460 if (s->mngr.info.encoding == GST_PNM_ENCODING_ASCII) {
461 gst_buffer_unmap (s->buf, &imap);
462 } else {
463 gst_buffer_unmap (frame->input_buffer, &imap);
464 }
465 gst_buffer_unmap (frame->output_buffer, &omap);
466
467 s->current_size = 0;
468
469 r = gst_video_decoder_finish_frame (GST_VIDEO_DECODER (s), frame);
470
471 out:
472 gst_pnmdec_flush (s);
473
474 return r;
475 }
476
477 static GstFlowReturn
gst_pnmdec_parse(GstVideoDecoder * decoder,GstVideoCodecFrame * frame,GstAdapter * adapter,gboolean at_eos)478 gst_pnmdec_parse (GstVideoDecoder * decoder, GstVideoCodecFrame * frame,
479 GstAdapter * adapter, gboolean at_eos)
480 {
481 gsize size;
482 GstPnmdec *s = GST_PNMDEC (decoder);
483 GstFlowReturn r = GST_FLOW_OK;
484 guint offset = 0;
485 const guint8 *raw_data = NULL;
486
487 GST_VIDEO_CODEC_FRAME_SET_SYNC_POINT (frame);
488
489 size = gst_adapter_available (adapter);
490 if (size > 0)
491 raw_data = gst_adapter_map (adapter, size);
492
493 GST_LOG_OBJECT (s, "Entering parse with %" G_GSIZE_FORMAT " bytes. at_eos %d",
494 size, at_eos);
495
496 if (s->mngr.info.fields != GST_PNM_INFO_FIELDS_ALL) {
497 GstPnmInfoMngrResult res;
498
499 if (size < 8)
500 goto need_more_data;
501
502 res = gst_pnm_info_mngr_scan (&s->mngr, raw_data, size);
503
504 switch (res) {
505 case GST_PNM_INFO_MNGR_RESULT_FAILED:
506 r = GST_FLOW_ERROR;
507 goto out;
508 case GST_PNM_INFO_MNGR_RESULT_READING:
509 r = GST_FLOW_OK;
510 goto out;
511 case GST_PNM_INFO_MNGR_RESULT_FINISHED:
512
513 r = gst_pnmdec_negotiate (decoder);
514 if (r != GST_FLOW_OK)
515 goto out;
516
517 if (s->mngr.info.encoding == GST_PNM_ENCODING_ASCII) {
518 /* It is not possible to know the size of input ascii data to parse.
519 So we have to parse and know the number of pixels parsed and
520 then finally decide when we have full frame */
521 GST_DEBUG_OBJECT (s, "Allocating output frame of size %u", s->size);
522 s->buf = gst_buffer_new_and_alloc (s->size);
523 }
524 offset = s->mngr.data_offset;
525 gst_adapter_flush (adapter, offset);
526 size = size - offset;
527 }
528 }
529
530 if (s->mngr.info.encoding == GST_PNM_ENCODING_ASCII) {
531 /* Parse ASCII data and populate s->current_size with the number of
532 bytes actually parsed from the input data */
533 GST_DEBUG_OBJECT (s, "Parsing %u bytes at offset %u", (guint) size, offset);
534 r = gst_pnmdec_parse_ascii (s, raw_data + offset, size);
535 } else {
536 /* Bitmap Contains 8 pixels in a byte */
537 if (s->mngr.info.type == GST_PNM_TYPE_BITMAP)
538 s->current_size += (size * 8);
539 else
540 s->current_size += size;
541 }
542
543 gst_video_decoder_add_to_frame (decoder, size);
544 if (s->size <= s->current_size) {
545 goto have_full_frame;
546 }
547
548 need_more_data:
549 return GST_VIDEO_DECODER_FLOW_NEED_DATA;
550
551 have_full_frame:
552 return gst_video_decoder_have_frame (decoder);
553
554 out:
555 return r;
556 }
557
558 static gboolean
gst_pnmdec_start(GstVideoDecoder * decoder)559 gst_pnmdec_start (GstVideoDecoder * decoder)
560 {
561 GstPnmdec *pnmdec = (GstPnmdec *) decoder;
562 gst_video_decoder_set_packetized (GST_VIDEO_DECODER (pnmdec), FALSE);
563 gst_pnmdec_flush (pnmdec);
564 return TRUE;
565 }
566
567 static GstFlowReturn
gst_pnmdec_finish(GstVideoDecoder * decoder)568 gst_pnmdec_finish (GstVideoDecoder * decoder)
569 {
570 GstPnmdec *s = (GstPnmdec *) decoder;
571
572 GST_LOG_OBJECT (s, "finishing");
573
574 if (s->mngr.info.encoding == GST_PNM_ENCODING_ASCII) {
575 /* One last go at outputting any final value */
576 gst_pnmdec_parse_ascii (s, 0, 0);
577 if (s->size && s->size <= s->current_size) {
578 return gst_video_decoder_have_frame (decoder);
579 }
580 }
581
582 return GST_FLOW_OK;
583 }
584