mirror of https://github.com/sipwise/sems.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
420 lines
12 KiB
420 lines
12 KiB
/*
|
|
* Copyright (C) 2002-2003 Fhg Fokus
|
|
*
|
|
* This file is part of SEMS, a free SIP media server.
|
|
*
|
|
* SEMS 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 2 of the License, or
|
|
* (at your option) any later version. This program is released under
|
|
* the GPL with the additional exemption that compiling, linking,
|
|
* and/or using OpenSSL is allowed.
|
|
*
|
|
* For a license to use the SEMS software under conditions
|
|
* other than those described here, or to purchase support for this
|
|
* software, please contact iptel.org by e-mail at the following addresses:
|
|
* info@iptel.org
|
|
*
|
|
* SEMS 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, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "amci.h"
|
|
#include "codecs.h"
|
|
#include "../../log.h"
|
|
|
|
#include <math.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
/**
|
|
* @file plug-in/opus/opus.c
|
|
* OPUS support
|
|
* This plug-in imports the OPUS Codec.
|
|
*
|
|
* See http://www.opus-codec.org/ .
|
|
* Features: <ul>
|
|
* <li>OPUS codec/payload/subtype
|
|
* <li>OPUS file format
|
|
* </ul>
|
|
*
|
|
*/
|
|
|
|
#include <opus/opus.h>
|
|
#include <stdio.h>
|
|
|
|
#define _OPUS_APPLICATION_ OPUS_APPLICATION_VOIP
|
|
/* Allowed values:
|
|
OPUS_APPLICATION_VOIP Process signal for improved speech intelligibility.
|
|
OPUS_APPLICATION_AUDIO Favor faithfulness to the original input.
|
|
OPUS_APPLICATION_RESTRICTED_LOWDELAY Configure the minimum possible coding delay by disabling certain modes of operation.*/
|
|
|
|
#define _OPUS_MAX_BANDWIDTH_ OPUS_BANDWIDTH_FULLBAND
|
|
/* Allowed values:
|
|
OPUS_BANDWIDTH_NARROWBAND - 4 kHz passband
|
|
OPUS_BANDWIDTH_MEDIUMBAND - 6 kHz passband
|
|
OPUS_BANDWIDTH_WIDEBAND - 8 kHz passband
|
|
OPUS_BANDWIDTH_SUPERWIDEBAND - 12 kHz passband
|
|
OPUS_BANDWIDTH_FULLBAND - 20 kHz passband */
|
|
|
|
#define _OPUS_PKT_LOSS_PCT_ 5
|
|
/* Allowed values: 0 - 100 */
|
|
|
|
#define _OPUS_COMPLEXITY_ 10
|
|
/* Allowed values: 0 - 10, where 10 is highest computational complexity */
|
|
|
|
#define _OPUS_INBAND_FEC_ 1
|
|
/* Forward error correction.
|
|
Allowed values: 0 - 1 */
|
|
|
|
#define _OPUS_DTX_ 0
|
|
/* Discontinued transmission
|
|
Allowed values: 0 - 1 */
|
|
|
|
static int opus_2_pcm16( unsigned char* out_buf, unsigned char* in_buf, unsigned int size,
|
|
unsigned int channels, unsigned int rate, long h_codec );
|
|
|
|
static int opus_plc( unsigned char* out_buf, unsigned int size,
|
|
unsigned int channels, unsigned int rate, long h_codec );
|
|
|
|
static int pcm16_2_opus( unsigned char* out_buf, unsigned char* in_buf, unsigned int size,
|
|
unsigned int channels, unsigned int rate, long h_codec );
|
|
static long opus_create(const char* format_parameters, const char** format_parameters_out,
|
|
amci_codec_fmt_info_t** format_description);
|
|
static void opus_destroy(long h_inst);
|
|
|
|
static int opus_negotiate_fmt(int is_offer, const char* params_in, char* params_out, unsigned int params_out_len);
|
|
|
|
static int opus_load(const char* ModConfigPath);
|
|
|
|
#if SYSTEM_SAMPLECLOCK_RATE >= 48000
|
|
#define _OPUS_RATE 48000
|
|
#elif SYSTEM_SAMPLECLOCK_RATE >= 24000
|
|
#define _OPUS_RATE 24000
|
|
#elif SYSTEM_SAMPLECLOCK_RATE >= 12000
|
|
#define _OPUS_RATE 12000
|
|
#elif SYSTEM_SAMPLECLOCK_RATE >= 8000
|
|
#define _OPUS_RATE 8000
|
|
#else
|
|
#error Minimal sample rate for OPUS codec is 8000.
|
|
#endif
|
|
|
|
static amci_codec_fmt_info_t opus_fmt_description[] = { {AMCI_FMT_FRAME_LENGTH, 20},
|
|
{AMCI_FMT_FRAME_SIZE, 20 * _OPUS_RATE / 1000}, {0,0}};
|
|
|
|
BEGIN_EXPORTS( "opus" , opus_load, AMCI_NO_MODULEDESTROY )
|
|
|
|
BEGIN_CODECS
|
|
CODEC_WITH_FMT( CODEC_OPUS, pcm16_2_opus, opus_2_pcm16, opus_plc,
|
|
opus_create,
|
|
opus_destroy,
|
|
NULL, NULL ,
|
|
opus_negotiate_fmt)
|
|
END_CODECS
|
|
|
|
BEGIN_PAYLOADS
|
|
PAYLOAD( -1, "opus", _OPUS_RATE, 48000, 2, CODEC_OPUS, AMCI_PT_AUDIO_FRAME )
|
|
END_PAYLOADS
|
|
|
|
BEGIN_FILE_FORMATS
|
|
END_FILE_FORMATS
|
|
|
|
END_EXPORTS
|
|
|
|
typedef struct {
|
|
OpusEncoder* opus_enc;
|
|
OpusDecoder* opus_dec;
|
|
} opus_state_t;
|
|
|
|
/* e.g. "maxplaybackrate=8000; stereo=0; useinbandfec=1" */
|
|
char default_format_parameters[80];
|
|
|
|
int opus_load(const char* ModConfigPath) {
|
|
default_format_parameters[0]='\0';
|
|
char conf_file[256];
|
|
if (NULL != ModConfigPath) {
|
|
sprintf(conf_file, "%sopus.conf",ModConfigPath);
|
|
FILE* fp = fopen(conf_file, "rt");
|
|
if (fp) {
|
|
char line[80];
|
|
while(fgets(line, 80, fp) != NULL) {
|
|
if (!line[0] ||line[0]=='#')
|
|
continue;
|
|
strcpy(default_format_parameters, line);
|
|
break;
|
|
}
|
|
DBG("initialized default format parameters as '%s'\n", default_format_parameters);
|
|
fclose(fp);
|
|
}
|
|
}
|
|
DBG("OPUS: initialized\n");
|
|
return 0;
|
|
}
|
|
|
|
int opus_negotiate_fmt(int is_offer, const char* params_in, char* params_out, unsigned int params_out_len) {
|
|
// todo: properly negotiating features
|
|
strncpy(params_out, default_format_parameters, params_out_len);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
Search for a parameter assignement in input string.
|
|
If it's not found *param_value is null, otherwise *param_value points to the
|
|
right hand term.
|
|
In both cases a pointer suitable for a new search is returned
|
|
*/
|
|
static char* read_param(char* input, const char *param, char** param_value)
|
|
{
|
|
int param_size;
|
|
|
|
/* Eat spaces and semi-colons */
|
|
while (*input && (*input == ' ' || *input == ';' || *input == '"'))
|
|
input++;
|
|
|
|
*param_value = NULL;
|
|
param_size = strlen(param);
|
|
|
|
if (strncmp(input, param, param_size))
|
|
return input;
|
|
|
|
if (*(input+param_size) != '=')
|
|
return input;
|
|
|
|
/* move by the param_size + 1 */
|
|
input += (param_size + 1);
|
|
|
|
/* Found and discarded a matching parameter */
|
|
*param_value = input;
|
|
while (*input && *input != ' ' && *input != ';' && *input != '"')
|
|
input++;
|
|
|
|
if (*input=='"')
|
|
{
|
|
*param_value = *param_value + 1; /* remove " */
|
|
|
|
/* string will end after next: " */
|
|
while (*input && *input != '"' && *input != '\r' && *input != '\n')
|
|
input++;
|
|
|
|
if (*input == '"')
|
|
input--; /* remove " */
|
|
}
|
|
|
|
if (*input)
|
|
*input = 0;
|
|
|
|
return input;
|
|
}
|
|
|
|
#define BLEN 63
|
|
|
|
void decode_format_parameters(const char* format_parameters, unsigned int* maxbandwidth, int* useinbandfec, int* stereo) {
|
|
if (format_parameters && strlen(format_parameters)<=BLEN){
|
|
|
|
char buffer2[BLEN+1];
|
|
char *buffer = buffer2;
|
|
|
|
strcpy(buffer, format_parameters);
|
|
|
|
while (*buffer) {
|
|
char *param_value;
|
|
|
|
/* maxplaybackrate */
|
|
buffer=read_param(buffer, "maxplaybackrate", ¶m_value);
|
|
if (param_value) {
|
|
*maxbandwidth = atoi(param_value);
|
|
if (!*maxbandwidth) {
|
|
*maxbandwidth = _OPUS_RATE;
|
|
DBG("wrong maxbandwidth value '%s'\n", param_value);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
/* stereo */
|
|
buffer=read_param(buffer, "stereo", ¶m_value);
|
|
if (param_value) {
|
|
if (*param_value == '1')
|
|
*stereo = 1;
|
|
else
|
|
*stereo = 0;
|
|
|
|
continue;
|
|
}
|
|
|
|
/* useinbandfec */
|
|
buffer=read_param(buffer, "useinbandfec", ¶m_value);
|
|
if (param_value) {
|
|
if (*param_value == '1')
|
|
*useinbandfec = 1;
|
|
else
|
|
*useinbandfec = 0;
|
|
|
|
continue;
|
|
}
|
|
|
|
/* Unknown parameter */
|
|
if (*buffer) {
|
|
param_value = buffer;
|
|
|
|
while (*buffer && *buffer != ';')
|
|
buffer++;
|
|
|
|
if (*buffer)
|
|
*buffer = 0;
|
|
|
|
DBG("OPUS: SDP parameter fmtp: %s ignored in creating encoder.\n", param_value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
long opus_create(const char* format_parameters, const char** format_parameters_out,
|
|
amci_codec_fmt_info_t** format_description) {
|
|
opus_state_t* codec_inst;
|
|
int error;
|
|
|
|
unsigned int maxbandwidth = _OPUS_RATE;
|
|
int useinbandfec = _OPUS_INBAND_FEC_;
|
|
int stereo = 0;
|
|
|
|
if (format_parameters) {
|
|
DBG("OPUS params: >>%s<<.\n", format_parameters);
|
|
decode_format_parameters(format_parameters, &maxbandwidth, &useinbandfec, &stereo);
|
|
}
|
|
|
|
codec_inst = (opus_state_t*)malloc(sizeof(opus_state_t));
|
|
|
|
if (!codec_inst)
|
|
return -1;
|
|
|
|
DBG("OPUS: creating encoder with maxbandwidth=%u, stereo=%s, useinbandfec=%s\n",
|
|
maxbandwidth, stereo?"true":"false", useinbandfec?"true":"false");
|
|
|
|
codec_inst->opus_enc = opus_encoder_create(_OPUS_RATE,1,_OPUS_APPLICATION_,&error);
|
|
if (error) {
|
|
DBG("OPUS: error %d while creating encoder state.\n", error);
|
|
free(codec_inst);
|
|
return -1;
|
|
}
|
|
|
|
opus_encoder_ctl(codec_inst->opus_enc, OPUS_SET_FORCE_CHANNELS(stereo ? 2:1));
|
|
|
|
unsigned int opus_set_bw = _OPUS_RATE;
|
|
if (maxbandwidth <= 8000) {
|
|
opus_set_bw = OPUS_BANDWIDTH_NARROWBAND;
|
|
} else if (maxbandwidth <= 12000) {
|
|
opus_set_bw = OPUS_BANDWIDTH_MEDIUMBAND;
|
|
} else if (maxbandwidth <= 16000) {
|
|
opus_set_bw = OPUS_BANDWIDTH_WIDEBAND;
|
|
} else if (maxbandwidth <= 24000) {
|
|
opus_set_bw = OPUS_BANDWIDTH_SUPERWIDEBAND;
|
|
} else {
|
|
opus_set_bw = OPUS_BANDWIDTH_FULLBAND;
|
|
}
|
|
opus_encoder_ctl(codec_inst->opus_enc, OPUS_SET_MAX_BANDWIDTH(opus_set_bw));
|
|
|
|
opus_encoder_ctl(codec_inst->opus_enc, OPUS_SET_PACKET_LOSS_PERC(_OPUS_PKT_LOSS_PCT_));
|
|
opus_encoder_ctl(codec_inst->opus_enc, OPUS_SET_COMPLEXITY(_OPUS_COMPLEXITY_));
|
|
opus_encoder_ctl(codec_inst->opus_enc, OPUS_SET_INBAND_FEC(useinbandfec ? 1:0));
|
|
opus_encoder_ctl(codec_inst->opus_enc, OPUS_SET_DTX(_OPUS_DTX_));
|
|
|
|
codec_inst->opus_dec = opus_decoder_create(_OPUS_RATE,1,&error);
|
|
if (error) {
|
|
DBG("OPUS: error %d while creating decoder state.\n", error);
|
|
opus_encoder_destroy(codec_inst->opus_enc);
|
|
free(codec_inst);
|
|
return -1;
|
|
}
|
|
|
|
*format_description = opus_fmt_description;
|
|
|
|
return (long)codec_inst;
|
|
}
|
|
|
|
void opus_destroy(long h_inst) {
|
|
opus_state_t* codec_inst;
|
|
|
|
if (h_inst) {
|
|
codec_inst = (opus_state_t*)h_inst;
|
|
opus_encoder_destroy(codec_inst->opus_enc);
|
|
opus_decoder_destroy(codec_inst->opus_dec);
|
|
free(codec_inst);
|
|
}
|
|
}
|
|
|
|
int pcm16_2_opus( unsigned char* out_buf, unsigned char* in_buf, unsigned int size,
|
|
unsigned int channels, unsigned int rate, long h_codec )
|
|
{
|
|
opus_state_t* codec_inst;
|
|
int res;
|
|
|
|
if (!h_codec){
|
|
ERROR("opus codec not initialized.\n");
|
|
return 0;
|
|
}
|
|
codec_inst = (opus_state_t*)h_codec;
|
|
|
|
res = opus_encode(codec_inst->opus_enc, (opus_int16*)in_buf, size/2/channels, out_buf, AUDIO_BUFFER_SIZE);
|
|
/* returns bytes in encoded frame */
|
|
|
|
/* DBG ("OPUS encode: size: %d, chan: %d, rate: %d, result %d.\n", size, channels, rate, res); */
|
|
return res;
|
|
}
|
|
|
|
static int opus_2_pcm16( unsigned char* out_buf, unsigned char* in_buf, unsigned int size,
|
|
unsigned int channels, unsigned int rate, long h_codec )
|
|
{
|
|
opus_state_t* codec_inst;
|
|
int res;
|
|
|
|
if (!h_codec){
|
|
ERROR("opus codec not initialized.\n");
|
|
return 0;
|
|
}
|
|
codec_inst = (opus_state_t*)h_codec;
|
|
|
|
if (0<(res = opus_decode(codec_inst->opus_dec, in_buf, size, (opus_int16*)out_buf, AUDIO_BUFFER_SIZE/2, 0))) {
|
|
/* returns samples in encoded frame */
|
|
res*=2;
|
|
}
|
|
|
|
/* DBG ("OPUS decode: size: %d, chan: %d, rate: %d, result %d.\n", size, channels, rate, res); */
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
static int opus_plc( unsigned char* out_buf, unsigned int size,
|
|
unsigned int channels, unsigned int rate, long h_codec )
|
|
{
|
|
opus_state_t* codec_inst;
|
|
int res;
|
|
|
|
if (!h_codec){
|
|
ERROR("opus codec not initialized.\n");
|
|
return 0;
|
|
}
|
|
codec_inst = (opus_state_t*)h_codec;
|
|
|
|
if (size/channels > AUDIO_BUFFER_SIZE) {
|
|
/* DBG("OPUS plc: size %d, chan %d exceeds buffer size %d.\n", size, channels, AUDIO_BUFFER_SIZE); */
|
|
return 0;
|
|
}
|
|
|
|
if (0<(res = opus_decode(codec_inst->opus_dec, NULL, 0, (opus_int16*)out_buf, size/2/channels, 0))) {
|
|
/* returns samples in encoded frame */
|
|
res*=2;
|
|
}
|
|
|
|
/* DBG ("OPUS plc: size: %d, chan: %d, rate: %d, result %d.\n", size, channels, rate, res); */
|
|
|
|
return res;
|
|
}
|
|
|