/* AudioStreamOutALSA.cpp ** ** Copyright 2008-2009 Wind River Systems ** Copyright (c) 2011, Code Aurora Forum. All rights reserved. ** ** 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> #include <math.h> #define LOG_TAG "AudioStreamOutALSA" //#define LOG_NDEBUG 0 #define LOG_NDDEBUG 0 #include <utils/Log.h> #include <utils/String8.h> #include <cutils/properties.h> #include <media/AudioRecord.h> #include <hardware_legacy/power.h> #include "AudioHardwareALSA.h" #ifndef ALSA_DEFAULT_SAMPLE_RATE #define ALSA_DEFAULT_SAMPLE_RATE 44100 // in Hz #endif namespace android_audio_legacy { // ---------------------------------------------------------------------------- static const int DEFAULT_SAMPLE_RATE = ALSA_DEFAULT_SAMPLE_RATE; // ---------------------------------------------------------------------------- AudioStreamOutALSA::AudioStreamOutALSA(AudioHardwareALSA *parent, alsa_handle_t *handle) : ALSAStreamOps(parent, handle), mParent(parent), mFrameCount(0) { } AudioStreamOutALSA::~AudioStreamOutALSA() { close(); } uint32_t AudioStreamOutALSA::channels() const { int c = ALSAStreamOps::channels(); return c; } status_t AudioStreamOutALSA::setVolume(float left, float right) { int vol; float volume; status_t status = NO_ERROR; volume = (left + right) / 2; if (volume < 0.0) { ALOGW("AudioSessionOutALSA::setVolume(%f) under 0.0, assuming 0.0\n", volume); volume = 0.0; } else if (volume > 1.0) { ALOGW("AudioSessionOutALSA::setVolume(%f) over 1.0, assuming 1.0\n", volume); volume = 1.0; } vol = lrint((volume * 0x2000)+0.5); if(!strcmp(mHandle->useCase, SND_USE_CASE_VERB_HIFI_LOW_POWER) || !strcmp(mHandle->useCase, SND_USE_CASE_MOD_PLAY_LPA)) { ALOGV("setLpaVolume(%f)\n", volume); ALOGV("Setting LPA volume to %d (available range is 0 to 100)\n", vol); mHandle->module->setLpaVolume(vol); return status; } else if(!strcmp(mHandle->useCase, SND_USE_CASE_VERB_HIFI_TUNNEL) || !strcmp(mHandle->useCase, SND_USE_CASE_MOD_PLAY_TUNNEL)) { ALOGV("setCompressedVolume(%f)\n", volume); ALOGV("Setting Compressed volume to %d (available range is 0 to 100)\n", vol); mHandle->module->setCompressedVolume(vol); return status; } else if(!strncmp(mHandle->useCase, SND_USE_CASE_VERB_IP_VOICECALL, sizeof(mHandle->useCase)) || !strncmp(mHandle->useCase, SND_USE_CASE_MOD_PLAY_VOIP, sizeof(mHandle->useCase))) { ALOGV("Avoid Software volume by returning success\n"); return status; } return INVALID_OPERATION; } ssize_t AudioStreamOutALSA::write(const void *buffer, size_t bytes) { int period_size; char *use_case; ALOGV("write:: buffer %p, bytes %d", buffer, bytes); snd_pcm_sframes_t n = 0; size_t sent = 0; status_t err; int write_pending = bytes; if((mHandle->handle == NULL) && (mHandle->rxHandle == NULL) && (strcmp(mHandle->useCase, SND_USE_CASE_VERB_IP_VOICECALL)) && (strcmp(mHandle->useCase, SND_USE_CASE_MOD_PLAY_VOIP))) { mParent->mLock.lock(); ALOGD("mHandle->useCase: %s", mHandle->useCase); snd_use_case_get(mHandle->ucMgr, "_verb", (const char **)&use_case); if ((use_case == NULL) || (!strcmp(use_case, SND_USE_CASE_VERB_INACTIVE))) { if(!strcmp(mHandle->useCase, SND_USE_CASE_MOD_PLAY_VOIP)){ strlcpy(mHandle->useCase, SND_USE_CASE_VERB_IP_VOICECALL, sizeof(SND_USE_CASE_VERB_IP_VOICECALL)); } else if(!strcmp(mHandle->useCase,SND_USE_CASE_MOD_PLAY_MUSIC2)) { strlcpy(mHandle->useCase, SND_USE_CASE_VERB_HIFI2, sizeof(SND_USE_CASE_MOD_PLAY_MUSIC2)); } else if (!strcmp(mHandle->useCase,SND_USE_CASE_MOD_PLAY_MUSIC)){ strlcpy(mHandle->useCase, SND_USE_CASE_VERB_HIFI, sizeof(SND_USE_CASE_MOD_PLAY_MUSIC)); } else if(!strcmp(mHandle->useCase, SND_USE_CASE_MOD_PLAY_LOWLATENCY_MUSIC)) { strlcpy(mHandle->useCase, SND_USE_CASE_VERB_HIFI_LOWLATENCY_MUSIC, sizeof(SND_USE_CASE_MOD_PLAY_LOWLATENCY_MUSIC)); } } else { if(!strcmp(mHandle->useCase, SND_USE_CASE_VERB_IP_VOICECALL)){ strlcpy(mHandle->useCase, SND_USE_CASE_MOD_PLAY_VOIP, sizeof(SND_USE_CASE_MOD_PLAY_VOIP)); } else if(!strcmp(mHandle->useCase,SND_USE_CASE_VERB_HIFI2)) { strlcpy(mHandle->useCase, SND_USE_CASE_MOD_PLAY_MUSIC2, sizeof(SND_USE_CASE_MOD_PLAY_MUSIC2)); } else if (!strcmp(mHandle->useCase,SND_USE_CASE_VERB_HIFI)){ strlcpy(mHandle->useCase, SND_USE_CASE_MOD_PLAY_MUSIC, sizeof(SND_USE_CASE_MOD_PLAY_MUSIC)); } else if(!strcmp(mHandle->useCase, SND_USE_CASE_VERB_HIFI_LOWLATENCY_MUSIC)) { strlcpy(mHandle->useCase, SND_USE_CASE_MOD_PLAY_LOWLATENCY_MUSIC, sizeof(SND_USE_CASE_MOD_PLAY_LOWLATENCY_MUSIC)); } } free(use_case); if((!strcmp(mHandle->useCase, SND_USE_CASE_VERB_IP_VOICECALL)) || (!strcmp(mHandle->useCase, SND_USE_CASE_MOD_PLAY_VOIP))) { #ifdef QCOM_USBAUDIO_ENABLED if((mDevices & AudioSystem::DEVICE_OUT_ANLG_DOCK_HEADSET)|| (mDevices & AudioSystem::DEVICE_OUT_DGTL_DOCK_HEADSET)|| (mDevices & AudioSystem::DEVICE_OUT_PROXY)) { mDevices |= AudioSystem::DEVICE_OUT_PROXY; mHandle->module->route(mHandle, mDevices , mParent->mode()); }else #endif { mHandle->module->route(mHandle, mDevices , AudioSystem::MODE_IN_COMMUNICATION); } #ifdef QCOM_USBAUDIO_ENABLED } else if((mDevices & AudioSystem::DEVICE_OUT_ANLG_DOCK_HEADSET)|| (mDevices & AudioSystem::DEVICE_OUT_DGTL_DOCK_HEADSET)|| (mDevices & AudioSystem::DEVICE_OUT_PROXY)) { mDevices |= AudioSystem::DEVICE_OUT_PROXY; mHandle->module->route(mHandle, mDevices , mParent->mode()); #endif } else { mHandle->module->route(mHandle, mDevices , mParent->mode()); } if (!strcmp(mHandle->useCase, SND_USE_CASE_VERB_HIFI) || !strcmp(mHandle->useCase, SND_USE_CASE_VERB_HIFI2) || !strcmp(mHandle->useCase, SND_USE_CASE_VERB_HIFI_LOWLATENCY_MUSIC) || !strcmp(mHandle->useCase, SND_USE_CASE_VERB_IP_VOICECALL)) { snd_use_case_set(mHandle->ucMgr, "_verb", mHandle->useCase); } else { snd_use_case_set(mHandle->ucMgr, "_enamod", mHandle->useCase); } if((!strcmp(mHandle->useCase, SND_USE_CASE_VERB_IP_VOICECALL)) || (!strcmp(mHandle->useCase, SND_USE_CASE_MOD_PLAY_VOIP))) { err = mHandle->module->startVoipCall(mHandle); } else mHandle->module->open(mHandle); if(mHandle->handle == NULL) { ALOGE("write:: device open failed"); mParent->mLock.unlock(); return bytes; } #ifdef QCOM_USBAUDIO_ENABLED if((mHandle->devices == AudioSystem::DEVICE_IN_ANLG_DOCK_HEADSET)|| (mHandle->devices == AudioSystem::DEVICE_OUT_ANLG_DOCK_HEADSET)){ if((!strcmp(mHandle->useCase, SND_USE_CASE_VERB_IP_VOICECALL)) || (!strcmp(mHandle->useCase, SND_USE_CASE_MOD_PLAY_VOIP))) { mParent->musbPlaybackState |= USBPLAYBACKBIT_VOIPCALL; } else { mParent->startUsbPlaybackIfNotStarted(); mParent->musbPlaybackState |= USBPLAYBACKBIT_MUSIC; } } #endif mParent->mLock.unlock(); } #ifdef QCOM_USBAUDIO_ENABLED if(((mDevices & AudioSystem::DEVICE_OUT_ANLG_DOCK_HEADSET) || (mDevices & AudioSystem::DEVICE_OUT_DGTL_DOCK_HEADSET)) && (!mParent->musbPlaybackState)) { mParent->mLock.lock(); mParent->startUsbPlaybackIfNotStarted(); ALOGV("Starting playback on USB"); if(!strcmp(mHandle->useCase, SND_USE_CASE_VERB_IP_VOICECALL) || !strcmp(mHandle->useCase, SND_USE_CASE_MOD_PLAY_VOIP)) { ALOGD("Setting VOIPCALL bit here, musbPlaybackState %d", mParent->musbPlaybackState); mParent->musbPlaybackState |= USBPLAYBACKBIT_VOIPCALL; }else{ ALOGV("enabling music, musbPlaybackState: %d ", mParent->musbPlaybackState); mParent->musbPlaybackState |= USBPLAYBACKBIT_MUSIC; } mParent->mLock.unlock(); } #endif period_size = mHandle->periodSize; do { if (write_pending < period_size) { write_pending = period_size; } if((mParent->mVoipStreamCount) && (mHandle->rxHandle != 0)) { n = pcm_write(mHandle->rxHandle, (char *)buffer + sent, period_size); } else if (mHandle->handle != 0){ n = pcm_write(mHandle->handle, (char *)buffer + sent, period_size); } if (n < 0) { mParent->mLock.lock(); if (mHandle->handle != NULL) { ALOGE("pcm_write returned error %d, trying to recover\n", n); pcm_close(mHandle->handle); mHandle->handle = NULL; if((!strncmp(mHandle->useCase, SND_USE_CASE_VERB_IP_VOICECALL, strlen(SND_USE_CASE_VERB_IP_VOICECALL))) || (!strncmp(mHandle->useCase, SND_USE_CASE_MOD_PLAY_VOIP, strlen(SND_USE_CASE_MOD_PLAY_VOIP)))) { pcm_close(mHandle->rxHandle); mHandle->rxHandle = NULL; mHandle->module->startVoipCall(mHandle); } else mHandle->module->open(mHandle); if(mHandle->handle == NULL) { ALOGE("write:: device re-open failed"); mParent->mLock.unlock(); return bytes; } } mParent->mLock.unlock(); continue; } else { mFrameCount += n; sent += static_cast<ssize_t>((period_size)); write_pending -= period_size; } } while ((mHandle->handle||(mHandle->rxHandle && mParent->mVoipStreamCount)) && sent < bytes); return sent; } status_t AudioStreamOutALSA::dump(int fd, const Vector<String16>& args) { return NO_ERROR; } status_t AudioStreamOutALSA::open(int mode) { Mutex::Autolock autoLock(mParent->mLock); return ALSAStreamOps::open(mode); } status_t AudioStreamOutALSA::close() { Mutex::Autolock autoLock(mParent->mLock); ALOGV("close"); if((!strcmp(mHandle->useCase, SND_USE_CASE_VERB_IP_VOICECALL)) || (!strcmp(mHandle->useCase, SND_USE_CASE_MOD_PLAY_VOIP))) { if((mParent->mVoipStreamCount)) { #ifdef QCOM_USBAUDIO_ENABLED if(mParent->mVoipStreamCount == 1) { ALOGV("Deregistering VOIP Call bit, musbPlaybackState:%d, musbRecordingState: %d", mParent->musbPlaybackState, mParent->musbRecordingState); mParent->musbPlaybackState &= ~USBPLAYBACKBIT_VOIPCALL; mParent->musbRecordingState &= ~USBRECBIT_VOIPCALL; mParent->closeUsbPlaybackIfNothingActive(); mParent->closeUsbRecordingIfNothingActive(); } #endif return NO_ERROR; } mParent->mVoipStreamCount = 0; } #ifdef QCOM_USBAUDIO_ENABLED else if((!strcmp(mHandle->useCase, SND_USE_CASE_VERB_HIFI_LOW_POWER)) || (!strcmp(mHandle->useCase, SND_USE_CASE_MOD_PLAY_LPA))) { mParent->musbPlaybackState &= ~USBPLAYBACKBIT_LPA; } else { mParent->musbPlaybackState &= ~USBPLAYBACKBIT_MUSIC; } mParent->closeUsbPlaybackIfNothingActive(); #endif ALSAStreamOps::close(); return NO_ERROR; } status_t AudioStreamOutALSA::standby() { Mutex::Autolock autoLock(mParent->mLock); ALOGV("standby"); if((!strcmp(mHandle->useCase, SND_USE_CASE_VERB_IP_VOICECALL)) || (!strcmp(mHandle->useCase, SND_USE_CASE_MOD_PLAY_VOIP))) { return NO_ERROR; } #ifdef QCOM_USBAUDIO_ENABLED if((!strcmp(mHandle->useCase, SND_USE_CASE_VERB_HIFI_LOW_POWER)) || (!strcmp(mHandle->useCase, SND_USE_CASE_MOD_PLAY_LPA))) { ALOGV("Deregistering LPA bit"); mParent->musbPlaybackState &= ~USBPLAYBACKBIT_LPA; } else { ALOGV("Deregistering MUSIC bit, musbPlaybackState: %d", mParent->musbPlaybackState); mParent->musbPlaybackState &= ~USBPLAYBACKBIT_MUSIC; } #endif mHandle->module->standby(mHandle); #ifdef QCOM_USBAUDIO_ENABLED mParent->closeUsbPlaybackIfNothingActive(); #endif mFrameCount = 0; return NO_ERROR; } #define USEC_TO_MSEC(x) ((x + 999) / 1000) uint32_t AudioStreamOutALSA::latency() const { // Android wants latency in milliseconds. return USEC_TO_MSEC (mHandle->latency); } // return the number of audio frames written by the audio dsp to DAC since // the output has exited standby status_t AudioStreamOutALSA::getRenderPosition(uint32_t *dspFrames) { *dspFrames = mFrameCount; return NO_ERROR; } } // namespace android_audio_legacy