blob: 533e2225266551939110895bef22e94fdfef7f78 [file] [log] [blame] [edit]
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <dlfcn.h>
#include <stdint.h>
#include <sys/types.h>
#include "oboe/AudioClock.h"
#include "oboe/Definitions.h"
#include "oboe/Utilities.h"
#include "AdpfWrapper.h"
#include "OboeDebug.h"
#include "Trace.h"
using namespace oboe;
typedef APerformanceHintManager* (*APH_getManager)();
typedef APerformanceHintSession* (*APH_createSession)(APerformanceHintManager*, const int32_t*,
size_t, int64_t);
typedef void (*APH_reportActualWorkDuration)(APerformanceHintSession*, int64_t);
typedef void (*APH_closeSession)(APerformanceHintSession* session);
typedef int (*APH_notifyWorkloadIncrease)(APerformanceHintSession*, bool, bool, const char*);
typedef int (*APH_notifyWorkloadSpike)(APerformanceHintSession*, bool, bool, const char*);
typedef int (*APH_notifyWorkloadReset)(APerformanceHintSession*, bool, bool, const char*);
typedef int (*APH_createSessionUsingConfig)(APerformanceHintManager*, void* /*ASessionCreationConfig*/, APerformanceHintSession**);
typedef void* (*APH_sessionConfig_create)();
typedef void (*APH_sessionConfig_release)(void*);
typedef void (*APH_sessionConfig_setTids)(void*, const pid_t*, size_t);
typedef void (*APH_sessionConfig_setTargetWorkDurationNanos)(void*, int64_t);
typedef void (*APH_sessionConfig_setAudioPerformance)(void*, bool);
typedef bool (*APH_isFeatureSupported)(void* /*APerformanceHintFeature*/);
static bool gAPerformanceHintBindingInitialized = false;
static APH_getManager gAPH_getManagerFn = nullptr;
static APH_createSession gAPH_createSessionFn = nullptr;
static APH_reportActualWorkDuration gAPH_reportActualWorkDurationFn = nullptr;
static APH_closeSession gAPH_closeSessionFn = nullptr;
static APH_notifyWorkloadIncrease gAPH_notifyWorkloadIncreaseFn = nullptr;
static APH_notifyWorkloadSpike gAPH_notifyWorkloadSpikeFn = nullptr;
static APH_notifyWorkloadReset gAPH_notifyWorkloadResetFn = nullptr;
static APH_createSessionUsingConfig gAPH_createSessionUsingConfigFn = nullptr;
static APH_sessionConfig_create gAPH_sessionConfigCreateFn = nullptr;
static APH_sessionConfig_release gAPH_sessionConfigReleaseFn = nullptr;
static APH_sessionConfig_setTids gAPH_sessionConfigSetTidsFn = nullptr;
static APH_sessionConfig_setTargetWorkDurationNanos gAPH_sessionConfigSetTargetWorkDurationNanosFn = nullptr;
static APH_sessionConfig_setAudioPerformance gAPH_sessionConfigSetAudioPerformanceFn = nullptr;
static APH_isFeatureSupported gAPH_isFeatureSupportedFn = nullptr;
#ifndef __ANDROID_API_B__
#define __ANDROID_API_B__ 36
#endif
#ifndef APERF_HINT_SESSIONS
#define APERF_HINT_SESSIONS 0
#endif
#ifndef APERF_HINT_AUDIO_PERFORMANCE
#define APERF_HINT_AUDIO_PERFORMANCE 6
#endif
static int loadAphFunctions() {
if (gAPerformanceHintBindingInitialized) return true;
void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE);
if (handle_ == nullptr) {
return -1000;
}
gAPH_getManagerFn = (APH_getManager)dlsym(handle_, "APerformanceHint_getManager");
if (gAPH_getManagerFn == nullptr) {
return -1001;
}
gAPH_createSessionFn = (APH_createSession)dlsym(handle_, "APerformanceHint_createSession");
if (gAPH_createSessionFn == nullptr) {
return -1002;
}
gAPH_reportActualWorkDurationFn = (APH_reportActualWorkDuration)dlsym(
handle_, "APerformanceHint_reportActualWorkDuration");
if (gAPH_reportActualWorkDurationFn == nullptr) {
return -1003;
}
gAPH_closeSessionFn = (APH_closeSession)dlsym(handle_, "APerformanceHint_closeSession");
if (gAPH_closeSessionFn == nullptr) {
return -1004;
}
// TODO: Remove pre-release check after Android B release
if (getSdkVersion() >= __ANDROID_API_B__ || isAtLeastPreReleaseCodename("Baklava")) {
gAPH_notifyWorkloadIncreaseFn = (APH_notifyWorkloadIncrease)dlsym(
handle_, "APerformanceHint_notifyWorkloadIncrease");
if (gAPH_notifyWorkloadIncreaseFn == nullptr) {
return -1005;
}
gAPH_notifyWorkloadSpikeFn = (APH_notifyWorkloadSpike)dlsym(
handle_, "APerformanceHint_notifyWorkloadSpike");
if (gAPH_notifyWorkloadSpikeFn == nullptr) {
return -1006;
}
gAPH_notifyWorkloadResetFn = (APH_notifyWorkloadReset)dlsym(
handle_, "APerformanceHint_notifyWorkloadReset");
if (gAPH_notifyWorkloadResetFn == nullptr) {
return -1007;
}
// Optional config-based session creation (API 36+). These may be missing on older
// platforms or pre-release builds; load them if available.
gAPH_createSessionUsingConfigFn = (APH_createSessionUsingConfig)dlsym(
handle_, "APerformanceHint_createSessionUsingConfig");
gAPH_sessionConfigCreateFn = (APH_sessionConfig_create)dlsym(handle_, "ASessionCreationConfig_create");
gAPH_sessionConfigReleaseFn = (APH_sessionConfig_release)dlsym(handle_, "ASessionCreationConfig_release");
gAPH_sessionConfigSetTidsFn = (APH_sessionConfig_setTids)dlsym(handle_, "ASessionCreationConfig_setTids");
gAPH_sessionConfigSetTargetWorkDurationNanosFn = (APH_sessionConfig_setTargetWorkDurationNanos)dlsym(
handle_, "ASessionCreationConfig_setTargetWorkDurationNanos");
gAPH_sessionConfigSetAudioPerformanceFn = (APH_sessionConfig_setAudioPerformance)dlsym(
handle_, "ASessionCreationConfig_setAudioPerformance");
gAPH_isFeatureSupportedFn = (APH_isFeatureSupported)dlsym(
handle_, "APerformanceHint_isFeatureSupported");
}
gAPerformanceHintBindingInitialized = true;
return 0;
}
bool AdpfWrapper::sUseAlternativeHack = false; // TODO remove hack
void AdpfWrapper::setReportActualDurationDisabled(bool disabled) {
mIsReportActualDurationDisabled = disabled;
}
bool AdpfWrapper::isHighPerformanceAudioSupported() {
// Ensure the performance hint functions are loaded.
int result = loadAphFunctions();
if (result < 0) return false;
if (gAPH_isFeatureSupportedFn == nullptr) return false;
// The binding expects a feature identifier; we use the integer constant
// APERF_HINT_AUDIO_PERFORMANCE cast into the expected parameter type.
return gAPH_isFeatureSupportedFn(
reinterpret_cast<void *>(static_cast<int32_t>(APERF_HINT_AUDIO_PERFORMANCE)));
}
int AdpfWrapper::open(pid_t threadId,
int64_t targetDurationNanos,
bool highPerformanceAudio) {
std::lock_guard<std::mutex> lock(mLock);
int result = loadAphFunctions();
if (result < 0) return result;
// This is a singleton.
APerformanceHintManager* manager = gAPH_getManagerFn();
int32_t thread32 = threadId;
if (sUseAlternativeHack) {
// TODO Remove this hack when we finish experimenting with alternative algorithms.
// The A5 is an arbitrary signal to a hacked version of ADPF to try an alternative
// algorithm that is not based on PID.
targetDurationNanos = (targetDurationNanos & ~0xFF) | 0xA5;
}
// If caller requested highPerformanceAudio and we have the newer config-based
// session creation API, try to create a session with a creation config.
if (highPerformanceAudio && gAPH_isFeatureSupportedFn != nullptr
&& gAPH_isFeatureSupportedFn(
reinterpret_cast<void *>(static_cast<int32_t>(APERF_HINT_SESSIONS)))
&& gAPH_isFeatureSupportedFn(
reinterpret_cast<void *>(static_cast<int32_t>(APERF_HINT_AUDIO_PERFORMANCE)))
&& gAPH_createSessionUsingConfigFn != nullptr
&& gAPH_sessionConfigCreateFn != nullptr
&& gAPH_sessionConfigReleaseFn != nullptr
&& gAPH_sessionConfigSetTidsFn != nullptr
&& gAPH_sessionConfigSetTargetWorkDurationNanosFn != nullptr
&& gAPH_sessionConfigSetAudioPerformanceFn != nullptr) {
void* config = gAPH_sessionConfigCreateFn();
if (config != nullptr) {
// set tids
gAPH_sessionConfigSetTidsFn(config, &thread32, 1);
// set target duration
gAPH_sessionConfigSetTargetWorkDurationNanosFn(config, targetDurationNanos);
// set high performance audio hint
gAPH_sessionConfigSetAudioPerformanceFn(config, true);
APerformanceHintSession *sessionOut = nullptr;
int createResult = gAPH_createSessionUsingConfigFn(manager, config, &sessionOut);
// release config now
gAPH_sessionConfigReleaseFn(config);
if (createResult == 0 && sessionOut != nullptr) {
mHintSession = sessionOut;
return 0;
}
// otherwise fall through to legacy createSession
}
}
mHintSession = gAPH_createSessionFn(manager, &thread32, 1 /* size */, targetDurationNanos);
if (mHintSession == nullptr) {
return -1;
}
return 0;
}
void AdpfWrapper::reportActualDuration(int64_t actualDurationNanos) {
// Skip reporting when disabled; report only when enabled.
if (mIsReportActualDurationDisabled) return;
//LOGD("ADPF Oboe %s(dur=%lld)", __func__, (long long)actualDurationNanos);
std::lock_guard<std::mutex> lock(mLock);
if (mHintSession != nullptr) {
bool traceEnabled = Trace::getInstance().isEnabled();
if (traceEnabled) {
Trace::getInstance().beginSection("reportActualDuration");
Trace::getInstance().setCounter("actualDurationNanos", actualDurationNanos);
}
gAPH_reportActualWorkDurationFn(mHintSession, actualDurationNanos);
if (traceEnabled) {
Trace::getInstance().endSection();
}
}
}
void AdpfWrapper::close() {
std::lock_guard<std::mutex> lock(mLock);
if (mHintSession != nullptr) {
gAPH_closeSessionFn(mHintSession);
mHintSession = nullptr;
}
}
void AdpfWrapper::onBeginCallback() {
if (isOpen()) {
mBeginCallbackNanos = oboe::AudioClock::getNanoseconds();
}
}
void AdpfWrapper::onEndCallback(double durationScaler) {
if (isOpen()) {
int64_t endCallbackNanos = oboe::AudioClock::getNanoseconds();
int64_t actualDurationNanos = endCallbackNanos - mBeginCallbackNanos;
int64_t scaledDurationNanos = static_cast<int64_t>(actualDurationNanos * durationScaler);
reportActualDuration(scaledDurationNanos);
// When the workload is non-zero, update the conversion factor from workload
// units to nanoseconds duration.
if (mPreviousWorkload > 0) {
mNanosPerWorkloadUnit = ((double) scaledDurationNanos) / mPreviousWorkload;
}
}
}
void AdpfWrapper::reportWorkload(int32_t appWorkload) {
if (isOpen()) {
// Compare with previous workload. If we think we will need more
// time to render the callback then warn ADPF as soon as possible.
if (appWorkload > mPreviousWorkload && mNanosPerWorkloadUnit > 0.0) {
int64_t predictedDuration = (int64_t) (appWorkload * mNanosPerWorkloadUnit);
reportActualDuration(predictedDuration);
}
mPreviousWorkload = appWorkload;
}
}
oboe::Result AdpfWrapper::notifyWorkloadIncrease(bool cpu, bool gpu, const char* debugName) {
std::lock_guard<std::mutex> lock(mLock);
bool traceEnabled = Trace::getInstance().isEnabled();
if (traceEnabled) {
Trace::getInstance().beginSection("notifyWorkloadIncrease");
}
if (gAPH_notifyWorkloadIncreaseFn == nullptr) {
return Result::ErrorUnimplemented;
}
if (mHintSession == nullptr) {
return Result::ErrorClosed;
}
int result = gAPH_notifyWorkloadIncreaseFn(mHintSession, cpu, gpu, debugName);
if (result == 0) {
return Result::OK;
} else if (result == EINVAL) { // no hints were requested
return Result::ErrorInvalidHandle;
} else if (result == EBUSY) { // the hint was rate limited
return Result::ErrorInvalidRate;
} else if (result == EPIPE) { // communication with the system service has failed
return Result::ErrorNoService;
} else if (result == ENOTSUP) { // the hint is not supported
return Result::ErrorUnavailable;
} else {
return Result::ErrorInternal; // Unknown error
}
if (traceEnabled) {
Trace::getInstance().endSection();
}
}
oboe::Result AdpfWrapper::notifyWorkloadSpike(bool cpu, bool gpu, const char* debugName) {
std::lock_guard<std::mutex> lock(mLock);
bool traceEnabled = Trace::getInstance().isEnabled();
if (traceEnabled) {
Trace::getInstance().beginSection("notifyWorkloadSpike");
}
if (gAPH_notifyWorkloadSpikeFn == nullptr) {
return Result::ErrorUnimplemented;
}
if (mHintSession == nullptr) {
return Result::ErrorClosed;
}
int result = gAPH_notifyWorkloadSpikeFn(mHintSession, cpu, gpu, debugName);
if (result == 0) {
return Result::OK;
} else if (result == EINVAL) { // no hints were requested
return Result::ErrorInvalidHandle;
} else if (result == EBUSY) { // the hint was rate limited
return Result::ErrorInvalidRate;
} else if (result == EPIPE) { // communication with the system service has failed
return Result::ErrorNoService;
} else if (result == ENOTSUP) { // the hint is not supported
return Result::ErrorUnavailable;
} else {
return Result::ErrorInternal; // Unknown error
}
if (traceEnabled) {
Trace::getInstance().endSection();
}
}
oboe::Result AdpfWrapper::notifyWorkloadReset(bool cpu, bool gpu, const char* debugName) {
std::lock_guard<std::mutex> lock(mLock);
bool traceEnabled = Trace::getInstance().isEnabled();
if (traceEnabled) {
Trace::getInstance().beginSection("notifyWorkloadReset");
}
if (gAPH_notifyWorkloadResetFn == nullptr) {
return Result::ErrorUnimplemented;
}
if (mHintSession == nullptr) {
return Result::ErrorClosed;
}
int result = gAPH_notifyWorkloadResetFn(mHintSession, cpu, gpu, debugName);
if (result == 0) {
return Result::OK;
} else if (result == EINVAL) { // no hints were requested
return Result::ErrorInvalidHandle;
} else if (result == EBUSY) { // the hint was rate limited
return Result::ErrorInvalidRate;
} else if (result == EPIPE) { // communication with the system service has failed
return Result::ErrorNoService;
} else if (result == ENOTSUP) { // the hint is not supported
return Result::ErrorUnavailable;
} else {
return Result::ErrorInternal; // Unknown error
}
if (traceEnabled) {
Trace::getInstance().endSection();
}
}