|
// $ sudo apt-get install libboost-all-dev |
|
// $ sudo apt-get install libjpeg-turbo8-dev |
|
// g++ -std=c++0x jpeg_test.cpp -ljpeg -lboost_filesystem -lboost_system -lrt -pthread |
|
// cp some/where/*.jpeg ./data |
|
// ./a.out --output ./data # output test |
|
// ./a.out --multi-thread ./data # memory-to-memory decompression benchmark |
|
|
|
#define _CRT_SECURE_NO_WARNINGS // VC++ : prevent nonsense warnings |
|
#include <assert.h> |
|
#include <stdio.h> |
|
#include <vector> |
|
#include <string> |
|
#include <future> // std::async |
|
|
|
// |
|
#if defined(_WIN32) |
|
#include <filesystem> // std::tr2:sys |
|
#include <windows.h> |
|
#include "jpeglib.h" |
|
namespace tr2_filesystem = std::tr2::sys; |
|
|
|
static uint64_t getMicrosec() { |
|
uint64_t t; |
|
QueryPerformanceCounter((LARGE_INTEGER*) &t); |
|
static uint64_t freq = 0; |
|
static uint64_t origin = 0; |
|
if(freq == 0) { |
|
origin = t; |
|
QueryPerformanceFrequency((LARGE_INTEGER*) &freq); |
|
freq /= 1000; |
|
} |
|
return ((t - origin) * 1000) / freq; |
|
} |
|
|
|
static std::string getFilenameString(const std::tr2::sys::path& path) { |
|
// MSVC++2012's <filesystem> is incompatible with TR2: |
|
// Filesystem Library for C++11/TR2 (Revision 1) |
|
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3335.html |
|
return path.filename(); |
|
} |
|
#elif defined(__linux__) |
|
#include <time.h> // clock_gettime |
|
#include <boost/filesystem.hpp> // boost::filesystem |
|
#include <jpeglib.h> // libjpeg-turbo |
|
namespace tr2_filesystem = boost::filesystem; |
|
|
|
static uint64_t getMicrosec() { |
|
struct timespec t; |
|
clock_gettime(CLOCK_MONOTONIC_RAW, &t); |
|
return (uint64_t) (t.tv_sec * 1000*1000) + (t.tv_nsec / 1000); |
|
} |
|
|
|
static std::string getFilenameString(const boost::filesystem::path& path) { |
|
return path.filename().string(); |
|
} |
|
#else |
|
# error abort. |
|
#endif |
|
|
|
static_assert(BITS_IN_JSAMPLE == 8, "BITS_IN_JSAMPLE must be '8'"); |
|
static_assert(sizeof(JSAMPLE) == 1, "sizeof(JSAMPLE) must be '1'"); |
|
|
|
// |
|
template<typename I, typename F> |
|
void para_for_each(I first, I last, const F& func) { |
|
auto n = last - first; |
|
std::vector<decltype(std::async(func, *first))> vh(n); |
|
for(decltype(n) i = 0; i < n; ++i) { // fork |
|
vh[i] = std::async(func, first[i]); |
|
} |
|
for(decltype(n) i = 0; i < n; ++i) { // join |
|
vh[i].get(); |
|
} |
|
} |
|
|
|
// |
|
struct File : public std::vector<uint8_t> { |
|
std::string filename; |
|
|
|
bool load(const std::string& filename) { |
|
bool ret = false; |
|
this->filename = filename; |
|
if(FILE* fp = fopen(filename.c_str(), "rb")) { |
|
fseek(fp, 0, SEEK_END); |
|
resize(ftell(fp)); |
|
rewind(fp); |
|
if(fread(&(*this)[0], 1, size(), fp) == size()) { |
|
ret = true; |
|
} |
|
fclose(fp); |
|
} |
|
return ret; |
|
} |
|
}; |
|
|
|
// |
|
struct FileEntries : std::vector<std::string> { |
|
void addDirExtFile(const std::string& filepath, const std::string& ext) { |
|
using namespace tr2_filesystem; |
|
path dirPath(filepath); |
|
if(exists(dirPath)) { |
|
for(directory_iterator it(dirPath), it_end; it != it_end; ++it) { |
|
if(is_regular_file(it->status()) && extension(it->path()) == ext) { |
|
std::string f = getFilenameString(it->path()); |
|
push_back(filepath + "/" + f); |
|
} |
|
} |
|
} |
|
} |
|
}; |
|
|
|
// |
|
class MyMemoryDstManager { |
|
public: |
|
MyMemoryDstManager() { |
|
clear(); |
|
} |
|
|
|
~MyMemoryDstManager() { |
|
} |
|
|
|
MyMemoryDstManager(const struct jpeg_decompress_struct* cinfo) { |
|
clear(); |
|
if(cinfo->quantize_colors) { |
|
// |
|
} else { |
|
bytesPerPixel = cinfo->out_color_components; |
|
bufferStride = cinfo->output_width * bytesPerPixel; |
|
bufferHeight = cinfo->output_height; |
|
ioBuffer.resize(bufferStride * bufferHeight); |
|
pixrow = (JSAMPROW) &ioBuffer[0]; |
|
} |
|
} |
|
|
|
void clear() { |
|
bufferStride = 0; |
|
bufferHeight = 0; |
|
bytesPerPixel = 1; |
|
pixrow = nullptr; |
|
} |
|
|
|
JSAMPROW* getDstBuffer() { |
|
return &pixrow; |
|
} |
|
|
|
size_t getDstBufferHeight() const { |
|
return 1; |
|
} |
|
|
|
const void* getIoBuffer() { |
|
if(ioBuffer.empty()) { |
|
return nullptr; |
|
} else { |
|
return &ioBuffer[0]; |
|
} |
|
} |
|
|
|
int getIoBufferStride() const { |
|
return bufferStride; |
|
} |
|
|
|
int getIoBufferBytesPerPixel() const { |
|
return bytesPerPixel; |
|
} |
|
|
|
int getIoBufferWidth() const { |
|
return bufferStride / bytesPerPixel; |
|
} |
|
|
|
int getIoBufferHeight() const { |
|
return bufferHeight; |
|
} |
|
|
|
void putPixelRows(JDIMENSION rowsSupplied) { |
|
auto p = (char*) pixrow; |
|
if(p) { |
|
p += bufferStride * rowsSupplied; |
|
pixrow = (JSAMPROW) p; |
|
} |
|
} |
|
|
|
protected: |
|
size_t bufferStride; |
|
size_t bufferHeight; |
|
size_t bytesPerPixel; |
|
std::vector<char> ioBuffer; |
|
JSAMPROW pixrow; |
|
}; |
|
|
|
// |
|
class MyMemorySrcManager { |
|
public: |
|
MyMemorySrcManager() { |
|
clear(); |
|
} |
|
|
|
~MyMemorySrcManager() { |
|
} |
|
|
|
MyMemorySrcManager(const void* memory, size_t memoryBytes) { |
|
clear(); |
|
|
|
pub.init_source = [](j_decompress_ptr cinfo) { |
|
auto that = (MyMemorySrcManager*) (cinfo->src); |
|
return that->init_source(cinfo); |
|
}; |
|
pub.fill_input_buffer = [](j_decompress_ptr cinfo) { |
|
auto that = (MyMemorySrcManager*) (cinfo->src); |
|
return that->fill_input_buffer(cinfo); |
|
}; |
|
pub.skip_input_data = [](j_decompress_ptr cinfo, long num_bytes) { |
|
auto that = (MyMemorySrcManager*) (cinfo->src); |
|
return that->skip_input_data(cinfo, num_bytes); |
|
}; |
|
pub.term_source = [](j_decompress_ptr cinfo) { |
|
return; |
|
}; |
|
pub.resync_to_restart = jpeg_resync_to_restart; // use default method |
|
|
|
memoryCurrent = (const char*) memory; |
|
memoryEnd = memoryCurrent + memoryBytes; |
|
} |
|
|
|
void clear() { |
|
pub.bytes_in_buffer = 0; |
|
pub.next_input_byte = nullptr; |
|
memoryCurrent = nullptr; |
|
memoryEnd = nullptr; |
|
} |
|
|
|
struct jpeg_source_mgr* getJpegSourceMgr() { |
|
return &pub; |
|
} |
|
|
|
void init_source(j_decompress_ptr cinfo) { |
|
} |
|
|
|
boolean fill_input_buffer(j_decompress_ptr cinfo) { |
|
auto nBytes = memoryEnd - memoryCurrent; |
|
if(nBytes > 0) { |
|
pub.next_input_byte = (const JOCTET*) memoryCurrent; |
|
pub.bytes_in_buffer = nBytes; |
|
memoryCurrent += nBytes; |
|
} else { |
|
static uint8_t fakeEoi[2] = { 0xff, JPEG_EOI }; |
|
pub.next_input_byte = &fakeEoi[0]; |
|
pub.bytes_in_buffer = sizeof(fakeEoi); |
|
} |
|
return TRUE; |
|
} |
|
|
|
void skip_input_data(j_decompress_ptr cinfo, long num_bytes) { |
|
if(num_bytes > 0) { |
|
while(num_bytes > (long) pub.bytes_in_buffer) { |
|
num_bytes -= (long) pub.bytes_in_buffer; |
|
(void) fill_input_buffer(cinfo); |
|
} |
|
pub.next_input_byte += (size_t) num_bytes; |
|
pub.bytes_in_buffer -= (size_t) num_bytes; |
|
} |
|
} |
|
|
|
protected: |
|
struct jpeg_source_mgr pub; |
|
const char* memoryCurrent; |
|
const char* memoryEnd; |
|
}; |
|
|
|
// |
|
class MyErrorManager { |
|
public: |
|
MyErrorManager() { |
|
jpeg_std_error(&jerr); |
|
|
|
jerr.error_exit = [](j_common_ptr cinfo) { |
|
auto that = (MyErrorManager*) (cinfo->err); |
|
return that->errorExit(cinfo); |
|
}; |
|
jerr.emit_message = [](j_common_ptr cinfo, int msg_level) { |
|
auto that = (MyErrorManager*) (cinfo->err); |
|
return that->emitMessage(cinfo, msg_level); |
|
}; |
|
jerr.output_message = [](j_common_ptr cinfo) { |
|
auto that = (MyErrorManager*) (cinfo->err); |
|
return that->outputMessage(cinfo); |
|
}; |
|
jerr.format_message = [](j_common_ptr cinfo, char* buffer) { |
|
auto that = (MyErrorManager*) (cinfo->err); |
|
return that->formatMessage(cinfo, buffer); |
|
}; |
|
jerr.reset_error_mgr = [](j_common_ptr cinfo) { |
|
auto that = (MyErrorManager*) (cinfo->err); |
|
return that->resetErrorMgr(cinfo); |
|
}; |
|
} |
|
|
|
struct jpeg_error_mgr* getJpegErrorMgr() { |
|
return &jerr; |
|
} |
|
|
|
void errorExit(j_common_ptr cinfo) { |
|
outputMessage(cinfo); |
|
jpeg_destroy(cinfo); |
|
throw jerr.msg_code; |
|
} |
|
|
|
void emitMessage(j_common_ptr cinfo, int msgLevel) { |
|
if(msgLevel < 0) { |
|
// It's a warning message. Since corrupt files may generate many warnings, |
|
// the policy implemented here is to show only the first warning, |
|
// unless trace_level >= 3. |
|
if(jerr.num_warnings == 0 || jerr.trace_level >= 3) { |
|
outputMessage(cinfo); |
|
} |
|
jerr.num_warnings += 1; |
|
} else { |
|
// It's a trace message. Show it if trace_level >= msg_level. |
|
if(jerr.trace_level >= msgLevel) { |
|
outputMessage(cinfo); |
|
} |
|
} |
|
} |
|
|
|
void outputMessage(j_common_ptr cinfo) { |
|
char buffer[JMSG_LENGTH_MAX]; |
|
formatMessage(cinfo, buffer); |
|
fprintf(stderr, "%s\n", buffer); |
|
} |
|
|
|
void formatMessage(j_common_ptr cinfo, char* buffer) { |
|
const char * msgtext = nullptr; |
|
{ |
|
int msg_code = jerr.msg_code; |
|
if(msg_code > 0 && msg_code <= jerr.last_jpeg_message) { |
|
msgtext = jerr.jpeg_message_table[msg_code]; |
|
} |
|
if(!msgtext) { |
|
jerr.msg_parm.i[0] = msg_code; |
|
msgtext = jerr.jpeg_message_table[0]; |
|
} |
|
} |
|
|
|
boolean isstring = FALSE; |
|
{ |
|
const char * msgptr = msgtext; |
|
char ch; |
|
while((ch = *msgptr++) != '\0') { |
|
if(ch == '%') { |
|
if(*msgptr == 's') { |
|
isstring = TRUE; |
|
} |
|
break; |
|
} |
|
} |
|
} |
|
|
|
if(isstring) { |
|
sprintf(buffer, msgtext, jerr.msg_parm.s); |
|
} else { |
|
sprintf(buffer, msgtext, |
|
jerr.msg_parm.i[0], jerr.msg_parm.i[1], |
|
jerr.msg_parm.i[2], jerr.msg_parm.i[3], |
|
jerr.msg_parm.i[4], jerr.msg_parm.i[5], |
|
jerr.msg_parm.i[6], jerr.msg_parm.i[7]); |
|
} |
|
} |
|
|
|
void resetErrorMgr(j_common_ptr cinfo) { |
|
jerr.num_warnings = 0; |
|
jerr.msg_code = 0; |
|
} |
|
|
|
protected: |
|
struct jpeg_error_mgr jerr; // public fields |
|
}; |
|
|
|
// |
|
int main(int argc, const char* argv[]) { |
|
bool bFileOut = false; |
|
bool bMultiThread = false; |
|
FileEntries files; |
|
|
|
for(int iArg = 1; iArg < argc; ++iArg) { |
|
const std::string o(argv[iArg]); |
|
if(o == "--output") { |
|
bFileOut = true; |
|
} else if(o == "--multi-thread") { |
|
bMultiThread = true; |
|
} else { |
|
files.addDirExtFile(o, ".jpg"); |
|
files.addDirExtFile(o, ".jpeg"); |
|
} |
|
} |
|
|
|
std::vector<File> vFile(files.size()); |
|
for(size_t i = 0; i < files.size(); ++i) { |
|
std::string filename = files[i]; |
|
File& f = vFile[i]; |
|
f.load(filename); |
|
printf("#%3d : %-32s %10d Bytes\r", i, filename.c_str(), f.size()); |
|
} |
|
printf("\n"); |
|
|
|
uint64_t totalInput = 0; |
|
uint64_t totalOutput = 0; |
|
auto cl = [bFileOut, &totalInput, &totalOutput](const File& f) { |
|
try { |
|
struct jpeg_decompress_struct cinfo = { 0 }; |
|
MyErrorManager errMan; |
|
cinfo.err = errMan.getJpegErrorMgr(); |
|
jpeg_create_decompress(&cinfo); |
|
cinfo.quantize_colors = FALSE; |
|
cinfo.two_pass_quantize = FALSE; |
|
cinfo.dither_mode = JDITHER_NONE; |
|
cinfo.dct_method = JDCT_FASTEST; |
|
cinfo.do_fancy_upsampling = FALSE; |
|
|
|
MyMemorySrcManager srcMan(&f[0], f.size()); |
|
cinfo.src = srcMan.getJpegSourceMgr(); |
|
jpeg_read_header(&cinfo, TRUE); |
|
jpeg_calc_output_dimensions(&cinfo); |
|
MyMemoryDstManager dstMan(&cinfo); |
|
|
|
jpeg_start_decompress(&cinfo); |
|
while(cinfo.output_scanline < cinfo.output_height) { |
|
JDIMENSION n = jpeg_read_scanlines( |
|
&cinfo, dstMan.getDstBuffer(), dstMan.getDstBufferHeight()); |
|
dstMan.putPixelRows(n); |
|
} |
|
jpeg_finish_decompress(&cinfo); |
|
totalInput += f.size(); |
|
totalOutput += dstMan.getIoBufferStride() * dstMan.getIoBufferHeight(); |
|
|
|
if(bFileOut && dstMan.getIoBuffer()) { |
|
std::string dstFilename = f.filename + ".ppm"; |
|
if(FILE* fp = fopen(dstFilename.c_str(), "wb")) { |
|
int width = dstMan.getIoBufferWidth(); |
|
int height = dstMan.getIoBufferHeight(); |
|
auto buf = dstMan.getIoBuffer(); |
|
auto bufBytes = dstMan.getIoBufferStride() * height; |
|
fprintf(fp, "P6\n%d %d\n%d\n", width, height, 255); |
|
fwrite(buf, 1, bufBytes, fp); |
|
fclose(fp); |
|
} |
|
} |
|
jpeg_destroy_decompress(&cinfo); |
|
} catch(int msg_code) { |
|
printf("[%s]: JPEG Decoder error occured (%d).\n" |
|
, f.filename.c_str(), msg_code); |
|
} |
|
}; |
|
|
|
auto t0 = getMicrosec(); |
|
if(bMultiThread) { |
|
para_for_each(begin(vFile), end(vFile), cl); |
|
} else { |
|
std::for_each(begin(vFile), end(vFile), cl); |
|
} |
|
auto totSec = (double) (getMicrosec() - t0) / 1000.0 / 1000.0; |
|
auto totInpMb = (double) totalInput / 1024.0 / 1024.0; |
|
auto totOutMb = (double) totalOutput / 1024.0 / 1024.0; |
|
printf("total files =%10d files\n", vFile.size()); |
|
printf(" time =%10.5f msec\n", totSec * 1000.0); |
|
printf(" input =%10.5f MBytes\n", totInpMb); |
|
printf(" output =%10.5f MBytes\n", totOutMb); |
|
printf("avg. input =%10.5f MBytes/sec\n", totInpMb / totSec); |
|
printf(" output =%10.5f MBytes/sec\n", totOutMb / totSec); |
|
|
|
return 0; |
|
} |