jpeg2mpeg

jpeg2mpeg 遇到的问题总结

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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值