cabana: support multiple cameras (#30339)
support multiple cameras old-commit-hash: fe4ad9670179b6f823b7332d5a903a7049a3ac03
This commit is contained in:
@@ -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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user