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
6057using namespace sofa ;
6158using namespace sofa ::gui::common;
@@ -76,9 +73,6 @@ namespace sofaglfw
7673SofaGLFWBaseGUI::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
8478SofaGLFWBaseGUI::~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
0 commit comments