From bbb13a13954da9e566b664f7916362dd0313e4d1 Mon Sep 17 00:00:00 2001
From: Stefan Sayer <stefan.sayer@googlemail.com>
Date: Wed, 24 Jun 2015 05:43:22 +0200
Subject: [PATCH] opus: configurable codec settings for OPUS codec

opus.conf may contain an fmtp line with opus codec parameters,
all in one line, e.g.
maxplaybackrate=16000;stereo=0;useinbandfec=1

supported parameters:
 maxplaybackrate
 stereo
 useinbandfec
---
 core/plug-in/opus/opus.c | 179 +++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 172 insertions(+), 7 deletions(-)

diff --git a/core/plug-in/opus/opus.c b/core/plug-in/opus/opus.c
index 57a92bc..a8ad70e 100644
--- a/core/plug-in/opus/opus.c
+++ b/core/plug-in/opus/opus.c
@@ -47,6 +47,7 @@
  */
 
 #include <opus/opus.h>
+#include <stdio.h>
 
 #define _OPUS_APPLICATION_ OPUS_APPLICATION_VOIP
 /* Allowed values:
@@ -88,6 +89,10 @@ static long opus_create(const char* format_parameters, const char** format_param
 			  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
@@ -103,13 +108,14 @@ static void opus_destroy(long h_inst);
 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" , AMCI_NO_MODULEINIT, AMCI_NO_MODULEDESTROY )
+BEGIN_EXPORTS( "opus" , opus_load, AMCI_NO_MODULEDESTROY )
 
   BEGIN_CODECS
-    CODEC( CODEC_OPUS, pcm16_2_opus, opus_2_pcm16, opus_plc,
+    CODEC_WITH_FMT( CODEC_OPUS, pcm16_2_opus, opus_2_pcm16, opus_plc,
            opus_create, 
            opus_destroy,
-           NULL, NULL )
+           NULL, NULL ,
+	   opus_negotiate_fmt)
   END_CODECS
     
   BEGIN_PAYLOADS
@@ -126,13 +132,155 @@ typedef struct {
   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;
+  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", &param_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", &param_value);
+      if (param_value) {
+	if (*param_value == '1')
+	  *stereo = 1;
+	else
+	  *stereo = 0;
+
+	continue;
+      }
+
+      /* useinbandfec */
+      buffer=read_param(buffer, "useinbandfec", &param_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("\n\n\n");
     DBG("OPUS params: >>%s<<.\n", format_parameters);
+    DBG("\n\n\n");
+
+    decode_format_parameters(format_parameters, &maxbandwidth, &useinbandfec, &stereo);
   } 
     
   codec_inst = (opus_state_t*)malloc(sizeof(opus_state_t));
@@ -140,17 +288,34 @@ long opus_create(const char* format_parameters, const char** format_parameters_o
   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);
     return -1;
   }
 
-  opus_encoder_ctl(codec_inst->opus_enc, OPUS_SET_FORCE_CHANNELS(1));
-  opus_encoder_ctl(codec_inst->opus_enc, OPUS_SET_MAX_BANDWIDTH(_OPUS_MAX_BANDWIDTH_));
+  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(_OPUS_INBAND_FEC_));
+  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);
-- 
2.1.4

