OBJS += $(OBJS_WITH_MOC)
OBJS += $(OBJS_WITH_MOC:.o=.moc.o)
-OBJS += ffmpeg_raii.o main.o
+OBJS += ffmpeg_raii.o main.o player.o
%.o: %.cpp
$(CXX) -MMD -MP $(CPPFLAGS) $(CXXFLAGS) -o $@ -c $<
}
}
Clip *operator->() { return &clip; }
+ Clip &operator*() { return clip; }
private:
Clip &clip;
--- /dev/null
+#ifndef _DEFS_H
+#define _DEFS_H 1
+
+#define MAX_STREAMS 16
+
+#endif // !defined(_DEFS_H)
#include <stdint.h>
#include <chrono>
+#include <condition_variable>
#include <memory>
#include <mutex>
#include <string>
#include <QApplication>
+#include "clip_list.h"
+#include "defs.h"
#include "mainwindow.h"
#include "ffmpeg_raii.h"
+#include "player.h"
#include "post_to_main_thread.h"
#include "ui_mainwindow.h"
-#define MAX_STREAMS 16
-
using namespace std;
using namespace std::chrono;
mutex frame_mu;
vector<int64_t> frames[MAX_STREAMS];
-int thread_func();
+int record_thread_func();
int main(int argc, char **argv)
{
MainWindow mainWindow;
mainWindow.show();
- thread(thread_func).detach();
+ thread(record_thread_func).detach();
+ start_player_thread();
return app.exec();
}
-int thread_func()
+int record_thread_func()
{
auto format_ctx = avformat_open_input_unique("multiangle.mp4", nullptr, nullptr);
if (format_ctx == nullptr) {
#include "mainwindow.h"
#include "clip_list.h"
+#include "player.h"
#include "ui_mainwindow.h"
#include <string>
MainWindow *global_mainwindow = nullptr;
extern int64_t current_pts;
+ClipList *clips;
MainWindow::MainWindow()
: ui(new Ui::MainWindow)
global_mainwindow = this;
ui->setupUi(this);
- ClipList *clips = new ClipList;
+ clips = new ClipList;
ui->clip_list->setModel(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, [clips]{
+ connect(cue_in, &QShortcut::activated, []{
if (!clips->empty() && clips->back()->pts_out < 0) {
clips->back()->pts_in = current_pts;
return;
});
QShortcut *cue_out = new QShortcut(QKeySequence(Qt::Key_S), this);
- connect(cue_out, &QShortcut::activated, [clips]{
+ connect(cue_out, &QShortcut::activated, []{
if (!clips->empty()) {
clips->back()->pts_out = current_pts;
// TODO: select the row in the clip list?
void MainWindow::preview_clicked()
{
- printf("preview\n");
+ play_clip(*clips->back(), 0);
}
--- /dev/null
+#include <algorithm>
+#include <chrono>
+#include <condition_variable>
+#include <mutex>
+#include <thread>
+#include <vector>
+
+#include "clip_list.h"
+#include "defs.h"
+#include "mainwindow.h"
+#include "ffmpeg_raii.h"
+#include "post_to_main_thread.h"
+#include "ui_mainwindow.h"
+
+using namespace std;
+using namespace std::chrono;
+
+extern mutex frame_mu;
+extern vector<int64_t> frames[MAX_STREAMS];
+
+struct PlaylistClip {
+ Clip clip;
+ unsigned stream_idx;
+};
+vector<PlaylistClip> 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()
+{
+ for ( ;; ) {
+ // Wait until we're supposed to play something.
+ {
+ unique_lock<mutex> lock(cue_state_mu);
+ cue_is_playing.wait(lock, []{
+ return cue_state == PLAYING;
+ //return current_cue_status.origin != steady_clock::time_point::max();
+ });
+ }
+
+ PlaylistClip clip;
+ {
+ lock_guard<mutex> lock2(playlist_mu);
+ clip = current_cue_playlist[0];
+ }
+ steady_clock::time_point origin = steady_clock::now();
+ int64_t pts_origin = clip.clip.pts_in;
+
+ int64_t next_pts = pts_origin;
+
+ bool eof = false;
+ while (!eof) { // TODO: check for abort
+ // FIXME: assumes a given timebase.
+ double speed = 0.5;
+ 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);
+
+ // Find the next frame.
+ {
+ lock_guard<mutex> lock2(frame_mu);
+ auto it = upper_bound(frames[clip.stream_idx].begin(),
+ frames[clip.stream_idx].end(),
+ next_pts);
+ if (it == frames[clip.stream_idx].end()) {
+ eof = true;
+ } else {
+ next_pts = *it;
+ if (next_pts >= clip.clip.pts_out) {
+ eof = true;
+ }
+ }
+ }
+ if (eof) break;
+ }
+
+ // TODO: advance the playlist and look for the next element.
+ {
+ unique_lock<mutex> lock(cue_state_mu);
+ cue_state = PAUSED;
+ }
+ }
+}
+
+void start_player_thread()
+{
+ thread(preview_thread_func).detach();
+}
+
+void play_clip(const Clip &clip, unsigned stream_idx)
+{
+ {
+ lock_guard<mutex> lock(playlist_mu);
+ current_cue_playlist.clear();
+ current_cue_playlist.push_back(PlaylistClip{ clip, stream_idx });
+ }
+
+ {
+ lock_guard<mutex> lock(cue_state_mu);
+ cue_state = PLAYING;
+ cue_is_playing.notify_all();
+ }
+}
--- /dev/null
+#ifndef _PLAYER_H
+#define _PLAYER_H 1
+
+#include "clip_list.h"
+
+void start_player_thread();
+void play_clip(const Clip &clip, unsigned stream_idx);
+
+#endif // !defined(_PLAYER_H)
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
- <widget class="QGraphicsView" name="preview_display"/>
+ <widget class="JPEGFrameView" name="preview_display"/>
</item>
<item>
<widget class="QLabel" name="label_2">
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
- <widget class="QGraphicsView" name="live_display"/>
+ <widget class="JPEGFrameView" name="live_display"/>
</item>
<item>
<widget class="QLabel" name="label_3">