From d357bff359e00a9ad8e5a1d7ec70d0653db46398 Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Wed, 13 Jun 2018 00:24:32 +0200 Subject: [PATCH] Some refactoring of the player code, and begin working on the playlist. --- clip_list.cpp | 63 ++++++++++++++++++++++++++------------ clip_list.h | 19 +++++++++++- main.cpp | 1 - mainwindow.cpp | 79 +++++++++++++++++++++++++++++++++++++----------- mainwindow.h | 6 ++++ player.cpp | 58 ++++++++++++++--------------------- player.h | 26 ++++++++++++++-- ui_mainwindow.ui | 72 +++++++++++++++++++++++++++---------------- 8 files changed, 222 insertions(+), 102 deletions(-) diff --git a/clip_list.cpp b/clip_list.cpp index 2599dcc..0d9fc6c 100644 --- a/clip_list.cpp +++ b/clip_list.cpp @@ -15,7 +15,11 @@ int ClipList::rowCount(const QModelIndex &parent) const { int ClipList::columnCount(const QModelIndex &parent) const { if (parent.isValid()) return 0; - return Column::NUM_COLUMNS; + if (display_type == ListDisplay::CLIP_LIST) { + return int(ClipListColumn::NUM_COLUMNS); + } else { + return int(PlayListColumn::NUM_COLUMNS); + } } QVariant ClipList::data(const QModelIndex &parent, int role) const { @@ -54,23 +58,40 @@ QVariant ClipList::headerData(int section, Qt::Orientation orientation, int role if (orientation != Qt::Horizontal) return QVariant(); - switch (section) { - case Column::IN: - return "In"; - case Column::OUT: - return "Out"; - case Column::DURATION: - return "Duration"; - case Column::CAMERA_1: - return "Camera 1"; - case Column::CAMERA_2: - return "Camera 2"; - case Column::CAMERA_3: - return "Camera 3"; - case Column::CAMERA_4: - return "Camera 4"; - default: - return ""; + if (display_type == ListDisplay::CLIP_LIST) { + switch (ClipListColumn(section)) { + case ClipListColumn::IN: + return "In"; + case ClipListColumn::OUT: + return "Out"; + case ClipListColumn::DURATION: + return "Duration"; + case ClipListColumn::CAMERA_1: + return "Camera 1"; + case ClipListColumn::CAMERA_2: + return "Camera 2"; + case ClipListColumn::CAMERA_3: + return "Camera 3"; + case ClipListColumn::CAMERA_4: + return "Camera 4"; + default: + return ""; + } + } else { + switch (PlayListColumn(section)) { + case PlayListColumn::IN: + return "In"; + case PlayListColumn::OUT: + return "Out"; + case PlayListColumn::DURATION: + return "Duration"; + case PlayListColumn::CAMERA: + return "Camera"; + case PlayListColumn::DESCRIPTION: + return "Description"; + default: + return ""; + } } } @@ -83,5 +104,9 @@ void ClipList::add_clip(const Clip &clip) void ClipList::emit_data_changed(size_t row) { - emit dataChanged(index(row, 0), index(row, 6)); + if (display_type == ListDisplay::CLIP_LIST) { + emit dataChanged(index(row, 0), index(row, int(ClipListColumn::NUM_COLUMNS))); + } else { + emit dataChanged(index(row, 0), index(row, int(PlayListColumn::NUM_COLUMNS))); + } } diff --git a/clip_list.h b/clip_list.h index 1cdbf35..ddb4a4a 100644 --- a/clip_list.h +++ b/clip_list.h @@ -11,13 +11,21 @@ struct Clip { int64_t pts_in = -1, pts_out = -1; std::vector descriptions; // One per camera. + unsigned stream_idx = 0; // For the playlist only. }; +// FIXME: This should be split into a separate clip list and play list model. class ClipList : public QAbstractTableModel { Q_OBJECT public: - enum Column { + enum class ListDisplay { + CLIP_LIST, + PLAY_LIST + }; + ClipList(ListDisplay display_type) : display_type(display_type) {} + + enum class ClipListColumn { IN, OUT, DURATION, @@ -27,6 +35,14 @@ public: CAMERA_4, NUM_COLUMNS }; + enum class PlayListColumn { + IN, + OUT, + DURATION, + CAMERA, + DESCRIPTION, + NUM_COLUMNS + }; int rowCount(const QModelIndex &parent) const override; int columnCount(const QModelIndex &parent) const override; @@ -66,6 +82,7 @@ public: private: std::vector clips; + ListDisplay display_type; }; #endif // !defined (_CLIP_LIST_H) diff --git a/main.cpp b/main.cpp index a0e9342..52c105f 100644 --- a/main.cpp +++ b/main.cpp @@ -52,7 +52,6 @@ int main(int argc, char **argv) mainWindow.show(); thread(record_thread_func).detach(); - start_player_thread(); return app.exec(); } diff --git a/mainwindow.cpp b/mainwindow.cpp index 1a5953d..aba38db 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -13,7 +13,7 @@ using namespace std; MainWindow *global_mainwindow = nullptr; extern int64_t current_pts; -ClipList *clips; +ClipList *cliplist_clips, *playlist_clips; MainWindow::MainWindow() : ui(new Ui::MainWindow) @@ -21,46 +21,89 @@ MainWindow::MainWindow() global_mainwindow = this; ui->setupUi(this); - clips = new ClipList; - ui->clip_list->setModel(clips); + cliplist_clips = new ClipList(ClipList::ListDisplay::CLIP_LIST); + ui->clip_list->setModel(cliplist_clips); + + playlist_clips = new ClipList(ClipList::ListDisplay::PLAY_LIST); + ui->playlist->setModel(playlist_clips); - // TODO: Make these into buttons. // TODO: These are too big for lambdas. QShortcut *cue_in = new QShortcut(QKeySequence(Qt::Key_A), this); - connect(cue_in, &QShortcut::activated, []{ - if (!clips->empty() && clips->back()->pts_out < 0) { - clips->back()->pts_in = current_pts; + connect(cue_in, &QShortcut::activated, ui->cue_in_btn, &QPushButton::click); + connect(ui->cue_in_btn, &QPushButton::clicked, []{ + if (!cliplist_clips->empty() && cliplist_clips->back()->pts_out < 0) { + cliplist_clips->back()->pts_in = current_pts; return; } Clip clip; clip.pts_in = current_pts; - clips->add_clip(clip); + cliplist_clips->add_clip(clip); }); QShortcut *cue_out = new QShortcut(QKeySequence(Qt::Key_S), this); - connect(cue_out, &QShortcut::activated, []{ - if (!clips->empty()) { - clips->back()->pts_out = current_pts; + connect(cue_out, &QShortcut::activated, ui->cue_out_btn, &QPushButton::click); + connect(ui->cue_out_btn, &QPushButton::clicked, []{ + if (!cliplist_clips->empty()) { + cliplist_clips->back()->pts_out = current_pts; // TODO: select the row in the clip list? } }); - QShortcut *preview_shortcut = new QShortcut(QKeySequence(Qt::Key_W), this); - connect(preview_shortcut, &QShortcut::activated, this, &MainWindow::preview_clicked); + QShortcut *queue = new QShortcut(QKeySequence(Qt::Key_Q), this); + connect(queue, &QShortcut::activated, ui->queue_btn, &QPushButton::click); + connect(ui->queue_btn, &QPushButton::clicked, this, &MainWindow::queue_clicked); + + QShortcut *preview = new QShortcut(QKeySequence(Qt::Key_W), this); + connect(preview, &QShortcut::activated, ui->preview_btn, &QPushButton::click); + connect(ui->preview_btn, &QPushButton::clicked, this, &MainWindow::preview_clicked); + + QShortcut *play = new QShortcut(QKeySequence(Qt::Key_Space), this); + connect(play, &QShortcut::activated, ui->play_btn, &QPushButton::click); + connect(ui->play_btn, &QPushButton::clicked, this, &MainWindow::play_clicked); + + preview_player = new Player(ui->preview_display); +} + +void MainWindow::queue_clicked() +{ + QItemSelectionModel *selected = ui->clip_list->selectionModel(); + if (!selected->hasSelection()) { + Clip clip = *cliplist_clips->back(); + clip.stream_idx = 0; + playlist_clips->add_clip(clip); + return; + } + + QModelIndex index = selected->currentIndex(); + if (index.column() >= int(ClipList::ClipListColumn::CAMERA_1) && + index.column() <= int(ClipList::ClipListColumn::CAMERA_4)) { + Clip clip = *cliplist_clips->clip(index.row()); + clip.stream_idx = index.column() - int(ClipList::ClipListColumn::CAMERA_1); + playlist_clips->add_clip(clip); + } } void MainWindow::preview_clicked() { QItemSelectionModel *selected = ui->clip_list->selectionModel(); if (!selected->hasSelection()) { - play_clip(*clips->back(), 0); + preview_player->play_clip(*cliplist_clips->back(), 0); return; } QModelIndex index = selected->currentIndex(); - if (index.column() >= ClipList::Column::CAMERA_1 && - index.column() <= ClipList::Column::CAMERA_4) { - unsigned stream_idx = index.column() - ClipList::Column::CAMERA_1; - play_clip(*clips->clip(index.row()), stream_idx); + if (index.column() >= int(ClipList::ClipListColumn::CAMERA_1) && + index.column() <= int(ClipList::ClipListColumn::CAMERA_4)) { + unsigned stream_idx = index.column() - int(ClipList::ClipListColumn::CAMERA_1); + preview_player->play_clip(*cliplist_clips->clip(index.row()), stream_idx); + } +} + +void MainWindow::play_clicked() +{ + QItemSelectionModel *selected = ui->playlist->selectionModel(); + if (!selected->hasSelection()) { + ui->playlist->selectRow(0); + return; } } diff --git a/mainwindow.h b/mainwindow.h index a1254b1..583f0e2 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -9,6 +9,8 @@ namespace Ui { class MainWindow; } // namespace Ui +class Player; + class MainWindow : public QMainWindow { Q_OBJECT @@ -20,7 +22,11 @@ public: Ui::MainWindow *ui; private: + Player *preview_player; + + void queue_clicked(); void preview_clicked(); + void play_clicked(); }; extern MainWindow *global_mainwindow; diff --git a/player.cpp b/player.cpp index a2c1a93..6e45f39 100644 --- a/player.cpp +++ b/player.cpp @@ -7,10 +7,8 @@ #include "clip_list.h" #include "defs.h" -#include "mainwindow.h" -#include "ffmpeg_raii.h" -#include "post_to_main_thread.h" -#include "ui_mainwindow.h" +#include "jpeg_frame_view.h" +#include "player.h" using namespace std; using namespace std::chrono; @@ -18,38 +16,27 @@ using namespace std::chrono; extern mutex frame_mu; extern vector frames[MAX_STREAMS]; -struct PlaylistClip { - Clip clip; - unsigned stream_idx; -}; -vector current_cue_playlist; -mutex playlist_mu; - -enum { PAUSED, PLAYING } cue_state = PAUSED; -mutex cue_state_mu; -condition_variable cue_is_playing; -//int cue_playlist_index = -1; -//int64_t cue_playlist_pos = 0; - -int preview_thread_func() +void Player::thread_func() { for ( ;; ) { // Wait until we're supposed to play something. { unique_lock lock(cue_state_mu); - cue_is_playing.wait(lock, []{ + cue_is_playing.wait(lock, [this]{ return cue_state == PLAYING; //return current_cue_status.origin != steady_clock::time_point::max(); }); } - PlaylistClip clip; + Clip clip; + unsigned stream_idx; { - lock_guard lock2(playlist_mu); - clip = current_cue_playlist[0]; + lock_guard lock2(mu); + clip = current_clip; + stream_idx = current_stream_idx; } steady_clock::time_point origin = steady_clock::now(); - int64_t pts_origin = clip.clip.pts_in; + int64_t pts_origin = clip.pts_in; int64_t next_pts = pts_origin; @@ -60,19 +47,19 @@ int preview_thread_func() steady_clock::time_point next_frame_start = origin + microseconds((next_pts - pts_origin) * int(1000000 / speed) / 12800); this_thread::sleep_until(next_frame_start); - global_mainwindow->ui->preview_display->setFrame(clip.stream_idx, next_pts); + destination->setFrame(stream_idx, next_pts); // Find the next frame. { lock_guard lock2(frame_mu); - auto it = upper_bound(frames[clip.stream_idx].begin(), - frames[clip.stream_idx].end(), + auto it = upper_bound(frames[stream_idx].begin(), + frames[stream_idx].end(), next_pts); - if (it == frames[clip.stream_idx].end()) { + if (it == frames[stream_idx].end()) { eof = true; } else { next_pts = *it; - if (next_pts >= clip.clip.pts_out) { + if (next_pts >= clip.pts_out) { eof = true; } } @@ -80,7 +67,7 @@ int preview_thread_func() if (eof) break; } - // TODO: advance the playlist and look for the next element. + // TODO: callback so that the next playlist item can be cued. { unique_lock lock(cue_state_mu); cue_state = PAUSED; @@ -88,17 +75,18 @@ int preview_thread_func() } } -void start_player_thread() +Player::Player(JPEGFrameView *destination) + : destination(destination) { - thread(preview_thread_func).detach(); + thread(&Player::thread_func, this).detach(); } -void play_clip(const Clip &clip, unsigned stream_idx) +void Player::play_clip(const Clip &clip, unsigned stream_idx) { { - lock_guard lock(playlist_mu); - current_cue_playlist.clear(); - current_cue_playlist.push_back(PlaylistClip{ clip, stream_idx }); + lock_guard lock(mu); + current_clip = clip; + current_stream_idx = stream_idx; } { diff --git a/player.h b/player.h index 960bc22..debf919 100644 --- a/player.h +++ b/player.h @@ -3,7 +3,29 @@ #include "clip_list.h" -void start_player_thread(); -void play_clip(const Clip &clip, unsigned stream_idx); +#include +#include + +class JPEGFrameView; + +class Player { +public: + Player(JPEGFrameView *destination); + + void play_clip(const Clip &clip, unsigned stream_idx); + +private: + void thread_func(); + + JPEGFrameView *destination; + + std::mutex mu; + Clip current_clip; // Under mu. + unsigned current_stream_idx; // Under mu. + + enum { PAUSED, PLAYING } cue_state = PAUSED; // Under cue_state_mu. + std::mutex cue_state_mu; + std::condition_variable cue_is_playing; +}; #endif // !defined(_PLAYER_H) diff --git a/ui_mainwindow.ui b/ui_mainwindow.ui index 9f8fee7..7e51adc 100644 --- a/ui_mainwindow.ui +++ b/ui_mainwindow.ui @@ -90,36 +90,56 @@ + + + + + + Queue (&Q) + + + + + + + Preview (&W) + + + + + + + Cue in (&A) + + + + + + + Cue out (&S) + + + + + + + Play (space) + + + + + - - - - In - - - - - Out - - - - - Duration - - - - - Camera - - - - - Description - - + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + -- 2.39.2