]> OCCT Git - occt-copy.git/commitdiff
0025382: Visualization, TKOpenGl - improved video recording capability
authorkgv <kgv@opencascade.com>
Mon, 17 Apr 2017 12:27:45 +0000 (15:27 +0300)
committerkgv <kgv@opencascade.com>
Mon, 31 Jul 2017 14:37:07 +0000 (17:37 +0300)
Image_VideoRecorder - added new class for video recording using FFmpeg framework.
Draw Harness command vanimation has been extended with new options for video recording.
New optional dependency has been introduced - CSF_FFmpeg.

22 files changed:
CMakeLists.txt
adm/cmake/ffmpeg.cmake [new file with mode: 0644]
adm/cmake/occt_csf.cmake
adm/cmake/occt_toolkit.cmake
adm/cmake/vardescr.cmake
adm/genconfdeps.tcl
adm/templates/OpenCASCADEConfig.cmake.in
adm/templates/custom.build.bat.in
adm/templates/custom.build.sh.in
adm/templates/custom.install.bat.in
adm/templates/custom.install.sh.in
adm/templates/env.bat.in
adm/templates/env.sh.in
dox/overview/overview.md
genconf
src/Draw/Draw_BasicCommands.cxx
src/Image/FILES
src/Image/Image_VideoRecorder.cxx [new file with mode: 0644]
src/Image/Image_VideoRecorder.hxx [new file with mode: 0644]
src/TKService/EXTERNLIB
src/ViewerTest/ViewerTest_ViewerCommands.cxx
tests/v3d/anim/videorecorder [new file with mode: 0644]

index e1b3bbec3e134e59d530a6c7442a48889238a3be..eac91c2e8f706f8f1222de252c6e9c9628103509 100644 (file)
@@ -561,6 +561,27 @@ else()
   OCCT_CHECK_AND_UNSET ("INSTALL_FREEIMAGE")
 endif()
 
+# FFmpeg
+# search for CSF_FFmpeg variable in EXTERNLIB of each being used toolkit
+OCCT_IS_PRODUCT_REQUIRED (CSF_FFmpeg CAN_USE_FFMPEG)
+
+if (CAN_USE_FFMPEG)
+  set (USE_FFMPEG OFF CACHE BOOL "${USE_FFMPEG_DESCR}")
+
+  if (USE_FFMPEG)
+    add_definitions (-DHAVE_FFMPEG)
+    OCCT_INCLUDE_CMAKE_FILE ("adm/cmake/ffmpeg")
+  else()
+    OCCT_CHECK_AND_UNSET_GROUP ("3RDPARTY_FFMPEG")
+    OCCT_CHECK_AND_UNSET ("INSTALL_FFMPEG")
+  endif()
+else()
+  OCCT_CHECK_AND_UNSET ("USE_FFMPEG")
+
+  OCCT_CHECK_AND_UNSET_GROUP ("3RDPARTY_FFMPEG")
+  OCCT_CHECK_AND_UNSET ("INSTALL_FFMPEG")
+endif()
+
 # OpenGL ES 2.0
 if (WIN32 AND CAN_USE_GLES2)
   if ("${CMAKE_SYSTEM_NAME}" STREQUAL "WindowsStore")
diff --git a/adm/cmake/ffmpeg.cmake b/adm/cmake/ffmpeg.cmake
new file mode 100644 (file)
index 0000000..47deaec
--- /dev/null
@@ -0,0 +1,228 @@
+# FFmpeg
+
+if (NOT DEFINED INSTALL_FFMPEG)
+  set (INSTALL_FFMPEG OFF CACHE BOOL "${INSTALL_FFMPEG_DESCR}")
+endif()
+
+if (NOT DEFINED 3RDPARTY_FFMPEG_DIR)
+  set (3RDPARTY_FFMPEG_DIR "" CACHE PATH "The directory containing FFmpeg")
+endif()
+
+# include occt macros. compiler_bitness, os_wiht_bit, compiler
+OCCT_INCLUDE_CMAKE_FILE ("adm/cmake/occt_macros")
+
+# specify FFMPEG folder in connection with 3RDPARTY_DIR
+if (3RDPARTY_DIR AND EXISTS "${3RDPARTY_DIR}")
+  if (NOT 3RDPARTY_FFMPEG_DIR OR NOT EXISTS "${3RDPARTY_FFMPEG_DIR}")
+    FIND_PRODUCT_DIR ("${3RDPARTY_DIR}" FFMPEG FFMPEG_DIR_NAME)
+    if (FFMPEG_DIR_NAME)
+      set (3RDPARTY_FFMPEG_DIR "${3RDPARTY_DIR}/${FFMPEG_DIR_NAME}" CACHE PATH "The directory containing FFmpeg" FORCE)
+    endif()
+  endif()
+else()
+endif()
+
+# define required FFMPEG variables
+if (NOT DEFINED 3RDPARTY_FFMPEG_INCLUDE_DIR)
+  set (3RDPARTY_FFMPEG_INCLUDE_DIR  "" CACHE PATH "the path of headers directory")
+endif()
+
+if (NOT DEFINED 3RDPARTY_FFMPEG_LIBRARY OR NOT 3RDPARTY_FFMPEG_LIBRARY_DIR OR NOT EXISTS "${3RDPARTY_FFMPEG_LIBRARY_DIR}")
+  set (3RDPARTY_FFMPEG_LIBRARY               "" CACHE FILEPATH "FFmpeg framework" FORCE)
+endif()
+
+if (NOT DEFINED 3RDPARTY_FFMPEG_LIBRARY_DIR)
+  set (3RDPARTY_FFMPEG_LIBRARY_DIR           "" CACHE PATH "The directory containing FFmpeg framework")
+endif()
+
+if (WIN32)
+  if (NOT DEFINED 3RDPARTY_FFMPEG_DLL OR NOT 3RDPARTY_FFMPEG_DLL_DIR OR NOT EXISTS "${3RDPARTY_FFMPEG_DLL_DIR}")
+    set (3RDPARTY_FFMPEG_DLL                 "" CACHE FILEPATH "FFmpeg shared libraries" FORCE)
+  endif()
+endif()
+
+if (WIN32)
+  if (NOT DEFINED 3RDPARTY_FFMPEG_DLL_DIR)
+    set (3RDPARTY_FFMPEG_DLL_DIR               "" CACHE PATH "The directory containing FFmpeg shared libraries")
+  endif()
+endif()
+
+# check 3RDPARTY_${PRODUCT_NAME}_ paths for consistency with specified 3RDPARTY_${PRODUCT_NAME}_DIR
+if (3RDPARTY_FFMPEG_DIR AND EXISTS "${3RDPARTY_FFMPEG_DIR}")
+  CHECK_PATH_FOR_CONSISTENCY (3RDPARTY_FFMPEG_DIR 3RDPARTY_FFMPEG_INCLUDE_DIR PATH "the path to FFmpeg")
+  CHECK_PATH_FOR_CONSISTENCY (3RDPARTY_FFMPEG_DIR 3RDPARTY_FFMPEG_LIBRARY FILEPATH "the path to FFmpeg framework")
+
+  if (3RDPARTY_FFMPEG_LIBRARY AND EXISTS "${3RDPARTY_FFMPEG_LIBRARY}")
+    get_filename_component (3RDPARTY_FFMPEG_LIBRARY_DIR "${3RDPARTY_FFMPEG_LIBRARY}" PATH)
+    set (3RDPARTY_FFMPEG_LIBRARY_DIR "${3RDPARTY_FFMPEG_LIBRARY_DIR}" CACHE PATH "The directory containing FFmpeg libraries" FORCE)
+  else()
+    CHECK_PATH_FOR_CONSISTENCY (3RDPARTY_FFMPEG_DIR 3RDPARTY_FFMPEG_LIBRARY_DIR PATH "The directory containing FFmpeg libraries")
+  endif()
+
+  if (WIN32)
+    CHECK_PATH_FOR_CONSISTENCY (3RDPARTY_FFMPEG_DIR 3RDPARTY_FFMPEG_DLL FILEPATH "the path to FFmpeg shared libraries")
+
+    if (3RDPARTY_FFMPEG_DLL AND EXISTS "${3RDPARTY_FFMPEG_DLL}")
+      get_filename_component (3RDPARTY_FFMPEG_DLL_DIR "${3RDPARTY_FFMPEG_DLL}" PATH)
+      set (3RDPARTY_FFMPEG_DLL_DIR "${3RDPARTY_FFMPEG_DLL_DIR}" CACHE PATH "The directory containing FFmpeg shared libraries" FORCE)
+    else()
+      CHECK_PATH_FOR_CONSISTENCY (3RDPARTY_FFMPEG_DIR 3RDPARTY_FFMPEG_DLL_DIR PATH "The directory containing FFmpeg shared libraries")
+    endif()
+  endif()
+endif()
+
+# header
+if (NOT 3RDPARTY_FFMPEG_INCLUDE_DIR OR NOT EXISTS "${3RDPARTY_FFMPEG_INCLUDE_DIR}")
+  set (HEADER_NAMES avutil.h libavutil/avutil.h)
+
+  # set 3RDPARTY_FFMPEG_INCLUDE_DIR as notfound, otherwise find_library can't assign a new value to 3RDPARTY_FFMPEG_INCLUDE_DIR
+  set (3RDPARTY_FFMPEG_INCLUDE_DIR "3RDPARTY_FFMPEG_INCLUDE_DIR-NOTFOUND" CACHE FILEPATH "the path to header directory" FORCE)
+
+  if (3RDPARTY_FFMPEG_DIR AND EXISTS "${3RDPARTY_FFMPEG_DIR}")
+    find_path (3RDPARTY_FFMPEG_INCLUDE_DIR NAMES ${HEADER_NAMES}
+                                                 PATHS ${3RDPARTY_FFMPEG_DIR}
+                                                 PATH_SUFFIXES include
+                                                 CMAKE_FIND_ROOT_PATH_BOTH
+                                                 NO_DEFAULT_PATH)
+  else()
+    find_path (3RDPARTY_FFMPEG_INCLUDE_DIR NAMES ${HEADER_NAMES}
+                                                 PATH_SUFFIXES include
+                                                 CMAKE_FIND_ROOT_PATH_BOTH)
+  endif()
+endif()
+
+if (3RDPARTY_FFMPEG_INCLUDE_DIR AND EXISTS "${3RDPARTY_FFMPEG_INCLUDE_DIR}")
+  list (APPEND 3RDPARTY_INCLUDE_DIRS "${3RDPARTY_FFMPEG_INCLUDE_DIR}")
+else()
+  list (APPEND 3RDPARTY_NOT_INCLUDED 3RDPARTY_FFMPEG_INCLUDE_DIR)
+
+  set (3RDPARTY_FFMPEG_INCLUDE_DIR "" CACHE FILEPATH "the path to avutil.h" FORCE)
+endif()
+
+# library
+if (NOT 3RDPARTY_FFMPEG_LIBRARY OR NOT EXISTS "${3RDPARTY_FFMPEG_LIBRARY}")
+  set (CMAKE_FIND_LIBRARY_SUFFIXES .lib .so .dylib .a)
+
+  set (FFMPEG_PATH_SUFFIXES lib)
+  if (ANDROID)
+    set (FFMPEG_PATH_SUFFIXES ${FFMPEG_PATH_SUFFIXES} libs/${ANDROID_ABI})
+  elseif(APPLE)
+    set (FFMPEG_PATH_SUFFIXES ${FFMPEG_PATH_SUFFIXES} Frameworks)
+  endif()
+
+  # set 3RDPARTY_FFMPEG_LIBRARY as notfound, otherwise find_library can't assign a new value to 3RDPARTY_FFMPEG_LIBRARY
+  set (3RDPARTY_FFMPEG_LIBRARY "3RDPARTY_FFMPEG_LIBRARY-NOTFOUND" CACHE FILEPATH "The path to FFmpeg library" FORCE)
+
+  if (3RDPARTY_FFMPEG_DIR AND EXISTS "${3RDPARTY_FFMPEG_DIR}")
+    find_library (3RDPARTY_FFMPEG_LIBRARY NAMES avutil
+                  PATHS "${3RDPARTY_FFMPEG_LIBRARY_DIR}" "${3RDPARTY_FFMPEG_DIR}"
+                  PATH_SUFFIXES ${FFMPEG_PATH_SUFFIXES}
+                  CMAKE_FIND_ROOT_PATH_BOTH
+                  NO_DEFAULT_PATH)
+  else()
+    find_library (3RDPARTY_FFMPEG_LIBRARY NAMES avutil
+                  PATH_SUFFIXES ${FFMPEG_PATH_SUFFIXES}
+                  CMAKE_FIND_ROOT_PATH_BOTH)
+  endif()
+
+  if (3RDPARTY_FFMPEG_LIBRARY AND EXISTS "${3RDPARTY_FFMPEG_LIBRARY}")
+    get_filename_component (3RDPARTY_FFMPEG_LIBRARY_DIR "${3RDPARTY_FFMPEG_LIBRARY}" PATH)
+    set (3RDPARTY_FFMPEG_LIBRARY_DIR "${3RDPARTY_FFMPEG_LIBRARY_DIR}" CACHE PATH "The directory containing FFmpeg library" FORCE)
+  else()
+    set (3RDPARTY_FFMPEG_LIBRARY_DIR "" CACHE PATH "The directory containing FFmpeg library" FORCE)
+  endif()
+endif()
+
+if (3RDPARTY_FFMPEG_LIBRARY_DIR AND EXISTS "${3RDPARTY_FFMPEG_LIBRARY_DIR}")
+  list (APPEND 3RDPARTY_LIBRARY_DIRS "${3RDPARTY_FFMPEG_LIBRARY_DIR}")
+else()
+  list (APPEND 3RDPARTY_NOT_INCLUDED 3RDPARTY_FFMPEG_LIBRARY_DIR)
+
+  set (3RDPARTY_FFMPEG_LIBRARY "" CACHE FILEPATH "The path to FFmpeg library" FORCE)
+endif()
+
+# shared library
+if (WIN32)
+  if (NOT 3RDPARTY_FFMPEG_DLL OR NOT EXISTS "${3RDPARTY_FFMPEG_DLL}")
+
+    set (CMAKE_FIND_LIBRARY_SUFFIXES .dll)
+    set (3RDPARTY_FFMPEG_DLL "3RDPARTY_FFMPEG_DLL-NOTFOUND" CACHE FILEPATH "The path to FFmpeg shared library" FORCE)
+
+    # find FFmpeg shared library
+    file (GLOB 3RDPARTY_FFMPEG_DLL "${3RDPARTY_FFMPEG_DIR}/bin/avutil[-][0-9]*")
+
+    if (3RDPARTY_FFMPEG_DLL AND EXISTS "${3RDPARTY_FFMPEG_DLL}")
+      set (3RDPARTY_FFMPEG_DLL "${3RDPARTY_FFMPEG_DLL}" CACHE FILEPATH "FFmpeg shared library" FORCE)
+      get_filename_component (3RDPARTY_FFMPEG_DLL_DIR "${3RDPARTY_FFMPEG_DLL}" PATH)
+      set (3RDPARTY_FFMPEG_DLL_DIR "${3RDPARTY_FFMPEG_DLL_DIR}" CACHE PATH "The directory containing FFmpeg library" FORCE)
+    else()
+      set (3RDPARTY_FFMPEG_DLL_DIR "" CACHE PATH "The directory containing FFmpeg shared library" FORCE)
+    endif()
+  endif()
+
+  if (3RDPARTY_FFMPEG_DLL_DIR OR EXISTS "${3RDPARTY_FFMPEG_DLL_DIR}")
+    list (APPEND 3RDPARTY_DLL_DIRS "${3RDPARTY_FFMPEG_DLL_DIR}")
+  else()
+    list (APPEND 3RDPARTY_NOT_INCLUDED 3RDPARTY_FFMPEG_DLL_DIR)
+  endif()
+endif()
+
+# install instructions
+if (INSTALL_FFMPEG)
+  OCCT_MAKE_OS_WITH_BITNESS()
+  OCCT_MAKE_COMPILER_SHORT_NAME()
+
+  if (WIN32)
+    if (DEFINED INSTALL_BIN_DIR)
+      install (FILES "${3RDPARTY_FFMPEG_DLL}" DESTINATION "${INSTALL_BIN_DIR}")
+    else()
+      install (FILES "${3RDPARTY_FFMPEG_DLL}"
+               CONFIGURATIONS Release
+               DESTINATION "${INSTALL_DIR}/${OS_WITH_BIT}/${COMPILER}/bin")
+      install (FILES "${3RDPARTY_FFMPEG_DLL}"
+               CONFIGURATIONS RelWithDebInfo
+               DESTINATION "${INSTALL_DIR}/${OS_WITH_BIT}/${COMPILER}/bini")
+      install (FILES "${3RDPARTY_FFMPEG_DLL}"
+               CONFIGURATIONS Debug
+               DESTINATION "${INSTALL_DIR}/${OS_WITH_BIT}/${COMPILER}/bind")
+    endif()
+  else()
+    get_filename_component(3RDPARTY_FFMPEG_LIBRARY_ABS ${3RDPARTY_FFMPEG_LIBRARY} REALPATH)
+    get_filename_component(3RDPARTY_FFMPEG_LIBRARY_NAME ${3RDPARTY_FFMPEG_LIBRARY} NAME)
+
+    if (DEFINED INSTALL_LIB_DIR)
+      install (FILES "${3RDPARTY_FFMPEG_LIBRARY_ABS}"
+               DESTINATION "${INSTALL_LIB_DIR}"
+               RENAME ${3RDPARTY_FFMPEG_LIBRARY_NAME}.6)
+    else()
+      install (FILES "${3RDPARTY_FFMPEG_LIBRARY_ABS}"
+               CONFIGURATIONS Release
+               DESTINATION "${INSTALL_DIR}/${OS_WITH_BIT}/${COMPILER}/lib"
+               RENAME ${3RDPARTY_FFMPEG_LIBRARY_NAME}.6)
+      install (FILES "${3RDPARTY_FFMPEG_LIBRARY_ABS}"
+               CONFIGURATIONS RelWithDebInfo
+               DESTINATION "${INSTALL_DIR}/${OS_WITH_BIT}/${COMPILER}/libi"
+               RENAME ${3RDPARTY_FFMPEG_LIBRARY_NAME}.6)
+      install (FILES "${3RDPARTY_FFMPEG_LIBRARY_ABS}"
+               CONFIGURATIONS Debug
+               DESTINATION "${INSTALL_DIR}/${OS_WITH_BIT}/${COMPILER}/libd"
+               RENAME ${3RDPARTY_FFMPEG_LIBRARY_NAME}.6)
+    endif()
+  endif()
+
+  set (USED_3RDPARTY_FFMPEG_DIR "")
+else()
+  # the library directory for using by the executable
+  if (WIN32)
+    set (USED_3RDPARTY_FFMPEG_DIR ${3RDPARTY_FFMPEG_DLL_DIR})
+  else()
+    set (USED_3RDPARTY_FFMPEG_DIR ${3RDPARTY_FFMPEG_LIBRARY_DIR})
+  endif()
+endif()
+
+# unset all redundant variables
+OCCT_CHECK_AND_UNSET (FFMPEG_INCLUDE_DIRS)
+OCCT_CHECK_AND_UNSET (FFMPEG_LIBRARY_DIRS)
+OCCT_CHECK_AND_UNSET (FFMPEG_DIR)
+
+mark_as_advanced (3RDPARTY_FFMPEG_LIBRARY 3RDPARTY_FFMPEG_DLL)
index 4bb1d48b03765fc3bfb3b85e00363591a27a268a..7e978bba814c091ab1380139d5cc645ee778e4d3 100644 (file)
@@ -24,6 +24,13 @@ else()
   set (CSF_FREETYPE)
 endif()
 
+# FFmpeg
+if (USE_FFMPEG)
+  set (CSF_FFmpeg "avcodec avformat swscale avutil")
+else()
+  set (CSF_FFmpeg)
+endif()
+
 # FREEIMAGE
 if (USE_FREEIMAGE)
   set (CSF_FreeImagePlus "freeimage")
index a78c1aac871e2198b0231b8acb20a0562d8a2a4f..e6b1d94e6bd2b4b142fc1f6b95e4eae6e5f5daa9 100644 (file)
@@ -316,6 +316,7 @@ if (BUILD_USE_PCH)
 
   # workaround for old gcc
   if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)
+    add_definitions("-D__STDC_CONSTANT_MACROS")
     add_definitions("-D__STDC_FORMAT_MACROS")
   endif()
 
index 9c4967e26fb680bb6c0f70df15a5ff7a86e5fc80..a21807038f33e416d14d077426499df9bd2183cc 100644 (file)
@@ -82,6 +82,7 @@ endmacro()
 INSTALL_MESSAGE (INSTALL_SAMPLES          "OCCT samples")
 INSTALL_MESSAGE (INSTALL_TEST_CASES       "non-regression OCCT test scripts")
 INSTALL_MESSAGE (INSTALL_DOC_Overview     "OCCT overview documentation (HTML format)")
+INSTALL_MESSAGE (INSTALL_FFMPEG           "FFmpeg binaries")
 INSTALL_MESSAGE (INSTALL_FREEIMAGE        "FreeImage binaries")
 INSTALL_MESSAGE (INSTALL_EGL              "EGL binaries")
 INSTALL_MESSAGE (INSTALL_GLES2            "OpenGL ES 2.0 binaries")
@@ -136,6 +137,10 @@ set (3RDPARTY_DIR_DESCR
 third-party product have been found - corresponding CMake variables will be specified
 (VTK: 3RDPARTY_VTK_DIR, 3RDPARTY_VTK_INCLUDE_DIR, 3RDPARTY_VTK_LIBRARY_DIR)")
 
+set (USE_FFMPEG_DESCR
+"Indicates whether FFmpeg framework is used or not. FFmpeg stands for
+multimedia data handling, open-source software libraries used for video encoding and decoding.")
+
 set (USE_FREEIMAGE_DESCR
 "Indicates whether Freeimage product should be used in OCCT visualization
 module for support of popular graphics image formats (PNG, BMP etc)")
index 7accc56c5d8816b26fb781f8655fe308cd291656..452da9c95d65f54ae5ca51f75fd72e2b15e159df 100644 (file)
@@ -119,6 +119,21 @@ proc wokdep:SearchHeader {theHeader} {
   if { [file exists "$aPath"] } {
     return "$aPath"
   }
+
+  if { "$::tcl_platform(os)" == "Linux" } {
+    if { "$::ARCH" == "64" } {
+      set aPath "/usr/include/x86_64-linux-gnu/${theHeader}"
+      if { [file exists "$aPath"] } {
+        return "$aPath"
+      }
+    } else {
+      set aPath "/usr/include/i386-linux-gnu/${theHeader}"
+      if { [file exists "$aPath"] } {
+        return "$aPath"
+      }
+    }
+  }
+
   return ""
 }
 
index ca45c3f1cf82cd36066fd2320d6612c02a6f459d..da4313571afd61a74aac7a43125045af385756a8 100644 (file)
@@ -61,6 +61,7 @@ set (OpenCASCADE_WITH_FREEIMAGE @USE_FREEIMAGE@)
 set (OpenCASCADE_WITH_GL2PS     @USE_GL2PS@)
 set (OpenCASCADE_WITH_TBB       @USE_TBB@)
 set (OpenCASCADE_WITH_VTK       @USE_VTK@)
+set (OpenCASCADE_WITH_FFMPEG    @USE_FFMPEG@)
 set (OpenCASCADE_WITH_GLES2     @USE_GLES2@)
 @SET_OpenCASCADE_WITH_D3D@
 @SET_OpenCASCADE_WITH_GLX@
index 1d1eac8d8aace660fcaf67423fb6d9989ee69996..327bc9cc4b2d63acd501826ac97cf946b3f06452 100644 (file)
@@ -14,6 +14,7 @@ if /I "%VCVER%" == "@COMPILER@" (
     set "GL2PS_DIR=@3RDPARTY_GL2PS_DLL_DIRS@"
     set "TBB_DIR=@3RDPARTY_TBB_DLL_DIR@"
     set "VTK_DIR=@3RDPARTY_VTK_DLL_DIR@"
+    set "FFMPEG_DIR=@3RDPARTY_FFMPEG_DLL_DIR@"
 
     if not "@3RDPARTY_QT_DIR@" == "" (
       set "QTDIR=@3RDPARTY_QT_DIR@"
index b3887d0e54da5c95c248ecdb7f4a588db0c23c0c..5e7a3f0551c871b09bb0e31b9d3c9850146749e0 100644 (file)
@@ -12,6 +12,7 @@ if [ "$1" == "@BIN_LETTER@" ]; then
     export GL2PS_DIR="@3RDPARTY_GL2PS_LIBRARY_DIRS@"
     export TBB_DIR="@3RDPARTY_TBB_LIBRARY_DIR@"
     export VTK_DIR="@3RDPARTY_VTK_LIBRARY_DIR@"
+    export FFMPEG_DIR="@3RDPARTY_FFMPEG_LIBRARY_DIR@"
 
     if [ "x@3RDPARTY_QT_DIR" != "x" ]; then
       export QTDIR="@3RDPARTY_QT_DIR@"
index 4699fe774d605a3ad38ede53d89f207291952801..32683e2ed726b6fa39fcc366057b18120c87e2f0 100644 (file)
@@ -16,6 +16,7 @@ if /I "%VCVER%" == "@COMPILER@" (
     set "GL2PS_DIR=@USED_3RDPARTY_GL2PS_DIRS@"
     set "TBB_DIR=@USED_3RDPARTY_TBB_DIR@"
     set "VTK_DIR=@USED_3RDPARTY_VTK_DIR@"
+    set "FFMPEG_DIR=@USED_3RDPARTY_FFMPEG_DIR@"
 
     if not "@USED_3RDPARTY_QT_DIR@" == "" (
       set "QTDIR=@USED_3RDPARTY_QT_DIR@"
index d81a1b2f5e5d1571f9eaef916a8946bbf6c3ede6..8bf6310f738311d37ecf49569058ac5dacaa868f 100644 (file)
@@ -12,6 +12,7 @@ if [ "$1" == "@BIN_LETTER@" ]; then
     export GL2PS_DIR="@USED_3RDPARTY_GL2PS_DIRS@"
     export TBB_DIR="@USED_3RDPARTY_TBB_DIR@"
     export VTK_DIR="@USED_3RDPARTY_VTK_DIR@"
+    export FFMPEG_DIR="@USED_3RDPARTY_FFMPEG_DIR@"
 
     if [ "x@USED_3RDPARTY_QT_DIR@" != "x" ]; then
       export QTDIR="@USED_3RDPARTY_QT_DIR@"
index aa416268874d6ddbc85bcbb17a336fe8ae6ec5df..d53eb4a91914a8d436d9be65355bf59c2bf7c6c6 100644 (file)
@@ -115,6 +115,7 @@ if not ["%GLES2_DIR%"] == [""]         set "PATH=%GLES2_DIR%;%PATH%"
 if not ["%GL2PS_DIR%"] == [""]         set "PATH=%GL2PS_DIR%;%PATH%"
 if not ["%TBB_DIR%"] == [""]           set "PATH=%TBB_DIR%;%PATH%"
 if not ["%VTK_DIR%"] == [""]           set "PATH=%VTK_DIR%;%PATH%"
+if not ["%FFMPEG_DIR%"] == [""]        set "PATH=%FFMPEG_DIR%;%PATH%"
 if not ["%QTDIR%"] == [""]             set "PATH=%QTDIR%/bin;%PATH%"
 
 rem ----- Set path to 3rd party and OCCT libraries -----
index d38f0f0687ac02f11948140420bf3465e27da86e..0c1e822b9ae69d0bdae30396a3ef392cc17cf86c 100644 (file)
@@ -66,6 +66,10 @@ if [ "$VTK_DIR" != "" ]; then
   THRDPARTY_PATH="${VTK_DIR}:${THRDPARTY_PATH}"
 fi
 
+if [ "$FFMPEG_DIR" != "" ]; then
+  THRDPARTY_PATH="${FFMPEG_DIR}:${THRDPARTY_PATH}"
+fi
+
 if [ "$QTDIR" != "" ]; then
   THRDPARTY_PATH="${QTDIR}/lib:${THRDPARTY_PATH}"
 fi
index 176527efbc69beb7f26124213d0ed10aef00c135..1897d1804ab3cbd4987d37931206dba863bbf102 100644 (file)
@@ -135,6 +135,9 @@ by fully automating techniques as precompiled header usage and single compilatio
 Cotire is included in OCCT repository and used optionally by OCCT CMake scripts to accelerate builds by use of precompiled headers.
 Cotire is licensed under the MIT license (https://github.com/sakra/cotire/blob/master/license).
 
+**FFmpeg** is an Open Source framework supporting various image, video and audio codecs.
+FFmpeg is optionally used by OCCT for video recording, on LGPL conditions (https://www.ffmpeg.org/legal.html).
+
 **MikTEX** is up-to-date implementation of TeX/LaTeX and related programs for Windows. It is used 
 for generation of User and Developer Guides in PDF format. See https://miktex.org for information
 on this tool.
@@ -210,6 +213,7 @@ for which OCCT is certified to work.
 | TCL (for testing tools)    | Tcl/Tk 8.6.3+ http://www.tcl.tk/software/tcltk/download.html <br> or ActiveTcl 8.6 http://www.activestate.com/activetcl/downloads (for Windows)| 
 | Freetype (for text rendering) | FreeType 2.4.11-2.5.5 http://sourceforge.net/projects/freetype/files/ |
 | FreeImage (optional, for support of common 2D graphic formats) | FreeImage 3.17.0+ http://sourceforge.net/projects/freeimage/files |
+| FFmpeg (optional, for video recording) | FFmpeg 3.1+ https://www.ffmpeg.org |
 | gl2ps (optional, for export contents of OCCT viewer to vector formats) | gl2ps-1.3.8+  http://geuz.org/gl2ps/ |
 | Intel TBB (optional, for multithreaded algorithms) | TBB 4.x or 5.x http://www.threadingbuildingblocks.org/ |
 | VTK (for VTK Integration Services | VTK 6.1+ http://www.vtk.org/VTK/resources/software.html |
diff --git a/genconf b/genconf
index a90a3f57fc0af41ae134e93a7e0dbff01908ccf8..1801833524d52ec21982425641c290a86efbb486 100755 (executable)
--- a/genconf
+++ b/genconf
@@ -6,8 +6,10 @@
 # initialize environment
 aScriptPath=${BASH_SOURCE%/*}; if [ -d "${aScriptPath}" ]; then cd "$aScriptPath"; fi; aScriptPath="$PWD";
 if [ ! -e "${aScriptPath}/env.sh" ]; then 
-cat ${aScriptPath}/adm/templates/env.sh | sed -e '/__CASROOT__/d' > ${aScriptPath}/env.sh
+  cat ${aScriptPath}/adm/templates/env.sh | sed -e '/__CASROOT__/d' > ${aScriptPath}/env.sh
 fi
 
+if [ -e "${aScriptPath}/custom.sh" ]; then source "${aScriptPath}/custom.sh"; fi
+
 # run GUI tool
 tclsh "${aScriptPath}/adm/genconf.tcl"
index 3b06434fbca504ee27bc214bfdae55d033b0ad60..3257b9cf733951159fda240539b5dc1038f8f80b 100644 (file)
@@ -350,6 +350,11 @@ static Standard_Integer dversion(Draw_Interpretor& di, Standard_Integer, const c
 #else
   di << "FreeImage disabled\n";
 #endif
+#ifdef HAVE_FFMPEG
+  di << "FFmpeg enabled (HAVE_FFMPEG)\n";
+#else
+  di << "FFmpeg disabled\n";
+#endif
 #ifdef HAVE_GLES2
   di << "OpenGL: ES2\n";
 #else
index ecd9e06c0c952edb3aa4a71004e583f6904b097f..3ca8be8c923a5903c65be89007c8ad9d8633b675 100755 (executable)
@@ -7,3 +7,5 @@ Image_Format.hxx
 Image_PixMap.cxx
 Image_PixMap.hxx
 Image_PixMapData.hxx
+Image_VideoRecorder.cxx
+Image_VideoRecorder.hxx
diff --git a/src/Image/Image_VideoRecorder.cxx b/src/Image/Image_VideoRecorder.cxx
new file mode 100644 (file)
index 0000000..9af4c31
--- /dev/null
@@ -0,0 +1,509 @@
+// Created on: 2016-04-01
+// Created by: Anastasia BORISOVA
+// Copyright (c) 2016 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+// activate some C99 macros like UINT64_C in "stdint.h" which used by FFmpeg
+#ifndef __STDC_CONSTANT_MACROS
+  #define __STDC_CONSTANT_MACROS
+#endif
+
+#include <Image_VideoRecorder.hxx>
+
+#include <Message.hxx>
+#include <Message_Messenger.hxx>
+
+#ifdef HAVE_FFMPEG
+
+// Suppress deprecation warnings - it is difficult to provide compatibility with old and new API at once
+// since new APIs are introduced too often.
+// Should be disabled from time to time to clean up usage of old APIs.
+#if (defined(__GNUC__) && (__GNUC__ == 4 && __GNUC_MINOR__ >= 2))
+  _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"")
+#else
+  Standard_DISABLE_DEPRECATION_WARNINGS
+#endif
+
+extern "C"
+{
+#ifdef _MSC_VER
+  // suppress some common warnings in FFmpeg headers
+  #pragma warning(disable : 4244)
+#endif
+
+  #include <libavcodec/avcodec.h>
+  #include <libavformat/avformat.h>
+  #include <libswscale/swscale.h>
+  #include <libavutil/imgutils.h>
+
+#ifdef _MSC_VER
+  #pragma warning(default : 4244)
+#endif
+};
+
+#endif
+
+IMPLEMENT_STANDARD_RTTIEXT(Image_VideoRecorder, Standard_Transient)
+
+//=============================================================================
+//function : Constructor
+//purpose  :
+//=============================================================================
+Image_VideoRecorder::Image_VideoRecorder()
+: myAVContext   (NULL),
+  myVideoStream (NULL),
+  myVideoCodec  (NULL),
+  myFrame       (NULL),
+  myScaleCtx    (NULL),
+  myFrameCount  (0)
+{
+  myFrameRate.num = 1;
+  myFrameRate.den = 30;
+
+#ifdef HAVE_FFMPEG
+  // initialize libavcodec, and register all codecs and formats, should be done once
+  av_register_all();
+#endif
+}
+
+//=============================================================================
+//function : ~Image_VideoRecorder
+//purpose  :
+//=============================================================================
+Image_VideoRecorder::~Image_VideoRecorder()
+{
+  Close();
+}
+
+//=============================================================================
+//function : formatAvError
+//purpose  :
+//=============================================================================
+TCollection_AsciiString Image_VideoRecorder::formatAvError (const int theError) const
+{
+#ifdef HAVE_FFMPEG
+  char anErrBuf[AV_ERROR_MAX_STRING_SIZE] = {};
+  av_strerror (theError, anErrBuf, AV_ERROR_MAX_STRING_SIZE);
+  return anErrBuf;
+#else
+  return TCollection_AsciiString(theError);
+#endif
+}
+
+//=============================================================================
+//function : Close
+//purpose  :
+//=============================================================================
+void Image_VideoRecorder::Close()
+{
+#ifdef HAVE_FFMPEG
+  if (myScaleCtx != NULL)
+  {
+    sws_freeContext (myScaleCtx);
+    myScaleCtx = NULL;
+  }
+
+  if (myAVContext == NULL)
+  {
+    myFrameCount = 0;
+    return;
+  }
+
+  // Write the trailer, if any. The trailer must be written before you close the CodecContexts open when you wrote the header;
+  // otherwise av_write_trailer() may try to use memory that was freed on av_codec_close().
+  if (myFrameCount != 0)
+  {
+    av_write_trailer (myAVContext);
+    myFrameCount = 0;
+  }
+
+  // close each codec
+  if (myVideoStream != NULL)
+  {
+    avcodec_close (myVideoStream->codec);
+    myVideoStream = NULL;
+  }
+  if (myFrame != NULL)
+  {
+    av_free (myFrame->data[0]);
+    av_frame_free (&myFrame);
+    myFrame = NULL;
+  }
+
+  if (!(myAVContext->oformat->flags & AVFMT_NOFILE))
+  {
+    // close the output file
+    avio_close (myAVContext->pb);
+  }
+
+  // free the stream
+  avformat_free_context (myAVContext);
+  myAVContext = NULL;
+#endif
+}
+
+//=============================================================================
+//function : Open
+//purpose  :
+//=============================================================================
+Standard_Boolean Image_VideoRecorder::Open (const char* theFileName,
+                                            const Image_VideoParams& theParams)
+{
+#ifdef HAVE_FFMPEG
+  Close();
+  if (theParams.Width  <= 0
+   || theParams.Height <= 0)
+  {
+    return Standard_False;
+  }
+
+  // allocate the output media context
+  avformat_alloc_output_context2 (&myAVContext, NULL, theParams.Format.IsEmpty() ? NULL : theParams.Format.ToCString(), theFileName);
+  if (myAVContext == NULL)
+  {
+    ::Message::DefaultMessenger()->Send ("ViewerTest_VideoRecorder, could not deduce output format from file extension", Message_Fail);
+    return Standard_False;
+  }
+
+  // add the audio stream using the default format codecs and initialize the codecs
+  if (!addVideoStream (theParams, myAVContext->oformat->video_codec))
+  {
+    Close();
+    return Standard_False;
+  }
+
+  // open video codec and allocate the necessary encode buffers
+  if (!openVideoCodec (theParams))
+  {
+    Close();
+    return Standard_False;
+  }
+
+#ifdef OCCT_DEBUG
+  av_dump_format (myAVContext, 0, theFileName, 1);
+#endif
+
+  // open the output file, if needed
+  if ((myAVContext->oformat->flags & AVFMT_NOFILE) == 0)
+  {
+    const int aResAv = avio_open (&myAVContext->pb, theFileName, AVIO_FLAG_WRITE);
+    if (aResAv < 0)
+    {
+      const TCollection_AsciiString aMsg = TCollection_AsciiString ("Error: could not open '") + theFileName + "', " + formatAvError (aResAv);
+      ::Message::DefaultMessenger()->Send (aMsg, Message_Fail);
+      Close();
+      return Standard_False;
+    }
+  }
+
+  // write the stream header, if any
+  const int aResAv = avformat_write_header (myAVContext, NULL);
+  if (aResAv < 0)
+  {
+    const TCollection_AsciiString aMsg = TCollection_AsciiString ("Error: can not open output file '") + theFileName + "', " + formatAvError (aResAv);
+    ::Message::DefaultMessenger()->Send (aMsg, Message_Fail);
+    Close();
+    return Standard_False;
+  }
+#else
+  (void )theFileName;
+  (void )theParams;
+#endif
+  return Standard_True;
+}
+
+//=============================================================================
+//function : addVideoStream
+//purpose  :
+//=============================================================================
+Standard_Boolean Image_VideoRecorder::addVideoStream (const Image_VideoParams& theParams,
+                                                      const Standard_Integer   theDefCodecId)
+{
+  myFrameRate.num = theParams.FpsNum;
+  myFrameRate.den = theParams.FpsDen;
+
+#ifdef HAVE_FFMPEG
+  // find the encoder
+  TCollection_AsciiString aCodecName;
+  if (!theParams.VideoCodec.IsEmpty())
+  {
+    myVideoCodec = avcodec_find_encoder_by_name (theParams.VideoCodec.ToCString());
+    aCodecName   = theParams.VideoCodec;
+  }
+  else
+  {
+    myVideoCodec = avcodec_find_encoder ((AVCodecID )theDefCodecId);
+    aCodecName   = avcodec_get_name     ((AVCodecID )theDefCodecId);
+  }
+  if (myVideoCodec == NULL)
+  {
+    const TCollection_AsciiString aMsg = TCollection_AsciiString ("Error: can not find encoder for ") + aCodecName;
+    ::Message::DefaultMessenger()->Send (aMsg, Message_Fail);
+    return Standard_False;
+  }
+
+  const AVCodecID aCodecId = myVideoCodec->id;
+  myVideoStream = avformat_new_stream (myAVContext, myVideoCodec);
+  if (myVideoStream == NULL)
+  {
+    const TCollection_AsciiString aMsg = TCollection_AsciiString ("Error: can not allocate stream!");
+    ::Message::DefaultMessenger()->Send (aMsg, Message_Fail);
+    return Standard_False;
+  }
+  myVideoStream->id = myAVContext->nb_streams - 1;
+
+  AVCodecContext* aCodecCtx = myVideoStream->codec;
+  aCodecCtx->codec_id = aCodecId;
+  // resolution must be a multiple of two
+  aCodecCtx->width    = theParams.Width;
+  aCodecCtx->height   = theParams.Height;
+  // Timebase is the fundamental unit of time (in seconds) in terms of which frame timestamps are represented.
+  // For fixed-fps content, timebase should be 1/framerate and timestamp increments should be identical to 1.
+  aCodecCtx->time_base.den = myFrameRate.num;
+  aCodecCtx->time_base.num = myFrameRate.den;
+  aCodecCtx->gop_size      = 12; // emit one intra frame every twelve frames at most
+
+  // some formats want stream headers to be separate
+  if (myAVContext->oformat->flags & AVFMT_GLOBALHEADER)
+  {
+    aCodecCtx->flags |= CODEC_FLAG_GLOBAL_HEADER;
+  }
+  return Standard_True;
+#else
+  (void )theParams;
+  (void )theDefCodecId;
+  return Standard_False;
+#endif
+}
+
+//=============================================================================
+//function : openVideoCodec
+//purpose  :
+//=============================================================================
+Standard_Boolean Image_VideoRecorder::openVideoCodec (const Image_VideoParams& theParams)
+{
+#ifdef HAVE_FFMPEG
+  AVDictionary* anOptions = NULL;
+  AVCodecContext* aCodecCtx = myVideoStream->codec;
+
+  // setup default values
+  aCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
+  //av_dict_set (&anOptions, "threads", "auto", 0);
+  if (aCodecCtx->codec == avcodec_find_encoder_by_name ("mpeg2video"))
+  {
+    // just for testing, we also add B frames
+    aCodecCtx->max_b_frames = 2;
+    aCodecCtx->bit_rate = 6000000;
+  }
+  else if (aCodecCtx->codec == avcodec_find_encoder_by_name ("mpeg4"))
+  {
+    //
+  }
+  else if (aCodecCtx->codec == avcodec_find_encoder_by_name ("mjpeg"))
+  {
+    aCodecCtx->pix_fmt = AV_PIX_FMT_YUVJ420P;
+    aCodecCtx->qmin = aCodecCtx->qmax = 5;
+  }
+  else if (aCodecCtx->codec == avcodec_find_encoder_by_name ("huffyuv"))
+  {
+    aCodecCtx->pix_fmt = AV_PIX_FMT_RGB24;
+  }
+  else if (aCodecCtx->codec == avcodec_find_encoder_by_name ("png"))
+  {
+    aCodecCtx->pix_fmt = AV_PIX_FMT_RGB24;
+    aCodecCtx->compression_level = 9; // 0..9
+  }
+  else if (aCodecCtx->codec == avcodec_find_encoder_by_name ("h264")
+        || aCodecCtx->codec == avcodec_find_encoder_by_name ("libx264"))
+  {
+    // use CRF (Constant Rate Factor) as best single-pass compressing method
+    av_dict_set (&anOptions, "crf",     "20",        0); // quality 18-28, 23 is default (normal), 18 is almost lossless
+    av_dict_set (&anOptions, "preset",  "slow",      0); // good compression (see also "veryslow", "ultrafast")
+
+    // live-capturing
+    //av_dict_set (&anOptions, "qp",     "0",         0); // instead of crf
+    //av_dict_set (&anOptions, "preset", "ultrafast", 0);
+
+    // compatibility with devices
+    //av_dict_set (&anOptions, "profile", "baseline",  0);
+    //av_dict_set (&anOptions, "level",   "3.0",       0);
+  }
+  else if (aCodecCtx->codec == avcodec_find_encoder_by_name ("vp8")
+        || aCodecCtx->codec == avcodec_find_encoder_by_name ("vp9"))
+  {
+    av_dict_set (&anOptions, "crf", "20", 0); // quality 4\9663, 10 is normal
+  }
+
+  // override defaults with specified options
+  if (!theParams.PixelFormat.IsEmpty())
+  {
+    const AVPixelFormat aPixFormat = av_get_pix_fmt (theParams.PixelFormat.ToCString());
+    if (aPixFormat == AV_PIX_FMT_NONE)
+    {
+      const TCollection_AsciiString aMsg = TCollection_AsciiString ("Error: unknown pixel format has been specified '") + theParams.PixelFormat + "'";
+      ::Message::DefaultMessenger()->Send (aMsg, Message_Fail);
+      return Standard_False;
+    }
+
+    aCodecCtx->pix_fmt = aPixFormat;
+    for (Resource_DataMapOfAsciiStringAsciiString::Iterator aParamIter (theParams.VideoCodecParams);
+         aParamIter.More(); aParamIter.Next())
+    {
+      av_dict_set (&anOptions, aParamIter.Key().ToCString(), aParamIter.Value().ToCString(), 0);
+    }
+  }
+
+  // open codec
+  int aResAv = avcodec_open2 (aCodecCtx, myVideoCodec, &anOptions);
+  if (anOptions != NULL)
+  {
+    av_dict_free (&anOptions);
+  }
+  if (aResAv < 0)
+  {
+    const TCollection_AsciiString aMsg = TCollection_AsciiString ("Error: can not open video codec, ") + formatAvError (aResAv);
+    ::Message::DefaultMessenger()->Send (aMsg, Message_Fail);
+    return Standard_False;
+  }
+
+  // allocate and init a re-usable frame
+  myFrame = av_frame_alloc();
+  if (myFrame == NULL)
+  {
+    const TCollection_AsciiString aMsg = TCollection_AsciiString ("Error: can not allocate video frame!");
+    ::Message::DefaultMessenger()->Send (aMsg, Message_Fail);
+    return Standard_False;
+  }
+
+  // allocate the encoded raw picture
+  aResAv = av_image_alloc (myFrame->data, myFrame->linesize,
+                           aCodecCtx->width, aCodecCtx->height, aCodecCtx->pix_fmt, 1);
+  if (aResAv < 0)
+  {
+    memset (myFrame->data,     0, sizeof(myFrame->data));
+    memset (myFrame->linesize, 0, sizeof(myFrame->linesize));
+    const TCollection_AsciiString aMsg = TCollection_AsciiString ("Error: can not allocate picture ")
+                                       + aCodecCtx->width+ "x" + aCodecCtx->height + ", " + formatAvError (aResAv);
+    ::Message::DefaultMessenger()->Send (aMsg, Message_Fail);
+    return Standard_False;
+  }
+  // copy data and linesize picture pointers to frame
+  myFrame->format = aCodecCtx->pix_fmt;
+  myFrame->width  = aCodecCtx->width;
+  myFrame->height = aCodecCtx->height;
+
+  const Standard_Size aStride = aCodecCtx->width + 16 - (aCodecCtx->width % 16);
+  if (!myImgSrcRgba.InitZero (Image_PixMap::ImgRGBA, aCodecCtx->width, aCodecCtx->height, aStride))
+  {
+    const TCollection_AsciiString aMsg = TCollection_AsciiString ("Error: can not allocate RGBA32 picture ")
+                                       + aCodecCtx->width+ "x" + aCodecCtx->height;
+    ::Message::DefaultMessenger()->Send (aMsg, Message_Fail);
+    return Standard_False;
+  }
+
+  myScaleCtx = sws_getContext (aCodecCtx->width, aCodecCtx->height, AV_PIX_FMT_RGBA,
+                               aCodecCtx->width, aCodecCtx->height, aCodecCtx->pix_fmt,
+                               SWS_BICUBIC, NULL, NULL, NULL);
+  if (myScaleCtx == NULL)
+  {
+    const TCollection_AsciiString aMsg = TCollection_AsciiString ("Error: can not initialize the conversion context!");
+    ::Message::DefaultMessenger()->Send (aMsg, Message_Fail);
+    return Standard_False;
+  }
+  return Standard_True;
+#else
+  (void )theParams;
+  return Standard_False;
+#endif
+}
+
+//=============================================================================
+//function : writeVideoFrame
+//purpose  :
+//=============================================================================
+Standard_Boolean Image_VideoRecorder::writeVideoFrame (const Standard_Boolean theToFlush)
+{
+#ifdef HAVE_FFMPEG
+  int aResAv = 0;
+  AVCodecContext* aCodecCtx = myVideoStream->codec;
+  if (!theToFlush)
+  {
+    uint8_t* aSrcData[4]     = { (uint8_t*)myImgSrcRgba.ChangeData(),   NULL, NULL, NULL };
+    int      aSrcLinesize[4] = { (int     )myImgSrcRgba.SizeRowBytes(), 0,    0,    0    };
+    sws_scale (myScaleCtx,
+               aSrcData, aSrcLinesize,
+               0, aCodecCtx->height,
+               myFrame->data, myFrame->linesize);
+  }
+
+  AVPacket aPacket;
+  memset (&aPacket, 0, sizeof(aPacket));
+  av_init_packet (&aPacket);
+  if ((myAVContext->oformat->flags & AVFMT_RAWPICTURE) != 0
+   && !theToFlush)
+  {
+    // raw video case - directly store the picture in the packet
+    aPacket.flags        |= AV_PKT_FLAG_KEY;
+    aPacket.stream_index  = myVideoStream->index;
+    aPacket.data          = myFrame->data[0];
+    aPacket.size          = sizeof(AVPicture);
+
+    aResAv = av_interleaved_write_frame (myAVContext, &aPacket);
+  }
+  else
+  {
+    // encode the image
+    myFrame->pts = myFrameCount;
+    int isGotPacket = 0;
+    aResAv = avcodec_encode_video2 (aCodecCtx, &aPacket, theToFlush ? NULL : myFrame, &isGotPacket);
+    if (aResAv < 0)
+    {
+      const TCollection_AsciiString aMsg = TCollection_AsciiString ("Error: can not encode video frame, ") + formatAvError (aResAv);
+      ::Message::DefaultMessenger()->Send (aMsg, Message_Fail);
+      return Standard_False;
+    }
+
+    // if size is zero, it means the image was buffered
+    if (isGotPacket)
+    {
+      const AVRational& aTimeBase = aCodecCtx->time_base;
+
+      // rescale output packet timestamp values from codec to stream timebase
+      aPacket.pts          = av_rescale_q_rnd (aPacket.pts,      aTimeBase, myVideoStream->time_base, AVRounding(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
+      aPacket.dts          = av_rescale_q_rnd (aPacket.dts,      aTimeBase, myVideoStream->time_base, AVRounding(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
+      aPacket.duration     = av_rescale_q     (aPacket.duration, aTimeBase, myVideoStream->time_base);
+      aPacket.stream_index = myVideoStream->index;
+
+      // write the compressed frame to the media file
+      aResAv = av_interleaved_write_frame (myAVContext, &aPacket);
+    }
+    else
+    {
+      aResAv = 0;
+    }
+  }
+
+  if (aResAv < 0)
+  {
+    const TCollection_AsciiString aMsg = TCollection_AsciiString ("Error: can not write video frame, ") + formatAvError (aResAv);
+    ::Message::DefaultMessenger()->Send (aMsg, Message_Fail);
+    return Standard_False;
+  }
+
+  ++myFrameCount;
+  return Standard_True;
+#else
+  (void)theToFlush;
+  return Standard_False;
+#endif
+}
diff --git a/src/Image/Image_VideoRecorder.hxx b/src/Image/Image_VideoRecorder.hxx
new file mode 100644 (file)
index 0000000..85ebd7f
--- /dev/null
@@ -0,0 +1,141 @@
+// Created on: 2016-04-01
+// Created by: Anastasia BORISOVA
+// Copyright (c) 2016 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#ifndef Image_VideoRecorder_HeaderFile_
+#define Image_VideoRecorder_HeaderFile_
+
+#include <Image_PixMap.hxx>
+#include <Resource_DataMapOfAsciiStringAsciiString.hxx>
+#include <Standard_Transient.hxx>
+#include <TCollection_AsciiString.hxx>
+
+// forward declarations
+struct AVFormatContext;
+struct AVStream;
+struct AVCodec;
+struct AVFrame;
+struct SwsContext;
+
+//! Auxiliary structure defining video parameters.
+//! Please refer to FFmpeg documentation for defining text values.
+struct Image_VideoParams
+{
+  TCollection_AsciiString Format;           //!< [optional]  video format (container), if empty - will be determined from the file name
+  TCollection_AsciiString VideoCodec;       //!< [optional]  codec identifier, if empty - default codec from file format will be used
+  TCollection_AsciiString PixelFormat;      //!< [optional]  pixel format, if empty - default codec pixel format will be used
+  Standard_Integer        Width;            //!< [mandatory] video frame width
+  Standard_Integer        Height;           //!< [mandatory] video frame height
+  Standard_Integer        FpsNum;           //!< [mandatory] framerate numerator
+  Standard_Integer        FpsDen;           //!< [mandatory] framerate denumerator
+  Resource_DataMapOfAsciiStringAsciiString
+                          VideoCodecParams; //!< map of advanced video codec parameters
+
+  //! Empty constructor.
+  Image_VideoParams() : Width (0), Height (0), FpsNum (0), FpsDen (1) {}
+
+  //! Setup playback FPS.
+  void SetFramerate (const Standard_Integer theNumerator,
+                     const Standard_Integer theDenominator)
+  {
+    FpsNum = theNumerator;
+    FpsDen = theDenominator;
+  }
+
+  //! Setup playback FPS.
+  //! For fixed-fps content, timebase should be 1/framerate and timestamp increments should be identical to 1.
+  void SetFramerate (const Standard_Integer theValue)
+  {
+    FpsNum = theValue;
+    FpsDen = 1;
+  }
+};
+
+//! Video recording tool based on FFmpeg framework.
+class Image_VideoRecorder : public Standard_Transient
+{
+  DEFINE_STANDARD_RTTIEXT(Image_VideoRecorder, Standard_Transient)
+public:
+
+  //! Empty constructor.
+  Standard_EXPORT Image_VideoRecorder();
+
+  //! Destructor.
+  Standard_EXPORT virtual ~Image_VideoRecorder();
+
+  //! Close the stream - stop recorder.
+  Standard_EXPORT void Close();
+
+  //! Open output stream - initialize recorder.
+  //! @param theFileName [in] video filename
+  //! @param theParams   [in] video parameters
+  Standard_EXPORT Standard_Boolean Open (const char* theFileName,
+                                         const Image_VideoParams& theParams);
+
+  //! Access RGBA frame, should NOT be re-initialized outside.
+  //! Note that image is expected to have upper-left origin.
+  Image_PixMap& ChangeFrame() { return myImgSrcRgba; }
+
+  //! Return current frame index.
+  int64_t FrameCount() const { return myFrameCount; }
+
+  //! Push new frame, should be called after Open().
+  Standard_Boolean PushFrame()
+  {
+    return writeVideoFrame (Standard_False);
+  }
+
+protected:
+
+  //! Wrapper for av_strerror().
+  Standard_EXPORT TCollection_AsciiString formatAvError (const int theError) const;
+
+  //! Append video stream.
+  //! theParams     [in] video parameters
+  //! theDefCodecId [in] identifier of codec managed by FFmpeg library (AVCodecID enum)
+  Standard_EXPORT Standard_Boolean addVideoStream (const Image_VideoParams& theParams,
+                                                   const Standard_Integer   theDefCodecId);
+
+  //! Open video codec.
+  Standard_EXPORT Standard_Boolean openVideoCodec (const Image_VideoParams& theParams);
+
+  //! Write new video frame.
+  Standard_EXPORT Standard_Boolean writeVideoFrame (const Standard_Boolean theToFlush);
+
+protected:
+
+  //! AVRational alias.
+  struct VideoRational
+  {
+    int num; //!< numerator
+    int den; //!< denominator
+  };
+
+protected:
+
+  AVFormatContext* myAVContext;   //!< video context
+  AVStream*        myVideoStream; //!< video stream
+  AVCodec*         myVideoCodec;  //!< video codec
+  AVFrame*         myFrame;       //!< frame to record
+  SwsContext*      myScaleCtx;    //!< scale context for conversion from RGBA to YUV
+
+  Image_PixMap     myImgSrcRgba;  //!< input RGBA image
+  VideoRational    myFrameRate;   //!< video framerate
+  int64_t          myFrameCount;  //!< current frame index
+
+};
+
+DEFINE_STANDARD_HANDLE(Image_VideoRecorder, Standard_Transient)
+
+#endif // Image_VideoRecorder_HeaderFile_
index 90a01f53d00dcf3111d813f6c4cf48df778b41c9..a2bba11a8380d7c94db3caae253f20129b4631c8 100755 (executable)
@@ -16,3 +16,4 @@ CSF_Appkit
 CSF_IOKit
 CSF_FreeImagePlus
 CSF_FREETYPE
+CSF_FFmpeg
index 20b1ed0c2170ff1e7bf3842d35db2fc30bcd7295..60ba84788f4efda2300047b68190511efeaf393f 100644 (file)
@@ -27,6 +27,7 @@
 #include <AIS_ListOfInteractive.hxx>
 #include <AIS_ListIteratorOfListOfInteractive.hxx>
 #include <DBRep.hxx>
+#include <Draw_ProgressIndicator.hxx>
 #include <Graphic3d_ArrayOfPolylines.hxx>
 #include <Graphic3d_AspectMarker3d.hxx>
 #include <Graphic3d_ExportFormat.hxx>
@@ -46,6 +47,7 @@
 #include <V3d_DirectionalLight.hxx>
 #include <V3d_PositionalLight.hxx>
 #include <V3d_SpotLight.hxx>
+#include <Message_ProgressSentry.hxx>
 #include <NCollection_DoubleMap.hxx>
 #include <NCollection_List.hxx>
 #include <NCollection_Vector.hxx>
@@ -54,6 +56,7 @@
 #include <Draw.hxx>
 #include <Draw_Appli.hxx>
 #include <Image_AlienPixMap.hxx>
+#include <Image_VideoRecorder.hxx>
 #include <OpenGl_GraphicDriver.hxx>
 #include <OSD_Timer.hxx>
 #include <TColStd_HSequenceOfAsciiString.hxx>
@@ -6262,6 +6265,48 @@ namespace
     return Standard_True;
   }
 
+  //! Auxiliary class for flipping image upside-down.
+  class ImageFlipper
+  {
+  public:
+
+    //! Empty constructor.
+    ImageFlipper() : myTmp (NCollection_BaseAllocator::CommonBaseAllocator()) {}
+
+    //! Perform flipping.
+    Standard_Boolean FlipY (Image_PixMap& theImage)
+    {
+      if (theImage.IsEmpty()
+       || theImage.SizeX() == 0
+       || theImage.SizeY() == 0)
+      {
+        return Standard_False;
+      }
+
+      const Standard_Size aRowSize = theImage.SizeRowBytes();
+      if (myTmp.Size() < aRowSize
+      && !myTmp.Allocate (aRowSize))
+      {
+        return Standard_False;
+      }
+
+      // for odd height middle row should be left as is
+      Standard_Size aNbRowsHalf = theImage.SizeY() / 2;
+      for (Standard_Size aRowT = 0, aRowB = theImage.SizeY() - 1; aRowT < aNbRowsHalf; ++aRowT, --aRowB)
+      {
+        Standard_Byte* aTop = theImage.ChangeRow (aRowT);
+        Standard_Byte* aBot = theImage.ChangeRow (aRowB);
+        memcpy (myTmp.ChangeData(), aTop,         aRowSize);
+        memcpy (aTop,               aBot,         aRowSize);
+        memcpy (aBot,               myTmp.Data(), aRowSize);
+      }
+      return Standard_True;
+    }
+
+  private:
+    NCollection_Buffer myTmp;
+  };
+
 }
 
 //=================================================================================================
@@ -6590,10 +6635,13 @@ static Standard_Integer VAnimation (Draw_Interpretor& theDI,
   Standard_Real aPlaySpeed     = 1.0;
   Standard_Real aPlayStartTime = anAnimation->StartPts();
   Standard_Real aPlayDuration  = anAnimation->Duration();
-  Standard_Integer aFpsNum     = 0;
-  Standard_Integer aFpsDen     = 1;
   Standard_Boolean isFreeCamera = Standard_False;
   Standard_Boolean isLockLoop   = Standard_False;
+
+  // video recording parameters
+  TCollection_AsciiString aRecFile;
+  Image_VideoParams aRecParams;
+
   Handle(V3d_View) aView = ViewerTest::CurrentView();
   for (; anArgIter < theArgNb; ++anArgIter)
   {
@@ -6679,6 +6727,37 @@ static Standard_Integer VAnimation (Draw_Interpretor& theDI,
     {
       isFreeCamera = Standard_True;
     }
+    // video recodring options
+    else if (anArg == "-rec"
+          || anArg == "-record")
+    {
+      if (++anArgIter >= theArgNb)
+      {
+        std::cout << "Syntax error at " << anArg << ".\n";
+        return 1;
+      }
+
+      aRecFile = theArgVec[anArgIter];
+      if (aRecParams.FpsNum <= 0)
+      {
+        aRecParams.FpsNum = 24;
+      }
+
+      if (anArgIter + 2 < theArgNb
+      && *theArgVec[anArgIter + 1] != '-'
+      && *theArgVec[anArgIter + 2] != '-')
+      {
+        TCollection_AsciiString aWidthArg  (theArgVec[anArgIter + 1]);
+        TCollection_AsciiString aHeightArg (theArgVec[anArgIter + 2]);
+        if (aWidthArg .IsIntegerValue()
+         && aHeightArg.IsIntegerValue())
+        {
+          aRecParams.Width  = aWidthArg .IntegerValue();
+          aRecParams.Height = aHeightArg.IntegerValue();
+          anArgIter += 2;
+        }
+      }
+    }
     else if (anArg == "-fps")
     {
       if (++anArgIter >= theArgNb)
@@ -6691,22 +6770,66 @@ static Standard_Integer VAnimation (Draw_Interpretor& theDI,
       Standard_Integer aSplitIndex = aFpsArg.FirstLocationInSet ("/", 1, aFpsArg.Length());
       if (aSplitIndex == 0)
       {
-        aFpsNum = aFpsArg.IntegerValue();
+        aRecParams.FpsNum = aFpsArg.IntegerValue();
       }
       else
       {
         const TCollection_AsciiString aDenStr = aFpsArg.Split (aSplitIndex);
         aFpsArg.Split (aFpsArg.Length() - 1);
         const TCollection_AsciiString aNumStr = aFpsArg;
-        aFpsNum = aNumStr.IntegerValue();
-        aFpsDen = aDenStr.IntegerValue();
-        if (aFpsDen < 1)
+        aRecParams.FpsNum = aNumStr.IntegerValue();
+        aRecParams.FpsDen = aDenStr.IntegerValue();
+        if (aRecParams.FpsDen < 1)
         {
           std::cout << "Syntax error at " << anArg << ".\n";
           return 1;
         }
       }
     }
+    else if (anArg == "-format")
+    {
+      if (++anArgIter >= theArgNb)
+      {
+        std::cout << "Syntax error at " << anArg << ".\n";
+        return 1;
+      }
+      aRecParams.Format = theArgVec[anArgIter];
+    }
+    else if (anArg == "-pix_fmt"
+          || anArg == "-pixfmt"
+          || anArg == "-pixelformat")
+    {
+      if (++anArgIter >= theArgNb)
+      {
+        std::cout << "Syntax error at " << anArg << ".\n";
+        return 1;
+      }
+      aRecParams.PixelFormat = theArgVec[anArgIter];
+    }
+    else if (anArg == "-codec"
+          || anArg == "-vcodec"
+          || anArg == "-videocodec")
+    {
+      if (++anArgIter >= theArgNb)
+      {
+        std::cout << "Syntax error at " << anArg << ".\n";
+        return 1;
+      }
+      aRecParams.VideoCodec = theArgVec[anArgIter];
+    }
+    else if (anArg == "-crf"
+          || anArg == "-preset"
+          || anArg == "-qp")
+    {
+      const TCollection_AsciiString aParamName = anArg.SubString (2, anArg.Length());
+      if (++anArgIter >= theArgNb)
+      {
+        std::cout << "Syntax error at " << anArg << ".\n";
+        return 1;
+      }
+
+      aRecParams.VideoCodecParams.Bind (aParamName, theArgVec[anArgIter]);
+    }
     // animation definition options
     else if (anArg == "-start"
           || anArg == "-starttime"
@@ -6958,7 +7081,7 @@ static Standard_Integer VAnimation (Draw_Interpretor& theDI,
     }
   }
 
-  if (!toPlay)
+  if (!toPlay && aRecFile.IsEmpty())
   {
     return 0;
   }
@@ -6974,7 +7097,7 @@ static Standard_Integer VAnimation (Draw_Interpretor& theDI,
   }
 
   const Standard_Real anUpperPts = aPlayStartTime + aPlayDuration;
-  if (aFpsNum <= 0)
+  if (aRecParams.FpsNum <= 0)
   {
     while (!anAnimation->IsStopped())
     {
@@ -7028,12 +7151,35 @@ static Standard_Integer VAnimation (Draw_Interpretor& theDI,
     OSD_Timer aPerfTimer;
     aPerfTimer.Start();
 
+    Handle(Image_VideoRecorder) aRecorder;
+    ImageFlipper aFlipper;
+    Handle(Draw_ProgressIndicator) aProgress;
+    if (!aRecFile.IsEmpty())
+    {
+      if (aRecParams.Width  <= 0
+       || aRecParams.Height <= 0)
+      {
+        aView->Window()->Size (aRecParams.Width, aRecParams.Height);
+      }
+
+      aRecorder = new Image_VideoRecorder();
+      if (!aRecorder->Open (aRecFile.ToCString(), aRecParams))
+      {
+        std::cout << "Error: failed to open video file for recording\n";
+        return 0;
+      }
+
+      aProgress = new Draw_ProgressIndicator (theDI, 1);
+    }
+
     // Manage frame-rated animation here
     Standard_Real aPts = aPlayStartTime;
     int64_t aNbFrames = 0;
-    for (; aPts <= anUpperPts;)
+    Message_ProgressSentry aPSentry (aProgress, "Video recording, sec", 0, Max (1, Standard_Integer(aPlayDuration / aPlaySpeed)), 1);
+    Standard_Integer aSecondsProgress = 0;
+    for (; aPts <= anUpperPts && aPSentry.More();)
     {
-      const Standard_Real aRecPts = aPlaySpeed * ((Standard_Real(aFpsDen) / Standard_Real(aFpsNum)) * Standard_Real(aNbFrames));
+      const Standard_Real aRecPts = aPlaySpeed * ((Standard_Real(aRecParams.FpsDen) / Standard_Real(aRecParams.FpsNum)) * Standard_Real(aNbFrames));
       aPts = aPlayStartTime + aRecPts;
       ++aNbFrames;
       if (!anAnimation->Update (aPts))
@@ -7041,7 +7187,35 @@ static Standard_Integer VAnimation (Draw_Interpretor& theDI,
         break;
       }
 
-      aView->Redraw();
+      if (!aRecorder.IsNull())
+      {
+        V3d_ImageDumpOptions aDumpParams;
+        aDumpParams.Width          = aRecParams.Width;
+        aDumpParams.Height         = aRecParams.Height;
+        aDumpParams.BufferType     = Graphic3d_BT_RGBA;
+        aDumpParams.StereoOptions  = V3d_SDO_MONO;
+        aDumpParams.ToAdjustAspect = Standard_True;
+        if (!aView->ToPixMap (aRecorder->ChangeFrame(), aDumpParams))
+        {
+          std::cout << "Error: view dump is failed!\n";
+          return 0;
+        }
+        aFlipper.FlipY (aRecorder->ChangeFrame());
+        if (!aRecorder->PushFrame())
+        {
+          return 0;
+        }
+      }
+      else
+      {
+        aView->Redraw();
+      }
+
+      while (aSecondsProgress < Standard_Integer(aRecPts / aPlaySpeed))
+      {
+        aPSentry.Next();
+        ++aSecondsProgress;
+      }
     }
 
     aPerfTimer.Stop();
@@ -11172,6 +11346,17 @@ void ViewerTest::ViewerCommands(Draw_Interpretor& theCommands)
     "\n\t\t:   %Pts        overall animation presentation timestamp"
     "\n\t\t:   %LocalPts   local animation timestamp"
     "\n\t\t:   %Normalized local animation normalized value in range 0..1"
+    "\n\t\t:"
+    "\n\t\t: Video recording:"
+    "\n\t\t:  vanim name -record FileName [Width Height] [-fps FrameRate=24]"
+    "\n\t\t:             [-format Format] [-vcodec Codec] [-pix_fmt PixelFormat]"
+    "\n\t\t:             [-crf Value] [-preset Preset]"
+    "\n\t\t:   -fps     video framerate"
+    "\n\t\t:   -format  file format, container (matroska, etc.)"
+    "\n\t\t:   -vcodec  video codec identifier (ffv1, mjpeg, etc.)"
+    "\n\t\t:   -pix_fmt image pixel format (yuv420p, rgb24, etc.)"
+    "\n\t\t:   -crf     constant rate factor (specific to codec)"
+    "\n\t\t:   -preset  codec parameters preset (specific to codec)"
     __FILE__, VAnimation, group);
 
   theCommands.Add("vchangeselected",
diff --git a/tests/v3d/anim/videorecorder b/tests/v3d/anim/videorecorder
new file mode 100644 (file)
index 0000000..2752024
--- /dev/null
@@ -0,0 +1,17 @@
+puts "================"
+puts "OCC25382"
+puts "================"
+puts ""
+puts "==============================================================="
+puts "Visualization, TKOpenGl - improved video recording capability"
+puts "==============================================================="
+
+set aFile ${imagedir}/propeller.mkv
+file delete -force ${aFile}
+
+source $env(CSF_OCCTTestsPath)/v3d/anim/propeller
+vraytrace 1
+vrenderparams -msaa 8
+vrenderparams -fsaa 1
+XProgress +g
+vanimation anim -play -record ${aFile} 1920 1080 -vcodec ffv1 -fps 30 -speed 0.5
\ No newline at end of file