本文为作者原创,转载请注明出处:https://www.cnblogs.com/leisure_chn/p/10584937.html

FFmpeg编解码处理系列笔记:

[0]. FFmpeg时间戳详解

[1]. FFmpeg编解码处理1-转码全流程简介

[2]. FFmpeg编解码处理2-编解码API详解

[3]. FFmpeg编解码处理3-视频编码

[4]. FFmpeg编解码处理4-音频编码

基于 FFmpeg 4.1 版本。

5. 视频编码

编码使用 avcodec_send_frame() 和 avcodec_receive_packet() 两个函数。

视频编码的步骤:

[1] 初始化打开输出文件时构建编码器上下文

[2] 视频帧编码

[2.1] 设置帧类型 "frame->pict_type=AV_PICTURE_TYPE_NONE",让编码器根据设定参数自行生成 I/B/P 帧类型

[2.2] 将原始帧送入编码器,从编码器取出编码帧

[2.3] 更新编码帧流索引

[2.4] 将帧中时间参数按输出封装格式的时间基进行转换

5.1 打开视频编码器

完整源码在 open_output_file() 函数中,下面摘出关键部分:

  1. // 3. 构建AVCodecContext
  2. if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO ||
  3. dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) // 音频流或视频流
  4. {
  5. // 3.1 查找编码器AVCodec,本例使用与解码器相同的编码器
  6. AVCodec *encoder = NULL;
  7. if ((dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO) && (strcmp(v_enc_name, "copy") != 0))
  8. {
  9. encoder = avcodec_find_encoder_by_name(v_enc_name);
  10. }
  11. else if ((dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) && (strcmp(a_enc_name, "copy") != 0))
  12. {
  13. encoder = avcodec_find_encoder_by_name(a_enc_name);
  14. }
  15. else
  16. {
  17. encoder = avcodec_find_encoder(dec_ctx->codec_id);
  18. }
  19. if (!encoder)
  20. {
  21. av_log(NULL, AV_LOG_FATAL, "Necessary encoder not found\n");
  22. return AVERROR_INVALIDDATA;
  23. }
  24. // 3.2 AVCodecContext初始化:分配结构体,使用AVCodec初始化AVCodecContext相应成员为默认值
  25. AVCodecContext *enc_ctx = avcodec_alloc_context3(encoder);
  26. if (!enc_ctx)
  27. {
  28. av_log(NULL, AV_LOG_FATAL, "Failed to allocate the encoder context\n");
  29. return AVERROR(ENOMEM);
  30. }
  31. // 3.3 AVCodecContext初始化:配置图像/声音相关属性
  32. /* In this example, we transcode to same properties (picture size,
  33. * sample rate etc.). These properties can be changed for output
  34. * streams easily using filters */
  35. if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO)
  36. {
  37. enc_ctx->height = dec_ctx->height; // 图像高
  38. enc_ctx->width = dec_ctx->width; // 图像宽
  39. enc_ctx->sample_aspect_ratio = dec_ctx->sample_aspect_ratio; // 采样宽高比:像素宽/像素高
  40. /* take first format from list of supported formats */
  41. if (encoder->pix_fmts) // 编码器支持的像素格式列表
  42. {
  43. enc_ctx->pix_fmt = encoder->pix_fmts[0]; // 编码器采用所支持的第一种像素格式
  44. }
  45. else
  46. {
  47. enc_ctx->pix_fmt = dec_ctx->pix_fmt; // 编码器采用解码器的像素格式
  48. }
  49. /* video time_base can be set to whatever is handy and supported by encoder */
  50. enc_ctx->time_base = av_inv_q(dec_ctx->framerate); // 时基:解码器帧率取倒数
  51. enc_ctx->framerate = dec_ctx->framerate;
  52. //enc_ctx->bit_rate = dec_ctx->bit_rate;
  53. /* emit one intra frame every ten frames
  54. * check frame pict_type before passing frame
  55. * to encoder, if frame->pict_type is AV_PICTURE_TYPE_I
  56. * then gop_size is ignored and the output of encoder
  57. * will always be I frame irrespective to gop_size
  58. */
  59. //enc_ctx->gop_size = 10;
  60. //enc_ctx->max_b_frames = 1;
  61. }
  62. else
  63. {
  64. enc_ctx->sample_rate = dec_ctx->sample_rate; // 采样率
  65. enc_ctx->channel_layout = dec_ctx->channel_layout; // 声道布局
  66. enc_ctx->channels = av_get_channel_layout_nb_channels(enc_ctx->channel_layout); // 声道数量
  67. /* take first format from list of supported formats */
  68. enc_ctx->sample_fmt = encoder->sample_fmts[0]; // 编码器采用所支持的第一种采样格式
  69. enc_ctx->time_base = (AVRational){1, enc_ctx->sample_rate}; // 时基:编码器采样率取倒数
  70. // enc_ctx->codec->capabilities |= AV_CODEC_CAP_VARIABLE_FRAME_SIZE; // 只读标志
  71. // 初始化一个FIFO用于存储待编码的音频帧,初始化FIFO大小的1个采样点
  72. // av_audio_fifo_alloc()第二个参数是声道数,第三个参数是单个声道的采样点数
  73. // 采样格式及声道数在初始化FIFO时已设置,各处涉及FIFO大小的地方都是用的单个声道的采样点数
  74. pp_audio_fifo[i] = av_audio_fifo_alloc(enc_ctx->sample_fmt, enc_ctx->channels, 1);
  75. if (pp_audio_fifo == NULL)
  76. {
  77. av_log(NULL, AV_LOG_ERROR, "Could not allocate FIFO\n");
  78. return AVERROR(ENOMEM);
  79. }
  80. }
  81. if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
  82. {
  83. enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
  84. }
  85. // 3.4 AVCodecContext初始化:使用AVCodec初始化AVCodecContext,初始化完成
  86. /* Third parameter can be used to pass settings to encoder */
  87. ret = avcodec_open2(enc_ctx, encoder, NULL);
  88. if (ret < 0)
  89. {
  90. av_log(NULL, AV_LOG_ERROR, "Cannot open video encoder for stream #%u\n", i);
  91. return ret;
  92. }
  93. // 3.5 设置输出流codecpar
  94. ret = avcodec_parameters_from_context(out_stream->codecpar, enc_ctx);
  95. if (ret < 0)
  96. {
  97. av_log(NULL, AV_LOG_ERROR, "Failed to copy encoder parameters to output stream #%u\n", i);
  98. return ret;
  99. }
  100. // 3.6 保存输出流contex
  101. pp_enc_ctx[i] = enc_ctx;
  102. }

5.2 编码视频帧

完整源码在 transcode_video() 函数中,下面摘出关键部分:

  1. // 2. 滤镜处理
  2. ret = filtering_frame(sctx->flt_ctx, frame_dec, frame_flt);
  3. if (ret == AVERROR_EOF)
  4. {
  5. av_log(NULL, AV_LOG_INFO, "filtering vframe EOF\n");
  6. flt_finished = true;
  7. av_frame_free(&frame_flt); // flush encoder
  8. }
  9. else if (ret < 0)
  10. {
  11. av_log(NULL, AV_LOG_INFO, "filtering vframe error %d\n", ret);
  12. goto end;
  13. }
  14. flush_encoder:
  15. // 3. 编码
  16. if (frame_flt != NULL)
  17. {
  18. // 3.1 设置帧类型。如果不设置,则使用输入流中的帧类型。
  19. frame_flt->pict_type = AV_PICTURE_TYPE_NONE;
  20. }
  21. // 3.2 编码
  22. ret = av_encode_frame(sctx->o_codec_ctx, frame_flt, &opacket);
  23. if (ret == AVERROR(EAGAIN)) // 需要读取新的packet喂给编码器
  24. {
  25. //av_log(NULL, AV_LOG_INFO, "encode vframe need more packet\n");
  26. goto end;
  27. }
  28. else if (ret == AVERROR_EOF)
  29. {
  30. av_log(NULL, AV_LOG_INFO, "encode vframe EOF\n");
  31. enc_finished = true;
  32. goto end;
  33. }
  34. else if (ret < 0)
  35. {
  36. av_log(NULL, AV_LOG_ERROR, "encode vframe error %d\n", ret);
  37. goto end;
  38. }
  39. // 3.3 更新编码帧中流序号,并进行时间基转换
  40. // AVPacket.pts和AVPacket.dts的单位是AVStream.time_base,不同的封装格式其AVStream.time_base不同
  41. // 所以输出文件中,每个packet需要根据输出封装格式重新计算pts和dts
  42. opacket.stream_index = sctx->stream_idx;
  43. av_packet_rescale_ts(&opacket, sctx->o_codec_ctx->time_base, sctx->o_stream->time_base);
  44. // 4. 将编码后的packet写入输出媒体文件
  45. ret = av_interleaved_write_frame(sctx->o_fmt_ctx, &opacket);
  46. av_packet_unref(&opacket);

5.3 视频编码中的 I/B/P 帧类型

做一个实验,修改 5.1.2 节 frame_flt->pict_type 值和 5.1.1 节 enc_ctx->gop_size 和 enc_ctx->max_b_frames,将编码后视频帧 I/B/P 类型打印出来,观察实验结果。

我们选一个很短的视频文件用于测试(右键另存为):tnmil3.flv

  1. tnmil3.flv 重命名为 tnmil.flv
  2. 转码:tnmil.flv ==> tnmilo1.flv
  3. 命令:./transcode -i tnmil.flv -c:v copy -c:a copy tnmilo1.flv
  4. tnmil.flv ==> tnmilo1.flv 不修改frame IBP类型,不设置编码器gop_sizemax_b_frames
  5. IBPBPBPBPBPBBBPBBBPBBBPBBPBBBPBBBPBBPBBBPBBBPBBBPBPBPBBPBBBPBBBPBPBBBPBBBPBPBBPBBBPPIBBP
  6. IBPBPBPBPBPBBBPBBBPBBBPBBPBBBPBBBPBBPBBBPBBBPBBBPBPBPBBPBBBPBBBPBPBBBPBBBPBPBBPBBBPPIBBP
  7. tnmil.flv ==> tnmilo3.flv frame IBP类型设为NONE,将编码器gop_size设为10max_b_frames设为1
  8. IBPBPBPBPBPBBBPBBBPBBBPBBPBBBPBBBPBBPBBBPBBBPBBBPBPBPBBPBBBPBBBPBPBBBPBBBPBPBBPBBBPPIBBP
  9. IBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPPIBPP
  10. tnmilo3.flv ==> tnmilo4.flv 不修改frame IBP类型,不设置编码器gop_sizemax_b_frames
  11. IBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPPIBPP
  12. IBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPPIBPP
  13. tnmilo3.flv ==> tnmilo5.flv frame IBP类型设为NONE,不设置编码器gop_size(默认-1)和max_b_frames(默认0)
  14. IBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPPIBPP
  15. IBPBPBPBPBBBPBPBPBBBPBBBPBBBPBBBPBBBPBBBPBBBPBBBPBBBPBBBPBBBPBBBPBBBPBBBPBBBPBPBBBPPIBBP

实验结论如下:将原始视频帧 frame 送入视频编码器后生成编码帧 packet,那么

[1] 手工设置每一帧 frame 的帧类型为 I/B/P,则编码后的 packet 的帧类型和 frame 中的一样。编码器是否设置 gop_size 和 max_b_frames 两个参数无影响。

[2] 将每一帧 frame 的帧类型设置为 NONE,如果未设置编码器的 gop_size(默认值 -1)和 max_b_frames (默认值 0)两个参数,则编码器自动选择合适参数来进行编码,生成帧类型。

[3] 将每一帧 frame 的帧类型设置为 NONE,如果设置了编码器的 gop_size 和 max_b_frames 两个参数,则编码器按照这两个参数来进行编码,生成帧类型。

FFmpeg编解码处理3-视频编码的更多相关文章

  1. FFmpeg编解码处理4-音频编码

    本文为作者原创,转载请注明出处:https://www.cnblogs.com/leisure_chn/p/10584948.html FFmpeg编解码处理系列笔记: [0]. FFmpeg时间戳详 ...

  2. FFmpeg编解码处理2-编解码API详解

    本文为作者原创,转载请注明出处:https://www.cnblogs.com/leisure_chn/p/10584925.html FFmpeg编解码处理系列笔记: [0]. FFmpeg时间戳详 ...

  3. FFmpeg编解码处理1-转码全流程简介

    本文为作者原创,转载请注明出处:https://www.cnblogs.com/leisure_chn/p/10584901.html FFmpeg编解码处理系列笔记: [0]. FFmpeg时间戳详 ...

  4. ffmpeg 编解码详细过程

    ffmpeg编解码详细过程     bobbypollo 转:ffmpeg编解码详细过程 原文地址:ffmpeg编解码详细过程(转)作者:心在飞翔原文出处: http://www.360doc.com ...

  5. 最近在研究FFmpeg编解码

    好几年没上CNBLOGS了, 最近在研究FFmpeg编解码,一个人研究感到很寂寞,所以想通过博客来和大家分享和交流,呵呵. 最近研究的主题是: ANDROID手机同屏技术: 需要用到ANDROID截屏 ...

  6. ffmpeg编解码视频导致噪声增大的一种解决方法

    一.前言 ffmpeg在视音频编解码领域算是一个比较成熟的解决方案了.公司的一款视频编辑软件正是基于ffmpeg做了二次封装,并在此基础上进行音视频的编解码处理.然而,在观察编码后的视频质量时,发现图 ...

  7. ffmpeg编解码音频AAC

    本次项目的需求:手机端和PC端共享同一个音视频网络源. 所以编解码需要满足手机上编码和解码原来PC端的音视频流. 这里先封装安卓手机端音频的编解码. 编译工作依然是在linux下 ubuntu 12. ...

  8. 【GPU编解码】GPU硬编码

    一.OpenCV中的硬编码 OpenCV2.4.6中,已实现利用GPU进行写视频,编码过程由cv::gpu::VideoWriter_GPU完成,其示例程序如下. int main(int argc, ...

  9. 【GPU编解码】GPU硬编码 (转)

    一.OpenCV中的硬编码 OpenCV2.4.6中,已实现利用GPU进行写视频,编码过程由cv::gpu::VideoWriter_GPU完成,其示例程序如下. 1 int main(int arg ...

随机推荐

  1. solr7.7.0搜索引擎使用(一)(下载安装)

    一.下载安装 可以直接在官网下载地址:https://lucene.apache.org/solr/ 解压之后,目录结构如下图,bin里边提供部署的文件,contrib提供额外的jar包,docs提供 ...

  2. Chapter3_操作符_直接常量和指数计数法

    (1)直接常量 在程序中使用直接常量,相当于指导编译器,告诉它要生成什么样的类型,这样就不会产生模棱两可的情况.比如flaot a = 1f等,后缀表示告诉编译器想生成的类型.常用的后缀有l/L(lo ...

  3. c语言基础课第三次作业

    7-1找出最小值 1.实验代码 #include <stdio.h> int main(void) int n, i, m, min; scanf("%d", & ...

  4. 主引导扇区MBR的解析

    http://blog.chinaunix.net/uid-24774106-id-3340397.html 最近排查一个USB相关的故障,由于信息安全就不多说工作上的事情了,顺路学习了MBR的相关知 ...

  5. 滑块视图容器   swiper

    属性名 类型 默认值 说明 indicator-dots Boolean false 是否显示面板指示点 autoplay Boolean false 是否自动切换 current Number 0 ...

  6. TortoiseSVN与TortoiseGit

    TortoiseSVN与TortoiseGit 功能:版本控制+备份处理 差异:SVN二段式,没有中间存储点,直接提交后到达了远程存储点:要想对本地的修改进行记录,必须要与SVN服务器进行通讯,无法只 ...

  7. CURL超时时间设置

    一次完整的http请求,一般包含三个步骤: 通过DNS把域名解析成IP 通过IP地址连接到目标主机 获取目标主机数据(1.给目标主机输出http请求头,以\r\n\r\n结尾:2.获取目标主机传过来的 ...

  8. Servlet案例6:显示用户的上次访问时间

    这里是cookie的简单应用 告诉用户您的上次访问时间是:xxxx-xx-xx xx:xx:xx 思路: 第一次访问该网站时候,记录当前访问时间(new Date()) 把当前时间以cookie的形式 ...

  9. 一致性hash理解、拜占庭将军问题解读和CAP理论总结

    一致性hash理解 白话概述: 比如说存储图片,有10台服务器用来存储,对图片名进行hash(pic_name)%10得到的值就是图片存放的服务器序号.这是正常的hash算法分散图片存储.但是有一天, ...

  10. Testing - 软件测试知识梳理 - 自动化测试

    软件开发的过程是一个持续集成和改进的过程,而每一次的改进都可能引进新bug,因此当软件的一部,或者全部修改时,都需要对软件产品重新进行测试. 其目的是要验证修改后的产品是符合需求的,而当没有自动化测试 ...