diff options
Diffstat (limited to 'cras/src/server/cras_alsa_io.c')
-rw-r--r-- | cras/src/server/cras_alsa_io.c | 2473 |
1 files changed, 0 insertions, 2473 deletions
diff --git a/cras/src/server/cras_alsa_io.c b/cras/src/server/cras_alsa_io.c deleted file mode 100644 index 275a6810..00000000 --- a/cras/src/server/cras_alsa_io.c +++ /dev/null @@ -1,2473 +0,0 @@ -/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved. - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. - */ - -#include <alsa/asoundlib.h> -#include <errno.h> -#include <limits.h> -#include <stdio.h> -#include <sys/param.h> -#include <sys/select.h> -#include <sys/socket.h> -#include <sys/time.h> -#include <syslog.h> -#include <time.h> - -#include "audio_thread.h" -#include "cras_alsa_helpers.h" -#include "cras_alsa_io.h" -#include "cras_alsa_jack.h" -#include "cras_alsa_mixer.h" -#include "cras_alsa_ucm.h" -#include "cras_audio_area.h" -#include "cras_config.h" -#include "cras_utf8.h" -#include "cras_hotword_handler.h" -#include "cras_iodev.h" -#include "cras_iodev_list.h" -#include "cras_messages.h" -#include "cras_ramp.h" -#include "cras_rclient.h" -#include "cras_shm.h" -#include "cras_system_state.h" -#include "cras_types.h" -#include "cras_util.h" -#include "cras_volume_curve.h" -#include "sfh.h" -#include "softvol_curve.h" -#include "utlist.h" - -#define HOTWORD_DEV "Wake on Voice" -#define DEFAULT "(default)" -#define HDMI "HDMI" -#define INTERNAL_MICROPHONE "Internal Mic" -#define INTERNAL_SPEAKER "Speaker" -#define KEYBOARD_MIC "Keyboard Mic" -#define HEADPHONE "Headphone" -#define MIC "Mic" -#define USB "USB" -#define LOOPBACK_CAPTURE "Loopback Capture" -#define LOOPBACK_PLAYBACK "Loopback Playback" - -/* - * For USB, pad the output buffer. This avoids a situation where there isn't a - * complete URB's worth of audio ready to be transmitted when it is requested. - * The URB interval does track directly to the audio clock, making it hard to - * predict the exact interval. - */ -#define USB_EXTRA_BUFFER_FRAMES 768 - -/* - * When snd_pcm_avail returns a value that is greater than buffer size, - * we know there is an underrun. If the number of underrun samples - * (avail - buffer_size) is greater than SEVERE_UNDERRUN_MS * rate, - * it is a severe underrun. Main thread should disable and then enable - * device to recover it from underrun. - */ -#define SEVERE_UNDERRUN_MS 5000 - -/* - * When entering no stream state, audio thread needs to fill extra zeros in - * order to play remaining valid frames. The value indicates how many - * time will be filled. - */ -static const struct timespec no_stream_fill_zeros_duration = { - 0, 50 * 1000 * 1000 /* 50 msec. */ -}; - -/* - * This extends cras_ionode to include alsa-specific information. - * Members: - * mixer_output - From cras_alsa_mixer. - * pcm_name - PCM name for snd_pcm_open. - * volume_curve - Volume curve for this node. - * jack - The jack associated with the node. - */ -struct alsa_output_node { - struct cras_ionode base; - struct mixer_control *mixer_output; - const char *pcm_name; - struct cras_volume_curve *volume_curve; - const struct cras_alsa_jack *jack; -}; - -struct alsa_input_node { - struct cras_ionode base; - struct mixer_control *mixer_input; - const char *pcm_name; - const struct cras_alsa_jack *jack; - int8_t *channel_layout; -}; - -/* - * Child of cras_iodev, alsa_io handles ALSA interaction for sound devices. - * base - The cras_iodev structure "base class". - * pcm_name - The PCM name passed to snd_pcm_open() (e.g. "hw:0,0"). - * dev_name - value from snd_pcm_info_get_name - * dev_id - value from snd_pcm_info_get_id - * device_index - ALSA index of device, Y in "hw:X:Y". - * next_ionode_index - The index we will give to the next ionode. Each ionode - * have a unique index within the iodev. - * card_type - the type of the card this iodev belongs. - * is_first - true if this is the first iodev on the card. - * fully_specified - true if this device and it's nodes were fully specified. - * That is, don't automatically create nodes for it. - * handle - Handle to the opened ALSA device. - * num_severe_underruns - Number of times we have run out of data badly. - Unlike num_underruns which records for the duration - where device is opened, num_severe_underruns records - since device is created. When severe underrun occurs - a possible action is to close/open device. - * alsa_stream - Playback or capture type. - * mixer - Alsa mixer used to control volume and mute of the device. - * config - Card config for this alsa device. - * jack_list - List of alsa jack controls for this device. - * ucm - CRAS use case manager, if configuration is found. - * mmap_offset - offset returned from mmap_begin. - * poll_fd - Descriptor used to block until data is ready. - * dma_period_set_microsecs - If non-zero, the value to apply to the dma_period. - * free_running - true if device is playing zeros in the buffer without - * user filling meaningful data. The device buffer is filled - * with zeros. In this state, appl_ptr remains the same - * while hw_ptr keeps running ahead. - * filled_zeros_for_draining - The number of zeros filled for draining. - * severe_underrun_frames - The threshold for severe underrun. - * default_volume_curve - Default volume curve that converts from an index - * to dBFS. - * has_dependent_dev - true if this iodev has dependent device. - */ -struct alsa_io { - struct cras_iodev base; - char *pcm_name; - char *dev_name; - char *dev_id; - uint32_t device_index; - uint32_t next_ionode_index; - enum CRAS_ALSA_CARD_TYPE card_type; - int is_first; - int fully_specified; - snd_pcm_t *handle; - unsigned int num_severe_underruns; - snd_pcm_stream_t alsa_stream; - struct cras_alsa_mixer *mixer; - const struct cras_card_config *config; - struct cras_alsa_jack_list *jack_list; - struct cras_use_case_mgr *ucm; - snd_pcm_uframes_t mmap_offset; - int poll_fd; - unsigned int dma_period_set_microsecs; - int free_running; - unsigned int filled_zeros_for_draining; - snd_pcm_uframes_t severe_underrun_frames; - struct cras_volume_curve *default_volume_curve; - int hwparams_set; - int has_dependent_dev; -}; - -static void init_device_settings(struct alsa_io *aio); - -static int alsa_iodev_set_active_node(struct cras_iodev *iodev, - struct cras_ionode *ionode, - unsigned dev_enabled); - -static int get_fixed_rate(struct alsa_io *aio); - -static int update_supported_formats(struct cras_iodev *iodev); - -/* - * Defines the default values of nodes. - */ -static const struct { - const char *name; - enum CRAS_NODE_TYPE type; - enum CRAS_NODE_POSITION position; -} node_defaults[] = { - { - .name = DEFAULT, - .type = CRAS_NODE_TYPE_UNKNOWN, - .position = NODE_POSITION_INTERNAL, - }, - { - .name = INTERNAL_SPEAKER, - .type = CRAS_NODE_TYPE_INTERNAL_SPEAKER, - .position = NODE_POSITION_INTERNAL, - }, - { - .name = INTERNAL_MICROPHONE, - .type = CRAS_NODE_TYPE_MIC, - .position = NODE_POSITION_INTERNAL, - }, - { - .name = KEYBOARD_MIC, - .type = CRAS_NODE_TYPE_MIC, - .position = NODE_POSITION_KEYBOARD, - }, - { - .name = HDMI, - .type = CRAS_NODE_TYPE_HDMI, - .position = NODE_POSITION_EXTERNAL, - }, - { - .name = "IEC958", - .type = CRAS_NODE_TYPE_HDMI, - .position = NODE_POSITION_EXTERNAL, - }, - { - .name = "Headphone", - .type = CRAS_NODE_TYPE_HEADPHONE, - .position = NODE_POSITION_EXTERNAL, - }, - { - .name = "Front Headphone", - .type = CRAS_NODE_TYPE_HEADPHONE, - .position = NODE_POSITION_EXTERNAL, - }, - { - .name = "Front Mic", - .type = CRAS_NODE_TYPE_MIC, - .position = NODE_POSITION_FRONT, - }, - { - .name = "Rear Mic", - .type = CRAS_NODE_TYPE_MIC, - .position = NODE_POSITION_REAR, - }, - { - .name = "Mic", - .type = CRAS_NODE_TYPE_MIC, - .position = NODE_POSITION_EXTERNAL, - }, - { - .name = HOTWORD_DEV, - .type = CRAS_NODE_TYPE_HOTWORD, - .position = NODE_POSITION_INTERNAL, - }, - { - .name = "Haptic", - .type = CRAS_NODE_TYPE_HAPTIC, - .position = NODE_POSITION_INTERNAL, - }, - { - .name = "Rumbler", - .type = CRAS_NODE_TYPE_HAPTIC, - .position = NODE_POSITION_INTERNAL, - }, - { - .name = "Line Out", - .type = CRAS_NODE_TYPE_LINEOUT, - .position = NODE_POSITION_EXTERNAL, - }, - { - .name = "SCO Line In", - .type = CRAS_NODE_TYPE_BLUETOOTH, - .position = NODE_POSITION_EXTERNAL, - }, - { - .name = "SCO Line Out", - .type = CRAS_NODE_TYPE_BLUETOOTH, - .position = NODE_POSITION_EXTERNAL, - }, - { - .name = "Echo Reference", - .type = CRAS_NODE_TYPE_ECHO_REFERENCE, - .position = NODE_POSITION_INTERNAL, - }, - { - .name = LOOPBACK_CAPTURE, - .type = CRAS_NODE_TYPE_ALSA_LOOPBACK, - .position = NODE_POSITION_INTERNAL, - }, - { - .name = LOOPBACK_PLAYBACK, - .type = CRAS_NODE_TYPE_ALSA_LOOPBACK, - .position = NODE_POSITION_INTERNAL, - }, -}; - -static int set_hwparams(struct cras_iodev *iodev) -{ - struct alsa_io *aio = (struct alsa_io *)iodev; - int period_wakeup; - int rc; - - /* Only need to set hardware params once. */ - if (aio->hwparams_set) - return 0; - - /* If it's a wake on voice device, period_wakeups are required. */ - period_wakeup = (iodev->active_node->type == CRAS_NODE_TYPE_HOTWORD); - - /* Sets frame rate and channel count to alsa device before - * we test channel mapping. */ - rc = cras_alsa_set_hwparams(aio->handle, iodev->format, - &iodev->buffer_size, period_wakeup, - aio->dma_period_set_microsecs); - if (rc < 0) - return rc; - - aio->hwparams_set = 1; - return 0; -} - -/* - * iodev callbacks. - */ - -static int frames_queued(const struct cras_iodev *iodev, - struct timespec *tstamp) -{ - struct alsa_io *aio = (struct alsa_io *)iodev; - int rc; - snd_pcm_uframes_t frames; - - rc = cras_alsa_get_avail_frames(aio->handle, aio->base.buffer_size, - aio->severe_underrun_frames, - iodev->info.name, &frames, tstamp); - if (rc < 0) { - if (rc == -EPIPE) - aio->num_severe_underruns++; - return rc; - } - clock_gettime(CLOCK_MONOTONIC_RAW, tstamp); - if (iodev->direction == CRAS_STREAM_INPUT) - return (int)frames; - - /* For output, return number of frames that are used. */ - return iodev->buffer_size - frames; -} - -static int delay_frames(const struct cras_iodev *iodev) -{ - struct alsa_io *aio = (struct alsa_io *)iodev; - snd_pcm_sframes_t delay; - int rc; - - rc = cras_alsa_get_delay_frames(aio->handle, iodev->buffer_size, - &delay); - if (rc < 0) - return rc; - - return (int)delay; -} - -static int close_dev(struct cras_iodev *iodev) -{ - struct alsa_io *aio = (struct alsa_io *)iodev; - - /* Removes audio thread callback from main thread. */ - if (aio->poll_fd >= 0) - audio_thread_rm_callback_sync( - cras_iodev_list_get_audio_thread(), aio->poll_fd); - if (!aio->handle) - return 0; - cras_alsa_pcm_close(aio->handle); - aio->handle = NULL; - aio->free_running = 0; - aio->filled_zeros_for_draining = 0; - aio->hwparams_set = 0; - cras_iodev_free_format(&aio->base); - cras_iodev_free_audio_area(&aio->base); - return 0; -} - -static int empty_hotword_cb(void *arg, int revents) -{ - /* Only need this once. */ - struct alsa_io *aio = (struct alsa_io *)arg; - audio_thread_rm_callback(aio->poll_fd); - aio->poll_fd = -1; - aio->base.input_streaming = 1; - - /* Send hotword triggered signal. */ - cras_hotword_send_triggered_msg(); - - return 0; -} - -static int open_dev(struct cras_iodev *iodev) -{ - struct alsa_io *aio = (struct alsa_io *)iodev; - snd_pcm_t *handle; - int rc; - const char *pcm_name = NULL; - int enable_noise_cancellation; - - if (aio->base.direction == CRAS_STREAM_OUTPUT) { - struct alsa_output_node *aout = - (struct alsa_output_node *)aio->base.active_node; - pcm_name = aout->pcm_name; - } else { - struct alsa_input_node *ain = - (struct alsa_input_node *)aio->base.active_node; - pcm_name = ain->pcm_name; - } - - /* For legacy UCM path which doesn't have PlaybackPCM or CapturePCM. */ - if (pcm_name == NULL) - pcm_name = aio->pcm_name; - - rc = cras_alsa_pcm_open(&handle, pcm_name, aio->alsa_stream); - if (rc < 0) - return rc; - - aio->handle = handle; - - /* Enable or disable noise cancellation if it supports. */ - if (aio->ucm && iodev->direction == CRAS_STREAM_INPUT && - ucm_node_noise_cancellation_exists(aio->ucm, - iodev->active_node->name)) { - enable_noise_cancellation = - cras_system_get_noise_cancellation_enabled(); - rc = ucm_enable_node_noise_cancellation( - aio->ucm, iodev->active_node->name, - enable_noise_cancellation); - if (rc < 0) - return rc; - } - - return 0; -} - -static int configure_dev(struct cras_iodev *iodev) -{ - struct alsa_io *aio = (struct alsa_io *)iodev; - int rc; - - /* This is called after the first stream added so configure for it. - * format must be set before opening the device. - */ - if (iodev->format == NULL) - return -EINVAL; - aio->free_running = 0; - aio->filled_zeros_for_draining = 0; - aio->severe_underrun_frames = - SEVERE_UNDERRUN_MS * iodev->format->frame_rate / 1000; - - cras_iodev_init_audio_area(iodev, iodev->format->num_channels); - - syslog(LOG_DEBUG, "Configure alsa device %s rate %zuHz, %zu channels", - aio->pcm_name, iodev->format->frame_rate, - iodev->format->num_channels); - - rc = set_hwparams(iodev); - if (rc < 0) - return rc; - - /* Set channel map to device */ - rc = cras_alsa_set_channel_map(aio->handle, iodev->format); - if (rc < 0) - return rc; - - /* Configure software params. */ - rc = cras_alsa_set_swparams(aio->handle); - if (rc < 0) - return rc; - - /* Initialize device settings. */ - init_device_settings(aio); - - aio->poll_fd = -1; - if (iodev->active_node->type == CRAS_NODE_TYPE_HOTWORD) { - struct pollfd *ufds; - int count, i; - - count = snd_pcm_poll_descriptors_count(aio->handle); - if (count <= 0) { - syslog(LOG_ERR, "Invalid poll descriptors count\n"); - return count; - } - - ufds = (struct pollfd *)malloc(sizeof(struct pollfd) * count); - if (ufds == NULL) - return -ENOMEM; - - rc = snd_pcm_poll_descriptors(aio->handle, ufds, count); - if (rc < 0) { - syslog(LOG_ERR, - "Getting hotword poll descriptors: %s\n", - snd_strerror(rc)); - free(ufds); - return rc; - } - - for (i = 0; i < count; i++) { - if (ufds[i].events & POLLIN) { - aio->poll_fd = ufds[i].fd; - break; - } - } - free(ufds); - - if (aio->poll_fd >= 0) - audio_thread_add_events_callback( - aio->poll_fd, empty_hotword_cb, aio, POLLIN); - } - - /* Capture starts right away, playback will wait for samples. */ - if (aio->alsa_stream == SND_PCM_STREAM_CAPTURE) - cras_alsa_pcm_start(aio->handle); - - return 0; -} - -/* - * Check if ALSA device is opened by checking if handle is valid. - * Note that to fully open a cras_iodev, ALSA device is opened first, then there - * are some device init settings to be done in init_device_settings. - * Therefore, when setting volume/mute/gain in init_device_settings, - * cras_iodev is not in CRAS_IODEV_STATE_OPEN yet. We need to check if handle - * is valid when setting those properties, instead of checking - * cras_iodev_is_open. - */ -static int has_handle(const struct alsa_io *aio) -{ - return !!aio->handle; -} - -static int start(const struct cras_iodev *iodev) -{ - struct alsa_io *aio = (struct alsa_io *)iodev; - snd_pcm_t *handle = aio->handle; - int rc; - - if (snd_pcm_state(handle) == SND_PCM_STATE_RUNNING) - return 0; - - if (snd_pcm_state(handle) == SND_PCM_STATE_SUSPENDED) { - rc = cras_alsa_attempt_resume(handle); - if (rc < 0) { - syslog(LOG_ERR, "Resume error: %s", snd_strerror(rc)); - return rc; - } - cras_iodev_reset_rate_estimator(iodev); - } else { - rc = cras_alsa_pcm_start(handle); - if (rc < 0) { - syslog(LOG_ERR, "Start error: %s", snd_strerror(rc)); - return rc; - } - } - - return 0; -} - -static int get_buffer(struct cras_iodev *iodev, struct cras_audio_area **area, - unsigned *frames) -{ - struct alsa_io *aio = (struct alsa_io *)iodev; - snd_pcm_uframes_t nframes = *frames; - uint8_t *dst = NULL; - size_t format_bytes; - int rc; - - aio->mmap_offset = 0; - format_bytes = cras_get_format_bytes(iodev->format); - - rc = cras_alsa_mmap_begin(aio->handle, format_bytes, &dst, - &aio->mmap_offset, &nframes); - - iodev->area->frames = nframes; - cras_audio_area_config_buf_pointers(iodev->area, iodev->format, dst); - - *area = iodev->area; - *frames = nframes; - - return rc; -} - -static int put_buffer(struct cras_iodev *iodev, unsigned nwritten) -{ - struct alsa_io *aio = (struct alsa_io *)iodev; - - return cras_alsa_mmap_commit(aio->handle, aio->mmap_offset, nwritten); -} - -static int flush_buffer(struct cras_iodev *iodev) -{ - struct alsa_io *aio = (struct alsa_io *)iodev; - snd_pcm_uframes_t nframes; - - if (iodev->direction == CRAS_STREAM_INPUT) { - nframes = snd_pcm_avail(aio->handle); - nframes = snd_pcm_forwardable(aio->handle); - return snd_pcm_forward(aio->handle, nframes); - } - return 0; -} - -/* - * Gets the first plugged node in list. This is used as the - * default node to set as active. - */ -static struct cras_ionode *first_plugged_node(struct cras_iodev *iodev) -{ - struct cras_ionode *n; - - /* When this is called at iodev creation, none of the nodes - * are selected. Just pick the first plugged one and let Chrome - * choose it later. */ - DL_FOREACH (iodev->nodes, n) { - if (n->plugged) - return n; - } - return iodev->nodes; -} - -static void update_active_node(struct cras_iodev *iodev, unsigned node_idx, - unsigned dev_enabled) -{ - struct cras_ionode *n; - - /* If a node exists for node_idx, set it as active. */ - DL_FOREACH (iodev->nodes, n) { - if (n->idx == node_idx) { - alsa_iodev_set_active_node(iodev, n, dev_enabled); - return; - } - } - - alsa_iodev_set_active_node(iodev, first_plugged_node(iodev), - dev_enabled); -} - -static int update_channel_layout(struct cras_iodev *iodev) -{ - struct alsa_io *aio = (struct alsa_io *)iodev; - int err = 0; - - /* If the capture channel map is specified in UCM, prefer it over - * what ALSA provides. */ - if (aio->ucm && (iodev->direction == CRAS_STREAM_INPUT)) { - struct alsa_input_node *input = - (struct alsa_input_node *)iodev->active_node; - - if (input->channel_layout) { - memcpy(iodev->format->channel_layout, - input->channel_layout, - CRAS_CH_MAX * sizeof(*input->channel_layout)); - return 0; - } - } - - err = set_hwparams(iodev); - if (err < 0) - return err; - - return cras_alsa_get_channel_map(aio->handle, iodev->format); -} - -static int set_hotword_model(struct cras_iodev *iodev, const char *model_name) -{ - struct alsa_io *aio = (struct alsa_io *)iodev; - if (!aio->ucm) - return -EINVAL; - - return ucm_set_hotword_model(aio->ucm, model_name); -} - -static char *get_hotword_models(struct cras_iodev *iodev) -{ - struct alsa_io *aio = (struct alsa_io *)iodev; - if (!aio->ucm) - return NULL; - - return ucm_get_hotword_models(aio->ucm); -} - -/* - * Alsa helper functions. - */ - -static struct alsa_output_node *get_active_output(const struct alsa_io *aio) -{ - return (struct alsa_output_node *)aio->base.active_node; -} - -static struct alsa_input_node *get_active_input(const struct alsa_io *aio) -{ - return (struct alsa_input_node *)aio->base.active_node; -} - -/* - * Gets the curve for the active output node. If the node doesn't have volume - * curve specified, return the default volume curve of the parent iodev. - */ -static const struct cras_volume_curve * -get_curve_for_output_node(const struct alsa_io *aio, - const struct alsa_output_node *node) -{ - if (node && node->volume_curve) - return node->volume_curve; - return aio->default_volume_curve; -} - -/* - * Gets the curve for the active output. - */ -static const struct cras_volume_curve * -get_curve_for_active_output(const struct alsa_io *aio) -{ - struct alsa_output_node *node = get_active_output(aio); - return get_curve_for_output_node(aio, node); -} - -/* - * Informs the system of the volume limits for this device. - */ -static void set_alsa_volume_limits(struct alsa_io *aio) -{ - const struct cras_volume_curve *curve; - - /* Only set the limits if the dev is active. */ - if (!has_handle(aio)) - return; - - curve = get_curve_for_active_output(aio); - cras_system_set_volume_limits(curve->get_dBFS(curve, 1), /* min */ - curve->get_dBFS(curve, - CRAS_MAX_SYSTEM_VOLUME)); -} - -/* - * Sets the volume of the playback device to the specified level. Receives a - * volume index from the system settings, ranging from 0 to 100, converts it to - * dB using the volume curve, and sends the dB value to alsa. - */ -static void set_alsa_volume(struct cras_iodev *iodev) -{ - const struct alsa_io *aio = (const struct alsa_io *)iodev; - const struct cras_volume_curve *curve; - size_t volume; - struct alsa_output_node *aout; - - assert(aio); - if (aio->mixer == NULL) - return; - - /* Only set the volume if the dev is active. */ - if (!has_handle(aio)) - return; - - volume = cras_system_get_volume(); - curve = get_curve_for_active_output(aio); - if (curve == NULL) - return; - aout = get_active_output(aio); - if (aout) - volume = cras_iodev_adjust_node_volume(&aout->base, volume); - - /* Samples get scaled for devices using software volume, set alsa - * volume to 100. */ - if (cras_iodev_software_volume_needed(iodev)) - volume = 100; - - cras_alsa_mixer_set_dBFS(aio->mixer, curve->get_dBFS(curve, volume), - aout ? aout->mixer_output : NULL); -} - -/* - * Sets the alsa mute control for this iodev. - */ -static void set_alsa_mute(struct cras_iodev *iodev) -{ - const struct alsa_io *aio = (const struct alsa_io *)iodev; - struct alsa_output_node *aout; - - if (!has_handle(aio)) - return; - - aout = get_active_output(aio); - cras_alsa_mixer_set_mute(aio->mixer, cras_system_get_mute(), - aout ? aout->mixer_output : NULL); -} - -/* - * Sets the capture gain to the current system input gain level, given in dBFS. - * Set mute based on the system mute state. This gain can be positive or - * negative and might be adjusted often if an app is running an AGC. - */ -static void set_alsa_capture_gain(struct cras_iodev *iodev) -{ - const struct alsa_io *aio = (const struct alsa_io *)iodev; - struct alsa_input_node *ain; - long min_capture_gain, max_capture_gain, gain; - assert(aio); - if (aio->mixer == NULL) - return; - - /* Only set the volume if the dev is active. */ - if (!has_handle(aio)) - return; - - ain = get_active_input(aio); - - cras_alsa_mixer_set_capture_mute(aio->mixer, - cras_system_get_capture_mute(), - ain ? ain->mixer_input : NULL); - - /* For USB device without UCM config, not change a gain control. */ - if (ain && ain->base.type == CRAS_NODE_TYPE_USB && !aio->ucm) - return; - - /* Set hardware gain to 0dB if software gain is needed. */ - if (cras_iodev_software_volume_needed(iodev)) - gain = 0; - else { - min_capture_gain = cras_alsa_mixer_get_minimum_capture_gain( - aio->mixer, ain ? ain->mixer_input : NULL); - max_capture_gain = cras_alsa_mixer_get_maximum_capture_gain( - aio->mixer, ain ? ain->mixer_input : NULL); - gain = MAX(iodev->active_node->capture_gain, min_capture_gain); - gain = MIN(gain, max_capture_gain); - } - - cras_alsa_mixer_set_capture_dBFS(aio->mixer, gain, - ain ? ain->mixer_input : NULL); -} - -/* - * Swaps the left and right channels of the given node. - */ -static int set_alsa_node_swapped(struct cras_iodev *iodev, - struct cras_ionode *node, int enable) -{ - const struct alsa_io *aio = (const struct alsa_io *)iodev; - assert(aio); - return ucm_enable_swap_mode(aio->ucm, node->name, enable); -} - -/* - * Initializes the device settings according to system volume, mute, gain - * settings. - * Updates system capture gain limits based on current active device/node. - */ -static void init_device_settings(struct alsa_io *aio) -{ - /* Register for volume/mute callback and set initial volume/mute for - * the device. */ - if (aio->base.direction == CRAS_STREAM_OUTPUT) { - set_alsa_volume_limits(aio); - set_alsa_volume(&aio->base); - set_alsa_mute(&aio->base); - } else { - set_alsa_capture_gain(&aio->base); - } -} - -/* - * Functions run in the main server context. - */ - -/* - * Frees resources used by the alsa iodev. - * Args: - * iodev - the iodev to free the resources from. - */ -static void free_alsa_iodev_resources(struct alsa_io *aio) -{ - struct cras_ionode *node; - struct alsa_output_node *aout; - struct alsa_input_node *ain; - - free(aio->base.supported_rates); - free(aio->base.supported_channel_counts); - free(aio->base.supported_formats); - - DL_FOREACH (aio->base.nodes, node) { - if (aio->base.direction == CRAS_STREAM_OUTPUT) { - aout = (struct alsa_output_node *)node; - cras_volume_curve_destroy(aout->volume_curve); - free((void *)aout->pcm_name); - } else { - ain = (struct alsa_input_node *)node; - free((void *)ain->pcm_name); - } - cras_iodev_rm_node(&aio->base, node); - free(node->softvol_scalers); - free((void *)node->dsp_name); - free(node); - } - - cras_iodev_free_resources(&aio->base); - free(aio->pcm_name); - if (aio->dev_id) - free(aio->dev_id); - if (aio->dev_name) - free(aio->dev_name); -} - -/* - * Returns true if this is the first internal device. - */ -static int first_internal_device(struct alsa_io *aio) -{ - return aio->is_first && aio->card_type == ALSA_CARD_TYPE_INTERNAL; -} - -/* - * Returns true if there is already a node created with the given name. - */ -static int has_node(struct alsa_io *aio, const char *name) -{ - struct cras_ionode *node; - - DL_FOREACH (aio->base.nodes, node) - if (!strcmp(node->name, name)) - return 1; - - return 0; -} - -/* - * Returns true if string s ends with the given suffix. - */ -int endswith(const char *s, const char *suffix) -{ - size_t n = strlen(s); - size_t m = strlen(suffix); - return n >= m && !strcmp(s + (n - m), suffix); -} - -#ifdef CRAS_DBUS -/* - * Drop the node name and replace it with node type. - */ -static void drop_node_name(struct cras_ionode *node) -{ - if (node->type == CRAS_NODE_TYPE_USB) - strcpy(node->name, USB); - else if (node->type == CRAS_NODE_TYPE_HDMI) - strcpy(node->name, HDMI); - else { - /* Only HDMI or USB node might have invalid name to drop */ - syslog(LOG_ERR, - "Unexpectedly drop node name for " - "node: %s, type: %d", - node->name, node->type); - strcpy(node->name, DEFAULT); - } -} -#endif - -/* - * Sets the initial plugged state and type of a node based on its - * name. Chrome will assign priority to nodes base on node type. - */ -static void set_node_initial_state(struct cras_ionode *node, - enum CRAS_ALSA_CARD_TYPE card_type) -{ - unsigned i; - - node->volume = 100; - node->type = CRAS_NODE_TYPE_UNKNOWN; - /* Go through the known names */ - for (i = 0; i < ARRAY_SIZE(node_defaults); i++) - if (!strncmp(node->name, node_defaults[i].name, - strlen(node_defaults[i].name))) { - node->position = node_defaults[i].position; - node->plugged = - (node->position != NODE_POSITION_EXTERNAL); - node->type = node_defaults[i].type; - if (node->plugged) - gettimeofday(&node->plugged_time, NULL); - break; - } - - /* - * If we didn't find a matching name above, but the node is a jack node, - * and there is no "HDMI" in the node name, then this must be a 3.5mm - * headphone/mic. - * Set its type and name to headphone/mic. The name is important because - * it associates the UCM section to the node so the properties like - * default node gain can be obtained. - * This matches node names like "DAISY-I2S Mic Jack". - * If HDMI is in the node name, set its type to HDMI. This matches node names - * like "Rockchip HDMI Jack". - */ - if (i == ARRAY_SIZE(node_defaults)) { - if (endswith(node->name, "Jack") && !strstr(node->name, HDMI)) { - if (node->dev->direction == CRAS_STREAM_OUTPUT) { - node->type = CRAS_NODE_TYPE_HEADPHONE; - strncpy(node->name, HEADPHONE, - sizeof(node->name) - 1); - } else { - node->type = CRAS_NODE_TYPE_MIC; - strncpy(node->name, MIC, - sizeof(node->name) - 1); - } - } - if (strstr(node->name, HDMI) && - node->dev->direction == CRAS_STREAM_OUTPUT) - node->type = CRAS_NODE_TYPE_HDMI; - } - - /* Regardless of the node name of a USB headset (it can be "Speaker"), - * set it's type to usb. - */ - if (card_type == ALSA_CARD_TYPE_USB) { - node->type = CRAS_NODE_TYPE_USB; - node->position = NODE_POSITION_EXTERNAL; - } - -#ifdef CRAS_DBUS - if (!is_utf8_string(node->name)) - drop_node_name(node); -#endif -} - -static int get_ucm_flag_integer(struct alsa_io *aio, const char *flag_name, - int *result) -{ - char *value; - int i; - - if (!aio->ucm) - return -1; - - value = ucm_get_flag(aio->ucm, flag_name); - if (!value) - return -1; - - i = atoi(value); - free(value); - *result = i; - return 0; -} - -static int auto_unplug_input_node(struct alsa_io *aio) -{ - int result; - if (get_ucm_flag_integer(aio, "AutoUnplugInputNode", &result)) - return 0; - return result; -} - -static int auto_unplug_output_node(struct alsa_io *aio) -{ - int result; - if (get_ucm_flag_integer(aio, "AutoUnplugOutputNode", &result)) - return 0; - return result; -} - -static int no_create_default_input_node(struct alsa_io *aio) -{ - int result; - if (get_ucm_flag_integer(aio, "NoCreateDefaultInputNode", &result)) - return 0; - return result; -} - -static int no_create_default_output_node(struct alsa_io *aio) -{ - int result; - if (get_ucm_flag_integer(aio, "NoCreateDefaultOutputNode", &result)) - return 0; - return result; -} - -static void -set_output_node_software_volume_needed(struct alsa_output_node *output, - struct alsa_io *aio) -{ - struct cras_alsa_mixer *mixer = aio->mixer; - long range = 0; - - if (aio->ucm && ucm_get_disable_software_volume(aio->ucm)) { - output->base.software_volume_needed = 0; - syslog(LOG_DEBUG, "Disable software volume for %s from ucm.", - output->base.name); - return; - } - - /* Use software volume for HDMI output and nodes without volume mixer - * control. */ - if ((output->base.type == CRAS_NODE_TYPE_HDMI) || - (!cras_alsa_mixer_has_main_volume(mixer) && - !cras_alsa_mixer_has_volume(output->mixer_output))) - output->base.software_volume_needed = 1; - - /* Use software volume if the usb device's volume range is smaller - * than 40dB */ - if (output->base.type == CRAS_NODE_TYPE_USB) { - range += cras_alsa_mixer_get_dB_range(mixer); - range += cras_alsa_mixer_get_output_dB_range( - output->mixer_output); - if (range < 4000) - output->base.software_volume_needed = 1; - } - if (output->base.software_volume_needed) - syslog(LOG_DEBUG, "Use software volume for node: %s", - output->base.name); -} - -static void set_input_default_node_gain(struct alsa_input_node *input, - struct alsa_io *aio) -{ - long gain; - - input->base.capture_gain = DEFAULT_CAPTURE_GAIN; - input->base.ui_gain_scaler = 1.0f; - - if (!aio->ucm) - return; - - if (ucm_get_default_node_gain(aio->ucm, input->base.name, &gain) == 0) - input->base.capture_gain = gain; -} - -static void set_input_node_intrinsic_sensitivity(struct alsa_input_node *input, - struct alsa_io *aio) -{ - long sensitivity; - int rc; - - input->base.intrinsic_sensitivity = 0; - - if (aio->ucm) { - rc = ucm_get_intrinsic_sensitivity(aio->ucm, input->base.name, - &sensitivity); - if (rc) - return; - } else if (input->base.type == CRAS_NODE_TYPE_USB) { - /* - * For USB devices without UCM config, trust the default capture gain. - * Set sensitivity to the default dbfs so the capture gain is 0. - */ - sensitivity = DEFAULT_CAPTURE_VOLUME_DBFS; - } else { - return; - } - - input->base.intrinsic_sensitivity = sensitivity; - input->base.capture_gain = DEFAULT_CAPTURE_VOLUME_DBFS - sensitivity; - syslog(LOG_INFO, - "Use software gain %ld for %s because IntrinsicSensitivity %ld is" - " specified in UCM", - input->base.capture_gain, input->base.name, sensitivity); -} - -static void check_auto_unplug_output_node(struct alsa_io *aio, - struct cras_ionode *node, int plugged) -{ - struct cras_ionode *tmp; - - if (!auto_unplug_output_node(aio)) - return; - - /* Auto unplug internal speaker if any output node has been created */ - if (!strcmp(node->name, INTERNAL_SPEAKER) && plugged) { - DL_FOREACH (aio->base.nodes, tmp) - if (tmp->plugged && (tmp != node)) - cras_iodev_set_node_plugged(node, 0); - } else { - DL_FOREACH (aio->base.nodes, tmp) { - if (!strcmp(tmp->name, INTERNAL_SPEAKER)) - cras_iodev_set_node_plugged(tmp, !plugged); - } - } -} - -/* - * Callback for listing mixer outputs. The mixer will call this once for each - * output associated with this device. Most commonly this is used to tell the - * device it has Headphones and Speakers. - */ -static struct alsa_output_node *new_output(struct alsa_io *aio, - struct mixer_control *cras_output, - const char *name) -{ - struct alsa_output_node *output; - syslog(LOG_DEBUG, "New output node for '%s'", name); - if (aio == NULL) { - syslog(LOG_ERR, "Invalid aio when listing outputs."); - return NULL; - } - output = (struct alsa_output_node *)calloc(1, sizeof(*output)); - if (output == NULL) { - syslog(LOG_ERR, "Out of memory when listing outputs."); - return NULL; - } - output->base.dev = &aio->base; - output->base.idx = aio->next_ionode_index++; - output->base.stable_id = - SuperFastHash(name, strlen(name), aio->base.info.stable_id); - if (aio->ucm) - output->base.dsp_name = - ucm_get_dsp_name_for_dev(aio->ucm, name); - - if (strcmp(name, "SCO Line Out") == 0) - output->base.is_sco_pcm = 1; - output->mixer_output = cras_output; - - /* Volume curve. */ - output->volume_curve = cras_card_config_get_volume_curve_for_control( - aio->config, - name ? name : cras_alsa_mixer_get_control_name(cras_output)); - - strncpy(output->base.name, name, sizeof(output->base.name) - 1); - set_node_initial_state(&output->base, aio->card_type); - set_output_node_software_volume_needed(output, aio); - - cras_iodev_add_node(&aio->base, &output->base); - - check_auto_unplug_output_node(aio, &output->base, output->base.plugged); - return output; -} - -static void new_output_by_mixer_control(struct mixer_control *cras_output, - void *callback_arg) -{ - struct alsa_io *aio = (struct alsa_io *)callback_arg; - char node_name[CRAS_IODEV_NAME_BUFFER_SIZE]; - const char *ctl_name; - - ctl_name = cras_alsa_mixer_get_control_name(cras_output); - if (!ctl_name) - return; - - if (aio->card_type == ALSA_CARD_TYPE_USB) { - if (snprintf(node_name, sizeof(node_name), "%s: %s", - aio->base.info.name, ctl_name) > 0) { - new_output(aio, cras_output, node_name); - } - } else { - new_output(aio, cras_output, ctl_name); - } -} - -static void check_auto_unplug_input_node(struct alsa_io *aio, - struct cras_ionode *node, int plugged) -{ - struct cras_ionode *tmp; - if (!auto_unplug_input_node(aio)) - return; - - /* Auto unplug internal mic if any input node has already - * been created */ - if (!strcmp(node->name, INTERNAL_MICROPHONE) && plugged) { - DL_FOREACH (aio->base.nodes, tmp) - if (tmp->plugged && (tmp != node)) - cras_iodev_set_node_plugged(node, 0); - } else { - DL_FOREACH (aio->base.nodes, tmp) - if (!strcmp(tmp->name, INTERNAL_MICROPHONE)) - cras_iodev_set_node_plugged(tmp, !plugged); - } -} - -static struct alsa_input_node *new_input(struct alsa_io *aio, - struct mixer_control *cras_input, - const char *name) -{ - struct cras_iodev *iodev = &aio->base; - struct alsa_input_node *input; - int err; - - input = (struct alsa_input_node *)calloc(1, sizeof(*input)); - if (input == NULL) { - syslog(LOG_ERR, "Out of memory when listing inputs."); - return NULL; - } - input->base.dev = &aio->base; - input->base.idx = aio->next_ionode_index++; - input->base.stable_id = - SuperFastHash(name, strlen(name), aio->base.info.stable_id); - if (strcmp(name, "SCO Line In") == 0) - input->base.is_sco_pcm = 1; - input->mixer_input = cras_input; - strncpy(input->base.name, name, sizeof(input->base.name) - 1); - set_node_initial_state(&input->base, aio->card_type); - set_input_default_node_gain(input, aio); - set_input_node_intrinsic_sensitivity(input, aio); - - if (aio->ucm) { - /* Check if channel map is specified in UCM. */ - input->channel_layout = (int8_t *)malloc( - CRAS_CH_MAX * sizeof(*input->channel_layout)); - err = ucm_get_capture_chmap_for_dev(aio->ucm, name, - input->channel_layout); - if (err) { - free(input->channel_layout); - input->channel_layout = 0; - } - if (ucm_get_preempt_hotword(aio->ucm, name)) { - iodev->pre_open_iodev_hook = - cras_iodev_list_suspend_hotword_streams; - iodev->post_close_iodev_hook = - cras_iodev_list_resume_hotword_stream; - } - - input->base.dsp_name = ucm_get_dsp_name_for_dev(aio->ucm, name); - } - - cras_iodev_add_node(&aio->base, &input->base); - check_auto_unplug_input_node(aio, &input->base, input->base.plugged); - return input; -} - -static void new_input_by_mixer_control(struct mixer_control *cras_input, - void *callback_arg) -{ - struct alsa_io *aio = (struct alsa_io *)callback_arg; - char node_name[CRAS_IODEV_NAME_BUFFER_SIZE]; - const char *ctl_name = cras_alsa_mixer_get_control_name(cras_input); - - if (aio->card_type == ALSA_CARD_TYPE_USB) { - int ret = snprintf(node_name, sizeof(node_name), "%s: %s", - aio->base.info.name, ctl_name); - // Truncation is OK, but add a check to make the compiler happy. - if (ret == sizeof(node_name)) - node_name[sizeof(node_name) - 1] = '\0'; - new_input(aio, cras_input, node_name); - } else { - new_input(aio, cras_input, ctl_name); - } -} - -/* - * Finds the output node associated with the jack. Returns NULL if not found. - */ -static struct alsa_output_node * -get_output_node_from_jack(struct alsa_io *aio, - const struct cras_alsa_jack *jack) -{ - struct mixer_control *mixer_output; - struct cras_ionode *node = NULL; - struct alsa_output_node *aout = NULL; - - /* Search by jack first. */ - DL_SEARCH_SCALAR_WITH_CAST(aio->base.nodes, node, aout, jack, jack); - if (aout) - return aout; - - /* Search by mixer control next. */ - mixer_output = cras_alsa_jack_get_mixer_output(jack); - if (mixer_output == NULL) - return NULL; - - DL_SEARCH_SCALAR_WITH_CAST(aio->base.nodes, node, aout, mixer_output, - mixer_output); - return aout; -} - -static struct alsa_input_node * -get_input_node_from_jack(struct alsa_io *aio, const struct cras_alsa_jack *jack) -{ - struct mixer_control *mixer_input; - struct cras_ionode *node = NULL; - struct alsa_input_node *ain = NULL; - - mixer_input = cras_alsa_jack_get_mixer_input(jack); - if (mixer_input == NULL) { - DL_SEARCH_SCALAR_WITH_CAST(aio->base.nodes, node, ain, jack, - jack); - return ain; - } - - DL_SEARCH_SCALAR_WITH_CAST(aio->base.nodes, node, ain, mixer_input, - mixer_input); - return ain; -} - -static const struct cras_alsa_jack *get_jack_from_node(struct cras_ionode *node) -{ - const struct cras_alsa_jack *jack = NULL; - - if (node == NULL) - return NULL; - - if (node->dev->direction == CRAS_STREAM_OUTPUT) - jack = ((struct alsa_output_node *)node)->jack; - else if (node->dev->direction == CRAS_STREAM_INPUT) - jack = ((struct alsa_input_node *)node)->jack; - - return jack; -} - -/* - * Returns the dsp name specified in the ucm config. If there is a dsp name - * specified for the active node, use that. Otherwise NULL should be returned. - */ -static const char *get_active_dsp_name(struct alsa_io *aio) -{ - struct cras_ionode *node = aio->base.active_node; - - if (node == NULL) - return NULL; - - return node->dsp_name; -} - -/* - * Creates volume curve for the node associated with given jack. - */ -static struct cras_volume_curve * -create_volume_curve_for_jack(const struct cras_card_config *config, - const struct cras_alsa_jack *jack) -{ - struct cras_volume_curve *curve; - const char *name; - - /* Use jack's UCM device name as key to get volume curve. */ - name = cras_alsa_jack_get_ucm_device(jack); - curve = cras_card_config_get_volume_curve_for_control(config, name); - if (curve) - return curve; - - /* Use alsa jack's name as key to get volume curve. */ - name = cras_alsa_jack_get_name(jack); - curve = cras_card_config_get_volume_curve_for_control(config, name); - if (curve) - return curve; - - return NULL; -} - -/* - * Updates max_supported_channels value into cras_iodev_info. - * Note that supported_rates, supported_channel_counts, and supported_formats of - * iodev will be updated to the latest values after calling. - */ -static void update_max_supported_channels(struct cras_iodev *iodev) -{ - struct alsa_io *aio = (struct alsa_io *)iodev; - unsigned int max_channels = 0; - size_t i; - bool active_node_predicted = false; - int rc; - - /* - * max_supported_channels might be wrong in dependent PCM cases. Always - * return 2 for such cases. - */ - if (aio->has_dependent_dev) { - max_channels = 2; - goto update_info; - } - - if (aio->handle) { - syslog(LOG_ERR, - "update_max_supported_channels should not be called " - "while device is opened."); - return; - } - - /* - * In the case of updating max_supported_channels on changing jack - * plugging status of devices, the active node may not be determined - * yet. Use the first node as the active node for obtaining the value of - * max_supported_channels. - */ - if (!iodev->active_node) { - if (!iodev->nodes) - goto update_info; - iodev->active_node = iodev->nodes; - syslog(LOG_DEBUG, - "Predict ionode %s as active node temporarily.", - iodev->active_node->name); - active_node_predicted = true; - } - - rc = open_dev(iodev); - if (active_node_predicted) - iodev->active_node = NULL; // Reset the predicted active_node. - if (rc) - goto update_info; - - rc = update_supported_formats(iodev); - if (rc) - goto close_iodev; - - for (i = 0; iodev->supported_channel_counts[i] != 0; i++) { - if (iodev->supported_channel_counts[i] > max_channels) - max_channels = iodev->supported_channel_counts[i]; - } - -close_iodev: - close_dev(iodev); - -update_info: - iodev->info.max_supported_channels = max_channels; -} - -/* - * Callback that is called when an output jack is plugged or unplugged. - */ -static void jack_output_plug_event(const struct cras_alsa_jack *jack, - int plugged, void *arg) -{ - struct alsa_io *aio; - struct alsa_output_node *node; - const char *jack_name; - - if (arg == NULL) - return; - - aio = (struct alsa_io *)arg; - node = get_output_node_from_jack(aio, jack); - jack_name = cras_alsa_jack_get_name(jack); - if (!strcmp(jack_name, "Speaker Phantom Jack")) - jack_name = INTERNAL_SPEAKER; - - /* If there isn't a node for this jack, create one. */ - if (node == NULL) { - if (aio->fully_specified) { - /* When fully specified, can't have new nodes. */ - syslog(LOG_ERR, "No matching output node for jack %s!", - jack_name); - return; - } - node = new_output(aio, NULL, jack_name); - if (node == NULL) - return; - - cras_alsa_jack_update_node_type(jack, &(node->base.type)); - } - - if (!node->jack) { - if (aio->fully_specified) - syslog(LOG_ERR, - "Jack '%s' was found to match output node '%s'." - " Please fix your UCM configuration to match.", - jack_name, node->base.name); - - /* If we already have the node, associate with the jack. */ - node->jack = jack; - if (node->volume_curve == NULL) - node->volume_curve = - create_volume_curve_for_jack(aio->config, jack); - } - - syslog(LOG_DEBUG, "%s plugged: %d, %s", jack_name, plugged, - cras_alsa_mixer_get_control_name(node->mixer_output)); - - cras_alsa_jack_update_monitor_name(jack, node->base.name, - sizeof(node->base.name)); - -#ifdef CRAS_DBUS - /* The name got from jack might be an invalid UTF8 string. */ - if (!is_utf8_string(node->base.name)) - drop_node_name(&node->base); -#endif - - cras_iodev_set_node_plugged(&node->base, plugged); - - check_auto_unplug_output_node(aio, &node->base, plugged); - - /* - * For HDMI plug event cases, update max supported channels according - * to the current active node. - */ - if (node->base.type == CRAS_NODE_TYPE_HDMI && plugged) - update_max_supported_channels(&aio->base); -} - -/* - * Callback that is called when an input jack is plugged or unplugged. - */ -static void jack_input_plug_event(const struct cras_alsa_jack *jack, - int plugged, void *arg) -{ - struct alsa_io *aio; - struct alsa_input_node *node; - struct mixer_control *cras_input; - const char *jack_name; - - if (arg == NULL) - return; - aio = (struct alsa_io *)arg; - node = get_input_node_from_jack(aio, jack); - jack_name = cras_alsa_jack_get_name(jack); - - /* If there isn't a node for this jack, create one. */ - if (node == NULL) { - if (aio->fully_specified) { - /* When fully specified, can't have new nodes. */ - syslog(LOG_ERR, "No matching input node for jack %s!", - jack_name); - return; - } - cras_input = cras_alsa_jack_get_mixer_input(jack); - node = new_input(aio, cras_input, jack_name); - if (node == NULL) - return; - } - - syslog(LOG_DEBUG, "%s plugged: %d, %s", jack_name, plugged, - cras_alsa_mixer_get_control_name(node->mixer_input)); - - /* If we already have the node, associate with the jack. */ - if (!node->jack) { - if (aio->fully_specified) - syslog(LOG_ERR, - "Jack '%s' was found to match input node '%s'." - " Please fix your UCM configuration to match.", - jack_name, node->base.name); - node->jack = jack; - } - - cras_iodev_set_node_plugged(&node->base, plugged); - - check_auto_unplug_input_node(aio, &node->base, plugged); -} - -/* - * Sets the name of the given iodev, using the name and index of the card - * combined with the device index and direction. - */ -static void set_iodev_name(struct cras_iodev *dev, const char *card_name, - const char *dev_name, size_t card_index, - size_t device_index, - enum CRAS_ALSA_CARD_TYPE card_type, size_t usb_vid, - size_t usb_pid, char *usb_serial_number) -{ - snprintf(dev->info.name, sizeof(dev->info.name), "%s: %s:%zu,%zu", - card_name, dev_name, card_index, device_index); - dev->info.name[ARRAY_SIZE(dev->info.name) - 1] = '\0'; - syslog(LOG_DEBUG, "Add device name=%s", dev->info.name); - - dev->info.stable_id = - SuperFastHash(card_name, strlen(card_name), strlen(card_name)); - dev->info.stable_id = - SuperFastHash(dev_name, strlen(dev_name), dev->info.stable_id); - - switch (card_type) { - case ALSA_CARD_TYPE_INTERNAL: - dev->info.stable_id = SuperFastHash((const char *)&device_index, - sizeof(device_index), - dev->info.stable_id); - break; - case ALSA_CARD_TYPE_USB: - dev->info.stable_id = - SuperFastHash((const char *)&usb_vid, sizeof(usb_vid), - dev->info.stable_id); - dev->info.stable_id = - SuperFastHash((const char *)&usb_pid, sizeof(usb_pid), - dev->info.stable_id); - dev->info.stable_id = SuperFastHash(usb_serial_number, - strlen(usb_serial_number), - dev->info.stable_id); - break; - default: - break; - } - syslog(LOG_DEBUG, "Stable ID=%08x", dev->info.stable_id); -} - -static int get_fixed_rate(struct alsa_io *aio) -{ - const char *name; - - if (aio->base.direction == CRAS_STREAM_OUTPUT) { - struct alsa_output_node *active = get_active_output(aio); - if (!active) - return -ENOENT; - name = active->base.name; - } else { - struct alsa_input_node *active = get_active_input(aio); - if (!active) - return -ENOENT; - name = active->base.name; - } - - return ucm_get_sample_rate_for_dev(aio->ucm, name, aio->base.direction); -} - -static size_t get_fixed_channels(struct alsa_io *aio) -{ - const char *name; - int rc; - size_t channels; - - if (aio->base.direction == CRAS_STREAM_OUTPUT) { - struct alsa_output_node *active = get_active_output(aio); - if (!active) - return -ENOENT; - name = active->base.name; - } else { - struct alsa_input_node *active = get_active_input(aio); - if (!active) - return -ENOENT; - name = active->base.name; - } - - rc = ucm_get_channels_for_dev(aio->ucm, name, aio->base.direction, - &channels); - return (rc) ? 0 : channels; -} - -/* - * Updates the supported sample rates and channel counts. - */ -static int update_supported_formats(struct cras_iodev *iodev) -{ - struct alsa_io *aio = (struct alsa_io *)iodev; - int err; - int fixed_rate; - size_t fixed_channels; - - free(iodev->supported_rates); - iodev->supported_rates = NULL; - free(iodev->supported_channel_counts); - iodev->supported_channel_counts = NULL; - free(iodev->supported_formats); - iodev->supported_formats = NULL; - - err = cras_alsa_fill_properties(aio->handle, &iodev->supported_rates, - &iodev->supported_channel_counts, - &iodev->supported_formats); - if (err) - return err; - - if (aio->ucm) { - /* Allow UCM to override supplied rates. */ - fixed_rate = get_fixed_rate(aio); - if (fixed_rate > 0) { - free(iodev->supported_rates); - iodev->supported_rates = (size_t *)malloc( - 2 * sizeof(iodev->supported_rates[0])); - iodev->supported_rates[0] = fixed_rate; - iodev->supported_rates[1] = 0; - } - - /* Allow UCM to override supported channel counts. */ - fixed_channels = get_fixed_channels(aio); - if (fixed_channels > 0) { - free(iodev->supported_channel_counts); - iodev->supported_channel_counts = (size_t *)malloc( - 2 * sizeof(iodev->supported_channel_counts[0])); - iodev->supported_channel_counts[0] = fixed_channels; - iodev->supported_channel_counts[1] = 0; - } - } - return 0; -} - -/* - * Builds software volume scalers for output nodes in the device. - */ -static void build_softvol_scalers(struct alsa_io *aio) -{ - struct cras_ionode *ionode; - - DL_FOREACH (aio->base.nodes, ionode) { - struct alsa_output_node *aout; - const struct cras_volume_curve *curve; - - aout = (struct alsa_output_node *)ionode; - curve = get_curve_for_output_node(aio, aout); - - ionode->softvol_scalers = softvol_build_from_curve(curve); - } -} - -static void enable_active_ucm(struct alsa_io *aio, int plugged) -{ - const struct cras_alsa_jack *jack; - const char *name; - - if (aio->base.direction == CRAS_STREAM_OUTPUT) { - struct alsa_output_node *active = get_active_output(aio); - if (!active) - return; - name = active->base.name; - jack = active->jack; - } else { - struct alsa_input_node *active = get_active_input(aio); - if (!active) - return; - name = active->base.name; - jack = active->jack; - } - - if (jack) - cras_alsa_jack_enable_ucm(jack, plugged); - else if (aio->ucm) - ucm_set_enabled(aio->ucm, name, plugged); -} - -static int fill_whole_buffer_with_zeros(struct cras_iodev *iodev) -{ - struct alsa_io *aio = (struct alsa_io *)iodev; - int rc; - uint8_t *dst = NULL; - size_t format_bytes; - - /* Fill whole buffer with zeros. */ - rc = cras_alsa_mmap_get_whole_buffer(aio->handle, &dst); - - if (rc < 0) { - syslog(LOG_ERR, "Failed to get whole buffer: %s", - snd_strerror(rc)); - return rc; - } - - format_bytes = cras_get_format_bytes(iodev->format); - memset(dst, 0, iodev->buffer_size * format_bytes); - - return 0; -} - -/* - * Move appl_ptr to min_buffer_level + min_cb_level frames ahead of hw_ptr - * when resuming from free run. - */ -static int adjust_appl_ptr_for_leaving_free_run(struct cras_iodev *odev) -{ - struct alsa_io *aio = (struct alsa_io *)odev; - snd_pcm_uframes_t ahead; - - ahead = odev->min_buffer_level + odev->min_cb_level; - return cras_alsa_resume_appl_ptr(aio->handle, ahead); -} - -/* - * Move appl_ptr to min_buffer_level + min_cb_level * 1.5 frames ahead of - * hw_ptr when adjusting appl_ptr from underrun. - */ -static int adjust_appl_ptr_for_underrun(struct cras_iodev *odev) -{ - struct alsa_io *aio = (struct alsa_io *)odev; - snd_pcm_uframes_t ahead; - - ahead = odev->min_buffer_level + odev->min_cb_level + - odev->min_cb_level / 2; - return cras_alsa_resume_appl_ptr(aio->handle, ahead); -} - -/* This function is for leaving no-stream state but still not in free run yet. - * The device may have valid samples remaining. We need to adjust appl_ptr to - * the correct position, which is MAX(min_cb_level + min_buffer_level, - * valid_sample) */ -static int adjust_appl_ptr_samples_remaining(struct cras_iodev *odev) -{ - struct alsa_io *aio = (struct alsa_io *)odev; - int rc; - unsigned int real_hw_level, valid_sample, offset; - struct timespec hw_tstamp; - - /* Get the amount of valid samples which haven't been played yet. - * The real_hw_level is the real hw_level in device buffer. It doesn't - * subtract min_buffer_level. */ - valid_sample = 0; - rc = odev->frames_queued(odev, &hw_tstamp); - if (rc < 0) - return rc; - real_hw_level = rc; - - /* - * If underrun happened, handle it. Because alsa_output_underrun function - * has already called adjust_appl_ptr, we don't need to call it again. - */ - if (real_hw_level <= odev->min_buffer_level) - return cras_iodev_output_underrun(odev, real_hw_level, 0); - - if (real_hw_level > aio->filled_zeros_for_draining) - valid_sample = real_hw_level - aio->filled_zeros_for_draining; - - offset = MAX(odev->min_buffer_level + odev->min_cb_level, valid_sample); - - /* Fill zeros to make sure there are enough zero samples in device buffer.*/ - if (offset > real_hw_level) { - rc = cras_iodev_fill_odev_zeros(odev, offset - real_hw_level); - if (rc) - return rc; - } - return cras_alsa_resume_appl_ptr(aio->handle, offset); -} - -static int alsa_output_underrun(struct cras_iodev *odev) -{ - int rc; - - /* Fill whole buffer with zeros. This avoids samples left in buffer causing - * noise when device plays them. */ - rc = fill_whole_buffer_with_zeros(odev); - if (rc) - return rc; - /* Adjust appl_ptr to leave underrun. */ - return adjust_appl_ptr_for_underrun(odev); -} - -static int possibly_enter_free_run(struct cras_iodev *odev) -{ - struct alsa_io *aio = (struct alsa_io *)odev; - int rc; - unsigned int real_hw_level, fr_to_write; - struct timespec hw_tstamp; - - if (aio->free_running) - return 0; - - /* Check if all valid samples are played. If all valid samples are played, - * fill whole buffer with zeros. The real_hw_level is the real hw_level in - * device buffer. It doesn't subtract min_buffer_level.*/ - rc = odev->frames_queued(odev, &hw_tstamp); - if (rc < 0) - return rc; - real_hw_level = rc; - - /* If underrun happened, handle it and enter free run state. */ - if (real_hw_level <= odev->min_buffer_level) { - rc = cras_iodev_output_underrun(odev, real_hw_level, 0); - if (rc < 0) - return rc; - aio->free_running = 1; - return 0; - } - - if (real_hw_level <= aio->filled_zeros_for_draining || - real_hw_level == 0) { - rc = fill_whole_buffer_with_zeros(odev); - if (rc < 0) - return rc; - aio->free_running = 1; - return 0; - } - - /* Fill zeros to drain valid samples. */ - fr_to_write = MIN(cras_time_to_frames(&no_stream_fill_zeros_duration, - odev->format->frame_rate), - odev->buffer_size - real_hw_level); - rc = cras_iodev_fill_odev_zeros(odev, fr_to_write); - if (rc) - return rc; - aio->filled_zeros_for_draining += fr_to_write; - - return 0; -} - -static int leave_free_run(struct cras_iodev *odev) -{ - struct alsa_io *aio = (struct alsa_io *)odev; - int rc; - - /* Restart rate estimation because free run internval should not - * be included. */ - cras_iodev_reset_rate_estimator(odev); - - if (aio->free_running) - rc = adjust_appl_ptr_for_leaving_free_run(odev); - else - rc = adjust_appl_ptr_samples_remaining(odev); - if (rc) { - syslog(LOG_ERR, "device %s failed to leave free run, rc = %d", - odev->info.name, rc); - return rc; - } - aio->free_running = 0; - aio->filled_zeros_for_draining = 0; - - return 0; -} - -/* - * Free run state is the optimization of no_stream playback on alsa_io. - * The whole buffer will be filled with zeros. Device can play these zeros - * indefinitely. When there is new meaningful sample, appl_ptr should be - * resumed to some distance ahead of hw_ptr. - */ -static int no_stream(struct cras_iodev *odev, int enable) -{ - if (enable) - return possibly_enter_free_run(odev); - else - return leave_free_run(odev); -} - -static int is_free_running(const struct cras_iodev *odev) -{ - struct alsa_io *aio = (struct alsa_io *)odev; - - return aio->free_running; -} - -static unsigned int get_num_severe_underruns(const struct cras_iodev *iodev) -{ - const struct alsa_io *aio = (const struct alsa_io *)iodev; - return aio->num_severe_underruns; -} - -static void set_default_hotword_model(struct cras_iodev *iodev) -{ - const char *default_models[] = { "en_all", "en_us" }; - cras_node_id_t node_id; - unsigned i; - - if (!iodev->active_node || - iodev->active_node->type != CRAS_NODE_TYPE_HOTWORD) - return; - - node_id = cras_make_node_id(iodev->info.idx, iodev->active_node->idx); - /* This is a no-op if the default_model is not supported */ - for (i = 0; i < ARRAY_SIZE(default_models); ++i) - if (!cras_iodev_list_set_hotword_model(node_id, - default_models[i])) - return; -} - -static int get_valid_frames(struct cras_iodev *odev, struct timespec *tstamp) -{ - struct alsa_io *aio = (struct alsa_io *)odev; - int rc; - unsigned int real_hw_level; - - /* - * Get the amount of valid frames which haven't been played yet. - * The real_hw_level is the real hw_level in device buffer. It doesn't - * subtract min_buffer_level. - */ - if (aio->free_running) { - clock_gettime(CLOCK_MONOTONIC_RAW, tstamp); - return 0; - } - - rc = odev->frames_queued(odev, tstamp); - if (rc < 0) - return rc; - real_hw_level = rc; - - if (real_hw_level > aio->filled_zeros_for_draining) - return real_hw_level - aio->filled_zeros_for_draining; - - return 0; -} - -static int support_noise_cancellation(const struct cras_iodev *iodev) -{ - struct alsa_io *aio = (struct alsa_io *)iodev; - - if (!aio->ucm || !iodev->active_node) - return 0; - - return ucm_node_noise_cancellation_exists(aio->ucm, - iodev->active_node->name); -} - -/* - * Exported Interface. - */ - -struct cras_iodev * -alsa_iodev_create(size_t card_index, const char *card_name, size_t device_index, - const char *pcm_name, const char *dev_name, - const char *dev_id, enum CRAS_ALSA_CARD_TYPE card_type, - int is_first, struct cras_alsa_mixer *mixer, - const struct cras_card_config *config, - struct cras_use_case_mgr *ucm, snd_hctl_t *hctl, - enum CRAS_STREAM_DIRECTION direction, size_t usb_vid, - size_t usb_pid, char *usb_serial_number) -{ - struct alsa_io *aio; - struct cras_iodev *iodev; - - if (direction != CRAS_STREAM_INPUT && direction != CRAS_STREAM_OUTPUT) - return NULL; - - aio = (struct alsa_io *)calloc(1, sizeof(*aio)); - if (!aio) - return NULL; - iodev = &aio->base; - iodev->direction = direction; - - aio->device_index = device_index; - aio->card_type = card_type; - aio->is_first = is_first; - aio->handle = NULL; - aio->num_severe_underruns = 0; - if (dev_name) { - aio->dev_name = strdup(dev_name); - if (!aio->dev_name) - goto cleanup_iodev; - } - if (dev_id) { - aio->dev_id = strdup(dev_id); - if (!aio->dev_id) - goto cleanup_iodev; - } - aio->free_running = 0; - aio->filled_zeros_for_draining = 0; - aio->has_dependent_dev = 0; - aio->pcm_name = strdup(pcm_name); - if (aio->pcm_name == NULL) - goto cleanup_iodev; - - if (direction == CRAS_STREAM_INPUT) { - aio->alsa_stream = SND_PCM_STREAM_CAPTURE; - aio->base.set_capture_gain = set_alsa_capture_gain; - aio->base.set_capture_mute = set_alsa_capture_gain; - } else { - aio->alsa_stream = SND_PCM_STREAM_PLAYBACK; - aio->base.set_volume = set_alsa_volume; - aio->base.set_mute = set_alsa_mute; - aio->base.output_underrun = alsa_output_underrun; - } - iodev->open_dev = open_dev; - iodev->configure_dev = configure_dev; - iodev->close_dev = close_dev; - iodev->update_supported_formats = update_supported_formats; - iodev->frames_queued = frames_queued; - iodev->delay_frames = delay_frames; - iodev->get_buffer = get_buffer; - iodev->put_buffer = put_buffer; - iodev->flush_buffer = flush_buffer; - iodev->start = start; - iodev->update_active_node = update_active_node; - iodev->update_channel_layout = update_channel_layout; - iodev->set_hotword_model = set_hotword_model; - iodev->get_hotword_models = get_hotword_models; - iodev->no_stream = no_stream; - iodev->is_free_running = is_free_running; - iodev->get_num_severe_underruns = get_num_severe_underruns; - iodev->get_valid_frames = get_valid_frames; - iodev->set_swap_mode_for_node = cras_iodev_dsp_set_swap_mode_for_node; - iodev->support_noise_cancellation = support_noise_cancellation; - - if (card_type == ALSA_CARD_TYPE_USB) - iodev->min_buffer_level = USB_EXTRA_BUFFER_FRAMES; - - iodev->ramp = cras_ramp_create(); - if (iodev->ramp == NULL) - goto cleanup_iodev; - iodev->initial_ramp_request = CRAS_IODEV_RAMP_REQUEST_UP_START_PLAYBACK; - - aio->mixer = mixer; - aio->config = config; - if (direction == CRAS_STREAM_OUTPUT) { - aio->default_volume_curve = - cras_card_config_get_volume_curve_for_control( - config, "Default"); - if (aio->default_volume_curve == NULL) - aio->default_volume_curve = - cras_volume_curve_create_default(); - } - aio->ucm = ucm; - if (ucm) { - unsigned int level; - int rc; - - /* Set callback for swap mode if it is supported - * in ucm modifier. */ - if (ucm_swap_mode_exists(ucm)) - aio->base.set_swap_mode_for_node = - set_alsa_node_swapped; - - rc = ucm_get_min_buffer_level(ucm, &level); - if (!rc && direction == CRAS_STREAM_OUTPUT) - iodev->min_buffer_level = level; - } - - set_iodev_name(iodev, card_name, dev_name, card_index, device_index, - card_type, usb_vid, usb_pid, usb_serial_number); - - aio->jack_list = cras_alsa_jack_list_create( - card_index, card_name, device_index, is_first, mixer, ucm, hctl, - direction, - direction == CRAS_STREAM_OUTPUT ? jack_output_plug_event : - jack_input_plug_event, - aio); - if (!aio->jack_list) - goto cleanup_iodev; - - /* HDMI outputs don't have volume adjustment, do it in software. */ - if (direction == CRAS_STREAM_OUTPUT && strstr(dev_name, HDMI)) - iodev->software_volume_needed = 1; - - /* Add this now so that cleanup of the iodev (in case of error or card - * card removal will function as expected. */ - if (direction == CRAS_STREAM_OUTPUT) - cras_iodev_list_add_output(&aio->base); - else - cras_iodev_list_add_input(&aio->base); - return &aio->base; - -cleanup_iodev: - free_alsa_iodev_resources(aio); - free(aio); - return NULL; -} - -int alsa_iodev_legacy_complete_init(struct cras_iodev *iodev) -{ - struct alsa_io *aio = (struct alsa_io *)iodev; - const char *dev_name; - const char *dev_id; - enum CRAS_STREAM_DIRECTION direction; - int err; - int is_first; - struct cras_alsa_mixer *mixer; - - if (!aio) - return -EINVAL; - direction = iodev->direction; - dev_name = aio->dev_name; - dev_id = aio->dev_id; - is_first = aio->is_first; - mixer = aio->mixer; - - /* Create output nodes for mixer controls, such as Headphone - * and Speaker, only for the first device. */ - if (direction == CRAS_STREAM_OUTPUT && is_first) - cras_alsa_mixer_list_outputs(mixer, new_output_by_mixer_control, - aio); - else if (direction == CRAS_STREAM_INPUT && is_first) - cras_alsa_mixer_list_inputs(mixer, new_input_by_mixer_control, - aio); - - err = cras_alsa_jack_list_find_jacks_by_name_matching(aio->jack_list); - if (err) - return err; - - /* Create nodes for jacks that aren't associated with an - * already existing node. Get an initial read of the jacks for - * this device. */ - cras_alsa_jack_list_report(aio->jack_list); - - /* Make a default node if there is still no node for this - * device, or we still don't have the "Speaker"/"Internal Mic" - * node for the first internal device. Note that the default - * node creation can be supressed by UCM flags for platforms - * which really don't have an internal device. */ - if ((direction == CRAS_STREAM_OUTPUT) && - !no_create_default_output_node(aio)) { - if (first_internal_device(aio) && - !has_node(aio, INTERNAL_SPEAKER) && !has_node(aio, HDMI)) { - if (strstr(aio->base.info.name, HDMI)) - new_output(aio, NULL, HDMI); - else - new_output(aio, NULL, INTERNAL_SPEAKER); - } else if (!aio->base.nodes) { - new_output(aio, NULL, DEFAULT); - } - } else if ((direction == CRAS_STREAM_INPUT) && - !no_create_default_input_node(aio)) { - if (first_internal_device(aio) && - !has_node(aio, INTERNAL_MICROPHONE)) - new_input(aio, NULL, INTERNAL_MICROPHONE); - else if (strstr(dev_name, KEYBOARD_MIC)) - new_input(aio, NULL, KEYBOARD_MIC); - else if (dev_id && strstr(dev_id, HOTWORD_DEV)) - new_input(aio, NULL, HOTWORD_DEV); - else if (!aio->base.nodes) - new_input(aio, NULL, DEFAULT); - } - - /* Build software volume scalers. */ - if (direction == CRAS_STREAM_OUTPUT) - build_softvol_scalers(aio); - - /* Set the active node as the best node we have now. */ - alsa_iodev_set_active_node(&aio->base, first_plugged_node(&aio->base), - 0); - - /* Set plugged for the first USB device per card when it appears if - * there is no jack reporting plug status. */ - if (aio->card_type == ALSA_CARD_TYPE_USB && is_first && - !get_jack_from_node(iodev->active_node)) - cras_iodev_set_node_plugged(iodev->active_node, 1); - - set_default_hotword_model(iodev); - - /* Record max supported channels into cras_iodev_info. */ - update_max_supported_channels(iodev); - - return 0; -} - -int alsa_iodev_ucm_add_nodes_and_jacks(struct cras_iodev *iodev, - struct ucm_section *section) -{ - struct alsa_io *aio = (struct alsa_io *)iodev; - struct mixer_control *control; - struct alsa_input_node *input_node = NULL; - struct cras_alsa_jack *jack; - struct alsa_output_node *output_node = NULL; - int rc; - - if (!aio || !section) - return -EINVAL; - - /* Allow this section to add as a new node only if the device id - * or dependent device id matches this iodev. */ - if (((uint32_t)section->dev_idx != aio->device_index) && - ((uint32_t)section->dependent_dev_idx != aio->device_index)) - return -EINVAL; - - /* Set flag has_dependent_dev for the case of dependent device. */ - if (section->dependent_dev_idx != -1) - aio->has_dependent_dev = 1; - - /* This iodev is fully specified. Avoid automatic node creation. */ - aio->fully_specified = 1; - - /* Check here in case the DmaPeriodMicrosecs flag has only been - * specified on one of many device entries with the same PCM. */ - if (!aio->dma_period_set_microsecs) - aio->dma_period_set_microsecs = - ucm_get_dma_period_for_dev(aio->ucm, section->name); - - /* Create a node matching this section. If there is a matching - * control use that, otherwise make a node without a control. */ - control = cras_alsa_mixer_get_control_for_section(aio->mixer, section); - if (iodev->direction == CRAS_STREAM_OUTPUT) { - output_node = new_output(aio, control, section->name); - if (!output_node) - return -ENOMEM; - output_node->pcm_name = strdup(section->pcm_name); - } else if (iodev->direction == CRAS_STREAM_INPUT) { - input_node = new_input(aio, control, section->name); - if (!input_node) - return -ENOMEM; - input_node->pcm_name = strdup(section->pcm_name); - } - - /* Find any jack controls for this device. */ - rc = cras_alsa_jack_list_add_jack_for_section(aio->jack_list, section, - &jack); - if (rc) - return rc; - - /* Associated the jack with the node. */ - if (jack) { - if (output_node) { - output_node->jack = jack; - if (!output_node->volume_curve) - output_node->volume_curve = - create_volume_curve_for_jack( - aio->config, jack); - } else if (input_node) { - input_node->jack = jack; - } - } - return 0; -} - -void alsa_iodev_ucm_complete_init(struct cras_iodev *iodev) -{ - struct alsa_io *aio = (struct alsa_io *)iodev; - struct cras_ionode *node; - - if (!iodev) - return; - - /* Get an initial read of the jacks for this device. */ - cras_alsa_jack_list_report(aio->jack_list); - - /* Build software volume scaler. */ - if (iodev->direction == CRAS_STREAM_OUTPUT) - build_softvol_scalers(aio); - - /* Set the active node as the best node we have now. */ - alsa_iodev_set_active_node(&aio->base, first_plugged_node(&aio->base), - 0); - - /* - * Set plugged for the USB device per card when it appears if - * there is no jack reporting plug status - */ - if (aio->card_type == ALSA_CARD_TYPE_USB) { - DL_FOREACH (iodev->nodes, node) { - if (!get_jack_from_node(node)) - cras_iodev_set_node_plugged(node, 1); - } - } - - set_default_hotword_model(iodev); - - node = iodev->active_node; - - /* Record max supported channels into cras_iodev_info. */ - if (node && node->plugged) - update_max_supported_channels(iodev); -} - -void alsa_iodev_destroy(struct cras_iodev *iodev) -{ - struct alsa_io *aio = (struct alsa_io *)iodev; - int rc; - - if (iodev->direction == CRAS_STREAM_INPUT) - rc = cras_iodev_list_rm_input(iodev); - else - rc = cras_iodev_list_rm_output(iodev); - - if (rc == -EBUSY) { - syslog(LOG_ERR, "Failed to remove iodev %s", iodev->info.name); - return; - } - - /* Free resources when device successfully removed. */ - cras_alsa_jack_list_destroy(aio->jack_list); - free_alsa_iodev_resources(aio); - cras_volume_curve_destroy(aio->default_volume_curve); - free(iodev); -} - -unsigned alsa_iodev_index(struct cras_iodev *iodev) -{ - struct alsa_io *aio = (struct alsa_io *)iodev; - return aio->device_index; -} - -int alsa_iodev_has_hctl_jacks(struct cras_iodev *iodev) -{ - struct alsa_io *aio = (struct alsa_io *)iodev; - return cras_alsa_jack_list_has_hctl_jacks(aio->jack_list); -} - -static void alsa_iodev_unmute_node(struct alsa_io *aio, - struct cras_ionode *ionode) -{ - struct alsa_output_node *active = (struct alsa_output_node *)ionode; - struct mixer_control *mixer = active->mixer_output; - struct alsa_output_node *output; - struct cras_ionode *node; - - /* If this node is associated with mixer output, unmute the - * active mixer output and mute all others, otherwise just set - * the node as active and set the volume curve. */ - if (mixer) { - /* Unmute the active mixer output, mute all others. */ - DL_FOREACH (aio->base.nodes, node) { - output = (struct alsa_output_node *)node; - if (output->mixer_output) - cras_alsa_mixer_set_output_active_state( - output->mixer_output, node == ionode); - } - } -} - -static int alsa_iodev_set_active_node(struct cras_iodev *iodev, - struct cras_ionode *ionode, - unsigned dev_enabled) -{ - struct alsa_io *aio = (struct alsa_io *)iodev; - int rc = 0; - - if (iodev->active_node == ionode) - goto skip; - - /* Disable jack ucm before switching node. */ - enable_active_ucm(aio, 0); - if (dev_enabled && (iodev->direction == CRAS_STREAM_OUTPUT)) - alsa_iodev_unmute_node(aio, ionode); - - cras_iodev_set_active_node(iodev, ionode); - aio->base.dsp_name = get_active_dsp_name(aio); - cras_iodev_update_dsp(iodev); -skip: - enable_active_ucm(aio, dev_enabled); - if (ionode->type == CRAS_NODE_TYPE_HOTWORD) { - if (dev_enabled) { - rc = ucm_enable_hotword_model(aio->ucm); - if (rc < 0) - return rc; - } else - ucm_disable_all_hotword_models(aio->ucm); - } - /* Setting the volume will also unmute if the system isn't muted. */ - init_device_settings(aio); - return 0; -} |