-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathpivid_play.cpp
198 lines (167 loc) · 6.76 KB
/
pivid_play.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
// Simple command line tool to exercise video decoding and playback.
#include <cmath>
#include <fstream>
#include <CLI/App.hpp>
#include <CLI/Config.hpp>
#include <CLI/Formatter.hpp>
#include <fmt/core.h>
#include <nlohmann/json.hpp>
#include "display_output.h"
#include "logging_policy.h"
#include "script_data.h"
#include "script_runner.h"
namespace pivid {
namespace {
std::shared_ptr<log::logger> const& play_logger() {
static const auto logger = make_logger("pivid_play");
return logger;
}
std::unique_ptr<DisplayDriver> find_driver(std::string const& dev_arg) {
fmt::print("=== Video drivers ===\n");
std::optional<DisplayDriverListing> found;
for (auto const& d : list_display_drivers(global_system())) {
auto const text = debug(d);
if (!found && text.find(dev_arg) != std::string::npos)
found = d;
fmt::print("{} {}\n", (found == d) ? "=>" : " ", debug(d));
}
fmt::print("\n");
CHECK_RUNTIME(found, "No DRM device matching \"{}\"", dev_arg);
return open_display_driver(global_system(), found->dev_file);
}
void set_kernel_debug(bool enable) {
auto const debug_file = "/sys/module/drm/parameters/debug";
auto const debug_stat = global_system()->stat(debug_file).ex(debug_file);
if ((debug_stat.st_mode & 022) == 0 && debug_stat.st_uid == 0) {
if (!enable) return; // No permissions, assume disabled
std::vector<std::string> argv = {"sudo", "chmod", "go+rw", debug_file};
fmt::print("!!! Running:");
for (auto const& arg : argv) fmt::print(" {}", arg);
fmt::print("\n");
fflush(stdout);
auto const pid = global_system()->spawn(argv[0], argv).ex(argv[0]);
auto const ex = global_system()->wait(P_PID, pid, WEXITED).ex(argv[0]);
CHECK_RUNTIME(!ex.si_status, "Kernel debug chmod error");
}
auto const fd = global_system()->open(debug_file, O_WRONLY).ex(debug_file);
auto const val = fmt::format("0x{:x}", enable ? 0x3DF : 0);
fmt::print("Kernel debug: Writing {} to {}\n\n", val, debug_file);
fd->write(val.data(), val.size()).ex(debug_file);
}
Script make_script(
std::string const& media_arg,
std::string const& screen_arg,
ScriptMode const& mode,
double seek_arg
) {
CHECK_ARG(!media_arg.empty(), "No media for make_script");
Script script;
auto *screen = &script.screens.try_emplace(screen_arg).first->second;
screen->mode = mode;
ScriptLayer* layer = &screen->layers.emplace_back();
layer->media = media_arg;
layer->play.segments.push_back(
linear_segment({0, 0 + 1e12}, {seek_arg, seek_arg + 1e12})
);
script.zero_time = global_system()->clock();
play_logger()->info("Start: {}", format_realtime(script.zero_time));
return script;
}
Script load_script(std::string const& script_file) {
auto const logger = play_logger();
auto const sys = global_system();
std::ifstream ifs;
ifs.exceptions(~std::ifstream::goodbit);
ifs.open(script_file, std::ios::binary);
std::string const text(
(std::istreambuf_iterator<char>(ifs)),
(std::istreambuf_iterator<char>())
);
double const default_zero_time = sys->clock();
play_logger()->info("Start: {}", format_realtime(default_zero_time));
return parse_script(text, default_zero_time);
}
void run_script(ScriptContext const& context, Script const& script) {
auto const logger = play_logger();
auto const sys = global_system();
auto const waiter = sys->make_flag(CLOCK_MONOTONIC);
ASSERT(script.main_loop_hz > 0);
double const period = 1.0 / script.main_loop_hz;
double loop_mono = 0.0;
auto const runner = make_script_runner(context);
for (;;) {
loop_mono = std::max(sys->clock(CLOCK_MONOTONIC), loop_mono + period);
waiter->sleep_until(loop_mono);
runner->update(script);
bool done = true;
const double now_t0 = sys->clock() - script.zero_time;
for (auto const& [conn, screen] : script.screens) {
for (auto const& layer : screen.layers) {
auto const range = layer.play.range({now_t0, now_t0 + 1e12});
auto const& info = runner->file_info(layer.media);
auto const duration = info.duration.value_or(0.001);
TRACE(
logger, "{:.3f} / {:.3f}s: {}",
range.bounds().begin, duration, layer.media
);
if (range.bounds().begin < duration) done = false;
}
}
if (done) {
logger->info("All media done playing");
break;
}
}
}
} // namespace
// Main program, parses flags and calls the decoder loop.
extern "C" int main(int const argc, char const* const* const argv) {
std::string dev_arg;
std::string screen_arg = "HDMI-1";
std::string log_arg;
std::string media_arg;
std::string script_arg;
ScriptMode mode_arg = {{1920, 1080}, 60};
double seek_arg = -0.2;
bool debug_kernel = false;
CLI::App app("Decode and show a media file");
app.add_option("--dev", dev_arg, "DRM driver description substring");
app.add_option("--log", log_arg, "Log level/configuration");
app.add_option("--mode_x", mode_arg.size.x, "Video pixels per line");
app.add_option("--mode_y", mode_arg.size.y, "Video scan lines");
app.add_option("--mode_hz", mode_arg.hz, "Video refresh rate");
app.add_option("--screen", screen_arg, "Video output connector");
app.add_option("--seek", seek_arg, "Seconds into media to start");
app.add_flag("--debug_kernel", debug_kernel, "Enable kernel DRM debugging");
auto input = app.add_option_group("Input")->require_option(0, 1);
input->add_option("--media", media_arg, "Media file to play");
input->add_option("--script", script_arg, "Script file to play");
CLI11_PARSE(app, argc, argv);
configure_logging(log_arg);
set_kernel_debug(debug_kernel);
auto const logger = play_logger();
try {
if (!script_arg.empty()) {
logger->info("Script: {}", script_arg);
ScriptContext context = {};
context.driver = find_driver(dev_arg);
context.file_base = script_arg;
run_script(context, load_script(script_arg));
} else if (!media_arg.empty()) {
ScriptContext context = {};
context.driver = find_driver(dev_arg);
context.file_base = global_system()->realpath(".").ex("getcwd");
run_script(
context, make_script(media_arg, screen_arg, mode_arg, seek_arg)
);
} else {
logger->warn("No --script or --media to play");
}
} catch (std::exception const& e) {
logger->critical("{}", e.what());
return 1;
}
fmt::print("Done!\n\n");
return 0;
}
} // namespace pivid