基于am335x平台 mjpeg转码h264

简单介绍下:公司am335x平台谈了一个安防方向的应用,基本功能差不多实现,客户提出在特定场景采集视频,然后转码为h264,通过局域网传输到服务器。采集视频采用uvc摄像头,采集格式支持mjpeg,yuv。考虑到两者采集文件都偏大,如果客户端较多,这样造成服务器端网络风暴,因此需要转码为h264.

uv视频格式,相同条件下文件过大,以及一个很现实的问题(am335x平台usb dma存在bug,高速率传输会丢包,因此限定分辨率上限是320x240)因此确定采集mjpeg视频(这个问题很烦人,我还花了2周时间追usb、uvc、cppi41驱动代码,追ti官方usb的buglist。。)。查阅资料,最终确定方案为:采集到mjpeg视频文件,通过ffmpeg+x264转码,最终以h264形式保存。文件显著变小:5s 25fps 640x480分辨率 文件大小为8.5M mjpeg视频,转码后390k。

下面分2方面介绍这里的工作:完成转码基本功能、转码优化。建议刚接触音视频转码的童鞋先了解一下ffmpeg编解码的基本流程,以及一些基本概念:封装格式、编码格式、未经过压缩格式RGB、YUV422P、YUV420P,以及之间的转换:http://blog.csdn.net/leixiaohua1020/article/details/15811977。

一、实现转码基本功能

1.移植ffmepg+x264+yasm:

从官网上下载最新的源码,交叉编译,应该比较简单。这里只说明一下编译选项。

yasm:./configure --enable-shared --prefix=/usr/local/cross-ffmpeg --host=arm-linux CC=/opt/arm-2014.05/bin/arm-none-linux-gnueabi-gcc

x264:./configure --enable-shared --host=arm-linux  --prefix=/usr/local/cross-ffmpeg   --cross-prefix=/opt/arm-2014.05/bin/arm-none-linux-gnueabi-  --disable-asm

ffmpeg:./configure --enable-cross-compile  --arch=armv7 --target-os=linux --cross-prefix=/opt/arm-2014.05/bin/arm-none-linux-gnueabi-  --enable-shared --disable-static  --enable-gpl --enable-libx264 --prefix=/usr/local/cross-ffmpeg --extra-cflags=-I/usr/local/cross-ffmpeg/include --extra-ldflags=-L/usr/local/cross-ffmpeg/lib/ --extra-libs=-ldl

ffmpeg是转码工具,x264是h264格式编解码器,yasm汇编级别的优化。

2.转码有两种方式:直接调用ffmpeg 或者编写code。考虑到转码工具不够灵活、可操作性不好使用code方式,并且mjpeg解码得到yuv422p,h264解码得到yuv420p,中间格式转码无法实现(这个猜想证明是错的,后来测试直接调用ffmepg是可以的,但是代码中yuv422转h264,转码后文件很大,不知道其中的差别在哪里)。

首先考虑直接在网上寻找成熟代码,然而真是没有。。查看ffmpeg 官方demo(/share/ffmpeg/example),使用decode_video.c 以及encode_video.c,但是怎么都运行不起来,总是报错退出(demo中是stream流形式,解码的是mjpeg1,编码的源文件的自己构造的数据。。反正种种不一致,加上自己好多东西不了解。)。参考了这个博客:http://blog.csdn.net/u011913612/article/details/53419986,完成了基本代码。

另外这里要说明的是:v4l2接口采集并保存的文件偏大,vim查看文件发现文件很大一部分为0,看了下采集的demo,发现v4l2接口的大小并不准确,改为通过寻找0xff 0xd9(jpeg结束码)获得数据长度,然后再保存。

二、优化

网络上优化的方法基本上是:编译时enable-yasm,enable-neon,自己实现yuv、rgb格式转换(官方提供的sws_scale效率低)或者在io操作优化。依次尝试后,发现有一定改善,不过cpu转码仍然需要转码好长时间,比如:5s 25fps 640x480分辨率 文件大小为8.5M mjpeg视频,转码后390k,图像质量基本一致,转码时间2min50s。

针对这个现象,网络上方案基本是在转码过程中加sleep,来降低cpu占有率,也有通过cgroup进行资源分配。我采用了降低解码进程的优先级的方式,这样既能提高cpu对其他任务的相应,也能在空闲时,最大化利用cpu。

另一个问题是ffmpeg转码占用了%20内存。。。没有发现内存泄露,也没有发现可以优化的部分,各位童鞋能给个建议吗?

附录:code

#include <math.h>
#include <libavutil/opt.h>
#include <libavcodec/avcodec.h>
#include <libavutil/channel_layout.h>
#include <libavutil/common.h>
#include <libavutil/imgutils.h>
#include <libavutil/mathematics.h>
#include <libavutil/samplefmt.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#define uinit8_t unsigned char
#include <sys/time.h>
#include <sys/resource.h>
#include <sched.h>
#include <sys/types.h>
#include <unistd.h>


static int  video_decode_example(const char *filename,const char *outfilename)
{
	av_log_set_level(AV_LOG_ERROR);  /* close part of ffmepg prints */
	/* register all the codecs */
	av_register_all();
	FILE* out = fopen(outfilename,"wb");
	
	AVFormatContext* pFormatCtx = NULL;
	//step 1:open file,get format info from file header
	if (avformat_open_input(&pFormatCtx, filename, NULL, NULL) != 0){
		fprintf(stderr,"avformat_open_input");
		return;
	}
	//step 2:get stread info
	if (avformat_find_stream_info(pFormatCtx, NULL) < 0){
		fprintf(stderr,"avformat_find_stream_info");
		return; 
	}
	//just output format info of input file
	av_dump_format(pFormatCtx, 0, filename, 0);
	int videoStream = -1;
	int i;
	//step 3:find vido stream
	for ( i = 0; i < pFormatCtx->nb_streams; i++)
	{
		if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
		{
			videoStream = i;
			break;
		}
	}
	if (videoStream == -1){
		fprintf(stderr,"find video stream error");
		return;
	}
	AVCodecContext* pCodecCtxOrg = NULL;
	AVCodecContext* pCodecCtx = NULL;

	AVCodec* pCodec = NULL;

	AVCodec* enc = avcodec_find_encoder(AV_CODEC_ID_H264);
	AVCodecContext* enc_ctx = avcodec_alloc_context3(enc);

	pCodecCtxOrg = pFormatCtx->streams[videoStream]->codec; // codec context        
	//step 4:find  decoder
	pCodec = avcodec_find_decoder(pCodecCtxOrg->codec_id);

	if (!pCodec){
		fprintf(stderr,"avcodec_find_decoder error");
		return;
	}
	//step 5:get one instance of AVCodecContext,decode need it.
	pCodecCtx = avcodec_alloc_context3(pCodec);
	if (avcodec_copy_context(pCodecCtx, pCodecCtxOrg) != 0){
		fprintf(stderr,"avcodec_copy_context error");
		return;
	}
	//step 6: open codec
	if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0){
		fprintf(stderr,"avcodec_open2 error");
		return;
	}
	AVFrame* pFrame = NULL;
	AVFrame* pFrameYUV = NULL;

	pFrame = av_frame_alloc();
	pFrameYUV = av_frame_alloc();

	int numBytes = 0;
	uint8_t* buffer = NULL;

 	enc_ctx->width = pCodecCtx->width;
	enc_ctx->height = pCodecCtx->height;
//	enc_ctx->bit_rate = 500000;
	enc_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
	pCodecCtx->framerate = (AVRational){1,15};
	pCodecCtx->time_base = (AVRational){1,15};
	enc_ctx->time_base = pCodecCtx->time_base;
	enc_ctx->framerate = pCodecCtx->framerate;
	enc_ctx->gop_size = 12;
	enc_ctx->max_b_frames = 3;
	av_opt_set(enc_ctx->priv_data, "preset", "slow", 0);

	if (avcodec_open2(enc_ctx,enc,NULL)<0)
	{
		perror("open encodec");
		return -1;
	}

	buffer=(uint8_t *)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height)*sizeof(uinit8_t)); 
	avpicture_fill((AVPicture *)pFrameYUV, buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);
	struct SwsContext* sws_ctx = NULL;

	AVPacket packet;
	AVPacket dst_packet;
	av_init_packet(&dst_packet);
	dst_packet.data = NULL;
	dst_packet.size = 0;
	int cnt0=0;
	int cnt1=0;
	 i = 0;
		int frameFinished = 0;
	//step 7:read frame
	while (av_read_frame(pFormatCtx, &packet) >= 0)
	{
		cnt0++;
		 frameFinished = 0;
		avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
		if (frameFinished)
		{
	#if 0   // trancode between raw data(yuv420p yuv422p rgb and so on) 
			// using sws_ctx take more time,so do it ourself

			sws_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
				pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BILINEAR, NULL, NULL, NULL);
				//pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);

			sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data, pFrame->linesize, 0,
					pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);
	#else
			memset(pFrameYUV->data[0],'\0',pCodecCtx->width*pCodecCtx->height);
			memset(pFrameYUV->data[1],'\0',pCodecCtx->width*pCodecCtx->height/4);
			memset(pFrameYUV->data[2],'\0',pCodecCtx->width*pCodecCtx->height/4);

			memcpy(pFrameYUV->data[0],pFrame->data[0],pCodecCtx->width*pCodecCtx->height);
			for(i=0;i<pCodecCtx->height;i++){
				if(i%2){
					memcpy(pFrameYUV->data[1]+i/2*pCodecCtx->width/2,pFrame->data[1]+i/2*2*pCodecCtx->width/2,pCodecCtx->width/2);
				}else{
					memcpy(pFrameYUV->data[2]+i/2*pCodecCtx->width/2,pFrame->data[2]+i/2*2*pCodecCtx->width/2,pCodecCtx->width/2);
				}	
			}
	#endif
			if(avcodec_encode_video2(enc_ctx,&dst_packet,pFrameYUV,&frameFinished)<0){
				perror("encode video ");
				return -1;
			}
			if(frameFinished)
			{
				cnt1++;
				int ret = fwrite(dst_packet.data,1,dst_packet.size, out);
				//fflush(out);
				dst_packet.data = NULL;
				dst_packet.size = 0;
			}
		}
	}
	/* get the delayed frames */
	for ( frameFinished= 1;frameFinished; i++) {
		//fflush(stdout);

			if(avcodec_encode_video2(enc_ctx,&dst_packet,NULL,&frameFinished)<0){
				perror("encode video ");
				return -1;
			}
			if(frameFinished)
			{
				cnt1++;
				int ret = fwrite(dst_packet.data,1,dst_packet.size, out);
				//fflush(out);
				dst_packet.data = NULL;
				dst_packet.size = 0;
			}
	}   
	//release resource
	av_free_packet(&packet);

	av_free(buffer);
	av_frame_free(&pFrameYUV);

	av_frame_free(&pFrame);

	avcodec_close(pCodecCtx);
	avcodec_close(pCodecCtxOrg);

	avformat_close_input(&pFormatCtx);
	printf("total=%d,ok=%d\n",cnt0,cnt1);
	fclose(out);
}

int main(int argc, char **argv)
{
	/* set current process priority low to let the app run smooth */
	if (setpriority(PRIO_PROCESS,getpid(), 19) <0)
	{
		perror("fail to setpriority");
		exit(-1);
	}


	if (argc < 2) {
		printf("usage: %s input_file\n"
				"transcode video from mjpeg  to h264 to save memory\n"
				"example: ./transcode 3.avi out.h264",
				argv[0]);
		return 1;
	}
	if(video_decode_example(argv[1], argv[2])<0){
		printf("transcode fail\n");
		return -1;
	}

	return 0;
}






  • 0
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值