音视频入门基础:MPEG2-PS专题(5)——FFmpeg源码中,解析PS流中的PES流的实现

一、引言

从《音视频入门基础:MPEG2-PS专题(3)------MPEG2-PS格式简介》中可以知道,PS流由一个个pack(包装)组成。一个pack = 一个pack_header + 一个或多个PES_packet。pack_header中还可能存在system header。

但是pack_header和system header中并没有什么重要信息,所以FFmpeg源码在解析PS流时会跳过pack_header和system header,直接解析PES packet。

FFmpeg源码中通过mpegps_read_pes_header函数解析PS流中的PES packet。

二、mpegps_read_pes_header函数的定义

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

cpp 复制代码
/* read the next PES header. Return its position in ppos
 * (if not NULL), and its start code, pts and dts.
 */
static int mpegps_read_pes_header(AVFormatContext *s,
                                  int64_t *ppos, int *pstart_code,
                                  int64_t *ppts, int64_t *pdts)
{
    MpegDemuxContext *m = s->priv_data;
    int len, size, startcode, c, flags, header_len;
    int pes_ext, ext2_len, id_ext, skip;
    int64_t pts, dts;
    int64_t last_sync = avio_tell(s->pb);

error_redo:
    avio_seek(s->pb, last_sync, SEEK_SET);
redo:
    /* next start code (should be immediately after) */
    m->header_state = 0xff;
    size      = MAX_SYNC_SIZE;
    startcode = find_next_start_code(s->pb, &size, &m->header_state);
    last_sync = avio_tell(s->pb);
    if (startcode < 0) {
        if (avio_feof(s->pb))
            return AVERROR_EOF;
        // FIXME we should remember header_state
        return FFERROR_REDO;
    }

    if (startcode == PACK_START_CODE)
        goto redo;
    if (startcode == SYSTEM_HEADER_START_CODE)
        goto redo;
    if (startcode == PADDING_STREAM) {
        avio_skip(s->pb, avio_rb16(s->pb));
        goto redo;
    }
    if (startcode == PRIVATE_STREAM_2) {
        if (!m->sofdec) {
            /* Need to detect whether this from a DVD or a 'Sofdec' stream */
            int len = avio_rb16(s->pb);
            int bytesread = 0;
            uint8_t *ps2buf = av_malloc(len);

            if (ps2buf) {
                bytesread = avio_read(s->pb, ps2buf, len);

                if (bytesread != len) {
                    avio_skip(s->pb, len - bytesread);
                } else {
                    uint8_t *p = 0;
                    if (len >= 6)
                        p = memchr(ps2buf, 'S', len - 5);

                    if (p)
                        m->sofdec = !memcmp(p+1, "ofdec", 5);

                    m->sofdec -= !m->sofdec;

                    if (m->sofdec < 0) {
                        if (len == 980  && ps2buf[0] == 0) {
                            /* PCI structure? */
                            uint32_t startpts = AV_RB32(ps2buf + 0x0d);
                            uint32_t endpts = AV_RB32(ps2buf + 0x11);
                            uint8_t hours = ((ps2buf[0x19] >> 4) * 10) + (ps2buf[0x19] & 0x0f);
                            uint8_t mins  = ((ps2buf[0x1a] >> 4) * 10) + (ps2buf[0x1a] & 0x0f);
                            uint8_t secs  = ((ps2buf[0x1b] >> 4) * 10) + (ps2buf[0x1b] & 0x0f);

                            m->dvd = (hours <= 23 &&
                                      mins  <= 59 &&
                                      secs  <= 59 &&
                                      (ps2buf[0x19] & 0x0f) < 10 &&
                                      (ps2buf[0x1a] & 0x0f) < 10 &&
                                      (ps2buf[0x1b] & 0x0f) < 10 &&
                                      endpts >= startpts);
                        } else if (len == 1018 && ps2buf[0] == 1) {
                            /* DSI structure? */
                            uint8_t hours = ((ps2buf[0x1d] >> 4) * 10) + (ps2buf[0x1d] & 0x0f);
                            uint8_t mins  = ((ps2buf[0x1e] >> 4) * 10) + (ps2buf[0x1e] & 0x0f);
                            uint8_t secs  = ((ps2buf[0x1f] >> 4) * 10) + (ps2buf[0x1f] & 0x0f);

                            m->dvd = (hours <= 23 &&
                                      mins  <= 59 &&
                                      secs  <= 59 &&
                                      (ps2buf[0x1d] & 0x0f) < 10 &&
                                      (ps2buf[0x1e] & 0x0f) < 10 &&
                                      (ps2buf[0x1f] & 0x0f) < 10);
                        }
                    }
                }

                av_free(ps2buf);

                /* If this isn't a DVD packet or no memory
                 * could be allocated, just ignore it.
                 * If we did, move back to the start of the
                 * packet (plus 'length' field) */
                if (!m->dvd || avio_skip(s->pb, -(len + 2)) < 0) {
                    /* Skip back failed.
                     * This packet will be lost but that can't be helped
                     * if we can't skip back
                     */
                    goto redo;
                }
            } else {
                /* No memory */
                avio_skip(s->pb, len);
                goto redo;
            }
        } else if (!m->dvd) {
            int len = avio_rb16(s->pb);
            avio_skip(s->pb, len);
            goto redo;
        }
    }
    if (startcode == PROGRAM_STREAM_MAP) {
        mpegps_psm_parse(m, s->pb);
        goto redo;
    }

    /* find matching stream */
    if (!((startcode >= 0x1c0 && startcode <= 0x1df) ||
          (startcode >= 0x1e0 && startcode <= 0x1ef) ||
          (startcode == 0x1bd) ||
          (startcode == PRIVATE_STREAM_2) ||
          (startcode == 0x1fd)))
        goto redo;
    if (ppos) {
        *ppos = avio_tell(s->pb) - 4;
    }
    len = avio_rb16(s->pb);
    pts =
    dts = AV_NOPTS_VALUE;
    if (startcode != PRIVATE_STREAM_2)
    {
    /* stuffing */
    for (;;) {
        if (len < 1)
            goto error_redo;
        c = avio_r8(s->pb);
        len--;
        /* XXX: for MPEG-1, should test only bit 7 */
        if (c != 0xff)
            break;
    }
    if ((c & 0xc0) == 0x40) {
        /* buffer scale & size */
        avio_r8(s->pb);
        c    = avio_r8(s->pb);
        len -= 2;
    }
    if ((c & 0xe0) == 0x20) {
        dts  =
        pts  = get_pts(s->pb, c);
        len -= 4;
        if (c & 0x10) {
            dts  = get_pts(s->pb, -1);
            len -= 5;
        }
    } else if ((c & 0xc0) == 0x80) {
        /* mpeg 2 PES */
        flags      = avio_r8(s->pb);
        header_len = avio_r8(s->pb);
        len       -= 2;
        if (header_len > len)
            goto error_redo;
        len -= header_len;
        if (flags & 0x80) {
            dts         = pts = get_pts(s->pb, -1);
            header_len -= 5;
            if (flags & 0x40) {
                dts         = get_pts(s->pb, -1);
                header_len -= 5;
            }
        }
        if (flags & 0x3f && header_len == 0) {
            flags &= 0xC0;
            av_log(s, AV_LOG_WARNING, "Further flags set but no bytes left\n");
        }
        if (flags & 0x01) { /* PES extension */
            pes_ext = avio_r8(s->pb);
            header_len--;
            /* Skip PES private data, program packet sequence counter
             * and P-STD buffer */
            skip  = (pes_ext >> 4) & 0xb;
            skip += skip & 0x9;
            if (pes_ext & 0x40 || skip > header_len) {
                av_log(s, AV_LOG_WARNING, "pes_ext %X is invalid\n", pes_ext);
                pes_ext = skip = 0;
            }
            avio_skip(s->pb, skip);
            header_len -= skip;

            if (pes_ext & 0x01) { /* PES extension 2 */
                ext2_len = avio_r8(s->pb);
                header_len--;
                if ((ext2_len & 0x7f) > 0) {
                    id_ext = avio_r8(s->pb);
                    if ((id_ext & 0x80) == 0)
                        startcode = ((startcode & 0xff) << 8) | id_ext;
                    header_len--;
                }
            }
        }
        if (header_len < 0)
            goto error_redo;
        avio_skip(s->pb, header_len);
    } else if (c != 0xf)
        goto redo;
    }

    if (startcode == PRIVATE_STREAM_1) {
        int ret = ffio_ensure_seekback(s->pb, 2);

        if (ret < 0)
            return ret;

        startcode = avio_r8(s->pb);
        m->raw_ac3 = 0;
        if (startcode == 0x0b) {
            if (avio_r8(s->pb) == 0x77) {
                startcode = 0x80;
                m->raw_ac3 = 1;
                avio_skip(s->pb, -2);
            } else {
                avio_skip(s->pb, -1);
            }
        } else {
            len--;
        }
    }
    if (len < 0)
        goto error_redo;
    if (dts != AV_NOPTS_VALUE && ppos) {
        int i;
        for (i = 0; i < s->nb_streams; i++) {
            if (startcode == s->streams[i]->id &&
                (s->pb->seekable & AVIO_SEEKABLE_NORMAL) /* index useless on streams anyway */) {
                ff_reduce_index(s, i);
                av_add_index_entry(s->streams[i], *ppos, dts, 0, 0,
                                   AVINDEX_KEYFRAME /* FIXME keyframe? */);
            }
        }
    }

    *pstart_code = startcode;
    *ppts        = pts;
    *pdts        = dts;
    return len;
}

该函数的作用就是:解析PS流中的一个PES packet,将其PES packet header里面的信息解析出来。

形参s:既是输入型参数也是输出型参数,指向一个AVFormatContext类型的变量。s->pb包含需要被解析的PS流的二进制数据。mpegps_read_pes_header函数解析的是s->pb->buf_ptr指向的PS流数据中的下一个PES packet。

形参ppos:输出型参数,*ppos为读取到的PES packet相对于文件首的偏移字节数。

形参pstart_code:输出型参数。如果读取到了PES packet,且该PES packet里面包含的不是的private_stream_1,*pstart_code为其PES packet header中的stream_id属性的值加0x100。

形参ppts:输出型参数。*ppts为从该PES packet的PES packet header中读取到的pts。

形参pdts:输出型参数。*pdts为从该PES packet的PES packet header中读取到的dts。

返回值:解析成功,返回该PES packet去掉PES packet header后的大小(即基本码流ES数据的大小)。解析失败,返回一个负数。

三、mpegps_read_pes_header函数的内部实现分析

mpegps_read_pes_header函数中,首先通过find_next_start_code函数找到s->pb->buf_ptr指向的PS流数据中的下一个pack header的起始码 或 下一个system header的起始码 或 下一个PES packet header的起始码:

cpp 复制代码
redo:
    /* next start code (should be immediately after) */
    m->header_state = 0xff;
    size      = MAX_SYNC_SIZE;
    startcode = find_next_start_code(s->pb, &size, &m->header_state);
    last_sync = avio_tell(s->pb);
    if (startcode < 0) {
        if (avio_feof(s->pb))
            return AVERROR_EOF;
        // FIXME we should remember header_state
        return FFERROR_REDO;
    }

如果找到的是pack header的起始码 或 system header的起始码 或 找到的PES packet中包含padding_stream数据,通过goto语句跳转,然后重新通过find_next_start_code函数查找下一个起始码:

cpp 复制代码
    if (startcode == PACK_START_CODE)
        goto redo;
    if (startcode == SYSTEM_HEADER_START_CODE)
        goto redo;
    if (startcode == PADDING_STREAM) {
        avio_skip(s->pb, avio_rb16(s->pb));
        goto redo;
    }

如果找到了符合要求的PES packet header的起始码,通过avio_tell函数读取该PES packet相对于文件首的偏移字节数,赋值给*ppos。关于avio_tell函数的用法可以参考:《FFmpeg源码:avio_tell函数分析》:

cpp 复制代码
    /* find matching stream */
    if (!((startcode >= 0x1c0 && startcode <= 0x1df) ||
          (startcode >= 0x1e0 && startcode <= 0x1ef) ||
          (startcode == 0x1bd) ||
          (startcode == PRIVATE_STREAM_2) ||
          (startcode == 0x1fd)))
        goto redo;
    if (ppos) {
        *ppos = avio_tell(s->pb) - 4;
    }

读取PES packet header中的PES_packet_length属性,赋值给变量len。关于avio_rb16函数的用法可以参考:《FFmpeg源码:avio_r8、avio_rl16、avio_rl24、avio_rl32、avio_rl64函数分析》:

cpp 复制代码
    len = avio_rb16(s->pb);

读取PES packet header中的PES_scrambling_control、PES_priority、data_alignment_indicator、copyright、original_or_copy属性,赋值给变量c:

cpp 复制代码
        c = avio_r8(s->pb);

如果上述读取到的PES_packet_length属性后面的值为"10((c & 0xc0) == 0x80为真),表示读取到的PES packet header的格式正确:

执行大括号内的内容:

cpp 复制代码
else if ((c & 0xc0) == 0x80) {
//...
}

读取PTS_DTS_flags、ESCR_flag、ES_rate_flag、DSM_trick_mode_flag、additional_copy_info_flag、PES_CRC_flag、PES_extension_flag这7个属性,赋值给变量flags:

cpp 复制代码
        flags      = avio_r8(s->pb);

读取PES_header_data_length属性,赋值给变量header_len:

cpp 复制代码
        header_len = avio_r8(s->pb);

如果PTS_DTS_flags属性的值为'10',表示PES packet header中会存在PTS,读取PTS;值为'11'时,表示PES packet header中会同时存在PTS和DTS,读取PTS和DTS,分别赋值给变量dts和pts:

cpp 复制代码
        if (flags & 0x80) {
            dts         = pts = get_pts(s->pb, -1);
            header_len -= 5;
            if (flags & 0x40) {
                dts         = get_pts(s->pb, -1);
                header_len -= 5;
            }
        }

如果PES_extension_flag属性的值为1,表示PES packet header有PES_extension域,读取PES_extension域:

cpp 复制代码
        if (flags & 0x01) { /* PES extension */
            pes_ext = avio_r8(s->pb);
            header_len--;
            /* Skip PES private data, program packet sequence counter
             * and P-STD buffer */
            skip  = (pes_ext >> 4) & 0xb;
            skip += skip & 0x9;
            if (pes_ext & 0x40 || skip > header_len) {
                av_log(s, AV_LOG_WARNING, "pes_ext %X is invalid\n", pes_ext);
                pes_ext = skip = 0;
            }
            avio_skip(s->pb, skip);
            header_len -= skip;

            if (pes_ext & 0x01) { /* PES extension 2 */
                ext2_len = avio_r8(s->pb);
                header_len--;
                if ((ext2_len & 0x7f) > 0) {
                    id_ext = avio_r8(s->pb);
                    if ((id_ext & 0x80) == 0)
                        startcode = ((startcode & 0xff) << 8) | id_ext;
                    header_len--;
                }
            }
        }

最后返回从该PES packet的PES packet header中读取到的pts、dts、该PES packet去掉PES packet header后的大小等信息:

cpp 复制代码
    *pstart_code = startcode;
    *ppts        = pts;
    *pdts        = dts;
    return len;

四、FFmpeg源码中,解析TS流中的PES流的实现

FFmpeg源码中,解析TS流中的PES流所使用的函数不一样,是通过mpegts_push_data函数进行解析的,具体可以参考:《音视频入门基础:MPEG2-TS专题(19)------FFmpeg源码中,解析TS流中的PES流的实现》。

相关推荐
达子6668 小时前
笔记-使用ffmpeg产生rtsp视频流,然后用进行VLC播放
windows·笔记·ffmpeg
水彩橘子8 小时前
docker+ffmpeg+nginx+rtmp 拉取摄像机视频
nginx·docker·ffmpeg
Eric.Lee20219 小时前
不同方式获取音频时长 - python 实现
音视频·音频处理·音频时长·音频时长限制
爱研究的小牛9 小时前
Synthesia技术浅析(二):虚拟人物视频生成
人工智能·深度学习·机器学习·aigc·音视频
EasyGBS14 小时前
国标GB28181-2022视频平台EasyGBS小知识:局域网ip地址不够用怎么解决?
tcp/ip·安全·智能路由器·音视频·视频监控
丘上人17 小时前
ffplay 命令行 从视频第N帧开始读取 ffmpeg 命令行 提取第N帧图片
ffmpeg·音视频
EasyNVR19 小时前
EasyNVR平台现已支持AAC、G711A及G711U音频编码格式
数据库·安全·音视频·视频监控·aac·g711
qq7621182219 小时前
ffmpeg7.0 合并2个 aac 文件
c++·ffmpeg·aac
denghai邓海19 小时前
用OpenCV实现UVC视频分屏
人工智能·opencv·音视频