0. jpeg_read_raw_data(cinfo, JSAMPIMAGE data, max_lines); data 指针的正确理解。
如果没搞好,立马segfault或segabort。
jpeglib.h中的定义:
typedef unsigned char JSAMPLE;
typedef JSAMPLE FAR *JSAMPROW; /* ptr to one image row of pixel samples. */
typedef JSAMPROW *JSAMPARRAY; /* ptr to some rows (a 2-D sample array) */
typedef JSAMPARRAY *JSAMPIMAGE; /* a 3-D sample array: top index is color */
因此
data[0] 类型是 JSAMPARRAY,表示二维Y采样阵列,data[1], data[2]分别对应到Cb, Cr。
data[0][i] 类型是 JSAMPROW,表示Y的第i行。
data[0][i][j] 类型是 JSAMPLE,表示一个0-255的值。
在data[k] 这个层次上,下面的y_ptr, cb_ptr, cr_ptr 我曾经改成指向同一个malloc分配的内存,对于大的图片比如720x576正常,但是
对于小的图片100x100莫名其妙的死掉时发现cb_ptr[4]的指针为零,虽然我确定这个指针是已经初始化的。地址每次加100x4, 内存对齐都是对齐的,
剩下的就是怀疑这个二级指针跟cpu cache了。存疑,如有高人请留言指教。
1. ffmpeg直接输入是yuv420p的,因此要让jpeglib生成这种格式的数据。yuv444p怎么转成yuv420p?
最初我是采用netpbm里面的ppmtompeg,但是它编出来是独立执行程序,不便于发布,有500多KB,也稍微大了点。这个对于16倍大小yuv420p的jpg图片是可以的,但是悲剧的是我要转码的图片是yuv444p的,PS输出高质量图就是这种格式。不得已,网上找这两种格式的区别。其实先前我也弄过yuv420p的,但是不够深入。
先上图。
这里必须要理解subsampling,我以前在学校听另一个研究生老师经常念叨过,当时觉得很高深的样子,因为当时也看类似的图,立马就云里雾里的了。subsampling的字面意思是子采样,
比如在一条线上均匀的有0,1,2,3,...,n个点, 我们可以隔2个取:0,2,4,...。这是一维,可以推广到二维。
4:4:4等的抽象形式是J:a:b,表示Jx2 像素矩形区域上,顶行的色度采样数是a,底行的色度采样数是b。
4:2:0 中b = 0 并不表示没有,而是表示隔行才有。这里的色度采样数是指Cr或者Cb的采样个数。这里我们只关心通常Cr和Cb数量一样的情形。
因为人眼对亮度敏感,所以对Y是全采样喔。
采用率大概也可以类似理解。怎么从4:4:4变到4:2:0呢?可以先从4:4:4 到4:2:2,纵向不变,横向2倍子采样;再从4:2:2变到4:2:0,横向不变,纵向2倍子采样。
子采样精妙的地方是两个维度是独立的,这给我们代码带来很大简化。有了这些装备后,从内存的一维线性分配到两个for循环的二维视图相互转换,就容易得多了。
2. jpeglib的坑,如果没注意到,会导致非8倍大小的图片异常,在左边会出现一列短的条纹。
原因是每次处理的宽度是DCT的倍数,解决办法是分配的工作内存宽度是cinfo.output_width稍大一些,也不用太大,浪费内存,向上16倍(或8倍)取整就行了,详见working_width。
3. ffmpeg 是从git 提取的2002.06.14 07版本,这个版本可以正确处理100x100的小图片,之前的版本处理非8倍大小的图片会在右边和底边上没处理好。
4. 调试经验总结。
1) data指针我花了最多时间,一个教训是凡指针都初始化为NULL, 死掉的时候bt看引用的地址是0就知道大致原因了。
2) 二进制分析。上面的8倍大小问题我是通过查看二进制数据知道的,和正常的YUV数据相比,U分量开始的几个值明显不对了,也只有一行起始的地方,于是我想到应该是被Y数据覆盖了。
3) 对比分析。生成YUV我认为已经没有问题了,但是为什么右边和底边还有问题呢? 我把jpeg生成的test.yuv丢给电脑上的ffmpeg编码,结果正常,那就说明是我使用的ffmpeg版本问题了。于是我在ffmpeggit 二分查找早期的版本,尽可能早期的,因为我不想增加代码体积。这个很是辛苦,因为直接从git下来的不一定编的过,不过都是些小问题,最终定在2002.06.14 07这个版本是好的。其实比这个早的版本是2002.06.06 22的,只是右下角有个瑕疵,但是我在ubuntu上编译测试正常。我看了06.06到06.14的提交日志,看了好几遍也没找到本质差异,最后也算了不去计较。
最后附上代码:(由于一些特殊原因,我不能完整上传修改后的ffmpeg代码,请见谅。
/*jpg2mpeg.c -- transcode file from jpg to mpeg1 编译:请参考ffmpeg/libavcodec目录下,make apiexample 说明: 使用的jpeglib 版本是6b2修改版,支持从内存中读数据;如果使用v8d,把buffer_height 改成固定的16即可。 ffmpeg使用的是2002.06.14 07版本。 author: ludi 2014.02 licence: public domain */ #include <stdlib.h> #include <string.h> /*the following trick is for typedef conflict.*/ #define UINT32 JPEGLIB_UINT32 #define UINT16 JPEGLIB_UINT16 #define UINT8 JPEGLIB_UINT8 #define INT32 JPEGLIB_INT32 #define INT16 JPEGLIB_INT16 #define INT8 JPEGLIB_INT8 #include "jpeglib.h" #undef UINT32 #undef UINT16 #undef UINT8 #undef INT32 #undef INT16 #undef INT8 #define UINT32 AVCODEC_UINT32 #define UINT16 AVCODEC_UINT16 #define UINT8 AVCODEC_UINT8 #define INT32 AVCODEC_INT32 #define INT16 AVCODEC_INT16 #define INT8 AVCODEC_INT8 #include "avcodec.h" #undef UINT32 #undef UINT16 #undef UINT8 #undef INT32 #undef INT16 #undef INT8 #define UINT32 JPEGLIB_UINT32 #define UINT16 JPEGLIB_UINT16 #define UINT8 JPEGLIB_UINT8 #define INT32 JPEGLIB_INT32 #define INT16 JPEGLIB_INT16 #define INT8 JPEGLIB_INT8 static void error_exit (j_common_ptr cinfo) { char buffer[JMSG_LENGTH_MAX]; (*cinfo->err->format_message) (cinfo, buffer); fprintf(stderr, "[jpg error]:%s\n", buffer); } static AVPicture read_jpeg(AVCodecContext *ctx, unsigned char *jpegbuf, long jpeglen) { AVPicture picture = {{0}}; struct jpeg_decompress_struct cinfo; struct jpeg_error_mgr jerr; int buffer_height; UINT8 **orig[3]; int h_samp[3],v_samp[3]; int size, i, j, copy_h, y; UINT8 *ptr, *picture_buf, *UV_Plane, *u_ptr, *v_ptr; UINT8 **y_ptr, **cb_ptr, **cr_ptr; int working_width; /* Allocate and initialize JPEG decompression object */ cinfo.err = jpeg_std_error(&jerr); jerr.error_exit = error_exit; jpeg_create_decompress(&cinfo); (void)jpeg_stdio_src(&cinfo, jpegbuf, jpeglen); (void)jpeg_read_header(&cinfo, TRUE); cinfo.raw_data_out = TRUE; cinfo.out_color_space = JCS_YCbCr; jpeg_start_decompress(&cinfo); /*h0:v0 1:1 --> 444, 2:2 -->420, 2:1 --> 422*/ for(j = 0; j < cinfo.num_components; j++) { h_samp[j] = cinfo.comp_info[j].h_samp_factor; v_samp[j] = cinfo.comp_info[j].v_samp_factor; if((1 != h_samp[j]) && (2 != v_samp[j])){ fprintf(stderr, "not support sample factor: %d %d at %d\n", h_samp[j], v_samp[j], j); picture.linesize[0] = 0; goto _end; } } /*jpeg_read_raw_data() requires at least buffer_height >= max_v_samp_factor * DCTSIZE, usually buffer_height is 8 or 16.*/ buffer_height = cinfo.max_v_samp_factor * cinfo.min_DCT_scaled_size; i = cinfo.min_DCT_scaled_size - 1; working_width = (cinfo.output_width + i) & ~i; printf("working width %d --> %d\n", cinfo.output_width, working_width); /*alloc mem for YUV420 and working space.*/ ctx->width = cinfo.output_width; ctx->height = cinfo.output_height; size = ctx->width * ctx->height; picture_buf = calloc(1, size + size/ 2); picture.data[0] = picture_buf; picture.data[1] = picture.data[0] + size; picture.data[2] = picture.data[1] + size / 4; picture.linesize[0] = ctx->width; picture.linesize[1] = ctx->width / 2; picture.linesize[2] = ctx->width / 2; /*for simplicity, alloc maximum memory.*/ UV_Plane = calloc(1, working_width*buffer_height*3); y_ptr = calloc(1, buffer_height * sizeof(UINT8*)); cb_ptr = calloc(1, buffer_height * sizeof(UINT8*)); cr_ptr = calloc(1, buffer_height * sizeof(UINT8*)); if(!UV_Plane || !y_ptr){ fprintf(stderr, "no enough mem UV_Plane %p y_ptr %p\n", UV_Plane, y_ptr); picture.linesize[0] = 0; goto _end; } orig[0] = y_ptr; orig[1] = cb_ptr; orig[2] = cr_ptr; ptr = &UV_Plane[0]; for(j = 0; j < buffer_height; ++j, ptr += working_width){ orig[0][j] = ptr; } for(j = 0; j < buffer_height; ++j, ptr += working_width){ orig[1][j] = ptr; } for(j = 0; j < buffer_height; ++j, ptr += working_width){ orig[2][j] = ptr; } while (cinfo.output_scanline < cinfo.output_height) { jpeg_read_raw_data(&cinfo, orig, buffer_height); y = cinfo.output_scanline - buffer_height; copy_h = (cinfo.output_height - y > buffer_height) ? buffer_height : (cinfo.output_height - y); /*copy Y data*/ ptr = picture.data[0] + y * ctx->width; for(i = 0; i < copy_h; ++i){ memcpy(ptr, y_ptr[i], ctx->width); ptr += ctx->width; } /*horizontal downsampling*/ if(1 == h_samp[0]){ for(i = 0; i < copy_h; ++i){ for(j = 0; j < ctx->width/2; ++j){ cb_ptr[i][j] = (cb_ptr[i][2*j+0] + cb_ptr[i][2*j+1])/2; cr_ptr[i][j] = (cr_ptr[i][2*j+0] + cr_ptr[i][2*j+1])/2; } } } /*copy U V data*/ u_ptr = picture.data[1] + y/2 * ctx->width/2; v_ptr = picture.data[2] + y/2 * ctx->width/2; for(i = 0; i < copy_h/2; ++i){ for(j = 0; j < ctx->width/2; ++j){ if(1 == v_samp[0]){ /*vertical resampling*/ u_ptr[j] = (cb_ptr[2*i+0][j] + cb_ptr[2*i+1][j])/2; v_ptr[j] = (cr_ptr[2*i+0][j] + cr_ptr[2*i+1][j])/2; }else{ u_ptr[j] = cb_ptr[i][j]; v_ptr[j] = cr_ptr[i][j]; } } u_ptr += ctx->width/2; v_ptr += ctx->width/2; } } free(UV_Plane); free(y_ptr); free(cb_ptr); free(cr_ptr); _end: (void) jpeg_finish_decompress(&cinfo); /*Release JPEG object,it will release a good deal of memory */ jpeg_destroy_decompress(&cinfo); /* At this point you may want to check to see whether any corrupt-data * warnings occurred (test whether jerr.pub.num_warnings is nonzero). * If you prefer to treat corrupt data as a fatal error, override the * error handler's emit_message method to call error_exit on a warning. */ return picture; } jpg_to_mpeg(unsigned char *jpgbuf, unsigned int jpglen, unsigned char *outbuf, unsigned int *outlen) { AVCodec *codec; AVCodecContext codec_context = {0}, *c = &codec_context; int out_size,outbuf_size; AVPicture picture; unsigned char *outbuf0 = outbuf; picture = read_jpeg(c, jpgbuf, jpglen); if(!picture.linesize[0]){ return -1; } /* resolution must be a multiple of two */ if((c->width & 0x1) || (c->height & 0x1)){ fprintf(stderr, "resolution must be a multiple of two\n"); return -1; } c->bit_rate = 104857*1000; /*suppose big enough*/ /* frames per second */ c->frame_rate = 25 * FRAME_RATE_BASE; c->gop_size = 0; /*i want i-frame*/ c->flags |= CODEC_FLAG_QSCALE; c->quality = 7; /*if output size is too large, try to inc this value.*/ #if 0 /*for comparison with PC's newest ffmpeg: ffmpeg -f rawvideo -s 720x576 -r 25 -pix_fmt yuv420p -i test.yuv xx.mpeg*/ extern int write_file(unsigned char *file, unsigned char *data, int size); write_file("test.yuv", picture.data[0], c->width * c->height * 3/2); #endif /* find the mpeg1 video encoder */ codec = avcodec_find_encoder(CODEC_ID_MPEG1VIDEO); if (!codec) { fprintf(stderr, "codec not found\n"); return 1; } /* open it */ if (avcodec_open(c, codec) < 0) { fprintf(stderr, "could not open codec\n"); return 1; } outbuf_size = *outlen - 4; out_size = avcodec_encode_video(c, outbuf, outbuf_size, &picture); outbuf += out_size; /* add sequence end code to have a real mpeg file */ outbuf[0] = 0x00; outbuf[1] = 0x00; outbuf[2] = 0x01; outbuf[3] = 0xb7; outbuf += 4; free(picture.data[0]); avcodec_close(c); *outlen = outbuf - outbuf0; return 0; } int main(int ac, char **av) { char *jpgname = "test.jpg"; char *mpgname = "test.mpeg"; unsigned int jpglen = 512*1024; unsigned int mpglen = 512*1024; unsigned char *jpgbuf = malloc(jpglen); unsigned char *mpgbuf = malloc(mpglen); int ret = -1; FILE *fp = fopen(jpgname, "rb"); if(!fp){ printf("can't open %s\n", jpgname); return; } avcodec_init(); avcodec_register_all(); jpglen = fread(jpgbuf, 1, jpglen, fp); fclose(fp); ret = jpg_to_mpeg(jpgbuf, jpglen, mpgbuf, &mpglen); if(!ret){ fp = fopen(mpgname, "wb"); fwrite(mpgbuf, 1, mpglen, fp); fclose(fp); printf("jpglen %u mpglen %u ret %d\n", jpglen, mpglen, ret); } free(jpgbuf); free(mpgbuf); return 0; }