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

  VLC解析TS流是通过libts库来分离的,libts库使用libdvbpsi库来解TS表。VLC使用模块加载机制来加载libts库,具体调用的文件是ts.c.

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[]; msg_Dbg( p_demux, "PATCallBack called" ); if( ( pat->psi->i_pat_version != - &&
( !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 == )
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 = ; 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-];
    
    ......
// 注册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 == )
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 <= ; /* We read at most 100 TS packet or until a frame is completed */
for( int i_pkt = ; i_pkt < p_sys->i_ts_read; i_pkt++ )
{
bool b_frame = false;
block_t *p_pkt;
if( !(p_pkt = ReadTSPacket( p_demux )) )
{
return ;
} 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 == || ( 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 = ; 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 > ) )
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 ;
}

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[]&0x40;// payload_unit_start_indicator是否被置位
const bool b_scrambled = p[]&0x80;// 是否加扰
const bool b_adaptation = p[]&0x20;// 是否有调整字段控制
const bool b_payload = p[]&0x10;// 有无payload
const int i_cc = p[]&0x0f; /* continuity counter */// 获取连续的计数
bool b_discontinuity = false; /* discontinuity */ /* transport_scrambling_control is ignored */
int i_skip = ;
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[]&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 = ;
}
else
{
/* p[4] is adaptation_field_length minus one */
// 如果有调整字段,那么TS头后的第一个字节就是调整字段的长度
i_skip = + p[];
if( p[] > )
{
/* discontinuity indicator found in stream */
b_discontinuity = (p[]&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 == )
{
pid->i_cc = ( pid->i_cc + ) & 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 != && !b_discontinuity )
{
msg_Warn( p_demux, "discontinuity received 0x%x instead of 0x%x (pid=%d)",
i_cc, ( pid->i_cc + )&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 >= || 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 = ; 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 > )
{
int i_pointer_field = __MIN( p_bk->p_buffer[], p_bk->i_buffer - );
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 -= + i_pointer_field;
p_bk->p_buffer += + 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 > )
{
// 一个PES包含一帧图像,此时获取PES包的长度
// 当收集到的数据大于等于i_data_size时,将接收到的一整帧图像放入block中,
// 等待解码线程解码
// PES包头为24位(3个字节),流ID为8位(1个字节),故PES包长度从第5个字节开始
pid->es->i_data_size = GetWBE( &p_bk->p_buffer[] );
if( pid->es->i_data_size > )
{
pid->es->i_data_size += ;
}
}
}
else if( pid->es->data_type == TS_ES_DATA_TABLE_SECTION )
{
if( p_bk->i_buffer > && p_bk->p_buffer[] != 0xff )
{
pid->es->i_data_size = + (((p_bk->p_buffer[] & 0xf) << ) | p_bk->p_buffer[]);
}
}
pid->es->i_data_gathered += p_bk->i_buffer;
if( pid->es->i_data_size > &&
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 > &&
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可以直接编译运行,调试学习非常方便。

  

vlc源码分析(四) 调用libts接收TS流的更多相关文章

  1. MPTCP 源码分析(四) 发送和接收数据

    简述:      MPTCP在发送数据方面和TCP的区别是可以从多条路径中选择一条 路径来发送数据.MPTCP在接收数据方面与TCP的区别是子路径对无序包 进行重排后,MPTCP的mpcb需要多所有子 ...

  2. vlc源码分析(七) 调试学习HLS协议

    HTTP Live Streaming(HLS)是苹果公司提出来的流媒体传输协议.与RTP协议不同的是,HLS可以穿透某些允许HTTP协议通过的防火墙. 一.HLS播放模式 (1) 点播模式(Vide ...

  3. vlc源码分析(三) 调用live555接收RTSP数据

    首先了解RTSP/RTP/RTCP相关概念,尤其是了解RTP协议:RTP与RTCP协议介绍(转载). vlc使用模块加载机制调用live555,调用live555的文件是live555.cpp. 一. ...

  4. Dubbo 源码分析 - 服务调用过程

    注: 本系列文章已捐赠给 Dubbo 社区,你也可以在 Dubbo 官方文档中阅读本系列文章. 1. 简介 在前面的文章中,我们分析了 Dubbo SPI.服务导出与引入.以及集群容错方面的代码.经过 ...

  5. 使用react全家桶制作博客后台管理系统 网站PWA升级 移动端常见问题处理 循序渐进学.Net Core Web Api开发系列【4】:前端访问WebApi [Abp 源码分析]四、模块配置 [Abp 源码分析]三、依赖注入

    使用react全家桶制作博客后台管理系统   前面的话 笔者在做一个完整的博客上线项目,包括前台.后台.后端接口和服务器配置.本文将详细介绍使用react全家桶制作的博客后台管理系统 概述 该项目是基 ...

  6. ABP源码分析四:Configuration

    核心模块的配置 Configuration是ABP中设计比较巧妙的地方.其通过AbpStartupConfiguration,Castle的依赖注入,Dictionary对象和扩展方法很巧妙的实现了配 ...

  7. ABP源码分析四十七:ABP中的异常处理

    ABP 中异常处理的思路是很清晰的.一共五种类型的异常类. AbpInitializationException用于封装ABP初始化过程中出现的异常,只要抛出AbpInitializationExce ...

  8. docker 源码分析 四(基于1.8.2版本),Docker镜像的获取和存储

    前段时间一直忙些其他事情,docker源码分析的事情耽搁了,今天接着写,上一章了解了docker client 和 docker daemon(会启动一个http server)是C/S的结构,cli ...

  9. VLC源码分析知识总结

    1.  关于#和## 1.1).在C语言的宏中,#的功能是将其后面的宏参数进行字符串化操作(Stringfication),简单说就是在对它所引用的宏变量通过替换后在其左右各加上一个双引号. 比如在早 ...

随机推荐

  1. html5 css选择器 井号, 句点的区别

    一.理解CSS的样式组成CSS里的样式表是有规则组成的,每条规则有三个部分组成:1.选择器(如下面例子中的:"body"),告诉浏览器文档的哪个部分受规则影响:2.属性(如实例中的 ...

  2. Sql 列转行字符串

    select OrderID,ProdDetailID from A 表A : OrderID,ProdDetailID 1             6 1             7 1       ...

  3. selenium元素定位方法

    一.如何找到页面元素 Webdriver的findElement方法可以用来找到页面的某个元素,最常用的方法是用id和name查找.下面介绍几种比较常用的方法. 1.1By ID 假设页面写成这样:i ...

  4. MySQL 练习题2

    CREATE TABLE `dept` ( `did` ) NOT NULL AUTO_INCREMENT, `dname` ) DEFAULT NULL, `address` ) DEFAULT N ...

  5. 使用ifstream和getline读取文件内容[c++] ZZ

      假设有一个叫 data.txt 的文件, 它包含以下内容: Fry: One Jillion dollars.[Everyone gasps.]Auctioneer: Sir, that's no ...

  6. 动态展开tableView的cell[1]

    动态展开tableView的cell[1] 源码地址:https://github.com/xerxes235/HVTableView 虽然作者写的demo很好看,可是,你很难理解他是怎么玩的-_-! ...

  7. Linux入门-3 Linux磁盘及文件系统管理

    1. 磁盘基本概念 1.1 磁盘结构:盘片(单碟vs多碟).磁头(读写数据) 1.2 磁盘在Linux中的表示 1.3 分区概念 2 使用fdisk进行磁盘管理 3 Linux文件系统 mke2fs ...

  8. Java学习---JFreeChart动态图表

    JFreeChart是Java中开源的制图组件,主要用于生成各种动态图表.在Java的图形报表技术中,JFreeChart组件提供了方便.快捷.灵活的制图方法.作为一个功能强大的图形报表组件,JFre ...

  9. Linux bash内置命令集

    man cd  -->查询不到,所以会提示bash的内置命令 . alias bg bind break builtin caller cd command compgen complete c ...

  10. ASP.NET Core 上传多文件 超简单教程

    示例源码下载地址 https://qcloud.coding.net/api/project/3915794/files/4463836/download 项目地址 https://dev.tence ...