做摄像头视频推流,主要是下面几个点:
1、RTSP推流信令;
这部分参考rtsp推流协议(参考),AI一通辅助,很快就给出了基础框架;关键参数,包括SPS/PPS/profile_id等需要通过H264视频解析出来。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <ctype.h> #include <netinet/in.h> #include <pthread.h> #include <errno.h> #include "net.h" #include "rtspservice.h" #include "rtsputils.h" #include "rtputils.h" #define RTSP_PORT 554 #define BUFFER_SIZE 1024 // RTSP 请求命令结构 const char *RTSP_OPTIONS = "OPTIONS rtsp://%s:%d/live/%s RTSP/1.0\r\nCSeq: 1\r\nUser-Agent: demo-rtsp-pusher-v0.1\r\n\r\n"; const char *GET_PARMETER = "GET_PARAMETER rtsp://%s:%d/live/%s RTSP/1.0\r\nCSeq: %d\r\nUser-Agent: demo-rtsp-pusher-v0.1\r\n\r\n"; const char *RTSP_ANNOUNCE = "ANNOUNCE rtsp://%s:%d/live/%s RTSP/1.0\r\nCSeq: 2\r\nContent-Type: application/sdp\r\nContent-Length: %d\r\nUser-Agent: demo-rtsp-pusher-v0.1\r\n\r\n%s"; const char *RTSP_SETUP = "SETUP rtsp://%s:%d/live/%s/streamid=0 RTSP/1.0\r\nCSeq: 3\r\nTransport: RTP/AVP;unicast;client_port=%d-%d\r\nSession: %s\r\nUser-Agent: demo-rtsp-pusher-v0.1\r\n\r\n"; const char *RTSP_SETUP_TCP = "SETUP rtsp://%s:%d/live/%s/streamid=0 RTSP/1.0\r\nCSeq: 3\r\nTransport: RTP/AVP/TCP;unicast;interleaved=0-1;mode=record\r\nSession: %s\r\nUser-Agent: demo-rtsp-pusher-v0.1\r\n\r\n"; const char *RTSP_RECORD = "RECORD rtsp://%s:%d/live/%s/streamid=0 RTSP/1.0\r\nCSeq: 4\r\nSession: %s\r\nUser-Agent: demo-rtsp-pusher-v0.1\r\n\r\n"; extern struct profileid_sps_pps psp; //存base64编码的profileid sps pps // 基本认证生成函数 extern void base64_encode2(char *in, const int in_len, char *out, int out_len); // 简单的基础认证生成函数 char *generate_basic_auth(const char *username, const char *password) { char auth_string[256]; snprintf(auth_string, sizeof(auth_string), "%s:%s", username, password); size_t len = strlen(auth_string); char base64_string[256] = "\0"; base64_encode2((unsigned char *)auth_string, len, base64_string, 256); char *auth_header = (char *)malloc(strlen("Basic ") + strlen(base64_string) + 1); snprintf(auth_header, strlen("Basic ") + strlen(base64_string) + 1, "Basic %s", base64_string); free(base64_string); return auth_header; } // 建立与 RTSP 服务器的 TCP 连接 int create_rtsp_connection(const char *ip, int port) { int sockfd; struct sockaddr_in server_addr; sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { perror("Socket creation failed"); return -1; } memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(port); if (inet_pton(AF_INET, ip, &server_addr.sin_addr) <= 0) { perror("Invalid address or address not supported"); close(sockfd); return -1; } if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { perror("Connection failed"); close(sockfd); return -1; } return sockfd; } // 发送并接收 RTSP 请求和响应 int send_rtsp_request(int sockfd, const char *request, char *response) { char buffer[BUFFER_SIZE]; ssize_t sent_bytes, recv_bytes; // 发送请求 sent_bytes = send(sockfd, request, strlen(request), 0); if (sent_bytes < 0) { perror("Send failed"); return -1; } //sleep(1); // 接收响应 recv_bytes = recv(sockfd, buffer, sizeof(buffer) - 1, 0); if (recv_bytes < 0) { perror("Recv failed"); return -1; } buffer[recv_bytes] = '\0'; // Null-terminate the received data strcpy(response, buffer); return 0; } // 解析响应中的 Session 字段 int extract_session_id(const char *response, char *session_id) { if (response == NULL || session_id== NULL){ return -1; } const char *session_start = strstr(response, "Session:"); if (!session_start) { return -1; // 没有找到 Session 字段 } // 跳过 "Session:" 部分并查找实际的会话 ID session_start += strlen("Session:"); // 跳过空格和换行符 while (isspace(*session_start)) { session_start++; } const char *session_end = strstr(session_start, "\r\n"); if (!session_end) { return -1; // 未找到会话 ID 的结束符 } // 提取会话 ID size_t session_len = session_end - session_start; strncpy(session_id, session_start, session_len); session_id[session_len] = '\0'; return 0; // 成功提取会话 ID } // 解析响应中的 tranport 字段 /* Transport: RTP/AVP/UDP;unicast;client_port=6970-6971;server_port=30288-30289;ssrc=00000000 */ int extract_setup_server_port(const char *response, int*server_port, int *server_rtcp_port, int *server_ssrc) { if (response == NULL || server_port== NULL){ return -1; } char transport[128] = "\0"; const char *session_start = strstr(response, "Transport:"); if (!session_start) { return -1; } session_start += strlen("Transport:"); while (isspace(*session_start)) { session_start++; } const char *session_end = strstr(session_start, "\r\n"); if (!session_end) { return -1; } size_t session_len = session_end - session_start; strncpy(transport, session_start, session_len); transport[session_len] = '\0'; char *pStr; int rtp_port = 0; int rtcp_port = 0; int ssrc = 0; if( (pStr = strstr(transport, "server_port")) ) { pStr = strstr(pStr, "="); sscanf(pStr + 1, "%d", &rtp_port); pStr = strstr(pStr, "-"); sscanf(pStr + 1, "%d", &rtcp_port); } if( (pStr = strstr(transport, "ssrc")) ) { pStr = strstr(pStr, "="); sscanf(pStr + 1, "%d", &ssrc); } *server_port = rtp_port; *server_rtcp_port = rtcp_port; *server_ssrc = ssrc; printf("rtp_port:%d, rtcp_port:%d, ssrc:%d\r\n", rtp_port, rtcp_port, ssrc); return 0; } int get_udp_port(udp_t *rtp_udp, udp_t *rtcp_udp){ static int sCurrentRTPPortToUse = 6970; static const int kMaxRTPPort = 36970; do{ int ret = udp_server_init(rtp_udp, sCurrentRTPPortToUse); if (ret == 0){ printf("get_udp_port rtp:%d\r\n", rtp_udp->port); break; } sCurrentRTPPortToUse++; if (sCurrentRTPPortToUse == kMaxRTPPort){ printf("sCurrentRTPPortToUse is:%d\r\n", sCurrentRTPPortToUse); return -1; } }while(1); sCurrentRTPPortToUse++; do{ int ret = udp_server_init(rtcp_udp, sCurrentRTPPortToUse); if (ret == 0){ printf("get_udp_port rtcp:%d\r\n", rtcp_udp->port); break; } sCurrentRTPPortToUse++; if (sCurrentRTPPortToUse == kMaxRTPPort){ printf("sCurrentRTPPortToUse is:%d\r\n", sCurrentRTPPortToUse); if (rtp_udp->port != 0){ udp_server_deinit(rtp_udp); } return -1; } }while(1); return 0; } static int recv_with_timeout(int socketfd){ fd_set rset; int len = 0; FD_ZERO(&rset); FD_SET(socketfd, &rset); int maxfdp1 = socketfd + 1; struct timeval tv = { 0 }; tv.tv_sec = 0; tv.tv_usec = 20000%1000000;//20ms? select(maxfdp1, &rset, NULL, NULL, &tv); if (!FD_ISSET(socketfd, &rset)){ //printf( "timeout no data received"); //sleep_ms(10); return -1; } return 0; } static uint8_t rtsp_push_client_thread_running_flag = 0; static char rtsp_push_server[255] = "\0"; static int rtsp_push_server_port = 554; static char rtsp_push_server_streamname[255] = "1234"; static int use_tcp_flag = 0; // 主函数,连接 RTSP 服务器并进行协议协商 int start_rtsp_push_client(const char *server_ip, int server_port) { if (server_ip == NULL || server_port < 0){ printf("parmeter error.\r\n"); return -1; } // 连接到 RTSP 服务器 int sockfd = create_rtsp_connection(server_ip, server_port); if (sockfd < 0) { return -1; } // 发送 OPTIONS 请求 char options_request[BUFFER_SIZE]; snprintf(options_request, sizeof(options_request), RTSP_OPTIONS, server_ip, server_port, rtsp_push_server_streamname); char response[BUFFER_SIZE]; printf("option send:%s\r\n", options_request); if (send_rtsp_request(sockfd, options_request, response) < 0) { close(sockfd); return -1; } printf("option response:%s\r\n", response); // 发送 ANNOUNCE 请求 char sdp_data_user_profile[1024] = "\0"; const char *sdp_default_template="v=0\n" "o=- 0 0 IN IP4 0.0.0.0\n" "s=RTSP Stream\n" "c=IN IP4 0.0.0.0\n" "t=0 0\n" "m=video 0 RTP/AVP 96\n" "a=rtpmap:96 H264/90000\n" "a=fmtp:96 packetization-mode=1; sprop-parameter-sets=%s,%s; profile-level-id=%s\n" "a=control:streamid=0\n"; sprintf(sdp_data_user_profile, sdp_default_template, psp.base64sps, psp.base64pps, psp.base64profileid); const char *sdp_data = "v=0\n" "o=- 0 0 IN IP4 0.0.0.0\n" "s=RTSP Stream\n" "c=IN IP4 0.0.0.0\n" "t=0 0\n" "m=video 0 RTP/AVP 96\n" "a=rtpmap:96 H264/90000\n" "a=fmtp:96 packetization-mode=1; sprop-parameter-sets=Z00AKp2oHgCJ+WbgICAoAAADAAgAAAMBlCA=,aO48gA==; profile-level-id=4D002A\n" "a=control:streamid=0\n"; const char *sdp_data_2 = "v=0\n" "o=- 0 0 IN IP4 0.0.0.0\n" "s=RTSP Stream\n" "c=IN IP4 0.0.0.0\n" "t=0 0\n" "m=video 0 RTP/AVP 96\n" "a=rtpmap:96 H264/90000\n" "a=fmtp:96 packetization-mode=1; sprop-parameter-sets=%s,%s; profile-level-id=4D002A\n" "a=control:streamid=0\n" "m=audio 0 RTP/AVP 97\n" "a=rtpmap:97 MPEG4-GENERIC/44100/1\n" "a=fmtp:97 profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3; config=1208\n" "a=control:streamid=1\n"; char announce_request[BUFFER_SIZE]; snprintf(announce_request, sizeof(announce_request), RTSP_ANNOUNCE, server_ip, server_port, rtsp_push_server_streamname, strlen(sdp_data_user_profile), sdp_data_user_profile); printf("announce setup:%s\r\n", announce_request); // 发送请求并处理响应 if (send_rtsp_request(sockfd, announce_request, response) < 0) { close(sockfd); return -1; } printf("announce response:%s\r\n", response); // 解析响应中的 Session 字段 char session_id[256] = "\0"; if (extract_session_id(response, session_id) == 0) { printf("Session ID: %s\n", session_id); } else { fprintf(stderr, "Failed to extract Session ID from response\n"); //close(sockfd); //return -1; } // 如果遇到 403 错误,则尝试鉴权 if (strstr(response, "403 Forbidden")) { fprintf(stderr, "Unsupported authentication type or no WWW-Authenticate header found\n"); close(sockfd); return -1; } //创建rtp_session RTP_session *rtp_s = (RTP_session *) calloc(1, sizeof(RTP_session)); RTP_transport transport; rtp_s->pause = 1; rtp_s->hndRtp = NULL; transport.type = RTP_no_transport; transport.rtsp_fd = sockfd; extern int RTP_get_port_pair(port_pair *pair); if (RTP_get_port_pair(&transport.u.udp.cli_ports) != ERR_NOERROR) { printf( "Error %s,%d\n", __FILE__, __LINE__); close(sockfd); return ERR_GENERIC; } printf("****transport.u.udp.cli_ports.RTP:%d, transport.u.udp.cli_ports.RTCP:%d\r\n", transport.u.udp.cli_ports.RTP, transport.u.udp.cli_ports.RTCP); // 发送 SETUP 请求 char setup_request[BUFFER_SIZE]; int s32Port = 0; //struct sockaddr_in server_addr; struct in_addr server_addr; inet_pton(AF_INET, server_ip, (void *)&server_addr); printf("server_ip:%s, x%x\r\n", server_ip, server_addr.s_addr); char str[32] = ""; inet_ntop(AF_INET, &server_addr.s_addr, str, sizeof(str)); printf("----tcp server: %s \r\n",str); if (use_tcp_flag == 0){ /* UDP */ snprintf(setup_request, sizeof(setup_request), RTSP_SETUP, server_ip, server_port, rtsp_push_server_streamname, transport.u.udp.cli_ports.RTP, transport.u.udp.cli_ports.RTCP,session_id); //transport.u.udp.cli_ports.RTP rtp_s->hndRtp = (struct _tagStRtpHandle*)RtpCreate((unsigned int)server_addr.s_addr, 0, _h264);// _h264 _h264nalu bindLocalRTPPort(rtp_s->hndRtp, transport.u.udp.cli_ports.RTP); transport.u.udp.is_multicast = 0; transport.type = RTP_rtp_avp; }else{ snprintf(setup_request, sizeof(setup_request), RTSP_SETUP_TCP, server_ip, server_port, rtsp_push_server_streamname, session_id); /* TCP */ rtp_s->hndRtp = (struct _tagStRtpHandle*)RtpCreate((unsigned int)&server_addr.s_addr, transport.u.tcp.interleaved.RTP, _h264); transport.rtp_fd = sockfd; transport.rtsp_fd = sockfd; if (rtp_s->hndRtp != NULL){ setUseTcpSocket((unsigned int)rtp_s->hndRtp, transport.rtsp_fd); } transport.type = RTP_rtp_avp_tcp; } printf( "--setup_request %s \n", setup_request); if (send_rtsp_request(sockfd, setup_request, response) < 0) { RtpDelete((unsigned int)rtp_s->hndRtp); free(rtp_s); close(sockfd); return -1; } printf("setup response:%s\r\n", response); //解析出 Session: 1SmcMbrIrgt0 if (extract_session_id(response, session_id) == 0) { printf("Session ID: %s\n", session_id); } else { fprintf(stderr, "Failed to extract Session ID from response\n"); RtpDelete((unsigned int)rtp_s->hndRtp); free(rtp_s); close(sockfd); return -1; } printf("--setup_request response:%s\r\n", response); int rtp_port; int rtcp_port; int ssrc; extract_setup_server_port(response, &rtp_port, &rtcp_port, &ssrc); //parse to server rtp port extern void updateRTPServerPort(void* hRtp, int s32Port); memcpy(&rtp_s->transport, &transport, sizeof(RTP_transport)); updateRTPServerPort((void*)rtp_s->hndRtp, rtp_port); // 发送 RECORD 请求 char record_request[BUFFER_SIZE]; snprintf(record_request, sizeof(record_request), RTSP_RECORD, server_ip, server_port,rtsp_push_server_streamname, session_id); if (send_rtsp_request(sockfd, record_request, response) < 0) { RtpDelete((unsigned int)rtp_s->hndRtp); free(rtp_s); close(sockfd); return -1; } printf("record_request response:%s\r\n", response); int sched_id = schedule_add(rtp_s); schedule_start(sched_id, NULL); //发送,直到退出 int seq = 5; while(rtsp_push_client_thread_running_flag){ snprintf(options_request, sizeof(options_request), GET_PARMETER, server_ip, server_port, rtsp_push_server_streamname, seq++); if (send_rtsp_request(sockfd, options_request, response) < 0) { printf("send faild:%s\r\n", response); break; } printf("response:%s\r\n", response); sleep(15); } schedule_stop(sched_id); // 关闭连接 close(sockfd); return 0; } void set_use_tcp_transport(int flag){ use_tcp_flag = flag; } int get_use_tcp_tranposrt(){ return use_tcp_flag; } void set_rtsp_push_server_streamname(const char *streamname){ if (streamname== NULL){ return; } snprintf(rtsp_push_server_streamname, sizeof(rtsp_push_server_streamname) - 1, "%s", streamname); printf("rtsp_push_server:%s, port:%d,stream_name:%s\r\n", rtsp_push_server, rtsp_push_server_port, rtsp_push_server_streamname); } void set_rtsp_push_server_info(const char * server_ip, int server_port){ if (server_ip== NULL){ return; } if (strlen(server_ip) == 0 || server_port <= 0){ return NULL; } snprintf(rtsp_push_server, sizeof(rtsp_push_server) - 1, "%s", server_ip); printf("rtsp_push_server:%s, port:%d\r\n", rtsp_push_server, server_port); rtsp_push_server_port = server_port; } void* rtsp_push_client_thread(void *arg){ printf("--rtsp_push_client_thread rtsp_push_server:%s, port:%d\r\n", rtsp_push_server, rtsp_push_server_port); if (strlen(rtsp_push_server) == 0 || rtsp_push_server_port <= 0){ return NULL; } rtsp_push_client_thread_running_flag = 1; printf("rtsp_push_client_thread rtsp_push_server:%s, port:%d\r\n", rtsp_push_server, rtsp_push_server_port); start_rtsp_push_client(rtsp_push_server, rtsp_push_server_port); return NULL; } void start_rtsp_push_client_in_thread(){ printf("--start_rtsp_push_client_in_thread rtsp_push_server:%s, port:%d\r\n", rtsp_push_server, rtsp_push_server_port); pthread_t thread_id; rtsp_push_client_thread_running_flag = 1; pthread_create(&thread_id, NULL, rtsp_push_client_thread, (void *)NULL); pthread_detach(thread_id); } void stop_rtsp_push_client(){ rtsp_push_client_thread_running_flag = 0; }
编译完成后,将librkadk.so拷贝到 /oem/usr/lib/路径下
rkadk_rtsp_test拷贝到1106开发板上,手动运行:
chmod +x ./rkadk_rtsp_test
推流命令 ./rkadk_rtsp_test tcp 118.25.219.130 8554 xxxxx
播放地址:rtsp://118.25.219.130:8554/live/xxxxx
不到1s的延时,推流到腾讯云服务器。
2、摄像头数据对接;
这部分同之前1106的视频对接过程。
3、RTP包组包和发送,支持UDP和TCP;
这部分同之前1106使用librtsp的集成。
4、腾讯云服务器配置,开554转发TCP端口,UDP的端口范围,考虑到UDP丢包,尽量使用TCP对流。
#该范围同时限制rtsp服务器udp端口范围
port_range=30000-35000
题外话:
这个需求是咸鱼同学提出来的,确实是有这样的需求。
-------------------广告线---------------
项目、合作,欢迎勾搭,邮箱:promall@qq.com
本文为呱牛笔记原创文章,转载无需和我联系,但请注明来自呱牛笔记 ,it3q.com