FFmpeg的HEVC解码器源代码学习笔记-1

一直想写一个HEVC的码流解析工具,看了雷神264码流解析工具,本来想尝试模仿写一个相似的265码流分析工具,但是发现265的解码过程和结构体和264的不太一样,很多结构体并没有完全暴露出来,没有想到很好的方法获得量化参数,运动向量等这些信息。想着从头学习一下ffmpeg中的265解码函数,再来获取解码后的量化参数,运动向量等一系列信息,再做码流分析。

这里主要学习HEVC的解析函数代码

众所周知,解码器是标准的,因此只要按照官方给定的解码流程对码流进行解码就能正常解码。(以前不知道下图这种语法元素描述的作用,最近才知道解码器的代码完全和描述对的上,就是伪代码形式,难怪雷神说parse vps,sps,pps的代码没什么技术含量)

ffmpeg为了封装成多种编解码器,是提供了包装函数,这些函数根据提供编解码标准来选择解码器。我主要看的是HEVC解码,不过其他的解码器也相似。

参考我前面的博客,经过一堆初始化函数后,首先进行的是av_parser_parse2()函数,对码流进行解析。主要是VPS,SPS,PPS进行解码。

av_parser_parse2( )

//此代码位于libavcodec\parser.c中
int av_parser_parse2(AVCodecParserContext *s, AVCodecContext *avctx,uint8_t **poutbuf, int *poutbuf_size,const uint8_t *buf, int buf_size,int64_t pts, int64_t dts, int64_t pos)
{int index, i;uint8_t dummy_buf[AV_INPUT_BUFFER_PADDING_SIZE];av_assert1(avctx->codec_id != AV_CODEC_ID_NONE);/* Parsers only work for the specified codec ids. */av_assert1(avctx->codec_id == s->parser->codec_ids[0] ||avctx->codec_id == s->parser->codec_ids[1] ||avctx->codec_id == s->parser->codec_ids[2] ||avctx->codec_id == s->parser->codec_ids[3] ||avctx->codec_id == s->parser->codec_ids[4] ||avctx->codec_id == s->parser->codec_ids[5] ||avctx->codec_id == s->parser->codec_ids[6]);if (!(s->flags & PARSER_FLAG_FETCHED_OFFSET)) {s->next_frame_offset =s->cur_offset        = pos;s->flags            |= PARSER_FLAG_FETCHED_OFFSET;}if (buf_size == 0) {/* padding is always necessary even if EOF, so we add it here */memset(dummy_buf, 0, sizeof(dummy_buf));buf = dummy_buf;} else if (s->cur_offset + buf_size != s->cur_frame_end[s->cur_frame_start_index]) { /* skip remainder packets *//* add a new packet descriptor */i = (s->cur_frame_start_index + 1) & (AV_PARSER_PTS_NB - 1);s->cur_frame_start_index = i;s->cur_frame_offset[i]   = s->cur_offset;s->cur_frame_end[i]      = s->cur_offset + buf_size;s->cur_frame_pts[i]      = pts;s->cur_frame_dts[i]      = dts;s->cur_frame_pos[i]      = pos;}if (s->fetch_timestamp) {s->fetch_timestamp = 0;s->last_pts        = s->pts;s->last_dts        = s->dts;s->last_pos        = s->pos;ff_fetch_timestamp(s, 0, 0, 0);}/* WARNING: the returned index can be negative */// 从这里进入码流解析,parser_parse是一个函数指针,在hevc的解码过程中会指向hevc_parse()函数,// 此代码位于libavcodec\hevc_parser.c中,后续其他的解析函数也均在此文件内。// s->parser在av_parser_init()函数中被赋值。// 通过av_parser_iterate()遍历来寻找对应的解析结构体,这个函数的定义位于libavcodec\parser.c中,这个文件里面的parser_list通过#include "libavcodec/parser_list.c"导入,// 但是没有找到原始文件里面有这个文件,后面发现这个文件是在configure后生成的,类似的生成文件还包括codec_list.cindex = s->parser->parser_parse(s, avctx, (const uint8_t **) poutbuf,poutbuf_size, buf, buf_size);// 这个函数里面会调用对应编码标准的解析函数,这里会调用hevc_parse()函数。av_assert0(index > -0x20000000); // The API does not allow returning AVERROR codes
#define FILL(name) if(s->name > 0 && avctx->name <= 0) avctx->name = s->nameif (avctx->codec_type == AVMEDIA_TYPE_VIDEO) {FILL(field_order);FILL(coded_width);FILL(coded_height);FILL(width);FILL(height);}/* update the file pointer */if (*poutbuf_size) {/* fill the data for the current frame */s->frame_offset = s->next_frame_offset;/* offset of the next frame */s->next_frame_offset = s->cur_offset + index;s->fetch_timestamp   = 1;} else {/* Don't return a pointer to dummy_buf. */*poutbuf = NULL;}if (index < 0)index = 0;s->cur_offset += index;return index;
}

hevc_parse( )

hevc_parse()位于libavcodec\hevc_parser.c,这个函数主要是解析额外数据,获得完整帧数据,并进行解析。

static int hevc_parse(AVCodecParserContext *s, AVCodecContext *avctx,const uint8_t **poutbuf, int *poutbuf_size,const uint8_t *buf, int buf_size)
{int next;HEVCParserContext *ctx = s->priv_data;ParseContext *pc = &ctx->pc;int is_dummy_buf = !buf_size;const uint8_t *dummy_buf = buf;// 解析额外的数据,主要包含用于存储一些对于编解码过程非必需,但又是非常有用的附加信息。这些信息通常是特定于编码的,用于初始化编解码器。if (avctx->extradata && !ctx->parsed_extradata) {ff_hevc_decode_extradata(avctx->extradata, avctx->extradata_size, &ctx->ps, &ctx->sei,&ctx->is_avc, &ctx->nal_length_size, avctx->err_recognition,1, avctx);ctx->parsed_extradata = 1;}if (s->flags & PARSER_FLAG_COMPLETE_FRAMES) {next = buf_size;} else {next = hevc_find_frame_end(s, buf, buf_size);//寻找帧的起始标记,也即#define START_CODE 0x000001 ///< start_code_prefix_one_3bytes,可以用UltraEdit查看码流的16进制表示,这样更清晰知道解码的完整流程// 这里将传递的码流组成为一个完整帧数据if (ff_combine_frame(pc, next, &buf, &buf_size) < 0) {*poutbuf      = NULL;*poutbuf_size = 0;return buf_size;}}is_dummy_buf &= (dummy_buf == buf);if (!is_dummy_buf)// 这里开始进行解析parse_nal_units(s, buf, buf_size, avctx);*poutbuf      = buf;*poutbuf_size = buf_size;return next;
}

parse_nal_units()

parse_nal_units()函数位于libavcodec\hevc_parser.c,里面主要根据nal的类型分别对VPS,SPS,PPS,SEI等信息进行解析

static int parse_nal_units(AVCodecParserContext *s, const uint8_t *buf,int buf_size, AVCodecContext *avctx)
{HEVCParserContext *ctx = s->priv_data;HEVCParamSets *ps = &ctx->ps;HEVCSEI *sei = &ctx->sei;int ret, i;/* set some sane default values */s->pict_type         = AV_PICTURE_TYPE_I;s->key_frame         = 0;s->picture_structure = AV_PICTURE_STRUCTURE_UNKNOWN;ff_hevc_reset_sei(sei);ret = ff_h2645_packet_split(&ctx->pkt, buf, buf_size, avctx, ctx->is_avc,ctx->nal_length_size, AV_CODEC_ID_HEVC, 1, 0);if (ret < 0)return ret;for (i = 0; i < ctx->pkt.nb_nals; i++) {H2645NAL *nal = &ctx->pkt.nals[i];GetBitContext *gb = &nal->gb;if (nal->nuh_layer_id > 0)continue;switch (nal->type) {case HEVC_NAL_VPS:ff_hevc_decode_nal_vps(gb, avctx, ps);break;case HEVC_NAL_SPS:ff_hevc_decode_nal_sps(gb, avctx, ps, 1);break;case HEVC_NAL_PPS:ff_hevc_decode_nal_pps(gb, avctx, ps);break;case HEVC_NAL_SEI_PREFIX:case HEVC_NAL_SEI_SUFFIX:ff_hevc_decode_nal_sei(gb, avctx, sei, ps, nal->type);break;case HEVC_NAL_TRAIL_N:case HEVC_NAL_TRAIL_R:case HEVC_NAL_TSA_N:case HEVC_NAL_TSA_R:case HEVC_NAL_STSA_N:case HEVC_NAL_STSA_R:case HEVC_NAL_BLA_W_LP:case HEVC_NAL_BLA_W_RADL:case HEVC_NAL_BLA_N_LP:case HEVC_NAL_IDR_W_RADL:case HEVC_NAL_IDR_N_LP:case HEVC_NAL_CRA_NUT:case HEVC_NAL_RADL_N:case HEVC_NAL_RADL_R:case HEVC_NAL_RASL_N:case HEVC_NAL_RASL_R:if (ctx->sei.picture_timing.picture_struct == HEVC_SEI_PIC_STRUCT_FRAME_DOUBLING) {s->repeat_pict = 1;} else if (ctx->sei.picture_timing.picture_struct == HEVC_SEI_PIC_STRUCT_FRAME_TRIPLING) {s->repeat_pict = 2;}ret = hevc_parse_slice_header(s, nal, avctx);if (ret)return ret;break;}}/* didn't find a picture! */av_log(avctx, AV_LOG_ERROR, "missing picture in access unit with size %d\n", buf_size);return -1;
}

这里主要看了VPS,SPS和PPS的函数
VPS,SPS和PPS的函数功能都相差不大,由于代码过长就不贴了,这些函数都位于libavcodec\hevc_ps.c中,这里以VPS的解析函数ff_hevc_decode_nal_vps()为例。
下图为VPS的语法元素描述(《新一代高效视频编码 H.265/HEVC:原理、标准与实现》 每一章都有对应的语法描述,或者去官方文件里面查看)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

int ff_hevc_decode_nal_vps(GetBitContext *gb, AVCodecContext *avctx,HEVCParamSets *ps)
{int i,j;int vps_id = 0;ptrdiff_t nal_size;HEVCVPS *vps = ff_refstruct_allocz(sizeof(*vps));if (!vps)return AVERROR(ENOMEM);av_log(avctx, AV_LOG_DEBUG, "Decoding VPS\n");nal_size = gb->buffer_end - gb->buffer;if (nal_size > sizeof(vps->data)) {av_log(avctx, AV_LOG_WARNING, "Truncating likely oversized VPS ""(%"PTRDIFF_SPECIFIER" > %"SIZE_SPECIFIER")\n",nal_size, sizeof(vps->data));vps->data_size = sizeof(vps->data);} else {vps->data_size = nal_size;}memcpy(vps->data, gb->buffer, vps->data_size);vps_id = vps->vps_id = get_bits(gb, 4);if (get_bits(gb, 2) != 3) { // vps_reserved_three_2bitsav_log(avctx, AV_LOG_ERROR, "vps_reserved_three_2bits is not three\n");goto err;}vps->vps_max_layers               = get_bits(gb, 6) + 1;vps->vps_max_sub_layers           = get_bits(gb, 3) + 1;vps->vps_temporal_id_nesting_flag = get_bits1(gb);if (get_bits(gb, 16) != 0xffff) { // vps_reserved_ffff_16bitsav_log(avctx, AV_LOG_ERROR, "vps_reserved_ffff_16bits is not 0xffff\n");goto err;}if (vps->vps_max_sub_layers > HEVC_MAX_SUB_LAYERS) {av_log(avctx, AV_LOG_ERROR, "vps_max_sub_layers out of range: %d\n",vps->vps_max_sub_layers);goto err;}if (parse_ptl(gb, avctx, &vps->ptl, vps->vps_max_sub_layers) < 0)goto err;vps->vps_sub_layer_ordering_info_present_flag = get_bits1(gb);i = vps->vps_sub_layer_ordering_info_present_flag ? 0 : vps->vps_max_sub_layers - 1;for (; i < vps->vps_max_sub_layers; i++) {vps->vps_max_dec_pic_buffering[i] = get_ue_golomb_long(gb) + 1;vps->vps_num_reorder_pics[i]      = get_ue_golomb_long(gb);vps->vps_max_latency_increase[i]  = get_ue_golomb_long(gb) - 1;if (vps->vps_max_dec_pic_buffering[i] > HEVC_MAX_DPB_SIZE || !vps->vps_max_dec_pic_buffering[i]) {av_log(avctx, AV_LOG_ERROR, "vps_max_dec_pic_buffering_minus1 out of range: %d\n",vps->vps_max_dec_pic_buffering[i] - 1);goto err;}if (vps->vps_num_reorder_pics[i] > vps->vps_max_dec_pic_buffering[i] - 1) {av_log(avctx, AV_LOG_WARNING, "vps_max_num_reorder_pics out of range: %d\n",vps->vps_num_reorder_pics[i]);if (avctx->err_recognition & AV_EF_EXPLODE)goto err;}}vps->vps_max_layer_id   = get_bits(gb, 6);vps->vps_num_layer_sets = get_ue_golomb_long(gb) + 1;if (vps->vps_num_layer_sets < 1 || vps->vps_num_layer_sets > 1024 ||(vps->vps_num_layer_sets - 1LL) * (vps->vps_max_layer_id + 1LL) > get_bits_left(gb)) {av_log(avctx, AV_LOG_ERROR, "too many layer_id_included_flags\n");goto err;}for (i = 1; i < vps->vps_num_layer_sets; i++)for (j = 0; j <= vps->vps_max_layer_id; j++)skip_bits(gb, 1);  // layer_id_included_flag[i][j]vps->vps_timing_info_present_flag = get_bits1(gb);if (vps->vps_timing_info_present_flag) {vps->vps_num_units_in_tick               = get_bits_long(gb, 32);vps->vps_time_scale                      = get_bits_long(gb, 32);vps->vps_poc_proportional_to_timing_flag = get_bits1(gb);if (vps->vps_poc_proportional_to_timing_flag)vps->vps_num_ticks_poc_diff_one = get_ue_golomb_long(gb) + 1;vps->vps_num_hrd_parameters = get_ue_golomb_long(gb);if (vps->vps_num_hrd_parameters > (unsigned)vps->vps_num_layer_sets) {av_log(avctx, AV_LOG_ERROR,"vps_num_hrd_parameters %d is invalid\n", vps->vps_num_hrd_parameters);goto err;}for (i = 0; i < vps->vps_num_hrd_parameters; i++) {int common_inf_present = 1;get_ue_golomb_long(gb); // hrd_layer_set_idxif (i)common_inf_present = get_bits1(gb);decode_hrd(gb, common_inf_present, &vps->hdr[i],vps->vps_max_sub_layers);}}get_bits1(gb); /* vps_extension_flag */if (get_bits_left(gb) < 0) {av_log(avctx, AV_LOG_ERROR,"Overread VPS by %d bits\n", -get_bits_left(gb));if (ps->vps_list[vps_id])goto err;}if (ps->vps_list[vps_id] &&!memcmp(ps->vps_list[vps_id], vps, sizeof(*vps))) {ff_refstruct_unref(&vps);} else {remove_vps(ps, vps_id);ps->vps_list[vps_id] = vps;}return 0;err:ff_refstruct_unref(&vps);return AVERROR_INVALIDDATA;
}

可以看出每个语法元素都能找到对应的一行代码,且变量名都完全一样,将上述语法描述和代码对应起来看就可以明白这些解析函数代码的含义。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/695356.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

自增a++和自减a--详细解析

1.自增、自减运算符是什么&#xff0c;有什么作用&#xff0c;需要注意什么? 、–;对当前变量值1、-1只能操作变量&#xff0c;不能操作字面量 2.自增、自减运算符放在变量前后有区别吗? 如果单独使用放前放后是没有区别的非单独使用:在变量前&#xff0c;先进行变量自增/…

unity学习(36)——角色选取界面(自制美工)

1.添加一个背景图片&#xff0c;记不住可以查之前的资料&#xff08;4&#xff09; 图片拖入asset&#xff0c;属性设成sprite&#xff1b;把图片拖到source image中&#xff1b;colour白色&#xff08;透明&#xff0c;点一下右边的笔即可&#xff09;&#xff1b;material为…

SpringCloud-Gateway网关的使用

本文介绍如何再 SpringCloud 项目中引入 Gateway 网关并完成网关服务的调用。Gateway 网关是一个在微服务架构中起到入口和路由控制的关键组件。它负责处理客户端请求&#xff0c;进行路由决策&#xff0c;并将请求转发到相应的微服务。Gateway 网关还可以实现负载均衡、安全认…

vue大文件读取部分内容,避免重复加载大文件,造成流量浪费

使用场景&#xff1a;项目点云地图是pcd文件&#xff0c;但是文件可能上百兆&#xff0c;我需要获取到文件中的版本信息&#xff0c;跟本地的缓存文件做比较&#xff0c;如果不一致&#xff0c;才会加载整个文件。从而节省流量。 避免重复加载整个“.pcd文件&#xff0c;以最大…

【PX4学习笔记】04.QGC地面站的使用

目录 文章目录 目录PX4代码烧入PX4固件代码的烧入方式1PX4固件代码的烧入方式2 QGC地面站的基础使用连接地面站的方式查看关键的硬件信息 QGC地面站的Application Settings模块Application Settings模块-常规界面单位其他设置数据持久化飞机中的数传日志飞行视图计划视图自动连…

观察者模式, 发布-订阅模式, 监听器模式

观察者模式, 发布-订阅模式, 监听器模式 观察者模式 观察者模式是一种行为型设计模式, 定义对象间的一种一对多的依赖关系&#xff0c;当一个对象的状态发生改变时&#xff0c;所有依赖于它的对象都得到通知并被自动更新 角色模型和结构图 在观察者模式中&#xff0c;只有两种…

HarmonyOS Stage模型基本概念讲解

本文 我们来说harmonyos中的一种应用模型 Stage模型 官方提供了两种模型 一种是早期的 FA模型 另一种就是就是 harmonyos 3.1才开始的新增的一种模型 Stage模型 目前来讲 Stage 会成为现在乃至将来 长期推进的一种模型 也就是 无论是 现在的harmonyos 4.0 乃至 之后要发布的 …

IP地理位置查询定位:技术原理与实际应用

在互联网时代&#xff0c;IP地址是连接世界的桥梁&#xff0c;而了解IP地址的地理位置对于网络管理、个性化服务以及安全监控都至关重要。IP数据云将深入探讨IP地理位置查询定位的技术原理、实际应用场景以及相关的隐私保护问题&#xff0c;旨在为读者提供全面了解和应用该技术…

印刷机械故障诊断:虹科MSR165助力Müller Martini AG成功案例

在为杂志装订机开发新产品的过程中&#xff0c;作为印刷后处理机械领域的全球领导者&#xff0c;Mller Martini AG公司发现了传感器故障的问题。通过使用虹科MSR 微型加速度数据记录仪&#xff0c;成功地确定了故障的原因。 新杂志装订机中的三刀修整装置的故障部件是边缘传感器…

BOSS直聘招聘经验

招聘低端兼职岗位。流量很大&#xff0c;来的人通常实力也不足。 招聘高端兼职岗位。流量不多。来的人通常具备一定实力。 招聘高薪职位&#xff0c;流量一般&#xff0c;会有有实力的勾搭。 招聘低薪职位&#xff0c;流量一般。通常没什么实力。

使用 Optimum Intel 在英特尔至强上加速 StarCoder: Q8/Q4 及投机解码

引言 近来&#xff0c;随着 BigCode 的 StarCoder 以及 Meta AI 的 Code Llama 等诸多先进模型的发布&#xff0c;代码生成模型变得炙手可热。同时&#xff0c;业界也涌现出了大量的致力于优化大语言模型 (LLM) 的运行速度及易用性的工作。我们很高兴能够分享我们在英特尔至强 …

测试多线程架构的问题

在测试多线程架构时&#xff0c;需要考虑多个方面以确保系统的稳定性和性能。以下是一些关键问题&#xff0c;需要在测试过程中特别关注&#xff1a; 线程同步 多线程环境中&#xff0c;线程同步是非常重要的问题。由于多个线程可能同时访问共享资源&#xff0c;因此需要使用…

Linux环境下查看磁盘层级占用空间的解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

C++从入门到精通 第十三章(认识STL)

写在前面&#xff1a; 本系列专栏主要介绍C的相关知识&#xff0c;思路以下面的参考链接教程为主&#xff0c;大部分笔记也出自该教程&#xff0c;笔者的原创部分主要在示例代码的注释部分。除了参考下面的链接教程以外&#xff0c;笔者还参考了其它的一些C教材&#xff08;比…

下一代自动化爬虫神器--playwright,所见即所得,配合逆向不要太香!!!

文章目录 1.Playwright介绍2.与 Selenium 和 pyppeteer 相比&#xff0c;Playwright 具有以下几个区别和优势3.在爬虫中使用 Playwright 的好处4.环境安装5.屏幕录制6.保留记录cookie信息7.playwright代码编写详解1.第一个Playwright脚本&#xff08;1&#xff09;同步模式&…

Redis之缓存穿透问题解决方案实践SpringBoot3+Docker

文章目录 一、介绍二、方案介绍三、Redis Docker部署四、SpringBoot3 Base代码1. 依赖配置2. 基本代码 五、缓存优化代码1. 校验机制2. 布隆过滤器3. 逻辑优化 一、介绍 当一种请求&#xff0c;总是能越过缓存&#xff0c;调用数据库&#xff0c;就是缓存穿透。 比如当请求一…

阿里云国际站如何助力餐饮行业出海?

近些年&#xff0c;中国企业出海方兴未艾。全球不同国家的经济政治诉求加剧了商业领域的博弈&#xff0c;全球产业供应链格局持续发生深刻变化。无论是海外建厂&#xff0c;还是海外找市场&#xff0c;中国产业链的全球布局蔚然成风,企业想突破现阶段瓶颈&#xff0c;谋求更好的…

⭐北邮复试刷题106. 从中序与后序遍历序列构造二叉树__递归分治 (力扣每日一题)

106. 从中序与后序遍历序列构造二叉树 给定两个整数数组 inorder 和 postorder &#xff0c;其中 inorder 是二叉树的中序遍历&#xff0c; postorder 是同一棵树的后序遍历&#xff0c;请你构造并返回这颗 二叉树 。 示例 1: 输入&#xff1a;inorder [9,3,15,20,7], postor…

ros自定义action记录

文章目录 自定义action1. 定义action文件2. 修改 package.xml3. 修改 CMakeLists.txt4. 运行 catkin build5. simple_action_server.py6. simple_action_client.py 测试 自定义action ros 版本&#xff1a;kinetic 自定义test包的文件结构如下 |-- test | |-- CMakeLists.t…

Internet Download Manager 6.42.3 (IDM) 中文免激活绿色版

相信很多网友都遇到过一种情况&#xff0c;网页有些视频资源或者音频资源不知道如何下载&#xff0c;一直不知道如何解决&#xff0c;为此小编特意带来了这款&#xff1a;Internet Download Manager电脑版&#xff0c;这是一款非常专业且十分好用的下载工具&#xff0c;也就是大…