Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 20 additions & 25 deletions SofaGLFW/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -40,25 +40,6 @@ elseif( (DEFINED SOFA_ALLOW_FETCH_DEPENDENCIES AND SOFA_ALLOW_FETCH_DEPENDENCIES

endif()

find_package(PkgConfig QUIET)
if(PkgConfig_FOUND)

pkg_check_modules(FFMPEG IMPORTED_TARGET
libavcodec
libavformat
libavutil
libswscale
libswresample
)
endif()

if(TARGET PkgConfig::FFMPEG)
message("Enabling the Video Recorder for GLFW.")
else()
message("Disabling the Video Recorder for GLFW. PkgConfig and libav libraries are required to enable the Video Recorder.")
endif()


set(SOFAGLFW_SOURCE_DIR src/SofaGLFW)

set(HEADER_FILES
Expand All @@ -69,7 +50,6 @@ set(HEADER_FILES
${SOFAGLFW_SOURCE_DIR}/BaseGUIEngine.h
${SOFAGLFW_SOURCE_DIR}/NullGUIEngine.h
${SOFAGLFW_SOURCE_DIR}/SofaGLFWMouseManager.h
${SOFAGLFW_SOURCE_DIR}/utils/VideoEncoder.h
)

set(SOURCE_FILES
Expand All @@ -85,11 +65,6 @@ if(Sofa.GUI.Common_FOUND)
LIST(APPEND SOURCE_FILES ${SOFAGLFW_SOURCE_DIR}/SofaGLFWGUI.cpp)
endif()

if(TARGET PkgConfig::FFMPEG)
LIST(APPEND HEADER_FILES ${SOFAGLFW_SOURCE_DIR}/utils/VideoEncoderFFMPEG.h)
LIST(APPEND SOURCE_FILES ${SOFAGLFW_SOURCE_DIR}/utils/VideoEncoderFFMPEG.cpp)
endif()

add_library(${PROJECT_NAME} SHARED ${HEADER_FILES} ${SOURCE_FILES})

target_link_libraries(${PROJECT_NAME} PUBLIC Sofa.GL Sofa.Simulation.Graph Sofa.Component.Visual)
Expand All @@ -115,6 +90,26 @@ endif()

add_definitions("-DSOFAGLFW_RESOURCES_DIR=\"${CMAKE_CURRENT_SOURCE_DIR}/resources\"")

# FFMPEG_exec
sofa_find_package(FFMPEG_exec BOTH_SCOPES)
# FFMPEG
if(FFMPEG_EXEC_FOUND)
install(PROGRAMS "${FFMPEG_EXEC_FILE}" DESTINATION ${CMAKE_INSTALL_PREFIX}/bin COMPONENT applications)
endif()

# Create build and install versions of .ini file for resources finding
#### For build tree
set(FFMPEG_EXEC_PATH "${FFMPEG_EXEC_FILE}") # absolute path for build dir, see .ini file
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/etc/${PROJECT_NAME}.ini.in "${CMAKE_BINARY_DIR}/etc/${PROJECT_NAME}.ini")

#### For install tree
get_filename_component(FFMPEG_EXEC_FILENAME "${FFMPEG_EXEC_FILE}" NAME)
set(FFMPEG_EXEC_PATH "../bin/${FFMPEG_EXEC_FILENAME}") # relative path for install dir, see .ini file
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/etc/${PROJECT_NAME}.ini.in "${CMAKE_BINARY_DIR}/etc/installed${PROJECT_NAME}.ini")
install(FILES "${CMAKE_BINARY_DIR}/etc/installed${PROJECT_NAME}.ini" DESTINATION ${CMAKE_INSTALL_PREFIX}/etc RENAME ${PROJECT_NAME}.ini COMPONENT applications)



sofa_create_package_with_targets(
PACKAGE_NAME ${PROJECT_NAME}
PACKAGE_VERSION ${Sofa_VERSION}
Expand Down
1 change: 1 addition & 0 deletions SofaGLFW/etc/SofaGLFW.ini.in
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
FFMPEG_EXEC_PATH=@FFMPEG_EXEC_PATH@
6 changes: 3 additions & 3 deletions SofaGLFW/src/SofaGLFW/NullGUIEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
******************************************************************************/
#include <SofaGLFW/config.h>
#include <SofaGLFW/NullGUIEngine.h>
#include <GLFW/glfw3.h>
#include <sofa/core/visual/VisualParams.h>
#include <GLFW/glfw3.h>

namespace sofaglfw
{
Expand Down Expand Up @@ -70,8 +70,8 @@ sofa::type::Vec2i NullGUIEngine::getFrameBufferPixels(std::vector<uint8_t>& pixe
{
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
pixels.resize(viewport[2] * viewport[3] * 3);
glReadPixels(viewport[0], viewport[1], viewport[2], viewport[3], GL_RGB, GL_UNSIGNED_BYTE, pixels.data());
pixels.resize(viewport[2] * viewport[3] * 4);
glReadPixels(viewport[0], viewport[1], viewport[2], viewport[3], GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());

return {viewport[2], viewport[3]};
}
Expand Down
112 changes: 59 additions & 53 deletions SofaGLFW/src/SofaGLFW/SofaGLFWBaseGUI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,21 +41,18 @@
#include <sofa/simulation/Node.h>
#include <sofa/simulation/Simulation.h>
#include <sofa/simulation/SimulationLoop.h>
#include <sofa/component/visual/InteractiveCamera.h>
#include <sofa/component/visual/VisualStyle.h>
#include <sofa/component/visual/LineAxis.h>
#include <sofa/gui/common/BaseViewer.h>
#include <sofa/gui/common/BaseGUI.h>
#include <sofa/gui/common/PickHandler.h>

#include <sofa/helper/ScopedAdvancedTimer.h>

#if SOFAGLFW_HAVE_FFMPEG == 1
#include <SofaGLFW/utils/VideoEncoderFFMPEG.h>
#endif
#include <sofa/helper/system/SetDirectory.h>
#include <sofa/helper/Utils.h>

#include <algorithm>
#include <filesystem>
#include <map>

using namespace sofa;
using namespace sofa::gui::common;
Expand All @@ -76,9 +73,6 @@ namespace sofaglfw
SofaGLFWBaseGUI::SofaGLFWBaseGUI()
{
m_guiEngine = std::make_shared<NullGUIEngine>();
#if SOFAGLFW_HAVE_FFMPEG == 1
m_videoEncoder = std::make_unique<VideoEncoderFFMPEG>();
#endif
}

SofaGLFWBaseGUI::~SofaGLFWBaseGUI()
Expand Down Expand Up @@ -485,6 +479,8 @@ std::size_t SofaGLFWBaseGUI::runLoop(std::size_t targetNbIterations)
bool running = true;
std::size_t currentNbIterations = 0;
std::stringstream tmpStr;
std::vector<uint8_t> pixels;

while (s_numberOfActiveWindows > 0 && running)
{
SIMULATION_LOOP_SCOPE
Expand Down Expand Up @@ -518,7 +514,8 @@ std::size_t SofaGLFWBaseGUI::runLoop(std::size_t targetNbIterations)
// Read framebuffer
if(this->groot->getAnimate() && this->m_bVideoRecording)
{
this->encodeFrame();
const auto [width, height] = this->m_guiEngine->getFrameBufferPixels(pixels);
m_videoRecorderFFMPEG.addFrame(pixels.data(), width, height);
}

glfwSwapBuffers(glfwWindow);
Expand Down Expand Up @@ -639,9 +636,9 @@ void SofaGLFWBaseGUI::terminate()
if (m_guiEngine)
m_guiEngine->terminate();

if(m_videoEncoder)
if(m_bVideoRecording)
{
m_videoEncoder->finish();
m_videoRecorderFFMPEG.finishVideo();
}

glfwTerminate();
Expand Down Expand Up @@ -1185,59 +1182,68 @@ bool SofaGLFWBaseGUI::centerWindow(GLFWwindow* window)
}


void SofaGLFWBaseGUI::encodeFrame()
void SofaGLFWBaseGUI::toggleVideoRecording()
{
if(!m_videoEncoder)
if(m_bVideoRecording)
{
return;
m_bVideoRecording = false;
m_videoRecorderFFMPEG.finishVideo();
msg_info("SofaGLFWBaseGUI") << "End recording";
}

std::vector<uint8_t> pixels;
const auto [width, height] = this->m_guiEngine->getFrameBufferPixels(pixels);

if(!m_videoEncoder->isInitialized())
else
{
using sofa::helper::system::FileSystem;
std::string baseSceneFilename{};
if (!this->getSceneFileName().empty())
{
std::filesystem::path path(this->getSceneFileName());
baseSceneFilename = path.stem().string();
}

const auto videoDirectory = FileSystem::append(sofa::helper::Utils::getSofaDataDirectory(), "recordings");
FileSystem::ensureFolderExists(videoDirectory);

const std::string currentTimeString = [](){ auto t = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); std::ostringstream oss; oss << std::put_time(std::localtime(&t), "%Y%m%d%H%M%S"); return oss.str(); }();
// Initialize recorder with default parameters
const int width = std::max(1, m_viewPortWidth);
const int height = std::max(1, m_viewPortHeight);
const unsigned int framerate = 60;
const unsigned int bitrate = 2000000;
const std::string codecExtension = "mp4";
const std::string codecName = "yuv420p";

const std::string videoExtension = ".mp4";
const auto videoFilename = baseSceneFilename + "_" + currentTimeString + videoExtension;
const auto videoPath = FileSystem::append(videoDirectory,videoFilename);

// assuming that the video path is unique and does not exist
// it would overwrite otherwise
constexpr int nbFramePerSecond = 60;
if(m_videoEncoder->init(videoPath.c_str(), width, height, nbFramePerSecond))
if(initRecorder(width, height, framerate, bitrate, codecExtension, codecName))
{
msg_info("SofaGLFWBaseGUI") << "Writting in " << videoPath;
m_bVideoRecording = true;
msg_info("SofaGLFWBaseGUI") << "Start recording";
}
else
{
msg_error("SofaGLFWBaseGUI") << "Error while trying to write in " << videoPath;
return;
msg_error("SofaGLFWBaseGUI") << "Failed to initialize recorder";
}
}


// Flip vertically (OpenGL has origin at bottom-left)
std::vector<uint8_t> flipped(width * height * 3);
for (int y = 0; y < height; y++) {
memcpy(&flipped[y * width * 3],
&pixels[(height - 1 - y) * width * 3],
width * 3);
}

bool SofaGLFWBaseGUI::initRecorder(int width, int height, unsigned int framerate, unsigned int bitrate, const std::string& codecExtension, const std::string& codecName)
{
// Validate parameters
if (width <= 0 || height <= 0)
{
msg_error("SofaGLFWBaseGUI") << "Invalid video dimensions: " << width << "x" << height;
return false;
}

m_videoEncoder->encodeFrame(flipped.data(), width, height);

bool res = true;
std::string ffmpeg_exec_path = "";
const std::string ffmpegIniFilePath = sofa::helper::Utils::getSofaPathTo("etc/SofaGLFW.ini");
std::map<std::string, std::string> iniFileValues = sofa::helper::Utils::readBasicIniFile(ffmpegIniFilePath);
if (iniFileValues.find("FFMPEG_EXEC_PATH") != iniFileValues.end())
{
// get absolute path of FFMPEG executable
msg_info("SofaGLFWBaseGUI") << " The file " << ffmpegIniFilePath << " points to " << ffmpeg_exec_path << " for the ffmpeg executable.";
ffmpeg_exec_path = sofa::helper::system::SetDirectory::GetRelativeFromProcess(iniFileValues["FFMPEG_EXEC_PATH"].c_str());
}
else
{
msg_warning("SofaGLFWBaseGUI") << " The file " << helper::Utils::getSofaPathPrefix() <<"/etc/SofaGLFW.ini either doesn't exist or doesn't contain the string FFMPEG_EXEC_PATH."
" The initialization of the FFMPEG video recorder will likely fail. To fix this, provide a valid path to the ffmpeg executable inside this file using the syntax \"FFMPEG_EXEC_PATH=/usr/bin/ffmpeg\".";
}

const std::string videoFilename = m_videoRecorderFFMPEG.findFilename(framerate, bitrate / 1024, codecExtension);

res = m_videoRecorderFFMPEG.init(ffmpeg_exec_path, videoFilename, width, height, framerate, bitrate, codecName);

return res;
}



} // namespace sofaglfw
27 changes: 5 additions & 22 deletions SofaGLFW/src/SofaGLFW/SofaGLFWBaseGUI.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,17 @@
******************************************************************************/
#pragma once

#include <sofa/simulation/Simulation.h>
#include <sofa/simulation/Node.h>
#include <sofa/gl/DrawToolGL.h>
#include <sofa/component/visual/BaseCamera.h>
#include <sofa/simulation/Node.h>

#include <SofaGLFW/BaseGUIEngine.h>
#include <SofaGLFW/NullGUIEngine.h>
#include <sofa/gui/common/BaseViewer.h>
#include <memory>

#include <SofaGLFW/SofaGLFWMouseManager.h>
#include <SofaGLFW/utils/VideoEncoder.h>
#include <sofa/gl/VideoRecorderFFMPEG.h>

struct GLFWwindow;
struct GLFWmonitor;
Expand Down Expand Up @@ -115,30 +114,14 @@ class SOFAGLFW_API SofaGLFWBaseGUI : public BaseViewer
}
void moveRayPickInteractor(int eventX, int eventY) override ;

void toggleVideoRecording()
{
if(m_videoEncoder)
{
m_bVideoRecording = !m_bVideoRecording;
if(m_bVideoRecording)
{
msg_info("SofaGLFWBaseGUI") << "Start recording";
}
else
{
msg_info("SofaGLFWBaseGUI") << "End recording";
}
}
}
void toggleVideoRecording();
bool initRecorder(int width, int height, unsigned int framerate, unsigned int bitrate, const std::string& codecExtension, const std::string& codecName);

bool isVideoRecording() const
{
return m_bVideoRecording;
}


void encodeFrame();

static void triggerSceneAxis(sofa::simulation::NodeSPtr groot);

private:
Expand Down Expand Up @@ -187,7 +170,7 @@ class SOFAGLFW_API SofaGLFWBaseGUI : public BaseViewer
std::shared_ptr<BaseGUIEngine> m_guiEngine;

bool m_bVideoRecording {false};
std::unique_ptr<VideoEncoder> m_videoEncoder;
sofa::gl::VideoRecorderFFMPEG m_videoRecorderFFMPEG;
};

} // namespace sofaglfw
41 changes: 0 additions & 41 deletions SofaGLFW/src/SofaGLFW/utils/VideoEncoder.h

This file was deleted.

Loading
Loading