当前位置: 首页 > news >正文

828华为云征文|采用Flexus云服务器X实例部署RTSP直播服务器

一、前言

这篇文章讲解: 采用华为云最新推出的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/

image-20240905140332361

在官网首页的轮播图里可以看到,有Flexus云服务器的宣传。这是华为云匠心打造的下一代跃级产品,面向中低负载场景,性能倍增、体验跃级的服务器。

2.2 选购服务器

在产品页面,也可以看到Flexus云服务的选项,点击进去选购服务器。

image-20240905140614881

链接:https://www.huaweicloud.com/product/flexus.html

image-20240905140518001

在选购页面可以看到服务器推广器件,1年36块钱。 每个月的流量是100G,对于一些访问量不高的服务器或者测试用是非常合适的。

image-20240905140827022

当前我要选择的服务器是:Flexus云服务器X实例 ,点击Flexus系列产品,选择X实例。Flexus云服务器X实例符合:柔性算力,六倍性能,旗舰体验,覆盖高科技、零售、金融、游戏等行业大多数通用工作负载场景。

image-20240905142342028

2.3 选择服务器区域

针对时延敏感型业务请选择靠近您业务的区域,以降低网络时延,提高访问速度;针对和存量云产品有内网互通需求的业务,请选择和存量产品相同的区域。

image-20240905142546892

2.4 选择服务器规格

image-20240905143518832

2.5 选择系统镜像

我这选择ubuntu系统,用来搭建服务器。这个根据自己的情况选择,自己适合那一种就选择哪一种。

image-20240905160440609

2.6 选择存储盘

我选择150G大小。

image-20240905154238881

2.7 配置密码

设置好服务器的名字(如果你有多个服务器,为了自己好区别)和系统的登录密码。

image-20240905153958099

2.8 配置云备份

云备份这个不买。有需要自己可以购买。

image-20240905154322590

2.9 确认配置

image-20240905160511662

2.10 立即购买

image-20240905160537231

购买成功。

image-20240905160626605

创建成功之后,邮箱会收到提示的。

image-20240905160724142

2.10 后台控制台

链接:https://console.huaweicloud.com/ecm

在控制台可以看到服务器的详情。

image-20240905161142694

三、服务器登录

3.1 查看服务器的详情

点击服务器的名称,可以进去到详情页面。

image-20240905161621249

image-20240905161739687

3.2 远程登录

image-20240905161848020

填入设置好的密码。

image-20240905161919502

登录成功。

image-20240905161948036

3.9 采用FinalShell登录

自带的在浏览器里运行,每次需要打开浏览器,文件也不方便上传下载。

所以,这里开发阶段,我采用的 FinalShell登录到服务器。

新建SSH连接,输入连接信息。

image-20240905162205597

登录成功。

image-20240905162444451

接下来就可以进行开发了。

四、搭建RTSP流媒体服务器

4.1 simple-rtsp-server库

simple-rtsp-server依赖ffmpeg,版本要求>=4.x。

GitHub仓库地址:https://github.com/BreakingY/simple-rtsp-server

image-20240905163846205

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

安装过程中:

image-20240905163954528

安装完成。

image-20240905164128024

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.bz2tar xjvf ffmpeg-4.0.5.tar.bz2cd ffmpeg-4.0.5

image-20240905164424466

编译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/libmakemake install

4.5 simple-rtsp-server下载编译

git clone https://github.com/BreakingY/simple-rtsp-server.gitcd simple-rtsp-servermkdir buildcd buildcmake ..make -j4

image-20240905165233540

4.6 运行服务器

cp -r ../mp4path .
./rtsp_server loop 0

运行效果:

image-20240905170225684

4.7 开放规则

要记得把服务器的 端口开放出来, 不然无法访问。

image-20240905170441508

image-20240905170457783

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播放器播放的效果:

image-20240905170542053

播放过程中,在服务器上会打印出每个过程的详情。

image-20240906104406331

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_tcpint sig_0 = -1;int sig_1 = -1;int sig_2 = -1;int sig_3 = -1;int ture_of_rtp_tcp = 0;// rtp_over_udpint 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){// authorizationif(!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 -track1while (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/PCMAhandleCmd_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;elseinfo->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;elseinfo->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_LCswitch(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                          高8bitsadts_header_buffer[1] = 0xf0;         //syncword:0xfff                          低4bitsadts_header_buffer[1] |= (0 << 3);    //MPEG Version:0 for MPEG-4,1 for MPEG-2  1bitadts_header_buffer[1] |= (0 << 1);    //Layer:0                                 2bitsadts_header_buffer[1] |= 1;           //protection absent:1                     1bitadts_header_buffer[2] = (audio_object_type - 1)<<6;            //profile:audio_object_type - 1                      2bitsadts_header_buffer[2] |= (sampling_frequency_index & 0x0f)<<2; //sampling frequency index:sampling_frequency_index  4bitsadts_header_buffer[2] |= (0 << 1);                             //private bit:0                                      1bitadts_header_buffer[2] |= (channel_config & 0x04)>>2;           //channel configuration:channel_config               高1bitadts_header_buffer[3] = (channel_config & 0x03)<<6;     //channel configuration:channel_config      低2bitsadts_header_buffer[3] |= (0 << 5);                      //original:0                               1bitadts_header_buffer[3] |= (0 << 4);                      //home:0                                   1bitadts_header_buffer[3] |= (0 << 3);                      //copyright id bit:0                       1bitadts_header_buffer[3] |= (0 << 2);                      //copyright id start:0                     1bitadts_header_buffer[3] |= ((adts_len & 0x1800) >> 11);           //frame length:value   高2bitsadts_header_buffer[4] = (uint8_t)((adts_len & 0x7f8) >> 3);     //frame length:value    中间8bitsadts_header_buffer[5] = (uint8_t)((adts_len & 0x7) << 5);       //frame length:value    低3bitsadts_header_buffer[5] |= 0x1f;                                 //buffer fullness:0x7ff 高5bitsadts_header_buffer[6] = 0xfc;return;
}

五、总结

华为云正在举行其年度828 B2B企业节活动,期间提供了包括Flexus X实例在内的多种产品的优惠。对于那些对计算性能有较高要求,并且需要自行部署MySQL、Redis、Nginx等服务的用户来说,这次促销是一个很好的机会,建议有兴趣的朋友可以前往查看相关的优惠信息。

官网直达:https://activity.huaweicloud.com/828_promotion/index.html

image-20240906111030734

相关文章:

828华为云征文|采用Flexus云服务器X实例部署RTSP直播服务器

一、前言 这篇文章讲解&#xff1a; 采用华为云最新推出的Flexus云服务器X实例搭建RTSP服务器&#xff0c;完成视频直播需求。 随着实时视频流传输需求的增长&#xff0c;RTSP&#xff08;实时流协议&#xff09;服务器成为了许多视频监控、直播和多媒体应用的核心组件。在当…...

Spring Cloud Gateway(二)

Spring Cloud Gateway&#xff08;二&#xff09; 文章目录 Spring Cloud Gateway&#xff08;二&#xff09;Gateway工作原理为什么使用API网关高并发Gateway性能优化 Gateway工作原理 Spring Cloud Gateway旨在为微服务架构提供简单、有效并且统一的API路由管理方式。它不仅…...

docker 简易入门

# docker 简易入门 docker由几个组成部分 docker client: 即 docker 命令行工具 docker host: 宿主机&#xff0c;docker daemon 的运行环境服务器 docker daemon: docker 的守护进程&#xff0c;docker client 通过命令行与 docker daemon 交互 container: 最小型的一个操…...

【看雪-注册安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞…...

记录一个前端学习小组的收集的模版

问题1&#xff1a;输入“您的姓名”&#xff0c;选择“短答案”作为问题类型。问题2&#xff1a;输入“您是否愿意继续参加前端学习小组&#xff1f;”&#xff0c;选择“单选”作为问题类型&#xff0c;并添加选项“是”和“否”。问题3&#xff1a;输入“如果您选择‘是’&am…...

Rk3588 Android12 AIDL 开发

AIDL (Android Interface Definition Language) 和 HIDL (HAL Interface Definition Language) 都是 Android 系统中用于定义接口的工具&#xff0c;但它们有不同的用途和特性。 AIDL (Android Interface Definition Language) 用途&#xff1a; 主要用于应用程序之间的进程间…...

两个长整数字符串求和(不允许使用ES6+)

两个长整数字符串求和(不允许使用ES6), 面试手撸代码遇到到这个问题 1. 实现方式第一种 // 短整数字符串前边补 0; num需要补 0 的短整数字符串, len 长整数字符串的长度 function fillZero (num, len) {let str num.toString();if (str.length < len) {str 0.repeat(…...

11 Java 方法引用、异常处理、Java接口之函数式编程(接口知识补充Function<T,R>、BiFunction<T, U, R>和自定义泛型接口)

文章目录 前言一、Java接口之函数式编程 --- 接口知识补充1 Function<T,R>泛型接口2 BiFunction<T, U, R>泛型接口3 自定义泛型函数式编程接口4 使用lambda表达式、方法引用进行函数式编程二、方法引用1 方法引用初体验(以Array.sort()方法为例)(1)什么是方法引…...

深入探索 Go 语言的编译器与垃圾回收机制

Go 编译器 Go 编译器是通过 go 工具执行的&#xff0c;这个工具的功能不仅仅是生成可执行文件。你可以使用 go tool compile 命令来编译一个 Go 源文件。这个操作将生成一个目标文件&#xff0c;也就是 .o 后缀的文件。以下是在 macOS Mojave 系统上执行的命令和结果展示&…...

2024国赛数学建模-模拟火算法(MATLAB 实现)

模拟退火算法 1.1 算法原理 模拟退火算法的基本思想是从一给定解开始 ,从邻域 中随机产生另一个解 ,接受 Metropolis准则允许目标函数在 有限范围内变坏 ,它由一控制参数 t决定 ,其作用类似于物 理过程中的温度 T,对于控制参数的每一取值 ,算法持续进 行“产生 —判断 —接受…...

YOLOv8 只检测人 只画框不要标签

参考了这个&#xff1a;YOLOv8只检测人&#xff08;或其他一种或者多种类别&#xff09;_yolov8只检测指定类别-CSDN博客 1. 只检测人&#xff1a;predict的时候指定参数classes[0] 2. 只画框不要标签&#xff1a;plot的时候传入labelsFalse 3. 标签中去掉置信度&#xff1a…...

如何将网络安全防范游戏化

组织对威胁的准备和恢复能力跟不上网络犯罪分子的进步。 一些首席执行官仍然认为网络安全需要偶尔干预&#xff0c;而不是持续关注。 但对于许多公司来说&#xff0c;情况并非如此&#xff1b;网络威胁准备需要协调一致的培训工作&#xff0c;因此网络安全团队在攻击发生时已…...

Qt QGraphicsView实现图片放缩、鼠标拖动移动、鼠标点位置放大缩小_图片查看

QtQGraphicsView实现图片放缩、鼠标拖动移动、鼠标点位置放大缩小 头文件&#xff1a; #ifndef TIMGWIDGET_H #define TIMGWIDGET_H#include <QGraphicsItem> #include <QMainWindow> #include <QObject> #include <QWidget>// class TImgWidget : pu…...

Percona Toolkit 神器全攻略(复制类)

Percona Toolkit 神器全攻略&#xff08;复制类&#xff09; Percona Toolkit 神器全攻略系列共八篇&#xff0c;前文回顾&#xff1a; 前文回顾Percona Toolkit 神器全攻略Percona Toolkit 神器全攻略&#xff08;实用类&#xff09;Percona Toolkit 神器全攻略&#xff08;配…...

SQLite3 数据类型深入全面讲解

SQLite3&#xff0c;作为一款轻量级的数据库管理系统&#xff0c;在数据存储方面展现出了其独特的魅力。它不仅支持标准的SQL语法&#xff0c;还提供了丰富的数据类型供开发者选择。这些数据类型不仅涵盖了基本的数值和文本类型&#xff0c;还包括了日期时间、二进制数据等复杂…...

Python高效实现Trie(前缀树)及其插入和查找操作

Python高效实现Trie(前缀树)及其插入和查找操作 在Python面试中,考官通常会关注候选人的编程能力、问题解决能力以及对Python语言特性的理解。Trie(前缀树)是一种高效的数据结构,广泛应用于字符串处理、自动补全、拼写检查等场景。本文将详细介绍如何实现一个Trie,并提…...

傅里叶变换家族

禹晶、肖创柏、廖庆敏《数字图像处理&#xff08;面向新工科的电工电子信息基础课程系列教材&#xff09;》 禹晶、肖创柏、廖庆敏《数字图像处理》资源二维码...

深度学习——强化学习算法介绍

强化学习算法介绍 强化学习讨论的问题是一个智能体(agent) 怎么在一个复杂不确定的环境(environment)里面去极大化它能获得的奖励。 强化学习和监督学习 强化学习有这个试错探索(trial-and-error exploration)&#xff0c;它需要通过探索环境来获取对环境的理解。强化学习 ag…...

轴承知识大全,详细介绍(附3D图纸免费下载)

轴承一般由内圈、外圈、滚动体和保持架组成。对于密封轴承&#xff0c;再加上润滑剂和密封圈&#xff08;或防尘盖&#xff09;。这就是轴承的全部组成。 根据轴承使用的工作状况来选用不同类型的轴承&#xff0c;才能更好的发挥轴承的功能&#xff0c;并延长轴承的使用寿命。我…...

【PyTorch】基础环境如何打开

前期安装可以基于这个视频&#xff0c;本文是为了给自己存档如何打开pycharm和jupyter notebookPyTorch深度学习快速入门教程&#xff08;绝对通俗易懂&#xff01;&#xff09;【小土堆】_哔哩哔哩_bilibili Pycharm 配置 新建项目的时候选择解释器pytorch-gpu即可。 Jupyte…...

QMC5883L的驱动

简介 本篇文章的代码已经上传到了github上面&#xff0c;开源代码 作为一个电子罗盘模块&#xff0c;我们可以通过I2C从中获取偏航角yaw&#xff0c;相对于六轴陀螺仪的yaw&#xff0c;qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...

Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务

通过akshare库&#xff0c;获取股票数据&#xff0c;并生成TabPFN这个模型 可以识别、处理的格式&#xff0c;写一个完整的预处理示例&#xff0c;并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务&#xff0c;进行预测并输…...

基于当前项目通过npm包形式暴露公共组件

1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹&#xff0c;并新增内容 3.创建package文件夹...

【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)

升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点&#xff0c;但无自动故障转移能力&#xff0c;Master宕机后需人工切换&#xff0c;期间消息可能无法读取。Slave仅存储数据&#xff0c;无法主动升级为Master响应请求&#xff…...

基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解

JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用&#xff0c;结合SQLite数据库实现联系人管理功能&#xff0c;并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能&#xff0c;同时可以最小化到系统…...

使用Spring AI和MCP协议构建图片搜索服务

目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式&#xff08;本地调用&#xff09; SSE模式&#xff08;远程调用&#xff09; 4. 注册工具提…...

Python+ZeroMQ实战:智能车辆状态监控与模拟模式自动切换

目录 关键点 技术实现1 技术实现2 摘要&#xff1a; 本文将介绍如何利用Python和ZeroMQ消息队列构建一个智能车辆状态监控系统。系统能够根据时间策略自动切换驾驶模式&#xff08;自动驾驶、人工驾驶、远程驾驶、主动安全&#xff09;&#xff0c;并通过实时消息推送更新车…...

代码规范和架构【立芯理论一】(2025.06.08)

1、代码规范的目标 代码简洁精炼、美观&#xff0c;可持续性好高效率高复用&#xff0c;可移植性好高内聚&#xff0c;低耦合没有冗余规范性&#xff0c;代码有规可循&#xff0c;可以看出自己当时的思考过程特殊排版&#xff0c;特殊语法&#xff0c;特殊指令&#xff0c;必须…...

Python Einops库:深度学习中的张量操作革命

Einops&#xff08;爱因斯坦操作库&#xff09;就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库&#xff0c;用类似自然语言的表达式替代了晦涩的API调用&#xff0c;彻底改变了深度学习工程…...

数据库——redis

一、Redis 介绍 1. 概述 Redis&#xff08;Remote Dictionary Server&#xff09;是一个开源的、高性能的内存键值数据库系统&#xff0c;具有以下核心特点&#xff1a; 内存存储架构&#xff1a;数据主要存储在内存中&#xff0c;提供微秒级的读写响应 多数据结构支持&…...