From f254311363f3cd751904453fb06c7441da306ae4 Mon Sep 17 00:00:00 2001 From: liyulin <1012327963@qq.com> Date: Fri, 22 May 2026 21:36:32 +0800 Subject: [PATCH] Add Cyber Recorder Gui Simple --- modules/cyber_recorder_gui_simple/BUILD | 75 ++++ .../cyber_recorder_gui_simple/CMakeLists.txt | 62 +++ modules/cyber_recorder_gui_simple/README.md | 169 +++++++ .../cyber_recorder_gui_simple/cyberfile.xml | 23 + .../cyber_recorder_gui_simple/doubleslider.cc | 105 +++++ .../cyber_recorder_gui_simple/doubleslider.h | 45 ++ modules/cyber_recorder_gui_simple/info.cc | 127 ++++++ modules/cyber_recorder_gui_simple/info.h | 46 ++ .../launch/cyber_recorder_gui_simple.launch | 10 + modules/cyber_recorder_gui_simple/main.cpp | 16 + .../cyber_recorder_gui_simple/mainwindow.cpp | 343 ++++++++++++++ .../cyber_recorder_gui_simple/mainwindow.h | 90 ++++ .../cyber_recorder_gui_simple/mainwindow.ui | 175 ++++++++ .../player/play_param.h | 50 +++ .../player/play_task.cc | 54 +++ .../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 ++++++ .../player/player.cc | 321 ++++++++++++++ .../cyber_recorder_gui_simple/player/player.h | 130 ++++++ .../record_playbar.cpp | 40 ++ .../record_playbar.h | 44 ++ 26 files changed, 2885 insertions(+) create mode 100755 modules/cyber_recorder_gui_simple/BUILD create mode 100755 modules/cyber_recorder_gui_simple/CMakeLists.txt create mode 100755 modules/cyber_recorder_gui_simple/README.md create mode 100755 modules/cyber_recorder_gui_simple/cyberfile.xml create mode 100755 modules/cyber_recorder_gui_simple/doubleslider.cc create mode 100755 modules/cyber_recorder_gui_simple/doubleslider.h create mode 100755 modules/cyber_recorder_gui_simple/info.cc create mode 100755 modules/cyber_recorder_gui_simple/info.h create mode 100644 modules/cyber_recorder_gui_simple/launch/cyber_recorder_gui_simple.launch create mode 100755 modules/cyber_recorder_gui_simple/main.cpp create mode 100755 modules/cyber_recorder_gui_simple/mainwindow.cpp create mode 100755 modules/cyber_recorder_gui_simple/mainwindow.h create mode 100755 modules/cyber_recorder_gui_simple/mainwindow.ui create mode 100755 modules/cyber_recorder_gui_simple/player/play_param.h create mode 100755 modules/cyber_recorder_gui_simple/player/play_task.cc create mode 100755 modules/cyber_recorder_gui_simple/player/play_task.h create mode 100755 modules/cyber_recorder_gui_simple/player/play_task_buffer.cc create mode 100755 modules/cyber_recorder_gui_simple/player/play_task_buffer.h create mode 100755 modules/cyber_recorder_gui_simple/player/play_task_consumer.cc create mode 100755 modules/cyber_recorder_gui_simple/player/play_task_consumer.h create mode 100755 modules/cyber_recorder_gui_simple/player/play_task_producer.cc create mode 100755 modules/cyber_recorder_gui_simple/player/play_task_producer.h create mode 100755 modules/cyber_recorder_gui_simple/player/player.cc create mode 100755 modules/cyber_recorder_gui_simple/player/player.h create mode 100755 modules/cyber_recorder_gui_simple/record_playbar.cpp create mode 100755 modules/cyber_recorder_gui_simple/record_playbar.h diff --git a/modules/cyber_recorder_gui_simple/BUILD b/modules/cyber_recorder_gui_simple/BUILD new file mode 100755 index 00000000000..5d58308779b --- /dev/null +++ b/modules/cyber_recorder_gui_simple/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_simple", + srcs = ["main.cpp"], + copts = [ + "-Iexternal/qt", + "-std=c++17", + "-fPIC", + ], + linkopts = [ + "-pthread", + ], + deps = [ + ":cyber_recorder_gui_simple_lib", + "//cyber", + "//cyber/proto:record_cc_proto", + "@qt//:qt_core", + "@qt//:qt_gui", + "@qt//:qt_widgets", + ], +) + +apollo_qt_library( + name = "cyber_recorder_gui_simple_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/cyber_recorder_gui_simple/CMakeLists.txt b/modules/cyber_recorder_gui_simple/CMakeLists.txt new file mode 100755 index 00000000000..4b222a0545e --- /dev/null +++ b/modules/cyber_recorder_gui_simple/CMakeLists.txt @@ -0,0 +1,62 @@ +cmake_minimum_required(VERSION 3.5) + +project(cyber_recorder_gui_simple 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_simple + MANUAL_FINALIZATION + ${PROJECT_SOURCES} + ) +# Define target properties for Android with Qt 6 as: +# set_property(TARGET cyber_recorder_gui_simple 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_simple 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_simple + ${PROJECT_SOURCES} + ) + endif() +endif() + +target_link_libraries(cyber_recorder_gui_simple PRIVATE Qt${QT_VERSION_MAJOR}::Widgets) + +set_target_properties(cyber_recorder_gui_simple 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_simple) +endif() diff --git a/modules/cyber_recorder_gui_simple/README.md b/modules/cyber_recorder_gui_simple/README.md new file mode 100755 index 00000000000..a20441e2054 --- /dev/null +++ b/modules/cyber_recorder_gui_simple/README.md @@ -0,0 +1,169 @@ +# cyber_recorder_gui_simple 使用说明 + +`cyber_recorder_gui_simple` 是一个基于 Qt 的 Apollo Cyber record 简易回放工具。它可以加载单个 `.record` 文件,或加载目录下按文件名排序的 `.record` / `.record.*` 文件,并把 record 中的消息重新发布到 Cyber 通道中,便于调试感知、定位、规划等依赖历史数据的模块。 + +## 适用场景 + +- 查看 record 文件基础信息,例如时间范围、大小、消息数、通道列表和消息类型。 +- 通过图形界面播放、暂停、继续、停止 record。 +- 在播放过程中拖动进度条跳转到指定进度。 +- 一次加载一个目录中的连续分片 record 文件。 + +## 构建 + +在 Apollo 工作空间根目录执行: + +```bash +bazel build //modules/cyber_recorder_gui_simple:cyber_recorder_gui_simple +``` + +如果工程使用 AEM / buildtool 工作流,请先进入 Apollo 容器并完成依赖安装,再执行上面的 Bazel 构建命令。 + +## 启动 + +构建完成后,可以直接运行二进制: + +```bash +./bazel-bin/modules/cyber_recorder_gui_simple/cyber_recorder_gui_simple +``` + +也可以通过 `cyber_launch` 启动: + +```bash +cyber_launch start modules/cyber_recorder_gui_simple/launch/cyber_recorder_gui_simple.launch +``` + +窗口标题为 `Cyber Recorder GUI Simple`。程序启动后会初始化 Cyber 节点 `cyber_recorder_gui_simple`。 + +## 界面说明 + +| 控件 | 说明 | +| --- | --- | +| `File` | 选择单个 record 文件。选择成功后会读取并显示该文件信息。 | +| `Dir` | 选择一个目录,自动加载目录下的 `.record` 和 `.record.*` 文件。默认打开路径为 `/apollo_workplace/data/`。 | +| `Play` | 开始播放已加载的 record。播放后按钮文字会变为 `Pause`。 | +| `Pause` | 暂停当前播放。暂停后按钮文字会变为 `Resume`。 | +| `Resume` | 从暂停位置继续播放。 | +| `Stop` | 停止播放,并将播放进度重置到起点。 | +| 上方进度条 | 显示当前播放进度;播放过程中可拖动或点击跳转。 | +| 下方双端滑条 | 显示选中的时间范围文本。当前版本只更新 `Range` 显示,不会实际裁剪播放区间。 | +| 信息框 | 显示 record 文件信息、通道列表和加载状态。 | +| 状态栏 | 显示 `Ready`、`Loading record files`、`Playing`、`Paused`、`Stopped` 等状态。 | + +## 基本使用流程 + +1. 启动 GUI: + + ```bash + ./bazel-bin/modules/cyber_recorder_gui_simple/cyber_recorder_gui_simple + ``` + +2. 点击 `File` 选择单个 record 文件,或点击 `Dir` 选择 record 目录。 + +3. 确认信息框中显示 record 信息。目录模式下会显示加载文件数量,并预览第一个 record 文件的信息。 + +4. 点击 `Play` 开始回放。 + +5. 播放过程中可以: + + - 点击 `Pause` 暂停。 + - 点击 `Resume` 继续。 + - 拖动上方进度条跳转播放位置。 + - 点击 `Stop` 停止并回到起点。 + +6. 使用其他 Cyber 工具或业务模块订阅对应通道,检查回放消息。例如: + + ```bash + cyber_channel list + cyber_channel echo /your/channel/name + ``` + +## record 文件加载规则 + +### 单文件模式 + +点击 `File` 后选择一个 record 文件。程序会: + +1. 清空之前加载的文件列表。 +2. 读取该 record 的基础信息和通道索引。 +3. 初始化播放器。 + +### 目录模式 + +点击 `Dir` 后选择目录。程序会: + +1. 搜索目录下的普通文件。 +2. 只加载文件名匹配 `*.record` 或 `*.record.*` 的文件。 +3. 按文件名排序后加入播放列表。 +4. 预览第一个 record 文件的信息。 + +建议连续分片文件保持自然排序,例如: + +```text +demo.record +demo.record.00000 +demo.record.00001 +demo.record.00002 +``` + +## 当前默认播放参数 + +当前界面没有暴露命令行参数或高级设置,源码中的默认播放参数如下: + +| 参数 | 默认值 | 说明 | +| --- | --- | --- | +| 播放速率 | `1.0` | 原速播放。 | +| 起始时间 | `0` | 从 record 起点开始。 | +| 结束时间 | `uint64_t` 最大值 | 默认播放到 record 末尾。 | +| 预加载时间 | `3` 秒 | 播放任务预加载时间。 | +| 循环播放 | `false` | 默认不循环。 | +| 通道过滤 | 全通道 | 未配置白名单或黑名单时播放全部通道。 | + +## 常见问题 + +### 启动后没有窗口 + +确认当前环境支持图形界面显示。如果在容器中运行,需要确保 X11 / Wayland 显示环境已经正确透传。 + +### 选择文件后提示读取失败 + +可能原因: + +- 选择的文件不是 Apollo Cyber record 格式。 +- 文件损坏或 record 索引不完整。 +- 当前用户没有读取权限。 + +可以先用 Cyber 自带工具检查 record 文件: + +```bash +cyber_recorder info /path/to/file.record +``` + +### 点击 `Play` 后其他模块收不到消息 + +检查以下内容: + +- record 文件中是否包含目标通道。 +- 订阅模块是否已经启动并连接到同一个 Cyber 环境。 +- 使用 `cyber_channel list` 确认通道是否出现。 +- 使用 `cyber_channel echo ` 直接观察消息。 + +### 进度条跳转不生效 + +上方播放进度条的跳转只在播放状态下生效。先点击 `Play`,再拖动或点击进度条。 + +### 双端范围条没有裁剪播放 + +这是当前实现的限制。双端范围条目前只用于计算并显示 `Range: 开始时间 - 结束时间`,没有把选中范围写回播放器的 `begin_time_ns` / `end_time_ns`。 + +## 相关文件 + +| 文件 | 说明 | +| --- | --- | +| `main.cpp` | Qt 应用入口,并初始化 / 清理 Cyber。 | +| `mainwindow.ui` | 主窗口 UI 布局。 | +| `mainwindow.cpp` | 文件选择、播放控制、进度刷新和状态更新逻辑。 | +| `info.cc` | 读取并格式化 record 文件信息。 | +| `player/` | record 播放、任务生产、任务消费和缓冲逻辑。 | +| `launch/cyber_recorder_gui_simple.launch` | `cyber_launch` 启动配置。 | +| `BUILD` | Bazel 构建配置。 | diff --git a/modules/cyber_recorder_gui_simple/cyberfile.xml b/modules/cyber_recorder_gui_simple/cyberfile.xml new file mode 100755 index 00000000000..66c58be30ac --- /dev/null +++ b/modules/cyber_recorder_gui_simple/cyberfile.xml @@ -0,0 +1,23 @@ + + cyber-recorder-gui-simple + 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/cyber_recorder_gui_simple + bazel + + + common + common-msgs + bazel-extend-tools + 3rd-qt5 + \ No newline at end of file diff --git a/modules/cyber_recorder_gui_simple/doubleslider.cc b/modules/cyber_recorder_gui_simple/doubleslider.cc new file mode 100755 index 00000000000..26c89c19521 --- /dev/null +++ b/modules/cyber_recorder_gui_simple/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/cyber_recorder_gui_simple/doubleslider.h b/modules/cyber_recorder_gui_simple/doubleslider.h new file mode 100755 index 00000000000..e01e9886768 --- /dev/null +++ b/modules/cyber_recorder_gui_simple/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/cyber_recorder_gui_simple/info.cc b/modules/cyber_recorder_gui_simple/info.cc new file mode 100755 index 00000000000..d56cf9e5a2e --- /dev/null +++ b/modules/cyber_recorder_gui_simple/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/cyber_recorder_gui_simple/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/cyber_recorder_gui_simple/info.h b/modules/cyber_recorder_gui_simple/info.h new file mode 100755 index 00000000000..4b44559a3bc --- /dev/null +++ b/modules/cyber_recorder_gui_simple/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/cyber_recorder_gui_simple/launch/cyber_recorder_gui_simple.launch b/modules/cyber_recorder_gui_simple/launch/cyber_recorder_gui_simple.launch new file mode 100644 index 00000000000..4260b63daf3 --- /dev/null +++ b/modules/cyber_recorder_gui_simple/launch/cyber_recorder_gui_simple.launch @@ -0,0 +1,10 @@ + + + cyber_recorder_gui_simple + + binary + + bazel-bin/modules/cyber_recorder_gui_simple/cyber_recorder_gui_simple + + + diff --git a/modules/cyber_recorder_gui_simple/main.cpp b/modules/cyber_recorder_gui_simple/main.cpp new file mode 100755 index 00000000000..9e0fa72c1d0 --- /dev/null +++ b/modules/cyber_recorder_gui_simple/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_simple", "cyber_recorder_gui_simple"); + MainWindow w; + w.show(); + const int result = a.exec(); + ::apollo::cyber::Clear(); + return result; +} diff --git a/modules/cyber_recorder_gui_simple/mainwindow.cpp b/modules/cyber_recorder_gui_simple/mainwindow.cpp new file mode 100755 index 00000000000..04376dabc5e --- /dev/null +++ b/modules/cyber_recorder_gui_simple/mainwindow.cpp @@ -0,0 +1,343 @@ +#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/cyber_recorder_gui_simple/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_workplace/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 remainder_ns = 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); + + // 格式化输出日期时间 + 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/cyber_recorder_gui_simple/mainwindow.h b/modules/cyber_recorder_gui_simple/mainwindow.h new file mode 100755 index 00000000000..c651eecf1df --- /dev/null +++ b/modules/cyber_recorder_gui_simple/mainwindow.h @@ -0,0 +1,90 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H +#include +#include + +#include +#include +#include +#include "modules/cyber_recorder_gui_simple/player/player.h" +#include "modules/cyber_recorder_gui_simple/record_playbar.h" +#include "doubleslider.h" + +QT_BEGIN_NAMESPACE +using namespace apollo::goodman::record; + +// 删除bazel-out/k8-opt/bin/modules/cyber_recorder_gui_simple +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/cyber_recorder_gui_simple/mainwindow.ui b/modules/cyber_recorder_gui_simple/mainwindow.ui new file mode 100755 index 00000000000..949c677846d --- /dev/null +++ b/modules/cyber_recorder_gui_simple/mainwindow.ui @@ -0,0 +1,175 @@ + + + MainWindow + + + + 0 + 0 + 900 + 220 + + + + Cyber Recorder GUI Simple + + + + + 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/cyber_recorder_gui_simple/player/play_param.h b/modules/cyber_recorder_gui_simple/player/play_param.h new file mode 100755 index 00000000000..8d3110da4f1 --- /dev/null +++ b/modules/cyber_recorder_gui_simple/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/cyber_recorder_gui_simple/player/play_task.cc b/modules/cyber_recorder_gui_simple/player/play_task.cc new file mode 100755 index 00000000000..9ce603e9580 --- /dev/null +++ b/modules/cyber_recorder_gui_simple/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/cyber_recorder_gui_simple/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/cyber_recorder_gui_simple/player/play_task.h b/modules/cyber_recorder_gui_simple/player/play_task.h new file mode 100755 index 00000000000..3ee3de3f600 --- /dev/null +++ b/modules/cyber_recorder_gui_simple/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/cyber_recorder_gui_simple/player/play_task_buffer.cc b/modules/cyber_recorder_gui_simple/player/play_task_buffer.cc new file mode 100755 index 00000000000..24e3cb8b375 --- /dev/null +++ b/modules/cyber_recorder_gui_simple/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/cyber_recorder_gui_simple/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/cyber_recorder_gui_simple/player/play_task_buffer.h b/modules/cyber_recorder_gui_simple/player/play_task_buffer.h new file mode 100755 index 00000000000..6575e4973c8 --- /dev/null +++ b/modules/cyber_recorder_gui_simple/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 +#include +#include + +#include "modules/cyber_recorder_gui_simple/player/play_task.h" + +namespace apollo { +namespace goodman { +namespace record { + +class PlayTaskBuffer { +public: + using TaskPtr = std::shared_ptr; + // if all tasks are in order, we can use other container to replace this + using TaskMap = std::multimap; + + PlayTaskBuffer(); + virtual ~PlayTaskBuffer(); + + size_t Size() const; + bool Empty() const; + + void Push(const TaskPtr& task); + TaskPtr Front(); + void PopFront(); + void Clear(); + +private: + TaskMap tasks_; + mutable std::mutex mutex_; +}; + +} // namespace record +} // namespace goodman +} // namespace apollo + +#endif // CYBER_TOOLS_CYBER_RECORDER_PLAYER_PLAY_TASK_BUFFER_H_ diff --git a/modules/cyber_recorder_gui_simple/player/play_task_consumer.cc b/modules/cyber_recorder_gui_simple/player/play_task_consumer.cc new file mode 100755 index 00000000000..5c65e57d1ea --- /dev/null +++ b/modules/cyber_recorder_gui_simple/player/play_task_consumer.cc @@ -0,0 +1,129 @@ +/****************************************************************************** + * 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/cyber_recorder_gui_simple/player/play_task_consumer.h" + +#include "cyber/common/log.h" +#include "cyber/time/time.h" + +namespace apollo { +namespace goodman { +namespace record { + +const uint64_t PlayTaskConsumer::kPauseSleepNanoSec = 100000000UL; +const uint64_t PlayTaskConsumer::kWaitProduceSleepNanoSec = 5000000UL; +const uint64_t PlayTaskConsumer::MIN_SLEEP_DURATION_NS = 200000000UL; + +PlayTaskConsumer::PlayTaskConsumer(const TaskBufferPtr& task_buffer, double play_rate) : + play_rate_(play_rate), + consume_th_(nullptr), + task_buffer_(task_buffer), + is_stopped_(true), + is_paused_(false), + is_playonce_(false), + base_msg_play_time_ns_(0), + base_msg_real_time_ns_(0), + last_played_msg_real_time_ns_(0) { + if (play_rate_ <= 0) { + AERROR << "invalid play rate: " << play_rate_ << " , we will use default value(1.0)."; + play_rate_ = 1.0; + } +} + +PlayTaskConsumer::~PlayTaskConsumer() { + Stop(); +} + +void PlayTaskConsumer::Start(uint64_t begin_time_ns) { + if (!is_stopped_.exchange(false)) { + return; + } + begin_time_ns_ = begin_time_ns; + consume_th_.reset(new std::thread(&PlayTaskConsumer::ThreadFunc, this)); +} + +void PlayTaskConsumer::Stop() { + is_stopped_.store(true); + if (consume_th_ != nullptr && consume_th_->joinable()) { + consume_th_->join(); + consume_th_ = nullptr; + } + // reset value for next use consumer + base_msg_play_time_ns_ = 0; + base_msg_real_time_ns_ = 0; + last_played_msg_real_time_ns_ = 0; +} + +void PlayTaskConsumer::ThreadFunc() { + uint64_t base_real_time_ns = 0; + uint64_t accumulated_pause_time_ns = 0; + + while (!is_stopped_.load()) { + auto task = task_buffer_->Front(); + if (task == nullptr) { + std::this_thread::sleep_for(std::chrono::nanoseconds(kWaitProduceSleepNanoSec)); + continue; + } + + uint64_t sleep_ns = 0; + + if (base_msg_play_time_ns_ == 0) { + base_msg_play_time_ns_ = task->msg_play_time_ns(); + base_msg_real_time_ns_ = task->msg_real_time_ns(); + if (base_msg_play_time_ns_ > begin_time_ns_) { + sleep_ns = static_cast( + static_cast(base_msg_play_time_ns_ - begin_time_ns_) / play_rate_); + while (sleep_ns > MIN_SLEEP_DURATION_NS && !is_stopped_.load()) { + std::this_thread::sleep_for(std::chrono::nanoseconds(MIN_SLEEP_DURATION_NS)); + sleep_ns -= MIN_SLEEP_DURATION_NS; + } + + if (is_stopped_.load()) { + break; + } + + std::this_thread::sleep_for(std::chrono::nanoseconds(sleep_ns)); + } + base_real_time_ns = Time::Now().ToNanosecond(); + ADEBUG << "base_msg_play_time_ns: " << base_msg_play_time_ns_ << "base_real_time_ns: " << base_real_time_ns; + } + + uint64_t task_interval_ns = static_cast( + static_cast(task->msg_play_time_ns() - base_msg_play_time_ns_) / play_rate_); + uint64_t real_time_interval_ns = Time::Now().ToNanosecond() - base_real_time_ns - accumulated_pause_time_ns; + if (task_interval_ns > real_time_interval_ns) { + sleep_ns = task_interval_ns - real_time_interval_ns; + std::this_thread::sleep_for(std::chrono::nanoseconds(sleep_ns)); + } + + task->Play(); + is_playonce_.store(false); + + last_played_msg_real_time_ns_ = task->msg_real_time_ns(); + while (is_paused_.load() && !is_stopped_.load()) { + if (is_playonce_.load()) { + break; + } + std::this_thread::sleep_for(std::chrono::nanoseconds(kPauseSleepNanoSec)); + accumulated_pause_time_ns += kPauseSleepNanoSec; + } + task_buffer_->PopFront(); + } +} + +} // namespace record +} // namespace goodman +} // namespace apollo diff --git a/modules/cyber_recorder_gui_simple/player/play_task_consumer.h b/modules/cyber_recorder_gui_simple/player/play_task_consumer.h new file mode 100755 index 00000000000..a0325ea9b6c --- /dev/null +++ b/modules/cyber_recorder_gui_simple/player/play_task_consumer.h @@ -0,0 +1,83 @@ +/****************************************************************************** + * 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_CONSUMER_H_ +#define CYBER_TOOLS_CYBER_RECORDER_PLAYER_PLAY_TASK_CONSUMER_H_ + +#include +#include +#include +#include + +#include "modules/cyber_recorder_gui_simple/player/play_task_buffer.h" + +namespace apollo { +namespace goodman { +namespace record { + +class PlayTaskConsumer { +public: + using ThreadPtr = std::unique_ptr; + using TaskBufferPtr = std::shared_ptr; + + explicit PlayTaskConsumer(const TaskBufferPtr& task_buffer, double play_rate = 1.0); + virtual ~PlayTaskConsumer(); + + void Start(uint64_t begin_time_ns); + void Stop(); + void Pause() { + is_paused_.exchange(true); + } + void PlayOnce() { + is_playonce_.exchange(true); + } + void Continue() { + is_paused_.exchange(false); + } + + uint64_t base_msg_play_time_ns() const { + return base_msg_play_time_ns_; + } + uint64_t base_msg_real_time_ns() const { + return base_msg_real_time_ns_; + } + uint64_t last_played_msg_real_time_ns() const { + return last_played_msg_real_time_ns_; + } + +private: + void ThreadFunc(); + + double play_rate_; + ThreadPtr consume_th_; + TaskBufferPtr task_buffer_; + std::atomic is_stopped_; + std::atomic is_paused_; + std::atomic is_playonce_; + uint64_t begin_time_ns_; + uint64_t base_msg_play_time_ns_; + uint64_t base_msg_real_time_ns_; + uint64_t last_played_msg_real_time_ns_; + static const uint64_t kPauseSleepNanoSec; + static const uint64_t kWaitProduceSleepNanoSec; + static const uint64_t MIN_SLEEP_DURATION_NS; +}; + +} // namespace record +} // namespace goodman +} // namespace apollo + +#endif // CYBER_TOOLS_CYBER_RECORDER_PLAYER_PLAY_TASK_CONSUMER_H_ diff --git a/modules/cyber_recorder_gui_simple/player/play_task_producer.cc b/modules/cyber_recorder_gui_simple/player/play_task_producer.cc new file mode 100755 index 00000000000..9ef1cf31830 --- /dev/null +++ b/modules/cyber_recorder_gui_simple/player/play_task_producer.cc @@ -0,0 +1,419 @@ +/****************************************************************************** + * 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/cyber_recorder_gui_simple/player/play_task_producer.h" + +#include +#include + +#include "cyber/common/log.h" +#include "cyber/common/time_conversion.h" +#include "cyber/cyber.h" +#include "cyber/message/protobuf_factory.h" + +namespace apollo { +namespace goodman { +namespace record { + +const uint32_t PlayTaskProducer::kMinTaskBufferSize = 500; +const uint32_t PlayTaskProducer::kPreloadTimeSec = 3; +const uint64_t PlayTaskProducer::kSleepIntervalNanoSec = 1000000; +const char record_info_channel[] = "/apollo/cyber/record_info"; + +PlayTaskProducer::PlayTaskProducer( + const TaskBufferPtr& task_buffer, + const PlayParam& play_param, + const NodePtr& node, + const bool preload_fill_buffer_mode) : + play_param_(play_param), + task_buffer_(task_buffer), + produce_th_(nullptr), + is_initialized_(false), + is_stopped_(true), + node_(node), + record_viewer_ptr_(nullptr), + earliest_begin_time_(std::numeric_limits::max()), + latest_end_time_(0), + total_msg_num_(0), + preload_fill_buffer_mode_(preload_fill_buffer_mode) {} + +PlayTaskProducer::~PlayTaskProducer() { + Stop(); +} + +bool PlayTaskProducer::Init() { + if (is_initialized_.exchange(true)) { + AERROR << "producer has been initialized."; + return false; + } + if (preload_fill_buffer_mode_ && node_ == nullptr) { + AERROR << "node from same process should pass node param for construct"; + return false; + } + if (!preload_fill_buffer_mode_ && node_ != nullptr) { + AERROR << "invalid param: nullptr node"; + node_ = nullptr; + } + + if (!ReadRecordInfo() || !UpdatePlayParam() || !CreateWriters()) { + is_initialized_.store(false); + return false; + } + + return true; +} + +void PlayTaskProducer::Start() { + if (!is_initialized_.load()) { + AERROR << "please call Init firstly."; + return; + } + + if (!is_stopped_.exchange(false)) { + AERROR << "producer has been started."; + return; + } + + if (preload_fill_buffer_mode_) { + produce_th_.reset(new std::thread(&PlayTaskProducer::ThreadFuncUnderPreloadMode, this)); + } else { + produce_th_.reset(new std::thread(&PlayTaskProducer::ThreadFunc, this)); + } +} + +void PlayTaskProducer::Stop() { + is_stopped_.store(true); + if (produce_th_ != nullptr && produce_th_->joinable()) { + produce_th_->join(); + produce_th_ = nullptr; + } +} + +bool PlayTaskProducer::ReadRecordInfo() { + if (play_param_.files_to_play.empty()) { + AINFO << "no file to play."; + return false; + } + + record_readers_.clear(); + msg_types_.clear(); + writers_.clear(); + earliest_begin_time_ = std::numeric_limits::max(); + latest_end_time_ = 0; + total_msg_num_ = 0; + + auto pb_factory = message::ProtobufFactory::Instance(); + + // loop each file + for (auto& file : play_param_.files_to_play) { + ADEBUG << "files_to_play: " << file; + auto record_reader = std::make_shared(file); + if (!record_reader->IsValid()) { + AERROR << "record_reader->IsValid() FALSE"; + continue; + } + if (!record_reader->GetHeader().is_complete()) { + AINFO << "file: " << file << " is not complete."; + continue; + } + + record_readers_.emplace_back(record_reader); + + auto channel_list = record_reader->GetChannelList(); + // loop each channel info + for (auto& channel_name : channel_list) { + if (play_param_.black_channels.find(channel_name) != play_param_.black_channels.end()) { + continue; + } + + auto& msg_type = record_reader->GetMessageType(channel_name); + msg_types_[channel_name] = msg_type; + + if (play_param_.is_play_all_channels || play_param_.channels_to_play.count(channel_name) > 0) { + total_msg_num_ += record_reader->GetMessageNumber(channel_name); + } + + auto& proto_desc = record_reader->GetProtoDesc(channel_name); + pb_factory->RegisterMessage(proto_desc); + } + + auto& header = record_reader->GetHeader(); + // std::cout << "============before=============" << std::endl; + + // std::cout << "header.begin_time()=" << header.begin_time() << std::endl; + // std::cout << "earliest_begin_time_=" << earliest_begin_time_ << std::endl; + + // std::cout << "header.end_time()=" << header.end_time() << std::endl; + // std::cout << "latest_end_time_=" << latest_end_time_ << std::endl; + + if (header.begin_time() < earliest_begin_time_) { + earliest_begin_time_ = header.begin_time(); + ADEBUG << "update header.begin_time()"; + } + if (header.end_time() > latest_end_time_) { + latest_end_time_ = header.end_time(); + ADEBUG << "update header.end_time()"; + } + // std::cout << "============after=============" << std::endl; + + // std::cout << "header.begin_time()=" << header.begin_time() << std::endl; + // std::cout << "earliest_begin_time_=" << earliest_begin_time_ << std::endl; + + // std::cout << "header.end_time()=" << header.end_time() << std::endl; + // std::cout << "latest_end_time_=" << latest_end_time_ << std::endl; + auto begin_time_s = static_cast(header.begin_time()) / 1e9; + auto end_time_s = static_cast(header.end_time()) / 1e9; + auto begin_time_str = common::UnixSecondsToString(static_cast(begin_time_s)); + auto end_time_str = common::UnixSecondsToString(static_cast(end_time_s)); + + // std::cout << "file: " << file << ", chunk_number: " << header.chunk_number() + // << ", begin_time: " << header.begin_time() << " (" << begin_time_str << ")" + // << ", end_time: " << header.end_time() << " (" << end_time_str << ")" + // << ", message_number: " << header.message_number() << std::endl; + } + + // std::cout << "earliest_begin_time: " << earliest_begin_time_ << ", latest_end_time: " << latest_end_time_ + // << ", total_msg_num: " << total_msg_num_ << std::endl; + + return true; +} + +bool PlayTaskProducer::UpdatePlayParam() { + if (play_param_.begin_time_ns < earliest_begin_time_) { + AINFO << play_param_.begin_time_ns << " " << play_param_.base_begin_time_ns; + play_param_.begin_time_ns = earliest_begin_time_; + play_param_.base_begin_time_ns = earliest_begin_time_; + AINFO << "play_param_.begin_time_ns < earliest_begin_time_" + << " play_param_.begin_time_ns =" << play_param_.begin_time_ns + << " play_param_.base_begin_time_ns =" << play_param_.base_begin_time_ns + << " earliest_begin_time_=" << earliest_begin_time_; + } + if (play_param_.start_time_s > 0) { + play_param_.begin_time_ns = play_param_.base_begin_time_ns + + static_cast(static_cast(play_param_.start_time_s) * 1e9); + } + if (play_param_.end_time_ns > latest_end_time_) { + play_param_.end_time_ns = latest_end_time_; + } + if (play_param_.begin_time_ns >= play_param_.end_time_ns) { + AERROR << "begin time are equal or larger than end time" + << ", begin_time_ns=" << play_param_.begin_time_ns << ", end_time_ns=" << play_param_.end_time_ns; + return false; + } + if (play_param_.preload_time_s == 0) { + AINFO << "preload time is zero, we will use defalut value: " << kPreloadTimeSec << " seconds."; + play_param_.preload_time_s = kPreloadTimeSec; + } + return true; +} + +void PlayTaskProducer::WriteRecordProgress(const double& curr_time_s, const double& total_time_s) { + RecordInfo record_info; + record_info.set_total_time_s(total_time_s); + record_info.set_curr_time_s(curr_time_s); + record_info.set_progress(curr_time_s / total_time_s); + record_info.set_record_name(play_param_.record_id); + std::string content; + record_info.SerializeToString(&content); + auto raw_msg = std::make_shared(content); + writers_[record_info_channel]->Write(raw_msg); +} + +void PlayTaskProducer::Reset(const double& progress_s) { + play_param_.begin_time_ns = play_param_.base_begin_time_ns + progress_s * 1e9; + play_param_.start_time_s = progress_s; + record_viewer_ptr_ = nullptr; + record_viewer_ptr_ = std::make_shared( + record_readers_, play_param_.begin_time_ns, play_param_.end_time_ns, play_param_.channels_to_play); + record_viewer_ptr_->set_curr_itr(record_viewer_ptr_->begin()); +} + +bool PlayTaskProducer::CreatePlayTaskWriter(const std::string& channel_name, const std::string& msg_type) { + proto::RoleAttributes attr; + attr.set_channel_name(channel_name); + attr.set_message_type(msg_type); + auto writer = node_->CreateWriter(attr); + if (writer == nullptr) { + AERROR << "create writer failed. channel name: " << channel_name << ", message type: " << msg_type; + return false; + } + writers_[channel_name] = writer; + return true; +} + +bool PlayTaskProducer::CreateWriters() { + if (node_ == nullptr && !preload_fill_buffer_mode_) { + std::string node_name = "cyber_recorder_play_" + std::to_string(getpid()); + node_ = apollo::cyber::CreateNode(node_name); + if (node_ == nullptr) { + AERROR << "create node failed."; + return false; + } + } + + for (auto& item : msg_types_) { + auto& channel_name = item.first; + auto& msg_type = item.second; + + if (play_param_.is_play_all_channels || play_param_.channels_to_play.count(channel_name) > 0) { + if (play_param_.black_channels.find(channel_name) != play_param_.black_channels.end()) { + continue; + } + if (!CreatePlayTaskWriter(channel_name, msg_type)) { + return false; + } + } + } + return CreatePlayTaskWriter(record_info_channel, "apollo.cyber.proto.RecordInfo"); +} + +void PlayTaskProducer::FillPlayTaskBuffer() { + task_buffer_->Clear(); + // use fixed preload buffer size + uint32_t preload_size = kMinTaskBufferSize * 2; + + if (!record_viewer_ptr_) { + record_viewer_ptr_ = std::make_shared( + record_readers_, play_param_.begin_time_ns, play_param_.end_time_ns, play_param_.channels_to_play); + record_viewer_ptr_->set_curr_itr(record_viewer_ptr_->begin()); + } + + auto itr = record_viewer_ptr_->curr_itr(); + + for (; itr != record_viewer_ptr_->end(); ++itr) { + if (task_buffer_->Size() > preload_size) { + record_viewer_ptr_->set_curr_itr(itr); + break; + } + + auto search = writers_.find(itr->channel_name); + if (search == writers_.end()) { + continue; + } + + auto raw_msg = std::make_shared(itr->content); + auto task = std::make_shared(raw_msg, search->second, itr->time, itr->time); + task_buffer_->Push(task); + } +} + +void PlayTaskProducer::ThreadFuncUnderPreloadMode() { + const uint64_t loop_time_ns = play_param_.end_time_ns - play_param_.begin_time_ns; + uint64_t avg_interval_time_ns = kSleepIntervalNanoSec; + if (total_msg_num_ > 0) { + avg_interval_time_ns = loop_time_ns / total_msg_num_; + } + + uint32_t preload_size = kMinTaskBufferSize * 2; + + if (preload_fill_buffer_mode_ && !record_viewer_ptr_) { + AERROR << "Preload should not nullptr"; + return; + } + if (!preload_fill_buffer_mode_ && record_viewer_ptr_) { + AERROR << "No preload should nullptr"; + return; + } + if (!record_viewer_ptr_) { + record_viewer_ptr_ = std::make_shared( + record_readers_, play_param_.begin_time_ns, play_param_.end_time_ns, play_param_.channels_to_play); + record_viewer_ptr_->set_curr_itr(record_viewer_ptr_->begin()); + } + + while (!is_stopped_.load()) { + auto itr = record_viewer_ptr_->curr_itr(); + auto itr_end = record_viewer_ptr_->end(); + + while (itr != itr_end && !is_stopped_.load()) { + while (!is_stopped_.load() && task_buffer_->Size() > preload_size) { + std::this_thread::sleep_for(std::chrono::nanoseconds(avg_interval_time_ns)); + } + for (; itr != itr_end && !is_stopped_.load(); ++itr) { + if (task_buffer_->Size() > preload_size) { + break; + } + + auto search = writers_.find(itr->channel_name); + if (search == writers_.end()) { + continue; + } + + auto raw_msg = std::make_shared(itr->content); + auto task = std::make_shared(raw_msg, search->second, itr->time, itr->time); + task_buffer_->Push(task); + } + } + // not support loop + is_stopped_.store(true); + break; + } +} + +void PlayTaskProducer::ThreadFunc() { + const uint64_t loop_time_ns = play_param_.end_time_ns - play_param_.begin_time_ns; + uint64_t avg_interval_time_ns = kSleepIntervalNanoSec; + if (total_msg_num_ > 0) { + avg_interval_time_ns = loop_time_ns / total_msg_num_; + } + + double avg_freq_hz = static_cast(total_msg_num_) / (static_cast(loop_time_ns) * 1e-9); + uint32_t preload_size = (uint32_t)avg_freq_hz * play_param_.preload_time_s; + AINFO << "preload_size: " << preload_size; + if (preload_size < kMinTaskBufferSize) { + preload_size = kMinTaskBufferSize; + } + + record_viewer_ptr_ = std::make_shared( + record_readers_, play_param_.begin_time_ns, play_param_.end_time_ns, play_param_.channels_to_play); + record_viewer_ptr_->set_curr_itr(record_viewer_ptr_->begin()); + + uint32_t loop_num = 0; + while (!is_stopped_.load()) { + uint64_t plus_time_ns = loop_num * loop_time_ns; + auto itr = record_viewer_ptr_->begin(); + auto itr_end = record_viewer_ptr_->end(); + + while (itr != itr_end && !is_stopped_.load()) { + while (!is_stopped_.load() && task_buffer_->Size() > preload_size) { + std::this_thread::sleep_for(std::chrono::nanoseconds(avg_interval_time_ns)); + } + for (; itr != itr_end && !is_stopped_.load(); ++itr) { + if (task_buffer_->Size() > preload_size) { + break; + } + + auto search = writers_.find(itr->channel_name); + if (search == writers_.end()) { + continue; + } + + auto raw_msg = std::make_shared(itr->content); + auto task = std::make_shared(raw_msg, search->second, itr->time, itr->time + plus_time_ns); + task_buffer_->Push(task); + } + } + + if (!play_param_.is_loop_playback) { + is_stopped_.store(true); + break; + } + ++loop_num; + } +} + +} // namespace record +} // namespace goodman +} // namespace apollo diff --git a/modules/cyber_recorder_gui_simple/player/play_task_producer.h b/modules/cyber_recorder_gui_simple/player/play_task_producer.h new file mode 100755 index 00000000000..57dfeb56083 --- /dev/null +++ b/modules/cyber_recorder_gui_simple/player/play_task_producer.h @@ -0,0 +1,133 @@ +/****************************************************************************** + * 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_PRODUCER_H_ +#define CYBER_TOOLS_CYBER_RECORDER_PLAYER_PLAY_TASK_PRODUCER_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cyber/message/raw_message.h" +#include "cyber/node/node.h" +#include "cyber/node/writer.h" +#include "cyber/record/record_reader.h" +#include "modules/cyber_recorder_gui_simple/player/play_param.h" +#include "modules/cyber_recorder_gui_simple/player/play_task_buffer.h" +#include "cyber/proto/record.pb.h" +#include "cyber/record/record_viewer.h" + +namespace apollo { +namespace goodman { +namespace record { +using namespace apollo::cyber::record; +using ::apollo::cyber::proto::RecordInfo; + +class PlayTaskProducer { +public: + using NodePtr = std::shared_ptr; + using ThreadPtr = std::unique_ptr; + using TaskBufferPtr = std::shared_ptr; + using RecordReaderPtr = std::shared_ptr; + using WriterPtr = std::shared_ptr>; + using WriterMap = std::unordered_map; + using MessageTypeMap = std::unordered_map; + using RecordViewerPtr = std::shared_ptr; + + PlayTaskProducer( + const TaskBufferPtr& task_buffer, + const PlayParam& play_param, + const NodePtr& node = nullptr, + const bool preload_fill_buffer_mode = false); + virtual ~PlayTaskProducer(); + + bool Init(); + void Start(); + void Stop(); + + const PlayParam& play_param() const { + return play_param_; + } + bool is_stopped() const { + return is_stopped_.load(); + } + bool is_initialized() const { + return is_initialized_.load(); + } + void set_stopped() { + is_stopped_.exchange(true); + } + void WriteRecordProgress(const double& curr_time_s, const double& total_time_s); + /** + * @brief Preload the player,producer fill play_task_buffer before + * playing. + */ + void FillPlayTaskBuffer(); + /** + * @brief Reset player producer for dv will repeatedly use it. + * reset the start time when dv reset play record progress. + */ + void Reset(const double& progress_time_s); + +private: + bool ReadRecordInfo(); + bool UpdatePlayParam(); + + bool CreateWriters(); + bool CreatePlayTaskWriter(const std::string& channel_name, const std::string& msg_type); + void ThreadFunc(); + void ThreadFuncUnderPreloadMode(); + + PlayParam play_param_; + TaskBufferPtr task_buffer_; + ThreadPtr produce_th_; + + std::atomic is_initialized_; + std::atomic is_stopped_; + + NodePtr node_; + WriterMap writers_; + MessageTypeMap msg_types_; + std::vector record_readers_; + RecordViewerPtr record_viewer_ptr_; + + uint64_t earliest_begin_time_; + uint64_t latest_end_time_; + uint64_t total_msg_num_; + + // This parameter indicates whether the producer needs to preload the buffer + // When this value is true, it means that we preload the buffer before playing + // we use it when dv play record under nohup process,all record player related + // to the same node,so when this value is true,we pass parameter node to + // assign value to node_ + bool preload_fill_buffer_mode_; + + static const uint32_t kMinTaskBufferSize; + static const uint32_t kPreloadTimeSec; + static const uint64_t kSleepIntervalNanoSec; +}; + +} // namespace record +} // namespace goodman +} // namespace apollo + +#endif // CYBER_TOOLS_CYBER_RECORDER_PLAYER_PLAY_TASK_PRODUCER_H_ diff --git a/modules/cyber_recorder_gui_simple/player/player.cc b/modules/cyber_recorder_gui_simple/player/player.cc new file mode 100755 index 00000000000..4687c2139a3 --- /dev/null +++ b/modules/cyber_recorder_gui_simple/player/player.cc @@ -0,0 +1,321 @@ +/****************************************************************************** + * 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/cyber_recorder_gui_simple/player/player.h" + +#include + +#include "cyber/init.h" +#include "cyber/common/log.h" +#include "cyber/common/time_conversion.h" +#include "cyber/cyber.h" +namespace apollo { +namespace goodman { +namespace record { + +const uint64_t Player::kSleepIntervalMiliSec = 100; + +Player::Player(const PlayParam& play_param, const NodePtr& node, const bool preload_fill_buffer_mode) : + is_initialized_(false), + is_stopped_(true), + consumer_(nullptr), + producer_(nullptr), + task_buffer_(nullptr), + last_played_msg_real_time_s_(0.0), + progress_time_s_(0.0), + total_progress_time_s_(0.0) { + task_buffer_ = std::make_shared(); + consumer_.reset(new PlayTaskConsumer(task_buffer_, play_param.play_rate)); + producer_.reset(new PlayTaskProducer(task_buffer_, play_param, node, preload_fill_buffer_mode)); +} + +Player::~Player() { + Stop(); +} + +bool Player::Init() { + if (is_initialized_.exchange(true)) { + AERROR << "player has been initialized."; + return false; + } + if (producer_->Init()) { + return true; + } + + is_initialized_.store(false); + return false; +} + +static char Getch() { + char buf = 0; + struct termios old = {0}; + fflush(stdout); + if (tcgetattr(0, &old) < 0) { + perror("tcsetattr()"); + } + old.c_lflag &= ~ICANON; + old.c_lflag &= ~ECHO; + old.c_cc[VMIN] = 0; + old.c_cc[VTIME] = 1; + if (tcsetattr(0, TCSANOW, &old) < 0) { + perror("tcsetattr ICANON"); + } + if (read(0, &buf, 1) < 0) { + perror("read()"); + } + old.c_lflag |= ICANON; + old.c_lflag |= ECHO; + if (tcsetattr(0, TCSADRAIN, &old) < 0) { + perror("tcsetattr ~ICANON"); + } + return buf; +} + +bool Player::ThreadFunc_Play_Nohup() { + if (!is_initialized_.load()) { + AERROR << "please call Init firstly."; + return false; + } + + if (!is_stopped_.exchange(false)) { + AERROR << "player has been stopped."; + return false; + } + auto& play_param = producer_->play_param(); + producer_->Start(); + consumer_->Start(play_param.begin_time_ns); + const double total_progress_time_s = static_cast(play_param.end_time_ns - play_param.begin_time_ns) / 1e9 + + static_cast(play_param.start_time_s); + total_progress_time_s_.store(total_progress_time_s); + AINFO << "total_progress_time_s_: " << total_progress_time_s + << ", play_param.end_time_ns: " << play_param.end_time_ns + << ", play_param.begin_time_ns: " << play_param.begin_time_ns; + while (!is_stopped_.load() && apollo::cyber::OK()) { + if (is_paused_) { + consumer_->Pause(); + } else { + consumer_->Continue(); + } + + double progress_time_s = static_cast(producer_->play_param().start_time_s); + if (consumer_->last_played_msg_real_time_ns() > 0) { + progress_time_s += static_cast( + consumer_->last_played_msg_real_time_ns() - consumer_->base_msg_play_time_ns() + + consumer_->base_msg_real_time_ns() - producer_->play_param().begin_time_ns) + / 1e9; + } + progress_time_s_.store(progress_time_s); + + producer_->WriteRecordProgress(progress_time_s, total_progress_time_s); + + if (producer_->is_stopped() && task_buffer_->Empty()) { + consumer_->Stop(); + break; + } + std::this_thread::sleep_for(std::chrono::milliseconds(kSleepIntervalMiliSec)); + } + is_stopped_.store(true); + return true; +} + +void Player::HandleNohupThreadStatus() { + is_paused_ = !is_paused_; +} + +void Player::ThreadFunc_Term() { + while (!is_stopped_.load()) { + char ch = Getch(); + switch (ch) { + case 's': + is_playonce_ = true; + break; + case ' ': + is_paused_ = !is_paused_; + break; + default: + break; + } + } +} + +void Player::NohupPlayRecord() { + if (nohup_play_th_ != nullptr && nohup_play_th_->joinable()) { + nohup_play_th_->join(); + nohup_play_th_ = nullptr; + } + nohup_play_th_.reset(new std::thread(&Player::ThreadFunc_Play_Nohup, this)); +} + +bool Player::PreloadPlayRecord(const double& progress_s, bool paused_status) { + if (!producer_->is_initialized()) { + return false; + } + if (is_preloaded_.load()) { + producer_->Reset(progress_s); + is_preloaded_.store(false); + } + if (progress_s == 0) { + // When the progress is 0, it is completely reloaded. At this + // time, the is_paused_ of the player should change to the initial + // state to avoid being disturbed by the last state.On the contrary, + // reset the progress needs to preserve the past state to ensure the + // unity of the state before and after. + is_paused_.exchange(false); + } else { + is_paused_.exchange(paused_status); + } + producer_->FillPlayTaskBuffer(); + is_preloaded_.store(true); + return true; +} + +bool Player::Start() { + if (!is_initialized_.load()) { + AERROR << "please call Init firstly."; + return false; + } + if (!is_stopped_.load()) { + AERROR << "player has been started."; + return false; + } + if (nohup_play_th_ != nullptr && nohup_play_th_->joinable()) { + nohup_play_th_->join(); + nohup_play_th_ = nullptr; + } + nohup_play_th_.reset(new std::thread(&Player::ThreadFunc_Play_Nohup, this)); + return true; + + // if (!is_initialized_.load()) { + // AERROR << "please call Init firstly."; + // return false; + // } + + // if (!is_stopped_.exchange(false)) { + // AERROR << "player has been stopped."; + // return false; + // } + + // auto& play_param = producer_->play_param(); + // // std::cout << "\nPlease wait " << play_param.preload_time_s << " second(s) for loading...\n" + // // << "Hit Ctrl+C to stop, Space to pause, or 's' to step.\n" + // // << std::endl; + // producer_->Start(); + + // auto preload_sec = play_param.preload_time_s; + // while (preload_sec > 0 && !is_stopped_.load() && apollo::cyber::OK()) { + // std::this_thread::sleep_for(std::chrono::seconds(1)); + // --preload_sec; + // } + + // auto delay_sec = play_param.delay_time_s; + // while (delay_sec > 0 && !is_stopped_.load() && apollo::cyber::OK()) { + // std::this_thread::sleep_for(std::chrono::seconds(1)); + // --delay_sec; + // } + + // consumer_->Start(play_param.begin_time_ns); + + // // std::ios::fmtflags before(std::cout.flags()); + // // std::cout << std::fixed; + // total_progress_time_s_ = static_cast(play_param.end_time_ns - play_param.begin_time_ns) / 1e9 + // + static_cast(play_param.start_time_s); + + // term_thread_.reset(new std::thread(&Player::ThreadFunc_Term, this)); + // while (!is_stopped_.load() && apollo::cyber::OK()) { + // if (is_playonce_) { + // consumer_->PlayOnce(); + // is_playonce_ = false; + // } + // if (is_paused_) { + // consumer_->Pause(); + // // std::cout << "\r[PAUSED ] Record Time: "; + // } else { + // consumer_->Continue(); + // // std::cout << "\r[RUNNING] Current Record Time : "; + // } + + // last_played_msg_real_time_s_ = static_cast(consumer_->last_played_msg_real_time_ns()) / 1e9; + + // progress_time_s_ = static_cast(producer_->play_param().start_time_s); + // if (consumer_->last_played_msg_real_time_ns() > 0) { + // progress_time_s_ += static_cast( + // consumer_->last_played_msg_real_time_ns() - + // consumer_->base_msg_play_time_ns() + // + consumer_->base_msg_real_time_ns() - producer_->play_param().begin_time_ns) + // / 1e9; + // } + + // // std::cout << std::setprecision(3) << last_played_msg_real_time_s_ << " Progress: " << progress_time_s_ + // // << " / " << total_progress_time_s_; + // // std::cout.flush(); + + // if (producer_->is_stopped() && task_buffer_->Empty()) { + // consumer_->Stop(); + // break; + // } + // std::this_thread::sleep_for(std::chrono::milliseconds(kSleepIntervalMiliSec)); + // } + // // std::cout << "\nplay finished." << std::endl; + // // std::cout.flags(before); + // return true; +} + +bool Player::Stop() { + const bool was_stopped = is_stopped_.exchange(true); + producer_->Stop(); + consumer_->Stop(); + + if (term_thread_ != nullptr && term_thread_->joinable()) { + term_thread_->join(); + term_thread_ = nullptr; + } + if (nohup_play_th_ != nullptr && nohup_play_th_->joinable()) { + nohup_play_th_->join(); + nohup_play_th_ = nullptr; + } + return !was_stopped; +} +bool Player::ResetProcessTime(double process_time) { + Reset(); + producer_->Reset(process_time); + Start(); + return true; +} + +bool Player::Reset() { + const bool was_stopped = is_stopped_.exchange(true); + // produer may not be stopped under reset progress + // reset is_stopped_ to true to ensure logical unity + producer_->set_stopped(); + producer_->Stop(); + consumer_->Stop(); + // clear task buffer for refill it + task_buffer_->Clear(); + if (nohup_play_th_ != nullptr && nohup_play_th_->joinable()) { + nohup_play_th_->join(); + nohup_play_th_ = nullptr; + } + + // if (is_initialized_.exchange(true)) { + // AERROR << "player has been initialized."; + // return false; + // } + return !was_stopped; +} + +} // namespace record +} // namespace goodman +} // namespace apollo diff --git a/modules/cyber_recorder_gui_simple/player/player.h b/modules/cyber_recorder_gui_simple/player/player.h new file mode 100755 index 00000000000..31d7e3831f6 --- /dev/null +++ b/modules/cyber_recorder_gui_simple/player/player.h @@ -0,0 +1,130 @@ +/****************************************************************************** + * 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_PLAYER_H_ +#define CYBER_TOOLS_CYBER_RECORDER_PLAYER_PLAYER_H_ + +#include +#include +#include + +#include "modules/cyber_recorder_gui_simple/player/play_param.h" +#include "modules/cyber_recorder_gui_simple/player/play_task_buffer.h" +#include "modules/cyber_recorder_gui_simple/player/play_task_consumer.h" +#include "modules/cyber_recorder_gui_simple/player/play_task_producer.h" +#include "cyber/node/node.h" + +namespace apollo { +namespace goodman { +namespace record { + +class Player { +public: + using ConsumerPtr = std::unique_ptr; + using ProducerPtr = std::unique_ptr; + using TaskBufferPtr = std::shared_ptr; + using NodePtr = std::shared_ptr; + + Player(const PlayParam& play_param, const NodePtr& node = nullptr, const bool preload_fill_buffer_mode = false); + virtual ~Player(); + + bool Init(); + bool Start(); + bool Stop(); + /** + * @brief Reset player for dv will repeatedly use + * @return If the action executed successfully. + */ + bool Reset(); + /** + * @brief Preload the player,fill play_task_buffer ahead ofr + * time to ensure fast playback and avoid consumer waiting. + * @param progress_s The start time we begin to read record + * and fill task buffer. + * @return If the action executed successfully. + */ + bool PreloadPlayRecord(const double& progress_s = 0, bool paused_status = false); + /** + * @brief set nohup process to play record. + */ + void NohupPlayRecord(); + /** + * @brief Pause or continue to play record by change the nohup process status. + */ + void HandleNohupThreadStatus(); + + PlayParam get_parm() { + return producer_->play_param(); + } + + double last_played_msg_real_time_s() { + return last_played_msg_real_time_s_.load(); + } + double progress_time_s() { + return progress_time_s_.load(); + } + double total_progress_time_s() { + return total_progress_time_s_.load(); + } + bool is_initialized() { + return is_initialized_; + } + bool is_stopped() { + return is_stopped_; + } + bool is_paused() { + return is_paused_; + } + void set_is_paused() { + is_paused_ = !is_paused_; + } + + bool ResetProcessTime(double process_time); + +private: + void ThreadFunc_Term(); + /** + * @brief The nohup process to play record. + * @return If the action executed successfully. + */ + bool ThreadFunc_Play_Nohup(); + +private: + std::atomic is_initialized_ = {false}; + std::atomic is_stopped_ = {false}; + std::atomic is_paused_ = {false}; + std::atomic is_playonce_ = {false}; + // is_preloaded_ is only used under nohup play mode to + // mark whether the task_buffer_ is preloaded. + std::atomic is_preloaded_ = {false}; + ConsumerPtr consumer_; + ProducerPtr producer_; + TaskBufferPtr task_buffer_; + std::shared_ptr term_thread_ = nullptr; + // add nohup_play_th_ to allow background play record + std::shared_ptr nohup_play_th_ = nullptr; + static const uint64_t kSleepIntervalMiliSec; + + std::atomic last_played_msg_real_time_s_; + std::atomic progress_time_s_; + std::atomic total_progress_time_s_; +}; + +} // namespace record +} // namespace goodman +} // namespace apollo + +#endif // CYBER_TOOLS_CYBER_RECORDER_PLAYER_PLAYER_H_ diff --git a/modules/cyber_recorder_gui_simple/record_playbar.cpp b/modules/cyber_recorder_gui_simple/record_playbar.cpp new file mode 100755 index 00000000000..d71cf1a6f70 --- /dev/null +++ b/modules/cyber_recorder_gui_simple/record_playbar.cpp @@ -0,0 +1,40 @@ +#include "record_playbar.h" + +// 构造函数的实现 +RecordPlaybar::RecordPlaybar(Qt::Orientation orientation, QWidget *parent) : QSlider(orientation, parent) { + connect(this, &QSlider::sliderPressed, this, &RecordPlaybar::onSliderPressed); + // 连接 sliderReleased 信号到槽函数 + connect(this, &QSlider::sliderReleased, this, &RecordPlaybar::onSliderReleased); +} + +// 鼠标按下事件处理函数的实现 +void RecordPlaybar::mousePressEvent(QMouseEvent *event) { + if (!is_jump_) { + return; + } + // 调用基类的鼠标按下事件处理函数,确保默认行为 + QSlider::mousePressEvent(event); + + // 获取滑块的最小值和最大值 + int minVal = minimum(); + int maxVal = maximum(); + + // 根据滑块的方向处理点击事件 + if (orientation() == Qt::Horizontal) { + // 计算点击位置在滑块总长度中的比例 + double clickRatio = static_cast(event->pos().x()) / width(); + // 根据比例计算对应的滑块值 + newVal_ = minVal + static_cast(clickRatio * (maxVal - minVal)); + emit jumpSignal(); + // 设置滑块的值 + setValue(newVal_); + } else if (orientation() == Qt::Vertical) { + // 计算点击位置在滑块总长度中的比例 + double clickRatio = static_cast(event->pos().y()) / height(); + // 根据比例计算对应的滑块值 + newVal_ = minVal + static_cast((1 - clickRatio) * (maxVal - minVal)); + // 设置滑块的值 + emit jumpSignal(); + setValue(newVal_); + } +} diff --git a/modules/cyber_recorder_gui_simple/record_playbar.h b/modules/cyber_recorder_gui_simple/record_playbar.h new file mode 100755 index 00000000000..80bf38318dd --- /dev/null +++ b/modules/cyber_recorder_gui_simple/record_playbar.h @@ -0,0 +1,44 @@ +#ifndef RECORDPLAYBAR_H +#define RECORDPLAYBAR_H + +#include +#include +#include + +class RecordPlaybar : public QSlider { + Q_OBJECT +public: + // explicit RecordPlaybar(QWidget *parent = nullptr); + // 构造函数,接收滑块方向和父窗口指针 + explicit RecordPlaybar(Qt::Orientation orientation, QWidget *parent = nullptr); + int GetNewVal() { + return this->newVal_; + } + bool is_jump() { + return this->is_jump_; + } + +protected: + // 重写鼠标按下事件处理函数 + void mousePressEvent(QMouseEvent *event) override; +private slots: + void onSliderPressed() { + qDebug() << "onSliderPressed"; + is_jump_ = false; + } + + void onSliderReleased() { + qDebug() << "onSliderReleased"; + + is_jump_ = true; + } + +private: + int newVal_ = 0; + bool is_jump_ = true; + +signals: + void jumpSignal(); +}; + +#endif // RECORDPLAYBAR_H