AIDL effect: add draining state support
- effects can transit to DRAINING state with STOP command and continue
the fade out processing
- add draining in the audio eraser effect as an example
- update VTS to support draining
Flag: EXEMPT bugfix
Bug: 379776482
Test: --test-mapping hardware/interfaces/audio/aidl/vts:presubmit
Change-Id: I2ca25cd085d1b6ae6cf8b0d1b58cd713aef0f7e5
diff --git a/audio/aidl/default/EffectContext.cpp b/audio/aidl/default/EffectContext.cpp
index 26c88b2..b354dd1 100644
--- a/audio/aidl/default/EffectContext.cpp
+++ b/audio/aidl/default/EffectContext.cpp
@@ -258,4 +258,18 @@
return RetCode::SUCCESS;
}
+RetCode EffectContext::startDraining() {
+ mIsDraining = true;
+ return RetCode::SUCCESS;
+}
+
+RetCode EffectContext::finishDraining() {
+ mIsDraining = false;
+ return RetCode::SUCCESS;
+}
+
+bool EffectContext::isDraining() {
+ return mIsDraining;
+}
+
} // namespace aidl::android::hardware::audio::effect
diff --git a/audio/aidl/default/EffectImpl.cpp b/audio/aidl/default/EffectImpl.cpp
index 3e61335..7857f53 100644
--- a/audio/aidl/default/EffectImpl.cpp
+++ b/audio/aidl/default/EffectImpl.cpp
@@ -79,7 +79,6 @@
std::lock_guard lg(mImplMutex);
RETURN_IF(mState == State::INIT, EX_ILLEGAL_STATE, "alreadyClosed");
- // TODO: b/302036943 add reopen implementation
RETURN_IF(!mImplContext, EX_NULL_POINTER, "nullContext");
mImplContext->dupeFmqWithReopen(ret);
return ndk::ScopedAStatus::ok();
@@ -347,7 +346,7 @@
{
std::lock_guard lg(mImplMutex);
- if (mState != State::PROCESSING) {
+ if (mState != State::PROCESSING && mState != State::DRAINING) {
LOG(DEBUG) << getEffectNameWithVersion()
<< " skip process in state: " << toString(mState);
return;
diff --git a/audio/aidl/default/EffectThread.cpp b/audio/aidl/default/EffectThread.cpp
index b515385..1a52c13 100644
--- a/audio/aidl/default/EffectThread.cpp
+++ b/audio/aidl/default/EffectThread.cpp
@@ -68,7 +68,11 @@
RetCode EffectThread::startThread() {
{
std::lock_guard lg(mThreadMutex);
- mStop = false;
+ if (mDraining) {
+ mDraining = false;
+ } else {
+ mStop = false;
+ }
mCv.notify_one();
}
@@ -87,6 +91,25 @@
return RetCode::SUCCESS;
}
+RetCode EffectThread::startDraining() {
+ std::lock_guard lg(mThreadMutex);
+ mDraining = true;
+ mCv.notify_one();
+
+ LOG(VERBOSE) << mName << __func__;
+ return RetCode::SUCCESS;
+}
+
+RetCode EffectThread::finishDraining() {
+ std::lock_guard lg(mThreadMutex);
+ mDraining = false;
+ mStop = true;
+ mCv.notify_one();
+
+ LOG(VERBOSE) << mName << __func__;
+ return RetCode::SUCCESS;
+}
+
void EffectThread::threadLoop() {
pthread_setname_np(pthread_self(), mName.substr(0, kMaxTaskNameLen - 1).c_str());
setpriority(PRIO_PROCESS, 0, mPriority);
diff --git a/audio/aidl/default/audio_effects_config.xml b/audio/aidl/default/audio_effects_config.xml
index 2cef9eb..2e860d8 100644
--- a/audio/aidl/default/audio_effects_config.xml
+++ b/audio/aidl/default/audio_effects_config.xml
@@ -76,6 +76,7 @@
<effect name="bassboost" library="bundle" uuid="8631f300-72e2-11df-b57e-0002a5d5c51b"/>
<effect name="downmix" library="downmix" uuid="93f04452-e4fe-41cc-91f9-e475b6d1d69f"/>
<effect name="dynamics_processing" library="dynamics_processing" uuid="e0e6539b-1781-7261-676f-6d7573696340"/>
+ <effect name="eraser" library="erasersw" uuid="fa81ab46-588b-11ed-9b6a-0242ac120002"/>
<effect name="haptic_generator" library="haptic_generator" uuid="97c4acd1-8b82-4f2f-832e-c2fe5d7a9931"/>
<effect name="loudness_enhancer" library="loudness_enhancer" uuid="fa415329-2034-4bea-b5dc-5b381c8d1e2c"/>
<effect name="reverb_env_aux" library="reverb" uuid="4a387fc0-8ab3-11df-8bad-0002a5d5c51b"/>
diff --git a/audio/aidl/default/eraser/Eraser.cpp b/audio/aidl/default/eraser/Eraser.cpp
index 157ec79..59cc9a2 100644
--- a/audio/aidl/default/eraser/Eraser.cpp
+++ b/audio/aidl/default/eraser/Eraser.cpp
@@ -133,10 +133,70 @@
LOG(DEBUG) << __func__;
}
+ndk::ScopedAStatus EraserSw::command(CommandId command) {
+ std::lock_guard lg(mImplMutex);
+ RETURN_IF(mState == State::INIT, EX_ILLEGAL_STATE, "instanceNotOpen");
+
+ switch (command) {
+ case CommandId::START:
+ RETURN_OK_IF(mState == State::PROCESSING);
+ mState = State::PROCESSING;
+ mContext->enable();
+ startThread();
+ RETURN_IF(notifyEventFlag(mDataMqNotEmptyEf) != RetCode::SUCCESS, EX_ILLEGAL_STATE,
+ "notifyEventFlagNotEmptyFailed");
+ break;
+ case CommandId::STOP:
+ RETURN_OK_IF(mState == State::IDLE || mState == State::DRAINING);
+ if (mVersion < kDrainSupportedVersion) {
+ mState = State::IDLE;
+ stopThread();
+ mContext->disable();
+ } else {
+ mState = State::DRAINING;
+ startDraining();
+ mContext->startDraining();
+ }
+ RETURN_IF(notifyEventFlag(mDataMqNotEmptyEf) != RetCode::SUCCESS, EX_ILLEGAL_STATE,
+ "notifyEventFlagNotEmptyFailed");
+ break;
+ case CommandId::RESET:
+ mState = State::IDLE;
+ RETURN_IF(notifyEventFlag(mDataMqNotEmptyEf) != RetCode::SUCCESS, EX_ILLEGAL_STATE,
+ "notifyEventFlagNotEmptyFailed");
+ stopThread();
+ mImplContext->disable();
+ mImplContext->reset();
+ mImplContext->resetBuffer();
+ break;
+ default:
+ LOG(ERROR) << getEffectNameWithVersion() << __func__ << " instance still processing";
+ return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
+ "CommandIdNotSupported");
+ }
+ LOG(VERBOSE) << getEffectNameWithVersion() << __func__
+ << " transfer to state: " << toString(mState);
+ return ndk::ScopedAStatus::ok();
+}
+
// Processing method running in EffectWorker thread.
IEffect::Status EraserSw::effectProcessImpl(float* in, float* out, int samples) {
RETURN_VALUE_IF(!mContext, (IEffect::Status{EX_NULL_POINTER, 0, 0}), "nullContext");
- return mContext->process(in, out, samples);
+ IEffect::Status procStatus{STATUS_NOT_ENOUGH_DATA, 0, 0};
+ procStatus = mContext->process(in, out, samples);
+ if (mState == State::DRAINING && procStatus.status == STATUS_NOT_ENOUGH_DATA) {
+ drainingComplete_l();
+ }
+
+ return procStatus;
+}
+
+void EraserSw::drainingComplete_l() {
+ if (mState != State::DRAINING) return;
+
+ LOG(DEBUG) << getEffectNameWithVersion() << __func__;
+ finishDraining();
+ mState = State::IDLE;
}
EraserSwContext::EraserSwContext(int statusDepth, const Parameter::Common& common)
@@ -164,24 +224,47 @@
IEffect::Status EraserSwContext::process(float* in, float* out, int samples) {
LOG(DEBUG) << __func__ << " in " << in << " out " << out << " samples " << samples;
- IEffect::Status status = {EX_ILLEGAL_ARGUMENT, 0, 0};
-
+ IEffect::Status procStatus = {EX_ILLEGAL_ARGUMENT, 0, 0};
const auto inputChannelCount = getChannelCount(mCommon.input.base.channelMask);
const auto outputChannelCount = getChannelCount(mCommon.output.base.channelMask);
if (inputChannelCount < outputChannelCount) {
LOG(ERROR) << __func__ << " invalid channel count, in: " << inputChannelCount
<< " out: " << outputChannelCount;
- return status;
+ return procStatus;
}
- int iFrames = samples / inputChannelCount;
+ if (samples <= 0 || 0 != samples % inputChannelCount) {
+ LOG(ERROR) << __func__ << " invalid samples: " << samples;
+ return procStatus;
+ }
+
+ const int iFrames = samples / inputChannelCount;
+ const float gainPerSample = 1.f / iFrames;
for (int i = 0; i < iFrames; i++) {
- std::memcpy(out, in, outputChannelCount);
+ if (isDraining()) {
+ const float gain = (iFrames - i - 1) * gainPerSample;
+ for (size_t c = 0; c < outputChannelCount; c++) {
+ out[c] = in[c] * gain;
+ }
+ } else {
+ std::memcpy(out, in, outputChannelCount * sizeof(float));
+ }
+
in += inputChannelCount;
out += outputChannelCount;
}
- return {STATUS_OK, static_cast<int32_t>(iFrames * inputChannelCount),
- static_cast<int32_t>(iFrames * outputChannelCount)};
+
+ // drain for one cycle
+ if (isDraining()) {
+ procStatus.status = STATUS_NOT_ENOUGH_DATA;
+ finishDraining();
+ } else {
+ procStatus.status = STATUS_OK;
+ }
+ procStatus.fmqConsumed = static_cast<int32_t>(iFrames * inputChannelCount);
+ procStatus.fmqProduced = static_cast<int32_t>(iFrames * outputChannelCount);
+
+ return procStatus;
}
} // namespace aidl::android::hardware::audio::effect
diff --git a/audio/aidl/default/eraser/Eraser.h b/audio/aidl/default/eraser/Eraser.h
index 0d4eb8f..7bf2f57 100644
--- a/audio/aidl/default/eraser/Eraser.h
+++ b/audio/aidl/default/eraser/Eraser.h
@@ -63,6 +63,9 @@
IEffect::Status effectProcessImpl(float* in, float* out, int samples)
REQUIRES(mImplMutex) final;
+ ndk::ScopedAStatus command(CommandId command) final;
+ void drainingComplete_l() REQUIRES(mImplMutex);
+
private:
static const std::vector<Range::SpatializerRange> kRanges;
std::shared_ptr<EraserSwContext> mContext GUARDED_BY(mImplMutex);
diff --git a/audio/aidl/default/include/effect-impl/EffectContext.h b/audio/aidl/default/include/effect-impl/EffectContext.h
index 02a4caa..9e44349 100644
--- a/audio/aidl/default/include/effect-impl/EffectContext.h
+++ b/audio/aidl/default/include/effect-impl/EffectContext.h
@@ -86,7 +86,12 @@
virtual RetCode disable();
virtual RetCode reset();
+ virtual RetCode startDraining();
+ virtual RetCode finishDraining();
+ virtual bool isDraining();
+
protected:
+ bool mIsDraining = false;
int mVersion = 0;
size_t mInputFrameSize = 0;
size_t mOutputFrameSize = 0;
diff --git a/audio/aidl/default/include/effect-impl/EffectThread.h b/audio/aidl/default/include/effect-impl/EffectThread.h
index ec2a658..9abcdb8 100644
--- a/audio/aidl/default/include/effect-impl/EffectThread.h
+++ b/audio/aidl/default/include/effect-impl/EffectThread.h
@@ -38,6 +38,8 @@
RetCode destroyThread();
RetCode startThread();
RetCode stopThread();
+ RetCode startDraining();
+ RetCode finishDraining();
// Will call process() in a loop if the thread is running.
void threadLoop();
@@ -49,6 +51,9 @@
*/
virtual void process() = 0;
+ protected:
+ bool mDraining GUARDED_BY(mThreadMutex) = false;
+
private:
static constexpr int kMaxTaskNameLen = 15;