* 音视频入门文章目录 *
RGB-to-JPEG 回顾
上一篇 【手动生成一张JPEG图片】 根据 【JPEG文件格式详解】 一步一步地实现了将 RGB 数据生成了一张 JPEG 图片。
可以感受到,自己来实现 JPEG 的基本系统编码还是有相当的复杂度的,JPEG 压缩编码算法一共分为 11 个步骤:
颜色模式转换
采样
分块
离散余弦变换(DCT)
量化
Zigzag 扫描排序
DC 系数的差分脉冲调制编码
DC 系数的中间格式计算
AC 系数的游程长度编码
AC 系数的中间格式计算
熵编码
下面,我们使用 libjpeg-turbo 来处理 JPEG 图片。
使用 libjpeg-turbo
Building libjpeg-turbo
官方 Build 文档
mkdir libjpeg-turbo/build
cd libjpeg-turbo/build
cmake -G"Unix Makefiles" -DCMAKE_INSTALL_PREFIX:PATH=./ -DCMAKE_INSTALL_BINDIR:PATH=./ -DCMAKE_INSTALL_DATAROOTDIR:PATH=./ -DCMAKE_INSTALL_DOCDIR:PATH=./ -DCMAKE_INSTALL_LIBDIR:PATH=./ -DCMAKE_INSTALL_INCLUDEDIR:PATH=./ -DCMAKE_INSTALL_MANDIR:PATH=./ ..
make
make install
CMakeLists.txt
cmake_minimum_required(VERSION 3.14)
set(CMAKE_C_STANDARD 99)
#aux_source_directory(../3rd/libjpeg-turbo LIBJPEG_TURBO_SRC)
link_directories(../3rd/libjpeg-turbo/build)
include_directories(../3rd/libjpeg-turbo/build)
add_executable(16-rgb-to-jpeg-library rgb-to-jpeg-with-libjpeg-turbo.c util.c)
# 添加链接库
target_link_libraries(16-rgb-to-jpeg-library libturbojpeg.dylib)
libjpeg-turbo 处理 JPEG 图片
JPEG to RGB24
使用 libjpeg-turbo 解码 JPEG 图片成 RGB 格式的数据。
代码中使用的 JPEG 图片
struct ImageData {
unsigned char *pixels;
long width;
long height;
};
int decode_JPEG_file(char *inJpegName, char *outRgbName) {
struct jpeg_decompress_struct cinfo;
struct jpeg_error_mgr jerr;
FILE * infile;
FILE * outfile;
if ((infile = fopen(inJpegName, "rb")) == NULL) {
fprintf(stderr, "can't open %s\n", inJpegName);
return -1;
}
if ((outfile = fopen(outRgbName, "wb")) == NULL) {
fprintf(stderr, "can't open %s\n", outRgbName);
return -1;
}
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_decompress(&cinfo);
jpeg_stdio_src(&cinfo, infile);
jpeg_read_header(&cinfo, TRUE);
printf("image_width = %d\n", cinfo.image_width);
printf("image_height = %d\n", cinfo.image_height);
printf("num_components = %d\n", cinfo.num_components);
printf("enter scale M/N:\n");
jpeg_start_decompress(&cinfo);
//输出的图象的信息
printf("output_width = %d\n", cinfo.output_width);
printf("output_height = %d\n", cinfo.output_height);
printf("output_components = %d\n", cinfo.output_components);
int row_stride = cinfo.output_width * cinfo.output_components;
/* Make a one-row-high sample array that will go away when done with image */
JSAMPARRAY buffer = (JSAMPARRAY)malloc(sizeof(JSAMPROW));
buffer[0] = (JSAMPROW)malloc(sizeof(JSAMPLE) * row_stride);
struct ImageData imageData = {
.width = cinfo.image_width,
.height = cinfo.image_height,
.pixels = malloc(row_stride*cinfo.image_height)
};
long counter = 0;
while (cinfo.output_scanline < cinfo.output_height) {
jpeg_read_scanlines(&cinfo, buffer, 1);
memcpy(imageData.pixels + counter, buffer[0], row_stride);
counter += row_stride;
}
printf("total size: %ld\n", counter);
fwrite(imageData.pixels, counter, 1, outfile);
jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
fclose(infile);
fclose(outfile);
free(imageData.pixels);
return 0;
}
int main(int argc, char* argv[]) {
printf("↓↓↓↓↓↓↓↓↓↓ Decode JPEG to RGB24 ↓↓↓↓↓↓↓↓↓↓\n");
char *inJpegName1 = "/Users/staff/Desktop/libjpeg-turbo-test-image.jpg";
char *outRgbName1 = "/Users/staff/Desktop/libjpeg-turbo-test-image.rgb24";
int flag1 = decode_JPEG_file(inJpegName1, outRgbName1);
if (flag1 == 0) {
printf("decode ok!\n");
} else {
printf("decode error!\n");
}
printf("↑↑↑↑↑↑↑↑↑↑ Decode JPEG to RGB24 ↑↑↑↑↑↑↑↑↑↑\n\n");
}
运行上面的代码,将得到解码后的 RGB 文件 libjpeg-turbo-test-image.rgb24
:
使用 ffplay 查看 RGB24 文件:
ffplay -f rawvideo -pixel_format rgb24 -s 800x800 /Users/staff/Desktop/libjpeg-turbo-test-image.rgb24
RGB24 to JPEG
// 彩虹的七种颜色
uint32_t rainbowColors[] = {
0XFF0000, // 红
0XFFA500, // 橙
0XFFFF00, // 黄
0X00FF00, // 绿
0X007FFF, // 青
0X0000FF, // 蓝
0X8B00FF // 紫
};
void genRGB24Data(uint8_t *rgbData, int width, int height) {
for (int i = 0; i < width; ++i) {
// 当前颜色
uint32_t currentColor = rainbowColors[0];
if(i < 100) {
currentColor = rainbowColors[0];
} else if(i < 200) {
currentColor = rainbowColors[1];
} else if(i < 300) {
currentColor = rainbowColors[2];
} else if(i < 400) {
currentColor = rainbowColors[3];
} else if(i < 500) {
currentColor = rainbowColors[4];
} else if(i < 600) {
currentColor = rainbowColors[5];
} else if(i < 700) {
currentColor = rainbowColors[6];
}
// 当前颜色 R 分量
uint8_t R = (currentColor & 0xFF0000) >> 16;
// 当前颜色 G 分量
uint8_t G = (currentColor & 0x00FF00) >> 8;
// 当前颜色 B 分量
uint8_t B = currentColor & 0x0000FF;
for (int j = 0; j < height; ++j) {
int currentIndex = 3*(i*height+j);
rgbData[currentIndex] = R;
rgbData[currentIndex+1] = G;
rgbData[currentIndex+2] = B;
}
}
}
int encode_JPEG_file(char *strImageName,uint8_t *image_buffer, int image_height, int image_width, int quality) {
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
/* More stuff */
FILE * outfile; /* target file */
JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */
int row_stride; /* physical row width in image buffer */
cinfo.err = jpeg_std_error(&jerr);
/* Now we can initialize the JPEG compression object. */
jpeg_create_compress(&cinfo);
if ((outfile = fopen(strImageName, "wb")) == NULL) {
fprintf(stderr, "can't open %s\n", strImageName);
//exit(1);
return -1;
}
jpeg_stdio_dest(&cinfo, outfile);
cinfo.image_width = image_width; /* image width and height, in pixels */
cinfo.image_height = image_height;
cinfo.input_components = 3; /* # of color components per pixel */
cinfo.in_color_space = JCS_RGB; /* colorspace of input image */
jpeg_set_defaults(&cinfo);
jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */);
jpeg_start_compress(&cinfo, TRUE);
row_stride = image_width * 3; /* JSAMPLEs per row in image_buffer */
int line = 0;
while (line < cinfo.image_height) {
//row_pointer[0] = &image_buffer[cinfo.next_scanline * row_stride];
row_pointer[0] = &image_buffer[line * row_stride];
jpeg_write_scanlines(&cinfo, row_pointer, 1);
line++;
}
/* Step 6: Finish compression */
jpeg_finish_compress(&cinfo);
/* After finish_compress, we can close the output file. */
fclose(outfile);
/* Step 7: release JPEG compression object */
/* This is an important step since it will release a good deal of memory. */
jpeg_destroy_compress(&cinfo);
return 0;
}
int main(int argc, char* argv[]) {
printf("↓↓↓↓↓↓↓↓↓↓ Encode RGB24 to JPEG ↓↓↓↓↓↓↓↓↓↓\n");
int width = 700, height = 700;
char *outJpegName2 = "/Users/staff/Desktop/rainbow-rgb24.jpeg";
//uint8_t rgbBuffer[width*height*3];
uint8_t *rgbBuffer = malloc(width*height*3);
genRGB24Data(rgbBuffer, width, height);
int flag2 = encode_JPEG_file(outJpegName2, rgbBuffer, width, height, 80);
if (flag2 == 0) {
printf("encode ok!\n");
} else {
printf("encode error!\n");
}
free(rgbBuffer);
printf("↑↑↑↑↑↑↑↑↑↑ Encode RGB24 to JPEG ↑↑↑↑↑↑↑↑↑↑\n\n");
}
仍然是生成彩虹图,运行上面的代码,将得到编码后的 JPEG 文件 rainbow-rgb24.jpeg
:
JPEG to YUV
使用 libjpeg-turbo 解码 JPEG 图片成 YUV 格式的数据。
int tjpeg2yuv(unsigned char* jpeg_buffer, int jpeg_size, unsigned char** yuv_buffer, int* yuv_size, int* yuv_type)
{
tjhandle handle = NULL;
int width, height, subsample, colorspace;
int flags = 0;
int padding = 1; // 1或4均可,但不能是0
int ret = 0;
handle = tjInitDecompress();
tjDecompressHeader3(handle, jpeg_buffer, jpeg_size, &width, &height, &subsample, &colorspace);
printf("w: %d h: %d subsample: %d color: %d\n", width, height, subsample, colorspace);
flags |= 0;
*yuv_type = subsample;
// 注:经测试,指定的yuv采样格式只对YUV缓冲区大小有影响,实际上还是按JPEG本身的YUV格式来转换的
*yuv_size = tjBufSizeYUV2(width, padding, height, subsample);
*yuv_buffer =(unsigned char *)malloc(*yuv_size);
if (*yuv_buffer == NULL)
{
printf("malloc buffer for rgb failed.\n");
return -1;
}
ret = tjDecompressToYUV2(handle, jpeg_buffer, jpeg_size, *yuv_buffer, width,
padding, height, flags);
if (ret < 0)
{
printf("compress to jpeg failed: %s\n", tjGetErrorStr());
}
tjDestroy(handle);
return ret;
}
int main(int argc, char* argv[]) {
printf("↓↓↓↓↓↓↓↓↓↓ Decode JPEG to YUV ↓↓↓↓↓↓↓↓↓↓\n");
char *inJpegName3 = "/Users/staff/Desktop/libjpeg-turbo-test-image.jpg";
FILE *jpegFile = fopen(inJpegName3, "rb");
struct stat statbuf;
stat(inJpegName3, &statbuf);
int fileLen=statbuf.st_size;
printf("fileLength2: %d\n", fileLen);
uint8_t *jpegData = malloc(fileLen);
fread(jpegData, fileLen, 1, jpegFile);
fclose(jpegFile);
uint8_t *yuvData;
int yuvSize;
int yuvType;
tjpeg2yuv(jpegData, fileLen, &yuvData, &yuvSize, &yuvType);
printf("size: %d; type: %d\n", yuvSize, yuvType);
char *yuvSuffix;
if(yuvType == TJSAMP_444) {
yuvSuffix = ".yuv444";
} else if(yuvType == TJSAMP_422) {
yuvSuffix = ".yuv422";
} else if(yuvType == TJSAMP_420) {
yuvSuffix = ".yuv420";
} else if(yuvType == TJSAMP_GRAY) {
yuvSuffix = ".yuv-gray";
} else if(yuvType == TJSAMP_440) {
yuvSuffix = ".yuv440";
} else if(yuvType == TJSAMP_411) {
yuvSuffix = ".yuv411";
} else {
printf("Unsupported type!");
return -1;
}
printf("yuv samp: %s\n", yuvSuffix);
char yuvFileName[100];
sprintf(yuvFileName, "/Users/staff/Desktop/libjpeg-turbo-test-image%s", yuvSuffix);
FILE *yuvFile = fopen(yuvFileName, "wb");
fwrite(yuvData, yuvSize, 1, yuvFile);
free(jpegData);
free(yuvData);
fflush(yuvFile);
fclose(yuvFile);
printf("↑↑↑↑↑↑↑↑↑↑ Decode JPEG to YUV ↑↑↑↑↑↑↑↑↑↑\n\n");
}
运行上面的代码,将得到解码后的 YUV 文件 libjpeg-turbo-test-image.yuv420
:
使用 ffplay 查看 YUV 文件:
ffplay -f rawvideo -pixel_format yuv420p -s 800x800 /Users/staff/Desktop/libjpeg-turbo-test-image.yuv420
YUV to JPEG
利用上一步获得的 YUV 文件,再次编码成 JPEG 文件。
int tyuv2jpeg(unsigned char* yuv_buffer, int yuv_size, int width, int height, int subsample, unsigned char** jpeg_buffer, unsigned long* jpeg_size, int quality) {
tjhandle handle = NULL;
int flags = 0;
int padding = 1; // 1或4均可,但不能是0
int need_size = 0;
int ret = 0;
handle = tjInitCompress();
flags |= 0;
need_size = tjBufSizeYUV2(width, padding, height, subsample);
if (need_size != yuv_size) {
printf("we detect yuv size: %d, but you give: %d, check again.\n", need_size, yuv_size);
return 0;
}
ret = tjCompressFromYUV(handle, yuv_buffer, width, padding, height, subsample, jpeg_buffer, jpeg_size, quality, flags);
if (ret < 0) {
printf("compress to jpeg failed: %s\n", tjGetErrorStr());
}
tjDestroy(handle);
return ret;
}
int main(int argc, char* argv[]) {
printf("↓↓↓↓↓↓↓↓↓↓ Encode YUV to JPEG ↓↓↓↓↓↓↓↓↓↓\n");
char *yuv420FileName = "/Users/staff/Desktop/libjpeg-turbo-test-image.yuv420";
FILE *yuv420File = fopen(yuv420FileName, "rb");
int yuv420Width = 800, yuv420Height = 800;
int yuvSubsample = TJSAMP_420;
uint8_t *yuv2jpegBuffer;
unsigned long yuv2JpegSize;
struct stat yuv420FileStat;
stat(yuv420FileName, &yuv420FileStat);
int yuv420FileLen = yuv420FileStat.st_size;
printf("yuv420 file length: %d\n", yuv420FileLen);
uint8_t * yuv420Data = malloc(yuv420FileLen);
fread(yuv420Data, yuv420FileLen, 1, yuv420File);
printf("yuv420 read finish!\n");
tyuv2jpeg(yuv420Data, yuv420FileLen, yuv420Width, yuv420Height, yuvSubsample, &yuv2jpegBuffer, &yuv2JpegSize, 80);
printf("jpeg data size: %ld\n", yuv2JpegSize);
FILE *yuv2JpegOutFile = fopen("/Users/staff/Desktop/libjpeg-turbo-yuv-to-jpeg.jpeg", "wb");
fwrite(yuv2jpegBuffer, yuv2JpegSize, 1, yuv2JpegOutFile);
fclose(yuv420File);
fflush(yuv2JpegOutFile);
fclose(yuv2JpegOutFile);
free(yuv420Data);
printf("↑↑↑↑↑↑↑↑↑↑ Encode YUV to JPEG ↑↑↑↑↑↑↑↑↑↑\n\n");
return 0;
}
运行上面的代码,将得到编码后的 JPEG 文件 libjpeg-turbo-yuv-to-jpeg.jpeg
:
Congratulations!
至此,我们学会了使用 libjpeg-turbo 处理 JPEG 图片。
代码:
16-rgb-to-jpeg-library
参考资料:
Main libjpeg-turbo repository
JPEG图像压缩算法流程详解
libjpeg学习4:libjpeg-turbo之YUV
关于在Linux下使用TurboJPEG库
FFmpeg & FFPlay 命令基本用法