最简单的FFMPEG的视频编码器

来源:互联网 发布:windows live下载 编辑:程序博客网 时间:2024/03/28 17:58

本文介绍一个最简单的基于FFMPEG的视频编码器。该编码器实现了YUV420P的像素数据编码为H.264的压缩编码数据。编码器代码十分简单,但是每一行代码都很重要,适合好好研究一下。弄清楚了本代码也就基本弄清楚了FFMPEG的编码流程。目前我虽然已经调通了程序,但是还是有些地方没有完全搞明白,需要下一步继续探究然后补充内容。

本程序使用最新版的类库(编译时间为2014.5.6),开发平台为VC2010。所有的配置都已经做好,只需要运行就可以了。


流程

下面附一张使用FFmpeg编码视频的流程图。使用该流程,不仅可以编码H.264的视频,而且可以编码MPEG4/MPEG2/VP8等等各种FFmpeg支持的视频。图中蓝色背景的函数是实际输出数据的函数。浅绿色的函数是视频编码的函数。


简单介绍一下流程中各个函数的意义:

av_register_all():注册FFmpeg所有编解码器。

avformat_alloc_output_context2():初始化输出码流的AVFormatContext。

avio_open():打开输出文件。

av_new_stream():创建输出码流的AVStream。

avcodec_find_encoder():查找编码器。

avcodec_open2():打开编码器。

avformat_write_header():写文件头(对于某些没有文件头的封装格式,不需要此函数。比如说MPEG2TS)。

avcodec_encode_video2():编码一帧视频。即将AVFrame(存储YUV像素数据)编码为AVPacket(存储H.264等格式的码流数据)。

av_write_frame():将编码后的视频码流写入文件。

flush_encoder():输入的像素数据读取完成后调用此函数。用于输出编码器中剩余的AVPacket。

av_write_trailer():写文件尾(对于某些没有文件头的封装格式,不需要此函数。比如说MPEG2TS)。


代码

 

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * 最简单的基于FFmpeg的视频编码器 
  3.  * Simplest FFmpeg Video Encoder 
  4.  *  
  5.  * 雷霄骅 Lei Xiaohua 
  6.  * leixiaohua1020@126.com 
  7.  * 中国传媒大学/数字电视技术 
  8.  * Communication University of China / Digital TV Technology 
  9.  * http://blog.csdn.net/leixiaohua1020 
  10.  *  
  11.  * 本程序实现了YUV像素数据编码为视频码流(H264,MPEG2,VP8等等)。 
  12.  * 是最简单的FFmpeg视频编码方面的教程。 
  13.  * 通过学习本例子可以了解FFmpeg的编码流程。 
  14.  * This software encode YUV420P data to H.264 bitstream. 
  15.  * It's the simplest video encoding software based on FFmpeg.  
  16.  * Suitable for beginner of FFmpeg  
  17.  */  
  18.   
  19. #include <stdio.h>  
  20.   
  21. #define __STDC_CONSTANT_MACROS  
  22.   
  23. #ifdef _WIN32  
  24. //Windows  
  25. extern "C"  
  26. {  
  27. #include "libavutil/opt.h"  
  28. #include "libavcodec/avcodec.h"  
  29. #include "libavformat/avformat.h"  
  30. };  
  31. #else  
  32. //Linux...  
  33. #ifdef __cplusplus  
  34. extern "C"  
  35. {  
  36. #endif  
  37. #include <libavutil/opt.h>  
  38. #include <libavcodec/avcodec.h>  
  39. #include <libavformat/avformat.h>  
  40. #ifdef __cplusplus  
  41. };  
  42. #endif  
  43. #endif  
  44.   
  45.   
  46. int flush_encoder(AVFormatContext *fmt_ctx,unsigned int stream_index){  
  47.     int ret;  
  48.     int got_frame;  
  49.     AVPacket enc_pkt;  
  50.     if (!(fmt_ctx->streams[stream_index]->codec->codec->capabilities &  
  51.         CODEC_CAP_DELAY))  
  52.         return 0;  
  53.     while (1) {  
  54.         enc_pkt.data = NULL;  
  55.         enc_pkt.size = 0;  
  56.         av_init_packet(&enc_pkt);  
  57.         ret = avcodec_encode_video2 (fmt_ctx->streams[stream_index]->codec, &enc_pkt,  
  58.             NULL, &got_frame);  
  59.         av_frame_free(NULL);  
  60.         if (ret < 0)  
  61.             break;  
  62.         if (!got_frame){  
  63.             ret=0;  
  64.             break;  
  65.         }  
  66.         printf("Flush Encoder: Succeed to encode 1 frame!\tsize:%5d\n",enc_pkt.size);  
  67.         /* mux encoded frame */  
  68.         ret = av_write_frame(fmt_ctx, &enc_pkt);  
  69.         if (ret < 0)  
  70.             break;  
  71.     }  
  72.     return ret;  
  73. }  
  74.   
  75. int main(int argc, char* argv[])  
  76. {  
  77.     AVFormatContext* pFormatCtx;  
  78.     AVOutputFormat* fmt;  
  79.     AVStream* video_st;  
  80.     AVCodecContext* pCodecCtx;  
  81.     AVCodec* pCodec;  
  82.     AVPacket pkt;  
  83.     uint8_t* picture_buf;  
  84.     AVFrame* pFrame;  
  85.     int picture_size;  
  86.     int y_size;  
  87.     int framecnt=0;  
  88.     FILE *in_file = fopen("src01_480x272.yuv""rb");   //Input raw YUV data   
  89.     //FILE *in_file = fopen("../ds_480x272.yuv", "rb");   //Input raw YUV data  
  90.     int in_w=480,in_h=272;                              //Input data's width and height  
  91.     int framenum=100;                                   //Frames to encode  
  92.     const char* out_file = "src01.h264";              //Output Filepath   
  93.     //const char* out_file = "src01.ts";  
  94.     //const char* out_file = "src01.hevc";  
  95.     //const char* out_file = "ds.h264";  
  96.   
  97.     av_register_all();  
  98.     //Method1.  
  99.     pFormatCtx = avformat_alloc_context();  
  100.     //Guess Format  
  101.     fmt = av_guess_format(NULL, out_file, NULL);  
  102.     pFormatCtx->oformat = fmt;  
  103.       
  104.     //Method 2.  
  105.     //avformat_alloc_output_context2(&pFormatCtx, NULL, NULL, out_file);  
  106.     //fmt = pFormatCtx->oformat;  
  107.   
  108.   
  109.     //Open output URL  
  110.     if (avio_open(&pFormatCtx->pb,out_file, AVIO_FLAG_READ_WRITE) < 0){  
  111.         printf("Failed to open output file! \n");  
  112.         return -1;  
  113.     }  
  114.   
  115.     video_st = avformat_new_stream(pFormatCtx, 0);  
  116.     video_st->time_base.num = 1;   
  117.     video_st->time_base.den = 25;    
  118.   
  119.     if (video_st==NULL){  
  120.         return -1;  
  121.     }  
  122.     //Param that must set  
  123.     pCodecCtx = video_st->codec;  
  124.     //pCodecCtx->codec_id =AV_CODEC_ID_HEVC;  
  125.     pCodecCtx->codec_id = fmt->video_codec;  
  126.     pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;  
  127.     pCodecCtx->pix_fmt = PIX_FMT_YUV420P;  
  128.     pCodecCtx->width = in_w;    
  129.     pCodecCtx->height = in_h;  
  130.     pCodecCtx->time_base.num = 1;    
  131.     pCodecCtx->time_base.den = 25;    
  132.     pCodecCtx->bit_rate = 400000;    
  133.     pCodecCtx->gop_size=250;  
  134.     //H264  
  135.     //pCodecCtx->me_range = 16;  
  136.     //pCodecCtx->max_qdiff = 4;  
  137.     //pCodecCtx->qcompress = 0.6;  
  138.     pCodecCtx->qmin = 10;  
  139.     pCodecCtx->qmax = 51;  
  140.   
  141.     //Optional Param  
  142.     pCodecCtx->max_b_frames=3;  
  143.   
  144.     // Set Option  
  145.     AVDictionary *param = 0;  
  146.     //H.264  
  147.     if(pCodecCtx->codec_id == AV_CODEC_ID_H264) {  
  148.         av_dict_set(¶m, "preset""slow", 0);  
  149.         av_dict_set(¶m, "tune""zerolatency", 0);  
  150.         //av_dict_set(¶m, "profile", "main", 0);  
  151.     }  
  152.     //H.265  
  153.     if(pCodecCtx->codec_id == AV_CODEC_ID_H265){  
  154.         av_dict_set(¶m, "preset""ultrafast", 0);  
  155.         av_dict_set(¶m, "tune""zero-latency", 0);  
  156.     }  
  157.   
  158.     //Show some Information  
  159.     av_dump_format(pFormatCtx, 0, out_file, 1);  
  160.   
  161.     pCodec = avcodec_find_encoder(pCodecCtx->codec_id);  
  162.     if (!pCodec){  
  163.         printf("Can not find encoder! \n");  
  164.         return -1;  
  165.     }  
  166.     if (avcodec_open2(pCodecCtx, pCodec,¶m) < 0){  
  167.         printf("Failed to open encoder! \n");  
  168.         return -1;  
  169.     }  
  170.   
  171.   
  172.     pFrame = av_frame_alloc();  
  173.     picture_size = avpicture_get_size(pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);  
  174.     picture_buf = (uint8_t *)av_malloc(picture_size);  
  175.     avpicture_fill((AVPicture *)pFrame, picture_buf, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);  
  176.   
  177.     //Write File Header  
  178.     avformat_write_header(pFormatCtx,NULL);  
  179.   
  180.     av_new_packet(&pkt,picture_size);  
  181.   
  182.     y_size = pCodecCtx->width * pCodecCtx->height;  
  183.   
  184.     for (int i=0; i<framenum; i++){  
  185.         //Read raw YUV data  
  186.         if (fread(picture_buf, 1, y_size*3/2, in_file) <= 0){  
  187.             printf("Failed to read raw data! \n");  
  188.             return -1;  
  189.         }else if(feof(in_file)){  
  190.             break;  
  191.         }  
  192.         pFrame->data[0] = picture_buf;              // Y  
  193.         pFrame->data[1] = picture_buf+ y_size;      // U   
  194.         pFrame->data[2] = picture_buf+ y_size*5/4;  // V  
  195.         //PTS  
  196.         pFrame->pts=i;  
  197.         int got_picture=0;  
  198.         //Encode  
  199.         int ret = avcodec_encode_video2(pCodecCtx, &pkt,pFrame, &got_picture);  
  200.         if(ret < 0){  
  201.             printf("Failed to encode! \n");  
  202.             return -1;  
  203.         }  
  204.         if (got_picture==1){  
  205.             printf("Succeed to encode frame: %5d\tsize:%5d\n",framecnt,pkt.size);  
  206.             framecnt++;  
  207.             pkt.stream_index = video_st->index;  
  208.             ret = av_write_frame(pFormatCtx, &pkt);  
  209.             av_free_packet(&pkt);  
  210.         }  
  211.     }  
  212.     //Flush Encoder  
  213.     int ret = flush_encoder(pFormatCtx,0);  
  214.     if (ret < 0) {  
  215.         printf("Flushing encoder failed\n");  
  216.         return -1;  
  217.     }  
  218.   
  219.     //Write file trailer  
  220.     av_write_trailer(pFormatCtx);  
  221.   
  222.     //Clean  
  223.     if (video_st){  
  224.         avcodec_close(video_st->codec);  
  225.         av_free(pFrame);  
  226.         av_free(picture_buf);  
  227.     }  
  228.     avio_close(pFormatCtx->pb);  
  229.     avformat_free_context(pFormatCtx);  
  230.   
  231.     fclose(in_file);  
  232.   
  233.     return 0;  
  234. }  


结果

软件运行截图(受限于文件体积,原始YUV帧数很少):


编码前的YUV序列:


编码后的H.264码流:



下载


Simplest FFmpeg Video Encoder


项目主页

SourceForge:https://sourceforge.net/projects/simplestffmpegvideoencoder/

Github:https://github.com/leixiaohua1020/simplest_ffmpeg_video_encoder

开源中国:http://git.oschina.net/leixiaohua1020/simplest_ffmpeg_video_encoder


下载地址:

http://download.csdn.net/detail/leixiaohua1020/7324115

【修正】之前发现编码后的H.264码流与YUV输入的帧数不同。经过观察对比其他程序后发现需要调用flush_encoder()将编码器中剩余的视频帧输出。已经将该问题修正。

CSDN下载地址(修正后):

http://download.csdn.net/detail/leixiaohua1020/7466649

PUDN下载地址(修正后):

http://www.pudn.com/downloads644/sourcecode/multimedia/detail2605258.html

SourceForge上已经更新。


更新-1.1 (2015.1.03)=========================================

增加了《最简单的基于FFmpeg的编码器-纯净版(不包含libavformat)》中的simplest_ffmpeg_video_encoder_pure工程。

CSDN下载地址:http://download.csdn.net/detail/leixiaohua1020/8322003


更新-1.2 (2015.2.13)=========================================

这次考虑到了跨平台的要求,调整了源代码。经过这次调整之后,源代码可以在以下平台编译通过:

VC++:打开sln文件即可编译,无需配置。

cl.exe:打开compile_cl.bat即可命令行下使用cl.exe进行编译,注意可能需要按照VC的安装路径调整脚本里面的参数。编译命令如下。

[plain] view plaincopy在CODE上查看代码片派生到我的代码片
  1. ::VS2010 Environment  
  2. call "D:\Program Files\Microsoft Visual Studio 10.0\VC\vcvarsall.bat"  
  3. ::include  
  4. @set INCLUDE=include;%INCLUDE%  
  5. ::lib  
  6. @set LIB=lib;%LIB%  
  7. ::compile and link  
  8. cl simplest_ffmpeg_video_encoder.cpp /link avcodec.lib avformat.lib avutil.lib ^  
  9. avdevice.lib avfilter.lib postproc.lib swresample.lib swscale.lib /OPT:NOREF  

MinGW:MinGW命令行下运行compile_mingw.sh即可使用MinGW的g++进行编译。编译命令如下。

[plain] view plaincopy在CODE上查看代码片派生到我的代码片
  1. g++ simplest_ffmpeg_video_encoder.cpp -g -o simplest_ffmpeg_video_encoder.exe \  
  2. -I /usr/local/include -L /usr/local/lib \  
  3. -lavformat -lavcodec -lavutil  

GCC:Linux或者MacOS命令行下运行compile_gcc.sh即可使用GCC进行编译。编译命令如下。

[plain] view plaincopy在CODE上查看代码片派生到我的代码片
  1. gcc simplest_ffmpeg_video_encoder.cpp -g -o simplest_ffmpeg_video_encoder.out \  
  2. -I /usr/local/include -L /usr/local/lib -lavformat -lavcodec -lavutil  

PS:相关的编译命令已经保存到了工程文件夹中


CSDN下载地址:http://download.csdn.net/detail/leixiaohua1020/8444967

SourceForge上已经更新。


感谢雷博士分享,原文地址:http://blog.csdn.net/leixiaohua1020/article/details/25430425

0 0
原创粉丝点击