gstjpegenc.c 21.8 KB
Newer Older
Christian Schaller's avatar
Christian Schaller committed
1
/* GStreamer
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */
19
20
21
22
/**
 * SECTION:element-jpegenc
 *
 * Encodes jpeg images.
23
24
25
26
27
28
29
30
 *
 * <refsect2>
 * <title>Example launch line</title>
 * |[
 * gst-launch videotestsrc num-buffers=50 ! video/x-raw-yuv, framerate='(fraction)'5/1 ! jpegenc ! avimux ! filesink location=mjpeg.avi
 * ]| a pipeline to mux 5 JPEG frames per second into a 10 sec. long motion jpeg
 * avi.
 * </refsect2>
31
 */
32

33
34
35
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
36
37
38
#include <string.h>

#include "gstjpegenc.h"
39
#include "gstjpeg.h"
Iain Holmes's avatar
Iain Holmes committed
40
#include <gst/video/video.h>
41

Stefan Kost's avatar
Stefan Kost committed
42
43
44
45
/* experimental */
/* setting smoothig seems to have no effect in libjepeg
#define ENABLE_SMOOTHING 1
*/
46

47
GST_DEBUG_CATEGORY_STATIC (jpegenc_debug);
48
49
#define GST_CAT_DEFAULT jpegenc_debug

50
51
52
#define JPEG_DEFAULT_QUALITY 85
#define JPEG_DEFAULT_SMOOTHING 0
#define JPEG_DEFAULT_IDCT_METHOD	JDCT_FASTEST
53

54
/* JpegEnc signals and args */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
55
56
enum
{
57
58
59
60
61
  FRAME_ENCODED,
  /* FILL ME */
  LAST_SIGNAL
};

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
62
63
enum
{
Stefan Kost's avatar
Stefan Kost committed
64
65
66
67
  PROP_0,
  PROP_QUALITY,
  PROP_SMOOTHING,
  PROP_IDCT_METHOD
68
69
};

70
static void gst_jpegenc_reset (GstJpegEnc * enc);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
71
72
73
static void gst_jpegenc_base_init (gpointer g_class);
static void gst_jpegenc_class_init (GstJpegEnc * klass);
static void gst_jpegenc_init (GstJpegEnc * jpegenc);
74
static void gst_jpegenc_finalize (GObject * object);
75

76
77
static GstFlowReturn gst_jpegenc_chain (GstPad * pad, GstBuffer * buf);
static gboolean gst_jpegenc_setcaps (GstPad * pad, GstCaps * caps);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
78
static GstCaps *gst_jpegenc_getcaps (GstPad * pad);
79

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
80
81
82
83
84
static void gst_jpegenc_resync (GstJpegEnc * jpegenc);
static void gst_jpegenc_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec);
static void gst_jpegenc_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec);
85
86
static GstStateChangeReturn gst_jpegenc_change_state (GstElement * element,
    GstStateChange transition);
87

88
89
90
91
92
93
94
95
96
97
98

static GstElementClass *parent_class = NULL;
static guint gst_jpegenc_signals[LAST_SIGNAL] = { 0 };

GType
gst_jpegenc_get_type (void)
{
  static GType jpegenc_type = 0;

  if (!jpegenc_type) {
    static const GTypeInfo jpegenc_info = {
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
99
      sizeof (GstJpegEnc),
100
      (GBaseInitFunc) gst_jpegenc_base_init,
101
      NULL,
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
102
      (GClassInitFunc) gst_jpegenc_class_init,
103
104
      NULL,
      NULL,
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
105
      sizeof (GstJpegEnc),
106
      0,
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
107
      (GInstanceInitFunc) gst_jpegenc_init,
108
    };
109

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
110
    jpegenc_type =
111
112
        g_type_register_static (GST_TYPE_ELEMENT, "GstJpegEnc", &jpegenc_info,
        0);
113
114
115
116
  }
  return jpegenc_type;
}

117
/* *INDENT-OFF* */
118
static GstStaticPadTemplate gst_jpegenc_sink_pad_template =
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
119
GST_STATIC_PAD_TEMPLATE ("sink",
120
121
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
122
123
124
125
126
    GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV
        ("{ I420, YV12, YUY2, UYVY, Y41B, Y42B, YVYU, Y444 }") "; "
        GST_VIDEO_CAPS_RGB "; " GST_VIDEO_CAPS_BGR "; "
        GST_VIDEO_CAPS_RGBx "; " GST_VIDEO_CAPS_xRGB "; "
        GST_VIDEO_CAPS_BGRx "; " GST_VIDEO_CAPS_xBGR)
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
127
    );
128
/* *INDENT-ON* */
129
130

static GstStaticPadTemplate gst_jpegenc_src_pad_template =
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
131
GST_STATIC_PAD_TEMPLATE ("src",
132
133
134
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("image/jpeg, "
135
136
        "width = (int) [ 16, 65535 ], "
        "height = (int) [ 16, 65535 ], " "framerate = (fraction) [ 0/1, MAX ]")
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
137
    );
Iain Holmes's avatar
Iain Holmes committed
138
139
140
141
142

static void
gst_jpegenc_base_init (gpointer g_class)
{
  GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
143

144
145
146
147
  gst_element_class_add_pad_template (element_class,
      gst_static_pad_template_get (&gst_jpegenc_sink_pad_template));
  gst_element_class_add_pad_template (element_class,
      gst_static_pad_template_get (&gst_jpegenc_src_pad_template));
148
149
150
  gst_element_class_set_details_simple (element_class, "JPEG image encoder",
      "Codec/Encoder/Image",
      "Encode images in JPEG format", "Wim Taymans <wim.taymans@tvd.be>");
Iain Holmes's avatar
Iain Holmes committed
151
152
}

153
static void
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
154
gst_jpegenc_class_init (GstJpegEnc * klass)
155
156
157
158
{
  GObjectClass *gobject_class;
  GstElementClass *gstelement_class;

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
159
160
  gobject_class = (GObjectClass *) klass;
  gstelement_class = (GstElementClass *) klass;
161

162
  parent_class = g_type_class_peek_parent (klass);
163
164

  gst_jpegenc_signals[FRAME_ENCODED] =
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
165
166
167
      g_signal_new ("frame-encoded", G_TYPE_FROM_CLASS (klass),
      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstJpegEncClass, frame_encoded), NULL,
      NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
168

169
170
171
172
  gobject_class->set_property = gst_jpegenc_set_property;
  gobject_class->get_property = gst_jpegenc_get_property;


Stefan Kost's avatar
Stefan Kost committed
173
  g_object_class_install_property (gobject_class, PROP_QUALITY,
174
      g_param_spec_int ("quality", "Quality", "Quality of encoding",
175
          0, 100, JPEG_DEFAULT_QUALITY, G_PARAM_READWRITE));
Stefan Kost's avatar
Stefan Kost committed
176

Benjamin Otte's avatar
Benjamin Otte committed
177
#ifdef ENABLE_SMOOTHING
178
  /* disabled, since it doesn't seem to work */
Stefan Kost's avatar
Stefan Kost committed
179
  g_object_class_install_property (gobject_class, PROP_SMOOTHING,
180
      g_param_spec_int ("smoothing", "Smoothing", "Smoothing factor",
181
          0, 100, JPEG_DEFAULT_SMOOTHING, G_PARAM_READWRITE));
182
183
#endif

Stefan Kost's avatar
Stefan Kost committed
184
185
186
  g_object_class_install_property (gobject_class, PROP_IDCT_METHOD,
      g_param_spec_enum ("idct-method", "IDCT Method",
          "The IDCT algorithm to use", GST_TYPE_IDCT_METHOD,
187
          JPEG_DEFAULT_IDCT_METHOD, G_PARAM_READWRITE));
Stefan Kost's avatar
Stefan Kost committed
188

189
190
191
  gstelement_class->change_state = gst_jpegenc_change_state;

  gobject_class->finalize = gst_jpegenc_finalize;
192
193
194

  GST_DEBUG_CATEGORY_INIT (jpegenc_debug, "jpegenc", 0,
      "JPEG encoding element");
195
196
197
198
199
}

static void
gst_jpegenc_init_destination (j_compress_ptr cinfo)
{
200
  GST_DEBUG ("gst_jpegenc_chain: init_destination");
201
202
}

203
static boolean
204
205
gst_jpegenc_flush_destination (j_compress_ptr cinfo)
{
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
  GstBuffer *overflow_buffer;
  guint32 old_buffer_size;
  GstJpegEnc *jpegenc = (GstJpegEnc *) (cinfo->client_data);
  GST_DEBUG_OBJECT (jpegenc,
      "gst_jpegenc_chain: flush_destination: buffer too small");

  /* Our output buffer wasn't big enough.
   * Make a new buffer that's twice the size, */
  old_buffer_size = GST_BUFFER_SIZE (jpegenc->output_buffer);
  gst_pad_alloc_buffer_and_set_caps (jpegenc->srcpad,
      GST_BUFFER_OFFSET_NONE, old_buffer_size * 2,
      GST_PAD_CAPS (jpegenc->srcpad), &overflow_buffer);
  memcpy (GST_BUFFER_DATA (overflow_buffer),
      GST_BUFFER_DATA (jpegenc->output_buffer), old_buffer_size);

221
222
223
  gst_buffer_copy_metadata (overflow_buffer, jpegenc->output_buffer,
      GST_BUFFER_COPY_TIMESTAMPS);

224
225
226
227
228
229
230
231
232
233
  /* drop it into place, */
  gst_buffer_unref (jpegenc->output_buffer);
  jpegenc->output_buffer = overflow_buffer;

  /* and last, update libjpeg on where to work. */
  jpegenc->jdest.next_output_byte =
      GST_BUFFER_DATA (jpegenc->output_buffer) + old_buffer_size;
  jpegenc->jdest.free_in_buffer =
      GST_BUFFER_SIZE (jpegenc->output_buffer) - old_buffer_size;

234
235
236
  return TRUE;
}

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
237
238
static void
gst_jpegenc_term_destination (j_compress_ptr cinfo)
239
{
240
241
242
243
244
245
246
247
248
249
250
251
  GstJpegEnc *jpegenc = (GstJpegEnc *) (cinfo->client_data);
  GST_DEBUG_OBJECT (jpegenc, "gst_jpegenc_chain: term_source");

  /* Trim the buffer size and push it. */
  GST_BUFFER_SIZE (jpegenc->output_buffer) =
      GST_ROUND_UP_4 (GST_BUFFER_SIZE (jpegenc->output_buffer) -
      jpegenc->jdest.free_in_buffer);

  g_signal_emit (G_OBJECT (jpegenc), gst_jpegenc_signals[FRAME_ENCODED], 0);

  jpegenc->last_ret = gst_pad_push (jpegenc->srcpad, jpegenc->output_buffer);
  jpegenc->output_buffer = NULL;
252
253
254
}

static void
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
255
gst_jpegenc_init (GstJpegEnc * jpegenc)
256
257
{
  /* create the sink and src pads */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
258
  jpegenc->sinkpad =
259
      gst_pad_new_from_static_template (&gst_jpegenc_sink_pad_template, "sink");
260
261
262
263
264
265
  gst_pad_set_chain_function (jpegenc->sinkpad,
      GST_DEBUG_FUNCPTR (gst_jpegenc_chain));
  gst_pad_set_getcaps_function (jpegenc->sinkpad,
      GST_DEBUG_FUNCPTR (gst_jpegenc_getcaps));
  gst_pad_set_setcaps_function (jpegenc->sinkpad,
      GST_DEBUG_FUNCPTR (gst_jpegenc_setcaps));
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
266
267
268
  gst_element_add_pad (GST_ELEMENT (jpegenc), jpegenc->sinkpad);

  jpegenc->srcpad =
269
      gst_pad_new_from_static_template (&gst_jpegenc_src_pad_template, "src");
270
  gst_pad_use_fixed_caps (jpegenc->srcpad);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
271
  gst_element_add_pad (GST_ELEMENT (jpegenc), jpegenc->srcpad);
272

Christian Schaller's avatar
Christian Schaller committed
273
  /* reset the initial video state */
274
275
276
277
  jpegenc->width = -1;
  jpegenc->height = -1;

  /* setup jpeglib */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
278
279
280
281
  memset (&jpegenc->cinfo, 0, sizeof (jpegenc->cinfo));
  memset (&jpegenc->jerr, 0, sizeof (jpegenc->jerr));
  jpegenc->cinfo.err = jpeg_std_error (&jpegenc->jerr);
  jpeg_create_compress (&jpegenc->cinfo);
282
283
284
285
286

  jpegenc->jdest.init_destination = gst_jpegenc_init_destination;
  jpegenc->jdest.empty_output_buffer = gst_jpegenc_flush_destination;
  jpegenc->jdest.term_destination = gst_jpegenc_term_destination;
  jpegenc->cinfo.dest = &jpegenc->jdest;
287
288
  jpegenc->cinfo.client_data = jpegenc;

Stefan Kost's avatar
Stefan Kost committed
289
  /* init properties */
290
291
292
  jpegenc->quality = JPEG_DEFAULT_QUALITY;
  jpegenc->smoothing = JPEG_DEFAULT_SMOOTHING;
  jpegenc->idct_method = JPEG_DEFAULT_IDCT_METHOD;
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318

  gst_jpegenc_reset (jpegenc);
}

static void
gst_jpegenc_reset (GstJpegEnc * enc)
{
  gint i, j;

  g_free (enc->line[0]);
  g_free (enc->line[1]);
  g_free (enc->line[2]);
  enc->line[0] = NULL;
  enc->line[1] = NULL;
  enc->line[2] = NULL;
  for (i = 0; i < 3; i++) {
    for (j = 0; j < 4 * DCTSIZE; j++) {
      g_free (enc->row[i][j]);
    }
  }

  enc->width = -1;
  enc->height = -1;
  enc->format = GST_VIDEO_FORMAT_UNKNOWN;
  enc->fps_den = enc->par_den = 0;
  enc->height = enc->width = 0;
319
320
}

321
322
323
324
325
326
327
328
329
330
static void
gst_jpegenc_finalize (GObject * object)
{
  GstJpegEnc *filter = GST_JPEGENC (object);

  jpeg_destroy_compress (&filter->cinfo);

  G_OBJECT_CLASS (parent_class)->finalize (object);
}

331
static GstCaps *
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
332
gst_jpegenc_getcaps (GstPad * pad)
333
334
{
  GstJpegEnc *jpegenc = GST_JPEGENC (gst_pad_get_parent (pad));
335
336
337
  GstCaps *caps, *othercaps;
  const GstCaps *templ;
  gint i, j;
338
  GstStructure *structure = NULL;
339

340
341
  /* we want to proxy properties like width, height and framerate from the
     other end of the element */
342

343
344
  othercaps = gst_pad_get_allowed_caps (jpegenc->srcpad);
  if (othercaps == NULL ||
Tim-Philipp Müller's avatar
Tim-Philipp Müller committed
345
346
347
348
      gst_caps_is_empty (othercaps) || gst_caps_is_any (othercaps)) {
    caps = gst_caps_copy (gst_pad_get_pad_template_caps (pad));
    goto done;
  }
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368

  caps = gst_caps_new_empty ();
  templ = gst_pad_get_pad_template_caps (pad);

  for (i = 0; i < gst_caps_get_size (templ); i++) {
    /* pick fields from peer caps */
    for (j = 0; j < gst_caps_get_size (othercaps); j++) {
      GstStructure *s = gst_caps_get_structure (othercaps, j);
      const GValue *val;

      structure = gst_structure_copy (gst_caps_get_structure (templ, i));
      if ((val = gst_structure_get_value (s, "width")))
        gst_structure_set_value (structure, "width", val);
      if ((val = gst_structure_get_value (s, "height")))
        gst_structure_set_value (structure, "height", val);
      if ((val = gst_structure_get_value (s, "framerate")))
        gst_structure_set_value (structure, "framerate", val);

      gst_caps_merge_structure (caps, structure);
    }
369
  }
Tim-Philipp Müller's avatar
Tim-Philipp Müller committed
370
371
372
373

done:

  gst_caps_replace (&othercaps, NULL);
374
  gst_object_unref (jpegenc);
375
376

  return caps;
377
378
}

379
380
static gboolean
gst_jpegenc_setcaps (GstPad * pad, GstCaps * caps)
381
{
382
383
384
385
386
387
388
  GstJpegEnc *enc = GST_JPEGENC (gst_pad_get_parent (pad));
  GstVideoFormat format;
  gint width, height;
  gint fps_num, fps_den;
  gint par_num, par_den;
  gint i;
  GstCaps *othercaps;
389
  gboolean ret;
390

391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
  /* get info from caps */
  if (!gst_video_format_parse_caps (caps, &format, &width, &height))
    goto refuse_caps;
  /* optional; pass along if present */
  fps_num = fps_den = -1;
  par_num = par_den = -1;
  gst_video_parse_caps_framerate (caps, &fps_num, &fps_den);
  gst_video_parse_caps_pixel_aspect_ratio (caps, &par_num, &par_den);

  if (width == enc->width && height == enc->height && enc->format == format
      && fps_num == enc->fps_num && fps_den == enc->fps_den
      && par_num == enc->par_num && par_den == enc->par_den)
    return TRUE;

  /* store input description */
  enc->format = format;
  enc->width = width;
  enc->height = height;
  enc->fps_num = fps_num;
  enc->fps_den = fps_den;
  enc->par_num = par_num;
  enc->par_den = par_den;

  /* prepare a cached image description  */
  enc->channels = 3 + (gst_video_format_has_alpha (format) ? 1 : 0);
  /* ... but any alpha is disregarded in encoding */
  enc->channels = 3;
  enc->h_max_samp = 0;
  enc->v_max_samp = 0;
  for (i = 0; i < enc->channels; ++i) {
    enc->cwidth[i] = gst_video_format_get_component_width (format, i, width);
    enc->cheight[i] = gst_video_format_get_component_height (format, i, height);
    enc->offset[i] = gst_video_format_get_component_offset (format, i, width,
        height);
    enc->stride[i] = gst_video_format_get_row_stride (format, i, width);
    enc->inc[i] = gst_video_format_get_pixel_stride (format, i);
    enc->h_samp[i] = GST_ROUND_UP_4 (width) / enc->cwidth[i];
    enc->h_max_samp = MAX (enc->h_max_samp, enc->h_samp[i]);
    enc->v_samp[i] = GST_ROUND_UP_4 (height) / enc->cheight[i];
    enc->v_max_samp = MAX (enc->v_max_samp, enc->v_samp[i]);
  }
  /* samp should only be 1, 2 or 4 */
  g_assert (enc->h_max_samp <= 4);
  g_assert (enc->v_max_samp <= 4);
  /* now invert */
  /* maximum is invariant, as one of the components should have samp 1 */
  for (i = 0; i < enc->channels; ++i) {
    enc->h_samp[i] = enc->h_max_samp / enc->h_samp[i];
    enc->v_samp[i] = enc->v_max_samp / enc->v_samp[i];
  }
  enc->planar = (enc->inc[0] == 1 && enc->inc[1] == 1 && enc->inc[2] == 1);

  othercaps = gst_caps_copy (gst_pad_get_pad_template_caps (enc->srcpad));
  gst_caps_set_simple (othercaps,
      "width", G_TYPE_INT, enc->width, "height", G_TYPE_INT, enc->height, NULL);
  if (enc->fps_den > 0)
    gst_caps_set_simple (othercaps,
        "framerate", GST_TYPE_FRACTION, enc->fps_num, enc->fps_den, NULL);
  if (enc->par_den > 0)
    gst_caps_set_simple (othercaps,
        "pixel-aspect-ratio", GST_TYPE_FRACTION, enc->par_num, enc->par_den,
        NULL);

  ret = gst_pad_set_caps (enc->srcpad, othercaps);
  gst_caps_unref (othercaps);
456

Wim Taymans's avatar
Wim Taymans committed
457
  if (ret)
458
    gst_jpegenc_resync (enc);
459

460
  gst_object_unref (enc);
461

462
  return ret;
463
464
465
466
467
468
469
470

  /* ERRORS */
refuse_caps:
  {
    GST_WARNING_OBJECT (enc, "refused caps %" GST_PTR_FORMAT, caps);
    gst_object_unref (enc);
    return FALSE;
  }
471
472
}

473
static void
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
474
gst_jpegenc_resync (GstJpegEnc * jpegenc)
475
476
{
  gint width, height;
477
  gint i, j;
478

479
  GST_DEBUG_OBJECT (jpegenc, "resync");
480
481
482
483
484

  jpegenc->cinfo.image_width = width = jpegenc->width;
  jpegenc->cinfo.image_height = height = jpegenc->height;
  jpegenc->cinfo.input_components = 3;

485
  GST_DEBUG_OBJECT (jpegenc, "width %d, height %d", width, height);
486
487
488
489
490
491
492
493
494
  GST_DEBUG_OBJECT (jpegenc, "format %d", jpegenc->format);

  if (gst_video_format_is_rgb (jpegenc->format)) {
    GST_DEBUG_OBJECT (jpegenc, "RGB");
    jpegenc->cinfo.in_color_space = JCS_RGB;
  } else {
    GST_DEBUG_OBJECT (jpegenc, "YUV");
    jpegenc->cinfo.in_color_space = JCS_YCbCr;
  }
495

496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
  /* input buffer size as max output */
  jpegenc->bufsize = gst_video_format_get_size (jpegenc->format, width, height);
  jpeg_set_defaults (&jpegenc->cinfo);
  jpegenc->cinfo.raw_data_in = TRUE;
  /* duh, libjpeg maps RGB to YUV ... and don't expect some conversion */
  if (jpegenc->cinfo.in_color_space == JCS_RGB)
    jpeg_set_colorspace (&jpegenc->cinfo, JCS_RGB);

  GST_DEBUG_OBJECT (jpegenc, "h_max_samp=%d, v_max_samp=%d",
      jpegenc->h_max_samp, jpegenc->v_max_samp);
  /* image dimension info */
  for (i = 0; i < 3; i++) {
    GST_DEBUG_OBJECT (jpegenc, "comp %i: h_samp=%d, v_samp=%d", i,
        jpegenc->h_samp[i], jpegenc->v_samp[i]);
    jpegenc->cinfo.comp_info[i].h_samp_factor = jpegenc->h_samp[i];
    jpegenc->cinfo.comp_info[i].v_samp_factor = jpegenc->v_samp[i];
    jpegenc->line[i] = g_realloc (jpegenc->line[i],
        jpegenc->v_max_samp * DCTSIZE * sizeof (char *));
    if (!jpegenc->planar) {
      for (j = 0; j < jpegenc->v_max_samp * DCTSIZE; j++) {
        jpegenc->row[i][j] = g_realloc (jpegenc->row[i][j], width);
        jpegenc->line[i][j] = jpegenc->row[i][j];
518
      }
519
    }
520
521
  }

522
523
524
525
  /* guard against a potential error in gst_jpegenc_term_destination
     which occurs iff bufsize % 4 < free_space_remaining */
  jpegenc->bufsize = GST_ROUND_UP_4 (jpegenc->bufsize);

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
526
  jpeg_suppress_tables (&jpegenc->cinfo, TRUE);
527

528
  GST_DEBUG_OBJECT (jpegenc, "resync done");
529
530
}

531
532
static GstFlowReturn
gst_jpegenc_chain (GstPad * pad, GstBuffer * buf)
533
{
534
  GstFlowReturn ret;
535
  GstJpegEnc *jpegenc;
536
537
  guchar *data;
  gulong size;
Tim-Philipp Müller's avatar
Tim-Philipp Müller committed
538
  guint height, width;
539
  guchar *base[3], *end[3];
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
540
  gint i, j, k;
541
542
543

  jpegenc = GST_JPEGENC (GST_OBJECT_PARENT (pad));

544
545
546
  if (G_UNLIKELY (jpegenc->width <= 0 || jpegenc->height <= 0))
    goto not_negotiated;

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
547
548
  data = GST_BUFFER_DATA (buf);
  size = GST_BUFFER_SIZE (buf);
549

Stefan Kost's avatar
Stefan Kost committed
550
  GST_LOG_OBJECT (jpegenc, "got buffer of %lu bytes", size);
551

Andy Wingo's avatar
Andy Wingo committed
552
553
554
  ret =
      gst_pad_alloc_buffer_and_set_caps (jpegenc->srcpad,
      GST_BUFFER_OFFSET_NONE, jpegenc->bufsize, GST_PAD_CAPS (jpegenc->srcpad),
555
      &jpegenc->output_buffer);
556
557
558

  if (ret != GST_FLOW_OK)
    goto done;
559

560
561
  gst_buffer_copy_metadata (jpegenc->output_buffer, buf,
      GST_BUFFER_COPY_TIMESTAMPS);
562
563
564
565

  width = jpegenc->width;
  height = jpegenc->height;

566
567
568
  base[0] = data + jpegenc->offset[0];
  base[1] = data + jpegenc->offset[1];
  base[2] = data + jpegenc->offset[2];
569

570
571
572
  end[0] = base[0] + jpegenc->cheight[0] * jpegenc->stride[0];
  end[1] = base[1] + jpegenc->cheight[1] * jpegenc->stride[1];
  end[2] = base[2] + jpegenc->cheight[2] * jpegenc->stride[2];
573

574
575
  jpegenc->jdest.next_output_byte = GST_BUFFER_DATA (jpegenc->output_buffer);
  jpegenc->jdest.free_in_buffer = GST_BUFFER_SIZE (jpegenc->output_buffer);
576

577
578
579
580
  /* prepare for raw input */
#if JPEG_LIB_VERSION >= 70
  jpegenc->cinfo.do_fancy_downsampling = FALSE;
#endif
581
  jpegenc->cinfo.smoothing_factor = jpegenc->smoothing;
Stefan Kost's avatar
Stefan Kost committed
582
  jpegenc->cinfo.dct_method = jpegenc->idct_method;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
583
584
  jpeg_set_quality (&jpegenc->cinfo, jpegenc->quality, TRUE);
  jpeg_start_compress (&jpegenc->cinfo, TRUE);
585

Stefan Kost's avatar
Stefan Kost committed
586
  GST_LOG_OBJECT (jpegenc, "compressing");
587

588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
  if (jpegenc->planar) {
    for (i = 0; i < height; i += jpegenc->v_max_samp * DCTSIZE) {
      for (k = 0; k < jpegenc->channels; k++) {
        for (j = 0; j < jpegenc->v_samp[k] * DCTSIZE; j++) {
          jpegenc->line[k][j] = base[k];
          if (base[k] + jpegenc->stride[k] < end[k])
            base[k] += jpegenc->stride[k];
        }
      }
      jpeg_write_raw_data (&jpegenc->cinfo, jpegenc->line,
          jpegenc->v_max_samp * DCTSIZE);
    }
  } else {
    for (i = 0; i < height; i += jpegenc->v_max_samp * DCTSIZE) {
      for (k = 0; k < jpegenc->channels; k++) {
        for (j = 0; j < jpegenc->v_samp[k] * DCTSIZE; j++) {
          guchar *src, *dst;
          gint l;

          /* ouch, copy line */
          src = base[k];
          dst = jpegenc->line[k][j];
          for (l = jpegenc->cwidth[k]; l > 0; l--) {
            *dst = *src;
            src += jpegenc->inc[k];
            dst++;
          }
          if (base[k] + jpegenc->stride[k] < end[k])
            base[k] += jpegenc->stride[k];
        }
      }
      jpeg_write_raw_data (&jpegenc->cinfo, jpegenc->line,
          jpegenc->v_max_samp * DCTSIZE);
621
622
    }
  }
Tim-Philipp Müller's avatar
Tim-Philipp Müller committed
623

624
625
  /* This will ensure that gst_jpegenc_term_destination is called; we push
     the final output buffer from there */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
626
  jpeg_finish_compress (&jpegenc->cinfo);
Stefan Kost's avatar
Stefan Kost committed
627
  GST_LOG_OBJECT (jpegenc, "compressing done");
628

629
630
done:
  gst_buffer_unref (buf);
631

632
  return ret;
633
634
635
636
637
638
639
640

/* ERRORS */
not_negotiated:
  {
    GST_WARNING_OBJECT (jpegenc, "no input format set (no caps on buffer)");
    ret = GST_FLOW_NOT_NEGOTIATED;
    goto done;
  }
641
}
642
643
644
645
646

static void
gst_jpegenc_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec)
{
647
  GstJpegEnc *jpegenc = GST_JPEGENC (object);
648

649
  GST_OBJECT_LOCK (jpegenc);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
650

651
  switch (prop_id) {
Stefan Kost's avatar
Stefan Kost committed
652
    case PROP_QUALITY:
653
654
      jpegenc->quality = g_value_get_int (value);
      break;
Benjamin Otte's avatar
Benjamin Otte committed
655
#ifdef ENABLE_SMOOTHING
Stefan Kost's avatar
Stefan Kost committed
656
    case PROP_SMOOTHING:
657
658
      jpegenc->smoothing = g_value_get_int (value);
      break;
Stefan Kost's avatar
Stefan Kost committed
659
660
661
662
#endif
    case PROP_IDCT_METHOD:
      jpegenc->idct_method = g_value_get_enum (value);
      break;
663
    default:
664
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
665
666
      break;
  }
667
668

  GST_OBJECT_UNLOCK (jpegenc);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
669
}
670
671
672
673
674

static void
gst_jpegenc_get_property (GObject * object, guint prop_id, GValue * value,
    GParamSpec * pspec)
{
675
  GstJpegEnc *jpegenc = GST_JPEGENC (object);
676

677
  GST_OBJECT_LOCK (jpegenc);
678
679

  switch (prop_id) {
Stefan Kost's avatar
Stefan Kost committed
680
    case PROP_QUALITY:
681
682
      g_value_set_int (value, jpegenc->quality);
      break;
Benjamin Otte's avatar
Benjamin Otte committed
683
#ifdef ENABLE_SMOOTHING
Stefan Kost's avatar
Stefan Kost committed
684
    case PROP_SMOOTHING:
685
686
      g_value_set_int (value, jpegenc->smoothing);
      break;
Stefan Kost's avatar
Stefan Kost committed
687
688
689
690
#endif
    case PROP_IDCT_METHOD:
      g_value_set_enum (value, jpegenc->idct_method);
      break;
691
692
693
694
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
695
696

  GST_OBJECT_UNLOCK (jpegenc);
697
}
698

699
700
static GstStateChangeReturn
gst_jpegenc_change_state (GstElement * element, GstStateChange transition)
701
{
702
  GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
703
704
  GstJpegEnc *filter = GST_JPEGENC (element);

705
706
  switch (transition) {
    case GST_STATE_CHANGE_NULL_TO_READY:
707
      GST_DEBUG_OBJECT (element, "setting line buffers");
708
709
710
711
      filter->line[0] = NULL;
      filter->line[1] = NULL;
      filter->line[2] = NULL;
      break;
712
713
714
715
716
717
718
719
720
    default:
      break;
  }

  ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
  if (ret == GST_STATE_CHANGE_FAILURE)
    return ret;

  switch (transition) {
721
722
    case GST_STATE_CHANGE_PAUSED_TO_READY:
      gst_jpegenc_reset (filter);
723
724
725
726
727
      break;
    default:
      break;
  }

728
  return ret;
729
}