关键点:
该功能实现,主要需要考虑RTSP取摄像头视频流,拆RTP包,组H264帧,通过PJSIP的视频通道转发;这个过程中,涉及到RTP通道保活,RTSP通道保活;调试时间多耗费在对摄像头返回的RTP数据包的拆解和重新组H264帧上面。
1、RTSP信令通道;
curl支持rtsp的客户端取流,demo实现也是很简单的,主要有几个点,一是用户鉴权,二是RTSP通道保活;
用户鉴权:参考 https://github.com/lminiero/rtsp-auth-test/
代码语言:javascript复制 /* 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端口
代码语言:javascript复制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
代码语言:javascript复制static void rtsp_get_parmeter(CURL *curl, const char *uri) {
CURLcode ret = CURLE_OK;
printf("nRTSP: rtsp_get_parmeter %sn", 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端口:
代码语言:javascript复制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:%drn", rtp_udp->port);
break;
}
sCurrentRTPPortToUse ;
if (sCurrentRTPPortToUse == kMaxRTPPort){
printf("sCurrentRTPPortToUse is:%drn", sCurrentRTPPortToUse);
return -1;
}
}while(1);
sCurrentRTPPortToUse ;
do{
int ret = udp_server_init(rtcp_udp, sCurrentRTPPortToUse);
if (ret == 0){
printf("get_udp_port rtcp:%drn", rtcp_udp->port);
break;
}
sCurrentRTPPortToUse ;
if (sCurrentRTPPortToUse == kMaxRTPPort){
printf("sCurrentRTPPortToUse is:%drn", sCurrentRTPPortToUse);
if (rtp_udp->port != 0){
udp_server_deinit(rtp_udp);
}
return -1;
}
}while(1);
return 0;
}
媒体通道保活
代码语言:javascript复制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.rn", 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("nrsend error.nr");
}
return;
}
RTP重新组包:
关键的点,就是将H264的RTP封包重新组成完整的H264帧,每一个不同的帧类型包括如下,每个帧类型前面都要补充0x00,0x00,0x00,0x01,否则不能解码。
代码语言:javascript复制0x67: SPS
0x68: PPS
0x65: IDR
0x61: non-IDR Slice
0x01: B Slice
0x06: SEI
0x09: AU Delimiter
代码语言:javascript复制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:%drn", 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 rn", 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, rn");
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:x, nalu_type:%d marker:%d, len:%d,last_len:%drn", 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, rn");
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, rn");
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:%drn", last_rtp_frame_cache_len);
getFrameCallback(rtp_frame_cache, last_rtp_frame_cache_len, frameCallbackArgs);
}
last_rtp_frame_cache_len = 0;
}
}
3、开放的接口;
rtsp_client部分的接口,
代码语言:javascript复制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的接口:
代码语言:javascript复制void register_rtsp_client_source(const char *url){
if (url == NULL){
return;
}
printf("call register url:%srn", url);
set_use_rtsp_source(url, &factory_op);
}
代码目录结构:
交叉编译,拷贝过来的交叉编译器,需要调整sysroot,否则gcc报错。
代码语言:javascript复制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