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;
}