| /* 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_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 MAX_ALSA_DEV_NAME_LENGTH 9 /* Alsa names "hw:XX,YY" + 1 for null. */ |
| #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 USB "USB" |
| |
| /* |
| * 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 |
| |
| /* |
| * This extends cras_ionode to include alsa-specific information. |
| * Members: |
| * mixer_output - From cras_alsa_mixer. |
| * 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; |
| 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 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". |
| * dev - String that names this device (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. |
| * enable_htimestamp - True when the device's htimestamp is used. |
| * handle - Handle to the opened ALSA device. |
| * num_underruns - Number of times we have run out of data (playback only). |
| * 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. |
| * dsp_name_default - the default dsp name for the device. It can be overridden |
| * by the jack specific dsp name. |
| * 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. |
| * is_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. |
| */ |
| struct alsa_io { |
| struct cras_iodev base; |
| char *dev; |
| 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; |
| int enable_htimestamp; |
| snd_pcm_t *handle; |
| unsigned int num_underruns; |
| 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; |
| const char *dsp_name_default; |
| int poll_fd; |
| unsigned int dma_period_set_microsecs; |
| int is_free_running; |
| unsigned int filled_zeros_for_draining; |
| snd_pcm_uframes_t severe_underrun_frames; |
| struct cras_volume_curve *default_volume_curve; |
| }; |
| |
| 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); |
| |
| /* |
| * 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, |
| }, |
| }; |
| |
| /* |
| * 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, |
| &aio->num_underruns); |
| if (rc < 0) { |
| if (rc == -EPIPE) |
| aio->num_severe_underruns++; |
| return rc; |
| } |
| if (!aio->enable_htimestamp) |
| 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->is_free_running = 0; |
| aio->filled_zeros_for_draining = 0; |
| cras_iodev_free_format(&aio->base); |
| cras_iodev_free_audio_area(&aio->base); |
| return 0; |
| } |
| |
| static int dummy_hotword_cb(void *arg) |
| { |
| /* Only need this once. */ |
| struct alsa_io *aio = (struct alsa_io *)arg; |
| audio_thread_rm_callback(aio->poll_fd); |
| aio->poll_fd = -1; |
| return 0; |
| } |
| |
| static int open_dev(struct cras_iodev *iodev) |
| { |
| struct alsa_io *aio = (struct alsa_io *)iodev; |
| snd_pcm_t *handle; |
| int period_wakeup; |
| 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->num_underruns = 0; |
| aio->is_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->dev, iodev->format->frame_rate, |
| iodev->format->num_channels); |
| handle = 0; /* Avoid unused warning. */ |
| rc = cras_alsa_pcm_open(&handle, aio->dev, aio->alsa_stream); |
| if (rc < 0) |
| return rc; |
| |
| /* If it's a wake on voice device, period_wakeups are required. */ |
| period_wakeup = (iodev->active_node->type == CRAS_NODE_TYPE_HOTWORD); |
| |
| rc = cras_alsa_set_hwparams(handle, iodev->format, |
| &iodev->buffer_size, period_wakeup, |
| aio->dma_period_set_microsecs); |
| if (rc < 0) { |
| cras_alsa_pcm_close(handle); |
| return rc; |
| } |
| |
| /* Set channel map to device */ |
| rc = cras_alsa_set_channel_map(handle, |
| iodev->format); |
| if (rc < 0) { |
| cras_alsa_pcm_close(handle); |
| return rc; |
| } |
| |
| /* Configure software params. */ |
| rc = cras_alsa_set_swparams(handle, &aio->enable_htimestamp); |
| if (rc < 0) { |
| cras_alsa_pcm_close(handle); |
| return rc; |
| } |
| |
| /* Assign pcm handle then initialize device settings. */ |
| aio->handle = handle; |
| 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(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(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_callback(aio->poll_fd, |
| dummy_hotword_cb, |
| aio); |
| } |
| |
| /* 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, |
| &aio->num_underruns); |
| |
| 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, |
| &aio->num_underruns); |
| } |
| |
| 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_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; |
| snd_pcm_t *handle = NULL; |
| snd_pcm_uframes_t buf_size = 0; |
| 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 = cras_alsa_pcm_open(&handle, aio->dev, aio->alsa_stream); |
| if (err < 0) { |
| syslog(LOG_ERR, "snd_pcm_open_failed: %s", snd_strerror(err)); |
| return err; |
| } |
| |
| /* Sets frame rate and channel count to alsa device before |
| * we test channel mapping. */ |
| err = cras_alsa_set_hwparams(handle, iodev->format, &buf_size, 0, |
| aio->dma_period_set_microsecs); |
| if (err < 0) { |
| cras_alsa_pcm_close(handle); |
| return err; |
| } |
| |
| err = cras_alsa_get_channel_map(handle, iodev->format); |
| |
| cras_alsa_pcm_close(handle); |
| return err; |
| } |
| |
| 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 alsa mute control for this iodev. |
| */ |
| static void set_alsa_mute_control(const struct alsa_io *aio, int muted) |
| { |
| struct alsa_output_node *aout; |
| |
| if (!has_handle(aio)) |
| return; |
| |
| aout = get_active_output(aio); |
| cras_alsa_mixer_set_mute( |
| aio->mixer, |
| muted, |
| aout ? aout->mixer_output : NULL); |
| } |
| |
| /* |
| * 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); |
| } |
| |
| static void set_alsa_mute(struct cras_iodev *iodev) |
| { |
| /* Mute for zero. */ |
| const struct alsa_io *aio = (const struct alsa_io *)iodev; |
| set_alsa_mute_control(aio, cras_system_get_mute()); |
| } |
| |
| /* |
| * 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 gain; |
| |
| assert(aio); |
| if (aio->mixer == NULL) |
| return; |
| |
| /* Only set the volume if the dev is active. */ |
| if (!has_handle(aio)) |
| return; |
| gain = cras_iodev_adjust_active_node_gain( |
| iodev, cras_system_get_capture_gain()); |
| |
| /* Set hardware gain to 0dB if software gain is needed. */ |
| if (cras_iodev_software_volume_needed(iodev)) |
| gain = 0; |
| |
| ain = get_active_input(aio); |
| |
| cras_alsa_mixer_set_capture_dBFS( |
| aio->mixer, |
| gain, |
| ain ? ain->mixer_input : NULL); |
| cras_alsa_mixer_set_capture_mute(aio->mixer, |
| cras_system_get_capture_mute(), |
| 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 { |
| struct mixer_control *mixer_input = NULL; |
| struct alsa_input_node *ain = get_active_input(aio); |
| long min_capture_gain, max_capture_gain; |
| |
| if (ain) |
| mixer_input = ain->mixer_input; |
| |
| if (cras_iodev_software_volume_needed(&aio->base)) { |
| min_capture_gain = DEFAULT_MIN_CAPTURE_GAIN; |
| max_capture_gain = cras_iodev_maximum_software_gain( |
| &aio->base); |
| } else { |
| min_capture_gain = |
| cras_alsa_mixer_get_minimum_capture_gain( |
| aio->mixer, mixer_input); |
| max_capture_gain = |
| cras_alsa_mixer_get_maximum_capture_gain( |
| aio->mixer, mixer_input); |
| } |
| cras_system_set_capture_gain_limits(min_capture_gain, |
| max_capture_gain); |
| 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; |
| |
| 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); |
| } |
| cras_iodev_rm_node(&aio->base, node); |
| free(node->softvol_scalers); |
| free(node); |
| } |
| |
| free((void *)aio->dsp_name_default); |
| cras_iodev_free_resources(&aio->base); |
| free(aio->dev); |
| 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); |
| } |
| |
| /* |
| * 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); |
| } |
| } |
| |
| /* |
| * 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, |
| * set its type to headphone/mic. 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")) { |
| if (node->dev->direction == CRAS_STREAM_OUTPUT) |
| node->type = CRAS_NODE_TYPE_HEADPHONE; |
| else |
| node->type = CRAS_NODE_TYPE_MIC; |
| } |
| 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; |
| } |
| |
| if (!is_utf8_string(node->name)) |
| drop_node_name(node); |
| } |
| |
| 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_node_software_volume_needed( |
| struct alsa_input_node *input, struct alsa_io *aio) |
| { |
| long max_software_gain; |
| int rc; |
| |
| input->base.software_volume_needed = 0; |
| input->base.max_software_gain = 0; |
| |
| /* Enable software gain only if max software gain is specified in UCM.*/ |
| if (!aio->ucm) |
| return; |
| |
| rc = ucm_get_max_software_gain(aio->ucm, input->base.name, |
| &max_software_gain); |
| if (rc) |
| return; |
| |
| input->base.software_volume_needed = 1; |
| input->base.max_software_gain = max_software_gain; |
| syslog(LOG_INFO, |
| "Use software gain for %s with max %ld because it is specified" |
| " in UCM", input->base.name, max_software_gain); |
| } |
| |
| static void set_input_default_node_gain(struct alsa_input_node *input, |
| struct alsa_io *aio) |
| { |
| long default_node_gain; |
| int rc; |
| |
| if (!aio->ucm) |
| return; |
| |
| rc = ucm_get_default_node_gain(aio->ucm, input->base.name, |
| &default_node_gain); |
| if (rc) |
| return; |
| |
| input->base.capture_gain = default_node_gain; |
| } |
| |
| 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_attr(node, |
| IONODE_ATTR_PLUGGED, |
| 0); |
| } else { |
| DL_FOREACH(aio->base.nodes, tmp) { |
| if (!strcmp(tmp->name, INTERNAL_SPEAKER)) |
| cras_iodev_set_node_attr(tmp, |
| IONODE_ATTR_PLUGGED, |
| !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); |
| output->base.stable_id_new = SuperFastHash(name, |
| strlen(name), |
| aio->base.info.stable_id_new |
| ); |
| 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) { |
| snprintf(node_name, sizeof(node_name), "%s: %s", |
| aio->base.info.name, ctl_name); |
| 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_attr(node, |
| IONODE_ATTR_PLUGGED, |
| 0); |
| } else { |
| DL_FOREACH(aio->base.nodes, tmp) |
| if (!strcmp(tmp->name, INTERNAL_MICROPHONE)) |
| cras_iodev_set_node_attr(tmp, |
| IONODE_ATTR_PLUGGED, |
| !plugged); |
| } |
| } |
| |
| static struct alsa_input_node *new_input(struct alsa_io *aio, |
| struct mixer_control *cras_input, const char *name) |
| { |
| struct alsa_input_node *input; |
| char *mic_positions; |
| 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); |
| input->base.stable_id_new = SuperFastHash(name, |
| strlen(name), |
| aio->base.info.stable_id_new); |
| 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_node_software_volume_needed(input, aio); |
| set_input_default_node_gain(input, aio); |
| |
| if (aio->ucm) { |
| /* Check mic positions only for internal mic. */ |
| if ((input->base.type == CRAS_NODE_TYPE_MIC) && |
| (input->base.position == NODE_POSITION_INTERNAL)) { |
| mic_positions = ucm_get_mic_positions(aio->ucm); |
| if (mic_positions) { |
| strncpy(input->base.mic_positions, |
| mic_positions, |
| sizeof(input->base.mic_positions) - 1); |
| free(mic_positions); |
| } |
| } |
| |
| /* 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; |
| } |
| } |
| |
| 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) { |
| snprintf(node_name , sizeof(node_name), "%s: %s", |
| aio->base.info.name, ctl_name); |
| 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; |
| } |
| |
| /* |
| * Returns the dsp name specified in the ucm config. If there is a dsp |
| * name specified for the jack of the active node, use that. Otherwise |
| * use the default dsp name for the alsa_io device. |
| */ |
| static const char *get_active_dsp_name(struct alsa_io *aio) |
| { |
| struct cras_ionode *node = aio->base.active_node; |
| const struct cras_alsa_jack *jack; |
| |
| if (node == NULL) |
| return NULL; |
| |
| if (aio->base.direction == CRAS_STREAM_OUTPUT) |
| jack = ((struct alsa_output_node *) node)->jack; |
| else |
| jack = ((struct alsa_input_node *) node)->jack; |
| |
| return cras_alsa_jack_get_dsp_name(jack) ? : aio->dsp_name_default; |
| } |
| |
| /* |
| * 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; |
| } |
| |
| /* |
| * 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)); |
| /* The name got from jack might be an invalid UTF8 string. */ |
| if (!is_utf8_string(node->base.name)) |
| drop_node_name(&node->base); |
| |
| cras_iodev_set_node_attr(&node->base, IONODE_ATTR_PLUGGED, plugged); |
| |
| check_auto_unplug_output_node(aio, &node->base, plugged); |
| } |
| |
| /* |
| * 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_attr(&node->base, IONODE_ATTR_PLUGGED, 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); |
| dev->info.stable_id_new = 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_new = |
| SuperFastHash(usb_serial_number, |
| strlen(usb_serial_number), |
| dev->info.stable_id); |
| break; |
| default: |
| dev->info.stable_id_new = dev->info.stable_id; |
| break; |
| } |
| syslog(LOG_DEBUG, "Stable ID=%08x, New Stable ID=%08x", |
| dev->info.stable_id, dev->info.stable_id_new); |
| } |
| |
| 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); |
| } |
| |
| /* |
| * 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; |
| |
| 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->dev, aio->alsa_stream, |
| &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; |
| } |
| } |
| 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, &aio->num_underruns); |
| |
| 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; |
| } |
| |
| static int adjust_appl_ptr(struct cras_iodev *odev) |
| { |
| struct alsa_io *aio = (struct alsa_io *)odev; |
| |
| /* Move appl_ptr to min_buffer_level + min_cb_level frames ahead of |
| * hw_ptr when resuming from free run or adjusting appl_ptr from |
| * underrun. */ |
| return cras_alsa_resume_appl_ptr( |
| aio->handle, |
| odev->min_buffer_level + odev->min_cb_level); |
| } |
| |
| 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(odev); |
| } |
| |
| static int possibly_enter_free_run(struct cras_iodev *odev) |
| { |
| struct alsa_io *aio = (struct alsa_io *)odev; |
| int rc; |
| unsigned int hw_level, fr_to_write; |
| unsigned int target_hw_level = odev->min_cb_level * 2; |
| struct timespec hw_tstamp; |
| |
| if (aio->is_free_running) |
| return 0; |
| |
| /* Check if all valid samples are played. |
| * If all valid samples are played, fill whole buffer with zeros. */ |
| rc = cras_iodev_frames_queued(odev, &hw_tstamp); |
| if (rc < 0) |
| return rc; |
| hw_level = rc; |
| |
| if (hw_level < aio->filled_zeros_for_draining || hw_level == 0) { |
| rc = fill_whole_buffer_with_zeros(odev); |
| if (rc < 0) |
| return rc; |
| aio->is_free_running = 1; |
| return 0; |
| } |
| |
| /* Fill some zeros to drain valid samples. */ |
| fr_to_write = cras_iodev_buffer_avail(odev, hw_level); |
| |
| if (hw_level <= target_hw_level) { |
| fr_to_write = MIN(target_hw_level - hw_level, fr_to_write); |
| 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; |
| |
| if (!aio->is_free_running) |
| return 0; |
| |
| rc = adjust_appl_ptr(odev); |
| if (rc) { |
| syslog(LOG_ERR, "device %s failed to leave free run, rc = %d", |
| odev->info.name, rc); |
| return rc; |
| } |
| aio->is_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 output_should_wake(const struct cras_iodev *odev) |
| { |
| struct alsa_io *aio = (struct alsa_io *)odev; |
| if (aio->is_free_running) |
| return 0; |
| else |
| return ((cras_iodev_state(odev) == |
| CRAS_IODEV_STATE_NO_STREAM_RUN) || |
| (cras_iodev_state(odev) == |
| CRAS_IODEV_STATE_NORMAL_RUN)); |
| } |
| |
| static unsigned int get_num_underruns(const struct cras_iodev *iodev) |
| { |
| const struct alsa_io *aio = (const struct alsa_io *)iodev; |
| return aio->num_underruns; |
| } |
| |
| 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_model = "en_us"; |
| cras_node_id_t node_id; |
| |
| 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 */ |
| cras_iodev_list_set_hotword_model(node_id, default_model); |
| } |
| |
| /* |
| * Exported Interface. |
| */ |
| |
| struct cras_iodev *alsa_iodev_create(size_t card_index, |
| const char *card_name, |
| size_t device_index, |
| 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->is_free_running = 0; |
| aio->filled_zeros_for_draining = 0; |
| aio->dev = (char *)malloc(MAX_ALSA_DEV_NAME_LENGTH); |
| if (aio->dev == NULL) |
| goto cleanup_iodev; |
| snprintf(aio->dev, |
| MAX_ALSA_DEV_NAME_LENGTH, |
| "hw:%zu,%zu", |
| card_index, |
| device_index); |
| |
| 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->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->output_should_wake = output_should_wake; |
| iodev->get_num_underruns = get_num_underruns; |
| iodev->get_num_severe_underruns = get_num_severe_underruns; |
| iodev->set_swap_mode_for_node = cras_iodev_dsp_set_swap_mode_for_node; |
| |
| 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; |
| |
| 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; |
| |
| aio->dsp_name_default = ucm_get_dsp_name_default(ucm, |
| direction); |
| /* 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; |
| |
| level = ucm_get_min_buffer_level(ucm); |
| if (level && direction == CRAS_STREAM_OUTPUT) |
| iodev->min_buffer_level = level; |
| |
| aio->enable_htimestamp = |
| ucm_get_enable_htimestamp_flag(ucm); |
| } |
| |
| 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 (aio->card_type == ALSA_CARD_TYPE_USB && is_first) |
| cras_iodev_set_node_attr(iodev->active_node, |
| IONODE_ATTR_PLUGGED, 1); |
| |
| set_default_hotword_model(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; |
| if ((uint32_t)section->dev_idx != aio->device_index) |
| return -EINVAL; |
| |
| /* 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; |
| } else if (iodev->direction == CRAS_STREAM_INPUT) { |
| input_node = new_input(aio, control, section->name); |
| if (!input_node) |
| return -ENOMEM; |
| } |
| |
| /* 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; |
| |
| 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 first USB device per card when it appears. */ |
| if (aio->card_type == ALSA_CARD_TYPE_USB && aio->is_first) |
| cras_iodev_set_node_attr(iodev->active_node, |
| IONODE_ATTR_PLUGGED, 1); |
| |
| set_default_hotword_model(iodev); |
| } |
| |
| void alsa_iodev_destroy(struct cras_iodev *iodev) |
| { |
| struct alsa_io *aio = (struct alsa_io *)iodev; |
| int rc; |
| |
| cras_alsa_jack_list_destroy(aio->jack_list); |
| 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. */ |
| 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) { |
| set_alsa_mute_control(aio, 1); |
| /* 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; |
| |
| if (iodev->active_node == ionode) { |
| enable_active_ucm(aio, dev_enabled); |
| init_device_settings(aio); |
| return 0; |
| } |
| |
| /* 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); |
| enable_active_ucm(aio, dev_enabled); |
| /* Setting the volume will also unmute if the system isn't muted. */ |
| init_device_settings(aio); |
| return 0; |
| } |