Ключевые моменты:
Чтобы реализовать эту функцию, нам в основном нужно рассмотреть RTSP для получения видеопотока камеры, распаковки пакетов RTP, сборки кадров H264 и пересылки их через видеоканал PJSIP. Этот процесс включает в себя поддержание активности канала RTP и поддержание активности канала RTSP; большая часть времени отладки тратится на то, чтобы RTP-пакеты, возвращаемые камерой, разбирались и снова собирались в кадры H264.
1. Канал сигнализации RTSP;
Curl поддерживает потоковую передачу клиента rtsp, а демонстрационная реализация также очень проста. Есть несколько основных моментов: один — аутентификация пользователя, а другой — поддержание активности канала 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 отправляет сообщение GET_PARAMETER каждые 8 секунд.
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: идентификатор уровня профиля, наборы параметров sprop, порт 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"
Отправляйте Get_PARAMETER каждые 8 секунд.
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 изменился во время сигнализации 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 в полный кадр 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;
}
//Копируем полезную нагрузку
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;
}
//Копируем полезную нагрузку
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.
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;
}
//Копируем полезную нагрузку
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. Открытый интерфейс;
Интерфейс части 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);
}
Структура каталога кода:
Для кросс-компиляции скопированный кросс-компилятор должен настроить 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)
Эта статья является оригинальной статьей Guiniu Notes. Для перепечатки обращаться ко мне не обязательно, но, пожалуйста, укажите, что она взята из Guiniu Notes, it3q.com.