一、前言
这篇文章讲解: 采用华为云最新推出的Flexus云服务器X实例搭建RTSP服务器,完成视频直播需求。
随着实时视频流传输需求的增长,RTSP(实时流协议)服务器成为了许多视频监控、直播和多媒体应用的核心组件。在当今高度互联的世界中,能够快速部署且稳定运行的RTSP服务器对于确保高质量的视频流体验至关重要。本文将指导如何在华为云Flexus X实例上部署一款轻量级的RTSP服务器——(采用simple-rtsp-server)。
通过本教程,将详细介绍如何在华为云Flexus X实例上安装和配置simple-rtsp-server。simple-rtsp-server以其简单易用的特点而闻名,非常适合那些希望快速搭建起基本RTSP服务的开发者。将涵盖从环境准备到软件安装,再到基本配置的所有步骤,帮助顺利搭建一个可靠的RTSP服务器,为视频流应用提供坚实的基础。
华为云Flexus云服务器X实例是由国家科技进步奖获得者、华为公司Fellow及华为云首席架构师顾炯炯牵头研发的一款创新性云服务器。该实例基于华为的擎天QingTian架构、瑶光云脑和盘古大模型等核心技术,是业界首款应用驱动的柔性算力云服务器,适用于高科技、零售、金融、游戏等多个行业的通用工作负载场景,如网络应用、数据库、虚拟桌面、分析索引、微服务及持续集成/持续部署(CI/CD)等。
传统的云服务器通常只提供固定的CPU和内存规格,无法精准匹配用户的实际资源需求,导致资源利用效率低下。相比之下,华为云Flexus X实例提供了更为灵活的算力配置,支持超过100种不同的CPU与内存配比,最高可达到3:1的比例,从而更好地适应各种业务应用的需求。
Flexus X实例不仅在性能方面表现出色,还内置了智能应用调优算法,结合华为技术专家多年积累的经验,在基础模式下,其GeekBench单核及多核跑分可达业界同规格独享型实例的1.6倍。在性能模式下,Flexus X实例的性能超过了同类C系/G系/R系及S系旗舰型云主机的标准。
此外,Flexus X实例还配备了X-Turbo加速技术和大模型底层智能调度技术,为关键业务应用提供加速功能。例如,在Flexus X实例上部署的MySQL、Redis和Nginx等应用,其性能最高可达业界同规格独享型实例的6倍(MySQL性能),长期运行时也能保持2倍的性能优势。
Flexus X实例在定价策略上定位于经济型级别,但其性能表现却超越了旗舰级云主机。通过动态业务画像规格优化等技术,用户在将业务从本地服务器或其他云服务提供商迁移到Flexus X实例时,可以节省高达30%的算力成本,从而实现业务的全面提速和效能提升,享受到云基础设施的显著改进体验。
二、服务器选购
2.1 登录官网
链接:https://www.huaweicloud.com/
在官网首页的轮播图里可以看到,有Flexus
云服务器的宣传。这是华为云匠心打造的下一代跃级产品,面向中低负载场景,性能倍增、体验跃级的服务器。
2.2 选购服务器
在产品页面,也可以看到Flexus
云服务的选项,点击进去选购服务器。
链接:https://www.huaweicloud.com/product/flexus.html
在选购页面可以看到服务器推广器件,1年36
块钱。 每个月的流量是100G
,对于一些访问量不高的服务器或者测试用是非常合适的。
当前我要选择的服务器是:Flexus云服务器X实例
,点击Flexus
系列产品,选择X实例。Flexus云服务器X实例符合:柔性算力,六倍性能,旗舰体验,覆盖高科技、零售、金融、游戏等行业大多数通用工作负载场景。
2.3 选择服务器区域
针对时延敏感型业务请选择靠近您业务的区域,以降低网络时延,提高访问速度;针对和存量云产品有内网互通需求的业务,请选择和存量产品相同的区域。
2.4 选择服务器规格
2.5 选择系统镜像
我这选择ubuntu系统,用来搭建服务器。这个根据自己的情况选择,自己适合那一种就选择哪一种。
2.6 选择存储盘
我选择150G大小。
2.7 配置密码
设置好服务器的名字(如果你有多个服务器,为了自己好区别)和系统的登录密码。
2.8 配置云备份
云备份这个不买。有需要自己可以购买。
2.9 确认配置
2.10 立即购买
购买成功。
创建成功之后,邮箱会收到提示的。
2.10 后台控制台
链接:https://console.huaweicloud.com/ecm
在控制台可以看到服务器的详情。
三、服务器登录
3.1 查看服务器的详情
点击服务器的名称,可以进去到详情页面。
3.2 远程登录
填入设置好的密码。
登录成功。
3.9 采用FinalShell登录
自带的在浏览器里运行,每次需要打开浏览器,文件也不方便上传下载。
所以,这里开发阶段,我采用的 FinalShell登录到服务器。
新建SSH连接,输入连接信息。
登录成功。
接下来就可以进行开发了。
四、搭建RTSP流媒体服务器
4.1 simple-rtsp-server库
simple-rtsp-server依赖ffmpeg,版本要求>=4.x。
GitHub仓库地址:https://github.com/BreakingY/simple-rtsp-server
4.2 安装基础依赖
在命令行运行。
sudo apt-get -y install autoconf automake build-essential libass-dev libfreetype6-dev libsdl2-dev libtheora-dev libtool libva-dev libvdpau-dev libvorbis-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev pkg-config texinfo zlib1g-dev
安装过程中:
安装完成。
4.3 安装依赖库
在系统命令行分别运行以下命令,安装依赖库。
汇编库: sudo apt-get install yasm sudo apt-get install nasm 视频库: sudo apt-get install libx264-dev sudo apt-get install libx265-dev 音频库: sudo apt-get install libfdk-aac-dev sudo apt-get install libmp3lame-dev sudo apt-get install libopus-dev
4.4 安装ffmpeg
下载ffmpeg的库源码。
wget https://ffmpeg.org//releases/ffmpeg-4.0.5.tar.bz2 tar xjvf ffmpeg-4.0.5.tar.bz2 cd ffmpeg-4.0.5
编译ffmpeg库源码。
./configure --prefix=/usr/local --enable-libx264 --disable-x86asm --enable-nonfree --enable-libfdk-aac --enable-shared --enable-gpl --enable-libmp3lame --enable-libopus --extra-cflags=-I/usr/local/include --extra-ldflags=-L/usr/local/lib make make install
4.5 simple-rtsp-server下载编译
git clone https://github.com/BreakingY/simple-rtsp-server.git cd simple-rtsp-server mkdir build cd build cmake .. make -j4
4.6 运行服务器
cp -r ../mp4path . ./rtsp_server loop 0
运行效果:
4.7 开放规则
要记得把服务器的 端口开放出来, 不然无法访问。
4.8 播放测试(RTSP协议播放云端视频文件)
自带了4个测试文件。 可以直接播放。
这是自带的4个测试文件的路径:
root@flexusx-1a58:~/work/simple-rtsp-server# ls mp4path/ test_1280x720_h264_aac.mp4 test_h264_aac.mp4 test_h264_pcma.mkv test_h265_aac.mp4
下面是播放格式:
rtsp://116.205.107.156:8554/test_1280x720_h264_aac.mp4
其中:
rtsp://<自己的公网IP>:8554/<mp4path目录下的视频文件名称>
下面是potplayer播放器播放的效果:
播放过程中,在服务器上会打印出每个过程的详情。
4.9 rtsp_server.c代码
#include "common.h" #include "session.h" #include <arpa/inet.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <unistd.h> #define BUF_MAX_SIZE (1024 * 1024) char *mp4Dir = "mp4path/\0"; // MP4文件存放位置 int auth = 1; #define USER "admin" #define PASSWORD "123456" /*doClientThd线程参数*/ struct thd_arg_st { int client_sock_fd; char client_ip[30]; int client_port; }; int create_rtp_sockets(int *fd1, int *fd2, int *port1, int *port2) { struct sockaddr_in addr; int port = 0; *fd1 = socket(AF_INET, SOCK_DGRAM, 0); if (*fd1 < 0) { perror("socket"); return -1; } *fd2 = socket(AF_INET, SOCK_DGRAM, 0); if (*fd2 < 0) { perror("socket"); close(*fd1); return -1; } memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_ANY); for (port = 1024; port <= 65535; port += 2) { addr.sin_port = htons(port); if (bind(*fd1, (struct sockaddr *)&addr, sizeof(addr)) == 0) { addr.sin_port = htons(port + 1); if (bind(*fd2, (struct sockaddr *)&addr, sizeof(addr)) == 0) { *port1 = port; *port2 = port + 1; return 0; } close(*fd1); } } close(*fd1); close(*fd2); return -1; } static void generate_session_id(char *session_id, size_t size) { if (size < 9) { return; } time_t timestamp = time(NULL); srand((unsigned int)timestamp); int random_part = rand() % 1000000; snprintf(session_id, size, "%02ld%06d", timestamp % 100, random_part); return; } /*处理客户端rtsp请求*/ void *doClientThd(void *arg) { signal(SIGINT, sig_handler); signal(SIGQUIT, sig_handler); signal(SIGKILL, sig_handler); struct thd_arg_st *arg_thd = (struct thd_arg_st *)arg; int client_sock_fd = arg_thd->client_sock_fd; char *client_ip = arg_thd->client_ip; int client_port = arg_thd->client_port; char method[40]; char url[100]; char url_tmp[100]; char filename[100]; char url_setup[100]; char track[1024]; char url_play[1024]; char local_ip[40]; char version[40]; int cseq; char *buf_ptr; char *buf_tmp; char *recv_buf = malloc(BUF_MAX_SIZE); char *send_buf = malloc(BUF_MAX_SIZE); char line[400]; // rtp_over_tcp int sig_0 = -1; int sig_1 = -1; int sig_2 = -1; int sig_3 = -1; int ture_of_rtp_tcp = 0; // rtp_over_udp int client_rtp_port = -1; int client_rtcp_port = -1; int client_rtp_port_1 = -1; int client_rtcp_port_1 = -1; int server_rtp_port = -1; int server_rtcp_port = -1; int server_rtp_port_1 = -1; int server_rtcp_port_1 = -1; int server_udp_socket_rtp_fd = -1; int server_udp_socket_rtcp_fd = -1; int server_udp_socket_rtp_1_fd = -1; int server_udp_socket_rtcp_1_fd = -1; char ch = '/'; int findflag = 0; char path[100]; memcpy(path, mp4Dir, strlen(mp4Dir)); path[strlen(mp4Dir)] = '\0'; char path_tmp[100]; memcpy(path_tmp, mp4Dir, strlen(mp4Dir)); path_tmp[strlen(mp4Dir)] = '\0'; int fd; char *realm = "simple-rtsp-server"; char nonce[33] = {0}; generate_nonce(nonce, sizeof(nonce)); char session_id[512]; generate_session_id(session_id, sizeof(session_id)); while (1) { int recv_len; recv_len = recv(client_sock_fd, recv_buf, BUF_MAX_SIZE, 0); if (recv_len <= 0) goto out; recv_buf[recv_len] = '\0'; printf("---------------C->S--------------\n"); printf("%s", recv_buf); buf_ptr = getLineFromBuf(recv_buf, line); buf_tmp = buf_ptr; if (sscanf(line, "%s %s %s\r\n", method, url, version) != 3) { printf("parse err\n"); goto out; } /*解析序列号*/ while (1) { buf_ptr = getLineFromBuf(buf_ptr, line); if (!strncmp(line, "CSeq:", strlen("CSeq:"))) { if (sscanf(line, "CSeq: %d\r\n", &cseq) != 1) { printf("parse err\n"); goto out; } break; } } if(auth == 1){ // authorization if(!strcmp(method, "SETUP") || !strcmp(method, "DESCRIBE") || !strcmp(method, "PLAY")){ AuthorizationInfo *auth_info = find_authorization(recv_buf); if(auth_info == NULL){ handleCmd_Unauthorized(send_buf, cseq, realm, nonce); printf("---------------S->C--------------\n"); printf("%s", send_buf); send(client_sock_fd, send_buf, strlen(send_buf), 0); continue; } else{ // printf("nonce:%s\n", auth_info->nonce); // printf("realm:%s\n", auth_info->realm); // printf("response:%s\n", auth_info->response); // printf("uri:%s\n", auth_info->uri); // printf("username:%s\n", auth_info->username); // 鉴权校验 int ret = authorization_verify(USER, PASSWORD, realm, nonce, auth_info->uri, method, auth_info->response); free_authorization_info(auth_info); if(ret < 0){ // 鉴权失败 goto out; } } } } /* 如果是SETUP,需要解析是RTP_OVER_TCP还是RTP_OVER_UDP模式 */ if (!strcmp(method, "SETUP")) { memset(url_setup, 0, sizeof(url_setup)); memset(track, 0, sizeof(track)); strcpy(url_setup, url); char *p = strrchr(url_setup, ch); memcpy(track, p + 1, strlen(p)); // video-track0 audio -track1 while (1) { buf_tmp = getLineFromBuf(buf_tmp, line); if (!buf_tmp) { break; } if (!strncmp(line, "Transport: RTP/AVP/TCP", strlen("Transport: RTP/AVP/TCP"))) { if (memcmp(track, "track0", 6) == 0) { sscanf(line, "Transport: RTP/AVP/TCP;unicast;interleaved=%d-%d\r\n", &sig_0, &sig_1); } else { sscanf(line, "Transport: RTP/AVP/TCP;unicast;interleaved=%d-%d\r\n", &sig_2, &sig_3); } ture_of_rtp_tcp = 1; break; } if (!strncmp(line, "Transport: RTP/AVP/UDP", strlen("Transport: RTP/AVP/UDP"))) { if (memcmp(track, "track0", 6) == 0) { sscanf(line, "Transport: RTP/AVP/UDP;unicast;client_port=%d-%d\r\n", &client_rtp_port, &client_rtcp_port); } else { sscanf(line, "Transport: RTP/AVP/UDP;unicast;client_port=%d-%d\r\n", &client_rtp_port_1, &client_rtcp_port_1); } break; } if (!strncmp(line, "Transport: RTP/AVP", strlen("Transport: RTP/AVP"))) { if (memcmp(track, "track0", 6) == 0) { sscanf(line, "Transport: RTP/AVP;unicast;client_port=%d-%d\r\n", &client_rtp_port, &client_rtcp_port); } else { sscanf(line, "Transport: RTP/AVP;unicast;client_port=%d-%d\r\n", &client_rtp_port_1, &client_rtcp_port_1); } break; } } } if (!strcmp(method, "OPTIONS")) { char *p = strrchr(url, ch); memcpy(filename, p + 1, strlen(p)); char *tmp = strcat(path_tmp, filename); findflag = 1; fd = open(tmp, O_RDONLY); if (fd < 0) // 请求的资源不存在返回404并关闭客户端文件描述符 { perror("failed"); handleCmd_404(send_buf, cseq); send(client_sock_fd, send_buf, strlen(send_buf), 0); goto out; } else { close(fd); if (handleCmd_OPTIONS(send_buf, cseq)) { printf("failed to handle options\n"); goto out; } } } else if (!strcmp(method, "DESCRIBE")) { if (findflag == 0) { char *p = strrchr(url, ch); memcpy(filename, p + 1, strlen(p)); char *tmp = strcat(path_tmp, filename); fd = open(tmp, O_RDONLY); if (fd < 0) // 请求的资源不存在返回404并关闭客户端文件描述符 { perror("failed"); handleCmd_404(send_buf, cseq); send(client_sock_fd, send_buf, strlen(send_buf), 0); goto out; } close(fd); findflag = 1; } char sdp[1024]; char localIp[100]; sscanf(url, "rtsp://%[^:]:", localIp); int ret = generateSDP(path_tmp, localIp, sdp, sizeof(sdp)); if (ret < 0) { // mp4文件有问题,或者视频不是H264/H265,音频不是AAC/PCMA handleCmd_500(send_buf, cseq); send(client_sock_fd, send_buf, strlen(send_buf), 0); goto out; } if (handleCmd_DESCRIBE(send_buf, cseq, url, sdp)) { printf("failed to handle describe\n"); goto out; } } else if (!strcmp(method, "SETUP") && ture_of_rtp_tcp == 0) // RTP_OVER_UDP { sscanf(url, "rtsp://%[^:]:", local_ip); if (memcmp(track, "track0", 6) == 0) { create_rtp_sockets(&server_udp_socket_rtp_fd, &server_udp_socket_rtcp_fd, &server_rtp_port, &server_rtp_port); handleCmd_SETUP_UDP(send_buf, cseq, client_rtp_port, server_rtp_port, session_id); } else { create_rtp_sockets(&server_udp_socket_rtp_1_fd, &server_udp_socket_rtcp_1_fd, &server_rtp_port_1, &server_rtp_port_1); handleCmd_SETUP_UDP(send_buf, cseq, client_rtp_port_1, server_rtp_port_1, session_id); } } else if (!strcmp(method, "SETUP") && ture_of_rtp_tcp == 1) // RTP_OVER_TCP { sscanf(url, "rtsp://%[^:]:", local_ip); if (memcmp(track, "track0", 6) == 0) { handleCmd_SETUP_TCP(send_buf, cseq, local_ip, client_ip, sig_0, session_id); } else { handleCmd_SETUP_TCP(send_buf, cseq, local_ip, client_ip, sig_2, session_id); } } else if (!strcmp(method, "PLAY")) { memset(url_play, 0, sizeof(url_play)); memset(track, 0, sizeof(track)); strcpy(url_play, url); if (handleCmd_PLAY(send_buf, cseq, url_play, session_id)) { printf("failed to handle play\n"); goto out; } } else { goto out; } printf("---------------S->C--------------\n"); printf("%s", send_buf); send(client_sock_fd, send_buf, strlen(send_buf), 0); if (!strcmp(method, "PLAY")) { struct timeval time_pre, time_now; gettimeofday(&time_pre, NULL); char *tmp = strcat(path, filename); int ret = addClient(tmp, client_sock_fd, sig_0, sig_2, ture_of_rtp_tcp, client_ip, client_rtp_port, client_rtp_port_1, server_udp_socket_rtp_fd, server_udp_socket_rtcp_fd, server_udp_socket_rtp_1_fd, server_udp_socket_rtcp_1_fd); if (ret < 0) goto out; int sum = getClientNum(); gettimeofday(&time_now, NULL); int time_handle = 1000 * (time_now.tv_sec - time_pre.tv_sec) + (time_now.tv_usec - time_pre.tv_usec) / 1000; printf("timeuse:%dms sum_client:%d\n\n", time_handle, sum); goto over; } } out: close(client_sock_fd); free(recv_buf); free(send_buf); free(arg); return NULL; over: free(recv_buf); free(send_buf); free(arg); return NULL; } int main(int argc, char *argv[]) { if(argc < 3){ printf("./rtsp_server auth(0-not authentication; 1-authentication) loop(0-not loop 1-loop)\n"); return -1; } auth = atoi(argv[1]); reloop_flag = atoi(argv[2]); int server_sock_fd; int ret; signal(SIGINT, sig_handler); signal(SIGQUIT, sig_handler); signal(SIGKILL, sig_handler); signal(SIGPIPE, SIG_IGN); signal(SIGFPE, SIG_IGN); server_sock_fd = createTcpSocket(); if (server_sock_fd < 0) { printf("failed to create tcp socket\n"); return -1; } ret = bindSocketAddr(server_sock_fd, SERVER_IP, SERVER_PORT); if (ret < 0) { printf("failed to bind addr\n"); return -1; } ret = listen(server_sock_fd, 100); if (ret < 0) { printf("failed to listen\n"); return -1; } moduleInit(); printf("rtsp://%s:%d/filename\n", SERVER_IP, SERVER_PORT); while (1) { int client_sock_fd; char client_ip[40]; int client_port; pthread_t tid; client_sock_fd = acceptClient(server_sock_fd, client_ip, &client_port); if (client_sock_fd < 0) { printf("failed to accept client\n"); return -1; } printf("###########accept client --> client_sock_fd:%d client ip:%s,client port:%d###########\n", client_sock_fd, client_ip, client_port); struct thd_arg_st *arg; arg = malloc(sizeof(struct thd_arg_st)); memcpy(arg->client_ip, client_ip, strlen(client_ip)); arg->client_port = client_port; arg->client_sock_fd = client_sock_fd; ret = pthread_create(&tid, NULL, doClientThd, (void *)arg); if (ret < 0) { perror("doClientThd pthread_create()"); } pthread_detach(tid); } moduleDel(); close(server_sock_fd); return 0; }
4.10 common.c 代码
#include "common.h" #include "md5.h" #include <arpa/inet.h> #include <assert.h> #include <errno.h> #include <fcntl.h> #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libavutil/log.h> #include <libavutil/time.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> int createTcpSocket() { int sockfd; int on = 1; sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) return -1; setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char *)&on, sizeof(on)); return sockfd; } int createUdpSocket() { int sockfd; int on = 1; sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) return -1; setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char *)&on, sizeof(on)); return sockfd; } int bindSocketAddr(int sockfd, const char *ip, int port) { struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(port); addr.sin_addr.s_addr = inet_addr(ip); if (bind(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr)) < 0) return -1; return 0; } int acceptClient(int sockfd, char *ip, int *port) { int clientfd; socklen_t len = 0; struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); len = sizeof(addr); clientfd = accept(sockfd, (struct sockaddr *)&addr, &len); if (clientfd < 0) { printf("accept err:%s\n", strerror(errno)); return -1; } strcpy(ip, inet_ntoa(addr.sin_addr)); *port = ntohs(addr.sin_port); return clientfd; } void rtpHeaderInit(struct RtpPacket *rtpPacket, uint8_t csrcLen, uint8_t extension, uint8_t padding, uint8_t version, uint8_t payloadType, uint8_t marker, uint16_t seq, uint32_t timestamp, uint32_t ssrc) { rtpPacket->rtpHeader.csrcLen = csrcLen; rtpPacket->rtpHeader.extension = extension; rtpPacket->rtpHeader.padding = padding; rtpPacket->rtpHeader.version = version; rtpPacket->rtpHeader.payloadType = payloadType; rtpPacket->rtpHeader.marker = marker; rtpPacket->rtpHeader.seq = seq; rtpPacket->rtpHeader.timestamp = timestamp; rtpPacket->rtpHeader.ssrc = ssrc; return; } char *getLineFromBuf(char *buf, char *line) { while (*buf != '\n') { *line = *buf; line++; buf++; } *line = '\n'; ++line; *line = '\0'; ++buf; return buf; } static char *extract_value(const char *source, const char *key) { const char *start = strstr(source, key); if (!start) { return NULL; } start += strlen(key) + 2; // 跳过 key=" const char *end = strchr(start, '"'); if (!end) { return NULL; } size_t len = end - start; char *value = (char *)malloc(len + 1); if (!value) { return NULL; } strncpy(value, start, len); value[len] = '\0'; return value; } AuthorizationInfo *find_authorization(const char *request) { const char *auth_start = strstr(request, "Authorization: "); if (!auth_start) { return NULL; // Authorization字段未找到 } auth_start += strlen("Authorization: "); const char *auth_end = strchr(auth_start, '\r'); if (!auth_end) { auth_end = strchr(auth_start, '\n'); } if (!auth_end) { return NULL; // 无法找到行尾 } char *auth_value = (char *)malloc(auth_end - auth_start + 1); if (!auth_value) { return NULL; // 内存分配失败 } strncpy(auth_value, auth_start, auth_end - auth_start); auth_value[auth_end - auth_start] = '\0'; AuthorizationInfo *auth_info = (AuthorizationInfo *)malloc(sizeof(AuthorizationInfo)); if (!auth_info) { free(auth_value); return NULL; // 内存分配失败 } auth_info->username = extract_value(auth_value, "username"); auth_info->realm = extract_value(auth_value, "realm"); auth_info->nonce = extract_value(auth_value, "nonce"); auth_info->uri = extract_value(auth_value, "uri"); auth_info->response = extract_value(auth_value, "response"); free(auth_value); return auth_info; } void free_authorization_info(AuthorizationInfo *auth_info) { if (auth_info) { free(auth_info->username); free(auth_info->realm); free(auth_info->nonce); free(auth_info->uri); free(auth_info->response); free(auth_info); } return; } // 生成随机字符串 static void generate_random_string(char *buf, int length) { static const char charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; for (size_t i = 0; i < length; i++) { buf[i] = charset[rand() % (sizeof(charset) - 1)]; } return; } // 生成nonce void generate_nonce(char *nonce, int length) { if (length < 1) { nonce[0] = '\0'; return; } memset(nonce, 0, length); srand((unsigned int)time(NULL)); char random_string[128] = {0}; generate_random_string(random_string, sizeof(random_string)); char timestamp[32]; snprintf(timestamp, sizeof(timestamp), "%ld", (long)time(NULL)); char combined[256] = {0}; snprintf(combined, sizeof(combined), "%s%s", random_string, timestamp); MD5_CTX md5; unsigned char decrypt[16]; MD5Init(&md5); MD5Update(&md5, combined, strlen(combined)); MD5Final(&md5, decrypt); for(int i = 0; i < 16; i++) { snprintf(&(nonce[i * 2]), 3, "%02x", decrypt[i]); } return; } int authorization_verify(char *username, char *password, char *realm, char *nonce, char *uri, char * method, char *response){ // md5(username:realm:password) unsigned char res1[16]; char res1_hex[33] = {0}; char buffer1[256] = {0}; sprintf(buffer1,"%s:%s:%s", username, realm, password); MD5_CTX md5_1; MD5Init(&md5_1); MD5Update(&md5_1, buffer1, strlen(buffer1)); MD5Final(&md5_1, res1); for(int i = 0; i < 16; i++) { snprintf(&(res1_hex[i * 2]), 3, "%02x", res1[i]); } // md5(public_method:url) unsigned char res2[16]; char res2_hex[33] = {0}; char buffer2[256] = {0}; sprintf(buffer2,"%s:%s", method, uri); MD5_CTX md5_2; MD5Init(&md5_2); MD5Update(&md5_2, buffer2, strlen(buffer2)); MD5Final(&md5_2, res2); for(int i = 0; i < 16; i++) { snprintf(&(res2_hex[i * 2]), 3, "%02x", res2[i]); } // md5( md5(username:realm:password):nonce:md5(public_method:url) ) unsigned char res[16]; char res_hex[33] = {0}; char buffer[512] = {0}; sprintf(buffer,"%s:%s:%s", res1_hex, nonce, res2_hex); MD5_CTX md5; MD5Init(&md5); MD5Update(&md5, buffer, strlen(buffer)); MD5Final(&md5, res); for(int i = 0; i < 16; i++) { snprintf(&(res_hex[i * 2]), 3, "%02x", res[i]); } // printf("res:%s response:%s\n", res_hex, response); if(strcmp(res_hex, response) == 0){ return 0; } return -1; } int handleCmd_Unauthorized(char *result, int cseq, char *realm, char *nonce){ sprintf(result, "RTSP/1.0 401 Unauthorized\r\n" "CSeq: %d\r\n" "WWW-Authenticate: Digest realm=\"%s\", nonce=\"%s\"\r\n" "\r\n", cseq, realm, nonce); return 0; } int handleCmd_OPTIONS(char *result, int cseq) { sprintf(result, "RTSP/1.0 200 OK\r\n" "CSeq: %d\r\n" "Public: OPTIONS, DESCRIBE, SETUP, PLAY\r\n" "\r\n", cseq); return 0; } int handleCmd_DESCRIBE(char *result, int cseq, char *url, char *sdp) { sprintf(result, "RTSP/1.0 200 OK\r\nCSeq: %d\r\n" "Content-Base: %s\r\n" "Content-type: application/sdp\r\n" "Content-length: %d\r\n\r\n" "%s", cseq, url, strlen(sdp), sdp); return 0; } int handleCmd_SETUP_TCP(char *result, int cseq, char *localIp, char *clientIp, int sig_0, char *session) { sprintf(result, "RTSP/1.0 200 OK\r\n" "CSeq: %d\r\n" "Transport: RTP/AVP/TCP;unicast;destination=%s;source=%s;interleaved=%d-%d\r\n" "Session: %s\r\n" "\r\n", cseq, clientIp, localIp, sig_0, sig_0 + 1, session); return 0; } int handleCmd_SETUP_UDP(char *result, int cseq, int clientRtpPort, int serverRtpPort, char *session) { sprintf(result, "RTSP/1.0 200 OK\r\n" "CSeq: %d\r\n" "Transport: RTP/AVP;unicast;client_port=%d-%d;server_port=%d-%d\r\n" "Session: %s\r\n" "\r\n", cseq, clientRtpPort, clientRtpPort + 1, serverRtpPort, serverRtpPort + 1, session); return 0; } int handleCmd_PLAY(char *result, int cseq, char *url_setup, char *session) { sprintf(result, "RTSP/1.0 200 OK\r\n" "CSeq: %d\r\n" "Range: npt=0.000-\r\n" "Session: %s; timeout=60\r\n\r\n", cseq, session); return 0; } int handleCmd_404(char *result, int cseq) { sprintf(result, "RTSP/1.0 404 NOT FOUND\r\n" "CSeq: %d\r\n" "\r\n", cseq); return 0; } int handleCmd_500(char *result, int cseq) { sprintf(result, "RTSP/1.0 500 SERVER ERROR\r\n" "CSeq: %d\r\n" "\r\n", cseq); return 0; } int check_media_info(const char *filename, MediaInfo *info) { AVFormatContext *format_ctx = NULL; int ret; if ((ret = avformat_open_input(&format_ctx, filename, NULL, NULL)) < 0) { fprintf(stderr, "Could not open source file %s\n", filename); return ret; } if ((ret = avformat_find_stream_info(format_ctx, NULL)) < 0) { fprintf(stderr, "Could not find stream information\n"); avformat_close_input(&format_ctx); return ret; } info->has_audio = 0; info->has_video = 0; info->is_video_h26x = 0; info->is_audio_aac_pcma = 0; info->audio_sample_rate = 0; info->audio_channels = 0; // info->vps = NULL; // info->sps = NULL; // info->pps = NULL; // info->vps_size = 0; // info->sps_size = 0; // info->pps_size = 0; for (unsigned int i = 0; i < format_ctx->nb_streams; i++) { AVStream *stream = format_ctx->streams[i]; AVCodecParameters *codecpar = stream->codecpar; if (codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { info->has_video = 1; if (codecpar->codec_id == AV_CODEC_ID_H264 || codecpar->codec_id == AV_CODEC_ID_H265 || codecpar->codec_id == AV_CODEC_ID_HEVC) { info->is_video_h26x = 1; if (codecpar->codec_id == AV_CODEC_ID_H264) info->video_type = H264; else info->video_type = H265; } } else if (codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { info->has_audio = 1; if (codecpar->codec_id == AV_CODEC_ID_AAC || codecpar->codec_id == AV_CODEC_ID_PCM_ALAW) { info->is_audio_aac_pcma = 1; info->audio_sample_rate = codecpar->sample_rate; info->audio_channels = codecpar->channels; info->profile = codecpar->profile; if(codecpar->codec_id == AV_CODEC_ID_AAC) info->audio_type = AAC; else info->audio_type = PCMA; } } } avformat_close_input(&format_ctx); return 0; } void free_media_info(MediaInfo *info) { // if (info->vps) // { // free(info->vps); // } // if (info->sps) // { // free(info->sps); // } // if (info->pps) // { // free(info->pps); // } return; } /* #define FF_PROFILE_AAC_MAIN 0 #define FF_PROFILE_AAC_LOW 1 #define FF_PROFILE_AAC_SSR 2 #define FF_PROFILE_AAC_LTP 3 #define FF_PROFILE_AAC_HE 4 #define FF_PROFILE_AAC_HE_V2 28 #define FF_PROFILE_AAC_LD 22 #define FF_PROFILE_AAC_ELD 38 #define FF_PROFILE_MPEG2_AAC_LOW 128 #define FF_PROFILE_MPEG2_AAC_HE 131 */ static int get_audio_obj_type(int aactype){ //AAC HE V2 = AAC LC + SBR + PS //AAV HE = AAC LC + SBR //所以无论是 AAC_HEv2 还是 AAC_HE 都是 AAC_LC switch(aactype){ case 0: case 2: case 3: return aactype + 1; case 1: case 4: case 28: return 2; default: return 2; } return 2; } static int get_sample_rate_index(int freq, int aactype){ int i = 0; int freq_arr[13] = { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350 }; //如果是 AAC HEv2 或 AAC HE, 则频率减半 if(aactype == 28 || aactype == 4){ freq /= 2; } for(i = 0; i < 13; i++){ if(freq == freq_arr[i]){ return i; } } return 4;//默认是44100 } static int get_channel_config(int channels, int aactype){ //如果是 AAC HEv2 通道数减半 if(aactype == 28){ return (channels / 2); } return channels; } int generateSDP(char *file, char *localIp, char *buffer, int buffer_len) { memset(buffer, 0, buffer_len); MediaInfo info; if (check_media_info(file, &info) != 0) { printf("server error\n"); free_media_info(&info); return -1; } if (info.has_video && !info.is_video_h26x) { printf("only support h264 h265\n"); free_media_info(&info); return -1; } if (info.has_audio && !info.is_audio_aac_pcma) { printf("only support aac pcma\n"); free_media_info(&info); return -1; } sprintf(buffer, "v=0\r\n" "o=- 9%ld 1 IN IP4 %s\r\n" "c=IN IP4 %s\r\n" "t=0 0\r\n" "a=control:*\r\n", time(NULL), localIp, localIp); if (info.has_video) { sprintf(buffer + strlen(buffer), "m=video 0 RTP/AVP %d\r\n" "a=rtpmap:%d %s/90000\r\n" //"a=fmtp:%d profile-level-id=42A01E; packetization-mode=1; sprop-parameter-sets=Z0IACpZTBYmI,aMljiA==\r\n" "a=fmtp:%d packetization-mode=1\r\n" "a=control:track0\r\n", RTP_PAYLOAD_TYPE_H26X, RTP_PAYLOAD_TYPE_H26X, info.video_type == H264 ? "H264" : "H265", RTP_PAYLOAD_TYPE_H26X); } if (info.has_audio) { if (info.audio_type == AAC) { char config[10] = {0}; int index = get_sample_rate_index(info.audio_sample_rate, info.profile); sprintf(config, "%02x%02x", (uint8_t)((get_audio_obj_type(info.profile)) << 3)|(index >> 1), (uint8_t)((index << 7)|(info.audio_channels << 3))); sprintf(buffer + strlen(buffer), "m=audio 0 RTP/AVP %d\r\n" "a=rtpmap:%d MPEG4-GENERIC/%u/%u\r\n" "a=fmtp:%d streamtype=5;profile-level-id=1;mode=AAC-hbr;config=%04u;sizelength=13;indexlength=3;indexdeltalength=3\r\n" "a=control:track1\r\n", RTP_PAYLOAD_TYPE_AAC, RTP_PAYLOAD_TYPE_AAC, info.audio_sample_rate, info.audio_channels, RTP_PAYLOAD_TYPE_AAC, atoi(config)); } else { sprintf(buffer + strlen(buffer), "m=audio 0 RTP/AVP %d\r\n" "a=rtpmap:%d PCMA/%u/%u\r\n" "a=control:track1\r\n", RTP_PAYLOAD_TYPE_PCMA, RTP_PAYLOAD_TYPE_PCMA, info.audio_sample_rate, info.audio_channels); } } free_media_info(&info); return 0; } // aactype = ffmpeg --> AVCodecParameters *codecpar->profile void adts_header(char *adts_header_buffer, int data_len, int aactype, int frequency, int channels){ int audio_object_type = get_audio_obj_type(aactype); int sampling_frequency_index = get_sample_rate_index(frequency, aactype); int channel_config = get_channel_config(channels, aactype); int adts_len = data_len + 7; adts_header_buffer[0] = 0xff; //syncword:0xfff 高8bits adts_header_buffer[1] = 0xf0; //syncword:0xfff 低4bits adts_header_buffer[1] |= (0 << 3); //MPEG Version:0 for MPEG-4,1 for MPEG-2 1bit adts_header_buffer[1] |= (0 << 1); //Layer:0 2bits adts_header_buffer[1] |= 1; //protection absent:1 1bit adts_header_buffer[2] = (audio_object_type - 1)<<6; //profile:audio_object_type - 1 2bits adts_header_buffer[2] |= (sampling_frequency_index & 0x0f)<<2; //sampling frequency index:sampling_frequency_index 4bits adts_header_buffer[2] |= (0 << 1); //private bit:0 1bit adts_header_buffer[2] |= (channel_config & 0x04)>>2; //channel configuration:channel_config 高1bit adts_header_buffer[3] = (channel_config & 0x03)<<6; //channel configuration:channel_config 低2bits adts_header_buffer[3] |= (0 << 5); //original:0 1bit adts_header_buffer[3] |= (0 << 4); //home:0 1bit adts_header_buffer[3] |= (0 << 3); //copyright id bit:0 1bit adts_header_buffer[3] |= (0 << 2); //copyright id start:0 1bit adts_header_buffer[3] |= ((adts_len & 0x1800) >> 11); //frame length:value 高2bits adts_header_buffer[4] = (uint8_t)((adts_len & 0x7f8) >> 3); //frame length:value 中间8bits adts_header_buffer[5] = (uint8_t)((adts_len & 0x7) << 5); //frame length:value 低3bits adts_header_buffer[5] |= 0x1f; //buffer fullness:0x7ff 高5bits adts_header_buffer[6] = 0xfc; return; }
五、总结
华为云正在举行其年度828 B2B企业节活动,期间提供了包括Flexus X实例在内的多种产品的优惠。对于那些对计算性能有较高要求,并且需要自行部署MySQL、Redis、Nginx等服务的用户来说,这次促销是一个很好的机会,建议有兴趣的朋友可以前往查看相关的优惠信息。
官网直达:https://activity.huaweicloud.com/828_promotion/index.html