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