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 由文件大小/总时长确定