代码分析前,先要了解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. Eclipse3.2查找jre的问题

    前几天遇到一个问题,最开始电脑上使用的是解压的JDK(带jre),但是不能打开jar文件.所以从网上下载了一个jre1.8,然后问题来了,Eclipse打开就报错,弹出一个框,在eclipse的目录下 ...

  2. idea新建maven多模块spring boot项目

    1.新建一个maven多模块项目,比如这种结构: maven-demo |--demo-common |--demo-order |--demo-user 2.先新建一个maven项目,在maven项 ...

  3. 工厂模式的认识(GOF23)

    ---恢复内容开始--- 对于所有的设计模式来说,其本质是哪里变化封装哪里.寻找变化点,没有万能的模式,只有适合情况的应用 工厂模式从简单工厂开始演化 1.简单工厂的主要作用在于从源头开始封装实例化, ...

  4. 13 Reasons Why You Should Pay Attention to Mobile Web Performance

    Mobile is no longer on the sidelines. If you’re not already thinking mobile first, you should at lea ...

  5. js 数据监听--对象的变化

    class Observer { constructor(data) { this.data = data; this.filterObj(data); } static isObject(obj) ...

  6. grep用法详解:grep与正则表达式

    首先要记住的是: 正则表达式与通配符不一样,它们表示的含义并不相同!正则表达式只是一种表示法,只要工具支持这种表示法, 那么该工具就可以处理正则表达式的字符串.vim.grep.awk .sed 都支 ...

  7. Android Fragment重要函数

    Fragment的常用函数: 一.Fragment对象 1.void setArguments(Bundle args); 这个函数为Fragment提供构造参数(也就是数据),参数以Bundle类型 ...

  8. Array inversion case

    package basic.java; import java.util.Scanner; /* * 需求: * (1)键盘录入5个int类型的数据存储数组arr中 * (2)定义方法将arr数组中的 ...

  9. 4类Storage方案(AS开发实战第四章学习笔记)

    4.1 共享参数SharedPreferences SharedPreferences按照key-value对的方式把数据保存在配置文件中,该配置文件符合XML规范,文件路径是/data/data/应 ...

  10. 转:Java Socket编程

    对于Java Socket编程而言,有两个概念,一个是ServerSocket,一个是Socket.服务端和客户端之间通过Socket建立连接,之后它们就可以进行通信了.首先ServerSocket将 ...