MpegTS流解复用程序实现(解复用得到PES和ES)

MpegTS基础看这几篇博文:

MpegTS基础

MpegTS之TS,PES,ES结构分析

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

时间: 2024-08-12 15:42:53

MpegTS流解复用程序实现(解复用得到PES和ES)的相关文章

I/O复用模型详解

一.httpd工作模型 prefork:进程模型,两级结构,主进程master负责生成子进程,每个子进程负责响应一个请求 worker:线程模型,三级结构,主进程master负责生成子进程,每个子进程负责生成多个线程,每个线程相应一个请求 event:线程模型,三级结构,主进程master负责生成子进程,每个子进程响应多个请求 二.I/O模型名词概念 同步/异步:关注的是消息通信机制 同步:synchronous,调用者等待被调用者返回信息,才能继续执行异步:asynchronous,被调用者通

Object-C 入门 Xcode 环境详解 HelloWorld 程序

作者 : 韩曙亮 转载请注明出处 : http://blog.csdn.net/shulianghan/article/details/38424965 一. Xcode 环境安装 与 工程创建 1. 下载环境 相关资源下载 : -- IOS 相关资料下载页面 : https://developer.apple.com/devcenter/ios/index.action ; -- Xcode 下载页面 : https://developer.apple.com/xcode/downloads/

Linux开机启动程序详解

我们假设大家已经熟悉其它操作系统的引导过程,了解硬件的自检引导步骤,就只从Linux操作系统的引导加载程序(对个人电脑而言通常是LILO)开始,介绍Linux开机引导的步骤. 加载内核LILO启动之后,如果你选择了Linux作为准备引导的操作系统,第一个被加载的东西就是内核.请记住此时的计算机内存中还不存在任何操作系统,PC(因为它们天然的设计缺陷)也还没有办法存取机器上全部的内存.因此,内核就必须完整地加载到可用RAM的第一个兆字节之内.为了实现这个目的,内核是被压缩了的.这个文件的头部包含着

基于哈夫曼编码的压缩解压程序

这个程序是研一上学期的课程大作业.当时,跨专业的我只有一点 C 语言和数据结构基础,为此,我查阅了不少资料,再加上自己的思考和分析,实现后不断调试.测试和完善,耗时一周左右,在 2012/11/19 完成.虽然这是一个很小的程序,但却是我完成的第一个程序. 源码托管在 Github:点此打开链接 一.问题描述: 名称:基于哈夫曼编码的文件压缩解压 目的:利用哈夫曼编码压缩存储文件,节省空间 输入:任何格式的文件(压缩)或压缩文件(解压) 输出:压缩文件或解压后的原文件 功能:利用哈夫曼编码压缩解

VC++深入详解1--Win32程序

创建一个窗口,并在该窗口中响应键盘及鼠标消息,程序实现的步骤为: 1.WinMain函数的定义: 2.创建一个窗口: 3.进行消息循环: 4.编写窗口过程函数. VC++深入详解1--Win32程序,布布扣,bubuko.com

Linux如何实现开机启动程序详解

Linux开机启动程序详解我们假设大家已经熟悉其它操作系统的引导过程,了解硬件的自检引导步骤,就只从Linux操作系统的引导加载程序(对个人电脑而言通常是LILO)开始,介绍Linux开机引导的步骤. 加载内核LILO启动之后,如果你选择了Linux作为准备引导的操作系统,第一个被加载的东西就是内核.请记住此时的计算机内存中还不存在任何操作系统,PC(因为它们天然的设计缺陷)也还没有办法存取机器上全部的内存.因此,内核就必须完整地加载到可用RAM的第一个兆字节之内.为了实现这个目的,内核是被压缩

nstallShield制作打包程序详解(图)

InstallShield产品,是安装工具领域事实上的标准.InstallShield 软件是软件安装.配置软件包和升级解决方案领域内公认的标准.InstallShield已经成为安全安装软件的标准解决方案,涉及全球6.9万多个开发组织和5亿台电脑.公司提供广泛的产品和服务,为软件供应商.系统管理员以及最终用户提供成功的销售.管理和应用安装.本文将以InstallShield10.5 Premier Edition为例详述打包的过程.使用工程助手(Project assistant)设计    

Linux如何实现开机启动程序详解(转)

Linux开机启动程序详解我们假设大家已经熟悉其它操作系统的引导过程,了解硬件的自检引导步骤,就只从Linux操作系统的引导加载程序(对个人电脑而言通常是LILO)开始,介绍Linux开机引导的步骤. 加载内核LILO启动之后,如果你选择了Linux作为准备引导的操作系统,第一个被加载的东西就是内核.请记住此时的计算机内存中还不存在任何操作系统,PC(因为它们天然的设计缺陷)也还没有办法存取机器上全部的内存.因此,内核就必须完整地加载到可用RAM的第一个兆字节之内.为了实现这个目的,内核是被压缩

微信小程序-04-详解介绍.json 配置文件

致我自己:小程序开发不是简单一两天的事,一两天只能算是了解,有时候看多了会烦,感觉很熟悉了,其实只是对表面进行了解,对编程却知之甚少,小程序开发不是简单的改模板,一两天很多部分改模板可能都做不到,坚持! 微信小程序-04-详解介绍.json 配置文件 宝典官方文档: https://developers.weixin.qq.com/miniprogram/dev/framework/MINA.html 今天开始深度学习编程语法,岁大部分是拷贝官方文档,代码类都是我自己敲的,希望能自己敲一遍表格里