blob: e4cbba5f0dc4255479fb9becb685e8f6b55135a3 [file] [log] [blame]
Benny Prijonoe3ebd552009-02-18 20:14:15 +00001/* $Id$ */
2/*
3 * Copyright (C) 2008-2009 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#include <pjmedia-audiodev/audiodev.h>
21#include <pjmedia-audiodev/audiotest.h>
22#include <pjmedia.h>
23#include <pjlib.h>
24#include <pjlib-util.h>
25
26#define THIS_FILE "auddemo.c"
27#define MAX_DEVICES 64
Benny Prijono555139d2009-02-19 12:08:19 +000028#define WAV_FILE "auddemo.wav"
Benny Prijonoe3ebd552009-02-18 20:14:15 +000029
30
31static unsigned dev_count;
32static pjmedia_aud_dev_id dev_id[MAX_DEVICES];
33
34static void app_perror(const char *title, pj_status_t status)
35{
36 char errmsg[PJ_ERR_MSG_SIZE];
37
38 pj_strerror(status, errmsg, sizeof(errmsg));
39 printf( "%s: %s (err=%d)\n",
40 title, errmsg, status);
41}
42
43static void list_devices(void)
44{
45 unsigned i;
46 pj_status_t status;
47
48 dev_count = pjmedia_aud_dev_count();
49 if (dev_count == 0) {
50 PJ_LOG(3,(THIS_FILE, "No devices found"));
51 return;
52 }
53
54 PJ_LOG(3,(THIS_FILE, "Found %d devices:", dev_count));
55
56 dev_count = pjmedia_aud_dev_enum(PJ_ARRAY_SIZE(dev_id), dev_id);
57
58 for (i=0; i<dev_count; ++i) {
59 pjmedia_aud_dev_info info;
60
61 status = pjmedia_aud_dev_get_info(dev_id[i], &info);
62 if (status != PJ_SUCCESS)
63 continue;
64
65 PJ_LOG(3,(THIS_FILE," %2d: %s [%s] (%d/%d)",
66 i, info.driver, info.name, info.input_count, info.output_count));
67 }
68}
69
70static const char *decode_caps(unsigned caps)
71{
72 static char text[200];
73
74 text[0] = '\0';
75
76 if (caps & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT) {
77 strcat(text, "extfmt ");
78 }
79
80 if (caps & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY) {
81 strcat(text, "inlatency ");
82 }
83
84 if (caps & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY) {
85 strcat(text, "outlatency ");
86 }
87
88 if (caps & PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING) {
89 strcat(text, "invol ");
90 }
91
92 if (caps & PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) {
93 strcat(text, "outvol ");
94 }
95
96 if (caps & PJMEDIA_AUD_DEV_CAP_INPUT_SIGNAL_VOLUME) {
97 strcat(text, "insignal ");
98 }
99
100 if (caps & PJMEDIA_AUD_DEV_CAP_OUTPUT_SIGNAL_VOLUME) {
101 strcat(text, "outsignal ");
102 }
103
104 if (caps & PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE) {
105 strcat(text, "inroute ");
106 }
107
108 if (caps & PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE) {
109 strcat(text, "outroute ");
110 }
111
112 if (caps & PJMEDIA_AUD_DEV_CAP_EC) {
113 strcat(text, "ec ");
114 }
115
116 if (caps & PJMEDIA_AUD_DEV_CAP_EC_TAIL) {
117 strcat(text, "ectail ");
118 }
119
120 if (caps & PJMEDIA_AUD_DEV_CAP_VAD) {
121 strcat(text, "vad ");
122 }
123
124 if (caps & PJMEDIA_AUD_DEV_CAP_CNG) {
125 strcat(text, "cng ");
126 }
127
128 if (caps & PJMEDIA_AUD_DEV_CAP_PLC) {
129 strcat(text, "plc ");
130 }
131
132 return text;
133}
134
135static void show_dev_info(unsigned index)
136{
137#define H "%-20s"
138 pjmedia_aud_dev_info info;
139 char formats[200];
140 pj_status_t status;
141
142 if (index >= dev_count) {
143 PJ_LOG(1,(THIS_FILE, "Error: invalid index %u", index));
144 return;
145 }
146
147 status = pjmedia_aud_dev_get_info(dev_id[index], &info);
148 if (status != PJ_SUCCESS) {
149 app_perror("pjmedia_aud_dev_get_info() error", status);
150 return;
151 }
152
153 PJ_LOG(3, (THIS_FILE, "Device at index %u:", index));
154 PJ_LOG(3, (THIS_FILE, "-------------------------"));
155
156 PJ_LOG(3, (THIS_FILE, H": %u (0x%x)", "ID", dev_id[index], dev_id[index]));
157 PJ_LOG(3, (THIS_FILE, H": %s", "Name", info.name));
158 PJ_LOG(3, (THIS_FILE, H": %s", "Driver", info.driver));
159 PJ_LOG(3, (THIS_FILE, H": %u", "Input channels", info.input_count));
160 PJ_LOG(3, (THIS_FILE, H": %u", "Output channels", info.output_count));
161 PJ_LOG(3, (THIS_FILE, H": %s", "Capabilities", decode_caps(info.caps)));
162
163 formats[0] = '\0';
164 if (info.caps & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT) {
165 unsigned i;
166
167 for (i=0; i<info.ext_fmt_cnt; ++i) {
168 char bitrate[32];
169
Benny Prijono555139d2009-02-19 12:08:19 +0000170 switch (info.ext_fmt[i].id) {
Benny Prijonoe3ebd552009-02-18 20:14:15 +0000171 case PJMEDIA_FORMAT_L16:
172 strcat(formats, "L16/");
173 break;
174 case PJMEDIA_FORMAT_PCMA:
175 strcat(formats, "PCMA/");
176 break;
177 case PJMEDIA_FORMAT_PCMU:
178 strcat(formats, "PCMU/");
179 break;
180 case PJMEDIA_FORMAT_AMR:
181 strcat(formats, "AMR/");
182 break;
183 case PJMEDIA_FORMAT_G729:
184 strcat(formats, "G729/");
185 break;
186 case PJMEDIA_FORMAT_ILBC:
187 strcat(formats, "ILBC/");
188 break;
189 default:
190 strcat(formats, "unknown/");
191 break;
192 }
193 sprintf(bitrate, "%u", info.ext_fmt[i].bitrate);
194 strcat(formats, bitrate);
195 strcat(formats, " ");
196 }
197 }
198 PJ_LOG(3, (THIS_FILE, H": %s", "Extended formats", formats));
199
200#undef H
201}
202
203static void test_device(pjmedia_dir dir, unsigned rec_id, unsigned play_id,
204 unsigned clock_rate, unsigned ptime,
205 unsigned chnum)
206{
207 pjmedia_aud_dev_param param;
208 pjmedia_aud_test_results result;
209 pj_status_t status;
210
211 if (dir & PJMEDIA_DIR_CAPTURE) {
212 status = pjmedia_aud_dev_default_param(dev_id[rec_id], &param);
213 } else {
214 status = pjmedia_aud_dev_default_param(dev_id[play_id], &param);
215 }
216
217 if (status != PJ_SUCCESS) {
218 app_perror("pjmedia_aud_dev_default_param()", status);
219 return;
220 }
221
222 param.dir = dir;
223 param.rec_id = dev_id[rec_id];
224 param.play_id = dev_id[play_id];
225 param.clock_rate = clock_rate;
226 param.channel_count = chnum;
227 param.samples_per_frame = clock_rate * chnum * ptime / 1000;
228
229 PJ_LOG(3,(THIS_FILE, "Performing test.."));
230
231 status = pjmedia_aud_test(&param, &result);
232 if (status != PJ_SUCCESS) {
233 app_perror("Test has completed with error", status);
234 return;
235 }
236
237 PJ_LOG(3,(THIS_FILE, "Done. Result:"));
238
239 if (dir & PJMEDIA_DIR_CAPTURE) {
240 if (result.rec.frame_cnt==0) {
241 PJ_LOG(1,(THIS_FILE, "Error: no frames captured!"));
242 } else {
243 PJ_LOG(3,(THIS_FILE, " %-20s: max interval=%u, burst=%u",
244 "Recording result",
245 result.rec.max_interval,
246 result.rec.max_burst));
247 }
248 }
249
250 if (dir & PJMEDIA_DIR_PLAYBACK) {
251 if (result.play.frame_cnt==0) {
252 PJ_LOG(1,(THIS_FILE, "Error: no playback!"));
253 } else {
254 PJ_LOG(3,(THIS_FILE, " %-20s: max interval=%u, burst=%u",
255 "Playback result",
256 result.play.max_interval,
257 result.play.max_burst));
258 }
259 }
260
261 if (dir==PJMEDIA_DIR_CAPTURE_PLAYBACK) {
262 if (result.rec_drift_per_sec) {
263 PJ_LOG(3,(THIS_FILE, " No clock drift detected"));
264 } else {
265 const char *which = result.rec_drift_per_sec>=0 ? "faster" : "slower";
266 unsigned drift = result.rec_drift_per_sec>=0 ?
267 result.rec_drift_per_sec :
268 -result.rec_drift_per_sec;
269
270 PJ_LOG(3,(THIS_FILE, " Clock drifts detected. Capture device "
271 "is running %d samples per second %s "
272 "than the playback device",
273 drift, which));
274 }
275 }
276}
277
278
Benny Prijono555139d2009-02-19 12:08:19 +0000279static pj_status_t wav_rec_cb(void *user_data, pjmedia_frame *frame)
280{
281 return pjmedia_port_put_frame((pjmedia_port*)user_data, frame);
282}
283
284static void record(unsigned rec_index, const char *filename)
285{
286 pj_pool_t *pool = NULL;
287 pjmedia_port *wav = NULL;
288 pjmedia_aud_dev_param param;
289 pjmedia_aud_stream *strm = NULL;
290 char line[10];
291 pj_status_t status;
292
293 if (filename == NULL)
294 filename = WAV_FILE;
295
296 pool = pj_pool_create(pjmedia_aud_subsys_get_pool_factory(), "wav",
297 1000, 1000, NULL);
298
299 status = pjmedia_wav_writer_port_create(pool, filename, 16000,
300 1, 320, 16, 0, 0, &wav);
301 if (status != PJ_SUCCESS) {
302 app_perror("Error creating WAV file", status);
303 goto on_return;
304 }
305
306 status = pjmedia_aud_dev_default_param(dev_id[rec_index], &param);
307 if (status != PJ_SUCCESS) {
308 app_perror("pjmedia_aud_dev_default_param()", status);
309 goto on_return;
310 }
311
312 param.dir = PJMEDIA_DIR_CAPTURE;
313 param.clock_rate = wav->info.clock_rate;
314 param.samples_per_frame = wav->info.samples_per_frame;
315 param.channel_count = wav->info.channel_count;
316 param.bits_per_sample = wav->info.bits_per_sample;
317
318 status = pjmedia_aud_stream_create(&param, &wav_rec_cb, NULL, wav,
319 &strm);
320 if (status != PJ_SUCCESS) {
321 app_perror("Error opening the sound device", status);
322 goto on_return;
323 }
324
325 status = pjmedia_aud_stream_start(strm);
326 if (status != PJ_SUCCESS) {
327 app_perror("Error starting the sound device", status);
328 goto on_return;
329 }
330
331 PJ_LOG(3,(THIS_FILE, "Recording started, press ENTER to stop"));
332 fgets(line, sizeof(line), stdin);
333
334on_return:
335 if (strm) {
336 pjmedia_aud_stream_stop(strm);
337 pjmedia_aud_stream_destroy(strm);
338 }
339 if (wav)
340 pjmedia_port_destroy(wav);
341 if (pool)
342 pj_pool_release(pool);
343}
344
345
346static pj_status_t wav_play_cb(void *user_data, pjmedia_frame *frame)
347{
348 return pjmedia_port_get_frame((pjmedia_port*)user_data, frame);
349}
350
351
352static void play_file(unsigned play_index, const char *filename)
353{
354 pj_pool_t *pool = NULL;
355 pjmedia_port *wav = NULL;
356 pjmedia_aud_dev_param param;
357 pjmedia_aud_stream *strm = NULL;
358 char line[10];
359 pj_status_t status;
360
361 if (filename == NULL)
362 filename = WAV_FILE;
363
364 pool = pj_pool_create(pjmedia_aud_subsys_get_pool_factory(), "wav",
365 1000, 1000, NULL);
366
367 status = pjmedia_wav_player_port_create(pool, filename, 20, 0, 0, &wav);
368 if (status != PJ_SUCCESS) {
369 app_perror("Error opening WAV file", status);
370 goto on_return;
371 }
372
373 status = pjmedia_aud_dev_default_param(dev_id[play_index], &param);
374 if (status != PJ_SUCCESS) {
375 app_perror("pjmedia_aud_dev_default_param()", status);
376 goto on_return;
377 }
378
379 param.dir = PJMEDIA_DIR_PLAYBACK;
380 param.clock_rate = wav->info.clock_rate;
381 param.samples_per_frame = wav->info.samples_per_frame;
382 param.channel_count = wav->info.channel_count;
383 param.bits_per_sample = wav->info.bits_per_sample;
384
385 status = pjmedia_aud_stream_create(&param, NULL, &wav_play_cb, wav,
386 &strm);
387 if (status != PJ_SUCCESS) {
388 app_perror("Error opening the sound device", status);
389 goto on_return;
390 }
391
392 status = pjmedia_aud_stream_start(strm);
393 if (status != PJ_SUCCESS) {
394 app_perror("Error starting the sound device", status);
395 goto on_return;
396 }
397
398 PJ_LOG(3,(THIS_FILE, "Playback started, press ENTER to stop"));
399 fgets(line, sizeof(line), stdin);
400
401on_return:
402 if (strm) {
403 pjmedia_aud_stream_stop(strm);
404 pjmedia_aud_stream_destroy(strm);
405 }
406 if (wav)
407 pjmedia_port_destroy(wav);
408 if (pool)
409 pj_pool_release(pool);
410}
411
412
Benny Prijonoe3ebd552009-02-18 20:14:15 +0000413static void print_menu(void)
414{
415 puts("");
416 puts("Audio demo menu:");
417 puts("-------------------------------");
418 puts(" l List devices");
419 puts(" i ID Show device info for device ID");
420 puts(" t RID PID CR PTIM [CH] Perform test on the device:");
421 puts(" RID: record device ID (-1 for no)");
422 puts(" PID: playback device ID (-1 for no)");
423 puts(" CR: clock rate");
424 puts(" PTIM: ptime in ms");
425 puts(" CH: # of channels");
Benny Prijono555139d2009-02-19 12:08:19 +0000426 puts(" r RID [FILE] Record capture device RID to WAV file");
427 puts(" p PID [FILE] Playback WAV file to device ID PID");
Benny Prijonoe3ebd552009-02-18 20:14:15 +0000428 puts(" v Toggle log verbosity");
429 puts(" q Quit");
430 puts("");
431 printf("Enter selection: ");
432 fflush(stdout);
433}
434
435int main()
436{
437 pj_caching_pool cp;
438 pj_bool_t done = PJ_FALSE;
439 pj_status_t status;
440
441 /* Init pjlib */
442 status = pj_init();
443 PJ_ASSERT_RETURN(status==PJ_SUCCESS, 1);
444
445 pj_log_set_decor(PJ_LOG_HAS_NEWLINE | PJ_LOG_HAS_COLOR);
446
447 /* Must create a pool factory before we can allocate any memory. */
448 pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);
449
450 status = pjmedia_aud_subsys_init(&cp.factory);
451 if (status != PJ_SUCCESS) {
452 app_perror("pjmedia_aud_subsys_init()", status);
453 pj_caching_pool_destroy(&cp);
454 pj_shutdown();
455 return 1;
456 }
457
458 list_devices();
459
460 while (!done) {
461 char line[80];
462
463 print_menu();
464
465 if (fgets(line, sizeof(line), stdin)==NULL)
466 break;
467
468 switch (line[0]) {
469 case 'l':
470 list_devices();
471 break;
472
473 case 'i':
474 {
475 unsigned dev_index;
476 if (sscanf(line+2, "%u", &dev_index) != 1) {
477 puts("error: device ID required");
478 break;
479 }
480 show_dev_info(dev_index);
481 }
482 break;
483
484 case 't':
485 {
486 pjmedia_dir dir;
487 int rec_id, play_id;
488 unsigned clock_rate, ptime, chnum;
489 int cnt;
490
491 cnt = sscanf(line+2, "%d %d %u %u %u", &rec_id, &play_id,
492 &clock_rate, &ptime, &chnum);
493 if (cnt < 4) {
494 puts("error: not enough parameters");
495 break;
496 }
497 if (clock_rate < 8000 || clock_rate > 128000) {
498 puts("error: invalid clock rate");
499 break;
500 }
501 if (ptime < 10 || ptime > 500) {
502 puts("error: invalid ptime");
503 break;
504 }
505 if (cnt==5) {
506 if (chnum < 1 || chnum > 4) {
507 puts("error: invalid number of channels");
508 break;
509 }
510 } else {
511 chnum = 1;
512 }
513
514 if (rec_id >= 0 && rec_id < (int)dev_count) {
515 if (play_id >= 0 && play_id < (int)dev_count)
516 dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
517 else
518 dir = PJMEDIA_DIR_CAPTURE;
519 } else if (play_id >= 0 && play_id < (int)dev_count) {
520 dir = PJMEDIA_DIR_PLAYBACK;
521 } else {
522 puts("error: at least one valid device index required");
523 break;
524 }
525
526 test_device(dir, rec_id, play_id, clock_rate, ptime, chnum);
527
528 }
529 break;
530
Benny Prijono555139d2009-02-19 12:08:19 +0000531 case 'r':
532 /* record */
533 {
534 int index;
535 char filename[80];
536 int count;
537
538 count = sscanf(line+2, "%d %s", &index, filename);
539 if (count==1)
540 record(index, NULL);
541 else if (count==2)
542 record(index, filename);
543 else
544 puts("error: invalid command syntax");
545 }
546 break;
547
548 case 'p':
549 /* playback */
550 {
551 int index;
552 char filename[80];
553 int count;
554
555 count = sscanf(line+2, "%d %s", &index, filename);
556 if (count==1)
557 play_file(index, NULL);
558 else if (count==2)
559 play_file(index, filename);
560 else
561 puts("error: invalid command syntax");
562 }
563 break;
564
Benny Prijonoe3ebd552009-02-18 20:14:15 +0000565 case 'v':
566 if (pj_log_get_level() <= 3) {
567 pj_log_set_level(5);
568 puts("Logging set to detail");
569 } else {
570 pj_log_set_level(3);
571 puts("Logging set to quiet");
572 }
573 break;
574
575 case 'q':
576 done = PJ_TRUE;
577 break;
578 }
579 }
580
581 pj_caching_pool_destroy(&cp);
582 pj_shutdown();
583 return 0;
584}
585
586