diff options
author | Mathias Agopian <mathias@google.com> | 2010-07-22 15:27:48 -0700 |
---|---|---|
committer | Jean-Baptiste Queru <jbq@google.com> | 2010-07-22 15:28:19 -0700 |
commit | ccaa41429d2afc47f0fb828a0146a1b1936188fe (patch) | |
tree | de8102d8322f1006344faac66148528b26248203 | |
parent | 51da506d87b071ea01bc8f4707a2045869394e83 (diff) | |
download | frameworks_av-ccaa41429d2afc47f0fb828a0146a1b1936188fe.zip frameworks_av-ccaa41429d2afc47f0fb828a0146a1b1936188fe.tar.gz frameworks_av-ccaa41429d2afc47f0fb828a0146a1b1936188fe.tar.bz2 |
move native services under services/
moved surfaceflinger, audioflinger, cameraservice
all native services should now reside in this location.
Change-Id: Icd7336f7289db35df9c8c1857a5122bb8a6f1c86
35 files changed, 17369 insertions, 2 deletions
diff --git a/media/mediaserver/Android.mk b/media/mediaserver/Android.mk index a92cea8..0559812 100644 --- a/media/mediaserver/Android.mk +++ b/media/mediaserver/Android.mk @@ -14,8 +14,8 @@ LOCAL_SHARED_LIBRARIES := \ base := $(LOCAL_PATH)/../.. LOCAL_C_INCLUDES := \ - $(base)/libs/audioflinger \ - $(base)/camera/libcameraservice \ + $(base)/services/audioflinger \ + $(base)/services/camera/libcameraservice \ $(base)/media/libmediaplayerservice LOCAL_MODULE:= mediaserver diff --git a/services/audioflinger/A2dpAudioInterface.cpp b/services/audioflinger/A2dpAudioInterface.cpp new file mode 100644 index 0000000..995e31c --- /dev/null +++ b/services/audioflinger/A2dpAudioInterface.cpp @@ -0,0 +1,466 @@ +/* + * 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 "A2dpAudioInterface.h" +#include "audio/liba2dp.h" + + +namespace android { + +// ---------------------------------------------------------------------------- + +//AudioHardwareInterface* A2dpAudioInterface::createA2dpInterface() +//{ +// AudioHardwareInterface* hw = 0; +// +// hw = AudioHardwareInterface::create(); +// LOGD("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 (!AudioSystem::isA2dpDevice((AudioSystem::audio_devices)devices)) { + LOGV("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; + + LOGV("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()); + } + + LOGV("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; + + LOGD("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; + return NO_ERROR; +} + +A2dpAudioInterface::A2dpAudioStreamOut::~A2dpAudioStreamOut() +{ + LOGV("A2dpAudioStreamOut destructor"); + standby(); + close(); + LOGV("A2dpAudioStreamOut destructor returning from close()"); +} + +ssize_t A2dpAudioInterface::A2dpAudioStreamOut::write(const void* buffer, size_t bytes) +{ + Mutex::Autolock lock(mLock); + + size_t remaining = bytes; + status_t status = -1; + + if (!mBluetoothEnabled || mClosing || mSuspended) { + LOGV("A2dpAudioStreamOut::write(), but bluetooth disabled \ + mBluetoothEnabled %d, mClosing %d, mSuspended %d", + mBluetoothEnabled, mClosing, mSuspended); + goto Error; + } + + status = init(); + if (status < 0) + goto Error; + + while (remaining > 0) { + status = a2dp_write(mData, buffer, remaining); + if (status <= 0) { + LOGE("a2dp_write failed err: %d\n", status); + goto Error; + } + remaining -= status; + buffer = ((char *)buffer) + status; + } + + mStandby = false; + + return bytes; + +Error: + // Simulate audio output timing in case of error + usleep(((bytes * 1000 )/ frameSize() / sampleRate()) * 1000); + + return status; +} + +status_t A2dpAudioInterface::A2dpAudioStreamOut::init() +{ + if (!mData) { + status_t status = a2dp_init(44100, 2, &mData); + if (status < 0) { + LOGE("a2dp_init failed err: %d\n", status); + mData = NULL; + return status; + } + a2dp_set_sink(mData, mA2dpAddress); + } + + return 0; +} + +status_t A2dpAudioInterface::A2dpAudioStreamOut::standby() +{ + int result = 0; + + if (mClosing) { + LOGV("Ignore standby, closing"); + return result; + } + + Mutex::Autolock lock(mLock); + + if (!mStandby) { + result = a2dp_stop(mData); + if (result == 0) + 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; + LOGV("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"); + param.remove(key); + } + key = AudioParameter::keyRouting; + if (param.getInt(key, device) == NO_ERROR) { + if (AudioSystem::isA2dpDevice((AudioSystem::audio_devices)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); + } + + LOGV("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) +{ + LOGD("setBluetoothEnabled %d", enabled); + + Mutex::Autolock lock(mLock); + + mBluetoothEnabled = enabled; + if (!enabled) { + return close_l(); + } + return NO_ERROR; +} + +status_t A2dpAudioInterface::A2dpAudioStreamOut::setSuspended(bool onOff) +{ + LOGV("setSuspended %d", onOff); + mSuspended = onOff; + standby(); + return NO_ERROR; +} + +status_t A2dpAudioInterface::A2dpAudioStreamOut::close() +{ + Mutex::Autolock lock(mLock); + LOGV("A2dpAudioStreamOut::close() calling close_l()"); + return close_l(); +} + +status_t A2dpAudioInterface::A2dpAudioStreamOut::close_l() +{ + if (mData) { + LOGV("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 diff --git a/services/audioflinger/A2dpAudioInterface.h b/services/audioflinger/A2dpAudioInterface.h new file mode 100644 index 0000000..48154f9 --- /dev/null +++ b/services/audioflinger/A2dpAudioInterface.h @@ -0,0 +1,135 @@ +/* + * 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. + */ + +#ifndef A2DP_AUDIO_HARDWARE_H +#define A2DP_AUDIO_HARDWARE_H + +#include <stdint.h> +#include <sys/types.h> + +#include <utils/threads.h> + +#include <hardware_legacy/AudioHardwareBase.h> + + +namespace android { + +class A2dpAudioInterface : public AudioHardwareBase +{ + class A2dpAudioStreamOut; + +public: + A2dpAudioInterface(AudioHardwareInterface* hw); + virtual ~A2dpAudioInterface(); + virtual status_t initCheck(); + + virtual status_t setVoiceVolume(float volume); + virtual status_t setMasterVolume(float volume); + + virtual status_t setMode(int mode); + + // mic mute + virtual status_t setMicMute(bool state); + virtual status_t getMicMute(bool* state); + + virtual status_t setParameters(const String8& keyValuePairs); + virtual String8 getParameters(const String8& keys); + + virtual size_t getInputBufferSize(uint32_t sampleRate, int format, int channelCount); + + // create I/O streams + virtual AudioStreamOut* openOutputStream( + uint32_t devices, + int *format=0, + uint32_t *channels=0, + uint32_t *sampleRate=0, + status_t *status=0); + virtual void closeOutputStream(AudioStreamOut* out); + + virtual AudioStreamIn* openInputStream( + uint32_t devices, + int *format, + uint32_t *channels, + uint32_t *sampleRate, + status_t *status, + AudioSystem::audio_in_acoustics acoustics); + virtual void closeInputStream(AudioStreamIn* in); +// static AudioHardwareInterface* createA2dpInterface(); + +protected: + virtual status_t dump(int fd, const Vector<String16>& args); + +private: + class A2dpAudioStreamOut : public AudioStreamOut { + public: + A2dpAudioStreamOut(); + virtual ~A2dpAudioStreamOut(); + status_t set(uint32_t device, + int *pFormat, + uint32_t *pChannels, + uint32_t *pRate); + virtual uint32_t sampleRate() const { return 44100; } + // SBC codec wants a multiple of 512 + virtual size_t bufferSize() const { return 512 * 20; } + virtual uint32_t channels() const { return AudioSystem::CHANNEL_OUT_STEREO; } + virtual int format() const { return AudioSystem::PCM_16_BIT; } + virtual uint32_t latency() const { return ((1000*bufferSize())/frameSize())/sampleRate() + 200; } + virtual status_t setVolume(float left, float right) { return INVALID_OPERATION; } + virtual ssize_t write(const void* buffer, size_t bytes); + status_t standby(); + virtual status_t dump(int fd, const Vector<String16>& args); + virtual status_t setParameters(const String8& keyValuePairs); + virtual String8 getParameters(const String8& keys); + virtual status_t getRenderPosition(uint32_t *dspFrames); + + private: + friend class A2dpAudioInterface; + status_t init(); + status_t close(); + status_t close_l(); + status_t setAddress(const char* address); + status_t setBluetoothEnabled(bool enabled); + status_t setSuspended(bool onOff); + + private: + int mFd; + bool mStandby; + int mStartCount; + int mRetryCount; + char mA2dpAddress[20]; + void* mData; + Mutex mLock; + bool mBluetoothEnabled; + uint32_t mDevice; + bool mClosing; + bool mSuspended; + }; + + friend class A2dpAudioStreamOut; + + A2dpAudioStreamOut* mOutput; + AudioHardwareInterface *mHardwareInterface; + char mA2dpAddress[20]; + bool mBluetoothEnabled; + bool mSuspended; +}; + + +// ---------------------------------------------------------------------------- + +}; // namespace android + +#endif // A2DP_AUDIO_HARDWARE_H diff --git a/services/audioflinger/Android.mk b/services/audioflinger/Android.mk new file mode 100644 index 0000000..870c0b8 --- /dev/null +++ b/services/audioflinger/Android.mk @@ -0,0 +1,130 @@ +LOCAL_PATH:= $(call my-dir) + +#AUDIO_POLICY_TEST := true +#ENABLE_AUDIO_DUMP := true + +include $(CLEAR_VARS) + + +ifeq ($(AUDIO_POLICY_TEST),true) + ENABLE_AUDIO_DUMP := true +endif + + +LOCAL_SRC_FILES:= \ + AudioHardwareGeneric.cpp \ + AudioHardwareStub.cpp \ + AudioHardwareInterface.cpp + +ifeq ($(ENABLE_AUDIO_DUMP),true) + LOCAL_SRC_FILES += AudioDumpInterface.cpp + LOCAL_CFLAGS += -DENABLE_AUDIO_DUMP +endif + +LOCAL_SHARED_LIBRARIES := \ + libcutils \ + libutils \ + libbinder \ + libmedia \ + libhardware_legacy + +ifeq ($(strip $(BOARD_USES_GENERIC_AUDIO)),true) + LOCAL_CFLAGS += -DGENERIC_AUDIO +endif + +LOCAL_MODULE:= libaudiointerface + +ifeq ($(BOARD_HAVE_BLUETOOTH),true) + LOCAL_SRC_FILES += A2dpAudioInterface.cpp + LOCAL_SHARED_LIBRARIES += liba2dp + LOCAL_CFLAGS += -DWITH_BLUETOOTH -DWITH_A2DP + LOCAL_C_INCLUDES += $(call include-path-for, bluez) +endif + +include $(BUILD_STATIC_LIBRARY) + + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + AudioPolicyManagerBase.cpp + +LOCAL_SHARED_LIBRARIES := \ + libcutils \ + libutils \ + libmedia + +ifeq ($(TARGET_SIMULATOR),true) + LOCAL_LDLIBS += -ldl +else + LOCAL_SHARED_LIBRARIES += libdl +endif + +LOCAL_MODULE:= libaudiopolicybase + +ifeq ($(BOARD_HAVE_BLUETOOTH),true) + LOCAL_CFLAGS += -DWITH_A2DP +endif + +ifeq ($(AUDIO_POLICY_TEST),true) + LOCAL_CFLAGS += -DAUDIO_POLICY_TEST +endif + +include $(BUILD_STATIC_LIBRARY) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + AudioFlinger.cpp \ + AudioMixer.cpp.arm \ + AudioResampler.cpp.arm \ + AudioResamplerSinc.cpp.arm \ + AudioResamplerCubic.cpp.arm \ + AudioPolicyService.cpp + +LOCAL_SHARED_LIBRARIES := \ + libcutils \ + libutils \ + libbinder \ + libmedia \ + libhardware_legacy + +ifeq ($(strip $(BOARD_USES_GENERIC_AUDIO)),true) + LOCAL_STATIC_LIBRARIES += libaudiointerface libaudiopolicybase + LOCAL_CFLAGS += -DGENERIC_AUDIO +else + LOCAL_SHARED_LIBRARIES += libaudio libaudiopolicy +endif + +ifeq ($(TARGET_SIMULATOR),true) + LOCAL_LDLIBS += -ldl +else + LOCAL_SHARED_LIBRARIES += libdl +endif + +LOCAL_MODULE:= libaudioflinger + +ifeq ($(BOARD_HAVE_BLUETOOTH),true) + LOCAL_CFLAGS += -DWITH_BLUETOOTH -DWITH_A2DP + LOCAL_SHARED_LIBRARIES += liba2dp +endif + +ifeq ($(AUDIO_POLICY_TEST),true) + LOCAL_CFLAGS += -DAUDIO_POLICY_TEST +endif + +ifeq ($(TARGET_SIMULATOR),true) + ifeq ($(HOST_OS),linux) + LOCAL_LDLIBS += -lrt -lpthread + endif +endif + +ifeq ($(BOARD_USE_LVMX),true) + LOCAL_CFLAGS += -DLVMX + LOCAL_C_INCLUDES += vendor/nxp + LOCAL_STATIC_LIBRARIES += liblifevibes + LOCAL_SHARED_LIBRARIES += liblvmxservice +# LOCAL_SHARED_LIBRARIES += liblvmxipc +endif + +include $(BUILD_SHARED_LIBRARY) diff --git a/services/audioflinger/AudioBufferProvider.h b/services/audioflinger/AudioBufferProvider.h new file mode 100644 index 0000000..81c5c39 --- /dev/null +++ b/services/audioflinger/AudioBufferProvider.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2007 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. + */ + +#ifndef ANDROID_AUDIO_BUFFER_PROVIDER_H +#define ANDROID_AUDIO_BUFFER_PROVIDER_H + +#include <stdint.h> +#include <sys/types.h> +#include <utils/Errors.h> + +namespace android { +// ---------------------------------------------------------------------------- + +class AudioBufferProvider +{ +public: + + struct Buffer { + union { + void* raw; + short* i16; + int8_t* i8; + }; + size_t frameCount; + }; + + virtual ~AudioBufferProvider() {} + + virtual status_t getNextBuffer(Buffer* buffer) = 0; + virtual void releaseBuffer(Buffer* buffer) = 0; +}; + +// ---------------------------------------------------------------------------- +}; // namespace android + +#endif // ANDROID_AUDIO_BUFFER_PROVIDER_H diff --git a/services/audioflinger/AudioDumpInterface.cpp b/services/audioflinger/AudioDumpInterface.cpp new file mode 100644 index 0000000..a018b4c --- /dev/null +++ b/services/audioflinger/AudioDumpInterface.cpp @@ -0,0 +1,531 @@ +/* //device/servers/AudioFlinger/AudioDumpInterface.cpp +** +** Copyright 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. +*/ + +#define LOG_TAG "AudioFlingerDump" +//#define LOG_NDEBUG 0 + +#include <stdint.h> +#include <sys/types.h> +#include <utils/Log.h> + +#include <stdlib.h> +#include <unistd.h> + +#include "AudioDumpInterface.h" + +namespace android { + +// ---------------------------------------------------------------------------- + +AudioDumpInterface::AudioDumpInterface(AudioHardwareInterface* hw) + : mFirstHwOutput(true), mPolicyCommands(String8("")), mFileName(String8("")) +{ + if(hw == 0) { + LOGE("Dump construct hw = 0"); + } + mFinalInterface = hw; + LOGV("Constructor %p, mFinalInterface %p", this, mFinalInterface); +} + + +AudioDumpInterface::~AudioDumpInterface() +{ + for (size_t i = 0; i < mOutputs.size(); i++) { + closeOutputStream((AudioStreamOut *)mOutputs[i]); + } + if(mFinalInterface) delete mFinalInterface; +} + + +AudioStreamOut* AudioDumpInterface::openOutputStream( + uint32_t devices, int *format, uint32_t *channels, uint32_t *sampleRate, status_t *status) +{ + AudioStreamOut* outFinal = NULL; + int lFormat = AudioSystem::PCM_16_BIT; + uint32_t lChannels = AudioSystem::CHANNEL_OUT_STEREO; + uint32_t lRate = 44100; + + + if (AudioSystem::isA2dpDevice((AudioSystem::audio_devices)devices) || mFirstHwOutput) { + outFinal = mFinalInterface->openOutputStream(devices, format, channels, sampleRate, status); + if (outFinal != 0) { + lFormat = outFinal->format(); + lChannels = outFinal->channels(); + lRate = outFinal->sampleRate(); + if (!AudioSystem::isA2dpDevice((AudioSystem::audio_devices)devices)) { + mFirstHwOutput = false; + } + } + } else { + if (format != 0 && *format != 0) { + lFormat = *format; + } else { + lFormat = AudioSystem::PCM_16_BIT; + } + if (channels != 0 && *channels != 0) { + lChannels = *channels; + } else { + lChannels = AudioSystem::CHANNEL_OUT_STEREO; + } + if (sampleRate != 0 && *sampleRate != 0) { + lRate = *sampleRate; + } else { + lRate = 44100; + } + if (status) *status = NO_ERROR; + } + LOGV("openOutputStream(), outFinal %p", outFinal); + + AudioStreamOutDump *dumOutput = new AudioStreamOutDump(this, mOutputs.size(), outFinal, + devices, lFormat, lChannels, lRate); + mOutputs.add(dumOutput); + + return dumOutput; +} + +void AudioDumpInterface::closeOutputStream(AudioStreamOut* out) +{ + AudioStreamOutDump *dumpOut = (AudioStreamOutDump *)out; + + if (mOutputs.indexOf(dumpOut) < 0) { + LOGW("Attempt to close invalid output stream"); + return; + } + + LOGV("closeOutputStream() output %p", out); + + dumpOut->standby(); + if (dumpOut->finalStream() != NULL) { + mFinalInterface->closeOutputStream(dumpOut->finalStream()); + mFirstHwOutput = true; + } + + mOutputs.remove(dumpOut); + delete dumpOut; +} + +AudioStreamIn* AudioDumpInterface::openInputStream(uint32_t devices, int *format, uint32_t *channels, + uint32_t *sampleRate, status_t *status, AudioSystem::audio_in_acoustics acoustics) +{ + AudioStreamIn* inFinal = NULL; + int lFormat = AudioSystem::PCM_16_BIT; + uint32_t lChannels = AudioSystem::CHANNEL_IN_MONO; + uint32_t lRate = 8000; + + + if (mInputs.size() == 0) { + inFinal = mFinalInterface->openInputStream(devices, format, channels, sampleRate, status, acoustics); + if (inFinal == 0) return 0; + + lFormat = inFinal->format(); + lChannels = inFinal->channels(); + lRate = inFinal->sampleRate(); + } else { + if (format != 0 && *format != 0) lFormat = *format; + if (channels != 0 && *channels != 0) lChannels = *channels; + if (sampleRate != 0 && *sampleRate != 0) lRate = *sampleRate; + if (status) *status = NO_ERROR; + } + LOGV("openInputStream(), inFinal %p", inFinal); + + AudioStreamInDump *dumInput = new AudioStreamInDump(this, mInputs.size(), inFinal, + devices, lFormat, lChannels, lRate); + mInputs.add(dumInput); + + return dumInput; +} +void AudioDumpInterface::closeInputStream(AudioStreamIn* in) +{ + AudioStreamInDump *dumpIn = (AudioStreamInDump *)in; + + if (mInputs.indexOf(dumpIn) < 0) { + LOGW("Attempt to close invalid input stream"); + return; + } + dumpIn->standby(); + if (dumpIn->finalStream() != NULL) { + mFinalInterface->closeInputStream(dumpIn->finalStream()); + } + + mInputs.remove(dumpIn); + delete dumpIn; +} + + +status_t AudioDumpInterface::setParameters(const String8& keyValuePairs) +{ + AudioParameter param = AudioParameter(keyValuePairs); + String8 value; + int valueInt; + LOGV("setParameters %s", keyValuePairs.string()); + + if (param.get(String8("test_cmd_file_name"), value) == NO_ERROR) { + mFileName = value; + param.remove(String8("test_cmd_file_name")); + } + if (param.get(String8("test_cmd_policy"), value) == NO_ERROR) { + Mutex::Autolock _l(mLock); + param.remove(String8("test_cmd_policy")); + mPolicyCommands = param.toString(); + LOGV("test_cmd_policy command %s written", mPolicyCommands.string()); + return NO_ERROR; + } + + if (mFinalInterface != 0 ) return mFinalInterface->setParameters(keyValuePairs); + return NO_ERROR; +} + +String8 AudioDumpInterface::getParameters(const String8& keys) +{ + AudioParameter param = AudioParameter(keys); + AudioParameter response; + String8 value; + +// LOGV("getParameters %s", keys.string()); + if (param.get(String8("test_cmd_policy"), value) == NO_ERROR) { + Mutex::Autolock _l(mLock); + if (mPolicyCommands.length() != 0) { + response = AudioParameter(mPolicyCommands); + response.addInt(String8("test_cmd_policy"), 1); + } else { + response.addInt(String8("test_cmd_policy"), 0); + } + param.remove(String8("test_cmd_policy")); +// LOGV("test_cmd_policy command %s read", mPolicyCommands.string()); + } + + if (param.get(String8("test_cmd_file_name"), value) == NO_ERROR) { + response.add(String8("test_cmd_file_name"), mFileName); + param.remove(String8("test_cmd_file_name")); + } + + String8 keyValuePairs = response.toString(); + + if (param.size() && mFinalInterface != 0 ) { + keyValuePairs += ";"; + keyValuePairs += mFinalInterface->getParameters(param.toString()); + } + + return keyValuePairs; +} + + +// ---------------------------------------------------------------------------- + +AudioStreamOutDump::AudioStreamOutDump(AudioDumpInterface *interface, + int id, + AudioStreamOut* finalStream, + uint32_t devices, + int format, + uint32_t channels, + uint32_t sampleRate) + : mInterface(interface), mId(id), + mSampleRate(sampleRate), mFormat(format), mChannels(channels), mLatency(0), mDevice(devices), + mBufferSize(1024), mFinalStream(finalStream), mOutFile(0), mFileCount(0) +{ + LOGV("AudioStreamOutDump Constructor %p, mInterface %p, mFinalStream %p", this, mInterface, mFinalStream); +} + + +AudioStreamOutDump::~AudioStreamOutDump() +{ + LOGV("AudioStreamOutDump destructor"); + Close(); +} + +ssize_t AudioStreamOutDump::write(const void* buffer, size_t bytes) +{ + ssize_t ret; + + if (mFinalStream) { + ret = mFinalStream->write(buffer, bytes); + } else { + usleep((bytes * 1000000) / frameSize() / sampleRate()); + ret = bytes; + } + if(!mOutFile) { + if (mInterface->fileName() != "") { + char name[255]; + sprintf(name, "%s_%d_%d.pcm", mInterface->fileName().string(), mId, ++mFileCount); + mOutFile = fopen(name, "wb"); + LOGV("Opening dump file %s, fh %p", name, mOutFile); + } + } + if (mOutFile) { + fwrite(buffer, bytes, 1, mOutFile); + } + return ret; +} + +status_t AudioStreamOutDump::standby() +{ + LOGV("AudioStreamOutDump standby(), mOutFile %p, mFinalStream %p", mOutFile, mFinalStream); + + Close(); + if (mFinalStream != 0 ) return mFinalStream->standby(); + return NO_ERROR; +} + +uint32_t AudioStreamOutDump::sampleRate() const +{ + if (mFinalStream != 0 ) return mFinalStream->sampleRate(); + return mSampleRate; +} + +size_t AudioStreamOutDump::bufferSize() const +{ + if (mFinalStream != 0 ) return mFinalStream->bufferSize(); + return mBufferSize; +} + +uint32_t AudioStreamOutDump::channels() const +{ + if (mFinalStream != 0 ) return mFinalStream->channels(); + return mChannels; +} +int AudioStreamOutDump::format() const +{ + if (mFinalStream != 0 ) return mFinalStream->format(); + return mFormat; +} +uint32_t AudioStreamOutDump::latency() const +{ + if (mFinalStream != 0 ) return mFinalStream->latency(); + return 0; +} +status_t AudioStreamOutDump::setVolume(float left, float right) +{ + if (mFinalStream != 0 ) return mFinalStream->setVolume(left, right); + return NO_ERROR; +} +status_t AudioStreamOutDump::setParameters(const String8& keyValuePairs) +{ + LOGV("AudioStreamOutDump::setParameters %s", keyValuePairs.string()); + + if (mFinalStream != 0 ) { + return mFinalStream->setParameters(keyValuePairs); + } + + AudioParameter param = AudioParameter(keyValuePairs); + String8 value; + int valueInt; + status_t status = NO_ERROR; + + if (param.getInt(String8("set_id"), valueInt) == NO_ERROR) { + mId = valueInt; + } + + if (param.getInt(String8("format"), valueInt) == NO_ERROR) { + if (mOutFile == 0) { + mFormat = valueInt; + } else { + status = INVALID_OPERATION; + } + } + if (param.getInt(String8("channels"), valueInt) == NO_ERROR) { + if (valueInt == AudioSystem::CHANNEL_OUT_STEREO || valueInt == AudioSystem::CHANNEL_OUT_MONO) { + mChannels = valueInt; + } else { + status = BAD_VALUE; + } + } + if (param.getInt(String8("sampling_rate"), valueInt) == NO_ERROR) { + if (valueInt > 0 && valueInt <= 48000) { + if (mOutFile == 0) { + mSampleRate = valueInt; + } else { + status = INVALID_OPERATION; + } + } else { + status = BAD_VALUE; + } + } + return status; +} + +String8 AudioStreamOutDump::getParameters(const String8& keys) +{ + if (mFinalStream != 0 ) return mFinalStream->getParameters(keys); + + AudioParameter param = AudioParameter(keys); + return param.toString(); +} + +status_t AudioStreamOutDump::dump(int fd, const Vector<String16>& args) +{ + if (mFinalStream != 0 ) return mFinalStream->dump(fd, args); + return NO_ERROR; +} + +void AudioStreamOutDump::Close() +{ + if(mOutFile) { + fclose(mOutFile); + mOutFile = 0; + } +} + +status_t AudioStreamOutDump::getRenderPosition(uint32_t *dspFrames) +{ + if (mFinalStream != 0 ) return mFinalStream->getRenderPosition(dspFrames); + return INVALID_OPERATION; +} + +// ---------------------------------------------------------------------------- + +AudioStreamInDump::AudioStreamInDump(AudioDumpInterface *interface, + int id, + AudioStreamIn* finalStream, + uint32_t devices, + int format, + uint32_t channels, + uint32_t sampleRate) + : mInterface(interface), mId(id), + mSampleRate(sampleRate), mFormat(format), mChannels(channels), mDevice(devices), + mBufferSize(1024), mFinalStream(finalStream), mInFile(0) +{ + LOGV("AudioStreamInDump Constructor %p, mInterface %p, mFinalStream %p", this, mInterface, mFinalStream); +} + + +AudioStreamInDump::~AudioStreamInDump() +{ + Close(); +} + +ssize_t AudioStreamInDump::read(void* buffer, ssize_t bytes) +{ + if (mFinalStream) { + return mFinalStream->read(buffer, bytes); + } + + usleep((bytes * 1000000) / frameSize() / sampleRate()); + + if(!mInFile) { + char name[255]; + strcpy(name, "/sdcard/music/sine440"); + if (channels() == AudioSystem::CHANNEL_IN_MONO) { + strcat(name, "_mo"); + } else { + strcat(name, "_st"); + } + if (format() == AudioSystem::PCM_16_BIT) { + strcat(name, "_16b"); + } else { + strcat(name, "_8b"); + } + if (sampleRate() < 16000) { + strcat(name, "_8k"); + } else if (sampleRate() < 32000) { + strcat(name, "_22k"); + } else if (sampleRate() < 48000) { + strcat(name, "_44k"); + } else { + strcat(name, "_48k"); + } + strcat(name, ".wav"); + mInFile = fopen(name, "rb"); + LOGV("Opening dump file %s, fh %p", name, mInFile); + if (mInFile) { + fseek(mInFile, AUDIO_DUMP_WAVE_HDR_SIZE, SEEK_SET); + } + + } + if (mInFile) { + ssize_t bytesRead = fread(buffer, bytes, 1, mInFile); + if (bytesRead != bytes) { + fseek(mInFile, AUDIO_DUMP_WAVE_HDR_SIZE, SEEK_SET); + fread((uint8_t *)buffer+bytesRead, bytes-bytesRead, 1, mInFile); + } + } + return bytes; +} + +status_t AudioStreamInDump::standby() +{ + LOGV("AudioStreamInDump standby(), mInFile %p, mFinalStream %p", mInFile, mFinalStream); + + Close(); + if (mFinalStream != 0 ) return mFinalStream->standby(); + return NO_ERROR; +} + +status_t AudioStreamInDump::setGain(float gain) +{ + if (mFinalStream != 0 ) return mFinalStream->setGain(gain); + return NO_ERROR; +} + +uint32_t AudioStreamInDump::sampleRate() const +{ + if (mFinalStream != 0 ) return mFinalStream->sampleRate(); + return mSampleRate; +} + +size_t AudioStreamInDump::bufferSize() const +{ + if (mFinalStream != 0 ) return mFinalStream->bufferSize(); + return mBufferSize; +} + +uint32_t AudioStreamInDump::channels() const +{ + if (mFinalStream != 0 ) return mFinalStream->channels(); + return mChannels; +} + +int AudioStreamInDump::format() const +{ + if (mFinalStream != 0 ) return mFinalStream->format(); + return mFormat; +} + +status_t AudioStreamInDump::setParameters(const String8& keyValuePairs) +{ + LOGV("AudioStreamInDump::setParameters()"); + if (mFinalStream != 0 ) return mFinalStream->setParameters(keyValuePairs); + return NO_ERROR; +} + +String8 AudioStreamInDump::getParameters(const String8& keys) +{ + if (mFinalStream != 0 ) return mFinalStream->getParameters(keys); + + AudioParameter param = AudioParameter(keys); + return param.toString(); +} + +unsigned int AudioStreamInDump::getInputFramesLost() const +{ + if (mFinalStream != 0 ) return mFinalStream->getInputFramesLost(); + return 0; +} + +status_t AudioStreamInDump::dump(int fd, const Vector<String16>& args) +{ + if (mFinalStream != 0 ) return mFinalStream->dump(fd, args); + return NO_ERROR; +} + +void AudioStreamInDump::Close() +{ + if(mInFile) { + fclose(mInFile); + mInFile = 0; + } +} +}; // namespace android diff --git a/services/audioflinger/AudioDumpInterface.h b/services/audioflinger/AudioDumpInterface.h new file mode 100644 index 0000000..4c62b3e --- /dev/null +++ b/services/audioflinger/AudioDumpInterface.h @@ -0,0 +1,166 @@ +/* //device/servers/AudioFlinger/AudioDumpInterface.h +** +** Copyright 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. +*/ + +#ifndef ANDROID_AUDIO_DUMP_INTERFACE_H +#define ANDROID_AUDIO_DUMP_INTERFACE_H + +#include <stdint.h> +#include <sys/types.h> +#include <utils/String8.h> +#include <utils/SortedVector.h> + +#include <hardware_legacy/AudioHardwareBase.h> + +namespace android { + +#define AUDIO_DUMP_WAVE_HDR_SIZE 44 + +class AudioDumpInterface; + +class AudioStreamOutDump : public AudioStreamOut { +public: + AudioStreamOutDump(AudioDumpInterface *interface, + int id, + AudioStreamOut* finalStream, + uint32_t devices, + int format, + uint32_t channels, + uint32_t sampleRate); + ~AudioStreamOutDump(); + + virtual ssize_t write(const void* buffer, size_t bytes); + virtual uint32_t sampleRate() const; + virtual size_t bufferSize() const; + virtual uint32_t channels() const; + virtual int format() const; + virtual uint32_t latency() const; + virtual status_t setVolume(float left, float right); + virtual status_t standby(); + virtual status_t setParameters(const String8& keyValuePairs); + virtual String8 getParameters(const String8& keys); + virtual status_t dump(int fd, const Vector<String16>& args); + void Close(void); + AudioStreamOut* finalStream() { return mFinalStream; } + uint32_t device() { return mDevice; } + int getId() { return mId; } + virtual status_t getRenderPosition(uint32_t *dspFrames); + +private: + AudioDumpInterface *mInterface; + int mId; + uint32_t mSampleRate; // + uint32_t mFormat; // + uint32_t mChannels; // output configuration + uint32_t mLatency; // + uint32_t mDevice; // current device this output is routed to + size_t mBufferSize; + AudioStreamOut *mFinalStream; + FILE *mOutFile; // output file + int mFileCount; +}; + +class AudioStreamInDump : public AudioStreamIn { +public: + AudioStreamInDump(AudioDumpInterface *interface, + int id, + AudioStreamIn* finalStream, + uint32_t devices, + int format, + uint32_t channels, + uint32_t sampleRate); + ~AudioStreamInDump(); + + virtual uint32_t sampleRate() const; + virtual size_t bufferSize() const; + virtual uint32_t channels() const; + virtual int format() const; + + virtual status_t setGain(float gain); + virtual ssize_t read(void* buffer, ssize_t bytes); + virtual status_t standby(); + virtual status_t setParameters(const String8& keyValuePairs); + virtual String8 getParameters(const String8& keys); + virtual unsigned int getInputFramesLost() const; + virtual status_t dump(int fd, const Vector<String16>& args); + void Close(void); + AudioStreamIn* finalStream() { return mFinalStream; } + uint32_t device() { return mDevice; } + +private: + AudioDumpInterface *mInterface; + int mId; + uint32_t mSampleRate; // + uint32_t mFormat; // + uint32_t mChannels; // output configuration + uint32_t mDevice; // current device this output is routed to + size_t mBufferSize; + AudioStreamIn *mFinalStream; + FILE *mInFile; // output file +}; + +class AudioDumpInterface : public AudioHardwareBase +{ + +public: + AudioDumpInterface(AudioHardwareInterface* hw); + virtual AudioStreamOut* openOutputStream( + uint32_t devices, + int *format=0, + uint32_t *channels=0, + uint32_t *sampleRate=0, + status_t *status=0); + virtual void closeOutputStream(AudioStreamOut* out); + + virtual ~AudioDumpInterface(); + + virtual status_t initCheck() + {return mFinalInterface->initCheck();} + virtual status_t setVoiceVolume(float volume) + {return mFinalInterface->setVoiceVolume(volume);} + virtual status_t setMasterVolume(float volume) + {return mFinalInterface->setMasterVolume(volume);} + + // mic mute + virtual status_t setMicMute(bool state) + {return mFinalInterface->setMicMute(state);} + virtual status_t getMicMute(bool* state) + {return mFinalInterface->getMicMute(state);} + + virtual status_t setParameters(const String8& keyValuePairs); + virtual String8 getParameters(const String8& keys); + + virtual AudioStreamIn* openInputStream(uint32_t devices, int *format, uint32_t *channels, + uint32_t *sampleRate, status_t *status, AudioSystem::audio_in_acoustics acoustics); + virtual void closeInputStream(AudioStreamIn* in); + + virtual status_t dump(int fd, const Vector<String16>& args) { return mFinalInterface->dumpState(fd, args); } + + String8 fileName() const { return mFileName; } +protected: + + AudioHardwareInterface *mFinalInterface; + SortedVector<AudioStreamOutDump *> mOutputs; + bool mFirstHwOutput; + SortedVector<AudioStreamInDump *> mInputs; + Mutex mLock; + String8 mPolicyCommands; + String8 mFileName; +}; + +}; // namespace android + +#endif // ANDROID_AUDIO_DUMP_INTERFACE_H diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp new file mode 100644 index 0000000..2414e8d --- /dev/null +++ b/services/audioflinger/AudioFlinger.cpp @@ -0,0 +1,4055 @@ +/* //device/include/server/AudioFlinger/AudioFlinger.cpp +** +** Copyright 2007, 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. +*/ + + +#define LOG_TAG "AudioFlinger" +//#define LOG_NDEBUG 0 + +#include <math.h> +#include <signal.h> +#include <sys/time.h> +#include <sys/resource.h> + +#include <binder/IServiceManager.h> +#include <utils/Log.h> +#include <binder/Parcel.h> +#include <binder/IPCThreadState.h> +#include <utils/String16.h> +#include <utils/threads.h> + +#include <cutils/properties.h> + +#include <media/AudioTrack.h> +#include <media/AudioRecord.h> + +#include <private/media/AudioTrackShared.h> + +#include <hardware_legacy/AudioHardwareInterface.h> + +#include "AudioMixer.h" +#include "AudioFlinger.h" + +#ifdef WITH_A2DP +#include "A2dpAudioInterface.h" +#endif + +#ifdef LVMX +#include "lifevibes.h" +#endif + +// ---------------------------------------------------------------------------- +// the sim build doesn't have gettid + +#ifndef HAVE_GETTID +# define gettid getpid +#endif + +// ---------------------------------------------------------------------------- + +namespace android { + +static const char* kDeadlockedString = "AudioFlinger may be deadlocked\n"; +static const char* kHardwareLockedString = "Hardware lock is taken\n"; + +//static const nsecs_t kStandbyTimeInNsecs = seconds(3); +static const float MAX_GAIN = 4096.0f; + +// retry counts for buffer fill timeout +// 50 * ~20msecs = 1 second +static const int8_t kMaxTrackRetries = 50; +static const int8_t kMaxTrackStartupRetries = 50; +// allow less retry attempts on direct output thread. +// direct outputs can be a scarce resource in audio hardware and should +// be released as quickly as possible. +static const int8_t kMaxTrackRetriesDirect = 2; + +static const int kDumpLockRetries = 50; +static const int kDumpLockSleep = 20000; + +static const nsecs_t kWarningThrottle = seconds(5); + + +#define AUDIOFLINGER_SECURITY_ENABLED 1 + +// ---------------------------------------------------------------------------- + +static bool recordingAllowed() { +#ifndef HAVE_ANDROID_OS + return true; +#endif +#if AUDIOFLINGER_SECURITY_ENABLED + if (getpid() == IPCThreadState::self()->getCallingPid()) return true; + bool ok = checkCallingPermission(String16("android.permission.RECORD_AUDIO")); + if (!ok) LOGE("Request requires android.permission.RECORD_AUDIO"); + return ok; +#else + if (!checkCallingPermission(String16("android.permission.RECORD_AUDIO"))) + LOGW("WARNING: Need to add android.permission.RECORD_AUDIO to manifest"); + return true; +#endif +} + +static bool settingsAllowed() { +#ifndef HAVE_ANDROID_OS + return true; +#endif +#if AUDIOFLINGER_SECURITY_ENABLED + if (getpid() == IPCThreadState::self()->getCallingPid()) return true; + bool ok = checkCallingPermission(String16("android.permission.MODIFY_AUDIO_SETTINGS")); + if (!ok) LOGE("Request requires android.permission.MODIFY_AUDIO_SETTINGS"); + return ok; +#else + if (!checkCallingPermission(String16("android.permission.MODIFY_AUDIO_SETTINGS"))) + LOGW("WARNING: Need to add android.permission.MODIFY_AUDIO_SETTINGS to manifest"); + return true; +#endif +} + +// ---------------------------------------------------------------------------- + +AudioFlinger::AudioFlinger() + : BnAudioFlinger(), + mAudioHardware(0), mMasterVolume(1.0f), mMasterMute(false), mNextThreadId(0) +{ + mHardwareStatus = AUDIO_HW_IDLE; + + mAudioHardware = AudioHardwareInterface::create(); + + mHardwareStatus = AUDIO_HW_INIT; + if (mAudioHardware->initCheck() == NO_ERROR) { + // open 16-bit output stream for s/w mixer + + setMode(AudioSystem::MODE_NORMAL); + + setMasterVolume(1.0f); + setMasterMute(false); + } else { + LOGE("Couldn't even initialize the stubbed audio hardware!"); + } +#ifdef LVMX + LifeVibes::init(); +#endif +} + +AudioFlinger::~AudioFlinger() +{ + while (!mRecordThreads.isEmpty()) { + // closeInput() will remove first entry from mRecordThreads + closeInput(mRecordThreads.keyAt(0)); + } + while (!mPlaybackThreads.isEmpty()) { + // closeOutput() will remove first entry from mPlaybackThreads + closeOutput(mPlaybackThreads.keyAt(0)); + } + if (mAudioHardware) { + delete mAudioHardware; + } +} + + + +status_t AudioFlinger::dumpClients(int fd, const Vector<String16>& args) +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + + result.append("Clients:\n"); + for (size_t i = 0; i < mClients.size(); ++i) { + wp<Client> wClient = mClients.valueAt(i); + if (wClient != 0) { + sp<Client> client = wClient.promote(); + if (client != 0) { + snprintf(buffer, SIZE, " pid: %d\n", client->pid()); + result.append(buffer); + } + } + } + write(fd, result.string(), result.size()); + return NO_ERROR; +} + + +status_t AudioFlinger::dumpInternals(int fd, const Vector<String16>& args) +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + int hardwareStatus = mHardwareStatus; + + snprintf(buffer, SIZE, "Hardware status: %d\n", hardwareStatus); + result.append(buffer); + write(fd, result.string(), result.size()); + return NO_ERROR; +} + +status_t AudioFlinger::dumpPermissionDenial(int fd, const Vector<String16>& args) +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + snprintf(buffer, SIZE, "Permission Denial: " + "can't dump AudioFlinger from pid=%d, uid=%d\n", + IPCThreadState::self()->getCallingPid(), + IPCThreadState::self()->getCallingUid()); + result.append(buffer); + write(fd, result.string(), result.size()); + return NO_ERROR; +} + +static bool tryLock(Mutex& mutex) +{ + bool locked = false; + for (int i = 0; i < kDumpLockRetries; ++i) { + if (mutex.tryLock() == NO_ERROR) { + locked = true; + break; + } + usleep(kDumpLockSleep); + } + return locked; +} + +status_t AudioFlinger::dump(int fd, const Vector<String16>& args) +{ + if (checkCallingPermission(String16("android.permission.DUMP")) == false) { + dumpPermissionDenial(fd, args); + } else { + // get state of hardware lock + bool hardwareLocked = tryLock(mHardwareLock); + if (!hardwareLocked) { + String8 result(kHardwareLockedString); + write(fd, result.string(), result.size()); + } else { + mHardwareLock.unlock(); + } + + bool locked = tryLock(mLock); + + // failed to lock - AudioFlinger is probably deadlocked + if (!locked) { + String8 result(kDeadlockedString); + write(fd, result.string(), result.size()); + } + + dumpClients(fd, args); + dumpInternals(fd, args); + + // dump playback threads + for (size_t i = 0; i < mPlaybackThreads.size(); i++) { + mPlaybackThreads.valueAt(i)->dump(fd, args); + } + + // dump record threads + for (size_t i = 0; i < mRecordThreads.size(); i++) { + mRecordThreads.valueAt(i)->dump(fd, args); + } + + if (mAudioHardware) { + mAudioHardware->dumpState(fd, args); + } + if (locked) mLock.unlock(); + } + return NO_ERROR; +} + + +// IAudioFlinger interface + + +sp<IAudioTrack> AudioFlinger::createTrack( + pid_t pid, + int streamType, + uint32_t sampleRate, + int format, + int channelCount, + int frameCount, + uint32_t flags, + const sp<IMemory>& sharedBuffer, + int output, + status_t *status) +{ + sp<PlaybackThread::Track> track; + sp<TrackHandle> trackHandle; + sp<Client> client; + wp<Client> wclient; + status_t lStatus; + + if (streamType >= AudioSystem::NUM_STREAM_TYPES) { + LOGE("invalid stream type"); + lStatus = BAD_VALUE; + goto Exit; + } + + { + Mutex::Autolock _l(mLock); + PlaybackThread *thread = checkPlaybackThread_l(output); + if (thread == NULL) { + LOGE("unknown output thread"); + lStatus = BAD_VALUE; + goto Exit; + } + + wclient = mClients.valueFor(pid); + + if (wclient != NULL) { + client = wclient.promote(); + } else { + client = new Client(this, pid); + mClients.add(pid, client); + } + track = thread->createTrack_l(client, streamType, sampleRate, format, + channelCount, frameCount, sharedBuffer, &lStatus); + } + if (lStatus == NO_ERROR) { + trackHandle = new TrackHandle(track); + } else { + // remove local strong reference to Client before deleting the Track so that the Client + // destructor is called by the TrackBase destructor with mLock held + client.clear(); + track.clear(); + } + +Exit: + if(status) { + *status = lStatus; + } + return trackHandle; +} + +uint32_t AudioFlinger::sampleRate(int output) const +{ + Mutex::Autolock _l(mLock); + PlaybackThread *thread = checkPlaybackThread_l(output); + if (thread == NULL) { + LOGW("sampleRate() unknown thread %d", output); + return 0; + } + return thread->sampleRate(); +} + +int AudioFlinger::channelCount(int output) const +{ + Mutex::Autolock _l(mLock); + PlaybackThread *thread = checkPlaybackThread_l(output); + if (thread == NULL) { + LOGW("channelCount() unknown thread %d", output); + return 0; + } + return thread->channelCount(); +} + +int AudioFlinger::format(int output) const +{ + Mutex::Autolock _l(mLock); + PlaybackThread *thread = checkPlaybackThread_l(output); + if (thread == NULL) { + LOGW("format() unknown thread %d", output); + return 0; + } + return thread->format(); +} + +size_t AudioFlinger::frameCount(int output) const +{ + Mutex::Autolock _l(mLock); + PlaybackThread *thread = checkPlaybackThread_l(output); + if (thread == NULL) { + LOGW("frameCount() unknown thread %d", output); + return 0; + } + return thread->frameCount(); +} + +uint32_t AudioFlinger::latency(int output) const +{ + Mutex::Autolock _l(mLock); + PlaybackThread *thread = checkPlaybackThread_l(output); + if (thread == NULL) { + LOGW("latency() unknown thread %d", output); + return 0; + } + return thread->latency(); +} + +status_t AudioFlinger::setMasterVolume(float value) +{ + // check calling permissions + if (!settingsAllowed()) { + return PERMISSION_DENIED; + } + + // when hw supports master volume, don't scale in sw mixer + AutoMutex lock(mHardwareLock); + mHardwareStatus = AUDIO_HW_SET_MASTER_VOLUME; + if (mAudioHardware->setMasterVolume(value) == NO_ERROR) { + value = 1.0f; + } + mHardwareStatus = AUDIO_HW_IDLE; + + mMasterVolume = value; + for (uint32_t i = 0; i < mPlaybackThreads.size(); i++) + mPlaybackThreads.valueAt(i)->setMasterVolume(value); + + return NO_ERROR; +} + +status_t AudioFlinger::setMode(int mode) +{ + // check calling permissions + if (!settingsAllowed()) { + return PERMISSION_DENIED; + } + if ((mode < 0) || (mode >= AudioSystem::NUM_MODES)) { + LOGW("Illegal value: setMode(%d)", mode); + return BAD_VALUE; + } + + AutoMutex lock(mHardwareLock); + mHardwareStatus = AUDIO_HW_SET_MODE; + status_t ret = mAudioHardware->setMode(mode); +#ifdef LVMX + if (NO_ERROR == ret) { + LifeVibes::setMode(mode); + } +#endif + mHardwareStatus = AUDIO_HW_IDLE; + return ret; +} + +status_t AudioFlinger::setMicMute(bool state) +{ + // check calling permissions + if (!settingsAllowed()) { + return PERMISSION_DENIED; + } + + AutoMutex lock(mHardwareLock); + mHardwareStatus = AUDIO_HW_SET_MIC_MUTE; + status_t ret = mAudioHardware->setMicMute(state); + mHardwareStatus = AUDIO_HW_IDLE; + return ret; +} + +bool AudioFlinger::getMicMute() const +{ + bool state = AudioSystem::MODE_INVALID; + mHardwareStatus = AUDIO_HW_GET_MIC_MUTE; + mAudioHardware->getMicMute(&state); + mHardwareStatus = AUDIO_HW_IDLE; + return state; +} + +status_t AudioFlinger::setMasterMute(bool muted) +{ + // check calling permissions + if (!settingsAllowed()) { + return PERMISSION_DENIED; + } + + mMasterMute = muted; + for (uint32_t i = 0; i < mPlaybackThreads.size(); i++) + mPlaybackThreads.valueAt(i)->setMasterMute(muted); + + return NO_ERROR; +} + +float AudioFlinger::masterVolume() const +{ + return mMasterVolume; +} + +bool AudioFlinger::masterMute() const +{ + return mMasterMute; +} + +status_t AudioFlinger::setStreamVolume(int stream, float value, int output) +{ + // check calling permissions + if (!settingsAllowed()) { + return PERMISSION_DENIED; + } + + if (stream < 0 || uint32_t(stream) >= AudioSystem::NUM_STREAM_TYPES) { + return BAD_VALUE; + } + + AutoMutex lock(mLock); + PlaybackThread *thread = NULL; + if (output) { + thread = checkPlaybackThread_l(output); + if (thread == NULL) { + return BAD_VALUE; + } + } + + mStreamTypes[stream].volume = value; + + if (thread == NULL) { + for (uint32_t i = 0; i < mPlaybackThreads.size(); i++) { + mPlaybackThreads.valueAt(i)->setStreamVolume(stream, value); + } + } else { + thread->setStreamVolume(stream, value); + } + + return NO_ERROR; +} + +status_t AudioFlinger::setStreamMute(int stream, bool muted) +{ + // check calling permissions + if (!settingsAllowed()) { + return PERMISSION_DENIED; + } + + if (stream < 0 || uint32_t(stream) >= AudioSystem::NUM_STREAM_TYPES || + uint32_t(stream) == AudioSystem::ENFORCED_AUDIBLE) { + return BAD_VALUE; + } + + mStreamTypes[stream].mute = muted; + for (uint32_t i = 0; i < mPlaybackThreads.size(); i++) + mPlaybackThreads.valueAt(i)->setStreamMute(stream, muted); + + return NO_ERROR; +} + +float AudioFlinger::streamVolume(int stream, int output) const +{ + if (stream < 0 || uint32_t(stream) >= AudioSystem::NUM_STREAM_TYPES) { + return 0.0f; + } + + AutoMutex lock(mLock); + float volume; + if (output) { + PlaybackThread *thread = checkPlaybackThread_l(output); + if (thread == NULL) { + return 0.0f; + } + volume = thread->streamVolume(stream); + } else { + volume = mStreamTypes[stream].volume; + } + + return volume; +} + +bool AudioFlinger::streamMute(int stream) const +{ + if (stream < 0 || stream >= (int)AudioSystem::NUM_STREAM_TYPES) { + return true; + } + + return mStreamTypes[stream].mute; +} + +bool AudioFlinger::isStreamActive(int stream) const +{ + Mutex::Autolock _l(mLock); + for (uint32_t i = 0; i < mPlaybackThreads.size(); i++) { + if (mPlaybackThreads.valueAt(i)->isStreamActive(stream)) { + return true; + } + } + return false; +} + +status_t AudioFlinger::setParameters(int ioHandle, const String8& keyValuePairs) +{ + status_t result; + + LOGV("setParameters(): io %d, keyvalue %s, tid %d, calling tid %d", + ioHandle, keyValuePairs.string(), gettid(), IPCThreadState::self()->getCallingPid()); + // check calling permissions + if (!settingsAllowed()) { + return PERMISSION_DENIED; + } + +#ifdef LVMX + AudioParameter param = AudioParameter(keyValuePairs); + LifeVibes::setParameters(ioHandle,keyValuePairs); + String8 key = String8(AudioParameter::keyRouting); + int device; + if (NO_ERROR != param.getInt(key, device)) { + device = -1; + } + + key = String8(LifevibesTag); + String8 value; + int musicEnabled = -1; + if (NO_ERROR == param.get(key, value)) { + if (value == LifevibesEnable) { + musicEnabled = 1; + } else if (value == LifevibesDisable) { + musicEnabled = 0; + } + } +#endif + + // ioHandle == 0 means the parameters are global to the audio hardware interface + if (ioHandle == 0) { + AutoMutex lock(mHardwareLock); + mHardwareStatus = AUDIO_SET_PARAMETER; + result = mAudioHardware->setParameters(keyValuePairs); +#ifdef LVMX + if ((NO_ERROR == result) && (musicEnabled != -1)) { + LifeVibes::enableMusic((bool) musicEnabled); + } +#endif + mHardwareStatus = AUDIO_HW_IDLE; + return result; + } + + // hold a strong ref on thread in case closeOutput() or closeInput() is called + // and the thread is exited once the lock is released + sp<ThreadBase> thread; + { + Mutex::Autolock _l(mLock); + thread = checkPlaybackThread_l(ioHandle); + if (thread == NULL) { + thread = checkRecordThread_l(ioHandle); + } + } + if (thread != NULL) { + result = thread->setParameters(keyValuePairs); +#ifdef LVMX + if ((NO_ERROR == result) && (device != -1)) { + LifeVibes::setDevice(LifeVibes::threadIdToAudioOutputType(thread->id()), device); + } +#endif + return result; + } + return BAD_VALUE; +} + +String8 AudioFlinger::getParameters(int ioHandle, const String8& keys) +{ +// LOGV("getParameters() io %d, keys %s, tid %d, calling tid %d", +// ioHandle, keys.string(), gettid(), IPCThreadState::self()->getCallingPid()); + + if (ioHandle == 0) { + return mAudioHardware->getParameters(keys); + } + + Mutex::Autolock _l(mLock); + + PlaybackThread *playbackThread = checkPlaybackThread_l(ioHandle); + if (playbackThread != NULL) { + return playbackThread->getParameters(keys); + } + RecordThread *recordThread = checkRecordThread_l(ioHandle); + if (recordThread != NULL) { + return recordThread->getParameters(keys); + } + return String8(""); +} + +size_t AudioFlinger::getInputBufferSize(uint32_t sampleRate, int format, int channelCount) +{ + return mAudioHardware->getInputBufferSize(sampleRate, format, channelCount); +} + +unsigned int AudioFlinger::getInputFramesLost(int ioHandle) +{ + if (ioHandle == 0) { + return 0; + } + + Mutex::Autolock _l(mLock); + + RecordThread *recordThread = checkRecordThread_l(ioHandle); + if (recordThread != NULL) { + return recordThread->getInputFramesLost(); + } + return 0; +} + +status_t AudioFlinger::setVoiceVolume(float value) +{ + // check calling permissions + if (!settingsAllowed()) { + return PERMISSION_DENIED; + } + + AutoMutex lock(mHardwareLock); + mHardwareStatus = AUDIO_SET_VOICE_VOLUME; + status_t ret = mAudioHardware->setVoiceVolume(value); + mHardwareStatus = AUDIO_HW_IDLE; + + return ret; +} + +status_t AudioFlinger::getRenderPosition(uint32_t *halFrames, uint32_t *dspFrames, int output) +{ + status_t status; + + Mutex::Autolock _l(mLock); + + PlaybackThread *playbackThread = checkPlaybackThread_l(output); + if (playbackThread != NULL) { + return playbackThread->getRenderPosition(halFrames, dspFrames); + } + + return BAD_VALUE; +} + +void AudioFlinger::registerClient(const sp<IAudioFlingerClient>& client) +{ + + LOGV("registerClient() %p, tid %d, calling tid %d", client.get(), gettid(), IPCThreadState::self()->getCallingPid()); + Mutex::Autolock _l(mLock); + + sp<IBinder> binder = client->asBinder(); + if (mNotificationClients.indexOf(binder) < 0) { + LOGV("Adding notification client %p", binder.get()); + binder->linkToDeath(this); + mNotificationClients.add(binder); + } + + // the config change is always sent from playback or record threads to avoid deadlock + // with AudioSystem::gLock + for (size_t i = 0; i < mPlaybackThreads.size(); i++) { + mPlaybackThreads.valueAt(i)->sendConfigEvent(AudioSystem::OUTPUT_OPENED); + } + + for (size_t i = 0; i < mRecordThreads.size(); i++) { + mRecordThreads.valueAt(i)->sendConfigEvent(AudioSystem::INPUT_OPENED); + } +} + +void AudioFlinger::binderDied(const wp<IBinder>& who) { + + LOGV("binderDied() %p, tid %d, calling tid %d", who.unsafe_get(), gettid(), IPCThreadState::self()->getCallingPid()); + Mutex::Autolock _l(mLock); + + IBinder *binder = who.unsafe_get(); + + if (binder != NULL) { + int index = mNotificationClients.indexOf(binder); + if (index >= 0) { + LOGV("Removing notification client %p", binder); + mNotificationClients.removeAt(index); + } + } +} + +// audioConfigChanged_l() must be called with AudioFlinger::mLock held +void AudioFlinger::audioConfigChanged_l(int event, int ioHandle, void *param2) { + size_t size = mNotificationClients.size(); + for (size_t i = 0; i < size; i++) { + sp<IBinder> binder = mNotificationClients.itemAt(i); + LOGV("audioConfigChanged_l() Notifying change to client %p", binder.get()); + sp<IAudioFlingerClient> client = interface_cast<IAudioFlingerClient> (binder); + client->ioConfigChanged(event, ioHandle, param2); + } +} + +// removeClient_l() must be called with AudioFlinger::mLock held +void AudioFlinger::removeClient_l(pid_t pid) +{ + LOGV("removeClient_l() pid %d, tid %d, calling tid %d", pid, gettid(), IPCThreadState::self()->getCallingPid()); + mClients.removeItem(pid); +} + +// ---------------------------------------------------------------------------- + +AudioFlinger::ThreadBase::ThreadBase(const sp<AudioFlinger>& audioFlinger, int id) + : Thread(false), + mAudioFlinger(audioFlinger), mSampleRate(0), mFrameCount(0), mChannelCount(0), + mFormat(0), mFrameSize(1), mStandby(false), mId(id), mExiting(false) +{ +} + +AudioFlinger::ThreadBase::~ThreadBase() +{ + mParamCond.broadcast(); + mNewParameters.clear(); +} + +void AudioFlinger::ThreadBase::exit() +{ + // keep a strong ref on ourself so that we wont get + // destroyed in the middle of requestExitAndWait() + sp <ThreadBase> strongMe = this; + + LOGV("ThreadBase::exit"); + { + AutoMutex lock(&mLock); + mExiting = true; + requestExit(); + mWaitWorkCV.signal(); + } + requestExitAndWait(); +} + +uint32_t AudioFlinger::ThreadBase::sampleRate() const +{ + return mSampleRate; +} + +int AudioFlinger::ThreadBase::channelCount() const +{ + return mChannelCount; +} + +int AudioFlinger::ThreadBase::format() const +{ + return mFormat; +} + +size_t AudioFlinger::ThreadBase::frameCount() const +{ + return mFrameCount; +} + +status_t AudioFlinger::ThreadBase::setParameters(const String8& keyValuePairs) +{ + status_t status; + + LOGV("ThreadBase::setParameters() %s", keyValuePairs.string()); + Mutex::Autolock _l(mLock); + + mNewParameters.add(keyValuePairs); + mWaitWorkCV.signal(); + // wait condition with timeout in case the thread loop has exited + // before the request could be processed + if (mParamCond.waitRelative(mLock, seconds(2)) == NO_ERROR) { + status = mParamStatus; + mWaitWorkCV.signal(); + } else { + status = TIMED_OUT; + } + return status; +} + +void AudioFlinger::ThreadBase::sendConfigEvent(int event, int param) +{ + Mutex::Autolock _l(mLock); + sendConfigEvent_l(event, param); +} + +// sendConfigEvent_l() must be called with ThreadBase::mLock held +void AudioFlinger::ThreadBase::sendConfigEvent_l(int event, int param) +{ + ConfigEvent *configEvent = new ConfigEvent(); + configEvent->mEvent = event; + configEvent->mParam = param; + mConfigEvents.add(configEvent); + LOGV("sendConfigEvent() num events %d event %d, param %d", mConfigEvents.size(), event, param); + mWaitWorkCV.signal(); +} + +void AudioFlinger::ThreadBase::processConfigEvents() +{ + mLock.lock(); + while(!mConfigEvents.isEmpty()) { + LOGV("processConfigEvents() remaining events %d", mConfigEvents.size()); + ConfigEvent *configEvent = mConfigEvents[0]; + mConfigEvents.removeAt(0); + // release mLock because audioConfigChanged() will lock AudioFlinger mLock + // before calling Audioflinger::audioConfigChanged_l() thus creating + // potential cross deadlock between AudioFlinger::mLock and mLock + mLock.unlock(); + audioConfigChanged(configEvent->mEvent, configEvent->mParam); + delete configEvent; + mLock.lock(); + } + mLock.unlock(); +} + +status_t AudioFlinger::ThreadBase::dumpBase(int fd, const Vector<String16>& args) +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + + bool locked = tryLock(mLock); + if (!locked) { + snprintf(buffer, SIZE, "thread %p maybe dead locked\n", this); + write(fd, buffer, strlen(buffer)); + } + + snprintf(buffer, SIZE, "standby: %d\n", mStandby); + result.append(buffer); + snprintf(buffer, SIZE, "Sample rate: %d\n", mSampleRate); + result.append(buffer); + snprintf(buffer, SIZE, "Frame count: %d\n", mFrameCount); + result.append(buffer); + snprintf(buffer, SIZE, "Channel Count: %d\n", mChannelCount); + result.append(buffer); + snprintf(buffer, SIZE, "Format: %d\n", mFormat); + result.append(buffer); + snprintf(buffer, SIZE, "Frame size: %d\n", mFrameSize); + result.append(buffer); + + snprintf(buffer, SIZE, "\nPending setParameters commands: \n"); + result.append(buffer); + result.append(" Index Command"); + for (size_t i = 0; i < mNewParameters.size(); ++i) { + snprintf(buffer, SIZE, "\n %02d ", i); + result.append(buffer); + result.append(mNewParameters[i]); + } + + snprintf(buffer, SIZE, "\n\nPending config events: \n"); + result.append(buffer); + snprintf(buffer, SIZE, " Index event param\n"); + result.append(buffer); + for (size_t i = 0; i < mConfigEvents.size(); i++) { + snprintf(buffer, SIZE, " %02d %02d %d\n", i, mConfigEvents[i]->mEvent, mConfigEvents[i]->mParam); + result.append(buffer); + } + result.append("\n"); + + write(fd, result.string(), result.size()); + + if (locked) { + mLock.unlock(); + } + return NO_ERROR; +} + + +// ---------------------------------------------------------------------------- + +AudioFlinger::PlaybackThread::PlaybackThread(const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output, int id) + : ThreadBase(audioFlinger, id), + mMixBuffer(0), mSuspended(0), mBytesWritten(0), mOutput(output), + mLastWriteTime(0), mNumWrites(0), mNumDelayedWrites(0), mInWrite(false) +{ + readOutputParameters(); + + mMasterVolume = mAudioFlinger->masterVolume(); + mMasterMute = mAudioFlinger->masterMute(); + + for (int stream = 0; stream < AudioSystem::NUM_STREAM_TYPES; stream++) { + mStreamTypes[stream].volume = mAudioFlinger->streamVolumeInternal(stream); + mStreamTypes[stream].mute = mAudioFlinger->streamMute(stream); + } + // notify client processes that a new input has been opened + sendConfigEvent(AudioSystem::OUTPUT_OPENED); +} + +AudioFlinger::PlaybackThread::~PlaybackThread() +{ + delete [] mMixBuffer; +} + +status_t AudioFlinger::PlaybackThread::dump(int fd, const Vector<String16>& args) +{ + dumpInternals(fd, args); + dumpTracks(fd, args); + return NO_ERROR; +} + +status_t AudioFlinger::PlaybackThread::dumpTracks(int fd, const Vector<String16>& args) +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + + snprintf(buffer, SIZE, "Output thread %p tracks\n", this); + result.append(buffer); + result.append(" Name Clien Typ Fmt Chn Buf S M F SRate LeftV RighV Serv User\n"); + for (size_t i = 0; i < mTracks.size(); ++i) { + sp<Track> track = mTracks[i]; + if (track != 0) { + track->dump(buffer, SIZE); + result.append(buffer); + } + } + + snprintf(buffer, SIZE, "Output thread %p active tracks\n", this); + result.append(buffer); + result.append(" Name Clien Typ Fmt Chn Buf S M F SRate LeftV RighV Serv User\n"); + for (size_t i = 0; i < mActiveTracks.size(); ++i) { + wp<Track> wTrack = mActiveTracks[i]; + if (wTrack != 0) { + sp<Track> track = wTrack.promote(); + if (track != 0) { + track->dump(buffer, SIZE); + result.append(buffer); + } + } + } + write(fd, result.string(), result.size()); + return NO_ERROR; +} + +status_t AudioFlinger::PlaybackThread::dumpInternals(int fd, const Vector<String16>& args) +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + + snprintf(buffer, SIZE, "\nOutput thread %p internals\n", this); + result.append(buffer); + snprintf(buffer, SIZE, "last write occurred (msecs): %llu\n", ns2ms(systemTime() - mLastWriteTime)); + result.append(buffer); + snprintf(buffer, SIZE, "total writes: %d\n", mNumWrites); + result.append(buffer); + snprintf(buffer, SIZE, "delayed writes: %d\n", mNumDelayedWrites); + result.append(buffer); + snprintf(buffer, SIZE, "blocked in write: %d\n", mInWrite); + result.append(buffer); + snprintf(buffer, SIZE, "suspend count: %d\n", mSuspended); + result.append(buffer); + write(fd, result.string(), result.size()); + + dumpBase(fd, args); + + return NO_ERROR; +} + +// Thread virtuals +status_t AudioFlinger::PlaybackThread::readyToRun() +{ + if (mSampleRate == 0) { + LOGE("No working audio driver found."); + return NO_INIT; + } + LOGI("AudioFlinger's thread %p ready to run", this); + return NO_ERROR; +} + +void AudioFlinger::PlaybackThread::onFirstRef() +{ + const size_t SIZE = 256; + char buffer[SIZE]; + + snprintf(buffer, SIZE, "Playback Thread %p", this); + + run(buffer, ANDROID_PRIORITY_URGENT_AUDIO); +} + +// PlaybackThread::createTrack_l() must be called with AudioFlinger::mLock held +sp<AudioFlinger::PlaybackThread::Track> AudioFlinger::PlaybackThread::createTrack_l( + const sp<AudioFlinger::Client>& client, + int streamType, + uint32_t sampleRate, + int format, + int channelCount, + int frameCount, + const sp<IMemory>& sharedBuffer, + status_t *status) +{ + sp<Track> track; + status_t lStatus; + + if (mType == DIRECT) { + if (sampleRate != mSampleRate || format != mFormat || channelCount != mChannelCount) { + LOGE("createTrack_l() Bad parameter: sampleRate %d format %d, channelCount %d for output %p", + sampleRate, format, channelCount, mOutput); + lStatus = BAD_VALUE; + goto Exit; + } + } else { + // Resampler implementation limits input sampling rate to 2 x output sampling rate. + if (sampleRate > mSampleRate*2) { + LOGE("Sample rate out of range: %d mSampleRate %d", sampleRate, mSampleRate); + lStatus = BAD_VALUE; + goto Exit; + } + } + + if (mOutput == 0) { + LOGE("Audio driver not initialized."); + lStatus = NO_INIT; + goto Exit; + } + + { // scope for mLock + Mutex::Autolock _l(mLock); + track = new Track(this, client, streamType, sampleRate, format, + channelCount, frameCount, sharedBuffer); + if (track->getCblk() == NULL || track->name() < 0) { + lStatus = NO_MEMORY; + goto Exit; + } + mTracks.add(track); + } + lStatus = NO_ERROR; + +Exit: + if(status) { + *status = lStatus; + } + return track; +} + +uint32_t AudioFlinger::PlaybackThread::latency() const +{ + if (mOutput) { + return mOutput->latency(); + } + else { + return 0; + } +} + +status_t AudioFlinger::PlaybackThread::setMasterVolume(float value) +{ +#ifdef LVMX + int audioOutputType = LifeVibes::getMixerType(mId, mType); + if (LifeVibes::audioOutputTypeIsLifeVibes(audioOutputType)) { + LifeVibes::setMasterVolume(audioOutputType, value); + } +#endif + mMasterVolume = value; + return NO_ERROR; +} + +status_t AudioFlinger::PlaybackThread::setMasterMute(bool muted) +{ +#ifdef LVMX + int audioOutputType = LifeVibes::getMixerType(mId, mType); + if (LifeVibes::audioOutputTypeIsLifeVibes(audioOutputType)) { + LifeVibes::setMasterMute(audioOutputType, muted); + } +#endif + mMasterMute = muted; + return NO_ERROR; +} + +float AudioFlinger::PlaybackThread::masterVolume() const +{ + return mMasterVolume; +} + +bool AudioFlinger::PlaybackThread::masterMute() const +{ + return mMasterMute; +} + +status_t AudioFlinger::PlaybackThread::setStreamVolume(int stream, float value) +{ +#ifdef LVMX + int audioOutputType = LifeVibes::getMixerType(mId, mType); + if (LifeVibes::audioOutputTypeIsLifeVibes(audioOutputType)) { + LifeVibes::setStreamVolume(audioOutputType, stream, value); + } +#endif + mStreamTypes[stream].volume = value; + return NO_ERROR; +} + +status_t AudioFlinger::PlaybackThread::setStreamMute(int stream, bool muted) +{ +#ifdef LVMX + int audioOutputType = LifeVibes::getMixerType(mId, mType); + if (LifeVibes::audioOutputTypeIsLifeVibes(audioOutputType)) { + LifeVibes::setStreamMute(audioOutputType, stream, muted); + } +#endif + mStreamTypes[stream].mute = muted; + return NO_ERROR; +} + +float AudioFlinger::PlaybackThread::streamVolume(int stream) const +{ + return mStreamTypes[stream].volume; +} + +bool AudioFlinger::PlaybackThread::streamMute(int stream) const +{ + return mStreamTypes[stream].mute; +} + +bool AudioFlinger::PlaybackThread::isStreamActive(int stream) const +{ + Mutex::Autolock _l(mLock); + size_t count = mActiveTracks.size(); + for (size_t i = 0 ; i < count ; ++i) { + sp<Track> t = mActiveTracks[i].promote(); + if (t == 0) continue; + Track* const track = t.get(); + if (t->type() == stream) + return true; + } + return false; +} + +// addTrack_l() must be called with ThreadBase::mLock held +status_t AudioFlinger::PlaybackThread::addTrack_l(const sp<Track>& track) +{ + status_t status = ALREADY_EXISTS; + + // set retry count for buffer fill + track->mRetryCount = kMaxTrackStartupRetries; + if (mActiveTracks.indexOf(track) < 0) { + // the track is newly added, make sure it fills up all its + // buffers before playing. This is to ensure the client will + // effectively get the latency it requested. + track->mFillingUpStatus = Track::FS_FILLING; + track->mResetDone = false; + mActiveTracks.add(track); + status = NO_ERROR; + } + + LOGV("mWaitWorkCV.broadcast"); + mWaitWorkCV.broadcast(); + + return status; +} + +// destroyTrack_l() must be called with ThreadBase::mLock held +void AudioFlinger::PlaybackThread::destroyTrack_l(const sp<Track>& track) +{ + track->mState = TrackBase::TERMINATED; + if (mActiveTracks.indexOf(track) < 0) { + mTracks.remove(track); + deleteTrackName_l(track->name()); + } +} + +String8 AudioFlinger::PlaybackThread::getParameters(const String8& keys) +{ + return mOutput->getParameters(keys); +} + +void AudioFlinger::PlaybackThread::audioConfigChanged(int event, int param) { + AudioSystem::OutputDescriptor desc; + void *param2 = 0; + + LOGV("PlaybackThread::audioConfigChanged, thread %p, event %d, param %d", this, event, param); + + switch (event) { + case AudioSystem::OUTPUT_OPENED: + case AudioSystem::OUTPUT_CONFIG_CHANGED: + desc.channels = mChannelCount; + desc.samplingRate = mSampleRate; + desc.format = mFormat; + desc.frameCount = mFrameCount; + desc.latency = latency(); + param2 = &desc; + break; + + case AudioSystem::STREAM_CONFIG_CHANGED: + param2 = ¶m; + case AudioSystem::OUTPUT_CLOSED: + default: + break; + } + Mutex::Autolock _l(mAudioFlinger->mLock); + mAudioFlinger->audioConfigChanged_l(event, mId, param2); +} + +void AudioFlinger::PlaybackThread::readOutputParameters() +{ + mSampleRate = mOutput->sampleRate(); + mChannelCount = AudioSystem::popCount(mOutput->channels()); + + mFormat = mOutput->format(); + mFrameSize = mOutput->frameSize(); + mFrameCount = mOutput->bufferSize() / mFrameSize; + + // FIXME - Current mixer implementation only supports stereo output: Always + // Allocate a stereo buffer even if HW output is mono. + if (mMixBuffer != NULL) delete mMixBuffer; + mMixBuffer = new int16_t[mFrameCount * 2]; + memset(mMixBuffer, 0, mFrameCount * 2 * sizeof(int16_t)); +} + +status_t AudioFlinger::PlaybackThread::getRenderPosition(uint32_t *halFrames, uint32_t *dspFrames) +{ + if (halFrames == 0 || dspFrames == 0) { + return BAD_VALUE; + } + if (mOutput == 0) { + return INVALID_OPERATION; + } + *halFrames = mBytesWritten/mOutput->frameSize(); + + return mOutput->getRenderPosition(dspFrames); +} + +// ---------------------------------------------------------------------------- + +AudioFlinger::MixerThread::MixerThread(const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output, int id) + : PlaybackThread(audioFlinger, output, id), + mAudioMixer(0) +{ + mType = PlaybackThread::MIXER; + mAudioMixer = new AudioMixer(mFrameCount, mSampleRate); + + // FIXME - Current mixer implementation only supports stereo output + if (mChannelCount == 1) { + LOGE("Invalid audio hardware channel count"); + } +} + +AudioFlinger::MixerThread::~MixerThread() +{ + delete mAudioMixer; +} + +bool AudioFlinger::MixerThread::threadLoop() +{ + int16_t* curBuf = mMixBuffer; + Vector< sp<Track> > tracksToRemove; + uint32_t mixerStatus = MIXER_IDLE; + nsecs_t standbyTime = systemTime(); + size_t mixBufferSize = mFrameCount * mFrameSize; + // FIXME: Relaxed timing because of a certain device that can't meet latency + // Should be reduced to 2x after the vendor fixes the driver issue + nsecs_t maxPeriod = seconds(mFrameCount) / mSampleRate * 3; + nsecs_t lastWarning = 0; + bool longStandbyExit = false; + uint32_t activeSleepTime = activeSleepTimeUs(); + uint32_t idleSleepTime = idleSleepTimeUs(); + uint32_t sleepTime = idleSleepTime; + + while (!exitPending()) + { + processConfigEvents(); + + mixerStatus = MIXER_IDLE; + { // scope for mLock + + Mutex::Autolock _l(mLock); + + if (checkForNewParameters_l()) { + mixBufferSize = mFrameCount * mFrameSize; + // FIXME: Relaxed timing because of a certain device that can't meet latency + // Should be reduced to 2x after the vendor fixes the driver issue + maxPeriod = seconds(mFrameCount) / mSampleRate * 3; + activeSleepTime = activeSleepTimeUs(); + idleSleepTime = idleSleepTimeUs(); + } + + const SortedVector< wp<Track> >& activeTracks = mActiveTracks; + + // put audio hardware into standby after short delay + if UNLIKELY((!activeTracks.size() && systemTime() > standbyTime) || + mSuspended) { + if (!mStandby) { + LOGV("Audio hardware entering standby, mixer %p, mSuspended %d\n", this, mSuspended); + mOutput->standby(); + mStandby = true; + mBytesWritten = 0; + } + + if (!activeTracks.size() && mConfigEvents.isEmpty()) { + // we're about to wait, flush the binder command buffer + IPCThreadState::self()->flushCommands(); + + if (exitPending()) break; + + // wait until we have something to do... + LOGV("MixerThread %p TID %d going to sleep\n", this, gettid()); + mWaitWorkCV.wait(mLock); + LOGV("MixerThread %p TID %d waking up\n", this, gettid()); + + if (mMasterMute == false) { + char value[PROPERTY_VALUE_MAX]; + property_get("ro.audio.silent", value, "0"); + if (atoi(value)) { + LOGD("Silence is golden"); + setMasterMute(true); + } + } + + standbyTime = systemTime() + kStandbyTimeInNsecs; + sleepTime = idleSleepTime; + continue; + } + } + + mixerStatus = prepareTracks_l(activeTracks, &tracksToRemove); + } + + if (LIKELY(mixerStatus == MIXER_TRACKS_READY)) { + // mix buffers... + mAudioMixer->process(curBuf); + sleepTime = 0; + standbyTime = systemTime() + kStandbyTimeInNsecs; + } else { + // If no tracks are ready, sleep once for the duration of an output + // buffer size, then write 0s to the output + if (sleepTime == 0) { + if (mixerStatus == MIXER_TRACKS_ENABLED) { + sleepTime = activeSleepTime; + } else { + sleepTime = idleSleepTime; + } + } else if (mBytesWritten != 0 || + (mixerStatus == MIXER_TRACKS_ENABLED && longStandbyExit)) { + memset (curBuf, 0, mixBufferSize); + sleepTime = 0; + LOGV_IF((mBytesWritten == 0 && (mixerStatus == MIXER_TRACKS_ENABLED && longStandbyExit)), "anticipated start"); + } + } + + if (mSuspended) { + sleepTime = idleSleepTime; + } + // sleepTime == 0 means we must write to audio hardware + if (sleepTime == 0) { + mLastWriteTime = systemTime(); + mInWrite = true; + mBytesWritten += mixBufferSize; +#ifdef LVMX + int audioOutputType = LifeVibes::getMixerType(mId, mType); + if (LifeVibes::audioOutputTypeIsLifeVibes(audioOutputType)) { + LifeVibes::process(audioOutputType, curBuf, mixBufferSize); + } +#endif + int bytesWritten = (int)mOutput->write(curBuf, mixBufferSize); + if (bytesWritten < 0) mBytesWritten -= mixBufferSize; + mNumWrites++; + mInWrite = false; + nsecs_t now = systemTime(); + nsecs_t delta = now - mLastWriteTime; + if (delta > maxPeriod) { + mNumDelayedWrites++; + if ((now - lastWarning) > kWarningThrottle) { + LOGW("write blocked for %llu msecs, %d delayed writes, thread %p", + ns2ms(delta), mNumDelayedWrites, this); + lastWarning = now; + } + if (mStandby) { + longStandbyExit = true; + } + } + mStandby = false; + } else { + usleep(sleepTime); + } + + // finally let go of all our tracks, without the lock held + // since we can't guarantee the destructors won't acquire that + // same lock. + tracksToRemove.clear(); + } + + if (!mStandby) { + mOutput->standby(); + } + + LOGV("MixerThread %p exiting", this); + return false; +} + +// prepareTracks_l() must be called with ThreadBase::mLock held +uint32_t AudioFlinger::MixerThread::prepareTracks_l(const SortedVector< wp<Track> >& activeTracks, Vector< sp<Track> > *tracksToRemove) +{ + + uint32_t mixerStatus = MIXER_IDLE; + // find out which tracks need to be processed + size_t count = activeTracks.size(); + + float masterVolume = mMasterVolume; + bool masterMute = mMasterMute; + +#ifdef LVMX + bool tracksConnectedChanged = false; + bool stateChanged = false; + + int audioOutputType = LifeVibes::getMixerType(mId, mType); + if (LifeVibes::audioOutputTypeIsLifeVibes(audioOutputType)) + { + int activeTypes = 0; + for (size_t i=0 ; i<count ; i++) { + sp<Track> t = activeTracks[i].promote(); + if (t == 0) continue; + Track* const track = t.get(); + int iTracktype=track->type(); + activeTypes |= 1<<track->type(); + } + LifeVibes::computeVolumes(audioOutputType, activeTypes, tracksConnectedChanged, stateChanged, masterVolume, masterMute); + } +#endif + + for (size_t i=0 ; i<count ; i++) { + sp<Track> t = activeTracks[i].promote(); + if (t == 0) continue; + + Track* const track = t.get(); + audio_track_cblk_t* cblk = track->cblk(); + + // The first time a track is added we wait + // for all its buffers to be filled before processing it + mAudioMixer->setActiveTrack(track->name()); + if (cblk->framesReady() && (track->isReady() || track->isStopped()) && + !track->isPaused() && !track->isTerminated()) + { + //LOGV("track %d u=%08x, s=%08x [OK] on thread %p", track->name(), cblk->user, cblk->server, this); + + // compute volume for this track + int16_t left, right; + if (track->isMuted() || masterMute || track->isPausing() || + mStreamTypes[track->type()].mute) { + left = right = 0; + if (track->isPausing()) { + track->setPaused(); + } + } else { + // read original volumes with volume control + float typeVolume = mStreamTypes[track->type()].volume; +#ifdef LVMX + bool streamMute=false; + // read the volume from the LivesVibes audio engine. + if (LifeVibes::audioOutputTypeIsLifeVibes(audioOutputType)) + { + LifeVibes::getStreamVolumes(audioOutputType, track->type(), &typeVolume, &streamMute); + if (streamMute) { + typeVolume = 0; + } + } +#endif + float v = masterVolume * typeVolume; + float v_clamped = v * cblk->volume[0]; + if (v_clamped > MAX_GAIN) v_clamped = MAX_GAIN; + left = int16_t(v_clamped); + v_clamped = v * cblk->volume[1]; + if (v_clamped > MAX_GAIN) v_clamped = MAX_GAIN; + right = int16_t(v_clamped); + } + + // XXX: these things DON'T need to be done each time + mAudioMixer->setBufferProvider(track); + mAudioMixer->enable(AudioMixer::MIXING); + + int param = AudioMixer::VOLUME; + if (track->mFillingUpStatus == Track::FS_FILLED) { + // no ramp for the first volume setting + track->mFillingUpStatus = Track::FS_ACTIVE; + if (track->mState == TrackBase::RESUMING) { + track->mState = TrackBase::ACTIVE; + param = AudioMixer::RAMP_VOLUME; + } + } else if (cblk->server != 0) { + // If the track is stopped before the first frame was mixed, + // do not apply ramp + param = AudioMixer::RAMP_VOLUME; + } +#ifdef LVMX + if ( tracksConnectedChanged || stateChanged ) + { + // only do the ramp when the volume is changed by the user / application + param = AudioMixer::VOLUME; + } +#endif + mAudioMixer->setParameter(param, AudioMixer::VOLUME0, left); + mAudioMixer->setParameter(param, AudioMixer::VOLUME1, right); + mAudioMixer->setParameter( + AudioMixer::TRACK, + AudioMixer::FORMAT, track->format()); + mAudioMixer->setParameter( + AudioMixer::TRACK, + AudioMixer::CHANNEL_COUNT, track->channelCount()); + mAudioMixer->setParameter( + AudioMixer::RESAMPLE, + AudioMixer::SAMPLE_RATE, + int(cblk->sampleRate)); + + // reset retry count + track->mRetryCount = kMaxTrackRetries; + mixerStatus = MIXER_TRACKS_READY; + } else { + //LOGV("track %d u=%08x, s=%08x [NOT READY] on thread %p", track->name(), cblk->user, cblk->server, this); + if (track->isStopped()) { + track->reset(); + } + if (track->isTerminated() || track->isStopped() || track->isPaused()) { + // We have consumed all the buffers of this track. + // Remove it from the list of active tracks. + tracksToRemove->add(track); + mAudioMixer->disable(AudioMixer::MIXING); + } else { + // No buffers for this track. Give it a few chances to + // fill a buffer, then remove it from active list. + if (--(track->mRetryCount) <= 0) { + LOGV("BUFFER TIMEOUT: remove(%d) from active list on thread %p", track->name(), this); + tracksToRemove->add(track); + } else if (mixerStatus != MIXER_TRACKS_READY) { + mixerStatus = MIXER_TRACKS_ENABLED; + } + + mAudioMixer->disable(AudioMixer::MIXING); + } + } + } + + // remove all the tracks that need to be... + count = tracksToRemove->size(); + if (UNLIKELY(count)) { + for (size_t i=0 ; i<count ; i++) { + const sp<Track>& track = tracksToRemove->itemAt(i); + mActiveTracks.remove(track); + if (track->isTerminated()) { + mTracks.remove(track); + deleteTrackName_l(track->mName); + } + } + } + + return mixerStatus; +} + +void AudioFlinger::MixerThread::getTracks( + SortedVector < sp<Track> >& tracks, + SortedVector < wp<Track> >& activeTracks, + int streamType) +{ + LOGV ("MixerThread::getTracks() mixer %p, mTracks.size %d, mActiveTracks.size %d", this, mTracks.size(), mActiveTracks.size()); + Mutex::Autolock _l(mLock); + size_t size = mTracks.size(); + for (size_t i = 0; i < size; i++) { + sp<Track> t = mTracks[i]; + if (t->type() == streamType) { + tracks.add(t); + int j = mActiveTracks.indexOf(t); + if (j >= 0) { + t = mActiveTracks[j].promote(); + if (t != NULL) { + activeTracks.add(t); + } + } + } + } + + size = activeTracks.size(); + for (size_t i = 0; i < size; i++) { + mActiveTracks.remove(activeTracks[i]); + } + + size = tracks.size(); + for (size_t i = 0; i < size; i++) { + sp<Track> t = tracks[i]; + mTracks.remove(t); + deleteTrackName_l(t->name()); + } +} + +void AudioFlinger::MixerThread::putTracks( + SortedVector < sp<Track> >& tracks, + SortedVector < wp<Track> >& activeTracks) +{ + LOGV ("MixerThread::putTracks() mixer %p, tracks.size %d, activeTracks.size %d", this, tracks.size(), activeTracks.size()); + Mutex::Autolock _l(mLock); + size_t size = tracks.size(); + for (size_t i = 0; i < size ; i++) { + sp<Track> t = tracks[i]; + int name = getTrackName_l(); + + if (name < 0) return; + + t->mName = name; + t->mThread = this; + mTracks.add(t); + + int j = activeTracks.indexOf(t); + if (j >= 0) { + mActiveTracks.add(t); + // force buffer refilling and no ramp volume when the track is mixed for the first time + t->mFillingUpStatus = Track::FS_FILLING; + } + } +} + +// getTrackName_l() must be called with ThreadBase::mLock held +int AudioFlinger::MixerThread::getTrackName_l() +{ + return mAudioMixer->getTrackName(); +} + +// deleteTrackName_l() must be called with ThreadBase::mLock held +void AudioFlinger::MixerThread::deleteTrackName_l(int name) +{ + LOGV("remove track (%d) and delete from mixer", name); + mAudioMixer->deleteTrackName(name); +} + +// checkForNewParameters_l() must be called with ThreadBase::mLock held +bool AudioFlinger::MixerThread::checkForNewParameters_l() +{ + bool reconfig = false; + + while (!mNewParameters.isEmpty()) { + status_t status = NO_ERROR; + String8 keyValuePair = mNewParameters[0]; + AudioParameter param = AudioParameter(keyValuePair); + int value; + + if (param.getInt(String8(AudioParameter::keySamplingRate), value) == NO_ERROR) { + reconfig = true; + } + if (param.getInt(String8(AudioParameter::keyFormat), value) == NO_ERROR) { + if (value != AudioSystem::PCM_16_BIT) { + status = BAD_VALUE; + } else { + reconfig = true; + } + } + if (param.getInt(String8(AudioParameter::keyChannels), value) == NO_ERROR) { + if (value != AudioSystem::CHANNEL_OUT_STEREO) { + status = BAD_VALUE; + } else { + reconfig = true; + } + } + if (param.getInt(String8(AudioParameter::keyFrameCount), value) == NO_ERROR) { + // do not accept frame count changes if tracks are open as the track buffer + // size depends on frame count and correct behavior would not be garantied + // if frame count is changed after track creation + if (!mTracks.isEmpty()) { + status = INVALID_OPERATION; + } else { + reconfig = true; + } + } + if (status == NO_ERROR) { + status = mOutput->setParameters(keyValuePair); + if (!mStandby && status == INVALID_OPERATION) { + mOutput->standby(); + mStandby = true; + mBytesWritten = 0; + status = mOutput->setParameters(keyValuePair); + } + if (status == NO_ERROR && reconfig) { + delete mAudioMixer; + readOutputParameters(); + mAudioMixer = new AudioMixer(mFrameCount, mSampleRate); + for (size_t i = 0; i < mTracks.size() ; i++) { + int name = getTrackName_l(); + if (name < 0) break; + mTracks[i]->mName = name; + // limit track sample rate to 2 x new output sample rate + if (mTracks[i]->mCblk->sampleRate > 2 * sampleRate()) { + mTracks[i]->mCblk->sampleRate = 2 * sampleRate(); + } + } + sendConfigEvent_l(AudioSystem::OUTPUT_CONFIG_CHANGED); + } + } + + mNewParameters.removeAt(0); + + mParamStatus = status; + mParamCond.signal(); + mWaitWorkCV.wait(mLock); + } + return reconfig; +} + +status_t AudioFlinger::MixerThread::dumpInternals(int fd, const Vector<String16>& args) +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + + PlaybackThread::dumpInternals(fd, args); + + snprintf(buffer, SIZE, "AudioMixer tracks: %08x\n", mAudioMixer->trackNames()); + result.append(buffer); + write(fd, result.string(), result.size()); + return NO_ERROR; +} + +uint32_t AudioFlinger::MixerThread::activeSleepTimeUs() +{ + return (uint32_t)(mOutput->latency() * 1000) / 2; +} + +uint32_t AudioFlinger::MixerThread::idleSleepTimeUs() +{ + return (uint32_t)((mFrameCount * 1000) / mSampleRate) * 1000; +} + +// ---------------------------------------------------------------------------- +AudioFlinger::DirectOutputThread::DirectOutputThread(const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output, int id) + : PlaybackThread(audioFlinger, output, id), + mLeftVolume (1.0), mRightVolume(1.0) +{ + mType = PlaybackThread::DIRECT; +} + +AudioFlinger::DirectOutputThread::~DirectOutputThread() +{ +} + + +bool AudioFlinger::DirectOutputThread::threadLoop() +{ + uint32_t mixerStatus = MIXER_IDLE; + sp<Track> trackToRemove; + sp<Track> activeTrack; + nsecs_t standbyTime = systemTime(); + int8_t *curBuf; + size_t mixBufferSize = mFrameCount*mFrameSize; + uint32_t activeSleepTime = activeSleepTimeUs(); + uint32_t idleSleepTime = idleSleepTimeUs(); + uint32_t sleepTime = idleSleepTime; + // use shorter standby delay as on normal output to release + // hardware resources as soon as possible + nsecs_t standbyDelay = microseconds(activeSleepTime*2); + + + while (!exitPending()) + { + processConfigEvents(); + + mixerStatus = MIXER_IDLE; + + { // scope for the mLock + + Mutex::Autolock _l(mLock); + + if (checkForNewParameters_l()) { + mixBufferSize = mFrameCount*mFrameSize; + activeSleepTime = activeSleepTimeUs(); + idleSleepTime = idleSleepTimeUs(); + standbyDelay = microseconds(activeSleepTime*2); + } + + // put audio hardware into standby after short delay + if UNLIKELY((!mActiveTracks.size() && systemTime() > standbyTime) || + mSuspended) { + // wait until we have something to do... + if (!mStandby) { + LOGV("Audio hardware entering standby, mixer %p\n", this); + mOutput->standby(); + mStandby = true; + mBytesWritten = 0; + } + + if (!mActiveTracks.size() && mConfigEvents.isEmpty()) { + // we're about to wait, flush the binder command buffer + IPCThreadState::self()->flushCommands(); + + if (exitPending()) break; + + LOGV("DirectOutputThread %p TID %d going to sleep\n", this, gettid()); + mWaitWorkCV.wait(mLock); + LOGV("DirectOutputThread %p TID %d waking up in active mode\n", this, gettid()); + + if (mMasterMute == false) { + char value[PROPERTY_VALUE_MAX]; + property_get("ro.audio.silent", value, "0"); + if (atoi(value)) { + LOGD("Silence is golden"); + setMasterMute(true); + } + } + + standbyTime = systemTime() + standbyDelay; + sleepTime = idleSleepTime; + continue; + } + } + + // find out which tracks need to be processed + if (mActiveTracks.size() != 0) { + sp<Track> t = mActiveTracks[0].promote(); + if (t == 0) continue; + + Track* const track = t.get(); + audio_track_cblk_t* cblk = track->cblk(); + + // The first time a track is added we wait + // for all its buffers to be filled before processing it + if (cblk->framesReady() && (track->isReady() || track->isStopped()) && + !track->isPaused() && !track->isTerminated()) + { + //LOGV("track %d u=%08x, s=%08x [OK]", track->name(), cblk->user, cblk->server); + + // compute volume for this track + float left, right; + if (track->isMuted() || mMasterMute || track->isPausing() || + mStreamTypes[track->type()].mute) { + left = right = 0; + if (track->isPausing()) { + track->setPaused(); + } + } else { + float typeVolume = mStreamTypes[track->type()].volume; + float v = mMasterVolume * typeVolume; + float v_clamped = v * cblk->volume[0]; + if (v_clamped > MAX_GAIN) v_clamped = MAX_GAIN; + left = v_clamped/MAX_GAIN; + v_clamped = v * cblk->volume[1]; + if (v_clamped > MAX_GAIN) v_clamped = MAX_GAIN; + right = v_clamped/MAX_GAIN; + } + + if (left != mLeftVolume || right != mRightVolume) { + mOutput->setVolume(left, right); + left = mLeftVolume; + right = mRightVolume; + } + + if (track->mFillingUpStatus == Track::FS_FILLED) { + track->mFillingUpStatus = Track::FS_ACTIVE; + if (track->mState == TrackBase::RESUMING) { + track->mState = TrackBase::ACTIVE; + } + } + + // reset retry count + track->mRetryCount = kMaxTrackRetriesDirect; + activeTrack = t; + mixerStatus = MIXER_TRACKS_READY; + } else { + //LOGV("track %d u=%08x, s=%08x [NOT READY]", track->name(), cblk->user, cblk->server); + if (track->isStopped()) { + track->reset(); + } + if (track->isTerminated() || track->isStopped() || track->isPaused()) { + // We have consumed all the buffers of this track. + // Remove it from the list of active tracks. + trackToRemove = track; + } else { + // No buffers for this track. Give it a few chances to + // fill a buffer, then remove it from active list. + if (--(track->mRetryCount) <= 0) { + LOGV("BUFFER TIMEOUT: remove(%d) from active list", track->name()); + trackToRemove = track; + } else { + mixerStatus = MIXER_TRACKS_ENABLED; + } + } + } + } + + // remove all the tracks that need to be... + if (UNLIKELY(trackToRemove != 0)) { + mActiveTracks.remove(trackToRemove); + if (trackToRemove->isTerminated()) { + mTracks.remove(trackToRemove); + deleteTrackName_l(trackToRemove->mName); + } + } + } + + if (LIKELY(mixerStatus == MIXER_TRACKS_READY)) { + AudioBufferProvider::Buffer buffer; + size_t frameCount = mFrameCount; + curBuf = (int8_t *)mMixBuffer; + // output audio to hardware + while(frameCount) { + buffer.frameCount = frameCount; + activeTrack->getNextBuffer(&buffer); + if (UNLIKELY(buffer.raw == 0)) { + memset(curBuf, 0, frameCount * mFrameSize); + break; + } + memcpy(curBuf, buffer.raw, buffer.frameCount * mFrameSize); + frameCount -= buffer.frameCount; + curBuf += buffer.frameCount * mFrameSize; + activeTrack->releaseBuffer(&buffer); + } + sleepTime = 0; + standbyTime = systemTime() + standbyDelay; + } else { + if (sleepTime == 0) { + if (mixerStatus == MIXER_TRACKS_ENABLED) { + sleepTime = activeSleepTime; + } else { + sleepTime = idleSleepTime; + } + } else if (mBytesWritten != 0 && AudioSystem::isLinearPCM(mFormat)) { + memset (mMixBuffer, 0, mFrameCount * mFrameSize); + sleepTime = 0; + } + } + + if (mSuspended) { + sleepTime = idleSleepTime; + } + // sleepTime == 0 means we must write to audio hardware + if (sleepTime == 0) { + mLastWriteTime = systemTime(); + mInWrite = true; + mBytesWritten += mixBufferSize; + int bytesWritten = (int)mOutput->write(mMixBuffer, mixBufferSize); + if (bytesWritten < 0) mBytesWritten -= mixBufferSize; + mNumWrites++; + mInWrite = false; + mStandby = false; + } else { + usleep(sleepTime); + } + + // finally let go of removed track, without the lock held + // since we can't guarantee the destructors won't acquire that + // same lock. + trackToRemove.clear(); + activeTrack.clear(); + } + + if (!mStandby) { + mOutput->standby(); + } + + LOGV("DirectOutputThread %p exiting", this); + return false; +} + +// getTrackName_l() must be called with ThreadBase::mLock held +int AudioFlinger::DirectOutputThread::getTrackName_l() +{ + return 0; +} + +// deleteTrackName_l() must be called with ThreadBase::mLock held +void AudioFlinger::DirectOutputThread::deleteTrackName_l(int name) +{ +} + +// checkForNewParameters_l() must be called with ThreadBase::mLock held +bool AudioFlinger::DirectOutputThread::checkForNewParameters_l() +{ + bool reconfig = false; + + while (!mNewParameters.isEmpty()) { + status_t status = NO_ERROR; + String8 keyValuePair = mNewParameters[0]; + AudioParameter param = AudioParameter(keyValuePair); + int value; + + if (param.getInt(String8(AudioParameter::keyFrameCount), value) == NO_ERROR) { + // do not accept frame count changes if tracks are open as the track buffer + // size depends on frame count and correct behavior would not be garantied + // if frame count is changed after track creation + if (!mTracks.isEmpty()) { + status = INVALID_OPERATION; + } else { + reconfig = true; + } + } + if (status == NO_ERROR) { + status = mOutput->setParameters(keyValuePair); + if (!mStandby && status == INVALID_OPERATION) { + mOutput->standby(); + mStandby = true; + mBytesWritten = 0; + status = mOutput->setParameters(keyValuePair); + } + if (status == NO_ERROR && reconfig) { + readOutputParameters(); + sendConfigEvent_l(AudioSystem::OUTPUT_CONFIG_CHANGED); + } + } + + mNewParameters.removeAt(0); + + mParamStatus = status; + mParamCond.signal(); + mWaitWorkCV.wait(mLock); + } + return reconfig; +} + +uint32_t AudioFlinger::DirectOutputThread::activeSleepTimeUs() +{ + uint32_t time; + if (AudioSystem::isLinearPCM(mFormat)) { + time = (uint32_t)(mOutput->latency() * 1000) / 2; + } else { + time = 10000; + } + return time; +} + +uint32_t AudioFlinger::DirectOutputThread::idleSleepTimeUs() +{ + uint32_t time; + if (AudioSystem::isLinearPCM(mFormat)) { + time = (uint32_t)((mFrameCount * 1000) / mSampleRate) * 1000; + } else { + time = 10000; + } + return time; +} + +// ---------------------------------------------------------------------------- + +AudioFlinger::DuplicatingThread::DuplicatingThread(const sp<AudioFlinger>& audioFlinger, AudioFlinger::MixerThread* mainThread, int id) + : MixerThread(audioFlinger, mainThread->getOutput(), id), mWaitTimeMs(UINT_MAX) +{ + mType = PlaybackThread::DUPLICATING; + addOutputTrack(mainThread); +} + +AudioFlinger::DuplicatingThread::~DuplicatingThread() +{ + for (size_t i = 0; i < mOutputTracks.size(); i++) { + mOutputTracks[i]->destroy(); + } + mOutputTracks.clear(); +} + +bool AudioFlinger::DuplicatingThread::threadLoop() +{ + int16_t* curBuf = mMixBuffer; + Vector< sp<Track> > tracksToRemove; + uint32_t mixerStatus = MIXER_IDLE; + nsecs_t standbyTime = systemTime(); + size_t mixBufferSize = mFrameCount*mFrameSize; + SortedVector< sp<OutputTrack> > outputTracks; + uint32_t writeFrames = 0; + uint32_t activeSleepTime = activeSleepTimeUs(); + uint32_t idleSleepTime = idleSleepTimeUs(); + uint32_t sleepTime = idleSleepTime; + + while (!exitPending()) + { + processConfigEvents(); + + mixerStatus = MIXER_IDLE; + { // scope for the mLock + + Mutex::Autolock _l(mLock); + + if (checkForNewParameters_l()) { + mixBufferSize = mFrameCount*mFrameSize; + updateWaitTime(); + activeSleepTime = activeSleepTimeUs(); + idleSleepTime = idleSleepTimeUs(); + } + + const SortedVector< wp<Track> >& activeTracks = mActiveTracks; + + for (size_t i = 0; i < mOutputTracks.size(); i++) { + outputTracks.add(mOutputTracks[i]); + } + + // put audio hardware into standby after short delay + if UNLIKELY((!activeTracks.size() && systemTime() > standbyTime) || + mSuspended) { + if (!mStandby) { + for (size_t i = 0; i < outputTracks.size(); i++) { + outputTracks[i]->stop(); + } + mStandby = true; + mBytesWritten = 0; + } + + if (!activeTracks.size() && mConfigEvents.isEmpty()) { + // we're about to wait, flush the binder command buffer + IPCThreadState::self()->flushCommands(); + outputTracks.clear(); + + if (exitPending()) break; + + LOGV("DuplicatingThread %p TID %d going to sleep\n", this, gettid()); + mWaitWorkCV.wait(mLock); + LOGV("DuplicatingThread %p TID %d waking up\n", this, gettid()); + if (mMasterMute == false) { + char value[PROPERTY_VALUE_MAX]; + property_get("ro.audio.silent", value, "0"); + if (atoi(value)) { + LOGD("Silence is golden"); + setMasterMute(true); + } + } + + standbyTime = systemTime() + kStandbyTimeInNsecs; + sleepTime = idleSleepTime; + continue; + } + } + + mixerStatus = prepareTracks_l(activeTracks, &tracksToRemove); + } + + if (LIKELY(mixerStatus == MIXER_TRACKS_READY)) { + // mix buffers... + if (outputsReady(outputTracks)) { + mAudioMixer->process(curBuf); + } else { + memset(curBuf, 0, mixBufferSize); + } + sleepTime = 0; + writeFrames = mFrameCount; + } else { + if (sleepTime == 0) { + if (mixerStatus == MIXER_TRACKS_ENABLED) { + sleepTime = activeSleepTime; + } else { + sleepTime = idleSleepTime; + } + } else if (mBytesWritten != 0) { + // flush remaining overflow buffers in output tracks + for (size_t i = 0; i < outputTracks.size(); i++) { + if (outputTracks[i]->isActive()) { + sleepTime = 0; + writeFrames = 0; + break; + } + } + } + } + + if (mSuspended) { + sleepTime = idleSleepTime; + } + // sleepTime == 0 means we must write to audio hardware + if (sleepTime == 0) { + standbyTime = systemTime() + kStandbyTimeInNsecs; + for (size_t i = 0; i < outputTracks.size(); i++) { + outputTracks[i]->write(curBuf, writeFrames); + } + mStandby = false; + mBytesWritten += mixBufferSize; + } else { + usleep(sleepTime); + } + + // finally let go of all our tracks, without the lock held + // since we can't guarantee the destructors won't acquire that + // same lock. + tracksToRemove.clear(); + outputTracks.clear(); + } + + return false; +} + +void AudioFlinger::DuplicatingThread::addOutputTrack(MixerThread *thread) +{ + int frameCount = (3 * mFrameCount * mSampleRate) / thread->sampleRate(); + OutputTrack *outputTrack = new OutputTrack((ThreadBase *)thread, + this, + mSampleRate, + mFormat, + mChannelCount, + frameCount); + if (outputTrack->cblk() != NULL) { + thread->setStreamVolume(AudioSystem::NUM_STREAM_TYPES, 1.0f); + mOutputTracks.add(outputTrack); + LOGV("addOutputTrack() track %p, on thread %p", outputTrack, thread); + updateWaitTime(); + } +} + +void AudioFlinger::DuplicatingThread::removeOutputTrack(MixerThread *thread) +{ + Mutex::Autolock _l(mLock); + for (size_t i = 0; i < mOutputTracks.size(); i++) { + if (mOutputTracks[i]->thread() == (ThreadBase *)thread) { + mOutputTracks[i]->destroy(); + mOutputTracks.removeAt(i); + updateWaitTime(); + return; + } + } + LOGV("removeOutputTrack(): unkonwn thread: %p", thread); +} + +void AudioFlinger::DuplicatingThread::updateWaitTime() +{ + mWaitTimeMs = UINT_MAX; + for (size_t i = 0; i < mOutputTracks.size(); i++) { + sp<ThreadBase> strong = mOutputTracks[i]->thread().promote(); + if (strong != NULL) { + uint32_t waitTimeMs = (strong->frameCount() * 2 * 1000) / strong->sampleRate(); + if (waitTimeMs < mWaitTimeMs) { + mWaitTimeMs = waitTimeMs; + } + } + } +} + + +bool AudioFlinger::DuplicatingThread::outputsReady(SortedVector< sp<OutputTrack> > &outputTracks) +{ + for (size_t i = 0; i < outputTracks.size(); i++) { + sp <ThreadBase> thread = outputTracks[i]->thread().promote(); + if (thread == 0) { + LOGW("DuplicatingThread::outputsReady() could not promote thread on output track %p", outputTracks[i].get()); + return false; + } + PlaybackThread *playbackThread = (PlaybackThread *)thread.get(); + if (playbackThread->standby() && !playbackThread->isSuspended()) { + LOGV("DuplicatingThread output track %p on thread %p Not Ready", outputTracks[i].get(), thread.get()); + return false; + } + } + return true; +} + +uint32_t AudioFlinger::DuplicatingThread::activeSleepTimeUs() +{ + return (mWaitTimeMs * 1000) / 2; +} + +// ---------------------------------------------------------------------------- + +// TrackBase constructor must be called with AudioFlinger::mLock held +AudioFlinger::ThreadBase::TrackBase::TrackBase( + const wp<ThreadBase>& thread, + const sp<Client>& client, + uint32_t sampleRate, + int format, + int channelCount, + int frameCount, + uint32_t flags, + const sp<IMemory>& sharedBuffer) + : RefBase(), + mThread(thread), + mClient(client), + mCblk(0), + mFrameCount(0), + mState(IDLE), + mClientTid(-1), + mFormat(format), + mFlags(flags & ~SYSTEM_FLAGS_MASK) +{ + LOGV_IF(sharedBuffer != 0, "sharedBuffer: %p, size: %d", sharedBuffer->pointer(), sharedBuffer->size()); + + // LOGD("Creating track with %d buffers @ %d bytes", bufferCount, bufferSize); + size_t size = sizeof(audio_track_cblk_t); + size_t bufferSize = frameCount*channelCount*sizeof(int16_t); + if (sharedBuffer == 0) { + size += bufferSize; + } + + if (client != NULL) { + mCblkMemory = client->heap()->allocate(size); + if (mCblkMemory != 0) { + mCblk = static_cast<audio_track_cblk_t *>(mCblkMemory->pointer()); + if (mCblk) { // construct the shared structure in-place. + new(mCblk) audio_track_cblk_t(); + // clear all buffers + mCblk->frameCount = frameCount; + mCblk->sampleRate = sampleRate; + mCblk->channels = (uint8_t)channelCount; + if (sharedBuffer == 0) { + mBuffer = (char*)mCblk + sizeof(audio_track_cblk_t); + memset(mBuffer, 0, frameCount*channelCount*sizeof(int16_t)); + // Force underrun condition to avoid false underrun callback until first data is + // written to buffer + mCblk->flowControlFlag = 1; + } else { + mBuffer = sharedBuffer->pointer(); + } + mBufferEnd = (uint8_t *)mBuffer + bufferSize; + } + } else { + LOGE("not enough memory for AudioTrack size=%u", size); + client->heap()->dump("AudioTrack"); + return; + } + } else { + mCblk = (audio_track_cblk_t *)(new uint8_t[size]); + if (mCblk) { // construct the shared structure in-place. + new(mCblk) audio_track_cblk_t(); + // clear all buffers + mCblk->frameCount = frameCount; + mCblk->sampleRate = sampleRate; + mCblk->channels = (uint8_t)channelCount; + mBuffer = (char*)mCblk + sizeof(audio_track_cblk_t); + memset(mBuffer, 0, frameCount*channelCount*sizeof(int16_t)); + // Force underrun condition to avoid false underrun callback until first data is + // written to buffer + mCblk->flowControlFlag = 1; + mBufferEnd = (uint8_t *)mBuffer + bufferSize; + } + } +} + +AudioFlinger::ThreadBase::TrackBase::~TrackBase() +{ + if (mCblk) { + mCblk->~audio_track_cblk_t(); // destroy our shared-structure. + if (mClient == NULL) { + delete mCblk; + } + } + mCblkMemory.clear(); // and free the shared memory + if (mClient != NULL) { + Mutex::Autolock _l(mClient->audioFlinger()->mLock); + mClient.clear(); + } +} + +void AudioFlinger::ThreadBase::TrackBase::releaseBuffer(AudioBufferProvider::Buffer* buffer) +{ + buffer->raw = 0; + mFrameCount = buffer->frameCount; + step(); + buffer->frameCount = 0; +} + +bool AudioFlinger::ThreadBase::TrackBase::step() { + bool result; + audio_track_cblk_t* cblk = this->cblk(); + + result = cblk->stepServer(mFrameCount); + if (!result) { + LOGV("stepServer failed acquiring cblk mutex"); + mFlags |= STEPSERVER_FAILED; + } + return result; +} + +void AudioFlinger::ThreadBase::TrackBase::reset() { + audio_track_cblk_t* cblk = this->cblk(); + + cblk->user = 0; + cblk->server = 0; + cblk->userBase = 0; + cblk->serverBase = 0; + mFlags &= (uint32_t)(~SYSTEM_FLAGS_MASK); + LOGV("TrackBase::reset"); +} + +sp<IMemory> AudioFlinger::ThreadBase::TrackBase::getCblk() const +{ + return mCblkMemory; +} + +int AudioFlinger::ThreadBase::TrackBase::sampleRate() const { + return (int)mCblk->sampleRate; +} + +int AudioFlinger::ThreadBase::TrackBase::channelCount() const { + return (int)mCblk->channels; +} + +void* AudioFlinger::ThreadBase::TrackBase::getBuffer(uint32_t offset, uint32_t frames) const { + audio_track_cblk_t* cblk = this->cblk(); + int8_t *bufferStart = (int8_t *)mBuffer + (offset-cblk->serverBase)*cblk->frameSize; + int8_t *bufferEnd = bufferStart + frames * cblk->frameSize; + + // Check validity of returned pointer in case the track control block would have been corrupted. + if (bufferStart < mBuffer || bufferStart > bufferEnd || bufferEnd > mBufferEnd || + ((unsigned long)bufferStart & (unsigned long)(cblk->frameSize - 1))) { + LOGE("TrackBase::getBuffer buffer out of range:\n start: %p, end %p , mBuffer %p mBufferEnd %p\n \ + server %d, serverBase %d, user %d, userBase %d, channels %d", + bufferStart, bufferEnd, mBuffer, mBufferEnd, + cblk->server, cblk->serverBase, cblk->user, cblk->userBase, cblk->channels); + return 0; + } + + return bufferStart; +} + +// ---------------------------------------------------------------------------- + +// Track constructor must be called with AudioFlinger::mLock and ThreadBase::mLock held +AudioFlinger::PlaybackThread::Track::Track( + const wp<ThreadBase>& thread, + const sp<Client>& client, + int streamType, + uint32_t sampleRate, + int format, + int channelCount, + int frameCount, + const sp<IMemory>& sharedBuffer) + : TrackBase(thread, client, sampleRate, format, channelCount, frameCount, 0, sharedBuffer), + mMute(false), mSharedBuffer(sharedBuffer), mName(-1) +{ + if (mCblk != NULL) { + sp<ThreadBase> baseThread = thread.promote(); + if (baseThread != 0) { + PlaybackThread *playbackThread = (PlaybackThread *)baseThread.get(); + mName = playbackThread->getTrackName_l(); + } + LOGV("Track constructor name %d, calling thread %d", mName, IPCThreadState::self()->getCallingPid()); + if (mName < 0) { + LOGE("no more track names available"); + } + mVolume[0] = 1.0f; + mVolume[1] = 1.0f; + mStreamType = streamType; + // NOTE: audio_track_cblk_t::frameSize for 8 bit PCM data is based on a sample size of + // 16 bit because data is converted to 16 bit before being stored in buffer by AudioTrack + mCblk->frameSize = AudioSystem::isLinearPCM(format) ? channelCount * sizeof(int16_t) : sizeof(int8_t); + } +} + +AudioFlinger::PlaybackThread::Track::~Track() +{ + LOGV("PlaybackThread::Track destructor"); + sp<ThreadBase> thread = mThread.promote(); + if (thread != 0) { + Mutex::Autolock _l(thread->mLock); + mState = TERMINATED; + } +} + +void AudioFlinger::PlaybackThread::Track::destroy() +{ + // NOTE: destroyTrack_l() can remove a strong reference to this Track + // by removing it from mTracks vector, so there is a risk that this Tracks's + // desctructor is called. As the destructor needs to lock mLock, + // we must acquire a strong reference on this Track before locking mLock + // here so that the destructor is called only when exiting this function. + // On the other hand, as long as Track::destroy() is only called by + // TrackHandle destructor, the TrackHandle still holds a strong ref on + // this Track with its member mTrack. + sp<Track> keep(this); + { // scope for mLock + sp<ThreadBase> thread = mThread.promote(); + if (thread != 0) { + if (!isOutputTrack()) { + if (mState == ACTIVE || mState == RESUMING) { + AudioSystem::stopOutput(thread->id(), (AudioSystem::stream_type)mStreamType); + } + AudioSystem::releaseOutput(thread->id()); + } + Mutex::Autolock _l(thread->mLock); + PlaybackThread *playbackThread = (PlaybackThread *)thread.get(); + playbackThread->destroyTrack_l(this); + } + } +} + +void AudioFlinger::PlaybackThread::Track::dump(char* buffer, size_t size) +{ + snprintf(buffer, size, " %5d %5d %3u %3u %3u %04u %1d %1d %1d %5u %5u %5u %08x %08x\n", + mName - AudioMixer::TRACK0, + (mClient == NULL) ? getpid() : mClient->pid(), + mStreamType, + mFormat, + mCblk->channels, + mFrameCount, + mState, + mMute, + mFillingUpStatus, + mCblk->sampleRate, + mCblk->volume[0], + mCblk->volume[1], + mCblk->server, + mCblk->user); +} + +status_t AudioFlinger::PlaybackThread::Track::getNextBuffer(AudioBufferProvider::Buffer* buffer) +{ + audio_track_cblk_t* cblk = this->cblk(); + uint32_t framesReady; + uint32_t framesReq = buffer->frameCount; + + // Check if last stepServer failed, try to step now + if (mFlags & TrackBase::STEPSERVER_FAILED) { + if (!step()) goto getNextBuffer_exit; + LOGV("stepServer recovered"); + mFlags &= ~TrackBase::STEPSERVER_FAILED; + } + + framesReady = cblk->framesReady(); + + if (LIKELY(framesReady)) { + uint32_t s = cblk->server; + uint32_t bufferEnd = cblk->serverBase + cblk->frameCount; + + bufferEnd = (cblk->loopEnd < bufferEnd) ? cblk->loopEnd : bufferEnd; + if (framesReq > framesReady) { + framesReq = framesReady; + } + if (s + framesReq > bufferEnd) { + framesReq = bufferEnd - s; + } + + buffer->raw = getBuffer(s, framesReq); + if (buffer->raw == 0) goto getNextBuffer_exit; + + buffer->frameCount = framesReq; + return NO_ERROR; + } + +getNextBuffer_exit: + buffer->raw = 0; + buffer->frameCount = 0; + LOGV("getNextBuffer() no more data for track %d on thread %p", mName, mThread.unsafe_get()); + return NOT_ENOUGH_DATA; +} + +bool AudioFlinger::PlaybackThread::Track::isReady() const { + if (mFillingUpStatus != FS_FILLING) return true; + + if (mCblk->framesReady() >= mCblk->frameCount || + mCblk->forceReady) { + mFillingUpStatus = FS_FILLED; + mCblk->forceReady = 0; + return true; + } + return false; +} + +status_t AudioFlinger::PlaybackThread::Track::start() +{ + status_t status = NO_ERROR; + LOGV("start(%d), calling thread %d", mName, IPCThreadState::self()->getCallingPid()); + sp<ThreadBase> thread = mThread.promote(); + if (thread != 0) { + Mutex::Autolock _l(thread->mLock); + int state = mState; + // here the track could be either new, or restarted + // in both cases "unstop" the track + if (mState == PAUSED) { + mState = TrackBase::RESUMING; + LOGV("PAUSED => RESUMING (%d) on thread %p", mName, this); + } else { + mState = TrackBase::ACTIVE; + LOGV("? => ACTIVE (%d) on thread %p", mName, this); + } + + if (!isOutputTrack() && state != ACTIVE && state != RESUMING) { + thread->mLock.unlock(); + status = AudioSystem::startOutput(thread->id(), (AudioSystem::stream_type)mStreamType); + thread->mLock.lock(); + } + if (status == NO_ERROR) { + PlaybackThread *playbackThread = (PlaybackThread *)thread.get(); + playbackThread->addTrack_l(this); + } else { + mState = state; + } + } else { + status = BAD_VALUE; + } + return status; +} + +void AudioFlinger::PlaybackThread::Track::stop() +{ + LOGV("stop(%d), calling thread %d", mName, IPCThreadState::self()->getCallingPid()); + sp<ThreadBase> thread = mThread.promote(); + if (thread != 0) { + Mutex::Autolock _l(thread->mLock); + int state = mState; + if (mState > STOPPED) { + mState = STOPPED; + // If the track is not active (PAUSED and buffers full), flush buffers + PlaybackThread *playbackThread = (PlaybackThread *)thread.get(); + if (playbackThread->mActiveTracks.indexOf(this) < 0) { + reset(); + } + LOGV("(> STOPPED) => STOPPED (%d) on thread %p", mName, playbackThread); + } + if (!isOutputTrack() && (state == ACTIVE || state == RESUMING)) { + thread->mLock.unlock(); + AudioSystem::stopOutput(thread->id(), (AudioSystem::stream_type)mStreamType); + thread->mLock.lock(); + } + } +} + +void AudioFlinger::PlaybackThread::Track::pause() +{ + LOGV("pause(%d), calling thread %d", mName, IPCThreadState::self()->getCallingPid()); + sp<ThreadBase> thread = mThread.promote(); + if (thread != 0) { + Mutex::Autolock _l(thread->mLock); + if (mState == ACTIVE || mState == RESUMING) { + mState = PAUSING; + LOGV("ACTIVE/RESUMING => PAUSING (%d) on thread %p", mName, thread.get()); + if (!isOutputTrack()) { + thread->mLock.unlock(); + AudioSystem::stopOutput(thread->id(), (AudioSystem::stream_type)mStreamType); + thread->mLock.lock(); + } + } + } +} + +void AudioFlinger::PlaybackThread::Track::flush() +{ + LOGV("flush(%d)", mName); + sp<ThreadBase> thread = mThread.promote(); + if (thread != 0) { + Mutex::Autolock _l(thread->mLock); + if (mState != STOPPED && mState != PAUSED && mState != PAUSING) { + return; + } + // No point remaining in PAUSED state after a flush => go to + // STOPPED state + mState = STOPPED; + + mCblk->lock.lock(); + // NOTE: reset() will reset cblk->user and cblk->server with + // the risk that at the same time, the AudioMixer is trying to read + // data. In this case, getNextBuffer() would return a NULL pointer + // as audio buffer => the AudioMixer code MUST always test that pointer + // returned by getNextBuffer() is not NULL! + reset(); + mCblk->lock.unlock(); + } +} + +void AudioFlinger::PlaybackThread::Track::reset() +{ + // Do not reset twice to avoid discarding data written just after a flush and before + // the audioflinger thread detects the track is stopped. + if (!mResetDone) { + TrackBase::reset(); + // Force underrun condition to avoid false underrun callback until first data is + // written to buffer + mCblk->flowControlFlag = 1; + mCblk->forceReady = 0; + mFillingUpStatus = FS_FILLING; + mResetDone = true; + } +} + +void AudioFlinger::PlaybackThread::Track::mute(bool muted) +{ + mMute = muted; +} + +void AudioFlinger::PlaybackThread::Track::setVolume(float left, float right) +{ + mVolume[0] = left; + mVolume[1] = right; +} + +// ---------------------------------------------------------------------------- + +// RecordTrack constructor must be called with AudioFlinger::mLock held +AudioFlinger::RecordThread::RecordTrack::RecordTrack( + const wp<ThreadBase>& thread, + const sp<Client>& client, + uint32_t sampleRate, + int format, + int channelCount, + int frameCount, + uint32_t flags) + : TrackBase(thread, client, sampleRate, format, + channelCount, frameCount, flags, 0), + mOverflow(false) +{ + if (mCblk != NULL) { + LOGV("RecordTrack constructor, size %d", (int)mBufferEnd - (int)mBuffer); + if (format == AudioSystem::PCM_16_BIT) { + mCblk->frameSize = channelCount * sizeof(int16_t); + } else if (format == AudioSystem::PCM_8_BIT) { + mCblk->frameSize = channelCount * sizeof(int8_t); + } else { + mCblk->frameSize = sizeof(int8_t); + } + } +} + +AudioFlinger::RecordThread::RecordTrack::~RecordTrack() +{ + sp<ThreadBase> thread = mThread.promote(); + if (thread != 0) { + AudioSystem::releaseInput(thread->id()); + } +} + +status_t AudioFlinger::RecordThread::RecordTrack::getNextBuffer(AudioBufferProvider::Buffer* buffer) +{ + audio_track_cblk_t* cblk = this->cblk(); + uint32_t framesAvail; + uint32_t framesReq = buffer->frameCount; + + // Check if last stepServer failed, try to step now + if (mFlags & TrackBase::STEPSERVER_FAILED) { + if (!step()) goto getNextBuffer_exit; + LOGV("stepServer recovered"); + mFlags &= ~TrackBase::STEPSERVER_FAILED; + } + + framesAvail = cblk->framesAvailable_l(); + + if (LIKELY(framesAvail)) { + uint32_t s = cblk->server; + uint32_t bufferEnd = cblk->serverBase + cblk->frameCount; + + if (framesReq > framesAvail) { + framesReq = framesAvail; + } + if (s + framesReq > bufferEnd) { + framesReq = bufferEnd - s; + } + + buffer->raw = getBuffer(s, framesReq); + if (buffer->raw == 0) goto getNextBuffer_exit; + + buffer->frameCount = framesReq; + return NO_ERROR; + } + +getNextBuffer_exit: + buffer->raw = 0; + buffer->frameCount = 0; + return NOT_ENOUGH_DATA; +} + +status_t AudioFlinger::RecordThread::RecordTrack::start() +{ + sp<ThreadBase> thread = mThread.promote(); + if (thread != 0) { + RecordThread *recordThread = (RecordThread *)thread.get(); + return recordThread->start(this); + } else { + return BAD_VALUE; + } +} + +void AudioFlinger::RecordThread::RecordTrack::stop() +{ + sp<ThreadBase> thread = mThread.promote(); + if (thread != 0) { + RecordThread *recordThread = (RecordThread *)thread.get(); + recordThread->stop(this); + TrackBase::reset(); + // Force overerrun condition to avoid false overrun callback until first data is + // read from buffer + mCblk->flowControlFlag = 1; + } +} + +void AudioFlinger::RecordThread::RecordTrack::dump(char* buffer, size_t size) +{ + snprintf(buffer, size, " %05d %03u %03u %04u %01d %05u %08x %08x\n", + (mClient == NULL) ? getpid() : mClient->pid(), + mFormat, + mCblk->channels, + mFrameCount, + mState, + mCblk->sampleRate, + mCblk->server, + mCblk->user); +} + + +// ---------------------------------------------------------------------------- + +AudioFlinger::PlaybackThread::OutputTrack::OutputTrack( + const wp<ThreadBase>& thread, + DuplicatingThread *sourceThread, + uint32_t sampleRate, + int format, + int channelCount, + int frameCount) + : Track(thread, NULL, AudioSystem::NUM_STREAM_TYPES, sampleRate, format, channelCount, frameCount, NULL), + mActive(false), mSourceThread(sourceThread) +{ + + PlaybackThread *playbackThread = (PlaybackThread *)thread.unsafe_get(); + if (mCblk != NULL) { + mCblk->out = 1; + mCblk->buffers = (char*)mCblk + sizeof(audio_track_cblk_t); + mCblk->volume[0] = mCblk->volume[1] = 0x1000; + mOutBuffer.frameCount = 0; + playbackThread->mTracks.add(this); + LOGV("OutputTrack constructor mCblk %p, mBuffer %p, mCblk->buffers %p, mCblk->frameCount %d, mCblk->sampleRate %d, mCblk->channels %d mBufferEnd %p", + mCblk, mBuffer, mCblk->buffers, mCblk->frameCount, mCblk->sampleRate, mCblk->channels, mBufferEnd); + } else { + LOGW("Error creating output track on thread %p", playbackThread); + } +} + +AudioFlinger::PlaybackThread::OutputTrack::~OutputTrack() +{ + clearBufferQueue(); +} + +status_t AudioFlinger::PlaybackThread::OutputTrack::start() +{ + status_t status = Track::start(); + if (status != NO_ERROR) { + return status; + } + + mActive = true; + mRetryCount = 127; + return status; +} + +void AudioFlinger::PlaybackThread::OutputTrack::stop() +{ + Track::stop(); + clearBufferQueue(); + mOutBuffer.frameCount = 0; + mActive = false; +} + +bool AudioFlinger::PlaybackThread::OutputTrack::write(int16_t* data, uint32_t frames) +{ + Buffer *pInBuffer; + Buffer inBuffer; + uint32_t channels = mCblk->channels; + bool outputBufferFull = false; + inBuffer.frameCount = frames; + inBuffer.i16 = data; + + uint32_t waitTimeLeftMs = mSourceThread->waitTimeMs(); + + if (!mActive && frames != 0) { + start(); + sp<ThreadBase> thread = mThread.promote(); + if (thread != 0) { + MixerThread *mixerThread = (MixerThread *)thread.get(); + if (mCblk->frameCount > frames){ + if (mBufferQueue.size() < kMaxOverFlowBuffers) { + uint32_t startFrames = (mCblk->frameCount - frames); + pInBuffer = new Buffer; + pInBuffer->mBuffer = new int16_t[startFrames * channels]; + pInBuffer->frameCount = startFrames; + pInBuffer->i16 = pInBuffer->mBuffer; + memset(pInBuffer->raw, 0, startFrames * channels * sizeof(int16_t)); + mBufferQueue.add(pInBuffer); + } else { + LOGW ("OutputTrack::write() %p no more buffers in queue", this); + } + } + } + } + + while (waitTimeLeftMs) { + // First write pending buffers, then new data + if (mBufferQueue.size()) { + pInBuffer = mBufferQueue.itemAt(0); + } else { + pInBuffer = &inBuffer; + } + + if (pInBuffer->frameCount == 0) { + break; + } + + if (mOutBuffer.frameCount == 0) { + mOutBuffer.frameCount = pInBuffer->frameCount; + nsecs_t startTime = systemTime(); + if (obtainBuffer(&mOutBuffer, waitTimeLeftMs) == (status_t)AudioTrack::NO_MORE_BUFFERS) { + LOGV ("OutputTrack::write() %p thread %p no more output buffers", this, mThread.unsafe_get()); + outputBufferFull = true; + break; + } + uint32_t waitTimeMs = (uint32_t)ns2ms(systemTime() - startTime); + if (waitTimeLeftMs >= waitTimeMs) { + waitTimeLeftMs -= waitTimeMs; + } else { + waitTimeLeftMs = 0; + } + } + + uint32_t outFrames = pInBuffer->frameCount > mOutBuffer.frameCount ? mOutBuffer.frameCount : pInBuffer->frameCount; + memcpy(mOutBuffer.raw, pInBuffer->raw, outFrames * channels * sizeof(int16_t)); + mCblk->stepUser(outFrames); + pInBuffer->frameCount -= outFrames; + pInBuffer->i16 += outFrames * channels; + mOutBuffer.frameCount -= outFrames; + mOutBuffer.i16 += outFrames * channels; + + if (pInBuffer->frameCount == 0) { + if (mBufferQueue.size()) { + mBufferQueue.removeAt(0); + delete [] pInBuffer->mBuffer; + delete pInBuffer; + LOGV("OutputTrack::write() %p thread %p released overflow buffer %d", this, mThread.unsafe_get(), mBufferQueue.size()); + } else { + break; + } + } + } + + // If we could not write all frames, allocate a buffer and queue it for next time. + if (inBuffer.frameCount) { + sp<ThreadBase> thread = mThread.promote(); + if (thread != 0 && !thread->standby()) { + if (mBufferQueue.size() < kMaxOverFlowBuffers) { + pInBuffer = new Buffer; + pInBuffer->mBuffer = new int16_t[inBuffer.frameCount * channels]; + pInBuffer->frameCount = inBuffer.frameCount; + pInBuffer->i16 = pInBuffer->mBuffer; + memcpy(pInBuffer->raw, inBuffer.raw, inBuffer.frameCount * channels * sizeof(int16_t)); + mBufferQueue.add(pInBuffer); + LOGV("OutputTrack::write() %p thread %p adding overflow buffer %d", this, mThread.unsafe_get(), mBufferQueue.size()); + } else { + LOGW("OutputTrack::write() %p thread %p no more overflow buffers", mThread.unsafe_get(), this); + } + } + } + + // Calling write() with a 0 length buffer, means that no more data will be written: + // If no more buffers are pending, fill output track buffer to make sure it is started + // by output mixer. + if (frames == 0 && mBufferQueue.size() == 0) { + if (mCblk->user < mCblk->frameCount) { + frames = mCblk->frameCount - mCblk->user; + pInBuffer = new Buffer; + pInBuffer->mBuffer = new int16_t[frames * channels]; + pInBuffer->frameCount = frames; + pInBuffer->i16 = pInBuffer->mBuffer; + memset(pInBuffer->raw, 0, frames * channels * sizeof(int16_t)); + mBufferQueue.add(pInBuffer); + } else if (mActive) { + stop(); + } + } + + return outputBufferFull; +} + +status_t AudioFlinger::PlaybackThread::OutputTrack::obtainBuffer(AudioBufferProvider::Buffer* buffer, uint32_t waitTimeMs) +{ + int active; + status_t result; + audio_track_cblk_t* cblk = mCblk; + uint32_t framesReq = buffer->frameCount; + +// LOGV("OutputTrack::obtainBuffer user %d, server %d", cblk->user, cblk->server); + buffer->frameCount = 0; + + uint32_t framesAvail = cblk->framesAvailable(); + + + if (framesAvail == 0) { + Mutex::Autolock _l(cblk->lock); + goto start_loop_here; + while (framesAvail == 0) { + active = mActive; + if (UNLIKELY(!active)) { + LOGV("Not active and NO_MORE_BUFFERS"); + return AudioTrack::NO_MORE_BUFFERS; + } + result = cblk->cv.waitRelative(cblk->lock, milliseconds(waitTimeMs)); + if (result != NO_ERROR) { + return AudioTrack::NO_MORE_BUFFERS; + } + // read the server count again + start_loop_here: + framesAvail = cblk->framesAvailable_l(); + } + } + +// if (framesAvail < framesReq) { +// return AudioTrack::NO_MORE_BUFFERS; +// } + + if (framesReq > framesAvail) { + framesReq = framesAvail; + } + + uint32_t u = cblk->user; + uint32_t bufferEnd = cblk->userBase + cblk->frameCount; + + if (u + framesReq > bufferEnd) { + framesReq = bufferEnd - u; + } + + buffer->frameCount = framesReq; + buffer->raw = (void *)cblk->buffer(u); + return NO_ERROR; +} + + +void AudioFlinger::PlaybackThread::OutputTrack::clearBufferQueue() +{ + size_t size = mBufferQueue.size(); + Buffer *pBuffer; + + for (size_t i = 0; i < size; i++) { + pBuffer = mBufferQueue.itemAt(i); + delete [] pBuffer->mBuffer; + delete pBuffer; + } + mBufferQueue.clear(); +} + +// ---------------------------------------------------------------------------- + +AudioFlinger::Client::Client(const sp<AudioFlinger>& audioFlinger, pid_t pid) + : RefBase(), + mAudioFlinger(audioFlinger), + mMemoryDealer(new MemoryDealer(1024*1024, "AudioFlinger::Client")), + mPid(pid) +{ + // 1 MB of address space is good for 32 tracks, 8 buffers each, 4 KB/buffer +} + +// Client destructor must be called with AudioFlinger::mLock held +AudioFlinger::Client::~Client() +{ + mAudioFlinger->removeClient_l(mPid); +} + +const sp<MemoryDealer>& AudioFlinger::Client::heap() const +{ + return mMemoryDealer; +} + +// ---------------------------------------------------------------------------- + +AudioFlinger::TrackHandle::TrackHandle(const sp<AudioFlinger::PlaybackThread::Track>& track) + : BnAudioTrack(), + mTrack(track) +{ +} + +AudioFlinger::TrackHandle::~TrackHandle() { + // just stop the track on deletion, associated resources + // will be freed from the main thread once all pending buffers have + // been played. Unless it's not in the active track list, in which + // case we free everything now... + mTrack->destroy(); +} + +status_t AudioFlinger::TrackHandle::start() { + return mTrack->start(); +} + +void AudioFlinger::TrackHandle::stop() { + mTrack->stop(); +} + +void AudioFlinger::TrackHandle::flush() { + mTrack->flush(); +} + +void AudioFlinger::TrackHandle::mute(bool e) { + mTrack->mute(e); +} + +void AudioFlinger::TrackHandle::pause() { + mTrack->pause(); +} + +void AudioFlinger::TrackHandle::setVolume(float left, float right) { + mTrack->setVolume(left, right); +} + +sp<IMemory> AudioFlinger::TrackHandle::getCblk() const { + return mTrack->getCblk(); +} + +status_t AudioFlinger::TrackHandle::onTransact( + uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) +{ + return BnAudioTrack::onTransact(code, data, reply, flags); +} + +// ---------------------------------------------------------------------------- + +sp<IAudioRecord> AudioFlinger::openRecord( + pid_t pid, + int input, + uint32_t sampleRate, + int format, + int channelCount, + int frameCount, + uint32_t flags, + status_t *status) +{ + sp<RecordThread::RecordTrack> recordTrack; + sp<RecordHandle> recordHandle; + sp<Client> client; + wp<Client> wclient; + status_t lStatus; + RecordThread *thread; + size_t inFrameCount; + + // check calling permissions + if (!recordingAllowed()) { + lStatus = PERMISSION_DENIED; + goto Exit; + } + + // add client to list + { // scope for mLock + Mutex::Autolock _l(mLock); + thread = checkRecordThread_l(input); + if (thread == NULL) { + lStatus = BAD_VALUE; + goto Exit; + } + + wclient = mClients.valueFor(pid); + if (wclient != NULL) { + client = wclient.promote(); + } else { + client = new Client(this, pid); + mClients.add(pid, client); + } + + // create new record track. The record track uses one track in mHardwareMixerThread by convention. + recordTrack = new RecordThread::RecordTrack(thread, client, sampleRate, + format, channelCount, frameCount, flags); + } + if (recordTrack->getCblk() == NULL) { + // remove local strong reference to Client before deleting the RecordTrack so that the Client + // destructor is called by the TrackBase destructor with mLock held + client.clear(); + recordTrack.clear(); + lStatus = NO_MEMORY; + goto Exit; + } + + // return to handle to client + recordHandle = new RecordHandle(recordTrack); + lStatus = NO_ERROR; + +Exit: + if (status) { + *status = lStatus; + } + return recordHandle; +} + +// ---------------------------------------------------------------------------- + +AudioFlinger::RecordHandle::RecordHandle(const sp<AudioFlinger::RecordThread::RecordTrack>& recordTrack) + : BnAudioRecord(), + mRecordTrack(recordTrack) +{ +} + +AudioFlinger::RecordHandle::~RecordHandle() { + stop(); +} + +status_t AudioFlinger::RecordHandle::start() { + LOGV("RecordHandle::start()"); + return mRecordTrack->start(); +} + +void AudioFlinger::RecordHandle::stop() { + LOGV("RecordHandle::stop()"); + mRecordTrack->stop(); +} + +sp<IMemory> AudioFlinger::RecordHandle::getCblk() const { + return mRecordTrack->getCblk(); +} + +status_t AudioFlinger::RecordHandle::onTransact( + uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) +{ + return BnAudioRecord::onTransact(code, data, reply, flags); +} + +// ---------------------------------------------------------------------------- + +AudioFlinger::RecordThread::RecordThread(const sp<AudioFlinger>& audioFlinger, AudioStreamIn *input, uint32_t sampleRate, uint32_t channels, int id) : + ThreadBase(audioFlinger, id), + mInput(input), mResampler(0), mRsmpOutBuffer(0), mRsmpInBuffer(0) +{ + mReqChannelCount = AudioSystem::popCount(channels); + mReqSampleRate = sampleRate; + readInputParameters(); + sendConfigEvent(AudioSystem::INPUT_OPENED); +} + + +AudioFlinger::RecordThread::~RecordThread() +{ + delete[] mRsmpInBuffer; + if (mResampler != 0) { + delete mResampler; + delete[] mRsmpOutBuffer; + } +} + +void AudioFlinger::RecordThread::onFirstRef() +{ + const size_t SIZE = 256; + char buffer[SIZE]; + + snprintf(buffer, SIZE, "Record Thread %p", this); + + run(buffer, PRIORITY_URGENT_AUDIO); +} + +bool AudioFlinger::RecordThread::threadLoop() +{ + AudioBufferProvider::Buffer buffer; + sp<RecordTrack> activeTrack; + + // start recording + while (!exitPending()) { + + processConfigEvents(); + + { // scope for mLock + Mutex::Autolock _l(mLock); + checkForNewParameters_l(); + if (mActiveTrack == 0 && mConfigEvents.isEmpty()) { + if (!mStandby) { + mInput->standby(); + mStandby = true; + } + + if (exitPending()) break; + + LOGV("RecordThread: loop stopping"); + // go to sleep + mWaitWorkCV.wait(mLock); + LOGV("RecordThread: loop starting"); + continue; + } + if (mActiveTrack != 0) { + if (mActiveTrack->mState == TrackBase::PAUSING) { + if (!mStandby) { + mInput->standby(); + mStandby = true; + } + mActiveTrack.clear(); + mStartStopCond.broadcast(); + } else if (mActiveTrack->mState == TrackBase::RESUMING) { + if (mReqChannelCount != mActiveTrack->channelCount()) { + mActiveTrack.clear(); + mStartStopCond.broadcast(); + } else if (mBytesRead != 0) { + // record start succeeds only if first read from audio input + // succeeds + if (mBytesRead > 0) { + mActiveTrack->mState = TrackBase::ACTIVE; + } else { + mActiveTrack.clear(); + } + mStartStopCond.broadcast(); + } + mStandby = false; + } + } + } + + if (mActiveTrack != 0) { + if (mActiveTrack->mState != TrackBase::ACTIVE && + mActiveTrack->mState != TrackBase::RESUMING) { + usleep(5000); + continue; + } + buffer.frameCount = mFrameCount; + if (LIKELY(mActiveTrack->getNextBuffer(&buffer) == NO_ERROR)) { + size_t framesOut = buffer.frameCount; + if (mResampler == 0) { + // no resampling + while (framesOut) { + size_t framesIn = mFrameCount - mRsmpInIndex; + if (framesIn) { + int8_t *src = (int8_t *)mRsmpInBuffer + mRsmpInIndex * mFrameSize; + int8_t *dst = buffer.i8 + (buffer.frameCount - framesOut) * mActiveTrack->mCblk->frameSize; + if (framesIn > framesOut) + framesIn = framesOut; + mRsmpInIndex += framesIn; + framesOut -= framesIn; + if (mChannelCount == mReqChannelCount || + mFormat != AudioSystem::PCM_16_BIT) { + memcpy(dst, src, framesIn * mFrameSize); + } else { + int16_t *src16 = (int16_t *)src; + int16_t *dst16 = (int16_t *)dst; + if (mChannelCount == 1) { + while (framesIn--) { + *dst16++ = *src16; + *dst16++ = *src16++; + } + } else { + while (framesIn--) { + *dst16++ = (int16_t)(((int32_t)*src16 + (int32_t)*(src16 + 1)) >> 1); + src16 += 2; + } + } + } + } + if (framesOut && mFrameCount == mRsmpInIndex) { + if (framesOut == mFrameCount && + (mChannelCount == mReqChannelCount || mFormat != AudioSystem::PCM_16_BIT)) { + mBytesRead = mInput->read(buffer.raw, mInputBytes); + framesOut = 0; + } else { + mBytesRead = mInput->read(mRsmpInBuffer, mInputBytes); + mRsmpInIndex = 0; + } + if (mBytesRead < 0) { + LOGE("Error reading audio input"); + if (mActiveTrack->mState == TrackBase::ACTIVE) { + // Force input into standby so that it tries to + // recover at next read attempt + mInput->standby(); + usleep(5000); + } + mRsmpInIndex = mFrameCount; + framesOut = 0; + buffer.frameCount = 0; + } + } + } + } else { + // resampling + + memset(mRsmpOutBuffer, 0, framesOut * 2 * sizeof(int32_t)); + // alter output frame count as if we were expecting stereo samples + if (mChannelCount == 1 && mReqChannelCount == 1) { + framesOut >>= 1; + } + mResampler->resample(mRsmpOutBuffer, framesOut, this); + // ditherAndClamp() works as long as all buffers returned by mActiveTrack->getNextBuffer() + // are 32 bit aligned which should be always true. + if (mChannelCount == 2 && mReqChannelCount == 1) { + AudioMixer::ditherAndClamp(mRsmpOutBuffer, mRsmpOutBuffer, framesOut); + // the resampler always outputs stereo samples: do post stereo to mono conversion + int16_t *src = (int16_t *)mRsmpOutBuffer; + int16_t *dst = buffer.i16; + while (framesOut--) { + *dst++ = (int16_t)(((int32_t)*src + (int32_t)*(src + 1)) >> 1); + src += 2; + } + } else { + AudioMixer::ditherAndClamp((int32_t *)buffer.raw, mRsmpOutBuffer, framesOut); + } + + } + mActiveTrack->releaseBuffer(&buffer); + mActiveTrack->overflow(); + } + // client isn't retrieving buffers fast enough + else { + if (!mActiveTrack->setOverflow()) + LOGW("RecordThread: buffer overflow"); + // Release the processor for a while before asking for a new buffer. + // This will give the application more chance to read from the buffer and + // clear the overflow. + usleep(5000); + } + } + } + + if (!mStandby) { + mInput->standby(); + } + mActiveTrack.clear(); + + mStartStopCond.broadcast(); + + LOGV("RecordThread %p exiting", this); + return false; +} + +status_t AudioFlinger::RecordThread::start(RecordThread::RecordTrack* recordTrack) +{ + LOGV("RecordThread::start"); + sp <ThreadBase> strongMe = this; + status_t status = NO_ERROR; + { + AutoMutex lock(&mLock); + if (mActiveTrack != 0) { + if (recordTrack != mActiveTrack.get()) { + status = -EBUSY; + } else if (mActiveTrack->mState == TrackBase::PAUSING) { + mActiveTrack->mState = TrackBase::ACTIVE; + } + return status; + } + + recordTrack->mState = TrackBase::IDLE; + mActiveTrack = recordTrack; + mLock.unlock(); + status_t status = AudioSystem::startInput(mId); + mLock.lock(); + if (status != NO_ERROR) { + mActiveTrack.clear(); + return status; + } + mActiveTrack->mState = TrackBase::RESUMING; + mRsmpInIndex = mFrameCount; + mBytesRead = 0; + // signal thread to start + LOGV("Signal record thread"); + mWaitWorkCV.signal(); + // do not wait for mStartStopCond if exiting + if (mExiting) { + mActiveTrack.clear(); + status = INVALID_OPERATION; + goto startError; + } + mStartStopCond.wait(mLock); + if (mActiveTrack == 0) { + LOGV("Record failed to start"); + status = BAD_VALUE; + goto startError; + } + LOGV("Record started OK"); + return status; + } +startError: + AudioSystem::stopInput(mId); + return status; +} + +void AudioFlinger::RecordThread::stop(RecordThread::RecordTrack* recordTrack) { + LOGV("RecordThread::stop"); + sp <ThreadBase> strongMe = this; + { + AutoMutex lock(&mLock); + if (mActiveTrack != 0 && recordTrack == mActiveTrack.get()) { + mActiveTrack->mState = TrackBase::PAUSING; + // do not wait for mStartStopCond if exiting + if (mExiting) { + return; + } + mStartStopCond.wait(mLock); + // if we have been restarted, recordTrack == mActiveTrack.get() here + if (mActiveTrack == 0 || recordTrack != mActiveTrack.get()) { + mLock.unlock(); + AudioSystem::stopInput(mId); + mLock.lock(); + LOGV("Record stopped OK"); + } + } + } +} + +status_t AudioFlinger::RecordThread::dump(int fd, const Vector<String16>& args) +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + pid_t pid = 0; + + snprintf(buffer, SIZE, "\nInput thread %p internals\n", this); + result.append(buffer); + + if (mActiveTrack != 0) { + result.append("Active Track:\n"); + result.append(" Clien Fmt Chn Buf S SRate Serv User\n"); + mActiveTrack->dump(buffer, SIZE); + result.append(buffer); + + snprintf(buffer, SIZE, "In index: %d\n", mRsmpInIndex); + result.append(buffer); + snprintf(buffer, SIZE, "In size: %d\n", mInputBytes); + result.append(buffer); + snprintf(buffer, SIZE, "Resampling: %d\n", (mResampler != 0)); + result.append(buffer); + snprintf(buffer, SIZE, "Out channel count: %d\n", mReqChannelCount); + result.append(buffer); + snprintf(buffer, SIZE, "Out sample rate: %d\n", mReqSampleRate); + result.append(buffer); + + + } else { + result.append("No record client\n"); + } + write(fd, result.string(), result.size()); + + dumpBase(fd, args); + + return NO_ERROR; +} + +status_t AudioFlinger::RecordThread::getNextBuffer(AudioBufferProvider::Buffer* buffer) +{ + size_t framesReq = buffer->frameCount; + size_t framesReady = mFrameCount - mRsmpInIndex; + int channelCount; + + if (framesReady == 0) { + mBytesRead = mInput->read(mRsmpInBuffer, mInputBytes); + if (mBytesRead < 0) { + LOGE("RecordThread::getNextBuffer() Error reading audio input"); + if (mActiveTrack->mState == TrackBase::ACTIVE) { + // Force input into standby so that it tries to + // recover at next read attempt + mInput->standby(); + usleep(5000); + } + buffer->raw = 0; + buffer->frameCount = 0; + return NOT_ENOUGH_DATA; + } + mRsmpInIndex = 0; + framesReady = mFrameCount; + } + + if (framesReq > framesReady) { + framesReq = framesReady; + } + + if (mChannelCount == 1 && mReqChannelCount == 2) { + channelCount = 1; + } else { + channelCount = 2; + } + buffer->raw = mRsmpInBuffer + mRsmpInIndex * channelCount; + buffer->frameCount = framesReq; + return NO_ERROR; +} + +void AudioFlinger::RecordThread::releaseBuffer(AudioBufferProvider::Buffer* buffer) +{ + mRsmpInIndex += buffer->frameCount; + buffer->frameCount = 0; +} + +bool AudioFlinger::RecordThread::checkForNewParameters_l() +{ + bool reconfig = false; + + while (!mNewParameters.isEmpty()) { + status_t status = NO_ERROR; + String8 keyValuePair = mNewParameters[0]; + AudioParameter param = AudioParameter(keyValuePair); + int value; + int reqFormat = mFormat; + int reqSamplingRate = mReqSampleRate; + int reqChannelCount = mReqChannelCount; + + if (param.getInt(String8(AudioParameter::keySamplingRate), value) == NO_ERROR) { + reqSamplingRate = value; + reconfig = true; + } + if (param.getInt(String8(AudioParameter::keyFormat), value) == NO_ERROR) { + reqFormat = value; + reconfig = true; + } + if (param.getInt(String8(AudioParameter::keyChannels), value) == NO_ERROR) { + reqChannelCount = AudioSystem::popCount(value); + reconfig = true; + } + if (param.getInt(String8(AudioParameter::keyFrameCount), value) == NO_ERROR) { + // do not accept frame count changes if tracks are open as the track buffer + // size depends on frame count and correct behavior would not be garantied + // if frame count is changed after track creation + if (mActiveTrack != 0) { + status = INVALID_OPERATION; + } else { + reconfig = true; + } + } + if (status == NO_ERROR) { + status = mInput->setParameters(keyValuePair); + if (status == INVALID_OPERATION) { + mInput->standby(); + status = mInput->setParameters(keyValuePair); + } + if (reconfig) { + if (status == BAD_VALUE && + reqFormat == mInput->format() && reqFormat == AudioSystem::PCM_16_BIT && + ((int)mInput->sampleRate() <= 2 * reqSamplingRate) && + (AudioSystem::popCount(mInput->channels()) < 3) && (reqChannelCount < 3)) { + status = NO_ERROR; + } + if (status == NO_ERROR) { + readInputParameters(); + sendConfigEvent_l(AudioSystem::INPUT_CONFIG_CHANGED); + } + } + } + + mNewParameters.removeAt(0); + + mParamStatus = status; + mParamCond.signal(); + mWaitWorkCV.wait(mLock); + } + return reconfig; +} + +String8 AudioFlinger::RecordThread::getParameters(const String8& keys) +{ + return mInput->getParameters(keys); +} + +void AudioFlinger::RecordThread::audioConfigChanged(int event, int param) { + AudioSystem::OutputDescriptor desc; + void *param2 = 0; + + switch (event) { + case AudioSystem::INPUT_OPENED: + case AudioSystem::INPUT_CONFIG_CHANGED: + desc.channels = mChannelCount; + desc.samplingRate = mSampleRate; + desc.format = mFormat; + desc.frameCount = mFrameCount; + desc.latency = 0; + param2 = &desc; + break; + + case AudioSystem::INPUT_CLOSED: + default: + break; + } + Mutex::Autolock _l(mAudioFlinger->mLock); + mAudioFlinger->audioConfigChanged_l(event, mId, param2); +} + +void AudioFlinger::RecordThread::readInputParameters() +{ + if (mRsmpInBuffer) delete mRsmpInBuffer; + if (mRsmpOutBuffer) delete mRsmpOutBuffer; + if (mResampler) delete mResampler; + mResampler = 0; + + mSampleRate = mInput->sampleRate(); + mChannelCount = AudioSystem::popCount(mInput->channels()); + mFormat = mInput->format(); + mFrameSize = mInput->frameSize(); + mInputBytes = mInput->bufferSize(); + mFrameCount = mInputBytes / mFrameSize; + mRsmpInBuffer = new int16_t[mFrameCount * mChannelCount]; + + if (mSampleRate != mReqSampleRate && mChannelCount < 3 && mReqChannelCount < 3) + { + int channelCount; + // optmization: if mono to mono, use the resampler in stereo to stereo mode to avoid + // stereo to mono post process as the resampler always outputs stereo. + if (mChannelCount == 1 && mReqChannelCount == 2) { + channelCount = 1; + } else { + channelCount = 2; + } + mResampler = AudioResampler::create(16, channelCount, mReqSampleRate); + mResampler->setSampleRate(mSampleRate); + mResampler->setVolume(AudioMixer::UNITY_GAIN, AudioMixer::UNITY_GAIN); + mRsmpOutBuffer = new int32_t[mFrameCount * 2]; + + // optmization: if mono to mono, alter input frame count as if we were inputing stereo samples + if (mChannelCount == 1 && mReqChannelCount == 1) { + mFrameCount >>= 1; + } + + } + mRsmpInIndex = mFrameCount; +} + +unsigned int AudioFlinger::RecordThread::getInputFramesLost() +{ + return mInput->getInputFramesLost(); +} + +// ---------------------------------------------------------------------------- + +int AudioFlinger::openOutput(uint32_t *pDevices, + uint32_t *pSamplingRate, + uint32_t *pFormat, + uint32_t *pChannels, + uint32_t *pLatencyMs, + uint32_t flags) +{ + status_t status; + PlaybackThread *thread = NULL; + mHardwareStatus = AUDIO_HW_OUTPUT_OPEN; + uint32_t samplingRate = pSamplingRate ? *pSamplingRate : 0; + uint32_t format = pFormat ? *pFormat : 0; + uint32_t channels = pChannels ? *pChannels : 0; + uint32_t latency = pLatencyMs ? *pLatencyMs : 0; + + LOGV("openOutput(), Device %x, SamplingRate %d, Format %d, Channels %x, flags %x", + pDevices ? *pDevices : 0, + samplingRate, + format, + channels, + flags); + + if (pDevices == NULL || *pDevices == 0) { + return 0; + } + Mutex::Autolock _l(mLock); + + AudioStreamOut *output = mAudioHardware->openOutputStream(*pDevices, + (int *)&format, + &channels, + &samplingRate, + &status); + LOGV("openOutput() openOutputStream returned output %p, SamplingRate %d, Format %d, Channels %x, status %d", + output, + samplingRate, + format, + channels, + status); + + mHardwareStatus = AUDIO_HW_IDLE; + if (output != 0) { + if ((flags & AudioSystem::OUTPUT_FLAG_DIRECT) || + (format != AudioSystem::PCM_16_BIT) || + (channels != AudioSystem::CHANNEL_OUT_STEREO)) { + thread = new DirectOutputThread(this, output, ++mNextThreadId); + LOGV("openOutput() created direct output: ID %d thread %p", mNextThreadId, thread); + } else { + thread = new MixerThread(this, output, ++mNextThreadId); + LOGV("openOutput() created mixer output: ID %d thread %p", mNextThreadId, thread); + +#ifdef LVMX + unsigned bitsPerSample = + (format == AudioSystem::PCM_16_BIT) ? 16 : + ((format == AudioSystem::PCM_8_BIT) ? 8 : 0); + unsigned channelCount = (channels == AudioSystem::CHANNEL_OUT_STEREO) ? 2 : 1; + int audioOutputType = LifeVibes::threadIdToAudioOutputType(thread->id()); + + LifeVibes::init_aot(audioOutputType, samplingRate, bitsPerSample, channelCount); + LifeVibes::setDevice(audioOutputType, *pDevices); +#endif + + } + mPlaybackThreads.add(mNextThreadId, thread); + + if (pSamplingRate) *pSamplingRate = samplingRate; + if (pFormat) *pFormat = format; + if (pChannels) *pChannels = channels; + if (pLatencyMs) *pLatencyMs = thread->latency(); + + return mNextThreadId; + } + + return 0; +} + +int AudioFlinger::openDuplicateOutput(int output1, int output2) +{ + Mutex::Autolock _l(mLock); + MixerThread *thread1 = checkMixerThread_l(output1); + MixerThread *thread2 = checkMixerThread_l(output2); + + if (thread1 == NULL || thread2 == NULL) { + LOGW("openDuplicateOutput() wrong output mixer type for output %d or %d", output1, output2); + return 0; + } + + + DuplicatingThread *thread = new DuplicatingThread(this, thread1, ++mNextThreadId); + thread->addOutputTrack(thread2); + mPlaybackThreads.add(mNextThreadId, thread); + return mNextThreadId; +} + +status_t AudioFlinger::closeOutput(int output) +{ + // keep strong reference on the playback thread so that + // it is not destroyed while exit() is executed + sp <PlaybackThread> thread; + { + Mutex::Autolock _l(mLock); + thread = checkPlaybackThread_l(output); + if (thread == NULL) { + return BAD_VALUE; + } + + LOGV("closeOutput() %d", output); + + if (thread->type() == PlaybackThread::MIXER) { + for (size_t i = 0; i < mPlaybackThreads.size(); i++) { + if (mPlaybackThreads.valueAt(i)->type() == PlaybackThread::DUPLICATING) { + DuplicatingThread *dupThread = (DuplicatingThread *)mPlaybackThreads.valueAt(i).get(); + dupThread->removeOutputTrack((MixerThread *)thread.get()); + } + } + } + void *param2 = 0; + audioConfigChanged_l(AudioSystem::OUTPUT_CLOSED, output, param2); + mPlaybackThreads.removeItem(output); + } + thread->exit(); + + if (thread->type() != PlaybackThread::DUPLICATING) { + mAudioHardware->closeOutputStream(thread->getOutput()); + } + return NO_ERROR; +} + +status_t AudioFlinger::suspendOutput(int output) +{ + Mutex::Autolock _l(mLock); + PlaybackThread *thread = checkPlaybackThread_l(output); + + if (thread == NULL) { + return BAD_VALUE; + } + + LOGV("suspendOutput() %d", output); + thread->suspend(); + + return NO_ERROR; +} + +status_t AudioFlinger::restoreOutput(int output) +{ + Mutex::Autolock _l(mLock); + PlaybackThread *thread = checkPlaybackThread_l(output); + + if (thread == NULL) { + return BAD_VALUE; + } + + LOGV("restoreOutput() %d", output); + + thread->restore(); + + return NO_ERROR; +} + +int AudioFlinger::openInput(uint32_t *pDevices, + uint32_t *pSamplingRate, + uint32_t *pFormat, + uint32_t *pChannels, + uint32_t acoustics) +{ + status_t status; + RecordThread *thread = NULL; + uint32_t samplingRate = pSamplingRate ? *pSamplingRate : 0; + uint32_t format = pFormat ? *pFormat : 0; + uint32_t channels = pChannels ? *pChannels : 0; + uint32_t reqSamplingRate = samplingRate; + uint32_t reqFormat = format; + uint32_t reqChannels = channels; + + if (pDevices == NULL || *pDevices == 0) { + return 0; + } + Mutex::Autolock _l(mLock); + + AudioStreamIn *input = mAudioHardware->openInputStream(*pDevices, + (int *)&format, + &channels, + &samplingRate, + &status, + (AudioSystem::audio_in_acoustics)acoustics); + LOGV("openInput() openInputStream returned input %p, SamplingRate %d, Format %d, Channels %x, acoustics %x, status %d", + input, + samplingRate, + format, + channels, + acoustics, + status); + + // If the input could not be opened with the requested parameters and we can handle the conversion internally, + // try to open again with the proposed parameters. The AudioFlinger can resample the input and do mono to stereo + // or stereo to mono conversions on 16 bit PCM inputs. + if (input == 0 && status == BAD_VALUE && + reqFormat == format && format == AudioSystem::PCM_16_BIT && + (samplingRate <= 2 * reqSamplingRate) && + (AudioSystem::popCount(channels) < 3) && (AudioSystem::popCount(reqChannels) < 3)) { + LOGV("openInput() reopening with proposed sampling rate and channels"); + input = mAudioHardware->openInputStream(*pDevices, + (int *)&format, + &channels, + &samplingRate, + &status, + (AudioSystem::audio_in_acoustics)acoustics); + } + + if (input != 0) { + // Start record thread + thread = new RecordThread(this, input, reqSamplingRate, reqChannels, ++mNextThreadId); + mRecordThreads.add(mNextThreadId, thread); + LOGV("openInput() created record thread: ID %d thread %p", mNextThreadId, thread); + if (pSamplingRate) *pSamplingRate = reqSamplingRate; + if (pFormat) *pFormat = format; + if (pChannels) *pChannels = reqChannels; + + input->standby(); + + return mNextThreadId; + } + + return 0; +} + +status_t AudioFlinger::closeInput(int input) +{ + // keep strong reference on the record thread so that + // it is not destroyed while exit() is executed + sp <RecordThread> thread; + { + Mutex::Autolock _l(mLock); + thread = checkRecordThread_l(input); + if (thread == NULL) { + return BAD_VALUE; + } + + LOGV("closeInput() %d", input); + void *param2 = 0; + audioConfigChanged_l(AudioSystem::INPUT_CLOSED, input, param2); + mRecordThreads.removeItem(input); + } + thread->exit(); + + mAudioHardware->closeInputStream(thread->getInput()); + + return NO_ERROR; +} + +status_t AudioFlinger::setStreamOutput(uint32_t stream, int output) +{ + Mutex::Autolock _l(mLock); + MixerThread *dstThread = checkMixerThread_l(output); + if (dstThread == NULL) { + LOGW("setStreamOutput() bad output id %d", output); + return BAD_VALUE; + } + + LOGV("setStreamOutput() stream %d to output %d", stream, output); + + for (size_t i = 0; i < mPlaybackThreads.size(); i++) { + PlaybackThread *thread = mPlaybackThreads.valueAt(i).get(); + if (thread != dstThread && + thread->type() != PlaybackThread::DIRECT) { + MixerThread *srcThread = (MixerThread *)thread; + SortedVector < sp<MixerThread::Track> > tracks; + SortedVector < wp<MixerThread::Track> > activeTracks; + srcThread->getTracks(tracks, activeTracks, stream); + if (tracks.size()) { + dstThread->putTracks(tracks, activeTracks); + } + } + } + + dstThread->sendConfigEvent(AudioSystem::STREAM_CONFIG_CHANGED, stream); + + return NO_ERROR; +} + +// checkPlaybackThread_l() must be called with AudioFlinger::mLock held +AudioFlinger::PlaybackThread *AudioFlinger::checkPlaybackThread_l(int output) const +{ + PlaybackThread *thread = NULL; + if (mPlaybackThreads.indexOfKey(output) >= 0) { + thread = (PlaybackThread *)mPlaybackThreads.valueFor(output).get(); + } + return thread; +} + +// checkMixerThread_l() must be called with AudioFlinger::mLock held +AudioFlinger::MixerThread *AudioFlinger::checkMixerThread_l(int output) const +{ + PlaybackThread *thread = checkPlaybackThread_l(output); + if (thread != NULL) { + if (thread->type() == PlaybackThread::DIRECT) { + thread = NULL; + } + } + return (MixerThread *)thread; +} + +// checkRecordThread_l() must be called with AudioFlinger::mLock held +AudioFlinger::RecordThread *AudioFlinger::checkRecordThread_l(int input) const +{ + RecordThread *thread = NULL; + if (mRecordThreads.indexOfKey(input) >= 0) { + thread = (RecordThread *)mRecordThreads.valueFor(input).get(); + } + return thread; +} + +// ---------------------------------------------------------------------------- + +status_t AudioFlinger::onTransact( + uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) +{ + return BnAudioFlinger::onTransact(code, data, reply, flags); +} + +// ---------------------------------------------------------------------------- + +void AudioFlinger::instantiate() { + defaultServiceManager()->addService( + String16("media.audio_flinger"), new AudioFlinger()); +} + +}; // namespace android diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h new file mode 100644 index 0000000..739ec33 --- /dev/null +++ b/services/audioflinger/AudioFlinger.h @@ -0,0 +1,807 @@ +/* //device/include/server/AudioFlinger/AudioFlinger.h +** +** Copyright 2007, 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. +*/ + +#ifndef ANDROID_AUDIO_FLINGER_H +#define ANDROID_AUDIO_FLINGER_H + +#include <stdint.h> +#include <sys/types.h> +#include <limits.h> + +#include <media/IAudioFlinger.h> +#include <media/IAudioFlingerClient.h> +#include <media/IAudioTrack.h> +#include <media/IAudioRecord.h> +#include <media/AudioTrack.h> + +#include <utils/Atomic.h> +#include <utils/Errors.h> +#include <utils/threads.h> +#include <binder/MemoryDealer.h> +#include <utils/SortedVector.h> +#include <utils/Vector.h> + +#include <hardware_legacy/AudioHardwareInterface.h> + +#include "AudioBufferProvider.h" + +namespace android { + +class audio_track_cblk_t; +class AudioMixer; +class AudioBuffer; +class AudioResampler; + + +// ---------------------------------------------------------------------------- + +#define LIKELY( exp ) (__builtin_expect( (exp) != 0, true )) +#define UNLIKELY( exp ) (__builtin_expect( (exp) != 0, false )) + + +// ---------------------------------------------------------------------------- + +static const nsecs_t kStandbyTimeInNsecs = seconds(3); + +class AudioFlinger : public BnAudioFlinger, public IBinder::DeathRecipient +{ +public: + static void instantiate(); + + virtual status_t dump(int fd, const Vector<String16>& args); + + // IAudioFlinger interface + virtual sp<IAudioTrack> createTrack( + pid_t pid, + int streamType, + uint32_t sampleRate, + int format, + int channelCount, + int frameCount, + uint32_t flags, + const sp<IMemory>& sharedBuffer, + int output, + status_t *status); + + virtual uint32_t sampleRate(int output) const; + virtual int channelCount(int output) const; + virtual int format(int output) const; + virtual size_t frameCount(int output) const; + virtual uint32_t latency(int output) const; + + virtual status_t setMasterVolume(float value); + virtual status_t setMasterMute(bool muted); + + virtual float masterVolume() const; + virtual bool masterMute() const; + + virtual status_t setStreamVolume(int stream, float value, int output); + virtual status_t setStreamMute(int stream, bool muted); + + virtual float streamVolume(int stream, int output) const; + virtual bool streamMute(int stream) const; + + virtual status_t setMode(int mode); + + virtual status_t setMicMute(bool state); + virtual bool getMicMute() const; + + virtual bool isStreamActive(int stream) const; + + virtual status_t setParameters(int ioHandle, const String8& keyValuePairs); + virtual String8 getParameters(int ioHandle, const String8& keys); + + virtual void registerClient(const sp<IAudioFlingerClient>& client); + + virtual size_t getInputBufferSize(uint32_t sampleRate, int format, int channelCount); + virtual unsigned int getInputFramesLost(int ioHandle); + + virtual int openOutput(uint32_t *pDevices, + uint32_t *pSamplingRate, + uint32_t *pFormat, + uint32_t *pChannels, + uint32_t *pLatencyMs, + uint32_t flags); + + virtual int openDuplicateOutput(int output1, int output2); + + virtual status_t closeOutput(int output); + + virtual status_t suspendOutput(int output); + + virtual status_t restoreOutput(int output); + + virtual int openInput(uint32_t *pDevices, + uint32_t *pSamplingRate, + uint32_t *pFormat, + uint32_t *pChannels, + uint32_t acoustics); + + virtual status_t closeInput(int input); + + virtual status_t setStreamOutput(uint32_t stream, int output); + + virtual status_t setVoiceVolume(float volume); + + virtual status_t getRenderPosition(uint32_t *halFrames, uint32_t *dspFrames, int output); + + // IBinder::DeathRecipient + virtual void binderDied(const wp<IBinder>& who); + + enum hardware_call_state { + AUDIO_HW_IDLE = 0, + AUDIO_HW_INIT, + AUDIO_HW_OUTPUT_OPEN, + AUDIO_HW_OUTPUT_CLOSE, + AUDIO_HW_INPUT_OPEN, + AUDIO_HW_INPUT_CLOSE, + AUDIO_HW_STANDBY, + AUDIO_HW_SET_MASTER_VOLUME, + AUDIO_HW_GET_ROUTING, + AUDIO_HW_SET_ROUTING, + AUDIO_HW_GET_MODE, + AUDIO_HW_SET_MODE, + AUDIO_HW_GET_MIC_MUTE, + AUDIO_HW_SET_MIC_MUTE, + AUDIO_SET_VOICE_VOLUME, + AUDIO_SET_PARAMETER, + }; + + // record interface + virtual sp<IAudioRecord> openRecord( + pid_t pid, + int input, + uint32_t sampleRate, + int format, + int channelCount, + int frameCount, + uint32_t flags, + status_t *status); + + virtual status_t onTransact( + uint32_t code, + const Parcel& data, + Parcel* reply, + uint32_t flags); + +private: + AudioFlinger(); + virtual ~AudioFlinger(); + + + // Internal dump utilites. + status_t dumpPermissionDenial(int fd, const Vector<String16>& args); + status_t dumpClients(int fd, const Vector<String16>& args); + status_t dumpInternals(int fd, const Vector<String16>& args); + + // --- Client --- + class Client : public RefBase { + public: + Client(const sp<AudioFlinger>& audioFlinger, pid_t pid); + virtual ~Client(); + const sp<MemoryDealer>& heap() const; + pid_t pid() const { return mPid; } + sp<AudioFlinger> audioFlinger() { return mAudioFlinger; } + + private: + Client(const Client&); + Client& operator = (const Client&); + sp<AudioFlinger> mAudioFlinger; + sp<MemoryDealer> mMemoryDealer; + pid_t mPid; + }; + + + class TrackHandle; + class RecordHandle; + class RecordThread; + class PlaybackThread; + class MixerThread; + class DirectOutputThread; + class DuplicatingThread; + class Track; + class RecordTrack; + + class ThreadBase : public Thread { + public: + ThreadBase (const sp<AudioFlinger>& audioFlinger, int id); + virtual ~ThreadBase(); + + status_t dumpBase(int fd, const Vector<String16>& args); + + // base for record and playback + class TrackBase : public AudioBufferProvider, public RefBase { + + public: + enum track_state { + IDLE, + TERMINATED, + STOPPED, + RESUMING, + ACTIVE, + PAUSING, + PAUSED + }; + + enum track_flags { + STEPSERVER_FAILED = 0x01, // StepServer could not acquire cblk->lock mutex + SYSTEM_FLAGS_MASK = 0x0000ffffUL, + // The upper 16 bits are used for track-specific flags. + }; + + TrackBase(const wp<ThreadBase>& thread, + const sp<Client>& client, + uint32_t sampleRate, + int format, + int channelCount, + int frameCount, + uint32_t flags, + const sp<IMemory>& sharedBuffer); + ~TrackBase(); + + virtual status_t start() = 0; + virtual void stop() = 0; + sp<IMemory> getCblk() const; + audio_track_cblk_t* cblk() const { return mCblk; } + + protected: + friend class ThreadBase; + friend class RecordHandle; + friend class PlaybackThread; + friend class RecordThread; + friend class MixerThread; + friend class DirectOutputThread; + + TrackBase(const TrackBase&); + TrackBase& operator = (const TrackBase&); + + virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer) = 0; + virtual void releaseBuffer(AudioBufferProvider::Buffer* buffer); + + int format() const { + return mFormat; + } + + int channelCount() const ; + + int sampleRate() const; + + void* getBuffer(uint32_t offset, uint32_t frames) const; + + bool isStopped() const { + return mState == STOPPED; + } + + bool isTerminated() const { + return mState == TERMINATED; + } + + bool step(); + void reset(); + + wp<ThreadBase> mThread; + sp<Client> mClient; + sp<IMemory> mCblkMemory; + audio_track_cblk_t* mCblk; + void* mBuffer; + void* mBufferEnd; + uint32_t mFrameCount; + // we don't really need a lock for these + int mState; + int mClientTid; + uint8_t mFormat; + uint32_t mFlags; + }; + + class ConfigEvent { + public: + ConfigEvent() : mEvent(0), mParam(0) {} + + int mEvent; + int mParam; + }; + + uint32_t sampleRate() const; + int channelCount() const; + int format() const; + size_t frameCount() const; + void wakeUp() { mWaitWorkCV.broadcast(); } + void exit(); + virtual bool checkForNewParameters_l() = 0; + virtual status_t setParameters(const String8& keyValuePairs); + virtual String8 getParameters(const String8& keys) = 0; + virtual void audioConfigChanged(int event, int param = 0) = 0; + void sendConfigEvent(int event, int param = 0); + void sendConfigEvent_l(int event, int param = 0); + void processConfigEvents(); + int id() const { return mId;} + bool standby() { return mStandby; } + + mutable Mutex mLock; + + protected: + + friend class Track; + friend class TrackBase; + friend class PlaybackThread; + friend class MixerThread; + friend class DirectOutputThread; + friend class DuplicatingThread; + friend class RecordThread; + friend class RecordTrack; + + Condition mWaitWorkCV; + sp<AudioFlinger> mAudioFlinger; + uint32_t mSampleRate; + size_t mFrameCount; + int mChannelCount; + int mFormat; + uint32_t mFrameSize; + Condition mParamCond; + Vector<String8> mNewParameters; + status_t mParamStatus; + Vector<ConfigEvent *> mConfigEvents; + bool mStandby; + int mId; + bool mExiting; + }; + + // --- PlaybackThread --- + class PlaybackThread : public ThreadBase { + public: + + enum type { + MIXER, + DIRECT, + DUPLICATING + }; + + enum mixer_state { + MIXER_IDLE, + MIXER_TRACKS_ENABLED, + MIXER_TRACKS_READY + }; + + // playback track + class Track : public TrackBase { + public: + Track( const wp<ThreadBase>& thread, + const sp<Client>& client, + int streamType, + uint32_t sampleRate, + int format, + int channelCount, + int frameCount, + const sp<IMemory>& sharedBuffer); + ~Track(); + + void dump(char* buffer, size_t size); + virtual status_t start(); + virtual void stop(); + void pause(); + + void flush(); + void destroy(); + void mute(bool); + void setVolume(float left, float right); + int name() const { + return mName; + } + + int type() const { + return mStreamType; + } + + + protected: + friend class ThreadBase; + friend class AudioFlinger; + friend class TrackHandle; + friend class PlaybackThread; + friend class MixerThread; + friend class DirectOutputThread; + + Track(const Track&); + Track& operator = (const Track&); + + virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer); + bool isMuted() { return mMute; } + bool isPausing() const { + return mState == PAUSING; + } + bool isPaused() const { + return mState == PAUSED; + } + bool isReady() const; + void setPaused() { mState = PAUSED; } + void reset(); + + bool isOutputTrack() const { + return (mStreamType == AudioSystem::NUM_STREAM_TYPES); + } + + // we don't really need a lock for these + float mVolume[2]; + volatile bool mMute; + // FILLED state is used for suppressing volume ramp at begin of playing + enum {FS_FILLING, FS_FILLED, FS_ACTIVE}; + mutable uint8_t mFillingUpStatus; + int8_t mRetryCount; + sp<IMemory> mSharedBuffer; + bool mResetDone; + int mStreamType; + int mName; + }; // end of Track + + + // playback track + class OutputTrack : public Track { + public: + + class Buffer: public AudioBufferProvider::Buffer { + public: + int16_t *mBuffer; + }; + + OutputTrack( const wp<ThreadBase>& thread, + DuplicatingThread *sourceThread, + uint32_t sampleRate, + int format, + int channelCount, + int frameCount); + ~OutputTrack(); + + virtual status_t start(); + virtual void stop(); + bool write(int16_t* data, uint32_t frames); + bool bufferQueueEmpty() { return (mBufferQueue.size() == 0) ? true : false; } + bool isActive() { return mActive; } + wp<ThreadBase>& thread() { return mThread; } + + private: + + status_t obtainBuffer(AudioBufferProvider::Buffer* buffer, uint32_t waitTimeMs); + void clearBufferQueue(); + + // Maximum number of pending buffers allocated by OutputTrack::write() + static const uint8_t kMaxOverFlowBuffers = 10; + + Vector < Buffer* > mBufferQueue; + AudioBufferProvider::Buffer mOutBuffer; + bool mActive; + DuplicatingThread* mSourceThread; + }; // end of OutputTrack + + PlaybackThread (const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output, int id); + virtual ~PlaybackThread(); + + virtual status_t dump(int fd, const Vector<String16>& args); + + // Thread virtuals + virtual status_t readyToRun(); + virtual void onFirstRef(); + + virtual uint32_t latency() const; + + virtual status_t setMasterVolume(float value); + virtual status_t setMasterMute(bool muted); + + virtual float masterVolume() const; + virtual bool masterMute() const; + + virtual status_t setStreamVolume(int stream, float value); + virtual status_t setStreamMute(int stream, bool muted); + + virtual float streamVolume(int stream) const; + virtual bool streamMute(int stream) const; + + bool isStreamActive(int stream) const; + + sp<Track> createTrack_l( + const sp<AudioFlinger::Client>& client, + int streamType, + uint32_t sampleRate, + int format, + int channelCount, + int frameCount, + const sp<IMemory>& sharedBuffer, + status_t *status); + + AudioStreamOut* getOutput() { return mOutput; } + + virtual int type() const { return mType; } + void suspend() { mSuspended++; } + void restore() { if (mSuspended) mSuspended--; } + bool isSuspended() { return (mSuspended != 0); } + virtual String8 getParameters(const String8& keys); + virtual void audioConfigChanged(int event, int param = 0); + virtual status_t getRenderPosition(uint32_t *halFrames, uint32_t *dspFrames); + + struct stream_type_t { + stream_type_t() + : volume(1.0f), + mute(false) + { + } + float volume; + bool mute; + }; + + protected: + int mType; + int16_t* mMixBuffer; + int mSuspended; + int mBytesWritten; + bool mMasterMute; + SortedVector< wp<Track> > mActiveTracks; + + virtual int getTrackName_l() = 0; + virtual void deleteTrackName_l(int name) = 0; + virtual uint32_t activeSleepTimeUs() = 0; + virtual uint32_t idleSleepTimeUs() = 0; + + private: + + friend class AudioFlinger; + friend class OutputTrack; + friend class Track; + friend class TrackBase; + friend class MixerThread; + friend class DirectOutputThread; + friend class DuplicatingThread; + + PlaybackThread(const Client&); + PlaybackThread& operator = (const PlaybackThread&); + + status_t addTrack_l(const sp<Track>& track); + void destroyTrack_l(const sp<Track>& track); + + void readOutputParameters(); + + virtual status_t dumpInternals(int fd, const Vector<String16>& args); + status_t dumpTracks(int fd, const Vector<String16>& args); + + SortedVector< sp<Track> > mTracks; + // mStreamTypes[] uses 1 additionnal stream type internally for the OutputTrack used by DuplicatingThread + stream_type_t mStreamTypes[AudioSystem::NUM_STREAM_TYPES + 1]; + AudioStreamOut* mOutput; + float mMasterVolume; + nsecs_t mLastWriteTime; + int mNumWrites; + int mNumDelayedWrites; + bool mInWrite; + }; + + class MixerThread : public PlaybackThread { + public: + MixerThread (const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output, int id); + virtual ~MixerThread(); + + // Thread virtuals + virtual bool threadLoop(); + + void getTracks(SortedVector < sp<Track> >& tracks, + SortedVector < wp<Track> >& activeTracks, + int streamType); + void putTracks(SortedVector < sp<Track> >& tracks, + SortedVector < wp<Track> >& activeTracks); + virtual bool checkForNewParameters_l(); + virtual status_t dumpInternals(int fd, const Vector<String16>& args); + + protected: + uint32_t prepareTracks_l(const SortedVector< wp<Track> >& activeTracks, Vector< sp<Track> > *tracksToRemove); + virtual int getTrackName_l(); + virtual void deleteTrackName_l(int name); + virtual uint32_t activeSleepTimeUs(); + virtual uint32_t idleSleepTimeUs(); + + AudioMixer* mAudioMixer; + }; + + class DirectOutputThread : public PlaybackThread { + public: + + DirectOutputThread (const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output, int id); + ~DirectOutputThread(); + + // Thread virtuals + virtual bool threadLoop(); + + virtual bool checkForNewParameters_l(); + + protected: + virtual int getTrackName_l(); + virtual void deleteTrackName_l(int name); + virtual uint32_t activeSleepTimeUs(); + virtual uint32_t idleSleepTimeUs(); + + private: + float mLeftVolume; + float mRightVolume; + }; + + class DuplicatingThread : public MixerThread { + public: + DuplicatingThread (const sp<AudioFlinger>& audioFlinger, MixerThread* mainThread, int id); + ~DuplicatingThread(); + + // Thread virtuals + virtual bool threadLoop(); + void addOutputTrack(MixerThread* thread); + void removeOutputTrack(MixerThread* thread); + uint32_t waitTimeMs() { return mWaitTimeMs; } + protected: + virtual uint32_t activeSleepTimeUs(); + + private: + bool outputsReady(SortedVector< sp<OutputTrack> > &outputTracks); + void updateWaitTime(); + + SortedVector < sp<OutputTrack> > mOutputTracks; + uint32_t mWaitTimeMs; + }; + + PlaybackThread *checkPlaybackThread_l(int output) const; + MixerThread *checkMixerThread_l(int output) const; + RecordThread *checkRecordThread_l(int input) const; + float streamVolumeInternal(int stream) const { return mStreamTypes[stream].volume; } + void audioConfigChanged_l(int event, int ioHandle, void *param2); + + friend class AudioBuffer; + + class TrackHandle : public android::BnAudioTrack { + public: + TrackHandle(const sp<PlaybackThread::Track>& track); + virtual ~TrackHandle(); + virtual status_t start(); + virtual void stop(); + virtual void flush(); + virtual void mute(bool); + virtual void pause(); + virtual void setVolume(float left, float right); + virtual sp<IMemory> getCblk() const; + virtual status_t onTransact( + uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags); + private: + sp<PlaybackThread::Track> mTrack; + }; + + friend class Client; + friend class PlaybackThread::Track; + + + void removeClient_l(pid_t pid); + + + // record thread + class RecordThread : public ThreadBase, public AudioBufferProvider + { + public: + + // record track + class RecordTrack : public TrackBase { + public: + RecordTrack(const wp<ThreadBase>& thread, + const sp<Client>& client, + uint32_t sampleRate, + int format, + int channelCount, + int frameCount, + uint32_t flags); + ~RecordTrack(); + + virtual status_t start(); + virtual void stop(); + + bool overflow() { bool tmp = mOverflow; mOverflow = false; return tmp; } + bool setOverflow() { bool tmp = mOverflow; mOverflow = true; return tmp; } + + void dump(char* buffer, size_t size); + private: + friend class AudioFlinger; + friend class RecordThread; + + RecordTrack(const RecordTrack&); + RecordTrack& operator = (const RecordTrack&); + + virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer); + + bool mOverflow; + }; + + + RecordThread(const sp<AudioFlinger>& audioFlinger, + AudioStreamIn *input, + uint32_t sampleRate, + uint32_t channels, + int id); + ~RecordThread(); + + virtual bool threadLoop(); + virtual status_t readyToRun() { return NO_ERROR; } + virtual void onFirstRef(); + + status_t start(RecordTrack* recordTrack); + void stop(RecordTrack* recordTrack); + status_t dump(int fd, const Vector<String16>& args); + AudioStreamIn* getInput() { return mInput; } + + virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer); + virtual void releaseBuffer(AudioBufferProvider::Buffer* buffer); + virtual bool checkForNewParameters_l(); + virtual String8 getParameters(const String8& keys); + virtual void audioConfigChanged(int event, int param = 0); + void readInputParameters(); + virtual unsigned int getInputFramesLost(); + + private: + RecordThread(); + AudioStreamIn *mInput; + sp<RecordTrack> mActiveTrack; + Condition mStartStopCond; + AudioResampler *mResampler; + int32_t *mRsmpOutBuffer; + int16_t *mRsmpInBuffer; + size_t mRsmpInIndex; + size_t mInputBytes; + int mReqChannelCount; + uint32_t mReqSampleRate; + ssize_t mBytesRead; + }; + + class RecordHandle : public android::BnAudioRecord { + public: + RecordHandle(const sp<RecordThread::RecordTrack>& recordTrack); + virtual ~RecordHandle(); + virtual status_t start(); + virtual void stop(); + virtual sp<IMemory> getCblk() const; + virtual status_t onTransact( + uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags); + private: + sp<RecordThread::RecordTrack> mRecordTrack; + }; + + friend class RecordThread; + friend class PlaybackThread; + + + mutable Mutex mLock; + + DefaultKeyedVector< pid_t, wp<Client> > mClients; + + mutable Mutex mHardwareLock; + AudioHardwareInterface* mAudioHardware; + mutable int mHardwareStatus; + + + DefaultKeyedVector< int, sp<PlaybackThread> > mPlaybackThreads; + PlaybackThread::stream_type_t mStreamTypes[AudioSystem::NUM_STREAM_TYPES]; + float mMasterVolume; + bool mMasterMute; + + DefaultKeyedVector< int, sp<RecordThread> > mRecordThreads; + + SortedVector< sp<IBinder> > mNotificationClients; + int mNextThreadId; +}; + +// ---------------------------------------------------------------------------- + +}; // namespace android + +#endif // ANDROID_AUDIO_FLINGER_H diff --git a/services/audioflinger/AudioHardwareGeneric.cpp b/services/audioflinger/AudioHardwareGeneric.cpp new file mode 100644 index 0000000..d63c031 --- /dev/null +++ b/services/audioflinger/AudioHardwareGeneric.cpp @@ -0,0 +1,411 @@ +/* +** +** Copyright 2007, 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 <stdint.h> +#include <sys/types.h> + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <sched.h> +#include <fcntl.h> +#include <sys/ioctl.h> + +#define LOG_TAG "AudioHardware" +#include <utils/Log.h> +#include <utils/String8.h> + +#include "AudioHardwareGeneric.h" +#include <media/AudioRecord.h> + +namespace android { + +// ---------------------------------------------------------------------------- + +static char const * const kAudioDeviceName = "/dev/eac"; + +// ---------------------------------------------------------------------------- + +AudioHardwareGeneric::AudioHardwareGeneric() + : mOutput(0), mInput(0), mFd(-1), mMicMute(false) +{ + mFd = ::open(kAudioDeviceName, O_RDWR); +} + +AudioHardwareGeneric::~AudioHardwareGeneric() +{ + if (mFd >= 0) ::close(mFd); + closeOutputStream((AudioStreamOut *)mOutput); + closeInputStream((AudioStreamIn *)mInput); +} + +status_t AudioHardwareGeneric::initCheck() +{ + if (mFd >= 0) { + if (::access(kAudioDeviceName, O_RDWR) == NO_ERROR) + return NO_ERROR; + } + return NO_INIT; +} + +AudioStreamOut* AudioHardwareGeneric::openOutputStream( + uint32_t devices, int *format, uint32_t *channels, uint32_t *sampleRate, status_t *status) +{ + AutoMutex lock(mLock); + + // only one output stream allowed + if (mOutput) { + if (status) { + *status = INVALID_OPERATION; + } + return 0; + } + + // create new output stream + AudioStreamOutGeneric* out = new AudioStreamOutGeneric(); + status_t lStatus = out->set(this, mFd, devices, format, channels, sampleRate); + if (status) { + *status = lStatus; + } + if (lStatus == NO_ERROR) { + mOutput = out; + } else { + delete out; + } + return mOutput; +} + +void AudioHardwareGeneric::closeOutputStream(AudioStreamOut* out) { + if (mOutput && out == mOutput) { + delete mOutput; + mOutput = 0; + } +} + +AudioStreamIn* AudioHardwareGeneric::openInputStream( + uint32_t devices, int *format, uint32_t *channels, uint32_t *sampleRate, + status_t *status, AudioSystem::audio_in_acoustics acoustics) +{ + // check for valid input source + if (!AudioSystem::isInputDevice((AudioSystem::audio_devices)devices)) { + return 0; + } + + AutoMutex lock(mLock); + + // only one input stream allowed + if (mInput) { + if (status) { + *status = INVALID_OPERATION; + } + return 0; + } + + // create new output stream + AudioStreamInGeneric* in = new AudioStreamInGeneric(); + status_t lStatus = in->set(this, mFd, devices, format, channels, sampleRate, acoustics); + if (status) { + *status = lStatus; + } + if (lStatus == NO_ERROR) { + mInput = in; + } else { + delete in; + } + return mInput; +} + +void AudioHardwareGeneric::closeInputStream(AudioStreamIn* in) { + if (mInput && in == mInput) { + delete mInput; + mInput = 0; + } +} + +status_t AudioHardwareGeneric::setVoiceVolume(float v) +{ + // Implement: set voice volume + return NO_ERROR; +} + +status_t AudioHardwareGeneric::setMasterVolume(float v) +{ + // Implement: set master volume + // return error - software mixer will handle it + return INVALID_OPERATION; +} + +status_t AudioHardwareGeneric::setMicMute(bool state) +{ + mMicMute = state; + return NO_ERROR; +} + +status_t AudioHardwareGeneric::getMicMute(bool* state) +{ + *state = mMicMute; + return NO_ERROR; +} + +status_t AudioHardwareGeneric::dumpInternals(int fd, const Vector<String16>& args) +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + result.append("AudioHardwareGeneric::dumpInternals\n"); + snprintf(buffer, SIZE, "\tmFd: %d mMicMute: %s\n", mFd, mMicMute? "true": "false"); + result.append(buffer); + ::write(fd, result.string(), result.size()); + return NO_ERROR; +} + +status_t AudioHardwareGeneric::dump(int fd, const Vector<String16>& args) +{ + dumpInternals(fd, args); + if (mInput) { + mInput->dump(fd, args); + } + if (mOutput) { + mOutput->dump(fd, args); + } + return NO_ERROR; +} + +// ---------------------------------------------------------------------------- + +status_t AudioStreamOutGeneric::set( + AudioHardwareGeneric *hw, + int fd, + 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; + + // 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; + + mAudioHardware = hw; + mFd = fd; + mDevice = devices; + return NO_ERROR; +} + +AudioStreamOutGeneric::~AudioStreamOutGeneric() +{ +} + +ssize_t AudioStreamOutGeneric::write(const void* buffer, size_t bytes) +{ + Mutex::Autolock _l(mLock); + return ssize_t(::write(mFd, buffer, bytes)); +} + +status_t AudioStreamOutGeneric::standby() +{ + // Implement: audio hardware to standby mode + return NO_ERROR; +} + +status_t AudioStreamOutGeneric::dump(int fd, const Vector<String16>& args) +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + snprintf(buffer, SIZE, "AudioStreamOutGeneric::dump\n"); + result.append(buffer); + 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, "\tdevice: %d\n", mDevice); + result.append(buffer); + snprintf(buffer, SIZE, "\tmAudioHardware: %p\n", mAudioHardware); + result.append(buffer); + snprintf(buffer, SIZE, "\tmFd: %d\n", mFd); + result.append(buffer); + ::write(fd, result.string(), result.size()); + return NO_ERROR; +} + +status_t AudioStreamOutGeneric::setParameters(const String8& keyValuePairs) +{ + AudioParameter param = AudioParameter(keyValuePairs); + String8 key = String8(AudioParameter::keyRouting); + status_t status = NO_ERROR; + int device; + LOGV("setParameters() %s", keyValuePairs.string()); + + if (param.getInt(key, device) == NO_ERROR) { + mDevice = device; + param.remove(key); + } + + if (param.size()) { + status = BAD_VALUE; + } + return status; +} + +String8 AudioStreamOutGeneric::getParameters(const String8& keys) +{ + AudioParameter param = AudioParameter(keys); + String8 value; + String8 key = String8(AudioParameter::keyRouting); + + if (param.get(key, value) == NO_ERROR) { + param.addInt(key, (int)mDevice); + } + + LOGV("getParameters() %s", param.toString().string()); + return param.toString(); +} + +status_t AudioStreamOutGeneric::getRenderPosition(uint32_t *dspFrames) +{ + return INVALID_OPERATION; +} + +// ---------------------------------------------------------------------------- + +// record functions +status_t AudioStreamInGeneric::set( + AudioHardwareGeneric *hw, + int fd, + uint32_t devices, + int *pFormat, + uint32_t *pChannels, + uint32_t *pRate, + AudioSystem::audio_in_acoustics acoustics) +{ + if (pFormat == 0 || pChannels == 0 || pRate == 0) return BAD_VALUE; + LOGV("AudioStreamInGeneric::set(%p, %d, %d, %d, %u)", hw, fd, *pFormat, *pChannels, *pRate); + // check values + if ((*pFormat != format()) || + (*pChannels != channels()) || + (*pRate != sampleRate())) { + LOGE("Error opening input channel"); + *pFormat = format(); + *pChannels = channels(); + *pRate = sampleRate(); + return BAD_VALUE; + } + + mAudioHardware = hw; + mFd = fd; + mDevice = devices; + return NO_ERROR; +} + +AudioStreamInGeneric::~AudioStreamInGeneric() +{ +} + +ssize_t AudioStreamInGeneric::read(void* buffer, ssize_t bytes) +{ + AutoMutex lock(mLock); + if (mFd < 0) { + LOGE("Attempt to read from unopened device"); + return NO_INIT; + } + return ::read(mFd, buffer, bytes); +} + +status_t AudioStreamInGeneric::dump(int fd, const Vector<String16>& args) +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + snprintf(buffer, SIZE, "AudioStreamInGeneric::dump\n"); + result.append(buffer); + 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, "\tdevice: %d\n", mDevice); + result.append(buffer); + snprintf(buffer, SIZE, "\tmAudioHardware: %p\n", mAudioHardware); + result.append(buffer); + snprintf(buffer, SIZE, "\tmFd: %d\n", mFd); + result.append(buffer); + ::write(fd, result.string(), result.size()); + return NO_ERROR; +} + +status_t AudioStreamInGeneric::setParameters(const String8& keyValuePairs) +{ + AudioParameter param = AudioParameter(keyValuePairs); + String8 key = String8(AudioParameter::keyRouting); + status_t status = NO_ERROR; + int device; + LOGV("setParameters() %s", keyValuePairs.string()); + + if (param.getInt(key, device) == NO_ERROR) { + mDevice = device; + param.remove(key); + } + + if (param.size()) { + status = BAD_VALUE; + } + return status; +} + +String8 AudioStreamInGeneric::getParameters(const String8& keys) +{ + AudioParameter param = AudioParameter(keys); + String8 value; + String8 key = String8(AudioParameter::keyRouting); + + if (param.get(key, value) == NO_ERROR) { + param.addInt(key, (int)mDevice); + } + + LOGV("getParameters() %s", param.toString().string()); + return param.toString(); +} + +// ---------------------------------------------------------------------------- + +}; // namespace android diff --git a/services/audioflinger/AudioHardwareGeneric.h b/services/audioflinger/AudioHardwareGeneric.h new file mode 100644 index 0000000..aa4e78d --- /dev/null +++ b/services/audioflinger/AudioHardwareGeneric.h @@ -0,0 +1,151 @@ +/* +** +** Copyright 2007, 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. +*/ + +#ifndef ANDROID_AUDIO_HARDWARE_GENERIC_H +#define ANDROID_AUDIO_HARDWARE_GENERIC_H + +#include <stdint.h> +#include <sys/types.h> + +#include <utils/threads.h> + +#include <hardware_legacy/AudioHardwareBase.h> + +namespace android { + +// ---------------------------------------------------------------------------- + +class AudioHardwareGeneric; + +class AudioStreamOutGeneric : public AudioStreamOut { +public: + AudioStreamOutGeneric() : mAudioHardware(0), mFd(-1) {} + virtual ~AudioStreamOutGeneric(); + + virtual status_t set( + AudioHardwareGeneric *hw, + int mFd, + uint32_t devices, + int *pFormat, + uint32_t *pChannels, + uint32_t *pRate); + + virtual uint32_t sampleRate() const { return 44100; } + virtual size_t bufferSize() const { return 4096; } + virtual uint32_t channels() const { return AudioSystem::CHANNEL_OUT_STEREO; } + virtual int format() const { return AudioSystem::PCM_16_BIT; } + virtual uint32_t latency() const { return 20; } + virtual status_t setVolume(float left, float right) { return INVALID_OPERATION; } + virtual ssize_t write(const void* buffer, size_t bytes); + virtual status_t standby(); + virtual status_t dump(int fd, const Vector<String16>& args); + virtual status_t setParameters(const String8& keyValuePairs); + virtual String8 getParameters(const String8& keys); + virtual status_t getRenderPosition(uint32_t *dspFrames); + +private: + AudioHardwareGeneric *mAudioHardware; + Mutex mLock; + int mFd; + uint32_t mDevice; +}; + +class AudioStreamInGeneric : public AudioStreamIn { +public: + AudioStreamInGeneric() : mAudioHardware(0), mFd(-1) {} + virtual ~AudioStreamInGeneric(); + + virtual status_t set( + AudioHardwareGeneric *hw, + int mFd, + uint32_t devices, + int *pFormat, + uint32_t *pChannels, + uint32_t *pRate, + AudioSystem::audio_in_acoustics acoustics); + + virtual uint32_t sampleRate() const { return 8000; } + virtual size_t bufferSize() const { return 320; } + virtual uint32_t channels() const { return AudioSystem::CHANNEL_IN_MONO; } + virtual int format() const { return AudioSystem::PCM_16_BIT; } + virtual status_t setGain(float gain) { return INVALID_OPERATION; } + virtual ssize_t read(void* buffer, ssize_t bytes); + virtual status_t dump(int fd, const Vector<String16>& args); + virtual status_t standby() { return NO_ERROR; } + virtual status_t setParameters(const String8& keyValuePairs); + virtual String8 getParameters(const String8& keys); + virtual unsigned int getInputFramesLost() const { return 0; } + +private: + AudioHardwareGeneric *mAudioHardware; + Mutex mLock; + int mFd; + uint32_t mDevice; +}; + + +class AudioHardwareGeneric : public AudioHardwareBase +{ +public: + AudioHardwareGeneric(); + virtual ~AudioHardwareGeneric(); + virtual status_t initCheck(); + virtual status_t setVoiceVolume(float volume); + virtual status_t setMasterVolume(float volume); + + // mic mute + virtual status_t setMicMute(bool state); + virtual status_t getMicMute(bool* state); + + // create I/O streams + virtual AudioStreamOut* openOutputStream( + uint32_t devices, + int *format=0, + uint32_t *channels=0, + uint32_t *sampleRate=0, + status_t *status=0); + virtual void closeOutputStream(AudioStreamOut* out); + + virtual AudioStreamIn* openInputStream( + uint32_t devices, + int *format, + uint32_t *channels, + uint32_t *sampleRate, + status_t *status, + AudioSystem::audio_in_acoustics acoustics); + virtual void closeInputStream(AudioStreamIn* in); + + void closeOutputStream(AudioStreamOutGeneric* out); + void closeInputStream(AudioStreamInGeneric* in); +protected: + virtual status_t dump(int fd, const Vector<String16>& args); + +private: + status_t dumpInternals(int fd, const Vector<String16>& args); + + Mutex mLock; + AudioStreamOutGeneric *mOutput; + AudioStreamInGeneric *mInput; + int mFd; + bool mMicMute; +}; + +// ---------------------------------------------------------------------------- + +}; // namespace android + +#endif // ANDROID_AUDIO_HARDWARE_GENERIC_H diff --git a/services/audioflinger/AudioHardwareInterface.cpp b/services/audioflinger/AudioHardwareInterface.cpp new file mode 100644 index 0000000..9a4a7f9 --- /dev/null +++ b/services/audioflinger/AudioHardwareInterface.cpp @@ -0,0 +1,182 @@ +/* +** +** Copyright 2007, 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 <cutils/properties.h> +#include <string.h> +#include <unistd.h> +//#define LOG_NDEBUG 0 + +#define LOG_TAG "AudioHardwareInterface" +#include <utils/Log.h> +#include <utils/String8.h> + +#include "AudioHardwareStub.h" +#include "AudioHardwareGeneric.h" +#ifdef WITH_A2DP +#include "A2dpAudioInterface.h" +#endif + +#ifdef ENABLE_AUDIO_DUMP +#include "AudioDumpInterface.h" +#endif + + +// change to 1 to log routing calls +#define LOG_ROUTING_CALLS 1 + +namespace android { + +#if LOG_ROUTING_CALLS +static const char* routingModeStrings[] = +{ + "OUT OF RANGE", + "INVALID", + "CURRENT", + "NORMAL", + "RINGTONE", + "IN_CALL" +}; + +static const char* routeNone = "NONE"; + +static const char* displayMode(int mode) +{ + if ((mode < -2) || (mode > 2)) + return routingModeStrings[0]; + return routingModeStrings[mode+3]; +} +#endif + +// ---------------------------------------------------------------------------- + +AudioHardwareInterface* AudioHardwareInterface::create() +{ + /* + * FIXME: This code needs to instantiate the correct audio device + * interface. For now - we use compile-time switches. + */ + AudioHardwareInterface* hw = 0; + char value[PROPERTY_VALUE_MAX]; + +#ifdef GENERIC_AUDIO + hw = new AudioHardwareGeneric(); +#else + // if running in emulation - use the emulator driver + if (property_get("ro.kernel.qemu", value, 0)) { + LOGD("Running in emulation - using generic audio driver"); + hw = new AudioHardwareGeneric(); + } + else { + LOGV("Creating Vendor Specific AudioHardware"); + hw = createAudioHardware(); + } +#endif + if (hw->initCheck() != NO_ERROR) { + LOGW("Using stubbed audio hardware. No sound will be produced."); + delete hw; + hw = new AudioHardwareStub(); + } + +#ifdef WITH_A2DP + hw = new A2dpAudioInterface(hw); +#endif + +#ifdef ENABLE_AUDIO_DUMP + // This code adds a record of buffers in a file to write calls made by AudioFlinger. + // It replaces the current AudioHardwareInterface object by an intermediate one which + // will record buffers in a file (after sending them to hardware) for testing purpose. + // This feature is enabled by defining symbol ENABLE_AUDIO_DUMP. + // The output file is set with setParameters("test_cmd_file_name=<name>"). Pause are not recorded in the file. + LOGV("opening PCM dump interface"); + hw = new AudioDumpInterface(hw); // replace interface +#endif + return hw; +} + +AudioStreamOut::~AudioStreamOut() +{ +} + +AudioStreamIn::~AudioStreamIn() {} + +AudioHardwareBase::AudioHardwareBase() +{ + mMode = 0; +} + +status_t AudioHardwareBase::setMode(int mode) +{ +#if LOG_ROUTING_CALLS + LOGD("setMode(%s)", displayMode(mode)); +#endif + if ((mode < 0) || (mode >= AudioSystem::NUM_MODES)) + return BAD_VALUE; + if (mMode == mode) + return ALREADY_EXISTS; + mMode = mode; + return NO_ERROR; +} + +// default implementation +status_t AudioHardwareBase::setParameters(const String8& keyValuePairs) +{ + return NO_ERROR; +} + +// default implementation +String8 AudioHardwareBase::getParameters(const String8& keys) +{ + AudioParameter param = AudioParameter(keys); + return param.toString(); +} + +// default implementation +size_t AudioHardwareBase::getInputBufferSize(uint32_t sampleRate, int format, int channelCount) +{ + if (sampleRate != 8000) { + LOGW("getInputBufferSize bad sampling rate: %d", sampleRate); + return 0; + } + if (format != AudioSystem::PCM_16_BIT) { + LOGW("getInputBufferSize bad format: %d", format); + return 0; + } + if (channelCount != 1) { + LOGW("getInputBufferSize bad channel count: %d", channelCount); + return 0; + } + + return 320; +} + +status_t AudioHardwareBase::dumpState(int fd, const Vector<String16>& args) +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + snprintf(buffer, SIZE, "AudioHardwareBase::dumpState\n"); + result.append(buffer); + snprintf(buffer, SIZE, "\tmMode: %d\n", mMode); + result.append(buffer); + ::write(fd, result.string(), result.size()); + dump(fd, args); // Dump the state of the concrete child. + return NO_ERROR; +} + +// ---------------------------------------------------------------------------- + +}; // namespace android diff --git a/services/audioflinger/AudioHardwareStub.cpp b/services/audioflinger/AudioHardwareStub.cpp new file mode 100644 index 0000000..d481150 --- /dev/null +++ b/services/audioflinger/AudioHardwareStub.cpp @@ -0,0 +1,209 @@ +/* //device/servers/AudioFlinger/AudioHardwareStub.cpp +** +** Copyright 2007, 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 <stdint.h> +#include <sys/types.h> + +#include <stdlib.h> +#include <unistd.h> +#include <utils/String8.h> + +#include "AudioHardwareStub.h" +#include <media/AudioRecord.h> + +namespace android { + +// ---------------------------------------------------------------------------- + +AudioHardwareStub::AudioHardwareStub() : mMicMute(false) +{ +} + +AudioHardwareStub::~AudioHardwareStub() +{ +} + +status_t AudioHardwareStub::initCheck() +{ + return NO_ERROR; +} + +AudioStreamOut* AudioHardwareStub::openOutputStream( + uint32_t devices, int *format, uint32_t *channels, uint32_t *sampleRate, status_t *status) +{ + AudioStreamOutStub* out = new AudioStreamOutStub(); + status_t lStatus = out->set(format, channels, sampleRate); + if (status) { + *status = lStatus; + } + if (lStatus == NO_ERROR) + return out; + delete out; + return 0; +} + +void AudioHardwareStub::closeOutputStream(AudioStreamOut* out) +{ + delete out; +} + +AudioStreamIn* AudioHardwareStub::openInputStream( + uint32_t devices, int *format, uint32_t *channels, uint32_t *sampleRate, + status_t *status, AudioSystem::audio_in_acoustics acoustics) +{ + // check for valid input source + if (!AudioSystem::isInputDevice((AudioSystem::audio_devices)devices)) { + return 0; + } + + AudioStreamInStub* in = new AudioStreamInStub(); + status_t lStatus = in->set(format, channels, sampleRate, acoustics); + if (status) { + *status = lStatus; + } + if (lStatus == NO_ERROR) + return in; + delete in; + return 0; +} + +void AudioHardwareStub::closeInputStream(AudioStreamIn* in) +{ + delete in; +} + +status_t AudioHardwareStub::setVoiceVolume(float volume) +{ + return NO_ERROR; +} + +status_t AudioHardwareStub::setMasterVolume(float volume) +{ + return NO_ERROR; +} + +status_t AudioHardwareStub::dumpInternals(int fd, const Vector<String16>& args) +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + result.append("AudioHardwareStub::dumpInternals\n"); + snprintf(buffer, SIZE, "\tmMicMute: %s\n", mMicMute? "true": "false"); + result.append(buffer); + ::write(fd, result.string(), result.size()); + return NO_ERROR; +} + +status_t AudioHardwareStub::dump(int fd, const Vector<String16>& args) +{ + dumpInternals(fd, args); + return NO_ERROR; +} + +// ---------------------------------------------------------------------------- + +status_t AudioStreamOutStub::set(int *pFormat, uint32_t *pChannels, uint32_t *pRate) +{ + if (pFormat) *pFormat = format(); + if (pChannels) *pChannels = channels(); + if (pRate) *pRate = sampleRate(); + + return NO_ERROR; +} + +ssize_t AudioStreamOutStub::write(const void* buffer, size_t bytes) +{ + // fake timing for audio output + usleep(bytes * 1000000 / sizeof(int16_t) / AudioSystem::popCount(channels()) / sampleRate()); + return bytes; +} + +status_t AudioStreamOutStub::standby() +{ + return NO_ERROR; +} + +status_t AudioStreamOutStub::dump(int fd, const Vector<String16>& args) +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + snprintf(buffer, SIZE, "AudioStreamOutStub::dump\n"); + snprintf(buffer, SIZE, "\tsample rate: %d\n", sampleRate()); + snprintf(buffer, SIZE, "\tbuffer size: %d\n", bufferSize()); + snprintf(buffer, SIZE, "\tchannels: %d\n", channels()); + snprintf(buffer, SIZE, "\tformat: %d\n", format()); + result.append(buffer); + ::write(fd, result.string(), result.size()); + return NO_ERROR; +} + +String8 AudioStreamOutStub::getParameters(const String8& keys) +{ + AudioParameter param = AudioParameter(keys); + return param.toString(); +} + +status_t AudioStreamOutStub::getRenderPosition(uint32_t *dspFrames) +{ + return INVALID_OPERATION; +} + +// ---------------------------------------------------------------------------- + +status_t AudioStreamInStub::set(int *pFormat, uint32_t *pChannels, uint32_t *pRate, + AudioSystem::audio_in_acoustics acoustics) +{ + return NO_ERROR; +} + +ssize_t AudioStreamInStub::read(void* buffer, ssize_t bytes) +{ + // fake timing for audio input + usleep(bytes * 1000000 / sizeof(int16_t) / AudioSystem::popCount(channels()) / sampleRate()); + memset(buffer, 0, bytes); + return bytes; +} + +status_t AudioStreamInStub::dump(int fd, const Vector<String16>& args) +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + snprintf(buffer, SIZE, "AudioStreamInStub::dump\n"); + result.append(buffer); + 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); + ::write(fd, result.string(), result.size()); + return NO_ERROR; +} + +String8 AudioStreamInStub::getParameters(const String8& keys) +{ + AudioParameter param = AudioParameter(keys); + return param.toString(); +} + +// ---------------------------------------------------------------------------- + +}; // namespace android diff --git a/services/audioflinger/AudioHardwareStub.h b/services/audioflinger/AudioHardwareStub.h new file mode 100644 index 0000000..06a29de --- /dev/null +++ b/services/audioflinger/AudioHardwareStub.h @@ -0,0 +1,106 @@ +/* //device/servers/AudioFlinger/AudioHardwareStub.h +** +** Copyright 2007, 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. +*/ + +#ifndef ANDROID_AUDIO_HARDWARE_STUB_H +#define ANDROID_AUDIO_HARDWARE_STUB_H + +#include <stdint.h> +#include <sys/types.h> + +#include <hardware_legacy/AudioHardwareBase.h> + +namespace android { + +// ---------------------------------------------------------------------------- + +class AudioStreamOutStub : public AudioStreamOut { +public: + virtual status_t set(int *pFormat, uint32_t *pChannels, uint32_t *pRate); + virtual uint32_t sampleRate() const { return 44100; } + virtual size_t bufferSize() const { return 4096; } + virtual uint32_t channels() const { return AudioSystem::CHANNEL_OUT_STEREO; } + virtual int format() const { return AudioSystem::PCM_16_BIT; } + virtual uint32_t latency() const { return 0; } + virtual status_t setVolume(float left, float right) { return NO_ERROR; } + virtual ssize_t write(const void* buffer, size_t bytes); + virtual status_t standby(); + virtual status_t dump(int fd, const Vector<String16>& args); + virtual status_t setParameters(const String8& keyValuePairs) { return NO_ERROR;} + virtual String8 getParameters(const String8& keys); + virtual status_t getRenderPosition(uint32_t *dspFrames); +}; + +class AudioStreamInStub : public AudioStreamIn { +public: + virtual status_t set(int *pFormat, uint32_t *pChannels, uint32_t *pRate, AudioSystem::audio_in_acoustics acoustics); + virtual uint32_t sampleRate() const { return 8000; } + virtual size_t bufferSize() const { return 320; } + virtual uint32_t channels() const { return AudioSystem::CHANNEL_IN_MONO; } + virtual int format() const { return AudioSystem::PCM_16_BIT; } + virtual status_t setGain(float gain) { return NO_ERROR; } + virtual ssize_t read(void* buffer, ssize_t bytes); + virtual status_t dump(int fd, const Vector<String16>& args); + virtual status_t standby() { return NO_ERROR; } + virtual status_t setParameters(const String8& keyValuePairs) { return NO_ERROR;} + virtual String8 getParameters(const String8& keys); + virtual unsigned int getInputFramesLost() const { return 0; } +}; + +class AudioHardwareStub : public AudioHardwareBase +{ +public: + AudioHardwareStub(); + virtual ~AudioHardwareStub(); + virtual status_t initCheck(); + virtual status_t setVoiceVolume(float volume); + virtual status_t setMasterVolume(float volume); + + // mic mute + virtual status_t setMicMute(bool state) { mMicMute = state; return NO_ERROR; } + virtual status_t getMicMute(bool* state) { *state = mMicMute ; return NO_ERROR; } + + // create I/O streams + virtual AudioStreamOut* openOutputStream( + uint32_t devices, + int *format=0, + uint32_t *channels=0, + uint32_t *sampleRate=0, + status_t *status=0); + virtual void closeOutputStream(AudioStreamOut* out); + + virtual AudioStreamIn* openInputStream( + uint32_t devices, + int *format, + uint32_t *channels, + uint32_t *sampleRate, + status_t *status, + AudioSystem::audio_in_acoustics acoustics); + virtual void closeInputStream(AudioStreamIn* in); + +protected: + virtual status_t dump(int fd, const Vector<String16>& args); + + bool mMicMute; +private: + status_t dumpInternals(int fd, const Vector<String16>& args); +}; + +// ---------------------------------------------------------------------------- + +}; // namespace android + +#endif // ANDROID_AUDIO_HARDWARE_STUB_H diff --git a/services/audioflinger/AudioMixer.cpp b/services/audioflinger/AudioMixer.cpp new file mode 100644 index 0000000..19a442a --- /dev/null +++ b/services/audioflinger/AudioMixer.cpp @@ -0,0 +1,915 @@ +/* //device/include/server/AudioFlinger/AudioMixer.cpp +** +** Copyright 2007, 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. +*/ + +#define LOG_TAG "AudioMixer" +//#define LOG_NDEBUG 0 + +#include <stdint.h> +#include <string.h> +#include <stdlib.h> +#include <sys/types.h> + +#include <utils/Errors.h> +#include <utils/Log.h> + +#include "AudioMixer.h" + +namespace android { +// ---------------------------------------------------------------------------- + +static inline int16_t clamp16(int32_t sample) +{ + if ((sample>>15) ^ (sample>>31)) + sample = 0x7FFF ^ (sample>>31); + return sample; +} + +// ---------------------------------------------------------------------------- + +AudioMixer::AudioMixer(size_t frameCount, uint32_t sampleRate) + : mActiveTrack(0), mTrackNames(0), mSampleRate(sampleRate) +{ + mState.enabledTracks= 0; + mState.needsChanged = 0; + mState.frameCount = frameCount; + mState.outputTemp = 0; + mState.resampleTemp = 0; + mState.hook = process__nop; + track_t* t = mState.tracks; + for (int i=0 ; i<32 ; i++) { + t->needs = 0; + t->volume[0] = UNITY_GAIN; + t->volume[1] = UNITY_GAIN; + t->volumeInc[0] = 0; + t->volumeInc[1] = 0; + t->channelCount = 2; + t->enabled = 0; + t->format = 16; + t->buffer.raw = 0; + t->bufferProvider = 0; + t->hook = 0; + t->resampler = 0; + t->sampleRate = mSampleRate; + t->in = 0; + t++; + } +} + + AudioMixer::~AudioMixer() + { + track_t* t = mState.tracks; + for (int i=0 ; i<32 ; i++) { + delete t->resampler; + t++; + } + delete [] mState.outputTemp; + delete [] mState.resampleTemp; + } + + int AudioMixer::getTrackName() + { + uint32_t names = mTrackNames; + uint32_t mask = 1; + int n = 0; + while (names & mask) { + mask <<= 1; + n++; + } + if (mask) { + LOGV("add track (%d)", n); + mTrackNames |= mask; + return TRACK0 + n; + } + return -1; + } + + void AudioMixer::invalidateState(uint32_t mask) + { + if (mask) { + mState.needsChanged |= mask; + mState.hook = process__validate; + } + } + + void AudioMixer::deleteTrackName(int name) + { + name -= TRACK0; + if (uint32_t(name) < MAX_NUM_TRACKS) { + LOGV("deleteTrackName(%d)", name); + track_t& track(mState.tracks[ name ]); + if (track.enabled != 0) { + track.enabled = 0; + invalidateState(1<<name); + } + if (track.resampler) { + // delete the resampler + delete track.resampler; + track.resampler = 0; + track.sampleRate = mSampleRate; + invalidateState(1<<name); + } + track.volumeInc[0] = 0; + track.volumeInc[1] = 0; + mTrackNames &= ~(1<<name); + } + } + +status_t AudioMixer::enable(int name) +{ + switch (name) { + case MIXING: { + if (mState.tracks[ mActiveTrack ].enabled != 1) { + mState.tracks[ mActiveTrack ].enabled = 1; + LOGV("enable(%d)", mActiveTrack); + invalidateState(1<<mActiveTrack); + } + } break; + default: + return NAME_NOT_FOUND; + } + return NO_ERROR; +} + +status_t AudioMixer::disable(int name) +{ + switch (name) { + case MIXING: { + if (mState.tracks[ mActiveTrack ].enabled != 0) { + mState.tracks[ mActiveTrack ].enabled = 0; + LOGV("disable(%d)", mActiveTrack); + invalidateState(1<<mActiveTrack); + } + } break; + default: + return NAME_NOT_FOUND; + } + return NO_ERROR; +} + +status_t AudioMixer::setActiveTrack(int track) +{ + if (uint32_t(track-TRACK0) >= MAX_NUM_TRACKS) { + return BAD_VALUE; + } + mActiveTrack = track - TRACK0; + return NO_ERROR; +} + +status_t AudioMixer::setParameter(int target, int name, int value) +{ + switch (target) { + case TRACK: + if (name == CHANNEL_COUNT) { + if ((uint32_t(value) <= MAX_NUM_CHANNELS) && (value)) { + if (mState.tracks[ mActiveTrack ].channelCount != value) { + mState.tracks[ mActiveTrack ].channelCount = value; + LOGV("setParameter(TRACK, CHANNEL_COUNT, %d)", value); + invalidateState(1<<mActiveTrack); + } + return NO_ERROR; + } + } + break; + case RESAMPLE: + if (name == SAMPLE_RATE) { + if (value > 0) { + track_t& track = mState.tracks[ mActiveTrack ]; + if (track.setResampler(uint32_t(value), mSampleRate)) { + LOGV("setParameter(RESAMPLE, SAMPLE_RATE, %u)", + uint32_t(value)); + invalidateState(1<<mActiveTrack); + } + return NO_ERROR; + } + } + break; + case RAMP_VOLUME: + case VOLUME: + if ((uint32_t(name-VOLUME0) < MAX_NUM_CHANNELS)) { + track_t& track = mState.tracks[ mActiveTrack ]; + if (track.volume[name-VOLUME0] != value) { + track.prevVolume[name-VOLUME0] = track.volume[name-VOLUME0] << 16; + track.volume[name-VOLUME0] = value; + if (target == VOLUME) { + track.prevVolume[name-VOLUME0] = value << 16; + track.volumeInc[name-VOLUME0] = 0; + } else { + int32_t d = (value<<16) - track.prevVolume[name-VOLUME0]; + int32_t volInc = d / int32_t(mState.frameCount); + track.volumeInc[name-VOLUME0] = volInc; + if (volInc == 0) { + track.prevVolume[name-VOLUME0] = value << 16; + } + } + invalidateState(1<<mActiveTrack); + } + return NO_ERROR; + } + break; + } + return BAD_VALUE; +} + +bool AudioMixer::track_t::setResampler(uint32_t value, uint32_t devSampleRate) +{ + if (value!=devSampleRate || resampler) { + if (sampleRate != value) { + sampleRate = value; + if (resampler == 0) { + resampler = AudioResampler::create( + format, channelCount, devSampleRate); + } + return true; + } + } + return false; +} + +bool AudioMixer::track_t::doesResample() const +{ + return resampler != 0; +} + +inline +void AudioMixer::track_t::adjustVolumeRamp() +{ + for (int i=0 ; i<2 ; i++) { + if (((volumeInc[i]>0) && (((prevVolume[i]+volumeInc[i])>>16) >= volume[i])) || + ((volumeInc[i]<0) && (((prevVolume[i]+volumeInc[i])>>16) <= volume[i]))) { + volumeInc[i] = 0; + prevVolume[i] = volume[i]<<16; + } + } +} + + +status_t AudioMixer::setBufferProvider(AudioBufferProvider* buffer) +{ + mState.tracks[ mActiveTrack ].bufferProvider = buffer; + return NO_ERROR; +} + + + +void AudioMixer::process(void* output) +{ + mState.hook(&mState, output); +} + + +void AudioMixer::process__validate(state_t* state, void* output) +{ + LOGW_IF(!state->needsChanged, + "in process__validate() but nothing's invalid"); + + uint32_t changed = state->needsChanged; + state->needsChanged = 0; // clear the validation flag + + // recompute which tracks are enabled / disabled + uint32_t enabled = 0; + uint32_t disabled = 0; + while (changed) { + const int i = 31 - __builtin_clz(changed); + const uint32_t mask = 1<<i; + changed &= ~mask; + track_t& t = state->tracks[i]; + (t.enabled ? enabled : disabled) |= mask; + } + state->enabledTracks &= ~disabled; + state->enabledTracks |= enabled; + + // compute everything we need... + int countActiveTracks = 0; + int all16BitsStereoNoResample = 1; + int resampling = 0; + int volumeRamp = 0; + uint32_t en = state->enabledTracks; + while (en) { + const int i = 31 - __builtin_clz(en); + en &= ~(1<<i); + + countActiveTracks++; + track_t& t = state->tracks[i]; + uint32_t n = 0; + n |= NEEDS_CHANNEL_1 + t.channelCount - 1; + n |= NEEDS_FORMAT_16; + n |= t.doesResample() ? NEEDS_RESAMPLE_ENABLED : NEEDS_RESAMPLE_DISABLED; + + if (t.volumeInc[0]|t.volumeInc[1]) { + volumeRamp = 1; + } else if (!t.doesResample() && t.volumeRL == 0) { + n |= NEEDS_MUTE_ENABLED; + } + t.needs = n; + + if ((n & NEEDS_MUTE__MASK) == NEEDS_MUTE_ENABLED) { + t.hook = track__nop; + } else { + if ((n & NEEDS_RESAMPLE__MASK) == NEEDS_RESAMPLE_ENABLED) { + all16BitsStereoNoResample = 0; + resampling = 1; + t.hook = track__genericResample; + } else { + if ((n & NEEDS_CHANNEL_COUNT__MASK) == NEEDS_CHANNEL_1){ + t.hook = track__16BitsMono; + all16BitsStereoNoResample = 0; + } + if ((n & NEEDS_CHANNEL_COUNT__MASK) == NEEDS_CHANNEL_2){ + t.hook = track__16BitsStereo; + } + } + } + } + + // select the processing hooks + state->hook = process__nop; + if (countActiveTracks) { + if (resampling) { + if (!state->outputTemp) { + state->outputTemp = new int32_t[MAX_NUM_CHANNELS * state->frameCount]; + } + if (!state->resampleTemp) { + state->resampleTemp = new int32_t[MAX_NUM_CHANNELS * state->frameCount]; + } + state->hook = process__genericResampling; + } else { + if (state->outputTemp) { + delete [] state->outputTemp; + state->outputTemp = 0; + } + if (state->resampleTemp) { + delete [] state->resampleTemp; + state->resampleTemp = 0; + } + state->hook = process__genericNoResampling; + if (all16BitsStereoNoResample && !volumeRamp) { + if (countActiveTracks == 1) { + state->hook = process__OneTrack16BitsStereoNoResampling; + } + } + } + } + + LOGV("mixer configuration change: %d activeTracks (%08x) " + "all16BitsStereoNoResample=%d, resampling=%d, volumeRamp=%d", + countActiveTracks, state->enabledTracks, + all16BitsStereoNoResample, resampling, volumeRamp); + + state->hook(state, output); + + // Now that the volume ramp has been done, set optimal state and + // track hooks for subsequent mixer process + if (countActiveTracks) { + int allMuted = 1; + uint32_t en = state->enabledTracks; + while (en) { + const int i = 31 - __builtin_clz(en); + en &= ~(1<<i); + track_t& t = state->tracks[i]; + if (!t.doesResample() && t.volumeRL == 0) + { + t.needs |= NEEDS_MUTE_ENABLED; + t.hook = track__nop; + } else { + allMuted = 0; + } + } + if (allMuted) { + state->hook = process__nop; + } else if (!resampling && all16BitsStereoNoResample) { + if (countActiveTracks == 1) { + state->hook = process__OneTrack16BitsStereoNoResampling; + } + } + } +} + +static inline +int32_t mulAdd(int16_t in, int16_t v, int32_t a) +{ +#if defined(__arm__) && !defined(__thumb__) + int32_t out; + asm( "smlabb %[out], %[in], %[v], %[a] \n" + : [out]"=r"(out) + : [in]"%r"(in), [v]"r"(v), [a]"r"(a) + : ); + return out; +#else + return a + in * int32_t(v); +#endif +} + +static inline +int32_t mul(int16_t in, int16_t v) +{ +#if defined(__arm__) && !defined(__thumb__) + int32_t out; + asm( "smulbb %[out], %[in], %[v] \n" + : [out]"=r"(out) + : [in]"%r"(in), [v]"r"(v) + : ); + return out; +#else + return in * int32_t(v); +#endif +} + +static inline +int32_t mulAddRL(int left, uint32_t inRL, uint32_t vRL, int32_t a) +{ +#if defined(__arm__) && !defined(__thumb__) + int32_t out; + if (left) { + asm( "smlabb %[out], %[inRL], %[vRL], %[a] \n" + : [out]"=r"(out) + : [inRL]"%r"(inRL), [vRL]"r"(vRL), [a]"r"(a) + : ); + } else { + asm( "smlatt %[out], %[inRL], %[vRL], %[a] \n" + : [out]"=r"(out) + : [inRL]"%r"(inRL), [vRL]"r"(vRL), [a]"r"(a) + : ); + } + return out; +#else + if (left) { + return a + int16_t(inRL&0xFFFF) * int16_t(vRL&0xFFFF); + } else { + return a + int16_t(inRL>>16) * int16_t(vRL>>16); + } +#endif +} + +static inline +int32_t mulRL(int left, uint32_t inRL, uint32_t vRL) +{ +#if defined(__arm__) && !defined(__thumb__) + int32_t out; + if (left) { + asm( "smulbb %[out], %[inRL], %[vRL] \n" + : [out]"=r"(out) + : [inRL]"%r"(inRL), [vRL]"r"(vRL) + : ); + } else { + asm( "smultt %[out], %[inRL], %[vRL] \n" + : [out]"=r"(out) + : [inRL]"%r"(inRL), [vRL]"r"(vRL) + : ); + } + return out; +#else + if (left) { + return int16_t(inRL&0xFFFF) * int16_t(vRL&0xFFFF); + } else { + return int16_t(inRL>>16) * int16_t(vRL>>16); + } +#endif +} + + +void AudioMixer::track__genericResample(track_t* t, int32_t* out, size_t outFrameCount, int32_t* temp) +{ + t->resampler->setSampleRate(t->sampleRate); + + // ramp gain - resample to temp buffer and scale/mix in 2nd step + if UNLIKELY(t->volumeInc[0]|t->volumeInc[1]) { + t->resampler->setVolume(UNITY_GAIN, UNITY_GAIN); + memset(temp, 0, outFrameCount * MAX_NUM_CHANNELS * sizeof(int32_t)); + t->resampler->resample(temp, outFrameCount, t->bufferProvider); + volumeRampStereo(t, out, outFrameCount, temp); + } + + // constant gain + else { + t->resampler->setVolume(t->volume[0], t->volume[1]); + t->resampler->resample(out, outFrameCount, t->bufferProvider); + } +} + +void AudioMixer::track__nop(track_t* t, int32_t* out, size_t outFrameCount, int32_t* temp) +{ +} + +void AudioMixer::volumeRampStereo(track_t* t, int32_t* out, size_t frameCount, int32_t* temp) +{ + int32_t vl = t->prevVolume[0]; + int32_t vr = t->prevVolume[1]; + const int32_t vlInc = t->volumeInc[0]; + const int32_t vrInc = t->volumeInc[1]; + + //LOGD("[0] %p: inc=%f, v0=%f, v1=%d, final=%f, count=%d", + // t, vlInc/65536.0f, vl/65536.0f, t->volume[0], + // (vl + vlInc*frameCount)/65536.0f, frameCount); + + // ramp volume + do { + *out++ += (vl >> 16) * (*temp++ >> 12); + *out++ += (vr >> 16) * (*temp++ >> 12); + vl += vlInc; + vr += vrInc; + } while (--frameCount); + + t->prevVolume[0] = vl; + t->prevVolume[1] = vr; + t->adjustVolumeRamp(); +} + +void AudioMixer::track__16BitsStereo(track_t* t, int32_t* out, size_t frameCount, int32_t* temp) +{ + int16_t const *in = static_cast<int16_t const *>(t->in); + + // ramp gain + if UNLIKELY(t->volumeInc[0]|t->volumeInc[1]) { + int32_t vl = t->prevVolume[0]; + int32_t vr = t->prevVolume[1]; + const int32_t vlInc = t->volumeInc[0]; + const int32_t vrInc = t->volumeInc[1]; + + // LOGD("[1] %p: inc=%f, v0=%f, v1=%d, final=%f, count=%d", + // t, vlInc/65536.0f, vl/65536.0f, t->volume[0], + // (vl + vlInc*frameCount)/65536.0f, frameCount); + + do { + *out++ += (vl >> 16) * (int32_t) *in++; + *out++ += (vr >> 16) * (int32_t) *in++; + vl += vlInc; + vr += vrInc; + } while (--frameCount); + + t->prevVolume[0] = vl; + t->prevVolume[1] = vr; + t->adjustVolumeRamp(); + } + + // constant gain + else { + const uint32_t vrl = t->volumeRL; + do { + uint32_t rl = *reinterpret_cast<uint32_t const *>(in); + in += 2; + out[0] = mulAddRL(1, rl, vrl, out[0]); + out[1] = mulAddRL(0, rl, vrl, out[1]); + out += 2; + } while (--frameCount); + } + t->in = in; +} + +void AudioMixer::track__16BitsMono(track_t* t, int32_t* out, size_t frameCount, int32_t* temp) +{ + int16_t const *in = static_cast<int16_t const *>(t->in); + + // ramp gain + if UNLIKELY(t->volumeInc[0]|t->volumeInc[1]) { + int32_t vl = t->prevVolume[0]; + int32_t vr = t->prevVolume[1]; + const int32_t vlInc = t->volumeInc[0]; + const int32_t vrInc = t->volumeInc[1]; + + // LOGD("[2] %p: inc=%f, v0=%f, v1=%d, final=%f, count=%d", + // t, vlInc/65536.0f, vl/65536.0f, t->volume[0], + // (vl + vlInc*frameCount)/65536.0f, frameCount); + + do { + int32_t l = *in++; + *out++ += (vl >> 16) * l; + *out++ += (vr >> 16) * l; + vl += vlInc; + vr += vrInc; + } while (--frameCount); + + t->prevVolume[0] = vl; + t->prevVolume[1] = vr; + t->adjustVolumeRamp(); + } + // constant gain + else { + const int16_t vl = t->volume[0]; + const int16_t vr = t->volume[1]; + do { + int16_t l = *in++; + out[0] = mulAdd(l, vl, out[0]); + out[1] = mulAdd(l, vr, out[1]); + out += 2; + } while (--frameCount); + } + t->in = in; +} + +void AudioMixer::ditherAndClamp(int32_t* out, int32_t const *sums, size_t c) +{ + for (size_t i=0 ; i<c ; i++) { + int32_t l = *sums++; + int32_t r = *sums++; + int32_t nl = l >> 12; + int32_t nr = r >> 12; + l = clamp16(nl); + r = clamp16(nr); + *out++ = (r<<16) | (l & 0xFFFF); + } +} + +// no-op case +void AudioMixer::process__nop(state_t* state, void* output) +{ + // this assumes output 16 bits stereo, no resampling + memset(output, 0, state->frameCount*4); + uint32_t en = state->enabledTracks; + while (en) { + const int i = 31 - __builtin_clz(en); + en &= ~(1<<i); + track_t& t = state->tracks[i]; + size_t outFrames = state->frameCount; + while (outFrames) { + t.buffer.frameCount = outFrames; + t.bufferProvider->getNextBuffer(&t.buffer); + if (!t.buffer.raw) break; + outFrames -= t.buffer.frameCount; + t.bufferProvider->releaseBuffer(&t.buffer); + } + } +} + +// generic code without resampling +void AudioMixer::process__genericNoResampling(state_t* state, void* output) +{ + int32_t outTemp[BLOCKSIZE * MAX_NUM_CHANNELS] __attribute__((aligned(32))); + + // acquire each track's buffer + uint32_t enabledTracks = state->enabledTracks; + uint32_t en = enabledTracks; + while (en) { + const int i = 31 - __builtin_clz(en); + en &= ~(1<<i); + track_t& t = state->tracks[i]; + t.buffer.frameCount = state->frameCount; + t.bufferProvider->getNextBuffer(&t.buffer); + t.frameCount = t.buffer.frameCount; + t.in = t.buffer.raw; + // t.in == NULL can happen if the track was flushed just after having + // been enabled for mixing. + if (t.in == NULL) + enabledTracks &= ~(1<<i); + } + + // this assumes output 16 bits stereo, no resampling + int32_t* out = static_cast<int32_t*>(output); + size_t numFrames = state->frameCount; + do { + memset(outTemp, 0, sizeof(outTemp)); + + en = enabledTracks; + while (en) { + const int i = 31 - __builtin_clz(en); + en &= ~(1<<i); + track_t& t = state->tracks[i]; + size_t outFrames = BLOCKSIZE; + + while (outFrames) { + size_t inFrames = (t.frameCount > outFrames)?outFrames:t.frameCount; + if (inFrames) { + (t.hook)(&t, outTemp + (BLOCKSIZE-outFrames)*MAX_NUM_CHANNELS, inFrames, state->resampleTemp); + t.frameCount -= inFrames; + outFrames -= inFrames; + } + if (t.frameCount == 0 && outFrames) { + t.bufferProvider->releaseBuffer(&t.buffer); + t.buffer.frameCount = numFrames - (BLOCKSIZE - outFrames); + t.bufferProvider->getNextBuffer(&t.buffer); + t.in = t.buffer.raw; + if (t.in == NULL) { + enabledTracks &= ~(1<<i); + break; + } + t.frameCount = t.buffer.frameCount; + } + } + } + + ditherAndClamp(out, outTemp, BLOCKSIZE); + out += BLOCKSIZE; + numFrames -= BLOCKSIZE; + } while (numFrames); + + + // release each track's buffer + en = enabledTracks; + while (en) { + const int i = 31 - __builtin_clz(en); + en &= ~(1<<i); + track_t& t = state->tracks[i]; + t.bufferProvider->releaseBuffer(&t.buffer); + } +} + +// generic code with resampling +void AudioMixer::process__genericResampling(state_t* state, void* output) +{ + int32_t* const outTemp = state->outputTemp; + const size_t size = sizeof(int32_t) * MAX_NUM_CHANNELS * state->frameCount; + memset(outTemp, 0, size); + + int32_t* out = static_cast<int32_t*>(output); + size_t numFrames = state->frameCount; + + uint32_t en = state->enabledTracks; + while (en) { + const int i = 31 - __builtin_clz(en); + en &= ~(1<<i); + track_t& t = state->tracks[i]; + + // this is a little goofy, on the resampling case we don't + // acquire/release the buffers because it's done by + // the resampler. + if ((t.needs & NEEDS_RESAMPLE__MASK) == NEEDS_RESAMPLE_ENABLED) { + (t.hook)(&t, outTemp, numFrames, state->resampleTemp); + } else { + + size_t outFrames = numFrames; + + while (outFrames) { + t.buffer.frameCount = outFrames; + t.bufferProvider->getNextBuffer(&t.buffer); + t.in = t.buffer.raw; + // t.in == NULL can happen if the track was flushed just after having + // been enabled for mixing. + if (t.in == NULL) break; + + (t.hook)(&t, outTemp + (numFrames-outFrames)*MAX_NUM_CHANNELS, t.buffer.frameCount, state->resampleTemp); + outFrames -= t.buffer.frameCount; + t.bufferProvider->releaseBuffer(&t.buffer); + } + } + } + + ditherAndClamp(out, outTemp, numFrames); +} + +// one track, 16 bits stereo without resampling is the most common case +void AudioMixer::process__OneTrack16BitsStereoNoResampling(state_t* state, void* output) +{ + const int i = 31 - __builtin_clz(state->enabledTracks); + const track_t& t = state->tracks[i]; + + AudioBufferProvider::Buffer& b(t.buffer); + + int32_t* out = static_cast<int32_t*>(output); + size_t numFrames = state->frameCount; + + const int16_t vl = t.volume[0]; + const int16_t vr = t.volume[1]; + const uint32_t vrl = t.volumeRL; + while (numFrames) { + b.frameCount = numFrames; + t.bufferProvider->getNextBuffer(&b); + int16_t const *in = b.i16; + + // in == NULL can happen if the track was flushed just after having + // been enabled for mixing. + if (in == NULL || ((unsigned long)in & 3)) { + memset(out, 0, numFrames*MAX_NUM_CHANNELS*sizeof(int16_t)); + LOGE_IF(((unsigned long)in & 3), "process stereo track: input buffer alignment pb: buffer %p track %d, channels %d, needs %08x", + in, i, t.channelCount, t.needs); + return; + } + size_t outFrames = b.frameCount; + + if (UNLIKELY(uint32_t(vl) > UNITY_GAIN || uint32_t(vr) > UNITY_GAIN)) { + // volume is boosted, so we might need to clamp even though + // we process only one track. + do { + uint32_t rl = *reinterpret_cast<uint32_t const *>(in); + in += 2; + int32_t l = mulRL(1, rl, vrl) >> 12; + int32_t r = mulRL(0, rl, vrl) >> 12; + // clamping... + l = clamp16(l); + r = clamp16(r); + *out++ = (r<<16) | (l & 0xFFFF); + } while (--outFrames); + } else { + do { + uint32_t rl = *reinterpret_cast<uint32_t const *>(in); + in += 2; + int32_t l = mulRL(1, rl, vrl) >> 12; + int32_t r = mulRL(0, rl, vrl) >> 12; + *out++ = (r<<16) | (l & 0xFFFF); + } while (--outFrames); + } + numFrames -= b.frameCount; + t.bufferProvider->releaseBuffer(&b); + } +} + +// 2 tracks is also a common case +void AudioMixer::process__TwoTracks16BitsStereoNoResampling(state_t* state, void* output) +{ + int i; + uint32_t en = state->enabledTracks; + + i = 31 - __builtin_clz(en); + const track_t& t0 = state->tracks[i]; + AudioBufferProvider::Buffer& b0(t0.buffer); + + en &= ~(1<<i); + i = 31 - __builtin_clz(en); + const track_t& t1 = state->tracks[i]; + AudioBufferProvider::Buffer& b1(t1.buffer); + + int16_t const *in0; + const int16_t vl0 = t0.volume[0]; + const int16_t vr0 = t0.volume[1]; + size_t frameCount0 = 0; + + int16_t const *in1; + const int16_t vl1 = t1.volume[0]; + const int16_t vr1 = t1.volume[1]; + size_t frameCount1 = 0; + + int32_t* out = static_cast<int32_t*>(output); + size_t numFrames = state->frameCount; + int16_t const *buff = NULL; + + + while (numFrames) { + + if (frameCount0 == 0) { + b0.frameCount = numFrames; + t0.bufferProvider->getNextBuffer(&b0); + if (b0.i16 == NULL) { + if (buff == NULL) { + buff = new int16_t[MAX_NUM_CHANNELS * state->frameCount]; + } + in0 = buff; + b0.frameCount = numFrames; + } else { + in0 = b0.i16; + } + frameCount0 = b0.frameCount; + } + if (frameCount1 == 0) { + b1.frameCount = numFrames; + t1.bufferProvider->getNextBuffer(&b1); + if (b1.i16 == NULL) { + if (buff == NULL) { + buff = new int16_t[MAX_NUM_CHANNELS * state->frameCount]; + } + in1 = buff; + b1.frameCount = numFrames; + } else { + in1 = b1.i16; + } + frameCount1 = b1.frameCount; + } + + size_t outFrames = frameCount0 < frameCount1?frameCount0:frameCount1; + + numFrames -= outFrames; + frameCount0 -= outFrames; + frameCount1 -= outFrames; + + do { + int32_t l0 = *in0++; + int32_t r0 = *in0++; + l0 = mul(l0, vl0); + r0 = mul(r0, vr0); + int32_t l = *in1++; + int32_t r = *in1++; + l = mulAdd(l, vl1, l0) >> 12; + r = mulAdd(r, vr1, r0) >> 12; + // clamping... + l = clamp16(l); + r = clamp16(r); + *out++ = (r<<16) | (l & 0xFFFF); + } while (--outFrames); + + if (frameCount0 == 0) { + t0.bufferProvider->releaseBuffer(&b0); + } + if (frameCount1 == 0) { + t1.bufferProvider->releaseBuffer(&b1); + } + } + + if (buff != NULL) { + delete [] buff; + } +} + +// ---------------------------------------------------------------------------- +}; // namespace android + diff --git a/services/audioflinger/AudioMixer.h b/services/audioflinger/AudioMixer.h new file mode 100644 index 0000000..15766cd --- /dev/null +++ b/services/audioflinger/AudioMixer.h @@ -0,0 +1,193 @@ +/* //device/include/server/AudioFlinger/AudioMixer.h +** +** Copyright 2007, 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. +*/ + +#ifndef ANDROID_AUDIO_MIXER_H +#define ANDROID_AUDIO_MIXER_H + +#include <stdint.h> +#include <sys/types.h> + +#include "AudioBufferProvider.h" +#include "AudioResampler.h" + +namespace android { + +// ---------------------------------------------------------------------------- + +#define LIKELY( exp ) (__builtin_expect( (exp) != 0, true )) +#define UNLIKELY( exp ) (__builtin_expect( (exp) != 0, false )) + +// ---------------------------------------------------------------------------- + +class AudioMixer +{ +public: + AudioMixer(size_t frameCount, uint32_t sampleRate); + + ~AudioMixer(); + + static const uint32_t MAX_NUM_TRACKS = 32; + static const uint32_t MAX_NUM_CHANNELS = 2; + + static const uint16_t UNITY_GAIN = 0x1000; + + enum { // names + + // track units (32 units) + TRACK0 = 0x1000, + + // enable/disable + MIXING = 0x2000, + + // setParameter targets + TRACK = 0x3000, + RESAMPLE = 0x3001, + RAMP_VOLUME = 0x3002, // ramp to new volume + VOLUME = 0x3003, // don't ramp + + // set Parameter names + // for target TRACK + CHANNEL_COUNT = 0x4000, + FORMAT = 0x4001, + // for TARGET RESAMPLE + SAMPLE_RATE = 0x4100, + // for TARGET VOLUME (8 channels max) + VOLUME0 = 0x4200, + VOLUME1 = 0x4201, + }; + + + int getTrackName(); + void deleteTrackName(int name); + + status_t enable(int name); + status_t disable(int name); + + status_t setActiveTrack(int track); + status_t setParameter(int target, int name, int value); + + status_t setBufferProvider(AudioBufferProvider* bufferProvider); + void process(void* output); + + uint32_t trackNames() const { return mTrackNames; } + + static void ditherAndClamp(int32_t* out, int32_t const *sums, size_t c); + +private: + + enum { + NEEDS_CHANNEL_COUNT__MASK = 0x00000003, + NEEDS_FORMAT__MASK = 0x000000F0, + NEEDS_MUTE__MASK = 0x00000100, + NEEDS_RESAMPLE__MASK = 0x00001000, + }; + + enum { + NEEDS_CHANNEL_1 = 0x00000000, + NEEDS_CHANNEL_2 = 0x00000001, + + NEEDS_FORMAT_16 = 0x00000010, + + NEEDS_MUTE_DISABLED = 0x00000000, + NEEDS_MUTE_ENABLED = 0x00000100, + + NEEDS_RESAMPLE_DISABLED = 0x00000000, + NEEDS_RESAMPLE_ENABLED = 0x00001000, + }; + + static inline int32_t applyVolume(int32_t in, int32_t v) { + return in * v; + } + + + struct state_t; + + typedef void (*mix_t)(state_t* state, void* output); + + static const int BLOCKSIZE = 16; // 4 cache lines + + struct track_t { + uint32_t needs; + + union { + int16_t volume[2]; // [0]3.12 fixed point + int32_t volumeRL; + }; + + int32_t prevVolume[2]; + + int32_t volumeInc[2]; + + uint16_t frameCount; + + uint8_t channelCount : 4; + uint8_t enabled : 1; + uint8_t reserved0 : 3; + uint8_t format; + + AudioBufferProvider* bufferProvider; + mutable AudioBufferProvider::Buffer buffer; + + void (*hook)(track_t* t, int32_t* output, size_t numOutFrames, int32_t* temp); + void const* in; // current location in buffer + + AudioResampler* resampler; + uint32_t sampleRate; + + bool setResampler(uint32_t sampleRate, uint32_t devSampleRate); + bool doesResample() const; + void adjustVolumeRamp(); + }; + + // pad to 32-bytes to fill cache line + struct state_t { + uint32_t enabledTracks; + uint32_t needsChanged; + size_t frameCount; + mix_t hook; + int32_t *outputTemp; + int32_t *resampleTemp; + int32_t reserved[2]; + track_t tracks[32]; __attribute__((aligned(32))); + }; + + int mActiveTrack; + uint32_t mTrackNames; + const uint32_t mSampleRate; + + state_t mState __attribute__((aligned(32))); + + void invalidateState(uint32_t mask); + + static void track__genericResample(track_t* t, int32_t* out, size_t numFrames, int32_t* temp); + static void track__nop(track_t* t, int32_t* out, size_t numFrames, int32_t* temp); + static void volumeRampStereo(track_t* t, int32_t* out, size_t frameCount, int32_t* temp); + static void track__16BitsStereo(track_t* t, int32_t* out, size_t numFrames, int32_t* temp); + static void track__16BitsMono(track_t* t, int32_t* out, size_t numFrames, int32_t* temp); + + static void process__validate(state_t* state, void* output); + static void process__nop(state_t* state, void* output); + static void process__genericNoResampling(state_t* state, void* output); + static void process__genericResampling(state_t* state, void* output); + static void process__OneTrack16BitsStereoNoResampling(state_t* state, void* output); + static void process__TwoTracks16BitsStereoNoResampling(state_t* state, void* output); +}; + +// ---------------------------------------------------------------------------- +}; // namespace android + +#endif // ANDROID_AUDIO_MIXER_H diff --git a/services/audioflinger/AudioPolicyManagerBase.cpp b/services/audioflinger/AudioPolicyManagerBase.cpp new file mode 100644 index 0000000..c8b3f48 --- /dev/null +++ b/services/audioflinger/AudioPolicyManagerBase.cpp @@ -0,0 +1,1972 @@ +/* + * Copyright (C) 2009 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. + */ + +#define LOG_TAG "AudioPolicyManagerBase" +//#define LOG_NDEBUG 0 +#include <utils/Log.h> +#include <hardware_legacy/AudioPolicyManagerBase.h> +#include <media/mediarecorder.h> + +namespace android { + + +// ---------------------------------------------------------------------------- +// AudioPolicyInterface implementation +// ---------------------------------------------------------------------------- + + +status_t AudioPolicyManagerBase::setDeviceConnectionState(AudioSystem::audio_devices device, + AudioSystem::device_connection_state state, + const char *device_address) +{ + + LOGV("setDeviceConnectionState() device: %x, state %d, address %s", device, state, device_address); + + // connect/disconnect only 1 device at a time + if (AudioSystem::popCount(device) != 1) return BAD_VALUE; + + if (strlen(device_address) >= MAX_DEVICE_ADDRESS_LEN) { + LOGE("setDeviceConnectionState() invalid address: %s", device_address); + return BAD_VALUE; + } + + // handle output devices + if (AudioSystem::isOutputDevice(device)) { + +#ifndef WITH_A2DP + if (AudioSystem::isA2dpDevice(device)) { + LOGE("setDeviceConnectionState() invalid device: %x", device); + return BAD_VALUE; + } +#endif + + switch (state) + { + // handle output device connection + case AudioSystem::DEVICE_STATE_AVAILABLE: + if (mAvailableOutputDevices & device) { + LOGW("setDeviceConnectionState() device already connected: %x", device); + return INVALID_OPERATION; + } + LOGV("setDeviceConnectionState() connecting device %x", device); + + // register new device as available + mAvailableOutputDevices |= device; + +#ifdef WITH_A2DP + // handle A2DP device connection + if (AudioSystem::isA2dpDevice(device)) { + status_t status = handleA2dpConnection(device, device_address); + if (status != NO_ERROR) { + mAvailableOutputDevices &= ~device; + return status; + } + } else +#endif + { + if (AudioSystem::isBluetoothScoDevice(device)) { + LOGV("setDeviceConnectionState() BT SCO device, address %s", device_address); + // keep track of SCO device address + mScoDeviceAddress = String8(device_address, MAX_DEVICE_ADDRESS_LEN); +#ifdef WITH_A2DP + if (mA2dpOutput != 0 && + mPhoneState != AudioSystem::MODE_NORMAL) { + mpClientInterface->suspendOutput(mA2dpOutput); + } +#endif + } + } + break; + // handle output device disconnection + case AudioSystem::DEVICE_STATE_UNAVAILABLE: { + if (!(mAvailableOutputDevices & device)) { + LOGW("setDeviceConnectionState() device not connected: %x", device); + return INVALID_OPERATION; + } + + + LOGV("setDeviceConnectionState() disconnecting device %x", device); + // remove device from available output devices + mAvailableOutputDevices &= ~device; + +#ifdef WITH_A2DP + // handle A2DP device disconnection + if (AudioSystem::isA2dpDevice(device)) { + status_t status = handleA2dpDisconnection(device, device_address); + if (status != NO_ERROR) { + mAvailableOutputDevices |= device; + return status; + } + } else +#endif + { + if (AudioSystem::isBluetoothScoDevice(device)) { + mScoDeviceAddress = ""; +#ifdef WITH_A2DP + if (mA2dpOutput != 0 && + mPhoneState != AudioSystem::MODE_NORMAL) { + mpClientInterface->restoreOutput(mA2dpOutput); + } +#endif + } + } + } break; + + default: + LOGE("setDeviceConnectionState() invalid state: %x", state); + return BAD_VALUE; + } + + // request routing change if necessary + uint32_t newDevice = getNewDevice(mHardwareOutput, false); +#ifdef WITH_A2DP + checkOutputForAllStrategies(newDevice); + // A2DP outputs must be closed after checkOutputForAllStrategies() is executed + if (state == AudioSystem::DEVICE_STATE_UNAVAILABLE && AudioSystem::isA2dpDevice(device)) { + closeA2dpOutputs(); + } +#endif + updateDeviceForStrategy(); + setOutputDevice(mHardwareOutput, newDevice); + + if (device == AudioSystem::DEVICE_OUT_WIRED_HEADSET) { + device = AudioSystem::DEVICE_IN_WIRED_HEADSET; + } else if (device == AudioSystem::DEVICE_OUT_BLUETOOTH_SCO || + device == AudioSystem::DEVICE_OUT_BLUETOOTH_SCO_HEADSET || + device == AudioSystem::DEVICE_OUT_BLUETOOTH_SCO_CARKIT) { + device = AudioSystem::DEVICE_IN_BLUETOOTH_SCO_HEADSET; + } else { + return NO_ERROR; + } + } + // handle input devices + if (AudioSystem::isInputDevice(device)) { + + switch (state) + { + // handle input device connection + case AudioSystem::DEVICE_STATE_AVAILABLE: { + if (mAvailableInputDevices & device) { + LOGW("setDeviceConnectionState() device already connected: %d", device); + return INVALID_OPERATION; + } + mAvailableInputDevices |= device; + } + break; + + // handle input device disconnection + case AudioSystem::DEVICE_STATE_UNAVAILABLE: { + if (!(mAvailableInputDevices & device)) { + LOGW("setDeviceConnectionState() device not connected: %d", device); + return INVALID_OPERATION; + } + mAvailableInputDevices &= ~device; + } break; + + default: + LOGE("setDeviceConnectionState() invalid state: %x", state); + return BAD_VALUE; + } + + audio_io_handle_t activeInput = getActiveInput(); + if (activeInput != 0) { + AudioInputDescriptor *inputDesc = mInputs.valueFor(activeInput); + uint32_t newDevice = getDeviceForInputSource(inputDesc->mInputSource); + if (newDevice != inputDesc->mDevice) { + LOGV("setDeviceConnectionState() changing device from %x to %x for input %d", + inputDesc->mDevice, newDevice, activeInput); + inputDesc->mDevice = newDevice; + AudioParameter param = AudioParameter(); + param.addInt(String8(AudioParameter::keyRouting), (int)newDevice); + mpClientInterface->setParameters(activeInput, param.toString()); + } + } + + return NO_ERROR; + } + + LOGW("setDeviceConnectionState() invalid device: %x", device); + return BAD_VALUE; +} + +AudioSystem::device_connection_state AudioPolicyManagerBase::getDeviceConnectionState(AudioSystem::audio_devices device, + const char *device_address) +{ + AudioSystem::device_connection_state state = AudioSystem::DEVICE_STATE_UNAVAILABLE; + String8 address = String8(device_address); + if (AudioSystem::isOutputDevice(device)) { + if (device & mAvailableOutputDevices) { +#ifdef WITH_A2DP + if (AudioSystem::isA2dpDevice(device) && + address != "" && mA2dpDeviceAddress != address) { + return state; + } +#endif + if (AudioSystem::isBluetoothScoDevice(device) && + address != "" && mScoDeviceAddress != address) { + return state; + } + state = AudioSystem::DEVICE_STATE_AVAILABLE; + } + } else if (AudioSystem::isInputDevice(device)) { + if (device & mAvailableInputDevices) { + state = AudioSystem::DEVICE_STATE_AVAILABLE; + } + } + + return state; +} + +void AudioPolicyManagerBase::setPhoneState(int state) +{ + LOGV("setPhoneState() state %d", state); + uint32_t newDevice = 0; + if (state < 0 || state >= AudioSystem::NUM_MODES) { + LOGW("setPhoneState() invalid state %d", state); + return; + } + + if (state == mPhoneState ) { + LOGW("setPhoneState() setting same state %d", state); + return; + } + + // if leaving call state, handle special case of active streams + // pertaining to sonification strategy see handleIncallSonification() + if (mPhoneState == AudioSystem::MODE_IN_CALL) { + LOGV("setPhoneState() in call state management: new state is %d", state); + for (int stream = 0; stream < AudioSystem::NUM_STREAM_TYPES; stream++) { + handleIncallSonification(stream, false, true); + } + } + + // store previous phone state for management of sonification strategy below + int oldState = mPhoneState; + mPhoneState = state; + bool force = false; + + // are we entering or starting a call + if ((oldState != AudioSystem::MODE_IN_CALL) && (state == AudioSystem::MODE_IN_CALL)) { + LOGV(" Entering call in setPhoneState()"); + // force routing command to audio hardware when starting a call + // even if no device change is needed + force = true; + } else if ((oldState == AudioSystem::MODE_IN_CALL) && (state != AudioSystem::MODE_IN_CALL)) { + LOGV(" Exiting call in setPhoneState()"); + // force routing command to audio hardware when exiting a call + // even if no device change is needed + force = true; + } + + // check for device and output changes triggered by new phone state + newDevice = getNewDevice(mHardwareOutput, false); +#ifdef WITH_A2DP + checkOutputForAllStrategies(newDevice); + // suspend A2DP output if a SCO device is present. + if (mA2dpOutput != 0 && mScoDeviceAddress != "") { + if (oldState == AudioSystem::MODE_NORMAL) { + mpClientInterface->suspendOutput(mA2dpOutput); + } else if (state == AudioSystem::MODE_NORMAL) { + mpClientInterface->restoreOutput(mA2dpOutput); + } + } +#endif + updateDeviceForStrategy(); + + AudioOutputDescriptor *hwOutputDesc = mOutputs.valueFor(mHardwareOutput); + + // force routing command to audio hardware when ending call + // even if no device change is needed + if (oldState == AudioSystem::MODE_IN_CALL && newDevice == 0) { + newDevice = hwOutputDesc->device(); + } + + // when changing from ring tone to in call mode, mute the ringing tone + // immediately and delay the route change to avoid sending the ring tone + // tail into the earpiece or headset. + int delayMs = 0; + if (state == AudioSystem::MODE_IN_CALL && oldState == AudioSystem::MODE_RINGTONE) { + // delay the device change command by twice the output latency to have some margin + // and be sure that audio buffers not yet affected by the mute are out when + // we actually apply the route change + delayMs = hwOutputDesc->mLatency*2; + setStreamMute(AudioSystem::RING, true, mHardwareOutput); + } + + // change routing is necessary + setOutputDevice(mHardwareOutput, newDevice, force, delayMs); + + // if entering in call state, handle special case of active streams + // pertaining to sonification strategy see handleIncallSonification() + if (state == AudioSystem::MODE_IN_CALL) { + LOGV("setPhoneState() in call state management: new state is %d", state); + // unmute the ringing tone after a sufficient delay if it was muted before + // setting output device above + if (oldState == AudioSystem::MODE_RINGTONE) { + setStreamMute(AudioSystem::RING, false, mHardwareOutput, MUTE_TIME_MS); + } + for (int stream = 0; stream < AudioSystem::NUM_STREAM_TYPES; stream++) { + handleIncallSonification(stream, true, true); + } + } + + // Flag that ringtone volume must be limited to music volume until we exit MODE_RINGTONE + if (state == AudioSystem::MODE_RINGTONE && + (hwOutputDesc->mRefCount[AudioSystem::MUSIC] || + (systemTime() - mMusicStopTime) < seconds(SONIFICATION_HEADSET_MUSIC_DELAY))) { + mLimitRingtoneVolume = true; + } else { + mLimitRingtoneVolume = false; + } +} + +void AudioPolicyManagerBase::setRingerMode(uint32_t mode, uint32_t mask) +{ + LOGV("setRingerMode() mode %x, mask %x", mode, mask); + + mRingerMode = mode; +} + +void AudioPolicyManagerBase::setForceUse(AudioSystem::force_use usage, AudioSystem::forced_config config) +{ + LOGV("setForceUse() usage %d, config %d, mPhoneState %d", usage, config, mPhoneState); + + bool forceVolumeReeval = false; + switch(usage) { + case AudioSystem::FOR_COMMUNICATION: + if (config != AudioSystem::FORCE_SPEAKER && config != AudioSystem::FORCE_BT_SCO && + config != AudioSystem::FORCE_NONE) { + LOGW("setForceUse() invalid config %d for FOR_COMMUNICATION", config); + return; + } + mForceUse[usage] = config; + break; + case AudioSystem::FOR_MEDIA: + if (config != AudioSystem::FORCE_HEADPHONES && config != AudioSystem::FORCE_BT_A2DP && + config != AudioSystem::FORCE_WIRED_ACCESSORY && config != AudioSystem::FORCE_NONE) { + LOGW("setForceUse() invalid config %d for FOR_MEDIA", config); + return; + } + mForceUse[usage] = config; + break; + case AudioSystem::FOR_RECORD: + if (config != AudioSystem::FORCE_BT_SCO && config != AudioSystem::FORCE_WIRED_ACCESSORY && + config != AudioSystem::FORCE_NONE) { + LOGW("setForceUse() invalid config %d for FOR_RECORD", config); + return; + } + mForceUse[usage] = config; + break; + case AudioSystem::FOR_DOCK: + if (config != AudioSystem::FORCE_NONE && config != AudioSystem::FORCE_BT_CAR_DOCK && + config != AudioSystem::FORCE_BT_DESK_DOCK && config != AudioSystem::FORCE_WIRED_ACCESSORY) { + LOGW("setForceUse() invalid config %d for FOR_DOCK", config); + } + forceVolumeReeval = true; + mForceUse[usage] = config; + break; + default: + LOGW("setForceUse() invalid usage %d", usage); + break; + } + + // check for device and output changes triggered by new phone state + uint32_t newDevice = getNewDevice(mHardwareOutput, false); +#ifdef WITH_A2DP + checkOutputForAllStrategies(newDevice); +#endif + updateDeviceForStrategy(); + setOutputDevice(mHardwareOutput, newDevice); + if (forceVolumeReeval) { + applyStreamVolumes(mHardwareOutput, newDevice); + } +} + +AudioSystem::forced_config AudioPolicyManagerBase::getForceUse(AudioSystem::force_use usage) +{ + return mForceUse[usage]; +} + +void AudioPolicyManagerBase::setSystemProperty(const char* property, const char* value) +{ + LOGV("setSystemProperty() property %s, value %s", property, value); + if (strcmp(property, "ro.camera.sound.forced") == 0) { + if (atoi(value)) { + LOGV("ENFORCED_AUDIBLE cannot be muted"); + mStreams[AudioSystem::ENFORCED_AUDIBLE].mCanBeMuted = false; + } else { + LOGV("ENFORCED_AUDIBLE can be muted"); + mStreams[AudioSystem::ENFORCED_AUDIBLE].mCanBeMuted = true; + } + } +} + +audio_io_handle_t AudioPolicyManagerBase::getOutput(AudioSystem::stream_type stream, + uint32_t samplingRate, + uint32_t format, + uint32_t channels, + AudioSystem::output_flags flags) +{ + audio_io_handle_t output = 0; + uint32_t latency = 0; + routing_strategy strategy = getStrategy((AudioSystem::stream_type)stream); + uint32_t device = getDeviceForStrategy(strategy); + LOGV("getOutput() stream %d, samplingRate %d, format %d, channels %x, flags %x", stream, samplingRate, format, channels, flags); + +#ifdef AUDIO_POLICY_TEST + if (mCurOutput != 0) { + LOGV("getOutput() test output mCurOutput %d, samplingRate %d, format %d, channels %x, mDirectOutput %d", + mCurOutput, mTestSamplingRate, mTestFormat, mTestChannels, mDirectOutput); + + if (mTestOutputs[mCurOutput] == 0) { + LOGV("getOutput() opening test output"); + AudioOutputDescriptor *outputDesc = new AudioOutputDescriptor(); + outputDesc->mDevice = mTestDevice; + outputDesc->mSamplingRate = mTestSamplingRate; + outputDesc->mFormat = mTestFormat; + outputDesc->mChannels = mTestChannels; + outputDesc->mLatency = mTestLatencyMs; + outputDesc->mFlags = (AudioSystem::output_flags)(mDirectOutput ? AudioSystem::OUTPUT_FLAG_DIRECT : 0); + outputDesc->mRefCount[stream] = 0; + mTestOutputs[mCurOutput] = mpClientInterface->openOutput(&outputDesc->mDevice, + &outputDesc->mSamplingRate, + &outputDesc->mFormat, + &outputDesc->mChannels, + &outputDesc->mLatency, + outputDesc->mFlags); + if (mTestOutputs[mCurOutput]) { + AudioParameter outputCmd = AudioParameter(); + outputCmd.addInt(String8("set_id"),mCurOutput); + mpClientInterface->setParameters(mTestOutputs[mCurOutput],outputCmd.toString()); + addOutput(mTestOutputs[mCurOutput], outputDesc); + } + } + return mTestOutputs[mCurOutput]; + } +#endif //AUDIO_POLICY_TEST + + // open a direct output if required by specified parameters + if (needsDirectOuput(stream, samplingRate, format, channels, flags, device)) { + + LOGV("getOutput() opening direct output device %x", device); + AudioOutputDescriptor *outputDesc = new AudioOutputDescriptor(); + outputDesc->mDevice = device; + outputDesc->mSamplingRate = samplingRate; + outputDesc->mFormat = format; + outputDesc->mChannels = channels; + outputDesc->mLatency = 0; + outputDesc->mFlags = (AudioSystem::output_flags)(flags | AudioSystem::OUTPUT_FLAG_DIRECT); + outputDesc->mRefCount[stream] = 0; + output = mpClientInterface->openOutput(&outputDesc->mDevice, + &outputDesc->mSamplingRate, + &outputDesc->mFormat, + &outputDesc->mChannels, + &outputDesc->mLatency, + outputDesc->mFlags); + + // only accept an output with the requeted parameters + if (output == 0 || + (samplingRate != 0 && samplingRate != outputDesc->mSamplingRate) || + (format != 0 && format != outputDesc->mFormat) || + (channels != 0 && channels != outputDesc->mChannels)) { + LOGV("getOutput() failed opening direct output: samplingRate %d, format %d, channels %d", + samplingRate, format, channels); + if (output != 0) { + mpClientInterface->closeOutput(output); + } + delete outputDesc; + return 0; + } + addOutput(output, outputDesc); + return output; + } + + if (channels != 0 && channels != AudioSystem::CHANNEL_OUT_MONO && + channels != AudioSystem::CHANNEL_OUT_STEREO) { + return 0; + } + // open a non direct output + + // get which output is suitable for the specified stream. The actual routing change will happen + // when startOutput() will be called + uint32_t a2dpDevice = device & AudioSystem::DEVICE_OUT_ALL_A2DP; + if (AudioSystem::popCount((AudioSystem::audio_devices)device) == 2) { +#ifdef WITH_A2DP + if (a2dpUsedForSonification() && a2dpDevice != 0) { + // if playing on 2 devices among which one is A2DP, use duplicated output + LOGV("getOutput() using duplicated output"); + LOGW_IF((mA2dpOutput == 0), "getOutput() A2DP device in multiple %x selected but A2DP output not opened", device); + output = mDuplicatedOutput; + } else +#endif + { + // if playing on 2 devices among which none is A2DP, use hardware output + output = mHardwareOutput; + } + LOGV("getOutput() using output %d for 2 devices %x", output, device); + } else { +#ifdef WITH_A2DP + if (a2dpDevice != 0) { + // if playing on A2DP device, use a2dp output + LOGW_IF((mA2dpOutput == 0), "getOutput() A2DP device %x selected but A2DP output not opened", device); + output = mA2dpOutput; + } else +#endif + { + // if playing on not A2DP device, use hardware output + output = mHardwareOutput; + } + } + + + LOGW_IF((output ==0), "getOutput() could not find output for stream %d, samplingRate %d, format %d, channels %x, flags %x", + stream, samplingRate, format, channels, flags); + + return output; +} + +status_t AudioPolicyManagerBase::startOutput(audio_io_handle_t output, AudioSystem::stream_type stream) +{ + LOGV("startOutput() output %d, stream %d", output, stream); + ssize_t index = mOutputs.indexOfKey(output); + if (index < 0) { + LOGW("startOutput() unknow output %d", output); + return BAD_VALUE; + } + + AudioOutputDescriptor *outputDesc = mOutputs.valueAt(index); + routing_strategy strategy = getStrategy((AudioSystem::stream_type)stream); + +#ifdef WITH_A2DP + if (mA2dpOutput != 0 && !a2dpUsedForSonification() && strategy == STRATEGY_SONIFICATION) { + setStrategyMute(STRATEGY_MEDIA, true, mA2dpOutput); + } +#endif + + // incremenent usage count for this stream on the requested output: + // NOTE that the usage count is the same for duplicated output and hardware output which is + // necassary for a correct control of hardware output routing by startOutput() and stopOutput() + outputDesc->changeRefCount(stream, 1); + + setOutputDevice(output, getNewDevice(output)); + + // handle special case for sonification while in call + if (mPhoneState == AudioSystem::MODE_IN_CALL) { + handleIncallSonification(stream, true, false); + } + + // apply volume rules for current stream and device if necessary + checkAndSetVolume(stream, mStreams[stream].mIndexCur, output, outputDesc->device()); + + return NO_ERROR; +} + +status_t AudioPolicyManagerBase::stopOutput(audio_io_handle_t output, AudioSystem::stream_type stream) +{ + LOGV("stopOutput() output %d, stream %d", output, stream); + ssize_t index = mOutputs.indexOfKey(output); + if (index < 0) { + LOGW("stopOutput() unknow output %d", output); + return BAD_VALUE; + } + + AudioOutputDescriptor *outputDesc = mOutputs.valueAt(index); + routing_strategy strategy = getStrategy((AudioSystem::stream_type)stream); + + // handle special case for sonification while in call + if (mPhoneState == AudioSystem::MODE_IN_CALL) { + handleIncallSonification(stream, false, false); + } + + if (outputDesc->mRefCount[stream] > 0) { + // decrement usage count of this stream on the output + outputDesc->changeRefCount(stream, -1); + // store time at which the last music track was stopped - see computeVolume() + if (stream == AudioSystem::MUSIC) { + mMusicStopTime = systemTime(); + } + + setOutputDevice(output, getNewDevice(output)); + +#ifdef WITH_A2DP + if (mA2dpOutput != 0 && !a2dpUsedForSonification() && strategy == STRATEGY_SONIFICATION) { + setStrategyMute(STRATEGY_MEDIA, false, mA2dpOutput, mOutputs.valueFor(mHardwareOutput)->mLatency*2); + } +#endif + if (output != mHardwareOutput) { + setOutputDevice(mHardwareOutput, getNewDevice(mHardwareOutput), true); + } + return NO_ERROR; + } else { + LOGW("stopOutput() refcount is already 0 for output %d", output); + return INVALID_OPERATION; + } +} + +void AudioPolicyManagerBase::releaseOutput(audio_io_handle_t output) +{ + LOGV("releaseOutput() %d", output); + ssize_t index = mOutputs.indexOfKey(output); + if (index < 0) { + LOGW("releaseOutput() releasing unknown output %d", output); + return; + } + +#ifdef AUDIO_POLICY_TEST + int testIndex = testOutputIndex(output); + if (testIndex != 0) { + AudioOutputDescriptor *outputDesc = mOutputs.valueAt(index); + if (outputDesc->refCount() == 0) { + mpClientInterface->closeOutput(output); + delete mOutputs.valueAt(index); + mOutputs.removeItem(output); + mTestOutputs[testIndex] = 0; + } + return; + } +#endif //AUDIO_POLICY_TEST + + if (mOutputs.valueAt(index)->mFlags & AudioSystem::OUTPUT_FLAG_DIRECT) { + mpClientInterface->closeOutput(output); + delete mOutputs.valueAt(index); + mOutputs.removeItem(output); + } +} + +audio_io_handle_t AudioPolicyManagerBase::getInput(int inputSource, + uint32_t samplingRate, + uint32_t format, + uint32_t channels, + AudioSystem::audio_in_acoustics acoustics) +{ + audio_io_handle_t input = 0; + uint32_t device = getDeviceForInputSource(inputSource); + + LOGV("getInput() inputSource %d, samplingRate %d, format %d, channels %x, acoustics %x", inputSource, samplingRate, format, channels, acoustics); + + if (device == 0) { + return 0; + } + + // adapt channel selection to input source + switch(inputSource) { + case AUDIO_SOURCE_VOICE_UPLINK: + channels = AudioSystem::CHANNEL_IN_VOICE_UPLINK; + break; + case AUDIO_SOURCE_VOICE_DOWNLINK: + channels = AudioSystem::CHANNEL_IN_VOICE_DNLINK; + break; + case AUDIO_SOURCE_VOICE_CALL: + channels = (AudioSystem::CHANNEL_IN_VOICE_UPLINK | AudioSystem::CHANNEL_IN_VOICE_DNLINK); + break; + default: + break; + } + + AudioInputDescriptor *inputDesc = new AudioInputDescriptor(); + + inputDesc->mInputSource = inputSource; + inputDesc->mDevice = device; + inputDesc->mSamplingRate = samplingRate; + inputDesc->mFormat = format; + inputDesc->mChannels = channels; + inputDesc->mAcoustics = acoustics; + inputDesc->mRefCount = 0; + input = mpClientInterface->openInput(&inputDesc->mDevice, + &inputDesc->mSamplingRate, + &inputDesc->mFormat, + &inputDesc->mChannels, + inputDesc->mAcoustics); + + // only accept input with the exact requested set of parameters + if (input == 0 || + (samplingRate != inputDesc->mSamplingRate) || + (format != inputDesc->mFormat) || + (channels != inputDesc->mChannels)) { + LOGV("getInput() failed opening input: samplingRate %d, format %d, channels %d", + samplingRate, format, channels); + if (input != 0) { + mpClientInterface->closeInput(input); + } + delete inputDesc; + return 0; + } + mInputs.add(input, inputDesc); + return input; +} + +status_t AudioPolicyManagerBase::startInput(audio_io_handle_t input) +{ + LOGV("startInput() input %d", input); + ssize_t index = mInputs.indexOfKey(input); + if (index < 0) { + LOGW("startInput() unknow input %d", input); + return BAD_VALUE; + } + AudioInputDescriptor *inputDesc = mInputs.valueAt(index); + +#ifdef AUDIO_POLICY_TEST + if (mTestInput == 0) +#endif //AUDIO_POLICY_TEST + { + // refuse 2 active AudioRecord clients at the same time + if (getActiveInput() != 0) { + LOGW("startInput() input %d failed: other input already started", input); + return INVALID_OPERATION; + } + } + + AudioParameter param = AudioParameter(); + param.addInt(String8(AudioParameter::keyRouting), (int)inputDesc->mDevice); + + // use Voice Recognition mode or not for this input based on input source + int vr_enabled = inputDesc->mInputSource == AUDIO_SOURCE_VOICE_RECOGNITION ? 1 : 0; + param.addInt(String8("vr_mode"), vr_enabled); + LOGV("AudioPolicyManager::startInput(%d), setting vr_mode to %d", inputDesc->mInputSource, vr_enabled); + + mpClientInterface->setParameters(input, param.toString()); + + inputDesc->mRefCount = 1; + return NO_ERROR; +} + +status_t AudioPolicyManagerBase::stopInput(audio_io_handle_t input) +{ + LOGV("stopInput() input %d", input); + ssize_t index = mInputs.indexOfKey(input); + if (index < 0) { + LOGW("stopInput() unknow input %d", input); + return BAD_VALUE; + } + AudioInputDescriptor *inputDesc = mInputs.valueAt(index); + + if (inputDesc->mRefCount == 0) { + LOGW("stopInput() input %d already stopped", input); + return INVALID_OPERATION; + } else { + AudioParameter param = AudioParameter(); + param.addInt(String8(AudioParameter::keyRouting), 0); + mpClientInterface->setParameters(input, param.toString()); + inputDesc->mRefCount = 0; + return NO_ERROR; + } +} + +void AudioPolicyManagerBase::releaseInput(audio_io_handle_t input) +{ + LOGV("releaseInput() %d", input); + ssize_t index = mInputs.indexOfKey(input); + if (index < 0) { + LOGW("releaseInput() releasing unknown input %d", input); + return; + } + mpClientInterface->closeInput(input); + delete mInputs.valueAt(index); + mInputs.removeItem(input); + LOGV("releaseInput() exit"); +} + +void AudioPolicyManagerBase::initStreamVolume(AudioSystem::stream_type stream, + int indexMin, + int indexMax) +{ + LOGV("initStreamVolume() stream %d, min %d, max %d", stream , indexMin, indexMax); + if (indexMin < 0 || indexMin >= indexMax) { + LOGW("initStreamVolume() invalid index limits for stream %d, min %d, max %d", stream , indexMin, indexMax); + return; + } + mStreams[stream].mIndexMin = indexMin; + mStreams[stream].mIndexMax = indexMax; +} + +status_t AudioPolicyManagerBase::setStreamVolumeIndex(AudioSystem::stream_type stream, int index) +{ + + if ((index < mStreams[stream].mIndexMin) || (index > mStreams[stream].mIndexMax)) { + return BAD_VALUE; + } + + // Force max volume if stream cannot be muted + if (!mStreams[stream].mCanBeMuted) index = mStreams[stream].mIndexMax; + + LOGV("setStreamVolumeIndex() stream %d, index %d", stream, index); + mStreams[stream].mIndexCur = index; + + // compute and apply stream volume on all outputs according to connected device + status_t status = NO_ERROR; + for (size_t i = 0; i < mOutputs.size(); i++) { + status_t volStatus = checkAndSetVolume(stream, index, mOutputs.keyAt(i), mOutputs.valueAt(i)->device()); + if (volStatus != NO_ERROR) { + status = volStatus; + } + } + return status; +} + +status_t AudioPolicyManagerBase::getStreamVolumeIndex(AudioSystem::stream_type stream, int *index) +{ + if (index == 0) { + return BAD_VALUE; + } + LOGV("getStreamVolumeIndex() stream %d", stream); + *index = mStreams[stream].mIndexCur; + return NO_ERROR; +} + +status_t AudioPolicyManagerBase::dump(int fd) +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + + snprintf(buffer, SIZE, "\nAudioPolicyManager Dump: %p\n", this); + result.append(buffer); + snprintf(buffer, SIZE, " Hardware Output: %d\n", mHardwareOutput); + result.append(buffer); +#ifdef WITH_A2DP + snprintf(buffer, SIZE, " A2DP Output: %d\n", mA2dpOutput); + result.append(buffer); + snprintf(buffer, SIZE, " Duplicated Output: %d\n", mDuplicatedOutput); + result.append(buffer); + snprintf(buffer, SIZE, " A2DP device address: %s\n", mA2dpDeviceAddress.string()); + result.append(buffer); +#endif + snprintf(buffer, SIZE, " SCO device address: %s\n", mScoDeviceAddress.string()); + result.append(buffer); + snprintf(buffer, SIZE, " Output devices: %08x\n", mAvailableOutputDevices); + result.append(buffer); + snprintf(buffer, SIZE, " Input devices: %08x\n", mAvailableInputDevices); + result.append(buffer); + snprintf(buffer, SIZE, " Phone state: %d\n", mPhoneState); + result.append(buffer); + snprintf(buffer, SIZE, " Ringer mode: %d\n", mRingerMode); + result.append(buffer); + snprintf(buffer, SIZE, " Force use for communications %d\n", mForceUse[AudioSystem::FOR_COMMUNICATION]); + result.append(buffer); + snprintf(buffer, SIZE, " Force use for media %d\n", mForceUse[AudioSystem::FOR_MEDIA]); + result.append(buffer); + snprintf(buffer, SIZE, " Force use for record %d\n", mForceUse[AudioSystem::FOR_RECORD]); + result.append(buffer); + snprintf(buffer, SIZE, " Force use for dock %d\n", mForceUse[AudioSystem::FOR_DOCK]); + result.append(buffer); + write(fd, result.string(), result.size()); + + snprintf(buffer, SIZE, "\nOutputs dump:\n"); + write(fd, buffer, strlen(buffer)); + for (size_t i = 0; i < mOutputs.size(); i++) { + snprintf(buffer, SIZE, "- Output %d dump:\n", mOutputs.keyAt(i)); + write(fd, buffer, strlen(buffer)); + mOutputs.valueAt(i)->dump(fd); + } + + snprintf(buffer, SIZE, "\nInputs dump:\n"); + write(fd, buffer, strlen(buffer)); + for (size_t i = 0; i < mInputs.size(); i++) { + snprintf(buffer, SIZE, "- Input %d dump:\n", mInputs.keyAt(i)); + write(fd, buffer, strlen(buffer)); + mInputs.valueAt(i)->dump(fd); + } + + snprintf(buffer, SIZE, "\nStreams dump:\n"); + write(fd, buffer, strlen(buffer)); + snprintf(buffer, SIZE, " Stream Index Min Index Max Index Cur Can be muted\n"); + write(fd, buffer, strlen(buffer)); + for (size_t i = 0; i < AudioSystem::NUM_STREAM_TYPES; i++) { + snprintf(buffer, SIZE, " %02d", i); + mStreams[i].dump(buffer + 3, SIZE); + write(fd, buffer, strlen(buffer)); + } + + return NO_ERROR; +} + +// ---------------------------------------------------------------------------- +// AudioPolicyManagerBase +// ---------------------------------------------------------------------------- + +AudioPolicyManagerBase::AudioPolicyManagerBase(AudioPolicyClientInterface *clientInterface) + : +#ifdef AUDIO_POLICY_TEST + Thread(false), +#endif //AUDIO_POLICY_TEST + mPhoneState(AudioSystem::MODE_NORMAL), mRingerMode(0), mMusicStopTime(0), mLimitRingtoneVolume(false) +{ + mpClientInterface = clientInterface; + + for (int i = 0; i < AudioSystem::NUM_FORCE_USE; i++) { + mForceUse[i] = AudioSystem::FORCE_NONE; + } + + // devices available by default are speaker, ear piece and microphone + mAvailableOutputDevices = AudioSystem::DEVICE_OUT_EARPIECE | + AudioSystem::DEVICE_OUT_SPEAKER; + mAvailableInputDevices = AudioSystem::DEVICE_IN_BUILTIN_MIC; + +#ifdef WITH_A2DP + mA2dpOutput = 0; + mDuplicatedOutput = 0; + mA2dpDeviceAddress = String8(""); +#endif + mScoDeviceAddress = String8(""); + + // open hardware output + AudioOutputDescriptor *outputDesc = new AudioOutputDescriptor(); + outputDesc->mDevice = (uint32_t)AudioSystem::DEVICE_OUT_SPEAKER; + mHardwareOutput = mpClientInterface->openOutput(&outputDesc->mDevice, + &outputDesc->mSamplingRate, + &outputDesc->mFormat, + &outputDesc->mChannels, + &outputDesc->mLatency, + outputDesc->mFlags); + + if (mHardwareOutput == 0) { + LOGE("Failed to initialize hardware output stream, samplingRate: %d, format %d, channels %d", + outputDesc->mSamplingRate, outputDesc->mFormat, outputDesc->mChannels); + } else { + addOutput(mHardwareOutput, outputDesc); + setOutputDevice(mHardwareOutput, (uint32_t)AudioSystem::DEVICE_OUT_SPEAKER, true); + } + + updateDeviceForStrategy(); +#ifdef AUDIO_POLICY_TEST + AudioParameter outputCmd = AudioParameter(); + outputCmd.addInt(String8("set_id"), 0); + mpClientInterface->setParameters(mHardwareOutput, outputCmd.toString()); + + mTestDevice = AudioSystem::DEVICE_OUT_SPEAKER; + mTestSamplingRate = 44100; + mTestFormat = AudioSystem::PCM_16_BIT; + mTestChannels = AudioSystem::CHANNEL_OUT_STEREO; + mTestLatencyMs = 0; + mCurOutput = 0; + mDirectOutput = false; + for (int i = 0; i < NUM_TEST_OUTPUTS; i++) { + mTestOutputs[i] = 0; + } + + const size_t SIZE = 256; + char buffer[SIZE]; + snprintf(buffer, SIZE, "AudioPolicyManagerTest"); + run(buffer, ANDROID_PRIORITY_AUDIO); +#endif //AUDIO_POLICY_TEST +} + +AudioPolicyManagerBase::~AudioPolicyManagerBase() +{ +#ifdef AUDIO_POLICY_TEST + exit(); +#endif //AUDIO_POLICY_TEST + for (size_t i = 0; i < mOutputs.size(); i++) { + mpClientInterface->closeOutput(mOutputs.keyAt(i)); + delete mOutputs.valueAt(i); + } + mOutputs.clear(); + for (size_t i = 0; i < mInputs.size(); i++) { + mpClientInterface->closeInput(mInputs.keyAt(i)); + delete mInputs.valueAt(i); + } + mInputs.clear(); +} + +#ifdef AUDIO_POLICY_TEST +bool AudioPolicyManagerBase::threadLoop() +{ + LOGV("entering threadLoop()"); + while (!exitPending()) + { + String8 command; + int valueInt; + String8 value; + + Mutex::Autolock _l(mLock); + mWaitWorkCV.waitRelative(mLock, milliseconds(50)); + + command = mpClientInterface->getParameters(0, String8("test_cmd_policy")); + AudioParameter param = AudioParameter(command); + + if (param.getInt(String8("test_cmd_policy"), valueInt) == NO_ERROR && + valueInt != 0) { + LOGV("Test command %s received", command.string()); + String8 target; + if (param.get(String8("target"), target) != NO_ERROR) { + target = "Manager"; + } + if (param.getInt(String8("test_cmd_policy_output"), valueInt) == NO_ERROR) { + param.remove(String8("test_cmd_policy_output")); + mCurOutput = valueInt; + } + if (param.get(String8("test_cmd_policy_direct"), value) == NO_ERROR) { + param.remove(String8("test_cmd_policy_direct")); + if (value == "false") { + mDirectOutput = false; + } else if (value == "true") { + mDirectOutput = true; + } + } + if (param.getInt(String8("test_cmd_policy_input"), valueInt) == NO_ERROR) { + param.remove(String8("test_cmd_policy_input")); + mTestInput = valueInt; + } + + if (param.get(String8("test_cmd_policy_format"), value) == NO_ERROR) { + param.remove(String8("test_cmd_policy_format")); + int format = AudioSystem::INVALID_FORMAT; + if (value == "PCM 16 bits") { + format = AudioSystem::PCM_16_BIT; + } else if (value == "PCM 8 bits") { + format = AudioSystem::PCM_8_BIT; + } else if (value == "Compressed MP3") { + format = AudioSystem::MP3; + } + if (format != AudioSystem::INVALID_FORMAT) { + if (target == "Manager") { + mTestFormat = format; + } else if (mTestOutputs[mCurOutput] != 0) { + AudioParameter outputParam = AudioParameter(); + outputParam.addInt(String8("format"), format); + mpClientInterface->setParameters(mTestOutputs[mCurOutput], outputParam.toString()); + } + } + } + if (param.get(String8("test_cmd_policy_channels"), value) == NO_ERROR) { + param.remove(String8("test_cmd_policy_channels")); + int channels = 0; + + if (value == "Channels Stereo") { + channels = AudioSystem::CHANNEL_OUT_STEREO; + } else if (value == "Channels Mono") { + channels = AudioSystem::CHANNEL_OUT_MONO; + } + if (channels != 0) { + if (target == "Manager") { + mTestChannels = channels; + } else if (mTestOutputs[mCurOutput] != 0) { + AudioParameter outputParam = AudioParameter(); + outputParam.addInt(String8("channels"), channels); + mpClientInterface->setParameters(mTestOutputs[mCurOutput], outputParam.toString()); + } + } + } + if (param.getInt(String8("test_cmd_policy_sampleRate"), valueInt) == NO_ERROR) { + param.remove(String8("test_cmd_policy_sampleRate")); + if (valueInt >= 0 && valueInt <= 96000) { + int samplingRate = valueInt; + if (target == "Manager") { + mTestSamplingRate = samplingRate; + } else if (mTestOutputs[mCurOutput] != 0) { + AudioParameter outputParam = AudioParameter(); + outputParam.addInt(String8("sampling_rate"), samplingRate); + mpClientInterface->setParameters(mTestOutputs[mCurOutput], outputParam.toString()); + } + } + } + + if (param.get(String8("test_cmd_policy_reopen"), value) == NO_ERROR) { + param.remove(String8("test_cmd_policy_reopen")); + + mpClientInterface->closeOutput(mHardwareOutput); + delete mOutputs.valueFor(mHardwareOutput); + mOutputs.removeItem(mHardwareOutput); + + AudioOutputDescriptor *outputDesc = new AudioOutputDescriptor(); + outputDesc->mDevice = (uint32_t)AudioSystem::DEVICE_OUT_SPEAKER; + mHardwareOutput = mpClientInterface->openOutput(&outputDesc->mDevice, + &outputDesc->mSamplingRate, + &outputDesc->mFormat, + &outputDesc->mChannels, + &outputDesc->mLatency, + outputDesc->mFlags); + if (mHardwareOutput == 0) { + LOGE("Failed to reopen hardware output stream, samplingRate: %d, format %d, channels %d", + outputDesc->mSamplingRate, outputDesc->mFormat, outputDesc->mChannels); + } else { + AudioParameter outputCmd = AudioParameter(); + outputCmd.addInt(String8("set_id"), 0); + mpClientInterface->setParameters(mHardwareOutput, outputCmd.toString()); + addOutput(mHardwareOutput, outputDesc); + } + } + + + mpClientInterface->setParameters(0, String8("test_cmd_policy=")); + } + } + return false; +} + +void AudioPolicyManagerBase::exit() +{ + { + AutoMutex _l(mLock); + requestExit(); + mWaitWorkCV.signal(); + } + requestExitAndWait(); +} + +int AudioPolicyManagerBase::testOutputIndex(audio_io_handle_t output) +{ + for (int i = 0; i < NUM_TEST_OUTPUTS; i++) { + if (output == mTestOutputs[i]) return i; + } + return 0; +} +#endif //AUDIO_POLICY_TEST + +// --- + +void AudioPolicyManagerBase::addOutput(audio_io_handle_t id, AudioOutputDescriptor *outputDesc) +{ + outputDesc->mId = id; + mOutputs.add(id, outputDesc); +} + + +#ifdef WITH_A2DP +status_t AudioPolicyManagerBase::handleA2dpConnection(AudioSystem::audio_devices device, + const char *device_address) +{ + // when an A2DP device is connected, open an A2DP and a duplicated output + LOGV("opening A2DP output for device %s", device_address); + AudioOutputDescriptor *outputDesc = new AudioOutputDescriptor(); + outputDesc->mDevice = device; + mA2dpOutput = mpClientInterface->openOutput(&outputDesc->mDevice, + &outputDesc->mSamplingRate, + &outputDesc->mFormat, + &outputDesc->mChannels, + &outputDesc->mLatency, + outputDesc->mFlags); + if (mA2dpOutput) { + // add A2DP output descriptor + addOutput(mA2dpOutput, outputDesc); + // set initial stream volume for A2DP device + applyStreamVolumes(mA2dpOutput, device); + if (a2dpUsedForSonification()) { + mDuplicatedOutput = mpClientInterface->openDuplicateOutput(mA2dpOutput, mHardwareOutput); + } + if (mDuplicatedOutput != 0 || + !a2dpUsedForSonification()) { + // If both A2DP and duplicated outputs are open, send device address to A2DP hardware + // interface + AudioParameter param; + param.add(String8("a2dp_sink_address"), String8(device_address)); + mpClientInterface->setParameters(mA2dpOutput, param.toString()); + mA2dpDeviceAddress = String8(device_address, MAX_DEVICE_ADDRESS_LEN); + + if (a2dpUsedForSonification()) { + // add duplicated output descriptor + AudioOutputDescriptor *dupOutputDesc = new AudioOutputDescriptor(); + dupOutputDesc->mOutput1 = mOutputs.valueFor(mHardwareOutput); + dupOutputDesc->mOutput2 = mOutputs.valueFor(mA2dpOutput); + dupOutputDesc->mSamplingRate = outputDesc->mSamplingRate; + dupOutputDesc->mFormat = outputDesc->mFormat; + dupOutputDesc->mChannels = outputDesc->mChannels; + dupOutputDesc->mLatency = outputDesc->mLatency; + addOutput(mDuplicatedOutput, dupOutputDesc); + applyStreamVolumes(mDuplicatedOutput, device); + } + } else { + LOGW("getOutput() could not open duplicated output for %d and %d", + mHardwareOutput, mA2dpOutput); + mpClientInterface->closeOutput(mA2dpOutput); + mOutputs.removeItem(mA2dpOutput); + mA2dpOutput = 0; + delete outputDesc; + return NO_INIT; + } + } else { + LOGW("setDeviceConnectionState() could not open A2DP output for device %x", device); + delete outputDesc; + return NO_INIT; + } + AudioOutputDescriptor *hwOutputDesc = mOutputs.valueFor(mHardwareOutput); + + if (mScoDeviceAddress != "") { + // It is normal to suspend twice if we are both in call, + // and have the hardware audio output routed to BT SCO + if (mPhoneState != AudioSystem::MODE_NORMAL) { + mpClientInterface->suspendOutput(mA2dpOutput); + } + if (AudioSystem::isBluetoothScoDevice((AudioSystem::audio_devices)hwOutputDesc->device())) { + mpClientInterface->suspendOutput(mA2dpOutput); + } + } + + if (!a2dpUsedForSonification()) { + // mute music on A2DP output if a notification or ringtone is playing + uint32_t refCount = hwOutputDesc->strategyRefCount(STRATEGY_SONIFICATION); + for (uint32_t i = 0; i < refCount; i++) { + setStrategyMute(STRATEGY_MEDIA, true, mA2dpOutput); + } + } + return NO_ERROR; +} + +status_t AudioPolicyManagerBase::handleA2dpDisconnection(AudioSystem::audio_devices device, + const char *device_address) +{ + if (mA2dpOutput == 0) { + LOGW("setDeviceConnectionState() disconnecting A2DP and no A2DP output!"); + return INVALID_OPERATION; + } + + if (mA2dpDeviceAddress != device_address) { + LOGW("setDeviceConnectionState() disconnecting unknow A2DP sink address %s", device_address); + return INVALID_OPERATION; + } + + // mute media strategy to avoid outputting sound on hardware output while music stream + // is switched from A2DP output and before music is paused by music application + setStrategyMute(STRATEGY_MEDIA, true, mHardwareOutput); + setStrategyMute(STRATEGY_MEDIA, false, mHardwareOutput, MUTE_TIME_MS); + + if (!a2dpUsedForSonification()) { + // unmute music on A2DP output if a notification or ringtone is playing + uint32_t refCount = mOutputs.valueFor(mHardwareOutput)->strategyRefCount(STRATEGY_SONIFICATION); + for (uint32_t i = 0; i < refCount; i++) { + setStrategyMute(STRATEGY_MEDIA, false, mA2dpOutput); + } + } + mA2dpDeviceAddress = ""; + return NO_ERROR; +} + +void AudioPolicyManagerBase::closeA2dpOutputs() +{ + LOGV("setDeviceConnectionState() closing A2DP and duplicated output!"); + + if (mDuplicatedOutput != 0) { + mpClientInterface->closeOutput(mDuplicatedOutput); + delete mOutputs.valueFor(mDuplicatedOutput); + mOutputs.removeItem(mDuplicatedOutput); + mDuplicatedOutput = 0; + } + if (mA2dpOutput != 0) { + AudioParameter param; + param.add(String8("closing"), String8("true")); + mpClientInterface->setParameters(mA2dpOutput, param.toString()); + mpClientInterface->closeOutput(mA2dpOutput); + delete mOutputs.valueFor(mA2dpOutput); + mOutputs.removeItem(mA2dpOutput); + mA2dpOutput = 0; + } +} + +void AudioPolicyManagerBase::checkOutputForStrategy(routing_strategy strategy, uint32_t &newDevice) +{ + uint32_t prevDevice = getDeviceForStrategy(strategy); + uint32_t curDevice = getDeviceForStrategy(strategy, false); + bool a2dpWasUsed = AudioSystem::isA2dpDevice((AudioSystem::audio_devices)(prevDevice & ~AudioSystem::DEVICE_OUT_SPEAKER)); + bool a2dpIsUsed = AudioSystem::isA2dpDevice((AudioSystem::audio_devices)(curDevice & ~AudioSystem::DEVICE_OUT_SPEAKER)); + AudioOutputDescriptor *hwOutputDesc = mOutputs.valueFor(mHardwareOutput); + AudioOutputDescriptor *a2dpOutputDesc; + + if (a2dpWasUsed && !a2dpIsUsed) { + bool dupUsed = a2dpUsedForSonification() && a2dpWasUsed && (AudioSystem::popCount(prevDevice) == 2); + + if (dupUsed) { + LOGV("checkOutputForStrategy() moving strategy %d to duplicated", strategy); + a2dpOutputDesc = mOutputs.valueFor(mDuplicatedOutput); + } else { + LOGV("checkOutputForStrategy() moving strategy %d to a2dp", strategy); + a2dpOutputDesc = mOutputs.valueFor(mA2dpOutput); + } + + for (int i = 0; i < (int)AudioSystem::NUM_STREAM_TYPES; i++) { + if (getStrategy((AudioSystem::stream_type)i) == strategy) { + mpClientInterface->setStreamOutput((AudioSystem::stream_type)i, mHardwareOutput); + int refCount = a2dpOutputDesc->mRefCount[i]; + // in the case of duplicated output, the ref count is first incremented + // and then decremented on hardware output tus keeping its value + hwOutputDesc->changeRefCount((AudioSystem::stream_type)i, refCount); + a2dpOutputDesc->changeRefCount((AudioSystem::stream_type)i,-refCount); + } + } + // do not change newDevice if it was already set before this call by a previous call to + // getNewDevice() or checkOutputForStrategy() for a strategy with higher priority + if (newDevice == 0 && hwOutputDesc->isUsedByStrategy(strategy)) { + newDevice = getDeviceForStrategy(strategy, false); + } + } + if (a2dpIsUsed && !a2dpWasUsed) { + bool dupUsed = a2dpUsedForSonification() && a2dpIsUsed && (AudioSystem::popCount(curDevice) == 2); + audio_io_handle_t a2dpOutput; + + if (dupUsed) { + LOGV("checkOutputForStrategy() moving strategy %d from duplicated", strategy); + a2dpOutputDesc = mOutputs.valueFor(mDuplicatedOutput); + a2dpOutput = mDuplicatedOutput; + } else { + LOGV("checkOutputForStrategy() moving strategy %d from a2dp", strategy); + a2dpOutputDesc = mOutputs.valueFor(mA2dpOutput); + a2dpOutput = mA2dpOutput; + } + + for (int i = 0; i < (int)AudioSystem::NUM_STREAM_TYPES; i++) { + if (getStrategy((AudioSystem::stream_type)i) == strategy) { + mpClientInterface->setStreamOutput((AudioSystem::stream_type)i, a2dpOutput); + int refCount = hwOutputDesc->mRefCount[i]; + // in the case of duplicated output, the ref count is first incremented + // and then decremented on hardware output tus keeping its value + a2dpOutputDesc->changeRefCount((AudioSystem::stream_type)i, refCount); + hwOutputDesc->changeRefCount((AudioSystem::stream_type)i,-refCount); + } + } + } +} + +void AudioPolicyManagerBase::checkOutputForAllStrategies(uint32_t &newDevice) +{ + // Check strategies in order of priority so that once newDevice is set + // for a given strategy it is not modified by subsequent calls to + // checkOutputForStrategy() + checkOutputForStrategy(STRATEGY_PHONE, newDevice); + checkOutputForStrategy(STRATEGY_SONIFICATION, newDevice); + checkOutputForStrategy(STRATEGY_MEDIA, newDevice); + checkOutputForStrategy(STRATEGY_DTMF, newDevice); +} + +#endif + +uint32_t AudioPolicyManagerBase::getNewDevice(audio_io_handle_t output, bool fromCache) +{ + uint32_t device = 0; + + AudioOutputDescriptor *outputDesc = mOutputs.valueFor(output); + // check the following by order of priority to request a routing change if necessary: + // 1: we are in call or the strategy phone is active on the hardware output: + // use device for strategy phone + // 2: the strategy sonification is active on the hardware output: + // use device for strategy sonification + // 3: the strategy media is active on the hardware output: + // use device for strategy media + // 4: the strategy DTMF is active on the hardware output: + // use device for strategy DTMF + if (mPhoneState == AudioSystem::MODE_IN_CALL || + outputDesc->isUsedByStrategy(STRATEGY_PHONE)) { + device = getDeviceForStrategy(STRATEGY_PHONE, fromCache); + } else if (outputDesc->isUsedByStrategy(STRATEGY_SONIFICATION)) { + device = getDeviceForStrategy(STRATEGY_SONIFICATION, fromCache); + } else if (outputDesc->isUsedByStrategy(STRATEGY_MEDIA)) { + device = getDeviceForStrategy(STRATEGY_MEDIA, fromCache); + } else if (outputDesc->isUsedByStrategy(STRATEGY_DTMF)) { + device = getDeviceForStrategy(STRATEGY_DTMF, fromCache); + } + + LOGV("getNewDevice() selected device %x", device); + return device; +} + +AudioPolicyManagerBase::routing_strategy AudioPolicyManagerBase::getStrategy(AudioSystem::stream_type stream) +{ + // stream to strategy mapping + switch (stream) { + case AudioSystem::VOICE_CALL: + case AudioSystem::BLUETOOTH_SCO: + return STRATEGY_PHONE; + case AudioSystem::RING: + case AudioSystem::NOTIFICATION: + case AudioSystem::ALARM: + case AudioSystem::ENFORCED_AUDIBLE: + return STRATEGY_SONIFICATION; + case AudioSystem::DTMF: + return STRATEGY_DTMF; + default: + LOGE("unknown stream type"); + case AudioSystem::SYSTEM: + // NOTE: SYSTEM stream uses MEDIA strategy because muting music and switching outputs + // while key clicks are played produces a poor result + case AudioSystem::TTS: + case AudioSystem::MUSIC: + return STRATEGY_MEDIA; + } +} + +uint32_t AudioPolicyManagerBase::getDeviceForStrategy(routing_strategy strategy, bool fromCache) +{ + uint32_t device = 0; + + if (fromCache) { + LOGV("getDeviceForStrategy() from cache strategy %d, device %x", strategy, mDeviceForStrategy[strategy]); + return mDeviceForStrategy[strategy]; + } + + switch (strategy) { + case STRATEGY_DTMF: + if (mPhoneState != AudioSystem::MODE_IN_CALL) { + // when off call, DTMF strategy follows the same rules as MEDIA strategy + device = getDeviceForStrategy(STRATEGY_MEDIA, false); + break; + } + // when in call, DTMF and PHONE strategies follow the same rules + // FALL THROUGH + + case STRATEGY_PHONE: + // for phone strategy, we first consider the forced use and then the available devices by order + // of priority + switch (mForceUse[AudioSystem::FOR_COMMUNICATION]) { + case AudioSystem::FORCE_BT_SCO: + if (mPhoneState != AudioSystem::MODE_IN_CALL || strategy != STRATEGY_DTMF) { + device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_SCO_CARKIT; + if (device) break; + } + device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_SCO_HEADSET; + if (device) break; + device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_SCO; + if (device) break; + // if SCO device is requested but no SCO device is available, fall back to default case + // FALL THROUGH + + default: // FORCE_NONE + device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_WIRED_HEADPHONE; + if (device) break; + device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_WIRED_HEADSET; + if (device) break; +#ifdef WITH_A2DP + // when not in a phone call, phone strategy should route STREAM_VOICE_CALL to A2DP + if (mPhoneState != AudioSystem::MODE_IN_CALL) { + device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP; + if (device) break; + device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES; + if (device) break; + } +#endif + device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_EARPIECE; + if (device == 0) { + LOGE("getDeviceForStrategy() earpiece device not found"); + } + break; + + case AudioSystem::FORCE_SPEAKER: + if (mPhoneState != AudioSystem::MODE_IN_CALL || strategy != STRATEGY_DTMF) { + device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_SCO_CARKIT; + if (device) break; + } +#ifdef WITH_A2DP + // when not in a phone call, phone strategy should route STREAM_VOICE_CALL to + // A2DP speaker when forcing to speaker output + if (mPhoneState != AudioSystem::MODE_IN_CALL) { + device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER; + if (device) break; + } +#endif + device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_SPEAKER; + if (device == 0) { + LOGE("getDeviceForStrategy() speaker device not found"); + } + break; + } + break; + + case STRATEGY_SONIFICATION: + + // If incall, just select the STRATEGY_PHONE device: The rest of the behavior is handled by + // handleIncallSonification(). + if (mPhoneState == AudioSystem::MODE_IN_CALL) { + device = getDeviceForStrategy(STRATEGY_PHONE, false); + break; + } + device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_SPEAKER; + if (device == 0) { + LOGE("getDeviceForStrategy() speaker device not found"); + } + // The second device used for sonification is the same as the device used by media strategy + // FALL THROUGH + + case STRATEGY_MEDIA: { + uint32_t device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_AUX_DIGITAL; + if (device2 == 0) { + device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_WIRED_HEADPHONE; + } + if (device2 == 0) { + device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_WIRED_HEADSET; + } +#ifdef WITH_A2DP + if (mA2dpOutput != 0) { + if (strategy == STRATEGY_SONIFICATION && !a2dpUsedForSonification()) { + break; + } + if (device2 == 0) { + device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP; + } + if (device2 == 0) { + device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES; + } + if (device2 == 0) { + device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER; + } + } +#endif + if (device2 == 0) { + device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_SPEAKER; + } + + // device is DEVICE_OUT_SPEAKER if we come from case STRATEGY_SONIFICATION, 0 otherwise + device |= device2; + if (device == 0) { + LOGE("getDeviceForStrategy() speaker device not found"); + } + } break; + + default: + LOGW("getDeviceForStrategy() unknown strategy: %d", strategy); + break; + } + + LOGV("getDeviceForStrategy() strategy %d, device %x", strategy, device); + return device; +} + +void AudioPolicyManagerBase::updateDeviceForStrategy() +{ + for (int i = 0; i < NUM_STRATEGIES; i++) { + mDeviceForStrategy[i] = getDeviceForStrategy((routing_strategy)i, false); + } +} + +void AudioPolicyManagerBase::setOutputDevice(audio_io_handle_t output, uint32_t device, bool force, int delayMs) +{ + LOGV("setOutputDevice() output %d device %x delayMs %d", output, device, delayMs); + AudioOutputDescriptor *outputDesc = mOutputs.valueFor(output); + + + if (outputDesc->isDuplicated()) { + setOutputDevice(outputDesc->mOutput1->mId, device, force, delayMs); + setOutputDevice(outputDesc->mOutput2->mId, device, force, delayMs); + return; + } +#ifdef WITH_A2DP + // filter devices according to output selected + if (output == mA2dpOutput) { + device &= AudioSystem::DEVICE_OUT_ALL_A2DP; + } else { + device &= ~AudioSystem::DEVICE_OUT_ALL_A2DP; + } +#endif + + uint32_t prevDevice = (uint32_t)outputDesc->device(); + // Do not change the routing if: + // - the requestede device is 0 + // - the requested device is the same as current device and force is not specified. + // Doing this check here allows the caller to call setOutputDevice() without conditions + if ((device == 0 || device == prevDevice) && !force) { + LOGV("setOutputDevice() setting same device %x or null device for output %d", device, output); + return; + } + + outputDesc->mDevice = device; + // mute media streams if both speaker and headset are selected + if (output == mHardwareOutput && AudioSystem::popCount(device) == 2) { + setStrategyMute(STRATEGY_MEDIA, true, output); + // wait for the PCM output buffers to empty before proceeding with the rest of the command + usleep(outputDesc->mLatency*2*1000); + } +#ifdef WITH_A2DP + // suspend A2DP output if SCO device is selected + if (AudioSystem::isBluetoothScoDevice((AudioSystem::audio_devices)device)) { + if (mA2dpOutput != 0) { + mpClientInterface->suspendOutput(mA2dpOutput); + } + } +#endif + // do the routing + AudioParameter param = AudioParameter(); + param.addInt(String8(AudioParameter::keyRouting), (int)device); + mpClientInterface->setParameters(mHardwareOutput, param.toString(), delayMs); + // update stream volumes according to new device + applyStreamVolumes(output, device, delayMs); + +#ifdef WITH_A2DP + // if disconnecting SCO device, restore A2DP output + if (AudioSystem::isBluetoothScoDevice((AudioSystem::audio_devices)prevDevice)) { + if (mA2dpOutput != 0) { + LOGV("restore A2DP output"); + mpClientInterface->restoreOutput(mA2dpOutput); + } + } +#endif + // if changing from a combined headset + speaker route, unmute media streams + if (output == mHardwareOutput && AudioSystem::popCount(prevDevice) == 2) { + setStrategyMute(STRATEGY_MEDIA, false, output, delayMs); + } +} + +uint32_t AudioPolicyManagerBase::getDeviceForInputSource(int inputSource) +{ + uint32_t device; + + switch(inputSource) { + case AUDIO_SOURCE_DEFAULT: + case AUDIO_SOURCE_MIC: + case AUDIO_SOURCE_VOICE_RECOGNITION: + if (mForceUse[AudioSystem::FOR_RECORD] == AudioSystem::FORCE_BT_SCO && + mAvailableInputDevices & AudioSystem::DEVICE_IN_BLUETOOTH_SCO_HEADSET) { + device = AudioSystem::DEVICE_IN_BLUETOOTH_SCO_HEADSET; + } else if (mAvailableInputDevices & AudioSystem::DEVICE_IN_WIRED_HEADSET) { + device = AudioSystem::DEVICE_IN_WIRED_HEADSET; + } else { + device = AudioSystem::DEVICE_IN_BUILTIN_MIC; + } + break; + case AUDIO_SOURCE_CAMCORDER: + if (hasBackMicrophone()) { + device = AudioSystem::DEVICE_IN_BACK_MIC; + } else { + device = AudioSystem::DEVICE_IN_BUILTIN_MIC; + } + break; + case AUDIO_SOURCE_VOICE_UPLINK: + case AUDIO_SOURCE_VOICE_DOWNLINK: + case AUDIO_SOURCE_VOICE_CALL: + device = AudioSystem::DEVICE_IN_VOICE_CALL; + break; + default: + LOGW("getInput() invalid input source %d", inputSource); + device = 0; + break; + } + LOGV("getDeviceForInputSource()input source %d, device %08x", inputSource, device); + return device; +} + +audio_io_handle_t AudioPolicyManagerBase::getActiveInput() +{ + for (size_t i = 0; i < mInputs.size(); i++) { + if (mInputs.valueAt(i)->mRefCount > 0) { + return mInputs.keyAt(i); + } + } + return 0; +} + +float AudioPolicyManagerBase::computeVolume(int stream, int index, audio_io_handle_t output, uint32_t device) +{ + float volume = 1.0; + AudioOutputDescriptor *outputDesc = mOutputs.valueFor(output); + StreamDescriptor &streamDesc = mStreams[stream]; + + if (device == 0) { + device = outputDesc->device(); + } + + int volInt = (100 * (index - streamDesc.mIndexMin)) / (streamDesc.mIndexMax - streamDesc.mIndexMin); + volume = AudioSystem::linearToLog(volInt); + + // if a headset is connected, apply the following rules to ring tones and notifications + // to avoid sound level bursts in user's ears: + // - always attenuate ring tones and notifications volume by 6dB + // - if music is playing, always limit the volume to current music volume, + // with a minimum threshold at -36dB so that notification is always perceived. + if ((device & + (AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP | + AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES | + AudioSystem::DEVICE_OUT_WIRED_HEADSET | + AudioSystem::DEVICE_OUT_WIRED_HEADPHONE)) && + (getStrategy((AudioSystem::stream_type)stream) == STRATEGY_SONIFICATION) && + streamDesc.mCanBeMuted) { + volume *= SONIFICATION_HEADSET_VOLUME_FACTOR; + // when the phone is ringing we must consider that music could have been paused just before + // by the music application and behave as if music was active if the last music track was + // just stopped + if (outputDesc->mRefCount[AudioSystem::MUSIC] || mLimitRingtoneVolume) { + float musicVol = computeVolume(AudioSystem::MUSIC, mStreams[AudioSystem::MUSIC].mIndexCur, output, device); + float minVol = (musicVol > SONIFICATION_HEADSET_VOLUME_MIN) ? musicVol : SONIFICATION_HEADSET_VOLUME_MIN; + if (volume > minVol) { + volume = minVol; + LOGV("computeVolume limiting volume to %f musicVol %f", minVol, musicVol); + } + } + } + + return volume; +} + +status_t AudioPolicyManagerBase::checkAndSetVolume(int stream, int index, audio_io_handle_t output, uint32_t device, int delayMs, bool force) +{ + + // do not change actual stream volume if the stream is muted + if (mOutputs.valueFor(output)->mMuteCount[stream] != 0) { + LOGV("checkAndSetVolume() stream %d muted count %d", stream, mOutputs.valueFor(output)->mMuteCount[stream]); + return NO_ERROR; + } + + // do not change in call volume if bluetooth is connected and vice versa + if ((stream == AudioSystem::VOICE_CALL && mForceUse[AudioSystem::FOR_COMMUNICATION] == AudioSystem::FORCE_BT_SCO) || + (stream == AudioSystem::BLUETOOTH_SCO && mForceUse[AudioSystem::FOR_COMMUNICATION] != AudioSystem::FORCE_BT_SCO)) { + LOGV("checkAndSetVolume() cannot set stream %d volume with force use = %d for comm", + stream, mForceUse[AudioSystem::FOR_COMMUNICATION]); + return INVALID_OPERATION; + } + + float volume = computeVolume(stream, index, output, device); + // do not set volume if the float value did not change + if (volume != mOutputs.valueFor(output)->mCurVolume[stream] || force) { + mOutputs.valueFor(output)->mCurVolume[stream] = volume; + LOGV("setStreamVolume() for output %d stream %d, volume %f, delay %d", output, stream, volume, delayMs); + if (stream == AudioSystem::VOICE_CALL || + stream == AudioSystem::DTMF || + stream == AudioSystem::BLUETOOTH_SCO) { + float voiceVolume = -1.0; + // offset value to reflect actual hardware volume that never reaches 0 + // 1% corresponds roughly to first step in VOICE_CALL stream volume setting (see AudioService.java) + volume = 0.01 + 0.99 * volume; + if (stream == AudioSystem::VOICE_CALL) { + voiceVolume = (float)index/(float)mStreams[stream].mIndexMax; + } else if (stream == AudioSystem::BLUETOOTH_SCO) { + voiceVolume = 1.0; + } + if (voiceVolume >= 0 && output == mHardwareOutput) { + mpClientInterface->setVoiceVolume(voiceVolume, delayMs); + } + } + mpClientInterface->setStreamVolume((AudioSystem::stream_type)stream, volume, output, delayMs); + } + + return NO_ERROR; +} + +void AudioPolicyManagerBase::applyStreamVolumes(audio_io_handle_t output, uint32_t device, int delayMs) +{ + LOGV("applyStreamVolumes() for output %d and device %x", output, device); + + for (int stream = 0; stream < AudioSystem::NUM_STREAM_TYPES; stream++) { + checkAndSetVolume(stream, mStreams[stream].mIndexCur, output, device, delayMs); + } +} + +void AudioPolicyManagerBase::setStrategyMute(routing_strategy strategy, bool on, audio_io_handle_t output, int delayMs) +{ + LOGV("setStrategyMute() strategy %d, mute %d, output %d", strategy, on, output); + for (int stream = 0; stream < AudioSystem::NUM_STREAM_TYPES; stream++) { + if (getStrategy((AudioSystem::stream_type)stream) == strategy) { + setStreamMute(stream, on, output, delayMs); + } + } +} + +void AudioPolicyManagerBase::setStreamMute(int stream, bool on, audio_io_handle_t output, int delayMs) +{ + StreamDescriptor &streamDesc = mStreams[stream]; + AudioOutputDescriptor *outputDesc = mOutputs.valueFor(output); + + LOGV("setStreamMute() stream %d, mute %d, output %d, mMuteCount %d", stream, on, output, outputDesc->mMuteCount[stream]); + + if (on) { + if (outputDesc->mMuteCount[stream] == 0) { + if (streamDesc.mCanBeMuted) { + checkAndSetVolume(stream, 0, output, outputDesc->device(), delayMs); + } + } + // increment mMuteCount after calling checkAndSetVolume() so that volume change is not ignored + outputDesc->mMuteCount[stream]++; + } else { + if (outputDesc->mMuteCount[stream] == 0) { + LOGW("setStreamMute() unmuting non muted stream!"); + return; + } + if (--outputDesc->mMuteCount[stream] == 0) { + checkAndSetVolume(stream, streamDesc.mIndexCur, output, outputDesc->device(), delayMs); + } + } +} + +void AudioPolicyManagerBase::handleIncallSonification(int stream, bool starting, bool stateChange) +{ + // if the stream pertains to sonification strategy and we are in call we must + // mute the stream if it is low visibility. If it is high visibility, we must play a tone + // in the device used for phone strategy and play the tone if the selected device does not + // interfere with the device used for phone strategy + // if stateChange is true, we are called from setPhoneState() and we must mute or unmute as + // many times as there are active tracks on the output + + if (getStrategy((AudioSystem::stream_type)stream) == STRATEGY_SONIFICATION) { + AudioOutputDescriptor *outputDesc = mOutputs.valueFor(mHardwareOutput); + LOGV("handleIncallSonification() stream %d starting %d device %x stateChange %d", + stream, starting, outputDesc->mDevice, stateChange); + if (outputDesc->mRefCount[stream]) { + int muteCount = 1; + if (stateChange) { + muteCount = outputDesc->mRefCount[stream]; + } + if (AudioSystem::isLowVisibility((AudioSystem::stream_type)stream)) { + LOGV("handleIncallSonification() low visibility, muteCount %d", muteCount); + for (int i = 0; i < muteCount; i++) { + setStreamMute(stream, starting, mHardwareOutput); + } + } else { + LOGV("handleIncallSonification() high visibility"); + if (outputDesc->device() & getDeviceForStrategy(STRATEGY_PHONE)) { + LOGV("handleIncallSonification() high visibility muted, muteCount %d", muteCount); + for (int i = 0; i < muteCount; i++) { + setStreamMute(stream, starting, mHardwareOutput); + } + } + if (starting) { + mpClientInterface->startTone(ToneGenerator::TONE_SUP_CALL_WAITING, AudioSystem::VOICE_CALL); + } else { + mpClientInterface->stopTone(); + } + } + } + } +} + +bool AudioPolicyManagerBase::needsDirectOuput(AudioSystem::stream_type stream, + uint32_t samplingRate, + uint32_t format, + uint32_t channels, + AudioSystem::output_flags flags, + uint32_t device) +{ + return ((flags & AudioSystem::OUTPUT_FLAG_DIRECT) || + (format !=0 && !AudioSystem::isLinearPCM(format))); +} + +// --- AudioOutputDescriptor class implementation + +AudioPolicyManagerBase::AudioOutputDescriptor::AudioOutputDescriptor() + : mId(0), mSamplingRate(0), mFormat(0), mChannels(0), mLatency(0), + mFlags((AudioSystem::output_flags)0), mDevice(0), mOutput1(0), mOutput2(0) +{ + // clear usage count for all stream types + for (int i = 0; i < AudioSystem::NUM_STREAM_TYPES; i++) { + mRefCount[i] = 0; + mCurVolume[i] = -1.0; + mMuteCount[i] = 0; + } +} + +uint32_t AudioPolicyManagerBase::AudioOutputDescriptor::device() +{ + uint32_t device = 0; + if (isDuplicated()) { + device = mOutput1->mDevice | mOutput2->mDevice; + } else { + device = mDevice; + } + return device; +} + +void AudioPolicyManagerBase::AudioOutputDescriptor::changeRefCount(AudioSystem::stream_type stream, int delta) +{ + // forward usage count change to attached outputs + if (isDuplicated()) { + mOutput1->changeRefCount(stream, delta); + mOutput2->changeRefCount(stream, delta); + } + if ((delta + (int)mRefCount[stream]) < 0) { + LOGW("changeRefCount() invalid delta %d for stream %d, refCount %d", delta, stream, mRefCount[stream]); + mRefCount[stream] = 0; + return; + } + mRefCount[stream] += delta; + LOGV("changeRefCount() stream %d, count %d", stream, mRefCount[stream]); +} + +uint32_t AudioPolicyManagerBase::AudioOutputDescriptor::refCount() +{ + uint32_t refcount = 0; + for (int i = 0; i < (int)AudioSystem::NUM_STREAM_TYPES; i++) { + refcount += mRefCount[i]; + } + return refcount; +} + +uint32_t AudioPolicyManagerBase::AudioOutputDescriptor::strategyRefCount(routing_strategy strategy) +{ + uint32_t refCount = 0; + for (int i = 0; i < (int)AudioSystem::NUM_STREAM_TYPES; i++) { + if (getStrategy((AudioSystem::stream_type)i) == strategy) { + refCount += mRefCount[i]; + } + } + return refCount; +} + + +status_t AudioPolicyManagerBase::AudioOutputDescriptor::dump(int fd) +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + + snprintf(buffer, SIZE, " Sampling rate: %d\n", mSamplingRate); + result.append(buffer); + snprintf(buffer, SIZE, " Format: %d\n", mFormat); + result.append(buffer); + snprintf(buffer, SIZE, " Channels: %08x\n", mChannels); + result.append(buffer); + snprintf(buffer, SIZE, " Latency: %d\n", mLatency); + result.append(buffer); + snprintf(buffer, SIZE, " Flags %08x\n", mFlags); + result.append(buffer); + snprintf(buffer, SIZE, " Devices %08x\n", device()); + result.append(buffer); + snprintf(buffer, SIZE, " Stream volume refCount muteCount\n"); + result.append(buffer); + for (int i = 0; i < AudioSystem::NUM_STREAM_TYPES; i++) { + snprintf(buffer, SIZE, " %02d %.03f %02d %02d\n", i, mCurVolume[i], mRefCount[i], mMuteCount[i]); + result.append(buffer); + } + write(fd, result.string(), result.size()); + + return NO_ERROR; +} + +// --- AudioInputDescriptor class implementation + +AudioPolicyManagerBase::AudioInputDescriptor::AudioInputDescriptor() + : mSamplingRate(0), mFormat(0), mChannels(0), + mAcoustics((AudioSystem::audio_in_acoustics)0), mDevice(0), mRefCount(0) +{ +} + +status_t AudioPolicyManagerBase::AudioInputDescriptor::dump(int fd) +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + + snprintf(buffer, SIZE, " Sampling rate: %d\n", mSamplingRate); + result.append(buffer); + snprintf(buffer, SIZE, " Format: %d\n", mFormat); + result.append(buffer); + snprintf(buffer, SIZE, " Channels: %08x\n", mChannels); + result.append(buffer); + snprintf(buffer, SIZE, " Acoustics %08x\n", mAcoustics); + result.append(buffer); + snprintf(buffer, SIZE, " Devices %08x\n", mDevice); + result.append(buffer); + snprintf(buffer, SIZE, " Ref Count %d\n", mRefCount); + result.append(buffer); + write(fd, result.string(), result.size()); + + return NO_ERROR; +} + +// --- StreamDescriptor class implementation + +void AudioPolicyManagerBase::StreamDescriptor::dump(char* buffer, size_t size) +{ + snprintf(buffer, size, " %02d %02d %02d %d\n", + mIndexMin, + mIndexMax, + mIndexCur, + mCanBeMuted); +} + + +}; // namespace android diff --git a/services/audioflinger/AudioPolicyService.cpp b/services/audioflinger/AudioPolicyService.cpp new file mode 100644 index 0000000..bb3905c --- /dev/null +++ b/services/audioflinger/AudioPolicyService.cpp @@ -0,0 +1,924 @@ +/* + * Copyright (C) 2009 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. + */ + +#define LOG_TAG "AudioPolicyService" +//#define LOG_NDEBUG 0 + +#undef __STRICT_ANSI__ +#define __STDINT_LIMITS +#define __STDC_LIMIT_MACROS +#include <stdint.h> + +#include <sys/time.h> +#include <binder/IServiceManager.h> +#include <utils/Log.h> +#include <cutils/properties.h> +#include <binder/IPCThreadState.h> +#include <utils/String16.h> +#include <utils/threads.h> +#include "AudioPolicyService.h" +#include <hardware_legacy/AudioPolicyManagerBase.h> +#include <cutils/properties.h> +#include <dlfcn.h> +#include <hardware_legacy/power.h> + +// ---------------------------------------------------------------------------- +// the sim build doesn't have gettid + +#ifndef HAVE_GETTID +# define gettid getpid +#endif + +namespace android { + + +static const char *kDeadlockedString = "AudioPolicyService may be deadlocked\n"; +static const char *kCmdDeadlockedString = "AudioPolicyService command thread may be deadlocked\n"; + +static const int kDumpLockRetries = 50; +static const int kDumpLockSleep = 20000; + +static bool checkPermission() { +#ifndef HAVE_ANDROID_OS + return true; +#endif + if (getpid() == IPCThreadState::self()->getCallingPid()) return true; + bool ok = checkCallingPermission(String16("android.permission.MODIFY_AUDIO_SETTINGS")); + if (!ok) LOGE("Request requires android.permission.MODIFY_AUDIO_SETTINGS"); + return ok; +} + +// ---------------------------------------------------------------------------- + +AudioPolicyService::AudioPolicyService() + : BnAudioPolicyService() , mpPolicyManager(NULL) +{ + char value[PROPERTY_VALUE_MAX]; + + // start tone playback thread + mTonePlaybackThread = new AudioCommandThread(String8("")); + // start audio commands thread + mAudioCommandThread = new AudioCommandThread(String8("ApmCommandThread")); + +#if (defined GENERIC_AUDIO) || (defined AUDIO_POLICY_TEST) + mpPolicyManager = new AudioPolicyManagerBase(this); + LOGV("build for GENERIC_AUDIO - using generic audio policy"); +#else + // if running in emulation - use the emulator driver + if (property_get("ro.kernel.qemu", value, 0)) { + LOGV("Running in emulation - using generic audio policy"); + mpPolicyManager = new AudioPolicyManagerBase(this); + } + else { + LOGV("Using hardware specific audio policy"); + mpPolicyManager = createAudioPolicyManager(this); + } +#endif + + // load properties + property_get("ro.camera.sound.forced", value, "0"); + mpPolicyManager->setSystemProperty("ro.camera.sound.forced", value); +} + +AudioPolicyService::~AudioPolicyService() +{ + mTonePlaybackThread->exit(); + mTonePlaybackThread.clear(); + mAudioCommandThread->exit(); + mAudioCommandThread.clear(); + + if (mpPolicyManager) { + delete mpPolicyManager; + } +} + + +status_t AudioPolicyService::setDeviceConnectionState(AudioSystem::audio_devices device, + AudioSystem::device_connection_state state, + const char *device_address) +{ + if (mpPolicyManager == NULL) { + return NO_INIT; + } + if (!checkPermission()) { + return PERMISSION_DENIED; + } + if (!AudioSystem::isOutputDevice(device) && !AudioSystem::isInputDevice(device)) { + return BAD_VALUE; + } + if (state != AudioSystem::DEVICE_STATE_AVAILABLE && state != AudioSystem::DEVICE_STATE_UNAVAILABLE) { + return BAD_VALUE; + } + + LOGV("setDeviceConnectionState() tid %d", gettid()); + Mutex::Autolock _l(mLock); + return mpPolicyManager->setDeviceConnectionState(device, state, device_address); +} + +AudioSystem::device_connection_state AudioPolicyService::getDeviceConnectionState(AudioSystem::audio_devices device, + const char *device_address) +{ + if (mpPolicyManager == NULL) { + return AudioSystem::DEVICE_STATE_UNAVAILABLE; + } + if (!checkPermission()) { + return AudioSystem::DEVICE_STATE_UNAVAILABLE; + } + return mpPolicyManager->getDeviceConnectionState(device, device_address); +} + +status_t AudioPolicyService::setPhoneState(int state) +{ + if (mpPolicyManager == NULL) { + return NO_INIT; + } + if (!checkPermission()) { + return PERMISSION_DENIED; + } + if (state < 0 || state >= AudioSystem::NUM_MODES) { + return BAD_VALUE; + } + + LOGV("setPhoneState() tid %d", gettid()); + + // TODO: check if it is more appropriate to do it in platform specific policy manager + AudioSystem::setMode(state); + + Mutex::Autolock _l(mLock); + mpPolicyManager->setPhoneState(state); + return NO_ERROR; +} + +status_t AudioPolicyService::setRingerMode(uint32_t mode, uint32_t mask) +{ + if (mpPolicyManager == NULL) { + return NO_INIT; + } + if (!checkPermission()) { + return PERMISSION_DENIED; + } + + mpPolicyManager->setRingerMode(mode, mask); + return NO_ERROR; +} + +status_t AudioPolicyService::setForceUse(AudioSystem::force_use usage, AudioSystem::forced_config config) +{ + if (mpPolicyManager == NULL) { + return NO_INIT; + } + if (!checkPermission()) { + return PERMISSION_DENIED; + } + if (usage < 0 || usage >= AudioSystem::NUM_FORCE_USE) { + return BAD_VALUE; + } + if (config < 0 || config >= AudioSystem::NUM_FORCE_CONFIG) { + return BAD_VALUE; + } + LOGV("setForceUse() tid %d", gettid()); + Mutex::Autolock _l(mLock); + mpPolicyManager->setForceUse(usage, config); + return NO_ERROR; +} + +AudioSystem::forced_config AudioPolicyService::getForceUse(AudioSystem::force_use usage) +{ + if (mpPolicyManager == NULL) { + return AudioSystem::FORCE_NONE; + } + if (!checkPermission()) { + return AudioSystem::FORCE_NONE; + } + if (usage < 0 || usage >= AudioSystem::NUM_FORCE_USE) { + return AudioSystem::FORCE_NONE; + } + return mpPolicyManager->getForceUse(usage); +} + +audio_io_handle_t AudioPolicyService::getOutput(AudioSystem::stream_type stream, + uint32_t samplingRate, + uint32_t format, + uint32_t channels, + AudioSystem::output_flags flags) +{ + if (mpPolicyManager == NULL) { + return 0; + } + LOGV("getOutput() tid %d", gettid()); + Mutex::Autolock _l(mLock); + return mpPolicyManager->getOutput(stream, samplingRate, format, channels, flags); +} + +status_t AudioPolicyService::startOutput(audio_io_handle_t output, AudioSystem::stream_type stream) +{ + if (mpPolicyManager == NULL) { + return NO_INIT; + } + LOGV("startOutput() tid %d", gettid()); + Mutex::Autolock _l(mLock); + return mpPolicyManager->startOutput(output, stream); +} + +status_t AudioPolicyService::stopOutput(audio_io_handle_t output, AudioSystem::stream_type stream) +{ + if (mpPolicyManager == NULL) { + return NO_INIT; + } + LOGV("stopOutput() tid %d", gettid()); + Mutex::Autolock _l(mLock); + return mpPolicyManager->stopOutput(output, stream); +} + +void AudioPolicyService::releaseOutput(audio_io_handle_t output) +{ + if (mpPolicyManager == NULL) { + return; + } + LOGV("releaseOutput() tid %d", gettid()); + Mutex::Autolock _l(mLock); + mpPolicyManager->releaseOutput(output); +} + +audio_io_handle_t AudioPolicyService::getInput(int inputSource, + uint32_t samplingRate, + uint32_t format, + uint32_t channels, + AudioSystem::audio_in_acoustics acoustics) +{ + if (mpPolicyManager == NULL) { + return 0; + } + Mutex::Autolock _l(mLock); + return mpPolicyManager->getInput(inputSource, samplingRate, format, channels, acoustics); +} + +status_t AudioPolicyService::startInput(audio_io_handle_t input) +{ + if (mpPolicyManager == NULL) { + return NO_INIT; + } + Mutex::Autolock _l(mLock); + return mpPolicyManager->startInput(input); +} + +status_t AudioPolicyService::stopInput(audio_io_handle_t input) +{ + if (mpPolicyManager == NULL) { + return NO_INIT; + } + Mutex::Autolock _l(mLock); + return mpPolicyManager->stopInput(input); +} + +void AudioPolicyService::releaseInput(audio_io_handle_t input) +{ + if (mpPolicyManager == NULL) { + return; + } + Mutex::Autolock _l(mLock); + mpPolicyManager->releaseInput(input); +} + +status_t AudioPolicyService::initStreamVolume(AudioSystem::stream_type stream, + int indexMin, + int indexMax) +{ + if (mpPolicyManager == NULL) { + return NO_INIT; + } + if (!checkPermission()) { + return PERMISSION_DENIED; + } + if (stream < 0 || stream >= AudioSystem::NUM_STREAM_TYPES) { + return BAD_VALUE; + } + mpPolicyManager->initStreamVolume(stream, indexMin, indexMax); + return NO_ERROR; +} + +status_t AudioPolicyService::setStreamVolumeIndex(AudioSystem::stream_type stream, int index) +{ + if (mpPolicyManager == NULL) { + return NO_INIT; + } + if (!checkPermission()) { + return PERMISSION_DENIED; + } + if (stream < 0 || stream >= AudioSystem::NUM_STREAM_TYPES) { + return BAD_VALUE; + } + + return mpPolicyManager->setStreamVolumeIndex(stream, index); +} + +status_t AudioPolicyService::getStreamVolumeIndex(AudioSystem::stream_type stream, int *index) +{ + if (mpPolicyManager == NULL) { + return NO_INIT; + } + if (!checkPermission()) { + return PERMISSION_DENIED; + } + if (stream < 0 || stream >= AudioSystem::NUM_STREAM_TYPES) { + return BAD_VALUE; + } + return mpPolicyManager->getStreamVolumeIndex(stream, index); +} + +void AudioPolicyService::binderDied(const wp<IBinder>& who) { + LOGW("binderDied() %p, tid %d, calling tid %d", who.unsafe_get(), gettid(), IPCThreadState::self()->getCallingPid()); +} + +static bool tryLock(Mutex& mutex) +{ + bool locked = false; + for (int i = 0; i < kDumpLockRetries; ++i) { + if (mutex.tryLock() == NO_ERROR) { + locked = true; + break; + } + usleep(kDumpLockSleep); + } + return locked; +} + +status_t AudioPolicyService::dumpInternals(int fd) +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + + snprintf(buffer, SIZE, "PolicyManager Interface: %p\n", mpPolicyManager); + result.append(buffer); + snprintf(buffer, SIZE, "Command Thread: %p\n", mAudioCommandThread.get()); + result.append(buffer); + snprintf(buffer, SIZE, "Tones Thread: %p\n", mTonePlaybackThread.get()); + result.append(buffer); + + write(fd, result.string(), result.size()); + return NO_ERROR; +} + +status_t AudioPolicyService::dump(int fd, const Vector<String16>& args) +{ + if (checkCallingPermission(String16("android.permission.DUMP")) == false) { + dumpPermissionDenial(fd); + } else { + bool locked = tryLock(mLock); + if (!locked) { + String8 result(kDeadlockedString); + write(fd, result.string(), result.size()); + } + + dumpInternals(fd); + if (mAudioCommandThread != NULL) { + mAudioCommandThread->dump(fd); + } + if (mTonePlaybackThread != NULL) { + mTonePlaybackThread->dump(fd); + } + + if (mpPolicyManager) { + mpPolicyManager->dump(fd); + } + + if (locked) mLock.unlock(); + } + return NO_ERROR; +} + +status_t AudioPolicyService::dumpPermissionDenial(int fd) +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + snprintf(buffer, SIZE, "Permission Denial: " + "can't dump AudioPolicyService from pid=%d, uid=%d\n", + IPCThreadState::self()->getCallingPid(), + IPCThreadState::self()->getCallingUid()); + result.append(buffer); + write(fd, result.string(), result.size()); + return NO_ERROR; +} + +status_t AudioPolicyService::onTransact( + uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) +{ + return BnAudioPolicyService::onTransact(code, data, reply, flags); +} + + +// ---------------------------------------------------------------------------- +void AudioPolicyService::instantiate() { + defaultServiceManager()->addService( + String16("media.audio_policy"), new AudioPolicyService()); +} + + +// ---------------------------------------------------------------------------- +// AudioPolicyClientInterface implementation +// ---------------------------------------------------------------------------- + + +audio_io_handle_t AudioPolicyService::openOutput(uint32_t *pDevices, + uint32_t *pSamplingRate, + uint32_t *pFormat, + uint32_t *pChannels, + uint32_t *pLatencyMs, + AudioSystem::output_flags flags) +{ + sp<IAudioFlinger> af = AudioSystem::get_audio_flinger(); + if (af == 0) { + LOGW("openOutput() could not get AudioFlinger"); + return 0; + } + + return af->openOutput(pDevices, pSamplingRate, (uint32_t *)pFormat, pChannels, pLatencyMs, flags); +} + +audio_io_handle_t AudioPolicyService::openDuplicateOutput(audio_io_handle_t output1, audio_io_handle_t output2) +{ + sp<IAudioFlinger> af = AudioSystem::get_audio_flinger(); + if (af == 0) { + LOGW("openDuplicateOutput() could not get AudioFlinger"); + return 0; + } + return af->openDuplicateOutput(output1, output2); +} + +status_t AudioPolicyService::closeOutput(audio_io_handle_t output) +{ + sp<IAudioFlinger> af = AudioSystem::get_audio_flinger(); + if (af == 0) return PERMISSION_DENIED; + + return af->closeOutput(output); +} + + +status_t AudioPolicyService::suspendOutput(audio_io_handle_t output) +{ + sp<IAudioFlinger> af = AudioSystem::get_audio_flinger(); + if (af == 0) { + LOGW("suspendOutput() could not get AudioFlinger"); + return PERMISSION_DENIED; + } + + return af->suspendOutput(output); +} + +status_t AudioPolicyService::restoreOutput(audio_io_handle_t output) +{ + sp<IAudioFlinger> af = AudioSystem::get_audio_flinger(); + if (af == 0) { + LOGW("restoreOutput() could not get AudioFlinger"); + return PERMISSION_DENIED; + } + + return af->restoreOutput(output); +} + +audio_io_handle_t AudioPolicyService::openInput(uint32_t *pDevices, + uint32_t *pSamplingRate, + uint32_t *pFormat, + uint32_t *pChannels, + uint32_t acoustics) +{ + sp<IAudioFlinger> af = AudioSystem::get_audio_flinger(); + if (af == 0) { + LOGW("openInput() could not get AudioFlinger"); + return 0; + } + + return af->openInput(pDevices, pSamplingRate, (uint32_t *)pFormat, pChannels, acoustics); +} + +status_t AudioPolicyService::closeInput(audio_io_handle_t input) +{ + sp<IAudioFlinger> af = AudioSystem::get_audio_flinger(); + if (af == 0) return PERMISSION_DENIED; + + return af->closeInput(input); +} + +status_t AudioPolicyService::setStreamVolume(AudioSystem::stream_type stream, float volume, audio_io_handle_t output, int delayMs) +{ + return mAudioCommandThread->volumeCommand((int)stream, volume, (int)output, delayMs); +} + +status_t AudioPolicyService::setStreamOutput(AudioSystem::stream_type stream, audio_io_handle_t output) +{ + sp<IAudioFlinger> af = AudioSystem::get_audio_flinger(); + if (af == 0) return PERMISSION_DENIED; + + return af->setStreamOutput(stream, output); +} + + +void AudioPolicyService::setParameters(audio_io_handle_t ioHandle, const String8& keyValuePairs, int delayMs) +{ + mAudioCommandThread->parametersCommand((int)ioHandle, keyValuePairs, delayMs); +} + +String8 AudioPolicyService::getParameters(audio_io_handle_t ioHandle, const String8& keys) +{ + String8 result = AudioSystem::getParameters(ioHandle, keys); + return result; +} + +status_t AudioPolicyService::startTone(ToneGenerator::tone_type tone, AudioSystem::stream_type stream) +{ + mTonePlaybackThread->startToneCommand(tone, stream); + return NO_ERROR; +} + +status_t AudioPolicyService::stopTone() +{ + mTonePlaybackThread->stopToneCommand(); + return NO_ERROR; +} + +status_t AudioPolicyService::setVoiceVolume(float volume, int delayMs) +{ + return mAudioCommandThread->voiceVolumeCommand(volume, delayMs); +} + +// ----------- AudioPolicyService::AudioCommandThread implementation ---------- + +AudioPolicyService::AudioCommandThread::AudioCommandThread(String8 name) + : Thread(false), mName(name) +{ + mpToneGenerator = NULL; +} + + +AudioPolicyService::AudioCommandThread::~AudioCommandThread() +{ + if (mName != "" && !mAudioCommands.isEmpty()) { + release_wake_lock(mName.string()); + } + mAudioCommands.clear(); + if (mpToneGenerator != NULL) delete mpToneGenerator; +} + +void AudioPolicyService::AudioCommandThread::onFirstRef() +{ + if (mName != "") { + run(mName.string(), ANDROID_PRIORITY_AUDIO); + } else { + run("AudioCommandThread", ANDROID_PRIORITY_AUDIO); + } +} + +bool AudioPolicyService::AudioCommandThread::threadLoop() +{ + nsecs_t waitTime = INT64_MAX; + + mLock.lock(); + while (!exitPending()) + { + while(!mAudioCommands.isEmpty()) { + nsecs_t curTime = systemTime(); + // commands are sorted by increasing time stamp: execute them from index 0 and up + if (mAudioCommands[0]->mTime <= curTime) { + AudioCommand *command = mAudioCommands[0]; + mAudioCommands.removeAt(0); + mLastCommand = *command; + + switch (command->mCommand) { + case START_TONE: { + mLock.unlock(); + ToneData *data = (ToneData *)command->mParam; + LOGV("AudioCommandThread() processing start tone %d on stream %d", + data->mType, data->mStream); + if (mpToneGenerator != NULL) + delete mpToneGenerator; + mpToneGenerator = new ToneGenerator(data->mStream, 1.0); + mpToneGenerator->startTone(data->mType); + delete data; + mLock.lock(); + }break; + case STOP_TONE: { + mLock.unlock(); + LOGV("AudioCommandThread() processing stop tone"); + if (mpToneGenerator != NULL) { + mpToneGenerator->stopTone(); + delete mpToneGenerator; + mpToneGenerator = NULL; + } + mLock.lock(); + }break; + case SET_VOLUME: { + VolumeData *data = (VolumeData *)command->mParam; + LOGV("AudioCommandThread() processing set volume stream %d, volume %f, output %d", data->mStream, data->mVolume, data->mIO); + command->mStatus = AudioSystem::setStreamVolume(data->mStream, data->mVolume, data->mIO); + if (command->mWaitStatus) { + command->mCond.signal(); + mWaitWorkCV.wait(mLock); + } + delete data; + }break; + case SET_PARAMETERS: { + ParametersData *data = (ParametersData *)command->mParam; + LOGV("AudioCommandThread() processing set parameters string %s, io %d", data->mKeyValuePairs.string(), data->mIO); + command->mStatus = AudioSystem::setParameters(data->mIO, data->mKeyValuePairs); + if (command->mWaitStatus) { + command->mCond.signal(); + mWaitWorkCV.wait(mLock); + } + delete data; + }break; + case SET_VOICE_VOLUME: { + VoiceVolumeData *data = (VoiceVolumeData *)command->mParam; + LOGV("AudioCommandThread() processing set voice volume volume %f", data->mVolume); + command->mStatus = AudioSystem::setVoiceVolume(data->mVolume); + if (command->mWaitStatus) { + command->mCond.signal(); + mWaitWorkCV.wait(mLock); + } + delete data; + }break; + default: + LOGW("AudioCommandThread() unknown command %d", command->mCommand); + } + delete command; + waitTime = INT64_MAX; + } else { + waitTime = mAudioCommands[0]->mTime - curTime; + break; + } + } + // release delayed commands wake lock + if (mName != "" && mAudioCommands.isEmpty()) { + release_wake_lock(mName.string()); + } + LOGV("AudioCommandThread() going to sleep"); + mWaitWorkCV.waitRelative(mLock, waitTime); + LOGV("AudioCommandThread() waking up"); + } + mLock.unlock(); + return false; +} + +status_t AudioPolicyService::AudioCommandThread::dump(int fd) +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + + snprintf(buffer, SIZE, "AudioCommandThread %p Dump\n", this); + result.append(buffer); + write(fd, result.string(), result.size()); + + bool locked = tryLock(mLock); + if (!locked) { + String8 result2(kCmdDeadlockedString); + write(fd, result2.string(), result2.size()); + } + + snprintf(buffer, SIZE, "- Commands:\n"); + result = String8(buffer); + result.append(" Command Time Wait pParam\n"); + for (int i = 0; i < (int)mAudioCommands.size(); i++) { + mAudioCommands[i]->dump(buffer, SIZE); + result.append(buffer); + } + result.append(" Last Command\n"); + mLastCommand.dump(buffer, SIZE); + result.append(buffer); + + write(fd, result.string(), result.size()); + + if (locked) mLock.unlock(); + + return NO_ERROR; +} + +void AudioPolicyService::AudioCommandThread::startToneCommand(int type, int stream) +{ + AudioCommand *command = new AudioCommand(); + command->mCommand = START_TONE; + ToneData *data = new ToneData(); + data->mType = type; + data->mStream = stream; + command->mParam = (void *)data; + command->mWaitStatus = false; + Mutex::Autolock _l(mLock); + insertCommand_l(command); + LOGV("AudioCommandThread() adding tone start type %d, stream %d", type, stream); + mWaitWorkCV.signal(); +} + +void AudioPolicyService::AudioCommandThread::stopToneCommand() +{ + AudioCommand *command = new AudioCommand(); + command->mCommand = STOP_TONE; + command->mParam = NULL; + command->mWaitStatus = false; + Mutex::Autolock _l(mLock); + insertCommand_l(command); + LOGV("AudioCommandThread() adding tone stop"); + mWaitWorkCV.signal(); +} + +status_t AudioPolicyService::AudioCommandThread::volumeCommand(int stream, float volume, int output, int delayMs) +{ + status_t status = NO_ERROR; + + AudioCommand *command = new AudioCommand(); + command->mCommand = SET_VOLUME; + VolumeData *data = new VolumeData(); + data->mStream = stream; + data->mVolume = volume; + data->mIO = output; + command->mParam = data; + if (delayMs == 0) { + command->mWaitStatus = true; + } else { + command->mWaitStatus = false; + } + Mutex::Autolock _l(mLock); + insertCommand_l(command, delayMs); + LOGV("AudioCommandThread() adding set volume stream %d, volume %f, output %d", stream, volume, output); + mWaitWorkCV.signal(); + if (command->mWaitStatus) { + command->mCond.wait(mLock); + status = command->mStatus; + mWaitWorkCV.signal(); + } + return status; +} + +status_t AudioPolicyService::AudioCommandThread::parametersCommand(int ioHandle, const String8& keyValuePairs, int delayMs) +{ + status_t status = NO_ERROR; + + AudioCommand *command = new AudioCommand(); + command->mCommand = SET_PARAMETERS; + ParametersData *data = new ParametersData(); + data->mIO = ioHandle; + data->mKeyValuePairs = keyValuePairs; + command->mParam = data; + if (delayMs == 0) { + command->mWaitStatus = true; + } else { + command->mWaitStatus = false; + } + Mutex::Autolock _l(mLock); + insertCommand_l(command, delayMs); + LOGV("AudioCommandThread() adding set parameter string %s, io %d ,delay %d", keyValuePairs.string(), ioHandle, delayMs); + mWaitWorkCV.signal(); + if (command->mWaitStatus) { + command->mCond.wait(mLock); + status = command->mStatus; + mWaitWorkCV.signal(); + } + return status; +} + +status_t AudioPolicyService::AudioCommandThread::voiceVolumeCommand(float volume, int delayMs) +{ + status_t status = NO_ERROR; + + AudioCommand *command = new AudioCommand(); + command->mCommand = SET_VOICE_VOLUME; + VoiceVolumeData *data = new VoiceVolumeData(); + data->mVolume = volume; + command->mParam = data; + if (delayMs == 0) { + command->mWaitStatus = true; + } else { + command->mWaitStatus = false; + } + Mutex::Autolock _l(mLock); + insertCommand_l(command, delayMs); + LOGV("AudioCommandThread() adding set voice volume volume %f", volume); + mWaitWorkCV.signal(); + if (command->mWaitStatus) { + command->mCond.wait(mLock); + status = command->mStatus; + mWaitWorkCV.signal(); + } + return status; +} + +// insertCommand_l() must be called with mLock held +void AudioPolicyService::AudioCommandThread::insertCommand_l(AudioCommand *command, int delayMs) +{ + ssize_t i; + Vector <AudioCommand *> removedCommands; + + command->mTime = systemTime() + milliseconds(delayMs); + + // acquire wake lock to make sure delayed commands are processed + if (mName != "" && mAudioCommands.isEmpty()) { + acquire_wake_lock(PARTIAL_WAKE_LOCK, mName.string()); + } + + // check same pending commands with later time stamps and eliminate them + for (i = mAudioCommands.size()-1; i >= 0; i--) { + AudioCommand *command2 = mAudioCommands[i]; + // commands are sorted by increasing time stamp: no need to scan the rest of mAudioCommands + if (command2->mTime <= command->mTime) break; + if (command2->mCommand != command->mCommand) continue; + + switch (command->mCommand) { + case SET_PARAMETERS: { + ParametersData *data = (ParametersData *)command->mParam; + ParametersData *data2 = (ParametersData *)command2->mParam; + if (data->mIO != data2->mIO) break; + LOGV("Comparing parameter command %s to new command %s", data2->mKeyValuePairs.string(), data->mKeyValuePairs.string()); + AudioParameter param = AudioParameter(data->mKeyValuePairs); + AudioParameter param2 = AudioParameter(data2->mKeyValuePairs); + for (size_t j = 0; j < param.size(); j++) { + String8 key; + String8 value; + param.getAt(j, key, value); + for (size_t k = 0; k < param2.size(); k++) { + String8 key2; + String8 value2; + param2.getAt(k, key2, value2); + if (key2 == key) { + param2.remove(key2); + LOGV("Filtering out parameter %s", key2.string()); + break; + } + } + } + // if all keys have been filtered out, remove the command. + // otherwise, update the key value pairs + if (param2.size() == 0) { + removedCommands.add(command2); + } else { + data2->mKeyValuePairs = param2.toString(); + } + } break; + + case SET_VOLUME: { + VolumeData *data = (VolumeData *)command->mParam; + VolumeData *data2 = (VolumeData *)command2->mParam; + if (data->mIO != data2->mIO) break; + if (data->mStream != data2->mStream) break; + LOGV("Filtering out volume command on output %d for stream %d", data->mIO, data->mStream); + removedCommands.add(command2); + } break; + case START_TONE: + case STOP_TONE: + default: + break; + } + } + + // remove filtered commands + for (size_t j = 0; j < removedCommands.size(); j++) { + // removed commands always have time stamps greater than current command + for (size_t k = i + 1; k < mAudioCommands.size(); k++) { + if (mAudioCommands[k] == removedCommands[j]) { + LOGV("suppressing command: %d", mAudioCommands[k]->mCommand); + mAudioCommands.removeAt(k); + break; + } + } + } + removedCommands.clear(); + + // insert command at the right place according to its time stamp + LOGV("inserting command: %d at index %d, num commands %d", command->mCommand, (int)i+1, mAudioCommands.size()); + mAudioCommands.insertAt(command, i + 1); +} + +void AudioPolicyService::AudioCommandThread::exit() +{ + LOGV("AudioCommandThread::exit"); + { + AutoMutex _l(mLock); + requestExit(); + mWaitWorkCV.signal(); + } + requestExitAndWait(); +} + +void AudioPolicyService::AudioCommandThread::AudioCommand::dump(char* buffer, size_t size) +{ + snprintf(buffer, size, " %02d %06d.%03d %01u %p\n", + mCommand, + (int)ns2s(mTime), + (int)ns2ms(mTime)%1000, + mWaitStatus, + mParam); +} + +}; // namespace android diff --git a/services/audioflinger/AudioPolicyService.h b/services/audioflinger/AudioPolicyService.h new file mode 100644 index 0000000..a13d0bd --- /dev/null +++ b/services/audioflinger/AudioPolicyService.h @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2009 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. + */ + +#ifndef ANDROID_AUDIOPOLICYSERVICE_H +#define ANDROID_AUDIOPOLICYSERVICE_H + +#include <media/IAudioPolicyService.h> +#include <hardware_legacy/AudioPolicyInterface.h> +#include <media/ToneGenerator.h> +#include <utils/Vector.h> + +namespace android { + +class String8; + +// ---------------------------------------------------------------------------- + +class AudioPolicyService: public BnAudioPolicyService, public AudioPolicyClientInterface, public IBinder::DeathRecipient +{ + +public: + static void instantiate(); + + virtual status_t dump(int fd, const Vector<String16>& args); + + // + // BnAudioPolicyService (see AudioPolicyInterface for method descriptions) + // + + virtual status_t setDeviceConnectionState(AudioSystem::audio_devices device, + AudioSystem::device_connection_state state, + const char *device_address); + virtual AudioSystem::device_connection_state getDeviceConnectionState(AudioSystem::audio_devices device, + const char *device_address); + virtual status_t setPhoneState(int state); + virtual status_t setRingerMode(uint32_t mode, uint32_t mask); + virtual status_t setForceUse(AudioSystem::force_use usage, AudioSystem::forced_config config); + virtual AudioSystem::forced_config getForceUse(AudioSystem::force_use usage); + virtual audio_io_handle_t getOutput(AudioSystem::stream_type stream, + uint32_t samplingRate = 0, + uint32_t format = AudioSystem::FORMAT_DEFAULT, + uint32_t channels = 0, + AudioSystem::output_flags flags = AudioSystem::OUTPUT_FLAG_INDIRECT); + virtual status_t startOutput(audio_io_handle_t output, AudioSystem::stream_type stream); + virtual status_t stopOutput(audio_io_handle_t output, AudioSystem::stream_type stream); + virtual void releaseOutput(audio_io_handle_t output); + virtual audio_io_handle_t getInput(int inputSource, + uint32_t samplingRate = 0, + uint32_t format = AudioSystem::FORMAT_DEFAULT, + uint32_t channels = 0, + AudioSystem::audio_in_acoustics acoustics = (AudioSystem::audio_in_acoustics)0); + virtual status_t startInput(audio_io_handle_t input); + virtual status_t stopInput(audio_io_handle_t input); + virtual void releaseInput(audio_io_handle_t input); + virtual status_t initStreamVolume(AudioSystem::stream_type stream, + int indexMin, + int indexMax); + virtual status_t setStreamVolumeIndex(AudioSystem::stream_type stream, int index); + virtual status_t getStreamVolumeIndex(AudioSystem::stream_type stream, int *index); + + virtual status_t onTransact( + uint32_t code, + const Parcel& data, + Parcel* reply, + uint32_t flags); + + // IBinder::DeathRecipient + virtual void binderDied(const wp<IBinder>& who); + + // + // AudioPolicyClientInterface + // + virtual audio_io_handle_t openOutput(uint32_t *pDevices, + uint32_t *pSamplingRate, + uint32_t *pFormat, + uint32_t *pChannels, + uint32_t *pLatencyMs, + AudioSystem::output_flags flags); + virtual audio_io_handle_t openDuplicateOutput(audio_io_handle_t output1, audio_io_handle_t output2); + virtual status_t closeOutput(audio_io_handle_t output); + virtual status_t suspendOutput(audio_io_handle_t output); + virtual status_t restoreOutput(audio_io_handle_t output); + virtual audio_io_handle_t openInput(uint32_t *pDevices, + uint32_t *pSamplingRate, + uint32_t *pFormat, + uint32_t *pChannels, + uint32_t acoustics); + virtual status_t closeInput(audio_io_handle_t input); + virtual status_t setStreamVolume(AudioSystem::stream_type stream, float volume, audio_io_handle_t output, int delayMs = 0); + virtual status_t setStreamOutput(AudioSystem::stream_type stream, audio_io_handle_t output); + virtual void setParameters(audio_io_handle_t ioHandle, const String8& keyValuePairs, int delayMs = 0); + virtual String8 getParameters(audio_io_handle_t ioHandle, const String8& keys); + virtual status_t startTone(ToneGenerator::tone_type tone, AudioSystem::stream_type stream); + virtual status_t stopTone(); + virtual status_t setVoiceVolume(float volume, int delayMs = 0); + +private: + AudioPolicyService(); + virtual ~AudioPolicyService(); + + status_t dumpInternals(int fd); + + // Thread used for tone playback and to send audio config commands to audio flinger + // For tone playback, using a separate thread is necessary to avoid deadlock with mLock because startTone() + // and stopTone() are normally called with mLock locked and requesting a tone start or stop will cause + // calls to AudioPolicyService and an attempt to lock mLock. + // For audio config commands, it is necessary because audio flinger requires that the calling process (user) + // has permission to modify audio settings. + class AudioCommandThread : public Thread { + class AudioCommand; + public: + + // commands for tone AudioCommand + enum { + START_TONE, + STOP_TONE, + SET_VOLUME, + SET_PARAMETERS, + SET_VOICE_VOLUME + }; + + AudioCommandThread (String8 name); + virtual ~AudioCommandThread(); + + status_t dump(int fd); + + // Thread virtuals + virtual void onFirstRef(); + virtual bool threadLoop(); + + void exit(); + void startToneCommand(int type = 0, int stream = 0); + void stopToneCommand(); + status_t volumeCommand(int stream, float volume, int output, int delayMs = 0); + status_t parametersCommand(int ioHandle, const String8& keyValuePairs, int delayMs = 0); + status_t voiceVolumeCommand(float volume, int delayMs = 0); + void insertCommand_l(AudioCommand *command, int delayMs = 0); + + private: + // descriptor for requested tone playback event + class AudioCommand { + + public: + AudioCommand() + : mCommand(-1) {} + + void dump(char* buffer, size_t size); + + int mCommand; // START_TONE, STOP_TONE ... + nsecs_t mTime; // time stamp + Condition mCond; // condition for status return + status_t mStatus; // command status + bool mWaitStatus; // true if caller is waiting for status + void *mParam; // command parameter (ToneData, VolumeData, ParametersData) + }; + + class ToneData { + public: + int mType; // tone type (START_TONE only) + int mStream; // stream type (START_TONE only) + }; + + class VolumeData { + public: + int mStream; + float mVolume; + int mIO; + }; + + class ParametersData { + public: + int mIO; + String8 mKeyValuePairs; + }; + + class VoiceVolumeData { + public: + float mVolume; + }; + + Mutex mLock; + Condition mWaitWorkCV; + Vector <AudioCommand *> mAudioCommands; // list of pending commands + ToneGenerator *mpToneGenerator; // the tone generator + AudioCommand mLastCommand; // last processed command (used by dump) + String8 mName; // string used by wake lock fo delayed commands + }; + + // Internal dump utilities. + status_t dumpPermissionDenial(int fd); + + + Mutex mLock; // prevents concurrent access to AudioPolicy manager functions changing device + // connection stated our routing + AudioPolicyInterface* mpPolicyManager; // the platform specific policy manager + sp <AudioCommandThread> mAudioCommandThread; // audio commands thread + sp <AudioCommandThread> mTonePlaybackThread; // tone playback thread +}; + +}; // namespace android + +#endif // ANDROID_AUDIOPOLICYSERVICE_H + + + + + + + + diff --git a/services/audioflinger/AudioResampler.cpp b/services/audioflinger/AudioResampler.cpp new file mode 100644 index 0000000..5dabacb --- /dev/null +++ b/services/audioflinger/AudioResampler.cpp @@ -0,0 +1,595 @@ +/* + * Copyright (C) 2007 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. + */ + +#define LOG_TAG "AudioResampler" +//#define LOG_NDEBUG 0 + +#include <stdint.h> +#include <stdlib.h> +#include <sys/types.h> +#include <cutils/log.h> +#include <cutils/properties.h> +#include "AudioResampler.h" +#include "AudioResamplerSinc.h" +#include "AudioResamplerCubic.h" + +namespace android { + +#ifdef __ARM_ARCH_5E__ // optimized asm option + #define ASM_ARM_RESAMP1 // enable asm optimisation for ResamplerOrder1 +#endif // __ARM_ARCH_5E__ +// ---------------------------------------------------------------------------- + +class AudioResamplerOrder1 : public AudioResampler { +public: + AudioResamplerOrder1(int bitDepth, int inChannelCount, int32_t sampleRate) : + AudioResampler(bitDepth, inChannelCount, sampleRate), mX0L(0), mX0R(0) { + } + virtual void resample(int32_t* out, size_t outFrameCount, + AudioBufferProvider* provider); +private: + // number of bits used in interpolation multiply - 15 bits avoids overflow + static const int kNumInterpBits = 15; + + // bits to shift the phase fraction down to avoid overflow + static const int kPreInterpShift = kNumPhaseBits - kNumInterpBits; + + void init() {} + void resampleMono16(int32_t* out, size_t outFrameCount, + AudioBufferProvider* provider); + void resampleStereo16(int32_t* out, size_t outFrameCount, + AudioBufferProvider* provider); +#ifdef ASM_ARM_RESAMP1 // asm optimisation for ResamplerOrder1 + void AsmMono16Loop(int16_t *in, int32_t* maxOutPt, int32_t maxInIdx, + size_t &outputIndex, int32_t* out, size_t &inputIndex, int32_t vl, int32_t vr, + uint32_t &phaseFraction, uint32_t phaseIncrement); + void AsmStereo16Loop(int16_t *in, int32_t* maxOutPt, int32_t maxInIdx, + size_t &outputIndex, int32_t* out, size_t &inputIndex, int32_t vl, int32_t vr, + uint32_t &phaseFraction, uint32_t phaseIncrement); +#endif // ASM_ARM_RESAMP1 + + static inline int32_t Interp(int32_t x0, int32_t x1, uint32_t f) { + return x0 + (((x1 - x0) * (int32_t)(f >> kPreInterpShift)) >> kNumInterpBits); + } + static inline void Advance(size_t* index, uint32_t* frac, uint32_t inc) { + *frac += inc; + *index += (size_t)(*frac >> kNumPhaseBits); + *frac &= kPhaseMask; + } + int mX0L; + int mX0R; +}; + +// ---------------------------------------------------------------------------- +AudioResampler* AudioResampler::create(int bitDepth, int inChannelCount, + int32_t sampleRate, int quality) { + + // can only create low quality resample now + AudioResampler* resampler; + + char value[PROPERTY_VALUE_MAX]; + if (property_get("af.resampler.quality", value, 0)) { + quality = atoi(value); + LOGD("forcing AudioResampler quality to %d", quality); + } + + if (quality == DEFAULT) + quality = LOW_QUALITY; + + switch (quality) { + default: + case LOW_QUALITY: + LOGV("Create linear Resampler"); + resampler = new AudioResamplerOrder1(bitDepth, inChannelCount, sampleRate); + break; + case MED_QUALITY: + LOGV("Create cubic Resampler"); + resampler = new AudioResamplerCubic(bitDepth, inChannelCount, sampleRate); + break; + case HIGH_QUALITY: + LOGV("Create sinc Resampler"); + resampler = new AudioResamplerSinc(bitDepth, inChannelCount, sampleRate); + break; + } + + // initialize resampler + resampler->init(); + return resampler; +} + +AudioResampler::AudioResampler(int bitDepth, int inChannelCount, + int32_t sampleRate) : + mBitDepth(bitDepth), mChannelCount(inChannelCount), + mSampleRate(sampleRate), mInSampleRate(sampleRate), mInputIndex(0), + mPhaseFraction(0) { + // sanity check on format + if ((bitDepth != 16) ||(inChannelCount < 1) || (inChannelCount > 2)) { + LOGE("Unsupported sample format, %d bits, %d channels", bitDepth, + inChannelCount); + // LOG_ASSERT(0); + } + + // initialize common members + mVolume[0] = mVolume[1] = 0; + mBuffer.frameCount = 0; + + // save format for quick lookup + if (inChannelCount == 1) { + mFormat = MONO_16_BIT; + } else { + mFormat = STEREO_16_BIT; + } +} + +AudioResampler::~AudioResampler() { +} + +void AudioResampler::setSampleRate(int32_t inSampleRate) { + mInSampleRate = inSampleRate; + mPhaseIncrement = (uint32_t)((kPhaseMultiplier * inSampleRate) / mSampleRate); +} + +void AudioResampler::setVolume(int16_t left, int16_t right) { + // TODO: Implement anti-zipper filter + mVolume[0] = left; + mVolume[1] = right; +} + +// ---------------------------------------------------------------------------- + +void AudioResamplerOrder1::resample(int32_t* out, size_t outFrameCount, + AudioBufferProvider* provider) { + + // should never happen, but we overflow if it does + // LOG_ASSERT(outFrameCount < 32767); + + // select the appropriate resampler + switch (mChannelCount) { + case 1: + resampleMono16(out, outFrameCount, provider); + break; + case 2: + resampleStereo16(out, outFrameCount, provider); + break; + } +} + +void AudioResamplerOrder1::resampleStereo16(int32_t* out, size_t outFrameCount, + AudioBufferProvider* provider) { + + int32_t vl = mVolume[0]; + int32_t vr = mVolume[1]; + + size_t inputIndex = mInputIndex; + uint32_t phaseFraction = mPhaseFraction; + uint32_t phaseIncrement = mPhaseIncrement; + size_t outputIndex = 0; + size_t outputSampleCount = outFrameCount * 2; + size_t inFrameCount = (outFrameCount*mInSampleRate)/mSampleRate; + + // LOGE("starting resample %d frames, inputIndex=%d, phaseFraction=%d, phaseIncrement=%d\n", + // outFrameCount, inputIndex, phaseFraction, phaseIncrement); + + while (outputIndex < outputSampleCount) { + + // buffer is empty, fetch a new one + while (mBuffer.frameCount == 0) { + mBuffer.frameCount = inFrameCount; + provider->getNextBuffer(&mBuffer); + if (mBuffer.raw == NULL) { + goto resampleStereo16_exit; + } + + // LOGE("New buffer fetched: %d frames\n", mBuffer.frameCount); + if (mBuffer.frameCount > inputIndex) break; + + inputIndex -= mBuffer.frameCount; + mX0L = mBuffer.i16[mBuffer.frameCount*2-2]; + mX0R = mBuffer.i16[mBuffer.frameCount*2-1]; + provider->releaseBuffer(&mBuffer); + // mBuffer.frameCount == 0 now so we reload a new buffer + } + + int16_t *in = mBuffer.i16; + + // handle boundary case + while (inputIndex == 0) { + // LOGE("boundary case\n"); + out[outputIndex++] += vl * Interp(mX0L, in[0], phaseFraction); + out[outputIndex++] += vr * Interp(mX0R, in[1], phaseFraction); + Advance(&inputIndex, &phaseFraction, phaseIncrement); + if (outputIndex == outputSampleCount) + break; + } + + // process input samples + // LOGE("general case\n"); + +#ifdef ASM_ARM_RESAMP1 // asm optimisation for ResamplerOrder1 + if (inputIndex + 2 < mBuffer.frameCount) { + int32_t* maxOutPt; + int32_t maxInIdx; + + maxOutPt = out + (outputSampleCount - 2); // 2 because 2 frames per loop + maxInIdx = mBuffer.frameCount - 2; + AsmStereo16Loop(in, maxOutPt, maxInIdx, outputIndex, out, inputIndex, vl, vr, + phaseFraction, phaseIncrement); + } +#endif // ASM_ARM_RESAMP1 + + while (outputIndex < outputSampleCount && inputIndex < mBuffer.frameCount) { + out[outputIndex++] += vl * Interp(in[inputIndex*2-2], + in[inputIndex*2], phaseFraction); + out[outputIndex++] += vr * Interp(in[inputIndex*2-1], + in[inputIndex*2+1], phaseFraction); + Advance(&inputIndex, &phaseFraction, phaseIncrement); + } + + // LOGE("loop done - outputIndex=%d, inputIndex=%d\n", outputIndex, inputIndex); + + // if done with buffer, save samples + if (inputIndex >= mBuffer.frameCount) { + inputIndex -= mBuffer.frameCount; + + // LOGE("buffer done, new input index %d", inputIndex); + + mX0L = mBuffer.i16[mBuffer.frameCount*2-2]; + mX0R = mBuffer.i16[mBuffer.frameCount*2-1]; + provider->releaseBuffer(&mBuffer); + + // verify that the releaseBuffer resets the buffer frameCount + // LOG_ASSERT(mBuffer.frameCount == 0); + } + } + + // LOGE("output buffer full - outputIndex=%d, inputIndex=%d\n", outputIndex, inputIndex); + +resampleStereo16_exit: + // save state + mInputIndex = inputIndex; + mPhaseFraction = phaseFraction; +} + +void AudioResamplerOrder1::resampleMono16(int32_t* out, size_t outFrameCount, + AudioBufferProvider* provider) { + + int32_t vl = mVolume[0]; + int32_t vr = mVolume[1]; + + size_t inputIndex = mInputIndex; + uint32_t phaseFraction = mPhaseFraction; + uint32_t phaseIncrement = mPhaseIncrement; + size_t outputIndex = 0; + size_t outputSampleCount = outFrameCount * 2; + size_t inFrameCount = (outFrameCount*mInSampleRate)/mSampleRate; + + // LOGE("starting resample %d frames, inputIndex=%d, phaseFraction=%d, phaseIncrement=%d\n", + // outFrameCount, inputIndex, phaseFraction, phaseIncrement); + while (outputIndex < outputSampleCount) { + // buffer is empty, fetch a new one + while (mBuffer.frameCount == 0) { + mBuffer.frameCount = inFrameCount; + provider->getNextBuffer(&mBuffer); + if (mBuffer.raw == NULL) { + mInputIndex = inputIndex; + mPhaseFraction = phaseFraction; + goto resampleMono16_exit; + } + // LOGE("New buffer fetched: %d frames\n", mBuffer.frameCount); + if (mBuffer.frameCount > inputIndex) break; + + inputIndex -= mBuffer.frameCount; + mX0L = mBuffer.i16[mBuffer.frameCount-1]; + provider->releaseBuffer(&mBuffer); + // mBuffer.frameCount == 0 now so we reload a new buffer + } + int16_t *in = mBuffer.i16; + + // handle boundary case + while (inputIndex == 0) { + // LOGE("boundary case\n"); + int32_t sample = Interp(mX0L, in[0], phaseFraction); + out[outputIndex++] += vl * sample; + out[outputIndex++] += vr * sample; + Advance(&inputIndex, &phaseFraction, phaseIncrement); + if (outputIndex == outputSampleCount) + break; + } + + // process input samples + // LOGE("general case\n"); + +#ifdef ASM_ARM_RESAMP1 // asm optimisation for ResamplerOrder1 + if (inputIndex + 2 < mBuffer.frameCount) { + int32_t* maxOutPt; + int32_t maxInIdx; + + maxOutPt = out + (outputSampleCount - 2); + maxInIdx = (int32_t)mBuffer.frameCount - 2; + AsmMono16Loop(in, maxOutPt, maxInIdx, outputIndex, out, inputIndex, vl, vr, + phaseFraction, phaseIncrement); + } +#endif // ASM_ARM_RESAMP1 + + while (outputIndex < outputSampleCount && inputIndex < mBuffer.frameCount) { + int32_t sample = Interp(in[inputIndex-1], in[inputIndex], + phaseFraction); + out[outputIndex++] += vl * sample; + out[outputIndex++] += vr * sample; + Advance(&inputIndex, &phaseFraction, phaseIncrement); + } + + + // LOGE("loop done - outputIndex=%d, inputIndex=%d\n", outputIndex, inputIndex); + + // if done with buffer, save samples + if (inputIndex >= mBuffer.frameCount) { + inputIndex -= mBuffer.frameCount; + + // LOGE("buffer done, new input index %d", inputIndex); + + mX0L = mBuffer.i16[mBuffer.frameCount-1]; + provider->releaseBuffer(&mBuffer); + + // verify that the releaseBuffer resets the buffer frameCount + // LOG_ASSERT(mBuffer.frameCount == 0); + } + } + + // LOGE("output buffer full - outputIndex=%d, inputIndex=%d\n", outputIndex, inputIndex); + +resampleMono16_exit: + // save state + mInputIndex = inputIndex; + mPhaseFraction = phaseFraction; +} + +#ifdef ASM_ARM_RESAMP1 // asm optimisation for ResamplerOrder1 + +/******************************************************************* +* +* AsmMono16Loop +* asm optimized monotonic loop version; one loop is 2 frames +* Input: +* in : pointer on input samples +* maxOutPt : pointer on first not filled +* maxInIdx : index on first not used +* outputIndex : pointer on current output index +* out : pointer on output buffer +* inputIndex : pointer on current input index +* vl, vr : left and right gain +* phaseFraction : pointer on current phase fraction +* phaseIncrement +* Ouput: +* outputIndex : +* out : updated buffer +* inputIndex : index of next to use +* phaseFraction : phase fraction for next interpolation +* +*******************************************************************/ +void AudioResamplerOrder1::AsmMono16Loop(int16_t *in, int32_t* maxOutPt, int32_t maxInIdx, + size_t &outputIndex, int32_t* out, size_t &inputIndex, int32_t vl, int32_t vr, + uint32_t &phaseFraction, uint32_t phaseIncrement) +{ +#define MO_PARAM5 "36" // offset of parameter 5 (outputIndex) + + asm( + "stmfd sp!, {r4, r5, r6, r7, r8, r9, r10, r11, lr}\n" + // get parameters + " ldr r6, [sp, #" MO_PARAM5 " + 20]\n" // &phaseFraction + " ldr r6, [r6]\n" // phaseFraction + " ldr r7, [sp, #" MO_PARAM5 " + 8]\n" // &inputIndex + " ldr r7, [r7]\n" // inputIndex + " ldr r8, [sp, #" MO_PARAM5 " + 4]\n" // out + " ldr r0, [sp, #" MO_PARAM5 " + 0]\n" // &outputIndex + " ldr r0, [r0]\n" // outputIndex + " add r8, r0, asl #2\n" // curOut + " ldr r9, [sp, #" MO_PARAM5 " + 24]\n" // phaseIncrement + " ldr r10, [sp, #" MO_PARAM5 " + 12]\n" // vl + " ldr r11, [sp, #" MO_PARAM5 " + 16]\n" // vr + + // r0 pin, x0, Samp + + // r1 in + // r2 maxOutPt + // r3 maxInIdx + + // r4 x1, i1, i3, Out1 + // r5 out0 + + // r6 frac + // r7 inputIndex + // r8 curOut + + // r9 inc + // r10 vl + // r11 vr + + // r12 + // r13 sp + // r14 + + // the following loop works on 2 frames + + ".Y4L01:\n" + " cmp r8, r2\n" // curOut - maxCurOut + " bcs .Y4L02\n" + +#define MO_ONE_FRAME \ + " add r0, r1, r7, asl #1\n" /* in + inputIndex */\ + " ldrsh r4, [r0]\n" /* in[inputIndex] */\ + " ldr r5, [r8]\n" /* out[outputIndex] */\ + " ldrsh r0, [r0, #-2]\n" /* in[inputIndex-1] */\ + " bic r6, r6, #0xC0000000\n" /* phaseFraction & ... */\ + " sub r4, r4, r0\n" /* in[inputIndex] - in[inputIndex-1] */\ + " mov r4, r4, lsl #2\n" /* <<2 */\ + " smulwt r4, r4, r6\n" /* (x1-x0)*.. */\ + " add r6, r6, r9\n" /* phaseFraction + phaseIncrement */\ + " add r0, r0, r4\n" /* x0 - (..) */\ + " mla r5, r0, r10, r5\n" /* vl*interp + out[] */\ + " ldr r4, [r8, #4]\n" /* out[outputIndex+1] */\ + " str r5, [r8], #4\n" /* out[outputIndex++] = ... */\ + " mla r4, r0, r11, r4\n" /* vr*interp + out[] */\ + " add r7, r7, r6, lsr #30\n" /* inputIndex + phaseFraction>>30 */\ + " str r4, [r8], #4\n" /* out[outputIndex++] = ... */ + + MO_ONE_FRAME // frame 1 + MO_ONE_FRAME // frame 2 + + " cmp r7, r3\n" // inputIndex - maxInIdx + " bcc .Y4L01\n" + ".Y4L02:\n" + + " bic r6, r6, #0xC0000000\n" // phaseFraction & ... + // save modified values + " ldr r0, [sp, #" MO_PARAM5 " + 20]\n" // &phaseFraction + " str r6, [r0]\n" // phaseFraction + " ldr r0, [sp, #" MO_PARAM5 " + 8]\n" // &inputIndex + " str r7, [r0]\n" // inputIndex + " ldr r0, [sp, #" MO_PARAM5 " + 4]\n" // out + " sub r8, r0\n" // curOut - out + " asr r8, #2\n" // new outputIndex + " ldr r0, [sp, #" MO_PARAM5 " + 0]\n" // &outputIndex + " str r8, [r0]\n" // save outputIndex + + " ldmfd sp!, {r4, r5, r6, r7, r8, r9, r10, r11, pc}\n" + ); +} + +/******************************************************************* +* +* AsmStereo16Loop +* asm optimized stereo loop version; one loop is 2 frames +* Input: +* in : pointer on input samples +* maxOutPt : pointer on first not filled +* maxInIdx : index on first not used +* outputIndex : pointer on current output index +* out : pointer on output buffer +* inputIndex : pointer on current input index +* vl, vr : left and right gain +* phaseFraction : pointer on current phase fraction +* phaseIncrement +* Ouput: +* outputIndex : +* out : updated buffer +* inputIndex : index of next to use +* phaseFraction : phase fraction for next interpolation +* +*******************************************************************/ +void AudioResamplerOrder1::AsmStereo16Loop(int16_t *in, int32_t* maxOutPt, int32_t maxInIdx, + size_t &outputIndex, int32_t* out, size_t &inputIndex, int32_t vl, int32_t vr, + uint32_t &phaseFraction, uint32_t phaseIncrement) +{ +#define ST_PARAM5 "40" // offset of parameter 5 (outputIndex) + asm( + "stmfd sp!, {r4, r5, r6, r7, r8, r9, r10, r11, r12, lr}\n" + // get parameters + " ldr r6, [sp, #" ST_PARAM5 " + 20]\n" // &phaseFraction + " ldr r6, [r6]\n" // phaseFraction + " ldr r7, [sp, #" ST_PARAM5 " + 8]\n" // &inputIndex + " ldr r7, [r7]\n" // inputIndex + " ldr r8, [sp, #" ST_PARAM5 " + 4]\n" // out + " ldr r0, [sp, #" ST_PARAM5 " + 0]\n" // &outputIndex + " ldr r0, [r0]\n" // outputIndex + " add r8, r0, asl #2\n" // curOut + " ldr r9, [sp, #" ST_PARAM5 " + 24]\n" // phaseIncrement + " ldr r10, [sp, #" ST_PARAM5 " + 12]\n" // vl + " ldr r11, [sp, #" ST_PARAM5 " + 16]\n" // vr + + // r0 pin, x0, Samp + + // r1 in + // r2 maxOutPt + // r3 maxInIdx + + // r4 x1, i1, i3, out1 + // r5 out0 + + // r6 frac + // r7 inputIndex + // r8 curOut + + // r9 inc + // r10 vl + // r11 vr + + // r12 temporary + // r13 sp + // r14 + + ".Y5L01:\n" + " cmp r8, r2\n" // curOut - maxCurOut + " bcs .Y5L02\n" + +#define ST_ONE_FRAME \ + " bic r6, r6, #0xC0000000\n" /* phaseFraction & ... */\ +\ + " add r0, r1, r7, asl #2\n" /* in + 2*inputIndex */\ +\ + " ldrsh r4, [r0]\n" /* in[2*inputIndex] */\ + " ldr r5, [r8]\n" /* out[outputIndex] */\ + " ldrsh r12, [r0, #-4]\n" /* in[2*inputIndex-2] */\ + " sub r4, r4, r12\n" /* in[2*InputIndex] - in[2*InputIndex-2] */\ + " mov r4, r4, lsl #2\n" /* <<2 */\ + " smulwt r4, r4, r6\n" /* (x1-x0)*.. */\ + " add r12, r12, r4\n" /* x0 - (..) */\ + " mla r5, r12, r10, r5\n" /* vl*interp + out[] */\ + " ldr r4, [r8, #4]\n" /* out[outputIndex+1] */\ + " str r5, [r8], #4\n" /* out[outputIndex++] = ... */\ +\ + " ldrsh r12, [r0, #+2]\n" /* in[2*inputIndex+1] */\ + " ldrsh r0, [r0, #-2]\n" /* in[2*inputIndex-1] */\ + " sub r12, r12, r0\n" /* in[2*InputIndex] - in[2*InputIndex-2] */\ + " mov r12, r12, lsl #2\n" /* <<2 */\ + " smulwt r12, r12, r6\n" /* (x1-x0)*.. */\ + " add r12, r0, r12\n" /* x0 - (..) */\ + " mla r4, r12, r11, r4\n" /* vr*interp + out[] */\ + " str r4, [r8], #4\n" /* out[outputIndex++] = ... */\ +\ + " add r6, r6, r9\n" /* phaseFraction + phaseIncrement */\ + " add r7, r7, r6, lsr #30\n" /* inputIndex + phaseFraction>>30 */ + + ST_ONE_FRAME // frame 1 + ST_ONE_FRAME // frame 1 + + " cmp r7, r3\n" // inputIndex - maxInIdx + " bcc .Y5L01\n" + ".Y5L02:\n" + + " bic r6, r6, #0xC0000000\n" // phaseFraction & ... + // save modified values + " ldr r0, [sp, #" ST_PARAM5 " + 20]\n" // &phaseFraction + " str r6, [r0]\n" // phaseFraction + " ldr r0, [sp, #" ST_PARAM5 " + 8]\n" // &inputIndex + " str r7, [r0]\n" // inputIndex + " ldr r0, [sp, #" ST_PARAM5 " + 4]\n" // out + " sub r8, r0\n" // curOut - out + " asr r8, #2\n" // new outputIndex + " ldr r0, [sp, #" ST_PARAM5 " + 0]\n" // &outputIndex + " str r8, [r0]\n" // save outputIndex + + " ldmfd sp!, {r4, r5, r6, r7, r8, r9, r10, r11, r12, pc}\n" + ); +} + +#endif // ASM_ARM_RESAMP1 + + +// ---------------------------------------------------------------------------- +} +; // namespace android + diff --git a/services/audioflinger/AudioResampler.h b/services/audioflinger/AudioResampler.h new file mode 100644 index 0000000..2dfac76 --- /dev/null +++ b/services/audioflinger/AudioResampler.h @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2007 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. + */ + +#ifndef ANDROID_AUDIO_RESAMPLER_H +#define ANDROID_AUDIO_RESAMPLER_H + +#include <stdint.h> +#include <sys/types.h> + +#include "AudioBufferProvider.h" + +namespace android { +// ---------------------------------------------------------------------------- + +class AudioResampler { +public: + // Determines quality of SRC. + // LOW_QUALITY: linear interpolator (1st order) + // MED_QUALITY: cubic interpolator (3rd order) + // HIGH_QUALITY: fixed multi-tap FIR (e.g. 48KHz->44.1KHz) + // NOTE: high quality SRC will only be supported for + // certain fixed rate conversions. Sample rate cannot be + // changed dynamically. + enum src_quality { + DEFAULT=0, + LOW_QUALITY=1, + MED_QUALITY=2, + HIGH_QUALITY=3 + }; + + static AudioResampler* create(int bitDepth, int inChannelCount, + int32_t sampleRate, int quality=DEFAULT); + + virtual ~AudioResampler(); + + virtual void init() = 0; + virtual void setSampleRate(int32_t inSampleRate); + virtual void setVolume(int16_t left, int16_t right); + + virtual void resample(int32_t* out, size_t outFrameCount, + AudioBufferProvider* provider) = 0; + +protected: + // number of bits for phase fraction - 30 bits allows nearly 2x downsampling + static const int kNumPhaseBits = 30; + + // phase mask for fraction + static const uint32_t kPhaseMask = (1LU<<kNumPhaseBits)-1; + + // multiplier to calculate fixed point phase increment + static const double kPhaseMultiplier = 1L << kNumPhaseBits; + + enum format {MONO_16_BIT, STEREO_16_BIT}; + AudioResampler(int bitDepth, int inChannelCount, int32_t sampleRate); + + // prevent copying + AudioResampler(const AudioResampler&); + AudioResampler& operator=(const AudioResampler&); + + int32_t mBitDepth; + int32_t mChannelCount; + int32_t mSampleRate; + int32_t mInSampleRate; + AudioBufferProvider::Buffer mBuffer; + union { + int16_t mVolume[2]; + uint32_t mVolumeRL; + }; + int16_t mTargetVolume[2]; + format mFormat; + size_t mInputIndex; + int32_t mPhaseIncrement; + uint32_t mPhaseFraction; +}; + +// ---------------------------------------------------------------------------- +} +; // namespace android + +#endif // ANDROID_AUDIO_RESAMPLER_H diff --git a/services/audioflinger/AudioResamplerCubic.cpp b/services/audioflinger/AudioResamplerCubic.cpp new file mode 100644 index 0000000..1d247bd --- /dev/null +++ b/services/audioflinger/AudioResamplerCubic.cpp @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2007 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 <stdint.h> +#include <string.h> +#include <sys/types.h> +#include <cutils/log.h> + +#include "AudioResampler.h" +#include "AudioResamplerCubic.h" + +#define LOG_TAG "AudioSRC" + +namespace android { +// ---------------------------------------------------------------------------- + +void AudioResamplerCubic::init() { + memset(&left, 0, sizeof(state)); + memset(&right, 0, sizeof(state)); +} + +void AudioResamplerCubic::resample(int32_t* out, size_t outFrameCount, + AudioBufferProvider* provider) { + + // should never happen, but we overflow if it does + // LOG_ASSERT(outFrameCount < 32767); + + // select the appropriate resampler + switch (mChannelCount) { + case 1: + resampleMono16(out, outFrameCount, provider); + break; + case 2: + resampleStereo16(out, outFrameCount, provider); + break; + } +} + +void AudioResamplerCubic::resampleStereo16(int32_t* out, size_t outFrameCount, + AudioBufferProvider* provider) { + + int32_t vl = mVolume[0]; + int32_t vr = mVolume[1]; + + size_t inputIndex = mInputIndex; + uint32_t phaseFraction = mPhaseFraction; + uint32_t phaseIncrement = mPhaseIncrement; + size_t outputIndex = 0; + size_t outputSampleCount = outFrameCount * 2; + size_t inFrameCount = (outFrameCount*mInSampleRate)/mSampleRate; + + // fetch first buffer + if (mBuffer.frameCount == 0) { + mBuffer.frameCount = inFrameCount; + provider->getNextBuffer(&mBuffer); + if (mBuffer.raw == NULL) + return; + // LOGW("New buffer: offset=%p, frames=%dn", mBuffer.raw, mBuffer.frameCount); + } + int16_t *in = mBuffer.i16; + + while (outputIndex < outputSampleCount) { + int32_t sample; + int32_t x; + + // calculate output sample + x = phaseFraction >> kPreInterpShift; + out[outputIndex++] += vl * interp(&left, x); + out[outputIndex++] += vr * interp(&right, x); + // out[outputIndex++] += vr * in[inputIndex*2]; + + // increment phase + phaseFraction += phaseIncrement; + uint32_t indexIncrement = (phaseFraction >> kNumPhaseBits); + phaseFraction &= kPhaseMask; + + // time to fetch another sample + while (indexIncrement--) { + + inputIndex++; + if (inputIndex == mBuffer.frameCount) { + inputIndex = 0; + provider->releaseBuffer(&mBuffer); + mBuffer.frameCount = inFrameCount; + provider->getNextBuffer(&mBuffer); + if (mBuffer.raw == NULL) + goto save_state; // ugly, but efficient + in = mBuffer.i16; + // LOGW("New buffer: offset=%p, frames=%d\n", mBuffer.raw, mBuffer.frameCount); + } + + // advance sample state + advance(&left, in[inputIndex*2]); + advance(&right, in[inputIndex*2+1]); + } + } + +save_state: + // LOGW("Done: index=%d, fraction=%u", inputIndex, phaseFraction); + mInputIndex = inputIndex; + mPhaseFraction = phaseFraction; +} + +void AudioResamplerCubic::resampleMono16(int32_t* out, size_t outFrameCount, + AudioBufferProvider* provider) { + + int32_t vl = mVolume[0]; + int32_t vr = mVolume[1]; + + size_t inputIndex = mInputIndex; + uint32_t phaseFraction = mPhaseFraction; + uint32_t phaseIncrement = mPhaseIncrement; + size_t outputIndex = 0; + size_t outputSampleCount = outFrameCount * 2; + size_t inFrameCount = (outFrameCount*mInSampleRate)/mSampleRate; + + // fetch first buffer + if (mBuffer.frameCount == 0) { + mBuffer.frameCount = inFrameCount; + provider->getNextBuffer(&mBuffer); + if (mBuffer.raw == NULL) + return; + // LOGW("New buffer: offset=%p, frames=%d\n", mBuffer.raw, mBuffer.frameCount); + } + int16_t *in = mBuffer.i16; + + while (outputIndex < outputSampleCount) { + int32_t sample; + int32_t x; + + // calculate output sample + x = phaseFraction >> kPreInterpShift; + sample = interp(&left, x); + out[outputIndex++] += vl * sample; + out[outputIndex++] += vr * sample; + + // increment phase + phaseFraction += phaseIncrement; + uint32_t indexIncrement = (phaseFraction >> kNumPhaseBits); + phaseFraction &= kPhaseMask; + + // time to fetch another sample + while (indexIncrement--) { + + inputIndex++; + if (inputIndex == mBuffer.frameCount) { + inputIndex = 0; + provider->releaseBuffer(&mBuffer); + mBuffer.frameCount = inFrameCount; + provider->getNextBuffer(&mBuffer); + if (mBuffer.raw == NULL) + goto save_state; // ugly, but efficient + // LOGW("New buffer: offset=%p, frames=%dn", mBuffer.raw, mBuffer.frameCount); + in = mBuffer.i16; + } + + // advance sample state + advance(&left, in[inputIndex]); + } + } + +save_state: + // LOGW("Done: index=%d, fraction=%u", inputIndex, phaseFraction); + mInputIndex = inputIndex; + mPhaseFraction = phaseFraction; +} + +// ---------------------------------------------------------------------------- +} +; // namespace android + diff --git a/services/audioflinger/AudioResamplerCubic.h b/services/audioflinger/AudioResamplerCubic.h new file mode 100644 index 0000000..b72b62a --- /dev/null +++ b/services/audioflinger/AudioResamplerCubic.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2007 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. + */ + +#ifndef ANDROID_AUDIO_RESAMPLER_CUBIC_H +#define ANDROID_AUDIO_RESAMPLER_CUBIC_H + +#include <stdint.h> +#include <sys/types.h> +#include <cutils/log.h> + +#include "AudioResampler.h" + +namespace android { +// ---------------------------------------------------------------------------- + +class AudioResamplerCubic : public AudioResampler { +public: + AudioResamplerCubic(int bitDepth, int inChannelCount, int32_t sampleRate) : + AudioResampler(bitDepth, inChannelCount, sampleRate) { + } + virtual void resample(int32_t* out, size_t outFrameCount, + AudioBufferProvider* provider); +private: + // number of bits used in interpolation multiply - 14 bits avoids overflow + static const int kNumInterpBits = 14; + + // bits to shift the phase fraction down to avoid overflow + static const int kPreInterpShift = kNumPhaseBits - kNumInterpBits; + typedef struct { + int32_t a, b, c, y0, y1, y2, y3; + } state; + void init(); + void resampleMono16(int32_t* out, size_t outFrameCount, + AudioBufferProvider* provider); + void resampleStereo16(int32_t* out, size_t outFrameCount, + AudioBufferProvider* provider); + static inline int32_t interp(state* p, int32_t x) { + return (((((p->a * x >> 14) + p->b) * x >> 14) + p->c) * x >> 14) + p->y1; + } + static inline void advance(state* p, int16_t in) { + p->y0 = p->y1; + p->y1 = p->y2; + p->y2 = p->y3; + p->y3 = in; + p->a = (3 * (p->y1 - p->y2) - p->y0 + p->y3) >> 1; + p->b = (p->y2 << 1) + p->y0 - (((5 * p->y1 + p->y3)) >> 1); + p->c = (p->y2 - p->y0) >> 1; + } + state left, right; +}; + +// ---------------------------------------------------------------------------- +}; // namespace android + +#endif /*ANDROID_AUDIO_RESAMPLER_CUBIC_H*/ diff --git a/services/audioflinger/AudioResamplerSinc.cpp b/services/audioflinger/AudioResamplerSinc.cpp new file mode 100644 index 0000000..9e5e254 --- /dev/null +++ b/services/audioflinger/AudioResamplerSinc.cpp @@ -0,0 +1,358 @@ +/* + * Copyright (C) 2007 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 <string.h> +#include "AudioResamplerSinc.h" + +namespace android { +// ---------------------------------------------------------------------------- + + +/* + * These coeficients are computed with the "fir" utility found in + * tools/resampler_tools + * TODO: A good optimization would be to transpose this matrix, to take + * better advantage of the data-cache. + */ +const int32_t AudioResamplerSinc::mFirCoefsUp[] = { + 0x7fffffff, 0x7f15d078, 0x7c5e0da6, 0x77ecd867, 0x71e2e251, 0x6a6c304a, 0x61be7269, 0x58170412, 0x4db8ab05, 0x42e92ea6, 0x37eee214, 0x2d0e3bb1, 0x22879366, 0x18951e95, 0x0f693d0d, 0x072d2621, + 0x00000000, 0xf9f66655, 0xf51a5fd7, 0xf16bbd84, 0xeee0d9ac, 0xed67a922, 0xece70de6, 0xed405897, 0xee50e505, 0xeff3be30, 0xf203370f, 0xf45a6741, 0xf6d67d53, 0xf957db66, 0xfbc2f647, 0xfe00f2b9, + 0x00000000, 0x01b37218, 0x0313a0c6, 0x041d930d, 0x04d28057, 0x053731b0, 0x05534dff, 0x05309bfd, 0x04da440d, 0x045c1aee, 0x03c1fcdd, 0x03173ef5, 0x02663ae8, 0x01b7f736, 0x0113ec79, 0x007fe6a9, + 0x00000000, 0xff96b229, 0xff44f99f, 0xff0a86be, 0xfee5f803, 0xfed518fd, 0xfed521fd, 0xfee2f4fd, 0xfefb54f8, 0xff1b159b, 0xff3f4203, 0xff6539e0, 0xff8ac502, 0xffae1ddd, 0xffcdf3f9, 0xffe96798, + 0x00000000, 0x00119de6, 0x001e6b7e, 0x0026cb7a, 0x002b4830, 0x002c83d6, 0x002b2a82, 0x0027e67a, 0x002356f9, 0x001e098e, 0x001875e4, 0x0012fbbe, 0x000de2d1, 0x00095c10, 0x00058414, 0x00026636, + 0x00000000, 0xfffe44a9, 0xfffd206d, 0xfffc7b7f, 0xfffc3c8f, 0xfffc4ac2, 0xfffc8f2b, 0xfffcf5c4, 0xfffd6df3, 0xfffdeab2, 0xfffe6275, 0xfffececf, 0xffff2c07, 0xffff788c, 0xffffb471, 0xffffe0f2, + 0x00000000, 0x000013e6, 0x00001f03, 0x00002396, 0x00002399, 0x000020b6, 0x00001c3c, 0x00001722, 0x00001216, 0x00000d81, 0x0000099c, 0x0000067c, 0x00000419, 0x0000025f, 0x00000131, 0x00000070, + 0x00000000, 0xffffffc7, 0xffffffb3, 0xffffffb3, 0xffffffbe, 0xffffffcd, 0xffffffdb, 0xffffffe7, 0xfffffff0, 0xfffffff7, 0xfffffffb, 0xfffffffe, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, + 0x00000000 // this one is needed for lerping the last coefficient +}; + +/* + * These coefficients are optimized for 48KHz -> 44.1KHz (stop-band at 22.050KHz) + * It's possible to use the above coefficient for any down-sampling + * at the expense of a slower processing loop (we can interpolate + * these coefficient from the above by "Stretching" them in time). + */ +const int32_t AudioResamplerSinc::mFirCoefsDown[] = { + 0x7fffffff, 0x7f55e46d, 0x7d5b4c60, 0x7a1b4b98, 0x75a7fb14, 0x7019f0bd, 0x698f875a, 0x622bfd59, 0x5a167256, 0x5178cc54, 0x487e8e6c, 0x3f53aae8, 0x36235ad4, 0x2d17047b, 0x245539ab, 0x1c00d540, + 0x14383e57, 0x0d14d5ca, 0x06aa910b, 0x0107c38b, 0xfc351654, 0xf835abae, 0xf5076b45, 0xf2a37202, 0xf0fe9faa, 0xf00a3bbd, 0xefb4aa81, 0xefea2b05, 0xf0959716, 0xf1a11e83, 0xf2f6f7a0, 0xf481fff4, + 0xf62e48ce, 0xf7e98ca5, 0xf9a38b4c, 0xfb4e4bfa, 0xfcde456f, 0xfe4a6d30, 0xff8c2fdf, 0x009f5555, 0x0181d393, 0x0233940f, 0x02b62f06, 0x030ca07d, 0x033afa62, 0x03461725, 0x03334f83, 0x030835fa, + 0x02ca59cc, 0x027f12d1, 0x022b570d, 0x01d39a49, 0x017bb78f, 0x0126e414, 0x00d7aaaf, 0x008feec7, 0x0050f584, 0x001b73e3, 0xffefa063, 0xffcd46ed, 0xffb3ddcd, 0xffa29aaa, 0xff988691, 0xff949066, + 0xff959d24, 0xff9a959e, 0xffa27195, 0xffac4011, 0xffb72d2b, 0xffc28569, 0xffcdb706, 0xffd85171, 0xffe20364, 0xffea97e9, 0xfff1f2b2, 0xfff80c06, 0xfffcec92, 0x0000a955, 0x00035fd8, 0x000532cf, + 0x00064735, 0x0006c1f9, 0x0006c62d, 0x000673ba, 0x0005e68f, 0x00053630, 0x000475a3, 0x0003b397, 0x0002fac1, 0x00025257, 0x0001be9e, 0x0001417a, 0x0000dafd, 0x000089eb, 0x00004c28, 0x00001f1d, + 0x00000000, 0xffffec10, 0xffffe0be, 0xffffdbc5, 0xffffdb39, 0xffffdd8b, 0xffffe182, 0xffffe638, 0xffffeb0a, 0xffffef8f, 0xfffff38b, 0xfffff6e3, 0xfffff993, 0xfffffba6, 0xfffffd30, 0xfffffe4a, + 0xffffff09, 0xffffff85, 0xffffffd1, 0xfffffffb, 0x0000000f, 0x00000016, 0x00000015, 0x00000012, 0x0000000d, 0x00000009, 0x00000006, 0x00000003, 0x00000002, 0x00000001, 0x00000000, 0x00000000, + 0x00000000 // this one is needed for lerping the last coefficient +}; + +// ---------------------------------------------------------------------------- + +static inline +int32_t mulRL(int left, int32_t in, uint32_t vRL) +{ +#if defined(__arm__) && !defined(__thumb__) + int32_t out; + if (left) { + asm( "smultb %[out], %[in], %[vRL] \n" + : [out]"=r"(out) + : [in]"%r"(in), [vRL]"r"(vRL) + : ); + } else { + asm( "smultt %[out], %[in], %[vRL] \n" + : [out]"=r"(out) + : [in]"%r"(in), [vRL]"r"(vRL) + : ); + } + return out; +#else + if (left) { + return int16_t(in>>16) * int16_t(vRL&0xFFFF); + } else { + return int16_t(in>>16) * int16_t(vRL>>16); + } +#endif +} + +static inline +int32_t mulAdd(int16_t in, int32_t v, int32_t a) +{ +#if defined(__arm__) && !defined(__thumb__) + int32_t out; + asm( "smlawb %[out], %[v], %[in], %[a] \n" + : [out]"=r"(out) + : [in]"%r"(in), [v]"r"(v), [a]"r"(a) + : ); + return out; +#else + return a + in * (v>>16); + // improved precision + // return a + in * (v>>16) + ((in * (v & 0xffff)) >> 16); +#endif +} + +static inline +int32_t mulAddRL(int left, uint32_t inRL, int32_t v, int32_t a) +{ +#if defined(__arm__) && !defined(__thumb__) + int32_t out; + if (left) { + asm( "smlawb %[out], %[v], %[inRL], %[a] \n" + : [out]"=r"(out) + : [inRL]"%r"(inRL), [v]"r"(v), [a]"r"(a) + : ); + } else { + asm( "smlawt %[out], %[v], %[inRL], %[a] \n" + : [out]"=r"(out) + : [inRL]"%r"(inRL), [v]"r"(v), [a]"r"(a) + : ); + } + return out; +#else + if (left) { + return a + (int16_t(inRL&0xFFFF) * (v>>16)); + //improved precision + // return a + (int16_t(inRL&0xFFFF) * (v>>16)) + ((int16_t(inRL&0xFFFF) * (v & 0xffff)) >> 16); + } else { + return a + (int16_t(inRL>>16) * (v>>16)); + } +#endif +} + +// ---------------------------------------------------------------------------- + +AudioResamplerSinc::AudioResamplerSinc(int bitDepth, + int inChannelCount, int32_t sampleRate) + : AudioResampler(bitDepth, inChannelCount, sampleRate), + mState(0) +{ + /* + * Layout of the state buffer for 32 tap: + * + * "present" sample beginning of 2nd buffer + * v v + * 0 01 2 23 3 + * 0 F0 0 F0 F + * [pppppppppppppppInnnnnnnnnnnnnnnnpppppppppppppppInnnnnnnnnnnnnnnn] + * ^ ^ head + * + * p = past samples, convoluted with the (p)ositive side of sinc() + * n = future samples, convoluted with the (n)egative side of sinc() + * r = extra space for implementing the ring buffer + * + */ + + const size_t numCoefs = 2*halfNumCoefs; + const size_t stateSize = numCoefs * inChannelCount * 2; + mState = new int16_t[stateSize]; + memset(mState, 0, sizeof(int16_t)*stateSize); + mImpulse = mState + (halfNumCoefs-1)*inChannelCount; + mRingFull = mImpulse + (numCoefs+1)*inChannelCount; +} + +AudioResamplerSinc::~AudioResamplerSinc() +{ + delete [] mState; +} + +void AudioResamplerSinc::init() { +} + +void AudioResamplerSinc::resample(int32_t* out, size_t outFrameCount, + AudioBufferProvider* provider) +{ + mFirCoefs = (mInSampleRate <= mSampleRate) ? mFirCoefsUp : mFirCoefsDown; + + // select the appropriate resampler + switch (mChannelCount) { + case 1: + resample<1>(out, outFrameCount, provider); + break; + case 2: + resample<2>(out, outFrameCount, provider); + break; + } +} + + +template<int CHANNELS> +void AudioResamplerSinc::resample(int32_t* out, size_t outFrameCount, + AudioBufferProvider* provider) +{ + int16_t* impulse = mImpulse; + uint32_t vRL = mVolumeRL; + size_t inputIndex = mInputIndex; + uint32_t phaseFraction = mPhaseFraction; + uint32_t phaseIncrement = mPhaseIncrement; + size_t outputIndex = 0; + size_t outputSampleCount = outFrameCount * 2; + size_t inFrameCount = (outFrameCount*mInSampleRate)/mSampleRate; + + AudioBufferProvider::Buffer& buffer(mBuffer); + while (outputIndex < outputSampleCount) { + // buffer is empty, fetch a new one + while (buffer.frameCount == 0) { + buffer.frameCount = inFrameCount; + provider->getNextBuffer(&buffer); + if (buffer.raw == NULL) { + goto resample_exit; + } + const uint32_t phaseIndex = phaseFraction >> kNumPhaseBits; + if (phaseIndex == 1) { + // read one frame + read<CHANNELS>(impulse, phaseFraction, buffer.i16, inputIndex); + } else if (phaseIndex == 2) { + // read 2 frames + read<CHANNELS>(impulse, phaseFraction, buffer.i16, inputIndex); + inputIndex++; + if (inputIndex >= mBuffer.frameCount) { + inputIndex -= mBuffer.frameCount; + provider->releaseBuffer(&buffer); + } else { + read<CHANNELS>(impulse, phaseFraction, buffer.i16, inputIndex); + } + } + } + int16_t *in = buffer.i16; + const size_t frameCount = buffer.frameCount; + + // Always read-in the first samples from the input buffer + int16_t* head = impulse + halfNumCoefs*CHANNELS; + head[0] = in[inputIndex*CHANNELS + 0]; + if (CHANNELS == 2) + head[1] = in[inputIndex*CHANNELS + 1]; + + // handle boundary case + int32_t l, r; + while (outputIndex < outputSampleCount) { + filterCoefficient<CHANNELS>(l, r, phaseFraction, impulse); + out[outputIndex++] += 2 * mulRL(1, l, vRL); + out[outputIndex++] += 2 * mulRL(0, r, vRL); + + phaseFraction += phaseIncrement; + const uint32_t phaseIndex = phaseFraction >> kNumPhaseBits; + if (phaseIndex == 1) { + inputIndex++; + if (inputIndex >= frameCount) + break; // need a new buffer + read<CHANNELS>(impulse, phaseFraction, in, inputIndex); + } else if(phaseIndex == 2) { // maximum value + inputIndex++; + if (inputIndex >= frameCount) + break; // 0 frame available, 2 frames needed + // read first frame + read<CHANNELS>(impulse, phaseFraction, in, inputIndex); + inputIndex++; + if (inputIndex >= frameCount) + break; // 0 frame available, 1 frame needed + // read second frame + read<CHANNELS>(impulse, phaseFraction, in, inputIndex); + } + } + + // if done with buffer, save samples + if (inputIndex >= frameCount) { + inputIndex -= frameCount; + provider->releaseBuffer(&buffer); + } + } + +resample_exit: + mImpulse = impulse; + mInputIndex = inputIndex; + mPhaseFraction = phaseFraction; +} + +template<int CHANNELS> +/*** +* read() +* +* This function reads only one frame from input buffer and writes it in +* state buffer +* +**/ +void AudioResamplerSinc::read( + int16_t*& impulse, uint32_t& phaseFraction, + int16_t const* in, size_t inputIndex) +{ + const uint32_t phaseIndex = phaseFraction >> kNumPhaseBits; + impulse += CHANNELS; + phaseFraction -= 1LU<<kNumPhaseBits; + if (impulse >= mRingFull) { + const size_t stateSize = (halfNumCoefs*2)*CHANNELS; + memcpy(mState, mState+stateSize, sizeof(int16_t)*stateSize); + impulse -= stateSize; + } + int16_t* head = impulse + halfNumCoefs*CHANNELS; + head[0] = in[inputIndex*CHANNELS + 0]; + if (CHANNELS == 2) + head[1] = in[inputIndex*CHANNELS + 1]; +} + +template<int CHANNELS> +void AudioResamplerSinc::filterCoefficient( + int32_t& l, int32_t& r, uint32_t phase, int16_t const *samples) +{ + // compute the index of the coefficient on the positive side and + // negative side + uint32_t indexP = (phase & cMask) >> cShift; + uint16_t lerpP = (phase & pMask) >> pShift; + uint32_t indexN = (-phase & cMask) >> cShift; + uint16_t lerpN = (-phase & pMask) >> pShift; + if ((indexP == 0) && (lerpP == 0)) { + indexN = cMask >> cShift; + lerpN = pMask >> pShift; + } + + l = 0; + r = 0; + int32_t const* coefs = mFirCoefs; + int16_t const *sP = samples; + int16_t const *sN = samples+CHANNELS; + for (unsigned int i=0 ; i<halfNumCoefs/4 ; i++) { + interpolate<CHANNELS>(l, r, coefs+indexP, lerpP, sP); + interpolate<CHANNELS>(l, r, coefs+indexN, lerpN, sN); + sP -= CHANNELS; sN += CHANNELS; coefs += 1<<coefsBits; + interpolate<CHANNELS>(l, r, coefs+indexP, lerpP, sP); + interpolate<CHANNELS>(l, r, coefs+indexN, lerpN, sN); + sP -= CHANNELS; sN += CHANNELS; coefs += 1<<coefsBits; + interpolate<CHANNELS>(l, r, coefs+indexP, lerpP, sP); + interpolate<CHANNELS>(l, r, coefs+indexN, lerpN, sN); + sP -= CHANNELS; sN += CHANNELS; coefs += 1<<coefsBits; + interpolate<CHANNELS>(l, r, coefs+indexP, lerpP, sP); + interpolate<CHANNELS>(l, r, coefs+indexN, lerpN, sN); + sP -= CHANNELS; sN += CHANNELS; coefs += 1<<coefsBits; + } +} + +template<int CHANNELS> +void AudioResamplerSinc::interpolate( + int32_t& l, int32_t& r, + int32_t const* coefs, int16_t lerp, int16_t const* samples) +{ + int32_t c0 = coefs[0]; + int32_t c1 = coefs[1]; + int32_t sinc = mulAdd(lerp, (c1-c0)<<1, c0); + if (CHANNELS == 2) { + uint32_t rl = *reinterpret_cast<uint32_t const*>(samples); + l = mulAddRL(1, rl, sinc, l); + r = mulAddRL(0, rl, sinc, r); + } else { + r = l = mulAdd(samples[0], sinc, l); + } +} + +// ---------------------------------------------------------------------------- +}; // namespace android + diff --git a/services/audioflinger/AudioResamplerSinc.h b/services/audioflinger/AudioResamplerSinc.h new file mode 100644 index 0000000..e6cb90b --- /dev/null +++ b/services/audioflinger/AudioResamplerSinc.h @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2007 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. + */ + +#ifndef ANDROID_AUDIO_RESAMPLER_SINC_H +#define ANDROID_AUDIO_RESAMPLER_SINC_H + +#include <stdint.h> +#include <sys/types.h> +#include <cutils/log.h> + +#include "AudioResampler.h" + +namespace android { + +// ---------------------------------------------------------------------------- + +class AudioResamplerSinc : public AudioResampler { +public: + AudioResamplerSinc(int bitDepth, int inChannelCount, int32_t sampleRate); + + ~AudioResamplerSinc(); + + virtual void resample(int32_t* out, size_t outFrameCount, + AudioBufferProvider* provider); +private: + void init(); + + template<int CHANNELS> + void resample(int32_t* out, size_t outFrameCount, + AudioBufferProvider* provider); + + template<int CHANNELS> + inline void filterCoefficient( + int32_t& l, int32_t& r, uint32_t phase, int16_t const *samples); + + template<int CHANNELS> + inline void interpolate( + int32_t& l, int32_t& r, + int32_t const* coefs, int16_t lerp, int16_t const* samples); + + template<int CHANNELS> + inline void read(int16_t*& impulse, uint32_t& phaseFraction, + int16_t const* in, size_t inputIndex); + + int16_t *mState; + int16_t *mImpulse; + int16_t *mRingFull; + + int32_t const * mFirCoefs; + static const int32_t mFirCoefsDown[]; + static const int32_t mFirCoefsUp[]; + + // ---------------------------------------------------------------------------- + static const int32_t RESAMPLE_FIR_NUM_COEF = 8; + static const int32_t RESAMPLE_FIR_LERP_INT_BITS = 4; + + // we have 16 coefs samples per zero-crossing + static const int coefsBits = RESAMPLE_FIR_LERP_INT_BITS; // 4 + static const int cShift = kNumPhaseBits - coefsBits; // 26 + static const uint32_t cMask = ((1<<coefsBits)-1) << cShift; // 0xf<<26 = 3c00 0000 + + // and we use 15 bits to interpolate between these samples + // this cannot change because the mul below rely on it. + static const int pLerpBits = 15; + static const int pShift = kNumPhaseBits - coefsBits - pLerpBits; // 11 + static const uint32_t pMask = ((1<<pLerpBits)-1) << pShift; // 0x7fff << 11 + + // number of zero-crossing on each side + static const unsigned int halfNumCoefs = RESAMPLE_FIR_NUM_COEF; +}; + +// ---------------------------------------------------------------------------- +}; // namespace android + +#endif /*ANDROID_AUDIO_RESAMPLER_SINC_H*/ diff --git a/services/camera/libcameraservice/Android.mk b/services/camera/libcameraservice/Android.mk new file mode 100644 index 0000000..df5c166 --- /dev/null +++ b/services/camera/libcameraservice/Android.mk @@ -0,0 +1,71 @@ +LOCAL_PATH:= $(call my-dir) + +# +# Set USE_CAMERA_STUB for non-emulator and non-simulator builds, if you want +# the camera service to use the fake camera. For emulator or simulator builds, +# we always use the fake camera. + +ifeq ($(USE_CAMERA_STUB),) +USE_CAMERA_STUB:=false +ifneq ($(filter sooner generic sim,$(TARGET_DEVICE)),) +USE_CAMERA_STUB:=true +endif #libcamerastub +endif + +ifeq ($(USE_CAMERA_STUB),true) +# +# libcamerastub +# + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + CameraHardwareStub.cpp \ + FakeCamera.cpp + +LOCAL_MODULE:= libcamerastub + +ifeq ($(TARGET_SIMULATOR),true) +LOCAL_CFLAGS += -DSINGLE_PROCESS +endif + +LOCAL_SHARED_LIBRARIES:= libui + +include $(BUILD_STATIC_LIBRARY) +endif # USE_CAMERA_STUB + +# +# libcameraservice +# + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + CameraService.cpp + +LOCAL_SHARED_LIBRARIES:= \ + libui \ + libutils \ + libbinder \ + libcutils \ + libmedia \ + libcamera_client \ + libsurfaceflinger_client + +LOCAL_MODULE:= libcameraservice + +LOCAL_CFLAGS += -DLOG_TAG=\"CameraService\" + +ifeq ($(TARGET_SIMULATOR),true) +LOCAL_CFLAGS += -DSINGLE_PROCESS +endif + +ifeq ($(USE_CAMERA_STUB), true) +LOCAL_STATIC_LIBRARIES += libcamerastub +LOCAL_CFLAGS += -include CameraHardwareStub.h +else +LOCAL_SHARED_LIBRARIES += libcamera +endif + +include $(BUILD_SHARED_LIBRARY) + diff --git a/services/camera/libcameraservice/CameraHardwareStub.cpp b/services/camera/libcameraservice/CameraHardwareStub.cpp new file mode 100644 index 0000000..8b66389 --- /dev/null +++ b/services/camera/libcameraservice/CameraHardwareStub.cpp @@ -0,0 +1,402 @@ +/* +** +** Copyright 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. +*/ + +#define LOG_TAG "CameraHardwareStub" +#include <utils/Log.h> + +#include "CameraHardwareStub.h" +#include <utils/threads.h> +#include <fcntl.h> +#include <sys/mman.h> + +#include "CannedJpeg.h" + +namespace android { + +CameraHardwareStub::CameraHardwareStub() + : mParameters(), + mPreviewHeap(0), + mRawHeap(0), + mFakeCamera(0), + mPreviewFrameSize(0), + mNotifyCb(0), + mDataCb(0), + mDataCbTimestamp(0), + mCallbackCookie(0), + mMsgEnabled(0), + mCurrentPreviewFrame(0) +{ + initDefaultParameters(); +} + +void CameraHardwareStub::initDefaultParameters() +{ + CameraParameters p; + + p.set("preview-size-values","320x240"); + p.setPreviewSize(320, 240); + p.setPreviewFrameRate(15); + p.setPreviewFormat("yuv422sp"); + + p.set("picture-size-values", "320x240"); + p.setPictureSize(320, 240); + p.setPictureFormat("jpeg"); + + if (setParameters(p) != NO_ERROR) { + LOGE("Failed to set default parameters?!"); + } +} + +void CameraHardwareStub::initHeapLocked() +{ + // Create raw heap. + int picture_width, picture_height; + mParameters.getPictureSize(&picture_width, &picture_height); + mRawHeap = new MemoryHeapBase(picture_width * 2 * picture_height); + + int preview_width, preview_height; + mParameters.getPreviewSize(&preview_width, &preview_height); + LOGD("initHeapLocked: preview size=%dx%d", preview_width, preview_height); + + // Note that we enforce yuv422 in setParameters(). + int how_big = preview_width * preview_height * 2; + + // If we are being reinitialized to the same size as before, no + // work needs to be done. + if (how_big == mPreviewFrameSize) + return; + + mPreviewFrameSize = how_big; + + // Make a new mmap'ed heap that can be shared across processes. + // use code below to test with pmem + mPreviewHeap = new MemoryHeapBase(mPreviewFrameSize * kBufferCount); + // Make an IMemory for each frame so that we can reuse them in callbacks. + for (int i = 0; i < kBufferCount; i++) { + mBuffers[i] = new MemoryBase(mPreviewHeap, i * mPreviewFrameSize, mPreviewFrameSize); + } + + // Recreate the fake camera to reflect the current size. + delete mFakeCamera; + mFakeCamera = new FakeCamera(preview_width, preview_height); +} + +CameraHardwareStub::~CameraHardwareStub() +{ + delete mFakeCamera; + mFakeCamera = 0; // paranoia + singleton.clear(); +} + +sp<IMemoryHeap> CameraHardwareStub::getPreviewHeap() const +{ + return mPreviewHeap; +} + +sp<IMemoryHeap> CameraHardwareStub::getRawHeap() const +{ + return mRawHeap; +} + +void CameraHardwareStub::setCallbacks(notify_callback notify_cb, + data_callback data_cb, + data_callback_timestamp data_cb_timestamp, + void* user) +{ + Mutex::Autolock lock(mLock); + mNotifyCb = notify_cb; + mDataCb = data_cb; + mDataCbTimestamp = data_cb_timestamp; + mCallbackCookie = user; +} + +void CameraHardwareStub::enableMsgType(int32_t msgType) +{ + Mutex::Autolock lock(mLock); + mMsgEnabled |= msgType; +} + +void CameraHardwareStub::disableMsgType(int32_t msgType) +{ + Mutex::Autolock lock(mLock); + mMsgEnabled &= ~msgType; +} + +bool CameraHardwareStub::msgTypeEnabled(int32_t msgType) +{ + Mutex::Autolock lock(mLock); + return (mMsgEnabled & msgType); +} + +// --------------------------------------------------------------------------- + +int CameraHardwareStub::previewThread() +{ + mLock.lock(); + // the attributes below can change under our feet... + + int previewFrameRate = mParameters.getPreviewFrameRate(); + + // Find the offset within the heap of the current buffer. + ssize_t offset = mCurrentPreviewFrame * mPreviewFrameSize; + + sp<MemoryHeapBase> heap = mPreviewHeap; + + // this assumes the internal state of fake camera doesn't change + // (or is thread safe) + FakeCamera* fakeCamera = mFakeCamera; + + sp<MemoryBase> buffer = mBuffers[mCurrentPreviewFrame]; + + mLock.unlock(); + + // TODO: here check all the conditions that could go wrong + if (buffer != 0) { + // Calculate how long to wait between frames. + int delay = (int)(1000000.0f / float(previewFrameRate)); + + // This is always valid, even if the client died -- the memory + // is still mapped in our process. + void *base = heap->base(); + + // Fill the current frame with the fake camera. + uint8_t *frame = ((uint8_t *)base) + offset; + fakeCamera->getNextFrameAsYuv422(frame); + + //LOGV("previewThread: generated frame to buffer %d", mCurrentPreviewFrame); + + // Notify the client of a new frame. + if (mMsgEnabled & CAMERA_MSG_PREVIEW_FRAME) + mDataCb(CAMERA_MSG_PREVIEW_FRAME, buffer, mCallbackCookie); + + // Advance the buffer pointer. + mCurrentPreviewFrame = (mCurrentPreviewFrame + 1) % kBufferCount; + + // Wait for it... + usleep(delay); + } + + return NO_ERROR; +} + +status_t CameraHardwareStub::startPreview() +{ + Mutex::Autolock lock(mLock); + if (mPreviewThread != 0) { + // already running + return INVALID_OPERATION; + } + mPreviewThread = new PreviewThread(this); + return NO_ERROR; +} + +void CameraHardwareStub::stopPreview() +{ + sp<PreviewThread> previewThread; + + { // scope for the lock + Mutex::Autolock lock(mLock); + previewThread = mPreviewThread; + } + + // don't hold the lock while waiting for the thread to quit + if (previewThread != 0) { + previewThread->requestExitAndWait(); + } + + Mutex::Autolock lock(mLock); + mPreviewThread.clear(); +} + +bool CameraHardwareStub::previewEnabled() { + return mPreviewThread != 0; +} + +status_t CameraHardwareStub::startRecording() +{ + return UNKNOWN_ERROR; +} + +void CameraHardwareStub::stopRecording() +{ +} + +bool CameraHardwareStub::recordingEnabled() +{ + return false; +} + +void CameraHardwareStub::releaseRecordingFrame(const sp<IMemory>& mem) +{ +} + +// --------------------------------------------------------------------------- + +int CameraHardwareStub::beginAutoFocusThread(void *cookie) +{ + CameraHardwareStub *c = (CameraHardwareStub *)cookie; + return c->autoFocusThread(); +} + +int CameraHardwareStub::autoFocusThread() +{ + if (mMsgEnabled & CAMERA_MSG_FOCUS) + mNotifyCb(CAMERA_MSG_FOCUS, true, 0, mCallbackCookie); + return NO_ERROR; +} + +status_t CameraHardwareStub::autoFocus() +{ + Mutex::Autolock lock(mLock); + if (createThread(beginAutoFocusThread, this) == false) + return UNKNOWN_ERROR; + return NO_ERROR; +} + +status_t CameraHardwareStub::cancelAutoFocus() +{ + return NO_ERROR; +} + +/*static*/ int CameraHardwareStub::beginPictureThread(void *cookie) +{ + CameraHardwareStub *c = (CameraHardwareStub *)cookie; + return c->pictureThread(); +} + +int CameraHardwareStub::pictureThread() +{ + if (mMsgEnabled & CAMERA_MSG_SHUTTER) + mNotifyCb(CAMERA_MSG_SHUTTER, 0, 0, mCallbackCookie); + + if (mMsgEnabled & CAMERA_MSG_RAW_IMAGE) { + //FIXME: use a canned YUV image! + // In the meantime just make another fake camera picture. + int w, h; + mParameters.getPictureSize(&w, &h); + sp<MemoryBase> mem = new MemoryBase(mRawHeap, 0, w * 2 * h); + FakeCamera cam(w, h); + cam.getNextFrameAsYuv422((uint8_t *)mRawHeap->base()); + mDataCb(CAMERA_MSG_RAW_IMAGE, mem, mCallbackCookie); + } + + if (mMsgEnabled & CAMERA_MSG_COMPRESSED_IMAGE) { + sp<MemoryHeapBase> heap = new MemoryHeapBase(kCannedJpegSize); + sp<MemoryBase> mem = new MemoryBase(heap, 0, kCannedJpegSize); + memcpy(heap->base(), kCannedJpeg, kCannedJpegSize); + mDataCb(CAMERA_MSG_COMPRESSED_IMAGE, mem, mCallbackCookie); + } + return NO_ERROR; +} + +status_t CameraHardwareStub::takePicture() +{ + stopPreview(); + if (createThread(beginPictureThread, this) == false) + return -1; + return NO_ERROR; +} + +status_t CameraHardwareStub::cancelPicture() +{ + return NO_ERROR; +} + +status_t CameraHardwareStub::dump(int fd, const Vector<String16>& args) const +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + AutoMutex lock(&mLock); + if (mFakeCamera != 0) { + mFakeCamera->dump(fd); + mParameters.dump(fd, args); + snprintf(buffer, 255, " preview frame(%d), size (%d), running(%s)\n", mCurrentPreviewFrame, mPreviewFrameSize, mPreviewRunning?"true": "false"); + result.append(buffer); + } else { + result.append("No camera client yet.\n"); + } + write(fd, result.string(), result.size()); + return NO_ERROR; +} + +status_t CameraHardwareStub::setParameters(const CameraParameters& params) +{ + Mutex::Autolock lock(mLock); + // XXX verify params + + if (strcmp(params.getPreviewFormat(), "yuv422sp") != 0) { + LOGE("Only yuv422sp preview is supported"); + return -1; + } + + if (strcmp(params.getPictureFormat(), "jpeg") != 0) { + LOGE("Only jpeg still pictures are supported"); + return -1; + } + + int w, h; + params.getPictureSize(&w, &h); + if (w != kCannedJpegWidth && h != kCannedJpegHeight) { + LOGE("Still picture size must be size of canned JPEG (%dx%d)", + kCannedJpegWidth, kCannedJpegHeight); + return -1; + } + + mParameters = params; + initHeapLocked(); + + return NO_ERROR; +} + +CameraParameters CameraHardwareStub::getParameters() const +{ + Mutex::Autolock lock(mLock); + return mParameters; +} + +status_t CameraHardwareStub::sendCommand(int32_t command, int32_t arg1, + int32_t arg2) +{ + return BAD_VALUE; +} + +void CameraHardwareStub::release() +{ +} + +wp<CameraHardwareInterface> CameraHardwareStub::singleton; + +sp<CameraHardwareInterface> CameraHardwareStub::createInstance() +{ + if (singleton != 0) { + sp<CameraHardwareInterface> hardware = singleton.promote(); + if (hardware != 0) { + return hardware; + } + } + sp<CameraHardwareInterface> hardware(new CameraHardwareStub()); + singleton = hardware; + return hardware; +} + +extern "C" sp<CameraHardwareInterface> openCameraHardware() +{ + return CameraHardwareStub::createInstance(); +} + +}; // namespace android diff --git a/services/camera/libcameraservice/CameraHardwareStub.h b/services/camera/libcameraservice/CameraHardwareStub.h new file mode 100644 index 0000000..957813a --- /dev/null +++ b/services/camera/libcameraservice/CameraHardwareStub.h @@ -0,0 +1,135 @@ +/* +** +** Copyright 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. +*/ + +#ifndef ANDROID_HARDWARE_CAMERA_HARDWARE_STUB_H +#define ANDROID_HARDWARE_CAMERA_HARDWARE_STUB_H + +#include "FakeCamera.h" +#include <utils/threads.h> +#include <camera/CameraHardwareInterface.h> +#include <binder/MemoryBase.h> +#include <binder/MemoryHeapBase.h> +#include <utils/threads.h> + +namespace android { + +class CameraHardwareStub : public CameraHardwareInterface { +public: + virtual sp<IMemoryHeap> getPreviewHeap() const; + virtual sp<IMemoryHeap> getRawHeap() const; + + virtual void setCallbacks(notify_callback notify_cb, + data_callback data_cb, + data_callback_timestamp data_cb_timestamp, + void* user); + + virtual void enableMsgType(int32_t msgType); + virtual void disableMsgType(int32_t msgType); + virtual bool msgTypeEnabled(int32_t msgType); + + virtual status_t startPreview(); + virtual void stopPreview(); + virtual bool previewEnabled(); + + virtual status_t startRecording(); + virtual void stopRecording(); + virtual bool recordingEnabled(); + virtual void releaseRecordingFrame(const sp<IMemory>& mem); + + virtual status_t autoFocus(); + virtual status_t cancelAutoFocus(); + virtual status_t takePicture(); + virtual status_t cancelPicture(); + virtual status_t dump(int fd, const Vector<String16>& args) const; + virtual status_t setParameters(const CameraParameters& params); + virtual CameraParameters getParameters() const; + virtual status_t sendCommand(int32_t command, int32_t arg1, + int32_t arg2); + virtual void release(); + + static sp<CameraHardwareInterface> createInstance(); + +private: + CameraHardwareStub(); + virtual ~CameraHardwareStub(); + + static wp<CameraHardwareInterface> singleton; + + static const int kBufferCount = 4; + + class PreviewThread : public Thread { + CameraHardwareStub* mHardware; + public: + PreviewThread(CameraHardwareStub* hw) : +#ifdef SINGLE_PROCESS + // In single process mode this thread needs to be a java thread, + // since we won't be calling through the binder. + Thread(true), +#else + Thread(false), +#endif + mHardware(hw) { } + virtual void onFirstRef() { + run("CameraPreviewThread", PRIORITY_URGENT_DISPLAY); + } + virtual bool threadLoop() { + mHardware->previewThread(); + // loop until we need to quit + return true; + } + }; + + void initDefaultParameters(); + void initHeapLocked(); + + int previewThread(); + + static int beginAutoFocusThread(void *cookie); + int autoFocusThread(); + + static int beginPictureThread(void *cookie); + int pictureThread(); + + mutable Mutex mLock; + + CameraParameters mParameters; + + sp<MemoryHeapBase> mPreviewHeap; + sp<MemoryHeapBase> mRawHeap; + sp<MemoryBase> mBuffers[kBufferCount]; + + FakeCamera *mFakeCamera; + bool mPreviewRunning; + int mPreviewFrameSize; + + // protected by mLock + sp<PreviewThread> mPreviewThread; + + notify_callback mNotifyCb; + data_callback mDataCb; + data_callback_timestamp mDataCbTimestamp; + void *mCallbackCookie; + + int32_t mMsgEnabled; + + // only used from PreviewThread + int mCurrentPreviewFrame; +}; + +}; // namespace android + +#endif diff --git a/services/camera/libcameraservice/CameraService.cpp b/services/camera/libcameraservice/CameraService.cpp new file mode 100644 index 0000000..00bd54e --- /dev/null +++ b/services/camera/libcameraservice/CameraService.cpp @@ -0,0 +1,1417 @@ +/* +** +** Copyright (C) 2008, The Android Open Source Project +** Copyright (C) 2008 HTC Inc. +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#define LOG_TAG "CameraService" +#include <utils/Log.h> + +#include <binder/IServiceManager.h> +#include <binder/IPCThreadState.h> +#include <utils/String16.h> +#include <utils/Errors.h> +#include <binder/MemoryBase.h> +#include <binder/MemoryHeapBase.h> +#include <camera/ICameraService.h> +#include <surfaceflinger/ISurface.h> +#include <ui/Overlay.h> + +#include <hardware/hardware.h> + +#include <media/mediaplayer.h> +#include <media/AudioSystem.h> +#include "CameraService.h" + +#include <cutils/atomic.h> + +namespace android { + +extern "C" { +#include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <pthread.h> +#include <signal.h> +} + +// When you enable this, as well as DEBUG_REFS=1 and +// DEBUG_REFS_ENABLED_BY_DEFAULT=0 in libutils/RefBase.cpp, this will track all +// references to the CameraService::Client in order to catch the case where the +// client is being destroyed while a callback from the CameraHardwareInterface +// is outstanding. This is a serious bug because if we make another call into +// CameraHardwreInterface that itself triggers a callback, we will deadlock. + +#define DEBUG_CLIENT_REFERENCES 0 + +#define PICTURE_TIMEOUT seconds(5) + +#define DEBUG_DUMP_PREVIEW_FRAME_TO_FILE 0 /* n-th frame to write */ +#define DEBUG_DUMP_JPEG_SNAPSHOT_TO_FILE 0 +#define DEBUG_DUMP_YUV_SNAPSHOT_TO_FILE 0 +#define DEBUG_DUMP_POSTVIEW_SNAPSHOT_TO_FILE 0 + +#if DEBUG_DUMP_PREVIEW_FRAME_TO_FILE +static int debug_frame_cnt; +#endif + +static int getCallingPid() { + return IPCThreadState::self()->getCallingPid(); +} + +// ---------------------------------------------------------------------------- + +void CameraService::instantiate() { + defaultServiceManager()->addService( + String16("media.camera"), new CameraService()); +} + +// ---------------------------------------------------------------------------- + +CameraService::CameraService() : + BnCameraService() +{ + LOGI("CameraService started: pid=%d", getpid()); + mUsers = 0; +} + +CameraService::~CameraService() +{ + if (mClient != 0) { + LOGE("mClient was still connected in destructor!"); + } +} + +sp<ICamera> CameraService::connect(const sp<ICameraClient>& cameraClient) +{ + int callingPid = getCallingPid(); + LOGV("CameraService::connect E (pid %d, client %p)", callingPid, + cameraClient->asBinder().get()); + + Mutex::Autolock lock(mServiceLock); + sp<Client> client; + if (mClient != 0) { + sp<Client> currentClient = mClient.promote(); + if (currentClient != 0) { + sp<ICameraClient> currentCameraClient(currentClient->getCameraClient()); + if (cameraClient->asBinder() == currentCameraClient->asBinder()) { + // This is the same client reconnecting... + LOGV("CameraService::connect X (pid %d, same client %p) is reconnecting...", + callingPid, cameraClient->asBinder().get()); + return currentClient; + } else { + // It's another client... reject it + LOGV("CameraService::connect X (pid %d, new client %p) rejected. " + "(old pid %d, old client %p)", + callingPid, cameraClient->asBinder().get(), + currentClient->mClientPid, currentCameraClient->asBinder().get()); + if (kill(currentClient->mClientPid, 0) == -1 && errno == ESRCH) { + LOGV("The old client is dead!"); + } + return client; + } + } else { + // can't promote, the previous client has died... + LOGV("New client (pid %d) connecting, old reference was dangling...", + callingPid); + mClient.clear(); + } + } + + if (mUsers > 0) { + LOGV("Still have client, rejected"); + return client; + } + + // create a new Client object + client = new Client(this, cameraClient, callingPid); + mClient = client; +#if DEBUG_CLIENT_REFERENCES + // Enable tracking for this object, and track increments and decrements of + // the refcount. + client->trackMe(true, true); +#endif + LOGV("CameraService::connect X"); + return client; +} + +void CameraService::removeClient(const sp<ICameraClient>& cameraClient) +{ + int callingPid = getCallingPid(); + + // Declare this outside the lock to make absolutely sure the + // destructor won't be called with the lock held. + sp<Client> client; + + Mutex::Autolock lock(mServiceLock); + + if (mClient == 0) { + // This happens when we have already disconnected. + LOGV("removeClient (pid %d): already disconnected", callingPid); + return; + } + + // Promote mClient. It can fail if we are called from this path: + // Client::~Client() -> disconnect() -> removeClient(). + client = mClient.promote(); + if (client == 0) { + LOGV("removeClient (pid %d): no more strong reference", callingPid); + mClient.clear(); + return; + } + + if (cameraClient->asBinder() != client->getCameraClient()->asBinder()) { + // ugh! that's not our client!! + LOGW("removeClient (pid %d): mClient doesn't match!", callingPid); + } else { + // okay, good, forget about mClient + mClient.clear(); + } + + LOGV("removeClient (pid %d) done", callingPid); +} + +// The reason we need this count is a new CameraService::connect() request may +// come in while the previous Client's destructor has not been run or is still +// running. If the last strong reference of the previous Client is gone but +// destructor has not been run, we should not allow the new Client to be created +// because we need to wait for the previous Client to tear down the hardware +// first. +void CameraService::incUsers() { + android_atomic_inc(&mUsers); +} + +void CameraService::decUsers() { + android_atomic_dec(&mUsers); +} + +static sp<MediaPlayer> newMediaPlayer(const char *file) +{ + sp<MediaPlayer> mp = new MediaPlayer(); + if (mp->setDataSource(file, NULL /* headers */) == NO_ERROR) { + mp->setAudioStreamType(AudioSystem::ENFORCED_AUDIBLE); + mp->prepare(); + } else { + mp.clear(); + LOGE("Failed to load CameraService sounds."); + } + return mp; +} + +CameraService::Client::Client(const sp<CameraService>& cameraService, + const sp<ICameraClient>& cameraClient, pid_t clientPid) +{ + int callingPid = getCallingPid(); + LOGV("Client::Client E (pid %d)", callingPid); + mCameraService = cameraService; + mCameraClient = cameraClient; + mClientPid = clientPid; + mHardware = openCameraHardware(); + mUseOverlay = mHardware->useOverlay(); + + mHardware->setCallbacks(notifyCallback, + dataCallback, + dataCallbackTimestamp, + mCameraService.get()); + + // Enable zoom, error, and focus messages by default + mHardware->enableMsgType(CAMERA_MSG_ERROR | + CAMERA_MSG_ZOOM | + CAMERA_MSG_FOCUS); + + mMediaPlayerClick = newMediaPlayer("/system/media/audio/ui/camera_click.ogg"); + mMediaPlayerBeep = newMediaPlayer("/system/media/audio/ui/VideoRecord.ogg"); + mOverlayW = 0; + mOverlayH = 0; + + // Callback is disabled by default + mPreviewCallbackFlag = FRAME_CALLBACK_FLAG_NOOP; + mOrientation = 0; + cameraService->incUsers(); + LOGV("Client::Client X (pid %d)", callingPid); +} + +status_t CameraService::Client::checkPid() +{ + int callingPid = getCallingPid(); + if (mClientPid == callingPid) return NO_ERROR; + LOGW("Attempt to use locked camera (client %p) from different process " + " (old pid %d, new pid %d)", + getCameraClient()->asBinder().get(), mClientPid, callingPid); + return -EBUSY; +} + +status_t CameraService::Client::lock() +{ + int callingPid = getCallingPid(); + LOGV("lock from pid %d (mClientPid %d)", callingPid, mClientPid); + Mutex::Autolock _l(mLock); + // lock camera to this client if the the camera is unlocked + if (mClientPid == 0) { + mClientPid = callingPid; + return NO_ERROR; + } + // returns NO_ERROR if the client already owns the camera, -EBUSY otherwise + return checkPid(); +} + +status_t CameraService::Client::unlock() +{ + int callingPid = getCallingPid(); + LOGV("unlock from pid %d (mClientPid %d)", callingPid, mClientPid); + Mutex::Autolock _l(mLock); + // allow anyone to use camera + status_t result = checkPid(); + if (result == NO_ERROR) { + mClientPid = 0; + LOGV("clear mCameraClient (pid %d)", callingPid); + // we need to remove the reference so that when app goes + // away, the reference count goes to 0. + mCameraClient.clear(); + } + return result; +} + +status_t CameraService::Client::connect(const sp<ICameraClient>& client) +{ + int callingPid = getCallingPid(); + + // connect a new process to the camera + LOGV("Client::connect E (pid %d, client %p)", callingPid, client->asBinder().get()); + + // I hate this hack, but things get really ugly when the media recorder + // service is handing back the camera to the app. The ICameraClient + // destructor will be called during the same IPC, making it look like + // the remote client is trying to disconnect. This hack temporarily + // sets the mClientPid to an invalid pid to prevent the hardware from + // being torn down. + { + + // hold a reference to the old client or we will deadlock if the client is + // in the same process and we hold the lock when we remove the reference + sp<ICameraClient> oldClient; + { + Mutex::Autolock _l(mLock); + if (mClientPid != 0 && checkPid() != NO_ERROR) { + LOGW("Tried to connect to locked camera (old pid %d, new pid %d)", + mClientPid, callingPid); + return -EBUSY; + } + oldClient = mCameraClient; + + // did the client actually change? + if ((mCameraClient != NULL) && (client->asBinder() == mCameraClient->asBinder())) { + LOGV("Connect to the same client"); + return NO_ERROR; + } + + mCameraClient = client; + mClientPid = -1; + mPreviewCallbackFlag = FRAME_CALLBACK_FLAG_NOOP; + LOGV("Connect to the new client (pid %d, client %p)", + callingPid, mCameraClient->asBinder().get()); + } + + } + // the old client destructor is called when oldClient goes out of scope + // now we set the new PID to lock the interface again + mClientPid = callingPid; + + return NO_ERROR; +} + +#if HAVE_ANDROID_OS +static void *unregister_surface(void *arg) +{ + ISurface *surface = (ISurface *)arg; + surface->unregisterBuffers(); + IPCThreadState::self()->flushCommands(); + return NULL; +} +#endif + +CameraService::Client::~Client() +{ + int callingPid = getCallingPid(); + + // tear down client + LOGV("Client::~Client E (pid %d, client %p)", + callingPid, getCameraClient()->asBinder().get()); + if (mSurface != 0 && !mUseOverlay) { +#if HAVE_ANDROID_OS + pthread_t thr; + // We unregister the buffers in a different thread because binder does + // not let us make sychronous transactions in a binder destructor (that + // is, upon our reaching a refcount of zero.) + pthread_create(&thr, NULL, + unregister_surface, + mSurface.get()); + pthread_join(thr, NULL); +#else + mSurface->unregisterBuffers(); +#endif + } + + if (mMediaPlayerBeep.get() != NULL) { + mMediaPlayerBeep->disconnect(); + mMediaPlayerBeep.clear(); + } + if (mMediaPlayerClick.get() != NULL) { + mMediaPlayerClick->disconnect(); + mMediaPlayerClick.clear(); + } + + // make sure we tear down the hardware + mClientPid = callingPid; + disconnect(); + LOGV("Client::~Client X (pid %d)", mClientPid); +} + +void CameraService::Client::disconnect() +{ + int callingPid = getCallingPid(); + + LOGV("Client::disconnect() E (pid %d client %p)", + callingPid, getCameraClient()->asBinder().get()); + + Mutex::Autolock lock(mLock); + if (mClientPid <= 0) { + LOGV("camera is unlocked (mClientPid = %d), don't tear down hardware", mClientPid); + return; + } + if (checkPid() != NO_ERROR) { + LOGV("Different client - don't disconnect"); + return; + } + + // Make sure disconnect() is done once and once only, whether it is called + // from the user directly, or called by the destructor. + if (mHardware == 0) return; + + LOGV("hardware teardown"); + // Before destroying mHardware, we must make sure it's in the + // idle state. + mHardware->stopPreview(); + // Cancel all picture callbacks. + mHardware->disableMsgType(CAMERA_MSG_SHUTTER | + CAMERA_MSG_POSTVIEW_FRAME | + CAMERA_MSG_RAW_IMAGE | + CAMERA_MSG_COMPRESSED_IMAGE); + mHardware->cancelPicture(); + // Turn off remaining messages. + mHardware->disableMsgType(CAMERA_MSG_ALL_MSGS); + // Release the hardware resources. + mHardware->release(); + // Release the held overlay resources. + if (mUseOverlay) + { + mOverlayRef = 0; + } + mHardware.clear(); + + mCameraService->removeClient(mCameraClient); + mCameraService->decUsers(); + + LOGV("Client::disconnect() X (pid %d)", callingPid); +} + +// pass the buffered ISurface to the camera service +status_t CameraService::Client::setPreviewDisplay(const sp<ISurface>& surface) +{ + LOGV("setPreviewDisplay(%p) (pid %d)", + ((surface == NULL) ? NULL : surface.get()), getCallingPid()); + Mutex::Autolock lock(mLock); + status_t result = checkPid(); + if (result != NO_ERROR) return result; + + Mutex::Autolock surfaceLock(mSurfaceLock); + result = NO_ERROR; + // asBinder() is safe on NULL (returns NULL) + if (surface->asBinder() != mSurface->asBinder()) { + if (mSurface != 0) { + LOGV("clearing old preview surface %p", mSurface.get()); + if ( !mUseOverlay) + { + mSurface->unregisterBuffers(); + } + else + { + // Force the destruction of any previous overlay + sp<Overlay> dummy; + mHardware->setOverlay( dummy ); + } + } + mSurface = surface; + mOverlayRef = 0; + // If preview has been already started, set overlay or register preview + // buffers now. + if (mHardware->previewEnabled()) { + if (mUseOverlay) { + result = setOverlay(); + } else if (mSurface != 0) { + result = registerPreviewBuffers(); + } + } + } + return result; +} + +// set the preview callback flag to affect how the received frames from +// preview are handled. +void CameraService::Client::setPreviewCallbackFlag(int callback_flag) +{ + LOGV("setPreviewCallbackFlag (pid %d)", getCallingPid()); + Mutex::Autolock lock(mLock); + if (checkPid() != NO_ERROR) return; + mPreviewCallbackFlag = callback_flag; + + if(mUseOverlay) { + if(mPreviewCallbackFlag & FRAME_CALLBACK_FLAG_ENABLE_MASK) + mHardware->enableMsgType(CAMERA_MSG_PREVIEW_FRAME); + else + mHardware->disableMsgType(CAMERA_MSG_PREVIEW_FRAME); + } +} + +// start preview mode +status_t CameraService::Client::startCameraMode(camera_mode mode) +{ + int callingPid = getCallingPid(); + + LOGV("startCameraMode(%d) (pid %d)", mode, callingPid); + + /* we cannot call into mHardware with mLock held because + * mHardware has callbacks onto us which acquire this lock + */ + + Mutex::Autolock lock(mLock); + status_t result = checkPid(); + if (result != NO_ERROR) return result; + + if (mHardware == 0) { + LOGE("mHardware is NULL, returning."); + return INVALID_OPERATION; + } + + switch(mode) { + case CAMERA_RECORDING_MODE: + if (mSurface == 0) { + LOGE("setPreviewDisplay must be called before startRecordingMode."); + return INVALID_OPERATION; + } + return startRecordingMode(); + + default: // CAMERA_PREVIEW_MODE + if (mSurface == 0) { + LOGV("mSurface is not set yet."); + } + return startPreviewMode(); + } +} + +status_t CameraService::Client::startRecordingMode() +{ + LOGV("startRecordingMode (pid %d)", getCallingPid()); + + status_t ret = UNKNOWN_ERROR; + + // if preview has not been started, start preview first + if (!mHardware->previewEnabled()) { + ret = startPreviewMode(); + if (ret != NO_ERROR) { + return ret; + } + } + + // if recording has been enabled, nothing needs to be done + if (mHardware->recordingEnabled()) { + return NO_ERROR; + } + + // start recording mode + ret = mHardware->startRecording(); + if (ret != NO_ERROR) { + LOGE("mHardware->startRecording() failed with status %d", ret); + } + return ret; +} + +status_t CameraService::Client::setOverlay() +{ + LOGV("setOverlay"); + int w, h; + CameraParameters params(mHardware->getParameters()); + params.getPreviewSize(&w, &h); + + if ( w != mOverlayW || h != mOverlayH ) + { + // Force the destruction of any previous overlay + sp<Overlay> dummy; + mHardware->setOverlay( dummy ); + mOverlayRef = 0; + } + + status_t ret = NO_ERROR; + if (mSurface != 0) { + if (mOverlayRef.get() == NULL) { + + // FIXME: + // Surfaceflinger may hold onto the previous overlay reference for some + // time after we try to destroy it. retry a few times. In the future, we + // should make the destroy call block, or possibly specify that we can + // wait in the createOverlay call if the previous overlay is in the + // process of being destroyed. + for (int retry = 0; retry < 50; ++retry) { + mOverlayRef = mSurface->createOverlay(w, h, OVERLAY_FORMAT_DEFAULT, + mOrientation); + if (mOverlayRef != NULL) break; + LOGW("Overlay create failed - retrying"); + usleep(20000); + } + if ( mOverlayRef.get() == NULL ) + { + LOGE("Overlay Creation Failed!"); + return -EINVAL; + } + ret = mHardware->setOverlay(new Overlay(mOverlayRef)); + } + } else { + ret = mHardware->setOverlay(NULL); + } + if (ret != NO_ERROR) { + LOGE("mHardware->setOverlay() failed with status %d\n", ret); + } + + mOverlayW = w; + mOverlayH = h; + + return ret; +} + +status_t CameraService::Client::registerPreviewBuffers() +{ + int w, h; + CameraParameters params(mHardware->getParameters()); + params.getPreviewSize(&w, &h); + + // don't use a hardcoded format here + ISurface::BufferHeap buffers(w, h, w, h, + HAL_PIXEL_FORMAT_YCrCb_420_SP, + mOrientation, + 0, + mHardware->getPreviewHeap()); + + status_t ret = mSurface->registerBuffers(buffers); + if (ret != NO_ERROR) { + LOGE("registerBuffers failed with status %d", ret); + } + return ret; +} + +status_t CameraService::Client::startPreviewMode() +{ + LOGV("startPreviewMode (pid %d)", getCallingPid()); + + // if preview has been enabled, nothing needs to be done + if (mHardware->previewEnabled()) { + return NO_ERROR; + } + + // start preview mode +#if DEBUG_DUMP_PREVIEW_FRAME_TO_FILE + debug_frame_cnt = 0; +#endif + status_t ret = NO_ERROR; + + if (mUseOverlay) { + // If preview display has been set, set overlay now. + if (mSurface != 0) { + ret = setOverlay(); + } + if (ret != NO_ERROR) return ret; + ret = mHardware->startPreview(); + } else { + mHardware->enableMsgType(CAMERA_MSG_PREVIEW_FRAME); + ret = mHardware->startPreview(); + if (ret != NO_ERROR) return ret; + // If preview display has been set, register preview buffers now. + if (mSurface != 0) { + // Unregister here because the surface registered with raw heap. + mSurface->unregisterBuffers(); + ret = registerPreviewBuffers(); + } + } + return ret; +} + +status_t CameraService::Client::startPreview() +{ + LOGV("startPreview (pid %d)", getCallingPid()); + + return startCameraMode(CAMERA_PREVIEW_MODE); +} + +status_t CameraService::Client::startRecording() +{ + LOGV("startRecording (pid %d)", getCallingPid()); + + if (mMediaPlayerBeep.get() != NULL) { + // do not play record jingle if stream volume is 0 + // (typically because ringer mode is silent). + int index; + AudioSystem::getStreamVolumeIndex(AudioSystem::ENFORCED_AUDIBLE, &index); + if (index != 0) { + mMediaPlayerBeep->seekTo(0); + mMediaPlayerBeep->start(); + } + } + + mHardware->enableMsgType(CAMERA_MSG_VIDEO_FRAME); + + return startCameraMode(CAMERA_RECORDING_MODE); +} + +// stop preview mode +void CameraService::Client::stopPreview() +{ + LOGV("stopPreview (pid %d)", getCallingPid()); + + // hold main lock during state transition + { + Mutex::Autolock lock(mLock); + if (checkPid() != NO_ERROR) return; + + if (mHardware == 0) { + LOGE("mHardware is NULL, returning."); + return; + } + + mHardware->stopPreview(); + mHardware->disableMsgType(CAMERA_MSG_PREVIEW_FRAME); + LOGV("stopPreview(), hardware stopped OK"); + + if (mSurface != 0 && !mUseOverlay) { + mSurface->unregisterBuffers(); + } + } + + // hold preview buffer lock + { + Mutex::Autolock lock(mPreviewLock); + mPreviewBuffer.clear(); + } +} + +// stop recording mode +void CameraService::Client::stopRecording() +{ + LOGV("stopRecording (pid %d)", getCallingPid()); + + // hold main lock during state transition + { + Mutex::Autolock lock(mLock); + if (checkPid() != NO_ERROR) return; + + if (mHardware == 0) { + LOGE("mHardware is NULL, returning."); + return; + } + + if (mMediaPlayerBeep.get() != NULL) { + mMediaPlayerBeep->seekTo(0); + mMediaPlayerBeep->start(); + } + + mHardware->stopRecording(); + mHardware->disableMsgType(CAMERA_MSG_VIDEO_FRAME); + LOGV("stopRecording(), hardware stopped OK"); + } + + // hold preview buffer lock + { + Mutex::Autolock lock(mPreviewLock); + mPreviewBuffer.clear(); + } +} + +// release a recording frame +void CameraService::Client::releaseRecordingFrame(const sp<IMemory>& mem) +{ + Mutex::Autolock lock(mLock); + if (checkPid() != NO_ERROR) return; + + if (mHardware == 0) { + LOGE("mHardware is NULL, returning."); + return; + } + + mHardware->releaseRecordingFrame(mem); +} + +bool CameraService::Client::previewEnabled() +{ + Mutex::Autolock lock(mLock); + if (mHardware == 0) return false; + return mHardware->previewEnabled(); +} + +bool CameraService::Client::recordingEnabled() +{ + Mutex::Autolock lock(mLock); + if (mHardware == 0) return false; + return mHardware->recordingEnabled(); +} + +// Safely retrieves a strong pointer to the client during a hardware callback. +sp<CameraService::Client> CameraService::Client::getClientFromCookie(void* user) +{ + sp<Client> client = 0; + CameraService *service = static_cast<CameraService*>(user); + if (service != NULL) { + Mutex::Autolock ourLock(service->mServiceLock); + if (service->mClient != 0) { + client = service->mClient.promote(); + if (client == 0) { + LOGE("getClientFromCookie: client appears to have died"); + service->mClient.clear(); + } + } else { + LOGE("getClientFromCookie: got callback but client was NULL"); + } + } + return client; +} + + +#if DEBUG_DUMP_JPEG_SNAPSHOT_TO_FILE || \ + DEBUG_DUMP_YUV_SNAPSHOT_TO_FILE || \ + DEBUG_DUMP_PREVIEW_FRAME_TO_FILE +static void dump_to_file(const char *fname, + uint8_t *buf, uint32_t size) +{ + int nw, cnt = 0; + uint32_t written = 0; + + LOGV("opening file [%s]\n", fname); + int fd = open(fname, O_RDWR | O_CREAT); + if (fd < 0) { + LOGE("failed to create file [%s]: %s", fname, strerror(errno)); + return; + } + + LOGV("writing %d bytes to file [%s]\n", size, fname); + while (written < size) { + nw = ::write(fd, + buf + written, + size - written); + if (nw < 0) { + LOGE("failed to write to file [%s]: %s", + fname, strerror(errno)); + break; + } + written += nw; + cnt++; + } + LOGV("done writing %d bytes to file [%s] in %d passes\n", + size, fname, cnt); + ::close(fd); +} +#endif + +status_t CameraService::Client::autoFocus() +{ + LOGV("autoFocus (pid %d)", getCallingPid()); + + Mutex::Autolock lock(mLock); + status_t result = checkPid(); + if (result != NO_ERROR) return result; + + if (mHardware == 0) { + LOGE("mHardware is NULL, returning."); + return INVALID_OPERATION; + } + + return mHardware->autoFocus(); +} + +status_t CameraService::Client::cancelAutoFocus() +{ + LOGV("cancelAutoFocus (pid %d)", getCallingPid()); + + Mutex::Autolock lock(mLock); + status_t result = checkPid(); + if (result != NO_ERROR) return result; + + if (mHardware == 0) { + LOGE("mHardware is NULL, returning."); + return INVALID_OPERATION; + } + + return mHardware->cancelAutoFocus(); +} + +// take a picture - image is returned in callback +status_t CameraService::Client::takePicture() +{ + LOGV("takePicture (pid %d)", getCallingPid()); + + Mutex::Autolock lock(mLock); + status_t result = checkPid(); + if (result != NO_ERROR) return result; + + if (mHardware == 0) { + LOGE("mHardware is NULL, returning."); + return INVALID_OPERATION; + } + + mHardware->enableMsgType(CAMERA_MSG_SHUTTER | + CAMERA_MSG_POSTVIEW_FRAME | + CAMERA_MSG_RAW_IMAGE | + CAMERA_MSG_COMPRESSED_IMAGE); + + return mHardware->takePicture(); +} + +// snapshot taken +void CameraService::Client::handleShutter( + image_rect_type *size // The width and height of yuv picture for + // registerBuffer. If this is NULL, use the picture + // size from parameters. +) +{ + // Play shutter sound. + if (mMediaPlayerClick.get() != NULL) { + // do not play shutter sound if stream volume is 0 + // (typically because ringer mode is silent). + int index; + AudioSystem::getStreamVolumeIndex(AudioSystem::ENFORCED_AUDIBLE, &index); + if (index != 0) { + mMediaPlayerClick->seekTo(0); + mMediaPlayerClick->start(); + } + } + + // Screen goes black after the buffer is unregistered. + if (mSurface != 0 && !mUseOverlay) { + mSurface->unregisterBuffers(); + } + + sp<ICameraClient> c = mCameraClient; + if (c != NULL) { + c->notifyCallback(CAMERA_MSG_SHUTTER, 0, 0); + } + mHardware->disableMsgType(CAMERA_MSG_SHUTTER); + + // It takes some time before yuvPicture callback to be called. + // Register the buffer for raw image here to reduce latency. + if (mSurface != 0 && !mUseOverlay) { + int w, h; + CameraParameters params(mHardware->getParameters()); + if (size == NULL) { + params.getPictureSize(&w, &h); + } else { + w = size->width; + h = size->height; + w &= ~1; + h &= ~1; + LOGV("Snapshot image width=%d, height=%d", w, h); + } + // FIXME: don't use hardcoded format constants here + ISurface::BufferHeap buffers(w, h, w, h, + HAL_PIXEL_FORMAT_YCrCb_420_SP, mOrientation, 0, + mHardware->getRawHeap()); + + mSurface->registerBuffers(buffers); + } +} + +// preview callback - frame buffer update +void CameraService::Client::handlePreviewData(const sp<IMemory>& mem) +{ + ssize_t offset; + size_t size; + sp<IMemoryHeap> heap = mem->getMemory(&offset, &size); + +#if DEBUG_HEAP_LEAKS && 0 // debugging + if (gWeakHeap == NULL) { + if (gWeakHeap != heap) { + LOGV("SETTING PREVIEW HEAP"); + heap->trackMe(true, true); + gWeakHeap = heap; + } + } +#endif +#if DEBUG_DUMP_PREVIEW_FRAME_TO_FILE + { + if (debug_frame_cnt++ == DEBUG_DUMP_PREVIEW_FRAME_TO_FILE) { + dump_to_file("/data/preview.yuv", + (uint8_t *)heap->base() + offset, size); + } + } +#endif + + if (!mUseOverlay) + { + Mutex::Autolock surfaceLock(mSurfaceLock); + if (mSurface != NULL) { + mSurface->postBuffer(offset); + } + } + + // local copy of the callback flags + int flags = mPreviewCallbackFlag; + + // is callback enabled? + if (!(flags & FRAME_CALLBACK_FLAG_ENABLE_MASK)) { + // If the enable bit is off, the copy-out and one-shot bits are ignored + LOGV("frame callback is diabled"); + return; + } + + // hold a strong pointer to the client + sp<ICameraClient> c = mCameraClient; + + // clear callback flags if no client or one-shot mode + if ((c == NULL) || (mPreviewCallbackFlag & FRAME_CALLBACK_FLAG_ONE_SHOT_MASK)) { + LOGV("Disable preview callback"); + mPreviewCallbackFlag &= ~(FRAME_CALLBACK_FLAG_ONE_SHOT_MASK | + FRAME_CALLBACK_FLAG_COPY_OUT_MASK | + FRAME_CALLBACK_FLAG_ENABLE_MASK); + // TODO: Shouldn't we use this API for non-overlay hardware as well? + if (mUseOverlay) + mHardware->disableMsgType(CAMERA_MSG_PREVIEW_FRAME); + } + + // Is the received frame copied out or not? + if (flags & FRAME_CALLBACK_FLAG_COPY_OUT_MASK) { + LOGV("frame is copied"); + copyFrameAndPostCopiedFrame(c, heap, offset, size); + } else { + LOGV("frame is forwarded"); + c->dataCallback(CAMERA_MSG_PREVIEW_FRAME, mem); + } +} + +// picture callback - postview image ready +void CameraService::Client::handlePostview(const sp<IMemory>& mem) +{ +#if DEBUG_DUMP_POSTVIEW_SNAPSHOT_TO_FILE // for testing pursposes only + { + ssize_t offset; + size_t size; + sp<IMemoryHeap> heap = mem->getMemory(&offset, &size); + dump_to_file("/data/postview.yuv", + (uint8_t *)heap->base() + offset, size); + } +#endif + + sp<ICameraClient> c = mCameraClient; + if (c != NULL) { + c->dataCallback(CAMERA_MSG_POSTVIEW_FRAME, mem); + } + mHardware->disableMsgType(CAMERA_MSG_POSTVIEW_FRAME); +} + +// picture callback - raw image ready +void CameraService::Client::handleRawPicture(const sp<IMemory>& mem) +{ + ssize_t offset; + size_t size; + sp<IMemoryHeap> heap = mem->getMemory(&offset, &size); +#if DEBUG_HEAP_LEAKS && 0 // debugging + gWeakHeap = heap; // debugging +#endif + + //LOGV("handleRawPicture(%d, %d)", offset, size); +#if DEBUG_DUMP_YUV_SNAPSHOT_TO_FILE // for testing pursposes only + dump_to_file("/data/photo.yuv", + (uint8_t *)heap->base() + offset, size); +#endif + + // Put the YUV version of the snapshot in the preview display. + if (mSurface != 0 && !mUseOverlay) { + mSurface->postBuffer(offset); + } + + sp<ICameraClient> c = mCameraClient; + if (c != NULL) { + c->dataCallback(CAMERA_MSG_RAW_IMAGE, mem); + } + mHardware->disableMsgType(CAMERA_MSG_RAW_IMAGE); +} + +// picture callback - compressed picture ready +void CameraService::Client::handleCompressedPicture(const sp<IMemory>& mem) +{ +#if DEBUG_DUMP_JPEG_SNAPSHOT_TO_FILE // for testing pursposes only + { + ssize_t offset; + size_t size; + sp<IMemoryHeap> heap = mem->getMemory(&offset, &size); + dump_to_file("/data/photo.jpg", + (uint8_t *)heap->base() + offset, size); + } +#endif + + sp<ICameraClient> c = mCameraClient; + if (c != NULL) { + c->dataCallback(CAMERA_MSG_COMPRESSED_IMAGE, mem); + } + mHardware->disableMsgType(CAMERA_MSG_COMPRESSED_IMAGE); +} + +void CameraService::Client::notifyCallback(int32_t msgType, int32_t ext1, int32_t ext2, void* user) +{ + LOGV("notifyCallback(%d)", msgType); + + sp<Client> client = getClientFromCookie(user); + if (client == 0) { + return; + } + + switch (msgType) { + case CAMERA_MSG_SHUTTER: + // ext1 is the dimension of the yuv picture. + client->handleShutter((image_rect_type *)ext1); + break; + default: + sp<ICameraClient> c = client->mCameraClient; + if (c != NULL) { + c->notifyCallback(msgType, ext1, ext2); + } + break; + } + +#if DEBUG_CLIENT_REFERENCES + if (client->getStrongCount() == 1) { + LOGE("++++++++++++++++ (NOTIFY CALLBACK) THIS WILL CAUSE A LOCKUP!"); + client->printRefs(); + } +#endif +} + +void CameraService::Client::dataCallback(int32_t msgType, const sp<IMemory>& dataPtr, void* user) +{ + LOGV("dataCallback(%d)", msgType); + + sp<Client> client = getClientFromCookie(user); + if (client == 0) { + return; + } + + sp<ICameraClient> c = client->mCameraClient; + if (dataPtr == NULL) { + LOGE("Null data returned in data callback"); + if (c != NULL) { + c->notifyCallback(CAMERA_MSG_ERROR, UNKNOWN_ERROR, 0); + c->dataCallback(msgType, NULL); + } + return; + } + + switch (msgType) { + case CAMERA_MSG_PREVIEW_FRAME: + client->handlePreviewData(dataPtr); + break; + case CAMERA_MSG_POSTVIEW_FRAME: + client->handlePostview(dataPtr); + break; + case CAMERA_MSG_RAW_IMAGE: + client->handleRawPicture(dataPtr); + break; + case CAMERA_MSG_COMPRESSED_IMAGE: + client->handleCompressedPicture(dataPtr); + break; + default: + if (c != NULL) { + c->dataCallback(msgType, dataPtr); + } + break; + } + +#if DEBUG_CLIENT_REFERENCES + if (client->getStrongCount() == 1) { + LOGE("++++++++++++++++ (DATA CALLBACK) THIS WILL CAUSE A LOCKUP!"); + client->printRefs(); + } +#endif +} + +void CameraService::Client::dataCallbackTimestamp(nsecs_t timestamp, int32_t msgType, + const sp<IMemory>& dataPtr, void* user) +{ + LOGV("dataCallbackTimestamp(%d)", msgType); + + sp<Client> client = getClientFromCookie(user); + if (client == 0) { + return; + } + sp<ICameraClient> c = client->mCameraClient; + + if (dataPtr == NULL) { + LOGE("Null data returned in data with timestamp callback"); + if (c != NULL) { + c->notifyCallback(CAMERA_MSG_ERROR, UNKNOWN_ERROR, 0); + c->dataCallbackTimestamp(0, msgType, NULL); + } + return; + } + + if (c != NULL) { + c->dataCallbackTimestamp(timestamp, msgType, dataPtr); + } + +#if DEBUG_CLIENT_REFERENCES + if (client->getStrongCount() == 1) { + LOGE("++++++++++++++++ (DATA CALLBACK TIMESTAMP) THIS WILL CAUSE A LOCKUP!"); + client->printRefs(); + } +#endif +} + +// set preview/capture parameters - key/value pairs +status_t CameraService::Client::setParameters(const String8& params) +{ + LOGV("setParameters(%s)", params.string()); + + Mutex::Autolock lock(mLock); + status_t result = checkPid(); + if (result != NO_ERROR) return result; + + if (mHardware == 0) { + LOGE("mHardware is NULL, returning."); + return INVALID_OPERATION; + } + + CameraParameters p(params); + + return mHardware->setParameters(p); +} + +// get preview/capture parameters - key/value pairs +String8 CameraService::Client::getParameters() const +{ + Mutex::Autolock lock(mLock); + + if (mHardware == 0) { + LOGE("mHardware is NULL, returning."); + return String8(); + } + + String8 params(mHardware->getParameters().flatten()); + LOGV("getParameters(%s)", params.string()); + return params; +} + +status_t CameraService::Client::sendCommand(int32_t cmd, int32_t arg1, int32_t arg2) +{ + LOGV("sendCommand (pid %d)", getCallingPid()); + Mutex::Autolock lock(mLock); + status_t result = checkPid(); + if (result != NO_ERROR) return result; + + if (cmd == CAMERA_CMD_SET_DISPLAY_ORIENTATION) { + // The orientation cannot be set during preview. + if (mHardware->previewEnabled()) { + return INVALID_OPERATION; + } + switch (arg1) { + case 0: + mOrientation = ISurface::BufferHeap::ROT_0; + break; + case 90: + mOrientation = ISurface::BufferHeap::ROT_90; + break; + case 180: + mOrientation = ISurface::BufferHeap::ROT_180; + break; + case 270: + mOrientation = ISurface::BufferHeap::ROT_270; + break; + default: + return BAD_VALUE; + } + return OK; + } + + if (mHardware == 0) { + LOGE("mHardware is NULL, returning."); + return INVALID_OPERATION; + } + + return mHardware->sendCommand(cmd, arg1, arg2); +} + +void CameraService::Client::copyFrameAndPostCopiedFrame(const sp<ICameraClient>& client, + const sp<IMemoryHeap>& heap, size_t offset, size_t size) +{ + LOGV("copyFrameAndPostCopiedFrame"); + // It is necessary to copy out of pmem before sending this to + // the callback. For efficiency, reuse the same MemoryHeapBase + // provided it's big enough. Don't allocate the memory or + // perform the copy if there's no callback. + + // hold the preview lock while we grab a reference to the preview buffer + sp<MemoryHeapBase> previewBuffer; + { + Mutex::Autolock lock(mPreviewLock); + if (mPreviewBuffer == 0) { + mPreviewBuffer = new MemoryHeapBase(size, 0, NULL); + } else if (size > mPreviewBuffer->virtualSize()) { + mPreviewBuffer.clear(); + mPreviewBuffer = new MemoryHeapBase(size, 0, NULL); + } + if (mPreviewBuffer == 0) { + LOGE("failed to allocate space for preview buffer"); + return; + } + previewBuffer = mPreviewBuffer; + } + memcpy(previewBuffer->base(), + (uint8_t *)heap->base() + offset, size); + + sp<MemoryBase> frame = new MemoryBase(previewBuffer, 0, size); + if (frame == 0) { + LOGE("failed to allocate space for frame callback"); + return; + } + client->dataCallback(CAMERA_MSG_PREVIEW_FRAME, frame); +} + +static const int kDumpLockRetries = 50; +static const int kDumpLockSleep = 60000; + +static bool tryLock(Mutex& mutex) +{ + bool locked = false; + for (int i = 0; i < kDumpLockRetries; ++i) { + if (mutex.tryLock() == NO_ERROR) { + locked = true; + break; + } + usleep(kDumpLockSleep); + } + return locked; +} + +status_t CameraService::dump(int fd, const Vector<String16>& args) +{ + static const char* kDeadlockedString = "CameraService may be deadlocked\n"; + + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + if (checkCallingPermission(String16("android.permission.DUMP")) == false) { + snprintf(buffer, SIZE, "Permission Denial: " + "can't dump CameraService from pid=%d, uid=%d\n", + getCallingPid(), + IPCThreadState::self()->getCallingUid()); + result.append(buffer); + write(fd, result.string(), result.size()); + } else { + bool locked = tryLock(mServiceLock); + // failed to lock - CameraService is probably deadlocked + if (!locked) { + String8 result(kDeadlockedString); + write(fd, result.string(), result.size()); + } + + if (mClient != 0) { + sp<Client> currentClient = mClient.promote(); + sprintf(buffer, "Client (%p) PID: %d\n", + currentClient->getCameraClient()->asBinder().get(), + currentClient->mClientPid); + result.append(buffer); + write(fd, result.string(), result.size()); + currentClient->mHardware->dump(fd, args); + } else { + result.append("No camera client yet.\n"); + write(fd, result.string(), result.size()); + } + + if (locked) mServiceLock.unlock(); + } + return NO_ERROR; +} + + +status_t CameraService::onTransact( + uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) +{ + // permission checks... + switch (code) { + case BnCameraService::CONNECT: + IPCThreadState* ipc = IPCThreadState::self(); + const int pid = ipc->getCallingPid(); + const int self_pid = getpid(); + if (pid != self_pid) { + // we're called from a different process, do the real check + if (!checkCallingPermission( + String16("android.permission.CAMERA"))) + { + const int uid = ipc->getCallingUid(); + LOGE("Permission Denial: " + "can't use the camera pid=%d, uid=%d", pid, uid); + return PERMISSION_DENIED; + } + } + break; + } + + status_t err = BnCameraService::onTransact(code, data, reply, flags); + +#if DEBUG_HEAP_LEAKS + LOGV("+++ onTransact err %d code %d", err, code); + + if (err == UNKNOWN_TRANSACTION || err == PERMISSION_DENIED) { + // the 'service' command interrogates this binder for its name, and then supplies it + // even for the debugging commands. that means we need to check for it here, using + // ISurfaceComposer (since we delegated the INTERFACE_TRANSACTION handling to + // BnSurfaceComposer before falling through to this code). + + LOGV("+++ onTransact code %d", code); + + CHECK_INTERFACE(ICameraService, data, reply); + + switch(code) { + case 1000: + { + if (gWeakHeap != 0) { + sp<IMemoryHeap> h = gWeakHeap.promote(); + IMemoryHeap *p = gWeakHeap.unsafe_get(); + LOGV("CHECKING WEAK REFERENCE %p (%p)", h.get(), p); + if (h != 0) + h->printRefs(); + bool attempt_to_delete = data.readInt32() == 1; + if (attempt_to_delete) { + // NOT SAFE! + LOGV("DELETING WEAK REFERENCE %p (%p)", h.get(), p); + if (p) delete p; + } + return NO_ERROR; + } + } + break; + default: + break; + } + } +#endif // DEBUG_HEAP_LEAKS + + return err; +} + +}; // namespace android diff --git a/services/camera/libcameraservice/CameraService.h b/services/camera/libcameraservice/CameraService.h new file mode 100644 index 0000000..bc49b1d --- /dev/null +++ b/services/camera/libcameraservice/CameraService.h @@ -0,0 +1,227 @@ +/* +** +** Copyright (C) 2008, The Android Open Source Project +** Copyright (C) 2008 HTC Inc. +** +** 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. +*/ + +#ifndef ANDROID_SERVERS_CAMERA_CAMERASERVICE_H +#define ANDROID_SERVERS_CAMERA_CAMERASERVICE_H + +#include <camera/ICameraService.h> +#include <camera/CameraHardwareInterface.h> +#include <camera/Camera.h> + +namespace android { + +class MemoryHeapBase; +class MediaPlayer; + +// ---------------------------------------------------------------------------- + +#define LIKELY( exp ) (__builtin_expect( (exp) != 0, true )) +#define UNLIKELY( exp ) (__builtin_expect( (exp) != 0, false )) + +// When enabled, this feature allows you to send an event to the CameraService +// so that you can cause all references to the heap object gWeakHeap, defined +// below, to be printed. You will also need to set DEBUG_REFS=1 and +// DEBUG_REFS_ENABLED_BY_DEFAULT=0 in libutils/RefBase.cpp. You just have to +// set gWeakHeap to the appropriate heap you want to track. + +#define DEBUG_HEAP_LEAKS 0 + +// ---------------------------------------------------------------------------- + +class CameraService : public BnCameraService +{ + class Client; + +public: + static void instantiate(); + + // ICameraService interface + virtual sp<ICamera> connect(const sp<ICameraClient>& cameraClient); + + virtual status_t dump(int fd, const Vector<String16>& args); + + void removeClient(const sp<ICameraClient>& cameraClient); + + virtual status_t onTransact( + uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags); + +private: + +// ---------------------------------------------------------------------------- + + class Client : public BnCamera { + + public: + virtual void disconnect(); + + // connect new client with existing camera remote + virtual status_t connect(const sp<ICameraClient>& client); + + // prevent other processes from using this ICamera interface + virtual status_t lock(); + + // allow other processes to use this ICamera interface + virtual status_t unlock(); + + // pass the buffered ISurface to the camera service + virtual status_t setPreviewDisplay(const sp<ISurface>& surface); + + // set the preview callback flag to affect how the received frames from + // preview are handled. + virtual void setPreviewCallbackFlag(int callback_flag); + + // start preview mode, must call setPreviewDisplay first + virtual status_t startPreview(); + + // stop preview mode + virtual void stopPreview(); + + // get preview state + virtual bool previewEnabled(); + + // start recording mode + virtual status_t startRecording(); + + // stop recording mode + virtual void stopRecording(); + + // get recording state + virtual bool recordingEnabled(); + + // release a recording frame + virtual void releaseRecordingFrame(const sp<IMemory>& mem); + + // auto focus + virtual status_t autoFocus(); + + // cancel auto focus + virtual status_t cancelAutoFocus(); + + // take a picture - returns an IMemory (ref-counted mmap) + virtual status_t takePicture(); + + // set preview/capture parameters - key/value pairs + virtual status_t setParameters(const String8& params); + + // get preview/capture parameters - key/value pairs + virtual String8 getParameters() const; + + // send command to camera driver + virtual status_t sendCommand(int32_t cmd, int32_t arg1, int32_t arg2); + + // our client... + const sp<ICameraClient>& getCameraClient() const { return mCameraClient; } + + private: + friend class CameraService; + Client(const sp<CameraService>& cameraService, + const sp<ICameraClient>& cameraClient, + pid_t clientPid); + Client(); + virtual ~Client(); + + status_t checkPid(); + + static void notifyCallback(int32_t msgType, int32_t ext1, int32_t ext2, void* user); + static void dataCallback(int32_t msgType, const sp<IMemory>& dataPtr, void* user); + static void dataCallbackTimestamp(nsecs_t timestamp, int32_t msgType, + const sp<IMemory>& dataPtr, void* user); + + static sp<Client> getClientFromCookie(void* user); + + void handlePreviewData(const sp<IMemory>&); + void handleShutter(image_rect_type *image); + void handlePostview(const sp<IMemory>&); + void handleRawPicture(const sp<IMemory>&); + void handleCompressedPicture(const sp<IMemory>&); + + void copyFrameAndPostCopiedFrame(const sp<ICameraClient>& client, + const sp<IMemoryHeap>& heap, size_t offset, size_t size); + + // camera operation mode + enum camera_mode { + CAMERA_PREVIEW_MODE = 0, // frame automatically released + CAMERA_RECORDING_MODE = 1, // frame has to be explicitly released by releaseRecordingFrame() + }; + status_t startCameraMode(camera_mode mode); + status_t startPreviewMode(); + status_t startRecordingMode(); + status_t setOverlay(); + status_t registerPreviewBuffers(); + + // Ensures atomicity among the public methods + mutable Mutex mLock; + + // mSurfaceLock synchronizes access to mSurface between + // setPreviewSurface() and postPreviewFrame(). Note that among + // the public methods, all accesses to mSurface are + // syncrhonized by mLock. However, postPreviewFrame() is called + // by the CameraHardwareInterface callback, and needs to + // access mSurface. It cannot hold mLock, however, because + // stopPreview() may be holding that lock while attempting + // to stop preview, and stopPreview itself will block waiting + // for a callback from CameraHardwareInterface. If this + // happens, it will cause a deadlock. + mutable Mutex mSurfaceLock; + mutable Condition mReady; + sp<CameraService> mCameraService; + sp<ISurface> mSurface; + int mPreviewCallbackFlag; + int mOrientation; + + sp<MediaPlayer> mMediaPlayerClick; + sp<MediaPlayer> mMediaPlayerBeep; + + // these are immutable once the object is created, + // they don't need to be protected by a lock + sp<ICameraClient> mCameraClient; + sp<CameraHardwareInterface> mHardware; + pid_t mClientPid; + bool mUseOverlay; + + sp<OverlayRef> mOverlayRef; + int mOverlayW; + int mOverlayH; + + mutable Mutex mPreviewLock; + sp<MemoryHeapBase> mPreviewBuffer; + }; + +// ---------------------------------------------------------------------------- + + CameraService(); + virtual ~CameraService(); + + // We use a count for number of clients (shoule only be 0 or 1). + volatile int32_t mUsers; + virtual void incUsers(); + virtual void decUsers(); + + mutable Mutex mServiceLock; + wp<Client> mClient; + +#if DEBUG_HEAP_LEAKS + wp<IMemoryHeap> gWeakHeap; +#endif +}; + +// ---------------------------------------------------------------------------- + +}; // namespace android + +#endif diff --git a/services/camera/libcameraservice/CannedJpeg.h b/services/camera/libcameraservice/CannedJpeg.h new file mode 100644 index 0000000..b6266fb --- /dev/null +++ b/services/camera/libcameraservice/CannedJpeg.h @@ -0,0 +1,734 @@ +const int kCannedJpegWidth = 320; +const int kCannedJpegHeight = 240; +const int kCannedJpegSize = 8733; + +const char kCannedJpeg[] = { + 0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01, + 0x01, 0x01, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0xff, 0xe1, 0x00, 0x66, + 0x45, 0x78, 0x69, 0x66, 0x00, 0x00, 0x49, 0x49, 0x2a, 0x00, 0x08, 0x00, + 0x00, 0x00, 0x04, 0x00, 0x1a, 0x01, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x3e, 0x00, 0x00, 0x00, 0x1b, 0x01, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x46, 0x00, 0x00, 0x00, 0x28, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x31, 0x01, 0x02, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x50, 0x61, 0x69, 0x6e, 0x74, 0x2e, 0x4e, 0x45, 0x54, 0x20, 0x76, 0x33, + 0x2e, 0x33, 0x36, 0x00, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, + 0x03, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x03, 0x03, 0x04, 0x05, + 0x08, 0x05, 0x05, 0x04, 0x04, 0x05, 0x0a, 0x07, 0x07, 0x06, 0x08, 0x0c, + 0x0a, 0x0c, 0x0c, 0x0b, 0x0a, 0x0b, 0x0b, 0x0d, 0x0e, 0x12, 0x10, 0x0d, + 0x0e, 0x11, 0x0e, 0x0b, 0x0b, 0x10, 0x16, 0x10, 0x11, 0x13, 0x14, 0x15, + 0x15, 0x15, 0x0c, 0x0f, 0x17, 0x18, 0x16, 0x14, 0x18, 0x12, 0x14, 0x15, + 0x14, 0xff, 0xdb, 0x00, 0x43, 0x01, 0x03, 0x04, 0x04, 0x05, 0x04, 0x05, + 0x09, 0x05, 0x05, 0x09, 0x14, 0x0d, 0x0b, 0x0d, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0xff, 0xc0, + 0x00, 0x11, 0x08, 0x00, 0xf0, 0x01, 0x40, 0x03, 0x01, 0x22, 0x00, 0x02, + 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xc4, 0x00, 0x1f, 0x00, 0x00, 0x01, + 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5, 0x10, 0x00, 0x02, 0x01, 0x03, 0x03, + 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7d, 0x01, + 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, + 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, + 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72, 0x82, + 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, 0x29, + 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, + 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, + 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, + 0x77, 0x78, 0x79, 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, + 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, + 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, + 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3, + 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, + 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xff, 0xc4, 0x00, 0x1f, 0x01, 0x00, 0x03, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5, 0x11, 0x00, 0x02, 0x01, 0x02, 0x04, + 0x04, 0x03, 0x04, 0x07, 0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77, 0x00, + 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, + 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, + 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72, 0xd1, 0x0a, + 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, + 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, + 0x76, 0x77, 0x78, 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, + 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, + 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, + 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, + 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2, 0xf3, 0xf4, 0xf5, + 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, + 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, 0xf9, 0xd2, 0xa3, 0x95, 0xbb, + 0x54, 0x84, 0xe0, 0x66, 0xa0, 0x27, 0x27, 0x35, 0xed, 0x9e, 0x50, 0x95, + 0x2c, 0x4b, 0xc6, 0x6a, 0x35, 0x1b, 0x8e, 0x2a, 0x70, 0x30, 0x28, 0x00, + 0xa8, 0xe5, 0x6e, 0x71, 0x52, 0x31, 0xda, 0x33, 0x50, 0x13, 0x93, 0x40, + 0x09, 0x52, 0xc6, 0xb8, 0x19, 0xf5, 0xa6, 0x2a, 0xee, 0x6c, 0x54, 0xd4, + 0x00, 0x54, 0x52, 0x36, 0x5b, 0x1e, 0x95, 0x23, 0xb6, 0xd5, 0xcd, 0x41, + 0x40, 0x05, 0x4c, 0x8b, 0xb5, 0x7d, 0xea, 0x34, 0x5d, 0xcd, 0xed, 0x53, + 0x50, 0x01, 0x50, 0xbb, 0x6e, 0x6f, 0x6a, 0x91, 0xdb, 0x6a, 0xfb, 0xd4, + 0x34, 0x00, 0x54, 0xe8, 0xbb, 0x57, 0x15, 0x1c, 0x6b, 0x96, 0xcf, 0xa5, + 0x4b, 0x40, 0x05, 0x42, 0xcd, 0xb9, 0xb3, 0x4f, 0x91, 0xb0, 0x31, 0xeb, + 0x51, 0x50, 0x02, 0x81, 0x93, 0x53, 0xa8, 0xda, 0x31, 0x51, 0xc4, 0xbc, + 0xe6, 0xa4, 0xa0, 0x00, 0x9c, 0x0a, 0x81, 0x8e, 0xe3, 0x9a, 0x92, 0x56, + 0xe3, 0x15, 0x15, 0x00, 0x28, 0x19, 0x38, 0xa9, 0xc0, 0xc0, 0xc5, 0x47, + 0x12, 0xf7, 0xa9, 0x28, 0x00, 0x27, 0x00, 0x9a, 0x80, 0x9c, 0x9c, 0xd3, + 0xe5, 0x6e, 0xd5, 0x1d, 0x00, 0x2a, 0x8d, 0xc7, 0x15, 0x3d, 0x32, 0x35, + 0xc0, 0xcf, 0xad, 0x3e, 0x80, 0x11, 0x8e, 0xd1, 0x9a, 0x82, 0x9f, 0x23, + 0x64, 0xe3, 0xd2, 0x99, 0x40, 0x0e, 0x45, 0xdc, 0xde, 0xd5, 0x35, 0x36, + 0x35, 0xc2, 0xfb, 0x9a, 0x75, 0x00, 0x35, 0xdb, 0x6a, 0xfb, 0xd4, 0x34, + 0xe9, 0x1b, 0x73, 0x7b, 0x0a, 0x6d, 0x00, 0x3e, 0x35, 0xcb, 0x7b, 0x0a, + 0x96, 0x91, 0x17, 0x6a, 0xd2, 0xd0, 0x03, 0x64, 0x6c, 0x2f, 0xb9, 0xa8, + 0x69, 0xce, 0xdb, 0x9a, 0x9b, 0xd6, 0x80, 0x1f, 0x12, 0xe4, 0xe7, 0xd2, + 0xa5, 0xa4, 0x51, 0xb4, 0x62, 0x97, 0xa5, 0x00, 0x67, 0xc9, 0xad, 0xd8, + 0x91, 0x81, 0x72, 0x9f, 0x9d, 0x47, 0xfd, 0xb3, 0x65, 0xff, 0x00, 0x3f, + 0x29, 0x5f, 0xa0, 0x1f, 0xf0, 0xe9, 0x6f, 0x09, 0x7f, 0xd0, 0xfb, 0xad, + 0x7f, 0xe0, 0x24, 0x34, 0x7f, 0xc3, 0xa5, 0xbc, 0x25, 0xff, 0x00, 0x43, + 0xee, 0xb5, 0xff, 0x00, 0x80, 0x90, 0xd7, 0x3f, 0xb7, 0x87, 0x73, 0x6f, + 0x63, 0x33, 0xe0, 0x28, 0xf5, 0x9b, 0x11, 0xc9, 0xb9, 0x4c, 0xfd, 0x69, + 0xff, 0x00, 0xdb, 0x96, 0x1f, 0xf3, 0xf5, 0x1f, 0xe7, 0x5f, 0x7d, 0x7f, + 0xc3, 0xa5, 0xbc, 0x25, 0xff, 0x00, 0x43, 0xee, 0xb5, 0xff, 0x00, 0x80, + 0x90, 0xd1, 0xff, 0x00, 0x0e, 0x96, 0xf0, 0x97, 0xfd, 0x0f, 0xba, 0xd7, + 0xfe, 0x02, 0x43, 0x47, 0xb7, 0x87, 0x70, 0xf6, 0x33, 0x3e, 0x02, 0x93, + 0x5b, 0xb1, 0x3c, 0x0b, 0x94, 0xc7, 0xd6, 0x99, 0xfd, 0xb3, 0x65, 0xff, + 0x00, 0x3f, 0x29, 0xf9, 0xd7, 0xe8, 0x07, 0xfc, 0x3a, 0x5b, 0xc2, 0x5f, + 0xf4, 0x3e, 0xeb, 0x5f, 0xf8, 0x09, 0x0d, 0x1f, 0xf0, 0xe9, 0x6f, 0x09, + 0x7f, 0xd0, 0xfb, 0xad, 0x7f, 0xe0, 0x24, 0x34, 0xbd, 0xbc, 0x03, 0xd8, + 0xcc, 0xf8, 0x0e, 0x3d, 0x6a, 0xc1, 0x47, 0x37, 0x29, 0x9f, 0xad, 0x3b, + 0xfb, 0x72, 0xc3, 0xfe, 0x7e, 0xa3, 0xfc, 0xeb, 0xef, 0xaf, 0xf8, 0x74, + 0xb7, 0x84, 0xbf, 0xe8, 0x7d, 0xd6, 0xbf, 0xf0, 0x12, 0x1a, 0x3f, 0xe1, + 0xd2, 0xde, 0x12, 0xff, 0x00, 0xa1, 0xf7, 0x5a, 0xff, 0x00, 0xc0, 0x48, + 0x69, 0xfb, 0x78, 0x77, 0x0f, 0x63, 0x33, 0xe0, 0x19, 0x35, 0xbb, 0x26, + 0x3c, 0x5c, 0xa6, 0x3e, 0xb4, 0xdf, 0xed, 0x9b, 0x2f, 0xf9, 0xf9, 0x4a, + 0xfd, 0x00, 0xff, 0x00, 0x87, 0x4b, 0x78, 0x4b, 0xfe, 0x87, 0xdd, 0x6b, + 0xff, 0x00, 0x01, 0x21, 0xa3, 0xfe, 0x1d, 0x2d, 0xe1, 0x2f, 0xfa, 0x1f, + 0x75, 0xaf, 0xfc, 0x04, 0x86, 0x97, 0xb7, 0x80, 0x7b, 0x19, 0x9f, 0x01, + 0xa6, 0xb5, 0x60, 0xab, 0xff, 0x00, 0x1f, 0x51, 0xe7, 0xeb, 0x4e, 0xfe, + 0xdc, 0xb0, 0xff, 0x00, 0x9f, 0xa8, 0xff, 0x00, 0x3a, 0xfb, 0xeb, 0xfe, + 0x1d, 0x2d, 0xe1, 0x2f, 0xfa, 0x1f, 0x75, 0xaf, 0xfc, 0x04, 0x86, 0x8f, + 0xf8, 0x74, 0xb7, 0x84, 0xbf, 0xe8, 0x7d, 0xd6, 0xbf, 0xf0, 0x12, 0x1a, + 0x3d, 0xbc, 0x03, 0xd8, 0xcc, 0xf8, 0x05, 0xf5, 0xab, 0x26, 0x6f, 0xf8, + 0xf9, 0x4c, 0x7d, 0x69, 0xbf, 0xdb, 0x36, 0x5f, 0xf3, 0xf2, 0x9f, 0x9d, + 0x7e, 0x80, 0x7f, 0xc3, 0xa5, 0xbc, 0x25, 0xff, 0x00, 0x43, 0xee, 0xb5, + 0xff, 0x00, 0x80, 0x90, 0xd1, 0xff, 0x00, 0x0e, 0x96, 0xf0, 0x97, 0xfd, + 0x0f, 0xba, 0xd7, 0xfe, 0x02, 0x43, 0x47, 0xb7, 0x80, 0x7b, 0x19, 0x9f, + 0x02, 0x26, 0xb5, 0x60, 0xab, 0x8f, 0xb5, 0x47, 0xf9, 0xd2, 0xff, 0x00, + 0x6e, 0x58, 0x7f, 0xcf, 0xd4, 0x7f, 0x9d, 0x7d, 0xf5, 0xff, 0x00, 0x0e, + 0x96, 0xf0, 0x97, 0xfd, 0x0f, 0xba, 0xd7, 0xfe, 0x02, 0x43, 0x47, 0xfc, + 0x3a, 0x5b, 0xc2, 0x5f, 0xf4, 0x3e, 0xeb, 0x5f, 0xf8, 0x09, 0x0d, 0x1e, + 0xde, 0x01, 0xec, 0x66, 0x7c, 0x00, 0xda, 0xd5, 0x93, 0x1c, 0xfd, 0xa5, + 0x3f, 0x3a, 0x4f, 0xed, 0x8b, 0x2f, 0xf9, 0xf9, 0x4f, 0xce, 0xbf, 0x40, + 0x3f, 0xe1, 0xd2, 0xde, 0x12, 0xff, 0x00, 0xa1, 0xf7, 0x5a, 0xff, 0x00, + 0xc0, 0x48, 0x68, 0xff, 0x00, 0x87, 0x4b, 0x78, 0x4b, 0xfe, 0x87, 0xdd, + 0x6b, 0xff, 0x00, 0x01, 0x21, 0xa7, 0xed, 0xe1, 0xdc, 0x3d, 0x8c, 0xcf, + 0x81, 0x57, 0x5a, 0xb0, 0x51, 0x8f, 0xb5, 0x47, 0xf9, 0xd1, 0xfd, 0xb9, + 0x61, 0xff, 0x00, 0x3f, 0x49, 0xf9, 0xd7, 0xdf, 0x5f, 0xf0, 0xe9, 0x6f, + 0x09, 0x7f, 0xd0, 0xfb, 0xad, 0x7f, 0xe0, 0x24, 0x34, 0x7f, 0xc3, 0xa5, + 0xbc, 0x25, 0xff, 0x00, 0x43, 0xee, 0xb5, 0xff, 0x00, 0x80, 0x90, 0xd2, + 0xf6, 0xf0, 0x0f, 0x63, 0x33, 0xe0, 0x06, 0xd6, 0xac, 0x98, 0xe7, 0xed, + 0x29, 0xf9, 0xd2, 0x0d, 0x62, 0xcb, 0xfe, 0x7e, 0x53, 0xf3, 0xaf, 0xd0, + 0x0f, 0xf8, 0x74, 0xb7, 0x84, 0xbf, 0xe8, 0x7d, 0xd6, 0xbf, 0xf0, 0x12, + 0x1a, 0x3f, 0xe1, 0xd2, 0xde, 0x12, 0xff, 0x00, 0xa1, 0xf7, 0x5a, 0xff, + 0x00, 0xc0, 0x48, 0x69, 0xfb, 0x78, 0x77, 0x0f, 0x63, 0x33, 0xe0, 0x51, + 0xad, 0xd8, 0x01, 0x8f, 0xb5, 0x47, 0xf9, 0xd0, 0x75, 0xcb, 0x0c, 0x7f, + 0xc7, 0xca, 0x7e, 0x75, 0xf7, 0xd7, 0xfc, 0x3a, 0x5b, 0xc2, 0x5f, 0xf4, + 0x3e, 0xeb, 0x5f, 0xf8, 0x09, 0x0d, 0x1f, 0xf0, 0xe9, 0x6f, 0x09, 0x7f, + 0xd0, 0xfb, 0xad, 0x7f, 0xe0, 0x24, 0x34, 0x7b, 0x78, 0x77, 0x0f, 0x63, + 0x33, 0xf3, 0xfc, 0xeb, 0x36, 0x44, 0xff, 0x00, 0xc7, 0xca, 0x7e, 0x74, + 0xa3, 0x58, 0xb1, 0x24, 0x66, 0xe5, 0x31, 0xf5, 0xaf, 0xbf, 0xff, 0x00, + 0xe1, 0xd2, 0xde, 0x12, 0xff, 0x00, 0xa1, 0xf7, 0x5a, 0xff, 0x00, 0xc0, + 0x48, 0x68, 0xff, 0x00, 0x87, 0x4b, 0x78, 0x4b, 0xfe, 0x87, 0xdd, 0x6b, + 0xff, 0x00, 0x01, 0x21, 0xa3, 0xdb, 0xc3, 0xb8, 0x7b, 0x19, 0x9f, 0x02, + 0xff, 0x00, 0x6d, 0xd8, 0x7f, 0xcf, 0xd4, 0x7f, 0x9d, 0x07, 0x5c, 0xb1, + 0x03, 0x8b, 0x94, 0xcf, 0xd6, 0xbe, 0xfa, 0xff, 0x00, 0x87, 0x4b, 0x78, + 0x4b, 0xfe, 0x87, 0xdd, 0x6b, 0xff, 0x00, 0x01, 0x21, 0xa3, 0xfe, 0x1d, + 0x2d, 0xe1, 0x2f, 0xfa, 0x1f, 0x75, 0xaf, 0xfc, 0x04, 0x86, 0x8f, 0x6f, + 0x0e, 0xe1, 0xec, 0x66, 0x7e, 0x7f, 0xff, 0x00, 0x6c, 0xd9, 0x7f, 0xcf, + 0xca, 0x7e, 0x74, 0xab, 0xac, 0x58, 0xe7, 0x9b, 0x94, 0xc7, 0xd6, 0xbe, + 0xff, 0x00, 0xff, 0x00, 0x87, 0x4b, 0x78, 0x4b, 0xfe, 0x87, 0xdd, 0x6b, + 0xff, 0x00, 0x01, 0x21, 0xa3, 0xfe, 0x1d, 0x2d, 0xe1, 0x2f, 0xfa, 0x1f, + 0x75, 0xaf, 0xfc, 0x04, 0x86, 0x8f, 0x6f, 0x0e, 0xe1, 0xec, 0x66, 0x7c, + 0x0b, 0xfd, 0xb9, 0x61, 0xff, 0x00, 0x3f, 0x51, 0xfe, 0x74, 0x8d, 0xae, + 0x58, 0xed, 0x38, 0xb9, 0x4c, 0xfd, 0x6b, 0xef, 0xbf, 0xf8, 0x74, 0xb7, + 0x84, 0xbf, 0xe8, 0x7d, 0xd6, 0xbf, 0xf0, 0x12, 0x1a, 0x3f, 0xe1, 0xd2, + 0xde, 0x12, 0xff, 0x00, 0xa1, 0xf7, 0x5a, 0xff, 0x00, 0xc0, 0x48, 0x68, + 0xf6, 0xf0, 0xee, 0x1e, 0xc6, 0x67, 0xe7, 0xff, 0x00, 0xf6, 0xc5, 0x97, + 0xfc, 0xfc, 0xa7, 0xe7, 0x4e, 0x4d, 0x62, 0xc7, 0x77, 0x37, 0x29, 0xf9, + 0xd7, 0xdf, 0xdf, 0xf0, 0xe9, 0x6f, 0x09, 0x7f, 0xd0, 0xfb, 0xad, 0x7f, + 0xe0, 0x24, 0x34, 0x7f, 0xc3, 0xa5, 0xbc, 0x25, 0xff, 0x00, 0x43, 0xee, + 0xb5, 0xff, 0x00, 0x80, 0x90, 0xd1, 0xed, 0xe1, 0xdc, 0x3d, 0x8c, 0xcf, + 0x81, 0x7f, 0xb7, 0x2c, 0x3f, 0xe7, 0xea, 0x3f, 0xce, 0x91, 0xf5, 0xcb, + 0x1c, 0x71, 0x72, 0x9f, 0x9d, 0x7d, 0xf7, 0xff, 0x00, 0x0e, 0x96, 0xf0, + 0x97, 0xfd, 0x0f, 0xba, 0xd7, 0xfe, 0x02, 0x43, 0x47, 0xfc, 0x3a, 0x5b, + 0xc2, 0x5f, 0xf4, 0x3e, 0xeb, 0x5f, 0xf8, 0x09, 0x0d, 0x1e, 0xde, 0x1d, + 0xc3, 0xd8, 0xcc, 0xfc, 0xff, 0x00, 0xfe, 0xd9, 0xb2, 0xff, 0x00, 0x9f, + 0x94, 0xfc, 0xe9, 0xd1, 0xeb, 0x36, 0x20, 0xe4, 0xdc, 0xa7, 0xe7, 0x5f, + 0x7f, 0x7f, 0xc3, 0xa5, 0xbc, 0x25, 0xff, 0x00, 0x43, 0xee, 0xb5, 0xff, + 0x00, 0x80, 0x90, 0xd1, 0xff, 0x00, 0x0e, 0x96, 0xf0, 0x97, 0xfd, 0x0f, + 0xba, 0xd7, 0xfe, 0x02, 0x43, 0x47, 0xb7, 0x87, 0x70, 0xf6, 0x33, 0x3e, + 0x05, 0xfe, 0xdc, 0xb0, 0xff, 0x00, 0x9f, 0xa8, 0xff, 0x00, 0x3a, 0x6c, + 0x9a, 0xdd, 0x89, 0x18, 0x17, 0x29, 0xf9, 0xd7, 0xdf, 0x9f, 0xf0, 0xe9, + 0x6f, 0x09, 0x7f, 0xd0, 0xfb, 0xad, 0x7f, 0xe0, 0x24, 0x34, 0x7f, 0xc3, + 0xa5, 0xbc, 0x25, 0xff, 0x00, 0x43, 0xee, 0xb5, 0xff, 0x00, 0x80, 0x90, + 0xd1, 0xed, 0xe1, 0xdc, 0x3d, 0x8c, 0xcf, 0xbc, 0xa8, 0xa2, 0x8a, 0xf3, + 0x0e, 0xf0, 0xa2, 0x8a, 0x28, 0x00, 0xa2, 0x8a, 0x28, 0x00, 0xa2, 0x8a, + 0x28, 0x00, 0xa2, 0x8a, 0x28, 0x00, 0xa2, 0x8a, 0x28, 0x00, 0xa2, 0x8a, + 0x28, 0x00, 0xa2, 0x8a, 0x28, 0x00, 0xa2, 0xa0, 0xbb, 0xbd, 0xb7, 0xb0, + 0x88, 0x49, 0x73, 0x3c, 0x56, 0xf1, 0x96, 0x0a, 0x1e, 0x57, 0x0a, 0x09, + 0x3d, 0x06, 0x4f, 0x7a, 0x9e, 0x95, 0xd3, 0x76, 0xea, 0x01, 0x45, 0x14, + 0x53, 0x00, 0xa2, 0x8a, 0x28, 0x00, 0xa2, 0x8a, 0x82, 0xda, 0xf6, 0xde, + 0xf0, 0xca, 0x2d, 0xe7, 0x8a, 0x73, 0x13, 0x98, 0xe4, 0xf2, 0xdc, 0x36, + 0xc6, 0x1d, 0x54, 0xe3, 0xa1, 0xf6, 0xa4, 0xda, 0x4e, 0xcc, 0x09, 0xe8, + 0xa2, 0x8a, 0x60, 0x14, 0x51, 0x45, 0x00, 0x14, 0x51, 0x45, 0x00, 0x14, + 0x51, 0x45, 0x00, 0x14, 0x51, 0x45, 0x00, 0x14, 0x51, 0x45, 0x00, 0x14, + 0x51, 0x45, 0x00, 0x14, 0x51, 0x45, 0x00, 0x14, 0x51, 0x45, 0x02, 0xb8, + 0x51, 0x45, 0x14, 0x05, 0xc2, 0x8a, 0x28, 0xa0, 0x2e, 0x14, 0x51, 0x45, + 0x01, 0x70, 0xa2, 0x8a, 0x28, 0x18, 0x51, 0x45, 0x14, 0x0a, 0xe1, 0x45, + 0x14, 0x50, 0x17, 0x0a, 0x28, 0xa2, 0x80, 0xb9, 0xca, 0xfc, 0x4a, 0xf0, + 0x52, 0x78, 0xef, 0xc2, 0xb7, 0x1a, 0x76, 0xef, 0x2e, 0xe5, 0x4f, 0x9d, + 0x6c, 0xe4, 0xe0, 0x09, 0x00, 0x38, 0xcf, 0xb1, 0xc9, 0x1f, 0x8e, 0x7b, + 0x57, 0x3d, 0xf0, 0x5b, 0xc7, 0x53, 0x6b, 0xba, 0x6c, 0xda, 0x16, 0xaa, + 0x5a, 0x3d, 0x73, 0x4a, 0xfd, 0xd4, 0x8b, 0x2f, 0xdf, 0x91, 0x01, 0xc0, + 0x27, 0xdc, 0x1e, 0x0f, 0xe0, 0x7b, 0xd7, 0xa3, 0x5c, 0xdc, 0xc5, 0x67, + 0x04, 0x93, 0xcf, 0x2a, 0x43, 0x0c, 0x60, 0xb3, 0xc9, 0x23, 0x05, 0x55, + 0x1e, 0xa4, 0x9e, 0x95, 0xf3, 0x47, 0xc4, 0x8f, 0x1f, 0xe9, 0x36, 0xdf, + 0x10, 0xed, 0x3c, 0x41, 0xe1, 0x39, 0x99, 0xaf, 0xa1, 0xe2, 0xea, 0x42, + 0x98, 0x82, 0x72, 0x38, 0xe3, 0x90, 0x4e, 0x46, 0x41, 0xe9, 0x9c, 0x0c, + 0x7a, 0xd7, 0xc4, 0x67, 0x98, 0x9a, 0x59, 0x3e, 0x26, 0x9e, 0x64, 0xa6, + 0x93, 0x7e, 0xec, 0xe3, 0x7d, 0x65, 0x1e, 0xe9, 0x77, 0x8b, 0xd7, 0xd3, + 0x4b, 0x99, 0x4d, 0xa8, 0xbe, 0x63, 0xe9, 0xca, 0x2b, 0xe4, 0x3d, 0x73, + 0xe3, 0x3f, 0x8b, 0xb5, 0xc6, 0x6d, 0xfa, 0xb4, 0x96, 0x71, 0x9e, 0x91, + 0x59, 0x0f, 0x28, 0x0f, 0xc4, 0x7c, 0xdf, 0x99, 0xae, 0x56, 0xe7, 0x5a, + 0xd4, 0x6f, 0x18, 0xb5, 0xc5, 0xfd, 0xd4, 0xec, 0x7b, 0xc9, 0x33, 0x31, + 0xfd, 0x4d, 0x78, 0x75, 0xf8, 0xfb, 0x0b, 0x07, 0x6a, 0x14, 0x65, 0x25, + 0xe6, 0xd2, 0xff, 0x00, 0x32, 0x1d, 0x75, 0xd1, 0x1f, 0x73, 0x51, 0x5f, + 0x0b, 0xdb, 0xea, 0xf7, 0xf6, 0xad, 0xba, 0x0b, 0xdb, 0x88, 0x58, 0x77, + 0x8e, 0x56, 0x53, 0xfa, 0x1a, 0xe9, 0xf4, 0x5f, 0x8b, 0xfe, 0x2e, 0xd0, + 0xd9, 0x7c, 0xad, 0x66, 0x7b, 0x84, 0x1f, 0xf2, 0xce, 0xec, 0xf9, 0xc0, + 0xff, 0x00, 0xdf, 0x59, 0x23, 0xf0, 0x34, 0xa8, 0x71, 0xf6, 0x1a, 0x4e, + 0xd5, 0xa8, 0x4a, 0x2b, 0xc9, 0xa7, 0xfe, 0x40, 0xab, 0xae, 0xa8, 0xfa, + 0x13, 0xe2, 0xff, 0x00, 0x8f, 0xcf, 0x82, 0xfc, 0x3e, 0x21, 0xb3, 0x6d, + 0xda, 0xcd, 0xfe, 0x62, 0xb5, 0x45, 0xe5, 0x97, 0xb1, 0x7c, 0x7b, 0x67, + 0x8f, 0x72, 0x3d, 0xea, 0x5f, 0x84, 0x7e, 0x05, 0x6f, 0x04, 0x78, 0x60, + 0x2d, 0xd1, 0x2d, 0xa9, 0xde, 0xb0, 0x9e, 0xe8, 0x93, 0x9d, 0xad, 0x8e, + 0x17, 0xf0, 0x1d, 0x4f, 0xa9, 0x35, 0xe2, 0x5e, 0x13, 0xf8, 0x89, 0x61, + 0xac, 0xfc, 0x49, 0x8f, 0xc4, 0x3e, 0x30, 0x76, 0xcc, 0x68, 0x16, 0xd8, + 0x43, 0x19, 0x68, 0x61, 0x61, 0xd0, 0x91, 0x92, 0x40, 0x1c, 0x9e, 0x33, + 0xc9, 0xcd, 0x7d, 0x3b, 0x63, 0x7f, 0x6d, 0xaa, 0x5a, 0x45, 0x75, 0x69, + 0x3c, 0x77, 0x36, 0xd2, 0x8d, 0xc9, 0x2c, 0x4c, 0x19, 0x58, 0x7b, 0x11, + 0x5e, 0xde, 0x4d, 0x8b, 0xa3, 0x9d, 0xe3, 0x2a, 0x66, 0x1c, 0xe9, 0xf2, + 0x5e, 0x30, 0x8f, 0x58, 0xae, 0xb2, 0x6b, 0xbc, 0xbf, 0x05, 0xa1, 0x50, + 0x6a, 0x6f, 0x98, 0xb1, 0x45, 0x14, 0x57, 0xdc, 0x9b, 0x5c, 0x28, 0xa2, + 0x8a, 0x02, 0xe1, 0x45, 0x14, 0x50, 0x17, 0x0a, 0x28, 0xa2, 0x80, 0xb8, + 0x51, 0x45, 0x14, 0x05, 0xc2, 0x8a, 0x28, 0xa0, 0x2e, 0x14, 0x51, 0x45, + 0x01, 0x70, 0xa2, 0x8a, 0x28, 0x0b, 0x8d, 0xcd, 0x19, 0xa6, 0xe4, 0x51, + 0x91, 0x55, 0x62, 0x47, 0x66, 0x8c, 0xd3, 0x72, 0x28, 0xc8, 0xa2, 0xc0, + 0x3b, 0x34, 0x66, 0x9b, 0x91, 0x46, 0x45, 0x16, 0x01, 0xd9, 0xa3, 0x34, + 0xdc, 0x8a, 0x32, 0x28, 0xb0, 0x0e, 0xcd, 0x19, 0xa6, 0xe4, 0x52, 0xe4, + 0x51, 0x60, 0xb8, 0xb9, 0xa3, 0x34, 0xdc, 0x8a, 0x32, 0x28, 0xb0, 0x0e, + 0xdd, 0x46, 0x69, 0xb9, 0x14, 0x64, 0x51, 0x60, 0x1d, 0x9a, 0xa7, 0xac, + 0x6b, 0x16, 0x9a, 0x0e, 0x9b, 0x71, 0xa8, 0x5f, 0x4c, 0x20, 0xb5, 0x81, + 0x37, 0xbb, 0x9e, 0xc3, 0xd0, 0x7a, 0x93, 0xd0, 0x0a, 0xb5, 0x91, 0x5f, + 0x39, 0xfe, 0xd1, 0x1e, 0x37, 0x7d, 0x4b, 0x5a, 0x4f, 0x0f, 0x5b, 0x48, + 0x45, 0xa5, 0x96, 0x1e, 0x70, 0xa7, 0xef, 0xca, 0x46, 0x40, 0x3f, 0xee, + 0x83, 0xf9, 0x93, 0xe9, 0x5e, 0x06, 0x79, 0x9a, 0xc3, 0x27, 0xc1, 0x4b, + 0x12, 0xd5, 0xe5, 0xb4, 0x57, 0x76, 0xff, 0x00, 0xab, 0xbf, 0x24, 0x44, + 0xe5, 0xca, 0xae, 0x72, 0xbf, 0x12, 0xbe, 0x2a, 0xea, 0x3e, 0x3e, 0xbd, + 0x78, 0xd5, 0x9e, 0xd3, 0x48, 0x46, 0xfd, 0xd5, 0xa2, 0x9f, 0xbd, 0xe8, + 0xcf, 0xea, 0x7f, 0x41, 0xdb, 0xd4, 0xc3, 0xe0, 0x5f, 0x85, 0x1a, 0xd7, + 0x8f, 0xed, 0xe6, 0xb9, 0xb1, 0xf2, 0x2d, 0xed, 0x22, 0x6d, 0x86, 0x7b, + 0x96, 0x21, 0x59, 0xb1, 0x9c, 0x0c, 0x02, 0x4f, 0x51, 0xf9, 0xd7, 0x19, + 0x5e, 0xcd, 0xf0, 0x73, 0xe3, 0x16, 0x97, 0xe1, 0x0d, 0x06, 0x4d, 0x23, + 0x57, 0x49, 0x63, 0x44, 0x95, 0xa5, 0x86, 0x78, 0x53, 0x78, 0x21, 0xba, + 0xab, 0x0e, 0xb9, 0xcf, 0x7f, 0x7f, 0x6a, 0xfc, 0x1b, 0x2e, 0xa9, 0x87, + 0xcd, 0xb3, 0x2f, 0x69, 0x9c, 0xd5, 0x6a, 0x2e, 0xfa, 0xde, 0xda, 0xf4, + 0x57, 0xe8, 0xbf, 0xe1, 0x8e, 0x48, 0xda, 0x52, 0xf7, 0x8f, 0x30, 0xf1, + 0x57, 0x85, 0x75, 0x0f, 0x06, 0xeb, 0x12, 0x69, 0xba, 0x94, 0x42, 0x3b, + 0x84, 0x01, 0x83, 0x21, 0xca, 0xba, 0x9e, 0x8c, 0xa7, 0xb8, 0xac, 0x8a, + 0xed, 0x3e, 0x2c, 0xf8, 0xee, 0x1f, 0x1f, 0xf8, 0x9c, 0x5e, 0xda, 0xc2, + 0xf0, 0xda, 0x41, 0x08, 0x82, 0x2f, 0x33, 0x01, 0xd8, 0x02, 0x49, 0x63, + 0xe9, 0xc9, 0x3c, 0x57, 0x17, 0x5e, 0x26, 0x3e, 0x9e, 0x1e, 0x96, 0x2a, + 0xa4, 0x30, 0xb2, 0xe6, 0xa6, 0x9b, 0xb3, 0xee, 0x88, 0x76, 0xbe, 0x81, + 0x5a, 0x1a, 0x06, 0x83, 0x7b, 0xe2, 0x7d, 0x5e, 0xdf, 0x4d, 0xd3, 0xe2, + 0xf3, 0xae, 0xa7, 0x38, 0x55, 0xce, 0x00, 0x00, 0x64, 0x92, 0x7b, 0x00, + 0x39, 0xac, 0xfa, 0xea, 0x3e, 0x1b, 0x78, 0xc1, 0x7c, 0x0d, 0xe2, 0xcb, + 0x5d, 0x52, 0x58, 0x4c, 0xf6, 0xe1, 0x5a, 0x39, 0x51, 0x3e, 0xf6, 0xd6, + 0x18, 0x24, 0x7b, 0x8e, 0x0d, 0x67, 0x83, 0x85, 0x1a, 0x98, 0x8a, 0x70, + 0xc4, 0x4b, 0x96, 0x0d, 0xae, 0x67, 0xd9, 0x5f, 0x50, 0x56, 0xbe, 0xa6, + 0x97, 0x8d, 0x7e, 0x0e, 0xeb, 0xde, 0x06, 0xd3, 0x17, 0x50, 0xbb, 0x36, + 0xf7, 0x56, 0x99, 0x0b, 0x24, 0x96, 0xae, 0x4f, 0x96, 0x4f, 0x4d, 0xc0, + 0x81, 0xc1, 0x3c, 0x66, 0xa9, 0xfc, 0x3e, 0xf8, 0x93, 0xaa, 0x78, 0x03, + 0x50, 0x0f, 0x6c, 0xe6, 0x7b, 0x07, 0x6f, 0xdf, 0xd9, 0x3b, 0x7c, 0x8e, + 0x3d, 0x47, 0xa3, 0x7b, 0xfe, 0x79, 0xaf, 0x45, 0xf8, 0xad, 0xf1, 0xb3, + 0x47, 0xf1, 0x27, 0x85, 0x26, 0xd2, 0x34, 0x84, 0x9a, 0x67, 0xbb, 0x2b, + 0xe6, 0xcb, 0x34, 0x7b, 0x04, 0x6a, 0x18, 0x36, 0x07, 0xa9, 0xc8, 0x1e, + 0xd5, 0xe1, 0x95, 0xf4, 0x39, 0xab, 0xc2, 0x65, 0x79, 0x84, 0x67, 0x93, + 0x55, 0x6d, 0x24, 0x9d, 0xd3, 0xbd, 0x9f, 0x55, 0x7e, 0xaa, 0xd6, 0xbe, + 0xfb, 0xd8, 0xb9, 0x5a, 0x32, 0xf7, 0x59, 0xf6, 0xef, 0x86, 0xbc, 0x49, + 0x63, 0xe2, 0xbd, 0x1a, 0xdf, 0x53, 0xd3, 0xe5, 0xf3, 0x2d, 0xe6, 0x1d, + 0xfe, 0xf2, 0x1e, 0xea, 0xc3, 0xb1, 0x15, 0xa9, 0x9a, 0xf9, 0x7b, 0xe0, + 0x27, 0x8d, 0xe4, 0xf0, 0xef, 0x8a, 0x53, 0x4a, 0x9e, 0x43, 0xfd, 0x9f, + 0xa9, 0x30, 0x8f, 0x69, 0x3c, 0x24, 0xdf, 0xc0, 0xc3, 0xeb, 0xf7, 0x7f, + 0x11, 0xe9, 0x5f, 0x4f, 0xe4, 0x57, 0xee, 0x3c, 0x3f, 0x9b, 0xc7, 0x39, + 0xc1, 0x2a, 0xed, 0x5a, 0x6b, 0x49, 0x2f, 0x3f, 0xf2, 0x7b, 0xfe, 0x1d, + 0x0e, 0xb8, 0x4f, 0x99, 0x5c, 0x76, 0x4d, 0x19, 0xa6, 0xe4, 0x51, 0x91, + 0x5f, 0x4b, 0x62, 0xc7, 0x64, 0xd1, 0x9a, 0x6e, 0x45, 0x19, 0x14, 0x58, + 0x2e, 0x3b, 0x34, 0x66, 0x9b, 0x91, 0x46, 0x45, 0x16, 0x0b, 0x8e, 0xcd, + 0x19, 0xa6, 0xe4, 0x51, 0x91, 0x45, 0x80, 0x76, 0x68, 0xcd, 0x37, 0x34, + 0x64, 0x51, 0x60, 0xb8, 0xec, 0xd1, 0x9a, 0x6e, 0x45, 0x19, 0x14, 0x58, + 0x07, 0x64, 0xd1, 0x9a, 0x6e, 0x45, 0x19, 0x14, 0x58, 0x06, 0x6e, 0xa3, + 0x75, 0x37, 0x34, 0x66, 0xae, 0xc4, 0x5c, 0x76, 0xea, 0x37, 0x53, 0x73, + 0x46, 0x68, 0xb0, 0x5c, 0x76, 0xea, 0x37, 0x53, 0x73, 0x46, 0x68, 0xb0, + 0x5c, 0x76, 0xea, 0x37, 0x53, 0x72, 0x28, 0xcd, 0x16, 0x0b, 0x8e, 0xdd, + 0x46, 0xea, 0x6e, 0x68, 0xcd, 0x16, 0x0b, 0x8e, 0xdd, 0x46, 0xea, 0x6e, + 0x68, 0xcd, 0x16, 0x0b, 0x8e, 0xdd, 0x46, 0xea, 0xc4, 0xf1, 0x57, 0x8c, + 0x34, 0xaf, 0x06, 0x69, 0xff, 0x00, 0x6b, 0xd5, 0x2e, 0x44, 0x28, 0xc7, + 0x08, 0x8a, 0x37, 0x3c, 0x87, 0xd1, 0x47, 0x7f, 0xe5, 0x5c, 0x0d, 0x9f, + 0xed, 0x1f, 0xe1, 0xcb, 0x8b, 0xc1, 0x14, 0xd6, 0x97, 0xf6, 0xb0, 0x93, + 0x81, 0x3b, 0xa2, 0xb0, 0x1e, 0xe4, 0x06, 0x27, 0xf2, 0xcd, 0x78, 0xf8, + 0xac, 0xdf, 0x2f, 0xc0, 0xd4, 0x54, 0x71, 0x35, 0xa3, 0x19, 0x3e, 0x8d, + 0xfe, 0x7d, 0xbe, 0x64, 0xb9, 0x25, 0xb9, 0xeb, 0x05, 0xf6, 0x82, 0x4f, + 0x41, 0x5f, 0x10, 0xeb, 0x7a, 0x93, 0xeb, 0x3a, 0xcd, 0xf5, 0xfc, 0x84, + 0x97, 0xb9, 0x9d, 0xe6, 0x39, 0xff, 0x00, 0x69, 0x89, 0xfe, 0xb5, 0xf6, + 0xad, 0x8e, 0xa1, 0x6b, 0xab, 0x58, 0xc5, 0x75, 0x69, 0x34, 0x77, 0x36, + 0xb3, 0x2e, 0xe4, 0x91, 0x0e, 0x55, 0x85, 0x78, 0x5f, 0xfc, 0x2d, 0x5f, + 0x87, 0x3f, 0xf4, 0x25, 0x27, 0xfe, 0x00, 0xdb, 0xff, 0x00, 0x8d, 0x7c, + 0x67, 0x18, 0xe1, 0xa8, 0x63, 0x61, 0x87, 0x55, 0x31, 0x31, 0xa7, 0x1f, + 0x79, 0xab, 0xdd, 0xf3, 0x7c, 0x3a, 0xab, 0x76, 0xfd, 0x4c, 0xea, 0x59, + 0xdb, 0x53, 0xc4, 0x68, 0xaf, 0x6e, 0xff, 0x00, 0x85, 0xab, 0xf0, 0xe7, + 0xfe, 0x84, 0xa4, 0xff, 0x00, 0xc0, 0x1b, 0x7f, 0xf1, 0xa3, 0xfe, 0x16, + 0xaf, 0xc3, 0x9f, 0xfa, 0x12, 0x93, 0xff, 0x00, 0x00, 0x6d, 0xff, 0x00, + 0xc6, 0xbf, 0x33, 0xfe, 0xc5, 0xc0, 0xff, 0x00, 0xd0, 0x7c, 0x3e, 0xe9, + 0x7f, 0x91, 0x8f, 0x2a, 0xee, 0x78, 0x8d, 0x15, 0xed, 0xdf, 0xf0, 0xb5, + 0x7e, 0x1c, 0xff, 0x00, 0xd0, 0x94, 0x9f, 0xf8, 0x03, 0x6f, 0xfe, 0x34, + 0x7f, 0xc2, 0xd5, 0xf8, 0x73, 0xff, 0x00, 0x42, 0x52, 0x7f, 0xe0, 0x0d, + 0xbf, 0xf8, 0xd1, 0xfd, 0x8b, 0x81, 0xff, 0x00, 0xa0, 0xf8, 0x7d, 0xd2, + 0xff, 0x00, 0x20, 0xe5, 0x5d, 0xcf, 0x11, 0xa2, 0xbd, 0xbb, 0xfe, 0x16, + 0xaf, 0xc3, 0x9f, 0xfa, 0x12, 0x93, 0xff, 0x00, 0x00, 0x6d, 0xff, 0x00, + 0xc6, 0x8f, 0xf8, 0x5a, 0xbf, 0x0e, 0x7f, 0xe8, 0x4a, 0x4f, 0xfc, 0x01, + 0xb7, 0xff, 0x00, 0x1a, 0x3f, 0xb1, 0x70, 0x3f, 0xf4, 0x1f, 0x0f, 0xba, + 0x5f, 0xe4, 0x1c, 0xab, 0xb9, 0xe2, 0x34, 0x57, 0xb7, 0x7f, 0xc2, 0xd5, + 0xf8, 0x73, 0xff, 0x00, 0x42, 0x52, 0x7f, 0xe0, 0x0d, 0xbf, 0xf8, 0xd1, + 0xff, 0x00, 0x0b, 0x57, 0xe1, 0xcf, 0xfd, 0x09, 0x49, 0xff, 0x00, 0x80, + 0x36, 0xff, 0x00, 0xe3, 0x47, 0xf6, 0x2e, 0x07, 0xfe, 0x83, 0xe1, 0xf7, + 0x4b, 0xfc, 0x83, 0x95, 0x77, 0x3c, 0x52, 0x09, 0xde, 0xda, 0x78, 0xe6, + 0x89, 0x8a, 0x49, 0x1b, 0x07, 0x56, 0x1d, 0x41, 0x07, 0x20, 0xd7, 0xdb, + 0xfa, 0x5d, 0xf0, 0xd4, 0x74, 0xdb, 0x4b, 0xb0, 0x30, 0x27, 0x85, 0x25, + 0x03, 0xfd, 0xe5, 0x07, 0xfa, 0xd7, 0x85, 0xff, 0x00, 0xc2, 0xd5, 0xf8, + 0x73, 0xff, 0x00, 0x42, 0x52, 0x7f, 0xe0, 0x0d, 0xbf, 0xf8, 0xd7, 0xb6, + 0x69, 0x1a, 0x95, 0xa5, 0xc7, 0x87, 0xec, 0xaf, 0xe1, 0x55, 0xb3, 0xb1, + 0x7b, 0x54, 0x99, 0x11, 0xf0, 0x82, 0x28, 0xca, 0x02, 0x01, 0xec, 0x30, + 0x3f, 0x0e, 0x2b, 0xf4, 0x7e, 0x0e, 0xc2, 0xd0, 0xc1, 0xce, 0xbc, 0x69, + 0x62, 0x63, 0x51, 0x34, 0x9b, 0x4a, 0xfa, 0x5a, 0xfa, 0xeb, 0xea, 0x6d, + 0x4e, 0xca, 0xfa, 0x9a, 0x5b, 0xa8, 0xdd, 0x5e, 0x57, 0xab, 0xfe, 0xd1, + 0x3e, 0x1b, 0xd3, 0xaf, 0x1a, 0x0b, 0x68, 0x6e, 0xf5, 0x15, 0x53, 0x83, + 0x34, 0x28, 0xaa, 0x87, 0xe9, 0xb8, 0x82, 0x7f, 0x2a, 0xeb, 0xbc, 0x1b, + 0xf1, 0x07, 0x45, 0xf1, 0xcd, 0xbb, 0xbe, 0x99, 0x70, 0x7c, 0xe8, 0xc6, + 0x64, 0xb6, 0x98, 0x6d, 0x91, 0x07, 0xa9, 0x1d, 0xc7, 0xb8, 0xc8, 0xaf, + 0xb7, 0xc3, 0xe7, 0x19, 0x76, 0x2a, 0xb7, 0xd5, 0xe8, 0x56, 0x8c, 0xa7, + 0xd9, 0x3f, 0xcb, 0xbf, 0xc8, 0xd1, 0x49, 0x3d, 0x2e, 0x74, 0xdb, 0xa8, + 0xcd, 0x37, 0x34, 0x64, 0x57, 0xb2, 0x55, 0xc7, 0x6e, 0xa3, 0x75, 0x37, + 0x34, 0x66, 0x8b, 0x05, 0xc7, 0x6e, 0xa3, 0x75, 0x37, 0x34, 0x66, 0x8b, + 0x05, 0xc7, 0x6e, 0xa3, 0x75, 0x37, 0x34, 0x66, 0x8b, 0x05, 0xc7, 0x6e, + 0xa3, 0x75, 0x37, 0x34, 0x64, 0x51, 0x60, 0xb8, 0xed, 0xd4, 0x6e, 0xa6, + 0xe4, 0x51, 0x9a, 0x2c, 0x17, 0x1b, 0x9a, 0x33, 0x51, 0xee, 0xa3, 0x75, + 0x55, 0x88, 0xb9, 0x26, 0x68, 0xcd, 0x47, 0xba, 0x8d, 0xd4, 0x58, 0x2e, + 0x49, 0x9a, 0x33, 0x51, 0xee, 0xa3, 0x75, 0x16, 0x15, 0xc9, 0x32, 0x28, + 0xa8, 0xf7, 0x51, 0xba, 0x8b, 0x0e, 0xe4, 0x99, 0xa3, 0x35, 0x1e, 0xea, + 0x37, 0x51, 0x60, 0xb9, 0x26, 0x68, 0xcd, 0x47, 0xba, 0x8c, 0xd3, 0xb0, + 0x5c, 0xf9, 0x7b, 0xe3, 0xb6, 0xaf, 0x3e, 0xa5, 0xf1, 0x0e, 0xf2, 0xde, + 0x47, 0x26, 0x1b, 0x24, 0x48, 0x62, 0x4e, 0xc0, 0x15, 0x0c, 0x4f, 0xe2, + 0x58, 0xfe, 0x95, 0xe7, 0x95, 0xda, 0x7c, 0x64, 0xff, 0x00, 0x92, 0x97, + 0xad, 0xff, 0x00, 0xbf, 0x1f, 0xfe, 0x8a, 0x4a, 0xe2, 0xeb, 0xf9, 0x47, + 0x3a, 0x9c, 0xaa, 0x66, 0x78, 0x99, 0x49, 0xdd, 0xf3, 0xcb, 0xf0, 0x6d, + 0x23, 0x92, 0x5b, 0x9e, 0xf7, 0xfb, 0x34, 0xeb, 0x13, 0xcd, 0x67, 0xac, + 0xe9, 0xb2, 0x39, 0x6b, 0x78, 0x1a, 0x39, 0xa2, 0x04, 0xfd, 0xd2, 0xdb, + 0x83, 0x0f, 0xc7, 0x68, 0xfd, 0x6b, 0xc1, 0x2b, 0xda, 0xff, 0x00, 0x66, + 0x73, 0x8b, 0xed, 0x7f, 0xfe, 0xb9, 0xc3, 0xfc, 0xde, 0xbc, 0x52, 0xbd, + 0x8c, 0xd2, 0x72, 0x9e, 0x4b, 0x97, 0x39, 0x3b, 0xdb, 0xda, 0xaf, 0x92, + 0x92, 0xb1, 0x4f, 0xe1, 0x41, 0x45, 0x14, 0x57, 0xc6, 0x90, 0x14, 0x51, + 0x45, 0x00, 0x14, 0x51, 0x45, 0x00, 0x14, 0x51, 0x45, 0x00, 0x15, 0xef, + 0x7f, 0x13, 0xf5, 0x89, 0xf4, 0xef, 0x82, 0xbe, 0x19, 0xb6, 0x81, 0xca, + 0x0b, 0xc8, 0x2d, 0x62, 0x94, 0x83, 0xd5, 0x04, 0x3b, 0x88, 0xfc, 0x48, + 0x15, 0xe0, 0x95, 0xed, 0x7f, 0x17, 0x0f, 0xfc, 0x5a, 0x5f, 0x05, 0xff, + 0x00, 0xd7, 0x38, 0x3f, 0xf4, 0x45, 0x7d, 0x96, 0x47, 0x39, 0x43, 0x03, + 0x98, 0x4a, 0x2e, 0xcf, 0x91, 0x7e, 0x32, 0xb3, 0xfc, 0x0a, 0x8e, 0xcc, + 0xf1, 0x4a, 0xe9, 0xbe, 0x1a, 0x6a, 0xf3, 0xe8, 0xbe, 0x3a, 0xd1, 0x67, + 0x81, 0xca, 0x99, 0x2e, 0x52, 0x07, 0x00, 0xfd, 0xe4, 0x76, 0x0a, 0xc0, + 0xfe, 0x07, 0xf4, 0xae, 0x66, 0xb6, 0x3c, 0x1b, 0xff, 0x00, 0x23, 0x7e, + 0x87, 0xff, 0x00, 0x5f, 0xd0, 0x7f, 0xe8, 0xc5, 0xaf, 0x9b, 0xc0, 0xce, + 0x54, 0xf1, 0x54, 0xa7, 0x07, 0x66, 0xa4, 0xbf, 0x31, 0x2d, 0xcf, 0xb4, + 0x33, 0x46, 0x6a, 0x3d, 0xd4, 0x6e, 0xaf, 0xeb, 0x9b, 0x1d, 0x57, 0x24, + 0xcd, 0x19, 0xa8, 0xf7, 0x51, 0xba, 0x8b, 0x0e, 0xe4, 0x99, 0xa3, 0x35, + 0x1e, 0xea, 0x37, 0x51, 0x60, 0xb9, 0x26, 0x68, 0xcd, 0x47, 0xba, 0x8d, + 0xd4, 0x58, 0x2e, 0x49, 0x9a, 0x33, 0x51, 0xee, 0xa3, 0x75, 0x16, 0x15, + 0xc9, 0x33, 0x46, 0x6a, 0x3d, 0xd4, 0x6e, 0xa2, 0xc3, 0xb8, 0xdd, 0xd4, + 0x9b, 0xa9, 0xbb, 0xa8, 0xdd, 0x5a, 0x19, 0xdc, 0x7e, 0xea, 0x4d, 0xd4, + 0xdd, 0xd4, 0x6e, 0xa0, 0x57, 0x1f, 0xba, 0x8d, 0xd4, 0xcd, 0xd4, 0x6e, + 0xa0, 0x77, 0x1f, 0xba, 0x8d, 0xd4, 0xcd, 0xd4, 0x6e, 0xa4, 0x2b, 0x8f, + 0xdd, 0x49, 0xba, 0x9b, 0xba, 0x8d, 0xd4, 0xc2, 0xe3, 0xb7, 0x52, 0xee, + 0xa6, 0x6e, 0xa3, 0x75, 0x20, 0xb9, 0xf2, 0x9f, 0xc6, 0x3f, 0xf9, 0x29, + 0x5a, 0xdf, 0xfb, 0xf1, 0xff, 0x00, 0xe8, 0xb4, 0xae, 0x32, 0xbb, 0x2f, + 0x8c, 0x5c, 0xfc, 0x49, 0xd6, 0xff, 0x00, 0xdf, 0x8f, 0xff, 0x00, 0x45, + 0xa5, 0x71, 0xb5, 0xfc, 0x99, 0x9c, 0x7f, 0xc8, 0xcb, 0x13, 0xfe, 0x39, + 0xff, 0x00, 0xe9, 0x4c, 0xc1, 0xee, 0x7b, 0x57, 0xec, 0xd2, 0x71, 0x7d, + 0xaf, 0x7f, 0xd7, 0x38, 0x7f, 0x9b, 0xd7, 0x8a, 0xd7, 0xb4, 0x7e, 0xcd, + 0x67, 0x17, 0xda, 0xf7, 0xfd, 0x73, 0x87, 0xf9, 0xbd, 0x78, 0xbd, 0x7b, + 0x19, 0x97, 0xfc, 0x89, 0x32, 0xef, 0xfb, 0x8b, 0xff, 0x00, 0xa5, 0x21, + 0xbd, 0x90, 0x51, 0x45, 0x15, 0xf2, 0x02, 0x0a, 0x28, 0xa2, 0x80, 0x0a, + 0x28, 0xa2, 0x80, 0x0a, 0x28, 0xa2, 0x80, 0x0a, 0xf6, 0xaf, 0x8b, 0x47, + 0x3f, 0x09, 0x7c, 0x17, 0xff, 0x00, 0x5c, 0xe0, 0xff, 0x00, 0xd1, 0x15, + 0xe2, 0xb5, 0xed, 0x1f, 0x16, 0x4f, 0xfc, 0x5a, 0x6f, 0x06, 0x7f, 0xd7, + 0x38, 0x3f, 0xf4, 0x45, 0x7d, 0x7e, 0x4d, 0xff, 0x00, 0x22, 0xfc, 0xc3, + 0xfc, 0x11, 0xff, 0x00, 0xd2, 0x90, 0xd6, 0xcc, 0xf1, 0x7a, 0xd8, 0xf0, + 0x67, 0xfc, 0x8e, 0x1a, 0x17, 0xfd, 0x7f, 0xc1, 0xff, 0x00, 0xa3, 0x16, + 0xb1, 0xeb, 0x63, 0xc1, 0xdf, 0xf2, 0x37, 0x68, 0x7f, 0xf5, 0xfd, 0x07, + 0xfe, 0x8c, 0x5a, 0xf9, 0xbc, 0x27, 0xfb, 0xcd, 0x3f, 0xf1, 0x2f, 0xcc, + 0x48, 0xfb, 0x2b, 0x75, 0x1b, 0xa9, 0x9b, 0xa8, 0xdd, 0x5f, 0xd8, 0x16, + 0x37, 0xb8, 0xfd, 0xd4, 0x6e, 0xa6, 0x6e, 0xa3, 0x75, 0x20, 0xb8, 0xed, + 0xd4, 0xbb, 0xa9, 0x9b, 0xa8, 0xdd, 0x4c, 0x2e, 0x3f, 0x75, 0x26, 0xea, + 0x6e, 0xea, 0x37, 0x52, 0x0b, 0x8f, 0xdd, 0x49, 0xba, 0x9b, 0xba, 0x8d, + 0xd4, 0xec, 0x3b, 0x8f, 0xdd, 0x49, 0xba, 0x9b, 0xba, 0x8d, 0xd4, 0x0a, + 0xe4, 0x74, 0x53, 0x33, 0x4b, 0x93, 0x57, 0x63, 0x3b, 0x8e, 0xa2, 0x9b, + 0x9a, 0x4c, 0xd1, 0x60, 0xb8, 0xfa, 0x29, 0x99, 0xa3, 0x34, 0x58, 0x2e, + 0x3f, 0x34, 0x53, 0x33, 0x4b, 0x93, 0x45, 0x82, 0xe3, 0xa8, 0xcd, 0x33, + 0x34, 0x66, 0x8b, 0x05, 0xc7, 0xd1, 0x4d, 0xc9, 0xa3, 0x26, 0x8b, 0x05, + 0xcf, 0x96, 0x3e, 0x30, 0x7f, 0xc9, 0x48, 0xd6, 0xbf, 0xdf, 0x8f, 0xff, + 0x00, 0x45, 0xa5, 0x71, 0xb5, 0xdd, 0xfc, 0x6c, 0xd3, 0xa6, 0xb1, 0xf8, + 0x85, 0x7f, 0x2c, 0x8a, 0x44, 0x77, 0x4b, 0x1c, 0xd1, 0xb7, 0x66, 0x1b, + 0x02, 0x9f, 0xd5, 0x4d, 0x70, 0x95, 0xfc, 0x97, 0x9d, 0x42, 0x50, 0xcc, + 0xf1, 0x31, 0x92, 0xb7, 0xbf, 0x2f, 0xfd, 0x29, 0x90, 0x7b, 0x3f, 0xec, + 0xdb, 0xff, 0x00, 0x1f, 0xba, 0xef, 0xfd, 0x73, 0x87, 0xf9, 0xbd, 0x78, + 0xc5, 0x7b, 0x87, 0xec, 0xe3, 0xa7, 0x4d, 0x1c, 0x3a, 0xd5, 0xf3, 0x29, + 0x58, 0x24, 0x31, 0xc2, 0x8d, 0xfd, 0xe2, 0x37, 0x16, 0xfc, 0xb2, 0x3f, + 0x3a, 0xf0, 0xfa, 0xf6, 0x73, 0x58, 0x4a, 0x19, 0x26, 0x5b, 0xcc, 0xad, + 0x7f, 0x6a, 0xff, 0x00, 0xf2, 0x64, 0x01, 0x45, 0x14, 0x57, 0xc6, 0x00, + 0x51, 0x45, 0x14, 0x00, 0x51, 0x45, 0x14, 0x00, 0x51, 0x45, 0x14, 0x00, + 0x57, 0xb3, 0xfc, 0x57, 0xff, 0x00, 0x92, 0x51, 0xe0, 0xdf, 0xfa, 0xe7, + 0x07, 0xfe, 0x88, 0xaf, 0x18, 0xaf, 0x70, 0xf8, 0x9b, 0xa7, 0x4d, 0x79, + 0xf0, 0x77, 0xc3, 0x37, 0x11, 0x29, 0x74, 0xb5, 0x8a, 0xd9, 0xe4, 0xc7, + 0x65, 0x30, 0xed, 0xcf, 0xe6, 0x40, 0xfc, 0x6b, 0xec, 0xf2, 0x38, 0x4a, + 0x78, 0x0c, 0xc1, 0x45, 0x5f, 0xdc, 0x5f, 0x84, 0xae, 0xc0, 0xf0, 0xfa, + 0xd8, 0xf0, 0x6f, 0xfc, 0x8d, 0xfa, 0x1f, 0xfd, 0x7f, 0x41, 0xff, 0x00, + 0xa3, 0x16, 0xb1, 0xeb, 0xa2, 0xf8, 0x79, 0xa7, 0x4d, 0xaa, 0x78, 0xdf, + 0x45, 0x86, 0x15, 0x2c, 0xcb, 0x75, 0x1c, 0xad, 0x8e, 0xca, 0x8c, 0x18, + 0x9f, 0xc8, 0x57, 0xcd, 0x60, 0x61, 0x29, 0xe2, 0xe9, 0x46, 0x2a, 0xed, + 0xca, 0x3f, 0x9a, 0x03, 0xeb, 0x9c, 0xd1, 0x4c, 0xcd, 0x2e, 0x4d, 0x7f, + 0x60, 0x58, 0xbb, 0x8e, 0xa2, 0x99, 0x9a, 0x33, 0x45, 0x82, 0xe3, 0xe8, + 0xa6, 0x66, 0x8c, 0xd1, 0x60, 0xb8, 0xfa, 0x29, 0x99, 0xa5, 0xc9, 0xa2, + 0xc1, 0x71, 0xd9, 0xa2, 0x9b, 0x93, 0x49, 0x9a, 0x2c, 0x17, 0x1f, 0x45, + 0x33, 0x34, 0x66, 0x8b, 0x05, 0xc6, 0x6e, 0xa3, 0x75, 0x30, 0x90, 0x3a, + 0x9c, 0x51, 0x9a, 0xd2, 0xc6, 0x57, 0x1f, 0xba, 0x8d, 0xd4, 0xda, 0x4c, + 0xd1, 0x60, 0xb8, 0xfd, 0xd4, 0x6e, 0xa6, 0x02, 0x0f, 0x43, 0x9a, 0x37, + 0x01, 0xdf, 0x14, 0x58, 0x2e, 0x3f, 0x75, 0x1b, 0xa9, 0xb4, 0x94, 0x58, + 0x2e, 0x3f, 0x75, 0x1b, 0xa9, 0xb4, 0x80, 0x83, 0xd0, 0xe6, 0x8b, 0x05, + 0xc7, 0xee, 0xa3, 0x75, 0x30, 0x90, 0x3a, 0x9c, 0x51, 0x9a, 0x2c, 0x17, + 0x31, 0x3c, 0x5d, 0xe0, 0xcd, 0x2f, 0xc6, 0xb6, 0x2b, 0x6f, 0xa8, 0xc4, + 0x4b, 0x26, 0x4c, 0x53, 0xc6, 0x71, 0x24, 0x64, 0xf5, 0xc1, 0xfe, 0x87, + 0x8a, 0xe0, 0x6d, 0x3f, 0x67, 0x7d, 0x32, 0x2b, 0xb0, 0xf7, 0x1a, 0xad, + 0xcc, 0xf6, 0xe0, 0xe7, 0xca, 0x58, 0xd5, 0x09, 0x1e, 0x85, 0xb2, 0x7f, + 0x95, 0x7a, 0xd5, 0x25, 0x78, 0x38, 0xcc, 0x87, 0x2c, 0xcc, 0x2b, 0x2a, + 0xf8, 0x9a, 0x2a, 0x52, 0xef, 0xaa, 0xfb, 0xec, 0xd5, 0xfe, 0x77, 0x0b, + 0x95, 0xf4, 0xad, 0x32, 0xd3, 0x44, 0xb0, 0x86, 0xca, 0xc6, 0x05, 0xb7, + 0xb6, 0x88, 0x61, 0x23, 0x4e, 0x83, 0xfc, 0x4f, 0xbd, 0x7c, 0x6b, 0x5f, + 0x69, 0x57, 0xc5, 0xb5, 0xf9, 0x8f, 0x88, 0xb0, 0x8d, 0x38, 0xe0, 0xe1, + 0x05, 0x64, 0xb9, 0xec, 0x97, 0xfd, 0xb8, 0x34, 0x14, 0x51, 0x45, 0x7e, + 0x32, 0x30, 0xa2, 0x8a, 0x28, 0x00, 0xa2, 0x8a, 0x28, 0x00, 0xa2, 0x8a, + 0x28, 0x00, 0xaf, 0xad, 0xfc, 0x2b, 0x04, 0x57, 0x9e, 0x06, 0xd1, 0xad, + 0xe7, 0x8d, 0x66, 0x86, 0x4d, 0x3a, 0x04, 0x78, 0xdc, 0x65, 0x58, 0x18, + 0xd7, 0x20, 0x8a, 0xf9, 0x22, 0xbe, 0xba, 0xf0, 0x67, 0xfc, 0x89, 0xfa, + 0x17, 0xfd, 0x78, 0x41, 0xff, 0x00, 0xa2, 0xd6, 0xbf, 0x5c, 0xf0, 0xee, + 0x2a, 0x58, 0x9c, 0x42, 0x7b, 0x72, 0xaf, 0xcc, 0x47, 0x05, 0xab, 0x7e, + 0xcf, 0x7a, 0x4d, 0xdd, 0xdb, 0x4b, 0x65, 0xa8, 0x4f, 0x63, 0x13, 0x1c, + 0xf9, 0x25, 0x04, 0x80, 0x7b, 0x02, 0x48, 0x3f, 0x9e, 0x6b, 0xaf, 0xf0, + 0x57, 0xc3, 0xcd, 0x27, 0xc0, 0xd1, 0xb9, 0xb3, 0x47, 0x9a, 0xee, 0x41, + 0xb6, 0x4b, 0xa9, 0xb0, 0x5c, 0x8f, 0x41, 0xe8, 0x3d, 0x87, 0xe3, 0x5d, + 0x36, 0x69, 0x6b, 0xf5, 0x7c, 0x37, 0x0f, 0xe5, 0x78, 0x3a, 0xff, 0x00, + 0x59, 0xa1, 0x41, 0x46, 0x7d, 0xf5, 0xd3, 0xd1, 0x6c, 0xbe, 0x49, 0x0a, + 0xe3, 0xb7, 0x51, 0xba, 0x99, 0x9a, 0x03, 0x03, 0xde, 0xbd, 0xfb, 0x05, + 0xc7, 0xee, 0xa3, 0x75, 0x30, 0x90, 0x06, 0x4f, 0x14, 0x51, 0x60, 0xb8, + 0xfd, 0xd4, 0x6e, 0xa6, 0xd1, 0x45, 0x82, 0xe3, 0xb7, 0x51, 0xba, 0x98, + 0x08, 0x3d, 0x0e, 0x68, 0x24, 0x0e, 0xf4, 0x58, 0x2e, 0x3f, 0x75, 0x1b, + 0xa9, 0x94, 0xb4, 0x58, 0x2e, 0x3b, 0x75, 0x1b, 0xa9, 0x99, 0xa0, 0x10, + 0x7a, 0x1a, 0x2c, 0x17, 0x31, 0x62, 0xbd, 0x91, 0x46, 0xf7, 0x90, 0x3e, + 0xd3, 0xf7, 0x1b, 0xa9, 0xad, 0x58, 0xa5, 0x13, 0x46, 0xae, 0xbd, 0x08, + 0xac, 0x3f, 0x38, 0xff, 0x00, 0x71, 0x3f, 0xef, 0x91, 0x56, 0x52, 0xf1, + 0xe3, 0x48, 0x55, 0x42, 0x80, 0x7a, 0x80, 0x3d, 0xeb, 0xb2, 0x70, 0xbe, + 0xc7, 0x9d, 0x4e, 0xaf, 0x2e, 0xec, 0xd6, 0xcd, 0x36, 0x59, 0x44, 0x31, + 0xb3, 0xb7, 0x41, 0x49, 0xba, 0xb3, 0x1e, 0xf2, 0x49, 0x12, 0x65, 0x60, + 0x08, 0x1d, 0x01, 0x1e, 0xf5, 0x84, 0x61, 0xcc, 0x75, 0x4e, 0xa7, 0x22, + 0x1b, 0x2d, 0xec, 0x8c, 0x0b, 0xa4, 0x81, 0x37, 0x1f, 0xb8, 0xbd, 0x7e, + 0xb4, 0xb1, 0x5e, 0xc8, 0xa0, 0x3b, 0xb8, 0x7d, 0xa7, 0xee, 0x37, 0x5f, + 0xad, 0x57, 0xf3, 0x8f, 0xf7, 0x13, 0xfe, 0xf9, 0x14, 0x79, 0xc7, 0xfb, + 0x89, 0xff, 0x00, 0x7c, 0x8a, 0xeb, 0xe5, 0x56, 0xb5, 0x8e, 0x0e, 0x77, + 0x7b, 0xdc, 0xdd, 0x8e, 0x41, 0x2a, 0x2b, 0xaf, 0x42, 0x33, 0x4e, 0xac, + 0xa8, 0xef, 0x1d, 0x3c, 0x85, 0x00, 0x05, 0x3d, 0x40, 0x1e, 0xe6, 0xb4, + 0x77, 0x57, 0x24, 0xa3, 0xca, 0x77, 0xc2, 0x7c, 0xe8, 0x74, 0x92, 0x08, + 0xd1, 0x99, 0x8f, 0x00, 0x66, 0xb2, 0xa5, 0xbd, 0x91, 0x81, 0x74, 0x70, + 0x99, 0x38, 0xd8, 0x3a, 0xfd, 0x69, 0xd2, 0x5e, 0x3b, 0xf9, 0xea, 0x40, + 0x2a, 0x3a, 0x02, 0x3d, 0xc5, 0x55, 0xf3, 0x8f, 0xf7, 0x13, 0xfe, 0xf9, + 0x15, 0xbc, 0x21, 0x6d, 0xce, 0x6a, 0x95, 0x6f, 0xa2, 0x64, 0xf1, 0x5e, + 0x48, 0xa3, 0x7b, 0x48, 0x1f, 0x69, 0xfb, 0x8d, 0xd4, 0xd6, 0xac, 0x52, + 0x89, 0xa3, 0x57, 0x5e, 0x86, 0xb0, 0xfc, 0xe3, 0xfd, 0xc4, 0xff, 0x00, + 0xbe, 0x45, 0x59, 0x4b, 0xc7, 0x8d, 0x61, 0x0a, 0x14, 0x03, 0xd4, 0x01, + 0xef, 0x44, 0xe1, 0x7d, 0x85, 0x4e, 0xaf, 0x2e, 0xec, 0xd6, 0xcd, 0x15, + 0x1e, 0xea, 0x5d, 0xd5, 0xcd, 0x63, 0xba, 0xe3, 0xeb, 0xc0, 0xbc, 0x71, + 0xf0, 0x57, 0x55, 0x83, 0x57, 0x9e, 0xe7, 0x44, 0x85, 0x6f, 0x6c, 0xa6, + 0x72, 0xe2, 0x25, 0x70, 0xaf, 0x16, 0x4e, 0x76, 0xe0, 0x91, 0x91, 0xe9, + 0x8a, 0xf7, 0xad, 0xd4, 0x66, 0xbe, 0x7f, 0x39, 0xc8, 0xf0, 0x99, 0xe5, + 0x18, 0xd2, 0xc4, 0xdd, 0x72, 0xbb, 0xa6, 0xb7, 0x5d, 0xfb, 0xef, 0xe8, + 0x17, 0xb1, 0xf2, 0xc5, 0xff, 0x00, 0xc3, 0x5f, 0x12, 0xe9, 0x96, 0x53, + 0x5d, 0xdd, 0x69, 0x52, 0x43, 0x6f, 0x0a, 0x97, 0x92, 0x42, 0xe8, 0x42, + 0x81, 0xd4, 0xf0, 0x6b, 0x99, 0xaf, 0xaa, 0xfe, 0x21, 0x9c, 0xf8, 0x1f, + 0x5c, 0xff, 0x00, 0xaf, 0x47, 0xfe, 0x55, 0xf2, 0xa5, 0x7e, 0x01, 0xc5, + 0x59, 0x1e, 0x1f, 0x22, 0xc4, 0x53, 0xa3, 0x87, 0x93, 0x92, 0x94, 0x6f, + 0xef, 0x5b, 0xbd, 0xba, 0x24, 0x5a, 0x77, 0x0a, 0xe9, 0xec, 0xbe, 0x19, + 0xf8, 0x9b, 0x51, 0xb3, 0x86, 0xea, 0xdb, 0x49, 0x92, 0x5b, 0x79, 0x90, + 0x49, 0x1b, 0x87, 0x40, 0x19, 0x48, 0xc8, 0x3d, 0x6b, 0x98, 0xaf, 0xac, + 0x3c, 0x08, 0x71, 0xe0, 0xad, 0x0b, 0xfe, 0xbc, 0xa1, 0xff, 0x00, 0xd0, + 0x05, 0x57, 0x0a, 0xe4, 0x58, 0x7c, 0xf6, 0xbd, 0x5a, 0x58, 0x89, 0x4a, + 0x2a, 0x2a, 0xfe, 0xed, 0xbb, 0xdb, 0xaa, 0x60, 0xdd, 0x8f, 0x9e, 0xdb, + 0xe1, 0x4f, 0x8a, 0xd1, 0x4b, 0x1d, 0x1a, 0x50, 0x00, 0xc9, 0x3e, 0x62, + 0x7f, 0xf1, 0x55, 0xc9, 0xd7, 0xd9, 0x17, 0x4d, 0xfe, 0x8d, 0x37, 0xfb, + 0x87, 0xf9, 0x57, 0xc6, 0xf5, 0xd1, 0xc5, 0x9c, 0x3d, 0x86, 0xc8, 0x5d, + 0x05, 0x87, 0x9c, 0xa5, 0xcf, 0xcd, 0x7e, 0x6b, 0x74, 0xb6, 0xd6, 0x4b, + 0xb8, 0x27, 0x70, 0xae, 0x87, 0x47, 0xf0, 0x07, 0x88, 0x35, 0xfb, 0x04, + 0xbd, 0xb0, 0xd3, 0x64, 0xb9, 0xb5, 0x72, 0x42, 0xc8, 0xae, 0xa0, 0x12, + 0x0e, 0x0f, 0x53, 0xeb, 0x5c, 0xf5, 0x7d, 0x27, 0xf0, 0x54, 0xe3, 0xe1, + 0xed, 0x8f, 0xfd, 0x74, 0x97, 0xff, 0x00, 0x43, 0x35, 0xe7, 0x70, 0xbe, + 0x4f, 0x43, 0x3c, 0xc6, 0xcb, 0x0d, 0x5e, 0x4d, 0x25, 0x16, 0xf4, 0xb5, + 0xee, 0x9a, 0x5d, 0x53, 0xee, 0x0d, 0xd8, 0xf3, 0x1f, 0x0d, 0x7c, 0x12, + 0xd7, 0xb5, 0x3d, 0x42, 0x31, 0xa9, 0x40, 0x34, 0xdb, 0x20, 0xc0, 0xc8, + 0xee, 0xea, 0xce, 0x47, 0x70, 0xa0, 0x13, 0xcf, 0xb9, 0xe2, 0xbe, 0x87, + 0xb7, 0x82, 0x3b, 0x4b, 0x78, 0xa0, 0x89, 0x42, 0x45, 0x12, 0x84, 0x45, + 0x1d, 0x80, 0x18, 0x02, 0x97, 0x34, 0x9b, 0xab, 0xfa, 0x07, 0x25, 0xe1, + 0xfc, 0x1e, 0x45, 0x09, 0x47, 0x0d, 0x76, 0xe5, 0xbb, 0x7a, 0xbd, 0x36, + 0x5a, 0x24, 0xad, 0xf2, 0x22, 0xf7, 0x24, 0xcd, 0x19, 0xa8, 0xf7, 0x51, + 0xba, 0xbe, 0x96, 0xc1, 0x71, 0xd2, 0x48, 0x22, 0x46, 0x76, 0xe0, 0x0e, + 0x6b, 0x2a, 0x5b, 0xd9, 0x18, 0x17, 0x47, 0x11, 0xe4, 0xe3, 0x60, 0xeb, + 0xf5, 0xa7, 0x49, 0x78, 0xef, 0xe7, 0xa9, 0x00, 0xa8, 0xe8, 0x08, 0xf7, + 0x02, 0xaa, 0xf9, 0xc7, 0xfb, 0x89, 0xff, 0x00, 0x7c, 0x8a, 0xe9, 0x84, + 0x2d, 0xb9, 0xc5, 0x56, 0xaf, 0x36, 0x89, 0x93, 0xc5, 0x7b, 0x22, 0x8d, + 0xef, 0x20, 0x7d, 0xa7, 0xee, 0x37, 0x53, 0x5a, 0xd1, 0x4a, 0x26, 0x8d, + 0x5d, 0x7a, 0x1a, 0xc2, 0xf3, 0x8f, 0xf7, 0x13, 0xfe, 0xf9, 0x15, 0x65, + 0x2f, 0x1e, 0x34, 0x84, 0x28, 0x50, 0x09, 0xe4, 0x01, 0xef, 0x44, 0xe1, + 0x7d, 0x85, 0x4e, 0xaf, 0x2e, 0xec, 0xd6, 0xa6, 0xcb, 0x28, 0x86, 0x36, + 0x76, 0xe8, 0x29, 0x37, 0x56, 0x63, 0xde, 0x3c, 0x89, 0x30, 0x60, 0xa4, + 0x0e, 0x80, 0x8f, 0x7a, 0xc2, 0x30, 0xe6, 0x3a, 0xa7, 0x53, 0x91, 0x0d, + 0x96, 0xf6, 0x46, 0x05, 0xd2, 0x40, 0x9b, 0x8f, 0xdc, 0x5e, 0xa3, 0xde, + 0x96, 0x2b, 0xd9, 0x14, 0x07, 0x77, 0x0f, 0xb4, 0xfd, 0xc6, 0xeb, 0xf5, + 0xaa, 0xfe, 0x71, 0xfe, 0xe2, 0x7f, 0xdf, 0x22, 0x8f, 0x38, 0xff, 0x00, + 0x71, 0x3f, 0xef, 0x91, 0x5d, 0x7c, 0xaa, 0xd6, 0xb1, 0xc1, 0xce, 0xef, + 0x7b, 0x9b, 0xb1, 0xc8, 0x25, 0x45, 0x75, 0xe8, 0x46, 0x69, 0xd5, 0x95, + 0x1d, 0xe3, 0xa0, 0x81, 0x46, 0x02, 0x9e, 0xa0, 0x0f, 0x72, 0x2b, 0x4b, + 0x35, 0xc9, 0x28, 0xf2, 0x9d, 0xf0, 0xa9, 0xce, 0x85, 0x92, 0x41, 0x1a, + 0x33, 0xb7, 0x40, 0x32, 0x6b, 0x2a, 0x5b, 0xd9, 0x1c, 0x17, 0x47, 0x09, + 0x93, 0x8d, 0x83, 0xaf, 0xd6, 0x9d, 0x25, 0xe3, 0xbf, 0x9e, 0xa4, 0x02, + 0xa3, 0xa0, 0x23, 0xdc, 0x55, 0x5f, 0x38, 0xff, 0x00, 0x71, 0x3f, 0xef, + 0x91, 0x5b, 0xc2, 0x16, 0xdc, 0xe6, 0xa9, 0x57, 0x9b, 0x44, 0xc8, 0xea, + 0x70, 0xa4, 0x88, 0x30, 0x09, 0xff, 0x00, 0xf5, 0xd3, 0x4d, 0xbb, 0x09, + 0x84, 0x59, 0x1b, 0xbd, 0x7b, 0x56, 0x95, 0xb4, 0x66, 0x18, 0x42, 0x13, + 0x92, 0x3d, 0x2a, 0xe5, 0x2b, 0x23, 0x2a, 0x70, 0x72, 0x6d, 0x32, 0x7a, + 0xc8, 0x2a, 0x40, 0x9f, 0x20, 0x8f, 0xff, 0x00, 0x5d, 0x6a, 0xe6, 0xa3, + 0xb9, 0x8c, 0xcf, 0x09, 0x40, 0x40, 0x27, 0xd6, 0xb1, 0x83, 0xe5, 0x3a, + 0x6a, 0x47, 0x99, 0x5c, 0xc7, 0xa2, 0xa5, 0x5b, 0x76, 0x69, 0x8c, 0x59, + 0x1b, 0xbf, 0x4a, 0x3e, 0xce, 0xde, 0x7f, 0x95, 0x91, 0xbb, 0xd7, 0xb5, + 0x74, 0xdd, 0x1c, 0x3c, 0xac, 0x7a, 0xa9, 0x2d, 0x6f, 0x80, 0x7f, 0xcb, + 0x1a, 0xd6, 0xa8, 0x6d, 0xe3, 0x30, 0xc2, 0xa8, 0x48, 0x24, 0x77, 0x15, + 0x26, 0x6b, 0x9a, 0x6f, 0x99, 0x9d, 0xf4, 0xe3, 0xca, 0x8c, 0xb6, 0x52, + 0x1a, 0xe3, 0x20, 0xff, 0x00, 0x96, 0x15, 0x5e, 0xb6, 0x2e, 0x23, 0x33, + 0x44, 0xc8, 0x08, 0x04, 0xd6, 0x67, 0xd9, 0xdb, 0xcf, 0xf2, 0xb2, 0x37, + 0x7a, 0xf6, 0xad, 0xa1, 0x24, 0xd1, 0xcb, 0x52, 0x0e, 0x2d, 0x58, 0x8a, + 0xa7, 0x0a, 0x48, 0x83, 0x00, 0x9f, 0xff, 0x00, 0x5d, 0x34, 0xdb, 0xb2, + 0xcc, 0x22, 0xc8, 0xdd, 0xeb, 0xda, 0xb4, 0xad, 0xa3, 0x30, 0xc2, 0xaa, + 0x4e, 0x48, 0xf4, 0xa2, 0x52, 0xb2, 0x0a, 0x70, 0x72, 0x6d, 0x32, 0x7a, + 0x29, 0xa4, 0xd1, 0x9a, 0xe5, 0xb1, 0xde, 0x3a, 0x8a, 0x6e, 0x73, 0x41, + 0x34, 0x58, 0x0e, 0x7f, 0xe2, 0x1f, 0xfc, 0x88, 0xfa, 0xe7, 0xfd, 0x7a, + 0xbf, 0xf2, 0xaf, 0x95, 0xab, 0xeb, 0x2f, 0x18, 0x58, 0x4b, 0xaa, 0xf8, + 0x57, 0x56, 0xb4, 0x80, 0x6e, 0x9a, 0x6b, 0x69, 0x15, 0x17, 0xd5, 0xb6, + 0x9c, 0x0f, 0xce, 0xbe, 0x4e, 0x65, 0x2a, 0x48, 0x20, 0x82, 0x38, 0x20, + 0xf6, 0xaf, 0xc1, 0x7c, 0x46, 0x84, 0x96, 0x32, 0x84, 0xed, 0xa3, 0x8b, + 0x5f, 0x73, 0xff, 0x00, 0x82, 0x8d, 0x20, 0x25, 0x7d, 0x5d, 0xe0, 0x5f, + 0xf9, 0x12, 0xf4, 0x2f, 0xfa, 0xf2, 0x87, 0xff, 0x00, 0x40, 0x15, 0xf2, + 0x9a, 0x23, 0x48, 0xea, 0x88, 0xa5, 0x9d, 0x8e, 0x02, 0x81, 0x92, 0x4d, + 0x7d, 0x69, 0xe1, 0x8b, 0x19, 0x34, 0xbf, 0x0d, 0xe9, 0x76, 0x73, 0x71, + 0x2c, 0x16, 0xd1, 0xc6, 0xe3, 0xd0, 0x85, 0x00, 0xd5, 0x78, 0x73, 0x09, + 0x3c, 0x56, 0x22, 0x76, 0xd1, 0x45, 0x2f, 0xc7, 0xfe, 0x00, 0x4c, 0xd0, + 0xba, 0xff, 0x00, 0x8f, 0x69, 0xbf, 0xdc, 0x3f, 0xca, 0xbe, 0x39, 0xaf, + 0xb1, 0xe5, 0x5f, 0x32, 0x27, 0x4c, 0xe3, 0x72, 0x91, 0x9a, 0xf9, 0x03, + 0x50, 0xb1, 0x9b, 0x4c, 0xbe, 0xb8, 0xb4, 0x9d, 0x0a, 0x4d, 0x04, 0x86, + 0x37, 0x53, 0xd8, 0x83, 0x8a, 0xed, 0xf1, 0x22, 0x12, 0xff, 0x00, 0x65, + 0x9d, 0xb4, 0xf7, 0xd7, 0xfe, 0x92, 0x10, 0x2b, 0xd7, 0xd2, 0x5f, 0x05, + 0xbf, 0xe4, 0x9f, 0x58, 0xff, 0x00, 0xd7, 0x49, 0x7f, 0xf4, 0x33, 0x5f, + 0x36, 0xd7, 0xd3, 0x9f, 0x0a, 0x74, 0xe9, 0xb4, 0xbf, 0x01, 0xe9, 0x91, + 0x4e, 0xa5, 0x24, 0x75, 0x69, 0x76, 0x9e, 0xa0, 0x33, 0x12, 0x3f, 0x42, + 0x2b, 0xc3, 0xf0, 0xf6, 0x12, 0x96, 0x69, 0x52, 0x49, 0x68, 0xa0, 0xff, + 0x00, 0x19, 0x44, 0x72, 0xd8, 0xeb, 0xe8, 0xa6, 0xe6, 0x8c, 0xd7, 0xf4, + 0x3d, 0x8c, 0x87, 0x51, 0x4d, 0xcd, 0x19, 0xa2, 0xc0, 0x65, 0xb2, 0x90, + 0xd7, 0x19, 0x04, 0x7f, 0xfb, 0x42, 0xab, 0xd6, 0xc5, 0xc4, 0x66, 0x68, + 0x59, 0x07, 0x04, 0xfa, 0xd6, 0x67, 0xd9, 0xdb, 0xcf, 0xf2, 0xb2, 0x37, + 0x7a, 0xf6, 0xae, 0xa8, 0x4a, 0xe8, 0xe0, 0xa9, 0x07, 0x16, 0xac, 0x45, + 0x53, 0x85, 0x24, 0x41, 0x80, 0x4f, 0xff, 0x00, 0xae, 0x9a, 0x6d, 0xd9, + 0x66, 0x11, 0x64, 0x6e, 0xf5, 0xed, 0x5a, 0x76, 0xd1, 0x98, 0x21, 0x0a, + 0x48, 0x27, 0xda, 0x89, 0x4a, 0xc8, 0x29, 0xc1, 0xc9, 0xb4, 0xc9, 0xab, + 0x20, 0xa9, 0x02, 0x7c, 0x82, 0x3f, 0xfd, 0x75, 0xab, 0x9a, 0x8a, 0xe6, + 0x33, 0x34, 0x45, 0x41, 0xc1, 0xf7, 0xac, 0x60, 0xf9, 0x4e, 0x9a, 0x91, + 0xe6, 0x46, 0x45, 0x15, 0x28, 0xb7, 0x66, 0x98, 0xc5, 0x91, 0xb8, 0x77, + 0xed, 0x47, 0xd9, 0xdb, 0xcf, 0xf2, 0xb2, 0x37, 0x7a, 0xf6, 0xae, 0x9b, + 0xa3, 0x87, 0x95, 0x8f, 0x55, 0x25, 0xad, 0xf0, 0x0f, 0xf9, 0x63, 0x5a, + 0xd5, 0x0d, 0xba, 0x18, 0x61, 0x54, 0x24, 0x12, 0x3d, 0x2a, 0x4c, 0xd7, + 0x34, 0xdf, 0x33, 0x3b, 0xe9, 0xc7, 0x95, 0x19, 0x6c, 0xa4, 0x35, 0xc6, + 0x41, 0xff, 0x00, 0x2c, 0x2a, 0xbd, 0x6c, 0x5c, 0x46, 0x66, 0x85, 0x90, + 0x1c, 0x13, 0xeb, 0x59, 0x9f, 0x67, 0x6f, 0x3f, 0xca, 0xc8, 0xdd, 0xfa, + 0x56, 0xd0, 0x92, 0x68, 0xe5, 0xa9, 0x06, 0x9a, 0xb1, 0xa0, 0xd6, 0xc1, + 0xae, 0x04, 0xb9, 0x39, 0x1d, 0xaa, 0x6a, 0x28, 0xae, 0x76, 0xdb, 0x3a, + 0xd2, 0xb6, 0xc1, 0x45, 0x14, 0x52, 0x19, 0x0a, 0xdb, 0x05, 0xb8, 0x32, + 0xe4, 0xe4, 0xf6, 0xa3, 0xec, 0xc3, 0xed, 0x1e, 0x6e, 0x4e, 0x7d, 0x2a, + 0x6a, 0x2a, 0xb9, 0x99, 0x3c, 0xa8, 0x28, 0xa2, 0x8a, 0x92, 0x82, 0xa1, + 0xfb, 0x30, 0xfb, 0x47, 0x9b, 0x93, 0x9f, 0x4a, 0x9a, 0x8a, 0x69, 0xd8, + 0x4d, 0x5f, 0x72, 0x16, 0xb6, 0x0d, 0x70, 0x25, 0xc9, 0xc8, 0xed, 0x53, + 0x51, 0x45, 0x0d, 0xb6, 0x09, 0x5b, 0x60, 0xa2, 0x8a, 0x29, 0x0c, 0x28, + 0xa2, 0x8a, 0x00, 0x2b, 0x83, 0xf1, 0x57, 0xc1, 0xdd, 0x1b, 0xc4, 0xb7, + 0xaf, 0x79, 0x1b, 0xcb, 0xa7, 0x5d, 0x48, 0x73, 0x21, 0x80, 0x02, 0x8e, + 0x7d, 0x4a, 0x9e, 0xff, 0x00, 0x42, 0x2b, 0xbc, 0xa2, 0xbc, 0xfc, 0x76, + 0x5f, 0x85, 0xcc, 0xa9, 0xfb, 0x1c, 0x5d, 0x35, 0x38, 0xf9, 0xfe, 0x8f, + 0x75, 0xf2, 0x1a, 0x6d, 0x1c, 0x3f, 0x84, 0xbe, 0x11, 0xe8, 0xde, 0x16, + 0xbb, 0x4b, 0xc2, 0xd2, 0x5f, 0xde, 0x27, 0x29, 0x24, 0xf8, 0xda, 0x87, + 0xd5, 0x54, 0x77, 0xf7, 0x39, 0xae, 0xe2, 0x8a, 0x29, 0xe0, 0xb0, 0x18, + 0x5c, 0xba, 0x97, 0xb1, 0xc2, 0x53, 0x50, 0x8f, 0x97, 0xeb, 0xd5, 0xfc, + 0xc2, 0xed, 0x85, 0x71, 0xfe, 0x31, 0xf8, 0x5f, 0xa4, 0x78, 0xc6, 0x6f, + 0xb4, 0xcd, 0xe6, 0x5a, 0x5e, 0xe3, 0x06, 0xe2, 0x0c, 0x65, 0xc7, 0x6d, + 0xc0, 0xf5, 0xfe, 0x7e, 0xf5, 0xd8, 0x51, 0x55, 0x8c, 0xc1, 0x61, 0xf1, + 0xf4, 0x9d, 0x0c, 0x54, 0x14, 0xe2, 0xfa, 0x3f, 0xeb, 0x46, 0x17, 0x68, + 0xf3, 0xbf, 0x0f, 0x7c, 0x11, 0xd1, 0x74, 0x6b, 0xc4, 0xb9, 0xb9, 0x96, + 0x5d, 0x49, 0xd0, 0xe5, 0x63, 0x94, 0x05, 0x8f, 0x3e, 0xa5, 0x47, 0x5f, + 0xc4, 0xe3, 0xda, 0xbd, 0x13, 0xa5, 0x14, 0x56, 0x38, 0x1c, 0xb7, 0x07, + 0x96, 0x41, 0xd3, 0xc1, 0xd3, 0x50, 0x4f, 0x7b, 0x75, 0xf5, 0x7b, 0xb0, + 0x6d, 0xb0, 0xa2, 0x8a, 0x2b, 0xd3, 0x10, 0x51, 0x45, 0x14, 0x00, 0x54, + 0x3f, 0x66, 0x1f, 0x68, 0xf3, 0x72, 0x73, 0xe9, 0x53, 0x51, 0x4d, 0x3b, + 0x09, 0xab, 0xee, 0x42, 0xd6, 0xc1, 0xae, 0x04, 0xb9, 0x39, 0x1d, 0xaa, + 0x6a, 0x28, 0xa1, 0xb6, 0xc1, 0x2b, 0x6c, 0x14, 0x51, 0x45, 0x21, 0x90, + 0xad, 0xb0, 0x5b, 0x83, 0x2e, 0x4e, 0x4f, 0x6a, 0x3e, 0xcc, 0x3e, 0xd1, + 0xe6, 0xe4, 0xe7, 0xd2, 0xa6, 0xa2, 0xab, 0x99, 0x93, 0xca, 0x82, 0x8a, + 0x28, 0xa9, 0x28, 0x2a, 0x1f, 0xb3, 0x0f, 0xb4, 0x79, 0xb9, 0x39, 0xf4, + 0xa9, 0xa8, 0xa6, 0x9d, 0x84, 0xd5, 0xf7, 0x3e, 0x20, 0xff, 0x00, 0x87, + 0xa6, 0xf8, 0x5b, 0xfe, 0x84, 0x7d, 0x63, 0xff, 0x00, 0x02, 0xa2, 0xa3, + 0xfe, 0x1e, 0x9b, 0xe1, 0x6f, 0xfa, 0x11, 0xf5, 0x8f, 0xfc, 0x0a, 0x8a, + 0xbe, 0x17, 0xfe, 0xc6, 0xb2, 0xff, 0x00, 0x9f, 0x64, 0xa7, 0x47, 0xa2, + 0x59, 0x31, 0xe6, 0xd9, 0x31, 0x45, 0x8e, 0x9e, 0x44, 0x7d, 0xcd, 0xff, + 0x00, 0x0f, 0x4d, 0xf0, 0xb7, 0xfd, 0x08, 0xfa, 0xc7, 0xfe, 0x05, 0x45, + 0x47, 0xfc, 0x3d, 0x37, 0xc2, 0xdf, 0xf4, 0x23, 0xeb, 0x1f, 0xf8, 0x15, + 0x15, 0x7c, 0x3b, 0xfd, 0x87, 0x61, 0xff, 0x00, 0x3e, 0xb1, 0xfe, 0x54, + 0xd9, 0x34, 0x6b, 0x05, 0x1c, 0x5b, 0x47, 0x9f, 0xa5, 0x16, 0x0e, 0x44, + 0x7d, 0xc9, 0xff, 0x00, 0x0f, 0x4d, 0xf0, 0xb7, 0xfd, 0x08, 0xfa, 0xc7, + 0xfe, 0x05, 0x45, 0x47, 0xfc, 0x3d, 0x37, 0xc2, 0xdf, 0xf4, 0x23, 0xeb, + 0x1f, 0xf8, 0x15, 0x15, 0x7c, 0x2f, 0xfd, 0x8d, 0x65, 0xff, 0x00, 0x3e, + 0xc9, 0xf9, 0x53, 0xe3, 0xd1, 0x2c, 0x4f, 0x26, 0xd9, 0x31, 0xf4, 0xa2, + 0xc1, 0xc8, 0x8f, 0xb9, 0x7f, 0xe1, 0xe9, 0xbe, 0x16, 0xff, 0x00, 0xa1, + 0x1f, 0x58, 0xff, 0x00, 0xc0, 0xa8, 0xa8, 0xff, 0x00, 0x87, 0xa6, 0xf8, + 0x5b, 0xfe, 0x84, 0x7d, 0x63, 0xff, 0x00, 0x02, 0xa2, 0xaf, 0x87, 0x7f, + 0xb0, 0xec, 0x3f, 0xe7, 0xd6, 0x3f, 0xca, 0x99, 0x26, 0x8d, 0x62, 0x38, + 0x16, 0xc9, 0x9a, 0x2c, 0x1c, 0x88, 0xfb, 0x97, 0xfe, 0x1e, 0x9b, 0xe1, + 0x6f, 0xfa, 0x11, 0xf5, 0x8f, 0xfc, 0x0a, 0x8a, 0x8f, 0xf8, 0x7a, 0x6f, + 0x85, 0xbf, 0xe8, 0x47, 0xd6, 0x3f, 0xf0, 0x2a, 0x2a, 0xf8, 0x5f, 0xfb, + 0x1a, 0xcb, 0xfe, 0x7d, 0x93, 0xf2, 0xa9, 0x23, 0xd1, 0x2c, 0x48, 0xc9, + 0xb6, 0x4f, 0xca, 0x8b, 0x07, 0x22, 0x3e, 0xe4, 0xff, 0x00, 0x87, 0xa6, + 0xf8, 0x5b, 0xfe, 0x84, 0x7d, 0x63, 0xff, 0x00, 0x02, 0xa2, 0xa3, 0xfe, + 0x1e, 0x9b, 0xe1, 0x6f, 0xfa, 0x11, 0xf5, 0x8f, 0xfc, 0x0a, 0x8a, 0xbe, + 0x1d, 0xfe, 0xc3, 0xb0, 0xff, 0x00, 0x9f, 0x58, 0xff, 0x00, 0x2a, 0x64, + 0x9a, 0x35, 0x88, 0x38, 0x16, 0xc9, 0xf9, 0x51, 0x60, 0xe4, 0x47, 0xdc, + 0xbf, 0xf0, 0xf4, 0xdf, 0x0b, 0x7f, 0xd0, 0x8f, 0xac, 0x7f, 0xe0, 0x54, + 0x54, 0x7f, 0xc3, 0xd3, 0x7c, 0x2d, 0xff, 0x00, 0x42, 0x3e, 0xb1, 0xff, + 0x00, 0x81, 0x51, 0x57, 0xc2, 0xff, 0x00, 0xd8, 0xd6, 0x5f, 0xf3, 0xec, + 0x9f, 0x95, 0x48, 0x9a, 0x1d, 0x89, 0x19, 0x36, 0xc9, 0xf9, 0x51, 0x60, + 0xe4, 0x47, 0xdc, 0x9f, 0xf0, 0xf4, 0xdf, 0x0b, 0x7f, 0xd0, 0x8f, 0xac, + 0x7f, 0xe0, 0x54, 0x54, 0x7f, 0xc3, 0xd3, 0x7c, 0x2d, 0xff, 0x00, 0x42, + 0x3e, 0xb1, 0xff, 0x00, 0x81, 0x51, 0x57, 0xc3, 0xbf, 0xd8, 0x76, 0x1f, + 0xf3, 0xeb, 0x1f, 0xe5, 0x51, 0xbe, 0x8d, 0x63, 0x9c, 0x0b, 0x64, 0xfc, + 0xa8, 0xb0, 0x72, 0x23, 0xee, 0x6f, 0xf8, 0x7a, 0x6f, 0x85, 0xbf, 0xe8, + 0x47, 0xd6, 0x3f, 0xf0, 0x2a, 0x2a, 0x3f, 0xe1, 0xe9, 0xbe, 0x16, 0xff, + 0x00, 0xa1, 0x1f, 0x58, 0xff, 0x00, 0xc0, 0xa8, 0xab, 0xe1, 0x7f, 0xec, + 0x7b, 0x2f, 0xf9, 0xf6, 0x4f, 0xca, 0x9b, 0x2e, 0x9d, 0xa7, 0xdb, 0xed, + 0x53, 0x67, 0xe6, 0xc8, 0xc0, 0x90, 0xb1, 0xae, 0x4e, 0x3d, 0x68, 0xb0, + 0x72, 0x23, 0xee, 0xaf, 0xf8, 0x7a, 0x6f, 0x85, 0xbf, 0xe8, 0x47, 0xd6, + 0x3f, 0xf0, 0x2a, 0x2a, 0x3f, 0xe1, 0xe9, 0xbe, 0x16, 0xff, 0x00, 0xa1, + 0x1f, 0x58, 0xff, 0x00, 0xc0, 0xa8, 0xab, 0xe1, 0x1f, 0x23, 0x4b, 0xd8, + 0x1b, 0xec, 0x44, 0x8d, 0xbb, 0x9b, 0x09, 0xf7, 0x06, 0x48, 0xc9, 0xe7, + 0xd8, 0xfe, 0x54, 0x3d, 0x9e, 0x9e, 0x2e, 0x04, 0x66, 0xcb, 0x60, 0x27, + 0x68, 0x72, 0xbf, 0x29, 0x3f, 0x5c, 0xd1, 0x60, 0xe4, 0x47, 0xdd, 0xdf, + 0xf0, 0xf4, 0xdf, 0x0b, 0x7f, 0xd0, 0x8f, 0xac, 0x7f, 0xe0, 0x54, 0x54, + 0x7f, 0xc3, 0xd3, 0x7c, 0x2d, 0xff, 0x00, 0x42, 0x3e, 0xb1, 0xff, 0x00, + 0x81, 0x51, 0x57, 0xc1, 0x82, 0xdf, 0x4e, 0x1b, 0xb7, 0xd9, 0x98, 0x88, + 0x19, 0xda, 0xeb, 0xc9, 0xe7, 0x1c, 0x73, 0xea, 0x45, 0x4d, 0x1d, 0x9e, + 0x9a, 0x46, 0x0d, 0x91, 0x12, 0x02, 0x41, 0x8c, 0xaf, 0xcd, 0x9c, 0x67, + 0xd7, 0xd2, 0x8b, 0x07, 0x22, 0x3e, 0xed, 0xff, 0x00, 0x87, 0xa6, 0xf8, + 0x5b, 0xfe, 0x84, 0x7d, 0x63, 0xff, 0x00, 0x02, 0xa2, 0xa3, 0xfe, 0x1e, + 0x9b, 0xe1, 0x6f, 0xfa, 0x11, 0xf5, 0x8f, 0xfc, 0x0a, 0x8a, 0xbe, 0x16, + 0xb7, 0xd3, 0xf4, 0xeb, 0x86, 0x65, 0xfb, 0x1f, 0x96, 0xea, 0x01, 0x2b, + 0x22, 0xe0, 0xe0, 0xf7, 0xa7, 0x36, 0x8f, 0x62, 0x49, 0xc5, 0xb2, 0x62, + 0x8b, 0x07, 0x22, 0x3e, 0xe7, 0xff, 0x00, 0x87, 0xa6, 0xf8, 0x5b, 0xfe, + 0x84, 0x7d, 0x63, 0xff, 0x00, 0x02, 0xa2, 0xa3, 0xfe, 0x1e, 0x9b, 0xe1, + 0x6f, 0xfa, 0x11, 0xf5, 0x8f, 0xfc, 0x0a, 0x8a, 0xbe, 0x17, 0xfe, 0xc6, + 0xb2, 0x3f, 0xf2, 0xec, 0x95, 0x30, 0xd0, 0xec, 0x40, 0xff, 0x00, 0x8f, + 0x64, 0xa2, 0xc1, 0xc8, 0x8f, 0xb8, 0xbf, 0xe1, 0xe9, 0xbe, 0x16, 0xff, + 0x00, 0xa1, 0x1f, 0x58, 0xff, 0x00, 0xc0, 0xa8, 0xa8, 0xff, 0x00, 0x87, + 0xa6, 0xf8, 0x5b, 0xfe, 0x84, 0x7d, 0x63, 0xff, 0x00, 0x02, 0xa2, 0xaf, + 0x87, 0x7f, 0xb1, 0x2c, 0x07, 0xfc, 0xba, 0xc7, 0xf9, 0x54, 0x27, 0x47, + 0xb2, 0x27, 0xfe, 0x3d, 0x92, 0x8b, 0x07, 0x22, 0x3e, 0xe8, 0xff, 0x00, + 0x87, 0xa6, 0xf8, 0x5b, 0xfe, 0x84, 0x7d, 0x63, 0xff, 0x00, 0x02, 0xa2, + 0xa3, 0xfe, 0x1e, 0x9b, 0xe1, 0x6f, 0xfa, 0x11, 0xf5, 0x8f, 0xfc, 0x0a, + 0x8a, 0xbe, 0x17, 0x1a, 0x2d, 0x91, 0x38, 0xfb, 0x32, 0x54, 0xdf, 0xd8, + 0x76, 0x1f, 0xf3, 0xea, 0x9f, 0x95, 0x16, 0x0e, 0x44, 0x7d, 0xc5, 0xff, + 0x00, 0x0f, 0x4d, 0xf0, 0xb7, 0xfd, 0x08, 0xfa, 0xc7, 0xfe, 0x05, 0x45, + 0x47, 0xfc, 0x3d, 0x37, 0xc2, 0xdf, 0xf4, 0x23, 0xeb, 0x1f, 0xf8, 0x15, + 0x15, 0x7c, 0x3a, 0x74, 0x4b, 0x00, 0x33, 0xf6, 0x58, 0xff, 0x00, 0x2a, + 0x84, 0xe8, 0xd6, 0x5f, 0xf3, 0xec, 0x94, 0x58, 0x39, 0x11, 0xf7, 0x47, + 0xfc, 0x3d, 0x37, 0xc2, 0xdf, 0xf4, 0x23, 0xeb, 0x1f, 0xf8, 0x15, 0x15, + 0x1f, 0xf0, 0xf4, 0xdf, 0x0b, 0x7f, 0xd0, 0x8f, 0xac, 0x7f, 0xe0, 0x54, + 0x55, 0xf0, 0xc2, 0xe8, 0xb6, 0x4c, 0x40, 0xfb, 0x32, 0x54, 0xbf, 0xd8, + 0x76, 0x1f, 0xf3, 0xea, 0x94, 0x58, 0x39, 0x11, 0xf7, 0x17, 0xfc, 0x3d, + 0x37, 0xc2, 0xdf, 0xf4, 0x23, 0xeb, 0x1f, 0xf8, 0x15, 0x15, 0x1f, 0xf0, + 0xf4, 0xdf, 0x0b, 0x7f, 0xd0, 0x8f, 0xac, 0x7f, 0xe0, 0x54, 0x55, 0xf0, + 0xe3, 0x68, 0xb6, 0x0a, 0x09, 0xfb, 0x2c, 0x7f, 0x95, 0x45, 0xfd, 0x8d, + 0x65, 0xff, 0x00, 0x3e, 0xc9, 0x45, 0x83, 0x91, 0x1f, 0x74, 0x7f, 0xc3, + 0xd3, 0x7c, 0x2d, 0xff, 0x00, 0x42, 0x3e, 0xb1, 0xff, 0x00, 0x81, 0x51, + 0x51, 0xff, 0x00, 0x0f, 0x4d, 0xf0, 0xb7, 0xfd, 0x08, 0xfa, 0xc7, 0xfe, + 0x05, 0x45, 0x5f, 0x0c, 0x26, 0x89, 0x64, 0xcd, 0xff, 0x00, 0x1e, 0xc9, + 0x52, 0xff, 0x00, 0x61, 0xd8, 0x7f, 0xcf, 0xac, 0x7f, 0x95, 0x16, 0x0e, + 0x44, 0x7d, 0xc5, 0xff, 0x00, 0x0f, 0x4d, 0xf0, 0xb7, 0xfd, 0x08, 0xfa, + 0xc7, 0xfe, 0x05, 0x45, 0x47, 0xfc, 0x3d, 0x37, 0xc2, 0xdf, 0xf4, 0x23, + 0xeb, 0x1f, 0xf8, 0x15, 0x15, 0x7c, 0x38, 0xfa, 0x2d, 0x82, 0xaf, 0xfc, + 0x7a, 0xc7, 0xf9, 0x54, 0x5f, 0xd8, 0xd6, 0x5f, 0xf3, 0xec, 0x94, 0x58, + 0x39, 0x11, 0x72, 0xa7, 0x45, 0xda, 0xb8, 0xa8, 0xe3, 0x5c, 0xb6, 0x7d, + 0x2a, 0x5a, 0xa2, 0xc2, 0xa1, 0x66, 0xdc, 0xd9, 0xa7, 0xc8, 0xd8, 0x18, + 0xf5, 0xa8, 0xa8, 0x01, 0x40, 0xc9, 0xa9, 0xd4, 0x6d, 0x18, 0xa8, 0xe2, + 0x5e, 0x73, 0x52, 0x50, 0x00, 0x4e, 0x05, 0x40, 0xc7, 0x71, 0xcd, 0x49, + 0x2b, 0x71, 0x8a, 0x8a, 0x80, 0x14, 0x0c, 0x9c, 0x54, 0xe0, 0x60, 0x62, + 0xa3, 0x89, 0x7b, 0xd4, 0x94, 0x00, 0x13, 0x80, 0x4d, 0x40, 0x4e, 0x4e, + 0x69, 0xf2, 0xb7, 0x6a, 0x8e, 0x80, 0x15, 0x46, 0xe3, 0x8a, 0x9e, 0x99, + 0x1a, 0xe0, 0x67, 0xd6, 0x9f, 0x40, 0x08, 0xc7, 0x68, 0xcd, 0x41, 0x4f, + 0x91, 0xb2, 0x71, 0xe9, 0x4c, 0xa0, 0x07, 0x22, 0xee, 0x6f, 0x6a, 0x27, + 0xb5, 0x59, 0xa4, 0x49, 0x37, 0xbc, 0x6e, 0xbc, 0x06, 0x43, 0x8c, 0x8f, + 0x43, 0xed, 0x52, 0x46, 0xb8, 0x5f, 0x73, 0x4e, 0xa0, 0x0a, 0xa6, 0xca, + 0x38, 0xa2, 0x95, 0x41, 0x6f, 0xde, 0x2e, 0xc3, 0xcf, 0x6c, 0x93, 0xff, + 0x00, 0xb3, 0x1a, 0x87, 0xec, 0x60, 0xca, 0xae, 0xd2, 0x48, 0xe1, 0x4e, + 0x42, 0x12, 0x36, 0x83, 0xf9, 0x55, 0xa9, 0x1b, 0x73, 0x7b, 0x0a, 0x6d, + 0x00, 0x57, 0x8b, 0x49, 0xb7, 0x0c, 0xfb, 0x53, 0xcb, 0x0c, 0x00, 0x21, + 0x38, 0xe8, 0x72, 0x0f, 0xd6, 0xa6, 0x5d, 0x39, 0x17, 0x90, 0xf2, 0x79, + 0x99, 0x2c, 0x64, 0x27, 0x2c, 0x4e, 0xdc, 0x7e, 0x82, 0xac, 0xa2, 0xed, + 0x5a, 0x5a, 0x00, 0xad, 0x1d, 0xaa, 0xdb, 0x16, 0x6f, 0x31, 0xe5, 0x91, + 0xc0, 0x05, 0xe4, 0x39, 0x38, 0x1d, 0xbf, 0x5a, 0x5a, 0x73, 0xb6, 0xe6, + 0xa6, 0xf5, 0xa0, 0x07, 0xc4, 0xb9, 0x39, 0xf4, 0xa9, 0x69, 0x14, 0x6d, + 0x18, 0xa5, 0xe9, 0x40, 0x0c, 0x95, 0xb0, 0x31, 0x51, 0x52, 0xb1, 0xdc, + 0x49, 0xa0, 0x0c, 0x9c, 0x50, 0x03, 0xe2, 0x5e, 0xf5, 0x25, 0x00, 0x60, + 0x62, 0x82, 0x70, 0x33, 0x40, 0x11, 0xca, 0xdd, 0xaa, 0x3a, 0x52, 0x72, + 0x73, 0x42, 0x8d, 0xc4, 0x0a, 0x00, 0x92, 0x25, 0xc0, 0xcd, 0x3e, 0x8e, + 0x94, 0x8c, 0x76, 0x82, 0x68, 0x02, 0x39, 0x5b, 0x27, 0x1e, 0x94, 0xca, + 0x3a, 0xd3, 0x91, 0x77, 0x35, 0x00, 0x49, 0x1a, 0xe1, 0x7d, 0xcd, 0x3a, + 0x8a, 0x47, 0x6d, 0xab, 0x40, 0x11, 0xc8, 0xd9, 0x6f, 0x61, 0x4c, 0xa2, + 0x9d, 0x1a, 0xee, 0x6f, 0x61, 0x40, 0x1f, 0xff, 0xd9 +}; diff --git a/services/camera/libcameraservice/FakeCamera.cpp b/services/camera/libcameraservice/FakeCamera.cpp new file mode 100644 index 0000000..6749899 --- /dev/null +++ b/services/camera/libcameraservice/FakeCamera.cpp @@ -0,0 +1,430 @@ +/* +** +** Copyright 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. +*/ + +#define LOG_TAG "FakeCamera" +#include <utils/Log.h> + +#include <string.h> +#include <stdlib.h> +#include <utils/String8.h> + +#include "FakeCamera.h" + + +namespace android { + +// TODO: All this rgb to yuv should probably be in a util class. + +// TODO: I think something is wrong in this class because the shadow is kBlue +// and the square color should alternate between kRed and kGreen. However on the +// emulator screen these are all shades of gray. Y seems ok but the U and V are +// probably not. + +static int tables_initialized = 0; +uint8_t *gYTable, *gCbTable, *gCrTable; + +static int +clamp(int x) +{ + if (x > 255) return 255; + if (x < 0) return 0; + return x; +} + +/* the equation used by the video code to translate YUV to RGB looks like this + * + * Y = (Y0 - 16)*k0 + * Cb = Cb0 - 128 + * Cr = Cr0 - 128 + * + * G = ( Y - k1*Cr - k2*Cb ) + * R = ( Y + k3*Cr ) + * B = ( Y + k4*Cb ) + * + */ + +static const double k0 = 1.164; +static const double k1 = 0.813; +static const double k2 = 0.391; +static const double k3 = 1.596; +static const double k4 = 2.018; + +/* let's try to extract the value of Y + * + * G + k1/k3*R + k2/k4*B = Y*( 1 + k1/k3 + k2/k4 ) + * + * Y = ( G + k1/k3*R + k2/k4*B ) / (1 + k1/k3 + k2/k4) + * Y0 = ( G0 + k1/k3*R0 + k2/k4*B0 ) / ((1 + k1/k3 + k2/k4)*k0) + 16 + * + * let define: + * kYr = k1/k3 + * kYb = k2/k4 + * kYy = k0 * ( 1 + kYr + kYb ) + * + * we have: + * Y = ( G + kYr*R + kYb*B ) + * Y0 = clamp[ Y/kYy + 16 ] + */ + +static const double kYr = k1/k3; +static const double kYb = k2/k4; +static const double kYy = k0*( 1. + kYr + kYb ); + +static void +initYtab( void ) +{ + const int imax = (int)( (kYr + kYb)*(31 << 2) + (61 << 3) + 0.1 ); + int i; + + gYTable = (uint8_t *)malloc(imax); + + for(i=0; i<imax; i++) { + int x = (int)(i/kYy + 16.5); + if (x < 16) x = 16; + else if (x > 235) x = 235; + gYTable[i] = (uint8_t) x; + } +} + +/* + * the source is RGB565, so adjust for 8-bit range of input values: + * + * G = (pixels >> 3) & 0xFC; + * R = (pixels >> 8) & 0xF8; + * B = (pixels & 0x1f) << 3; + * + * R2 = (pixels >> 11) R = R2*8 + * B2 = (pixels & 0x1f) B = B2*8 + * + * kYr*R = kYr2*R2 => kYr2 = kYr*8 + * kYb*B = kYb2*B2 => kYb2 = kYb*8 + * + * we want to use integer multiplications: + * + * SHIFT1 = 9 + * + * (ALPHA*R2) >> SHIFT1 == R*kYr => ALPHA = kYr*8*(1 << SHIFT1) + * + * ALPHA = kYr*(1 << (SHIFT1+3)) + * BETA = kYb*(1 << (SHIFT1+3)) + */ + +static const int SHIFT1 = 9; +static const int ALPHA = (int)( kYr*(1 << (SHIFT1+3)) + 0.5 ); +static const int BETA = (int)( kYb*(1 << (SHIFT1+3)) + 0.5 ); + +/* + * now let's try to get the values of Cb and Cr + * + * R-B = (k3*Cr - k4*Cb) + * + * k3*Cr = k4*Cb + (R-B) + * k4*Cb = k3*Cr - (R-B) + * + * R-G = (k1+k3)*Cr + k2*Cb + * = (k1+k3)*Cr + k2/k4*(k3*Cr - (R-B)/k0) + * = (k1 + k3 + k2*k3/k4)*Cr - k2/k4*(R-B) + * + * kRr*Cr = (R-G) + kYb*(R-B) + * + * Cr = ((R-G) + kYb*(R-B))/kRr + * Cr0 = clamp(Cr + 128) + */ + +static const double kRr = (k1 + k3 + k2*k3/k4); + +static void +initCrtab( void ) +{ + uint8_t *pTable; + int i; + + gCrTable = (uint8_t *)malloc(768*2); + + pTable = gCrTable + 384; + for(i=-384; i<384; i++) + pTable[i] = (uint8_t) clamp( i/kRr + 128.5 ); +} + +/* + * B-G = (k2 + k4)*Cb + k1*Cr + * = (k2 + k4)*Cb + k1/k3*(k4*Cb + (R-B)) + * = (k2 + k4 + k1*k4/k3)*Cb + k1/k3*(R-B) + * + * kBb*Cb = (B-G) - kYr*(R-B) + * + * Cb = ((B-G) - kYr*(R-B))/kBb + * Cb0 = clamp(Cb + 128) + * + */ + +static const double kBb = (k2 + k4 + k1*k4/k3); + +static void +initCbtab( void ) +{ + uint8_t *pTable; + int i; + + gCbTable = (uint8_t *)malloc(768*2); + + pTable = gCbTable + 384; + for(i=-384; i<384; i++) + pTable[i] = (uint8_t) clamp( i/kBb + 128.5 ); +} + +/* + * SHIFT2 = 16 + * + * DELTA = kYb*(1 << SHIFT2) + * GAMMA = kYr*(1 << SHIFT2) + */ + +static const int SHIFT2 = 16; +static const int DELTA = kYb*(1 << SHIFT2); +static const int GAMMA = kYr*(1 << SHIFT2); + +int32_t ccrgb16toyuv_wo_colorkey(uint8_t *rgb16,uint8_t *yuv422,uint32_t *param,uint8_t *table[]) +{ + uint16_t *inputRGB = (uint16_t*)rgb16; + uint8_t *outYUV = yuv422; + int32_t width_dst = param[0]; + int32_t height_dst = param[1]; + int32_t pitch_dst = param[2]; + int32_t mheight_dst = param[3]; + int32_t pitch_src = param[4]; + uint8_t *y_tab = table[0]; + uint8_t *cb_tab = table[1]; + uint8_t *cr_tab = table[2]; + + int32_t size16 = pitch_dst*mheight_dst; + int32_t i,j,count; + int32_t ilimit,jlimit; + uint8_t *tempY,*tempU,*tempV; + uint16_t pixels; + int tmp; +uint32_t temp; + + tempY = outYUV; + tempU = outYUV + (height_dst * pitch_dst); + tempV = tempU + 1; + + jlimit = height_dst; + ilimit = width_dst; + + for(j=0; j<jlimit; j+=1) + { + for (i=0; i<ilimit; i+=2) + { + int32_t G_ds = 0, B_ds = 0, R_ds = 0; + uint8_t y0, y1, u, v; + + pixels = inputRGB[i]; + temp = (BETA*(pixels & 0x001F) + ALPHA*(pixels>>11) ); + y0 = y_tab[(temp>>SHIFT1) + ((pixels>>3) & 0x00FC)]; + + G_ds += (pixels>>1) & 0x03E0; + B_ds += (pixels<<5) & 0x03E0; + R_ds += (pixels>>6) & 0x03E0; + + pixels = inputRGB[i+1]; + temp = (BETA*(pixels & 0x001F) + ALPHA*(pixels>>11) ); + y1 = y_tab[(temp>>SHIFT1) + ((pixels>>3) & 0x00FC)]; + + G_ds += (pixels>>1) & 0x03E0; + B_ds += (pixels<<5) & 0x03E0; + R_ds += (pixels>>6) & 0x03E0; + + R_ds >>= 1; + B_ds >>= 1; + G_ds >>= 1; + + tmp = R_ds - B_ds; + + u = cb_tab[(((B_ds-G_ds)<<SHIFT2) - GAMMA*tmp)>>(SHIFT2+2)]; + v = cr_tab[(((R_ds-G_ds)<<SHIFT2) + DELTA*tmp)>>(SHIFT2+2)]; + + tempY[0] = y0; + tempY[1] = y1; + tempU[0] = u; + tempV[0] = v; + + tempY += 2; + tempU += 2; + tempV += 2; + } + + inputRGB += pitch_src; + } + + return 1; +} + +#define min(a,b) ((a)<(b)?(a):(b)) +#define max(a,b) ((a)>(b)?(a):(b)) + +static void convert_rgb16_to_yuv422(uint8_t *rgb, uint8_t *yuv, int width, int height) +{ + if (!tables_initialized) { + initYtab(); + initCrtab(); + initCbtab(); + tables_initialized = 1; + } + + uint32_t param[6]; + param[0] = (uint32_t) width; + param[1] = (uint32_t) height; + param[2] = (uint32_t) width; + param[3] = (uint32_t) height; + param[4] = (uint32_t) width; + param[5] = (uint32_t) 0; + + uint8_t *table[3]; + table[0] = gYTable; + table[1] = gCbTable + 384; + table[2] = gCrTable + 384; + + ccrgb16toyuv_wo_colorkey(rgb, yuv, param, table); +} + +const int FakeCamera::kRed; +const int FakeCamera::kGreen; +const int FakeCamera::kBlue; + +FakeCamera::FakeCamera(int width, int height) + : mTmpRgb16Buffer(0) +{ + setSize(width, height); +} + +FakeCamera::~FakeCamera() +{ + delete[] mTmpRgb16Buffer; +} + +void FakeCamera::setSize(int width, int height) +{ + mWidth = width; + mHeight = height; + mCounter = 0; + mCheckX = 0; + mCheckY = 0; + + // This will cause it to be reallocated on the next call + // to getNextFrameAsYuv422(). + delete[] mTmpRgb16Buffer; + mTmpRgb16Buffer = 0; +} + +void FakeCamera::getNextFrameAsRgb565(uint16_t *buffer) +{ + int size = mWidth / 10; + + drawCheckerboard(buffer, size); + + int x = ((mCounter*3)&255); + if(x>128) x = 255 - x; + int y = ((mCounter*5)&255); + if(y>128) y = 255 - y; + + drawSquare(buffer, x*size/32, y*size/32, (size*5)>>1, (mCounter&0x100)?kRed:kGreen, kBlue); + + mCounter++; +} + +void FakeCamera::getNextFrameAsYuv422(uint8_t *buffer) +{ + if (mTmpRgb16Buffer == 0) + mTmpRgb16Buffer = new uint16_t[mWidth * mHeight]; + + getNextFrameAsRgb565(mTmpRgb16Buffer); + convert_rgb16_to_yuv422((uint8_t*)mTmpRgb16Buffer, buffer, mWidth, mHeight); +} + +void FakeCamera::drawSquare(uint16_t *dst, int x, int y, int size, int color, int shadow) +{ + int square_xstop, square_ystop, shadow_xstop, shadow_ystop; + + square_xstop = min(mWidth, x+size); + square_ystop = min(mHeight, y+size); + shadow_xstop = min(mWidth, x+size+(size/4)); + shadow_ystop = min(mHeight, y+size+(size/4)); + + // Do the shadow. + uint16_t *sh = &dst[(y+(size/4))*mWidth]; + for (int j = y + (size/4); j < shadow_ystop; j++) { + for (int i = x + (size/4); i < shadow_xstop; i++) { + sh[i] &= shadow; + } + sh += mWidth; + } + + // Draw the square. + uint16_t *sq = &dst[y*mWidth]; + for (int j = y; j < square_ystop; j++) { + for (int i = x; i < square_xstop; i++) { + sq[i] = color; + } + sq += mWidth; + } +} + +void FakeCamera::drawCheckerboard(uint16_t *dst, int size) +{ + bool black = true; + + if((mCheckX/size)&1) + black = false; + if((mCheckY/size)&1) + black = !black; + + int county = mCheckY%size; + int checkxremainder = mCheckX%size; + + for(int y=0;y<mHeight;y++) { + int countx = checkxremainder; + bool current = black; + for(int x=0;x<mWidth;x++) { + dst[y*mWidth+x] = current?0:0xffff; + if(countx++ >= size) { + countx=0; + current = !current; + } + } + if(county++ >= size) { + county=0; + black = !black; + } + } + mCheckX += 3; + mCheckY++; +} + + +void FakeCamera::dump(int fd) const +{ + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + snprintf(buffer, 255, " width x height (%d x %d), counter (%d), check x-y coordinate(%d, %d)\n", mWidth, mHeight, mCounter, mCheckX, mCheckY); + result.append(buffer); + ::write(fd, result.string(), result.size()); +} + + +}; // namespace android diff --git a/services/camera/libcameraservice/FakeCamera.h b/services/camera/libcameraservice/FakeCamera.h new file mode 100644 index 0000000..f7f8803 --- /dev/null +++ b/services/camera/libcameraservice/FakeCamera.h @@ -0,0 +1,67 @@ +/* +** +** Copyright 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. +*/ + +#ifndef ANDROID_HARDWARE_FAKECAMERA_H +#define ANDROID_HARDWARE_FAKECAMERA_H + +#include <sys/types.h> +#include <stdint.h> + +namespace android { + +/* + * FakeCamera is used in the CameraHardwareStub to provide a fake video feed + * when the system does not have a camera in hardware. + * The fake video is a moving black and white checkerboard background with a + * bouncing gray square in the foreground. + * This class is not thread-safe. + * + * TODO: Since the major methods provides a raw/uncompressed video feed, rename + * this class to RawVideoSource. + */ + +class FakeCamera { +public: + FakeCamera(int width, int height); + ~FakeCamera(); + + void setSize(int width, int height); + void getNextFrameAsYuv422(uint8_t *buffer); + // Write to the fd a string representing the current state. + void dump(int fd) const; + +private: + // TODO: remove the uint16_t buffer param everywhere since it is a field of + // this class. + void getNextFrameAsRgb565(uint16_t *buffer); + + void drawSquare(uint16_t *buffer, int x, int y, int size, int color, int shadow); + void drawCheckerboard(uint16_t *buffer, int size); + + static const int kRed = 0xf800; + static const int kGreen = 0x07c0; + static const int kBlue = 0x003e; + + int mWidth, mHeight; + int mCounter; + int mCheckX, mCheckY; + uint16_t *mTmpRgb16Buffer; +}; + +}; // namespace android + +#endif // ANDROID_HARDWARE_FAKECAMERA_H diff --git a/services/camera/tests/CameraServiceTest/Android.mk b/services/camera/tests/CameraServiceTest/Android.mk new file mode 100644 index 0000000..9bb190a --- /dev/null +++ b/services/camera/tests/CameraServiceTest/Android.mk @@ -0,0 +1,24 @@ +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= CameraServiceTest.cpp + +LOCAL_MODULE:= CameraServiceTest + +LOCAL_MODULE_TAGS := tests + +LOCAL_C_INCLUDES += \ + frameworks/base/libs + +LOCAL_CFLAGS := + +LOCAL_SHARED_LIBRARIES += \ + libbinder \ + libcutils \ + libutils \ + libui \ + libcamera_client \ + libsurfaceflinger_client + +include $(BUILD_EXECUTABLE) diff --git a/services/camera/tests/CameraServiceTest/CameraServiceTest.cpp b/services/camera/tests/CameraServiceTest/CameraServiceTest.cpp new file mode 100644 index 0000000..9fc795b --- /dev/null +++ b/services/camera/tests/CameraServiceTest/CameraServiceTest.cpp @@ -0,0 +1,849 @@ +#define LOG_TAG "CameraServiceTest" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> +#include <surfaceflinger/ISurface.h> +#include <camera/Camera.h> +#include <camera/CameraParameters.h> +#include <ui/GraphicBuffer.h> +#include <camera/ICamera.h> +#include <camera/ICameraClient.h> +#include <camera/ICameraService.h> +#include <ui/Overlay.h> +#include <binder/IPCThreadState.h> +#include <binder/IServiceManager.h> +#include <binder/ProcessState.h> +#include <utils/KeyedVector.h> +#include <utils/Log.h> +#include <utils/Vector.h> +#include <utils/threads.h> + +using namespace android; + +// +// Assertion and Logging utilities +// +#define INFO(...) \ + do { \ + printf(__VA_ARGS__); \ + printf("\n"); \ + LOGD(__VA_ARGS__); \ + } while(0) + +void assert_fail(const char *file, int line, const char *func, const char *expr) { + INFO("assertion failed at file %s, line %d, function %s:", + file, line, func); + INFO("%s", expr); + exit(1); +} + +void assert_eq_fail(const char *file, int line, const char *func, + const char *expr, int actual) { + INFO("assertion failed at file %s, line %d, function %s:", + file, line, func); + INFO("(expected) %s != (actual) %d", expr, actual); + exit(1); +} + +#define ASSERT(e) \ + do { \ + if (!(e)) \ + assert_fail(__FILE__, __LINE__, __func__, #e); \ + } while(0) + +#define ASSERT_EQ(expected, actual) \ + do { \ + int _x = (actual); \ + if (_x != (expected)) \ + assert_eq_fail(__FILE__, __LINE__, __func__, #expected, _x); \ + } while(0) + +// +// Holder service for pass objects between processes. +// +class IHolder : public IInterface { +protected: + enum { + HOLDER_PUT = IBinder::FIRST_CALL_TRANSACTION, + HOLDER_GET, + HOLDER_CLEAR + }; +public: + DECLARE_META_INTERFACE(Holder); + + virtual void put(sp<IBinder> obj) = 0; + virtual sp<IBinder> get() = 0; + virtual void clear() = 0; +}; + +class BnHolder : public BnInterface<IHolder> { + virtual status_t onTransact(uint32_t code, + const Parcel& data, + Parcel* reply, + uint32_t flags = 0); +}; + +class BpHolder : public BpInterface<IHolder> { +public: + BpHolder(const sp<IBinder>& impl) + : BpInterface<IHolder>(impl) { + } + + virtual void put(sp<IBinder> obj) { + Parcel data, reply; + data.writeStrongBinder(obj); + remote()->transact(HOLDER_PUT, data, &reply, IBinder::FLAG_ONEWAY); + } + + virtual sp<IBinder> get() { + Parcel data, reply; + remote()->transact(HOLDER_GET, data, &reply); + return reply.readStrongBinder(); + } + + virtual void clear() { + Parcel data, reply; + remote()->transact(HOLDER_CLEAR, data, &reply); + } +}; + +IMPLEMENT_META_INTERFACE(Holder, "CameraServiceTest.Holder"); + +status_t BnHolder::onTransact( + uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) { + switch(code) { + case HOLDER_PUT: { + put(data.readStrongBinder()); + return NO_ERROR; + } break; + case HOLDER_GET: { + reply->writeStrongBinder(get()); + return NO_ERROR; + } break; + case HOLDER_CLEAR: { + clear(); + return NO_ERROR; + } break; + default: + return BBinder::onTransact(code, data, reply, flags); + } +} + +class HolderService : public BnHolder { + virtual void put(sp<IBinder> obj) { + mObj = obj; + } + virtual sp<IBinder> get() { + return mObj; + } + virtual void clear() { + mObj.clear(); + } +private: + sp<IBinder> mObj; +}; + +// +// A mock CameraClient +// +class MCameraClient : public BnCameraClient { +public: + virtual void notifyCallback(int32_t msgType, int32_t ext1, int32_t ext2); + virtual void dataCallback(int32_t msgType, const sp<IMemory>& data); + virtual void dataCallbackTimestamp(nsecs_t timestamp, + int32_t msgType, const sp<IMemory>& data) {} + + // new functions + void clearStat(); + enum OP { EQ, GE, LE, GT, LT }; + void assertNotify(int32_t msgType, OP op, int count); + void assertData(int32_t msgType, OP op, int count); + void waitNotify(int32_t msgType, OP op, int count); + void waitData(int32_t msgType, OP op, int count); + void assertDataSize(int32_t msgType, OP op, int dataSize); + + void setReleaser(ICamera *releaser) { + mReleaser = releaser; + } +private: + Mutex mLock; + Condition mCond; + DefaultKeyedVector<int32_t, int> mNotifyCount; + DefaultKeyedVector<int32_t, int> mDataCount; + DefaultKeyedVector<int32_t, int> mDataSize; + bool test(OP op, int v1, int v2); + + ICamera *mReleaser; +}; + +void MCameraClient::clearStat() { + Mutex::Autolock _l(mLock); + mNotifyCount.clear(); + mDataCount.clear(); + mDataSize.clear(); +} + +bool MCameraClient::test(OP op, int v1, int v2) { + switch (op) { + case EQ: return v1 == v2; + case GT: return v1 > v2; + case LT: return v1 < v2; + case GE: return v1 >= v2; + case LE: return v1 <= v2; + default: ASSERT(0); break; + } + return false; +} + +void MCameraClient::assertNotify(int32_t msgType, OP op, int count) { + Mutex::Autolock _l(mLock); + int v = mNotifyCount.valueFor(msgType); + ASSERT(test(op, v, count)); +} + +void MCameraClient::assertData(int32_t msgType, OP op, int count) { + Mutex::Autolock _l(mLock); + int v = mDataCount.valueFor(msgType); + ASSERT(test(op, v, count)); +} + +void MCameraClient::assertDataSize(int32_t msgType, OP op, int dataSize) { + Mutex::Autolock _l(mLock); + int v = mDataSize.valueFor(msgType); + ASSERT(test(op, v, dataSize)); +} + +void MCameraClient::notifyCallback(int32_t msgType, int32_t ext1, int32_t ext2) { + INFO(__func__); + Mutex::Autolock _l(mLock); + ssize_t i = mNotifyCount.indexOfKey(msgType); + if (i < 0) { + mNotifyCount.add(msgType, 1); + } else { + ++mNotifyCount.editValueAt(i); + } + mCond.signal(); +} + +void MCameraClient::dataCallback(int32_t msgType, const sp<IMemory>& data) { + INFO(__func__); + int dataSize = data->size(); + INFO("data type = %d, size = %d", msgType, dataSize); + Mutex::Autolock _l(mLock); + ssize_t i = mDataCount.indexOfKey(msgType); + if (i < 0) { + mDataCount.add(msgType, 1); + mDataSize.add(msgType, dataSize); + } else { + ++mDataCount.editValueAt(i); + mDataSize.editValueAt(i) = dataSize; + } + mCond.signal(); + + if (msgType == CAMERA_MSG_VIDEO_FRAME) { + ASSERT(mReleaser != NULL); + mReleaser->releaseRecordingFrame(data); + } +} + +void MCameraClient::waitNotify(int32_t msgType, OP op, int count) { + INFO("waitNotify: %d, %d, %d", msgType, op, count); + Mutex::Autolock _l(mLock); + while (true) { + int v = mNotifyCount.valueFor(msgType); + if (test(op, v, count)) { + break; + } + mCond.wait(mLock); + } +} + +void MCameraClient::waitData(int32_t msgType, OP op, int count) { + INFO("waitData: %d, %d, %d", msgType, op, count); + Mutex::Autolock _l(mLock); + while (true) { + int v = mDataCount.valueFor(msgType); + if (test(op, v, count)) { + break; + } + mCond.wait(mLock); + } +} + +// +// A mock Surface +// +class MSurface : public BnSurface { +public: + virtual status_t registerBuffers(const BufferHeap& buffers); + virtual void postBuffer(ssize_t offset); + virtual void unregisterBuffers(); + virtual sp<OverlayRef> createOverlay( + uint32_t w, uint32_t h, int32_t format, int32_t orientation); + virtual sp<GraphicBuffer> requestBuffer(int bufferIdx, int usage); + + // new functions + void clearStat(); + void waitUntil(int c0, int c1, int c2); + +private: + // check callback count + Condition mCond; + Mutex mLock; + int registerBuffersCount; + int postBufferCount; + int unregisterBuffersCount; +}; + +status_t MSurface::registerBuffers(const BufferHeap& buffers) { + INFO(__func__); + Mutex::Autolock _l(mLock); + ++registerBuffersCount; + mCond.signal(); + return NO_ERROR; +} + +void MSurface::postBuffer(ssize_t offset) { + // INFO(__func__); + Mutex::Autolock _l(mLock); + ++postBufferCount; + mCond.signal(); +} + +void MSurface::unregisterBuffers() { + INFO(__func__); + Mutex::Autolock _l(mLock); + ++unregisterBuffersCount; + mCond.signal(); +} + +sp<GraphicBuffer> MSurface::requestBuffer(int bufferIdx, int usage) { + INFO(__func__); + return NULL; +} + +void MSurface::clearStat() { + Mutex::Autolock _l(mLock); + registerBuffersCount = 0; + postBufferCount = 0; + unregisterBuffersCount = 0; +} + +void MSurface::waitUntil(int c0, int c1, int c2) { + INFO("waitUntil: %d %d %d", c0, c1, c2); + Mutex::Autolock _l(mLock); + while (true) { + if (registerBuffersCount >= c0 && + postBufferCount >= c1 && + unregisterBuffersCount >= c2) { + break; + } + mCond.wait(mLock); + } +} + +sp<OverlayRef> MSurface::createOverlay(uint32_t w, uint32_t h, int32_t format, + int32_t orientation) { + // We don't expect this to be called in current hardware. + ASSERT(0); + sp<OverlayRef> dummy; + return dummy; +} + +// +// Utilities to use the Holder service +// +sp<IHolder> getHolder() { + sp<IServiceManager> sm = defaultServiceManager(); + ASSERT(sm != 0); + sp<IBinder> binder = sm->getService(String16("CameraServiceTest.Holder")); + ASSERT(binder != 0); + sp<IHolder> holder = interface_cast<IHolder>(binder); + ASSERT(holder != 0); + return holder; +} + +void putTempObject(sp<IBinder> obj) { + INFO(__func__); + getHolder()->put(obj); +} + +sp<IBinder> getTempObject() { + INFO(__func__); + return getHolder()->get(); +} + +void clearTempObject() { + INFO(__func__); + getHolder()->clear(); +} + +// +// Get a Camera Service +// +sp<ICameraService> getCameraService() { + sp<IServiceManager> sm = defaultServiceManager(); + ASSERT(sm != 0); + sp<IBinder> binder = sm->getService(String16("media.camera")); + ASSERT(binder != 0); + sp<ICameraService> cs = interface_cast<ICameraService>(binder); + ASSERT(cs != 0); + return cs; +} + +// +// Various Connect Tests +// +void testConnect() { + INFO(__func__); + sp<ICameraService> cs = getCameraService(); + sp<MCameraClient> cc = new MCameraClient(); + sp<ICamera> c = cs->connect(cc); + ASSERT(c != 0); + c->disconnect(); +} + +void testAllowConnectOnceOnly() { + INFO(__func__); + sp<ICameraService> cs = getCameraService(); + // Connect the first client. + sp<MCameraClient> cc = new MCameraClient(); + sp<ICamera> c = cs->connect(cc); + ASSERT(c != 0); + // Same client -- ok. + ASSERT(cs->connect(cc) != 0); + // Different client -- not ok. + sp<MCameraClient> cc2 = new MCameraClient(); + ASSERT(cs->connect(cc2) == 0); + c->disconnect(); +} + +void testReconnectFailed() { + INFO(__func__); + sp<ICamera> c = interface_cast<ICamera>(getTempObject()); + sp<MCameraClient> cc2 = new MCameraClient(); + ASSERT(c->connect(cc2) != NO_ERROR); +} + +void testReconnectSuccess() { + INFO(__func__); + sp<ICamera> c = interface_cast<ICamera>(getTempObject()); + sp<MCameraClient> cc = new MCameraClient(); + ASSERT(c->connect(cc) == NO_ERROR); +} + +void testLockFailed() { + INFO(__func__); + sp<ICamera> c = interface_cast<ICamera>(getTempObject()); + ASSERT(c->lock() != NO_ERROR); +} + +void testLockUnlockSuccess() { + INFO(__func__); + sp<ICamera> c = interface_cast<ICamera>(getTempObject()); + ASSERT(c->lock() == NO_ERROR); + ASSERT(c->unlock() == NO_ERROR); +} + +void testLockSuccess() { + INFO(__func__); + sp<ICamera> c = interface_cast<ICamera>(getTempObject()); + ASSERT(c->lock() == NO_ERROR); +} + +// +// Run the connect tests in another process. +// +const char *gExecutable; + +struct FunctionTableEntry { + const char *name; + void (*func)(); +}; + +FunctionTableEntry function_table[] = { +#define ENTRY(x) {#x, &x} + ENTRY(testReconnectFailed), + ENTRY(testReconnectSuccess), + ENTRY(testLockUnlockSuccess), + ENTRY(testLockFailed), + ENTRY(testLockSuccess), +#undef ENTRY +}; + +void runFunction(const char *tag) { + INFO("runFunction: %s", tag); + int entries = sizeof(function_table) / sizeof(function_table[0]); + for (int i = 0; i < entries; i++) { + if (strcmp(function_table[i].name, tag) == 0) { + (*function_table[i].func)(); + return; + } + } + ASSERT(0); +} + +void runInAnotherProcess(const char *tag) { + pid_t pid = fork(); + if (pid == 0) { + execlp(gExecutable, gExecutable, tag, NULL); + ASSERT(0); + } else { + int status; + ASSERT_EQ(pid, wait(&status)); + ASSERT_EQ(0, status); + } +} + +void testReconnect() { + INFO(__func__); + sp<ICameraService> cs = getCameraService(); + sp<MCameraClient> cc = new MCameraClient(); + sp<ICamera> c = cs->connect(cc); + ASSERT(c != 0); + // Reconnect to the same client -- ok. + ASSERT(c->connect(cc) == NO_ERROR); + // Reconnect to a different client (but the same pid) -- ok. + sp<MCameraClient> cc2 = new MCameraClient(); + ASSERT(c->connect(cc2) == NO_ERROR); + c->disconnect(); + cc->assertNotify(CAMERA_MSG_ERROR, MCameraClient::EQ, 0); +} + +void testLockUnlock() { + sp<ICameraService> cs = getCameraService(); + sp<MCameraClient> cc = new MCameraClient(); + sp<ICamera> c = cs->connect(cc); + ASSERT(c != 0); + // We can lock as many times as we want. + ASSERT(c->lock() == NO_ERROR); + ASSERT(c->lock() == NO_ERROR); + // Lock from a different process -- not ok. + putTempObject(c->asBinder()); + runInAnotherProcess("testLockFailed"); + // Unlock then lock from a different process -- ok. + ASSERT(c->unlock() == NO_ERROR); + runInAnotherProcess("testLockUnlockSuccess"); + // Unlock then lock from a different process -- ok. + runInAnotherProcess("testLockSuccess"); + c->disconnect(); + clearTempObject(); +} + +void testReconnectFromAnotherProcess() { + INFO(__func__); + + sp<ICameraService> cs = getCameraService(); + sp<MCameraClient> cc = new MCameraClient(); + sp<ICamera> c = cs->connect(cc); + ASSERT(c != 0); + // Reconnect from a different process -- not ok. + putTempObject(c->asBinder()); + runInAnotherProcess("testReconnectFailed"); + // Unlock then reconnect from a different process -- ok. + ASSERT(c->unlock() == NO_ERROR); + runInAnotherProcess("testReconnectSuccess"); + c->disconnect(); + clearTempObject(); +} + +// We need to flush the command buffer after the reference +// to ICamera is gone. The sleep is for the server to run +// the destructor for it. +static void flushCommands() { + IPCThreadState::self()->flushCommands(); + usleep(200000); // 200ms +} + +// Run a test case +#define RUN(class_name) do { \ + { \ + INFO(#class_name); \ + class_name instance; \ + instance.run(); \ + } \ + flushCommands(); \ +} while(0) + +// Base test case after the the camera is connected. +class AfterConnect { +protected: + sp<ICameraService> cs; + sp<MCameraClient> cc; + sp<ICamera> c; + + AfterConnect() { + cs = getCameraService(); + cc = new MCameraClient(); + c = cs->connect(cc); + ASSERT(c != 0); + } + + ~AfterConnect() { + c.clear(); + cc.clear(); + cs.clear(); + } +}; + +class TestSetPreviewDisplay : public AfterConnect { +public: + void run() { + sp<MSurface> surface = new MSurface(); + ASSERT(c->setPreviewDisplay(surface) == NO_ERROR); + c->disconnect(); + cc->assertNotify(CAMERA_MSG_ERROR, MCameraClient::EQ, 0); + } +}; + +class TestStartPreview : public AfterConnect { +public: + void run() { + sp<MSurface> surface = new MSurface(); + ASSERT(c->setPreviewDisplay(surface) == NO_ERROR); + + ASSERT(c->startPreview() == NO_ERROR); + ASSERT(c->previewEnabled() == true); + + surface->waitUntil(1, 10, 0); // needs 1 registerBuffers and 10 postBuffer + surface->clearStat(); + + c->disconnect(); + // TODO: CameraService crashes for this. Fix it. +#if 0 + sp<MSurface> another_surface = new MSurface(); + c->setPreviewDisplay(another_surface); // just to make sure unregisterBuffers + // is called. + surface->waitUntil(0, 0, 1); // needs unregisterBuffers +#endif + cc->assertNotify(CAMERA_MSG_ERROR, MCameraClient::EQ, 0); + } +}; + +class TestStartPreviewWithoutDisplay : AfterConnect { +public: + void run() { + ASSERT(c->startPreview() == NO_ERROR); + ASSERT(c->previewEnabled() == true); + c->disconnect(); + cc->assertNotify(CAMERA_MSG_ERROR, MCameraClient::EQ, 0); + } +}; + +// Base test case after the the camera is connected and the preview is started. +class AfterStartPreview : public AfterConnect { +protected: + sp<MSurface> surface; + + AfterStartPreview() { + surface = new MSurface(); + ASSERT(c->setPreviewDisplay(surface) == NO_ERROR); + ASSERT(c->startPreview() == NO_ERROR); + } + + ~AfterStartPreview() { + surface.clear(); + } +}; + +class TestAutoFocus : public AfterStartPreview { +public: + void run() { + cc->assertNotify(CAMERA_MSG_FOCUS, MCameraClient::EQ, 0); + c->autoFocus(); + cc->waitNotify(CAMERA_MSG_FOCUS, MCameraClient::EQ, 1); + c->disconnect(); + cc->assertNotify(CAMERA_MSG_ERROR, MCameraClient::EQ, 0); + } +}; + +class TestStopPreview : public AfterStartPreview { +public: + void run() { + ASSERT(c->previewEnabled() == true); + c->stopPreview(); + ASSERT(c->previewEnabled() == false); + c->disconnect(); + cc->assertNotify(CAMERA_MSG_ERROR, MCameraClient::EQ, 0); + } +}; + +class TestTakePicture: public AfterStartPreview { +public: + void run() { + ASSERT(c->takePicture() == NO_ERROR); + cc->waitNotify(CAMERA_MSG_SHUTTER, MCameraClient::EQ, 1); + cc->waitData(CAMERA_MSG_RAW_IMAGE, MCameraClient::EQ, 1); + cc->waitData(CAMERA_MSG_COMPRESSED_IMAGE, MCameraClient::EQ, 1); + c->stopPreview(); +#if 1 // TODO: It crashes if we don't have this. Fix it. + usleep(100000); +#endif + c->disconnect(); + cc->assertNotify(CAMERA_MSG_ERROR, MCameraClient::EQ, 0); + } +}; + +class TestTakeMultiplePictures: public AfterStartPreview { +public: + void run() { + for (int i = 0; i < 10; i++) { + cc->clearStat(); + ASSERT(c->takePicture() == NO_ERROR); + cc->waitNotify(CAMERA_MSG_SHUTTER, MCameraClient::EQ, 1); + cc->waitData(CAMERA_MSG_RAW_IMAGE, MCameraClient::EQ, 1); + cc->waitData(CAMERA_MSG_COMPRESSED_IMAGE, MCameraClient::EQ, 1); + usleep(100000); // 100ms + } + c->disconnect(); + cc->assertNotify(CAMERA_MSG_ERROR, MCameraClient::EQ, 0); + } +}; + +class TestGetParameters: public AfterStartPreview { +public: + void run() { + String8 param_str = c->getParameters(); + INFO(param_str); + } +}; + +class TestPictureSize : public AfterStartPreview { +public: + void checkOnePicture(int w, int h) { + const float rate = 0.5; // byte per pixel limit + int pixels = w * h; + + CameraParameters param(c->getParameters()); + param.setPictureSize(w, h); + c->setParameters(param.flatten()); + + cc->clearStat(); + ASSERT(c->takePicture() == NO_ERROR); + cc->waitData(CAMERA_MSG_RAW_IMAGE, MCameraClient::EQ, 1); + cc->assertDataSize(CAMERA_MSG_RAW_IMAGE, MCameraClient::EQ, pixels*3/2); + cc->waitData(CAMERA_MSG_COMPRESSED_IMAGE, MCameraClient::EQ, 1); + cc->assertDataSize(CAMERA_MSG_COMPRESSED_IMAGE, MCameraClient::LT, + int(pixels * rate)); + cc->assertDataSize(CAMERA_MSG_COMPRESSED_IMAGE, MCameraClient::GT, 0); + cc->assertNotify(CAMERA_MSG_ERROR, MCameraClient::EQ, 0); + usleep(100000); // 100ms + } + + void run() { + checkOnePicture(2048, 1536); + checkOnePicture(1600, 1200); + checkOnePicture(1024, 768); + } +}; + +class TestPreviewCallbackFlag : public AfterConnect { +public: + void run() { + sp<MSurface> surface = new MSurface(); + ASSERT(c->setPreviewDisplay(surface) == NO_ERROR); + + // Try all flag combinations. + for (int v = 0; v < 8; v++) { + cc->clearStat(); + c->setPreviewCallbackFlag(v); + ASSERT(c->previewEnabled() == false); + ASSERT(c->startPreview() == NO_ERROR); + ASSERT(c->previewEnabled() == true); + sleep(2); + c->stopPreview(); + if ((v & FRAME_CALLBACK_FLAG_ENABLE_MASK) == 0) { + cc->assertData(CAMERA_MSG_PREVIEW_FRAME, MCameraClient::EQ, 0); + } else { + if ((v & FRAME_CALLBACK_FLAG_ONE_SHOT_MASK) == 0) { + cc->assertData(CAMERA_MSG_PREVIEW_FRAME, MCameraClient::GE, 10); + } else { + cc->assertData(CAMERA_MSG_PREVIEW_FRAME, MCameraClient::EQ, 1); + } + } + } + } +}; + +class TestRecording : public AfterConnect { +public: + void run() { + ASSERT(c->recordingEnabled() == false); + sp<MSurface> surface = new MSurface(); + ASSERT(c->setPreviewDisplay(surface) == NO_ERROR); + c->setPreviewCallbackFlag(FRAME_CALLBACK_FLAG_ENABLE_MASK); + cc->setReleaser(c.get()); + c->startRecording(); + ASSERT(c->recordingEnabled() == true); + sleep(2); + c->stopRecording(); + cc->setReleaser(NULL); + cc->assertData(CAMERA_MSG_VIDEO_FRAME, MCameraClient::GE, 10); + } +}; + +class TestPreviewSize : public AfterStartPreview { +public: + void checkOnePicture(int w, int h) { + int size = w*h*3/2; // should read from parameters + + c->stopPreview(); + + CameraParameters param(c->getParameters()); + param.setPreviewSize(w, h); + c->setPreviewCallbackFlag(FRAME_CALLBACK_FLAG_ENABLE_MASK); + c->setParameters(param.flatten()); + + c->startPreview(); + + cc->clearStat(); + cc->waitData(CAMERA_MSG_PREVIEW_FRAME, MCameraClient::GE, 1); + cc->assertDataSize(CAMERA_MSG_PREVIEW_FRAME, MCameraClient::EQ, size); + } + + void run() { + checkOnePicture(480, 320); + checkOnePicture(352, 288); + checkOnePicture(176, 144); + } +}; + +void runHolderService() { + defaultServiceManager()->addService( + String16("CameraServiceTest.Holder"), new HolderService()); + ProcessState::self()->startThreadPool(); +} + +int main(int argc, char **argv) +{ + if (argc != 1) { + runFunction(argv[1]); + return 0; + } + INFO("CameraServiceTest start"); + gExecutable = argv[0]; + runHolderService(); + + testConnect(); flushCommands(); + testAllowConnectOnceOnly(); flushCommands(); + testReconnect(); flushCommands(); + testLockUnlock(); flushCommands(); + testReconnectFromAnotherProcess(); flushCommands(); + + RUN(TestSetPreviewDisplay); + RUN(TestStartPreview); + RUN(TestStartPreviewWithoutDisplay); + RUN(TestAutoFocus); + RUN(TestStopPreview); + RUN(TestTakePicture); + RUN(TestTakeMultiplePictures); + RUN(TestGetParameters); + RUN(TestPictureSize); + RUN(TestPreviewCallbackFlag); + RUN(TestRecording); + RUN(TestPreviewSize); +} |