/* * Copyright (C) 2008 The Android Open Source Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* this small program is used to measure the performance of libjpeg decompression * algorithm... */ #include <time.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <sys/time.h> #include "jpeglib.h" #include <setjmp.h> #ifdef HAVE_ANDROID_OS #include <hardware_legacy/qemu_tracing.h> #endif #define USE_STDIO #define CHUNK 32768 typedef struct { struct jpeg_source_mgr jpeg_mgr; char* base; char* cursor; char* end; } SourceMgrRec, *SourceMgr; static void _source_init_source(j_decompress_ptr cinfo) { SourceMgr src = (SourceMgr) cinfo->src; src->jpeg_mgr.next_input_byte = (unsigned char*)src->base, src->jpeg_mgr.bytes_in_buffer = src->end - src->base; } static int _source_fill_input_buffer(j_decompress_ptr cinfo) { SourceMgr src = (SourceMgr) cinfo->src; cinfo->err->error_exit((j_common_ptr)cinfo); return FALSE; } static void _source_skip_input_data(j_decompress_ptr cinfo, long num_bytes) { SourceMgr src = (SourceMgr) cinfo->src; if (src->jpeg_mgr.next_input_byte + num_bytes > (unsigned char*)src->end ) { cinfo->err->error_exit((j_common_ptr)cinfo); } src->jpeg_mgr.next_input_byte += num_bytes; src->jpeg_mgr.bytes_in_buffer -= num_bytes; } static int _source_resync_to_restart( j_decompress_ptr cinfo, int desired) { SourceMgr src = (SourceMgr) cinfo->src; src->jpeg_mgr.next_input_byte = (unsigned char*)src->base; src->jpeg_mgr.bytes_in_buffer = src->end - src->base; return TRUE; } static void _source_term_source(j_decompress_ptr cinfo) { // nothing to do } static void _source_init( SourceMgr src, char* base, long size ) { src->base = base; src->cursor = base; src->end = base + size; src->jpeg_mgr.init_source = _source_init_source; src->jpeg_mgr.fill_input_buffer = _source_fill_input_buffer; src->jpeg_mgr.skip_input_data = _source_skip_input_data; src->jpeg_mgr.resync_to_restart = _source_resync_to_restart; src->jpeg_mgr.term_source = _source_term_source; } typedef struct { struct jpeg_error_mgr jpeg_mgr; jmp_buf jumper; int volatile error; } ErrorMgrRec, *ErrorMgr; static void _error_exit(j_common_ptr cinfo) { ErrorMgr error = (ErrorMgr) cinfo->err; (*error->jpeg_mgr.output_message) (cinfo); /* Let the memory manager delete any temp files before we die */ longjmp(error->jumper, -1); } #ifdef USE_STDIO int decompress(FILE* input_file, int dct_method, int disable_rgb) #else int decompress(char* data, long fsize) #endif { ErrorMgrRec errmgr; SourceMgrRec sourcemgr; struct jpeg_decompress_struct cinfo; int volatile error = 0; jmp_buf jumper; int isRGB; char* pixels; JSAMPLE* temprow; memset( &cinfo, 0, sizeof(cinfo) ); memset( &errmgr, 0, sizeof(errmgr) ); jpeg_create_decompress(&cinfo); cinfo.err = jpeg_std_error(&errmgr.jpeg_mgr); #if 0 errmgr.jpeg_mgr.error_exit = _error_exit; errmgr.error = 0; #endif if (setjmp(errmgr.jumper) != 0) { fprintf(stderr, "returning error from jpeglib ---\n" ); goto Exit; } #ifdef USE_STDIO /* Specify data source for decompression */ jpeg_stdio_src(&cinfo, input_file); #else _source_init( &sourcemgr, data, fsize ); cinfo.src = &sourcemgr.jpeg_mgr; #endif jpeg_read_header(&cinfo, 1); if (3 == cinfo.num_components && JCS_RGB == cinfo.out_color_space) isRGB = 1; else if (1 == cinfo.num_components && JCS_GRAYSCALE == cinfo.out_color_space) isRGB = 0; // could use Index8 config if we want... else { fprintf( stderr, "unsupported jpeg colorspace %d with %d components\n", cinfo.jpeg_color_space, cinfo.num_components ); goto Exit; } cinfo.dct_method = dct_method; if (disable_rgb) cinfo.out_color_space = JCS_YCbCr; jpeg_start_decompress(&cinfo); temprow = calloc( cinfo.num_components * cinfo.output_width, sizeof(JSAMPLE) ); { unsigned y; for (y = 0; y < cinfo.output_height; y++) { JSAMPLE* rowptr = temprow; (void)jpeg_read_scanlines(&cinfo, &rowptr, 1); } } jpeg_finish_decompress(&cinfo); free( temprow ); Exit: jpeg_destroy_decompress(&cinfo); return error; } #define DEFAULT_REPEAT 10 static void usage(void) { fprintf(stderr, "usage: test_jpeg [options] filename.jpg [filename2.jpg ...]\n" ); fprintf(stderr, "options: -r NN repeat count (default %d)\n", DEFAULT_REPEAT ); fprintf(stderr, " -d N idct method (0=default, 1=fastest, 2=slow, 3=float)\n" ); fprintf(stderr, " -C no RGB color conversion (YCbCr instead)\n" ); exit(1); } static double get_time_usec( void ) { #ifdef HAVE_ANDROID_OS struct timespec ts; if ( clock_gettime( CLOCK_MONOTONIC, &ts ) < 0 ) fprintf(stderr, "clock_gettime: %s\n", strerror(errno) ); return ts.tv_sec*1e6 + ts.tv_nsec*1e-3; #else struct timeval tv; if (gettimeofday( &tv, NULL ) < 0) fprintf(stderr, "gettimeofday: %s\n", strerror(errno) ); return tv.tv_sec*1000000. + tv.tv_usec*1.0; #endif } int main( int argc, char** argv ) { FILE* f; int repeat_count = DEFAULT_REPEAT; int dct_method = JDCT_DEFAULT; int disable_rgb = 0; double usec0, usec1; if (argc < 2) usage(); for ( ; argc > 1 && argv[1][0] == '-'; argc--, argv++) { const char* arg = &argv[1][1]; switch (arg[0]) { case 'r': if (arg[1] == 0) { if (argc < 3) usage(); arg = argv[2]; argc--; argv++; } else arg += 1; repeat_count = strtol(arg, NULL, 10); if (repeat_count <= 0) repeat_count = 1; break; case 'C': disable_rgb = 1; break; case 'd': if (arg[1] == 0) { if (argc < 3) usage(); arg = argv[2]; argc--; argv++; } else arg += 1; dct_method = strtol(arg, NULL, 10); switch (dct_method) { case 0: dct_method = JDCT_DEFAULT; break; case 1: dct_method = JDCT_IFAST; break; case 2: dct_method = JDCT_ISLOW; break; case 3: dct_method = JDCT_FLOAT; break; default: usage(); } break; default: usage(); } } for ( ; argc > 1; argc--, argv++ ) { long fsize; char* data; FILE* f = fopen( argv[1], "rb" ); int rr; if (f == NULL) { fprintf(stderr, "could not open '%s': %s\n", argv[1], strerror(errno) ); continue; } fseek( f, 0, SEEK_END ); fsize = ftell(f); fseek( f, 0, SEEK_SET ); usec0 = get_time_usec(); #ifdef HAVE_ANDROID_OS qemu_start_tracing(); #endif #ifdef USE_STDIO for ( rr = repeat_count; rr > 0; rr-- ) { fseek( f, 0, SEEK_SET ); decompress(f, dct_method, disable_rgb); } fclose( f ); #else data = malloc( fsize ); if (data == NULL) { if (fsize > 0) fprintf(stderr, "could not allocate %ld bytes to load '%s'\n", fsize, argv[1] ); fclose(f); continue; } fread( data, 1, fsize, f ); fclose(f); usec1 = get_time_usec() - usec0; printf( "compressed load: %10.2f ms (%ld bytes)\n", usec1*1e-3, fsize ); usec0 = get_time_usec(); for ( rr = repeat_count; rr > 0; rr -- ) { decompress( data, fsize ); } free( data ); #endif #ifdef HAVE_ANDROID_OS qemu_stop_tracing(); #endif usec1 = get_time_usec() - usec0; printf( "decompression took: %10.3f ms (%.2f KB/s, %d passes)\n", usec1/1e3, fsize*(1e6/1024)*repeat_count/usec1, repeat_count ); } return 0; }