zoukankan      html  css  js  c++  java
  • FFMpeg对MPEG2 TS流解码的流程分析[2]

    分类: DVB相关

    FFMpeg对MPEG2 TS流解码的流程分析[2]

    5.渐入佳境
    恩,前面的基础因该已近够了,有点像手剥洋葱头的感觉,我们来看看针对MPEG TS的相
    应解析过程

    我们后面的代码,主要集中在[libavformat/mpegts.c]里面,毛爷爷说:集中优势兵力打
    围歼,恩,开始吧,蚂蚁啃骨头。

    static int mpegts_read_header(AVFormatContext *s,
                                  AVFormatParameters *ap)
    {
        MpegTSContext *ts = s->priv_data;
        ByteIOContext *pb = s->pb;
        uint8_t buf[1024];
        int len;
        int64_t pos;

        ......

        /* read the first 1024 bytes to get packet size */
        #####################################################################
        【1】有了前面分析缓冲IO的经历,下面的代码就不是什么问题了:)
        #####################################################################
        pos = url_ftell(pb);
        len = get_buffer(pb, buf, sizeof(buf));
        if (len != sizeof(buf))
            goto fail;
         #####################################################################
        【2】前面侦测文件格式时候其实已经知道TS包的大小了,这里又侦测一次,其实
        有些多余,估计是因为解码框架的原因,已近侦测的包大小没能从前面被带过来,
        可见框架虽好,却也会带来或多或少的一些不利影响
        #####################################################################
        ts->raw_packet_size = get_packet_size(buf, sizeof(buf));
        if (ts->raw_packet_size <= 0)
            goto fail;
        ts->stream = s;
        ts->auto_guess = 0;

        if (s->iformat == &mpegts_demuxer) {
            /* normal demux */

            /* first do a scaning to get all the services */
            url_fseek(pb, pos, SEEK_SET);
            ##################################################################
            【3】
            ##################################################################
            mpegts_scan_sdt(ts);
        
        ##################################################################
            【4】
            ##################################################################
            mpegts_set_service(ts);
       
        ##################################################################
            【5】
            ##################################################################
            handle_packets(ts, s->probesize);
            /* if could not find service, enable auto_guess */

            ts->auto_guess = 1;

    #ifdef DEBUG_SI
            av_log(ts->stream, AV_LOG_DEBUG, "tuning done\n");
    #endif
            s->ctx_flags |= AVFMTCTX_NOHEADER;
        } else {
            ......
        }

        url_fseek(pb, pos, SEEK_SET);
        return 0;
     fail:
        return -1;
    }

    这里简单说一下MpegTSContext *ts,从上面可以看到,其实这是为了解码不同容器格式
    所使用的私有数据,只有在相应的诸如mpegts.c文件才可以使用的,这样,增加了这个库
    的模块化,而模块化的最大好处,则在于把问题集中到了一个很小的有限区域里面,如果
    你自己构造程序时候,不妨多参考其基本思想--这样的化,你之后的代码,还有你之后的
    生活,都将轻松许多。

    【3】【4】其实调用的是同一个函数:mpegts_open_section_filter()
    我们来看看意欲何为。

    static
    MpegTSFilter *mpegts_open_section_filter(MpegTSContext *ts, unsigned int pid,
                                             SectionCallback *section_cb,
                                             void *opaque,
                                             int check_crc)
    {
        MpegTSFilter *filter;
        MpegTSSectionFilter *sec;

    #ifdef DEBUG_SI
        av_log(ts->stream, AV_LOG_DEBUG, "Filter: pid=0x%x\n", pid);
    #endif
        if (pid >= NB_PID_MAX || ts->pids[pid])
            return NULL;
        filter = av_mallocz(sizeof(MpegTSFilter));
        if (!filter)
            return NULL;
        ts->pids[pid] = filter;
        filter->type = MPEGTS_SECTION;
        filter->pid = pid;
        filter->last_cc = -1;
        sec = &filter->u.section_filter;
        sec->section_cb = section_cb;
        sec->opaque = opaque;
        sec->section_buf = av_malloc(MAX_SECTION_SIZE);
        sec->check_crc = check_crc;
        if (!sec->section_buf) {
            av_free(filter);
            return NULL;
        }
        return filter;
    }

    要完全明白这部分代码,其实需要分析作者对数据结构的定义:
    依次为:

        struct MpegTSContext;
                   |
                   V
        struct MpegTSFilter;
                   |
                   V
    +---------------+---------------+
    |                               |
    V                               V
    MpegTSPESFilter        MpegTSSectionFilter

    其实很简单,就是struct MpegTSContext;中有NB_PID_MAX(8192)个TS的Filter,而每个
    struct MpegTSFilter可能是PES的Filter或者Section的Filter。

    我们先说为什么是8192,在前面的分析中:
    给出过TS的语法结构:
    Syntax                    No. of bits    Mnemonic
    transport_packet(){       
        sync_byte            8        bslbf
        transport_error_indicator    1        bslbf
        payload_unit_start_indicator    1        bslbf
        transport_priority        1        bslbf
        PID                13        uimsbf
        transport_scrambling_control    2        bslbf
        adaptation_field_control    2        bslbf
        continuity_counter        4        uimsbf
        if(adaptation_field_control=='10'
        || adaptation_field_control=='11'){       
            adaptation_field()       
        }       
        if(adaptation_field_control=='01'
        || adaptation_field_control=='11') {       
            for (i=0;i<N;i++){       
                data_byte    8        bslbf
            }       
        }       
    }       

    而8192,则是2^13=8192(PID)的最大数目,而为什么会有PES和Section的区分,请参考
    ISO/IEC-13818-1,我实在不太喜欢重复已有的东西.

    可见【3】【4】,就是挂载了两个Section类型的过滤器,其实在TS的两种负载中,section
    是PES的元数据,只有先解析了section,才能进一步解析PES数据,因此先挂上section的
    过滤器。

    挂载上了两种section过滤器,如下:
    =========================================================================
    PID                |Section Name           |Callback
    =========================================================================
    SDT_PID(0x0011)    |ServiceDescriptionTable|sdt_cb
                       |                       |
    PAT_PID(0x0000)    |ProgramAssociationTable|pat_cb

    既然自是挂上Callback,自然是在后面的地方使用,因此,我们还是继续

    【5】处的代码看看是最重要的地方了,简单看来:
    handle_packets()
        |
        +->read_packet()
        |
        +->handle_packet()
            |
            +->write_section_data()
       
    read_packet()很简单,就是去找sync_byte(0x47),而看来handle_packet()才会是我们真
    正因该关注的地方了:)

    这个函数很重要,我们贴出代码,以备分析:
    /* handle one TS packet */
    static void handle_packet(MpegTSContext *ts, const uint8_t *packet)
    {
        AVFormatContext *s = ts->stream;
        MpegTSFilter *tss;
        int len, pid, cc, cc_ok, afc, is_start;
        const uint8_t *p, *p_end;

        ##########################################################
        获取该包的PID
        ##########################################################
        pid = AV_RB16(packet + 1) & 0x1fff;
        if(pid && discard_pid(ts, pid))
            return;
        ##########################################################
        是否是PES或者Section的开头(payload_unit_start_indicator)
        ##########################################################
        is_start = packet[1] & 0x40;
        tss = ts->pids[pid];
       
        ##########################################################
        ts->auto_guess此时为0,因此不考虑下面的代码
        ##########################################################
        if (ts->auto_guess && tss == NULL && is_start) {
            add_pes_stream(ts, pid, -1, 0);
            tss = ts->pids[pid];
        }
        if (!tss)
            return;
           
        ##########################################################
        代码说的很清楚,虽然检查,但不利用检查的结果
        ##########################################################
        /* continuity check (currently not used) */
        cc = (packet[3] & 0xf);
        cc_ok = (tss->last_cc < 0) || ((((tss->last_cc + 1) & 0x0f) == cc));
        tss->last_cc = cc;
       
        ##########################################################
        跳到adaptation_field_control
        ##########################################################
        /* skip adaptation field */
        afc = (packet[3] >> 4) & 3;
        p = packet + 4;
        if (afc == 0) /* reserved value */
            return;
        if (afc == 2) /* adaptation field only */
            return;
        if (afc == 3) {
            /* skip adapation field */
            p += p[0] + 1;
        }
       
        ##########################################################
        p已近到达TS包中的有效负载的地方
        ##########################################################
        /* if past the end of packet, ignore */
        p_end = packet + TS_PACKET_SIZE;
        if (p >= p_end)
            return;

        ts->pos47= url_ftell(ts->stream->pb) % ts->raw_packet_size;

        if (tss->type == MPEGTS_SECTION) {
            if (is_start) {
                #############################################################
                针对Section,符合部分第一个字节为pointer field,该字段如果为0,
                则表示后面紧跟着的是Section的开头,否则是某Section的End部分和
                另一Section的开头,因此,这里的流程实际上由两个值is_start
                (payload_unit_start_indicator)和len(pointer field)一起来决定
                #############################################################
                /* pointer field present */
                len = *p++;
                if (p + len > p_end)
                    return;
                if (len && cc_ok) {
                    ########################################################
                    1).is_start == 1
                       len > 0
                    负载部分由A Section的End部分和B Section的Start组成,把A的
                    End部分写入
                    ########################################################
                    /* write remaining section bytes */
                    write_section_data(s, tss,
                                       p, len, 0);
                    /* check whether filter has been closed */
                    if (!ts->pids[pid])
                        return;
                }
                p += len;
                if (p < p_end) {
                    ########################################################
                    2).is_start == 1
                       len > 0
                    负载部分由A Section的End部分和B Section的Start组成,把B的
                    Start部分写入
                    或者:
                    3).
                      is_start == 1
                      len == 0
                    负载部分仅是一个Section的Start部分,将其写入
                    ########################################################
                    write_section_data(s, tss,
                                       p, p_end - p, 1);
                }
            } else {
                if (cc_ok) {
                    ########################################################
                    4).is_start == 0
                    负载部分仅是一个Section的中间部分部分,将其写入
                    ########################################################
                    write_section_data(s, tss,
                                       p, p_end - p, 0);
                }
            }
        } else {
            ##########################################################
            如果是PES类型,直接调用其Callback,但显然,只有Section部分
            解析完成后才可能解析PES
            ##########################################################
            tss->u.pes_filter.pes_cb(tss,
                                     p, p_end - p, is_start);
        }
    }


    write_section_data()函数则反复收集buffer中的数据,指导完成相关Section的重组过
    程,然后调用之前注册的两个section_cb:

    后面我们将分析之前挂在的两个section_cb,待续......

  • 相关阅读:
    JavaScript对象编程-第3章
    JavaScript对象编程-第3章
    JavaScript基础-第2章
    JavaScript基础-第2章
    JavaScript基础-第2章
    第十八节:详解Java抽象类和接口的区别
    第十八节:详解Java抽象类和接口的区别
    第十八节:详解Java抽象类和接口的区别
    JavaScript概述-第1章
    JavaScript概述-第1章
  • 原文地址:https://www.cnblogs.com/moonvan/p/2221258.html
Copyright © 2011-2022 走看看