FFmpeg源码解析:mpegts_read_header函数代码走读

 `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` 函数了。

你可能感兴趣的:(FFmpeg源码解析,ffmpeg,音视频,编解码)