diff --git a/CMakeLists.txt b/CMakeLists.txt index 8fef848..dce2901 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -223,6 +223,7 @@ set(VMIX_SRCS GarbageVisitor.cpp SessionCreator.cpp Mixer.cpp + Recorder.cpp Settings.cpp Screenshot.cpp Resource.cpp diff --git a/FrameBuffer.h b/FrameBuffer.h index 29e9677..87c19a7 100644 --- a/FrameBuffer.h +++ b/FrameBuffer.h @@ -37,6 +37,7 @@ public: // width & height inline uint width() const { return attrib_.viewport.x; } inline uint height() const { return attrib_.viewport.y; } + inline bool use_alpha() const { return use_alpha_; } glm::vec3 resolution() const; float aspectRatio() const; diff --git a/Recorder.cpp b/Recorder.cpp new file mode 100644 index 0000000..fae34f4 --- /dev/null +++ b/Recorder.cpp @@ -0,0 +1,59 @@ +#include + +#include + +// standalone image loader +#include +#include + +#include "SystemToolkit.h" +#include "FrameBuffer.h" +#include "Log.h" + +#include "Recorder.h" + + +Recorder::Recorder() : enabled_(true), finished_(false) +{ + +} + + +FrameRecorder::FrameRecorder() : Recorder() +{ + filename_ = SystemToolkit::home_path() + SystemToolkit::date_time_string() + "_vimix.png"; +} + + +// Thread to perform slow operation of saving to file +void save_png(std::string filename, unsigned char *data, uint w, uint h, uint c) +{ + // got data to save ? + if (data) { + // save file + stbi_write_png(filename.c_str(), w, h, c, data, w * c); + // notify + Log::Notify("Capture %s saved.", filename.c_str()); + } +} + +void FrameRecorder::addFrame(const FrameBuffer *frame_buffer, float dt) +{ + if (enabled_) + { + uint w = frame_buffer->width(); + uint h = frame_buffer->height(); + uint c = frame_buffer->use_alpha() ? 4 : 3; + GLenum format = frame_buffer->use_alpha() ? GL_RGBA : GL_RGB; + uint size = w * h * c; + unsigned char * data = (unsigned char*) malloc(size); + + glGetTextureSubImage( frame_buffer->texture(), 0, 0, 0, 0, w, h, 1, format, GL_UNSIGNED_BYTE, size, data); + + // save in separate thread + std::thread(save_png, filename_, data, w, h, c).detach(); + } + + // record one frame only + finished_ = true; +} diff --git a/Recorder.h b/Recorder.h new file mode 100644 index 0000000..09aca2e --- /dev/null +++ b/Recorder.h @@ -0,0 +1,37 @@ +#ifndef RECORDER_H +#define RECORDER_H + +#include +#include +#include + +class FrameBuffer; + +class Recorder +{ +public: + Recorder(); + virtual ~Recorder() {} + + virtual void addFrame(const FrameBuffer *frame_buffer, float dt) = 0; + + inline bool finished() const { return finished_; } + inline bool enabled() const { return enabled_; } + +protected: + std::atomic enabled_; + std::atomic finished_; +}; + +class FrameRecorder : public Recorder +{ + std::string filename_; + +public: + + FrameRecorder(); + void addFrame(const FrameBuffer *frame_buffer, float dt); + +}; + +#endif // RECORDER_H diff --git a/Session.cpp b/Session.cpp index dfcbe74..26eb073 100644 --- a/Session.cpp +++ b/Session.cpp @@ -5,6 +5,7 @@ #include "FrameBuffer.h" #include "Session.h" #include "GarbageVisitor.h" +#include "Recorder.h" #include "Log.h" @@ -71,6 +72,24 @@ void Session::update(float dt) // draw render view in Frame Buffer render_.draw(); + + // send frame to recorders + std::list::iterator iter; + for (iter=recorders_.begin(); iter != recorders_.end(); ) + { + Recorder *rec = *iter; + + if (rec->enabled()) + rec->addFrame(render_.frame(), dt); + + if (rec->finished()) { + iter = recorders_.erase(iter); + delete rec; + } + else { + iter++; + } + } } @@ -209,4 +228,19 @@ int Session::index(SourceList::iterator it) const return index; } +void Session::addRecorder(Recorder *rec) +{ + recorders_.push_back(rec); +} + +void Session::clearRecorders() +{ + std::list::iterator iter; + for (iter=recorders_.begin(); iter != recorders_.end(); ) + { + Recorder *rec = *iter; + iter = recorders_.erase(iter); + delete rec; + } +} diff --git a/Session.h b/Session.h index b1abd4b..5306087 100644 --- a/Session.h +++ b/Session.h @@ -5,6 +5,8 @@ #include "View.h" #include "Source.h" +class Recorder; + class Session { public: @@ -45,6 +47,10 @@ public: // get frame result of render inline FrameBuffer *frame () const { return render_.frame(); } + // add recorders + void addRecorder(Recorder *rec); + void clearRecorders(); + // configure rendering resolution void setResolution(glm::vec3 resolution); @@ -67,6 +73,8 @@ protected: SourceList sources_; std::map config_; bool active_; + std::list recorders_; + }; #endif // SESSION_H diff --git a/UserInterfaceManager.cpp b/UserInterfaceManager.cpp index f7b91da..de28e54 100644 --- a/UserInterfaceManager.cpp +++ b/UserInterfaceManager.cpp @@ -44,6 +44,7 @@ #include "ImGuiVisitor.h" #include "GstToolkit.h" #include "Mixer.h" +#include "Recorder.h" #include "Selection.h" #include "FrameBuffer.h" #include "MediaPlayer.h" @@ -865,6 +866,9 @@ void UserInterface::RenderPreview() if ( ImGui::MenuItem( ICON_FA_WINDOW_RESTORE " Show output window") ) Rendering::manager().outputWindow().show(); + if ( ImGui::MenuItem( ICON_FA_CAMERA_RETRO " Save capture") ) + Mixer::manager().session()->addRecorder(new FrameRecorder); + if ( ImGui::MenuItem( ICON_FA_TIMES " Close") ) Settings::application.widget.preview = false;