blob: 41d6e94f27d6d39d857af489a2a55aba59aac1f1 [file] [log] [blame]
Alexandre Lision0e143012014-01-22 11:02:46 -05001/* $Id: pjsua_aud.c 4704 2014-01-16 05:30:46Z ming $ */
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;
Alexandre Lision0e143012014-01-22 11:02:46 -0500744 pjmedia_format_copy(&info->format, &cinfo.format);
Tristan Matthews0a329cc2013-07-17 13:20:14 -0400745 info->clock_rate = cinfo.clock_rate;
746 info->channel_count = cinfo.channel_count;
747 info->samples_per_frame = cinfo.samples_per_frame;
748 info->bits_per_sample = cinfo.bits_per_sample;
Alexandre Lision0e143012014-01-22 11:02:46 -0500749 info->tx_level_adj = ((float)cinfo.tx_adj_level) / 128 + 1;
750 info->rx_level_adj = ((float)cinfo.rx_adj_level) / 128 + 1;
Tristan Matthews0a329cc2013-07-17 13:20:14 -0400751
752 /* Build array of listeners */
753 info->listener_cnt = cinfo.listener_cnt;
754 for (i=0; i<cinfo.listener_cnt; ++i) {
755 info->listeners[i] = cinfo.listener_slots[i];
756 }
757
758 return PJ_SUCCESS;
759}
760
761
762/*
763 * Add arbitrary media port to PJSUA's conference bridge.
764 */
765PJ_DEF(pj_status_t) pjsua_conf_add_port( pj_pool_t *pool,
766 pjmedia_port *port,
767 pjsua_conf_port_id *p_id)
768{
769 pj_status_t status;
770
771 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
772 port, NULL, (unsigned*)p_id);
773 if (status != PJ_SUCCESS) {
774 if (p_id)
775 *p_id = PJSUA_INVALID_ID;
776 }
777
778 return status;
779}
780
781
782/*
783 * Remove arbitrary slot from the conference bridge.
784 */
785PJ_DEF(pj_status_t) pjsua_conf_remove_port(pjsua_conf_port_id id)
786{
787 pj_status_t status;
788
789 status = pjmedia_conf_remove_port(pjsua_var.mconf, (unsigned)id);
790 pjsua_check_snd_dev_idle();
791
792 return status;
793}
794
795
796/*
797 * Establish unidirectional media flow from souce to sink.
798 */
799PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source,
800 pjsua_conf_port_id sink)
801{
802 pj_status_t status = PJ_SUCCESS;
803
804 PJ_LOG(4,(THIS_FILE, "%s connect: %d --> %d",
805 (pjsua_var.is_mswitch ? "Switch" : "Conf"),
806 source, sink));
807 pj_log_push_indent();
808
809 PJSUA_LOCK();
810
811 /* If sound device idle timer is active, cancel it first. */
812 if (pjsua_var.snd_idle_timer.id) {
813 pjsip_endpt_cancel_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer);
814 pjsua_var.snd_idle_timer.id = PJ_FALSE;
815 }
816
817
818 /* For audio switchboard (i.e. APS-Direct):
819 * Check if sound device need to be reopened, i.e: its attributes
820 * (format, clock rate, channel count) must match to peer's.
821 * Note that sound device can be reopened only if it doesn't have
822 * any connection.
823 */
824 if (pjsua_var.is_mswitch) {
825 pjmedia_conf_port_info port0_info;
826 pjmedia_conf_port_info peer_info;
827 unsigned peer_id;
828 pj_bool_t need_reopen = PJ_FALSE;
829
830 peer_id = (source!=0)? source : sink;
831 status = pjmedia_conf_get_port_info(pjsua_var.mconf, peer_id,
832 &peer_info);
833 pj_assert(status == PJ_SUCCESS);
834
835 status = pjmedia_conf_get_port_info(pjsua_var.mconf, 0, &port0_info);
836 pj_assert(status == PJ_SUCCESS);
837
838 /* Check if sound device is instantiated. */
839 need_reopen = (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
840 !pjsua_var.no_snd);
841
842 /* Check if sound device need to reopen because it needs to modify
843 * settings to match its peer. Sound device must be idle in this case
844 * though.
845 */
846 if (!need_reopen &&
847 port0_info.listener_cnt==0 && port0_info.transmitter_cnt==0)
848 {
849 need_reopen = (peer_info.format.id != port0_info.format.id ||
850 peer_info.format.det.aud.avg_bps !=
851 port0_info.format.det.aud.avg_bps ||
852 peer_info.clock_rate != port0_info.clock_rate ||
853 peer_info.channel_count!=port0_info.channel_count);
854 }
855
856 if (need_reopen) {
857 if (pjsua_var.cap_dev != NULL_SND_DEV_ID) {
858 pjmedia_snd_port_param param;
859
860 pjmedia_snd_port_param_default(&param);
861 param.ec_options = pjsua_var.media_cfg.ec_options;
862
863 /* Create parameter based on peer info */
864 status = create_aud_param(&param.base, pjsua_var.cap_dev,
865 pjsua_var.play_dev,
866 peer_info.clock_rate,
867 peer_info.channel_count,
868 peer_info.samples_per_frame,
869 peer_info.bits_per_sample);
870 if (status != PJ_SUCCESS) {
871 pjsua_perror(THIS_FILE, "Error opening sound device",
872 status);
873 goto on_return;
874 }
875
876 /* And peer format */
877 if (peer_info.format.id != PJMEDIA_FORMAT_PCM) {
878 param.base.flags |= PJMEDIA_AUD_DEV_CAP_EXT_FORMAT;
879 param.base.ext_fmt = peer_info.format;
880 }
881
882 param.options = 0;
883 status = open_snd_dev(&param);
884 if (status != PJ_SUCCESS) {
885 pjsua_perror(THIS_FILE, "Error opening sound device",
886 status);
887 goto on_return;
888 }
889 } else {
890 /* Null-audio */
891 status = pjsua_set_snd_dev(pjsua_var.cap_dev,
892 pjsua_var.play_dev);
893 if (status != PJ_SUCCESS) {
894 pjsua_perror(THIS_FILE, "Error opening sound device",
895 status);
896 goto on_return;
897 }
898 }
899 } else if (pjsua_var.no_snd) {
900 if (!pjsua_var.snd_is_on) {
901 pjsua_var.snd_is_on = PJ_TRUE;
902 /* Notify app */
903 if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
904 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
905 }
906 }
907 }
908
909 } else {
910 /* The bridge version */
911
912 /* Create sound port if none is instantiated */
913 if (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
914 !pjsua_var.no_snd)
915 {
916 status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
917 if (status != PJ_SUCCESS) {
918 pjsua_perror(THIS_FILE, "Error opening sound device", status);
919 goto on_return;
920 }
921 } else if (pjsua_var.no_snd && !pjsua_var.snd_is_on) {
922 pjsua_var.snd_is_on = PJ_TRUE;
923 /* Notify app */
924 if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
925 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
926 }
927 }
928 }
929
930on_return:
931 PJSUA_UNLOCK();
932
933 if (status == PJ_SUCCESS) {
934 status = pjmedia_conf_connect_port(pjsua_var.mconf, source, sink, 0);
935 }
936
937 pj_log_pop_indent();
938 return status;
939}
940
941
942/*
943 * Disconnect media flow from the source to destination port.
944 */
945PJ_DEF(pj_status_t) pjsua_conf_disconnect( pjsua_conf_port_id source,
946 pjsua_conf_port_id sink)
947{
948 pj_status_t status;
949
950 PJ_LOG(4,(THIS_FILE, "%s disconnect: %d -x- %d",
951 (pjsua_var.is_mswitch ? "Switch" : "Conf"),
952 source, sink));
953 pj_log_push_indent();
954
955 status = pjmedia_conf_disconnect_port(pjsua_var.mconf, source, sink);
956 pjsua_check_snd_dev_idle();
957
958 pj_log_pop_indent();
959 return status;
960}
961
962
963/*
964 * Adjust the signal level to be transmitted from the bridge to the
965 * specified port by making it louder or quieter.
966 */
967PJ_DEF(pj_status_t) pjsua_conf_adjust_tx_level(pjsua_conf_port_id slot,
968 float level)
969{
970 return pjmedia_conf_adjust_tx_level(pjsua_var.mconf, slot,
971 (int)((level-1) * 128));
972}
973
974/*
975 * Adjust the signal level to be received from the specified port (to
976 * the bridge) by making it louder or quieter.
977 */
978PJ_DEF(pj_status_t) pjsua_conf_adjust_rx_level(pjsua_conf_port_id slot,
979 float level)
980{
981 return pjmedia_conf_adjust_rx_level(pjsua_var.mconf, slot,
982 (int)((level-1) * 128));
983}
984
985
986/*
987 * Get last signal level transmitted to or received from the specified port.
988 */
989PJ_DEF(pj_status_t) pjsua_conf_get_signal_level(pjsua_conf_port_id slot,
990 unsigned *tx_level,
991 unsigned *rx_level)
992{
993 return pjmedia_conf_get_signal_level(pjsua_var.mconf, slot,
994 tx_level, rx_level);
995}
996
997/*****************************************************************************
998 * File player.
999 */
1000
1001static char* get_basename(const char *path, unsigned len)
1002{
1003 char *p = ((char*)path) + len;
1004
1005 if (len==0)
1006 return p;
1007
1008 for (--p; p!=path && *p!='/' && *p!='\\'; ) --p;
1009
1010 return (p==path) ? p : p+1;
1011}
1012
1013
1014/*
1015 * Create a file player, and automatically connect this player to
1016 * the conference bridge.
1017 */
1018PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename,
1019 unsigned options,
1020 pjsua_player_id *p_id)
1021{
1022 unsigned slot, file_id;
1023 char path[PJ_MAXPATH];
1024 pj_pool_t *pool = NULL;
1025 pjmedia_port *port;
1026 pj_status_t status = PJ_SUCCESS;
1027
1028 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
1029 return PJ_ETOOMANY;
1030
1031 PJ_LOG(4,(THIS_FILE, "Creating file player: %.*s..",
1032 (int)filename->slen, filename->ptr));
1033 pj_log_push_indent();
1034
1035 PJSUA_LOCK();
1036
1037 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
1038 if (pjsua_var.player[file_id].port == NULL)
1039 break;
1040 }
1041
1042 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
1043 /* This is unexpected */
1044 pj_assert(0);
1045 status = PJ_EBUG;
1046 goto on_error;
1047 }
1048
1049 pj_memcpy(path, filename->ptr, filename->slen);
1050 path[filename->slen] = '\0';
1051
1052 pool = pjsua_pool_create(get_basename(path, (unsigned)filename->slen), 1000,
1053 1000);
1054 if (!pool) {
1055 status = PJ_ENOMEM;
1056 goto on_error;
1057 }
1058
1059 status = pjmedia_wav_player_port_create(
1060 pool, path,
1061 pjsua_var.mconf_cfg.samples_per_frame *
1062 1000 / pjsua_var.media_cfg.channel_count /
1063 pjsua_var.media_cfg.clock_rate,
1064 options, 0, &port);
1065 if (status != PJ_SUCCESS) {
1066 pjsua_perror(THIS_FILE, "Unable to open file for playback", status);
1067 goto on_error;
1068 }
1069
1070 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
1071 port, filename, &slot);
1072 if (status != PJ_SUCCESS) {
1073 pjmedia_port_destroy(port);
1074 pjsua_perror(THIS_FILE, "Unable to add file to conference bridge",
1075 status);
1076 goto on_error;
1077 }
1078
1079 pjsua_var.player[file_id].type = 0;
1080 pjsua_var.player[file_id].pool = pool;
1081 pjsua_var.player[file_id].port = port;
1082 pjsua_var.player[file_id].slot = slot;
1083
1084 if (p_id) *p_id = file_id;
1085
1086 ++pjsua_var.player_cnt;
1087
1088 PJSUA_UNLOCK();
1089
1090 PJ_LOG(4,(THIS_FILE, "Player created, id=%d, slot=%d", file_id, slot));
1091
1092 pj_log_pop_indent();
1093 return PJ_SUCCESS;
1094
1095on_error:
1096 PJSUA_UNLOCK();
1097 if (pool) pj_pool_release(pool);
1098 pj_log_pop_indent();
1099 return status;
1100}
1101
1102
1103/*
1104 * Create a file playlist media port, and automatically add the port
1105 * to the conference bridge.
1106 */
1107PJ_DEF(pj_status_t) pjsua_playlist_create( const pj_str_t file_names[],
1108 unsigned file_count,
1109 const pj_str_t *label,
1110 unsigned options,
1111 pjsua_player_id *p_id)
1112{
1113 unsigned slot, file_id, ptime;
1114 pj_pool_t *pool = NULL;
1115 pjmedia_port *port;
1116 pj_status_t status = PJ_SUCCESS;
1117
1118 if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
1119 return PJ_ETOOMANY;
1120
1121 PJ_LOG(4,(THIS_FILE, "Creating playlist with %d file(s)..", file_count));
1122 pj_log_push_indent();
1123
1124 PJSUA_LOCK();
1125
1126 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
1127 if (pjsua_var.player[file_id].port == NULL)
1128 break;
1129 }
1130
1131 if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
1132 /* This is unexpected */
1133 pj_assert(0);
1134 status = PJ_EBUG;
1135 goto on_error;
1136 }
1137
1138
1139 ptime = pjsua_var.mconf_cfg.samples_per_frame * 1000 /
1140 pjsua_var.media_cfg.clock_rate;
1141
1142 pool = pjsua_pool_create("playlist", 1000, 1000);
1143 if (!pool) {
1144 status = PJ_ENOMEM;
1145 goto on_error;
1146 }
1147
1148 status = pjmedia_wav_playlist_create(pool, label,
1149 file_names, file_count,
1150 ptime, options, 0, &port);
1151 if (status != PJ_SUCCESS) {
1152 pjsua_perror(THIS_FILE, "Unable to create playlist", status);
1153 goto on_error;
1154 }
1155
1156 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
1157 port, &port->info.name, &slot);
1158 if (status != PJ_SUCCESS) {
1159 pjmedia_port_destroy(port);
1160 pjsua_perror(THIS_FILE, "Unable to add port", status);
1161 goto on_error;
1162 }
1163
1164 pjsua_var.player[file_id].type = 1;
1165 pjsua_var.player[file_id].pool = pool;
1166 pjsua_var.player[file_id].port = port;
1167 pjsua_var.player[file_id].slot = slot;
1168
1169 if (p_id) *p_id = file_id;
1170
1171 ++pjsua_var.player_cnt;
1172
1173 PJSUA_UNLOCK();
1174
1175 PJ_LOG(4,(THIS_FILE, "Playlist created, id=%d, slot=%d", file_id, slot));
1176
1177 pj_log_pop_indent();
1178
1179 return PJ_SUCCESS;
1180
1181on_error:
1182 PJSUA_UNLOCK();
1183 if (pool) pj_pool_release(pool);
1184 pj_log_pop_indent();
1185
1186 return status;
1187}
1188
1189
1190/*
1191 * Get conference port ID associated with player.
1192 */
1193PJ_DEF(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id)
1194{
1195 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
1196 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
1197
1198 return pjsua_var.player[id].slot;
1199}
1200
1201/*
1202 * Get the media port for the player.
1203 */
1204PJ_DEF(pj_status_t) pjsua_player_get_port( pjsua_player_id id,
1205 pjmedia_port **p_port)
1206{
1207 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
1208 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
1209 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
1210
1211 *p_port = pjsua_var.player[id].port;
1212
1213 return PJ_SUCCESS;
1214}
1215
1216/*
1217 * Set playback position.
1218 */
1219PJ_DEF(pj_status_t) pjsua_player_set_pos( pjsua_player_id id,
1220 pj_uint32_t samples)
1221{
1222 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
1223 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
1224 PJ_ASSERT_RETURN(pjsua_var.player[id].type == 0, PJ_EINVAL);
1225
1226 return pjmedia_wav_player_port_set_pos(pjsua_var.player[id].port, samples);
1227}
1228
1229
1230/*
1231 * Close the file, remove the player from the bridge, and free
1232 * resources associated with the file player.
1233 */
1234PJ_DEF(pj_status_t) pjsua_player_destroy(pjsua_player_id id)
1235{
1236 PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
1237 PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
1238
1239 PJ_LOG(4,(THIS_FILE, "Destroying player %d..", id));
1240 pj_log_push_indent();
1241
1242 PJSUA_LOCK();
1243
1244 if (pjsua_var.player[id].port) {
1245 pjsua_conf_remove_port(pjsua_var.player[id].slot);
1246 pjmedia_port_destroy(pjsua_var.player[id].port);
1247 pjsua_var.player[id].port = NULL;
1248 pjsua_var.player[id].slot = 0xFFFF;
1249 pj_pool_release(pjsua_var.player[id].pool);
1250 pjsua_var.player[id].pool = NULL;
1251 pjsua_var.player_cnt--;
1252 }
1253
1254 PJSUA_UNLOCK();
1255 pj_log_pop_indent();
1256
1257 return PJ_SUCCESS;
1258}
1259
1260
1261/*****************************************************************************
1262 * File recorder.
1263 */
1264
1265/*
1266 * Create a file recorder, and automatically connect this recorder to
1267 * the conference bridge.
1268 */
1269PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename,
1270 unsigned enc_type,
1271 void *enc_param,
1272 pj_ssize_t max_size,
1273 unsigned options,
1274 pjsua_recorder_id *p_id)
1275{
1276 enum Format
1277 {
1278 FMT_UNKNOWN,
1279 FMT_WAV,
1280 FMT_MP3,
1281 };
1282 unsigned slot, file_id;
1283 char path[PJ_MAXPATH];
1284 pj_str_t ext;
1285 int file_format;
1286 pj_pool_t *pool = NULL;
1287 pjmedia_port *port;
1288 pj_status_t status = PJ_SUCCESS;
1289
1290 /* Filename must present */
1291 PJ_ASSERT_RETURN(filename != NULL, PJ_EINVAL);
1292
1293 /* Don't support max_size at present */
1294 PJ_ASSERT_RETURN(max_size == 0 || max_size == -1, PJ_EINVAL);
1295
1296 /* Don't support encoding type at present */
1297 PJ_ASSERT_RETURN(enc_type == 0, PJ_EINVAL);
1298
1299 PJ_LOG(4,(THIS_FILE, "Creating recorder %.*s..",
1300 (int)filename->slen, filename->ptr));
1301 pj_log_push_indent();
1302
1303 if (pjsua_var.rec_cnt >= PJ_ARRAY_SIZE(pjsua_var.recorder)) {
1304 pj_log_pop_indent();
1305 return PJ_ETOOMANY;
1306 }
1307
1308 /* Determine the file format */
1309 ext.ptr = filename->ptr + filename->slen - 4;
1310 ext.slen = 4;
1311
1312 if (pj_stricmp2(&ext, ".wav") == 0)
1313 file_format = FMT_WAV;
1314 else if (pj_stricmp2(&ext, ".mp3") == 0)
1315 file_format = FMT_MP3;
1316 else {
1317 PJ_LOG(1,(THIS_FILE, "pjsua_recorder_create() error: unable to "
1318 "determine file format for %.*s",
1319 (int)filename->slen, filename->ptr));
1320 pj_log_pop_indent();
1321 return PJ_ENOTSUP;
1322 }
1323
1324 PJSUA_LOCK();
1325
1326 for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.recorder); ++file_id) {
1327 if (pjsua_var.recorder[file_id].port == NULL)
1328 break;
1329 }
1330
1331 if (file_id == PJ_ARRAY_SIZE(pjsua_var.recorder)) {
1332 /* This is unexpected */
1333 pj_assert(0);
1334 status = PJ_EBUG;
1335 goto on_return;
1336 }
1337
1338 pj_memcpy(path, filename->ptr, filename->slen);
1339 path[filename->slen] = '\0';
1340
1341 pool = pjsua_pool_create(get_basename(path, (unsigned)filename->slen), 1000,
1342 1000);
1343 if (!pool) {
1344 status = PJ_ENOMEM;
1345 goto on_return;
1346 }
1347
1348 if (file_format == FMT_WAV) {
1349 status = pjmedia_wav_writer_port_create(pool, path,
1350 pjsua_var.media_cfg.clock_rate,
1351 pjsua_var.mconf_cfg.channel_count,
1352 pjsua_var.mconf_cfg.samples_per_frame,
1353 pjsua_var.mconf_cfg.bits_per_sample,
1354 options, 0, &port);
1355 } else {
1356 PJ_UNUSED_ARG(enc_param);
1357 port = NULL;
1358 status = PJ_ENOTSUP;
1359 }
1360
1361 if (status != PJ_SUCCESS) {
1362 pjsua_perror(THIS_FILE, "Unable to open file for recording", status);
1363 goto on_return;
1364 }
1365
1366 status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
1367 port, filename, &slot);
1368 if (status != PJ_SUCCESS) {
1369 pjmedia_port_destroy(port);
1370 goto on_return;
1371 }
1372
1373 pjsua_var.recorder[file_id].port = port;
1374 pjsua_var.recorder[file_id].slot = slot;
1375 pjsua_var.recorder[file_id].pool = pool;
1376
1377 if (p_id) *p_id = file_id;
1378
1379 ++pjsua_var.rec_cnt;
1380
1381 PJSUA_UNLOCK();
1382
1383 PJ_LOG(4,(THIS_FILE, "Recorder created, id=%d, slot=%d", file_id, slot));
1384
1385 pj_log_pop_indent();
1386 return PJ_SUCCESS;
1387
1388on_return:
1389 PJSUA_UNLOCK();
1390 if (pool) pj_pool_release(pool);
1391 pj_log_pop_indent();
1392 return status;
1393}
1394
1395
1396/*
1397 * Get conference port associated with recorder.
1398 */
1399PJ_DEF(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id)
1400{
1401 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
1402 PJ_EINVAL);
1403 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
1404
1405 return pjsua_var.recorder[id].slot;
1406}
1407
1408/*
1409 * Get the media port for the recorder.
1410 */
1411PJ_DEF(pj_status_t) pjsua_recorder_get_port( pjsua_recorder_id id,
1412 pjmedia_port **p_port)
1413{
1414 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
1415 PJ_EINVAL);
1416 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
1417 PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
1418
1419 *p_port = pjsua_var.recorder[id].port;
1420 return PJ_SUCCESS;
1421}
1422
1423/*
1424 * Destroy recorder (this will complete recording).
1425 */
1426PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id)
1427{
1428 PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
1429 PJ_EINVAL);
1430 PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
1431
1432 PJ_LOG(4,(THIS_FILE, "Destroying recorder %d..", id));
1433 pj_log_push_indent();
1434
1435 PJSUA_LOCK();
1436
1437 if (pjsua_var.recorder[id].port) {
1438 pjsua_conf_remove_port(pjsua_var.recorder[id].slot);
1439 pjmedia_port_destroy(pjsua_var.recorder[id].port);
1440 pjsua_var.recorder[id].port = NULL;
1441 pjsua_var.recorder[id].slot = 0xFFFF;
1442 pj_pool_release(pjsua_var.recorder[id].pool);
1443 pjsua_var.recorder[id].pool = NULL;
1444 pjsua_var.rec_cnt--;
1445 }
1446
1447 PJSUA_UNLOCK();
1448 pj_log_pop_indent();
1449
1450 return PJ_SUCCESS;
1451}
1452
1453
1454/*****************************************************************************
1455 * Sound devices.
1456 */
1457
1458/*
1459 * Enum sound devices.
1460 */
1461
1462PJ_DEF(pj_status_t) pjsua_enum_aud_devs( pjmedia_aud_dev_info info[],
1463 unsigned *count)
1464{
1465 unsigned i, dev_count;
1466
1467 dev_count = pjmedia_aud_dev_count();
1468
1469 if (dev_count > *count) dev_count = *count;
1470
1471 for (i=0; i<dev_count; ++i) {
1472 pj_status_t status;
1473
1474 status = pjmedia_aud_dev_get_info(i, &info[i]);
1475 if (status != PJ_SUCCESS)
1476 return status;
1477 }
1478
1479 *count = dev_count;
1480
1481 return PJ_SUCCESS;
1482}
1483
1484
1485PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[],
1486 unsigned *count)
1487{
1488 unsigned i, dev_count;
1489
1490 dev_count = pjmedia_aud_dev_count();
1491
1492 if (dev_count > *count) dev_count = *count;
1493 pj_bzero(info, dev_count * sizeof(pjmedia_snd_dev_info));
1494
1495 for (i=0; i<dev_count; ++i) {
1496 pjmedia_aud_dev_info ai;
1497 pj_status_t status;
1498
1499 status = pjmedia_aud_dev_get_info(i, &ai);
1500 if (status != PJ_SUCCESS)
1501 return status;
1502
1503 strncpy(info[i].name, ai.name, sizeof(info[i].name));
1504 info[i].name[sizeof(info[i].name)-1] = '\0';
1505 info[i].input_count = ai.input_count;
1506 info[i].output_count = ai.output_count;
1507 info[i].default_samples_per_sec = ai.default_samples_per_sec;
1508 }
1509
1510 *count = dev_count;
1511
1512 return PJ_SUCCESS;
1513}
1514
1515/* Create audio device parameter to open the device */
1516static pj_status_t create_aud_param(pjmedia_aud_param *param,
1517 pjmedia_aud_dev_index capture_dev,
1518 pjmedia_aud_dev_index playback_dev,
1519 unsigned clock_rate,
1520 unsigned channel_count,
1521 unsigned samples_per_frame,
1522 unsigned bits_per_sample)
1523{
1524 pj_status_t status;
1525
1526 /* Normalize device ID with new convention about default device ID */
1527 if (playback_dev == PJMEDIA_AUD_DEFAULT_CAPTURE_DEV)
1528 playback_dev = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV;
1529
1530 /* Create default parameters for the device */
1531 status = pjmedia_aud_dev_default_param(capture_dev, param);
1532 if (status != PJ_SUCCESS) {
1533 pjsua_perror(THIS_FILE, "Error retrieving default audio "
1534 "device parameters", status);
1535 return status;
1536 }
1537 param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
1538 param->rec_id = capture_dev;
1539 param->play_id = playback_dev;
1540 param->clock_rate = clock_rate;
1541 param->channel_count = channel_count;
1542 param->samples_per_frame = samples_per_frame;
1543 param->bits_per_sample = bits_per_sample;
1544
1545 /* Update the setting with user preference */
1546#define update_param(cap, field) \
1547 if (pjsua_var.aud_param.flags & cap) { \
1548 param->flags |= cap; \
1549 param->field = pjsua_var.aud_param.field; \
1550 }
1551 update_param( PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
1552 update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
1553 update_param( PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
1554 update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
1555#undef update_param
1556
1557 /* Latency settings */
1558 param->flags |= (PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
1559 PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY);
1560 param->input_latency_ms = pjsua_var.media_cfg.snd_rec_latency;
1561 param->output_latency_ms = pjsua_var.media_cfg.snd_play_latency;
1562
1563 /* EC settings */
1564 if (pjsua_var.media_cfg.ec_tail_len) {
1565 param->flags |= (PJMEDIA_AUD_DEV_CAP_EC | PJMEDIA_AUD_DEV_CAP_EC_TAIL);
1566 param->ec_enabled = PJ_TRUE;
1567 param->ec_tail_ms = pjsua_var.media_cfg.ec_tail_len;
1568 } else {
1569 param->flags &= ~(PJMEDIA_AUD_DEV_CAP_EC|PJMEDIA_AUD_DEV_CAP_EC_TAIL);
1570 }
1571
1572 /* VAD settings */
1573 if (pjsua_var.media_cfg.no_vad) {
1574 param->flags &= ~PJMEDIA_AUD_DEV_CAP_VAD;
1575 } else {
1576 param->flags |= PJMEDIA_AUD_DEV_CAP_VAD;
1577 param->vad_enabled = PJ_TRUE;
1578 }
1579
1580 return PJ_SUCCESS;
1581}
1582
1583/* Internal: the first time the audio device is opened (during app
1584 * startup), retrieve the audio settings such as volume level
1585 * so that aud_get_settings() will work.
1586 */
1587static pj_status_t update_initial_aud_param()
1588{
1589 pjmedia_aud_stream *strm;
1590 pjmedia_aud_param param;
1591 pj_status_t status;
1592
1593 PJ_ASSERT_RETURN(pjsua_var.snd_port != NULL, PJ_EBUG);
1594
1595 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
1596
1597 status = pjmedia_aud_stream_get_param(strm, &param);
1598 if (status != PJ_SUCCESS) {
1599 pjsua_perror(THIS_FILE, "Error audio stream "
1600 "device parameters", status);
1601 return status;
1602 }
1603
1604#define update_saved_param(cap, field) \
1605 if (param.flags & cap) { \
1606 pjsua_var.aud_param.flags |= cap; \
1607 pjsua_var.aud_param.field = param.field; \
1608 }
1609
1610 update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
1611 update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
1612 update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
1613 update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
1614#undef update_saved_param
1615
1616 return PJ_SUCCESS;
1617}
1618
1619/* Get format name */
1620static const char *get_fmt_name(pj_uint32_t id)
1621{
1622 static char name[8];
1623
1624 if (id == PJMEDIA_FORMAT_L16)
1625 return "PCM";
1626 pj_memcpy(name, &id, 4);
1627 name[4] = '\0';
1628 return name;
1629}
1630
1631/* Open sound device with the setting. */
1632static pj_status_t open_snd_dev(pjmedia_snd_port_param *param)
1633{
1634 pjmedia_port *conf_port;
1635 pj_status_t status;
1636
1637 PJ_ASSERT_RETURN(param, PJ_EINVAL);
1638
1639 /* Check if NULL sound device is used */
1640 if (NULL_SND_DEV_ID==param->base.rec_id ||
1641 NULL_SND_DEV_ID==param->base.play_id)
1642 {
1643 return pjsua_set_null_snd_dev();
1644 }
1645
1646 /* Close existing sound port */
1647 close_snd_dev();
1648
1649 /* Notify app */
1650 if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
1651 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
1652 }
1653
1654 /* Create memory pool for sound device. */
1655 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
1656 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
1657
1658
1659 PJ_LOG(4,(THIS_FILE, "Opening sound device %s@%d/%d/%dms",
1660 get_fmt_name(param->base.ext_fmt.id),
1661 param->base.clock_rate, param->base.channel_count,
1662 param->base.samples_per_frame / param->base.channel_count *
1663 1000 / param->base.clock_rate));
1664 pj_log_push_indent();
1665
1666 status = pjmedia_snd_port_create2( pjsua_var.snd_pool,
1667 param, &pjsua_var.snd_port);
1668 if (status != PJ_SUCCESS)
1669 goto on_error;
1670
1671 /* Get the port0 of the conference bridge. */
1672 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
1673 pj_assert(conf_port != NULL);
1674
1675 /* For conference bridge, resample if necessary if the bridge's
1676 * clock rate is different than the sound device's clock rate.
1677 */
1678 if (!pjsua_var.is_mswitch &&
1679 param->base.ext_fmt.id == PJMEDIA_FORMAT_PCM &&
1680 PJMEDIA_PIA_SRATE(&conf_port->info) != param->base.clock_rate)
1681 {
1682 pjmedia_port *resample_port;
1683 unsigned resample_opt = 0;
1684
1685 if (pjsua_var.media_cfg.quality >= 3 &&
1686 pjsua_var.media_cfg.quality <= 4)
1687 {
1688 resample_opt |= PJMEDIA_RESAMPLE_USE_SMALL_FILTER;
1689 }
1690 else if (pjsua_var.media_cfg.quality < 3) {
1691 resample_opt |= PJMEDIA_RESAMPLE_USE_LINEAR;
1692 }
1693
1694 status = pjmedia_resample_port_create(pjsua_var.snd_pool,
1695 conf_port,
1696 param->base.clock_rate,
1697 resample_opt,
1698 &resample_port);
1699 if (status != PJ_SUCCESS) {
1700 char errmsg[PJ_ERR_MSG_SIZE];
1701 pj_strerror(status, errmsg, sizeof(errmsg));
1702 PJ_LOG(4, (THIS_FILE,
1703 "Error creating resample port: %s",
1704 errmsg));
1705 close_snd_dev();
1706 goto on_error;
1707 }
1708
1709 conf_port = resample_port;
1710 }
1711
1712 /* Otherwise for audio switchboard, the switch's port0 setting is
1713 * derived from the sound device setting, so update the setting.
1714 */
1715 if (pjsua_var.is_mswitch) {
1716 if (param->base.flags & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT) {
1717 conf_port->info.fmt = param->base.ext_fmt;
1718 } else {
1719 unsigned bps, ptime_usec;
1720 bps = param->base.clock_rate * param->base.bits_per_sample;
1721 ptime_usec = param->base.samples_per_frame /
1722 param->base.channel_count * 1000000 /
1723 param->base.clock_rate;
1724 pjmedia_format_init_audio(&conf_port->info.fmt,
1725 PJMEDIA_FORMAT_PCM,
1726 param->base.clock_rate,
1727 param->base.channel_count,
1728 param->base.bits_per_sample,
1729 ptime_usec,
1730 bps, bps);
1731 }
1732 }
1733
1734
1735 /* Connect sound port to the bridge */
1736 status = pjmedia_snd_port_connect(pjsua_var.snd_port,
1737 conf_port );
1738 if (status != PJ_SUCCESS) {
1739 pjsua_perror(THIS_FILE, "Unable to connect conference port to "
1740 "sound device", status);
1741 pjmedia_snd_port_destroy(pjsua_var.snd_port);
1742 pjsua_var.snd_port = NULL;
1743 goto on_error;
1744 }
1745
1746 /* Save the device IDs */
1747 pjsua_var.cap_dev = param->base.rec_id;
1748 pjsua_var.play_dev = param->base.play_id;
1749
1750 /* Update sound device name. */
1751 {
1752 pjmedia_aud_dev_info rec_info;
1753 pjmedia_aud_stream *strm;
1754 pjmedia_aud_param si;
1755 pj_str_t tmp;
1756
1757 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
1758 status = pjmedia_aud_stream_get_param(strm, &si);
1759 if (status == PJ_SUCCESS)
1760 status = pjmedia_aud_dev_get_info(si.rec_id, &rec_info);
1761
1762 if (status==PJ_SUCCESS) {
1763 if (param->base.clock_rate != pjsua_var.media_cfg.clock_rate) {
1764 char tmp_buf[128];
Alexandre Lision8af73cb2013-12-10 14:11:20 -05001765 int tmp_buf_len;
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001766
Alexandre Lision8af73cb2013-12-10 14:11:20 -05001767 tmp_buf_len = pj_ansi_snprintf(tmp_buf, sizeof(tmp_buf),
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001768 "%s (%dKHz)",
1769 rec_info.name,
1770 param->base.clock_rate/1000);
Alexandre Lision8af73cb2013-12-10 14:11:20 -05001771 if (tmp_buf_len < 1 || tmp_buf_len >= (int)sizeof(tmp_buf))
1772 tmp_buf_len = sizeof(tmp_buf) - 1;
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001773 pj_strset(&tmp, tmp_buf, tmp_buf_len);
1774 pjmedia_conf_set_port0_name(pjsua_var.mconf, &tmp);
1775 } else {
1776 pjmedia_conf_set_port0_name(pjsua_var.mconf,
1777 pj_cstr(&tmp, rec_info.name));
1778 }
1779 }
1780
1781 /* Any error is not major, let it through */
1782 status = PJ_SUCCESS;
1783 }
1784
1785 /* If this is the first time the audio device is open, retrieve some
1786 * settings from the device (such as volume settings) so that the
1787 * pjsua_snd_get_setting() work.
1788 */
1789 if (pjsua_var.aud_open_cnt == 0) {
1790 update_initial_aud_param();
1791 ++pjsua_var.aud_open_cnt;
1792 }
1793
1794 pjsua_var.snd_is_on = PJ_TRUE;
1795
1796 pj_log_pop_indent();
1797 return PJ_SUCCESS;
1798
1799on_error:
1800 pj_log_pop_indent();
1801 return status;
1802}
1803
1804
1805/* Close existing sound device */
1806static void close_snd_dev(void)
1807{
1808 pj_log_push_indent();
1809
1810 /* Notify app */
1811 if (pjsua_var.snd_is_on && pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
1812 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(0);
1813 }
1814
1815 /* Close sound device */
1816 if (pjsua_var.snd_port) {
1817 pjmedia_aud_dev_info cap_info, play_info;
1818 pjmedia_aud_stream *strm;
1819 pjmedia_aud_param param;
1820
1821 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
1822 pjmedia_aud_stream_get_param(strm, &param);
1823
1824 if (pjmedia_aud_dev_get_info(param.rec_id, &cap_info) != PJ_SUCCESS)
1825 cap_info.name[0] = '\0';
1826 if (pjmedia_aud_dev_get_info(param.play_id, &play_info) != PJ_SUCCESS)
1827 play_info.name[0] = '\0';
1828
1829 PJ_LOG(4,(THIS_FILE, "Closing %s sound playback device and "
1830 "%s sound capture device",
1831 play_info.name, cap_info.name));
1832
1833 pjmedia_snd_port_disconnect(pjsua_var.snd_port);
1834 pjmedia_snd_port_destroy(pjsua_var.snd_port);
1835 pjsua_var.snd_port = NULL;
1836 }
1837
1838 /* Close null sound device */
1839 if (pjsua_var.null_snd) {
1840 PJ_LOG(4,(THIS_FILE, "Closing null sound device.."));
1841 pjmedia_master_port_destroy(pjsua_var.null_snd, PJ_FALSE);
1842 pjsua_var.null_snd = NULL;
1843 }
1844
1845 if (pjsua_var.snd_pool)
1846 pj_pool_release(pjsua_var.snd_pool);
1847
1848 pjsua_var.snd_pool = NULL;
1849 pjsua_var.snd_is_on = PJ_FALSE;
1850
1851 pj_log_pop_indent();
1852}
1853
1854
1855/*
1856 * Select or change sound device. Application may call this function at
1857 * any time to replace current sound device.
1858 */
1859PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
1860 int playback_dev)
1861{
1862 unsigned alt_cr_cnt = 1;
1863 unsigned alt_cr[] = {0, 44100, 48000, 32000, 16000, 8000};
1864 unsigned i;
1865 pj_status_t status = -1;
1866
1867 PJ_LOG(4,(THIS_FILE, "Set sound device: capture=%d, playback=%d",
1868 capture_dev, playback_dev));
1869 pj_log_push_indent();
1870
1871 PJSUA_LOCK();
1872
1873 /* Null-sound */
1874 if (capture_dev==NULL_SND_DEV_ID && playback_dev==NULL_SND_DEV_ID) {
1875 PJSUA_UNLOCK();
1876 status = pjsua_set_null_snd_dev();
1877 pj_log_pop_indent();
1878 return status;
1879 }
1880
1881 /* Set default clock rate */
1882 alt_cr[0] = pjsua_var.media_cfg.snd_clock_rate;
1883 if (alt_cr[0] == 0)
1884 alt_cr[0] = pjsua_var.media_cfg.clock_rate;
1885
1886 /* Allow retrying of different clock rate if we're using conference
1887 * bridge (meaning audio format is always PCM), otherwise lock on
1888 * to one clock rate.
1889 */
1890 if (pjsua_var.is_mswitch) {
1891 alt_cr_cnt = 1;
1892 } else {
1893 alt_cr_cnt = PJ_ARRAY_SIZE(alt_cr);
1894 }
1895
1896 /* Attempts to open the sound device with different clock rates */
1897 for (i=0; i<alt_cr_cnt; ++i) {
1898 pjmedia_snd_port_param param;
1899 unsigned samples_per_frame;
1900
1901 /* Create the default audio param */
1902 samples_per_frame = alt_cr[i] *
1903 pjsua_var.media_cfg.audio_frame_ptime *
1904 pjsua_var.media_cfg.channel_count / 1000;
1905 pjmedia_snd_port_param_default(&param);
1906 param.ec_options = pjsua_var.media_cfg.ec_options;
1907 status = create_aud_param(&param.base, capture_dev, playback_dev,
1908 alt_cr[i], pjsua_var.media_cfg.channel_count,
1909 samples_per_frame, 16);
1910 if (status != PJ_SUCCESS)
1911 goto on_error;
1912
1913 /* Open! */
1914 param.options = 0;
1915 status = open_snd_dev(&param);
1916 if (status == PJ_SUCCESS)
1917 break;
1918 }
1919
1920 if (status != PJ_SUCCESS) {
1921 pjsua_perror(THIS_FILE, "Unable to open sound device", status);
1922 goto on_error;
1923 }
1924
1925 pjsua_var.no_snd = PJ_FALSE;
1926 pjsua_var.snd_is_on = PJ_TRUE;
1927
1928 PJSUA_UNLOCK();
1929 pj_log_pop_indent();
1930 return PJ_SUCCESS;
1931
1932on_error:
1933 PJSUA_UNLOCK();
1934 pj_log_pop_indent();
1935 return status;
1936}
1937
1938
1939/*
1940 * Get currently active sound devices. If sound devices has not been created
1941 * (for example when pjsua_start() is not called), it is possible that
1942 * the function returns PJ_SUCCESS with -1 as device IDs.
1943 */
1944PJ_DEF(pj_status_t) pjsua_get_snd_dev(int *capture_dev,
1945 int *playback_dev)
1946{
1947 PJSUA_LOCK();
1948
1949 if (capture_dev) {
1950 *capture_dev = pjsua_var.cap_dev;
1951 }
1952 if (playback_dev) {
1953 *playback_dev = pjsua_var.play_dev;
1954 }
1955
1956 PJSUA_UNLOCK();
1957 return PJ_SUCCESS;
1958}
1959
1960
1961/*
1962 * Use null sound device.
1963 */
1964PJ_DEF(pj_status_t) pjsua_set_null_snd_dev(void)
1965{
1966 pjmedia_port *conf_port;
1967 pj_status_t status;
1968
1969 PJ_LOG(4,(THIS_FILE, "Setting null sound device.."));
1970 pj_log_push_indent();
1971
1972 PJSUA_LOCK();
1973
1974 /* Close existing sound device */
1975 close_snd_dev();
1976
1977 /* Notify app */
1978 if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
1979 (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
1980 }
1981
1982 /* Create memory pool for sound device. */
1983 pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
1984 PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
1985
1986 PJ_LOG(4,(THIS_FILE, "Opening null sound device.."));
1987
1988 /* Get the port0 of the conference bridge. */
1989 conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
1990 pj_assert(conf_port != NULL);
1991
1992 /* Create master port, connecting port0 of the conference bridge to
1993 * a null port.
1994 */
1995 status = pjmedia_master_port_create(pjsua_var.snd_pool, pjsua_var.null_port,
1996 conf_port, 0, &pjsua_var.null_snd);
1997 if (status != PJ_SUCCESS) {
1998 pjsua_perror(THIS_FILE, "Unable to create null sound device",
1999 status);
2000 PJSUA_UNLOCK();
2001 pj_log_pop_indent();
2002 return status;
2003 }
2004
2005 /* Start the master port */
2006 status = pjmedia_master_port_start(pjsua_var.null_snd);
2007 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
2008
2009 pjsua_var.cap_dev = NULL_SND_DEV_ID;
2010 pjsua_var.play_dev = NULL_SND_DEV_ID;
2011
2012 pjsua_var.no_snd = PJ_FALSE;
2013 pjsua_var.snd_is_on = PJ_TRUE;
2014
2015 PJSUA_UNLOCK();
2016 pj_log_pop_indent();
2017 return PJ_SUCCESS;
2018}
2019
2020
2021
2022/*
2023 * Use no device!
2024 */
2025PJ_DEF(pjmedia_port*) pjsua_set_no_snd_dev(void)
2026{
2027 PJSUA_LOCK();
2028
2029 /* Close existing sound device */
2030 close_snd_dev();
2031 pjsua_var.no_snd = PJ_TRUE;
2032
2033 PJSUA_UNLOCK();
2034
2035 return pjmedia_conf_get_master_port(pjsua_var.mconf);
2036}
2037
2038
2039/*
2040 * Configure the AEC settings of the sound port.
2041 */
2042PJ_DEF(pj_status_t) pjsua_set_ec(unsigned tail_ms, unsigned options)
2043{
2044 pj_status_t status = PJ_SUCCESS;
2045
2046 PJSUA_LOCK();
2047
2048 pjsua_var.media_cfg.ec_tail_len = tail_ms;
2049 pjsua_var.media_cfg.ec_options = options;
2050
2051 if (pjsua_var.snd_port)
2052 status = pjmedia_snd_port_set_ec(pjsua_var.snd_port, pjsua_var.pool,
2053 tail_ms, options);
2054
2055 PJSUA_UNLOCK();
2056 return status;
2057}
2058
2059
2060/*
2061 * Get current AEC tail length.
2062 */
2063PJ_DEF(pj_status_t) pjsua_get_ec_tail(unsigned *p_tail_ms)
2064{
2065 *p_tail_ms = pjsua_var.media_cfg.ec_tail_len;
2066 return PJ_SUCCESS;
2067}
2068
2069
2070/*
2071 * Check whether the sound device is currently active.
2072 */
2073PJ_DEF(pj_bool_t) pjsua_snd_is_active(void)
2074{
2075 return pjsua_var.snd_port != NULL;
2076}
2077
2078
2079/*
2080 * Configure sound device setting to the sound device being used.
2081 */
2082PJ_DEF(pj_status_t) pjsua_snd_set_setting( pjmedia_aud_dev_cap cap,
2083 const void *pval,
2084 pj_bool_t keep)
2085{
2086 pj_status_t status;
2087
2088 /* Check if we are allowed to set the cap */
2089 if ((cap & pjsua_var.aud_svmask) == 0) {
2090 return PJMEDIA_EAUD_INVCAP;
2091 }
2092
2093 PJSUA_LOCK();
2094
2095 /* If sound is active, set it immediately */
2096 if (pjsua_snd_is_active()) {
2097 pjmedia_aud_stream *strm;
2098
2099 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
2100 status = pjmedia_aud_stream_set_cap(strm, cap, pval);
2101 } else {
2102 status = PJ_SUCCESS;
2103 }
2104
2105 if (status != PJ_SUCCESS) {
2106 PJSUA_UNLOCK();
2107 return status;
2108 }
2109
2110 /* Save in internal param for later device open */
2111 if (keep) {
2112 status = pjmedia_aud_param_set_cap(&pjsua_var.aud_param,
2113 cap, pval);
2114 }
2115
2116 PJSUA_UNLOCK();
2117 return status;
2118}
2119
2120/*
2121 * Retrieve a sound device setting.
2122 */
2123PJ_DEF(pj_status_t) pjsua_snd_get_setting( pjmedia_aud_dev_cap cap,
2124 void *pval)
2125{
2126 pj_status_t status;
2127
2128 PJSUA_LOCK();
2129
2130 /* If sound device has never been opened before, open it to
2131 * retrieve the initial setting from the device (e.g. audio
2132 * volume)
2133 */
2134 if (pjsua_var.aud_open_cnt==0) {
2135 PJ_LOG(4,(THIS_FILE, "Opening sound device to get initial settings"));
2136 pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
2137 close_snd_dev();
2138 }
2139
2140 if (pjsua_snd_is_active()) {
2141 /* Sound is active, retrieve from device directly */
2142 pjmedia_aud_stream *strm;
2143
2144 strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
2145 status = pjmedia_aud_stream_get_cap(strm, cap, pval);
2146 } else {
2147 /* Otherwise retrieve from internal param */
2148 status = pjmedia_aud_param_get_cap(&pjsua_var.aud_param,
2149 cap, pval);
2150 }
2151
2152 PJSUA_UNLOCK();
2153 return status;
2154}
2155
2156#endif /* PJSUA_MEDIA_HAS_PJMEDIA */