cabana: support multiple cameras (#30339)

support multiple cameras
old-commit-hash: fe4ad96701
testing-closet
Dean Lee 2 years ago committed by GitHub
parent 8855a694a3
commit 897a62dcfc
  1. 12
      tools/cabana/cabana.cc
  2. 3
      tools/cabana/streams/abstractstream.h
  3. 29
      tools/cabana/streams/replaystream.cc
  4. 6
      tools/cabana/streams/replaystream.h
  5. 49
      tools/cabana/videowidget.cc
  6. 4
      tools/cabana/videowidget.h

@ -26,6 +26,7 @@ int main(int argc, char *argv[]) {
cmd_parser.addOption({"demo", "use a demo route instead of providing your own"});
cmd_parser.addOption({"qcam", "load qcamera"});
cmd_parser.addOption({"ecam", "load wide road camera"});
cmd_parser.addOption({"dcam", "load driver camera"});
cmd_parser.addOption({"stream", "read can messages from live streaming"});
cmd_parser.addOption({"panda", "read can messages from panda"});
cmd_parser.addOption({"panda-serial", "read can messages from panda with given serial", "panda-serial"});
@ -60,13 +61,10 @@ int main(int argc, char *argv[]) {
stream = new SocketCanStream(&app, config);
} else {
uint32_t replay_flags = REPLAY_FLAG_NONE;
if (cmd_parser.isSet("ecam")) {
replay_flags |= REPLAY_FLAG_ECAM;
} else if (cmd_parser.isSet("qcam")) {
replay_flags |= REPLAY_FLAG_QCAMERA;
} else if (cmd_parser.isSet("no-vipc")) {
replay_flags |= REPLAY_FLAG_NO_VIPC;
}
if (cmd_parser.isSet("ecam")) replay_flags |= REPLAY_FLAG_ECAM;
if (cmd_parser.isSet("qcam")) replay_flags |= REPLAY_FLAG_QCAMERA;
if (cmd_parser.isSet("dcam")) replay_flags |= REPLAY_FLAG_DCAM;
if (cmd_parser.isSet("no-vipc")) replay_flags |= REPLAY_FLAG_NO_VIPC;
const QStringList args = cmd_parser.positionalArguments();
QString route;

@ -3,7 +3,6 @@
#include <array>
#include <atomic>
#include <memory>
#include <tuple>
#include <unordered_map>
#include <vector>
@ -70,7 +69,6 @@ public:
virtual double currentSec() const = 0;
virtual double totalSeconds() const { return lastEventMonoTime() / 1e9 - routeStartTime(); }
const CanData &lastMessage(const MessageId &id);
virtual VisionStreamType visionStreamType() const { return VISION_STREAM_ROAD; }
virtual const Route *route() const { return nullptr; }
virtual void setSpeed(float speed) {}
virtual double getSpeed() { return 1; }
@ -79,7 +77,6 @@ public:
const MessageEventsMap &eventsMap() const { return events_; }
const std::vector<const CanEvent *> &allEvents() const { return all_events_; }
const std::vector<const CanEvent *> &events(const MessageId &id) const;
virtual const std::vector<std::tuple<double, double, TimelineType>> getTimeline() { return {}; }
signals:
void paused();

@ -35,7 +35,8 @@ void ReplayStream::mergeSegments() {
}
bool ReplayStream::loadRoute(const QString &route, const QString &data_dir, uint32_t replay_flags) {
replay.reset(new Replay(route, {"can", "roadEncodeIdx", "wideRoadEncodeIdx", "carParams"}, {}, {}, nullptr, replay_flags, data_dir, this));
replay.reset(new Replay(route, {"can", "roadEncodeIdx", "driverEncodeIdx", "wideRoadEncodeIdx", "carParams"},
{}, {}, nullptr, replay_flags, data_dir, this));
replay->setSegmentCacheLimit(settings.max_cached_minutes);
replay->installEventFilter(event_filter, this);
QObject::connect(replay.get(), &Replay::seekedTo, this, &AbstractStream::seekedTo);
@ -84,25 +85,21 @@ AbstractOpenStreamWidget *ReplayStream::widget(AbstractStream **stream) {
OpenReplayWidget::OpenReplayWidget(AbstractStream **stream) : AbstractOpenStreamWidget(stream) {
// TODO: get route list from api.comma.ai
QGridLayout *grid_layout = new QGridLayout();
QGridLayout *grid_layout = new QGridLayout(this);
grid_layout->addWidget(new QLabel(tr("Route")), 0, 0);
grid_layout->addWidget(route_edit = new QLineEdit(this), 0, 1);
route_edit->setPlaceholderText(tr("Enter remote route name or click browse to select a local route"));
auto file_btn = new QPushButton(tr("Browse..."), this);
grid_layout->addWidget(file_btn, 0, 2);
grid_layout->addWidget(new QLabel(tr("Video")), 1, 0);
grid_layout->addWidget(choose_video_cb = new QComboBox(this), 1, 1);
QString items[] = {tr("No Video"), tr("Road Camera"), tr("Wide Road Camera"), tr("Driver Camera"), tr("QCamera")};
for (int i = 0; i < std::size(items); ++i) {
choose_video_cb->addItem(items[i]);
}
choose_video_cb->setCurrentIndex(1); // default is road camera;
grid_layout->addWidget(new QLabel(tr("Camera")), 1, 0);
QHBoxLayout *camera_layout = new QHBoxLayout();
for (auto c : {tr("Road camera"), tr("Driver camera"), tr("Wide road camera")})
camera_layout->addWidget(cameras.emplace_back(new QCheckBox(c, this)));
camera_layout->addStretch(1);
grid_layout->addItem(camera_layout, 1, 1);
QVBoxLayout *main_layout = new QVBoxLayout(this);
main_layout->addLayout(grid_layout);
setMinimumWidth(550);
QObject::connect(file_btn, &QPushButton::clicked, [=]() {
QString dir = QFileDialog::getExistingDirectory(this, tr("Open Local Route"), settings.last_route_dir);
if (!dir.isEmpty()) {
@ -124,9 +121,13 @@ bool OpenReplayWidget::open() {
if (!is_valid_format) {
QMessageBox::warning(nullptr, tr("Warning"), tr("Invalid route format: '%1'").arg(route));
} else {
uint32_t flags[] = {REPLAY_FLAG_NO_VIPC, REPLAY_FLAG_NONE, REPLAY_FLAG_ECAM, REPLAY_FLAG_DCAM, REPLAY_FLAG_QCAMERA};
auto replay_stream = std::make_unique<ReplayStream>(qApp);
if (replay_stream->loadRoute(route, data_dir, flags[choose_video_cb->currentIndex()])) {
uint32_t flags = REPLAY_FLAG_NONE;
if (cameras[1]->isChecked()) flags |= REPLAY_FLAG_DCAM;
if (cameras[2]->isChecked()) flags |= REPLAY_FLAG_ECAM;
if (flags == REPLAY_FLAG_NONE && !cameras[0]->isChecked()) flags = REPLAY_FLAG_NO_VIPC;
if (replay_stream->loadRoute(route, data_dir, flags)) {
*stream = replay_stream.release();
} else {
QMessageBox::warning(nullptr, tr("Warning"), tr("Failed to load route: '%1'").arg(route));

@ -1,9 +1,9 @@
#pragma once
#include <QCheckBox>
#include <algorithm>
#include <memory>
#include <set>
#include <tuple>
#include <vector>
#include "common/prefix.h"
@ -21,7 +21,6 @@ public:
inline QString routeName() const override { return replay->route()->name(); }
inline QString carFingerprint() const override { return replay->carFingerprint().c_str(); }
double totalSeconds() const override { return replay->totalSeconds(); }
inline VisionStreamType visionStreamType() const override { return replay->hasFlag(REPLAY_FLAG_ECAM) ? VISION_STREAM_WIDE_ROAD : VISION_STREAM_ROAD; }
inline QDateTime beginDateTime() const { return replay->route()->datetime(); }
inline double routeStartTime() const override { return replay->routeStartTime() / (double)1e9; }
inline double currentSec() const override { return replay->currentSeconds(); }
@ -31,7 +30,6 @@ public:
inline Replay *getReplay() const { return replay.get(); }
inline bool isPaused() const override { return replay->isPaused(); }
void pause(bool pause) override;
inline const std::vector<std::tuple<double, double, TimelineType>> getTimeline() override { return replay->getTimeline(); }
static AbstractOpenStreamWidget *widget(AbstractStream **stream);
signals:
@ -54,5 +52,5 @@ public:
private:
QLineEdit *route_edit;
QComboBox *choose_video_cb;
std::vector<QCheckBox *> cameras;
};

@ -33,11 +33,9 @@ VideoWidget::VideoWidget(QWidget *parent) : QFrame(parent) {
main_layout->addLayout(createPlaybackController());
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum);
QObject::connect(can, &AbstractStream::paused, this, &VideoWidget::updatePlayBtnState);
QObject::connect(can, &AbstractStream::resume, this, &VideoWidget::updatePlayBtnState);
QObject::connect(can, &AbstractStream::updated, this, &VideoWidget::updateState);
QObject::connect(&settings, &Settings::changed, this, &VideoWidget::updatePlayBtnState);
updatePlayBtnState();
setWhatsThis(tr(R"(
@ -131,10 +129,15 @@ QWidget *VideoWidget::createCameraWidget() {
QWidget *w = new QWidget(this);
QVBoxLayout *l = new QVBoxLayout(w);
l->setContentsMargins(0, 0, 0, 0);
l->setSpacing(0);
l->addWidget(camera_tab = new TabBar(w));
camera_tab->setAutoHide(true);
camera_tab->setExpanding(false);
QStackedLayout *stacked = new QStackedLayout();
stacked->setStackingMode(QStackedLayout::StackAll);
stacked->addWidget(cam_widget = new CameraWidget("camerad", can->visionStreamType(), false));
stacked->addWidget(cam_widget = new CameraWidget("camerad", VISION_STREAM_ROAD, false));
cam_widget->setMinimumHeight(MIN_VIDEO_HEIGHT);
cam_widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
stacked->addWidget(alert_label = new InfoLabel(this));
@ -146,11 +149,34 @@ QWidget *VideoWidget::createCameraWidget() {
setMaximumTime(can->totalSeconds());
QObject::connect(slider, &QSlider::sliderReleased, [this]() { can->seekTo(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<ReplayStream*>(can), &ReplayStream::qLogLoaded, slider, &Slider::parseQLog);
QObject::connect(cam_widget, &CameraWidget::clicked, []() { can->pause(!can->isPaused()); });
QObject::connect(cam_widget, &CameraWidget::vipcAvailableStreamsUpdated, this, &VideoWidget::vipcAvailableStreamsUpdated);
QObject::connect(camera_tab, &QTabBar::currentChanged, [this](int index) {
if (index != -1) cam_widget->setStreamType((VisionStreamType)camera_tab->tabData(index).toInt());
});
return w;
}
void VideoWidget::vipcAvailableStreamsUpdated(std::set<VisionStreamType> streams) {
static const QString stream_names[] = {
[VISION_STREAM_ROAD] = "Road camera",
[VISION_STREAM_WIDE_ROAD] = "Wide road camera",
[VISION_STREAM_DRIVER] = "Driver camera"};
for (int i = 0; i < streams.size(); ++i) {
if (camera_tab->count() <= i) {
camera_tab->addTab(QString());
}
int type = *std::next(streams.begin(), i);
camera_tab->setTabText(i, stream_names[type]);
camera_tab->setTabData(i, type);
}
while (camera_tab->count() > streams.size()) {
camera_tab->removeTab(camera_tab->count() - 1);
}
}
void VideoWidget::loopPlaybackClicked() {
auto replay = qobject_cast<ReplayStream *>(can)->getReplay();
if (replay->hasFlag(REPLAY_FLAG_NO_LOOP)) {
@ -172,12 +198,8 @@ void VideoWidget::updateTimeRange(double min, double max, bool is_zoomed) {
skip_to_end_btn->setEnabled(!is_zoomed);
return;
}
if (!is_zoomed) {
min = 0;
max = maximum_time;
}
slider->setTimeRange(min, max);
is_zoomed ? slider->setTimeRange(min, max)
: slider->setTimeRange(0, maximum_time);
}
QString VideoWidget::formatTime(double sec, bool include_milliseconds) {
@ -262,7 +284,7 @@ void Slider::paintEvent(QPaintEvent *ev) {
double min = minimum() / factor;
double max = maximum() / factor;
for (auto [begin, end, type] : qobject_cast<ReplayStream *>(can)->getTimeline()) {
for (auto [begin, end, type] : qobject_cast<ReplayStream *>(can)->getReplay()->getTimeline()) {
if (begin > max || end < min)
continue;
r.setLeft(((std::max(min, begin) - min) / (max - min)) * width());
@ -282,8 +304,7 @@ void Slider::paintEvent(QPaintEvent *ev) {
void Slider::mousePressEvent(QMouseEvent *e) {
QSlider::mousePressEvent(e);
if (e->button() == Qt::LeftButton && !isSliderDown()) {
int value = minimum() + ((maximum() - minimum()) * e->x()) / width();
setValue(value);
setValue(minimum() + ((maximum() - minimum()) * e->x()) / width());
emit sliderReleased();
}
}
@ -294,7 +315,7 @@ void Slider::mouseMoveEvent(QMouseEvent *e) {
QPixmap thumb = thumbnail(seconds);
if (!thumb.isNull()) {
int x = std::clamp(pos - thumb.width() / 2, THUMBNAIL_MARGIN, width() - thumb.width() - THUMBNAIL_MARGIN + 1);
int y = -thumb.height() - THUMBNAIL_MARGIN - 6;
int y = -thumb.height() - THUMBNAIL_MARGIN;
thumbnail_label.showPixmap(mapToParent(QPoint(x, y)), utils::formatSeconds(seconds), thumb, alertInfo(seconds));
} else {
thumbnail_label.hide();

@ -2,9 +2,11 @@
#include <map>
#include <memory>
#include <set>
#include <QHBoxLayout>
#include <QSlider>
#include <QTabBar>
#include <QToolButton>
#include "selfdrive/ui/qt/widgets/cameraview.h"
@ -70,6 +72,7 @@ protected:
QWidget *createCameraWidget();
QHBoxLayout *createPlaybackController();
void loopPlaybackClicked();
void vipcAvailableStreamsUpdated(std::set<VisionStreamType> streams);
CameraWidget *cam_widget;
double maximum_time = 0;
@ -82,4 +85,5 @@ protected:
ToolButton *skip_to_end_btn = nullptr;
InfoLabel *alert_label = nullptr;
Slider *slider = nullptr;
QTabBar *camera_tab = nullptr;
};

Loading…
Cancel
Save