From db9c7e12543d72cfa1042f1515fa96e68af28a77 Mon Sep 17 00:00:00 2001
From: liyulin <1012327963@qq.com>
Date: Fri, 22 May 2026 21:36:32 +0800
Subject: [PATCH] Tools: Add Cyber Recorder GUI Lite
---
modules/tools/cyber_recorder_gui/BUILD | 75 ++++
.../tools/cyber_recorder_gui/CMakeLists.txt | 62 +++
modules/tools/cyber_recorder_gui/README.md | 119 +++++
modules/tools/cyber_recorder_gui/README_cn.md | 119 +++++
.../tools/cyber_recorder_gui/cyberfile.xml | 23 +
.../tools/cyber_recorder_gui/doubleslider.cc | 105 +++++
.../tools/cyber_recorder_gui/doubleslider.h | 45 ++
modules/tools/cyber_recorder_gui/info.cc | 127 ++++++
modules/tools/cyber_recorder_gui/info.h | 46 ++
.../launch/cyber_recorder_gui_lite.launch | 10 +
modules/tools/cyber_recorder_gui/main.cpp | 16 +
.../tools/cyber_recorder_gui/mainwindow.cpp | 347 +++++++++++++++
modules/tools/cyber_recorder_gui/mainwindow.h | 94 ++++
.../tools/cyber_recorder_gui/mainwindow.ui | 175 ++++++++
.../cyber_recorder_gui/player/play_param.h | 50 +++
.../cyber_recorder_gui/player/play_task.cc | 54 +++
.../cyber_recorder_gui/player/play_task.h | 66 +++
.../player/play_task_buffer.cc | 73 +++
.../player/play_task_buffer.h | 57 +++
.../player/play_task_consumer.cc | 129 ++++++
.../player/play_task_consumer.h | 83 ++++
.../player/play_task_producer.cc | 419 ++++++++++++++++++
.../player/play_task_producer.h | 133 ++++++
.../tools/cyber_recorder_gui/player/player.cc | 321 ++++++++++++++
.../tools/cyber_recorder_gui/player/player.h | 130 ++++++
.../cyber_recorder_gui/record_playbar.cpp | 40 ++
.../tools/cyber_recorder_gui/record_playbar.h | 44 ++
27 files changed, 2962 insertions(+)
create mode 100755 modules/tools/cyber_recorder_gui/BUILD
create mode 100755 modules/tools/cyber_recorder_gui/CMakeLists.txt
create mode 100644 modules/tools/cyber_recorder_gui/README.md
create mode 100644 modules/tools/cyber_recorder_gui/README_cn.md
create mode 100755 modules/tools/cyber_recorder_gui/cyberfile.xml
create mode 100755 modules/tools/cyber_recorder_gui/doubleslider.cc
create mode 100755 modules/tools/cyber_recorder_gui/doubleslider.h
create mode 100755 modules/tools/cyber_recorder_gui/info.cc
create mode 100755 modules/tools/cyber_recorder_gui/info.h
create mode 100644 modules/tools/cyber_recorder_gui/launch/cyber_recorder_gui_lite.launch
create mode 100755 modules/tools/cyber_recorder_gui/main.cpp
create mode 100755 modules/tools/cyber_recorder_gui/mainwindow.cpp
create mode 100755 modules/tools/cyber_recorder_gui/mainwindow.h
create mode 100755 modules/tools/cyber_recorder_gui/mainwindow.ui
create mode 100755 modules/tools/cyber_recorder_gui/player/play_param.h
create mode 100755 modules/tools/cyber_recorder_gui/player/play_task.cc
create mode 100755 modules/tools/cyber_recorder_gui/player/play_task.h
create mode 100755 modules/tools/cyber_recorder_gui/player/play_task_buffer.cc
create mode 100755 modules/tools/cyber_recorder_gui/player/play_task_buffer.h
create mode 100755 modules/tools/cyber_recorder_gui/player/play_task_consumer.cc
create mode 100755 modules/tools/cyber_recorder_gui/player/play_task_consumer.h
create mode 100755 modules/tools/cyber_recorder_gui/player/play_task_producer.cc
create mode 100755 modules/tools/cyber_recorder_gui/player/play_task_producer.h
create mode 100755 modules/tools/cyber_recorder_gui/player/player.cc
create mode 100755 modules/tools/cyber_recorder_gui/player/player.h
create mode 100755 modules/tools/cyber_recorder_gui/record_playbar.cpp
create mode 100755 modules/tools/cyber_recorder_gui/record_playbar.h
diff --git a/modules/tools/cyber_recorder_gui/BUILD b/modules/tools/cyber_recorder_gui/BUILD
new file mode 100755
index 00000000000..b9d5badda22
--- /dev/null
+++ b/modules/tools/cyber_recorder_gui/BUILD
@@ -0,0 +1,75 @@
+load("//tools:apollo_package.bzl", "apollo_package", "apollo_cc_binary", "apollo_qt_library")
+
+package(default_visibility = ["//visibility:public"])
+
+apollo_cc_binary(
+ name = "cyber_recorder_gui_lite",
+ srcs = ["main.cpp"],
+ copts = [
+ "-Iexternal/qt",
+ "-std=c++17",
+ "-fPIC",
+ ],
+ linkopts = [
+ "-pthread",
+ ],
+ deps = [
+ ":cyber_recorder_gui_lite_lib",
+ "//cyber",
+ "//cyber/proto:record_cc_proto",
+ "@qt//:qt_core",
+ "@qt//:qt_gui",
+ "@qt//:qt_widgets",
+ ],
+)
+
+apollo_qt_library(
+ name = "cyber_recorder_gui_lite_lib",
+ srcs=["mainwindow.cpp",
+ "record_playbar.cpp",
+ "info.cc",
+ "doubleslider.cc",
+ "player/play_task_buffer.cc",
+ "player/play_task_consumer.cc",
+ "player/play_task_producer.cc",
+ "player/play_task.cc",
+ "player/player.cc"],
+ hdrs=["mainwindow.h",
+ "record_playbar.h",
+ "info.h",
+ "doubleslider.h",
+ "player/play_param.h",
+ "player/play_task_buffer.h",
+ "player/play_task_consumer.h",
+ "player/play_task_producer.h",
+ "player/play_task.h",
+ "player/player.h"],
+ #srcs = glob(["*.cpp","*.cc"]),
+ #hdrs = glob(["*.h"]),
+ uis = glob(["*.ui"]),
+ copts = [
+ "-Iexternal/qt",
+ "-std=c++17",
+ "-fPIC",
+ ],
+ includes = [
+ ".",
+ ],
+ linkstatic = False,
+ deps = [
+ "//cyber",
+ "//cyber/common:cyber_common",
+ "//cyber/proto:record_cc_proto",
+ "@qt//:qt_core",
+ "@qt//:qt_gui",
+ "@qt//:qt_widgets",
+ ],
+)
+
+
+# TODO(all): Disable linter temporarily as the generated ui files should be
+# excluded from check. But we should also check the .h and .cc files, if they
+# are extracted to their own cc_libraries. See the TODO above.
+# cpplint()
+apollo_package(enable_source=False)
+apollo_package()
diff --git a/modules/tools/cyber_recorder_gui/CMakeLists.txt b/modules/tools/cyber_recorder_gui/CMakeLists.txt
new file mode 100755
index 00000000000..952e6acf5d4
--- /dev/null
+++ b/modules/tools/cyber_recorder_gui/CMakeLists.txt
@@ -0,0 +1,62 @@
+cmake_minimum_required(VERSION 3.5)
+
+project(cyber_recorder_gui_lite VERSION 0.1 LANGUAGES CXX)
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+set(CMAKE_AUTOUIC ON)
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_AUTORCC ON)
+
+set(CMAKE_CXX_STANDARD 11)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+find_package(QT NAMES Qt6 Qt5 COMPONENTS Widgets REQUIRED)
+find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets REQUIRED)
+
+set(PROJECT_SOURCES
+ main.cpp
+ mainwindow.cpp
+ mainwindow.h
+ mainwindow.ui
+ record_playbar.cpp
+ record_playbar.h
+
+)
+
+if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
+ qt_add_executable(cyber_recorder_gui_lite
+ MANUAL_FINALIZATION
+ ${PROJECT_SOURCES}
+ )
+# Define target properties for Android with Qt 6 as:
+# set_property(TARGET cyber_recorder_gui_lite APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
+# ${CMAKE_CURRENT_SOURCE_DIR}/android)
+# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation
+else()
+ if(ANDROID)
+ add_library(cyber_recorder_gui_lite SHARED
+ ${PROJECT_SOURCES}
+ )
+# Define properties for Android with Qt 5 after find_package() calls as:
+# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
+ else()
+ add_executable(cyber_recorder_gui_lite
+ ${PROJECT_SOURCES}
+ )
+ endif()
+endif()
+
+target_link_libraries(cyber_recorder_gui_lite PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)
+
+set_target_properties(cyber_recorder_gui_lite PROPERTIES
+ MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com
+ MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
+ MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
+ MACOSX_BUNDLE TRUE
+ WIN32_EXECUTABLE TRUE
+)
+
+if(QT_VERSION_MAJOR EQUAL 6)
+ qt_finalize_executable(cyber_recorder_gui_lite)
+endif()
diff --git a/modules/tools/cyber_recorder_gui/README.md b/modules/tools/cyber_recorder_gui/README.md
new file mode 100644
index 00000000000..d48489c5a04
--- /dev/null
+++ b/modules/tools/cyber_recorder_gui/README.md
@@ -0,0 +1,119 @@
+# Cyber Recorder GUI Lite
+
+`cyber_recorder_gui_lite` is a lightweight Qt graphical interface for replaying Apollo Cyber record files. It can open one record file or all record shards in a directory, display basic record information, and publish the recorded messages back to Cyber for debugging.
+
+This module is the reduced version of `modules/cyber_recorder_gui`. It is intentionally kept as a separate package and executable so that both versions can coexist without an install-name conflict.
+
+## Features
+
+- Open a single `.record` file.
+- Open a directory containing `.record` and `.record.*` shards.
+- Preview record metadata and channel information.
+- Play, pause, resume, and stop playback.
+- Seek during playback with the progress bar.
+- Display the selected time range and current playback status.
+
+The lite version does not include the full version's map selector or `log2worldsim` export workflow.
+
+## Build
+
+From the Apollo workspace root (`/apollo_workspace` inside the development container):
+
+```bash
+# Apollo buildtool workflow
+buildtool build -p modules/tools/cyber_recorder_gui --cpu
+```
+
+For a direct Bazel build (after the Apollo dependencies are installed):
+
+```bash
+bazel build //modules/tools/cyber_recorder_gui:cyber_recorder_gui_lite
+```
+
+The generated executable is:
+
+```text
+bazel-bin/modules/tools/cyber_recorder_gui/cyber_recorder_gui_lite
+```
+
+## Run
+
+Run the executable directly:
+
+```bash
+./bazel-bin/modules/tools/cyber_recorder_gui/cyber_recorder_gui_lite
+```
+
+Or start it with `cyber_launch`:
+
+```bash
+cyber_launch start modules/tools/cyber_recorder_gui/launch/cyber_recorder_gui_lite.launch
+```
+
+The application initializes the Cyber node `cyber_recorder_gui_lite` and shows a window titled **Cyber Recorder GUI Lite**.
+
+## Basic usage
+
+1. Click **File** and select one record file, or click **Dir** and select a directory.
+2. Check the metadata shown in the information panel.
+3. Click **Play** to start replay.
+4. Use **Pause** / **Resume** to control playback.
+5. Drag or click the upper progress bar to seek while playing.
+6. Click **Stop** to reset playback to the beginning.
+
+The directory picker starts at `/apollo_workspace/data/`. Directory playback loads files matching `*.record` and `*.record.*`, sorted by file name. Keep record shards in a naturally sortable sequence, for example:
+
+```text
+demo.record
+demo.record.00000
+demo.record.00001
+```
+
+## Playback defaults
+
+| Setting | Default |
+| --- | --- |
+| Playback rate | `1.0` |
+| Start time | Record beginning |
+| End time | Record end (`uint64_t` maximum is used as the default bound) |
+| Preload time | `3` seconds |
+| Loop playback | Disabled |
+| Channel selection | All channels when no filter is configured |
+
+The lower dual-handle slider currently updates the displayed `Range` label only; it does not restrict the actual playback interval.
+
+## Troubleshooting
+
+### No window appears
+
+Make sure the container has access to the host display (X11 or Wayland) and that Qt can connect to it.
+
+### A record cannot be opened
+
+Verify that the path points to an Apollo Cyber record, that the file is not corrupted, and that the current user can read it. You can inspect a file with Apollo's record tools before starting the GUI.
+
+### Other modules receive no messages
+
+Confirm that the record contains the expected channel and that the subscriber is running in the same Cyber environment. The following commands can help:
+
+```bash
+cyber_channel list
+cyber_channel echo /your/channel/name
+```
+
+### The progress bar does not seek
+
+Seeking is enabled while playback is active. Start playback before dragging or clicking the upper progress bar.
+
+## Source layout
+
+| Path | Purpose |
+| --- | --- |
+| `main.cpp` | Qt application entry point and Cyber initialization |
+| `mainwindow.ui` | Main window layout |
+| `mainwindow.cpp` / `mainwindow.h` | File selection, playback controls, and status updates |
+| `info.cc` / `info.h` | Record metadata reader |
+| `player/` | Playback tasks, buffering, and message publishing |
+| `launch/cyber_recorder_gui_lite.launch` | `cyber_launch` configuration |
+| `BUILD` | Bazel targets and package installation rule |
+| `cyberfile.xml` | Apollo package metadata (`cyber-recorder-gui-lite`) |
diff --git a/modules/tools/cyber_recorder_gui/README_cn.md b/modules/tools/cyber_recorder_gui/README_cn.md
new file mode 100644
index 00000000000..284fe8790c1
--- /dev/null
+++ b/modules/tools/cyber_recorder_gui/README_cn.md
@@ -0,0 +1,119 @@
+# Cyber Recorder GUI Lite 使用说明
+
+`cyber_recorder_gui_lite` 是一个基于 Qt 的 Apollo Cyber record 轻量级图形回放工具。它可以打开单个 record 文件,也可以加载目录中的分片 record 文件,显示 record 基础信息,并将消息重新发布到 Cyber 通道,方便调试依赖历史数据的模块。
+
+本模块是 `modules/cyber_recorder_gui` 的缩减版。它使用独立的包名、节点名和可执行文件名,可以与完整版同时保留,避免安装目标冲突。
+
+## 功能
+
+- 打开单个 `.record` 文件。
+- 打开包含 `.record` 和 `.record.*` 分片文件的目录。
+- 预览 record 元信息和通道信息。
+- 播放、暂停、继续和停止回放。
+- 使用进度条在播放过程中跳转。
+- 显示选定时间范围和当前播放状态。
+
+缩减版不包含完整版中的地图选择器和 `log2worldsim` 导出流程。
+
+## 编译
+
+在 Apollo 工作空间根目录执行(容器内通常为 `/apollo_workspace`):
+
+```bash
+# 使用 Apollo buildtool
+buildtool build -p modules/tools/cyber_recorder_gui --cpu
+```
+
+如果 Apollo 依赖已经安装,也可以直接使用 Bazel:
+
+```bash
+bazel build //modules/tools/cyber_recorder_gui:cyber_recorder_gui_lite
+```
+
+生成的可执行文件路径为:
+
+```text
+bazel-bin/modules/tools/cyber_recorder_gui/cyber_recorder_gui_lite
+```
+
+## 启动
+
+直接运行:
+
+```bash
+./bazel-bin/modules/tools/cyber_recorder_gui/cyber_recorder_gui_lite
+```
+
+或者使用 `cyber_launch`:
+
+```bash
+cyber_launch start modules/tools/cyber_recorder_gui/launch/cyber_recorder_gui_lite.launch
+```
+
+程序启动时会初始化 Cyber 节点 `cyber_recorder_gui_lite`,窗口标题为 **Cyber Recorder GUI Lite**。
+
+## 基本使用流程
+
+1. 点击 **File** 选择单个 record 文件,或点击 **Dir** 选择一个目录。
+2. 在信息框中检查 record 元信息。
+3. 点击 **Play** 开始回放。
+4. 使用 **Pause** / **Resume** 暂停或继续。
+5. 播放时拖动或点击上方进度条进行跳转。
+6. 点击 **Stop** 停止播放并将进度重置到起点。
+
+目录选择框默认打开 `/apollo_workspace/data/`。目录模式只加载文件名匹配 `*.record` 或 `*.record.*` 的文件,并按文件名排序。建议分片文件保持自然排序,例如:
+
+```text
+demo.record
+demo.record.00000
+demo.record.00001
+```
+
+## 默认播放参数
+
+| 参数 | 默认值 |
+| --- | --- |
+| 播放速率 | `1.0` |
+| 起始时间 | record 起点 |
+| 结束时间 | record 末尾(默认边界使用 `uint64_t` 最大值) |
+| 预加载时间 | `3` 秒 |
+| 循环播放 | 关闭 |
+| 通道选择 | 未配置过滤条件时播放全部通道 |
+
+下方双端滑条目前只更新 `Range` 文本,不会真正限制播放器的播放起止时间。
+
+## 常见问题
+
+### 启动后没有窗口
+
+请确认容器已经正确透传宿主机的 X11 或 Wayland 显示环境,并且 Qt 能够连接显示服务。
+
+### 无法打开 record 文件
+
+请确认路径指向 Apollo Cyber record 文件,文件没有损坏,且当前用户具有读取权限。可以先使用 Apollo 自带的 record 工具检查文件。
+
+### 其他模块收不到回放消息
+
+确认 record 中包含目标通道,并确认订阅模块和 GUI 运行在同一个 Cyber 环境中。可以执行:
+
+```bash
+cyber_channel list
+cyber_channel echo /your/channel/name
+```
+
+### 进度条无法跳转
+
+只有在播放状态下才会处理跳转。请先点击 **Play**,再拖动或点击上方进度条。
+
+## 源码结构
+
+| 路径 | 用途 |
+| --- | --- |
+| `main.cpp` | Qt 程序入口及 Cyber 初始化 |
+| `mainwindow.ui` | 主窗口布局 |
+| `mainwindow.cpp` / `mainwindow.h` | 文件选择、播放控制和状态更新 |
+| `info.cc` / `info.h` | record 元信息读取 |
+| `player/` | 播放任务、缓冲和消息发布 |
+| `launch/cyber_recorder_gui_lite.launch` | `cyber_launch` 启动配置 |
+| `BUILD` | Bazel 构建目标和安装规则 |
+| `cyberfile.xml` | Apollo 包元数据(`cyber-recorder-gui-lite`) |
diff --git a/modules/tools/cyber_recorder_gui/cyberfile.xml b/modules/tools/cyber_recorder_gui/cyberfile.xml
new file mode 100755
index 00000000000..ab6dbd5bcae
--- /dev/null
+++ b/modules/tools/cyber_recorder_gui/cyberfile.xml
@@ -0,0 +1,23 @@
+
+ cyber-recorder-gui-lite
+ local
+
+ This is a demo package
+
+
+ Apollo Developer
+ Apache License 2.0
+ https://www.apollo.auto/
+ https://github.com/ApolloAuto/apollo
+ https://github.com/ApolloAuto/apollo/issues
+
+ module
+ //modules/tools/cyber_recorder_gui
+ bazel
+
+
+ common
+ common-msgs
+ bazel-extend-tools
+ 3rd-qt5
+
\ No newline at end of file
diff --git a/modules/tools/cyber_recorder_gui/doubleslider.cc b/modules/tools/cyber_recorder_gui/doubleslider.cc
new file mode 100755
index 00000000000..26c89c19521
--- /dev/null
+++ b/modules/tools/cyber_recorder_gui/doubleslider.cc
@@ -0,0 +1,105 @@
+#include "doubleslider.h"
+
+DoubleSlider::DoubleSlider(QWidget* parent) :
+ QWidget(parent), m_min(0), m_max(100), m_leftValue(m_min), m_rightValue(m_max), m_activeHandle(None) {
+ setFixedHeight(40);
+}
+
+void DoubleSlider::setRange(int min, int max) {
+ m_min = min;
+ m_max = max;
+ if (m_max <= m_min) {
+ m_max = m_min + 1;
+ }
+ m_leftValue = std::clamp(m_leftValue, m_min, m_max);
+ m_rightValue = std::clamp(m_rightValue, m_min, m_max);
+ if (m_leftValue > m_rightValue) {
+ m_leftValue = m_rightValue;
+ }
+ update();
+}
+
+int DoubleSlider::leftValue() const {
+ return m_leftValue;
+}
+
+int DoubleSlider::rightValue() const {
+ return m_rightValue;
+}
+
+void DoubleSlider::paintEvent(QPaintEvent*) {
+ QPainter painter(this);
+ painter.setRenderHint(QPainter::Antialiasing);
+
+ // 绘制背景
+ painter.setPen(Qt::NoPen);
+ painter.setBrush(QColor(200, 200, 200));
+ painter.drawRoundedRect(0, height() / 2 - 2, width(), 4, 2, 2);
+
+ // 绘制活动区域
+ painter.setBrush(QColor(100, 150, 250));
+ painter.drawRoundedRect(leftHandlePos(), height() / 2 - 2, rightHandlePos() - leftHandlePos(), 4, 2, 2);
+
+ // 绘制左右滑块
+ drawHandle(&painter, leftHandlePos(), true);
+ drawHandle(&painter, rightHandlePos(), false);
+}
+
+void DoubleSlider::mousePressEvent(QMouseEvent* event) {
+ const int x = event->pos().x();
+ const int left = leftHandlePos();
+ const int right = rightHandlePos();
+
+ if (std::abs(x - left) < handleSize / 2) {
+ m_activeHandle = LeftHandle;
+ } else if (std::abs(x - right) < handleSize / 2) {
+ m_activeHandle = RightHandle;
+ } else {
+ m_activeHandle = None;
+ }
+ update();
+}
+
+void DoubleSlider::mouseMoveEvent(QMouseEvent* event) {
+ if (m_activeHandle == None)
+ return;
+
+ const int x = std::clamp(event->pos().x(), 0, width());
+ const int value = (x * (m_max - m_min) / width()) + m_min;
+
+ if (m_activeHandle == LeftHandle) {
+ m_leftValue = std::min(value, m_rightValue);
+ } else {
+ m_rightValue = std::max(value, m_leftValue);
+ }
+
+ update();
+ emit rangeChanged(m_leftValue, m_rightValue);
+}
+
+void DoubleSlider::mouseReleaseEvent(QMouseEvent*) {
+ m_activeHandle = None;
+ update();
+}
+
+int DoubleSlider::leftHandlePos() const {
+ if (m_max == m_min) {
+ return 0;
+ }
+ return (m_leftValue - m_min) * width() / (m_max - m_min);
+}
+
+int DoubleSlider::rightHandlePos() const {
+ if (m_max == m_min) {
+ return width();
+ }
+ return (m_rightValue - m_min) * width() / (m_max - m_min);
+}
+
+void DoubleSlider::drawHandle(QPainter* painter, int x, bool isLeft) {
+ painter->setPen(QPen(Qt::darkGray, 1));
+ painter->setBrush(isLeft ? Qt::white : QColor(220, 220, 220));
+
+ QRect handleRect(x - handleSize / 2, height() / 2 - handleSize / 2, handleSize, handleSize);
+ painter->drawEllipse(handleRect);
+}
diff --git a/modules/tools/cyber_recorder_gui/doubleslider.h b/modules/tools/cyber_recorder_gui/doubleslider.h
new file mode 100755
index 00000000000..e01e9886768
--- /dev/null
+++ b/modules/tools/cyber_recorder_gui/doubleslider.h
@@ -0,0 +1,45 @@
+#ifndef DOUBLESLIDER_H
+#define DOUBLESLIDER_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+class DoubleSlider : public QWidget {
+ Q_OBJECT
+public:
+ explicit DoubleSlider(QWidget* parent = nullptr);
+
+ void setRange(int min, int max);
+ int leftValue() const;
+ int rightValue() const;
+
+signals:
+ void rangeChanged(int min, int max);
+
+protected:
+ void paintEvent(QPaintEvent*) override;
+ void mousePressEvent(QMouseEvent* event) override;
+ void mouseMoveEvent(QMouseEvent* event) override;
+ void mouseReleaseEvent(QMouseEvent*) override;
+
+private:
+ enum Handle { None, LeftHandle, RightHandle };
+
+ int m_min;
+ int m_max;
+ int m_leftValue;
+ int m_rightValue;
+ Handle m_activeHandle;
+ const int handleSize = 16;
+
+ int leftHandlePos() const;
+ int rightHandlePos() const;
+ void drawHandle(QPainter* painter, int x, bool isLeft);
+};
+
+#endif // DOUBLESLIDER_H
\ No newline at end of file
diff --git a/modules/tools/cyber_recorder_gui/info.cc b/modules/tools/cyber_recorder_gui/info.cc
new file mode 100755
index 00000000000..204fd5806da
--- /dev/null
+++ b/modules/tools/cyber_recorder_gui/info.cc
@@ -0,0 +1,127 @@
+/******************************************************************************
+ * Copyright 2018 The Apollo Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *****************************************************************************/
+
+#include "modules/tools/cyber_recorder_gui/info.h"
+
+#include "cyber/record/record_message.h"
+
+namespace apollo {
+namespace goodman {
+namespace record {
+
+// using apollo::cyber::proto::ChannelCache;
+// using apollo::cyber::proto::Header;
+// using apollo::cyber::proto::Index;
+// using apollo::cyber::proto::SectionType;
+using namespace apollo::cyber::proto;
+using namespace apollo::cyber::record;
+using apollo::cyber::record::kGB;
+using apollo::cyber::record::kKB;
+using apollo::cyber::record::kMB;
+// using apollo::cyber::record::RecordFileReader;
+Info::Info() {}
+
+Info::~Info() {}
+
+bool Info::Display(const std::string& file, std::string* info_text) {
+ std::ostringstream channel_info_oss;
+
+ apollo::cyber::record::RecordFileReader file_reader;
+
+ if (!file_reader.Open(file)) {
+ AERROR << "open record file error. file: " << file;
+ return false;
+ }
+ Header hdr = file_reader.GetHeader();
+
+ channel_info_oss << setiosflags(std::ios::left);
+ channel_info_oss << setiosflags(std::ios::fixed);
+
+ int w = 16;
+ // file name
+ channel_info_oss << std::setw(w) << "record_file: " << file << std::endl;
+
+ // version
+ channel_info_oss << std::setw(w) << "version: " << hdr.major_version() << "." << hdr.minor_version() << std::endl;
+
+ // time and duration
+ auto begin_time_s = static_cast(hdr.begin_time()) / 1e9;
+ auto end_time_s = static_cast(hdr.end_time()) / 1e9;
+ auto duration_s = end_time_s - begin_time_s;
+ auto begin_time_str = UnixSecondsToString(static_cast(begin_time_s));
+ auto end_time_str = UnixSecondsToString(static_cast(end_time_s));
+ channel_info_oss << std::setw(w) << "duration: " << duration_s << " Seconds" << std::endl;
+ channel_info_oss << std::setw(w) << "begin_time: " << begin_time_str << std::endl;
+ channel_info_oss << std::setw(w) << "end_time: " << end_time_str << std::endl;
+
+ // size
+ channel_info_oss << std::setw(w) << "size: " << hdr.size() << " Bytes";
+ if (hdr.size() >= kGB) {
+ channel_info_oss << " (" << static_cast(hdr.size()) / kGB << " GB)";
+ } else if (hdr.size() >= kMB) {
+ channel_info_oss << " (" << static_cast(hdr.size()) / kMB << " MB)";
+ } else if (hdr.size() >= kKB) {
+ channel_info_oss << " (" << static_cast(hdr.size()) / kKB << " KB)";
+ }
+ channel_info_oss << std::endl;
+
+ // is_complete
+ channel_info_oss << std::setw(w) << "is_complete:";
+ if (hdr.is_complete()) {
+ channel_info_oss << "true";
+ } else {
+ channel_info_oss << "false";
+ }
+ channel_info_oss << std::endl;
+
+ // message_number
+ channel_info_oss << std::setw(w) << "message_number: " << hdr.message_number() << std::endl;
+
+ // channel_number
+ channel_info_oss << std::setw(w) << "channel_number: " << hdr.channel_number() << std::endl;
+
+ // read index section
+ if (!file_reader.ReadIndex()) {
+ AERROR << "read index section of the file fail. file: " << file;
+ return false;
+ }
+
+ // channel info
+ channel_info_oss << std::setw(w) << "channel_info: " << std::endl;
+
+ Index idx = file_reader.GetIndex();
+ for (int i = 0; i < idx.indexes_size(); ++i) {
+ ChannelCache* cache = idx.mutable_indexes(i)->mutable_channel_cache();
+ if (idx.mutable_indexes(i)->type() == SectionType::SECTION_CHANNEL) {
+ channel_info_oss << std::setw(w) << "";
+ channel_info_oss << resetiosflags(std::ios::right);
+ channel_info_oss << std::setw(50) << cache->name();
+ channel_info_oss << setiosflags(std::ios::right);
+ channel_info_oss << std::setw(8) << cache->message_number();
+ channel_info_oss << std::setw(0) << " messages: ";
+ channel_info_oss << cache->message_type();
+ channel_info_oss << std::endl;
+ }
+ }
+ *info_text = channel_info_oss.str();
+
+ file_reader.Close();
+ return true;
+}
+
+} // namespace record
+} // namespace goodman
+} // namespace apollo
diff --git a/modules/tools/cyber_recorder_gui/info.h b/modules/tools/cyber_recorder_gui/info.h
new file mode 100755
index 00000000000..4b44559a3bc
--- /dev/null
+++ b/modules/tools/cyber_recorder_gui/info.h
@@ -0,0 +1,46 @@
+/******************************************************************************
+ * Copyright 2018 The Apollo Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *****************************************************************************/
+
+#ifndef CYBER_TOOLS_CYBER_RECORDER_INFO_H_
+#define CYBER_TOOLS_CYBER_RECORDER_INFO_H_
+
+#include
+#include
+#include
+#include
+
+#include "cyber/common/time_conversion.h"
+#include "cyber/proto/record.pb.h"
+#include "cyber/record/file/record_file_reader.h"
+
+using ::apollo::cyber::common::UnixSecondsToString;
+
+namespace apollo {
+namespace goodman {
+namespace record {
+
+class Info {
+public:
+ Info();
+ ~Info();
+ bool Display(const std::string& file, std::string* info_text);
+};
+
+} // namespace record
+} // namespace goodman
+} // namespace apollo
+
+#endif // CYBER_TOOLS_CYBER_RECORDER_INFO_H_
diff --git a/modules/tools/cyber_recorder_gui/launch/cyber_recorder_gui_lite.launch b/modules/tools/cyber_recorder_gui/launch/cyber_recorder_gui_lite.launch
new file mode 100644
index 00000000000..4d3811914c2
--- /dev/null
+++ b/modules/tools/cyber_recorder_gui/launch/cyber_recorder_gui_lite.launch
@@ -0,0 +1,10 @@
+
+
+ cyber_recorder_gui_lite
+
+ binary
+
+ bazel-bin/modules/tools/cyber_recorder_gui/cyber_recorder_gui_lite
+
+
+
diff --git a/modules/tools/cyber_recorder_gui/main.cpp b/modules/tools/cyber_recorder_gui/main.cpp
new file mode 100755
index 00000000000..913383a9da8
--- /dev/null
+++ b/modules/tools/cyber_recorder_gui/main.cpp
@@ -0,0 +1,16 @@
+#include "mainwindow.h"
+
+#include "cyber/init.h"
+
+#include
+
+int main(int argc, char *argv[])
+{
+ QApplication a(argc, argv);
+ ::apollo::cyber::Init("cyber_recorder_gui_lite", "cyber_recorder_gui_lite");
+ MainWindow w;
+ w.show();
+ const int result = a.exec();
+ ::apollo::cyber::Clear();
+ return result;
+}
diff --git a/modules/tools/cyber_recorder_gui/mainwindow.cpp b/modules/tools/cyber_recorder_gui/mainwindow.cpp
new file mode 100755
index 00000000000..2dc37cb7587
--- /dev/null
+++ b/modules/tools/cyber_recorder_gui/mainwindow.cpp
@@ -0,0 +1,347 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "cyber/common/environment.h"
+#include "cyber/common/file.h"
+#include "cyber/common/time_conversion.h"
+#include "cyber/init.h"
+#include "modules/tools/cyber_recorder_gui/info.h"
+#include "mainwindow.h"
+#include "ui_mainwindow.h"
+#include
+#include
+#include
+
+#include
+
+MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow) {
+ ui->setupUi(this);
+ Qt::WindowFlags m_flags = windowFlags();
+ this->setWindowFlags(m_flags | Qt::WindowStaysOnTopHint); // this可以省略
+
+ playbar = new RecordPlaybar(Qt::Horizontal, this);
+ playbar->setRange(0, max_range_);
+ // 将 Playbar 添加到布局中
+ ui->horizontalLayout_2->addWidget(playbar);
+ slider_ = new DoubleSlider(this);
+
+ // slider = new DoubleSlider(this);
+ slider_->setRange(0, max_range_);
+ ui->horizontalLayout_2->addWidget(slider_);
+
+ play_process_timer = new QTimer(this);
+ play_process_timer->setInterval(100);
+ connect(play_process_timer, &QTimer::timeout, this, &MainWindow::PlayProcessTick);
+
+ connect(playbar, &QSlider::sliderReleased, this, &MainWindow::HandlePlaybarReleased);
+ connect(playbar, &QSlider::sliderPressed, this, &MainWindow::HandlePlaybarPressed);
+ connect(playbar, &RecordPlaybar::jumpSignal, this, &MainWindow::HandlePlaybarJump);
+
+ connect(playbar, &QSlider::actionTriggered, this, &MainWindow::HandlePlaybarActionTriggered);
+
+ connect(slider_, &DoubleSlider::rangeChanged, [this](int min, int max) {
+ if (player_ != nullptr) {
+ qDebug() << "Selected range:" << min << "-" << max;
+ UpdateRangeLabel();
+ }
+ });
+ UpdateUiState();
+}
+void MainWindow::GetTimelineRange(int min, int max) {
+ qDebug() << "Selected range:" << min << "-" << max;
+}
+MainWindow::~MainWindow() {
+ play_process_timer->stop();
+ if (player_ != nullptr) {
+ player_->Stop();
+ }
+ delete ui;
+}
+bool MainWindow::HasInitializedPlayer() const {
+ return player_ != nullptr && player_->is_initialized();
+}
+
+void MainWindow::SetStatus(const QString& status) {
+ ui->statusLabel->setText(status);
+}
+
+void MainWindow::UpdateRangeLabel() {
+ if (!HasInitializedPlayer()) {
+ ui->rangeLabel->setText("Range: full record");
+ return;
+ }
+ uint64_t total_ns = player_->get_parm().end_time_ns - player_->get_parm().begin_time_ns;
+ double start_percent = slider_->leftValue() / (double)max_range_;
+ double end_percent = slider_->rightValue() / (double)max_range_;
+ uint64_t range_start = total_ns * start_percent + player_->get_parm().begin_time_ns;
+ uint64_t range_end = total_ns * end_percent + player_->get_parm().begin_time_ns;
+ ui->rangeLabel->setText(
+ QString("Range: %1 - %2")
+ .arg(QString::fromStdString(nsToDateTime(range_start)))
+ .arg(QString::fromStdString(nsToDateTime(range_end))));
+}
+
+void MainWindow::UpdateUiState() {
+ const bool has_player = HasInitializedPlayer();
+ ui->pushButton_2->setEnabled(has_player);
+ ui->pushButton_3->setEnabled(has_player);
+ if (!has_player || !is_play_) {
+ ui->pushButton_2->setText("Play");
+ } else if (player_->is_paused()) {
+ ui->pushButton_2->setText("Resume");
+ } else {
+ ui->pushButton_2->setText("Pause");
+ }
+}
+
+bool MainWindow::LoadPlayerFromFiles() {
+ if (opt_file_vec.empty()) {
+ ui->textEdit->setText("No record files found.");
+ SetStatus("No record files found");
+ UpdateRangeLabel();
+ UpdateUiState();
+ return false;
+ }
+ if (is_play_) {
+ StopAndReset();
+ } else if (player_ != nullptr) {
+ player_->Stop();
+ }
+
+ play_param.is_play_all_channels = opt_all || opt_white_channels.empty();
+ play_param.is_loop_playback = opt_loop;
+ play_param.play_rate = opt_rate;
+ play_param.begin_time_ns = opt_begin;
+ play_param.end_time_ns = opt_end;
+ play_param.start_time_s = opt_start;
+ play_param.delay_time_s = opt_delay;
+ play_param.preload_time_s = opt_preload;
+ play_param.files_to_play.clear();
+ play_param.files_to_play.insert(opt_file_vec.begin(), opt_file_vec.end());
+
+ play_param.black_channels.clear();
+ play_param.black_channels.insert(opt_black_channels.begin(), opt_black_channels.end());
+ play_param.channels_to_play.clear();
+ play_param.channels_to_play.insert(opt_white_channels.begin(), opt_white_channels.end());
+
+ SetStatus("Loading record files");
+ UpdateUiState();
+ player_ = std::make_shared(play_param);
+ if (!player_->Init()) {
+ ui->textEdit->setText("Record file init failed.");
+ SetStatus("Record init failed");
+ qDebug() << "record file init failed!";
+ player_.reset();
+ UpdateRangeLabel();
+ UpdateUiState();
+ return false;
+ }
+ SetStatus(QString("%1 record file(s) loaded").arg(opt_file_vec.size()));
+ UpdateRangeLabel();
+ UpdateUiState();
+ return true;
+}
+
+void MainWindow::PlayProcessTick() {
+ if (!HasInitializedPlayer() || player_->total_progress_time_s() <= 0) {
+ play_process_timer->stop();
+ UpdateUiState();
+ return;
+ }
+ playbar->setValue(player_->progress_time_s() / player_->total_progress_time_s() * max_range_);
+ ui->label->setText(
+ double_to_qstring(player_->progress_time_s(), 1) + "/"
+ + double_to_qstring(player_->total_progress_time_s(), 1));
+ if (player_->progress_time_s() >= player_->total_progress_time_s()) {
+ StopAndReset();
+ }
+}
+
+QStringList MainWindow::getRecordFiles(const QString& directoryPath) {
+ QDir directory(directoryPath);
+ QStringList filters;
+ filters << "*.record" << "*.record.*";
+ directory.setNameFilters(filters);
+ directory.setFilter(QDir::Files | QDir::NoDotAndDotDot);
+ directory.setSorting(QDir::Name);
+ return directory.entryList();
+}
+
+void MainWindow::on_pushButton_5_clicked() {
+ QString selectedDirectory = QFileDialog::getExistingDirectory(
+ nullptr,
+ tr("Select Directory"),
+ "/apollo_workspace/data/",
+ QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
+ if (!selectedDirectory.isEmpty()) {
+ ui->lineEdit->setText(selectedDirectory);
+ QStringList files = getRecordFiles(selectedDirectory);
+ opt_file_vec.clear();
+ for (const auto& file_name : files) {
+ opt_file_vec.emplace_back((selectedDirectory + "/" + file_name).toStdString());
+ }
+
+ if (!files.empty()) {
+ const QString first_record_file = selectedDirectory + "/" + files.first();
+ Info info;
+ std::string info_text;
+ if (info.Display(first_record_file.toUtf8().constData(), &info_text)) {
+ ui->textEdit->setText(
+ QString("Loaded %1 record file(s).\n\nPreviewing first file:\n%2")
+ .arg(files.size())
+ .arg(QString::fromStdString(info_text)));
+ }
+ }
+
+ LoadPlayerFromFiles();
+
+ } else {
+ // 如果用户取消了选择,输出相应信息
+ SetStatus("Directory selection canceled");
+ qDebug() << "你取消了文件选择。";
+ }
+ return;
+}
+void MainWindow::on_pushButton_clicked() {
+ QString dialog_title = "select record file";
+
+ QString default_dir = QCoreApplication::applicationDirPath();
+ QString filter = "ALL (*.*);;文本文件 (*.record.*)";
+
+ QString selected_file_path = QFileDialog::getOpenFileName(nullptr, dialog_title, default_dir, filter);
+
+ if (!selected_file_path.isEmpty()) {
+ opt_file_vec.clear();
+ ui->lineEdit->setText(selected_file_path);
+ Info info;
+ std::string info_text;
+ bool info_result = info.Display(selected_file_path.toUtf8().constData(), &info_text);
+ if (!info_result) {
+ ui->textEdit->setText("Failed to read record file info.");
+ SetStatus("Failed to read record info");
+ UpdateUiState();
+ return;
+ }
+ ui->textEdit->setText(QString::fromStdString(info_text));
+ /////
+ opt_file_vec.emplace_back(ui->lineEdit->text().toStdString());
+ LoadPlayerFromFiles();
+
+ } else {
+ // 如果用户取消了选择,输出相应信息
+ SetStatus("File selection canceled");
+ qDebug() << "你取消了文件选择。";
+ }
+}
+
+void MainWindow::on_pushButton_2_clicked() {
+ if (HasInitializedPlayer()) {
+ if (!is_play_) {
+ is_play_ = player_->Start();
+ if (is_play_) {
+ play_process_timer->start();
+ SetStatus("Playing");
+ }
+ } else {
+ player_->set_is_paused();
+ if (player_->is_paused()) {
+ SetStatus("Paused");
+ } else {
+ SetStatus("Playing");
+ }
+ }
+ UpdateUiState();
+ }
+}
+void MainWindow::StopAndReset() {
+ play_process_timer->stop();
+ if (!HasInitializedPlayer()) {
+ is_play_ = false;
+ playbar->setValue(0);
+ ui->label->setText("0.0/0.0");
+ SetStatus("Ready");
+ UpdateRangeLabel();
+ UpdateUiState();
+ return;
+ }
+ if (player_->Reset()) {
+ is_play_ = false;
+ playbar->setValue(0);
+ ui->label->setText("0.0/0.0");
+ SetStatus("Stopped");
+ UpdateRangeLabel();
+ UpdateUiState();
+ return;
+ }
+ is_play_ = false;
+ SetStatus("Stopped");
+ UpdateUiState();
+}
+void MainWindow::on_pushButton_3_clicked() {
+ StopAndReset();
+}
+
+void MainWindow::HandlePlaybarPressed() {
+ qDebug() << "is_play_=" << is_play_;
+ if (is_play_) {
+ play_process_timer->stop();
+ }
+}
+
+void MainWindow::HandlePlaybarReleased() {
+ qDebug() << "is_play_=" << is_play_;
+ if (is_play_) {
+ double start_time = playbar->value() / (double)max_range_ * player_->total_progress_time_s();
+ qDebug() << "start_time";
+ play_process_timer->stop();
+ player_->ResetProcessTime(start_time);
+ play_process_timer->start();
+ }
+}
+void MainWindow::HandlePlaybarJump() {
+ qDebug() << "is_play_=" << is_play_;
+
+ if (is_play_) {
+ if (playbar->is_jump()) {
+ play_process_timer->stop();
+ double start_time = playbar->GetNewVal() / (double)max_range_ * player_->total_progress_time_s();
+ player_->ResetProcessTime(start_time);
+ play_process_timer->start();
+ }
+ }
+}
+
+std::string MainWindow::nsToDateTime(long long ns_timestamp) {
+ // 将纳秒转换为秒
+ auto sec_timestamp = std::chrono::seconds(ns_timestamp / 1000000000);
+
+ // 创建时间点
+ auto time_point = std::chrono::time_point(sec_timestamp);
+
+ // 将时间点转换为系统时间
+ std::time_t current_time = std::chrono::system_clock::to_time_t(time_point);
+
+ // 转换为 tm 结构
+ std::tm* time_info = std::localtime(¤t_time);
+ if (time_info == nullptr) {
+ return {};
+ }
+
+ // 格式化输出日期时间
+ char buffer[80];
+ std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", time_info);
+ return std::string(buffer);
+}
+void MainWindow::HandlePlaybarActionTriggered(int action) {
+ // if (action == 3 || action == 4) {
+ // int new_pos = playbar->GetNewVal();
+ // qDebug() << "action=" << action << " " << new_pos;
+ // }
+}
diff --git a/modules/tools/cyber_recorder_gui/mainwindow.h b/modules/tools/cyber_recorder_gui/mainwindow.h
new file mode 100755
index 00000000000..979df774be4
--- /dev/null
+++ b/modules/tools/cyber_recorder_gui/mainwindow.h
@@ -0,0 +1,94 @@
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include "modules/tools/cyber_recorder_gui/player/player.h"
+#include "modules/tools/cyber_recorder_gui/record_playbar.h"
+#include "doubleslider.h"
+
+QT_BEGIN_NAMESPACE
+using namespace apollo::goodman::record;
+
+// 删除bazel-out/k8-opt/bin/modules/tools/cyber_recorder_gui
+namespace Ui {
+
+class MainWindow;
+} // namespace Ui
+QT_END_NAMESPACE
+
+class MainWindow : public QMainWindow {
+ Q_OBJECT
+
+public:
+ MainWindow(QWidget *parent = nullptr);
+ ~MainWindow();
+
+private slots:
+ void on_pushButton_clicked();
+ void on_pushButton_2_clicked();
+ void PlayProcessTick();
+
+ void on_pushButton_3_clicked();
+ void on_pushButton_5_clicked();
+
+ void HandlePlaybarPressed();
+ void HandlePlaybarJump();
+ void HandlePlaybarReleased();
+ void HandlePlaybarActionTriggered(int action);
+
+private:
+ bool HasInitializedPlayer() const;
+ bool LoadPlayerFromFiles();
+ void SetStatus(const QString &status);
+ void UpdateRangeLabel();
+ void UpdateUiState();
+ void GetTimelineRange(int min, int max);
+ Ui::MainWindow *ui;
+ RecordPlaybar *playbar;
+ QTimer *play_process_timer;
+ PlayParam play_param;
+ // QThread play_process_thread;
+ std::vector opt_file_vec;
+ std::vector opt_output_vec;
+ std::vector opt_white_channels;
+ std::vector opt_black_channels;
+ bool is_play_ = false;
+ bool opt_all = false;
+ bool opt_loop = false;
+ float opt_rate = 1.0f;
+ uint64_t opt_begin = 0;
+ uint64_t opt_end = std::numeric_limits::max();
+ double opt_start = 0;
+ uint64_t opt_delay = 0;
+ uint32_t opt_preload = 3;
+ QString double_to_qstring(double dValue, int iPos = 1) {
+ QString qstrValue;
+ int iValue = dValue * std::pow(10, iPos);
+ qstrValue = QString::number(iValue);
+ int insertIndex = qstrValue.length() - iPos;
+ if (0 == insertIndex) {
+ qstrValue.insert(insertIndex, '.');
+ qstrValue.insert(insertIndex, '0');
+ } else {
+ qstrValue.insert(insertIndex, '.');
+ }
+
+ return qstrValue;
+ }
+ DoubleSlider *slider_;
+ int max_range_ = 100;
+ void StopAndReset();
+ std::string nsToDateTime(long long ns_timestamp);
+ std::shared_ptr player_;
+ QStringList getRecordFiles(const QString &directoryPath);
+};
+
+#endif // MAINWINDOW_H
diff --git a/modules/tools/cyber_recorder_gui/mainwindow.ui b/modules/tools/cyber_recorder_gui/mainwindow.ui
new file mode 100755
index 00000000000..82dd4ba1ff0
--- /dev/null
+++ b/modules/tools/cyber_recorder_gui/mainwindow.ui
@@ -0,0 +1,175 @@
+
+
+ MainWindow
+
+
+
+ 0
+ 0
+ 900
+ 220
+
+
+
+ Cyber Recorder GUI Lite
+
+
+
+
+ 8
+
+
+ 10
+
+
+ 10
+
+
+ 10
+
+
+ 10
+
+ -
+
+
+ 6
+
+
-
+
+
+ Select a record file or directory
+
+
+ false
+
+
+
+ -
+
+
+
+ 72
+ 30
+
+
+
+ File
+
+
+
+ -
+
+
+
+ 72
+ 30
+
+
+
+ Dir
+
+
+
+ -
+
+
+
+ 78
+ 30
+
+
+
+ Play
+
+
+
+ -
+
+
+
+ 78
+ 30
+
+
+
+ Stop
+
+
+
+
+
+ -
+
+
+ 4
+
+
-
+
+
+
+ 0
+ 22
+
+
+
+ Qt::LeftToRight
+
+
+ 0.0/0.0
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ Range: full record
+
+
+ Qt::AlignCenter
+
+
+
+
+
+ -
+
+
-
+
+
+
+ Monospace
+
+
+
+ true
+
+
+ Record information will appear here
+
+
+
+ -
+
+
+
+ 0
+ 22
+
+
+
+ Ready
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/tools/cyber_recorder_gui/player/play_param.h b/modules/tools/cyber_recorder_gui/player/play_param.h
new file mode 100755
index 00000000000..8d3110da4f1
--- /dev/null
+++ b/modules/tools/cyber_recorder_gui/player/play_param.h
@@ -0,0 +1,50 @@
+/******************************************************************************
+ * Copyright 2018 The Apollo Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *****************************************************************************/
+
+#ifndef CYBER_TOOLS_CYBER_RECORDER_PLAYER_PLAY_PARAM_H_
+#define CYBER_TOOLS_CYBER_RECORDER_PLAYER_PLAY_PARAM_H_
+
+#include
+#include
+#include
+#include
+
+namespace apollo {
+namespace goodman {
+namespace record {
+
+struct PlayParam {
+ bool is_play_all_channels = false;
+ bool is_loop_playback = false;
+ double play_rate = 1.0;
+ uint64_t begin_time_ns = 0;
+ uint64_t base_begin_time_ns = 0;
+ uint64_t end_time_ns = std::numeric_limits::max();
+ double start_time_s = 0;
+ uint64_t delay_time_s = 0;
+ uint32_t preload_time_s = 3;
+ std::set files_to_play;
+ std::set channels_to_play;
+ std::set black_channels;
+ // for dreamview_plus play record;use record_id to check source
+ std::string record_id = "";
+};
+
+} // namespace record
+} // namespace goodman
+} // namespace apollo
+
+#endif // CYBER_TOOLS_CYBER_RECORDER_PLAYER_PLAY_PARAM_H_
diff --git a/modules/tools/cyber_recorder_gui/player/play_task.cc b/modules/tools/cyber_recorder_gui/player/play_task.cc
new file mode 100755
index 00000000000..59f0a03b2b2
--- /dev/null
+++ b/modules/tools/cyber_recorder_gui/player/play_task.cc
@@ -0,0 +1,54 @@
+/******************************************************************************
+ * Copyright 2018 The Apollo Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *****************************************************************************/
+
+#include "modules/tools/cyber_recorder_gui/player/play_task.h"
+
+#include "cyber/common/log.h"
+
+namespace apollo {
+namespace goodman {
+namespace record {
+
+std::atomic PlayTask::played_msg_num_ = {0};
+
+PlayTask::PlayTask(
+ const MessagePtr& msg,
+ const WriterPtr& writer,
+ uint64_t msg_real_time_ns,
+ uint64_t msg_play_time_ns) :
+ msg_(msg), writer_(writer), msg_real_time_ns_(msg_real_time_ns), msg_play_time_ns_(msg_play_time_ns) {}
+
+void PlayTask::Play() {
+ if (writer_ == nullptr) {
+ AERROR << "writer is nullptr, can't write message.";
+ return;
+ }
+
+ if (!writer_->Write(msg_)) {
+ AERROR << "write message failed, played num: " << played_msg_num_.load() << ", real time: " << msg_real_time_ns_
+ << ", play time: " << msg_play_time_ns_;
+ return;
+ }
+
+ played_msg_num_.fetch_add(1);
+
+ ADEBUG << "write message succ, played num: " << played_msg_num_.load() << ", real time: " << msg_real_time_ns_
+ << ", play time: " << msg_play_time_ns_;
+}
+
+} // namespace record
+} // namespace goodman
+} // namespace apollo
diff --git a/modules/tools/cyber_recorder_gui/player/play_task.h b/modules/tools/cyber_recorder_gui/player/play_task.h
new file mode 100755
index 00000000000..3ee3de3f600
--- /dev/null
+++ b/modules/tools/cyber_recorder_gui/player/play_task.h
@@ -0,0 +1,66 @@
+/******************************************************************************
+ * Copyright 2018 The Apollo Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *****************************************************************************/
+
+#ifndef CYBER_TOOLS_CYBER_RECORDER_PLAYER_PLAY_TASK_H_
+#define CYBER_TOOLS_CYBER_RECORDER_PLAYER_PLAY_TASK_H_
+
+#include
+#include
+#include
+
+#include "cyber/message/raw_message.h"
+#include "cyber/node/writer.h"
+
+namespace apollo {
+namespace goodman {
+namespace record {
+
+using namespace apollo::cyber;
+
+class PlayTask {
+public:
+ using MessagePtr = std::shared_ptr;
+ using WriterPtr = std::shared_ptr>;
+
+ PlayTask(const MessagePtr& msg, const WriterPtr& writer, uint64_t msg_real_time_ns, uint64_t msg_play_time_ns);
+ virtual ~PlayTask() {}
+
+ void Play();
+
+ uint64_t msg_real_time_ns() const {
+ return msg_real_time_ns_;
+ }
+ uint64_t msg_play_time_ns() const {
+ return msg_play_time_ns_;
+ }
+ static uint64_t played_msg_num() {
+ return played_msg_num_.load();
+ }
+
+private:
+ MessagePtr msg_;
+ WriterPtr writer_;
+ uint64_t msg_real_time_ns_;
+ uint64_t msg_play_time_ns_;
+
+ static std::atomic played_msg_num_;
+};
+
+} // namespace record
+} // namespace goodman
+} // namespace apollo
+
+#endif // CYBER_TOOLS_CYBER_RECORDER_PLAYER_PLAY_TASK_H_
diff --git a/modules/tools/cyber_recorder_gui/player/play_task_buffer.cc b/modules/tools/cyber_recorder_gui/player/play_task_buffer.cc
new file mode 100755
index 00000000000..0686f5e768c
--- /dev/null
+++ b/modules/tools/cyber_recorder_gui/player/play_task_buffer.cc
@@ -0,0 +1,73 @@
+/******************************************************************************
+ * Copyright 2018 The Apollo Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *****************************************************************************/
+
+#include "modules/tools/cyber_recorder_gui/player/play_task_buffer.h"
+#include
+
+namespace apollo {
+namespace goodman {
+namespace record {
+
+PlayTaskBuffer::PlayTaskBuffer() {}
+
+PlayTaskBuffer::~PlayTaskBuffer() {
+ tasks_.clear();
+}
+
+size_t PlayTaskBuffer::Size() const {
+ std::lock_guard lck(mutex_);
+ return tasks_.size();
+}
+
+bool PlayTaskBuffer::Empty() const {
+ std::lock_guard lck(mutex_);
+ return tasks_.empty();
+}
+
+void PlayTaskBuffer::Push(const TaskPtr& task) {
+ if (task == nullptr) {
+ return;
+ }
+ std::lock_guard lck(mutex_);
+ tasks_.insert(std::make_pair(task->msg_play_time_ns(), task));
+}
+
+PlayTaskBuffer::TaskPtr PlayTaskBuffer::Front() {
+ std::lock_guard lck(mutex_);
+ if (tasks_.empty()) {
+ return nullptr;
+ }
+ auto res = tasks_.begin()->second;
+ return res;
+}
+
+void PlayTaskBuffer::PopFront() {
+ std::lock_guard lck(mutex_);
+ if (!tasks_.empty()) {
+ tasks_.erase(tasks_.begin());
+ }
+}
+
+void PlayTaskBuffer::Clear() {
+ std::lock_guard lck(mutex_);
+ while (!tasks_.empty()) {
+ tasks_.erase(tasks_.begin());
+ }
+}
+
+} // namespace record
+} // namespace goodman
+} // namespace apollo
diff --git a/modules/tools/cyber_recorder_gui/player/play_task_buffer.h b/modules/tools/cyber_recorder_gui/player/play_task_buffer.h
new file mode 100755
index 00000000000..cea919037cb
--- /dev/null
+++ b/modules/tools/cyber_recorder_gui/player/play_task_buffer.h
@@ -0,0 +1,57 @@
+/******************************************************************************
+ * Copyright 2018 The Apollo Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *****************************************************************************/
+
+#ifndef CYBER_TOOLS_CYBER_RECORDER_PLAYER_PLAY_TASK_BUFFER_H_
+#define CYBER_TOOLS_CYBER_RECORDER_PLAYER_PLAY_TASK_BUFFER_H_
+
+#include
+#include