stash pixel buffers and write video frames in chunks
This commit is contained in:
parent
92178b8f42
commit
bef7c1a740
|
@ -13,10 +13,12 @@
|
|||
},
|
||||
"keys":
|
||||
{
|
||||
"context": " "
|
||||
"context": " ",
|
||||
"keep-stash": "k"
|
||||
},
|
||||
"recording":
|
||||
{
|
||||
"write-mp4": true
|
||||
"write-mp4": true,
|
||||
"video-frame-length": 30
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ Configuration::Configuration(Node *parent, fs::path path) : Node(parent)
|
|||
void Configuration::set_defaults()
|
||||
{
|
||||
sys_config["keys"] = {
|
||||
{"record", {"CTRL", "SHIFT", "f10"}},
|
||||
{"record", {"CTRL", "SHIFT", "r"}},
|
||||
{"screenshot", "f9"},
|
||||
{"action", "space"},
|
||||
{"up", "up"},
|
||||
|
@ -35,7 +35,10 @@ void Configuration::set_defaults()
|
|||
{"screenshot-extension", ".png"},
|
||||
{"screenshot-zfill", 5},
|
||||
{"gif-frame-length", 100},
|
||||
{"write-mp4", false}
|
||||
{"write-mp4", false},
|
||||
{"max-stash-length", 5000},
|
||||
{"max-in-game-stashes", 3},
|
||||
{"max-video-stashes", 40}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
162
src/Recorder.cpp
162
src/Recorder.cpp
|
@ -5,6 +5,8 @@
|
|||
Recorder::Recorder(Node* parent) : Node(parent)
|
||||
{
|
||||
get_delegate().subscribe(&Recorder::respond, this);
|
||||
in_game_stashes.push_back(Stash());
|
||||
animation.play();
|
||||
}
|
||||
|
||||
float Recorder::get_frame_length()
|
||||
|
@ -21,15 +23,19 @@ void Recorder::respond(SDL_Event& event)
|
|||
}
|
||||
else if (get_delegate().compare(event, "record"))
|
||||
{
|
||||
if (animation.playing)
|
||||
if (is_recording)
|
||||
{
|
||||
end_recording();
|
||||
}
|
||||
else
|
||||
else if (not writing_recording)
|
||||
{
|
||||
start_recording();
|
||||
}
|
||||
}
|
||||
else if (get_delegate().compare(event, "keep-stash"))
|
||||
{
|
||||
std::cout << get_memory_size() << "mb" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void Recorder::capture_screen()
|
||||
|
@ -42,7 +48,7 @@ void Recorder::capture_screen()
|
|||
get<std::string>();
|
||||
std::string extension = config["recording"]["screenshot-extension"].
|
||||
get<std::string>();
|
||||
int zfill = config["recording"]["screenshot-zfill"].get<int>();
|
||||
int zfill = config["recording"]["screenshot-zfill"];
|
||||
fs::path path = sfw::get_next_file_name(directory, zfill, prefix, extension);
|
||||
IMG_SavePNG(surface, path.c_str());
|
||||
SDL_FreeSurface(surface);
|
||||
|
@ -52,58 +58,92 @@ void Recorder::capture_screen()
|
|||
void Recorder::start_recording()
|
||||
{
|
||||
std::cout << "Starting recording..." << std::endl;
|
||||
animation.set_frame_length(get_frame_length());
|
||||
animation.play();
|
||||
is_recording = true;
|
||||
video_stashes.push_back(Stash());
|
||||
}
|
||||
|
||||
void Recorder::add_frame_to_video()
|
||||
void Recorder::add_frame()
|
||||
{
|
||||
// frames.push_back(get_display().get_screen_surface());
|
||||
glm::ivec2 size = get_display().get_window_size();
|
||||
unsigned char* pixels = new unsigned char[Display::bpp / 8 * size.x * size.y];
|
||||
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);
|
||||
pixel_buffers.push_back(pixels);
|
||||
Stash& stash = in_game_stashes.back();
|
||||
int max_length = get_configuration()["recording"]["max-stash-length"];
|
||||
float length = get_frame_length() * stash.pixel_buffers.size();
|
||||
if (length > max_length)
|
||||
{
|
||||
delete[] stash.pixel_buffers.front();
|
||||
stash.pixel_buffers.erase(stash.pixel_buffers.begin());
|
||||
}
|
||||
stash.pixel_buffers.push_back(pixels);
|
||||
if (is_recording)
|
||||
{
|
||||
unsigned char* vid_pixels = new unsigned char[bytes];
|
||||
memcpy(vid_pixels, pixels, bytes);
|
||||
video_stashes.back().pixel_buffers.push_back(vid_pixels);
|
||||
if (video_stashes.back().pixel_buffers.size() * get_frame_length() > max_length)
|
||||
{
|
||||
std::function<void(int)> f =
|
||||
std::bind(&Recorder::write_stash_frames, this, std::placeholders::_1);
|
||||
std::thread writing(f, video_stashes.size() - 1);
|
||||
writing.detach();
|
||||
video_stashes.push_back(
|
||||
Stash(video_stashes.back().frame_offset + video_stashes.back().pixel_buffers.size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Recorder::end_recording()
|
||||
int Recorder::get_memory_size()
|
||||
{
|
||||
std::cout << "Ending recording..." << std::endl;
|
||||
animation.reset();
|
||||
if (pixel_buffers.size() > 0)
|
||||
glm::ivec2 window = get_display().get_window_size();
|
||||
int bytes = Display::bpp / 8 * window.x * window.y;
|
||||
int size = 0;
|
||||
for (Stash& stash : in_game_stashes)
|
||||
{
|
||||
size += stash.pixel_buffers.size();
|
||||
}
|
||||
for (Stash& stash : video_stashes)
|
||||
{
|
||||
size += stash.pixel_buffers.size();
|
||||
}
|
||||
return size * bytes / 1000000;
|
||||
}
|
||||
|
||||
void Recorder::write_stash_frames(int index)
|
||||
{
|
||||
if (video_stashes[index].frame_offset == 0)
|
||||
{
|
||||
nlohmann::json config = get_configuration();
|
||||
fs::path root = config["path"]["video"];
|
||||
fs::create_directories(root);
|
||||
fs::path directory = sfw::get_next_file_name(root, 5, "video-");
|
||||
fs::create_directories(directory);
|
||||
std::function<void(fs::path)> f = std::bind(&Recorder::write_video_frames, this, std::placeholders::_1);
|
||||
std::thread writing(f, directory);
|
||||
writing.detach();
|
||||
current_video_directory = directory;
|
||||
}
|
||||
}
|
||||
|
||||
void Recorder::write_video_frames(fs::path directory)
|
||||
{
|
||||
std::cout << "Writing recording to " << directory << "..." << std::endl;
|
||||
std::cout << "Writing stash index " << index << " to " << current_video_directory << "..." <<
|
||||
std::endl;
|
||||
SDL_Surface* frame;
|
||||
GifWriter gif_writer;
|
||||
int gif_frame_length = get_configuration()["recording"]["gif-frame-length"];
|
||||
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;
|
||||
std::stringstream gif_name;
|
||||
gif_name << directory.string() << ".gif";
|
||||
fs::path gif_path = directory / gif_name.str();
|
||||
for (int ii = 0; not pixel_buffers.empty(); ii++)
|
||||
for (int ii = video_stashes[index].frame_offset;
|
||||
not video_stashes[index].pixel_buffers.empty(); ii++)
|
||||
{
|
||||
frame = get_display().get_screen_surface_from_pixels(pixel_buffers.front());
|
||||
frame = get_display().get_screen_surface_from_pixels(
|
||||
video_stashes[index].pixel_buffers.front());
|
||||
std::stringstream name;
|
||||
name << sfw::pad(ii, 5) << ".png";
|
||||
fs::path path = directory / name.str();
|
||||
fs::path path = current_video_directory / name.str();
|
||||
IMG_SavePNG(frame, path.string().c_str());
|
||||
if (ii == 0 or elapsed - last_gif_write + gif_write_overflow >= gif_frame_length)
|
||||
if (ii == video_stashes[index].frame_offset or
|
||||
elapsed - last_gif_write + gif_write_overflow >= gif_frame_length)
|
||||
{
|
||||
if (ii == 0)
|
||||
if (ii == video_stashes[index].frame_offset)
|
||||
{
|
||||
GifBegin(&gif_writer, gif_name.str().c_str(), frame->w,
|
||||
GifBegin(&gif_writer, gif_path.string().c_str(), frame->w,
|
||||
frame->h, gif_frame_length / 10);
|
||||
}
|
||||
else
|
||||
|
@ -115,25 +155,75 @@ void Recorder::write_video_frames(fs::path directory)
|
|||
frame->w, frame->h, gif_frame_length / 10);
|
||||
}
|
||||
elapsed += get_frame_length();
|
||||
delete[] pixel_buffers.front();
|
||||
pixel_buffers.erase(pixel_buffers.begin());
|
||||
delete[] video_stashes[index].pixel_buffers.front();
|
||||
video_stashes[index].pixel_buffers.erase(video_stashes[index].pixel_buffers.begin());
|
||||
SDL_FreeSurface(frame);
|
||||
}
|
||||
GifEnd(&gif_writer);
|
||||
}
|
||||
|
||||
void Recorder::keep_stash()
|
||||
{
|
||||
in_game_stashes.push_back(Stash());
|
||||
int max_stashes = get_configuration()["recording"]["max-in-game-stashes"];
|
||||
if (in_game_stashes.size() > max_stashes)
|
||||
{
|
||||
Stash& stash = in_game_stashes.front();
|
||||
while (not stash.pixel_buffers.empty())
|
||||
{
|
||||
delete[] stash.pixel_buffers.back();
|
||||
stash.pixel_buffers.pop_back();
|
||||
}
|
||||
in_game_stashes.erase(in_game_stashes.begin());
|
||||
}
|
||||
}
|
||||
|
||||
void Recorder::end_recording()
|
||||
{
|
||||
std::cout << "Ending recording..." << std::endl;
|
||||
is_recording = false;
|
||||
writing_recording = true;
|
||||
std::function<void()> f = std::bind(&Recorder::finish_writing_video, this);
|
||||
std::thread finishing(f);
|
||||
finishing.detach();
|
||||
}
|
||||
|
||||
void Recorder::finish_writing_video()
|
||||
{
|
||||
write_stash_frames(video_stashes.size() - 1);
|
||||
int count;
|
||||
while (true)
|
||||
{
|
||||
count = 0;
|
||||
for (Stash& stash : video_stashes)
|
||||
{
|
||||
count += stash.pixel_buffers.size();
|
||||
}
|
||||
if (count == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
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 = directory / "%05d.png";
|
||||
fs::path images_match = current_video_directory / "%05d.png";
|
||||
mp4_command << "ffmpeg -f image2 -framerate " << (1000 / get_frame_length()) <<
|
||||
" -i " << images_match.string() << " -s " << frame->w << "x" << frame->h <<
|
||||
" -c:v libx264 -crf 17 -pix_fmt yuv420p " << directory.string() << ".mp4";
|
||||
" -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 " << directory << std::endl;
|
||||
}
|
||||
|
||||
void Recorder::update()
|
||||
{
|
||||
animation.set_frame_length(get_frame_length());
|
||||
animation.update();
|
||||
}
|
||||
|
|
|
@ -20,20 +20,34 @@
|
|||
#include "Display.hpp"
|
||||
#include "extension.hpp"
|
||||
|
||||
struct Stash
|
||||
{
|
||||
std::vector<unsigned char*> pixel_buffers;
|
||||
int frame_offset;
|
||||
|
||||
Stash(int frame_offset = 0) : frame_offset(frame_offset) {}
|
||||
};
|
||||
|
||||
struct Recorder : Node
|
||||
{
|
||||
|
||||
std::vector<SDL_Surface*> frames;
|
||||
std::vector<unsigned char*> pixel_buffers;
|
||||
Animation animation = Animation(&Recorder::add_frame_to_video, this);
|
||||
std::vector<Stash> in_game_stashes;
|
||||
std::vector<Stash> video_stashes;
|
||||
fs::path current_video_directory;
|
||||
Animation animation = Animation(&Recorder::add_frame, this);
|
||||
bool is_recording = false, writing_recording = false;
|
||||
|
||||
Recorder(Node*);
|
||||
float get_frame_length();
|
||||
void respond(SDL_Event&);
|
||||
void capture_screen();
|
||||
void start_recording();
|
||||
void add_frame_to_video();
|
||||
void add_frame();
|
||||
int get_memory_size();
|
||||
void write_stash_frames(int);
|
||||
void keep_stash();
|
||||
void end_recording();
|
||||
void finish_writing_video();
|
||||
void write_video_frames(fs::path);
|
||||
void update();
|
||||
std::string get_class_name() { return "Recorder"; }
|
||||
|
|
Loading…
Reference in New Issue