| /* $Id$ */ |
| /* |
| * 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/transport_loop.h> |
| #include <pj/array.h> |
| #include <pj/assert.h> |
| #include <pj/errno.h> |
| #include <pj/ioqueue.h> |
| #include <pj/log.h> |
| #include <pj/pool.h> |
| #include <pj/rand.h> |
| #include <pj/string.h> |
| |
| |
| struct user |
| { |
| pj_bool_t rx_disabled; /**< Doesn't want to receive pkt? */ |
| void *user_data; /**< Only valid when attached */ |
| void (*rtp_cb)( void*, /**< To report incoming RTP. */ |
| void*, |
| pj_ssize_t); |
| void (*rtcp_cb)( void*, /**< To report incoming RTCP. */ |
| void*, |
| pj_ssize_t); |
| }; |
| |
| struct transport_loop |
| { |
| pjmedia_transport base; /**< Base transport. */ |
| |
| pj_pool_t *pool; /**< Memory pool */ |
| unsigned user_cnt; /**< Number of attachments */ |
| struct user users[4]; /**< Array of users. */ |
| |
| unsigned tx_drop_pct; /**< Percent of tx pkts to drop. */ |
| unsigned rx_drop_pct; /**< Percent of rx pkts to drop. */ |
| |
| }; |
| |
| |
| |
| /* |
| * These are media transport operations. |
| */ |
| static pj_status_t transport_get_info (pjmedia_transport *tp, |
| pjmedia_transport_info *info); |
| static pj_status_t transport_attach (pjmedia_transport *tp, |
| void *user_data, |
| const pj_sockaddr_t *rem_addr, |
| const pj_sockaddr_t *rem_rtcp, |
| unsigned addr_len, |
| void (*rtp_cb)(void*, |
| void*, |
| pj_ssize_t), |
| void (*rtcp_cb)(void*, |
| void*, |
| pj_ssize_t)); |
| static void transport_detach (pjmedia_transport *tp, |
| void *strm); |
| static pj_status_t transport_send_rtp( pjmedia_transport *tp, |
| const void *pkt, |
| pj_size_t size); |
| static pj_status_t transport_send_rtcp(pjmedia_transport *tp, |
| const void *pkt, |
| pj_size_t size); |
| static pj_status_t transport_send_rtcp2(pjmedia_transport *tp, |
| const pj_sockaddr_t *addr, |
| unsigned addr_len, |
| const void *pkt, |
| pj_size_t size); |
| static pj_status_t transport_media_create(pjmedia_transport *tp, |
| pj_pool_t *pool, |
| unsigned options, |
| const pjmedia_sdp_session *sdp_remote, |
| unsigned media_index); |
| static pj_status_t transport_encode_sdp(pjmedia_transport *tp, |
| pj_pool_t *pool, |
| pjmedia_sdp_session *sdp_local, |
| const pjmedia_sdp_session *rem_sdp, |
| unsigned media_index); |
| static pj_status_t transport_media_start (pjmedia_transport *tp, |
| pj_pool_t *pool, |
| const pjmedia_sdp_session *sdp_local, |
| const pjmedia_sdp_session *sdp_remote, |
| unsigned media_index); |
| static pj_status_t transport_media_stop(pjmedia_transport *tp); |
| static pj_status_t transport_simulate_lost(pjmedia_transport *tp, |
| pjmedia_dir dir, |
| unsigned pct_lost); |
| static pj_status_t transport_destroy (pjmedia_transport *tp); |
| |
| |
| static pjmedia_transport_op transport_udp_op = |
| { |
| &transport_get_info, |
| &transport_attach, |
| &transport_detach, |
| &transport_send_rtp, |
| &transport_send_rtcp, |
| &transport_send_rtcp2, |
| &transport_media_create, |
| &transport_encode_sdp, |
| &transport_media_start, |
| &transport_media_stop, |
| &transport_simulate_lost, |
| &transport_destroy |
| }; |
| |
| |
| /** |
| * Create loopback transport. |
| */ |
| PJ_DEF(pj_status_t) pjmedia_transport_loop_create(pjmedia_endpt *endpt, |
| pjmedia_transport **p_tp) |
| { |
| struct transport_loop *tp; |
| pj_pool_t *pool; |
| |
| /* Sanity check */ |
| PJ_ASSERT_RETURN(endpt && p_tp, PJ_EINVAL); |
| |
| /* Create transport structure */ |
| pool = pjmedia_endpt_create_pool(endpt, "tploop", 512, 512); |
| if (!pool) |
| return PJ_ENOMEM; |
| |
| tp = PJ_POOL_ZALLOC_T(pool, struct transport_loop); |
| tp->pool = pool; |
| pj_ansi_strncpy(tp->base.name, tp->pool->obj_name, PJ_MAX_OBJ_NAME-1); |
| tp->base.op = &transport_udp_op; |
| tp->base.type = PJMEDIA_TRANSPORT_TYPE_UDP; |
| |
| /* Done */ |
| *p_tp = &tp->base; |
| return PJ_SUCCESS; |
| } |
| |
| |
| PJ_DEF(pj_status_t) pjmedia_transport_loop_disable_rx( pjmedia_transport *tp, |
| void *user, |
| pj_bool_t disabled) |
| { |
| struct transport_loop *loop = (struct transport_loop*) tp; |
| unsigned i; |
| |
| for (i=0; i<loop->user_cnt; ++i) { |
| if (loop->users[i].user_data == user) { |
| loop->users[i].rx_disabled = disabled; |
| return PJ_SUCCESS; |
| } |
| } |
| pj_assert(!"Invalid stream user"); |
| return PJ_ENOTFOUND; |
| } |
| |
| /** |
| * Close loopback transport. |
| */ |
| static pj_status_t transport_destroy(pjmedia_transport *tp) |
| { |
| struct transport_loop *loop = (struct transport_loop*) tp; |
| |
| /* Sanity check */ |
| PJ_ASSERT_RETURN(tp, PJ_EINVAL); |
| |
| pj_pool_release(loop->pool); |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| /* Called to get the transport info */ |
| static pj_status_t transport_get_info(pjmedia_transport *tp, |
| pjmedia_transport_info *info) |
| { |
| PJ_ASSERT_RETURN(tp && info, PJ_EINVAL); |
| |
| info->sock_info.rtp_sock = 1; |
| pj_sockaddr_in_init(&info->sock_info.rtp_addr_name.ipv4, 0, 0); |
| info->sock_info.rtcp_sock = 2; |
| pj_sockaddr_in_init(&info->sock_info.rtcp_addr_name.ipv4, 0, 0); |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| /* Called by application to initialize the transport */ |
| static pj_status_t transport_attach( pjmedia_transport *tp, |
| void *user_data, |
| const pj_sockaddr_t *rem_addr, |
| const pj_sockaddr_t *rem_rtcp, |
| unsigned addr_len, |
| void (*rtp_cb)(void*, |
| void*, |
| pj_ssize_t), |
| void (*rtcp_cb)(void*, |
| void*, |
| pj_ssize_t)) |
| { |
| struct transport_loop *loop = (struct transport_loop*) tp; |
| unsigned i; |
| const pj_sockaddr *rtcp_addr; |
| |
| /* Validate arguments */ |
| PJ_ASSERT_RETURN(tp && rem_addr && addr_len, PJ_EINVAL); |
| |
| /* Must not be "attached" to same user */ |
| for (i=0; i<loop->user_cnt; ++i) { |
| PJ_ASSERT_RETURN(loop->users[i].user_data != user_data, |
| PJ_EINVALIDOP); |
| } |
| PJ_ASSERT_RETURN(loop->user_cnt != PJ_ARRAY_SIZE(loop->users), |
| PJ_ETOOMANY); |
| |
| PJ_UNUSED_ARG(rem_rtcp); |
| PJ_UNUSED_ARG(rtcp_addr); |
| |
| /* "Attach" the application: */ |
| |
| /* Save the new user */ |
| loop->users[loop->user_cnt].rtp_cb = rtp_cb; |
| loop->users[loop->user_cnt].rtcp_cb = rtcp_cb; |
| loop->users[loop->user_cnt].user_data = user_data; |
| ++loop->user_cnt; |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| /* Called by application when it no longer needs the transport */ |
| static void transport_detach( pjmedia_transport *tp, |
| void *user_data) |
| { |
| struct transport_loop *loop = (struct transport_loop*) tp; |
| unsigned i; |
| |
| pj_assert(tp); |
| |
| for (i=0; i<loop->user_cnt; ++i) { |
| if (loop->users[i].user_data == user_data) |
| break; |
| } |
| |
| /* Remove this user */ |
| if (i != loop->user_cnt) { |
| pj_array_erase(loop->users, sizeof(loop->users[0]), |
| loop->user_cnt, i); |
| --loop->user_cnt; |
| } |
| } |
| |
| |
| /* Called by application to send RTP packet */ |
| static pj_status_t transport_send_rtp( pjmedia_transport *tp, |
| const void *pkt, |
| pj_size_t size) |
| { |
| struct transport_loop *loop = (struct transport_loop*)tp; |
| unsigned i; |
| |
| /* Simulate packet lost on TX direction */ |
| if (loop->tx_drop_pct) { |
| if ((pj_rand() % 100) <= (int)loop->tx_drop_pct) { |
| PJ_LOG(5,(loop->base.name, |
| "TX RTP packet dropped because of pkt lost " |
| "simulation")); |
| return PJ_SUCCESS; |
| } |
| } |
| |
| /* Simulate packet lost on RX direction */ |
| if (loop->rx_drop_pct) { |
| if ((pj_rand() % 100) <= (int)loop->rx_drop_pct) { |
| PJ_LOG(5,(loop->base.name, |
| "RX RTP packet dropped because of pkt lost " |
| "simulation")); |
| return PJ_SUCCESS; |
| } |
| } |
| |
| /* Distribute to users */ |
| for (i=0; i<loop->user_cnt; ++i) { |
| if (!loop->users[i].rx_disabled && loop->users[i].rtp_cb) |
| (*loop->users[i].rtp_cb)(loop->users[i].user_data, (void*)pkt, |
| size); |
| } |
| |
| return PJ_SUCCESS; |
| } |
| |
| /* Called by application to send RTCP packet */ |
| static pj_status_t transport_send_rtcp(pjmedia_transport *tp, |
| const void *pkt, |
| pj_size_t size) |
| { |
| return transport_send_rtcp2(tp, NULL, 0, pkt, size); |
| } |
| |
| |
| /* Called by application to send RTCP packet */ |
| static pj_status_t transport_send_rtcp2(pjmedia_transport *tp, |
| const pj_sockaddr_t *addr, |
| unsigned addr_len, |
| const void *pkt, |
| pj_size_t size) |
| { |
| struct transport_loop *loop = (struct transport_loop*)tp; |
| unsigned i; |
| |
| PJ_UNUSED_ARG(addr_len); |
| PJ_UNUSED_ARG(addr); |
| |
| /* Distribute to users */ |
| for (i=0; i<loop->user_cnt; ++i) { |
| if (!loop->users[i].rx_disabled && loop->users[i].rtcp_cb) |
| (*loop->users[i].rtcp_cb)(loop->users[i].user_data, (void*)pkt, |
| size); |
| } |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| static pj_status_t transport_media_create(pjmedia_transport *tp, |
| pj_pool_t *pool, |
| unsigned options, |
| const pjmedia_sdp_session *sdp_remote, |
| unsigned media_index) |
| { |
| PJ_UNUSED_ARG(tp); |
| PJ_UNUSED_ARG(pool); |
| PJ_UNUSED_ARG(options); |
| PJ_UNUSED_ARG(sdp_remote); |
| PJ_UNUSED_ARG(media_index); |
| return PJ_SUCCESS; |
| } |
| |
| static pj_status_t transport_encode_sdp(pjmedia_transport *tp, |
| pj_pool_t *pool, |
| pjmedia_sdp_session *sdp_local, |
| const pjmedia_sdp_session *rem_sdp, |
| unsigned media_index) |
| { |
| PJ_UNUSED_ARG(tp); |
| PJ_UNUSED_ARG(pool); |
| PJ_UNUSED_ARG(sdp_local); |
| PJ_UNUSED_ARG(rem_sdp); |
| PJ_UNUSED_ARG(media_index); |
| return PJ_SUCCESS; |
| } |
| |
| static pj_status_t transport_media_start(pjmedia_transport *tp, |
| pj_pool_t *pool, |
| const pjmedia_sdp_session *sdp_local, |
| const pjmedia_sdp_session *sdp_remote, |
| unsigned media_index) |
| { |
| PJ_UNUSED_ARG(tp); |
| PJ_UNUSED_ARG(pool); |
| PJ_UNUSED_ARG(sdp_local); |
| PJ_UNUSED_ARG(sdp_remote); |
| PJ_UNUSED_ARG(media_index); |
| return PJ_SUCCESS; |
| } |
| |
| static pj_status_t transport_media_stop(pjmedia_transport *tp) |
| { |
| PJ_UNUSED_ARG(tp); |
| return PJ_SUCCESS; |
| } |
| |
| static pj_status_t transport_simulate_lost(pjmedia_transport *tp, |
| pjmedia_dir dir, |
| unsigned pct_lost) |
| { |
| struct transport_loop *loop = (struct transport_loop*)tp; |
| |
| PJ_ASSERT_RETURN(tp && pct_lost <= 100, PJ_EINVAL); |
| |
| if (dir & PJMEDIA_DIR_ENCODING) |
| loop->tx_drop_pct = pct_lost; |
| |
| if (dir & PJMEDIA_DIR_DECODING) |
| loop->rx_drop_pct = pct_lost; |
| |
| return PJ_SUCCESS; |
| } |
| |