diff --git a/demo/Demo.cpp b/demo/Demo.cpp index 691437d..0e54658 100644 --- a/demo/Demo.cpp +++ b/demo/Demo.cpp @@ -140,13 +140,11 @@ Demo::Demo() Mix_PlayMusic(music, -1); load_gl_context(); delegate.subscribe(&Demo::respond, this); - // Input* l = new Input(this); - // delete l; - // input.print_branch(); - // mushroom.print_branch(); - // Input* i = new Input(this); - // get_delegate().unsubscribe(i); - // delete i; + // audio_file.open("audio.raw", std::ios::binary); + // SDL_AudioSpec spec; + // audio_device_id = SDL_OpenAudioDevice(NULL, SDL_TRUE, &spec, &spec, 0); + // SDL_Log("opened audio device %i", audio_device_id); + // SDL_PauseAudioDevice(audio_device_id, SDL_FALSE); } void Demo::load_sdl_context() @@ -317,6 +315,11 @@ void Demo::respond(SDL_Event& event) 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() diff --git a/demo/Demo.hpp b/demo/Demo.hpp index c8832b7..8f6c646 100644 --- a/demo/Demo.hpp +++ b/demo/Demo.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include "sdl2-gfx/SDL2_gfxPrimitives.h" @@ -54,7 +55,7 @@ struct Demo : Game SDL_Texture *grass_texture; int frame_count = 0, frame_count_timestamp; bool right_active = false, down_active = false, left_active = false, - up_active = false; + up_active = false, is_writing_audio = true; SDL_Event event; GLuint vbo, space_texture_id, mvp_id, framerate_texture_id, flat_program, world_program, fake_texture_id; @@ -62,6 +63,8 @@ struct Demo : Game int abc, def, ghi, jkl, mno, pqr, stu, vwx, yz; Mushroom mushroom = Mushroom(this); Sprite grass = Sprite(this, "resource/Field.png"); + // SDL_AudioDeviceID audio_device_id; + // std::ofstream audio_file; Demo(); void load_sdl_context(); diff --git a/demo/config.json b/demo/config.json index 1d959aa..4c3c9bb 100644 --- a/demo/config.json +++ b/demo/config.json @@ -14,7 +14,8 @@ "keys": { "context": " ", - "print-video-memory": "?" + "print-video-memory": "?", + "play-sound": "s" }, "recording": { diff --git a/demo/resource/Ag.ogg b/demo/resource/Ag.ogg new file mode 100644 index 0000000..cafe518 Binary files /dev/null and b/demo/resource/Ag.ogg differ diff --git a/src/Game.cpp b/src/Game.cpp index e49c2d2..3cdae8b 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -57,6 +57,14 @@ Game::Game() { 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(); } diff --git a/src/Recorder.cpp b/src/Recorder.cpp index f35af8a..02b10c9 100644 --- a/src/Recorder.cpp +++ b/src/Recorder.cpp @@ -6,6 +6,7 @@ Recorder::Recorder(Node* parent) : Node(parent) { get_delegate().subscribe(&Recorder::respond, this); animation.play(); + Mix_SetPostMix(&process_audio, this); } float Recorder::get_frame_length() @@ -32,14 +33,17 @@ void Recorder::respond(SDL_Event& event) } else { - std::cout << "Writing in progress, cannot start recording" << - std::endl; + SDL_Log("Writing in progress, cannot start recording"); } } else if (get_delegate().compare(event, "save-current-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() @@ -56,7 +60,7 @@ void Recorder::capture_screen() fs::path path = sfw::get_next_file_name(directory, zfill, prefix, extension); IMG_SavePNG(surface, path.c_str()); 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() @@ -64,8 +68,7 @@ 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; + SDL_Log("Stashing most recent %i seconds of video...", length / 1000); most_recent_stash = current_stash; current_stash = Stash(); writing_recording = true; @@ -76,20 +79,30 @@ void Recorder::grab_stash() } else { - std::cout << "Recording in progress, cannot grab most recent frames" << - std::endl; + SDL_Log("Recording in progress, cannot grab most recent frames"); } } void Recorder::write_most_recent_frames() { + make_directory(); 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"]) { write_mp4(); } - std::cout << "Wrote video frames to " << current_video_directory.string() << - std::endl; + SDL_Log("Wrote video frames to %s", current_video_directory.c_str()); writing_recording = false; } @@ -97,16 +110,26 @@ void Recorder::start_recording() { if (not writing_recording) { - std::cout << "Starting recording..." << std::endl; + SDL_Log("Starting recording..."); is_recording = true; video_stashes.push_back(Stash()); + make_directory(); + open_audio_file(); } 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() { glm::ivec2 size = get_display().get_window_size(); @@ -143,31 +166,36 @@ void Recorder::add_frame() int Recorder::get_memory_size() { glm::ivec2 window = get_display().get_window_size(); - int bytes = Display::bpp / 8 * window.x * window.y; - int size = 0; + int bytes_per_buffer = Display::bpp / 8 * window.x * window.y; + int count = 0; for (Stash& stash : in_game_stashes) { - size += stash.pixel_buffers.size(); + count += stash.pixel_buffers.size(); } 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) { 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::endl; SDL_Surface* frame; @@ -248,6 +276,7 @@ void Recorder::keep_stash() void Recorder::end_recording() { std::cout << "Ending recording..." << std::endl; + audio_file.close(); is_recording = false; writing_recording = true; std::function f = std::bind(&Recorder::finish_writing_video, this); @@ -288,7 +317,8 @@ void Recorder::write_mp4() 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()) << + 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 << " -c:v libx264 -crf 17 -pix_fmt " << pixel_format << " " << current_video_directory.string() << ".mp4"; @@ -296,6 +326,11 @@ void Recorder::write_mp4() std::system(mp4_command.str().c_str()); } +void Recorder::write_audio(Uint8* stream, int len) +{ + audio_file.write(reinterpret_cast(stream), len); +} + void Recorder::update() { if (is_recording and get_memory_size() > @@ -306,3 +341,25 @@ void Recorder::update() animation.set_frame_length(get_frame_length()); animation.update(); } + +void process_audio(void* user_data, Uint8* stream, int len) +{ + Recorder* recorder = static_cast(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); + } +} diff --git a/src/Recorder.hpp b/src/Recorder.hpp index 6310093..f2e23d8 100644 --- a/src/Recorder.hpp +++ b/src/Recorder.hpp @@ -23,6 +23,8 @@ struct Stash { std::vector pixel_buffers; + std::vector audio_buffers; + std::vector audio_buffer_lengths; int frame_offset; Stash(int frame_offset = 0) : frame_offset(frame_offset) {} @@ -35,9 +37,10 @@ struct Recorder : Node Stash most_recent_stash; std::vector in_game_stashes; std::vector video_stashes; - fs::path current_video_directory; + fs::path current_video_directory, current_audio_path; Animation animation = Animation(&Recorder::add_frame, this); bool is_recording = false, writing_recording = false, writing_most_recent = false; + std::ofstream audio_file; Recorder(Node*); float get_frame_length(); @@ -46,16 +49,21 @@ struct Recorder : Node void grab_stash(); void write_most_recent_frames(); void start_recording(); + void open_audio_file(); void add_frame(); int get_memory_size(); + void make_directory(); void write_stash_frames(bool, int); void keep_stash(); void end_recording(); void finish_writing_video(); void write_mp4(); + void write_audio(Uint8*, int); void update(); std::string get_class_name() { return "Recorder"; } }; +void process_audio(void*, Uint8*, int); + #endif