关键点:
该功能实现,主要需要考虑RTSP取摄像头视频流,拆RTP包,组H264帧,通过PJSIP的视频通道转发;这个过程中,涉及到RTP通道保活,RTSP通道保活;调试时间多耗费在对摄像头返回的RTP数据包的拆解和重新组H264帧上面。
1、RTSP信令通道;
curl支持rtsp的客户端取流,demo实现也是很简单的,主要有几个点,一是用户鉴权,二是RTSP通道保活;
用户鉴权:参考 https://github.com/lminiero/rtsp-auth-test/
1 2 3 4 5 | /* 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端口
1 2 3 4 | 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
1 2 3 4 5 6 7 8 9 10 | 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端口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | 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; } |
媒体通道保活
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | 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部分的接口,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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的接口:
1 2 3 4 5 6 7 | 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报错。
1 2 3 4 5 6 7 8 | 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) |
-------------------广告线---------------
项目、合作,欢迎勾搭,邮箱:promall@qq.com
本文为呱牛笔记原创文章,转载无需和我联系,但请注明来自呱牛笔记 ,it3q.com