audio: hal: Volume control support for transcode loopback

Changes to enable volume control for DSP HW
transcode loopback use case type.

CRs-Fixed: 2092579
Change-Id: I48b9020ec1f8137495ddf84151445f423d01a1ff
diff --git a/hal/audio_extn/hw_loopback.c b/hal/audio_extn/hw_loopback.c
index 64f5ae6..9938c04 100644
--- a/hal/audio_extn/hw_loopback.c
+++ b/hal/audio_extn/hw_loopback.c
@@ -41,7 +41,10 @@
 #define PATCH_HANDLE_INVALID 0xFFFF
 #define MAX_SOURCE_PORTS_PER_PATCH 1
 #define MAX_SINK_PORTS_PER_PATCH 1
+#define HW_LOOPBACK_RX_VOLUME     "Trans Loopback RX Volume"
+#define HW_LOOPBACK_RX_UNITY_GAIN 0x2000
 
+#include <math.h>
 #include <stdlib.h>
 #include <pthread.h>
 #include <errno.h>
@@ -131,6 +134,34 @@
     }
 }
 
+/* Set loopback volume : for mute implementation */
+static int hw_loopback_set_volume(struct audio_device *adev, int value)
+{
+    int32_t ret = 0;
+    struct mixer_ctl *ctl;
+    char mixer_ctl_name[MAX_LENGTH_MIXER_CONTROL_IN_INT];
+    snprintf(mixer_ctl_name, sizeof(mixer_ctl_name),
+            "Transcode Loopback Rx Volume");
+
+    ALOGD("%s: (%d)\n", __func__, value);
+
+    ALOGD("%s: Setting HW loopback volume to %d \n", __func__, value);
+    ctl = mixer_get_ctl_by_name(adev->mixer, mixer_ctl_name);
+    if (!ctl) {
+        ALOGE("%s: Could not get ctl for mixer cmd - %s",
+              __func__, mixer_ctl_name);
+        return -EINVAL;
+    }
+
+    if(mixer_ctl_set_value(ctl, 0, value) < 0) {
+        ALOGE("%s: Couldn't set HW Loopback Volume: [%d]", __func__, value);
+        return -EINVAL;
+    }
+
+    ALOGV("%s: exit", __func__);
+    return ret;
+}
+
 /* Initialize patch database */
 int init_patch_database(patch_db_t* patch_db)
 {
@@ -662,8 +693,7 @@
                                     struct audio_port *port_in)
 {
     int status = 0, n=0, patch_num=-1;
-    loopback_patch_t *active_loopback_patch = NULL;
-    port_info_t *port_info = NULL;
+    port_info_t port_info;
     struct audio_port_config *port_out=NULL;
     ALOGV("%s %d", __func__, __LINE__);
 
@@ -675,10 +705,10 @@
 
     pthread_mutex_lock(&audio_loopback_mod->lock);
 
-    port_info->id = port_in->id;
-    port_info->role = port_in->role;              /* sink or source */
-    port_info->type = port_in->type;              /* device, mix ... */
-    port_out = get_port_from_patch_db(port_info, &audio_loopback_mod->patch_db,
+    port_info.id = port_in->id;
+    port_info.role = port_in->role;              /* sink or source */
+    port_info.type = port_in->type;              /* device, mix ... */
+    port_out = get_port_from_patch_db(&port_info, &audio_loopback_mod->patch_db,
                                       &patch_num);
     if (port_out == NULL) {
         ALOGE("%s, Unable to find a valid matching port in patch \
@@ -709,40 +739,64 @@
                                         const struct audio_port_config *config)
 {
     int status = 0, n=0, patch_num=-1;
-    loopback_patch_t *active_loopback_patch = NULL;
-    port_info_t *port_info = NULL;
+    port_info_t port_info;
     struct audio_port_config *port_out=NULL;
+    struct audio_device *adev = audio_loopback_mod->adev;
+    int loopback_gain = HW_LOOPBACK_RX_UNITY_GAIN;
+
     ALOGV("%s %d", __func__, __LINE__);
 
     if ((audio_loopback_mod == NULL) || (dev == NULL)) {
         ALOGE("%s, Invalid device", __func__);
-        status = -1;
+        status = -EINVAL;
         return status;
     }
 
     pthread_mutex_lock(&audio_loopback_mod->lock);
 
-    port_info->id = config->id;
-    port_info->role = config->role;              /* sink or source */
-    port_info->type = config->type;              /* device, mix  */
-    port_out = get_port_from_patch_db(port_info, &audio_loopback_mod->patch_db
+    port_info.id = config->id;
+    port_info.role = config->role;              /* sink or source */
+    port_info.type = config->type;              /* device, mix  */
+    port_out = get_port_from_patch_db(&port_info, &audio_loopback_mod->patch_db
                                     , &patch_num);
 
     if (port_out == NULL) {
         ALOGE("%s, Unable to find a valid matching port in patch \
         database,exiting", __func__);
-        status = -1;
-        return status;
+        status = -EINVAL;
+        goto exit_set_port_config;
     }
 
-    port_out->config_mask = config->config_mask;
-    port_out->channel_mask = config->channel_mask;
-    port_out->format = config->format;
-    port_out->gain = config->gain;
-    port_out->sample_rate = config->sample_rate;
+    port_out->config_mask |= config->config_mask;
+    if(config->config_mask & AUDIO_PORT_CONFIG_CHANNEL_MASK)
+        port_out->channel_mask = config->channel_mask;
+    if(config->config_mask & AUDIO_PORT_CONFIG_FORMAT)
+        port_out->format = config->format;
+    if(config->config_mask & AUDIO_PORT_CONFIG_GAIN)
+        port_out->gain = config->gain;
+    if(config->config_mask & AUDIO_PORT_CONFIG_SAMPLE_RATE)
+        port_out->sample_rate = config->sample_rate;
+
+    /* Convert gain in millibels to ratio and convert to Q13 */
+    loopback_gain = pow(10, (float)((float)port_out->gain.values[0]/2000)) *
+                       (1 << 13);
+    ALOGV("%s, Port config gain_in_mbells: %d, gain_in_q13 : %d", __func__,
+          port_out->gain.values[0], loopback_gain);
+    if((port_out->config_mask & AUDIO_PORT_CONFIG_GAIN) &&
+        port_out->gain.mode == AUDIO_GAIN_MODE_JOINT ) {
+        status = hw_loopback_set_volume(adev, loopback_gain);
+        if (status) {
+            ALOGE("%s, Error setting loopback gain config: status %d",
+                  __func__, status);
+        }
+    } else {
+        ALOGE("%s, Unsupported port config ,exiting", __func__);
+        status = -EINVAL;
+    }
 
     /* Currently, port config is not used for anything,
     need to restart session    */
+exit_set_port_config:
     pthread_mutex_unlock(&audio_loopback_mod->lock);
     return status;
 }
diff --git a/qahw_api/test/trans_loopback_test.c b/qahw_api/test/trans_loopback_test.c
index 0ecbdcb..eb9530e 100644
--- a/qahw_api/test/trans_loopback_test.c
+++ b/qahw_api/test/trans_loopback_test.c
@@ -45,6 +45,7 @@
 #include <sys/socket.h>
 #include <sys/types.h>
 #include <utils/Log.h>
+#include <math.h>
 
 #include <cutils/list.h>
 #include "qahw_api.h"
@@ -87,6 +88,7 @@
 
 FILE * log_file = NULL;
 volatile bool stop_loopback = false;
+static float loopback_gain = 1.0;
 const char *log_filename = NULL;
 
 #define TRANSCODE_LOOPBACK_SOURCE_PORT_ID 0x4C00
@@ -394,6 +396,23 @@
                         &transcode_loopback_config->sink_config,
                         &transcode_loopback_config->patch_handle);
     fprintf(log_file,"\nCreate patch returned %d\n",rc);
+    if(!rc) {
+        struct audio_port_config sink_gain_config;
+        /* Convert loopback gain to millibels */
+        int loopback_gain_in_millibels = 2000 * log10(loopback_gain);
+        sink_gain_config.gain.index = 0;
+        sink_gain_config.gain.mode = AUDIO_GAIN_MODE_JOINT;
+        sink_gain_config.gain.channel_mask = 1;
+        sink_gain_config.gain.values[0] = loopback_gain_in_millibels;
+        sink_gain_config.id = transcode_loopback_config->sink_config.id;
+        sink_gain_config.role = transcode_loopback_config->sink_config.role;
+        sink_gain_config.type = transcode_loopback_config->sink_config.type;
+        sink_gain_config.config_mask = AUDIO_PORT_CONFIG_GAIN;
+
+        (void)qahw_set_audio_port_config(transcode_loopback_config->hal_handle,
+                    &sink_gain_config);
+    }
+
     return rc;
 }
 
@@ -494,7 +513,7 @@
                     j++;
                 }
 
-                if ((dev_path != NULL) && (switch_name != NULL)) 
+                if ((dev_path != NULL) && (switch_name != NULL))
                     fprintf(log_file,"devpath = %s, switch_name = %s \n",dev_path, switch_name);
 
                 if((DEV_NODE_CHECK(tlb_hdmi_in_audio_dev_path, dev_path) == 0)  || (DEV_NODE_CHECK(tlb_hdmi_in_audio_sample_rate_dev_path, dev_path) == 0)
@@ -543,7 +562,7 @@
 int main(int argc, char *argv[]) {
 
     int status = 0;
-    uint32_t play_duration_in_seconds = 600,play_duration_elapsed_msec = 0,play_duration_in_msec = 0, sink_device = 2;
+    uint32_t play_duration_in_seconds = 600,play_duration_elapsed_msec = 0,play_duration_in_msec = 0, sink_device = 2, volume_in_millibels = 0;
     source_port_type_t source_port_type = SOURCE_PORT_NONE;
     log_file = stdout;
     transcode_loopback_config_t    *transcode_loopback_config = NULL;
@@ -553,6 +572,7 @@
         /* These options set a flag. */
         {"sink-device", required_argument,    0, 'd'},
         {"play-duration",  required_argument,    0, 'p'},
+        {"play-volume",  required_argument,    0, 'v'},
         {"help",          no_argument,          0, 'h'},
         {0, 0, 0, 0}
     };
@@ -562,7 +582,7 @@
 
     while ((opt = getopt_long(argc,
                               argv,
-                              "-d:p:h",
+                              "-d:p:v:h",
                               long_options,
                               &option_index)) != -1) {
 
@@ -575,6 +595,9 @@
         case 'p':
             play_duration_in_seconds = atoi(optarg);
             break;
+        case 'v':
+            loopback_gain = atof(optarg);
+            break;
         case 'h':
         default :
             usage();
@@ -584,9 +607,9 @@
     }
 
     fprintf(log_file,"\nTranscode loopback test begin\n");
-    if (play_duration_in_seconds < 0 | play_duration_in_seconds > 3600) {
+    if (play_duration_in_seconds < 0 | play_duration_in_seconds > 360000) {
             fprintf(log_file,
-                    "\nPlayback duration %d invalid or unsupported(range : 1 to 3600, defaulting to 600 seconds )\n",
+                    "\nPlayback duration %d invalid or unsupported(range : 1 to 360000, defaulting to 600 seconds )\n",
                     play_duration_in_seconds);
             play_duration_in_seconds = 600;
     }
@@ -654,8 +677,8 @@
 
 void usage()
 {
-    fprintf(log_file,"\nUsage : trans_loopback_test -p <duration_in_seconds> -d <sink_device_id>\n");
-    fprintf(log_file,"\nExample to play for 1 minute on speaker device: trans_loopback_test -p 60 -d 2\n");
+    fprintf(log_file,"\nUsage : trans_loopback_test -p <duration_in_seconds> -d <sink_device_id> -v <loopback_volume(range 0 to 4.0)>\n");
+    fprintf(log_file,"\nExample to play for 1 minute on speaker device with volume unity: trans_loopback_test -p 60 -d 2 -v 1.0\n");
     fprintf(log_file,"\nExample to play for 5 minutes on headphone device: trans_loopback_test -p 300 -d 8\n");
     fprintf(log_file,"\nHelp : trans_loopback_test -h\n");
  }