`mpegts_read_header` 是 FFmpeg 中用于读取 MPEG-TS(MPEG transport stream)文件头部信息的函数,代码位于libavformat/mpegts.c文件中。它会解析 TS 文件的 PAT(Program Association Table)表,获取其中的 PMT(Program Map Table)表的个数,并为每个 PMT 表分配内存。
PAT 表中包含了 TS 文件中所有节目的信息,而 PMT 表则描述了一个特定节目的信息,如其包含的视频流、音频流等等。通过解析这些表,FFmpeg 可以确定 TS 文件中所有节目的信息,便于后续的解码和处理。
因此,在使用 FFmpeg 解码 MPEG-TS 文件之前,需要先调用 `mpegts_read_header` 函数来读取 TS 文件的头部信息。
下面是对mpegts_read_header代码逐行解释:
static int mpegts_read_header(AVFormatContext *s)
{
// 获取私有数据和AVIO上下文
MpegTSContext *ts = s->priv_data;
AVIOContext *pb = s->pb;
// 获取探测大小和位置
int64_t pos, probesize = s->probesize;
// 设置解码器偏好帧率
s->internal->prefer_codec_framerate = 1;
// 确保能够进行回退操作
if (ffio_ensure_seekback(pb, probesize) < 0)
av_log(s, AV_LOG_WARNING, "Failed to allocate buffers for seekback\n");
// 获取当前位置,获取原始包的大小
pos = avio_tell(pb);
ts->raw_packet_size = get_packet_size(s);
if (ts->raw_packet_size <= 0) {
av_log(s, AV_LOG_WARNING, "Could not detect TS packet size, defaulting to non-FEC/DVHS\n");
ts->raw_packet_size = TS_PACKET_SIZE;
}
// 设置流信息和自动猜测标志
ts->stream = s;
ts->auto_guess = 0;
// 判断是否为MPEGTS解复用器,如果是则进行解复用
if (s->iformat == &ff_mpegts_demuxer) {
/* normal demux */
/* 首先执行扫描操作,以获取所有的服务 */
seek_back(s, pb, pos);
// 打开SDT、PAT和EIT的过滤器
mpegts_open_section_filter(ts, SDT_PID, sdt_cb, ts, 1);
mpegts_open_section_filter(ts, PAT_PID, pat_cb, ts, 1);
mpegts_open_section_filter(ts, EIT_PID, eit_cb, ts, 1);
// 处理所有的数据包
handle_packets(ts, probesize / ts->raw_packet_size, s);
/* 如果无法找到服务,则启用自动猜测 */
ts->auto_guess = 1;
av_log(ts->stream, AV_LOG_TRACE, "tuning done\n");
// 设置上下文标志
s->ctx_flags |= AVFMTCTX_NOHEADER;
} else {
// 仅读取数据包
// 创建新的流
AVStream *st;
st = avformat_new_stream(s, NULL);
if (!st)
return AVERROR(ENOMEM);
// 设置PTS信息和编解码器类型
avpriv_set_pts_info(st, 60, 1, 27000000);
st->codecpar->codec_type = AVMEDIA_TYPE_DATA;
st->codecpar->codec_id = AV_CODEC_ID_MPEG2TS;
// 循环读取数据包,直到获取两个PCR
int pcr_pid, pid, nb_packets, nb_pcrs, ret, pcr_l;
int64_t pcrs[2], pcr_h;
int packet_count[2];
uint8_t packet[TS_PACKET_SIZE];
const uint8_t *data;
/* only read packets */
//这部分代码创建一个新的 AVStream 结构体,并设置了相关参数,
//用于表示读取的 MPEG-2 TS 流的基本信息,包括帧率、编解码类型等。
st = avformat_new_stream(s, NULL);
if (!st)
return AVERROR(ENOMEM);
avpriv_set_pts_info(st, 60, 1, 27000000);
st->codecpar->codec_type = AVMEDIA_TYPE_DATA;
st->codecpar->codec_id = AV_CODEC_ID_MPEG2TS;
/* 这部分代码通过迭代读取数据包来估计比特率,直到找到两个 PCR 值。
PCR(Program Clock Reference)是一种用于计算时间戳的机制,
它在 MPEG-2 TS 流中作为定时器被使用。
函数 parse_pcr() 从数据包中提取出 PCR 值,并计算出比特率。*/
/* we iterate until we find two PCRs to estimate the bitrate */
pcr_pid = -1;
nb_pcrs = 0;
nb_packets = 0;
for (;;) {
ret = read_packet(s, packet, ts->raw_packet_size, &data);
if (ret < 0)
return ret;
pid = AV_RB16(data + 1) & 0x1fff;
if ((pcr_pid == -1 || pcr_pid == pid) &&
parse_pcr(&pcr_h, &pcr_l, data) == 0) {
finished_reading_packet(s, ts->raw_packet_size);
pcr_pid = pid;
packet_count[nb_pcrs] = nb_packets;
pcrs[nb_pcrs] = pcr_h * 300 + pcr_l;
nb_pcrs++;
if (nb_pcrs >= 2) {
if (pcrs[1] - pcrs[0] > 0) {
/* the difference needs to be positive to make sense for bitrate computation */
break;
} else {
av_log(ts->stream, AV_LOG_WARNING, "invalid pcr pair %"PRId64" >= %"PRId64"\n", pcrs[0], pcrs[1]);
pcrs[0] = pcrs[1];
packet_count[0] = packet_count[1];
nb_pcrs--;
}
}
} else {
finished_reading_packet(s, ts->raw_packet_size);
}
nb_packets++;
}
/* NOTE1: the bitrate is computed without the FEC */
/* NOTE2: it is only the bitrate of the start of the stream */
ts->pcr_incr = (pcrs[1] - pcrs[0]) / (packet_count[1] - packet_count[0]);
ts->cur_pcr = pcrs[0] - ts->pcr_incr * packet_count[0];
s->bit_rate = TS_PACKET_SIZE * 8 * 27000000LL / ts->pcr_incr;
st->codecpar->bit_rate = s->bit_rate;
st->start_time = ts->cur_pcr;
av_log(ts->stream, AV_LOG_TRACE, "start=%0.3f pcr=%0.3f incr=%"PRId64"\n",
st->start
}
seek_back(s, pb, pos);
return 0;
}
在 `if (s->iformat == &ff_mpegts_demuxer)` 分支中,首先调用 `seek_back` 函数进行回溯,以保证在该函数之前没有已经读取过的数据。然后通过调用 `mpegts_open_section_filter` 函数,对 `SDT_PID`、`PAT_PID` 和 `EIT_PID` 三个 PID 打开节目号表、节目关联表和事件信息表的过滤器。这些过滤器会用来解析相应的数据包,获取其中的信息,并保存到 `MpegTSContext` 结构体中。
接着调用 `handle_packets` 函数处理从当前位置开始,累计大小为 `probesize / ts->raw_packet_size` 个 TS 数据包。在调用该函数之前,需要保证已经打开了上述三个 PID 的过滤器。在 `handle_packets` 函数内部,会循环调用 `mpegts_read_packet` 函数读取 TS 数据包,并对其进行处理,包括调用相应的回调函数、更新 PTS/DTS 以及处理 H.264、H.265 等视频流的 SPS/PPS。如果在该函数执行期间无法获取到足够的信息,就会调用 `mpegts_find_stream_type` 函数来判断是否需要切换到 `auto_guess` 模式,以尝试从数据流中自动猜测出相应的流类型和 PID。
最后,在该分支的结尾处,将 `AVFMTCTX_NOHEADER` 标志设置到 `s->ctx_flags` 中,表示不需要再调用 `read_header` 函数了。