Skip to content

Commit 0538e99

Browse files
committed
Use Sofa/GL VideoRecorderFFMPEG (#262)
* Use the original sofa/gl recorder using the ffmpeg executable * Small optim * Clean * Fix installation of ini file + add more insight in informations * Fix compilation
1 parent 6264de5 commit 0538e99

File tree

9 files changed

+90
-359
lines changed

9 files changed

+90
-359
lines changed

SofaGLFW/CMakeLists.txt

Lines changed: 20 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -40,25 +40,6 @@ elseif( (DEFINED SOFA_ALLOW_FETCH_DEPENDENCIES AND SOFA_ALLOW_FETCH_DEPENDENCIES
4040

4141
endif()
4242

43-
find_package(PkgConfig QUIET)
44-
if(PkgConfig_FOUND)
45-
46-
pkg_check_modules(FFMPEG IMPORTED_TARGET
47-
libavcodec
48-
libavformat
49-
libavutil
50-
libswscale
51-
libswresample
52-
)
53-
endif()
54-
55-
if(TARGET PkgConfig::FFMPEG)
56-
message("Enabling the Video Recorder for GLFW.")
57-
else()
58-
message("Disabling the Video Recorder for GLFW. PkgConfig and libav libraries are required to enable the Video Recorder.")
59-
endif()
60-
61-
6243
set(SOFAGLFW_SOURCE_DIR src/SofaGLFW)
6344

6445
set(HEADER_FILES
@@ -69,7 +50,6 @@ set(HEADER_FILES
6950
${SOFAGLFW_SOURCE_DIR}/BaseGUIEngine.h
7051
${SOFAGLFW_SOURCE_DIR}/NullGUIEngine.h
7152
${SOFAGLFW_SOURCE_DIR}/SofaGLFWMouseManager.h
72-
${SOFAGLFW_SOURCE_DIR}/utils/VideoEncoder.h
7353
)
7454

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

88-
if(TARGET PkgConfig::FFMPEG)
89-
LIST(APPEND HEADER_FILES ${SOFAGLFW_SOURCE_DIR}/utils/VideoEncoderFFMPEG.h)
90-
LIST(APPEND SOURCE_FILES ${SOFAGLFW_SOURCE_DIR}/utils/VideoEncoderFFMPEG.cpp)
91-
endif()
92-
9368
add_library(${PROJECT_NAME} SHARED ${HEADER_FILES} ${SOURCE_FILES})
9469

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

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

93+
# FFMPEG_exec
94+
sofa_find_package(FFMPEG_exec BOTH_SCOPES)
95+
# FFMPEG
96+
if(FFMPEG_EXEC_FOUND)
97+
install(PROGRAMS "${FFMPEG_EXEC_FILE}" DESTINATION ${CMAKE_INSTALL_PREFIX}/bin COMPONENT applications)
98+
endif()
99+
100+
# Create build and install versions of .ini file for resources finding
101+
#### For build tree
102+
set(FFMPEG_EXEC_PATH "${FFMPEG_EXEC_FILE}") # absolute path for build dir, see .ini file
103+
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/etc/${PROJECT_NAME}.ini.in "${CMAKE_BINARY_DIR}/etc/${PROJECT_NAME}.ini")
104+
105+
#### For install tree
106+
get_filename_component(FFMPEG_EXEC_FILENAME "${FFMPEG_EXEC_FILE}" NAME)
107+
set(FFMPEG_EXEC_PATH "../bin/${FFMPEG_EXEC_FILENAME}") # relative path for install dir, see .ini file
108+
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/etc/${PROJECT_NAME}.ini.in "${CMAKE_BINARY_DIR}/etc/installed${PROJECT_NAME}.ini")
109+
install(FILES "${CMAKE_BINARY_DIR}/etc/installed${PROJECT_NAME}.ini" DESTINATION ${CMAKE_INSTALL_PREFIX}/etc RENAME ${PROJECT_NAME}.ini COMPONENT applications)
110+
111+
112+
118113
sofa_create_package_with_targets(
119114
PACKAGE_NAME ${PROJECT_NAME}
120115
PACKAGE_VERSION ${Sofa_VERSION}

SofaGLFW/etc/SofaGLFW.ini.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
FFMPEG_EXEC_PATH=@FFMPEG_EXEC_PATH@

SofaGLFW/src/SofaGLFW/NullGUIEngine.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
******************************************************************************/
2222
#include <SofaGLFW/config.h>
2323
#include <SofaGLFW/NullGUIEngine.h>
24-
#include <GLFW/glfw3.h>
2524
#include <sofa/core/visual/VisualParams.h>
25+
#include <GLFW/glfw3.h>
2626

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

7676
return {viewport[2], viewport[3]};
7777
}

SofaGLFW/src/SofaGLFW/SofaGLFWBaseGUI.cpp

Lines changed: 59 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -41,21 +41,18 @@
4141
#include <sofa/simulation/Node.h>
4242
#include <sofa/simulation/Simulation.h>
4343
#include <sofa/simulation/SimulationLoop.h>
44-
#include <sofa/component/visual/InteractiveCamera.h>
4544
#include <sofa/component/visual/VisualStyle.h>
4645
#include <sofa/component/visual/LineAxis.h>
4746
#include <sofa/gui/common/BaseViewer.h>
4847
#include <sofa/gui/common/BaseGUI.h>
4948
#include <sofa/gui/common/PickHandler.h>
5049

51-
#include <sofa/helper/ScopedAdvancedTimer.h>
52-
53-
#if SOFAGLFW_HAVE_FFMPEG == 1
54-
#include <SofaGLFW/utils/VideoEncoderFFMPEG.h>
55-
#endif
50+
#include <sofa/helper/system/SetDirectory.h>
51+
#include <sofa/helper/Utils.h>
5652

5753
#include <algorithm>
5854
#include <filesystem>
55+
#include <map>
5956

6057
using namespace sofa;
6158
using namespace sofa::gui::common;
@@ -76,9 +73,6 @@ namespace sofaglfw
7673
SofaGLFWBaseGUI::SofaGLFWBaseGUI()
7774
{
7875
m_guiEngine = std::make_shared<NullGUIEngine>();
79-
#if SOFAGLFW_HAVE_FFMPEG == 1
80-
m_videoEncoder = std::make_unique<VideoEncoderFFMPEG>();
81-
#endif
8276
}
8377

8478
SofaGLFWBaseGUI::~SofaGLFWBaseGUI()
@@ -485,6 +479,8 @@ std::size_t SofaGLFWBaseGUI::runLoop(std::size_t targetNbIterations)
485479
bool running = true;
486480
std::size_t currentNbIterations = 0;
487481
std::stringstream tmpStr;
482+
std::vector<uint8_t> pixels;
483+
488484
while (s_numberOfActiveWindows > 0 && running)
489485
{
490486
SIMULATION_LOOP_SCOPE
@@ -518,7 +514,8 @@ std::size_t SofaGLFWBaseGUI::runLoop(std::size_t targetNbIterations)
518514
// Read framebuffer
519515
if(this->groot->getAnimate() && this->m_bVideoRecording)
520516
{
521-
this->encodeFrame();
517+
const auto [width, height] = this->m_guiEngine->getFrameBufferPixels(pixels);
518+
m_videoRecorderFFMPEG.addFrame(pixels.data(), width, height);
522519
}
523520

524521
glfwSwapBuffers(glfwWindow);
@@ -639,9 +636,9 @@ void SofaGLFWBaseGUI::terminate()
639636
if (m_guiEngine)
640637
m_guiEngine->terminate();
641638

642-
if(m_videoEncoder)
639+
if(m_bVideoRecording)
643640
{
644-
m_videoEncoder->finish();
641+
m_videoRecorderFFMPEG.finishVideo();
645642
}
646643

647644
glfwTerminate();
@@ -1185,59 +1182,68 @@ bool SofaGLFWBaseGUI::centerWindow(GLFWwindow* window)
11851182
}
11861183

11871184

1188-
void SofaGLFWBaseGUI::encodeFrame()
1185+
void SofaGLFWBaseGUI::toggleVideoRecording()
11891186
{
1190-
if(!m_videoEncoder)
1187+
if(m_bVideoRecording)
11911188
{
1192-
return;
1189+
m_bVideoRecording = false;
1190+
m_videoRecorderFFMPEG.finishVideo();
1191+
msg_info("SofaGLFWBaseGUI") << "End recording";
11931192
}
1194-
1195-
std::vector<uint8_t> pixels;
1196-
const auto [width, height] = this->m_guiEngine->getFrameBufferPixels(pixels);
1197-
1198-
if(!m_videoEncoder->isInitialized())
1193+
else
11991194
{
1200-
using sofa::helper::system::FileSystem;
1201-
std::string baseSceneFilename{};
1202-
if (!this->getSceneFileName().empty())
1203-
{
1204-
std::filesystem::path path(this->getSceneFileName());
1205-
baseSceneFilename = path.stem().string();
1206-
}
1207-
1208-
const auto videoDirectory = FileSystem::append(sofa::helper::Utils::getSofaDataDirectory(), "recordings");
1209-
FileSystem::ensureFolderExists(videoDirectory);
1210-
1211-
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(); }();
1195+
// Initialize recorder with default parameters
1196+
const int width = std::max(1, m_viewPortWidth);
1197+
const int height = std::max(1, m_viewPortHeight);
1198+
const unsigned int framerate = 60;
1199+
const unsigned int bitrate = 2000000;
1200+
const std::string codecExtension = "mp4";
1201+
const std::string codecName = "yuv420p";
12121202

1213-
const std::string videoExtension = ".mp4";
1214-
const auto videoFilename = baseSceneFilename + "_" + currentTimeString + videoExtension;
1215-
const auto videoPath = FileSystem::append(videoDirectory,videoFilename);
1216-
1217-
// assuming that the video path is unique and does not exist
1218-
// it would overwrite otherwise
1219-
constexpr int nbFramePerSecond = 60;
1220-
if(m_videoEncoder->init(videoPath.c_str(), width, height, nbFramePerSecond))
1203+
if(initRecorder(width, height, framerate, bitrate, codecExtension, codecName))
12211204
{
1222-
msg_info("SofaGLFWBaseGUI") << "Writting in " << videoPath;
1205+
m_bVideoRecording = true;
1206+
msg_info("SofaGLFWBaseGUI") << "Start recording";
12231207
}
12241208
else
12251209
{
1226-
msg_error("SofaGLFWBaseGUI") << "Error while trying to write in " << videoPath;
1227-
return;
1210+
msg_error("SofaGLFWBaseGUI") << "Failed to initialize recorder";
12281211
}
12291212
}
1230-
1231-
1232-
// Flip vertically (OpenGL has origin at bottom-left)
1233-
std::vector<uint8_t> flipped(width * height * 3);
1234-
for (int y = 0; y < height; y++) {
1235-
memcpy(&flipped[y * width * 3],
1236-
&pixels[(height - 1 - y) * width * 3],
1237-
width * 3);
1213+
}
1214+
1215+
bool SofaGLFWBaseGUI::initRecorder(int width, int height, unsigned int framerate, unsigned int bitrate, const std::string& codecExtension, const std::string& codecName)
1216+
{
1217+
// Validate parameters
1218+
if (width <= 0 || height <= 0)
1219+
{
1220+
msg_error("SofaGLFWBaseGUI") << "Invalid video dimensions: " << width << "x" << height;
1221+
return false;
12381222
}
1239-
1240-
m_videoEncoder->encodeFrame(flipped.data(), width, height);
1223+
1224+
bool res = true;
1225+
std::string ffmpeg_exec_path = "";
1226+
const std::string ffmpegIniFilePath = sofa::helper::Utils::getSofaPathTo("etc/SofaGLFW.ini");
1227+
std::map<std::string, std::string> iniFileValues = sofa::helper::Utils::readBasicIniFile(ffmpegIniFilePath);
1228+
if (iniFileValues.find("FFMPEG_EXEC_PATH") != iniFileValues.end())
1229+
{
1230+
// get absolute path of FFMPEG executable
1231+
msg_info("SofaGLFWBaseGUI") << " The file " << ffmpegIniFilePath << " points to " << ffmpeg_exec_path << " for the ffmpeg executable.";
1232+
ffmpeg_exec_path = sofa::helper::system::SetDirectory::GetRelativeFromProcess(iniFileValues["FFMPEG_EXEC_PATH"].c_str());
1233+
}
1234+
else
1235+
{
1236+
msg_warning("SofaGLFWBaseGUI") << " The file " << helper::Utils::getSofaPathPrefix() <<"/etc/SofaGLFW.ini either doesn't exist or doesn't contain the string FFMPEG_EXEC_PATH."
1237+
" 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\".";
1238+
}
1239+
1240+
const std::string videoFilename = m_videoRecorderFFMPEG.findFilename(framerate, bitrate / 1024, codecExtension);
1241+
1242+
res = m_videoRecorderFFMPEG.init(ffmpeg_exec_path, videoFilename, width, height, framerate, bitrate, codecName);
1243+
1244+
return res;
12411245
}
12421246

1247+
1248+
12431249
} // namespace sofaglfw

SofaGLFW/src/SofaGLFW/SofaGLFWBaseGUI.h

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,17 @@
2121
******************************************************************************/
2222
#pragma once
2323

24-
#include <sofa/simulation/Simulation.h>
24+
#include <sofa/simulation/Node.h>
2525
#include <sofa/gl/DrawToolGL.h>
2626
#include <sofa/component/visual/BaseCamera.h>
27-
#include <sofa/simulation/Node.h>
2827

2928
#include <SofaGLFW/BaseGUIEngine.h>
3029
#include <SofaGLFW/NullGUIEngine.h>
3130
#include <sofa/gui/common/BaseViewer.h>
3231
#include <memory>
3332

3433
#include <SofaGLFW/SofaGLFWMouseManager.h>
35-
#include <SofaGLFW/utils/VideoEncoder.h>
34+
#include <sofa/gl/VideoRecorderFFMPEG.h>
3635

3736
struct GLFWwindow;
3837
struct GLFWmonitor;
@@ -115,30 +114,14 @@ class SOFAGLFW_API SofaGLFWBaseGUI : public BaseViewer
115114
}
116115
void moveRayPickInteractor(int eventX, int eventY) override ;
117116

118-
void toggleVideoRecording()
119-
{
120-
if(m_videoEncoder)
121-
{
122-
m_bVideoRecording = !m_bVideoRecording;
123-
if(m_bVideoRecording)
124-
{
125-
msg_info("SofaGLFWBaseGUI") << "Start recording";
126-
}
127-
else
128-
{
129-
msg_info("SofaGLFWBaseGUI") << "End recording";
130-
}
131-
}
132-
}
117+
void toggleVideoRecording();
118+
bool initRecorder(int width, int height, unsigned int framerate, unsigned int bitrate, const std::string& codecExtension, const std::string& codecName);
133119

134120
bool isVideoRecording() const
135121
{
136122
return m_bVideoRecording;
137123
}
138124

139-
140-
void encodeFrame();
141-
142125
static void triggerSceneAxis(sofa::simulation::NodeSPtr groot);
143126

144127
private:
@@ -187,7 +170,7 @@ class SOFAGLFW_API SofaGLFWBaseGUI : public BaseViewer
187170
std::shared_ptr<BaseGUIEngine> m_guiEngine;
188171

189172
bool m_bVideoRecording {false};
190-
std::unique_ptr<VideoEncoder> m_videoEncoder;
173+
sofa::gl::VideoRecorderFFMPEG m_videoRecorderFFMPEG;
191174
};
192175

193176
} // namespace sofaglfw

SofaGLFW/src/SofaGLFW/utils/VideoEncoder.h

Lines changed: 0 additions & 41 deletions
This file was deleted.

0 commit comments

Comments
 (0)