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

news2025/1/10 15:33:18

=================================================================

音视频入门基础:MPEG2-PS专题系列文章:

音视频入门基础:MPEG2-PS专题(1)——MPEG2-PS官方文档下载

音视频入门基础:MPEG2-PS专题(2)——使用FFmpeg命令生成ps文件

音视频入门基础:MPEG2-PS专题(3)——MPEG2-PS格式简介

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

音视频入门基础:MPEG2-PS专题(5)——FFmpeg源码中,解析PS流中的PES流的实现

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

音视频入门基础:MPEG2-PS专题(7)——通过FFprobe显示PS流每个packet的信息

=================================================================

一、引言

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

./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函数分析》):

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中:

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函数的定义如下:

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得到视频压缩编码格式:

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:

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函数中将视频压缩编码格式打印出来:

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属性获取视频帧率的实现》)。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

读书笔记:分布式系统原理介绍

写在前面 已经大概三个月左右没有更新博客了,哈哈哈哈; 此博客是笔者在对《分布式系统原理介绍》进行概述,对于整个分布式系统协议的理解基于一些量化的指标考虑了数据的分布副本协议(中心化/去中心化)进行了总结&…

Dexcap复现代码数据预处理全流程(四)——demo_clipping_3d.py

此脚本的主要功能是可视化点云数据文件(.pcd 文件),并通过键盘交互选择演示数据的起始帧和结束帧,生成片段标记文件 (clip_marks.json) 主要流程包括: 用户指定数据目录:检查目录是否存在并处理标记文件 -…

MBM指尖六维力触觉传感器:高灵敏度、低漂移,精准掌控力学世界

MBM指尖六维力触觉传感器是一种专为机器人设计的高性能传感器。它通过集成三轴力和三轴力矩的感知能力,能够精准捕捉复杂的力学信息。传感器采用MEMS与应变体复合测量技术,具备数字输出功能,显著降低漂移并减少安装偏移的影响。其紧凑轻便的设…

C#,图论与图算法,任意一对节点之间最短距离的弗洛伊德·沃肖尔(Floyd Warshall)算法与源程序

一、弗洛伊德沃肖尔算法 Floyd-Warshall算法是图的最短路径算法。与Bellman-Ford算法或Dijkstra算法一样,它计算图中的最短路径。然而,Bellman Ford和Dijkstra都是单源最短路径算法。这意味着他们只计算来自单个源的最短路径。另一方面,Floy…

为答疑机器人扩展问题分类与路由功能

1.意图识别 2. 构建路由模块 简单的意图识别 from chatbot import llmfrom config.load_key import load_key load_key()prompt 【角色背景】 你是一个问题分类路由器,需要识别问题的类型。 --- 【任务要求】 问题的类型目前有:公司内部文档查询、内…

spring boot启动源码分析(三)之Environment准备

上一篇《spring-boot启动源码分析(二)之SpringApplicationRunListener》 环境介绍: spring boot版本:2.7.18 主要starter:spring-boot-starter-web 本篇开始讲启动过程中Environment环境准备,Environment是管理所有…

Pandas-RFM会员价值度模型

文章目录 一. 会员价值度模型介绍二. RFM计算与显示1. 背景2. 技术点3. 数据4. 代码① 导入模块② 读取数据③ 数据预处理Ⅰ. 数据清洗, 即: 删除缺失值, 去掉异常值.Ⅱ. 查看清洗后的数据Ⅲ. 把前四年的数据, 拼接到一起 ④ 计算RFM的原始值⑤ 确定RFM划分区间⑥ RFM计算过程⑦…

【理论】测试框架体系TDD、BDD、ATDD、DDT介绍

一、测试框架是什么 测试框架是一组用于创建和设计测试用例的指南或规则。框架由旨在帮助 QA 专业人员更有效地测试的实践和工具的组合组成。 这些指南可能包括编码标准、测试数据处理方法、对象存储库、存储测试结果的过程或有关如何访问外部资源的信息。 A testing framewo…

FreeU: Free Lunch in Diffusion U-Net 笔记

FreeU: Free Lunch in Diffusion U-Net 摘要 作者研究了 U-Net 架构对去噪过程的关键贡献,并发现其主干部分主要在去噪方面发挥作用,而其跳跃连接主要是向解码器模块引入高频特征,这使得网络忽略了主干部分的语义信息。基于这一发现&#…

JAVA 使用apache poi实现EXCEL文件的输出;apache poi实现标题行的第一个字符为红色;EXCEL设置某几个字符为别的颜色

设置输出文件的列宽,防止文件过于丑陋 Sheet sheet workbook.createSheet(FileConstants.ERROR_FILE_SHEET_NAME); sheet.setColumnWidth(0, 40 * 256); sheet.setColumnWidth(1, 20 * 256); sheet.setColumnWidth(2, 20 * 256); sheet.setColumnWidth(3, 20 * 25…

【STM32】无源蜂鸣器播放音乐《千与千寻》,HAL库

目录 一、工程链接 二、简单介绍 主要特点: 应用: 驱动电路: 三、原理图 四、cubeMX配置 时钟配置 五、keil配置 六、驱动编写 演奏函数 主函数编写 七、效果展示 八、驱动附录 music.h music.c 一、工程链接 STM32无源蜂鸣…

在 Vue 3 集成 e签宝电子合同签署功能

实现 Vue 3 e签宝电子合同签署功能,需要使用 e签宝提供的实际 SDK 或 API。 e签宝通常提供针对不同平台(如 Web、Android、iOS)的 SDK,而 Web 端一般通过 WebView 或直接使用嵌入式 iframe 来加载合同签署页面。 下面举个 &…

04、Redis深入数据结构

一、简单动态字符串SDS 无论是Redis中的key还是value,其基础数据类型都是字符串。如,Hash型value的field与value的类型,List型,Set型,ZSet型value的元素的类型等都是字符串。redis没有使用传统C中的字符串而是自定义了…

如何用Python编程实现自动整理XML发票文件

传统手工整理发票耗时费力且易出错,而 XML 格式发票因其结构化、标准化的特点,为实现发票的自动化整理与保存提供了可能。本文将详细探讨用python来编程实现对 XML 格式的发票进行自动整理。 一、XML 格式发票的特点 结构化数据:XML 格式发票…

Linux——修改USB网卡设备节点名称

修改驱动: 测试: 参考资料: https://blog.csdn.net/ablexu2018/article/details/144868950

(STM32笔记)十二、DMA的基础知识与用法 第三部分

我用的是正点的STM32F103来进行学习,板子和教程是野火的指南者。 之后的这个系列笔记开头未标明的话,用的也是这个板子和教程。 DMA的基础知识与用法 三、DMA程序验证1、DMA 存储器到存储器模式实验(1)DMA结构体解释(2…

论文笔记(六十一)Implicit Behavioral Cloning

Implicit Behavioral Cloning 文章概括摘要1 引言2 背景:隐式模型的训练与推理3 隐式模型与显式模型的有趣属性4 policy学习成果5 理论见解:隐式模型的通用逼近性6 相关工作7 结论 文章概括 引用: inproceedings{florence2022implicit,titl…

高斯函数Gaussian绘制matlab

高斯 约翰卡尔弗里德里希高斯,(德语:Johann Carl Friedrich Gau,英语:Gauss,拉丁语:Carolus Fridericus Gauss)1777年4月30日–1855年2月23日,德国著名数学家、物理学家…

vue的路由守卫逻辑处理不当导致部署在nginx上无法捕捉后端异步响应消息等问题

近期对前端的路由卫士有了更多的认识。 何为路由守卫?这可能是一种约定俗成的名称。就是VUE中的自定义函数,用来处理路由跳转。 import { createRouter, createWebHashHistory } from "vue-router";const router createRouter({history: cr…

如何在 Ubuntu 22.04 上使用 LEMP 安装 WordPress 教程

简介: 本教程旨在指导你如何在 Ubuntu 22.04 上使用 LEMP 栈安装 WordPress。 WordPress 是一个用 PHP 编写的开源内容管理系统。LEMP 栈是 Linux,NGINX,MySQL 和 PHP 的缩写。WordPress 非常用户友好,并提供了多种选项&#xff…