soundd: misc cleanup + tests (#22796)
* pass AudibleAlert to setAlert * apply reviews * remove trailing underscore * add current_alert_type * common function get_alert * same freq as ui * small cleanup * setvolumne on change * static Alert::get * test_sounds * remove old test files * loop twice * assert loopsRemaining * assert stop * init to min_volumne * Revert "remove old test files" This reverts commit 12e8bfefae1ae7f8649d3eda1e4126ff8eb1fe17. * move to ui/soundd * move test to tests/test_sound.cc * remove old tests * cleanup * lower volume * Revert "remove old tests" This reverts commitpull/22844/heade6bb12abfa
. * return Alert * cleanup * remove #include <optional> * cleanup .gitignore * revert to std::optional * Revert "revert to std::optional" This reverts commita66291c51f
. * cleanup * remove volume check Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
parent
fb8ba34f31
commit
797bb1acb0
17 changed files with 230 additions and 160 deletions
@ -1,119 +0,0 @@ |
|||||||
#include <sys/resource.h> |
|
||||||
|
|
||||||
#include <map> |
|
||||||
|
|
||||||
#include <QApplication> |
|
||||||
#include <QString> |
|
||||||
#include <QSoundEffect> |
|
||||||
|
|
||||||
#include "selfdrive/ui/qt/util.h" |
|
||||||
#include "cereal/messaging/messaging.h" |
|
||||||
#include "selfdrive/common/util.h" |
|
||||||
#include "selfdrive/hardware/hw.h" |
|
||||||
#include "selfdrive/ui/ui.h" |
|
||||||
|
|
||||||
// TODO: detect when we can't play sounds
|
|
||||||
// TODO: detect when we can't display the UI
|
|
||||||
|
|
||||||
void sigHandler(int s) { |
|
||||||
qApp->quit(); |
|
||||||
} |
|
||||||
|
|
||||||
class Sound : public QObject { |
|
||||||
public: |
|
||||||
explicit Sound(QObject *parent = 0) { |
|
||||||
// TODO: merge again and add EQ in the amp config
|
|
||||||
const QString sound_asset_path = Hardware::TICI() ? "../assets/sounds_tici/" : "../assets/sounds/"; |
|
||||||
std::tuple<AudibleAlert, QString, bool> sound_list[] = { |
|
||||||
{AudibleAlert::CHIME_DISENGAGE, sound_asset_path + "disengaged.wav", false}, |
|
||||||
{AudibleAlert::CHIME_ENGAGE, sound_asset_path + "engaged.wav", false}, |
|
||||||
{AudibleAlert::CHIME_WARNING1, sound_asset_path + "warning_1.wav", false}, |
|
||||||
{AudibleAlert::CHIME_WARNING2, sound_asset_path + "warning_2.wav", false}, |
|
||||||
{AudibleAlert::CHIME_WARNING2_REPEAT, sound_asset_path + "warning_2.wav", true}, |
|
||||||
{AudibleAlert::CHIME_WARNING_REPEAT, sound_asset_path + "warning_repeat.wav", true}, |
|
||||||
{AudibleAlert::CHIME_ERROR, sound_asset_path + "error.wav", false}, |
|
||||||
{AudibleAlert::CHIME_PROMPT, sound_asset_path + "error.wav", false} |
|
||||||
}; |
|
||||||
for (auto &[alert, fn, loops] : sound_list) { |
|
||||||
QSoundEffect *s = new QSoundEffect(this); |
|
||||||
QObject::connect(s, &QSoundEffect::statusChanged, this, &Sound::checkStatus); |
|
||||||
s->setSource(QUrl::fromLocalFile(fn)); |
|
||||||
sounds[alert] = {s, loops ? QSoundEffect::Infinite : 0}; |
|
||||||
} |
|
||||||
|
|
||||||
sm = new SubMaster({"carState", "controlsState"}); |
|
||||||
|
|
||||||
QTimer *timer = new QTimer(this); |
|
||||||
QObject::connect(timer, &QTimer::timeout, this, &Sound::update); |
|
||||||
timer->start(); |
|
||||||
}; |
|
||||||
~Sound() { |
|
||||||
delete sm; |
|
||||||
}; |
|
||||||
|
|
||||||
private slots: |
|
||||||
void checkStatus() { |
|
||||||
QSoundEffect *s = qobject_cast<QSoundEffect*>(sender()); |
|
||||||
assert(s->status() != QSoundEffect::Error); |
|
||||||
} |
|
||||||
|
|
||||||
void update() { |
|
||||||
sm->update(100); |
|
||||||
if (sm->updated("carState")) { |
|
||||||
// scale volume with speed
|
|
||||||
volume = util::map_val((*sm)["carState"].getCarState().getVEgo(), 0.f, 20.f, |
|
||||||
Hardware::MIN_VOLUME, Hardware::MAX_VOLUME); |
|
||||||
for (auto &[s, loops] : sounds) { |
|
||||||
s->setVolume(std::round(100 * volume) / 100); |
|
||||||
} |
|
||||||
} |
|
||||||
if (sm->updated("controlsState")) { |
|
||||||
const cereal::ControlsState::Reader &cs = (*sm)["controlsState"].getControlsState(); |
|
||||||
setAlert({QString::fromStdString(cs.getAlertText1()), |
|
||||||
QString::fromStdString(cs.getAlertText2()), |
|
||||||
QString::fromStdString(cs.getAlertType()), |
|
||||||
cs.getAlertSize(), cs.getAlertSound()}); |
|
||||||
} else if (sm->rcv_frame("controlsState") > 0 && (*sm)["controlsState"].getControlsState().getEnabled() && |
|
||||||
((nanos_since_boot() - sm->rcv_time("controlsState")) / 1e9 > CONTROLS_TIMEOUT)) { |
|
||||||
setAlert(CONTROLS_UNRESPONSIVE_ALERT); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
void setAlert(Alert a) { |
|
||||||
if (!alert.equal(a)) { |
|
||||||
alert = a; |
|
||||||
// stop sounds
|
|
||||||
for (auto &[s, loops] : sounds) { |
|
||||||
// Only stop repeating sounds
|
|
||||||
if (s->loopsRemaining() == QSoundEffect::Infinite) { |
|
||||||
s->stop(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// play sound
|
|
||||||
if (alert.sound != AudibleAlert::NONE) { |
|
||||||
auto &[s, loops] = sounds[alert.sound]; |
|
||||||
s->setLoopCount(loops); |
|
||||||
s->play(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private: |
|
||||||
Alert alert; |
|
||||||
float volume = Hardware::MIN_VOLUME; |
|
||||||
QMap<AudibleAlert, QPair<QSoundEffect*, int>> sounds; |
|
||||||
SubMaster *sm; |
|
||||||
}; |
|
||||||
|
|
||||||
int main(int argc, char **argv) { |
|
||||||
qInstallMessageHandler(swagLogMessageHandler); |
|
||||||
setpriority(PRIO_PROCESS, 0, -20); |
|
||||||
|
|
||||||
QApplication a(argc, argv); |
|
||||||
std::signal(SIGINT, sigHandler); |
|
||||||
std::signal(SIGTERM, sigHandler); |
|
||||||
|
|
||||||
Sound sound; |
|
||||||
return a.exec(); |
|
||||||
} |
|
@ -0,0 +1 @@ |
|||||||
|
_soundd |
@ -0,0 +1,22 @@ |
|||||||
|
#include <sys/resource.h> |
||||||
|
|
||||||
|
#include <QApplication> |
||||||
|
|
||||||
|
#include "selfdrive/ui/qt/util.h" |
||||||
|
#include "selfdrive/ui/soundd/sound.h" |
||||||
|
|
||||||
|
void sigHandler(int s) { |
||||||
|
qApp->quit(); |
||||||
|
} |
||||||
|
|
||||||
|
int main(int argc, char **argv) { |
||||||
|
qInstallMessageHandler(swagLogMessageHandler); |
||||||
|
setpriority(PRIO_PROCESS, 0, -20); |
||||||
|
|
||||||
|
QApplication a(argc, argv); |
||||||
|
std::signal(SIGINT, sigHandler); |
||||||
|
std::signal(SIGTERM, sigHandler); |
||||||
|
|
||||||
|
Sound sound; |
||||||
|
return a.exec(); |
||||||
|
} |
@ -0,0 +1,58 @@ |
|||||||
|
#include "selfdrive/ui/soundd/sound.h" |
||||||
|
|
||||||
|
#include "cereal/messaging/messaging.h" |
||||||
|
#include "selfdrive/common/util.h" |
||||||
|
|
||||||
|
// TODO: detect when we can't play sounds
|
||||||
|
// TODO: detect when we can't display the UI
|
||||||
|
|
||||||
|
Sound::Sound(QObject *parent) : sm({"carState", "controlsState"}) { |
||||||
|
const QString sound_asset_path = Hardware::TICI() ? "../../assets/sounds_tici/" : "../../assets/sounds/"; |
||||||
|
for (auto &[alert, fn, loops] : sound_list) { |
||||||
|
QSoundEffect *s = new QSoundEffect(this); |
||||||
|
QObject::connect(s, &QSoundEffect::statusChanged, [=]() { |
||||||
|
assert(s->status() != QSoundEffect::Error); |
||||||
|
}); |
||||||
|
s->setSource(QUrl::fromLocalFile(sound_asset_path + fn)); |
||||||
|
s->setVolume(Hardware::MIN_VOLUME); |
||||||
|
sounds[alert] = {s, loops ? QSoundEffect::Infinite : 0}; |
||||||
|
} |
||||||
|
|
||||||
|
QTimer *timer = new QTimer(this); |
||||||
|
QObject::connect(timer, &QTimer::timeout, this, &Sound::update); |
||||||
|
timer->start(1000 / UI_FREQ); |
||||||
|
}; |
||||||
|
|
||||||
|
void Sound::update() { |
||||||
|
sm.update(0); |
||||||
|
if (sm.updated("carState")) { |
||||||
|
// scale volume with speed
|
||||||
|
float volume = util::map_val(sm["carState"].getCarState().getVEgo(), 0.f, 20.f, |
||||||
|
Hardware::MIN_VOLUME, Hardware::MAX_VOLUME); |
||||||
|
for (auto &[s, loops] : sounds) { |
||||||
|
s->setVolume(std::round(100 * volume) / 100); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
setAlert(Alert::get(sm, 1)); |
||||||
|
} |
||||||
|
|
||||||
|
void Sound::setAlert(const Alert &alert) { |
||||||
|
if (!current_alert.equal(alert)) { |
||||||
|
current_alert = alert; |
||||||
|
// stop sounds
|
||||||
|
for (auto &[s, loops] : sounds) { |
||||||
|
// Only stop repeating sounds
|
||||||
|
if (s->loopsRemaining() == QSoundEffect::Infinite) { |
||||||
|
s->stop(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// play sound
|
||||||
|
if (alert.sound != AudibleAlert::NONE) { |
||||||
|
auto &[s, loops] = sounds[alert.sound]; |
||||||
|
s->setLoopCount(loops); |
||||||
|
s->play(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,30 @@ |
|||||||
|
#include <QMap> |
||||||
|
#include <QSoundEffect> |
||||||
|
#include <QString> |
||||||
|
|
||||||
|
#include "selfdrive/hardware/hw.h" |
||||||
|
#include "selfdrive/ui/ui.h" |
||||||
|
|
||||||
|
const std::tuple<AudibleAlert, QString, bool> sound_list[] = { |
||||||
|
{AudibleAlert::CHIME_DISENGAGE, "disengaged.wav", false}, |
||||||
|
{AudibleAlert::CHIME_ENGAGE, "engaged.wav", false}, |
||||||
|
{AudibleAlert::CHIME_WARNING1, "warning_1.wav", false}, |
||||||
|
{AudibleAlert::CHIME_WARNING2, "warning_2.wav", false}, |
||||||
|
{AudibleAlert::CHIME_WARNING2_REPEAT, "warning_2.wav", true}, |
||||||
|
{AudibleAlert::CHIME_WARNING_REPEAT, "warning_repeat.wav", true}, |
||||||
|
{AudibleAlert::CHIME_ERROR, "error.wav", false}, |
||||||
|
{AudibleAlert::CHIME_PROMPT, "error.wav", false}, |
||||||
|
}; |
||||||
|
|
||||||
|
class Sound : public QObject { |
||||||
|
public: |
||||||
|
explicit Sound(QObject *parent = 0); |
||||||
|
|
||||||
|
protected: |
||||||
|
void update(); |
||||||
|
void setAlert(const Alert &alert); |
||||||
|
|
||||||
|
Alert current_alert = {}; |
||||||
|
QMap<AudibleAlert, QPair<QSoundEffect *, int>> sounds; |
||||||
|
SubMaster sm; |
||||||
|
}; |
@ -1,2 +1,3 @@ |
|||||||
test |
test |
||||||
play_sound |
playsound |
||||||
|
test_sound |
||||||
|
@ -0,0 +1,10 @@ |
|||||||
|
#define CATCH_CONFIG_RUNNER |
||||||
|
#include "catch2/catch.hpp" |
||||||
|
#include <QCoreApplication> |
||||||
|
|
||||||
|
int main(int argc, char **argv) { |
||||||
|
// unit tests for Qt
|
||||||
|
QCoreApplication app(argc, argv); |
||||||
|
const int res = Catch::Session().run(argc, argv); |
||||||
|
return (res < 0xff ? res : 0xff); |
||||||
|
} |
@ -0,0 +1,63 @@ |
|||||||
|
#include <QEventLoop> |
||||||
|
#include <QMap> |
||||||
|
#include <QThread> |
||||||
|
|
||||||
|
#include "catch2/catch.hpp" |
||||||
|
#include "selfdrive/ui/soundd/sound.h" |
||||||
|
|
||||||
|
class TestSound : public Sound { |
||||||
|
public: |
||||||
|
TestSound() : Sound() { |
||||||
|
for (auto i = sounds.constBegin(); i != sounds.constEnd(); ++i) { |
||||||
|
QObject::connect(i.value().first, &QSoundEffect::playingChanged, [=, s = i.value().first, a = i.key()]() { |
||||||
|
if (s->isPlaying()) { |
||||||
|
bool repeat = a == AudibleAlert::CHIME_WARNING_REPEAT || a == AudibleAlert::CHIME_WARNING2_REPEAT; |
||||||
|
REQUIRE((s->loopsRemaining() == repeat ? QSoundEffect::Infinite : 1)); |
||||||
|
sound_stats[a].first++; |
||||||
|
} else { |
||||||
|
sound_stats[a].second++; |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
QMap<AudibleAlert, std::pair<int, int>> sound_stats; |
||||||
|
}; |
||||||
|
|
||||||
|
void controls_thread(int loop_cnt) { |
||||||
|
PubMaster pm({"controlsState"}); |
||||||
|
const int DT_CTRL = 10; // ms
|
||||||
|
for (int i = 0; i < loop_cnt; ++i) { |
||||||
|
for (auto &[alert, fn, loops] : sound_list) { |
||||||
|
printf("testing %s\n", qPrintable(fn)); |
||||||
|
for (int j = 0; j < 1000 / DT_CTRL; ++j) { |
||||||
|
MessageBuilder msg; |
||||||
|
auto cs = msg.initEvent().initControlsState(); |
||||||
|
cs.setAlertSound(alert); |
||||||
|
cs.setAlertType(fn.toStdString()); |
||||||
|
pm.send("controlsState", msg); |
||||||
|
QThread::msleep(DT_CTRL); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
QThread::currentThread()->quit(); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_CASE("test sound") { |
||||||
|
QEventLoop loop; |
||||||
|
|
||||||
|
TestSound test_sound; |
||||||
|
|
||||||
|
const int test_loop_cnt = 2; |
||||||
|
QThread t; |
||||||
|
QObject::connect(&t, &QThread::started, [=]() { controls_thread(test_loop_cnt); }); |
||||||
|
QObject::connect(&t, &QThread::finished, [&]() { loop.quit(); }); |
||||||
|
t.start(); |
||||||
|
|
||||||
|
loop.exec(); |
||||||
|
|
||||||
|
for (auto [play, stop] : test_sound.sound_stats) { |
||||||
|
REQUIRE(play == test_loop_cnt); |
||||||
|
REQUIRE(stop == test_loop_cnt); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue