音视频入门基础: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的实现
一、引言 从《音视频入门基础:MPEG2-TS专题(9)——FFmpeg源码中,解码TS Header的实现》可以知道:FFmpeg源码中使用handle_packet函数来处理一个transport packet(TS包),该函数的前半…...
【数据结构】二叉树的性质和存储结构
性质 在二叉树的第i层上至多有2^{i-1}个结点,至少有1个结点 深度为k的二叉树至多有2^{k-1}个结点(k≥1),至少有k个结点 对任何一棵二叉树T,如果其叶子数为n0,度为2的结点数为n2,则n0n21 具有n个结点的完…...
gbase8s之查看锁表的sql
#只能看当前锁表的sql,看不到历史的。 #使用方法: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)
一、问题描述:在springboot项目中的resources中新建mybatis-config.xml文件时,从mybatis文档中复制的代码报错:URI 未注册(设置 | 语言和框架 | 架构和 DTD) 二、解决:在Springboot项目的设置->架构和DTD中添加 红色的网址&…...
Ubuntu上使用system()函数运行不需要输入密码
使用system()运行一些终端命令的时候,需要sudo权限,也就是必须输入密码,那么在程序自启动的时候就无法成功启动。如果设置Ubuntu下所有操作都不需要密码,安全性太低,所以我们可以将需要用到的终端指令给予无需输入密码…...
【MySQL】数据库必备知识:全面整合表的约束与深度解析
前言:本节内容讲述表的约束的相关内容。 表的约束博主将会通过两篇文章进行讲解, 这是第一篇上半部分。 讲到了约束概念。 以及几种常见约束。下面友友们开始学习吧! ps:友友们使用了mysql就可以放心观看喽! 目录 表的约束概念 …...
Windows下Docker快速安装使用教程
在当今软件开发和部署的世界中,Docker 已经成为一个不可或缺的工具。这里不对Docker进行详细阐述,需要系统学习Docker的伙伴可寻求更专业详细的教程或书籍学习。本文主要讲解Windows系统下Docker安装及使用。 一、环境准备 1.1检查电脑是否开启虚拟化 …...
PTA DS 6-2 另类堆栈 (C补全函数)
6-2 另类堆栈 分数 15 全屏浏览 切换布局 作者 DS课程组 单位 浙江大学 在栈的顺序存储实现中,另有一种方法是将Top定义为栈顶的上一个位置。请编写程序实现这种定义下堆栈的入栈、出栈操作。如何判断堆栈为空或者满? 函数接口定义: …...
rk3568之mpp开发笔记mpp移植到开发板
前言: 大家好,今天给大家介绍的内容是rk平台的mpp编解码这块的内容,在rk目前看到有三套框架涉及到编解码内容: 1、rkmedia 2、rockit 3、mpp 这三种不同形式的编解码方式,后面再做详细的框架对比,今天我…...
Vue解决跨域问题
要解决 Vue 项目的跨域问题并通过 vue.config.js 配置代理,可以按照以下步骤修改 vue.config.js 文件。你提供的代码大部分已经正确,只需要做一些格式上的调整。以下是正确的 vue.config.js 配置: // vue.config.jsmodule.exports {devServ…...
Kubernetes Nginx-Ingress | 禁用HSTS/禁止重定向到https
目录 前言禁用HSTS禁止重定向到https关闭 HSTS 和设置 ssl-redirect 为 false 的区别 前言 客户请求经过ingress到服务后,默认加上了strict-transport-security,导致客户服务跨域请求失败,具体Response Headers信息如下; 分析 n…...
TortoiseGit的下载、安装和配置
一、TortoiseGit的简介 tortoiseGit是一个开放的git版本控制系统的源客户端,支持Winxp/vista/win7.该软件功能和git一样 不同的是:git是命令行操作模式,tortoiseGit界面化操作模式,不用记git相关命令就可以直接操作,读…...
如何绕过IP禁令
网站、游戏和应用程序可以屏蔽特定IP地址,从而阻止使用该IP地址的任何人访问其服务。这称为IP禁令。管理员可以出于多种原因(例如发出过多请求或可疑活动)屏蔽IP地址。但是,这些禁令会使收集数据或访问在线内容变得更加困难。 一…...
Vue3的provide和inject实现多级传递的原理
先来看个demo,这个是父组件,代码如下: <template><ChildDemo /> </template><script setup> import ChildDemo from "./child.vue"; import { ref, provide } from "vue"; // 提供响应式的值 c…...
使用html2canvas实现前端截图
一、主要功能 网页截图:html2canvas通过读取DOM结构和元素的CSS样式,在客户端生成图像,不依赖于服务端的渲染。它可以将指定的DOM元素渲染为画布(canvas),并生成图像。多种输出格式:生成的图像…...
使用 Python 爬取某网站简历模板(bs4/lxml+协程)
使用 Python 爬取站长素材简历模板 简介 在本教程中,我们将学习如何使用 Python 来爬取站长素材网站上的简历模板。我们将使用requests和BeautifulSoup库来发送 HTTP 请求和解析 HTML 页面。本教程将分为两个部分:第一部分是使用BeautifulSoup的方法&am…...
深度学习模型中音频流式处理
音频流式处理的介绍 在现代深度学习应用中,音频处理是一个重要的领域,尤其是在语音识别、音乐生成和音频分类等任务中。流式处理(Streaming Processing)是一种有效的处理方式,它允许模型逐帧处理音频数据,…...
C语言(字符数组和字符指针)
字符串实现 在C语言中,表示一个字符串有以下两种形式: 用字符数组存放一个字符串。用字符指针指向一个字符串。 案例 #include <stdio.h>/*** 方式1:使用字符数组实现字符串*/ 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 …...
从WWDC看苹果产品发展的规律
WWDC 是苹果公司一年一度面向全球开发者的盛会,其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具,对过去十年 WWDC 主题演讲内容进行了系统化分析,形成了这份…...
【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器
——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的一体化测试平台,覆盖应用全生命周期测试需求,主要提供五大核心能力: 测试类型检测目标关键指标功能体验基…...
UE5 学习系列(三)创建和移动物体
这篇博客是该系列的第三篇,是在之前两篇博客的基础上展开,主要介绍如何在操作界面中创建和拖动物体,这篇博客跟随的视频链接如下: B 站视频:s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序
一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...
NLP学习路线图(二十三):长短期记忆网络(LSTM)
在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...
多种风格导航菜单 HTML 实现(附源码)
下面我将为您展示 6 种不同风格的导航菜单实现,每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...
QT: `long long` 类型转换为 `QString` 2025.6.5
在 Qt 中,将 long long 类型转换为 QString 可以通过以下两种常用方法实现: 方法 1:使用 QString::number() 直接调用 QString 的静态方法 number(),将数值转换为字符串: long long value 1234567890123456789LL; …...
【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具
第2章 虚拟机性能监控,故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令:jps [options] [hostid] 功能:本地虚拟机进程显示进程ID(与ps相同),可同时显示主类&#x…...
HashMap中的put方法执行流程(流程图)
1 put操作整体流程 HashMap 的 put 操作是其最核心的功能之一。在 JDK 1.8 及以后版本中,其主要逻辑封装在 putVal 这个内部方法中。整个过程大致如下: 初始判断与哈希计算: 首先,putVal 方法会检查当前的 table(也就…...
基于Java+MySQL实现(GUI)客户管理系统
客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息,对客户进行统一管理,可以把所有客户信息录入系统,进行维护和统计功能。可通过文件的方式保存相关录入数据,对…...
