| /* |
| ** Copyright 2008-2010, The Android Open-Source Project |
| ** |
| ** 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 <math.h> |
| |
| //#define LOG_NDEBUG 0 |
| #define LOG_TAG "AudioHardwareTegra" |
| #include <utils/Log.h> |
| #include <utils/String8.h> |
| |
| #include <stdio.h> |
| #include <unistd.h> |
| #include <sys/ioctl.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <dlfcn.h> |
| #include <fcntl.h> |
| |
| #include "AudioHardware.h" |
| #include <media/AudioRecord.h> |
| #include <audio_effects/effect_aec.h> |
| #include <audio_effects/effect_ns.h> |
| |
| namespace android_audio_legacy { |
| const uint32_t AudioHardware::inputSamplingRates[] = { |
| 8000, 11025, 12000, 16000, 22050, 32000, 44100, 48000 |
| }; |
| |
| // number of times to attempt init() before giving up |
| const uint32_t MAX_INIT_TRIES = 10; |
| |
| // When another thread wants to acquire the Mutex on the input or output stream, a short sleep |
| // period is forced in the read or write function to release the processor before acquiring the |
| // Mutex. Otherwise, as the read/write thread sleeps most of the time waiting for DMA buffers with |
| // the Mutex locked, the other thread could wait quite long before being able to acquire the Mutex. |
| #define FORCED_SLEEP_TIME_US 10000 |
| |
| // ---------------------------------------------------------------------------- |
| |
| // always succeeds, must call init() immediately after |
| AudioHardware::AudioHardware() : |
| mInit(false), mMicMute(false), mBluetoothNrec(true), mBluetoothId(0), |
| mOutput(0), /*mCurOut/InDevice*/ mCpcapCtlFd(-1), mHwOutRate(0), mHwInRate(0), |
| mMasterVol(1.0), mVoiceVol(1.0), |
| /*mCpcapGain*/ |
| mSpkrVolume(-1), mMicVolume(-1), mEcnsEnabled(0), mEcnsRequested(0), mBtScoOn(false) |
| { |
| LOGV("AudioHardware constructor"); |
| } |
| |
| // designed to be called multiple times for retries |
| status_t AudioHardware::init() { |
| |
| if (mInit) { |
| return NO_ERROR; |
| } |
| |
| mCpcapCtlFd = ::open("/dev/audio_ctl", O_RDWR); |
| if (mCpcapCtlFd < 0) { |
| LOGE("open /dev/audio_ctl failed: %s", strerror(errno)); |
| goto error; |
| } |
| |
| if (::ioctl(mCpcapCtlFd, CPCAP_AUDIO_OUT_GET_OUTPUT, &mCurOutDevice) < 0) { |
| LOGE("could not get output device: %s", strerror(errno)); |
| goto error; |
| } |
| if (::ioctl(mCpcapCtlFd, CPCAP_AUDIO_IN_GET_INPUT, &mCurInDevice) < 0) { |
| LOGE("could not get input device: %s", strerror(errno)); |
| goto error; |
| } |
| // For bookkeeping only |
| if (::ioctl(mCpcapCtlFd, CPCAP_AUDIO_OUT_GET_RATE, &mHwOutRate) < 0) { |
| LOGE("could not get output rate: %s", strerror(errno)); |
| goto error; |
| } |
| if (::ioctl(mCpcapCtlFd, CPCAP_AUDIO_IN_GET_RATE, &mHwInRate) < 0) { |
| LOGE("could not get input rate: %s", strerror(errno)); |
| goto error; |
| } |
| |
| #ifdef USE_PROPRIETARY_AUDIO_EXTENSIONS |
| // Init the MM Audio Post Processing |
| mAudioPP.setAudioDev(&mCurOutDevice, &mCurInDevice, false, false, false); |
| #endif |
| |
| readHwGainFile(); |
| |
| mInit = true; |
| return NO_ERROR; |
| |
| error: |
| if (mCpcapCtlFd >= 0) { |
| (void) ::close(mCpcapCtlFd); |
| mCpcapCtlFd = -1; |
| } |
| return NO_INIT; |
| } |
| |
| AudioHardware::~AudioHardware() |
| { |
| LOGV("AudioHardware destructor"); |
| for (size_t index = 0; index < mInputs.size(); index++) { |
| closeInputStream((AudioStreamIn*)mInputs[index]); |
| } |
| mInputs.clear(); |
| closeOutputStream((AudioStreamOut*)mOutput); |
| if (mCpcapCtlFd >= 0) { |
| (void) ::close(mCpcapCtlFd); |
| mCpcapCtlFd = -1; |
| } |
| } |
| |
| void AudioHardware::readHwGainFile() |
| { |
| int fd; |
| int rc=0; |
| int i; |
| uint32_t format, version, barker; |
| fd = open("/system/etc/cpcap_gain.bin", O_RDONLY); |
| if (fd>=0) { |
| ::read(fd, &format, sizeof(uint32_t)); |
| ::read(fd, &version, sizeof(uint32_t)); |
| ::read(fd, &barker, sizeof(uint32_t)); |
| rc = ::read(fd, mCpcapGain, sizeof(mCpcapGain)); |
| LOGD("Read gain file, format %X version %X", format, version); |
| ::close(fd); |
| } |
| if (rc != sizeof(mCpcapGain) || format != 0x30303032) { |
| int gain; |
| LOGE("CPCAP gain file not valid. Using defaults."); |
| for (int i=0; i<AUDIO_HW_GAIN_NUM_DIRECTIONS; i++) { |
| if (i==AUDIO_HW_GAIN_SPKR_GAIN) |
| gain = 11; |
| else |
| gain = 31; |
| for (int j=0; j<AUDIO_HW_GAIN_NUM_USECASES; j++) |
| for (int k=0; k<AUDIO_HW_GAIN_NUM_PATHS; k++) |
| mCpcapGain[i][j][k]=gain; |
| } |
| } |
| return; |
| } |
| |
| status_t AudioHardware::initCheck() |
| { |
| return mInit ? NO_ERROR : NO_INIT; |
| } |
| |
| AudioStreamOut* AudioHardware::openOutputStream( |
| uint32_t devices, int *format, uint32_t *channels, uint32_t *sampleRate, status_t *status) |
| { |
| { // scope for the lock |
| Mutex::Autolock lock(mLock); |
| |
| // only one output stream allowed |
| if (mOutput) { |
| if (status) { |
| *status = INVALID_OPERATION; |
| } |
| return 0; |
| } |
| |
| // create new output stream |
| AudioStreamOutTegra* out = new AudioStreamOutTegra(); |
| for (unsigned tries = 0; tries < MAX_INIT_TRIES; ++tries) { |
| if (NO_ERROR == out->init()) |
| break; |
| LOGW("AudioStreamOutTegra::init failed soft, retrying"); |
| sleep(1); |
| } |
| status_t lStatus; |
| lStatus = out->initCheck(); |
| if (NO_ERROR != lStatus) { |
| LOGE("AudioStreamOutTegra::init failed hard"); |
| } else { |
| lStatus = out->set(this, devices, format, channels, sampleRate); |
| } |
| if (status) { |
| *status = lStatus; |
| } |
| if (lStatus == NO_ERROR) { |
| mOutput = out; |
| } else { |
| mLock.unlock(); |
| delete out; |
| out = NULL; |
| mLock.lock(); |
| } |
| } |
| return mOutput; |
| } |
| |
| void AudioHardware::closeOutputStream(AudioStreamOut* out) { |
| Mutex::Autolock lock(mLock); |
| if (mOutput == 0 || mOutput != out) { |
| LOGW("Attempt to close invalid output stream"); |
| } |
| else { |
| // AudioStreamOutTegra destructor calls standby which locks |
| mOutput = 0; |
| mLock.unlock(); |
| delete out; |
| mLock.lock(); |
| } |
| } |
| |
| AudioStreamIn* AudioHardware::openInputStream( |
| uint32_t devices, int *format, uint32_t *channels, uint32_t *sampleRate, status_t *status, |
| AudioSystem::audio_in_acoustics acoustic_flags) |
| { |
| // check for valid input source |
| if (!AudioSystem::isInputDevice((AudioSystem::audio_devices)devices)) { |
| return 0; |
| } |
| |
| Mutex::Autolock lock(mLock); |
| |
| AudioStreamInTegra* in = new AudioStreamInTegra(); |
| // this serves a similar purpose as init() |
| status_t lStatus = in->set(this, devices, format, channels, sampleRate, acoustic_flags); |
| if (status) { |
| *status = lStatus; |
| } |
| if (lStatus != NO_ERROR) { |
| mLock.unlock(); |
| delete in; |
| mLock.lock(); |
| return 0; |
| } |
| |
| mInputs.add(in); |
| |
| return in; |
| } |
| |
| void AudioHardware::closeInputStream(AudioStreamIn* in) |
| { |
| Mutex::Autolock lock(mLock); |
| |
| ssize_t index = mInputs.indexOf((AudioStreamInTegra *)in); |
| if (index < 0) { |
| LOGW("Attempt to close invalid input stream"); |
| } else { |
| mInputs.removeAt(index); |
| mLock.unlock(); |
| delete in; |
| mLock.lock(); |
| } |
| } |
| |
| status_t AudioHardware::setMode(int mode) |
| { |
| AutoMutex lock(mLock); |
| bool wasInCall = isInCall(); |
| LOGV("setMode() : new %d, old %d", mode, mMode); |
| status_t status = AudioHardwareBase::setMode(mode); |
| if (status == NO_ERROR) { |
| if (wasInCall ^ isInCall()) { |
| doRouting_l(); |
| if (wasInCall) { |
| setMicMute_l(false); |
| } |
| } |
| } |
| |
| return status; |
| } |
| |
| // Must be called with mLock held |
| status_t AudioHardware::doStandby(int stop_fd, bool output, bool enable) |
| { |
| status_t status = NO_ERROR; |
| struct cpcap_audio_stream standby; |
| |
| LOGV("AudioHardware::doStandby() putting %s in %s mode", |
| output ? "output" : "input", |
| enable ? "standby" : "online" ); |
| |
| // Debug code |
| if (!mLock.tryLock()) { |
| LOGE("doStandby called without mLock held."); |
| mLock.unlock(); |
| } |
| // end Debug code |
| |
| if (output) { |
| standby.id = CPCAP_AUDIO_OUT_STANDBY; |
| standby.on = enable; |
| |
| if (enable) { |
| /* Flush the queued playback data. Putting the output in standby |
| * will cause CPCAP to not drive the i2s interface, and write() |
| * will block until playback is resumed. |
| */ |
| if (mOutput) |
| mOutput->flush(); |
| } |
| |
| if (::ioctl(mCpcapCtlFd, CPCAP_AUDIO_OUT_SET_OUTPUT, &standby) < 0) { |
| LOGE("could not turn off current output device: %s", |
| strerror(errno)); |
| status = errno; |
| } |
| |
| if (::ioctl(mCpcapCtlFd, CPCAP_AUDIO_OUT_GET_OUTPUT, &mCurOutDevice) < 0) { |
| LOGE("could not get current output device after standby: %s", |
| strerror(errno)); |
| } |
| LOGV("%s: after standby %s, output device %d is %s", __FUNCTION__, |
| enable ? "enable" : "disable", mCurOutDevice.id, |
| mCurOutDevice.on ? "on" : "off"); |
| } else { |
| standby.id = CPCAP_AUDIO_IN_STANDBY; |
| standby.on = enable; |
| |
| if (enable && stop_fd >= 0) { |
| /* Stop recording, if ongoing. Muting the microphone will cause |
| * CPCAP to not send data through the i2s interface, and read() |
| * will block until recording is resumed. |
| */ |
| LOGV("%s: stop recording", __FUNCTION__); |
| if (::ioctl(stop_fd, TEGRA_AUDIO_IN_STOP) < 0) { |
| LOGE("could not stop recording: %s", |
| strerror(errno)); |
| } |
| } |
| |
| if (::ioctl(mCpcapCtlFd, CPCAP_AUDIO_IN_SET_INPUT, &standby) < 0) { |
| LOGE("could not turn off current input device: %s", |
| strerror(errno)); |
| status = errno; |
| } |
| ::ioctl(mCpcapCtlFd, CPCAP_AUDIO_IN_GET_INPUT, &mCurInDevice); |
| LOGV("%s: after standby %s, input device %d is %s", __FUNCTION__, |
| enable ? "enable" : "disable", mCurInDevice.id, |
| mCurInDevice.on ? "on" : "off"); |
| } |
| |
| return status; |
| } |
| |
| status_t AudioHardware::setMicMute(bool state) |
| { |
| Mutex::Autolock lock(mLock); |
| return setMicMute_l(state); |
| } |
| |
| status_t AudioHardware::setMicMute_l(bool state) |
| { |
| if (mMicMute != state) { |
| mMicMute = state; |
| LOGV("setMicMute() %s", (state)?"ON":"OFF"); |
| } |
| return NO_ERROR; |
| } |
| |
| status_t AudioHardware::getMicMute(bool* state) |
| { |
| *state = mMicMute; |
| return NO_ERROR; |
| } |
| |
| status_t AudioHardware::setParameters(const String8& keyValuePairs) |
| { |
| AudioParameter param = AudioParameter(keyValuePairs); |
| String8 value; |
| String8 key; |
| const char BT_NREC_KEY[] = "bt_headset_nrec"; |
| const char BT_NAME_KEY[] = "bt_headset_name"; |
| const char BT_NREC_VALUE_ON[] = "on"; |
| |
| |
| LOGV("setParameters() %s", keyValuePairs.string()); |
| |
| if (keyValuePairs.length() == 0) return BAD_VALUE; |
| |
| key = String8(BT_NREC_KEY); |
| if (param.get(key, value) == NO_ERROR) { |
| if (value == BT_NREC_VALUE_ON) { |
| mBluetoothNrec = true; |
| LOGD("Turn on bluetooth NREC"); |
| } else { |
| mBluetoothNrec = false; |
| LOGD("Turning noise reduction and echo cancellation off for BT " |
| "headset"); |
| } |
| doRouting(); |
| } |
| key = String8(BT_NAME_KEY); |
| if (param.get(key, value) == NO_ERROR) { |
| mBluetoothId = 0; |
| #if 0 |
| for (int i = 0; i < mNumSndEndpoints; i++) { |
| if (!strcasecmp(value.string(), mSndEndpoints[i].name)) { |
| mBluetoothId = mSndEndpoints[i].id; |
| LOGD("Using custom acoustic parameters for %s", value.string()); |
| break; |
| } |
| } |
| #endif |
| if (mBluetoothId == 0) { |
| LOGD("Using default acoustic parameters " |
| "(%s not in acoustic database)", value.string()); |
| doRouting(); |
| } |
| } |
| return NO_ERROR; |
| } |
| |
| String8 AudioHardware::getParameters(const String8& keys) |
| { |
| AudioParameter request = AudioParameter(keys); |
| AudioParameter reply = AudioParameter(); |
| String8 value; |
| String8 key; |
| |
| LOGV("getParameters() %s", keys.string()); |
| |
| #ifdef USE_PROPRIETARY_AUDIO_EXTENSIONS |
| key = "ec_supported"; |
| if (request.get(key, value) == NO_ERROR) { |
| value = "yes"; |
| reply.add(key, value); |
| } |
| #endif |
| |
| return reply.toString(); |
| } |
| |
| size_t AudioHardware::getInputBufferSize(uint32_t sampleRate, int format, int channelCount) |
| { |
| size_t bufsize; |
| |
| if (format != AudioSystem::PCM_16_BIT) { |
| LOGW("getInputBufferSize bad format: %d", format); |
| return 0; |
| } |
| if (channelCount < 1 || channelCount > 2) { |
| LOGW("getInputBufferSize bad channel count: %d", channelCount); |
| return 0; |
| } |
| |
| // Return 20 msec input buffer size. |
| bufsize = sampleRate * sizeof(int16_t) * channelCount / 50; |
| if (bufsize & 0x7) { |
| // Not divisible by 8. |
| bufsize +=8; |
| bufsize &= ~0x7; |
| } |
| LOGV("%s: returns %d for rate %d", __FUNCTION__, bufsize, sampleRate); |
| return bufsize; |
| } |
| |
| //setVoiceVolume is only useful for setting sidetone gains with a baseband |
| //controlling volume. Don't adjust hardware volume with this API. |
| // |
| //(On Stingray, don't use mVoiceVol for anything.) |
| status_t AudioHardware::setVoiceVolume(float v) |
| { |
| if (v < 0.0) |
| v = 0.0; |
| else if (v > 1.0) |
| v = 1.0; |
| |
| LOGV("Setting unused in-call vol to %f",v); |
| mVoiceVol = v; |
| |
| return NO_ERROR; |
| } |
| |
| status_t AudioHardware::setMasterVolume(float v) |
| { |
| if (v < 0.0) |
| v = 0.0; |
| else if (v > 1.0) |
| v = 1.0; |
| |
| LOGV("Set master vol to %f.", v); |
| mMasterVol = v; |
| Mutex::Autolock lock(mLock); |
| int useCase = AUDIO_HW_GAIN_USECASE_MM; |
| AudioStreamInTegra *input = getActiveInput_l(); |
| if (input) { |
| if (isInCall() && mOutput && !mOutput->getStandby() && |
| input->source() == AUDIO_SOURCE_VOICE_COMMUNICATION) { |
| useCase = AUDIO_HW_GAIN_USECASE_VOICE; |
| } else if (input->source() == AUDIO_SOURCE_VOICE_RECOGNITION) { |
| useCase = AUDIO_HW_GAIN_USECASE_VOICE_REC; |
| } |
| } |
| setVolume_l(v, useCase); |
| return NO_ERROR; |
| } |
| |
| // Call with mLock held. |
| status_t AudioHardware::setVolume_l(float v, int usecase) |
| { |
| int spkr = getGain(AUDIO_HW_GAIN_SPKR_GAIN, usecase); |
| int mic = getGain(AUDIO_HW_GAIN_MIC_GAIN, usecase); |
| |
| if (spkr==0) { |
| // no device to set volume on. Ignore request. |
| return -1; |
| } |
| |
| spkr = ceil(v * spkr); |
| if (mSpkrVolume != spkr) { |
| LOGV("Set tx volume to %d", spkr); |
| int ret = ::ioctl(mCpcapCtlFd, CPCAP_AUDIO_OUT_SET_VOLUME, spkr); |
| if (ret < 0) { |
| LOGE("could not set spkr volume: %s", strerror(errno)); |
| return ret; |
| } |
| mSpkrVolume = spkr; |
| } |
| if (mMicVolume != mic) { |
| LOGV("Set rx volume to %d", mic); |
| int ret = ::ioctl(mCpcapCtlFd, CPCAP_AUDIO_IN_SET_VOLUME, mic); |
| if (ret < 0) { |
| LOGE("could not set mic volume: %s", strerror(errno)); |
| return ret; |
| } |
| mMicVolume = mic; |
| } |
| |
| return NO_ERROR; |
| } |
| |
| uint8_t AudioHardware::getGain(int direction, int usecase) |
| { |
| int path; |
| AudioStreamInTegra *input = getActiveInput_l(); |
| uint32_t inDev = (input == NULL) ? 0 : input->devices(); |
| if (!mOutput) { |
| LOGE("No output device."); |
| return 0; |
| } |
| uint32_t outDev = mOutput->devices(); |
| |
| // In case of an actual phone, with an actual earpiece, uncomment. |
| // if (outDev & AudioSystem::DEVICE_OUT_EARPIECE) |
| // path = AUDIO_HW_GAIN_EARPIECE; |
| // else |
| if (outDev & AudioSystem::DEVICE_OUT_WIRED_HEADPHONE) |
| path = AUDIO_HW_GAIN_HEADSET_NO_MIC; |
| else if (outDev & AudioSystem::DEVICE_OUT_WIRED_HEADSET) |
| path = AUDIO_HW_GAIN_HEADSET_W_MIC; |
| else if (outDev & AudioSystem::DEVICE_OUT_ANLG_DOCK_HEADSET) |
| path = AUDIO_HW_GAIN_EMU_DEVICE; |
| else |
| path = AUDIO_HW_GAIN_SPEAKERPHONE; |
| |
| LOGV("Picked gain[%d][%d][%d] which is %d.",direction, usecase, path, |
| mCpcapGain[direction][usecase][path]); |
| |
| return mCpcapGain[direction][usecase][path]; |
| } |
| |
| int AudioHardware::getActiveInputRate() |
| { |
| AudioStreamInTegra *input = getActiveInput_l(); |
| return (input != NULL) ? input->sampleRate() : 0; |
| } |
| |
| status_t AudioHardware::doRouting() |
| { |
| Mutex::Autolock lock(mLock); |
| return doRouting_l(); |
| } |
| |
| // Call this with mLock held. |
| status_t AudioHardware::doRouting_l() |
| { |
| if (!mOutput) { |
| return NO_ERROR; |
| } |
| uint32_t outputDevices = mOutput->devices(); |
| AudioStreamInTegra *input = getActiveInput_l(); |
| uint32_t inputDevice = (input == NULL) ? 0 : input->devices(); |
| uint32_t btScoOutDevices = outputDevices & ( |
| AudioSystem::DEVICE_OUT_BLUETOOTH_SCO | |
| AudioSystem::DEVICE_OUT_BLUETOOTH_SCO_HEADSET | |
| AudioSystem::DEVICE_OUT_BLUETOOTH_SCO_CARKIT ); |
| uint32_t spdifOutDevices = outputDevices & ( |
| AudioSystem::DEVICE_OUT_DGTL_DOCK_HEADSET | |
| AudioSystem::DEVICE_OUT_AUX_DIGITAL ); |
| uint32_t speakerOutDevices = outputDevices ^ btScoOutDevices ^ spdifOutDevices; |
| uint32_t btScoInDevice = inputDevice & AudioSystem::DEVICE_IN_BLUETOOTH_SCO_HEADSET; |
| uint32_t micInDevice = inputDevice ^ btScoInDevice; |
| int sndOutDevice = -1; |
| int sndInDevice = -1; |
| bool btScoOn = btScoOutDevices||btScoInDevice; |
| |
| LOGV("%s: inputDevice %x, outputDevices %x", __FUNCTION__, |
| inputDevice, outputDevices); |
| |
| switch (inputDevice) { |
| case AudioSystem::DEVICE_IN_DEFAULT: |
| case AudioSystem::DEVICE_IN_BUILTIN_MIC: |
| sndInDevice = CPCAP_AUDIO_IN_MIC1; |
| break; |
| case AudioSystem::DEVICE_IN_WIRED_HEADSET: |
| sndInDevice = CPCAP_AUDIO_IN_MIC2; |
| break; |
| default: |
| break; |
| } |
| |
| switch (speakerOutDevices) { |
| case AudioSystem::DEVICE_OUT_EARPIECE: |
| case AudioSystem::DEVICE_OUT_DEFAULT: |
| case AudioSystem::DEVICE_OUT_SPEAKER: |
| sndOutDevice = CPCAP_AUDIO_OUT_SPEAKER; |
| break; |
| case AudioSystem::DEVICE_OUT_WIRED_HEADSET: |
| case AudioSystem::DEVICE_OUT_WIRED_HEADPHONE: |
| sndOutDevice = CPCAP_AUDIO_OUT_HEADSET; |
| break; |
| case AudioSystem::DEVICE_OUT_SPEAKER | AudioSystem::DEVICE_OUT_WIRED_HEADSET: |
| case AudioSystem::DEVICE_OUT_SPEAKER | AudioSystem::DEVICE_OUT_WIRED_HEADPHONE: |
| sndOutDevice = CPCAP_AUDIO_OUT_HEADSET_AND_SPEAKER; |
| break; |
| case AudioSystem::DEVICE_OUT_ANLG_DOCK_HEADSET: |
| sndOutDevice = CPCAP_AUDIO_OUT_ANLG_DOCK_HEADSET; |
| break; |
| case AudioSystem::DEVICE_OUT_DGTL_DOCK_HEADSET: |
| // To be implemented |
| break; |
| default: |
| break; |
| } |
| |
| if (sndInDevice != (int)mCurInDevice.id) { |
| if (sndInDevice == -1) { |
| LOGV("input device set %x not supported, defaulting to on-board mic", |
| inputDevice); |
| mCurInDevice.id = CPCAP_AUDIO_IN_MIC1; |
| } |
| else |
| mCurInDevice.id = sndInDevice; |
| |
| if (::ioctl(mCpcapCtlFd, CPCAP_AUDIO_IN_SET_INPUT, |
| &mCurInDevice) < 0) |
| LOGE("could not set input (%d, on %d): %s", |
| mCurInDevice.id, mCurInDevice.on, strerror(errno)); |
| |
| LOGV("current input %d, %s", |
| mCurInDevice.id, |
| mCurInDevice.on ? "on" : "off"); |
| } |
| |
| if (sndOutDevice != (int)mCurOutDevice.id) { |
| if (sndOutDevice == -1) { |
| LOGW("output device set %x not supported, defaulting to speaker", |
| outputDevices); |
| mCurOutDevice.id = CPCAP_AUDIO_OUT_SPEAKER; |
| } |
| else |
| mCurOutDevice.id = sndOutDevice; |
| |
| if (::ioctl(mCpcapCtlFd, CPCAP_AUDIO_OUT_SET_OUTPUT, |
| &mCurOutDevice) < 0) |
| LOGE("could not set output (%d, on %d): %s", |
| mCurOutDevice.id, mCurOutDevice.on, |
| strerror(errno)); |
| |
| LOGV("current output %d, %s", |
| mCurOutDevice.id, |
| mCurOutDevice.on ? "on" : "off"); |
| } |
| |
| // enable EC if: |
| // - mEcnsRequested AND |
| // - the output stream is active |
| mEcnsEnabled = mEcnsRequested; |
| if (mOutput->getStandby()) { |
| mEcnsEnabled &= ~PREPROC_AEC; |
| } |
| |
| #ifdef USE_PROPRIETARY_AUDIO_EXTENSIONS |
| int ecnsRate = (btScoOn || (getActiveInputRate() < 16000)) ? 8000 : 16000; |
| // Check input/output rates for HW. |
| if (mEcnsEnabled) { |
| mHwInRate = ecnsRate; |
| // rx path is altered only if AEC is enabled |
| if (mEcnsEnabled & PREPROC_AEC) { |
| mHwOutRate = mHwInRate; |
| } else { |
| mHwOutRate = AUDIO_HW_OUT_SAMPLERATE; |
| } |
| LOGD("EC/NS active, requests rate as %d for in/out", mHwInRate); |
| } |
| else |
| #endif |
| { |
| if (input) { |
| mHwInRate = getActiveInputRate(); |
| } |
| mHwOutRate = AUDIO_HW_OUT_SAMPLERATE; |
| LOGV("No EC/NS, set input rate %d, output %d.", mHwInRate, mHwOutRate); |
| } |
| if (btScoOn) { |
| mHwOutRate = 8000; |
| mHwInRate = 8000; |
| LOGD("Bluetooth SCO active, rate forced to 8K"); |
| } |
| |
| if (input) { |
| // acquire mutex if not already locked by read() |
| if (!input->isLocked()) { |
| input->lock(); |
| } |
| } |
| // acquire mutex if not already locked by write() |
| if (!mOutput->isLocked()) { |
| mOutput->lock(); |
| } |
| #ifdef USE_PROPRIETARY_AUDIO_EXTENSIONS |
| mAudioPP.setAudioDev(&mCurOutDevice, &mCurInDevice, |
| btScoOn, mBluetoothNrec, |
| spdifOutDevices?true:false); |
| mAudioPP.enableEcns(mEcnsEnabled); |
| #endif |
| |
| mOutput->setDriver_l(speakerOutDevices?true:false, |
| btScoOn, |
| spdifOutDevices?true:false, mHwOutRate); |
| |
| if (input) { |
| input->setDriver_l(micInDevice?true:false, |
| btScoOn, mHwInRate); |
| } |
| |
| // Changing I2S to port connection when bluetooth starts or stopS must be done simultaneously |
| // for input and output while both DMAs are stopped |
| if (btScoOn != mBtScoOn) { |
| if (input) { |
| #ifdef USE_PROPRIETARY_AUDIO_EXTENSIONS |
| if (mEcnsEnabled) { |
| mAudioPP.enableEcns(0); |
| mAudioPP.enableEcns(mEcnsEnabled); |
| } |
| #endif |
| input->lockFd(); |
| input->stop_l(); |
| } |
| mOutput->lockFd(); |
| mOutput->flush_l(); |
| |
| int bit_format = TEGRA_AUDIO_BIT_FORMAT_DEFAULT; |
| bool is_bt_bypass = false; |
| if (btScoOn) { |
| bit_format = TEGRA_AUDIO_BIT_FORMAT_DSP; |
| is_bt_bypass = true; |
| } |
| LOGV("%s: bluetooth state changed. is_bt_bypass %d bit_format %d", |
| __FUNCTION__, is_bt_bypass, bit_format); |
| // Setup the I2S2-> DAP2/4 capture/playback path. |
| if (::ioctl(mOutput->mBtFdIoCtl, TEGRA_AUDIO_SET_BIT_FORMAT, &bit_format) < 0) { |
| LOGE("could not set bit format %s", strerror(errno)); |
| } |
| if (::ioctl(mCpcapCtlFd, CPCAP_AUDIO_SET_BLUETOOTH_BYPASS, is_bt_bypass) < 0) { |
| LOGE("could not set bluetooth bypass %s", strerror(errno)); |
| } |
| |
| mBtScoOn = btScoOn; |
| mOutput->unlockFd(); |
| if (input) { |
| input->unlockFd(); |
| } |
| } |
| |
| if (!mOutput->isLocked()) { |
| mOutput->unlock(); |
| } |
| if (input && !input->isLocked()) { |
| input->unlock(); |
| } |
| |
| // Since HW path may have changed, set the hardware gains. |
| int useCase = AUDIO_HW_GAIN_USECASE_MM; |
| if (mEcnsEnabled) { |
| useCase = AUDIO_HW_GAIN_USECASE_VOICE; |
| } else if (input && input->source() == AUDIO_SOURCE_VOICE_RECOGNITION) { |
| useCase = AUDIO_HW_GAIN_USECASE_VOICE_REC; |
| } |
| setVolume_l(mMasterVol, useCase); |
| |
| return NO_ERROR; |
| } |
| |
| status_t AudioHardware::dumpInternals(int fd, const Vector<String16>& args) |
| { |
| const size_t SIZE = 256; |
| char buffer[SIZE]; |
| String8 result; |
| result.append("AudioHardware::dumpInternals\n"); |
| snprintf(buffer, SIZE, "\tmInit: %s\n", mInit? "true": "false"); |
| result.append(buffer); |
| snprintf(buffer, SIZE, "\tmMicMute: %s\n", mMicMute? "true": "false"); |
| result.append(buffer); |
| snprintf(buffer, SIZE, "\tmBluetoothNrec: %s\n", mBluetoothNrec? "true": "false"); |
| result.append(buffer); |
| snprintf(buffer, SIZE, "\tmBluetoothId: %d\n", mBluetoothId); |
| result.append(buffer); |
| ::write(fd, result.string(), result.size()); |
| return NO_ERROR; |
| } |
| |
| status_t AudioHardware::dump(int fd, const Vector<String16>& args) |
| { |
| dumpInternals(fd, args); |
| for (size_t index = 0; index < mInputs.size(); index++) { |
| mInputs[index]->dump(fd, args); |
| } |
| |
| if (mOutput) { |
| mOutput->dump(fd, args); |
| } |
| return NO_ERROR; |
| } |
| |
| uint32_t AudioHardware::getInputSampleRate(uint32_t sampleRate) |
| { |
| uint32_t i; |
| uint32_t prevDelta; |
| uint32_t delta; |
| |
| for (i = 0, prevDelta = 0xFFFFFFFF; i < sizeof(inputSamplingRates)/sizeof(uint32_t); i++, prevDelta = delta) { |
| delta = abs(sampleRate - inputSamplingRates[i]); |
| if (delta > prevDelta) break; |
| } |
| // i is always > 0 here |
| return inputSamplingRates[i-1]; |
| } |
| |
| // getActiveInput_l() must be called with mLock held |
| AudioHardware::AudioStreamInTegra *AudioHardware::getActiveInput_l() |
| { |
| for (size_t i = 0; i < mInputs.size(); i++) { |
| // return first input found not being in standby mode |
| // as only one input can be in this state |
| if (!mInputs[i]->getStandby()) { |
| return mInputs[i]; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| void AudioHardware::setEcnsRequested_l(int ecns, bool enabled) |
| { |
| if (enabled) { |
| mEcnsRequested |= ecns; |
| } else { |
| mEcnsRequested &= ~ecns; |
| } |
| } |
| |
| // ---------------------------------------------------------------------------- |
| // Sample Rate Converter wrapper |
| // |
| #ifdef USE_PROPRIETARY_AUDIO_EXTENSIONS |
| AudioHardware::AudioStreamSrc::AudioStreamSrc() : |
| mSrcBuffer(NULL), mSrcInitted(false) |
| { |
| } |
| AudioHardware::AudioStreamSrc::~AudioStreamSrc() |
| { |
| if (mSrcBuffer != NULL) { |
| delete[] mSrcBuffer; |
| } |
| } |
| |
| void AudioHardware::AudioStreamSrc::init(int inRate, int outRate) |
| { |
| if (mSrcBuffer == NULL) { |
| mSrcBuffer = new char[src_memory_required_stereo(MAX_FRAME_LEN, MAX_CONVERT_RATIO)]; |
| } |
| if (mSrcBuffer == NULL) { |
| LOGE("Failed to allocate memory for sample rate converter."); |
| return; |
| } |
| mSrcInit.memory = (SRC16*)(mSrcBuffer); |
| mSrcInit.input_rate = inRate; |
| mSrcInit.output_rate = outRate; |
| mSrcInit.frame_length = MAX_FRAME_LEN; |
| mSrcInit.stereo_flag = SRC_OFF; |
| mSrcInit.input_interleaved = SRC_OFF; |
| mSrcInit.output_interleaved = SRC_OFF; |
| rate_convert_init(&mSrcInit, &mSrcObj); |
| |
| mSrcInitted = true; |
| mSrcInRate = inRate; |
| mSrcOutRate = outRate; |
| } |
| #endif |
| |
| // ---------------------------------------------------------------------------- |
| |
| // always succeeds, must call init() immediately after |
| AudioHardware::AudioStreamOutTegra::AudioStreamOutTegra() : |
| mBtFdIoCtl(-1), mHardware(0), mFd(-1), mFdCtl(-1), |
| mBtFd(-1), mBtFdCtl(-1), |
| mSpdifFd(-1), mSpdifFdCtl(-1), |
| mStartCount(0), mRetryCount(0), mDevices(0), |
| mIsSpkrEnabled(false), mIsBtEnabled(false), mIsSpdifEnabled(false), |
| mIsSpkrEnabledReq(false), mIsBtEnabledReq(false), mIsSpdifEnabledReq(false), |
| mSpareSample(0), mHaveSpareSample(false), |
| mState(AUDIO_STREAM_IDLE), /*mSrc*/ mLocked(false), mDriverRate(AUDIO_HW_OUT_SAMPLERATE), |
| mInit(false) |
| { |
| LOGV("AudioStreamOutTegra constructor"); |
| } |
| |
| // designed to be called multiple times for retries |
| status_t AudioHardware::AudioStreamOutTegra::init() |
| { |
| if (mInit) { |
| return NO_ERROR; |
| } |
| |
| #define OPEN_FD(fd, dev) fd = ::open(dev, O_RDWR); \ |
| if (fd < 0) { \ |
| LOGE("open " dev " failed: %s", strerror(errno)); \ |
| goto error; \ |
| } |
| OPEN_FD(mFd, "/dev/audio0_out") |
| OPEN_FD(mFdCtl, "/dev/audio0_out_ctl") |
| OPEN_FD(mBtFd, "/dev/audio1_out") |
| OPEN_FD(mBtFdCtl, "/dev/audio1_out_ctl") |
| OPEN_FD(mBtFdIoCtl, "/dev/audio1_ctl") |
| // may need to be changed to warnings |
| OPEN_FD(mSpdifFd, "/dev/spdif_out") |
| OPEN_FD(mSpdifFdCtl, "/dev/spdif_out_ctl") |
| #undef OPEN_FD |
| |
| setNumBufs(AUDIO_HW_NUM_OUT_BUF_LONG); |
| |
| mInit = true; |
| return NO_ERROR; |
| |
| error: |
| #define CLOSE_FD(fd) if (fd >= 0) { \ |
| (void) ::close(fd); \ |
| fd = -1; \ |
| } |
| CLOSE_FD(mFd) |
| CLOSE_FD(mFdCtl) |
| CLOSE_FD(mBtFd) |
| CLOSE_FD(mBtFdCtl) |
| CLOSE_FD(mBtFdIoCtl) |
| CLOSE_FD(mSpdifFd) |
| CLOSE_FD(mSpdifFdCtl) |
| #undef CLOSE_FD |
| return NO_INIT; |
| } |
| |
| status_t AudioHardware::AudioStreamOutTegra::initCheck() |
| { |
| return mInit ? NO_ERROR : NO_INIT; |
| } |
| |
| // Called with mHardware->mLock and mLock held. |
| void AudioHardware::AudioStreamOutTegra::setDriver_l( |
| bool speaker, bool bluetooth, bool spdif, int sampleRate) |
| { |
| LOGV("Out setDriver_l() Analog speaker? %s. Bluetooth? %s. S/PDIF? %s. sampleRate %d", |
| speaker?"yes":"no", bluetooth?"yes":"no", spdif?"yes":"no", sampleRate); |
| |
| // force some reconfiguration at next write() |
| if (mState == AUDIO_STREAM_CONFIGURED) { |
| if (mIsSpkrEnabled != speaker || mIsBtEnabled != bluetooth || mIsSpdifEnabled != spdif) { |
| mState = AUDIO_STREAM_CONFIG_REQ; |
| } else if (sampleRate != mDriverRate) { |
| mState = AUDIO_STREAM_NEW_RATE_REQ; |
| } |
| } |
| |
| mIsSpkrEnabledReq = speaker; |
| mIsBtEnabledReq = bluetooth; |
| mIsSpdifEnabledReq = spdif; |
| |
| } |
| |
| status_t AudioHardware::AudioStreamOutTegra::set( |
| AudioHardware* hw, uint32_t devices, int *pFormat, uint32_t *pChannels, uint32_t *pRate) |
| { |
| int lFormat = pFormat ? *pFormat : 0; |
| uint32_t lChannels = pChannels ? *pChannels : 0; |
| uint32_t lRate = pRate ? *pRate : 0; |
| |
| mHardware = hw; |
| |
| // fix up defaults |
| if (lFormat == 0) lFormat = format(); |
| if (lChannels == 0) lChannels = channels(); |
| if (lRate == 0) lRate = sampleRate(); |
| |
| // check values |
| if ((lFormat != format()) || |
| (lChannels != channels()) || |
| (lRate != sampleRate())) { |
| if (pFormat) *pFormat = format(); |
| if (pChannels) *pChannels = channels(); |
| if (pRate) *pRate = sampleRate(); |
| return BAD_VALUE; |
| } |
| |
| #ifdef USE_PROPRIETARY_AUDIO_EXTENSIONS |
| mHardware->mAudioPP.setPlayAudioRate(lRate); |
| #endif |
| |
| if (pFormat) *pFormat = lFormat; |
| if (pChannels) *pChannels = lChannels; |
| if (pRate) *pRate = lRate; |
| |
| mDevices = devices; |
| if (mFd >= 0 && mFdCtl >= 0 && |
| mBtFd >= 0 && |
| mBtFdCtl >= 0 && |
| mBtFdIoCtl >= 0) { |
| if (mSpdifFd < 0 || mSpdifFdCtl < 0) |
| LOGW("s/pdif driver not present"); |
| return NO_ERROR; |
| } else { |
| LOGE("Problem opening device files - Is your kernel compatible?"); |
| return NO_INIT; |
| } |
| } |
| |
| AudioHardware::AudioStreamOutTegra::~AudioStreamOutTegra() |
| { |
| standby(); |
| // Prevent someone from flushing the fd during a close. |
| Mutex::Autolock lock(mFdLock); |
| if (mFd >= 0) { ::close(mFd); mFd = -1; } |
| if (mFdCtl >= 0) { ::close(mFdCtl); mFdCtl = -1; } |
| if (mBtFd >= 0) { ::close(mBtFd); mBtFd = -1; } |
| if (mBtFdCtl >= 0) { ::close(mBtFdCtl); mBtFdCtl = -1; } |
| if (mBtFdIoCtl >= 0) { ::close(mBtFdIoCtl); mBtFdIoCtl = -1; } |
| if (mSpdifFd >= 0) { ::close(mSpdifFd); mSpdifFd = -1; } |
| if (mSpdifFdCtl >= 0) { ::close(mSpdifFdCtl); mSpdifFdCtl = -1; } |
| } |
| |
| ssize_t AudioHardware::AudioStreamOutTegra::write(const void* buffer, size_t bytes) |
| { |
| status_t status; |
| if (!mHardware) { |
| LOGE("%s: mHardware is null", __FUNCTION__); |
| return NO_INIT; |
| } |
| // LOGD("AudioStreamOutTegra::write(%p, %u) TID %d", buffer, bytes, gettid()); |
| // Protect output state during the write process. |
| |
| if (mSleepReq) { |
| // sleep a few milliseconds so that the processor can be given to the thread attempting to |
| // lock mLock before we sleep with mLock held while writing below |
| usleep(FORCED_SLEEP_TIME_US); |
| } |
| |
| bool needsOnline = false; |
| if (mState < AUDIO_STREAM_CONFIGURED) { |
| mHardware->mLock.lock(); |
| if (mState < AUDIO_STREAM_CONFIGURED) { |
| needsOnline = true; |
| } else { |
| mHardware->mLock.unlock(); |
| } |
| } |
| |
| { // scope for the lock |
| Mutex::Autolock lock(mLock); |
| |
| ssize_t written = 0; |
| const uint8_t* p = static_cast<const uint8_t*>(buffer); |
| size_t outsize = bytes; |
| int outFd = mFd; |
| bool stereo; |
| ssize_t writtenToSpdif = 0; |
| |
| if (needsOnline) { |
| status = online_l(); |
| mHardware->mLock.unlock(); |
| if (status < 0) { |
| goto error; |
| } |
| } |
| stereo = mIsBtEnabled ? false : (channels() == AudioSystem::CHANNEL_OUT_STEREO); |
| |
| #ifdef USE_PROPRIETARY_AUDIO_EXTENSIONS |
| // Do Multimedia processing if appropriate for device and usecase. |
| mHardware->mAudioPP.doMmProcessing((void *)buffer, bytes / frameSize()); |
| #endif |
| |
| if (mIsSpkrEnabled && mIsBtEnabled) { |
| // When dual routing to CPCAP and Bluetooth, piggyback CPCAP audio now, |
| // and then down convert for the BT. |
| // CPCAP is always 44.1 in this case. |
| // This also works in the three-way routing case. |
| Mutex::Autolock lock2(mFdLock); |
| ::write(outFd, buffer, outsize); |
| } |
| if (mIsSpdifEnabled) { |
| // When dual routing to Speaker and HDMI, piggyback HDMI now, since it |
| // has no mic we'll leave the rest of the acoustic processing for the |
| // CPCAP hardware path. |
| // This also works in the three-way routing case, except the acoustic |
| // tuning will be done on Bluetooth, since it has the exclusive mic amd |
| // it also needs the sample rate conversion |
| Mutex::Autolock lock2(mFdLock); |
| if (mSpdifFd >= 0) { |
| writtenToSpdif = ::write(mSpdifFd, buffer, outsize); |
| LOGV("%s: written %d bytes to SPDIF", __FUNCTION__, (int)writtenToSpdif); |
| } else { |
| LOGW("s/pdif enabled but unavailable"); |
| } |
| } |
| if (mIsBtEnabled) { |
| outFd = mBtFd; |
| } else if (mIsSpdifEnabled && !mIsSpkrEnabled) { |
| outFd = -1; |
| } |
| |
| #ifdef USE_PROPRIETARY_AUDIO_EXTENSIONS |
| // Check if sample rate conversion or ECNS are required. |
| // Caution: Upconversion (from 44.1 to 48) would require a new output buffer larger than the |
| // original one. |
| if (mDriverRate != (int)sampleRate()) { |
| if (!mSrc.initted() || |
| mSrc.inRate() != (int)sampleRate() || |
| mSrc.outRate() != mDriverRate) { |
| LOGD("%s: downconvert started from %d to %d",__FUNCTION__, |
| sampleRate(), mDriverRate); |
| mSrc.init(sampleRate(), mDriverRate); |
| if (!mSrc.initted()) { |
| status = -1; |
| goto error; |
| } |
| // Workaround to give multiple of 4 bytes to driver: Keep one sample |
| // buffered in case SRC returns an odd number of samples. |
| mHaveSpareSample = false; |
| } |
| } else { |
| mSrc.deinit(); |
| } |
| |
| if (mHardware->mAudioPP.isEcEnabled() || mSrc.initted()) |
| { |
| // cut audio down to Mono for SRC or ECNS |
| if (channels() == AudioSystem::CHANNEL_OUT_STEREO) |
| { |
| // Do stereo-to-mono downmix before SRC, in-place |
| int16_t *destBuf = (int16_t *) buffer; |
| for (int i = 0; i < (int)bytes/2; i++) { |
| destBuf[i] = (destBuf[i*2]>>1) + (destBuf[i*2+1]>>1); |
| } |
| outsize >>= 1; |
| } |
| } |
| |
| if (mSrc.initted()) { |
| // Apply the sample rate conversion. |
| mSrc.mIoData.in_buf_ch1 = (SRC16 *) (buffer); |
| mSrc.mIoData.in_buf_ch2 = 0; |
| mSrc.mIoData.input_count = outsize / sizeof(SRC16); |
| mSrc.mIoData.out_buf_ch1 = (SRC16 *) (buffer); |
| mSrc.mIoData.out_buf_ch2 = 0; |
| mSrc.mIoData.output_count = outsize / sizeof(SRC16); |
| if (mHaveSpareSample) { |
| // Leave room for placing the spare. |
| mSrc.mIoData.out_buf_ch1++; |
| mSrc.mIoData.output_count--; |
| } |
| mSrc.srcConvert(); |
| LOGV("Converted %d bytes at %d to %d bytes at %d", |
| outsize, sampleRate(), mSrc.mIoData.output_count*2, mDriverRate); |
| if (mHaveSpareSample) { |
| int16_t *bufp = (int16_t*)buffer; |
| bufp[0]=mSpareSample; |
| mSrc.mIoData.output_count++; |
| mHaveSpareSample = false; |
| } |
| outsize = mSrc.mIoData.output_count*2; |
| LOGV("Outsize is now %d", outsize); |
| } |
| if (mHardware->mAudioPP.isEcEnabled()) { |
| // EC/NS is a blocking interface, to synchronise with read. |
| // It also consumes data when EC/NS is running. |
| // It expects MONO data. |
| // If EC/NS is not running, it will return 0, and we need to write this data to the |
| // driver ourselves. |
| |
| // Indicate that it is safe to call setDriver_l() without locking mLock: if the input |
| // stream is started, doRouting_l() will not block when setDriver_l() is called. |
| mLocked = true; |
| LOGV("writeDownlinkEcns size %d", outsize); |
| written = mHardware->mAudioPP.writeDownlinkEcns(outFd,(void *)buffer, |
| stereo, outsize, &mFdLock); |
| mLocked = false; |
| } |
| if (mHardware->mAudioPP.isEcEnabled() || mSrc.initted()) { |
| // Move audio back up to Stereo, if the EC/NS wasn't in fact running and we're |
| // writing to a stereo device. |
| if (stereo && |
| written != (ssize_t)outsize) { |
| // Back up to stereo, in place. |
| int16_t *destBuf = (int16_t *) buffer; |
| for (int i = outsize/2-1; i >= 0; i--) { |
| destBuf[i*2] = destBuf[i]; |
| destBuf[i*2+1] = destBuf[i]; |
| } |
| outsize <<= 1; |
| } |
| } |
| #endif |
| |
| if (written != (ssize_t)outsize) { |
| // The sample rate conversion modifies the output size. |
| if (outsize&0x3) { |
| int16_t* bufp = (int16_t *)buffer; |
| // LOGV("Keep the spare sample away from the driver."); |
| mHaveSpareSample = true; |
| mSpareSample = bufp[outsize/2 - 1]; |
| } |
| |
| if (outFd >= 0) { |
| Mutex::Autolock lock2(mFdLock); |
| written = ::write(outFd, buffer, outsize&(~0x3)); |
| if (written != ((ssize_t)outsize&(~0x3))) { |
| status = written; |
| goto error; |
| } |
| } else { |
| written = writtenToSpdif; |
| } |
| } |
| if (written < 0) { |
| LOGE("Error writing %d bytes to output: %s", outsize, strerror(errno)); |
| status = written; |
| goto error; |
| } |
| |
| // Sample rate converter may be stashing a couple of bytes here or there, |
| // so just report that all bytes were consumed. (it would be a bug not to.) |
| LOGV("write() written %d", bytes); |
| return bytes; |
| |
| } |
| error: |
| LOGE("write(): error, return %d", status); |
| standby(); |
| usleep(bytes * 1000 / frameSize() / sampleRate() * 1000); |
| |
| return status; |
| } |
| |
| void AudioHardware::AudioStreamOutTegra::flush() |
| { |
| // Prevent someone from writing the fd while we flush |
| Mutex::Autolock lock(mFdLock); |
| flush_l(); |
| } |
| |
| void AudioHardware::AudioStreamOutTegra::flush_l() |
| { |
| LOGV("AudioStreamOutTegra::flush()"); |
| if (::ioctl(mFdCtl, TEGRA_AUDIO_OUT_FLUSH) < 0) |
| LOGE("could not flush playback: %s", strerror(errno)); |
| if (::ioctl(mBtFdCtl, TEGRA_AUDIO_OUT_FLUSH) < 0) |
| LOGE("could not flush bluetooth: %s", strerror(errno)); |
| if (mSpdifFdCtl >= 0 && ::ioctl(mSpdifFdCtl, TEGRA_AUDIO_OUT_FLUSH) < 0) |
| LOGE("could not flush spdif: %s", strerror(errno)); |
| LOGV("AudioStreamOutTegra::flush() returns"); |
| } |
| |
| // FIXME: this is a workaround for issue 3387419 with impact on latency |
| // to be removed when root cause is fixed |
| void AudioHardware::AudioStreamOutTegra::setNumBufs(int numBufs) |
| { |
| Mutex::Autolock lock(mFdLock); |
| LOGV("AudioStreamOutTegra::setNumBufs(%d)", numBufs); |
| if (::ioctl(mFdCtl, TEGRA_AUDIO_OUT_SET_NUM_BUFS, &numBufs) < 0) |
| LOGE("could not set number of output buffers: %s", strerror(errno)); |
| } |
| |
| // Called with mLock and mHardware->mLock held |
| status_t AudioHardware::AudioStreamOutTegra::online_l() |
| { |
| status_t status = NO_ERROR; |
| |
| if (mState < AUDIO_STREAM_NEW_RATE_REQ) { |
| if (mState == AUDIO_STREAM_IDLE) { |
| LOGV("output %p going online", this); |
| mState = AUDIO_STREAM_CONFIG_REQ; |
| // update EC state if necessary |
| if (mHardware->getActiveInput_l() && mHardware->isEcRequested()) { |
| // doRouting_l() will not try to lock mLock when calling setDriver_l() |
| mLocked = true; |
| mHardware->doRouting_l(); |
| mLocked = false; |
| } |
| } |
| |
| // If there's no hardware speaker, leave the HW alone. (i.e. SCO/SPDIF is on) |
| if (mIsSpkrEnabledReq) { |
| status = mHardware->doStandby(mFdCtl, true, false); // output, online |
| } else { |
| status = mHardware->doStandby(mFdCtl, true, true); // output, standby |
| } |
| mIsSpkrEnabled = mIsSpkrEnabledReq; |
| |
| mIsBtEnabled = mIsBtEnabledReq; |
| mIsSpdifEnabled = mIsSpdifEnabledReq; |
| |
| } |
| |
| // Flush old data (wrong rate) from I2S driver before changing rate. |
| flush(); |
| if (mHardware->mEcnsEnabled) { |
| setNumBufs(AUDIO_HW_NUM_OUT_BUF); |
| } else { |
| setNumBufs(AUDIO_HW_NUM_OUT_BUF_LONG); |
| } |
| int speaker_rate = mHardware->mHwOutRate; |
| if (mIsBtEnabled) { |
| speaker_rate = AUDIO_HW_OUT_SAMPLERATE; |
| } |
| // Now the DMA is empty, change the rate. |
| if (::ioctl(mHardware->mCpcapCtlFd, CPCAP_AUDIO_OUT_SET_RATE, |
| speaker_rate) < 0) |
| LOGE("could not set output rate(%d): %s", |
| speaker_rate, strerror(errno)); |
| |
| mDriverRate = mHardware->mHwOutRate; |
| |
| // If EC is on, pre load one DMA buffer with 20ms of silence to limit underruns |
| if (mHardware->mEcnsEnabled) { |
| int fd = -1; |
| if (mIsBtEnabled) { |
| fd = mBtFd; |
| } else if (mIsSpkrEnabled) { |
| fd = mFd; |
| } |
| if (fd >= 0) { |
| size_t bufSize = (mDriverRate * 2 /* stereo */ * sizeof(int16_t))/ 50; |
| char buf[bufSize]; |
| memset(buf, 0, bufSize); |
| Mutex::Autolock lock2(mFdLock); |
| ::write(fd, buf, bufSize); |
| } |
| } |
| |
| mState = AUDIO_STREAM_CONFIGURED; |
| |
| return status; |
| } |
| |
| status_t AudioHardware::AudioStreamOutTegra::standby() |
| { |
| if (!mHardware) { |
| return NO_INIT; |
| } |
| |
| status_t status = NO_ERROR; |
| Mutex::Autolock lock(mHardware->mLock); |
| Mutex::Autolock lock2(mLock); |
| |
| if (mState != AUDIO_STREAM_IDLE) { |
| LOGV("output %p going into standby", this); |
| mState = AUDIO_STREAM_IDLE; |
| |
| // update EC state if necessary |
| if (mHardware->getActiveInput_l() && mHardware->isEcRequested()) { |
| // doRouting_l will not try to lock mLock when calling setDriver_l() |
| mLocked = true; |
| mHardware->doRouting_l(); |
| mLocked = false; |
| } |
| |
| #ifdef USE_PROPRIETARY_AUDIO_EXTENSIONS |
| // Prevent EC/NS from writing to the file anymore. |
| mHardware->mAudioPP.writeDownlinkEcns(-1,0,false,0,&mFdLock); |
| #endif |
| if (mIsSpkrEnabled) { |
| // doStandby() calls flush() which also handles the case where multiple devices |
| // including bluetooth or SPDIF are selected |
| status = mHardware->doStandby(mFdCtl, true, true); // output, standby |
| } else if (mIsBtEnabled || mIsSpdifEnabled) { |
| flush(); |
| } |
| } |
| |
| return status; |
| } |
| |
| status_t AudioHardware::AudioStreamOutTegra::dump(int fd, const Vector<String16>& args) |
| { |
| const size_t SIZE = 256; |
| char buffer[SIZE]; |
| String8 result; |
| result.append("AudioStreamOutTegra::dump\n"); |
| snprintf(buffer, SIZE, "\tsample rate: %d\n", sampleRate()); |
| result.append(buffer); |
| snprintf(buffer, SIZE, "\tbuffer size: %d\n", bufferSize()); |
| result.append(buffer); |
| snprintf(buffer, SIZE, "\tchannels: %d\n", channels()); |
| result.append(buffer); |
| snprintf(buffer, SIZE, "\tformat: %d\n", format()); |
| result.append(buffer); |
| snprintf(buffer, SIZE, "\tmHardware: %p\n", mHardware); |
| result.append(buffer); |
| snprintf(buffer, SIZE, "\tmFd: %d\n", mFd); |
| result.append(buffer); |
| snprintf(buffer, SIZE, "\tmStartCount: %d\n", mStartCount); |
| result.append(buffer); |
| snprintf(buffer, SIZE, "\tmRetryCount: %d\n", mRetryCount); |
| result.append(buffer); |
| if (mHardware) |
| snprintf(buffer, SIZE, "\tmStandby: %s\n", |
| mHardware->mCurOutDevice.on ? "false": "true"); |
| else |
| snprintf(buffer, SIZE, "\tmStandby: unknown\n"); |
| |
| result.append(buffer); |
| ::write(fd, result.string(), result.size()); |
| return NO_ERROR; |
| } |
| |
| bool AudioHardware::AudioStreamOutTegra::getStandby() |
| { |
| return mState == AUDIO_STREAM_IDLE;; |
| } |
| |
| status_t AudioHardware::AudioStreamOutTegra::setParameters(const String8& keyValuePairs) |
| { |
| AudioParameter param = AudioParameter(keyValuePairs); |
| String8 key = String8(AudioParameter::keyRouting); |
| status_t status = NO_ERROR; |
| int device; |
| LOGV("AudioStreamOutTegra::setParameters() %s", keyValuePairs.string()); |
| |
| if (param.getInt(key, device) == NO_ERROR) { |
| if (device != 0) { |
| mDevices = device; |
| LOGV("set output routing %x", mDevices); |
| status = mHardware->doRouting(); |
| } |
| param.remove(key); |
| } |
| |
| if (param.size()) { |
| status = BAD_VALUE; |
| } |
| return status; |
| } |
| |
| String8 AudioHardware::AudioStreamOutTegra::getParameters(const String8& keys) |
| { |
| AudioParameter param = AudioParameter(keys); |
| String8 value; |
| String8 key = String8(AudioParameter::keyRouting); |
| |
| if (param.get(key, value) == NO_ERROR) { |
| LOGV("get routing %x", mDevices); |
| param.addInt(key, (int)mDevices); |
| } |
| |
| LOGV("AudioStreamOutTegra::getParameters() %s", param.toString().string()); |
| return param.toString(); |
| } |
| |
| status_t AudioHardware::AudioStreamOutTegra::getRenderPosition(uint32_t *dspFrames) |
| { |
| //TODO: enable when supported by driver |
| return INVALID_OPERATION; |
| } |
| |
| // ---------------------------------------------------------------------------- |
| |
| // always succeeds, must call set() immediately after |
| AudioHardware::AudioStreamInTegra::AudioStreamInTegra() : |
| mHardware(0), mFd(-1), mFdCtl(-1), mState(AUDIO_STREAM_IDLE), mRetryCount(0), |
| mFormat(AUDIO_HW_IN_FORMAT), mChannels(AUDIO_HW_IN_CHANNELS), |
| mSampleRate(AUDIO_HW_IN_SAMPLERATE), mBufferSize(AUDIO_HW_IN_BUFFERSIZE), |
| mAcoustics((AudioSystem::audio_in_acoustics)0), mDevices(0), |
| mIsMicEnabled(0), mIsBtEnabled(0), |
| mSource(AUDIO_SOURCE_DEFAULT), mLocked(false), mTotalBuffersRead(0), |
| mDriverRate(AUDIO_HW_IN_SAMPLERATE), mEcnsRequested(0) |
| { |
| LOGV("AudioStreamInTegra constructor"); |
| } |
| |
| // serves a similar purpose as init() |
| status_t AudioHardware::AudioStreamInTegra::set( |
| AudioHardware* hw, uint32_t devices, int *pFormat, uint32_t *pChannels, uint32_t *pRate, |
| AudioSystem::audio_in_acoustics acoustic_flags) |
| { |
| Mutex::Autolock lock(mLock); |
| status_t status = BAD_VALUE; |
| mHardware = hw; |
| if (pFormat == 0) |
| return status; |
| if (*pFormat != AUDIO_HW_IN_FORMAT) { |
| LOGE("wrong in format %d, expecting %lld", *pFormat, AUDIO_HW_IN_FORMAT); |
| *pFormat = AUDIO_HW_IN_FORMAT; |
| return status; |
| } |
| |
| if (pRate == 0) |
| return status; |
| |
| uint32_t rate = hw->getInputSampleRate(*pRate); |
| if (rate != *pRate) { |
| LOGE("wrong sample rate %d, expecting %d", *pRate, rate); |
| *pRate = rate; |
| return status; |
| } |
| |
| if (pChannels == 0) |
| return status; |
| |
| if (*pChannels != AudioSystem::CHANNEL_IN_MONO && |
| *pChannels != AudioSystem::CHANNEL_IN_STEREO) { |
| LOGE("wrong number of channels %d", *pChannels); |
| *pChannels = AUDIO_HW_IN_CHANNELS; |
| return status; |
| } |
| |
| LOGV("AudioStreamInTegra::set(%d, %d, %u)", *pFormat, *pChannels, *pRate); |
| |
| mDevices = devices; |
| mFormat = AUDIO_HW_IN_FORMAT; |
| mChannels = *pChannels; |
| mSampleRate = *pRate; |
| mBufferSize = mHardware->getInputBufferSize(mSampleRate, AudioSystem::PCM_16_BIT, |
| AudioSystem::popCount(mChannels)); |
| return NO_ERROR; |
| } |
| |
| AudioHardware::AudioStreamInTegra::~AudioStreamInTegra() |
| { |
| LOGV("AudioStreamInTegra destructor"); |
| |
| standby(); |
| |
| } |
| |
| // Called with mHardware->mLock and mLock held. |
| void AudioHardware::AudioStreamInTegra::setDriver_l(bool mic, bool bluetooth, int sampleRate) |
| { |
| LOGV("In setDriver_l() Analog mic? %s. Bluetooth? %s.", mic?"yes":"no", bluetooth?"yes":"no"); |
| |
| // force some reconfiguration at next read() |
| // Note: mState always == AUDIO_STREAM_CONFIGURED when setDriver_l() is called on an input |
| if (mic != mIsMicEnabled || bluetooth != mIsBtEnabled) { |
| mState = AUDIO_STREAM_CONFIG_REQ; |
| } else if (sampleRate != mDriverRate) { |
| mState = AUDIO_STREAM_NEW_RATE_REQ; |
| } |
| |
| mIsMicEnabled = mic; |
| mIsBtEnabled = bluetooth; |
| |
| } |
| |
| ssize_t AudioHardware::AudioStreamInTegra::read(void* buffer, ssize_t bytes) |
| { |
| status_t status; |
| if (!mHardware) { |
| LOGE("%s: mHardware is null", __FUNCTION__); |
| return NO_INIT; |
| } |
| // |
| LOGV("AudioStreamInTegra::read(%p, %ld) TID %d", buffer, bytes, gettid()); |
| |
| if (mSleepReq) { |
| // sleep a few milliseconds so that the processor can be given to the thread attempting to |
| // lock mLock before we sleep with mLock held while reading below |
| usleep(FORCED_SLEEP_TIME_US); |
| } |
| |
| bool needsOnline = false; |
| if (mState < AUDIO_STREAM_CONFIGURED) { |
| mHardware->mLock.lock(); |
| if (mState < AUDIO_STREAM_CONFIGURED) { |
| needsOnline = true; |
| } else { |
| mHardware->mLock.unlock(); |
| } |
| } |
| |
| { // scope for mLock |
| Mutex::Autolock lock(mLock); |
| |
| ssize_t ret; |
| bool srcReqd; |
| int hwReadBytes; |
| int16_t * inbuf; |
| |
| if (needsOnline) { |
| status = online_l(); |
| mHardware->mLock.unlock(); |
| if (status != NO_ERROR) { |
| LOGE("%s: Problem switching to online.",__FUNCTION__); |
| goto error; |
| } |
| } |
| |
| srcReqd = (mDriverRate != (int)mSampleRate); |
| |
| #ifdef USE_PROPRIETARY_AUDIO_EXTENSIONS |
| if (srcReqd) { |
| hwReadBytes = ( bytes*mDriverRate/mSampleRate ) & (~0x7); |
| LOGV("Running capture SRC. HW=%d bytes at %d, Flinger=%d bytes at %d", |
| hwReadBytes, mDriverRate, (int)bytes, mSampleRate); |
| inbuf = mInScratch; |
| if ((size_t)bytes > sizeof(mInScratch)) { |
| LOGE("read: buf size problem. %d>%d",(int)bytes,sizeof(mInScratch)); |
| status = BAD_VALUE; |
| goto error; |
| } |
| // Check if we need to init the rate converter |
| if (!mSrc.initted() || |
| mSrc.inRate() != mDriverRate || |
| mSrc.outRate() != (int)mSampleRate) { |
| LOGD ("%s: Upconvert started from %d to %d", __FUNCTION__, |
| mDriverRate, mSampleRate); |
| mSrc.init(mDriverRate, mSampleRate); |
| if (!mSrc.initted()) { |
| status = NO_INIT; |
| goto error; |
| } |
| } |
| } else { |
| hwReadBytes = bytes; |
| inbuf = (int16_t *)buffer; |
| mSrc.deinit(); |
| } |
| // Read from driver, or ECNS thread, as appropriate. |
| { |
| Mutex::Autolock dfl(mFdLock); |
| ret = mHardware->mAudioPP.read(mFd, inbuf, hwReadBytes, mDriverRate); |
| } |
| if (ret>0 && srcReqd) { |
| mSrc.mIoData.in_buf_ch1 = (SRC16 *) (inbuf); |
| mSrc.mIoData.in_buf_ch2 = 0; |
| mSrc.mIoData.input_count = hwReadBytes / sizeof(SRC16); |
| mSrc.mIoData.out_buf_ch1 = (SRC16 *) (buffer); |
| mSrc.mIoData.out_buf_ch2 = 0; |
| mSrc.mIoData.output_count = bytes/sizeof(SRC16); |
| mSrc.srcConvert(); |
| ret = mSrc.mIoData.output_count*sizeof(SRC16); |
| if (ret > bytes) { |
| LOGE("read: buffer overrun"); |
| } |
| } |
| #else |
| if (srcReqd) { |
| LOGE("%s: sample rate mismatch HAL %d, driver %d", |
| __FUNCTION__, mSampleRate, mDriverRate); |
| status = INVALID_OPERATION; |
| goto error; |
| } |
| { |
| Mutex::Autolock dfl(mFdLock); |
| ret = ::read(mFd, buffer, bytes); |
| } |
| #endif |
| |
| // It is not optimal to mute after all the above processing but it is necessary to |
| // keep the clock sync from input device. It also avoids glitches on output streams due |
| // to EC being turned on and off |
| bool muted; |
| mHardware->getMicMute(&muted); |
| if (muted) { |
| LOGV("%s muted",__FUNCTION__); |
| memset(buffer, 0, bytes); |
| } |
| |
| LOGV("%s returns %d.",__FUNCTION__, (int)ret); |
| if (ret < 0) { |
| status = ret; |
| goto error; |
| } |
| |
| { |
| Mutex::Autolock _fl(mFramesLock); |
| mTotalBuffersRead++; |
| } |
| return ret; |
| } |
| |
| error: |
| LOGE("read(): error, return %d", status); |
| standby(); |
| usleep(bytes * 1000 / frameSize() / sampleRate() * 1000); |
| return status; |
| } |
| |
| bool AudioHardware::AudioStreamInTegra::getStandby() const |
| { |
| return mState == AUDIO_STREAM_IDLE; |
| } |
| |
| status_t AudioHardware::AudioStreamInTegra::standby() |
| { |
| if (!mHardware) { |
| return NO_INIT; |
| } |
| |
| Mutex::Autolock lock(mHardware->mLock); |
| Mutex::Autolock lock2(mLock); |
| status_t status = NO_ERROR; |
| if (mState != AUDIO_STREAM_IDLE) { |
| LOGV("input %p going into standby", this); |
| mState = AUDIO_STREAM_IDLE; |
| // stopping capture now so that the input stream state (AUDIO_STREAM_IDLE) |
| // is consistent with the driver state when doRouting_l() is executed. |
| // Not doing so makes that I2S reconfiguration fails when switching from |
| // BT SCO to built-in mic. |
| stop_l(); |
| // reset global pre processing state before disabling the input |
| mHardware->setEcnsRequested_l(PREPROC_AEC|PREPROC_NS, false); |
| // setDriver_l() will not try to lock mLock when called by doRouting_l() |
| mLocked = true; |
| mHardware->doRouting_l(); |
| mLocked = false; |
| status = mHardware->doStandby(mFdCtl, false, true); // input, standby |
| if (mFd >= 0) { |
| ::close(mFd); |
| mFd = -1; |
| } |
| if (mFdCtl >= 0) { |
| ::close(mFdCtl); |
| mFdCtl = -1; |
| } |
| } |
| |
| return status; |
| } |
| |
| // Called with mLock and mHardware->mLock held |
| status_t AudioHardware::AudioStreamInTegra::online_l() |
| { |
| status_t status = NO_ERROR; |
| |
| reopenReconfigDriver(); |
| |
| if (mState < AUDIO_STREAM_NEW_RATE_REQ) { |
| |
| // Use standby to flush the driver. mHardware->mLock should already be held |
| |
| mHardware->doStandby(mFdCtl, false, true); |
| if (mDevices & ~AudioSystem::DEVICE_IN_BLUETOOTH_SCO_HEADSET) { |
| status = mHardware->doStandby(mFdCtl, false, false); |
| } |
| |
| if (mState == AUDIO_STREAM_IDLE) { |
| mState = AUDIO_STREAM_CONFIG_REQ; |
| LOGV("input %p going online", this); |
| // apply pre processing requested for this input |
| mHardware->setEcnsRequested_l(mEcnsRequested, true); |
| // setDriver_l() will not try to lock mLock when called by doRouting_l() |
| mLocked = true; |
| mHardware->doRouting_l(); |
| mLocked = false; |
| { |
| Mutex::Autolock _fl(mFramesLock); |
| mTotalBuffersRead = 0; |
| mStartTimeNs = systemTime(); |
| } |
| } |
| |
| // configuration |
| struct tegra_audio_in_config config; |
| status = ::ioctl(mFdCtl, TEGRA_AUDIO_IN_GET_CONFIG, &config); |
| if (status < 0) { |
| LOGE("cannot read input config: %s", strerror(errno)); |
| return status; |
| } |
| config.stereo = AudioSystem::popCount(mChannels) == 2; |
| config.rate = mHardware->mHwInRate; |
| status = ::ioctl(mFdCtl, TEGRA_AUDIO_IN_SET_CONFIG, &config); |
| |
| if (status < 0) { |
| LOGE("cannot set input config: %s", strerror(errno)); |
| if (::ioctl(mFdCtl, TEGRA_AUDIO_IN_GET_CONFIG, &config) == 0) { |
| if (config.stereo) { |
| mChannels = AudioSystem::CHANNEL_IN_STEREO; |
| } else { |
| mChannels = AudioSystem::CHANNEL_IN_MONO; |
| } |
| } |
| } |
| |
| |
| } |
| |
| mDriverRate = mHardware->mHwInRate; |
| |
| if (::ioctl(mHardware->mCpcapCtlFd, CPCAP_AUDIO_IN_SET_RATE, |
| mDriverRate) < 0) |
| LOGE("could not set input rate(%d): %s", mDriverRate, strerror(errno)); |
| |
| mState = AUDIO_STREAM_CONFIGURED; |
| |
| return status; |
| } |
| |
| // serves a similar purpose as the init() method of other classes |
| void AudioHardware::AudioStreamInTegra::reopenReconfigDriver() |
| { |
| #ifdef USE_PROPRIETARY_AUDIO_EXTENSIONS |
| if (mHardware->mEcnsEnabled) { |
| mHardware->mAudioPP.enableEcns(0); |
| mHardware->mAudioPP.enableEcns(mHardware->mEcnsEnabled); |
| } |
| #endif |
| // Need to "restart" the driver when changing the buffer configuration. |
| if (mFdCtl >= 0 && ::ioctl(mFdCtl, TEGRA_AUDIO_IN_STOP) < 0) { |
| LOGE("%s: could not stop recording: %s", __FUNCTION__, strerror(errno)); |
| } |
| if (mFd >= 0) { |
| ::close(mFd); |
| mFd = -1; |
| } |
| if (mFdCtl >= 0) { |
| ::close(mFdCtl); |
| mFdCtl = -1; |
| } |
| |
| // This does not have a retry loop to avoid blocking if another record session already in progress |
| mFd = ::open("/dev/audio1_in", O_RDWR); |
| if (mFd < 0) { |
| LOGE("open /dev/audio1_in failed: %s", strerror(errno)); |
| } |
| mFdCtl = ::open("/dev/audio1_in_ctl", O_RDWR); |
| if (mFdCtl < 0) { |
| LOGE("open /dev/audio1_in_ctl failed: %s", strerror(errno)); |
| if (mFd >= 0) { |
| ::close(mFd); |
| mFd = -1; |
| } |
| } else { |
| // here we would set mInit = true; |
| } |
| } |
| |
| |
| status_t AudioHardware::AudioStreamInTegra::dump(int fd, const Vector<String16>& args) |
| { |
| const size_t SIZE = 256; |
| char buffer[SIZE]; |
| String8 result; |
| result.append("AudioStreamInTegra::dump\n"); |
| snprintf(buffer, SIZE, "\tsample rate: %d\n", sampleRate()); |
| result.append(buffer); |
| snprintf(buffer, SIZE, "\tbuffer size: %d\n", bufferSize()); |
| result.append(buffer); |
| snprintf(buffer, SIZE, "\tchannels: %d\n", channels()); |
| result.append(buffer); |
| snprintf(buffer, SIZE, "\tformat: %d\n", format()); |
| result.append(buffer); |
| snprintf(buffer, SIZE, "\tmHardware: %p\n", mHardware); |
| result.append(buffer); |
| snprintf(buffer, SIZE, "\tmFd count: %d\n", mFd); |
| result.append(buffer); |
| snprintf(buffer, SIZE, "\tmState: %d\n", mState); |
| result.append(buffer); |
| snprintf(buffer, SIZE, "\tmRetryCount: %d\n", mRetryCount); |
| result.append(buffer); |
| ::write(fd, result.string(), result.size()); |
| return NO_ERROR; |
| } |
| |
| status_t AudioHardware::AudioStreamInTegra::setParameters(const String8& keyValuePairs) |
| { |
| AudioParameter param = AudioParameter(keyValuePairs); |
| String8 key = String8(AudioParameter::keyRouting); |
| status_t status = NO_ERROR; |
| int device; |
| int source; |
| LOGV("AudioStreamInTegra::setParameters() %s", keyValuePairs.string()); |
| |
| // read source before device so that it is upto date when doRouting() is called |
| if (param.getInt(String8(AudioParameter::keyInputSource), source) == NO_ERROR) { |
| mSource = source; |
| param.remove(String8(AudioParameter::keyInputSource)); |
| } |
| |
| if (param.getInt(key, device) == NO_ERROR) { |
| LOGV("set input routing %x", device); |
| if (device & (device - 1)) { |
| status = BAD_VALUE; |
| } else { |
| mDevices = device; |
| if (!getStandby() && device != 0) { |
| status = mHardware->doRouting(); |
| } |
| } |
| param.remove(key); |
| } |
| |
| if (param.size()) { |
| status = BAD_VALUE; |
| } |
| return status; |
| } |
| |
| String8 AudioHardware::AudioStreamInTegra::getParameters(const String8& keys) |
| { |
| AudioParameter param = AudioParameter(keys); |
| String8 value; |
| String8 key = String8(AudioParameter::keyRouting); |
| |
| if (param.get(key, value) == NO_ERROR) { |
| LOGV("get routing %x", mDevices); |
| param.addInt(key, (int)mDevices); |
| } |
| |
| LOGV("AudioStreamInTegra::getParameters() %s", param.toString().string()); |
| return param.toString(); |
| } |
| |
| unsigned int AudioHardware::AudioStreamInTegra::getInputFramesLost() const |
| { |
| Mutex::Autolock _l(mFramesLock); |
| unsigned int lostFrames = 0; |
| if (!getStandby()) { |
| unsigned int framesPerBuffer = bufferSize() / frameSize(); |
| uint64_t expectedFrames = ((systemTime() - mStartTimeNs) * mSampleRate) / 1000000000; |
| expectedFrames = (expectedFrames / framesPerBuffer) * framesPerBuffer; |
| uint64_t actualFrames = (uint64_t)mTotalBuffersRead * framesPerBuffer; |
| if (expectedFrames > actualFrames) { |
| lostFrames = (unsigned int)(expectedFrames - actualFrames); |
| LOGW("getInputFramesLost() expected %d actual %d lost %d", |
| (unsigned int)expectedFrames, (unsigned int)actualFrames, lostFrames); |
| } |
| } |
| |
| mTotalBuffersRead = 0; |
| mStartTimeNs = systemTime(); |
| |
| return lostFrames; |
| } |
| |
| // must be called with mLock and mFdLock held |
| void AudioHardware::AudioStreamInTegra::stop_l() |
| { |
| LOGV("AudioStreamInTegra::stop_l() starts"); |
| if (::ioctl(mFdCtl, TEGRA_AUDIO_IN_STOP) < 0) { |
| LOGE("could not stop recording: %d %s", errno, strerror(errno)); |
| } |
| LOGV("AudioStreamInTegra::stop_l() returns"); |
| } |
| |
| void AudioHardware::AudioStreamInTegra::updateEcnsRequested(effect_handle_t effect, bool enabled) |
| { |
| #ifdef USE_PROPRIETARY_AUDIO_EXTENSIONS |
| effect_descriptor_t desc; |
| status_t status = (*effect)->get_descriptor(effect, &desc); |
| if (status == 0) { |
| int ecns = 0; |
| if (memcmp(&desc.type, FX_IID_AEC, sizeof(effect_uuid_t)) == 0) { |
| ecns = PREPROC_AEC; |
| } else if (memcmp(&desc.type, FX_IID_NS, sizeof(effect_uuid_t)) == 0) { |
| ecns = PREPROC_NS; |
| } |
| LOGV("AudioStreamInTegra::updateEcnsRequested() %s effect %s", |
| enabled ? "enabling" : "disabling", desc.name); |
| if (enabled) { |
| mEcnsRequested |= ecns; |
| } else { |
| mEcnsRequested &= ~ecns; |
| } |
| standby(); |
| } |
| #endif |
| } |
| |
| status_t AudioHardware::AudioStreamInTegra::addAudioEffect(effect_handle_t effect) |
| { |
| updateEcnsRequested(effect, true); |
| return NO_ERROR; |
| } |
| |
| status_t AudioHardware::AudioStreamInTegra::removeAudioEffect(effect_handle_t effect) |
| { |
| updateEcnsRequested(effect, false); |
| return NO_ERROR; |
| } |
| |
| // ---------------------------------------------------------------------------- |
| |
| extern "C" AudioHardwareInterface* createAudioHardware(void) { |
| AudioHardware *hw = new AudioHardware(); |
| for (unsigned tries = 0; tries < MAX_INIT_TRIES; ++tries) { |
| if (NO_ERROR == hw->init()) |
| break; |
| LOGW("AudioHardware::init failed soft, retrying"); |
| sleep(1); |
| } |
| if (NO_ERROR != hw->initCheck()) { |
| LOGE("AudioHardware::init failed hard"); |
| delete hw; |
| hw = NULL; |
| } |
| return hw; |
| } |
| |
| }; // namespace android |