/* 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_NDEBUG 0 #define LOG_TAG "ALSAModule" #include #include "AudioHardwareALSA.h" #include #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 (handle->format) > SND_PCM_FORMAT_UNKNOWN) && (static_cast (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(handle), 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(handle)); 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", streamName(handle), requestedRate); #ifdef DISABLE_HARWARE_RESAMPLING // Disable hardware re-sampling. err = snd_pcm_hw_params_set_rate_resample(handle->handle, hardwareParams, static_cast(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); } }