Spatial Audio: Log head tracking velocity in degrees / s

Head tracking velocity is given as the intrinsic axis * (dangle / dt).
We express this as degrees/s with d_pitch, d_roll, d_yaw.

Cleanup of the VectorRecorder index offset and add delimiters.

Test: adb shell dumpsys media.audio_policy
Bug: 270763710
Change-Id: I64a194a9b839567529c0359c2ebe3a78d89e7c61
diff --git a/media/libheadtracking/VectorRecorder.cpp b/media/libheadtracking/VectorRecorder.cpp
index 5d0588e..5c87d05 100644
--- a/media/libheadtracking/VectorRecorder.cpp
+++ b/media/libheadtracking/VectorRecorder.cpp
@@ -21,7 +21,7 @@
 // Convert data to string with level indentation.
 // No need for a lock as the SimpleLog is thread-safe.
 std::string VectorRecorder::toString(size_t indent) const {
-    return mRecordLog.dumpToString(std::string(indent + 1, ' ').c_str(), mMaxLocalLogLine);
+    return mRecordLog.dumpToString(std::string(indent, ' ').c_str(), mMaxLocalLogLine);
 }
 
 // Record into local log when it is time.
@@ -36,9 +36,9 @@
         sumToAverage_l();
         mRecordLog.log(
                 "mean: %s, min: %s, max %s, calculated %zu samples in %0.4f second(s)",
-                toString(mSum).c_str(),
-                toString(mMin).c_str(),
-                toString(mMax).c_str(),
+                toString(mSum, mDelimiterIdx, mFormatString.c_str()).c_str(),
+                toString(mMin, mDelimiterIdx, mFormatString.c_str()).c_str(),
+                toString(mMax, mDelimiterIdx, mFormatString.c_str()).c_str(),
                 mNumberOfSamples,
                 mNumberOfSecondsSinceFirstSample.count());
         resetRecord_l();
diff --git a/media/libheadtracking/include/media/VectorRecorder.h b/media/libheadtracking/include/media/VectorRecorder.h
index 1fb7521..4103a7d 100644
--- a/media/libheadtracking/include/media/VectorRecorder.h
+++ b/media/libheadtracking/include/media/VectorRecorder.h
@@ -34,9 +34,25 @@
  */
 class VectorRecorder {
   public:
+    /**
+     * @param vectorSize is the size of the vector input.
+     *        If the input does not match this size, it is ignored.
+     * @param threshold is the time interval we bucket for averaging.
+     * @param maxLogLine is the number of lines we log.  At this
+     *        threshold, the oldest line will expire when the new line comes in.
+     * @param delimiterIdx is an optional array of delimiter indices that
+     *        replace the ',' with a ':'.  For example if delimiterIdx = { 3 } then
+     *        the above example would format as [0.00, 0.00, 0.00 : -1.29, -0.50, 15.27].
+     * @param formatString is the sprintf format string for the double converted data
+     *        to use.
+     */
     VectorRecorder(
-        size_t vectorSize, std::chrono::duration<double> threshold, int maxLogLine)
+        size_t vectorSize, std::chrono::duration<double> threshold, int maxLogLine,
+            std::vector<size_t> delimiterIdx = {},
+            const std::string_view formatString = {})
         : mVectorSize(vectorSize)
+        , mDelimiterIdx(std::move(delimiterIdx))
+        , mFormatString(formatString)
         , mRecordLog(maxLogLine)
         , mRecordThreshold(threshold)
     {
@@ -55,19 +71,38 @@
 
     /**
      * Format vector to a string, [0.00, 0.00, 0.00, -1.29, -0.50, 15.27].
+     *
+     * @param delimiterIdx is an optional array of delimiter indices that
+     *        replace the ',' with a ':'.  For example if delimiterIdx = { 3 } then
+     *        the above example would format as [0.00, 0.00, 0.00 : -1.29, -0.50, 15.27].
+     * @param formatString is the sprintf format string for the double converted data
+     *        to use.
      */
     template <typename T>
-    static std::string toString(const std::vector<T>& record) {
+    static std::string toString(const std::vector<T>& record,
+            const std::vector<size_t>& delimiterIdx = {},
+            const char * const formatString = nullptr) {
         if (record.size() == 0) {
             return "[]";
         }
 
         std::string ss = "[";
+        auto nextDelimiter = delimiterIdx.begin();
         for (size_t i = 0; i < record.size(); ++i) {
             if (i > 0) {
-                ss.append(", ");
+                if (nextDelimiter != delimiterIdx.end()
+                        && *nextDelimiter <= i) {
+                     ss.append(" : ");
+                     ++nextDelimiter;
+                } else {
+                    ss.append(", ");
+                }
             }
-            base::StringAppendF(&ss, "%0.2lf", static_cast<double>(record[i]));
+            if (formatString != nullptr && *formatString) {
+                base::StringAppendF(&ss, formatString, static_cast<double>(record[i]));
+            } else {
+                base::StringAppendF(&ss, "%5.2lf", static_cast<double>(record[i]));
+            }
         }
         ss.append("]");
         return ss;
@@ -77,6 +112,8 @@
     static constexpr int mMaxLocalLogLine = 10;
 
     const size_t mVectorSize;
+    const std::vector<size_t> mDelimiterIdx;
+    const std::string mFormatString;
 
     // Local log for historical vector data.
     // Locked internally, so does not need mutex below.
diff --git a/services/audiopolicy/service/Spatializer.cpp b/services/audiopolicy/service/Spatializer.cpp
index 2f65f39..d868d31 100644
--- a/services/audiopolicy/service/Spatializer.cpp
+++ b/services/audiopolicy/service/Spatializer.cpp
@@ -1092,13 +1092,13 @@
     if (mPoseController != nullptr) {
         ss.append(mPoseController->toString(level + 1))
             .append(prefixSpace)
-            .append("Pose (active stage-to-head) [tx, ty, tz, pitch, roll, yaw]:\n")
+            .append("Pose (active stage-to-head) [tx, ty, tz : pitch, roll, yaw]:\n")
             .append(prefixSpace)
             .append(" PerMinuteHistory:\n")
-            .append(mPoseDurableRecorder.toString(level + 2))
+            .append(mPoseDurableRecorder.toString(level + 3))
             .append(prefixSpace)
             .append(" PerSecondHistory:\n")
-            .append(mPoseRecorder.toString(level + 2));
+            .append(mPoseRecorder.toString(level + 3));
     } else {
         ss.append(prefixSpace).append("SpatializerPoseController not exist\n");
     }
diff --git a/services/audiopolicy/service/Spatializer.h b/services/audiopolicy/service/Spatializer.h
index f03d764..c677868 100644
--- a/services/audiopolicy/service/Spatializer.h
+++ b/services/audiopolicy/service/Spatializer.h
@@ -404,10 +404,10 @@
      */
     // Record one log line per second (up to mMaxLocalLogLine) to capture most recent sensor data.
     media::VectorRecorder mPoseRecorder GUARDED_BY(mLock) {
-        6 /* vectorSize */, std::chrono::seconds(1), mMaxLocalLogLine };
+        6 /* vectorSize */, std::chrono::seconds(1), mMaxLocalLogLine, { 3 } /* delimiterIdx */};
     // Record one log line per minute (up to mMaxLocalLogLine) to capture durable sensor data.
     media::VectorRecorder mPoseDurableRecorder  GUARDED_BY(mLock) {
-        6 /* vectorSize */, std::chrono::minutes(1), mMaxLocalLogLine };
+        6 /* vectorSize */, std::chrono::minutes(1), mMaxLocalLogLine, { 3 } /* delimiterIdx */};
 };  // Spatializer
 
 }; // namespace android
diff --git a/services/audiopolicy/service/SpatializerPoseController.cpp b/services/audiopolicy/service/SpatializerPoseController.cpp
index 2ac2af7..032572e 100644
--- a/services/audiopolicy/service/SpatializerPoseController.cpp
+++ b/services/audiopolicy/service/SpatializerPoseController.cpp
@@ -290,22 +290,29 @@
     const float delayMs = (elapsedRealtimeNano() - timestamp) * NANOS_TO_MILLIS; // CLOCK_BOOTTIME
 
     if (sensor == mHeadSensor) {
-        std::vector<float> pryxyzdt(8);  // pitch, roll, yaw, rot_vel_x, rot_vel_y, rot_vel_z,
+        std::vector<float> pryprydt(8);  // pitch, roll, yaw, d_pitch, d_roll, d_yaw,
                                          // discontinuity, timestamp_delay
-        media::quaternionToAngles(pose.rotation(), &pryxyzdt[0], &pryxyzdt[1], &pryxyzdt[2]);
+        media::quaternionToAngles(pose.rotation(), &pryprydt[0], &pryprydt[1], &pryprydt[2]);
         if (twist) {
             const auto rotationalVelocity = twist->rotationalVelocity();
-            for (size_t i = 0; i < 3; ++i) {
-                pryxyzdt[i + 3] = rotationalVelocity[i];
-            }
+            // The rotational velocity is an intrinsic transform (i.e. based on the head
+            // coordinate system, not the world coordinate system).  It is a 3 element vector:
+            // axis (d theta / dt).
+            //
+            // We leave rotational velocity relative to the head coordinate system,
+            // as the initial head tracking sensor's world frame is arbitrary.
+            media::quaternionToAngles(media::rotationVectorToQuaternion(rotationalVelocity),
+                    &pryprydt[3], &pryprydt[4], &pryprydt[5]);
         }
-        pryxyzdt[6] = isNewReference;
-        pryxyzdt[7] = delayMs;
-        for (size_t i = 0; i < 3; ++i) { // pitch, roll, yaw only.  rotational velocity in rad/s.
-            pryxyzdt[i] *= RAD_TO_DEGREE;
+        pryprydt[6] = isNewReference;
+        pryprydt[7] = delayMs;
+        for (size_t i = 0; i < 6; ++i) {
+            // pitch, roll, yaw in degrees, referenced in degrees on the world frame.
+            // d_pitch, d_roll, d_yaw rotational velocity in degrees/s, based on the world frame.
+            pryprydt[i] *= RAD_TO_DEGREE;
         }
-        mHeadSensorRecorder.record(pryxyzdt);
-        mHeadSensorDurableRecorder.record(pryxyzdt);
+        mHeadSensorRecorder.record(pryprydt);
+        mHeadSensorDurableRecorder.record(pryprydt);
 
         mProcessor->setWorldToHeadPose(timestamp, pose,
                                        twist.value_or(Twist3f()) / kTicksPerSecond);
@@ -346,15 +353,16 @@
     if (mHeadSensor == INVALID_SENSOR) {
         ss += "HeadSensor: INVALID\n";
     } else {
-        base::StringAppendF(&ss, "HeadSensor: 0x%08x (active world-to-head) "
-            "[ pitch, roll, yaw, vx, vy, vz, disc, delay ] "
-            "(degrees, rad/s, bool, ms)\n", mHeadSensor);
+        base::StringAppendF(&ss, "HeadSensor: 0x%08x "
+            "(active world-to-head : head-relative velocity) "
+            "[ pitch, roll, yaw : d_pitch, d_roll, d_yaw : disc : delay ] "
+            "(degrees, degrees/s, bool, ms)\n", mHeadSensor);
         ss.append(prefixSpace)
             .append(" PerMinuteHistory:\n")
-            .append(mHeadSensorDurableRecorder.toString(level + 2))
+            .append(mHeadSensorDurableRecorder.toString(level + 3))
             .append(prefixSpace)
             .append(" PerSecondHistory:\n")
-            .append(mHeadSensorRecorder.toString(level + 2));
+            .append(mHeadSensorRecorder.toString(level + 3));
     }
 
     ss += prefixSpace;
@@ -362,14 +370,14 @@
         ss += "ScreenSensor: INVALID\n";
     } else {
         base::StringAppendF(&ss, "ScreenSensor: 0x%08x (active world-to-screen) "
-            "[ pitch, roll, yaw, delay ] "
+            "[ pitch, roll, yaw : delay ] "
             "(degrees, ms)\n", mScreenSensor);
         ss.append(prefixSpace)
             .append(" PerMinuteHistory:\n")
-            .append(mScreenSensorDurableRecorder.toString(level + 2))
+            .append(mScreenSensorDurableRecorder.toString(level + 3))
             .append(prefixSpace)
             .append(" PerSecondHistory:\n")
-            .append(mScreenSensorRecorder.toString(level + 2));
+            .append(mScreenSensorRecorder.toString(level + 3));
     }
 
     ss += prefixSpace;
diff --git a/services/audiopolicy/service/SpatializerPoseController.h b/services/audiopolicy/service/SpatializerPoseController.h
index ee2c2be..9d78188 100644
--- a/services/audiopolicy/service/SpatializerPoseController.h
+++ b/services/audiopolicy/service/SpatializerPoseController.h
@@ -133,14 +133,18 @@
     bool mCalculated = false;
 
     media::VectorRecorder mHeadSensorRecorder{
-        8 /* vectorSize */, std::chrono::seconds(1), 10 /* maxLogLine */};
+        8 /* vectorSize */, std::chrono::seconds(1), 10 /* maxLogLine */,
+        { 3, 6, 7 } /* delimiterIdx */};
     media::VectorRecorder mHeadSensorDurableRecorder{
-        8 /* vectorSize */, std::chrono::minutes(1), 10 /* maxLogLine */};
+        8 /* vectorSize */, std::chrono::minutes(1), 10 /* maxLogLine */,
+        { 3, 6, 7 } /* delimiterIdx */};
 
     media::VectorRecorder mScreenSensorRecorder{
-        4 /* vectorSize */, std::chrono::seconds(1), 10 /* maxLogLine */};
+        4 /* vectorSize */, std::chrono::seconds(1), 10 /* maxLogLine */,
+        { 3 } /* delimiterIdx */};
     media::VectorRecorder mScreenSensorDurableRecorder{
-        4 /* vectorSize */, std::chrono::minutes(1), 10 /* maxLogLine */};
+        4 /* vectorSize */, std::chrono::minutes(1), 10 /* maxLogLine */,
+        { 3 } /* delimiterIdx */};
 
     // It's important that mThread is the last variable in this class
     // since we starts mThread in initializer list