TS流解析-提取PSI信息等
代码加注释如下:
#include <iostream> #include <fstream> using namespace std; struct programs //封装节目信息的结构体 { int programID;//节目编号 int pmtPID;//所属PMT的pid int videoPID;//视频pid int audioPID1;//音频pid int audioPID2;//音频pid }myProg[20]; bool FindAndParsePAT(unsigned char *buffer,int pID,int curPack);//传入BUF和PID的值 bool FindAndParsePMT(unsigned char *buffer,int pID,int curPack); int program=0; int prog_count=0; void main() { unsigned char *buffer=new unsigned char[500]; int startPos=0;//第一个TS分组在流中的位置序号 int packageLen=0;//分组长度 int pmtCount=-1;//PMT表序号 int pID=0; int nullpack=0; //0.以二进制方式打开TS文件 ifstream myFile("test.ts",ios::binary|ios::in); //1.读入文件的前500个字节,找同步头、确定包长 myFile.read((char *)buffer,500); for(int i=0;i<500;i++) { //判断有无压缩 if(buffer[i]==0x47&&buffer[i+188]==0x47) { startPos=i;//第一个TS分组在流中的位置序号 packageLen=188;//分组长度 break; } else if(buffer[i]==0x47&&buffer[i+204]==0x47) { startPos=i; packageLen=204; break; } } //2.遍历流中的TS分组,查找PAT myFile.seekg(0,ios::end);//定位到文件尾部 int totalBytes=myFile.tellg();//获取尾部距离首部的偏移量,即TS文件字节总数totalBytes int packageCount=(totalBytes-startPos)/packageLen;//确定进行遍历的循环次数 即总TS包数 int curPack=0; while (curPack<packageCount)//遍历分组 { myFile.seekg(startPos+curPack*packageLen);//定位到第curPack个分组的首字节 myFile.read((char *)buffer,packageLen);//读出当前分组,保存到缓存buffer中,读一段分组长度188或204 pID=((buffer[1]&31)<<8)+buffer[2];//解析出当前分组的pid(13位=第2个字节的后5位+第3个字节全8位) if(pID==0x1fff) //检查空包数 { nullpack++; } if(FindAndParsePAT(buffer,pID,curPack))//执行程序:解析PAT 有效 break; //表明只要解析一个PAT就行 curPack++; } curPack=0; int a=0; while (curPack<packageCount) { myFile.seekg(startPos+curPack*packageLen);//定位到第curPack个分组的首字节 myFile.read((char *)buffer,packageLen);//读出当前分组,保存到缓存buffer中,读一段分组长度188或204 pID=((buffer[1]&31)<<8)+buffer[2];//解析出当前分组的pid(13位=第2个字节的后5位+第3个字节全8位) for(int k=0;k<prog_count;k++) { if(pID==myProg[k].pmtPID) //根据PAT表内容确定如何查PMT表 { cout<<"第"<<k+1<<"套节目:"<<endl; FindAndParsePMT(buffer,pID,curPack);//执行程序:解析PMT a++; } } if(a==prog_count) { break; } curPack++; } cout<<endl; cout<<"TS流相关信息:流中第一个TS分组起始位置"<<startPos<<","<<"TS分组长度"<<packageLen<<","<<"节目数"<<program<<","<<"空包数"<<nullpack<<endl; cout<<"所有节目相关PID信息"<<endl; delete[]buffer; myFile.close(); } //查找并解析PAT bool FindAndParsePAT(unsigned char *buffer,int pID,int curPack) { //3.根据pid值是否为0确认PAT分组,并从中读PMT的PID int adapLen=0;//TS分组适配字段长度 int offset=0;//实际净荷在当前分组中的偏移量 if(pID==0) { int payload_unit_start = (buffer[1]>>6) & 0X01;//净荷单元起始指示 int adaptation_field_control = (buffer[3]>>4) & 0X03;//自适应字段控制 //3.1 确定净荷起始位置(4字节固定首部+适配字段长度,adaption_field_control) if(adaptation_field_control==0x01)//无调整字段,仅净荷 { adapLen=0;//TS分组适配字段长度为0 } else if(adaptation_field_control==0x11)//有调整字段和净荷 { adapLen=buffer[4];//自适应字段长度 } else//无有效载荷,查找下一个分组 { curPack++; //continue; } offset=4+adapLen;//确定净荷在当前分组中的偏移量,头的字节长度 //3.2 确定PAT首部在净荷中的偏移量(如payload_unit_start_indicator为1, //则净荷首字节为偏移指针,指示PAT首部与其之间的偏移值) if(payload_unit_start==0x01)//如果净荷单元起始指示为1 { offset+=buffer[offset]+1;//pointer_field字段长为1字节 } //3.3 开始解析PAT表 int tableID=buffer[offset];//从净荷起始 if(tableID==0)//进入节目关联表PAT { int section_len=((buffer[offset+1]&0x0F)<<8)+buffer[offset+2];//code here:初始化 int transport_stream_idd=(buffer[offset+3]<<8)+buffer[offset+4];//code here:初始化 int current_next_indicator=buffer[offset+5]&0x01;//code here:初始化 if (current_next_indicator)//当前PAT有效 { prog_count=(section_len-9)/4-1; for(int i=0;i<prog_count;i++) { myProg[i].programID=(buffer[offset+12+i*4]<<8)+buffer[offset+12+i*4+1];//用2个字节表示节目号 cout<<"节目号"<<myProg[i].programID<<" "; myProg[i].pmtPID=(buffer[offset+14+i*4]&0x1F<<8)+buffer[offset+14+i*4+1];//用13位表示映射表 cout<<"映射表ID"<<myProg[i].pmtPID<<"\n"; program++; } //your code here 读出PAT包中存储的有关节目PMT的信息,确定节目数以及每路节目的PMT表pid,存储到myProg中 return true; } } } return false; } //查找并解析PMT bool FindAndParsePMT(unsigned char *buffer,int pID,int curPack) { //PMT 标志位 int payload_unit_start_indicator; //1比特标志位,用来指示传送流分组带有PES分组或PSI数据时的情况 int adaption_field_length; //自适应字段长度。 int pointer_field; // int section_length; //规定此字段之后此分段的字节数,包括CRC int section_number; int last_section_number; //8位字段,值总为0x00 int i=0; if (((buffer[3])<<2)/64==1) //判断adaption_field_control '01',无调整字段,仅含有有效负载 //2位字段。用于指示本传送流分组首部是否跟随有调整字段和/或有效负载。 /*00 为ISO/IEC未来使用保留 01 无调整字段,仅含有效负载 10 仅含调整字段,无有效负载 11 调整字段后为有效负载 */ { adaption_field_length=0; } else if (((buffer[3])<<2)/64==2) //判断adaption_field_control'10',仅含有调整字段,无有效负载 { return true; } else if (((buffer[3])<<2)/64==3) //判断adaption_field_control'11',调整字段后为有效负载 { adaption_field_length=buffer[4];//获得自适应字段长度 } i=adaption_field_length;//指针指向有效负载部分 /* 如果传输流分组带有一个 PSI部分的第一个字节,payload_unit_start_indicator值应被置'1',表明传输流分组的第一个字 节带有 pointer_field。如果传输流分组不带有一个 PSI部分的第一个字节payload_unit_start_indicator值应被置'0',表明在有 效负载中没有pointer_field。空分组的payload_unit_start_indicator应置'0'。 */ payload_unit_start_indicator=( (buffer[1])<<1 )/128; //1bit,1表示有pointer-field //0表示无 if(payload_unit_start_indicator) //判断payload_unit_start_indicator { pointer_field=buffer[i+4]; //8位,其值为在此字段之后到传输流分组有效负载的第一个字段的第一个字节之间的字节数。 } else { pointer_field=-1; } i=i+pointer_field+1+4;//确定净荷在当前分组中的偏移量,头的字节长度 if(buffer[i]==2) //判断table_id,PMT==0x02 { section_length=buffer[i+1]%16*256+buffer[i+2]; //section_length,12bit,表明在section_length字段之后此分段的字节数,包括CRC } section_number=buffer[i+6]; last_section_number=buffer[i+7]; for(int m=0;m<(section_length-57)/5;m++) //视频或音频流数目N的计算方法是N=(section_length--57)/5 //section_length后面占用9字节+一堆从tsr.exe中看到的字节共57,CRC占用4字节 { if(buffer[i+23+m*5]==0x02) { cout<<"视频PID:"<<buffer[i+24+m*5]%32*256+buffer[i+25+m*5]<<"\t"; } if(buffer[i+23+m*(5+16)]==0x04) { cout<<"音频PID:"<<buffer[i+24+m*(5+16)]%32*256+buffer[i+25+m*(5+16)]<<"\t"; } } cout<<endl; //your code here return true; }