/* * Create the echo canceller. */ PJ_DEF(pj_status_t) pjmedia_echo_create( pj_pool_t *pool, unsigned clock_rate, unsigned samples_per_frame, unsigned tail_ms, unsigned latency_ms, unsigned options, pjmedia_echo_state **p_echo ) { return pjmedia_echo_create2(pool, clock_rate, 1, samples_per_frame, tail_ms, latency_ms, options, p_echo); }
PJ_DEF(pj_status_t) pjmedia_echo_port_create(pj_pool_t *pool, pjmedia_port *dn_port, unsigned tail_ms, unsigned latency_ms, unsigned options, pjmedia_port **p_port ) { const pj_str_t AEC = { "EC", 2 }; pjmedia_audio_format_detail *afd; struct ec *ec; pj_status_t status; PJ_ASSERT_RETURN(pool && dn_port && p_port, PJ_EINVAL); afd = pjmedia_format_get_audio_format_detail(&dn_port->info.fmt, PJ_TRUE); PJ_ASSERT_RETURN(afd->bits_per_sample==16 && tail_ms, PJ_EINVAL); /* Create the port and the AEC itself */ ec = PJ_POOL_ZALLOC_T(pool, struct ec); pjmedia_port_info_init(&ec->base.info, &AEC, SIGNATURE, afd->clock_rate, afd->channel_count, afd->bits_per_sample, PJMEDIA_AFD_SPF(afd)); status = pjmedia_echo_create2(pool, afd->clock_rate, afd->channel_count, PJMEDIA_AFD_SPF(afd), tail_ms, latency_ms, options, &ec->ec); if (status != PJ_SUCCESS) return status; /* More init */ ec->dn_port = dn_port; ec->base.get_frame = &ec_get_frame; ec->base.put_frame = &ec_put_frame; ec->base.on_destroy = &ec_on_destroy; /* Done */ *p_port = &ec->base; return PJ_SUCCESS; }
/* * Change EC settings. */ PJ_DEF(pj_status_t) pjmedia_snd_port_set_ec( pjmedia_snd_port *snd_port, pj_pool_t *pool, unsigned tail_ms, unsigned options) { pjmedia_aud_param prm; pj_status_t status; /* Sound must be opened in full-duplex mode */ PJ_ASSERT_RETURN(snd_port && snd_port->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK, PJ_EINVALIDOP); /* Determine whether we use device or software EC */ if ((snd_port->prm_ec_options & PJMEDIA_ECHO_USE_SW_ECHO) == 0 && (snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC)) { /* We use device EC */ pj_bool_t ec_enabled; /* Query EC status */ status = pjmedia_aud_stream_get_cap(snd_port->aud_stream, PJMEDIA_AUD_DEV_CAP_EC, &ec_enabled); if (status != PJ_SUCCESS) return status; if (tail_ms != 0) { /* Change EC setting */ if (!ec_enabled) { /* Enable EC first */ pj_bool_t value = PJ_TRUE; status = pjmedia_aud_stream_set_cap(snd_port->aud_stream, PJMEDIA_AUD_DEV_CAP_EC, &value); if (status != PJ_SUCCESS) return status; } if ((snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC_TAIL)==0) { /* Device does not support setting EC tail */ return PJMEDIA_EAUD_INVCAP; } return pjmedia_aud_stream_set_cap(snd_port->aud_stream, PJMEDIA_AUD_DEV_CAP_EC_TAIL, &tail_ms); } else if (ec_enabled) { /* Disable EC */ pj_bool_t value = PJ_FALSE; return pjmedia_aud_stream_set_cap(snd_port->aud_stream, PJMEDIA_AUD_DEV_CAP_EC, &value); } else { /* Request to disable EC but EC has been disabled */ /* Do nothing */ return PJ_SUCCESS; } } else { /* We use software EC */ /* Check if there is change in parameters */ if (tail_ms==snd_port->ec_tail_len && options==snd_port->ec_options) { PJ_LOG(5,(THIS_FILE, "pjmedia_snd_port_set_ec() ignored, no " "change in settings")); return PJ_SUCCESS; } status = pjmedia_aud_stream_get_param(snd_port->aud_stream, &prm); if (status != PJ_SUCCESS) return status; /* Audio stream must be in PCM format */ PJ_ASSERT_RETURN(prm.ext_fmt.id == PJMEDIA_FORMAT_PCM, PJ_EINVALIDOP); /* Destroy AEC */ if (snd_port->ec_state) { pjmedia_echo_destroy(snd_port->ec_state); snd_port->ec_state = NULL; } if (tail_ms != 0) { unsigned delay_ms; //No need to add input latency in the latency calculation, //since actual input latency should be zero. //delay_ms = (si.rec_latency + si.play_latency) * 1000 / // snd_port->clock_rate; /* Set EC latency to 3/4 of output latency to reduce the * possibility of missing/late reference frame. */ delay_ms = prm.output_latency_ms * 3/4; status = pjmedia_echo_create2(pool, snd_port->clock_rate, snd_port->channel_count, snd_port->samples_per_frame, tail_ms, delay_ms, options, &snd_port->ec_state); if (status != PJ_SUCCESS) snd_port->ec_state = NULL; else snd_port->ec_suspended = PJ_FALSE; } else { PJ_LOG(4,(THIS_FILE, "Echo canceller is now disabled in the " "sound port")); status = PJ_SUCCESS; } snd_port->ec_options = options; snd_port->ec_tail_len = tail_ms; } return status; }
/* * main() */ int main(int argc, char *argv[]) { pj_caching_pool cp; pjmedia_endpt *med_endpt; pj_pool_t *pool; pjmedia_port *wav_play; pjmedia_port *wav_rec; pjmedia_port *wav_out; pj_status_t status; pjmedia_echo_state *ec; pjmedia_frame play_frame, rec_frame; unsigned opt = 0; unsigned latency_ms = 25; unsigned tail_ms = TAIL_LENGTH; pj_timestamp t0, t1; int i, repeat=1, interactive=0, c; pj_optind = 0; while ((c=pj_getopt(argc, argv, "d:l:a:r:i")) !=-1) { switch (c) { case 'd': latency_ms = atoi(pj_optarg); if (latency_ms < 25) { puts("Invalid delay"); puts(desc); } break; case 'l': tail_ms = atoi(pj_optarg); break; case 'a': { int alg = atoi(pj_optarg); switch (alg) { case 0: opt = 0; case 1: opt = PJMEDIA_ECHO_SPEEX; break; case 3: opt = PJMEDIA_ECHO_SIMPLE; break; default: puts("Invalid algorithm"); puts(desc); return 1; } } break; case 'r': repeat = atoi(pj_optarg); if (repeat < 1) { puts("Invalid repeat count"); puts(desc); return 1; } break; case 'i': interactive = 1; break; } } if (argc - pj_optind != 3) { puts("Error: missing argument(s)"); puts(desc); return 1; } /* Must init PJLIB first: */ status = pj_init(); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); /* Must create a pool factory before we can allocate any memory. */ pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0); /* * Initialize media endpoint. * This will implicitly initialize PJMEDIA too. */ status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); /* Create memory pool for our file player */ pool = pj_pool_create( &cp.factory, /* pool factory */ "wav", /* pool name. */ 4000, /* init size */ 4000, /* increment size */ NULL /* callback on error */ ); /* Open wav_play */ status = pjmedia_wav_player_port_create(pool, argv[pj_optind], PTIME, PJMEDIA_FILE_NO_LOOP, 0, &wav_play); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Error opening playback WAV file", status); return 1; } /* Open recorded wav */ status = pjmedia_wav_player_port_create(pool, argv[pj_optind+1], PTIME, PJMEDIA_FILE_NO_LOOP, 0, &wav_rec); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Error opening recorded WAV file", status); return 1; } /* play and rec WAVs must have the same clock rate */ if (wav_play->info.clock_rate != wav_rec->info.clock_rate) { puts("Error: clock rate mismatch in the WAV files"); return 1; } /* .. and channel count */ if (wav_play->info.channel_count != wav_rec->info.channel_count) { puts("Error: clock rate mismatch in the WAV files"); return 1; } /* Create output wav */ status = pjmedia_wav_writer_port_create(pool, argv[pj_optind+2], wav_play->info.clock_rate, wav_play->info.channel_count, wav_play->info.samples_per_frame, wav_play->info.bits_per_sample, 0, 0, &wav_out); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Error opening output WAV file", status); return 1; } /* Create echo canceller */ status = pjmedia_echo_create2(pool, wav_play->info.clock_rate, wav_play->info.channel_count, wav_play->info.samples_per_frame, tail_ms, latency_ms, opt, &ec); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Error creating EC", status); return 1; } /* Processing loop */ play_frame.buf = pj_pool_alloc(pool, wav_play->info.samples_per_frame<<1); rec_frame.buf = pj_pool_alloc(pool, wav_play->info.samples_per_frame<<1); pj_get_timestamp(&t0); for (i=0; i < repeat; ++i) { for (;;) { play_frame.size = wav_play->info.samples_per_frame << 1; status = pjmedia_port_get_frame(wav_play, &play_frame); if (status != PJ_SUCCESS) break; status = pjmedia_echo_playback(ec, (short*)play_frame.buf); rec_frame.size = wav_play->info.samples_per_frame << 1; status = pjmedia_port_get_frame(wav_rec, &rec_frame); if (status != PJ_SUCCESS) break; status = pjmedia_echo_capture(ec, (short*)rec_frame.buf, 0); //status = pjmedia_echo_cancel(ec, (short*)rec_frame.buf, // (short*)play_frame.buf, 0, NULL); pjmedia_port_put_frame(wav_out, &rec_frame); } pjmedia_wav_player_port_set_pos(wav_play, 0); pjmedia_wav_player_port_set_pos(wav_rec, 0); } pj_get_timestamp(&t1); i = pjmedia_wav_writer_port_get_pos(wav_out) / sizeof(pj_int16_t) * 1000 / (wav_out->info.clock_rate * wav_out->info.channel_count); PJ_LOG(3,(THIS_FILE, "Processed %3d.%03ds audio", i / 1000, i % 1000)); PJ_LOG(3,(THIS_FILE, "Completed in %u msec\n", pj_elapsed_msec(&t0, &t1))); /* Destroy file port(s) */ status = pjmedia_port_destroy( wav_play ); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); status = pjmedia_port_destroy( wav_rec ); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); status = pjmedia_port_destroy( wav_out ); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); /* Destroy ec */ pjmedia_echo_destroy(ec); /* Release application pool */ pj_pool_release( pool ); /* Destroy media endpoint. */ pjmedia_endpt_destroy( med_endpt ); /* Destroy pool factory */ pj_caching_pool_destroy( &cp ); /* Shutdown PJLIB */ pj_shutdown(); if (interactive) { char s[10], *dummy; puts("ENTER to quit"); dummy = fgets(s, sizeof(s), stdin); } /* Done. */ return 0; }