MpegTS基础看这几篇博文:
TS流复用和解复用是一个相逆的过程。TS解复用得到的是音视频的PES裸流。一般来讲,每个TS包的长度是188个字节,也有一种204个字节的,就是在每个包后面加上16个字节的RS冗余校验信息。在这里分析188个字节的情况,其余的都类似了。
从文件中循环读取188个字节的包,然后对包进行逐字节分析,分析流程如下:
TS包的标志是首字节以0x47开头
如下图是一个ts包:
按位解析,得到pid,flag,错误标志,负载类型,PSI, PMI等信息。
源码分析如下:该源码是从一开源工具tsDemux截取,所有的ts流的解析过程无非也就是整么一个过程了。
<span style="font-family:SimHei;font-size:18px;">int ts::demuxer::demux_ts_packet(const char* ptr)
{
u_int32_t timecode = 0;
const char* end_ptr = ptr + 188;
if (ptr[0] != 0x47) // ts sync byte
return -1;
u_int16_t pid = to_int(ptr + 1);//get 2, 3 Byte
u_int8_t flags = to_byte(ptr + 3);
bool transport_error = pid & 0x8000;
/*ts带有PES包时,1:负载是PES,0:负载不是PES
ts带有PSI数据时,1:带有PSI部分的第一个字节 0:不带有PSI部分的第一个字节
*/
bool payload_unit_start_indicator = pid & 0x4000;
bool adaptation_field_exist = flags & 0x20;
bool payload_data_exist = flags & 0x10;
u_int8_t continuity_counter = flags & 0x0f;
//get PID
pid &= 0x1fff;
if (transport_error)
return -2;
//empty payload
if (pid == 0x1fff || !payload_data_exist)
return 0;
ptr += 4;
// skip adaptation field
if (adaptation_field_exist)
{
ptr += to_byte(ptr) + 1;
if (ptr >= end_ptr)
return -3;
}
stream& s = streams[pid];
if (!pid || (s.channel != 0xffff && s.type == 0xff))
{
// PSI
if (payload_unit_start_indicator)
{
// begin of PSI table
ptr++;
if (ptr >= end_ptr)
return -4;
if (*ptr != 0x00 && *ptr != 0x02)
return 0;
if (end_ptr - ptr < 3)
return -5;
u_int16_t l = to_int(ptr + 1);
if (l & 0x3000 != 0x3000)
return -6;
l &= 0x0fff;
ptr += 3;
int len = end_ptr - ptr;
if (l > len)
{
if (l > ts::table::max_buf_len)
return -7;
s.psi.reset();
memcpy(s.psi.buf, ptr, len);
s.psi.offset += len;
s.psi.len = l;
return 0;
}
else
end_ptr = ptr + l;
}
else
{
// next part of PSI
if (!s.psi.offset)
return -8;
int len = end_ptr - ptr;
if (len > ts::table::max_buf_len - s.psi.offset)
return -9;
memcpy(s.psi.buf + s.psi.offset, ptr, len);
s.psi.offset += len;
if (s.psi.offset < s.psi.len)
return 0;
else
{
ptr = s.psi.buf;
end_ptr = ptr + s.psi.len;
}
}
if (!pid)
{
// PAT
ptr += 5;
if (ptr >= end_ptr)
return -10;
int len = end_ptr - ptr - 4;
if (len < 0 || len % 4)
return -11;
int n = len / 4;
for (int i = 0; i < n; i++, ptr += 4)
{
u_int16_t channel = to_int(ptr);
u_int16_t pid = to_int(ptr + 2);
if (pid & 0xe000 != 0xe000)
return -12;
pid &= 0x1fff;
if (!demuxer::channel || demuxer::channel == channel)
{
stream& ss = streams[pid];
ss.channel = channel;
ss.type = 0xff;
}
}
}
else
{
// PMT
ptr += 7;
if (ptr >= end_ptr)
return -13;
u_int16_t info_len = to_int(ptr) & 0x0fff;
ptr += info_len + 2;
end_ptr -= 4;
if (ptr >= end_ptr)
return -14;
while (ptr < end_ptr)
{
if (end_ptr - ptr < 5)
return -15;
u_int8_t type = to_byte(ptr);
u_int16_t pid = to_int(ptr + 1);
if (pid & 0xe000 != 0xe000)
return -16;
pid &= 0x1fff;
info_len = to_int(ptr + 3) & 0x0fff;
ptr += 5 + info_len;
// ignore unknown streams
if (validate_type(type))
{
stream& ss = streams[pid];
if (ss.channel != s.channel || ss.type != type)
{
ss.channel = s.channel;
ss.type = type;
ss.id = ++s.id;
if (!parse_only && !ss.file.is_opened())
{
if (dst.length())
ss.file.open(file::out, false, "%s%c%strack_%i.%s", dst.c_str(), os_slash, prefix.c_str(), pid, get_stream_ext(get_stream_type(ss.type)));
else
ss.file.open(file::out, false, "%strack_%i.%s", prefix.c_str(), pid, get_stream_ext(get_stream_type(ss.type)));
if (es_parse)
ss.file.open(file::out, true, "%ses_strack_%i.%s", prefix.c_str(), pid, "es");
}
}
}
}
if (ptr != end_ptr)
return -18;
}
}
else
{
if (s.type != 0xff)
{
// PES
if (payload_unit_start_indicator)//等于true代表一个PES包的开始
{
s.psi.reset();
s.psi.len = 9;
}
while (s.psi.offset<s.psi.len)
{
int len = end_ptr - ptr;
if (len <= 0)
return 0;
int n = s.psi.len - s.psi.offset;
if (len>n)
len = n;
memcpy(s.psi.buf + s.psi.offset, ptr, len);
s.psi.offset += len;
ptr += len;
if (s.psi.len == 9)
s.psi.len += to_byte(s.psi.buf + 8);
}
if (s.psi.len)
{
if (memcmp(s.psi.buf, "\x00\x00\x01", 3))
return -19;
s.stream_id = to_byte(s.psi.buf + 3);
u_int8_t flags = to_byte(s.psi.buf + 7);
s.frame_num++;
switch (flags & 0xc0)
{
case 0x80: // PTS only 音频包PTS和DTS相同,所以只有PTS
{
u_int64_t pts = decode_pts(s.psi.buf + 9);
if (dump == 2)
printf("%.4x: %llu\n", pid, pts);
else if (dump == 3)
printf("%.4x: track=%.4x.%.2i, type=%.2x, stream=%.2x, pts=%llums\n", pid, s.channel, s.id, s.type, s.stream_id, pts / 90);
if (s.dts > 0 && pts > s.dts)
s.frame_length = pts - s.dts;
s.dts = pts;
if (pts > s.last_pts)
s.last_pts = pts;
if (!s.first_pts)
s.first_pts = pts;
}
break;
case 0xc0: // PTS,DTS 视频包含有PTS和DTS
{
u_int64_t pts = decode_pts(s.psi.buf + 9);
u_int64_t dts = decode_pts(s.psi.buf + 14);
if (dump == 2)
printf("%.4x: %llu %llu\n", pid, pts, dts);
else if (dump == 3)
printf("%.4x: track=%.4x.%.2i, type=%.2x, stream=%.2x, pts=%llums, dts=%llums\n", pid, s.channel, s.id, s.type, s.stream_id, pts / 90, dts / 90);
if (s.dts > 0 && dts > s.dts)
s.frame_length = dts - s.dts;
s.dts = dts;
if (pts > s.last_pts)
s.last_pts = pts;
if (!s.first_dts)
s.first_dts = dts;
}
break;
}
if (pes_output && s.file.is_opened())
{
s.file.write(s.psi.buf, s.psi.len, false);//将PES包头信息存入文件
}
s.psi.reset();
}
if (s.frame_num)
{
int len = end_ptr - ptr;
if (s.file.is_opened())
{
s.file.write(ptr, len, true);//此处获取的是ES包
}
}
}
}
return 0;
}</span>
该代码是根据TSDemux工程修改,源项目只能解复用得到PES,在此基础上修改能同时获取音视频的PES,ES共4个文件。需要详细学习TS的同学可以研究下。
源代码已经上传到CSDN:
http://download.csdn.net/detail/rootusers/8426227
原文:http://blog.csdn.net/rootusers/article/details/43528261