Merge changes Ie494e03f,Ia85d5699,I7f5a39a0
* changes:
Tune head pose processing parameters
Force "not still" for the duration of the window after motion
Consider the screen still until proven otherwise
diff --git a/media/libheadtracking/HeadTrackingProcessor.cpp b/media/libheadtracking/HeadTrackingProcessor.cpp
index 257ee42..b2d2dbd 100644
--- a/media/libheadtracking/HeadTrackingProcessor.cpp
+++ b/media/libheadtracking/HeadTrackingProcessor.cpp
@@ -42,11 +42,13 @@
.rotationalDriftTimeConstant = options.rotationalDriftTimeConstant,
}),
mHeadStillnessDetector(StillnessDetector::Options{
+ .defaultValue = false,
.windowDuration = options.autoRecenterWindowDuration,
.translationalThreshold = options.autoRecenterTranslationalThreshold,
.rotationalThreshold = options.autoRecenterRotationalThreshold,
}),
mScreenStillnessDetector(StillnessDetector::Options{
+ .defaultValue = true,
.windowDuration = options.screenStillnessWindowDuration,
.translationalThreshold = options.screenStillnessTranslationalThreshold,
.rotationalThreshold = options.screenStillnessRotationalThreshold,
diff --git a/media/libheadtracking/StillnessDetector-test.cpp b/media/libheadtracking/StillnessDetector-test.cpp
index 29b036e..02f9d8a 100644
--- a/media/libheadtracking/StillnessDetector-test.cpp
+++ b/media/libheadtracking/StillnessDetector-test.cpp
@@ -28,101 +28,146 @@
using Eigen::Vector3f;
using Options = StillnessDetector::Options;
-TEST(StillnessDetectorTest, Still) {
- StillnessDetector detector(Options{
- .windowDuration = 1000, .translationalThreshold = 1, .rotationalThreshold = 0.05});
+class StillnessDetectorTest : public testing::TestWithParam<bool> {
+ public:
+ void SetUp() override { mDefaultValue = GetParam(); }
+
+ protected:
+ bool mDefaultValue;
+};
+
+TEST_P(StillnessDetectorTest, Still) {
+ StillnessDetector detector(Options{.defaultValue = mDefaultValue,
+ .windowDuration = 1000,
+ .translationalThreshold = 1,
+ .rotationalThreshold = 0.05});
const Pose3f baseline(Vector3f{1, 2, 3}, Quaternionf::UnitRandom());
const Pose3f withinThreshold =
baseline * Pose3f(Vector3f(0.3, -0.3, 0), rotateX(0.01) * rotateY(-0.01));
- EXPECT_FALSE(detector.calculate(0));
+ EXPECT_EQ(mDefaultValue, detector.calculate(0));
detector.setInput(0, baseline);
- EXPECT_FALSE(detector.calculate(0));
+ EXPECT_EQ(mDefaultValue, detector.calculate(0));
detector.setInput(300, withinThreshold);
- EXPECT_FALSE(detector.calculate(300));
+ EXPECT_EQ(mDefaultValue, detector.calculate(300));
detector.setInput(600, baseline);
- EXPECT_FALSE(detector.calculate(600));
+ EXPECT_EQ(mDefaultValue, detector.calculate(600));
detector.setInput(999, withinThreshold);
- EXPECT_FALSE(detector.calculate(999));
+ EXPECT_EQ(mDefaultValue, detector.calculate(999));
detector.setInput(1000, baseline);
EXPECT_TRUE(detector.calculate(1000));
}
-TEST(StillnessDetectorTest, ZeroDuration) {
- StillnessDetector detector(Options{.windowDuration = 0});
+TEST_P(StillnessDetectorTest, ZeroDuration) {
+ StillnessDetector detector(Options{.defaultValue = mDefaultValue, .windowDuration = 0});
EXPECT_TRUE(detector.calculate(0));
EXPECT_TRUE(detector.calculate(1000));
}
-TEST(StillnessDetectorTest, NotStillTranslation) {
- StillnessDetector detector(Options{
- .windowDuration = 1000, .translationalThreshold = 1, .rotationalThreshold = 0.05});
+TEST_P(StillnessDetectorTest, NotStillTranslation) {
+ StillnessDetector detector(Options{.defaultValue = mDefaultValue,
+ .windowDuration = 1000,
+ .translationalThreshold = 1,
+ .rotationalThreshold = 0.05});
const Pose3f baseline(Vector3f{1, 2, 3}, Quaternionf::UnitRandom());
const Pose3f withinThreshold =
baseline * Pose3f(Vector3f(0.3, -0.3, 0), rotateX(0.01) * rotateY(-0.01));
const Pose3f outsideThreshold = baseline * Pose3f(Vector3f(1, 1, 0));
- EXPECT_FALSE(detector.calculate(0));
+ EXPECT_EQ(mDefaultValue, detector.calculate(0));
detector.setInput(0, baseline);
- EXPECT_FALSE(detector.calculate(0));
+ EXPECT_EQ(mDefaultValue, detector.calculate(0));
detector.setInput(300, outsideThreshold);
- EXPECT_FALSE(detector.calculate(300));
+ EXPECT_EQ(mDefaultValue, detector.calculate(300));
detector.setInput(600, baseline);
- EXPECT_FALSE(detector.calculate(600));
+ EXPECT_EQ(mDefaultValue, detector.calculate(600));
detector.setInput(900, withinThreshold);
- EXPECT_FALSE(detector.calculate(900));
- detector.setInput(1299, baseline);
- EXPECT_FALSE(detector.calculate(1299));
- EXPECT_TRUE(detector.calculate(1300));
+ EXPECT_EQ(mDefaultValue, detector.calculate(900));
+ detector.setInput(1300, baseline);
+ EXPECT_FALSE(detector.calculate(1300));
+ detector.setInput(1500, baseline);
+ EXPECT_FALSE(detector.calculate(1899));
+ EXPECT_TRUE(detector.calculate(1900));
}
-TEST(StillnessDetectorTest, NotStillRotation) {
- StillnessDetector detector(Options{
- .windowDuration = 1000, .translationalThreshold = 1, .rotationalThreshold = 0.05});
+TEST_P(StillnessDetectorTest, NotStillRotation) {
+ StillnessDetector detector(Options{.defaultValue = mDefaultValue,
+ .windowDuration = 1000,
+ .translationalThreshold = 1,
+ .rotationalThreshold = 0.05});
const Pose3f baseline(Vector3f{1, 2, 3}, Quaternionf::UnitRandom());
const Pose3f withinThreshold =
baseline * Pose3f(Vector3f(0.3, -0.3, 0), rotateX(0.03) * rotateY(-0.03));
const Pose3f outsideThreshold = baseline * Pose3f(rotateZ(0.06));
- EXPECT_FALSE(detector.calculate(0));
+
+ EXPECT_EQ(mDefaultValue, detector.calculate(0));
detector.setInput(0, baseline);
- EXPECT_FALSE(detector.calculate(0));
+ EXPECT_EQ(mDefaultValue, detector.calculate(0));
detector.setInput(300, outsideThreshold);
- EXPECT_FALSE(detector.calculate(300));
+ EXPECT_EQ(mDefaultValue, detector.calculate(300));
detector.setInput(600, baseline);
- EXPECT_FALSE(detector.calculate(600));
+ EXPECT_EQ(mDefaultValue, detector.calculate(600));
detector.setInput(900, withinThreshold);
- EXPECT_FALSE(detector.calculate(900));
- detector.setInput(1299, baseline);
- EXPECT_FALSE(detector.calculate(1299));
- EXPECT_TRUE(detector.calculate(1300));
+ EXPECT_EQ(mDefaultValue, detector.calculate(900));
+ detector.setInput(1300, baseline);
+ EXPECT_FALSE(detector.calculate(1300));
+ detector.setInput(1500, baseline);
+ EXPECT_FALSE(detector.calculate(1899));
+ EXPECT_TRUE(detector.calculate(1900));
}
-TEST(StillnessDetectorTest, Reset) {
- StillnessDetector detector(Options{
- .windowDuration = 1000, .translationalThreshold = 1, .rotationalThreshold = 0.05});
+TEST_P(StillnessDetectorTest, Suppression) {
+ StillnessDetector detector(Options{.defaultValue = mDefaultValue,
+ .windowDuration = 1000,
+ .translationalThreshold = 1,
+ .rotationalThreshold = 0.05});
+
+ const Pose3f baseline(Vector3f{1, 2, 3}, Quaternionf::UnitRandom());
+ const Pose3f outsideThreshold = baseline * Pose3f(Vector3f(1.1, 0, 0));
+ const Pose3f middlePoint = baseline * Pose3f(Vector3f(0.55, 0, 0));
+
+ detector.setInput(0, baseline);
+ detector.setInput(1000, baseline);
+ EXPECT_TRUE(detector.calculate(1000));
+ detector.setInput(1100, outsideThreshold);
+ EXPECT_FALSE(detector.calculate(1100));
+ detector.setInput(2000, middlePoint);
+ EXPECT_FALSE(detector.calculate(2000));
+ EXPECT_FALSE(detector.calculate(2099));
+ EXPECT_TRUE(detector.calculate(2100));
+}
+
+TEST_P(StillnessDetectorTest, Reset) {
+ StillnessDetector detector(Options{.defaultValue = mDefaultValue,
+ .windowDuration = 1000,
+ .translationalThreshold = 1,
+ .rotationalThreshold = 0.05});
const Pose3f baseline(Vector3f{1, 2, 3}, Quaternionf::UnitRandom());
const Pose3f withinThreshold =
baseline * Pose3f(Vector3f(0.3, -0.3, 0), rotateX(0.01) * rotateY(-0.01));
- EXPECT_FALSE(detector.calculate(0));
+ EXPECT_EQ(mDefaultValue, detector.calculate(0));
detector.setInput(0, baseline);
- EXPECT_FALSE(detector.calculate(0));
+ EXPECT_EQ(mDefaultValue, detector.calculate(0));
detector.reset();
detector.setInput(600, baseline);
- EXPECT_FALSE(detector.calculate(600));
+ EXPECT_EQ(mDefaultValue, detector.calculate(600));
detector.setInput(900, withinThreshold);
- EXPECT_FALSE(detector.calculate(900));
+ EXPECT_EQ(mDefaultValue, detector.calculate(900));
detector.setInput(1200, baseline);
- EXPECT_FALSE(detector.calculate(1200));
+ EXPECT_EQ(mDefaultValue, detector.calculate(1200));
detector.setInput(1599, withinThreshold);
- EXPECT_FALSE(detector.calculate(1599));
+ EXPECT_EQ(mDefaultValue, detector.calculate(1599));
detector.setInput(1600, baseline);
EXPECT_TRUE(detector.calculate(1600));
}
+INSTANTIATE_TEST_SUITE_P(StillnessDetectorTestParametrized, StillnessDetectorTest,
+ testing::Values(false, true));
+
} // namespace
} // namespace media
} // namespace android
diff --git a/media/libheadtracking/StillnessDetector.cpp b/media/libheadtracking/StillnessDetector.cpp
index 5fa4e3a..9806352 100644
--- a/media/libheadtracking/StillnessDetector.cpp
+++ b/media/libheadtracking/StillnessDetector.cpp
@@ -25,6 +25,7 @@
void StillnessDetector::reset() {
mFifo.clear();
mWindowFull = false;
+ mSuppressionDeadline.reset();
}
void StillnessDetector::setInput(int64_t timestamp, const Pose3f& input) {
@@ -35,27 +36,34 @@
bool StillnessDetector::calculate(int64_t timestamp) {
discardOld(timestamp);
- // If the window has not been full, we don't consider ourselves still.
- if (!mWindowFull) {
- return false;
- }
+ // Check whether all the poses in the queue are in the proximity of the new
+ // one. We want to do this before checking the overriding conditions below, in order to update
+ // the suppression deadline correctly.
+ bool moved = false;
- // An empty FIFO and window full is considered still (this will happen in the unlikely case when
- // the window duration is shorter than the gap between samples).
- if (mFifo.empty()) {
- return true;
- }
-
- // Otherwise, check whether all the poses remaining in the queue are in the proximity of the new
- // one.
- for (auto iter = mFifo.begin(); iter != mFifo.end() - 1; ++iter) {
- const auto& event = *iter;
- if (!areNear(event.pose, mFifo.back().pose)) {
- return false;
+ if (!mFifo.empty()) {
+ for (auto iter = mFifo.begin(); iter != mFifo.end() - 1; ++iter) {
+ const auto& event = *iter;
+ if (!areNear(event.pose, mFifo.back().pose)) {
+ // Enable suppression for the duration of the window.
+ mSuppressionDeadline = timestamp + mOptions.windowDuration;
+ moved = true;
+ break;
+ }
}
}
- return true;
+ // If the window has not been full, return the default value.
+ if (!mWindowFull) {
+ return mOptions.defaultValue;
+ }
+
+ // Force "in motion" while the suppression deadline is active.
+ if (mSuppressionDeadline.has_value()) {
+ return false;
+ }
+
+ return !moved;
}
void StillnessDetector::discardOld(int64_t timestamp) {
@@ -72,6 +80,11 @@
mWindowFull = true;
mFifo.pop_front();
}
+
+ // Expire the suppression deadline.
+ if (mSuppressionDeadline.has_value() && mSuppressionDeadline <= timestamp) {
+ mSuppressionDeadline.reset();
+ }
}
bool StillnessDetector::areNear(const Pose3f& pose1, const Pose3f& pose2) const {
diff --git a/media/libheadtracking/StillnessDetector.h b/media/libheadtracking/StillnessDetector.h
index cae9d9d..ee4b2d8 100644
--- a/media/libheadtracking/StillnessDetector.h
+++ b/media/libheadtracking/StillnessDetector.h
@@ -36,10 +36,11 @@
* bool still = detector.calculate(timestamp);
* }
*
- * The stream is considered not still until a sufficient number of samples has been provided for an
- * initial fill-up of the window. In the special case of the window size being 0, this is not
- * required and the state is considered always "still". The reset() method can be used to empty the
- * window again and get back to this initial state.
+ * The detection is not considered reliable until a sufficient number of samples has been provided
+ * for an initial fill-up of the window. During that time, the detector will return whatever default
+ * value has been configured.
+ * The reset() method can be used to empty the window again and get back to this initial state.
+ * In the special case of the window size being 0, the state will always be considered "still".
*/
class StillnessDetector {
public:
@@ -48,6 +49,10 @@
*/
struct Options {
/**
+ * During the initial fill of the window, should we consider the state still?
+ */
+ bool defaultValue;
+ /**
* How long is the window, in ticks. The special value of 0 indicates that the stream is
* always considered still.
*/
@@ -87,6 +92,11 @@
const float mCosHalfRotationalThreshold;
std::deque<TimestampedPose> mFifo;
bool mWindowFull = false;
+ // As soon as motion is detected, this will be set for the time of detection + window duration,
+ // and during this time we will always consider outselves in motion without checking. This is
+ // used for hyteresis purposes, since because of the approximate method we use for determining
+ // stillness, we may toggle back and forth at a rate faster than the window side.
+ std::optional<int64_t> mSuppressionDeadline;
bool areNear(const Pose3f& pose1, const Pose3f& pose2) const;
void discardOld(int64_t timestamp);
diff --git a/services/audiopolicy/service/SpatializerPoseController.cpp b/services/audiopolicy/service/SpatializerPoseController.cpp
index 80a3d29..cb017f0 100644
--- a/services/audiopolicy/service/SpatializerPoseController.cpp
+++ b/services/audiopolicy/service/SpatializerPoseController.cpp
@@ -38,7 +38,7 @@
constexpr auto kMaxTranslationalVelocity = 2;
// This is how fast, in rad/s, we allow rotation angle to shift during rate-limiting.
-constexpr auto kMaxRotationalVelocity = 4 * M_PI;
+constexpr auto kMaxRotationalVelocity = 8;
// This should be set to the typical time scale that the translation sensors used drift in. This
// means, loosely, for how long we can trust the reading to be "accurate enough". This would
@@ -65,23 +65,23 @@
constexpr auto kMaxLostSamples = 4;
// Auto-recenter kicks in after the head has been still for this long.
-constexpr auto kAutoRecenterWindowDuration = 10s;
+constexpr auto kAutoRecenterWindowDuration = 6s;
// Auto-recenter considers head not still if translated by this much (in meters, approx).
constexpr float kAutoRecenterTranslationThreshold = 0.1f;
// Auto-recenter considers head not still if rotated by this much (in radians, approx).
-constexpr float kAutoRecenterRotationThreshold = 5.0f / 180 * M_PI;
+constexpr float kAutoRecenterRotationThreshold = 7.0f / 180 * M_PI;
// Screen is considered to be unstable (not still) if it has moved significantly within the last
// time window of this duration.
-constexpr auto kScreenStillnessWindowDuration = 10s;
+constexpr auto kScreenStillnessWindowDuration = 3s;
// Screen is considered to have moved significantly if translated by this much (in meter, approx).
constexpr float kScreenStillnessTranslationThreshold = 0.1f;
// Screen is considered to have moved significantly if rotated by this much (in radians, approx).
-constexpr float kScreenStillnessRotationThreshold = 5.0f / 180 * M_PI;
+constexpr float kScreenStillnessRotationThreshold = 7.0f / 180 * M_PI;
// Time units for system clock ticks. This is what the Sensor Framework timestamps represent and
// what we use for pose filtering.