diff options
Diffstat (limited to 'pjmedia/src/pjmedia/endpoint.c')
-rw-r--r-- | pjmedia/src/pjmedia/endpoint.c | 942 |
1 files changed, 942 insertions, 0 deletions
diff --git a/pjmedia/src/pjmedia/endpoint.c b/pjmedia/src/pjmedia/endpoint.c new file mode 100644 index 0000000..e20a5a4 --- /dev/null +++ b/pjmedia/src/pjmedia/endpoint.c @@ -0,0 +1,942 @@ +/* $Id: endpoint.c 3999 2012-03-30 07:10:13Z bennylp $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * 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 <pjmedia/endpoint.h> +#include <pjmedia/errno.h> +#include <pjmedia/sdp.h> +#include <pjmedia/vid_codec.h> +#include <pjmedia-audiodev/audiodev.h> +#include <pj/assert.h> +#include <pj/ioqueue.h> +#include <pj/lock.h> +#include <pj/log.h> +#include <pj/os.h> +#include <pj/pool.h> +#include <pj/sock.h> +#include <pj/string.h> + + +#define THIS_FILE "endpoint.c" + +static const pj_str_t STR_AUDIO = { "audio", 5}; +static const pj_str_t STR_VIDEO = { "video", 5}; +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; + + + +/* 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_create(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; + + /* Sound */ + 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; i<worker_cnt; ++i) { + status = pj_thread_create( endpt->pool, "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; i<endpt->thread_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_destroy (pjmedia_endpt *endpt) +{ + exit_cb *ecb; + unsigned i; + + PJ_ASSERT_RETURN(endpt, PJ_EINVAL); + + endpt->quit_flag = 1; + + /* Destroy threads */ + for (i=0; i<endpt->thread_cnt; ++i) { + if (endpt->thread[i]) { + pj_thread_join(endpt->thread[i]); + pj_thread_destroy(endpt->thread[i]); + endpt->thread[i] = NULL; + } + } + + /* 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]; +} + +/** + * 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; + + 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); + + /* 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; i<endpt->codec_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; + + 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++]; + + fmt->ptr = (char*) pj_pool_alloc(pool, 8); + fmt->slen = pj_utoa(codec_info->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 (codec_info->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 (codec_info->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, i; + 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", + codec_info->pt); + + for (i = 0; i < dec_fmtp->cnt; ++i) { + unsigned test_len = 2; + + /* Check if buf still available */ + test_len = dec_fmtp->param[i].val.slen + + dec_fmtp->param[i].name.slen; + 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, + (i == 0?" ":";")); + + /* Print an fmtp param */ + if (dec_fmtp->param[i].name.slen) + buf_len += pj_ansi_snprintf( + &buf[buf_len], + MAX_FMTP_STR_LEN - buf_len, + "%.*s=%.*s", + (int)dec_fmtp->param[i].name.slen, + dec_fmtp->param[i].name.ptr, + (int)dec_fmtp->param[i].val.slen, + dec_fmtp->param[i].val.ptr); + else + buf_len += pj_ansi_snprintf(&buf[buf_len], + MAX_FMTP_STR_LEN - buf_len, + "%.*s", + (int)dec_fmtp->param[i].val.slen, + dec_fmtp->param[i].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; + } + +#if defined(PJMEDIA_RTP_PT_TELEPHONE_EVENTS) && \ + PJMEDIA_RTP_PT_TELEPHONE_EVENTS != 0 + /* + * Add support telephony event + */ + if (endpt->has_telephone_event) { + m->desc.fmt[m->desc.fmt_count++] = + pj_str(PJMEDIA_RTP_PT_TELEPHONE_EVENTS_STR); + + /* Add rtpmap. */ + attr = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_attr); + attr->name = pj_str("rtpmap"); + attr->value = pj_str(PJMEDIA_RTP_PT_TELEPHONE_EVENTS_STR + " telephone-event/8000"); + m->attr[m->attr_count++] = attr; + + /* Add fmtp */ + attr = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_attr); + attr->name = pj_str("fmtp"); + attr->value = pj_str(PJMEDIA_RTP_PT_TELEPHONE_EVENTS_STR " 0-15"); + m->attr[m->attr_count++] = attr; + } +#endif + + /* Put bandwidth info in media level using bandwidth modifier "TIAS" + * (RFC3890). + */ + if (max_bitrate) { + 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<cnt; ++i) { + pjmedia_sdp_rtpmap rtpmap; + pjmedia_vid_codec_param codec_param; + pj_str_t *fmt; + pjmedia_video_format_detail *vfd; + + pj_bzero(&rtpmap, sizeof(rtpmap)); + + if (codec_prio[i] == PJMEDIA_CODEC_PRIO_DISABLED) + break; + + if (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) { + unsigned test_len = 2; + + /* Check if buf still available */ + test_len = dec_fmtp->param[j].val.slen + + dec_fmtp->param[j].name.slen; + 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) { + 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) +{ + 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; + pj_strdup2(pool, &sdp->origin.addr, + pj_inet_ntoa(origin->ipv4.sin_addr)); + } else if (origin->addr.sa_family == pj_AF_INET6()) { + char tmp_addr[PJ_INET6_ADDRSTRLEN]; + + sdp->origin.addr_type = STR_IP6; + pj_strdup2(pool, &sdp->origin.addr, + pj_sockaddr_print(origin, tmp_addr, sizeof(tmp_addr), 0)); + + } else { + pj_assert(!"Invalid address family"); + return PJ_EAFNOTSUP; + } + + 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; i<stream_cnt; ++i) { + status = pjmedia_endpt_create_video_sdp(endpt, pool, + &sock_info[i], 0, &m); + if (status != PJ_SUCCESS) + return status; + sdp->media[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; i<count; ++i) { + const char *type; + pjmedia_codec_param param; + char bps[32]; + + switch (codec_info[i].type) { + case PJMEDIA_TYPE_AUDIO: + type = "Audio"; break; + case PJMEDIA_TYPE_VIDEO: + type = "Video"; break; + default: + type = "Unknown type"; break; + } + + if (pjmedia_codec_mgr_get_default_param(&endpt->codec_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; +} |