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







發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章