最简单的 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_INET2. 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 的地址,这个地址事实上表示不确定地址,一般在各个系统中均定义为全 0serAddr.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 接收缓冲区拷贝数据到 recvDataint 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 RTPif (parse_rtp != 0){char payload_str[10] = { 0 };RTP_FIXED_HEADER rtp_header;int rtp_header_size = sizeof(RTP_FIXED_HEADER);// RTP Headermemcpy((void *)&rtp_header, recvData, rtp_header_size);// RFC3551char 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 Datachar *rtp_data = recvData + rtp_header_size;int rtp_data_size = pktsize - rtp_header_size;fwrite(rtp_data, rtp_data_size, 1, fp1);// Parse MPEGTSif (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) // 判断同步字节,固定为 0x47break;// 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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Sqli-labs-master靶场1-20通关教程

目录 SQL注入基本语句 Less-1&#xff08;字符型-闭合 &#xff09; Less-2&#xff08;数字型&#xff09; Less-3&#xff08;字符型-闭合 ) &#xff09; Less-4&#xff08;字符型-闭合 ") &#xff09; Less-5&#xff08;报错注入-闭合 &#xff09; Less-…

激光雷达测试板智能系统应用

在自动驾驶技术和机器人感知系统的迅猛发展中&#xff0c;激光雷达&#xff08;Lidar&#xff09;作为一种先进的测距技术&#xff0c;正逐渐成为这些系统不可或缺的组成部分。而在这一技术的实际应用前&#xff0c;对激光雷达进行精确的测试和校准是至关重要的一步。激光雷达测…

【原创】java+springboot+mysql日程管理系统设计与实现

个人主页&#xff1a;程序猿小小杨 个人简介&#xff1a;从事开发多年&#xff0c;Java、Php、Python、前端开发均有涉猎 博客内容&#xff1a;Java项目实战、项目演示、技术分享 文末有作者名片&#xff0c;希望和大家一起共同进步&#xff0c;你只管努力&#xff0c;剩下的交…

基于tensorflow的咖啡豆识别

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 一、前期工作 1. 设置GPU import tensorflow as tfgpus tf.config.list_physical_devices("GPU")if gpus:tf.config.experimental.set_memory_gr…

全球伦敦银收盘时间一致吗

跟伦敦金市场相似&#xff0c;伦敦银市场也是一个全球化的无形市场&#xff0c;无论来自世界上什么地方的投资者参与其中&#xff0c;都可以得到全天接近24个小时的连贯行情&#xff0c;只要精力足够&#xff0c;根本不用担心没有交易获利的机会。但由于交易平台始终有维护的需…

轻松实现PDF文件的在线浏览

福昕软件最近发布了一款名为Cloud API的产品&#xff0c;通过几行代码即可轻松实现PDF文件的在线浏览。先一睹为快吧。 简介 先看看产品官网&#xff1a;福昕 Cloud API Cloud API包括两个形态产品&#xff0c;一个是在线的PDF查看工具&#xff0c;叫PDF Embed API,另外一个…

git 学习(一)

一、版本控制 &#xff08;一&#xff09;介绍 版本迭代 每一次更新代码 都会出现新的版本如果我们需要之前的版本的文件 我们就得需要版本控制的文件 每一次更新的结果我们都保存下来 多人开发必须要用版本控制器 否则代价会很大 &#xff08;二&#xff09;主流的版本控制…

uniapp开发微信小程序:用户手机号授权获取全流程详解与实战示例

随着多端小程序研发工具的日益普及&#xff0c;诸如uniapp、Taro、Flutter等跨平台解决方案使得开发者能够高效地构建同时适配多个主流小程序平台&#xff08;如微信、支付宝、百度、字节跳动等&#xff09;的应用。尽管各平台间存在一定的差异性&#xff0c;但在获取用户手机号…

防火墙技术基础篇:基于Ensp配置防火墙NAT server(服务器映射)

配置防火墙NAT server(服务器映射) 什么是NAT Server (服务器映射) NAT&#xff08;Network Address Translation&#xff0c;网络地址转换&#xff09;是一种允许多个设备共享一个公共IP地址的技术。NAT Server&#xff0c;也称为服务器映射&#xff0c;是NAT技术中的一种应…

7 Series FPGAs Integrated Block for PCI Express IP核设计中的物理层控制核状态接口

物理层控制和状态允许用户应用程序根据数据吞吐量和电源需求来更改链路的宽度和速度。 1 Design Considerations for a Directed Link Change 在Directed Link Change&#xff08;定向链接更改&#xff09;期间需要注意的事项有&#xff1a; 链接更改操作&#xff08;Link c…

C++STL---模拟实现string

我们这篇文章进行string的模拟实现。 为了防止标准库和我们自己写的string类发生命名冲突&#xff0c;我们将我们自己写的string类放在我们自己的命名空间中&#xff1a; 我们先来搭一个class string的框架&#xff1a; namespace CYF{ public://各种成员函数 priva…

基于单片机智能防触电装置的研究与设计

摘 要 &#xff1a; 针对潮湿天气下配电线路附近易发生触电事故等问题 &#xff0c; 对单片机的控制算法进行了研究 &#xff0c; 设 计 了 一 种 基 于 单片机的野外智能防触电装置。 首先建立了该装置的整体结构框架 &#xff0c; 再分别进行硬件设计和软件流程分析 &#xf…

IDEA升级web项目为maven项目乱码

今天将一个java web项目改造为maven项目。 首先&#xff0c;创建一个新的maven项目&#xff0c;将文件拷贝到新项目中。 其次&#xff0c;将旧项目的jar包&#xff0c;在maven的pom.xml做成依赖 接着&#xff0c;把没有maven坐标的jar包在编译的时候也包含进来 <build>…

实战教程:使用Go的net/http/fcgi包打造高性能Web应用

实战教程&#xff1a;使用Go的net/http/fcgi包打造高性能Web应用 简介理解FCGI协议FastCGI工作原理CGI与FastCGI对比其他接口对比 初步使用net/http/fcgi包设置和配置基础环境一个简单的FastCGI应用示例本地测试与调试 深入net/http/fcgi包的高级用法探索net/http/fcgi的主要功…

气膜建筑的运行保障:应对停电的解决方案—轻空间

气膜建筑作为一种现代化的建筑形式&#xff0c;以其独特的结构和多样的应用赢得了广泛关注。这种建筑依靠风机不断往内部吹气来维持其结构形态&#xff0c;那么如果遇到停电的情况&#xff0c;该如何确保其正常运行呢&#xff1f; 气膜建筑的供风系统 气膜建筑内部的气压维持依…

信创崛起:从安可到国产化,中国信息技术创新之路

在信息技术迅速演进的时代背景下&#xff0c;几个核心概念日益凸显其重要性&#xff1a;安全可靠&#xff08;简称安可&#xff09;、信息技术创新&#xff08;简称信创&#xff09;&#xff0c;以及国产化。这些概念紧密关联&#xff0c;共同服务于一个宏伟目标——构建一个独…

MFC 发起 HTTP Post 请求 发送MES消息

文章目录 获取Token将获取的Token写入JSON文件 将测试参数发送到http首先将测试参数写入到TestData.JSON文件rapidjson 库需要将CString 进行类型转换才能使用&#xff0c;将CString 转换为const char* 发送JSON 参数到http中&#xff0c;并且获取返回结果写入TestFinish.JSON文…

SpringSecurity6从入门到实战之SpringSecurity快速入门

SpringSecurity6从入门到实战之SpringSecurity快速入门 环境准备 依赖版本号springsecurity6.0.8springboot3.0.12JDK17 这里尽量与我依赖一致,免得在学习过程中出现位置的bug等 创建工程 这里直接选择springboot初始化快速搭建工程,导入对应的jdk17进行创建 直接勾选一个web…

Redhat9 LAMP安全配置方案及测试

目录 数据库主机 安装Mariadb数据库服务 设置mariadb开机自动启动 Php主机 部署Apache服务器 设置apache服务开机自启 安装php 安装 phpMyAdmin 打开测试机 更新软件包列表&#xff1a; 首先&#xff0c;确保你的软件包列表是最新的。打开终端并输入以下命令&#xf…

Linux查看设备信息命令

dmidecode | grep Product Name 查看grub版本号&#xff1a;rpm -qa | grep -i "grub" 客户端操作系统版本&#xff1a; cat /etc/issue cat /etc/redhat-release 处理器品牌及型号&#xff1a; less /proc/cpuinfo |grep model