Linux C编程实现RTSP组播技术 (linux c使用rtsp组播)
随着网络技术的不断进步,视频传输已经成为人们生活中不可或缺的一部分。RTSP(Real Time Streaming Protocol)作为一种用于显示多媒体数据的应用层协议,广泛应用于视频传输领域。而在视频传输的过程中,如果需要同时向多个用户传输展示同一视频信息,使用组播(Multicast)技术将是比较明智的选择。本篇文章将介绍如何使用。
一、组播简介
组播是一种数据传输方式,其将同一组播组内的数据只发送一份,让组播组内的所有接收端共享数据。与广播(Broadcast)不同,组播只向那些负责请求接收数据的多播组成员发送数据。组播在传输大规模多媒体数据及多点数据传输时有很好的效果。在IP协议中使用IGMP(Internet Group Management Protocol)协议作为组播协议。
二、RTSP协议
RTSP协议是用于在IP网络中控制多媒体数据的流协议。通过RTSP协议控制的流可以由Real Player、Media Player、QuickTime Player等多媒体播放器来播放。RTSP协议使用TCP传输控制命令,并使用UDP或TCP传输多媒体数据。其基本控制命令包括:DESCRIBE、SETUP、PLAY、PAUSE、TEARDOWN、GET_PARAMETER、SET_PARAMETER。
三、RTSP组播技术实现
1. 创建RTSP TCP监听接口
使用Linux C语言的socket库可以创建TCP监听,并绑定一个端口等待客户端的请求连接。
“`
/* 创建RTSP TCP监听接口 */
#include
#include
#include
#define RTSP_PORT 554
int mn(int argc, char *argv[]) {
int rtsp_sockfd;
struct sockaddr_in rtsp_addr;
/* 创建TCP监听socket */
rtsp_sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (rtsp_sockfd == -1) {
perror(“Fled to create RTSP listen socket”);
return -1;
}
/* 创建监听地址 */
rtsp_addr.sin_family = AF_INET;
rtsp_addr.sin_addr.s_addr = htonl(INADDR_ANY);
rtsp_addr.sin_port = htons(RTSP_PORT);
/* 绑定socket到监听地址 */
if (bind(rtsp_sockfd, (struct sockaddr*)(&rtsp_addr), sizeof(struct sockaddr)) == -1) {
perror(“Fled to bind RTSP listen socket”);
return -1;
}
/* 开始监听 */
if (listen(rtsp_sockfd, 5) == -1) {
perror(“Fled to listen on RTSP listen socket”);
return -1;
}
return 0;
}
“`
2. 支持DESCRIBE命令
RTSP的DESCRIBE命令返回一个SDP(Session Description Protocol)描述,它包含有关媒体流的信息,例如媒体的类型、格式、连接信息等。在组播场景下,需要将SDP中的unicast地址替换为组播地址。
“`
/* 支持DESCRIBE命令 */
#include
#include
#include
#include
#define DESC_FILE “video.sdp”
void handle_describe(int sockfd, struct sockaddr_in *client_addr) {
char sdp_buf[2023], send_buf[8192];
int sdp_fd, rlen;
/* 打开sdp文件 */
sdp_fd = open(DESC_FILE, O_RDON);
if (sdp_fd == -1) {
perror(“Fled to open SDP file”);
return;
}
/* 读取sdp文件内容 */
rlen = read(sdp_fd, sdp_buf, sizeof(sdp_buf));
if (rlen == -1) {
perror(“Fled to read SDP file”);
close(sdp_fd);
return;
}
/* 关闭sdp文件 */
close(sdp_fd);
/* 组装响应内容 */
sprintf(send_buf, “RTSP/1.0 200 OK\r\n”
“Content-Type: application/sdp\r\n”
“Content-Base: rtsp://%s:%d/\r\n”
“Server: My RTSP Server/1.0\r\n”
“%s”,
inet_ntoa(client_addr->sin_addr),
ntohs(client_addr->sin_port),
sdp_buf);
/* 发送响应内容 */
send(sockfd, send_buf, strlen(send_buf), 0);
}
“`
需要注意的是,SDP中的unicast地址需要替换为组播地址。在SDP中有如下格式的信息:
“`
m=video 0 RTP/AVP 96
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1; sprop-parameter-sets=Z0IAH5WoFAFuQA==,aM4wpIA=
c=IN IP4 192.168.1.100 // 该字段为unicast地址
“`
我们需要将其修改为:
“`
m=video 0 RTP/AVP 96
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1; sprop-parameter-sets=Z0IAH5WoFAFuQA==,aM4wpIA=
c=IN IP4 224.0.1.100 // 该字段修改为组播地址
“`
3. 获取组播地址
组播地址的选取需满足以下规定:
– 组播地址为D类IP地址,其地址范围为224.0.0.0-239.255.255.255
– 组播地址的之一字节必须为224-239,即之一字节范围为0xE0-0xEF
– 组播地址的第二字节为用户自选值,一般选用0~255中未重复的数字
– 组播地址的第三、四字节分别表示组播组的ID,可以使用随机数或用户手动指定
在C程序中,可以使用rand()函数生成一个随机值。
“`
/* 获取组播地址 */
#include
char *get_multi_addr() {
char *addr;
int b1, b2, b3;
/* 随机生成组播地址 */
b1 = 224 + rand() % 16;
b2 = rand() % 256;
b3 = rand() % 256;
/* 组装组播地址 */
addr = malloc(16);
sprintf(addr, “%d.%d.%d.100”, b1, b2, b3);
return addr;
}
“`
4. 支持SETUP命令
RTSP的SETUP命令用于请求服务器初始化一个数据流。在组播场景下需要根据客户端的交互来确定需要向哪个组播地址发送数据。当收到SETUP命令后,服务器将分配一个新的组播地址,并发送响应内容包含组播地址以及RTP传输端口等。此时,在RTP传输端口上,服务器开始不断向组播地址的RTP端口发送数据包。
我们需要在关键的设置中获取到组播地址和对应的端口号,并使用UDP传输媒体数据。
“`
/* 支持SETUP命令 */
#include
#include
#include
#define RTP_PORT 47000
void handle_setup(int sockfd, struct sockaddr_in *client_addr) {
char *multi_addr, send_buf[512];
struct timeval tv;
int rtp_fd, ctrl_fd, send_len, addr_len;
struct sockaddr_in rtp_addr, ctrl_addr;
/* 创建rtp组播socket */
rtp_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (rtp_fd == -1) {
perror(“Fled to create RTP listen socket”);
return;
}
/* 创建rtp地址 */
rtp_addr.sin_family = AF_INET;
rtp_addr.sin_addr.s_addr = htonl(INADDR_ANY);
rtp_addr.sin_port = htons(RTP_PORT);
/* 绑定rtp socket到rtp地址 */
if (bind(rtp_fd, (struct sockaddr*)(&rtp_addr), sizeof(struct sockaddr)) == -1) {
perror(“Fled to bind RTP listen socket”);
return;
}
/* 获取组播地址 */
srand(time(NULL));
multi_addr = get_multi_addr();
/* 创建ctrl组播socket */
ctrl_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (ctrl_fd == -1) {
perror(“Fled to create CTRL socket”);
return;
}
/* 创建ctrl地址 */
ctrl_addr.sin_family = AF_INET;
ctrl_addr.sin_addr.s_addr = inet_addr(multi_addr);
ctrl_addr.sin_port = htons(RTP_PORT + 1);
/* 设置组播选项 */
unsigned char ttl = 32;
setsockopt(ctrl_fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl));
unsigned char loop_on = 1;
setsockopt(ctrl_fd, IPPROTO_IP, IP_MULTICAST_LOOP, &loop_on, sizeof(loop_on));
/* 发送控制内容 */
sprintf(send_buf, “RTSP/1.0 200 OK\r\n”
“Session: 12345678\r\n”
“Transport: RTP/AVP;unicast;client_port=50000-50001;mode=play\r\n”
“Server: My RTSP Server/1.0\r\n”
“Content-Length: 0\r\n\r\n”);
send_len = strlen(send_buf);
addr_len = sizeof(struct sockaddr_in);
sendto(sockfd, send_buf, send_len, 0, (struct sockaddr*)&(*client_addr), addr_len);
/* 发送控制包(组播) */
gettimeofday(&tv, NULL);
int now = tv.tv_sec * 1000 + tv.tv_usec / 1000;
int last_frame = now;
int frame_rate = 25;
while (1) {
send_len = rtp_send_frame(multi_addr, RTP_PORT, now, last_frame, frame_rate);
last_frame = now;
send_len = sendto(rtp_fd, rtp_buf, send_len, 0, (struct sockaddr*)&ctrl_addr, addr_len);
usleep(40000);
}
}
“`
5. RTP数据帧发送
在处理SETUP命令时,我们开始在指定的组播地址和端口号上不断发送数据包。那么在每个数据包中包含了什么样的信息呢?我们在创建数据包时需要注意以下几点:
– 前12字节为RTP头部,其中标识传输类型、传输时间等关键信息
– 前12字节之后的数据包为媒体数据
我们使用最简单的方式生成一个视频数据包,其中数据的pattern为所有像素为0x80的视频帧。
“`
/* RTP数据包发送 */
#define MAX_RTP_PKT_LENGTH 1400
#define H264_PAYLOAD_TYPE 96 /* 参考RFC3984 */
char rtp_buf[MAX_RTP_PKT_LENGTH];
int rtp_send_frame(const char *p_multi_ip, unsigned short port, int current_time, int last_time, int frame_rate) {
unsigned int len = 150000; // 150KB 视频数据大小(假设)
int nalu_payload_size = len – 4;
int nal_payload_position = 4;
/* 生成RTP头部 */
memcpy(rtp_buf, “$$__header__$$”, 12);
/* 填充NALU信息 */
rtp_buf[0] = rtp_buf[0] | 0x80; // 填充RTP版本位和标志位
rtp_buf[1] = rtp_buf[1] & 0x7F; // 填充长度位(更高位为1则表示后面有padding)
rtp_buf[1] = rtp_buf[1] | ((nalu_payload_size & 0x1FE00) >> 13);
rtp_buf[2] = (nalu_payload_size & 0x1FC0) >> 6;
rtp_buf[3] = (nalu_payload_size & 0x3F)
/* 填充NALU内容 */
for(int s = nal_payload_position; s
rtp_buf[s] = 0x80; //0x80 是数据位
}
return nal_payload_position + nalu_payload_size;
}
“`
四、