blob: d783c647fe97e44336b9409b31aef6dd808af790 [file] [log] [blame]
/*
* Copyright 2022 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_NDEBUG 0
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <memory>
#include <string>
#include <utility>
#define LOG_TAG "EffectsFactoryHalInterfaceTest"
#include <aidl/android/media/audio/common/AudioUuid.h>
#include <android/media/audio/common/HeadTracking.h>
#include <android/media/audio/common/Spatialization.h>
#include <gtest/gtest.h>
#include <media/AidlConversionCppNdk.h>
#include <media/audiohal/EffectsFactoryHalInterface.h>
#include <system/audio_aidl_utils.h>
#include <system/audio_effect.h>
#include <system/audio_effects/audio_effects_utils.h>
#include <system/audio_effects/effect_aec.h>
#include <system/audio_effects/effect_agc.h>
#include <system/audio_effects/effect_agc2.h>
#include <system/audio_effects/effect_bassboost.h>
#include <system/audio_effects/effect_downmix.h>
#include <system/audio_effects/effect_dynamicsprocessing.h>
#include <system/audio_effects/effect_hapticgenerator.h>
#include <system/audio_effects/effect_loudnessenhancer.h>
#include <system/audio_effects/effect_ns.h>
#include <system/audio_effects/effect_spatializer.h>
#include <utils/RefBase.h>
#include <vibrator/ExternalVibrationUtils.h>
namespace android {
using aidl::android::media::audio::common::AudioUuid;
using android::audio::utils::toString;
using effect::utils::EffectParamReader;
using effect::utils::EffectParamWriter;
using media::audio::common::HeadTracking;
using media::audio::common::Spatialization;
// EffectsFactoryHalInterface
TEST(libAudioHalTest, createEffectsFactoryHalInterface) {
ASSERT_NE(nullptr, EffectsFactoryHalInterface::create());
}
TEST(libAudioHalTest, queryNumberEffects) {
auto factory = EffectsFactoryHalInterface::create();
ASSERT_NE(nullptr, factory);
uint32_t numEffects = 0;
EXPECT_EQ(OK, factory->queryNumberEffects(&numEffects));
EXPECT_NE(0ul, numEffects);
}
TEST(libAudioHalTest, getDescriptorByNumber) {
auto factory = EffectsFactoryHalInterface::create();
ASSERT_NE(nullptr, factory);
uint32_t numEffects = 0;
EXPECT_EQ(OK, factory->queryNumberEffects(&numEffects));
EXPECT_NE(0ul, numEffects);
effect_descriptor_t desc;
for (uint32_t i = 0; i < numEffects; i++) {
EXPECT_EQ(OK, factory->getDescriptor(i, &desc));
}
}
TEST(libAudioHalTest, createEffect) {
auto factory = EffectsFactoryHalInterface::create();
ASSERT_NE(nullptr, factory);
uint32_t numEffects = 0;
EXPECT_EQ(OK, factory->queryNumberEffects(&numEffects));
EXPECT_NE(0ul, numEffects);
effect_descriptor_t desc;
for (uint32_t i = 0; i < numEffects; i++) {
sp<EffectHalInterface> interface;
EXPECT_EQ(OK, factory->getDescriptor(i, &desc));
EXPECT_EQ(OK, factory->createEffect(&desc.uuid, 1 /* sessionId */, 1 /* ioId */,
1 /* deviceId */, &interface));
}
}
TEST(libAudioHalTest, getProcessings) {
auto factory = EffectsFactoryHalInterface::create();
ASSERT_NE(nullptr, factory);
const auto &processings = factory->getProcessings();
if (processings) {
EXPECT_NE(0UL, processings->preprocess.size() + processings->postprocess.size() +
processings->deviceprocess.size());
auto processingChecker = [](const auto& processings) {
if (processings.size() != 0) {
// any process need at least 1 effect inside
std::for_each(processings.begin(), processings.end(), [](const auto& process) {
EXPECT_NE(0ul, process.effects.size());
// any effect should have a valid name string, and not proxy
for (const auto& effect : process.effects) {
SCOPED_TRACE("Effect: {" +
(effect == nullptr
? "NULL}"
: ("{name: " + effect->name + ", isproxy: " +
(effect->isProxy ? "true" : "false") + ", sw: " +
(effect->libSw ? "non-null" : "null") + ", hw: " +
(effect->libHw ? "non-null" : "null") + "}")));
EXPECT_NE(nullptr, effect);
EXPECT_NE("", effect->name);
EXPECT_EQ(false, effect->isProxy);
EXPECT_EQ(nullptr, effect->libSw);
EXPECT_EQ(nullptr, effect->libHw);
}
});
}
};
processingChecker(processings->preprocess);
processingChecker(processings->postprocess);
processingChecker(processings->deviceprocess);
} else {
GTEST_SKIP() << "no processing found, skipping the test";
}
}
TEST(libAudioHalTest, getHalVersion) {
auto factory = EffectsFactoryHalInterface::create();
ASSERT_NE(nullptr, factory);
auto version = factory->getHalVersion();
EXPECT_NE(0, version.getMajorVersion());
}
enum ParamSetGetType { SET_N_GET, SET_ONLY, GET_ONLY };
class EffectParamCombination {
public:
template <typename P, typename V>
void init(const P& p, const V& v, size_t len, ParamSetGetType type) {
if (type != GET_ONLY) {
mSetBuffer.resize(sizeof(effect_param_t) + sizeof(p) + sizeof(v) + 4);
mParameterSet =
std::make_shared<EffectParamReader>(createEffectParam(mSetBuffer.data(), p, v));
}
if (type != SET_ONLY) {
mGetBuffer.resize(sizeof(effect_param_t) + sizeof(p) + len + 4);
mExpectBuffer.resize(sizeof(effect_param_t) + sizeof(p) + len + 4);
mParameterGet =
std::make_shared<EffectParamReader>(createEffectParam(mGetBuffer.data(), p, v));
mParameterExpect = std::make_shared<EffectParamReader>(
createEffectParam(mExpectBuffer.data(), p, v));
mValueSize = len;
}
mType = type;
}
std::shared_ptr<EffectParamReader> mParameterSet; /* setParameter */
std::shared_ptr<EffectParamReader> mParameterGet; /* getParameter */
std::shared_ptr<EffectParamReader> mParameterExpect; /* expected from getParameter */
size_t mValueSize = 0ul; /* ValueSize expect to write in reply data buffer */
ParamSetGetType mType = SET_N_GET;
std::string toString() {
uint32_t command = 0;
std::string str = "Command: ";
if (mType != GET_ONLY) {
str += (OK == mParameterSet->readFromParameter(&command) ? std::to_string(command)
: mParameterSet->toString());
} else {
str += (OK == mParameterGet->readFromParameter(&command) ? std::to_string(command)
: mParameterSet->toString());
}
str += "_";
str += toString(mType);
return str;
}
static std::string toString(ParamSetGetType type) {
switch (type) {
case SET_N_GET:
return "Type:SetAndGet";
case SET_ONLY:
return "Type:SetOnly";
case GET_ONLY:
return "Type:GetOnly";
}
}
private:
std::vector<uint8_t> mSetBuffer;
std::vector<uint8_t> mGetBuffer;
std::vector<uint8_t> mExpectBuffer;
template <typename P, typename V>
static EffectParamReader createEffectParam(void* buf, const P& p, const V& v) {
effect_param_t* paramRet = (effect_param_t*)buf;
paramRet->psize = sizeof(P);
paramRet->vsize = sizeof(V);
EffectParamWriter writer(*paramRet);
EXPECT_EQ(OK, writer.writeToParameter(&p));
EXPECT_EQ(OK, writer.writeToValue(&v));
writer.finishValueWrite();
return writer;
}
};
template <typename P, typename V>
std::shared_ptr<EffectParamCombination> createEffectParamCombination(
const P& p, const V& v, size_t len, ParamSetGetType type = SET_N_GET) {
auto comb = std::make_shared<EffectParamCombination>();
comb->init(p, v, len, type);
return comb;
}
enum ParamName { TUPLE_UUID, TUPLE_IS_INPUT, TUPLE_PARAM_COMBINATION };
using EffectParamTestTuple = std::tuple<const effect_uuid_t* /* type UUID */, bool /* isInput */,
std::vector<std::shared_ptr<EffectParamCombination>>>;
static const effect_uuid_t EXTEND_EFFECT_TYPE_UUID = {
0xfa81dbde, 0x588b, 0x11ed, 0x9b6a, {0x02, 0x42, 0xac, 0x12, 0x00, 0x02}};
constexpr std::array<uint8_t, 10> kVendorExtensionData({0xff, 0x5, 0x50, 0xab, 0xcd, 0x00, 0xbd,
0xdb, 0xee, 0xff});
static std::vector<EffectParamTestTuple> testPairs = {
std::make_tuple(
FX_IID_AEC, true /* isInput */,
std::vector<std::shared_ptr<EffectParamCombination>>{
createEffectParamCombination(AEC_PARAM_ECHO_DELAY, 0xff /* echoDelayMs */,
sizeof(int32_t) /* returnValueSize */)}),
std::make_tuple(
FX_IID_AGC, false /* isInput */,
std::vector<std::shared_ptr<EffectParamCombination>>{
createEffectParamCombination(AGC_PARAM_TARGET_LEVEL, 20 /* targetLevel */,
sizeof(int16_t) /* returnValueSize */)}),
std::make_tuple(
SL_IID_BASSBOOST, false /* isInput */,
std::vector<std::shared_ptr<EffectParamCombination>>{
createEffectParamCombination(BASSBOOST_PARAM_STRENGTH, 20 /* strength */,
sizeof(int16_t) /* returnValueSize */)}),
std::make_tuple(
EFFECT_UIID_DOWNMIX, false /* isInput */,
std::vector<std::shared_ptr<EffectParamCombination>>{
createEffectParamCombination(DOWNMIX_PARAM_TYPE, DOWNMIX_TYPE_FOLD,
sizeof(int16_t) /* returnValueSize */)}),
std::make_tuple(
SL_IID_DYNAMICSPROCESSING, false /* isInput */,
std::vector<std::shared_ptr<EffectParamCombination>>{createEffectParamCombination(
std::array<uint32_t, 2>({DP_PARAM_INPUT_GAIN, 0 /* channel */}),
30 /* gainDb */, sizeof(int32_t) /* returnValueSize */)}),
std::make_tuple(
FX_IID_LOUDNESS_ENHANCER, false /* isInput */,
std::vector<std::shared_ptr<EffectParamCombination>>{createEffectParamCombination(
LOUDNESS_ENHANCER_PARAM_TARGET_GAIN_MB, 5 /* gain */,
sizeof(int32_t) /* returnValueSize */)}),
std::make_tuple(
FX_IID_NS, true /* isInput */,
std::vector<std::shared_ptr<EffectParamCombination>>{createEffectParamCombination(
NS_PARAM_LEVEL, 1 /* level */, sizeof(int32_t) /* returnValueSize */)}),
std::make_tuple(
FX_IID_SPATIALIZER, false /* isInput */,
std::vector<std::shared_ptr<EffectParamCombination>>{
createEffectParamCombination(SPATIALIZER_PARAM_LEVEL,
SPATIALIZATION_LEVEL_MULTICHANNEL,
sizeof(uint8_t), SET_N_GET),
createEffectParamCombination(SPATIALIZER_PARAM_HEADTRACKING_MODE,
HeadTracking::Mode::RELATIVE_WORLD,
sizeof(uint8_t), SET_N_GET),
createEffectParamCombination(
SPATIALIZER_PARAM_HEAD_TO_STAGE,
std::array<float, 6>{.55f, 0.2f, 1.f, .999f, .43f, 19.f},
sizeof(std::array<float, 6>), SET_ONLY),
createEffectParamCombination(
SPATIALIZER_PARAM_HEADTRACKING_CONNECTION,
std::array<uint32_t, 2>{
static_cast<uint32_t>(HeadTracking::ConnectionMode::
DIRECT_TO_SENSOR_TUNNEL),
0x5e /* sensorId */},
sizeof(std::array<uint32_t, 2>), SET_N_GET),
createEffectParamCombination(
SPATIALIZER_PARAM_SUPPORTED_LEVELS,
std::array<Spatialization::Level, 3>{
Spatialization::Level::NONE,
Spatialization::Level::MULTICHANNEL,
Spatialization::Level::BED_PLUS_OBJECTS},
sizeof(std::array<uint8_t, 3>), GET_ONLY),
createEffectParamCombination(SPATIALIZER_PARAM_HEADTRACKING_SUPPORTED, true,
sizeof(bool), GET_ONLY),
createEffectParamCombination(SPATIALIZER_PARAM_SUPPORTED_CHANNEL_MASKS,
AUDIO_CHANNEL_OUT_5POINT1, sizeof(uint8_t),
GET_ONLY),
createEffectParamCombination(
SPATIALIZER_PARAM_SUPPORTED_SPATIALIZATION_MODES,
std::array<Spatialization::Mode, 2>{
Spatialization::Mode::BINAURAL,
Spatialization::Mode::TRANSAURAL},
sizeof(std::array<uint8_t, 2>), GET_ONLY),
createEffectParamCombination(
SPATIALIZER_PARAM_SUPPORTED_HEADTRACKING_CONNECTION,
std::array<HeadTracking::ConnectionMode, 3>{
HeadTracking::ConnectionMode::FRAMEWORK_PROCESSED,
HeadTracking::ConnectionMode::DIRECT_TO_SENSOR_SW,
HeadTracking::ConnectionMode::DIRECT_TO_SENSOR_TUNNEL},
sizeof(std::array<uint8_t, 3>), GET_ONLY),
}),
std::make_tuple(
&EXTEND_EFFECT_TYPE_UUID, false /* isInput */,
std::vector<std::shared_ptr<EffectParamCombination>>{createEffectParamCombination(
uint32_t{8}, kVendorExtensionData, sizeof(kVendorExtensionData))}),
};
class libAudioHalEffectParamTest : public ::testing::TestWithParam<EffectParamTestTuple> {
public:
libAudioHalEffectParamTest()
: mParamTuple(GetParam()),
mFactory(EffectsFactoryHalInterface::create()),
mTypeUuid(std::get<TUPLE_UUID>(mParamTuple)),
mCombinations(std::get<TUPLE_PARAM_COMBINATION>(mParamTuple)),
mIsInput(std::get<TUPLE_IS_INPUT>(mParamTuple)),
mDescs([&]() {
std::vector<effect_descriptor_t> descs;
if (mFactory && mTypeUuid && OK == mFactory->getDescriptors(mTypeUuid, &descs)) {
return descs;
}
return descs;
}()) {}
void SetUp() override {
if (0ul == mDescs.size()) {
GTEST_SKIP() << "Effect type not available on device, skipping";
}
for (const auto& desc : mDescs) {
sp<EffectHalInterface> interface = createEffectHal(desc);
ASSERT_NE(nullptr, interface);
mHalInterfaces.push_back(interface);
}
}
void initEffect(const sp<EffectHalInterface>& interface) {
uint32_t reply = 0;
uint32_t replySize = sizeof(reply);
ASSERT_EQ(OK, interface->command(EFFECT_CMD_INIT, 0, nullptr, &replySize, &reply));
ASSERT_EQ(OK, interface->command(EFFECT_CMD_SET_CONFIG, sizeof(effect_config_t),
&mEffectConfig, &replySize, &reply));
}
void TearDown() override {
for (auto& interface : mHalInterfaces) {
interface->close();
}
}
sp<EffectHalInterface> createEffectHal(const effect_descriptor_t& desc) {
sp<EffectHalInterface> interface = nullptr;
if (0 == std::memcmp(&desc.type, mTypeUuid, sizeof(effect_uuid_t)) &&
OK == mFactory->createEffect(&desc.uuid, 1 /* sessionId */, 1 /* ioId */,
1 /* deviceId */, &interface)) {
return interface;
}
return nullptr;
}
void setAndGetParameter(const sp<EffectHalInterface>& interface) {
for (const auto combination : mCombinations) {
uint32_t replySize = kSetParamReplySize;
uint8_t reply[replySize];
const auto type = combination->mType;
if (type != GET_ONLY) {
const auto& set = combination->mParameterSet;
ASSERT_EQ(OK,
interface->command(EFFECT_CMD_SET_PARAM, (uint32_t)set->getTotalSize(),
const_cast<effect_param_t*>(&set->getEffectParam()),
&replySize, &reply))
<< set->toString();
ASSERT_EQ(replySize, kSetParamReplySize);
}
if (type != SET_ONLY) {
auto get = combination->mParameterGet;
auto expect = combination->mParameterExpect;
effect_param_t* getParam = const_cast<effect_param_t*>(&get->getEffectParam());
size_t maxReplySize = combination->mValueSize + sizeof(effect_param_t) +
sizeof(expect->getPaddedParameterSize());
replySize = maxReplySize;
EXPECT_EQ(OK,
interface->command(EFFECT_CMD_GET_PARAM, (uint32_t)expect->getTotalSize(),
const_cast<effect_param_t*>(&expect->getEffectParam()),
&replySize, getParam));
EffectParamReader getReader(*getParam);
EXPECT_EQ(replySize, getReader.getTotalSize()) << getReader.toString();
if (combination->mValueSize) {
std::vector<uint8_t> expectedData(combination->mValueSize);
EXPECT_EQ(OK, expect->readFromValue(expectedData.data(), expectedData.size()))
<< combination->toString();
std::vector<uint8_t> response(combination->mValueSize);
EXPECT_EQ(OK, getReader.readFromValue(response.data(), combination->mValueSize))
<< " try get valueSize " << combination->mValueSize << " from:\n"
<< getReader.toString() << "\nexpect:\n"
<< expect->toString();
EXPECT_EQ(expectedData, response) << combination->toString();
}
}
}
}
static constexpr size_t kSetParamReplySize = sizeof(uint32_t);
const EffectParamTestTuple mParamTuple;
const sp<EffectsFactoryHalInterface> mFactory;
const effect_uuid_t* mTypeUuid;
std::vector<std::shared_ptr<EffectParamCombination>> mCombinations{};
const bool mIsInput;
const std::vector<effect_descriptor_t> mDescs;
std::vector<sp<EffectHalInterface>> mHalInterfaces{};
effect_config_t mEffectConfig = {
.inputCfg =
{
.buffer = {.frameCount = 0x100},
.samplingRate = 48000,
.channels = mIsInput ? AUDIO_CHANNEL_IN_VOICE_CALL_MONO
: AUDIO_CHANNEL_IN_STEREO,
.bufferProvider = {.getBuffer = nullptr,
.releaseBuffer = nullptr,
.cookie = nullptr},
.format = AUDIO_FORMAT_PCM_FLOAT,
.accessMode = EFFECT_BUFFER_ACCESS_READ,
.mask = EFFECT_CONFIG_ALL,
},
.outputCfg =
{
.buffer = {.frameCount = 0x100},
.samplingRate = 48000,
.channels = mIsInput ? AUDIO_CHANNEL_IN_VOICE_CALL_MONO
: AUDIO_CHANNEL_OUT_STEREO,
.bufferProvider = {.getBuffer = nullptr,
.releaseBuffer = nullptr,
.cookie = nullptr},
.format = AUDIO_FORMAT_PCM_FLOAT,
.accessMode = EFFECT_BUFFER_ACCESS_WRITE,
.mask = EFFECT_CONFIG_ALL,
},
};
};
TEST_P(libAudioHalEffectParamTest, setAndGetParam) {
for (auto& interface : mHalInterfaces) {
EXPECT_NO_FATAL_FAILURE(initEffect(interface));
EXPECT_NO_FATAL_FAILURE(setAndGetParameter(interface));
}
}
TEST_P(libAudioHalEffectParamTest, deviceIndicationUpdate) {
for (auto& interface : mHalInterfaces) {
EXPECT_NO_FATAL_FAILURE(initEffect(interface));
// output device
uint32_t deviceTypes = AUDIO_DEVICE_OUT_SPEAKER | AUDIO_DEVICE_OUT_BLE_SPEAKER;
status_t cmdStatus;
uint32_t replySize = sizeof(cmdStatus);
EXPECT_EQ(OK, interface->command(EFFECT_CMD_SET_DEVICE, sizeof(uint32_t), &deviceTypes,
&replySize, &cmdStatus));
// input device
deviceTypes = AUDIO_DEVICE_IN_WIRED_HEADSET | AUDIO_DEVICE_IN_BLUETOOTH_BLE;
EXPECT_EQ(OK, interface->command(EFFECT_CMD_SET_DEVICE, sizeof(uint32_t), &deviceTypes,
&replySize, &cmdStatus));
}
}
TEST_P(libAudioHalEffectParamTest, audioModeIndicationUpdate) {
for (auto& interface : mHalInterfaces) {
EXPECT_NO_FATAL_FAILURE(initEffect(interface));
uint32_t mode = AUDIO_MODE_IN_CALL;
status_t cmdStatus;
uint32_t replySize = sizeof(cmdStatus);
EXPECT_EQ(OK, interface->command(EFFECT_CMD_SET_AUDIO_MODE, sizeof(uint32_t), &mode,
&replySize, &cmdStatus));
}
}
TEST_P(libAudioHalEffectParamTest, audioSourceIndicationUpdate) {
for (auto& interface : mHalInterfaces) {
EXPECT_NO_FATAL_FAILURE(initEffect(interface));
uint32_t source = AUDIO_SOURCE_MIC;
status_t cmdStatus;
uint32_t replySize = sizeof(cmdStatus);
EXPECT_EQ(OK, interface->command(EFFECT_CMD_SET_AUDIO_SOURCE, sizeof(uint32_t), &source,
&replySize, &cmdStatus));
}
}
INSTANTIATE_TEST_SUITE_P(
libAudioHalEffectParamTest, libAudioHalEffectParamTest, ::testing::ValuesIn(testPairs),
[](const testing::TestParamInfo<libAudioHalEffectParamTest::ParamType>& info) {
AudioUuid uuid = ::aidl::android::legacy2aidl_audio_uuid_t_AudioUuid(
*std::get<TUPLE_UUID>(info.param))
.value();
std::string name = "UUID_" + toString(uuid) + "_";
name += std::get<TUPLE_IS_INPUT>(info.param) ? "_input" : "_output";
std::replace_if(
name.begin(), name.end(), [](const char c) { return !std::isalnum(c); }, '_');
return name;
});
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(libAudioHalEffectParamTest);
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
} // namespace android