You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
433 lines
13 KiB
433 lines
13 KiB
/* 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
|
|
|