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,待续......

  • 相关阅读:
    【React Native】某个页面禁用物理返回键
    【React Native】DeviceEventEmitter监听通知及带参数传值
    转载【React Native代码】手写验证码倒计时组件
    【React Native】 中设置 APP 名称、应用图标、为安卓添加启动图
    【React Native错误集】* What went wrong: Execution failed for task ':app:installDebug'.
    【React Native错误集】Import fails with "Failed to execute 'ImportScripts' on 'WorkerGlobalScope'"
    【React Native错误集】Android error “Could not get BatchedBridge, make sure your bundle is packaged properly” on start of app
    「React Native笔记」在React的 setState 中操作数组和对象的多种方法(合集)
    【React Native】Error: Attribute application@allowBackup value=(false) from AndroidManifest.xml
    坚果云如何使用二次验证码/谷歌身份验证器/两步验证/虚拟MFA?
  • 原文地址:https://www.cnblogs.com/moonvan/p/2221258.html
Copyright © 2011-2022 走看看