diff --git a/demo/config.json b/demo/config.json index f1ce35b..1d959aa 100644 --- a/demo/config.json +++ b/demo/config.json @@ -14,11 +14,11 @@ "keys": { "context": " ", - "keep-stash": "k" + "print-video-memory": "?" }, "recording": { "write-mp4": true, - "video-frame-length": 30 + "video-frame-length": 16.667 } } diff --git a/src/Configuration.cpp b/src/Configuration.cpp index 4ef4112..1daad5f 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -13,7 +13,8 @@ Configuration::Configuration(Node *parent, fs::path path) : Node(parent) void Configuration::set_defaults() { sys_config["keys"] = { - {"record", {"CTRL", "SHIFT", "r"}}, + {"record", {"CTRL", "SHIFT", "f10"}}, + {"save-current-stash", {"CTRL", "SHIFT", "v"}}, {"screenshot", "f9"}, {"action", "space"}, {"up", "up"}, @@ -38,7 +39,9 @@ void Configuration::set_defaults() {"write-mp4", false}, {"max-stash-length", 5000}, {"max-in-game-stashes", 3}, - {"max-video-stashes", 40} + {"max-video-stashes", 40}, + {"max-video-memory", 1000}, + {"mp4-pixel-format", "yuv444p"} }; } diff --git a/src/Recorder.cpp b/src/Recorder.cpp index c682137..7606c71 100644 --- a/src/Recorder.cpp +++ b/src/Recorder.cpp @@ -5,7 +5,8 @@ Recorder::Recorder(Node* parent) : Node(parent) { get_delegate().subscribe(&Recorder::respond, this); - in_game_stashes.push_back(Stash()); + // in_game_stashes.push_back(Stash()); + animation.play(); } @@ -31,10 +32,15 @@ void Recorder::respond(SDL_Event& event) { 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; } +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 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() { - std::cout << "Starting recording..." << std::endl; - is_recording = true; - video_stashes.push_back(Stash()); + if (not writing_recording) + { + 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() @@ -68,15 +115,14 @@ void Recorder::add_frame() int bytes = Display::bpp / 8 * size.x * size.y; unsigned char* pixels = new unsigned char[bytes]; 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"]; - float length = get_frame_length() * stash.pixel_buffers.size(); + float length = get_frame_length() * current_stash.pixel_buffers.size(); if (length > max_length) { - delete[] stash.pixel_buffers.front(); - stash.pixel_buffers.erase(stash.pixel_buffers.begin()); + delete[] current_stash.pixel_buffers.front(); + 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) { 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); if (video_stashes.back().pixel_buffers.size() * get_frame_length() > max_length) { - std::function f = - std::bind(&Recorder::write_stash_frames, this, std::placeholders::_1); - std::thread writing(f, video_stashes.size() - 1); + std::function f = + std::bind(&Recorder::write_stash_frames, this, + std::placeholders::_1, std::placeholders::_2); + std::thread writing(f, true, video_stashes.size() - 1); writing.detach(); 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; } -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(); 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( current_video_directory, 3, "gif-", ".gif"); float elapsed = 0, last_gif_write = 0, gif_write_overflow = 0; - for (int ii = video_stashes[index].frame_offset; - not video_stashes[index].pixel_buffers.empty(); ii++) + for (int ii = frame_offset; + not (is_video ? video_stashes[index].pixel_buffers.empty() : + most_recent_stash.pixel_buffers.empty()); ii++) { - frame = get_display().get_screen_surface_from_pixels( - video_stashes[index].pixel_buffers.front()); + if (is_video) + { + 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; name << sfw::pad(ii, 5) << ".png"; fs::path path = current_video_directory / name.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) { - if (ii == video_stashes[index].frame_offset) + if (ii == frame_offset) { GifBegin(&gif_writer, gif_path.string().c_str(), frame->w, 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); } elapsed += get_frame_length(); - delete[] video_stashes[index].pixel_buffers.front(); - video_stashes[index].pixel_buffers.erase(video_stashes[index].pixel_buffers.begin()); + if (is_video) + { + 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); } GifEnd(&gif_writer); @@ -164,7 +232,8 @@ void Recorder::write_stash_frames(int index) 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"]; if (in_game_stashes.size() > max_stashes) { @@ -190,7 +259,7 @@ void Recorder::end_recording() void Recorder::finish_writing_video() { - write_stash_frames(video_stashes.size() - 1); + write_stash_frames(true, video_stashes.size() - 1); int count; while (true) { @@ -205,25 +274,37 @@ void Recorder::finish_writing_video() } } 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"]) { - std::stringstream mp4_command; - 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()); + write_mp4(); } + 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(); + 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() { + if (is_recording and get_memory_size() > + get_configuration()["recording"]["max-video-memory"]) + { + end_recording(); + } animation.set_frame_length(get_frame_length()); animation.update(); } diff --git a/src/Recorder.hpp b/src/Recorder.hpp index 6d14791..6310093 100644 --- a/src/Recorder.hpp +++ b/src/Recorder.hpp @@ -31,24 +31,28 @@ struct Stash struct Recorder : Node { + Stash current_stash = Stash(); + Stash most_recent_stash; std::vector in_game_stashes; std::vector video_stashes; fs::path current_video_directory; 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*); float get_frame_length(); void respond(SDL_Event&); void capture_screen(); + void grab_stash(); + void write_most_recent_frames(); void start_recording(); void add_frame(); int get_memory_size(); - void write_stash_frames(int); + void write_stash_frames(bool, int); void keep_stash(); void end_recording(); void finish_writing_video(); - void write_video_frames(fs::path); + void write_mp4(); void update(); std::string get_class_name() { return "Recorder"; }