From fde7cc2ce319bf294ded54da0822672fe33b1923 Mon Sep 17 00:00:00 2001 From: Andreas Eversberg Date: Sun, 27 Sep 2020 14:17:11 +0200 Subject: Initial GIT import --- src/router/audio.c | 286 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 286 insertions(+) create mode 100644 src/router/audio.c (limited to 'src/router/audio.c') diff --git a/src/router/audio.c b/src/router/audio.c new file mode 100644 index 0000000..0c4b6c3 --- /dev/null +++ b/src/router/audio.c @@ -0,0 +1,286 @@ +/* audio handling + * + * (C) 2020 by Andreas Eversberg + * All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* + * Audio flow diagram: + * + * This diagrams shows the audio processing. The function for each processing + * segment is given by the names ending with "()". + * + * ORIGINATOR + * + * receive_originator() + * | /|\ + * | | + * \|/ | + * +-------+ +-------+ + * |int to | |samples| + * |samples| |to int | + * +-------+ +-------+ + * | /|\ + * +------+ | | + * | |/ | | + * | DTMF |---| | + * | |\ | | + * +------+ | | + * \|/ | + * +-------+ +-------+ + * | TX- | | RX- | + * | GAIN | | GAIN | + * +-------+ +-------+ + * | /|\ + * | | + * | | + * +------+ | | +------+ + * | TX- |/ | | \| RX- | + * | |---| |---| | + * |JITTER|\ | | /|JITTER| + * +------+ | | +------+ + * | | + * +------+ | | + * | WAVE | | | + * | |_ | | + * | PLAY | \ | | + * +------+ \| | + * | | + * \|/ send_originator() + *----------------------------------- + * send_terminator() /|\ + * | | +------+ + * | |\ | WAVE | + * | | \_| | call_clock() + * | | | PLAY | + * \|/ | +------+ + * +-------+ +-------+ + * |samples| |int to | + * |to int | |samples| + * +-------+ +-------+ + * | /|\ + * | | + * \|/ | + * receive_terminator() + * + * TERMINATOR + * + * In recording mode: + * Data is stored into jitter buffer of each endpoint. + * The clock triggers dejittering of TX and RX data and writes it to wave file. + * + * In playback mode: + * The clock triggers read from wave file and forwards it to the originator. + * +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "../libdebug/debug.h" +#include "call.h" +#include "audio.h" + +#define db2level(db) pow(10, (double)db / 20.0) + +static void gain_samples(sample_t *samples, int length, double gain) +{ + double level = db2level(gain); + int i; + + for (i = 0; i < length; i++) + *samples++ *= level; +} + +static void send_terminator(call_relation_t *relation, sample_t *samples, int len) +{ + int16_t spl[len]; + + /* convert samples to int16 */ + samples_to_int16(spl, samples, len); + + /* encode and send via RTP */ + osmo_cc_rtp_send(relation->codec, (uint8_t *)spl, len * sizeof(*spl), 1, len); +} + +void receive_originator(struct osmo_cc_session_codec *codec, uint16_t __attribute__((unused)) sequence_number, uint32_t __attribute__((unused)) timestamp, uint8_t *data, int len) +{ + call_relation_t *relation = codec->media->session->priv; + len = len / 2; + sample_t samples[len]; + + /* convert int16 to samples */ + int16_to_samples(samples, (int16_t *)data, len); + + /* dtmf decoding */ + if (relation->dtmf_dec_enable) + dtmf_decode(&relation->dtmf_dec, samples, len); + + /* adjust gain */ + if (relation->call->tx_gain) + gain_samples(samples, len, relation->call->tx_gain); + + /* store to originator jitter buffer */ + jitter_save(&relation->orig_dejitter, samples, len); + + /* forward to terminators */ + for (relation = relation->next; relation; relation = relation->next) { + if (relation->cc_session && relation->codec && !relation->play.fp) + send_terminator(relation, samples, len); + } +} + +static void send_originator(call_relation_t *relation, sample_t *samples, int len) +{ + int16_t spl[len]; + + /* store to terminator jitter buffer */ + jitter_save(&relation->term_dejitter, samples, len); + + if (relation->call->rx_gain) + gain_samples(samples, len, relation->call->rx_gain); + + samples_to_int16(spl, samples, len); + + osmo_cc_rtp_send(relation->codec, (uint8_t *)spl, len * sizeof(*spl), 1, len); +} + +void receive_terminator(struct osmo_cc_session_codec *codec, uint16_t __attribute__((unused)) sequence_number, uint32_t __attribute__((unused)) timestamp, uint8_t *data, int len) +{ + call_relation_t *relation = codec->media->session->priv; + len = len / 2; + sample_t samples[len]; + + int16_to_samples(samples, (int16_t *)data, len); + + /* forward to originator, if not a forking call */ + if (!relation->call->forking) { + relation = relation->call->relation_list; + if (relation->cc_session && relation->codec && !relation->play.fp) + send_originator(relation, samples, len); + } +} + +void call_media_handle(void) +{ + call_t *call; + call_relation_t *relation; + + for (call = call_list; call; call = call->next) { + for (relation = call->relation_list; relation; relation = relation->next) { + if (relation->cc_session) + osmo_cc_session_handle(relation->cc_session); + } + } +} + +void call_clock(int len) +{ + call_t *call; + call_relation_t *relation; + sample_t buffer[len], buffer2[len], *samples[2]; + int i; + int rc; + + for (call = call_list; call; call = call->next) { + relation = call->relation_list; + if (!relation->cc_session || !relation->codec) + continue; + /* play */ + if (relation->play.fp) { + int got = 0; + read_again: + samples[0] = buffer + got; + samples[1] = buffer2 + got; + rc = wave_read(&relation->play, samples, len - got); + got += rc; + /* we have a short read (hit the end) or nothing to play left (hit the end without short read) */ + if (!relation->play.left) { + wave_destroy_playback(&relation->play); + if (relation->play_loop) { + int samplerate = 0, channels = 0; + int rc; + rc = wave_create_playback(&relation->play, relation->play_filename, &samplerate, &channels, relation->play_deviation); + if (rc >= 0) + goto read_again; + } else { + /* notify routing about finished playback */ + if (call->routing.routing) + routing_send(&call->routing, "wave-finished"); + } + } + /* in case wie do not get all samples filled, append silence */ + while (got < len) + buffer[got++] = 0; + /* convert stereo to mono */ + if (relation->play.channels == 2) { + for (i = 0; i < len; i++) + buffer[i] += buffer2[i]; + } + /* forward audio */ + if (relation == call->relation_list) + send_originator(relation, buffer, len); + else + send_terminator(relation, buffer, len); + } + /* record + * NOTE: jitter buffer is recorded at send_originator() or send_terminator, so it already includes wave playback */ + if (relation->rec.fp) { + samples[0] = buffer; + samples[1] = buffer2; + jitter_load(&relation->orig_dejitter, samples[0], len); + if (!call->forking && relation->next) + jitter_load(&relation->term_dejitter, samples[1], len); + else + memset(samples[1], 0, len * sizeof(sample_t)); + wave_write(&relation->rec, samples, len); + } + } +} + +void encode_l16(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len) +{ + uint16_t *src = (uint16_t *)src_data, *dst; + int len = src_len / 2, i; + + dst = malloc(len * 2); + if (!dst) + return; + for (i = 0; i < len; i++) + dst[i] = htons(src[i]); + *dst_data = (uint8_t *)dst; + *dst_len = len * 2; +} + +void decode_l16(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len) +{ + uint16_t *src = (uint16_t *)src_data, *dst; + int len = src_len / 2, i; + + dst = malloc(len * 2); + if (!dst) + return; + for (i = 0; i < len; i++) + dst[i] = ntohs(src[i]); + *dst_data = (uint8_t *)dst; + *dst_len = len * 2; +} + -- cgit v1.2.3