音视频入门基础:MPEG2-TS专题(7)——FFmpeg源码中,读取出一个transport packet数据的实现

一、引言

从《音视频入门基础:MPEG2-TS专题(3)------TS Header简介》可以知道,TS格式有三种:分别为transport packet长度固定为188、192和204字节。而FFmpeg源码中是通过read_packet函数从一段MPEG2-TS传输流/TS文件中读取出一个transport packet的。

二、read_packet函数

(一)read_packet函数的定义

read_packet函数定义在FFmpeg源码(本文演示用的FFmpeg源码版本为7.0.1)的源文件libavformat/mpegts.c中:

cpp 复制代码
/* return AVERROR_something if error or EOF. Return 0 if OK. */
static int read_packet(AVFormatContext *s, uint8_t *buf, int raw_packet_size,
                       const uint8_t **data)
{
    AVIOContext *pb = s->pb;
    int len;

    for (;;) {
        len = ffio_read_indirect(pb, buf, TS_PACKET_SIZE, data);
        if (len != TS_PACKET_SIZE)
            return len < 0 ? len : AVERROR_EOF;
        /* check packet sync byte */
        if ((*data)[0] != 0x47) {
            /* find a new packet start */

            if (mpegts_resync(s, raw_packet_size, *data) < 0)
                return AVERROR(EAGAIN);
            else
                continue;
        } else {
            break;
        }
    }
    return 0;
}

该函数的作用是:从一段MPEG2-TS传输流/TS文件或内存中读取出接下来的一个transport packet的前188个字节。transport packet长度为192和204字节的TS格式实际上是在188字节的Packet后部加上额外的字段,所以read_packet函数只会读取出transport packet的前188字节。对于transport packet长度固定为188字节的TS格式,使用read_packet函数可以读取出一个transport packet的全部数据。

形参s:既是输入型参数也是输出型参数。指向一个AVFormatContext类型变量。

形参buf:输出型参数。仅当"AVIOContext输入缓冲区中还未被读取的数据量不小于计划要读取的字节数(TS_PACKET_SIZE,188个字节)",并且"该MPEG2-TS传输流/TS文件被打开不是为了写入的"时有意义。保存读上来的数据的缓冲区。

形参data:输出型参数。"*data"指向保存读上来的数据的缓冲区。执行read_packet函数后,"*data"指向缓冲区会存贮被读取到的这个transport packet的前188字节。

返回值:返回0表示成功,返回一个负数表示出错。

(二)read_packet函数的内部实现分析

TS_PACKET_SIZE为宏定义,等价于188,表示一个普通transport packet的长度:

cpp 复制代码
#define TS_PACKET_SIZE 188

read_packet函数中,首先通过ffio_read_indirect函数从内存或TS文件或socket中读取188个字节数据,如果实际读取到的数据小于188字节,表示文件读完了或出错了,read_packet函数直接返回。关于ffio_read_indirect函数的用法可以参考:《FFmpeg源码:ffio_read_indirect函数分析》:

cpp 复制代码
        len = ffio_read_indirect(pb, buf, TS_PACKET_SIZE, data);
        if (len != TS_PACKET_SIZE)
            return len < 0 ? len : AVERROR_EOF;

如果上述读取到的数据的第一个字节不是同步字节(0x47),执行mpegts_resync函数进行重新同步操作,让AVIOContext文件位置指针(s->pb->buf_ptr)指向MPEG2-TS传输流/TS文件中的值为"0x47"的同步字节。再通过continue关键字在for循环中重新执行ffio_read_indirect函数,重新读取188个字节数据,保证读取到的数据的第一个字节为同步字节,从而保证通过read_packet函数读取的是一个transport packet的前188个字节数据:

cpp 复制代码
        /* check packet sync byte */
        if ((*data)[0] != 0x47) {
            /* find a new packet start */

            if (mpegts_resync(s, raw_packet_size, *data) < 0)
                return AVERROR(EAGAIN);
            else
                continue;
        } else {
            break;
        }

三、mpegts_resync函数

(一)mpegts_resync函数的定义

mpegts_resync函数定义在源文件libavformat/mpegts.c中:

cpp 复制代码
static int mpegts_resync(AVFormatContext *s, int seekback, const uint8_t *current_packet)
{
    MpegTSContext *ts = s->priv_data;
    AVIOContext *pb = s->pb;
    int c, i;
    uint64_t pos = avio_tell(pb);
    int64_t back = FFMIN(seekback, pos);

    //Special case for files like 01c56b0dc1.ts
    if (current_packet[0] == 0x80 && current_packet[12] == 0x47 && pos >= TS_PACKET_SIZE) {
        avio_seek(pb, 12 - TS_PACKET_SIZE, SEEK_CUR);
        return 0;
    }

    avio_seek(pb, -back, SEEK_CUR);

    for (i = 0; i < ts->resync_size; i++) {
        c = avio_r8(pb);
        if (avio_feof(pb))
            return AVERROR_EOF;
        if (c == 0x47) {
            int new_packet_size, ret;
            avio_seek(pb, -1, SEEK_CUR);
            pos = avio_tell(pb);
            ret = ffio_ensure_seekback(pb, PROBE_PACKET_MAX_BUF);
            if (ret < 0)
                return ret;
            new_packet_size = get_packet_size(s);
            if (new_packet_size > 0 && new_packet_size != ts->raw_packet_size) {
                av_log(ts->stream, AV_LOG_WARNING, "changing packet size to %d\n", new_packet_size);
                ts->raw_packet_size = new_packet_size;
            }
            avio_seek(pb, pos, SEEK_SET);
            return 0;
        }
    }
    av_log(s, AV_LOG_ERROR,
           "max resync size reached, could not find sync byte\n");
    /* no sync found */
    return AVERROR_INVALIDDATA;
}

该函数的作用是:进行重新同步操作,让AVIOContext文件位置指针(s->pb->buf_ptr)指向MPEG2-TS传输流/TS文件中的值为"0x47"的同步字节。

形参s:既是输入型参数也是输出型参数。指向一个AVFormatContext类型变量。

形参seekback:输入型参数。需要回退的大小,值一般为该MPEG2-TS传输流/TS文件中的一个transport packet的长度,以字节为单位。

形参current_packet:输入型参数,保存一个transport packet的数据。

返回值:返回0表示成功,返回一个负数表示出错。

(二)mpegts_resync函数的内部实现分析

mpegts_resync函数中,首先通过avio_tell函数,得到文件位置指针当前位置(s->buf_ptr)相对于TS文件的文件首(s->buffer)的偏移字节数。关于avio_tell函数用法可以参考:《FFmpeg源码:avio_tell函数分析》:

cpp 复制代码
    uint64_t pos = avio_tell(pb);

得到需要回退的最小值:

cpp 复制代码
    int64_t back = FFMIN(seekback, pos);

判断该媒体文件/流是不是属于TS文件的特殊例子(非标的TS文件),如果是,把AVIOContext的文件位置指针回退到离当前位置(12 - TS_PACKET_SIZE)字节处。关于avio_seek函数的有用法可以参考:《FFmpeg源码:avio_seek函数分析》:

cpp 复制代码
    //Special case for files like 01c56b0dc1.ts
    if (current_packet[0] == 0x80 && current_packet[12] == 0x47 && pos >= TS_PACKET_SIZE) {
        avio_seek(pb, 12 - TS_PACKET_SIZE, SEEK_CUR);
        return 0;
    }

不断通过avio_r8函数读取一个字节数据(关于avio_r8函数的有用法可以参考:FFmpeg源码:avio_r8、avio_rl16、avio_rl24、avio_rl32、avio_rl64函数分析),如果读取到TS文件的末尾了(avio_feof(pb)为真),返回AVERROR_EOF(关于avio_feof函数的有用法可以参考:FFmpeg源码:avio_feof函数分析)。如果还没读取到末尾,并且读取到的数据是"0x47",表示是同步字节,执行if (c == 0x47)为真时大括号里的操作:

cpp 复制代码
    for (i = 0; i < ts->resync_size; i++) {
        c = avio_r8(pb);
        if (avio_feof(pb))
            return AVERROR_EOF;
        if (c == 0x47) {
        //...
            return 0;
        }
    }

c == 0x47为真时,首先通过avio_seek函数让AVIOContext的文件位置指针回退一个字节,这样pb->buf_ptr就会指向值为"0x47"的同步字节:

cpp 复制代码
            int new_packet_size, ret;
            avio_seek(pb, -1, SEEK_CUR);

重新得到此时文件位置指针当前位置(s->buf_ptr)相对于TS文件的文件首(s->buffer)的偏移字节数:

cpp 复制代码
            pos = avio_tell(pb);

确保请求的seekback缓冲区大小可用:

cpp 复制代码
            ret = ffio_ensure_seekback(pb, PROBE_PACKET_MAX_BUF);
            if (ret < 0)
                return ret;

得到这段MPEG2-TS传输流/TS文件中每个transport packet的长度,赋值给变量new_packet_size。关于get_packet_size函数的用法可以参考:《音视频入门基础:MPEG2-TS专题(6)------FFmpeg源码中,获取MPEG2-TS传输流每个transport packet长度的实现》:

cpp 复制代码
            new_packet_size = get_packet_size(s);

如果上述获取到的transport packet的长度跟原来内存中保存的transport packet长度不一致,打印日志:"changing packet size to XXX",调整内存中保存的transport packet长度(ts->raw_packet_size)为新的长度:

cpp 复制代码
            if (new_packet_size > 0 && new_packet_size != ts->raw_packet_size) {
                av_log(ts->stream, AV_LOG_WARNING, "changing packet size to %d\n", new_packet_size);
                ts->raw_packet_size = new_packet_size;
            }

由于执行上述get_packet_size函数后,AVIOContext的文件位置指针(pb->buf_ptr)会改变,所以重新执行一次avio_seek函数,确保pb->buf_ptr指向值为"0x47"的同步字节:

cpp 复制代码
            avio_seek(pb, pos, SEEK_SET);
            return 0;
相关推荐
EasyNTS1 小时前
RTSP播放器EasyPlayer.js播放器分辨率高的视频在设置container的宽高较小时,会出现锯齿状的画面效果
音视频
EasyCVR7 小时前
ISUP协议视频平台EasyCVR私有化视频平台新能源汽车充电停车管理方案的创新与实践
大数据·网络·汽车·音视频·h.265·h.264
sp_fyf_202410 小时前
【大语言模型】ACL2024论文-17 VIDEO-CSR:面向视觉-语言模型的复杂视频摘要创建
人工智能·深度学习·神经网络·机器学习·语言模型·音视频
柳鲲鹏10 小时前
全网首发:Ubuntu编译跨平台嵌入式支持ffmpeg的OpenCV
linux·ubuntu·ffmpeg
lgily-122510 小时前
视频对接rtsp协议学习
后端·学习·音视频
Mike_66610 小时前
控制1秒30帧的视频频率
算法·音视频
冰山一脚201311 小时前
ffplay音频SDL播放处理
ffmpeg
xindoo11 小时前
如何用GPT-4o解读视频
aigc·gpt-3·音视频
9527华安12 小时前
FPGA实现PCIE3.0视频采集转SFP光口千兆UDP网络输出,基于XDMA+GTH架构,提供2套工程源码和技术支持
网络·fpga开发·udp·音视频·xdma·pcie3.0
cv君14 小时前
视频修复技术和实时在线处理
深度学习·音视频·transformer·视频修复