Cabana: show video thumbnail over slider (#26689)

* show thumbnail

* copy segments vector

* cleanup

cleanup mouseMoveEvent

cleanup include

* move lambda to memeber function

* reduce lock time

* private functions

* split to small functions
pull/26698/head
Dean Lee 2 years ago committed by GitHub
parent 0ff703d82f
commit f6496ce670
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      tools/cabana/canmessages.h
  2. 2
      tools/cabana/mainwin.cc
  3. 62
      tools/cabana/videowidget.cc
  4. 15
      tools/cabana/videowidget.h

@ -31,7 +31,7 @@ public:
QList<QPointF> findSignalValues(const QString&id, const Signal* signal, double value, FindFlags flag, int max_count); QList<QPointF> findSignalValues(const QString&id, const Signal* signal, double value, FindFlags flag, int max_count);
bool eventFilter(const Event *event); bool eventFilter(const Event *event);
inline QString route() const { return replay->route()->name(); } inline QString routeName() const { return replay->route()->name(); }
inline QString carFingerprint() const { return replay->carFingerprint().c_str(); } inline QString carFingerprint() const { return replay->carFingerprint().c_str(); }
inline double totalSeconds() const { return replay->totalSeconds(); } inline double totalSeconds() const { return replay->totalSeconds(); }
inline double routeStartTime() const { return replay->routeStartTime() / (double)1e9; } inline double routeStartTime() const { return replay->routeStartTime() / (double)1e9; }
@ -39,6 +39,7 @@ public:
const std::deque<CanData> messages(const QString &id); const std::deque<CanData> messages(const QString &id);
inline const CanData &lastMessage(const QString &id) { return can_msgs[id]; } inline const CanData &lastMessage(const QString &id) { return can_msgs[id]; }
inline const Route* route() const { return replay->route(); }
inline const std::vector<Event *> *events() const { return replay->events(); } inline const std::vector<Event *> *events() const { return replay->events(); }
inline void setSpeed(float speed) { replay->setSpeed(speed); } inline void setSpeed(float speed) { replay->setSpeed(speed); }
inline bool isPaused() const { return replay->isPaused(); } inline bool isPaused() const { return replay->isPaused(); }

@ -71,7 +71,7 @@ MainWindow::MainWindow() : QMainWindow() {
right_hlayout->addWidget(fingerprint_label, 0, Qt::AlignLeft); right_hlayout->addWidget(fingerprint_label, 0, Qt::AlignLeft);
// TODO: click to select another route. // TODO: click to select another route.
right_hlayout->addWidget(new QLabel(can->route()), 0, Qt::AlignRight); right_hlayout->addWidget(new QLabel(can->routeName()), 0, Qt::AlignRight);
r_layout->addLayout(right_hlayout); r_layout->addLayout(right_hlayout);
video_widget = new VideoWidget(this); video_widget = new VideoWidget(this);

@ -1,13 +1,17 @@
#include "tools/cabana/videowidget.h" #include "tools/cabana/videowidget.h"
#include <QBuffer>
#include <QButtonGroup> #include <QButtonGroup>
#include <QDateTime> #include <QDateTime>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QMouseEvent> #include <QMouseEvent>
#include <QPainter> #include <QPainter>
#include <QPixmap>
#include <QStyleOptionSlider> #include <QStyleOptionSlider>
#include <QTimer> #include <QTimer>
#include <QToolTip>
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QtConcurrent>
inline QString formatTime(int seconds) { inline QString formatTime(int seconds) {
return QDateTime::fromTime_t(seconds).toString(seconds > 60 * 60 ? "hh:mm:ss" : "mm:ss"); return QDateTime::fromTime_t(seconds).toString(seconds > 60 * 60 ? "hh:mm:ss" : "mm:ss");
@ -92,7 +96,50 @@ Slider::Slider(QWidget *parent) : QSlider(Qt::Horizontal, parent) {
timeline = can->getTimeline(); timeline = can->getTimeline();
update(); update();
}); });
setMouseTracking(true);
QObject::connect(can, SIGNAL(streamStarted()), timer, SLOT(start())); QObject::connect(can, SIGNAL(streamStarted()), timer, SLOT(start()));
QObject::connect(can, &CANMessages::streamStarted, this, &Slider::streamStarted);
}
void Slider::streamStarted() {
abort_load_thumbnail = true;
thumnail_future.waitForFinished();
abort_load_thumbnail = false;
thumbnails.clear();
thumnail_future = QtConcurrent::run(this, &Slider::loadThumbnails);
}
void Slider::loadThumbnails() {
const auto segments = can->route()->segments();
for (auto it = segments.rbegin(); it != segments.rend() && !abort_load_thumbnail; ++it) {
std::string qlog = it->second.qlog.toStdString();
if (!qlog.empty()) {
LogReader log;
if (log.load(qlog, &abort_load_thumbnail, {cereal::Event::Which::THUMBNAIL}, true, 0, 3)) {
for (auto ev = log.events.cbegin(); ev != log.events.cend() && !abort_load_thumbnail; ++ev) {
auto thumb = (*ev)->event.getThumbnail();
QString str = getThumbnailString(thumb.getThumbnail());
std::lock_guard lk(thumbnail_lock);
thumbnails[thumb.getTimestampEof()] = str;
}
}
}
}
}
QString Slider::getThumbnailString(const capnp::Data::Reader &data) {
QPixmap thumb;
if (thumb.loadFromData(data.begin(), data.size(), "jpeg")) {
thumb = thumb.scaled({thumb.width()/3, thumb.height()/3}, Qt::KeepAspectRatio);
thumbnail_size = thumb.size();
QByteArray bytes;
QBuffer buffer(&bytes);
buffer.open(QIODevice::WriteOnly);
thumb.save(&buffer, "png");
return QString("<img src='data:image/png;base64, %0'>").arg(QString(bytes.toBase64()));
}
return {};
} }
void Slider::sliderChange(QAbstractSlider::SliderChange change) { void Slider::sliderChange(QAbstractSlider::SliderChange change) {
@ -146,3 +193,18 @@ void Slider::mousePressEvent(QMouseEvent *e) {
emit sliderReleased(); emit sliderReleased();
} }
} }
void Slider::mouseMoveEvent(QMouseEvent *e) {
QString thumb;
{
double seconds = (minimum() + e->pos().x() * ((maximum() - minimum()) / (double)width())) / 1000.0;
std::lock_guard lk(thumbnail_lock);
auto it = thumbnails.lowerBound((seconds + can->routeStartTime()) * 1e9);
if (it != thumbnails.end()) {
thumb = it.value();
}
}
QPoint pt = mapToGlobal({e->pos().x() - thumbnail_size.width() / 2, -thumbnail_size.height() - 30});
QToolTip::showText(pt, thumb, this, rect());
QSlider::mouseMoveEvent(e);
}

@ -1,5 +1,9 @@
#pragma once #pragma once
#include <atomic>
#include <mutex>
#include <QFuture>
#include <QLabel> #include <QLabel>
#include <QPushButton> #include <QPushButton>
#include <QSlider> #include <QSlider>
@ -12,12 +16,23 @@ class Slider : public QSlider {
public: public:
Slider(QWidget *parent); Slider(QWidget *parent);
private:
void mousePressEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void sliderChange(QAbstractSlider::SliderChange change) override; void sliderChange(QAbstractSlider::SliderChange change) override;
void paintEvent(QPaintEvent *ev) override; void paintEvent(QPaintEvent *ev) override;
void streamStarted();
void loadThumbnails();
QString getThumbnailString(const capnp::Data::Reader &data);
int slider_x = -1; int slider_x = -1;
std::vector<std::tuple<int, int, TimelineType>> timeline; std::vector<std::tuple<int, int, TimelineType>> timeline;
std::mutex thumbnail_lock;
std::atomic<bool> abort_load_thumbnail = false;
QMap<uint64_t, QString> thumbnails;
QFuture<void> thumnail_future;
QSize thumbnail_size = {};
}; };
class VideoWidget : public QWidget { class VideoWidget : public QWidget {

Loading…
Cancel
Save