| /* |
| * Copyright (c) 2017-2019, 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 <getopt.h> |
| #include <pthread.h> |
| #include <poll.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <signal.h> |
| #include <errno.h> |
| #include <sys/prctl.h> |
| #include <sys/stat.h> |
| #include <sys/socket.h> |
| #include <sys/types.h> |
| #include <utils/Log.h> |
| #include <math.h> |
| |
| #include <cutils/list.h> |
| #include "qahw_api.h" |
| #include "qahw_defs.h" |
| |
| static int sock_event_fd = -1; |
| |
| pthread_t data_event_th = -1; |
| pthread_attr_t data_event_attr; |
| |
| typedef struct tlb_hdmi_config { |
| int hdmi_conn_state; |
| int hdmi_audio_state; |
| int hdmi_sample_rate; |
| int hdmi_num_channels; |
| int hdmi_data_format; |
| } 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"; |
| const char tlb_hdmi_in_audio_format_sys_path[] = |
| "/sys/devices/virtual/switch/audio_format/state"; |
| const char tlb_hdmi_in_audio_format_dev_path[] = |
| "/devices/virtual/switch/audio_format"; |
| |
| qahw_module_handle_t *primary_hal_handle = NULL; |
| |
| FILE * log_file = NULL; |
| volatile bool stop_loopback = false; |
| volatile bool exit_process_thread = false; |
| static float loopback_gain = 1.0; |
| 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 |
| |
| #define DEV_NODE_CHECK(node_name,node_id) strncmp(node_name,node_id,strlen(node_name)) |
| |
| /* Function declarations */ |
| void usage(); |
| int poll_data_event_init(); |
| |
| typedef enum source_port_type { |
| SOURCE_PORT_NONE, |
| SOURCE_PORT_HDMI, |
| SOURCE_PORT_SPDIF, |
| SOURCE_PORT_MIC, |
| SOURCE_PORT_BT |
| } source_port_type_t; |
| |
| typedef enum source_port_state { |
| SOURCE_PORT_INACTIVE=0, |
| SOURCE_PORT_ACTIVE, |
| SOURCE_PORT_CONFIG_CHANGED |
| } source_port_state_t; |
| |
| typedef struct source_port_config { |
| source_port_type_t source_port_type; |
| source_port_state_t source_port_state; |
| union { |
| tlb_hdmi_config_t hdmi_in_port_config; |
| } port_config; |
| } source_port_config_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; |
| source_port_config_t source_port_config; |
| } transcode_loopback_config_t; |
| |
| transcode_loopback_config_t g_trnscode_loopback_config; |
| |
| static int poll_data_event_exit() |
| { |
| close(sock_event_fd); |
| } |
| |
| void break_signal_handler(int signal __attribute__((unused))) |
| { |
| stop_loopback = true; |
| } |
| |
| int poll_data_event_init() |
| { |
| struct sockaddr_nl sock_addr; |
| int sz = (64*1024); |
| int soc; |
| |
| memset(&sock_addr, 0, sizeof(sock_addr)); |
| sock_addr.nl_family = AF_NETLINK; |
| sock_addr.nl_pid = getpid(); |
| sock_addr.nl_groups = 0xffffffff; |
| |
| soc = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT); |
| if (soc < 0) { |
| return 0; |
| } |
| |
| setsockopt(soc, SOL_SOCKET, SO_RCVBUFFORCE, &sz, sizeof(sz)); |
| |
| if (bind(soc, (struct sockaddr*) &sock_addr, sizeof(sock_addr)) < 0) { |
| close(soc); |
| return 0; |
| } |
| |
| sock_event_fd = soc; |
| |
| return (soc > 0); |
| } |
| |
| 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; |
| |
| memset(&g_trnscode_loopback_config.source_port_config,0,sizeof(source_port_config_t)); |
| g_trnscode_loopback_config.source_port_config.source_port_type = SOURCE_PORT_HDMI; |
| g_trnscode_loopback_config.source_port_config.source_port_state = SOURCE_PORT_INACTIVE; |
| |
| poll_data_event_init(); |
| *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; |
| tlb_hdmi_config_t hdmi_config = {0}; |
| transcode_loopback_config_t *transcode_loopback_config = &g_trnscode_loopback_config; |
| transcode_loopback_config->source_port_config.source_port_state = SOURCE_PORT_INACTIVE; |
| 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); |
| read_data_from_fd(tlb_hdmi_in_audio_format_sys_path, |
| &hdmi_config.hdmi_data_format); |
| break; |
| default : |
| fprintf(log_file,"\nUnsupported port type, cannot set configuration\n"); |
| rc = -1; |
| break; |
| } |
| |
| hdmi_config.hdmi_num_channels = actual_channels_from_audio_infoframe(hdmi_config.hdmi_num_channels); |
| |
| if(hdmi_config.hdmi_data_format) { |
| if(!( hdmi_config.hdmi_num_channels == 2 || hdmi_config.hdmi_num_channels == 8 )) { |
| transcode_loopback_config->source_port_config.source_port_state = SOURCE_PORT_INACTIVE; |
| return rc; |
| } |
| } |
| dest_port_config->sample_rate = hdmi_config.hdmi_sample_rate; |
| switch(hdmi_config.hdmi_num_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", |
| hdmi_config.hdmi_num_channels); |
| rc = -1; |
| break; |
| } |
| switch(hdmi_config.hdmi_data_format) { |
| case 0 : |
| // TODO : HDMI driver detecting as 0 for compressed also as of now |
| //dest_port_config->format = AUDIO_FORMAT_AC3; |
| dest_port_config->format = AUDIO_FORMAT_PCM_16_BIT; |
| break; |
| case 1 : |
| dest_port_config->format = AUDIO_FORMAT_AC3; |
| break; |
| default : |
| fprintf(log_file,"\nUnsupported data format in source port %d\n", |
| hdmi_config.hdmi_data_format); |
| rc = -1; |
| break; |
| } |
| |
| fprintf(log_file,"\nExisting HDMI In state: %d, audio_state: %d, samplerate: %d, channels: %d, format: %d\n", |
| transcode_loopback_config->source_port_config.port_config.hdmi_in_port_config.hdmi_conn_state, transcode_loopback_config->source_port_config.port_config.hdmi_in_port_config.hdmi_audio_state, |
| transcode_loopback_config->source_port_config.port_config.hdmi_in_port_config.hdmi_sample_rate, transcode_loopback_config->source_port_config.port_config.hdmi_in_port_config.hdmi_num_channels, |
| transcode_loopback_config->source_port_config.port_config.hdmi_in_port_config.hdmi_data_format); |
| |
| fprintf(log_file,"\nSource port connection_state: %d, audio_state: %d, samplerate: %d, channels: %d, format: %d\n", |
| hdmi_config.hdmi_conn_state, hdmi_config.hdmi_audio_state, |
| hdmi_config.hdmi_sample_rate, hdmi_config.hdmi_num_channels, |
| hdmi_config.hdmi_data_format); |
| |
| if( rc == 0 ) { |
| if(hdmi_config.hdmi_audio_state) |
| { |
| if(memcmp(&hdmi_config,&(transcode_loopback_config->source_port_config.port_config.hdmi_in_port_config),sizeof(tlb_hdmi_config_t))) { |
| transcode_loopback_config->source_port_config.source_port_state = SOURCE_PORT_CONFIG_CHANGED; |
| } else { |
| transcode_loopback_config->source_port_config.source_port_state = SOURCE_PORT_ACTIVE; |
| } |
| } else { |
| transcode_loopback_config->source_port_config.source_port_state = SOURCE_PORT_INACTIVE; |
| } |
| memcpy(&(transcode_loopback_config->source_port_config.port_config.hdmi_in_port_config),&hdmi_config,sizeof(tlb_hdmi_config_t)); |
| } |
| return rc; |
| } |
| |
| void stop_transcode_loopback( |
| transcode_loopback_config_t *transcode_loopback_config) |
| { |
| fprintf(log_file,"\nStopping current loopback session\n"); |
| if(transcode_loopback_config->patch_handle != AUDIO_PATCH_HANDLE_NONE) |
| qahw_release_audio_patch(transcode_loopback_config->hal_handle, |
| transcode_loopback_config->patch_handle); |
| transcode_loopback_config->patch_handle = AUDIO_PATCH_HANDLE_NONE; |
| } |
| |
| 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"); |
| if (transcode_loopback_config->patch_handle != AUDIO_PATCH_HANDLE_NONE) { |
| fprintf(log_file,"\nPatch already existing, release the patch before opening a new patch\n"); |
| return rc; |
| } |
| 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); |
| 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; |
| } |
| |
| static qahw_module_handle_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; |
| } |
| |
| |
| void source_data_event_handler(transcode_loopback_config_t *transcode_loopback_config) |
| { |
| int status =0; |
| source_port_type_t source_port_type = transcode_loopback_config->source_port_config.source_port_type; |
| |
| if (source_port_type == SOURCE_PORT_HDMI) { |
| status = read_and_set_source_config(source_port_type,&transcode_loopback_config->source_config); |
| if (status) { |
| fprintf(log_file,"\nFailure in source port configuration with status: %d\n", status); |
| return; |
| } |
| } else { |
| transcode_loopback_config->source_port_config.source_port_state = SOURCE_PORT_CONFIG_CHANGED; |
| } |
| |
| fprintf(log_file,"\nSource port state : %d\n", transcode_loopback_config->source_port_config.source_port_state); |
| |
| if(transcode_loopback_config->source_port_config.source_port_state == SOURCE_PORT_CONFIG_CHANGED) { |
| fprintf(log_file,"\nAudio state changed, Create and start transcode loopback session begin\n"); |
| stop_transcode_loopback(transcode_loopback_config); |
| status = create_run_transcode_loopback(transcode_loopback_config); |
| if(status) |
| { |
| fprintf(log_file,"\nCreate audio patch failed with status %d\n",status); |
| stop_transcode_loopback(transcode_loopback_config); |
| } |
| } else if(transcode_loopback_config->source_port_config.source_port_state == SOURCE_PORT_INACTIVE) { |
| stop_transcode_loopback(transcode_loopback_config); |
| } |
| } |
| |
| void process_loopback_data(void *ptr) |
| { |
| char buffer[64*1024]; |
| struct pollfd fds; |
| int i, count, status; |
| int j; |
| char *dev_path = NULL; |
| char *switch_state = NULL; |
| char *switch_name = NULL; |
| transcode_loopback_config_t *transcode_loopback_config = &g_trnscode_loopback_config; |
| |
| fprintf(log_file,"\nEvent thread loop\n"); |
| source_data_event_handler(transcode_loopback_config); |
| while (!stop_loopback) { |
| |
| fds.fd = sock_event_fd; |
| fds.events = POLLIN; |
| fds.revents = 0; |
| /* poll wait time modified to wait forever */ |
| i = poll(&fds, 1, -1); |
| |
| if (i > 0 && (fds.revents & POLLIN)) { |
| count = recv(sock_event_fd, buffer, (64*1024), 0 ); |
| if (count > 0) { |
| buffer[count] = '\0'; |
| j = 0; |
| while(j < count) { |
| if (strncmp(&buffer[j], "DEVPATH=", 8) == 0) { |
| dev_path = &buffer[j+8]; |
| j += 8; |
| continue; |
| } else if (strncmp(&buffer[j], "SWITCH_NAME=", 12) == 0) { |
| switch_name = &buffer[j+12]; |
| j += 12; |
| continue; |
| } else if (strncmp(&buffer[j], "SWITCH_STATE=", 13) == 0) { |
| switch_state = &buffer[j+13]; |
| j += 13; |
| continue; |
| } |
| j++; |
| } |
| |
| if (dev_path == NULL) { |
| fprintf(log_file, "NULL dev_path!"); |
| continue; |
| } |
| |
| 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) |
| || (DEV_NODE_CHECK(tlb_hdmi_in_audio_state_dev_path, dev_path) == 0) |
| || (DEV_NODE_CHECK(tlb_hdmi_in_audio_channel_dev_path, dev_path) == 0) |
| || (DEV_NODE_CHECK(tlb_hdmi_in_audio_format_dev_path, dev_path) == 0)) { |
| source_data_event_handler(transcode_loopback_config); |
| } |
| } |
| } else { |
| ALOGD("NO Data\n"); |
| } |
| } |
| fprintf(log_file,"\nEvent thread loop exit\n"); |
| |
| stop_transcode_loopback(transcode_loopback_config); |
| |
| fprintf(log_file,"\nStop transcode loopback done\n"); |
| |
| exit_process_thread = true; |
| } |
| |
| void set_device(uint32_t source_device, uint32_t sink_device) |
| { |
| transcode_loopback_config_t *transcode_loopback_config = &g_trnscode_loopback_config; |
| |
| transcode_loopback_config->sink_config.ext.device.type = sink_device; |
| transcode_loopback_config->source_config.ext.device.type = source_device; |
| |
| switch (source_device) { |
| case AUDIO_DEVICE_IN_SPDIF: |
| g_trnscode_loopback_config.source_port_config.source_port_type = SOURCE_PORT_SPDIF; |
| break; |
| case AUDIO_DEVICE_IN_BLUETOOTH_A2DP: |
| g_trnscode_loopback_config.source_port_config.source_port_type = SOURCE_PORT_BT; |
| break; |
| case AUDIO_DEVICE_IN_LINE: |
| g_trnscode_loopback_config.source_port_config.source_port_type = SOURCE_PORT_MIC; |
| break; |
| case AUDIO_DEVICE_IN_HDMI: |
| default: |
| g_trnscode_loopback_config.source_port_config.source_port_type = SOURCE_PORT_HDMI; |
| break; |
| } |
| } |
| |
| 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, volume_in_millibels = 0; |
| uint32_t source_device = AUDIO_DEVICE_IN_HDMI; |
| source_port_type_t source_port_type = SOURCE_PORT_NONE; |
| log_file = stdout; |
| transcode_loopback_config_t *transcode_loopback_config = NULL; |
| transcode_loopback_config_t *temp = NULL; |
| char param[100] = {0}; |
| |
| struct option long_options[] = { |
| /* These options set a flag. */ |
| {"sink-device", required_argument, 0, 'o'}, |
| {"source-device", required_argument, 0, 'i'}, |
| {"play-duration", required_argument, 0, 'p'}, |
| {"play-volume", required_argument, 0, 'v'}, |
| {"help", no_argument, 0, 'h'}, |
| {0, 0, 0, 0} |
| }; |
| |
| int opt = 0; |
| int option_index = 0; |
| |
| while ((opt = getopt_long(argc, |
| argv, |
| "-o:i:p:v:h", |
| long_options, |
| &option_index)) != -1) { |
| |
| fprintf(log_file, "for argument %c, value is %s\n", opt, optarg); |
| |
| switch (opt) { |
| case 'o': |
| sink_device = atoll(optarg); |
| break; |
| case 'i': |
| source_device = atoll(optarg); |
| break; |
| case 'p': |
| play_duration_in_seconds = atoi(optarg); |
| break; |
| case 'v': |
| loopback_gain = atof(optarg); |
| break; |
| case 'h': |
| default : |
| usage(); |
| return 0; |
| break; |
| } |
| } |
| |
| fprintf(log_file, "source %#x sink %#x\n", source_device, sink_device); |
| fprintf(log_file,"\nTranscode loopback test begin\n"); |
| if (play_duration_in_seconds < 0 | play_duration_in_seconds > 360000) { |
| fprintf(log_file, |
| "\nPlayback duration %d invalid or unsupported(range : 1 to 360000, defaulting to 600 seconds )\n", |
| play_duration_in_seconds); |
| play_duration_in_seconds = 600; |
| } |
| play_duration_in_msec = play_duration_in_seconds * 1000; |
| |
| /* Register the SIGINT to close the App properly */ |
| if (signal(SIGINT, break_signal_handler) == SIG_ERR) { |
| fprintf(log_file, "Failed to register SIGINT:%d\n",errno); |
| fprintf(stderr, "Failed to register SIGINT:%d\n",errno); |
| } |
| |
| /* Initialize global transcode loopback struct */ |
| init_transcode_loopback_config(&temp); |
| transcode_loopback_config = &g_trnscode_loopback_config; |
| |
| /* Set devices */ |
| set_device(source_device, sink_device); |
| |
| /* 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"); |
| /* Set the exit_process_thread flag for exiting test */ |
| exit_process_thread = true; |
| goto exit_transcode_loopback_test; |
| } |
| |
| if (sink_device == AUDIO_DEVICE_OUT_BLUETOOTH_A2DP) { |
| snprintf(param, sizeof(param), "%s=%d", "connect", sink_device); |
| qahw_set_parameters(primary_hal_handle, param); |
| } |
| |
| transcode_loopback_config->hal_handle = primary_hal_handle; |
| fprintf(log_file,"\nLoading HAL for loopback usecase done\n"); |
| |
| pthread_attr_init(&data_event_attr); |
| fprintf(log_file,"\nData thread init done\n"); |
| pthread_attr_setdetachstate(&data_event_attr, PTHREAD_CREATE_JOINABLE); |
| fprintf(log_file,"\nData thread setdetachstate done\n"); |
| |
| fprintf(log_file,"\nData thread create\n"); |
| pthread_create(&data_event_th, &data_event_attr, |
| (void *) process_loopback_data, NULL); |
| fprintf(log_file,"\nMain thread loop\n"); |
| while(!stop_loopback) { |
| usleep(5000*1000); |
| play_duration_elapsed_msec += 5000; |
| if(play_duration_in_msec <= play_duration_elapsed_msec) |
| { |
| stop_loopback = true; |
| fprintf(log_file,"\nElapsed set playback duration %d seconds, exiting test\n",play_duration_in_msec/1000); |
| break; |
| } |
| } |
| fprintf(log_file,"\nMain thread loop exit\n"); |
| |
| exit_transcode_loopback_test: |
| poll_data_event_exit(); |
| /* Wait for process thread to exit */ |
| while (!exit_process_thread) { |
| usleep(10*1000); |
| } |
| fprintf(log_file,"\nJoining loopback thread\n"); |
| status = pthread_join(data_event_th, NULL); |
| fprintf(log_file, "\n thread join completed, status:%d \n", status); |
| exit_process_thread = false; |
| |
| 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; |
| } |
| |
| void usage() |
| { |
| 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"); |
| } |