confsample.c 19 KB

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