TS流之代码分析

  代码分析前,先要了解TS流基本概念:TS流之基本概念

  VLC解析TS流是通过libts库来分离的,libts库使用libdvbpsi库来解TS表。

1. libts库在加载的时候,会将以下如下两个函数注册下去,当接收到PAT或者PMT的时候,会进行调用。PAT和PMT每隔一段时间就会发送一次,以更新节目信息。

static void PATCallBack( void*, dvbpsi_pat_t * );
static void PMTCallBack( void *data, dvbpsi_pmt_t *p_pmt );

2. PATCallBack回调函数,获取并保存所有节目列表及其PID。

static void PATCallBack( void *data, dvbpsi_pat_t *p_pat )
{
    demux_t              *p_demux = (demux_t *)data;
    demux_sys_t          *p_sys = p_demux->p_sys;
    dvbpsi_pat_program_t *p_program;
    ts_pid_t             *pat = &p_sys->pid[0];

    msg_Dbg( p_demux, "PATCallBack called" );

    if( ( pat->psi->i_pat_version != -1 &&
            ( !p_pat->b_current_next ||
              p_pat->i_version == pat->psi->i_pat_version ) ) ||
        p_sys->b_user_pmt )
    {
        dvbpsi_DeletePAT( p_pat );
        return;
    }

    msg_Dbg( p_demux, "new PAT ts_id=%d version=%d current_next=%d",
             p_pat->i_ts_id, p_pat->i_version, p_pat->b_current_next );
  ....../* now create programs */
    for( p_program = p_pat->p_first_program; p_program != NULL;
         p_program = p_program->p_next )
    {
        // 依次输出PAT表中的节目,包括其number和PID
        msg_Dbg( p_demux, "  * number=%d pid=%d", p_program->i_number,
                 p_program->i_pid );
        if( p_program->i_number == 0 )
            continue;
        // 节目的PID就是它所对应的PMT,这里获取该节目对应的PMT
        ts_pid_t *pmt = &p_sys->pid[p_program->i_pid];

        ValidateDVBMeta( p_demux, p_program->i_pid );

        if( pmt->b_valid )
        {
            bool b_add = true;
            for( int i_prg = 0; b_add && i_prg < pmt->psi->i_prg; i_prg++ )
                if( pmt->psi->prg[i_prg]->i_number == p_program->i_number )
                    b_add = false;

            if( !b_add )
                continue;
        }
        else
        {
            TAB_APPEND( (ts_pid_t **), p_sys->i_pmt, p_sys->pmt, pmt );            // sunqueen modify
        }

        PIDInit( pmt, true, pat->psi );
        ts_prg_psi_t *prg = pmt->psi->prg[pmt->psi->i_prg-1];
        ......
        // 注册PMT回调函数
        prg->handle = dvbpsi_AttachPMT( p_program->i_number, PMTCallBack, p_demux );
        prg->i_number = p_program->i_number;
        prg->i_pid_pmt = p_program->i_pid;

        /* Now select PID at access level */
        if( ProgramIsSelected( p_demux, p_program->i_number ) )
        {
            if( p_sys->i_current_program == 0 )
                p_sys->i_current_program = p_program->i_number;

            if( SetPIDFilter( p_demux, p_program->i_pid, true ) )
                p_sys->b_access_control = false;
        }
    }
    pat->psi->i_pat_version = p_pat->i_version;

    dvbpsi_DeletePAT( p_pat );
}

3. PMTCallBack与PATCallBack类似,主要是循环读取某一节目的码流列表及及其PID,不再赘述。

4. Demux分离,使用GatherData收集一帧。

static int Demux( demux_t *p_demux )
{
    demux_sys_t *p_sys = p_demux->p_sys;
    bool b_wait_es = p_sys->i_pmt_es <= 0;

    /* We read at most 100 TS packet or until a frame is completed */
    for( int i_pkt = 0; i_pkt < p_sys->i_ts_read; i_pkt++ )
    {
        bool         b_frame = false;
        block_t     *p_pkt;
        if( !(p_pkt = ReadTSPacket( p_demux )) )
        {
            return 0;
        }

        if( p_sys->b_start_record )
        {
            /* Enable recording once synchronized */
            stream_Control( p_demux->s, STREAM_SET_RECORD_STATE, true, "ts" );
            p_sys->b_start_record = false;
        }

        if( p_sys->b_udp_out )
        {
            memcpy( &p_sys->buffer[i_pkt * p_sys->i_packet_size],
                    p_pkt->p_buffer, p_sys->i_packet_size );
        }

        /* Parse the TS packet */
        ts_pid_t *p_pid = &p_sys->pid[PIDGet( p_pkt )];
        // 这里可以把接收到的包的pid打出来,方便调试
        msg_Dbg( p_demux, "received pkt pid[%d]", PIDGet( p_pkt ) );

        if( p_pid->b_valid )
        {
            // 如果当前包是PSI信息,使用dvbpsi库解表
            if( p_pid->psi )
            {
                // 如果是PAT或者0x11 0x12等
                if( p_pid->i_pid == 0 || ( p_sys->b_dvb_meta && ( p_pid->i_pid == 0x11 || p_pid->i_pid == 0x12 || p_pid->i_pid == 0x14 ) ) )
                {
                    dvbpsi_PushPacket( p_pid->psi->handle, p_pkt->p_buffer );
                }
                else
                {
                    for( int i_prg = 0; i_prg < p_pid->psi->i_prg; i_prg++ )
                    {
                        dvbpsi_PushPacket( p_pid->psi->prg[i_prg]->handle,
                                           p_pkt->p_buffer );
                    }
                }
                block_Release( p_pkt );
            }       // 如果当前的包是数据,则收集 
            else if( !p_sys->b_udp_out )
            {
                // 返回值为是否已经获取了一整帧的数据
                b_frame = GatherData( p_demux, p_pid, p_pkt );
            }
            else
            {
                PCRHandle( p_demux, p_pid, p_pkt );
                block_Release( p_pkt );
            }
        }
        else
        {
            if( !p_pid->b_seen )
            {
                msg_Dbg( p_demux, "pid[%d] unknown", p_pid->i_pid );
            }
            /* We have to handle PCR if present */
            PCRHandle( p_demux, p_pid, p_pkt );
            block_Release( p_pkt );
        }
        p_pid->b_seen = true;

        if( b_frame || ( b_wait_es && p_sys->i_pmt_es > 0 ) )
            break;
    }

    if( p_sys->b_udp_out )
    {
        /* Send the complete block */
        net_Write( p_demux, p_sys->fd, NULL, p_sys->buffer,
                   p_sys->i_ts_read * p_sys->i_packet_size );
    }

    return 1;
}

5. GatherData解析TS包

// 解析TS包
static bool GatherData( demux_t *p_demux, ts_pid_t *pid, block_t *p_bk )
{
    const uint8_t *p = p_bk->p_buffer;// 第一个字节(8位)为同步字节,此处没有获取
    const bool b_unit_start = p[1]&0x40;// payload_unit_start_indicator是否被置位
    const bool b_scrambled  = p[3]&0x80;// 是否加扰
    const bool b_adaptation = p[3]&0x20;// 是否有调整字段控制
    const bool b_payload    = p[3]&0x10;// 有无payload
    const int  i_cc         = p[3]&0x0f; /* continuity counter */// 获取连续的计数
    bool       b_discontinuity = false;  /* discontinuity */

    /* transport_scrambling_control is ignored */
    int         i_skip = 0;
    bool        i_ret  = false;
    /* For now, ignore additional error correction
     * TODO: handle Reed-Solomon 204,188 error correction */
    // 目前暂时忽略扩展错误纠正的情况
    p_bk->i_buffer = TS_PACKET_SIZE_188;

    // 至少发生了1位错误,且不可纠正
    if( p[1]&0x80 )
    {
        msg_Dbg( p_demux, "transport_error_indicator set (pid=%d)",
                 pid->i_pid );
        if( pid->es->p_data ) //&& pid->es->fmt.i_cat == VIDEO_ES )
            pid->es->p_data->i_flags |= BLOCK_FLAG_CORRUPTED;
    }

    if( p_demux->p_sys->csa )
    {
        vlc_mutex_lock( &p_demux->p_sys->csa_lock );
        csa_Decrypt( p_demux->p_sys->csa, p_bk->p_buffer, p_demux->p_sys->i_csa_pkt_size );
        vlc_mutex_unlock( &p_demux->p_sys->csa_lock );
    }

    if( !b_adaptation )
    {
        /* We don‘t have any adaptation_field, so payload starts
         * immediately after the 4 byte TS header */
        // 没有调整字段,所以有效净荷紧跟着4个字节的TS头
        i_skip = 4;
    }
    else
    {
        /* p[4] is adaptation_field_length minus one */
        // 如果有调整字段,那么TS头后的第一个字节就是调整字段的长度
        i_skip = 5 + p[4];
        if( p[4] > 0 )
        {
            /* discontinuity indicator found in stream */
            b_discontinuity = (p[5]&0x80) ? true : false;
            if( b_discontinuity && pid->es->p_data )
            {
                msg_Warn( p_demux, "discontinuity indicator (pid=%d) ",
                            pid->i_pid );
                /* pid->es->p_data->i_flags |= BLOCK_FLAG_DISCONTINUITY; */
            }
        }
    }

    /* Test continuity counter */
    /* continuous when (one of this):
        * diff == 1
        * diff == 0 and payload == 0
        * diff == 0 and duplicate packet (playload != 0) <- should we
        *   test the content ?
     */
    const int i_diff = ( i_cc - pid->i_cc )&0x0f;
    if( b_payload && i_diff == 1 )
    {
        pid->i_cc = ( pid->i_cc + 1 ) & 0xf;
    }
    else
    {
        if( pid->i_cc == 0xff )
        {
            msg_Warn( p_demux, "first packet for pid=%d cc=0x%x",
                      pid->i_pid, i_cc );
            pid->i_cc = i_cc;
        }
        else if( i_diff != 0 && !b_discontinuity )
        {
            msg_Warn( p_demux, "discontinuity received 0x%x instead of 0x%x (pid=%d)",
                      i_cc, ( pid->i_cc + 1 )&0x0f, pid->i_pid );

            pid->i_cc = i_cc;
            if( pid->es->p_data && pid->es->fmt.i_cat != VIDEO_ES )
            {
                /* Small video artifacts are usually better than
                 * dropping full frames */
                pid->es->p_data->i_flags |= BLOCK_FLAG_CORRUPTED;
            }
        }
    }

    // 校正PCR
    PCRHandle( p_demux, pid, p_bk );

    if( i_skip >= 188 || pid->es->id == NULL || p_demux->p_sys->b_udp_out )
    {
        block_Release( p_bk );
        return i_ret;
    }

    /* */
    if( !pid->b_scrambled != !b_scrambled )
    {
        msg_Warn( p_demux, "scrambled state changed on pid %d (%d->%d)",
                  pid->i_pid, pid->b_scrambled, b_scrambled );

        pid->b_scrambled = b_scrambled;

        for( int i = 0; i < pid->i_extra_es; i++ )
        {
            es_out_Control( p_demux->out, ES_OUT_SET_ES_SCRAMBLED_STATE,
                            pid->extra_es[i]->id, b_scrambled );
        }
        es_out_Control( p_demux->out, ES_OUT_SET_ES_SCRAMBLED_STATE,
                        pid->es->id, b_scrambled );
    }

    /* We have to gather it */
    p_bk->p_buffer += i_skip;
    p_bk->i_buffer -= i_skip;

    if( b_unit_start )
    {
        if( pid->es->data_type == TS_ES_DATA_TABLE_SECTION && p_bk->i_buffer > 0 )
        {
            int i_pointer_field = __MIN( p_bk->p_buffer[0], p_bk->i_buffer - 1 );
            block_t *p = block_Duplicate( p_bk );
            if( p )
            {
                p->i_buffer = i_pointer_field;
                p->p_buffer++;
                block_ChainLastAppend( &pid->es->pp_last, p );
            }
            p_bk->i_buffer -= 1 + i_pointer_field;
            p_bk->p_buffer += 1 + i_pointer_field;
        }
        if( pid->es->p_data )
        {
            ParseData( p_demux, pid );
            i_ret = true;
        }

        block_ChainLastAppend( &pid->es->pp_last, p_bk );
        if( pid->es->data_type == TS_ES_DATA_PES )
        {
            if( p_bk->i_buffer > 6 )
            {
                // 一个PES包含一帧图像,此时获取PES包的长度
                // 当收集到的数据大于等于i_data_size时,将接收到的一整帧图像放入block中
                // 等待解码线程解码
                // PES包头为24位(3个字节),流ID为8位(1个字节),故PES包长度从第5个字节开始
                pid->es->i_data_size = GetWBE( &p_bk->p_buffer[4] );
                if( pid->es->i_data_size > 0 )
                {
                    pid->es->i_data_size += 6;
                }
            }
        }
        else if( pid->es->data_type == TS_ES_DATA_TABLE_SECTION )
        {
            if( p_bk->i_buffer > 3 && p_bk->p_buffer[0] != 0xff )
            {
                pid->es->i_data_size = 3 + (((p_bk->p_buffer[1] & 0xf) << 8) | p_bk->p_buffer[2]);
            }
        }
        pid->es->i_data_gathered += p_bk->i_buffer;
        if( pid->es->i_data_size > 0 &&
            pid->es->i_data_gathered >= pid->es->i_data_size )
        {
            ParseData( p_demux, pid );
            i_ret = true;
        }
    }
    else
    {
        if( pid->es->p_data == NULL )
        {
            /* msg_Dbg( p_demux, "broken packet" ); */
            block_Release( p_bk );
        }
        else
        {
            block_ChainLastAppend( &pid->es->pp_last, p_bk );
            pid->es->i_data_gathered += p_bk->i_buffer;

            if( pid->es->i_data_size > 0 &&
                pid->es->i_data_gathered >= pid->es->i_data_size )
            {
                // 认为此时已经获取了一整帧数据,那么push到block的队列中等待解码线程解码
                ParseData( p_demux, pid );
                i_ret = true;
            }
        }
    }

    return i_ret;
}

  附:

  配置好的Windows版vlc工程下载:https://github.com/jiayayao/vlc_2.1.0-vs_2010,下载后使用vs2010可以直接编译运行,调试学习非常方便。

  

时间: 2024-10-10 23:17:02

TS流之代码分析的相关文章

OVS 内核KEY值提取及匹配流表代码分析

OVS内核KEY值提取及匹配流表代码分析 原文地址:https://www.cnblogs.com/wangjq19920210/p/10198991.html

TS流分析

一 从TS流开始 数字电视机顶盒接收到的是一段段的码流,我们称之为TS(Transport Stream,传输流),每个TS流都携带一些信息,如Video.Audio以及我们需要学习的PAT.PMT等信息.因此,我们首先需要了解TS流是什么,以及TS流是怎样形成.有着怎样的结构. (一) TS流.PS流.PES流和ES流都是什么? ES流(Elementary Stream):基本码流,不分段的音频.视频或其他信息的连续码流. PES流:把基本流ES分割成段,并加上相应头文件打包成形的打包基本码

wifi display代码 分析

转自:http://blog.csdn.net/lilian0118/article/details/23168531 这一章中我们来看Wifi Display连接过程的建立,包含P2P的部分和RTSP的部分,首先来大致看一下Wifi Display规范相关的东西. HIDC: Human Interface Device Class  (遵循HID标准的设备类)UIBC: User Input Back Channel  (UIBC分为两种,一种是Generic,包含鼠标.键盘等:另一种是HI

TS流解析 四

一 从TS流开始 数字电视机顶盒接收到的是一段段的码流,我们称之为TS(Transport Stream,传输流),每个TS流都携带一些信息,如Video.Audio以及我们需要学习的PAT.PMT等信息.因此,我们首先需要了解TS流是什么,以及TS流是怎样形成.有着怎样的结构. (一) TS流.PS流.PES流和ES流都是什么? ES流(Elementary Stream):基本码流,不分段的音频.视频或其他信息的连续码流. PES流:把基本流ES分割成段,并加上相应头文件打包成形的打包基本码

ffmpeg解析TS流

介绍:  MPEG的系统层编码为不同的应用场景设计了两种格式: TS(Transport Stream) 和PS(Program Stream), 它们两者之间不具有层级关系, 在逻辑上,它们两者都是由PES(Packetized Elementary Stream)包组成的, 所以可以很方便地实现相互转换. TS(Transport Stream): 是将具有一个或多个独立时间基的一个或多个节目(包括音频和视频)组成一个流, 组成同一个节目的基本流(如一个视频流,多个音频流)的PES包有一个共

TS流格式(转)

一 从TS流开始 数字电视机顶盒接收到的是一段段的码流,我们称之为TS(Transport Stream,传输流),每个TS流都携带一些信息,如Video.Audio以及我们需要学习的PAT.PMT等信息.因此,我们首先需要了解TS流是什么,以及TS流是怎样形成.有着怎样的结构. (一) TS流.PS流.PES流和ES流都是什么? ES流(Elementary Stream):基本码流,不分段的音频.视频或其他信息的连续码流. PES流:把基本流ES分割成段,并加上相应头文件打包成形的打包基本码

常用 Java 静态代码分析工具的分析与比较

转载自: http://www.oschina.net/question/129540_23043 简介: 本文首先介绍了静态代码分析的基本概念及主要技术,随后分别介绍了现有 4 种主流 Java 静态代码分析工具 (Checkstyle,FindBugs,PMD,Jtest),最后从功能.特性等方面对它们进行分析和比较,希望能够帮助 Java 软件开发人员了解静态代码分析工具,并选择合适的工具应用到软件开发中. 引言 在 Java 软件开发过程中,开发团队往往要花费大量的时间和精力发现并修改代

C++11的thread代码分析

本文分析的是llvm libc++的实现:http://libcxx.llvm.org/ class thread thread类直接包装了一个pthread_t,在linux下实际是unsigned long int. class thread { pthread_t __t_; id get_id() const _NOEXCEPT {return __t_;} } 用了一个std::unique_ptr来包装用户定义的线程函数: 创建线程用的是 template <class _Fp>

完整全面的Java资源库(包括构建、操作、代码分析、编译器、数据库、社区等等)

构建 这里搜集了用来构建应用程序的工具. Apache Maven:Maven使用声明进行构建并进行依赖管理,偏向于使用约定而不是配置进行构建.Maven优于Apache Ant.后者采用了一种过程化的方式进行配置,所以维护起来相当困难.Gradle:Gradle采用增量构建.Gradle通过Groovy编程而不是传统的XML声明进行配置.Gradle可以很好地配合Maven进行依赖管理,并且把Ant脚本当作头等公民.字节码操作 编程操作Java字节码的函数库. ASM:通用底层字节码操作及分析