#pragma once #include #include #include #include #include #include #include #include #include #include #include "tools/replay/camera.h" #include "tools/replay/route.h" const QString DEMO_ROUTE = "a2a0ccea32023010|2023-07-27--13-01-19"; // one segment uses about 100M of memory constexpr int MIN_SEGMENTS_CACHE = 5; enum REPLAY_FLAGS { REPLAY_FLAG_NONE = 0x0000, REPLAY_FLAG_DCAM = 0x0002, REPLAY_FLAG_ECAM = 0x0004, REPLAY_FLAG_NO_LOOP = 0x0010, REPLAY_FLAG_NO_FILE_CACHE = 0x0020, REPLAY_FLAG_QCAMERA = 0x0040, REPLAY_FLAG_NO_HW_DECODER = 0x0100, REPLAY_FLAG_NO_VIPC = 0x0400, REPLAY_FLAG_ALL_SERVICES = 0x0800, }; enum class FindFlag { nextEngagement, nextDisEngagement, nextUserFlag, nextInfo, nextWarning, nextCritical }; enum class TimelineType { None, Engaged, AlertInfo, AlertWarning, AlertCritical, UserFlag }; typedef bool (*replayEventFilter)(const Event *, void *); Q_DECLARE_METATYPE(std::shared_ptr); class Replay : public QObject { Q_OBJECT public: 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(); RouteLoadError lastRouteError() const { return route_->lastError(); } void start(int seconds = 0); void stop(); void pause(bool pause); void seekToFlag(FindFlag flag); void seekTo(double seconds, bool relative); inline bool isPaused() const { return user_paused_; } // the filter is called in streaming thread.try to return quickly from it to avoid blocking streaming. // the filter function must return true if the event should be filtered. // otherwise it must return false. inline void installEventFilter(replayEventFilter filter, void *opaque) { filter_opaque = opaque; event_filter = filter; } inline int segmentCacheLimit() const { return segment_cache_limit; } inline void setSegmentCacheLimit(int n) { segment_cache_limit = std::max(MIN_SEGMENTS_CACHE, n); } inline bool hasFlag(REPLAY_FLAGS flag) const { return flags_ & flag; } inline void addFlag(REPLAY_FLAGS flag) { flags_ |= flag; } inline void removeFlag(REPLAY_FLAGS flag) { flags_ &= ~flag; } inline const Route* route() const { return route_.get(); } inline double currentSeconds() const { return double(cur_mono_time_ - route_start_ts_) / 1e9; } inline QDateTime routeDateTime() const { return route_date_time_; } inline QDateTime currentDateTime() const { return route_date_time_.addSecs(currentSeconds()); } inline uint64_t routeStartNanos() const { return route_start_ts_; } inline double toSeconds(uint64_t mono_time) const { return (mono_time - route_start_ts_) / 1e9; } inline double minSeconds() const { return !segments_.empty() ? segments_.begin()->first * 60 : 0; } inline double maxSeconds() const { return max_seconds_; } inline void setSpeed(float speed) { speed_ = speed; } inline float getSpeed() const { return speed_; } inline const std::vector *events() const { return &events_; } inline const std::map> &segments() const { return segments_; } inline const std::string &carFingerprint() const { return car_fingerprint_; } inline const std::vector> getTimeline() { std::lock_guard lk(timeline_lock); return timeline_; } signals: void streamStarted(); void segmentsMerged(); void seeking(double sec); void seekedTo(double sec); void qLogLoaded(std::shared_ptr qlog); void minMaxTimeChanged(double min_sec, double max_sec); protected slots: void segmentLoadFinished(bool success); protected: typedef std::map> SegmentMap; std::optional find(FindFlag flag); void pauseStreamThread(); void startStream(const Segment *cur_segment); void streamThread(); void updateSegmentsCache(); void loadSegmentInRange(SegmentMap::iterator begin, SegmentMap::iterator cur, SegmentMap::iterator end); void mergeSegments(const SegmentMap::iterator &begin, const SegmentMap::iterator &end); void updateEvents(const std::function& update_events_function); std::vector::const_iterator publishEvents(std::vector::const_iterator first, std::vector::const_iterator last); void publishMessage(const Event *e); void publishFrame(const Event *e); void buildTimeline(); void checkSeekProgress(); inline bool isSegmentMerged(int n) const { return merged_segments_.count(n) > 0; } pthread_t stream_thread_id = 0; QThread *stream_thread_ = nullptr; std::mutex stream_lock_; bool user_paused_ = false; std::condition_variable stream_cv_; std::atomic current_segment_ = 0; std::optional seeking_to_; SegmentMap segments_; // the following variables must be protected with stream_lock_ std::atomic exit_ = false; std::atomic paused_ = false; bool events_ready_ = false; QDateTime route_date_time_; uint64_t route_start_ts_ = 0; std::atomic cur_mono_time_ = 0; std::atomic max_seconds_ = 0; std::vector events_; std::set merged_segments_; // messaging SubMaster *sm = nullptr; std::unique_ptr pm; std::vector sockets_; std::vector filters_; std::unique_ptr route_; std::unique_ptr camera_server_; std::atomic flags_ = REPLAY_FLAG_NONE; std::mutex timeline_lock; QFuture timeline_future; std::vector> timeline_; std::string car_fingerprint_; std::atomic speed_ = 1.0; replayEventFilter event_filter = nullptr; void *filter_opaque = nullptr; int segment_cache_limit = MIN_SEGMENTS_CACHE; };