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