FFMPEG 编码YUYV 数据

《最简单的基于FFMPEG的视频编码器(YUV编码为H.264)》,它介绍的是使用FFMPEG将YUV420 数据编码成H.264数据。在它的博客中,没有介绍到YUYV数据格式的编码,但是我们在实际应用中,有时候摄像头只能输出YUYV数据格式,这样他的工程就不能使用了。经过一通折腾,终于在他的基础上实现了使用FFMPEG将YUYV编码成H264数据。

    因为YUYV数据是属于YUV422格式,而FFMPEG的编码器又不支持AV_PIX_FMT_YUYV422 编码格式。因此我们需要做的是将YUYV数据格式转换为YUV422P数据格式,然后再进行编码。因为YUYV转YUV422P比较简单,因此我这里没有调用FFMPEG的转换器而是直接转换了。这里需要注意一下,我使用的FFMPEG版本是ffmpeg-3.2.4 ,因为版本的更新,有些接口可能改变了,也正是因为这样,在调试的时候出现了不少的问题。代码如下:

[objc]  view plain  copy
  1. /*=============================================================================   
  2. #     FileName: ffmpeg_encoder.c   
  3. #         Desc: an example of ffmpeg fileter  
  4. #       Author: licaibiao   
  5. #   LastChange: 2017-03-18    
  6. =============================================================================*/  
  7.   
  8. #include <stdio.h>  
  9. #define __STDC_CONSTANT_MACROS  
  10.   
  11. #include <libavutil/opt.h>  
  12. #include <libavcodec/avcodec.h>  
  13. #include <libavformat/avformat.h>  
  14.   
  15. //#define ENCODE_YUV  
  16.   
  17. #ifdef ENCODE_YUV  
  18. #define INPUT_FILE      "yuv_512_288.yuv"  
  19. #define INPUT_WIDTH     512  
  20. #define INPUT_HEIGHT    288  
  21. #else  
  22. #define INPUT_FILE      "yuyv_640_480.yuyv"  
  23. #define INPUT_WIDTH     640  
  24. #define INPUT_HEIGHT    480  
  25. #endif  
  26.   
  27. #define OUTPUT_FILE     "ds.h264"  
  28. #define FRAMENUM        300  
  29.   
  30.   
  31. int flush_encoder(AVFormatContext *fmt_ctx,unsigned int stream_index){  
  32.     int ret;  
  33.     int got_frame;  
  34.     AVPacket enc_pkt;  
  35.     if (!(fmt_ctx->streams[stream_index]->codec->codec->capabilities &  
  36.         CODEC_CAP_DELAY))  
  37.         return 0;  
  38.     while (1) {  
  39.         enc_pkt.data = NULL;  
  40.         enc_pkt.size = 0;  
  41.         av_init_packet(&enc_pkt);  
  42.         ret = avcodec_encode_video2 (fmt_ctx->streams[stream_index]->codec, &enc_pkt,  
  43.             NULL, &got_frame);  
  44.         av_frame_free(NULL);  
  45.         if (ret < 0)  
  46.             break;  
  47.         if (!got_frame){  
  48.             ret=0;  
  49.             break;  
  50.         }  
  51.         printf("Flush Encoder: Succeed to encode 1 frame!\tsize:%5d\n",enc_pkt.size);  
  52.         /* mux encoded frame */  
  53.         ret = av_write_frame(fmt_ctx, &enc_pkt);  
  54.         if (ret < 0)  
  55.             break;  
  56.     }  
  57.     return ret;  
  58. }  
  59.   
  60. int main(int argc, char* argv[])  
  61. {  
  62.     AVFormatContext* pFormatCtx;  
  63.     AVOutputFormat* fmt;  
  64.     AVStream* video_st;  
  65.     AVCodecContext* pCodecCtx;  
  66.     AVCodec* pCodec;  
  67.     AVPacket pkt;  
  68.     uint8_t* picture_buf;  
  69.     AVFrame* pFrame;  
  70.     int picture_size;  
  71.     int y_size;  
  72.     int framecnt=0;  
  73.     FILEFILE *in_file ;   //Input raw YUV data  
  74.     int in_w=INPUT_WIDTH, in_h=INPUT_HEIGHT;   //Input data's width and height  
  75.     int framenum=FRAMENUM;                     //Frames to encode  
  76.     const char* out_file = OUTPUT_FILE;  
  77.   
  78.     int i,j,k;  
  79.     int num;  
  80.     int index_y, index_u, index_v;   
  81.     uint8_t *y_, *u_, *v_, *in;  
  82.   
  83.     int got_picture =0;  
  84.     int ret;  
  85.   
  86.   
  87.     in_file =  fopen(INPUT_FILE, "rb");   //Input raw YUV data  
  88.     av_register_all();  
  89.     //Method1.  
  90.     pFormatCtx = avformat_alloc_context();  
  91.     //Guess Format  
  92.     fmt = av_guess_format(NULL, out_file, NULL);  
  93.     pFormatCtx->oformat = fmt;  
  94.       
  95.     //Method 2.  
  96.     /* 初始化输出码流的AVFormatContext */  
  97.     //avformat_alloc_output_context2(&pFormatCtx, NULL, NULL, out_file);  
  98.     //fmt = pFormatCtx->oformat;  
  99.     //Open output URL  
  100.     if (avio_open(&pFormatCtx->pb,out_file, AVIO_FLAG_READ_WRITE) < 0){  
  101.         printf("Failed to open output file! \n");  
  102.         return -1;  
  103.     }  
  104.   
  105.     /* 创建输出码流的AVStream */  
  106.     video_st = avformat_new_stream(pFormatCtx, 0);  
  107.     //video_st->time_base.num = 1;   
  108.     //video_st->time_base.den = 25;    
  109.   
  110.     if (video_st==NULL){  
  111.         return -1;  
  112.     }  
  113.     //Param that must set  
  114.     pCodecCtx = video_st->codec;  
  115.     //pCodecCtx->codec_id =AV_CODEC_ID_HEVC;  
  116.     pCodecCtx->codec_id = fmt->video_codec;  
  117.     pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;  
  118. #ifdef ENCODE_YUV  
  119.     pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;  
  120. #else  
  121.     pCodecCtx->pix_fmt = AV_PIX_FMT_YUV422P;  
  122. #endif  
  123.     pCodecCtx->width = in_w;    
  124.     pCodecCtx->height = in_h;  
  125.     pCodecCtx->bit_rate = 2000000;    
  126.     pCodecCtx->gop_size=10;  
  127.   
  128.     pCodecCtx->time_base.num = 1;    
  129.     pCodecCtx->time_base.den = 25;    
  130.   
  131.     //H264  
  132.     //pCodecCtx->me_range = 16;  
  133.     //pCodecCtx->max_qdiff = 4;  
  134.     //pCodecCtx->qcompress = 0.6;  
  135.     pCodecCtx->qmin = 10;  
  136.     pCodecCtx->qmax = 51;  
  137.   
  138.     //Optional Param  
  139.     pCodecCtx->max_b_frames=3;  
  140.   
  141.     // Set Option  
  142.     AVDictionary *param = 0;  
  143.     //H.264  
  144.     if(pCodecCtx->codec_id == AV_CODEC_ID_H264) {  
  145.         av_dict_set(¶m, "preset""slow"0);  
  146.         /* 这个可以让libav不缓存视频帧 */  
  147.         av_dict_set(¶m, "tune""zerolatency"0);  
  148.         //av_dict_set(¶m, "profile", "main", 0);  
  149.     }  
  150.       
  151.     //Show some Information  
  152.     av_dump_format(pFormatCtx, 0, out_file, 1);  
  153.   
  154.     /* 查找编码器 */  
  155.     pCodec = avcodec_find_encoder(pCodecCtx->codec_id);  
  156.     if (!pCodec){  
  157.         printf("Can not find encoder! \n");  
  158.         return -1;  
  159.     }  
  160.   
  161.     /* 打开编码器 */  
  162.     if (avcodec_open2(pCodecCtx, pCodec,¶m) < 0){  
  163.         printf("Failed to open encoder! \n");  
  164.         return -1;  
  165.     }  
  166.   
  167.     pFrame = av_frame_alloc();  
  168.     picture_size = avpicture_get_size(pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);  
  169.     picture_buf = (uint8_t *)av_malloc(picture_size);  
  170.       
  171. #ifdef ENCODE_YUV  
  172.     avpicture_fill((AVPicture *)pFrame, picture_buf, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);  
  173. #else  
  174.     pFrame->format = pCodecCtx->pix_fmt;   
  175.     pFrame->width  = pCodecCtx->width;      
  176.     pFrame->height = pCodecCtx->height;    
  177.     av_image_alloc(pFrame->data, pFrame->linesize, pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, 32);   
  178. #endif  
  179.   
  180.     //Write File Header  
  181.     /* 写文件头(对于某些没有文件头的封装格式,不需要此函数。比如说MPEG2TS) */  
  182.     avformat_write_header(pFormatCtx,NULL);  
  183.   
  184.     /* Allocate the payload of a packet and initialize its fields with default values.  */  
  185.     av_new_packet(&pkt,picture_size);  
  186.   
  187.     y_size = pCodecCtx->width * pCodecCtx->height;  
  188.     for (j=0; j< framenum; j++){  
  189.         //Read raw YUV data  
  190. #ifdef ENCODE_YUV  
  191.         if (fread(picture_buf, 1, y_size*3/2, in_file) <= 0){  
  192. #else  
  193.         if (fread(picture_buf, 1, y_size*2, in_file) <= 0){  
  194. #endif  
  195.             printf("Failed to read raw data! \n");  
  196.             return -1;  
  197.         }else if(feof(in_file)){  
  198.             break;  
  199.         }  
  200.           
  201. #ifdef ENCODE_YUV  
  202.         pFrame->data[0] = picture_buf;          // Y  
  203.         pFrame->data[1] = picture_buf + y_size;      // U   
  204.         pFrame->data[2] = picture_buf + y_size*5/4;  // V  
  205. #else  
  206.         num = y_size * 2 - 4;  
  207.         index_y = 0;  
  208.         index_u = 0;  
  209.         index_v = 0;   
  210.         y_ = pFrame->data[0];       
  211.         u_ = pFrame->data[1];       
  212.         v_ = pFrame->data[2];   
  213.         in = picture_buf;  
  214.            
  215.         for(i=0; i<num; i=i+4)  
  216.         {  
  217.             *(y_ + (index_y++)) = *(in + i);    
  218.             *(u_ + (index_u++)) = *(in + i + 1);   
  219.             *(y_ + (index_y++)) = *(in + i + 2);   
  220.             *(v_ + (index_v++)) = *(in + i + 3);   
  221.         }  
  222. #endif    
  223.         //PTS  
  224.         //pFrame->pts=i;  
  225.         pFrame->pts=j*(video_st->time_base.den)/((video_st->time_base.num)*25);  
  226.   
  227.         //Encode  
  228.         /* 编码一帧视频。即将AVFrame(存储YUV像素数据)编码为AVPacket(存储H.264等格式的码流数据) */  
  229.         int ret = avcodec_encode_video2(pCodecCtx, &pkt,pFrame, &got_picture);  
  230.         if(ret < 0){  
  231.             printf("Failed to encode! \n");  
  232.             return -1;  
  233.         }  
  234.         if (got_picture==1){  
  235.             //printf("Succeed to encode frame: %5d\tsize:%5d\n",framecnt,pkt.size);  
  236.             framecnt++;  
  237.             pkt.stream_index = video_st->index;  
  238.               
  239.             /* 将编码后的视频码流写入文件 */  
  240.             ret = av_write_frame(pFormatCtx, &pkt);  
  241.   
  242.             av_free_packet(&pkt);  
  243.         }  
  244.     }  
  245.     //Flush Encoder  
  246.     /* 输入的像素数据读取完成后调用此函数。用于输出编码器中剩余的AVPacket */  
  247.     ret = flush_encoder(pFormatCtx,0);  
  248.     if (ret < 0) {  
  249.         printf("Flushing encoder failed\n");  
  250.         return -1;  
  251.     }  
  252.   
  253.     //Write file trailer  
  254.     /* 写文件尾(对于某些没有文件头的封装格式,不需要此函数。比如说MPEG2TS) */  
  255.     av_write_trailer(pFormatCtx);  
  256.   
  257.     //Clean  
  258.     if (video_st){  
  259.         avcodec_close(video_st->codec);  
  260.         av_free(pFrame);  
  261.         av_free(picture_buf);  
  262.     }  
  263.     avio_close(pFormatCtx->pb);  
  264.     avformat_free_context(pFormatCtx);  
  265.   
  266.     fclose(in_file);  
  267.   
  268.     return 0;  
  269. }  

    Makefile文件书写如下:

[objc]  view plain  copy
  1. OUT_APP      = test  
  2. INCLUDE_PATH = /usr/local/include/  
  3. INCLUDE = -I$(INCLUDE_PATH)libavutil/ -I$(INCLUDE_PATH)libavdevice/ \  
  4.             -I$(INCLUDE_PATH)libavcodec/ -I$(INCLUDE_PATH)libswresample \  
  5.             -I$(INCLUDE_PATH)libavfilter/ -I$(INCLUDE_PATH)libavformat \  
  6.             -I$(INCLUDE_PATH)libswscale/  
  7.   
  8. FFMPEG_LIBS = -lavformat -lavutil -lavdevice -lavcodec -lswresample -lavfilter -lswscale  
  9. SDL_LIBS    =   
  10. LIBS        = $(FFMPEG_LIBS)$(SDL_LIBS)  
  11.   
  12. COMPILE_OPTS = $(INCLUDE)  
  13. C            = c  
  14. OBJ          = o  
  15. C_COMPILER   = cc  
  16. C_FLAGS      = $(COMPILE_OPTS) $(CPPFLAGS) $(CFLAGS)  
  17.   
  18. LINK         = cc -o   
  19. LINK_OPTS    = -lz -lm  -lpthread  
  20. LINK_OBJ     = test.o   
  21.   
  22. .$(C).$(OBJ):  
  23.     $(C_COMPILER) -c -g $(C_FLAGS) $<  
  24.   
  25.   
  26. $(OUT_APP): $(LINK_OBJ)  
  27.     $(LINK)$@  $(LINK_OBJ)  $(LIBS) $(LINK_OPTS)  
  28.   
  29. clean:  
  30.     rm -rf *.$(OBJ) $(OUT_APP) core *.core *~  *.h264 yuyv.yuyv  
    编译运行结果如下:

[objc]  view plain  copy
  1. licaibiao@ubuntu:~/test/FFMPEG/encoder$ ls  
  2. Makefile  test  test.c  test.o  yuv_512_288.yuv  yuyv_640_480.yuyv  
  3. licaibiao@ubuntu:~/test/FFMPEG/encoder$ ./test   
  4. Output #0, h264, to 'ds.h264':  
  5.     Stream #0:0: Unknown: none  
  6. [libx264 @ 0xbe7140using cpu capabilities: none!  
  7. [libx264 @ 0xbe7140] profile High 4:2:2, level 3.0, 4:2:2 8-bit  
  8. [h264 @ 0xbde240] Using AVStream.codec.time_base as a timebase hint to the muxer is deprecated. Set AVStream.time_base instead.  
  9. [h264 @ 0xbde240] Using AVStream.codec to pass codec parameters to muxers is deprecated, use AVStream.codecpar instead.  
  10. Flush Encoder: Succeed to encode 1 frame!       size3131  
  11. Flush Encoder: Succeed to encode 1 frame!       size5618  
  12. Flush Encoder: Succeed to encode 1 frame!       size:16635  
  13. [libx264 @ 0xbe7140] frame I:30    Avg QP:18.56  size31571  
  14. [libx264 @ 0xbe7140] frame P:61    Avg QP:19.89  size15100  
  15. [libx264 @ 0xbe7140] frame B:209   Avg QP:21.10  size:  5299  
  16. [libx264 @ 0xbe7140] consecutive B-frames:  0.719.3%  0.080.0%  
  17. [libx264 @ 0xbe7140] mb I  I16..414.040.445.6%  
  18. [libx264 @ 0xbe7140] mb P  I16..4:  8.715.4%  9.8%  P16..436.215.1%  5.2%  0.0%  0.0%    skip9.6%  
  19. [libx264 @ 0xbe7140] mb B  I16..4:  1.0%  4.0%  1.7%  B16..827.6%  9.3%  1.6 direct:10.3 skip:44.5 L0:41.6% L1:42.2% BI:16.2%  
  20. [libx264 @ 0xbe7140] final ratefactor17.64  
  21. [libx264 @ 0xbe71408x8 transform intra:46.1% inter:53.3%  
  22. [libx264 @ 0xbe7140] direct mvs  spatial:99.5% temporal:0.5%  
  23. [libx264 @ 0xbe7140] coded y,uvDC,uvAC intra74.588.970.6% inter14.634.05.8%  
  24. [libx264 @ 0xbe7140] i16 v,h,dc,p: 48%  9%  242%  
  25. [libx264 @ 0xbe7140] i8 v,h,dc,ddl,ddr,vr,hd,vl,hu: 1213%  6111113101211%  
  26. [libx264 @ 0xbe7140] i4 v,h,dc,ddl,ddr,vr,hd,vl,hu: 1221%  6%  9141310%  9%  7%  
  27. [libx264 @ 0xbe7140] i8c dc,h,v,p: 41242213%  
  28. [libx264 @ 0xbe7140] Weighted P-Frames: Y:41.0% UV:19.7%  
  29. [libx264 @ 0xbe7140] kb/s:1983.80  
  30. licaibiao@ubuntu:~/test/FFMPEG/encoder$ ll  
  31. total 413032  
  32. drwxrwxr-x  2 licaibiao licaibiao      4096 Mar 18 04:24 ./  
  33. drwxrwxr-x 10 licaibiao licaibiao      4096 Mar 17 03:46 ../  
  34. -rw-rw-r--  1 licaibiao licaibiao   2975705 Mar 18 04:24 ds.h264  
  35. -rwxrwxr-x  1 licaibiao licaibiao       800 Mar 18 01:40 Makefile*  
  36. -rwxrwxr-x  1 licaibiao licaibiao     61800 Mar 18 04:01 test*  
  37. -rw-rw-r--  1 licaibiao licaibiao      7131 Mar 18 04:22 test.c  
  38. -rw-rw-r--  1 licaibiao licaibiao     95184 Mar 18 04:01 test.o  
  39. -rw-r--r--  1 licaibiao licaibiao 112582656 Mar 16 18:35 yuv_512_288.yuv  
  40. -rw-r--r--  1 licaibiao licaibiao 307200000 Mar 17 04:25 yuyv_640_480.yuyv  
  41. licaibiao@ubuntu:~/test/FFMPEG/encoder$   
    生成了ds.h264文件,使用VLC播放器播放:


     
    这里需要特别注意:在雷神的例子中,他是这样初始化frame的: 
    avpicture_fill((AVPicture *)pFrame, picture_buf, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);
    按照他的例子设置,编码YUV420P数据是没有问题,但是当编码YUV422P的时候,它就会出现花屏的问题,下面的这张图。




    出现这样的原因是,在它的工程中没有设置帧的帧类型,默认它是按YUV420P分配空间的。因为YUYV会比YUV420P多出1/4的数据,所以会造成有1/4的数据是异常的。我将他的例子该部分修改如下:
[objc]  view plain  copy
  1. pFrame->format = pCodecCtx->pix_fmt;   
  2. pFrame->width  = pCodecCtx->width;      
  3. pFrame->height = pCodecCtx->height;    
  4. av_image_alloc(pFrame->data, pFrame->linesize, pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, 32);   
    在我的例子中,有下面的代码,它的作用就是讲YUYV数据格式转换成YUV422P格式然后在填充到编码器中去,具体的装换算法在以前的博客中已经有介绍。
[objc]  view plain  copy
  1.         
  2. for(i=0; i<num; i=i+4)  
  3. {  
  4.     *(y_ + (index_y++)) = *(in + i);    
  5.     *(u_ + (index_u++)) = *(in + i + 1);   
  6.     *(y_ + (index_y++)) = *(in + i + 2);   
  7.     *(v_ + (index_v++)) = *(in + i + 3);   
  8. }  

猜你喜欢

转载自blog.csdn.net/u011426247/article/details/79736212