hal: configure playback stream rendering based on output policy
For the usecases where individual streams have to be rendered
through different post processing chain on DSP an output policy
is defined. The output configuration is read from the
output policy file based on the stream atrributes to render the
playback appropriately.
Change-Id: I095592013ffa5b04bea69254026ebb78e2934822
diff --git a/hal/audio_extn/utils.c b/hal/audio_extn/utils.c
new file mode 100644
index 0000000..bb34bf0
--- /dev/null
+++ b/hal/audio_extn/utils.c
@@ -0,0 +1,446 @@
+/*
+ * Copyright (c) 2014, The Linux Foundation. All rights reserved.
+ * Not a Contribution.
+ *
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "audio_hw_utils"
+/* #define LOG_NDEBUG 0 */
+
+#include <errno.h>
+#include <cutils/properties.h>
+#include <cutils/config_utils.h>
+#include <stdlib.h>
+#include <dlfcn.h>
+#include <cutils/str_parms.h>
+#include <cutils/log.h>
+#include <cutils/misc.h>
+
+#include "audio_hw.h"
+#include "platform.h"
+#include "platform_api.h"
+#include "audio_extn.h"
+
+#define AUDIO_OUTPUT_POLICY_VENDOR_CONFIG_FILE "/vendor/etc/audio_output_policy.conf"
+
+#define OUTPUTS_TAG "outputs"
+
+#define DYNAMIC_VALUE_TAG "dynamic"
+#define FLAGS_TAG "flags"
+#define FORMATS_TAG "formats"
+#define SAMPLING_RATES_TAG "sampling_rates"
+#define BIT_WIDTH_TAG "bit_width"
+#define APP_TYPE_TAG "app_type"
+
+#define STRING_TO_ENUM(string) { #string, string }
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+
+struct string_to_enum {
+ const char *name;
+ uint32_t value;
+};
+
+const struct string_to_enum s_flag_name_to_enum_table[] = {
+ STRING_TO_ENUM(AUDIO_OUTPUT_FLAG_DIRECT),
+ STRING_TO_ENUM(AUDIO_OUTPUT_FLAG_PRIMARY),
+ STRING_TO_ENUM(AUDIO_OUTPUT_FLAG_FAST),
+ STRING_TO_ENUM(AUDIO_OUTPUT_FLAG_DEEP_BUFFER),
+ STRING_TO_ENUM(AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD),
+ STRING_TO_ENUM(AUDIO_OUTPUT_FLAG_NON_BLOCKING),
+#ifdef INCALL_MUSIC_ENABLED
+ STRING_TO_ENUM(AUDIO_OUTPUT_FLAG_INCALL_MUSIC),
+#endif
+#ifdef COMPRESS_VOIP_ENABLED
+ STRING_TO_ENUM(AUDIO_OUTPUT_FLAG_VOIP_RX),
+#endif
+};
+
+const struct string_to_enum s_format_name_to_enum_table[] = {
+ STRING_TO_ENUM(AUDIO_FORMAT_PCM_16_BIT),
+ STRING_TO_ENUM(AUDIO_FORMAT_PCM_8_BIT),
+ STRING_TO_ENUM(AUDIO_FORMAT_MP3),
+ STRING_TO_ENUM(AUDIO_FORMAT_AAC),
+ STRING_TO_ENUM(AUDIO_FORMAT_VORBIS),
+#ifdef FORMATS_ENABLED
+ STRING_TO_ENUM(AUDIO_FORMAT_AC3),
+ STRING_TO_ENUM(AUDIO_FORMAT_EAC3),
+ STRING_TO_ENUM(AUDIO_FORMAT_DTS),
+ STRING_TO_ENUM(AUDIO_FORMAT_DTS_LBR),
+ STRING_TO_ENUM(AUDIO_FORMAT_WMA),
+ STRING_TO_ENUM(AUDIO_FORMAT_WMA_PRO),
+ STRING_TO_ENUM(AUDIO_FORMAT_AAC_ADIF),
+ STRING_TO_ENUM(AUDIO_FORMAT_AMR_NB),
+ STRING_TO_ENUM(AUDIO_FORMAT_AMR_WB),
+ STRING_TO_ENUM(AUDIO_FORMAT_AMR_WB_PLUS),
+ STRING_TO_ENUM(AUDIO_FORMAT_EVRC),
+ STRING_TO_ENUM(AUDIO_FORMAT_EVRCB),
+ STRING_TO_ENUM(AUDIO_FORMAT_EVRCWB),
+ STRING_TO_ENUM(AUDIO_FORMAT_QCELP),
+ STRING_TO_ENUM(AUDIO_FORMAT_MP2),
+ STRING_TO_ENUM(AUDIO_FORMAT_EVRCNW),
+ STRING_TO_ENUM(AUDIO_FORMAT_PCM_16_BIT_OFFLOAD),
+ STRING_TO_ENUM(AUDIO_FORMAT_PCM_24_BIT_OFFLOAD),
+#endif
+};
+
+static uint32_t string_to_enum(const struct string_to_enum *table, size_t size,
+ const char *name)
+{
+ size_t i;
+ for (i = 0; i < size; i++) {
+ if (strcmp(table[i].name, name) == 0) {
+ ALOGV("%s found %s", __func__, table[i].name);
+ return table[i].value;
+ }
+ }
+ return 0;
+}
+
+static audio_output_flags_t parse_flag_names(char *name)
+{
+ uint32_t flag = 0;
+ char *flag_name = strtok(name, "|");
+ while (flag_name != NULL) {
+ if (strlen(flag_name) != 0) {
+ flag |= string_to_enum(s_flag_name_to_enum_table,
+ ARRAY_SIZE(s_flag_name_to_enum_table),
+ flag_name);
+ }
+ flag_name = strtok(NULL, "|");
+ }
+
+ ALOGV("parse_flag_names: flag - %d", flag);
+ return (audio_output_flags_t)flag;
+}
+
+static void parse_format_names(char *name, struct streams_output_cfg *so_info)
+{
+ struct stream_format *sf_info = NULL;
+ char *str = strtok(name, "|");
+
+ if (str != NULL && strcmp(str, DYNAMIC_VALUE_TAG) == 0)
+ return;
+
+ list_init(&so_info->format_list);
+ while (str != NULL) {
+ audio_format_t format = (audio_format_t)string_to_enum(s_format_name_to_enum_table,
+ ARRAY_SIZE(s_format_name_to_enum_table), str);
+ ALOGV("%s: format - %d", __func__, format);
+ if (format != 0) {
+ sf_info = (struct stream_format *)calloc(1, sizeof(struct stream_format));
+ sf_info->format = format;
+ list_add_tail(&so_info->format_list, &sf_info->list);
+ }
+ str = strtok(NULL, "|");
+ }
+}
+
+static int parse_sample_rate_names(char *name)
+{
+ int sample_rate = 48000;
+ char *str = strtok(name, "|");
+
+ if (str != NULL && strcmp(str, DYNAMIC_VALUE_TAG))
+ sample_rate = (int)strtol(str, (char **)NULL, 10);
+
+ ALOGV("%s: sample_rate - %d", __func__, sample_rate);
+ return sample_rate;
+}
+
+static int parse_bit_width_names(char *name)
+{
+ int bit_width = 16;
+ char *str = strtok(name, "|");
+
+ if (str != NULL && strcmp(str, DYNAMIC_VALUE_TAG))
+ bit_width = (int)strtol(str, (char **)NULL, 10);
+
+ ALOGV("%s: bit_width - %d", __func__, bit_width);
+ return bit_width;
+}
+
+static int parse_app_type_names(void *platform, char *name)
+{
+ int app_type = 0; /* TODO: default app type from acdb when exposed using "platform" */
+ char *str = strtok(name, "|");
+
+ if (str != NULL && strcmp(str, DYNAMIC_VALUE_TAG))
+ app_type = (int)strtol(str, (char **)NULL, 10);
+
+ ALOGV("%s: app_type - %d", __func__, app_type);
+ return app_type;
+}
+
+static void update_streams_output_cfg_list(cnode *root, void *platform,
+ struct listnode *streams_output_cfg_list)
+{
+ cnode *node = root->first_child;
+ struct streams_output_cfg *so_info;
+
+ ALOGV("%s", __func__);
+ so_info = (struct streams_output_cfg *)calloc(1, sizeof(struct streams_output_cfg));
+ while (node) {
+ if (strcmp(node->name, FLAGS_TAG) == 0) {
+ so_info->flags = parse_flag_names((char *)node->value);
+ } else if (strcmp(node->name, FORMATS_TAG) == 0) {
+ parse_format_names((char *)node->value, so_info);
+ } else if (strcmp(node->name, SAMPLING_RATES_TAG) == 0) {
+ so_info->app_type_cfg.sample_rate = parse_sample_rate_names((char *)node->value);
+ } else if (strcmp(node->name, BIT_WIDTH_TAG) == 0) {
+ so_info->app_type_cfg.bit_width = parse_bit_width_names((char *)node->value);
+ } else if (strcmp(node->name, APP_TYPE_TAG) == 0) {
+ so_info->app_type_cfg.app_type = parse_app_type_names(platform, (char *)node->value);
+ }
+ node = node->next;
+ }
+ list_add_tail(streams_output_cfg_list, &so_info->list);
+}
+
+static void load_output(cnode *root, void *platform,
+ struct listnode *streams_output_cfg_list)
+{
+ cnode *node = config_find(root, OUTPUTS_TAG);
+ if (node == NULL) {
+ ALOGE("%s: could not load output, node is NULL", __func__);
+ return;
+ }
+
+ node = node->first_child;
+ while (node) {
+ ALOGV("%s: loading output %s", __func__, node->name);
+ update_streams_output_cfg_list(node, platform, streams_output_cfg_list);
+ node = node->next;
+ }
+}
+
+static void send_app_type_cfg(void *platform, struct mixer *mixer,
+ struct listnode *streams_output_cfg_list)
+{
+ int app_type_cfg[MAX_LENGTH_MIXER_CONTROL_IN_INT] = {-1};
+ int length = 0, i, num_app_types = 0;
+ struct listnode *node;
+ bool update;
+ struct mixer_ctl *ctl = NULL;
+ const char *mixer_ctl_name = "App Type Config";
+ struct streams_output_cfg *so_info;
+
+ if (!mixer) {
+ ALOGE("%s: mixer is null",__func__);
+ return;
+ }
+ ctl = mixer_get_ctl_by_name(mixer, mixer_ctl_name);
+ if (!ctl) {
+ ALOGE("%s: Could not get ctl for mixer cmd - %s",__func__, mixer_ctl_name);
+ return;
+ }
+ if (streams_output_cfg_list == NULL) {
+ app_type_cfg[length++] = 1;
+ app_type_cfg[length++] = 0; /* TODO: default app type from acdb when exposed from "platform" */
+ app_type_cfg[length++] = 48000;
+ app_type_cfg[length++] = 16;
+ mixer_ctl_set_array(ctl, app_type_cfg, length);
+ return;
+ }
+
+ app_type_cfg[length++] = num_app_types;
+ list_for_each(node, streams_output_cfg_list) {
+ so_info = node_to_item(node, struct streams_output_cfg, list);
+ update = true;
+ for (i=0; i<length; i=i+3) {
+ if (app_type_cfg[i+1] == -1)
+ break;
+ else if (app_type_cfg[i+1] == so_info->app_type_cfg.app_type) {
+ update = false;
+ break;
+ }
+ }
+ if (update && ((length + 3) <= MAX_LENGTH_MIXER_CONTROL_IN_INT)) {
+ num_app_types += 1 ;
+ app_type_cfg[length++] = so_info->app_type_cfg.app_type;
+ app_type_cfg[length++] = so_info->app_type_cfg.sample_rate;
+ app_type_cfg[length++] = so_info->app_type_cfg.bit_width;
+ }
+ }
+ ALOGV("%s: num_app_types: %d", __func__, num_app_types);
+ if (num_app_types) {
+ app_type_cfg[0] = num_app_types;
+ mixer_ctl_set_array(ctl, app_type_cfg, length);
+ }
+}
+
+void audio_extn_utils_update_streams_output_cfg_list(void *platform,
+ struct mixer *mixer,
+ struct listnode *streams_output_cfg_list)
+{
+ cnode *root;
+ char *data;
+
+ ALOGV("%s", __func__);
+ list_init(streams_output_cfg_list);
+ data = (char *)load_file(AUDIO_OUTPUT_POLICY_VENDOR_CONFIG_FILE, NULL);
+ if (data == NULL) {
+ send_app_type_cfg(platform, mixer, NULL);
+ ALOGE("%s: could not load output policy config file", __func__);
+ return;
+ }
+
+ root = config_node("", "");
+ config_load(root, data);
+ load_output(root, platform, streams_output_cfg_list);
+
+ send_app_type_cfg(platform, mixer, streams_output_cfg_list);
+}
+
+void audio_extn_utils_dump_streams_output_cfg_list(
+ struct listnode *streams_output_cfg_list)
+{
+ int i=0;
+ struct listnode *node_i, *node_j;
+ struct streams_output_cfg *so_info;
+ struct stream_format *sf_info;
+ ALOGV("%s", __func__);
+ list_for_each(node_i, streams_output_cfg_list) {
+ so_info = node_to_item(node_i, struct streams_output_cfg, list);
+ ALOGV("%d: flags-%d, output_sample_rate-%d, output_bit_width-%d, app_type-%d",
+ i++, so_info->flags, so_info->app_type_cfg.sample_rate,
+ so_info->app_type_cfg.bit_width, so_info->app_type_cfg.app_type);
+ list_for_each(node_j, &so_info->format_list) {
+ sf_info = node_to_item(node_j, struct stream_format, list);
+ ALOGV("format-%x", sf_info->format);
+ }
+ }
+}
+
+void audio_extn_utils_release_streams_output_cfg_list(
+ struct listnode *streams_output_cfg_list)
+{
+ struct listnode *node_i, *node_j;
+ struct streams_output_cfg *so_info;
+ struct stream_format *sf_info;
+
+ ALOGV("%s", __func__);
+ while (!list_empty(streams_output_cfg_list)) {
+ node_i = list_head(streams_output_cfg_list);
+ so_info = node_to_item(node_i, struct streams_output_cfg, list);
+ while (!list_empty(&so_info->format_list)) {
+ node_j = list_head(&so_info->format_list);
+ list_remove(node_j);
+ free(node_to_item(node_j, struct stream_format, list));
+ }
+ list_remove(node_i);
+ free(node_to_item(node_i, struct streams_output_cfg, list));
+ }
+}
+
+void audio_extn_utils_update_stream_app_type_cfg(void *platform,
+ struct listnode *streams_output_cfg_list,
+ audio_output_flags_t flags,
+ audio_format_t format,
+ struct stream_app_type_cfg *app_type_cfg)
+{
+ struct listnode *node_i, *node_j;
+ struct streams_output_cfg *so_info;
+ struct stream_format *sf_info;
+
+ ALOGV("%s: flags: %x, format: %x", __func__, flags, format);
+ list_for_each(node_i, streams_output_cfg_list) {
+ so_info = node_to_item(node_i, struct streams_output_cfg, list);
+ if (so_info->flags == flags) {
+ list_for_each(node_j, &so_info->format_list) {
+ sf_info = node_to_item(node_j, struct stream_format, list);
+ if (sf_info->format == format) {
+ ALOGV("App type: %d", so_info->app_type_cfg.app_type);
+ app_type_cfg->app_type = so_info->app_type_cfg.app_type;
+ app_type_cfg->sample_rate = so_info->app_type_cfg.sample_rate;
+ app_type_cfg->bit_width = so_info->app_type_cfg.bit_width;
+ return;
+ }
+ }
+ }
+ }
+ list_for_each(node_i, streams_output_cfg_list) {
+ so_info = node_to_item(node_i, struct streams_output_cfg, list);
+ if (so_info->flags == AUDIO_OUTPUT_FLAG_PRIMARY) {
+ ALOGV("Compatible output profile not found.");
+ ALOGV("App type default to primary output: %d", so_info->app_type_cfg.app_type);
+ app_type_cfg->app_type = so_info->app_type_cfg.app_type;
+ app_type_cfg->sample_rate = so_info->app_type_cfg.sample_rate;
+ app_type_cfg->bit_width = so_info->app_type_cfg.bit_width;
+ return;
+ }
+ }
+ ALOGW("%s: App type could not be selected. Falling back to default", __func__);
+ app_type_cfg->app_type = 0; /* TODO: default app type from acdb when exposed from "platform" */
+ app_type_cfg->sample_rate = 48000;
+ app_type_cfg->bit_width = 16;
+}
+
+int audio_extn_utils_send_app_type_cfg(struct audio_usecase *usecase)
+{
+ char mixer_ctl_name[MAX_LENGTH_MIXER_CONTROL_IN_INT];
+ int app_type_cfg[MAX_LENGTH_MIXER_CONTROL_IN_INT], len = 0, rc;
+ struct stream_out *out = usecase->stream.out;
+ struct audio_device *adev = out->dev;
+ struct mixer_ctl *ctl;
+ int pcm_device_id, acdb_dev_id, snd_device = usecase->out_snd_device;
+
+ ALOGV("%s", __func__);
+
+ if (usecase->type != PCM_PLAYBACK) {
+ ALOGV("%s: not a playback path, no need to cfg app type", __func__);
+ rc = 0;
+ goto exit_send_app_type_cfg;
+ }
+ if ((usecase->id != USECASE_AUDIO_PLAYBACK_DEEP_BUFFER) &&
+ (usecase->id != USECASE_AUDIO_PLAYBACK_LOW_LATENCY) &&
+ (usecase->id != USECASE_AUDIO_PLAYBACK_MULTI_CH) &&
+ (usecase->id != USECASE_AUDIO_PLAYBACK_OFFLOAD)) {
+ ALOGV("%s: a playback path where app type cfg is not required", __func__);
+ rc = 0;
+ goto exit_send_app_type_cfg;
+ }
+
+ snd_device = usecase->out_snd_device;
+
+ pcm_device_id = platform_get_pcm_device_id(out->usecase, PCM_PLAYBACK);
+
+ snprintf(mixer_ctl_name, sizeof(mixer_ctl_name),
+ "Audio Stream %d App Type Cfg", pcm_device_id);
+
+ 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);
+ rc = -EINVAL;
+ goto exit_send_app_type_cfg;
+ }
+ snd_device = (snd_device == SND_DEVICE_OUT_SPEAKER) ?
+ audio_extn_get_spkr_prot_snd_device(snd_device) : snd_device;
+ acdb_dev_id = platform_get_snd_device_acdb_id(snd_device);
+ if (acdb_dev_id < 0) {
+ ALOGE("%s: Couldn't get the acdb dev id", __func__);
+ rc = -EINVAL;
+ goto exit_send_app_type_cfg;
+ }
+ app_type_cfg[len++] = out->app_type_cfg.app_type;
+ app_type_cfg[len++] = acdb_dev_id;
+
+ mixer_ctl_set_array(ctl, app_type_cfg, len);
+
+ rc = 0;
+exit_send_app_type_cfg:
+ return rc;
+}