287 lines
8.1 KiB
C
287 lines
8.1 KiB
C
/* 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;
|
|
}
|
|
|