/* ALSAMixer.cpp
**
** Copyright 2008-2010 Wind River Systems
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
#include <errno.h>
#include <stdarg.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <dlfcn.h>
#define LOG_TAG "AudioHardwareALSA"
#include <utils/Log.h>
#include <utils/String8.h>
#include <cutils/properties.h>
#include <media/AudioRecord.h>
#include <hardware_legacy/power.h>
#include "AudioHardwareALSA.h"
#define SND_MIXER_VOL_RANGE_MIN (0)
#define SND_MIXER_VOL_RANGE_MAX (100)
#define ALSA_NAME_MAX 128
#define ALSA_STRCAT(x,y) \
if (strlen(x) + strlen(y) < ALSA_NAME_MAX) \
strcat(x, y);
namespace android
{
// ----------------------------------------------------------------------------
struct mixer_info_t;
struct alsa_properties_t
{
const AudioSystem::audio_devices device;
const char *propName;
const char *propDefault;
mixer_info_t *mInfo;
};
#define ALSA_PROP(dev, name, out, in) \
{\
{dev, "alsa.mixer.playback." name, out, NULL},\
{dev, "alsa.mixer.capture." name, in, NULL}\
}
static alsa_properties_t
mixerMasterProp[SND_PCM_STREAM_LAST+1] =
ALSA_PROP(AudioSystem::DEVICE_OUT_ALL, "master", "PCM", "Capture");
static alsa_properties_t
mixerProp[][SND_PCM_STREAM_LAST+1] = {
ALSA_PROP(AudioSystem::DEVICE_OUT_EARPIECE, "earpiece", "Earpiece", "Capture"),
ALSA_PROP(AudioSystem::DEVICE_OUT_SPEAKER, "speaker", "Speaker", ""),
ALSA_PROP(AudioSystem::DEVICE_OUT_WIRED_HEADSET, "headset", "Headphone", "Capture"),
ALSA_PROP(AudioSystem::DEVICE_OUT_BLUETOOTH_SCO, "bluetooth.sco", "Bluetooth", "Bluetooth Capture"),
ALSA_PROP(AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP, "bluetooth.a2dp", "Bluetooth A2DP", "Bluetooth A2DP Capture"),
ALSA_PROP(static_cast<AudioSystem::audio_devices>(0), "", NULL, NULL)
};
struct mixer_info_t
{
mixer_info_t() :
elem(0),
min(SND_MIXER_VOL_RANGE_MIN),
max(SND_MIXER_VOL_RANGE_MAX),
mute(false)
{
}
snd_mixer_elem_t *elem;
long min;
long max;
long volume;
bool mute;
char name[ALSA_NAME_MAX];
};
static int initMixer (snd_mixer_t **mixer, const char *name)
{
int err;
if ((err = snd_mixer_open(mixer, 0)) < 0) {
ALOGE("Unable to open mixer: %s", snd_strerror(err));
return err;
}
if ((err = snd_mixer_attach(*mixer, name)) < 0) {
ALOGW("Unable to attach mixer to device %s: %s",
name, snd_strerror(err));
if ((err = snd_mixer_attach(*mixer, "hw:00")) < 0) {
ALOGE("Unable to attach mixer to device default: %s",
snd_strerror(err));
snd_mixer_close (*mixer);
*mixer = NULL;
return err;
}
}
if ((err = snd_mixer_selem_register(*mixer, NULL, NULL)) < 0) {
ALOGE("Unable to register mixer elements: %s", snd_strerror(err));
snd_mixer_close (*mixer);
*mixer = NULL;
return err;
}
// Get the mixer controls from the kernel
if ((err = snd_mixer_load(*mixer)) < 0) {
ALOGE("Unable to load mixer elements: %s", snd_strerror(err));
snd_mixer_close (*mixer);
*mixer = NULL;
return err;
}
return 0;
}
typedef int (*hasVolume_t)(snd_mixer_elem_t*);
static const hasVolume_t hasVolume[] = {
snd_mixer_selem_has_playback_volume,
snd_mixer_selem_has_capture_volume
};
typedef int (*getVolumeRange_t)(snd_mixer_elem_t*, long int*, long int*);
static const getVolumeRange_t getVolumeRange[] = {
snd_mixer_selem_get_playback_volume_range,
snd_mixer_selem_get_capture_volume_range
};
typedef int (*setVolume_t)(snd_mixer_elem_t*, long int);
static const setVolume_t setVol[] = {
snd_mixer_selem_set_playback_volume_all,
snd_mixer_selem_set_capture_volume_all
};
ALSAMixer::ALSAMixer()
{
int err;
initMixer (&mMixer[SND_PCM_STREAM_PLAYBACK], "AndroidOut");
initMixer (&mMixer[SND_PCM_STREAM_CAPTURE], "AndroidIn");
snd_mixer_selem_id_t *sid;
snd_mixer_selem_id_alloca(&sid);
for (int i = 0; i <= SND_PCM_STREAM_LAST; i++) {
if (!mMixer[i]) continue;
mixer_info_t *info = mixerMasterProp[i].mInfo = new mixer_info_t;
property_get (mixerMasterProp[i].propName,
info->name,
mixerMasterProp[i].propDefault);
for (snd_mixer_elem_t *elem = snd_mixer_first_elem(mMixer[i]);
elem;
elem = snd_mixer_elem_next(elem)) {
if (!snd_mixer_selem_is_active(elem))
continue;
snd_mixer_selem_get_id(elem, sid);
// Find PCM playback volume control element.
const char *elementName = snd_mixer_selem_id_get_name(sid);
if (info->elem == NULL &&
strcmp(elementName, info->name) == 0 &&
hasVolume[i] (elem)) {
info->elem = elem;
getVolumeRange[i] (elem, &info->min, &info->max);
info->volume = info->max;
setVol[i] (elem, info->volume);
if (i == SND_PCM_STREAM_PLAYBACK &&
snd_mixer_selem_has_playback_switch (elem))
snd_mixer_selem_set_playback_switch_all (elem, 1);
break;
}
}
ALOGV("Mixer: master '%s' %s.", info->name, info->elem ? "found" : "not found");
for (int j = 0; mixerProp[j][i].device; j++) {
mixer_info_t *info = mixerProp[j][i].mInfo = new mixer_info_t;
property_get (mixerProp[j][i].propName,
info->name,
mixerProp[j][i].propDefault);
for (snd_mixer_elem_t *elem = snd_mixer_first_elem(mMixer[i]);
elem;
elem = snd_mixer_elem_next(elem)) {
if (!snd_mixer_selem_is_active(elem))
continue;
snd_mixer_selem_get_id(elem, sid);
// Find PCM playback volume control element.
const char *elementName = snd_mixer_selem_id_get_name(sid);
if (info->elem == NULL &&
strcmp(elementName, info->name) == 0 &&
hasVolume[i] (elem)) {
info->elem = elem;
getVolumeRange[i] (elem, &info->min, &info->max);
info->volume = info->max;
setVol[i] (elem, info->volume);
if (i == SND_PCM_STREAM_PLAYBACK &&
snd_mixer_selem_has_playback_switch (elem))
snd_mixer_selem_set_playback_switch_all (elem, 1);
break;
}
}
ALOGV("Mixer: route '%s' %s.", info->name, info->elem ? "found" : "not found");
}
}
ALOGV("mixer initialized.");
}
ALSAMixer::~ALSAMixer()
{
for (int i = 0; i <= SND_PCM_STREAM_LAST; i++) {
if (mMixer[i]) snd_mixer_close (mMixer[i]);
if (mixerMasterProp[i].mInfo) {
delete mixerMasterProp[i].mInfo;
mixerMasterProp[i].mInfo = NULL;
}
for (int j = 0; mixerProp[j][i].device; j++) {
if (mixerProp[j][i].mInfo) {
delete mixerProp[j][i].mInfo;
mixerProp[j][i].mInfo = NULL;
}
}
}
ALOGV("mixer destroyed.");
}
status_t ALSAMixer::setMasterVolume(float volume)
{
mixer_info_t *info = mixerMasterProp[SND_PCM_STREAM_PLAYBACK].mInfo;
if (!info || !info->elem) return INVALID_OPERATION;
long minVol = info->min;
long maxVol = info->max;
// Make sure volume is between bounds.
long vol = minVol + volume * (maxVol - minVol);
if (vol > maxVol) vol = maxVol;
if (vol < minVol) vol = minVol;
info->volume = vol;
snd_mixer_selem_set_playback_volume_all (info->elem, vol);
return NO_ERROR;
}
status_t ALSAMixer::setMasterGain(float gain)
{
mixer_info_t *info = mixerMasterProp[SND_PCM_STREAM_CAPTURE].mInfo;
if (!info || !info->elem) return INVALID_OPERATION;
long minVol = info->min;
long maxVol = info->max;
// Make sure volume is between bounds.
long vol = minVol + gain * (maxVol - minVol);
if (vol > maxVol) vol = maxVol;
if (vol < minVol) vol = minVol;
info->volume = vol;
snd_mixer_selem_set_capture_volume_all (info->elem, vol);
return NO_ERROR;
}
status_t ALSAMixer::setVolume(uint32_t device, float left, float right)
{
for (int j = 0; mixerProp[j][SND_PCM_STREAM_PLAYBACK].device; j++)
if (mixerProp[j][SND_PCM_STREAM_PLAYBACK].device & device) {
mixer_info_t *info = mixerProp[j][SND_PCM_STREAM_PLAYBACK].mInfo;
if (!info || !info->elem) return INVALID_OPERATION;
long minVol = info->min;
long maxVol = info->max;
// Make sure volume is between bounds.
long vol = minVol + left * (maxVol - minVol);
if (vol > maxVol) vol = maxVol;
if (vol < minVol) vol = minVol;
info->volume = vol;
snd_mixer_selem_set_playback_volume_all (info->elem, vol);
}
return NO_ERROR;
}
status_t ALSAMixer::setGain(uint32_t device, float gain)
{
for (int j = 0; mixerProp[j][SND_PCM_STREAM_CAPTURE].device; j++)
if (mixerProp[j][SND_PCM_STREAM_CAPTURE].device & device) {
mixer_info_t *info = mixerProp[j][SND_PCM_STREAM_CAPTURE].mInfo;
if (!info || !info->elem) return INVALID_OPERATION;
long minVol = info->min;
long maxVol = info->max;
// Make sure volume is between bounds.
long vol = minVol + gain * (maxVol - minVol);
if (vol > maxVol) vol = maxVol;
if (vol < minVol) vol = minVol;
info->volume = vol;
snd_mixer_selem_set_capture_volume_all (info->elem, vol);
}
return NO_ERROR;
}
status_t ALSAMixer::setCaptureMuteState(uint32_t device, bool state)
{
for (int j = 0; mixerProp[j][SND_PCM_STREAM_CAPTURE].device; j++)
if (mixerProp[j][SND_PCM_STREAM_CAPTURE].device & device) {
mixer_info_t *info = mixerProp[j][SND_PCM_STREAM_CAPTURE].mInfo;
if (!info || !info->elem) return INVALID_OPERATION;
if (snd_mixer_selem_has_capture_switch (info->elem)) {
int err = snd_mixer_selem_set_capture_switch_all (info->elem, static_cast<int>(!state));
if (err < 0) {
ALOGE("Unable to %s capture mixer switch %s",
state ? "enable" : "disable", info->name);
return INVALID_OPERATION;
}
}
info->mute = state;
}
return NO_ERROR;
}
status_t ALSAMixer::getCaptureMuteState(uint32_t device, bool *state)
{
if (!state) return BAD_VALUE;
for (int j = 0; mixerProp[j][SND_PCM_STREAM_CAPTURE].device; j++)
if (mixerProp[j][SND_PCM_STREAM_CAPTURE].device & device) {
mixer_info_t *info = mixerProp[j][SND_PCM_STREAM_CAPTURE].mInfo;
if (!info || !info->elem) return INVALID_OPERATION;
*state = info->mute;
return NO_ERROR;
}
return BAD_VALUE;
}
status_t ALSAMixer::setPlaybackMuteState(uint32_t device, bool state)
{
for (int j = 0; mixerProp[j][SND_PCM_STREAM_PLAYBACK].device; j++)
if (mixerProp[j][SND_PCM_STREAM_PLAYBACK].device & device) {
mixer_info_t *info = mixerProp[j][SND_PCM_STREAM_PLAYBACK].mInfo;
if (!info || !info->elem) return INVALID_OPERATION;
if (snd_mixer_selem_has_playback_switch (info->elem)) {
int err = snd_mixer_selem_set_playback_switch_all (info->elem, static_cast<int>(!state));
if (err < 0) {
ALOGE("Unable to %s playback mixer switch %s",
state ? "enable" : "disable", info->name);
return INVALID_OPERATION;
}
}
info->mute = state;
}
return NO_ERROR;
}
status_t ALSAMixer::getPlaybackMuteState(uint32_t device, bool *state)
{
if (!state) return BAD_VALUE;
for (int j = 0; mixerProp[j][SND_PCM_STREAM_PLAYBACK].device; j++)
if (mixerProp[j][SND_PCM_STREAM_PLAYBACK].device & device) {
mixer_info_t *info = mixerProp[j][SND_PCM_STREAM_PLAYBACK].mInfo;
if (!info || !info->elem) return INVALID_OPERATION;
*state = info->mute;
return NO_ERROR;
}
return BAD_VALUE;
}
}; // namespace android