Tizen 2.0 Release
[framework/multimedia/gst-plugins-good0.10.git] / ext / jpeg / smokecodec.c
1 /* Smoke codec
2  * Copyright (C) <2004> Wim Taymans <wim@fluendo.com> 
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <math.h>
29
30 /* this is a hack hack hack to get around jpeglib header bugs... */
31 #ifdef HAVE_STDLIB_H
32 # undef HAVE_STDLIB_H
33 #endif
34 #include <jpeglib.h>
35
36 #include "smokecodec.h"
37 #include "smokeformat.h"
38
39 #include <gst/gstinfo.h>
40
41 struct _SmokeCodecInfo
42 {
43   unsigned int width;
44   unsigned int height;
45   unsigned int fps_num;
46   unsigned int fps_denom;
47
48   unsigned int minquality;
49   unsigned int maxquality;
50   unsigned int bitrate;
51   unsigned int threshold;
52
53   unsigned int refdec;
54
55   unsigned char **line[3];
56   unsigned char *compbuf[3];
57
58   struct jpeg_error_mgr jerr;
59
60   struct jpeg_compress_struct cinfo;
61   struct jpeg_destination_mgr jdest;
62
63   struct jpeg_decompress_struct dinfo;
64   struct jpeg_source_mgr jsrc;
65
66   int need_keyframe;
67   unsigned char *reference;
68 };
69
70 static void
71 smokecodec_init_destination (j_compress_ptr cinfo)
72 {
73 }
74
75 static boolean
76 smokecodec_flush_destination (j_compress_ptr cinfo)
77 {
78   return 1;
79 }
80
81 static void
82 smokecodec_term_destination (j_compress_ptr cinfo)
83 {
84 }
85
86 static void
87 smokecodec_init_source (j_decompress_ptr cinfo)
88 {
89 }
90
91 static boolean
92 smokecodec_fill_input_buffer (j_decompress_ptr cinfo)
93 {
94   return 1;
95 }
96
97 static void
98 smokecodec_skip_input_data (j_decompress_ptr cinfo, long num_bytes)
99 {
100 }
101
102 static boolean
103 smokecodec_resync_to_restart (j_decompress_ptr cinfo, int desired)
104 {
105   return 1;
106 }
107
108 static void
109 smokecodec_term_source (j_decompress_ptr cinfo)
110 {
111 }
112
113
114 int
115 smokecodec_encode_new (SmokeCodecInfo ** info,
116     const unsigned int width,
117     const unsigned int height,
118     const unsigned int fps_num, const unsigned int fps_denom)
119 {
120   SmokeCodecInfo *newinfo;
121   int i, j;
122   unsigned char *base[3];
123
124   if (!info)
125     return SMOKECODEC_NULLPTR;
126   if ((width & 0xf) || (height & 0xf))
127     return SMOKECODEC_WRONGSIZE;
128
129   newinfo = malloc (sizeof (SmokeCodecInfo));
130   if (!newinfo) {
131     return SMOKECODEC_NOMEM;
132   }
133   newinfo->width = width;
134   newinfo->height = height;
135   newinfo->fps_num = fps_num;
136   newinfo->fps_denom = fps_denom;
137
138   /* setup jpeglib */
139   memset (&newinfo->cinfo, 0, sizeof (newinfo->cinfo));
140   memset (&newinfo->jerr, 0, sizeof (newinfo->jerr));
141   newinfo->cinfo.err = jpeg_std_error (&newinfo->jerr);
142   jpeg_create_compress (&newinfo->cinfo);
143   newinfo->cinfo.input_components = 3;
144   jpeg_set_defaults (&newinfo->cinfo);
145
146   newinfo->cinfo.dct_method = JDCT_FASTEST;
147
148   /* prepare for raw input */
149 #if JPEG_LIB_VERSION >= 70
150   newinfo->cinfo.do_fancy_downsampling = FALSE;
151 #endif
152
153   newinfo->cinfo.raw_data_in = TRUE;
154   newinfo->cinfo.in_color_space = JCS_YCbCr;
155   newinfo->cinfo.comp_info[0].h_samp_factor = 2;
156   newinfo->cinfo.comp_info[0].v_samp_factor = 2;
157   newinfo->cinfo.comp_info[1].h_samp_factor = 1;
158   newinfo->cinfo.comp_info[1].v_samp_factor = 1;
159   newinfo->cinfo.comp_info[2].h_samp_factor = 1;
160   newinfo->cinfo.comp_info[2].v_samp_factor = 1;
161
162   newinfo->line[0] = malloc (DCTSIZE * 2 * sizeof (char *));
163   newinfo->line[1] = malloc (DCTSIZE * sizeof (char *));
164   newinfo->line[2] = malloc (DCTSIZE * sizeof (char *));
165   base[0] = newinfo->compbuf[0] = malloc (256 * 2 * DCTSIZE * 2 * DCTSIZE);
166   base[1] = newinfo->compbuf[1] = malloc (256 * DCTSIZE * DCTSIZE);
167   base[2] = newinfo->compbuf[2] = malloc (256 * DCTSIZE * DCTSIZE);
168
169   for (i = 0, j = 0; i < 2 * DCTSIZE; i += 2, j++) {
170     newinfo->line[0][i] = base[0];
171     base[0] += 2 * DCTSIZE * 256;
172     newinfo->line[0][i + 1] = base[0];
173     base[0] += 2 * DCTSIZE * 256;
174     newinfo->line[1][j] = base[1];
175     base[1] += DCTSIZE * 256;
176     newinfo->line[2][j] = base[2];
177     base[2] += DCTSIZE * 256;
178   }
179
180   newinfo->jdest.init_destination = smokecodec_init_destination;
181   newinfo->jdest.empty_output_buffer = smokecodec_flush_destination;
182   newinfo->jdest.term_destination = smokecodec_term_destination;
183   newinfo->cinfo.dest = &newinfo->jdest;
184
185   jpeg_suppress_tables (&newinfo->cinfo, FALSE);
186
187   memset (&newinfo->dinfo, 0, sizeof (newinfo->dinfo));
188   newinfo->dinfo.err = jpeg_std_error (&newinfo->jerr);
189   jpeg_create_decompress (&newinfo->dinfo);
190
191   newinfo->jsrc.init_source = smokecodec_init_source;
192   newinfo->jsrc.fill_input_buffer = smokecodec_fill_input_buffer;
193   newinfo->jsrc.skip_input_data = smokecodec_skip_input_data;
194   newinfo->jsrc.resync_to_restart = smokecodec_resync_to_restart;
195   newinfo->jsrc.term_source = smokecodec_term_source;
196   newinfo->dinfo.src = &newinfo->jsrc;
197
198   newinfo->need_keyframe = 1;
199   newinfo->threshold = 4000;
200   newinfo->minquality = 10;
201   newinfo->maxquality = 85;
202   newinfo->reference = malloc (3 * (width * height) / 2);
203   newinfo->refdec = 0;
204
205   *info = newinfo;
206
207   return SMOKECODEC_OK;
208 }
209
210 int
211 smokecodec_decode_new (SmokeCodecInfo ** info)
212 {
213   return smokecodec_encode_new (info, 16, 16, 1, 1);
214 }
215
216 int
217 smokecodec_info_free (SmokeCodecInfo * info)
218 {
219   free (info->line[0]);
220   free (info->line[1]);
221   free (info->line[2]);
222   free (info->compbuf[0]);
223   free (info->compbuf[1]);
224   free (info->compbuf[2]);
225   free (info->reference);
226   jpeg_destroy_compress (&info->cinfo);
227   jpeg_destroy_decompress (&info->dinfo);
228   free (info);
229
230   return SMOKECODEC_OK;
231 }
232
233 SmokeCodecResult
234 smokecodec_set_quality (SmokeCodecInfo * info,
235     const unsigned int min, const unsigned int max)
236 {
237   info->minquality = min;
238   info->maxquality = max;
239
240   return SMOKECODEC_OK;
241 }
242
243 SmokeCodecResult
244 smokecodec_get_quality (SmokeCodecInfo * info,
245     unsigned int *min, unsigned int *max)
246 {
247   *min = info->minquality;
248   *max = info->maxquality;
249
250   return SMOKECODEC_OK;
251 }
252
253 SmokeCodecResult
254 smokecodec_set_threshold (SmokeCodecInfo * info, const unsigned int threshold)
255 {
256   info->threshold = threshold;
257
258   return SMOKECODEC_OK;
259 }
260
261 SmokeCodecResult
262 smokecodec_get_threshold (SmokeCodecInfo * info, unsigned int *threshold)
263 {
264   *threshold = info->threshold;
265
266   return SMOKECODEC_OK;
267 }
268
269 SmokeCodecResult
270 smokecodec_set_bitrate (SmokeCodecInfo * info, const unsigned int bitrate)
271 {
272   info->bitrate = bitrate;
273
274   return SMOKECODEC_OK;
275 }
276
277 SmokeCodecResult
278 smokecodec_get_bitrate (SmokeCodecInfo * info, unsigned int *bitrate)
279 {
280   *bitrate = info->bitrate;
281
282   return SMOKECODEC_OK;
283 }
284
285 static void
286 find_best_size (int blocks, unsigned int *width, unsigned int *height)
287 {
288   int sqchng;
289   int w, h;
290   int best, bestw;
291   int free;
292
293   sqchng = ceil (sqrt (blocks));
294   w = sqchng;
295   h = sqchng;
296
297   GST_DEBUG ("guess: %d %d", w, h);
298
299   free = w * h - blocks;
300   best = free;
301   bestw = w;
302
303   while (w < 256) {
304     GST_DEBUG ("current: %d %d", w, h);
305     if (free < best) {
306       best = free;
307       bestw = w;
308       if (free == 0)
309         break;
310     }
311     // if we cannot reduce the height, increase width
312     if (free < w) {
313       w++;
314       free += h;
315     }
316     // reduce height while possible
317     while (free >= w) {
318       h--;
319       free -= w;
320     }
321   }
322   *width = bestw;
323   *height = (blocks + best) / bestw;
324 }
325
326 static int
327 abs_diff (const unsigned char *in1, const unsigned char *in2, const int stride)
328 {
329   int s;
330   int i, j, diff;
331
332   s = 0;
333
334   for (i = 0; i < 2 * DCTSIZE; i++) {
335     for (j = 0; j < 2 * DCTSIZE; j++) {
336       diff = in1[j] - in2[j];
337       s += diff * diff;
338     }
339     in1 += stride;
340     in2 += stride;
341   }
342   return s;
343 }
344
345 static void
346 put (const unsigned char *src, unsigned char *dest,
347     int width, int height, int srcstride, int deststride)
348 {
349   int i, j;
350
351   for (i = 0; i < height; i++) {
352     for (j = 0; j < width; j++) {
353       dest[j] = src[j];
354     }
355     src += srcstride;
356     dest += deststride;
357   }
358 }
359
360 /* encoding */
361 SmokeCodecResult
362 smokecodec_encode_id (SmokeCodecInfo * info,
363     unsigned char *out, unsigned int *outsize)
364 {
365   int i;
366
367   *out++ = SMOKECODEC_TYPE_ID;
368   for (i = 0; i < strlen (SMOKECODEC_ID_STRING); i++) {
369     *out++ = SMOKECODEC_ID_STRING[i];
370   }
371   *out++ = 0;
372   *out++ = 1;
373   *out++ = 0;
374
375   *outsize = 9;
376
377   return SMOKECODEC_OK;
378 }
379
380 SmokeCodecResult
381 smokecodec_encode (SmokeCodecInfo * info,
382     const unsigned char *in,
383     SmokeCodecFlags flags, unsigned char *out, unsigned int *outsize)
384 {
385   unsigned int i, j, s;
386   const unsigned char *ip;
387   unsigned char *op;
388   unsigned int blocks, encoding;
389   unsigned int size;
390   unsigned int width, height;
391   unsigned int blocks_w, blocks_h;
392   unsigned int threshold;
393   unsigned int max;
394
395   if (info->need_keyframe) {
396     flags |= SMOKECODEC_KEYFRAME;
397     info->need_keyframe = 0;
398   }
399
400   if (flags & SMOKECODEC_KEYFRAME)
401     threshold = 0;
402   else
403     threshold = info->threshold;
404
405   ip = in;
406   op = info->reference;
407
408   width = info->width;
409   height = info->height;
410
411   blocks_w = width / (DCTSIZE * 2);
412   blocks_h = height / (DCTSIZE * 2);
413
414   max = blocks_w * blocks_h;
415
416   out[IDX_TYPE] = SMOKECODEC_TYPE_DATA;
417
418 #define STORE16(var, pos, x) \
419    var[pos]   = (x >> 8); \
420    var[pos+1] = (x & 0xff);
421 #define STORE32(var, pos, x) \
422    var[pos]   = ((x >> 24) & 0xff); \
423    var[pos+1] = ((x >> 16) & 0xff); \
424    var[pos+2] = ((x >> 8) & 0xff); \
425    var[pos+3] =  (x & 0xff);
426
427   /* write dimension */
428   STORE16 (out, IDX_WIDTH, width);
429   STORE16 (out, IDX_HEIGHT, height);
430
431   /* write framerate */
432   STORE32 (out, IDX_FPS_NUM, info->fps_num);
433   STORE32 (out, IDX_FPS_DENOM, info->fps_denom);
434
435   if (!(flags & SMOKECODEC_KEYFRAME)) {
436     int block = 0;
437
438     blocks = 0;
439     for (i = 0; i < height; i += 2 * DCTSIZE) {
440       for (j = 0; j < width; j += 2 * DCTSIZE) {
441         s = abs_diff (ip, op, width);
442         if (s >= threshold) {
443           STORE16 (out, blocks * 2 + IDX_BLOCKS, block);
444           blocks++;
445         }
446
447         ip += 2 * DCTSIZE;
448         op += 2 * DCTSIZE;
449         block++;
450       }
451       ip += (2 * DCTSIZE - 1) * width;
452       op += (2 * DCTSIZE - 1) * width;
453     }
454     if (blocks == max) {
455       flags |= SMOKECODEC_KEYFRAME;
456       blocks = 0;
457       encoding = max;
458     } else {
459       encoding = blocks;
460     }
461   } else {
462     blocks = 0;
463     encoding = max;
464   }
465   STORE16 (out, IDX_NUM_BLOCKS, blocks);
466   out[IDX_FLAGS] = (flags & 0xff);
467
468   GST_DEBUG ("blocks %d, encoding %d", blocks, encoding);
469
470   info->jdest.next_output_byte = &out[blocks * 2 + OFFS_PICT];
471   info->jdest.free_in_buffer = (*outsize) - OFFS_PICT;
472
473   if (encoding > 0) {
474     int quality;
475
476     if (!(flags & SMOKECODEC_KEYFRAME))
477       find_best_size (encoding, &blocks_w, &blocks_h);
478
479     GST_DEBUG ("best: %d %d", blocks_w, blocks_h);
480
481     info->cinfo.image_width = blocks_w * DCTSIZE * 2;
482     info->cinfo.image_height = blocks_h * DCTSIZE * 2;
483
484     if (flags & SMOKECODEC_KEYFRAME) {
485       quality = (info->maxquality * 60) / 100;
486     } else {
487       quality =
488           info->maxquality - ((info->maxquality -
489               info->minquality) * blocks) / max;
490     }
491
492     GST_DEBUG ("set q %d %d %d", quality, encoding, max);
493     jpeg_set_quality (&info->cinfo, quality, TRUE);
494     GST_DEBUG ("start");
495     jpeg_start_compress (&info->cinfo, TRUE);
496
497     for (i = 0; i < encoding; i++) {
498       int pos;
499       int x, y;
500
501       if (flags & SMOKECODEC_KEYFRAME)
502         pos = i;
503       else
504         pos = (out[i * 2 + IDX_BLOCKS] << 8) | (out[i * 2 + IDX_BLOCKS + 1]);
505
506       x = pos % (width / (DCTSIZE * 2));
507       y = pos / (width / (DCTSIZE * 2));
508
509       ip = in + (x * (DCTSIZE * 2)) + (y * (DCTSIZE * 2) * width);
510       op = info->compbuf[0] + (i % blocks_w) * (DCTSIZE * 2);
511       put (ip, op, 2 * DCTSIZE, 2 * DCTSIZE, width, 256 * (DCTSIZE * 2));
512
513       ip = in + width * height + (x * DCTSIZE) + (y * DCTSIZE * width / 2);
514       op = info->compbuf[1] + (i % blocks_w) * (DCTSIZE);
515       put (ip, op, DCTSIZE, DCTSIZE, width / 2, 256 * DCTSIZE);
516
517       ip = in + 5 * (width * height) / 4 + (x * DCTSIZE) +
518           (y * DCTSIZE * width / 2);
519       op = info->compbuf[2] + (i % blocks_w) * (DCTSIZE);
520       put (ip, op, DCTSIZE, DCTSIZE, width / 2, 256 * DCTSIZE);
521
522       if ((i % blocks_w) == (blocks_w - 1) || (i == encoding - 1)) {
523         GST_DEBUG ("write %d", pos);
524         jpeg_write_raw_data (&info->cinfo, info->line, 2 * DCTSIZE);
525       }
526     }
527     GST_DEBUG ("finish");
528     jpeg_finish_compress (&info->cinfo);
529   }
530
531   size = ((((*outsize) - OFFS_PICT - info->jdest.free_in_buffer) + 3) & ~3);
532   STORE16 (out, IDX_SIZE, size);
533
534   *outsize = size + blocks * 2 + OFFS_PICT;
535   GST_DEBUG ("outsize %d", *outsize);
536
537   // and decode in reference frame again
538   if (info->refdec) {
539     smokecodec_decode (info, out, *outsize, info->reference);
540   } else {
541     memcpy (info->reference, in, 3 * (width * height) / 2);
542   }
543
544   return SMOKECODEC_OK;
545 }
546
547 SmokeCodecResult
548 smokecodec_parse_id (SmokeCodecInfo * info,
549     const unsigned char *in, const unsigned int insize)
550 {
551   int i;
552
553   if (insize < 4 + strlen (SMOKECODEC_ID_STRING)) {
554     return SMOKECODEC_WRONGVERSION;
555   }
556
557   if (*in++ != SMOKECODEC_TYPE_ID)
558     return SMOKECODEC_ERROR;
559
560   for (i = 0; i < strlen (SMOKECODEC_ID_STRING); i++) {
561     if (*in++ != SMOKECODEC_ID_STRING[i])
562       return SMOKECODEC_ERROR;
563   }
564   if (*in++ != 0 || *in++ != 1 || *in++ != 0)
565     return SMOKECODEC_ERROR;
566
567   return SMOKECODEC_OK;
568 }
569
570 #define READ16(var, pos, x) \
571  x = var[pos]<<8 | var[pos+1];
572
573 #define READ32(var, pos, x) \
574  x = var[pos]<<24 | var[pos+1]<<16 | \
575      var[pos+2]<<8 | var[pos+3];
576
577 /* decoding */
578 SmokeCodecResult
579 smokecodec_parse_header (SmokeCodecInfo * info,
580     const unsigned char *in,
581     const unsigned int insize,
582     SmokeCodecFlags * flags,
583     unsigned int *width,
584     unsigned int *height, unsigned int *fps_num, unsigned int *fps_denom)
585 {
586
587   READ16 (in, IDX_WIDTH, *width);
588   READ16 (in, IDX_HEIGHT, *height);
589   *flags = in[IDX_FLAGS];
590   READ32 (in, IDX_FPS_NUM, *fps_num);
591   READ32 (in, IDX_FPS_DENOM, *fps_denom);
592
593   if (info->width != *width ||
594       info->height != *height ||
595       info->fps_num != *fps_num || info->fps_denom != *fps_denom) {
596     GST_DEBUG ("new width: %d %d", *width, *height);
597
598     info->reference = realloc (info->reference, 3 * ((*width) * (*height)) / 2);
599     info->width = *width;
600     info->height = *height;
601     info->fps_num = *fps_num;
602     info->fps_denom = *fps_denom;
603   }
604
605   return SMOKECODEC_OK;
606 }
607
608 SmokeCodecResult
609 smokecodec_decode (SmokeCodecInfo * info,
610     const unsigned char *in, const unsigned int insize, unsigned char *out)
611 {
612   unsigned int width, height;
613   unsigned int fps_num, fps_denom;
614   SmokeCodecFlags flags;
615   int i, j;
616   int blocks_w, blocks_h;
617   int blockptr;
618   int blocks, decoding;
619   const unsigned char *ip;
620   unsigned char *op;
621   int res;
622
623   smokecodec_parse_header (info, in, insize, &flags, &width, &height,
624       &fps_num, &fps_denom);
625
626   READ16 (in, IDX_NUM_BLOCKS, blocks);
627   GST_DEBUG ("blocks %d", blocks);
628
629   if (flags & SMOKECODEC_KEYFRAME)
630     decoding = width / (DCTSIZE * 2) * height / (DCTSIZE * 2);
631   else
632     decoding = blocks;
633
634   if (decoding > 0) {
635     info->jsrc.next_input_byte = &in[blocks * 2 + OFFS_PICT];
636     info->jsrc.bytes_in_buffer = insize - (blocks * 2 + OFFS_PICT);
637
638     GST_DEBUG ("header %02x %d", in[blocks * 2 + OFFS_PICT], insize);
639     res = jpeg_read_header (&info->dinfo, TRUE);
640     GST_DEBUG ("header %d %d %d", res, info->dinfo.image_width,
641         info->dinfo.image_height);
642
643     blocks_w = info->dinfo.image_width / (2 * DCTSIZE);
644     blocks_h = info->dinfo.image_height / (2 * DCTSIZE);
645
646     info->dinfo.output_width = info->dinfo.image_width;
647     info->dinfo.output_height = info->dinfo.image_height;
648
649     GST_DEBUG ("start");
650     info->dinfo.do_fancy_upsampling = FALSE;
651     info->dinfo.do_block_smoothing = FALSE;
652     info->dinfo.out_color_space = JCS_YCbCr;
653     info->dinfo.dct_method = JDCT_IFAST;
654     info->dinfo.raw_data_out = TRUE;
655     jpeg_start_decompress (&info->dinfo);
656
657     blockptr = 0;
658
659     for (i = 0; i < blocks_h; i++) {
660       GST_DEBUG ("read");
661       jpeg_read_raw_data (&info->dinfo, info->line, 2 * DCTSIZE);
662
663       GST_DEBUG ("copy %d", blocks_w);
664       for (j = 0; j < blocks_w; j++) {
665         int pos;
666         int x, y;
667
668         if (flags & SMOKECODEC_KEYFRAME)
669           pos = blockptr;
670         else
671           READ16 (in, blockptr * 2 + IDX_BLOCKS, pos);
672
673         x = pos % (width / (DCTSIZE * 2));
674         y = pos / (width / (DCTSIZE * 2));
675
676         GST_DEBUG ("block %d %d %d", pos, x, y);
677
678         ip = info->compbuf[0] + j * (DCTSIZE * 2);
679         op = info->reference + (x * (DCTSIZE * 2)) +
680             (y * (DCTSIZE * 2) * width);
681         put (ip, op, 2 * DCTSIZE, 2 * DCTSIZE, 256 * (DCTSIZE * 2), width);
682
683         ip = info->compbuf[1] + j * (DCTSIZE);
684         op = info->reference + width * height + (x * DCTSIZE) +
685             (y * DCTSIZE * width / 2);
686         put (ip, op, DCTSIZE, DCTSIZE, 256 * DCTSIZE, width / 2);
687
688         ip = info->compbuf[2] + j * (DCTSIZE);
689         op = info->reference + 5 * (width * height) / 4 + (x * DCTSIZE) +
690             (y * DCTSIZE * width / 2);
691         put (ip, op, DCTSIZE, DCTSIZE, 256 * DCTSIZE, width / 2);
692
693         GST_DEBUG ("block done %d %d %d", pos, x, y);
694         blockptr++;
695         if (blockptr >= decoding)
696           break;
697       }
698     }
699     GST_DEBUG ("finish");
700     jpeg_finish_decompress (&info->dinfo);
701   }
702
703   GST_DEBUG ("copy");
704   if (out != info->reference)
705     memcpy (out, info->reference, 3 * (width * height) / 2);
706   GST_DEBUG ("copy done");
707
708   return SMOKECODEC_OK;
709 }