write most recent frames to video
This commit is contained in:
parent
bef7c1a740
commit
daa7945c89
|
@ -14,11 +14,11 @@
|
||||||
"keys":
|
"keys":
|
||||||
{
|
{
|
||||||
"context": " ",
|
"context": " ",
|
||||||
"keep-stash": "k"
|
"print-video-memory": "?"
|
||||||
},
|
},
|
||||||
"recording":
|
"recording":
|
||||||
{
|
{
|
||||||
"write-mp4": true,
|
"write-mp4": true,
|
||||||
"video-frame-length": 30
|
"video-frame-length": 16.667
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,8 @@ Configuration::Configuration(Node *parent, fs::path path) : Node(parent)
|
||||||
void Configuration::set_defaults()
|
void Configuration::set_defaults()
|
||||||
{
|
{
|
||||||
sys_config["keys"] = {
|
sys_config["keys"] = {
|
||||||
{"record", {"CTRL", "SHIFT", "r"}},
|
{"record", {"CTRL", "SHIFT", "f10"}},
|
||||||
|
{"save-current-stash", {"CTRL", "SHIFT", "v"}},
|
||||||
{"screenshot", "f9"},
|
{"screenshot", "f9"},
|
||||||
{"action", "space"},
|
{"action", "space"},
|
||||||
{"up", "up"},
|
{"up", "up"},
|
||||||
|
@ -38,7 +39,9 @@ void Configuration::set_defaults()
|
||||||
{"write-mp4", false},
|
{"write-mp4", false},
|
||||||
{"max-stash-length", 5000},
|
{"max-stash-length", 5000},
|
||||||
{"max-in-game-stashes", 3},
|
{"max-in-game-stashes", 3},
|
||||||
{"max-video-stashes", 40}
|
{"max-video-stashes", 40},
|
||||||
|
{"max-video-memory", 1000},
|
||||||
|
{"mp4-pixel-format", "yuv444p"}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
159
src/Recorder.cpp
159
src/Recorder.cpp
|
@ -5,7 +5,8 @@
|
||||||
Recorder::Recorder(Node* parent) : Node(parent)
|
Recorder::Recorder(Node* parent) : Node(parent)
|
||||||
{
|
{
|
||||||
get_delegate().subscribe(&Recorder::respond, this);
|
get_delegate().subscribe(&Recorder::respond, this);
|
||||||
in_game_stashes.push_back(Stash());
|
// in_game_stashes.push_back(Stash());
|
||||||
|
|
||||||
animation.play();
|
animation.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,10 +32,15 @@ void Recorder::respond(SDL_Event& event)
|
||||||
{
|
{
|
||||||
start_recording();
|
start_recording();
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::cout << "Writing in progress, cannot start recording" <<
|
||||||
|
std::endl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (get_delegate().compare(event, "keep-stash"))
|
else if (get_delegate().compare(event, "save-current-stash"))
|
||||||
{
|
{
|
||||||
std::cout << get_memory_size() << "mb" << std::endl;
|
grab_stash();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,11 +61,52 @@ void Recorder::capture_screen()
|
||||||
std::cout << "Saved screenshot to " << path.string() << std::endl;
|
std::cout << "Saved screenshot to " << path.string() << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Recorder::grab_stash()
|
||||||
|
{
|
||||||
|
if (not is_recording and not writing_recording)
|
||||||
|
{
|
||||||
|
int length = get_configuration()["recording"]["max-stash-length"];
|
||||||
|
std::cout << "stashing most recent " << (length / 1000) <<
|
||||||
|
"seconds of video..." << std::endl;
|
||||||
|
most_recent_stash = current_stash;
|
||||||
|
current_stash = Stash();
|
||||||
|
writing_recording = true;
|
||||||
|
std::function<void()> f =
|
||||||
|
std::bind(&Recorder::write_most_recent_frames, this);
|
||||||
|
std::thread writing(f);
|
||||||
|
writing.detach();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::cout << "Recording in progress, cannot grab most recent frames" <<
|
||||||
|
std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Recorder::write_most_recent_frames()
|
||||||
|
{
|
||||||
|
write_stash_frames(false, 0);
|
||||||
|
if (get_configuration()["recording"]["write-mp4"])
|
||||||
|
{
|
||||||
|
write_mp4();
|
||||||
|
}
|
||||||
|
std::cout << "Wrote video frames to " << current_video_directory.string() <<
|
||||||
|
std::endl;
|
||||||
|
writing_recording = false;
|
||||||
|
}
|
||||||
|
|
||||||
void Recorder::start_recording()
|
void Recorder::start_recording()
|
||||||
{
|
{
|
||||||
std::cout << "Starting recording..." << std::endl;
|
if (not writing_recording)
|
||||||
is_recording = true;
|
{
|
||||||
video_stashes.push_back(Stash());
|
std::cout << "Starting recording..." << std::endl;
|
||||||
|
is_recording = true;
|
||||||
|
video_stashes.push_back(Stash());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::cout << "Writing in progress, cannot start recording" << std::endl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Recorder::add_frame()
|
void Recorder::add_frame()
|
||||||
|
@ -68,15 +115,14 @@ void Recorder::add_frame()
|
||||||
int bytes = Display::bpp / 8 * size.x * size.y;
|
int bytes = Display::bpp / 8 * size.x * size.y;
|
||||||
unsigned char* pixels = new unsigned char[bytes];
|
unsigned char* pixels = new unsigned char[bytes];
|
||||||
get_display().get_screen_pixels(pixels, size.x, size.y);
|
get_display().get_screen_pixels(pixels, size.x, size.y);
|
||||||
Stash& stash = in_game_stashes.back();
|
|
||||||
int max_length = get_configuration()["recording"]["max-stash-length"];
|
int max_length = get_configuration()["recording"]["max-stash-length"];
|
||||||
float length = get_frame_length() * stash.pixel_buffers.size();
|
float length = get_frame_length() * current_stash.pixel_buffers.size();
|
||||||
if (length > max_length)
|
if (length > max_length)
|
||||||
{
|
{
|
||||||
delete[] stash.pixel_buffers.front();
|
delete[] current_stash.pixel_buffers.front();
|
||||||
stash.pixel_buffers.erase(stash.pixel_buffers.begin());
|
current_stash.pixel_buffers.erase(current_stash.pixel_buffers.begin());
|
||||||
}
|
}
|
||||||
stash.pixel_buffers.push_back(pixels);
|
current_stash.pixel_buffers.push_back(pixels);
|
||||||
if (is_recording)
|
if (is_recording)
|
||||||
{
|
{
|
||||||
unsigned char* vid_pixels = new unsigned char[bytes];
|
unsigned char* vid_pixels = new unsigned char[bytes];
|
||||||
|
@ -84,12 +130,14 @@ void Recorder::add_frame()
|
||||||
video_stashes.back().pixel_buffers.push_back(vid_pixels);
|
video_stashes.back().pixel_buffers.push_back(vid_pixels);
|
||||||
if (video_stashes.back().pixel_buffers.size() * get_frame_length() > max_length)
|
if (video_stashes.back().pixel_buffers.size() * get_frame_length() > max_length)
|
||||||
{
|
{
|
||||||
std::function<void(int)> f =
|
std::function<void(bool, int)> f =
|
||||||
std::bind(&Recorder::write_stash_frames, this, std::placeholders::_1);
|
std::bind(&Recorder::write_stash_frames, this,
|
||||||
std::thread writing(f, video_stashes.size() - 1);
|
std::placeholders::_1, std::placeholders::_2);
|
||||||
|
std::thread writing(f, true, video_stashes.size() - 1);
|
||||||
writing.detach();
|
writing.detach();
|
||||||
video_stashes.push_back(
|
video_stashes.push_back(
|
||||||
Stash(video_stashes.back().frame_offset + video_stashes.back().pixel_buffers.size()));
|
Stash(video_stashes.back().frame_offset +
|
||||||
|
video_stashes.back().pixel_buffers.size()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,9 +158,10 @@ int Recorder::get_memory_size()
|
||||||
return size * bytes / 1000000;
|
return size * bytes / 1000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Recorder::write_stash_frames(int index)
|
void Recorder::write_stash_frames(bool is_video, int index)
|
||||||
{
|
{
|
||||||
if (video_stashes[index].frame_offset == 0)
|
int frame_offset = is_video ? video_stashes[index].frame_offset : 0;
|
||||||
|
if (not frame_offset)
|
||||||
{
|
{
|
||||||
nlohmann::json config = get_configuration();
|
nlohmann::json config = get_configuration();
|
||||||
fs::path root = config["path"]["video"];
|
fs::path root = config["path"]["video"];
|
||||||
|
@ -129,19 +178,28 @@ void Recorder::write_stash_frames(int index)
|
||||||
fs::path gif_path = sfw::get_next_file_name(
|
fs::path gif_path = sfw::get_next_file_name(
|
||||||
current_video_directory, 3, "gif-", ".gif");
|
current_video_directory, 3, "gif-", ".gif");
|
||||||
float elapsed = 0, last_gif_write = 0, gif_write_overflow = 0;
|
float elapsed = 0, last_gif_write = 0, gif_write_overflow = 0;
|
||||||
for (int ii = video_stashes[index].frame_offset;
|
for (int ii = frame_offset;
|
||||||
not video_stashes[index].pixel_buffers.empty(); ii++)
|
not (is_video ? video_stashes[index].pixel_buffers.empty() :
|
||||||
|
most_recent_stash.pixel_buffers.empty()); ii++)
|
||||||
{
|
{
|
||||||
frame = get_display().get_screen_surface_from_pixels(
|
if (is_video)
|
||||||
video_stashes[index].pixel_buffers.front());
|
{
|
||||||
|
frame = get_display().get_screen_surface_from_pixels(
|
||||||
|
video_stashes[index].pixel_buffers.front());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
frame = get_display().get_screen_surface_from_pixels(
|
||||||
|
most_recent_stash.pixel_buffers.front());
|
||||||
|
}
|
||||||
std::stringstream name;
|
std::stringstream name;
|
||||||
name << sfw::pad(ii, 5) << ".png";
|
name << sfw::pad(ii, 5) << ".png";
|
||||||
fs::path path = current_video_directory / name.str();
|
fs::path path = current_video_directory / name.str();
|
||||||
IMG_SavePNG(frame, path.string().c_str());
|
IMG_SavePNG(frame, path.string().c_str());
|
||||||
if (ii == video_stashes[index].frame_offset or
|
if (ii == frame_offset or
|
||||||
elapsed - last_gif_write + gif_write_overflow >= gif_frame_length)
|
elapsed - last_gif_write + gif_write_overflow >= gif_frame_length)
|
||||||
{
|
{
|
||||||
if (ii == video_stashes[index].frame_offset)
|
if (ii == frame_offset)
|
||||||
{
|
{
|
||||||
GifBegin(&gif_writer, gif_path.string().c_str(), frame->w,
|
GifBegin(&gif_writer, gif_path.string().c_str(), frame->w,
|
||||||
frame->h, gif_frame_length / 10);
|
frame->h, gif_frame_length / 10);
|
||||||
|
@ -155,8 +213,18 @@ void Recorder::write_stash_frames(int index)
|
||||||
frame->w, frame->h, gif_frame_length / 10);
|
frame->w, frame->h, gif_frame_length / 10);
|
||||||
}
|
}
|
||||||
elapsed += get_frame_length();
|
elapsed += get_frame_length();
|
||||||
delete[] video_stashes[index].pixel_buffers.front();
|
if (is_video)
|
||||||
video_stashes[index].pixel_buffers.erase(video_stashes[index].pixel_buffers.begin());
|
{
|
||||||
|
delete[] video_stashes[index].pixel_buffers.front();
|
||||||
|
video_stashes[index].pixel_buffers.erase(
|
||||||
|
video_stashes[index].pixel_buffers.begin());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
delete[] most_recent_stash.pixel_buffers.front();
|
||||||
|
most_recent_stash.pixel_buffers.erase(
|
||||||
|
most_recent_stash.pixel_buffers.begin());
|
||||||
|
}
|
||||||
SDL_FreeSurface(frame);
|
SDL_FreeSurface(frame);
|
||||||
}
|
}
|
||||||
GifEnd(&gif_writer);
|
GifEnd(&gif_writer);
|
||||||
|
@ -164,7 +232,8 @@ void Recorder::write_stash_frames(int index)
|
||||||
|
|
||||||
void Recorder::keep_stash()
|
void Recorder::keep_stash()
|
||||||
{
|
{
|
||||||
in_game_stashes.push_back(Stash());
|
in_game_stashes.push_back(current_stash);
|
||||||
|
current_stash = Stash();
|
||||||
int max_stashes = get_configuration()["recording"]["max-in-game-stashes"];
|
int max_stashes = get_configuration()["recording"]["max-in-game-stashes"];
|
||||||
if (in_game_stashes.size() > max_stashes)
|
if (in_game_stashes.size() > max_stashes)
|
||||||
{
|
{
|
||||||
|
@ -190,7 +259,7 @@ void Recorder::end_recording()
|
||||||
|
|
||||||
void Recorder::finish_writing_video()
|
void Recorder::finish_writing_video()
|
||||||
{
|
{
|
||||||
write_stash_frames(video_stashes.size() - 1);
|
write_stash_frames(true, video_stashes.size() - 1);
|
||||||
int count;
|
int count;
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
|
@ -205,25 +274,37 @@ void Recorder::finish_writing_video()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
video_stashes.clear();
|
video_stashes.clear();
|
||||||
writing_recording = false;
|
|
||||||
std::cout << "Wrote video frames to " << current_video_directory.string() <<
|
|
||||||
std::endl;
|
|
||||||
glm::ivec2 size = get_display().get_window_size();
|
|
||||||
if (get_configuration()["recording"]["write-mp4"])
|
if (get_configuration()["recording"]["write-mp4"])
|
||||||
{
|
{
|
||||||
std::stringstream mp4_command;
|
write_mp4();
|
||||||
fs::path images_match = current_video_directory / "%05d.png";
|
|
||||||
mp4_command << "ffmpeg -f image2 -framerate " << (1000 / get_frame_length()) <<
|
|
||||||
" -i " << images_match.string() << " -s " << size.x << "x" << size.y <<
|
|
||||||
" -c:v libx264 -crf 17 -pix_fmt yuv444p " <<
|
|
||||||
current_video_directory.string() << ".mp4";
|
|
||||||
std::cout << mp4_command.str() << std::endl;
|
|
||||||
std::system(mp4_command.str().c_str());
|
|
||||||
}
|
}
|
||||||
|
std::cout << "Wrote video frames to " << current_video_directory.string() <<
|
||||||
|
std::endl;
|
||||||
|
writing_recording = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Recorder::write_mp4()
|
||||||
|
{
|
||||||
|
glm::ivec2 size = get_display().get_window_size();
|
||||||
|
std::stringstream mp4_command;
|
||||||
|
std::string pixel_format =
|
||||||
|
get_configuration()["recording"]["mp4-pixel-format"].get<std::string>();
|
||||||
|
fs::path images_match = current_video_directory / "%05d.png";
|
||||||
|
mp4_command << "ffmpeg -f image2 -framerate " << (1000 / get_frame_length()) <<
|
||||||
|
" -i " << images_match.string() << " -s " << size.x << "x" << size.y <<
|
||||||
|
" -c:v libx264 -crf 17 -pix_fmt " << pixel_format << " " <<
|
||||||
|
current_video_directory.string() << ".mp4";
|
||||||
|
std::cout << mp4_command.str() << std::endl;
|
||||||
|
std::system(mp4_command.str().c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Recorder::update()
|
void Recorder::update()
|
||||||
{
|
{
|
||||||
|
if (is_recording and get_memory_size() >
|
||||||
|
get_configuration()["recording"]["max-video-memory"])
|
||||||
|
{
|
||||||
|
end_recording();
|
||||||
|
}
|
||||||
animation.set_frame_length(get_frame_length());
|
animation.set_frame_length(get_frame_length());
|
||||||
animation.update();
|
animation.update();
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,24 +31,28 @@ struct Stash
|
||||||
struct Recorder : Node
|
struct Recorder : Node
|
||||||
{
|
{
|
||||||
|
|
||||||
|
Stash current_stash = Stash();
|
||||||
|
Stash most_recent_stash;
|
||||||
std::vector<Stash> in_game_stashes;
|
std::vector<Stash> in_game_stashes;
|
||||||
std::vector<Stash> video_stashes;
|
std::vector<Stash> video_stashes;
|
||||||
fs::path current_video_directory;
|
fs::path current_video_directory;
|
||||||
Animation animation = Animation(&Recorder::add_frame, this);
|
Animation animation = Animation(&Recorder::add_frame, this);
|
||||||
bool is_recording = false, writing_recording = false;
|
bool is_recording = false, writing_recording = false, writing_most_recent = false;
|
||||||
|
|
||||||
Recorder(Node*);
|
Recorder(Node*);
|
||||||
float get_frame_length();
|
float get_frame_length();
|
||||||
void respond(SDL_Event&);
|
void respond(SDL_Event&);
|
||||||
void capture_screen();
|
void capture_screen();
|
||||||
|
void grab_stash();
|
||||||
|
void write_most_recent_frames();
|
||||||
void start_recording();
|
void start_recording();
|
||||||
void add_frame();
|
void add_frame();
|
||||||
int get_memory_size();
|
int get_memory_size();
|
||||||
void write_stash_frames(int);
|
void write_stash_frames(bool, int);
|
||||||
void keep_stash();
|
void keep_stash();
|
||||||
void end_recording();
|
void end_recording();
|
||||||
void finish_writing_video();
|
void finish_writing_video();
|
||||||
void write_video_frames(fs::path);
|
void write_mp4();
|
||||||
void update();
|
void update();
|
||||||
std::string get_class_name() { return "Recorder"; }
|
std::string get_class_name() { return "Recorder"; }
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue