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 e1b3bbe..eac91c2 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 4bb1d48..7e978bb 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 a78c1aa..e6b1d94 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 9c4967e..a218070 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 7accc56..452da9c 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 ca45c3f..da43135 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 1d1eac8..327bc9c 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 b3887d0..5e7a3f0 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 4699fe7..32683e2 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 d81a1b2..8bf6310 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 aa41626..d53eb4a 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 d38f0f0..0c1e822 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 176527e..1897d18 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 a90a3f5..1801833 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 3b06434..3257b9c 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 ecd9e06..3ca8be8 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–63, 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 90a01f5..a2bba11 100755 (executable)
@@ -16,3 +16,4 @@ CSF_Appkit
 CSF_IOKit
 CSF_FreeImagePlus
 CSF_FREETYPE
+CSF_FFmpeg
index 20b1ed0..60ba847 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