做摄像头视频推流,主要是下面几个点:
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
