diff --git a/tools/cabana/streams/replaystream.cc b/tools/cabana/streams/replaystream.cc index 550e2be6a3..8a6b806a8a 100644 --- a/tools/cabana/streams/replaystream.cc +++ b/tools/cabana/streams/replaystream.cc @@ -40,6 +40,7 @@ bool ReplayStream::loadRoute(const QString &route, const QString &data_dir, uint replay->installEventFilter(event_filter, this); QObject::connect(replay.get(), &Replay::seekedTo, this, &AbstractStream::seekedTo); QObject::connect(replay.get(), &Replay::segmentsMerged, this, &ReplayStream::mergeSegments); + QObject::connect(replay.get(), &Replay::qLogLoaded, this, &ReplayStream::qLogLoaded, Qt::QueuedConnection); return replay->load(); } diff --git a/tools/cabana/streams/replaystream.h b/tools/cabana/streams/replaystream.h index de69d9e86c..3f301d2ce2 100644 --- a/tools/cabana/streams/replaystream.h +++ b/tools/cabana/streams/replaystream.h @@ -32,6 +32,9 @@ public: inline const std::vector> getTimeline() override { return replay->getTimeline(); } static AbstractOpenStreamWidget *widget(AbstractStream **stream); +signals: + void qLogLoaded(int segnum, std::shared_ptr qlog); + private: void mergeSegments(); std::unique_ptr replay = nullptr; diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc index 7e051ef3b9..3e22464d06 100644 --- a/tools/cabana/videowidget.cc +++ b/tools/cabana/videowidget.cc @@ -1,8 +1,6 @@ #include "tools/cabana/videowidget.h" #include -#include -#include #include #include @@ -11,7 +9,6 @@ #include #include #include -#include #include #include @@ -125,6 +122,7 @@ QWidget *VideoWidget::createCameraWidget() { QObject::connect(slider, &QSlider::valueChanged, [=](int value) { time_label->setText(utils::formatSeconds(slider->currentSecond())); }); QObject::connect(slider, &Slider::updateMaximumTime, this, &VideoWidget::setMaximumTime, Qt::QueuedConnection); QObject::connect(cam_widget, &CameraWidget::clicked, []() { can->pause(!can->isPaused()); }); + QObject::connect(static_cast(can), &ReplayStream::qLogLoaded, slider, &Slider::parseQLog); QObject::connect(can, &AbstractStream::updated, this, &VideoWidget::updateState); return w; } @@ -165,29 +163,9 @@ void VideoWidget::updatePlayBtnState() { Slider::Slider(QWidget *parent) : thumbnail_label(parent), QSlider(Qt::Horizontal, parent) { setMouseTracking(true); - auto timer = new QTimer(this); - timer->callOnTimeout([this]() { - timeline = can->getTimeline(); - std::sort(timeline.begin(), timeline.end(), [](auto &l, auto &r) { return std::get<2>(l) < std::get<2>(r); }); - update(); - }); - timer->start(2000); - QObject::connect(can, &AbstractStream::eventsMerged, [this]() { - if (!qlog_future) { - qlog_future = std::make_unique>(QtConcurrent::run(this, &Slider::parseQLog)); - } - }); - QObject::connect(qApp, &QApplication::aboutToQuit, [this]() { - abort_parse_qlog = true; - if (qlog_future && qlog_future->isRunning()) { - qDebug() << "stopping thumbnail thread"; - qlog_future->waitForFinished(); - } - }); } AlertInfo Slider::alertInfo(double seconds) { - std::lock_guard lk(thumbnail_lock); uint64_t mono_time = (seconds + can->routeStartTime()) * 1e9; auto alert_it = alerts.lower_bound(mono_time); bool has_alert = (alert_it != alerts.end()) && ((alert_it->first - mono_time) <= 1e8); @@ -195,7 +173,6 @@ AlertInfo Slider::alertInfo(double seconds) { } QPixmap Slider::thumbnail(double seconds) { - std::lock_guard lk(thumbnail_lock); uint64_t mono_time = (seconds + can->routeStartTime()) * 1e9; auto it = thumbnails.lowerBound(mono_time); return it != thumbnails.end() ? it.value() : QPixmap(); @@ -206,36 +183,32 @@ void Slider::setTimeRange(double min, double max) { setRange(min * factor, max * factor); } -void Slider::parseQLog() { - const auto &segments = can->route()->segments(); - for (auto it = segments.rbegin(); it != segments.rend() && !abort_parse_qlog; ++it) { - LogReader log; - std::string qlog = it->second.qlog.toStdString(); - if (!qlog.empty() && log.load(qlog, &abort_parse_qlog, {cereal::Event::Which::THUMBNAIL, cereal::Event::Which::CONTROLS_STATE}, true, 0, 3)) { - if (it == segments.rbegin() && !log.events.empty()) { - double max_time = log.events.back()->mono_time / 1e9 - can->routeStartTime(); - emit updateMaximumTime(max_time); +void Slider::parseQLog(int segnum, std::shared_ptr qlog) { + const auto &segments = qobject_cast(can)->route()->segments(); + if (segments.size() > 0 && segnum == segments.rbegin()->first && !qlog->events.empty()) { + emit updateMaximumTime(qlog->events.back()->mono_time / 1e9 - can->routeStartTime()); + } + + std::mutex mutex; + QtConcurrent::blockingMap(qlog->events.cbegin(), qlog->events.cend(), [&mutex, this](const Event *e) { + if (e->which == cereal::Event::Which::THUMBNAIL) { + auto thumb = e->event.getThumbnail(); + auto data = thumb.getThumbnail(); + if (QPixmap pm; pm.loadFromData(data.begin(), data.size(), "jpeg")) { + QPixmap scaled = pm.scaledToHeight(MIN_VIDEO_HEIGHT - THUMBNAIL_MARGIN * 2, Qt::SmoothTransformation); + std::lock_guard lk(mutex); + thumbnails[thumb.getTimestampEof()] = scaled; } - for (auto ev = log.events.cbegin(); ev != log.events.cend() && !abort_parse_qlog; ++ev) { - if ((*ev)->which == cereal::Event::Which::THUMBNAIL) { - auto thumb = (*ev)->event.getThumbnail(); - auto data = thumb.getThumbnail(); - if (QPixmap pm; pm.loadFromData(data.begin(), data.size(), "jpeg")) { - pm = pm.scaledToHeight(MIN_VIDEO_HEIGHT - THUMBNAIL_MARGIN * 2, Qt::SmoothTransformation); - std::lock_guard lk(thumbnail_lock); - thumbnails[thumb.getTimestampEof()] = pm; - } - } else if ((*ev)->which == cereal::Event::Which::CONTROLS_STATE) { - auto cs = (*ev)->event.getControlsState(); - if (cs.getAlertType().size() > 0 && cs.getAlertText1().size() > 0 && - cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE) { - std::lock_guard lk(thumbnail_lock); - alerts.emplace((*ev)->mono_time, AlertInfo{cs.getAlertStatus(), cs.getAlertText1().cStr(), cs.getAlertText2().cStr()}); - } - } + } else if (e->which == cereal::Event::Which::CONTROLS_STATE) { + auto cs = e->event.getControlsState(); + if (cs.getAlertType().size() > 0 && cs.getAlertText1().size() > 0 && + cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE) { + std::lock_guard lk(mutex); + alerts.emplace(e->mono_time, AlertInfo{cs.getAlertStatus(), cs.getAlertText1().cStr(), cs.getAlertText2().cStr()}); } } - } + }); + update(); } void Slider::paintEvent(QPaintEvent *ev) { @@ -245,7 +218,7 @@ void Slider::paintEvent(QPaintEvent *ev) { double min = minimum() / factor; double max = maximum() / factor; - for (auto [begin, end, type] : timeline) { + for (auto [begin, end, type] : qobject_cast(can)->getTimeline()) { if (begin > max || end < min) continue; r.setLeft(((std::max(min, begin) - min) / (max - min)) * width()); diff --git a/tools/cabana/videowidget.h b/tools/cabana/videowidget.h index aca44d0ac1..12961cd061 100644 --- a/tools/cabana/videowidget.h +++ b/tools/cabana/videowidget.h @@ -1,19 +1,14 @@ #pragma once -#include #include #include -#include -#include -#include -#include #include #include #include #include "selfdrive/ui/qt/widgets/cameraview.h" -#include "tools/cabana/streams/abstractstream.h" +#include "tools/cabana/streams/replaystream.h" struct AlertInfo { cereal::ControlsState::AlertStatus status; @@ -42,6 +37,7 @@ public: void setTimeRange(double min, double max); AlertInfo alertInfo(double sec); QPixmap thumbnail(double sec); + void parseQLog(int segnum, std::shared_ptr qlog); signals: void updateMaximumTime(double); @@ -51,15 +47,10 @@ private: void mouseMoveEvent(QMouseEvent *e) override; bool event(QEvent *event) override; void paintEvent(QPaintEvent *ev) override; - void parseQLog(); const double factor = 1000.0; - std::vector> timeline; - std::mutex thumbnail_lock; - std::atomic abort_parse_qlog = false; QMap thumbnails; std::map alerts; - std::unique_ptr> qlog_future; InfoLabel thumbnail_label; }; diff --git a/tools/replay/replay.cc b/tools/replay/replay.cc index d3a32a2651..b83f657e39 100644 --- a/tools/replay/replay.cc +++ b/tools/replay/replay.cc @@ -153,12 +153,10 @@ void Replay::buildTimeline() { const auto &route_segments = route_->segments(); for (auto it = route_segments.cbegin(); it != route_segments.cend() && !exit_; ++it) { - LogReader log; - if (!log.load(it->second.qlog.toStdString(), &exit_, - {cereal::Event::Which::CONTROLS_STATE, cereal::Event::Which::USER_FLAG}, - !hasFlag(REPLAY_FLAG_NO_FILE_CACHE), 0, 3)) continue; + std::shared_ptr log(new LogReader()); + if (!log->load(it->second.qlog.toStdString(), &exit_, {}, !hasFlag(REPLAY_FLAG_NO_FILE_CACHE), 0, 3)) continue; - for (const Event *e : log.events) { + for (const Event *e : log->events) { if (e->which == cereal::Event::Which::CONTROLS_STATE) { auto cs = e->event.getControlsState(); @@ -186,6 +184,8 @@ void Replay::buildTimeline() { timeline.push_back({toSeconds(e->mono_time), toSeconds(e->mono_time), TimelineType::UserFlag}); } } + std::sort(timeline.begin(), timeline.end(), [](auto &l, auto &r) { return std::get<2>(l) < std::get<2>(r); }); + emit qLogLoaded(it->first, log); } } diff --git a/tools/replay/replay.h b/tools/replay/replay.h index 5ed4ff11b5..01969b0a9f 100644 --- a/tools/replay/replay.h +++ b/tools/replay/replay.h @@ -44,6 +44,7 @@ enum class FindFlag { enum class TimelineType { None, Engaged, AlertInfo, AlertWarning, AlertCritical, UserFlag }; typedef bool (*replayEventFilter)(const Event *, void *); +Q_DECLARE_METATYPE(std::shared_ptr); class Replay : public QObject { Q_OBJECT @@ -91,6 +92,7 @@ signals: void streamStarted(); void segmentsMerged(); void seekedTo(double sec); + void qLogLoaded(int segnum, std::shared_ptr qlog); protected slots: void segmentLoadFinished(bool success);