blob: de9c29ffa8ddf95b654ae8f48a18dd99a4c53065 [file] [log] [blame]
Tristan Matthews0a329cc2013-07-17 13:20:14 -04001/* $Id$ */
2/*
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
21#include <pjmedia.h>
22#include <pjlib-util.h> /* pj_getopt */
23#include <pjlib.h>
24
25#include <stdlib.h> /* atoi() */
26#include <stdio.h>
27
28#include "util.h"
29
30/**
31 * \page page_pjmedia_samples_confsample_c Samples: Using Conference Bridge
32 *
33 * Sample to mix multiple files in the conference bridge and play the
34 * result to sound device.
35 *
36 * This file is pjsip-apps/src/samples/confsample.c
37 *
38 * \includelineno confsample.c
39 */
40
41
42/* For logging purpose. */
43#define THIS_FILE "confsample.c"
44
45
46/* Shall we put recorder in the conference */
47#define RECORDER 1
48
49
50static const char *desc =
51 " FILE: \n"
52 " \n"
53 " confsample.c \n"
54 " \n"
55 " PURPOSE: \n"
56 " \n"
57 " Demonstrate how to use conference bridge. \n"
58 " \n"
59 " USAGE: \n"
60 " \n"
61 " confsample [options] [file1.wav] [file2.wav] ... \n"
62 " \n"
63 " options: \n"
64 SND_USAGE
65 " \n"
66 " fileN.wav are optional WAV files to be connected to the conference \n"
67 " bridge. The WAV files MUST have single channel (mono) and 16 bit PCM \n"
68 " samples. It can have arbitrary sampling rate. \n"
69 " \n"
70 " DESCRIPTION: \n"
71 " \n"
72 " Here we create a conference bridge, with at least one port (port zero \n"
73 " is always created for the sound device). \n"
74 " \n"
75 " If WAV files are specified, the WAV file player ports will be connected \n"
76 " to slot starting from number one in the bridge. The WAV files can have \n"
77 " arbitrary sampling rate; the bridge will convert it to its clock rate. \n"
78 " However, the files MUST have a single audio channel only (i.e. mono). \n";
79
80
81
82/*
83 * Prototypes:
84 */
85
86/* List the ports in the conference bridge */
87static void conf_list(pjmedia_conf *conf, pj_bool_t detail);
88
89/* Display VU meter */
90static void monitor_level(pjmedia_conf *conf, int slot, int dir, int dur);
91
92
93/* Show usage */
94static void usage(void)
95{
96 puts("");
97 puts(desc);
98}
99
100
101
102/* Input simple string */
103static pj_bool_t input(const char *title, char *buf, pj_size_t len)
104{
105 char *p;
106
107 printf("%s (empty to cancel): ", title); fflush(stdout);
108 if (fgets(buf, (int)len, stdin) == NULL)
109 return PJ_FALSE;
110
111 /* Remove trailing newlines. */
112 for (p=buf; ; ++p) {
113 if (*p=='\r' || *p=='\n') *p='\0';
114 else if (!*p) break;
115 }
116
117 if (!*buf)
118 return PJ_FALSE;
119
120 return PJ_TRUE;
121}
122
123
124/*****************************************************************************
125 * main()
126 */
127int main(int argc, char *argv[])
128{
129 int dev_id = -1;
130 int clock_rate = CLOCK_RATE;
131 int channel_count = NCHANNELS;
132 int samples_per_frame = NSAMPLES;
133 int bits_per_sample = NBITS;
134
135 pj_caching_pool cp;
136 pjmedia_endpt *med_endpt;
137 pj_pool_t *pool;
138 pjmedia_conf *conf;
139
140 int i, port_count, file_count;
141 pjmedia_port **file_port; /* Array of file ports */
142 pjmedia_port *rec_port = NULL; /* Wav writer port */
143
144 char tmp[10];
145 pj_status_t status;
146
147
148 /* Must init PJLIB first: */
149 status = pj_init();
150 PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
151
152 /* Get command line options. */
153 if (get_snd_options(THIS_FILE, argc, argv, &dev_id, &clock_rate,
154 &channel_count, &samples_per_frame, &bits_per_sample))
155 {
156 usage();
157 return 1;
158 }
159
160 /* Must create a pool factory before we can allocate any memory. */
161 pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);
162
163 /*
164 * Initialize media endpoint.
165 * This will implicitly initialize PJMEDIA too.
166 */
167 status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt);
168 PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
169
170 /* Create memory pool to allocate memory */
171 pool = pj_pool_create( &cp.factory, /* pool factory */
172 "wav", /* pool name. */
173 4000, /* init size */
174 4000, /* increment size */
175 NULL /* callback on error */
176 );
177
178
179 file_count = argc - pj_optind;
180 port_count = file_count + 1 + RECORDER;
181
182 /* Create the conference bridge.
183 * With default options (zero), the bridge will create an instance of
184 * sound capture and playback device and connect them to slot zero.
185 */
186 status = pjmedia_conf_create( pool, /* pool to use */
187 port_count,/* number of ports */
188 clock_rate,
189 channel_count,
190 samples_per_frame,
191 bits_per_sample,
192 0, /* options */
193 &conf /* result */
194 );
195 if (status != PJ_SUCCESS) {
196 app_perror(THIS_FILE, "Unable to create conference bridge", status);
197 return 1;
198 }
199
200#if RECORDER
201 status = pjmedia_wav_writer_port_create( pool, "confrecord.wav",
202 clock_rate, channel_count,
203 samples_per_frame,
204 bits_per_sample, 0, 0,
205 &rec_port);
206 if (status != PJ_SUCCESS) {
207 app_perror(THIS_FILE, "Unable to create WAV writer", status);
208 return 1;
209 }
210
211 pjmedia_conf_add_port(conf, pool, rec_port, NULL, NULL);
212#endif
213
214
215 /* Create file ports. */
216 file_port = pj_pool_alloc(pool, file_count * sizeof(pjmedia_port*));
217
218 for (i=0; i<file_count; ++i) {
219
220 /* Load the WAV file to file port. */
221 status = pjmedia_wav_player_port_create(
222 pool, /* pool. */
223 argv[i+pj_optind], /* filename */
224 0, /* use default ptime */
225 0, /* flags */
226 0, /* buf size */
227 &file_port[i] /* result */
228 );
229 if (status != PJ_SUCCESS) {
230 char title[80];
231 pj_ansi_sprintf(title, "Unable to use %s", argv[i+pj_optind]);
232 app_perror(THIS_FILE, title, status);
233 usage();
234 return 1;
235 }
236
237 /* Add the file port to conference bridge */
238 status = pjmedia_conf_add_port( conf, /* The bridge */
239 pool, /* pool */
240 file_port[i], /* port to connect */
241 NULL, /* Use port's name */
242 NULL /* ptr for slot # */
243 );
244 if (status != PJ_SUCCESS) {
245 app_perror(THIS_FILE, "Unable to add conference port", status);
246 return 1;
247 }
248 }
249
250
251 /*
252 * All ports are set up in the conference bridge.
253 * But at this point, no media will be flowing since no ports are
254 * "connected". User must connect the port manually.
255 */
256
257
258 /* Dump memory usage */
259 dump_pool_usage(THIS_FILE, &cp);
260
261 /* Sleep to allow log messages to flush */
262 pj_thread_sleep(100);
263
264
265 /*
266 * UI Menu:
267 */
268 for (;;) {
269 char tmp1[10];
270 char tmp2[10];
271 char *err;
272 int src, dst, level, dur;
273
274 puts("");
275 conf_list(conf, 0);
276 puts("");
277 puts("Menu:");
278 puts(" s Show ports details");
279 puts(" c Connect one port to another");
280 puts(" d Disconnect port connection");
281 puts(" t Adjust signal level transmitted (tx) to a port");
282 puts(" r Adjust signal level received (rx) from a port");
283 puts(" v Display VU meter for a particular port");
284 puts(" q Quit");
285 puts("");
286
287 printf("Enter selection: "); fflush(stdout);
288
289 if (fgets(tmp, sizeof(tmp), stdin) == NULL)
290 break;
291
292 switch (tmp[0]) {
293 case 's':
294 puts("");
295 conf_list(conf, 1);
296 break;
297
298 case 'c':
299 puts("");
300 puts("Connect source port to destination port");
301 if (!input("Enter source port number", tmp1, sizeof(tmp1)) )
302 continue;
303 src = strtol(tmp1, &err, 10);
304 if (*err || src < 0 || src >= port_count) {
305 puts("Invalid slot number");
306 continue;
307 }
308
309 if (!input("Enter destination port number", tmp2, sizeof(tmp2)) )
310 continue;
311 dst = strtol(tmp2, &err, 10);
312 if (*err || dst < 0 || dst >= port_count) {
313 puts("Invalid slot number");
314 continue;
315 }
316
317 status = pjmedia_conf_connect_port(conf, src, dst, 0);
318 if (status != PJ_SUCCESS)
319 app_perror(THIS_FILE, "Error connecting port", status);
320
321 break;
322
323 case 'd':
324 puts("");
325 puts("Disconnect port connection");
326 if (!input("Enter source port number", tmp1, sizeof(tmp1)) )
327 continue;
328 src = strtol(tmp1, &err, 10);
329 if (*err || src < 0 || src >= port_count) {
330 puts("Invalid slot number");
331 continue;
332 }
333
334 if (!input("Enter destination port number", tmp2, sizeof(tmp2)) )
335 continue;
336 dst = strtol(tmp2, &err, 10);
337 if (*err || dst < 0 || dst >= port_count) {
338 puts("Invalid slot number");
339 continue;
340 }
341
342 status = pjmedia_conf_disconnect_port(conf, src, dst);
343 if (status != PJ_SUCCESS)
344 app_perror(THIS_FILE, "Error connecting port", status);
345
346
347 break;
348
349 case 't':
350 puts("");
351 puts("Adjust transmit level of a port");
352 if (!input("Enter port number", tmp1, sizeof(tmp1)) )
353 continue;
354 src = strtol(tmp1, &err, 10);
355 if (*err || src < 0 || src >= port_count) {
356 puts("Invalid slot number");
357 continue;
358 }
359
360 if (!input("Enter level (-128 to >127, 0 for normal)",
361 tmp2, sizeof(tmp2)) )
362 continue;
363 level = strtol(tmp2, &err, 10);
364 if (*err || level < -128) {
365 puts("Invalid level");
366 continue;
367 }
368
369 status = pjmedia_conf_adjust_tx_level( conf, src, level);
370 if (status != PJ_SUCCESS)
371 app_perror(THIS_FILE, "Error adjusting level", status);
372 break;
373
374
375 case 'r':
376 puts("");
377 puts("Adjust receive level of a port");
378 if (!input("Enter port number", tmp1, sizeof(tmp1)) )
379 continue;
380 src = strtol(tmp1, &err, 10);
381 if (*err || src < 0 || src >= port_count) {
382 puts("Invalid slot number");
383 continue;
384 }
385
386 if (!input("Enter level (-128 to >127, 0 for normal)",
387 tmp2, sizeof(tmp2)) )
388 continue;
389 level = strtol(tmp2, &err, 10);
390 if (*err || level < -128) {
391 puts("Invalid level");
392 continue;
393 }
394
395 status = pjmedia_conf_adjust_rx_level( conf, src, level);
396 if (status != PJ_SUCCESS)
397 app_perror(THIS_FILE, "Error adjusting level", status);
398 break;
399
400 case 'v':
401 puts("");
402 puts("Display VU meter");
403 if (!input("Enter port number to monitor", tmp1, sizeof(tmp1)) )
404 continue;
405 src = strtol(tmp1, &err, 10);
406 if (*err || src < 0 || src >= port_count) {
407 puts("Invalid slot number");
408 continue;
409 }
410
411 if (!input("Enter r for rx level or t for tx level", tmp2, sizeof(tmp2)))
412 continue;
413 if (tmp2[0] != 'r' && tmp2[0] != 't') {
414 puts("Invalid option");
415 continue;
416 }
417
418 if (!input("Duration to monitor (in seconds)", tmp1, sizeof(tmp1)) )
419 continue;
420 dur = strtol(tmp1, &err, 10);
421 if (*err) {
422 puts("Invalid duration number");
423 continue;
424 }
425
426 monitor_level(conf, src, tmp2[0], dur);
427 break;
428
429 case 'q':
430 goto on_quit;
431
432 default:
433 printf("Invalid input character '%c'\n", tmp[0]);
434 break;
435 }
436 }
437
438on_quit:
439
440 /* Start deinitialization: */
441
442 /* Destroy conference bridge */
443 status = pjmedia_conf_destroy( conf );
444 PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
445
446
447 /* Destroy file ports */
448 for (i=0; i<file_count; ++i) {
449 status = pjmedia_port_destroy( file_port[i]);
450 PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
451 }
452
453 /* Destroy recorder port */
454 if (rec_port)
455 pjmedia_port_destroy(rec_port);
456
457 /* Release application pool */
458 pj_pool_release( pool );
459
460 /* Destroy media endpoint. */
461 pjmedia_endpt_destroy( med_endpt );
462
463 /* Destroy pool factory */
464 pj_caching_pool_destroy( &cp );
465
466 /* Shutdown PJLIB */
467 pj_shutdown();
468
469 /* Done. */
470 return 0;
471}
472
473
474/*
475 * List the ports in conference bridge
476 */
477static void conf_list(pjmedia_conf *conf, int detail)
478{
479 enum { MAX_PORTS = 32 };
480 unsigned i, count;
481 pjmedia_conf_port_info info[MAX_PORTS];
482
483 printf("Conference ports:\n");
484
485 count = PJ_ARRAY_SIZE(info);
486 pjmedia_conf_get_ports_info(conf, &count, info);
487
488 for (i=0; i<count; ++i) {
489 char txlist[4*MAX_PORTS];
490 unsigned j;
491 pjmedia_conf_port_info *port_info = &info[i];
492
493 txlist[0] = '\0';
494 for (j=0; j<port_info->listener_cnt; ++j) {
495 char s[10];
496 pj_ansi_sprintf(s, "#%d ", port_info->listener_slots[j]);
497 pj_ansi_strcat(txlist, s);
498
499 }
500
501 if (txlist[0] == '\0') {
502 txlist[0] = '-';
503 txlist[1] = '\0';
504 }
505
506 if (!detail) {
507 printf("Port #%02d %-25.*s transmitting to: %s\n",
508 port_info->slot,
509 (int)port_info->name.slen,
510 port_info->name.ptr,
511 txlist);
512 } else {
513 unsigned tx_level, rx_level;
514
515 pjmedia_conf_get_signal_level(conf, port_info->slot,
516 &tx_level, &rx_level);
517
518 printf("Port #%02d:\n"
519 " Name : %.*s\n"
520 " Sampling rate : %d Hz\n"
521 " Samples per frame : %d\n"
522 " Frame time : %d ms\n"
523 " Signal level adjustment : tx=%d, rx=%d\n"
524 " Current signal level : tx=%u, rx=%u\n"
525 " Transmitting to ports : %s\n\n",
526 port_info->slot,
527 (int)port_info->name.slen,
528 port_info->name.ptr,
529 port_info->clock_rate,
530 port_info->samples_per_frame,
531 port_info->samples_per_frame*1000/port_info->clock_rate,
532 port_info->tx_adj_level,
533 port_info->rx_adj_level,
534 tx_level,
535 rx_level,
536 txlist);
537 }
538
539 }
540 puts("");
541}
542
543
544/*
545 * Display VU meter
546 */
547static void monitor_level(pjmedia_conf *conf, int slot, int dir, int dur)
548{
549 enum { SLEEP = 20, SAMP_CNT = 2};
550 pj_status_t status;
551 int i, total_count;
552 unsigned level, samp_cnt;
553
554
555 puts("");
556 printf("Displaying VU meter for port %d for about %d seconds\n",
557 slot, dur);
558
559 total_count = dur * 1000 / SLEEP;
560
561 level = 0;
562 samp_cnt = 0;
563
564 for (i=0; i<total_count; ++i) {
565 unsigned tx_level, rx_level;
566 int j, length;
567 char meter[21];
568
569 /* Poll the volume every 20 msec */
570 status = pjmedia_conf_get_signal_level(conf, slot,
571 &tx_level, &rx_level);
572 if (status != PJ_SUCCESS) {
573 app_perror(THIS_FILE, "Unable to read level", status);
574 return;
575 }
576
577 level += (dir=='r' ? rx_level : tx_level);
578 ++samp_cnt;
579
580 /* Accumulate until we have enough samples */
581 if (samp_cnt < SAMP_CNT) {
582 pj_thread_sleep(SLEEP);
583 continue;
584 }
585
586 /* Get average */
587 level = level / samp_cnt;
588
589 /* Draw bar */
590 length = 20 * level / 255;
591 for (j=0; j<length; ++j)
592 meter[j] = '#';
593 for (; j<20; ++j)
594 meter[j] = ' ';
595 meter[20] = '\0';
596
597 printf("Port #%02d %cx level: [%s] %d \r",
598 slot, dir, meter, level);
599
600 /* Next.. */
601 samp_cnt = 0;
602 level = 0;
603
604 pj_thread_sleep(SLEEP);
605 }
606
607 puts("");
608}
609