当前位置: 首页 > 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 …...

从WWDC看苹果产品发展的规律

WWDC 是苹果公司一年一度面向全球开发者的盛会&#xff0c;其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具&#xff0c;对过去十年 WWDC 主题演讲内容进行了系统化分析&#xff0c;形成了这份…...

【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器

——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的​​一体化测试平台​​&#xff0c;覆盖应用全生命周期测试需求&#xff0c;主要提供五大核心能力&#xff1a; ​​测试类型​​​​检测目标​​​​关键指标​​功能体验基…...

UE5 学习系列(三)创建和移动物体

这篇博客是该系列的第三篇&#xff0c;是在之前两篇博客的基础上展开&#xff0c;主要介绍如何在操作界面中创建和拖动物体&#xff0c;这篇博客跟随的视频链接如下&#xff1a; B 站视频&#xff1a;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 种不同风格的导航菜单实现&#xff0c;每种都包含完整 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 中&#xff0c;将 long long 类型转换为 QString 可以通过以下两种常用方法实现&#xff1a; 方法 1&#xff1a;使用 QString::number() 直接调用 QString 的静态方法 number()&#xff0c;将数值转换为字符串&#xff1a; long long value 1234567890123456789LL; …...

【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具

第2章 虚拟机性能监控&#xff0c;故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令&#xff1a;jps [options] [hostid] 功能&#xff1a;本地虚拟机进程显示进程ID&#xff08;与ps相同&#xff09;&#xff0c;可同时显示主类&#x…...

HashMap中的put方法执行流程(流程图)

1 put操作整体流程 HashMap 的 put 操作是其最核心的功能之一。在 JDK 1.8 及以后版本中&#xff0c;其主要逻辑封装在 putVal 这个内部方法中。整个过程大致如下&#xff1a; 初始判断与哈希计算&#xff1a; 首先&#xff0c;putVal 方法会检查当前的 table&#xff08;也就…...

基于Java+MySQL实现(GUI)客户管理系统

客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息&#xff0c;对客户进行统一管理&#xff0c;可以把所有客户信息录入系统&#xff0c;进行维护和统计功能。可通过文件的方式保存相关录入数据&#xff0c;对…...