blob: ff4b323802e263de507e44f2d0adb4c6c347abac [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 <alsa/use-case.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_dbus_util.h"
#include "cras_iodev.h"
#include "cras_iodev_list.h"
#include "cras_messages.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 AOKR_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
/* This extends cras_ionode to include alsa-specific information.
* Members:
* mixer_output - From cras_alsa_mixer.
* jack_curve - In absense of a mixer output, holds a volume curve to use
* when this jack is plugged.
* jack - The jack associated with the jack_curve (if it exists).
*/
struct alsa_output_node {
struct cras_ionode base;
struct mixer_control *mixer_output;
struct cras_volume_curve *jack_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;
};
/* 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").
* 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.
* handle - Handle to the opened ALSA device.
* num_underruns - Number of times we have run out of data (playback only).
* alsa_stream - Playback or capture type.
* mixer - Alsa mixer used to control volume and mute of the device.
* jack_list - List of alsa jack controls for this device.
* ucm - ALSA 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.
*/
struct alsa_io {
struct cras_iodev base;
char *dev;
uint32_t device_index;
uint32_t next_ionode_index;
enum CRAS_ALSA_CARD_TYPE card_type;
int is_first;
snd_pcm_t *handle;
unsigned int num_underruns;
snd_pcm_stream_t alsa_stream;
struct cras_alsa_mixer *mixer;
struct cras_alsa_jack_list *jack_list;
snd_use_case_mgr_t *ucm;
snd_pcm_uframes_t mmap_offset;
const char *dsp_name_default;
int poll_fd;
};
static void init_device_settings(struct alsa_io *aio);
/*
* iodev callbacks.
*/
static int frames_queued(const struct cras_iodev *iodev)
{
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,
&frames);
if (rc < 0)
return rc;
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;
if (aio->poll_fd >= 0)
audio_thread_rm_callback(aio->poll_fd);
if (!aio->handle)
return 0;
cras_alsa_pcm_close(aio->handle);
aio->handle = NULL;
cras_iodev_free_format(&aio->base);
cras_iodev_free_audio_area(&aio->base);
return 0;
}
static int dummy_aokr_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 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;
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;
rc = cras_alsa_set_hwparams(handle, iodev->format,
&iodev->buffer_size);
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);
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_AOKR) {
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_aokr_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;
}
static int is_open(const struct cras_iodev *iodev)
{
struct alsa_io *aio = (struct alsa_io *)iodev;
return !!aio->handle;
}
static int dev_running(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 1;
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 0;
}
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 0;
}
}
return 1;
}
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)
{
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);
return;
}
}
alsa_iodev_set_active_node(iodev, first_plugged_node(iodev));
}
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;
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);
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;
}
/*
* 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. */
static const struct cras_volume_curve *get_curve_for_output_node(
const struct alsa_io *aio,
const struct alsa_output_node *aout)
{
struct cras_volume_curve *curve = NULL;
if (aout) {
curve = cras_alsa_mixer_get_output_volume_curve(
aout->mixer_output);
if (curve)
return curve;
else if (aout->jack_curve)
return aout->jack_curve;
}
return cras_alsa_mixer_default_volume_curve(aio->mixer);
}
/* 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 *aout = get_active_output(aio);
return get_curve_for_output_node(aio, aout);
}
/* 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 (!is_open(&aio->base))
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 state for this iodev. */
static void set_alsa_mute(const struct alsa_io *aio, int muted)
{
struct alsa_output_node *aout;
if (!is_open(&aio->base))
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. Handles mute and
* unmute, including muting when volume is zero. */
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;
int mute;
struct alsa_output_node *aout;
assert(aio);
if (aio->mixer == NULL)
return;
/* Only set the volume if the dev is active. */
if (!is_open(&aio->base))
return;
volume = cras_system_get_volume();
mute = cras_system_get_mute();
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);
/* Mute for zero. */
set_alsa_mute(aio, mute || (volume == 0));
}
/* 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 and 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 (!is_open(&aio->base))
return;
gain = cras_system_get_capture_gain();
ain = get_active_input(aio);
if (ain)
gain += ain->base.capture_gain;
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 and registers for callbacks when system
* settings have been changed.
*/
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);
} else {
struct mixer_control *mixer_input = NULL;
struct alsa_input_node *ain = get_active_input(aio);
if (ain)
mixer_input = ain->mixer_input;
cras_system_set_capture_gain_limits(
cras_alsa_mixer_get_minimum_capture_gain(aio->mixer,
mixer_input),
cras_alsa_mixer_get_maximum_capture_gain(aio->mixer,
mixer_input));
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->jack_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);
}
/* 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)
{
static const struct {
const char *name;
int initial_plugged;
enum CRAS_NODE_TYPE type;
} node_defaults[] = {
{ DEFAULT, 1, CRAS_NODE_TYPE_UNKNOWN},
{ INTERNAL_SPEAKER, 1, CRAS_NODE_TYPE_INTERNAL_SPEAKER },
{ INTERNAL_MICROPHONE, 1, CRAS_NODE_TYPE_INTERNAL_MIC },
{ KEYBOARD_MIC, 1, CRAS_NODE_TYPE_KEYBOARD_MIC },
{ HDMI, 0, CRAS_NODE_TYPE_HDMI },
{ "IEC958", 0, CRAS_NODE_TYPE_HDMI },
{ "Headphone", 0, CRAS_NODE_TYPE_HEADPHONE },
{ "Front Headphone", 0, CRAS_NODE_TYPE_HEADPHONE },
{ "Mic", 0, CRAS_NODE_TYPE_MIC },
{ AOKR_DEV, 1, CRAS_NODE_TYPE_AOKR },
};
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->plugged = node_defaults[i].initial_plugged;
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;
if (!is_utf8_string(node->name))
drop_node_name(node);
}
static const char *get_output_node_name(struct alsa_io *aio,
struct mixer_control *cras_output)
{
if (cras_output)
return cras_alsa_mixer_get_control_name(cras_output);
if (first_internal_device(aio) && !has_node(aio, INTERNAL_SPEAKER)) {
if (strstr(aio->base.info.name, HDMI))
return HDMI;
return INTERNAL_SPEAKER;
} else {
return DEFAULT;
}
}
static const char *get_input_node_name(struct alsa_io *aio,
struct mixer_control *cras_input)
{
if (cras_input)
return cras_alsa_mixer_get_control_name(cras_input);
if (first_internal_device(aio) && !has_node(aio, INTERNAL_MICROPHONE))
return INTERNAL_MICROPHONE;
else
return DEFAULT;
}
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 */
if (output->base.type == CRAS_NODE_TYPE_HDMI)
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);
}
/* 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 void new_output(struct mixer_control *cras_output,
void *callback_arg)
{
struct alsa_io *aio;
struct alsa_output_node *output;
const char *name;
aio = (struct alsa_io *)callback_arg;
if (aio == NULL) {
syslog(LOG_ERR, "Invalid aio when listing outputs.");
return;
}
output = (struct alsa_output_node *)calloc(1, sizeof(*output));
if (output == NULL) {
syslog(LOG_ERR, "Out of memory when listing outputs.");
return;
}
output->base.dev = &aio->base;
output->base.idx = aio->next_ionode_index++;
output->mixer_output = cras_output;
name = get_output_node_name(aio, 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);
/* Auto unplug internal speaker if any output node has been created */
if (auto_unplug_output_node(aio) && !strcmp(name, INTERNAL_SPEAKER)) {
struct cras_ionode *tmp;
DL_FOREACH(aio->base.nodes, tmp)
if (tmp->plugged)
output->base.plugged = 0;
}
cras_iodev_add_node(&aio->base, &output->base);
}
static void _new_input(struct mixer_control *cras_input,
const char *name,
struct alsa_io *aio)
{
struct alsa_input_node *input;
char *mic_positions;
input = (struct alsa_input_node *)calloc(1, sizeof(*input));
if (input == NULL) {
syslog(LOG_ERR, "Out of memory when listing inputs.");
return;
}
input->base.dev = &aio->base;
input->base.idx = aio->next_ionode_index++;
input->mixer_input = cras_input;
strncpy(input->base.name, name, sizeof(input->base.name) - 1);
set_node_initial_state(&input->base, aio->card_type);
/* Check mic positions only for internal mic. */
if (aio->ucm && input->base.type == CRAS_NODE_TYPE_INTERNAL_MIC) {
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);
}
}
/* Auto unplug internal mic if any input node has already
* been created */
if (auto_unplug_input_node(aio) && !strcmp(name, INTERNAL_MICROPHONE)) {
struct cras_ionode *tmp;
DL_FOREACH(aio->base.nodes, tmp)
if (tmp->plugged)
input->base.plugged = 0;
}
cras_iodev_add_node(&aio->base, &input->base);
}
static void new_input(struct mixer_control *cras_input,
void *callback_arg)
{
struct alsa_io *aio;
const char* name;
aio = (struct alsa_io *)callback_arg;
name = get_input_node_name(aio, cras_input);
_new_input(cras_input, name, aio);
}
static void new_input_by_name(const char *name, struct alsa_io *aio)
{
_new_input(NULL, name, aio);
}
/* 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;
mixer_output = cras_alsa_jack_get_mixer_output(jack);
if (mixer_output == NULL) {
/* no mixer output, search by node. */
DL_SEARCH_SCALAR_WITH_CAST(aio->base.nodes, node, aout,
jack, jack);
return aout;
}
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;
}
/* 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);
/* If there isn't a node for this jack, create one. */
if (node == NULL) {
node = (struct alsa_output_node *)calloc(1, sizeof(*node));
if (node == NULL) {
syslog(LOG_ERR, "Out of memory creating jack node.");
return;
}
node->base.dev = &aio->base;
node->base.idx = aio->next_ionode_index++;
jack_name = cras_alsa_jack_get_name(jack);
node->jack_curve = cras_alsa_mixer_create_volume_curve_for_name(
aio->mixer, jack_name);
node->jack = jack;
/* Speaker phantom jack is actually for internal speaker. */
if (!strcmp(jack_name, "Speaker Phantom Jack"))
jack_name = INTERNAL_SPEAKER;
strncpy(node->base.name, jack_name,
sizeof(node->base.name) - 1);
set_node_initial_state(&node->base, aio->card_type);
set_output_node_software_volume_needed(node, aio);
cras_alsa_jack_update_node_type(jack, &(node->base.type));
cras_iodev_add_node(&aio->base, &node->base);
} else if (!node->jack) {
/* If we already have the node, associate with the jack. */
jack_name = cras_alsa_jack_get_name(jack);
node->jack_curve = cras_alsa_mixer_create_volume_curve_for_name(
aio->mixer, jack_name);
node->jack = jack;
}
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);
if (auto_unplug_output_node(aio)) {
struct cras_ionode *tmp;
DL_FOREACH(aio->base.nodes, tmp) {
if (!strcmp(tmp->name, INTERNAL_SPEAKER))
cras_iodev_set_node_attr(tmp,
IONODE_ATTR_PLUGGED,
!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;
const char *jack_name;
if (arg == NULL)
return;
aio = (struct alsa_io *)arg;
node = get_input_node_from_jack(aio, jack);
/* If there isn't a node for this jack, create one. */
if (node == NULL) {
node = (struct alsa_input_node *)calloc(1, sizeof(*node));
if (node == NULL) {
syslog(LOG_ERR, "Out of memory creating jack node.");
return;
}
node->base.dev = &aio->base;
node->base.idx = aio->next_ionode_index++;
jack_name = cras_alsa_jack_get_name(jack);
node->jack = jack;
node->mixer_input = cras_alsa_jack_get_mixer_input(jack);
strncpy(node->base.name, jack_name,
sizeof(node->base.name) - 1);
set_node_initial_state(&node->base, aio->card_type);
cras_iodev_add_node(&aio->base, &node->base);
} else if (!node->jack) {
/* If we already have the node, associate with the jack. */
node->jack = jack;
}
cras_iodev_set_node_attr(&node->base, IONODE_ATTR_PLUGGED, plugged);
if (auto_unplug_input_node(aio)) {
struct cras_ionode *tmp;
DL_FOREACH(aio->base.nodes, tmp)
if (!strcmp(tmp->name, INTERNAL_MICROPHONE))
cras_iodev_set_node_attr(tmp,
IONODE_ATTR_PLUGGED,
!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)
{
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(dev->info.name,
strlen(dev->info.name),
strlen(dev->info.name));
switch (card_type) {
case ALSA_CARD_TYPE_INTERNAL:
dev->info.stable_id = SuperFastHash((const char *)&card_index,
sizeof(card_index),
dev->info.stable_id);
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);
break;
}
syslog(LOG_DEBUG, "Stable ID=%08x", dev->info.stable_id);
}
/* 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;
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);
return err;
}
/* 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);
}
}
/*
* 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,
snd_use_case_mgr_t *ucm,
enum CRAS_STREAM_DIRECTION direction,
size_t usb_vid,
size_t usb_pid)
{
struct alsa_io *aio;
struct cras_iodev *iodev;
int err;
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->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_volume;
}
iodev->open_dev = open_dev;
iodev->close_dev = close_dev;
iodev->is_open = is_open;
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->dev_running = dev_running;
iodev->update_active_node = update_active_node;
iodev->update_channel_layout = update_channel_layout;
if (card_type == ALSA_CARD_TYPE_USB)
iodev->min_buffer_level = USB_EXTRA_BUFFER_FRAMES;
err = cras_alsa_fill_properties(aio->dev, aio->alsa_stream,
&iodev->supported_rates,
&iodev->supported_channel_counts,
&iodev->supported_formats);
if (err < 0 || iodev->supported_rates[0] == 0 ||
iodev->supported_channel_counts[0] == 0 ||
iodev->supported_formats[0] == 0) {
syslog(LOG_ERR, "cras_alsa_fill_properties: %s", strerror(err));
goto cleanup_iodev;
}
aio->mixer = mixer;
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;
}
set_iodev_name(iodev, card_name, dev_name, card_index, device_index,
card_type, usb_vid, usb_pid);
/* 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, aio);
else if (direction == CRAS_STREAM_INPUT && is_first)
cras_alsa_mixer_list_inputs(mixer, new_input, aio);
/* Find any jack controls for this device. */
aio->jack_list = cras_alsa_jack_list_create(
card_index,
card_name,
device_index,
is_first,
mixer,
ucm,
direction,
direction == CRAS_STREAM_OUTPUT ?
jack_output_plug_event :
jack_input_plug_event,
aio);
/* 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 (!aio->base.nodes || (first_internal_device(aio) &&
!has_node(aio, INTERNAL_SPEAKER) &&
!has_node(aio, HDMI)))
new_output(NULL, aio);
} else if ((direction == CRAS_STREAM_INPUT) &&
!no_create_default_input_node(aio)) {
if (first_internal_device(aio) &&
!has_node(aio, INTERNAL_MICROPHONE))
new_input_by_name(INTERNAL_MICROPHONE, aio);
else if (strstr(dev_name, KEYBOARD_MIC))
new_input_by_name(KEYBOARD_MIC, aio);
else if (dev_id && strstr(dev_id, AOKR_DEV))
new_input_by_name(AOKR_DEV, aio);
else if (!aio->base.nodes)
new_input_by_name(DEFAULT, aio);
}
/* 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;
/* 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));
if (direction == CRAS_STREAM_OUTPUT)
cras_iodev_list_add_output(&aio->base);
else
cras_iodev_list_add_input(&aio->base);
/* Set plugged for the first USB device per card when it appears. */
if (card_type == ALSA_CARD_TYPE_USB && is_first)
cras_iodev_set_node_attr(iodev->active_node,
IONODE_ATTR_PLUGGED, 1);
return &aio->base;
cleanup_iodev:
free_alsa_iodev_resources(aio);
free(aio);
return NULL;
}
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);
free(iodev);
}
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(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 void enable_jack_ucm(struct alsa_io *aio, int plugged)
{
if (aio->base.direction == CRAS_STREAM_OUTPUT) {
struct alsa_output_node *active = get_active_output(aio);
if (active)
cras_alsa_jack_enable_ucm(active->jack, plugged);
} else {
struct alsa_input_node *active = get_active_input(aio);
if (active)
cras_alsa_jack_enable_ucm(active->jack, plugged);
}
}
int alsa_iodev_set_active_node(struct cras_iodev *iodev,
struct cras_ionode *ionode)
{
struct alsa_io *aio = (struct alsa_io *)iodev;
if (iodev->active_node == ionode)
return 0;
enable_jack_ucm(aio, 0);
if (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_jack_ucm(aio, 1);
/* Setting the volume will also unmute if the system isn't muted. */
init_device_settings(aio);
return 0;
}