ffmpeg视频编码

打印 上一主题 下一主题

主题 851|帖子 851|积分 2553

一、视频编码流程

使用ffmpeg解码视频帧主要可分为两大步骤:初始化编码器编码视频帧,以下代码以h264为例
1. 初始化编码器

初始化编码器包含以下步骤:
(1)查找编码器
  1. videoCodec = avcodec_find_encoder_by_name(videoCodecName);
  2. if (!videoCodec) {
  3.     release();
  4.     return false;
  5. }
复制代码
(2)设置编码器上下文参数
  1. pCodecCtx = avcodec_alloc_context3(videoCodec);
  2. pCodecCtx->time_base = { 1, m_fps }; // 设置编码器上下文的时间基准
  3. pCodecCtx->framerate = { m_fps, 1 };
  4. pCodecCtx->bit_rate = videoBitrate;
  5. pCodecCtx->gop_size = 25;//影响视频的压缩效率、随机访问性能以及编码复杂度,对视频质量、文件大小和编码/解码性能也有影响
  6. pCodecCtx->width = in_w;
  7. pCodecCtx->height = in_h;
  8. pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;// AV_PIX_FMT_NV12,AV_PIX_FMT_YUV420P
  9. pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
  10. av_opt_set(pCodecCtx->priv_data, "preset", "superfast", 0); // 设置编码速度
  11. // 以下为根据对应的编码格式设置相关的参数
  12. if (pCodecCtx->codec_id == AV_CODEC_ID_H264)
  13. {
  14.     pCodecCtx->keyint_min = m_fps;
  15.     av_opt_set(pCodecCtx->priv_data, "profile", "main", 0);
  16.     av_opt_set(pCodecCtx->priv_data, "b-pyramid", "none", 0);
  17.     av_opt_set(pCodecCtx->priv_data, "tune", "zerolatency", 0);
  18. }
  19. else if (pCodecCtx->codec_id == AV_CODEC_ID_MPEG2VIDEO)
  20.     pCodecCtx->max_b_frames = 2;
  21. else if (pCodecCtx->codec_id == AV_CODEC_ID_MPEG1VIDEO)
  22.     pCodecCtx->mb_decision = 2;
复制代码
(3)打开编码器
  1. if (avcodec_open2(pCodecCtx, videoCodec, NULL) < 0) // 将编码器上下文和编码器进行关联
  2. {
  3.     release();
  4.     return false;
  5. }
复制代码
(4)设置图像帧参数
  1. // 初始化编码帧
  2. in_frame = av_frame_alloc();
  3. if (!in_frame) {
  4.     return false;
  5. }
  6. // 设置 AVFrame 的其他属性
  7. in_frame->width = pCodecCtx->width;
  8. in_frame->height = pCodecCtx->height;
  9. in_frame->format = pCodecCtx->pix_fmt;
  10. // 计算所需缓冲区大小
  11. int size = av_image_get_buffer_size(pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, 1);
  12. in_frame_buf = (uint8_t*)av_malloc(size); // 分配图像帧内存空间
  13. // 填充 AVFrame
  14. av_image_fill_arrays(in_frame->data, in_frame->linesize, in_frame_buf, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, 1);
复制代码
(5)创建格式转换上下文
ffmpeg的libx264编码器只支持输入格式为P,如果输入格式不是YUV420P,则需要转换
  1. // 创建缩放上下文,图像缩放和格式转换上下文
  2. if (videoSrcFormat != AV_PIX_FMT_YUV420P) {
  3.     sws_ctx = sws_getContext(in_w, in_h, videoSrcFormat,
  4.         in_w, in_h, AV_PIX_FMT_YUV420P,
  5.         SWS_BILINEAR, NULL, NULL, NULL);
  6.     if (!sws_ctx) {
  7.         release();
  8.         return false;
  9.     }
  10. }
复制代码
(6)封装设置和打开文件
如果要将编码后数据举行封装(如封装成mp4文件),则需要使用AVFormatContext,并设置视频流
  1. // 打开格式上下文
  2. if (avformat_alloc_output_context2(&pFormatCtx, NULL, NULL, mp4_file) < 0) {
  3.     release();
  4.     return false;
  5. }
  6.   // 初始化视频码流
  7. video_st = avformat_new_stream(pFormatCtx, pCodecCtx->codec);
  8. if (!video_st) {
  9.     std::cout << "avformat_new_stream error" << endl;
  10.     return false;
  11. }
  12. fmt = pFormatCtx->oformat;
  13. video_st->id = pFormatCtx->nb_streams - 1;
  14. avcodec_parameters_from_context(video_st->codecpar, pCodecCtx);
  15. pFormatCtx->video_codec_id = pFormatCtx->oformat->video_codec;
  16. // 打开文件
  17. if (avio_open(&pFormatCtx->pb, mp4_file, AVIO_FLAG_READ_WRITE) < 0) {
  18.     //std::cout << "output file open fail!" << endl;
  19.     swprintf(buf, 1024, L"avio_open error");
  20.     logger.logg(buf);
  21.     return false;
  22. }
  23. // 输出格式信息
  24. av_dump_format(pFormatCtx, 0, mp4_file, 1);
复制代码
2. 编码视频帧

(1)将编码数据送往解码器
  1.    
  2.     // data为需要编码的数据地址,为uint8_t类型的指针,size为输入帧的大小
  3.     // 如果输入数据格式不是YUV420P则需要转化
  4.     if (videoSrcFormat != AV_PIX_FMT_YUV420P) {
  5.         if (sws_scale(sws_ctx, (const uint8_t* const*)&data, mVideoSrcStride, 0, in_h, in_frame->data, in_frame->linesize) < 0) {
  6.             
  7.             return false;
  8.         }
  9.     }
  10.     else {
  11.         memcpy(in_frame->data[0],  data, size);
  12.     }
  13.     in_frame->pts = videoPktCount; // videoPktCount为输入帧的序号
  14.    
  15.     // 编码
  16.     int ret = avcodec_send_frame(pCodecCtx, in_frame);//avcodec_send_frame() 函数用于将解码器处理的视频帧发送给编码器
  17.     if (ret < 0) {
  18.         return false;
  19.     }
复制代码
(2)吸收编码数据
  1. AVPacket* vicdeo_pkt = av_packet_alloc();
  2. while (ret >= 0) {
  3.     ret = avcodec_receive_packet(pCodecCtx, &vicdeo_pkt);//用于从编码器接收编码后的数据包
  4.     if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
  5.         break;
  6.     else if (ret < 0) {
  7.         //cout << "Error during encoding" << endl;
  8.         return false;
  9.     }
  10.     // Prepare packet for muxing
  11.     pkt.stream_index = video_st->index;
  12.     av_packet_rescale_ts(&pkt, pCodecCtx->time_base, video_st->time_base);//用于将 AVPacket 中的时间戳(PTS和DTS)从一个时间基转换到另一个时间基
  13.     pkt.pos = -1;//在某些情况下,如果数据包的原始位置是未知的,或者该数据包不是从文件中读取的,而是由其他方式生成的,那么可能会将 pos 设置为 -1。
  14.     ret = av_interleaved_write_frame(pFormatCtx, &pkt); //用于将一个 AVPacket 写入到一个输出媒体文件中
  15.     if (ret < 0) {
  16.         
  17.         return false;
  18.     }
  19.    
  20.     // Free the packet
  21.     av_packet_unref(&pkt);
  22. }
  23. ++videoPktCount; // 每编码完成一帧,videoPktCount加一
  24. av_packet_free(&vicdeo_pkt);
  25. videoPkt = nullptr;
复制代码
二、使用ffmpeg实现对内存中的视频帧数据编码

以下代码实现了ffmpeg对视频流数据举行编码的主要过程,可分为初始化编码器(InitEncoder)和编码视频帧(DecodeVideoFrame)
  1. extern "C" {
  2. #include <libavformat/avformat.h>
  3. #include <libavcodec/avcodec.h>
  4. #include <libavutil/avutil.h>
  5. #include <libavutil/opt.h>
  6. #include <libavutil/mathematics.h>
  7. #include <libavutil/timestamp.h>
  8. #include <libswresample/swresample.h>
  9. #include <libswscale/swscale.h>
  10. #include <libavutil/imgutils.h>
  11. #include <libavutil/error.h>
  12. }
  13. bool mHasVideo = false;
  14. char* mp4_file = "test.mp4";
  15. int in_w = 1920;
  16. int in_h = 1080;
  17. int m_fps = 30;
  18. int64_t videoBitrate = 6000000;
  19. int      mVideoSrcChannel = 0;
  20. int      mVideoSrcStride[1] = { 0 };
  21. int64_t   videoPktCount = 0;
  22. AVPixelFormat videoSrcFormat = AV_PIX_FMT_YUV420P; // 输入的像素格式
  23. AVStream* video_st = nullptr;
  24. const AVCodec* videoCodec = nullptr; // 视频编码器
  25. AVCodecContext* pCodecCtx = nullptr; // 视频编码器上下文
  26. uint8_t* in_frame_buf = nullptr;
  27. AVFrame* in_frame = nullptr;
  28. SwsContext* sws_ctx = nullptr;
  29. // 初始化编码器
  30. bool InitEncoder() {
  31.      /****   编码器设置      ****/
  32.     // 查找编码器
  33.     videoCodec = avcodec_find_encoder_by_name(videoCodecName);
  34.     if (!videoCodec) {
  35.         release();
  36.         return false;
  37.     }
  38.     pCodecCtx = avcodec_alloc_context3(videoCodec);
  39.     pCodecCtx->time_base = { 1, m_fps }; // 设置编码器上下文的时间基准
  40.     pCodecCtx->framerate = { m_fps, 1 };
  41.     pCodecCtx->bit_rate = videoBitrate;
  42.     pCodecCtx->gop_size = 25;//影响视频的压缩效率、随机访问性能以及编码复杂度,对视频质量、文件大小和编码/解码性能也有影响
  43.     pCodecCtx->width = in_w;
  44.     pCodecCtx->height = in_h;
  45.     pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;// AV_PIX_FMT_NV12,AV_PIX_FMT_YUV420P
  46.     pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
  47.     av_opt_set(pCodecCtx->priv_data, "preset", "superfast", 0);
  48.     // 以下为根据对应的编码格式设置相关的参数
  49.     if (pCodecCtx->codec_id == AV_CODEC_ID_H264)
  50.     {
  51.         pCodecCtx->keyint_min = m_fps;
  52.         av_opt_set(pCodecCtx->priv_data, "profile", "main", 0);
  53.         av_opt_set(pCodecCtx->priv_data, "b-pyramid", "none", 0);
  54.         av_opt_set(pCodecCtx->priv_data, "tune", "zerolatency", 0);
  55.     }
  56.     else if (pCodecCtx->codec_id == AV_CODEC_ID_MPEG2VIDEO)
  57.         pCodecCtx->max_b_frames = 2;
  58.     else if (pCodecCtx->codec_id == AV_CODEC_ID_MPEG1VIDEO)
  59.         pCodecCtx->mb_decision = 2;
  60.     if (avcodec_open2(pCodecCtx, videoCodec, NULL) < 0) // 将编码器上下文和编码器进行关联
  61.     {
  62.         release();
  63.         return false;
  64.     }
  65.     /****   封装设置      ****/
  66.     if (avformat_alloc_output_context2(&pFormatCtx, NULL, NULL, mp4_file) < 0) {
  67.         swprintf(buf, 1024, L"avformat_alloc_output_context2 error");
  68.         logger.logg(buf);
  69.         return false;
  70.     }
  71.    
  72.     // 初始化视频码流
  73.     video_st = avformat_new_stream(pFormatCtx, pCodecCtx->codec);
  74.     if (!video_st) {
  75.         std::cout << "avformat_new_stream error" << endl;
  76.         return false;
  77.     }
  78.     video_st->id = pFormatCtx->nb_streams - 1;
  79.     avcodec_parameters_from_context(video_st->codecpar, pCodecCtx);
  80.     pFormatCtx->video_codec_id = pFormatCtx->oformat->video_codec;
  81.    
  82.     // 打开文件
  83.     if (avio_open(&pFormatCtx->pb, mp4_file, AVIO_FLAG_READ_WRITE) < 0) {
  84.         release();
  85.         return false;
  86.     }
  87.     // 输出格式信息
  88.     av_dump_format(pFormatCtx, 0, mp4_file, 1);
  89.    
  90.     /****** 输入参数 *********/
  91.     // 初始化编码帧
  92.     in_frame = av_frame_alloc();
  93.     if (!in_frame) {
  94.         return false;
  95.     }
  96.     // 设置 AVFrame 的其他属性
  97.     in_frame->width = pCodecCtx->width;
  98.     in_frame->height = pCodecCtx->height;
  99.     in_frame->format = pCodecCtx->pix_fmt;
  100.     // 计算所需缓冲区大小
  101.     int size = av_image_get_buffer_size(pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, 1);
  102.     in_frame_buf = (uint8_t*)av_malloc(size);
  103.     // 填充 AVFrame
  104.     av_image_fill_arrays(in_frame->data, in_frame->linesize, in_frame_buf, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, 1);
  105.     // 创建缩放上下文,图像缩放和格式转换上下文
  106.     if (videoSrcFormat != AV_PIX_FMT_YUV420P) {
  107.         sws_ctx = sws_getContext(in_w, in_h, videoSrcFormat,
  108.             in_w, in_h, AV_PIX_FMT_YUV420P,
  109.             SWS_BILINEAR, NULL, NULL, NULL);
  110.         if (!sws_ctx) {
  111.             release();
  112.             return false;
  113.         }
  114.     }
  115.     av_image_fill_linesizes(mVideoSrcStride, videoSrcFormat, in_w); // 计算图像的行大小,格式转换时会用上
  116.    
  117.     videoPktCount = 0;
  118.    
  119.    
  120. }
  121. bool DecodeVideoFrame(uint8_t* data, int size) {
  122.     // data为需要编码的数据地址,为uint8_t类型的指针,size为输入帧大小
  123.     // 如果输入数据格式不是YUV420P则需要转化
  124.     if (videoSrcFormat != AV_PIX_FMT_YUV420P) {
  125.         if (sws_scale(sws_ctx, (const uint8_t* const*)&data, mVideoSrcStride, 0, in_h, in_frame->data, in_frame->linesize) < 0) {
  126.             
  127.             return false;
  128.         }
  129.     }
  130.     else {
  131.         memcpy(in_frame->data[0],  data, size);
  132.     }
  133.     in_frame->pts = videoPktCount; // videoPktCount为输入帧的序号
  134.    
  135.     // 编码
  136.     int ret = avcodec_send_frame(pCodecCtx, in_frame);//avcodec_send_frame() 函数用于将解码器处理的视频帧发送给编码器
  137.     if (ret < 0) {
  138.         return false;
  139.     }
  140.     AVPacket* vicdeo_pkt = av_packet_alloc();
  141.     while (ret >= 0) {
  142.         ret = avcodec_receive_packet(pCodecCtx, &vicdeo_pkt);//用于从编码器接收编码后的数据包
  143.         if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
  144.             break;
  145.         else if (ret < 0) {
  146.             
  147.             return false;
  148.         }
  149.         // Prepare packet for muxing
  150.         vicdeo_pkt.stream_index = video_st->index;
  151.         av_packet_rescale_ts(&pkt, pCodecCtx->time_base, video_st->time_base);//用于将 AVPacket 中的时间戳(PTS和DTS)从一个时间基转换到另一个时间基
  152.         vicdeo_pkt.pos = -1;//在某些情况下,如果数据包的原始位置是未知的,或者该数据包不是从文件中读取的,而是由其他方式生成的,那么可能会将 pos 设置为 -1。
  153.         ret = av_interleaved_write_frame(pFormatCtx, vicdeo_pkt); //用于将一个 AVPacket 写入到一个输出媒体文件中
  154.         if (ret < 0) {
  155.             
  156.             return false;
  157.         }
  158.         
  159.         // Free the packet
  160.         av_packet_unref(vicdeo_pkt);
  161.         
  162.     }
  163.     av_packet_free(&vicdeo_pkt);
  164.     videoPkt = nullptr;
  165.     ++videoPktCount; // 每编码完成一帧,videoPktCount加一
  166. }
  167. void release() {
  168.    
  169.     if (in_frame_buf) {
  170.         av_free(in_frame_buf);
  171.         in_frame_buf = nullptr;
  172.     }
  173.     if (in_frame) {
  174.         av_frame_free(&in_frame);
  175.         in_frame = nullptr;
  176.     }
  177.    
  178.     if (pCodecCtx) {
  179.         avcodec_close(pCodecCtx);
  180.         avcodec_free_context(&pCodecCtx);
  181.         pCodecCtx = nullptr;
  182.     }
  183.     if (sws_ctx) {
  184.         sws_freeContext(sws_ctx);
  185.         sws_ctx = nullptr;
  186.     }
  187.     if (pFormatCtx) {
  188.         avio_close(pFormatCtx->pb);
  189.         avformat_free_context(pFormatCtx);
  190.         pFormatCtx = nullptr;
  191.     }
  192. }
  193.    
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

道家人

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表