mpegts代码分析

因为TS流的复用和解复用是通过一个结构体 AVInputFormat 传递给解复用器的。所以重点分析该结构体提供的外部接口:

AVInputFormat mpegtsraw_demuxer = {
    "mpegtsraw",
    NULL_IF_CONFIG_SMALL("MPEG-2 raw transport stream format"),
    sizeof(MpegTSContext),
    NULL,
    mpegts_read_header,
    mpegts_raw_read_packet,
    mpegts_read_close,
    read_seek,
    mpegts_get_pcr,
    .flags = AVFMT_SHOW_IDS|AVFMT_TS_DISCONT,
};


1、mpegts_read_header():


功能:判断是TS流的类型,建立TS流的业务信息表SDT表和PAD表


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;

    if (ap) {
        ts->mpeg2ts_compute_pcr = ap->mpeg2ts_compute_pcr;
        if(ap->mpeg2ts_raw){
            av_log(s, AV_LOG_ERROR, "use mpegtsraw_demuxer!\n");
            return -1;
        }
    }/* read the first 1024 bytes to get packet size */
    pos = url_ftell(pb); //保存流的当前位置,便于检测操作完成后恢复到原来的位置,这样在播放的时候就不会浪费一段流。
    len = get_buffer(pb, buf, sizeof(buf));////读取一段流来检测TS包的大小
    if (len != sizeof(buf))
        goto fail;
    ts->raw_packet_size = get_packet_size(buf, sizeof(buf)); //得到TS包的pack的size。在get_packedt_size中buf的大小必须大于1024,否则直接返回-1。得到TS流包的大小,通常是188bytes,我目前见过的都是188个字节的。
    //TS包的大小有三种:
    //1) 通常情况下的188字节
    //2)日本弄了个192Bytes的DVH-S格式
    //3)在188Bytes的基础上,加上16Bytes的FEC(前向纠错),也就是204bytes
/*static int get_packet_size(const uint8_t *buf, int size)
{
    int score, fec_score, dvhs_score;
	//Must have 1024 byte
    if (size < (TS_FEC_PACKET_SIZE * 5 + 1))
        return -1;


    score    = analyze(buf, size, TS_PACKET_SIZE, NULL); //通过在size长度内进行对比,获得一个0x47在在间隔packet_size出现的最大概率,以下类似
    dvhs_score    = analyze(buf, size, TS_DVHS_PACKET_SIZE, NULL);
    fec_score= analyze(buf, size, TS_FEC_PACKET_SIZE, NULL);
//    av_log(NULL, AV_LOG_DEBUG, "score: %d, dvhs_score: %d, fec_score: %d \n", score, dvhs_score, fec_score);


    if     (score > fec_score && score > dvhs_score) return TS_PACKET_SIZE;          //188
    else if(dvhs_score > score && dvhs_score > fec_score) return TS_DVHS_PACKET_SIZE;//192
    else if(score < fec_score && dvhs_score < fec_score) return TS_FEC_PACKET_SIZE;  //204
    else                       return -1;
}*/
/* static int analyze(const uint8_t *buf, int size, int packet_size, int *index){
    int stat[packet_size];
    int i;
    int x=0;
    int best_score=0;


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


    for(x=i=0; i<size-3; i++){
        if(buf[i] == 0x47 && !(buf[i+1] & 0x80) && (buf[i+3] & 0x30)){
            stat[x]++;             //sta[x]记录了在x位置上(这是在0~packet_size范围内),0x47出现的次数。记住x是每到paket_size就清零的。
            if(stat[x] > best_score){
                best_score= stat[x]; //best_score存储了0x47在某个位置上出现的最大的次数
                if(index) *index= x;
            }
        }
        x++;
        if(x == packet_size) x= 0;
    }
    return best_score;
} */
  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) { //若当前输入的流为TS流 /* normal demux */ /* first do a scaning to get all the services */ url_fseek(pb, pos, SEEK_SET);
        //挂载解析SDT表的回调函数到ts->pids变量上,这样在handle_packet函数中根据对应的pid找到对应处理回调函数
mpegts_scan_sdt(ts);//这儿调用了mpegts_open_section_filter(ts, SDT_PID,sdt_cb, ts, 1),这个函数将在后面进行分析。//同上,只是挂上PAT表解析的回调函数 mpegts_set_service(ts);//这儿调用了mpegts_open_section_filter(ts, PAT_PID,pat_cb, ts, 1)函数 //探测一段流,便于检测出SDT,PAT,PMT表 handle_packets(ts, s->probesize); //这儿调用了handle_packet(ts, packet)函数 /* if could not find service, enable auto_guess *///打开add pes stream的标志,这样在handle_packet函数中发现了pes的
        //pid,就会自动建立该pes的stream。 ts->auto_guess = 1;#ifdef DEBUG_SI av_log(ts->stream, AV_LOG_DEBUG, "tuning done\n");#endif s->ctx_flags |= AVFMTCTX_NOHEADER; } else { AVStream *st; int pcr_pid, pid, nb_packets, nb_pcrs, ret, pcr_l; int64_t pcrs[2], pcr_h; int packet_count[2]; uint8_t packet[TS_PACKET_SIZE]; /* only read packets */ st = av_new_stream(s, 0); if (!st) goto fail; av_set_pts_info(st, 60, 1, 27000000); // st->codec->codec_type = CODEC_TYPE_DATA; st->codec->codec_id = CODEC_ID_MPEG2TS; /* we iterate until we find two PCRs to estimate the bitrate */ pcr_pid = -1; nb_pcrs = 0; nb_packets = 0; for(;;) { ret = read_packet(s->pb, packet, ts->raw_packet_size); if (ret < 0) return -1; pid = AV_RB16(packet + 1) & 0x1fff; if ((pcr_pid == -1 || pcr_pid == pid) && parse_pcr(&pcr_h, &pcr_l, packet) == 0) { pcr_pid = pid; packet_count[nb_pcrs] = nb_packets; pcrs[nb_pcrs] = pcr_h * 300 + pcr_l; nb_pcrs++; if (nb_pcrs >= 2) break; } nb_packets++; } /* NOTE1: the bitrate is computed without the FEC */ /* NOTE2: it is only the bitrate of the start of the stream */ ts->pcr_incr = (pcrs[1] - pcrs[0]) / (packet_count[1] - packet_count[0]); ts->cur_pcr = pcrs[0] - ts->pcr_incr * packet_count[0]; s->bit_rate = (TS_PACKET_SIZE * 8) * 27e6 / ts->pcr_incr; st->codec->bit_rate = s->bit_rate; st->start_time = ts->cur_pcr;#if 0 av_log(ts->stream, AV_LOG_DEBUG, "start=%0.3f pcr=%0.3f incr=%d\n", st->start_time / 1000000.0, pcrs[0] / 27e6, ts->pcr_incr);#endif } //恢复到检测前的位置。 url_fseek(pb, pos, SEEK_SET); return 0; fail: return -1;}


2、下面接着分析上面注释时候的函数mpegts_open_section_filter(MpegTSContext   *ts,   unsigned int pid , SectionCallback   *section_cb, void  *opaque,                                         int  check_crc):

函数的功能:将pid值为pid的滤波器挂载在ts->PID[pid]上,并设置filter的相关参数

struct MpegTSContext {
    /* user data */
    AVFormatContext *stream;
    /** raw packet size, including FEC if present            */
    int raw_packet_size;

    int pos47;

    /** if true, all pids are analyzed to find streams       */
    int auto_guess;

    /** compute exact PCR for each transport stream packet   */
    int mpeg2ts_compute_pcr;

    int64_t cur_pcr;    /**< used to estimate the exact PCR  */
    int pcr_incr;       /**< used to estimate the exact PCR  */

    /* data needed to handle file based ts */
    /** stop parsing loop                                    */
    int stop_parse;
    /** packet containing Audio/Video data                   */
    AVPacket *pkt;

    /******************************************/
    /* private mpegts data */
    /* scan context */
    /** structure to keep track of Program->pids mapping     */
    unsigned int nb_prg;
    struct Program *prg;


    /** filters for various streams specified by PMT + for the PAT and PMT */
    MpegTSFilter *pids[NB_PID_MAX];  //这儿每一个PID都有一个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)); //给filter分配空间,挂载到MpegTSContext的pids上,就是该实例
    if (!filter)
        return NULL;
    ts->pids[pid] = filter;   //挂载filter
    filter->type = MPEGTS_SECTION;
    filter->pid = pid;        //需要截获的PID值
    filter->last_cc = -1;
    sec = &filter->u.section_filter; //设置filter相关的参数,因为业务信息表的分析的单位是段,
    //所以该filter的类型是MPEGTS_SECTION sec->section_cb = section_cb; //设置filter回调处理函数 sec->opaque = opaque; sec->section_buf = av_malloc(MAX_SECTION_SIZE); //分配段数据处理的缓冲区,调用handle_packet函数后会调用
    //write_section_data将ts包中的业务信息表的数据存储在这儿,
    //直到一个段收集完成才交付上面注册的回调函数处理 sec->check_crc = check_crc; if (!sec->section_buf) { av_free(filter); return NULL; } return filter; }


3、函数:handle_packets(MpegTSContext *ts, int nb_packets)



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;

    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;
		//resrech syn (0x47),cpy one ts pkt to packet.
        ret = read_packet(pb, packet, ts->raw_packet_size); 
        if (ret != 0)
            return ret;
		
        handle_packet(ts, packet);
    }
    return 0;
}


read_packet()函数:读取一个packet到buf中:


static int read_packet(ByteIOContext *pb, uint8_t *buf, int raw_packet_size)
{
    int skip, len;

    for(;;) {
        len = get_buffer(pb, buf, TS_PACKET_SIZE); //读一个包的长度到buf中
        if (len != TS_PACKET_SIZE)
            return AVERROR(EIO);
        /* check paquet sync byte */
        if (buf[0] != 0x47) {    //如果当前的数据没有同步,则需要重新同步后,在读数据到buf中。
            /* find a new packet start */
            url_fseek(pb, -TS_PACKET_SIZE, SEEK_CUR); //移动指针
            if (mpegts_resync(pb) < 0)		      //重新进行同步,即将指针指向packet的头部	
                return AVERROR_INVALIDDATA;
            else
                continue;
        } else {
            skip = raw_packet_size - TS_PACKET_SIZE;
            if (skip > 0)
                url_fskip(pb, skip); //跳过一个包的长度,即指向下一个包
            break;
        }
    }
    return 0;
}


handle_packet()函数:

收集PID的信息

/* 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;
    int64_t pos;


    pid = AV_RB16(packet + 1) & 0x1fff; //获得当前包的PID的值
    if(pid && discard_pid(ts, pid))     //
        return;
    //PES or PSI 
    is_start = packet[1] & 0x40;
    tss = ts->pids[pid];               
    if (ts->auto_guess && tss == NULL && is_start) {
        add_pes_stream(ts, pid, -1, 0);
        tss = ts->pids[pid];
    }
    if (!tss)                         //仅当PID为SDT或PAT时,才继续处理,否则直接返回
        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;


    /* 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;
    }
    /* if past the end of packet, ignore */
    p_end = packet + TS_PACKET_SIZE;
    if (p >= p_end)
        return;


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


    if (tss->type == MPEGTS_SECTION) {
        if (is_start) {
            /* pointer field present */
            len = *p++;              //pointer_field指针:当一个分段开始时,其值为0。当pay_unit_start_indicator为1时,传输流有效负载的第一字节应该包含此指针。如果一个传输流中没有一个分段开始,则pay_unit_start_indicator为0,且没有pointer_field字段。


            if (p + len > p_end)
                return;
            if (len && cc_ok) {
                /* 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) {
                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 {
        // Note: The position here points actually behind the current packet.
        tss->u.pes_filter.pes_cb(tss,
                                 p, p_end - p, is_start, pos - ts->raw_packet_size);
    }
}







  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值