blob: 275a6810026e09fb8b2f1ffa5a6d7f9a6640e1bd [file] [log] [blame]
/* 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;
}