mirror of
https://github.com/dragonpilot/dragonpilot.git
synced 2026-10-01 03:43:42 +08:00
openpilot v0.9.5 release
date: 2023-11-17T23:53:40
master commit: d3aad9ca46
This commit is contained in:
+17
-3
@@ -1,7 +1,19 @@
|
||||
#include "tools/replay/camera.h"
|
||||
#include "tools/replay/util.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <tuple>
|
||||
|
||||
#include "third_party/linux/include/msm_media_info.h"
|
||||
#include "tools/replay/util.h"
|
||||
|
||||
std::tuple<size_t, size_t, size_t> get_nv12_info(int width, int height) {
|
||||
int nv12_width = VENUS_Y_STRIDE(COLOR_FMT_NV12, width);
|
||||
int nv12_height = VENUS_Y_SCANLINES(COLOR_FMT_NV12, height);
|
||||
assert(nv12_width == VENUS_UV_STRIDE(COLOR_FMT_NV12, width));
|
||||
assert(nv12_height / 2 == VENUS_UV_SCANLINES(COLOR_FMT_NV12, height));
|
||||
size_t nv12_buffer_size = 2346 * nv12_width; // comes from v4l2_format.fmt.pix_mp.plane_fmt[0].sizeimage
|
||||
return {nv12_width, nv12_height, nv12_buffer_size};
|
||||
}
|
||||
|
||||
CameraServer::CameraServer(std::pair<int, int> camera_size[MAX_CAMERAS]) {
|
||||
for (int i = 0; i < MAX_CAMERAS; ++i) {
|
||||
@@ -25,7 +37,9 @@ void CameraServer::startVipcServer() {
|
||||
for (auto &cam : cameras_) {
|
||||
if (cam.width > 0 && cam.height > 0) {
|
||||
rInfo("camera[%d] frame size %dx%d", cam.type, cam.width, cam.height);
|
||||
vipc_server_->create_buffers(cam.stream_type, YUV_BUFFER_COUNT, false, cam.width, cam.height);
|
||||
auto [nv12_width, nv12_height, nv12_buffer_size] = get_nv12_info(cam.width, cam.height);
|
||||
vipc_server_->create_buffers_with_sizes(cam.stream_type, YUV_BUFFER_COUNT, false, cam.width, cam.height,
|
||||
nv12_buffer_size, nv12_width, nv12_width * nv12_height);
|
||||
if (!cam.thread.joinable()) {
|
||||
cam.thread = std::thread(&CameraServer::cameraThread, this, std::ref(cam));
|
||||
}
|
||||
@@ -38,7 +52,7 @@ void CameraServer::cameraThread(Camera &cam) {
|
||||
auto read_frame = [&](FrameReader *fr, int frame_id) {
|
||||
VisionBuf *yuv_buf = vipc_server_->get_buffer(cam.stream_type);
|
||||
assert(yuv_buf);
|
||||
bool ret = fr->get(frame_id, (uint8_t *)yuv_buf->addr);
|
||||
bool ret = fr->get(frame_id, yuv_buf);
|
||||
return ret ? yuv_buf : nullptr;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
#include "cereal/visionipc/visionipc_server.h"
|
||||
#include "common/queue.h"
|
||||
#include "tools/replay/framereader.h"
|
||||
#include "tools/replay/logreader.h"
|
||||
|
||||
std::tuple<size_t, size_t, size_t> get_nv12_info(int width, int height);
|
||||
|
||||
class CameraServer {
|
||||
public:
|
||||
CameraServer(std::pair<int, int> camera_size[MAX_CAMERAS] = nullptr);
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
#include "tools/replay/consoleui.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <initializer_list>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
#include "common/version.h"
|
||||
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
#include <fstream>
|
||||
|
||||
#include "common/util.h"
|
||||
#include "system/hardware/hw.h"
|
||||
#include "tools/replay/util.h"
|
||||
|
||||
std::string cacheFilePath(const std::string &url) {
|
||||
static std::string cache_path = [] {
|
||||
const std::string comma_cache = util::getenv("COMMA_CACHE", "/tmp/comma_download_cache/");
|
||||
const std::string comma_cache = Path::download_cache_root();
|
||||
util::create_directories(comma_cache, 0755);
|
||||
return comma_cache.back() == '/' ? comma_cache : comma_cache + "/";
|
||||
}();
|
||||
|
||||
+14
-18
@@ -2,9 +2,8 @@
|
||||
#include "tools/replay/util.h"
|
||||
|
||||
#include <cassert>
|
||||
#include "libyuv.h"
|
||||
|
||||
#include "cereal/visionipc/visionbuf.h"
|
||||
#include <algorithm>
|
||||
#include "third_party/libyuv/include/libyuv.h"
|
||||
|
||||
#ifdef __APPLE__
|
||||
#define HW_DEVICE_TYPE AV_HWDEVICE_TYPE_VIDEOTOOLBOX
|
||||
@@ -118,7 +117,6 @@ bool FrameReader::load(const std::byte *data, size_t size, bool no_hw_decoder, s
|
||||
|
||||
width = (decoder_ctx->width + 3) & ~3;
|
||||
height = decoder_ctx->height;
|
||||
visionbuf_compute_aligned_width_and_height(width, height, &aligned_width, &aligned_height);
|
||||
|
||||
if (has_hw_decoder && !no_hw_decoder) {
|
||||
if (!initHardwareDecoder(HW_DEVICE_TYPE)) {
|
||||
@@ -177,15 +175,15 @@ bool FrameReader::initHardwareDecoder(AVHWDeviceType hw_device_type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FrameReader::get(int idx, uint8_t *yuv) {
|
||||
assert(yuv != nullptr);
|
||||
bool FrameReader::get(int idx, VisionBuf *buf) {
|
||||
assert(buf != nullptr);
|
||||
if (!valid_ || idx < 0 || idx >= packets.size()) {
|
||||
return false;
|
||||
}
|
||||
return decode(idx, yuv);
|
||||
return decode(idx, buf);
|
||||
}
|
||||
|
||||
bool FrameReader::decode(int idx, uint8_t *yuv) {
|
||||
bool FrameReader::decode(int idx, VisionBuf *buf) {
|
||||
int from_idx = idx;
|
||||
if (idx != prev_idx + 1 && key_frames_count_ > 1) {
|
||||
// seeking to the nearest key frame
|
||||
@@ -201,7 +199,7 @@ bool FrameReader::decode(int idx, uint8_t *yuv) {
|
||||
for (int i = from_idx; i <= idx; ++i) {
|
||||
AVFrame *f = decodeFrame(packets[i]);
|
||||
if (f && i == idx) {
|
||||
return copyBuffers(f, yuv);
|
||||
return copyBuffers(f, buf);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -233,22 +231,20 @@ AVFrame *FrameReader::decodeFrame(AVPacket *pkt) {
|
||||
}
|
||||
}
|
||||
|
||||
bool FrameReader::copyBuffers(AVFrame *f, uint8_t *yuv) {
|
||||
assert(f != nullptr && yuv != nullptr);
|
||||
uint8_t *y = yuv;
|
||||
uint8_t *uv = y + width * height;
|
||||
bool FrameReader::copyBuffers(AVFrame *f, VisionBuf *buf) {
|
||||
assert(f != nullptr && buf != nullptr);
|
||||
if (hw_pix_fmt == HW_PIX_FMT) {
|
||||
for (int i = 0; i < height/2; i++) {
|
||||
memcpy(y + (i*2 + 0)*width, f->data[0] + (i*2 + 0)*f->linesize[0], width);
|
||||
memcpy(y + (i*2 + 1)*width, f->data[0] + (i*2 + 1)*f->linesize[0], width);
|
||||
memcpy(uv + i*width, f->data[1] + i*f->linesize[1], width);
|
||||
memcpy(buf->y + (i*2 + 0)*buf->stride, f->data[0] + (i*2 + 0)*f->linesize[0], width);
|
||||
memcpy(buf->y + (i*2 + 1)*buf->stride, f->data[0] + (i*2 + 1)*f->linesize[0], width);
|
||||
memcpy(buf->uv + i*buf->stride, f->data[1] + i*f->linesize[1], width);
|
||||
}
|
||||
} else {
|
||||
libyuv::I420ToNV12(f->data[0], f->linesize[0],
|
||||
f->data[1], f->linesize[1],
|
||||
f->data[2], f->linesize[2],
|
||||
y, width,
|
||||
uv, width,
|
||||
buf->y, buf->stride,
|
||||
buf->uv, buf->stride,
|
||||
width, height);
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "cereal/visionipc/visionbuf.h"
|
||||
#include "tools/replay/filereader.h"
|
||||
|
||||
extern "C" {
|
||||
@@ -22,19 +23,18 @@ public:
|
||||
bool load(const std::string &url, bool no_hw_decoder = false, std::atomic<bool> *abort = nullptr, bool local_cache = false,
|
||||
int chunk_size = -1, int retries = 0);
|
||||
bool load(const std::byte *data, size_t size, bool no_hw_decoder = false, std::atomic<bool> *abort = nullptr);
|
||||
bool get(int idx, uint8_t *yuv);
|
||||
bool get(int idx, VisionBuf *buf);
|
||||
int getYUVSize() const { return width * height * 3 / 2; }
|
||||
size_t getFrameCount() const { return packets.size(); }
|
||||
bool valid() const { return valid_; }
|
||||
|
||||
int width = 0, height = 0;
|
||||
int aligned_width = 0, aligned_height = 0;
|
||||
|
||||
private:
|
||||
bool initHardwareDecoder(AVHWDeviceType hw_device_type);
|
||||
bool decode(int idx, uint8_t *yuv);
|
||||
bool decode(int idx, VisionBuf *buf);
|
||||
AVFrame * decodeFrame(AVPacket *pkt);
|
||||
bool copyBuffers(AVFrame *f, uint8_t *yuv);
|
||||
bool copyBuffers(AVFrame *f, VisionBuf *buf);
|
||||
|
||||
std::vector<AVPacket*> packets;
|
||||
std::unique_ptr<AVFrame, AVFrameDeleter>av_frame_, hw_frame;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "tools/replay/logreader.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include "tools/replay/filereader.h"
|
||||
#include "tools/replay/util.h"
|
||||
|
||||
Event::Event(const kj::ArrayPtr<const capnp::word> &amsg, bool frame) : reader(amsg), frame(frame) {
|
||||
@@ -29,8 +30,7 @@ Event::Event(const kj::ArrayPtr<const capnp::word> &amsg, bool frame) : reader(a
|
||||
LogReader::LogReader(size_t memory_pool_block_size) {
|
||||
#ifdef HAS_MEMORY_RESOURCE
|
||||
const size_t buf_size = sizeof(Event) * memory_pool_block_size;
|
||||
pool_buffer_ = ::operator new(buf_size);
|
||||
mbr_ = new std::pmr::monotonic_buffer_resource(pool_buffer_, buf_size);
|
||||
mbr_ = std::make_unique<std::pmr::monotonic_buffer_resource>(buf_size);
|
||||
#endif
|
||||
events.reserve(memory_pool_block_size);
|
||||
}
|
||||
@@ -39,16 +39,9 @@ LogReader::~LogReader() {
|
||||
for (Event *e : events) {
|
||||
delete e;
|
||||
}
|
||||
|
||||
#ifdef HAS_MEMORY_RESOURCE
|
||||
delete mbr_;
|
||||
::operator delete(pool_buffer_);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool LogReader::load(const std::string &url, std::atomic<bool> *abort,
|
||||
const std::set<cereal::Event::Which> &allow,
|
||||
bool local_cache, int chunk_size, int retries) {
|
||||
bool LogReader::load(const std::string &url, std::atomic<bool> *abort, bool local_cache, int chunk_size, int retries) {
|
||||
raw_ = FileReader(local_cache, chunk_size, retries).read(url, abort);
|
||||
if (raw_.empty()) return false;
|
||||
|
||||
@@ -56,36 +49,30 @@ bool LogReader::load(const std::string &url, std::atomic<bool> *abort,
|
||||
raw_ = decompressBZ2(raw_, abort);
|
||||
if (raw_.empty()) return false;
|
||||
}
|
||||
return parse(allow, abort);
|
||||
return parse(abort);
|
||||
}
|
||||
|
||||
bool LogReader::load(const std::byte *data, size_t size, std::atomic<bool> *abort) {
|
||||
raw_.assign((const char *)data, size);
|
||||
return parse({}, abort);
|
||||
return parse(abort);
|
||||
}
|
||||
|
||||
bool LogReader::parse(const std::set<cereal::Event::Which> &allow, std::atomic<bool> *abort) {
|
||||
bool LogReader::parse(std::atomic<bool> *abort) {
|
||||
try {
|
||||
kj::ArrayPtr<const capnp::word> words((const capnp::word *)raw_.data(), raw_.size() / sizeof(capnp::word));
|
||||
while (words.size() > 0 && !(abort && *abort)) {
|
||||
#ifdef HAS_MEMORY_RESOURCE
|
||||
Event *evt = new (mbr_) Event(words);
|
||||
Event *evt = new (mbr_.get()) Event(words);
|
||||
#else
|
||||
Event *evt = new Event(words);
|
||||
#endif
|
||||
if (!allow.empty() && allow.find(evt->which) == allow.end()) {
|
||||
words = kj::arrayPtr(evt->reader.getEnd(), words.end());
|
||||
delete evt;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add encodeIdx packet again as a frame packet for the video stream
|
||||
if (evt->which == cereal::Event::ROAD_ENCODE_IDX ||
|
||||
evt->which == cereal::Event::DRIVER_ENCODE_IDX ||
|
||||
evt->which == cereal::Event::WIDE_ROAD_ENCODE_IDX) {
|
||||
|
||||
#ifdef HAS_MEMORY_RESOURCE
|
||||
Event *frame_evt = new (mbr_) Event(words, true);
|
||||
Event *frame_evt = new (mbr_.get()) Event(words, true);
|
||||
#else
|
||||
Event *frame_evt = new Event(words, true);
|
||||
#endif
|
||||
|
||||
@@ -5,11 +5,12 @@
|
||||
#include <memory_resource>
|
||||
#endif
|
||||
|
||||
#include <set>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "cereal/gen/cpp/log.capnp.h"
|
||||
#include "system/camerad/cameras/camera_common.h"
|
||||
#include "tools/replay/filereader.h"
|
||||
|
||||
const CameraType ALL_CAMERAS[] = {RoadCam, DriverCam, WideRoadCam};
|
||||
const int MAX_CAMERAS = std::size(ALL_CAMERAS);
|
||||
@@ -52,16 +53,15 @@ class LogReader {
|
||||
public:
|
||||
LogReader(size_t memory_pool_block_size = DEFAULT_EVENT_MEMORY_POOL_BLOCK_SIZE);
|
||||
~LogReader();
|
||||
bool load(const std::string &url, std::atomic<bool> *abort = nullptr, const std::set<cereal::Event::Which> &allow = {},
|
||||
bool load(const std::string &url, std::atomic<bool> *abort = nullptr,
|
||||
bool local_cache = false, int chunk_size = -1, int retries = 0);
|
||||
bool load(const std::byte *data, size_t size, std::atomic<bool> *abort = nullptr);
|
||||
std::vector<Event*> events;
|
||||
|
||||
private:
|
||||
bool parse(const std::set<cereal::Event::Which> &allow, std::atomic<bool> *abort);
|
||||
bool parse(std::atomic<bool> *abort);
|
||||
std::string raw_;
|
||||
#ifdef HAS_MEMORY_RESOURCE
|
||||
std::pmr::monotonic_buffer_resource *mbr_ = nullptr;
|
||||
void *pool_buffer_ = nullptr;
|
||||
std::unique_ptr<std::pmr::monotonic_buffer_resource> mbr_;
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -6,9 +6,13 @@
|
||||
#include "tools/replay/replay.h"
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
#ifdef __APPLE__
|
||||
// With all sockets opened, we might hit the default limit of 256 on macOS
|
||||
util::set_file_descriptor_limit(1024);
|
||||
#endif
|
||||
|
||||
QCoreApplication app(argc, argv);
|
||||
|
||||
const QStringList base_blacklist = {"uiDebug", "userFlag"};
|
||||
const std::tuple<QString, REPLAY_FLAGS, QString> flags[] = {
|
||||
{"dcam", REPLAY_FLAG_DCAM, "load driver camera"},
|
||||
{"ecam", REPLAY_FLAG_ECAM, "load wide road camera"},
|
||||
@@ -17,7 +21,7 @@ int main(int argc, char *argv[]) {
|
||||
{"qcam", REPLAY_FLAG_QCAMERA, "load qcamera"},
|
||||
{"no-hw-decoder", REPLAY_FLAG_NO_HW_DECODER, "disable HW video decoding"},
|
||||
{"no-vipc", REPLAY_FLAG_NO_VIPC, "do not output video"},
|
||||
{"all", REPLAY_FLAG_ALL_SERVICES, "do output all messages including " + base_blacklist.join(", ") +
|
||||
{"all", REPLAY_FLAG_ALL_SERVICES, "do output all messages including uiDebug, userFlag"
|
||||
". this may causes issues when used along with UI"}
|
||||
};
|
||||
|
||||
@@ -59,7 +63,7 @@ int main(int argc, char *argv[]) {
|
||||
op_prefix.reset(new OpenpilotPrefix(prefix.toStdString()));
|
||||
}
|
||||
|
||||
Replay *replay = new Replay(route, allow, block, base_blacklist, nullptr, replay_flags, parser.value("data_dir"), &app);
|
||||
Replay *replay = new Replay(route, allow, block, nullptr, replay_flags, parser.value("data_dir"), &app);
|
||||
if (!parser.value("c").isEmpty()) {
|
||||
replay->setSegmentCacheLimit(parser.value("c").toInt());
|
||||
}
|
||||
|
||||
+46
-82
@@ -7,40 +7,25 @@
|
||||
#include "cereal/services.h"
|
||||
#include "common/params.h"
|
||||
#include "common/timing.h"
|
||||
#include "system/hardware/hw.h"
|
||||
#include "tools/replay/util.h"
|
||||
|
||||
Replay::Replay(QString route, QStringList allow, QStringList block, QStringList base_blacklist, SubMaster *sm_, uint32_t flags, QString data_dir, QObject *parent)
|
||||
: sm(sm_), flags_(flags), QObject(parent) {
|
||||
std::vector<const char *> s;
|
||||
Replay::Replay(QString route, QStringList allow, QStringList block, SubMaster *sm_,
|
||||
uint32_t flags, QString data_dir, QObject *parent) : sm(sm_), flags_(flags), QObject(parent) {
|
||||
if (!(flags_ & REPLAY_FLAG_ALL_SERVICES)) {
|
||||
block << "uiDebug" << "userFlag";
|
||||
}
|
||||
auto event_struct = capnp::Schema::from<cereal::Event>().asStruct();
|
||||
sockets_.resize(event_struct.getUnionFields().size());
|
||||
for (const auto &it : services) {
|
||||
uint16_t which = event_struct.getFieldByName(it.name).getProto().getDiscriminantValue();
|
||||
if ((which == cereal::Event::Which::UI_DEBUG || which == cereal::Event::Which::USER_FLAG) &&
|
||||
!(flags & REPLAY_FLAG_ALL_SERVICES) &&
|
||||
!allow.contains(it.name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((allow.empty() || allow.contains(it.name)) && !block.contains(it.name)) {
|
||||
sockets_[which] = it.name;
|
||||
if (!allow.empty() || !block.empty()) {
|
||||
allow_list.insert((cereal::Event::Which)which);
|
||||
}
|
||||
s.push_back(it.name);
|
||||
}
|
||||
}
|
||||
|
||||
if (!allow_list.empty()) {
|
||||
// the following events are needed for replay to work properly.
|
||||
allow_list.insert(cereal::Event::Which::INIT_DATA);
|
||||
allow_list.insert(cereal::Event::Which::CAR_PARAMS);
|
||||
if (sockets_[cereal::Event::Which::PANDA_STATES] != nullptr) {
|
||||
allow_list.insert(cereal::Event::Which::PANDA_STATE_D_E_P_R_E_C_A_T_E_D);
|
||||
for (const auto &[name, _] : services) {
|
||||
if (!block.contains(name.c_str()) && (allow.empty() || allow.contains(name.c_str()))) {
|
||||
uint16_t which = event_struct.getFieldByName(name).getProto().getDiscriminantValue();
|
||||
sockets_[which] = name.c_str();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<const char *> s;
|
||||
std::copy_if(sockets_.begin(), sockets_.end(), std::back_inserter(s),
|
||||
[](const char *name) { return name != nullptr; });
|
||||
qDebug() << "services " << s;
|
||||
qDebug() << "loading route " << route;
|
||||
|
||||
@@ -150,13 +135,12 @@ void Replay::buildTimeline() {
|
||||
[(int)cereal::ControlsState::AlertStatus::CRITICAL] = TimelineType::AlertCritical,
|
||||
};
|
||||
|
||||
for (auto it = segments_.cbegin(); it != segments_.cend() && !exit_; ++it) {
|
||||
LogReader log;
|
||||
if (!log.load(route_->at(it->first).qlog.toStdString(), &exit_,
|
||||
{cereal::Event::Which::CONTROLS_STATE, cereal::Event::Which::USER_FLAG},
|
||||
!hasFlag(REPLAY_FLAG_NO_FILE_CACHE), 0, 3)) continue;
|
||||
const auto &route_segments = route_->segments();
|
||||
for (auto it = route_segments.cbegin(); it != route_segments.cend() && !exit_; ++it) {
|
||||
std::shared_ptr<LogReader> 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();
|
||||
|
||||
@@ -184,6 +168,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,36 +212,26 @@ void Replay::segmentLoadFinished(bool success) {
|
||||
if (!success) {
|
||||
Segment *seg = qobject_cast<Segment *>(sender());
|
||||
rWarning("failed to load segment %d, removing it from current replay list", seg->seg_num);
|
||||
segments_.erase(seg->seg_num);
|
||||
updateEvents([&]() {
|
||||
segments_.erase(seg->seg_num);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
queueSegment();
|
||||
}
|
||||
|
||||
void Replay::queueSegment() {
|
||||
if (segments_.empty()) return;
|
||||
|
||||
SegmentMap::iterator begin, cur;
|
||||
begin = cur = segments_.lower_bound(std::min(current_segment_.load(), segments_.rbegin()->first));
|
||||
int distance = std::max<int>(std::ceil(segment_cache_limit / 2.0) - 1, segment_cache_limit - std::distance(cur, segments_.end()));
|
||||
for (int i = 0; begin != segments_.begin() && i < distance; ++i) {
|
||||
--begin;
|
||||
}
|
||||
auto end = begin;
|
||||
for (int i = 0; end != segments_.end() && i < segment_cache_limit; ++i) {
|
||||
++end;
|
||||
}
|
||||
auto cur = segments_.lower_bound(current_segment_.load());
|
||||
if (cur == segments_.end()) return;
|
||||
|
||||
auto begin = std::prev(cur, std::min<int>(segment_cache_limit / 2, std::distance(segments_.begin(), cur)));
|
||||
auto end = std::next(begin, std::min<int>(segment_cache_limit, segments_.size()));
|
||||
// load one segment at a time
|
||||
for (auto it = cur; it != end; ++it) {
|
||||
auto &[n, seg] = *it;
|
||||
if ((seg && !seg->isLoaded()) || !seg) {
|
||||
if (!seg) {
|
||||
rDebug("loading segment %d...", n);
|
||||
seg = std::make_unique<Segment>(n, route_->at(n), flags_, allow_list);
|
||||
QObject::connect(seg.get(), &Segment::loadFinished, this, &Replay::segmentLoadFinished);
|
||||
}
|
||||
break;
|
||||
}
|
||||
auto it = std::find_if(cur, end, [](auto &it) { return !it.second || !it.second->isLoaded(); });
|
||||
if (it != end && !it->second) {
|
||||
rDebug("loading segment %d...", it->first);
|
||||
it->second = std::make_unique<Segment>(it->first, route_->at(it->first), flags_);
|
||||
QObject::connect(it->second.get(), &Segment::loadFinished, this, &Replay::segmentLoadFinished);
|
||||
}
|
||||
|
||||
mergeSegments(begin, end);
|
||||
@@ -292,13 +268,11 @@ void Replay::mergeSegments(const SegmentMap::iterator &begin, const SegmentMap::
|
||||
new_events_->clear();
|
||||
new_events_->reserve(new_events_size);
|
||||
for (int n : segments_need_merge) {
|
||||
const auto &e = segments_[n]->log->events;
|
||||
if (e.size() > 0) {
|
||||
auto insert_from = e.begin();
|
||||
if (new_events_->size() > 0 && (*insert_from)->which == cereal::Event::Which::INIT_DATA) ++insert_from;
|
||||
auto middle = new_events_->insert(new_events_->end(), insert_from, e.end());
|
||||
std::inplace_merge(new_events_->begin(), middle, new_events_->end(), Event::lessThan());
|
||||
}
|
||||
size_t size = new_events_->size();
|
||||
const auto &events = segments_[n]->log->events;
|
||||
std::copy_if(events.begin(), events.end(), std::back_inserter(*new_events_),
|
||||
[this](auto e) { return e->which < sockets_.size() && sockets_[e->which] != nullptr; });
|
||||
std::inplace_merge(new_events_->begin(), new_events_->begin() + size, new_events_->end(), Event::lessThan());
|
||||
}
|
||||
|
||||
if (stream_thread_) {
|
||||
@@ -307,7 +281,8 @@ void Replay::mergeSegments(const SegmentMap::iterator &begin, const SegmentMap::
|
||||
updateEvents([&]() {
|
||||
events_.swap(new_events_);
|
||||
segments_merged_ = segments_need_merge;
|
||||
return true;
|
||||
// Do not wake up the stream thread if the current segment has not been merged.
|
||||
return isSegmentMerged(current_segment_) || (segments_.count(current_segment_) == 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -315,13 +290,12 @@ void Replay::mergeSegments(const SegmentMap::iterator &begin, const SegmentMap::
|
||||
void Replay::startStream(const Segment *cur_segment) {
|
||||
const auto &events = cur_segment->log->events;
|
||||
|
||||
// get route start time from initData
|
||||
auto it = std::find_if(events.begin(), events.end(), [](auto e) { return e->which == cereal::Event::Which::INIT_DATA; });
|
||||
route_start_ts_ = it != events.end() ? (*it)->mono_time : events[0]->mono_time;
|
||||
cur_mono_time_ += route_start_ts_;
|
||||
// each segment has an INIT_DATA
|
||||
route_start_ts_ = events.front()->mono_time;
|
||||
cur_mono_time_ += route_start_ts_ - 1;
|
||||
|
||||
// write CarParams
|
||||
it = std::find_if(events.begin(), events.end(), [](auto e) { return e->which == cereal::Event::Which::CAR_PARAMS; });
|
||||
auto it = std::find_if(events.begin(), events.end(), [](auto e) { return e->which == cereal::Event::Which::CAR_PARAMS; });
|
||||
if (it != events.end()) {
|
||||
car_fingerprint_ = (*it)->event.getCarParams().getCarFingerprint();
|
||||
capnp::MallocMessageBuilder builder;
|
||||
@@ -413,22 +387,12 @@ void Replay::stream() {
|
||||
cur_mono_time_ = evt->mono_time;
|
||||
setCurrentSegment(toSeconds(cur_mono_time_) / 60);
|
||||
|
||||
// migration for pandaState -> pandaStates to keep UI working for old segments
|
||||
if (cur_which == cereal::Event::Which::PANDA_STATE_D_E_P_R_E_C_A_T_E_D &&
|
||||
sockets_[cereal::Event::Which::PANDA_STATES] != nullptr) {
|
||||
MessageBuilder msg;
|
||||
auto ps = msg.initEvent().initPandaStates(1);
|
||||
ps[0].setIgnitionLine(true);
|
||||
ps[0].setPandaType(cereal::PandaState::PandaType::DOS);
|
||||
pm->send(sockets_[cereal::Event::Which::PANDA_STATES], msg);
|
||||
}
|
||||
|
||||
if (cur_which < sockets_.size() && sockets_[cur_which] != nullptr) {
|
||||
if (sockets_[cur_which] != nullptr) {
|
||||
// keep time
|
||||
long etime = (cur_mono_time_ - evt_start_ts) / speed_;
|
||||
long rtime = nanos_since_boot() - loop_start_ts;
|
||||
long behind_ns = etime - rtime;
|
||||
// if behind_ns is greater than 1 second, it means that an invalid segemnt is skipped by seeking/replaying
|
||||
// if behind_ns is greater than 1 second, it means that an invalid segment is skipped by seeking/replaying
|
||||
if (behind_ns >= 1 * 1e9 || speed_ != prev_replay_speed) {
|
||||
// reset event start times
|
||||
evt_start_ts = cur_mono_time_;
|
||||
@@ -454,7 +418,7 @@ void Replay::stream() {
|
||||
}
|
||||
|
||||
if (eit == events_->end() && !hasFlag(REPLAY_FLAG_NO_LOOP)) {
|
||||
int last_segment = segments_.rbegin()->first;
|
||||
int last_segment = segments_.empty() ? 0 : segments_.rbegin()->first;
|
||||
if (current_segment_ >= last_segment && isSegmentMerged(last_segment)) {
|
||||
rInfo("reaches the end of route, restart from beginning");
|
||||
QMetaObject::invokeMethod(this, std::bind(&Replay::seekTo, this, 0, false), Qt::QueuedConnection);
|
||||
|
||||
+14
-8
@@ -1,13 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
|
||||
#include <QThread>
|
||||
|
||||
#include "tools/replay/camera.h"
|
||||
#include "tools/replay/route.h"
|
||||
|
||||
const QString DEMO_ROUTE = "4cf7a6ad03080c90|2021-09-29--13-46-36";
|
||||
const QString DEMO_ROUTE = "a2a0ccea32023010|2023-07-27--13-01-19";
|
||||
|
||||
// one segment uses about 100M of memory
|
||||
constexpr int MIN_SEGMENTS_CACHE = 5;
|
||||
@@ -36,13 +43,14 @@ enum class FindFlag {
|
||||
|
||||
enum class TimelineType { None, Engaged, AlertInfo, AlertWarning, AlertCritical, UserFlag };
|
||||
typedef bool (*replayEventFilter)(const Event *, void *);
|
||||
Q_DECLARE_METATYPE(std::shared_ptr<LogReader>);
|
||||
|
||||
class Replay : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Replay(QString route, QStringList allow, QStringList block, QStringList base_blacklist, SubMaster *sm = nullptr,
|
||||
uint32_t flags = REPLAY_FLAG_NONE, QString data_dir = "", QObject *parent = 0);
|
||||
Replay(QString route, QStringList allow, QStringList block, SubMaster *sm = nullptr,
|
||||
uint32_t flags = REPLAY_FLAG_NONE, QString data_dir = "", QObject *parent = 0);
|
||||
~Replay();
|
||||
bool load();
|
||||
void start(int seconds = 0);
|
||||
@@ -72,7 +80,7 @@ public:
|
||||
inline void setSpeed(float speed) { speed_ = speed; }
|
||||
inline float getSpeed() const { return speed_; }
|
||||
inline const std::vector<Event *> *events() const { return events_.get(); }
|
||||
inline const std::map<int, std::unique_ptr<Segment>> &segments() const { return segments_; };
|
||||
inline const std::map<int, std::unique_ptr<Segment>> &segments() const { return segments_; }
|
||||
inline const std::string &carFingerprint() const { return car_fingerprint_; }
|
||||
inline const std::vector<std::tuple<double, double, TimelineType>> getTimeline() {
|
||||
std::lock_guard lk(timeline_lock);
|
||||
@@ -83,6 +91,7 @@ signals:
|
||||
void streamStarted();
|
||||
void segmentsMerged();
|
||||
void seekedTo(double sec);
|
||||
void qLogLoaded(int segnum, std::shared_ptr<LogReader> qlog);
|
||||
|
||||
protected slots:
|
||||
void segmentLoadFinished(bool success);
|
||||
@@ -104,8 +113,6 @@ protected:
|
||||
}
|
||||
|
||||
QThread *stream_thread_ = nullptr;
|
||||
|
||||
// logs
|
||||
std::mutex stream_lock_;
|
||||
std::condition_variable stream_cv_;
|
||||
std::atomic<bool> updating_events_ = false;
|
||||
@@ -132,9 +139,8 @@ protected:
|
||||
std::mutex timeline_lock;
|
||||
QFuture<void> timeline_future;
|
||||
std::vector<std::tuple<double, double, TimelineType>> timeline;
|
||||
std::set<cereal::Event::Which> allow_list;
|
||||
std::string car_fingerprint_;
|
||||
float speed_ = 1.0;
|
||||
std::atomic<float> speed_ = 1.0;
|
||||
replayEventFilter event_filter = nullptr;
|
||||
void *filter_opaque = nullptr;
|
||||
int segment_cache_limit = MIN_SEGMENTS_CACHE;
|
||||
|
||||
@@ -6,11 +6,10 @@
|
||||
#include <QJsonDocument>
|
||||
#include <QRegExp>
|
||||
#include <QtConcurrent>
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "system/hardware/hw.h"
|
||||
#include "selfdrive/ui/qt/api.h"
|
||||
#include "system/hardware/hw.h"
|
||||
#include "tools/replay/replay.h"
|
||||
#include "tools/replay/util.h"
|
||||
|
||||
@@ -100,9 +99,7 @@ void Route::addFileToSegment(int n, const QString &file) {
|
||||
|
||||
// class Segment
|
||||
|
||||
Segment::Segment(int n, const SegmentFile &files, uint32_t flags,
|
||||
const std::set<cereal::Event::Which> &allow)
|
||||
: seg_num(n), flags(flags), allow(allow) {
|
||||
Segment::Segment(int n, const SegmentFile &files, uint32_t flags) : seg_num(n), flags(flags) {
|
||||
// [RoadCam, DriverCam, WideRoadCam, log]. fallback to qcamera/qlog
|
||||
const std::array file_list = {
|
||||
(flags & REPLAY_FLAG_QCAMERA) || files.road_cam.isEmpty() ? files.qcamera : files.road_cam,
|
||||
@@ -133,7 +130,7 @@ void Segment::loadFile(int id, const std::string file) {
|
||||
success = frames[id]->load(file, flags & REPLAY_FLAG_NO_HW_DECODER, &abort_, local_cache, 20 * 1024 * 1024, 3);
|
||||
} else {
|
||||
log = std::make_unique<LogReader>();
|
||||
success = log->load(file, &abort_, allow, local_cache, 0, 3);
|
||||
success = log->load(file, &abort_, local_cache, 0, 3);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QFutureSynchronizer>
|
||||
|
||||
@@ -50,7 +54,7 @@ class Segment : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Segment(int n, const SegmentFile &files, uint32_t flags, const std::set<cereal::Event::Which> &allow = {});
|
||||
Segment(int n, const SegmentFile &files, uint32_t flags);
|
||||
~Segment();
|
||||
inline bool isLoaded() const { return !loading_ && !abort_; }
|
||||
|
||||
@@ -68,5 +72,4 @@ protected:
|
||||
std::atomic<int> loading_ = 0;
|
||||
QFutureSynchronizer<void> synchronizer_;
|
||||
uint32_t flags;
|
||||
std::set<cereal::Event::Which> allow;
|
||||
};
|
||||
|
||||
+15
-4
@@ -4,22 +4,22 @@
|
||||
#include <curl/curl.h>
|
||||
#include <openssl/sha.h>
|
||||
|
||||
#include <cstdarg>
|
||||
#include <cstring>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <numeric>
|
||||
#include <utility>
|
||||
|
||||
#include "common/timing.h"
|
||||
#include "common/util.h"
|
||||
|
||||
ReplayMessageHandler message_handler = nullptr;
|
||||
DownloadProgressHandler download_progress_handler = nullptr;
|
||||
|
||||
void installMessageHandler(ReplayMessageHandler handler) { message_handler = handler; }
|
||||
void installDownloadProgressHandler(DownloadProgressHandler handler) { download_progress_handler = handler; }
|
||||
|
||||
void logMessage(ReplyMsgType type, const char *fmt, ...) {
|
||||
static std::mutex lock;
|
||||
@@ -91,6 +91,11 @@ size_t write_cb(char *data, size_t size, size_t count, void *userp) {
|
||||
size_t dumy_write_cb(char *data, size_t size, size_t count, void *userp) { return size * count; }
|
||||
|
||||
struct DownloadStats {
|
||||
void installDownloadProgressHandler(DownloadProgressHandler handler) {
|
||||
std::lock_guard lk(lock);
|
||||
download_progress_handler = handler;
|
||||
}
|
||||
|
||||
void add(const std::string &url, uint64_t total_bytes) {
|
||||
std::lock_guard lk(lock);
|
||||
items[url] = {0, total_bytes};
|
||||
@@ -118,10 +123,17 @@ struct DownloadStats {
|
||||
std::mutex lock;
|
||||
std::map<std::string, std::pair<uint64_t, uint64_t>> items;
|
||||
double prev_tm = 0;
|
||||
DownloadProgressHandler download_progress_handler = nullptr;
|
||||
};
|
||||
|
||||
static DownloadStats download_stats;
|
||||
|
||||
} // namespace
|
||||
|
||||
void installDownloadProgressHandler(DownloadProgressHandler handler) {
|
||||
download_stats.installDownloadProgressHandler(handler);
|
||||
}
|
||||
|
||||
std::string formattedDataSize(size_t size) {
|
||||
if (size < 1024) {
|
||||
return std::to_string(size) + " B";
|
||||
@@ -164,7 +176,6 @@ std::string getUrlWithoutQuery(const std::string &url) {
|
||||
|
||||
template <class T>
|
||||
bool httpDownload(const std::string &url, T &buf, size_t chunk_size, size_t content_length, std::atomic<bool> *abort) {
|
||||
static DownloadStats download_stats;
|
||||
download_stats.add(url, content_length);
|
||||
|
||||
int parts = 1;
|
||||
|
||||
Reference in New Issue
Block a user