stash and write audio to recording

This commit is contained in:
Frank DeMarco 2019-06-04 03:20:13 -04:00
parent 437f2d321c
commit 7e51a04b71
7 changed files with 116 additions and 36 deletions

View File

@ -140,13 +140,11 @@ Demo::Demo()
Mix_PlayMusic(music, -1); Mix_PlayMusic(music, -1);
load_gl_context(); load_gl_context();
delegate.subscribe(&Demo::respond, this); delegate.subscribe(&Demo::respond, this);
// Input* l = new Input(this); // audio_file.open("audio.raw", std::ios::binary);
// delete l; // SDL_AudioSpec spec;
// input.print_branch(); // audio_device_id = SDL_OpenAudioDevice(NULL, SDL_TRUE, &spec, &spec, 0);
// mushroom.print_branch(); // SDL_Log("opened audio device %i", audio_device_id);
// Input* i = new Input(this); // SDL_PauseAudioDevice(audio_device_id, SDL_FALSE);
// get_delegate().unsubscribe(i);
// delete i;
} }
void Demo::load_sdl_context() void Demo::load_sdl_context()
@ -317,6 +315,11 @@ void Demo::respond(SDL_Event& event)
load_gl_context(); load_gl_context();
} }
} }
else if (delegate.compare(event, "play-sound"))
{
Mix_Chunk* music = Mix_LoadWAV("resource/Ag.ogg");
Mix_PlayChannel(-1, music, 0);
}
} }
void Demo::update() void Demo::update()

View File

@ -8,6 +8,7 @@
#include <algorithm> #include <algorithm>
#include <string> #include <string>
#include <functional> #include <functional>
#include <fstream>
#include <SDL_image.h> #include <SDL_image.h>
#include "sdl2-gfx/SDL2_gfxPrimitives.h" #include "sdl2-gfx/SDL2_gfxPrimitives.h"
@ -54,7 +55,7 @@ struct Demo : Game
SDL_Texture *grass_texture; SDL_Texture *grass_texture;
int frame_count = 0, frame_count_timestamp; int frame_count = 0, frame_count_timestamp;
bool right_active = false, down_active = false, left_active = false, bool right_active = false, down_active = false, left_active = false,
up_active = false; up_active = false, is_writing_audio = true;
SDL_Event event; SDL_Event event;
GLuint vbo, space_texture_id, mvp_id, framerate_texture_id, flat_program, GLuint vbo, space_texture_id, mvp_id, framerate_texture_id, flat_program,
world_program, fake_texture_id; world_program, fake_texture_id;
@ -62,6 +63,8 @@ struct Demo : Game
int abc, def, ghi, jkl, mno, pqr, stu, vwx, yz; int abc, def, ghi, jkl, mno, pqr, stu, vwx, yz;
Mushroom mushroom = Mushroom(this); Mushroom mushroom = Mushroom(this);
Sprite grass = Sprite(this, "resource/Field.png"); Sprite grass = Sprite(this, "resource/Field.png");
// SDL_AudioDeviceID audio_device_id;
// std::ofstream audio_file;
Demo(); Demo();
void load_sdl_context(); void load_sdl_context();

View File

@ -14,7 +14,8 @@
"keys": "keys":
{ {
"context": " ", "context": " ",
"print-video-memory": "?" "print-video-memory": "?",
"play-sound": "s"
}, },
"recording": "recording":
{ {

BIN
demo/resource/Ag.ogg Normal file

Binary file not shown.

View File

@ -57,6 +57,14 @@ Game::Game()
{ {
print_sdl_error("Could not set up audio"); print_sdl_error("Could not set up audio");
} }
std::cout << "Using audio driver: " << SDL_GetCurrentAudioDriver() <<
std::endl;
const int audio_device_count = SDL_GetNumAudioDevices(SDL_TRUE);
for (int ii = 0; ii < audio_device_count; ii++)
{
std::cout << "Found audio capture device " << ii << ": " <<
SDL_GetAudioDeviceName(ii, SDL_TRUE) << std::endl;
}
last_frame_timestamp = SDL_GetTicks(); last_frame_timestamp = SDL_GetTicks();
} }

View File

@ -6,6 +6,7 @@ Recorder::Recorder(Node* parent) : Node(parent)
{ {
get_delegate().subscribe(&Recorder::respond, this); get_delegate().subscribe(&Recorder::respond, this);
animation.play(); animation.play();
Mix_SetPostMix(&process_audio, this);
} }
float Recorder::get_frame_length() float Recorder::get_frame_length()
@ -32,14 +33,17 @@ void Recorder::respond(SDL_Event& event)
} }
else else
{ {
std::cout << "Writing in progress, cannot start recording" << SDL_Log("Writing in progress, cannot start recording");
std::endl;
} }
} }
else if (get_delegate().compare(event, "save-current-stash")) else if (get_delegate().compare(event, "save-current-stash"))
{ {
grab_stash(); grab_stash();
} }
else if (get_delegate().compare(event, "print-video-memory-size"))
{
SDL_Log("Video memory size is %iMB", get_memory_size());
}
} }
void Recorder::capture_screen() void Recorder::capture_screen()
@ -56,7 +60,7 @@ void Recorder::capture_screen()
fs::path path = sfw::get_next_file_name(directory, zfill, prefix, extension); fs::path path = sfw::get_next_file_name(directory, zfill, prefix, extension);
IMG_SavePNG(surface, path.c_str()); IMG_SavePNG(surface, path.c_str());
SDL_FreeSurface(surface); SDL_FreeSurface(surface);
std::cout << "Saved screenshot to " << path.string() << std::endl; SDL_Log("Saved screenshot to %s", path.c_str());
} }
void Recorder::grab_stash() void Recorder::grab_stash()
@ -64,8 +68,7 @@ void Recorder::grab_stash()
if (not is_recording and not writing_recording) if (not is_recording and not writing_recording)
{ {
int length = get_configuration()["recording"]["max-stash-length"]; int length = get_configuration()["recording"]["max-stash-length"];
std::cout << "stashing most recent " << (length / 1000) << SDL_Log("Stashing most recent %i seconds of video...", length / 1000);
"seconds of video..." << std::endl;
most_recent_stash = current_stash; most_recent_stash = current_stash;
current_stash = Stash(); current_stash = Stash();
writing_recording = true; writing_recording = true;
@ -76,20 +79,30 @@ void Recorder::grab_stash()
} }
else else
{ {
std::cout << "Recording in progress, cannot grab most recent frames" << SDL_Log("Recording in progress, cannot grab most recent frames");
std::endl;
} }
} }
void Recorder::write_most_recent_frames() void Recorder::write_most_recent_frames()
{ {
make_directory();
write_stash_frames(false, 0); write_stash_frames(false, 0);
open_audio_file();
while (not most_recent_stash.audio_buffers.empty())
{
write_audio(most_recent_stash.audio_buffers.front(),
most_recent_stash.audio_buffer_lengths.front());
most_recent_stash.audio_buffers.erase(
most_recent_stash.audio_buffers.begin());
most_recent_stash.audio_buffer_lengths.erase(
most_recent_stash.audio_buffer_lengths.begin());
}
audio_file.close();
if (get_configuration()["recording"]["write-mp4"]) if (get_configuration()["recording"]["write-mp4"])
{ {
write_mp4(); write_mp4();
} }
std::cout << "Wrote video frames to " << current_video_directory.string() << SDL_Log("Wrote video frames to %s", current_video_directory.c_str());
std::endl;
writing_recording = false; writing_recording = false;
} }
@ -97,16 +110,26 @@ void Recorder::start_recording()
{ {
if (not writing_recording) if (not writing_recording)
{ {
std::cout << "Starting recording..." << std::endl; SDL_Log("Starting recording...");
is_recording = true; is_recording = true;
video_stashes.push_back(Stash()); video_stashes.push_back(Stash());
make_directory();
open_audio_file();
} }
else else
{ {
std::cout << "Writing in progress, cannot start recording" << std::endl; SDL_Log("Writing in progress, cannot start recording");
} }
} }
void Recorder::open_audio_file()
{
std::stringstream audio_path;
audio_path << current_video_directory.string() << ".raw";
current_audio_path = audio_path.str();
audio_file.open(audio_path.str(), std::ios::binary);
}
void Recorder::add_frame() void Recorder::add_frame()
{ {
glm::ivec2 size = get_display().get_window_size(); glm::ivec2 size = get_display().get_window_size();
@ -143,31 +166,36 @@ void Recorder::add_frame()
int Recorder::get_memory_size() int Recorder::get_memory_size()
{ {
glm::ivec2 window = get_display().get_window_size(); glm::ivec2 window = get_display().get_window_size();
int bytes = Display::bpp / 8 * window.x * window.y; int bytes_per_buffer = Display::bpp / 8 * window.x * window.y;
int size = 0; int count = 0;
for (Stash& stash : in_game_stashes) for (Stash& stash : in_game_stashes)
{ {
size += stash.pixel_buffers.size(); count += stash.pixel_buffers.size();
} }
for (Stash& stash : video_stashes) for (Stash& stash : video_stashes)
{ {
size += stash.pixel_buffers.size(); count += stash.pixel_buffers.size();
} }
return size * bytes / 1000000; count += current_stash.pixel_buffers.size();
count += most_recent_stash.pixel_buffers.size();
int size_in_bytes = 0;
size_in_bytes = count * bytes_per_buffer;
return size_in_bytes / 1000000;
}
void Recorder::make_directory()
{
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);
current_video_directory = directory;
} }
void Recorder::write_stash_frames(bool is_video, int index) void Recorder::write_stash_frames(bool is_video, int index)
{ {
int frame_offset = is_video ? 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"];
fs::create_directories(root);
fs::path directory = sfw::get_next_file_name(root, 5, "video-");
fs::create_directories(directory);
current_video_directory = directory;
}
std::cout << "Writing stash index " << index << " to " << current_video_directory << "..." << std::cout << "Writing stash index " << index << " to " << current_video_directory << "..." <<
std::endl; std::endl;
SDL_Surface* frame; SDL_Surface* frame;
@ -248,6 +276,7 @@ void Recorder::keep_stash()
void Recorder::end_recording() void Recorder::end_recording()
{ {
std::cout << "Ending recording..." << std::endl; std::cout << "Ending recording..." << std::endl;
audio_file.close();
is_recording = false; is_recording = false;
writing_recording = true; writing_recording = true;
std::function<void()> f = std::bind(&Recorder::finish_writing_video, this); std::function<void()> f = std::bind(&Recorder::finish_writing_video, this);
@ -288,7 +317,8 @@ void Recorder::write_mp4()
std::string pixel_format = std::string pixel_format =
get_configuration()["recording"]["mp4-pixel-format"].get<std::string>(); get_configuration()["recording"]["mp4-pixel-format"].get<std::string>();
fs::path images_match = current_video_directory / "%05d.png"; fs::path images_match = current_video_directory / "%05d.png";
mp4_command << "ffmpeg -f image2 -framerate " << (1000 / get_frame_length()) << mp4_command << "ffmpeg -f s16le -ac 2 -ar 22050 -i " << current_audio_path.string() <<
" -f image2 -framerate " << (1000 / get_frame_length()) <<
" -i " << images_match.string() << " -s " << size.x << "x" << size.y << " -i " << images_match.string() << " -s " << size.x << "x" << size.y <<
" -c:v libx264 -crf 17 -pix_fmt " << pixel_format << " " << " -c:v libx264 -crf 17 -pix_fmt " << pixel_format << " " <<
current_video_directory.string() << ".mp4"; current_video_directory.string() << ".mp4";
@ -296,6 +326,11 @@ void Recorder::write_mp4()
std::system(mp4_command.str().c_str()); std::system(mp4_command.str().c_str());
} }
void Recorder::write_audio(Uint8* stream, int len)
{
audio_file.write(reinterpret_cast<char*>(stream), len);
}
void Recorder::update() void Recorder::update()
{ {
if (is_recording and get_memory_size() > if (is_recording and get_memory_size() >
@ -306,3 +341,25 @@ void Recorder::update()
animation.set_frame_length(get_frame_length()); animation.set_frame_length(get_frame_length());
animation.update(); animation.update();
} }
void process_audio(void* user_data, Uint8* stream, int len)
{
Recorder* recorder = static_cast<Recorder*>(user_data);
int max_length = recorder->get_configuration()["recording"]["max-stash-length"];
float length = recorder->get_frame_length() * recorder->current_stash.pixel_buffers.size();
if (length > max_length)
{
delete[] recorder->current_stash.audio_buffers.front();
recorder->current_stash.audio_buffers.erase(recorder->current_stash.audio_buffers.begin());
recorder->current_stash.audio_buffer_lengths.erase(
recorder->current_stash.audio_buffer_lengths.begin());
}
Uint8* stream_copy = new Uint8[len];
std::memcpy(stream_copy, stream, len);
recorder->current_stash.audio_buffers.push_back(stream_copy);
recorder->current_stash.audio_buffer_lengths.push_back(len);
if (recorder->is_recording)
{
recorder->write_audio(stream_copy, len);
}
}

View File

@ -23,6 +23,8 @@
struct Stash struct Stash
{ {
std::vector<unsigned char*> pixel_buffers; std::vector<unsigned char*> pixel_buffers;
std::vector<Uint8*> audio_buffers;
std::vector<int> audio_buffer_lengths;
int frame_offset; int frame_offset;
Stash(int frame_offset = 0) : frame_offset(frame_offset) {} Stash(int frame_offset = 0) : frame_offset(frame_offset) {}
@ -35,9 +37,10 @@ struct Recorder : Node
Stash most_recent_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, current_audio_path;
Animation animation = Animation(&Recorder::add_frame, this); Animation animation = Animation(&Recorder::add_frame, this);
bool is_recording = false, writing_recording = false, writing_most_recent = false; bool is_recording = false, writing_recording = false, writing_most_recent = false;
std::ofstream audio_file;
Recorder(Node*); Recorder(Node*);
float get_frame_length(); float get_frame_length();
@ -46,16 +49,21 @@ struct Recorder : Node
void grab_stash(); void grab_stash();
void write_most_recent_frames(); void write_most_recent_frames();
void start_recording(); void start_recording();
void open_audio_file();
void add_frame(); void add_frame();
int get_memory_size(); int get_memory_size();
void make_directory();
void write_stash_frames(bool, 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_mp4(); void write_mp4();
void write_audio(Uint8*, int);
void update(); void update();
std::string get_class_name() { return "Recorder"; } std::string get_class_name() { return "Recorder"; }
}; };
void process_audio(void*, Uint8*, int);
#endif #endif