write most recent frames to video

This commit is contained in:
Frank DeMarco 2019-05-31 02:54:31 -04:00
parent bef7c1a740
commit daa7945c89
4 changed files with 134 additions and 46 deletions

View File

@ -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
} }
} }

View File

@ -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"}
}; };
} }

View File

@ -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();
} }

View File

@ -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"; }