ffmpeg 问答系列->demux 部分


author: hjjdebug

date: 2025年 11月 21日 星期五 15:26:36 CST

description: ffmpeg 问答系列->demux 部分


文章目录

  • [1. 码流中节目个数是怎样确定的?](#1. 码流中节目个数是怎样确定的?)
  • [2. 码流中有多少个stream.](#2. 码流中有多少个stream.)
  • [3. 每个节目由几个流构成的?有哪几个流构成的?](#3. 每个节目由几个流构成的?有哪几个流构成的?)
  • [4. st->codecpar 是怎样得到的?](#4. st->codecpar 是怎样得到的?)
  • [5 视频流参数的获取](#5 视频流参数的获取)
  • [6.stream 中的帧率信息?](#6.stream 中的帧率信息?)
    • [6.1 平均帧率](#6.1 平均帧率)
    • [6.2 基本帧率](#6.2 基本帧率)
  • [7. 考察音频参数信息.](#7. 考察音频参数信息.)
  • [8. 节目的总时长, bitrate 的获取](#8. 节目的总时长, bitrate 的获取)

demux 主要工作就是打开文件,分析数据.
下面的信息主要取自avformat_open_input() 和 avformat_find_stream_info() 两个函数
后者会依据codec_id 找到codec, 打开codec_context, 并读包,解码,分析数据.
以mpegts 流做输入文件测试.

1. 码流中节目个数是怎样确定的?

在打开文件,返回一个handle AVFormatContext* s中, 有一个参数s->nb_programs 记录了节目个数.

我们看看它是怎么赋值的.

在ts 流中sdt_cb 中会创建新节目.

sdt 是服务描述表, 它包含了节目id, 服务名称,提供商名称等信息, 所以从这个表中可以拿到节目个数.

表格的具体形式:

sdt 表是一个section 数据, 其pid 是 0x11, table_id 是 0x42, 读取完table头信息再读表信息.

表格记录了每个节目对应的2个字符串, 节目名称,节目供应商. 具体操作你需要调试代码.

2. 码流中有多少个stream.

s 中有一个参数s->nb_streams 记录了流的个数,看看它是怎么赋值的.

在ts流的pmt_cb 中会创建新的流, avformat_new_stream(s,...);

s->streams[s->nb_streams++] = st

可见当pmt 表中找到一个新的基础流时,就会创建一个stream

并将指针保存下来.

以后若指定stream_index, 则可以找到对应的stream

3. 每个节目由几个流构成的?有哪几个流构成的?

在program 结构中,
有一个stream_index表, 表中记录着包含的流对应的stream_index
有一个成员叫nb_stream_indexes, 记录了有多少个stream.

可以观察它的形成.

一个节目中包含几个stream, 这是由pmt 表决定的. 我们看一下它的形成过程.

这需要在新创建一个program 后中断, 此时的nb_stream_indexes 为0, 观察其变化.

//创建一个stream 时就自动填好了 st->index

//它是s->nb_streams 的个数, 新的stream 存入后, s->nb_streams 加1

st->index = s->nb_streams;

s->streams[s->nb_streams++] = st;

可见stream_index, 就是s->streams 中的索引号. 要把这个索引号, 保存到 program的stream_index 表中

void av_program_add_stream_index(AVFormatContext *ac, int progid, unsigned idx)

在一个program 中加入一个stream_index, program 要由program_id 来区别

如果找不到这个program, 就返回.

查看program->stream_index 表中是否有这个idx, 如果有,表示已经加过了,

如果没有,stream_index表扩充一项, 然后把这个idx 放进去.

然后nb_stream_indexes 会加1, 表示流的个数增加了一个,同时也是下一个基础流的索引号.

pmt 的tableid 为0x02
它的表只是记录有stream_type(1byte)+pid(elementary) + info_descriptor(长度可能是0,表示没有)

当发现这个流是pes 流时 (大部分流都满足这个条件)

is_pes_stream(stream_type, prog_reg_desc)

就会创建一个 pes 流 pes = add_pes_stream(ts, pid, pcr_pid);

此时pes->st 为空, 继续创建一个stream , stream 是包含codecpar 甚至其内部结构sti还包含codec_context结构.

if (pes && !pes->st)

{
st = avformat_new_stream(pes->stream, NULL); / /pes->stream 就是avformat_context s,创建流时,st->index就是当时context中流的个数

}

4. st->codecpar 是怎样得到的?

codecpar 有很多项, 先看两个具体项? codec_id,codec_type

由pmt的steam_type 确定

ISO_types 定义了一个streamtype 与codec_type codec_id 的关联表,有17项

cpp 复制代码
static const StreamType ISO_types[] = {
  { 0x01, AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_MPEG2VIDEO },
  { 0x02, AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_MPEG2VIDEO },
  { 0x03, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_MP3 },
  { 0x04, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_MP3 },
  { 0x0f, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AAC },
  { 0x10, AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_MPEG4 },
  { 0x1b, AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_H264 },
  { 0x1c, AVMEDIA_TYPE_AUDIO, AV_CODEC_ID_AAC },
  { 0x20, AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_H264 },
  { 0x21, AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_JPEG2000 },
  { 0x24, AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_HEVC },
  { 0x42, AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_CAVS },
  { 0xd1, AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_DIRAC },
  { 0xd2, AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_AVS2 },
  { 0xd4, AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_AVS3 },
  { 0xea, AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_VC1 },
  { 0 },
};

这样pmt 中定义的stream_type 就可以确定codec_id,codec_type

5 视频流参数的获取

codec_param 中有不少参数,下面是视频参数

profile pix_fmt, width,height,field_order,color_range,color_space,sample_aspect_ratio,has_b_frames,framerate 这些参数从哪里来的?

params->format 就是 pix_fmt(AVPixelFormat), 怎么来的?
这些参数都是从codec_context 中来的,

如下示:

cpp 复制代码
int avcodec_parameters_from_context(AVCodecParameters *par,
                                    const AVCodecContext *c_ctx)
{
    switch (par->codec_type) {
    case AVMEDIA_TYPE_VIDEO:
        par->format              = c_ctx->pix_fmt;
        par->width               = c_ctx->width;
        par->height              = c_ctx->height;
        par->field_order         = c_ctx->field_order;
        par->color_range         = c_ctx->color_range;
        par->color_primaries     = c_ctx->color_primaries;
        par->color_trc           = c_ctx->color_trc;
        par->color_space         = c_ctx->colorspace;
        par->chroma_location     = c_ctx->chroma_sample_location;
        par->sample_aspect_ratio = c_ctx->sample_aspect_ratio;
        par->video_delay         = c_ctx->has_b_frames;
        par->framerate           = c_ctx->framerate;
        break;
	....
	}

codec_context 参数从哪里来? 解码得到的, 即send_packet(), receive_frame(),然后分析

cpp 复制代码
代码片段1: 解码包获取参数放入codec_context
profile, level 等要靠解码mpeg_decode_frame得到.
    skip_bits(&s->gb, 1); /* profile and level esc*/
    s->avctx->profile       = get_bits(&s->gb, 3);
    s->avctx->level         = get_bits(&s->gb, 4);


代码片段2: 从解出的frame数据中填充avctx信息
int av_parser_parse2(AVCodecParserContext *s, AVCodecContext *avctx,
                     uint8_t **poutbuf, int *poutbuf_size,
                     const uint8_t *buf, int buf_size,
                     int64_t pts, int64_t dts, int64_t pos)

    if (avctx->codec_type == AVMEDIA_TYPE_VIDEO) {
        FILL(field_order);
        FILL(coded_width);
        FILL(coded_height);
        FILL(width);
        FILL(height);
    }
	...
}

color_range, mpeg_decode_init 时被初始化为 AVCOL_RANGE_MPEG(1)

color_space: 默认, unspecify

has_b_frames: av_parser_parse2->mpegvideo_parse->mpegvideo_extract_headers()解析出. 在start_code 中

framerate: 同上,但在seq_paramter 中

6.stream 中的帧率信息?

st->avg_frame_rate, st->r_frame_rate,st->time_base

6.1 平均帧率

st->avg_frame_rate 的计算.

av_reduce(&st->avg_frame_rate.num, &st->avg_frame_rate.den,

sti->info->codec_info_duration_fields * (int64_t) st->time_base.den,

sti->info->codec_info_duration * 2 * (int64_t) st->time_base.num, 60000);

可见由时基(一般是1/90000)及 sti->info->codec_info_duration(codec 信息时长),

sti->info->codec_info_duration_field(codec场数) 来决定

//信息时长是pkt的累加时长

sti->info->codec_info_duration += pkt->duration; //累积pkt->duration, ,pkt->duration典型值3600

//场数,一个pkt 一般按2场计算

//累加field, field一般就是2, sti->parser->repeat_pict也会等于1

sti->info->codec_info_duration_fields += sti->parser && sti->need_parsing && fields

? sti->parser->repeat_pict + 1 : 2;
场的平均时长=各个包的时长相加/总场数, 典型值(1800), 乘以2为帧时长,典型值(3600)
平均帧率=时基的倒数90K/(场的平均时长*2), 典型值25帧

6.2 基本帧率

st->r_frame_rate 的计算,

av_reduce(&st->r_frame_rate.num, &st->r_frame_rate.den, st->time_base.den, st->time_base.num * sti->info->duration_gcd, INT_MAX);
可见基本帧率是时基的倒数/duration_gcd 计算得到的.

由时基(一般是1/90000)

duration_gcd (gcd 是greatest common divsor最大公约数), 最大公约duration

是每读取一个个frame 都要计算一下得到的.

info->duration_gcd = av_gcd(info->duration_gcd, duration);

例如流的包间隔duration 都是3600, 则duration_gcd 是3600, 基本帧率90000/3600=25帧

如果duration 是1800, 则duration_gcd 就是1800, 基本帧率变成50帧

7. 考察音频参数信息.

par->sample_fmt, par->sample_rate, par->ch_layout,
与视频一样, codec->parameters 是来自stream 内部参数sti->avctx 的. 而avctx 要解包来完善信息.

sample_fmt 在avcodec_open2()->decode_init() 中在codec_context中设置

sample_rate 会在解码时(send_packet,receive_frame)时在codec_context中设置

ch_layout 也是解码时设置的.

8. 节目的总时长, bitrate 的获取

节目的总时长是由各个流的时长计算决定的.把最先开始的做开头,最后结尾的做结尾算作总时长

bitrate 由文件大小/总时长确定

相关推荐
chjqxxxx2 小时前
php使用ffmpeg实现视频随机截图并转成图片
ffmpeg·php·音视频
陈陈陈建蕾2 天前
Mac使用FFmpeg进行屏幕录制,并使用VLC本地播放
ffmpeg·github
vivo互联网技术3 天前
Android动效探索:彻底弄清如何让你的视频更加酷炫
android·ffmpeg·跨平台·图形·mediaplayer·纹理·opengl es·坐标系
stereohomology3 天前
ffmpeg视频mp4到gif用大模型很方便
ffmpeg·音视频
f***45324 天前
从MySQL5.7平滑升级到MySQL8.0的最佳实践分享
ffmpeg
努力还债的学术吗喽5 天前
ffmpeg离线安装到服务器:解决conda/sudo/无法安装的通用方案
服务器·ffmpeg·conda
zymill6 天前
hysAnalyser --- UDP实时流分析使用指南
ffmpeg·ts流分析·mpegts·音视频分析·数字电视流录制·audio vivid·视频分析工具
Everbrilliant896 天前
FFmpeg解码音频数据AudioTrack/OpenSL播放
ffmpeg·音视频·audiotrack·opensl·ffmpeg音频解码播放·decodethread·opensl播放与解码同步
海南java第二人9 天前
数据库范式详解:从冗余到规范的升华之旅
数据库·oracle·ffmpeg