BLOG | DOCUMENTATION | TRAC

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 /* $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 
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 
50 static 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 */
87 static void conf_list(pjmedia_conf *conf, pj_bool_t detail);
88 
89 /* Display VU meter */
90 static void monitor_level(pjmedia_conf *conf, int slot, int dir, int dur);
91 
92 
93 /* Show usage */
94 static void usage(void)
95 {
96  puts("");
97  puts(desc);
98 }
99 
100 
101 
102 /* Input simple string */
103 static 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  */
127 int 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. */
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. */
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 
438 on_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 */
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  */
477 static 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  */
547 static 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 
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)
PJMEDIA main header file.
pj_ssize_t slen
#define PJ_ASSERT_RETURN(expr, retval)
char * ptr
void pj_pool_release(pj_pool_t *pool)
int pj_bool_t
pj_status_t pjmedia_port_destroy(pjmedia_port *port)
struct pjmedia_endpt pjmedia_endpt
Definition: types.h:187
Definition: conference.h:73
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)
int pj_status_t
unsigned slot
Definition: conference.h:75
pj_pool_factory_policy pj_pool_factory_default_policy
void * pj_pool_alloc(pj_pool_t *pool, pj_size_t size)
pj_status_t pjmedia_conf_disconnect_port(pjmedia_conf *conf, unsigned src_slot, unsigned sink_slot)
unsigned * listener_slots
Definition: conference.h:81
Definition: port.h:377
pj_str_t info
pj_pool_factory factory
PJ_TRUE
pj_status_t pjmedia_conf_connect_port(pjmedia_conf *conf, unsigned src_slot, unsigned sink_slot, int adj_level)
pj_status_t pjmedia_endpt_create(pj_pool_factory *pf, pj_ioqueue_t *ioqueue, unsigned worker_cnt, pjmedia_endpt **p_endpt)
Definition: endpoint.h:128
pj_status_t pjmedia_conf_get_ports_info(pjmedia_conf *conf, unsigned *size, pjmedia_conf_port_info info[])
pj_str_t name
Definition: conference.h:76
unsigned listener_cnt
Definition: conference.h:80
struct pjmedia_conf pjmedia_conf
Definition: conference.h:68
void pj_caching_pool_init(pj_caching_pool *ch_pool, const pj_pool_factory_policy *policy, pj_size_t max_capacity)
pj_status_t pjmedia_conf_adjust_rx_level(pjmedia_conf *conf, unsigned slot, int adj_level)
unsigned clock_rate
Definition: conference.h:85
unsigned samples_per_frame
Definition: conference.h:87
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_destroy(pjmedia_conf *conf)
int rx_adj_level
Definition: conference.h:90
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)
#define PJ_ARRAY_SIZE(a)
int tx_adj_level
Definition: conference.h:89
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_shutdown(void)
pj_status_t pj_thread_sleep(unsigned msec)
pj_status_t pjmedia_conf_get_signal_level(pjmedia_conf *conf, unsigned slot, unsigned *tx_level, unsigned *rx_level)
PJ_SUCCESS
pj_status_t pj_init(void)
pj_status_t pjmedia_endpt_destroy(pjmedia_endpt *endpt)
Definition: endpoint.h:169
size_t pj_size_t
PJ_FALSE
void pj_caching_pool_destroy(pj_caching_pool *ch_pool)

 


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