android_device_samsung_c1-c.../libaudio/alsa_default.cpp
2011-06-12 00:07:25 +02:00

550 lines
17 KiB
C++

/* alsa_default.cpp
**
** Copyright 2009 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.
*/
#define LOG_TAG "ALSAModule"
#include <utils/Log.h>
#include "AudioHardwareALSA.h"
#include <media/AudioRecord.h>
#undef DISABLE_HARWARE_RESAMPLING
#define ALSA_NAME_MAX 128
#define ALSA_STRCAT(x,y) \
if (strlen(x) + strlen(y) < ALSA_NAME_MAX) \
strcat(x, y);
#ifndef ALSA_DEFAULT_SAMPLE_RATE
#define ALSA_DEFAULT_SAMPLE_RATE 44100 // in Hz
#endif
namespace android
{
static int s_device_open(const hw_module_t*, const char*, hw_device_t**);
static int s_device_close(hw_device_t*);
static status_t s_init(alsa_device_t *, ALSAHandleList &);
static status_t s_open(alsa_handle_t *, uint32_t, int);
static status_t s_close(alsa_handle_t *);
static status_t s_standby(alsa_handle_t *);
static status_t s_route(alsa_handle_t *, uint32_t, int);
static hw_module_methods_t s_module_methods = {
open : s_device_open
};
extern "C" const hw_module_t HAL_MODULE_INFO_SYM = {
tag : HARDWARE_MODULE_TAG,
version_major : 1,
version_minor : 0,
id : ALSA_HARDWARE_MODULE_ID,
name : "ALSA module",
author : "Wind River",
methods : &s_module_methods,
dso : 0,
reserved : { 0, },
};
static int s_device_open(const hw_module_t* module, const char* name,
hw_device_t** device)
{
alsa_device_t *dev;
dev = (alsa_device_t *) malloc(sizeof(*dev));
if (!dev) return -ENOMEM;
memset(dev, 0, sizeof(*dev));
/* initialize the procs */
dev->common.tag = HARDWARE_DEVICE_TAG;
dev->common.version = 0;
dev->common.module = (hw_module_t *) module;
dev->common.close = s_device_close;
dev->init = s_init;
dev->open = s_open;
dev->close = s_close;
dev->standby = s_standby;
dev->route = s_route;
*device = &dev->common;
return 0;
}
static int s_device_close(hw_device_t* device)
{
free(device);
return 0;
}
// ----------------------------------------------------------------------------
static const int DEFAULT_SAMPLE_RATE = ALSA_DEFAULT_SAMPLE_RATE;
static const char *devicePrefix[SND_PCM_STREAM_LAST + 1] = {
/* SND_PCM_STREAM_PLAYBACK : */"AndroidPlayback",
/* SND_PCM_STREAM_CAPTURE : */"AndroidCapture",
};
static alsa_handle_t _defaultsOut = {
module : 0,
devices : AudioSystem::DEVICE_OUT_ALL,
curDev : 0,
curMode : 0,
handle : 0,
format : SND_PCM_FORMAT_S16_LE, // AudioSystem::PCM_16_BIT
channels : 2,
sampleRate : DEFAULT_SAMPLE_RATE,
latency : 200000, // Desired Delay in usec
bufferSize : DEFAULT_SAMPLE_RATE / 5, // Desired Number of samples
mmap : 0,
modPrivate : 0,
};
static alsa_handle_t _defaultsIn = {
module : 0,
devices : AudioSystem::DEVICE_IN_ALL,
curDev : 0,
curMode : 0,
handle : 0,
format : SND_PCM_FORMAT_S16_LE, // AudioSystem::PCM_16_BIT
channels : 1,
sampleRate : AudioRecord::DEFAULT_SAMPLE_RATE,
latency : 250000, // Desired Delay in usec
bufferSize : 2048, // Desired Number of samples
mmap : 0,
modPrivate : 0,
};
struct device_suffix_t {
const AudioSystem::audio_devices device;
const char *suffix;
};
/* The following table(s) need to match in order of the route bits
*/
static const device_suffix_t deviceSuffix[] = {
{AudioSystem::DEVICE_OUT_EARPIECE, "_Earpiece"},
{AudioSystem::DEVICE_OUT_SPEAKER, "_Speaker"},
{AudioSystem::DEVICE_OUT_BLUETOOTH_SCO, "_Bluetooth"},
{AudioSystem::DEVICE_OUT_WIRED_HEADSET, "_Headset"},
{AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP, "_Bluetooth-A2DP"},
};
static const int deviceSuffixLen = (sizeof(deviceSuffix)
/ sizeof(device_suffix_t));
// ----------------------------------------------------------------------------
snd_pcm_stream_t direction(alsa_handle_t *handle)
{
return (handle->devices & AudioSystem::DEVICE_OUT_ALL) ? SND_PCM_STREAM_PLAYBACK
: SND_PCM_STREAM_CAPTURE;
}
const char *deviceName(alsa_handle_t *handle, uint32_t device, int mode)
{
static char devString[ALSA_NAME_MAX];
int hasDevExt = 0;
strcpy(devString, devicePrefix[direction(handle)]);
for (int dev = 0; device && dev < deviceSuffixLen; dev++)
if (device & deviceSuffix[dev].device) {
ALSA_STRCAT (devString, deviceSuffix[dev].suffix);
device &= ~deviceSuffix[dev].device;
hasDevExt = 1;
}
if (hasDevExt) switch (mode) {
case AudioSystem::MODE_NORMAL:
ALSA_STRCAT (devString, "_normal")
;
break;
case AudioSystem::MODE_RINGTONE:
ALSA_STRCAT (devString, "_ringtone")
;
break;
case AudioSystem::MODE_IN_CALL:
ALSA_STRCAT (devString, "_incall")
;
break;
};
return devString;
}
const char *streamName(alsa_handle_t *handle)
{
return snd_pcm_stream_name(direction(handle));
}
status_t setHardwareParams(alsa_handle_t *handle)
{
snd_pcm_hw_params_t *hardwareParams;
status_t err;
snd_pcm_uframes_t bufferSize = handle->bufferSize;
unsigned int requestedRate = handle->sampleRate;
unsigned int latency = handle->latency;
// snd_pcm_format_description() and snd_pcm_format_name() do not perform
// proper bounds checking.
bool validFormat = (static_cast<int> (handle->format)
> SND_PCM_FORMAT_UNKNOWN) && (static_cast<int> (handle->format)
<= SND_PCM_FORMAT_LAST);
const char *formatDesc = validFormat ? snd_pcm_format_description(
handle->format) : "Invalid Format";
const char *formatName = validFormat ? snd_pcm_format_name(handle->format)
: "UNKNOWN";
if (snd_pcm_hw_params_malloc(&hardwareParams) < 0) {
LOG_ALWAYS_FATAL("Failed to allocate ALSA hardware parameters!");
return NO_INIT;
}
err = snd_pcm_hw_params_any(handle->handle, hardwareParams);
if (err < 0) {
LOGE("Unable to configure hardware: %s", snd_strerror(err));
goto done;
}
// Set the interleaved read and write format.
err = snd_pcm_hw_params_set_access(handle->handle, hardwareParams,
SND_PCM_ACCESS_RW_INTERLEAVED);
if (err < 0) {
LOGE("Unable to configure PCM read/write format: %s",
snd_strerror(err));
goto done;
}
err = snd_pcm_hw_params_set_format(handle->handle, hardwareParams,
handle->format);
if (err < 0) {
LOGE("Unable to configure PCM format %s (%s): %s",
formatName, formatDesc, snd_strerror(err));
goto done;
}
LOGV("Set %s PCM format to %s (%s)", streamName(), formatName, formatDesc);
err = snd_pcm_hw_params_set_channels(handle->handle, hardwareParams,
handle->channels);
if (err < 0) {
LOGE("Unable to set channel count to %i: %s",
handle->channels, snd_strerror(err));
goto done;
}
LOGV("Using %i %s for %s.", handle->channels,
handle->channels == 1 ? "channel" : "channels", streamName());
err = snd_pcm_hw_params_set_rate_near(handle->handle, hardwareParams,
&requestedRate, 0);
if (err < 0)
LOGE("Unable to set %s sample rate to %u: %s",
streamName(handle), handle->sampleRate, snd_strerror(err));
else if (requestedRate != handle->sampleRate)
// Some devices have a fixed sample rate, and can not be changed.
// This may cause resampling problems; i.e. PCM playback will be too
// slow or fast.
LOGW("Requested rate (%u HZ) does not match actual rate (%u HZ)",
handle->sampleRate, requestedRate);
else
LOGV("Set %s sample rate to %u HZ", stream, requestedRate);
#ifdef DISABLE_HARWARE_RESAMPLING
// Disable hardware re-sampling.
err = snd_pcm_hw_params_set_rate_resample(handle->handle,
hardwareParams,
static_cast<int>(resample));
if (err < 0) {
LOGE("Unable to %s hardware resampling: %s",
resample ? "enable" : "disable",
snd_strerror(err));
goto done;
}
#endif
// Make sure we have at least the size we originally wanted
err = snd_pcm_hw_params_set_buffer_size_near(handle->handle, hardwareParams,
&bufferSize);
if (err < 0) {
LOGE("Unable to set buffer size to %d: %s",
(int)bufferSize, snd_strerror(err));
goto done;
}
// Setup buffers for latency
err = snd_pcm_hw_params_set_buffer_time_near(handle->handle,
hardwareParams, &latency, NULL);
if (err < 0) {
/* That didn't work, set the period instead */
unsigned int periodTime = latency / 4;
err = snd_pcm_hw_params_set_period_time_near(handle->handle,
hardwareParams, &periodTime, NULL);
if (err < 0) {
LOGE("Unable to set the period time for latency: %s", snd_strerror(err));
goto done;
}
snd_pcm_uframes_t periodSize;
err = snd_pcm_hw_params_get_period_size(hardwareParams, &periodSize,
NULL);
if (err < 0) {
LOGE("Unable to get the period size for latency: %s", snd_strerror(err));
goto done;
}
bufferSize = periodSize * 4;
if (bufferSize < handle->bufferSize) bufferSize = handle->bufferSize;
err = snd_pcm_hw_params_set_buffer_size_near(handle->handle,
hardwareParams, &bufferSize);
if (err < 0) {
LOGE("Unable to set the buffer size for latency: %s", snd_strerror(err));
goto done;
}
} else {
// OK, we got buffer time near what we expect. See what that did for bufferSize.
err = snd_pcm_hw_params_get_buffer_size(hardwareParams, &bufferSize);
if (err < 0) {
LOGE("Unable to get the buffer size for latency: %s", snd_strerror(err));
goto done;
}
// Does set_buffer_time_near change the passed value? It should.
err = snd_pcm_hw_params_get_buffer_time(hardwareParams, &latency, NULL);
if (err < 0) {
LOGE("Unable to get the buffer time for latency: %s", snd_strerror(err));
goto done;
}
unsigned int periodTime = latency / 4;
err = snd_pcm_hw_params_set_period_time_near(handle->handle,
hardwareParams, &periodTime, NULL);
if (err < 0) {
LOGE("Unable to set the period time for latency: %s", snd_strerror(err));
goto done;
}
}
LOGV("Buffer size: %d", (int)bufferSize);
LOGV("Latency: %d", (int)latency);
handle->bufferSize = bufferSize;
handle->latency = latency;
// Commit the hardware parameters back to the device.
err = snd_pcm_hw_params(handle->handle, hardwareParams);
if (err < 0) LOGE("Unable to set hardware parameters: %s", snd_strerror(err));
done:
snd_pcm_hw_params_free(hardwareParams);
return err;
}
status_t setSoftwareParams(alsa_handle_t *handle)
{
snd_pcm_sw_params_t * softwareParams;
int err;
snd_pcm_uframes_t bufferSize = 0;
snd_pcm_uframes_t periodSize = 0;
snd_pcm_uframes_t startThreshold, stopThreshold;
if (snd_pcm_sw_params_malloc(&softwareParams) < 0) {
LOG_ALWAYS_FATAL("Failed to allocate ALSA software parameters!");
return NO_INIT;
}
// Get the current software parameters
err = snd_pcm_sw_params_current(handle->handle, softwareParams);
if (err < 0) {
LOGE("Unable to get software parameters: %s", snd_strerror(err));
goto done;
}
// Configure ALSA to start the transfer when the buffer is almost full.
snd_pcm_get_params(handle->handle, &bufferSize, &periodSize);
if (handle->devices & AudioSystem::DEVICE_OUT_ALL) {
// For playback, configure ALSA to start the transfer when the
// buffer is full.
startThreshold = bufferSize - 1;
stopThreshold = bufferSize;
} else {
// For recording, configure ALSA to start the transfer on the
// first frame.
startThreshold = 1;
stopThreshold = bufferSize;
}
err = snd_pcm_sw_params_set_start_threshold(handle->handle, softwareParams,
startThreshold);
if (err < 0) {
LOGE("Unable to set start threshold to %lu frames: %s",
startThreshold, snd_strerror(err));
goto done;
}
err = snd_pcm_sw_params_set_stop_threshold(handle->handle, softwareParams,
stopThreshold);
if (err < 0) {
LOGE("Unable to set stop threshold to %lu frames: %s",
stopThreshold, snd_strerror(err));
goto done;
}
// Allow the transfer to start when at least periodSize samples can be
// processed.
err = snd_pcm_sw_params_set_avail_min(handle->handle, softwareParams,
periodSize);
if (err < 0) {
LOGE("Unable to configure available minimum to %lu: %s",
periodSize, snd_strerror(err));
goto done;
}
// Commit the software parameters back to the device.
err = snd_pcm_sw_params(handle->handle, softwareParams);
if (err < 0) LOGE("Unable to configure software parameters: %s",
snd_strerror(err));
done:
snd_pcm_sw_params_free(softwareParams);
return err;
}
// ----------------------------------------------------------------------------
static status_t s_init(alsa_device_t *module, ALSAHandleList &list)
{
list.clear();
snd_pcm_uframes_t bufferSize = _defaultsOut.bufferSize;
for (size_t i = 1; (bufferSize & ~i) != 0; i <<= 1)
bufferSize &= ~i;
_defaultsOut.module = module;
_defaultsOut.bufferSize = bufferSize;
list.push_back(_defaultsOut);
bufferSize = _defaultsIn.bufferSize;
for (size_t i = 1; (bufferSize & ~i) != 0; i <<= 1)
bufferSize &= ~i;
_defaultsIn.module = module;
_defaultsIn.bufferSize = bufferSize;
list.push_back(_defaultsIn);
return NO_ERROR;
}
static status_t s_open(alsa_handle_t *handle, uint32_t devices, int mode)
{
// Close off previously opened device.
// It would be nice to determine if the underlying device actually
// changes, but we might be recovering from an error or manipulating
// mixer settings (see asound.conf).
//
s_close(handle);
LOGD("open called for devices %08x in mode %d...", devices, mode);
const char *stream = streamName(handle);
const char *devName = deviceName(handle, devices, mode);
int err;
for (;;) {
// The PCM stream is opened in blocking mode, per ALSA defaults. The
// AudioFlinger seems to assume blocking mode too, so asynchronous mode
// should not be used.
err = snd_pcm_open(&handle->handle, devName, direction(handle),
SND_PCM_ASYNC);
if (err == 0) break;
// See if there is a less specific name we can try.
// Note: We are changing the contents of a const char * here.
char *tail = strrchr(devName, '_');
if (!tail) break;
*tail = 0;
}
if (err < 0) {
// None of the Android defined audio devices exist. Open a generic one.
devName = "default";
err = snd_pcm_open(&handle->handle, devName, direction(handle), 0);
}
if (err < 0) {
LOGE("Failed to Initialize any ALSA %s device: %s",
stream, strerror(err));
return NO_INIT;
}
err = setHardwareParams(handle);
if (err == NO_ERROR) err = setSoftwareParams(handle);
LOGI("Initialized ALSA %s device %s", stream, devName);
handle->curDev = devices;
handle->curMode = mode;
return err;
}
static status_t s_close(alsa_handle_t *handle)
{
status_t err = NO_ERROR;
snd_pcm_t *h = handle->handle;
handle->handle = 0;
handle->curDev = 0;
handle->curMode = 0;
if (h) {
snd_pcm_drain(h);
err = snd_pcm_close(h);
}
return err;
}
static status_t s_standby(alsa_handle_t *handle)
{
//hw specific modules may choose to implement
//this differently to gain a power savings during
//standby
snd_pcm_drain (handle->handle);
return NO_ERROR;
}
static status_t s_route(alsa_handle_t *handle, uint32_t devices, int mode)
{
LOGD("route called for devices %08x in mode %d...", devices, mode);
if (handle->handle && handle->curDev == devices && handle->curMode == mode) return NO_ERROR;
return s_open(handle, devices, mode);
}
}