audio: hal: Add Hal extension for transcode loopback usecase
Adding HAL extension for transcode hw loopback
Userspace qahwi APIs introduced to exercise this extension
Change-Id: Iffae3f6936c2519f0c5349c2430eb598b97c3364
diff --git a/configure.ac b/configure.ac
index 6a0e9c2..e3d1bc0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -111,6 +111,7 @@
AM_CONDITIONAL([AUDIO_IP_HDLR], [test x$AUDIO_FEATURE_IP_HDLR_ENABLED = xtrue])
AM_CONDITIONAL([SPLIT_A2DP], [test x$AUDIO_FEATURE_ENABLED_SPLIT_A2DP = xtrue])
AM_CONDITIONAL([QAF_SUPPORT], [test x$AUDIO_FEATURE_ENABLED_QAF = xtrue])
+AM_CONDITIONAL([AUDIO_HW_LOOPBACK], [test x$AUDIO_FEATURE_ENABLED_AUDIO_HW_LOOPBACK = xtrue])
AC_CONFIG_FILES([ \
Makefile \
diff --git a/hal/Makefile.am b/hal/Makefile.am
index bc71c50..2c811f6 100644
--- a/hal/Makefile.am
+++ b/hal/Makefile.am
@@ -177,6 +177,11 @@
c_sources += audio_extn/qaf.c
endif
+if AUDIO_HW_LOOPBACK
+AM_CFLAGS += -DAUDIO_HW_LOOPBACK_ENABLED
+c_sources += audio_extn/hw_loopback.c
+endif
+
h_sources = audio_extn/audio_defs.h \
audio_extn/audio_extn.h \
audio_hw.h \
diff --git a/hal/audio_extn/adsp_hdlr.c b/hal/audio_extn/adsp_hdlr.c
index 436da96..3b073c3 100644
--- a/hal/audio_extn/adsp_hdlr.c
+++ b/hal/audio_extn/adsp_hdlr.c
@@ -423,6 +423,7 @@
pthread_mutex_lock(&adsp_hdlr_inst->event_list_lock);
if (list_empty(&adsp_hdlr_inst->event_list)) {
ALOGD("%s: event list is empty", __func__);
+ pthread_mutex_unlock(&adsp_hdlr_inst->event_list_lock);
return 0;
}
list_for_each_safe(node, tempnode, &adsp_hdlr_inst->event_list) {
diff --git a/hal/audio_extn/audio_extn.h b/hal/audio_extn/audio_extn.h
index 7c86d18..ff35374 100644
--- a/hal/audio_extn/audio_extn.h
+++ b/hal/audio_extn/audio_extn.h
@@ -884,4 +884,55 @@
int audio_extn_utils_set_channel_map(
struct stream_out *out,
struct audio_out_channel_map_param *channel_map_param);
+#ifdef AUDIO_HW_LOOPBACK_ENABLED
+/* API to create audio patch */
+int audio_extn_hw_loopback_create_audio_patch(struct audio_hw_device *dev,
+ unsigned int num_sources,
+ const struct audio_port_config *sources,
+ unsigned int num_sinks,
+ const struct audio_port_config *sinks,
+ audio_patch_handle_t *handle);
+/* API to release audio patch */
+int audio_extn_hw_loopback_release_audio_patch(struct audio_hw_device *dev,
+ audio_patch_handle_t handle);
+
+int audio_extn_hw_loopback_set_audio_port_config(struct audio_hw_device *dev,
+ const struct audio_port_config *config);
+int audio_extn_hw_loopback_get_audio_port(struct audio_hw_device *dev,
+ struct audio_port *port_in);
+int audio_extn_loopback_init(struct audio_device *adev);
+void audio_extn_loopback_deinit(struct audio_device *adev);
+#else
+static int __unused audio_extn_hw_loopback_create_audio_patch(struct audio_hw_device *dev __unused,
+ unsigned int num_sources __unused,
+ const struct audio_port_config *sources __unused,
+ unsigned int num_sinks __unused,
+ const struct audio_port_config *sinks __unused,
+ audio_patch_handle_t *handle __unused)
+{
+ return -ENOSYS;
+}
+static int __unused audio_extn_hw_loopback_release_audio_patch(struct audio_hw_device *dev __unused,
+ audio_patch_handle_t handle __unused)
+{
+ return -ENOSYS;
+}
+static int __unused audio_extn_hw_loopback_set_audio_port_config(struct audio_hw_device *dev __unused,
+ const struct audio_port_config *config __unused)
+{
+ return -ENOSYS;
+}
+static int __unused audio_extn_hw_loopback_get_audio_port(struct audio_hw_device *dev __unused,
+ struct audio_port *port_in __unused)
+{
+ return -ENOSYS;
+}
+static int __unused audio_extn_loopback_init(struct audio_device *adev __unused)
+{
+ return -ENOSYS;
+}
+static void __unused audio_extn_loopback_deinit(struct audio_device *adev __unused)
+{
+}
+#endif
#endif /* AUDIO_EXTN_H */
diff --git a/hal/audio_extn/hw_loopback.c b/hal/audio_extn/hw_loopback.c
new file mode 100644
index 0000000..ddca6b8
--- /dev/null
+++ b/hal/audio_extn/hw_loopback.c
@@ -0,0 +1,722 @@
+/*
+* Copyright (c) 2017, The Linux Foundation. All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions are
+* met:
+* * Redistributions of source code must retain the above copyright
+* notice, this list of conditions and the following disclaimer.
+* * Redistributions in binary form must reproduce the above
+* copyright notice, this list of conditions and the following
+* disclaimer in the documentation and/or other materials provided
+* with the distribution.
+* * Neither the name of The Linux Foundation nor the names of its
+* contributors may be used to endorse or promote products derived
+* from this software without specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#define LOG_TAG "audio_hw_loopback"
+/*#define LOG_NDEBUG 0*/
+/*#define VERY_VERY_VERBOSE_LOGGING*/
+#ifdef VERY_VERY_VERBOSE_LOGGING
+#define ALOGVV ALOGV
+#else
+#define ALOGVV(a...) do { } while(0)
+#endif
+
+#define MAX_NUM_PATCHES 1
+#define MAX_NUM_HW_LOOPBACK_PATCHES 1
+#define PATCH_HANDLE_INVALID 0xFFFF
+#define MAX_SOURCE_PORTS_PER_PATCH 1
+#define MAX_SINK_PORTS_PER_PATCH 1
+
+#include <stdlib.h>
+#include <pthread.h>
+#include <errno.h>
+#include <dlfcn.h>
+#include <sys/resource.h>
+#include <sys/prctl.h>
+#include <cutils/properties.h>
+#include <cutils/str_parms.h>
+#include <cutils/log.h>
+#include <cutils/atomic.h>
+#include "audio_utils/primitives.h"
+#include "audio_hw.h"
+#include "platform_api.h"
+#include <platform.h>
+#include <system/thread_defs.h>
+#include <cutils/sched_policy.h>
+#include "audio_extn.h"
+#include "sound/compress_params.h"
+#include <system/audio.h>
+
+/*
+* Unique patch handle ID = (unique_patch_handle_type << 8 | patch_handle_num)
+* Eg : HDMI_IN_SPKR_OUT handles can be 0x1000, 0x1001 and so on..
+*/
+typedef enum patch_handle_type {
+ AUDIO_PATCH_HDMI_IN_SPKR_OUT=0x10,
+ AUDIO_PATCH_SPDIF_IN_SPKR_OUT,
+ AUDIO_PATCH_MIC_IN_SPKR_OUT,
+ AUDIO_PATCH_MIC_IN_HDMI_OUT
+} patch_handle_type_t;
+
+typedef enum patch_state {
+ PATCH_INACTIVE,// Patch is not created yet
+ PATCH_CREATED, // Patch created but not in running state yet, probably due
+ // to lack of proper port config
+ PATCH_RUNNING, // Patch in running state, moves to this state when patch
+ // created and proper port config is available
+} patch_state_t;
+
+typedef struct loopback_patch {
+ audio_patch_handle_t patch_handle_id; /* patch unique ID */
+ struct audio_port_config loopback_source; /* Source port config */
+ struct audio_port_config loopback_sink; /* Source port config */
+ struct compress *source_stream; /* Source stream */
+ struct compress *sink_stream; /* Source stream */
+ struct stream_inout patch_stream; /* InOut type stream */
+ patch_state_t patch_state; /* Patch operation state */
+} loopback_patch_t;
+
+typedef struct patch_db_struct {
+ int32_t num_patches;
+ loopback_patch_t loopback_patch[MAX_NUM_PATCHES];
+} patch_db_t;
+
+typedef struct audio_loopback {
+ struct audio_device *adev;
+ patch_db_t patch_db;
+ audio_usecase_t uc_id;
+ usecase_type_t uc_type;
+ pthread_mutex_t lock;
+} audio_loopback_t;
+
+typedef struct port_info {
+ audio_port_handle_t id; /* port unique ID */
+ audio_port_role_t role; /* sink or source */
+ audio_port_type_t type; /* device, mix ... */
+} port_info_t;
+
+/* Audio loopback module struct */
+static audio_loopback_t *audio_loopback_mod = NULL;
+
+uint32_t format_to_bitwidth(audio_format_t format)
+{
+ switch (format) {
+ case AUDIO_FORMAT_PCM_16_BIT:
+ return 16;
+ case AUDIO_FORMAT_PCM_8_BIT:
+ return 8;
+ case AUDIO_FORMAT_PCM_32_BIT:
+ return 32;
+ case AUDIO_FORMAT_PCM_8_24_BIT:
+ return 32;
+ case AUDIO_FORMAT_PCM_24_BIT_PACKED:
+ return 24;
+ default:
+ return 16;
+ }
+}
+
+/* Initialize patch database */
+int init_patch_database(patch_db_t* patch_db)
+{
+ int patch_init_rc = 0, patch_num=0;
+ patch_db->num_patches = 0;
+ for (patch_num=0;patch_num < MAX_NUM_PATCHES;patch_num++) {
+ patch_db->loopback_patch[patch_num].patch_handle_id = (int32_t)
+ PATCH_HANDLE_INVALID;
+ }
+ return patch_init_rc;
+}
+
+/* Get patch type based on source and sink ports configuration */
+/* Only ports of type 'DEVICE' are supported */
+patch_handle_type_t get_loopback_patch_type(loopback_patch_t* loopback_patch)
+{
+ bool is_source_hdmi=false, is_sink_spkr=false;
+ if (loopback_patch->patch_handle_id != PATCH_HANDLE_INVALID) {
+ ALOGE("%s, Patch handle already exists", __func__);
+ return loopback_patch->patch_handle_id;
+ }
+
+ if (loopback_patch->loopback_source.role == AUDIO_PORT_ROLE_SOURCE) {
+ switch (loopback_patch->loopback_source.type) {
+ case AUDIO_PORT_TYPE_DEVICE :
+ if ((loopback_patch->loopback_source.config_mask &
+ AUDIO_PORT_CONFIG_FORMAT) && (loopback_patch->loopback_source.ext.device.type & AUDIO_DEVICE_IN_HDMI)) {
+ switch (loopback_patch->loopback_source.format) {
+ case AUDIO_FORMAT_PCM:
+ case AUDIO_FORMAT_PCM_16_BIT:
+ case AUDIO_FORMAT_PCM_8_24_BIT:
+ case AUDIO_FORMAT_PCM_24_BIT_PACKED:
+ case AUDIO_FORMAT_IEC61937:
+ case AUDIO_FORMAT_AC3:
+ case AUDIO_FORMAT_E_AC3:
+ is_source_hdmi = true;
+ break;
+ }
+ }
+ break;
+ default :
+ break;
+ //Unsupported as of now, need to extend for other source types
+ }
+ }
+ if (loopback_patch->loopback_sink.role == AUDIO_PORT_ROLE_SINK) {
+ switch (loopback_patch->loopback_sink.type) {
+ case AUDIO_PORT_TYPE_DEVICE :
+ if ((loopback_patch->loopback_sink.config_mask &
+ AUDIO_PORT_CONFIG_FORMAT) &&
+ (loopback_patch->loopback_sink.ext.device.type &
+ AUDIO_DEVICE_OUT_SPEAKER)) {
+ switch (loopback_patch->loopback_sink.format) {
+ case AUDIO_FORMAT_PCM:
+ case AUDIO_FORMAT_PCM_16_BIT:
+ case AUDIO_FORMAT_PCM_32_BIT:
+ case AUDIO_FORMAT_PCM_8_24_BIT:
+ case AUDIO_FORMAT_PCM_24_BIT_PACKED:
+ is_sink_spkr = true;
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+ default :
+ break;
+ //Unsupported as of now, need to extend for other sink types
+ }
+ }
+ if (is_source_hdmi && is_sink_spkr) {
+ return AUDIO_PATCH_HDMI_IN_SPKR_OUT;
+ }
+ ALOGE("%s, Unsupported source or sink port config", __func__);
+ return loopback_patch->patch_handle_id;
+}
+
+/* Releases an existing loopback session */
+/* Conditions : Session setup goes bad or actual session teardown */
+int32_t release_loopback_session(loopback_patch_t *active_loopback_patch)
+{
+ int32_t ret = 0;
+ struct audio_usecase *uc_info;
+ struct audio_device *adev = audio_loopback_mod->adev;
+
+ /* 1. Close the PCM devices */
+ if (active_loopback_patch->source_stream) {
+ compress_close(active_loopback_patch->source_stream);
+ active_loopback_patch->source_stream = NULL;
+ } else {
+ ALOGE("%s: Failed to close loopback stream in capture path",
+ __func__);
+ }
+ if (active_loopback_patch->sink_stream) {
+ compress_close(active_loopback_patch->sink_stream);
+ active_loopback_patch->sink_stream = NULL;
+ } else {
+ ALOGE("%s: Failed to close loopback stream in playback path",
+ __func__);
+ }
+
+ uc_info = get_usecase_from_list(adev, audio_loopback_mod->uc_id);
+ if (uc_info == NULL) {
+ ALOGE("%s: Could not find the loopback usecase (%d) in the list",
+ __func__, active_loopback_patch->patch_handle_id);
+ return -EINVAL;
+ }
+
+ active_loopback_patch->patch_state = PATCH_INACTIVE;
+
+ /* 2. Get and set stream specific mixer controls */
+ disable_audio_route(adev, uc_info);
+
+ /* 3. Disable the rx and tx devices */
+ disable_snd_device(adev, uc_info->out_snd_device);
+ disable_snd_device(adev, uc_info->in_snd_device);
+
+ list_remove(&uc_info->list);
+ free(uc_info);
+
+ ALOGD("%s: Release loopback session exit: status(%d)", __func__, ret);
+ return ret;
+}
+
+/* Create a loopback session based on active loopback patch selected */
+int create_loopback_session(loopback_patch_t *active_loopback_patch)
+{
+ int32_t ret = 0, bits_per_sample;
+ struct audio_usecase *uc_info;
+ int32_t pcm_dev_asm_rx_id, pcm_dev_asm_tx_id;
+ char dummy_write_buf[64];
+ struct audio_device *adev = audio_loopback_mod->adev;
+ struct compr_config source_config, sink_config;
+ struct snd_codec codec;
+ struct audio_port_config *source_patch_config = &active_loopback_patch->
+ loopback_source;
+ struct audio_port_config *sink_patch_config = &active_loopback_patch->
+ loopback_sink;
+
+ ALOGD("%s: Create loopback session begin", __func__);
+
+ uc_info = (struct audio_usecase *)calloc(1, sizeof(struct audio_usecase));
+
+ if (!uc_info) {
+ ALOGE("%s: Failure to open loopback session", __func__);
+ return -ENOMEM;
+ }
+
+ uc_info->id = USECASE_AUDIO_TRANSCODE_LOOPBACK;
+ uc_info->type = audio_loopback_mod->uc_type;
+ uc_info->stream.inout = &active_loopback_patch->patch_stream;
+ uc_info->devices = active_loopback_patch->patch_stream.out_config.devices;
+ uc_info->in_snd_device = SND_DEVICE_NONE;
+ uc_info->out_snd_device = SND_DEVICE_NONE;
+
+ list_add_tail(&adev->usecase_list, &uc_info->list);
+
+ select_devices(adev, uc_info->id);
+
+ pcm_dev_asm_rx_id = platform_get_pcm_device_id(uc_info->id, PCM_PLAYBACK);
+ pcm_dev_asm_tx_id = platform_get_pcm_device_id(uc_info->id, PCM_CAPTURE);
+
+ if (pcm_dev_asm_rx_id < 0 || pcm_dev_asm_tx_id < 0 ) {
+ ALOGE("%s: Invalid PCM devices (asm: rx %d tx %d) for the usecase(%d)",
+ __func__, pcm_dev_asm_rx_id, pcm_dev_asm_tx_id, uc_info->id);
+ ret = -EIO;
+ goto exit;
+ }
+
+ ALOGD("%s: LOOPBACK PCM devices (rx: %d tx: %d) usecase(%d)",
+ __func__, pcm_dev_asm_rx_id, pcm_dev_asm_tx_id, uc_info->id);
+
+ if (source_patch_config->format == AUDIO_FORMAT_IEC61937) {
+ // This is needed to set a known format to DSP and handle
+ // any format change via ADSP event
+ codec.id = AUDIO_FORMAT_AC3;
+ }
+
+ /* Set config for compress stream open in capture path */
+ codec.id = get_snd_codec_id(source_patch_config->format);
+ codec.ch_in = audio_channel_count_from_out_mask(source_patch_config->
+ channel_mask);
+ codec.ch_out = 2; // Irrelevant for loopback case in this direction
+ codec.sample_rate = source_patch_config->sample_rate;
+ codec.format = hal_format_to_alsa(source_patch_config->format);
+ source_config.fragment_size = 1024;
+ source_config.fragments = 1;
+ source_config.codec = &codec;
+
+ /* Open compress stream in capture path */
+ active_loopback_patch->source_stream = compress_open(adev->snd_card,
+ pcm_dev_asm_tx_id, COMPRESS_OUT, &source_config);
+ if (active_loopback_patch->source_stream && !is_compress_ready(
+ active_loopback_patch->source_stream)) {
+ ALOGE("%s: %s", __func__, compress_get_error(active_loopback_patch->
+ source_stream));
+ ret = -EIO;
+ goto exit;
+ } else if (active_loopback_patch->source_stream == NULL) {
+ ALOGE("%s: Failure to open loopback stream in capture path", __func__);
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ /* Set config for compress stream open in playback path */
+ codec.id = get_snd_codec_id(sink_patch_config->format);
+ codec.ch_in = 2; // Irrelevant for loopback case in this direction
+ codec.ch_out = audio_channel_count_from_out_mask(sink_patch_config->
+ channel_mask);
+ codec.sample_rate = sink_patch_config->sample_rate;
+ codec.format = hal_format_to_alsa(sink_patch_config->format);
+ sink_config.fragment_size = 1024;
+ sink_config.fragments = 1;
+ sink_config.codec = &codec;
+
+ /* Open compress stream in playback path */
+ active_loopback_patch->sink_stream = compress_open(adev->snd_card,
+ pcm_dev_asm_rx_id, COMPRESS_IN, &sink_config);
+ if (active_loopback_patch->sink_stream && !is_compress_ready(
+ active_loopback_patch->sink_stream)) {
+ ALOGE("%s: %s", __func__, compress_get_error(active_loopback_patch->
+ sink_stream));
+ ret = -EIO;
+ goto exit;
+ } else if (active_loopback_patch->sink_stream == NULL) {
+ ALOGE("%s: Failure to open loopback stream in playback path", __func__);
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ active_loopback_patch->patch_state = PATCH_CREATED;
+
+ if (compress_start(active_loopback_patch->source_stream) < 0) {
+ ALOGE("%s: Failure to start loopback stream in capture path",
+ __func__);
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ /* Dummy compress_write to ensure compress_start does not fail */
+ compress_write(active_loopback_patch->sink_stream, dummy_write_buf, 64);
+ if (compress_start(active_loopback_patch->sink_stream) < 0) {
+ ALOGE("%s: Cannot start loopback stream in playback path",
+ __func__);
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ /* Move patch state to running, now that session is set up */
+ active_loopback_patch->patch_state = PATCH_RUNNING;
+ ALOGD("%s: Create loopback session end: status(%d)", __func__, ret);
+ return ret;
+
+exit:
+ ALOGE("%s: Problem in Loopback session creation: \
+ status(%d), releasing session ", __func__, ret);
+ release_loopback_session(active_loopback_patch);
+ return ret;
+}
+
+void update_patch_stream_config(struct stream_config *stream_cfg ,
+ struct audio_port_config *port_cfg)
+{
+ stream_cfg->sample_rate = port_cfg->sample_rate;
+ stream_cfg->channel_mask = port_cfg->channel_mask;
+ stream_cfg->format = port_cfg->format;
+ stream_cfg->devices = port_cfg->ext.device.type;
+ stream_cfg->bit_width = format_to_bitwidth(port_cfg->format);
+}
+/* API to create audio patch */
+int audio_extn_hw_loopback_create_audio_patch(struct audio_hw_device *dev,
+ unsigned int num_sources,
+ const struct audio_port_config *sources,
+ unsigned int num_sinks,
+ const struct audio_port_config *sinks,
+ audio_patch_handle_t *handle)
+{
+ int status = 0;
+ patch_handle_type_t loopback_patch_type=0x0;
+ loopback_patch_t loopback_patch, *active_loopback_patch = NULL;
+ ALOGV("%s : Create audio patch begin", __func__);
+
+ if ((audio_loopback_mod == NULL) || (dev == NULL)) {
+ ALOGE("%s, Loopback module not initialized orInvalid device", __func__);
+ status = -EINVAL;
+ return status;
+ }
+
+ pthread_mutex_lock(&audio_loopback_mod->lock);
+ if (audio_loopback_mod->patch_db.num_patches >= MAX_NUM_PATCHES ) {
+ ALOGE("%s, Exhausted maximum possible patches per device", __func__);
+ status = -EINVAL;
+ goto exit_create_patch;
+ }
+
+ /* Port configuration check & validation */
+ if (num_sources > MAX_SOURCE_PORTS_PER_PATCH ||
+ num_sinks > MAX_SINK_PORTS_PER_PATCH) {
+ ALOGE("%s, Unsupported patch configuration, sources %d sinks %d ",
+ __func__, num_sources, num_sources);
+ status = -EINVAL;
+ goto exit_create_patch;
+ }
+
+ /* Use an empty patch from patch database and initialze */
+ active_loopback_patch = &(audio_loopback_mod->patch_db.loopback_patch[
+ audio_loopback_mod->patch_db.num_patches]);
+ active_loopback_patch->patch_handle_id = PATCH_HANDLE_INVALID;
+ active_loopback_patch->patch_state = PATCH_INACTIVE;
+ memcpy(&active_loopback_patch->loopback_source, &sources[0], sizeof(struct
+ audio_port_config));
+ memcpy(&active_loopback_patch->loopback_sink, &sinks[0], sizeof(struct
+ audio_port_config));
+
+ /* Get loopback patch type based on source and sink ports configuration */
+ loopback_patch_type = get_loopback_patch_type(active_loopback_patch);
+
+ if (loopback_patch_type == PATCH_HANDLE_INVALID) {
+ ALOGE("%s, Unsupported patch type", __func__);
+ status = -EINVAL;
+ goto exit_create_patch;
+ }
+
+ update_patch_stream_config(&active_loopback_patch->patch_stream.in_config,
+ &active_loopback_patch->loopback_source);
+ update_patch_stream_config(&active_loopback_patch->patch_stream.out_config,
+ &active_loopback_patch->loopback_sink);
+ // Lock patch database, create patch handle and add patch handle to the list
+
+ active_loopback_patch->patch_handle_id = (loopback_patch_type << 8 |
+ audio_loopback_mod->patch_db.num_patches);
+
+ /* Is usecase transcode loopback? If yes, invoke loopback driver */
+ if ((active_loopback_patch->loopback_source.type == AUDIO_PORT_TYPE_DEVICE)
+ &&
+ (active_loopback_patch->loopback_sink.type == AUDIO_PORT_TYPE_DEVICE)) {
+ status = create_loopback_session(active_loopback_patch);
+ if (status != 0)
+ goto exit_create_patch;
+ }
+
+ // Create callback thread to listen to events from HW data path
+
+ /* Fill unique handle ID generated based on active loopback patch */
+ *handle = audio_loopback_mod->patch_db.loopback_patch[audio_loopback_mod->
+ patch_db.num_patches].patch_handle_id;
+ audio_loopback_mod->patch_db.num_patches++;
+
+exit_create_patch :
+ ALOGV("%s : Create audio patch end, status(%d)", __func__, status);
+ pthread_mutex_unlock(&audio_loopback_mod->lock);
+ return status;
+}
+
+/* API to release audio patch */
+int audio_extn_hw_loopback_release_audio_patch(struct audio_hw_device *dev,
+ audio_patch_handle_t handle)
+{
+ int status = 0, n=0, patch_index=-1;
+ bool patch_found = false;
+ loopback_patch_t *active_loopback_patch = NULL;
+ ALOGV("%s audio_extn_hw_loopback_release_audio_patch begin %d", __func__, __LINE__);
+
+ if ((audio_loopback_mod == NULL) || (dev == NULL)) {
+ ALOGE("%s, Invalid device", __func__);
+ status = -1;
+ return status;
+ }
+
+ pthread_mutex_lock(&audio_loopback_mod->lock);
+
+ for (n=0;n < MAX_NUM_PATCHES;n++) {
+ if (audio_loopback_mod->patch_db.loopback_patch[n].patch_handle_id ==
+ handle) {
+ patch_found = true;
+ patch_index = n;
+ break;
+ }
+ }
+
+ if (patch_found) {
+ active_loopback_patch = &(audio_loopback_mod->patch_db.loopback_patch[
+ patch_index]);
+ status = release_loopback_session(active_loopback_patch);
+ } else {
+ ALOGE("%s, Requested Patch handle does not exist", __func__);
+ status = -1;
+ }
+ pthread_mutex_unlock(&audio_loopback_mod->lock);
+
+ ALOGV("%s audio_extn_hw_loopback_release_audio_patch done, status(%d)", __func__,
+ status);
+ return status;
+}
+
+/* Find port config from patch database based on port info */
+struct audio_port_config* get_port_from_patch_db(port_info_t *port,
+ patch_db_t *audio_patch_db, int *patch_num)
+{
+ int n=0, patch_index=-1;
+ struct audio_port_config *cur_port=NULL;
+
+ if (port->role == AUDIO_PORT_ROLE_SOURCE) {
+ for (n=0;n < audio_patch_db->num_patches;n++) {
+ cur_port = &(audio_patch_db->loopback_patch[n].loopback_source);
+ if ((cur_port->id == port->id) && (cur_port->type == port->type) && (
+ cur_port->role == port->role)) {
+ patch_index = n;
+ break;
+ }
+ }
+ } else if (port->role == AUDIO_PORT_ROLE_SINK) {
+ for (n=0;n < audio_patch_db->num_patches;n++) {
+ cur_port = &(audio_patch_db->loopback_patch[n].loopback_sink);
+ if ((cur_port->id == port->id) && (cur_port->type == port->type) && (
+ cur_port->role == port->role)) {
+ patch_index = n;
+ break;
+ }
+ }
+ }
+ *patch_num = patch_index;
+ return cur_port;
+}
+
+/* API to get port config based on port unique ID */
+int audio_extn_hw_loopback_get_audio_port(struct audio_hw_device *dev,
+ 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;
+ struct audio_port_config *port_out=NULL;
+ ALOGV("%s %d", __func__, __LINE__);
+
+ if ((audio_loopback_mod == NULL) || (dev == NULL)) {
+ ALOGE("%s, Invalid device", __func__);
+ status = -1;
+ return status;
+ }
+
+ 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,
+ &patch_num);
+ if (port_out == NULL) {
+ ALOGE("%s, Unable to find a valid matching port in patch \
+ database,exiting", __func__);
+ status = -EINVAL;
+ return status;
+ }
+
+ /* Fill port output properties before returning the port */
+ memcpy(&port_in->active_config,port_out, sizeof(struct audio_port_config));
+
+ /* Multiple fields are not valid for loopback extension usecases, TODO :
+ enhance for all patch handler cases. */
+ port_in->num_sample_rates = 1;
+ port_in->sample_rates[0] = port_out->sample_rate;
+ port_in->num_channel_masks = 1;
+ port_in->channel_masks[0] = port_out->channel_mask;
+ port_in->num_formats = 1;
+ port_in->formats[0] = port_out->format;
+ port_in->num_gains = 1;
+
+ pthread_mutex_unlock(&audio_loopback_mod->lock);
+ return status;
+}
+
+/* API to set port config based on port unique ID */
+int audio_extn_hw_loopback_set_audio_port_config(struct audio_hw_device *dev,
+ 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;
+ struct audio_port_config *port_out=NULL;
+ ALOGV("%s %d", __func__, __LINE__);
+
+ if ((audio_loopback_mod == NULL) || (dev == NULL)) {
+ ALOGE("%s, Invalid device", __func__);
+ status = -1;
+ 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
+ , &patch_num);
+
+ if (port_out == NULL) {
+ ALOGE("%s, Unable to find a valid matching port in patch \
+ database,exiting", __func__);
+ status = -1;
+ return status;
+ }
+
+ 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;
+
+ /* Currently, port config is not used for anything,
+ need to restart session */
+ pthread_mutex_unlock(&audio_loopback_mod->lock);
+ return status;
+}
+
+/* Loopback extension initialization, part of hal init sequence */
+int audio_extn_loopback_init(struct audio_device *adev)
+{
+ ALOGV("%s Audio loopback extension initializing", __func__);
+ int ret = 0, size = 0;
+
+ if (audio_loopback_mod != NULL) {
+ pthread_mutex_lock(&audio_loopback_mod->lock);
+ if (audio_loopback_mod->adev == adev) {
+ ALOGV("%s %d : Audio loopback module already exists", __func__,
+ __LINE__);
+ } else {
+ ALOGV("%s %d : Audio loopback module called for invalid device",
+ __func__, __LINE__);
+ ret = -EINVAL;
+ }
+ goto loopback_done;
+ }
+ audio_loopback_mod = malloc(sizeof(struct audio_loopback));
+ if (audio_loopback_mod == NULL) {
+ ALOGE("%s, out of memory", __func__);
+ ret = -ENOMEM;
+ goto loopback_done;
+ }
+
+ pthread_mutex_init(&audio_loopback_mod->lock,
+ (const pthread_mutexattr_t *)NULL);
+ pthread_mutex_lock(&audio_loopback_mod->lock);
+ audio_loopback_mod->adev = adev;
+
+ ret = init_patch_database(&audio_loopback_mod->patch_db);
+
+ audio_loopback_mod->uc_id = USECASE_AUDIO_TRANSCODE_LOOPBACK;
+ audio_loopback_mod->uc_type = TRANSCODE_LOOPBACK;
+
+loopback_done:
+ if (ret != 0) {
+ if (audio_loopback_mod != NULL) {
+ pthread_mutex_unlock(&audio_loopback_mod->lock);
+ pthread_mutex_destroy(&audio_loopback_mod->lock);
+ free(audio_loopback_mod);
+ audio_loopback_mod = NULL;
+ }
+ } else {
+ pthread_mutex_unlock(&audio_loopback_mod->lock);
+ }
+ ALOGV("%s Audio loopback extension initialized", __func__);
+ return ret;
+}
+
+void audio_extn_loopback_deinit(struct audio_device *adev)
+{
+ ALOGV("%s Audio loopback extension de-initializing", __func__);
+
+ if (audio_loopback_mod == NULL) {
+ ALOGE("%s, loopback module NULL, cannot deinitialize", __func__);
+ return;
+ }
+ pthread_mutex_lock(&audio_loopback_mod->lock);
+
+ if (audio_loopback_mod->adev == adev) {
+ if (audio_loopback_mod != NULL) {
+ pthread_mutex_unlock(&audio_loopback_mod->lock);
+ pthread_mutex_destroy(&audio_loopback_mod->lock);
+ free(audio_loopback_mod);
+ audio_loopback_mod = NULL;
+ }
+ return;
+ } else {
+ ALOGE("%s, loopback module not valid, cannot deinitialize", __func__);
+ }
+ pthread_mutex_unlock(&audio_loopback_mod->lock);
+ return;
+}
diff --git a/hal/audio_hw.c b/hal/audio_hw.c
index 3d3a5d7..6375ec5 100644
--- a/hal/audio_hw.c
+++ b/hal/audio_hw.c
@@ -263,6 +263,8 @@
[USECASE_AUDIO_PLAYBACK_AFE_PROXY] = "afe-proxy-playback",
[USECASE_AUDIO_RECORD_AFE_PROXY] = "afe-proxy-record",
[USECASE_AUDIO_PLAYBACK_EXT_DISP_SILENCE] = "silence-playback",
+ /* Transcode loopback cases */
+ [USECASE_AUDIO_TRANSCODE_LOOPBACK] = "audio-transcode-loopback",
};
static const audio_usecase_t offload_usecases[] = {
@@ -989,12 +991,22 @@
struct audio_usecase *new_uc,
snd_device_t new_snd_device)
{
- audio_devices_t a1 = uc->stream.out->devices;
- audio_devices_t a2 = new_uc->stream.out->devices;
+ audio_devices_t a1, a2;
snd_device_t d1 = uc->out_snd_device;
snd_device_t d2 = new_snd_device;
+ switch (uc->type) {
+ case TRANSCODE_LOOPBACK :
+ a1 = uc->stream.inout->out_config.devices;
+ a2 = new_uc->stream.inout->out_config.devices;
+ break;
+ default :
+ a1 = uc->stream.out->devices;
+ a2 = new_uc->stream.out->devices;
+ break;
+ }
+
// Treat as a special case when a1 and a2 are not disjoint
if ((a1 != a2) && (a1 & a2)) {
snd_device_t d3[2];
@@ -1554,6 +1566,14 @@
usecase->stream.out);
in_snd_device = platform_get_input_snd_device(adev->platform, usecase->stream.out->devices);
usecase->devices = usecase->stream.out->devices;
+ } else if (usecase->type == TRANSCODE_LOOPBACK ) {
+ if (usecase->stream.inout == NULL) {
+ ALOGE("%s: stream.inout is NULL", __func__);
+ return -EINVAL;
+ }
+ out_snd_device = usecase->stream.inout->out_config.devices;
+ in_snd_device = usecase->stream.inout->in_config.devices;
+ usecase->devices = (out_snd_device | in_snd_device);
} else {
/*
* If the voice call is active, use the sound devices of voice call usecase
@@ -5353,6 +5373,41 @@
return;
}
+int adev_create_audio_patch(struct audio_hw_device *dev,
+ unsigned int num_sources,
+ const struct audio_port_config *sources,
+ unsigned int num_sinks,
+ const struct audio_port_config *sinks,
+ audio_patch_handle_t *handle)
+{
+
+
+ return audio_extn_hw_loopback_create_audio_patch(dev,
+ num_sources,
+ sources,
+ num_sinks,
+ sinks,
+ handle);
+
+}
+
+int adev_release_audio_patch(struct audio_hw_device *dev,
+ audio_patch_handle_t handle)
+{
+ return audio_extn_hw_loopback_release_audio_patch(dev, handle);
+}
+
+int adev_get_audio_port(struct audio_hw_device *dev, struct audio_port *config)
+{
+ return audio_extn_hw_loopback_get_audio_port(dev, config);
+}
+
+int adev_set_audio_port_config(struct audio_hw_device *dev,
+ const struct audio_port_config *config)
+{
+ return audio_extn_hw_loopback_set_audio_port_config(dev, config);
+}
+
static int adev_dump(const audio_hw_device_t *device __unused,
int fd __unused)
{
@@ -5386,6 +5441,7 @@
qahwi_deinit(device);
audio_extn_adsp_hdlr_deinit();
audio_extn_snd_mon_deinit();
+ audio_extn_loopback_deinit(adev);
free(device);
adev = NULL;
}
@@ -5498,6 +5554,10 @@
adev->device.close_output_stream = adev_close_output_stream;
adev->device.open_input_stream = adev_open_input_stream;
adev->device.close_input_stream = adev_close_input_stream;
+ adev->device.create_audio_patch = adev_create_audio_patch;
+ adev->device.release_audio_patch = adev_release_audio_patch;
+ adev->device.get_audio_port = adev_get_audio_port;
+ adev->device.set_audio_port_config = adev_set_audio_port_config;
adev->device.dump = adev_dump;
/* Set the default route before the PCM stream is opened */
@@ -5564,6 +5624,7 @@
audio_extn_init(adev);
audio_extn_listen_init(adev, adev->snd_card);
audio_extn_gef_init(adev);
+ audio_extn_loopback_init(adev);
if (access(OFFLOAD_EFFECTS_BUNDLE_LIBRARY_PATH, R_OK) == 0) {
adev->offload_effects_lib = dlopen(OFFLOAD_EFFECTS_BUNDLE_LIBRARY_PATH, RTLD_NOW);
diff --git a/hal/audio_hw.h b/hal/audio_hw.h
index 3980ab6..20edc99 100644
--- a/hal/audio_hw.h
+++ b/hal/audio_hw.h
@@ -81,6 +81,12 @@
#define DEFAULT_HDMI_OUT_SAMPLE_RATE 48000
#define DEFAULT_HDMI_OUT_FORMAT AUDIO_FORMAT_PCM_16_BIT
+#define SND_CARD_STATE_OFFLINE 0
+#define SND_CARD_STATE_ONLINE 1
+
+#define STREAM_DIRECTION_IN 0
+#define STREAM_DIRECTION_OUT 1
+
#define MAX_PERF_LOCK_OPTS 20
#define MAX_STREAM_PROFILE_STR_LEN 32
@@ -157,6 +163,7 @@
USECASE_AUDIO_PLAYBACK_EXT_DISP_SILENCE,
+ USECASE_AUDIO_TRANSCODE_LOOPBACK,
AUDIO_USECASE_MAX
};
@@ -204,6 +211,20 @@
int app_type;
};
+struct stream_config {
+ unsigned int sample_rate;
+ audio_channel_mask_t channel_mask;
+ audio_format_t format;
+ audio_devices_t devices;
+ unsigned int bit_width;
+};
+struct stream_inout {
+ pthread_mutex_t lock; /* see note below on mutex acquisition order */
+ pthread_mutex_t pre_lock; /* acquire before lock to avoid DOS by playback thread */
+ pthread_cond_t cond;
+ struct stream_config in_config;
+ struct stream_config out_config;
+};
struct stream_out {
struct audio_stream_out stream;
pthread_mutex_t lock; /* see note below on mutex acquisition order */
@@ -310,12 +331,14 @@
PCM_CAPTURE,
VOICE_CALL,
VOIP_CALL,
- PCM_HFP_CALL
+ PCM_HFP_CALL,
+ TRANSCODE_LOOPBACK
} usecase_type_t;
union stream_ptr {
struct stream_in *in;
struct stream_out *out;
+ struct stream_inout *inout;
};
struct audio_usecase {
diff --git a/hal/msm8916/platform.c b/hal/msm8916/platform.c
index f7a0335..6a0338f 100644
--- a/hal/msm8916/platform.c
+++ b/hal/msm8916/platform.c
@@ -370,6 +370,8 @@
[USECASE_AUDIO_RECORD_AFE_PROXY] = {AFE_PROXY_PLAYBACK_PCM_DEVICE,
AFE_PROXY_RECORD_PCM_DEVICE},
[USECASE_AUDIO_PLAYBACK_EXT_DISP_SILENCE] = {MULTIMEDIA9_PCM_DEVICE, -1},
+ [USECASE_AUDIO_TRANSCODE_LOOPBACK] = {TRANSCODE_LOOPBACK_RX_DEV_ID, TRANSCODE_LOOPBACK_TX_DEV_ID},
+
};
/* Array to store sound devices */
@@ -5732,10 +5734,19 @@
backend_idx = platform_get_backend_index(snd_device);
- backend_cfg.bit_width = usecase->stream.out->bit_width;
- backend_cfg.sample_rate = usecase->stream.out->sample_rate;
- backend_cfg.format = usecase->stream.out->format;
- backend_cfg.channels = audio_channel_count_from_out_mask(usecase->stream.out->channel_mask);
+ if (usecase->type == TRANSCODE_LOOPBACK) {
+ backend_cfg.bit_width = usecase->stream.inout->out_config.bit_width;
+ backend_cfg.sample_rate = usecase->stream.inout->out_config.sample_rate;
+ backend_cfg.format = usecase->stream.inout->out_config.format;
+ backend_cfg.channels = audio_channel_count_from_out_mask(
+ usecase->stream.inout->out_config.channel_mask);
+ } else {
+ backend_cfg.bit_width = usecase->stream.out->bit_width;
+ backend_cfg.sample_rate = usecase->stream.out->sample_rate;
+ backend_cfg.format = usecase->stream.out->format;
+ backend_cfg.channels = audio_channel_count_from_out_mask(usecase->stream.out->channel_mask);
+ }
+
/*this is populated by check_codec_backend_cfg hence set default value to false*/
backend_cfg.passthrough_enabled = false;
@@ -5866,7 +5877,14 @@
struct audio_backend_cfg backend_cfg;
backend_cfg.passthrough_enabled = false;
- if(usecase->type == PCM_CAPTURE) {
+
+ if (usecase->type == TRANSCODE_LOOPBACK) {
+ backend_cfg.bit_width = usecase->stream.inout->in_config.bit_width;
+ backend_cfg.sample_rate = usecase->stream.inout->in_config.sample_rate;
+ backend_cfg.format = usecase->stream.inout->in_config.format;
+ backend_cfg.channels = audio_channel_count_from_out_mask(
+ usecase->stream.inout->in_config.channel_mask);
+ } else if (usecase->type == PCM_CAPTURE) {
backend_cfg.sample_rate= usecase->stream.in->sample_rate;
backend_cfg.bit_width= usecase->stream.in->bit_width;
backend_cfg.format= usecase->stream.in->format;
diff --git a/hal/msm8916/platform.h b/hal/msm8916/platform.h
index 28fe62b..5aa02a0 100644
--- a/hal/msm8916/platform.h
+++ b/hal/msm8916/platform.h
@@ -345,6 +345,9 @@
#define AFE_PROXY_PLAYBACK_PCM_DEVICE 7
#define AFE_PROXY_RECORD_PCM_DEVICE 8
+#define TRANSCODE_LOOPBACK_RX_DEV_ID 43
+#define TRANSCODE_LOOPBACK_TX_DEV_ID 44
+
#define PLATFORM_MAX_MIC_COUNT "input_mic_max_count"
#define PLATFORM_DEFAULT_MIC_COUNT 2
diff --git a/hal/msm8974/platform.c b/hal/msm8974/platform.c
index ad3927c..fdabbd5 100755
--- a/hal/msm8974/platform.c
+++ b/hal/msm8974/platform.c
@@ -349,6 +349,7 @@
[USECASE_AUDIO_RECORD_AFE_PROXY] = {AFE_PROXY_PLAYBACK_PCM_DEVICE,
AFE_PROXY_RECORD_PCM_DEVICE},
[USECASE_AUDIO_PLAYBACK_EXT_DISP_SILENCE] = {MULTIMEDIA9_PCM_DEVICE, -1},
+ [USECASE_AUDIO_TRANSCODE_LOOPBACK] = {TRANSCODE_LOOPBACK_RX_DEV_ID, TRANSCODE_LOOPBACK_TX_DEV_ID},
};
@@ -5583,10 +5584,19 @@
backend_idx = platform_get_backend_index(snd_device);
- backend_cfg.bit_width = usecase->stream.out->bit_width;
- backend_cfg.sample_rate = usecase->stream.out->sample_rate;
- backend_cfg.format = usecase->stream.out->format;
- backend_cfg.channels = audio_channel_count_from_out_mask(usecase->stream.out->channel_mask);
+ if (usecase->type == TRANSCODE_LOOPBACK) {
+ backend_cfg.bit_width = usecase->stream.inout->out_config.bit_width;
+ backend_cfg.sample_rate = usecase->stream.inout->out_config.sample_rate;
+ backend_cfg.format = usecase->stream.inout->out_config.format;
+ backend_cfg.channels = audio_channel_count_from_out_mask(
+ usecase->stream.inout->out_config.channel_mask);
+ } else {
+ backend_cfg.bit_width = usecase->stream.out->bit_width;
+ backend_cfg.sample_rate = usecase->stream.out->sample_rate;
+ backend_cfg.format = usecase->stream.out->format;
+ backend_cfg.channels = audio_channel_count_from_out_mask(usecase->stream.out->channel_mask);
+ }
+
/*this is populated by check_codec_backend_cfg hence set default value to false*/
backend_cfg.passthrough_enabled = false;
@@ -5716,7 +5726,14 @@
struct audio_backend_cfg backend_cfg;
backend_cfg.passthrough_enabled = false;
- if(usecase->type == PCM_CAPTURE) {
+
+ if (usecase->type == TRANSCODE_LOOPBACK) {
+ backend_cfg.bit_width = usecase->stream.inout->in_config.bit_width;
+ backend_cfg.sample_rate = usecase->stream.inout->in_config.sample_rate;
+ backend_cfg.format = usecase->stream.inout->in_config.format;
+ backend_cfg.channels = audio_channel_count_from_out_mask(
+ usecase->stream.inout->in_config.channel_mask);
+ } else if (usecase->type == PCM_CAPTURE) {
backend_cfg.sample_rate= usecase->stream.in->sample_rate;
backend_cfg.bit_width= usecase->stream.in->bit_width;
backend_cfg.format= usecase->stream.in->format;
diff --git a/hal/msm8974/platform.h b/hal/msm8974/platform.h
index e4d797e..7329e7a 100644
--- a/hal/msm8974/platform.h
+++ b/hal/msm8974/platform.h
@@ -451,6 +451,9 @@
#define HFP_ASM_RX_TX 24
#endif
+#define TRANSCODE_LOOPBACK_RX_DEV_ID 43
+#define TRANSCODE_LOOPBACK_TX_DEV_ID 44
+
#ifdef PLATFORM_APQ8084
#define FM_RX_VOLUME "Quat MI2S FM RX Volume"
#elif PLATFORM_MSM8994
diff --git a/qahw_api/inc/qahw_api.h b/qahw_api/inc/qahw_api.h
index 0f3fc50..5e0e661 100644
--- a/qahw_api/inc/qahw_api.h
+++ b/qahw_api/inc/qahw_api.h
@@ -444,6 +444,33 @@
qahw_param_id param_id,
qahw_param_payload *payload);
+/* Creates an audio patch between several source and sink ports.
+ * The handle is allocated by the HAL and should be unique for this
+ * audio HAL module.
+ */
+int qahw_create_audio_patch(qahw_module_handle_t *hw_module,
+ unsigned int num_sources,
+ const struct audio_port_config *sources,
+ unsigned int num_sinks,
+ const struct audio_port_config *sinks,
+ audio_patch_handle_t *handle);
+
+/* Release an audio patch */
+int qahw_release_audio_patch(qahw_module_handle_t *hw_module,
+ audio_patch_handle_t handle);
+/* Fills the list of supported attributes for a given audio port.
+ * As input, "port" contains the information (type, role, address etc...)
+ * needed by the HAL to identify the port.
+ * As output, "port" contains possible attributes (sampling rates, formats,
+ * channel masks, gain controllers...) for this port.
+ */
+int qahw_get_audio_port(qahw_module_handle_t *hw_module,
+ struct audio_port *port);
+
+/* Set audio port configuration */
+int qahw_set_audio_port_config(qahw_module_handle_t *hw_module,
+ const struct audio_port_config *config);
+
__END_DECLS
#endif // QTI_AUDIO_HAL_API_H
diff --git a/qahw_api/src/qahw.c b/qahw_api/src/qahw.c
index df69df5..b5a85d7 100644
--- a/qahw_api/src/qahw.c
+++ b/qahw_api/src/qahw.c
@@ -1363,6 +1363,146 @@
return ret;
}
+/* Creates an audio patch between several source and sink ports.
+ * The handle is allocated by the HAL and should be unique for this
+ * audio HAL module.
+ */
+int qahw_create_audio_patch(qahw_module_handle_t *hw_module,
+ unsigned int num_sources,
+ const struct audio_port_config *sources,
+ unsigned int num_sinks,
+ const struct audio_port_config *sinks,
+ audio_patch_handle_t *handle)
+{
+ int ret = 0;
+ qahw_module_t *qahw_module = (qahw_module_t *)hw_module;
+ qahw_module_t *qahw_module_temp;
+
+ pthread_mutex_lock(&qahw_module_init_lock);
+ qahw_module_temp = get_qahw_module_by_ptr(qahw_module);
+ pthread_mutex_unlock(&qahw_module_init_lock);
+ if (qahw_module_temp == NULL) {
+ ALOGE("%s:: invalid hw module %p", __func__, qahw_module);
+ goto exit;
+ }
+
+ pthread_mutex_lock(&qahw_module->lock);
+ if (qahw_module->audio_device->create_audio_patch) {
+ ret = qahw_module->audio_device->create_audio_patch(
+ qahw_module->audio_device,
+ num_sources,
+ sources,
+ num_sinks,
+ sinks,
+ handle);
+ } else {
+ ret = -ENOSYS;
+ ALOGE("%s not supported\n",__func__);
+ }
+ pthread_mutex_unlock(&qahw_module->lock);
+
+exit:
+ return ret;
+}
+
+/* Release an audio patch */
+int qahw_release_audio_patch(qahw_module_handle_t *hw_module,
+ audio_patch_handle_t handle)
+{
+ int ret = 0;
+ qahw_module_t *qahw_module = (qahw_module_t *)hw_module;
+ qahw_module_t *qahw_module_temp;
+
+ pthread_mutex_lock(&qahw_module_init_lock);
+ qahw_module_temp = get_qahw_module_by_ptr(qahw_module);
+ pthread_mutex_unlock(&qahw_module_init_lock);
+ if (qahw_module_temp == NULL) {
+ ALOGE("%s:: invalid hw module %p", __func__, qahw_module);
+ goto exit;
+ }
+
+ pthread_mutex_lock(&qahw_module->lock);
+ if (qahw_module->audio_device->release_audio_patch) {
+ ret = qahw_module->audio_device->release_audio_patch(
+ qahw_module->audio_device,
+ handle);
+ } else {
+ ret = -ENOSYS;
+ ALOGE("%s not supported\n",__func__);
+ }
+ pthread_mutex_unlock(&qahw_module->lock);
+
+exit:
+ return ret;
+}
+
+/* Fills the list of supported attributes for a given audio port.
+ * As input, "port" contains the information (type, role, address etc...)
+ * needed by the HAL to identify the port.
+ * As output, "port" contains possible attributes (sampling rates, formats,
+ * channel masks, gain controllers...) for this port.
+ */
+int qahw_get_audio_port(qahw_module_handle_t *hw_module,
+ struct audio_port *port)
+{
+ int ret = 0;
+ qahw_module_t *qahw_module = (qahw_module_t *)hw_module;
+ qahw_module_t *qahw_module_temp;
+
+ pthread_mutex_lock(&qahw_module_init_lock);
+ qahw_module_temp = get_qahw_module_by_ptr(qahw_module);
+ pthread_mutex_unlock(&qahw_module_init_lock);
+ if (qahw_module_temp == NULL) {
+ ALOGE("%s:: invalid hw module %p", __func__, qahw_module);
+ goto exit;
+ }
+
+ pthread_mutex_lock(&qahw_module->lock);
+ if (qahw_module->audio_device->get_audio_port) {
+ ret = qahw_module->audio_device->get_audio_port(
+ qahw_module->audio_device,
+ port);
+ } else {
+ ret = -ENOSYS;
+ ALOGE("%s not supported\n",__func__);
+ }
+ pthread_mutex_unlock(&qahw_module->lock);
+
+exit:
+ return ret;
+}
+
+/* Set audio port configuration */
+int qahw_set_audio_port_config(qahw_module_handle_t *hw_module,
+ const struct audio_port_config *config)
+{
+ int ret = 0;
+ qahw_module_t *qahw_module = (qahw_module_t *)hw_module;
+ qahw_module_t *qahw_module_temp;
+
+ pthread_mutex_lock(&qahw_module_init_lock);
+ qahw_module_temp = get_qahw_module_by_ptr(qahw_module);
+ pthread_mutex_unlock(&qahw_module_init_lock);
+ if (qahw_module_temp == NULL) {
+ ALOGE("%s:: invalid hw module %p", __func__, qahw_module);
+ goto exit;
+ }
+
+ pthread_mutex_lock(&qahw_module->lock);
+ if (qahw_module->audio_device->set_audio_port_config) {
+ ret = qahw_module->audio_device->set_audio_port_config(
+ qahw_module->audio_device,
+ config);
+ } else {
+ ret = -ENOSYS;
+ ALOGE("%s not supported\n",__func__);
+ }
+ pthread_mutex_unlock(&qahw_module->lock);
+
+exit:
+ return ret;
+}
+
/* Returns audio input buffer size according to parameters passed or
* 0 if one of the parameters is not supported.
* See also get_buffer_size which is for a particular stream.
diff --git a/qahw_api/test/Makefile.am b/qahw_api/test/Makefile.am
index abccdce..d87962b 100644
--- a/qahw_api/test/Makefile.am
+++ b/qahw_api/test/Makefile.am
@@ -19,3 +19,13 @@
hal_rec_test_CPPFLAGS = -Dstrlcat=g_strlcat $(GLIB_CFLAGS) -include glib.h
hal_rec_test_CPPFLAGS += $(REC_CPPFLAGS) $(REC_INCLUDES)
hal_rec_test_LDADD = -lutils ../libqahw.la $(GLIB_LIBS)
+
+bin_PROGRAMS += trans_loopback_test
+
+trans_loopback_test_INCLUDES = -I $(top_srcdir)/qahw_api/inc
+
+trans_loopback_test_SOURCES = trans_loopback_test.c
+
+trans_loopback_test_CFLAGS = $(CFLAGS) -Wno-sign-compare -Werror
+trans_loopback_test_CFLAGS += $(trans_loopback_test_INCLUDES)
+trans_loopback_test_LDADD = -llog -lutils ../libqahw.la -lcutils
diff --git a/qahw_api/test/trans_loopback_test.c b/qahw_api/test/trans_loopback_test.c
new file mode 100644
index 0000000..c0435c7
--- /dev/null
+++ b/qahw_api/test/trans_loopback_test.c
@@ -0,0 +1,391 @@
+/*
+* Copyright (c) 2017, The Linux Foundation. All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions are
+* met:
+* * Redistributions of source code must retain the above copyright
+* notice, this list of conditions and the following disclaimer.
+* * Redistributions in binary form must reproduce the above
+* copyright notice, this list of conditions and the following
+* disclaimer in the documentation and/or other materials provided
+* with the distribution.
+* * Neither the name of The Linux Foundation nor the names of its
+* contributors may be used to endorse or promote products derived
+* from this software without specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/* Test app to capture event updates from kernel */
+/*#define LOG_NDEBUG 0*/
+#include <fcntl.h>
+#include <linux/netlink.h>
+#include <pthread.h>
+#include <poll.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/prctl.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <utils/Log.h>
+
+#include <cutils/list.h>
+#include <hardware/audio.h>
+#include <system/audio.h>
+#include "qahw_api.h"
+#include "qahw_defs.h"
+
+
+typedef struct tlb_hdmi_config {
+ int hdmi_conn_state;
+ int hdmi_audio_state;
+ int hdmi_sample_rate;
+ int hdmi_num_channels;
+} tlb_hdmi_config_t;
+
+const char tlb_hdmi_in_audio_sys_path[] =
+"/sys/devices/virtual/switch/hpd_state/state";
+const char tlb_hdmi_in_audio_dev_path[] = "/devices/virtual/switch/hpd_state";
+const char tlb_hdmi_in_audio_state_sys_path[] =
+"/sys/devices/virtual/switch/audio_state/state";
+const char tlb_hdmi_in_audio_state_dev_path[] =
+"/devices/virtual/switch/audio_state";
+const char tlb_hdmi_in_audio_sample_rate_sys_path[] =
+"/sys/devices/virtual/switch/sample_rate/state";
+const char tlb_hdmi_in_audio_sample_rate_dev_path[] =
+"/devices/virtual/switch/sample_rate";
+const char tlb_hdmi_in_audio_channel_sys_path[] =
+"/sys/devices/virtual/switch/channels/state";
+const char tlb_hdmi_in_audio_channel_dev_path[] =
+"/devices/virtual/switch/channels";
+
+qahw_module_handle_t *primary_hal_handle = NULL;
+
+FILE * log_file = NULL;
+volatile bool stop_playback = false;
+const char *log_filename = NULL;
+
+#define TRANSCODE_LOOPBACK_SOURCE_PORT_ID 0x4C00
+#define TRANSCODE_LOOPBACK_SINK_PORT_ID 0x4D00
+
+#define MAX_MODULE_NAME_LENGTH 100
+
+typedef enum source_port_type {
+ SOURCE_PORT_NONE,
+ SOURCE_PORT_HDMI,
+ SOURCE_PORT_SPDIF,
+ SOURCE_PORT_MIC
+} source_port_type_t;
+
+typedef struct trnscode_loopback_config {
+ qahw_module_handle_t *hal_handle;
+ audio_devices_t devices;
+ struct audio_port_config source_config;
+ struct audio_port_config sink_config;
+ audio_patch_handle_t patch_handle;
+} transcode_loopback_config_t;
+
+transcode_loopback_config_t g_trnscode_loopback_config;
+
+
+void init_transcode_loopback_config(transcode_loopback_config_t **p_transcode_loopback_config)
+{
+ fprintf(log_file,"\nInitializing global transcode loopback config\n");
+ g_trnscode_loopback_config.hal_handle = NULL;
+
+ audio_devices_t out_device = AUDIO_DEVICE_OUT_SPEAKER; // Get output device mask from connected device
+ audio_devices_t in_device = AUDIO_DEVICE_IN_HDMI;
+
+ g_trnscode_loopback_config.devices = (out_device | in_device);
+
+ /* Patch source port config init */
+ g_trnscode_loopback_config.source_config.id = TRANSCODE_LOOPBACK_SOURCE_PORT_ID;
+ g_trnscode_loopback_config.source_config.role = AUDIO_PORT_ROLE_SOURCE;
+ g_trnscode_loopback_config.source_config.type = AUDIO_PORT_TYPE_DEVICE;
+ g_trnscode_loopback_config.source_config.config_mask =
+ (AUDIO_PORT_CONFIG_ALL ^ AUDIO_PORT_CONFIG_GAIN);
+ g_trnscode_loopback_config.source_config.sample_rate = 48000;
+ g_trnscode_loopback_config.source_config.channel_mask =
+ AUDIO_CHANNEL_OUT_STEREO; // Using OUT as this is digital data and not mic capture
+ g_trnscode_loopback_config.source_config.format = AUDIO_FORMAT_PCM_16_BIT;
+ /*TODO: add gain */
+ g_trnscode_loopback_config.source_config.ext.device.hw_module =
+ AUDIO_MODULE_HANDLE_NONE;
+ g_trnscode_loopback_config.source_config.ext.device.type = in_device;
+
+ /* Patch sink port config init */
+ g_trnscode_loopback_config.sink_config.id = TRANSCODE_LOOPBACK_SINK_PORT_ID;
+ g_trnscode_loopback_config.sink_config.role = AUDIO_PORT_ROLE_SINK;
+ g_trnscode_loopback_config.sink_config.type = AUDIO_PORT_TYPE_DEVICE;
+ g_trnscode_loopback_config.sink_config.config_mask =
+ (AUDIO_PORT_CONFIG_ALL ^ AUDIO_PORT_CONFIG_GAIN);
+ g_trnscode_loopback_config.sink_config.sample_rate = 48000;
+ g_trnscode_loopback_config.sink_config.channel_mask =
+ AUDIO_CHANNEL_OUT_STEREO;
+ g_trnscode_loopback_config.sink_config.format = AUDIO_FORMAT_PCM_16_BIT;
+
+ g_trnscode_loopback_config.sink_config.ext.device.hw_module =
+ AUDIO_MODULE_HANDLE_NONE;
+ g_trnscode_loopback_config.sink_config.ext.device.type = out_device;
+
+ /* Init patch handle */
+ g_trnscode_loopback_config.patch_handle = AUDIO_PATCH_HANDLE_NONE;
+
+ *p_transcode_loopback_config = &g_trnscode_loopback_config;
+
+ fprintf(log_file,"\nDone Initializing global transcode loopback config\n");
+}
+
+void deinit_transcode_loopback_config()
+{
+ g_trnscode_loopback_config.hal_handle = NULL;
+
+ g_trnscode_loopback_config.devices = AUDIO_DEVICE_NONE;
+}
+
+void read_data_from_fd(const char* path, int *value)
+{
+ int fd = -1;
+ char buf[16];
+ int ret;
+
+ fd = open(path, O_RDONLY, 0);
+ if (fd < 0) {
+ ALOGE("Unable open fd for file %s", path);
+ return;
+ }
+
+ ret = read(fd, buf, 15);
+ if (ret < 0) {
+ ALOGE("File %s Data is empty", path);
+ close(fd);
+ return;
+ }
+
+ buf[ret] = '\0';
+ *value = atoi(buf);
+ close(fd);
+}
+
+int actual_channels_from_audio_infoframe(int infoframe_channels)
+{
+ if (infoframe_channels > 0 && infoframe_channels < 8) {
+ /* refer CEA-861-D Table 17 Audio InfoFrame Data Byte 1 */
+ return (infoframe_channels+1);
+ }
+ fprintf(log_file,"\nInfoframe channels 0, need to get from stream, returning default 2\n");
+ return 2;
+}
+
+int read_and_set_source_config(source_port_type_t source_port_type,
+ struct audio_port_config *dest_port_config)
+{
+ int rc=0, channels = 2;
+ tlb_hdmi_config_t hdmi_config = {0};
+ switch(source_port_type)
+ {
+ case SOURCE_PORT_HDMI :
+ read_data_from_fd(tlb_hdmi_in_audio_sys_path, &hdmi_config.hdmi_conn_state);
+ read_data_from_fd(tlb_hdmi_in_audio_state_sys_path,
+ &hdmi_config.hdmi_audio_state);
+ read_data_from_fd(tlb_hdmi_in_audio_sample_rate_sys_path,
+ &hdmi_config.hdmi_sample_rate);
+ read_data_from_fd(tlb_hdmi_in_audio_channel_sys_path,
+ &hdmi_config.hdmi_num_channels);
+
+ channels = actual_channels_from_audio_infoframe(hdmi_config.hdmi_num_channels);
+ fprintf(log_file,"\nHDMI In state: %d, audio_state: %d, samplerate: %d, channels: %d\n",
+ hdmi_config.hdmi_conn_state, hdmi_config.hdmi_audio_state,
+ hdmi_config.hdmi_sample_rate, channels);
+
+ ALOGD("HDMI In state: %d, audio_state: %d, samplerate: %d, channels: %d",
+ hdmi_config.hdmi_conn_state, hdmi_config.hdmi_audio_state,
+ hdmi_config.hdmi_sample_rate, channels);
+
+ dest_port_config->sample_rate = hdmi_config.hdmi_sample_rate;
+ switch(channels) {
+ case 2 :
+ dest_port_config->channel_mask = AUDIO_CHANNEL_OUT_STEREO;
+ break;
+ case 3 :
+ dest_port_config->channel_mask = AUDIO_CHANNEL_OUT_2POINT1;
+ break;
+ case 4 :
+ dest_port_config->channel_mask = AUDIO_CHANNEL_OUT_QUAD;
+ break;
+ case 5 :
+ dest_port_config->channel_mask = AUDIO_CHANNEL_OUT_PENTA;
+ break;
+ case 6 :
+ dest_port_config->channel_mask = AUDIO_CHANNEL_OUT_5POINT1;
+ break;
+ case 7 :
+ dest_port_config->channel_mask = AUDIO_CHANNEL_OUT_6POINT1;
+ break;
+ case 8 :
+ dest_port_config->channel_mask = AUDIO_CHANNEL_OUT_7POINT1;
+ break;
+ default :
+ fprintf(log_file,"\nUnsupported number of channels in source port %d\n",
+ channels);
+ rc = -1;
+ break;
+ }
+ break;
+ default :
+ fprintf(log_file,"\nUnsupported port type, cannot set configuration\n");
+ rc = -1;
+ break;
+ }
+ return rc;
+}
+
+void stop_transcode_loopback(
+ transcode_loopback_config_t *transcode_loopback_config)
+{
+ qahw_release_audio_patch(transcode_loopback_config->hal_handle,
+ transcode_loopback_config->patch_handle);
+}
+
+int create_run_transcode_loopback(
+ transcode_loopback_config_t *transcode_loopback_config)
+{
+ int rc=0;
+ qahw_module_handle_t *module_handle = transcode_loopback_config->hal_handle;
+
+
+ fprintf(log_file,"\nCreating audio patch\n");
+ rc = qahw_create_audio_patch(module_handle,
+ 1,
+ &transcode_loopback_config->source_config,
+ 1,
+ &transcode_loopback_config->sink_config,
+ &transcode_loopback_config->patch_handle);
+ fprintf(log_file,"\nCreate patch returned %d\n",rc);
+ return rc;
+}
+
+static audio_hw_device_t *load_hal(audio_devices_t dev)
+{
+ if (primary_hal_handle == NULL) {
+ primary_hal_handle = qahw_load_module(QAHW_MODULE_ID_PRIMARY);
+ if (primary_hal_handle == NULL) {
+ fprintf(stderr,"failure in Loading primary HAL\n");
+ goto exit;
+ }
+ }
+
+exit:
+ return primary_hal_handle;
+}
+
+/*
+* this function unloads all the loaded hal modules so this should be called
+* after all the stream playback are concluded.
+*/
+static int unload_hals(void) {
+ if (primary_hal_handle) {
+ qahw_unload_module(primary_hal_handle);
+ primary_hal_handle = NULL;
+ }
+ return 1;
+}
+
+int main(int argc, char *argv[]) {
+
+ int status = 0,play_duration_in_seconds = 30;
+ source_port_type_t source_port_type = SOURCE_PORT_NONE;
+ log_file = stdout;
+
+ fprintf(log_file,"\nTranscode loopback test begin\n");
+ if (argc == 2) {
+ play_duration_in_seconds = atoi(argv[1]);
+ if (play_duration_in_seconds < 0 | play_duration_in_seconds > 3600) {
+ fprintf(log_file,
+ "\nPlayback duration %s invalid or unsupported(range : 1 to 3600 )\n",
+ argv[1]);
+ goto usage;
+ }
+ } else {
+ goto usage;
+ }
+
+ transcode_loopback_config_t *transcode_loopback_config = NULL;
+ transcode_loopback_config_t *temp = NULL;
+
+ /* Initialize global transcode loopback struct */
+ init_transcode_loopback_config(&temp);
+ transcode_loopback_config = &g_trnscode_loopback_config;
+
+ /* Load HAL */
+ fprintf(log_file,"\nLoading HAL for loopback usecase begin\n");
+ primary_hal_handle = load_hal(transcode_loopback_config->devices);
+ if (primary_hal_handle == NULL) {
+ fprintf(log_file,"\n Failure in Loading HAL, exiting\n");
+ goto exit_transcode_loopback_test;
+ }
+ transcode_loopback_config->hal_handle = primary_hal_handle;
+ fprintf(log_file,"\nLoading HAL for loopback usecase done\n");
+
+ /* Configuration assuming source port is HDMI */
+ {
+ source_port_type = SOURCE_PORT_HDMI;
+ fprintf(log_file,"\nSet port config being\n");
+ status = read_and_set_source_config(source_port_type,&transcode_loopback_config->source_config);
+ fprintf(log_file,"\nSet port config end\n");
+
+ if (status != 0) {
+ fprintf(log_file,"\nFailed to set port config, exiting\n");
+ goto exit_transcode_loopback_test;
+ }
+ }
+
+ /* Open transcode loopback session */
+ fprintf(log_file,"\nCreate and start transcode loopback session begin\n");
+ status = create_run_transcode_loopback(transcode_loopback_config);
+ fprintf(log_file,"\nCreate and start transcode loopback session end\n");
+
+ /* If session opened successfully, run for a duration and close session */
+ if (status == 0) {
+ fprintf(log_file,"\nSleeping for %d seconds for loopback session to run\n",
+ play_duration_in_seconds);
+ usleep(play_duration_in_seconds*1000*1000);
+
+ fprintf(log_file,"\nStop transcode loopback session begin\n");
+ stop_transcode_loopback(transcode_loopback_config);
+ fprintf(log_file,"\nStop transcode loopback session end\n");
+ } else {
+ fprintf(log_file,"\nEncountered error %d in creating transcode loopback session\n",
+ status);
+ }
+
+exit_transcode_loopback_test:
+ fprintf(log_file,"\nUnLoading HAL for loopback usecase begin\n");
+ unload_hals();
+ fprintf(log_file,"\nUnLoading HAL for loopback usecase end\n");
+
+ deinit_transcode_loopback_config();
+ transcode_loopback_config = NULL;
+
+ fprintf(log_file,"\nTranscode loopback test end\n");
+ return 0;
+usage:
+ fprintf(log_file,"\nInvald arguments\n");
+ fprintf(log_file,"\nUsage : trans_loopback_test <duration_in_seconds>\n");
+ fprintf(log_file,"\nExample to play for 1 minute : trans_loopback_test 60\n");
+ return 0;
+}
+
+