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://xiahunao.cn/news/2799407.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

HAL STM32 HW I2C DMA + SSD1306/SH1106驱动示例

HAL STM32 HW I2C DMA SSD1306/SH1106驱动示例 &#x1f4cd;硬件I2C DMA驱动参考&#xff1a;https://blog.csdn.net/weixin_45065888/article/details/118225993 &#x1f516;本工程基于STM32F103VCT6&#xff0c;驱动程序独立&#xff0c;可以移植到任意STM32型号上使用。…

VSCODE中使用Django处理后端data和data models

链接&#xff1a; Python and Django tutorial in Visual Studio Code MVC的理解 在实际的程序中采用MVC的方式进行任务拆分。 Model&#xff08;模型&#xff09;负责封装应用程序的数据和业务逻辑部分。Model包含数据结构&#xff0c;数据处理逻辑以及相关的操作方法&#…

如何在debian上实现一键恢复操作系统?

在Debian或任何其他Linux发行版上实现一键恢复操作系统&#xff0c;需要创建一个系统镜像或快照&#xff0c;并设置一个简单的方法来从该镜像恢复。以下是创建和恢复系统的基本步骤&#xff1a; 1. 创建系统镜像&#xff1a; 使用像dd&#xff0c;rsync或专门的备份工具&#…

Kubernetes 卷存储 NFS | nfs搭建配置 原理介绍 nfs作为存储卷使用

1、NFS介绍 NFS&#xff08;Network File System&#xff09;是一种分布式文件系统协议&#xff0c;允许客户端远程访问服务器上的文件&#xff0c;实现数据共享。它整合多个存储设备为统一文件系统&#xff0c;方便数据存储和管理&#xff0c;支持负载均衡和故障转移&#xf…

【专利】专利缴费清单与汇款金额不一致的处理方法

缴纳专利年费时&#xff0c;很容易算错滞纳金。比如有个专利年费滞纳金应交690&#xff0c;结果我算成了660&#xff0c;报给财务转账660。第二天补录缴费信息时&#xff0c;汇款金额660&#xff0c;然后缴费清单填写总是690无法修改&#xff0c;导致无法进行下一步&#xff01…

【AI大模型】ChatGPT在地学、GIS、气象、农业、生态、环境等领域中的高级应用

以ChatGPT、LLaMA、Gemini、DALLE、Midjourney、Stable Diffusion、星火大模型、文心一言、千问为代表AI大语言模型带来了新一波人工智能浪潮&#xff0c;可以面向科研选题、思维导图、数据清洗、统计分析、高级编程、代码调试、算法学习、论文检索、写作、翻译、润色、文献辅助…

【MySQL】学习连接查询和案例演示

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-KOxr1rwR9cQTlydJ {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…

win32 汇编读文件

做了2个小程序&#xff0c;没有读成功&#xff1b;文件打开了&#xff1b; .386.model flat, stdcalloption casemap :noneinclude windows.inc include user32.inc includelib user32.lib include kernel32.inc includelib kernel32.lib include Comdlg32.inc includelib …

探索海洋世界,基于YOLOv7【tiny/l/x】不同系列参数模型开发构建海洋场景下海洋生物检测识别分析系统

前面的博文中&#xff0c;开发实践过海底相关生物检测识别的项目&#xff0c;对于海洋场景下的海洋生物检测则很少有所涉及&#xff0c;这里本文的主要目的就是想要开发构建基于YOLOv7不同系列参数模型的海洋场景下的海洋生物检测识别系统。 前文已有相关实践&#xff0c;感兴…

C#知识点-16(计算器插件开发、事件、递归、XML)

计算器插件开发 1、Calculator.cs using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace Calculator_DLL {//用来明确所有插件开发人员的开发规范public abstract class Calculator{public int N…

部署安装有道QanyThing

前提条件&#xff1a; 1、win10系统更新到最新的版本&#xff0c;系统版本最好为专业版本 winver 查看系统版本&#xff0c;内部版本要大于19045 2、CPU开启虚拟化 3、开启虚拟化功能&#xff0c;1、2、3每步完成后均需要重启电脑&#xff1b; 注&#xff1a;windows 虚拟…

(done) 如何判断一个矩阵是否可逆?

参考视频&#xff1a;https://www.bilibili.com/video/BV15H4y1y737/?spm_id_from333.337.search-card.all.click&vd_source7a1a0bc74158c6993c7355c5490fc600 这个视频里还暗含了一些引理 1.若 AX XB 且 X 和 A,B 同阶可逆&#xff0c;那么 A 和 B 相似。原因&#xff1…

Redis 工具类 与 Redis 布隆过滤器

Redis 工具类 1. 核心依赖 <!--redis--> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency><groupId>com.google.guava…

如何修改docker容器的端口映射

要修改 Docker 容器的端口映射&#xff0c;你需要停止并删除现有的容器&#xff0c;然后使用新的端口映射重新运行容器。以下是详细步骤&#xff1a; 停止容器&#xff1a; 使用 docker stop 命令停止正在运行的容器。替换 <container_id> 为你要停止的容器的 ID 或者容器…

Python3零基础教程之Python解释器与开发环境搭建

大家好&#xff0c;我是千与编程&#xff0c;硕士毕业于北京大学&#xff0c;曾先后就职于字节跳动&#xff0c;京东等互联网大厂&#xff0c;目前在编程导航知识星球担任星球嘉宾&#xff0c;著有《AI算法毕设智囊袋》&#xff0c;《保姆级带你通关秋招教程》两大专栏。 今天开…

AndroidStudio 2024-2-21 Win10/11最新安装配置(Ktlion快速构建配置,gradle镜像源)

AndroidStudio 2024 Win10/11最新安装配置 教程目的&#xff1a; (从安装到卸载) &#xff0c;针对Kotlin开发配置&#xff0c;gradle-8.2-src/bin下载慢&#xff0c;以及Kotlin构建慢的解决 好久没玩AS了,下载发现装个AS很麻烦,就觉得有必要出个教程了(就是记录一下:嘻嘻) 因…

Java Web演化史:从Servlet到SpringBoot的技术进程及未来趋势

引言 在快速演进的IT世界里&#xff0c;Java Web开发始终屹立不倒&#xff0c;它不仅承担着历史的厚重&#xff0c;也始终面向未来。 自诞生之日起&#xff0c;Java Web技术就在不断地进化&#xff0c;以适应不同时代的需求。 本文将回顾Java Web开发的重要里程碑&#xff0c;…

Linux环境安装Git(详细图文)

说明 此文档Linux环境为&#xff1a;Ubuntu 22.04&#xff0c;本文档介绍两种安装方式&#xff0c;一种是服务器能联网的情况通过yum或apt命令下载&#xff0c;第二种采用源码方式安装。 一、yum/apt方式安装 1.yum方式安装Git 如果你的服务器环境是centos/redhot&#xff…

js谐音梗创意小游戏《望子成龙》

&#x1f33b; 前言 龙年到来&#xff0c;祥瑞满天。愿您如龙般矫健&#xff0c;事业腾飞&#xff1b;如龙鳞闪耀&#xff0c;生活美满。祝您龙年大吉&#xff0c;万事如意&#xff01; 龙年伊始&#xff0c;我给各位设计了一款原创的小游戏&#xff0c;话不多说&#xff0c;直…

B端系统:工作台页面,如何从平庸走向出众

Hi&#xff0c;大家好&#xff0c;我是贝格前端工场&#xff0c;从事8年前端开发的老司机。大家看过很多平庸的工作台页面&#xff0c;但是仔细分析过平庸的表现吗&#xff0c;仔细思考过如何实现出众的效果吗&#xff1f;这篇文章为你解读。 一、工作台页面是什么&#xff0c;…