Created
October 15, 2019 17:19
-
-
Save namchuai/e782ec8006fc0bc79b774a1d2e34cab5 to your computer and use it in GitHub Desktop.
Modified android audioflinger
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
** | |
** Copyright 2012, 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 | |
#define ATRACE_TAG ATRACE_TAG_AUDIO | |
#include "Configuration.h" | |
#include <math.h> | |
#include <fcntl.h> | |
#include <linux/futex.h> | |
#include <sys/stat.h> | |
#include <sys/syscall.h> | |
#include <cutils/properties.h> | |
#include <media/AudioParameter.h> | |
#include <media/AudioResamplerPublic.h> | |
#include <media/RecordBufferConverter.h> | |
#include <media/TypeConverter.h> | |
#include <utils/Log.h> | |
#include <utils/Trace.h> | |
#include <private/media/AudioTrackShared.h> | |
#include <private/android_filesystem_config.h> | |
#include <audio_utils/mono_blend.h> | |
#include <audio_utils/primitives.h> | |
#include <audio_utils/format.h> | |
#include <audio_utils/minifloat.h> | |
#include <system/audio_effects/effect_ns.h> | |
#include <system/audio_effects/effect_aec.h> | |
#include <system/audio.h> | |
// NBAIO implementations | |
#include <media/nbaio/AudioStreamInSource.h> | |
#include <media/nbaio/AudioStreamOutSink.h> | |
#include <media/nbaio/MonoPipe.h> | |
#include <media/nbaio/MonoPipeReader.h> | |
#include <media/nbaio/Pipe.h> | |
#include <media/nbaio/PipeReader.h> | |
#include <media/nbaio/SourceAudioBufferProvider.h> | |
#include <mediautils/BatteryNotifier.h> | |
#include <powermanager/PowerManager.h> | |
#include "AudioFlinger.h" | |
#include "FastMixer.h" | |
#include "FastCapture.h" | |
#include "ServiceUtilities.h" | |
#include "mediautils/SchedulingPolicyService.h" | |
#ifdef ADD_BATTERY_DATA | |
#include <media/IMediaPlayerService.h> | |
#include <media/IMediaDeathNotifier.h> | |
#endif | |
#ifdef DEBUG_CPU_USAGE | |
#include <cpustats/CentralTendencyStatistics.h> | |
#include <cpustats/ThreadCpuUsage.h> | |
#endif | |
#include "AutoPark.h" | |
#include <pthread.h> | |
#include "TypedLogger.h" | |
#include <iostream> | |
#include <fstream> | |
// ---------------------------------------------------------------------------- | |
// Note: the following macro is used for extremely verbose logging message. In | |
// order to run with ALOG_ASSERT turned on, we need to have LOG_NDEBUG set to | |
// 0; but one side effect of this is to turn all LOGV's as well. Some messages | |
// are so verbose that we want to suppress them even when we have ALOG_ASSERT | |
// turned on. Do not uncomment the #def below unless you really know what you | |
// are doing and want to see all of the extremely verbose messages. | |
//#define VERY_VERY_VERBOSE_LOGGING | |
#ifdef VERY_VERY_VERBOSE_LOGGING | |
#define ALOGVV ALOGV | |
#else | |
#define ALOGVV(a...) do { } while(0) | |
#endif | |
// TODO: Move these macro/inlines to a header file. | |
#define max(a, b) ((a) > (b) ? (a) : (b)) | |
template <typename T> | |
static inline T min(const T& a, const T& b) | |
{ | |
return a < b ? a : b; | |
} | |
namespace android { | |
// 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; | |
// don't warn about blocked writes or record buffer overflows more often than this | |
static const nsecs_t kWarningThrottleNs = seconds(5); | |
// RecordThread loop sleep time upon application overrun or audio HAL read error | |
static const int kRecordThreadSleepUs = 5000; | |
// maximum time to wait in sendConfigEvent_l() for a status to be received | |
static const nsecs_t kConfigEventTimeoutNs = seconds(2); | |
// minimum sleep time for the mixer thread loop when tracks are active but in underrun | |
static const uint32_t kMinThreadSleepTimeUs = 5000; | |
// maximum divider applied to the active sleep time in the mixer thread loop | |
static const uint32_t kMaxThreadSleepTimeShift = 2; | |
// minimum normal sink buffer size, expressed in milliseconds rather than frames | |
// FIXME This should be based on experimentally observed scheduling jitter | |
static const uint32_t kMinNormalSinkBufferSizeMs = 20; | |
// maximum normal sink buffer size | |
static const uint32_t kMaxNormalSinkBufferSizeMs = 24; | |
// minimum capture buffer size in milliseconds to _not_ need a fast capture thread | |
// FIXME This should be based on experimentally observed scheduling jitter | |
static const uint32_t kMinNormalCaptureBufferSizeMs = 12; | |
// Offloaded output thread standby delay: allows track transition without going to standby | |
static const nsecs_t kOffloadStandbyDelayNs = seconds(1); | |
// Direct output thread minimum sleep time in idle or active(underrun) state | |
static const nsecs_t kDirectMinSleepTimeUs = 10000; | |
// The universal constant for ubiquitous 20ms value. The value of 20ms seems to provide a good | |
// balance between power consumption and latency, and allows threads to be scheduled reliably | |
// by the CFS scheduler. | |
// FIXME Express other hardcoded references to 20ms with references to this constant and move | |
// it appropriately. | |
#define FMS_20 20 | |
// Whether to use fast mixer | |
static const enum { | |
FastMixer_Never, // never initialize or use: for debugging only | |
FastMixer_Always, // always initialize and use, even if not needed: for debugging only | |
// normal mixer multiplier is 1 | |
FastMixer_Static, // initialize if needed, then use all the time if initialized, | |
// multiplier is calculated based on min & max normal mixer buffer size | |
FastMixer_Dynamic, // initialize if needed, then use dynamically depending on track load, | |
// multiplier is calculated based on min & max normal mixer buffer size | |
// FIXME for FastMixer_Dynamic: | |
// Supporting this option will require fixing HALs that can't handle large writes. | |
// For example, one HAL implementation returns an error from a large write, | |
// and another HAL implementation corrupts memory, possibly in the sample rate converter. | |
// We could either fix the HAL implementations, or provide a wrapper that breaks | |
// up large writes into smaller ones, and the wrapper would need to deal with scheduler. | |
} kUseFastMixer = FastMixer_Static; | |
// Whether to use fast capture | |
static const enum { | |
FastCapture_Never, // never initialize or use: for debugging only | |
FastCapture_Always, // always initialize and use, even if not needed: for debugging only | |
FastCapture_Static, // initialize if needed, then use all the time if initialized | |
} kUseFastCapture = FastCapture_Static; | |
// Priorities for requestPriority | |
static const int kPriorityAudioApp = 2; | |
static const int kPriorityFastMixer = 3; | |
static const int kPriorityFastCapture = 3; | |
// IAudioFlinger::createTrack() has an in/out parameter 'pFrameCount' for the total size of the | |
// track buffer in shared memory. Zero on input means to use a default value. For fast tracks, | |
// AudioFlinger derives the default from HAL buffer size and 'fast track multiplier'. | |
// This is the default value, if not specified by property. | |
static const int kFastTrackMultiplier = 2; | |
// The minimum and maximum allowed values | |
static const int kFastTrackMultiplierMin = 1; | |
static const int kFastTrackMultiplierMax = 2; | |
// The actual value to use, which can be specified per-device via property af.fast_track_multiplier. | |
static int sFastTrackMultiplier = kFastTrackMultiplier; | |
// See Thread::readOnlyHeap(). | |
// Initially this heap is used to allocate client buffers for "fast" AudioRecord. | |
// Eventually it will be the single buffer that FastCapture writes into via HAL read(), | |
// and that all "fast" AudioRecord clients read from. In either case, the size can be small. | |
static const size_t kRecordThreadReadOnlyHeapSize = 0x4000; | |
// ---------------------------------------------------------------------------- | |
static pthread_once_t sFastTrackMultiplierOnce = PTHREAD_ONCE_INIT; | |
static void sFastTrackMultiplierInit() | |
{ | |
char value[PROPERTY_VALUE_MAX]; | |
if (property_get("af.fast_track_multiplier", value, NULL) > 0) { | |
char *endptr; | |
unsigned long ul = strtoul(value, &endptr, 0); | |
if (*endptr == '\0' && kFastTrackMultiplierMin <= ul && ul <= kFastTrackMultiplierMax) { | |
sFastTrackMultiplier = (int) ul; | |
} | |
} | |
} | |
// ---------------------------------------------------------------------------- | |
#ifdef ADD_BATTERY_DATA | |
// To collect the amplifier usage | |
static void addBatteryData(uint32_t params) { | |
sp<IMediaPlayerService> service = IMediaDeathNotifier::getMediaPlayerService(); | |
if (service == NULL) { | |
// it already logged | |
return; | |
} | |
service->addBatteryData(params); | |
} | |
#endif | |
// Track the CLOCK_BOOTTIME versus CLOCK_MONOTONIC timebase offset | |
struct { | |
// call when you acquire a partial wakelock | |
void acquire(const sp<IBinder> &wakeLockToken) { | |
pthread_mutex_lock(&mLock); | |
if (wakeLockToken.get() == nullptr) { | |
adjustTimebaseOffset(&mBoottimeOffset, ExtendedTimestamp::TIMEBASE_BOOTTIME); | |
} else { | |
if (mCount == 0) { | |
adjustTimebaseOffset(&mBoottimeOffset, ExtendedTimestamp::TIMEBASE_BOOTTIME); | |
} | |
++mCount; | |
} | |
pthread_mutex_unlock(&mLock); | |
} | |
// call when you release a partial wakelock. | |
void release(const sp<IBinder> &wakeLockToken) { | |
if (wakeLockToken.get() == nullptr) { | |
return; | |
} | |
pthread_mutex_lock(&mLock); | |
if (--mCount < 0) { | |
ALOGE("negative wakelock count"); | |
mCount = 0; | |
} | |
pthread_mutex_unlock(&mLock); | |
} | |
// retrieves the boottime timebase offset from monotonic. | |
int64_t getBoottimeOffset() { | |
pthread_mutex_lock(&mLock); | |
int64_t boottimeOffset = mBoottimeOffset; | |
pthread_mutex_unlock(&mLock); | |
return boottimeOffset; | |
} | |
// Adjusts the timebase offset between TIMEBASE_MONOTONIC | |
// and the selected timebase. | |
// Currently only TIMEBASE_BOOTTIME is allowed. | |
// | |
// This only needs to be called upon acquiring the first partial wakelock | |
// after all other partial wakelocks are released. | |
// | |
// We do an empirical measurement of the offset rather than parsing | |
// /proc/timer_list since the latter is not a formal kernel ABI. | |
static void adjustTimebaseOffset(int64_t *offset, ExtendedTimestamp::Timebase timebase) { | |
int clockbase; | |
switch (timebase) { | |
case ExtendedTimestamp::TIMEBASE_BOOTTIME: | |
clockbase = SYSTEM_TIME_BOOTTIME; | |
break; | |
default: | |
LOG_ALWAYS_FATAL("invalid timebase %d", timebase); | |
break; | |
} | |
// try three times to get the clock offset, choose the one | |
// with the minimum gap in measurements. | |
const int tries = 3; | |
nsecs_t bestGap, measured; | |
for (int i = 0; i < tries; ++i) { | |
const nsecs_t tmono = systemTime(SYSTEM_TIME_MONOTONIC); | |
const nsecs_t tbase = systemTime(clockbase); | |
const nsecs_t tmono2 = systemTime(SYSTEM_TIME_MONOTONIC); | |
const nsecs_t gap = tmono2 - tmono; | |
if (i == 0 || gap < bestGap) { | |
bestGap = gap; | |
measured = tbase - ((tmono + tmono2) >> 1); | |
} | |
} | |
// to avoid micro-adjusting, we don't change the timebase | |
// unless it is significantly different. | |
// | |
// Assumption: It probably takes more than toleranceNs to | |
// suspend and resume the device. | |
static int64_t toleranceNs = 10000; // 10 us | |
if (llabs(*offset - measured) > toleranceNs) { | |
ALOGV("Adjusting timebase offset old: %lld new: %lld", | |
(long long)*offset, (long long)measured); | |
*offset = measured; | |
} | |
} | |
pthread_mutex_t mLock; | |
int32_t mCount; | |
int64_t mBoottimeOffset; | |
} gBoottime = { PTHREAD_MUTEX_INITIALIZER, 0, 0 }; // static, so use POD initialization | |
// ---------------------------------------------------------------------------- | |
// CPU Stats | |
// ---------------------------------------------------------------------------- | |
class CpuStats { | |
public: | |
CpuStats(); | |
void sample(const String8 &title); | |
#ifdef DEBUG_CPU_USAGE | |
private: | |
ThreadCpuUsage mCpuUsage; // instantaneous thread CPU usage in wall clock ns | |
CentralTendencyStatistics mWcStats; // statistics on thread CPU usage in wall clock ns | |
CentralTendencyStatistics mHzStats; // statistics on thread CPU usage in cycles | |
int mCpuNum; // thread's current CPU number | |
int mCpukHz; // frequency of thread's current CPU in kHz | |
#endif | |
}; | |
CpuStats::CpuStats() | |
#ifdef DEBUG_CPU_USAGE | |
: mCpuNum(-1), mCpukHz(-1) | |
#endif | |
{ | |
} | |
void CpuStats::sample(const String8 &title | |
#ifndef DEBUG_CPU_USAGE | |
__unused | |
#endif | |
) { | |
#ifdef DEBUG_CPU_USAGE | |
// get current thread's delta CPU time in wall clock ns | |
double wcNs; | |
bool valid = mCpuUsage.sampleAndEnable(wcNs); | |
// record sample for wall clock statistics | |
if (valid) { | |
mWcStats.sample(wcNs); | |
} | |
// get the current CPU number | |
int cpuNum = sched_getcpu(); | |
// get the current CPU frequency in kHz | |
int cpukHz = mCpuUsage.getCpukHz(cpuNum); | |
// check if either CPU number or frequency changed | |
if (cpuNum != mCpuNum || cpukHz != mCpukHz) { | |
mCpuNum = cpuNum; | |
mCpukHz = cpukHz; | |
// ignore sample for purposes of cycles | |
valid = false; | |
} | |
// if no change in CPU number or frequency, then record sample for cycle statistics | |
if (valid && mCpukHz > 0) { | |
double cycles = wcNs * cpukHz * 0.000001; | |
mHzStats.sample(cycles); | |
} | |
unsigned n = mWcStats.n(); | |
// mCpuUsage.elapsed() is expensive, so don't call it every loop | |
if ((n & 127) == 1) { | |
long long elapsed = mCpuUsage.elapsed(); | |
if (elapsed >= DEBUG_CPU_USAGE * 1000000000LL) { | |
double perLoop = elapsed / (double) n; | |
double perLoop100 = perLoop * 0.01; | |
double perLoop1k = perLoop * 0.001; | |
double mean = mWcStats.mean(); | |
double stddev = mWcStats.stddev(); | |
double minimum = mWcStats.minimum(); | |
double maximum = mWcStats.maximum(); | |
double meanCycles = mHzStats.mean(); | |
double stddevCycles = mHzStats.stddev(); | |
double minCycles = mHzStats.minimum(); | |
double maxCycles = mHzStats.maximum(); | |
mCpuUsage.resetElapsed(); | |
mWcStats.reset(); | |
mHzStats.reset(); | |
ALOGD("CPU usage for %s over past %.1f secs\n" | |
" (%u mixer loops at %.1f mean ms per loop):\n" | |
" us per mix loop: mean=%.0f stddev=%.0f min=%.0f max=%.0f\n" | |
" %% of wall: mean=%.1f stddev=%.1f min=%.1f max=%.1f\n" | |
" MHz: mean=%.1f, stddev=%.1f, min=%.1f max=%.1f", | |
title.string(), | |
elapsed * .000000001, n, perLoop * .000001, | |
mean * .001, | |
stddev * .001, | |
minimum * .001, | |
maximum * .001, | |
mean / perLoop100, | |
stddev / perLoop100, | |
minimum / perLoop100, | |
maximum / perLoop100, | |
meanCycles / perLoop1k, | |
stddevCycles / perLoop1k, | |
minCycles / perLoop1k, | |
maxCycles / perLoop1k); | |
} | |
} | |
#endif | |
}; | |
// ---------------------------------------------------------------------------- | |
// ThreadBase | |
// ---------------------------------------------------------------------------- | |
// static | |
const char *AudioFlinger::ThreadBase::threadTypeToString(AudioFlinger::ThreadBase::type_t type) | |
{ | |
switch (type) { | |
case MIXER: | |
return "MIXER"; | |
case DIRECT: | |
return "DIRECT"; | |
case DUPLICATING: | |
return "DUPLICATING"; | |
case RECORD: | |
return "RECORD"; | |
case OFFLOAD: | |
return "OFFLOAD"; | |
case MMAP: | |
return "MMAP"; | |
default: | |
return "unknown"; | |
} | |
} | |
std::string devicesToString(audio_devices_t devices) | |
{ | |
std::string result; | |
if (devices & AUDIO_DEVICE_BIT_IN) { | |
InputDeviceConverter::maskToString(devices, result); | |
} else { | |
OutputDeviceConverter::maskToString(devices, result); | |
} | |
return result; | |
} | |
std::string inputFlagsToString(audio_input_flags_t flags) | |
{ | |
std::string result; | |
InputFlagConverter::maskToString(flags, result); | |
return result; | |
} | |
std::string outputFlagsToString(audio_output_flags_t flags) | |
{ | |
std::string result; | |
OutputFlagConverter::maskToString(flags, result); | |
return result; | |
} | |
const char *sourceToString(audio_source_t source) | |
{ | |
switch (source) { | |
case AUDIO_SOURCE_DEFAULT: return "default"; | |
case AUDIO_SOURCE_MIC: return "mic"; | |
case AUDIO_SOURCE_VOICE_UPLINK: return "voice uplink"; | |
case AUDIO_SOURCE_VOICE_DOWNLINK: return "voice downlink"; | |
case AUDIO_SOURCE_VOICE_CALL: return "voice call"; | |
case AUDIO_SOURCE_CAMCORDER: return "camcorder"; | |
case AUDIO_SOURCE_VOICE_RECOGNITION: return "voice recognition"; | |
case AUDIO_SOURCE_VOICE_COMMUNICATION: return "voice communication"; | |
case AUDIO_SOURCE_REMOTE_SUBMIX: return "remote submix"; | |
case AUDIO_SOURCE_UNPROCESSED: return "unprocessed"; | |
case AUDIO_SOURCE_FM_TUNER: return "FM tuner"; | |
case AUDIO_SOURCE_HOTWORD: return "hotword"; | |
default: return "unknown"; | |
} | |
} | |
AudioFlinger::ThreadBase::ThreadBase(const sp<AudioFlinger>& audioFlinger, audio_io_handle_t id, | |
audio_devices_t outDevice, audio_devices_t inDevice, type_t type, bool systemReady) | |
: Thread(false /*canCallJava*/), | |
mType(type), | |
mAudioFlinger(audioFlinger), | |
// mSampleRate, mFrameCount, mChannelMask, mChannelCount, mFrameSize, mFormat, mBufferSize | |
// are set by PlaybackThread::readOutputParameters_l() or | |
// RecordThread::readInputParameters_l() | |
//FIXME: mStandby should be true here. Is this some kind of hack? | |
mStandby(false), mOutDevice(outDevice), mInDevice(inDevice), | |
mPrevOutDevice(AUDIO_DEVICE_NONE), mPrevInDevice(AUDIO_DEVICE_NONE), | |
mAudioSource(AUDIO_SOURCE_DEFAULT), mId(id), | |
// mName will be set by concrete (non-virtual) subclass | |
mDeathRecipient(new PMDeathRecipient(this)), | |
mSystemReady(systemReady), | |
mSignalPending(false) | |
{ | |
memset(&mPatch, 0, sizeof(struct audio_patch)); | |
} | |
AudioFlinger::ThreadBase::~ThreadBase() | |
{ | |
// mConfigEvents should be empty, but just in case it isn't, free the memory it owns | |
mConfigEvents.clear(); | |
// do not lock the mutex in destructor | |
releaseWakeLock_l(); | |
if (mPowerManager != 0) { | |
sp<IBinder> binder = IInterface::asBinder(mPowerManager); | |
binder->unlinkToDeath(mDeathRecipient); | |
} | |
} | |
status_t AudioFlinger::ThreadBase::readyToRun() | |
{ | |
status_t status = initCheck(); | |
if (status == NO_ERROR) { | |
ALOGI("AudioFlinger's thread %p tid=%d ready to run", this, getTid()); | |
} else { | |
ALOGE("No working audio driver found."); | |
} | |
return status; | |
} | |
void AudioFlinger::ThreadBase::exit() | |
{ | |
ALOGV("ThreadBase::exit"); | |
// do any cleanup required for exit to succeed | |
preExit(); | |
{ | |
// This lock prevents the following race in thread (uniprocessor for illustration): | |
// if (!exitPending()) { | |
// // context switch from here to exit() | |
// // exit() calls requestExit(), what exitPending() observes | |
// // exit() calls signal(), which is dropped since no waiters | |
// // context switch back from exit() to here | |
// mWaitWorkCV.wait(...); | |
// // now thread is hung | |
// } | |
AutoMutex lock(mLock); | |
requestExit(); | |
mWaitWorkCV.broadcast(); | |
} | |
// When Thread::requestExitAndWait is made virtual and this method is renamed to | |
// "virtual status_t requestExitAndWait()", replace by "return Thread::requestExitAndWait();" | |
requestExitAndWait(); | |
} | |
status_t AudioFlinger::ThreadBase::setParameters(const String8& keyValuePairs) | |
{ | |
ALOGV("ThreadBase::setParameters() %s", keyValuePairs.string()); | |
Mutex::Autolock _l(mLock); | |
return sendSetParameterConfigEvent_l(keyValuePairs); | |
} | |
// sendConfigEvent_l() must be called with ThreadBase::mLock held | |
// Can temporarily release the lock if waiting for a reply from processConfigEvents_l(). | |
status_t AudioFlinger::ThreadBase::sendConfigEvent_l(sp<ConfigEvent>& event) | |
{ | |
status_t status = NO_ERROR; | |
if (event->mRequiresSystemReady && !mSystemReady) { | |
event->mWaitStatus = false; | |
mPendingConfigEvents.add(event); | |
return status; | |
} | |
mConfigEvents.add(event); | |
ALOGV("sendConfigEvent_l() num events %zu event %d", mConfigEvents.size(), event->mType); | |
mWaitWorkCV.signal(); | |
mLock.unlock(); | |
{ | |
Mutex::Autolock _l(event->mLock); | |
while (event->mWaitStatus) { | |
if (event->mCond.waitRelative(event->mLock, kConfigEventTimeoutNs) != NO_ERROR) { | |
event->mStatus = TIMED_OUT; | |
event->mWaitStatus = false; | |
} | |
} | |
status = event->mStatus; | |
} | |
mLock.lock(); | |
return status; | |
} | |
void AudioFlinger::ThreadBase::sendIoConfigEvent(audio_io_config_event event, pid_t pid) | |
{ | |
Mutex::Autolock _l(mLock); | |
sendIoConfigEvent_l(event, pid); | |
} | |
// sendIoConfigEvent_l() must be called with ThreadBase::mLock held | |
void AudioFlinger::ThreadBase::sendIoConfigEvent_l(audio_io_config_event event, pid_t pid) | |
{ | |
sp<ConfigEvent> configEvent = (ConfigEvent *)new IoConfigEvent(event, pid); | |
sendConfigEvent_l(configEvent); | |
} | |
void AudioFlinger::ThreadBase::sendPrioConfigEvent(pid_t pid, pid_t tid, int32_t prio, bool forApp) | |
{ | |
Mutex::Autolock _l(mLock); | |
sendPrioConfigEvent_l(pid, tid, prio, forApp); | |
} | |
// sendPrioConfigEvent_l() must be called with ThreadBase::mLock held | |
void AudioFlinger::ThreadBase::sendPrioConfigEvent_l( | |
pid_t pid, pid_t tid, int32_t prio, bool forApp) | |
{ | |
sp<ConfigEvent> configEvent = (ConfigEvent *)new PrioConfigEvent(pid, tid, prio, forApp); | |
sendConfigEvent_l(configEvent); | |
} | |
// sendSetParameterConfigEvent_l() must be called with ThreadBase::mLock held | |
status_t AudioFlinger::ThreadBase::sendSetParameterConfigEvent_l(const String8& keyValuePair) | |
{ | |
sp<ConfigEvent> configEvent; | |
AudioParameter param(keyValuePair); | |
int value; | |
if (param.getInt(String8(AudioParameter::keyMonoOutput), value) == NO_ERROR) { | |
setMasterMono_l(value != 0); | |
if (param.size() == 1) { | |
return NO_ERROR; // should be a solo parameter - we don't pass down | |
} | |
param.remove(String8(AudioParameter::keyMonoOutput)); | |
configEvent = new SetParameterConfigEvent(param.toString()); | |
} else { | |
configEvent = new SetParameterConfigEvent(keyValuePair); | |
} | |
return sendConfigEvent_l(configEvent); | |
} | |
status_t AudioFlinger::ThreadBase::sendCreateAudioPatchConfigEvent( | |
const struct audio_patch *patch, | |
audio_patch_handle_t *handle) | |
{ | |
Mutex::Autolock _l(mLock); | |
sp<ConfigEvent> configEvent = (ConfigEvent *)new CreateAudioPatchConfigEvent(*patch, *handle); | |
status_t status = sendConfigEvent_l(configEvent); | |
if (status == NO_ERROR) { | |
CreateAudioPatchConfigEventData *data = | |
(CreateAudioPatchConfigEventData *)configEvent->mData.get(); | |
*handle = data->mHandle; | |
} | |
return status; | |
} | |
status_t AudioFlinger::ThreadBase::sendReleaseAudioPatchConfigEvent( | |
const audio_patch_handle_t handle) | |
{ | |
Mutex::Autolock _l(mLock); | |
sp<ConfigEvent> configEvent = (ConfigEvent *)new ReleaseAudioPatchConfigEvent(handle); | |
return sendConfigEvent_l(configEvent); | |
} | |
// post condition: mConfigEvents.isEmpty() | |
void AudioFlinger::ThreadBase::processConfigEvents_l() | |
{ | |
bool configChanged = false; | |
while (!mConfigEvents.isEmpty()) { | |
ALOGV("processConfigEvents_l() remaining events %zu", mConfigEvents.size()); | |
sp<ConfigEvent> event = mConfigEvents[0]; | |
mConfigEvents.removeAt(0); | |
switch (event->mType) { | |
case CFG_EVENT_PRIO: { | |
PrioConfigEventData *data = (PrioConfigEventData *)event->mData.get(); | |
// FIXME Need to understand why this has to be done asynchronously | |
int err = requestPriority(data->mPid, data->mTid, data->mPrio, data->mForApp, | |
true /*asynchronous*/); | |
if (err != 0) { | |
ALOGW("Policy SCHED_FIFO priority %d is unavailable for pid %d tid %d; error %d", | |
data->mPrio, data->mPid, data->mTid, err); | |
} | |
} break; | |
case CFG_EVENT_IO: { | |
IoConfigEventData *data = (IoConfigEventData *)event->mData.get(); | |
ioConfigChanged(data->mEvent, data->mPid); | |
} break; | |
case CFG_EVENT_SET_PARAMETER: { | |
SetParameterConfigEventData *data = (SetParameterConfigEventData *)event->mData.get(); | |
if (checkForNewParameter_l(data->mKeyValuePairs, event->mStatus)) { | |
configChanged = true; | |
mLocalLog.log("CFG_EVENT_SET_PARAMETER: (%s) configuration changed", | |
data->mKeyValuePairs.string()); | |
} | |
} break; | |
case CFG_EVENT_CREATE_AUDIO_PATCH: { | |
const audio_devices_t oldDevice = getDevice(); | |
CreateAudioPatchConfigEventData *data = | |
(CreateAudioPatchConfigEventData *)event->mData.get(); | |
event->mStatus = createAudioPatch_l(&data->mPatch, &data->mHandle); | |
const audio_devices_t newDevice = getDevice(); | |
mLocalLog.log("CFG_EVENT_CREATE_AUDIO_PATCH: old device %#x (%s) new device %#x (%s)", | |
(unsigned)oldDevice, devicesToString(oldDevice).c_str(), | |
(unsigned)newDevice, devicesToString(newDevice).c_str()); | |
} break; | |
case CFG_EVENT_RELEASE_AUDIO_PATCH: { | |
const audio_devices_t oldDevice = getDevice(); | |
ReleaseAudioPatchConfigEventData *data = | |
(ReleaseAudioPatchConfigEventData *)event->mData.get(); | |
event->mStatus = releaseAudioPatch_l(data->mHandle); | |
const audio_devices_t newDevice = getDevice(); | |
mLocalLog.log("CFG_EVENT_RELEASE_AUDIO_PATCH: old device %#x (%s) new device %#x (%s)", | |
(unsigned)oldDevice, devicesToString(oldDevice).c_str(), | |
(unsigned)newDevice, devicesToString(newDevice).c_str()); | |
} break; | |
default: | |
ALOG_ASSERT(false, "processConfigEvents_l() unknown event type %d", event->mType); | |
break; | |
} | |
{ | |
Mutex::Autolock _l(event->mLock); | |
if (event->mWaitStatus) { | |
event->mWaitStatus = false; | |
event->mCond.signal(); | |
} | |
} | |
ALOGV_IF(mConfigEvents.isEmpty(), "processConfigEvents_l() DONE thread %p", this); | |
} | |
if (configChanged) { | |
cacheParameters_l(); | |
} | |
} | |
String8 channelMaskToString(audio_channel_mask_t mask, bool output) { | |
String8 s; | |
const audio_channel_representation_t representation = | |
audio_channel_mask_get_representation(mask); | |
switch (representation) { | |
case AUDIO_CHANNEL_REPRESENTATION_POSITION: { | |
if (output) { | |
if (mask & AUDIO_CHANNEL_OUT_FRONT_LEFT) s.append("front-left, "); | |
if (mask & AUDIO_CHANNEL_OUT_FRONT_RIGHT) s.append("front-right, "); | |
if (mask & AUDIO_CHANNEL_OUT_FRONT_CENTER) s.append("front-center, "); | |
if (mask & AUDIO_CHANNEL_OUT_LOW_FREQUENCY) s.append("low freq, "); | |
if (mask & AUDIO_CHANNEL_OUT_BACK_LEFT) s.append("back-left, "); | |
if (mask & AUDIO_CHANNEL_OUT_BACK_RIGHT) s.append("back-right, "); | |
if (mask & AUDIO_CHANNEL_OUT_FRONT_LEFT_OF_CENTER) s.append("front-left-of-center, "); | |
if (mask & AUDIO_CHANNEL_OUT_FRONT_RIGHT_OF_CENTER) s.append("front-right-of-center, "); | |
if (mask & AUDIO_CHANNEL_OUT_BACK_CENTER) s.append("back-center, "); | |
if (mask & AUDIO_CHANNEL_OUT_SIDE_LEFT) s.append("side-left, "); | |
if (mask & AUDIO_CHANNEL_OUT_SIDE_RIGHT) s.append("side-right, "); | |
if (mask & AUDIO_CHANNEL_OUT_TOP_CENTER) s.append("top-center ,"); | |
if (mask & AUDIO_CHANNEL_OUT_TOP_FRONT_LEFT) s.append("top-front-left, "); | |
if (mask & AUDIO_CHANNEL_OUT_TOP_FRONT_CENTER) s.append("top-front-center, "); | |
if (mask & AUDIO_CHANNEL_OUT_TOP_FRONT_RIGHT) s.append("top-front-right, "); | |
if (mask & AUDIO_CHANNEL_OUT_TOP_BACK_LEFT) s.append("top-back-left, "); | |
if (mask & AUDIO_CHANNEL_OUT_TOP_BACK_CENTER) s.append("top-back-center, " ); | |
if (mask & AUDIO_CHANNEL_OUT_TOP_BACK_RIGHT) s.append("top-back-right, " ); | |
if (mask & ~AUDIO_CHANNEL_OUT_ALL) s.append("unknown, "); | |
} else { | |
if (mask & AUDIO_CHANNEL_IN_LEFT) s.append("left, "); | |
if (mask & AUDIO_CHANNEL_IN_RIGHT) s.append("right, "); | |
if (mask & AUDIO_CHANNEL_IN_FRONT) s.append("front, "); | |
if (mask & AUDIO_CHANNEL_IN_BACK) s.append("back, "); | |
if (mask & AUDIO_CHANNEL_IN_LEFT_PROCESSED) s.append("left-processed, "); | |
if (mask & AUDIO_CHANNEL_IN_RIGHT_PROCESSED) s.append("right-processed, "); | |
if (mask & AUDIO_CHANNEL_IN_FRONT_PROCESSED) s.append("front-processed, "); | |
if (mask & AUDIO_CHANNEL_IN_BACK_PROCESSED) s.append("back-processed, "); | |
if (mask & AUDIO_CHANNEL_IN_PRESSURE) s.append("pressure, "); | |
if (mask & AUDIO_CHANNEL_IN_X_AXIS) s.append("X, "); | |
if (mask & AUDIO_CHANNEL_IN_Y_AXIS) s.append("Y, "); | |
if (mask & AUDIO_CHANNEL_IN_Z_AXIS) s.append("Z, "); | |
if (mask & AUDIO_CHANNEL_IN_VOICE_UPLINK) s.append("voice-uplink, "); | |
if (mask & AUDIO_CHANNEL_IN_VOICE_DNLINK) s.append("voice-dnlink, "); | |
if (mask & ~AUDIO_CHANNEL_IN_ALL) s.append("unknown, "); | |
} | |
const int len = s.length(); | |
if (len > 2) { | |
(void) s.lockBuffer(len); // needed? | |
s.unlockBuffer(len - 2); // remove trailing ", " | |
} | |
return s; | |
} | |
case AUDIO_CHANNEL_REPRESENTATION_INDEX: | |
s.appendFormat("index mask, bits:%#x", audio_channel_mask_get_bits(mask)); | |
return s; | |
default: | |
s.appendFormat("unknown mask, representation:%d bits:%#x", | |
representation, audio_channel_mask_get_bits(mask)); | |
return s; | |
} | |
} | |
void AudioFlinger::ThreadBase::dumpBase(int fd, const Vector<String16>& args __unused) | |
{ | |
const size_t SIZE = 256; | |
char buffer[SIZE]; | |
String8 result; | |
dprintf(fd, "\n%s thread %p, name %s, tid %d, type %d (%s):\n", isOutput() ? "Output" : "Input", | |
this, mThreadName, getTid(), type(), threadTypeToString(type())); | |
bool locked = AudioFlinger::dumpTryLock(mLock); | |
if (!locked) { | |
dprintf(fd, " Thread may be deadlocked\n"); | |
} | |
dprintf(fd, " I/O handle: %d\n", mId); | |
dprintf(fd, " Standby: %s\n", mStandby ? "yes" : "no"); | |
dprintf(fd, " Sample rate: %u Hz\n", mSampleRate); | |
dprintf(fd, " HAL frame count: %zu\n", mFrameCount); | |
dprintf(fd, " HAL format: 0x%x (%s)\n", mHALFormat, formatToString(mHALFormat).c_str()); | |
dprintf(fd, " HAL buffer size: %zu bytes\n", mBufferSize); | |
dprintf(fd, " Channel count: %u\n", mChannelCount); | |
dprintf(fd, " Channel mask: 0x%08x (%s)\n", mChannelMask, | |
channelMaskToString(mChannelMask, mType != RECORD).string()); | |
dprintf(fd, " Processing format: 0x%x (%s)\n", mFormat, formatToString(mFormat).c_str()); | |
dprintf(fd, " Processing frame size: %zu bytes\n", mFrameSize); | |
dprintf(fd, " Pending config events:"); | |
size_t numConfig = mConfigEvents.size(); | |
if (numConfig) { | |
for (size_t i = 0; i < numConfig; i++) { | |
mConfigEvents[i]->dump(buffer, SIZE); | |
dprintf(fd, "\n %s", buffer); | |
} | |
dprintf(fd, "\n"); | |
} else { | |
dprintf(fd, " none\n"); | |
} | |
// Note: output device may be used by capture threads for effects such as AEC. | |
dprintf(fd, " Output device: %#x (%s)\n", mOutDevice, devicesToString(mOutDevice).c_str()); | |
dprintf(fd, " Input device: %#x (%s)\n", mInDevice, devicesToString(mInDevice).c_str()); | |
dprintf(fd, " Audio source: %d (%s)\n", mAudioSource, sourceToString(mAudioSource)); | |
if (locked) { | |
mLock.unlock(); | |
} | |
} | |
void AudioFlinger::ThreadBase::dumpEffectChains(int fd, const Vector<String16>& args) | |
{ | |
const size_t SIZE = 256; | |
char buffer[SIZE]; | |
String8 result; | |
size_t numEffectChains = mEffectChains.size(); | |
snprintf(buffer, SIZE, " %zu Effect Chains\n", numEffectChains); | |
write(fd, buffer, strlen(buffer)); | |
for (size_t i = 0; i < numEffectChains; ++i) { | |
sp<EffectChain> chain = mEffectChains[i]; | |
if (chain != 0) { | |
chain->dump(fd, args); | |
} | |
} | |
} | |
void AudioFlinger::ThreadBase::acquireWakeLock() | |
{ | |
Mutex::Autolock _l(mLock); | |
acquireWakeLock_l(); | |
} | |
String16 AudioFlinger::ThreadBase::getWakeLockTag() | |
{ | |
switch (mType) { | |
case MIXER: | |
return String16("AudioMix"); | |
case DIRECT: | |
return String16("AudioDirectOut"); | |
case DUPLICATING: | |
return String16("AudioDup"); | |
case RECORD: | |
return String16("AudioIn"); | |
case OFFLOAD: | |
return String16("AudioOffload"); | |
case MMAP: | |
return String16("Mmap"); | |
default: | |
ALOG_ASSERT(false); | |
return String16("AudioUnknown"); | |
} | |
} | |
void AudioFlinger::ThreadBase::acquireWakeLock_l() | |
{ | |
getPowerManager_l(); | |
if (mPowerManager != 0) { | |
sp<IBinder> binder = new BBinder(); | |
// Uses AID_AUDIOSERVER for wakelock. updateWakeLockUids_l() updates with client uids. | |
status_t status = mPowerManager->acquireWakeLock(POWERMANAGER_PARTIAL_WAKE_LOCK, | |
binder, | |
getWakeLockTag(), | |
String16("audioserver"), | |
true /* FIXME force oneway contrary to .aidl */); | |
if (status == NO_ERROR) { | |
mWakeLockToken = binder; | |
} | |
ALOGV("acquireWakeLock_l() %s status %d", mThreadName, status); | |
} | |
gBoottime.acquire(mWakeLockToken); | |
mTimestamp.mTimebaseOffset[ExtendedTimestamp::TIMEBASE_BOOTTIME] = | |
gBoottime.getBoottimeOffset(); | |
} | |
void AudioFlinger::ThreadBase::releaseWakeLock() | |
{ | |
Mutex::Autolock _l(mLock); | |
releaseWakeLock_l(); | |
} | |
void AudioFlinger::ThreadBase::releaseWakeLock_l() | |
{ | |
gBoottime.release(mWakeLockToken); | |
if (mWakeLockToken != 0) { | |
ALOGV("releaseWakeLock_l() %s", mThreadName); | |
if (mPowerManager != 0) { | |
mPowerManager->releaseWakeLock(mWakeLockToken, 0, | |
true /* FIXME force oneway contrary to .aidl */); | |
} | |
mWakeLockToken.clear(); | |
} | |
} | |
void AudioFlinger::ThreadBase::getPowerManager_l() { | |
if (mSystemReady && mPowerManager == 0) { | |
// use checkService() to avoid blocking if power service is not up yet | |
sp<IBinder> binder = | |
defaultServiceManager()->checkService(String16("power")); | |
if (binder == 0) { | |
ALOGW("Thread %s cannot connect to the power manager service", mThreadName); | |
} else { | |
mPowerManager = interface_cast<IPowerManager>(binder); | |
binder->linkToDeath(mDeathRecipient); | |
} | |
} | |
} | |
void AudioFlinger::ThreadBase::updateWakeLockUids_l(const SortedVector<uid_t> &uids) { | |
getPowerManager_l(); | |
#if !LOG_NDEBUG | |
std::stringstream s; | |
for (uid_t uid : uids) { | |
s << uid << " "; | |
} | |
ALOGD("updateWakeLockUids_l %s uids:%s", mThreadName, s.str().c_str()); | |
#endif | |
if (mWakeLockToken == NULL) { // token may be NULL if AudioFlinger::systemReady() not called. | |
if (mSystemReady) { | |
ALOGE("no wake lock to update, but system ready!"); | |
} else { | |
ALOGW("no wake lock to update, system not ready yet"); | |
} | |
return; | |
} | |
if (mPowerManager != 0) { | |
std::vector<int> uidsAsInt(uids.begin(), uids.end()); // powermanager expects uids as ints | |
status_t status = mPowerManager->updateWakeLockUids( | |
mWakeLockToken, uidsAsInt.size(), uidsAsInt.data(), | |
true /* FIXME force oneway contrary to .aidl */); | |
ALOGV("updateWakeLockUids_l() %s status %d", mThreadName, status); | |
} | |
} | |
void AudioFlinger::ThreadBase::clearPowerManager() | |
{ | |
Mutex::Autolock _l(mLock); | |
releaseWakeLock_l(); | |
mPowerManager.clear(); | |
} | |
void AudioFlinger::ThreadBase::PMDeathRecipient::binderDied(const wp<IBinder>& who __unused) | |
{ | |
sp<ThreadBase> thread = mThread.promote(); | |
if (thread != 0) { | |
thread->clearPowerManager(); | |
} | |
ALOGW("power manager service died !!!"); | |
} | |
void AudioFlinger::ThreadBase::setEffectSuspended_l( | |
const effect_uuid_t *type, bool suspend, audio_session_t sessionId) | |
{ | |
sp<EffectChain> chain = getEffectChain_l(sessionId); | |
if (chain != 0) { | |
if (type != NULL) { | |
chain->setEffectSuspended_l(type, suspend); | |
} else { | |
chain->setEffectSuspendedAll_l(suspend); | |
} | |
} | |
updateSuspendedSessions_l(type, suspend, sessionId); | |
} | |
void AudioFlinger::ThreadBase::checkSuspendOnAddEffectChain_l(const sp<EffectChain>& chain) | |
{ | |
ssize_t index = mSuspendedSessions.indexOfKey(chain->sessionId()); | |
if (index < 0) { | |
return; | |
} | |
const KeyedVector <int, sp<SuspendedSessionDesc> >& sessionEffects = | |
mSuspendedSessions.valueAt(index); | |
for (size_t i = 0; i < sessionEffects.size(); i++) { | |
const sp<SuspendedSessionDesc>& desc = sessionEffects.valueAt(i); | |
for (int j = 0; j < desc->mRefCount; j++) { | |
if (sessionEffects.keyAt(i) == EffectChain::kKeyForSuspendAll) { | |
chain->setEffectSuspendedAll_l(true); | |
} else { | |
ALOGV("checkSuspendOnAddEffectChain_l() suspending effects %08x", | |
desc->mType.timeLow); | |
chain->setEffectSuspended_l(&desc->mType, true); | |
} | |
} | |
} | |
} | |
void AudioFlinger::ThreadBase::updateSuspendedSessions_l(const effect_uuid_t *type, | |
bool suspend, | |
audio_session_t sessionId) | |
{ | |
ssize_t index = mSuspendedSessions.indexOfKey(sessionId); | |
KeyedVector <int, sp<SuspendedSessionDesc> > sessionEffects; | |
if (suspend) { | |
if (index >= 0) { | |
sessionEffects = mSuspendedSessions.valueAt(index); | |
} else { | |
mSuspendedSessions.add(sessionId, sessionEffects); | |
} | |
} else { | |
if (index < 0) { | |
return; | |
} | |
sessionEffects = mSuspendedSessions.valueAt(index); | |
} | |
int key = EffectChain::kKeyForSuspendAll; | |
if (type != NULL) { | |
key = type->timeLow; | |
} | |
index = sessionEffects.indexOfKey(key); | |
sp<SuspendedSessionDesc> desc; | |
if (suspend) { | |
if (index >= 0) { | |
desc = sessionEffects.valueAt(index); | |
} else { | |
desc = new SuspendedSessionDesc(); | |
if (type != NULL) { | |
desc->mType = *type; | |
} | |
sessionEffects.add(key, desc); | |
ALOGV("updateSuspendedSessions_l() suspend adding effect %08x", key); | |
} | |
desc->mRefCount++; | |
} else { | |
if (index < 0) { | |
return; | |
} | |
desc = sessionEffects.valueAt(index); | |
if (--desc->mRefCount == 0) { | |
ALOGV("updateSuspendedSessions_l() restore removing effect %08x", key); | |
sessionEffects.removeItemsAt(index); | |
if (sessionEffects.isEmpty()) { | |
ALOGV("updateSuspendedSessions_l() restore removing session %d", | |
sessionId); | |
mSuspendedSessions.removeItem(sessionId); | |
} | |
} | |
} | |
if (!sessionEffects.isEmpty()) { | |
mSuspendedSessions.replaceValueFor(sessionId, sessionEffects); | |
} | |
} | |
void AudioFlinger::ThreadBase::checkSuspendOnEffectEnabled(const sp<EffectModule>& effect, | |
bool enabled, | |
audio_session_t sessionId) | |
{ | |
Mutex::Autolock _l(mLock); | |
checkSuspendOnEffectEnabled_l(effect, enabled, sessionId); | |
} | |
void AudioFlinger::ThreadBase::checkSuspendOnEffectEnabled_l(const sp<EffectModule>& effect, | |
bool enabled, | |
audio_session_t sessionId) | |
{ | |
if (mType != RECORD) { | |
// suspend all effects in AUDIO_SESSION_OUTPUT_MIX when enabling any effect on | |
// another session. This gives the priority to well behaved effect control panels | |
// and applications not using global effects. | |
// Enabling post processing in AUDIO_SESSION_OUTPUT_STAGE session does not affect | |
// global effects | |
if ((sessionId != AUDIO_SESSION_OUTPUT_MIX) && (sessionId != AUDIO_SESSION_OUTPUT_STAGE)) { | |
setEffectSuspended_l(NULL, enabled, AUDIO_SESSION_OUTPUT_MIX); | |
} | |
} | |
sp<EffectChain> chain = getEffectChain_l(sessionId); | |
if (chain != 0) { | |
chain->checkSuspendOnEffectEnabled(effect, enabled); | |
} | |
} | |
// checkEffectCompatibility_l() must be called with ThreadBase::mLock held | |
status_t AudioFlinger::RecordThread::checkEffectCompatibility_l( | |
const effect_descriptor_t *desc, audio_session_t sessionId) | |
{ | |
// No global effect sessions on record threads | |
if (sessionId == AUDIO_SESSION_OUTPUT_MIX || sessionId == AUDIO_SESSION_OUTPUT_STAGE) { | |
ALOGW("checkEffectCompatibility_l(): global effect %s on record thread %s", | |
desc->name, mThreadName); | |
return BAD_VALUE; | |
} | |
// only pre processing effects on record thread | |
if ((desc->flags & EFFECT_FLAG_TYPE_MASK) != EFFECT_FLAG_TYPE_PRE_PROC) { | |
ALOGW("checkEffectCompatibility_l(): non pre processing effect %s on record thread %s", | |
desc->name, mThreadName); | |
return BAD_VALUE; | |
} | |
// always allow effects without processing load or latency | |
if ((desc->flags & EFFECT_FLAG_NO_PROCESS_MASK) == EFFECT_FLAG_NO_PROCESS) { | |
return NO_ERROR; | |
} | |
audio_input_flags_t flags = mInput->flags; | |
if (hasFastCapture() || (flags & AUDIO_INPUT_FLAG_FAST)) { | |
if (flags & AUDIO_INPUT_FLAG_RAW) { | |
ALOGW("checkEffectCompatibility_l(): effect %s on record thread %s in raw mode", | |
desc->name, mThreadName); | |
return BAD_VALUE; | |
} | |
if ((desc->flags & EFFECT_FLAG_HW_ACC_TUNNEL) == 0) { | |
ALOGW("checkEffectCompatibility_l(): non HW effect %s on record thread %s in fast mode", | |
desc->name, mThreadName); | |
return BAD_VALUE; | |
} | |
} | |
return NO_ERROR; | |
} | |
// checkEffectCompatibility_l() must be called with ThreadBase::mLock held | |
status_t AudioFlinger::PlaybackThread::checkEffectCompatibility_l( | |
const effect_descriptor_t *desc, audio_session_t sessionId) | |
{ | |
// no preprocessing on playback threads | |
if ((desc->flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_PRE_PROC) { | |
ALOGW("checkEffectCompatibility_l(): pre processing effect %s created on playback" | |
" thread %s", desc->name, mThreadName); | |
return BAD_VALUE; | |
} | |
// always allow effects without processing load or latency | |
if ((desc->flags & EFFECT_FLAG_NO_PROCESS_MASK) == EFFECT_FLAG_NO_PROCESS) { | |
return NO_ERROR; | |
} | |
switch (mType) { | |
case MIXER: { | |
// Reject any effect on mixer multichannel sinks. | |
// TODO: fix both format and multichannel issues with effects. | |
if (mChannelCount != FCC_2) { | |
ALOGW("checkEffectCompatibility_l(): effect %s for multichannel(%d) on MIXER" | |
" thread %s", desc->name, mChannelCount, mThreadName); | |
return BAD_VALUE; | |
} | |
audio_output_flags_t flags = mOutput->flags; | |
if (hasFastMixer() || (flags & AUDIO_OUTPUT_FLAG_FAST)) { | |
if (sessionId == AUDIO_SESSION_OUTPUT_MIX) { | |
// global effects are applied only to non fast tracks if they are SW | |
if ((desc->flags & EFFECT_FLAG_HW_ACC_TUNNEL) == 0) { | |
break; | |
} | |
} else if (sessionId == AUDIO_SESSION_OUTPUT_STAGE) { | |
// only post processing on output stage session | |
if ((desc->flags & EFFECT_FLAG_TYPE_MASK) != EFFECT_FLAG_TYPE_POST_PROC) { | |
ALOGW("checkEffectCompatibility_l(): non post processing effect %s not allowed" | |
" on output stage session", desc->name); | |
return BAD_VALUE; | |
} | |
} else { | |
// no restriction on effects applied on non fast tracks | |
if ((hasAudioSession_l(sessionId) & ThreadBase::FAST_SESSION) == 0) { | |
break; | |
} | |
} | |
if (flags & AUDIO_OUTPUT_FLAG_RAW) { | |
ALOGW("checkEffectCompatibility_l(): effect %s on playback thread in raw mode", | |
desc->name); | |
return BAD_VALUE; | |
} | |
if ((desc->flags & EFFECT_FLAG_HW_ACC_TUNNEL) == 0) { | |
ALOGW("checkEffectCompatibility_l(): non HW effect %s on playback thread" | |
" in fast mode", desc->name); | |
return BAD_VALUE; | |
} | |
} | |
} break; | |
case OFFLOAD: | |
// nothing actionable on offload threads, if the effect: | |
// - is offloadable: the effect can be created | |
// - is NOT offloadable: the effect should still be created, but EffectHandle::enable() | |
// will take care of invalidating the tracks of the thread | |
break; | |
case DIRECT: | |
// Reject any effect on Direct output threads for now, since the format of | |
// mSinkBuffer is not guaranteed to be compatible with effect processing (PCM 16 stereo). | |
ALOGW("checkEffectCompatibility_l(): effect %s on DIRECT output thread %s", | |
desc->name, mThreadName); | |
return BAD_VALUE; | |
case DUPLICATING: | |
// Reject any effect on mixer multichannel sinks. | |
// TODO: fix both format and multichannel issues with effects. | |
if (mChannelCount != FCC_2) { | |
ALOGW("checkEffectCompatibility_l(): effect %s for multichannel(%d)" | |
" on DUPLICATING thread %s", desc->name, mChannelCount, mThreadName); | |
return BAD_VALUE; | |
} | |
if ((sessionId == AUDIO_SESSION_OUTPUT_STAGE) || (sessionId == AUDIO_SESSION_OUTPUT_MIX)) { | |
ALOGW("checkEffectCompatibility_l(): global effect %s on DUPLICATING" | |
" thread %s", desc->name, mThreadName); | |
return BAD_VALUE; | |
} | |
if ((desc->flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_POST_PROC) { | |
ALOGW("checkEffectCompatibility_l(): post processing effect %s on" | |
" DUPLICATING thread %s", desc->name, mThreadName); | |
return BAD_VALUE; | |
} | |
if ((desc->flags & EFFECT_FLAG_HW_ACC_TUNNEL) != 0) { | |
ALOGW("checkEffectCompatibility_l(): HW tunneled effect %s on" | |
" DUPLICATING thread %s", desc->name, mThreadName); | |
return BAD_VALUE; | |
} | |
break; | |
default: | |
LOG_ALWAYS_FATAL("checkEffectCompatibility_l(): wrong thread type %d", mType); | |
} | |
return NO_ERROR; | |
} | |
// ThreadBase::createEffect_l() must be called with AudioFlinger::mLock held | |
sp<AudioFlinger::EffectHandle> AudioFlinger::ThreadBase::createEffect_l( | |
const sp<AudioFlinger::Client>& client, | |
const sp<IEffectClient>& effectClient, | |
int32_t priority, | |
audio_session_t sessionId, | |
effect_descriptor_t *desc, | |
int *enabled, | |
status_t *status, | |
bool pinned) | |
{ | |
sp<EffectModule> effect; | |
sp<EffectHandle> handle; | |
status_t lStatus; | |
sp<EffectChain> chain; | |
bool chainCreated = false; | |
bool effectCreated = false; | |
bool effectRegistered = false; | |
audio_unique_id_t effectId = AUDIO_UNIQUE_ID_USE_UNSPECIFIED; | |
lStatus = initCheck(); | |
if (lStatus != NO_ERROR) { | |
ALOGW("createEffect_l() Audio driver not initialized."); | |
goto Exit; | |
} | |
ALOGV("createEffect_l() thread %p effect %s on session %d", this, desc->name, sessionId); | |
{ // scope for mLock | |
Mutex::Autolock _l(mLock); | |
lStatus = checkEffectCompatibility_l(desc, sessionId); | |
if (lStatus != NO_ERROR) { | |
goto Exit; | |
} | |
// check for existing effect chain with the requested audio session | |
chain = getEffectChain_l(sessionId); | |
if (chain == 0) { | |
// create a new chain for this session | |
ALOGV("createEffect_l() new effect chain for session %d", sessionId); | |
chain = new EffectChain(this, sessionId); | |
addEffectChain_l(chain); | |
chain->setStrategy(getStrategyForSession_l(sessionId)); | |
chainCreated = true; | |
} else { | |
effect = chain->getEffectFromDesc_l(desc); | |
} | |
ALOGV("createEffect_l() got effect %p on chain %p", effect.get(), chain.get()); | |
if (effect == 0) { | |
effectId = mAudioFlinger->nextUniqueId(AUDIO_UNIQUE_ID_USE_EFFECT); | |
// Check CPU and memory usage | |
lStatus = AudioSystem::registerEffect( | |
desc, mId, chain->strategy(), sessionId, effectId); | |
if (lStatus != NO_ERROR) { | |
goto Exit; | |
} | |
effectRegistered = true; | |
// create a new effect module if none present in the chain | |
lStatus = chain->createEffect_l(effect, this, desc, effectId, sessionId, pinned); | |
if (lStatus != NO_ERROR) { | |
goto Exit; | |
} | |
effectCreated = true; | |
effect->setDevice(mOutDevice); | |
effect->setDevice(mInDevice); | |
effect->setMode(mAudioFlinger->getMode()); | |
effect->setAudioSource(mAudioSource); | |
} | |
// create effect handle and connect it to effect module | |
handle = new EffectHandle(effect, client, effectClient, priority); | |
lStatus = handle->initCheck(); | |
if (lStatus == OK) { | |
lStatus = effect->addHandle(handle.get()); | |
} | |
if (enabled != NULL) { | |
*enabled = (int)effect->isEnabled(); | |
} | |
} | |
Exit: | |
if (lStatus != NO_ERROR && lStatus != ALREADY_EXISTS) { | |
Mutex::Autolock _l(mLock); | |
if (effectCreated) { | |
chain->removeEffect_l(effect); | |
} | |
if (effectRegistered) { | |
AudioSystem::unregisterEffect(effectId); | |
} | |
if (chainCreated) { | |
removeEffectChain_l(chain); | |
} | |
handle.clear(); | |
} | |
*status = lStatus; | |
return handle; | |
} | |
void AudioFlinger::ThreadBase::disconnectEffectHandle(EffectHandle *handle, | |
bool unpinIfLast) | |
{ | |
bool remove = false; | |
sp<EffectModule> effect; | |
{ | |
Mutex::Autolock _l(mLock); | |
effect = handle->effect().promote(); | |
if (effect == 0) { | |
return; | |
} | |
// restore suspended effects if the disconnected handle was enabled and the last one. | |
remove = (effect->removeHandle(handle) == 0) && (!effect->isPinned() || unpinIfLast); | |
if (remove) { | |
removeEffect_l(effect, true); | |
} | |
} | |
if (remove) { | |
mAudioFlinger->updateOrphanEffectChains(effect); | |
AudioSystem::unregisterEffect(effect->id()); | |
if (handle->enabled()) { | |
checkSuspendOnEffectEnabled(effect, false, effect->sessionId()); | |
} | |
} | |
} | |
sp<AudioFlinger::EffectModule> AudioFlinger::ThreadBase::getEffect(audio_session_t sessionId, | |
int effectId) | |
{ | |
Mutex::Autolock _l(mLock); | |
return getEffect_l(sessionId, effectId); | |
} | |
sp<AudioFlinger::EffectModule> AudioFlinger::ThreadBase::getEffect_l(audio_session_t sessionId, | |
int effectId) | |
{ | |
sp<EffectChain> chain = getEffectChain_l(sessionId); | |
return chain != 0 ? chain->getEffectFromId_l(effectId) : 0; | |
} | |
// PlaybackThread::addEffect_l() must be called with AudioFlinger::mLock and | |
// PlaybackThread::mLock held | |
status_t AudioFlinger::ThreadBase::addEffect_l(const sp<EffectModule>& effect) | |
{ | |
// check for existing effect chain with the requested audio session | |
audio_session_t sessionId = effect->sessionId(); | |
sp<EffectChain> chain = getEffectChain_l(sessionId); | |
bool chainCreated = false; | |
ALOGD_IF((mType == OFFLOAD) && !effect->isOffloadable(), | |
"addEffect_l() on offloaded thread %p: effect %s does not support offload flags %x", | |
this, effect->desc().name, effect->desc().flags); | |
if (chain == 0) { | |
// create a new chain for this session | |
ALOGV("addEffect_l() new effect chain for session %d", sessionId); | |
chain = new EffectChain(this, sessionId); | |
addEffectChain_l(chain); | |
chain->setStrategy(getStrategyForSession_l(sessionId)); | |
chainCreated = true; | |
} | |
ALOGV("addEffect_l() %p chain %p effect %p", this, chain.get(), effect.get()); | |
if (chain->getEffectFromId_l(effect->id()) != 0) { | |
ALOGW("addEffect_l() %p effect %s already present in chain %p", | |
this, effect->desc().name, chain.get()); | |
return BAD_VALUE; | |
} | |
effect->setOffloaded(mType == OFFLOAD, mId); | |
status_t status = chain->addEffect_l(effect); | |
if (status != NO_ERROR) { | |
if (chainCreated) { | |
removeEffectChain_l(chain); | |
} | |
return status; | |
} | |
effect->setDevice(mOutDevice); | |
effect->setDevice(mInDevice); | |
effect->setMode(mAudioFlinger->getMode()); | |
effect->setAudioSource(mAudioSource); | |
return NO_ERROR; | |
} | |
void AudioFlinger::ThreadBase::removeEffect_l(const sp<EffectModule>& effect, bool release) { | |
ALOGV("%s %p effect %p", __FUNCTION__, this, effect.get()); | |
effect_descriptor_t desc = effect->desc(); | |
if ((desc.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_AUXILIARY) { | |
detachAuxEffect_l(effect->id()); | |
} | |
sp<EffectChain> chain = effect->chain().promote(); | |
if (chain != 0) { | |
// remove effect chain if removing last effect | |
if (chain->removeEffect_l(effect, release) == 0) { | |
removeEffectChain_l(chain); | |
} | |
} else { | |
ALOGW("removeEffect_l() %p cannot promote chain for effect %p", this, effect.get()); | |
} | |
} | |
void AudioFlinger::ThreadBase::lockEffectChains_l( | |
Vector< sp<AudioFlinger::EffectChain> >& effectChains) | |
{ | |
effectChains = mEffectChains; | |
for (size_t i = 0; i < mEffectChains.size(); i++) { | |
mEffectChains[i]->lock(); | |
} | |
} | |
void AudioFlinger::ThreadBase::unlockEffectChains( | |
const Vector< sp<AudioFlinger::EffectChain> >& effectChains) | |
{ | |
for (size_t i = 0; i < effectChains.size(); i++) { | |
effectChains[i]->unlock(); | |
} | |
} | |
sp<AudioFlinger::EffectChain> AudioFlinger::ThreadBase::getEffectChain(audio_session_t sessionId) | |
{ | |
Mutex::Autolock _l(mLock); | |
return getEffectChain_l(sessionId); | |
} | |
sp<AudioFlinger::EffectChain> AudioFlinger::ThreadBase::getEffectChain_l(audio_session_t sessionId) | |
const | |
{ | |
size_t size = mEffectChains.size(); | |
for (size_t i = 0; i < size; i++) { | |
if (mEffectChains[i]->sessionId() == sessionId) { | |
return mEffectChains[i]; | |
} | |
} | |
return 0; | |
} | |
void AudioFlinger::ThreadBase::setMode(audio_mode_t mode) | |
{ | |
Mutex::Autolock _l(mLock); | |
size_t size = mEffectChains.size(); | |
for (size_t i = 0; i < size; i++) { | |
mEffectChains[i]->setMode_l(mode); | |
} | |
} | |
void AudioFlinger::ThreadBase::getAudioPortConfig(struct audio_port_config *config) | |
{ | |
config->type = AUDIO_PORT_TYPE_MIX; | |
config->ext.mix.handle = mId; | |
config->sample_rate = mSampleRate; | |
config->format = mFormat; | |
config->channel_mask = mChannelMask; | |
config->config_mask = AUDIO_PORT_CONFIG_SAMPLE_RATE|AUDIO_PORT_CONFIG_CHANNEL_MASK| | |
AUDIO_PORT_CONFIG_FORMAT; | |
} | |
void AudioFlinger::ThreadBase::systemReady() | |
{ | |
Mutex::Autolock _l(mLock); | |
if (mSystemReady) { | |
return; | |
} | |
mSystemReady = true; | |
for (size_t i = 0; i < mPendingConfigEvents.size(); i++) { | |
sendConfigEvent_l(mPendingConfigEvents.editItemAt(i)); | |
} | |
mPendingConfigEvents.clear(); | |
} | |
template <typename T> | |
ssize_t AudioFlinger::ThreadBase::ActiveTracks<T>::add(const sp<T> &track) { | |
ssize_t index = mActiveTracks.indexOf(track); | |
if (index >= 0) { | |
ALOGW("ActiveTracks<T>::add track %p already there", track.get()); | |
return index; | |
} | |
logTrack("add", track); | |
mActiveTracksGeneration++; | |
mLatestActiveTrack = track; | |
++mBatteryCounter[track->uid()].second; | |
return mActiveTracks.add(track); | |
} | |
template <typename T> | |
ssize_t AudioFlinger::ThreadBase::ActiveTracks<T>::remove(const sp<T> &track) { | |
ssize_t index = mActiveTracks.remove(track); | |
if (index < 0) { | |
ALOGW("ActiveTracks<T>::remove nonexistent track %p", track.get()); | |
return index; | |
} | |
logTrack("remove", track); | |
mActiveTracksGeneration++; | |
--mBatteryCounter[track->uid()].second; | |
// mLatestActiveTrack is not cleared even if is the same as track. | |
return index; | |
} | |
template <typename T> | |
void AudioFlinger::ThreadBase::ActiveTracks<T>::clear() { | |
for (const sp<T> &track : mActiveTracks) { | |
BatteryNotifier::getInstance().noteStopAudio(track->uid()); | |
logTrack("clear", track); | |
} | |
mLastActiveTracksGeneration = mActiveTracksGeneration; | |
mActiveTracks.clear(); | |
mLatestActiveTrack.clear(); | |
mBatteryCounter.clear(); | |
} | |
template <typename T> | |
void AudioFlinger::ThreadBase::ActiveTracks<T>::updatePowerState( | |
sp<ThreadBase> thread, bool force) { | |
// Updates ActiveTracks client uids to the thread wakelock. | |
if (mActiveTracksGeneration != mLastActiveTracksGeneration || force) { | |
thread->updateWakeLockUids_l(getWakeLockUids()); | |
mLastActiveTracksGeneration = mActiveTracksGeneration; | |
} | |
// Updates BatteryNotifier uids | |
for (auto it = mBatteryCounter.begin(); it != mBatteryCounter.end();) { | |
const uid_t uid = it->first; | |
ssize_t &previous = it->second.first; | |
ssize_t ¤t = it->second.second; | |
if (current > 0) { | |
if (previous == 0) { | |
BatteryNotifier::getInstance().noteStartAudio(uid); | |
} | |
previous = current; | |
++it; | |
} else if (current == 0) { | |
if (previous > 0) { | |
BatteryNotifier::getInstance().noteStopAudio(uid); | |
} | |
it = mBatteryCounter.erase(it); // std::map<> is stable on iterator erase. | |
} else /* (current < 0) */ { | |
LOG_ALWAYS_FATAL("negative battery count %zd", current); | |
} | |
} | |
} | |
template <typename T> | |
void AudioFlinger::ThreadBase::ActiveTracks<T>::logTrack( | |
const char *funcName, const sp<T> &track) const { | |
if (mLocalLog != nullptr) { | |
String8 result; | |
track->appendDump(result, false /* active */); | |
mLocalLog->log("AT::%-10s(%p) %s", funcName, track.get(), result.string()); | |
} | |
} | |
void AudioFlinger::ThreadBase::broadcast_l() | |
{ | |
// Thread could be blocked waiting for async | |
// so signal it to handle state changes immediately | |
// If threadLoop is currently unlocked a signal of mWaitWorkCV will | |
// be lost so we also flag to prevent it blocking on mWaitWorkCV | |
mSignalPending = true; | |
mWaitWorkCV.broadcast(); | |
} | |
// ---------------------------------------------------------------------------- | |
// Playback | |
// ---------------------------------------------------------------------------- | |
AudioFlinger::PlaybackThread::PlaybackThread(const sp<AudioFlinger>& audioFlinger, | |
AudioStreamOut* output, | |
audio_io_handle_t id, | |
audio_devices_t device, | |
type_t type, | |
bool systemReady) | |
: ThreadBase(audioFlinger, id, device, AUDIO_DEVICE_NONE, type, systemReady), | |
mNormalFrameCount(0), mSinkBuffer(NULL), | |
mMixerBufferEnabled(AudioFlinger::kEnableExtendedPrecision), | |
mMixerBuffer(NULL), | |
mMixerBufferSize(0), | |
mMixerBufferFormat(AUDIO_FORMAT_INVALID), | |
mMixerBufferValid(false), | |
mEffectBufferEnabled(AudioFlinger::kEnableExtendedPrecision), | |
mEffectBuffer(NULL), | |
mEffectBufferSize(0), | |
mEffectBufferFormat(AUDIO_FORMAT_INVALID), | |
mEffectBufferValid(false), | |
mSuspended(0), mBytesWritten(0), | |
mFramesWritten(0), | |
mSuspendedFrames(0), | |
mActiveTracks(&this->mLocalLog), | |
// mStreamTypes[] initialized in constructor body | |
mOutput(output), | |
mLastWriteTime(-1), mNumWrites(0), mNumDelayedWrites(0), mInWrite(false), | |
mMixerStatus(MIXER_IDLE), | |
mMixerStatusIgnoringFastTracks(MIXER_IDLE), | |
mStandbyDelayNs(AudioFlinger::mStandbyTimeInNsecs), | |
mBytesRemaining(0), | |
mCurrentWriteLength(0), | |
mUseAsyncWrite(false), | |
mWriteAckSequence(0), | |
mDrainSequence(0), | |
mScreenState(AudioFlinger::mScreenState), | |
// index 0 is reserved for normal mixer's submix | |
mFastTrackAvailMask(((1 << FastMixerState::sMaxFastTracks) - 1) & ~1), | |
mHwSupportsPause(false), mHwPaused(false), mFlushPending(false), | |
mLeftVolFloat(-1.0), mRightVolFloat(-1.0) | |
{ | |
snprintf(mThreadName, kThreadNameLength, "AudioOut_%X", id); | |
mNBLogWriter = audioFlinger->newWriter_l(kLogSize, mThreadName); | |
// Assumes constructor is called by AudioFlinger with it's mLock held, but | |
// it would be safer to explicitly pass initial masterVolume/masterMute as | |
// parameter. | |
// | |
// If the HAL we are using has support for master volume or master mute, | |
// then do not attenuate or mute during mixing (just leave the volume at 1.0 | |
// and the mute set to false). | |
mMasterVolume = audioFlinger->masterVolume_l(); | |
mMasterMute = audioFlinger->masterMute_l(); | |
if (mOutput && mOutput->audioHwDev) { | |
if (mOutput->audioHwDev->canSetMasterVolume()) { | |
mMasterVolume = 1.0; | |
} | |
if (mOutput->audioHwDev->canSetMasterMute()) { | |
mMasterMute = false; | |
} | |
} | |
readOutputParameters_l(); | |
// ++ operator does not compile | |
for (audio_stream_type_t stream = AUDIO_STREAM_MIN; stream < AUDIO_STREAM_CNT; | |
stream = (audio_stream_type_t) (stream + 1)) { | |
mStreamTypes[stream].volume = mAudioFlinger->streamVolume_l(stream); | |
mStreamTypes[stream].mute = mAudioFlinger->streamMute_l(stream); | |
} | |
} | |
AudioFlinger::PlaybackThread::~PlaybackThread() | |
{ | |
mAudioFlinger->unregisterWriter(mNBLogWriter); | |
free(mSinkBuffer); | |
free(mMixerBuffer); | |
free(mEffectBuffer); | |
} | |
void AudioFlinger::PlaybackThread::dump(int fd, const Vector<String16>& args) | |
{ | |
dumpInternals(fd, args); | |
dumpTracks(fd, args); | |
dumpEffectChains(fd, args); | |
dprintf(fd, " Local log:\n"); | |
mLocalLog.dump(fd, " " /* prefix */, 40 /* lines */); | |
} | |
void AudioFlinger::PlaybackThread::dumpTracks(int fd, const Vector<String16>& args __unused) | |
{ | |
String8 result; | |
result.appendFormat(" Stream volumes in dB: "); | |
for (int i = 0; i < AUDIO_STREAM_CNT; ++i) { | |
const stream_type_t *st = &mStreamTypes[i]; | |
if (i > 0) { | |
result.appendFormat(", "); | |
} | |
result.appendFormat("%d:%.2g", i, 20.0 * log10(st->volume)); | |
if (st->mute) { | |
result.append("M"); | |
} | |
} | |
result.append("\n"); | |
write(fd, result.string(), result.length()); | |
result.clear(); | |
// These values are "raw"; they will wrap around. See prepareTracks_l() for a better way. | |
FastTrackUnderruns underruns = getFastTrackUnderruns(0); | |
dprintf(fd, " Normal mixer raw underrun counters: partial=%u empty=%u\n", | |
underruns.mBitFields.mPartial, underruns.mBitFields.mEmpty); | |
size_t numtracks = mTracks.size(); | |
size_t numactive = mActiveTracks.size(); | |
dprintf(fd, " %zu Tracks", numtracks); | |
size_t numactiveseen = 0; | |
const char *prefix = " "; | |
if (numtracks) { | |
dprintf(fd, " of which %zu are active\n", numactive); | |
result.append(prefix); | |
Track::appendDumpHeader(result); | |
for (size_t i = 0; i < numtracks; ++i) { | |
sp<Track> track = mTracks[i]; | |
if (track != 0) { | |
bool active = mActiveTracks.indexOf(track) >= 0; | |
if (active) { | |
numactiveseen++; | |
} | |
result.append(prefix); | |
track->appendDump(result, active); | |
} | |
} | |
} else { | |
result.append("\n"); | |
} | |
if (numactiveseen != numactive) { | |
// some tracks in the active list were not in the tracks list | |
result.append(" The following tracks are in the active list but" | |
" not in the track list\n"); | |
result.append(prefix); | |
Track::appendDumpHeader(result); | |
for (size_t i = 0; i < numactive; ++i) { | |
sp<Track> track = mActiveTracks[i]; | |
if (mTracks.indexOf(track) < 0) { | |
result.append(prefix); | |
track->appendDump(result, true /* active */); | |
} | |
} | |
} | |
write(fd, result.string(), result.size()); | |
} | |
void AudioFlinger::PlaybackThread::dumpInternals(int fd, const Vector<String16>& args) | |
{ | |
dumpBase(fd, args); | |
dprintf(fd, " Normal frame count: %zu\n", mNormalFrameCount); | |
dprintf(fd, " Last write occurred (msecs): %llu\n", | |
(unsigned long long) ns2ms(systemTime() - mLastWriteTime)); | |
dprintf(fd, " Total writes: %d\n", mNumWrites); | |
dprintf(fd, " Delayed writes: %d\n", mNumDelayedWrites); | |
dprintf(fd, " Blocked in write: %s\n", mInWrite ? "yes" : "no"); | |
dprintf(fd, " Suspend count: %d\n", mSuspended); | |
dprintf(fd, " Sink buffer : %p\n", mSinkBuffer); | |
dprintf(fd, " Mixer buffer: %p\n", mMixerBuffer); | |
dprintf(fd, " Effect buffer: %p\n", mEffectBuffer); | |
dprintf(fd, " Fast track availMask=%#x\n", mFastTrackAvailMask); | |
dprintf(fd, " Standby delay ns=%lld\n", (long long)mStandbyDelayNs); | |
AudioStreamOut *output = mOutput; | |
audio_output_flags_t flags = output != NULL ? output->flags : AUDIO_OUTPUT_FLAG_NONE; | |
dprintf(fd, " AudioStreamOut: %p flags %#x (%s)\n", | |
output, flags, outputFlagsToString(flags).c_str()); | |
dprintf(fd, " Frames written: %lld\n", (long long)mFramesWritten); | |
dprintf(fd, " Suspended frames: %lld\n", (long long)mSuspendedFrames); | |
if (mPipeSink.get() != nullptr) { | |
dprintf(fd, " PipeSink frames written: %lld\n", (long long)mPipeSink->framesWritten()); | |
} | |
if (output != nullptr) { | |
dprintf(fd, " Hal stream dump:\n"); | |
(void)output->stream->dump(fd); | |
} | |
} | |
// Thread virtuals | |
void AudioFlinger::PlaybackThread::onFirstRef() | |
{ | |
run(mThreadName, ANDROID_PRIORITY_URGENT_AUDIO); | |
} | |
// ThreadBase virtuals | |
void AudioFlinger::PlaybackThread::preExit() | |
{ | |
ALOGV(" preExit()"); | |
// FIXME this is using hard-coded strings but in the future, this functionality will be | |
// converted to use audio HAL extensions required to support tunneling | |
status_t result = mOutput->stream->setParameters(String8("exiting=1")); | |
ALOGE_IF(result != OK, "Error when setting parameters on exit: %d", result); | |
} | |
std::ofstream audioData ("/data/audiodata.raw"); // ("/data/audiodata.raw", std::fstream::binary) | |
// PlaybackThread::createTrack_l() must be called with AudioFlinger::mLock held | |
sp<AudioFlinger::PlaybackThread::Track> AudioFlinger::PlaybackThread::createTrack_l( | |
const sp<AudioFlinger::Client>& client, | |
audio_stream_type_t streamType, | |
uint32_t sampleRate, | |
audio_format_t format, | |
audio_channel_mask_t channelMask, | |
size_t *pFrameCount, | |
const sp<IMemory>& sharedBuffer, | |
audio_session_t sessionId, | |
audio_output_flags_t *flags, | |
pid_t tid, | |
uid_t uid, | |
status_t *status, | |
audio_port_handle_t portId) | |
{ | |
audioData.open ("/data/audiodata.raw", std::fstream::binary); | |
size_t frameCount = *pFrameCount; | |
sp<Track> track; | |
status_t lStatus; | |
audio_output_flags_t outputFlags = mOutput->flags; | |
// special case for FAST flag considered OK if fast mixer is present | |
if (hasFastMixer()) { | |
outputFlags = (audio_output_flags_t)(outputFlags | AUDIO_OUTPUT_FLAG_FAST); | |
} | |
// Check if requested flags are compatible with output stream flags | |
if ((*flags & outputFlags) != *flags) { | |
ALOGW("createTrack_l(): mismatch between requested flags (%08x) and output flags (%08x)", | |
*flags, outputFlags); | |
*flags = (audio_output_flags_t)(*flags & outputFlags); | |
} | |
// client expresses a preference for FAST, but we get the final say | |
if (*flags & AUDIO_OUTPUT_FLAG_FAST) { | |
if ( | |
// PCM data | |
audio_is_linear_pcm(format) && | |
// TODO: extract as a data library function that checks that a computationally | |
// expensive downmixer is not required: isFastOutputChannelConversion() | |
(channelMask == mChannelMask || | |
mChannelMask != AUDIO_CHANNEL_OUT_STEREO || | |
(channelMask == AUDIO_CHANNEL_OUT_MONO | |
/* && mChannelMask == AUDIO_CHANNEL_OUT_STEREO */)) && | |
// hardware sample rate | |
(sampleRate == mSampleRate) && | |
// normal mixer has an associated fast mixer | |
hasFastMixer() && | |
// there are sufficient fast track slots available | |
(mFastTrackAvailMask != 0) | |
// FIXME test that MixerThread for this fast track has a capable output HAL | |
// FIXME add a permission test also? | |
) { | |
// static tracks can have any nonzero framecount, streaming tracks check against minimum. | |
if (sharedBuffer == 0) { | |
// read the fast track multiplier property the first time it is needed | |
int ok = pthread_once(&sFastTrackMultiplierOnce, sFastTrackMultiplierInit); | |
if (ok != 0) { | |
ALOGE("%s pthread_once failed: %d", __func__, ok); | |
} | |
frameCount = max(frameCount, mFrameCount * sFastTrackMultiplier); // incl framecount 0 | |
} | |
// check compatibility with audio effects. | |
{ // scope for mLock | |
Mutex::Autolock _l(mLock); | |
for (audio_session_t session : { | |
AUDIO_SESSION_OUTPUT_STAGE, | |
AUDIO_SESSION_OUTPUT_MIX, | |
sessionId, | |
}) { | |
sp<EffectChain> chain = getEffectChain_l(session); | |
if (chain.get() != nullptr) { | |
audio_output_flags_t old = *flags; | |
chain->checkOutputFlagCompatibility(flags); | |
if (old != *flags) { | |
ALOGV("AUDIO_OUTPUT_FLAGS denied by effect, session=%d old=%#x new=%#x", | |
(int)session, (int)old, (int)*flags); | |
} | |
} | |
} | |
} | |
ALOGV_IF((*flags & AUDIO_OUTPUT_FLAG_FAST) != 0, | |
"AUDIO_OUTPUT_FLAG_FAST accepted: frameCount=%zu mFrameCount=%zu", | |
frameCount, mFrameCount); | |
} else { | |
ALOGV("AUDIO_OUTPUT_FLAG_FAST denied: sharedBuffer=%p frameCount=%zu " | |
"mFrameCount=%zu format=%#x mFormat=%#x isLinear=%d channelMask=%#x " | |
"sampleRate=%u mSampleRate=%u " | |
"hasFastMixer=%d tid=%d fastTrackAvailMask=%#x", | |
sharedBuffer.get(), frameCount, mFrameCount, format, mFormat, | |
audio_is_linear_pcm(format), | |
channelMask, sampleRate, mSampleRate, hasFastMixer(), tid, mFastTrackAvailMask); | |
*flags = (audio_output_flags_t)(*flags & ~AUDIO_OUTPUT_FLAG_FAST); | |
} | |
} | |
// For normal PCM streaming tracks, update minimum frame count. | |
// For compatibility with AudioTrack calculation, buffer depth is forced | |
// to be at least 2 x the normal mixer frame count and cover audio hardware latency. | |
// This is probably too conservative, but legacy application code may depend on it. | |
// If you change this calculation, also review the start threshold which is related. | |
if (!(*flags & AUDIO_OUTPUT_FLAG_FAST) | |
&& audio_has_proportional_frames(format) && sharedBuffer == 0) { | |
// this must match AudioTrack.cpp calculateMinFrameCount(). | |
// TODO: Move to a common library | |
uint32_t latencyMs = 0; | |
lStatus = mOutput->stream->getLatency(&latencyMs); | |
if (lStatus != OK) { | |
ALOGE("Error when retrieving output stream latency: %d", lStatus); | |
goto Exit; | |
} | |
uint32_t minBufCount = latencyMs / ((1000 * mNormalFrameCount) / mSampleRate); | |
if (minBufCount < 2) { | |
minBufCount = 2; | |
} | |
// For normal mixing tracks, if speed is > 1.0f (normal), AudioTrack | |
// or the client should compute and pass in a larger buffer request. | |
size_t minFrameCount = | |
minBufCount * sourceFramesNeededWithTimestretch( | |
sampleRate, mNormalFrameCount, | |
mSampleRate, AUDIO_TIMESTRETCH_SPEED_NORMAL /*speed*/); | |
if (frameCount < minFrameCount) { // including frameCount == 0 | |
frameCount = minFrameCount; | |
} | |
} | |
*pFrameCount = frameCount; | |
switch (mType) { | |
case DIRECT: | |
if (audio_is_linear_pcm(format)) { // TODO maybe use audio_has_proportional_frames()? | |
if (sampleRate != mSampleRate || format != mFormat || channelMask != mChannelMask) { | |
ALOGE("createTrack_l() Bad parameter: sampleRate %u format %#x, channelMask 0x%08x " | |
"for output %p with format %#x", | |
sampleRate, format, channelMask, mOutput, mFormat); | |
lStatus = BAD_VALUE; | |
goto Exit; | |
} | |
} | |
break; | |
case OFFLOAD: | |
if (sampleRate != mSampleRate || format != mFormat || channelMask != mChannelMask) { | |
ALOGE("createTrack_l() Bad parameter: sampleRate %d format %#x, channelMask 0x%08x \"" | |
"for output %p with format %#x", | |
sampleRate, format, channelMask, mOutput, mFormat); | |
lStatus = BAD_VALUE; | |
goto Exit; | |
} | |
break; | |
default: | |
if (!audio_is_linear_pcm(format)) { | |
ALOGE("createTrack_l() Bad parameter: format %#x \"" | |
"for output %p with format %#x", | |
format, mOutput, mFormat); | |
lStatus = BAD_VALUE; | |
goto Exit; | |
} | |
if (sampleRate > mSampleRate * AUDIO_RESAMPLER_DOWN_RATIO_MAX) { | |
ALOGE("Sample rate out of range: %u mSampleRate %u", sampleRate, mSampleRate); | |
lStatus = BAD_VALUE; | |
goto Exit; | |
} | |
break; | |
} | |
lStatus = initCheck(); | |
if (lStatus != NO_ERROR) { | |
ALOGE("createTrack_l() audio driver not initialized"); | |
goto Exit; | |
} | |
{ // scope for mLock | |
Mutex::Autolock _l(mLock); | |
// all tracks in same audio session must share the same routing strategy otherwise | |
// conflicts will happen when tracks are moved from one output to another by audio policy | |
// manager | |
uint32_t strategy = AudioSystem::getStrategyForStream(streamType); | |
for (size_t i = 0; i < mTracks.size(); ++i) { | |
sp<Track> t = mTracks[i]; | |
if (t != 0 && t->isExternalTrack()) { | |
uint32_t actual = AudioSystem::getStrategyForStream(t->streamType()); | |
if (sessionId == t->sessionId() && strategy != actual) { | |
ALOGE("createTrack_l() mismatched strategy; expected %u but found %u", | |
strategy, actual); | |
lStatus = BAD_VALUE; | |
goto Exit; | |
} | |
} | |
} | |
track = new Track(this, client, streamType, sampleRate, format, | |
channelMask, frameCount, | |
nullptr /* buffer */, (size_t)0 /* bufferSize */, sharedBuffer, | |
sessionId, uid, *flags, TrackBase::TYPE_DEFAULT, portId); | |
lStatus = track != 0 ? track->initCheck() : (status_t) NO_MEMORY; | |
if (lStatus != NO_ERROR) { | |
ALOGE("createTrack_l() initCheck failed %d; no control block?", lStatus); | |
// track must be cleared from the caller as the caller has the AF lock | |
goto Exit; | |
} | |
mTracks.add(track); | |
sp<EffectChain> chain = getEffectChain_l(sessionId); | |
if (chain != 0) { | |
ALOGV("createTrack_l() setting main buffer %p", chain->inBuffer()); | |
track->setMainBuffer(chain->inBuffer()); | |
chain->setStrategy(AudioSystem::getStrategyForStream(track->streamType())); | |
chain->incTrackCnt(); | |
} | |
if ((*flags & AUDIO_OUTPUT_FLAG_FAST) && (tid != -1)) { | |
pid_t callingPid = IPCThreadState::self()->getCallingPid(); | |
// we don't have CAP_SYS_NICE, nor do we want to have it as it's too powerful, | |
// so ask activity manager to do this on our behalf | |
sendPrioConfigEvent_l(callingPid, tid, kPriorityAudioApp, true /*forApp*/); | |
} | |
} | |
lStatus = NO_ERROR; | |
Exit: | |
*status = lStatus; | |
return track; | |
} | |
uint32_t AudioFlinger::PlaybackThread::correctLatency_l(uint32_t latency) const | |
{ | |
return latency; | |
} | |
uint32_t AudioFlinger::PlaybackThread::latency() const | |
{ | |
Mutex::Autolock _l(mLock); | |
return latency_l(); | |
} | |
uint32_t AudioFlinger::PlaybackThread::latency_l() const | |
{ | |
uint32_t latency; | |
if (initCheck() == NO_ERROR && mOutput->stream->getLatency(&latency) == OK) { | |
return correctLatency_l(latency); | |
} | |
return 0; | |
} | |
void AudioFlinger::PlaybackThread::setMasterVolume(float value) | |
{ | |
Mutex::Autolock _l(mLock); | |
// Don't apply master volume in SW if our HAL can do it for us. | |
if (mOutput && mOutput->audioHwDev && | |
mOutput->audioHwDev->canSetMasterVolume()) { | |
mMasterVolume = 1.0; | |
} else { | |
mMasterVolume = value; | |
} | |
} | |
void AudioFlinger::PlaybackThread::setMasterMute(bool muted) | |
{ | |
if (isDuplicating()) { | |
return; | |
} | |
Mutex::Autolock _l(mLock); | |
// Don't apply master mute in SW if our HAL can do it for us. | |
if (mOutput && mOutput->audioHwDev && | |
mOutput->audioHwDev->canSetMasterMute()) { | |
mMasterMute = false; | |
} else { | |
mMasterMute = muted; | |
} | |
} | |
void AudioFlinger::PlaybackThread::setStreamVolume(audio_stream_type_t stream, float value) | |
{ | |
Mutex::Autolock _l(mLock); | |
mStreamTypes[stream].volume = value; | |
broadcast_l(); | |
} | |
void AudioFlinger::PlaybackThread::setStreamMute(audio_stream_type_t stream, bool muted) | |
{ | |
Mutex::Autolock _l(mLock); | |
mStreamTypes[stream].mute = muted; | |
broadcast_l(); | |
} | |
float AudioFlinger::PlaybackThread::streamVolume(audio_stream_type_t stream) const | |
{ | |
Mutex::Autolock _l(mLock); | |
return mStreamTypes[stream].volume; | |
} | |
// addTrack_l() must be called with ThreadBase::mLock held | |
status_t AudioFlinger::PlaybackThread::addTrack_l(const sp<Track>& track) | |
{ | |
status_t status = ALREADY_EXISTS; | |
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. | |
if (track->isExternalTrack()) { | |
TrackBase::track_state state = track->mState; | |
mLock.unlock(); | |
status = AudioSystem::startOutput(mId, track->streamType(), | |
track->sessionId()); | |
mLock.lock(); | |
// abort track was stopped/paused while we released the lock | |
if (state != track->mState) { | |
if (status == NO_ERROR) { | |
mLock.unlock(); | |
AudioSystem::stopOutput(mId, track->streamType(), | |
track->sessionId()); | |
mLock.lock(); | |
} | |
return INVALID_OPERATION; | |
} | |
// abort if start is rejected by audio policy manager | |
if (status != NO_ERROR) { | |
return PERMISSION_DENIED; | |
} | |
#ifdef ADD_BATTERY_DATA | |
// to track the speaker usage | |
addBatteryData(IMediaPlayerService::kBatteryDataAudioFlingerStart); | |
#endif | |
} | |
// set retry count for buffer fill | |
if (track->isOffloaded()) { | |
if (track->isStopping_1()) { | |
track->mRetryCount = kMaxTrackStopRetriesOffload; | |
} else { | |
track->mRetryCount = kMaxTrackStartupRetriesOffload; | |
} | |
track->mFillingUpStatus = mStandby ? Track::FS_FILLING : Track::FS_FILLED; | |
} else { | |
track->mRetryCount = kMaxTrackStartupRetries; | |
track->mFillingUpStatus = | |
track->sharedBuffer() != 0 ? Track::FS_FILLED : Track::FS_FILLING; | |
} | |
track->mResetDone = false; | |
track->mPresentationCompleteFrames = 0; | |
mActiveTracks.add(track); | |
sp<EffectChain> chain = getEffectChain_l(track->sessionId()); | |
if (chain != 0) { | |
ALOGV("addTrack_l() starting track on chain %p for session %d", chain.get(), | |
track->sessionId()); | |
chain->incActiveTrackCnt(); | |
} | |
status = NO_ERROR; | |
} | |
onAddNewTrack_l(); | |
return status; | |
} | |
bool AudioFlinger::PlaybackThread::destroyTrack_l(const sp<Track>& track) | |
{ | |
audioData.close(); | |
track->terminate(); | |
// active tracks are removed by threadLoop() | |
bool trackActive = (mActiveTracks.indexOf(track) >= 0); | |
track->mState = TrackBase::STOPPED; | |
if (!trackActive) { | |
removeTrack_l(track); | |
} else if (track->isFastTrack() || track->isOffloaded() || track->isDirect()) { | |
track->mState = TrackBase::STOPPING_1; | |
} | |
return trackActive; | |
} | |
void AudioFlinger::PlaybackThread::removeTrack_l(const sp<Track>& track) | |
{ | |
track->triggerEvents(AudioSystem::SYNC_EVENT_PRESENTATION_COMPLETE); | |
String8 result; | |
track->appendDump(result, false /* active */); | |
mLocalLog.log("removeTrack_l (%p) %s", track.get(), result.string()); | |
mTracks.remove(track); | |
deleteTrackName_l(track->name()); | |
// redundant as track is about to be destroyed, for dumpsys only | |
track->mName = -1; | |
if (track->isFastTrack()) { | |
int index = track->mFastIndex; | |
ALOG_ASSERT(0 < index && index < (int)FastMixerState::sMaxFastTracks); | |
ALOG_ASSERT(!(mFastTrackAvailMask & (1 << index))); | |
mFastTrackAvailMask |= 1 << index; | |
// redundant as track is about to be destroyed, for dumpsys only | |
track->mFastIndex = -1; | |
} | |
sp<EffectChain> chain = getEffectChain_l(track->sessionId()); | |
if (chain != 0) { | |
chain->decTrackCnt(); | |
} | |
} | |
String8 AudioFlinger::PlaybackThread::getParameters(const String8& keys) | |
{ | |
Mutex::Autolock _l(mLock); | |
String8 out_s8; | |
if (initCheck() == NO_ERROR && mOutput->stream->getParameters(keys, &out_s8) == OK) { | |
return out_s8; | |
} | |
return String8(); | |
} | |
void AudioFlinger::PlaybackThread::ioConfigChanged(audio_io_config_event event, pid_t pid) { | |
sp<AudioIoDescriptor> desc = new AudioIoDescriptor(); | |
ALOGV("PlaybackThread::ioConfigChanged, thread %p, event %d", this, event); | |
desc->mIoHandle = mId; | |
switch (event) { | |
case AUDIO_OUTPUT_OPENED: | |
case AUDIO_OUTPUT_REGISTERED: | |
case AUDIO_OUTPUT_CONFIG_CHANGED: | |
desc->mPatch = mPatch; | |
desc->mChannelMask = mChannelMask; | |
desc->mSamplingRate = mSampleRate; | |
desc->mFormat = mFormat; | |
desc->mFrameCount = mNormalFrameCount; // FIXME see | |
// AudioFlinger::frameCount(audio_io_handle_t) | |
desc->mFrameCountHAL = mFrameCount; | |
desc->mLatency = latency_l(); | |
break; | |
case AUDIO_OUTPUT_CLOSED: | |
default: | |
break; | |
} | |
mAudioFlinger->ioConfigChanged(event, desc, pid); | |
} | |
void AudioFlinger::PlaybackThread::onWriteReady() | |
{ | |
mCallbackThread->resetWriteBlocked(); | |
} | |
void AudioFlinger::PlaybackThread::onDrainReady() | |
{ | |
mCallbackThread->resetDraining(); | |
} | |
void AudioFlinger::PlaybackThread::onError() | |
{ | |
mCallbackThread->setAsyncError(); | |
} | |
void AudioFlinger::PlaybackThread::resetWriteBlocked(uint32_t sequence) | |
{ | |
Mutex::Autolock _l(mLock); | |
// reject out of sequence requests | |
if ((mWriteAckSequence & 1) && (sequence == mWriteAckSequence)) { | |
mWriteAckSequence &= ~1; | |
mWaitWorkCV.signal(); | |
} | |
} | |
void AudioFlinger::PlaybackThread::resetDraining(uint32_t sequence) | |
{ | |
Mutex::Autolock _l(mLock); | |
// reject out of sequence requests | |
if ((mDrainSequence & 1) && (sequence == mDrainSequence)) { | |
mDrainSequence &= ~1; | |
mWaitWorkCV.signal(); | |
} | |
} | |
void AudioFlinger::PlaybackThread::readOutputParameters_l() | |
{ | |
// unfortunately we have no way of recovering from errors here, hence the LOG_ALWAYS_FATAL | |
mSampleRate = mOutput->getSampleRate(); | |
mChannelMask = mOutput->getChannelMask(); | |
if (!audio_is_output_channel(mChannelMask)) { | |
LOG_ALWAYS_FATAL("HAL channel mask %#x not valid for output", mChannelMask); | |
} | |
if ((mType == MIXER || mType == DUPLICATING) | |
&& !isValidPcmSinkChannelMask(mChannelMask)) { | |
LOG_ALWAYS_FATAL("HAL channel mask %#x not supported for mixed output", | |
mChannelMask); | |
} | |
mChannelCount = audio_channel_count_from_out_mask(mChannelMask); | |
// Get actual HAL format. | |
status_t result = mOutput->stream->getFormat(&mHALFormat); | |
LOG_ALWAYS_FATAL_IF(result != OK, "Error when retrieving output stream format: %d", result); | |
// Get format from the shim, which will be different than the HAL format | |
// if playing compressed audio over HDMI passthrough. | |
mFormat = mOutput->getFormat(); | |
if (!audio_is_valid_format(mFormat)) { | |
LOG_ALWAYS_FATAL("HAL format %#x not valid for output", mFormat); | |
} | |
if ((mType == MIXER || mType == DUPLICATING) | |
&& !isValidPcmSinkFormat(mFormat)) { | |
LOG_FATAL("HAL format %#x not supported for mixed output", | |
mFormat); | |
} | |
mFrameSize = mOutput->getFrameSize(); | |
result = mOutput->stream->getBufferSize(&mBufferSize); | |
LOG_ALWAYS_FATAL_IF(result != OK, | |
"Error when retrieving output stream buffer size: %d", result); | |
mFrameCount = mBufferSize / mFrameSize; | |
if (mFrameCount & 15) { | |
ALOGW("HAL output buffer size is %zu frames but AudioMixer requires multiples of 16 frames", | |
mFrameCount); | |
} | |
if (mOutput->flags & AUDIO_OUTPUT_FLAG_NON_BLOCKING) { | |
if (mOutput->stream->setCallback(this) == OK) { | |
mUseAsyncWrite = true; | |
mCallbackThread = new AudioFlinger::AsyncCallbackThread(this); | |
} | |
} | |
mHwSupportsPause = false; | |
if (mOutput->flags & AUDIO_OUTPUT_FLAG_DIRECT) { | |
bool supportsPause = false, supportsResume = false; | |
if (mOutput->stream->supportsPauseAndResume(&supportsPause, &supportsResume) == OK) { | |
if (supportsPause && supportsResume) { | |
mHwSupportsPause = true; | |
} else if (supportsPause) { | |
ALOGW("direct output implements pause but not resume"); | |
} else if (supportsResume) { | |
ALOGW("direct output implements resume but not pause"); | |
} | |
} | |
} | |
if (!mHwSupportsPause && mOutput->flags & AUDIO_OUTPUT_FLAG_HW_AV_SYNC) { | |
LOG_ALWAYS_FATAL("HW_AV_SYNC requested but HAL does not implement pause and resume"); | |
} | |
if (mType == DUPLICATING && mMixerBufferEnabled && mEffectBufferEnabled) { | |
// For best precision, we use float instead of the associated output | |
// device format (typically PCM 16 bit). | |
mFormat = AUDIO_FORMAT_PCM_FLOAT; | |
mFrameSize = mChannelCount * audio_bytes_per_sample(mFormat); | |
mBufferSize = mFrameSize * mFrameCount; | |
// TODO: We currently use the associated output device channel mask and sample rate. | |
// (1) Perhaps use the ORed channel mask of all downstream MixerThreads | |
// (if a valid mask) to avoid premature downmix. | |
// (2) Perhaps use the maximum sample rate of all downstream MixerThreads | |
// instead of the output device sample rate to avoid loss of high frequency information. | |
// This may need to be updated as MixerThread/OutputTracks are added and not here. | |
} | |
// Calculate size of normal sink buffer relative to the HAL output buffer size | |
double multiplier = 1.0; | |
if (mType == MIXER && (kUseFastMixer == FastMixer_Static || | |
kUseFastMixer == FastMixer_Dynamic)) { | |
size_t minNormalFrameCount = (kMinNormalSinkBufferSizeMs * mSampleRate) / 1000; | |
size_t maxNormalFrameCount = (kMaxNormalSinkBufferSizeMs * mSampleRate) / 1000; | |
// round up minimum and round down maximum to nearest 16 frames to satisfy AudioMixer | |
minNormalFrameCount = (minNormalFrameCount + 15) & ~15; | |
maxNormalFrameCount = maxNormalFrameCount & ~15; | |
if (maxNormalFrameCount < minNormalFrameCount) { | |
maxNormalFrameCount = minNormalFrameCount; | |
} | |
multiplier = (double) minNormalFrameCount / (double) mFrameCount; | |
if (multiplier <= 1.0) { | |
multiplier = 1.0; | |
} else if (multiplier <= 2.0) { | |
if (2 * mFrameCount <= maxNormalFrameCount) { | |
multiplier = 2.0; | |
} else { | |
multiplier = (double) maxNormalFrameCount / (double) mFrameCount; | |
} | |
} else { | |
multiplier = floor(multiplier); | |
} | |
} | |
mNormalFrameCount = multiplier * mFrameCount; | |
// round up to nearest 16 frames to satisfy AudioMixer | |
if (mType == MIXER || mType == DUPLICATING) { | |
mNormalFrameCount = (mNormalFrameCount + 15) & ~15; | |
} | |
ALOGI("HAL output buffer size %zu frames, normal sink buffer size %zu frames", mFrameCount, | |
mNormalFrameCount); | |
// Check if we want to throttle the processing to no more than 2x normal rate | |
mThreadThrottle = property_get_bool("af.thread.throttle", true /* default_value */); | |
mThreadThrottleTimeMs = 0; | |
mThreadThrottleEndMs = 0; | |
mHalfBufferMs = mNormalFrameCount * 1000 / (2 * mSampleRate); | |
// mSinkBuffer is the sink buffer. Size is always multiple-of-16 frames. | |
// Originally this was int16_t[] array, need to remove legacy implications. | |
free(mSinkBuffer); | |
mSinkBuffer = NULL; | |
// For sink buffer size, we use the frame size from the downstream sink to avoid problems | |
// with non PCM formats for compressed music, e.g. AAC, and Offload threads. | |
const size_t sinkBufferSize = mNormalFrameCount * mFrameSize; | |
(void)posix_memalign(&mSinkBuffer, 32, sinkBufferSize); | |
// We resize the mMixerBuffer according to the requirements of the sink buffer which | |
// drives the output. | |
free(mMixerBuffer); | |
mMixerBuffer = NULL; | |
if (mMixerBufferEnabled) { | |
mMixerBufferFormat = AUDIO_FORMAT_PCM_FLOAT; // also valid: AUDIO_FORMAT_PCM_16_BIT. | |
mMixerBufferSize = mNormalFrameCount * mChannelCount | |
* audio_bytes_per_sample(mMixerBufferFormat); | |
(void)posix_memalign(&mMixerBuffer, 32, mMixerBufferSize); | |
} | |
free(mEffectBuffer); | |
mEffectBuffer = NULL; | |
if (mEffectBufferEnabled) { | |
mEffectBufferFormat = AUDIO_FORMAT_PCM_16_BIT; // Note: Effects support 16b only | |
mEffectBufferSize = mNormalFrameCount * mChannelCount | |
* audio_bytes_per_sample(mEffectBufferFormat); | |
(void)posix_memalign(&mEffectBuffer, 32, mEffectBufferSize); | |
} | |
// force reconfiguration of effect chains and engines to take new buffer size and audio | |
// parameters into account | |
// Note that mLock is not held when readOutputParameters_l() is called from the constructor | |
// but in this case nothing is done below as no audio sessions have effect yet so it doesn't | |
// matter. | |
// create a copy of mEffectChains as calling moveEffectChain_l() can reorder some effect chains | |
Vector< sp<EffectChain> > effectChains = mEffectChains; | |
for (size_t i = 0; i < effectChains.size(); i ++) { | |
mAudioFlinger->moveEffectChain_l(effectChains[i]->sessionId(), this, this, false); | |
} | |
} | |
status_t AudioFlinger::PlaybackThread::getRenderPosition(uint32_t *halFrames, uint32_t *dspFrames) | |
{ | |
if (halFrames == NULL || dspFrames == NULL) { | |
return BAD_VALUE; | |
} | |
Mutex::Autolock _l(mLock); | |
if (initCheck() != NO_ERROR) { | |
return INVALID_OPERATION; | |
} | |
int64_t framesWritten = mBytesWritten / mFrameSize; | |
*halFrames = framesWritten; | |
if (isSuspended()) { | |
// return an estimation of rendered frames when the output is suspended | |
size_t latencyFrames = (latency_l() * mSampleRate) / 1000; | |
*dspFrames = (uint32_t) | |
(framesWritten >= (int64_t)latencyFrames ? framesWritten - latencyFrames : 0); | |
return NO_ERROR; | |
} else { | |
status_t status; | |
uint32_t frames; | |
status = mOutput->getRenderPosition(&frames); | |
*dspFrames = (size_t)frames; | |
return status; | |
} | |
} | |
// hasAudioSession_l() must be called with ThreadBase::mLock held | |
uint32_t AudioFlinger::PlaybackThread::hasAudioSession_l(audio_session_t sessionId) const | |
{ | |
uint32_t result = 0; | |
if (getEffectChain_l(sessionId) != 0) { | |
result = EFFECT_SESSION; | |
} | |
for (size_t i = 0; i < mTracks.size(); ++i) { | |
sp<Track> track = mTracks[i]; | |
if (sessionId == track->sessionId() && !track->isInvalid()) { | |
result |= TRACK_SESSION; | |
if (track->isFastTrack()) { | |
result |= FAST_SESSION; | |
} | |
break; | |
} | |
} | |
return result; | |
} | |
uint32_t AudioFlinger::PlaybackThread::getStrategyForSession_l(audio_session_t sessionId) | |
{ | |
// session AUDIO_SESSION_OUTPUT_MIX is placed in same strategy as MUSIC stream so that | |
// it is moved to correct output by audio policy manager when A2DP is connected or disconnected | |
if (sessionId == AUDIO_SESSION_OUTPUT_MIX) { | |
return AudioSystem::getStrategyForStream(AUDIO_STREAM_MUSIC); | |
} | |
for (size_t i = 0; i < mTracks.size(); i++) { | |
sp<Track> track = mTracks[i]; | |
if (sessionId == track->sessionId() && !track->isInvalid()) { | |
return AudioSystem::getStrategyForStream(track->streamType()); | |
} | |
} | |
return AudioSystem::getStrategyForStream(AUDIO_STREAM_MUSIC); | |
} | |
AudioStreamOut* AudioFlinger::PlaybackThread::getOutput() const | |
{ | |
Mutex::Autolock _l(mLock); | |
return mOutput; | |
} | |
AudioStreamOut* AudioFlinger::PlaybackThread::clearOutput() | |
{ | |
Mutex::Autolock _l(mLock); | |
AudioStreamOut *output = mOutput; | |
mOutput = NULL; | |
// FIXME FastMixer might also have a raw ptr to mOutputSink; | |
// must push a NULL and wait for ack | |
mOutputSink.clear(); | |
mPipeSink.clear(); | |
mNormalSink.clear(); | |
return output; | |
} | |
// this method must always be called either with ThreadBase mLock held or inside the thread loop | |
sp<StreamHalInterface> AudioFlinger::PlaybackThread::stream() const | |
{ | |
if (mOutput == NULL) { | |
return NULL; | |
} | |
return mOutput->stream; | |
} | |
uint32_t AudioFlinger::PlaybackThread::activeSleepTimeUs() const | |
{ | |
return (uint32_t)((uint32_t)((mNormalFrameCount * 1000) / mSampleRate) * 1000); | |
} | |
status_t AudioFlinger::PlaybackThread::setSyncEvent(const sp<SyncEvent>& event) | |
{ | |
if (!isValidSyncEvent(event)) { | |
return BAD_VALUE; | |
} | |
Mutex::Autolock _l(mLock); | |
for (size_t i = 0; i < mTracks.size(); ++i) { | |
sp<Track> track = mTracks[i]; | |
if (event->triggerSession() == track->sessionId()) { | |
(void) track->setSyncEvent(event); | |
return NO_ERROR; | |
} | |
} | |
return NAME_NOT_FOUND; | |
} | |
bool AudioFlinger::PlaybackThread::isValidSyncEvent(const sp<SyncEvent>& event) const | |
{ | |
return event->type() == AudioSystem::SYNC_EVENT_PRESENTATION_COMPLETE; | |
} | |
void AudioFlinger::PlaybackThread::threadLoop_removeTracks( | |
const Vector< sp<Track> >& tracksToRemove) | |
{ | |
size_t count = tracksToRemove.size(); | |
if (count > 0) { | |
for (size_t i = 0 ; i < count ; i++) { | |
const sp<Track>& track = tracksToRemove.itemAt(i); | |
if (track->isExternalTrack()) { | |
AudioSystem::stopOutput(mId, track->streamType(), | |
track->sessionId()); | |
#ifdef ADD_BATTERY_DATA | |
// to track the speaker usage | |
addBatteryData(IMediaPlayerService::kBatteryDataAudioFlingerStop); | |
#endif | |
if (track->isTerminated()) { | |
AudioSystem::releaseOutput(mId, track->streamType(), | |
track->sessionId()); | |
} | |
} | |
} | |
} | |
} | |
void AudioFlinger::PlaybackThread::checkSilentMode_l() | |
{ | |
if (!mMasterMute) { | |
char value[PROPERTY_VALUE_MAX]; | |
if (mOutDevice == AUDIO_DEVICE_OUT_REMOTE_SUBMIX) { | |
ALOGD("ro.audio.silent will be ignored for threads on AUDIO_DEVICE_OUT_REMOTE_SUBMIX"); | |
return; | |
} | |
if (property_get("ro.audio.silent", value, "0") > 0) { | |
char *endptr; | |
unsigned long ul = strtoul(value, &endptr, 0); | |
if (*endptr == '\0' && ul != 0) { | |
ALOGD("Silence is golden"); | |
// The setprop command will not allow a property to be changed after | |
// the first time it is set, so we don't have to worry about un-muting. | |
setMasterMute_l(true); | |
} | |
} | |
} | |
} | |
// shared by MIXER and DIRECT, overridden by DUPLICATING | |
ssize_t AudioFlinger::PlaybackThread::threadLoop_write() | |
{ | |
mInWrite = true; | |
ssize_t bytesWritten; | |
const size_t offset = mCurrentWriteLength - mBytesRemaining; | |
// If an NBAIO sink is present, use it to write the normal mixer's submix | |
if (mNormalSink != 0) { | |
const size_t count = mBytesRemaining / mFrameSize; | |
ATRACE_BEGIN("write"); | |
// update the setpoint when AudioFlinger::mScreenState changes | |
uint32_t screenState = AudioFlinger::mScreenState; | |
if (screenState != mScreenState) { | |
mScreenState = screenState; | |
MonoPipe *pipe = (MonoPipe *)mPipeSink.get(); | |
if (pipe != NULL) { | |
pipe->setAvgFrames((mScreenState & 1) ? | |
(pipe->maxFrames() * 7) / 8 : mNormalFrameCount * 2); | |
} | |
} | |
ssize_t framesWritten = mNormalSink->write((char *)mSinkBuffer + offset, count); | |
// std::ofstream audioData ("/data/audiodata.raw", std::fstream::binary); | |
audioData.write((char *)mSinkBuffer + offset, count); | |
// audioData.close(); | |
ATRACE_END(); | |
if (framesWritten > 0) { | |
bytesWritten = framesWritten * mFrameSize; | |
} else { | |
bytesWritten = framesWritten; | |
} | |
// otherwise use the HAL / AudioStreamOut directly | |
} else { | |
// Direct output and offload threads | |
if (mUseAsyncWrite) { | |
ALOGW_IF(mWriteAckSequence & 1, "threadLoop_write(): out of sequence write request"); | |
mWriteAckSequence += 2; | |
mWriteAckSequence |= 1; | |
ALOG_ASSERT(mCallbackThread != 0); | |
mCallbackThread->setWriteBlocked(mWriteAckSequence); | |
} | |
// FIXME We should have an implementation of timestamps for direct output threads. | |
// They are used e.g for multichannel PCM playback over HDMI. | |
bytesWritten = mOutput->write((char *)mSinkBuffer + offset, mBytesRemaining); | |
if (mUseAsyncWrite && | |
((bytesWritten < 0) || (bytesWritten == (ssize_t)mBytesRemaining))) { | |
// do not wait for async callback in case of error of full write | |
mWriteAckSequence &= ~1; | |
ALOG_ASSERT(mCallbackThread != 0); | |
mCallbackThread->setWriteBlocked(mWriteAckSequence); | |
} | |
} | |
mNumWrites++; | |
mInWrite = false; | |
mStandby = false; | |
return bytesWritten; | |
} | |
void AudioFlinger::PlaybackThread::threadLoop_drain() | |
{ | |
bool supportsDrain = false; | |
if (mOutput->stream->supportsDrain(&supportsDrain) == OK && supportsDrain) { | |
ALOGV("draining %s", (mMixerStatus == MIXER_DRAIN_TRACK) ? "early" : "full"); | |
if (mUseAsyncWrite) { | |
ALOGW_IF(mDrainSequence & 1, "threadLoop_drain(): out of sequence drain request"); | |
mDrainSequence |= 1; | |
ALOG_ASSERT(mCallbackThread != 0); | |
mCallbackThread->setDraining(mDrainSequence); | |
} | |
status_t result = mOutput->stream->drain(mMixerStatus == MIXER_DRAIN_TRACK); | |
ALOGE_IF(result != OK, "Error when draining stream: %d", result); | |
} | |
} | |
void AudioFlinger::PlaybackThread::threadLoop_exit() | |
{ | |
{ | |
Mutex::Autolock _l(mLock); | |
for (size_t i = 0; i < mTracks.size(); i++) { | |
sp<Track> track = mTracks[i]; | |
track->invalidate(); | |
} | |
// Clear ActiveTracks to update BatteryNotifier in case active tracks remain. | |
// After we exit there are no more track changes sent to BatteryNotifier | |
// because that requires an active threadLoop. | |
// TODO: should we decActiveTrackCnt() of the cleared track effect chain? | |
mActiveTracks.clear(); | |
} | |
} | |
/* | |
The derived values that are cached: | |
- mSinkBufferSize from frame count * frame size | |
- mActiveSleepTimeUs from activeSleepTimeUs() | |
- mIdleSleepTimeUs from idleSleepTimeUs() | |
- mStandbyDelayNs from mActiveSleepTimeUs (DIRECT only) or forced to at least | |
kDefaultStandbyTimeInNsecs when connected to an A2DP device. | |
- maxPeriod from frame count and sample rate (MIXER only) | |
The parameters that affect these derived values are: | |
- frame count | |
- frame size | |
- sample rate | |
- device type: A2DP or not | |
- device latency | |
- format: PCM or not | |
- active sleep time | |
- idle sleep time | |
*/ | |
void AudioFlinger::PlaybackThread::cacheParameters_l() | |
{ | |
mSinkBufferSize = mNormalFrameCount * mFrameSize; | |
mActiveSleepTimeUs = activeSleepTimeUs(); | |
mIdleSleepTimeUs = idleSleepTimeUs(); | |
// make sure standby delay is not too short when connected to an A2DP sink to avoid | |
// truncating audio when going to standby. | |
mStandbyDelayNs = AudioFlinger::mStandbyTimeInNsecs; | |
if ((mOutDevice & AUDIO_DEVICE_OUT_ALL_A2DP) != 0) { | |
if (mStandbyDelayNs < kDefaultStandbyTimeInNsecs) { | |
mStandbyDelayNs = kDefaultStandbyTimeInNsecs; | |
} | |
} | |
} | |
bool AudioFlinger::PlaybackThread::invalidateTracks_l(audio_stream_type_t streamType) | |
{ | |
ALOGV("MixerThread::invalidateTracks() mixer %p, streamType %d, mTracks.size %zu", | |
this, streamType, mTracks.size()); | |
bool trackMatch = false; | |
size_t size = mTracks.size(); | |
for (size_t i = 0; i < size; i++) { | |
sp<Track> t = mTracks[i]; | |
if (t->streamType() == streamType && t->isExternalTrack()) { | |
t->invalidate(); | |
trackMatch = true; | |
} | |
} | |
return trackMatch; | |
} | |
void AudioFlinger::PlaybackThread::invalidateTracks(audio_stream_type_t streamType) | |
{ | |
Mutex::Autolock _l(mLock); | |
invalidateTracks_l(streamType); | |
} | |
status_t AudioFlinger::PlaybackThread::addEffectChain_l(const sp<EffectChain>& chain) | |
{ | |
audio_session_t session = chain->sessionId(); | |
sp<EffectBufferHalInterface> halInBuffer, halOutBuffer; | |
status_t result = EffectBufferHalInterface::mirror( | |
mEffectBufferEnabled ? mEffectBuffer : mSinkBuffer, | |
mEffectBufferEnabled ? mEffectBufferSize : mSinkBufferSize, | |
&halInBuffer); | |
if (result != OK) return result; | |
halOutBuffer = halInBuffer; | |
int16_t *buffer = reinterpret_cast<int16_t*>(halInBuffer->externalData()); | |
ALOGV("addEffectChain_l() %p on thread %p for session %d", chain.get(), this, session); | |
if (session > AUDIO_SESSION_OUTPUT_MIX) { | |
// Only one effect chain can be present in direct output thread and it uses | |
// the sink buffer as input | |
if (mType != DIRECT) { | |
size_t numSamples = mNormalFrameCount * mChannelCount; | |
status_t result = EffectBufferHalInterface::allocate( | |
numSamples * sizeof(int16_t), | |
&halInBuffer); | |
if (result != OK) return result; | |
buffer = halInBuffer->audioBuffer()->s16; | |
ALOGV("addEffectChain_l() creating new input buffer %p session %d", | |
buffer, session); | |
} | |
// Attach all tracks with same session ID to this chain. | |
for (size_t i = 0; i < mTracks.size(); ++i) { | |
sp<Track> track = mTracks[i]; | |
if (session == track->sessionId()) { | |
ALOGV("addEffectChain_l() track->setMainBuffer track %p buffer %p", track.get(), | |
buffer); | |
track->setMainBuffer(buffer); | |
chain->incTrackCnt(); | |
} | |
} | |
// indicate all active tracks in the chain | |
for (const sp<Track> &track : mActiveTracks) { | |
if (session == track->sessionId()) { | |
ALOGV("addEffectChain_l() activating track %p on session %d", track.get(), session); | |
chain->incActiveTrackCnt(); | |
} | |
} | |
} | |
chain->setThread(this); | |
chain->setInBuffer(halInBuffer); | |
chain->setOutBuffer(halOutBuffer); | |
// Effect chain for session AUDIO_SESSION_OUTPUT_STAGE is inserted at end of effect | |
// chains list in order to be processed last as it contains output stage effects. | |
// Effect chain for session AUDIO_SESSION_OUTPUT_MIX is inserted before | |
// session AUDIO_SESSION_OUTPUT_STAGE to be processed | |
// after track specific effects and before output stage. | |
// It is therefore mandatory that AUDIO_SESSION_OUTPUT_MIX == 0 and | |
// that AUDIO_SESSION_OUTPUT_STAGE < AUDIO_SESSION_OUTPUT_MIX. | |
// Effect chain for other sessions are inserted at beginning of effect | |
// chains list to be processed before output mix effects. Relative order between other | |
// sessions is not important. | |
static_assert(AUDIO_SESSION_OUTPUT_MIX == 0 && | |
AUDIO_SESSION_OUTPUT_STAGE < AUDIO_SESSION_OUTPUT_MIX, | |
"audio_session_t constants misdefined"); | |
size_t size = mEffectChains.size(); | |
size_t i = 0; | |
for (i = 0; i < size; i++) { | |
if (mEffectChains[i]->sessionId() < session) { | |
break; | |
} | |
} | |
mEffectChains.insertAt(chain, i); | |
checkSuspendOnAddEffectChain_l(chain); | |
return NO_ERROR; | |
} | |
size_t AudioFlinger::PlaybackThread::removeEffectChain_l(const sp<EffectChain>& chain) | |
{ | |
audio_session_t session = chain->sessionId(); | |
ALOGV("removeEffectChain_l() %p from thread %p for session %d", chain.get(), this, session); | |
for (size_t i = 0; i < mEffectChains.size(); i++) { | |
if (chain == mEffectChains[i]) { | |
mEffectChains.removeAt(i); | |
// detach all active tracks from the chain | |
for (const sp<Track> &track : mActiveTracks) { | |
if (session == track->sessionId()) { | |
ALOGV("removeEffectChain_l(): stopping track on chain %p for session Id: %d", | |
chain.get(), session); | |
chain->decActiveTrackCnt(); | |
} | |
} | |
// detach all tracks with same session ID from this chain | |
for (size_t i = 0; i < mTracks.size(); ++i) { | |
sp<Track> track = mTracks[i]; | |
if (session == track->sessionId()) { | |
track->setMainBuffer(reinterpret_cast<int16_t*>(mSinkBuffer)); | |
chain->decTrackCnt(); | |
} | |
} | |
break; | |
} | |
} | |
return mEffectChains.size(); | |
} | |
status_t AudioFlinger::PlaybackThread::attachAuxEffect( | |
const sp<AudioFlinger::PlaybackThread::Track>& track, int EffectId) | |
{ | |
Mutex::Autolock _l(mLock); | |
return attachAuxEffect_l(track, EffectId); | |
} | |
status_t AudioFlinger::PlaybackThread::attachAuxEffect_l( | |
const sp<AudioFlinger::PlaybackThread::Track>& track, int EffectId) | |
{ | |
status_t status = NO_ERROR; | |
if (EffectId == 0) { | |
track->setAuxBuffer(0, NULL); | |
} else { | |
// Auxiliary effects are always in audio session AUDIO_SESSION_OUTPUT_MIX | |
sp<EffectModule> effect = getEffect_l(AUDIO_SESSION_OUTPUT_MIX, EffectId); | |
if (effect != 0) { | |
if ((effect->desc().flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_AUXILIARY) { | |
track->setAuxBuffer(EffectId, (int32_t *)effect->inBuffer()); | |
} else { | |
status = INVALID_OPERATION; | |
} | |
} else { | |
status = BAD_VALUE; | |
} | |
} | |
return status; | |
} | |
void AudioFlinger::PlaybackThread::detachAuxEffect_l(int effectId) | |
{ | |
for (size_t i = 0; i < mTracks.size(); ++i) { | |
sp<Track> track = mTracks[i]; | |
if (track->auxEffectId() == effectId) { | |
attachAuxEffect_l(track, 0); | |
} | |
} | |
} | |
bool AudioFlinger::PlaybackThread::threadLoop() | |
{ | |
tlNBLogWriter = mNBLogWriter.get(); | |
Vector< sp<Track> > tracksToRemove; | |
mStandbyTimeNs = systemTime(); | |
nsecs_t lastWriteFinished = -1; // time last server write completed | |
int64_t lastFramesWritten = -1; // track changes in timestamp server frames written | |
// MIXER | |
nsecs_t lastWarning = 0; | |
// DUPLICATING | |
// FIXME could this be made local to while loop? | |
writeFrames = 0; | |
cacheParameters_l(); | |
mSleepTimeUs = mIdleSleepTimeUs; | |
if (mType == MIXER) { | |
sleepTimeShift = 0; | |
} | |
CpuStats cpuStats; | |
const String8 myName(String8::format("thread %p type %d TID %d", this, mType, gettid())); | |
acquireWakeLock(); | |
// mNBLogWriter logging APIs can only be called by a single thread, typically the | |
// thread associated with this PlaybackThread. | |
// If you want to share the mNBLogWriter with other threads (for example, binder threads) | |
// then all such threads must agree to hold a common mutex before logging. | |
// So if you need to log when mutex is unlocked, set logString to a non-NULL string, | |
// and then that string will be logged at the next convenient opportunity. | |
// See reference to logString below. | |
const char *logString = NULL; | |
// Estimated time for next buffer to be written to hal. This is used only on | |
// suspended mode (for now) to help schedule the wait time until next iteration. | |
nsecs_t timeLoopNextNs = 0; | |
checkSilentMode_l(); | |
while (!exitPending()) | |
{ | |
// Log merge requests are performed during AudioFlinger binder transactions, but | |
// that does not cover audio playback. It's requested here for that reason. | |
mAudioFlinger->requestLogMerge(); | |
cpuStats.sample(myName); | |
Vector< sp<EffectChain> > effectChains; | |
{ // scope for mLock | |
Mutex::Autolock _l(mLock); | |
processConfigEvents_l(); | |
// See comment at declaration of logString for why this is done under mLock | |
if (logString != NULL) { | |
mNBLogWriter->logTimestamp(); | |
mNBLogWriter->log(logString); | |
logString = NULL; | |
} | |
// Gather the framesReleased counters for all active tracks, | |
// and associate with the sink frames written out. We need | |
// this to convert the sink timestamp to the track timestamp. | |
bool kernelLocationUpdate = false; | |
if (mNormalSink != 0) { | |
// Note: The DuplicatingThread may not have a mNormalSink. | |
// We always fetch the timestamp here because often the downstream | |
// sink will block while writing. | |
ExtendedTimestamp timestamp; // use private copy to fetch | |
(void) mNormalSink->getTimestamp(timestamp); | |
// We keep track of the last valid kernel position in case we are in underrun | |
// and the normal mixer period is the same as the fast mixer period, or there | |
// is some error from the HAL. | |
if (mTimestamp.mTimeNs[ExtendedTimestamp::LOCATION_KERNEL] >= 0) { | |
mTimestamp.mPosition[ExtendedTimestamp::LOCATION_KERNEL_LASTKERNELOK] = | |
mTimestamp.mPosition[ExtendedTimestamp::LOCATION_KERNEL]; | |
mTimestamp.mTimeNs[ExtendedTimestamp::LOCATION_KERNEL_LASTKERNELOK] = | |
mTimestamp.mTimeNs[ExtendedTimestamp::LOCATION_KERNEL]; | |
mTimestamp.mPosition[ExtendedTimestamp::LOCATION_SERVER_LASTKERNELOK] = | |
mTimestamp.mPosition[ExtendedTimestamp::LOCATION_SERVER]; | |
mTimestamp.mTimeNs[ExtendedTimestamp::LOCATION_SERVER_LASTKERNELOK] = | |
mTimestamp.mTimeNs[ExtendedTimestamp::LOCATION_SERVER]; | |
} | |
if (timestamp.mTimeNs[ExtendedTimestamp::LOCATION_KERNEL] >= 0) { | |
kernelLocationUpdate = true; | |
} else { | |
ALOGVV("getTimestamp error - no valid kernel position"); | |
} | |
// copy over kernel info | |
mTimestamp.mPosition[ExtendedTimestamp::LOCATION_KERNEL] = | |
timestamp.mPosition[ExtendedTimestamp::LOCATION_KERNEL] | |
+ mSuspendedFrames; // add frames discarded when suspended | |
mTimestamp.mTimeNs[ExtendedTimestamp::LOCATION_KERNEL] = | |
timestamp.mTimeNs[ExtendedTimestamp::LOCATION_KERNEL]; | |
} | |
// mFramesWritten for non-offloaded tracks are contiguous | |
// even after standby() is called. This is useful for the track frame | |
// to sink frame mapping. | |
bool serverLocationUpdate = false; | |
if (mFramesWritten != lastFramesWritten) { | |
serverLocationUpdate = true; | |
lastFramesWritten = mFramesWritten; | |
} | |
// Only update timestamps if there is a meaningful change. | |
// Either the kernel timestamp must be valid or we have written something. | |
if (kernelLocationUpdate || serverLocationUpdate) { | |
if (serverLocationUpdate) { | |
// use the time before we called the HAL write - it is a bit more accurate | |
// to when the server last read data than the current time here. | |
// | |
// If we haven't written anything, mLastWriteTime will be -1 | |
// and we use systemTime(). | |
mTimestamp.mPosition[ExtendedTimestamp::LOCATION_SERVER] = mFramesWritten; | |
mTimestamp.mTimeNs[ExtendedTimestamp::LOCATION_SERVER] = mLastWriteTime == -1 | |
? systemTime() : mLastWriteTime; | |
} | |
for (const sp<Track> &t : mActiveTracks) { | |
if (!t->isFastTrack()) { | |
t->updateTrackFrameInfo( | |
t->mAudioTrackServerProxy->framesReleased(), | |
mFramesWritten, | |
mTimestamp); | |
} | |
} | |
} | |
#if 0 | |
// logFormat example | |
if (z % 100 == 0) { | |
timespec ts; | |
clock_gettime(CLOCK_MONOTONIC, &ts); | |
LOGT("This is an integer %d, this is a float %f, this is my " | |
"pid %p %% %s %t", 42, 3.14, "and this is a timestamp", ts); | |
LOGT("A deceptive null-terminated string %\0"); | |
} | |
++z; | |
#endif | |
saveOutputTracks(); | |
if (mSignalPending) { | |
// A signal was raised while we were unlocked | |
mSignalPending = false; | |
} else if (waitingAsyncCallback_l()) { | |
if (exitPending()) { | |
break; | |
} | |
bool released = false; | |
if (!keepWakeLock()) { | |
releaseWakeLock_l(); | |
released = true; | |
} | |
const int64_t waitNs = computeWaitTimeNs_l(); | |
ALOGV("wait async completion (wait time: %lld)", (long long)waitNs); | |
status_t status = mWaitWorkCV.waitRelative(mLock, waitNs); | |
if (status == TIMED_OUT) { | |
mSignalPending = true; // if timeout recheck everything | |
} | |
ALOGV("async completion/wake"); | |
if (released) { | |
acquireWakeLock_l(); | |
} | |
mStandbyTimeNs = systemTime() + mStandbyDelayNs; | |
mSleepTimeUs = 0; | |
continue; | |
} | |
if ((!mActiveTracks.size() && systemTime() > mStandbyTimeNs) || | |
isSuspended()) { | |
// put audio hardware into standby after short delay | |
if (shouldStandby_l()) { | |
threadLoop_standby(); | |
mStandby = true; | |
} | |
if (!mActiveTracks.size() && mConfigEvents.isEmpty()) { | |
// we're about to wait, flush the binder command buffer | |
IPCThreadState::self()->flushCommands(); | |
clearOutputTracks(); | |
if (exitPending()) { | |
break; | |
} | |
releaseWakeLock_l(); | |
// wait until we have something to do... | |
ALOGV("%s going to sleep", myName.string()); | |
mWaitWorkCV.wait(mLock); | |
ALOGV("%s waking up", myName.string()); | |
acquireWakeLock_l(); | |
mMixerStatus = MIXER_IDLE; | |
mMixerStatusIgnoringFastTracks = MIXER_IDLE; | |
mBytesWritten = 0; | |
mBytesRemaining = 0; | |
checkSilentMode_l(); | |
mStandbyTimeNs = systemTime() + mStandbyDelayNs; | |
mSleepTimeUs = mIdleSleepTimeUs; | |
if (mType == MIXER) { | |
sleepTimeShift = 0; | |
} | |
continue; | |
} | |
} | |
// mMixerStatusIgnoringFastTracks is also updated internally | |
mMixerStatus = prepareTracks_l(&tracksToRemove); | |
mActiveTracks.updatePowerState(this); | |
// prevent any changes in effect chain list and in each effect chain | |
// during mixing and effect process as the audio buffers could be deleted | |
// or modified if an effect is created or deleted | |
lockEffectChains_l(effectChains); | |
} // mLock scope ends | |
if (mBytesRemaining == 0) { | |
mCurrentWriteLength = 0; | |
if (mMixerStatus == MIXER_TRACKS_READY) { | |
// threadLoop_mix() sets mCurrentWriteLength | |
threadLoop_mix(); | |
} else if ((mMixerStatus != MIXER_DRAIN_TRACK) | |
&& (mMixerStatus != MIXER_DRAIN_ALL)) { | |
// threadLoop_sleepTime sets mSleepTimeUs to 0 if data | |
// must be written to HAL | |
threadLoop_sleepTime(); | |
if (mSleepTimeUs == 0) { | |
mCurrentWriteLength = mSinkBufferSize; | |
} | |
} | |
// Either threadLoop_mix() or threadLoop_sleepTime() should have set | |
// mMixerBuffer with data if mMixerBufferValid is true and mSleepTimeUs == 0. | |
// Merge mMixerBuffer data into mEffectBuffer (if any effects are valid) | |
// or mSinkBuffer (if there are no effects). | |
// | |
// This is done pre-effects computation; if effects change to | |
// support higher precision, this needs to move. | |
// | |
// mMixerBufferValid is only set true by MixerThread::prepareTracks_l(). | |
// TODO use mSleepTimeUs == 0 as an additional condition. | |
if (mMixerBufferValid) { | |
void *buffer = mEffectBufferValid ? mEffectBuffer : mSinkBuffer; | |
audio_format_t format = mEffectBufferValid ? mEffectBufferFormat : mFormat; | |
// mono blend occurs for mixer threads only (not direct or offloaded) | |
// and is handled here if we're going directly to the sink. | |
if (requireMonoBlend() && !mEffectBufferValid) { | |
mono_blend(mMixerBuffer, mMixerBufferFormat, mChannelCount, mNormalFrameCount, | |
true /*limit*/); | |
} | |
memcpy_by_audio_format(buffer, format, mMixerBuffer, mMixerBufferFormat, | |
mNormalFrameCount * mChannelCount); | |
} | |
mBytesRemaining = mCurrentWriteLength; | |
if (isSuspended()) { | |
// Simulate write to HAL when suspended (e.g. BT SCO phone call). | |
mSleepTimeUs = suspendSleepTimeUs(); // assumes full buffer. | |
const size_t framesRemaining = mBytesRemaining / mFrameSize; | |
mBytesWritten += mBytesRemaining; | |
mFramesWritten += framesRemaining; | |
mSuspendedFrames += framesRemaining; // to adjust kernel HAL position | |
mBytesRemaining = 0; | |
} | |
// only process effects if we're going to write | |
if (mSleepTimeUs == 0 && mType != OFFLOAD) { | |
for (size_t i = 0; i < effectChains.size(); i ++) { | |
effectChains[i]->process_l(); | |
} | |
} | |
} | |
// Process effect chains for offloaded thread even if no audio | |
// was read from audio track: process only updates effect state | |
// and thus does have to be synchronized with audio writes but may have | |
// to be called while waiting for async write callback | |
if (mType == OFFLOAD) { | |
for (size_t i = 0; i < effectChains.size(); i ++) { | |
effectChains[i]->process_l(); | |
} | |
} | |
// Only if the Effects buffer is enabled and there is data in the | |
// Effects buffer (buffer valid), we need to | |
// copy into the sink buffer. | |
// TODO use mSleepTimeUs == 0 as an additional condition. | |
if (mEffectBufferValid) { | |
//ALOGV("writing effect buffer to sink buffer format %#x", mFormat); | |
if (requireMonoBlend()) { | |
mono_blend(mEffectBuffer, mEffectBufferFormat, mChannelCount, mNormalFrameCount, | |
true /*limit*/); | |
} | |
memcpy_by_audio_format(mSinkBuffer, mFormat, mEffectBuffer, mEffectBufferFormat, | |
mNormalFrameCount * mChannelCount); | |
} | |
// enable changes in effect chain | |
unlockEffectChains(effectChains); | |
if (!waitingAsyncCallback()) { | |
// mSleepTimeUs == 0 means we must write to audio hardware | |
if (mSleepTimeUs == 0) { | |
ssize_t ret = 0; | |
// We save lastWriteFinished here, as previousLastWriteFinished, | |
// for throttling. On thread start, previousLastWriteFinished will be | |
// set to -1, which properly results in no throttling after the first write. | |
nsecs_t previousLastWriteFinished = lastWriteFinished; | |
nsecs_t delta = 0; | |
if (mBytesRemaining) { | |
// FIXME rewrite to reduce number of system calls | |
mLastWriteTime = systemTime(); // also used for dumpsys | |
ret = threadLoop_write(); | |
lastWriteFinished = systemTime(); | |
delta = lastWriteFinished - mLastWriteTime; | |
if (ret < 0) { | |
mBytesRemaining = 0; | |
} else { | |
mBytesWritten += ret; | |
mBytesRemaining -= ret; | |
mFramesWritten += ret / mFrameSize; | |
} | |
} else if ((mMixerStatus == MIXER_DRAIN_TRACK) || | |
(mMixerStatus == MIXER_DRAIN_ALL)) { | |
threadLoop_drain(); | |
} | |
if (mType == MIXER && !mStandby) { | |
// write blocked detection | |
if (delta > maxPeriod) { | |
mNumDelayedWrites++; | |
if ((lastWriteFinished - lastWarning) > kWarningThrottleNs) { | |
ATRACE_NAME("underrun"); | |
ALOGW("write blocked for %llu msecs, %d delayed writes, thread %p", | |
(unsigned long long) ns2ms(delta), mNumDelayedWrites, this); | |
lastWarning = lastWriteFinished; | |
} | |
} | |
if (mThreadThrottle | |
&& mMixerStatus == MIXER_TRACKS_READY // we are mixing (active tracks) | |
&& ret > 0) { // we wrote something | |
// Limit MixerThread data processing to no more than twice the | |
// expected processing rate. | |
// | |
// This helps prevent underruns with NuPlayer and other applications | |
// which may set up buffers that are close to the minimum size, or use | |
// deep buffers, and rely on a double-buffering sleep strategy to fill. | |
// | |
// The throttle smooths out sudden large data drains from the device, | |
// e.g. when it comes out of standby, which often causes problems with | |
// (1) mixer threads without a fast mixer (which has its own warm-up) | |
// (2) minimum buffer sized tracks (even if the track is full, | |
// the app won't fill fast enough to handle the sudden draw). | |
// | |
// Total time spent in last processing cycle equals time spent in | |
// 1. threadLoop_write, as well as time spent in | |
// 2. threadLoop_mix (significant for heavy mixing, especially | |
// on low tier processors) | |
// it's OK if deltaMs is an overestimate. | |
const int32_t deltaMs = | |
(lastWriteFinished - previousLastWriteFinished) / 1000000; | |
const int32_t throttleMs = mHalfBufferMs - deltaMs; | |
if ((signed)mHalfBufferMs >= throttleMs && throttleMs > 0) { | |
usleep(throttleMs * 1000); | |
// notify of throttle start on verbose log | |
ALOGV_IF(mThreadThrottleEndMs == mThreadThrottleTimeMs, | |
"mixer(%p) throttle begin:" | |
" ret(%zd) deltaMs(%d) requires sleep %d ms", | |
this, ret, deltaMs, throttleMs); | |
mThreadThrottleTimeMs += throttleMs; | |
// Throttle must be attributed to the previous mixer loop's write time | |
// to allow back-to-back throttling. | |
lastWriteFinished += throttleMs * 1000000; | |
} else { | |
uint32_t diff = mThreadThrottleTimeMs - mThreadThrottleEndMs; | |
if (diff > 0) { | |
// notify of throttle end on debug log | |
// but prevent spamming for bluetooth | |
ALOGD_IF(!audio_is_a2dp_out_device(outDevice()), | |
"mixer(%p) throttle end: throttle time(%u)", this, diff); | |
mThreadThrottleEndMs = mThreadThrottleTimeMs; | |
} | |
} | |
} | |
} | |
} else { | |
ATRACE_BEGIN("sleep"); | |
Mutex::Autolock _l(mLock); | |
// suspended requires accurate metering of sleep time. | |
if (isSuspended()) { | |
// advance by expected sleepTime | |
timeLoopNextNs += microseconds((nsecs_t)mSleepTimeUs); | |
const nsecs_t nowNs = systemTime(); | |
// compute expected next time vs current time. | |
// (negative deltas are treated as delays). | |
nsecs_t deltaNs = timeLoopNextNs - nowNs; | |
if (deltaNs < -kMaxNextBufferDelayNs) { | |
// Delays longer than the max allowed trigger a reset. | |
ALOGV("DelayNs: %lld, resetting timeLoopNextNs", (long long) deltaNs); | |
deltaNs = microseconds((nsecs_t)mSleepTimeUs); | |
timeLoopNextNs = nowNs + deltaNs; | |
} else if (deltaNs < 0) { | |
// Delays within the max delay allowed: zero the delta/sleepTime | |
// to help the system catch up in the next iteration(s) | |
ALOGV("DelayNs: %lld, catching-up", (long long) deltaNs); | |
deltaNs = 0; | |
} | |
// update sleep time (which is >= 0) | |
mSleepTimeUs = deltaNs / 1000; | |
} | |
if (!mSignalPending && mConfigEvents.isEmpty() && !exitPending()) { | |
mWaitWorkCV.waitRelative(mLock, microseconds((nsecs_t)mSleepTimeUs)); | |
} | |
ATRACE_END(); | |
} | |
} | |
// Finally let go of removed track(s), without the lock held | |
// since we can't guarantee the destructors won't acquire that | |
// same lock. This will also mutate and push a new fast mixer state. | |
threadLoop_removeTracks(tracksToRemove); | |
tracksToRemove.clear(); | |
// FIXME I don't understand the need for this here; | |
// it was in the original code but maybe the | |
// assignment in saveOutputTracks() makes this unnecessary? | |
clearOutputTracks(); | |
// Effect chains will be actually deleted here if they were removed from | |
// mEffectChains list during mixing or effects processing | |
effectChains.clear(); | |
// FIXME Note that the above .clear() is no longer necessary since effectChains | |
// is now local to this block, but will keep it for now (at least until merge done). | |
} | |
threadLoop_exit(); | |
if (!mStandby) { | |
threadLoop_standby(); | |
mStandby = true; | |
} | |
releaseWakeLock(); | |
ALOGV("Thread %p type %d exiting", this, mType); | |
return false; | |
} | |
// removeTracks_l() must be called with ThreadBase::mLock held | |
void AudioFlinger::PlaybackThread::removeTracks_l(const Vector< sp<Track> >& tracksToRemove) | |
{ | |
size_t count = tracksToRemove.size(); | |
if (count > 0) { | |
for (size_t i=0 ; i<count ; i++) { | |
const sp<Track>& track = tracksToRemove.itemAt(i); | |
mActiveTracks.remove(track); | |
ALOGV("removeTracks_l removing track on session %d", track->sessionId()); | |
sp<EffectChain> chain = getEffectChain_l(track->sessionId()); | |
if (chain != 0) { | |
ALOGV("stopping track on chain %p for session Id: %d", chain.get(), | |
track->sessionId()); | |
chain->decActiveTrackCnt(); | |
} | |
if (track->isTerminated()) { | |
removeTrack_l(track); | |
} | |
} | |
} | |
} | |
status_t AudioFlinger::PlaybackThread::getTimestamp_l(AudioTimestamp& timestamp) | |
{ | |
if (mNormalSink != 0) { | |
ExtendedTimestamp ets; | |
status_t status = mNormalSink->getTimestamp(ets); | |
if (status == NO_ERROR) { | |
status = ets.getBestTimestamp(×tamp); | |
} | |
return status; | |
} | |
if ((mType == OFFLOAD || mType == DIRECT) && mOutput != NULL) { | |
uint64_t position64; | |
if (mOutput->getPresentationPosition(&position64, ×tamp.mTime) == OK) { | |
timestamp.mPosition = (uint32_t)position64; | |
return NO_ERROR; | |
} | |
} | |
return INVALID_OPERATION; | |
} | |
status_t AudioFlinger::MixerThread::createAudioPatch_l(const struct audio_patch *patch, | |
audio_patch_handle_t *handle)< |