1、内容概览
本文主要基于Python语言实现了基于Socket UDP协议的RTP数据包通信与传输,其中,示例RTP数据包为PCMA编码的音频包,其他类型RTP同理,通过socket长连接将示例用户A与B连通并通信,在传输过程中,用户A或用户B的socket连接即作为服务端接受RTP数据包,也作为客户端发送RTP数据包,值得重点说明的是:RTP交互过程接收端和发送端的ip及port必须是一个socket连接才能做到交互及正确解码。对于UDP及RTP概念相关的内容本文不做过多阐述,请读者自行了解。
2、设计流程
主要流程如下:两个用户A、B作为两端传输并解析RTP数据包,用户A首先启动,然后用户B启动,首先向用户A发送RTP数据包,用户A接收到RTP时首先解析,然后将数据包再发送给B,B继续完成RTP解析操作,完成一次数据包通信。数据流及步骤如下图(1~4步):
3、示例实现
整体通过Python Socket库实现,首先创建Socket对象:
import socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
其中,AF_INET是指IPv4,SOCK_DGRAM是指UDP协议,然后将socket对象绑定到ip及端口上,如下:
server_address = ('127.0.0.1', 8000)
server_socket.bind(server_address)
本文将用户A绑定到了('127.0.0.1', 8000);用户B绑定到了('127.0.0.1', 8001);示例ip和port可根据实际情况修改,通常可由RTP数据包的address地址获取并返回数据包。
RTP交互时,socket的RTP发送使用sendto方法,RTP接收使用recvfrom方法,各方法的用法如下:
server_socket.sendto(rtp_packet, address)
packet, address = server_socket.recvfrom(4096)
对于recvfrom方法,参数4096表示接受rtp数据包的缓冲区大小为4096,对于PCMA编码的RTP数据包,大小通常为172个字节(包括头部信息及载荷信息,后续说明),因此4096是足够的,读者也可根据实际情况自行调整。
4、结果说明
下面按照第二部分的设计流程对实现结果进行说明。首先用户A启动,在此我们打印日志如下,表示用户A的服务端已经启动,且再监听传输过来的数据包。
socket A start listen
然后用户B使用sendto方法向用户A发送RTP,日志如下,表示用户B的socket客户端向用户A的服务端成功发送了数据包。
socket B send success to socket A:('127.0.0.1', 8000), time:2025-05-06 19:12:09.353820
示例RTP数据包内容如下:
packet = b'\x80\x08\x12\x00\x02\xd1|\x183B\xb9\xffU\xd5\xd4\xd4UTU\xd5\xd4TT\xd5\xd5\xd5UUU\xd5\xd5\xd5UU\xd5\xd5UUUU\xd5\xd5\xd5\xd5\xd5\xd5UUTUUUUUUUTUU\xd5UU\xd5\xd4\xd5\xd5\xd5\xd5\xd5\xd5\xd5UUUUUU\xd5\xd5\xd5U\xd5UUUUUUUU\xd5U\xd5\xd5\xd5\xd5U\xd5\xd5\xd5UUUU\xd5\xd5\xd5\xd5U\xd5UUUUUU\xd5\xd5\xd5UUUUUUUTU\xd5\xd5\xd5UU\xd5\xd4\xd5\xd5\xd5U\xd5\xd5U\xd5UU\xd5U\xd5\xd5U\xd5UUU\xd5\xd5U\xd5\xd5\xd5U\xd5UUUUU\xd5UUUU'
然后,用户A的服务端接收到用户B客户端发送的数据包,并解析数据包内容,日志如下:
socket A receive from socket B: 2025-05-06 19:12:09.353891---ip:port: ('127.0.0.1', 8001) Sequence Number: 4608, Version: 2, Padding: 0, Extension: 0, CSRC Count: 0, Marker: 0, Payload Type: 8, Timestamp: 47283224, SSRC: 860011007, Payload Length: 160
Payload Length: 160
Payload Content: b'U\xd5\xd4\xd4UTU\xd5\xd4TT\xd5\xd5\xd5UUU\xd5\xd5\xd5UU\xd5\xd5UUUU\xd5\xd5\xd5\xd5\xd5\xd5UUTUUUUUUUTUU\xd5UU\xd5\xd4\xd5\xd5\xd5\xd5\xd5\xd5\xd5UUUUUU\xd5\xd5\xd5U\xd5UUUUUUUU\xd5U\xd5\xd5\xd5\xd5U\xd5\xd5\xd5UUUU\xd5\xd5\xd5\xd5U\xd5UUUUUU\xd5\xd5\xd5UUUUUUUTU\xd5\xd5\xd5UU\xd5\xd4\xd5\xd5\xd5U\xd5\xd5U\xd5UU\xd5U\xd5\xd5U\xd5UUU\xd5\xd5U\xd5\xd5\xd5U\xd5UUUUU\xd5UUUU'
由上述日志可以看到,用户A接收到8001端口,即用户B发送来的数据包,并进行解析,由日志第一行可以看到,根据数据包分别解析到了:Sequence Number、Version、Padding等RTP的头部信息,并且输出了有效载荷的长度为160及有效载荷的内容,有效载荷将作为后续实际分析的原始数据,可根据实际情况处理,头部及载荷解析过程如下:
# RTP头部解析
# 获取版本
version = (packet[0] & 0xC0) >> 6
# 获取填充位
padding = (packet[0] & 0x20) >> 5
# 获取扩展位
extension = (packet[0] & 0x10) >> 4
# 获取CSRC计数器
csrc_count = (packet[0] & 0x0F)
# 获取标记位
marker = (packet[1] & 0x80) >> 7
# 获取payload类型
payload_type = (packet[1] & 0x7F)
# 获取序列号
sequence_number = (packet[2] << 8) | packet[3]
# 获取时间戳
timestamp = (packet[4] << 24) | (packet[5] << 16) | (packet[6] << 8) | packet[7]
# 获取同步源(SSRC)标识符
ssrc = (packet[8] << 24) | (packet[9] << 16) | (packet[10] << 8) | packet[11]
# RTP头部是12字节,有效载荷紧随其后
payload = packet[12:]
用户A解析数据包后,随即调用sendto方法向用户B发送数据包,日志如下:
socket A send success to socket B:('127.0.0.1', 8001), time:2025-05-06 19:12:09.353944
然后,用户B的服务端接收用户A的客户端发送的数据包并解析,解析过程同上,日志如下:
socket B receive from socket A: 2025-05-06 19:12:09.353970---ip:port: ('127.0.0.1', 8000)Sequence Number: 4608, Version: 2, Padding: 0, Extension: 0, CSRC Count: 0, Marker: 0, Payload Type: 8, Timestamp: 47283224, SSRC: 860011007, Payload Length: 160
Payload Length: 160
Payload Content: b'U\xd5\xd4\xd4UTU\xd5\xd4TT\xd5\xd5\xd5UUU\xd5\xd5\xd5UU\xd5\xd5UUUU\xd5\xd5\xd5\xd5\xd5\xd5UUTUUUUUUUTUU\xd5UU\xd5\xd4\xd5\xd5\xd5\xd5\xd5\xd5\xd5UUUUUU\xd5\xd5\xd5U\xd5UUUUUUUU\xd5U\xd5\xd5\xd5\xd5U\xd5\xd5\xd5UUUU\xd5\xd5\xd5\xd5U\xd5UUUUUU\xd5\xd5\xd5UUUUUUUTU\xd5\xd5\xd5UU\xd5\xd4\xd5\xd5\xd5U\xd5\xd5U\xd5UU\xd5U\xd5\xd5U\xd5UUU\xd5\xd5U\xd5\xd5\xd5U\xd5UUUUU\xd5UUUU'
由于该数据包的来源是用户A,因此端口为8000,至此,RTP的一次通信过程已完结,如需处理,可在接收数据包后对载荷进行处理,然后将处理结果通过sendto方法返回,用户A及用户B完整的日志如下:
# 用户A
socket A start listen
socket A receive from socket B: 2025-05-06 19:12:09.353891---ip:port: ('127.0.0.1', 8001) Sequence Number: 4608, Version: 2, Padding: 0, Extension: 0, CSRC Count: 0, Marker: 0, Payload Type: 8, Timestamp: 47283224, SSRC: 860011007, Payload Length: 160
Payload Length: 160
Payload Content: b'U\xd5\xd4\xd4UTU\xd5\xd4TT\xd5\xd5\xd5UUU\xd5\xd5\xd5UU\xd5\xd5UUUU\xd5\xd5\xd5\xd5\xd5\xd5UUTUUUUUUUTUU\xd5UU\xd5\xd4\xd5\xd5\xd5\xd5\xd5\xd5\xd5UUUUUU\xd5\xd5\xd5U\xd5UUUUUUUU\xd5U\xd5\xd5\xd5\xd5U\xd5\xd5\xd5UUUU\xd5\xd5\xd5\xd5U\xd5UUUUUU\xd5\xd5\xd5UUUUUUUTU\xd5\xd5\xd5UU\xd5\xd4\xd5\xd5\xd5U\xd5\xd5U\xd5UU\xd5U\xd5\xd5U\xd5UUU\xd5\xd5U\xd5\xd5\xd5U\xd5UUUUU\xd5UUUU'
socket A send success to socket B:('127.0.0.1', 8001), time:2025-05-06 19:12:09.353944
# 用户B
socket B send success to socket A:('127.0.0.1', 8000), time:2025-05-06 19:12:09.353820
socket B start listen
socket B receive from socket A: 2025-05-06 19:12:09.353970---ip:port: ('127.0.0.1', 8000)Sequence Number: 4608, Version: 2, Padding: 0, Extension: 0, CSRC Count: 0, Marker: 0, Payload Type: 8, Timestamp: 47283224, SSRC: 860011007, Payload Length: 160
Payload Length: 160
Payload Content: b'U\xd5\xd4\xd4UTU\xd5\xd4TT\xd5\xd5\xd5UUU\xd5\xd5\xd5UU\xd5\xd5UUUU\xd5\xd5\xd5\xd5\xd5\xd5UUTUUUUUUUTUU\xd5UU\xd5\xd4\xd5\xd5\xd5\xd5\xd5\xd5\xd5UUUUUU\xd5\xd5\xd5U\xd5UUUUUUUU\xd5U\xd5\xd5\xd5\xd5U\xd5\xd5\xd5UUUU\xd5\xd5\xd5\xd5U\xd5UUUUUU\xd5\xd5\xd5UUUUUUUTU\xd5\xd5\xd5UU\xd5\xd4\xd5\xd5\xd5U\xd5\xd5U\xd5UU\xd5U\xd5\xd5U\xd5UUU\xd5\xd5U\xd5\xd5\xd5U\xd5UUUUU\xd5UUUU'
注:对于RTP格式,如头部、载荷可自行了解,如有问题,可评论区留言交流