replay improvements (#22203)
* refactor replay
* cleanup
small cleanup
* merge 22239
* cleanup
* add optional argument for start time
* small cleaup
old-commit-hash: 50ae7dd6a9
commatwo_master
parent
e44eabb2f8
commit
5be69694ba
11 changed files with 418 additions and 315 deletions
@ -0,0 +1,153 @@ |
||||
#include "selfdrive/ui/replay/route.h" |
||||
|
||||
#include <QDir> |
||||
#include <QEventLoop> |
||||
#include <QFile> |
||||
#include <QJsonArray> |
||||
#include <QJsonDocument> |
||||
#include <QRegExp> |
||||
#include <future> |
||||
|
||||
#include "selfdrive/hardware/hw.h" |
||||
#include "selfdrive/ui/qt/api.h" |
||||
|
||||
Route::Route(const QString &route) : route_(route) {} |
||||
|
||||
bool Route::load() { |
||||
QEventLoop loop; |
||||
auto onError = [&loop](const QString &err) { |
||||
qInfo() << err; |
||||
loop.quit(); |
||||
}; |
||||
|
||||
bool ret = false; |
||||
HttpRequest http(nullptr, !Hardware::PC()); |
||||
QObject::connect(&http, &HttpRequest::failedResponse, onError); |
||||
QObject::connect(&http, &HttpRequest::timeoutResponse, onError); |
||||
QObject::connect(&http, &HttpRequest::receivedResponse, [&](const QString json) { |
||||
ret = loadFromJson(json); |
||||
loop.quit(); |
||||
}); |
||||
http.sendRequest("https://api.commadotai.com/v1/route/" + route_ + "/files"); |
||||
loop.exec(); |
||||
return ret; |
||||
} |
||||
|
||||
bool Route::loadFromJson(const QString &json) { |
||||
QJsonObject route_files = QJsonDocument::fromJson(json.trimmed().toUtf8()).object(); |
||||
if (route_files.empty()) { |
||||
qInfo() << "JSON Parse failed"; |
||||
return false; |
||||
} |
||||
|
||||
QRegExp rx(R"(\/(\d+)\/)"); |
||||
for (const QString &key : route_files.keys()) { |
||||
for (const auto &url : route_files[key].toArray()) { |
||||
QString url_str = url.toString(); |
||||
if (rx.indexIn(url_str) != -1) { |
||||
const int seg_num = rx.cap(1).toInt(); |
||||
if (segments_.size() <= seg_num) { |
||||
segments_.resize(seg_num + 1); |
||||
} |
||||
if (key == "logs") { |
||||
segments_[seg_num].rlog = url_str; |
||||
} else if (key == "qlogs") { |
||||
segments_[seg_num].qlog = url_str; |
||||
} else if (key == "cameras") { |
||||
segments_[seg_num].road_cam = url_str; |
||||
} else if (key == "dcameras") { |
||||
segments_[seg_num].driver_cam = url_str; |
||||
} else if (key == "ecameras") { |
||||
segments_[seg_num].wide_road_cam = url_str; |
||||
} else if (key == "qcameras") { |
||||
segments_[seg_num].qcamera = url_str; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
// class Segment
|
||||
|
||||
Segment::Segment(int n, const SegmentFile &segment_files) : seg_num_(n), files_(segment_files) { |
||||
static std::once_flag once_flag; |
||||
std::call_once(once_flag, [=]() { |
||||
if (!QDir(CACHE_DIR).exists()) QDir().mkdir(CACHE_DIR); |
||||
}); |
||||
|
||||
// fallback to qcamera
|
||||
road_cam_path_ = files_.road_cam.isEmpty() ? files_.qcamera : files_.road_cam; |
||||
valid_ = !files_.rlog.isEmpty() && !road_cam_path_.isEmpty(); |
||||
if (!valid_) return; |
||||
|
||||
if (!QUrl(files_.rlog).isLocalFile()) { |
||||
for (auto &url : {files_.rlog, road_cam_path_, files_.driver_cam, files_.wide_road_cam}) { |
||||
if (!url.isEmpty() && !QFile::exists(localPath(url))) { |
||||
qDebug() << "download" << url; |
||||
downloadFile(url); |
||||
++downloading_; |
||||
} |
||||
} |
||||
} |
||||
if (downloading_ == 0) { |
||||
QTimer::singleShot(0, this, &Segment::load); |
||||
} |
||||
} |
||||
|
||||
Segment::~Segment() { |
||||
// cancel download, qnam will not abort requests, need to abort them manually
|
||||
aborting_ = true; |
||||
for (QNetworkReply *replay : replies_) { |
||||
if (replay->isRunning()) { |
||||
replay->abort(); |
||||
} |
||||
replay->deleteLater(); |
||||
} |
||||
} |
||||
|
||||
void Segment::downloadFile(const QString &url) { |
||||
QNetworkReply *reply = qnam_.get(QNetworkRequest(url)); |
||||
replies_.insert(reply); |
||||
connect(reply, &QNetworkReply::finished, [=]() { |
||||
if (reply->error() == QNetworkReply::NoError) { |
||||
QFile file(localPath(url)); |
||||
if (file.open(QIODevice::WriteOnly)) { |
||||
file.write(reply->readAll()); |
||||
} |
||||
} |
||||
if (--downloading_ == 0 && !aborting_) { |
||||
load(); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
// load concurrency
|
||||
void Segment::load() { |
||||
std::vector<std::future<bool>> futures; |
||||
futures.emplace_back(std::async(std::launch::async, [=]() { |
||||
log = std::make_unique<LogReader>(); |
||||
return log->load(localPath(files_.rlog).toStdString()); |
||||
})); |
||||
|
||||
QString camera_files[] = {road_cam_path_, files_.driver_cam, files_.wide_road_cam}; |
||||
for (int i = 0; i < std::size(camera_files); ++i) { |
||||
if (!camera_files[i].isEmpty()) { |
||||
futures.emplace_back(std::async(std::launch::async, [=]() { |
||||
frames[i] = std::make_unique<FrameReader>(); |
||||
return frames[i]->load(localPath(camera_files[i]).toStdString()); |
||||
})); |
||||
} |
||||
} |
||||
|
||||
int success_cnt = std::accumulate(futures.begin(), futures.end(), 0, [=](int v, auto &f) { return f.get() + v; }); |
||||
loaded_ = valid_ = (success_cnt == futures.size()); |
||||
emit loadFinished(); |
||||
} |
||||
|
||||
QString Segment::localPath(const QUrl &url) { |
||||
if (url.isLocalFile()) return url.toString(); |
||||
|
||||
QByteArray url_no_query = url.toString(QUrl::RemoveQuery).toUtf8(); |
||||
return CACHE_DIR + QString(QCryptographicHash::hash(url_no_query, QCryptographicHash::Sha256).toHex()); |
||||
} |
@ -0,0 +1,65 @@ |
||||
#pragma once |
||||
|
||||
#include <QNetworkAccessManager> |
||||
#include <QString> |
||||
#include <vector> |
||||
|
||||
#include "selfdrive/common/util.h" |
||||
#include "selfdrive/ui/replay/filereader.h" |
||||
#include "selfdrive/ui/replay/framereader.h" |
||||
|
||||
const QString CACHE_DIR = util::getenv("COMMA_CACHE", "/tmp/comma_download_cache/").c_str(); |
||||
|
||||
struct SegmentFile { |
||||
QString rlog; |
||||
QString qlog; |
||||
QString road_cam; |
||||
QString driver_cam; |
||||
QString wide_road_cam; |
||||
QString qcamera; |
||||
}; |
||||
|
||||
class Route { |
||||
public: |
||||
Route(const QString &route); |
||||
bool load(); |
||||
|
||||
inline const QString &name() const { return route_; }; |
||||
inline int size() const { return segments_.size(); } |
||||
inline SegmentFile &at(int n) { return segments_[n]; } |
||||
|
||||
protected: |
||||
bool loadFromJson(const QString &json); |
||||
QString route_; |
||||
std::vector<SegmentFile> segments_; |
||||
}; |
||||
|
||||
class Segment : public QObject { |
||||
Q_OBJECT |
||||
|
||||
public: |
||||
Segment(int n, const SegmentFile &segment_files); |
||||
~Segment(); |
||||
inline bool isValid() const { return valid_; }; |
||||
inline bool isLoaded() const { return loaded_; } |
||||
|
||||
std::unique_ptr<LogReader> log; |
||||
std::unique_ptr<FrameReader> frames[MAX_CAMERAS] = {}; |
||||
|
||||
signals: |
||||
void loadFinished(); |
||||
|
||||
protected: |
||||
void load(); |
||||
void downloadFile(const QString &url); |
||||
QString localPath(const QUrl &url); |
||||
|
||||
bool loaded_ = false, valid_ = false; |
||||
bool aborting_ = false; |
||||
int downloading_ = 0; |
||||
int seg_num_ = 0; |
||||
SegmentFile files_; |
||||
QString road_cam_path_; |
||||
QSet<QNetworkReply *> replies_; |
||||
QNetworkAccessManager qnam_; |
||||
}; |
Loading…
Reference in new issue