/* * Let the Echo Canceller know that a frame has been played to the speaker. */ pj_status_t pjs_echo_canceller::playback(pj_int16_t *play_frm, unsigned size) { /* Playing frame should be stored, as it will be used by echo_capture() * as reference frame, delay buffer is used for storing the playing frames * as in case there was clock drift between mic & speaker. * * Ticket #830: * Note that pjmedia_delay_buf_put() may modify the input frame and those * modified frames may not be smooth, i.e: if there were two or more * consecutive pjmedia_delay_buf_get() before next pjmedia_delay_buf_put(), * so we'll just feed the delay buffer with the copy of playing frame, * instead of the original playing frame. However this will cause the EC * uses slight 'different' frames (for reference) than actually played * by the speaker. */ if(samples_per_frame!=size) { PJ_LOG(1, (THIS_FILE, "WRONG SIZE ON PLAYBACK %d != %d",size,samples_per_frame)); return -1; } PPJ_WaitAndLock wl(*lock); pjmedia_copy_samples(frm_buf, play_frm, samples_per_frame); pjmedia_delay_buf_put(delay_buf, frm_buf); if (!lat_ready) { /* We've not built enough latency in the buffer, so put this frame * in the latency buffer list. */ struct frame *frm; if (pj_list_empty(&lat_free)) { lat_ready = PJ_TRUE; PJ_LOG(4, (THIS_FILE, "Latency bufferring complete")); return PJ_SUCCESS; } frm = lat_free.prev; pj_list_erase(frm); /* Move one frame from delay buffer to the latency buffer. */ pjmedia_delay_buf_get(delay_buf, frm_buf); pjmedia_copy_samples(frm->buf, frm_buf, samples_per_frame); pj_list_push_back(&lat_buf, frm); } return PJ_SUCCESS; }
/* * Let the Echo Canceller know that a frame has been played to the speaker. */ PJ_DEF(pj_status_t) pjmedia_echo_playback( pjmedia_echo_state *echo, pj_int16_t *play_frm ) { /* If EC algo has playback handler, just pass the frame. */ if (echo->op->ec_playback) { return (*echo->op->ec_playback)(echo->state, play_frm); } /* Playing frame should be stored, as it will be used by echo_capture() * as reference frame, delay buffer is used for storing the playing frames * as in case there was clock drift between mic & speaker. * * Ticket #830: * Note that pjmedia_delay_buf_put() may modify the input frame and those * modified frames may not be smooth, i.e: if there were two or more * consecutive pjmedia_delay_buf_get() before next pjmedia_delay_buf_put(), * so we'll just feed the delay buffer with the copy of playing frame, * instead of the original playing frame. However this will cause the EC * uses slight 'different' frames (for reference) than actually played * by the speaker. */ pjmedia_copy_samples(echo->frm_buf, play_frm, echo->samples_per_frame); pjmedia_delay_buf_put(echo->delay_buf, echo->frm_buf); if (!echo->lat_ready) { /* We've not built enough latency in the buffer, so put this frame * in the latency buffer list. */ struct frame *frm; if (pj_list_empty(&echo->lat_free)) { echo->lat_ready = PJ_TRUE; PJ_LOG(5,(echo->obj_name, "Latency bufferring complete")); return PJ_SUCCESS; } frm = echo->lat_free.prev; pj_list_erase(frm); /* Move one frame from delay buffer to the latency buffer. */ pjmedia_delay_buf_get(echo->delay_buf, echo->frm_buf); pjmedia_copy_samples(frm->buf, echo->frm_buf, echo->samples_per_frame); pj_list_push_back(&echo->lat_buf, frm); } return PJ_SUCCESS; }
/* * Perform echo cancellation. */ PJ_DEF(pj_status_t) speex_aec_cancel_echo( void *state, pj_int16_t *rec_frm, const pj_int16_t *play_frm, unsigned options, void *reserved ) { speex_ec *echo = (speex_ec*) state; /* Sanity checks */ PJ_ASSERT_RETURN(echo && rec_frm && play_frm && options==0 && reserved==NULL, PJ_EINVAL); /* Cancel echo, put output in temporary buffer */ speex_echo_cancellation(echo->state, (const spx_int16_t*)rec_frm, (const spx_int16_t*)play_frm, (spx_int16_t*)echo->tmp_frame); /* Preprocess output */ speex_preprocess_run(echo->preprocess, (spx_int16_t*)echo->tmp_frame); /* Copy temporary buffer back to original rec_frm */ pjmedia_copy_samples(rec_frm, echo->tmp_frame, echo->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; }
/* * Get a mono frame from a reversed phase channel. */ static pj_status_t rport_get_frame(pjmedia_port *this_port, pjmedia_frame *frame) { struct reverse_port *rport = (struct reverse_port*) this_port; unsigned count; count = rport->base.info.samples_per_frame; frame->size = this_port->info.bytes_per_frame; frame->type = PJMEDIA_FRAME_TYPE_AUDIO; /* Check for underflows */ if (rport->dn_read_pos == rport->dn_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->dn_write_pos != rport->dn_underflow_pos) { rport->dn_underflow_pos = rport->dn_write_pos; LOG_DN_((THIS_FILE, "Underflow in downstream direction")); } /* Adjust read position */ rport->dn_read_pos = (rport->dn_write_pos - rport->buf_cnt/2) % rport->buf_cnt; } /* Get the samples from the circular buffer */ pjmedia_copy_samples(frame->buf, rport->dnstream_buf[rport->dn_read_pos], count); rport->dn_read_pos = (rport->dn_read_pos+1) % rport->buf_cnt; return PJ_SUCCESS; }
/* * Perform echo cancellation to captured frame. */ PJ_DEF(pj_status_t) speex_aec_capture( void *state, pj_int16_t *rec_frm, unsigned options ) { speex_ec *echo = (speex_ec*) state; /* Sanity checks */ PJ_ASSERT_RETURN(echo && rec_frm, PJ_EINVAL); PJ_UNUSED_ARG(options); /* Cancel echo */ pjmedia_copy_samples(echo->tmp_frame, rec_frm, echo->samples_per_frame); speex_echo_capture(echo->state, (spx_int16_t*)echo->tmp_frame, (spx_int16_t*)rec_frm); /* Apply preprocessing */ speex_preprocess_run(echo->preprocess, (spx_int16_t*)rec_frm); return PJ_SUCCESS; }
/* * Find out latency */ static int calculate_latency(pj_pool_t *pool, pjmedia_port *wav) { pjmedia_frame frm; short *buf; unsigned i, samples_per_frame, read, len; unsigned start_pos; pj_status_t status; unsigned lat_sum = 0, lat_cnt = 0, lat_min = 10000, lat_max = 0; samples_per_frame = PJMEDIA_PIA_SPF(&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 = 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 * PJMEDIA_PIA_SRATE(&wav->info)) { puts("Error: too short"); return -1; } start_pos = 0; while (start_pos < len/2 - PJMEDIA_PIA_SRATE(&wav->info)) { 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 + PJMEDIA_PIA_SRATE(&wav->info) * 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 * PJMEDIA_PIA_SRATE(&wav->info) / 1000; /* Get the largest signal in the next 500ms */ max_signal = 0; max_echo_pos = pos; for (i=pos; i<pos+PJMEDIA_PIA_SRATE(&wav->info)/2; ++i) { if (abs(buf[i]) > max_signal) { max_signal = abs(buf[i]); max_echo_pos = i; } } lat = (max_echo_pos - max_signal_pos) * 1000 / PJMEDIA_PIA_SRATE(&wav->info); #if 0 printf("Latency = %u\n", lat); #endif lat_sum += lat; lat_cnt++; if (lat < lat_min) lat_min = lat; if (lat > lat_max) lat_max = lat; /* Advance next loop */ start_pos += PJMEDIA_PIA_SRATE(&wav->info); } printf("Latency average = %u\n", lat_sum / lat_cnt); printf("Latency minimum = %u\n", lat_min); printf("Latency maximum = %u\n", lat_max); printf("Number of data = %u\n", lat_cnt); return 0; }
/**************************************************************************** * 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; }
static int PaPlayerCallback( const void *input, void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void *userData ) { struct pa_aud_stream *stream = (struct pa_aud_stream*) userData; pj_status_t status = 0; unsigned nsamples_req = frameCount * stream->channel_count; PJ_UNUSED_ARG(input); PJ_UNUSED_ARG(timeInfo); if (stream->quit_flag) goto on_break; if (output == NULL) return paContinue; /* Known cases of callback's thread: * - The thread may be changed in the middle of a session, e.g: in MacOS * it happens when plugging/unplugging headphone. * - The same thread may be reused in consecutive sessions. The first * session will leave TLS set, but release the TLS data address, * so the second session must re-register the callback's thread. */ if (stream->play_thread_initialized == 0 || !pj_thread_is_registered()) { pj_bzero(stream->play_thread_desc, sizeof(pj_thread_desc)); status = pj_thread_register("portaudio", stream->play_thread_desc, &stream->play_thread); stream->play_thread_initialized = 1; PJ_LOG(5,(THIS_FILE, "Player thread started")); } if (statusFlags & paOutputUnderflow) ++stream->underflow; if (statusFlags & paOutputOverflow) ++stream->overflow; /* Check if any buffered samples */ if (stream->play_buf_count) { /* samples buffered >= requested by sound device */ if (stream->play_buf_count >= nsamples_req) { pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf, nsamples_req); stream->play_buf_count -= nsamples_req; pjmedia_move_samples(stream->play_buf, stream->play_buf + nsamples_req, stream->play_buf_count); nsamples_req = 0; return paContinue; } /* samples buffered < requested by sound device */ pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf, stream->play_buf_count); nsamples_req -= stream->play_buf_count; output = (pj_int16_t*)output + stream->play_buf_count; stream->play_buf_count = 0; } /* Fill output buffer as requested */ while (nsamples_req && status == 0) { if (nsamples_req >= stream->samples_per_frame) { pjmedia_frame frame; frame.type = PJMEDIA_FRAME_TYPE_AUDIO; frame.buf = output; frame.size = stream->samples_per_frame * stream->bytes_per_sample; frame.timestamp.u64 = stream->play_timestamp.u64; frame.bit_info = 0; status = (*stream->play_cb)(stream->user_data, &frame); if (status != PJ_SUCCESS) goto on_break; if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO) pj_bzero(frame.buf, frame.size); nsamples_req -= stream->samples_per_frame; output = (pj_int16_t*)output + stream->samples_per_frame; } else { pjmedia_frame frame; frame.type = PJMEDIA_FRAME_TYPE_AUDIO; frame.buf = stream->play_buf; frame.size = stream->samples_per_frame * stream->bytes_per_sample; frame.timestamp.u64 = stream->play_timestamp.u64; frame.bit_info = 0; status = (*stream->play_cb)(stream->user_data, &frame); if (status != PJ_SUCCESS) goto on_break; if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO) pj_bzero(frame.buf, frame.size); pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf, nsamples_req); stream->play_buf_count = stream->samples_per_frame - nsamples_req; pjmedia_move_samples(stream->play_buf, stream->play_buf+nsamples_req, stream->play_buf_count); nsamples_req = 0; } stream->play_timestamp.u64 += stream->samples_per_frame / stream->channel_count; } if (status==0) return paContinue; on_break: stream->play_thread_exited = 1; return paAbort; }
static int PaRecorderCallback(const void *input, void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void *userData ) { struct pa_aud_stream *stream = (struct pa_aud_stream*) userData; pj_status_t status = 0; unsigned nsamples; PJ_UNUSED_ARG(output); PJ_UNUSED_ARG(timeInfo); if (stream->quit_flag) goto on_break; if (input == NULL) return paContinue; /* Known cases of callback's thread: * - The thread may be changed in the middle of a session, e.g: in MacOS * it happens when plugging/unplugging headphone. * - The same thread may be reused in consecutive sessions. The first * session will leave TLS set, but release the TLS data address, * so the second session must re-register the callback's thread. */ if (stream->rec_thread_initialized == 0 || !pj_thread_is_registered()) { pj_bzero(stream->rec_thread_desc, sizeof(pj_thread_desc)); status = pj_thread_register("pa_rec", stream->rec_thread_desc, &stream->rec_thread); stream->rec_thread_initialized = 1; PJ_LOG(5,(THIS_FILE, "Recorder thread started")); } if (statusFlags & paInputUnderflow) ++stream->underflow; if (statusFlags & paInputOverflow) ++stream->overflow; /* Calculate number of samples we've got */ nsamples = frameCount * stream->channel_count + stream->rec_buf_count; if (nsamples >= stream->samples_per_frame) { /* If buffer is not empty, combine the buffer with the just incoming * samples, then call put_frame. */ if (stream->rec_buf_count) { unsigned chunk_count = 0; pjmedia_frame frame; chunk_count = stream->samples_per_frame - stream->rec_buf_count; pjmedia_copy_samples(stream->rec_buf + stream->rec_buf_count, (pj_int16_t*)input, chunk_count); frame.type = PJMEDIA_FRAME_TYPE_AUDIO; frame.buf = (void*) stream->rec_buf; frame.size = stream->samples_per_frame * stream->bytes_per_sample; frame.timestamp.u64 = stream->rec_timestamp.u64; frame.bit_info = 0; status = (*stream->rec_cb)(stream->user_data, &frame); input = (pj_int16_t*) input + chunk_count; nsamples -= stream->samples_per_frame; stream->rec_buf_count = 0; stream->rec_timestamp.u64 += stream->samples_per_frame / stream->channel_count; } /* Give all frames we have */ while (nsamples >= stream->samples_per_frame && status == 0) { pjmedia_frame frame; frame.type = PJMEDIA_FRAME_TYPE_AUDIO; frame.buf = (void*) input; frame.size = stream->samples_per_frame * stream->bytes_per_sample; frame.timestamp.u64 = stream->rec_timestamp.u64; frame.bit_info = 0; status = (*stream->rec_cb)(stream->user_data, &frame); input = (pj_int16_t*) input + stream->samples_per_frame; nsamples -= stream->samples_per_frame; stream->rec_timestamp.u64 += stream->samples_per_frame / stream->channel_count; } /* Store the remaining samples into the buffer */ if (nsamples && status == 0) { stream->rec_buf_count = nsamples; pjmedia_copy_samples(stream->rec_buf, (pj_int16_t*)input, nsamples); } } else { /* Not enough samples, let's just store them in the buffer */ pjmedia_copy_samples(stream->rec_buf + stream->rec_buf_count, (pj_int16_t*)input, frameCount * stream->channel_count); stream->rec_buf_count += frameCount * stream->channel_count; } if (status==0) return paContinue; on_break: stream->rec_thread_exited = 1; return paAbort; }
/* * 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); }
/* * 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; }
/* callbacks to get data */ int bdimad_PlaybackCallback(void *buffer, int samples, void *user_data) { pj_status_t status = PJ_SUCCESS; pjmedia_frame frame; struct bd_stream *strm = (struct bd_stream*)user_data; unsigned nsamples_req = samples * strm->channel_count; if(!strm->go) goto on_break; /* Known cases of callback's thread: * - The thread may be changed in the middle of a session, e.g: in MacOS * it happens when plugging/unplugging headphone. * - The same thread may be reused in consecutive sessions. The first * session will leave TLS set, but release the TLS data address, * so the second session must re-register the callback's thread. */ if (strm->play_thread_initialized == 0 || !pj_thread_is_registered()) { pj_bzero(strm->play_thread_desc, sizeof(pj_thread_desc)); status = pj_thread_register("bd_PlaybackCallback", strm->play_thread_desc, &strm->play_thread); if (status != PJ_SUCCESS) goto on_break; strm->play_thread_initialized = 1; PJ_LOG(5,(THIS_FILE, "Player thread started")); } /* PLAY */ if(strm->fmt_id == PJMEDIA_FORMAT_L16) { /* Check if any buffered samples */ if (strm->play_buf_count) { /* samples buffered >= requested by sound device */ if (strm->play_buf_count >= nsamples_req) { pjmedia_copy_samples((pj_int16_t*)buffer, strm->play_buf, nsamples_req); strm->play_buf_count -= nsamples_req; pjmedia_move_samples(strm->play_buf, strm->play_buf + nsamples_req, strm->play_buf_count); return nsamples_req; } /* samples buffered < requested by sound device */ pjmedia_copy_samples((pj_int16_t*)buffer, strm->play_buf, strm->play_buf_count); nsamples_req -= strm->play_buf_count; buffer = (pj_int16_t*)buffer + strm->play_buf_count; strm->play_buf_count = 0; } /* Fill output buffer as requested */ while (nsamples_req && status == 0) { if (nsamples_req >= strm->samples_per_frame) { frame.type = PJMEDIA_FRAME_TYPE_AUDIO; frame.buf = buffer; frame.size = strm->bytes_per_frame; frame.timestamp.u64 = strm->timestampPlayback.u64; frame.bit_info = 0; status = (*strm->play_cb)(strm->user_data, &frame); if (status != PJ_SUCCESS) return 0; if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO) pj_bzero(frame.buf, frame.size); nsamples_req -= strm->samples_per_frame; buffer = (pj_int16_t*)buffer + strm->samples_per_frame; } else { frame.type = PJMEDIA_FRAME_TYPE_AUDIO; frame.buf = strm->play_buf; frame.size = strm->bytes_per_frame; frame.timestamp.u64 = strm->timestampPlayback.u64; frame.bit_info = 0; status = (*strm->play_cb)(strm->user_data, &frame); if (status != PJ_SUCCESS) return 0; if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO) pj_bzero(frame.buf, frame.size); pjmedia_copy_samples((pj_int16_t*)buffer, strm->play_buf, nsamples_req); strm->play_buf_count = strm->samples_per_frame - nsamples_req; pjmedia_move_samples(strm->play_buf, strm->play_buf+nsamples_req, strm->play_buf_count); nsamples_req = 0; } strm->timestampPlayback.u64 += strm->samples_per_frame / strm->channel_count; } } else { pj_assert(!"Frame type not supported"); } if(status != PJ_SUCCESS) { return 0; } strm->timestampPlayback.u64 += strm->param.samples_per_frame / strm->param.channel_count; if (status == 0) return samples; on_break: strm->play_thread_exited = 1; return 0; }
/* callbacks to set data */ void bdimad_CaptureCallback(void *buffer, int samples, void *user_data) { pj_status_t status = PJ_SUCCESS; pjmedia_frame frame; unsigned nsamples; struct bd_stream *strm = (struct bd_stream*)user_data; if(!strm->go) goto on_break; /* Known cases of callback's thread: * - The thread may be changed in the middle of a session, e.g: in MacOS * it happens when plugging/unplugging headphone. * - The same thread may be reused in consecutive sessions. The first * session will leave TLS set, but release the TLS data address, * so the second session must re-register the callback's thread. */ if (strm->rec_thread_initialized == 0 || !pj_thread_is_registered()) { pj_bzero(strm->rec_thread_desc, sizeof(pj_thread_desc)); status = pj_thread_register("bd_CaptureCallback", strm->rec_thread_desc, &strm->rec_thread); if (status != PJ_SUCCESS) goto on_break; strm->rec_thread_initialized = 1; PJ_LOG(5,(THIS_FILE, "Recorder thread started")); } /* Calculate number of samples we've got */ nsamples = samples * strm->channel_count + strm->rec_buf_count; /* RECORD */ if (strm->fmt_id == PJMEDIA_FORMAT_L16) { if (nsamples >= strm->samples_per_frame) { /* If buffer is not empty, combine the buffer with the just incoming * samples, then call put_frame. */ if (strm->rec_buf_count) { unsigned chunk_count = 0; chunk_count = strm->samples_per_frame - strm->rec_buf_count; pjmedia_copy_samples(strm->rec_buf + strm->rec_buf_count, (pj_int16_t*)buffer, chunk_count); frame.type = PJMEDIA_FRAME_TYPE_AUDIO; frame.buf = (void*) strm->rec_buf; frame.size = strm->bytes_per_frame; frame.timestamp.u64 = strm->timestampCapture.u64; frame.bit_info = 0; status = (*strm->rec_cb)(strm->user_data, &frame); buffer = (pj_int16_t*) buffer + chunk_count; nsamples -= strm->samples_per_frame; strm->rec_buf_count = 0; strm->timestampCapture.u64 += strm->samples_per_frame / strm->channel_count; } /* Give all frames we have */ while (nsamples >= strm->samples_per_frame && status == 0) { frame.type = PJMEDIA_FRAME_TYPE_AUDIO; frame.buf = (void*) buffer; frame.size = strm->bytes_per_frame; frame.timestamp.u64 = strm->timestampCapture.u64; frame.bit_info = 0; status = (*strm->rec_cb)(strm->user_data, &frame); buffer = (pj_int16_t*) buffer + strm->samples_per_frame; nsamples -= strm->samples_per_frame; strm->timestampCapture.u64 += strm->samples_per_frame / strm->channel_count; } /* Store the remaining samples into the buffer */ if (nsamples && status == 0) { strm->rec_buf_count = nsamples; pjmedia_copy_samples(strm->rec_buf, (pj_int16_t*)buffer, nsamples); } } else { /* Not enough samples, let's just store them in the buffer */ pjmedia_copy_samples(strm->rec_buf + strm->rec_buf_count, (pj_int16_t*)buffer, samples * strm->channel_count); strm->rec_buf_count += samples * strm->channel_count; } } else { pj_assert(!"Frame type not supported"); } strm->timestampCapture.u64 += strm->param.samples_per_frame / strm->param.channel_count; if (status==0) return; on_break: strm->rec_thread_exited = 1; }