| /* $Id$ |
| * |
| */ |
| #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; |
| } |
| |