blob: a801f756ebdeac67a68d573898a076e3ec6d2332 [file] [log] [blame]
Alexandre Lision8af73cb2013-12-10 14:11:20 -05001/* $Id: pjsua_aud.c 4613 2013-10-08 09:08:13Z bennylp $ */
Tristan Matthews0a329cc2013-07-17 13:20:14 -04002/*
3 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
4 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20#include <pjsua-lib/pjsua.h>
21#include <pjsua-lib/pjsua_internal.h>
22
23#if defined(PJSUA_MEDIA_HAS_PJMEDIA) && PJSUA_MEDIA_HAS_PJMEDIA != 0
24
25#define THIS_FILE "pjsua_aud.c"
26#define NULL_SND_DEV_ID -99
27
28/*****************************************************************************
29 *
30 * Prototypes
31 */
32/* Open sound dev */
33static pj_status_t open_snd_dev(pjmedia_snd_port_param *param);
34/* Close existing sound device */
35static void close_snd_dev(void);
36/* Create audio device param */
37static pj_status_t create_aud_param(pjmedia_aud_param *param,
38 pjmedia_aud_dev_index capture_dev,
39 pjmedia_aud_dev_index playback_dev,
40 unsigned clock_rate,
41 unsigned channel_count,
42 unsigned samples_per_frame,
43 unsigned bits_per_sample);
44
45/*****************************************************************************
46 *
47 * Call API that are closely tied to PJMEDIA
48 */
49/*
50 * Check if call has an active media session.
51 */
52PJ_DEF(pj_bool_t) pjsua_call_has_media(pjsua_call_id call_id)
53{
54 pjsua_call *call = &pjsua_var.calls[call_id];
55 PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
56 PJ_EINVAL);
57 return call->audio_idx >= 0 && call->media[call->audio_idx].strm.a.stream;
58}
59
60
61/*
62 * Get the conference port identification associated with the call.
63 */
64PJ_DEF(pjsua_conf_port_id) pjsua_call_get_conf_port(pjsua_call_id call_id)
65{
66 pjsua_call *call;
67 pjsua_conf_port_id port_id = PJSUA_INVALID_ID;
68
69 PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
70 PJ_EINVAL);
71
72 /* Use PJSUA_LOCK() instead of acquire_call():
73 * https://trac.pjsip.org/repos/ticket/1371
74 */
75 PJSUA_LOCK();
76
77 if (!pjsua_call_is_active(call_id))
78 goto on_return;
79
80 call = &pjsua_var.calls[call_id];
81 port_id = call->media[call->audio_idx].strm.a.conf_slot;
82
83on_return:
84 PJSUA_UNLOCK();
85
86 return port_id;
87}
88
89
90/*
91 * Get media stream info for the specified media index.
92 */
93PJ_DEF(pj_status_t) pjsua_call_get_stream_info( pjsua_call_id call_id,
94 unsigned med_idx,
95 pjsua_stream_info *psi)
96{
97 pjsua_call *call;
98 pjsua_call_media *call_med;
99 pj_status_t status;
100
101 PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
102 PJ_EINVAL);
103 PJ_ASSERT_RETURN(psi, PJ_EINVAL);
104
105 PJSUA_LOCK();
106
107 call = &pjsua_var.calls[call_id];
108
109 if (med_idx >= call->med_cnt) {
110 PJSUA_UNLOCK();
111 return PJ_EINVAL;
112 }
113
114 call_med = &call->media[med_idx];
115 psi->type = call_med->type;
116 switch (call_med->type) {
117 case PJMEDIA_TYPE_AUDIO:
118 status = pjmedia_stream_get_info(call_med->strm.a.stream,
119 &psi->info.aud);
120 break;
121#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
122 case PJMEDIA_TYPE_VIDEO:
123 status = pjmedia_vid_stream_get_info(call_med->strm.v.stream,
124 &psi->info.vid);
125 break;
126#endif
127 default:
128 status = PJMEDIA_EINVALIMEDIATYPE;
129 break;
130 }
131
132 PJSUA_UNLOCK();
133 return status;
134}
135
136
137/*
138 * Get media stream statistic for the specified media index.
139 */
140PJ_DEF(pj_status_t) pjsua_call_get_stream_stat( pjsua_call_id call_id,
141 unsigned med_idx,
142 pjsua_stream_stat *stat)
143{
144 pjsua_call *call;
145 pjsua_call_media *call_med;
146 pj_status_t status;
147
148 PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
149 PJ_EINVAL);
150 PJ_ASSERT_RETURN(stat, PJ_EINVAL);
151
152 PJSUA_LOCK();
153
154 call = &pjsua_var.calls[call_id];
155
156 if (med_idx >= call->med_cnt) {
157 PJSUA_UNLOCK();
158 return PJ_EINVAL;
159 }
160
161 call_med = &call->media[med_idx];
162 switch (call_med->type) {
163 case PJMEDIA_TYPE_AUDIO:
164 status = pjmedia_stream_get_stat(call_med->strm.a.stream,
165 &stat->rtcp);
166 if (status == PJ_SUCCESS)
167 status = pjmedia_stream_get_stat_jbuf(call_med->strm.a.stream,
168 &stat->jbuf);
169 break;
170#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
171 case PJMEDIA_TYPE_VIDEO:
172 status = pjmedia_vid_stream_get_stat(call_med->strm.v.stream,
173 &stat->rtcp);
174 if (status == PJ_SUCCESS)
175 status = pjmedia_vid_stream_get_stat_jbuf(call_med->strm.v.stream,
176 &stat->jbuf);
177 break;
178#endif
179 default:
180 status = PJMEDIA_EINVALIMEDIATYPE;
181 break;
182 }
183
184 PJSUA_UNLOCK();
185 return status;
186}
187
188/*
189 * Send DTMF digits to remote using RFC 2833 payload formats.
190 */
191PJ_DEF(pj_status_t) pjsua_call_dial_dtmf( pjsua_call_id call_id,
192 const pj_str_t *digits)
193{
194 pjsua_call *call;
195 pjsip_dialog *dlg = NULL;
196 pj_status_t status;
197
198 PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
199 PJ_EINVAL);
200
201 PJ_LOG(4,(THIS_FILE, "Call %d dialing DTMF %.*s",
202 call_id, (int)digits->slen, digits->ptr));
203 pj_log_push_indent();
204
205 status = acquire_call("pjsua_call_dial_dtmf()", call_id, &call, &dlg);
206 if (status != PJ_SUCCESS)
207 goto on_return;
208
209 if (!pjsua_call_has_media(call_id)) {
210 PJ_LOG(3,(THIS_FILE, "Media is not established yet!"));
211 status = PJ_EINVALIDOP;
212 goto on_return;
213 }
214
215 status = pjmedia_stream_dial_dtmf(
216 call->media[call->audio_idx].strm.a.stream, digits);
217
218on_return:
219 if (dlg) pjsip_dlg_dec_lock(dlg);
220 pj_log_pop_indent();
221 return status;
222}
223
224
225/*****************************************************************************
226 *
227 * Audio media with PJMEDIA backend
228 */
229
230/* Init pjmedia audio subsystem */
231pj_status_t pjsua_aud_subsys_init()
232{
233 pj_str_t codec_id = {NULL, 0};
234 unsigned opt;
235 pjmedia_audio_codec_config codec_cfg;
236 pj_status_t status;
237
238 /* To suppress warning about unused var when all codecs are disabled */
239 PJ_UNUSED_ARG(codec_id);
240
241 /*
242 * Register all codecs
243 */
244 pjmedia_audio_codec_config_default(&codec_cfg);
245 codec_cfg.speex.quality = pjsua_var.media_cfg.quality;
246 codec_cfg.speex.complexity = -1;
247 codec_cfg.ilbc.mode = pjsua_var.media_cfg.ilbc_mode;
248
249#if PJMEDIA_HAS_PASSTHROUGH_CODECS
250 /* Register passthrough codecs */
251 {
252 unsigned aud_idx;
253 unsigned ext_fmt_cnt = 0;
254 pjmedia_format ext_fmts[32];
255
256 /* List extended formats supported by audio devices */
257 for (aud_idx = 0; aud_idx < pjmedia_aud_dev_count(); ++aud_idx) {
258 pjmedia_aud_dev_info aud_info;
259 unsigned i;
260
261 status = pjmedia_aud_dev_get_info(aud_idx, &aud_info);
262 if (status != PJ_SUCCESS) {
263 pjsua_perror(THIS_FILE, "Error querying audio device info",
264 status);
265 goto on_error;
266 }
267
268 /* Collect extended formats supported by this audio device */
269 for (i = 0; i < aud_info.ext_fmt_cnt; ++i) {
270 unsigned j;
271 pj_bool_t is_listed = PJ_FALSE;
272
273 /* See if this extended format is already in the list */
274 for (j = 0; j < ext_fmt_cnt && !is_listed; ++j) {
275 if (ext_fmts[j].id == aud_info.ext_fmt[i].id &&
276 ext_fmts[j].det.aud.avg_bps ==
277 aud_info.ext_fmt[i].det.aud.avg_bps)
278 {
279 is_listed = PJ_TRUE;
280 }
281 }
282
283 /* Put this format into the list, if it is not in the list */
284 if (!is_listed)
285 ext_fmts[ext_fmt_cnt++] = aud_info.ext_fmt[i];
286
287 pj_assert(ext_fmt_cnt <= PJ_ARRAY_SIZE(ext_fmts));
288 }
289 }
290
291 /* Init the passthrough codec with supported formats only */
292 codec_cfg.passthrough.setting.fmt_cnt = ext_fmt_cnt;
293 codec_cfg.passthrough.setting.fmts = ext_fmts;
294 codec_cfg.passthrough.setting.ilbc_mode =
295 pjsua_var.media_cfg.ilbc_mode;
296 }
297#endif /* PJMEDIA_HAS_PASSTHROUGH_CODECS */
298
299 /* Register all codecs */
300 status = pjmedia_codec_register_audio_codecs(pjsua_var.med_endpt,
301 &codec_cfg);
302 if (status != PJ_SUCCESS) {
303 PJ_PERROR(1,(THIS_FILE, status, "Error registering codecs"));
304 goto on_error;
305 }
306
307 /* Set speex/16000 to higher priority*/
308 codec_id = pj_str("speex/16000");
309 pjmedia_codec_mgr_set_codec_priority(
310 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
311 &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+2);
312
313 /* Set speex/8000 to next higher priority*/
314 codec_id = pj_str("speex/8000");
315 pjmedia_codec_mgr_set_codec_priority(
316 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
317 &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+1);
318
319 /* Disable ALL L16 codecs */
320 codec_id = pj_str("L16");
321 pjmedia_codec_mgr_set_codec_priority(
322 pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
323 &codec_id, PJMEDIA_CODEC_PRIO_DISABLED);
324
325
326 /* Save additional conference bridge parameters for future
327 * reference.
328 */
329 pjsua_var.mconf_cfg.channel_count = pjsua_var.media_cfg.channel_count;
330 pjsua_var.mconf_cfg.bits_per_sample = 16;
331 pjsua_var.mconf_cfg.samples_per_frame = pjsua_var.media_cfg.clock_rate *
332 pjsua_var.mconf_cfg.channel_count *
333 pjsua_var.media_cfg.audio_frame_ptime /
334 1000;
335
336 /* Init options for conference bridge. */
337 opt = PJMEDIA_CONF_NO_DEVICE;
338 if (pjsua_var.media_cfg.quality >= 3 &&
339 pjsua_var.media_cfg.quality <= 4)
340 {
341 opt |= PJMEDIA_CONF_SMALL_FILTER;
342 }
343 else if (pjsua_var.media_cfg.quality < 3) {
344 opt |= PJMEDIA_CONF_USE_LINEAR;
345 }
346
347 /* Init conference bridge. */
348 status = pjmedia_conf_create(pjsua_var.pool,
349 pjsua_var.media_cfg.max_media_ports,
350 pjsua_var.media_cfg.clock_rate,
351 pjsua_var.mconf_cfg.channel_count,
352 pjsua_var.mconf_cfg.samples_per_frame,
353 pjsua_var.mconf_cfg.bits_per_sample,
354 opt, &pjsua_var.mconf);
355 if (status != PJ_SUCCESS) {
356 pjsua_perror(THIS_FILE, "Error creating conference bridge",
357 status);
358 goto on_error;
359 }
360
361 /* Are we using the audio switchboard (a.k.a APS-Direct)? */
362 pjsua_var.is_mswitch = pjmedia_conf_get_master_port(pjsua_var.mconf)
363 ->info.signature == PJMEDIA_CONF_SWITCH_SIGNATURE;
364
365 /* Create null port just in case user wants to use null sound. */
366 status = pjmedia_null_port_create(pjsua_var.pool,
367 pjsua_var.media_cfg.clock_rate,
368 pjsua_var.mconf_cfg.channel_count,
369 pjsua_var.mconf_cfg.samples_per_frame,
370 pjsua_var.mconf_cfg.bits_per_sample,
371 &pjsua_var.null_port);
372 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
373
374 return status;
375
376on_error:
377 return status;
378}
379
380/* Check if sound device is idle. */
381void pjsua_check_snd_dev_idle()
382{
383 unsigned call_cnt;
384
385 /* Check if the sound device auto-close feature is disabled. */
386 if (pjsua_var.media_cfg.snd_auto_close_time < 0)
387 return;
388
389 /* Check if the sound device is currently closed. */
390 if (!pjsua_var.snd_is_on)
391 return;
392
393 /* Get the call count, we shouldn't close the sound device when there is
394 * any calls active.
395 */
396 call_cnt = pjsua_call_get_count();
397
398 /* When this function is called from pjsua_media_channel_deinit() upon
399 * disconnecting call, actually the call count hasn't been updated/
400 * decreased. So we put additional check here, if there is only one
401 * call and it's in DISCONNECTED state, there is actually no active
402 * call.
403 */
404 if (call_cnt == 1) {
405 pjsua_call_id call_id;
406 pj_status_t status;
407
408 status = pjsua_enum_calls(&call_id, &call_cnt);
409 if (status == PJ_SUCCESS && call_cnt > 0 &&
410 !pjsua_call_is_active(call_id))
411 {
412 call_cnt = 0;
413 }
414 }
415
416 /* Activate sound device auto-close timer if sound device is idle.
417 * It is idle when there is no port connection in the bridge and
418 * there is no active call.
419 */
420 if (pjsua_var.snd_idle_timer.id == PJ_FALSE &&
421 call_cnt == 0 &&
422 pjmedia_conf_get_connect_count(pjsua_var.mconf) == 0)
423 {
424 pj_time_val delay;
425
426 delay.msec = 0;
427 delay.sec = pjsua_var.media_cfg.snd_auto_close_time;
428
429 pjsua_var.snd_idle_timer.id = PJ_TRUE;
430 pjsip_endpt_schedule_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer,
431 &delay);
432 }
433}
434
435/* Timer callback to close sound device */
436static void close_snd_timer_cb( pj_timer_heap_t *th,
437 pj_timer_entry *entry)
438{
439 PJ_UNUSED_ARG(th);
440
441 PJSUA_LOCK();
442 if (entry->id) {
443 PJ_LOG(4,(THIS_FILE,"Closing sound device after idle for %d second(s)",
444 pjsua_var.media_cfg.snd_auto_close_time));
445
446 entry->id = PJ_FALSE;
447
448 close_snd_dev();
449 }
450 PJSUA_UNLOCK();
451}
452
453pj_status_t pjsua_aud_subsys_start(void)
454{
455 pj_status_t status = PJ_SUCCESS;
456
457 pj_timer_entry_init(&pjsua_var.snd_idle_timer, PJ_FALSE, NULL,
458 &close_snd_timer_cb);
459
460 pjsua_check_snd_dev_idle();
461 return status;
462}
463
464pj_status_t pjsua_aud_subsys_destroy()
465{
466 unsigned i;
467
468 close_snd_dev();
469
470 if (pjsua_var.mconf) {
471 pjmedia_conf_destroy(pjsua_var.mconf);
472 pjsua_var.mconf = NULL;
473 }
474
475 if (pjsua_var.null_port) {
476 pjmedia_port_destroy(pjsua_var.null_port);
477 pjsua_var.null_port = NULL;
478 }
479
480 /* Destroy file players */
481 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.player); ++i) {
482 if (pjsua_var.player[i].port) {
483 pjmedia_port_destroy(pjsua_var.player[i].port);
484 pjsua_var.player[i].port = NULL;
485 }
486 }
487
488 /* Destroy file recorders */
489 for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.recorder); ++i) {
490 if (pjsua_var.recorder[i].port) {
491 pjmedia_port_destroy(pjsua_var.recorder[i].port);
492 pjsua_var.recorder[i].port = NULL;
493 }
494 }
495
496 return PJ_SUCCESS;
497}
498
499void pjsua_aud_stop_stream(pjsua_call_media *call_med)
500{
501 pjmedia_stream *strm = call_med->strm.a.stream;
502 pjmedia_rtcp_stat stat;
503
504 if (strm) {
505 pjmedia_stream_send_rtcp_bye(strm);
506
507 if (call_med->strm.a.conf_slot != PJSUA_INVALID_ID) {
508 if (pjsua_var.mconf) {
509 pjsua_conf_remove_port(call_med->strm.a.conf_slot);
510 }
511 call_med->strm.a.conf_slot = PJSUA_INVALID_ID;
512 }
513
514 if ((call_med->dir & PJMEDIA_DIR_ENCODING) &&
515 (pjmedia_stream_get_stat(strm, &stat) == PJ_SUCCESS))
516 {
517 /* Save RTP timestamp & sequence, so when media session is
518 * restarted, those values will be restored as the initial
519 * RTP timestamp & sequence of the new media session. So in
520 * the same call session, RTP timestamp and sequence are
521 * guaranteed to be contigue.
522 */
523 call_med->rtp_tx_seq_ts_set = 1 | (1 << 1);
524 call_med->rtp_tx_seq = stat.rtp_tx_last_seq;
525 call_med->rtp_tx_ts = stat.rtp_tx_last_ts;
526 }
527
528 if (pjsua_var.ua_cfg.cb.on_stream_destroyed) {
529 pjsua_var.ua_cfg.cb.on_stream_destroyed(call_med->call->index,
530 strm, call_med->idx);
531 }
532
533 pjmedia_stream_destroy(strm);
534 call_med->strm.a.stream = NULL;
535 }
536
537 pjsua_check_snd_dev_idle();
538}
539
540/*
541 * DTMF callback from the stream.
542 */
543static void dtmf_callback(pjmedia_stream *strm, void *user_data,
544 int digit)
545{
546 PJ_UNUSED_ARG(strm);
547
548 pj_log_push_indent();
549
550 /* For discussions about call mutex protection related to this
551 * callback, please see ticket #460:
552 * http://trac.pjsip.org/repos/ticket/460#comment:4
553 */
554 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
555 pjsua_call_id call_id;
556
557 call_id = (pjsua_call_id)(pj_ssize_t)user_data;
558 pjsua_var.ua_cfg.cb.on_dtmf_digit(call_id, digit);
559 }
560
561 pj_log_pop_indent();
562}
563
564/* Internal function: update audio channel after SDP negotiation.
565 * Warning: do not use temporary/flip-flop pool, e.g: inv->pool_prov,
566 * for creating stream, etc, as after SDP negotiation and when
567 * the SDP media is not changed, the stream should remain running
568 * while the temporary/flip-flop pool may be released.
569 */
570pj_status_t pjsua_aud_channel_update(pjsua_call_media *call_med,
571 pj_pool_t *tmp_pool,
572 pjmedia_stream_info *si,
573 const pjmedia_sdp_session *local_sdp,
574 const pjmedia_sdp_session *remote_sdp)
575{
576 pjsua_call *call = call_med->call;
577 pjmedia_port *media_port;
578 unsigned strm_idx = call_med->idx;
579 pj_status_t status = PJ_SUCCESS;
580
581 PJ_UNUSED_ARG(tmp_pool);
582 PJ_UNUSED_ARG(local_sdp);
583 PJ_UNUSED_ARG(remote_sdp);
584
585 PJ_LOG(4,(THIS_FILE,"Audio channel update.."));
586 pj_log_push_indent();
587
588 si->rtcp_sdes_bye_disabled = pjsua_var.media_cfg.no_rtcp_sdes_bye;
589
590 /* Check if no media is active */
591 if (si->dir != PJMEDIA_DIR_NONE) {
592
593 /* Optionally, application may modify other stream settings here
594 * (such as jitter buffer parameters, codec ptime, etc.)
595 */
596 si->jb_init = pjsua_var.media_cfg.jb_init;
597 si->jb_min_pre = pjsua_var.media_cfg.jb_min_pre;
598 si->jb_max_pre = pjsua_var.media_cfg.jb_max_pre;
599 si->jb_max = pjsua_var.media_cfg.jb_max;
600
601 /* Set SSRC */
602 si->ssrc = call_med->ssrc;
603
604 /* Set RTP timestamp & sequence, normally these value are intialized
605 * automatically when stream session created, but for some cases (e.g:
606 * call reinvite, call update) timestamp and sequence need to be kept
607 * contigue.
608 */
609 si->rtp_ts = call_med->rtp_tx_ts;
610 si->rtp_seq = call_med->rtp_tx_seq;
611 si->rtp_seq_ts_set = call_med->rtp_tx_seq_ts_set;
612
613#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0
614 /* Enable/disable stream keep-alive and NAT hole punch. */
615 si->use_ka = pjsua_var.acc[call->acc_id].cfg.use_stream_ka;
616#endif
617
618 /* Create session based on session info. */
619 status = pjmedia_stream_create(pjsua_var.med_endpt, NULL, si,
620 call_med->tp, NULL,
621 &call_med->strm.a.stream);
622 if (status != PJ_SUCCESS) {
623 goto on_return;
624 }
625
626 /* Start stream */
627 status = pjmedia_stream_start(call_med->strm.a.stream);
628 if (status != PJ_SUCCESS) {
629 goto on_return;
630 }
631
632 if (call_med->prev_state == PJSUA_CALL_MEDIA_NONE)
633 pjmedia_stream_send_rtcp_sdes(call_med->strm.a.stream);
634
635 /* If DTMF callback is installed by application, install our
636 * callback to the session.
637 */
638 if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
639 pjmedia_stream_set_dtmf_callback(call_med->strm.a.stream,
640 &dtmf_callback,
641 (void*)(pj_ssize_t)(call->index));
642 }
643
644 /* Get the port interface of the first stream in the session.
645 * We need the port interface to add to the conference bridge.
646 */
647 pjmedia_stream_get_port(call_med->strm.a.stream, &media_port);
648
649 /* Notify application about stream creation.
650 * Note: application may modify media_port to point to different
651 * media port
652 */
653 if (pjsua_var.ua_cfg.cb.on_stream_created) {
654 pjsua_var.ua_cfg.cb.on_stream_created(call->index,
655 call_med->strm.a.stream,
656 strm_idx, &media_port);
657 }
658
659 /*
660 * Add the call to conference bridge.
661 */
662 {
663 char tmp[PJSIP_MAX_URL_SIZE];
664 pj_str_t port_name;
665
666 port_name.ptr = tmp;
667 port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
668 call->inv->dlg->remote.info->uri,
669 tmp, sizeof(tmp));
670 if (port_name.slen < 1) {
671 port_name = pj_str("call");
672 }
673 status = pjmedia_conf_add_port( pjsua_var.mconf,
674 call->inv->pool,
675 media_port,
676 &port_name,
677 (unsigned*)
678 &call_med->strm.a.conf_slot);
679 if (status != PJ_SUCCESS) {
680 goto on_return;
681 }
682 }
683 }
684
685on_return:
686 pj_log_pop_indent();
687 return status;
688}
689
690
691/*
692 * Get maxinum number of conference ports.
693 */
694PJ_DEF(unsigned) pjsua_conf_get_max_ports(void)
695{
696 return pjsua_var.media_cfg.max_media_ports;
697}
698
699
700/*
701 * Get current number of active ports in the bridge.
702 */
703PJ_DEF(unsigned) pjsua_conf_get_active_ports(void)
704{
705 unsigned ports[PJSUA_MAX_CONF_PORTS];
706 unsigned count = PJ_ARRAY_SIZE(ports);
707 pj_status_t status;
708
709 status = pjmedia_conf_enum_ports(pjsua_var.mconf, ports, &count);
710 if (status != PJ_SUCCESS)
711 count = 0;
712
713 return count;
714}
715
716
717/*
718 * Enumerate all conference ports.
719 */
720PJ_DEF(pj_status_t) pjsua_enum_conf_ports(pjsua_conf_port_id id[],
721 unsigned *count)
722{
723 return pjmedia_conf_enum_ports(pjsua_var.mconf, (unsigned*)id, count);
724}
725
726
727/*
728 * Get information about the specified conference port
729 */
730PJ_DEF(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id,
731 pjsua_conf_port_info *info)
732{
733 pjmedia_conf_port_info cinfo;
734 unsigned i;
735 pj_status_t status;
736
737 status = pjmedia_conf_get_port_info( pjsua_var.mconf, id, &cinfo);
738 if (status != PJ_SUCCESS)
739 return status;
740
741 pj_bzero(info, sizeof(*info));
742 info->slot_id = id;
743 info->name = cinfo.name;
744 info->clock_rate = cinfo.clock_rate;
745 info->channel_count = cinfo.channel_count;
746 info->samples_per_frame = cinfo.samples_per_frame;
747 info->bits_per_sample = cinfo.bits_per_sample;
748
749 /* Build array of listeners */
750 info->listener_cnt = cinfo.listener_cnt;
751 for (i=0; i<cinfo.listener_cnt; ++i) {
752 info->listeners[i] = cinfo.listener_slots[i];
753 }
754
755 return PJ_SUCCESS;
756}
757
758
759/*
760 * Add arbitrary media port to PJSUA's conference bridge.
761 */
762PJ_DEF(pj_status_t) pjsua_conf_add_port( pj_pool_t *pool,
763 pjmedia_port *port,
764 pjsua_conf_port_id *p_id)
765{
766 pj_status_t status;
767
768 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
769 port, NULL, (unsigned*)p_id);
770 if (status != PJ_SUCCESS) {
771 if (p_id)
772 *p_id = PJSUA_INVALID_ID;
773 }
774
775 return status;
776}
777
778
779/*
780 * Remove arbitrary slot from the conference bridge.
781 */
782PJ_DEF(pj_status_t) pjsua_conf_remove_port(pjsua_conf_port_id id)
783{
784 pj_status_t status;
785
786 status = pjmedia_conf_remove_port(pjsua_var.mconf, (unsigned)id);
787 pjsua_check_snd_dev_idle();
788
789 return status;
790}
791
792
793/*
794 * Establish unidirectional media flow from souce to sink.
795 */
796PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source,
797 pjsua_conf_port_id sink)
798{
799 pj_status_t status = PJ_SUCCESS;
800
801 PJ_LOG(4,(THIS_FILE, "%s connect: %d --> %d",
802 (pjsua_var.is_mswitch ? "Switch" : "Conf"),
803 source, sink));
804 pj_log_push_indent();
805
806 PJSUA_LOCK();
807
808 /* If sound device idle timer is active, cancel it first. */
809 if (pjsua_var.snd_idle_timer.id) {
810 pjsip_endpt_cancel_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer);
811 pjsua_var.snd_idle_timer.id = PJ_FALSE;
812 }
813
814
815 /* For audio switchboard (i.e. APS-Direct):
816 * Check if sound device need to be reopened, i.e: its attributes
817 * (format, clock rate, channel count) must match to peer's.
818 * Note that sound device can be reopened only if it doesn't have
819 * any connection.
820 */
821 if (pjsua_var.is_mswitch) {
822 pjmedia_conf_port_info port0_info;
823 pjmedia_conf_port_info peer_info;
824 unsigned peer_id;
825 pj_bool_t need_reopen = PJ_FALSE;
826
827 peer_id = (source!=0)? source : sink;
828 status = pjmedia_conf_get_port_info(pjsua_var.mconf, peer_id,
829 &peer_info);
830 pj_assert(status == PJ_SUCCESS);
831
832 status = pjmedia_conf_get_port_info(pjsua_var.mconf, 0, &port0_info);
833 pj_assert(status == PJ_SUCCESS);
834
835 /* Check if sound device is instantiated. */
836 need_reopen = (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
837 !pjsua_var.no_snd);
838
839 /* Check if sound device need to reopen because it needs to modify
840 * settings to match its peer. Sound device must be idle in this case
841 * though.
842 */
843 if (!need_reopen &&
844 port0_info.listener_cnt==0 && port0_info.transmitter_cnt==0)
845 {
846 need_reopen = (peer_info.format.id != port0_info.format.id ||
847 peer_info.format.det.aud.avg_bps !=
848 port0_info.format.det.aud.avg_bps ||
849 peer_info.clock_rate != port0_info.clock_rate ||
850 peer_info.channel_count!=port0_info.channel_count);
851 }
852
853 if (need_reopen) {
854 if (pjsua_var.cap_dev != NULL_SND_DEV_ID) {
855 pjmedia_snd_port_param param;
856
857 pjmedia_snd_port_param_default(&param);
858 param.ec_options = pjsua_var.media_cfg.ec_options;
859
860 /* Create parameter based on peer info */
861 status = create_aud_param(&param.base, pjsua_var.cap_dev,
862 pjsua_var.play_dev,
863 peer_info.clock_rate,
864 peer_info.channel_count,
865 peer_info.samples_per_frame,
866 peer_info.bits_per_sample);
867 if (status != PJ_SUCCESS) {
868 pjsua_perror(THIS_FILE, "Error opening sound device",
869 status);
870 goto on_return;
871 }
872
873 /* And peer format */
874 if (peer_info.format.id != PJMEDIA_FORMAT_PCM) {
875 param.base.flags |= PJMEDIA_AUD_DEV_CAP_EXT_FORMAT;
876 param.base.ext_fmt = peer_info.format;
877 }
878
879 param.options = 0;
880 status = open_snd_dev(&param);
881 if (status != PJ_SUCCESS) {
882 pjsua_perror(THIS_FILE, "Error opening sound device",
883 status);
884 goto on_return;
885 }
886 } else {
887 /* Null-audio */
888 status = pjsua_set_snd_dev(pjsua_var.cap_dev,
889 pjsua_var.play_dev);
890 if (status != PJ_SUCCESS) {
891 pjsua_perror(THIS_FILE, "Error opening sound device",
892 status);
893 goto on_return;
894 }
895 }
896 } else if (pjsua_var.no_snd) {
897 if (!pjsua_var.snd_is_on) {
898 pjsua_var.snd_is_on = PJ_TRUE;
899 /* Notify app */
900 if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
901 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
902 }
903 }
904 }
905
906 } else {
907 /* The bridge version */
908
909 /* Create sound port if none is instantiated */
910 if (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
911 !pjsua_var.no_snd)
912 {
913 status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
914 if (status != PJ_SUCCESS) {
915 pjsua_perror(THIS_FILE, "Error opening sound device", status);
916 goto on_return;
917 }
918 } else if (pjsua_var.no_snd && !pjsua_var.snd_is_on) {
919 pjsua_var.snd_is_on = PJ_TRUE;
920 /* Notify app */
921 if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
922 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
923 }
924 }
925 }
926
927on_return:
928 PJSUA_UNLOCK();
929
930 if (status == PJ_SUCCESS) {
931 status = pjmedia_conf_connect_port(pjsua_var.mconf, source, sink, 0);
932 }
933
934 pj_log_pop_indent();
935 return status;
936}
937
938
939/*
940 * Disconnect media flow from the source to destination port.
941 */
942PJ_DEF(pj_status_t) pjsua_conf_disconnect( pjsua_conf_port_id source,
943 pjsua_conf_port_id sink)
944{
945 pj_status_t status;
946
947 PJ_LOG(4,(THIS_FILE, "%s disconnect: %d -x- %d",
948 (pjsua_var.is_mswitch ? "Switch" : "Conf"),
949 source, sink));
950 pj_log_push_indent();
951
952 status = pjmedia_conf_disconnect_port(pjsua_var.mconf, source, sink);
953 pjsua_check_snd_dev_idle();
954
955 pj_log_pop_indent();
956 return status;
957}
958
959
960/*
961 * Adjust the signal level to be transmitted from the bridge to the
962 * specified port by making it louder or quieter.
963 */
964PJ_DEF(pj_status_t) pjsua_conf_adjust_tx_level(pjsua_conf_port_id slot,
965 float level)
966{
967 return pjmedia_conf_adjust_tx_level(pjsua_var.mconf, slot,
968 (int)((level-1) * 128));
969}
970
971/*
972 * Adjust the signal level to be received from the specified port (to
973 * the bridge) by making it louder or quieter.
974 */
975PJ_DEF(pj_status_t) pjsua_conf_adjust_rx_level(pjsua_conf_port_id slot,
976 float level)
977{
978 return pjmedia_conf_adjust_rx_level(pjsua_var.mconf, slot,
979 (int)((level-1) * 128));
980}
981
982
983/*
984 * Get last signal level transmitted to or received from the specified port.
985 */
986PJ_DEF(pj_status_t) pjsua_conf_get_signal_level(pjsua_conf_port_id slot,
987 unsigned *tx_level,
988 unsigned *rx_level)
989{
990 return pjmedia_conf_get_signal_level(pjsua_var.mconf, slot,
991 tx_level, rx_level);
992}
993
994/*****************************************************************************
995 * File player.
996 */
997
998static char* get_basename(const char *path, unsigned len)
999{
1000 char *p = ((char*)path) + len;
1001
1002 if (len==0)
1003 return p;
1004
1005 for (--p; p!=path && *p!='/' && *p!='\\'; ) --p;
1006
1007 return (p==path) ? p : p+1;
1008}
1009
1010
1011/*
1012 * Create a file player, and automatically connect this player to
1013 * the conference bridge.
1014 */
1015PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename,
1016 unsigned options,
1017 pjsua_player_id *p_id)
1018{
1019 unsigned slot, file_id;
1020 char path[PJ_MAXPATH];
1021 pj_pool_t *pool = NULL;
1022 pjmedia_port *port;
1023 pj_status_t status = PJ_SUCCESS;
1024
1025 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
1026 return PJ_ETOOMANY;
1027
1028 PJ_LOG(4,(THIS_FILE, "Creating file player: %.*s..",
1029 (int)filename->slen, filename->ptr));
1030 pj_log_push_indent();
1031
1032 PJSUA_LOCK();
1033
1034 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
1035 if (pjsua_var.player[file_id].port == NULL)
1036 break;
1037 }
1038
1039 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
1040 /* This is unexpected */
1041 pj_assert(0);
1042 status = PJ_EBUG;
1043 goto on_error;
1044 }
1045
1046 pj_memcpy(path, filename->ptr, filename->slen);
1047 path[filename->slen] = '\0';
1048
1049 pool = pjsua_pool_create(get_basename(path, (unsigned)filename->slen), 1000,
1050 1000);
1051 if (!pool) {
1052 status = PJ_ENOMEM;
1053 goto on_error;
1054 }
1055
1056 status = pjmedia_wav_player_port_create(
1057 pool, path,
1058 pjsua_var.mconf_cfg.samples_per_frame *
1059 1000 / pjsua_var.media_cfg.channel_count /
1060 pjsua_var.media_cfg.clock_rate,
1061 options, 0, &port);
1062 if (status != PJ_SUCCESS) {
1063 pjsua_perror(THIS_FILE, "Unable to open file for playback", status);
1064 goto on_error;
1065 }
1066
1067 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
1068 port, filename, &slot);
1069 if (status != PJ_SUCCESS) {
1070 pjmedia_port_destroy(port);
1071 pjsua_perror(THIS_FILE, "Unable to add file to conference bridge",
1072 status);
1073 goto on_error;
1074 }
1075
1076 pjsua_var.player[file_id].type = 0;
1077 pjsua_var.player[file_id].pool = pool;
1078 pjsua_var.player[file_id].port = port;
1079 pjsua_var.player[file_id].slot = slot;
1080
1081 if (p_id) *p_id = file_id;
1082
1083 ++pjsua_var.player_cnt;
1084
1085 PJSUA_UNLOCK();
1086
1087 PJ_LOG(4,(THIS_FILE, "Player created, id=%d, slot=%d", file_id, slot));
1088
1089 pj_log_pop_indent();
1090 return PJ_SUCCESS;
1091
1092on_error:
1093 PJSUA_UNLOCK();
1094 if (pool) pj_pool_release(pool);
1095 pj_log_pop_indent();
1096 return status;
1097}
1098
1099
1100/*
1101 * Create a file playlist media port, and automatically add the port
1102 * to the conference bridge.
1103 */
1104PJ_DEF(pj_status_t) pjsua_playlist_create( const pj_str_t file_names[],
1105 unsigned file_count,
1106 const pj_str_t *label,
1107 unsigned options,
1108 pjsua_player_id *p_id)
1109{
1110 unsigned slot, file_id, ptime;
1111 pj_pool_t *pool = NULL;
1112 pjmedia_port *port;
1113 pj_status_t status = PJ_SUCCESS;
1114
1115 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
1116 return PJ_ETOOMANY;
1117
1118 PJ_LOG(4,(THIS_FILE, "Creating playlist with %d file(s)..", file_count));
1119 pj_log_push_indent();
1120
1121 PJSUA_LOCK();
1122
1123 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
1124 if (pjsua_var.player[file_id].port == NULL)
1125 break;
1126 }
1127
1128 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
1129 /* This is unexpected */
1130 pj_assert(0);
1131 status = PJ_EBUG;
1132 goto on_error;
1133 }
1134
1135
1136 ptime = pjsua_var.mconf_cfg.samples_per_frame * 1000 /
1137 pjsua_var.media_cfg.clock_rate;
1138
1139 pool = pjsua_pool_create("playlist", 1000, 1000);
1140 if (!pool) {
1141 status = PJ_ENOMEM;
1142 goto on_error;
1143 }
1144
1145 status = pjmedia_wav_playlist_create(pool, label,
1146 file_names, file_count,
1147 ptime, options, 0, &port);
1148 if (status != PJ_SUCCESS) {
1149 pjsua_perror(THIS_FILE, "Unable to create playlist", status);
1150 goto on_error;
1151 }
1152
1153 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
1154 port, &port->info.name, &slot);
1155 if (status != PJ_SUCCESS) {
1156 pjmedia_port_destroy(port);
1157 pjsua_perror(THIS_FILE, "Unable to add port", status);
1158 goto on_error;
1159 }
1160
1161 pjsua_var.player[file_id].type = 1;
1162 pjsua_var.player[file_id].pool = pool;
1163 pjsua_var.player[file_id].port = port;
1164 pjsua_var.player[file_id].slot = slot;
1165
1166 if (p_id) *p_id = file_id;
1167
1168 ++pjsua_var.player_cnt;
1169
1170 PJSUA_UNLOCK();
1171
1172 PJ_LOG(4,(THIS_FILE, "Playlist created, id=%d, slot=%d", file_id, slot));
1173
1174 pj_log_pop_indent();
1175
1176 return PJ_SUCCESS;
1177
1178on_error:
1179 PJSUA_UNLOCK();
1180 if (pool) pj_pool_release(pool);
1181 pj_log_pop_indent();
1182
1183 return status;
1184}
1185
1186
1187/*
1188 * Get conference port ID associated with player.
1189 */
1190PJ_DEF(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id)
1191{
1192 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
1193 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
1194
1195 return pjsua_var.player[id].slot;
1196}
1197
1198/*
1199 * Get the media port for the player.
1200 */
1201PJ_DEF(pj_status_t) pjsua_player_get_port( pjsua_player_id id,
1202 pjmedia_port **p_port)
1203{
1204 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
1205 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
1206 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
1207
1208 *p_port = pjsua_var.player[id].port;
1209
1210 return PJ_SUCCESS;
1211}
1212
1213/*
1214 * Set playback position.
1215 */
1216PJ_DEF(pj_status_t) pjsua_player_set_pos( pjsua_player_id id,
1217 pj_uint32_t samples)
1218{
1219 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
1220 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
1221 PJ_ASSERT_RETURN(pjsua_var.player[id].type == 0, PJ_EINVAL);
1222
1223 return pjmedia_wav_player_port_set_pos(pjsua_var.player[id].port, samples);
1224}
1225
1226
1227/*
1228 * Close the file, remove the player from the bridge, and free
1229 * resources associated with the file player.
1230 */
1231PJ_DEF(pj_status_t) pjsua_player_destroy(pjsua_player_id id)
1232{
1233 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
1234 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
1235
1236 PJ_LOG(4,(THIS_FILE, "Destroying player %d..", id));
1237 pj_log_push_indent();
1238
1239 PJSUA_LOCK();
1240
1241 if (pjsua_var.player[id].port) {
1242 pjsua_conf_remove_port(pjsua_var.player[id].slot);
1243 pjmedia_port_destroy(pjsua_var.player[id].port);
1244 pjsua_var.player[id].port = NULL;
1245 pjsua_var.player[id].slot = 0xFFFF;
1246 pj_pool_release(pjsua_var.player[id].pool);
1247 pjsua_var.player[id].pool = NULL;
1248 pjsua_var.player_cnt--;
1249 }
1250
1251 PJSUA_UNLOCK();
1252 pj_log_pop_indent();
1253
1254 return PJ_SUCCESS;
1255}
1256
1257
1258/*****************************************************************************
1259 * File recorder.
1260 */
1261
1262/*
1263 * Create a file recorder, and automatically connect this recorder to
1264 * the conference bridge.
1265 */
1266PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename,
1267 unsigned enc_type,
1268 void *enc_param,
1269 pj_ssize_t max_size,
1270 unsigned options,
1271 pjsua_recorder_id *p_id)
1272{
1273 enum Format
1274 {
1275 FMT_UNKNOWN,
1276 FMT_WAV,
1277 FMT_MP3,
1278 };
1279 unsigned slot, file_id;
1280 char path[PJ_MAXPATH];
1281 pj_str_t ext;
1282 int file_format;
1283 pj_pool_t *pool = NULL;
1284 pjmedia_port *port;
1285 pj_status_t status = PJ_SUCCESS;
1286
1287 /* Filename must present */
1288 PJ_ASSERT_RETURN(filename != NULL, PJ_EINVAL);
1289
1290 /* Don't support max_size at present */
1291 PJ_ASSERT_RETURN(max_size == 0 || max_size == -1, PJ_EINVAL);
1292
1293 /* Don't support encoding type at present */
1294 PJ_ASSERT_RETURN(enc_type == 0, PJ_EINVAL);
1295
1296 PJ_LOG(4,(THIS_FILE, "Creating recorder %.*s..",
1297 (int)filename->slen, filename->ptr));
1298 pj_log_push_indent();
1299
1300 if (pjsua_var.rec_cnt >= PJ_ARRAY_SIZE(pjsua_var.recorder)) {
1301 pj_log_pop_indent();
1302 return PJ_ETOOMANY;
1303 }
1304
1305 /* Determine the file format */
1306 ext.ptr = filename->ptr + filename->slen - 4;
1307 ext.slen = 4;
1308
1309 if (pj_stricmp2(&ext, ".wav") == 0)
1310 file_format = FMT_WAV;
1311 else if (pj_stricmp2(&ext, ".mp3") == 0)
1312 file_format = FMT_MP3;
1313 else {
1314 PJ_LOG(1,(THIS_FILE, "pjsua_recorder_create() error: unable to "
1315 "determine file format for %.*s",
1316 (int)filename->slen, filename->ptr));
1317 pj_log_pop_indent();
1318 return PJ_ENOTSUP;
1319 }
1320
1321 PJSUA_LOCK();
1322
1323 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.recorder); ++file_id) {
1324 if (pjsua_var.recorder[file_id].port == NULL)
1325 break;
1326 }
1327
1328 if (file_id == PJ_ARRAY_SIZE(pjsua_var.recorder)) {
1329 /* This is unexpected */
1330 pj_assert(0);
1331 status = PJ_EBUG;
1332 goto on_return;
1333 }
1334
1335 pj_memcpy(path, filename->ptr, filename->slen);
1336 path[filename->slen] = '\0';
1337
1338 pool = pjsua_pool_create(get_basename(path, (unsigned)filename->slen), 1000,
1339 1000);
1340 if (!pool) {
1341 status = PJ_ENOMEM;
1342 goto on_return;
1343 }
1344
1345 if (file_format == FMT_WAV) {
1346 status = pjmedia_wav_writer_port_create(pool, path,
1347 pjsua_var.media_cfg.clock_rate,
1348 pjsua_var.mconf_cfg.channel_count,
1349 pjsua_var.mconf_cfg.samples_per_frame,
1350 pjsua_var.mconf_cfg.bits_per_sample,
1351 options, 0, &port);
1352 } else {
1353 PJ_UNUSED_ARG(enc_param);
1354 port = NULL;
1355 status = PJ_ENOTSUP;
1356 }
1357
1358 if (status != PJ_SUCCESS) {
1359 pjsua_perror(THIS_FILE, "Unable to open file for recording", status);
1360 goto on_return;
1361 }
1362
1363 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
1364 port, filename, &slot);
1365 if (status != PJ_SUCCESS) {
1366 pjmedia_port_destroy(port);
1367 goto on_return;
1368 }
1369
1370 pjsua_var.recorder[file_id].port = port;
1371 pjsua_var.recorder[file_id].slot = slot;
1372 pjsua_var.recorder[file_id].pool = pool;
1373
1374 if (p_id) *p_id = file_id;
1375
1376 ++pjsua_var.rec_cnt;
1377
1378 PJSUA_UNLOCK();
1379
1380 PJ_LOG(4,(THIS_FILE, "Recorder created, id=%d, slot=%d", file_id, slot));
1381
1382 pj_log_pop_indent();
1383 return PJ_SUCCESS;
1384
1385on_return:
1386 PJSUA_UNLOCK();
1387 if (pool) pj_pool_release(pool);
1388 pj_log_pop_indent();
1389 return status;
1390}
1391
1392
1393/*
1394 * Get conference port associated with recorder.
1395 */
1396PJ_DEF(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id)
1397{
1398 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
1399 PJ_EINVAL);
1400 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
1401
1402 return pjsua_var.recorder[id].slot;
1403}
1404
1405/*
1406 * Get the media port for the recorder.
1407 */
1408PJ_DEF(pj_status_t) pjsua_recorder_get_port( pjsua_recorder_id id,
1409 pjmedia_port **p_port)
1410{
1411 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
1412 PJ_EINVAL);
1413 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
1414 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
1415
1416 *p_port = pjsua_var.recorder[id].port;
1417 return PJ_SUCCESS;
1418}
1419
1420/*
1421 * Destroy recorder (this will complete recording).
1422 */
1423PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id)
1424{
1425 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
1426 PJ_EINVAL);
1427 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
1428
1429 PJ_LOG(4,(THIS_FILE, "Destroying recorder %d..", id));
1430 pj_log_push_indent();
1431
1432 PJSUA_LOCK();
1433
1434 if (pjsua_var.recorder[id].port) {
1435 pjsua_conf_remove_port(pjsua_var.recorder[id].slot);
1436 pjmedia_port_destroy(pjsua_var.recorder[id].port);
1437 pjsua_var.recorder[id].port = NULL;
1438 pjsua_var.recorder[id].slot = 0xFFFF;
1439 pj_pool_release(pjsua_var.recorder[id].pool);
1440 pjsua_var.recorder[id].pool = NULL;
1441 pjsua_var.rec_cnt--;
1442 }
1443
1444 PJSUA_UNLOCK();
1445 pj_log_pop_indent();
1446
1447 return PJ_SUCCESS;
1448}
1449
1450
1451/*****************************************************************************
1452 * Sound devices.
1453 */
1454
1455/*
1456 * Enum sound devices.
1457 */
1458
1459PJ_DEF(pj_status_t) pjsua_enum_aud_devs( pjmedia_aud_dev_info info[],
1460 unsigned *count)
1461{
1462 unsigned i, dev_count;
1463
1464 dev_count = pjmedia_aud_dev_count();
1465
1466 if (dev_count > *count) dev_count = *count;
1467
1468 for (i=0; i<dev_count; ++i) {
1469 pj_status_t status;
1470
1471 status = pjmedia_aud_dev_get_info(i, &info[i]);
1472 if (status != PJ_SUCCESS)
1473 return status;
1474 }
1475
1476 *count = dev_count;
1477
1478 return PJ_SUCCESS;
1479}
1480
1481
1482PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[],
1483 unsigned *count)
1484{
1485 unsigned i, dev_count;
1486
1487 dev_count = pjmedia_aud_dev_count();
1488
1489 if (dev_count > *count) dev_count = *count;
1490 pj_bzero(info, dev_count * sizeof(pjmedia_snd_dev_info));
1491
1492 for (i=0; i<dev_count; ++i) {
1493 pjmedia_aud_dev_info ai;
1494 pj_status_t status;
1495
1496 status = pjmedia_aud_dev_get_info(i, &ai);
1497 if (status != PJ_SUCCESS)
1498 return status;
1499
1500 strncpy(info[i].name, ai.name, sizeof(info[i].name));
1501 info[i].name[sizeof(info[i].name)-1] = '\0';
1502 info[i].input_count = ai.input_count;
1503 info[i].output_count = ai.output_count;
1504 info[i].default_samples_per_sec = ai.default_samples_per_sec;
1505 }
1506
1507 *count = dev_count;
1508
1509 return PJ_SUCCESS;
1510}
1511
1512/* Create audio device parameter to open the device */
1513static pj_status_t create_aud_param(pjmedia_aud_param *param,
1514 pjmedia_aud_dev_index capture_dev,
1515 pjmedia_aud_dev_index playback_dev,
1516 unsigned clock_rate,
1517 unsigned channel_count,
1518 unsigned samples_per_frame,
1519 unsigned bits_per_sample)
1520{
1521 pj_status_t status;
1522
1523 /* Normalize device ID with new convention about default device ID */
1524 if (playback_dev == PJMEDIA_AUD_DEFAULT_CAPTURE_DEV)
1525 playback_dev = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV;
1526
1527 /* Create default parameters for the device */
1528 status = pjmedia_aud_dev_default_param(capture_dev, param);
1529 if (status != PJ_SUCCESS) {
1530 pjsua_perror(THIS_FILE, "Error retrieving default audio "
1531 "device parameters", status);
1532 return status;
1533 }
1534 param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
1535 param->rec_id = capture_dev;
1536 param->play_id = playback_dev;
1537 param->clock_rate = clock_rate;
1538 param->channel_count = channel_count;
1539 param->samples_per_frame = samples_per_frame;
1540 param->bits_per_sample = bits_per_sample;
1541
1542 /* Update the setting with user preference */
1543#define update_param(cap, field) \
1544 if (pjsua_var.aud_param.flags & cap) { \
1545 param->flags |= cap; \
1546 param->field = pjsua_var.aud_param.field; \
1547 }
1548 update_param( PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
1549 update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
1550 update_param( PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
1551 update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
1552#undef update_param
1553
1554 /* Latency settings */
1555 param->flags |= (PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
1556 PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY);
1557 param->input_latency_ms = pjsua_var.media_cfg.snd_rec_latency;
1558 param->output_latency_ms = pjsua_var.media_cfg.snd_play_latency;
1559
1560 /* EC settings */
1561 if (pjsua_var.media_cfg.ec_tail_len) {
1562 param->flags |= (PJMEDIA_AUD_DEV_CAP_EC | PJMEDIA_AUD_DEV_CAP_EC_TAIL);
1563 param->ec_enabled = PJ_TRUE;
1564 param->ec_tail_ms = pjsua_var.media_cfg.ec_tail_len;
1565 } else {
1566 param->flags &= ~(PJMEDIA_AUD_DEV_CAP_EC|PJMEDIA_AUD_DEV_CAP_EC_TAIL);
1567 }
1568
1569 /* VAD settings */
1570 if (pjsua_var.media_cfg.no_vad) {
1571 param->flags &= ~PJMEDIA_AUD_DEV_CAP_VAD;
1572 } else {
1573 param->flags |= PJMEDIA_AUD_DEV_CAP_VAD;
1574 param->vad_enabled = PJ_TRUE;
1575 }
1576
1577 return PJ_SUCCESS;
1578}
1579
1580/* Internal: the first time the audio device is opened (during app
1581 * startup), retrieve the audio settings such as volume level
1582 * so that aud_get_settings() will work.
1583 */
1584static pj_status_t update_initial_aud_param()
1585{
1586 pjmedia_aud_stream *strm;
1587 pjmedia_aud_param param;
1588 pj_status_t status;
1589
1590 PJ_ASSERT_RETURN(pjsua_var.snd_port != NULL, PJ_EBUG);
1591
1592 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
1593
1594 status = pjmedia_aud_stream_get_param(strm, &param);
1595 if (status != PJ_SUCCESS) {
1596 pjsua_perror(THIS_FILE, "Error audio stream "
1597 "device parameters", status);
1598 return status;
1599 }
1600
1601#define update_saved_param(cap, field) \
1602 if (param.flags & cap) { \
1603 pjsua_var.aud_param.flags |= cap; \
1604 pjsua_var.aud_param.field = param.field; \
1605 }
1606
1607 update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
1608 update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
1609 update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
1610 update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
1611#undef update_saved_param
1612
1613 return PJ_SUCCESS;
1614}
1615
1616/* Get format name */
1617static const char *get_fmt_name(pj_uint32_t id)
1618{
1619 static char name[8];
1620
1621 if (id == PJMEDIA_FORMAT_L16)
1622 return "PCM";
1623 pj_memcpy(name, &id, 4);
1624 name[4] = '\0';
1625 return name;
1626}
1627
1628/* Open sound device with the setting. */
1629static pj_status_t open_snd_dev(pjmedia_snd_port_param *param)
1630{
1631 pjmedia_port *conf_port;
1632 pj_status_t status;
1633
1634 PJ_ASSERT_RETURN(param, PJ_EINVAL);
1635
1636 /* Check if NULL sound device is used */
1637 if (NULL_SND_DEV_ID==param->base.rec_id ||
1638 NULL_SND_DEV_ID==param->base.play_id)
1639 {
1640 return pjsua_set_null_snd_dev();
1641 }
1642
1643 /* Close existing sound port */
1644 close_snd_dev();
1645
1646 /* Notify app */
1647 if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
1648 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
1649 }
1650
1651 /* Create memory pool for sound device. */
1652 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
1653 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
1654
1655
1656 PJ_LOG(4,(THIS_FILE, "Opening sound device %s@%d/%d/%dms",
1657 get_fmt_name(param->base.ext_fmt.id),
1658 param->base.clock_rate, param->base.channel_count,
1659 param->base.samples_per_frame / param->base.channel_count *
1660 1000 / param->base.clock_rate));
1661 pj_log_push_indent();
1662
1663 status = pjmedia_snd_port_create2( pjsua_var.snd_pool,
1664 param, &pjsua_var.snd_port);
1665 if (status != PJ_SUCCESS)
1666 goto on_error;
1667
1668 /* Get the port0 of the conference bridge. */
1669 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
1670 pj_assert(conf_port != NULL);
1671
1672 /* For conference bridge, resample if necessary if the bridge's
1673 * clock rate is different than the sound device's clock rate.
1674 */
1675 if (!pjsua_var.is_mswitch &&
1676 param->base.ext_fmt.id == PJMEDIA_FORMAT_PCM &&
1677 PJMEDIA_PIA_SRATE(&conf_port->info) != param->base.clock_rate)
1678 {
1679 pjmedia_port *resample_port;
1680 unsigned resample_opt = 0;
1681
1682 if (pjsua_var.media_cfg.quality >= 3 &&
1683 pjsua_var.media_cfg.quality <= 4)
1684 {
1685 resample_opt |= PJMEDIA_RESAMPLE_USE_SMALL_FILTER;
1686 }
1687 else if (pjsua_var.media_cfg.quality < 3) {
1688 resample_opt |= PJMEDIA_RESAMPLE_USE_LINEAR;
1689 }
1690
1691 status = pjmedia_resample_port_create(pjsua_var.snd_pool,
1692 conf_port,
1693 param->base.clock_rate,
1694 resample_opt,
1695 &resample_port);
1696 if (status != PJ_SUCCESS) {
1697 char errmsg[PJ_ERR_MSG_SIZE];
1698 pj_strerror(status, errmsg, sizeof(errmsg));
1699 PJ_LOG(4, (THIS_FILE,
1700 "Error creating resample port: %s",
1701 errmsg));
1702 close_snd_dev();
1703 goto on_error;
1704 }
1705
1706 conf_port = resample_port;
1707 }
1708
1709 /* Otherwise for audio switchboard, the switch's port0 setting is
1710 * derived from the sound device setting, so update the setting.
1711 */
1712 if (pjsua_var.is_mswitch) {
1713 if (param->base.flags & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT) {
1714 conf_port->info.fmt = param->base.ext_fmt;
1715 } else {
1716 unsigned bps, ptime_usec;
1717 bps = param->base.clock_rate * param->base.bits_per_sample;
1718 ptime_usec = param->base.samples_per_frame /
1719 param->base.channel_count * 1000000 /
1720 param->base.clock_rate;
1721 pjmedia_format_init_audio(&conf_port->info.fmt,
1722 PJMEDIA_FORMAT_PCM,
1723 param->base.clock_rate,
1724 param->base.channel_count,
1725 param->base.bits_per_sample,
1726 ptime_usec,
1727 bps, bps);
1728 }
1729 }
1730
1731
1732 /* Connect sound port to the bridge */
1733 status = pjmedia_snd_port_connect(pjsua_var.snd_port,
1734 conf_port );
1735 if (status != PJ_SUCCESS) {
1736 pjsua_perror(THIS_FILE, "Unable to connect conference port to "
1737 "sound device", status);
1738 pjmedia_snd_port_destroy(pjsua_var.snd_port);
1739 pjsua_var.snd_port = NULL;
1740 goto on_error;
1741 }
1742
1743 /* Save the device IDs */
1744 pjsua_var.cap_dev = param->base.rec_id;
1745 pjsua_var.play_dev = param->base.play_id;
1746
1747 /* Update sound device name. */
1748 {
1749 pjmedia_aud_dev_info rec_info;
1750 pjmedia_aud_stream *strm;
1751 pjmedia_aud_param si;
1752 pj_str_t tmp;
1753
1754 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
1755 status = pjmedia_aud_stream_get_param(strm, &si);
1756 if (status == PJ_SUCCESS)
1757 status = pjmedia_aud_dev_get_info(si.rec_id, &rec_info);
1758
1759 if (status==PJ_SUCCESS) {
1760 if (param->base.clock_rate != pjsua_var.media_cfg.clock_rate) {
1761 char tmp_buf[128];
Alexandre Lision8af73cb2013-12-10 14:11:20 -05001762 int tmp_buf_len;
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001763
Alexandre Lision8af73cb2013-12-10 14:11:20 -05001764 tmp_buf_len = pj_ansi_snprintf(tmp_buf, sizeof(tmp_buf),
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001765 "%s (%dKHz)",
1766 rec_info.name,
1767 param->base.clock_rate/1000);
Alexandre Lision8af73cb2013-12-10 14:11:20 -05001768 if (tmp_buf_len < 1 || tmp_buf_len >= (int)sizeof(tmp_buf))
1769 tmp_buf_len = sizeof(tmp_buf) - 1;
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001770 pj_strset(&tmp, tmp_buf, tmp_buf_len);
1771 pjmedia_conf_set_port0_name(pjsua_var.mconf, &tmp);
1772 } else {
1773 pjmedia_conf_set_port0_name(pjsua_var.mconf,
1774 pj_cstr(&tmp, rec_info.name));
1775 }
1776 }
1777
1778 /* Any error is not major, let it through */
1779 status = PJ_SUCCESS;
1780 }
1781
1782 /* If this is the first time the audio device is open, retrieve some
1783 * settings from the device (such as volume settings) so that the
1784 * pjsua_snd_get_setting() work.
1785 */
1786 if (pjsua_var.aud_open_cnt == 0) {
1787 update_initial_aud_param();
1788 ++pjsua_var.aud_open_cnt;
1789 }
1790
1791 pjsua_var.snd_is_on = PJ_TRUE;
1792
1793 pj_log_pop_indent();
1794 return PJ_SUCCESS;
1795
1796on_error:
1797 pj_log_pop_indent();
1798 return status;
1799}
1800
1801
1802/* Close existing sound device */
1803static void close_snd_dev(void)
1804{
1805 pj_log_push_indent();
1806
1807 /* Notify app */
1808 if (pjsua_var.snd_is_on && pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
1809 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(0);
1810 }
1811
1812 /* Close sound device */
1813 if (pjsua_var.snd_port) {
1814 pjmedia_aud_dev_info cap_info, play_info;
1815 pjmedia_aud_stream *strm;
1816 pjmedia_aud_param param;
1817
1818 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
1819 pjmedia_aud_stream_get_param(strm, &param);
1820
1821 if (pjmedia_aud_dev_get_info(param.rec_id, &cap_info) != PJ_SUCCESS)
1822 cap_info.name[0] = '\0';
1823 if (pjmedia_aud_dev_get_info(param.play_id, &play_info) != PJ_SUCCESS)
1824 play_info.name[0] = '\0';
1825
1826 PJ_LOG(4,(THIS_FILE, "Closing %s sound playback device and "
1827 "%s sound capture device",
1828 play_info.name, cap_info.name));
1829
1830 pjmedia_snd_port_disconnect(pjsua_var.snd_port);
1831 pjmedia_snd_port_destroy(pjsua_var.snd_port);
1832 pjsua_var.snd_port = NULL;
1833 }
1834
1835 /* Close null sound device */
1836 if (pjsua_var.null_snd) {
1837 PJ_LOG(4,(THIS_FILE, "Closing null sound device.."));
1838 pjmedia_master_port_destroy(pjsua_var.null_snd, PJ_FALSE);
1839 pjsua_var.null_snd = NULL;
1840 }
1841
1842 if (pjsua_var.snd_pool)
1843 pj_pool_release(pjsua_var.snd_pool);
1844
1845 pjsua_var.snd_pool = NULL;
1846 pjsua_var.snd_is_on = PJ_FALSE;
1847
1848 pj_log_pop_indent();
1849}
1850
1851
1852/*
1853 * Select or change sound device. Application may call this function at
1854 * any time to replace current sound device.
1855 */
1856PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
1857 int playback_dev)
1858{
1859 unsigned alt_cr_cnt = 1;
1860 unsigned alt_cr[] = {0, 44100, 48000, 32000, 16000, 8000};
1861 unsigned i;
1862 pj_status_t status = -1;
1863
1864 PJ_LOG(4,(THIS_FILE, "Set sound device: capture=%d, playback=%d",
1865 capture_dev, playback_dev));
1866 pj_log_push_indent();
1867
1868 PJSUA_LOCK();
1869
1870 /* Null-sound */
1871 if (capture_dev==NULL_SND_DEV_ID && playback_dev==NULL_SND_DEV_ID) {
1872 PJSUA_UNLOCK();
1873 status = pjsua_set_null_snd_dev();
1874 pj_log_pop_indent();
1875 return status;
1876 }
1877
1878 /* Set default clock rate */
1879 alt_cr[0] = pjsua_var.media_cfg.snd_clock_rate;
1880 if (alt_cr[0] == 0)
1881 alt_cr[0] = pjsua_var.media_cfg.clock_rate;
1882
1883 /* Allow retrying of different clock rate if we're using conference
1884 * bridge (meaning audio format is always PCM), otherwise lock on
1885 * to one clock rate.
1886 */
1887 if (pjsua_var.is_mswitch) {
1888 alt_cr_cnt = 1;
1889 } else {
1890 alt_cr_cnt = PJ_ARRAY_SIZE(alt_cr);
1891 }
1892
1893 /* Attempts to open the sound device with different clock rates */
1894 for (i=0; i<alt_cr_cnt; ++i) {
1895 pjmedia_snd_port_param param;
1896 unsigned samples_per_frame;
1897
1898 /* Create the default audio param */
1899 samples_per_frame = alt_cr[i] *
1900 pjsua_var.media_cfg.audio_frame_ptime *
1901 pjsua_var.media_cfg.channel_count / 1000;
1902 pjmedia_snd_port_param_default(&param);
1903 param.ec_options = pjsua_var.media_cfg.ec_options;
1904 status = create_aud_param(&param.base, capture_dev, playback_dev,
1905 alt_cr[i], pjsua_var.media_cfg.channel_count,
1906 samples_per_frame, 16);
1907 if (status != PJ_SUCCESS)
1908 goto on_error;
1909
1910 /* Open! */
1911 param.options = 0;
1912 status = open_snd_dev(&param);
1913 if (status == PJ_SUCCESS)
1914 break;
1915 }
1916
1917 if (status != PJ_SUCCESS) {
1918 pjsua_perror(THIS_FILE, "Unable to open sound device", status);
1919 goto on_error;
1920 }
1921
1922 pjsua_var.no_snd = PJ_FALSE;
1923 pjsua_var.snd_is_on = PJ_TRUE;
1924
1925 PJSUA_UNLOCK();
1926 pj_log_pop_indent();
1927 return PJ_SUCCESS;
1928
1929on_error:
1930 PJSUA_UNLOCK();
1931 pj_log_pop_indent();
1932 return status;
1933}
1934
1935
1936/*
1937 * Get currently active sound devices. If sound devices has not been created
1938 * (for example when pjsua_start() is not called), it is possible that
1939 * the function returns PJ_SUCCESS with -1 as device IDs.
1940 */
1941PJ_DEF(pj_status_t) pjsua_get_snd_dev(int *capture_dev,
1942 int *playback_dev)
1943{
1944 PJSUA_LOCK();
1945
1946 if (capture_dev) {
1947 *capture_dev = pjsua_var.cap_dev;
1948 }
1949 if (playback_dev) {
1950 *playback_dev = pjsua_var.play_dev;
1951 }
1952
1953 PJSUA_UNLOCK();
1954 return PJ_SUCCESS;
1955}
1956
1957
1958/*
1959 * Use null sound device.
1960 */
1961PJ_DEF(pj_status_t) pjsua_set_null_snd_dev(void)
1962{
1963 pjmedia_port *conf_port;
1964 pj_status_t status;
1965
1966 PJ_LOG(4,(THIS_FILE, "Setting null sound device.."));
1967 pj_log_push_indent();
1968
1969 PJSUA_LOCK();
1970
1971 /* Close existing sound device */
1972 close_snd_dev();
1973
1974 /* Notify app */
1975 if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
1976 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
1977 }
1978
1979 /* Create memory pool for sound device. */
1980 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
1981 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
1982
1983 PJ_LOG(4,(THIS_FILE, "Opening null sound device.."));
1984
1985 /* Get the port0 of the conference bridge. */
1986 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
1987 pj_assert(conf_port != NULL);
1988
1989 /* Create master port, connecting port0 of the conference bridge to
1990 * a null port.
1991 */
1992 status = pjmedia_master_port_create(pjsua_var.snd_pool, pjsua_var.null_port,
1993 conf_port, 0, &pjsua_var.null_snd);
1994 if (status != PJ_SUCCESS) {
1995 pjsua_perror(THIS_FILE, "Unable to create null sound device",
1996 status);
1997 PJSUA_UNLOCK();
1998 pj_log_pop_indent();
1999 return status;
2000 }
2001
2002 /* Start the master port */
2003 status = pjmedia_master_port_start(pjsua_var.null_snd);
2004 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
2005
2006 pjsua_var.cap_dev = NULL_SND_DEV_ID;
2007 pjsua_var.play_dev = NULL_SND_DEV_ID;
2008
2009 pjsua_var.no_snd = PJ_FALSE;
2010 pjsua_var.snd_is_on = PJ_TRUE;
2011
2012 PJSUA_UNLOCK();
2013 pj_log_pop_indent();
2014 return PJ_SUCCESS;
2015}
2016
2017
2018
2019/*
2020 * Use no device!
2021 */
2022PJ_DEF(pjmedia_port*) pjsua_set_no_snd_dev(void)
2023{
2024 PJSUA_LOCK();
2025
2026 /* Close existing sound device */
2027 close_snd_dev();
2028 pjsua_var.no_snd = PJ_TRUE;
2029
2030 PJSUA_UNLOCK();
2031
2032 return pjmedia_conf_get_master_port(pjsua_var.mconf);
2033}
2034
2035
2036/*
2037 * Configure the AEC settings of the sound port.
2038 */
2039PJ_DEF(pj_status_t) pjsua_set_ec(unsigned tail_ms, unsigned options)
2040{
2041 pj_status_t status = PJ_SUCCESS;
2042
2043 PJSUA_LOCK();
2044
2045 pjsua_var.media_cfg.ec_tail_len = tail_ms;
2046 pjsua_var.media_cfg.ec_options = options;
2047
2048 if (pjsua_var.snd_port)
2049 status = pjmedia_snd_port_set_ec(pjsua_var.snd_port, pjsua_var.pool,
2050 tail_ms, options);
2051
2052 PJSUA_UNLOCK();
2053 return status;
2054}
2055
2056
2057/*
2058 * Get current AEC tail length.
2059 */
2060PJ_DEF(pj_status_t) pjsua_get_ec_tail(unsigned *p_tail_ms)
2061{
2062 *p_tail_ms = pjsua_var.media_cfg.ec_tail_len;
2063 return PJ_SUCCESS;
2064}
2065
2066
2067/*
2068 * Check whether the sound device is currently active.
2069 */
2070PJ_DEF(pj_bool_t) pjsua_snd_is_active(void)
2071{
2072 return pjsua_var.snd_port != NULL;
2073}
2074
2075
2076/*
2077 * Configure sound device setting to the sound device being used.
2078 */
2079PJ_DEF(pj_status_t) pjsua_snd_set_setting( pjmedia_aud_dev_cap cap,
2080 const void *pval,
2081 pj_bool_t keep)
2082{
2083 pj_status_t status;
2084
2085 /* Check if we are allowed to set the cap */
2086 if ((cap & pjsua_var.aud_svmask) == 0) {
2087 return PJMEDIA_EAUD_INVCAP;
2088 }
2089
2090 PJSUA_LOCK();
2091
2092 /* If sound is active, set it immediately */
2093 if (pjsua_snd_is_active()) {
2094 pjmedia_aud_stream *strm;
2095
2096 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
2097 status = pjmedia_aud_stream_set_cap(strm, cap, pval);
2098 } else {
2099 status = PJ_SUCCESS;
2100 }
2101
2102 if (status != PJ_SUCCESS) {
2103 PJSUA_UNLOCK();
2104 return status;
2105 }
2106
2107 /* Save in internal param for later device open */
2108 if (keep) {
2109 status = pjmedia_aud_param_set_cap(&pjsua_var.aud_param,
2110 cap, pval);
2111 }
2112
2113 PJSUA_UNLOCK();
2114 return status;
2115}
2116
2117/*
2118 * Retrieve a sound device setting.
2119 */
2120PJ_DEF(pj_status_t) pjsua_snd_get_setting( pjmedia_aud_dev_cap cap,
2121 void *pval)
2122{
2123 pj_status_t status;
2124
2125 PJSUA_LOCK();
2126
2127 /* If sound device has never been opened before, open it to
2128 * retrieve the initial setting from the device (e.g. audio
2129 * volume)
2130 */
2131 if (pjsua_var.aud_open_cnt==0) {
2132 PJ_LOG(4,(THIS_FILE, "Opening sound device to get initial settings"));
2133 pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
2134 close_snd_dev();
2135 }
2136
2137 if (pjsua_snd_is_active()) {
2138 /* Sound is active, retrieve from device directly */
2139 pjmedia_aud_stream *strm;
2140
2141 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
2142 status = pjmedia_aud_stream_get_cap(strm, cap, pval);
2143 } else {
2144 /* Otherwise retrieve from internal param */
2145 status = pjmedia_aud_param_get_cap(&pjsua_var.aud_param,
2146 cap, pval);
2147 }
2148
2149 PJSUA_UNLOCK();
2150 return status;
2151}
2152
2153#endif /* PJSUA_MEDIA_HAS_PJMEDIA */