summaryrefslogtreecommitdiffstats
path: root/src/router/audio.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/router/audio.c')
-rw-r--r--src/router/audio.c286
1 files changed, 286 insertions, 0 deletions
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 <jolly@eversberg.eu>
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * 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 <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <math.h>
+#include <sys/types.h>
+#include <arpa/inet.h>
+#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;
+}
+