PJSIP实现转发RTSP流视频


关键点:

该功能实现,主要需要考虑RTSP取摄像头视频流,拆RTP包,组H264帧,通过PJSIP的视频通道转发;这个过程中,涉及到RTP通道保活,RTSP通道保活;调试时间多耗费在对摄像头返回的RTP数据包的拆解和重新组H264帧上面。


1、RTSP信令通道;

curl支持rtsp的客户端取流,demo实现也是很简单的,主要有几个点,一是用户鉴权,二是RTSP通道保活;


用户鉴权:参考 https://github.com/lminiero/rtsp-auth-test/

       /* Ugly workaround needed after curl 7.66 */
        curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_RTSP);
	curl_easy_setopt(curl, CURLOPT_HTTP09_ALLOWED, 1L);
    	my_curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
    	my_curl_easy_setopt(curl, CURLOPT_USERPWD, username_password);


信令通道保活,通过VLC抓包发现,VLC每隔8s发送一个GET_PARAMETER 消息

GET_PARAMETER rtsp://192.168.16.210/live/substream/ RTSP/1.0

CSeq: 8

User-Agent: LibVLC/2.2.1 (LIVE555 Streaming Media v2014.07.25)

Session: D8C225A1


需要解析DESCRIBE/SETUP返回的信令中的几个关键信息:profile-level-id,sprop-parameter-sets, 服务器的RTP端口

a=fmtp:96 profile-level-id=420029; packetization-mode=1; sprop-parameter-sets=J01gNImNUFAX/LCAAAADAIAAABkHixLc,KO4PyA==
            	

Transport: RTP/AVP;unicast;client_port=6970-6971;server_port=8236-8237;ssrc=3fa5beb6;mode="play"



每隔8s发送一个Get_PARAMETER

static void rtsp_get_parmeter(CURL *curl, const char *uri) {
    CURLcode ret = CURLE_OK;

    printf("\nRTSP: rtsp_get_parmeter %s\n", uri);

    my_curl_easy_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_GET_PARAMETER);
    my_curl_easy_perform(curl);

    return;
}


碰到的问题,windows版本调试过程中,发现curl的请求socket端口在DESCRIBE信令的时候发生了变化,导致服务器返回401,要求校验用户名密码,播放失败,但linux版本不存在这个问题。


2、RTSP的媒体通道RTP/RTCP;

动态获取RTP/RTCP端口:

typedef struct {
    int sock;
    int port;
    struct sockaddr_in addr;
} udp_t;

int get_udp_port(udp_t *rtp_udp, udp_t *rtcp_udp){

    static int sCurrentRTPPortToUse = 6970;
    static const int kMinRTPPort = 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 void send_stun_packet(int sock, char *dst_ip, int dst_port){

    struct sockaddr_in b_addr; 
    b_addr.sin_family=AF_INET;
    b_addr.sin_addr.s_addr=inet_addr(dst_ip); 
    b_addr.sin_port=htons(dst_port);  
    
    int b_addr_len=sizeof(b_addr);
    uint8_t temp_code[] = {0xce,0xfe, 0xed, 0xfe};
    printf("send_stun_packet,dst_ip:%s, port:%d.\r\n", dst_ip, dst_port);

    int send_len = sendto(sock, temp_code, sizeof(temp_code), 0,(struct sockaddr *)&b_addr, b_addr_len);
    if (send_len < 0) {
        printf("\n\rsend error.\n\r"); 
    }
    return;
}

RTP重新组包:

关键的点,就是将H264的RTP封包重新组成完整的H264帧,每一个不同的帧类型包括如下,每个帧类型前面都要补充0x00,0x00,0x00,0x01,否则不能解码。

0x67: SPS 
0x68: PPS 
0x65: IDR   
0x61: non-IDR Slice
0x01: B Slice
0x06: SEI
0x09: AU Delimiter
void put_frame(uint8_t *data, int len){
	uint8_t sync_bytes[] = {0, 0, 0, 1};
 
	if (data == NULL){
		return;
	} 
	int seq = (data[2] << 8) | data[3];
	int pt = data[1]&0x7f;
	int marker = ((data[1]&0x80) >> 7);
	uint8_t *payload = &data[12];
	data += 12;
	len -=12; 
	uint8_t nalu_hdr = *data;
	int nalu_type = data[0] & 0x1f;
	int old_type = nalu_type;
	if (last_rtp_frame_cache_len == 0){
		printf_data(data, 7);
		printf("[1]seq:%d, old_type:%d, nalu_type:%d marker:%d, len:%d,last_len:%d\r\n", seq,old_type, nalu_type, marker, len, last_rtp_frame_cache_len);
	}
	if (nalu_type == 28) { // 0x1c FU-A
		int start = (*(data + 1) & 0x80);
		int end = (*(data + 1) & 0x40);
		nalu_type = (*(data + 1) & 0x1f);
		if (start){
			uint8_t nalu_idc = (nalu_hdr & 0x60) >> 5;
			//printf_data(data, 7);
			nalu_type |= (nalu_idc << 5);  

			memcpy(rtp_frame_cache+last_rtp_frame_cache_len, sync_bytes, 4);
			last_rtp_frame_cache_len += 4;
			memcpy(rtp_frame_cache +last_rtp_frame_cache_len, &nalu_type, 1);
			last_rtp_frame_cache_len += 1;

			printf_data(rtp_frame_cache, last_rtp_frame_cache_len);
		}
		len -= 2;
		payload += 2;
 
		printf("nalu_type:%2x,start:%d, end:%d,last_rtp_frame_cache_len:%d \r\n", nalu_type, start, end,last_rtp_frame_cache_len);
		
		if (last_rtp_frame_cache_len + len >= rtp_frame_cache_max_len){ 
		   rtp_frame_cache = (uint8_t *)realloc(rtp_frame_cache, 10240);
		   rtp_frame_cache_max_len += 10240;
		}
		//拷贝payload
		memcpy((void *)(rtp_frame_cache+last_rtp_frame_cache_len), payload, len); 
		last_rtp_frame_cache_len += len;
	}else if (nalu_type == 24) { // 0x18 STAP-A
		nalu_type = *(data + 1) & 0x1f;
		len -= 3;
		payload += 3;
		nalu_hdr = *data;
		nalu_type = nalu_hdr & 0x1f;
		printf("[2]nalu_type:%2x, \r\n");

		memcpy(rtp_frame_cache+last_rtp_frame_cache_len, sync_bytes, 4);
		last_rtp_frame_cache_len += 4; 

		if (last_rtp_frame_cache_len + len >= rtp_frame_cache_max_len){ 
		   rtp_frame_cache = (uint8_t *)realloc(rtp_frame_cache, 10240);
		   rtp_frame_cache_max_len += 10240;
		}
		//拷贝payload
		memcpy((void *)(rtp_frame_cache+last_rtp_frame_cache_len), payload, len); 
		last_rtp_frame_cache_len += len;
	} else { 
		int pps_pos = 0; 
		printf("[2]seq:%d, data:%02x, nalu_type:%d marker:%d, len:%d,last_len:%d\r\n", seq,data[0], nalu_type, marker, len, last_rtp_frame_cache_len);

		if (nalu_type == 7||nalu_type == 8){
			memcpy((void *)(rtp_frame_cache+last_rtp_frame_cache_len), sync_bytes, 4);
			last_rtp_frame_cache_len += 4;  
			//找0x68.补上header
			if (nalu_type == 7){
				memcpy((void *)(rtp_frame_cache+last_rtp_frame_cache_len), rtsp_server_sps, rtsp_server_sps_len);
				last_rtp_frame_cache_len += rtsp_server_sps_len;
				if (len >= (rtsp_server_sps_len + rtsp_server_pps_len)){ 
					memcpy((void *)(rtp_frame_cache+last_rtp_frame_cache_len), sync_bytes, 4);
					last_rtp_frame_cache_len += 4; 
					memcpy((void *)(rtp_frame_cache+last_rtp_frame_cache_len), rtsp_server_pps, rtsp_server_pps_len);
					last_rtp_frame_cache_len += rtsp_server_pps_len;
					int reserved_len = len - rtsp_server_sps_len - rtsp_server_pps_len;
					if (reserved_len > 0){
						payload += rtsp_server_pps_len + rtsp_server_sps_len;
						printf("[2]reserved_len:%2x, \r\n");
						memcpy((void *)(rtp_frame_cache+last_rtp_frame_cache_len), payload, reserved_len);
						last_rtp_frame_cache_len += reserved_len;
					}
				}
			}else { 
				memcpy((void *)(rtp_frame_cache+last_rtp_frame_cache_len), rtsp_server_pps, rtsp_server_pps_len);
				last_rtp_frame_cache_len += rtsp_server_pps_len;
				int reserved_len = len - rtsp_server_pps_len;
				if (reserved_len > 0){
					payload += rtsp_server_pps_len + rtsp_server_sps_len;
					printf("[2]reserved_len:%2x, \r\n");
					memcpy((void *)(rtp_frame_cache+last_rtp_frame_cache_len), payload, reserved_len);
					last_rtp_frame_cache_len += reserved_len;
				}
			}

		}else{
			if (last_rtp_frame_cache_len == 0){
				memcpy((void *)(rtp_frame_cache+last_rtp_frame_cache_len), sync_bytes, 4);
				last_rtp_frame_cache_len += 4;
			}

			if (last_rtp_frame_cache_len + len >= rtp_frame_cache_max_len){ 
			   rtp_frame_cache = (uint8_t *)realloc(rtp_frame_cache, 10240);
			   rtp_frame_cache_max_len += 10240;
			}
			//拷贝payload
			memcpy((void *)(rtp_frame_cache+last_rtp_frame_cache_len), payload, len);
			last_rtp_frame_cache_len += len;
		} 
	}

	if (marker){ 
		//reset 0
		if (getFrameCallback != NULL){ 
			printf("last_rtp_frame_cache_len:%d\r\n", last_rtp_frame_cache_len); 
		    getFrameCallback(rtp_frame_cache, last_rtp_frame_cache_len, frameCallbackArgs);  
		}
		last_rtp_frame_cache_len = 0;
	}
}

3、参考增加一个虚拟的video设备;

参考:https://it3q.com/article/185


4、开放的接口;

rtsp_client部分的接口,

typedef struct pjmedia_rtsp_source_op
{ 
    int (*init_rtsp_client)();
    int (*deinit_rtsp_client)();
    int (*start_rtsp_client)(const char *url, OnGetFrameFromRTSP callback, void *user_data);
    int (*stop_rtsp_client)();
}pjmedia_rtsp_source_op;
   
extern void set_use_rtsp_source(const char *url, pjmedia_rtsp_source_op *op);
int start_rtsp_client_sip(const char *url, OnGetFrameFromRTSP callback, void *user_data);
static pjmedia_rtsp_source_op factory_op =
{
    &init_rtsp_client,
    &deinit_rtsp_client,
    &start_rtsp_client_sip,
    &stop_rtsp_client
};

pjsip的接口:

void register_rtsp_client_source(const char *url){
	if (url == NULL){
		return;
	}
	printf("call register url:%s\r\n", url);
	set_use_rtsp_source(url, &factory_op);
}


5、注意:rtsp协商的h264编码参数,需要同步到pjsip中,建议是先通过抓包,取的某个型号摄像头的参数后,配置到pjsip中。

主要包括:profile-level-id=420029; packetization-mode=1;


代码目录结构:

呱牛笔记


交叉编译,拷贝过来的交叉编译器,需要调整sysroot,否则gcc报错。

root@lyz-VirtualBox:/home/lyz/work/broadcast_app/v3s_ipc_rtsp_pjsip/curl-8.2.1# arm-buildroot-linux-uclibcgnueabihf-gcc -v
Using built-in specs.
COLLECT_GCC=/home/lyz/work/broadcast_app/v3s_ipc_rtsp_pjsip/buildroot-2018.08.2/output/host/bin/arm-buildroot-linux-uclibcgnueabihf-gcc.br_real
COLLECT_LTO_WRAPPER=/home/lyz/work/broadcast_app/v3s_ipc_rtsp_pjsip/buildroot-2018.08.2/output/host/bin/../libexec/gcc/arm-buildroot-linux-uclibcgnueabihf/7.3.0/lto-wrapper
Target: arm-buildroot-linux-uclibcgnueabihf
Configured with: ./configure --prefix=/home/psst/v3s/buildroot-2018.08.2/output/host --sysconfdir=/home/psst/v3s/buildroot-2018.08.2/output/host/etc --enable-static --target=arm-buildroot-linux-uclibcgnueabihf --with-sysroot=/home/psst/v3s/buildroot-2018.08.2/output/host/arm-buildroot-linux-uclibcgnueabihf/sysroot --disable-__cxa_atexit --with-gnu-ld --disable-libssp --disable-multilib --with-gmp=/home/psst/v3s/buildroot-2018.08.2/output/host --with-mpc=/home/psst/v3s/buildroot-2018.08.2/output/host --with-mpfr=/home/psst/v3s/buildroot-2018.08.2/output/host --with-pkgversion='Buildroot 2018.08.2' --with-bugurl=http://bugs.buildroot.net/ --disable-libquadmath --disable-libsanitizer --enable-tls --disable-libmudflap --enable-threads --without-isl --without-cloog --disable-decimal-float --with-abi=aapcs-linux --with-cpu=cortex-a7 --with-fpu=vfpv4 --with-float=hard --with-mode=arm --enable-languages=c,c++ --with-build-time-tools=/home/psst/v3s/buildroot-2018.08.2/output/host/arm-buildroot-linux-uclibcgnueabihf/bin --enable-shared --disable-libgomp
Thread model: posix
gcc version 7.3.0 (Buildroot 2018.08.2)

呱牛笔记

        

本文为呱牛笔记原创文章,转载无需和我联系,但请注明来自呱牛笔记 ,it3q.com

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