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

你可能感兴趣的:(ffmpeg)