/* $Id$ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * 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 #include #include #include #define THIS_FILE "endpoint.c" static const pj_str_t STR_IN = { "IN", 2 }; static const pj_str_t STR_IP4 = { "IP4", 3}; static const pj_str_t STR_IP6 = { "IP6", 3}; static const pj_str_t STR_RTP_AVP = { "RTP/AVP", 7 }; static const pj_str_t STR_SDP_NAME = { "pjmedia", 7 }; static const pj_str_t STR_SENDRECV = { "sendrecv", 8 }; /* Config to control rtpmap inclusion for static payload types */ pj_bool_t pjmedia_add_rtpmap_for_static_pt = PJMEDIA_ADD_RTPMAP_FOR_STATIC_PT; /* Config to control use of RFC3890 TIAS */ pj_bool_t pjmedia_add_bandwidth_tias_in_sdp = PJMEDIA_ADD_BANDWIDTH_TIAS_IN_SDP; /* Worker thread proc. */ static int PJ_THREAD_FUNC worker_proc(void*); #define MAX_THREADS 16 /* List of media endpoint exit callback. */ typedef struct exit_cb { PJ_DECL_LIST_MEMBER (struct exit_cb); pjmedia_endpt_exit_callback func; } exit_cb; /** Concrete declaration of media endpoint. */ struct pjmedia_endpt { /** Pool. */ pj_pool_t *pool; /** Pool factory. */ pj_pool_factory *pf; /** Codec manager. */ pjmedia_codec_mgr codec_mgr; /** IOqueue instance. */ pj_ioqueue_t *ioqueue; /** Do we own the ioqueue? */ pj_bool_t own_ioqueue; /** Number of threads. */ unsigned thread_cnt; /** IOqueue polling thread, if any. */ pj_thread_t *thread[MAX_THREADS]; /** To signal polling thread to quit. */ pj_bool_t quit_flag; /** Is telephone-event enable */ pj_bool_t has_telephone_event; /** List of exit callback. */ exit_cb exit_cb_list; }; /** * Initialize and get the instance of media endpoint. */ PJ_DEF(pj_status_t) pjmedia_endpt_create2(pj_pool_factory *pf, pj_ioqueue_t *ioqueue, unsigned worker_cnt, pjmedia_endpt **p_endpt) { pj_pool_t *pool; pjmedia_endpt *endpt; unsigned i; pj_status_t status; status = pj_register_strerror(PJMEDIA_ERRNO_START, PJ_ERRNO_SPACE_SIZE, &pjmedia_strerror); pj_assert(status == PJ_SUCCESS); PJ_ASSERT_RETURN(pf && p_endpt, PJ_EINVAL); PJ_ASSERT_RETURN(worker_cnt <= MAX_THREADS, PJ_EINVAL); pool = pj_pool_create(pf, "med-ept", 512, 512, NULL); if (!pool) return PJ_ENOMEM; endpt = PJ_POOL_ZALLOC_T(pool, struct pjmedia_endpt); endpt->pool = pool; endpt->pf = pf; endpt->ioqueue = ioqueue; endpt->thread_cnt = worker_cnt; endpt->has_telephone_event = PJ_TRUE; /* Initialize audio subsystem. * To avoid pjmedia's dependendy on pjmedia-audiodev, the initialization * (and shutdown) of audio subsystem will be done in the application * level instead, when it calls inline functions pjmedia_endpt_create() * and pjmedia_endpt_destroy(). */ //status = pjmedia_aud_subsys_init(pf); //if (status != PJ_SUCCESS) // goto on_error; /* Init codec manager. */ status = pjmedia_codec_mgr_init(&endpt->codec_mgr, endpt->pf); if (status != PJ_SUCCESS) goto on_error; /* Initialize exit callback list. */ pj_list_init(&endpt->exit_cb_list); /* Create ioqueue if none is specified. */ if (endpt->ioqueue == NULL) { endpt->own_ioqueue = PJ_TRUE; status = pj_ioqueue_create( endpt->pool, PJ_IOQUEUE_MAX_HANDLES, &endpt->ioqueue); if (status != PJ_SUCCESS) goto on_error; if (worker_cnt == 0) { PJ_LOG(4,(THIS_FILE, "Warning: no worker thread is created in" "media endpoint for internal ioqueue")); } } /* Create worker threads if asked. */ for (i=0; ipool, "media", &worker_proc, endpt, 0, 0, &endpt->thread[i]); if (status != PJ_SUCCESS) goto on_error; } *p_endpt = endpt; return PJ_SUCCESS; on_error: /* Destroy threads */ for (i=0; ithread_cnt; ++i) { if (endpt->thread[i]) { pj_thread_destroy(endpt->thread[i]); } } /* Destroy internal ioqueue */ if (endpt->ioqueue && endpt->own_ioqueue) pj_ioqueue_destroy(endpt->ioqueue); pjmedia_codec_mgr_destroy(&endpt->codec_mgr); //pjmedia_aud_subsys_shutdown(); pj_pool_release(pool); return status; } /** * Get the codec manager instance. */ PJ_DEF(pjmedia_codec_mgr*) pjmedia_endpt_get_codec_mgr(pjmedia_endpt *endpt) { return &endpt->codec_mgr; } /** * Deinitialize media endpoint. */ PJ_DEF(pj_status_t) pjmedia_endpt_destroy2 (pjmedia_endpt *endpt) { exit_cb *ecb; pjmedia_endpt_stop_threads(endpt); /* Destroy internal ioqueue */ if (endpt->ioqueue && endpt->own_ioqueue) { pj_ioqueue_destroy(endpt->ioqueue); endpt->ioqueue = NULL; } endpt->pf = NULL; pjmedia_codec_mgr_destroy(&endpt->codec_mgr); //pjmedia_aud_subsys_shutdown(); /* Call all registered exit callbacks */ ecb = endpt->exit_cb_list.next; while (ecb != &endpt->exit_cb_list) { (*ecb->func)(endpt); ecb = ecb->next; } pj_pool_release (endpt->pool); return PJ_SUCCESS; } PJ_DEF(pj_status_t) pjmedia_endpt_set_flag( pjmedia_endpt *endpt, pjmedia_endpt_flag flag, const void *value) { PJ_ASSERT_RETURN(endpt, PJ_EINVAL); switch (flag) { case PJMEDIA_ENDPT_HAS_TELEPHONE_EVENT_FLAG: endpt->has_telephone_event = *(pj_bool_t*)value; break; default: return PJ_EINVAL; } return PJ_SUCCESS; } PJ_DEF(pj_status_t) pjmedia_endpt_get_flag( pjmedia_endpt *endpt, pjmedia_endpt_flag flag, void *value) { PJ_ASSERT_RETURN(endpt, PJ_EINVAL); switch (flag) { case PJMEDIA_ENDPT_HAS_TELEPHONE_EVENT_FLAG: *(pj_bool_t*)value = endpt->has_telephone_event; break; default: return PJ_EINVAL; } return PJ_SUCCESS; } /** * Get the ioqueue instance of the media endpoint. */ PJ_DEF(pj_ioqueue_t*) pjmedia_endpt_get_ioqueue(pjmedia_endpt *endpt) { PJ_ASSERT_RETURN(endpt, NULL); return endpt->ioqueue; } /** * Get the number of worker threads in media endpoint. */ PJ_DEF(unsigned) pjmedia_endpt_get_thread_count(pjmedia_endpt *endpt) { PJ_ASSERT_RETURN(endpt, 0); return endpt->thread_cnt; } /** * Get a reference to one of the worker threads of the media endpoint */ PJ_DEF(pj_thread_t*) pjmedia_endpt_get_thread(pjmedia_endpt *endpt, unsigned index) { PJ_ASSERT_RETURN(endpt, NULL); PJ_ASSERT_RETURN(index < endpt->thread_cnt, NULL); /* here should be an assert on index >= 0 < endpt->thread_cnt */ return endpt->thread[index]; } /** * Stop and destroy the worker threads of the media endpoint */ PJ_DEF(pj_status_t) pjmedia_endpt_stop_threads(pjmedia_endpt *endpt) { unsigned i; PJ_ASSERT_RETURN(endpt, PJ_EINVAL); endpt->quit_flag = 1; /* Destroy threads */ for (i=0; ithread_cnt; ++i) { if (endpt->thread[i]) { pj_thread_join(endpt->thread[i]); pj_thread_destroy(endpt->thread[i]); endpt->thread[i] = NULL; } } return PJ_SUCCESS; } /** * Worker thread proc. */ static int PJ_THREAD_FUNC worker_proc(void *arg) { pjmedia_endpt *endpt = (pjmedia_endpt*) arg; while (!endpt->quit_flag) { pj_time_val timeout = { 0, 500 }; pj_ioqueue_poll(endpt->ioqueue, &timeout); } return 0; } /** * Create pool. */ PJ_DEF(pj_pool_t*) pjmedia_endpt_create_pool( pjmedia_endpt *endpt, const char *name, pj_size_t initial, pj_size_t increment) { pj_assert(endpt != NULL); return pj_pool_create(endpt->pf, name, initial, increment, NULL); } /* Common initialization for both audio and video SDP media line */ static pj_status_t init_sdp_media(pjmedia_sdp_media *m, pj_pool_t *pool, const pj_str_t *media_type, const pjmedia_sock_info *sock_info) { char tmp_addr[PJ_INET6_ADDRSTRLEN]; pjmedia_sdp_attr *attr; const pj_sockaddr *addr; pj_strdup(pool, &m->desc.media, media_type); addr = &sock_info->rtp_addr_name; /* Validate address family */ PJ_ASSERT_RETURN(addr->addr.sa_family == pj_AF_INET() || addr->addr.sa_family == pj_AF_INET6(), PJ_EAFNOTSUP); /* SDP connection line */ m->conn = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_conn); m->conn->net_type = STR_IN; m->conn->addr_type = (addr->addr.sa_family==pj_AF_INET())? STR_IP4:STR_IP6; pj_sockaddr_print(addr, tmp_addr, sizeof(tmp_addr), 0); pj_strdup2(pool, &m->conn->addr, tmp_addr); /* Port and transport in media description */ m->desc.port = pj_sockaddr_get_port(addr); m->desc.port_count = 1; pj_strdup (pool, &m->desc.transport, &STR_RTP_AVP); /* Add "rtcp" attribute */ #if defined(PJMEDIA_HAS_RTCP_IN_SDP) && PJMEDIA_HAS_RTCP_IN_SDP!=0 if (sock_info->rtcp_addr_name.addr.sa_family != 0) { attr = pjmedia_sdp_attr_create_rtcp(pool, &sock_info->rtcp_addr_name); if (attr) pjmedia_sdp_attr_add(&m->attr_count, m->attr, attr); } #endif /* Add sendrecv attribute. */ attr = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_attr); attr->name = STR_SENDRECV; m->attr[m->attr_count++] = attr; return PJ_SUCCESS; } /* Create m=audio SDP media line */ PJ_DEF(pj_status_t) pjmedia_endpt_create_audio_sdp(pjmedia_endpt *endpt, pj_pool_t *pool, const pjmedia_sock_info *si, unsigned options, pjmedia_sdp_media **p_m) { const pj_str_t STR_AUDIO = { "audio", 5 }; pjmedia_sdp_media *m; pjmedia_sdp_attr *attr; unsigned i; unsigned max_bitrate = 0; pj_status_t status; #if defined(PJMEDIA_RTP_PT_TELEPHONE_EVENTS) && \ PJMEDIA_RTP_PT_TELEPHONE_EVENTS != 0 unsigned televent_num = 0; unsigned televent_clockrates[8]; #endif unsigned used_pt_num = 0; unsigned used_pt[PJMEDIA_MAX_SDP_FMT]; PJ_UNUSED_ARG(options); /* Check that there are not too many codecs */ PJ_ASSERT_RETURN(endpt->codec_mgr.codec_cnt <= PJMEDIA_MAX_SDP_FMT, PJ_ETOOMANY); /* Insert PJMEDIA_RTP_PT_TELEPHONE_EVENTS as used PT */ #if defined(PJMEDIA_RTP_PT_TELEPHONE_EVENTS) && \ PJMEDIA_RTP_PT_TELEPHONE_EVENTS != 0 if (endpt->has_telephone_event) { used_pt[used_pt_num++] = PJMEDIA_RTP_PT_TELEPHONE_EVENTS; } #endif /* Create and init basic SDP media */ m = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media); status = init_sdp_media(m, pool, &STR_AUDIO, si); if (status != PJ_SUCCESS) return status; /* Add format, rtpmap, and fmtp (when applicable) for each codec */ for (i=0; icodec_mgr.codec_cnt; ++i) { pjmedia_codec_info *codec_info; pjmedia_sdp_rtpmap rtpmap; char tmp_param[3]; pjmedia_codec_param codec_param; pj_str_t *fmt; unsigned pt; if (endpt->codec_mgr.codec_desc[i].prio == PJMEDIA_CODEC_PRIO_DISABLED) break; codec_info = &endpt->codec_mgr.codec_desc[i].info; pjmedia_codec_mgr_get_default_param(&endpt->codec_mgr, codec_info, &codec_param); fmt = &m->desc.fmt[m->desc.fmt_count++]; pt = codec_info->pt; /* Rearrange dynamic payload type to make sure it is inside 96-127 * range and not being used by other codec/tel-event. */ if (pt >= 96) { unsigned pt_check = 96; unsigned j = 0; while (j < used_pt_num && pt_check <= 127) { if (pt_check==used_pt[j]) { pt_check++; j = 0; } else { j++; } } if (pt_check > 127) { /* No more available PT */ PJ_LOG(4,(THIS_FILE, "Warning: no available dynamic " "payload type for audio codec")); break; } pt = pt_check; } /* Take a note of used dynamic PT */ if (pt >= 96) used_pt[used_pt_num++] = pt; fmt->ptr = (char*) pj_pool_alloc(pool, 8); fmt->slen = pj_utoa(pt, fmt->ptr); rtpmap.pt = *fmt; rtpmap.enc_name = codec_info->encoding_name; #if defined(PJMEDIA_HANDLE_G722_MPEG_BUG) && (PJMEDIA_HANDLE_G722_MPEG_BUG != 0) if (pt == PJMEDIA_RTP_PT_G722) rtpmap.clock_rate = 8000; else rtpmap.clock_rate = codec_info->clock_rate; #else rtpmap.clock_rate = codec_info->clock_rate; #endif /* For audio codecs, rtpmap parameters denotes the number * of channels, which can be omited if the value is 1. */ if (codec_info->type == PJMEDIA_TYPE_AUDIO && codec_info->channel_cnt > 1) { /* Can only support one digit channel count */ pj_assert(codec_info->channel_cnt < 10); tmp_param[0] = (char)('0' + codec_info->channel_cnt); rtpmap.param.ptr = tmp_param; rtpmap.param.slen = 1; } else { rtpmap.param.ptr = ""; rtpmap.param.slen = 0; } if (pt >= 96 || pjmedia_add_rtpmap_for_static_pt) { pjmedia_sdp_rtpmap_to_attr(pool, &rtpmap, &attr); m->attr[m->attr_count++] = attr; } /* Add fmtp params */ if (codec_param.setting.dec_fmtp.cnt > 0) { enum { MAX_FMTP_STR_LEN = 160 }; char buf[MAX_FMTP_STR_LEN]; unsigned buf_len = 0, ii; pjmedia_codec_fmtp *dec_fmtp = &codec_param.setting.dec_fmtp; /* Print codec PT */ buf_len += pj_ansi_snprintf(buf, MAX_FMTP_STR_LEN - buf_len, "%d", pt); for (ii = 0; ii < dec_fmtp->cnt; ++ii) { pj_size_t test_len = 2; /* Check if buf still available */ test_len = dec_fmtp->param[ii].val.slen + dec_fmtp->param[ii].name.slen + 2; if (test_len + buf_len >= MAX_FMTP_STR_LEN) return PJ_ETOOBIG; /* Print delimiter */ buf_len += pj_ansi_snprintf(&buf[buf_len], MAX_FMTP_STR_LEN - buf_len, (ii == 0?" ":";")); /* Print an fmtp param */ if (dec_fmtp->param[ii].name.slen) buf_len += pj_ansi_snprintf( &buf[buf_len], MAX_FMTP_STR_LEN - buf_len, "%.*s=%.*s", (int)dec_fmtp->param[ii].name.slen, dec_fmtp->param[ii].name.ptr, (int)dec_fmtp->param[ii].val.slen, dec_fmtp->param[ii].val.ptr); else buf_len += pj_ansi_snprintf(&buf[buf_len], MAX_FMTP_STR_LEN - buf_len, "%.*s", (int)dec_fmtp->param[ii].val.slen, dec_fmtp->param[ii].val.ptr); } attr = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_attr); attr->name = pj_str("fmtp"); attr->value = pj_strdup3(pool, buf); m->attr[m->attr_count++] = attr; } /* Find maximum bitrate in this media */ if (max_bitrate < codec_param.info.max_bps) max_bitrate = codec_param.info.max_bps; /* List clock rate of audio codecs for generating telephone-event */ #if defined(PJMEDIA_RTP_PT_TELEPHONE_EVENTS) && \ PJMEDIA_RTP_PT_TELEPHONE_EVENTS != 0 if (endpt->has_telephone_event) { unsigned j; for (j=0; jhas_telephone_event) { for (i=0; i 127) { /* Not found? Find more, but now starting from 96 */ pt = 96; j = 0; while (j < used_pt_num && pt < PJMEDIA_RTP_PT_TELEPHONE_EVENTS) { if (pt == used_pt[j]) { pt++; j = 0; } else { j++; } } if (pt >= PJMEDIA_RTP_PT_TELEPHONE_EVENTS) { /* No more available PT */ PJ_LOG(4,(THIS_FILE, "Warning: no available dynamic " "payload type for telephone-event")); break; } } } used_pt[used_pt_num++] = pt; /* Print tel-event PT */ pj_ansi_snprintf(buf, sizeof(buf), "%d", pt); m->desc.fmt[m->desc.fmt_count++] = pj_strdup3(pool, buf); /* Add rtpmap. */ attr = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_attr); attr->name = pj_str("rtpmap"); pj_ansi_snprintf(buf, sizeof(buf), "%d telephone-event/%d", pt, televent_clockrates[i]); attr->value = pj_strdup3(pool, buf); m->attr[m->attr_count++] = attr; /* Add fmtp */ attr = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_attr); attr->name = pj_str("fmtp"); #if defined(PJMEDIA_HAS_DTMF_FLASH) && PJMEDIA_HAS_DTMF_FLASH!= 0 pj_ansi_snprintf(buf, sizeof(buf), "%d 0-16", pt); #else pj_ansi_snprintf(buf, sizeof(buf), "%d 0-15", pt); #endif attr->value = pj_strdup3(pool, buf); m->attr[m->attr_count++] = attr; } } #endif /* Put bandwidth info in media level using bandwidth modifier "TIAS" * (RFC3890). */ if (max_bitrate && pjmedia_add_bandwidth_tias_in_sdp) { const pj_str_t STR_BANDW_MODIFIER = { "TIAS", 4 }; pjmedia_sdp_bandw *b; b = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_bandw); b->modifier = STR_BANDW_MODIFIER; b->value = max_bitrate; m->bandw[m->bandw_count++] = b; } *p_m = m; return PJ_SUCCESS; } #if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0) /* Create m=video SDP media line */ PJ_DEF(pj_status_t) pjmedia_endpt_create_video_sdp(pjmedia_endpt *endpt, pj_pool_t *pool, const pjmedia_sock_info *si, unsigned options, pjmedia_sdp_media **p_m) { const pj_str_t STR_VIDEO = { "video", 5 }; pjmedia_sdp_media *m; pjmedia_vid_codec_info codec_info[PJMEDIA_VID_CODEC_MGR_MAX_CODECS]; unsigned codec_prio[PJMEDIA_VID_CODEC_MGR_MAX_CODECS]; pjmedia_sdp_attr *attr; unsigned cnt, i; unsigned max_bitrate = 0; pj_status_t status; PJ_UNUSED_ARG(options); /* Make sure video codec manager is instantiated */ if (!pjmedia_vid_codec_mgr_instance()) pjmedia_vid_codec_mgr_create(endpt->pool, NULL); /* Create and init basic SDP media */ m = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media); status = init_sdp_media(m, pool, &STR_VIDEO, si); if (status != PJ_SUCCESS) return status; cnt = PJ_ARRAY_SIZE(codec_info); status = pjmedia_vid_codec_mgr_enum_codecs(NULL, &cnt, codec_info, codec_prio); /* Check that there are not too many codecs */ PJ_ASSERT_RETURN(0 <= PJMEDIA_MAX_SDP_FMT, PJ_ETOOMANY); /* Add format, rtpmap, and fmtp (when applicable) for each codec */ for (i=0; i PJMEDIA_MAX_SDP_FMT) { /* Too many codecs, perhaps it is better to tell application by * returning appropriate status code. */ PJ_PERROR(3,(THIS_FILE, PJ_ETOOMANY, "Skipping some video codecs")); break; } /* Must support RTP packetization and bidirectional */ if ((codec_info[i].packings & PJMEDIA_VID_PACKING_PACKETS) == 0 || codec_info[i].dir != PJMEDIA_DIR_ENCODING_DECODING) { continue; } pjmedia_vid_codec_mgr_get_default_param(NULL, &codec_info[i], &codec_param); fmt = &m->desc.fmt[m->desc.fmt_count++]; fmt->ptr = (char*) pj_pool_alloc(pool, 8); fmt->slen = pj_utoa(codec_info[i].pt, fmt->ptr); rtpmap.pt = *fmt; /* Encoding name */ rtpmap.enc_name = codec_info[i].encoding_name; /* Clock rate */ rtpmap.clock_rate = codec_info[i].clock_rate; if (codec_info[i].pt >= 96 || pjmedia_add_rtpmap_for_static_pt) { pjmedia_sdp_rtpmap_to_attr(pool, &rtpmap, &attr); m->attr[m->attr_count++] = attr; } /* Add fmtp params */ if (codec_param.dec_fmtp.cnt > 0) { enum { MAX_FMTP_STR_LEN = 160 }; char buf[MAX_FMTP_STR_LEN]; unsigned buf_len = 0, j; pjmedia_codec_fmtp *dec_fmtp = &codec_param.dec_fmtp; /* Print codec PT */ buf_len += pj_ansi_snprintf(buf, MAX_FMTP_STR_LEN - buf_len, "%d", codec_info[i].pt); for (j = 0; j < dec_fmtp->cnt; ++j) { pj_size_t test_len = 2; /* Check if buf still available */ test_len = dec_fmtp->param[j].val.slen + dec_fmtp->param[j].name.slen + 2; if (test_len + buf_len >= MAX_FMTP_STR_LEN) return PJ_ETOOBIG; /* Print delimiter */ buf_len += pj_ansi_snprintf(&buf[buf_len], MAX_FMTP_STR_LEN - buf_len, (j == 0?" ":";")); /* Print an fmtp param */ if (dec_fmtp->param[j].name.slen) buf_len += pj_ansi_snprintf( &buf[buf_len], MAX_FMTP_STR_LEN - buf_len, "%.*s=%.*s", (int)dec_fmtp->param[j].name.slen, dec_fmtp->param[j].name.ptr, (int)dec_fmtp->param[j].val.slen, dec_fmtp->param[j].val.ptr); else buf_len += pj_ansi_snprintf(&buf[buf_len], MAX_FMTP_STR_LEN - buf_len, "%.*s", (int)dec_fmtp->param[j].val.slen, dec_fmtp->param[j].val.ptr); } attr = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_attr); attr->name = pj_str("fmtp"); attr->value = pj_strdup3(pool, buf); m->attr[m->attr_count++] = attr; } /* Find maximum bitrate in this media */ vfd = pjmedia_format_get_video_format_detail(&codec_param.enc_fmt, PJ_TRUE); if (vfd && max_bitrate < vfd->max_bps) max_bitrate = vfd->max_bps; } /* Put bandwidth info in media level using bandwidth modifier "TIAS" * (RFC3890). */ if (max_bitrate && pjmedia_add_bandwidth_tias_in_sdp) { const pj_str_t STR_BANDW_MODIFIER = { "TIAS", 4 }; pjmedia_sdp_bandw *b; b = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_bandw); b->modifier = STR_BANDW_MODIFIER; b->value = max_bitrate; m->bandw[m->bandw_count++] = b; } *p_m = m; return PJ_SUCCESS; } #endif /* PJMEDIA_HAS_VIDEO */ /** * Create a "blank" SDP session description. The SDP will contain basic SDP * fields such as origin, time, and name, but without any media lines. */ PJ_DEF(pj_status_t) pjmedia_endpt_create_base_sdp( pjmedia_endpt *endpt, pj_pool_t *pool, const pj_str_t *sess_name, const pj_sockaddr *origin, pjmedia_sdp_session **p_sdp) { char tmp_addr[PJ_INET6_ADDRSTRLEN]; pj_time_val tv; pjmedia_sdp_session *sdp; /* Sanity check arguments */ PJ_ASSERT_RETURN(endpt && pool && p_sdp, PJ_EINVAL); sdp = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_session); pj_gettimeofday(&tv); sdp->origin.user = pj_str("-"); sdp->origin.version = sdp->origin.id = tv.sec + 2208988800UL; sdp->origin.net_type = STR_IN; if (origin->addr.sa_family == pj_AF_INET()) { sdp->origin.addr_type = STR_IP4; } else if (origin->addr.sa_family == pj_AF_INET6()) { sdp->origin.addr_type = STR_IP6; } else { pj_assert(!"Invalid address family"); return PJ_EAFNOTSUP; } pj_strdup2(pool, &sdp->origin.addr, pj_sockaddr_print(origin, tmp_addr, sizeof(tmp_addr), 0)); if (sess_name) pj_strdup(pool, &sdp->name, sess_name); else sdp->name = STR_SDP_NAME; /* SDP time and attributes. */ sdp->time.start = sdp->time.stop = 0; sdp->attr_count = 0; /* Done */ *p_sdp = sdp; return PJ_SUCCESS; } /** * Create a SDP session description that describes the endpoint * capability. */ PJ_DEF(pj_status_t) pjmedia_endpt_create_sdp( pjmedia_endpt *endpt, pj_pool_t *pool, unsigned stream_cnt, const pjmedia_sock_info sock_info[], pjmedia_sdp_session **p_sdp ) { const pj_sockaddr *addr0; pjmedia_sdp_session *sdp; pjmedia_sdp_media *m; pj_status_t status; /* Sanity check arguments */ PJ_ASSERT_RETURN(endpt && pool && p_sdp && stream_cnt, PJ_EINVAL); PJ_ASSERT_RETURN(stream_cnt < PJMEDIA_MAX_SDP_MEDIA, PJ_ETOOMANY); addr0 = &sock_info[0].rtp_addr_name; /* Create and initialize basic SDP session */ status = pjmedia_endpt_create_base_sdp(endpt, pool, NULL, addr0, &sdp); if (status != PJ_SUCCESS) return status; /* Audio is first, by convention */ status = pjmedia_endpt_create_audio_sdp(endpt, pool, &sock_info[0], 0, &m); if (status != PJ_SUCCESS) return status; sdp->media[sdp->media_count++] = m; #if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0) { unsigned i; /* The remaining stream, if any, are videos (by convention as well) */ for (i=1; imedia[sdp->media_count++] = m; } } #endif /* Done */ *p_sdp = sdp; return PJ_SUCCESS; } #if PJ_LOG_MAX_LEVEL >= 3 static const char *good_number(char *buf, pj_int32_t val) { if (val < 1000) { pj_ansi_sprintf(buf, "%d", val); } else if (val < 1000000) { pj_ansi_sprintf(buf, "%d.%dK", val / 1000, (val % 1000) / 100); } else { pj_ansi_sprintf(buf, "%d.%02dM", val / 1000000, (val % 1000000) / 10000); } return buf; } #endif PJ_DEF(pj_status_t) pjmedia_endpt_dump(pjmedia_endpt *endpt) { #if PJ_LOG_MAX_LEVEL >= 3 unsigned i, count; pjmedia_codec_info codec_info[32]; unsigned prio[32]; PJ_LOG(3,(THIS_FILE, "Dumping PJMEDIA capabilities:")); count = PJ_ARRAY_SIZE(codec_info); if (pjmedia_codec_mgr_enum_codecs(&endpt->codec_mgr, &count, codec_info, prio) != PJ_SUCCESS) { PJ_LOG(3,(THIS_FILE, " -error: failed to enum codecs")); return PJ_SUCCESS; } PJ_LOG(3,(THIS_FILE, " Total number of installed codecs: %d", count)); for (i=0; icodec_mgr, &codec_info[i], ¶m) != PJ_SUCCESS) { pj_bzero(¶m, sizeof(pjmedia_codec_param)); } PJ_LOG(3,(THIS_FILE, " %s codec #%2d: pt=%d (%.*s @%dKHz/%d, %sbps, %dms%s%s%s%s%s)", type, i, codec_info[i].pt, (int)codec_info[i].encoding_name.slen, codec_info[i].encoding_name.ptr, codec_info[i].clock_rate/1000, codec_info[i].channel_cnt, good_number(bps, param.info.avg_bps), param.info.frm_ptime * param.setting.frm_per_pkt, (param.setting.vad ? " vad" : ""), (param.setting.cng ? " cng" : ""), (param.setting.plc ? " plc" : ""), (param.setting.penh ? " penh" : ""), (prio[i]==PJMEDIA_CODEC_PRIO_DISABLED?" disabled":""))); } #endif return PJ_SUCCESS; } PJ_DEF(pj_status_t) pjmedia_endpt_atexit( pjmedia_endpt *endpt, pjmedia_endpt_exit_callback func) { exit_cb *new_cb; PJ_ASSERT_RETURN(endpt && func, PJ_EINVAL); if (endpt->quit_flag) return PJ_EINVALIDOP; new_cb = PJ_POOL_ZALLOC_T(endpt->pool, exit_cb); new_cb->func = func; pj_enter_critical_section(); pj_list_push_back(&endpt->exit_cb_list, new_cb); pj_leave_critical_section(); return PJ_SUCCESS; }