# include "tools/cabana/streams/replaystream.h"
# include <QLabel>
# include <QFileDialog>
# include <QGridLayout>
# include <QMessageBox>
# include <QPushButton>
ReplayStream : : ReplayStream ( QObject * parent ) : AbstractStream ( parent ) {
unsetenv ( " ZMQ " ) ;
// TODO: Remove when OpenpilotPrefix supports ZMQ
# ifndef __APPLE__
op_prefix = std : : make_unique < OpenpilotPrefix > ( ) ;
# endif
QObject : : connect ( & settings , & Settings : : changed , [ this ] ( ) {
if ( replay ) replay - > setSegmentCacheLimit ( settings . max_cached_minutes ) ;
} ) ;
}
static bool event_filter ( const Event * e , void * opaque ) {
return ( ( ReplayStream * ) opaque ) - > eventFilter ( e ) ;
}
void ReplayStream : : mergeSegments ( ) {
for ( auto & [ n , seg ] : replay - > segments ( ) ) {
if ( seg & & seg - > isLoaded ( ) & & ! processed_segments . count ( n ) ) {
processed_segments . insert ( n ) ;
const auto & events = seg - > log - > events ;
mergeEvents ( events . cbegin ( ) , events . cend ( ) ) ;
}
}
}
bool ReplayStream : : loadRoute ( const QString & route , const QString & data_dir , uint32_t replay_flags ) {
replay . reset ( new Replay ( route , { " can " , " roadEncodeIdx " , " wideRoadEncodeIdx " , " carParams " } , { } , { } , nullptr , replay_flags , data_dir , this ) ) ;
replay - > setSegmentCacheLimit ( settings . max_cached_minutes ) ;
replay - > installEventFilter ( event_filter , this ) ;
QObject : : connect ( replay . get ( ) , & Replay : : seekedTo , this , & AbstractStream : : seekedTo ) ;
QObject : : connect ( replay . get ( ) , & Replay : : segmentsMerged , this , & ReplayStream : : mergeSegments ) ;
return replay - > load ( ) ;
}
void ReplayStream : : start ( ) {
emit streamStarted ( ) ;
replay - > start ( ) ;
}
bool ReplayStream : : eventFilter ( const Event * event ) {
static double prev_update_ts = 0 ;
// delay posting CAN message if UI thread is busy
if ( event - > which = = cereal : : Event : : Which : : CAN ) {
double current_sec = event - > mono_time / 1e9 - routeStartTime ( ) ;
for ( const auto & c : event - > event . getCan ( ) ) {
MessageId id = { . source = c . getSrc ( ) , . address = c . getAddress ( ) } ;
const auto dat = c . getDat ( ) ;
updateEvent ( id , current_sec , ( const uint8_t * ) dat . begin ( ) , dat . size ( ) ) ;
}
}
double ts = millis_since_boot ( ) ;
if ( ( ts - prev_update_ts ) > ( 1000.0 / settings . fps ) ) {
if ( postEvents ( ) ) {
prev_update_ts = ts ;
}
}
return true ;
}
void ReplayStream : : pause ( bool pause ) {
replay - > pause ( pause ) ;
emit ( pause ? paused ( ) : resume ( ) ) ;
}
AbstractOpenStreamWidget * ReplayStream : : widget ( AbstractStream * * stream ) {
return new OpenReplayWidget ( stream ) ;
}
// OpenReplayWidget
OpenReplayWidget : : OpenReplayWidget ( AbstractStream * * stream ) : AbstractOpenStreamWidget ( stream ) {
// TODO: get route list from api.comma.ai
QGridLayout * grid_layout = new QGridLayout ( ) ;
grid_layout - > addWidget ( new QLabel ( tr ( " Route " ) ) , 0 , 0 ) ;
grid_layout - > addWidget ( route_edit = new QLineEdit ( this ) , 0 , 1 ) ;
route_edit - > setPlaceholderText ( tr ( " Enter remote route name or click browse to select a local route " ) ) ;
auto file_btn = new QPushButton ( tr ( " Browse... " ) , this ) ;
grid_layout - > addWidget ( file_btn , 0 , 2 ) ;
grid_layout - > addWidget ( new QLabel ( tr ( " Video " ) ) , 1 , 0 ) ;
grid_layout - > addWidget ( choose_video_cb = new QComboBox ( this ) , 1 , 1 ) ;
QString items [ ] = { tr ( " No Video " ) , tr ( " Road Camera " ) , tr ( " Wide Road Camera " ) , tr ( " Driver Camera " ) , tr ( " QCamera " ) } ;
for ( int i = 0 ; i < std : : size ( items ) ; + + i ) {
choose_video_cb - > addItem ( items [ i ] ) ;
}
choose_video_cb - > setCurrentIndex ( 1 ) ; // default is road camera;
QVBoxLayout * main_layout = new QVBoxLayout ( this ) ;
main_layout - > addLayout ( grid_layout ) ;
setMinimumWidth ( 550 ) ;
QObject : : connect ( file_btn , & QPushButton : : clicked , [ = ] ( ) {
QString dir = QFileDialog : : getExistingDirectory ( this , tr ( " Open Local Route " ) , settings . last_route_dir ) ;
if ( ! dir . isEmpty ( ) ) {
route_edit - > setText ( dir ) ;
settings . last_route_dir = QFileInfo ( dir ) . absolutePath ( ) ;
}
} ) ;
}
bool OpenReplayWidget : : open ( ) {
QString route = route_edit - > text ( ) ;
QString data_dir ;
if ( int idx = route . lastIndexOf ( ' / ' ) ; idx ! = - 1 ) {
data_dir = route . mid ( 0 , idx + 1 ) ;
route = route . mid ( idx + 1 ) ;
}
bool is_valid_format = Route : : parseRoute ( route ) . str . size ( ) > 0 ;
if ( ! is_valid_format ) {
QMessageBox : : warning ( nullptr , tr ( " Warning " ) , tr ( " Invalid route format: '%1' " ) . arg ( route ) ) ;
} else {
uint32_t flags [ ] = { REPLAY_FLAG_NO_VIPC , REPLAY_FLAG_NONE , REPLAY_FLAG_ECAM , REPLAY_FLAG_DCAM , REPLAY_FLAG_QCAMERA } ;
auto replay_stream = std : : make_unique < ReplayStream > ( qApp ) ;
if ( replay_stream - > loadRoute ( route , data_dir , flags [ choose_video_cb - > currentIndex ( ) ] ) ) {
* stream = replay_stream . release ( ) ;
} else {
QMessageBox : : warning ( nullptr , tr ( " Warning " ) , tr ( " Failed to load route: '%1' " ) . arg ( route ) ) ;
}
}
return * stream ! = nullptr ;
}