最简单的 UDP-RTP 协议解析程序

最简单的 UDP-RTP 协议解析程序

  • 最简单的 UDP-RTP 协议解析程序
    • 原理
    • 源程序
    • 结果
    • 下载链接
    • 参考

最简单的 UDP-RTP 协议解析程序

本文介绍网络协议数据的处理程序。网络协议数据在视频播放器中的位置如下所示。

在这里插入图片描述

本文中的程序是一个 UDP/RTP 协议流媒体数据解析器。该程序可以分析 UDP 协议中的 RTP 包头中的内容,以及 RTP 负载中 MPEG-TS 封装格式的信息。通过修改该程序可以实现不同的 UDP/RTP 协议数据处理功能。

原理

MPEG-TS封装格式数据打包为RTP/UDP协议然后发送出去的流程如下图所示。图中首先每7个MPEG-TS Packet打包为一个RTP,然后每个RTP再打包为一个UDP。其中打包RTP的方法就是在MPEG-TS数据前面加上RTP Header,而打包RTP的方法就是在RTP数据前面加上UDP Header。

在这里插入图片描述

有关 MPEG-TS、RTP、UDP 的知识不再详细介绍,可以参考相关的文档了解其中的细节信息。本文记录的程序是一个收取流媒体的程序,因此本文程序的流程和上述发送 MPEG-TS 的流程正好是相反的。该程序可以通过 Socket 编程收取 UDP 包,解析其中的 RTP 包的信息,然后再解析 RTP 包中 MPEG-TS Packet 的信息。

源程序

// Simplest RTP Parser.cpp : 定义控制台应用程序的入口点。
//

/**
* 最简单的 UDP-RTP 协议解析程序
* Simplest RTP Parser
*
* 原程序:
* 雷霄骅 Lei Xiaohua
* leixiaohua1020@126.com
* 中国传媒大学/数字电视技术
* Communication University of China / Digital TV Technology
* http://blog.csdn.net/leixiaohua1020
*
* 修改:
* 刘文晨 Liu Wenchen
* 812288728@qq.com
* 电子科技大学/电子信息
* University of Electronic Science and Technology of China / Electronic and Information Science
* https://blog.csdn.net/ProgramNovice
*
* 本项目是一个 FLV 封装格式解析程序,可以分析 UDP/RTP/MPEG-TS 数据包。
*
* This project is the simplest UDP-RTP protocol parser,
* can analyze UDP/RTP/MPEG-TS packets.
*
*/

#include "stdafx.h"

#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>

#pragma comment(lib, "ws2_32.lib")
#pragma warning(disable:4996) // 解决 fopen 不安全报错
#pragma pack(1)

/*
* [memo] FFmpeg stream Command:
* ffmpeg -re -i sintel.ts -f mpegts udp://127.0.0.1:8880
* ffmpeg -re -i sintel.ts -f rtp_mpegts udp://127.0.0.1:8880
*/

typedef struct RTP_FIXED_HEADER
{
	/* byte 0 */
	unsigned char csrc_count : 4; // CSRC 计数器,指示 CSRC 标识符的个数
	unsigned char extension : 1; // 如果 X=1,拓展头部会放在 CSRC 之后,携带一些附加信息
	unsigned char padding : 1; // 如果 P=1,则该 RTP 包的尾部就附加一个或多个额外的八位组,它们不是有效载荷的一部分
	unsigned char version : 2; // 版本号,现在使用的都是第 2 个版本,所以该字段固定为 2
	/* byte 1 */
	unsigned char payload_type : 7; // 标识了 RTP 载荷的类型
	unsigned char marker : 1; // 该位的解释由配置文档决定,一般情况下用于标识边界(对于视频,标记一帧的结束;对于音频,标记会话的开始)。

	/* bytes 2, 3 */
	unsigned short sequence_number; // RTP 包序列号

	/* bytes 4-7 */
	unsigned  long timestamp; // 时间戳,反映了该 RTP 报文数据的第一个八位组的采样时刻

	/* bytes 8-11 */
	unsigned long ssrc; // 同步源标识符,标识 RTP 包流的来源,不同源的数据流之间用 SSRC 字段区分

} RTP_FIXED_HEADER; // RTP Header 占 12 字节


typedef struct MPEGTS_FIXED_HEADER
{
	/* byte 0 */
	unsigned sync_byte : 8; // 同步字节,值为 0x47

	/* byte 1, 2 */
	unsigned transport_error_indicator : 1; // 传输错误指示位,置 1 时,表示传送包中至少有一个不可纠正的错误位
	unsigned payload_unit_start_indicator : 1; // 负载单元起始指标位,表示该 TS 包是 PES 包的第一个负载单元
	unsigned transport_priority : 1; // 传输优先级,表明该包比同个 PID 的但未置位的 TS 包有更高的优先级
	unsigned PID : 13; // 该 TS 包的 ID 号,如果净荷是 PAT 包,则 PID 固定为 0x00

	/* byte 3 */
	unsigned transport_scrambling_control : 2; // 传输加扰控制位
	unsigned adaptation_field_control : 2; // 自适应调整域控制位,置位则表明该 TS 包存在自适应调整字段
	unsigned continuity_counter : 4;// 连续计数器,随着具有相同 PID 的 TS 包的增加而增加,达到最大时恢复为 0
	/* 如果两个连续相同 PID 的 TS 包具有相同的计数,则表明这两个包是一样的,只取一个解析即可。 */
} MPEGTS_FIXED_HEADER; // MPEG-TS 包头占 4 字节


int simplest_udp_parser(int port)
{
	WSADATA wsaData;
	WORD sockVersion = MAKEWORD(2, 2);
	int cnt = 0;

	// FILE *myout = fopen("output_log.txt", "wb+");
	FILE *myout = stdout;
	FILE *fp1 = fopen("output_dump.ts", "wb+");

	// 首先调用 WSAStartup 函数完成对 Winsock 服务的初始化
	if (WSAStartup(sockVersion, &wsaData) != 0)
	{
		return INVALID_SOCKET;
	}
	/* 调用 socket 函数,它有三个参数:
	1. af:程序使用的通信协议族,对于 TCP/IP,值为 AF_INET
	2. type:要创建的套接字类型,流套接字类型为 SOCK_STREAM(TCP),数据报套接字类型为 SOCK_DGRAM(UDP),还有 SOCK_RAW(原始 socket)
	3. protocol:程序使用的通信协议,若置 0,系统会自动决定传输层协议
	*/
	SOCKET serSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (serSocket == INVALID_SOCKET)
	{
		printf("socket error!");
		return 0;
	}

	sockaddr_in serAddr;
	// sin_family 指明了协议族/域,通常 AF_INET、AF_INET6、AF_LOCAL 等
	serAddr.sin_family = AF_INET;
	// sin_port 即端口号,使用网络字节序,即大端模式
	serAddr.sin_port = htons(port); // htons(port) 将 16 位数从主机字节序(小端字节序)转换成网络字节序(大端字节序)
	// sin_addr 存储 IP 地址,使用 in_addr 这个数据结构,也使用网络字节序
	// 这里的 INADDR_ANY 就是指定地址为 0.0.0.0 的地址,这个地址事实上表示不确定地址,一般在各个系统中均定义为全 0
	serAddr.sin_addr.S_un.S_addr = INADDR_ANY;

	// bind 函数可以将一组固定的地址绑定到 sockfd 上
	if (bind(serSocket, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
	{
		printf("bind error!");
		closesocket(serSocket);
		return 0;
	}

	sockaddr_in remoteAddr;
	int nAddrLen = sizeof(remoteAddr);

	// How to parse?
	int parse_rtp = 1;
	int parse_mpegts = 1;

	printf("Listening on port %d.\n", port);

	char recvData[10000];
	while (1)
	{
		// 从 socket 接收缓冲区拷贝数据到 recvData
		int pktsize = recvfrom(serSocket, recvData, 10000, 0, (sockaddr *)&remoteAddr, &nAddrLen);
		if (pktsize > 0)
		{
			// printf("Addr:%s\r\n", inet_ntoa(remoteAddr.sin_addr));
			// printf("packet size:%d\r\n", pktsize);

			// Parse RTP
			if (parse_rtp != 0)
			{
				char payload_str[10] = { 0 };
				RTP_FIXED_HEADER rtp_header;
				int rtp_header_size = sizeof(RTP_FIXED_HEADER);
				// RTP Header
				memcpy((void *)&rtp_header, recvData, rtp_header_size);

				// RFC3551
				char payloadType = rtp_header.payload_type;
				switch (payloadType)
				{
				case 0: sprintf(payload_str, "PCMU"); break;
				case 1:
				case 2: sprintf(payload_str, "reserved"); break;
				case 3: sprintf(payload_str, "GSM"); break;
				case 4: sprintf(payload_str, "G723"); break;
				case 5:
				case 6: sprintf(payload_str, "DVI4"); break;
				case 7: sprintf(payload_str, "LPC"); break;
				case 8: sprintf(payload_str, "PCMA"); break;
				case 9: sprintf(payload_str, "G722"); break;
				case 10:
				case 11: sprintf(payload_str, "L16"); break;
				case 12: sprintf(payload_str, "QCELP"); break;
				case 13: sprintf(payload_str, "CN"); break;
				case 14: sprintf(payload_str, "MPA"); break;
				case 15: sprintf(payload_str, "G728"); break;
				case 16:
				case 17: sprintf(payload_str, "DVI4"); break;
				case 18: sprintf(payload_str, "G729"); break;
				case 19: sprintf(payload_str, "reserved"); break;
				case 25: sprintf(payload_str, "CelB"); break;
				case 26: sprintf(payload_str, "JPEG"); break;
				case 28: sprintf(payload_str, "nv"); break;
				case 31: sprintf(payload_str, "H.261"); break;
				case 32: sprintf(payload_str, "MPV"); break;
				case 33: sprintf(payload_str, "MP2T"); break;
				case 34: sprintf(payload_str, "H.263"); break;
				case 72:
				case 73:
				case 74:
				case 75:
				case 76: sprintf(payload_str, "reserved"); break;
				case 96: sprintf(payload_str, "H.264"); break;
				default: sprintf(payload_str, "other"); break;
				}

				unsigned int timestamp = ntohl(rtp_header.timestamp);
				unsigned int seq_no = ntohs(rtp_header.sequence_number);

				fprintf(myout, "[RTP Pkt] %5d| %5s| %10u| %5d| %5d|\n", cnt, payload_str, timestamp, seq_no, pktsize);

				// RTP Data
				char *rtp_data = recvData + rtp_header_size;
				int rtp_data_size = pktsize - rtp_header_size;
				fwrite(rtp_data, rtp_data_size, 1, fp1);

				// Parse MPEGTS
				if (parse_mpegts != 0 && payloadType == 33)
				{
					MPEGTS_FIXED_HEADER mpegts_header;
					// 一个 TS 包长度固定 188 字节
					for (int i = 0; i < rtp_data_size; i = i + 188)
					{
						if (rtp_data[i] != 0x47) // 判断同步字节,固定为 0x47
							break;
						// MPEGTS Header
						// memcpy((void *)&mpegts_header, rtp_data + i, sizeof(MPEGTS_FIXED_HEADER));
						fprintf(myout, "   [MPEGTS Pkt]\n");
					}
				}
			}
			else
			{
				fprintf(myout, "[UDP Pkt] %5d| %5d|\n", cnt, pktsize);
				fwrite(recvData, pktsize, 1, fp1);
			}

			cnt++;
		}
	}

	closesocket(serSocket);
	WSACleanup();
	fclose(fp1);

	return 0;
}

int main()
{
	simplest_udp_parser(8880);

	system("pause");
	return 0;
}

结果

本程序输入为本机的一个端口号,输出为UDP/RTP/MPEG-TS的解析结果。程序开始运行后,可以使用推流软件向本机的udp://127.0.0.1:8880地址进行推流。例如可以使用VLC Media Player的“打开媒体”对话框中的“串流”功能(位于“播放”按钮旁边的小三角按钮的菜单中)。在该功能的对话框中添加一个“RTP / MPEG Transport Stream”的新目标。

在这里插入图片描述

在这里插入图片描述

也可以使用 FFmpeg 命令推流首先经过RTP封装,然后经过UDP封装的MPEG-TS,端口为 8880:

ffmpeg -re -i sintel.ts -f rtp_mpegts udp://127.0.0.1:8880

程序输出:

在这里插入图片描述

下载链接

CSDN:Simplest RTP Parser.zip

GitHub:UestcXiye/Simplest-RTP-Parser

参考

  1. https://blog.csdn.net/Kayson12345/article/details/81266587
  2. https://blog.csdn.net/leixiaohua1020/article/details/50535230
  3. https://blog.csdn.net/weixin_39766005/article/details/132301075
  4. https://blog.csdn.net/qingkongyeyue/article/details/52920104
  5. https://blog.csdn.net/qingkongyeyue/article/details/52921559

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/641273.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

路由_传递params参数和query参数

传递params参数 传递params参数可以直接在路径后面加上参数&#xff1a; 上述就是在路径变化的时候传过去三个值分别为哈哈、嘿嘿、呵呵的参数 但是这样的话会被认为三个参数是路径的一部分&#xff0c;计算机没有办法区分哪些是路径哪些是参数&#xff0c;所以首先要在这条路…

【新】snapd申请Let‘s Encrypt免费SSL证书、自动化续签证书

简介 之前写过一篇certbot申请SSL证书的文章&#xff1a;SSL证书申请&#xff0c;写得比较详细&#xff0c;但是最近发现使用snapd会更方便。 使用机器&#xff1a;Ubuntu 20.04 简单步骤 1、首先安装必要软件 sudo apt install snapd sudo apt install certbot sudo apt …

数据结构——顺序表基本操作的实现(初始化、取值、查找、插入)

一、线性表与顺序表的概述 线性表的数据元素的逻辑特征是线性的&#xff0c;是一种典型的线性结构。这样的结构可以借鉴数组&#xff0c;如数组a[10]中&#xff0c;a[1]前一定是a[0]&#xff0c;a[1]后一定是a[2]&#xff0c;首结点&#xff08;a[0]&#xff09;前面无元素&am…

基于springboot+vue的致远汽车租赁系统

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

如何用AI工具提升日常工作效率,帮我们提速增效减负

昨天&#xff0c;coze海外版支持了GPT4o&#xff0c; 立马体验了下&#xff0c;速度杠杠的。 https://www.coze.com 支持chatGP和gemini模型&#xff0c;需要科学上网。国内 https://www.coze.cn支持语雀、KIMI模型。 这里回到正题&#xff0c; 如何用AI工具提升日常工作效率…

对于高速信号完整性,一块聊聊啊(10)

本文包含的主要内容有: 过孔设计概述:从前面的各种基础知识到过孔设计,逐步对信号完整性有了初步了解,在过孔设计这里稍微做一个概述,也是个人的一些理解,算是一个小结。 过孔设计的必要性。 过孔结构的基础知识 实例:过孔设计仿真HFSS实例 过孔设计概述 通过前面…

2024年电工杯A题论文首发+摘要分享+问题一代码分享

问题一论文代码链接&#xff1a;https://pan.baidu.com/s/1kDV0DgSK3E4dv8Y6x7LExA 提取码&#xff1a;sxjm --来自百度网盘超级会员V5的分享 园区微电网风光储协调优化配置 摘要&#xff1a;园区微电网由风光发电和主电网联合为负荷供电&#xff0c;为了尽量提高风光电量的…

C# Sdcb.PaddleInference 中文分词、词性标注

C# Sdcb.PaddleInference 中文分词、词性标注 目录 效果 项目 代码 下载 参考 效果 项目 代码 using Sdcb.PaddleNLP.Lac; using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Windows.Forms; namespace C__Sdcb.Pad…

文心智能体,零代码构建情感表达大师智能体

前言 随着智能体技术的突飞猛进&#xff0c;各行各业正迎来前所未有的变革与机遇。智能体&#xff0c;作为人工智能领域的重要分支&#xff0c;以其自主性、智能性和适应性&#xff0c;正逐步渗透到我们生活的每一个角落&#xff0c;成为推动社会进步和科技发展的新动力。 为了…

王炸! Coze图像流发布,我用它实现了海马体影楼级形象照(内附喂饭级教程

最近Coze图像流发布&#xff0c;我用它实现了海马体风格照片Bot: 照片魔术师。你可以自定义提供模版&#xff0c;也可以根据你的需求生成模版&#xff01; 这篇文章&#xff0c;全文不废话&#xff0c;只讲干货 二话不说&#xff0c;先来看看帅气的奥特曼怎么生成吧吧&#xff…

海外媒体发稿的关键步骤和投稿策略:如何撰写高质量的新闻稿?国外软文发布平台有哪些?

发布国外新闻稿件是一个涉及多步骤的过程&#xff0c;旨在确保您的新闻稿能够有效覆盖目标受众。以下是一些关键步骤和实用的技巧&#xff0c;帮助你实现海外媒体发稿。 1. 明确目标和受众 首先&#xff0c;明确您发布新闻稿的目标&#xff0c;是为了增加品牌曝光、推出新产品…

低利率之后如何选择?以日本养老金为例

天风证券认为&#xff0c;日本养老金资产配置行为具备的关键特征包括&#xff1a;海外资产占比上升、日元境内资产占比下降&#xff1b;权益类&#xff08;包括境内和境外&#xff09;占比上升&#xff0c;等等。 日本从1990年代开始陷入低增长、低通胀的局面&#xff0c;我们以…

十五、Python模块(入门一定看!!!)「长期更新Python简单入门到适用」

首先什么是模块&#xff1f; 小伙伴们经常看我写的教程不难发现&#xff0c;前面我们用过几次模块就是sys的那个&#xff0c;其实python不仅标准库中包含了大量的模块&#xff08;也被称之为准模块&#xff09;&#xff0c;还有大量的第三方模块&#xff0c;开发者也可以自己发…

如何成为成功的AI产品经理

本文目录 1 AI产品经理的角色和职责 2 AI产品经理的必备知识技能 3 案例分析 4 总结一下 随着ChatGPT的大热&#xff0c;也带动今年的AI火了一把&#xff0c;很多公司都开始进行相关部署&#xff0c;自然产生了很多岗位需求。 来源&#xff1a;BOSS直聘 那么&#xff0c;要想当…

Go语言的内存泄漏如何检测和避免?

文章目录 Go语言内存泄漏的检测与避免一、内存泄漏的检测1. 使用性能分析工具2. 使用内存泄漏检测工具3. 代码审查与测试 二、内存泄漏的避免1. 使用defer关键字2. 使用垃圾回收机制3. 避免循环引用4. 使用缓冲池 Go语言内存泄漏的检测与避免 在Go语言开发中&#xff0c;内存泄…

React useState修改对象

在 React 中&#xff0c;useState 是一个 Hook&#xff0c;它可以让函数组件拥有状态。当想要改变一个对象类型的状态时&#xff0c;我们需要使用展开运算符&#xff08;...&#xff09;或者 Object.assign 来确保状态是正确地更新。 以下是一个使用 useState 来更新对象的例子…

ssm139选课排课系统的设计与开发+vue

选课排课系统的设计与开发vue 摘 要 互联网的普及&#xff0c;改变了人们正常的生活学习及消费习惯&#xff0c;而且也大大的节省了人们的时间&#xff0c;由于各种管理系统都再不断的增加&#xff0c;更方便了用户&#xff0c;也改良了很多的用户习惯。对于选课排课系统查询…

公司废弃的2014年群晖 DS215J 被我打包回家了,试玩一下

文章目录 简介安装查看存储、cpu、内存等信息DMS 学习video station 简介 群晖DS215J https://www.datastoreworks.com/DS215j.asp 下载&#xff1a;chrome-extension://oemmndcbldboiebfnladdacbdfmadadm/https://global.download.synology.com/download/Document/Hardware/…

针对上一篇微信同声传译语音播报部分坑的解决和优化

1. 上一篇语音播报其实是不完美的&#xff0c;就是如何停止上一个音频开始下一个音频的问题&#xff0c;我在此做一下修改 比如说&#xff1a;现在正在播放1&#xff0c;我点击2让2开始播放&#xff0c;1停止播放&#xff0c;我上面的写法是有问题的&#xff1a; 通过 innerAu…

【C++】AVL树和红黑树模拟实现

AVL树和红黑树 1. 背景2. AVL树的概念3. AVL树节点的定义4. AVL树的插入5. AVL树的旋转5.1. 左单旋5.2. 右单旋5.3. 左右单旋5.4. 右左单旋5.5. 旋转总结 6. AVL树的验证7. AVL树的性能8. 红黑树的概念9. 红黑树的节点的定义10. 红黑树的插入10.1. 情况一10.2.情况二 11. 红黑树…