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

一、引言

通过FFmpeg命令:

bash 复制代码
./ffmpeg -i XXX.ps

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

所以FFmpeg是怎样判断出某个文件是否为PS文件呢?它内部其实是通过mpegps_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)语句执行不同容器格式对应的解析函数,根据是否能被解析,以及匹配程度,来判断出这是哪种容器格式。而PS文件对应的解析函数就是mpegps_probe函数。

二、mpegps_probe函数的定义

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

cpp 复制代码
static int mpegps_probe(const AVProbeData *p)
{
    uint32_t code = -1;
    int i;
    int sys = 0, pspack = 0, priv1 = 0, vid = 0;
    int audio = 0, invalid = 0, score = 0;
    int endpes = 0;

    for (i = 0; i < p->buf_size; i++) {
        code = (code << 8) + p->buf[i];
        if ((code & 0xffffff00) == 0x100) {
            int len  = p->buf[i + 1] << 8 | p->buf[i + 2];
            int pes  = endpes <= i && check_pes(p->buf + i, p->buf + p->buf_size);
            int pack = check_pack_header(p->buf + i);

            if (code == SYSTEM_HEADER_START_CODE)
                sys++;
            else if (code == PACK_START_CODE && pack)
                pspack++;
            else if ((code & 0xf0) == VIDEO_ID && pes) {
                endpes = i + len;
                vid++;
            }
            // skip pes payload to avoid start code emulation for private
            // and audio streams
            else if ((code & 0xe0) == AUDIO_ID &&  pes) {audio++; i+=len;}
            else if (code == PRIVATE_STREAM_1  &&  pes) {priv1++; i+=len;}
            else if (code == 0x1fd             &&  pes) vid++; //VC1

            else if ((code & 0xf0) == VIDEO_ID && !pes) invalid++;
            else if ((code & 0xe0) == AUDIO_ID && !pes) invalid++;
            else if (code == PRIVATE_STREAM_1  && !pes) invalid++;
        }
    }

    if (vid + audio > invalid + 1) /* invalid VDR files nd short PES streams */
        score = AVPROBE_SCORE_EXTENSION / 2;

//     av_log(NULL, AV_LOG_ERROR, "vid:%d aud:%d sys:%d pspack:%d invalid:%d size:%d \n",
//            vid, audio, sys, pspack, invalid, p->buf_size);

    if (sys > invalid && sys * 9 <= pspack * 10)
        return (audio > 12 || vid > 3 || pspack > 2) ? AVPROBE_SCORE_EXTENSION + 2
                                                     : AVPROBE_SCORE_EXTENSION / 2 + (audio + vid + pspack > 1); // 1 more than mp3
    if (pspack > invalid && (priv1 + vid + audio) * 10 >= pspack * 9)
        return pspack > 2 ? AVPROBE_SCORE_EXTENSION + 2
                          : AVPROBE_SCORE_EXTENSION / 2; // 1 more than .mpg
    if ((!!vid ^ !!audio) && (audio > 4 || vid > 1) && !sys &&
        !pspack && p->buf_size > 2048 && vid + audio > invalid) /* PES stream */
        return (audio > 12 || vid > 6 + 2 * invalid) ? AVPROBE_SCORE_EXTENSION + 2
                                                     : AVPROBE_SCORE_EXTENSION / 2;

    // 02-Penguin.flac has sys:0 priv1:0 pspack:0 vid:0 audio:1
    // mp3_misidentified_2.mp3 has sys:0 priv1:0 pspack:0 vid:0 audio:6
    // Have\ Yourself\ a\ Merry\ Little\ Christmas.mp3 0 0 0 5 0 1 len:21618
    return score;
}

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

形参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的PS文件中读取出来的二进制数据"的缓冲区。

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

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

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

三、mpegps_probe函数的内部实现分析

mpegps_probe函数中首先会定义整形变量。其中,变量sys表示这段长度为p->buf_size的码流中system header的个数;pspack为这段码流中pack header的个数;priv1为这段码流中私有流的个数;vid为这段码流中视频流的个数:

cpp 复制代码
    int sys = 0, pspack = 0, priv1 = 0, vid = 0;

不断读取出存放在数组p->bufi中的二进制码流数据:

cpp 复制代码
    for (i = 0; i < p->buf_size; i++) {
        code = (code << 8) + p->buf[i];
        if ((code & 0xffffff00) == 0x100) {
    //...
        }
    }

宏SYSTEM_HEADER_START_CODE定义在libavformat/mpeg.h中,值为0x000001bb,表示system header的起始码(system header的system_header_start_code属性):

cpp 复制代码
#define SYSTEM_HEADER_START_CODE    ((unsigned int)0x000001bb)

根据是否是system header的起始码,判断是否读取到了PS流的system header,如果读取到了,让变量sys的值加1:

cpp 复制代码
            if (code == SYSTEM_HEADER_START_CODE)
                sys++;

宏PACK_START_CODE定义在libavformat/mpeg.h中,值为0x000001ba,表示pack header的起始码(pack header的pack_start_code属性):

cpp 复制代码
#define PACK_START_CODE             ((unsigned int)0x000001ba)

根据是否是pack header的起始码,判断是否读取到了PS流的pack header,如果读取到了,让变量pspack的值加1:

cpp 复制代码
          int pack = check_pack_header(p->buf + i);  
        //...
          else if (code == PACK_START_CODE && pack)
                pspack++;

检查是否读取到了PES流。如果读取到了,通过PES packet中的stream_id属性判断里面的ES流是否为视频流。如果是视频流,让变量vid的值加1:

cpp 复制代码
            int pes  = endpes <= i && check_pes(p->buf + i, p->buf + p->buf_size);
        //...
            else if ((code & 0xf0) == VIDEO_ID && pes) {
                endpes = i + len;
                vid++;
            }

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

cpp 复制代码
    if (sys > invalid && sys * 9 <= pspack * 10)
        return (audio > 12 || vid > 3 || pspack > 2) ? AVPROBE_SCORE_EXTENSION + 2
                                                     : AVPROBE_SCORE_EXTENSION / 2 + (audio + vid + pspack > 1); // 1 more than mp3
    if (pspack > invalid && (priv1 + vid + audio) * 10 >= pspack * 9)
        return pspack > 2 ? AVPROBE_SCORE_EXTENSION + 2
                          : AVPROBE_SCORE_EXTENSION / 2; // 1 more than .mpg
    if ((!!vid ^ !!audio) && (audio > 4 || vid > 1) && !sys &&
        !pspack && p->buf_size > 2048 && vid + audio > invalid) /* PES stream */
        return (audio > 12 || vid > 6 + 2 * invalid) ? AVPROBE_SCORE_EXTENSION + 2
                                                     : AVPROBE_SCORE_EXTENSION / 2;

    // 02-Penguin.flac has sys:0 priv1:0 pspack:0 vid:0 audio:1
    // mp3_misidentified_2.mp3 has sys:0 priv1:0 pspack:0 vid:0 audio:6
    // Have\ Yourself\ a\ Merry\ Little\ Christmas.mp3 0 0 0 5 0 1 len:21618
    return score;

四、总结

从上面我们可以知道,FFmpeg检测某个文件是否为PS文件,是通过判断这段码流中,读取到的system header、pack header、PES流中视频流的个数等信息实现的。

相关推荐
勤劳X码农27 分钟前
2026年电商视频AI配音软件怎么选?
人工智能·音视频
可乐鸡翅yeah_1 小时前
fmp4 碎片 MP4 格式 HLS 流,和传统 TS 分片有什么不一样
开发语言·javascript·ios·音视频·safari·m3u8·m3u8在线播放
m0_738185824 小时前
Flutter 鸿蒙化实战:flutter_quick_video_encoder 适配 OpenHarmony,逐帧编码视频
flutter·华为·音视频·harmonyos·鸿蒙
万物皆智能4 小时前
2026年视频字幕制作工具全面评测:15款专业软件深度解析
音视频
TK泰妞4 小时前
跨境卖家如何用AI完成TikTok带货视频研究与制作
大数据·人工智能·音视频
AOI小白新手上路5 小时前
视频去雨网络的模块缝合路线 · 实践笔记
网络·笔记·音视频
easyeye1236 小时前
降维打击~使用视频大模型去除路人工作流
音视频
企业数字化笔记6 小时前
视频视觉任务怎样选择媒体处理技术?FFmpeg、OpenCV、GStreamer 的职责与适用场景
ffmpeg·音视频·媒体
码上观世界19 小时前
一张图 + 一段音频,生成口型同步、有表情的数字人
音视频
鲲穹AI种草1 天前
自媒体矩阵批量剪辑怎么选?鲲剪短视频批量处理工具横向评测
人工智能·音视频·媒体