Merge "Improve VelocityTracker numerical stability."

This commit is contained in:
Jeff Brown 2011-03-15 20:01:16 -07:00 committed by Android (Google) Code Review
commit e3d4a68f2d
3 changed files with 145 additions and 35 deletions

View File

@ -311,6 +311,13 @@ public:
inline int32_t getAction() const { return mAction; }
inline int32_t getActionMasked() const { return mAction & AMOTION_EVENT_ACTION_MASK; }
inline int32_t getActionIndex() const {
return (mAction & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
>> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
}
inline void setAction(int32_t action) { mAction = action; }
inline int32_t getFlags() const { return mFlags; }
@ -458,6 +465,8 @@ public:
AMOTION_EVENT_AXIS_ORIENTATION, pointerIndex, historicalIndex);
}
ssize_t findPointerIndex(int32_t pointerId) const;
void initialize(
int32_t deviceId,
int32_t source,
@ -551,8 +560,7 @@ private:
};
/*
* Calculates the velocity of pointer motions over time.
* Uses essentially the same algorithm as android.view.VelocityTracker.
* Calculates the velocity of pointer movements over time.
*/
class VelocityTracker {
public:
@ -565,6 +573,11 @@ public:
// Resets the velocity tracker state.
void clear();
// Resets the velocity tracker state for specific pointers.
// Call this method when some pointers have changed and may be reusing
// an id that was assigned to a different pointer earlier.
void clearPointers(BitSet32 idBits);
// Adds movement information for a set of pointers.
// The idBits bitfield specifies the pointer ids of the pointers whose positions
// are included in the movement.
@ -572,11 +585,20 @@ public:
// increasing id. Its size should be equal to the number of one bits in idBits.
void addMovement(nsecs_t eventTime, BitSet32 idBits, const Position* positions);
// Adds movement information for all pointers in a MotionEvent, including historical samples.
void addMovement(const MotionEvent* event);
// Gets the velocity of the specified pointer id in position units per second.
// Returns false and sets the velocity components to zero if there is no movement
// information for the pointer.
bool getVelocity(uint32_t id, float* outVx, float* outVy) const;
// Gets the active pointer id, or -1 if none.
inline int32_t getActivePointerId() const { return mActivePointerId; }
// Gets a bitset containing all pointer ids from the most recent movement.
inline BitSet32 getCurrentPointerIdBits() const { return mMovements[mIndex].idBits; }
private:
// Number of samples to keep.
static const uint32_t HISTORY_SIZE = 10;
@ -585,7 +607,7 @@ private:
static const nsecs_t MAX_AGE = 200 * 1000000; // 200 ms
// The minimum duration between samples when estimating velocity.
static const nsecs_t MIN_DURATION = 5 * 1000000; // 5 ms
static const nsecs_t MIN_DURATION = 10 * 1000000; // 10 ms
struct Movement {
nsecs_t eventTime;
@ -595,6 +617,7 @@ private:
uint32_t mIndex;
Movement mMovements[HISTORY_SIZE];
int32_t mActivePointerId;
};
/*

View File

@ -61,6 +61,10 @@ struct BitSet32 {
// Result is undefined if all bits are marked.
inline uint32_t firstUnmarkedBit() const { return __builtin_clz(~ value); }
// Finds the last marked bit in the set.
// Result is undefined if all bits are unmarked.
inline uint32_t lastMarkedBit() const { return 31 - __builtin_ctz(value); }
// Gets the index of the specified bit in the set, which is the number of
// marked bits that appear before the specified bit.
inline uint32_t getIndexOfBit(uint32_t n) const {

View File

@ -469,6 +469,16 @@ float MotionEvent::getHistoricalAxisValue(int32_t axis, size_t pointerIndex,
return value;
}
ssize_t MotionEvent::findPointerIndex(int32_t pointerId) const {
size_t pointerCount = mPointerIds.size();
for (size_t i = 0; i < pointerCount; i++) {
if (mPointerIds.itemAt(i) == pointerId) {
return i;
}
}
return -1;
}
void MotionEvent::offsetLocation(float xOffset, float yOffset) {
mXOffset += xOffset;
mYOffset += yOffset;
@ -668,12 +678,27 @@ VelocityTracker::VelocityTracker() {
void VelocityTracker::clear() {
mIndex = 0;
mMovements[0].idBits.clear();
mActivePointerId = -1;
}
void VelocityTracker::clearPointers(BitSet32 idBits) {
BitSet32 remainingIdBits(mMovements[mIndex].idBits.value & ~idBits.value);
mMovements[mIndex].idBits = remainingIdBits;
if (mActivePointerId >= 0 && idBits.hasBit(mActivePointerId)) {
mActivePointerId = !remainingIdBits.isEmpty() ? remainingIdBits.firstMarkedBit() : -1;
}
}
void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits, const Position* positions) {
if (++mIndex == HISTORY_SIZE) {
mIndex = 0;
}
while (idBits.count() > MAX_POINTERS) {
idBits.clearBit(idBits.lastMarkedBit());
}
Movement& movement = mMovements[mIndex];
movement.eventTime = eventTime;
movement.idBits = idBits;
@ -682,8 +707,13 @@ void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits, const Posi
movement.positions[i] = positions[i];
}
if (mActivePointerId < 0 || !idBits.hasBit(mActivePointerId)) {
mActivePointerId = count != 0 ? idBits.firstMarkedBit() : -1;
}
#if DEBUG_VELOCITY
LOGD("VelocityTracker: addMovement eventTime=%lld, idBits=0x%08x", eventTime, idBits.value);
LOGD("VelocityTracker: addMovement eventTime=%lld, idBits=0x%08x, activePointerId=%d",
eventTime, idBits.value, mActivePointerId);
for (BitSet32 iterBits(idBits); !iterBits.isEmpty(); ) {
uint32_t id = iterBits.firstMarkedBit();
uint32_t index = idBits.getIndexOfBit(id);
@ -691,7 +721,7 @@ void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits, const Posi
float vx, vy;
bool available = getVelocity(id, &vx, &vy);
if (available) {
LOGD(" %d: position (%0.3f, %0.3f), velocity (%0.3f, %0.3f), speed %0.3f",
LOGD(" %d: position (%0.3f, %0.3f), vx=%0.3f, vy=%0.3f, speed=%0.3f",
id, positions[index].x, positions[index].y, vx, vy, sqrtf(vx * vx + vy * vy));
} else {
assert(vx == 0 && vy == 0);
@ -702,6 +732,70 @@ void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits, const Posi
#endif
}
void VelocityTracker::addMovement(const MotionEvent* event) {
int32_t actionMasked = event->getActionMasked();
switch (actionMasked) {
case AMOTION_EVENT_ACTION_DOWN:
// Clear all pointers on down before adding the new movement.
clear();
break;
case AMOTION_EVENT_ACTION_POINTER_DOWN: {
// Start a new movement trace for a pointer that just went down.
// We do this on down instead of on up because the client may want to query the
// final velocity for a pointer that just went up.
BitSet32 downIdBits;
downIdBits.markBit(event->getActionIndex());
clearPointers(downIdBits);
break;
}
case AMOTION_EVENT_ACTION_OUTSIDE:
case AMOTION_EVENT_ACTION_CANCEL:
case AMOTION_EVENT_ACTION_SCROLL:
case AMOTION_EVENT_ACTION_UP:
case AMOTION_EVENT_ACTION_POINTER_UP:
// Ignore these actions because they do not convey any new information about
// pointer movement. We also want to preserve the last known velocity of the pointers.
// Note that ACTION_UP and ACTION_POINTER_UP always report the last known position
// of the pointers that went up. ACTION_POINTER_UP does include the new position of
// pointers that remained down but we will also receive an ACTION_MOVE with this
// information if any of them actually moved. Since we don't know how many pointers
// will be going up at once it makes sense to just wait for the following ACTION_MOVE
// before adding the movement.
return;
}
size_t pointerCount = event->getPointerCount();
if (pointerCount > MAX_POINTERS) {
pointerCount = MAX_POINTERS;
}
BitSet32 idBits;
for (size_t i = 0; i < pointerCount; i++) {
idBits.markBit(event->getPointerId(i));
}
nsecs_t eventTime;
Position positions[pointerCount];
size_t historySize = event->getHistorySize();
for (size_t h = 0; h < historySize; h++) {
eventTime = event->getHistoricalEventTime(h);
for (size_t i = 0; i < pointerCount; i++) {
positions[i].x = event->getHistoricalX(i, h);
positions[i].y = event->getHistoricalY(i, h);
}
addMovement(eventTime, idBits, positions);
}
eventTime = event->getEventTime();
for (size_t i = 0; i < pointerCount; i++) {
positions[i].x = event->getX(i);
positions[i].y = event->getY(i);
}
addMovement(eventTime, idBits, positions);
}
bool VelocityTracker::getVelocity(uint32_t id, float* outVx, float* outVy) const {
const Movement& newestMovement = mMovements[mIndex];
if (newestMovement.idBits.hasBit(id)) {
@ -719,36 +813,17 @@ bool VelocityTracker::getVelocity(uint32_t id, float* outVx, float* outVy) const
oldestIndex = nextOldestIndex;
} while (++numTouches < HISTORY_SIZE);
// If we have a lot of samples, skip the last received sample since it is
// probably pretty noisy compared to the sum of all of the traces already acquired.
// Calculate an exponentially weighted moving average of the velocity estimate
// at different points in time measured relative to the oldest sample.
// This is essentially an IIR filter. Newer samples are weighted more heavily
// than older samples. Samples at equal time points are weighted more or less
// equally.
//
// NOTE: This condition exists in the android.view.VelocityTracker and imposes a
// bias against the most recent data.
if (numTouches > 3) {
numTouches -= 1;
}
// Calculate an exponentially weighted moving average of the velocity at different
// points in time measured relative to the oldest samples. This is essentially
// an IIR filter.
//
// One problem with this algorithm is that the sample data may be poorly conditioned.
// One tricky problem is that the sample data may be poorly conditioned.
// Sometimes samples arrive very close together in time which can cause us to
// overestimate the velocity at that time point. Most samples might be measured
// 16ms apart but some consecutive samples could be only 0.5sm apart due to
// the way they are reported by the hardware or driver (sometimes in bursts or with
// significant jitter). The instantaneous velocity for those samples 0.5ms apart will
// be calculated to be 32 times what it should have been.
// To work around this effect, we impose a minimum duration on the samples.
//
// FIXME: Samples close together in time can have an disproportionately large
// impact on the result because all samples are equally weighted. The average should
// instead take the time factor into account.
//
// FIXME: The minimum duration condition does not exist in
// android.view.VelocityTracker yet. It is less important there because sample times
// are truncated to the millisecond so back to back samples will often appear to be
// zero milliseconds apart and will be ignored if they are the oldest ones.
// 16ms apart but some consecutive samples could be only 0.5sm apart because
// the hardware or driver reports them irregularly or in bursts.
float accumVx = 0;
float accumVy = 0;
uint32_t index = oldestIndex;
@ -756,19 +831,27 @@ bool VelocityTracker::getVelocity(uint32_t id, float* outVx, float* outVy) const
const Movement& oldestMovement = mMovements[oldestIndex];
const Position& oldestPosition =
oldestMovement.positions[oldestMovement.idBits.getIndexOfBit(id)];
nsecs_t lastDuration = 0;
while (numTouches-- > 1) {
if (++index == HISTORY_SIZE) {
index = 0;
}
const Movement& movement = mMovements[index];
nsecs_t duration = movement.eventTime - oldestMovement.eventTime;
if (duration > MIN_DURATION) {
// If the duration between samples is small, we may significantly overestimate
// the velocity. Consequently, we impose a minimum duration constraint on the
// samples that we include in the calculation.
if (duration >= MIN_DURATION) {
const Position& position = movement.positions[movement.idBits.getIndexOfBit(id)];
float scale = 1000000000.0f / duration; // one over time delta in seconds
float vx = (position.x - oldestPosition.x) * scale;
float vy = (position.y - oldestPosition.y) * scale;
accumVx = accumVx == 0 ? vx : (accumVx + vx) * 0.5f;
accumVy = accumVy == 0 ? vy : (accumVy + vy) * 0.5f;
accumVx = (accumVx * lastDuration + vx * duration) / (duration + lastDuration);
accumVy = (accumVy * lastDuration + vy * duration) / (duration + lastDuration);
lastDuration = duration;
samplesUsed += 1;
}
}