static pj_status_t g729_dealloc_codec(pjmedia_codec_factory *factory, pjmedia_codec *codec ) { struct g729_private *priv = (struct g729_private*) codec->codec_data; int i = 0; PJ_ASSERT_RETURN(factory && codec, PJ_EINVAL); PJ_ASSERT_RETURN(factory==&g729_factory.base, PJ_EINVAL); /* Close codec, if it's not closed. */ g729_close(codec); #if !PLC_DISABLED /* Clear left samples in the PLC, since codec+plc will be reused * next time. */ for (i=0; i<2; ++i) { pj_int16_t frame[80]; pjmedia_zero_samples(frame, PJ_ARRAY_SIZE(frame)); pjmedia_plc_save(priv->plc, frame); } #else PJ_UNUSED_ARG(i); PJ_UNUSED_ARG(priv); #endif /* Re-init silence_period */ pj_set_timestamp32(&priv->last_tx, 0, 0); pj_pool_release(priv->pool); return PJ_SUCCESS; }
/* * Let the Echo Canceller knows that a frame has been captured from * the microphone. */ PJ_DEF(pj_status_t) pjmedia_echo_capture( pjmedia_echo_state *echo, pj_int16_t *rec_frm, unsigned options ) { struct frame *oldest_frm; pj_status_t status, rc; if (!echo->lat_ready) { /* Prefetching to fill in the desired latency */ PJ_LOG(5,(echo->obj_name, "Prefetching..")); return PJ_SUCCESS; } /* Retrieve oldest frame from the latency buffer */ oldest_frm = echo->lat_buf.next; pj_list_erase(oldest_frm); /* Cancel echo using this reference frame */ status = pjmedia_echo_cancel(echo, rec_frm, oldest_frm->buf, options, NULL); /* Move one frame from delay buffer to the latency buffer. */ rc = pjmedia_delay_buf_get(echo->delay_buf, oldest_frm->buf); if (rc != PJ_SUCCESS) { /* Ooops.. no frame! */ PJ_LOG(5,(echo->obj_name, "No frame from delay buffer. This will upset EC later")); pjmedia_zero_samples(oldest_frm->buf, echo->samples_per_frame); } pj_list_push_back(&echo->lat_buf, oldest_frm); return status; }
static void run_one_frame(pjmedia_port *src, pjmedia_port *dst, pj_bool_t *has_frame) { pjmedia_frame frame; pj_status_t status; pj_bzero(&frame, sizeof(frame)); frame.type = PJMEDIA_FRAME_TYPE_AUDIO; frame.buf = g_app.framebuf; frame.size = PJMEDIA_PIA_SPF(&dst->info) * 2; status = pjmedia_port_get_frame(src, &frame); pj_assert(status == PJ_SUCCESS); if (status!= PJ_SUCCESS || frame.type != PJMEDIA_FRAME_TYPE_AUDIO) { frame.buf = g_app.framebuf; pjmedia_zero_samples(g_app.framebuf, PJMEDIA_PIA_SPF(&src->info)); frame.size = PJMEDIA_PIA_SPF(&src->info) * 2; if (has_frame) *has_frame = PJ_FALSE; } else { if (has_frame) *has_frame = PJ_TRUE; } status = pjmedia_port_put_frame(dst, &frame); pj_assert(status == PJ_SUCCESS); }
/* * Let the AEC knows that a frame has been captured from the microphone. */ PJ_DEF(pj_status_t) echo_supp_capture( void *state, pj_int16_t *rec_frm, unsigned options ) { echo_supp *ec = state; pj_time_val now; unsigned delay_ms; PJ_UNUSED_ARG(options); pj_gettimeofday(&now); PJ_TIME_VAL_SUB(now, ec->last_signal); delay_ms = PJ_TIME_VAL_MSEC(now); if (delay_ms < ec->tail_ms) { #if defined(PJMEDIA_ECHO_SUPPRESS_FACTOR) && PJMEDIA_ECHO_SUPPRESS_FACTOR!=0 unsigned i; for (i=0; i<ec->samples_per_frame; ++i) { rec_frm[i] = (pj_int16_t)(rec_frm[i] >> PJMEDIA_ECHO_SUPPRESS_FACTOR); } #else pjmedia_zero_samples(rec_frm, ec->samples_per_frame); #endif }
/* * Recover lost frame. */ static pj_status_t codec_recover( pjmedia_codec *codec, unsigned output_buf_len, struct pjmedia_frame *output ) { struct opus_data *opus_data = (struct opus_data *)codec->codec_data; int decoded_samples; pjmedia_frame *inframe; pj_mutex_lock (opus_data->mutex); if (opus_data->dec_frame_index == -1) { /* Recover the first packet? Don't think so, fill it with zeroes. */ pj_uint16_t samples_per_frame; samples_per_frame = (opus_data->cfg.sample_rate * opus_data->ptime) / 1000; output->type = PJMEDIA_FRAME_TYPE_AUDIO; output->size = samples_per_frame << 1; pjmedia_zero_samples((pj_int16_t*)output->buf, samples_per_frame); pj_mutex_unlock (opus_data->mutex); return PJ_SUCCESS; } inframe = &opus_data->dec_frame[opus_data->dec_frame_index]; decoded_samples = opus_decode(opus_data->dec, inframe->type==PJMEDIA_FRAME_TYPE_AUDIO ? inframe->buf : NULL, inframe->type==PJMEDIA_FRAME_TYPE_AUDIO ? inframe->size : 0, (opus_int16*)output->buf, output->size / (sizeof(opus_int16) * opus_data->cfg.channel_cnt), 0); /* Mark current indexed frame as invalid */ inframe->type = PJMEDIA_FRAME_TYPE_NONE; /* Update current frame index */ opus_data->dec_frame_index++; if (opus_data->dec_frame_index > 1) opus_data->dec_frame_index = 0; /* Mark current indexed frame as invalid */ inframe = &opus_data->dec_frame[opus_data->dec_frame_index]; inframe->type = PJMEDIA_FRAME_TYPE_NONE; if (decoded_samples < 0) { PJ_LOG(4, (THIS_FILE, "Recover failed!")); pj_mutex_unlock (opus_data->mutex); return PJMEDIA_CODEC_EFAILED; } output->size = decoded_samples * sizeof(opus_int16) * opus_data->cfg.channel_cnt; output->type = PJMEDIA_FRAME_TYPE_AUDIO; output->timestamp = inframe->timestamp; pj_mutex_unlock (opus_data->mutex); return PJ_SUCCESS; }
/* * Get frame from file. */ static pj_status_t null_get_frame(pjmedia_port *this_port, pjmedia_frame *frame) { frame->type = PJMEDIA_FRAME_TYPE_AUDIO; frame->size = this_port->info.samples_per_frame * 2; frame->timestamp.u32.lo += this_port->info.samples_per_frame; pjmedia_zero_samples(frame->buf, this_port->info.samples_per_frame); return PJ_SUCCESS; }
/* * Let the Echo Canceller knows that a frame has been captured from * the microphone. */ pj_status_t pjs_echo_canceller::capture(pj_int16_t *rec_frm, unsigned size) { struct frame *oldest_frm; pj_status_t status, rc; if(samples_per_frame!=size) { PJ_LOG(1, (THIS_FILE, "WRONG SIZE ON CAPTURE %d != %d",size,samples_per_frame)); return -1; } for (unsigned i = 0; i < samples_per_frame; i++) { REAL f = hp00.highpass(rec_frm[i]); f = hp0.highpass(f); rec_frm[i] = round(f); } PPJ_WaitAndLock wl(*lock); if (!lat_ready) { /* Prefetching to fill in the desired latency */ PJ_LOG(4, (THIS_FILE, "Prefetching..")); return PJ_SUCCESS; } /* Retrieve oldest frame from the latency buffer */ oldest_frm = lat_buf.next; pj_list_erase(oldest_frm); lock->release(); speex_echo_cancellation(state, (const spx_int16_t*)rec_frm, (const spx_int16_t*)oldest_frm->buf, (spx_int16_t*)tmp_frame); /* Preprocess output */ speex_preprocess_run(preprocess, (spx_int16_t*)tmp_frame); pjmedia_copy_samples(rec_frm, tmp_frame, samples_per_frame); status = PJ_SUCCESS; /* Cancel echo using this reference frame */ lock->acquire(); /* Move one frame from delay buffer to the latency buffer. */ rc = pjmedia_delay_buf_get(delay_buf, oldest_frm->buf); if (rc != PJ_SUCCESS) { /* Ooops.. no frame! */ PJ_LOG(4, (THIS_FILE, "No frame from delay buffer. This will upset EC later")); pjmedia_zero_samples(oldest_frm->buf, samples_per_frame); } pj_list_push_back(&lat_buf, oldest_frm); return status; }
/* * Get a mono frame from a reversed phase channel. */ static pj_status_t rport_put_frame(pjmedia_port *this_port, const pjmedia_frame *frame) { struct reverse_port *rport = (struct reverse_port*) this_port; unsigned count; pj_assert(frame->size <= rport->base.info.bytes_per_frame); /* Check for overflows */ if (rport->up_write_pos == rport->up_read_pos) { /* Only report overflow if the frame is constantly read * at the other end of the buffer (the multichannel side). * It is possible that nobody reads the buffer, so causing * overflow to happen rapidly, and writing log message this * way does not seem to be wise. */ if (rport->up_read_pos != rport->up_overflow_pos) { rport->up_overflow_pos = rport->up_read_pos; LOG_UP_((THIS_FILE, "Overflow in upstream direction")); } /* Adjust the write position */ rport->up_write_pos = (rport->up_read_pos + rport->buf_cnt/2) % rport->buf_cnt; } /* Handle NULL frame */ if (frame->type != PJMEDIA_FRAME_TYPE_AUDIO) { TRACE_UP_((THIS_FILE, "Upstream write %d null samples at buf pos %d", this_port->info.samples_per_frame, rport->up_write_pos)); pjmedia_zero_samples(rport->upstream_buf[rport->up_write_pos], this_port->info.samples_per_frame); rport->up_write_pos = (rport->up_write_pos+1) % rport->buf_cnt; return PJ_SUCCESS; } /* Not sure how to handle partial frame, so better reject for now */ PJ_ASSERT_RETURN(frame->size == this_port->info.bytes_per_frame, PJ_EINVAL); /* Copy normal frame to curcular buffer */ count = frame->size * 8 / this_port->info.bits_per_sample; TRACE_UP_((THIS_FILE, "Upstream write %d samples at buf pos %d", count, rport->up_write_pos)); pjmedia_copy_samples(rport->upstream_buf[rport->up_write_pos], frame->buf, count); rport->up_write_pos = (rport->up_write_pos+1) % rport->buf_cnt; return PJ_SUCCESS; }
static pj_status_t ec_get_frame( pjmedia_port *this_port, pjmedia_frame *frame) { struct ec *ec = (struct ec*)this_port; pj_status_t status; PJ_ASSERT_RETURN(this_port->info.signature == SIGNATURE, PJ_EINVAL); status = pjmedia_port_get_frame(ec->dn_port, frame); if (status!=PJ_SUCCESS || frame->type!=PJMEDIA_FRAME_TYPE_AUDIO) { pjmedia_zero_samples(frame->buf, this_port->info.samples_per_frame); } pjmedia_echo_playback(ec->ec, frame->buf); return status; }
/* * Free codec. */ static pj_status_t gsm_dealloc_codec( pjmedia_codec_factory *factory, pjmedia_codec *codec ) { struct gsm_data *gsm_data; int i; PJ_ASSERT_RETURN(factory && codec, PJ_EINVAL); PJ_ASSERT_RETURN(factory == &gsm_codec_factory.base, PJ_EINVAL); gsm_data = (struct gsm_data*) codec->codec_data; /* Close codec, if it's not closed. */ gsm_codec_close(codec); #if !PLC_DISABLED /* Clear left samples in the PLC, since codec+plc will be reused * next time. */ for (i=0; i<2; ++i) { pj_int16_t frame[160]; pjmedia_zero_samples(frame, PJ_ARRAY_SIZE(frame)); pjmedia_plc_save(gsm_data->plc, frame); } #else PJ_UNUSED_ARG(i); #endif /* Re-init silence_period */ pj_set_timestamp32(&gsm_data->last_tx, 0, 0); /* Put in the free list. */ pj_mutex_lock(gsm_codec_factory.mutex); pj_list_push_front(&gsm_codec_factory.codec_list, codec); pj_mutex_unlock(gsm_codec_factory.mutex); return PJ_SUCCESS; }
/* * Decode frame. */ static pj_status_t spx_codec_decode( pjmedia_codec *codec, const struct pjmedia_frame *input, unsigned output_buf_len, struct pjmedia_frame *output) { struct spx_private *spx; unsigned samples_per_frame; spx = (struct spx_private*) codec->codec_data; samples_per_frame=spx_factory.speex_param[spx->param_id].samples_per_frame; PJ_ASSERT_RETURN(output_buf_len >= samples_per_frame << 1, PJMEDIA_CODEC_EPCMTOOSHORT); if (input->type != PJMEDIA_FRAME_TYPE_AUDIO) { pjmedia_zero_samples((pj_int16_t*)output->buf, samples_per_frame); output->size = samples_per_frame << 1; output->timestamp.u64 = input->timestamp.u64; output->type = PJMEDIA_FRAME_TYPE_AUDIO; return PJ_SUCCESS; } /* Copy the data into the bit-stream struct */ speex_bits_read_from(&spx->dec_bits, (char*)input->buf, (int)input->size); /* Set Speex dec_bits pointer to the start bit of the frame */ speex_bits_advance(&spx->dec_bits, input->bit_info); /* Decode the data */ speex_decode_int(spx->dec, &spx->dec_bits, (spx_int16_t*)output->buf); output->type = PJMEDIA_FRAME_TYPE_AUDIO; output->size = samples_per_frame << 1; output->timestamp.u64 = input->timestamp.u64; return PJ_SUCCESS; }
/* * "Write" a multichannel frame downstream. This would split * the multichannel frame into individual mono channel, and write * it to the appropriate port. */ static pj_status_t put_frame(pjmedia_port *this_port, pjmedia_frame *frame) { struct splitcomb *sc = (struct splitcomb*) this_port; unsigned ch; /* Handle null frame */ if (frame->type == PJMEDIA_FRAME_TYPE_NONE) { for (ch=0; ch < PJMEDIA_PIA_CCNT(&this_port->info); ++ch) { pjmedia_port *port = sc->port_desc[ch].port; if (!port) continue; if (!sc->port_desc[ch].reversed) { pjmedia_port_put_frame(port, frame); } else { struct reverse_port *rport = (struct reverse_port*)port; /* Update the number of NULL frames received. Once we have too * many of this, we'll stop calling op_update() to let the * media be suspended. */ if (++rport->buf[DIR_DOWNSTREAM].null_cnt > rport->max_null_frames) { /* Prevent the counter from overflowing and resetting * back to zero */ rport->buf[DIR_DOWNSTREAM].null_cnt = rport->max_null_frames + 1; continue; } /* Write zero port to delaybuf so that it doesn't underflow. * If we don't do this, get_frame() on this direction will * cause delaybuf to generate missing frame and the last * frame transmitted to delaybuf will be replayed multiple * times, which doesn't sound good. */ /* Update rport state. */ op_update(rport, DIR_DOWNSTREAM, OP_PUT); /* Discard frame if rport is paused on this direction */ if (rport->buf[DIR_DOWNSTREAM].paused) continue; /* Generate zero frame. */ pjmedia_zero_samples(sc->put_buf, PJMEDIA_PIA_SPF(&port->info)); /* Put frame to delay buffer */ pjmedia_delay_buf_put(rport->buf[DIR_DOWNSTREAM].dbuf, sc->put_buf); } } return PJ_SUCCESS; } /* Not sure how we would handle partial frame, so better reject * it for now. */ PJ_ASSERT_RETURN(frame->size == PJMEDIA_PIA_AVG_FSZ(&this_port->info), PJ_EINVAL); /* * Write mono frame into each channels */ for (ch=0; ch < PJMEDIA_PIA_CCNT(&this_port->info); ++ch) { pjmedia_port *port = sc->port_desc[ch].port; if (!port) continue; /* Extract the mono frame to temporary buffer */ extract_mono_frame((const pj_int16_t*)frame->buf, sc->put_buf, ch, PJMEDIA_PIA_CCNT(&this_port->info), (unsigned)frame->size * 8 / PJMEDIA_PIA_BITS(&this_port->info) / PJMEDIA_PIA_CCNT(&this_port->info)); if (!sc->port_desc[ch].reversed) { /* Write to normal port */ pjmedia_frame mono_frame; mono_frame.buf = sc->put_buf; mono_frame.size = frame->size / PJMEDIA_PIA_CCNT(&this_port->info); mono_frame.type = frame->type; mono_frame.timestamp.u64 = frame->timestamp.u64; /* Write */ pjmedia_port_put_frame(port, &mono_frame); } else { /* Write to reversed phase port */ struct reverse_port *rport = (struct reverse_port*)port; /* Reset NULL frame counter */ rport->buf[DIR_DOWNSTREAM].null_cnt = 0; /* Update rport state. */ op_update(rport, DIR_DOWNSTREAM, OP_PUT); if (!rport->buf[DIR_DOWNSTREAM].paused) { pjmedia_delay_buf_put(rport->buf[DIR_DOWNSTREAM].dbuf, sc->put_buf); } } } return PJ_SUCCESS; }
/* * Get a multichannel frame. * This will get mono channel frame from each port and put the * mono frame into the multichannel frame. */ static pj_status_t get_frame(pjmedia_port *this_port, pjmedia_frame *frame) { struct splitcomb *sc = (struct splitcomb*) this_port; unsigned ch; pj_bool_t has_frame = PJ_FALSE; /* Clear output frame */ pjmedia_zero_samples(frame->buf, this_port->info.samples_per_frame); /* Read frame from each port */ for (ch=0; ch < this_port->info.channel_count; ++ch) { pjmedia_port *port = sc->port_desc[ch].port; pjmedia_frame mono_frame; pj_status_t status; if (!port) continue; /* Read from the port */ if (sc->port_desc[ch].reversed == PJ_FALSE) { /* Read from normal port */ mono_frame.buf = sc->get_buf; mono_frame.size = port->info.bytes_per_frame; mono_frame.timestamp.u64 = frame->timestamp.u64; status = pjmedia_port_get_frame(port, &mono_frame); if (status != PJ_SUCCESS || mono_frame.type != PJMEDIA_FRAME_TYPE_AUDIO) { continue; } /* Combine the mono frame into multichannel frame */ store_mono_frame(mono_frame.buf, frame->buf, ch, this_port->info.channel_count, mono_frame.size * 8 / this_port->info.bits_per_sample); frame->timestamp.u64 = mono_frame.timestamp.u64; } else { /* Read from temporary buffer for reverse port */ struct reverse_port *rport = (struct reverse_port*)port; /* Check for underflows */ if (rport->up_read_pos == rport->up_write_pos) { /* Only report underflow if the buffer is constantly filled * up at the other side. * It is possible that nobody writes the buffer, so causing * underflow to happen rapidly, and writing log message this * way does not seem to be wise. */ if (rport->up_write_pos != rport->up_underflow_pos) { rport->up_underflow_pos = rport->up_write_pos; LOG_UP_((THIS_FILE, "Underflow in upstream direction")); } /* Adjust read position */ rport->up_read_pos = (rport->up_write_pos - rport->buf_cnt/2) % rport->buf_cnt; } TRACE_UP_((THIS_FILE, "Upstream read at buffer pos %d", rport->up_read_pos)); /* Combine the mono frame into multichannel frame */ store_mono_frame(rport->upstream_buf[rport->up_read_pos], frame->buf, ch, this_port->info.channel_count, port->info.samples_per_frame); rport->up_read_pos = (rport->up_read_pos + 1) % rport->buf_cnt; } has_frame = PJ_TRUE; } /* Return NO_FRAME is we don't get any frames from downstream ports */ if (has_frame) { frame->type = PJMEDIA_FRAME_TYPE_AUDIO; frame->size = this_port->info.bytes_per_frame; } else frame->type = PJMEDIA_FRAME_TYPE_NONE; return PJ_SUCCESS; }
PJ_DEF(pj_status_t) pjmedia_resample_create( pj_pool_t *pool, pj_bool_t high_quality, pj_bool_t large_filter, unsigned channel_count, unsigned rate_in, unsigned rate_out, unsigned samples_per_frame, pjmedia_resample **p_resample) { pjmedia_resample *resample; PJ_ASSERT_RETURN(pool && p_resample && rate_in && rate_out && samples_per_frame, PJ_EINVAL); resample = PJ_POOL_ZALLOC_T(pool, pjmedia_resample); PJ_ASSERT_RETURN(resample, PJ_ENOMEM); /* * If we're downsampling, always use the fast algorithm since it seems * to yield the same quality. */ if (rate_out < rate_in) { //no this is not a good idea. It sounds pretty good with speech, //but very poor with background noise etc. //high_quality = 0; } resample->factor = rate_out * 1.0 / rate_in; resample->large_filter = large_filter; resample->high_quality = high_quality; resample->channel_cnt = channel_count; resample->frame_size = samples_per_frame; if (high_quality) { /* This is a bug in xoff calculation, thanks Stephane Lussier * of Macadamian dot com. * resample->xoff = large_filter ? 32 : 6; */ resample->xoff = res_GetXOFF(resample->factor, (char)large_filter); } else { resample->xoff = 1; } if (channel_count == 1) { unsigned size; /* Allocate input buffer */ size = (samples_per_frame + 2*resample->xoff) * sizeof(pj_int16_t); resample->buffer = (pj_int16_t*) pj_pool_alloc(pool, size); PJ_ASSERT_RETURN(resample->buffer, PJ_ENOMEM); pjmedia_zero_samples(resample->buffer, resample->xoff*2); } else if (channel_count > 1) { unsigned i, size; /* Allocate input buffer table */ size = channel_count * sizeof(pj_int16_t*); resample->in_buffer = (pj_int16_t**)pj_pool_alloc(pool, size); /* Allocate input buffer */ size = (samples_per_frame/channel_count + 2*resample->xoff) * sizeof(pj_int16_t); for (i = 0; i < channel_count; ++i) { resample->in_buffer[i] = (pj_int16_t*)pj_pool_alloc(pool, size); PJ_ASSERT_RETURN(resample->in_buffer, PJ_ENOMEM); pjmedia_zero_samples(resample->in_buffer[i], resample->xoff*2); } /* Allocate temporary output buffer */ size = (unsigned) (resample->frame_size * sizeof(pj_int16_t) * resample->factor / channel_count); resample->tmp_buffer = (pj_int16_t*) pj_pool_alloc(pool, size); PJ_ASSERT_RETURN(resample->tmp_buffer, PJ_ENOMEM); } *p_resample = resample; PJ_LOG(5,(THIS_FILE, "resample created: %s qualiy, %s filter, in/out " "rate=%d/%d", (high_quality?"high":"low"), (large_filter?"large":"small"), rate_in, rate_out)); return PJ_SUCCESS; }
/**************************************************************************** * sound latency test */ static int calculate_latency(pj_pool_t *pool, pjmedia_port *wav, unsigned *lat_sum, unsigned *lat_cnt, unsigned *lat_min, unsigned *lat_max) { pjmedia_frame frm; short *buf; unsigned i, clock_rate, samples_per_frame; pj_size_t read, len; unsigned start_pos; pj_bool_t first; pj_status_t status; *lat_sum = 0; *lat_cnt = 0; *lat_min = 10000; *lat_max = 0; samples_per_frame = PJMEDIA_PIA_SPF(&wav->info); clock_rate = PJMEDIA_PIA_SRATE(&wav->info); frm.buf = pj_pool_alloc(pool, samples_per_frame * 2); frm.size = samples_per_frame * 2; len = pjmedia_wav_player_get_len(wav); buf = pj_pool_alloc(pool, len + samples_per_frame); /* Read the whole file */ read = 0; while (read < len/2) { status = pjmedia_port_get_frame(wav, &frm); if (status != PJ_SUCCESS) break; pjmedia_copy_samples(buf+read, (short*)frm.buf, samples_per_frame); read += samples_per_frame; } if (read < 2 * clock_rate) { systest_perror("The WAV file is too short", PJ_SUCCESS); return -1; } /* Zero the first 500ms to remove loud click noises * (keypad press, etc.) */ pjmedia_zero_samples(buf, clock_rate / 2); /* Loop to calculate latency */ start_pos = 0; first = PJ_TRUE; while (start_pos < len/2 - clock_rate) { int max_signal = 0; unsigned max_signal_pos = start_pos; unsigned max_echo_pos = 0; unsigned pos; unsigned lat; /* Get the largest signal in the next 0.7s */ for (i=start_pos; i<start_pos + clock_rate * 700 / 1000; ++i) { if (abs(buf[i]) > max_signal) { max_signal = abs(buf[i]); max_signal_pos = i; } } /* Advance 10ms from max_signal_pos */ pos = max_signal_pos + 10 * clock_rate / 1000; /* Get the largest signal in the next 800ms */ max_signal = 0; max_echo_pos = pos; for (i=pos; i<pos+clock_rate * 8 / 10; ++i) { if (abs(buf[i]) > max_signal) { max_signal = abs(buf[i]); max_echo_pos = i; } } lat = (max_echo_pos - max_signal_pos) * 1000 / clock_rate; #if 0 PJ_LOG(4,(THIS_FILE, "Signal at %dms, echo at %d ms, latency %d ms", max_signal_pos * 1000 / clock_rate, max_echo_pos * 1000 / clock_rate, lat)); #endif *lat_sum += lat; (*lat_cnt)++; if (lat < *lat_min) *lat_min = lat; if (lat > *lat_max) *lat_max = lat; /* Advance next loop */ if (first) { start_pos = max_signal_pos + clock_rate * 9 / 10; first = PJ_FALSE; } else { start_pos += clock_rate; } } return 0; }
/* * Put a frame into the buffer. When the buffer is full, flush the buffer * to the file. */ static pj_status_t file_put_frame(pjmedia_port *this_port, const pjmedia_frame *frame) { struct mp3_file_port *fport = (struct mp3_file_port *)this_port; unsigned long MP3Err; pj_ssize_t bytes; pj_status_t status; unsigned long WriteSize; /* Record silence if input is no-frame */ if (frame->type == PJMEDIA_FRAME_TYPE_NONE || frame->size == 0) { unsigned samples_left = fport->base.info.samples_per_frame; unsigned samples_copied = 0; /* Only want to record at most 1 second of silence */ if (fport->silence_duration >= fport->base.info.clock_rate) return PJ_SUCCESS; while (samples_left) { unsigned samples_needed = fport->mp3_samples_per_frame - fport->mp3_sample_pos; if (samples_needed > samples_left) samples_needed = samples_left; pjmedia_zero_samples(fport->mp3_sample_buf + fport->mp3_sample_pos, samples_needed); fport->mp3_sample_pos += samples_needed; samples_left -= samples_needed; samples_copied += samples_needed; /* Encode if we have full frame */ if (fport->mp3_sample_pos == fport->mp3_samples_per_frame) { /* Clear position */ fport->mp3_sample_pos = 0; /* Encode ! */ MP3Err = BladeDLL.beEncodeChunk(fport->mp3_stream, fport->mp3_samples_per_frame, fport->mp3_sample_buf, fport->mp3_buf, &WriteSize); if (MP3Err != BE_ERR_SUCCESSFUL) return PJMEDIA_ERROR; /* Write the chunk */ bytes = WriteSize; status = pj_file_write(fport->fd, fport->mp3_buf, &bytes); if (status != PJ_SUCCESS) return status; /* Increment total written. */ fport->total += bytes; } } fport->silence_duration += fport->base.info.samples_per_frame; } /* If encoder is expecting different sample size, then we need to * buffer the samples. */ else if (fport->mp3_samples_per_frame != fport->base.info.samples_per_frame) { unsigned samples_left = frame->size / 2; unsigned samples_copied = 0; const pj_int16_t *src_samples = frame->buf; fport->silence_duration = 0; while (samples_left) { unsigned samples_needed = fport->mp3_samples_per_frame - fport->mp3_sample_pos; if (samples_needed > samples_left) samples_needed = samples_left; pjmedia_copy_samples(fport->mp3_sample_buf + fport->mp3_sample_pos, src_samples + samples_copied, samples_needed); fport->mp3_sample_pos += samples_needed; samples_left -= samples_needed; samples_copied += samples_needed; /* Encode if we have full frame */ if (fport->mp3_sample_pos == fport->mp3_samples_per_frame) { /* Clear position */ fport->mp3_sample_pos = 0; /* Encode ! */ MP3Err = BladeDLL.beEncodeChunk(fport->mp3_stream, fport->mp3_samples_per_frame, fport->mp3_sample_buf, fport->mp3_buf, &WriteSize); if (MP3Err != BE_ERR_SUCCESSFUL) return PJMEDIA_ERROR; /* Write the chunk */ bytes = WriteSize; status = pj_file_write(fport->fd, fport->mp3_buf, &bytes); if (status != PJ_SUCCESS) return status; /* Increment total written. */ fport->total += bytes; } } } else { fport->silence_duration = 0; /* Encode ! */ MP3Err = BladeDLL.beEncodeChunk(fport->mp3_stream, fport->mp3_samples_per_frame, frame->buf, fport->mp3_buf, &WriteSize); if (MP3Err != BE_ERR_SUCCESSFUL) return PJMEDIA_ERROR; /* Write the chunk */ bytes = WriteSize; status = pj_file_write(fport->fd, fport->mp3_buf, &bytes); if (status != PJ_SUCCESS) return status; /* Increment total written. */ fport->total += bytes; } /* Increment total written, and check if we need to call callback */ if (fport->cb && fport->total >= fport->cb_size) { pj_status_t (*cb)(pjmedia_port*, void*); pj_status_t status; cb = fport->cb; fport->cb = NULL; status = (*cb)(this_port, this_port->port_data.pdata); return status; } return PJ_SUCCESS; }
/* * Put a frame in the reverse port (upstream direction). This frame * will be picked up by get_frame() above. */ static pj_status_t rport_put_frame(pjmedia_port *this_port, pjmedia_frame *frame) { struct reverse_port *rport = (struct reverse_port*) this_port; pj_assert(frame->size <= PJMEDIA_PIA_AVG_FSZ(&rport->base.info)); /* Handle NULL frame */ if (frame->type != PJMEDIA_FRAME_TYPE_AUDIO) { /* Update the number of NULL frames received. Once we have too * many of this, we'll stop calling op_update() to let the * media be suspended. */ if (++rport->buf[DIR_UPSTREAM].null_cnt > rport->max_null_frames) { /* Prevent the counter from overflowing and resetting back * to zero */ rport->buf[DIR_UPSTREAM].null_cnt = rport->max_null_frames + 1; return PJ_SUCCESS; } /* Write zero port to delaybuf so that it doesn't underflow. * If we don't do this, get_frame() on this direction will * cause delaybuf to generate missing frame and the last * frame transmitted to delaybuf will be replayed multiple * times, which doesn't sound good. */ /* Update rport state. */ op_update(rport, DIR_UPSTREAM, OP_PUT); /* Discard frame if rport is paused on this direction */ if (rport->buf[DIR_UPSTREAM].paused) return PJ_SUCCESS; /* Generate zero frame. */ pjmedia_zero_samples(rport->tmp_up_buf, PJMEDIA_PIA_SPF(&this_port->info)); /* Put frame to delay buffer */ return pjmedia_delay_buf_put(rport->buf[DIR_UPSTREAM].dbuf, rport->tmp_up_buf); } /* Not sure how to handle partial frame, so better reject for now */ PJ_ASSERT_RETURN(frame->size == PJMEDIA_PIA_AVG_FSZ(&this_port->info), PJ_EINVAL); /* Reset NULL frame counter */ rport->buf[DIR_UPSTREAM].null_cnt = 0; /* Update rport state. */ op_update(rport, DIR_UPSTREAM, OP_PUT); /* Discard frame if rport is paused on this direction */ if (rport->buf[DIR_UPSTREAM].paused) return PJ_SUCCESS; /* Unfortunately must copy to temporary buffer since delay buf * modifies the frame content. */ pjmedia_copy_samples(rport->tmp_up_buf, (const pj_int16_t*)frame->buf, PJMEDIA_PIA_SPF(&this_port->info)); /* Put frame to delay buffer */ return pjmedia_delay_buf_put(rport->buf[DIR_UPSTREAM].dbuf, rport->tmp_up_buf); }
/* * Get a multichannel frame upstream. * This will get mono channel frame from each port and put the * mono frame into the multichannel frame. */ static pj_status_t get_frame(pjmedia_port *this_port, pjmedia_frame *frame) { struct splitcomb *sc = (struct splitcomb*) this_port; unsigned ch; pj_bool_t has_frame = PJ_FALSE; /* Read frame from each port */ for (ch=0; ch < PJMEDIA_PIA_CCNT(&this_port->info); ++ch) { pjmedia_port *port = sc->port_desc[ch].port; pjmedia_frame mono_frame; pj_status_t status; if (!port) { pjmedia_zero_samples(sc->get_buf, PJMEDIA_PIA_SPF(&this_port->info) / PJMEDIA_PIA_CCNT(&this_port->info)); } else if (sc->port_desc[ch].reversed == PJ_FALSE) { /* Read from normal port */ mono_frame.buf = sc->get_buf; mono_frame.size = PJMEDIA_PIA_AVG_FSZ(&port->info); mono_frame.timestamp.u64 = frame->timestamp.u64; status = pjmedia_port_get_frame(port, &mono_frame); if (status != PJ_SUCCESS || mono_frame.type != PJMEDIA_FRAME_TYPE_AUDIO) { pjmedia_zero_samples(sc->get_buf, PJMEDIA_PIA_SPF(&port->info)); } frame->timestamp.u64 = mono_frame.timestamp.u64; } else { /* Read from temporary buffer for reverse port */ struct reverse_port *rport = (struct reverse_port*)port; /* Update rport state. */ op_update(rport, DIR_UPSTREAM, OP_GET); if (!rport->buf[DIR_UPSTREAM].paused) { pjmedia_delay_buf_get(rport->buf[DIR_UPSTREAM].dbuf, sc->get_buf); } else { pjmedia_zero_samples(sc->get_buf, PJMEDIA_PIA_SPF(&port->info)); } frame->timestamp.u64 = rport->buf[DIR_UPSTREAM].ts.u64; } /* Combine the mono frame into multichannel frame */ store_mono_frame(sc->get_buf, (pj_int16_t*)frame->buf, ch, PJMEDIA_PIA_CCNT(&this_port->info), PJMEDIA_PIA_SPF(&this_port->info) / PJMEDIA_PIA_CCNT(&this_port->info)); has_frame = PJ_TRUE; } /* Return NO_FRAME is we don't get any frames from downstream ports */ if (has_frame) { frame->type = PJMEDIA_FRAME_TYPE_AUDIO; frame->size = PJMEDIA_PIA_AVG_FSZ(&this_port->info); } else frame->type = PJMEDIA_FRAME_TYPE_NONE; return PJ_SUCCESS; }
/* * play_callback() * * This callback is called by sound device's player thread when it * needs to feed the player with some frames. */ static pj_status_t get_frame( pjmedia_port *port, pjmedia_frame *frame) { pjmedia_stream *stream = (pjmedia_stream*) port->port_data.pdata; pjmedia_channel *channel = stream->dec; unsigned samples_count, samples_per_frame, samples_required; pj_int16_t *p_out_samp; pj_status_t status; /* Return no frame is channel is paused */ if (channel->paused) { frame->type = PJMEDIA_FRAME_TYPE_NONE; return PJ_SUCCESS; } /* Repeat get frame from the jitter buffer and decode the frame * until we have enough frames according to codec's ptime. */ /* Lock jitter buffer mutex first */ pj_mutex_lock( stream->jb_mutex ); samples_required = stream->port.info.samples_per_frame; samples_per_frame = stream->codec_param.info.frm_ptime * stream->codec_param.info.clock_rate * stream->codec_param.info.channel_cnt / 1000; p_out_samp = (pj_int16_t*) frame->buf; for (samples_count=0; samples_count < samples_required; samples_count += samples_per_frame) { char frame_type; /* Get frame from jitter buffer. */ pjmedia_jbuf_get_frame(stream->jb, channel->out_pkt, &frame_type); if (frame_type == PJMEDIA_JB_MISSING_FRAME) { /* Activate PLC */ if (stream->codec->op->recover && stream->codec_param.setting.plc) { pjmedia_frame frame_out; frame_out.buf = p_out_samp + samples_count; frame_out.size = frame->size - samples_count*2; status = (*stream->codec->op->recover)(stream->codec, frame_out.size, &frame_out); } else { status = -1; } if (status != PJ_SUCCESS) { /* Either PLC failed or PLC not supported/enabled */ pjmedia_zero_samples(p_out_samp + samples_count, samples_required - samples_count); PJ_LOG(5,(stream->port.info.name.ptr, "Frame lost!")); } else { PJ_LOG(5,(stream->port.info.name.ptr, "Lost frame recovered")); } } else if (frame_type == PJMEDIA_JB_ZERO_EMPTY_FRAME) { /* Jitter buffer is empty. If this is the first "empty" state, * activate PLC to smoothen the fade-out, otherwise zero * the frame. */ if (frame_type != stream->jb_last_frm) { pjmedia_jb_state jb_state; /* Activate PLC to smoothen the missing frame */ if (stream->codec->op->recover && stream->codec_param.setting.plc) { pjmedia_frame frame_out; do { frame_out.buf = p_out_samp + samples_count; frame_out.size = frame->size - samples_count*2; status = (*stream->codec->op->recover)(stream->codec, frame_out.size, &frame_out); if (status != PJ_SUCCESS) break; samples_count += samples_per_frame; } while (samples_count < samples_required); } /* Report the state of jitter buffer */ pjmedia_jbuf_get_state(stream->jb, &jb_state); PJ_LOG(5,(stream->port.info.name.ptr, "Jitter buffer empty (prefetch=%d)", jb_state.prefetch)); } if (samples_count < samples_required) { pjmedia_zero_samples(p_out_samp + samples_count, samples_required - samples_count); samples_count = samples_required; } stream->jb_last_frm = frame_type; break; } else if (frame_type != PJMEDIA_JB_NORMAL_FRAME) { pjmedia_jb_state jb_state; /* It can only be PJMEDIA_JB_ZERO_PREFETCH frame */ pj_assert(frame_type == PJMEDIA_JB_ZERO_PREFETCH_FRAME); /* Get the state of jitter buffer */ pjmedia_jbuf_get_state(stream->jb, &jb_state); /* Always activate PLC when it's available.. */ if (stream->codec->op->recover && stream->codec_param.setting.plc) { pjmedia_frame frame_out; do { frame_out.buf = p_out_samp + samples_count; frame_out.size = frame->size - samples_count*2; status = (*stream->codec->op->recover)(stream->codec, frame_out.size, &frame_out); if (status != PJ_SUCCESS) break; samples_count += samples_per_frame; } while (samples_count < samples_required); if (stream->jb_last_frm != frame_type) { PJ_LOG(5,(stream->port.info.name.ptr, "Jitter buffer is bufferring with plc (prefetch=%d)", jb_state.prefetch)); } } if (samples_count < samples_required) { pjmedia_zero_samples(p_out_samp + samples_count, samples_required - samples_count); samples_count = samples_required; PJ_LOG(5,(stream->port.info.name.ptr, "Jitter buffer is bufferring (prefetch=%d)..", jb_state.prefetch)); } stream->jb_last_frm = frame_type; break; } else { /* Got "NORMAL" frame from jitter buffer */ pjmedia_frame frame_in, frame_out; /* Decode */ frame_in.buf = channel->out_pkt; frame_in.size = stream->frame_size; frame_in.type = PJMEDIA_FRAME_TYPE_AUDIO; /* ignored */ frame_out.buf = p_out_samp + samples_count; frame_out.size = frame->size - samples_count*BYTES_PER_SAMPLE; status = stream->codec->op->decode( stream->codec, &frame_in, frame_out.size, &frame_out); if (status != 0) { LOGERR_((port->info.name.ptr, "codec decode() error", status)); pjmedia_zero_samples(p_out_samp + samples_count, samples_per_frame); } } stream->jb_last_frm = frame_type; } /* Unlock jitter buffer mutex. */ pj_mutex_unlock( stream->jb_mutex ); /* Return PJMEDIA_FRAME_TYPE_NONE if we have no frames at all * (it can happen when jitter buffer returns PJMEDIA_JB_ZERO_EMPTY_FRAME). */ if (samples_count == 0) { frame->type = PJMEDIA_FRAME_TYPE_NONE; frame->size = 0; } else { frame->type = PJMEDIA_FRAME_TYPE_AUDIO; frame->size = samples_count * BYTES_PER_SAMPLE; frame->timestamp.u64 = 0; } return PJ_SUCCESS; }