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

一、引言

通过FFmpeg命令:

bash 复制代码
./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中:

cpp 复制代码
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 100

    if (!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中:

cpp 复制代码
/**
 * 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函数定义如下:

cpp 复制代码
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指向的二进制数据是否符合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字节):

cpp 复制代码
    int stat[TS_MAX_PACKET_SIZE];
    int stat_all = 0;
    int i;
    int best_score = 0;

    memset(stat, 0, packet_size * sizeof(*stat));

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

cpp 复制代码
if (buf[i] == 0x47)

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

cpp 复制代码
            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格式:

cpp 复制代码
            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格式的程度:

cpp 复制代码
    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格式:

cpp 复制代码
#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 :

cpp 复制代码
        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:

cpp 复制代码
        score = FFMAX3(score, dvhs_score, fec_score);
        sumscore += score;
        maxscore = FFMAX(maxscore, score);

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

cpp 复制代码
    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个字节实现的。

相关推荐
LNTON羚通1 小时前
入侵检测算法平台部署LiteAIServer视频智能分析平台行人入侵检测算法:科技守护安全的新篇章
科技·安全·目标检测·音视频·视频监控
liugddx7 小时前
Qwen2-VL:发票数据提取、视频聊天和使用 PDF 的多模态 RAG 的实践指南
人工智能·gpt·ai·pdf·开源·音视频
HarlanC10 小时前
FFmpeg转码音视频时间戳设置分析
ffmpeg·音视频·时间戳
一只老虎11 小时前
基于 OpenCV 和 dlib 方法进行视频人脸检测的研究
人工智能·opencv·音视频
那就可爱多一点点12 小时前
H5页面多个视频如何只同时播放一个?
前端·音视频
Mao.O13 小时前
WebRTC实现双端音视频聊天(Vue3 + SpringBoot)
音视频·webrtc
Deveuper15 小时前
UE5 UE4 播放视频没有声音解决
ue5·ue4·音视频
LNTON羚通20 小时前
算法定制LiteAIServer摄像机实时接入分析平台玩手机打电话检测算法:智能监控的新篇章
安全·目标检测·音视频·监控·视频监控
棱角~~1 天前
10款音频剪辑工具的个人实践体验感受!!
人工智能·经验分享·其他·音视频·学习方法