/* * Copyright (C) 2008 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 "A2dpAudioInterface" #include <utils/Log.h> #include <utils/String8.h> #include <utils/Timers.h> #include "A2dpAudioInterface.h" #include "audio/liba2dp.h" #include <hardware_legacy/power.h> namespace android_audio_legacy { static const char *sA2dpWakeLock = "A2dpOutputStream"; #define MAX_WRITE_RETRIES 5 // ---------------------------------------------------------------------------- //AudioHardwareInterface* A2dpAudioInterface::createA2dpInterface() //{ // AudioHardwareInterface* hw = 0; // // hw = AudioHardwareInterface::create(); // ALOGD("new A2dpAudioInterface(hw: %p)", hw); // hw = new A2dpAudioInterface(hw); // return hw; //} A2dpAudioInterface::A2dpAudioInterface(AudioHardwareInterface* hw) : mOutput(0), mHardwareInterface(hw), mBluetoothEnabled(true), mSuspended(false) { } A2dpAudioInterface::~A2dpAudioInterface() { closeOutputStream((AudioStreamOut *)mOutput); delete mHardwareInterface; } status_t A2dpAudioInterface::initCheck() { if (mHardwareInterface == 0) return NO_INIT; return mHardwareInterface->initCheck(); } AudioStreamOut* A2dpAudioInterface::openOutputStream( uint32_t devices, int *format, uint32_t *channels, uint32_t *sampleRate, status_t *status) { if (!audio_is_a2dp_out_device(devices)) { ALOGV("A2dpAudioInterface::openOutputStream() open HW device: %x", devices); return mHardwareInterface->openOutputStream(devices, format, channels, sampleRate, status); } status_t err = 0; // only one output stream allowed if (mOutput) { if (status) *status = -1; return NULL; } // create new output stream A2dpAudioStreamOut* out = new A2dpAudioStreamOut(); if ((err = out->set(devices, format, channels, sampleRate)) == NO_ERROR) { mOutput = out; mOutput->setBluetoothEnabled(mBluetoothEnabled); mOutput->setSuspended(mSuspended); } else { delete out; } if (status) *status = err; return mOutput; } void A2dpAudioInterface::closeOutputStream(AudioStreamOut* out) { if (mOutput == 0 || mOutput != out) { mHardwareInterface->closeOutputStream(out); } else { delete mOutput; mOutput = 0; } } AudioStreamIn* A2dpAudioInterface::openInputStream( uint32_t devices, int *format, uint32_t *channels, uint32_t *sampleRate, status_t *status, AudioSystem::audio_in_acoustics acoustics) { return mHardwareInterface->openInputStream(devices, format, channels, sampleRate, status, acoustics); } void A2dpAudioInterface::closeInputStream(AudioStreamIn* in) { return mHardwareInterface->closeInputStream(in); } status_t A2dpAudioInterface::setMode(int mode) { return mHardwareInterface->setMode(mode); } status_t A2dpAudioInterface::setMicMute(bool state) { return mHardwareInterface->setMicMute(state); } status_t A2dpAudioInterface::getMicMute(bool* state) { return mHardwareInterface->getMicMute(state); } status_t A2dpAudioInterface::setParameters(const String8& keyValuePairs) { AudioParameter param = AudioParameter(keyValuePairs); String8 value; String8 key; status_t status = NO_ERROR; ALOGV("setParameters() %s", keyValuePairs.string()); key = "bluetooth_enabled"; if (param.get(key, value) == NO_ERROR) { mBluetoothEnabled = (value == "true"); if (mOutput) { mOutput->setBluetoothEnabled(mBluetoothEnabled); } param.remove(key); } key = String8("A2dpSuspended"); if (param.get(key, value) == NO_ERROR) { mSuspended = (value == "true"); if (mOutput) { mOutput->setSuspended(mSuspended); } param.remove(key); } if (param.size()) { status_t hwStatus = mHardwareInterface->setParameters(param.toString()); if (status == NO_ERROR) { status = hwStatus; } } return status; } String8 A2dpAudioInterface::getParameters(const String8& keys) { AudioParameter param = AudioParameter(keys); AudioParameter a2dpParam = AudioParameter(); String8 value; String8 key; key = "bluetooth_enabled"; if (param.get(key, value) == NO_ERROR) { value = mBluetoothEnabled ? "true" : "false"; a2dpParam.add(key, value); param.remove(key); } key = "A2dpSuspended"; if (param.get(key, value) == NO_ERROR) { value = mSuspended ? "true" : "false"; a2dpParam.add(key, value); param.remove(key); } String8 keyValuePairs = a2dpParam.toString(); if (param.size()) { if (keyValuePairs != "") { keyValuePairs += ";"; } keyValuePairs += mHardwareInterface->getParameters(param.toString()); } ALOGV("getParameters() %s", keyValuePairs.string()); return keyValuePairs; } size_t A2dpAudioInterface::getInputBufferSize(uint32_t sampleRate, int format, int channelCount) { return mHardwareInterface->getInputBufferSize(sampleRate, format, channelCount); } status_t A2dpAudioInterface::setVoiceVolume(float v) { return mHardwareInterface->setVoiceVolume(v); } status_t A2dpAudioInterface::setMasterVolume(float v) { return mHardwareInterface->setMasterVolume(v); } status_t A2dpAudioInterface::dump(int fd, const Vector<String16>& args) { return mHardwareInterface->dumpState(fd, args); } // ---------------------------------------------------------------------------- A2dpAudioInterface::A2dpAudioStreamOut::A2dpAudioStreamOut() : mFd(-1), mStandby(true), mStartCount(0), mRetryCount(0), mData(NULL), // assume BT enabled to start, this is safe because its only the // enabled->disabled transition we are worried about mBluetoothEnabled(true), mDevice(0), mClosing(false), mSuspended(false) { // use any address by default strcpy(mA2dpAddress, "00:00:00:00:00:00"); init(); } status_t A2dpAudioInterface::A2dpAudioStreamOut::set( uint32_t device, 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; ALOGD("A2dpAudioStreamOut::set %x, %d, %d, %d\n", device, lFormat, lChannels, lRate); // 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; } if (pFormat) *pFormat = lFormat; if (pChannels) *pChannels = lChannels; if (pRate) *pRate = lRate; mDevice = device; mBufferDurationUs = ((bufferSize() * 1000 )/ frameSize() / sampleRate()) * 1000; return NO_ERROR; } A2dpAudioInterface::A2dpAudioStreamOut::~A2dpAudioStreamOut() { ALOGV("A2dpAudioStreamOut destructor"); close(); ALOGV("A2dpAudioStreamOut destructor returning from close()"); } ssize_t A2dpAudioInterface::A2dpAudioStreamOut::write(const void* buffer, size_t bytes) { status_t status = -1; { Mutex::Autolock lock(mLock); size_t remaining = bytes; if (!mBluetoothEnabled || mClosing || mSuspended) { ALOGV("A2dpAudioStreamOut::write(), but bluetooth disabled \ mBluetoothEnabled %d, mClosing %d, mSuspended %d", mBluetoothEnabled, mClosing, mSuspended); goto Error; } if (mStandby) { acquire_wake_lock (PARTIAL_WAKE_LOCK, sA2dpWakeLock); mStandby = false; mLastWriteTime = systemTime(); } status = init(); if (status < 0) goto Error; int retries = MAX_WRITE_RETRIES; while (remaining > 0 && retries) { status = a2dp_write(mData, buffer, remaining); if (status < 0) { ALOGE("a2dp_write failed err: %d\n", status); goto Error; } if (status == 0) { retries--; } remaining -= status; buffer = (char *)buffer + status; } // if A2DP sink runs abnormally fast, sleep a little so that audioflinger mixer thread // does no spin and starve other threads. // NOTE: It is likely that the A2DP headset is being disconnected nsecs_t now = systemTime(); if ((uint32_t)ns2us(now - mLastWriteTime) < (mBufferDurationUs >> 2)) { ALOGV("A2DP sink runs too fast"); usleep(mBufferDurationUs - (uint32_t)ns2us(now - mLastWriteTime)); } mLastWriteTime = now; return bytes; } Error: standby(); // Simulate audio output timing in case of error usleep(mBufferDurationUs); return status; } status_t A2dpAudioInterface::A2dpAudioStreamOut::init() { if (!mData) { status_t status = a2dp_init(44100, 2, &mData); if (status < 0) { ALOGE("a2dp_init failed err: %d\n", status); mData = NULL; return status; } a2dp_set_sink(mData, mA2dpAddress); } return 0; } status_t A2dpAudioInterface::A2dpAudioStreamOut::standby() { Mutex::Autolock lock(mLock); return standby_l(); } status_t A2dpAudioInterface::A2dpAudioStreamOut::standby_l() { int result = NO_ERROR; if (!mStandby) { ALOGV_IF(mClosing || !mBluetoothEnabled, "Standby skip stop: closing %d enabled %d", mClosing, mBluetoothEnabled); if (!mClosing && mBluetoothEnabled) { result = a2dp_stop(mData); } release_wake_lock(sA2dpWakeLock); mStandby = true; } return result; } status_t A2dpAudioInterface::A2dpAudioStreamOut::setParameters(const String8& keyValuePairs) { AudioParameter param = AudioParameter(keyValuePairs); String8 value; String8 key = String8("a2dp_sink_address"); status_t status = NO_ERROR; int device; ALOGV("A2dpAudioStreamOut::setParameters() %s", keyValuePairs.string()); if (param.get(key, value) == NO_ERROR) { if (value.length() != strlen("00:00:00:00:00:00")) { status = BAD_VALUE; } else { setAddress(value.string()); } param.remove(key); } key = String8("closing"); if (param.get(key, value) == NO_ERROR) { mClosing = (value == "true"); if (mClosing) { standby(); } param.remove(key); } key = AudioParameter::keyRouting; if (param.getInt(key, device) == NO_ERROR) { if (audio_is_a2dp_out_device(device)) { mDevice = device; status = NO_ERROR; } else { status = BAD_VALUE; } param.remove(key); } if (param.size()) { status = BAD_VALUE; } return status; } String8 A2dpAudioInterface::A2dpAudioStreamOut::getParameters(const String8& keys) { AudioParameter param = AudioParameter(keys); String8 value; String8 key = String8("a2dp_sink_address"); if (param.get(key, value) == NO_ERROR) { value = mA2dpAddress; param.add(key, value); } key = AudioParameter::keyRouting; if (param.get(key, value) == NO_ERROR) { param.addInt(key, (int)mDevice); } ALOGV("A2dpAudioStreamOut::getParameters() %s", param.toString().string()); return param.toString(); } status_t A2dpAudioInterface::A2dpAudioStreamOut::setAddress(const char* address) { Mutex::Autolock lock(mLock); if (strlen(address) != strlen("00:00:00:00:00:00")) return -EINVAL; strcpy(mA2dpAddress, address); if (mData) a2dp_set_sink(mData, mA2dpAddress); return NO_ERROR; } status_t A2dpAudioInterface::A2dpAudioStreamOut::setBluetoothEnabled(bool enabled) { ALOGD("setBluetoothEnabled %d", enabled); Mutex::Autolock lock(mLock); mBluetoothEnabled = enabled; if (!enabled) { return close_l(); } return NO_ERROR; } status_t A2dpAudioInterface::A2dpAudioStreamOut::setSuspended(bool onOff) { ALOGV("setSuspended %d", onOff); mSuspended = onOff; standby(); return NO_ERROR; } status_t A2dpAudioInterface::A2dpAudioStreamOut::close() { Mutex::Autolock lock(mLock); ALOGV("A2dpAudioStreamOut::close() calling close_l()"); return close_l(); } status_t A2dpAudioInterface::A2dpAudioStreamOut::close_l() { standby_l(); if (mData) { ALOGV("A2dpAudioStreamOut::close_l() calling a2dp_cleanup(mData)"); a2dp_cleanup(mData); mData = NULL; } return NO_ERROR; } status_t A2dpAudioInterface::A2dpAudioStreamOut::dump(int fd, const Vector<String16>& args) { return NO_ERROR; } status_t A2dpAudioInterface::A2dpAudioStreamOut::getRenderPosition(uint32_t *driverFrames) { //TODO: enable when supported by driver return INVALID_OPERATION; } }; // namespace android