加载中…
个人资料
南冠彤
南冠彤
  • 博客等级:
  • 博客积分:0
  • 博客访问:411,015
  • 关注人气:59
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
相关博文
推荐博文
谁看过这篇博文
加载中…
正文 字体大小:

(转)ffmpeg mpegts.c文件分析

(2012-12-31 22:59:34)
标签:

杂谈

分类: 音视频

http://blog.chinaunix.net/uid-24322243-id-2620178.html

1. 综述

  ffmpeg框架对应MPEG-2 TS流的解析的代码在mpegts.c文件中,该文件有两个解复用的实例:mpegts_demuxer和 mpegtsraw_demuxer,mpegts_demuxer对应的真实的TS流格式,也就是机顶盒直接处理的TS流,本文主要分析和该种格式相关 的代码;mpegtsraw_demuxer这个格式我没有遇见过,本文中不做分析。本文针对的ffmpeg的版本是0.5版本。

2. mpegts_demuxer结构分析

AVInputFormat mpegts_demuxer = {au
    "mpegts", //demux的名称
    NULL_IF_CONFIG_SMALL("MPEG-2 transport stream format"),//如果定义了CONFIG_SMALL宏,该域返回NULL,也就是取消long_name域的定义。
    sizeof(MpegTSContext),//每个demuxer的结构的私有域的大小
    mpegts_probe,//检测是否是TS流格式
    mpegts_read_header,//下文介绍
    mpegts_read_packet,//下文介绍
    mpegts_read_close,//关闭demuxer
    read_seek,//下文介绍
    mpegts_get_pcr,//下文介绍
    .flags = AVFMT_SHOW_IDS|AVFMT_TS_DISCONT,//下文介绍
};

  该结构通过av_register_all函数注册到ffmpeg的主框架中,通过mpegts_probe函数来检测是否是TS流格式,然后通过 mpegts_read_header函数找到一路音频流和一路视频流(注意:在该函数中没有找全所有的音频流和视频流),最后调用 mpegts_read_packet函数将找到的音频流和视频流数据提取出来,通过主框架推入解码器。

3. mpegts_probe函数分析

  mpegts_probe被av_probe_input_format2调用,根据返回的score来判断那种格式的可能性最大。mpegts_probe调用了analyze函数,我们先分析一下analyze函数。

static int analyze(const uint8_t *buf, int size, int packet_size, int *index){
    int stat[TS_MAX_PACKET_SIZE];//积分统计结果
    int i;
    int x=0;
    int best_score=0;

    memset(stat, 0, packet_size*sizeof(int));

    for(x=i=0; i
        if(buf[i] == 0x47 && !(buf[i+1] & 0x80) && (buf[i+3] & 0x30)){
            stat[x]++;
            if(stat[x] > best_score){
                best_score= stat[x];
                if(index) *index= x;
            }
        }

        x++;
        if(x == packet_size) x= 0;
    }

    return best_score;
}
analyze函数的思路:

  buf[i] == 0x47 && !(buf[i+1] & 0x80) && (buf[i+3] & 0x30)是TS流同步开始的模式,0x47是TS流同步的标志,(buf[i+1] & 0x80是传输错误标志,buf[i+3] & 0x30为0时表示为ISO/IEC未来使用保留,目前不存在这样的值。记该模式为“TS流同步模式”

  stat数组变量存储的是“TS流同步模式”在某个位置出现的次数。

  analyze函数扫描检测数据,如果发现该模式,则stat[i%packet_size]++(函数中的x变量就是用来取模运算的,因为当x==packet_size时,x就被归零),扫描完后,自然是同步位置的累加值最大。

  mpegts_probe函数通过调用analyze函数来得到相应的分数,ffmpeg框架会根据该分数判断是否是对应的格式,返回对应的AVInputFormat实例。

4. mpegts_read_header函数分析

下文中省略号代表的是和mpegtsraw_demuxer相关的代码,暂不涉及。
static int mpegts_read_header(AVFormatContext *s,
                              AVFormatParameters *ap)
{
    MpegTSContext *ts = s->priv_data;
    ByteIOContext *pb = s->pb;
    uint8_t buf[5*1024];
    int len;
    int64_t pos;

    ......

    //保存流的当前位置,便于检测操作完成后恢复到原来的位置,
    //这样在播放的时候就不会浪费一段流。
    pos = url_ftell(pb);

    //读取一段流来检测TS包的大小
    len = get_buffer(pb, buf, sizeof(buf));
    if (len != sizeof(buf))
        goto fail;

    //得到TS流包的大小,通常是188bytes,我目前见过的都是188个字节的。
    //TS包的大小有三种:
    //1) 通常情况下的188字节
    //2)日本弄了个192Bytes的DVH-S格式
    //3)在188Bytes的基础上,加上16Bytes的FEC(前向纠错),也就是204bytes
    ts->raw_packet_size = get_packet_size(buf, sizeof(buf));
    if (ts->raw_packet_size <= 0)
        goto fail;
    ts->stream = s;
   
    //auto_guess = 1, 则在handle_packet的函数中只要发现一个PES的pid就
    //建立该PES的stream
    //auto_guess = 0, 则忽略。

    //auto_guess主要作用是用来在TS流中没有业务信息时,如果被设置成了1的话,

    //那么就会将任何一个PID的流当做媒体流建立对应的PES数据结构。
    //在mpegts_read_header函数的过程中发现了PES的pid,但
    //是不建立对应的流,只是分析PSI信息。
    //相关的代码见handle_packet函数的下面的代码:
    // tss = ts->pids[pid];
    //if (ts->auto_guess && tss == NULL && is_start) {
    //    add_pes_stream(ts, pid, -1, 0);
    //    tss = ts->pids[pid];
    //}
    ts->auto_guess = 0;

    if (s->iformat == &mpegts_demuxer) {
       
       
        url_fseek(pb, pos, SEEK_SET);

        //挂载解析SDT表的回调函数到ts->pids变量上,
        //这样在handle_packet函数中根据对应的pid找到对应处理回调函数。
        mpegts_open_section_filter(ts, SDT_PID, sdt_cb, ts, 1);

        //同上,只是挂上PAT表解析的回调函数
        mpegts_open_section_filter(ts, PAT_PID, pat_cb, ts, 1);

        //探测一段流,便于检测出SDT,PAT,PMT表
        handle_packets(ts, s->probesize);
       
   
        //打开add pes stream的标志,这样在handle_packet函数中发现了pes的
        //pid,就会自动建立该pes的stream。
        ts->auto_guess = 1;

        dprintf(ts->stream, "tuning done\n");

        s->ctx_flags |= AVFMTCTX_NOHEADER;
    } else {

        ......

    }

    //恢复到检测前的位置。
    url_fseek(pb, pos, SEEK_SET);
    return 0;
 fail:
    return -1;
}

下面介绍被mpegts_read_header直接或者间接调用的几个函数:mpegts_open_section_filter,
handle_packets,handle_packet

5. mpegts_open_section_filter函数分析
这个函数可以解释mpegts.c代码结构的精妙之处,PSI业务信息表的处理
都是通过该函数挂载到MpegTSContext结构的pids字段上的。这样如果你
想增加别的业务信息的表处理函数只要通过这个函数来挂载即可,体现了
软件设计的著名的“开闭”原则。下面分析一下他的代码。
static MpegTSFilter *mpegts_open_section_filter(MpegTSContext *ts, unsigned int pid,
                                         SectionCallback *section_cb, void *opaque,
                                         int check_crc)
{
    MpegTSFilter *filter;
    MpegTSSectionFilter *sec;

    dprintf(ts->stream, "Filter: pid=0x%x\n", pid);

    if (pid >= NB_PID_MAX || ts->pids[pid])
        return NULL;

    //给filter分配空间,挂载到MpegTSContext的pids上
    //就是该实例
    filter = av_mallocz(sizeof(MpegTSFilter));
    if (!filter)
        return NULL;

    //挂载filter实例
    ts->pids[pid] = filter;

    //设置filter相关的参数,因为业务信息表的分析的单位是段,
    //所以该filter的类型是MPEGTS_SECTION
    filter->type = MPEGTS_SECTION;
   
    //设置pid
    filter->pid = pid;
    filter->last_cc = -1;

    //设置filter回调处理函数
    sec = &filter->u.section_filter;
    sec->section_cb = section_cb;

    sec->opaque = opaque;

    //分配段数据处理的缓冲区,调用handle_packet函数后会调用
    //write_section_data将ts包中的业务信息表的数据存储在这儿,
    //直到一个段收集完成才交付上面注册的回调函数处理。
    sec->section_buf = av_malloc(MAX_SECTION_SIZE);
    sec->check_crc = check_crc;
    if (!sec->section_buf) {
        av_free(filter);
        return NULL;
    }
    return filter;
}

6. handle_packets函数分析
  handle_packets函数在两个地方被调用,一个是mpegts_read_header函数中,
另外一个是mpegts_read_packet函数中,被mpegts_read_header函数调用是用
来搜索PSI业务信息,nb_packets参数为探测的ts包的个数;在mpegts_read_packet
函数中被调用用来搜索补充PSI业务信息和demux PES流,nb_packets为0,0不
是表示处理的包的个数为0。
static int handle_packets(MpegTSContext *ts, int nb_packets)
{
    AVFormatContext *s = ts->stream;
    ByteIOContext *pb = s->pb;
    uint8_t packet[TS_PACKET_SIZE];
    int packet_num, ret;

    //该变量指示一次handle_packets处理的结束。
    //在mpegts_read_packet被调用的时候,如果发现完一个PES的包,则
    // ts->stop_parse = 1,则当前分析结束。
    ts->stop_parse = 0;
    packet_num = 0;
    for(;;) {
        if (ts->stop_parse>0)
            break;
        packet_num++;
        if (nb_packets != 0 && packet_num >= nb_packets)
            break;
        //读取一个ts包,通常是188bytes
        ret = read_packet(pb, packet, ts->raw_packet_size);
        if (ret != 0)
            return ret;
        handle_packet(ts, packet);
    }
    return 0;
}

7. handle_packet函数分析
可以说handle_packet是mpegts.c代码的核心,所有的其他代码都是为
这个函数准备的。
在调用该函数之前先调用read_packet函数获得一个ts包(通常是188bytes),
然后传给该函数,packet参数就是TS包。
static int 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;
    int64_t pos;

    //从TS包获得包的PID。
    pid = AV_RB16(packet + 1) & 0x1fff;
    if(pid && discard_pid(ts, pid))
        return 0;

    is_start = packet[1] & 0x40;
    tss = ts->pids[pid];

    //ts->auto_guess在mpegts_read_header函数中被设置为0,
    //也就是说在ts检测过程中是不建立pes stream的。
    if (ts->auto_guess && tss == NULL && is_start) {
        add_pes_stream(ts, pid, -1, 0);
        tss = ts->pids[pid];
    }
    //mpegts_read_header函数调用handle_packet函数只是处理TS流的
    //业务信息,因为并没有为对应的PES建立tss,所以tss为空,直接返回。
    if (!tss)
        return 0;

   
    cc = (packet[3] & 0xf);
    cc_ok = (tss->last_cc < 0) || ((((tss->last_cc + 1) & 0x0f) == cc));
    tss->last_cc = cc;

   
    afc = (packet[3] >> 4) & 3;
    p = packet + 4;
    if (afc == 0)
        return 0;
    if (afc == 2)
        return 0;
    if (afc == 3) {
       
        p += p[0] + 1;
    }
   
    p_end = packet + TS_PACKET_SIZE;
    if (p >= p_end)
        return 0;

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

    if (tss->type == MPEGTS_SECTION) {
        //表示当前的TS包包含一个新的业务信息段
        if (is_start) {
            //获取pointer field字段,
            //新的段从pointer field字段指示的位置开始
            len = *p++;
            if (p + len > p_end)
                return 0;
            if (len && cc_ok) {
                //这个时候TS的负载有两个部分构成:

                //1)从TS负载开始到pointer field字段指示的位置;
                //2)从pointer field字段指示的位置到TS包结束


                //1)位置代表的是上一个段的末尾部分。
                //2)位置代表的新的段开始的部分。
                //下面的代码是保存上一个段末尾部分数据,也就是
                //1)位置的数据。
                write_section_data(s, tss,
                                   p, len, 0);
               
                if (!ts->pids[pid])
                    return 0;
            }
            p += len;
            //保留新的段数据,也就是2)位置的数据。
            if (p < p_end) {
                write_section_data(s, tss,
                                   p, p_end - p, 1);
            }
        } else {
            //保存段中间的数据。
            if (cc_ok) {
                write_section_data(s, tss,
                                   p, p_end - p, 0);
            }
        }
    } else {
        int ret;
        //正常的PES数据的处理
        // Note: The position here points actually behind the current packet.
        if ((ret = tss->u.pes_filter.pes_cb(tss, p, p_end - p, is_start,
                                            pos - ts->raw_packet_size)) < 0)
            return ret;
    }

    return 0;
}

8. write_section_data函数分析
  PSI业务信息表在TS流中是以段为单位传输的。
static void write_section_data(AVFormatContext *s, MpegTSFilter *tss1,
                               const uint8_t *buf, int buf_size, int is_start)
{
    MpegTSSectionFilter *tss = &tss1->u.section_filter;
    int len;
    //buf中是一个段的开始部分。
    if (is_start) {
        //将内容复制到tss->section_buf中保存
        memcpy(tss->section_buf, buf, buf_size);
        //tss->section_index段索引。
        tss->section_index = buf_size;
        //段的长度,现在还不知道,设置为-1
        tss->section_h_size = -1;
        //是否到达段的结尾。
        tss->end_of_section_reached = 0;
    } else {
        //buf中是段中间的数据。
        if (tss->end_of_section_reached)
            return;
        len = 4096 - tss->section_index;
        if (buf_size < len)
            len = buf_size;
        memcpy(tss->section_buf + tss->section_index, buf, len);
        tss->section_index += len;
    }

    //如果条件满足,计算段的长度
    if (tss->section_h_size == -1 && tss->section_index >= 3) {
        len = (AV_RB16(tss->section_buf + 1) & 0xfff) + 3;
        if (len > 4096)
            return;
        tss->section_h_size = len;
    }

    //判断段数据是否收集完毕,如果收集完毕,调用相应的回调函数处理该段。
    if (tss->section_h_size != -1 && tss->section_index >= tss->section_h_size) {
        tss->end_of_section_reached = 1;
        if (!tss->check_crc ||
            av_crc(av_crc_get_table(AV_CRC_32_IEEE), -1,
                   tss->section_buf, tss->section_h_size) == 0)
            tss->section_cb(tss1, tss->section_buf, tss->section_h_size);
    }
}

0

阅读 评论 收藏 转载 喜欢 打印举报/Report
  • 评论加载中,请稍候...
发评论

    发评论

    以上网友发言只代表其个人观点,不代表新浪网的观点或立场。

      

    新浪BLOG意见反馈留言板 电话:4000520066 提示音后按1键(按当地市话标准计费) 欢迎批评指正

    新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 会员注册 | 产品答疑

    新浪公司 版权所有