| /* |
| * Copyright (C) 2024 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #define LOG_TAG "Spatializer_Test" |
| |
| #include "Spatializer.h" |
| |
| #include <string> |
| #include <unordered_set> |
| |
| #include <gtest/gtest.h> |
| |
| #include <android/media/audio/common/AudioLatencyMode.h> |
| #include <android/media/audio/common/HeadTracking.h> |
| #include <android/media/audio/common/Spatialization.h> |
| #include <com_android_media_audio.h> |
| #include <utils/Log.h> |
| |
| using namespace android; |
| using media::audio::common::HeadTracking; |
| using media::audio::common::Spatialization; |
| |
| // Test Spatializer Helper Methods |
| |
| TEST(Spatializer, containsImmersiveChannelMask) { |
| // Regardless of the implementation, we expect the following |
| // behavior. |
| |
| // Pure non-immersive |
| EXPECT_FALSE(Spatializer::containsImmersiveChannelMask( |
| { AUDIO_CHANNEL_OUT_STEREO })); |
| EXPECT_FALSE(Spatializer::containsImmersiveChannelMask( |
| { AUDIO_CHANNEL_OUT_MONO, AUDIO_CHANNEL_OUT_STEREO })); |
| EXPECT_FALSE(Spatializer::containsImmersiveChannelMask( |
| { AUDIO_CHANNEL_OUT_MONO, AUDIO_CHANNEL_OUT_STEREO, |
| AUDIO_CHANNEL_OUT_STEREO, AUDIO_CHANNEL_OUT_MONO })); |
| |
| // Pure immersive |
| EXPECT_TRUE(Spatializer::containsImmersiveChannelMask( |
| { AUDIO_CHANNEL_OUT_5POINT1 })); |
| EXPECT_TRUE(Spatializer::containsImmersiveChannelMask( |
| { AUDIO_CHANNEL_OUT_7POINT1 })); |
| EXPECT_TRUE(Spatializer::containsImmersiveChannelMask( |
| { AUDIO_CHANNEL_OUT_5POINT1, AUDIO_CHANNEL_OUT_7POINT1, |
| AUDIO_CHANNEL_OUT_22POINT2 })); |
| |
| // Mixed immersive/non-immersive |
| EXPECT_TRUE(Spatializer::containsImmersiveChannelMask( |
| { AUDIO_CHANNEL_OUT_MONO, AUDIO_CHANNEL_OUT_7POINT1POINT4 })); |
| EXPECT_TRUE(Spatializer::containsImmersiveChannelMask( |
| { AUDIO_CHANNEL_OUT_MONO, AUDIO_CHANNEL_OUT_STEREO, |
| AUDIO_CHANNEL_OUT_7POINT1 })); |
| } |
| |
| class TestSpatializerPolicyCallback : |
| public SpatializerPolicyCallback { |
| public: |
| void onCheckSpatializer() override {}; |
| }; |
| |
| class SpatializerTest : public ::testing::Test { |
| protected: |
| void SetUp() override { |
| const sp<EffectsFactoryHalInterface> effectsFactoryHal |
| = EffectsFactoryHalInterface::create(); |
| mSpatializer = Spatializer::create(&mTestCallback, effectsFactoryHal); |
| if (mSpatializer == nullptr) { |
| GTEST_SKIP() << "Skipping Spatializer tests: no spatializer"; |
| } |
| std::vector<Spatialization::Level> levels; |
| binder::Status status = mSpatializer->getSupportedLevels(&levels); |
| ASSERT_TRUE(status.isOk()); |
| for (auto level : levels) { |
| if (level != Spatialization::Level::NONE) { |
| mSpatializer->setLevel(level); |
| break; |
| } |
| } |
| mSpatializer->setOutput(sTestOutput); |
| } |
| |
| void TearDown() override { |
| if (mSpatializer == nullptr) { |
| return; |
| } |
| mSpatializer->setLevel(Spatialization::Level::NONE); |
| mSpatializer->setOutput(AUDIO_IO_HANDLE_NONE); |
| mSpatializer->setDesiredHeadTrackingMode(HeadTracking::Mode::DISABLED); |
| mSpatializer->setHeadSensor(SpatializerPoseController::INVALID_SENSOR); |
| mSpatializer->updateActiveTracks({}); |
| } |
| |
| static constexpr audio_io_handle_t sTestOutput= 1977; |
| static constexpr int sTestSensorHandle = 1980; |
| |
| const static inline std::vector<audio_latency_mode_t> sA2DPLatencyModes = { |
| AUDIO_LATENCY_MODE_LOW, |
| AUDIO_LATENCY_MODE_FREE |
| }; |
| const static inline std::vector<audio_latency_mode_t> sBLELatencyModes = { |
| AUDIO_LATENCY_MODE_LOW, |
| AUDIO_LATENCY_MODE_DYNAMIC_SPATIAL_AUDIO_SOFTWARE, |
| AUDIO_LATENCY_MODE_DYNAMIC_SPATIAL_AUDIO_HARDWARE, |
| AUDIO_LATENCY_MODE_FREE |
| }; |
| |
| bool setpUpForHeadtracking() { |
| bool htSupported; |
| mSpatializer->isHeadTrackingSupported(&htSupported); |
| if (!htSupported) { |
| return false; |
| } |
| |
| std::vector<HeadTracking::Mode> htModes; |
| mSpatializer->getSupportedHeadTrackingModes(&htModes); |
| for (auto htMode : htModes) { |
| if (htMode != HeadTracking::Mode::DISABLED) { |
| mSpatializer->setDesiredHeadTrackingMode(htMode); |
| break; |
| } |
| } |
| |
| mSpatializer->setHeadSensor(sTestSensorHandle); |
| return true; |
| } |
| |
| TestSpatializerPolicyCallback mTestCallback; |
| sp<Spatializer> mSpatializer; |
| }; |
| |
| TEST_F(SpatializerTest, SupportedA2dpLatencyTest) { |
| if (!setpUpForHeadtracking()) { |
| GTEST_SKIP() << "Skipping SupportedA2dpLatencyTest: head tracking not supported"; |
| } |
| std::vector<audio_latency_mode_t> latencies = sA2DPLatencyModes; |
| mSpatializer->onSupportedLatencyModesChangedMsg(sTestOutput, std::move(latencies)); |
| |
| std::vector<audio_latency_mode_t> supportedLatencies = |
| mSpatializer->getSupportedLatencyModes(); |
| |
| ASSERT_TRUE(supportedLatencies == sA2DPLatencyModes); |
| // Free mode must always be the last of the ordered list |
| ASSERT_TRUE(supportedLatencies.back() == AUDIO_LATENCY_MODE_FREE); |
| } |
| |
| TEST_F(SpatializerTest, SupportedBleLatencyTest) { |
| if (!setpUpForHeadtracking()) { |
| GTEST_SKIP() << "Skipping SupportedBleLatencyTest: head tracking not supported"; |
| } |
| if (!com::android::media::audio::dsa_over_bt_le_audio()) { |
| GTEST_SKIP() << "Skipping SupportedBleLatencyTest: DSA over LE not enabled"; |
| } |
| std::vector<audio_latency_mode_t> latencies = sBLELatencyModes; |
| mSpatializer->onSupportedLatencyModesChangedMsg(sTestOutput, std::move(latencies)); |
| |
| std::vector<audio_latency_mode_t> supportedLatencies = |
| mSpatializer->getSupportedLatencyModes(); |
| |
| ASSERT_TRUE(supportedLatencies.back() == AUDIO_LATENCY_MODE_FREE); |
| ASSERT_TRUE(std::find(supportedLatencies.begin(), supportedLatencies.end(), |
| AUDIO_LATENCY_MODE_LOW) != supportedLatencies.end()); |
| |
| std::vector<audio_latency_mode_t> orderedLowLatencyModes = |
| mSpatializer->getOrderedLowLatencyModes(); |
| |
| std::vector<audio_latency_mode_t> supportedLowLatencyModes; |
| // remove free mode at the end of the supported list to only retain low latency modes |
| std::copy(supportedLatencies.begin(), |
| supportedLatencies.begin() + supportedLatencies.size() - 1, |
| std::back_inserter(supportedLowLatencyModes)); |
| |
| // Verify that supported low latency modes are always in ordered latency modes list and |
| // in the same order |
| std::vector<audio_latency_mode_t>::iterator lastIt = orderedLowLatencyModes.begin(); |
| for (auto latency : supportedLowLatencyModes) { |
| auto it = std::find(orderedLowLatencyModes.begin(), orderedLowLatencyModes.end(), latency); |
| ASSERT_NE(it, orderedLowLatencyModes.end()); |
| ASSERT_LE(lastIt, it); |
| lastIt = it; |
| } |
| } |
| |
| TEST_F(SpatializerTest, RequestedA2dpLatencyTest) { |
| if (!setpUpForHeadtracking()) { |
| GTEST_SKIP() << "Skipping RequestedA2dpLatencyTest: head tracking not supported"; |
| } |
| |
| std::vector<audio_latency_mode_t> latencies = sA2DPLatencyModes; |
| mSpatializer->onSupportedLatencyModesChangedMsg(sTestOutput, std::move(latencies)); |
| |
| // requested latency mode must be free if no spatialized tracks are active |
| audio_latency_mode_t requestedLatencyMode = mSpatializer->getRequestedLatencyMode(); |
| ASSERT_EQ(requestedLatencyMode, AUDIO_LATENCY_MODE_FREE); |
| |
| // requested latency mode must be low if at least one spatialized tracks is active |
| mSpatializer->updateActiveTracks({AUDIO_CHANNEL_OUT_5POINT1}); |
| requestedLatencyMode = mSpatializer->getRequestedLatencyMode(); |
| ASSERT_EQ(requestedLatencyMode, AUDIO_LATENCY_MODE_LOW); |
| |
| // requested latency mode must be free after stopping the last spatialized tracks |
| mSpatializer->updateActiveTracks({}); |
| requestedLatencyMode = mSpatializer->getRequestedLatencyMode(); |
| ASSERT_EQ(requestedLatencyMode, AUDIO_LATENCY_MODE_FREE); |
| } |
| |
| TEST_F(SpatializerTest, RequestedBleLatencyTest) { |
| if (!setpUpForHeadtracking()) { |
| GTEST_SKIP() << "Skipping RequestedBleLatencyTest: head tracking not supported"; |
| } |
| if (!com::android::media::audio::dsa_over_bt_le_audio()) { |
| GTEST_SKIP() << "Skipping RequestedBleLatencyTest: DSA over LE not enabled"; |
| } |
| |
| mSpatializer->onSupportedLatencyModesChangedMsg(sTestOutput, |
| { AUDIO_LATENCY_MODE_DYNAMIC_SPATIAL_AUDIO_SOFTWARE, |
| AUDIO_LATENCY_MODE_FREE }); |
| |
| // requested latency mode must be free if no spatialized tracks are active |
| audio_latency_mode_t requestedLatencyMode = mSpatializer->getRequestedLatencyMode(); |
| ASSERT_EQ(requestedLatencyMode, AUDIO_LATENCY_MODE_FREE); |
| |
| // requested latency mode must be low software if at least one spatialized tracks is active |
| // and the only supported low latency mode is low software |
| mSpatializer->updateActiveTracks({AUDIO_CHANNEL_OUT_5POINT1}); |
| requestedLatencyMode = mSpatializer->getRequestedLatencyMode(); |
| ASSERT_EQ(requestedLatencyMode, AUDIO_LATENCY_MODE_DYNAMIC_SPATIAL_AUDIO_SOFTWARE); |
| |
| mSpatializer->onSupportedLatencyModesChangedMsg(sTestOutput, |
| { AUDIO_LATENCY_MODE_DYNAMIC_SPATIAL_AUDIO_SOFTWARE, |
| AUDIO_LATENCY_MODE_DYNAMIC_SPATIAL_AUDIO_HARDWARE, |
| AUDIO_LATENCY_MODE_FREE }); |
| |
| requestedLatencyMode = mSpatializer->getRequestedLatencyMode(); |
| HeadTracking::ConnectionMode connectionMode = mSpatializer->getHeadtrackingConnectionMode(); |
| |
| // If low hardware mode is used, the spatializer must use either use one of the sensor |
| // connection tunneled modes. |
| // Otherwise, low software mode must be used |
| if (requestedLatencyMode == AUDIO_LATENCY_MODE_DYNAMIC_SPATIAL_AUDIO_HARDWARE) { |
| ASSERT_TRUE(connectionMode == HeadTracking::ConnectionMode::DIRECT_TO_SENSOR_TUNNEL |
| || connectionMode == HeadTracking::ConnectionMode::DIRECT_TO_SENSOR_SW); |
| } else { |
| ASSERT_EQ(requestedLatencyMode, AUDIO_LATENCY_MODE_DYNAMIC_SPATIAL_AUDIO_SOFTWARE); |
| } |
| |
| // requested latency mode must be free after stopping the last spatialized tracks |
| mSpatializer->updateActiveTracks({}); |
| requestedLatencyMode = mSpatializer->getRequestedLatencyMode(); |
| ASSERT_EQ(requestedLatencyMode, AUDIO_LATENCY_MODE_FREE); |
| } |