rv1106基于librtsp做摄像头视频推流


做摄像头视频推流,主要是下面几个点:

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

请先登录后发表评论
  • 最新评论
  • 总共0条评论