/* $Id$ */ /* * Copyright (C) 2018 Teluu Inc. (http://www.teluu.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include #include #define THIS_FILE "rtcp_fb.c" #define RTCP_RTPFB 205 #define RTCP_PSFB 206 /* * Build an RTCP-FB Generic NACK packet. */ PJ_DEF(pj_status_t) pjmedia_rtcp_fb_build_nack( pjmedia_rtcp_session *session, void *buf, pj_size_t *length, unsigned nack_cnt, const pjmedia_rtcp_fb_nack nack[]) { pjmedia_rtcp_common *hdr; pj_uint8_t *p; unsigned len, i; PJ_ASSERT_RETURN(session && buf && length && nack_cnt && nack, PJ_EINVAL); len = (3 + nack_cnt) * 4; if (len > *length) return PJ_ETOOSMALL; /* Build RTCP-FB NACK header */ hdr = (pjmedia_rtcp_common*)buf; pj_memcpy(hdr, &session->rtcp_rr_pkt.common, sizeof(*hdr)); hdr->pt = RTCP_RTPFB; hdr->count = 1; /* FMT = 1 */ hdr->length = pj_htons((pj_uint16_t)(len/4 - 1)); /* Build RTCP-FB NACK FCI */ p = (pj_uint8_t*)hdr + sizeof(*hdr); for (i = 0; i < nack_cnt; ++i) { pj_uint16_t val; val = pj_htons((pj_uint16_t)nack[i].pid); pj_memcpy(p, &val, 2); val = pj_htons(nack[i].blp); pj_memcpy(p+2, &val, 2); p += 4; } /* Finally */ *length = len; return PJ_SUCCESS; } /* * Build an RTCP-FB Picture Loss Indication (PLI) packet. */ PJ_DEF(pj_status_t) pjmedia_rtcp_fb_build_pli( pjmedia_rtcp_session *session, void *buf, pj_size_t *length) { pjmedia_rtcp_common *hdr; unsigned len; PJ_ASSERT_RETURN(session && buf && length, PJ_EINVAL); len = 12; if (len > *length) return PJ_ETOOSMALL; /* Build RTCP-FB PLI header */ hdr = (pjmedia_rtcp_common*)buf; pj_memcpy(hdr, &session->rtcp_rr_pkt.common, sizeof(*hdr)); hdr->pt = RTCP_PSFB; hdr->count = 1; /* FMT = 1 */ hdr->length = pj_htons((pj_uint16_t)(len/4 - 1)); /* Finally */ *length = len; return PJ_SUCCESS; } /* * Build an RTCP-FB Slice Loss Indication (SLI) packet. */ PJ_DEF(pj_status_t) pjmedia_rtcp_fb_build_sli( pjmedia_rtcp_session *session, void *buf, pj_size_t *length, unsigned sli_cnt, const pjmedia_rtcp_fb_sli sli[]) { pjmedia_rtcp_common *hdr; pj_uint8_t *p; unsigned len, i; PJ_ASSERT_RETURN(session && buf && length && sli_cnt && sli, PJ_EINVAL); len = (3 + sli_cnt) * 4; if (len > *length) return PJ_ETOOSMALL; /* Build RTCP-FB SLI header */ hdr = (pjmedia_rtcp_common*)buf; pj_memcpy(hdr, &session->rtcp_rr_pkt.common, sizeof(*hdr)); hdr->pt = RTCP_PSFB; hdr->count = 2; /* FMT = 2 */ hdr->length = pj_htons((pj_uint16_t)(len/4 - 1)); /* Build RTCP-FB SLI FCI */ p = (pj_uint8_t*)hdr + sizeof(*hdr); for (i = 0; i < sli_cnt; ++i) { /* 'first' takes 13 bit */ *p++ = (pj_uint8_t)((sli[i].first >> 5) & 0xFF); /* 8 MSB bits */ *p = (pj_uint8_t)((sli[i].first & 31) << 3); /* 5 LSB bits */ /* 'number' takes 13 bit */ *p++ |= (pj_uint8_t)((sli[i].number >> 10) & 7); /* 3 MSB bits */ *p++ = (pj_uint8_t)((sli[i].number >> 2) & 0xFF); /* 8 mid bits */ *p = (pj_uint8_t)((sli[i].number & 3) << 6); /* 2 LSB bits */ /* 'pict_id' takes 6 bit */ *p++ |= (sli[i].pict_id & 63); } /* Finally */ *length = len; return PJ_SUCCESS; } /* * Build an RTCP-FB Slice Loss Indication (SLI) packet. */ PJ_DEF(pj_status_t) pjmedia_rtcp_fb_build_rpsi( pjmedia_rtcp_session *session, void *buf, pj_size_t *length, const pjmedia_rtcp_fb_rpsi *rpsi) { pjmedia_rtcp_common *hdr; pj_uint8_t *p; unsigned bitlen, padlen, len; PJ_ASSERT_RETURN(session && buf && length && rpsi, PJ_EINVAL); bitlen = rpsi->rpsi_bit_len + 16; padlen = (32 - (bitlen % 32)) % 32; len = (3 + (bitlen+padlen)/32) * 4; if (len > *length) return PJ_ETOOSMALL; /* Build RTCP-FB RPSI header */ hdr = (pjmedia_rtcp_common*)buf; pj_memcpy(hdr, &session->rtcp_rr_pkt.common, sizeof(*hdr)); hdr->pt = RTCP_PSFB; hdr->count = 3; /* FMT = 3 */ hdr->length = pj_htons((pj_uint16_t)(len/4 - 1)); /* Build RTCP-FB RPSI FCI */ p = (pj_uint8_t*)hdr + sizeof(*hdr); /* PB (number of padding bits) */ *p++ = (pj_uint8_t)padlen; /* Payload type */ *p++ = rpsi->pt & 0x7F; /* RPSI bit string */ pj_memcpy(p, rpsi->rpsi.ptr, rpsi->rpsi_bit_len/8); p += rpsi->rpsi_bit_len/8; if (rpsi->rpsi_bit_len % 8) { *p++ = *(rpsi->rpsi.ptr + rpsi->rpsi_bit_len/8); } /* Zero padding */ if (padlen >= 8) pj_bzero(p, padlen/8); /* Finally */ *length = len; return PJ_SUCCESS; } /* * Initialize RTCP Feedback setting with default values. */ PJ_DEF(pj_status_t) pjmedia_rtcp_fb_setting_default( pjmedia_rtcp_fb_setting *opt) { pj_bzero(opt, sizeof(*opt)); opt->dont_use_avpf = PJ_TRUE; return PJ_SUCCESS; } static void pjmedia_rtcp_fb_cap_dup(pj_pool_t *pool, pjmedia_rtcp_fb_cap *dst, const pjmedia_rtcp_fb_cap *src) { pj_strdup(pool, &dst->codec_id, &src->codec_id); dst->type = src->type; pj_strdup(pool, &dst->type_name, &src->type_name); pj_strdup(pool, &dst->param, &src->param); } /* * Duplicate RTCP Feedback setting. */ PJ_DEF(void) pjmedia_rtcp_fb_setting_dup( pj_pool_t *pool, pjmedia_rtcp_fb_setting *dst, const pjmedia_rtcp_fb_setting *src) { unsigned i; pj_assert(pool && dst && src); pj_memcpy(dst, src, sizeof(pjmedia_rtcp_fb_setting)); for (i = 0; i < src->cap_count; ++i) { pjmedia_rtcp_fb_cap_dup(pool, &dst->caps[i], &src->caps[i]); } } /* * Duplicate RTCP Feedback info. */ PJ_DEF(void) pjmedia_rtcp_fb_info_dup( pj_pool_t *pool, pjmedia_rtcp_fb_info *dst, const pjmedia_rtcp_fb_info *src) { unsigned i; pj_assert(pool && dst && src); pj_memcpy(dst, src, sizeof(pjmedia_rtcp_fb_info)); for (i = 0; i < src->cap_count; ++i) { pjmedia_rtcp_fb_cap_dup(pool, &dst->caps[i], &src->caps[i]); } } struct rtcp_fb_type_name_t { pjmedia_rtcp_fb_type type; const char *name; } rtcp_fb_type_name[] = { {PJMEDIA_RTCP_FB_ACK, "ack"}, {PJMEDIA_RTCP_FB_NACK, "nack"}, {PJMEDIA_RTCP_FB_TRR_INT, "trr-int"} }; /* Generate a=rtcp-fb based on the specified PT & RTCP-FB capability */ static pj_status_t add_sdp_attr_rtcp_fb( pj_pool_t *pool, const char *pt, const pjmedia_rtcp_fb_cap *cap, pjmedia_sdp_media *m) { pjmedia_sdp_attr *a; char tmp[128]; pj_str_t val; pj_str_t type_name = {0}; if (cap->type < PJMEDIA_RTCP_FB_OTHER) pj_cstr(&type_name, rtcp_fb_type_name[cap->type].name); else if (cap->type == PJMEDIA_RTCP_FB_OTHER) type_name = cap->type_name; if (type_name.slen == 0) return PJ_EINVAL; /* Generate RTCP FB param */ if (cap->param.slen) { pj_ansi_snprintf(tmp, sizeof(tmp), "%s %.*s %.*s", pt, (int)type_name.slen, type_name.ptr, (int)cap->param.slen, cap->param.ptr); } else { pj_ansi_snprintf(tmp, sizeof(tmp), "%s %.*s", pt, (int)type_name.slen, type_name.ptr); } pj_strset2(&val, tmp); /* Generate and add SDP attribute a=rtcp-fb */ a = pjmedia_sdp_attr_create(pool, "rtcp-fb", &val); m->attr[m->attr_count++] = a; return PJ_SUCCESS; } /* SDP codec info (ID and PT) */ typedef struct sdp_codec_info_t { char id[32]; unsigned pt; } sdp_codec_info_t; /* Populate codec ID/name and PT in SDP */ static pj_status_t get_codec_info_from_sdp(pjmedia_endpt *endpt, const pjmedia_sdp_media *m, unsigned *sci_cnt, sdp_codec_info_t sci[]) { pjmedia_codec_mgr *codec_mgr; unsigned j, cnt = 0; pjmedia_type type = PJMEDIA_TYPE_UNKNOWN; pj_status_t status; type = pjmedia_get_type(&m->desc.media); if (type != PJMEDIA_TYPE_AUDIO && type != PJMEDIA_TYPE_VIDEO) return PJMEDIA_EUNSUPMEDIATYPE; codec_mgr = pjmedia_endpt_get_codec_mgr(endpt); for (j = 0; j < m->desc.fmt_count && cnt < *sci_cnt; ++j) { unsigned pt = 0; pt = pj_strtoul(&m->desc.fmt[j]); if (pt < 96) { if (type == PJMEDIA_TYPE_AUDIO) { const pjmedia_codec_info *ci; status = pjmedia_codec_mgr_get_codec_info(codec_mgr, pt, &ci); if (status != PJ_SUCCESS) continue; pjmedia_codec_info_to_id(ci, sci[cnt].id, sizeof(sci[0].id)); } else { #if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0) const pjmedia_vid_codec_info *ci; status = pjmedia_vid_codec_mgr_get_codec_info(NULL, pt, &ci); if (status != PJ_SUCCESS) continue; pjmedia_vid_codec_info_to_id(ci, sci[cnt].id, sizeof(sci[0].id)); #else continue; #endif } } else { pjmedia_sdp_attr *a; pjmedia_sdp_rtpmap r; a = pjmedia_sdp_media_find_attr2(m, "rtpmap", &m->desc.fmt[j]); if (a == NULL) continue; status = pjmedia_sdp_attr_get_rtpmap(a, &r); if (status != PJ_SUCCESS) continue; if (type == PJMEDIA_TYPE_AUDIO) { /* Audio codec id format: "name/clock-rate/channel-count" */ if (r.param.slen) { pj_ansi_snprintf(sci[cnt].id, sizeof(sci[0].id), "%.*s/%d/%.*s", (int)r.enc_name.slen, r.enc_name.ptr, r.clock_rate, (int)r.param.slen, r.param.ptr); } else { pj_ansi_snprintf(sci[cnt].id, sizeof(sci[0].id), "%.*s/%d/1", (int)r.enc_name.slen, r.enc_name.ptr, r.clock_rate); } } else { /* Video codec id format: "name/payload-type" */ pj_ansi_snprintf(sci[cnt].id, sizeof(sci[0].id), "%.*s/%d", (int)r.enc_name.slen, r.enc_name.ptr, pt); } } sci[cnt++].pt = pt; } *sci_cnt = cnt; return PJ_SUCCESS; } /* * Encode RTCP Feedback specific information into the SDP according to * the provided RTCP Feedback setting. */ PJ_DEF(pj_status_t) pjmedia_rtcp_fb_encode_sdp( pj_pool_t *pool, pjmedia_endpt *endpt, const pjmedia_rtcp_fb_setting *opt, pjmedia_sdp_session *sdp_local, unsigned med_idx, const pjmedia_sdp_session *sdp_remote) { pjmedia_sdp_media *m = sdp_local->media[med_idx]; unsigned i; unsigned sci_cnt = 0; sdp_codec_info_t sci[PJMEDIA_MAX_SDP_FMT]; pj_status_t status; PJ_UNUSED_ARG(sdp_remote); PJ_ASSERT_RETURN(pool && endpt && opt && sdp_local, PJ_EINVAL); PJ_ASSERT_RETURN(med_idx < sdp_local->media_count, PJ_EINVAL); /* Add RTCP Feedback profile (AVPF), if configured to */ if (!opt->dont_use_avpf) { unsigned proto = pjmedia_sdp_transport_get_proto(&m->desc.transport); if (!PJMEDIA_TP_PROTO_HAS_FLAG(proto, PJMEDIA_TP_PROFILE_RTCP_FB)) { pj_str_t new_tp; pj_strdup_with_null(pool, &new_tp, &m->desc.transport); new_tp.ptr[new_tp.slen++] = 'F'; m->desc.transport = new_tp; } } /* Add RTCP Feedback capability to SDP */ for (i = 0; i < opt->cap_count; ++i) { unsigned j; /* All codecs */ if (pj_strcmp2(&opt->caps[i].codec_id, "*") == 0) { status = add_sdp_attr_rtcp_fb(pool, "*", &opt->caps[i], m); if (status != PJ_SUCCESS) { PJ_PERROR(3, (THIS_FILE, status, "Failed generating SDP a=rtcp-fb:*")); } continue; } /* Specific codec */ if (sci_cnt == 0) { sci_cnt = PJ_ARRAY_SIZE(sci); status = get_codec_info_from_sdp(endpt, m, &sci_cnt, sci); if (status != PJ_SUCCESS) { PJ_PERROR(3, (THIS_FILE, status, "Failed populating codec info from SDP")); return status; } } for (j = 0; j < sci_cnt; ++j) { if (pj_strnicmp2(&opt->caps[i].codec_id, sci[j].id, opt->caps[i].codec_id.slen) == 0) { char tmp[4]; snprintf(tmp, sizeof(tmp), "%d", sci[j].pt); status = add_sdp_attr_rtcp_fb(pool, tmp, &opt->caps[i], m); if (status != PJ_SUCCESS) { PJ_PERROR(3, (THIS_FILE, status, "Failed generating SDP a=rtcp-fb:%d (%s)", sci[j].pt, opt->caps[i].codec_id.ptr)); } break; } } if (j == sci_cnt) { /* Codec ID not found in SDP (perhaps better ignore this error * as app may configure audio and video in single setting). */ PJ_PERROR(6, (THIS_FILE, PJ_ENOTFOUND, "Failed generating SDP a=rtcp-fb for %s", opt->caps[i].codec_id.ptr)); } } return PJ_SUCCESS; } /* * Decode RTCP Feedback specific information from SDP media. */ PJ_DEF(pj_status_t) pjmedia_rtcp_fb_decode_sdp( pj_pool_t *pool, pjmedia_endpt *endpt, const void *opt, const pjmedia_sdp_session *sdp, unsigned med_idx, pjmedia_rtcp_fb_info *info) { return pjmedia_rtcp_fb_decode_sdp2(pool, endpt, opt, sdp, med_idx, -1, info); } /* * Decode RTCP Feedback specific information from SDP media. */ PJ_DEF(pj_status_t) pjmedia_rtcp_fb_decode_sdp2( pj_pool_t *pool, pjmedia_endpt *endpt, const void *opt, const pjmedia_sdp_session *sdp, unsigned med_idx, int pt, pjmedia_rtcp_fb_info *info) { unsigned sci_cnt = PJMEDIA_MAX_SDP_FMT; sdp_codec_info_t sci[PJMEDIA_MAX_SDP_FMT]; const pjmedia_sdp_media *m; pj_status_t status; unsigned i; PJ_UNUSED_ARG(opt); PJ_ASSERT_RETURN(pool && endpt && opt==NULL && sdp, PJ_EINVAL); PJ_ASSERT_RETURN(med_idx < sdp->media_count, PJ_EINVAL); PJ_ASSERT_RETURN(pt <= 127, PJ_EINVAL); m = sdp->media[med_idx]; status = get_codec_info_from_sdp(endpt, m, &sci_cnt, sci); if (status != PJ_SUCCESS) return status; pj_bzero(info, sizeof(*info)); /* Iterate all SDP attribute a=rtcp-fb in the SDP media */ for (i = 0; i < m->attr_count; ++i) { const pjmedia_sdp_attr *a = m->attr[i]; pj_str_t token; pj_ssize_t tok_idx; unsigned j; const char *codec_id = NULL; pj_str_t type_name = {0}; pjmedia_rtcp_fb_type type = PJMEDIA_RTCP_FB_OTHER; /* Skip non a=rtcp-fb */ if (pj_strcmp2(&a->name, "rtcp-fb") != 0) continue; /* Get PT */ tok_idx = pj_strtok2(&a->value, " \t", &token, 0); if (tok_idx == a->value.slen) continue; if (pj_strcmp2(&token, "*") == 0) { /* All codecs */ codec_id = "*"; } else { /* Specific PT/codec */ unsigned pt_ = (unsigned) pj_strtoul2(&token, NULL, 10); for (j = 0; j < sci_cnt; ++j) { /* Check if payload type is valid and requested */ if (pt_ == sci[j].pt && (pt < 0 || pt == (int)pt_)) { codec_id = sci[j].id; break; } } } /* Skip this a=rtcp-fb if PT is not recognized or not requested */ if (!codec_id) continue; /* Get RTCP-FB type */ tok_idx = pj_strtok2(&a->value, " \t", &token, tok_idx + token.slen); if (tok_idx == a->value.slen) continue; for (j = 0; j < PJ_ARRAY_SIZE(rtcp_fb_type_name); ++j) { if (pj_strcmp2(&token, rtcp_fb_type_name[j].name) == 0) { type = rtcp_fb_type_name[j].type; break; } } if (type == PJMEDIA_RTCP_FB_OTHER) type_name = token; /* Got all the mandatory fields, let's initialize RTCP-FB cap */ pj_strdup2(pool, &info->caps[info->cap_count].codec_id, codec_id); info->caps[info->cap_count].type = type; if (type == PJMEDIA_RTCP_FB_OTHER) pj_strdup(pool, &info->caps[info->cap_count].type_name, &type_name); /* Get RTCP-FB param */ tok_idx = pj_strtok2(&a->value, " \t", &token, tok_idx + token.slen); if (tok_idx != a->value.slen) pj_strdup(pool, &info->caps[info->cap_count].param, &token); /* Next */ if (++info->cap_count == PJMEDIA_RTCP_FB_MAX_CAP) break; } return PJ_SUCCESS; } /* * Check whether the specified payload contains RTCP feedback generic NACK * message, and parse the payload if it does. */ PJ_DEF(pj_status_t) pjmedia_rtcp_fb_parse_nack( const void *buf, pj_size_t length, unsigned *nack_cnt, pjmedia_rtcp_fb_nack nack[]) { pjmedia_rtcp_common *hdr = (pjmedia_rtcp_common*) buf; pj_uint8_t *p; unsigned cnt, i; PJ_ASSERT_RETURN(buf && nack_cnt && nack, PJ_EINVAL); PJ_ASSERT_RETURN(length >= sizeof(pjmedia_rtcp_common), PJ_ETOOSMALL); /* Generic NACK uses pt==RTCP_RTPFB and FMT==1 */ if (hdr->pt != RTCP_RTPFB || hdr->count != 1) return PJ_ENOTFOUND; cnt = pj_ntohs((pj_uint16_t)hdr->length) - 2; if (length < (cnt+3)*4) return PJ_ETOOSMALL; *nack_cnt = PJ_MIN(*nack_cnt, cnt); p = (pj_uint8_t*)hdr + sizeof(*hdr); for (i = 0; i < *nack_cnt; ++i) { pj_uint16_t val; pj_memcpy(&val, p, 2); nack[i].pid = pj_ntohs(val); pj_memcpy(&val, p+2, 2); nack[i].blp = pj_ntohs(val); p += 4; } return PJ_SUCCESS; } /* * Check whether the specified payload contains RTCP feedback Picture Loss * Indication (PLI) message. */ PJ_DEF(pj_status_t) pjmedia_rtcp_fb_parse_pli( const void *buf, pj_size_t length) { pjmedia_rtcp_common *hdr = (pjmedia_rtcp_common*) buf; PJ_ASSERT_RETURN(buf, PJ_EINVAL); PJ_ASSERT_RETURN(length >= 12, PJ_ETOOSMALL); /* PLI uses pt==RTCP_PSFB and FMT==1 */ if (hdr->pt != RTCP_PSFB || hdr->count != 1) return PJ_ENOTFOUND; return PJ_SUCCESS; } /* * Check whether the specified payload contains RTCP feedback Slice Loss * Indication (SLI) message, and parse the payload if it does. */ PJ_DEF(pj_status_t) pjmedia_rtcp_fb_parse_sli( const void *buf, pj_size_t length, unsigned *sli_cnt, pjmedia_rtcp_fb_sli sli[]) { pjmedia_rtcp_common *hdr = (pjmedia_rtcp_common*) buf; pj_uint8_t *p; unsigned cnt, i; PJ_ASSERT_RETURN(buf && sli_cnt && sli, PJ_EINVAL); PJ_ASSERT_RETURN(length >= sizeof(pjmedia_rtcp_common), PJ_ETOOSMALL); /* PLI uses pt==RTCP_PSFB and FMT==2 */ if (hdr->pt != RTCP_PSFB || hdr->count != 2) return PJ_ENOTFOUND; cnt = pj_ntohs((pj_uint16_t)hdr->length) - 2; if (length < (cnt+3)*4) return PJ_ETOOSMALL; *sli_cnt = PJ_MIN(*sli_cnt, cnt); p = (pj_uint8_t*)hdr + sizeof(*hdr); for (i = 0; i < *sli_cnt; ++i) { /* 'first' takes 13 bit */ sli[i].first = (p[0] << 5) + ((p[1] & 0xF8) >> 3); /* 'number' takes 13 bit */ sli[i].number = ((p[1] & 0x07) << 10) + (p[2] << 2) + ((p[3] & 0xC0) >> 6); /* 'pict_id' takes 6 bit */ sli[i].pict_id = (p[3] & 0x3F); p += 4; } return PJ_SUCCESS; } /* * Check whether the specified payload contains RTCP feedback Reference * Picture Selection Indication (RPSI) message, and parse the payload * if it does. */ PJ_DEF(pj_status_t) pjmedia_rtcp_fb_parse_rpsi( const void *buf, pj_size_t length, pjmedia_rtcp_fb_rpsi *rpsi) { pjmedia_rtcp_common *hdr = (pjmedia_rtcp_common*) buf; pj_uint8_t *p; pj_uint8_t padlen; pj_size_t rpsi_len; PJ_ASSERT_RETURN(buf && rpsi, PJ_EINVAL); PJ_ASSERT_RETURN(length >= sizeof(pjmedia_rtcp_common), PJ_ETOOSMALL); /* RPSI uses pt==RTCP_PSFB and FMT==3 */ if (hdr->pt != RTCP_PSFB || hdr->count != 3) return PJ_ENOTFOUND; rpsi_len = (pj_ntohs((pj_uint16_t)hdr->length)-2) * 4; if (length < rpsi_len + 12) return PJ_ETOOSMALL; p = (pj_uint8_t*)hdr + sizeof(*hdr); padlen = *p++; rpsi->pt = (*p++ & 0x7F); rpsi->rpsi_bit_len = rpsi_len*8 - 16 - padlen; pj_strset(&rpsi->rpsi, (char*)p, (rpsi->rpsi_bit_len + 7)/8); return PJ_SUCCESS; }