summaryrefslogtreecommitdiffstats
path: root/media/libyuv/source/mjpeg_decoder.cc
blob: 59d969c549a04279567a3d9d4d00fbeb07517a36 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
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
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
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
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
/*
 *  Copyright 2012 The LibYuv Project Authors. All rights reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS. All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#include "libyuv/mjpeg_decoder.h"

#ifdef HAVE_JPEG
#include <assert.h>

#if !defined(__pnacl__) && !defined(__CLR_VER) && \
    !defined(COVERAGE_ENABLED) && !defined(TARGET_IPHONE_SIMULATOR)
// Must be included before jpeglib.
#include <setjmp.h>
#define HAVE_SETJMP

#if defined(_MSC_VER)
// disable warning 4324: structure was padded due to __declspec(align())
#pragma warning(disable:4324)
#endif

#endif
struct FILE;  // For jpeglib.h.

// C++ build requires extern C for jpeg internals.
#ifdef __cplusplus
extern "C" {
#endif

#include <jpeglib.h>

#ifdef __cplusplus
}  // extern "C"
#endif

#include "libyuv/planar_functions.h"  // For CopyPlane().

namespace libyuv {

#ifdef HAVE_SETJMP
struct SetJmpErrorMgr {
  jpeg_error_mgr base;  // Must be at the top
  jmp_buf setjmp_buffer;
};
#endif

const int MJpegDecoder::kColorSpaceUnknown = JCS_UNKNOWN;
const int MJpegDecoder::kColorSpaceGrayscale = JCS_GRAYSCALE;
const int MJpegDecoder::kColorSpaceRgb = JCS_RGB;
const int MJpegDecoder::kColorSpaceYCbCr = JCS_YCbCr;
const int MJpegDecoder::kColorSpaceCMYK = JCS_CMYK;
const int MJpegDecoder::kColorSpaceYCCK = JCS_YCCK;

// Methods that are passed to jpeglib.
boolean fill_input_buffer(jpeg_decompress_struct* cinfo);
void init_source(jpeg_decompress_struct* cinfo);
void skip_input_data(jpeg_decompress_struct* cinfo, long num_bytes);  // NOLINT
void term_source(jpeg_decompress_struct* cinfo);
void ErrorHandler(jpeg_common_struct* cinfo);
void OutputHandler(jpeg_common_struct* cinfo);

MJpegDecoder::MJpegDecoder()
    : has_scanline_padding_(LIBYUV_FALSE),
      num_outbufs_(0),
      scanlines_(NULL),
      scanlines_sizes_(NULL),
      databuf_(NULL),
      databuf_strides_(NULL) {
  decompress_struct_ = new jpeg_decompress_struct;
  source_mgr_ = new jpeg_source_mgr;
#ifdef HAVE_SETJMP
  error_mgr_ = new SetJmpErrorMgr;
  decompress_struct_->err = jpeg_std_error(&error_mgr_->base);
  // Override standard exit()-based error handler.
  error_mgr_->base.error_exit = &ErrorHandler;
#ifndef DEBUG_MJPEG
  error_mgr_->base.output_message = &OutputHandler;
#endif
#endif
  decompress_struct_->client_data = NULL;
  source_mgr_->init_source = &init_source;
  source_mgr_->fill_input_buffer = &fill_input_buffer;
  source_mgr_->skip_input_data = &skip_input_data;
  source_mgr_->resync_to_restart = &jpeg_resync_to_restart;
  source_mgr_->term_source = &term_source;
  jpeg_create_decompress(decompress_struct_);
  decompress_struct_->src = source_mgr_;
  buf_vec_.buffers = &buf_;
  buf_vec_.len = 1;
}

MJpegDecoder::~MJpegDecoder() {
  jpeg_destroy_decompress(decompress_struct_);
  delete decompress_struct_;
  delete source_mgr_;
#ifdef HAVE_SETJMP
  delete error_mgr_;
#endif
  DestroyOutputBuffers();
}

LIBYUV_BOOL MJpegDecoder::LoadFrame(const uint8* src, size_t src_len) {
  if (!ValidateJpeg(src, src_len)) {
    return LIBYUV_FALSE;
  }

  buf_.data = src;
  buf_.len = static_cast<int>(src_len);
  buf_vec_.pos = 0;
  decompress_struct_->client_data = &buf_vec_;
#ifdef HAVE_SETJMP
  if (setjmp(error_mgr_->setjmp_buffer)) {
    // We called jpeg_read_header, it experienced an error, and we called
    // longjmp() and rewound the stack to here. Return error.
    return LIBYUV_FALSE;
  }
#endif
  if (jpeg_read_header(decompress_struct_, TRUE) != JPEG_HEADER_OK) {
    // ERROR: Bad MJPEG header
    return LIBYUV_FALSE;
  }
  AllocOutputBuffers(GetNumComponents());
  for (int i = 0; i < num_outbufs_; ++i) {
    int scanlines_size = GetComponentScanlinesPerImcuRow(i);
    if (scanlines_sizes_[i] != scanlines_size) {
      if (scanlines_[i]) {
        delete scanlines_[i];
      }
      scanlines_[i] = new uint8* [scanlines_size];
      scanlines_sizes_[i] = scanlines_size;
    }

    // We allocate padding for the final scanline to pad it up to DCTSIZE bytes
    // to avoid memory errors, since jpeglib only reads full MCUs blocks. For
    // the preceding scanlines, the padding is not needed/wanted because the
    // following addresses will already be valid (they are the initial bytes of
    // the next scanline) and will be overwritten when jpeglib writes out that
    // next scanline.
    int databuf_stride = GetComponentStride(i);
    int databuf_size = scanlines_size * databuf_stride;
    if (databuf_strides_[i] != databuf_stride) {
      if (databuf_[i]) {
        delete databuf_[i];
      }
      databuf_[i] = new uint8[databuf_size];
      databuf_strides_[i] = databuf_stride;
    }

    if (GetComponentStride(i) != GetComponentWidth(i)) {
      has_scanline_padding_ = LIBYUV_TRUE;
    }
  }
  return LIBYUV_TRUE;
}

static int DivideAndRoundUp(int numerator, int denominator) {
  return (numerator + denominator - 1) / denominator;
}

static int DivideAndRoundDown(int numerator, int denominator) {
  return numerator / denominator;
}

// Returns width of the last loaded frame.
int MJpegDecoder::GetWidth() {
  return decompress_struct_->image_width;
}

// Returns height of the last loaded frame.
int MJpegDecoder::GetHeight() {
  return decompress_struct_->image_height;
}

// Returns format of the last loaded frame. The return value is one of the
// kColorSpace* constants.
int MJpegDecoder::GetColorSpace() {
  return decompress_struct_->jpeg_color_space;
}

// Number of color components in the color space.
int MJpegDecoder::GetNumComponents() {
  return decompress_struct_->num_components;
}

// Sample factors of the n-th component.
int MJpegDecoder::GetHorizSampFactor(int component) {
  return decompress_struct_->comp_info[component].h_samp_factor;
}

int MJpegDecoder::GetVertSampFactor(int component) {
  return decompress_struct_->comp_info[component].v_samp_factor;
}

int MJpegDecoder::GetHorizSubSampFactor(int component) {
  return decompress_struct_->max_h_samp_factor /
      GetHorizSampFactor(component);
}

int MJpegDecoder::GetVertSubSampFactor(int component) {
  return decompress_struct_->max_v_samp_factor /
      GetVertSampFactor(component);
}

int MJpegDecoder::GetImageScanlinesPerImcuRow() {
  return decompress_struct_->max_v_samp_factor * DCTSIZE;
}

int MJpegDecoder::GetComponentScanlinesPerImcuRow(int component) {
  int vs = GetVertSubSampFactor(component);
  return DivideAndRoundUp(GetImageScanlinesPerImcuRow(), vs);
}

int MJpegDecoder::GetComponentWidth(int component) {
  int hs = GetHorizSubSampFactor(component);
  return DivideAndRoundUp(GetWidth(), hs);
}

int MJpegDecoder::GetComponentHeight(int component) {
  int vs = GetVertSubSampFactor(component);
  return DivideAndRoundUp(GetHeight(), vs);
}

// Get width in bytes padded out to a multiple of DCTSIZE
int MJpegDecoder::GetComponentStride(int component) {
  return (GetComponentWidth(component) + DCTSIZE - 1) & ~(DCTSIZE - 1);
}

int MJpegDecoder::GetComponentSize(int component) {
  return GetComponentWidth(component) * GetComponentHeight(component);
}

LIBYUV_BOOL MJpegDecoder::UnloadFrame() {
#ifdef HAVE_SETJMP
  if (setjmp(error_mgr_->setjmp_buffer)) {
    // We called jpeg_abort_decompress, it experienced an error, and we called
    // longjmp() and rewound the stack to here. Return error.
    return LIBYUV_FALSE;
  }
#endif
  jpeg_abort_decompress(decompress_struct_);
  return LIBYUV_TRUE;
}

// TODO(fbarchard): Allow rectangle to be specified: x, y, width, height.
LIBYUV_BOOL MJpegDecoder::DecodeToBuffers(
    uint8** planes, int dst_width, int dst_height) {
  if (dst_width != GetWidth() ||
      dst_height > GetHeight()) {
    // ERROR: Bad dimensions
    return LIBYUV_FALSE;
  }
#ifdef HAVE_SETJMP
  if (setjmp(error_mgr_->setjmp_buffer)) {
    // We called into jpeglib, it experienced an error sometime during this
    // function call, and we called longjmp() and rewound the stack to here.
    // Return error.
    return LIBYUV_FALSE;
  }
#endif
  if (!StartDecode()) {
    return LIBYUV_FALSE;
  }
  SetScanlinePointers(databuf_);
  int lines_left = dst_height;
  // Compute amount of lines to skip to implement vertical crop.
  // TODO(fbarchard): Ensure skip is a multiple of maximum component
  // subsample. ie 2
  int skip = (GetHeight() - dst_height) / 2;
  if (skip > 0) {
    // There is no API to skip lines in the output data, so we read them
    // into the temp buffer.
    while (skip >= GetImageScanlinesPerImcuRow()) {
      if (!DecodeImcuRow()) {
        FinishDecode();
        return LIBYUV_FALSE;
      }
      skip -= GetImageScanlinesPerImcuRow();
    }
    if (skip > 0) {
      // Have a partial iMCU row left over to skip. Must read it and then
      // copy the parts we want into the destination.
      if (!DecodeImcuRow()) {
        FinishDecode();
        return LIBYUV_FALSE;
      }
      for (int i = 0; i < num_outbufs_; ++i) {
        // TODO(fbarchard): Compute skip to avoid this
        assert(skip % GetVertSubSampFactor(i) == 0);
        int rows_to_skip =
            DivideAndRoundDown(skip, GetVertSubSampFactor(i));
        int scanlines_to_copy = GetComponentScanlinesPerImcuRow(i) -
                                rows_to_skip;
        int data_to_skip = rows_to_skip * GetComponentStride(i);
        CopyPlane(databuf_[i] + data_to_skip, GetComponentStride(i),
                  planes[i], GetComponentWidth(i),
                  GetComponentWidth(i), scanlines_to_copy);
        planes[i] += scanlines_to_copy * GetComponentWidth(i);
      }
      lines_left -= (GetImageScanlinesPerImcuRow() - skip);
    }
  }

  // Read full MCUs but cropped horizontally
  for (; lines_left > GetImageScanlinesPerImcuRow();
         lines_left -= GetImageScanlinesPerImcuRow()) {
    if (!DecodeImcuRow()) {
      FinishDecode();
      return LIBYUV_FALSE;
    }
    for (int i = 0; i < num_outbufs_; ++i) {
      int scanlines_to_copy = GetComponentScanlinesPerImcuRow(i);
      CopyPlane(databuf_[i], GetComponentStride(i),
                planes[i], GetComponentWidth(i),
                GetComponentWidth(i), scanlines_to_copy);
      planes[i] += scanlines_to_copy * GetComponentWidth(i);
    }
  }

  if (lines_left > 0) {
    // Have a partial iMCU row left over to decode.
    if (!DecodeImcuRow()) {
      FinishDecode();
      return LIBYUV_FALSE;
    }
    for (int i = 0; i < num_outbufs_; ++i) {
      int scanlines_to_copy =
          DivideAndRoundUp(lines_left, GetVertSubSampFactor(i));
      CopyPlane(databuf_[i], GetComponentStride(i),
                planes[i], GetComponentWidth(i),
                GetComponentWidth(i), scanlines_to_copy);
      planes[i] += scanlines_to_copy * GetComponentWidth(i);
    }
  }
  return FinishDecode();
}

LIBYUV_BOOL MJpegDecoder::DecodeToCallback(CallbackFunction fn, void* opaque,
    int dst_width, int dst_height) {
  if (dst_width != GetWidth() ||
      dst_height > GetHeight()) {
    // ERROR: Bad dimensions
    return LIBYUV_FALSE;
  }
#ifdef HAVE_SETJMP
  if (setjmp(error_mgr_->setjmp_buffer)) {
    // We called into jpeglib, it experienced an error sometime during this
    // function call, and we called longjmp() and rewound the stack to here.
    // Return error.
    return LIBYUV_FALSE;
  }
#endif
  if (!StartDecode()) {
    return LIBYUV_FALSE;
  }
  SetScanlinePointers(databuf_);
  int lines_left = dst_height;
  // TODO(fbarchard): Compute amount of lines to skip to implement vertical crop
  int skip = (GetHeight() - dst_height) / 2;
  if (skip > 0) {
    while (skip >= GetImageScanlinesPerImcuRow()) {
      if (!DecodeImcuRow()) {
        FinishDecode();
        return LIBYUV_FALSE;
      }
      skip -= GetImageScanlinesPerImcuRow();
    }
    if (skip > 0) {
      // Have a partial iMCU row left over to skip.
      if (!DecodeImcuRow()) {
        FinishDecode();
        return LIBYUV_FALSE;
      }
      for (int i = 0; i < num_outbufs_; ++i) {
        // TODO(fbarchard): Compute skip to avoid this
        assert(skip % GetVertSubSampFactor(i) == 0);
        int rows_to_skip = DivideAndRoundDown(skip, GetVertSubSampFactor(i));
        int data_to_skip = rows_to_skip * GetComponentStride(i);
        // Change our own data buffer pointers so we can pass them to the
        // callback.
        databuf_[i] += data_to_skip;
      }
      int scanlines_to_copy = GetImageScanlinesPerImcuRow() - skip;
      (*fn)(opaque, databuf_, databuf_strides_, scanlines_to_copy);
      // Now change them back.
      for (int i = 0; i < num_outbufs_; ++i) {
        int rows_to_skip = DivideAndRoundDown(skip, GetVertSubSampFactor(i));
        int data_to_skip = rows_to_skip * GetComponentStride(i);
        databuf_[i] -= data_to_skip;
      }
      lines_left -= scanlines_to_copy;
    }
  }
  // Read full MCUs until we get to the crop point.
  for (; lines_left >= GetImageScanlinesPerImcuRow();
         lines_left -= GetImageScanlinesPerImcuRow()) {
    if (!DecodeImcuRow()) {
      FinishDecode();
      return LIBYUV_FALSE;
    }
    (*fn)(opaque, databuf_, databuf_strides_, GetImageScanlinesPerImcuRow());
  }
  if (lines_left > 0) {
    // Have a partial iMCU row left over to decode.
    if (!DecodeImcuRow()) {
      FinishDecode();
      return LIBYUV_FALSE;
    }
    (*fn)(opaque, databuf_, databuf_strides_, lines_left);
  }
  return FinishDecode();
}

void init_source(j_decompress_ptr cinfo) {
  fill_input_buffer(cinfo);
}

boolean fill_input_buffer(j_decompress_ptr cinfo) {
  BufferVector* buf_vec = reinterpret_cast<BufferVector*>(cinfo->client_data);
  if (buf_vec->pos >= buf_vec->len) {
    assert(0 && "No more data");
    // ERROR: No more data
    return FALSE;
  }
  cinfo->src->next_input_byte = buf_vec->buffers[buf_vec->pos].data;
  cinfo->src->bytes_in_buffer = buf_vec->buffers[buf_vec->pos].len;
  ++buf_vec->pos;
  return TRUE;
}

void skip_input_data(j_decompress_ptr cinfo, long num_bytes) {  // NOLINT
  cinfo->src->next_input_byte += num_bytes;
}

void term_source(j_decompress_ptr cinfo) {
  // Nothing to do.
}

#ifdef HAVE_SETJMP
void ErrorHandler(j_common_ptr cinfo) {
  // This is called when a jpeglib command experiences an error. Unfortunately
  // jpeglib's error handling model is not very flexible, because it expects the
  // error handler to not return--i.e., it wants the program to terminate. To
  // recover from errors we use setjmp() as shown in their example. setjmp() is
  // C's implementation for the "call with current continuation" functionality
  // seen in some functional programming languages.
  // A formatted message can be output, but is unsafe for release.
#ifdef DEBUG
  char buf[JMSG_LENGTH_MAX];
  (*cinfo->err->format_message)(cinfo, buf);
  // ERROR: Error in jpeglib: buf
#endif

  SetJmpErrorMgr* mgr = reinterpret_cast<SetJmpErrorMgr*>(cinfo->err);
  // This rewinds the call stack to the point of the corresponding setjmp()
  // and causes it to return (for a second time) with value 1.
  longjmp(mgr->setjmp_buffer, 1);
}

#ifndef DEBUG_MJPEG
void OutputHandler(j_common_ptr cinfo) {
  // silently eat messages
}
#endif
#endif // HAVE_SETJMP

void MJpegDecoder::AllocOutputBuffers(int num_outbufs) {
  if (num_outbufs != num_outbufs_) {
    // We could perhaps optimize this case to resize the output buffers without
    // necessarily having to delete and recreate each one, but it's not worth
    // it.
    DestroyOutputBuffers();

    scanlines_ = new uint8** [num_outbufs];
    scanlines_sizes_ = new int[num_outbufs];
    databuf_ = new uint8* [num_outbufs];
    databuf_strides_ = new int[num_outbufs];

    for (int i = 0; i < num_outbufs; ++i) {
      scanlines_[i] = NULL;
      scanlines_sizes_[i] = 0;
      databuf_[i] = NULL;
      databuf_strides_[i] = 0;
    }

    num_outbufs_ = num_outbufs;
  }
}

void MJpegDecoder::DestroyOutputBuffers() {
  for (int i = 0; i < num_outbufs_; ++i) {
    delete [] scanlines_[i];
    delete [] databuf_[i];
  }
  delete [] scanlines_;
  delete [] databuf_;
  delete [] scanlines_sizes_;
  delete [] databuf_strides_;
  scanlines_ = NULL;
  databuf_ = NULL;
  scanlines_sizes_ = NULL;
  databuf_strides_ = NULL;
  num_outbufs_ = 0;
}

// JDCT_IFAST and do_block_smoothing improve performance substantially.
LIBYUV_BOOL MJpegDecoder::StartDecode() {
  decompress_struct_->raw_data_out = TRUE;
  decompress_struct_->dct_method = JDCT_IFAST;  // JDCT_ISLOW is default
  decompress_struct_->dither_mode = JDITHER_NONE;
  // Not applicable to 'raw':
  decompress_struct_->do_fancy_upsampling = (boolean)(LIBYUV_FALSE);
  // Only for buffered mode:
  decompress_struct_->enable_2pass_quant = (boolean)(LIBYUV_FALSE);
  // Blocky but fast:
  decompress_struct_->do_block_smoothing = (boolean)(LIBYUV_FALSE);

  if (!jpeg_start_decompress(decompress_struct_)) {
    // ERROR: Couldn't start JPEG decompressor";
    return LIBYUV_FALSE;
  }
  return LIBYUV_TRUE;
}

LIBYUV_BOOL MJpegDecoder::FinishDecode() {
  // jpeglib considers it an error if we finish without decoding the whole
  // image, so we call "abort" rather than "finish".
  jpeg_abort_decompress(decompress_struct_);
  return LIBYUV_TRUE;
}

void MJpegDecoder::SetScanlinePointers(uint8** data) {
  for (int i = 0; i < num_outbufs_; ++i) {
    uint8* data_i = data[i];
    for (int j = 0; j < scanlines_sizes_[i]; ++j) {
      scanlines_[i][j] = data_i;
      data_i += GetComponentStride(i);
    }
  }
}

inline LIBYUV_BOOL MJpegDecoder::DecodeImcuRow() {
  return (unsigned int)(GetImageScanlinesPerImcuRow()) ==
      jpeg_read_raw_data(decompress_struct_,
                         scanlines_,
                         GetImageScanlinesPerImcuRow());
}

// The helper function which recognizes the jpeg sub-sampling type.
JpegSubsamplingType MJpegDecoder::JpegSubsamplingTypeHelper(
    int* subsample_x, int* subsample_y, int number_of_components) {
  if (number_of_components == 3) {  // Color images.
    if (subsample_x[0] == 1 && subsample_y[0] == 1 &&
        subsample_x[1] == 2 && subsample_y[1] == 2 &&
        subsample_x[2] == 2 && subsample_y[2] == 2) {
      return kJpegYuv420;
    } else if (subsample_x[0] == 1 && subsample_y[0] == 1 &&
        subsample_x[1] == 2 && subsample_y[1] == 1 &&
        subsample_x[2] == 2 && subsample_y[2] == 1) {
      return kJpegYuv422;
    } else if (subsample_x[0] == 1 && subsample_y[0] == 1 &&
        subsample_x[1] == 1 && subsample_y[1] == 1 &&
        subsample_x[2] == 1 && subsample_y[2] == 1) {
      return kJpegYuv444;
    }
  } else if (number_of_components == 1) {  // Grey-scale images.
    if (subsample_x[0] == 1 && subsample_y[0] == 1) {
      return kJpegYuv400;
    }
  }
  return kJpegUnknown;
}

}  // namespace libyuv
#endif  // HAVE_JPEG