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: