当前位置: 首页 > news >正文

音视频入门基础:MPEG2-TS专题(12)—— FFmpeg源码中,把各个transport packet组合成一个Section的实现

一、引言

从《音视频入门基础:MPEG2-TS专题(9)——FFmpeg源码中,解码TS Header的实现》可以知道:FFmpeg源码中使用handle_packet函数来处理一个transport packet(TS包),该函数的前半部分实现解析一个transport packet的TS Header。而在解析完TS Header后,handle_packet函数内部会调用write_section_data函数来把各个transport packet组合成一个Section,并在得到一个完整的Section后调用对应的解析PSI/SI表的方法:

/* handle one TS packet */
static int handle_packet(MpegTSContext *ts, const uint8_t *packet, int64_t pos)
{
//.../* if past the end of packet, ignore */p_end = packet + TS_PACKET_SIZE;if (p >= p_end || !has_payload)return 0;if (pos >= 0) {av_assert0(pos >= TS_PACKET_SIZE);ts->pos47_full = pos - TS_PACKET_SIZE;}if (tss->type == MPEGTS_SECTION) {if (is_start) {/* pointer field present */len = *p++;if (len > p_end - p)return 0;if (len && cc_ok) {/* write remaining section bytes */write_section_data(ts, tss,p, len, 0);/* check whether filter has been closed */if (!ts->pids[pid])return 0;}p += len;if (p < p_end) {write_section_data(ts, tss,p, p_end - p, 1);}} else {if (cc_ok) {write_section_data(ts, tss,p, p_end - p, 0);}}// stop find_stream_info from waiting for more streams// when all programs have received a PMTif (ts->stream->ctx_flags & AVFMTCTX_NOHEADER && ts->scan_all_pmts <= 0) {int i;for (i = 0; i < ts->nb_prg; i++) {if (!ts->prg[i].pmt_found)break;}if (i == ts->nb_prg && ts->nb_prg > 0) {av_log(ts->stream, AV_LOG_DEBUG, "All programs have pmt, headers found\n");ts->stream->ctx_flags &= ~AVFMTCTX_NOHEADER;}}} 
//...
}

上述代码中,首先让指针p_end指向该transport packet的末尾:

    /* if past the end of packet, ignore */p_end = packet + TS_PACKET_SIZE;

如果已经读取到了该transport packet的末尾(p >= p_end)或者载荷不存在(!has_payload),handle_packet函数直接返回,不继续进行处理:

    if (p >= p_end || !has_payload)return 0;

如果该transport packet不是PES分组,是Section数据(tss->type == MPEGTS_SECTION),并且TS Header中的payload_unit_start_indicator属性的值为1(is_start为真),表示该transport packet是一个Section的首包,这时TS Header后面还会有一个长度为1字节的pointer_field属性,通过语句:len = *p++读取该pointer_field属性,让指针p指向该transport packet的有效数据。根据有没有pointer_field属性和pointer_field的值是多少,调用write_section_data函数并传入不同参数:

    if (tss->type == MPEGTS_SECTION) {if (is_start) {/* pointer field present */len = *p++;if (len > p_end - p)return 0;if (len && cc_ok) {/* write remaining section bytes */write_section_data(ts, tss,p, len, 0);/* check whether filter has been closed */if (!ts->pids[pid])return 0;}p += len;if (p < p_end) {write_section_data(ts, tss,p, p_end - p, 1);}}//...
}

二、write_section_data函数

(一)write_section_data函数的定义

​​​​write_section_data函数定义在FFmpeg源码(本文演示用的FFmpeg源码版本为7.0.1)的源文件libavformat/mpegts.c中:

/***  Assemble PES packets out of TS packets, and then call the "section_cb"*  function when they are complete.*/
static void write_section_data(MpegTSContext *ts, MpegTSFilter *tss1,const uint8_t *buf, int buf_size, int is_start)
{MpegTSSectionFilter *tss = &tss1->u.section_filter;uint8_t *cur_section_buf = NULL;int len, offset;if (is_start) {memcpy(tss->section_buf, buf, buf_size);tss->section_index = buf_size;tss->section_h_size = -1;tss->end_of_section_reached = 0;} else {if (tss->end_of_section_reached)return;len = MAX_SECTION_SIZE - tss->section_index;if (buf_size < len)len = buf_size;memcpy(tss->section_buf + tss->section_index, buf, len);tss->section_index += len;}offset = 0;cur_section_buf = tss->section_buf;while (cur_section_buf - tss->section_buf < MAX_SECTION_SIZE && cur_section_buf[0] != 0xff) {/* compute section length if possible */if (tss->section_h_size == -1 && tss->section_index - offset >= 3) {len = (AV_RB16(cur_section_buf + 1) & 0xfff) + 3;if (len > MAX_SECTION_SIZE)return;tss->section_h_size = len;}if (tss->section_h_size != -1 &&tss->section_index >= offset + tss->section_h_size) {int crc_valid = 1;tss->end_of_section_reached = 1;if (tss->check_crc) {crc_valid = !av_crc(av_crc_get_table(AV_CRC_32_IEEE), -1, cur_section_buf, tss->section_h_size);if (tss->section_h_size >= 4)tss->crc = AV_RB32(cur_section_buf + tss->section_h_size - 4);if (crc_valid) {ts->crc_validity[ tss1->pid ] = 100;}else if (ts->crc_validity[ tss1->pid ] > -10) {ts->crc_validity[ tss1->pid ]--;}elsecrc_valid = 2;}if (crc_valid) {tss->section_cb(tss1, cur_section_buf, tss->section_h_size);if (crc_valid != 1)tss->last_ver = -1;}cur_section_buf += tss->section_h_size;offset += tss->section_h_size;tss->section_h_size = -1;} else {tss->section_h_size = -1;tss->end_of_section_reached = 0;break;}}
}

该函数的作用是:把各个transport packet组合成一个Section,并在得到一个完整的Section后调用对应的解析PSI/SI表的方法。

形参ts:既是输入型参数也是输出型参数,指向一个MpegTSContext类型变量。

形参tss1:既是输入型参数也是输出型参数,指向一个MpegTSFilter类型变量。

形参buf:指针,输入型参数,指向某个transport packet去掉TS Header和pointer_field后的有效数据。

形参buf_size:输入型参数,该transport packet有效数据的长度,单位为字节。

形参is_start:输入型参数,该transport packet是否为一个Section的首包。值为1表示是,值为0表示否。

返回值:无

(二)write_section_data函数的内部实现

write_section_data函数中,首先判断该transport packet是否为一个Section的首包。如果是,通过语句:memcpy(tss->section_buf, buf, buf_size)将该transport packet去掉TS Header和pointer_field后的有效数据拷贝到tss->section_buf中。tss->section_buf存放一个Section的数据,一个Section可能包含一个或多个transport packet。tss->section_index为该Section的累计长度,通过语句:tss->section_index = buf_size让该Section的累计长度等于该transport packet有效数据的长度:

    if (is_start) {memcpy(tss->section_buf, buf, buf_size);tss->section_index = buf_size;tss->section_h_size = -1;tss->end_of_section_reached = 0;}

如果该transport packet不是一个Section的首包,并且还未到达该Section的末尾,通过语句:memcpy(tss->section_buf + tss->section_index, buf, len)将该transport packet的有效数据拼接到tss->section_buf的末尾。通过语句:tss->section_index += len让该Section的累计长度增加:

    if (is_start) {//...} else {if (tss->end_of_section_reached)return;len = MAX_SECTION_SIZE - tss->section_index;if (buf_size < len)len = buf_size;memcpy(tss->section_buf + tss->section_index, buf, len);tss->section_index += len;}

读取该Section的Section Header中的section_length属性,section_length属性的值加3就是整个Section的长度,将整个Section的长度赋值给变量len和tss->section_h_size:

        /* compute section length if possible */if (tss->section_h_size == -1 && tss->section_index - offset >= 3) {len = (AV_RB16(cur_section_buf + 1) & 0xfff) + 3;if (len > MAX_SECTION_SIZE)return;tss->section_h_size = len;}

如果已经读取到该Section的末尾,并且需要检查CRC校验(tss->check_crc为真),通过语句:crc_valid = !av_crc(av_crc_get_table(AV_CRC_32_IEEE), -1, cur_section_buf, tss->section_h_size)判断该Section的CRC校验是否正确(关于av_crc函数用法可以参考:《FFmpeg源码中,计算CRC校验的实现》)。通过语句:tss->crc = AV_RB32(cur_section_buf + tss->section_h_size - 4)获取到该Section的CRC校验:

if (tss->section_h_size != -1 &&tss->section_index >= offset + tss->section_h_size) {int crc_valid = 1;tss->end_of_section_reached = 1;if (tss->check_crc) {crc_valid = !av_crc(av_crc_get_table(AV_CRC_32_IEEE), -1, cur_section_buf, tss->section_h_size);if (tss->section_h_size >= 4)tss->crc = AV_RB32(cur_section_buf + tss->section_h_size - 4);if (crc_valid) {ts->crc_validity[ tss1->pid ] = 100;}else if (ts->crc_validity[ tss1->pid ] > -10) {ts->crc_validity[ tss1->pid ]--;}elsecrc_valid = 2;}

如果CRC校验正确,根据Section类型调用对应的解析PSI/SI表的方法。tss->section_cb是函数指针,指向不同PSI/SI表的解析函数,比如SDT表对应的解析函数是sdt_cb,PAT表对应的解析函数是pat_cb,PMT表对应的解析函数是pmt_cb:

            if (crc_valid) {tss->section_cb(tss1, cur_section_buf, tss->section_h_size);if (crc_valid != 1)tss->last_ver = -1;}

三、总结

通过上面的代码分析可以看出来,TS流中使用Section分段传输的意义在于:只要接收到一个Section的完整数据就可以进行解析,而不需要接收到完整的PSI/SI表表时才开始解析工作。

相关文章:

音视频入门基础:MPEG2-TS专题(12)—— FFmpeg源码中,把各个transport packet组合成一个Section的实现

一、引言 从《音视频入门基础&#xff1a;MPEG2-TS专题&#xff08;9&#xff09;——FFmpeg源码中&#xff0c;解码TS Header的实现》可以知道&#xff1a;FFmpeg源码中使用handle_packet函数来处理一个transport packet&#xff08;TS包&#xff09;&#xff0c;该函数的前半…...

【数据结构】二叉树的性质和存储结构

性质 在二叉树的第i层上至多有2^{i-1}个结点,至少有1个结点 深度为k的二叉树至多有2^{k-1}个结点&#xff08;k≥1&#xff09;&#xff0c;至少有k个结点 对任何一棵二叉树T&#xff0c;如果其叶子数为n0&#xff0c;度为2的结点数为n2&#xff0c;则n0n21 具有n个结点的完…...

gbase8s之查看锁表的sql

#只能看当前锁表的sql&#xff0c;看不到历史的。 #使用方法&#xff1a;sh 脚本文件名 库名 表名 database$1 table$2 hexoncheck -pt $database:$table|grep -i partnum|awk {printf ("%x|",$3)} #echo $hex #echo ${hex%?} #ownonstat -k |grep -iE ${he…...

URI 未注册(设置 语言和框架 架构和 DTD)

一、问题描述&#xff1a;在springboot项目中的resources中新建mybatis-config.xml文件时&#xff0c;从mybatis文档中复制的代码报错&#xff1a;URI 未注册(设置 | 语言和框架 | 架构和 DTD) 二、解决&#xff1a;在Springboot项目的设置->架构和DTD中添加 红色的网址&…...

Ubuntu上使用system()函数运行不需要输入密码

使用system()运行一些终端命令的时候&#xff0c;需要sudo权限&#xff0c;也就是必须输入密码&#xff0c;那么在程序自启动的时候就无法成功启动。如果设置Ubuntu下所有操作都不需要密码&#xff0c;安全性太低&#xff0c;所以我们可以将需要用到的终端指令给予无需输入密码…...

【MySQL】数据库必备知识:全面整合表的约束与深度解析

前言&#xff1a;本节内容讲述表的约束的相关内容。 表的约束博主将会通过两篇文章进行讲解&#xff0c; 这是第一篇上半部分。 讲到了约束概念。 以及几种常见约束。下面友友们开始学习吧&#xff01; ps:友友们使用了mysql就可以放心观看喽&#xff01; 目录 表的约束概念 …...

Windows下Docker快速安装使用教程

在当今软件开发和部署的世界中&#xff0c;Docker 已经成为一个不可或缺的工具。这里不对Docker进行详细阐述&#xff0c;需要系统学习Docker的伙伴可寻求更专业详细的教程或书籍学习。本文主要讲解Windows系统下Docker安装及使用。 一、环境准备 1.1检查电脑是否开启虚拟化 …...

PTA DS 6-2 另类堆栈 (C补全函数)

6-2 另类堆栈 分数 15 全屏浏览 切换布局 作者 DS课程组 单位 浙江大学 在栈的顺序存储实现中&#xff0c;另有一种方法是将Top定义为栈顶的上一个位置。请编写程序实现这种定义下堆栈的入栈、出栈操作。如何判断堆栈为空或者满&#xff1f; 函数接口定义&#xff1a; …...

rk3568之mpp开发笔记mpp移植到开发板

前言&#xff1a; 大家好&#xff0c;今天给大家介绍的内容是rk平台的mpp编解码这块的内容&#xff0c;在rk目前看到有三套框架涉及到编解码内容&#xff1a; 1、rkmedia 2、rockit 3、mpp 这三种不同形式的编解码方式&#xff0c;后面再做详细的框架对比&#xff0c;今天我…...

Vue解决跨域问题

要解决 Vue 项目的跨域问题并通过 vue.config.js 配置代理&#xff0c;可以按照以下步骤修改 vue.config.js 文件。你提供的代码大部分已经正确&#xff0c;只需要做一些格式上的调整。以下是正确的 vue.config.js 配置&#xff1a; // vue.config.jsmodule.exports {devServ…...

Kubernetes Nginx-Ingress | 禁用HSTS/禁止重定向到https

目录 前言禁用HSTS禁止重定向到https关闭 HSTS 和设置 ssl-redirect 为 false 的区别 前言 客户请求经过ingress到服务后&#xff0c;默认加上了strict-transport-security&#xff0c;导致客户服务跨域请求失败&#xff0c;具体Response Headers信息如下&#xff1b; 分析 n…...

TortoiseGit的下载、安装和配置

一、TortoiseGit的简介 tortoiseGit是一个开放的git版本控制系统的源客户端&#xff0c;支持Winxp/vista/win7.该软件功能和git一样 不同的是&#xff1a;git是命令行操作模式&#xff0c;tortoiseGit界面化操作模式&#xff0c;不用记git相关命令就可以直接操作&#xff0c;读…...

如何绕过IP禁令

网站、游戏和应用程序可以屏蔽特定IP地址&#xff0c;从而阻止使用该IP地址的任何人访问其服务。这称为IP禁令。管理员可以出于多种原因&#xff08;例如发出过多请求或可疑活动&#xff09;屏蔽IP地址。但是&#xff0c;这些禁令会使收集数据或访问在线内容变得更加困难。 一…...

Vue3的provide和inject实现多级传递的原理

先来看个demo&#xff0c;这个是父组件&#xff0c;代码如下&#xff1a; <template><ChildDemo /> </template><script setup> import ChildDemo from "./child.vue"; import { ref, provide } from "vue"; // 提供响应式的值 c…...

使用html2canvas实现前端截图

一、主要功能 网页截图&#xff1a;html2canvas通过读取DOM结构和元素的CSS样式&#xff0c;在客户端生成图像&#xff0c;不依赖于服务端的渲染。它可以将指定的DOM元素渲染为画布&#xff08;canvas&#xff09;&#xff0c;并生成图像。多种输出格式&#xff1a;生成的图像…...

使用 Python 爬取某网站简历模板(bs4/lxml+协程)

使用 Python 爬取站长素材简历模板 简介 在本教程中&#xff0c;我们将学习如何使用 Python 来爬取站长素材网站上的简历模板。我们将使用requests和BeautifulSoup库来发送 HTTP 请求和解析 HTML 页面。本教程将分为两个部分&#xff1a;第一部分是使用BeautifulSoup的方法&am…...

深度学习模型中音频流式处理

音频流式处理的介绍 在现代深度学习应用中&#xff0c;音频处理是一个重要的领域&#xff0c;尤其是在语音识别、音乐生成和音频分类等任务中。流式处理&#xff08;Streaming Processing&#xff09;是一种有效的处理方式&#xff0c;它允许模型逐帧处理音频数据&#xff0c;…...

C语言(字符数组和字符指针)

字符串实现 在C语言中&#xff0c;表示一个字符串有以下两种形式&#xff1a; 用字符数组存放一个字符串。用字符指针指向一个字符串。 案例 #include <stdio.h>/*** 方式1&#xff1a;使用字符数组实现字符串*/ void str_test1(){// 定义一个伪字符串char str[] &q…...

SkyWalking Helm Chart 4.7.0 安装、配置

https://skywalking.apache.org/events/release-apache-skywalking-kubernetes-helm-chart-4.7.0/https://github.com/apache/skywalking-helm/tree/v4.7.0https://skywalking.apache.org/zh/2020-04-19-skywalking-quick-start/简介 skywalking 是分布式系统的 APM(Applicat…...

微搭低代码AI组件单词消消乐从0到1实践

目录 1 为什么要开发单词消消乐2 需要具备什么功能3 采用什么技术方案实现4 逻辑设计4.1 数据结构设计4.2 游戏的核心逻辑4.3 数据设计 5 代码详解5.1 导入依赖5.2 定义函数组件5.3 数据初始化5.4 状态定义5.5 打乱解释的逻辑5.6 定义选择单词的函数5.7 定义选择解释的函数5.8 …...

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…...

Cursor实现用excel数据填充word模版的方法

cursor主页&#xff1a;https://www.cursor.com/ 任务目标&#xff1a;把excel格式的数据里的单元格&#xff0c;按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例&#xff0c;…...

python打卡day49

知识点回顾&#xff1a; 通道注意力模块复习空间注意力模块CBAM的定义 作业&#xff1a;尝试对今天的模型检查参数数目&#xff0c;并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...

rknn优化教程(二)

文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK&#xff0c;开始写第二篇的内容了。这篇博客主要能写一下&#xff1a; 如何给一些三方库按照xmake方式进行封装&#xff0c;供调用如何按…...

java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别

UnsatisfiedLinkError 在对接硬件设备中&#xff0c;我们会遇到使用 java 调用 dll文件 的情况&#xff0c;此时大概率出现UnsatisfiedLinkError链接错误&#xff0c;原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用&#xff0c;结果 dll 未实现 JNI 协…...

Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务

通过akshare库&#xff0c;获取股票数据&#xff0c;并生成TabPFN这个模型 可以识别、处理的格式&#xff0c;写一个完整的预处理示例&#xff0c;并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务&#xff0c;进行预测并输…...

全球首个30米分辨率湿地数据集(2000—2022)

数据简介 今天我们分享的数据是全球30米分辨率湿地数据集&#xff0c;包含8种湿地亚类&#xff0c;该数据以0.5X0.5的瓦片存储&#xff0c;我们整理了所有属于中国的瓦片名称与其对应省份&#xff0c;方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...

Linux云原生安全:零信任架构与机密计算

Linux云原生安全&#xff1a;零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言&#xff1a;云原生安全的范式革命 随着云原生技术的普及&#xff0c;安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测&#xff0c;到2025年&#xff0c;零信任架构将成为超…...

论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)

宇树机器人多姿态起立控制强化学习框架论文解析 论文解读&#xff1a;交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架&#xff08;一&#xff09; 论文解读&#xff1a;交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...

零基础设计模式——行为型模式 - 责任链模式

第四部分&#xff1a;行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习&#xff01;行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想&#xff1a;使多个对象都有机会处…...