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;

}

“`

四、

相关问题拓展阅读:

我想在linux下写一个c程序调用linux的可执行文件或者程序,怎么做

Linux C编程中,调用另一个

可执行文件

或调用命令用system函判如行数最简单了,这个函数原理是在你编写的那个程橡敏序的内部启动另一个程序或命令,从而创建一个新进程,并等待这个进程执行完毕退出。如果正常执行,system函数掘哗将返回被执行程序或命令的退出码;如果无法运行这个程序或命令,将返回错误代码127;如果是其他错误,返回-1。这个函数的原型是:

#include

int system(const char *string);

参数string是将要执行的

程序文件

名或路径,如果是启动一个命令就是一个命令

字符串

还有一种执行外部程序的方法是exec系列函数,一般是在fork的子进程里面调用exec系列函数,那主进程里直接调用exec系列不行吗,为什么要fork再在子进程里调用呢?因为exec系列的函数(包括execl函数)是将当前进程替换成新进程,这里的当前进程就是你编写的程序,也就是说新进程启动后调用exec函数的进程就不存在了,所以exec系列函数调用之后的代码就不会再执行了。如果你不放在fork子进程里面,那你编写的程序的主进程在执行execl函数后就完全不存在了,所以exec系列函数的使用都是先fork然后在子进程里面调用。因为exec系列函数都要使用fork调用,所以我一般是用system函数。

linux c使用rtsp组播的介绍就聊到这里吧,感谢你花时间阅读本站内容,更多关于linux c使用rtsp组播,Linux C编程实现RTSP组播技术,我想在linux下写一个c程序调用linux的可执行文件或者程序,怎么做的信息别忘了在本站进行查找喔。


数据运维技术 » Linux C编程实现RTSP组播技术 (linux c使用rtsp组播)