| /* $Id$ */ |
| /* |
| * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) |
| * |
| * 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 <pjsua-lib/pjsua.h> |
| #include <pjsua-lib/pjsua_internal.h> |
| |
| #if defined(PJSUA_MEDIA_HAS_PJMEDIA) && PJSUA_MEDIA_HAS_PJMEDIA != 0 |
| # error The PJSUA_MEDIA_HAS_PJMEDIA should be declared as zero |
| #endif |
| |
| |
| #define THIS_FILE "alt_pjsua_aud.c" |
| #define UNIMPLEMENTED(func) PJ_LOG(2,(THIS_FILE, "*** Call to unimplemented function %s ***", #func)); |
| |
| |
| /***************************************************************************** |
| * Our dummy codecs. Since we won't use any PJMEDIA codecs, we need to declare |
| * our own codecs and register them to PJMEDIA's codec manager. We just need |
| * the info so that they can be listed in SDP. The encoding and decoding will |
| * happen in your third party media stream and will not use these codecs, |
| * hence the "dummy" name. |
| */ |
| static struct alt_codec |
| { |
| pj_str_t encoding_name; |
| pj_uint8_t payload_type; |
| unsigned clock_rate; |
| unsigned channel_cnt; |
| unsigned frm_ptime; |
| unsigned avg_bps; |
| unsigned max_bps; |
| } codec_list[] = |
| { |
| /* G.729 */ |
| { { "G729", 4 }, 18, 8000, 1, 10, 8000, 8000 }, |
| /* PCMU */ |
| { { "PCMU", 4 }, 0, 8000, 1, 10, 64000, 64000 }, |
| /* Our proprietary high end low bit rate (5kbps) codec, if you wish */ |
| { { "FOO", 3 }, PJMEDIA_RTP_PT_START+0, 16000, 1, 20, 5000, 5000 }, |
| }; |
| |
| static struct alt_codec_factory |
| { |
| pjmedia_codec_factory base; |
| } alt_codec_factory; |
| |
| static pj_status_t alt_codec_test_alloc( pjmedia_codec_factory *factory, |
| const pjmedia_codec_info *id ) |
| { |
| unsigned i; |
| for (i=0; i<PJ_ARRAY_SIZE(codec_list); ++i) { |
| if (pj_stricmp(&id->encoding_name, &codec_list[i].encoding_name)==0) |
| return PJ_SUCCESS; |
| } |
| return PJ_ENOTSUP; |
| } |
| |
| static pj_status_t alt_codec_default_attr( pjmedia_codec_factory *factory, |
| const pjmedia_codec_info *id, |
| pjmedia_codec_param *attr ) |
| { |
| struct alt_codec *ac; |
| unsigned i; |
| |
| PJ_UNUSED_ARG(factory); |
| |
| for (i=0; i<PJ_ARRAY_SIZE(codec_list); ++i) { |
| if (pj_stricmp(&id->encoding_name, &codec_list[i].encoding_name)==0) |
| break; |
| } |
| if (i == PJ_ARRAY_SIZE(codec_list)) |
| return PJ_ENOTFOUND; |
| |
| ac = &codec_list[i]; |
| |
| pj_bzero(attr, sizeof(pjmedia_codec_param)); |
| attr->info.clock_rate = ac->clock_rate; |
| attr->info.channel_cnt = ac->channel_cnt; |
| attr->info.avg_bps = ac->avg_bps; |
| attr->info.max_bps = ac->max_bps; |
| attr->info.pcm_bits_per_sample = 16; |
| attr->info.frm_ptime = ac->frm_ptime; |
| attr->info.pt = ac->payload_type; |
| |
| attr->setting.frm_per_pkt = 1; |
| attr->setting.vad = 1; |
| attr->setting.plc = 1; |
| |
| return PJ_SUCCESS; |
| } |
| |
| static pj_status_t alt_codec_enum_codecs(pjmedia_codec_factory *factory, |
| unsigned *count, |
| pjmedia_codec_info codecs[]) |
| { |
| unsigned i; |
| |
| for (i=0; i<*count && i<PJ_ARRAY_SIZE(codec_list); ++i) { |
| struct alt_codec *ac = &codec_list[i]; |
| pj_bzero(&codecs[i], sizeof(pjmedia_codec_info)); |
| codecs[i].encoding_name = ac->encoding_name; |
| codecs[i].pt = ac->payload_type; |
| codecs[i].type = PJMEDIA_TYPE_AUDIO; |
| codecs[i].clock_rate = ac->clock_rate; |
| codecs[i].channel_cnt = ac->channel_cnt; |
| } |
| |
| *count = i; |
| |
| return PJ_SUCCESS; |
| } |
| |
| static pj_status_t alt_codec_alloc_codec(pjmedia_codec_factory *factory, |
| const pjmedia_codec_info *id, |
| pjmedia_codec **p_codec) |
| { |
| /* This will never get called since we won't be using this codec */ |
| UNIMPLEMENTED(alt_codec_alloc_codec) |
| return PJ_ENOTSUP; |
| } |
| |
| static pj_status_t alt_codec_dealloc_codec( pjmedia_codec_factory *factory, |
| pjmedia_codec *codec ) |
| { |
| /* This will never get called */ |
| UNIMPLEMENTED(alt_codec_dealloc_codec) |
| return PJ_ENOTSUP; |
| } |
| |
| static pj_status_t alt_codec_deinit(void) |
| { |
| pjmedia_codec_mgr *codec_mgr; |
| codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt); |
| return pjmedia_codec_mgr_unregister_factory(codec_mgr, |
| &alt_codec_factory.base); |
| |
| } |
| |
| static pjmedia_codec_factory_op alt_codec_factory_op = |
| { |
| &alt_codec_test_alloc, |
| &alt_codec_default_attr, |
| &alt_codec_enum_codecs, |
| &alt_codec_alloc_codec, |
| &alt_codec_dealloc_codec, |
| &alt_codec_deinit |
| }; |
| |
| |
| /***************************************************************************** |
| * API |
| */ |
| |
| /* Initialize third party media library. */ |
| pj_status_t pjsua_aud_subsys_init() |
| { |
| pjmedia_codec_mgr *codec_mgr; |
| pj_status_t status; |
| |
| /* Register our "dummy" codecs */ |
| alt_codec_factory.base.op = &alt_codec_factory_op; |
| codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt); |
| status = pjmedia_codec_mgr_register_factory(codec_mgr, |
| &alt_codec_factory.base); |
| if (status != PJ_SUCCESS) |
| return status; |
| |
| /* TODO: initialize your evil library here */ |
| return PJ_SUCCESS; |
| } |
| |
| /* Start (audio) media library. */ |
| pj_status_t pjsua_aud_subsys_start(void) |
| { |
| /* TODO: */ |
| return PJ_SUCCESS; |
| } |
| |
| /* Cleanup and deinitialize third party media library. */ |
| pj_status_t pjsua_aud_subsys_destroy() |
| { |
| /* TODO: */ |
| return PJ_SUCCESS; |
| } |
| |
| /* Our callback to receive incoming RTP packets */ |
| static void aud_rtp_cb(void *user_data, void *pkt, pj_ssize_t size) |
| { |
| pjsua_call_media *call_med = (pjsua_call_media*) user_data; |
| |
| /* TODO: Do something with the packet */ |
| PJ_LOG(4,(THIS_FILE, "RX %d bytes audio RTP packet", (int)size)); |
| } |
| |
| /* Our callback to receive RTCP packets */ |
| static void aud_rtcp_cb(void *user_data, void *pkt, pj_ssize_t size) |
| { |
| pjsua_call_media *call_med = (pjsua_call_media*) user_data; |
| |
| /* TODO: Do something with the packet here */ |
| PJ_LOG(4,(THIS_FILE, "RX %d bytes audio RTCP packet", (int)size)); |
| } |
| |
| /* A demo function to send dummy "RTP" packets periodically. You would not |
| * need to have this function in the real app! |
| */ |
| static void timer_to_send_aud_rtp(void *user_data) |
| { |
| pjsua_call_media *call_med = (pjsua_call_media*) user_data; |
| const char *pkt = "Not RTP packet"; |
| |
| if (!call_med->call || !call_med->call->inv || !call_med->tp) { |
| /* Call has been disconnected. There is race condition here as |
| * this cb may be called sometime after call has been disconnected */ |
| return; |
| } |
| |
| pjmedia_transport_send_rtp(call_med->tp, pkt, strlen(pkt)); |
| |
| pjsua_schedule_timer2(&timer_to_send_aud_rtp, call_med, 2000); |
| } |
| |
| static void timer_to_send_aud_rtcp(void *user_data) |
| { |
| pjsua_call_media *call_med = (pjsua_call_media*) user_data; |
| const char *pkt = "Not RTCP packet"; |
| |
| if (!call_med->call || !call_med->call->inv || !call_med->tp) { |
| /* Call has been disconnected. There is race condition here as |
| * this cb may be called sometime after call has been disconnected */ |
| return; |
| } |
| |
| pjmedia_transport_send_rtcp(call_med->tp, pkt, strlen(pkt)); |
| |
| pjsua_schedule_timer2(&timer_to_send_aud_rtcp, call_med, 5000); |
| } |
| |
| /* Stop the audio stream of a call. */ |
| void pjsua_aud_stop_stream(pjsua_call_media *call_med) |
| { |
| /* Detach our RTP/RTCP callbacks from transport */ |
| pjmedia_transport_detach(call_med->tp, call_med); |
| |
| /* TODO: destroy your audio stream here */ |
| } |
| |
| /* |
| * This function is called whenever SDP negotiation has completed |
| * successfully. Here you'd want to start your audio stream |
| * based on the info in the SDPs. |
| */ |
| pj_status_t pjsua_aud_channel_update(pjsua_call_media *call_med, |
| pj_pool_t *tmp_pool, |
| pjmedia_stream_info *si, |
| const pjmedia_sdp_session *local_sdp, |
| const pjmedia_sdp_session *remote_sdp) |
| { |
| pj_status_t status = PJ_SUCCESS; |
| |
| PJ_LOG(4,(THIS_FILE,"Alt audio channel update..")); |
| pj_log_push_indent(); |
| |
| /* Check if no media is active */ |
| if (si->dir != PJMEDIA_DIR_NONE) { |
| /* Attach our RTP and RTCP callbacks to the media transport */ |
| status = pjmedia_transport_attach(call_med->tp, call_med, |
| &si->rem_addr, &si->rem_rtcp, |
| pj_sockaddr_get_len(&si->rem_addr), |
| &aud_rtp_cb, &aud_rtcp_cb); |
| |
| /* For a demonstration, let's use a timer to send "RTP" packet |
| * periodically. |
| */ |
| pjsua_schedule_timer2(&timer_to_send_aud_rtp, call_med, 0); |
| pjsua_schedule_timer2(&timer_to_send_aud_rtcp, call_med, 2500); |
| |
| /* TODO: |
| * - Create and start your media stream based on the parameters |
| * in si |
| */ |
| } |
| |
| on_return: |
| pj_log_pop_indent(); |
| return status; |
| } |
| |
| void pjsua_check_snd_dev_idle() |
| { |
| } |
| |
| /***************************************************************************** |
| * |
| * Call API which MAY need to be re-implemented if different backend is used. |
| */ |
| |
| /* Check if call has an active media session. */ |
| PJ_DEF(pj_bool_t) pjsua_call_has_media(pjsua_call_id call_id) |
| { |
| UNIMPLEMENTED(pjsua_call_has_media) |
| return PJ_TRUE; |
| } |
| |
| |
| /* Get the conference port identification associated with the call. */ |
| PJ_DEF(pjsua_conf_port_id) pjsua_call_get_conf_port(pjsua_call_id call_id) |
| { |
| UNIMPLEMENTED(pjsua_call_get_conf_port) |
| return PJSUA_INVALID_ID; |
| } |
| |
| /* Get media stream info for the specified media index. */ |
| PJ_DEF(pj_status_t) pjsua_call_get_stream_info( pjsua_call_id call_id, |
| unsigned med_idx, |
| pjsua_stream_info *psi) |
| { |
| pj_bzero(psi, sizeof(*psi)); |
| UNIMPLEMENTED(pjsua_call_get_stream_info) |
| return PJ_ENOTSUP; |
| } |
| |
| /* Get media stream statistic for the specified media index. */ |
| PJ_DEF(pj_status_t) pjsua_call_get_stream_stat( pjsua_call_id call_id, |
| unsigned med_idx, |
| pjsua_stream_stat *stat) |
| { |
| pj_bzero(stat, sizeof(*stat)); |
| UNIMPLEMENTED(pjsua_call_get_stream_stat) |
| return PJ_ENOTSUP; |
| } |
| |
| /* |
| * Send DTMF digits to remote using RFC 2833 payload formats. |
| */ |
| PJ_DEF(pj_status_t) pjsua_call_dial_dtmf( pjsua_call_id call_id, |
| const pj_str_t *digits) |
| { |
| UNIMPLEMENTED(pjsua_call_dial_dtmf) |
| return PJ_ENOTSUP; |
| } |
| |
| /***************************************************************************** |
| * Below are auxiliary API that we don't support (feel free to implement them |
| * with the other media stack) |
| */ |
| |
| /* Get maximum number of conference ports. */ |
| PJ_DEF(unsigned) pjsua_conf_get_max_ports(void) |
| { |
| UNIMPLEMENTED(pjsua_conf_get_max_ports) |
| return 0xFF; |
| } |
| |
| /* Get current number of active ports in the bridge. */ |
| PJ_DEF(unsigned) pjsua_conf_get_active_ports(void) |
| { |
| UNIMPLEMENTED(pjsua_conf_get_active_ports) |
| return 0; |
| } |
| |
| /* Enumerate all conference ports. */ |
| PJ_DEF(pj_status_t) pjsua_enum_conf_ports(pjsua_conf_port_id id[], |
| unsigned *count) |
| { |
| *count = 0; |
| UNIMPLEMENTED(pjsua_enum_conf_ports) |
| return PJ_ENOTSUP; |
| } |
| |
| /* Get information about the specified conference port */ |
| PJ_DEF(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id, |
| pjsua_conf_port_info *info) |
| { |
| UNIMPLEMENTED(pjsua_conf_get_port_info) |
| return PJ_ENOTSUP; |
| } |
| |
| /* Add arbitrary media port to PJSUA's conference bridge. */ |
| PJ_DEF(pj_status_t) pjsua_conf_add_port( pj_pool_t *pool, |
| pjmedia_port *port, |
| pjsua_conf_port_id *p_id) |
| { |
| *p_id = PJSUA_INVALID_ID; |
| UNIMPLEMENTED(pjsua_conf_add_port) |
| /* We should return PJ_ENOTSUP here, but this API is needed by pjsua |
| * application or otherwise it will refuse to start. |
| */ |
| return PJ_SUCCESS; |
| } |
| |
| /* Remove arbitrary slot from the conference bridge. */ |
| PJ_DEF(pj_status_t) pjsua_conf_remove_port(pjsua_conf_port_id id) |
| { |
| UNIMPLEMENTED(pjsua_conf_remove_port) |
| return PJ_ENOTSUP; |
| } |
| |
| /* Establish unidirectional media flow from souce to sink. */ |
| PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source, |
| pjsua_conf_port_id sink) |
| { |
| UNIMPLEMENTED(pjsua_conf_connect) |
| return PJ_ENOTSUP; |
| } |
| |
| /* Disconnect media flow from the source to destination port. */ |
| PJ_DEF(pj_status_t) pjsua_conf_disconnect( pjsua_conf_port_id source, |
| pjsua_conf_port_id sink) |
| { |
| UNIMPLEMENTED(pjsua_conf_disconnect) |
| return PJ_ENOTSUP; |
| } |
| |
| /* Adjust the signal level to be transmitted from the bridge to the |
| * specified port by making it louder or quieter. |
| */ |
| PJ_DEF(pj_status_t) pjsua_conf_adjust_tx_level(pjsua_conf_port_id slot, |
| float level) |
| { |
| UNIMPLEMENTED(pjsua_conf_adjust_tx_level) |
| return PJ_ENOTSUP; |
| } |
| |
| /* Adjust the signal level to be received from the specified port (to |
| * the bridge) by making it louder or quieter. |
| */ |
| PJ_DEF(pj_status_t) pjsua_conf_adjust_rx_level(pjsua_conf_port_id slot, |
| float level) |
| { |
| UNIMPLEMENTED(pjsua_conf_adjust_rx_level) |
| return PJ_ENOTSUP; |
| } |
| |
| |
| /* Get last signal level transmitted to or received from the specified port. */ |
| PJ_DEF(pj_status_t) pjsua_conf_get_signal_level(pjsua_conf_port_id slot, |
| unsigned *tx_level, |
| unsigned *rx_level) |
| { |
| UNIMPLEMENTED(pjsua_conf_get_signal_level) |
| return PJ_ENOTSUP; |
| } |
| |
| /* Create a file player, and automatically connect this player to |
| * the conference bridge. |
| */ |
| PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename, |
| unsigned options, |
| pjsua_player_id *p_id) |
| { |
| UNIMPLEMENTED(pjsua_player_create) |
| return PJ_ENOTSUP; |
| } |
| |
| /* Create a file playlist media port, and automatically add the port |
| * to the conference bridge. |
| */ |
| PJ_DEF(pj_status_t) pjsua_playlist_create( const pj_str_t file_names[], |
| unsigned file_count, |
| const pj_str_t *label, |
| unsigned options, |
| pjsua_player_id *p_id) |
| { |
| UNIMPLEMENTED(pjsua_playlist_create) |
| return PJ_ENOTSUP; |
| } |
| |
| /* Get conference port ID associated with player. */ |
| PJ_DEF(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id) |
| { |
| UNIMPLEMENTED(pjsua_player_get_conf_port) |
| return -1; |
| } |
| |
| /* Get the media port for the player. */ |
| PJ_DEF(pj_status_t) pjsua_player_get_port( pjsua_player_id id, |
| pjmedia_port **p_port) |
| { |
| UNIMPLEMENTED(pjsua_player_get_port) |
| return PJ_ENOTSUP; |
| } |
| |
| /* Set playback position. */ |
| PJ_DEF(pj_status_t) pjsua_player_set_pos( pjsua_player_id id, |
| pj_uint32_t samples) |
| { |
| UNIMPLEMENTED(pjsua_player_set_pos) |
| return PJ_ENOTSUP; |
| } |
| |
| /* Close the file, remove the player from the bridge, and free |
| * resources associated with the file player. |
| */ |
| PJ_DEF(pj_status_t) pjsua_player_destroy(pjsua_player_id id) |
| { |
| UNIMPLEMENTED(pjsua_player_destroy) |
| return PJ_ENOTSUP; |
| } |
| |
| /* Create a file recorder, and automatically connect this recorder to |
| * the conference bridge. |
| */ |
| PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename, |
| unsigned enc_type, |
| void *enc_param, |
| pj_ssize_t max_size, |
| unsigned options, |
| pjsua_recorder_id *p_id) |
| { |
| UNIMPLEMENTED(pjsua_recorder_create) |
| return PJ_ENOTSUP; |
| } |
| |
| |
| /* Get conference port associated with recorder. */ |
| PJ_DEF(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id) |
| { |
| UNIMPLEMENTED(pjsua_recorder_get_conf_port) |
| return -1; |
| } |
| |
| /* Get the media port for the recorder. */ |
| PJ_DEF(pj_status_t) pjsua_recorder_get_port( pjsua_recorder_id id, |
| pjmedia_port **p_port) |
| { |
| UNIMPLEMENTED(pjsua_recorder_get_port) |
| return PJ_ENOTSUP; |
| } |
| |
| /* Destroy recorder (this will complete recording). */ |
| PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id) |
| { |
| UNIMPLEMENTED(pjsua_recorder_destroy) |
| return PJ_ENOTSUP; |
| } |
| |
| /* Enum sound devices. */ |
| PJ_DEF(pj_status_t) pjsua_enum_aud_devs( pjmedia_aud_dev_info info[], |
| unsigned *count) |
| { |
| UNIMPLEMENTED(pjsua_enum_aud_devs) |
| return PJ_ENOTSUP; |
| } |
| |
| PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[], |
| unsigned *count) |
| { |
| UNIMPLEMENTED(pjsua_enum_snd_devs) |
| return PJ_ENOTSUP; |
| } |
| |
| /* Select or change sound device. */ |
| PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev, int playback_dev) |
| { |
| UNIMPLEMENTED(pjsua_set_snd_dev) |
| return PJ_SUCCESS; |
| } |
| |
| /* Get currently active sound devices. */ |
| PJ_DEF(pj_status_t) pjsua_get_snd_dev(int *capture_dev, int *playback_dev) |
| { |
| *capture_dev = *playback_dev = PJSUA_INVALID_ID; |
| UNIMPLEMENTED(pjsua_get_snd_dev) |
| return PJ_ENOTSUP; |
| } |
| |
| /* Use null sound device. */ |
| PJ_DEF(pj_status_t) pjsua_set_null_snd_dev(void) |
| { |
| UNIMPLEMENTED(pjsua_set_null_snd_dev) |
| return PJ_ENOTSUP; |
| } |
| |
| /* Use no device! */ |
| PJ_DEF(pjmedia_port*) pjsua_set_no_snd_dev(void) |
| { |
| UNIMPLEMENTED(pjsua_set_no_snd_dev) |
| return NULL; |
| } |
| |
| /* Configure the AEC settings of the sound port. */ |
| PJ_DEF(pj_status_t) pjsua_set_ec(unsigned tail_ms, unsigned options) |
| { |
| UNIMPLEMENTED(pjsua_set_ec) |
| return PJ_ENOTSUP; |
| } |
| |
| /* Get current AEC tail length. */ |
| PJ_DEF(pj_status_t) pjsua_get_ec_tail(unsigned *p_tail_ms) |
| { |
| UNIMPLEMENTED(pjsua_get_ec_tail) |
| return PJ_ENOTSUP; |
| } |
| |
| /* Check whether the sound device is currently active. */ |
| PJ_DEF(pj_bool_t) pjsua_snd_is_active(void) |
| { |
| UNIMPLEMENTED(pjsua_snd_is_active) |
| return PJ_FALSE; |
| } |
| |
| /* Configure sound device setting to the sound device being used. */ |
| PJ_DEF(pj_status_t) pjsua_snd_set_setting( pjmedia_aud_dev_cap cap, |
| const void *pval, pj_bool_t keep) |
| { |
| UNIMPLEMENTED(pjsua_snd_set_setting) |
| return PJ_ENOTSUP; |
| } |
| |
| /* Retrieve a sound device setting. */ |
| PJ_DEF(pj_status_t) pjsua_snd_get_setting(pjmedia_aud_dev_cap cap, void *pval) |
| { |
| UNIMPLEMENTED(pjsua_snd_get_setting) |
| return PJ_ENOTSUP; |
| } |