Changeset 3982 for pjproject/trunk/pjsip/src/pjsua-lib/pjsua_media.c
- Timestamp:
- Mar 22, 2012 9:56:52 AM (11 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
pjproject/trunk/pjsip/src/pjsua-lib/pjsua_media.c
r3981 r3982 26 26 #define DEFAULT_RTP_PORT 4000 27 27 28 #define NULL_SND_DEV_ID -9929 30 28 #ifndef PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT 31 29 # define PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT 0 32 30 #endif 33 31 34 35 32 /* Next RTP port to be used */ 36 33 static pj_uint16_t next_rtp_port; 37 38 /* Open sound dev */39 static pj_status_t open_snd_dev(pjmedia_snd_port_param *param);40 /* Close existing sound device */41 static void close_snd_dev(void);42 /* Create audio device param */43 static pj_status_t create_aud_param(pjmedia_aud_param *param,44 pjmedia_aud_dev_index capture_dev,45 pjmedia_aud_dev_index playback_dev,46 unsigned clock_rate,47 unsigned channel_count,48 unsigned samples_per_frame,49 unsigned bits_per_sample);50 51 34 52 35 static void pjsua_media_config_dup(pj_pool_t *pool, … … 65 48 pj_status_t pjsua_media_subsys_init(const pjsua_media_config *cfg) 66 49 { 67 pj_str_t codec_id = {NULL, 0};68 unsigned opt;69 pjmedia_audio_codec_config codec_cfg;70 50 pj_status_t status; 71 72 /* To suppress warning about unused var when all codecs are disabled */73 PJ_UNUSED_ARG(codec_id);74 51 75 52 pj_log_push_indent(); … … 116 93 } 117 94 118 /* 119 * Register all codecs 120 */ 121 pjmedia_audio_codec_config_default(&codec_cfg); 122 codec_cfg.speex.quality = pjsua_var.media_cfg.quality; 123 codec_cfg.speex.complexity = -1; 124 codec_cfg.ilbc.mode = pjsua_var.media_cfg.ilbc_mode; 125 126 #if PJMEDIA_HAS_PASSTHROUGH_CODECS 127 /* Register passthrough codecs */ 128 { 129 unsigned aud_idx; 130 unsigned ext_fmt_cnt = 0; 131 pjmedia_format ext_fmts[32]; 132 133 /* List extended formats supported by audio devices */ 134 for (aud_idx = 0; aud_idx < pjmedia_aud_dev_count(); ++aud_idx) { 135 pjmedia_aud_dev_info aud_info; 136 unsigned i; 137 138 status = pjmedia_aud_dev_get_info(aud_idx, &aud_info); 139 if (status != PJ_SUCCESS) { 140 pjsua_perror(THIS_FILE, "Error querying audio device info", 141 status); 142 goto on_error; 143 } 144 145 /* Collect extended formats supported by this audio device */ 146 for (i = 0; i < aud_info.ext_fmt_cnt; ++i) { 147 unsigned j; 148 pj_bool_t is_listed = PJ_FALSE; 149 150 /* See if this extended format is already in the list */ 151 for (j = 0; j < ext_fmt_cnt && !is_listed; ++j) { 152 if (ext_fmts[j].id == aud_info.ext_fmt[i].id && 153 ext_fmts[j].det.aud.avg_bps == 154 aud_info.ext_fmt[i].det.aud.avg_bps) 155 { 156 is_listed = PJ_TRUE; 157 } 158 } 159 160 /* Put this format into the list, if it is not in the list */ 161 if (!is_listed) 162 ext_fmts[ext_fmt_cnt++] = aud_info.ext_fmt[i]; 163 164 pj_assert(ext_fmt_cnt <= PJ_ARRAY_SIZE(ext_fmts)); 165 } 166 } 167 168 /* Init the passthrough codec with supported formats only */ 169 codec_cfg.passthrough.setting.fmt_cnt = ext_fmt_cnt; 170 codec_cfg.passthrough.setting.fmts = ext_fmts; 171 codec_cfg.passthrough.setting.ilbc_mode = cfg->ilbc_mode; 172 } 173 #endif /* PJMEDIA_HAS_PASSTHROUGH_CODECS */ 174 175 /* Register all codecs */ 176 status = pjmedia_codec_register_audio_codecs(pjsua_var.med_endpt, 177 &codec_cfg); 178 if (status != PJ_SUCCESS) { 179 PJ_PERROR(1,(THIS_FILE, status, "Error registering codecs")); 95 status = pjsua_aud_subsys_init(); 96 if (status != PJ_SUCCESS) 180 97 goto on_error; 181 }182 183 /* Set speex/16000 to higher priority*/184 codec_id = pj_str("speex/16000");185 pjmedia_codec_mgr_set_codec_priority(186 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),187 &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+2);188 189 /* Set speex/8000 to next higher priority*/190 codec_id = pj_str("speex/8000");191 pjmedia_codec_mgr_set_codec_priority(192 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),193 &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+1);194 195 /* Disable ALL L16 codecs */196 codec_id = pj_str("L16");197 pjmedia_codec_mgr_set_codec_priority(198 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),199 &codec_id, PJMEDIA_CODEC_PRIO_DISABLED);200 201 202 /* Save additional conference bridge parameters for future203 * reference.204 */205 pjsua_var.mconf_cfg.channel_count = pjsua_var.media_cfg.channel_count;206 pjsua_var.mconf_cfg.bits_per_sample = 16;207 pjsua_var.mconf_cfg.samples_per_frame = pjsua_var.media_cfg.clock_rate *208 pjsua_var.mconf_cfg.channel_count *209 pjsua_var.media_cfg.audio_frame_ptime /210 1000;211 212 /* Init options for conference bridge. */213 opt = PJMEDIA_CONF_NO_DEVICE;214 if (pjsua_var.media_cfg.quality >= 3 &&215 pjsua_var.media_cfg.quality <= 4)216 {217 opt |= PJMEDIA_CONF_SMALL_FILTER;218 }219 else if (pjsua_var.media_cfg.quality < 3) {220 opt |= PJMEDIA_CONF_USE_LINEAR;221 }222 223 /* Init conference bridge. */224 status = pjmedia_conf_create(pjsua_var.pool,225 pjsua_var.media_cfg.max_media_ports,226 pjsua_var.media_cfg.clock_rate,227 pjsua_var.mconf_cfg.channel_count,228 pjsua_var.mconf_cfg.samples_per_frame,229 pjsua_var.mconf_cfg.bits_per_sample,230 opt, &pjsua_var.mconf);231 if (status != PJ_SUCCESS) {232 pjsua_perror(THIS_FILE, "Error creating conference bridge",233 status);234 goto on_error;235 }236 237 /* Are we using the audio switchboard (a.k.a APS-Direct)? */238 pjsua_var.is_mswitch = pjmedia_conf_get_master_port(pjsua_var.mconf)239 ->info.signature == PJMEDIA_CONF_SWITCH_SIGNATURE;240 241 /* Create null port just in case user wants to use null sound. */242 status = pjmedia_null_port_create(pjsua_var.pool,243 pjsua_var.media_cfg.clock_rate,244 pjsua_var.mconf_cfg.channel_count,245 pjsua_var.mconf_cfg.samples_per_frame,246 pjsua_var.mconf_cfg.bits_per_sample,247 &pjsua_var.null_port);248 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);249 98 250 99 #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0) … … 273 122 } 274 123 275 276 /* Check if sound device is idle. */277 static void check_snd_dev_idle()278 {279 unsigned call_cnt;280 281 /* Check if the sound device auto-close feature is disabled. */282 if (pjsua_var.media_cfg.snd_auto_close_time < 0)283 return;284 285 /* Check if the sound device is currently closed. */286 if (!pjsua_var.snd_is_on)287 return;288 289 /* Get the call count, we shouldn't close the sound device when there is290 * any calls active.291 */292 call_cnt = pjsua_call_get_count();293 294 /* When this function is called from pjsua_media_channel_deinit() upon295 * disconnecting call, actually the call count hasn't been updated/296 * decreased. So we put additional check here, if there is only one297 * call and it's in DISCONNECTED state, there is actually no active298 * call.299 */300 if (call_cnt == 1) {301 pjsua_call_id call_id;302 pj_status_t status;303 304 status = pjsua_enum_calls(&call_id, &call_cnt);305 if (status == PJ_SUCCESS && call_cnt > 0 &&306 !pjsua_call_is_active(call_id))307 {308 call_cnt = 0;309 }310 }311 312 /* Activate sound device auto-close timer if sound device is idle.313 * It is idle when there is no port connection in the bridge and314 * there is no active call.315 */316 if (pjsua_var.snd_idle_timer.id == PJ_FALSE &&317 call_cnt == 0 &&318 pjmedia_conf_get_connect_count(pjsua_var.mconf) == 0)319 {320 pj_time_val delay;321 322 delay.msec = 0;323 delay.sec = pjsua_var.media_cfg.snd_auto_close_time;324 325 pjsua_var.snd_idle_timer.id = PJ_TRUE;326 pjsip_endpt_schedule_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer,327 &delay);328 }329 }330 331 332 /* Timer callback to close sound device */333 static void close_snd_timer_cb( pj_timer_heap_t *th,334 pj_timer_entry *entry)335 {336 PJ_UNUSED_ARG(th);337 338 PJSUA_LOCK();339 if (entry->id) {340 PJ_LOG(4,(THIS_FILE,"Closing sound device after idle for %d seconds",341 pjsua_var.media_cfg.snd_auto_close_time));342 343 entry->id = PJ_FALSE;344 345 close_snd_dev();346 }347 PJSUA_UNLOCK();348 }349 350 351 124 /* 352 125 * Start pjsua media subsystem. … … 375 148 #endif 376 149 377 pj_timer_entry_init(&pjsua_var.snd_idle_timer, PJ_FALSE, NULL, 378 &close_snd_timer_cb); 150 /* Audio */ 151 status = pjsua_aud_subsys_start(); 152 if (status != PJ_SUCCESS) { 153 pj_log_pop_indent(); 154 return status; 155 } 379 156 380 157 /* Video */ … … 382 159 status = pjsua_vid_subsys_start(); 383 160 if (status != PJ_SUCCESS) { 161 pjsua_aud_subsys_destroy(); 384 162 pj_log_pop_indent(); 385 163 return status; … … 408 186 pj_log_push_indent(); 409 187 410 close_snd_dev(); 411 412 if (pjsua_var.mconf) { 413 pjmedia_conf_destroy(pjsua_var.mconf); 414 pjsua_var.mconf = NULL; 415 } 416 417 if (pjsua_var.null_port) { 418 pjmedia_port_destroy(pjsua_var.null_port); 419 pjsua_var.null_port = NULL; 420 } 421 422 /* Destroy file players */ 423 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.player); ++i) { 424 if (pjsua_var.player[i].port) { 425 pjmedia_port_destroy(pjsua_var.player[i].port); 426 pjsua_var.player[i].port = NULL; 427 } 428 } 429 430 /* Destroy file recorders */ 431 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.recorder); ++i) { 432 if (pjsua_var.recorder[i].port) { 433 pjmedia_port_destroy(pjsua_var.recorder[i].port); 434 pjsua_var.recorder[i].port = NULL; 435 } 188 if (pjsua_var.med_endpt) { 189 pjsua_aud_subsys_destroy(); 436 190 } 437 191 … … 1328 1082 1329 1083 /* Set media transport state and notify the application via the callback. */ 1330 void set_media_tp_state(pjsua_call_media *call_med,1331 pjsua_med_tp_st tp_st)1084 void pjsua_set_media_tp_state(pjsua_call_media *call_med, 1085 pjsua_med_tp_st tp_st) 1332 1086 { 1333 1087 if (pjsua_var.ua_cfg.cb.on_call_media_transport_state && … … 1363 1117 1364 1118 if (call_med->tp_st == PJSUA_MED_TP_CREATING) 1365 set_media_tp_state(call_med, PJSUA_MED_TP_IDLE);1119 pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_IDLE); 1366 1120 1367 1121 if (!call_med->tp_orig && … … 1467 1221 if (call_med->tp == NULL) { 1468 1222 #if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0) 1469 pjsua_acc *acc = &pjsua_var.acc[call_med->call->acc_id];1470 1471 1223 /* While in initial call, set default video devices */ 1472 1224 if (type == PJMEDIA_TYPE_VIDEO) { 1473 call_med->strm.v.rdr_dev = acc->cfg.vid_rend_dev; 1474 call_med->strm.v.cap_dev = acc->cfg.vid_cap_dev; 1475 if (call_med->strm.v.rdr_dev == PJMEDIA_VID_DEFAULT_RENDER_DEV) { 1476 pjmedia_vid_dev_info info; 1477 pjmedia_vid_dev_get_info(call_med->strm.v.rdr_dev, &info); 1478 call_med->strm.v.rdr_dev = info.id; 1479 } 1480 if (call_med->strm.v.cap_dev == PJMEDIA_VID_DEFAULT_CAPTURE_DEV) { 1481 pjmedia_vid_dev_info info; 1482 pjmedia_vid_dev_get_info(call_med->strm.v.cap_dev, &info); 1483 call_med->strm.v.cap_dev = info.id; 1484 } 1225 status = pjsua_vid_channel_init(call_med); 1226 if (status != PJ_SUCCESS) 1227 return status; 1485 1228 } 1486 1229 #endif 1487 1230 1488 set_media_tp_state(call_med, PJSUA_MED_TP_CREATING);1231 pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_CREATING); 1489 1232 1490 1233 if (pjsua_var.media_cfg.enable_ice) { … … 1515 1258 } else if (call_med->tp_st == PJSUA_MED_TP_DISABLED) { 1516 1259 /* Media is being reenabled. */ 1517 set_media_tp_state(call_med, PJSUA_MED_TP_INIT);1260 pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_INIT); 1518 1261 } 1519 1262 … … 1616 1359 } 1617 1360 1618 set_media_tp_state(call_med, PJSUA_MED_TP_INIT);1361 pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_INIT); 1619 1362 } 1620 1363 } … … 1881 1624 pj_assert(call_med->tp_st == PJSUA_MED_TP_INIT || 1882 1625 call_med->tp_st == PJSUA_MED_TP_RUNNING); 1883 set_media_tp_state(call_med, PJSUA_MED_TP_DISABLED);1626 pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_DISABLED); 1884 1627 } 1885 1628 … … 2243 1986 2244 1987 if (call_med->type == PJMEDIA_TYPE_AUDIO) { 2245 pjmedia_stream *strm = call_med->strm.a.stream; 2246 pjmedia_rtcp_stat stat; 2247 2248 if (strm) { 2249 if (call_med->strm.a.conf_slot != PJSUA_INVALID_ID) { 2250 if (pjsua_var.mconf) { 2251 pjsua_conf_remove_port(call_med->strm.a.conf_slot); 2252 } 2253 call_med->strm.a.conf_slot = PJSUA_INVALID_ID; 2254 } 2255 2256 if ((call_med->dir & PJMEDIA_DIR_ENCODING) && 2257 (pjmedia_stream_get_stat(strm, &stat) == PJ_SUCCESS)) 2258 { 2259 /* Save RTP timestamp & sequence, so when media session is 2260 * restarted, those values will be restored as the initial 2261 * RTP timestamp & sequence of the new media session. So in 2262 * the same call session, RTP timestamp and sequence are 2263 * guaranteed to be contigue. 2264 */ 2265 call_med->rtp_tx_seq_ts_set = 1 | (1 << 1); 2266 call_med->rtp_tx_seq = stat.rtp_tx_last_seq; 2267 call_med->rtp_tx_ts = stat.rtp_tx_last_ts; 2268 } 2269 2270 if (pjsua_var.ua_cfg.cb.on_stream_destroyed) { 2271 pjsua_var.ua_cfg.cb.on_stream_destroyed(call_id, strm, mi); 2272 } 2273 2274 pjmedia_stream_destroy(strm); 2275 call_med->strm.a.stream = NULL; 2276 } 1988 pjsua_aud_stop_stream(call_med); 2277 1989 } 2278 1990 2279 1991 #if PJMEDIA_HAS_VIDEO 2280 1992 else if (call_med->type == PJMEDIA_TYPE_VIDEO) { 2281 stop_video_stream(call_med);1993 pjsua_vid_stop_stream(call_med); 2282 1994 } 2283 1995 #endif … … 2315 2027 pj_log_push_indent(); 2316 2028 2029 stop_media_session(call_id); 2030 2317 2031 for (mi=0; mi<call->med_cnt; ++mi) { 2318 2032 pjsua_call_media *call_med = &call->media[mi]; 2319 2033 2320 if (call_med->type == PJMEDIA_TYPE_AUDIO && call_med->strm.a.stream)2321 pjmedia_stream_send_rtcp_bye(call_med->strm.a.stream);2322 }2323 2324 stop_media_session(call_id);2325 2326 for (mi=0; mi<call->med_cnt; ++mi) {2327 pjsua_call_media *call_med = &call->media[mi];2328 2329 2034 if (call_med->tp_st > PJSUA_MED_TP_IDLE) { 2330 2035 pjmedia_transport_media_stop(call_med->tp); 2331 set_media_tp_state(call_med, PJSUA_MED_TP_IDLE);2036 pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_IDLE); 2332 2037 } 2333 2038 … … 2345 2050 } 2346 2051 2347 check_snd_dev_idle();2348 2052 pj_log_pop_indent(); 2349 2053 … … 2351 2055 } 2352 2056 2353 2354 /*2355 * DTMF callback from the stream.2356 */2357 static void dtmf_callback(pjmedia_stream *strm, void *user_data,2358 int digit)2359 {2360 PJ_UNUSED_ARG(strm);2361 2362 pj_log_push_indent();2363 2364 /* For discussions about call mutex protection related to this2365 * callback, please see ticket #460:2366 * http://trac.pjsip.org/repos/ticket/460#comment:42367 */2368 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {2369 pjsua_call_id call_id;2370 2371 call_id = (pjsua_call_id)(long)user_data;2372 pjsua_var.ua_cfg.cb.on_dtmf_digit(call_id, digit);2373 }2374 2375 pj_log_pop_indent();2376 }2377 2378 2379 static pj_status_t audio_channel_update(pjsua_call_media *call_med,2380 pj_pool_t *tmp_pool,2381 const pjmedia_sdp_session *local_sdp,2382 const pjmedia_sdp_session *remote_sdp)2383 {2384 pjsua_call *call = call_med->call;2385 pjmedia_stream_info the_si, *si = &the_si;2386 pjmedia_port *media_port;2387 unsigned strm_idx = call_med->idx;2388 pj_status_t status;2389 2390 PJ_LOG(4,(THIS_FILE,"Audio channel update.."));2391 pj_log_push_indent();2392 2393 status = pjmedia_stream_info_from_sdp(si, tmp_pool, pjsua_var.med_endpt,2394 local_sdp, remote_sdp, strm_idx);2395 if (status != PJ_SUCCESS)2396 goto on_return;2397 2398 si->rtcp_sdes_bye_disabled = PJ_TRUE;2399 2400 /* Check if no media is active */2401 if (si->dir == PJMEDIA_DIR_NONE) {2402 /* Call media state */2403 call_med->state = PJSUA_CALL_MEDIA_NONE;2404 2405 /* Call media direction */2406 call_med->dir = PJMEDIA_DIR_NONE;2407 2408 } else {2409 pjmedia_transport_info tp_info;2410 2411 /* Start/restart media transport */2412 status = pjmedia_transport_media_start(call_med->tp,2413 tmp_pool, local_sdp,2414 remote_sdp, strm_idx);2415 if (status != PJ_SUCCESS)2416 goto on_return;2417 2418 set_media_tp_state(call_med, PJSUA_MED_TP_RUNNING);2419 2420 /* Get remote SRTP usage policy */2421 pjmedia_transport_info_init(&tp_info);2422 pjmedia_transport_get_info(call_med->tp, &tp_info);2423 if (tp_info.specific_info_cnt > 0) {2424 unsigned i;2425 for (i = 0; i < tp_info.specific_info_cnt; ++i) {2426 if (tp_info.spc_info[i].type == PJMEDIA_TRANSPORT_TYPE_SRTP)2427 {2428 pjmedia_srtp_info *srtp_info =2429 (pjmedia_srtp_info*) tp_info.spc_info[i].buffer;2430 2431 call_med->rem_srtp_use = srtp_info->peer_use;2432 break;2433 }2434 }2435 }2436 2437 /* Override ptime, if this option is specified. */2438 if (pjsua_var.media_cfg.ptime != 0) {2439 si->param->setting.frm_per_pkt = (pj_uint8_t)2440 (pjsua_var.media_cfg.ptime / si->param->info.frm_ptime);2441 if (si->param->setting.frm_per_pkt == 0)2442 si->param->setting.frm_per_pkt = 1;2443 }2444 2445 /* Disable VAD, if this option is specified. */2446 if (pjsua_var.media_cfg.no_vad) {2447 si->param->setting.vad = 0;2448 }2449 2450 2451 /* Optionally, application may modify other stream settings here2452 * (such as jitter buffer parameters, codec ptime, etc.)2453 */2454 si->jb_init = pjsua_var.media_cfg.jb_init;2455 si->jb_min_pre = pjsua_var.media_cfg.jb_min_pre;2456 si->jb_max_pre = pjsua_var.media_cfg.jb_max_pre;2457 si->jb_max = pjsua_var.media_cfg.jb_max;2458 2459 /* Set SSRC */2460 si->ssrc = call_med->ssrc;2461 2462 /* Set RTP timestamp & sequence, normally these value are intialized2463 * automatically when stream session created, but for some cases (e.g:2464 * call reinvite, call update) timestamp and sequence need to be kept2465 * contigue.2466 */2467 si->rtp_ts = call_med->rtp_tx_ts;2468 si->rtp_seq = call_med->rtp_tx_seq;2469 si->rtp_seq_ts_set = call_med->rtp_tx_seq_ts_set;2470 2471 #if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=02472 /* Enable/disable stream keep-alive and NAT hole punch. */2473 si->use_ka = pjsua_var.acc[call->acc_id].cfg.use_stream_ka;2474 #endif2475 2476 /* Create session based on session info. */2477 status = pjmedia_stream_create(pjsua_var.med_endpt, NULL, si,2478 call_med->tp, NULL,2479 &call_med->strm.a.stream);2480 if (status != PJ_SUCCESS) {2481 goto on_return;2482 }2483 2484 /* Start stream */2485 status = pjmedia_stream_start(call_med->strm.a.stream);2486 if (status != PJ_SUCCESS) {2487 goto on_return;2488 }2489 2490 if (call_med->prev_state == PJSUA_CALL_MEDIA_NONE)2491 pjmedia_stream_send_rtcp_sdes(call_med->strm.a.stream);2492 2493 /* If DTMF callback is installed by application, install our2494 * callback to the session.2495 */2496 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {2497 pjmedia_stream_set_dtmf_callback(call_med->strm.a.stream,2498 &dtmf_callback,2499 (void*)(long)(call->index));2500 }2501 2502 /* Get the port interface of the first stream in the session.2503 * We need the port interface to add to the conference bridge.2504 */2505 pjmedia_stream_get_port(call_med->strm.a.stream, &media_port);2506 2507 /* Notify application about stream creation.2508 * Note: application may modify media_port to point to different2509 * media port2510 */2511 if (pjsua_var.ua_cfg.cb.on_stream_created) {2512 pjsua_var.ua_cfg.cb.on_stream_created(call->index,2513 call_med->strm.a.stream,2514 strm_idx, &media_port);2515 }2516 2517 /*2518 * Add the call to conference bridge.2519 */2520 {2521 char tmp[PJSIP_MAX_URL_SIZE];2522 pj_str_t port_name;2523 2524 port_name.ptr = tmp;2525 port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,2526 call->inv->dlg->remote.info->uri,2527 tmp, sizeof(tmp));2528 if (port_name.slen < 1) {2529 port_name = pj_str("call");2530 }2531 status = pjmedia_conf_add_port( pjsua_var.mconf,2532 call->inv->pool_prov,2533 media_port,2534 &port_name,2535 (unsigned*)2536 &call_med->strm.a.conf_slot);2537 if (status != PJ_SUCCESS) {2538 goto on_return;2539 }2540 }2541 2542 /* Call media direction */2543 call_med->dir = si->dir;2544 2545 /* Call media state */2546 if (call->local_hold)2547 call_med->state = PJSUA_CALL_MEDIA_LOCAL_HOLD;2548 else if (call_med->dir == PJMEDIA_DIR_DECODING)2549 call_med->state = PJSUA_CALL_MEDIA_REMOTE_HOLD;2550 else2551 call_med->state = PJSUA_CALL_MEDIA_ACTIVE;2552 }2553 2554 /* Print info. */2555 {2556 char info[80];2557 int info_len = 0;2558 int len;2559 const char *dir;2560 2561 switch (si->dir) {2562 case PJMEDIA_DIR_NONE:2563 dir = "inactive";2564 break;2565 case PJMEDIA_DIR_ENCODING:2566 dir = "sendonly";2567 break;2568 case PJMEDIA_DIR_DECODING:2569 dir = "recvonly";2570 break;2571 case PJMEDIA_DIR_ENCODING_DECODING:2572 dir = "sendrecv";2573 break;2574 default:2575 dir = "unknown";2576 break;2577 }2578 len = pj_ansi_sprintf( info+info_len,2579 ", stream #%d: %.*s (%s)", strm_idx,2580 (int)si->fmt.encoding_name.slen,2581 si->fmt.encoding_name.ptr,2582 dir);2583 if (len > 0)2584 info_len += len;2585 PJ_LOG(4,(THIS_FILE,"Audio updated%s", info));2586 }2587 2588 on_return:2589 pj_log_pop_indent();2590 return status;2591 }2592 2057 2593 2058 pj_status_t pjsua_media_channel_update(pjsua_call_id call_id, … … 2690 2155 } 2691 2156 2692 switch (call_med->type) { 2693 case PJMEDIA_TYPE_AUDIO: 2694 status = audio_channel_update(call_med, tmp_pool, 2695 local_sdp, remote_sdp); 2157 if (call_med->type==PJMEDIA_TYPE_AUDIO) { 2158 pjmedia_stream_info the_si, *si = &the_si; 2159 2160 status = pjmedia_stream_info_from_sdp(si, tmp_pool, pjsua_var.med_endpt, 2161 local_sdp, remote_sdp, mi); 2162 if (status != PJ_SUCCESS) { 2163 PJ_PERROR(1,(THIS_FILE, status, 2164 "pjmedia_stream_info_from_sdp() failed " 2165 "for call_id %d media %d", 2166 call_id, mi)); 2167 continue; 2168 } 2169 2170 /* Check if no media is active */ 2171 if (si->dir == PJMEDIA_DIR_NONE) { 2172 /* Update call media state and direction */ 2173 call_med->state = PJSUA_CALL_MEDIA_NONE; 2174 call_med->dir = PJMEDIA_DIR_NONE; 2175 2176 } else { 2177 pjmedia_transport_info tp_info; 2178 2179 /* Start/restart media transport based on info in SDP */ 2180 status = pjmedia_transport_media_start(call_med->tp, 2181 tmp_pool, local_sdp, 2182 remote_sdp, mi); 2183 if (status != PJ_SUCCESS) { 2184 PJ_PERROR(1,(THIS_FILE, status, 2185 "pjmedia_transport_media_start() failed " 2186 "for call_id %d media %d", 2187 call_id, mi)); 2188 continue; 2189 } 2190 2191 pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_RUNNING); 2192 2193 /* Get remote SRTP usage policy */ 2194 pjmedia_transport_info_init(&tp_info); 2195 pjmedia_transport_get_info(call_med->tp, &tp_info); 2196 if (tp_info.specific_info_cnt > 0) { 2197 unsigned i; 2198 for (i = 0; i < tp_info.specific_info_cnt; ++i) { 2199 if (tp_info.spc_info[i].type == PJMEDIA_TRANSPORT_TYPE_SRTP) 2200 { 2201 pjmedia_srtp_info *srtp_info = 2202 (pjmedia_srtp_info*) tp_info.spc_info[i].buffer; 2203 2204 call_med->rem_srtp_use = srtp_info->peer_use; 2205 break; 2206 } 2207 } 2208 } 2209 2210 /* Call media direction */ 2211 call_med->dir = si->dir; 2212 2213 /* Call media state */ 2214 if (call->local_hold) 2215 call_med->state = PJSUA_CALL_MEDIA_LOCAL_HOLD; 2216 else if (call_med->dir == PJMEDIA_DIR_DECODING) 2217 call_med->state = PJSUA_CALL_MEDIA_REMOTE_HOLD; 2218 else 2219 call_med->state = PJSUA_CALL_MEDIA_ACTIVE; 2220 } 2221 2222 /* Call implementation */ 2223 status = pjsua_aud_channel_update(call_med, tmp_pool, si, 2224 local_sdp, remote_sdp); 2225 if (status != PJ_SUCCESS) { 2226 PJ_PERROR(1,(THIS_FILE, status, 2227 "pjsua_aud_channel_update() failed " 2228 "for call_id %d media %d", 2229 call_id, mi)); 2230 continue; 2231 } 2232 2233 /* Print info. */ 2234 if (status == PJ_SUCCESS) { 2235 char info[80]; 2236 int info_len = 0; 2237 int len; 2238 const char *dir; 2239 2240 switch (si->dir) { 2241 case PJMEDIA_DIR_NONE: 2242 dir = "inactive"; 2243 break; 2244 case PJMEDIA_DIR_ENCODING: 2245 dir = "sendonly"; 2246 break; 2247 case PJMEDIA_DIR_DECODING: 2248 dir = "recvonly"; 2249 break; 2250 case PJMEDIA_DIR_ENCODING_DECODING: 2251 dir = "sendrecv"; 2252 break; 2253 default: 2254 dir = "unknown"; 2255 break; 2256 } 2257 len = pj_ansi_sprintf( info+info_len, 2258 ", stream #%d: %.*s (%s)", mi, 2259 (int)si->fmt.encoding_name.slen, 2260 si->fmt.encoding_name.ptr, 2261 dir); 2262 if (len > 0) 2263 info_len += len; 2264 PJ_LOG(4,(THIS_FILE,"Audio updated%s", info)); 2265 } 2266 2267 2696 2268 if (call->audio_idx==-1 && status==PJ_SUCCESS && 2697 call_med->strm.a.stream)2269 si->dir != PJMEDIA_DIR_NONE) 2698 2270 { 2699 2271 call->audio_idx = mi; 2700 2272 } 2701 break; 2273 2702 2274 #if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0) 2703 case PJMEDIA_TYPE_VIDEO: 2704 status = video_channel_update(call_med, tmp_pool, 2705 local_sdp, remote_sdp); 2706 break; 2275 } else if (call_med->type==PJMEDIA_TYPE_VIDEO) { 2276 pjmedia_vid_stream_info the_si, *si = &the_si; 2277 2278 status = pjmedia_vid_stream_info_from_sdp(si, tmp_pool, pjsua_var.med_endpt, 2279 local_sdp, remote_sdp, mi); 2280 if (status != PJ_SUCCESS) { 2281 PJ_PERROR(1,(THIS_FILE, status, 2282 "pjmedia_vid_stream_info_from_sdp() failed " 2283 "for call_id %d media %d", 2284 call_id, mi)); 2285 continue; 2286 } 2287 2288 /* Check if no media is active */ 2289 if (si->dir == PJMEDIA_DIR_NONE) { 2290 /* Call media state */ 2291 call_med->state = PJSUA_CALL_MEDIA_NONE; 2292 2293 /* Call media direction */ 2294 call_med->dir = PJMEDIA_DIR_NONE; 2295 2296 } else { 2297 pjmedia_transport_info tp_info; 2298 2299 /* Start/restart media transport */ 2300 status = pjmedia_transport_media_start(call_med->tp, 2301 tmp_pool, local_sdp, 2302 remote_sdp, mi); 2303 if (status != PJ_SUCCESS) { 2304 PJ_PERROR(1,(THIS_FILE, status, 2305 "pjmedia_transport_media_start() failed " 2306 "for call_id %d media %d", 2307 call_id, mi)); 2308 continue; 2309 } 2310 2311 pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_RUNNING); 2312 2313 /* Get remote SRTP usage policy */ 2314 pjmedia_transport_info_init(&tp_info); 2315 pjmedia_transport_get_info(call_med->tp, &tp_info); 2316 if (tp_info.specific_info_cnt > 0) { 2317 unsigned i; 2318 for (i = 0; i < tp_info.specific_info_cnt; ++i) { 2319 if (tp_info.spc_info[i].type == 2320 PJMEDIA_TRANSPORT_TYPE_SRTP) 2321 { 2322 pjmedia_srtp_info *sri; 2323 sri=(pjmedia_srtp_info*)tp_info.spc_info[i].buffer; 2324 call_med->rem_srtp_use = sri->peer_use; 2325 break; 2326 } 2327 } 2328 } 2329 2330 /* Call media direction */ 2331 call_med->dir = si->dir; 2332 2333 /* Call media state */ 2334 if (call->local_hold) 2335 call_med->state = PJSUA_CALL_MEDIA_LOCAL_HOLD; 2336 else if (call_med->dir == PJMEDIA_DIR_DECODING) 2337 call_med->state = PJSUA_CALL_MEDIA_REMOTE_HOLD; 2338 else 2339 call_med->state = PJSUA_CALL_MEDIA_ACTIVE; 2340 } 2341 2342 status = pjsua_vid_channel_update(call_med, tmp_pool, si, 2343 local_sdp, remote_sdp); 2344 if (status != PJ_SUCCESS) { 2345 PJ_PERROR(1,(THIS_FILE, status, 2346 "pjmedia_transport_media_start() failed " 2347 "for call_id %d media %d", 2348 call_id, mi)); 2349 continue; 2350 } 2351 2352 /* Print info. */ 2353 { 2354 char info[80]; 2355 int info_len = 0; 2356 int len; 2357 const char *dir; 2358 2359 switch (si->dir) { 2360 case PJMEDIA_DIR_NONE: 2361 dir = "inactive"; 2362 break; 2363 case PJMEDIA_DIR_ENCODING: 2364 dir = "sendonly"; 2365 break; 2366 case PJMEDIA_DIR_DECODING: 2367 dir = "recvonly"; 2368 break; 2369 case PJMEDIA_DIR_ENCODING_DECODING: 2370 dir = "sendrecv"; 2371 break; 2372 default: 2373 dir = "unknown"; 2374 break; 2375 } 2376 len = pj_ansi_sprintf( info+info_len, 2377 ", stream #%d: %.*s (%s)", mi, 2378 (int)si->codec_info.encoding_name.slen, 2379 si->codec_info.encoding_name.ptr, 2380 dir); 2381 if (len > 0) 2382 info_len += len; 2383 PJ_LOG(4,(THIS_FILE,"Video updated%s", info)); 2384 } 2385 2707 2386 #endif 2708 default:2387 } else { 2709 2388 status = PJMEDIA_EINVALIMEDIATYPE; 2710 break;2711 2389 } 2712 2390 … … 2718 2396 pjmedia_transport_close(call_med->tp); 2719 2397 call_med->tp = call_med->tp_orig = NULL; 2720 set_media_tp_state(call_med, PJSUA_MED_TP_IDLE);2398 pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_IDLE); 2721 2399 } 2722 2400 … … 2758 2436 } 2759 2437 2760 /*2761 * Get maxinum number of conference ports.2762 */2763 PJ_DEF(unsigned) pjsua_conf_get_max_ports(void)2764 {2765 return pjsua_var.media_cfg.max_media_ports;2766 }2767 2768 2769 /*2770 * Get current number of active ports in the bridge.2771 */2772 PJ_DEF(unsigned) pjsua_conf_get_active_ports(void)2773 {2774 unsigned ports[PJSUA_MAX_CONF_PORTS];2775 unsigned count = PJ_ARRAY_SIZE(ports);2776 pj_status_t status;2777 2778 status = pjmedia_conf_enum_ports(pjsua_var.mconf, ports, &count);2779 if (status != PJ_SUCCESS)2780 count = 0;2781 2782 return count;2783 }2784 2785 2786 /*2787 * Enumerate all conference ports.2788 */2789 PJ_DEF(pj_status_t) pjsua_enum_conf_ports(pjsua_conf_port_id id[],2790 unsigned *count)2791 {2792 return pjmedia_conf_enum_ports(pjsua_var.mconf, (unsigned*)id, count);2793 }2794 2795 2796 /*2797 * Get information about the specified conference port2798 */2799 PJ_DEF(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id,2800 pjsua_conf_port_info *info)2801 {2802 pjmedia_conf_port_info cinfo;2803 unsigned i;2804 pj_status_t status;2805 2806 status = pjmedia_conf_get_port_info( pjsua_var.mconf, id, &cinfo);2807 if (status != PJ_SUCCESS)2808 return status;2809 2810 pj_bzero(info, sizeof(*info));2811 info->slot_id = id;2812 info->name = cinfo.name;2813 info->clock_rate = cinfo.clock_rate;2814 info->channel_count = cinfo.channel_count;2815 info->samples_per_frame = cinfo.samples_per_frame;2816 info->bits_per_sample = cinfo.bits_per_sample;2817 2818 /* Build array of listeners */2819 info->listener_cnt = cinfo.listener_cnt;2820 for (i=0; i<cinfo.listener_cnt; ++i) {2821 info->listeners[i] = cinfo.listener_slots[i];2822 }2823 2824 return PJ_SUCCESS;2825 }2826 2827 2828 /*2829 * Add arbitrary media port to PJSUA's conference bridge.2830 */2831 PJ_DEF(pj_status_t) pjsua_conf_add_port( pj_pool_t *pool,2832 pjmedia_port *port,2833 pjsua_conf_port_id *p_id)2834 {2835 pj_status_t status;2836 2837 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,2838 port, NULL, (unsigned*)p_id);2839 if (status != PJ_SUCCESS) {2840 if (p_id)2841 *p_id = PJSUA_INVALID_ID;2842 }2843 2844 return status;2845 }2846 2847 2848 /*2849 * Remove arbitrary slot from the conference bridge.2850 */2851 PJ_DEF(pj_status_t) pjsua_conf_remove_port(pjsua_conf_port_id id)2852 {2853 pj_status_t status;2854 2855 status = pjmedia_conf_remove_port(pjsua_var.mconf, (unsigned)id);2856 check_snd_dev_idle();2857 2858 return status;2859 }2860 2861 2862 /*2863 * Establish unidirectional media flow from souce to sink.2864 */2865 PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source,2866 pjsua_conf_port_id sink)2867 {2868 pj_status_t status = PJ_SUCCESS;2869 2870 PJ_LOG(4,(THIS_FILE, "%s connect: %d --> %d",2871 (pjsua_var.is_mswitch ? "Switch" : "Conf"),2872 source, sink));2873 pj_log_push_indent();2874 2875 /* If sound device idle timer is active, cancel it first. */2876 PJSUA_LOCK();2877 if (pjsua_var.snd_idle_timer.id) {2878 pjsip_endpt_cancel_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer);2879 pjsua_var.snd_idle_timer.id = PJ_FALSE;2880 }2881 PJSUA_UNLOCK();2882 2883 2884 /* For audio switchboard (i.e. APS-Direct):2885 * Check if sound device need to be reopened, i.e: its attributes2886 * (format, clock rate, channel count) must match to peer's.2887 * Note that sound device can be reopened only if it doesn't have2888 * any connection.2889 */2890 if (pjsua_var.is_mswitch) {2891 pjmedia_conf_port_info port0_info;2892 pjmedia_conf_port_info peer_info;2893 unsigned peer_id;2894 pj_bool_t need_reopen = PJ_FALSE;2895 2896 peer_id = (source!=0)? source : sink;2897 status = pjmedia_conf_get_port_info(pjsua_var.mconf, peer_id,2898 &peer_info);2899 pj_assert(status == PJ_SUCCESS);2900 2901 status = pjmedia_conf_get_port_info(pjsua_var.mconf, 0, &port0_info);2902 pj_assert(status == PJ_SUCCESS);2903 2904 /* Check if sound device is instantiated. */2905 need_reopen = (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&2906 !pjsua_var.no_snd);2907 2908 /* Check if sound device need to reopen because it needs to modify2909 * settings to match its peer. Sound device must be idle in this case2910 * though.2911 */2912 if (!need_reopen &&2913 port0_info.listener_cnt==0 && port0_info.transmitter_cnt==0)2914 {2915 need_reopen = (peer_info.format.id != port0_info.format.id ||2916 peer_info.format.det.aud.avg_bps !=2917 port0_info.format.det.aud.avg_bps ||2918 peer_info.clock_rate != port0_info.clock_rate ||2919 peer_info.channel_count!=port0_info.channel_count);2920 }2921 2922 if (need_reopen) {2923 if (pjsua_var.cap_dev != NULL_SND_DEV_ID) {2924 pjmedia_snd_port_param param;2925 2926 /* Create parameter based on peer info */2927 status = create_aud_param(¶m.base, pjsua_var.cap_dev,2928 pjsua_var.play_dev,2929 peer_info.clock_rate,2930 peer_info.channel_count,2931 peer_info.samples_per_frame,2932 peer_info.bits_per_sample);2933 if (status != PJ_SUCCESS) {2934 pjsua_perror(THIS_FILE, "Error opening sound device",2935 status);2936 goto on_return;2937 }2938 2939 /* And peer format */2940 if (peer_info.format.id != PJMEDIA_FORMAT_PCM) {2941 param.base.flags |= PJMEDIA_AUD_DEV_CAP_EXT_FORMAT;2942 param.base.ext_fmt = peer_info.format;2943 }2944 2945 param.options = 0;2946 status = open_snd_dev(¶m);2947 if (status != PJ_SUCCESS) {2948 pjsua_perror(THIS_FILE, "Error opening sound device",2949 status);2950 goto on_return;2951 }2952 } else {2953 /* Null-audio */2954 status = pjsua_set_snd_dev(pjsua_var.cap_dev,2955 pjsua_var.play_dev);2956 if (status != PJ_SUCCESS) {2957 pjsua_perror(THIS_FILE, "Error opening sound device",2958 status);2959 goto on_return;2960 }2961 }2962 } else if (pjsua_var.no_snd) {2963 if (!pjsua_var.snd_is_on) {2964 pjsua_var.snd_is_on = PJ_TRUE;2965 /* Notify app */2966 if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {2967 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);2968 }2969 }2970 }2971 2972 } else {2973 /* The bridge version */2974 2975 /* Create sound port if none is instantiated */2976 if (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&2977 !pjsua_var.no_snd)2978 {2979 pj_status_t status;2980 2981 status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);2982 if (status != PJ_SUCCESS) {2983 pjsua_perror(THIS_FILE, "Error opening sound device", status);2984 goto on_return;2985 }2986 } else if (pjsua_var.no_snd && !pjsua_var.snd_is_on) {2987 pjsua_var.snd_is_on = PJ_TRUE;2988 /* Notify app */2989 if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {2990 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);2991 }2992 }2993 }2994 2995 status = pjmedia_conf_connect_port(pjsua_var.mconf, source, sink, 0);2996 2997 on_return:2998 pj_log_pop_indent();2999 return status;3000 }3001 3002 3003 /*3004 * Disconnect media flow from the source to destination port.3005 */3006 PJ_DEF(pj_status_t) pjsua_conf_disconnect( pjsua_conf_port_id source,3007 pjsua_conf_port_id sink)3008 {3009 pj_status_t status;3010 3011 PJ_LOG(4,(THIS_FILE, "%s disconnect: %d -x- %d",3012 (pjsua_var.is_mswitch ? "Switch" : "Conf"),3013 source, sink));3014 pj_log_push_indent();3015 3016 status = pjmedia_conf_disconnect_port(pjsua_var.mconf, source, sink);3017 check_snd_dev_idle();3018 3019 pj_log_pop_indent();3020 return status;3021 }3022 3023 3024 /*3025 * Adjust the signal level to be transmitted from the bridge to the3026 * specified port by making it louder or quieter.3027 */3028 PJ_DEF(pj_status_t) pjsua_conf_adjust_tx_level(pjsua_conf_port_id slot,3029 float level)3030 {3031 return pjmedia_conf_adjust_tx_level(pjsua_var.mconf, slot,3032 (int)((level-1) * 128));3033 }3034 3035 /*3036 * Adjust the signal level to be received from the specified port (to3037 * the bridge) by making it louder or quieter.3038 */3039 PJ_DEF(pj_status_t) pjsua_conf_adjust_rx_level(pjsua_conf_port_id slot,3040 float level)3041 {3042 return pjmedia_conf_adjust_rx_level(pjsua_var.mconf, slot,3043 (int)((level-1) * 128));3044 }3045 3046 3047 /*3048 * Get last signal level transmitted to or received from the specified port.3049 */3050 PJ_DEF(pj_status_t) pjsua_conf_get_signal_level(pjsua_conf_port_id slot,3051 unsigned *tx_level,3052 unsigned *rx_level)3053 {3054 return pjmedia_conf_get_signal_level(pjsua_var.mconf, slot,3055 tx_level, rx_level);3056 }3057 3058 /*****************************************************************************3059 * File player.3060 */3061 3062 static char* get_basename(const char *path, unsigned len)3063 {3064 char *p = ((char*)path) + len;3065 3066 if (len==0)3067 return p;3068 3069 for (--p; p!=path && *p!='/' && *p!='\\'; ) --p;3070 3071 return (p==path) ? p : p+1;3072 }3073 3074 3075 /*3076 * Create a file player, and automatically connect this player to3077 * the conference bridge.3078 */3079 PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename,3080 unsigned options,3081 pjsua_player_id *p_id)3082 {3083 unsigned slot, file_id;3084 char path[PJ_MAXPATH];3085 pj_pool_t *pool = NULL;3086 pjmedia_port *port;3087 pj_status_t status = PJ_SUCCESS;3088 3089 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))3090 return PJ_ETOOMANY;3091 3092 PJ_LOG(4,(THIS_FILE, "Creating file player: %.*s..",3093 (int)filename->slen, filename->ptr));3094 pj_log_push_indent();3095 3096 PJSUA_LOCK();3097 3098 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {3099 if (pjsua_var.player[file_id].port == NULL)3100 break;3101 }3102 3103 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {3104 /* This is unexpected */3105 pj_assert(0);3106 status = PJ_EBUG;3107 goto on_error;3108 }3109 3110 pj_memcpy(path, filename->ptr, filename->slen);3111 path[filename->slen] = '\0';3112 3113 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);3114 if (!pool) {3115 status = PJ_ENOMEM;3116 goto on_error;3117 }3118 3119 status = pjmedia_wav_player_port_create(3120 pool, path,3121 pjsua_var.mconf_cfg.samples_per_frame *3122 1000 / pjsua_var.media_cfg.channel_count /3123 pjsua_var.media_cfg.clock_rate,3124 options, 0, &port);3125 if (status != PJ_SUCCESS) {3126 pjsua_perror(THIS_FILE, "Unable to open file for playback", status);3127 goto on_error;3128 }3129 3130 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,3131 port, filename, &slot);3132 if (status != PJ_SUCCESS) {3133 pjmedia_port_destroy(port);3134 pjsua_perror(THIS_FILE, "Unable to add file to conference bridge",3135 status);3136 goto on_error;3137 }3138 3139 pjsua_var.player[file_id].type = 0;3140 pjsua_var.player[file_id].pool = pool;3141 pjsua_var.player[file_id].port = port;3142 pjsua_var.player[file_id].slot = slot;3143 3144 if (p_id) *p_id = file_id;3145 3146 ++pjsua_var.player_cnt;3147 3148 PJSUA_UNLOCK();3149 3150 PJ_LOG(4,(THIS_FILE, "Player created, id=%d, slot=%d", file_id, slot));3151 3152 pj_log_pop_indent();3153 return PJ_SUCCESS;3154 3155 on_error:3156 PJSUA_UNLOCK();3157 if (pool) pj_pool_release(pool);3158 pj_log_pop_indent();3159 return status;3160 }3161 3162 3163 /*3164 * Create a file playlist media port, and automatically add the port3165 * to the conference bridge.3166 */3167 PJ_DEF(pj_status_t) pjsua_playlist_create( const pj_str_t file_names[],3168 unsigned file_count,3169 const pj_str_t *label,3170 unsigned options,3171 pjsua_player_id *p_id)3172 {3173 unsigned slot, file_id, ptime;3174 pj_pool_t *pool = NULL;3175 pjmedia_port *port;3176 pj_status_t status = PJ_SUCCESS;3177 3178 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))3179 return PJ_ETOOMANY;3180 3181 PJ_LOG(4,(THIS_FILE, "Creating playlist with %d file(s)..", file_count));3182 pj_log_push_indent();3183 3184 PJSUA_LOCK();3185 3186 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {3187 if (pjsua_var.player[file_id].port == NULL)3188 break;3189 }3190 3191 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {3192 /* This is unexpected */3193 pj_assert(0);3194 status = PJ_EBUG;3195 goto on_error;3196 }3197 3198 3199 ptime = pjsua_var.mconf_cfg.samples_per_frame * 1000 /3200 pjsua_var.media_cfg.clock_rate;3201 3202 pool = pjsua_pool_create("playlist", 1000, 1000);3203 if (!pool) {3204 status = PJ_ENOMEM;3205 goto on_error;3206 }3207 3208 status = pjmedia_wav_playlist_create(pool, label,3209 file_names, file_count,3210 ptime, options, 0, &port);3211 if (status != PJ_SUCCESS) {3212 pjsua_perror(THIS_FILE, "Unable to create playlist", status);3213 goto on_error;3214 }3215 3216 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,3217 port, &port->info.name, &slot);3218 if (status != PJ_SUCCESS) {3219 pjmedia_port_destroy(port);3220 pjsua_perror(THIS_FILE, "Unable to add port", status);3221 goto on_error;3222 }3223 3224 pjsua_var.player[file_id].type = 1;3225 pjsua_var.player[file_id].pool = pool;3226 pjsua_var.player[file_id].port = port;3227 pjsua_var.player[file_id].slot = slot;3228 3229 if (p_id) *p_id = file_id;3230 3231 ++pjsua_var.player_cnt;3232 3233 PJSUA_UNLOCK();3234 3235 PJ_LOG(4,(THIS_FILE, "Playlist created, id=%d, slot=%d", file_id, slot));3236 3237 pj_log_pop_indent();3238 3239 return PJ_SUCCESS;3240 3241 on_error:3242 PJSUA_UNLOCK();3243 if (pool) pj_pool_release(pool);3244 pj_log_pop_indent();3245 3246 return status;3247 }3248 3249 3250 /*3251 * Get conference port ID associated with player.3252 */3253 PJ_DEF(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id)3254 {3255 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);3256 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);3257 3258 return pjsua_var.player[id].slot;3259 }3260 3261 /*3262 * Get the media port for the player.3263 */3264 PJ_DEF(pj_status_t) pjsua_player_get_port( pjsua_player_id id,3265 pjmedia_port **p_port)3266 {3267 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);3268 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);3269 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);3270 3271 *p_port = pjsua_var.player[id].port;3272 3273 return PJ_SUCCESS;3274 }3275 3276 /*3277 * Set playback position.3278 */3279 PJ_DEF(pj_status_t) pjsua_player_set_pos( pjsua_player_id id,3280 pj_uint32_t samples)3281 {3282 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);3283 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);3284 PJ_ASSERT_RETURN(pjsua_var.player[id].type == 0, PJ_EINVAL);3285 3286 return pjmedia_wav_player_port_set_pos(pjsua_var.player[id].port, samples);3287 }3288 3289 3290 /*3291 * Close the file, remove the player from the bridge, and free3292 * resources associated with the file player.3293 */3294 PJ_DEF(pj_status_t) pjsua_player_destroy(pjsua_player_id id)3295 {3296 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);3297 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);3298 3299 PJ_LOG(4,(THIS_FILE, "Destroying player %d..", id));3300 pj_log_push_indent();3301 3302 PJSUA_LOCK();3303 3304 if (pjsua_var.player[id].port) {3305 pjsua_conf_remove_port(pjsua_var.player[id].slot);3306 pjmedia_port_destroy(pjsua_var.player[id].port);3307 pjsua_var.player[id].port = NULL;3308 pjsua_var.player[id].slot = 0xFFFF;3309 pj_pool_release(pjsua_var.player[id].pool);3310 pjsua_var.player[id].pool = NULL;3311 pjsua_var.player_cnt--;3312 }3313 3314 PJSUA_UNLOCK();3315 pj_log_pop_indent();3316 3317 return PJ_SUCCESS;3318 }3319 3320 3321 /*****************************************************************************3322 * File recorder.3323 */3324 3325 /*3326 * Create a file recorder, and automatically connect this recorder to3327 * the conference bridge.3328 */3329 PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename,3330 unsigned enc_type,3331 void *enc_param,3332 pj_ssize_t max_size,3333 unsigned options,3334 pjsua_recorder_id *p_id)3335 {3336 enum Format3337 {3338 FMT_UNKNOWN,3339 FMT_WAV,3340 FMT_MP3,3341 };3342 unsigned slot, file_id;3343 char path[PJ_MAXPATH];3344 pj_str_t ext;3345 int file_format;3346 pj_pool_t *pool = NULL;3347 pjmedia_port *port;3348 pj_status_t status = PJ_SUCCESS;3349 3350 /* Filename must present */3351 PJ_ASSERT_RETURN(filename != NULL, PJ_EINVAL);3352 3353 /* Don't support max_size at present */3354 PJ_ASSERT_RETURN(max_size == 0 || max_size == -1, PJ_EINVAL);3355 3356 /* Don't support encoding type at present */3357 PJ_ASSERT_RETURN(enc_type == 0, PJ_EINVAL);3358 3359 PJ_LOG(4,(THIS_FILE, "Creating recorder %.*s..",3360 (int)filename->slen, filename->ptr));3361 pj_log_push_indent();3362 3363 if (pjsua_var.rec_cnt >= PJ_ARRAY_SIZE(pjsua_var.recorder)) {3364 pj_log_pop_indent();3365 return PJ_ETOOMANY;3366 }3367 3368 /* Determine the file format */3369 ext.ptr = filename->ptr + filename->slen - 4;3370 ext.slen = 4;3371 3372 if (pj_stricmp2(&ext, ".wav") == 0)3373 file_format = FMT_WAV;3374 else if (pj_stricmp2(&ext, ".mp3") == 0)3375 file_format = FMT_MP3;3376 else {3377 PJ_LOG(1,(THIS_FILE, "pjsua_recorder_create() error: unable to "3378 "determine file format for %.*s",3379 (int)filename->slen, filename->ptr));3380 pj_log_pop_indent();3381 return PJ_ENOTSUP;3382 }3383 3384 PJSUA_LOCK();3385 3386 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.recorder); ++file_id) {3387 if (pjsua_var.recorder[file_id].port == NULL)3388 break;3389 }3390 3391 if (file_id == PJ_ARRAY_SIZE(pjsua_var.recorder)) {3392 /* This is unexpected */3393 pj_assert(0);3394 status = PJ_EBUG;3395 goto on_return;3396 }3397 3398 pj_memcpy(path, filename->ptr, filename->slen);3399 path[filename->slen] = '\0';3400 3401 pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);3402 if (!pool) {3403 status = PJ_ENOMEM;3404 goto on_return;3405 }3406 3407 if (file_format == FMT_WAV) {3408 status = pjmedia_wav_writer_port_create(pool, path,3409 pjsua_var.media_cfg.clock_rate,3410 pjsua_var.mconf_cfg.channel_count,3411 pjsua_var.mconf_cfg.samples_per_frame,3412 pjsua_var.mconf_cfg.bits_per_sample,3413 options, 0, &port);3414 } else {3415 PJ_UNUSED_ARG(enc_param);3416 port = NULL;3417 status = PJ_ENOTSUP;3418 }3419 3420 if (status != PJ_SUCCESS) {3421 pjsua_perror(THIS_FILE, "Unable to open file for recording", status);3422 goto on_return;3423 }3424 3425 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,3426 port, filename, &slot);3427 if (status != PJ_SUCCESS) {3428 pjmedia_port_destroy(port);3429 goto on_return;3430 }3431 3432 pjsua_var.recorder[file_id].port = port;3433 pjsua_var.recorder[file_id].slot = slot;3434 pjsua_var.recorder[file_id].pool = pool;3435 3436 if (p_id) *p_id = file_id;3437 3438 ++pjsua_var.rec_cnt;3439 3440 PJSUA_UNLOCK();3441 3442 PJ_LOG(4,(THIS_FILE, "Recorder created, id=%d, slot=%d", file_id, slot));3443 3444 pj_log_pop_indent();3445 return PJ_SUCCESS;3446 3447 on_return:3448 PJSUA_UNLOCK();3449 if (pool) pj_pool_release(pool);3450 pj_log_pop_indent();3451 return status;3452 }3453 3454 3455 /*3456 * Get conference port associated with recorder.3457 */3458 PJ_DEF(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id)3459 {3460 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),3461 PJ_EINVAL);3462 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);3463 3464 return pjsua_var.recorder[id].slot;3465 }3466 3467 /*3468 * Get the media port for the recorder.3469 */3470 PJ_DEF(pj_status_t) pjsua_recorder_get_port( pjsua_recorder_id id,3471 pjmedia_port **p_port)3472 {3473 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),3474 PJ_EINVAL);3475 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);3476 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);3477 3478 *p_port = pjsua_var.recorder[id].port;3479 return PJ_SUCCESS;3480 }3481 3482 /*3483 * Destroy recorder (this will complete recording).3484 */3485 PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id)3486 {3487 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),3488 PJ_EINVAL);3489 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);3490 3491 PJ_LOG(4,(THIS_FILE, "Destroying recorder %d..", id));3492 pj_log_push_indent();3493 3494 PJSUA_LOCK();3495 3496 if (pjsua_var.recorder[id].port) {3497 pjsua_conf_remove_port(pjsua_var.recorder[id].slot);3498 pjmedia_port_destroy(pjsua_var.recorder[id].port);3499 pjsua_var.recorder[id].port = NULL;3500 pjsua_var.recorder[id].slot = 0xFFFF;3501 pj_pool_release(pjsua_var.recorder[id].pool);3502 pjsua_var.recorder[id].pool = NULL;3503 pjsua_var.rec_cnt--;3504 }3505 3506 PJSUA_UNLOCK();3507 pj_log_pop_indent();3508 3509 return PJ_SUCCESS;3510 }3511 3512 3513 /*****************************************************************************3514 * Sound devices.3515 */3516 3517 /*3518 * Enum sound devices.3519 */3520 3521 PJ_DEF(pj_status_t) pjsua_enum_aud_devs( pjmedia_aud_dev_info info[],3522 unsigned *count)3523 {3524 unsigned i, dev_count;3525 3526 dev_count = pjmedia_aud_dev_count();3527 3528 if (dev_count > *count) dev_count = *count;3529 3530 for (i=0; i<dev_count; ++i) {3531 pj_status_t status;3532 3533 status = pjmedia_aud_dev_get_info(i, &info[i]);3534 if (status != PJ_SUCCESS)3535 return status;3536 }3537 3538 *count = dev_count;3539 3540 return PJ_SUCCESS;3541 }3542 3543 3544 PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[],3545 unsigned *count)3546 {3547 unsigned i, dev_count;3548 3549 dev_count = pjmedia_aud_dev_count();3550 3551 if (dev_count > *count) dev_count = *count;3552 pj_bzero(info, dev_count * sizeof(pjmedia_snd_dev_info));3553 3554 for (i=0; i<dev_count; ++i) {3555 pjmedia_aud_dev_info ai;3556 pj_status_t status;3557 3558 status = pjmedia_aud_dev_get_info(i, &ai);3559 if (status != PJ_SUCCESS)3560 return status;3561 3562 strncpy(info[i].name, ai.name, sizeof(info[i].name));3563 info[i].name[sizeof(info[i].name)-1] = '\0';3564 info[i].input_count = ai.input_count;3565 info[i].output_count = ai.output_count;3566 info[i].default_samples_per_sec = ai.default_samples_per_sec;3567 }3568 3569 *count = dev_count;3570 3571 return PJ_SUCCESS;3572 }3573 3574 /* Create audio device parameter to open the device */3575 static pj_status_t create_aud_param(pjmedia_aud_param *param,3576 pjmedia_aud_dev_index capture_dev,3577 pjmedia_aud_dev_index playback_dev,3578 unsigned clock_rate,3579 unsigned channel_count,3580 unsigned samples_per_frame,3581 unsigned bits_per_sample)3582 {3583 pj_status_t status;3584 3585 /* Normalize device ID with new convention about default device ID */3586 if (playback_dev == PJMEDIA_AUD_DEFAULT_CAPTURE_DEV)3587 playback_dev = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV;3588 3589 /* Create default parameters for the device */3590 status = pjmedia_aud_dev_default_param(capture_dev, param);3591 if (status != PJ_SUCCESS) {3592 pjsua_perror(THIS_FILE, "Error retrieving default audio "3593 "device parameters", status);3594 return status;3595 }3596 param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;3597 param->rec_id = capture_dev;3598 param->play_id = playback_dev;3599 param->clock_rate = clock_rate;3600 param->channel_count = channel_count;3601 param->samples_per_frame = samples_per_frame;3602 param->bits_per_sample = bits_per_sample;3603 3604 /* Update the setting with user preference */3605 #define update_param(cap, field) \3606 if (pjsua_var.aud_param.flags & cap) { \3607 param->flags |= cap; \3608 param->field = pjsua_var.aud_param.field; \3609 }3610 update_param( PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);3611 update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);3612 update_param( PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);3613 update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);3614 #undef update_param3615 3616 /* Latency settings */3617 param->flags |= (PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |3618 PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY);3619 param->input_latency_ms = pjsua_var.media_cfg.snd_rec_latency;3620 param->output_latency_ms = pjsua_var.media_cfg.snd_play_latency;3621 3622 /* EC settings */3623 if (pjsua_var.media_cfg.ec_tail_len) {3624 param->flags |= (PJMEDIA_AUD_DEV_CAP_EC | PJMEDIA_AUD_DEV_CAP_EC_TAIL);3625 param->ec_enabled = PJ_TRUE;3626 param->ec_tail_ms = pjsua_var.media_cfg.ec_tail_len;3627 } else {3628 param->flags &= ~(PJMEDIA_AUD_DEV_CAP_EC|PJMEDIA_AUD_DEV_CAP_EC_TAIL);3629 }3630 3631 return PJ_SUCCESS;3632 }3633 3634 /* Internal: the first time the audio device is opened (during app3635 * startup), retrieve the audio settings such as volume level3636 * so that aud_get_settings() will work.3637 */3638 static pj_status_t update_initial_aud_param()3639 {3640 pjmedia_aud_stream *strm;3641 pjmedia_aud_param param;3642 pj_status_t status;3643 3644 PJ_ASSERT_RETURN(pjsua_var.snd_port != NULL, PJ_EBUG);3645 3646 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);3647 3648 status = pjmedia_aud_stream_get_param(strm, ¶m);3649 if (status != PJ_SUCCESS) {3650 pjsua_perror(THIS_FILE, "Error audio stream "3651 "device parameters", status);3652 return status;3653 }3654 3655 #define update_saved_param(cap, field) \3656 if (param.flags & cap) { \3657 pjsua_var.aud_param.flags |= cap; \3658 pjsua_var.aud_param.field = param.field; \3659 }3660 3661 update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);3662 update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);3663 update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);3664 update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);3665 #undef update_saved_param3666 3667 return PJ_SUCCESS;3668 }3669 3670 /* Get format name */3671 static const char *get_fmt_name(pj_uint32_t id)3672 {3673 static char name[8];3674 3675 if (id == PJMEDIA_FORMAT_L16)3676 return "PCM";3677 pj_memcpy(name, &id, 4);3678 name[4] = '\0';3679 return name;3680 }3681 3682 /* Open sound device with the setting. */3683 static pj_status_t open_snd_dev(pjmedia_snd_port_param *param)3684 {3685 pjmedia_port *conf_port;3686 pj_status_t status;3687 3688 PJ_ASSERT_RETURN(param, PJ_EINVAL);3689 3690 /* Check if NULL sound device is used */3691 if (NULL_SND_DEV_ID==param->base.rec_id ||3692 NULL_SND_DEV_ID==param->base.play_id)3693 {3694 return pjsua_set_null_snd_dev();3695 }3696 3697 /* Close existing sound port */3698 close_snd_dev();3699 3700 /* Notify app */3701 if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {3702 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);3703 }3704 3705 /* Create memory pool for sound device. */3706 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);3707 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);3708 3709 3710 PJ_LOG(4,(THIS_FILE, "Opening sound device %s@%d/%d/%dms",3711 get_fmt_name(param->base.ext_fmt.id),3712 param->base.clock_rate, param->base.channel_count,3713 param->base.samples_per_frame / param->base.channel_count *3714 1000 / param->base.clock_rate));3715 pj_log_push_indent();3716 3717 status = pjmedia_snd_port_create2( pjsua_var.snd_pool,3718 param, &pjsua_var.snd_port);3719 if (status != PJ_SUCCESS)3720 goto on_error;3721 3722 /* Get the port0 of the conference bridge. */3723 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);3724 pj_assert(conf_port != NULL);3725 3726 /* For conference bridge, resample if necessary if the bridge's3727 * clock rate is different than the sound device's clock rate.3728 */3729 if (!pjsua_var.is_mswitch &&3730 param->base.ext_fmt.id == PJMEDIA_FORMAT_PCM &&3731 PJMEDIA_PIA_SRATE(&conf_port->info) != param->base.clock_rate)3732 {3733 pjmedia_port *resample_port;3734 unsigned resample_opt = 0;3735 3736 if (pjsua_var.media_cfg.quality >= 3 &&3737 pjsua_var.media_cfg.quality <= 4)3738 {3739 resample_opt |= PJMEDIA_RESAMPLE_USE_SMALL_FILTER;3740 }3741 else if (pjsua_var.media_cfg.quality < 3) {3742 resample_opt |= PJMEDIA_RESAMPLE_USE_LINEAR;3743 }3744 3745 status = pjmedia_resample_port_create(pjsua_var.snd_pool,3746 conf_port,3747 param->base.clock_rate,3748 resample_opt,3749 &resample_port);3750 if (status != PJ_SUCCESS) {3751 char errmsg[PJ_ERR_MSG_SIZE];3752 pj_strerror(status, errmsg, sizeof(errmsg));3753 PJ_LOG(4, (THIS_FILE,3754 "Error creating resample port: %s",3755 errmsg));3756 close_snd_dev();3757 goto on_error;3758 }3759 3760 conf_port = resample_port;3761 }3762 3763 /* Otherwise for audio switchboard, the switch's port0 setting is3764 * derived from the sound device setting, so update the setting.3765 */3766 if (pjsua_var.is_mswitch) {3767 if (param->base.flags & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT) {3768 conf_port->info.fmt = param->base.ext_fmt;3769 } else {3770 unsigned bps, ptime_usec;3771 bps = param->base.clock_rate * param->base.bits_per_sample;3772 ptime_usec = param->base.samples_per_frame /3773 param->base.channel_count * 1000000 /3774 param->base.clock_rate;3775 pjmedia_format_init_audio(&conf_port->info.fmt,3776 PJMEDIA_FORMAT_PCM,3777 param->base.clock_rate,3778 param->base.channel_count,3779 param->base.bits_per_sample,3780 ptime_usec,3781 bps, bps);3782 }3783 }3784 3785 3786 /* Connect sound port to the bridge */3787 status = pjmedia_snd_port_connect(pjsua_var.snd_port,3788 conf_port );3789 if (status != PJ_SUCCESS) {3790 pjsua_perror(THIS_FILE, "Unable to connect conference port to "3791 "sound device", status);3792 pjmedia_snd_port_destroy(pjsua_var.snd_port);3793 pjsua_var.snd_port = NULL;3794 goto on_error;3795 }3796 3797 /* Save the device IDs */3798 pjsua_var.cap_dev = param->base.rec_id;3799 pjsua_var.play_dev = param->base.play_id;3800 3801 /* Update sound device name. */3802 {3803 pjmedia_aud_dev_info rec_info;3804 pjmedia_aud_stream *strm;3805 pjmedia_aud_param si;3806 pj_str_t tmp;3807 3808 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);3809 status = pjmedia_aud_stream_get_param(strm, &si);3810 if (status == PJ_SUCCESS)3811 status = pjmedia_aud_dev_get_info(si.rec_id, &rec_info);3812 3813 if (status==PJ_SUCCESS) {3814 if (param->base.clock_rate != pjsua_var.media_cfg.clock_rate) {3815 char tmp_buf[128];3816 int tmp_buf_len = sizeof(tmp_buf);3817 3818 tmp_buf_len = pj_ansi_snprintf(tmp_buf, sizeof(tmp_buf)-1,3819 "%s (%dKHz)",3820 rec_info.name,3821 param->base.clock_rate/1000);3822 pj_strset(&tmp, tmp_buf, tmp_buf_len);3823 pjmedia_conf_set_port0_name(pjsua_var.mconf, &tmp);3824 } else {3825 pjmedia_conf_set_port0_name(pjsua_var.mconf,3826 pj_cstr(&tmp, rec_info.name));3827 }3828 }3829 3830 /* Any error is not major, let it through */3831 status = PJ_SUCCESS;3832 }3833 3834 /* If this is the first time the audio device is open, retrieve some3835 * settings from the device (such as volume settings) so that the3836 * pjsua_snd_get_setting() work.3837 */3838 if (pjsua_var.aud_open_cnt == 0) {3839 update_initial_aud_param();3840 ++pjsua_var.aud_open_cnt;3841 }3842 3843 pjsua_var.snd_is_on = PJ_TRUE;3844 3845 pj_log_pop_indent();3846 return PJ_SUCCESS;3847 3848 on_error:3849 pj_log_pop_indent();3850 return status;3851 }3852 3853 3854 /* Close existing sound device */3855 static void close_snd_dev(void)3856 {3857 pj_log_push_indent();3858 3859 /* Notify app */3860 if (pjsua_var.snd_is_on && pjsua_var.ua_cfg.cb.on_snd_dev_operation) {3861 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(0);3862 }3863 3864 /* Close sound device */3865 if (pjsua_var.snd_port) {3866 pjmedia_aud_dev_info cap_info, play_info;3867 pjmedia_aud_stream *strm;3868 pjmedia_aud_param param;3869 3870 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);3871 pjmedia_aud_stream_get_param(strm, ¶m);3872 3873 if (pjmedia_aud_dev_get_info(param.rec_id, &cap_info) != PJ_SUCCESS)3874 cap_info.name[0] = '\0';3875 if (pjmedia_aud_dev_get_info(param.play_id, &play_info) != PJ_SUCCESS)3876 play_info.name[0] = '\0';3877 3878 PJ_LOG(4,(THIS_FILE, "Closing %s sound playback device and "3879 "%s sound capture device",3880 play_info.name, cap_info.name));3881 3882 pjmedia_snd_port_disconnect(pjsua_var.snd_port);3883 pjmedia_snd_port_destroy(pjsua_var.snd_port);3884 pjsua_var.snd_port = NULL;3885 }3886 3887 /* Close null sound device */3888 if (pjsua_var.null_snd) {3889 PJ_LOG(4,(THIS_FILE, "Closing null sound device.."));3890 pjmedia_master_port_destroy(pjsua_var.null_snd, PJ_FALSE);3891 pjsua_var.null_snd = NULL;3892 }3893 3894 if (pjsua_var.snd_pool)3895 pj_pool_release(pjsua_var.snd_pool);3896 pjsua_var.snd_pool = NULL;3897 pjsua_var.snd_is_on = PJ_FALSE;3898 3899 pj_log_pop_indent();3900 }3901 3902 3903 /*3904 * Select or change sound device. Application may call this function at3905 * any time to replace current sound device.3906 */3907 PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,3908 int playback_dev)3909 {3910 unsigned alt_cr_cnt = 1;3911 unsigned alt_cr[] = {0, 44100, 48000, 32000, 16000, 8000};3912 unsigned i;3913 pj_status_t status = -1;3914 3915 PJ_LOG(4,(THIS_FILE, "Set sound device: capture=%d, playback=%d",3916 capture_dev, playback_dev));3917 pj_log_push_indent();3918 3919 /* Null-sound */3920 if (capture_dev==NULL_SND_DEV_ID && playback_dev==NULL_SND_DEV_ID) {3921 status = pjsua_set_null_snd_dev();3922 pj_log_pop_indent();3923 return status;3924 }3925 3926 /* Set default clock rate */3927 alt_cr[0] = pjsua_var.media_cfg.snd_clock_rate;3928 if (alt_cr[0] == 0)3929 alt_cr[0] = pjsua_var.media_cfg.clock_rate;3930 3931 /* Allow retrying of different clock rate if we're using conference3932 * bridge (meaning audio format is always PCM), otherwise lock on3933 * to one clock rate.3934 */3935 if (pjsua_var.is_mswitch) {3936 alt_cr_cnt = 1;3937 } else {3938 alt_cr_cnt = PJ_ARRAY_SIZE(alt_cr);3939 }3940 3941 /* Attempts to open the sound device with different clock rates */3942 for (i=0; i<alt_cr_cnt; ++i) {3943 pjmedia_snd_port_param param;3944 unsigned samples_per_frame;3945 3946 /* Create the default audio param */3947 samples_per_frame = alt_cr[i] *3948 pjsua_var.media_cfg.audio_frame_ptime *3949 pjsua_var.media_cfg.channel_count / 1000;3950 status = create_aud_param(¶m.base, capture_dev, playback_dev,3951 alt_cr[i], pjsua_var.media_cfg.channel_count,3952 samples_per_frame, 16);3953 if (status != PJ_SUCCESS)3954 goto on_error;3955 3956 /* Open! */3957 param.options = 0;3958 status = open_snd_dev(¶m);3959 if (status == PJ_SUCCESS)3960 break;3961 }3962 3963 if (status != PJ_SUCCESS) {3964 pjsua_perror(THIS_FILE, "Unable to open sound device", status);3965 goto on_error;3966 }3967 3968 pjsua_var.no_snd = PJ_FALSE;3969 pjsua_var.snd_is_on = PJ_TRUE;3970 3971 pj_log_pop_indent();3972 return PJ_SUCCESS;3973 3974 on_error:3975 pj_log_pop_indent();3976 return status;3977 }3978 3979 3980 /*3981 * Get currently active sound devices. If sound devices has not been created3982 * (for example when pjsua_start() is not called), it is possible that3983 * the function returns PJ_SUCCESS with -1 as device IDs.3984 */3985 PJ_DEF(pj_status_t) pjsua_get_snd_dev(int *capture_dev,3986 int *playback_dev)3987 {3988 if (capture_dev) {3989 *capture_dev = pjsua_var.cap_dev;3990 }3991 if (playback_dev) {3992 *playback_dev = pjsua_var.play_dev;3993 }3994 3995 return PJ_SUCCESS;3996 }3997 3998 3999 /*4000 * Use null sound device.4001 */4002 PJ_DEF(pj_status_t) pjsua_set_null_snd_dev(void)4003 {4004 pjmedia_port *conf_port;4005 pj_status_t status;4006 4007 PJ_LOG(4,(THIS_FILE, "Setting null sound device.."));4008 pj_log_push_indent();4009 4010 4011 /* Close existing sound device */4012 close_snd_dev();4013 4014 /* Notify app */4015 if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {4016 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);4017 }4018 4019 /* Create memory pool for sound device. */4020 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);4021 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);4022 4023 PJ_LOG(4,(THIS_FILE, "Opening null sound device.."));4024 4025 /* Get the port0 of the conference bridge. */4026 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);4027 pj_assert(conf_port != NULL);4028 4029 /* Create master port, connecting port0 of the conference bridge to4030 * a null port.4031 */4032 status = pjmedia_master_port_create(pjsua_var.snd_pool, pjsua_var.null_port,4033 conf_port, 0, &pjsua_var.null_snd);4034 if (status != PJ_SUCCESS) {4035 pjsua_perror(THIS_FILE, "Unable to create null sound device",4036 status);4037 pj_log_pop_indent();4038 return status;4039 }4040 4041 /* Start the master port */4042 status = pjmedia_master_port_start(pjsua_var.null_snd);4043 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);4044 4045 pjsua_var.cap_dev = NULL_SND_DEV_ID;4046 pjsua_var.play_dev = NULL_SND_DEV_ID;4047 4048 pjsua_var.no_snd = PJ_FALSE;4049 pjsua_var.snd_is_on = PJ_TRUE;4050 4051 pj_log_pop_indent();4052 return PJ_SUCCESS;4053 }4054 4055 4056 4057 /*4058 * Use no device!4059 */4060 PJ_DEF(pjmedia_port*) pjsua_set_no_snd_dev(void)4061 {4062 /* Close existing sound device */4063 close_snd_dev();4064 4065 pjsua_var.no_snd = PJ_TRUE;4066 return pjmedia_conf_get_master_port(pjsua_var.mconf);4067 }4068 4069 4070 /*4071 * Configure the AEC settings of the sound port.4072 */4073 PJ_DEF(pj_status_t) pjsua_set_ec(unsigned tail_ms, unsigned options)4074 {4075 pjsua_var.media_cfg.ec_tail_len = tail_ms;4076 4077 if (pjsua_var.snd_port)4078 return pjmedia_snd_port_set_ec( pjsua_var.snd_port, pjsua_var.pool,4079 tail_ms, options);4080 4081 return PJ_SUCCESS;4082 }4083 4084 4085 /*4086 * Get current AEC tail length.4087 */4088 PJ_DEF(pj_status_t) pjsua_get_ec_tail(unsigned *p_tail_ms)4089 {4090 *p_tail_ms = pjsua_var.media_cfg.ec_tail_len;4091 return PJ_SUCCESS;4092 }4093 4094 4095 /*4096 * Check whether the sound device is currently active.4097 */4098 PJ_DEF(pj_bool_t) pjsua_snd_is_active(void)4099 {4100 return pjsua_var.snd_port != NULL;4101 }4102 4103 4104 /*4105 * Configure sound device setting to the sound device being used.4106 */4107 PJ_DEF(pj_status_t) pjsua_snd_set_setting( pjmedia_aud_dev_cap cap,4108 const void *pval,4109 pj_bool_t keep)4110 {4111 pj_status_t status;4112 4113 /* Check if we are allowed to set the cap */4114 if ((cap & pjsua_var.aud_svmask) == 0) {4115 return PJMEDIA_EAUD_INVCAP;4116 }4117 4118 /* If sound is active, set it immediately */4119 if (pjsua_snd_is_active()) {4120 pjmedia_aud_stream *strm;4121 4122 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);4123 status = pjmedia_aud_stream_set_cap(strm, cap, pval);4124 } else {4125 status = PJ_SUCCESS;4126 }4127 4128 if (status != PJ_SUCCESS)4129 return status;4130 4131 /* Save in internal param for later device open */4132 if (keep) {4133 status = pjmedia_aud_param_set_cap(&pjsua_var.aud_param,4134 cap, pval);4135 }4136 4137 return status;4138 }4139 4140 /*4141 * Retrieve a sound device setting.4142 */4143 PJ_DEF(pj_status_t) pjsua_snd_get_setting( pjmedia_aud_dev_cap cap,4144 void *pval)4145 {4146 /* If sound device has never been opened before, open it to4147 * retrieve the initial setting from the device (e.g. audio4148 * volume)4149 */4150 if (pjsua_var.aud_open_cnt==0) {4151 PJ_LOG(4,(THIS_FILE, "Opening sound device to get initial settings"));4152 pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);4153 close_snd_dev();4154 }4155 4156 if (pjsua_snd_is_active()) {4157 /* Sound is active, retrieve from device directly */4158 pjmedia_aud_stream *strm;4159 4160 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);4161 return pjmedia_aud_stream_get_cap(strm, cap, pval);4162 } else {4163 /* Otherwise retrieve from internal param */4164 return pjmedia_aud_param_get_cap(&pjsua_var.aud_param,4165 cap, pval);4166 }4167 }4168 4169 4170 2438 /***************************************************************************** 4171 2439 * Codecs.
Note: See TracChangeset
for help on using the changeset viewer.