1 /* GStreamer
2  *
3  * jifmux: JPEG interchange format muxer
4  *
5  * Copyright (C) 2010 Stefan Kost <stefan.kost@nokia.com>
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  */
22 
23 /**
24  * SECTION:element-jifmux
25  * @title: jifmux
26  * @short_description: JPEG interchange format writer
27  *
28  * Writes a JPEG image as JPEG/EXIF or JPEG/JFIF including various metadata. The
29  * jpeg image received on the sink pad should be minimal (e.g. should not
30  * contain metadata already).
31  *
32  * ## Example launch line
33  * |[
34  * gst-launch-1.0 -v videotestsrc num-buffers=1 ! jpegenc ! jifmux ! filesink location=...
35  * ]|
36  * The above pipeline renders a frame, encodes to jpeg, adds metadata and writes
37  * it to disk.
38  *
39  */
40 /*
41 jpeg interchange format:
42 file header : SOI, APPn{JFIF,EXIF,...}
43 frame header: DQT, SOF
44 scan header : {DAC,DHT},DRI,SOS
45 <scan data>
46 file trailer: EOI
47 
48 tests:
49 gst-launch-1.0 videotestsrc num-buffers=1 ! jpegenc ! jifmux ! filesink location=test1.jpeg
50 gst-launch-1.0 videotestsrc num-buffers=1 ! jpegenc ! taginject tags="comment=test image" ! jifmux ! filesink location=test2.jpeg
51 */
52 
53 #ifdef HAVE_CONFIG_H
54 #include <config.h>
55 #endif
56 
57 #include <string.h>
58 #include <gst/base/gstbytereader.h>
59 #include <gst/base/gstbytewriter.h>
60 #include <gst/tag/tag.h>
61 #include <gst/tag/xmpwriter.h>
62 
63 #include "gstjifmux.h"
64 
65 static GstStaticPadTemplate gst_jif_mux_src_pad_template =
66 GST_STATIC_PAD_TEMPLATE ("src",
67     GST_PAD_SRC,
68     GST_PAD_ALWAYS,
69     GST_STATIC_CAPS ("image/jpeg")
70     );
71 
72 static GstStaticPadTemplate gst_jif_mux_sink_pad_template =
73 GST_STATIC_PAD_TEMPLATE ("sink",
74     GST_PAD_SINK,
75     GST_PAD_ALWAYS,
76     GST_STATIC_CAPS ("image/jpeg")
77     );
78 
79 GST_DEBUG_CATEGORY_STATIC (jif_mux_debug);
80 #define GST_CAT_DEFAULT jif_mux_debug
81 
82 #define COLORSPACE_UNKNOWN         (0 << 0)
83 #define COLORSPACE_GRAYSCALE       (1 << 0)
84 #define COLORSPACE_YUV             (1 << 1)
85 #define COLORSPACE_RGB             (1 << 2)
86 #define COLORSPACE_CMYK            (1 << 3)
87 #define COLORSPACE_YCCK            (1 << 4)
88 
89 typedef struct _GstJifMuxMarker
90 {
91   guint8 marker;
92   guint16 size;
93 
94   const guint8 *data;
95   gboolean owned;
96 } GstJifMuxMarker;
97 
98 static void gst_jif_mux_finalize (GObject * object);
99 
100 static void gst_jif_mux_reset (GstJifMux * self);
101 static gboolean gst_jif_mux_sink_setcaps (GstJifMux * self, GstCaps * caps);
102 static gboolean gst_jif_mux_sink_event (GstPad * pad, GstObject * parent,
103     GstEvent * event);
104 static GstFlowReturn gst_jif_mux_chain (GstPad * pad, GstObject * parent,
105     GstBuffer * buffer);
106 static GstStateChangeReturn gst_jif_mux_change_state (GstElement * element,
107     GstStateChange transition);
108 
109 #define gst_jif_mux_parent_class parent_class
110 G_DEFINE_TYPE_WITH_CODE (GstJifMux, gst_jif_mux, GST_TYPE_ELEMENT,
111     G_IMPLEMENT_INTERFACE (GST_TYPE_TAG_SETTER, NULL);
112     G_IMPLEMENT_INTERFACE (GST_TYPE_TAG_XMP_WRITER, NULL));
113 
114 static void
gst_jif_mux_class_init(GstJifMuxClass * klass)115 gst_jif_mux_class_init (GstJifMuxClass * klass)
116 {
117   GObjectClass *gobject_class;
118   GstElementClass *gstelement_class;
119 
120   gobject_class = (GObjectClass *) klass;
121   gstelement_class = (GstElementClass *) klass;
122 
123   gobject_class->finalize = gst_jif_mux_finalize;
124 
125   gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_jif_mux_change_state);
126 
127   gst_element_class_add_static_pad_template (gstelement_class,
128       &gst_jif_mux_src_pad_template);
129   gst_element_class_add_static_pad_template (gstelement_class,
130       &gst_jif_mux_sink_pad_template);
131 
132   gst_element_class_set_static_metadata (gstelement_class,
133       "JPEG stream muxer",
134       "Video/Formatter",
135       "Remuxes JPEG images with markers and tags",
136       "Arnout Vandecappelle (Essensium/Mind) <arnout@mind.be>");
137 
138   GST_DEBUG_CATEGORY_INIT (jif_mux_debug, "jifmux", 0,
139       "JPEG interchange format muxer");
140 }
141 
142 static void
gst_jif_mux_init(GstJifMux * self)143 gst_jif_mux_init (GstJifMux * self)
144 {
145   GstPad *sinkpad;
146 
147   /* create the sink and src pads */
148   sinkpad = gst_pad_new_from_static_template (&gst_jif_mux_sink_pad_template,
149       "sink");
150   gst_pad_set_chain_function (sinkpad, GST_DEBUG_FUNCPTR (gst_jif_mux_chain));
151   gst_pad_set_event_function (sinkpad,
152       GST_DEBUG_FUNCPTR (gst_jif_mux_sink_event));
153   gst_element_add_pad (GST_ELEMENT (self), sinkpad);
154 
155   self->srcpad =
156       gst_pad_new_from_static_template (&gst_jif_mux_src_pad_template, "src");
157   gst_element_add_pad (GST_ELEMENT (self), self->srcpad);
158 }
159 
160 static void
gst_jif_mux_finalize(GObject * object)161 gst_jif_mux_finalize (GObject * object)
162 {
163   GstJifMux *self = GST_JIF_MUX (object);
164 
165   gst_jif_mux_reset (self);
166   G_OBJECT_CLASS (parent_class)->finalize (object);
167 }
168 
169 static gboolean
gst_jif_mux_sink_setcaps(GstJifMux * self,GstCaps * caps)170 gst_jif_mux_sink_setcaps (GstJifMux * self, GstCaps * caps)
171 {
172   GstStructure *s = gst_caps_get_structure (caps, 0);
173   const gchar *variant;
174 
175   /* should be {combined (default), EXIF, JFIF} */
176   if ((variant = gst_structure_get_string (s, "variant")) != NULL) {
177     GST_INFO_OBJECT (self, "muxing to '%s'", variant);
178     /* FIXME: do we want to switch it like this or use a gobject property ? */
179   }
180 
181   return gst_pad_set_caps (self->srcpad, caps);
182 }
183 
184 static gboolean
gst_jif_mux_sink_event(GstPad * pad,GstObject * parent,GstEvent * event)185 gst_jif_mux_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
186 {
187   GstJifMux *self = GST_JIF_MUX (parent);
188   gboolean ret;
189 
190   switch (GST_EVENT_TYPE (event)) {
191     case GST_EVENT_CAPS:
192     {
193       GstCaps *caps;
194 
195       gst_event_parse_caps (event, &caps);
196       ret = gst_jif_mux_sink_setcaps (self, caps);
197       gst_event_unref (event);
198       break;
199     }
200     case GST_EVENT_TAG:{
201       GstTagList *list;
202       GstTagSetter *setter = GST_TAG_SETTER (self);
203       const GstTagMergeMode mode = gst_tag_setter_get_tag_merge_mode (setter);
204 
205       gst_event_parse_tag (event, &list);
206 
207       gst_tag_setter_merge_tags (setter, list, mode);
208 
209       ret = gst_pad_event_default (pad, parent, event);
210       break;
211     }
212     default:
213       ret = gst_pad_event_default (pad, parent, event);
214       break;
215   }
216   return ret;
217 }
218 
219 static void
gst_jif_mux_marker_free(GstJifMuxMarker * m)220 gst_jif_mux_marker_free (GstJifMuxMarker * m)
221 {
222   if (m->owned)
223     g_free ((gpointer) m->data);
224 
225   g_slice_free (GstJifMuxMarker, m);
226 }
227 
228 static void
gst_jif_mux_reset(GstJifMux * self)229 gst_jif_mux_reset (GstJifMux * self)
230 {
231   GList *node;
232   GstJifMuxMarker *m;
233 
234   for (node = self->markers; node; node = g_list_next (node)) {
235     m = (GstJifMuxMarker *) node->data;
236     gst_jif_mux_marker_free (m);
237   }
238   g_list_free (self->markers);
239   self->markers = NULL;
240 }
241 
242 static GstJifMuxMarker *
gst_jif_mux_new_marker(guint8 marker,guint16 size,const guint8 * data,gboolean owned)243 gst_jif_mux_new_marker (guint8 marker, guint16 size, const guint8 * data,
244     gboolean owned)
245 {
246   GstJifMuxMarker *m = g_slice_new (GstJifMuxMarker);
247 
248   m->marker = marker;
249   m->size = size;
250   m->data = data;
251   m->owned = owned;
252 
253   return m;
254 }
255 
256 static gboolean
gst_jif_mux_parse_image(GstJifMux * self,GstBuffer * buf)257 gst_jif_mux_parse_image (GstJifMux * self, GstBuffer * buf)
258 {
259   GstByteReader reader;
260   GstJifMuxMarker *m;
261   guint8 marker = 0;
262   guint16 size = 0;
263   const guint8 *data = NULL;
264   GstMapInfo map;
265 
266   gst_buffer_map (buf, &map, GST_MAP_READ);
267   gst_byte_reader_init (&reader, map.data, map.size);
268 
269   GST_LOG_OBJECT (self, "Received buffer of size: %" G_GSIZE_FORMAT, map.size);
270 
271   if (!gst_byte_reader_peek_uint8 (&reader, &marker))
272     goto error;
273 
274   while (marker == 0xff) {
275     if (!gst_byte_reader_skip (&reader, 1))
276       goto error;
277 
278     if (!gst_byte_reader_get_uint8 (&reader, &marker))
279       goto error;
280 
281     switch (marker) {
282       case RST0:
283       case RST1:
284       case RST2:
285       case RST3:
286       case RST4:
287       case RST5:
288       case RST6:
289       case RST7:
290       case SOI:
291         GST_DEBUG_OBJECT (self, "marker = %x", marker);
292         m = gst_jif_mux_new_marker (marker, 0, NULL, FALSE);
293         self->markers = g_list_prepend (self->markers, m);
294         break;
295       case EOI:
296         GST_DEBUG_OBJECT (self, "marker = %x", marker);
297         m = gst_jif_mux_new_marker (marker, 0, NULL, FALSE);
298         self->markers = g_list_prepend (self->markers, m);
299         goto done;
300       default:
301         if (!gst_byte_reader_get_uint16_be (&reader, &size))
302           goto error;
303         if (!gst_byte_reader_get_data (&reader, size - 2, &data))
304           goto error;
305 
306         m = gst_jif_mux_new_marker (marker, size - 2, data, FALSE);
307         self->markers = g_list_prepend (self->markers, m);
308 
309         GST_DEBUG_OBJECT (self, "marker = %2x, size = %u", marker, size);
310         break;
311     }
312 
313     if (marker == SOS) {
314       gint eoi_pos = -1;
315       gint i;
316 
317       /* search the last 5 bytes for the EOI marker */
318       g_assert (map.size >= 5);
319       for (i = 5; i >= 2; i--) {
320         if (map.data[map.size - i] == 0xFF && map.data[map.size - i + 1] == EOI) {
321           eoi_pos = map.size - i;
322           break;
323         }
324       }
325       if (eoi_pos == -1) {
326         GST_WARNING_OBJECT (self, "Couldn't find an EOI marker");
327         eoi_pos = map.size;
328       }
329 
330       /* remaining size except EOI is scan data */
331       self->scan_size = eoi_pos - gst_byte_reader_get_pos (&reader);
332       if (!gst_byte_reader_get_data (&reader, self->scan_size,
333               &self->scan_data))
334         goto error;
335 
336       GST_DEBUG_OBJECT (self, "scan data, size = %u", self->scan_size);
337     }
338 
339     if (!gst_byte_reader_peek_uint8 (&reader, &marker))
340       goto error;
341   }
342   GST_INFO_OBJECT (self, "done parsing at 0x%x / 0x%x",
343       gst_byte_reader_get_pos (&reader), (guint) map.size);
344 
345 done:
346   self->markers = g_list_reverse (self->markers);
347   gst_buffer_unmap (buf, &map);
348 
349   return TRUE;
350 
351   /* ERRORS */
352 error:
353   {
354     GST_WARNING_OBJECT (self,
355         "Error parsing image header (need more that %u bytes available)",
356         gst_byte_reader_get_remaining (&reader));
357     gst_buffer_unmap (buf, &map);
358     return FALSE;
359   }
360 }
361 
362 static gboolean
gst_jif_mux_mangle_markers(GstJifMux * self)363 gst_jif_mux_mangle_markers (GstJifMux * self)
364 {
365   gboolean modified = FALSE;
366   GstTagList *tags = NULL;
367   gboolean cleanup_tags;
368   GstJifMuxMarker *m;
369   GList *node, *file_hdr = NULL, *frame_hdr = NULL, *scan_hdr = NULL;
370   GList *app0_jfif = NULL, *app1_exif = NULL, *app1_xmp = NULL, *com = NULL;
371   GstBuffer *xmp_data;
372   gchar *str = NULL;
373   gint colorspace = COLORSPACE_UNKNOWN;
374 
375   /* update the APP markers
376    * - put any JFIF APP0 first
377    * - the Exif APP1 next,
378    * - the XMP APP1 next,
379    * - the PSIR APP13 next,
380    * - followed by all other marker segments
381    */
382 
383   /* find some reference points where we insert before/after */
384   file_hdr = self->markers;
385   for (node = self->markers; node; node = g_list_next (node)) {
386     m = (GstJifMuxMarker *) node->data;
387 
388     switch (m->marker) {
389       case APP0:
390         if (m->size > 5 && !memcmp (m->data, "JFIF\0", 5)) {
391           GST_DEBUG_OBJECT (self, "found APP0 JFIF");
392           colorspace |= COLORSPACE_GRAYSCALE | COLORSPACE_YUV;
393           if (!app0_jfif)
394             app0_jfif = node;
395         }
396         break;
397       case APP1:
398         if (m->size > 6 && (!memcmp (m->data, "EXIF\0\0", 6) ||
399                 !memcmp (m->data, "Exif\0\0", 6))) {
400           GST_DEBUG_OBJECT (self, "found APP1 EXIF");
401           if (!app1_exif)
402             app1_exif = node;
403         } else if (m->size > 29
404             && !memcmp (m->data, "http://ns.adobe.com/xap/1.0/\0", 29)) {
405           GST_INFO_OBJECT (self, "found APP1 XMP, will be replaced");
406           if (!app1_xmp)
407             app1_xmp = node;
408         }
409         break;
410       case APP14:
411         /* check if this contains RGB */
412         /*
413          * This marker should have:
414          * - 'Adobe\0'
415          * - 2 bytes DCTEncodeVersion
416          * - 2 bytes flags0
417          * - 2 bytes flags1
418          * - 1 byte  ColorTransform
419          *             - 0 means unknown (RGB or CMYK)
420          *             - 1 YCbCr
421          *             - 2 YCCK
422          */
423 
424         if ((m->size >= 14)
425             && (strncmp ((gchar *) m->data, "Adobe\0", 6) == 0)) {
426           switch (m->data[11]) {
427             case 0:
428               colorspace |= COLORSPACE_RGB | COLORSPACE_CMYK;
429               break;
430             case 1:
431               colorspace |= COLORSPACE_YUV;
432               break;
433             case 2:
434               colorspace |= COLORSPACE_YCCK;
435               break;
436             default:
437               break;
438           }
439         }
440 
441         break;
442       case COM:
443         GST_INFO_OBJECT (self, "found COM, will be replaced");
444         if (!com)
445           com = node;
446         break;
447       case DQT:
448       case SOF0:
449       case SOF1:
450       case SOF2:
451       case SOF3:
452       case SOF5:
453       case SOF6:
454       case SOF7:
455       case SOF9:
456       case SOF10:
457       case SOF11:
458       case SOF13:
459       case SOF14:
460       case SOF15:
461         if (!frame_hdr)
462           frame_hdr = node;
463         break;
464       case DAC:
465       case DHT:
466       case DRI:
467       case SOS:
468         if (!scan_hdr)
469           scan_hdr = node;
470         break;
471     }
472   }
473 
474   /* if we want combined or JFIF */
475   /* check if we don't have JFIF APP0 */
476   if (!app0_jfif && (colorspace & (COLORSPACE_GRAYSCALE | COLORSPACE_YUV))) {
477     /* build jfif header */
478     static const struct
479     {
480       gchar id[5];
481       guint8 ver[2];
482       guint8 du;
483       guint8 xd[2], yd[2];
484       guint8 tw, th;
485     } jfif_data = {
486       "JFIF", {
487       1, 2}, 0, {
488       0, 1},                    /* FIXME: check pixel-aspect from caps */
489       {
490     0, 1}, 0, 0};
491     m = gst_jif_mux_new_marker (APP0, sizeof (jfif_data),
492         (const guint8 *) &jfif_data, FALSE);
493     /* insert into self->markers list */
494     self->markers = g_list_insert (self->markers, m, 1);
495     app0_jfif = g_list_nth (self->markers, 1);
496   }
497   /* else */
498   /* remove JFIF if exists */
499 
500   /* Existing exif tags will be removed and our own will be added */
501   if (!tags) {
502     tags = (GstTagList *) gst_tag_setter_get_tag_list (GST_TAG_SETTER (self));
503     cleanup_tags = FALSE;
504   }
505   if (!tags) {
506     tags = gst_tag_list_new_empty ();
507     cleanup_tags = TRUE;
508   }
509 
510   GST_DEBUG_OBJECT (self, "Tags to be serialized %" GST_PTR_FORMAT, tags);
511 
512   /* FIXME: not happy with those
513    * - else where we would use VIDEO_CODEC = "Jpeg"
514    gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE,
515    GST_TAG_VIDEO_CODEC, "image/jpeg", NULL);
516    */
517 
518   /* Add EXIF */
519   {
520     GstBuffer *exif_data;
521     gsize exif_size;
522     guint8 *data;
523     GstJifMuxMarker *m;
524     GList *pos;
525 
526     /* insert into self->markers list */
527     exif_data = gst_tag_list_to_exif_buffer_with_tiff_header (tags);
528     exif_size = exif_data ? gst_buffer_get_size (exif_data) : 0;
529 
530     if (exif_data && exif_size + 8 >= G_GUINT64_CONSTANT (65536)) {
531       GST_WARNING_OBJECT (self, "Exif tags data size exceed maximum size");
532       gst_buffer_unref (exif_data);
533       exif_data = NULL;
534     }
535     if (exif_data) {
536       data = g_malloc0 (exif_size + 6);
537       memcpy (data, "Exif", 4);
538       gst_buffer_extract (exif_data, 0, data + 6, exif_size);
539       m = gst_jif_mux_new_marker (APP1, exif_size + 6, data, TRUE);
540       gst_buffer_unref (exif_data);
541 
542       if (app1_exif) {
543         gst_jif_mux_marker_free ((GstJifMuxMarker *) app1_exif->data);
544         app1_exif->data = m;
545       } else {
546         pos = file_hdr;
547         if (app0_jfif)
548           pos = app0_jfif;
549         pos = g_list_next (pos);
550 
551         self->markers = g_list_insert_before (self->markers, pos, m);
552         if (pos) {
553           app1_exif = g_list_previous (pos);
554         } else {
555           app1_exif = g_list_last (self->markers);
556         }
557       }
558       modified = TRUE;
559     }
560   }
561 
562   /* add xmp */
563   xmp_data =
564       gst_tag_xmp_writer_tag_list_to_xmp_buffer (GST_TAG_XMP_WRITER (self),
565       tags, FALSE);
566   if (xmp_data) {
567     guint8 *data;
568     gsize size;
569     GList *pos;
570 
571     size = gst_buffer_get_size (xmp_data);
572     data = g_malloc (size + 29);
573     memcpy (data, "http://ns.adobe.com/xap/1.0/\0", 29);
574     gst_buffer_extract (xmp_data, 0, &data[29], size);
575     m = gst_jif_mux_new_marker (APP1, size + 29, data, TRUE);
576 
577     /*
578      * Replace the old xmp marker and not add a new one.
579      * There shouldn't be a xmp packet in the input, but it is better
580      * to be safe than add another one and end up with 2 packets.
581      */
582     if (app1_xmp) {
583       gst_jif_mux_marker_free ((GstJifMuxMarker *) app1_xmp->data);
584       app1_xmp->data = m;
585     } else {
586 
587       pos = file_hdr;
588       if (app1_exif)
589         pos = app1_exif;
590       else if (app0_jfif)
591         pos = app0_jfif;
592       pos = g_list_next (pos);
593 
594       self->markers = g_list_insert_before (self->markers, pos, m);
595 
596     }
597     gst_buffer_unref (xmp_data);
598     modified = TRUE;
599   }
600 
601   /* add jpeg comment from any of those */
602   (void) (gst_tag_list_get_string (tags, GST_TAG_COMMENT, &str) ||
603       gst_tag_list_get_string (tags, GST_TAG_DESCRIPTION, &str) ||
604       gst_tag_list_get_string (tags, GST_TAG_TITLE, &str));
605 
606   if (str) {
607     GST_DEBUG_OBJECT (self, "set COM marker to '%s'", str);
608     /* insert new marker into self->markers list */
609     m = gst_jif_mux_new_marker (COM, strlen (str) + 1, (const guint8 *) str,
610         TRUE);
611     /* FIXME: if we have one already, replace */
612     /* this should go before SOS, maybe at the end of file-header */
613     self->markers = g_list_insert_before (self->markers, frame_hdr, m);
614 
615     modified = TRUE;
616   }
617 
618   if (tags && cleanup_tags)
619     gst_tag_list_unref (tags);
620   return modified;
621 }
622 
623 static GstFlowReturn
gst_jif_mux_recombine_image(GstJifMux * self,GstBuffer ** new_buf,GstBuffer * old_buf)624 gst_jif_mux_recombine_image (GstJifMux * self, GstBuffer ** new_buf,
625     GstBuffer * old_buf)
626 {
627   GstBuffer *buf;
628   GstByteWriter *writer;
629   GstJifMuxMarker *m;
630   GList *node;
631   guint size = self->scan_size;
632   gboolean writer_status = TRUE;
633   GstMapInfo map;
634 
635   /* iterate list and collect size */
636   for (node = self->markers; node; node = g_list_next (node)) {
637     m = (GstJifMuxMarker *) node->data;
638 
639     /* some markers like e.g. SOI are empty */
640     if (m->size) {
641       size += 2 + m->size;
642     }
643     /* 0xff <marker> */
644     size += 2;
645   }
646   GST_INFO_OBJECT (self, "old size: %" G_GSIZE_FORMAT ", new size: %u",
647       gst_buffer_get_size (old_buf), size);
648 
649   /* allocate new buffer */
650   buf = gst_buffer_new_allocate (NULL, size, NULL);
651 
652   /* copy buffer metadata */
653   gst_buffer_copy_into (buf, old_buf,
654       GST_BUFFER_COPY_FLAGS | GST_BUFFER_COPY_TIMESTAMPS, 0, -1);
655 
656   /* memcopy markers */
657   gst_buffer_map (buf, &map, GST_MAP_WRITE);
658   writer = gst_byte_writer_new_with_data (map.data, map.size, TRUE);
659 
660   for (node = self->markers; node && writer_status; node = g_list_next (node)) {
661     m = (GstJifMuxMarker *) node->data;
662 
663     writer_status &= gst_byte_writer_put_uint8 (writer, 0xff);
664     writer_status &= gst_byte_writer_put_uint8 (writer, m->marker);
665 
666     GST_DEBUG_OBJECT (self, "marker = %2x, size = %u", m->marker, m->size + 2);
667 
668     if (m->size) {
669       writer_status &= gst_byte_writer_put_uint16_be (writer, m->size + 2);
670       writer_status &= gst_byte_writer_put_data (writer, m->data, m->size);
671     }
672 
673     if (m->marker == SOS) {
674       GST_DEBUG_OBJECT (self, "scan data, size = %u", self->scan_size);
675       writer_status &=
676           gst_byte_writer_put_data (writer, self->scan_data, self->scan_size);
677     }
678   }
679   gst_buffer_unmap (buf, &map);
680   gst_byte_writer_free (writer);
681 
682   if (!writer_status) {
683     GST_WARNING_OBJECT (self, "Failed to write to buffer, calculated size "
684         "was probably too short");
685     g_assert_not_reached ();
686   }
687 
688   *new_buf = buf;
689   return GST_FLOW_OK;
690 }
691 
692 static GstFlowReturn
gst_jif_mux_chain(GstPad * pad,GstObject * parent,GstBuffer * buf)693 gst_jif_mux_chain (GstPad * pad, GstObject * parent, GstBuffer * buf)
694 {
695   GstJifMux *self = GST_JIF_MUX (parent);
696   GstFlowReturn fret = GST_FLOW_OK;
697 
698 #if 0
699   GST_MEMDUMP ("jpeg beg", GST_BUFFER_DATA (buf), 64);
700   GST_MEMDUMP ("jpeg end", GST_BUFFER_DATA (buf) + GST_BUFFER_SIZE (buf) - 64,
701       64);
702 #endif
703 
704   /* we should have received a whole picture from SOI to EOI
705    * build a list of markers */
706   if (gst_jif_mux_parse_image (self, buf)) {
707     /* modify marker list */
708     if (gst_jif_mux_mangle_markers (self)) {
709       /* the list was changed, remux */
710       GstBuffer *old = buf;
711       fret = gst_jif_mux_recombine_image (self, &buf, old);
712       gst_buffer_unref (old);
713     }
714   }
715 
716   /* free the marker list */
717   gst_jif_mux_reset (self);
718 
719   if (fret == GST_FLOW_OK) {
720     fret = gst_pad_push (self->srcpad, buf);
721   }
722   return fret;
723 }
724 
725 static GstStateChangeReturn
gst_jif_mux_change_state(GstElement * element,GstStateChange transition)726 gst_jif_mux_change_state (GstElement * element, GstStateChange transition)
727 {
728   GstStateChangeReturn ret;
729   GstJifMux *self = GST_JIF_MUX_CAST (element);
730 
731   switch (transition) {
732     case GST_STATE_CHANGE_NULL_TO_READY:
733       break;
734     case GST_STATE_CHANGE_READY_TO_PAUSED:
735       break;
736     case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
737       break;
738     default:
739       break;
740   }
741 
742   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
743 
744   switch (transition) {
745     case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
746       break;
747     case GST_STATE_CHANGE_PAUSED_TO_READY:
748       gst_tag_setter_reset_tags (GST_TAG_SETTER (self));
749       break;
750     case GST_STATE_CHANGE_READY_TO_NULL:
751       break;
752     default:
753       break;
754   }
755 
756   return ret;
757 }
758