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

00001 /* $Id: confsample.c 3553 2011-05-05 06:14:19Z nanang $ */
00002 /* 
00003  * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
00004  * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
00005  *
00006  * This program is free software; you can redistribute it and/or modify
00007  * it under the terms of the GNU General Public License as published by
00008  * the Free Software Foundation; either version 2 of the License, or
00009  * (at your option) any later version.
00010  *
00011  * This program is distributed in the hope that it will be useful,
00012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00014  * GNU General Public License for more details.
00015  *
00016  * You should have received a copy of the GNU General Public License
00017  * along with this program; if not, write to the Free Software
00018  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
00019  */
00020 
00021 #include <pjmedia.h>
00022 #include <pjlib-util.h> /* pj_getopt */
00023 #include <pjlib.h>
00024 
00025 #include <stdlib.h>     /* atoi() */
00026 #include <stdio.h>
00027 
00028 #include "util.h"
00029 
00042 /* For logging purpose. */
00043 #define THIS_FILE   "confsample.c"
00044 
00045 
00046 /* Shall we put recorder in the conference */
00047 #define RECORDER    1
00048 
00049 
00050 static const char *desc = 
00051  " FILE:                                                                    \n"
00052  "                                                                          \n"
00053  "  confsample.c                                                            \n"
00054  "                                                                          \n"
00055  " PURPOSE:                                                                 \n"
00056  "                                                                          \n"
00057  "  Demonstrate how to use conference bridge.                               \n"
00058  "                                                                          \n"
00059  " USAGE:                                                                   \n"
00060  "                                                                          \n"
00061  "  confsample [options] [file1.wav] [file2.wav] ...                        \n"
00062  "                                                                          \n"
00063  " options:                                                                 \n"
00064  SND_USAGE
00065  "                                                                          \n"
00066  "  fileN.wav are optional WAV files to be connected to the conference      \n"
00067  "  bridge. The WAV files MUST have single channel (mono) and 16 bit PCM    \n"
00068  "  samples. It can have arbitrary sampling rate.                           \n"
00069  "                                                                          \n"
00070  " DESCRIPTION:                                                             \n"
00071  "                                                                          \n"
00072  "  Here we create a conference bridge, with at least one port (port zero   \n"
00073  "  is always created for the sound device).                                \n"
00074  "                                                                          \n"
00075  "  If WAV files are specified, the WAV file player ports will be connected \n"
00076  "  to slot starting from number one in the bridge. The WAV files can have  \n"
00077  "  arbitrary sampling rate; the bridge will convert it to its clock rate.  \n"
00078  "  However, the files MUST have a single audio channel only (i.e. mono).  \n";
00079 
00080 
00081  
00082 /* 
00083  * Prototypes: 
00084  */
00085 
00086 /* List the ports in the conference bridge */
00087 static void conf_list(pjmedia_conf *conf, pj_bool_t detail);
00088 
00089 /* Display VU meter */
00090 static void monitor_level(pjmedia_conf *conf, int slot, int dir, int dur);
00091 
00092 
00093 /* Show usage */
00094 static void usage(void)
00095 {
00096     puts("");
00097     puts(desc);
00098 }
00099 
00100 
00101 
00102 /* Input simple string */
00103 static pj_bool_t input(const char *title, char *buf, pj_size_t len)
00104 {
00105     char *p;
00106 
00107     printf("%s (empty to cancel): ", title); fflush(stdout);
00108     if (fgets(buf, len, stdin) == NULL)
00109         return PJ_FALSE;
00110 
00111     /* Remove trailing newlines. */
00112     for (p=buf; ; ++p) {
00113         if (*p=='\r' || *p=='\n') *p='\0';
00114         else if (!*p) break;
00115     }
00116 
00117     if (!*buf)
00118         return PJ_FALSE;
00119     
00120     return PJ_TRUE;
00121 }
00122 
00123 
00124 /*****************************************************************************
00125  * main()
00126  */
00127 int main(int argc, char *argv[])
00128 {
00129     int dev_id = -1;
00130     int clock_rate = CLOCK_RATE;
00131     int channel_count = NCHANNELS;
00132     int samples_per_frame = NSAMPLES;
00133     int bits_per_sample = NBITS;
00134 
00135     pj_caching_pool cp;
00136     pjmedia_endpt *med_endpt;
00137     pj_pool_t *pool;
00138     pjmedia_conf *conf;
00139 
00140     int i, port_count, file_count;
00141     pjmedia_port **file_port;   /* Array of file ports */
00142     pjmedia_port *rec_port = NULL;  /* Wav writer port */
00143 
00144     char tmp[10];
00145     pj_status_t status;
00146 
00147 
00148     /* Must init PJLIB first: */
00149     status = pj_init();
00150     PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
00151 
00152     /* Get command line options. */
00153     if (get_snd_options(THIS_FILE, argc, argv, &dev_id, &clock_rate,
00154                         &channel_count, &samples_per_frame, &bits_per_sample))
00155     {
00156         usage();
00157         return 1;
00158     }
00159 
00160     /* Must create a pool factory before we can allocate any memory. */
00161     pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);
00162 
00163     /* 
00164      * Initialize media endpoint.
00165      * This will implicitly initialize PJMEDIA too.
00166      */
00167     status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt);
00168     PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
00169 
00170     /* Create memory pool to allocate memory */
00171     pool = pj_pool_create( &cp.factory,     /* pool factory         */
00172                            "wav",           /* pool name.           */
00173                            4000,            /* init size            */
00174                            4000,            /* increment size       */
00175                            NULL             /* callback on error    */
00176                            );
00177 
00178 
00179     file_count = argc - pj_optind;
00180     port_count = file_count + 1 + RECORDER;
00181 
00182     /* Create the conference bridge. 
00183      * With default options (zero), the bridge will create an instance of
00184      * sound capture and playback device and connect them to slot zero.
00185      */
00186     status = pjmedia_conf_create( pool,     /* pool to use          */
00187                                   port_count,/* number of ports     */
00188                                   clock_rate,
00189                                   channel_count,
00190                                   samples_per_frame,
00191                                   bits_per_sample,
00192                                   0,        /* options              */
00193                                   &conf     /* result               */
00194                                   );
00195     if (status != PJ_SUCCESS) {
00196         app_perror(THIS_FILE, "Unable to create conference bridge", status);
00197         return 1;
00198     }
00199 
00200 #if RECORDER
00201     status = pjmedia_wav_writer_port_create(  pool, "confrecord.wav",
00202                                               clock_rate, channel_count,
00203                                               samples_per_frame, 
00204                                               bits_per_sample, 0, 0, 
00205                                               &rec_port);
00206     if (status != PJ_SUCCESS) {
00207         app_perror(THIS_FILE, "Unable to create WAV writer", status);
00208         return 1;
00209     }
00210 
00211     pjmedia_conf_add_port(conf, pool, rec_port, NULL, NULL);
00212 #endif
00213 
00214 
00215     /* Create file ports. */
00216     file_port = pj_pool_alloc(pool, file_count * sizeof(pjmedia_port*));
00217 
00218     for (i=0; i<file_count; ++i) {
00219 
00220         /* Load the WAV file to file port. */
00221         status = pjmedia_wav_player_port_create( 
00222                         pool,               /* pool.        */
00223                         argv[i+pj_optind],  /* filename     */
00224                         0,                  /* use default ptime */
00225                         0,                  /* flags        */
00226                         0,                  /* buf size     */
00227                         &file_port[i]       /* result       */
00228                         );
00229         if (status != PJ_SUCCESS) {
00230             char title[80];
00231             pj_ansi_sprintf(title, "Unable to use %s", argv[i+pj_optind]);
00232             app_perror(THIS_FILE, title, status);
00233             usage();
00234             return 1;
00235         }
00236 
00237         /* Add the file port to conference bridge */
00238         status = pjmedia_conf_add_port( conf,           /* The bridge       */
00239                                         pool,           /* pool             */
00240                                         file_port[i],   /* port to connect  */
00241                                         NULL,           /* Use port's name  */
00242                                         NULL            /* ptr for slot #   */
00243                                         );
00244         if (status != PJ_SUCCESS) {
00245             app_perror(THIS_FILE, "Unable to add conference port", status);
00246             return 1;
00247         }
00248     }
00249 
00250 
00251     /* 
00252      * All ports are set up in the conference bridge.
00253      * But at this point, no media will be flowing since no ports are
00254      * "connected". User must connect the port manually.
00255      */
00256 
00257 
00258     /* Dump memory usage */
00259     dump_pool_usage(THIS_FILE, &cp);
00260 
00261     /* Sleep to allow log messages to flush */
00262     pj_thread_sleep(100);
00263 
00264 
00265     /*
00266      * UI Menu: 
00267      */
00268     for (;;) {
00269         char tmp1[10];
00270         char tmp2[10];
00271         char *err;
00272         int src, dst, level, dur;
00273 
00274         puts("");
00275         conf_list(conf, 0);
00276         puts("");
00277         puts("Menu:");
00278         puts("  s    Show ports details");
00279         puts("  c    Connect one port to another");
00280         puts("  d    Disconnect port connection");
00281         puts("  t    Adjust signal level transmitted (tx) to a port");
00282         puts("  r    Adjust signal level received (rx) from a port");
00283         puts("  v    Display VU meter for a particular port");
00284         puts("  q    Quit");
00285         puts("");
00286         
00287         printf("Enter selection: "); fflush(stdout);
00288 
00289         if (fgets(tmp, sizeof(tmp), stdin) == NULL)
00290             break;
00291 
00292         switch (tmp[0]) {
00293         case 's':
00294             puts("");
00295             conf_list(conf, 1);
00296             break;
00297 
00298         case 'c':
00299             puts("");
00300             puts("Connect source port to destination port");
00301             if (!input("Enter source port number", tmp1, sizeof(tmp1)) )
00302                 continue;
00303             src = strtol(tmp1, &err, 10);
00304             if (*err || src < 0 || src >= port_count) {
00305                 puts("Invalid slot number");
00306                 continue;
00307             }
00308 
00309             if (!input("Enter destination port number", tmp2, sizeof(tmp2)) )
00310                 continue;
00311             dst = strtol(tmp2, &err, 10);
00312             if (*err || dst < 0 || dst >= port_count) {
00313                 puts("Invalid slot number");
00314                 continue;
00315             }
00316 
00317             status = pjmedia_conf_connect_port(conf, src, dst, 0);
00318             if (status != PJ_SUCCESS)
00319                 app_perror(THIS_FILE, "Error connecting port", status);
00320             
00321             break;
00322 
00323         case 'd':
00324             puts("");
00325             puts("Disconnect port connection");
00326             if (!input("Enter source port number", tmp1, sizeof(tmp1)) )
00327                 continue;
00328             src = strtol(tmp1, &err, 10);
00329             if (*err || src < 0 || src >= port_count) {
00330                 puts("Invalid slot number");
00331                 continue;
00332             }
00333 
00334             if (!input("Enter destination port number", tmp2, sizeof(tmp2)) )
00335                 continue;
00336             dst = strtol(tmp2, &err, 10);
00337             if (*err || dst < 0 || dst >= port_count) {
00338                 puts("Invalid slot number");
00339                 continue;
00340             }
00341 
00342             status = pjmedia_conf_disconnect_port(conf, src, dst);
00343             if (status != PJ_SUCCESS)
00344                 app_perror(THIS_FILE, "Error connecting port", status);
00345             
00346 
00347             break;
00348 
00349         case 't':
00350             puts("");
00351             puts("Adjust transmit level of a port");
00352             if (!input("Enter port number", tmp1, sizeof(tmp1)) )
00353                 continue;
00354             src = strtol(tmp1, &err, 10);
00355             if (*err || src < 0 || src >= port_count) {
00356                 puts("Invalid slot number");
00357                 continue;
00358             }
00359 
00360             if (!input("Enter level (-128 to >127, 0 for normal)", 
00361                               tmp2, sizeof(tmp2)) )
00362                 continue;
00363             level = strtol(tmp2, &err, 10);
00364             if (*err || level < -128) {
00365                 puts("Invalid level");
00366                 continue;
00367             }
00368 
00369             status = pjmedia_conf_adjust_tx_level( conf, src, level);
00370             if (status != PJ_SUCCESS)
00371                 app_perror(THIS_FILE, "Error adjusting level", status);
00372             break;
00373 
00374 
00375         case 'r':
00376             puts("");
00377             puts("Adjust receive level of a port");
00378             if (!input("Enter port number", tmp1, sizeof(tmp1)) )
00379                 continue;
00380             src = strtol(tmp1, &err, 10);
00381             if (*err || src < 0 || src >= port_count) {
00382                 puts("Invalid slot number");
00383                 continue;
00384             }
00385 
00386             if (!input("Enter level (-128 to >127, 0 for normal)", 
00387                               tmp2, sizeof(tmp2)) )
00388                 continue;
00389             level = strtol(tmp2, &err, 10);
00390             if (*err || level < -128) {
00391                 puts("Invalid level");
00392                 continue;
00393             }
00394 
00395             status = pjmedia_conf_adjust_rx_level( conf, src, level);
00396             if (status != PJ_SUCCESS)
00397                 app_perror(THIS_FILE, "Error adjusting level", status);
00398             break;
00399 
00400         case 'v':
00401             puts("");
00402             puts("Display VU meter");
00403             if (!input("Enter port number to monitor", tmp1, sizeof(tmp1)) )
00404                 continue;
00405             src = strtol(tmp1, &err, 10);
00406             if (*err || src < 0 || src >= port_count) {
00407                 puts("Invalid slot number");
00408                 continue;
00409             }
00410 
00411             if (!input("Enter r for rx level or t for tx level", tmp2, sizeof(tmp2)))
00412                 continue;
00413             if (tmp2[0] != 'r' && tmp2[0] != 't') {
00414                 puts("Invalid option");
00415                 continue;
00416             }
00417 
00418             if (!input("Duration to monitor (in seconds)", tmp1, sizeof(tmp1)) )
00419                 continue;
00420             dur = strtol(tmp1, &err, 10);
00421             if (*err) {
00422                 puts("Invalid duration number");
00423                 continue;
00424             }
00425 
00426             monitor_level(conf, src, tmp2[0], dur);
00427             break;
00428 
00429         case 'q':
00430             goto on_quit;
00431 
00432         default:
00433             printf("Invalid input character '%c'\n", tmp[0]);
00434             break;
00435         }
00436     }
00437 
00438 on_quit:
00439     
00440     /* Start deinitialization: */
00441 
00442     /* Destroy conference bridge */
00443     status = pjmedia_conf_destroy( conf );
00444     PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
00445 
00446 
00447     /* Destroy file ports */
00448     for (i=0; i<file_count; ++i) {
00449         status = pjmedia_port_destroy( file_port[i]);
00450         PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
00451     }
00452 
00453     /* Destroy recorder port */
00454     if (rec_port)
00455         pjmedia_port_destroy(rec_port);
00456 
00457     /* Release application pool */
00458     pj_pool_release( pool );
00459 
00460     /* Destroy media endpoint. */
00461     pjmedia_endpt_destroy( med_endpt );
00462 
00463     /* Destroy pool factory */
00464     pj_caching_pool_destroy( &cp );
00465 
00466     /* Shutdown PJLIB */
00467     pj_shutdown();
00468 
00469     /* Done. */
00470     return 0;
00471 }
00472 
00473 
00474 /*
00475  * List the ports in conference bridge
00476  */
00477 static void conf_list(pjmedia_conf *conf, int detail)
00478 {
00479     enum { MAX_PORTS = 32 };
00480     unsigned i, count;
00481     pjmedia_conf_port_info info[MAX_PORTS];
00482 
00483     printf("Conference ports:\n");
00484 
00485     count = PJ_ARRAY_SIZE(info);
00486     pjmedia_conf_get_ports_info(conf, &count, info);
00487 
00488     for (i=0; i<count; ++i) {
00489         char txlist[4*MAX_PORTS];
00490         unsigned j;
00491         pjmedia_conf_port_info *port_info = &info[i];   
00492         
00493         txlist[0] = '\0';
00494         for (j=0; j<port_info->listener_cnt; ++j) {
00495             char s[10];
00496             pj_ansi_sprintf(s, "#%d ", port_info->listener_slots[j]);
00497             pj_ansi_strcat(txlist, s);
00498 
00499         }
00500 
00501         if (txlist[0] == '\0') {
00502             txlist[0] = '-';
00503             txlist[1] = '\0';
00504         }
00505 
00506         if (!detail) {
00507             printf("Port #%02d %-25.*s  transmitting to: %s\n", 
00508                    port_info->slot, 
00509                    (int)port_info->name.slen, 
00510                    port_info->name.ptr,
00511                    txlist);
00512         } else {
00513             unsigned tx_level, rx_level;
00514 
00515             pjmedia_conf_get_signal_level(conf, port_info->slot,
00516                                           &tx_level, &rx_level);
00517 
00518             printf("Port #%02d:\n"
00519                    "  Name                    : %.*s\n"
00520                    "  Sampling rate           : %d Hz\n"
00521                    "  Samples per frame       : %d\n"
00522                    "  Frame time              : %d ms\n"
00523                    "  Signal level adjustment : tx=%d, rx=%d\n"
00524                    "  Current signal level    : tx=%u, rx=%u\n"
00525                    "  Transmitting to ports   : %s\n\n",
00526                    port_info->slot,
00527                    (int)port_info->name.slen,
00528                    port_info->name.ptr,
00529                    port_info->clock_rate,
00530                    port_info->samples_per_frame,
00531                    port_info->samples_per_frame*1000/port_info->clock_rate,
00532                    port_info->tx_adj_level,
00533                    port_info->rx_adj_level,
00534                    tx_level,
00535                    rx_level,
00536                    txlist);
00537         }
00538 
00539     }
00540     puts("");
00541 }
00542 
00543 
00544 /*
00545  * Display VU meter
00546  */
00547 static void monitor_level(pjmedia_conf *conf, int slot, int dir, int dur)
00548 {
00549     enum { SLEEP = 20, SAMP_CNT = 2};
00550     pj_status_t status;
00551     int i, total_count;
00552     unsigned level, samp_cnt;
00553 
00554 
00555     puts("");
00556     printf("Displaying VU meter for port %d for about %d seconds\n",
00557            slot, dur);
00558 
00559     total_count = dur * 1000 / SLEEP;
00560 
00561     level = 0;
00562     samp_cnt = 0;
00563 
00564     for (i=0; i<total_count; ++i) {
00565         unsigned tx_level, rx_level;
00566         int j, length;
00567         char meter[21];
00568 
00569         /* Poll the volume every 20 msec */
00570         status = pjmedia_conf_get_signal_level(conf, slot, 
00571                                                &tx_level, &rx_level);
00572         if (status != PJ_SUCCESS) {
00573             app_perror(THIS_FILE, "Unable to read level", status);
00574             return;
00575         }
00576 
00577         level += (dir=='r' ? rx_level : tx_level);
00578         ++samp_cnt;
00579 
00580         /* Accumulate until we have enough samples */
00581         if (samp_cnt < SAMP_CNT) {
00582             pj_thread_sleep(SLEEP);
00583             continue;
00584         }
00585 
00586         /* Get average */
00587         level = level / samp_cnt;
00588 
00589         /* Draw bar */
00590         length = 20 * level / 255;
00591         for (j=0; j<length; ++j)
00592             meter[j] = '#';
00593         for (; j<20; ++j)
00594             meter[j] = ' ';
00595         meter[20] = '\0';
00596 
00597         printf("Port #%02d %cx level: [%s] %d  \r",
00598                slot, dir, meter, level);
00599 
00600         /* Next.. */
00601         samp_cnt = 0;
00602         level = 0;
00603 
00604         pj_thread_sleep(SLEEP);
00605     }
00606 
00607     puts("");
00608 }
00609 

 


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