关键词:
代码分析前,先要了解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[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可以直接编译运行,调试学习非常方便。
小白学javad31》》》io流之缓冲流&转换流(代码片段)
【友情链接】➡➡▶IO流之File类&递归【友情链接】➡➡▶IO流之过滤器&字节流【友情链接】➡➡▶IO流之字符流&属性集(Properties集合)【友情链接】➡➡▶IO流之缓冲流&转换流【友情链接】➡➡▶IO流之序列... 查看详情
io流之bytearrayinput/outputstream(代码片段)
ByteArrayInputStream:是把字节数组当成源的输入流Stringstring="helloshanghai";ByteArrayInputStreambis=newByteArrayInputStream(string.getBytes());intdata=-1;while((data=bis.read())!=-1)System.out.print((char)data); 查看详情
io流之inputstream和outputstream(代码片段)
InputStream:定义了字节输入流的抽象类OutputStream:定义了字节输出流的抽象类;该类所有方法返回void值FileInputStream:FileOutputStream:packagecom.tanlei.InputOutputStream;importjava.io.File;importjava.io.FileInputStream;importjava.io.FileO 查看详情
io流之转换流(代码片段)
背景:有了字符输入输出流,读取的准确率和写入的效率确实提高不少,但是尴尬的是字符输出流只能针对系统默认的编码格式,那怎么把字符串用其他格式写入文件呢 InputStreamReader继承于Reader,是字节流通向字符流的桥梁... 查看详情
io流之datainputstream/dataoutputstream(代码片段)
DataInputStream继承于InputStream,允许应用程序以与机器无关方式从底层输入流中读取基本Java数据类型。 提供了readXXX():读取各种类型的数据 DataOutputStream继承于OutputStream,专门用于把基本java数据类型的数据写入... 查看详情
io流之bufferedreader/bufferedwriter(代码片段)
BufferedReader继承于Reader,除了之前提到的read ==》一个字符一个字符的读;read(char[]cbuf)==》多个字符(字符串)的读;还增加了自己独有的读取方法:readLine()==》用于读取一行文本。BufferedReader初始化时需要一个reader,所... 查看详情
io流之properties类(代码片段)
Properties类介绍Properties类表示了一个持久的属性集。Properties可保存在流中或从流中加载。属性列表中每个键及其对应值都是一个字符串。特点:1、Hashtable的子类,map集合中的方法都可以用。2、该集合没有泛型。键值都是字符串... 查看详情
用awk在命令行处理分析日志(代码片段)
日志分析时间戳格式化awk-F‘ ‘‘ts_high=$3/10000000;ts_low=$3%10000000;sub(/.*/,strftime("%Y-%m-%d%H:%M:%S",ts_high)"."ts_low,$3);sub(/.*/,$NF/10"us",$NF)1‘OFS=‘ ‘xxx.log这条日志中含有时间 查看详情
网络流之最大流(代码片段)
前一段阵子学了极小的一部分网络流,这里做一些总结,主要还是给自己看的a最大流:题干描述:给出一个网络图,以及其源点和汇点,求出其网络最大流。输入格式: 第一行包含四个正整数N、M、S、T,分别表示点的个数... 查看详情
javaio流之常用流总结(代码片段)
首先概述一下IO是什么 你想象并思考一下 当你编辑一个文本文件,忘记了ctrl+s而关闭了是不是很蛋疼,当你电脑上插入一个U盘把一个视频从U盘拷入你电脑硬盘里。这些数据都是在那些设备上? 我们可以把这种数据... 查看详情
io流之再战猜拳小游戏(代码片段)
文件IO流前言文件File类构造方法成员方法递归IO流概述IO流常用基类字节流的抽象基类1.InputStream2.OutputStream字符流的抽象基类字符流字符串中的编码解码问题1.InputStreamReader2.OutputStreamWriter字符缓冲流其他特殊流打印流对象序列化... 查看详情
io流之字符输入流,字符输出流(代码片段)
在我们日常开发中,我们经常会遇到要上传文件的操作,实现这个都是通过IO流去实现的,这次写的是普通字符输入流和普通输出流,由于效率有点低所以我们在日常开发中不会用到.所以这次的代码可能只是帮助到接触到java IO流... 查看详情
网络流之最大流和最小费用流(代码片段)
最大流邻接矩阵constintN=505;constintinf=0x3f3f3f3f;intmp[N][N],vis[N];intdfs(ints,intt,intf)if(s==t)returnf;vis[s]=1;for(inti=1;i<=n;i++)if(mp[s][i]>0&&!vis[i])intd=dfs(i,t,min(f,mp[s][i])); 查看详情
python编程基础控制流之ifelse(代码片段)
现实生活中会出现一些情况,当我们需要做出一些决定时,我们会根据这些决定来决定下一步应该做什么。类似的情况也出现在编程中,我们需要做出一些决定,并根据这些决定我们将执行下一个代码块。本文章... 查看详情
python编程基础控制流之ifelse(代码片段)
现实生活中会出现一些情况,当我们需要做出一些决定时,我们会根据这些决定来决定下一步应该做什么。类似的情况也出现在编程中,我们需要做出一些决定,并根据这些决定我们将执行下一个代码块。本文章... 查看详情
io流之fileinputstream终级版(代码片段)
FileInputStream终级版文件名称:text文件内容:publicstaticvoidMain(String[]args)System.out.println("Hello,World!");代码:importjava.io.FileInputStream;importjava.io.FileNotFoundException;importjava.io.IOException;/***一次读取多个字节*/publicclassFileInputStreamTest03... 查看详情
i/o流之filewriter
需求:1、编写程序,在main方法中,在当前目录下(项目目录)创建一个新目录newDir;2、获取当前目录(项目目录)中所有文件列表信息,把这些信息写到目录newDir中的info.txt文件中 代码:package课后练习;importjava.io.BufferedWriter... 查看详情
定位流之z-index属性(代码片段)
1.固定定位是脱离标准流的,不会占用标准流的空间2.和绝对定位有点像,也不区分行内块级元素3.类似于前面学的背景关联方式(让某个背景图片不随滚动而滚动)让某个元素不随着滚动条的滚动而滚动ie6不支持固定定位定义:... 查看详情