jpegparse: Warn only malformed data in APP data.
[platform/upstream/gstreamer.git] / subprojects / gst-plugins-bad / gst / jpegformat / gstjpegparse.c
1 /* GStreamer
2  *
3  * jpegparse: a parser for JPEG streams
4  *
5  * Copyright (C) <2009> Arnout Vandecappelle (Essensium/Mind) <arnout@mind.be>
6  *               <2022> Víctor Manuel Jáquez Leal <vjaquez@igalia.com>
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the
20  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
21  * Boston, MA 02110-1301, USA.
22  */
23
24 /**
25  * SECTION:element-jpegparse
26  * @title: jpegparse
27  * @short_description: JPEG parser
28  *
29  * Parses a JPEG stream into JPEG images.  It looks for EOI boundaries to
30  * split a continuous stream into single-frame buffers. Also reads the
31  * image header searching for image properties such as width and height
32  * among others. Jpegparse can also extract metadata (e.g. xmp).
33  *
34  * ## Example launch line
35  * |[
36  * gst-launch-1.0 -v souphttpsrc location=... ! jpegparse ! matroskamux ! filesink location=...
37  * ]|
38  * The above pipeline fetches a motion JPEG stream from an IP camera over
39  * HTTP and stores it in a matroska file.
40  *
41  */
42 /* FIXME: output plain JFIF APP marker only. This provides best code reuse.
43  * JPEG decoders would not need to handle this part anymore. Also when remuxing
44  * (... ! jpegparse ! ... ! jifmux ! ...) metadata consolidation would be
45  * easier.
46  */
47
48 /* TODO:
49  *  + APP2 -- ICC color profile
50  *  + APP3 -- meta (same as exif)
51  *  + APP12 -- Photoshop Save for Web: Ducky / Picture info
52  *  + APP13 -- Adobe IRB
53  *  + check for interlaced mjpeg
54  */
55
56 #ifdef HAVE_CONFIG_H
57 #include <config.h>
58 #endif
59
60 #include <string.h>
61 #include <gst/base/gstbytereader.h>
62 #include <gst/codecparsers/gstjpegparser.h>
63 #include <gst/tag/tag.h>
64 #include <gst/video/video.h>
65
66 #include "gstjpegparse.h"
67
68 enum ParserState
69 {
70   GST_JPEG_PARSER_STATE_GOT_SOI = 1 << 0,
71   GST_JPEG_PARSER_STATE_GOT_SOF = 1 << 1,
72   GST_JPEG_PARSER_STATE_GOT_SOS = 1 << 2,
73   GST_JPEG_PARSER_STATE_GOT_JFIF = 1 << 3,
74   GST_JPEG_PARSER_STATE_GOT_ADOBE = 1 << 4,
75
76   GST_JPEG_PARSER_STATE_VALID_PICTURE = (GST_JPEG_PARSER_STATE_GOT_SOI |
77       GST_JPEG_PARSER_STATE_GOT_SOF | GST_JPEG_PARSER_STATE_GOT_SOS),
78 };
79
80 static GstStaticPadTemplate gst_jpeg_parse_src_pad_template =
81 GST_STATIC_PAD_TEMPLATE ("src",
82     GST_PAD_SRC,
83     GST_PAD_ALWAYS,
84     GST_STATIC_CAPS ("image/jpeg, "
85         "framerate = (fraction) [ 0/1, MAX ], " "parsed = (boolean) true")
86     );
87
88 static GstStaticPadTemplate gst_jpeg_parse_sink_pad_template =
89 GST_STATIC_PAD_TEMPLATE ("sink",
90     GST_PAD_SINK,
91     GST_PAD_ALWAYS,
92     GST_STATIC_CAPS ("image/jpeg")
93     );
94
95 GST_DEBUG_CATEGORY_STATIC (jpeg_parse_debug);
96 #define GST_CAT_DEFAULT jpeg_parse_debug
97
98 static GstFlowReturn
99 gst_jpeg_parse_handle_frame (GstBaseParse * bparse, GstBaseParseFrame * frame,
100     gint * skipsize);
101 static gboolean gst_jpeg_parse_set_sink_caps (GstBaseParse * parse,
102     GstCaps * caps);
103 static gboolean gst_jpeg_parse_sink_event (GstBaseParse * parse,
104     GstEvent * event);
105 static gboolean gst_jpeg_parse_start (GstBaseParse * parse);
106 static gboolean gst_jpeg_parse_stop (GstBaseParse * parse);
107
108 #define gst_jpeg_parse_parent_class parent_class
109 G_DEFINE_TYPE (GstJpegParse, gst_jpeg_parse, GST_TYPE_BASE_PARSE);
110 GST_ELEMENT_REGISTER_DEFINE (jpegparse, "jpegparse", GST_RANK_NONE,
111     GST_TYPE_JPEG_PARSE);
112
113 enum GstJPEGColorspace
114 {
115   GST_JPEG_COLORSPACE_NONE,
116   GST_JPEG_COLORSPACE_RGB,
117   GST_JPEG_COLORSPACE_YUV,
118   GST_JPEG_COLORSPACE_GRAY,
119   GST_JPEG_COLORSPACE_CMYK,
120   GST_JPEG_COLORSPACE_YCCK,
121 };
122
123 static const gchar *gst_jpeg_colorspace_strings[] = {
124   [GST_JPEG_COLORSPACE_NONE] = NULL,
125   [GST_JPEG_COLORSPACE_RGB] = "sRGB",
126   [GST_JPEG_COLORSPACE_YUV] = "sYUV",
127   [GST_JPEG_COLORSPACE_GRAY] = "GRAY",
128   [GST_JPEG_COLORSPACE_CMYK] = "CMYK",
129   [GST_JPEG_COLORSPACE_YCCK] = "YCCK",
130 };
131
132 enum GstJPEGSampling
133 {
134   GST_JPEG_SAMPLING_NONE,
135   GST_JPEG_SAMPLING_RGB,
136   GST_JPEG_SAMPLING_BGR,
137   GST_JPEG_SAMPLING_YBR444,
138   GST_JPEG_SAMPLING_YBR422,
139   GST_JPEG_SAMPLING_YBR420,
140   GST_JPEG_SAMPLING_YBR440,
141   GST_JPEG_SAMPLING_YBR410,
142   GST_JPEG_SAMPLING_YBR411,
143   GST_JPEG_SAMPLING_GRAYSCALE,
144 };
145
146 static const gchar *gst_jpeg_sampling_strings[] = {
147   [GST_JPEG_SAMPLING_NONE] = NULL,
148   [GST_JPEG_SAMPLING_RGB] = "RGB",
149   [GST_JPEG_SAMPLING_BGR] = "BGR",
150   [GST_JPEG_SAMPLING_YBR444] = "YCbCr-4:4:4",
151   [GST_JPEG_SAMPLING_YBR422] = "YCbCr-4:2:2",
152   [GST_JPEG_SAMPLING_YBR420] = "YCbCr-4:2:0",
153   [GST_JPEG_SAMPLING_YBR440] = "YCbCr-4:4:0",
154   [GST_JPEG_SAMPLING_YBR410] = "YCbCr-4:1:0",
155   [GST_JPEG_SAMPLING_YBR411] = "YCbCr-4:1:1",
156   [GST_JPEG_SAMPLING_GRAYSCALE] = "GRAYSCALE",
157 };
158
159 static void
160 gst_jpeg_parse_class_init (GstJpegParseClass * klass)
161 {
162   GstBaseParseClass *gstbaseparse_class;
163   GstElementClass *gstelement_class;
164
165   gstbaseparse_class = (GstBaseParseClass *) klass;
166   gstelement_class = (GstElementClass *) klass;
167
168   gstbaseparse_class->start = gst_jpeg_parse_start;
169   gstbaseparse_class->stop = gst_jpeg_parse_stop;
170   gstbaseparse_class->set_sink_caps = gst_jpeg_parse_set_sink_caps;
171   gstbaseparse_class->sink_event = gst_jpeg_parse_sink_event;
172   gstbaseparse_class->handle_frame = gst_jpeg_parse_handle_frame;
173
174   gst_element_class_add_static_pad_template (gstelement_class,
175       &gst_jpeg_parse_src_pad_template);
176   gst_element_class_add_static_pad_template (gstelement_class,
177       &gst_jpeg_parse_sink_pad_template);
178
179   gst_element_class_set_static_metadata (gstelement_class,
180       "JPEG stream parser",
181       "Codec/Parser/Image",
182       "Parse JPEG images into single-frame buffers",
183       "Víctor Jáquez <vjaquez@igalia.com>");
184
185   GST_DEBUG_CATEGORY_INIT (jpeg_parse_debug, "jpegparse", 0, "JPEG parser");
186 }
187
188 static void
189 gst_jpeg_parse_init (GstJpegParse * parse)
190 {
191   parse->sof = -1;
192 }
193
194 static gboolean
195 gst_jpeg_parse_set_sink_caps (GstBaseParse * bparse, GstCaps * caps)
196 {
197   GstJpegParse *parse = GST_JPEG_PARSE_CAST (bparse);
198   GstStructure *s = gst_caps_get_structure (caps, 0);
199
200   GST_DEBUG_OBJECT (parse, "get sink caps %" GST_PTR_FORMAT, caps);
201
202   gst_structure_get_fraction (s, "framerate",
203       &parse->framerate_numerator, &parse->framerate_denominator);
204
205   return TRUE;
206 }
207
208 static inline gboolean
209 valid_state (guint state, guint ref_state)
210 {
211   return (state & ref_state) == ref_state;
212 }
213
214 /* https://zpl.fi/chroma-subsampling-and-jpeg-sampling-factors/ */
215 /* *INDENT-OFF* */
216 static const struct
217 {
218   gint h[3];
219   gint v[3];
220   enum GstJPEGSampling sampling;
221 } subsampling_map[] = {
222   {{1, 1, 1}, {1, 1, 1}, GST_JPEG_SAMPLING_YBR444},
223   {{2, 2, 2}, {1, 1, 1}, GST_JPEG_SAMPLING_YBR444},
224   {{3, 3, 3}, {1, 1, 1}, GST_JPEG_SAMPLING_YBR444},
225   {{1, 1, 1}, {2, 2, 2}, GST_JPEG_SAMPLING_YBR444},
226   {{1, 1, 1}, {3, 3, 3}, GST_JPEG_SAMPLING_YBR444},
227   {{1, 1, 1}, {2, 1, 1}, GST_JPEG_SAMPLING_YBR440},
228   {{2, 2, 2}, {2, 1, 1}, GST_JPEG_SAMPLING_YBR440},
229   {{1, 1, 1}, {4, 2, 2}, GST_JPEG_SAMPLING_YBR440},
230   {{2, 1, 1}, {1, 1, 1}, GST_JPEG_SAMPLING_YBR422},
231   {{2, 1, 1}, {2, 2, 2}, GST_JPEG_SAMPLING_YBR422},
232   {{4, 2, 2}, {1, 1, 1}, GST_JPEG_SAMPLING_YBR422},
233   {{2, 1, 1}, {2, 1, 1}, GST_JPEG_SAMPLING_YBR420},
234   {{4, 1, 1}, {1, 1, 1}, GST_JPEG_SAMPLING_YBR411},
235   {{4, 1, 1}, {2, 1, 1}, GST_JPEG_SAMPLING_YBR410},
236 };
237 /* *INDENT-ON* */
238
239 static guint16
240 yuv_sampling (GstJpegFrameHdr * frame_hdr)
241 {
242   int i, h0, h1, h2, v0, v1, v2;
243
244   g_return_val_if_fail (frame_hdr->num_components == 3, GST_JPEG_SAMPLING_NONE);
245
246   h0 = frame_hdr->components[0].horizontal_factor;
247   h1 = frame_hdr->components[1].horizontal_factor;
248   h2 = frame_hdr->components[2].horizontal_factor;
249   v0 = frame_hdr->components[0].vertical_factor;
250   v1 = frame_hdr->components[1].vertical_factor;
251   v2 = frame_hdr->components[2].vertical_factor;
252
253   for (i = 0; i < G_N_ELEMENTS (subsampling_map); i++) {
254     if (subsampling_map[i].h[0] == h0
255         && subsampling_map[i].h[1] == h1 && subsampling_map[i].h[2] == h2
256         && subsampling_map[i].v[0] == v0
257         && subsampling_map[i].v[1] == v1 && subsampling_map[i].v[2] == v2)
258       return subsampling_map[i].sampling;
259   }
260
261   return GST_JPEG_SAMPLING_NONE;
262 }
263
264 static const gchar *
265 colorspace_to_string (enum GstJPEGColorspace colorspace)
266 {
267   return gst_jpeg_colorspace_strings[colorspace];
268 }
269
270 /* https://entropymine.wordpress.com/2018/10/22/how-is-a-jpeg-images-color-type-determined/ */
271 /* T-REC-T.872-201206  6.1 Colour encodings and associated values to define white and black */
272 static gboolean
273 gst_jpeg_parse_sof (GstJpegParse * parse, GstJpegSegment * seg)
274 {
275   GstJpegFrameHdr hdr = { 0, };
276
277   if (!gst_jpeg_segment_parse_frame_header (seg, &hdr)) {
278     return FALSE;
279   }
280
281   parse->width = hdr.width;
282   parse->height = hdr.height;
283
284   parse->colorspace = GST_JPEG_COLORSPACE_NONE;
285   parse->sampling = GST_JPEG_SAMPLING_NONE;
286
287   switch (hdr.num_components) {
288     case 1:
289       parse->colorspace = GST_JPEG_COLORSPACE_GRAY;
290       parse->sampling = GST_JPEG_SAMPLING_GRAYSCALE;
291       break;
292     case 3:
293       if (valid_state (parse->state, GST_JPEG_PARSER_STATE_GOT_JFIF)) {
294         parse->colorspace = GST_JPEG_COLORSPACE_YUV;
295         parse->sampling = yuv_sampling (&hdr);
296       } else {
297         if (valid_state (parse->state, GST_JPEG_PARSER_STATE_GOT_ADOBE)) {
298           if (parse->adobe_transform == 0) {
299             parse->colorspace = GST_JPEG_COLORSPACE_RGB;
300             parse->sampling = GST_JPEG_SAMPLING_RGB;
301           } else if (parse->adobe_transform == 1) {
302             parse->colorspace = GST_JPEG_COLORSPACE_YUV;;
303             parse->sampling = yuv_sampling (&hdr);
304           } else {
305             GST_DEBUG_OBJECT (parse, "Unknown Adobe color transform code");
306             parse->colorspace = GST_JPEG_COLORSPACE_YUV;;
307             parse->sampling = yuv_sampling (&hdr);
308           }
309         } else {
310           int cid0, cid1, cid2;
311
312           cid0 = hdr.components[0].identifier;
313           cid1 = hdr.components[1].identifier;
314           cid2 = hdr.components[2].identifier;
315
316           if (cid0 == 1 && cid1 == 2 && cid2 == 3) {
317             parse->colorspace = GST_JPEG_COLORSPACE_YUV;
318             parse->sampling = yuv_sampling (&hdr);
319           } else if (cid0 == 'R' && cid1 == 'G' && cid2 == 'B') {
320             parse->colorspace = GST_JPEG_COLORSPACE_RGB;
321             parse->sampling = GST_JPEG_SAMPLING_RGB;
322           } else {
323             GST_DEBUG_OBJECT (parse, "Unrecognized component IDs");
324             parse->colorspace = GST_JPEG_COLORSPACE_YUV;
325             parse->sampling = yuv_sampling (&hdr);
326           }
327         }
328       }
329       break;
330     case 4:
331       if (valid_state (parse->state, GST_JPEG_PARSER_STATE_GOT_ADOBE)) {
332         if (parse->adobe_transform == 0) {
333           parse->colorspace = GST_JPEG_COLORSPACE_CMYK;
334         } else if (parse->adobe_transform == 2) {
335           parse->colorspace = GST_JPEG_COLORSPACE_YCCK;
336         } else {
337           GST_DEBUG_OBJECT (parse, "Unknown Adobe color transform code");
338           parse->colorspace = GST_JPEG_COLORSPACE_YCCK;
339         }
340       } else {
341         parse->colorspace = GST_JPEG_COLORSPACE_CMYK;
342       }
343       break;
344     default:
345       GST_WARNING_OBJECT (parse, "Unknown color space");
346       break;
347   }
348
349   GST_INFO_OBJECT (parse, "SOF [%dx%d] %d comp - %s", parse->width,
350       parse->height, hdr.num_components,
351       GST_STR_NULL (colorspace_to_string (parse->colorspace)));
352   return TRUE;
353 }
354
355 static inline GstTagList *
356 get_tag_list (GstJpegParse * parse)
357 {
358   if (!parse->tags)
359     parse->tags = gst_tag_list_new_empty ();
360   return parse->tags;
361 }
362
363 static gboolean
364 gst_jpeg_parse_app0 (GstJpegParse * parse, GstJpegSegment * seg)
365 {
366   GstByteReader reader;
367   const gchar *id_str;
368   guint16 xd, yd;
369   guint8 unit, xt, yt;
370
371   if (seg->size < 6)            /* less than 6 means no id string */
372     return FALSE;
373
374   gst_byte_reader_init (&reader, seg->data + seg->offset, seg->size);
375   gst_byte_reader_skip_unchecked (&reader, 2);
376
377   if (!gst_byte_reader_get_string_utf8 (&reader, &id_str))
378     return FALSE;
379
380   if (!valid_state (parse->state, GST_JPEG_PARSER_STATE_GOT_JFIF)
381       && g_strcmp0 (id_str, "JFIF") == 0) {
382
383     parse->state |= GST_JPEG_PARSER_STATE_GOT_JFIF;
384
385     /* version */
386     gst_byte_reader_skip_unchecked (&reader, 2);
387
388     /* units */
389     if (!gst_byte_reader_get_uint8 (&reader, &unit))
390       return FALSE;
391
392     /* x density */
393     if (!gst_byte_reader_get_uint16_be (&reader, &xd))
394       return FALSE;
395     /* y density */
396     if (!gst_byte_reader_get_uint16_be (&reader, &yd))
397       return FALSE;
398
399     /* x thumbnail */
400     if (!gst_byte_reader_get_uint8 (&reader, &xt))
401       return FALSE;
402     /* y thumbnail */
403     if (!gst_byte_reader_get_uint8 (&reader, &yt))
404       return FALSE;
405
406     if (unit == 0) {
407       /* no units, X and Y specify the pixel aspect ratio */
408       parse->x_density = xd;
409       parse->y_density = yd;
410     } else if (unit == 1 || unit == 2) {
411       /* tag pixel per inches */
412       double hppi = xd, vppi = yd;
413
414       /* cm to in */
415       if (unit == 2) {
416         hppi *= 2.54;
417         vppi *= 2.54;
418       }
419
420       gst_tag_register_musicbrainz_tags ();
421       gst_tag_list_add (get_tag_list (parse), GST_TAG_MERGE_REPLACE,
422           GST_TAG_IMAGE_HORIZONTAL_PPI, hppi, GST_TAG_IMAGE_VERTICAL_PPI, vppi,
423           NULL);
424     }
425
426     if (xt > 0 && yt > 0)
427       GST_FIXME_OBJECT (parse, "embedded thumbnail ignored");
428
429     return TRUE;
430   }
431
432   /* JFIF  Extension  */
433   if (g_strcmp0 (id_str, "JFXX") == 0) {
434     if (!valid_state (parse->state, GST_JPEG_PARSER_STATE_GOT_JFIF))
435       return FALSE;
436
437     return TRUE;
438   }
439
440   /* https://exiftool.org/TagNames/JPEG.html#AVI1 */
441   if (g_strcmp0 (id_str, "AVI1") == 0) {
442     /* polarity */
443     if (!gst_byte_reader_get_uint8 (&reader, &unit))
444       return FALSE;
445
446     /* TODO: update caps for interlaced MJPEG */
447     GST_DEBUG_OBJECT (parse, "MJPEG interleaved field: %d", unit);
448
449     return TRUE;
450   }
451
452   GST_DEBUG_OBJECT (parse, "Unhandled app0: %s", id_str);
453
454   return TRUE;
455 }
456
457 /* *INDENT-OFF* */
458 static const struct
459 {
460   const gchar *suffix;
461   GstTagList *(*tag_func) (GstBuffer * buff);
462 } TagMap[] = {
463   {"Exif", gst_tag_list_from_exif_buffer_with_tiff_header},
464   {"http://ns.adobe.com/xap/1.0/", gst_tag_list_from_xmp_buffer},
465 };
466 /* *INDENT-ON* */
467
468 static gboolean
469 gst_jpeg_parse_app1 (GstJpegParse * parse, GstJpegSegment * seg)
470 {
471   GstByteReader reader;
472   GstBuffer *buf;
473   guint16 size = 0;
474   const gchar *id_str;
475   const guint8 *data;
476   gint i;
477
478   if (seg->size < 6)            /* less than 6 means no id string */
479     return FALSE;
480
481   gst_byte_reader_init (&reader, seg->data + seg->offset, seg->size);
482   gst_byte_reader_skip_unchecked (&reader, 2);
483
484   if (!gst_byte_reader_get_string_utf8 (&reader, &id_str))
485     return FALSE;
486
487   for (i = 0; i < G_N_ELEMENTS (TagMap); i++) {
488     if (!g_str_has_suffix (id_str, TagMap[i].suffix))
489       continue;
490
491     /* skip NUL only for Exif */
492     if (i == 0) {
493       if (!gst_byte_reader_skip (&reader, 1))
494         return FALSE;
495     }
496
497     size = gst_byte_reader_get_remaining (&reader);
498
499     if (!gst_byte_reader_get_data (&reader, size, &data))
500       return FALSE;
501
502     buf = gst_buffer_new_wrapped_full (GST_MEMORY_FLAG_READONLY,
503         (gpointer) data, size, 0, size, NULL, NULL);
504
505     if (buf) {
506       GstTagList *tags;
507
508       tags = TagMap[i].tag_func (buf);
509       gst_buffer_unref (buf);
510
511       if (tags) {
512         GST_LOG_OBJECT (parse, "parsed marker %x: '%s' %" GST_PTR_FORMAT,
513             GST_JPEG_MARKER_APP1, id_str, tags);
514         gst_tag_list_insert (get_tag_list (parse), tags, GST_TAG_MERGE_REPLACE);
515         gst_tag_list_unref (tags);
516       } else {
517         GST_INFO_OBJECT (parse, "failed to parse %s: %s", id_str, data);
518         return FALSE;
519       }
520     }
521
522     return TRUE;
523   }
524
525   GST_DEBUG_OBJECT (parse, "Unhandled app1: %s", id_str);
526
527   return TRUE;
528 }
529
530 static gboolean
531 gst_jpeg_parse_app14 (GstJpegParse * parse, GstJpegSegment * seg)
532 {
533   GstByteReader reader;
534   const gchar *id_str;
535   guint8 transform;
536
537   if (seg->size < 6)            /* less than 6 means no id string */
538     return FALSE;
539
540   gst_byte_reader_init (&reader, seg->data + seg->offset, seg->size);
541   gst_byte_reader_skip_unchecked (&reader, 2);
542
543   if (!gst_byte_reader_get_string_utf8 (&reader, &id_str))
544     return FALSE;
545
546   if (!g_str_has_prefix (id_str, "Adobe")) {
547     GST_DEBUG_OBJECT (parse, "Unhandled app14: %s", id_str);
548     return TRUE;
549   }
550
551   /* skip version and flags */
552   if (!gst_byte_reader_skip (&reader, 6))
553     return FALSE;
554
555   parse->state |= GST_JPEG_PARSER_STATE_GOT_ADOBE;
556
557   /* transform bit might not exist  */
558   if (!gst_byte_reader_get_uint8 (&reader, &transform))
559     return TRUE;
560
561   parse->adobe_transform = transform;
562   return TRUE;
563 }
564
565 static inline gchar *
566 get_utf8_from_data (const guint8 * data, guint16 size)
567 {
568   const gchar *env_vars[] = { "GST_JPEG_TAG_ENCODING",
569     "GST_TAG_ENCODING", NULL
570   };
571   const char *str = (gchar *) data;
572
573   return gst_tag_freeform_string_to_utf8 (str, size, env_vars);
574 }
575
576 /* read comment and post as tag */
577 static inline gboolean
578 gst_jpeg_parse_com (GstJpegParse * parse, GstJpegSegment * seg)
579 {
580   GstByteReader reader;
581   const guint8 *data = NULL;
582   guint16 size;
583   gchar *comment;
584
585   gst_byte_reader_init (&reader, seg->data + seg->offset, seg->size);
586   gst_byte_reader_skip_unchecked (&reader, 2);
587
588   size = gst_byte_reader_get_remaining (&reader);
589
590   if (!gst_byte_reader_get_data (&reader, size, &data))
591     return FALSE;
592
593   comment = get_utf8_from_data (data, size);
594   if (!comment)
595     return FALSE;
596
597   GST_INFO_OBJECT (parse, "comment found: %s", comment);
598   gst_tag_list_add (get_tag_list (parse), GST_TAG_MERGE_REPLACE,
599       GST_TAG_COMMENT, comment, NULL);
600   g_free (comment);
601
602   return TRUE;
603 }
604
605 static void
606 gst_jpeg_parse_reset (GstJpegParse * parse)
607 {
608   parse->width = 0;
609   parse->height = 0;
610   parse->last_offset = 0;
611   parse->state = 0;
612   parse->sof = -1;
613   parse->adobe_transform = 0;
614   parse->x_density = 0;
615   parse->y_density = 0;
616
617   if (parse->tags) {
618     gst_tag_list_unref (parse->tags);
619     parse->tags = NULL;
620   }
621 }
622
623 static const gchar *
624 sampling_to_string (enum GstJPEGSampling sampling)
625 {
626   return gst_jpeg_sampling_strings[sampling];
627 }
628
629 static gboolean
630 gst_jpeg_parse_set_new_caps (GstJpegParse * parse)
631 {
632   GstCaps *caps;
633   gboolean res;
634
635   caps = gst_caps_new_simple ("image/jpeg", "parsed", G_TYPE_BOOLEAN, TRUE,
636       NULL);
637
638   if (parse->width > 0)
639     gst_caps_set_simple (caps, "width", G_TYPE_INT, parse->width, NULL);
640   if (parse->width > 0)
641     gst_caps_set_simple (caps, "height", G_TYPE_INT, parse->height, NULL);
642   if (parse->sof >= 0)
643     gst_caps_set_simple (caps, "sof-marker", G_TYPE_INT, parse->sof, NULL);
644   if (parse->colorspace != GST_JPEG_COLORSPACE_NONE) {
645     gst_caps_set_simple (caps, "colorspace", G_TYPE_STRING,
646         colorspace_to_string (parse->colorspace), NULL);
647   }
648   if (parse->sampling != GST_JPEG_SAMPLING_NONE) {
649     gst_caps_set_simple (caps, "sampling", G_TYPE_STRING,
650         sampling_to_string (parse->sampling), NULL);
651   }
652
653   gst_caps_set_simple (caps, "framerate", GST_TYPE_FRACTION,
654       parse->framerate_numerator, parse->framerate_denominator, NULL);
655
656   if (parse->x_density > 0 && parse->y_density > 0) {
657     gst_caps_set_simple (caps, "pixel-aspect-ratio", GST_TYPE_FRACTION,
658         parse->x_density, parse->y_density, NULL);
659   }
660
661   if (parse->prev_caps && gst_caps_is_equal_fixed (caps, parse->prev_caps)) {
662     gst_caps_unref (caps);
663     return TRUE;
664   }
665
666   GST_DEBUG_OBJECT (parse,
667       "setting downstream caps on %s:%s to %" GST_PTR_FORMAT,
668       GST_DEBUG_PAD_NAME (GST_BASE_PARSE_SRC_PAD (parse)), caps);
669   res = gst_pad_set_caps (GST_BASE_PARSE_SRC_PAD (parse), caps);
670
671   gst_caps_replace (&parse->prev_caps, caps);
672   gst_caps_unref (caps);
673
674   return res;
675
676 }
677
678 static GstFlowReturn
679 gst_jpeg_parse_finish_frame (GstJpegParse * parse, GstBaseParseFrame * frame,
680     gint size)
681 {
682   GstBaseParse *bparse = GST_BASE_PARSE (parse);
683   GstFlowReturn ret;
684
685   if (parse->tags)
686     gst_base_parse_merge_tags (bparse, parse->tags, GST_TAG_MERGE_REPLACE);
687
688   if (!gst_jpeg_parse_set_new_caps (parse))
689     return GST_FLOW_ERROR;
690
691   if (!valid_state (parse->state, GST_JPEG_PARSER_STATE_VALID_PICTURE)) {
692     /* this validation breaks unit tests */
693     /* frame->flags |= GST_BASE_PARSE_FRAME_FLAG_DROP; */
694     GST_WARNING_OBJECT (parse, "Potentially invalid picture");
695   }
696
697   ret = gst_base_parse_finish_frame (bparse, frame, size);
698
699   gst_jpeg_parse_reset (parse);
700
701   return ret;
702 }
703
704 static GstFlowReturn
705 gst_jpeg_parse_handle_frame (GstBaseParse * bparse, GstBaseParseFrame * frame,
706     gint * skipsize)
707 {
708   GstJpegParse *parse = GST_JPEG_PARSE_CAST (bparse);
709   GstMapInfo mapinfo;
710   GstJpegMarker marker;
711   GstJpegSegment seg;
712   guint offset;
713
714   if (!gst_buffer_map (frame->buffer, &mapinfo, GST_MAP_READ))
715     return GST_FLOW_ERROR;
716
717   offset = parse->last_offset;
718   if (offset > 0)
719     offset -= 1;                /* it migth be in the middle marker */
720
721   while (offset < mapinfo.size) {
722     if (!gst_jpeg_parse (&seg, mapinfo.data, mapinfo.size, offset)) {
723       if (!valid_state (parse->state, GST_JPEG_PARSER_STATE_GOT_SOI)) {
724         /* Skip any garbage until SOI */
725         *skipsize = mapinfo.size;
726         GST_INFO_OBJECT (parse, "skipping %d bytes", *skipsize);
727       } else {
728         /* Accept anything after SOI */
729         parse->last_offset = mapinfo.size;
730       }
731       goto beach;
732     }
733
734     offset = seg.offset;
735     marker = seg.marker;
736
737     if (!valid_state (parse->state, GST_JPEG_PARSER_STATE_GOT_SOI)
738         && marker != GST_JPEG_MARKER_SOI)
739       continue;
740
741     /* check if the whole segment is available */
742     if (offset + seg.size > mapinfo.size) {
743       GST_DEBUG_OBJECT (parse, "incomplete segment: %x [offset %d]", marker,
744           offset);
745       parse->last_offset = offset - 2;
746       goto beach;
747     }
748
749     offset += seg.size;
750
751     GST_LOG_OBJECT (parse, "marker found: %x [offset %d / size %"
752         G_GSSIZE_FORMAT "]", marker, seg.offset, seg.size);
753
754     switch (marker) {
755       case GST_JPEG_MARKER_SOI:
756         /* This means that new SOI comes without an previous EOI. */
757         if (offset > 2) {
758           /* If already some data segment parsed, push it as a frame. */
759           if (valid_state (parse->state, GST_JPEG_PARSER_STATE_GOT_SOS)) {
760             gst_buffer_unmap (frame->buffer, &mapinfo);
761
762             frame->out_buffer = gst_buffer_copy_region (frame->buffer,
763                 GST_BUFFER_COPY_ALL, 0, seg.offset - 2);
764             GST_MINI_OBJECT_FLAGS (frame->out_buffer) |=
765                 GST_BUFFER_FLAG_CORRUPTED;
766
767             GST_WARNING_OBJECT (parse, "Push a frame without EOI, size %d",
768                 seg.offset - 2);
769             return gst_jpeg_parse_finish_frame (parse, frame, seg.offset - 2);
770           }
771
772           gst_jpeg_parse_reset (parse);
773           parse->state |= GST_JPEG_PARSER_STATE_GOT_SOI;
774           /* unset tags */
775           gst_base_parse_merge_tags (bparse, NULL, GST_TAG_MERGE_UNDEFINED);
776
777           *skipsize = offset - 2;
778           GST_DEBUG_OBJECT (parse, "skipping %d bytes before SOI", *skipsize);
779           parse->last_offset = 2;
780           goto beach;
781         }
782
783         /* unset tags */
784         gst_base_parse_merge_tags (bparse, NULL, GST_TAG_MERGE_UNDEFINED);
785         parse->state |= GST_JPEG_PARSER_STATE_GOT_SOI;
786         break;
787       case GST_JPEG_MARKER_EOI:
788         gst_buffer_unmap (frame->buffer, &mapinfo);
789         return gst_jpeg_parse_finish_frame (parse, frame, seg.offset);
790         break;
791       case GST_JPEG_MARKER_SOS:
792         if (!valid_state (parse->state, GST_JPEG_PARSER_STATE_GOT_SOF))
793           GST_WARNING_OBJECT (parse, "SOS marker without SOF one");
794         parse->state |= GST_JPEG_PARSER_STATE_GOT_SOS;
795         break;
796       case GST_JPEG_MARKER_COM:
797         if (!gst_jpeg_parse_com (parse, &seg)) {
798           GST_ELEMENT_WARNING (parse, STREAM, FORMAT,
799               ("Failed to parse com segment"), ("Invalid data"));
800         }
801         break;
802       case GST_JPEG_MARKER_APP0:
803         if (!gst_jpeg_parse_app0 (parse, &seg)) {
804           GST_ELEMENT_WARNING (parse, STREAM, FORMAT,
805               ("Failed to parse app0 segment"), ("Invalid data"));
806         }
807         break;
808       case GST_JPEG_MARKER_APP1:
809         if (!gst_jpeg_parse_app1 (parse, &seg)) {
810           GST_ELEMENT_WARNING (parse, STREAM, FORMAT,
811               ("Failed to parse app1 segment"), ("Invalid data"));
812         }
813         break;
814       case GST_JPEG_MARKER_APP14:
815         if (!gst_jpeg_parse_app14 (parse, &seg)) {
816           GST_ELEMENT_WARNING (parse, STREAM, FORMAT,
817               ("Failed to parse app14 segment"), ("Invalid data"));
818         }
819         break;
820       case GST_JPEG_MARKER_DHT:
821       case GST_JPEG_MARKER_DAC:
822         /* to avoid break the below SOF interval */
823         break;
824       default:
825         /* SOFn segments */
826         if (marker >= GST_JPEG_MARKER_SOF_MIN &&
827             marker <= GST_JPEG_MARKER_SOF_MAX) {
828           if (!valid_state (parse->state, GST_JPEG_PARSER_STATE_GOT_SOF)
829               && gst_jpeg_parse_sof (parse, &seg)) {
830             parse->state |= GST_JPEG_PARSER_STATE_GOT_SOF;
831             parse->sof = marker - 0xc0;
832           } else {
833             GST_ELEMENT_ERROR (parse, STREAM, FORMAT,
834                 ("Duplicated or bad SOF marker"), (NULL));
835             gst_buffer_unmap (frame->buffer, &mapinfo);
836             gst_jpeg_parse_reset (parse);
837             return GST_FLOW_ERROR;
838           }
839         }
840         break;
841     }
842   }
843
844   parse->last_offset = offset;
845
846 beach:
847   gst_buffer_unmap (frame->buffer, &mapinfo);
848   return GST_FLOW_OK;
849 }
850
851 static gboolean
852 gst_jpeg_parse_sink_event (GstBaseParse * bparse, GstEvent * event)
853 {
854   GstJpegParse *parse = GST_JPEG_PARSE_CAST (bparse);
855
856   GST_DEBUG_OBJECT (parse, "event : %s", GST_EVENT_TYPE_NAME (event));
857
858   switch (GST_EVENT_TYPE (event)) {
859     case GST_EVENT_FLUSH_STOP:
860       gst_jpeg_parse_reset (parse);
861       break;
862     default:
863       break;
864   }
865
866   return GST_BASE_PARSE_CLASS (parent_class)->sink_event (bparse, event);
867 }
868
869 static gboolean
870 gst_jpeg_parse_start (GstBaseParse * bparse)
871 {
872   GstJpegParse *parse = GST_JPEG_PARSE_CAST (bparse);
873
874   parse->framerate_numerator = 0;
875   parse->framerate_denominator = 1;
876
877   gst_jpeg_parse_reset (parse);
878
879   gst_base_parse_set_min_frame_size (bparse, 2);
880
881   return TRUE;
882 }
883
884 static gboolean
885 gst_jpeg_parse_stop (GstBaseParse * bparse)
886 {
887   GstJpegParse *parse = GST_JPEG_PARSE_CAST (bparse);
888
889   if (parse->tags) {
890     gst_tag_list_unref (parse->tags);
891     parse->tags = NULL;
892   }
893   gst_clear_caps (&parse->prev_caps);
894
895   return TRUE;
896 }