基于am335x平台 mjpeg转码h264

来源:互联网 发布:sql数据库管理下载 编辑:程序博客网 时间:2024/05/19 17:57

简单介绍下:公司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 headerif (avformat_open_input(&pFormatCtx, filename, NULL, NULL) != 0){fprintf(stderr,"avformat_open_input");return;}//step 2:get stread infoif (avformat_find_stream_info(pFormatCtx, NULL) < 0){fprintf(stderr,"avformat_find_stream_info");return; }//just output format info of input fileav_dump_format(pFormatCtx, 0, filename, 0);int videoStream = -1;int i;//step 3:find vido streamfor ( 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  decoderpCodec = 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 codecif (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 framewhile (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 ourselfsws_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);#elsememset(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);}}#endifif(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 resourceav_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;}
原创粉丝点击