1 /*
2  *  Copyright 2012 The LibYuv Project Authors. All rights reserved.
3  *
4  *  Use of this source code is governed by a BSD-style license
5  *  that can be found in the LICENSE file in the root of the source
6  *  tree. An additional intellectual property rights grant can be found
7  *  in the file PATENTS. All contributing project authors may
8  *  be found in the AUTHORS file in the root of the source tree.
9  */
10 
11 #include "libyuv/mjpeg_decoder.h"
12 
13 #ifdef HAVE_JPEG
14 #include <assert.h>
15 
16 #if !defined(__pnacl__) && !defined(__CLR_VER) && \
17     !defined(COVERAGE_ENABLED) && !defined(TARGET_IPHONE_SIMULATOR)
18 // Must be included before jpeglib.
19 #include <setjmp.h>
20 #define HAVE_SETJMP
21 
22 #if defined(_MSC_VER)
23 // disable warning 4324: structure was padded due to __declspec(align())
24 #pragma warning(disable:4324)
25 #endif
26 
27 #endif
28 struct FILE;  // For jpeglib.h.
29 
30 // C++ build requires extern C for jpeg internals.
31 #ifdef __cplusplus
32 extern "C" {
33 #endif
34 
35 #include <jpeglib.h>
36 
37 #ifdef __cplusplus
38 }  // extern "C"
39 #endif
40 
41 #include "libyuv/planar_functions.h"  // For CopyPlane().
42 
43 namespace libyuv {
44 
45 #ifdef HAVE_SETJMP
46 struct SetJmpErrorMgr {
47   jpeg_error_mgr base;  // Must be at the top
48   jmp_buf setjmp_buffer;
49 };
50 #endif
51 
52 const int MJpegDecoder::kColorSpaceUnknown = JCS_UNKNOWN;
53 const int MJpegDecoder::kColorSpaceGrayscale = JCS_GRAYSCALE;
54 const int MJpegDecoder::kColorSpaceRgb = JCS_RGB;
55 const int MJpegDecoder::kColorSpaceYCbCr = JCS_YCbCr;
56 const int MJpegDecoder::kColorSpaceCMYK = JCS_CMYK;
57 const int MJpegDecoder::kColorSpaceYCCK = JCS_YCCK;
58 
59 // Methods that are passed to jpeglib.
60 boolean fill_input_buffer(jpeg_decompress_struct* cinfo);
61 void init_source(jpeg_decompress_struct* cinfo);
62 void skip_input_data(jpeg_decompress_struct* cinfo,
63                      long num_bytes);  // NOLINT
64 void term_source(jpeg_decompress_struct* cinfo);
65 void ErrorHandler(jpeg_common_struct* cinfo);
66 
MJpegDecoder()67 MJpegDecoder::MJpegDecoder()
68     : has_scanline_padding_(LIBYUV_FALSE),
69       num_outbufs_(0),
70       scanlines_(NULL),
71       scanlines_sizes_(NULL),
72       databuf_(NULL),
73       databuf_strides_(NULL) {
74   decompress_struct_ = new jpeg_decompress_struct;
75   source_mgr_ = new jpeg_source_mgr;
76 #ifdef HAVE_SETJMP
77   error_mgr_ = new SetJmpErrorMgr;
78   decompress_struct_->err = jpeg_std_error(&error_mgr_->base);
79   // Override standard exit()-based error handler.
80   error_mgr_->base.error_exit = &ErrorHandler;
81 #endif
82   decompress_struct_->client_data = NULL;
83   source_mgr_->init_source = &init_source;
84   source_mgr_->fill_input_buffer = &fill_input_buffer;
85   source_mgr_->skip_input_data = &skip_input_data;
86   source_mgr_->resync_to_restart = &jpeg_resync_to_restart;
87   source_mgr_->term_source = &term_source;
88   jpeg_create_decompress(decompress_struct_);
89   decompress_struct_->src = source_mgr_;
90   buf_vec_.buffers = &buf_;
91   buf_vec_.len = 1;
92 }
93 
~MJpegDecoder()94 MJpegDecoder::~MJpegDecoder() {
95   jpeg_destroy_decompress(decompress_struct_);
96   delete decompress_struct_;
97   delete source_mgr_;
98 #ifdef HAVE_SETJMP
99   delete error_mgr_;
100 #endif
101   DestroyOutputBuffers();
102 }
103 
LoadFrame(const uint8 * src,size_t src_len)104 LIBYUV_BOOL MJpegDecoder::LoadFrame(const uint8* src, size_t src_len) {
105   if (!ValidateJpeg(src, src_len)) {
106     return LIBYUV_FALSE;
107   }
108 
109   buf_.data = src;
110   buf_.len = static_cast<int>(src_len);
111   buf_vec_.pos = 0;
112   decompress_struct_->client_data = &buf_vec_;
113 #ifdef HAVE_SETJMP
114   if (setjmp(error_mgr_->setjmp_buffer)) {
115     // We called jpeg_read_header, it experienced an error, and we called
116     // longjmp() and rewound the stack to here. Return error.
117     return LIBYUV_FALSE;
118   }
119 #endif
120   if (jpeg_read_header(decompress_struct_, TRUE) != JPEG_HEADER_OK) {
121     // ERROR: Bad MJPEG header
122     return LIBYUV_FALSE;
123   }
124   AllocOutputBuffers(GetNumComponents());
125   for (int i = 0; i < num_outbufs_; ++i) {
126     int scanlines_size = GetComponentScanlinesPerImcuRow(i);
127     if (scanlines_sizes_[i] != scanlines_size) {
128       if (scanlines_[i]) {
129         delete scanlines_[i];
130       }
131       scanlines_[i] = new uint8* [scanlines_size];
132       scanlines_sizes_[i] = scanlines_size;
133     }
134 
135     // We allocate padding for the final scanline to pad it up to DCTSIZE bytes
136     // to avoid memory errors, since jpeglib only reads full MCUs blocks. For
137     // the preceding scanlines, the padding is not needed/wanted because the
138     // following addresses will already be valid (they are the initial bytes of
139     // the next scanline) and will be overwritten when jpeglib writes out that
140     // next scanline.
141     int databuf_stride = GetComponentStride(i);
142     int databuf_size = scanlines_size * databuf_stride;
143     if (databuf_strides_[i] != databuf_stride) {
144       if (databuf_[i]) {
145         delete databuf_[i];
146       }
147       databuf_[i] = new uint8[databuf_size];
148       databuf_strides_[i] = databuf_stride;
149     }
150 
151     if (GetComponentStride(i) != GetComponentWidth(i)) {
152       has_scanline_padding_ = LIBYUV_TRUE;
153     }
154   }
155   return LIBYUV_TRUE;
156 }
157 
DivideAndRoundUp(int numerator,int denominator)158 static int DivideAndRoundUp(int numerator, int denominator) {
159   return (numerator + denominator - 1) / denominator;
160 }
161 
DivideAndRoundDown(int numerator,int denominator)162 static int DivideAndRoundDown(int numerator, int denominator) {
163   return numerator / denominator;
164 }
165 
166 // Returns width of the last loaded frame.
GetWidth()167 int MJpegDecoder::GetWidth() {
168   return decompress_struct_->image_width;
169 }
170 
171 // Returns height of the last loaded frame.
GetHeight()172 int MJpegDecoder::GetHeight() {
173   return decompress_struct_->image_height;
174 }
175 
176 // Returns format of the last loaded frame. The return value is one of the
177 // kColorSpace* constants.
GetColorSpace()178 int MJpegDecoder::GetColorSpace() {
179   return decompress_struct_->jpeg_color_space;
180 }
181 
182 // Number of color components in the color space.
GetNumComponents()183 int MJpegDecoder::GetNumComponents() {
184   return decompress_struct_->num_components;
185 }
186 
187 // Sample factors of the n-th component.
GetHorizSampFactor(int component)188 int MJpegDecoder::GetHorizSampFactor(int component) {
189   return decompress_struct_->comp_info[component].h_samp_factor;
190 }
191 
GetVertSampFactor(int component)192 int MJpegDecoder::GetVertSampFactor(int component) {
193   return decompress_struct_->comp_info[component].v_samp_factor;
194 }
195 
GetHorizSubSampFactor(int component)196 int MJpegDecoder::GetHorizSubSampFactor(int component) {
197   return decompress_struct_->max_h_samp_factor /
198       GetHorizSampFactor(component);
199 }
200 
GetVertSubSampFactor(int component)201 int MJpegDecoder::GetVertSubSampFactor(int component) {
202   return decompress_struct_->max_v_samp_factor /
203       GetVertSampFactor(component);
204 }
205 
GetImageScanlinesPerImcuRow()206 int MJpegDecoder::GetImageScanlinesPerImcuRow() {
207   return decompress_struct_->max_v_samp_factor * DCTSIZE;
208 }
209 
GetComponentScanlinesPerImcuRow(int component)210 int MJpegDecoder::GetComponentScanlinesPerImcuRow(int component) {
211   int vs = GetVertSubSampFactor(component);
212   return DivideAndRoundUp(GetImageScanlinesPerImcuRow(), vs);
213 }
214 
GetComponentWidth(int component)215 int MJpegDecoder::GetComponentWidth(int component) {
216   int hs = GetHorizSubSampFactor(component);
217   return DivideAndRoundUp(GetWidth(), hs);
218 }
219 
GetComponentHeight(int component)220 int MJpegDecoder::GetComponentHeight(int component) {
221   int vs = GetVertSubSampFactor(component);
222   return DivideAndRoundUp(GetHeight(), vs);
223 }
224 
225 // Get width in bytes padded out to a multiple of DCTSIZE
GetComponentStride(int component)226 int MJpegDecoder::GetComponentStride(int component) {
227   return (GetComponentWidth(component) + DCTSIZE - 1) & ~(DCTSIZE - 1);
228 }
229 
GetComponentSize(int component)230 int MJpegDecoder::GetComponentSize(int component) {
231   return GetComponentWidth(component) * GetComponentHeight(component);
232 }
233 
UnloadFrame()234 LIBYUV_BOOL MJpegDecoder::UnloadFrame() {
235 #ifdef HAVE_SETJMP
236   if (setjmp(error_mgr_->setjmp_buffer)) {
237     // We called jpeg_abort_decompress, it experienced an error, and we called
238     // longjmp() and rewound the stack to here. Return error.
239     return LIBYUV_FALSE;
240   }
241 #endif
242   jpeg_abort_decompress(decompress_struct_);
243   return LIBYUV_TRUE;
244 }
245 
246 // TODO(fbarchard): Allow rectangle to be specified: x, y, width, height.
DecodeToBuffers(uint8 ** planes,int dst_width,int dst_height)247 LIBYUV_BOOL MJpegDecoder::DecodeToBuffers(
248     uint8** planes, int dst_width, int dst_height) {
249   if (dst_width != GetWidth() ||
250       dst_height > GetHeight()) {
251     // ERROR: Bad dimensions
252     return LIBYUV_FALSE;
253   }
254 #ifdef HAVE_SETJMP
255   if (setjmp(error_mgr_->setjmp_buffer)) {
256     // We called into jpeglib, it experienced an error sometime during this
257     // function call, and we called longjmp() and rewound the stack to here.
258     // Return error.
259     return LIBYUV_FALSE;
260   }
261 #endif
262   if (!StartDecode()) {
263     return LIBYUV_FALSE;
264   }
265   SetScanlinePointers(databuf_);
266   int lines_left = dst_height;
267   // Compute amount of lines to skip to implement vertical crop.
268   // TODO(fbarchard): Ensure skip is a multiple of maximum component
269   // subsample. ie 2
270   int skip = (GetHeight() - dst_height) / 2;
271   if (skip > 0) {
272     // There is no API to skip lines in the output data, so we read them
273     // into the temp buffer.
274     while (skip >= GetImageScanlinesPerImcuRow()) {
275       if (!DecodeImcuRow()) {
276         FinishDecode();
277         return LIBYUV_FALSE;
278       }
279       skip -= GetImageScanlinesPerImcuRow();
280     }
281     if (skip > 0) {
282       // Have a partial iMCU row left over to skip. Must read it and then
283       // copy the parts we want into the destination.
284       if (!DecodeImcuRow()) {
285         FinishDecode();
286         return LIBYUV_FALSE;
287       }
288       for (int i = 0; i < num_outbufs_; ++i) {
289         // TODO(fbarchard): Compute skip to avoid this
290         assert(skip % GetVertSubSampFactor(i) == 0);
291         int rows_to_skip =
292             DivideAndRoundDown(skip, GetVertSubSampFactor(i));
293         int scanlines_to_copy = GetComponentScanlinesPerImcuRow(i) -
294                                 rows_to_skip;
295         int data_to_skip = rows_to_skip * GetComponentStride(i);
296         CopyPlane(databuf_[i] + data_to_skip, GetComponentStride(i),
297                   planes[i], GetComponentWidth(i),
298                   GetComponentWidth(i), scanlines_to_copy);
299         planes[i] += scanlines_to_copy * GetComponentWidth(i);
300       }
301       lines_left -= (GetImageScanlinesPerImcuRow() - skip);
302     }
303   }
304 
305   // Read full MCUs but cropped horizontally
306   for (; lines_left > GetImageScanlinesPerImcuRow();
307          lines_left -= GetImageScanlinesPerImcuRow()) {
308     if (!DecodeImcuRow()) {
309       FinishDecode();
310       return LIBYUV_FALSE;
311     }
312     for (int i = 0; i < num_outbufs_; ++i) {
313       int scanlines_to_copy = GetComponentScanlinesPerImcuRow(i);
314       CopyPlane(databuf_[i], GetComponentStride(i),
315                 planes[i], GetComponentWidth(i),
316                 GetComponentWidth(i), scanlines_to_copy);
317       planes[i] += scanlines_to_copy * GetComponentWidth(i);
318     }
319   }
320 
321   if (lines_left > 0) {
322     // Have a partial iMCU row left over to decode.
323     if (!DecodeImcuRow()) {
324       FinishDecode();
325       return LIBYUV_FALSE;
326     }
327     for (int i = 0; i < num_outbufs_; ++i) {
328       int scanlines_to_copy =
329           DivideAndRoundUp(lines_left, GetVertSubSampFactor(i));
330       CopyPlane(databuf_[i], GetComponentStride(i),
331                 planes[i], GetComponentWidth(i),
332                 GetComponentWidth(i), scanlines_to_copy);
333       planes[i] += scanlines_to_copy * GetComponentWidth(i);
334     }
335   }
336   return FinishDecode();
337 }
338 
DecodeToCallback(CallbackFunction fn,void * opaque,int dst_width,int dst_height)339 LIBYUV_BOOL MJpegDecoder::DecodeToCallback(CallbackFunction fn, void* opaque,
340     int dst_width, int dst_height) {
341   if (dst_width != GetWidth() ||
342       dst_height > GetHeight()) {
343     // ERROR: Bad dimensions
344     return LIBYUV_FALSE;
345   }
346 #ifdef HAVE_SETJMP
347   if (setjmp(error_mgr_->setjmp_buffer)) {
348     // We called into jpeglib, it experienced an error sometime during this
349     // function call, and we called longjmp() and rewound the stack to here.
350     // Return error.
351     return LIBYUV_FALSE;
352   }
353 #endif
354   if (!StartDecode()) {
355     return LIBYUV_FALSE;
356   }
357   SetScanlinePointers(databuf_);
358   int lines_left = dst_height;
359   // TODO(fbarchard): Compute amount of lines to skip to implement vertical crop
360   int skip = (GetHeight() - dst_height) / 2;
361   if (skip > 0) {
362     while (skip >= GetImageScanlinesPerImcuRow()) {
363       if (!DecodeImcuRow()) {
364         FinishDecode();
365         return LIBYUV_FALSE;
366       }
367       skip -= GetImageScanlinesPerImcuRow();
368     }
369     if (skip > 0) {
370       // Have a partial iMCU row left over to skip.
371       if (!DecodeImcuRow()) {
372         FinishDecode();
373         return LIBYUV_FALSE;
374       }
375       for (int i = 0; i < num_outbufs_; ++i) {
376         // TODO(fbarchard): Compute skip to avoid this
377         assert(skip % GetVertSubSampFactor(i) == 0);
378         int rows_to_skip = DivideAndRoundDown(skip, GetVertSubSampFactor(i));
379         int data_to_skip = rows_to_skip * GetComponentStride(i);
380         // Change our own data buffer pointers so we can pass them to the
381         // callback.
382         databuf_[i] += data_to_skip;
383       }
384       int scanlines_to_copy = GetImageScanlinesPerImcuRow() - skip;
385       (*fn)(opaque, databuf_, databuf_strides_, scanlines_to_copy);
386       // Now change them back.
387       for (int i = 0; i < num_outbufs_; ++i) {
388         int rows_to_skip = DivideAndRoundDown(skip, GetVertSubSampFactor(i));
389         int data_to_skip = rows_to_skip * GetComponentStride(i);
390         databuf_[i] -= data_to_skip;
391       }
392       lines_left -= scanlines_to_copy;
393     }
394   }
395   // Read full MCUs until we get to the crop point.
396   for (; lines_left >= GetImageScanlinesPerImcuRow();
397          lines_left -= GetImageScanlinesPerImcuRow()) {
398     if (!DecodeImcuRow()) {
399       FinishDecode();
400       return LIBYUV_FALSE;
401     }
402     (*fn)(opaque, databuf_, databuf_strides_, GetImageScanlinesPerImcuRow());
403   }
404   if (lines_left > 0) {
405     // Have a partial iMCU row left over to decode.
406     if (!DecodeImcuRow()) {
407       FinishDecode();
408       return LIBYUV_FALSE;
409     }
410     (*fn)(opaque, databuf_, databuf_strides_, lines_left);
411   }
412   return FinishDecode();
413 }
414 
init_source(j_decompress_ptr cinfo)415 void init_source(j_decompress_ptr cinfo) {
416   fill_input_buffer(cinfo);
417 }
418 
fill_input_buffer(j_decompress_ptr cinfo)419 boolean fill_input_buffer(j_decompress_ptr cinfo) {
420   BufferVector* buf_vec = reinterpret_cast<BufferVector*>(cinfo->client_data);
421   if (buf_vec->pos >= buf_vec->len) {
422     assert(0 && "No more data");
423     // ERROR: No more data
424     return FALSE;
425   }
426   cinfo->src->next_input_byte = buf_vec->buffers[buf_vec->pos].data;
427   cinfo->src->bytes_in_buffer = buf_vec->buffers[buf_vec->pos].len;
428   ++buf_vec->pos;
429   return TRUE;
430 }
431 
skip_input_data(j_decompress_ptr cinfo,long num_bytes)432 void skip_input_data(j_decompress_ptr cinfo,
433                      long num_bytes) {  // NOLINT
434   cinfo->src->next_input_byte += num_bytes;
435 }
436 
term_source(j_decompress_ptr cinfo)437 void term_source(j_decompress_ptr cinfo) {
438   // Nothing to do.
439 }
440 
441 #ifdef HAVE_SETJMP
ErrorHandler(j_common_ptr cinfo)442 void ErrorHandler(j_common_ptr cinfo) {
443   // This is called when a jpeglib command experiences an error. Unfortunately
444   // jpeglib's error handling model is not very flexible, because it expects the
445   // error handler to not return--i.e., it wants the program to terminate. To
446   // recover from errors we use setjmp() as shown in their example. setjmp() is
447   // C's implementation for the "call with current continuation" functionality
448   // seen in some functional programming languages.
449   // A formatted message can be output, but is unsafe for release.
450 #ifdef DEBUG
451   char buf[JMSG_LENGTH_MAX];
452   (*cinfo->err->format_message)(cinfo, buf);
453   // ERROR: Error in jpeglib: buf
454 #endif
455 
456   SetJmpErrorMgr* mgr = reinterpret_cast<SetJmpErrorMgr*>(cinfo->err);
457   // This rewinds the call stack to the point of the corresponding setjmp()
458   // and causes it to return (for a second time) with value 1.
459   longjmp(mgr->setjmp_buffer, 1);
460 }
461 #endif
462 
AllocOutputBuffers(int num_outbufs)463 void MJpegDecoder::AllocOutputBuffers(int num_outbufs) {
464   if (num_outbufs != num_outbufs_) {
465     // We could perhaps optimize this case to resize the output buffers without
466     // necessarily having to delete and recreate each one, but it's not worth
467     // it.
468     DestroyOutputBuffers();
469 
470     scanlines_ = new uint8** [num_outbufs];
471     scanlines_sizes_ = new int[num_outbufs];
472     databuf_ = new uint8* [num_outbufs];
473     databuf_strides_ = new int[num_outbufs];
474 
475     for (int i = 0; i < num_outbufs; ++i) {
476       scanlines_[i] = NULL;
477       scanlines_sizes_[i] = 0;
478       databuf_[i] = NULL;
479       databuf_strides_[i] = 0;
480     }
481 
482     num_outbufs_ = num_outbufs;
483   }
484 }
485 
DestroyOutputBuffers()486 void MJpegDecoder::DestroyOutputBuffers() {
487   for (int i = 0; i < num_outbufs_; ++i) {
488     delete [] scanlines_[i];
489     delete [] databuf_[i];
490   }
491   delete [] scanlines_;
492   delete [] databuf_;
493   delete [] scanlines_sizes_;
494   delete [] databuf_strides_;
495   scanlines_ = NULL;
496   databuf_ = NULL;
497   scanlines_sizes_ = NULL;
498   databuf_strides_ = NULL;
499   num_outbufs_ = 0;
500 }
501 
502 // JDCT_IFAST and do_block_smoothing improve performance substantially.
StartDecode()503 LIBYUV_BOOL MJpegDecoder::StartDecode() {
504   decompress_struct_->raw_data_out = TRUE;
505   decompress_struct_->dct_method = JDCT_IFAST;  // JDCT_ISLOW is default
506   decompress_struct_->dither_mode = JDITHER_NONE;
507   // Not applicable to 'raw':
508   decompress_struct_->do_fancy_upsampling = (boolean)(LIBYUV_FALSE);
509   // Only for buffered mode:
510   decompress_struct_->enable_2pass_quant = (boolean)(LIBYUV_FALSE);
511   // Blocky but fast:
512   decompress_struct_->do_block_smoothing = (boolean)(LIBYUV_FALSE);
513 
514   if (!jpeg_start_decompress(decompress_struct_)) {
515     // ERROR: Couldn't start JPEG decompressor";
516     return LIBYUV_FALSE;
517   }
518   return LIBYUV_TRUE;
519 }
520 
FinishDecode()521 LIBYUV_BOOL MJpegDecoder::FinishDecode() {
522   // jpeglib considers it an error if we finish without decoding the whole
523   // image, so we call "abort" rather than "finish".
524   jpeg_abort_decompress(decompress_struct_);
525   return LIBYUV_TRUE;
526 }
527 
SetScanlinePointers(uint8 ** data)528 void MJpegDecoder::SetScanlinePointers(uint8** data) {
529   for (int i = 0; i < num_outbufs_; ++i) {
530     uint8* data_i = data[i];
531     for (int j = 0; j < scanlines_sizes_[i]; ++j) {
532       scanlines_[i][j] = data_i;
533       data_i += GetComponentStride(i);
534     }
535   }
536 }
537 
DecodeImcuRow()538 inline LIBYUV_BOOL MJpegDecoder::DecodeImcuRow() {
539   return (unsigned int)(GetImageScanlinesPerImcuRow()) ==
540       jpeg_read_raw_data(decompress_struct_,
541                          scanlines_,
542                          GetImageScanlinesPerImcuRow());
543 }
544 
545 // The helper function which recognizes the jpeg sub-sampling type.
JpegSubsamplingTypeHelper(int * subsample_x,int * subsample_y,int number_of_components)546 JpegSubsamplingType MJpegDecoder::JpegSubsamplingTypeHelper(
547     int* subsample_x, int* subsample_y, int number_of_components) {
548   if (number_of_components == 3) {  // Color images.
549     if (subsample_x[0] == 1 && subsample_y[0] == 1 &&
550         subsample_x[1] == 2 && subsample_y[1] == 2 &&
551         subsample_x[2] == 2 && subsample_y[2] == 2) {
552       return kJpegYuv420;
553     } else if (subsample_x[0] == 1 && subsample_y[0] == 1 &&
554         subsample_x[1] == 2 && subsample_y[1] == 1 &&
555         subsample_x[2] == 2 && subsample_y[2] == 1) {
556       return kJpegYuv422;
557     } else if (subsample_x[0] == 1 && subsample_y[0] == 1 &&
558         subsample_x[1] == 1 && subsample_y[1] == 1 &&
559         subsample_x[2] == 1 && subsample_y[2] == 1) {
560       return kJpegYuv444;
561     }
562   } else if (number_of_components == 1) {  // Grey-scale images.
563     if (subsample_x[0] == 1 && subsample_y[0] == 1) {
564       return kJpegYuv400;
565     }
566   }
567   return kJpegUnknown;
568 }
569 
570 }  // namespace libyuv
571 #endif  // HAVE_JPEG
572 
573