音视频入门基础:MPEG2-PS专题(6)——FFmpeg源码中,获取PS流的视频信息的实现

一、引言

通过FFmpeg命令可以获取到PS文件/PS流的视频压缩编码格式、色彩格式(像素格式)、分辨率、帧率信息:

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

本文以H.264为例讲述FFmpeg到底是从哪个地方获取到这些视频信息的。

二、视频压缩编码格式

(一)FFmpeg获取PS流的视频压缩编码格式的原理

FFmpeg获取PS文件/PS流的视频压缩编码格式,是从PES packet的有效载荷,即ES流数据中获取的。从《音视频入门基础:MPEG2-TS专题(18)------PES流简介》可以知道,PES packet的PES packet header里面存在一个stream_id属性,指定ES流的类型和编号。但是仅根据这个stream_id属性是无法判断视频压缩编码格式的:

所以要获取视频压缩编码格式,得从PES packet的有效载荷中获取。用Elecard Stream Analyzer工具可以看到,如果PS流的视频压缩编码格式为H.264,那PES packet的有效载荷中携带的就是以0x000001或0x00000001作为起始码的AnnexB格式的H.264码流(关于AnnexB可以参考:《音视频入门基础:H.264专题(3)------EBSP, RBSP和SODB》):

所以这时候就可以通过解析PES packet的有效载荷,即ES流来获取视频压缩编码格式。下面讲解相关代码。

(二)FFmpeg获取PS流的视频压缩编码格式的实现

由《音视频入门基础:MPEG2-PS专题(5)------FFmpeg源码中,解析PS流中的PES流的实现》可以知道,FFmpeg源码中通过mpegps_read_pes_header函数解析PS流中的一个PES packet,将其PES packet header里面的信息解析出来。而在调用完mpegps_read_pes_header函数后,指针s->pb.buf_ptr会指向该PES packet的有效载荷,如果PS流的视频压缩编码格式为H.264,那就是指向以0x000001或0x00000001作为起始码的AnnexB格式的H.264码流:

然后在mpegps_read_packet函数中,会通过av_get_packet函数将s->pb.buf_ptr指向的H.264码流数据保存到pkt->data指向的缓冲区中(关于av_get_packet函数的用法可以参考:《FFmpeg源码:append_packet_chunked、av_get_packet、av_append_packet函数分析》):

cpp 复制代码
static int mpegps_read_packet(AVFormatContext *s,
                              AVPacket *pkt)
{
//...
    len = mpegps_read_pes_header(s, &dummy_pos, &startcode, &pts, &dts);
//...
    ret = av_get_packet(s->pb, pkt, len);
//...
}

之后在probe_codec函数中,会通过语句:memcpy(pd->buf + pd->buf_size, pkt->data, pkt->size) 将上述H.264码流数据从pkt->data拷贝到pd->buf中:

cpp 复制代码
static int probe_codec(AVFormatContext *s, AVStream *st, const AVPacket *pkt)
{
//...
    if (sti->request_probe > 0) {
    //...
        AVProbeData *const pd = &sti->probe_data;
        int end;
        av_log(s, AV_LOG_DEBUG, "probing stream %d pp:%d\n", st->index, sti->probe_packets);
        --sti->probe_packets;

        if (pkt) {
            uint8_t *new_buf = av_realloc(pd->buf, pd->buf_size+pkt->size+AVPROBE_PADDING_SIZE);
            if (!new_buf) {
                av_log(s, AV_LOG_WARNING,
                       "Failed to reallocate probe buffer for stream %d\n",
                       st->index);
                goto no_packet;
            }
            pd->buf = new_buf;
            memcpy(pd->buf + pd->buf_size, pkt->data, pkt->size);
            pd->buf_size += pkt->size;
            memset(pd->buf + pd->buf_size, 0, AVPROBE_PADDING_SIZE);
        }
    //...
    }
//...
}

然后probe_codec函数中会调用set_codec_from_probe_data函数,set_codec_from_probe_data函数的定义如下:

cpp 复制代码
static int set_codec_from_probe_data(AVFormatContext *s, AVStream *st,
                                     AVProbeData *pd)
{
    static const struct {
        const char *name;
        enum AVCodecID id;
        enum AVMediaType type;
    } fmt_id_type[] = {
        { "aac",        AV_CODEC_ID_AAC,          AVMEDIA_TYPE_AUDIO    },
        { "ac3",        AV_CODEC_ID_AC3,          AVMEDIA_TYPE_AUDIO    },
        { "aptx",       AV_CODEC_ID_APTX,         AVMEDIA_TYPE_AUDIO    },
        { "dts",        AV_CODEC_ID_DTS,          AVMEDIA_TYPE_AUDIO    },
        { "dvbsub",     AV_CODEC_ID_DVB_SUBTITLE, AVMEDIA_TYPE_SUBTITLE },
        { "dvbtxt",     AV_CODEC_ID_DVB_TELETEXT, AVMEDIA_TYPE_SUBTITLE },
        { "eac3",       AV_CODEC_ID_EAC3,         AVMEDIA_TYPE_AUDIO    },
        { "h264",       AV_CODEC_ID_H264,         AVMEDIA_TYPE_VIDEO    },
        { "hevc",       AV_CODEC_ID_HEVC,         AVMEDIA_TYPE_VIDEO    },
        { "loas",       AV_CODEC_ID_AAC_LATM,     AVMEDIA_TYPE_AUDIO    },
        { "m4v",        AV_CODEC_ID_MPEG4,        AVMEDIA_TYPE_VIDEO    },
        { "mjpeg_2000", AV_CODEC_ID_JPEG2000,     AVMEDIA_TYPE_VIDEO    },
        { "mp3",        AV_CODEC_ID_MP3,          AVMEDIA_TYPE_AUDIO    },
        { "mpegvideo",  AV_CODEC_ID_MPEG2VIDEO,   AVMEDIA_TYPE_VIDEO    },
        { "truehd",     AV_CODEC_ID_TRUEHD,       AVMEDIA_TYPE_AUDIO    },
        { "evc",        AV_CODEC_ID_EVC,          AVMEDIA_TYPE_VIDEO    },
        { "vvc",        AV_CODEC_ID_VVC,          AVMEDIA_TYPE_VIDEO    },
        { 0 }
    };
    int score;
    const AVInputFormat *fmt = av_probe_input_format3(pd, 1, &score);
    FFStream *const sti = ffstream(st);

    if (fmt) {
        av_log(s, AV_LOG_DEBUG,
               "Probe with size=%d, packets=%d detected %s with score=%d\n",
               pd->buf_size, s->max_probe_packets - sti->probe_packets,
               fmt->name, score);
        for (int i = 0; fmt_id_type[i].name; i++) {
            if (!strcmp(fmt->name, fmt_id_type[i].name)) {
                if (fmt_id_type[i].type != AVMEDIA_TYPE_AUDIO &&
                    st->codecpar->sample_rate)
                    continue;
                if (sti->request_probe > score &&
                    st->codecpar->codec_id != fmt_id_type[i].id)
                    continue;
                st->codecpar->codec_id   = fmt_id_type[i].id;
                st->codecpar->codec_type = fmt_id_type[i].type;
                sti->need_context_update = 1;
                return score;
            }
        }
    }
    return 0;
}

可以看到set_codec_from_probe_data函数中调用了av_probe_input_format3函数来推测pd->buf中的码流的格式。关于av_probe_input_format3函数的用法可以参考:《FFmpeg源码:av_probe_input_format3函数和AVInputFormat结构体分析(FFmpeg源码5.0.3版本)》。对于H.264裸流,av_probe_input_format3函数中就是调用了h264_probe函数来检测这段码流是否为AnnexB格式的H.264裸流,具体可以参考:《音视频入门基础:H.264专题(16)------FFmpeg源码中,判断某文件是否为H.264裸流文件的实现》。

判断出这段码流为H.264裸流后,set_codec_from_probe_data函数中会执行语句:st->codecpar->codec_id = fmt_id_type[i].id,让AVCodecParameters的codec_id得到视频压缩编码格式:

cpp 复制代码
static int set_codec_from_probe_data(AVFormatContext *s, AVStream *st,
                                     AVProbeData *pd)
{
//...

    if (fmt) {
//...
        for (int i = 0; fmt_id_type[i].name; i++) {
            if (!strcmp(fmt->name, fmt_id_type[i].name)) {
                if (fmt_id_type[i].type != AVMEDIA_TYPE_AUDIO &&
                    st->codecpar->sample_rate)
                    continue;
                if (sti->request_probe > score &&
                    st->codecpar->codec_id != fmt_id_type[i].id)
                    continue;
                st->codecpar->codec_id   = fmt_id_type[i].id;
                st->codecpar->codec_type = fmt_id_type[i].type;
                sti->need_context_update = 1;
                return score;
            }
        }
    }
    return 0;
}

然后在set_codec_from_probe_data函数外部,通过avcodec_parameters_to_context函数将AVCodecParameters的codec_id赋值给AVCodecContext的codec_id:

cpp 复制代码
int avcodec_parameters_to_context(AVCodecContext *codec,
                                  const AVCodecParameters *par)
{
//...
    codec->codec_id   = par->codec_id;
//...
}

然后在dump_stream_format函数中,通过avcodec_string函数中的语句:codec_name = avcodec_get_name(enc->codec_id) 拿到AVCodecContext的codec_id对应的视频压缩编码格式名称。最后再在dump_stream_format函数中将视频压缩编码格式打印出来:

cpp 复制代码
void avcodec_string(char *buf, int buf_size, AVCodecContext *enc, int encode)
{
//...
    codec_name = avcodec_get_name(enc->codec_id);
//...
}

所以FFmpeg获取PS文件/PS流的视频压缩编码格式,是从PES packet的有效载荷,即ES流数据中获取的:

三、视频压缩编码格式的profile

如果PS文件/PS流中的视频压缩编码格式为H.264,FFmpeg获取其视频压缩编码格式的profile,是通过SPS的profile_idc属性获取到的,具体可以参考:《音视频入门基础:H.264专题(17)------FFmpeg源码中,获取H.264视频的profile的实现》:

四、视频的色彩格式

如果PS文件/PS流中的视频压缩编码格式为H.264,FFmpeg获取其视频的色彩格式,是通过SPS中的属性chroma_format_idc获取到的,具体可以参考:《音视频入门基础:H.264专题(13)------FFmpeg源码中通过SPS属性获取视频色彩格式的实现》:

五、视频分辨率

如果PS文件/PS流中的视频压缩编码格式为H.264,FFmpeg获取其视频分辨率,是通过SPS中的属性获取的,具体可以参考:《音视频入门基础:H.264专题(12)------FFmpeg源码中通过SPS属性计算视频分辨率的实现》:

六、视频码率

由于PS文件/PS流的文件格式(包括TS Header、PES packet header)不包含视频码率信息,所以无法通过FFmpeg直接获取到其视频码率。与之对应,由于FLV文件的Script Tag中包含视频码率信息,所以FFmpeg可以直接打印FLV文件的视频码率,具体可以参考:《音视频入门基础:FLV专题(24)------FFmpeg源码中,获取FLV文件视频信息的实现》。

七、视频帧率

如果TS文件/TS流中的视频压缩编码格式为H.264,对其视频进行编解码时,FFmpeg源码内部使用的是通过SPS中的属性计算得到的视频帧率(具体可以参考:《音视频入门基础:H.264专题(15)------FFmpeg源码中通过SPS属性获取视频帧率的实现》)。

相关推荐
皇族崛起2 小时前
【音频标注】- 音频标注项目调研
音视频·解决方案·音频标注·样本标注·项目规划
hazy1k7 小时前
K230基础-录放视频
网络·人工智能·stm32·单片机·嵌入式硬件·音视频·k230
wearegogog1238 小时前
基于块匹配的MATLAB视频去抖动算法
算法·matlab·音视频
aqi0015 小时前
FFmpeg开发笔记(八十二)使用国产直播服务器smart_rtmpd执行推流操作
ffmpeg·音视频·直播·流媒体
hazy1k19 小时前
K230基础-录放音频
人工智能·stm32·单片机·嵌入式硬件·音视频·k230
西瓜er1 天前
JAVA:Spring Boot 集成 FFmpeg 实现多媒体处理
java·spring boot·ffmpeg
小美哥13141 天前
格式工厂转换视频很慢、Pr视频剪辑导出很慢的优化设置(就用显卡的默认设置即可)
音视频·格式工厂
宠友信息2 天前
仿小红书短视频APP源码:Java微服务版支持小程序编译的技术解析
java·微服务·音视频
QMCY_jason2 天前
ubuntu 24.04 FFmpeg编译 带Nvidia 加速记录
linux·ubuntu·ffmpeg
bylander2 天前
【论文阅读】通义实验室,VACE: All-in-One Video Creation and Editing
论文阅读·人工智能·计算机视觉·音视频