summaryrefslogtreecommitdiff
path: root/pjmedia/src/pjmedia/session.c
diff options
context:
space:
mode:
Diffstat (limited to 'pjmedia/src/pjmedia/session.c')
-rw-r--r--pjmedia/src/pjmedia/session.c1666
1 files changed, 833 insertions, 833 deletions
diff --git a/pjmedia/src/pjmedia/session.c b/pjmedia/src/pjmedia/session.c
index 773bb7be..98b359f2 100644
--- a/pjmedia/src/pjmedia/session.c
+++ b/pjmedia/src/pjmedia/session.c
@@ -1,833 +1,833 @@
-/* $Id$ */
-/*
- * Copyright (C) 2003-2006 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/session.h>
-#include <pj/log.h>
-#include <pj/os.h>
-#include <pj/pool.h>
-#include <pj/string.h>
-
-typedef struct pj_media_stream_desc
-{
- pj_media_stream_info info;
- pj_media_stream_t *enc_stream, *dec_stream;
-} pj_media_stream_desc;
-
-struct pj_media_session_t
-{
- pj_pool_t *pool;
- pj_med_mgr_t *mediamgr;
- unsigned stream_cnt;
- pj_media_stream_desc *stream_desc[PJSDP_MAX_MEDIA];
-};
-
-#define THIS_FILE "session.c"
-
-#define PJ_MEDIA_SESSION_SIZE (48*1024)
-#define PJ_MEDIA_SESSION_INC 1024
-
-static const pj_str_t ID_AUDIO = { "audio", 5};
-static const pj_str_t ID_VIDEO = { "video", 5};
-static const pj_str_t ID_IN = { "IN", 2 };
-static const pj_str_t ID_IP4 = { "IP4", 3};
-static const pj_str_t ID_RTP_AVP = { "RTP/AVP", 7 };
-static const pj_str_t ID_SDP_NAME = { "pjmedia", 7 };
-
-static void session_init (pj_media_session_t *ses)
-{
- pj_memset (ses, 0, sizeof(pj_media_session_t));
-}
-
-
-/**
- * Create new session offering.
- */
-PJ_DEF(pj_media_session_t*)
-pj_media_session_create (pj_med_mgr_t *mgr, const pj_media_sock_info *sock_info)
-{
- pj_pool_factory *pf;
- pj_pool_t *pool;
- pj_media_session_t *session;
- pj_media_stream_desc *sd;
- unsigned i, codec_cnt;
- pj_codec_mgr *cm;
- const pj_codec_id *codecs[PJSDP_MAX_FMT];
-
- pf = pj_med_mgr_get_pool_factory(mgr);
-
- pool = pj_pool_create( pf, "session", PJ_MEDIA_SESSION_SIZE, PJ_MEDIA_SESSION_INC, NULL);
- if (!pool)
- return NULL;
-
- session = pj_pool_alloc(pool, sizeof(pj_media_session_t));
- if (!session)
- return NULL;
-
- session_init (session);
-
- session->pool = pool;
- session->mediamgr = mgr;
-
- /* Create first stream */
- sd = pj_pool_calloc (pool, 1, sizeof(pj_media_stream_desc));
- if (!sd)
- return NULL;
-
- sd->info.type = ID_AUDIO;
- sd->info.dir = PJ_MEDIA_DIR_ENCODING_DECODING;
- sd->info.transport = ID_RTP_AVP;
- pj_memcpy(&sd->info.sock_info, sock_info, sizeof(*sock_info));
-
- /* Enum audio codecs. */
- sd->info.fmt_cnt = 0;
- cm = pj_med_mgr_get_codec_mgr (mgr);
- codec_cnt = pj_codec_mgr_enum_codecs(cm, PJSDP_MAX_FMT, codecs);
- if (codec_cnt > PJSDP_MAX_FMT) codec_cnt = PJSDP_MAX_FMT;
- for (i=0; i<codec_cnt; ++i) {
- if (codecs[i]->type != PJ_MEDIA_TYPE_AUDIO)
- continue;
-
- sd->info.fmt[sd->info.fmt_cnt].pt = codecs[i]->pt;
- sd->info.fmt[sd->info.fmt_cnt].sample_rate = codecs[i]->sample_rate;
- pj_strdup (pool, &sd->info.fmt[sd->info.fmt_cnt].encoding_name, &codecs[i]->encoding_name);
- ++sd->info.fmt_cnt;
- }
-
- session->stream_desc[session->stream_cnt++] = sd;
-
- return session;
-}
-
-static int sdp_check (const pjsdp_session_desc *sdp)
-{
- int has_conn = 0;
- unsigned i;
-
- if (sdp->conn)
- has_conn = 1;
-
- if (sdp->media_count == 0) {
- PJ_LOG(4,(THIS_FILE, "SDP check failed: no media stream definition"));
- return -1;
- }
-
- for (i=0; i<sdp->media_count; ++i) {
- pjsdp_media_desc *m = sdp->media[i];
-
- if (!m) {
- pj_assert(0);
- return -1;
- }
-
- if (m->desc.fmt_count == 0) {
- PJ_LOG(4,(THIS_FILE, "SDP check failed: no format listed in media stream"));
- return -1;
- }
-
- if (!has_conn && m->conn == NULL) {
- PJ_LOG(4,(THIS_FILE, "SDP check failed: no connection information for media"));
- return -1;
- }
- }
-
- return 0;
-}
-
-/*
- * Create local stream definition that matches SDP received from peer.
- */
-static pj_media_stream_desc*
-create_stream_from_sdp (pj_pool_t *pool, pj_med_mgr_t *mgr, const pjsdp_conn_info *conn,
- const pjsdp_media_desc *m, const pj_media_sock_info *sock_info)
-{
- pj_media_stream_desc *sd;
-
- sd = pj_pool_calloc (pool, 1, sizeof(pj_media_stream_desc));
- if (!sd) {
- PJ_LOG(2,(THIS_FILE, "No memory to allocate stream descriptor"));
- return NULL;
- }
-
- if (pj_stricmp(&conn->net_type, &ID_IN)==0 &&
- pj_stricmp(&conn->addr_type, &ID_IP4)==0 &&
- pj_stricmp(&m->desc.media, &ID_AUDIO)==0 &&
- pj_stricmp(&m->desc.transport, &ID_RTP_AVP) == 0)
- {
- /*
- * Got audio stream.
- */
- unsigned i, codec_cnt;
- pj_codec_mgr *cm;
- const pj_codec_id *codecs[PJSDP_MAX_FMT];
-
- sd->info.type = ID_AUDIO;
- sd->info.transport = ID_RTP_AVP;
- pj_memcpy(&sd->info.sock_info, sock_info, sizeof(*sock_info));
- sd->info.rem_port = m->desc.port;
- pj_strdup (pool, &sd->info.rem_addr, &conn->addr);
-
- /* Enum audio codecs. */
- sd->info.fmt_cnt = 0;
- cm = pj_med_mgr_get_codec_mgr (mgr);
- codec_cnt = pj_codec_mgr_enum_codecs (cm, PJSDP_MAX_FMT, codecs);
- if (codec_cnt > PJSDP_MAX_FMT) codec_cnt = PJSDP_MAX_FMT;
-
- /* Find just one codec which we can support. */
- for (i=0; i<m->desc.fmt_count && sd->info.fmt_cnt == 0; ++i) {
- unsigned j, fmt_i;
-
- /* For static payload, just match payload type. */
- /* Else match clock rate and encoding name. */
- fmt_i = pj_strtoul(&m->desc.fmt[i]);
- if (fmt_i < PJ_RTP_PT_DYNAMIC) {
- for (j=0; j<codec_cnt; ++j) {
- if (codecs[j]->pt == fmt_i) {
- sd->info.fmt_cnt = 1;
- sd->info.fmt[0].type = PJ_MEDIA_TYPE_AUDIO;
- sd->info.fmt[0].pt = codecs[j]->pt;
- sd->info.fmt[0].sample_rate = codecs[j]->sample_rate;
- pj_strdup (pool, &sd->info.fmt[0].encoding_name, &codecs[j]->encoding_name);
- break;
- }
- }
- } else {
-
- /* Find the rtpmap for the payload type. */
- const pjsdp_rtpmap_attr *rtpmap = pjsdp_media_desc_find_rtpmap (m, fmt_i);
-
- /* Don't accept the media if no rtpmap for dynamic PT. */
- if (rtpmap == NULL) {
- PJ_LOG(4,(THIS_FILE, "SDP: No rtpmap found for payload id %d", m->desc.fmt[i]));
- continue;
- }
-
- /* Check whether we can take this codec. */
- for (j=0; j<codec_cnt; ++j) {
- if (rtpmap->clock_rate == codecs[j]->sample_rate &&
- pj_stricmp(&rtpmap->encoding_name, &codecs[j]->encoding_name) == 0)
- {
- sd->info.fmt_cnt = 1;
- sd->info.fmt[0].type = PJ_MEDIA_TYPE_AUDIO;
- sd->info.fmt[0].pt = codecs[j]->pt;
- sd->info.fmt[0].sample_rate = codecs[j]->sample_rate;
- sd->info.fmt[0].encoding_name = codecs[j]->encoding_name;
- break;
- }
- }
- }
- }
-
- /* Match codec and direction. */
- if (sd->info.fmt_cnt == 0 || m->desc.port == 0 ||
- pjsdp_media_desc_has_attr(m, PJSDP_ATTR_INACTIVE))
- {
- sd->info.dir = PJ_MEDIA_DIR_NONE;
- }
- else if (pjsdp_media_desc_has_attr(m, PJSDP_ATTR_RECV_ONLY)) {
- sd->info.dir = PJ_MEDIA_DIR_ENCODING;
- }
- else if (pjsdp_media_desc_has_attr(m, PJSDP_ATTR_SEND_ONLY)) {
- sd->info.dir = PJ_MEDIA_DIR_DECODING;
- }
- else {
- sd->info.dir = PJ_MEDIA_DIR_ENCODING_DECODING;
- }
-
- } else {
- /* Unsupported media stream. */
- unsigned fmt_num;
- const pjsdp_rtpmap_attr *rtpmap = NULL;
-
- pj_strdup(pool, &sd->info.type, &m->desc.media);
- pj_strdup(pool, &sd->info.transport, &m->desc.transport);
- pj_memset(&sd->info.sock_info, 0, sizeof(*sock_info));
- pj_strdup (pool, &sd->info.rem_addr, &conn->addr);
- sd->info.rem_port = m->desc.port;
-
- /* Just put one format and rtpmap, so that we don't have to make
- * special exception when we convert this stream to SDP.
- */
-
- /* Find the rtpmap for the payload type. */
- fmt_num = pj_strtoul(&m->desc.fmt[0]);
- rtpmap = pjsdp_media_desc_find_rtpmap (m, fmt_num);
-
- sd->info.fmt_cnt = 1;
- if (pj_stricmp(&m->desc.media, &ID_VIDEO)==0) {
- sd->info.fmt[0].type = PJ_MEDIA_TYPE_VIDEO;
- sd->info.fmt[0].pt = fmt_num;
- if (rtpmap) {
- pj_strdup (pool, &sd->info.fmt[0].encoding_name,
- &rtpmap->encoding_name);
- sd->info.fmt[0].sample_rate = rtpmap->clock_rate;
- }
- } else {
- sd->info.fmt[0].type = PJ_MEDIA_TYPE_UNKNOWN;
- pj_strdup(pool, &sd->info.fmt[0].encoding_name, &m->desc.fmt[0]);
- }
-
- sd->info.dir = PJ_MEDIA_DIR_NONE;
- }
-
- return sd;
-}
-
-/**
- * Create new session based on peer's offering.
- */
-PJ_DEF(pj_media_session_t*)
-pj_media_session_create_from_sdp (pj_med_mgr_t *mgr, const pjsdp_session_desc *sdp,
- const pj_media_sock_info *sock_info)
-{
- pj_pool_factory *pf;
- pj_pool_t *pool;
- pj_media_session_t *session;
- unsigned i;
-
- if (sdp_check(sdp) != 0)
- return NULL;
-
- pf = pj_med_mgr_get_pool_factory(mgr);
- pool = pj_pool_create( pf, "session", PJ_MEDIA_SESSION_SIZE, PJ_MEDIA_SESSION_INC, NULL);
- if (!pool)
- return NULL;
-
- session = pj_pool_alloc(pool, sizeof(pj_media_session_t));
- if (!session) {
- PJ_LOG(3,(THIS_FILE, "No memory to create media session descriptor"));
- pj_pool_release (pool);
- return NULL;
- }
-
- session_init (session);
-
- session->pool = pool;
- session->mediamgr = mgr;
-
- /* Enumerate each media stream and create our peer. */
- for (i=0; i<sdp->media_count; ++i) {
- const pjsdp_conn_info *conn;
- const pjsdp_media_desc *m;
- pj_media_stream_desc *sd;
-
- m = sdp->media[i];
- conn = m->conn ? m->conn : sdp->conn;
-
- /*
- * Bug:
- * the sock_info below is used by more than one 'm' lines
- */
- PJ_TODO(SUPPORT_MORE_THAN_ONE_SDP_M_LINES)
-
- sd = create_stream_from_sdp (pool, mgr, conn, m, sock_info);
- pj_assert (sd);
-
- session->stream_desc[session->stream_cnt++] = sd;
- }
-
- return session;
-}
-
-/**
- * Duplicate session. The new session is inactive.
- */
-PJ_DEF(pj_media_session_t*)
-pj_media_session_clone (const pj_media_session_t *rhs)
-{
- pj_pool_factory *pf;
- pj_pool_t *pool;
- pj_media_session_t *session;
- unsigned i;
-
- pf = pj_med_mgr_get_pool_factory(rhs->mediamgr);
- pool = pj_pool_create( pf, "session", PJ_MEDIA_SESSION_SIZE, PJ_MEDIA_SESSION_INC, NULL);
- if (!pool) {
- return NULL;
- }
-
- session = pj_pool_alloc (pool, sizeof(*session));
- if (!session) {
- PJ_LOG(3,(THIS_FILE, "No memory to create media session descriptor"));
- pj_pool_release (pool);
- return NULL;
- }
-
- session->pool = pool;
- session->mediamgr = rhs->mediamgr;
- session->stream_cnt = rhs->stream_cnt;
-
- for (i=0; i<rhs->stream_cnt; ++i) {
- pj_media_stream_desc *sd1 = pj_pool_alloc (session->pool, sizeof(pj_media_stream_desc));
- const pj_media_stream_desc *sd2 = rhs->stream_desc[i];
-
- if (!sd1) {
- PJ_LOG(3,(THIS_FILE, "No memory to create media stream descriptor"));
- pj_pool_release (pool);
- return NULL;
- }
-
- session->stream_desc[i] = sd1;
- sd1->enc_stream = sd1->dec_stream = NULL;
- pj_strdup (pool, &sd1->info.type, &sd2->info.type);
- sd1->info.dir = sd2->info.dir;
- pj_strdup (pool, &sd1->info.transport, &sd2->info.transport);
- pj_memcpy(&sd1->info.sock_info, &sd2->info.sock_info, sizeof(pj_media_sock_info));
- pj_strdup (pool, &sd1->info.rem_addr, &sd2->info.rem_addr);
- sd1->info.rem_port = sd2->info.rem_port;
- sd1->info.fmt_cnt = sd2->info.fmt_cnt;
- pj_memcpy (sd1->info.fmt, sd2->info.fmt, sizeof(sd2->info.fmt));
- }
-
- return session;
-}
-
-/**
- * Create SDP description from the session.
- */
-PJ_DEF(pjsdp_session_desc*)
-pj_media_session_create_sdp (const pj_media_session_t *session, pj_pool_t *pool,
- pj_bool_t only_first_fmt)
-{
- pjsdp_session_desc *sdp;
- pj_time_val tv;
- unsigned i;
- pj_media_sock_info *c_addr = NULL;
-
- if (session->stream_cnt == 0) {
- pj_assert(0);
- return NULL;
- }
-
- sdp = pj_pool_calloc (pool, 1, sizeof(pjsdp_session_desc));
- if (!sdp) {
- PJ_LOG(3,(THIS_FILE, "No memory to allocate SDP session descriptor"));
- return NULL;
- }
-
- pj_gettimeofday(&tv);
-
- sdp->origin.user = pj_str("-");
- sdp->origin.version = sdp->origin.id = tv.sec + 2208988800UL;
- sdp->origin.net_type = ID_IN;
- sdp->origin.addr_type = ID_IP4;
- sdp->origin.addr = *pj_gethostname();
-
- sdp->name = ID_SDP_NAME;
-
- /* If all media addresses are the same, then put the connection
- * info in the session level, otherwise put it in media stream
- * level.
- */
- for (i=0; i<session->stream_cnt; ++i) {
- if (c_addr == NULL) {
- c_addr = &session->stream_desc[i]->info.sock_info;
- } else if (c_addr->rtp_addr_name.sin_addr.s_addr != session->stream_desc[i]->info.sock_info.rtp_addr_name.sin_addr.s_addr)
- {
- c_addr = NULL;
- break;
- }
- }
-
- if (c_addr) {
- /* All addresses are the same, put connection info in session level. */
- sdp->conn = pj_pool_alloc (pool, sizeof(pjsdp_conn_info));
- if (!sdp->conn) {
- PJ_LOG(2,(THIS_FILE, "No memory to allocate SDP connection info"));
- return NULL;
- }
-
- sdp->conn->net_type = ID_IN;
- sdp->conn->addr_type = ID_IP4;
- pj_strdup2 (pool, &sdp->conn->addr, pj_inet_ntoa(c_addr->rtp_addr_name.sin_addr));
- }
-
- sdp->time.start = sdp->time.stop = 0;
- sdp->attr_count = 0;
-
- /* Create each media. */
- sdp->media_count = 0;
- for (i=0; i<session->stream_cnt; ++i) {
- const pj_media_stream_desc *sd = session->stream_desc[i];
- pjsdp_media_desc *m;
- unsigned j;
- unsigned fmt_cnt;
- pjsdp_attr *attr;
-
- m = pj_pool_calloc (pool, 1, sizeof(pjsdp_media_desc));
- if (!m) {
- PJ_LOG(3,(THIS_FILE, "No memory to allocate SDP media stream descriptor"));
- return NULL;
- }
-
- sdp->media[sdp->media_count++] = m;
-
- pj_strdup (pool, &m->desc.media, &sd->info.type);
- m->desc.port = pj_ntohs(sd->info.sock_info.rtp_addr_name.sin_port);
- m->desc.port_count = 1;
- pj_strdup (pool, &m->desc.transport, &sd->info.transport);
-
- /* Add format and rtpmap for each codec. */
- m->desc.fmt_count = 0;
- m->attr_count = 0;
- fmt_cnt = sd->info.fmt_cnt;
- if (fmt_cnt > 0 && only_first_fmt)
- fmt_cnt = 1;
- for (j=0; j<fmt_cnt; ++j) {
- pjsdp_rtpmap_attr *rtpmap;
- pj_str_t *fmt = &m->desc.fmt[m->desc.fmt_count++];
-
- if (sd->info.fmt[j].type==PJ_MEDIA_TYPE_UNKNOWN) {
- pj_strdup(pool, fmt, &sd->info.fmt[j].encoding_name);
- } else {
- fmt->ptr = pj_pool_alloc(pool, 8);
- fmt->slen = pj_utoa(sd->info.fmt[j].pt, fmt->ptr);
-
- rtpmap = pj_pool_calloc(pool, 1, sizeof(pjsdp_rtpmap_attr));
- if (rtpmap) {
- m->attr[m->attr_count++] = (pjsdp_attr*)rtpmap;
- rtpmap->type = PJSDP_ATTR_RTPMAP;
- rtpmap->payload_type = sd->info.fmt[j].pt;
- rtpmap->clock_rate = sd->info.fmt[j].sample_rate;
- pj_strdup (pool, &rtpmap->encoding_name, &sd->info.fmt[j].encoding_name);
- } else {
- PJ_LOG(3,(THIS_FILE, "No memory to allocate SDP rtpmap descriptor"));
- }
- }
- }
-
- /* If we don't have connection info in session level, create one. */
- if (sdp->conn == NULL) {
- m->conn = pj_pool_alloc (pool, sizeof(pjsdp_conn_info));
- if (m->conn) {
- m->conn->net_type = ID_IN;
- m->conn->addr_type = ID_IP4;
- pj_strdup2 (pool, &m->conn->addr, pj_inet_ntoa(sd->info.sock_info.rtp_addr_name.sin_addr));
- } else {
- PJ_LOG(3,(THIS_FILE, "No memory to allocate SDP media connection info"));
- return NULL;
- }
- }
-
- /* Add additional attribute to the media stream. */
- attr = pj_pool_alloc(pool, sizeof(pjsdp_attr));
- if (!attr) {
- PJ_LOG(3,(THIS_FILE, "No memory to allocate SDP attribute"));
- return NULL;
- }
- m->attr[m->attr_count++] = attr;
-
- switch (sd->info.dir) {
- case PJ_MEDIA_DIR_NONE:
- attr->type = PJSDP_ATTR_INACTIVE;
- break;
- case PJ_MEDIA_DIR_ENCODING:
- attr->type = PJSDP_ATTR_SEND_ONLY;
- break;
- case PJ_MEDIA_DIR_DECODING:
- attr->type = PJSDP_ATTR_RECV_ONLY;
- break;
- case PJ_MEDIA_DIR_ENCODING_DECODING:
- attr->type = PJSDP_ATTR_SEND_RECV;
- break;
- }
- }
-
- return sdp;
-}
-
-/**
- * Update session with SDP answer from peer.
- */
-PJ_DEF(pj_status_t)
-pj_media_session_update (pj_media_session_t *session,
- const pjsdp_session_desc *sdp)
-{
- unsigned i;
- unsigned count;
-
- /* Check SDP */
- if (sdp_check (sdp) != 0) {
- return -1;
- }
-
- /* If the media stream count doesn't match, only update one. */
- if (session->stream_cnt != sdp->media_count) {
- PJ_LOG(3,(THIS_FILE, "pj_media_session_update : "
- "SDP media count mismatch! (rmt=%d, lcl=%d)",
- sdp->media_count, session->stream_cnt));
- count = (session->stream_cnt < sdp->media_count) ?
- session->stream_cnt : sdp->media_count;
- } else {
- count = session->stream_cnt;
- }
-
- for (i=0; i<count; ++i) {
- pj_media_stream_desc *sd = session->stream_desc[i];
- const pjsdp_media_desc *m = sdp->media[i];
- const pjsdp_conn_info *conn;
- unsigned j;
-
- /* Check that the session is not active. */
- pj_assert (sd->enc_stream == NULL && sd->dec_stream == NULL);
-
- conn = m->conn ? m->conn : sdp->conn;
- pj_assert(conn);
-
- /* Update remote address. */
- sd->info.rem_port = m->desc.port;
- pj_strdup (session->pool, &sd->info.rem_addr, &conn->addr);
-
- /* Select one active codec according to what peer wants. */
- for (j=0; j<sd->info.fmt_cnt; ++j) {
- unsigned fmt_0 = pj_strtoul(&m->desc.fmt[0]);
- if (sd->info.fmt[j].pt == fmt_0) {
- pj_codec_id temp;
-
- /* Put active format to the front. */
- if (j == 0)
- break;
-
- pj_memcpy(&temp, &sd->info.fmt[0], sizeof(temp));
- pj_memcpy(&sd->info.fmt[0], &sd->info.fmt[j], sizeof(temp));
- pj_memcpy(&sd->info.fmt[j], &temp, sizeof(temp));
- break;
- }
- }
-
- if (j == sd->info.fmt_cnt) {
- /* Peer has answered SDP with new codec, which doesn't exist
- * in the offer!
- * Mute this media.
- */
- PJ_LOG(3,(THIS_FILE, "Peer has answered SDP with new codec!"));
- sd->info.dir = PJ_MEDIA_DIR_NONE;
- continue;
- }
-
- /* Check direction. */
- if (m->desc.port == 0 || pjsdp_media_desc_has_attr(m, PJSDP_ATTR_INACTIVE)) {
- sd->info.dir = PJ_MEDIA_DIR_NONE;
- }
- else if (pjsdp_media_desc_has_attr(m, PJSDP_ATTR_RECV_ONLY)) {
- sd->info.dir = PJ_MEDIA_DIR_ENCODING;
- }
- else if (pjsdp_media_desc_has_attr(m, PJSDP_ATTR_SEND_ONLY)) {
- sd->info.dir = PJ_MEDIA_DIR_DECODING;
- }
- else {
- sd->info.dir = PJ_MEDIA_DIR_ENCODING_DECODING;
- }
- }
-
- return 0;
-}
-
-/**
- * Enumerate media streams in the session.
- */
-PJ_DEF(unsigned)
-pj_media_session_enum_streams (const pj_media_session_t *session,
- unsigned count, const pj_media_stream_info *info[])
-{
- unsigned i;
-
- if (count > session->stream_cnt)
- count = session->stream_cnt;
-
- for (i=0; i<count; ++i) {
- info[i] = &session->stream_desc[i]->info;
- }
-
- return session->stream_cnt;
-}
-
-/**
- * Get statistics
- */
-PJ_DEF(pj_status_t)
-pj_media_session_get_stat (const pj_media_session_t *session, unsigned index,
- pj_media_stream_stat *tx_stat,
- pj_media_stream_stat *rx_stat)
-{
- pj_media_stream_desc *sd;
- int stat_cnt = 0;
-
- if (index >= session->stream_cnt) {
- pj_assert(0);
- return -1;
- }
-
- sd = session->stream_desc[index];
-
- if (sd->enc_stream && tx_stat) {
- pj_media_stream_get_stat (sd->enc_stream, tx_stat);
- ++stat_cnt;
- } else if (tx_stat) {
- pj_memset (tx_stat, 0, sizeof(*tx_stat));
- }
-
- if (sd->dec_stream && rx_stat) {
- pj_media_stream_get_stat (sd->dec_stream, rx_stat);
- ++stat_cnt;
- } else if (rx_stat) {
- pj_memset (rx_stat, 0, sizeof(*rx_stat));
- }
-
- return stat_cnt ? 0 : -1;
-}
-
-/**
- * Modify stream, only when stream is inactive.
- */
-PJ_DEF(pj_status_t)
-pj_media_session_modify_stream (pj_media_session_t *session, unsigned index,
- unsigned modify_flag, const pj_media_stream_info *info)
-{
- pj_media_stream_desc *sd;
-
- if (index >= session->stream_cnt) {
- pj_assert(0);
- return -1;
- }
-
- sd = session->stream_desc[index];
-
- if (sd->enc_stream || sd->dec_stream) {
- pj_assert(0);
- return -1;
- }
-
- if (modify_flag & PJ_MEDIA_STREAM_MODIFY_DIR) {
- sd->info.dir = info->dir;
- }
-
- return 0;
-}
-
-/**
- * Activate media session.
- */
-PJ_DEF(pj_status_t)
-pj_media_session_activate (pj_media_session_t *session)
-{
- unsigned i;
- pj_status_t status = 0;
-
- for (i=0; i<session->stream_cnt; ++i) {
- pj_status_t rc;
- rc = pj_media_session_activate_stream (session, i);
- if (status == 0)
- status = rc;
- }
- return status;
-}
-
-/**
- * Activate individual stream in media session.
- */
-PJ_DEF(pj_status_t)
-pj_media_session_activate_stream (pj_media_session_t *session, unsigned index)
-{
- pj_media_stream_desc *sd;
- pj_media_stream_create_param scp;
- pj_status_t status;
- pj_time_val tv;
-
- if (index < 0 || index >= session->stream_cnt) {
- pj_assert(0);
- return -1;
- }
-
- sd = session->stream_desc[index];
-
- if (sd->enc_stream || sd->dec_stream) {
- /* Stream already active. */
- pj_assert(0);
- return 0;
- }
-
- pj_gettimeofday(&tv);
-
- /* Initialize parameter to create stream. */
- pj_memset (&scp, 0, sizeof(scp));
- scp.codec_id = &sd->info.fmt[0];
- scp.mediamgr = session->mediamgr;
- scp.dir = sd->info.dir;
- scp.rtp_sock = sd->info.sock_info.rtp_sock;
- scp.rtcp_sock = sd->info.sock_info.rtcp_sock;
- scp.remote_addr = pj_pool_calloc (session->pool, 1, sizeof(pj_sockaddr_in));
- pj_sockaddr_init (scp.remote_addr, &sd->info.rem_addr, sd->info.rem_port);
- scp.ssrc = tv.sec;
- scp.jb_min = 1;
- scp.jb_max = 15;
- scp.jb_maxcnt = 16;
-
- /* Build the stream! */
- status = pj_media_stream_create (session->pool, &sd->enc_stream, &sd->dec_stream, &scp);
-
- if (status==0 && sd->enc_stream) {
- status = pj_media_stream_start (sd->enc_stream);
- if (status != 0)
- goto on_error;
- }
- if (status==0 && sd->dec_stream) {
- status = pj_media_stream_start (sd->dec_stream);
- if (status != 0)
- goto on_error;
- }
- return status;
-
-on_error:
- if (sd->enc_stream) {
- pj_media_stream_destroy (sd->enc_stream);
- sd->enc_stream = NULL;
- }
- if (sd->dec_stream) {
- pj_media_stream_destroy (sd->dec_stream);
- sd->dec_stream = NULL;
- }
- return status;
-}
-
-/**
- * Destroy media session.
- */
-PJ_DEF(pj_status_t)
-pj_media_session_destroy (pj_media_session_t *session)
-{
- unsigned i;
-
- if (!session)
- return -1;
-
- for (i=0; i<session->stream_cnt; ++i) {
- pj_media_stream_desc *sd = session->stream_desc[i];
-
- if (sd->enc_stream) {
- pj_media_stream_destroy (sd->enc_stream);
- sd->enc_stream = NULL;
- }
- if (sd->dec_stream) {
- pj_media_stream_destroy (sd->dec_stream);
- sd->dec_stream = NULL;
- }
- }
- pj_pool_release (session->pool);
- return 0;
-}
-
+/* $Id$ */
+/*
+ * Copyright (C) 2003-2006 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/session.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+typedef struct pj_media_stream_desc
+{
+ pj_media_stream_info info;
+ pj_media_stream_t *enc_stream, *dec_stream;
+} pj_media_stream_desc;
+
+struct pj_media_session_t
+{
+ pj_pool_t *pool;
+ pj_med_mgr_t *mediamgr;
+ unsigned stream_cnt;
+ pj_media_stream_desc *stream_desc[PJSDP_MAX_MEDIA];
+};
+
+#define THIS_FILE "session.c"
+
+#define PJ_MEDIA_SESSION_SIZE (48*1024)
+#define PJ_MEDIA_SESSION_INC 1024
+
+static const pj_str_t ID_AUDIO = { "audio", 5};
+static const pj_str_t ID_VIDEO = { "video", 5};
+static const pj_str_t ID_IN = { "IN", 2 };
+static const pj_str_t ID_IP4 = { "IP4", 3};
+static const pj_str_t ID_RTP_AVP = { "RTP/AVP", 7 };
+static const pj_str_t ID_SDP_NAME = { "pjmedia", 7 };
+
+static void session_init (pj_media_session_t *ses)
+{
+ pj_memset (ses, 0, sizeof(pj_media_session_t));
+}
+
+
+/**
+ * Create new session offering.
+ */
+PJ_DEF(pj_media_session_t*)
+pj_media_session_create (pj_med_mgr_t *mgr, const pj_media_sock_info *sock_info)
+{
+ pj_pool_factory *pf;
+ pj_pool_t *pool;
+ pj_media_session_t *session;
+ pj_media_stream_desc *sd;
+ unsigned i, codec_cnt;
+ pj_codec_mgr *cm;
+ const pj_codec_id *codecs[PJSDP_MAX_FMT];
+
+ pf = pj_med_mgr_get_pool_factory(mgr);
+
+ pool = pj_pool_create( pf, "session", PJ_MEDIA_SESSION_SIZE, PJ_MEDIA_SESSION_INC, NULL);
+ if (!pool)
+ return NULL;
+
+ session = pj_pool_alloc(pool, sizeof(pj_media_session_t));
+ if (!session)
+ return NULL;
+
+ session_init (session);
+
+ session->pool = pool;
+ session->mediamgr = mgr;
+
+ /* Create first stream */
+ sd = pj_pool_calloc (pool, 1, sizeof(pj_media_stream_desc));
+ if (!sd)
+ return NULL;
+
+ sd->info.type = ID_AUDIO;
+ sd->info.dir = PJ_MEDIA_DIR_ENCODING_DECODING;
+ sd->info.transport = ID_RTP_AVP;
+ pj_memcpy(&sd->info.sock_info, sock_info, sizeof(*sock_info));
+
+ /* Enum audio codecs. */
+ sd->info.fmt_cnt = 0;
+ cm = pj_med_mgr_get_codec_mgr (mgr);
+ codec_cnt = pj_codec_mgr_enum_codecs(cm, PJSDP_MAX_FMT, codecs);
+ if (codec_cnt > PJSDP_MAX_FMT) codec_cnt = PJSDP_MAX_FMT;
+ for (i=0; i<codec_cnt; ++i) {
+ if (codecs[i]->type != PJ_MEDIA_TYPE_AUDIO)
+ continue;
+
+ sd->info.fmt[sd->info.fmt_cnt].pt = codecs[i]->pt;
+ sd->info.fmt[sd->info.fmt_cnt].sample_rate = codecs[i]->sample_rate;
+ pj_strdup (pool, &sd->info.fmt[sd->info.fmt_cnt].encoding_name, &codecs[i]->encoding_name);
+ ++sd->info.fmt_cnt;
+ }
+
+ session->stream_desc[session->stream_cnt++] = sd;
+
+ return session;
+}
+
+static int sdp_check (const pjsdp_session_desc *sdp)
+{
+ int has_conn = 0;
+ unsigned i;
+
+ if (sdp->conn)
+ has_conn = 1;
+
+ if (sdp->media_count == 0) {
+ PJ_LOG(4,(THIS_FILE, "SDP check failed: no media stream definition"));
+ return -1;
+ }
+
+ for (i=0; i<sdp->media_count; ++i) {
+ pjsdp_media_desc *m = sdp->media[i];
+
+ if (!m) {
+ pj_assert(0);
+ return -1;
+ }
+
+ if (m->desc.fmt_count == 0) {
+ PJ_LOG(4,(THIS_FILE, "SDP check failed: no format listed in media stream"));
+ return -1;
+ }
+
+ if (!has_conn && m->conn == NULL) {
+ PJ_LOG(4,(THIS_FILE, "SDP check failed: no connection information for media"));
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Create local stream definition that matches SDP received from peer.
+ */
+static pj_media_stream_desc*
+create_stream_from_sdp (pj_pool_t *pool, pj_med_mgr_t *mgr, const pjsdp_conn_info *conn,
+ const pjsdp_media_desc *m, const pj_media_sock_info *sock_info)
+{
+ pj_media_stream_desc *sd;
+
+ sd = pj_pool_calloc (pool, 1, sizeof(pj_media_stream_desc));
+ if (!sd) {
+ PJ_LOG(2,(THIS_FILE, "No memory to allocate stream descriptor"));
+ return NULL;
+ }
+
+ if (pj_stricmp(&conn->net_type, &ID_IN)==0 &&
+ pj_stricmp(&conn->addr_type, &ID_IP4)==0 &&
+ pj_stricmp(&m->desc.media, &ID_AUDIO)==0 &&
+ pj_stricmp(&m->desc.transport, &ID_RTP_AVP) == 0)
+ {
+ /*
+ * Got audio stream.
+ */
+ unsigned i, codec_cnt;
+ pj_codec_mgr *cm;
+ const pj_codec_id *codecs[PJSDP_MAX_FMT];
+
+ sd->info.type = ID_AUDIO;
+ sd->info.transport = ID_RTP_AVP;
+ pj_memcpy(&sd->info.sock_info, sock_info, sizeof(*sock_info));
+ sd->info.rem_port = m->desc.port;
+ pj_strdup (pool, &sd->info.rem_addr, &conn->addr);
+
+ /* Enum audio codecs. */
+ sd->info.fmt_cnt = 0;
+ cm = pj_med_mgr_get_codec_mgr (mgr);
+ codec_cnt = pj_codec_mgr_enum_codecs (cm, PJSDP_MAX_FMT, codecs);
+ if (codec_cnt > PJSDP_MAX_FMT) codec_cnt = PJSDP_MAX_FMT;
+
+ /* Find just one codec which we can support. */
+ for (i=0; i<m->desc.fmt_count && sd->info.fmt_cnt == 0; ++i) {
+ unsigned j, fmt_i;
+
+ /* For static payload, just match payload type. */
+ /* Else match clock rate and encoding name. */
+ fmt_i = pj_strtoul(&m->desc.fmt[i]);
+ if (fmt_i < PJ_RTP_PT_DYNAMIC) {
+ for (j=0; j<codec_cnt; ++j) {
+ if (codecs[j]->pt == fmt_i) {
+ sd->info.fmt_cnt = 1;
+ sd->info.fmt[0].type = PJ_MEDIA_TYPE_AUDIO;
+ sd->info.fmt[0].pt = codecs[j]->pt;
+ sd->info.fmt[0].sample_rate = codecs[j]->sample_rate;
+ pj_strdup (pool, &sd->info.fmt[0].encoding_name, &codecs[j]->encoding_name);
+ break;
+ }
+ }
+ } else {
+
+ /* Find the rtpmap for the payload type. */
+ const pjsdp_rtpmap_attr *rtpmap = pjsdp_media_desc_find_rtpmap (m, fmt_i);
+
+ /* Don't accept the media if no rtpmap for dynamic PT. */
+ if (rtpmap == NULL) {
+ PJ_LOG(4,(THIS_FILE, "SDP: No rtpmap found for payload id %d", m->desc.fmt[i]));
+ continue;
+ }
+
+ /* Check whether we can take this codec. */
+ for (j=0; j<codec_cnt; ++j) {
+ if (rtpmap->clock_rate == codecs[j]->sample_rate &&
+ pj_stricmp(&rtpmap->encoding_name, &codecs[j]->encoding_name) == 0)
+ {
+ sd->info.fmt_cnt = 1;
+ sd->info.fmt[0].type = PJ_MEDIA_TYPE_AUDIO;
+ sd->info.fmt[0].pt = codecs[j]->pt;
+ sd->info.fmt[0].sample_rate = codecs[j]->sample_rate;
+ sd->info.fmt[0].encoding_name = codecs[j]->encoding_name;
+ break;
+ }
+ }
+ }
+ }
+
+ /* Match codec and direction. */
+ if (sd->info.fmt_cnt == 0 || m->desc.port == 0 ||
+ pjsdp_media_desc_has_attr(m, PJSDP_ATTR_INACTIVE))
+ {
+ sd->info.dir = PJ_MEDIA_DIR_NONE;
+ }
+ else if (pjsdp_media_desc_has_attr(m, PJSDP_ATTR_RECV_ONLY)) {
+ sd->info.dir = PJ_MEDIA_DIR_ENCODING;
+ }
+ else if (pjsdp_media_desc_has_attr(m, PJSDP_ATTR_SEND_ONLY)) {
+ sd->info.dir = PJ_MEDIA_DIR_DECODING;
+ }
+ else {
+ sd->info.dir = PJ_MEDIA_DIR_ENCODING_DECODING;
+ }
+
+ } else {
+ /* Unsupported media stream. */
+ unsigned fmt_num;
+ const pjsdp_rtpmap_attr *rtpmap = NULL;
+
+ pj_strdup(pool, &sd->info.type, &m->desc.media);
+ pj_strdup(pool, &sd->info.transport, &m->desc.transport);
+ pj_memset(&sd->info.sock_info, 0, sizeof(*sock_info));
+ pj_strdup (pool, &sd->info.rem_addr, &conn->addr);
+ sd->info.rem_port = m->desc.port;
+
+ /* Just put one format and rtpmap, so that we don't have to make
+ * special exception when we convert this stream to SDP.
+ */
+
+ /* Find the rtpmap for the payload type. */
+ fmt_num = pj_strtoul(&m->desc.fmt[0]);
+ rtpmap = pjsdp_media_desc_find_rtpmap (m, fmt_num);
+
+ sd->info.fmt_cnt = 1;
+ if (pj_stricmp(&m->desc.media, &ID_VIDEO)==0) {
+ sd->info.fmt[0].type = PJ_MEDIA_TYPE_VIDEO;
+ sd->info.fmt[0].pt = fmt_num;
+ if (rtpmap) {
+ pj_strdup (pool, &sd->info.fmt[0].encoding_name,
+ &rtpmap->encoding_name);
+ sd->info.fmt[0].sample_rate = rtpmap->clock_rate;
+ }
+ } else {
+ sd->info.fmt[0].type = PJ_MEDIA_TYPE_UNKNOWN;
+ pj_strdup(pool, &sd->info.fmt[0].encoding_name, &m->desc.fmt[0]);
+ }
+
+ sd->info.dir = PJ_MEDIA_DIR_NONE;
+ }
+
+ return sd;
+}
+
+/**
+ * Create new session based on peer's offering.
+ */
+PJ_DEF(pj_media_session_t*)
+pj_media_session_create_from_sdp (pj_med_mgr_t *mgr, const pjsdp_session_desc *sdp,
+ const pj_media_sock_info *sock_info)
+{
+ pj_pool_factory *pf;
+ pj_pool_t *pool;
+ pj_media_session_t *session;
+ unsigned i;
+
+ if (sdp_check(sdp) != 0)
+ return NULL;
+
+ pf = pj_med_mgr_get_pool_factory(mgr);
+ pool = pj_pool_create( pf, "session", PJ_MEDIA_SESSION_SIZE, PJ_MEDIA_SESSION_INC, NULL);
+ if (!pool)
+ return NULL;
+
+ session = pj_pool_alloc(pool, sizeof(pj_media_session_t));
+ if (!session) {
+ PJ_LOG(3,(THIS_FILE, "No memory to create media session descriptor"));
+ pj_pool_release (pool);
+ return NULL;
+ }
+
+ session_init (session);
+
+ session->pool = pool;
+ session->mediamgr = mgr;
+
+ /* Enumerate each media stream and create our peer. */
+ for (i=0; i<sdp->media_count; ++i) {
+ const pjsdp_conn_info *conn;
+ const pjsdp_media_desc *m;
+ pj_media_stream_desc *sd;
+
+ m = sdp->media[i];
+ conn = m->conn ? m->conn : sdp->conn;
+
+ /*
+ * Bug:
+ * the sock_info below is used by more than one 'm' lines
+ */
+ PJ_TODO(SUPPORT_MORE_THAN_ONE_SDP_M_LINES)
+
+ sd = create_stream_from_sdp (pool, mgr, conn, m, sock_info);
+ pj_assert (sd);
+
+ session->stream_desc[session->stream_cnt++] = sd;
+ }
+
+ return session;
+}
+
+/**
+ * Duplicate session. The new session is inactive.
+ */
+PJ_DEF(pj_media_session_t*)
+pj_media_session_clone (const pj_media_session_t *rhs)
+{
+ pj_pool_factory *pf;
+ pj_pool_t *pool;
+ pj_media_session_t *session;
+ unsigned i;
+
+ pf = pj_med_mgr_get_pool_factory(rhs->mediamgr);
+ pool = pj_pool_create( pf, "session", PJ_MEDIA_SESSION_SIZE, PJ_MEDIA_SESSION_INC, NULL);
+ if (!pool) {
+ return NULL;
+ }
+
+ session = pj_pool_alloc (pool, sizeof(*session));
+ if (!session) {
+ PJ_LOG(3,(THIS_FILE, "No memory to create media session descriptor"));
+ pj_pool_release (pool);
+ return NULL;
+ }
+
+ session->pool = pool;
+ session->mediamgr = rhs->mediamgr;
+ session->stream_cnt = rhs->stream_cnt;
+
+ for (i=0; i<rhs->stream_cnt; ++i) {
+ pj_media_stream_desc *sd1 = pj_pool_alloc (session->pool, sizeof(pj_media_stream_desc));
+ const pj_media_stream_desc *sd2 = rhs->stream_desc[i];
+
+ if (!sd1) {
+ PJ_LOG(3,(THIS_FILE, "No memory to create media stream descriptor"));
+ pj_pool_release (pool);
+ return NULL;
+ }
+
+ session->stream_desc[i] = sd1;
+ sd1->enc_stream = sd1->dec_stream = NULL;
+ pj_strdup (pool, &sd1->info.type, &sd2->info.type);
+ sd1->info.dir = sd2->info.dir;
+ pj_strdup (pool, &sd1->info.transport, &sd2->info.transport);
+ pj_memcpy(&sd1->info.sock_info, &sd2->info.sock_info, sizeof(pj_media_sock_info));
+ pj_strdup (pool, &sd1->info.rem_addr, &sd2->info.rem_addr);
+ sd1->info.rem_port = sd2->info.rem_port;
+ sd1->info.fmt_cnt = sd2->info.fmt_cnt;
+ pj_memcpy (sd1->info.fmt, sd2->info.fmt, sizeof(sd2->info.fmt));
+ }
+
+ return session;
+}
+
+/**
+ * Create SDP description from the session.
+ */
+PJ_DEF(pjsdp_session_desc*)
+pj_media_session_create_sdp (const pj_media_session_t *session, pj_pool_t *pool,
+ pj_bool_t only_first_fmt)
+{
+ pjsdp_session_desc *sdp;
+ pj_time_val tv;
+ unsigned i;
+ pj_media_sock_info *c_addr = NULL;
+
+ if (session->stream_cnt == 0) {
+ pj_assert(0);
+ return NULL;
+ }
+
+ sdp = pj_pool_calloc (pool, 1, sizeof(pjsdp_session_desc));
+ if (!sdp) {
+ PJ_LOG(3,(THIS_FILE, "No memory to allocate SDP session descriptor"));
+ return NULL;
+ }
+
+ pj_gettimeofday(&tv);
+
+ sdp->origin.user = pj_str("-");
+ sdp->origin.version = sdp->origin.id = tv.sec + 2208988800UL;
+ sdp->origin.net_type = ID_IN;
+ sdp->origin.addr_type = ID_IP4;
+ sdp->origin.addr = *pj_gethostname();
+
+ sdp->name = ID_SDP_NAME;
+
+ /* If all media addresses are the same, then put the connection
+ * info in the session level, otherwise put it in media stream
+ * level.
+ */
+ for (i=0; i<session->stream_cnt; ++i) {
+ if (c_addr == NULL) {
+ c_addr = &session->stream_desc[i]->info.sock_info;
+ } else if (c_addr->rtp_addr_name.sin_addr.s_addr != session->stream_desc[i]->info.sock_info.rtp_addr_name.sin_addr.s_addr)
+ {
+ c_addr = NULL;
+ break;
+ }
+ }
+
+ if (c_addr) {
+ /* All addresses are the same, put connection info in session level. */
+ sdp->conn = pj_pool_alloc (pool, sizeof(pjsdp_conn_info));
+ if (!sdp->conn) {
+ PJ_LOG(2,(THIS_FILE, "No memory to allocate SDP connection info"));
+ return NULL;
+ }
+
+ sdp->conn->net_type = ID_IN;
+ sdp->conn->addr_type = ID_IP4;
+ pj_strdup2 (pool, &sdp->conn->addr, pj_inet_ntoa(c_addr->rtp_addr_name.sin_addr));
+ }
+
+ sdp->time.start = sdp->time.stop = 0;
+ sdp->attr_count = 0;
+
+ /* Create each media. */
+ sdp->media_count = 0;
+ for (i=0; i<session->stream_cnt; ++i) {
+ const pj_media_stream_desc *sd = session->stream_desc[i];
+ pjsdp_media_desc *m;
+ unsigned j;
+ unsigned fmt_cnt;
+ pjsdp_attr *attr;
+
+ m = pj_pool_calloc (pool, 1, sizeof(pjsdp_media_desc));
+ if (!m) {
+ PJ_LOG(3,(THIS_FILE, "No memory to allocate SDP media stream descriptor"));
+ return NULL;
+ }
+
+ sdp->media[sdp->media_count++] = m;
+
+ pj_strdup (pool, &m->desc.media, &sd->info.type);
+ m->desc.port = pj_ntohs(sd->info.sock_info.rtp_addr_name.sin_port);
+ m->desc.port_count = 1;
+ pj_strdup (pool, &m->desc.transport, &sd->info.transport);
+
+ /* Add format and rtpmap for each codec. */
+ m->desc.fmt_count = 0;
+ m->attr_count = 0;
+ fmt_cnt = sd->info.fmt_cnt;
+ if (fmt_cnt > 0 && only_first_fmt)
+ fmt_cnt = 1;
+ for (j=0; j<fmt_cnt; ++j) {
+ pjsdp_rtpmap_attr *rtpmap;
+ pj_str_t *fmt = &m->desc.fmt[m->desc.fmt_count++];
+
+ if (sd->info.fmt[j].type==PJ_MEDIA_TYPE_UNKNOWN) {
+ pj_strdup(pool, fmt, &sd->info.fmt[j].encoding_name);
+ } else {
+ fmt->ptr = pj_pool_alloc(pool, 8);
+ fmt->slen = pj_utoa(sd->info.fmt[j].pt, fmt->ptr);
+
+ rtpmap = pj_pool_calloc(pool, 1, sizeof(pjsdp_rtpmap_attr));
+ if (rtpmap) {
+ m->attr[m->attr_count++] = (pjsdp_attr*)rtpmap;
+ rtpmap->type = PJSDP_ATTR_RTPMAP;
+ rtpmap->payload_type = sd->info.fmt[j].pt;
+ rtpmap->clock_rate = sd->info.fmt[j].sample_rate;
+ pj_strdup (pool, &rtpmap->encoding_name, &sd->info.fmt[j].encoding_name);
+ } else {
+ PJ_LOG(3,(THIS_FILE, "No memory to allocate SDP rtpmap descriptor"));
+ }
+ }
+ }
+
+ /* If we don't have connection info in session level, create one. */
+ if (sdp->conn == NULL) {
+ m->conn = pj_pool_alloc (pool, sizeof(pjsdp_conn_info));
+ if (m->conn) {
+ m->conn->net_type = ID_IN;
+ m->conn->addr_type = ID_IP4;
+ pj_strdup2 (pool, &m->conn->addr, pj_inet_ntoa(sd->info.sock_info.rtp_addr_name.sin_addr));
+ } else {
+ PJ_LOG(3,(THIS_FILE, "No memory to allocate SDP media connection info"));
+ return NULL;
+ }
+ }
+
+ /* Add additional attribute to the media stream. */
+ attr = pj_pool_alloc(pool, sizeof(pjsdp_attr));
+ if (!attr) {
+ PJ_LOG(3,(THIS_FILE, "No memory to allocate SDP attribute"));
+ return NULL;
+ }
+ m->attr[m->attr_count++] = attr;
+
+ switch (sd->info.dir) {
+ case PJ_MEDIA_DIR_NONE:
+ attr->type = PJSDP_ATTR_INACTIVE;
+ break;
+ case PJ_MEDIA_DIR_ENCODING:
+ attr->type = PJSDP_ATTR_SEND_ONLY;
+ break;
+ case PJ_MEDIA_DIR_DECODING:
+ attr->type = PJSDP_ATTR_RECV_ONLY;
+ break;
+ case PJ_MEDIA_DIR_ENCODING_DECODING:
+ attr->type = PJSDP_ATTR_SEND_RECV;
+ break;
+ }
+ }
+
+ return sdp;
+}
+
+/**
+ * Update session with SDP answer from peer.
+ */
+PJ_DEF(pj_status_t)
+pj_media_session_update (pj_media_session_t *session,
+ const pjsdp_session_desc *sdp)
+{
+ unsigned i;
+ unsigned count;
+
+ /* Check SDP */
+ if (sdp_check (sdp) != 0) {
+ return -1;
+ }
+
+ /* If the media stream count doesn't match, only update one. */
+ if (session->stream_cnt != sdp->media_count) {
+ PJ_LOG(3,(THIS_FILE, "pj_media_session_update : "
+ "SDP media count mismatch! (rmt=%d, lcl=%d)",
+ sdp->media_count, session->stream_cnt));
+ count = (session->stream_cnt < sdp->media_count) ?
+ session->stream_cnt : sdp->media_count;
+ } else {
+ count = session->stream_cnt;
+ }
+
+ for (i=0; i<count; ++i) {
+ pj_media_stream_desc *sd = session->stream_desc[i];
+ const pjsdp_media_desc *m = sdp->media[i];
+ const pjsdp_conn_info *conn;
+ unsigned j;
+
+ /* Check that the session is not active. */
+ pj_assert (sd->enc_stream == NULL && sd->dec_stream == NULL);
+
+ conn = m->conn ? m->conn : sdp->conn;
+ pj_assert(conn);
+
+ /* Update remote address. */
+ sd->info.rem_port = m->desc.port;
+ pj_strdup (session->pool, &sd->info.rem_addr, &conn->addr);
+
+ /* Select one active codec according to what peer wants. */
+ for (j=0; j<sd->info.fmt_cnt; ++j) {
+ unsigned fmt_0 = pj_strtoul(&m->desc.fmt[0]);
+ if (sd->info.fmt[j].pt == fmt_0) {
+ pj_codec_id temp;
+
+ /* Put active format to the front. */
+ if (j == 0)
+ break;
+
+ pj_memcpy(&temp, &sd->info.fmt[0], sizeof(temp));
+ pj_memcpy(&sd->info.fmt[0], &sd->info.fmt[j], sizeof(temp));
+ pj_memcpy(&sd->info.fmt[j], &temp, sizeof(temp));
+ break;
+ }
+ }
+
+ if (j == sd->info.fmt_cnt) {
+ /* Peer has answered SDP with new codec, which doesn't exist
+ * in the offer!
+ * Mute this media.
+ */
+ PJ_LOG(3,(THIS_FILE, "Peer has answered SDP with new codec!"));
+ sd->info.dir = PJ_MEDIA_DIR_NONE;
+ continue;
+ }
+
+ /* Check direction. */
+ if (m->desc.port == 0 || pjsdp_media_desc_has_attr(m, PJSDP_ATTR_INACTIVE)) {
+ sd->info.dir = PJ_MEDIA_DIR_NONE;
+ }
+ else if (pjsdp_media_desc_has_attr(m, PJSDP_ATTR_RECV_ONLY)) {
+ sd->info.dir = PJ_MEDIA_DIR_ENCODING;
+ }
+ else if (pjsdp_media_desc_has_attr(m, PJSDP_ATTR_SEND_ONLY)) {
+ sd->info.dir = PJ_MEDIA_DIR_DECODING;
+ }
+ else {
+ sd->info.dir = PJ_MEDIA_DIR_ENCODING_DECODING;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * Enumerate media streams in the session.
+ */
+PJ_DEF(unsigned)
+pj_media_session_enum_streams (const pj_media_session_t *session,
+ unsigned count, const pj_media_stream_info *info[])
+{
+ unsigned i;
+
+ if (count > session->stream_cnt)
+ count = session->stream_cnt;
+
+ for (i=0; i<count; ++i) {
+ info[i] = &session->stream_desc[i]->info;
+ }
+
+ return session->stream_cnt;
+}
+
+/**
+ * Get statistics
+ */
+PJ_DEF(pj_status_t)
+pj_media_session_get_stat (const pj_media_session_t *session, unsigned index,
+ pj_media_stream_stat *tx_stat,
+ pj_media_stream_stat *rx_stat)
+{
+ pj_media_stream_desc *sd;
+ int stat_cnt = 0;
+
+ if (index >= session->stream_cnt) {
+ pj_assert(0);
+ return -1;
+ }
+
+ sd = session->stream_desc[index];
+
+ if (sd->enc_stream && tx_stat) {
+ pj_media_stream_get_stat (sd->enc_stream, tx_stat);
+ ++stat_cnt;
+ } else if (tx_stat) {
+ pj_memset (tx_stat, 0, sizeof(*tx_stat));
+ }
+
+ if (sd->dec_stream && rx_stat) {
+ pj_media_stream_get_stat (sd->dec_stream, rx_stat);
+ ++stat_cnt;
+ } else if (rx_stat) {
+ pj_memset (rx_stat, 0, sizeof(*rx_stat));
+ }
+
+ return stat_cnt ? 0 : -1;
+}
+
+/**
+ * Modify stream, only when stream is inactive.
+ */
+PJ_DEF(pj_status_t)
+pj_media_session_modify_stream (pj_media_session_t *session, unsigned index,
+ unsigned modify_flag, const pj_media_stream_info *info)
+{
+ pj_media_stream_desc *sd;
+
+ if (index >= session->stream_cnt) {
+ pj_assert(0);
+ return -1;
+ }
+
+ sd = session->stream_desc[index];
+
+ if (sd->enc_stream || sd->dec_stream) {
+ pj_assert(0);
+ return -1;
+ }
+
+ if (modify_flag & PJ_MEDIA_STREAM_MODIFY_DIR) {
+ sd->info.dir = info->dir;
+ }
+
+ return 0;
+}
+
+/**
+ * Activate media session.
+ */
+PJ_DEF(pj_status_t)
+pj_media_session_activate (pj_media_session_t *session)
+{
+ unsigned i;
+ pj_status_t status = 0;
+
+ for (i=0; i<session->stream_cnt; ++i) {
+ pj_status_t rc;
+ rc = pj_media_session_activate_stream (session, i);
+ if (status == 0)
+ status = rc;
+ }
+ return status;
+}
+
+/**
+ * Activate individual stream in media session.
+ */
+PJ_DEF(pj_status_t)
+pj_media_session_activate_stream (pj_media_session_t *session, unsigned index)
+{
+ pj_media_stream_desc *sd;
+ pj_media_stream_create_param scp;
+ pj_status_t status;
+ pj_time_val tv;
+
+ if (index < 0 || index >= session->stream_cnt) {
+ pj_assert(0);
+ return -1;
+ }
+
+ sd = session->stream_desc[index];
+
+ if (sd->enc_stream || sd->dec_stream) {
+ /* Stream already active. */
+ pj_assert(0);
+ return 0;
+ }
+
+ pj_gettimeofday(&tv);
+
+ /* Initialize parameter to create stream. */
+ pj_memset (&scp, 0, sizeof(scp));
+ scp.codec_id = &sd->info.fmt[0];
+ scp.mediamgr = session->mediamgr;
+ scp.dir = sd->info.dir;
+ scp.rtp_sock = sd->info.sock_info.rtp_sock;
+ scp.rtcp_sock = sd->info.sock_info.rtcp_sock;
+ scp.remote_addr = pj_pool_calloc (session->pool, 1, sizeof(pj_sockaddr_in));
+ pj_sockaddr_init (scp.remote_addr, &sd->info.rem_addr, sd->info.rem_port);
+ scp.ssrc = tv.sec;
+ scp.jb_min = 1;
+ scp.jb_max = 15;
+ scp.jb_maxcnt = 16;
+
+ /* Build the stream! */
+ status = pj_media_stream_create (session->pool, &sd->enc_stream, &sd->dec_stream, &scp);
+
+ if (status==0 && sd->enc_stream) {
+ status = pj_media_stream_start (sd->enc_stream);
+ if (status != 0)
+ goto on_error;
+ }
+ if (status==0 && sd->dec_stream) {
+ status = pj_media_stream_start (sd->dec_stream);
+ if (status != 0)
+ goto on_error;
+ }
+ return status;
+
+on_error:
+ if (sd->enc_stream) {
+ pj_media_stream_destroy (sd->enc_stream);
+ sd->enc_stream = NULL;
+ }
+ if (sd->dec_stream) {
+ pj_media_stream_destroy (sd->dec_stream);
+ sd->dec_stream = NULL;
+ }
+ return status;
+}
+
+/**
+ * Destroy media session.
+ */
+PJ_DEF(pj_status_t)
+pj_media_session_destroy (pj_media_session_t *session)
+{
+ unsigned i;
+
+ if (!session)
+ return -1;
+
+ for (i=0; i<session->stream_cnt; ++i) {
+ pj_media_stream_desc *sd = session->stream_desc[i];
+
+ if (sd->enc_stream) {
+ pj_media_stream_destroy (sd->enc_stream);
+ sd->enc_stream = NULL;
+ }
+ if (sd->dec_stream) {
+ pj_media_stream_destroy (sd->dec_stream);
+ sd->dec_stream = NULL;
+ }
+ }
+ pj_pool_release (session->pool);
+ return 0;
+}
+