ffmpeg 问答系列
author: hjjdebug
date: 2025年 08月 23日 星期六 17:46:02 CST
descript: ffmpeg 问答系列.
文章目录
- 问1: 流有类别和类型之说吗? 例如 pes 中的 0xe0, 0xc0 是什么?
- 问2: 怎样区分流是音频流,视频流,或者数据流?
- 问3: 怎样知道流的索引号? 流的索引号到底是什么意思?
- 问4. codecpar->codec_id 从哪里来的? 是从codec 来的吗?
- 问5: 音视频 codec_context 信息又从哪里来?
- 回答第二个问题: ffmpeg读文件时, 流的索引信息是从哪里来的?
ffmpeg 中有些概念, 需要辨析一下, 逐步完善吧.
问1: 流有类别和类型之说吗? 例如 pes 中的 0xe0, 0xc0 是什么?
pes 中的 0xe0,0xc0 是 “流的ID”,
这些标识符被称为"流ID"(Stream ID)。在PES流中,流ID位于包头字段,用于区分负载内容类型
PES包头包含固定包头(6字节)和可选包头,其中"Stream ID"字段占用8位(1字节),
用于标识负载内容类型。例如:
"0xe0"对应视频帧
"0xc0"对应音频帧
私有数据:可能使用其他流ID(如0xf0),需结合具体协议定义
音频流,视频流,数据流这些概念是什么呢? 这是媒体类型. 说它是流的类型似乎不准确.
问2: 怎样区分流是音频流,视频流,或者数据流?
ffmpeg 通过codec->type 来区分. ,无codec 就是数据流, 那codec->type 又从哪里来? 从定义中来.
举个例子: 例如: AV_CODEC_ID_MPEG2VIDEO. 其codec 是
在libavcodec/mpeg12enc.c 中定义
const FFCodec ff_mpeg2video_encoder = {
.p.name = “mpeg2video”,
CODEC_LONG_NAME(“MPEG-2 video”),
.p.type = AVMEDIA_TYPE_VIDEO, //这个是流的type
.p.id = AV_CODEC_ID_MPEG2VIDEO, //这个是流的ID
… }
可见codec 把codec.type,codec.id 绑定在一起.
当我们依据id找到codec时, 其codec的类型也已经确定.
注意, codec_id 和 流的id 是不同的! 但它们是有关联的,后面有说明.
例如通过调用get_dvb_stream_type(s,st), 依据st->codecpar->codec_id可找到对应的stream_type
stream->codecparamter->codec_id 一般就等于找到的codec中定义的id
问3: 怎样知道流的索引号? 流的索引号到底是什么意思?
在FFmpeg中可通过AVFormatContext->streams[i]->index获取索引号,
其中i为流的顺序号.
那streams[i]->index 又是从哪里来的?
我们从创建AVFormatContext 来找一找出路. 开始的时候是没有stream的,
然后我们添加了一个stream(用:avformat_new_stream). 这第一个stream->index 就是0,
再创建一个stream ,stream->index 就是1… 依次类推.
它记录的就是stream 创建的顺序.
那这个顺序信息保留到哪里了呢? 我们读一个文件时怎样获得流的索引信息呢?
先解释第一句话:
在写文件pmt 时 调用 mpegts_write_pmt
static int mpegts_write_pmt(AVFormatContext* s, MpegTSService* service)
{MpegTSWrite* ts = s->priv_data;uint8_t data[SECTION_LENGTH], *q, *desc_length_ptr ;int val, stream_type, i, err = 0;....for (i = 0; i < s->nb_streams; i++) //然后进入循环, 依次写包含的stream 信息{AVStream* st = s->streams[i]; //枚举每一个streamMpegTSWriteStream* ts_st = st->priv_data;// 问codecpar->codec_id 从哪里来的? 是从codec 来的吗?enum AVCodecID codec_id = st->codecpar->codec_id; //get_dvb_stream_type: 从codecpar->codec_id 可以找到对应的stream_type
// 例如如下代码
//
// switch (st->codecpar->codec_id)
// {
// case AV_CODEC_ID_MPEG1VIDEO:
// case AV_CODEC_ID_MPEG2VIDEO:
// stream_type = STREAM_TYPE_VIDEO_MPEG2;
// break;stream_type = ts->m2ts_mode ? get_m2ts_stream_type(s, st) : get_dvb_stream_type(s, st);// stream_type 是写在pmt 表中的流类型*q++ = stream_type; //写stream_typeput16(&q, 0xe000 | ts_st->pid); //写stream -> piddesc_length_ptr = q;q += 2; /* patched after *//* write optional descriptors here */switch (st->codecpar->codec_type) // 写可选的描述符{case AVMEDIA_TYPE_AUDIO:......break;case AVMEDIA_TYPE_SUBTITLE:......break;case AVMEDIA_TYPE_VIDEO:......break;case AVMEDIA_TYPE_DATA:......break;}val = 0xf000 | (q - desc_length_ptr - 2); //描述符长度可能为0desc_length_ptr[0] = val >> 8;desc_length_ptr[1] = val;}mpegts_write_section1(&service->pmt, PMT_TID, service->sid, ts->tables_version, 0, 0,data, q - data);return 0;
}
可见stream->index 并没有使用, write_pmt 只是按顺序写入streams[i]信息.
所以这个顺序就是index 的信息.
问4. codecpar->codec_id 从哪里来的? 是从codec 来的吗?
答: 虽然是一致的,但来历还有点复杂,
codecpar->codec_id 是从codec_context 中来的.
通过调用 avcodec_parameters_from_context()得到.
这里context 用了名称codec, 不太准确,凑合看吧,保持原色原味. 如下:
int avcodec_parameters_from_context(AVCodecParameters *par, const AVCodecContext *codec)par->codec_type = codec->codec_type;par->codec_id = codec->codec_id;par->codec_tag = codec->codec_tag;
...
而调用avcodec_parameters_from_context 是用户调用的代码, 所以要想把codec_paramters搞对,
必需要调用这个接口. 它放到了open_video或open_audio 中,
如果你要open_data,也要把AVCodecParameters 要搞对.
由于data_stream 没有codec_context, 那就直接手工设置CodecParameters
问5: 音视频 codec_context 信息又从哪里来?
是用户编写的代码, codec_context 信息大部分来自codec, 还有一部分手工编写
查代码发现,
在add_stream 时,
根据codec_id 查找codec, AVCodec codec = avcodec_find_encoder(codec_id);
根据codec,创建codec_context AVCodecContext c_ctx = avcodec_alloc_context3(codec);
c_ctx->codec_id = codec_id; //手工付值的.
可见, context 中的codec_id, 就是codec中定义的id
回答第二个问题: ffmpeg读文件时, 流的索引信息是从哪里来的?
调用栈:
0 in avformat_new_stream of libavformat/options.c:321
1 in pmt_cb of libavformat/mpegts.c:2626
2 in write_section_data of libavformat/mpegts.c:507
3 in handle_packet of libavformat/mpegts.c:3053
4 in handle_packets of libavformat/mpegts.c:3233
5 in mpegts_read_header of libavformat/mpegts.c:3364
6 in avformat_open_input of libavformat/demux.c:316
7 in main of main.cpp:35
//AVCodec *c 没有使用, 下面是简化版,删掉了一些次要信息包括sti的信息
AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c)
{FFFormatContext *const si = ffformatcontext(s);FFStream *sti; //内部的stream AVStream *st; //暴露给外部的stream信息//把s->streams 扩充出一个位置s->streams = av_realloc_array(s->streams, s->nb_streams + 1, sizeof(*streams));sti = av_mallocz(sizeof(*sti)); //删除了sti 中的一些信息赋值,例如pts 一类st = &sti->pub;st->av_class = &stream_class;st->codecpar = avcodec_parameters_alloc(); //为stream 分配codec_parametersif (s->iformat) {avpriv_set_pts_info(st, 33, 1, 90000);} st->index = s->nb_streams; //我们看到,st->index, 就是当时的 nb_streams 个数st->start_time = AV_NOPTS_VALUE;st->duration = AV_NOPTS_VALUE;st->sample_aspect_ratio = (AVRational) { 0, 1 };s->streams[s->nb_streams++] = st;return st;
fail:ff_free_stream(&st);return NULL;
}
这个调用链, pmt_cb, write_section_data,handle_packet,handle_packets 还是很关键的代码.
如果必要,最好搞懂这些代码.
再强调一下, st->index, 就是你 avformat_new_stream 的顺序,依次长上来的.