VolumeShaper: Add AudioTrack restore
MediaPlayer VolumeShaper can now be set before start().
Test: CTS and Ducking
Bug: 31015569
Change-Id: Idf63c167e164161b200e2467fbeb9409b3097dbe
diff --git a/include/media/VolumeShaper.h b/include/media/VolumeShaper.h
index 1421ae7..f5a74d8 100644
--- a/include/media/VolumeShaper.h
+++ b/include/media/VolumeShaper.h
@@ -17,6 +17,7 @@
#ifndef ANDROID_VOLUME_SHAPER_H
#define ANDROID_VOLUME_SHAPER_H
+#include <cmath>
#include <list>
#include <math.h>
#include <sstream>
@@ -289,18 +290,28 @@
FLAG_TERMINATE = (1 << 1),
FLAG_JOIN = (1 << 2),
FLAG_DELAY = (1 << 3),
+ FLAG_CREATE_IF_NECESSARY = (1 << 4),
- FLAG_ALL = (FLAG_REVERSE | FLAG_TERMINATE | FLAG_JOIN | FLAG_DELAY),
+ FLAG_ALL = (FLAG_REVERSE | FLAG_TERMINATE | FLAG_JOIN | FLAG_DELAY
+ | FLAG_CREATE_IF_NECESSARY),
};
Operation()
- : mFlags(FLAG_NONE)
- , mReplaceId(-1) {
+ : Operation(FLAG_NONE, -1 /* replaceId */) {
}
explicit Operation(Flag flags, int replaceId)
+ : Operation(flags, replaceId, std::numeric_limits<S>::quiet_NaN() /* xOffset */) {
+ }
+
+ Operation(const Operation &operation)
+ : Operation(operation.mFlags, operation.mReplaceId, operation.mXOffset) {
+ }
+
+ explicit Operation(Flag flags, int replaceId, S xOffset)
: mFlags(flags)
- , mReplaceId(replaceId) {
+ , mReplaceId(replaceId)
+ , mXOffset(xOffset) {
}
int32_t getReplaceId() const {
@@ -311,6 +322,14 @@
mReplaceId = replaceId;
}
+ S getXOffset() const {
+ return mXOffset;
+ }
+
+ void setXOffset(S xOffset) {
+ mXOffset = xOffset;
+ }
+
Flag getFlags() const {
return mFlags;
}
@@ -327,13 +346,15 @@
status_t writeToParcel(Parcel *parcel) const {
if (parcel == nullptr) return BAD_VALUE;
return parcel->writeInt32((int32_t)mFlags)
- ?: parcel->writeInt32(mReplaceId);
+ ?: parcel->writeInt32(mReplaceId)
+ ?: parcel->writeFloat(mXOffset);
}
status_t readFromParcel(const Parcel &parcel) {
int32_t flags;
return parcel.readInt32(&flags)
?: parcel.readInt32(&mReplaceId)
+ ?: parcel.readFloat(&mXOffset)
?: setFlags((Flag)flags);
}
@@ -341,12 +362,14 @@
std::stringstream ss;
ss << "mFlags: " << mFlags << std::endl;
ss << "mReplaceId: " << mReplaceId << std::endl;
+ ss << "mXOffset: " << mXOffset << std::endl;
return ss.str();
}
private:
Flag mFlags;
int32_t mReplaceId;
+ S mXOffset;
}; // Operation
// must match with VolumeShaper.java in frameworks/base
@@ -454,14 +477,6 @@
return convertTimespecToUs(tv);
}
- Translate<S> mXTranslate;
- Translate<T> mYTranslate;
- sp<VolumeShaper::Configuration> mConfiguration;
- sp<VolumeShaper::Operation> mOperation;
- int64_t mStartFrame;
- T mLastVolume;
- S mXOffset;
-
// TODO: Since we pass configuration and operation as shared pointers
// there is a potential risk that the caller may modify these after
// delivery. Currently, we don't require copies made here.
@@ -472,7 +487,8 @@
, mOperation(operation) // ditto
, mStartFrame(-1)
, mLastVolume(T(1))
- , mXOffset(0.f) {
+ , mLastXOffset(0.f)
+ , mDelayXOffset(std::numeric_limits<S>::quiet_NaN()) {
if (configuration.get() != nullptr
&& (getFlags() & VolumeShaper::Operation::FLAG_DELAY) == 0) {
mLastVolume = configuration->first().second;
@@ -484,9 +500,11 @@
/ (mConfiguration->getDurationMs() * 0.001 * sampleRate);
const double minScale = 1. / INT64_MAX;
scale = std::max(scale, minScale);
- VS_LOG("update position: scale %lf frameCount:%lld, sampleRate:%lf",
- scale, (long long) startFrame, sampleRate);
- mXTranslate.setOffset(startFrame - mConfiguration->first().first / scale);
+ const S xOffset = std::isnan(mDelayXOffset) ? mConfiguration->first().first : mDelayXOffset;
+ VS_LOG("update position: scale %lf frameCount:%lld, sampleRate:%lf, xOffset:%f",
+ scale, (long long) startFrame, sampleRate, xOffset);
+
+ mXTranslate.setOffset(startFrame - xOffset / scale);
mXTranslate.setScale(scale);
VS_LOG("translate: %s", mXTranslate.toString().c_str());
}
@@ -498,7 +516,11 @@
}
sp<VolumeShaper::State> getState() const {
- return new VolumeShaper::State(mLastVolume, mXOffset);
+ return new VolumeShaper::State(mLastVolume, mLastXOffset);
+ }
+
+ void setDelayXOffset(S xOffset) {
+ mDelayXOffset = xOffset;
}
std::pair<T /* volume */, bool /* active */> getVolume(
@@ -506,7 +528,7 @@
if ((getFlags() & VolumeShaper::Operation::FLAG_DELAY) != 0) {
VS_LOG("delayed VolumeShaper, ignoring");
mLastVolume = T(1);
- mXOffset = 0.;
+ mLastXOffset = 0.;
return std::make_pair(T(1), false);
}
const bool clockTime = (mConfiguration->getOptionFlags()
@@ -527,7 +549,7 @@
x = 1.f - x;
VS_LOG("reversing to %f", x);
if (x < mConfiguration->first().first) {
- mXOffset = 1.f;
+ mLastXOffset = 1.f;
const T volume = mConfiguration->adjustVolume(
mConfiguration->first().second); // persist last value
VS_LOG("persisting volume %f", volume);
@@ -535,18 +557,18 @@
return std::make_pair(volume, false);
}
if (x > mConfiguration->last().first) {
- mXOffset = 0.f;
+ mLastXOffset = 0.f;
mLastVolume = 1.f;
return std::make_pair(T(1), true); // too early
}
} else {
if (x < mConfiguration->first().first) {
- mXOffset = 0.f;
+ mLastXOffset = 0.f;
mLastVolume = 1.f;
return std::make_pair(T(1), true); // too early
}
if (x > mConfiguration->last().first) {
- mXOffset = 1.f;
+ mLastXOffset = 1.f;
const T volume = mConfiguration->adjustVolume(
mConfiguration->last().second); // persist last value
VS_LOG("persisting volume %f", volume);
@@ -554,11 +576,10 @@
return std::make_pair(volume, false);
}
}
- mXOffset = x;
+ mLastXOffset = x;
// x contains the location on the volume curve to use.
const T unscaledVolume = mConfiguration->findY(x);
- const T volumeChange = mYTranslate(unscaledVolume);
- const T volume = mConfiguration->adjustVolume(volumeChange);
+ const T volume = mConfiguration->adjustVolume(unscaledVolume); // handle log scale
VS_LOG("volume: %f unscaled: %f", volume, unscaledVolume);
mLastVolume = volume;
return std::make_pair(volume, true);
@@ -568,7 +589,6 @@
std::stringstream ss;
ss << "StartFrame: " << mStartFrame << std::endl;
ss << mXTranslate.toString().c_str();
- ss << mYTranslate.toString().c_str();
if (mConfiguration.get() == nullptr) {
ss << "VolumeShaper::Configuration: nullptr" << std::endl;
} else {
@@ -583,6 +603,14 @@
}
return ss.str();
}
+
+ Translate<S> mXTranslate; // x axis translation from frames (in usec for clock time)
+ sp<VolumeShaper::Configuration> mConfiguration;
+ sp<VolumeShaper::Operation> mOperation;
+ int64_t mStartFrame; // starting frame, non-negative when started (in usec for clock time)
+ T mLastVolume; // last computed interpolated volume (y-axis)
+ S mLastXOffset; // last computed interpolated xOffset/time (x-axis)
+ S mDelayXOffset; // delay xOffset on first volumeshaper start.
}; // VolumeShaper
// VolumeHandler combines the volume factors of multiple VolumeShapers and handles
@@ -592,9 +620,15 @@
using S = float;
using T = float;
+ // A volume handler which just keeps track of active VolumeShapers does not need sampleRate.
+ VolumeHandler()
+ : VolumeHandler(0 /* sampleRate */) {
+ }
+
explicit VolumeHandler(uint32_t sampleRate)
: mSampleRate((double)sampleRate)
- , mLastFrame(0) {
+ , mLastFrame(0)
+ , mVolumeShaperIdCounter(VolumeShaper::kSystemIdMax) {
}
VolumeShaper::Status applyVolumeShaper(
@@ -638,10 +672,16 @@
}
(void)mVolumeShapers.erase(replaceIt);
}
+ operation->setReplaceId(-1);
}
// check if we have another of the same id.
auto oldIt = findId_l(id);
if (oldIt != mVolumeShapers.end()) {
+ if ((operation->getFlags()
+ & VolumeShaper::Operation::FLAG_CREATE_IF_NECESSARY) != 0) {
+ // TODO: move the case to a separate function.
+ goto HANDLE_TYPE_ID; // no need to create, take over existing id.
+ }
ALOGW("duplicate id, removing old %d", id);
(void)mVolumeShapers.erase(oldIt);
}
@@ -649,6 +689,7 @@
mVolumeShapers.emplace_back(configuration, operation);
}
// fall through to handle the operation
+ HANDLE_TYPE_ID:
case VolumeShaper::Configuration::TYPE_ID: {
VS_LOG("trying to find id: %d", id);
auto it = findId_l(id);
@@ -678,6 +719,17 @@
it->mXTranslate.setOffset(it->mXTranslate.getOffset()
+ (x - target) / it->mXTranslate.getScale());
}
+ const S xOffset = operation->getXOffset();
+ if (!std::isnan(xOffset)) {
+ const int64_t frameCount = clockTime ? VolumeShaper::getNowUs() : mLastFrame;
+ const S x = it->mXTranslate((T)frameCount);
+ VS_LOG("xOffset translation: %f", x);
+ const S target = xOffset; // offset
+ VS_LOG("xOffset target x offset: %f", target);
+ it->mXTranslate.setOffset(it->mXTranslate.getOffset()
+ + (x - target) / it->mXTranslate.getScale());
+ it->setDelayXOffset(xOffset);
+ }
it->mOperation = operation; // replace the operation
} break;
}
@@ -688,6 +740,7 @@
AutoMutex _l(mLock);
auto it = findId_l(id);
if (it == mVolumeShapers.end()) {
+ VS_LOG("cannot find state for id: %d", id);
return nullptr;
}
return it->getState();
@@ -719,6 +772,48 @@
return ss.str();
}
+ void forall(const std::function<VolumeShaper::Status (
+ const sp<VolumeShaper::Configuration> &configuration,
+ const sp<VolumeShaper::Operation> &operation)> &lambda) {
+ AutoMutex _l(mLock);
+ for (const auto &shaper : mVolumeShapers) {
+ VS_LOG("forall applying lambda");
+ (void)lambda(shaper.mConfiguration, shaper.mOperation);
+ }
+ }
+
+ void reset() {
+ AutoMutex _l(mLock);
+ mVolumeShapers.clear();
+ mLastFrame = -1;
+ // keep mVolumeShaperIdCounter as is.
+ }
+
+ // Sets the configuration id if necessary - This is based on the counter
+ // internal to the VolumeHandler.
+ void setIdIfNecessary(const sp<VolumeShaper::Configuration> &configuration) {
+ if (configuration->getType() == VolumeShaper::Configuration::TYPE_SCALE) {
+ const int id = configuration->getId();
+ if (id == -1) {
+ // Reassign to a unique id, skipping system ids.
+ AutoMutex _l(mLock);
+ while (true) {
+ if (mVolumeShaperIdCounter == INT32_MAX) {
+ mVolumeShaperIdCounter = VolumeShaper::kSystemIdMax;
+ } else {
+ ++mVolumeShaperIdCounter;
+ }
+ if (findId_l(mVolumeShaperIdCounter) != mVolumeShapers.end()) {
+ continue; // collision with an existing id.
+ }
+ configuration->setId(mVolumeShaperIdCounter);
+ ALOGD("setting id to %d", mVolumeShaperIdCounter);
+ break;
+ }
+ }
+ }
+ }
+
private:
std::list<VolumeShaper>::iterator findId_l(int32_t id) {
std::list<VolumeShaper>::iterator it = mVolumeShapers.begin();
@@ -733,6 +828,7 @@
mutable Mutex mLock;
double mSampleRate; // in samples (frames) per second
int64_t mLastFrame; // logging purpose only
+ int32_t mVolumeShaperIdCounter; // a counter to return a unique volume shaper id.
std::list<VolumeShaper> mVolumeShapers; // list provides stable iterators on erase
}; // VolumeHandler
diff --git a/media/libaudioclient/AudioTrack.cpp b/media/libaudioclient/AudioTrack.cpp
index d35cfe3..f878be9 100644
--- a/media/libaudioclient/AudioTrack.cpp
+++ b/media/libaudioclient/AudioTrack.cpp
@@ -184,7 +184,6 @@
mPreviousSchedulingGroup(SP_DEFAULT),
mPausedPosition(0),
mSelectedDeviceId(AUDIO_PORT_HANDLE_NONE),
- mVolumeShaperId(VolumeShaper::kSystemIdMax),
mPortId(AUDIO_PORT_HANDLE_NONE)
{
mAttributes.content_type = AUDIO_CONTENT_TYPE_UNKNOWN;
@@ -557,7 +556,7 @@
mFramesWritten = 0;
mFramesWrittenServerOffset = 0;
mFramesWrittenAtRestore = -1; // -1 is a unique initializer.
- mVolumeHandler = new VolumeHandler(mSampleRate);
+ mVolumeHandler = new VolumeHandler();
return NO_ERROR;
}
@@ -2251,6 +2250,20 @@
}
}
}
+ // restore volume handler
+ mVolumeHandler->forall([this](const sp<VolumeShaper::Configuration> &configuration,
+ const sp<VolumeShaper::Operation> &operation) -> VolumeShaper::Status {
+ sp<VolumeShaper::Operation> operationToEnd = new VolumeShaper::Operation(*operation);
+ // TODO: Ideally we would restore to the exact xOffset position
+ // as returned by getVolumeShaperState(), but we don't have that
+ // information when restoring at the client unless we periodically poll
+ // the server or create shared memory state.
+ //
+ // For now, we simply advance to the end of the VolumeShaper effect.
+ operationToEnd->setXOffset(1.f);
+ return mAudioTrack->applyVolumeShaper(configuration, operationToEnd);
+ });
+
if (mState == STATE_ACTIVE) {
result = mAudioTrack->start();
}
@@ -2316,24 +2329,12 @@
const sp<VolumeShaper::Operation>& operation)
{
AutoMutex lock(mLock);
- if (configuration->getType() == VolumeShaper::Configuration::TYPE_SCALE) {
- const int id = configuration->getId();
- LOG_ALWAYS_FATAL_IF(id >= VolumeShaper::kSystemIdMax || id < -1,
- "id must be -1 or a system id (less than kSystemIdMax)");
- if (id == -1) {
- // if not a system id, reassign to a unique id
- configuration->setId(mVolumeShaperId);
- ALOGD("setting id to %d", mVolumeShaperId);
- // increment and avoid signed overflow.
- if (mVolumeShaperId == INT32_MAX) {
- mVolumeShaperId = VolumeShaper::kSystemIdMax;
- } else {
- ++mVolumeShaperId;
- }
- }
- }
+ mVolumeHandler->setIdIfNecessary(configuration);
VolumeShaper::Status status = mAudioTrack->applyVolumeShaper(configuration, operation);
- // TODO: For restoration purposes, record successful creation and termination.
+ if (status >= 0) {
+ // save VolumeShaper for restore
+ mVolumeHandler->applyVolumeShaper(configuration, operation);
+ }
return status;
}
diff --git a/media/libaudioclient/include/AudioTrack.h b/media/libaudioclient/include/AudioTrack.h
index 1996dbe..0358363 100644
--- a/media/libaudioclient/include/AudioTrack.h
+++ b/media/libaudioclient/include/AudioTrack.h
@@ -1128,8 +1128,6 @@
sp<VolumeHandler> mVolumeHandler;
- int32_t mVolumeShaperId;
-
private:
class DeathNotifier : public IBinder::DeathRecipient {
public:
diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp
index a8b6c66..f3fc924 100644
--- a/media/libmediaplayerservice/MediaPlayerService.cpp
+++ b/media/libmediaplayerservice/MediaPlayerService.cpp
@@ -1566,7 +1566,8 @@
mPid(pid),
mSendLevel(0.0),
mAuxEffectId(0),
- mFlags(AUDIO_OUTPUT_FLAG_NONE)
+ mFlags(AUDIO_OUTPUT_FLAG_NONE),
+ mVolumeHandler(new VolumeHandler())
{
ALOGV("AudioOutput(%d)", sessionId);
if (attr != NULL) {
@@ -2030,6 +2031,13 @@
ALOGV("setVolume");
t->setVolume(mLeftVolume, mRightVolume);
+ // Dispatch any queued VolumeShapers when the track was not open.
+ mVolumeHandler->forall([&t](const sp<VolumeShaper::Configuration> &configuration,
+ const sp<VolumeShaper::Operation> &operation) -> VolumeShaper::Status {
+ return t->applyVolumeShaper(configuration, operation);
+ });
+ mVolumeHandler->reset(); // After dispatching, clear VolumeShaper queue.
+
mSampleRateHz = sampleRate;
mFlags = flags;
mMsecsPerFrame = 1E3f / (mPlaybackRate.mSpeed * sampleRate);
@@ -2272,10 +2280,14 @@
{
Mutex::Autolock lock(mLock);
ALOGV("AudioOutput::applyVolumeShaper");
+
+ // We take ownership of the VolumeShaper if set before the track is created.
+ mVolumeHandler->setIdIfNecessary(configuration);
if (mTrack != 0) {
return mTrack->applyVolumeShaper(configuration, operation);
+ } else {
+ return mVolumeHandler->applyVolumeShaper(configuration, operation);
}
- return VolumeShaper::Status(INVALID_OPERATION);
}
sp<VolumeShaper::State> MediaPlayerService::AudioOutput::getVolumeShaperState(int id)
@@ -2283,8 +2295,9 @@
Mutex::Autolock lock(mLock);
if (mTrack != 0) {
return mTrack->getVolumeShaperState(id);
+ } else {
+ return mVolumeHandler->getVolumeShaperState(id);
}
- return nullptr;
}
// static
diff --git a/media/libmediaplayerservice/MediaPlayerService.h b/media/libmediaplayerservice/MediaPlayerService.h
index dff7322..009fe73 100644
--- a/media/libmediaplayerservice/MediaPlayerService.h
+++ b/media/libmediaplayerservice/MediaPlayerService.h
@@ -165,6 +165,7 @@
float mSendLevel;
int mAuxEffectId;
audio_output_flags_t mFlags;
+ sp<VolumeHandler> mVolumeHandler;
mutable Mutex mLock;
// static variables below not protected by mutex