代码分析前,先要了解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. Tinymce 编辑器添加自定义图片管理插件

    在使用Tinymce的过程中需要用到图片上传功能,而提供的上传插件在上传文件后是给了一个连接地址,就想用户需要什么图片,不能用最直观的方式表现出来么! 虽然官网上也有一个文件管理的插件moxieman ...

  2. BZOJ2187:fraction

    Sol 分情况讨论 \(\lfloor\frac{a}{b}\rfloor+1\le \lceil\frac{c}{d}\rceil-1\) 直接取 \(q=1,p=\lfloor\frac{a}{b ...

  3. position布局影响点击事件以及冒泡获取事件目标

    在编写功能时总是会出现很多意想不到的问题,现在就讲讲我遇到的两个问题,通过举一个相似的例子来解说. <1> 元素互相独立,不存在包含于被包含 选择城市的按钮,为它绑定点击事件,点击后就弹出 ...

  4. SQL Server ->> 高可用与灾难恢复(HADR)技术之 -- Transaction Replication(事务复制)

    复制类型: 1)事务型复制:通过复制事务日志到订阅点重做的方式,属于增量型复制: 2)合并型复制:通过触发器和元数据表追踪表数据改变,同样属于增量型复制: 3)快照型复制:通过创建数据库快照,并把快照 ...

  5. [翻译] ZFTokenField

    ZFTokenField 本人视频教程系类   iOS中CALayer的使用 效果图: iOS custom view that let you add token view inside like ...

  6. C# 托管非托管资源释放

    1.C#几乎所有对象都为托管对象,不同点是有的对象封装了非托管资源. 2.C#大部分对象在进行垃圾回收时都可以回收,包括非托管资源,因为非托管资源都已经通过C#类进行了封装,会将非托管资源的释放放在析 ...

  7. android(2)——Structure of an Android Application

    1 structure of an android application An Android application is primarily made up of three pieces: t ...

  8. java抽象类,接口与异常

    1.抽象类: 定义;包含一个抽象方法的类称称为抽象类,抽象类在class前使用adstract关键词修饰. 抽象方法;只声明未实现的方法称为抽象方法,使用adstract关键字声明该方法. 抽象类定义 ...

  9. August 09th 2017 Week 32nd Wednesday

    Find hope from despair, life will become brilliant. 从绝望中寻找希望,人生终将辉煌. Have you ever seen the movie Ba ...

  10. January 09 2017 Week 2nd Monday

    Patience is bitter, but its fruit is sweet. 忍耐是痛苦的,但它的果实是甜蜜的. Some patience may be just fruitless, o ...