WARNING: The online documentation has moved to https://docs.pjsip.org.

Visit the new documentation at https://docs.pjsip.org:

BLOG | DOCUMENTATION | GITHUB

Home --> Documentations --> PJMEDIA Reference

Samples: Using Conference Bridge

Sample to mix multiple files in the conference bridge and play the result to sound device.

This file is pjsip-apps/src/samples/confsample.c

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

 


PJMEDIA small footprint Open Source media stack
Copyright (C) 2006-2008 Teluu Inc.