音视频入门基础:MPEG2-TS专题(5)——FFmpeg源码中,判断某文件是否为TS文件的实现

一、引言

通过FFmpeg命令:

./ffmpeg -i XXX.ts

可以判断出某个文件是否为TS文件:

所以FFmpeg是怎样判断出某个文件是否为TS文件呢?它内部其实是通过mpegts_probe函数来判断的。从《FFmpeg源码:av_probe_input_format3函数和AVInputFormat结构体分析(FFmpeg源码5.0.3版本)》和《7.0.1版本的FFmpeg源码中av_probe_input_format3函数和AVInputFormat结构体的改变》中可以知道:FFmpeg源码中实现容器格式检测的函数是av_probe_input_format3函数,其内部通过循环while ((fmt1 = av_demuxer_iterate(&i))) 拿到所有容器格式对应的AVInputFormat结构,然后通过score = fmt1->read_probe(&lpd)语句执行不同容器格式对应的解析函数,根据是否能被解析,以及匹配程度,来判断出这是哪种容器格式。而TS文件对应的解析函数就是mpegts_probe函数。

二、mpegts_probe函数的定义

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

static int mpegts_probe(const AVProbeData *p)
{const int size = p->buf_size;int maxscore = 0;int sumscore = 0;int i;int check_count = size / TS_FEC_PACKET_SIZE;
#define CHECK_COUNT 10
#define CHECK_BLOCK 100if (!check_count)return 0;for (i = 0; i<check_count; i+=CHECK_BLOCK) {int left = FFMIN(check_count - i, CHECK_BLOCK);int score      = analyze(p->buf + TS_PACKET_SIZE     *i, TS_PACKET_SIZE     *left, TS_PACKET_SIZE     , 1);int dvhs_score = analyze(p->buf + TS_DVHS_PACKET_SIZE*i, TS_DVHS_PACKET_SIZE*left, TS_DVHS_PACKET_SIZE, 1);int fec_score  = analyze(p->buf + TS_FEC_PACKET_SIZE *i, TS_FEC_PACKET_SIZE *left, TS_FEC_PACKET_SIZE , 1);score = FFMAX3(score, dvhs_score, fec_score);sumscore += score;maxscore = FFMAX(maxscore, score);}sumscore = sumscore * CHECK_COUNT / check_count;maxscore = maxscore * CHECK_COUNT / CHECK_BLOCK;ff_dlog(0, "TS score: %d %d\n", sumscore, maxscore);if        (check_count > CHECK_COUNT && sumscore > 6) {return AVPROBE_SCORE_MAX   + sumscore - CHECK_COUNT;} else if (check_count >= CHECK_COUNT && sumscore > 6) {return AVPROBE_SCORE_MAX/2 + sumscore - CHECK_COUNT;} else if (check_count >= CHECK_COUNT && maxscore > 6) {return AVPROBE_SCORE_MAX/2 + sumscore - CHECK_COUNT;} else if (sumscore > 6) {return 2;} else {return 0;}
}

该函数的作用就是检测某个文件是否为TS文件。

形参p:输入型参数,为AVProbeData类型的指针。

AVProbeData结构体声明在libavformat/avformat.h中:

/*** This structure contains the data a format has to probe a file.*/
typedef struct AVProbeData {const char *filename;unsigned char *buf; /**< Buffer must have AVPROBE_PADDING_SIZE of extra allocated bytes filled with zero. */int buf_size;       /**< Size of buf except extra allocated bytes */const char *mime_type; /**< mime_type, when known. */
} AVProbeData;

p->filename为:需要被推测格式的文件的路径。

p->buf:指向“存放从路径为p->filename的TS文件中读取出来的二进制数据”的缓冲区。

p->buf_size:缓冲区p->buf的大小,单位为字节。注:FFmpeg判断某个文件的格式时不会读取完整个文件,只会读取它前面的一部分,比如最开始的2048个字节。只要根据前面的这些字节就足够判断出它的格式了,所以p->buf_size的值一般就是2048。

p->mime_type:一般为NULL,可忽略。

返回值:返回一个类型为整形的分值。返回0表示该文件完全不符合TS格式。返回的值越接近100表示该文件越符合TS格式。

三、analyze函数的定义

mpegts_probe函数中,会调用analyze函数,analyze函数定义如下:

static int analyze(const uint8_t *buf, int size, int packet_size,int probe)
{int stat[TS_MAX_PACKET_SIZE];int stat_all = 0;int i;int best_score = 0;memset(stat, 0, packet_size * sizeof(*stat));for (i = 0; i < size - 3; i++) {if (buf[i] == 0x47) {int pid = AV_RB16(buf+1) & 0x1FFF;int asc = buf[i + 3] & 0x30;if (!probe || pid == 0x1FFF || asc) {int x = i % packet_size;stat[x]++;stat_all++;if (stat[x] > best_score) {best_score = stat[x];}}}}return best_score - FFMAX(stat_all - 10*best_score, 0)/10;
}

该函数的作用是:检测buf指向的码流的前size个字节,检测其是否符合每个transport packet(又称TS包,TS分组、传输流报文)的长度固定为packet_size个字节的TS格式。返回一个类型为整形的分值,返回的值越接近100表示越符合对应的TS格式。

从《音视频入门基础:MPEG2-TS专题(3)——TS Header简介》可以知道,TS格式有三种:分别为transport packet长度固定为188、192和204字节。

analyze函数中首先会定义一个元素个数为TS_MAX_PACKET_SIZE(值为204)的数组stat。因为加上了FEC前向纠错的情况下,一个transport packet长度为204字节;而普通的MPEG2-TS传输流中,一个transport packet长度固定为188字节。所以一个transport packet的最大长度为204字节,所以定义数组stat的元素个数为TS_MAX_PACKET_SIZE(值为204字节):

    int stat[TS_MAX_PACKET_SIZE];int stat_all = 0;int i;int best_score = 0;memset(stat, 0, packet_size * sizeof(*stat));

判断是否读取到了值为0x47的同步字节:

if (buf[i] == 0x47)

如果读取到了同步字节,读取TS Header中的PID属性,赋值给变量pid;读取TS Header中的adaptation_field_control属性,将该属性的值经过运算,赋值给变量asc:

            int pid = AV_RB16(buf+1) & 0x1FFF;int asc = buf[i + 3] & 0x30;

如果不是探测格式(!probe)或该transport packet为空包(pid == 0x1FFF)或适配域存在标志大于0(asc),通过取余运算,判断对应的二进制数据是否符合transport packet长度为packet_size个字节的TS格式:

            if (!probe || pid == 0x1FFF || asc) {int x = i % packet_size;stat[x]++;stat_all++;if (stat[x] > best_score) {best_score = stat[x];}}

不断循环,每符合一次“transport packet长度为packet_size个字节”的条件时,就让分值累加。最后返回最终得到的分值,该分值表示符合对应的TS格式的程度:

    for (i = 0; i < size - 3; i++) {//...}return best_score - FFMAX(stat_all - 10*best_score, 0)/10;

四、mpegts_probe函数的内部实现分析

宏TS_FEC_PACKET_SIZE、TS_DVHS_PACKET_SIZE、TS_PACKET_SIZE定义如下,分别对应transport packet长度固定为188、192和204字节的TS格式:

#define TS_FEC_PACKET_SIZE 204
#define TS_DVHS_PACKET_SIZE 192
#define TS_PACKET_SIZE 188
#define TS_MAX_PACKET_SIZE 204

mpegts_probe函数中会调用analyze函数。从上面对analyze函数的分析,我们可以知道:

1.语句int score = analyze(p->buf + TS_PACKET_SIZE     *i, TS_PACKET_SIZE     *left, TS_PACKET_SIZE     , 1)的作用是:检测“p->buf + TS_PACKET_SIZE*i”指向的码流符合transport packet长度固定为188字节的TS格式的程度,将对应的分数赋值给变量score。

2.语句int dvhs_score = analyze(p->buf + TS_DVHS_PACKET_SIZE*i, TS_DVHS_PACKET_SIZE*left, TS_DVHS_PACKET_SIZE, 1)的作用是:检测“p->buf + TS_DVHS_PACKET_SIZE*i”指向的码流符合transport packet长度固定为192字节的TS格式的程度,将对应的分数赋值给变量dvhs_score 。

3.语句int fec_score  = analyze(p->buf + TS_FEC_PACKET_SIZE *i, TS_FEC_PACKET_SIZE *left, TS_FEC_PACKET_SIZE , 1)的作用是:检测“p->buf + TS_FEC_PACKET_SIZE *i”指向的码流符合transport packet长度固定为204字节的TS格式的程度,将对应的分数赋值给变量fec_score  :

        int score      = analyze(p->buf + TS_PACKET_SIZE     *i, TS_PACKET_SIZE     *left, TS_PACKET_SIZE     , 1);int dvhs_score = analyze(p->buf + TS_DVHS_PACKET_SIZE*i, TS_DVHS_PACKET_SIZE*left, TS_DVHS_PACKET_SIZE, 1);int fec_score  = analyze(p->buf + TS_FEC_PACKET_SIZE *i, TS_FEC_PACKET_SIZE *left, TS_FEC_PACKET_SIZE , 1);

取变量score、dvhs_score、fec_score的最大值,即该码流最符合的那种TS格式的分数,赋值给变量score:

        score = FFMAX3(score, dvhs_score, fec_score);sumscore += score;maxscore = FFMAX(maxscore, score);

返回最终表示符合程度的分数:

    sumscore = sumscore * CHECK_COUNT / check_count;maxscore = maxscore * CHECK_COUNT / CHECK_BLOCK;ff_dlog(0, "TS score: %d %d\n", sumscore, maxscore);if        (check_count > CHECK_COUNT && sumscore > 6) {return AVPROBE_SCORE_MAX   + sumscore - CHECK_COUNT;} else if (check_count >= CHECK_COUNT && sumscore > 6) {return AVPROBE_SCORE_MAX/2 + sumscore - CHECK_COUNT;} else if (check_count >= CHECK_COUNT && maxscore > 6) {return AVPROBE_SCORE_MAX/2 + sumscore - CHECK_COUNT;} else if (sumscore > 6) {return 2;} else {return 0;}

五、总结

从上面我们可以知道,FFmpeg检测某个文件是否为TS文件,是通过判断是否读取到了同步字节,以及同步字节之间的transport packet长度是否固定为188或192或204个字节实现的。

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

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

相关文章

C++初阶学习第十一弹——list的用法和模拟实现

目录 一、list的使用 二.list的模拟实现 三.总结 一、list的使用 list的底层是双向链表结构&#xff0c;双向链表中每个元素存储在互不相关的独立节点中&#xff0c;在节点中通过指针指向 其前一个元素和后一个元素。 常见的list的函数的使用 std::list<int> It {1,…

Qlik Sense QVD 文件

QVD 文件 QVD (QlikView Data) 文件是包含从 Qlik Sense 或 QlikView 中所导出数据的表格的文件。QVD 是本地 Qlik 格式&#xff0c;只能由 Qlik Sense 或 QlikView 写入和读取。当从 Qlik Sense 脚本中读取数据时&#xff0c;该文件格式可提升速度&#xff0c;同时又非常紧凑…

攻防世界 Web新手练习区

GFSJ0475 get_post 获取在线场景后&#xff0c;点开网址 依据提示在搜索框输入信息 给出第二条提示信息 打开hackbar&#xff0c;将网址Load下来&#xff0c;勾选Post data&#xff0c;在下方输入框输入b2 点击Execute 出现flag值 GFSJ0476 robots 打开御剑扫描域名&#…

MySQL —— explain 查看执行计划与 MySQL 优化

文章目录 explain 查看执行计划explain 的作用——查看执行计划explain 查看执行计划返回信息详解表的读取顺序&#xff08;id&#xff09;查询类型&#xff08;select_type&#xff09;数据库表名&#xff08;table&#xff09;联接类型&#xff08;type&#xff09;可用的索引…

前端研发高德地图,如何根据经纬度获取地点名称和两点之间的距离?

地理编码与逆地理编码 引入插件&#xff0c;此示例采用异步引入&#xff0c;更多引入方式 https://lbs.amap.com/api/javascript-api-v2/guide/abc/plugins AMap.plugin("AMap.Geocoder", function () {var geocoder new AMap.Geocoder({city: "010", /…

React(二)

文章目录 项目地址七、数据流7.1 子组件传递数据给父组件7.1.1 方式一:給父设置回调函数,传递给子7.1.2 方式二:直接将父的setState传递给子7.2 给props传递jsx7.2.1 方式一:直接传递组件给子类7.2.2 方式二:传递函数给子组件7.3 props类型验证7.4 props的多层传递7.5 cla…

SpringBootTest常见错误解决

1.启动类所在包错误 问题 由于启动类所在包与需要自动注入的类的包不在一个包下&#xff1a; 启动类所在包&#xff1a; com.exmaple.test_02 但是对于需要注入的类却不在com.exmaple.test_02下或者其子包下&#xff0c;就会导致启动类无法扫描到该类&#xff0c;从而无法对…

Redis面试篇笔记(持续更新)

一、redis主从集群 单节点redis的并发能力是由上限的&#xff0c;要进一步提高redis的并发能力可以搭建主从集群&#xff0c;实现读写分离&#xff0c;一主多从&#xff0c;主节点写数据&#xff0c;从节点读数据 部署redis主从节点的docker-compose文件命令解析 version: &q…

ISUP协议视频平台EasyCVR私有化视频平台新能源汽车充电停车管理方案的创新与实践

在环保意识提升和能源转型的大背景下&#xff0c;新能源汽车作为低碳出行的选择&#xff0c;正在全球迅速推广。但这种快速增长也引发了充电基础设施短缺和停车秩序混乱等挑战&#xff0c;特别是在城市中心和人口密集的居住区&#xff0c;这些问题更加明显。因此&#xff0c;开…

goland单元测试

一、单元测试的概念 1.1 什么是单元测试&#xff0c;有什么用&#xff1f; 单元测试是针对于函数的测试&#xff0c;用来保证该函数的逻辑正确性。 1.2 单元测试的要求&#xff1f; 1. 单元测试在正式上线之前应该全部自动执行&#xff0c;并且需要保证全部通过 2. 单元测试需…

连接数据库:通过链和代理查询鲜花信息

目录 新的数据库查询范式 实战案例背景信息 创建数据库表 用 Chain 查询数据库 用 Agent 查询数据库 一直以来&#xff0c;在计算机编程和数据库管理领域&#xff0c;所有的操作都需要通过严格、专业且结构化的语法来完成。这就是结构化查询语言&#xff08;SQL&#xff0…

【c++丨STL】stack和queue的使用及模拟实现

&#x1f31f;&#x1f31f;作者主页&#xff1a;ephemerals__ &#x1f31f;&#x1f31f;所属专栏&#xff1a;C、STL 目录 前言 一、什么是容器适配器 二、stack的使用及模拟实现 1. stack的使用 empty size top push和pop swap 2. stack的模拟实现 三、queue的…

aws上安装ssm-agent

aws-cloudwatch 连接机器 下载ssm-agent aws-ec2 安装ssm-agent aws-linux安装ssm-agent 使用 SSM 代理查找 AMI 预装 先运行&#xff1a;systemctl status amazon-ssm-agent 查看sshm-agent的状态。 然后安装提示&#xff0c;执行 systemctl start amazon-ssm-agent 启动即…

百度世界2024:智能体引领AI应用新纪元

在近日盛大举行的百度世界2024大会上&#xff0c;百度创始人李彦宏以一场题为“文心一言”的精彩演讲&#xff0c;再次将全球科技界的目光聚焦于人工智能&#xff08;AI&#xff09;的无限可能。作为一名科技自媒体&#xff0c;我深感这场演讲不仅是对百度AI技术实力的一次全面…

纯血鸿蒙NEXT-组件导航 (Navigation)

Navigation组件是路由导航的根视图容器&#xff0c;一般作为Page页面的根容器使用&#xff0c;其内部默认包含了标题栏、内容区和工具栏&#xff0c;其中内容区默认首页显示导航内容&#xff08;Navigation的子组件&#xff09;或非首页显示&#xff08;NavDestination的子组件…

C语言 | Leetcode C语言题解之第564题寻找最近的回文数

题目&#xff1a; 题解&#xff1a; #define MAX_STR_LEN 32 typedef unsigned long long ULL;void reverseStr(char * str) {int n strlen(str);for (int l 0, r n-1; l < r; l, r--) {char c str[l];str[l] str[r];str[r] c;} }ULL * getCandidates(const char * n…

docker学习笔记跟常用命令总结

Docker简介 Docker是一个用于构建运行传送应用程序的平台 镜像 将应用所需的函数库、依赖、配置等与应用一起打包得到的就是镜 镜像结构 镜像管理命令 命令说明docker pull拉取镜像docker push推送镜像docker images查看本地镜像docker rmi删除本地镜像docker image prune…

MySQL 中 InnoDB 支持的四种事务隔离级别名称,以及逐级之间的区别?

MySQL中的InnoDB存储引擎支持四种事务隔离级别&#xff0c;这些级别定义了事务在并发环境中的行为和相互之间的可见性。以下是这四种隔离级别的名称以及它们之间的区别&#xff1a; 读未提交&#xff08;Read Uncommitted&#xff09; 特点&#xff1a;这是最低的隔离级别&…

【力扣热题100】[Java版] 刷题笔记-226. 翻转二叉树

题目:226. 翻转二叉树 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 解题思路 二叉树翻转&#xff0c;可以通过递归进行交换。 解题过程 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeN…

Android kotlin之配置kapt编译器插件

配置项目目录下的gradle/libs.versions.toml文件&#xff0c;添加kapt配置项&#xff1a; 在模块目录下build.gradle.kt中增加 plugins {alias(libs.plugins.android.application)alias(libs.plugins.jetbrains.kotlin.android)// 增加该行alias(libs.plugins.jetbrains.kotl…