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