Skip to content

Commit 9d3f185

Browse files
authored
Add start-offset play option (ros2#931)
* Add `start_offset` field to `PlayOptions`, exposed to CLI as `ros2 bag play --start-offset SECONDS`, which allows for starting playback after the beginning of a bag. Signed-off-by: Abrar Rahman Protyasha <[email protected]>
1 parent d6ffd91 commit 9d3f185

File tree

5 files changed

+72
-0
lines changed

5 files changed

+72
-0
lines changed

Diff for: ros2bag/ros2bag/verb/play.py

+4
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ def add_arguments(self, parser, cli_name): # noqa: D102
9090
parser.add_argument(
9191
'-p', '--start-paused', action='store_true', default=False,
9292
help='Start the playback player in a paused state.')
93+
parser.add_argument(
94+
'--start-offset', type=check_positive_float, default=0.0,
95+
help='Start the playback player this many seconds into the bag file.')
9396

9497
def main(self, *, args): # noqa: D102
9598
qos_profile_overrides = {} # Specify a valid default
@@ -127,6 +130,7 @@ def main(self, *, args): # noqa: D102
127130
play_options.delay = args.delay
128131
play_options.disable_keyboard_controls = args.disable_keyboard_controls
129132
play_options.start_paused = args.start_paused
133+
play_options.start_offset = args.start_offset
130134

131135
player = Player()
132136
player.play(storage_options, play_options)

Diff for: rosbag2_py/src/rosbag2_py/_transport.cpp

+14
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,16 @@ struct OptionsWrapper : public T
7474
return RCUTILS_NS_TO_S(static_cast<double>(this->delay.nanoseconds()));
7575
}
7676

77+
void setStartOffset(double start_offset)
78+
{
79+
this->start_offset = static_cast<rcutils_time_point_value_t>(RCUTILS_S_TO_NS(start_offset));
80+
}
81+
82+
double getStartOffset() const
83+
{
84+
return RCUTILS_NS_TO_S(static_cast<double>(this->start_offset));
85+
}
86+
7787
void setTopicQoSProfileOverrides(const py::dict & overrides)
7888
{
7989
py_dict = overrides;
@@ -233,6 +243,10 @@ PYBIND11_MODULE(_transport, m) {
233243
&PlayOptions::setDelay)
234244
.def_readwrite("disable_keyboard_controls", &PlayOptions::disable_keyboard_controls)
235245
.def_readwrite("start_paused", &PlayOptions::start_paused)
246+
.def_property(
247+
"start_offset",
248+
&PlayOptions::getStartOffset,
249+
&PlayOptions::setStartOffset)
236250
;
237251

238252
py::class_<RecordOptions>(m, "RecordOptions")

Diff for: rosbag2_transport/include/rosbag2_transport/play_options.hpp

+3
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ struct PlayOptions
5353
// Start paused.
5454
bool start_paused = false;
5555

56+
// Time to start playback as an offset from the beginning of the bag.
57+
rcutils_time_point_value_t start_offset = 0;
58+
5659
bool disable_keyboard_controls = false;
5760
// keybindings
5861
KeyboardHandler::KeyCode pause_resume_toggle_key = KeyboardHandler::KeyCode::SPACE;

Diff for: rosbag2_transport/src/rosbag2_transport/player.cpp

+11
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,17 @@ Player::Player(
142142
auto metadata = reader_->get_metadata();
143143
starting_time_ = std::chrono::duration_cast<std::chrono::nanoseconds>(
144144
metadata.starting_time.time_since_epoch()).count();
145+
// If a non-default (positive) starting time offset is provided in PlayOptions,
146+
// then add the offset to the starting time obtained from reader metadata
147+
if (play_options_.start_offset < 0) {
148+
RCLCPP_WARN_STREAM(
149+
get_logger(),
150+
"Invalid start offset value: " <<
151+
RCUTILS_NS_TO_S(static_cast<double>(play_options_.start_offset)) <<
152+
". Negative start offset ignored.");
153+
} else {
154+
starting_time_ += play_options_.start_offset;
155+
}
145156
clock_ = std::make_unique<rosbag2_cpp::TimeControllerClock>(
146157
starting_time_, std::chrono::steady_clock::now,
147158
std::chrono::milliseconds{100}, play_options_.start_paused);

Diff for: rosbag2_transport/test/rosbag2_transport/test_play_timing.cpp

+40
Original file line numberDiff line numberDiff line change
@@ -190,3 +190,43 @@ TEST_F(PlayerTestFixture, play_ignores_invalid_delay)
190190
EXPECT_THAT(replay_time, Gt(lower_expected_duration));
191191
EXPECT_THAT(replay_time, Lt(upper_expected_duration));
192192
}
193+
194+
TEST_F(PlayerTestFixture, play_respects_start_offset)
195+
{
196+
rclcpp::Duration start_delay(0, static_cast<uint32_t>(RCUTILS_S_TO_NS(0.5)));
197+
rclcpp::Duration delay_margin(1, 0);
198+
199+
// Start 0.5 seconds into the bag
200+
play_options_.start_offset = static_cast<rcutils_time_point_value_t>(RCUTILS_S_TO_NS(0.5));
201+
// Subtract the delay since we've sought further into the bag
202+
auto lower_expected_duration = message_time_difference - start_delay;
203+
auto upper_expected_duration = message_time_difference - start_delay + delay_margin;
204+
auto player = std::make_shared<rosbag2_transport::Player>(
205+
std::move(reader), storage_options_, play_options_);
206+
207+
auto start = clock.now();
208+
player->play();
209+
auto replay_duration = clock.now() - start;
210+
211+
EXPECT_THAT(replay_duration, Gt(lower_expected_duration));
212+
EXPECT_THAT(replay_duration, Lt(upper_expected_duration));
213+
}
214+
215+
TEST_F(PlayerTestFixture, play_ignores_invalid_start_offset)
216+
{
217+
rclcpp::Duration delay_margin(1, 0);
218+
219+
// Player should ignore an invalid (negative) start offset
220+
play_options_.start_offset = -5;
221+
auto lower_expected_duration = message_time_difference;
222+
auto upper_expected_duration = message_time_difference + delay_margin;
223+
auto player = std::make_shared<rosbag2_transport::Player>(
224+
std::move(reader), storage_options_, play_options_);
225+
226+
auto start = clock.now();
227+
player->play();
228+
auto replay_duration = clock.now() - start;
229+
230+
EXPECT_THAT(replay_duration, Gt(lower_expected_duration));
231+
EXPECT_THAT(replay_duration, Lt(upper_expected_duration));
232+
}

0 commit comments

Comments
 (0)