├── .github └── workflows │ ├── ccpp.yml │ └── pypi.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake ├── config.cmake ├── modules │ ├── CUDA.cmake │ └── FFmpeg.cmake └── util │ ├── FindCUDA.cmake │ ├── FindCUDAArchFlags.cmake │ ├── FindFFmpeg.cmake │ └── Util.cmake ├── docs ├── Makefile ├── api │ └── index.rst ├── conf.py ├── index.rst └── symbol.png ├── examples ├── Javelin_standing_throw_drill.mkv ├── audio_reader.ipynb ├── av_reader.ipynb ├── count.mov ├── example.mp3 ├── flipping_a_pancake.mkv ├── video_loader.ipynb └── video_reader.ipynb ├── gpu.Dockerfile ├── include └── decord │ ├── audio_interface.h │ ├── av_interface.h │ ├── base.h │ ├── runtime │ ├── c_backend_api.h │ ├── c_runtime_api.h │ ├── device_api.h │ ├── module.h │ ├── ndarray.h │ ├── node_base.h │ ├── packed_func.h │ ├── registry.h │ ├── serializer.h │ ├── threading_backend.h │ └── util.h │ └── video_interface.h ├── python ├── decord │ ├── __init__.py │ ├── _api_internal.py │ ├── _ffi │ │ ├── README.md │ │ ├── __init__.py │ │ ├── _ctypes │ │ │ ├── __init__.py │ │ │ ├── function.py │ │ │ ├── ndarray.py │ │ │ └── types.py │ │ ├── _cy2 │ │ │ └── __init__.py │ │ ├── _cy3 │ │ │ └── __init__.py │ │ ├── _cython │ │ │ ├── .gitignore │ │ │ ├── base.pxi │ │ │ ├── core.pyx │ │ │ ├── function.pxi │ │ │ ├── ndarray.pxi │ │ │ └── node.pxi │ │ ├── base.py │ │ ├── function.py │ │ ├── libinfo.py │ │ ├── ndarray.py │ │ └── runtime_ctypes.py │ ├── audio_reader.py │ ├── av_reader.py │ ├── base.py │ ├── bridge │ │ ├── __init__.py │ │ ├── mxnet.py │ │ ├── tf.py │ │ ├── torchdl.py │ │ ├── tvm.py │ │ └── utils.py │ ├── data │ │ ├── __init__.py │ │ ├── base_action.py │ │ ├── dataloader.py │ │ ├── kinetics │ │ │ ├── __init__.py │ │ │ └── kinetics400_action.py │ │ └── transforms │ │ │ ├── __init__.py │ │ │ └── action.py │ ├── function │ │ ├── __init__.py │ │ └── base.py │ ├── logging.py │ ├── ndarray.py │ ├── video_loader.py │ └── video_reader.py └── setup.py ├── src ├── audio │ ├── audio_interface.cc │ ├── audio_reader.cc │ └── audio_reader.h ├── improc │ ├── README.md │ ├── improc.cu │ └── improc.h ├── runtime │ ├── c_runtime_api.cc │ ├── cpu_device_api.cc │ ├── cuda │ │ ├── cuda_common.h │ │ ├── cuda_device_api.cc │ │ ├── cuda_module.cc │ │ └── cuda_module.h │ ├── dso_module.cc │ ├── file_util.cc │ ├── file_util.h │ ├── meta_data.h │ ├── module.cc │ ├── module_util.cc │ ├── module_util.h │ ├── ndarray.cc │ ├── pack_args.h │ ├── registry.cc │ ├── runtime_base.h │ ├── str_util.cc │ ├── str_util.h │ ├── system_lib_module.cc │ ├── thread_pool.cc │ ├── thread_storage_scope.h │ ├── threading_backend.cc │ ├── workspace_pool.cc │ └── workspace_pool.h ├── sampler │ ├── random_file_order_sampler.cc │ ├── random_file_order_sampler.h │ ├── random_sampler.cc │ ├── random_sampler.h │ ├── sampler_interface.h │ ├── sequential_sampler.cc │ ├── sequential_sampler.h │ ├── smart_random_sampler.cc │ └── smart_random_sampler.h ├── segmenter │ ├── README.md │ ├── cutter.h │ ├── interval_cutter.cpp │ └── scene_cutter.cc └── video │ ├── ffmpeg │ ├── README.md │ ├── ffmpeg_common.h │ ├── filter_graph.cc │ ├── filter_graph.h │ ├── threaded_decoder.cc │ └── threaded_decoder.h │ ├── logging.cc │ ├── nvcodec │ ├── LICENSE │ ├── README.md │ ├── cuda_context.cc │ ├── cuda_context.h │ ├── cuda_decoder_impl.cc │ ├── cuda_decoder_impl.h │ ├── cuda_mapped_frame.cc │ ├── cuda_mapped_frame.h │ ├── cuda_parser.cc │ ├── cuda_parser.h │ ├── cuda_stream.cc │ ├── cuda_stream.h │ ├── cuda_texture.cc │ ├── cuda_texture.h │ ├── cuda_threaded_decoder.cc │ ├── cuda_threaded_decoder.h │ └── nvcuvid │ │ ├── cuviddec.h │ │ └── nvcuvid.h │ ├── storage_pool.cc │ ├── storage_pool.h │ ├── threaded_decoder_interface.h │ ├── video_interface.cc │ ├── video_loader.cc │ ├── video_loader.h │ ├── video_reader.cc │ └── video_reader.h ├── tests ├── benchmark │ ├── bench_decord.py │ ├── bench_opencv.py │ ├── bench_pyav.py │ └── cv2.npy ├── cpp │ ├── audio │ │ └── test_ffmpeg_audio_reader.cc │ └── video │ │ └── test_ffmpeg_video_reader.cc ├── python │ └── unittests │ │ ├── test_audio_reader.py │ │ ├── test_av_reader.py │ │ ├── test_bridges.py │ │ └── test_video_reader.py ├── test_data │ ├── corrupted.mp4 │ ├── unordered.mov │ ├── video_0.mov │ ├── video_180.mov │ ├── video_270.mov │ └── video_90.mov └── utils │ ├── generate_test_videos.cmd │ └── generate_test_videos.sh └── tools ├── build_macos_10_9.sh ├── build_manylinux2010.sh └── update_version.py /.github/workflows/ccpp.yml: -------------------------------------------------------------------------------- 1 | name: C/C++ CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | with: 17 | submodules: recursive 18 | - name: install dependencies 19 | run: sudo apt-get update && sudo apt-get install -y build-essential python3-dev python3-setuptools make cmake ffmpeg libavcodec-dev libavfilter-dev libavformat-dev libavutil-dev libswresample-dev 20 | - name: configure 21 | run: mkdir build && cd build && cmake .. -DUSE_CUDA=0 22 | - name: make 23 | run: cd build && make -j$(nproc) 24 | - name: python install 25 | run: pip3 install -e ./python 26 | - name: sanity test 27 | run: python3 -c "import decord; print(decord.__version__)" 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Custom ignored 2 | .vscode 3 | build/* 4 | docs/_build 5 | .DS_Store 6 | __pycache__ 7 | *.pyc 8 | .ipynb_checkpoints 9 | 10 | # Prerequisites 11 | *.d 12 | 13 | # Compiled Object files 14 | *.slo 15 | *.lo 16 | *.o 17 | *.obj 18 | 19 | # Precompiled Headers 20 | *.gch 21 | *.pch 22 | 23 | # Compiled Dynamic libraries 24 | *.so 25 | *.dylib 26 | *.dll 27 | 28 | # Fortran module files 29 | *.mod 30 | *.smod 31 | 32 | # Compiled Static libraries 33 | *.lai 34 | *.la 35 | *.a 36 | *.lib 37 | 38 | # Executables 39 | *.exe 40 | *.out 41 | *.app 42 | 43 | python/build/ 44 | python/*egg-info/ 45 | python/dist/ 46 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "3rdparty/dmlc-core"] 2 | path = 3rdparty/dmlc-core 3 | url = https://github.com/dmlc/dmlc-core 4 | [submodule "3rdparty/dlpack"] 5 | path = 3rdparty/dlpack 6 | url = https://github.com/dmlc/dlpack 7 | -------------------------------------------------------------------------------- /cmake/config.cmake: -------------------------------------------------------------------------------- 1 | #-------------------------------------------------------------------- 2 | # Template custom cmake configuration for compiling 3 | # 4 | # This file is used to override the build options in build. 5 | # If you want to change the configuration, please use the following 6 | # steps. Assume you are on the root directory. First copy the this 7 | # file so that any local changes will be ignored by git 8 | # 9 | # $ mkdir build 10 | # $ cp cmake/config.cmake build 11 | # 12 | # Next modify the according entries, and then compile by 13 | # 14 | # $ cd build 15 | # $ cmake .. 16 | # 17 | # Then buld in parallel with 8 threads 18 | # 19 | # $ make -j8 20 | #-------------------------------------------------------------------- 21 | 22 | #--------------------------------------------- 23 | # Backend runtimes. 24 | #--------------------------------------------- 25 | 26 | # Whether enable CUDA during compile, 27 | # 28 | # Possible values: 29 | # - ON: enable CUDA with cmake's auto search 30 | # - OFF: disable CUDA 31 | # - /path/to/cuda: use specific path to cuda toolkit 32 | set(USE_CUDA OFF) 33 | 34 | # Whether build with MT mode on Windows 35 | # 36 | # Possible values: 37 | # - ON: enable MT 38 | # - OFF: disalbe MT 39 | set(USE_MSVC_MT OFF) -------------------------------------------------------------------------------- /cmake/modules/CUDA.cmake: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | 18 | # CUDA Module 19 | find_cuda(${USE_CUDA}) 20 | 21 | if(CUDA_FOUND) 22 | # always set the includedir when cuda is available 23 | # avoid global retrigger of cmake 24 | include_directories(${CUDA_INCLUDE_DIRS}) 25 | add_definitions(-DDECORD_USE_CUDA) 26 | endif(CUDA_FOUND) 27 | 28 | if(USE_CUDA) 29 | if(NOT CUDA_FOUND) 30 | message(FATAL_ERROR "Cannot find CUDA, USE_CUDA=" ${USE_CUDA}) 31 | endif() 32 | if(NOT CUDA_NVCUVID_LIBRARY) 33 | message(FATAL_ERROR "Cannot find libnvcuvid, you may need to manually register and download at https://developer.nvidia.com/nvidia-video-codec-sdk. Then copy libnvcuvid to cuda_toolkit_root/lib64/" ) 34 | endif() 35 | message(STATUS "Build with CUDA support") 36 | file(GLOB RUNTIME_CUDA_SRCS src/runtime/cuda/*.cc) 37 | file(GLOB NVDEC_SRCS src/video/nvcodec/*.cc) 38 | file(GLOB NVDEC_CUDA_SRCS src/improc/*.cu) 39 | 40 | list(APPEND DECORD_LINKER_LIBS ${CUDA_NVRTC_LIBRARY}) 41 | list(APPEND DECORD_RUNTIME_LINKER_LIBS ${CUDA_CUDART_LIBRARY}) 42 | list(APPEND DECORD_RUNTIME_LINKER_LIBS ${CUDA_CUDA_LIBRARY}) 43 | list(APPEND DECORD_RUNTIME_LINKER_LIBS ${CUDA_NVRTC_LIBRARY}) 44 | list(APPEND DECORD_RUNTIME_LINKER_LIBS ${CUDA_NVIDIA_ML_LIBRARY}) 45 | list(APPEND DECORD_RUNTIME_LINKER_LIBS ${CUDA_NVCUVID_LIBRARY}) 46 | 47 | else(USE_CUDA) 48 | message(STATUS "CUDA disabled, no nvdec capabilities will be enabled...") 49 | set(NVDEC_SRCS "") 50 | set(RUNTIME_CUDA_SRCS "") 51 | endif(USE_CUDA) 52 | -------------------------------------------------------------------------------- /cmake/modules/FFmpeg.cmake: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | 18 | if(FFMPEG_FOUND) 19 | # always set the includedir when ffmpeg is available 20 | # avoid global retrigger of cmake 21 | message("FFMPEG_INCLUDE_DIR = ${FFMPEG_INCLUDE_DIR} ") 22 | message("FFMPEG_LIBRARIES = ${FFMPEG_LIBRARIES} ") 23 | include_directories(${FFMPEG_INCLUDE_DIR}) 24 | file(GLOB DECORD_FFMPEG_SRCS src/video/ffmpeg/*.cc) 25 | list(APPEND DECORD_LINKER_LIBS ${FFMPEG_LIBRARIES}) 26 | else() 27 | message( FATAL_ERROR "Unable to find FFMPEG automatically, please specify FFMPEG location" ) 28 | endif(FFMPEG_FOUND) 29 | -------------------------------------------------------------------------------- /cmake/util/FindCUDAArchFlags.cmake: -------------------------------------------------------------------------------- 1 | # This code just add -gencode arguments to CMAKE_CUDA_FLAGS based on 2 | # the contents of CUDA_ARCH, which is a list of architectures. It can 3 | # contain generation names from Fermi, Kepler, Maxwell, Pascal, or 4 | # Volta, or specific architectures of the form sm_## (e.g. "sm_52") 5 | # The default list is Maxwell, Pascal, Volta. 6 | 7 | set(CUDA_ARCH "" CACHE STRING "List of GPU architectures to compile CUDA device code for.") 8 | 9 | if(NOT CUDA_ARCH) 10 | if(${CMAKE_CUDA_FLAGS} MATCHES "--gpu-architecture|-arch[= ]|--gpu-code| -code[= ]|--generate-code|-gencode") 11 | message(STATUS "Using device code generation options found in CMAKE_CUDA_FLAGS") 12 | return() 13 | endif() 14 | set(__arch_names "Maxwell" "Pascal") 15 | if(CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL "9.0") 16 | list(APPEND __arch_names "Volta") 17 | endif() 18 | 19 | else() 20 | set(__arch_names ${CUDA_ARCH}) 21 | endif() 22 | 23 | foreach(arch ${__arch_names}) 24 | if(${arch} STREQUAL "Fermi") 25 | message(FATAL_ERROR "ERROR: Fermi GPU does not have the necessary hardware video decoders") 26 | elseif(${arch} STREQUAL "Kepler") 27 | list(APPEND __arch_nums "30" "35" "37") 28 | elseif(${arch} STREQUAL "Maxwell") 29 | list(APPEND __arch_nums "50" "52") 30 | elseif(${arch} STREQUAL "Pascal") 31 | list(APPEND __arch_nums "60" "61") 32 | elseif(${arch} STREQUAL "Volta") 33 | if (CMAKE_CUDA_COMPILER_VERSION VERSION_LESS "9.0") 34 | message(FATAL_ERROR "Requested Volta architecture, but CUDA version ${CMAKE_CUDA_COMPILER_VERSION} is not new enough") 35 | endif() 36 | list(APPEND __arch_nums "70") 37 | elseif(${arch} MATCHES "sm_([0-9]+)") 38 | list(APPEND __arch_nums ${CMAKE_MATCH_1}) 39 | else() 40 | message(FATAL_ERROR "ERROR: Unknown architecture ${arch} in CUDA_ARCH") 41 | endif() 42 | endforeach() 43 | 44 | if(NOT __arch_nums) 45 | message(FATAL_ERROR "ERROR: Don't know what GPU architectures to compile for.") 46 | endif() 47 | 48 | foreach(arch ${__arch_nums}) 49 | string(APPEND CMAKE_CUDA_FLAGS " -gencode arch=compute_${arch},code=sm_${arch}") 50 | endforeach() 51 | -------------------------------------------------------------------------------- /cmake/util/FindFFmpeg.cmake: -------------------------------------------------------------------------------- 1 | # - Try to find ffmpeg libraries (libavcodec, libavformat and libavutil) 2 | # Once done this will define 3 | # 4 | # FFMPEG_FOUND - system has ffmpeg or libav 5 | # FFMPEG_INCLUDE_DIR - the ffmpeg include directory 6 | # FFMPEG_LIBRARIES - Link these to use ffmpeg 7 | # FFMPEG_LIBAVCODEC 8 | # FFMPEG_LIBAVFORMAT 9 | # FFMPEG_LIBAVUTIL 10 | # FFMPEG_LIBAVDEVICE 11 | # 12 | # Copyright (c) 2008 Andreas Schneider 13 | # Modified for other libraries by Lasse Kärkkäinen 14 | # Modified for Hedgewars by Stepik777 15 | # 16 | # Redistribution and use is allowed according to the terms of the New 17 | # BSD license. 18 | # 19 | 20 | if (FFMPEG_DIR) 21 | set(FFMPEG_INCLUDE_DIR ${FFMPEG_DIR}/include) 22 | if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") 23 | # Mac OS X specific code 24 | set(FFMPEG_LIBRARIES 25 | ${FFMPEG_DIR}/lib/libavformat.dylib 26 | ${FFMPEG_DIR}/lib/libavfilter.dylib 27 | ${FFMPEG_DIR}/lib/libavcodec.dylib 28 | ${FFMPEG_DIR}/lib/libavutil.dylib 29 | ${FFMPEG_DIR}/lib/libavdevice.dylib 30 | ${FFMPEG_DIR}/lib/libswresample.dylib 31 | ) 32 | elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux") 33 | set(FFMPEG_LIBRARIES 34 | ${FFMPEG_DIR}/lib/libavformat.so 35 | ${FFMPEG_DIR}/lib/libavfilter.so 36 | ${FFMPEG_DIR}/lib/libavcodec.so 37 | ${FFMPEG_DIR}/lib/libavutil.so 38 | ${FFMPEG_DIR}/lib/libavdevice.so 39 | ${FFMPEG_DIR}/lib/libswresample.so 40 | ) 41 | else() 42 | set(FFMPEG_LIBRARIES 43 | ${FFMPEG_DIR}/lib/libavformat.lib 44 | ${FFMPEG_DIR}/lib/libavfilter.lib 45 | ${FFMPEG_DIR}/lib/libavcodec.lib 46 | ${FFMPEG_DIR}/lib/libavutil.lib 47 | ${FFMPEG_DIR}/lib/libavdevice.lib 48 | ${FFMPEG_DIR}/lib/libswresample.lib 49 | ) 50 | endif() 51 | endif (FFMPEG_DIR) 52 | 53 | if (FFMPEG_LIBRARIES AND FFMPEG_INCLUDE_DIR) 54 | # in cache already 55 | set(FFMPEG_FOUND TRUE) 56 | else (FFMPEG_LIBRARIES AND FFMPEG_INCLUDE_DIR) 57 | # use pkg-config to get the directories and then use these values 58 | # in the FIND_PATH() and FIND_LIBRARY() calls 59 | find_package(PkgConfig) 60 | if (PKG_CONFIG_FOUND) 61 | pkg_check_modules(_FFMPEG_AVCODEC libavcodec) 62 | pkg_check_modules(_FFMPEG_AVFORMAT libavformat) 63 | pkg_check_modules(_FFMPEG_AVUTIL libavutil) 64 | pkg_check_modules(_FFMPEG_AVDEVICE libavdevice) 65 | 66 | pkg_check_modules(_FFMPEG_AVFILTER libavfilter) 67 | pkg_check_modules(_FFMPEG_SWRESAMPLE libswresample) 68 | endif (PKG_CONFIG_FOUND) 69 | 70 | find_path(FFMPEG_AVCODEC_INCLUDE_DIR 71 | NAMES libavcodec/avcodec.h 72 | PATHS ${_FFMPEG_AVCODEC_INCLUDE_DIRS} /usr/include /usr/local/include /opt/local/include /sw/include 73 | PATH_SUFFIXES ffmpeg libav 74 | ) 75 | 76 | find_library(FFMPEG_LIBAVCODEC 77 | NAMES avcodec 78 | PATHS ${_FFMPEG_AVCODEC_LIBRARY_DIRS} /usr/lib /usr/local/lib /opt/local/lib /sw/lib 79 | ) 80 | 81 | find_library(FFMPEG_LIBAVFORMAT 82 | NAMES avformat 83 | PATHS ${_FFMPEG_AVFORMAT_LIBRARY_DIRS} /usr/lib /usr/local/lib /opt/local/lib /sw/lib 84 | ) 85 | 86 | find_library(FFMPEG_LIBAVUTIL 87 | NAMES avutil 88 | PATHS ${_FFMPEG_AVUTIL_LIBRARY_DIRS} /usr/lib /usr/local/lib /opt/local/lib /sw/lib 89 | ) 90 | 91 | find_library(FFMPEG_LIBAVDEVICE 92 | NAMES avdevice 93 | PATHS ${_FFMPEG_AVDEVICE_LIBRARY_DIRS} /usr/lib /usr/local/lib /opt/local/lib /sw/lib 94 | ) 95 | 96 | find_library(FFMPEG_LIBAVFILTER 97 | NAMES avfilter 98 | PATHS ${_FFMPEG_AVFILTER_LIBRARY_DIRS} /usr/lib /usr/local/lib /opt/local/lib /sw/lib 99 | ) 100 | 101 | find_library(FFMPEG_SWRESAMPLE 102 | NAMES libswresample swresample 103 | PATHS ${_FFMPEG_SWRESAMPLE_LIBRARY_DIRS} /usr/lib /usr/local/lib /opt/local/lib /sw/lib 104 | ) 105 | 106 | if (FFMPEG_LIBAVCODEC AND FFMPEG_LIBAVFORMAT) 107 | set(FFMPEG_FOUND TRUE) 108 | endif() 109 | 110 | if (FFMPEG_FOUND) 111 | set(FFMPEG_INCLUDE_DIR ${FFMPEG_AVCODEC_INCLUDE_DIR}) 112 | 113 | set(FFMPEG_LIBRARIES 114 | ${FFMPEG_LIBAVFORMAT} 115 | ${FFMPEG_LIBAVFILTER} 116 | ${FFMPEG_LIBAVCODEC} 117 | ${FFMPEG_LIBAVUTIL} 118 | ${FFMPEG_SWRESAMPLE} 119 | ) 120 | 121 | if (FFMPEG_LIBAVDEVICE) 122 | message(STATUS "Found libavdevice, device input will be enabled") 123 | set(FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${FFMPEG_LIBAVDEVICE}) 124 | add_definitions(-DDECORD_USE_LIBAVDEVICE) 125 | else (FFMPEG_LIBAVDEVICE) 126 | message(STATUS "Unable to find libavdevice, device input API will not work!") 127 | endif (FFMPEG_LIBAVDEVICE) 128 | 129 | endif (FFMPEG_FOUND) 130 | 131 | if (FFMPEG_FOUND) 132 | if (NOT FFMPEG_FIND_QUIETLY) 133 | message(STATUS "Found FFMPEG or Libav: ${FFMPEG_LIBRARIES}, ${FFMPEG_INCLUDE_DIR}") 134 | endif (NOT FFMPEG_FIND_QUIETLY) 135 | else (FFMPEG_FOUND) 136 | if (FFMPEG_FIND_REQUIRED) 137 | message(FATAL_ERROR "Could not find libavcodec or libavformat or libavutil") 138 | endif (FFMPEG_FIND_REQUIRED) 139 | endif (FFMPEG_FOUND) 140 | 141 | endif (FFMPEG_LIBRARIES AND FFMPEG_INCLUDE_DIR) 142 | -------------------------------------------------------------------------------- /cmake/util/Util.cmake: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | 18 | macro(__decord_option variable description value) 19 | if(NOT DEFINED ${variable}) 20 | set(${variable} ${value} CACHE STRING ${description}) 21 | endif() 22 | endmacro() 23 | 24 | ####################################################### 25 | # An option that the user can select. Can accept condition to control when option is available for user. 26 | # Usage: 27 | # decord_option( "doc string" [IF ]) 28 | macro(decord_option variable description value) 29 | set(__value ${value}) 30 | set(__condition "") 31 | set(__varname "__value") 32 | foreach(arg ${ARGN}) 33 | if(arg STREQUAL "IF" OR arg STREQUAL "if") 34 | set(__varname "__condition") 35 | else() 36 | list(APPEND ${__varname} ${arg}) 37 | endif() 38 | endforeach() 39 | unset(__varname) 40 | if("${__condition}" STREQUAL "") 41 | set(__condition 2 GREATER 1) 42 | endif() 43 | 44 | if(${__condition}) 45 | if("${__value}" MATCHES ";") 46 | if(${__value}) 47 | __decord_option(${variable} "${description}" ON) 48 | else() 49 | __decord_option(${variable} "${description}" OFF) 50 | endif() 51 | elseif(DEFINED ${__value}) 52 | if(${__value}) 53 | __decord_option(${variable} "${description}" ON) 54 | else() 55 | __decord_option(${variable} "${description}" OFF) 56 | endif() 57 | else() 58 | __decord_option(${variable} "${description}" "${__value}") 59 | endif() 60 | else() 61 | unset(${variable} CACHE) 62 | endif() 63 | endmacro() 64 | 65 | function(assign_source_group group) 66 | foreach(_source IN ITEMS ${ARGN}) 67 | if (IS_ABSOLUTE "${_source}") 68 | file(RELATIVE_PATH _source_rel "${CMAKE_CURRENT_SOURCE_DIR}" "${_source}") 69 | else() 70 | set(_source_rel "${_source}") 71 | endif() 72 | get_filename_component(_source_path "${_source_rel}" PATH) 73 | string(REPLACE "/" "\\" _source_path_msvc "${_source_path}") 74 | source_group("${group}\\${_source_path_msvc}" FILES "${_source}") 75 | endforeach() 76 | endfunction(assign_source_group) 77 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SOURCEDIR = . 8 | BUILDDIR = _build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | %: Makefile 19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/api/index.rst: -------------------------------------------------------------------------------- 1 | Python API Reference 2 | ==================== 3 | 4 | .. currentmodule:: decord 5 | 6 | .. automodule:: decord 7 | 8 | .. autosummary:: 9 | 10 | VideoReader 11 | 12 | VideoLoader 13 | 14 | 15 | API Reference 16 | ------------- 17 | 18 | .. automodule:: decord.video_reader 19 | :members: 20 | 21 | .. automodule:: decord.video_loader 22 | :members: 23 | 24 | .. automodule:: decord.ndarray 25 | :members: 26 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. decord documentation master file, created by 2 | sphinx-quickstart on Tue Dec 3 15:00:39 2019. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to decord's documentation! 7 | ================================== 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: Contents: 12 | 13 | api/index 14 | 15 | Indices and tables 16 | ================== 17 | 18 | * :ref:`genindex` 19 | * :ref:`modindex` 20 | * :ref:`search` 21 | -------------------------------------------------------------------------------- /docs/symbol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmlc/decord/d2e56190286ae394032a8141885f76d5372bd44b/docs/symbol.png -------------------------------------------------------------------------------- /examples/Javelin_standing_throw_drill.mkv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmlc/decord/d2e56190286ae394032a8141885f76d5372bd44b/examples/Javelin_standing_throw_drill.mkv -------------------------------------------------------------------------------- /examples/count.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmlc/decord/d2e56190286ae394032a8141885f76d5372bd44b/examples/count.mov -------------------------------------------------------------------------------- /examples/example.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmlc/decord/d2e56190286ae394032a8141885f76d5372bd44b/examples/example.mp3 -------------------------------------------------------------------------------- /examples/flipping_a_pancake.mkv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmlc/decord/d2e56190286ae394032a8141885f76d5372bd44b/examples/flipping_a_pancake.mkv -------------------------------------------------------------------------------- /gpu.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nvcr.io/nvidia/cuda:11.2.0-cudnn8-devel-ubuntu20.04 2 | 3 | ENV DEBIAN_FRONTEND=noninteractive 4 | 5 | RUN apt-get -y update && apt-get install -y \ 6 | software-properties-common \ 7 | build-essential \ 8 | checkinstall \ 9 | cmake \ 10 | make \ 11 | pkg-config \ 12 | yasm \ 13 | git \ 14 | vim \ 15 | curl \ 16 | wget \ 17 | sudo \ 18 | apt-transport-https \ 19 | libcanberra-gtk-module \ 20 | libcanberra-gtk3-module \ 21 | dbus-x11 \ 22 | iputils-ping \ 23 | python3-dev \ 24 | python3-pip \ 25 | python3-setuptools 26 | 27 | # some image/media dependencies 28 | RUN apt-get -y update && apt-get install -y \ 29 | libjpeg8-dev \ 30 | libpng-dev \ 31 | libtiff5-dev \ 32 | libtiff-dev \ 33 | libavcodec-dev \ 34 | libavformat-dev \ 35 | libswscale-dev \ 36 | libdc1394-22-dev \ 37 | libxine2-dev \ 38 | libavfilter-dev \ 39 | libavutil-dev 40 | 41 | RUN apt-get -y update && apt-get install -y ffmpeg 42 | 43 | RUN apt-get clean && rm -rf /tmp/* /var/tmp/* /var/lib/apt/lists/* && apt-get -y autoremove 44 | 45 | ENV NVIDIA_DRIVER_CAPABILITIES=all 46 | RUN ln -s /usr/lib/x86_64-linux-gnu/libnvcuvid.so.1 /usr/local/cuda/lib64/libnvcuvid.so 47 | RUN git clone --recursive https://github.com/dmlc/decord 48 | RUN cd decord && mkdir build && cd build && cmake .. -DUSE_CUDA=ON -DCMAKE_BUILD_TYPE=Release && make -j2 && cd ../python && python3 setup.py install 49 | -------------------------------------------------------------------------------- /include/decord/audio_interface.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Yin, Weisu on 1/7/21. 3 | // 4 | 5 | #ifndef DECORD_AUDIO_INTERFACE_H 6 | #define DECORD_AUDIO_INTERFACE_H 7 | 8 | #include "runtime/ndarray.h" 9 | #include "../../src/video/ffmpeg/ffmpeg_common.h" 10 | 11 | namespace decord { 12 | typedef void* AudioReaderInterfaceHandle; 13 | 14 | enum IOType { 15 | kNormal = 0U, // normal file or URL 16 | kDevice, // device, e.g., camera 17 | kRawBytes, // raw bytes, e.g., raw data read from python file like object 18 | }; 19 | 20 | class AudioReaderInterface; 21 | typedef std::shared_ptr AudioReaderPtr; 22 | 23 | using NDArray = runtime::NDArray; 24 | 25 | class AudioReaderInterface { 26 | public: 27 | virtual ~AudioReaderInterface() = default; 28 | virtual NDArray GetNDArray() = 0; 29 | virtual int GetNumPaddingSamples() = 0; 30 | virtual double GetDuration() = 0; 31 | virtual int64_t GetNumSamplesPerChannel() = 0; 32 | virtual int GetNumChannels() = 0; 33 | virtual void GetInfo() = 0; 34 | }; 35 | 36 | DECORD_DLL AudioReaderPtr GetAudioReader(std::string fname, int sampleRate, DLContext ctx, int io_type, bool mono); 37 | } 38 | 39 | #endif //DECORD_AUDIO_INTERFACE_H 40 | -------------------------------------------------------------------------------- /include/decord/av_interface.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Yin, Weisu on 1/7/21. 3 | // 4 | 5 | #ifndef DECORD_AV_INTERFACE_H 6 | #define DECORD_AV_INTERFACE_H 7 | 8 | #include "runtime/ndarray.h" 9 | 10 | namespace decord { 11 | 12 | using NDArray = runtime::NDArray; 13 | 14 | struct AVContainer { 15 | NDArray audio; 16 | NDArray video; 17 | }; 18 | 19 | class AVReaderInterface { 20 | 21 | public: 22 | virtual AVContainer GetBatch(std::vector indices, AVContainer buf) = 0; 23 | virtual ~AVReaderInterface() = default; 24 | 25 | }; 26 | } 27 | 28 | #endif //DECORD_AV_INTERFACE_H 29 | -------------------------------------------------------------------------------- /include/decord/base.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file base.h 4 | * \brief configuration of decord and basic data structure. 5 | */ 6 | #ifndef DECORD_BASE_H_ 7 | #define DECORD_BASE_H_ 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | 17 | namespace decord { 18 | 19 | // common data types 20 | static const DLDataType kUInt8 = { kDLUInt, 8U, 1U }; 21 | static const DLDataType kUInt16 = { kDLUInt, 16U, 1U }; 22 | static const DLDataType kFloat16 = { kDLFloat, 16U, 1U }; 23 | static const DLDataType kFloat32 = { kDLFloat, 32U, 1U }; 24 | static const DLDataType kInt64 = {kDLInt, 64U, 1U}; 25 | 26 | /*! \brief check if current date type equals another one */ 27 | inline bool operator== (const DLDataType &d1, const DLDataType &d2) { 28 | return (d1.bits == d2.bits && d1.code == d2.code && d1.lanes == d2.lanes); 29 | } 30 | 31 | static const DLContext kCPU = {kDLCPU, 0}; 32 | static const DLContext kGPU = {kDLGPU, 0}; 33 | 34 | /*! \brief performance flags */ 35 | int constexpr kCPUAlignment = 32; // For video width alignment, not comply to this will result in sparse arrays 36 | 37 | } // namespace decord 38 | #endif // DECORD_BASE_H_ 39 | -------------------------------------------------------------------------------- /include/decord/runtime/serializer.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file decord/runtime/serializer.h 4 | * \brief Serializer extension to support DECORD data types 5 | * Include this file to enable serialization of DLDataType, DLContext 6 | */ 7 | #ifndef DECORD_RUNTIME_SERIALIZER_H_ 8 | #define DECORD_RUNTIME_SERIALIZER_H_ 9 | 10 | #include 11 | #include 12 | #include "c_runtime_api.h" 13 | #include "ndarray.h" 14 | 15 | namespace dmlc { 16 | namespace serializer { 17 | 18 | template<> 19 | struct Handler { 20 | inline static void Write(Stream *strm, const DLDataType& dtype) { 21 | Handler::Write(strm, dtype.code); 22 | Handler::Write(strm, dtype.bits); 23 | Handler::Write(strm, dtype.lanes); 24 | } 25 | inline static bool Read(Stream *strm, DLDataType* dtype) { 26 | if (!Handler::Read(strm, &(dtype->code))) return false; 27 | if (!Handler::Read(strm, &(dtype->bits))) return false; 28 | if (!Handler::Read(strm, &(dtype->lanes))) return false; 29 | return true; 30 | } 31 | }; 32 | 33 | template<> 34 | struct Handler { 35 | inline static void Write(Stream *strm, const DLContext& ctx) { 36 | int32_t device_type = static_cast(ctx.device_type); 37 | Handler::Write(strm, device_type); 38 | Handler::Write(strm, ctx.device_id); 39 | } 40 | inline static bool Read(Stream *strm, DLContext* ctx) { 41 | int32_t device_type = 0; 42 | if (!Handler::Read(strm, &(device_type))) return false; 43 | ctx->device_type = static_cast(device_type); 44 | if (!Handler::Read(strm, &(ctx->device_id))) return false; 45 | return true; 46 | } 47 | }; 48 | 49 | } // namespace serializer 50 | } // namespace dmlc 51 | #endif // DECORD_RUNTIME_SERIALIZER_H_ 52 | -------------------------------------------------------------------------------- /include/decord/runtime/threading_backend.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file decord/runtime/threading_backend.h 4 | * \brief Utilities for manipulating thread pool threads. 5 | */ 6 | #ifndef DECORD_RUNTIME_THREADING_BACKEND_H_ 7 | #define DECORD_RUNTIME_THREADING_BACKEND_H_ 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | namespace decord { 14 | namespace runtime { 15 | namespace threading { 16 | 17 | /*! 18 | * \brief A platform-agnostic abstraction for managing a collection of 19 | * thread pool threads. 20 | */ 21 | class ThreadGroup { 22 | public: 23 | class Impl; 24 | 25 | /*! 26 | * \brief Creates a collection of threads which run a provided function. 27 | * 28 | * \param num_workers The total number of worker threads in this group. 29 | Includes main thread if `exclude_worker0 = true` 30 | * \param worker_callback A callback which is run in its own thread. 31 | Receives the worker_id as an argument. 32 | * \param exclude_worker0 Whether to use the main thread as a worker. 33 | * If `true`, worker0 will not be launched in a new thread and 34 | * `worker_callback` will only be called for values >= 1. This 35 | * allows use of the main thread as a worker. 36 | */ 37 | ThreadGroup(int num_workers, 38 | std::function worker_callback, 39 | bool exclude_worker0 = false); 40 | ~ThreadGroup(); 41 | 42 | /*! 43 | * \brief Blocks until all non-main threads in the pool finish. 44 | */ 45 | void Join(); 46 | 47 | enum AffinityMode : int { 48 | kBig = 1, 49 | kLittle = -1, 50 | }; 51 | 52 | /*! 53 | * \brief configure the CPU id affinity 54 | * 55 | * \param mode The preferred CPU type (1 = big, -1 = little). 56 | * \param nthreads The number of threads to use (0 = use all). 57 | * \param exclude_worker0 Whether to use the main thread as a worker. 58 | * If `true`, worker0 will not be launched in a new thread and 59 | * `worker_callback` will only be called for values >= 1. This 60 | * allows use of the main thread as a worker. 61 | * 62 | * \return The number of workers to use. 63 | */ 64 | int Configure(AffinityMode mode, int nthreads, bool exclude_worker0); 65 | 66 | private: 67 | Impl* impl_; 68 | }; 69 | 70 | /*! 71 | * \brief Platform-agnostic no-op. 72 | */ 73 | void Yield(); 74 | 75 | /*! 76 | * \return the maximum number of effective workers for this system. 77 | */ 78 | int MaxConcurrency(); 79 | 80 | 81 | } // namespace threading 82 | } // namespace runtime 83 | } // namespace decord 84 | 85 | #endif // DECORD_RUNTIME_THREADING_BACKEND_H_ 86 | -------------------------------------------------------------------------------- /include/decord/runtime/util.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file decord/runtime/util.h 4 | * \brief Useful runtime util. 5 | */ 6 | #ifndef DECORD_RUNTIME_UTIL_H_ 7 | #define DECORD_RUNTIME_UTIL_H_ 8 | 9 | #include "c_runtime_api.h" 10 | 11 | namespace decord { 12 | namespace runtime { 13 | 14 | /*! 15 | * \brief Check whether type matches the given spec. 16 | * \param t The type 17 | * \param code The type code. 18 | * \param bits The number of bits to be matched. 19 | * \param lanes The number of lanes in the type. 20 | */ 21 | inline bool TypeMatch(DECORDType t, int code, int bits, int lanes = 1) { 22 | return t.code == code && t.bits == bits && t.lanes == lanes; 23 | } 24 | } // namespace runtime 25 | } // namespace decord 26 | // Forward declare the intrinsic id we need 27 | // in structure fetch to enable stackvm in runtime 28 | namespace decord { 29 | namespace ir { 30 | namespace intrinsic { 31 | /*! \brief The kind of structure field info used in intrinsic */ 32 | enum DECORDStructFieldKind : int { 33 | // array head address 34 | kArrAddr, 35 | kArrData, 36 | kArrShape, 37 | kArrStrides, 38 | kArrNDim, 39 | kArrTypeCode, 40 | kArrTypeBits, 41 | kArrTypeLanes, 42 | kArrByteOffset, 43 | kArrDeviceId, 44 | kArrDeviceType, 45 | kArrKindBound_, 46 | // DECORDValue field 47 | kDECORDValueContent, 48 | kDECORDValueKindBound_ 49 | }; 50 | } // namespace intrinsic 51 | } // namespace ir 52 | } // namespace decord 53 | #endif // DECORD_RUNTIME_UTIL_H_ 54 | -------------------------------------------------------------------------------- /include/decord/video_interface.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file video_interface.h 4 | * \brief Video interface 5 | */ 6 | 7 | #ifndef DECORD_VIDEO_INTERFACE_H_ 8 | #define DECORD_VIDEO_INTERFACE_H_ 9 | 10 | #include 11 | #include 12 | 13 | #include "base.h" 14 | #include "runtime/ndarray.h" 15 | 16 | namespace decord { 17 | typedef void* VideoReaderInterfaceHandle; 18 | typedef void* VideoLoaderInterfaceHandle; 19 | 20 | enum IOType { 21 | kNormal = 0U, // normal file or URL 22 | kDevice, // device, e.g., camera 23 | kRawBytes, // raw bytes, e.g., raw data read from python file like object 24 | }; 25 | 26 | enum VideoLoaderShuffleType { 27 | kSequential = 0U, 28 | kShuffleVideoOrderOnly, 29 | kShuffleBoth, 30 | kShuffleInsideVideoOnly, 31 | }; 32 | 33 | class VideoReaderInterface; 34 | typedef std::shared_ptr VideoReaderPtr; 35 | 36 | // Video Reader is an abtract class defining the reader interfaces 37 | class VideoReaderInterface { 38 | public: 39 | /*! \brief open video, return true if success */ 40 | // virtual bool Open(std::string& fname, AccessType acc_type, FrameProperty prop) = 0; 41 | /*! \brief query stream info in video */ 42 | virtual unsigned int QueryStreams() const = 0; 43 | /*! \brief check if video file successfully opened */ 44 | virtual void SetVideoStream(int stream_nb = -1) = 0; 45 | /*! \brief get the total frame count in current stream */ 46 | virtual int64_t GetFrameCount() const = 0; 47 | /*! \brief get the current frame position in current stream */ 48 | virtual int64_t GetCurrentPosition() const = 0; 49 | /*! \brief read the next frame, return NDArray */ 50 | virtual runtime::NDArray NextFrame() = 0; 51 | /*! \brief retrieve keyframe indices */ 52 | virtual runtime::NDArray GetKeyIndices() = 0; 53 | /*! \brief retrieve playback seconds by frame indices */ 54 | virtual runtime::NDArray GetFramePTS() const = 0; 55 | /*! \brief read bulk frames, defined by indices */ 56 | virtual runtime::NDArray GetBatch(std::vector indices, runtime::NDArray buf) = 0; 57 | /*! \brief skip certain frames without decoding */ 58 | virtual void SkipFrames(int64_t num = 1) = 0; 59 | /*! \brief get average fps */ 60 | virtual double GetAverageFPS() const = 0; 61 | /*! \brief get rotation */ 62 | virtual double GetRotation() const = 0; 63 | /*! \brief destructor */ 64 | virtual ~VideoReaderInterface() = default; 65 | 66 | // The following APIs have perf concerns, use with caucious 67 | /*! \brief seek to position, this will clear all buffer and queue */ 68 | // virtual runtime::NDArray Seek(uint64_t pos) = 0; 69 | /** 70 | * \brief Seek to nearest keyframe before frame pos 71 | * 72 | * \param pos The frame position 73 | * \return true Success 74 | * \return false Failed 75 | */ 76 | virtual bool Seek(int64_t pos) = 0; 77 | /** 78 | * \brief Seek accurately to given position 79 | * 80 | * \param pos Frame position 81 | * \return true Success 82 | * \return false Failed 83 | */ 84 | virtual bool SeekAccurate(int64_t pos) = 0; 85 | /*! \brief seek and read frame at position p */ 86 | // virtual runtime::NDArray GetFrame(uint64_t pos) = 0; 87 | }; // class VideoReader 88 | 89 | 90 | DECORD_DLL VideoReaderPtr GetVideoReader(std::string fname, DLContext ctx, 91 | int width=-1, int height=-1, int nb_thread=0, 92 | int io_type=kNormal); 93 | 94 | /** 95 | * \brief Interface of VideoLoader, pure virtual class 96 | * 97 | */ 98 | class VideoLoaderInterface { 99 | public: 100 | using NDArray = runtime::NDArray; 101 | virtual ~VideoLoaderInterface() = default; 102 | virtual void Reset() = 0; 103 | virtual bool HasNext() const = 0; 104 | virtual void Next() = 0; 105 | virtual NDArray NextData() = 0; 106 | virtual NDArray NextIndices() = 0; 107 | virtual int64_t Length() const = 0; 108 | }; // class VideoLoaderInterface 109 | 110 | } // namespace decord 111 | #endif // DECORD_VIDEO_INTERFACE_H_ 112 | -------------------------------------------------------------------------------- /python/decord/__init__.py: -------------------------------------------------------------------------------- 1 | """Decord python package""" 2 | from . import function 3 | 4 | from ._ffi.runtime_ctypes import TypeCode 5 | from ._ffi.function import register_func, get_global_func, list_global_func_names, extract_ext_funcs 6 | from ._ffi.base import DECORDError, DECORDLimitReachedError, __version__ 7 | 8 | from .base import ALL 9 | 10 | from . import ndarray as nd 11 | from .ndarray import cpu, gpu 12 | from . import bridge 13 | from . import logging 14 | from .video_reader import VideoReader 15 | from .video_loader import VideoLoader 16 | from .audio_reader import AudioReader 17 | from .av_reader import AVReader 18 | 19 | logging.set_level(logging.ERROR) 20 | -------------------------------------------------------------------------------- /python/decord/_api_internal.py: -------------------------------------------------------------------------------- 1 | """Namespace for internal apis.""" 2 | -------------------------------------------------------------------------------- /python/decord/_ffi/README.md: -------------------------------------------------------------------------------- 1 | # C API and runtime FFI module 2 | 3 | Borrowed and adapted from dmlc TVM/DGL projects. 4 | -------------------------------------------------------------------------------- /python/decord/_ffi/__init__.py: -------------------------------------------------------------------------------- 1 | """C interfacing code. 2 | 3 | This namespace contains everything that interacts with C code. 4 | Most C related object are ctypes compatible, which means 5 | they contains a handle field that is ctypes.c_void_p and can 6 | be used via ctypes function calls. 7 | 8 | Some performance critical functions are implemented by cython 9 | and have a ctypes fallback implementation. 10 | """ 11 | -------------------------------------------------------------------------------- /python/decord/_ffi/_ctypes/__init__.py: -------------------------------------------------------------------------------- 1 | """ctypes specific implementation of FFI""" 2 | -------------------------------------------------------------------------------- /python/decord/_ffi/_ctypes/ndarray.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=invalid-name 2 | """Runtime NDArray api""" 3 | from __future__ import absolute_import 4 | 5 | import ctypes 6 | from ..base import _LIB, check_call, c_str 7 | from ..runtime_ctypes import DECORDArrayHandle 8 | from .types import RETURN_SWITCH, C_TO_PY_ARG_SWITCH, _wrap_arg_func, _return_handle 9 | 10 | 11 | DECORDPyCapsuleDestructor = ctypes.CFUNCTYPE(None, ctypes.c_void_p) 12 | _c_str_dltensor = c_str('dltensor') 13 | _c_str_used_dltensor = c_str('used_dltensor') 14 | 15 | 16 | # used for PyCapsule manipulation 17 | if hasattr(ctypes, 'pythonapi'): 18 | ctypes.pythonapi.PyCapsule_GetName.restype = ctypes.c_char_p 19 | ctypes.pythonapi.PyCapsule_GetPointer.restype = ctypes.c_void_p 20 | ctypes.pythonapi.PyCapsule_New.restype = ctypes.py_object 21 | 22 | 23 | def _from_dlpack(dltensor): 24 | dltensor = ctypes.py_object(dltensor) 25 | if ctypes.pythonapi.PyCapsule_IsValid(dltensor, _c_str_dltensor): 26 | ptr = ctypes.pythonapi.PyCapsule_GetPointer(dltensor, _c_str_dltensor) 27 | # XXX(minjie): The below cast should be unnecessary given the code to 28 | # set restype of PyCapsule calls. But weirdly, this does not 29 | # work out always. 30 | ptr = ctypes.cast(ptr, ctypes.c_void_p) 31 | handle = DECORDArrayHandle() 32 | check_call(_LIB.DECORDArrayFromDLPack(ptr, ctypes.byref(handle))) 33 | ctypes.pythonapi.PyCapsule_SetName(dltensor, _c_str_used_dltensor) 34 | ctypes.pythonapi.PyCapsule_SetDestructor(dltensor, DECORDPyCapsuleDestructor(0)) 35 | return _make_array(handle, False) 36 | raise ValueError("Expect a dltensor field, PyCapsule can only be consumed once") 37 | 38 | 39 | def _dlpack_deleter(pycapsule): 40 | pycapsule = ctypes.cast(pycapsule, ctypes.py_object) 41 | if ctypes.pythonapi.PyCapsule_IsValid(pycapsule, _c_str_dltensor): 42 | ptr = ctypes.pythonapi.PyCapsule_GetPointer(pycapsule, _c_str_dltensor) 43 | # XXX(minjie): The below cast should be unnecessary given the code to 44 | # set restype of PyCapsule calls. But weirdly, this does not 45 | # work out always. 46 | ptr = ctypes.cast(ptr, ctypes.c_void_p) 47 | _LIB.DECORDDLManagedTensorCallDeleter(ptr) 48 | ctypes.pythonapi.PyCapsule_SetDestructor(pycapsule, DECORDPyCapsuleDestructor(0)) 49 | 50 | _c_dlpack_deleter = DECORDPyCapsuleDestructor(_dlpack_deleter) 51 | 52 | 53 | class NDArrayBase(object): 54 | """A simple Device/CPU Array object in runtime.""" 55 | __slots__ = ["handle", "is_view"] 56 | # pylint: disable=no-member 57 | def __init__(self, handle, is_view=False): 58 | """Initialize the function with handle 59 | 60 | Parameters 61 | ---------- 62 | handle : DECORDArrayHandle 63 | the handle to the underlying C++ DECORDArray 64 | """ 65 | self.handle = handle 66 | self.is_view = is_view 67 | 68 | def __del__(self): 69 | if not self.is_view and _LIB: 70 | check_call(_LIB.DECORDArrayFree(self.handle)) 71 | 72 | @property 73 | def _decord_handle(self): 74 | return ctypes.cast(self.handle, ctypes.c_void_p).value 75 | 76 | def to_dlpack(self): 77 | """Produce an array from a DLPack Tensor without copying memory 78 | 79 | Returns 80 | ------- 81 | dlpack : DLPack tensor view of the array data 82 | """ 83 | ptr = ctypes.c_void_p() 84 | check_call(_LIB.DECORDArrayToDLPack(self.handle, ctypes.byref(ptr))) 85 | return ctypes.pythonapi.PyCapsule_New(ptr, _c_str_dltensor, _c_dlpack_deleter) 86 | 87 | 88 | def _make_array(handle, is_view): 89 | handle = ctypes.cast(handle, DECORDArrayHandle) 90 | return _CLASS_NDARRAY(handle, is_view) 91 | 92 | _DECORD_COMPATS = () 93 | 94 | def _reg_extension(cls, fcreate): 95 | global _DECORD_COMPATS 96 | _DECORD_COMPATS += (cls,) 97 | if fcreate: 98 | fret = lambda x: fcreate(_return_handle(x)) 99 | RETURN_SWITCH[cls._decord_tcode] = fret 100 | C_TO_PY_ARG_SWITCH[cls._decord_tcode] = _wrap_arg_func(fret, cls._decord_tcode) 101 | 102 | 103 | _CLASS_NDARRAY = None 104 | 105 | def _set_class_ndarray(cls): 106 | global _CLASS_NDARRAY 107 | _CLASS_NDARRAY = cls 108 | -------------------------------------------------------------------------------- /python/decord/_ffi/_ctypes/types.py: -------------------------------------------------------------------------------- 1 | """The C Types used in API.""" 2 | # pylint: disable=invalid-name 3 | from __future__ import absolute_import as _abs 4 | 5 | import ctypes 6 | from ..base import py_str, check_call, _LIB 7 | from ..runtime_ctypes import DECORDByteArray, TypeCode 8 | 9 | class DECORDValue(ctypes.Union): 10 | """DECORDValue in C API""" 11 | _fields_ = [("v_int64", ctypes.c_int64), 12 | ("v_float64", ctypes.c_double), 13 | ("v_handle", ctypes.c_void_p), 14 | ("v_str", ctypes.c_char_p)] 15 | 16 | 17 | DECORDPackedCFunc = ctypes.CFUNCTYPE( 18 | ctypes.c_int, 19 | ctypes.POINTER(DECORDValue), 20 | ctypes.POINTER(ctypes.c_int), 21 | ctypes.c_int, 22 | ctypes.c_void_p, 23 | ctypes.c_void_p) 24 | 25 | 26 | DECORDCFuncFinalizer = ctypes.CFUNCTYPE( 27 | None, 28 | ctypes.c_void_p) 29 | 30 | 31 | def _return_handle(x): 32 | """return handle""" 33 | handle = x.v_handle 34 | if not isinstance(handle, ctypes.c_void_p): 35 | handle = ctypes.c_void_p(handle) 36 | return handle 37 | 38 | def _return_bytes(x): 39 | """return handle""" 40 | handle = x.v_handle 41 | if not isinstance(handle, ctypes.c_void_p): 42 | handle = ctypes.c_void_p(handle) 43 | arr = ctypes.cast(handle, ctypes.POINTER(DECORDByteArray))[0] 44 | size = arr.size 45 | res = bytearray(size) 46 | rptr = (ctypes.c_byte * size).from_buffer(res) 47 | if not ctypes.memmove(rptr, arr.data, size): 48 | raise RuntimeError('memmove failed') 49 | return res 50 | 51 | def _wrap_arg_func(return_f, type_code): 52 | tcode = ctypes.c_int(type_code) 53 | def _wrap_func(x): 54 | check_call(_LIB.DECORDCbArgToReturn(ctypes.byref(x), tcode)) 55 | return return_f(x) 56 | return _wrap_func 57 | 58 | RETURN_SWITCH = { 59 | TypeCode.INT: lambda x: x.v_int64, 60 | TypeCode.FLOAT: lambda x: x.v_float64, 61 | TypeCode.HANDLE: _return_handle, 62 | TypeCode.NULL: lambda x: None, 63 | TypeCode.STR: lambda x: py_str(x.v_str), 64 | TypeCode.BYTES: _return_bytes 65 | } 66 | 67 | C_TO_PY_ARG_SWITCH = { 68 | TypeCode.INT: lambda x: x.v_int64, 69 | TypeCode.FLOAT: lambda x: x.v_float64, 70 | TypeCode.HANDLE: _return_handle, 71 | TypeCode.NULL: lambda x: None, 72 | TypeCode.STR: lambda x: py_str(x.v_str), 73 | TypeCode.BYTES: _return_bytes 74 | } 75 | -------------------------------------------------------------------------------- /python/decord/_ffi/_cy2/__init__.py: -------------------------------------------------------------------------------- 1 | """cython2 namespace""" 2 | -------------------------------------------------------------------------------- /python/decord/_ffi/_cy3/__init__.py: -------------------------------------------------------------------------------- 1 | """cython3 namespace""" 2 | -------------------------------------------------------------------------------- /python/decord/_ffi/_cython/.gitignore: -------------------------------------------------------------------------------- 1 | *.cpp 2 | -------------------------------------------------------------------------------- /python/decord/_ffi/_cython/core.pyx: -------------------------------------------------------------------------------- 1 | include "./base.pxi" 2 | # (minjie): Node and class module are not used in DECORD. 3 | #include "./node.pxi" 4 | include "./function.pxi" 5 | include "./ndarray.pxi" 6 | -------------------------------------------------------------------------------- /python/decord/_ffi/_cython/ndarray.pxi: -------------------------------------------------------------------------------- 1 | from ..runtime_ctypes import DECORDArrayHandle 2 | 3 | cdef const char* _c_str_dltensor = "dltensor" 4 | cdef const char* _c_str_used_dltensor = "used_dltensor" 5 | 6 | 7 | cdef void _c_dlpack_deleter(object pycaps): 8 | cdef DLManagedTensor* dltensor 9 | if pycapsule.PyCapsule_IsValid(pycaps, _c_str_dltensor): 10 | dltensor = pycapsule.PyCapsule_GetPointer(pycaps, _c_str_dltensor) 11 | DECORDDLManagedTensorCallDeleter(dltensor) 12 | 13 | 14 | def _from_dlpack(object dltensor): 15 | cdef DLManagedTensor* ptr 16 | cdef DLTensorHandle chandle 17 | if pycapsule.PyCapsule_IsValid(dltensor, _c_str_dltensor): 18 | ptr = pycapsule.PyCapsule_GetPointer(dltensor, _c_str_dltensor) 19 | CALL(DECORDArrayFromDLPack(ptr, &chandle)) 20 | # set name and destructor to be empty 21 | pycapsule.PyCapsule_SetDestructor(dltensor, NULL) 22 | pycapsule.PyCapsule_SetName(dltensor, _c_str_used_dltensor) 23 | return c_make_array(chandle, 0) 24 | raise ValueError("Expect a dltensor field, pycapsule.PyCapsule can only be consumed once") 25 | 26 | 27 | cdef class NDArrayBase: 28 | cdef DLTensor* chandle 29 | cdef int c_is_view 30 | 31 | cdef inline _set_handle(self, handle): 32 | cdef unsigned long long ptr 33 | if handle is None: 34 | self.chandle = NULL 35 | else: 36 | ptr = ctypes.cast(handle, ctypes.c_void_p).value 37 | self.chandle = (ptr) 38 | 39 | property _decord_handle: 40 | def __get__(self): 41 | return self.chandle 42 | 43 | property handle: 44 | def __get__(self): 45 | if self.chandle == NULL: 46 | return None 47 | else: 48 | return ctypes.cast( 49 | self.chandle, DECORDArrayHandle) 50 | 51 | def __set__(self, value): 52 | self._set_handle(value) 53 | 54 | def __init__(self, handle, is_view): 55 | self._set_handle(handle) 56 | self.c_is_view = is_view 57 | 58 | def __dealloc__(self): 59 | if self.c_is_view == 0: 60 | CALL(DECORDArrayFree(self.chandle)) 61 | 62 | def to_dlpack(self): 63 | """Produce an array from a DLPack Tensor without copying memory 64 | 65 | Returns 66 | ------- 67 | dlpack : DLPack tensor view of the array data 68 | """ 69 | cdef DLManagedTensor* dltensor 70 | if self.c_is_view != 0: 71 | raise ValueError("to_dlpack do not work with memory views") 72 | CALL(DECORDArrayToDLPack(self.chandle, &dltensor)) 73 | return pycapsule.PyCapsule_New(dltensor, _c_str_dltensor, _c_dlpack_deleter) 74 | 75 | 76 | cdef c_make_array(void* chandle, is_view): 77 | ret = _CLASS_NDARRAY(None, is_view) 78 | (ret).chandle = chandle 79 | return ret 80 | 81 | 82 | cdef _DECORD_COMPATS = () 83 | 84 | cdef _DECORD_EXT_RET = {} 85 | 86 | def _reg_extension(cls, fcreate): 87 | global _DECORD_COMPATS 88 | _DECORD_COMPATS += (cls,) 89 | if fcreate: 90 | _DECORD_EXT_RET[cls._decord_tcode] = fcreate 91 | 92 | 93 | def _make_array(handle, is_view): 94 | cdef unsigned long long ptr 95 | ptr = ctypes.cast(handle, ctypes.c_void_p).value 96 | return c_make_array(ptr, is_view) 97 | 98 | cdef object _CLASS_NDARRAY = None 99 | 100 | def _set_class_ndarray(cls): 101 | global _CLASS_NDARRAY 102 | _CLASS_NDARRAY = cls 103 | -------------------------------------------------------------------------------- /python/decord/_ffi/_cython/node.pxi: -------------------------------------------------------------------------------- 1 | from ... import _api_internal 2 | from ..base import string_types 3 | from ..node_generic import _set_class_node_base 4 | 5 | """Maps node type to its constructor""" 6 | NODE_TYPE = [] 7 | 8 | def _register_node(int index, object cls): 9 | """register node class""" 10 | while len(NODE_TYPE) <= index: 11 | NODE_TYPE.append(None) 12 | NODE_TYPE[index] = cls 13 | 14 | 15 | cdef inline object make_ret_node(void* chandle): 16 | global NODE_TYPE 17 | cdef int tindex 18 | cdef list node_type 19 | cdef object cls 20 | node_type = NODE_TYPE 21 | CALL(DECORDNodeGetTypeIndex(chandle, &tindex)) 22 | if tindex < len(node_type): 23 | cls = node_type[tindex] 24 | if cls is not None: 25 | obj = cls.__new__(cls) 26 | else: 27 | obj = NodeBase.__new__(NodeBase) 28 | else: 29 | obj = NodeBase.__new__(NodeBase) 30 | (obj).chandle = chandle 31 | return obj 32 | 33 | 34 | cdef class NodeBase: 35 | cdef void* chandle 36 | 37 | cdef _set_handle(self, handle): 38 | cdef unsigned long long ptr 39 | if handle is None: 40 | self.chandle = NULL 41 | else: 42 | ptr = handle.value 43 | self.chandle = (ptr) 44 | 45 | property handle: 46 | def __get__(self): 47 | if self.chandle == NULL: 48 | return None 49 | else: 50 | return ctypes_handle(self.chandle) 51 | 52 | def __set__(self, value): 53 | self._set_handle(value) 54 | 55 | def __dealloc__(self): 56 | CALL(DECORDNodeFree(self.chandle)) 57 | 58 | def __getattr__(self, name): 59 | cdef DECORDValue ret_val 60 | cdef int ret_type_code, ret_succ 61 | CALL(DECORDNodeGetAttr(self.chandle, c_str(name), 62 | &ret_val, &ret_type_code, &ret_succ)) 63 | if ret_succ == 0: 64 | raise AttributeError( 65 | "'%s' object has no attribute '%s'" % (type(self), name)) 66 | return make_ret(ret_val, ret_type_code) 67 | 68 | def __init_handle_by_constructor__(self, fconstructor, *args): 69 | """Initialize the handle by calling constructor function. 70 | 71 | Parameters 72 | ---------- 73 | fconstructor : Function 74 | Constructor function. 75 | 76 | args: list of objects 77 | The arguments to the constructor 78 | 79 | Note 80 | ---- 81 | We have a special calling convention to call constructor functions. 82 | So the return handle is directly set into the Node object 83 | instead of creating a new Node. 84 | """ 85 | cdef void* chandle 86 | ConstructorCall( 87 | (fconstructor).chandle, 88 | kNodeHandle, args, &chandle) 89 | self.chandle = chandle 90 | 91 | _set_class_node_base(NodeBase) 92 | -------------------------------------------------------------------------------- /python/decord/_ffi/base.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # pylint: disable=invalid-name 3 | """ctypes library and helper functions """ 4 | from __future__ import absolute_import 5 | 6 | import sys 7 | import os 8 | import ctypes 9 | import numpy as np 10 | from . import libinfo 11 | 12 | #---------------------------- 13 | # library loading 14 | #---------------------------- 15 | if sys.version_info[0] == 3: 16 | string_types = (str,) 17 | numeric_types = (float, int, np.float32, np.int32) 18 | # this function is needed for python3 19 | # to convert ctypes.char_p .value back to python str 20 | py_str = lambda x: x.decode('utf-8') 21 | else: 22 | string_types = (basestring,) 23 | numeric_types = (float, int, long, np.float32, np.int32) 24 | py_str = lambda x: x 25 | 26 | 27 | class DECORDError(Exception): 28 | """Error thrown by DECORD function""" 29 | pass # pylint: disable=unnecessary-pass 30 | 31 | class DECORDLimitReachedError(Exception): 32 | """Limit Reached Error thrown by DECORD function""" 33 | pass # pylint: disable=unnecessary-pass 34 | 35 | def _load_lib(): 36 | """Load libary by searching possible path.""" 37 | lib_path = libinfo.find_lib_path() 38 | os.environ['PATH'] += os.pathsep + os.path.dirname(lib_path[0]) 39 | lib = ctypes.CDLL(lib_path[0], ctypes.RTLD_GLOBAL) 40 | # DMatrix functions 41 | lib.DECORDGetLastError.restype = ctypes.c_char_p 42 | return lib, os.path.basename(lib_path[0]) 43 | 44 | # version number 45 | __version__ = libinfo.__version__ 46 | # library instance of nnvm 47 | _LIB, _LIB_NAME = _load_lib() 48 | 49 | # The FFI mode of DECORD 50 | _FFI_MODE = os.environ.get("DECORD_FFI", "auto") 51 | 52 | # enable stack trace or not 53 | _ENABLE_STACK_TRACE = int(os.environ.get("DECORD_ENABLE_STACK_TRACE", "0")) 54 | 55 | #---------------------------- 56 | # helper function in ctypes. 57 | #---------------------------- 58 | def check_call(ret): 59 | """Check the return value of C API call 60 | 61 | This function will raise exception when error occurs. 62 | Wrap every API call with this function 63 | 64 | Parameters 65 | ---------- 66 | ret : int 67 | return value from API calls 68 | """ 69 | if ret != 0: 70 | err_str = py_str(_LIB.DECORDGetLastError()) 71 | if not _ENABLE_STACK_TRACE: 72 | if 'Stack trace' in err_str: 73 | err_str = err_str.split('Stack trace')[0].strip() 74 | if 'recovered from nearest frames' in err_str: 75 | if 'Stack trace' in err_str: 76 | err_str = err_str.split('Stack trace')[0].strip() 77 | raise DECORDLimitReachedError(err_str) 78 | raise DECORDError(err_str) 79 | 80 | 81 | def c_str(string): 82 | """Create ctypes char * from a python string 83 | Parameters 84 | ---------- 85 | string : string type 86 | python string 87 | 88 | Returns 89 | ------- 90 | str : c_char_p 91 | A char pointer that can be passed to C API 92 | """ 93 | return ctypes.c_char_p(string.encode('utf-8')) 94 | 95 | 96 | def c_array(ctype, values): 97 | """Create ctypes array from a python array 98 | 99 | Parameters 100 | ---------- 101 | ctype : ctypes data type 102 | data type of the array we want to convert to 103 | 104 | values : tuple or list 105 | data content 106 | 107 | Returns 108 | ------- 109 | out : ctypes array 110 | Created ctypes array 111 | """ 112 | return (ctype * len(values))(*values) 113 | 114 | 115 | def decorate(func, fwrapped): 116 | """A wrapper call of decorator package, differs to call time 117 | 118 | Parameters 119 | ---------- 120 | func : function 121 | The original function 122 | 123 | fwrapped : function 124 | The wrapped function 125 | """ 126 | import decorator 127 | return decorator.decorate(func, fwrapped) 128 | -------------------------------------------------------------------------------- /python/decord/_ffi/libinfo.py: -------------------------------------------------------------------------------- 1 | """Library information.""" 2 | from __future__ import absolute_import 3 | import sys 4 | import os 5 | 6 | 7 | def find_lib_path(name=None, search_path=None, optional=False): 8 | """Find dynamic library files. 9 | 10 | Parameters 11 | ---------- 12 | name : list of str 13 | List of names to be found. 14 | 15 | Returns 16 | ------- 17 | lib_path : list(string) 18 | List of all found path to the libraries 19 | """ 20 | # See https://github.com/dmlc/tvm/issues/281 for some background. 21 | 22 | # NB: This will either be the source directory (if DECORD is run 23 | # inplace) or the install directory (if DECORD is installed). 24 | # An installed DECORD's curr_path will look something like: 25 | # $PREFIX/lib/python3.6/site-packages/decord/_ffi 26 | ffi_dir = os.path.dirname(os.path.abspath(os.path.expanduser(__file__))) 27 | source_dir = os.path.join(ffi_dir, "..", "..", "..") 28 | install_lib_dir = os.path.join(ffi_dir, "..", "..", "..", "..") 29 | 30 | dll_path = [] 31 | 32 | if os.environ.get('DECORD_LIBRARY_PATH', None): 33 | dll_path.append(os.environ['DECORD_LIBRARY_PATH']) 34 | 35 | if sys.platform.startswith('linux') and os.environ.get('LD_LIBRARY_PATH', None): 36 | dll_path.extend([p.strip() for p in os.environ['LD_LIBRARY_PATH'].split(":")]) 37 | elif sys.platform.startswith('darwin') and os.environ.get('DYLD_LIBRARY_PATH', None): 38 | dll_path.extend([p.strip() for p in os.environ['DYLD_LIBRARY_PATH'].split(":")]) 39 | 40 | # Pip lib directory 41 | dll_path.append(os.path.join(ffi_dir, "..")) 42 | # Default cmake build directory 43 | dll_path.append(os.path.join(source_dir, "build")) 44 | dll_path.append(os.path.join(source_dir, "build", "Release")) 45 | # Default make build directory 46 | dll_path.append(os.path.join(source_dir, "lib")) 47 | 48 | dll_path.append(install_lib_dir) 49 | 50 | dll_path = [os.path.abspath(x) for x in dll_path] 51 | if search_path is not None: 52 | if search_path is list: 53 | dll_path = dll_path + search_path 54 | else: 55 | dll_path.append(search_path) 56 | if name is not None: 57 | if isinstance(name, list): 58 | lib_dll_path = [] 59 | for n in name: 60 | lib_dll_path += [os.path.join(p, n) for p in dll_path] 61 | else: 62 | lib_dll_path = [os.path.join(p, name) for p in dll_path] 63 | else: 64 | if sys.platform.startswith('win32'): 65 | lib_dll_path = [os.path.join(p, 'libdecord.dll') for p in dll_path] +\ 66 | [os.path.join(p, 'decord.dll') for p in dll_path] 67 | elif sys.platform.startswith('darwin'): 68 | lib_dll_path = [os.path.join(p, 'libdecord.dylib') for p in dll_path] 69 | else: 70 | lib_dll_path = [os.path.join(p, 'libdecord.so') for p in dll_path] 71 | 72 | # try to find lib_dll_path 73 | lib_found = [p for p in lib_dll_path if os.path.exists(p) and os.path.isfile(p)] 74 | 75 | if not lib_found: 76 | message = ('Cannot find the files.\n' + 77 | 'List of candidates:\n' + 78 | str('\n'.join(lib_dll_path))) 79 | if not optional: 80 | raise RuntimeError(message) 81 | return None 82 | 83 | return lib_found 84 | 85 | 86 | # current version 87 | # We use the version of the incoming release for code 88 | # that is under development. 89 | # The following line is set by decord/python/update_version.py 90 | __version__ = "0.6.0" 91 | -------------------------------------------------------------------------------- /python/decord/base.py: -------------------------------------------------------------------------------- 1 | """Module for base types and utilities.""" 2 | from __future__ import absolute_import 3 | 4 | import warnings 5 | 6 | from ._ffi.base import DECORDError # pylint: disable=unused-import 7 | from ._ffi.function import _init_internal_api 8 | 9 | # A special symbol for selecting all nodes or edges. 10 | ALL = "__ALL__" 11 | 12 | def is_all(arg): 13 | """Return true if the argument is a special symbol for all nodes or edges.""" 14 | return isinstance(arg, str) and arg == ALL 15 | 16 | def decord_warning(msg): 17 | """Print out warning messages.""" 18 | warnings.warn(msg) 19 | 20 | _init_internal_api() 21 | -------------------------------------------------------------------------------- /python/decord/bridge/__init__.py: -------------------------------------------------------------------------------- 1 | """Deep Learning Framework bridges.""" 2 | from __future__ import absolute_import 3 | import threading 4 | 5 | from .mxnet import to_mxnet, from_mxnet 6 | from .torchdl import to_torch, from_torch 7 | from .tf import to_tensorflow, from_tensorflow 8 | from .tvm import to_tvm, from_tvm 9 | 10 | _BRIDGE_TYPES = { 11 | 'native': (lambda x: x, lambda x: x), 12 | 'mxnet': (to_mxnet, from_mxnet), 13 | 'torch': (to_torch, from_torch), 14 | 'tensorflow': (to_tensorflow, from_tensorflow), 15 | 'tvm': (to_tvm, from_tvm), 16 | } 17 | 18 | _CURRENT_BRIDGE = threading.local() 19 | _CURRENT_BRIDGE.type = 'native' 20 | _GLOBAL_BRIDGE_TYPE = 'native' # child threads will derive from the global type but not overwrite 21 | 22 | def reset_bridge(): 23 | _CURRENT_BRIDGE.type = 'native' 24 | if threading.current_thread().name == 'MainThread': 25 | _GLOBAL_BRIDGE_TYPE = 'native' 26 | 27 | def set_bridge(new_bridge): 28 | assert isinstance(new_bridge, str), ( 29 | "New bridge type must be str. Choices: {}".format(_BRIDGE_TYPES.keys())) 30 | assert new_bridge in _BRIDGE_TYPES.keys(), ( 31 | "valid bridges: {}".format(_BRIDGE_TYPES.keys())) 32 | global _CURRENT_BRIDGE 33 | _CURRENT_BRIDGE.type = new_bridge 34 | if threading.current_thread().name == 'MainThread': 35 | _GLOBAL_BRIDGE_TYPE = new_bridge 36 | 37 | def bridge_out(native_arr): 38 | if not hasattr(_CURRENT_BRIDGE, 'type'): 39 | _CURRENT_BRIDGE.type = _GLOBAL_BRIDGE_TYPE 40 | return _BRIDGE_TYPES[_CURRENT_BRIDGE.type][0](native_arr) 41 | 42 | def bridge_in(arr): 43 | if not hasattr(_CURRENT_BRIDGE, 'type'): 44 | _CURRENT_BRIDGE.type = _GLOBAL_BRIDGE_TYPE 45 | return _BRIDGE_TYPES[_CURRENT_BRIDGE.type][1](arr) 46 | 47 | class _BridgeScope(object): 48 | def __init__(self, bridge_type='native'): 49 | self._type = bridge_type 50 | self._prev = None 51 | 52 | def __enter__(self): 53 | global _CURRENT_BRIDGE 54 | if not hasattr(_CURRENT_BRIDGE, 'type'): 55 | _CURRENT_BRIDGE.type = _GLOBAL_BRIDGE_TYPE 56 | try: 57 | self._prev = _CURRENT_BRIDGE.type 58 | except AttributeError: 59 | self._prev = 'native' 60 | set_bridge(self._type) 61 | 62 | def __exit__(self, type, value, traceback): 63 | if self._prev != self._type: 64 | set_bridge(self._prev) 65 | 66 | def use_mxnet(): 67 | return _BridgeScope('mxnet') 68 | 69 | def use_torch(): 70 | return _BridgeScope('torch') 71 | 72 | def use_tensorflow(): 73 | return _BridgeScope('tensorflow') 74 | 75 | def use_tvm(): 76 | return _BridgeScope('tvm') 77 | -------------------------------------------------------------------------------- /python/decord/bridge/mxnet.py: -------------------------------------------------------------------------------- 1 | """DECORD MXNet bridge""" 2 | from __future__ import absolute_import 3 | 4 | from .._ffi._ctypes.ndarray import _from_dlpack 5 | from .utils import try_import 6 | 7 | def try_import_mxnet(): 8 | """Try import mxnet at runtime. 9 | 10 | Returns 11 | ------- 12 | mxnet module if found. Raise ImportError otherwise 13 | """ 14 | msg = "mxnet is required, you can install by pip.\n \ 15 | CPU: `pip install mxnet-mkl`, GPU: `pip install mxnet-cu100mkl`" 16 | return try_import('mxnet', msg) 17 | 18 | def to_mxnet(decord_arr): 19 | """from decord to mxnet, no copy""" 20 | mx = try_import_mxnet() 21 | return mx.nd.from_dlpack(decord_arr.to_dlpack()) 22 | 23 | def from_mxnet(mxnet_arr): 24 | """from mxnet to decord, no copy""" 25 | return _from_dlpack(mxnet_arr.to_dlpack_for_read()) 26 | -------------------------------------------------------------------------------- /python/decord/bridge/tf.py: -------------------------------------------------------------------------------- 1 | """DECORD tensorflow bridge""" 2 | from __future__ import absolute_import 3 | 4 | from .._ffi._ctypes.ndarray import _from_dlpack 5 | 6 | def try_import_tfdl(): 7 | """Try to import tensorflow dlpack at runtime. 8 | 9 | Returns 10 | ------- 11 | tensorflow dlpack module if found. Raise ImportError otherwise 12 | """ 13 | try: 14 | return __import__('tensorflow.experimental.dlpack', fromlist=['']) 15 | except ImportError as e: 16 | raise ImportError("tensorflow >= 2.2.0 is required.") 17 | 18 | def to_tensorflow(decord_arr): 19 | """from decord to tensorflow, no copy""" 20 | tfdl = try_import_tfdl() 21 | from tensorflow.python import pywrap_tfe 22 | from tensorflow.python.eager import context 23 | ctx = context.context() 24 | ctx.ensure_initialized() 25 | return pywrap_tfe.TFE_FromDlpackCapsule(decord_arr.to_dlpack(), ctx._handle) 26 | 27 | def from_tensorflow(tf_tensor): 28 | """from tensorflow to decord, no copy""" 29 | tfdl = try_import_tfdl() 30 | return _from_dlpack(tfdl.to_dlpack(tf_tensor)) 31 | -------------------------------------------------------------------------------- /python/decord/bridge/torchdl.py: -------------------------------------------------------------------------------- 1 | """DECORD Pytorch bridge""" 2 | from __future__ import absolute_import 3 | 4 | from .._ffi._ctypes.ndarray import _from_dlpack 5 | 6 | def try_import_torch(): 7 | """Try import torch at runtime. 8 | 9 | Returns 10 | ------- 11 | torch module if found. Raise ImportError otherwise 12 | """ 13 | message = "torch is required, you can install by pip: `pip install torch`" 14 | try: 15 | return __import__('torch.utils.dlpack', fromlist=['object']) 16 | except ImportError as e: 17 | if not message: 18 | raise e 19 | raise ImportError(message) 20 | 21 | def to_torch(decord_arr): 22 | """From decord to torch. 23 | The tensor will share the memory with the object represented in the dlpack. 24 | Note that each dlpack can only be consumed once.""" 25 | dlpack = try_import_torch() 26 | return dlpack.from_dlpack(decord_arr.to_dlpack()) 27 | 28 | def from_torch(tensor): 29 | """From torch to decord. 30 | The dlpack shares the tensors memory. 31 | Note that each dlpack can only be consumed once.""" 32 | dlpack = try_import_torch() 33 | return _from_dlpack(dlpack.to_dlpack(tensor)) -------------------------------------------------------------------------------- /python/decord/bridge/tvm.py: -------------------------------------------------------------------------------- 1 | """DECORD TVM bridge""" 2 | from __future__ import absolute_import 3 | 4 | from .._ffi._ctypes.ndarray import _from_dlpack 5 | from .utils import try_import 6 | 7 | def try_import_tvm(): 8 | """Try import tvm at runtime. 9 | 10 | Returns 11 | ------- 12 | tvm module if found. Raise ImportError otherwise 13 | """ 14 | msg = "tvm is required, for installation guide, please checkout:\n \ 15 | https://tvm.apache.org/docs/install/index.html" 16 | return try_import('tvm', msg) 17 | 18 | def to_tvm(decord_arr): 19 | """from decord to tvm, no copy""" 20 | tvm = try_import_tvm() 21 | return tvm.nd.from_dlpack(decord_arr.to_dlpack()) 22 | 23 | def from_tvm(tvm_arr): 24 | """from tvm to decord, no copy""" 25 | return _from_dlpack(tvm_arr.to_dlpack()) 26 | -------------------------------------------------------------------------------- /python/decord/bridge/utils.py: -------------------------------------------------------------------------------- 1 | """Bridge utils.""" 2 | from __future__ import absolute_import 3 | 4 | def try_import(package, message=None): 5 | """Try import specified package, with custom message support. 6 | Parameters 7 | ---------- 8 | package : str 9 | The name of the targeting package. 10 | message : str, default is None 11 | If not None, this function will raise customized error message when import error is found. 12 | Returns 13 | ------- 14 | module if found, raise ImportError otherwise 15 | """ 16 | try: 17 | return __import__(package) 18 | except ImportError as e: 19 | if not message: 20 | raise e 21 | raise ImportError(message) 22 | -------------------------------------------------------------------------------- /python/decord/data/__init__.py: -------------------------------------------------------------------------------- 1 | """Decord data specific functions""" 2 | -------------------------------------------------------------------------------- /python/decord/data/kinetics/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmlc/decord/d2e56190286ae394032a8141885f76d5372bd44b/python/decord/data/kinetics/__init__.py -------------------------------------------------------------------------------- /python/decord/data/transforms/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmlc/decord/d2e56190286ae394032a8141885f76d5372bd44b/python/decord/data/transforms/__init__.py -------------------------------------------------------------------------------- /python/decord/function/__init__.py: -------------------------------------------------------------------------------- 1 | """Decord builtin functors""" 2 | # pylint: disable=redefined-builtin 3 | from __future__ import absolute_import 4 | 5 | from .base import * 6 | -------------------------------------------------------------------------------- /python/decord/function/base.py: -------------------------------------------------------------------------------- 1 | """Built-in function base class""" 2 | from __future__ import absolute_import 3 | 4 | __all__ = ['BuiltinFunction', 'BundledFunction'] 5 | 6 | class BuiltinFunction(object): 7 | """Base builtin function class.""" 8 | @property 9 | def name(self): 10 | """Return the name of this builtin function.""" 11 | raise NotImplementedError 12 | 13 | class BundledFunction(object): 14 | """A utility class that bundles multiple functions. 15 | 16 | Parameters 17 | ---------- 18 | fn_list : list of callable 19 | The function list. 20 | """ 21 | def __init__(self, fn_list): 22 | self.fn_list = fn_list 23 | 24 | def __call__(self, *args, **kwargs): 25 | """Regular computation of this builtin function 26 | 27 | This will be used when optimization is not available and should 28 | ONLY be called by DECORD framework. 29 | """ 30 | ret = {} 31 | for fn in self.fn_list: 32 | ret.update(fn(*args, **kwargs)) 33 | return ret 34 | 35 | @property 36 | def name(self): 37 | """Return the name.""" 38 | return "bundled" 39 | -------------------------------------------------------------------------------- /python/decord/logging.py: -------------------------------------------------------------------------------- 1 | """DECORD logging module. 2 | 3 | You can adjust the logging level for ffmpeg. 4 | """ 5 | from ._ffi.function import _init_api 6 | 7 | QUIET = -8 8 | PANIC = 0 9 | FATAL = 8 10 | ERROR = 16 11 | WARNING = 24 12 | INFO = 32 13 | VERBOSE = 40 14 | DEBUG = 48 15 | TRACE = 56 16 | 17 | # Mimicking stdlib. 18 | CRITICAL = FATAL 19 | 20 | def set_level(lvl=ERROR): 21 | _CAPI_SetLoggingLevel(lvl) 22 | 23 | _init_api("decord.logging") 24 | -------------------------------------------------------------------------------- /python/decord/ndarray.py: -------------------------------------------------------------------------------- 1 | """DECORD Runtime NDArray API. 2 | 3 | decord.ndarray provides a minimum runtime array structure to be 4 | used with C++ library. 5 | """ 6 | # pylint: disable=invalid-name,unused-import 7 | from __future__ import absolute_import as _abs 8 | 9 | import ctypes 10 | import functools 11 | import operator 12 | import numpy as _np 13 | 14 | from ._ffi.ndarray import DECORDContext, DECORDType, NDArrayBase 15 | from ._ffi.ndarray import context, empty, from_dlpack, numpyasarray 16 | from ._ffi.ndarray import _set_class_ndarray 17 | 18 | class NDArray(NDArrayBase): 19 | """Lightweight NDArray class for DECORD framework.""" 20 | def __len__(self): 21 | return functools.reduce(operator.mul, self.shape, 1) 22 | 23 | def cpu(dev_id=0): 24 | """Construct a CPU device 25 | 26 | Parameters 27 | ---------- 28 | dev_id : int, optional 29 | The integer device id 30 | 31 | Returns 32 | ------- 33 | ctx : DECORDContext 34 | The created context 35 | """ 36 | return DECORDContext(1, dev_id) 37 | 38 | def gpu(dev_id=0): 39 | """Construct a GPU device 40 | 41 | Parameters 42 | ---------- 43 | dev_id : int, optional 44 | The integer device id 45 | 46 | Returns 47 | ------- 48 | ctx : DECORDContext 49 | The created context 50 | """ 51 | return DECORDContext(2, dev_id) 52 | 53 | def array(arr, ctx=cpu(0)): 54 | """Create an array from source arr. 55 | 56 | Parameters 57 | ---------- 58 | arr : numpy.ndarray 59 | The array to be copied from 60 | 61 | ctx : DECORDContext, optional 62 | The device context to create the array 63 | 64 | Returns 65 | ------- 66 | ret : NDArray 67 | The created array 68 | """ 69 | if not isinstance(arr, (_np.ndarray, NDArray)): 70 | arr = _np.array(arr) 71 | return empty(arr.shape, arr.dtype, ctx).copyfrom(arr) 72 | 73 | def zerocopy_from_numpy(np_data): 74 | """Create an array that shares the given numpy data. 75 | 76 | Parameters 77 | ---------- 78 | np_data : numpy.ndarray 79 | The numpy data 80 | 81 | Returns 82 | ------- 83 | NDArray 84 | The array 85 | """ 86 | arr, _ = numpyasarray(np_data) 87 | handle = ctypes.pointer(arr) 88 | return NDArray(handle, is_view=True) 89 | 90 | _set_class_ndarray(NDArray) 91 | -------------------------------------------------------------------------------- /python/decord/video_loader.py: -------------------------------------------------------------------------------- 1 | """Video Loader.""" 2 | from __future__ import absolute_import 3 | 4 | import ctypes 5 | import numpy as np 6 | 7 | from ._ffi.base import c_array, c_str 8 | from ._ffi.function import _init_api 9 | from .base import DECORDError 10 | from . import ndarray as _nd 11 | from .ndarray import DECORDContext 12 | from .bridge import bridge_out 13 | 14 | VideoLoaderHandle = ctypes.c_void_p 15 | 16 | 17 | class VideoLoader(object): 18 | """Multiple video loader with advanced shuffling and batching methods. 19 | 20 | Parameters 21 | ---------- 22 | uris : list of str 23 | List of video paths. 24 | ctx : decord.Context or list of Context 25 | The context to decode the video file, can be decord.cpu() or decord.gpu(). 26 | If ctx is a list, videos will be evenly split over many ctxs. 27 | shape : tuple 28 | Returned shape of the batch images, e.g., (2, 320, 240, 3) as (Batch, H, W, 3) 29 | interval : int 30 | Intra-batch frame interval. 31 | skip : int 32 | Inter-batch frame interval. 33 | shuffle : int 34 | Shuffling strategy. Can be 35 | `0`: all sequential, no seeking, following initial filename order 36 | `1`: random filename order, no random access for each video, very efficient 37 | `2`: random order 38 | `3`: random frame access in each video only. 39 | 40 | """ 41 | def __init__(self, uris, ctx, shape, interval, skip, shuffle, prefetch=0): 42 | self._handle = None 43 | assert isinstance(uris, (list, tuple)) 44 | assert (len(uris) > 0) 45 | uri = ','.join(x.strip() for x in uris) 46 | if isinstance(ctx, DECORDContext): 47 | ctx = [ctx] 48 | for _ctx in ctx: 49 | assert isinstance(_ctx, DECORDContext) 50 | device_types = _nd.array([x.device_type for x in ctx]) 51 | device_ids = _nd.array([x.device_id for x in ctx]) 52 | assert isinstance(shape, (list, tuple)) 53 | assert len(shape) == 4, "expected shape: [bs, height, width, 3], given {}".format(shape) 54 | self._handle = _CAPI_VideoLoaderGetVideoLoader( 55 | uri, device_types, device_ids, shape[0], shape[1], shape[2], shape[3], interval, skip, shuffle, prefetch) 56 | assert self._handle is not None 57 | self._len = _CAPI_VideoLoaderLength(self._handle) 58 | self._curr = 0 59 | 60 | def __del__(self): 61 | if self._handle: 62 | _CAPI_VideoLoaderFree(self._handle) 63 | 64 | def __len__(self): 65 | """Get number of batches in each epoch. 66 | 67 | Returns 68 | ------- 69 | int 70 | number of batches in each epoch. 71 | 72 | """ 73 | return self._len 74 | 75 | def reset(self): 76 | """Reset loader for next epoch. 77 | 78 | """ 79 | assert self._handle is not None 80 | self._curr = 0 81 | _CAPI_VideoLoaderReset(self._handle) 82 | 83 | def __next__(self): 84 | """Get the next batch. 85 | 86 | Returns 87 | ------- 88 | ndarray, ndarray 89 | Frame data and corresponding indices in videos. 90 | Indices are [(n0, k0), (n1, k1)...] where n0 is the index of video, k0 is the index 91 | of frame in video n0. 92 | 93 | """ 94 | assert self._handle is not None 95 | # avoid calling CAPI HasNext 96 | if self._curr >= self._len: 97 | raise StopIteration 98 | _CAPI_VideoLoaderNext(self._handle) 99 | data = _CAPI_VideoLoaderNextData(self._handle) 100 | indices = _CAPI_VideoLoaderNextIndices(self._handle) 101 | self._curr += 1 102 | return bridge_out(data), bridge_out(indices) 103 | 104 | def next(self): 105 | """Alias of __next__ for python2. 106 | 107 | """ 108 | return self.__next__() 109 | 110 | def __iter__(self): 111 | assert self._handle is not None 112 | # if (self._curr >= self._len): 113 | # self.reset() 114 | # else: 115 | # err_msg = "Call __iter__ of VideoLoader during previous iteration is forbidden. \ 116 | # Consider using cached iterator by 'vl = iter(video_loader)' and reuse it." 117 | # raise RuntimeError(err_msg) 118 | return self 119 | 120 | 121 | _init_api("decord.video_loader") 122 | -------------------------------------------------------------------------------- /python/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import sys, os, platform, sysconfig 4 | import shutil 5 | import glob 6 | 7 | from setuptools import find_packages 8 | from setuptools.dist import Distribution 9 | 10 | # need to use distutils.core for correct placement of cython dll 11 | if '--inplace' in sys.argv: 12 | from distutils.core import setup 13 | from distutils.extension import Extension 14 | else: 15 | from setuptools import setup 16 | from setuptools.extension import Extension 17 | 18 | class BinaryDistribution(Distribution): 19 | def has_ext_modules(self): 20 | return platform.system() in ('Darwin', 'Linux') 21 | 22 | CURRENT_DIR = os.path.dirname(__file__) 23 | 24 | def get_lib_path(): 25 | """Get library path, name and version""" 26 | # We can not import `libinfo.py` in setup.py directly since __init__.py 27 | # Will be invoked which introduces dependencies 28 | libinfo_py = os.path.join(CURRENT_DIR, './decord/_ffi/libinfo.py') 29 | libinfo = {'__file__': libinfo_py} 30 | exec(compile(open(libinfo_py, "rb").read(), libinfo_py, 'exec'), libinfo, libinfo) 31 | version = libinfo['__version__'] 32 | 33 | lib_path = libinfo['find_lib_path']() 34 | libs = [lib_path[0]] 35 | 36 | return libs, version 37 | 38 | LIBS, VERSION = get_lib_path() 39 | 40 | include_libs = False 41 | wheel_include_libs = False 42 | if "bdist_wheel" in sys.argv or os.getenv('CONDA_BUILD'): 43 | wheel_include_libs = True 44 | else: 45 | include_libs = True 46 | 47 | setup_kwargs = {} 48 | 49 | # For bdist_wheel only 50 | if wheel_include_libs: 51 | with open("MANIFEST.in", "w") as fo: 52 | for path in LIBS: 53 | shutil.copy(path, os.path.join(CURRENT_DIR, 'decord')) 54 | _, libname = os.path.split(path) 55 | fo.write("include decord/%s\n" % libname) 56 | setup_kwargs = { 57 | "include_package_data": True 58 | } 59 | 60 | # For source tree setup 61 | # Conda build also includes the binary library 62 | if include_libs: 63 | rpath = [os.path.relpath(path, CURRENT_DIR) for path in LIBS] 64 | setup_kwargs = { 65 | "include_package_data": True, 66 | "data_files": [('decord', rpath)] 67 | } 68 | 69 | setup( 70 | name='decord', 71 | version=VERSION, 72 | description='Decord Video Loader', 73 | zip_safe=False, 74 | maintainer='Decord committers', 75 | maintainer_email='cheungchih@gmail.com', 76 | packages=find_packages(), 77 | install_requires=[ 78 | 'numpy>=1.14.0', 79 | ], 80 | url='https://github.com/dmlc/decord', 81 | distclass=BinaryDistribution, 82 | classifiers=[ 83 | 'Development Status :: 3 - Alpha', 84 | 'Programming Language :: Python :: 3', 85 | 'License :: OSI Approved :: Apache Software License', 86 | ], 87 | license='APACHE', 88 | **setup_kwargs 89 | ) 90 | 91 | if wheel_include_libs: 92 | # Wheel cleanup 93 | os.remove("MANIFEST.in") 94 | for path in LIBS: 95 | _, libname = os.path.split(path) 96 | os.remove("decord/%s" % libname) 97 | -------------------------------------------------------------------------------- /src/audio/audio_interface.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Yin, Weisu on 1/15/21. 3 | // 4 | 5 | #include "audio_reader.h" 6 | 7 | 8 | #include 9 | #include 10 | 11 | 12 | namespace decord { 13 | 14 | AudioReaderPtr GetAudioReader(std::string fn, int sampleRate, DLContext ctx, int io_type, bool mono) { 15 | std::shared_ptr ptr; 16 | ptr = std::make_shared(fn, sampleRate, ctx, io_type, mono); 17 | return ptr; 18 | } 19 | 20 | 21 | namespace runtime { 22 | DECORD_REGISTER_GLOBAL("audio_reader._CAPI_AudioReaderGetAudioReader") 23 | .set_body([](DECORDArgs args, DECORDRetValue* rv) { 24 | std::string fn = args[0]; 25 | int device_type = args[1]; 26 | int device_id = args[2]; 27 | int sampleRate = args[3]; 28 | int io_type = args[4]; 29 | bool mono = int(args[5]) == 1 ? true : false; 30 | 31 | // TODO: add io type 32 | DLContext ctx; 33 | ctx.device_type = static_cast(device_type); 34 | ctx.device_id = device_id; 35 | auto reader = new AudioReader(fn, sampleRate, ctx, io_type, mono); 36 | if (reader->GetNumSamplesPerChannel() <= 0) { 37 | *rv = nullptr; 38 | return; 39 | } 40 | AudioReaderInterfaceHandle handle = static_cast(reader); 41 | *rv = handle; 42 | }); 43 | 44 | DECORD_REGISTER_GLOBAL("audio_reader._CAPI_AudioReaderGetNDArray") 45 | .set_body([](DECORDArgs args, DECORDRetValue* rv) { 46 | AudioReaderInterfaceHandle handle = args[0]; 47 | NDArray array = static_cast(handle)->GetNDArray(); 48 | *rv = array; 49 | }); 50 | 51 | DECORD_REGISTER_GLOBAL("audio_reader._CAPI_AudioReaderGetNumPaddingSamples") 52 | .set_body([](DECORDArgs args, DECORDRetValue* rv) { 53 | AudioReaderInterfaceHandle handle = args[0]; 54 | int numPaddingSamples = static_cast(handle)->GetNumPaddingSamples(); 55 | *rv = numPaddingSamples; 56 | }); 57 | 58 | DECORD_REGISTER_GLOBAL("audio_reader._CAPI_AudioReaderGetDuration") 59 | .set_body([](DECORDArgs args, DECORDRetValue* rv) { 60 | AudioReaderInterfaceHandle handle = args[0]; 61 | double duration = static_cast(handle)->GetDuration(); 62 | *rv = duration; 63 | }); 64 | 65 | DECORD_REGISTER_GLOBAL("audio_reader._CAPI_AudioReaderGetNumSamplesPerChannel") 66 | .set_body([](DECORDArgs args, DECORDRetValue* rv) { 67 | AudioReaderInterfaceHandle handle = args[0]; 68 | int64_t numSamplesPerChannel = static_cast(handle)->GetNumSamplesPerChannel(); 69 | *rv = numSamplesPerChannel; 70 | }); 71 | 72 | DECORD_REGISTER_GLOBAL("audio_reader._CAPI_AudioReaderGetNumChannels") 73 | .set_body([](DECORDArgs args, DECORDRetValue* rv) { 74 | AudioReaderInterfaceHandle handle = args[0]; 75 | int numChannels = static_cast(handle)->GetNumChannels(); 76 | *rv = numChannels; 77 | }); 78 | 79 | DECORD_REGISTER_GLOBAL("audio_reader._CAPI_AudioReaderGetInfo") 80 | .set_body([](DECORDArgs args, DECORDRetValue* rv) { 81 | AudioReaderInterfaceHandle handle = args[0]; 82 | static_cast(handle)->GetInfo(); 83 | }); 84 | 85 | DECORD_REGISTER_GLOBAL("audio_reader._CAPI_AudioReaderFree") 86 | .set_body([](DECORDArgs args, DECORDRetValue* rv) { 87 | AudioReaderInterfaceHandle handle = args[0]; 88 | auto p = static_cast(handle); 89 | if (p) delete p; 90 | }); 91 | } 92 | } -------------------------------------------------------------------------------- /src/audio/audio_reader.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Yin, Weisu on 1/6/21. 3 | // 4 | 5 | #ifndef DECORD_AUDIO_READER_H_ 6 | #define DECORD_AUDIO_READER_H_ 7 | 8 | #include 9 | 10 | #include "../../include/decord/audio_interface.h" 11 | 12 | namespace decord { 13 | 14 | class AudioReader: public AudioReaderInterface { 15 | public: 16 | AudioReader(std::string fn, int sampleRate, DLContext ctx, int io_type=kNormal, bool mono=true); 17 | ~AudioReader(); 18 | NDArray GetNDArray(); 19 | int GetNumPaddingSamples(); 20 | double GetDuration(); 21 | int64_t GetNumSamplesPerChannel(); 22 | int GetNumChannels(); 23 | void GetInfo(); 24 | private: 25 | int Decode(std::string fn, int io_type); 26 | void DecodePacket(AVPacket *pPacket, AVCodecContext *pCodecContext, AVFrame *pFrame, int streamIndex); 27 | void HandleFrame(AVCodecContext *pCodecContext, AVFrame *pFrame); 28 | void DrainDecoder(AVCodecContext *pCodecContext, AVFrame *pFrame); 29 | void InitSWR(AVCodecContext *pCodecContext); 30 | void ToNDArray(); 31 | void SaveToVector(float** buffer, int numChannels, int numSamples); 32 | 33 | DLContext ctx; 34 | std::unique_ptr io_ctx_; // avio context for raw memory access 35 | AVFormatContext *pFormatContext; 36 | struct SwrContext* swr; 37 | // AVCodec* pCodec; 38 | AVCodecParameters* pCodecParameters; 39 | AVCodecContext * pCodecContext; 40 | int audioStreamIndex; 41 | // std::vector> audios; 42 | std::vector> outputVector; 43 | NDArray output; 44 | // padding is the start time in seconds of the first audio sample 45 | double padding; 46 | std::string filename; 47 | int originalSampleRate; 48 | int targetSampleRate; 49 | int numChannels; 50 | bool mono; 51 | int totalSamplesPerChannel; 52 | int totalConvertedSamplesPerChannel; 53 | double timeBase; 54 | double duration; 55 | }; 56 | 57 | } 58 | 59 | #endif //DECORD_AUDIO_INTERFACE_H 60 | -------------------------------------------------------------------------------- /src/improc/README.md: -------------------------------------------------------------------------------- 1 | # Image Processing Functions 2 | 3 | ## Essentials 4 | 5 | - Converting from original YUV to RGB 6 | - Resizing to reduce memory footprint. 7 | 8 | ### Implementation 9 | 10 | - CPU: FFMPEG filter graph 11 | - GPU: CUDA kernels need to be implemented 12 | -------------------------------------------------------------------------------- /src/improc/improc.cu: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file improc.cu 4 | * \brief CUDA image processing kernels 5 | */ 6 | 7 | #include "improc.h" 8 | #include 9 | // #include 10 | 11 | namespace decord { 12 | namespace cuda { 13 | namespace detail { 14 | 15 | template 16 | struct YUV { 17 | T y, u, v; 18 | }; 19 | 20 | __constant__ float yuv2rgb_mat[9] = { 21 | 1.164383f, 0.0f, 1.596027f, 22 | 1.164383f, -0.391762f, -0.812968f, 23 | 1.164383f, 2.017232f, 0.0f 24 | }; 25 | 26 | __device__ float clip(float x, float max) { 27 | return fmin(fmax(x, 0.0f), max); 28 | } 29 | 30 | template 31 | __device__ T convert(const float x) { 32 | return static_cast(x); 33 | } 34 | 35 | template<> 36 | __device__ half convert(const float x) { 37 | return __float2half(x); 38 | } 39 | 40 | template<> 41 | __device__ uint8_t convert(const float x) { 42 | return static_cast(roundf(x)); 43 | } 44 | 45 | template 46 | __device__ void yuv2rgb(const YUV& yuv, RGB_T* rgb, 47 | size_t stride, bool normalized) { 48 | auto mult = normalized ? 1.0f : 255.0f; 49 | auto y = (static_cast(yuv.y) - 16.0f/255) * mult; 50 | auto u = (static_cast(yuv.u) - 128.0f/255) * mult; 51 | auto v = (static_cast(yuv.v) - 128.0f/255) * mult; 52 | 53 | auto& m = yuv2rgb_mat; 54 | 55 | // could get tricky with a lambda, but this branch seems faster 56 | float r, g, b; 57 | if (normalized) { 58 | r = clip(y*m[0] + u*m[1] + v*m[2], 1.0); 59 | g = clip(y*m[3] + u*m[4] + v*m[5], 1.0); 60 | b = clip(y*m[6] + u*m[7] + v*m[8], 1.0); 61 | } else { 62 | r = clip(y*m[0] + u*m[1] + v*m[2], 255.0); 63 | g = clip(y*m[3] + u*m[4] + v*m[5], 255.0); 64 | b = clip(y*m[6] + u*m[7] + v*m[8], 255.0); 65 | } 66 | 67 | rgb[0] = convert(r); 68 | rgb[stride] = convert(g); 69 | rgb[stride*2] = convert(b); 70 | } 71 | 72 | template 73 | __global__ void process_frame_kernel( 74 | cudaTextureObject_t luma, cudaTextureObject_t chroma, 75 | T* dst, uint16_t input_width, uint16_t input_height, 76 | uint16_t output_width, uint16_t output_height, float fx, float fy) { 77 | 78 | const int dst_x = blockIdx.x * blockDim.x + threadIdx.x; 79 | const int dst_y = blockIdx.y * blockDim.y + threadIdx.y; 80 | 81 | if (dst_x >= output_width || dst_y >= output_height) 82 | return; 83 | 84 | auto src_x = static_cast(dst_x) * fx; 85 | 86 | auto src_y = static_cast(dst_y) * fy; 87 | 88 | YUV yuv; 89 | yuv.y = tex2D(luma, src_x + 0.5, src_y + 0.5); 90 | auto uv = tex2D(chroma, (src_x / 2) + 0.5, (src_y / 2) + 0.5); 91 | yuv.u = uv.x; 92 | yuv.v = uv.y; 93 | 94 | T* out = dst + (dst_x + dst_y * output_width) * 3; 95 | yuv2rgb(yuv, out, 1, false); 96 | } 97 | } // namespace detail 98 | 99 | int DivUp(int total, int grain) { 100 | return (total + grain - 1) / grain; 101 | } 102 | 103 | void ProcessFrame(cudaTextureObject_t chroma, cudaTextureObject_t luma, 104 | uint8_t* dst, cudaStream_t stream, uint16_t input_width, uint16_t input_height, 105 | int output_width, int output_height) { 106 | // resize factor 107 | auto fx = static_cast(input_width) / output_width; 108 | auto fy = static_cast(input_height) / output_height; 109 | 110 | dim3 block(32, 8); 111 | dim3 grid(DivUp(output_width, block.x), DivUp(output_height, block.y)); 112 | 113 | detail::process_frame_kernel<<>> 114 | (luma, chroma, dst, input_width, input_height, output_width, output_height, fx, fy); 115 | } 116 | } // namespace cuda 117 | } // namespace decord 118 | -------------------------------------------------------------------------------- /src/improc/improc.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file improc.h 4 | * \brief Image processing functions 5 | */ 6 | 7 | #ifndef DECORD_IMPROC_IMPROC_H_ 8 | #define DECORD_IMPROC_IMPROC_H_ 9 | 10 | #include 11 | 12 | namespace decord { 13 | namespace cuda { 14 | 15 | #ifdef DECORD_USE_CUDA 16 | 17 | void ProcessFrame(cudaTextureObject_t chroma, cudaTextureObject_t luma, 18 | uint8_t* dst, cudaStream_t stream, uint16_t input_width, uint16_t input_height, 19 | int output_width, int output_height); 20 | #endif 21 | } // namespace imp 22 | } // namespace decord 23 | 24 | 25 | #endif // DECORD_IMPROC_IMPROC_H_ 26 | -------------------------------------------------------------------------------- /src/runtime/cpu_device_api.cc: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file cpu_device_api.cc 4 | */ 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "workspace_pool.h" 12 | 13 | #ifdef __ANDROID__ 14 | #include 15 | #endif 16 | 17 | namespace decord { 18 | namespace runtime { 19 | class CPUDeviceAPI final : public DeviceAPI { 20 | public: 21 | void SetDevice(DECORDContext ctx) final {} 22 | void GetAttr(DECORDContext ctx, DeviceAttrKind kind, DECORDRetValue* rv) final { 23 | if (kind == kExist) { 24 | *rv = 1; 25 | } 26 | } 27 | void* AllocDataSpace(DECORDContext ctx, 28 | size_t nbytes, 29 | size_t alignment, 30 | DECORDType type_hint) final { 31 | void* ptr; 32 | #if _MSC_VER 33 | ptr = _aligned_malloc(nbytes, alignment); 34 | if (ptr == nullptr) throw std::bad_alloc(); 35 | #elif defined(_LIBCPP_SGX_CONFIG) || (defined(__ANDROID__) && __ANDROID_API__ < 16) 36 | ptr = memalign(alignment, nbytes); 37 | if (ptr == nullptr) throw std::bad_alloc(); 38 | #else 39 | // posix_memalign is available in android ndk since __ANDROID_API__ >= 16 40 | int ret = posix_memalign(&ptr, alignment, nbytes); 41 | if (ret != 0) throw std::bad_alloc(); 42 | #endif 43 | return ptr; 44 | } 45 | 46 | void FreeDataSpace(DECORDContext ctx, void* ptr) final { 47 | #if _MSC_VER 48 | _aligned_free(ptr); 49 | #else 50 | free(ptr); 51 | #endif 52 | } 53 | 54 | void CopyDataFromTo(const void* from, 55 | size_t from_offset, 56 | void* to, 57 | size_t to_offset, 58 | size_t size, 59 | DECORDContext ctx_from, 60 | DECORDContext ctx_to, 61 | DECORDType type_hint, 62 | DECORDStreamHandle stream) final { 63 | memcpy(static_cast(to) + to_offset, 64 | static_cast(from) + from_offset, 65 | size); 66 | } 67 | 68 | void StreamSync(DECORDContext ctx, DECORDStreamHandle stream) final { 69 | } 70 | 71 | void* AllocWorkspace(DECORDContext ctx, size_t size, DECORDType type_hint) final; 72 | void FreeWorkspace(DECORDContext ctx, void* data) final; 73 | 74 | static const std::shared_ptr& Global() { 75 | static std::shared_ptr inst = 76 | std::make_shared(); 77 | return inst; 78 | } 79 | }; 80 | 81 | struct CPUWorkspacePool : public WorkspacePool { 82 | CPUWorkspacePool() : 83 | WorkspacePool(kDLCPU, CPUDeviceAPI::Global()) {} 84 | }; 85 | 86 | void* CPUDeviceAPI::AllocWorkspace(DECORDContext ctx, 87 | size_t size, 88 | DECORDType type_hint) { 89 | return dmlc::ThreadLocalStore::Get() 90 | ->AllocWorkspace(ctx, size); 91 | } 92 | 93 | void CPUDeviceAPI::FreeWorkspace(DECORDContext ctx, void* data) { 94 | dmlc::ThreadLocalStore::Get()->FreeWorkspace(ctx, data); 95 | } 96 | 97 | DECORD_REGISTER_GLOBAL("device_api.cpu") 98 | .set_body([](DECORDArgs args, DECORDRetValue* rv) { 99 | DeviceAPI* ptr = CPUDeviceAPI::Global().get(); 100 | *rv = static_cast(ptr); 101 | }); 102 | } // namespace runtime 103 | } // namespace decord 104 | -------------------------------------------------------------------------------- /src/runtime/cuda/cuda_common.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | /*! 21 | * Copyright (c) 2017 by Contributors 22 | * \file cuda_common.h 23 | * \brief Common utilities for CUDA 24 | */ 25 | #ifndef DECORD_RUNTIME_CUDA_CUDA_COMMON_H_ 26 | #define DECORD_RUNTIME_CUDA_CUDA_COMMON_H_ 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include "../workspace_pool.h" 33 | 34 | namespace decord { 35 | namespace runtime { 36 | 37 | #ifdef __cuda_cuda_h__ 38 | inline bool check_cuda_call(CUresult e, int iLine, const char *szFile) { 39 | if (e != CUDA_SUCCESS) { 40 | const char* err; 41 | cuGetErrorString(e, &err); 42 | std::cerr << "CUDA error " << e << " at line " << iLine << " in file " << szFile 43 | << ": " << err << std::endl; 44 | return false; 45 | } 46 | return true; 47 | } 48 | #endif 49 | 50 | #ifdef __CUDA_RUNTIME_H__ 51 | inline bool check_cuda_call(cudaError_t e, int iLine, const char *szFile) { 52 | if (e != cudaSuccess) { 53 | std::cerr << "CUDA runtime error " << e << " at line " << iLine 54 | << " in file " << szFile 55 | << ": " << cudaGetErrorString(e) 56 | << std::endl; 57 | return false; 58 | } 59 | return true; 60 | } 61 | #endif 62 | 63 | #define CUDA_DRIVER_CALL(x) \ 64 | { \ 65 | CUresult result = x; \ 66 | if (result != CUDA_SUCCESS && result != CUDA_ERROR_DEINITIALIZED) { \ 67 | const char *msg; \ 68 | cuGetErrorName(result, &msg); \ 69 | LOG(FATAL) \ 70 | << "CUDAError: " #x " failed with error: " << msg \ 71 | << " at line: " << __LINE__ << " in file: " << __FILE__; \ 72 | } \ 73 | } 74 | 75 | #define CUDA_CALL(func) \ 76 | { \ 77 | cudaError_t e = (func); \ 78 | CHECK(e == cudaSuccess || e == cudaErrorCudartUnloading) \ 79 | << "CUDA: " << cudaGetErrorString(e) << " at line: " << __LINE__ \ 80 | << " in file: " << __FILE__; \ 81 | } 82 | 83 | #define CHECK_CUDA_CALL(x) check_cuda_call(x, __LINE__, __FILE__) 84 | 85 | /*! \brief Thread local workspace */ 86 | class CUDAThreadEntry { 87 | public: 88 | /*! \brief The cuda stream */ 89 | cudaStream_t stream{nullptr}; 90 | /*! \brief thread local pool*/ 91 | WorkspacePool pool; 92 | /*! \brief constructor */ 93 | CUDAThreadEntry(); 94 | // get the threadlocal workspace 95 | static CUDAThreadEntry* ThreadLocal(); 96 | }; 97 | } // namespace runtime 98 | } // namespace decord 99 | #endif // DECORD_RUNTIME_CUDA_CUDA_COMMON_H_ 100 | -------------------------------------------------------------------------------- /src/runtime/cuda/cuda_module.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | /*! 21 | * Copyright (c) 2017 by Contributors 22 | * \file cuda_module.h 23 | * \brief Execution handling of CUDA kernels 24 | */ 25 | #ifndef DECORD_RUNTIME_CUDA_CUDA_MODULE_H_ 26 | #define DECORD_RUNTIME_CUDA_CUDA_MODULE_H_ 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include "../meta_data.h" 34 | 35 | namespace decord { 36 | namespace runtime { 37 | 38 | /*! \brief Maximum number of GPU supported in CUDAModule */ 39 | static constexpr const int kMaxNumGPUs = 32; 40 | 41 | /*! 42 | * \brief create a cuda module from data. 43 | * 44 | * \param data The module data, can be ptx, cubin 45 | * \param fmt The format of the data, can be "ptx", "cubin" 46 | * \param fmap The map function information map of each function. 47 | * \param cuda_source Optional, cuda source file 48 | */ 49 | Module CUDAModuleCreate( 50 | std::string data, 51 | std::string fmt, 52 | std::unordered_map fmap, 53 | std::string cuda_source); 54 | } // namespace runtime 55 | } // namespace decord 56 | #endif // DECORD_RUNTIME_CUDA_CUDA_MODULE_H_ 57 | -------------------------------------------------------------------------------- /src/runtime/dso_module.cc: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file dso_dll_module.cc 4 | * \brief Module to load from dynamic shared library. 5 | */ 6 | #include 7 | #include 8 | #include 9 | #include "module_util.h" 10 | 11 | #if defined(_WIN32) 12 | #include 13 | #else 14 | #include 15 | #endif 16 | 17 | namespace decord { 18 | namespace runtime { 19 | 20 | // Module to load from dynamic shared libary. 21 | // This is the default module DECORD used for host-side AOT 22 | class DSOModuleNode final : public ModuleNode { 23 | public: 24 | ~DSOModuleNode() { 25 | if (lib_handle_) Unload(); 26 | } 27 | 28 | const char* type_key() const final { 29 | return "dso"; 30 | } 31 | 32 | PackedFunc GetFunction( 33 | const std::string& name, 34 | const std::shared_ptr& sptr_to_self) final { 35 | BackendPackedCFunc faddr; 36 | if (name == runtime::symbol::decord_module_main) { 37 | const char* entry_name = reinterpret_cast( 38 | GetSymbol(runtime::symbol::decord_module_main)); 39 | CHECK(entry_name!= nullptr) 40 | << "Symbol " << runtime::symbol::decord_module_main << " is not presented"; 41 | faddr = reinterpret_cast(GetSymbol(entry_name)); 42 | } else { 43 | faddr = reinterpret_cast(GetSymbol(name.c_str())); 44 | } 45 | if (faddr == nullptr) return PackedFunc(); 46 | return WrapPackedFunc(faddr, sptr_to_self); 47 | } 48 | 49 | void Init(const std::string& name) { 50 | Load(name); 51 | if (auto *ctx_addr = 52 | reinterpret_cast(GetSymbol(runtime::symbol::decord_module_ctx))) { 53 | *ctx_addr = this; 54 | } 55 | InitContextFunctions([this](const char* fname) { 56 | return GetSymbol(fname); 57 | }); 58 | // Load the imported modules 59 | const char* dev_mblob = 60 | reinterpret_cast( 61 | GetSymbol(runtime::symbol::decord_dev_mblob)); 62 | if (dev_mblob != nullptr) { 63 | ImportModuleBlob(dev_mblob, &imports_); 64 | } 65 | } 66 | 67 | private: 68 | // Platform dependent handling. 69 | #if defined(_WIN32) 70 | // library handle 71 | HMODULE lib_handle_{nullptr}; 72 | // Load the library 73 | void Load(const std::string& name) { 74 | // use wstring version that is needed by LLVM. 75 | std::wstring wname(name.begin(), name.end()); 76 | lib_handle_ = LoadLibraryW(wname.c_str()); 77 | CHECK(lib_handle_ != nullptr) 78 | << "Failed to load dynamic shared library " << name; 79 | } 80 | void* GetSymbol(const char* name) { 81 | return reinterpret_cast( 82 | GetProcAddress(lib_handle_, (LPCSTR)name)); // NOLINT(*) 83 | } 84 | void Unload() { 85 | FreeLibrary(lib_handle_); 86 | } 87 | #else 88 | // Library handle 89 | void* lib_handle_{nullptr}; 90 | // load the library 91 | void Load(const std::string& name) { 92 | lib_handle_ = dlopen(name.c_str(), RTLD_LAZY | RTLD_LOCAL); 93 | CHECK(lib_handle_ != nullptr) 94 | << "Failed to load dynamic shared library " << name 95 | << " " << dlerror(); 96 | } 97 | void* GetSymbol(const char* name) { 98 | return dlsym(lib_handle_, name); 99 | } 100 | void Unload() { 101 | dlclose(lib_handle_); 102 | } 103 | #endif 104 | }; 105 | 106 | DECORD_REGISTER_GLOBAL("module.loadfile_so") 107 | .set_body([](DECORDArgs args, DECORDRetValue* rv) { 108 | std::shared_ptr n = std::make_shared(); 109 | n->Init(args[0]); 110 | *rv = runtime::Module(n); 111 | }); 112 | } // namespace runtime 113 | } // namespace decord 114 | -------------------------------------------------------------------------------- /src/runtime/file_util.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file file_util.h 4 | * \brief Minimum file manipulation util for runtime. 5 | */ 6 | #ifndef DECORD_RUNTIME_FILE_UTIL_H_ 7 | #define DECORD_RUNTIME_FILE_UTIL_H_ 8 | 9 | #include 10 | #include "meta_data.h" 11 | 12 | namespace decord { 13 | namespace runtime { 14 | /*! 15 | * \brief Get file format from given file name or format argument. 16 | * \param file_name The name of the file. 17 | * \param format The format of the file. 18 | */ 19 | std::string GetFileFormat(const std::string& file_name, 20 | const std::string& format); 21 | 22 | /*! 23 | * \return the directory in which DECORD stores cached files. 24 | * May be set using DECORD_CACHE_DIR; defaults to system locations. 25 | */ 26 | std::string GetCacheDir(); 27 | 28 | /*! 29 | * \brief Get meta file path given file name and format. 30 | * \param file_name The name of the file. 31 | */ 32 | std::string GetMetaFilePath(const std::string& file_name); 33 | 34 | /*! 35 | * \brief Get file basename (i.e. without leading directories) 36 | * \param file_name The name of the file. 37 | * \return the base name 38 | */ 39 | std::string GetFileBasename(const std::string& file_name); 40 | 41 | /*! 42 | * \brief Load binary file into a in-memory buffer. 43 | * \param file_name The name of the file. 44 | * \param data The data to be loaded. 45 | */ 46 | void LoadBinaryFromFile(const std::string& file_name, 47 | std::string* data); 48 | 49 | /*! 50 | * \brief Load binary file into a in-memory buffer. 51 | * \param file_name The name of the file. 52 | * \param data The binary data to be saved. 53 | */ 54 | void SaveBinaryToFile(const std::string& file_name, 55 | const std::string& data); 56 | 57 | /*! 58 | * \brief Save meta data to file. 59 | * \param file_name The name of the file. 60 | * \param fmap The function info map. 61 | */ 62 | void SaveMetaDataToFile( 63 | const std::string& file_name, 64 | const std::unordered_map& fmap); 65 | 66 | /*! 67 | * \brief Load meta data to file. 68 | * \param file_name The name of the file. 69 | * \param fmap The function info map. 70 | */ 71 | void LoadMetaDataFromFile( 72 | const std::string& file_name, 73 | std::unordered_map* fmap); 74 | 75 | /*! 76 | * \brief Remove (unlink) a file. 77 | * \param file_name The file name. 78 | */ 79 | void RemoveFile(const std::string& file_name); 80 | } // namespace runtime 81 | } // namespace decord 82 | #endif // DECORD_RUNTIME_FILE_UTIL_H_ 83 | -------------------------------------------------------------------------------- /src/runtime/meta_data.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file meta_data.h 4 | * \brief Meta data related utilities 5 | */ 6 | #ifndef DECORD_RUNTIME_META_DATA_H_ 7 | #define DECORD_RUNTIME_META_DATA_H_ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include "runtime_base.h" 15 | 16 | namespace decord { 17 | namespace runtime { 18 | 19 | /*! \brief function information needed by device */ 20 | struct FunctionInfo { 21 | std::string name; 22 | std::vector arg_types; 23 | std::vector thread_axis_tags; 24 | 25 | void Save(dmlc::JSONWriter *writer) const; 26 | void Load(dmlc::JSONReader *reader); 27 | void Save(dmlc::Stream *writer) const; 28 | bool Load(dmlc::Stream *reader); 29 | }; 30 | } // namespace runtime 31 | } // namespace decord 32 | 33 | namespace dmlc { 34 | DMLC_DECLARE_TRAITS(has_saveload, ::decord::runtime::FunctionInfo, true); 35 | } // namespace dmlc 36 | #endif // DECORD_RUNTIME_META_DATA_H_ 37 | -------------------------------------------------------------------------------- /src/runtime/module_util.cc: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file module_util.cc 4 | * \brief Utilities for module. 5 | */ 6 | #ifndef _LIBCPP_SGX_CONFIG 7 | #include 8 | #endif 9 | #include 10 | #include 11 | #include 12 | #include "module_util.h" 13 | 14 | namespace decord { 15 | namespace runtime { 16 | 17 | void ImportModuleBlob(const char* mblob, std::vector* mlist) { 18 | #ifndef _LIBCPP_SGX_CONFIG 19 | CHECK(mblob != nullptr); 20 | uint64_t nbytes = 0; 21 | for (size_t i = 0; i < sizeof(nbytes); ++i) { 22 | uint64_t c = mblob[i]; 23 | nbytes |= (c & 0xffUL) << (i * 8); 24 | } 25 | dmlc::MemoryFixedSizeStream fs( 26 | const_cast(mblob + sizeof(nbytes)), static_cast(nbytes)); 27 | dmlc::Stream* stream = &fs; 28 | uint64_t size; 29 | CHECK(stream->Read(&size)); 30 | for (uint64_t i = 0; i < size; ++i) { 31 | std::string tkey; 32 | CHECK(stream->Read(&tkey)); 33 | std::string fkey = "module.loadbinary_" + tkey; 34 | const PackedFunc* f = Registry::Get(fkey); 35 | CHECK(f != nullptr) 36 | << "Loader of " << tkey << "(" 37 | << fkey << ") is not presented."; 38 | Module m = (*f)(static_cast(stream)); 39 | mlist->push_back(m); 40 | } 41 | #else 42 | LOG(FATAL) << "SGX does not support ImportModuleBlob"; 43 | #endif 44 | } 45 | 46 | PackedFunc WrapPackedFunc(BackendPackedCFunc faddr, 47 | const std::shared_ptr& sptr_to_self) { 48 | return PackedFunc([faddr, sptr_to_self](DECORDArgs args, DECORDRetValue* rv) { 49 | int ret = (*faddr)( 50 | const_cast(args.values), 51 | const_cast(args.type_codes), 52 | args.num_args); 53 | CHECK_EQ(ret, 0) << DECORDGetLastError(); 54 | }); 55 | } 56 | 57 | } // namespace runtime 58 | } // namespace decord 59 | -------------------------------------------------------------------------------- /src/runtime/module_util.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file module_util.h 4 | * \brief Helper utilities for module building 5 | */ 6 | #ifndef DECORD_RUNTIME_MODULE_UTIL_H_ 7 | #define DECORD_RUNTIME_MODULE_UTIL_H_ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | extern "C" { 15 | // Function signature for generated packed function in shared library 16 | typedef int (*BackendPackedCFunc)(void* args, 17 | int* type_codes, 18 | int num_args); 19 | } // extern "C" 20 | 21 | namespace decord { 22 | namespace runtime { 23 | /*! 24 | * \brief Wrap a BackendPackedCFunc to packed function. 25 | * \param faddr The function address 26 | * \param mptr The module pointer node. 27 | */ 28 | PackedFunc WrapPackedFunc(BackendPackedCFunc faddr, const std::shared_ptr& mptr); 29 | /*! 30 | * \brief Load and append module blob to module list 31 | * \param mblob The module blob. 32 | * \param module_list The module list to append to 33 | */ 34 | void ImportModuleBlob(const char* mblob, std::vector* module_list); 35 | 36 | /*! 37 | * \brief Utility to initialize conext function symbols during startup 38 | * \param flookup A symbol lookup function. 39 | * \tparam FLookup a function of signature string->void* 40 | */ 41 | template 42 | void InitContextFunctions(FLookup flookup) { 43 | #define DECORD_INIT_CONTEXT_FUNC(FuncName) \ 44 | if (auto *fp = reinterpret_cast \ 45 | (flookup("__" #FuncName))) { \ 46 | *fp = FuncName; \ 47 | } 48 | // Initialize the functions 49 | DECORD_INIT_CONTEXT_FUNC(DECORDFuncCall); 50 | DECORD_INIT_CONTEXT_FUNC(DECORDAPISetLastError); 51 | DECORD_INIT_CONTEXT_FUNC(DECORDBackendGetFuncFromEnv); 52 | DECORD_INIT_CONTEXT_FUNC(DECORDBackendAllocWorkspace); 53 | DECORD_INIT_CONTEXT_FUNC(DECORDBackendFreeWorkspace); 54 | DECORD_INIT_CONTEXT_FUNC(DECORDBackendParallelLaunch); 55 | DECORD_INIT_CONTEXT_FUNC(DECORDBackendParallelBarrier); 56 | 57 | #undef DECORD_INIT_CONTEXT_FUNC 58 | } 59 | } // namespace runtime 60 | } // namespace decord 61 | #endif // DECORD_RUNTIME_MODULE_UTIL_H_ 62 | -------------------------------------------------------------------------------- /src/runtime/runtime_base.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file runtime_base.h 4 | * \brief Base of all C APIs 5 | */ 6 | #ifndef DECORD_RUNTIME_RUNTIME_BASE_H_ 7 | #define DECORD_RUNTIME_RUNTIME_BASE_H_ 8 | 9 | #include 10 | #include 11 | 12 | /*! \brief macro to guard beginning and end section of all functions */ 13 | #define API_BEGIN() try { 14 | /*! \brief every function starts with API_BEGIN(); 15 | and finishes with API_END() or API_END_HANDLE_ERROR */ 16 | #define API_END() } catch(std::runtime_error &_except_) { return DECORDAPIHandleException(_except_); } return 0; // NOLINT(*) 17 | /*! 18 | * \brief every function starts with API_BEGIN(); 19 | * and finishes with API_END() or API_END_HANDLE_ERROR 20 | * The finally clause contains procedure to cleanup states when an error happens. 21 | */ 22 | #define API_END_HANDLE_ERROR(Finalize) } catch(std::runtime_error &_except_) { Finalize; return DECORDAPIHandleException(_except_); } return 0; // NOLINT(*) 23 | 24 | /*! 25 | * \brief handle exception throwed out 26 | * \param e the exception 27 | * \return the return value of API after exception is handled 28 | */ 29 | inline int DECORDAPIHandleException(const std::runtime_error &e) { 30 | DECORDAPISetLastError(e.what()); 31 | return -1; 32 | } 33 | 34 | #endif // DECORD_RUNTIME_RUNTIME_BASE_H_ 35 | -------------------------------------------------------------------------------- /src/runtime/str_util.cc: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file str_util.cc 4 | * \brief Minimum string manipulation util for runtime. 5 | */ 6 | 7 | #include "str_util.h" 8 | #include "file_util.h" 9 | 10 | namespace decord { 11 | namespace runtime { 12 | 13 | std::vector SplitString(std::string const &in, char sep) { 14 | std::string::size_type b = 0; 15 | std::vector result; 16 | 17 | while ((b = in.find_first_not_of(sep, b)) != std::string::npos) { 18 | auto e = in.find_first_of(sep, b); 19 | result.push_back(in.substr(b, e-b)); 20 | b = e; 21 | } 22 | return result; 23 | } 24 | 25 | std::string GetEnvironmentVariableOrDefault(const std::string& variable_name, 26 | const std::string& default_value) 27 | { 28 | const char* value = getenv(variable_name.c_str()); 29 | return value ? value : default_value; 30 | } 31 | 32 | int ParseIntOrFloat(const std::string& str, int64_t& ivalue, double& fvalue) { 33 | char* p = nullptr; 34 | auto i = std::strtol(str.data(), &p, 10); 35 | if (p == str.data() + str.size()) { 36 | ivalue = int64_t(i); 37 | return 0; 38 | } 39 | 40 | auto f = std::strtod(str.data(), &p); 41 | if (p == str.data() + str.size()) { 42 | fvalue = f; 43 | return 1; 44 | } 45 | 46 | return -1; 47 | } 48 | 49 | } // namespace runtime 50 | } // namespace decord 51 | -------------------------------------------------------------------------------- /src/runtime/str_util.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file str_util.h 4 | * \brief Minimum string manipulation util for runtime. 5 | */ 6 | #ifndef DECORD_RUNTIME_STR_UTIL_H_ 7 | #define DECORD_RUNTIME_STR_UTIL_H_ 8 | 9 | #include 10 | #include 11 | 12 | namespace decord { 13 | namespace runtime { 14 | 15 | std::vector SplitString(std::string const &in, char sep); 16 | 17 | std::string GetEnvironmentVariableOrDefault(const std::string& variable_name, 18 | const std::string& default_value); 19 | 20 | int ParseIntOrFloat(const std::string& str, int64_t& ivalue, double& fvalue); 21 | } // namespace runtime 22 | } // namespace decord 23 | #endif // DECORD_RUNTIME_STR_UTIL_H_ 24 | -------------------------------------------------------------------------------- /src/runtime/system_lib_module.cc: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file system_lib_module.cc 4 | * \brief SystemLib module. 5 | */ 6 | #include 7 | #include 8 | #include 9 | #include "module_util.h" 10 | 11 | namespace decord { 12 | namespace runtime { 13 | 14 | class SystemLibModuleNode : public ModuleNode { 15 | public: 16 | SystemLibModuleNode() = default; 17 | 18 | const char* type_key() const final { 19 | return "system_lib"; 20 | } 21 | 22 | PackedFunc GetFunction( 23 | const std::string& name, 24 | const std::shared_ptr& sptr_to_self) final { 25 | std::lock_guard lock(mutex_); 26 | 27 | if (module_blob_ != nullptr) { 28 | // If we previously recorded submodules, load them now. 29 | ImportModuleBlob(reinterpret_cast(module_blob_), &imports_); 30 | module_blob_ = nullptr; 31 | } 32 | 33 | auto it = tbl_.find(name); 34 | if (it != tbl_.end()) { 35 | return WrapPackedFunc( 36 | reinterpret_cast(it->second), sptr_to_self); 37 | } else { 38 | return PackedFunc(); 39 | } 40 | } 41 | 42 | void RegisterSymbol(const std::string& name, void* ptr) { 43 | std::lock_guard lock(mutex_); 44 | if (name == symbol::decord_module_ctx) { 45 | void** ctx_addr = reinterpret_cast(ptr); 46 | *ctx_addr = this; 47 | } else if (name == symbol::decord_dev_mblob) { 48 | // Record pointer to content of submodules to be loaded. 49 | // We defer loading submodules to the first call to GetFunction(). 50 | // The reason is that RegisterSymbol() gets called when initializing the 51 | // syslib (i.e. library loading time), and the registeries aren't ready 52 | // yet. Therefore, we might not have the functionality to load submodules 53 | // now. 54 | CHECK(module_blob_ == nullptr) << "Resetting mobule blob?"; 55 | module_blob_ = ptr; 56 | } else { 57 | auto it = tbl_.find(name); 58 | if (it != tbl_.end() && ptr != it->second) { 59 | LOG(WARNING) << "SystemLib symbol " << name 60 | << " get overriden to a different address " 61 | << ptr << "->" << it->second; 62 | } 63 | tbl_[name] = ptr; 64 | } 65 | } 66 | 67 | static const std::shared_ptr& Global() { 68 | static std::shared_ptr inst = 69 | std::make_shared(); 70 | return inst; 71 | } 72 | 73 | private: 74 | // Internal mutex 75 | std::mutex mutex_; 76 | // Internal symbol table 77 | std::unordered_map tbl_; 78 | // Module blob to be imported 79 | void* module_blob_{nullptr}; 80 | }; 81 | 82 | DECORD_REGISTER_GLOBAL("module._GetSystemLib") 83 | .set_body([](DECORDArgs args, DECORDRetValue* rv) { 84 | *rv = runtime::Module(SystemLibModuleNode::Global()); 85 | }); 86 | } // namespace runtime 87 | } // namespace decord 88 | 89 | int DECORDBackendRegisterSystemLibSymbol(const char* name, void* ptr) { 90 | decord::runtime::SystemLibModuleNode::Global()->RegisterSymbol(name, ptr); 91 | return 0; 92 | } 93 | -------------------------------------------------------------------------------- /src/runtime/workspace_pool.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file workspace_pool.h 4 | * \brief Workspace pool utility. 5 | */ 6 | #ifndef DECORD_RUNTIME_WORKSPACE_POOL_H_ 7 | #define DECORD_RUNTIME_WORKSPACE_POOL_H_ 8 | 9 | #include 10 | #include 11 | 12 | namespace decord { 13 | namespace runtime { 14 | /*! 15 | * \brief A workspace pool to manage 16 | * 17 | * \note We have the following assumption about backend temporal 18 | * workspace allocation, and will optimize for such assumption, 19 | * some of these assumptions can be enforced by the compiler. 20 | * 21 | * - Only a few allocation will happen, and space will be released after use. 22 | * - The release order is usually in reverse order of allocate 23 | * - Repetitive pattern of same allocations over different runs. 24 | */ 25 | class DECORD_DLL WorkspacePool { 26 | public: 27 | /*! 28 | * \brief Create pool with specific device type and device. 29 | * \param device_type The device type. 30 | * \param device The device API. 31 | */ 32 | WorkspacePool(DLDeviceType device_type, std::shared_ptr device); 33 | /*! \brief destructor */ 34 | ~WorkspacePool(); 35 | /*! 36 | * \brief Allocate temporal workspace. 37 | * \param ctx The context of allocation. 38 | * \param size The size to be allocated. 39 | */ 40 | void* AllocWorkspace(DECORDContext ctx, size_t size); 41 | /*! 42 | * \brief Free temporal workspace in backend execution. 43 | * 44 | * \param ctx The context of allocation. 45 | * \param ptr The pointer to be freed. 46 | */ 47 | void FreeWorkspace(DECORDContext ctx, void* ptr); 48 | 49 | private: 50 | class Pool; 51 | /*! \brief pool of device local array */ 52 | std::vector array_; 53 | /*! \brief device type this pool support */ 54 | DLDeviceType device_type_; 55 | /*! \brief The device API */ 56 | std::shared_ptr device_; 57 | }; 58 | 59 | } // namespace runtime 60 | } // namespace decord 61 | #endif // DECORD_RUNTIME_WORKSPACE_POOL_H_ 62 | -------------------------------------------------------------------------------- /src/sampler/random_file_order_sampler.cc: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file random_file_order_sampler.cc 4 | * \brief Randomly shuffle file order but not internal frame order 5 | */ 6 | 7 | #include "random_file_order_sampler.h" 8 | 9 | #include 10 | 11 | #include 12 | 13 | namespace decord { 14 | namespace sampler { 15 | 16 | RandomFileOrderSampler::RandomFileOrderSampler(std::vector lens, std::vector range, int bs, int interval, int skip) 17 | : bs_(bs), visit_idx_(0) { 18 | CHECK_GT(bs_, 0) << "Batch size cannot be smaller than 1."; 19 | CHECK(range.size() % 2 == 0) << "Range (begin, end) size incorrect, expected: " << lens.size() * 2; 20 | CHECK_EQ(lens.size(), range.size() / 2) << "Video reader size mismatch with range: " << lens.size() << " vs " << range.size() / 2; 21 | 22 | // return sample buffer 23 | samples_.resize(bs_); 24 | 25 | // records for each video's property 26 | // visit order for shuffling 27 | records_.reserve(lens.size()); 28 | visit_order_.clear(); 29 | for (size_t i = 0; i < lens.size(); ++i) { 30 | auto begin = range[i*2]; 31 | auto end = range[i*2 + 1]; 32 | if (end < 0) { 33 | // allow negative indices, e.g., -20 means total_frame - 20 34 | end = lens[i] - end; 35 | } 36 | int64_t bs_skip = bs * (1 + interval) - interval + skip; 37 | // how many batchsizes in this reader 38 | int64_t num_batches = (end + skip - begin) / bs_skip; 39 | visit_order_.insert(visit_order_.end(), num_batches, i); 40 | CHECK_GE(end, 0) << "Video{" << i << "} has range end smaller than 0: " << end; 41 | CHECK(begin < end) << "Video{" << i << "} has invalid begin and end config: " << begin << "->" << end; 42 | CHECK(end < lens[i]) << "Video{" << i <<"} has range end larger than # frames: " << lens[i]; 43 | records_.emplace_back(ReaderRecord{begin, end, interval, skip, begin}); 44 | } 45 | } 46 | 47 | void RandomFileOrderSampler::Reset() { 48 | // shuffle orders 49 | std::random_shuffle(visit_order_.begin(), visit_order_.end()); 50 | // reset visit idx 51 | visit_idx_ = 0; 52 | // clear and reset status to begin indices 53 | for (auto& record : records_) { 54 | record.current = record.begin; 55 | } 56 | } 57 | 58 | bool RandomFileOrderSampler::HasNext() const { 59 | return visit_idx_ < visit_order_.size(); 60 | } 61 | 62 | const Samples& RandomFileOrderSampler::Next() { 63 | CHECK(HasNext()); 64 | CHECK(samples_.size() == static_cast(bs_)); 65 | auto next_reader = visit_order_[visit_idx_]; 66 | auto& record = records_[next_reader]; 67 | auto pos = record.current; 68 | int idx = 0; 69 | for (idx = 0; idx < bs_; ++idx) { 70 | CHECK(pos < record.end); 71 | samples_[idx].first = next_reader; 72 | samples_[idx].second = pos; 73 | pos += record.interval + 1; 74 | } 75 | record.current = pos - record.interval + record.skip; 76 | ++visit_idx_; 77 | return samples_; 78 | } 79 | 80 | size_t RandomFileOrderSampler::Size() const { 81 | return visit_order_.size(); 82 | } 83 | } // sampler 84 | } // decord 85 | -------------------------------------------------------------------------------- /src/sampler/random_file_order_sampler.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file random_file_order_sampler.h 4 | * \brief Randomly shuffle file order but not internal frame order 5 | */ 6 | 7 | #ifndef DECORD_SAMPLER_RANDOM_FILE_ORDER_SAMPLER_H_ 8 | #define DECORD_SAMPLER_RANDOM_FILE_ORDER_SAMPLER_H_ 9 | 10 | #include "sampler_interface.h" 11 | 12 | namespace decord { 13 | namespace sampler { 14 | 15 | class RandomFileOrderSampler : public SamplerInterface { 16 | public: 17 | RandomFileOrderSampler(std::vector lens, std::vector range, int bs, int interval, int skip); 18 | ~RandomFileOrderSampler() = default; 19 | void Reset(); 20 | bool HasNext() const; 21 | const Samples& Next(); 22 | size_t Size() const; 23 | 24 | private: 25 | struct ReaderRecord { 26 | // immutable 27 | const int64_t begin; 28 | const int64_t end; 29 | const int interval; 30 | const int skip; 31 | // mutable 32 | int64_t current; 33 | }; // struct Record 34 | 35 | int bs_; 36 | Samples samples_; 37 | std::vector records_; 38 | std::vector visit_order_; 39 | std::size_t visit_idx_; 40 | 41 | }; // class RandomFileOrderSampler 42 | 43 | } // sampler 44 | } // decord 45 | 46 | #endif // DECORD_SAMPLER_RANDOM_FILE_ORDER_SAMPLER_H_ 47 | -------------------------------------------------------------------------------- /src/sampler/random_sampler.cc: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file random_file_order_sampler.cc 4 | * \brief Randomly shuffle file order but not internal frame order 5 | */ 6 | 7 | #include "random_sampler.h" 8 | 9 | #include 10 | 11 | #include 12 | 13 | namespace decord { 14 | namespace sampler { 15 | 16 | RandomSampler::RandomSampler(std::vector lens, std::vector range, int bs, int interval, int skip) 17 | : bs_(bs), curr_(0) { 18 | CHECK(range.size() % 2 == 0) << "Range (begin, end) size incorrect, expected: " << lens.size() * 2; 19 | CHECK_EQ(lens.size(), range.size() / 2) << "Video reader size mismatch with range: " << lens.size() << " vs " << range.size() / 2; 20 | 21 | // output buffer 22 | samples_.resize(bs); 23 | 24 | // visit order for shuffling 25 | visit_order_.clear(); 26 | for (size_t i = 0; i < lens.size(); ++i) { 27 | auto begin = range[i*2]; 28 | auto end = range[i*2 + 1]; 29 | if (end < 0) { 30 | // allow negative indices, e.g., -20 means total_frame - 20 31 | end = lens[i] - end; 32 | } 33 | CHECK_GE(end, 0) << "Video{" << i << "} has range end smaller than 0: " << end; 34 | CHECK(begin < end) << "Video{" << i << "} has invalid begin and end config: " << begin << "->" << end; 35 | CHECK(end < lens[i]) << "Video{" << i <<"} has range end larger than # frames: " << lens[i]; 36 | int64_t bs_skip = bs * (1 + interval) - interval + skip; 37 | int64_t bs_length = bs_skip - skip; 38 | for (int64_t b = begin; b + bs_length < end; b += bs_skip) { 39 | int offset = 0; 40 | for (int j = 0; j < bs; ++j) { 41 | samples_[j] = std::make_pair(i, b + offset); 42 | offset += interval + 1; 43 | } 44 | visit_order_.emplace_back(samples_); 45 | } 46 | } 47 | } 48 | 49 | void RandomSampler::Reset() { 50 | // shuffle orders 51 | std::random_shuffle(visit_order_.begin(), visit_order_.end()); 52 | // reset visit idx 53 | curr_ = 0; 54 | } 55 | 56 | bool RandomSampler::HasNext() const { 57 | return curr_ < visit_order_.size(); 58 | } 59 | 60 | const Samples& RandomSampler::Next() { 61 | CHECK(HasNext()); 62 | CHECK_EQ(samples_.size(), bs_); 63 | samples_ = visit_order_[curr_++]; 64 | return samples_; 65 | } 66 | 67 | size_t RandomSampler::Size() const { 68 | return visit_order_.size(); 69 | } 70 | } // sampler 71 | } // decord 72 | -------------------------------------------------------------------------------- /src/sampler/random_sampler.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file random_sampler.h 4 | * \brief Fully random sampler, with random file order and access position 5 | */ 6 | 7 | #ifndef DECORD_SAMPLER_RANDOM_SAMPLER_H_ 8 | #define DECORD_SAMPLER_RANDOM_SAMPLER_H_ 9 | 10 | #include "sampler_interface.h" 11 | 12 | namespace decord { 13 | namespace sampler { 14 | 15 | class RandomSampler : public SamplerInterface { 16 | public: 17 | RandomSampler(std::vector lens, std::vector range, int bs, int interval, int skip); 18 | ~RandomSampler() = default; 19 | void Reset(); 20 | bool HasNext() const; 21 | const Samples& Next(); 22 | size_t Size() const; 23 | 24 | private: 25 | size_t bs_; 26 | Samples samples_; 27 | size_t curr_; 28 | std::vector visit_order_; 29 | 30 | }; // class RandomSampler 31 | 32 | } // sampler 33 | } // decord 34 | 35 | #endif // DECORD_SAMPLER_RANDOM_SAMPLER_H_ 36 | -------------------------------------------------------------------------------- /src/sampler/sampler_interface.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file sampler_interface.h 4 | * \brief Sampler interface 5 | */ 6 | 7 | #ifndef DECORD_SAMPLER_SAMPLER_INTERFACE_H_ 8 | #define DECORD_SAMPLER_SAMPLER_INTERFACE_H_ 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | namespace decord { 15 | namespace sampler { 16 | 17 | using SamplerIndex = std::pair; 18 | using Samples = std::vector; 19 | 20 | class SamplerInterface { 21 | public: 22 | virtual ~SamplerInterface() = default; 23 | virtual void Reset() = 0; 24 | virtual bool HasNext() const = 0; 25 | virtual const Samples& Next() = 0; 26 | virtual size_t Size() const = 0; 27 | }; // class SamplerInterface 28 | 29 | using SamplerPtr = std::unique_ptr; 30 | 31 | } // sampler 32 | } // decord 33 | #endif 34 | -------------------------------------------------------------------------------- /src/sampler/sequential_sampler.cc: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file random_file_order_sampler.cc 4 | * \brief Randomly shuffle file order but not internal frame order 5 | */ 6 | 7 | #include "sequential_sampler.h" 8 | 9 | #include 10 | 11 | namespace decord { 12 | namespace sampler { 13 | 14 | SequentialSampler::SequentialSampler(std::vector lens, std::vector range, int bs, int interval, int skip) 15 | : bs_(bs), curr_(0) { 16 | CHECK(range.size() % 2 == 0) << "Range (begin, end) size incorrect, expected: " << lens.size() * 2; 17 | CHECK_EQ(lens.size(), range.size() / 2) << "Video reader size mismatch with range: " << lens.size() << " vs " << range.size() / 2; 18 | 19 | // output buffer 20 | samples_.resize(bs); 21 | 22 | // visit order for shuffling 23 | visit_order_.clear(); 24 | for (size_t i = 0; i < lens.size(); ++i) { 25 | auto begin = range[i*2]; 26 | auto end = range[i*2 + 1]; 27 | if (end < 0) { 28 | // allow negative indices, e.g., -20 means total_frame - 20 29 | end = lens[i] - end; 30 | } 31 | CHECK_GE(end, 0) << "Video{" << i << "} has range end smaller than 0: " << end; 32 | CHECK(begin < end) << "Video{" << i << "} has invalid begin and end config: " << begin << "->" << end; 33 | CHECK(end < lens[i]) << "Video{" << i <<"} has range end larger than # frames: " << lens[i]; 34 | int64_t bs_skip = bs * (1 + interval) - interval + skip; 35 | int64_t bs_length = bs_skip - skip; 36 | for (int64_t b = begin; b + bs_length < end; b += bs_skip) { 37 | int offset = 0; 38 | for (int j = 0; j < bs; ++j) { 39 | samples_[j] = std::make_pair(i, b + offset); 40 | offset += interval + 1; 41 | } 42 | visit_order_.emplace_back(samples_); 43 | } 44 | } 45 | } 46 | 47 | void SequentialSampler::Reset() { 48 | // reset visit idx 49 | curr_ = 0; 50 | } 51 | 52 | bool SequentialSampler::HasNext() const { 53 | return curr_ < visit_order_.size(); 54 | } 55 | 56 | const Samples& SequentialSampler::Next() { 57 | CHECK(HasNext()); 58 | CHECK_EQ(samples_.size(), bs_); 59 | samples_ = visit_order_[curr_++]; 60 | return samples_; 61 | } 62 | 63 | size_t SequentialSampler::Size() const { 64 | return visit_order_.size(); 65 | } 66 | } // sampler 67 | } // decord 68 | -------------------------------------------------------------------------------- /src/sampler/sequential_sampler.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file sequential_sampler.h 4 | * \brief Sequential sampler, with fixed reading order 5 | */ 6 | 7 | #ifndef DECORD_SAMPLER_SEQUENTIAL_SAMPLER_H_ 8 | #define DECORD_SAMPLER_SEQUENTIAL_SAMPLER_H_ 9 | 10 | #include "sampler_interface.h" 11 | 12 | namespace decord { 13 | namespace sampler { 14 | 15 | class SequentialSampler : public SamplerInterface { 16 | public: 17 | SequentialSampler(std::vector lens, std::vector range, int bs, int interval, int skip); 18 | ~SequentialSampler() = default; 19 | void Reset(); 20 | bool HasNext() const; 21 | const Samples& Next(); 22 | size_t Size() const; 23 | 24 | private: 25 | size_t bs_; 26 | Samples samples_; 27 | size_t curr_; 28 | std::vector visit_order_; 29 | 30 | }; // class SequentialSampler 31 | 32 | } // sampler 33 | } // decord 34 | 35 | #endif // DECORD_SAMPLER_SEQUENTIAL_SAMPLER_H_ 36 | -------------------------------------------------------------------------------- /src/sampler/smart_random_sampler.cc: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file smart_random_sampler.cc 4 | * \brief Smart random sampler for faster video random access 5 | */ 6 | 7 | #include "smart_random_sampler.h" 8 | 9 | namespace decord { 10 | namespace sampler { 11 | 12 | } // sampler 13 | } // decord 14 | -------------------------------------------------------------------------------- /src/sampler/smart_random_sampler.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file smart_random_sampler.h 4 | * \brief Smart random sampler for faster video random access 5 | */ 6 | 7 | #ifndef DECORD_SAMPLER_SMART_RANDOM_SAMPLER_H_ 8 | #define DECORD_SAMPLER_SMART_RANDOM_SAMPLER_H_ 9 | 10 | #include "sampler_interface.h" 11 | 12 | namespace decord { 13 | namespace sampler { 14 | 15 | class SmartRandomSampler : public SamplerInterface { 16 | public: 17 | SmartRandomSampler(std::vector lens, int interval, int bs_skip); 18 | 19 | private: 20 | 21 | }; // class SmartRandomSampler 22 | 23 | } // sampler 24 | } // decord 25 | 26 | #endif // DECORD_SAMPLER_SMART_RANDOM_SAMPLER_H_ 27 | -------------------------------------------------------------------------------- /src/segmenter/README.md: -------------------------------------------------------------------------------- 1 | # Algorithms for cutting clips by scene contents/fixed intervals, etc. 2 | -------------------------------------------------------------------------------- /src/segmenter/cutter.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmlc/decord/d2e56190286ae394032a8141885f76d5372bd44b/src/segmenter/cutter.h -------------------------------------------------------------------------------- /src/segmenter/interval_cutter.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmlc/decord/d2e56190286ae394032a8141885f76d5372bd44b/src/segmenter/interval_cutter.cpp -------------------------------------------------------------------------------- /src/segmenter/scene_cutter.cc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmlc/decord/d2e56190286ae394032a8141885f76d5372bd44b/src/segmenter/scene_cutter.cc -------------------------------------------------------------------------------- /src/video/ffmpeg/README.md: -------------------------------------------------------------------------------- 1 | # Decoding through FFMPEG 2 | 3 | Require FFMPEG >= 4.0 4 | 5 | ## List of components 6 | 7 | - Libavcodec 8 | - Libavfilter 9 | - Libavformat -------------------------------------------------------------------------------- /src/video/ffmpeg/filter_graph.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file filter_graph.h 4 | * \brief FFmpeg Filter Graph Definition 5 | */ 6 | 7 | #ifndef DECORD_VIDEO_FFMPEG_FILTER_GRAPH_H_ 8 | #define DECORD_VIDEO_FFMPEG_FILTER_GRAPH_H_ 9 | 10 | #include "ffmpeg_common.h" 11 | 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | namespace decord { 18 | namespace ffmpeg { 19 | 20 | /** 21 | * \brief FFMPEGFilterGraph for filtering operations 22 | * 23 | */ 24 | class FFMPEGFilterGraph { 25 | public: 26 | /** 27 | * \brief Construct a new FFMPEGFilterGraph object 28 | * 29 | * \param filter_desc String defining filter descriptions 30 | * \param dec_ctx Decoder context 31 | */ 32 | FFMPEGFilterGraph(std::string filter_desc, AVCodecContext *dec_ctx); 33 | /** 34 | * \brief Push frame to be processed into filter graph 35 | * 36 | * \param frame Pointer to AVFrame 37 | */ 38 | void Push(AVFrame *frame); 39 | /** 40 | * \brief Pop filtered frame from graph 41 | * 42 | * \param frame Pointer to pointer to AVFrame 43 | * \return true Success 44 | * \return false Failed 45 | */ 46 | bool Pop(AVFrame **frame); 47 | /** 48 | * \brief Destroy the FFMPEGFilterGraph object 49 | * 50 | */ 51 | ~FFMPEGFilterGraph(); 52 | private: 53 | /** 54 | * \brief Initialize filter graph 55 | * 56 | * \param filter_desc String defining filter descriptions 57 | * \param dec_ctx Decoder context 58 | */ 59 | void Init(std::string filter_desc, AVCodecContext *dec_ctx); 60 | /** 61 | * \brief Buffer sink context, the output side of graph 62 | * 63 | */ 64 | AVFilterContext *buffersink_ctx_; 65 | /** 66 | * \brief Buffer src context, the input side of graph 67 | * 68 | */ 69 | AVFilterContext *buffersrc_ctx_; 70 | /** 71 | * \brief Smart pointer to filter graph 72 | * 73 | */ 74 | AVFilterGraphPtr filter_graph_; 75 | /** 76 | * \brief Size of buffered frames under processing 77 | * 78 | */ 79 | std::atomic count_; 80 | 81 | DISALLOW_COPY_AND_ASSIGN(FFMPEGFilterGraph); 82 | }; // FFMPEGFilterGraph 83 | 84 | } // namespace ffmpeg 85 | } // namespace decord 86 | #endif // DECORD_VIDEO_FFMPEG_FILTER_GRAPH_H_ 87 | -------------------------------------------------------------------------------- /src/video/ffmpeg/threaded_decoder.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file threaded_decoder.h 4 | * \brief FFmpeg threaded decoder definition 5 | */ 6 | 7 | #ifndef DECORD_VIDEO_FFMPEG_THREADED_DECODER_H_ 8 | #define DECORD_VIDEO_FFMPEG_THREADED_DECODER_H_ 9 | 10 | #include "filter_graph.h" 11 | #include "../threaded_decoder_interface.h" 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | 20 | namespace decord { 21 | namespace ffmpeg { 22 | 23 | class FFMPEGThreadedDecoder final : public ThreadedDecoderInterface { 24 | using PacketQueue = dmlc::ConcurrentBlockingQueue; 25 | using PacketQueuePtr = std::unique_ptr; 26 | using FrameQueue = dmlc::ConcurrentBlockingQueue; 27 | using FrameQueuePtr = std::unique_ptr; 28 | using BufferQueue = dmlc::ConcurrentBlockingQueue; 29 | using BufferQueuePtr = std::unique_ptr; 30 | using FFMPEGFilterGraphPtr = std::shared_ptr; 31 | 32 | public: 33 | FFMPEGThreadedDecoder(); 34 | void SetCodecContext(AVCodecContext *dec_ctx, int width = -1, int height = -1, 35 | int rotation = 0); 36 | void Start(); 37 | void Stop(); 38 | void Clear(); 39 | void Push(ffmpeg::AVPacketPtr pkt, runtime::NDArray buf); 40 | bool Pop(runtime::NDArray *frame); 41 | void SuggestDiscardPTS(std::vector dts); 42 | void ClearDiscardPTS(); 43 | ~FFMPEGThreadedDecoder(); 44 | private: 45 | void WorkerThread(); 46 | void WorkerThreadImpl(); 47 | void RecordInternalError(std::string message); 48 | void CheckErrorStatus(); 49 | void ProcessFrame(AVFramePtr p, NDArray out_buf); 50 | NDArray CopyToNDArray(AVFramePtr p); 51 | NDArray AsNDArray(AVFramePtr p); 52 | // void FetcherThread(std::condition_variable& cv, FrameQueuePtr frame_queue); 53 | PacketQueuePtr pkt_queue_; 54 | FrameQueuePtr frame_queue_; 55 | BufferQueuePtr buffer_queue_; 56 | std::atomic frame_count_; 57 | std::atomic draining_; 58 | std::thread t_; 59 | // std::thread fetcher_; 60 | // std::condition_variable cv_; 61 | std::atomic run_; 62 | FFMPEGFilterGraphPtr filter_graph_; 63 | AVCodecContextPtr dec_ctx_; 64 | std::unordered_set discard_pts_; 65 | std::mutex pts_mutex_; 66 | std::mutex error_mutex_; 67 | std::atomic error_status_; 68 | std::string error_message_; 69 | 70 | DISALLOW_COPY_AND_ASSIGN(FFMPEGThreadedDecoder); 71 | }; 72 | 73 | } // namespace ffmpeg 74 | } // namespace decord 75 | 76 | #endif // DECORD_VIDEO_FFMPEG_THREADED_DECODER_H_ 77 | -------------------------------------------------------------------------------- /src/video/logging.cc: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file logging.cc 4 | * \brief Logging utils for decord 5 | */ 6 | 7 | #include "ffmpeg/ffmpeg_common.h" 8 | 9 | #include 10 | 11 | namespace decord { 12 | namespace runtime { 13 | 14 | DECORD_REGISTER_GLOBAL("logging._CAPI_SetLoggingLevel") 15 | .set_body([] (DECORDArgs args, DECORDRetValue* rv) { 16 | int log_level = args[0]; 17 | av_log_set_level(log_level); 18 | }); 19 | 20 | } // namespace runtime 21 | } // namespace decord 22 | -------------------------------------------------------------------------------- /src/video/nvcodec/LICENSE: -------------------------------------------------------------------------------- 1 | Unless otherwise stated, files in this subdirectory are modified versions 2 | following the LICENSE below: 3 | 4 | * Copyright (c) 2018, NVIDIA CORPORATION. All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions 8 | * are met: 9 | * * Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * * Neither the name of NVIDIA CORPORATION nor the names of its 15 | * contributors may be used to endorse or promote products derived 16 | * from this software without specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY 19 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 21 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 22 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 23 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 24 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 25 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 26 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /src/video/nvcodec/README.md: -------------------------------------------------------------------------------- 1 | # GPU accelerated decoding through nvdec 2 | 3 | Require CUDA Driver and toolkit >= 8.0 and a supported graphic card. 4 | 5 | -------------------------------------------------------------------------------- /src/video/nvcodec/cuda_context.cc: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file cuda_context.cc 4 | * \brief CUDA Context 5 | */ 6 | 7 | #include "cuda_context.h" 8 | #include "../../runtime/cuda/cuda_common.h" 9 | 10 | namespace decord { 11 | namespace cuda { 12 | using namespace runtime; 13 | 14 | CUContext::CUContext() : context_{0}, initialized_{false} { 15 | } 16 | 17 | CUContext::CUContext(CUdevice device, unsigned int flags) 18 | : device_{device}, context_{0}, initialized_{false} { 19 | CHECK_CUDA_CALL(cuInit(0)); 20 | if (!CHECK_CUDA_CALL(cuDevicePrimaryCtxRetain(&context_, device))) { 21 | throw std::runtime_error("cuDevicePrimaryCtxRetain failed, can't go forward without a context"); 22 | } 23 | Push(); 24 | CUdevice dev; 25 | if (!CHECK_CUDA_CALL(cuCtxGetDevice(&dev))) { 26 | throw std::runtime_error("Unable to get device"); 27 | } 28 | initialized_ = true; 29 | CHECK_CUDA_CALL(cuCtxSynchronize()); 30 | } 31 | 32 | CUContext::CUContext(CUcontext ctx) 33 | : context_{ctx}, initialized_{true} { 34 | } 35 | 36 | CUContext::~CUContext() { 37 | if (initialized_) { 38 | // cuCtxPopCurrent? 39 | CHECK_CUDA_CALL(cuDevicePrimaryCtxRelease(device_)); 40 | } 41 | } 42 | 43 | CUContext::CUContext(CUContext&& other) 44 | : device_{other.device_}, context_{other.context_}, 45 | initialized_{other.initialized_} { 46 | other.device_ = 0; 47 | other.context_ = 0; 48 | other.initialized_ = false; 49 | } 50 | 51 | CUContext& CUContext::operator=(CUContext&& other) { 52 | if (initialized_) { 53 | CHECK_CUDA_CALL(cuCtxDestroy(context_)); 54 | } 55 | device_ = other.device_; 56 | context_ = other.context_; 57 | initialized_ = other.initialized_; 58 | other.device_ = 0; 59 | other.context_ = 0; 60 | other.initialized_ = false; 61 | return *this; 62 | } 63 | 64 | void CUContext::Push() const { 65 | CUcontext current; 66 | if (!CHECK_CUDA_CALL(cuCtxGetCurrent(¤t))) { 67 | throw std::runtime_error("Unable to get current context"); 68 | } 69 | if (current != context_) { 70 | if (!CHECK_CUDA_CALL(cuCtxPushCurrent(context_))) { 71 | throw std::runtime_error("Unable to push current context"); 72 | } 73 | } 74 | } 75 | 76 | bool CUContext::Initialized() const { 77 | return initialized_; 78 | } 79 | 80 | CUContext::operator CUcontext() const { 81 | return context_; 82 | } 83 | 84 | } // namespace cuda 85 | } // namespace decord -------------------------------------------------------------------------------- /src/video/nvcodec/cuda_context.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file cuda_context.h 4 | * \brief CUDA Context 5 | */ 6 | 7 | #ifndef DECORD_VIDEO_NVCODEC_CUDA_CONTEXT_H 8 | #define DECORD_VIDEO_NVCODEC_CUDA_CONTEXT_H 9 | 10 | #include 11 | 12 | namespace decord { 13 | namespace cuda { 14 | 15 | class CUContext { 16 | public: 17 | CUContext(); 18 | CUContext(CUdevice device, unsigned int flags = 0); 19 | CUContext(CUcontext); 20 | ~CUContext(); 21 | 22 | // no copying 23 | CUContext(const CUContext&) = delete; 24 | CUContext& operator=(const CUContext&) = delete; 25 | 26 | CUContext(CUContext&& other); 27 | CUContext& operator=(CUContext&& other); 28 | 29 | operator CUcontext() const; 30 | 31 | void Push() const; 32 | bool Initialized() const; 33 | private: 34 | CUdevice device_; 35 | CUcontext context_; 36 | bool initialized_; 37 | }; 38 | 39 | } // namespace cuda 40 | } // namespace decord 41 | 42 | #endif // DECORD_VIDEO_NVCODEC_CUDA_CONTEXT_H 43 | -------------------------------------------------------------------------------- /src/video/nvcodec/cuda_decoder_impl.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file cuda_decoder_impl.h 4 | * \brief NVCUVID based decoder implementation class 5 | */ 6 | 7 | #ifndef DECORD_VIDEO_NVCODEC_CUDA_DECODER_IMPL_H_ 8 | #define DECORD_VIDEO_NVCODEC_CUDA_DECODER_IMPL_H_ 9 | 10 | #include "nvcuvid/nvcuvid.h" 11 | #include 12 | 13 | namespace decord { 14 | namespace cuda { 15 | 16 | class CUVideoDecoderImpl { 17 | public: 18 | CUVideoDecoderImpl(); 19 | CUVideoDecoderImpl(CUvideodecoder d); 20 | ~CUVideoDecoderImpl(); 21 | 22 | // no copying 23 | CUVideoDecoderImpl(const CUVideoDecoderImpl&) = delete; 24 | CUVideoDecoderImpl& operator=(const CUVideoDecoderImpl&) = delete; 25 | 26 | CUVideoDecoderImpl(CUVideoDecoderImpl&& other); 27 | CUVideoDecoderImpl& operator=(CUVideoDecoderImpl&& other); 28 | 29 | operator CUvideodecoder() const; 30 | 31 | int Initialize(CUVIDEOFORMAT* format); 32 | bool Initialized() const; 33 | 34 | uint16_t Width() const; 35 | uint16_t Height() const; 36 | 37 | private: 38 | CUvideodecoder decoder_; 39 | CUVIDDECODECREATEINFO decoder_info_; 40 | bool initialized_; 41 | }; // class CUVideoDecoderImpl 42 | 43 | } // namespace cuda 44 | } // namespace decord 45 | #endif 46 | -------------------------------------------------------------------------------- /src/video/nvcodec/cuda_mapped_frame.cc: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file cuda_mapped_frame.cc 4 | * \brief NVCUVID mapped frame 5 | */ 6 | 7 | #include "nvcuvid/cuviddec.h" 8 | #include "cuda_mapped_frame.h" 9 | #include "../../runtime/cuda/cuda_common.h" 10 | #include 11 | 12 | namespace decord { 13 | namespace cuda { 14 | using namespace runtime; 15 | 16 | CUMappedFrame::CUMappedFrame() 17 | : disp_info{nullptr}, valid_{false} { 18 | } 19 | 20 | CUMappedFrame::CUMappedFrame(CUVIDPARSERDISPINFO* disp_info, 21 | CUvideodecoder decoder, 22 | CUstream stream) 23 | : disp_info{disp_info}, valid_{false}, decoder_(decoder), params_{0} { 24 | 25 | if (!disp_info->progressive_frame) { 26 | LOG(FATAL) << "Got an interlaced frame. We don't do interlaced frames."; 27 | } 28 | 29 | params_.progressive_frame = disp_info->progressive_frame; 30 | params_.top_field_first = disp_info->top_field_first; 31 | params_.second_field = 0; 32 | params_.output_stream = stream; 33 | 34 | if (!CHECK_CUDA_CALL(cuvidMapVideoFrame(decoder_, disp_info->picture_index, 35 | &ptr_, &pitch_, ¶ms_))) { 36 | LOG(FATAL) << "Unable to map video frame"; 37 | } 38 | valid_ = true; 39 | } 40 | 41 | CUMappedFrame::CUMappedFrame(CUMappedFrame&& other) 42 | : disp_info{other.disp_info}, valid_{other.valid_}, decoder_{other.decoder_}, 43 | ptr_{other.ptr_}, pitch_{other.pitch_}, params_{other.params_} { 44 | other.disp_info = nullptr; 45 | other.valid_ = false; 46 | } 47 | 48 | CUMappedFrame::~CUMappedFrame() { 49 | if (valid_) { 50 | if (!CHECK_CUDA_CALL(cuvidUnmapVideoFrame(decoder_, ptr_))) { 51 | LOG(FATAL) << "Error unmapping video frame"; 52 | } 53 | } 54 | } 55 | 56 | uint8_t* CUMappedFrame::get_ptr() const { 57 | return reinterpret_cast(ptr_); 58 | } 59 | 60 | unsigned int CUMappedFrame::get_pitch() const { 61 | return pitch_; 62 | } 63 | 64 | } // namespace cuda 65 | } // namespace decord 66 | -------------------------------------------------------------------------------- /src/video/nvcodec/cuda_mapped_frame.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file cuda_mapped_frame.h 4 | * \brief NVCUVID mapped frame 5 | */ 6 | 7 | #ifndef DECORD_VIDEO_NVCODEC_CUDA_MAPPED_FRAME_H_ 8 | #define DECORD_VIDEO_NVCODEC_CUDA_MAPPED_FRAME_H_ 9 | 10 | #include "nvcuvid/nvcuvid.h" 11 | 12 | #include 13 | 14 | namespace decord { 15 | namespace cuda { 16 | 17 | class CUMappedFrame { 18 | public: 19 | CUMappedFrame(); 20 | CUMappedFrame(CUVIDPARSERDISPINFO* disp_info, CUvideodecoder decoder, 21 | CUstream stream); 22 | ~CUMappedFrame(); 23 | CUMappedFrame(const CUMappedFrame&) = delete; 24 | CUMappedFrame& operator=(const CUMappedFrame&) = delete; 25 | CUMappedFrame(CUMappedFrame&& other); 26 | CUMappedFrame& operator=(CUMappedFrame&&) = delete; 27 | 28 | uint8_t* get_ptr() const; 29 | unsigned int get_pitch() const; 30 | 31 | CUVIDPARSERDISPINFO* disp_info; 32 | 33 | private: 34 | bool valid_; 35 | CUvideodecoder decoder_; 36 | CUdeviceptr ptr_; 37 | unsigned int pitch_; 38 | CUVIDPROCPARAMS params_; 39 | 40 | }; 41 | 42 | } // namespace cuda 43 | } // namespace decord 44 | 45 | #endif // DECORD_VIDEO_NVCODEC_CUDA_MAPPED_FRAME_H_ 46 | -------------------------------------------------------------------------------- /src/video/nvcodec/cuda_parser.cc: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file cuda_parser.h 4 | * \brief NVCUVID based video parser 5 | */ 6 | #include "cuda_parser.h" 7 | #include "cuda_threaded_decoder.h" 8 | 9 | 10 | namespace decord { 11 | namespace cuda { 12 | 13 | void CUVideoParser::InitParams(AVCodecID codec, CUThreadedDecoder* decoder, int decode_surfaces, 14 | uint8_t* extradata, int extradata_size) { 15 | switch (codec) { 16 | case AV_CODEC_ID_H264: 17 | parser_info_.CodecType = cudaVideoCodec_H264; 18 | break; 19 | case AV_CODEC_ID_HEVC: 20 | parser_info_.CodecType = cudaVideoCodec_HEVC; 21 | // this can probably be better 22 | parser_info_.ulMaxNumDecodeSurfaces = 20; 23 | break; 24 | case AV_CODEC_ID_MPEG4: 25 | parser_info_.CodecType = cudaVideoCodec_MPEG4; 26 | parser_info_.ulMaxNumDecodeSurfaces = 20; 27 | break; 28 | case AV_CODEC_ID_VP9: 29 | parser_info_.CodecType = cudaVideoCodec_VP9; 30 | parser_info_.ulMaxNumDecodeSurfaces = 20; 31 | break; 32 | default: 33 | LOG(FATAL) << "Invalid codec: " << avcodec_get_name(AVCodecID(codec)); 34 | return; 35 | } 36 | parser_info_.ulMaxNumDecodeSurfaces = decode_surfaces; 37 | parser_info_.ulErrorThreshold = 0; 38 | parser_info_.ulMaxDisplayDelay = 0; 39 | parser_info_.pUserData = decoder; 40 | parser_info_.pfnSequenceCallback = CUThreadedDecoder::HandlePictureSequence; 41 | parser_info_.pfnDecodePicture = CUThreadedDecoder::HandlePictureDecode; 42 | parser_info_.pfnDisplayPicture = CUThreadedDecoder::HandlePictureDisplay; 43 | parser_info_.pExtVideoInfo = &parser_extinfo_; 44 | if (extradata_size > 0) { 45 | auto hdr_size = std::min(sizeof(parser_extinfo_.raw_seqhdr_data), 46 | static_cast(extradata_size)); 47 | parser_extinfo_.format.seqhdr_data_length = hdr_size; 48 | memcpy(parser_extinfo_.raw_seqhdr_data, extradata, hdr_size); 49 | } 50 | } 51 | 52 | } // namespace cuda 53 | } // namespace decord 54 | -------------------------------------------------------------------------------- /src/video/nvcodec/cuda_parser.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file cuda_parser.h 4 | * \brief NVCUVID based parser 5 | */ 6 | 7 | #ifndef DECORD_VIDEO_NVCODEC_CU_PARSER_H_ 8 | #define DECORD_VIDEO_NVCODEC_CU_PARSER_H_ 9 | 10 | #include 11 | 12 | #include "nvcuvid/nvcuvid.h" 13 | #include "../../runtime/cuda/cuda_common.h" 14 | 15 | #ifdef __cplusplus 16 | extern "C" { 17 | #endif 18 | #include 19 | #ifdef __cplusplus 20 | } 21 | #endif 22 | 23 | namespace decord { 24 | namespace cuda { 25 | 26 | class CUVideoParser { 27 | public: 28 | CUVideoParser() : parser_{0}, initialized_{false} {} 29 | 30 | CUVideoParser(AVCodecID codec, CUThreadedDecoder* decoder, int decode_surfaces) 31 | : CUVideoParser{codec, decoder, decode_surfaces, nullptr, 0} {} 32 | 33 | CUVideoParser(AVCodecID codec, CUThreadedDecoder* decoder, int decode_surfaces, 34 | uint8_t* extradata, int extradata_size) 35 | : parser_{0}, parser_info_{}, parser_extinfo_{}, initialized_{false} 36 | { 37 | InitParams(codec, decoder, decode_surfaces, extradata, extradata_size); 38 | 39 | CUDA_DRIVER_CALL(cuvidCreateVideoParser(&parser_, &parser_info_)); 40 | initialized_ = true; 41 | } 42 | 43 | CUVideoParser(CUvideoparser parser) 44 | : parser_{parser}, initialized_{true} 45 | { 46 | } 47 | 48 | ~CUVideoParser() { 49 | if (initialized_) { 50 | CUDA_DRIVER_CALL(cuvidDestroyVideoParser(parser_)); 51 | } 52 | } 53 | 54 | CUVideoParser(CUVideoParser&& other) 55 | : parser_{other.parser_}, initialized_{other.initialized_} 56 | { 57 | other.parser_ = 0; 58 | other.initialized_ = false; 59 | } 60 | 61 | CUVideoParser& operator=(CUVideoParser&& other) { 62 | if (initialized_) { 63 | CUDA_DRIVER_CALL(cuvidDestroyVideoParser(parser_)); 64 | } 65 | parser_ = other.parser_; 66 | parser_info_ = other.parser_info_; 67 | parser_extinfo_ = other.parser_extinfo_; 68 | initialized_ = other.initialized_; 69 | other.parser_ = 0; 70 | other.initialized_ = false; 71 | return *this; 72 | } 73 | 74 | bool Initialized() const { 75 | return initialized_; 76 | } 77 | 78 | operator CUvideoparser() const { 79 | return parser_; 80 | } 81 | 82 | private: 83 | void InitParams(AVCodecID codec, CUThreadedDecoder* decoder, int decode_surfaces, 84 | uint8_t* extradata, int extradata_size); 85 | CUvideoparser parser_; 86 | CUVIDPARSERPARAMS parser_info_; 87 | CUVIDEOFORMATEX parser_extinfo_; 88 | 89 | bool initialized_; 90 | }; 91 | 92 | } // namespace decord 93 | } // namespace cuda 94 | 95 | #endif 96 | -------------------------------------------------------------------------------- /src/video/nvcodec/cuda_stream.cc: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file cuda_stream.cc 4 | * \brief CUDA stream 5 | */ 6 | 7 | #include "cuda_stream.h" 8 | 9 | namespace decord { 10 | namespace cuda { 11 | CUStream::CUStream(int device_id, bool default_stream) : created_{false}, stream_{0} { 12 | if (!default_stream) { 13 | int orig_device; 14 | cudaGetDevice(&orig_device); 15 | auto set_device = false; 16 | if (device_id >= 0 && orig_device != device_id) { 17 | set_device = true; 18 | cudaSetDevice(device_id); 19 | } 20 | CUDA_CALL(cudaStreamCreateWithFlags(&stream_, cudaStreamNonBlocking)); 21 | created_ = true; 22 | if (set_device) { 23 | CUDA_CALL(cudaSetDevice(orig_device)); 24 | } 25 | } 26 | } 27 | 28 | CUStream::~CUStream() { 29 | if (created_) { 30 | CUDA_CALL(cudaStreamDestroy(stream_)); 31 | } 32 | } 33 | 34 | CUStream::CUStream(CUStream&& other) 35 | : created_{other.created_}, stream_{other.stream_} 36 | { 37 | other.stream_ = 0; 38 | other.created_ = false; 39 | } 40 | 41 | CUStream& CUStream::operator=(CUStream&& other) { 42 | stream_ = other.stream_; 43 | created_ = other.created_; 44 | other.stream_ = 0; 45 | other.created_ = false; 46 | return *this; 47 | } 48 | 49 | CUStream::operator cudaStream_t() { 50 | return stream_; 51 | } 52 | } // namespace cuda 53 | } // namespace decord 54 | -------------------------------------------------------------------------------- /src/video/nvcodec/cuda_stream.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file cuda_stream.h 4 | * \brief CUDA stream 5 | */ 6 | 7 | #ifndef DECORD_VIDEO_NVCODEC_CUDA_STREAM_H_ 8 | #define DECORD_VIDEO_NVCODEC_CUDA_STREAM_H_ 9 | 10 | #include "../../runtime/cuda/cuda_common.h" 11 | 12 | namespace decord { 13 | namespace cuda { 14 | 15 | class CUStream { 16 | public: 17 | CUStream(int device_id, bool default_stream); 18 | ~CUStream(); 19 | CUStream(const CUStream&) = delete; 20 | CUStream& operator=(const CUStream&) = delete; 21 | CUStream(CUStream&&); 22 | CUStream& operator=(CUStream&&); 23 | operator cudaStream_t(); 24 | 25 | private: 26 | bool created_; 27 | cudaStream_t stream_; 28 | }; // class CUStream 29 | 30 | } // namespace cuda 31 | } // namespace decord 32 | #endif 33 | -------------------------------------------------------------------------------- /src/video/nvcodec/cuda_texture.cc: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file cuda_texture.cc 4 | * \brief NVCUVID texture objects 5 | */ 6 | 7 | #include "cuda_texture.h" 8 | #include "nvcuvid/nvcuvid.h" 9 | 10 | #include "../../runtime/cuda/cuda_common.h" 11 | 12 | namespace decord { 13 | namespace cuda { 14 | using namespace runtime; 15 | 16 | CUTexture::CUTexture() : valid_{false} { 17 | } 18 | 19 | CUTexture::CUTexture(const cudaResourceDesc* pResDesc, 20 | const cudaTextureDesc* pTexDesc, 21 | const cudaResourceViewDesc* pResViewDesc) 22 | : valid_{false} 23 | { 24 | if (!CHECK_CUDA_CALL(cudaCreateTextureObject(&object_, pResDesc, pTexDesc, pResViewDesc))) { 25 | LOG(FATAL) << "Unable to create a texture object"; 26 | } 27 | valid_ = true; 28 | } 29 | 30 | CUTexture::~CUTexture() { 31 | if (valid_) { 32 | cudaDestroyTextureObject(object_); 33 | } 34 | } 35 | 36 | CUTexture::CUTexture(CUTexture&& other) 37 | : valid_{other.valid_}, object_{other.object_} 38 | { 39 | other.valid_ = false; 40 | } 41 | 42 | CUTexture& CUTexture::operator=(CUTexture&& other) { 43 | valid_ = other.valid_; 44 | object_ = other.object_; 45 | other.valid_ = false; 46 | return *this; 47 | } 48 | 49 | CUTexture::operator cudaTextureObject_t() const { 50 | if (valid_) { 51 | return object_; 52 | } else { 53 | return cudaTextureObject_t{}; 54 | } 55 | } 56 | 57 | CUTextureRegistry::CUTextureRegistry() { 58 | 59 | } 60 | 61 | const CUImageTexture& CUTextureRegistry::GetTexture(uint8_t* ptr, unsigned int input_pitch, 62 | uint16_t input_width, uint16_t input_height, 63 | ScaleMethod scale_method, ChromaUpMethod chroma_up_method) { 64 | auto tex_id = std::make_tuple(ptr, scale_method, chroma_up_method); 65 | 66 | // find existing registed texture, if so return directly 67 | auto tex = textures_.find(tex_id); 68 | if (tex != textures_.end()) { 69 | return tex->second; 70 | } 71 | 72 | // not found, create new texture object 73 | CUImageTexture tex_object; 74 | cudaTextureDesc tex_desc = {}; 75 | tex_desc.addressMode[0] = cudaAddressModeClamp; 76 | tex_desc.addressMode[1] = cudaAddressModeClamp; 77 | if (scale_method == ScaleMethod_Nearest) { 78 | tex_desc.filterMode = cudaFilterModePoint; 79 | } else { 80 | tex_desc.filterMode = cudaFilterModeLinear; 81 | } 82 | tex_desc.readMode = cudaReadModeNormalizedFloat; 83 | tex_desc.normalizedCoords = 0; 84 | 85 | cudaResourceDesc res_desc = {}; 86 | res_desc.resType = cudaResourceTypePitch2D; 87 | res_desc.res.pitch2D.devPtr = ptr; 88 | res_desc.res.pitch2D.desc = cudaCreateChannelDesc(); 89 | res_desc.res.pitch2D.width = input_width; 90 | res_desc.res.pitch2D.height = input_height; 91 | res_desc.res.pitch2D.pitchInBytes = input_pitch; 92 | 93 | tex_object.luma = CUTexture{&res_desc, &tex_desc, nullptr}; 94 | 95 | tex_desc.addressMode[0] = cudaAddressModeClamp; 96 | tex_desc.addressMode[1] = cudaAddressModeClamp; 97 | // only one ChromaUpMethod for now... 98 | tex_desc.filterMode = cudaFilterModeLinear; 99 | tex_desc.readMode = cudaReadModeNormalizedFloat; 100 | tex_desc.normalizedCoords = 0; 101 | 102 | res_desc.resType = cudaResourceTypePitch2D; 103 | res_desc.res.pitch2D.devPtr = ptr + (input_height * input_pitch); 104 | res_desc.res.pitch2D.desc = cudaCreateChannelDesc(); 105 | res_desc.res.pitch2D.width = input_width / 2; // YUV420 106 | res_desc.res.pitch2D.height = input_height / 2; // YUV420 107 | res_desc.res.pitch2D.pitchInBytes = input_pitch; 108 | 109 | tex_object.chroma = CUTexture{&res_desc, &tex_desc, nullptr}; 110 | 111 | auto p = textures_.emplace(tex_id, std::move(tex_object)); 112 | if (!p.second) { 113 | LOG(FATAL) << "Unable to cache a new texture object."; 114 | } 115 | return p.first->second; 116 | } 117 | 118 | } // namespace cuda 119 | } // namespace decord 120 | -------------------------------------------------------------------------------- /src/video/nvcodec/cuda_texture.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file cuda_texture.h 4 | * \brief NVCUVID texture objects 5 | */ 6 | 7 | #ifndef DECORD_VIDEO_NVCODEC_CUDA_TEXTURE_H_ 8 | #define DECORD_VIDEO_NVCODEC_CUDA_TEXTURE_H_ 9 | 10 | #include "nvcuvid/nvcuvid.h" 11 | #include "../../runtime/cuda/cuda_common.h" 12 | 13 | #include 14 | 15 | #include 16 | 17 | namespace decord { 18 | namespace cuda { 19 | 20 | /** 21 | * How the image is scaled up/down from the original 22 | */ 23 | enum ScaleMethod { 24 | /** 25 | * The value for the nearest neighbor is used, no interpolation 26 | */ 27 | ScaleMethod_Nearest, 28 | 29 | /** 30 | * Simple bilinear interpolation of four nearest neighbors 31 | */ 32 | ScaleMethod_Linear 33 | 34 | // These are possibilities but currently unimplemented (PRs welcome) 35 | // ScaleMethod_Area 36 | // ScaleMethod_Cubic 37 | // ScaleMethod_Lanczos 38 | }; 39 | 40 | /** 41 | * How the chroma channels are upscaled from yuv 4:2:0 to 4:4:4 42 | */ 43 | enum ChromaUpMethod { 44 | /** 45 | * Simple bilinear interpolation of four nearest neighbors 46 | */ 47 | ChromaUpMethod_Linear 48 | 49 | // These are possibilities but currently unimplemented (PRs welcome) 50 | // ChromaUpMethod_CatmullRom 51 | }; 52 | 53 | class CUTexture { 54 | public: 55 | CUTexture(); 56 | CUTexture(const cudaResourceDesc* pResDesc, 57 | const cudaTextureDesc* pTexDesc, 58 | const cudaResourceViewDesc* pResViewDesc); 59 | ~CUTexture(); 60 | CUTexture(CUTexture&& other); 61 | CUTexture& operator=(CUTexture&& other); 62 | CUTexture(const CUTexture&) = delete; 63 | CUTexture& operator=(const CUTexture&) = delete; 64 | operator cudaTextureObject_t() const; 65 | private: 66 | bool valid_; 67 | cudaTextureObject_t object_; 68 | }; // class CUTexture 69 | 70 | struct CUImageTexture { 71 | CUTexture luma; 72 | CUTexture chroma; 73 | }; // struct CUImageTexture 74 | 75 | 76 | /** 77 | * \brief A registry of CUDA Texture Objects, for fast retrieval 78 | * 79 | */ 80 | class CUTextureRegistry { 81 | public: 82 | // Here we assume that a data pointer, scale method, chroma_up_method uniquely defines a texture 83 | using TexID = std::tuple; 84 | CUTextureRegistry(); 85 | const CUImageTexture& GetTexture(uint8_t* ptr, unsigned int input_pitch, 86 | uint16_t input_width, uint16_t input_height, 87 | ScaleMethod scale_method, ChromaUpMethod chroma_up_method); 88 | 89 | private: 90 | struct TexHash { 91 | std::hash ptr_hash; 92 | std::hash scale_hash; 93 | std::hash up_hash; 94 | std::size_t operator () (const TexID& tex) const { 95 | return ptr_hash(std::get<0>(tex)) 96 | ^ scale_hash(std::get<1>(tex)) 97 | ^ up_hash(std::get<2>(tex)); 98 | } 99 | }; // struct TexHash 100 | std::unordered_map textures_; 101 | 102 | }; // class CUTextureRegistry 103 | 104 | } // namespace cuda 105 | } // namespace decord 106 | 107 | #endif 108 | -------------------------------------------------------------------------------- /src/video/nvcodec/cuda_threaded_decoder.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file cuda_decoder.h 4 | * \brief NVCUVID based decoder 5 | */ 6 | 7 | #ifndef DECORD_VIDEO_NVCODEC_CUDA_THREADED_DECODER_H_ 8 | #define DECORD_VIDEO_NVCODEC_CUDA_THREADED_DECODER_H_ 9 | 10 | #include "cuda_stream.h" 11 | #include "cuda_parser.h" 12 | #include "cuda_context.h" 13 | #include "cuda_decoder_impl.h" 14 | #include "cuda_texture.h" 15 | #include "../ffmpeg/ffmpeg_common.h" 16 | #include "../threaded_decoder_interface.h" 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | namespace decord { 27 | namespace cuda { 28 | 29 | class CUThreadedDecoder final : public ThreadedDecoderInterface { 30 | constexpr static int kMaxOutputSurfaces = 20; 31 | using NDArray = runtime::NDArray; 32 | using AVPacketPtr = ffmpeg::AVPacketPtr; 33 | using AVCodecContextPtr = ffmpeg::AVCodecContextPtr; 34 | using AVBSFContextPtr = ffmpeg::AVBSFContextPtr; 35 | using PacketQueue = dmlc::ConcurrentBlockingQueue; 36 | using PacketQueuePtr = std::unique_ptr; 37 | using BufferQueue = dmlc::ConcurrentBlockingQueue; 38 | using BufferQueuePtr = std::unique_ptr; 39 | using FrameQueue = dmlc::ConcurrentBlockingQueue; 40 | using FrameQueuePtr = std::unique_ptr; 41 | using PermitQueue = dmlc::ConcurrentBlockingQueue; 42 | using PermitQueuePtr = std::shared_ptr; 43 | using ReorderQueue = dmlc::ConcurrentBlockingQueue; 44 | using ReorderQueuePtr = std::unique_ptr; 45 | using FrameOrderQueue = dmlc::ConcurrentBlockingQueue; 46 | using FrameOrderQueuePtr = std::unique_ptr; 47 | 48 | public: 49 | CUThreadedDecoder(int device_id, AVCodecParameters *codecpar, AVInputFormat *iformat); 50 | void SetCodecContext(AVCodecContext *dec_ctx, int width = -1, int height = -1, int rotation = 0); 51 | bool Initialized() const; 52 | void Start(); 53 | void Stop(); 54 | void Clear(); 55 | void Push(AVPacketPtr pkt, NDArray buf); 56 | bool Pop(NDArray *frame); 57 | void SuggestDiscardPTS(std::vector dts); 58 | void ClearDiscardPTS(); 59 | ~CUThreadedDecoder(); 60 | 61 | static int CUDAAPI HandlePictureSequence(void* user_data, CUVIDEOFORMAT* format); 62 | static int CUDAAPI HandlePictureDecode(void* user_data, CUVIDPICPARAMS* pic_params); 63 | static int CUDAAPI HandlePictureDisplay(void* user_data, CUVIDPARSERDISPINFO* disp_info); 64 | 65 | private: 66 | int HandlePictureSequence_(CUVIDEOFORMAT* format); 67 | int HandlePictureDecode_(CUVIDPICPARAMS* pic_params); 68 | int HandlePictureDisplay_(CUVIDPARSERDISPINFO* disp_info); 69 | void LaunchThread(); 70 | void LaunchThreadImpl(); 71 | void RecordInternalError(std::string message); 72 | void CheckErrorStatus(); 73 | void InitBitStreamFilter(AVCodecParameters *codecpar, AVInputFormat *iformat); 74 | 75 | int device_id_; 76 | CUStream stream_; 77 | CUdevice device_; 78 | CUContext ctx_; 79 | CUVideoParser parser_; 80 | CUVideoDecoderImpl decoder_; 81 | PacketQueuePtr pkt_queue_; 82 | FrameQueuePtr frame_queue_; 83 | //BufferQueuePtr buffer_queue_; 84 | //std::unordered_map reorder_buffer_; 85 | ReorderQueuePtr reorder_queue_; 86 | //FrameOrderQueuePtr frame_order_; 87 | // int64_t last_pts_; 88 | std::thread launcher_t_; 89 | //std::thread converter_t_; 90 | //std::vector permits_; 91 | // std::vector frame_in_use_; 92 | std::atomic run_; 93 | std::atomic frame_count_; 94 | std::atomic draining_; 95 | 96 | CUTextureRegistry tex_registry_; 97 | AVRational nv_time_base_; 98 | AVRational frame_base_; 99 | AVCodecContextPtr dec_ctx_; 100 | /*! \brief AV bitstream filter context */ 101 | AVBSFContextPtr bsf_ctx_; 102 | unsigned int width_; 103 | unsigned int height_; 104 | // uint64_t decoded_cnt_; 105 | std::unordered_set discard_pts_; 106 | std::mutex pts_mutex_; 107 | std::mutex error_mutex_; 108 | std::atomic error_status_; 109 | std::string error_message_; 110 | 111 | DISALLOW_COPY_AND_ASSIGN(CUThreadedDecoder); 112 | }; 113 | } // namespace cuda 114 | } // namespace decord 115 | #endif // DECORD_VIDEO_NVCODEC_CUDA_THREADED_DECODER_H_ 116 | -------------------------------------------------------------------------------- /src/video/storage_pool.cc: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file storage_pool.cc 4 | * \brief Simple pool for storage 5 | */ 6 | 7 | #include "storage_pool.h" 8 | 9 | namespace decord { 10 | 11 | NDArrayPool::NDArrayPool() : init_(false) { 12 | 13 | } 14 | 15 | NDArrayPool::NDArrayPool(std::size_t sz, std::vector shape, DLDataType dtype, DLContext ctx) 16 | : size_(sz), shape_(shape), dtype_(dtype), ctx_(ctx), init_(true) { 17 | } 18 | 19 | NDArrayPool::~NDArrayPool() { 20 | while (queue_.size() > 0) { 21 | auto arr = queue_.front(); 22 | queue_.pop(); 23 | arr.data_->manager_ctx = nullptr; 24 | } 25 | } 26 | 27 | runtime::NDArray NDArrayPool::Acquire() { 28 | CHECK(init_) << "NDArrayPool not initialized with shape and ctx"; 29 | if (queue_.size() > 0) { 30 | auto arr = queue_.front(); 31 | queue_.pop(); 32 | return arr; 33 | } else { 34 | // Allocate 35 | auto arr = NDArray::Empty(shape_, dtype_, ctx_); 36 | arr.data_->manager_ctx = this; 37 | arr.data_->deleter = &NDArrayPool::Deleter; 38 | return arr; 39 | } 40 | } 41 | 42 | void NDArrayPool::Deleter(NDArray::Container* ptr) { 43 | if (!ptr) return; 44 | if (ptr->manager_ctx != nullptr) { 45 | auto pool = static_cast(ptr->manager_ctx); 46 | if (pool->size_ <= pool->queue_.size()) { 47 | decord::runtime::DeviceAPI::Get(ptr->dl_tensor.ctx)->FreeDataSpace( 48 | ptr->dl_tensor.ctx, ptr->dl_tensor.data); 49 | delete ptr; 50 | ptr = nullptr; 51 | } else { 52 | static_cast(ptr->manager_ctx)->queue_.push(NDArray(ptr)); 53 | } 54 | } else if (ptr->dl_tensor.data != nullptr) { 55 | decord::runtime::DeviceAPI::Get(ptr->dl_tensor.ctx)->FreeDataSpace( 56 | ptr->dl_tensor.ctx, ptr->dl_tensor.data); 57 | delete ptr; 58 | ptr = nullptr; 59 | } 60 | } 61 | 62 | } // namespace decord 63 | -------------------------------------------------------------------------------- /src/video/storage_pool.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file storage_pool.h 4 | * \brief Simple storage pool 5 | */ 6 | 7 | #ifndef DECORD_VIDEO_STORAGE_POOL_H_ 8 | #define DECORD_VIDEO_STORAGE_POOL_H_ 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | namespace decord { 20 | 21 | /** 22 | * \brief A pool with auto release memory management 23 | * 24 | * \tparam T Pointer type 25 | * \tparam S Pool size 26 | */ 27 | template 28 | class AutoReleasePool { 29 | public: 30 | using ptr_type = std::shared_ptr; 31 | using pool_type = dmlc::ThreadLocalStore>; 32 | /** 33 | * \brief Construct a new Auto Release Pool object 34 | * 35 | */ 36 | AutoReleasePool() : active_(true) {}; 37 | /** 38 | * \brief Destroy the Auto Release Pool object 39 | * 40 | */ 41 | virtual ~AutoReleasePool() { 42 | active_.store(false); 43 | } 44 | 45 | /** 46 | * \brief Acquire a new smart pointer object, either from pool (if exist) or from new memory 47 | * 48 | * \return ptr_type 49 | */ 50 | ptr_type Acquire() { 51 | if (pool_type::Get()->empty()) { 52 | return std::shared_ptr(Allocate(), std::bind(&AutoReleasePool::Recycle, this, std::placeholders::_1)); 53 | } 54 | ptr_type ret = pool_type::Get()->front(); 55 | pool_type::Get()->pop(); 56 | return ret; 57 | } 58 | 59 | private: 60 | /** 61 | * \brief Recycle function for on-destroying smart pointer object 62 | * 63 | * \param p Raw pointer 64 | */ 65 | void Recycle(T* p) { 66 | if (!p) return; 67 | if (!active_.load() || pool_type::Get()->size() + 1 > S) { 68 | Delete(p); 69 | } else { 70 | pool_type::Get()->push(std::shared_ptr(p, std::bind(&AutoReleasePool::Recycle, this, std::placeholders::_1))); 71 | } 72 | } 73 | 74 | /** 75 | * \brief Virtual allocation method for T* 76 | * 77 | * \return T* New raw pointer 78 | */ 79 | virtual T* Allocate() { 80 | return new T; 81 | } 82 | 83 | /** 84 | * \brief Deleter for raw pointer 85 | * 86 | * \param p Raw pointer to be freed 87 | */ 88 | virtual void Delete(T* p) { 89 | delete p; 90 | } 91 | 92 | /** 93 | * \brief whether pool is active or on-destroying 94 | * 95 | */ 96 | std::atomic active_; 97 | 98 | DISALLOW_COPY_AND_ASSIGN(AutoReleasePool); 99 | }; 100 | 101 | class NDArrayPool { 102 | using NDArray = runtime::NDArray; 103 | public: 104 | NDArrayPool(); 105 | NDArrayPool(std::size_t sz, std::vector shape, DLDataType dtype, DLContext ctx); 106 | NDArray Acquire(); 107 | ~NDArrayPool(); 108 | static void Deleter(NDArray::Container* ptr); 109 | // static void DefaultDeleter(NDArray::Container* ptr); 110 | 111 | private: 112 | std::size_t size_; 113 | std::vector shape_; 114 | DLDataType dtype_; 115 | DLContext ctx_; 116 | std::queue queue_; 117 | bool init_; 118 | }; // NDArrayPool 119 | 120 | } // namespace decord 121 | 122 | #endif // DECORD_VIDEO_STORAGE_POOL_H_ 123 | -------------------------------------------------------------------------------- /src/video/threaded_decoder_interface.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file video_decoder_interface.h 4 | * \brief Video Decoder Interface 5 | */ 6 | 7 | #ifndef DECORD_VIDEO_THREADED_DECODER_INTERFACE_H_ 8 | #define DECORD_VIDEO_THREADED_DECODER_INTERFACE_H_ 9 | 10 | #include "ffmpeg/ffmpeg_common.h" 11 | #include 12 | #include 13 | 14 | namespace decord { 15 | typedef enum { 16 | DECORD_SKIP_FRAME = 0x01, /**< Set when the frame is not wanted, we can skip image processing */ 17 | } ThreadedDecoderFlags; 18 | 19 | class ThreadedDecoderInterface { 20 | public: 21 | virtual void SetCodecContext(AVCodecContext *dec_ctx, int width = -1, int height = -1, int rotation = 0) = 0; 22 | virtual void Start() = 0; 23 | virtual void Stop() = 0; 24 | virtual void Clear() = 0; 25 | virtual void Push(ffmpeg::AVPacketPtr pkt, runtime::NDArray buf) = 0; 26 | virtual bool Pop(runtime::NDArray *frame) = 0; 27 | virtual void SuggestDiscardPTS(std::vector dts) = 0; 28 | virtual void ClearDiscardPTS() = 0; 29 | virtual ~ThreadedDecoderInterface() = default; 30 | }; // class ThreadedDecoderInterface 31 | 32 | } // namespace decord 33 | #endif // DECORD_VIDEO_THREADED_DECODER_INTERFACE_H_ 34 | -------------------------------------------------------------------------------- /src/video/video_loader.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file video_loader.h 4 | * \brief FFmpeg video loader, implements VideoLoaderInterface 5 | */ 6 | 7 | #ifndef DECORD_VIDEO_VIDEO_LOADER_H_ 8 | #define DECORD_VIDEO_VIDEO_LOADER_H_ 9 | 10 | #include "video_reader.h" 11 | #include "../sampler/sampler_interface.h" 12 | 13 | #include 14 | 15 | #include 16 | 17 | namespace decord { 18 | 19 | enum ShuffleTypes { 20 | kNoShuffle = 0, 21 | kRandomFileOrderShuffle, 22 | kRandomShuffle, 23 | kRandomInFileShuffle, 24 | kSmartRandomShuffle, 25 | }; // enum ShuffleTypes 26 | 27 | class VideoLoader : public VideoLoaderInterface { 28 | public: 29 | VideoLoader(std::vector filenames, std::vector ctxs, 30 | std::vector shape, int interval, 31 | int skip, int shuffle, 32 | int prefetch); 33 | ~VideoLoader(); 34 | void Reset(); 35 | bool HasNext() const; 36 | int64_t Length() const; 37 | void Next(); 38 | NDArray NextData(); 39 | NDArray NextIndices(); 40 | 41 | private: 42 | using ReaderPtr = std::shared_ptr; 43 | struct Entry { 44 | ReaderPtr ptr; 45 | std::vector key_indices; 46 | int64_t frame_count; 47 | 48 | Entry(ReaderPtr p, std::vector keys, int64_t frames) 49 | : ptr(p), key_indices(keys), frame_count(frames) {} 50 | }; 51 | std::vector readers_; 52 | std::vector shape_; 53 | int intvl_; 54 | int skip_; 55 | int shuffle_; 56 | int prefetch_; 57 | char next_ready_; // ready flag, use with 0xFE for data, 0xFD for label 58 | NDArray next_data_; 59 | std::vector next_indices_; 60 | sampler::SamplerPtr sampler_; 61 | // std::vector > visit_order_; 62 | // std::vector visit_bounds_; 63 | // std::vector > > visit_buffer_; 64 | // std::size_t curr_; 65 | std::vector ctxs_; 66 | NDArrayPool ndarray_pool_; 67 | }; // class VideoLoader 68 | } // namespace decord 69 | 70 | #endif // DECORD_VIDEO_VIDEO_LOADER_H_ 71 | -------------------------------------------------------------------------------- /src/video/video_reader.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2019 by Contributors if not otherwise specified 3 | * \file video_reader.h 4 | * \brief FFmpeg video reader, implements VideoReaderInterface 5 | */ 6 | 7 | #ifndef DECORD_VIDEO_VIDEO_READER_H_ 8 | #define DECORD_VIDEO_VIDEO_READER_H_ 9 | 10 | #include "threaded_decoder_interface.h" 11 | #include "storage_pool.h" 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | #include 18 | #include 19 | 20 | 21 | namespace decord { 22 | using timestamp_t = float; 23 | struct AVFrameTime { 24 | int64_t pts; // presentation timestamp, unit is stream time_base 25 | int64_t dts; // decoding timestamp, unit is stream time_base 26 | timestamp_t start; // real world start timestamp, unit is second 27 | timestamp_t stop; // real world stop timestamp, unit is second 28 | 29 | AVFrameTime(int64_t pts=AV_NOPTS_VALUE, int64_t dts=AV_NOPTS_VALUE, timestamp_t start=0, timestamp_t stop=0) 30 | : pts(pts), dts(dts), start(start), stop(stop) {} 31 | }; // struct AVFrameTime 32 | 33 | class VideoReader : public VideoReaderInterface { 34 | using ThreadedDecoderPtr = std::unique_ptr; 35 | using NDArray = runtime::NDArray; 36 | public: 37 | VideoReader(std::string fn, DLContext ctx, int width=-1, int height=-1, 38 | int nb_thread=0, int io_type=kNormal, std::string fault_tol="-1"); 39 | /*! \brief Destructor, note that FFMPEG resources has to be managed manually to avoid resource leak */ 40 | ~VideoReader(); 41 | void SetVideoStream(int stream_nb = -1); 42 | unsigned int QueryStreams() const; 43 | int64_t GetFrameCount() const; 44 | int64_t GetCurrentPosition() const; 45 | NDArray NextFrame(); 46 | NDArray GetBatch(std::vector indices, NDArray buf); 47 | void SkipFrames(int64_t num = 1); 48 | bool Seek(int64_t pos); 49 | bool SeekAccurate(int64_t pos); 50 | NDArray GetKeyIndices(); 51 | NDArray GetFramePTS() const; 52 | double GetAverageFPS() const; 53 | double GetRotation() const; 54 | protected: 55 | friend class VideoLoader; 56 | std::vector GetKeyIndicesVector() const; 57 | private: 58 | void IndexKeyframes(); 59 | void PushNext(); 60 | int64_t LocateKeyframe(int64_t pos); 61 | void SkipFramesImpl(int64_t num = 1); 62 | bool CheckKeyFrame(); 63 | NDArray NextFrameImpl(); 64 | int64_t FrameToPTS(int64_t pos); 65 | std::vector FramesToPTS(const std::vector& positions); 66 | void CacheFrame(NDArray frame); 67 | bool FetchCachedFrame(NDArray &frame, int64_t pos); 68 | 69 | DLContext ctx_; 70 | std::vector key_indices_; 71 | std::map pts_frame_map_; 72 | NDArray tmp_key_frame_; 73 | bool overrun_; 74 | /*! \brief a lookup table for per frame pts/dts */ 75 | std::vector frame_ts_; 76 | /*! \brief Video Streams Codecs in original videos */ 77 | std::vector codecs_; 78 | /*! \brief Currently active video stream index */ 79 | int actv_stm_idx_; 80 | /*! \brief AV format context holder */ 81 | ffmpeg::AVFormatContextPtr fmt_ctx_; 82 | ThreadedDecoderPtr decoder_; 83 | int64_t curr_frame_; // current frame location 84 | int64_t nb_thread_decoding_; // number of threads for decoding 85 | int width_; // output video width 86 | int height_; // output video height 87 | bool eof_; // end of file indicator 88 | NDArrayPool ndarray_pool_; 89 | std::unique_ptr io_ctx_; // avio context for raw memory access 90 | std::string filename_; // file name if from file directly, can be empty if from bytes 91 | NDArray cached_frame_; // last valid frame, for error tolerance 92 | bool use_cached_frame_; // switch to enable frame recovery if failed to decode 93 | std::unordered_set failed_idx_; // idx of failed frames(recovered from other frames) 94 | int64_t fault_tol_thresh_; // fault tolerance threshold, raise if recovered frames retrieved exceeds thresh 95 | bool fault_warn_emit_; // whether a fault warning has been emitted 96 | }; // class VideoReader 97 | } // namespace decord 98 | #endif // DECORD_VIDEO_VIDEO_READER_H_ 99 | -------------------------------------------------------------------------------- /tests/benchmark/bench_decord.py: -------------------------------------------------------------------------------- 1 | """Benchmark using opencv's VideoCapture""" 2 | import time 3 | import sys 4 | import os 5 | import argparse 6 | import warnings 7 | import numpy as np 8 | sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../../python')) 9 | import decord as de 10 | 11 | parser = argparse.ArgumentParser("Decord benchmark") 12 | parser.add_argument('--gpu', type=int, default=-1, help='context to run, use --gpu=-1 to use cpu only') 13 | parser.add_argument('--file', type=str, default='/tmp/testsrc_h264_100s_default.mp4', help='Test video') 14 | parser.add_argument('--seed', type=int, default=666, help='numpy random seed for random access indices') 15 | parser.add_argument('--random-frames', type=int, default=300, help='number of random frames to run') 16 | parser.add_argument('--width', type=int, default=320, help='resize frame width') 17 | parser.add_argument('--height', type=int, default=240, help='resize frame height') 18 | 19 | args = parser.parse_args() 20 | 21 | test_video = args.file 22 | if args.gpu > -1: 23 | ctx = de.gpu(args.gpu) 24 | else: 25 | ctx = de.cpu() 26 | 27 | vr = de.VideoReader(test_video, ctx, width=args.width, height=args.height) 28 | cnt = 0 29 | tic = time.time() 30 | while True: 31 | try: 32 | frame = vr.next() 33 | except StopIteration: 34 | break 35 | cnt += 1 36 | print(cnt, ' frames, elapsed time for sequential read: ', time.time() - tic) 37 | 38 | np.random.seed(args.seed) # fix seed for all random tests 39 | acc_indices = np.arange(len(vr)) 40 | np.random.shuffle(acc_indices) 41 | if args.random_frames > len(vr): 42 | warnings.warn('Number of random frames reduced to {} to fit test video'.format(len(vr))) 43 | args.random_frames = len(vr) 44 | indices = acc_indices[:args.random_frames] 45 | 46 | vr.seek(0) 47 | tic = time.time() 48 | for idx in indices: 49 | vr.seek(idx) 50 | frame = vr.next() 51 | 52 | print(len(indices), ' frames, elapsed time for random access(not accurate): ', time.time() - tic) 53 | 54 | vr.seek(0) 55 | tic = time.time() 56 | for idx in indices: 57 | frame = vr[idx] 58 | 59 | print(len(indices), ' frames, elapsed time for random access(accurate): ', time.time() - tic) 60 | -------------------------------------------------------------------------------- /tests/benchmark/bench_opencv.py: -------------------------------------------------------------------------------- 1 | """Benchmark using opencv's VideoCapture""" 2 | import time 3 | import random 4 | import numpy as np 5 | import cv2 6 | import argparse 7 | import warnings 8 | import numpy as np 9 | 10 | parser = argparse.ArgumentParser("OpenCV benchmark") 11 | parser.add_argument('--file', type=str, default='/tmp/testsrc_h264_100s_default.mp4', help='Test video') 12 | parser.add_argument('--seed', type=int, default=666, help='numpy random seed for random access indices') 13 | parser.add_argument('--random-frames', type=int, default=300, help='number of random frames to run') 14 | parser.add_argument('--width', type=int, default=320, help='resize frame width') 15 | parser.add_argument('--height', type=int, default=240, help='resize frame height') 16 | 17 | args = parser.parse_args() 18 | 19 | def cv2_seek_frame(cap, pos): 20 | cap.set(cv2.CAP_PROP_POS_FRAMES, pos) 21 | #print(pos, cap.get(cv2.CAP_PROP_POS_FRAMES)) 22 | 23 | class CV2VideoReader(object): 24 | def __init__(self, fn, width, height): 25 | self._cap = cv2.VideoCapture(fn) 26 | self._len = int(self._cap.get(cv2.CAP_PROP_FRAME_COUNT)) 27 | self._width = width 28 | self._height = height 29 | self._curr = 0 30 | 31 | def __len__(self): 32 | return self._len 33 | 34 | def __getitem__(self, idx): 35 | cv2_seek_frame(self._cap, idx) 36 | self._curr = idx 37 | return self.__next__() 38 | 39 | def __del__(self): 40 | self._cap.release() 41 | 42 | def __next__(self): 43 | if self._curr >= self.__len__(): 44 | raise StopIteration 45 | 46 | ret, frame = self._cap.read() 47 | if not ret: 48 | raise RuntimeError 49 | ret = cv2.resize(frame, (self._width, self._height)) 50 | self._curr += 1 51 | return ret 52 | 53 | def next(self): 54 | return self.__next__() 55 | 56 | 57 | class CV2VideoLoader(object): 58 | def __init__(self, fns, shape, interval, skip, shuffle): 59 | self._shape = shape 60 | self._interval = interval 61 | self._skip = skip 62 | self._shuffle = shuffle 63 | self._cap = [cv2.VideoCapture(fn) for fn in fns] 64 | # for cap in self._cap: 65 | # cap.set(cv2.CAP_PROP_FRAME_WIDTH, shape[2]) 66 | # cap.set(cv2.CAP_PROP_FRAME_HEIGHT, shape[1]) 67 | self._frame_len = sum([int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) for cap in self._cap]) 68 | self._curr = 0 69 | self._init_orders() 70 | self.reset() 71 | 72 | def __del__(self): 73 | for cap in self._cap: 74 | cap.release() 75 | 76 | def _init_orders(self): 77 | self._orders = [] 78 | for i, cap in enumerate(self._cap): 79 | l = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) 80 | order = np.arange(0, l, self._shape[0] * (1 + self._interval) - self._interval + self._skip) 81 | for o in order: 82 | self._orders.append((i, o)) 83 | 84 | def reset(self): 85 | self._curr = 0 86 | random.shuffle(self._orders) 87 | 88 | def __len__(self): 89 | return len(self._orders) 90 | 91 | def __next__(self): 92 | if self._curr >= self.__len__(): 93 | raise StopIteration 94 | 95 | i, o = self._orders[self._curr] 96 | cap = self._cap[i] 97 | cap.set(cv2.CAP_PROP_POS_FRAMES, o) 98 | data = np.empty(shape=self._shape) 99 | for j in range(self._shape[0]): 100 | ret, frame = cap.read() 101 | if not ret: 102 | raise RuntimeError 103 | data[j][:] = cv2.resize(frame, (self._shape[2], self._shape[1])) 104 | for k in range(self._interval): 105 | ret, frame = cap.read() 106 | self._curr += 1 107 | return 108 | 109 | def __iter__(self): 110 | return self 111 | 112 | vr = CV2VideoReader(args.file, width=args.width, height=args.height) 113 | tic = time.time() 114 | cnt = 0 115 | while True: 116 | try: 117 | frame = vr.__next__() 118 | cnt += 1 119 | except: 120 | break 121 | print(cnt, ' frames. Elapsed time for sequential read: ', time.time() - tic) 122 | 123 | vr = CV2VideoReader(args.file, width=args.width, height=args.height) 124 | np.random.seed(args.seed) # fix seed for all random tests 125 | acc_indices = np.arange(len(vr)) 126 | np.random.shuffle(acc_indices) 127 | if args.random_frames > len(vr): 128 | warnings.warn('Number of random frames reduced to {} to fit test video'.format(len(vr))) 129 | args.random_frames = len(vr) 130 | indices = acc_indices[:args.random_frames] 131 | 132 | tic = time.time() 133 | for idx in indices: 134 | frame = vr[idx] 135 | 136 | print(len(indices), ' frames, elapsed time for random access(not accurate): ', time.time() - tic) 137 | -------------------------------------------------------------------------------- /tests/benchmark/bench_pyav.py: -------------------------------------------------------------------------------- 1 | """Benchmark using opencv's VideoCapture""" 2 | import time 3 | import random 4 | import numpy as np 5 | import av 6 | import argparse 7 | import warnings 8 | import numpy as np 9 | import pims 10 | import cv2 11 | 12 | parser = argparse.ArgumentParser("PyAV benchmark") 13 | parser.add_argument('--file', type=str, default='/tmp/testsrc_h264_100s_default.mp4', help='Test video') 14 | parser.add_argument('--seed', type=int, default=666, help='numpy random seed for random access indices') 15 | parser.add_argument('--random-frames', type=int, default=300, help='number of random frames to run') 16 | parser.add_argument('--width', type=int, default=320, help='resize frame width') 17 | parser.add_argument('--height', type=int, default=240, help='resize frame height') 18 | 19 | args = parser.parse_args() 20 | 21 | 22 | class PyAVVideoReader(object): 23 | def __init__(self, fn, width, height, any_frame=False): 24 | self._cap = pims.Video(fn) 25 | self._len = len(self._cap) 26 | self._width = width 27 | self._height = height 28 | self._curr = 0 29 | 30 | def __len__(self): 31 | return self._len 32 | 33 | def __getitem__(self, idx): 34 | self._curr = idx 35 | frame = self._cap[idx] 36 | frame = cv2.resize(frame, (self._width, self._height)) 37 | return frame 38 | 39 | def __next__(self): 40 | if self._curr >= self.__len__(): 41 | raise StopIteration 42 | 43 | return self.__getitem__(self._curr + 1) 44 | 45 | def next(self): 46 | return self.__next__() 47 | 48 | vr = PyAVVideoReader(args.file, width=args.width, height=args.height) 49 | tic = time.time() 50 | cnt = 0 51 | while True: 52 | try: 53 | frame = vr.__next__() 54 | cnt += 1 55 | except: 56 | break 57 | print(cnt, ' frames. Elapsed time for sequential read: ', time.time() - tic) 58 | 59 | vr = PyAVVideoReader(args.file, width=args.width, height=args.height) 60 | np.random.seed(args.seed) # fix seed for all random tests 61 | acc_indices = np.arange(len(vr)) 62 | np.random.shuffle(acc_indices) 63 | if args.random_frames > len(vr): 64 | warnings.warn('Number of random frames reduced to {} to fit test video'.format(len(vr))) 65 | args.random_frames = len(vr) 66 | indices = acc_indices[:args.random_frames] 67 | 68 | tic = time.time() 69 | for idx in indices: 70 | frame = vr[idx] 71 | 72 | print(len(indices), ' frames, elapsed time for random access(accurate): ', time.time() - tic) 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /tests/benchmark/cv2.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmlc/decord/d2e56190286ae394032a8141885f76d5372bd44b/tests/benchmark/cv2.npy -------------------------------------------------------------------------------- /tests/cpp/audio/test_ffmpeg_audio_reader.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Yin, Weisu on 1/15/21. 3 | // 4 | 5 | #include 6 | #include 7 | 8 | 9 | int main() { 10 | auto audioReader = decord::GetAudioReader("/Users/weisy/Developer/yinweisu/decord/examples/example.mp3", -1, decord::kCPU, 0, 0); 11 | return 0; 12 | } -------------------------------------------------------------------------------- /tests/cpp/video/test_ffmpeg_video_reader.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | // #include 8 | // #include 9 | 10 | using NDArray = decord::runtime::NDArray; 11 | using namespace decord; 12 | 13 | std::time_t getTimeStamp() { 14 | std::chrono::time_point tp = std::chrono::time_point_cast(std::chrono::system_clock::now()); 15 | return tp.time_since_epoch().count(); 16 | } 17 | 18 | int main(int argc, const char **argv) { 19 | auto vr = decord::GetVideoReader("/tmp/testsrc_h264_10s_default.mp4", kCPU); 20 | LOG(INFO) << "Frame count: " << vr->GetFrameCount(); 21 | vr->QueryStreams(); 22 | NDArray array; 23 | int cnt = 0; 24 | auto start = getTimeStamp(); 25 | while (0) { 26 | array = vr->NextFrame(); 27 | // LOG(INFO) << array.Size(); 28 | if (!array.Size()) break; 29 | cnt++; 30 | // if (cnt > 200) break; 31 | // LOG(INFO) << "Frame: " << cnt; 32 | } 33 | auto end = getTimeStamp(); 34 | LOG(INFO) << cnt << " frame. Elapsed time: " << (end - start) / 1000.0; 35 | 36 | std::vector indices(vr->GetFrameCount()); 37 | std::iota(std::begin(indices), std::end(indices), 0); 38 | std::random_shuffle(std::begin(indices), std::end(indices)); 39 | 40 | start = getTimeStamp(); 41 | cnt = 0; 42 | for (size_t i = 0; i < 300; ++i) { 43 | if (i >= indices.size()) break; 44 | vr->SeekAccurate(indices[i]); 45 | array = vr->NextFrame(); 46 | cnt++; 47 | } 48 | end = getTimeStamp(); 49 | LOG(INFO) << cnt << " frame. Elapsed time: " << (end - start) / 1000.0; 50 | return 0; 51 | } -------------------------------------------------------------------------------- /tests/python/unittests/test_audio_reader.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | from decord import AudioReader, cpu, gpu 4 | from decord.base import DECORDError 5 | 6 | CTX = cpu(0) 7 | 8 | def get_single_channel_reader(): 9 | return AudioReader(os.path.join(os.path.dirname(__file__), '..', '..', 'cpp', 'audio', 'count_down.mov'), CTX) 10 | 11 | def get_double_channels_reader(): 12 | return AudioReader(os.path.join(os.path.dirname(__file__), '..', '..', 'cpp', 'audio', 'sample-mov-file.mov'), CTX, mono=False) 13 | 14 | def get_resampled_reader(): 15 | return AudioReader(os.path.join(os.path.dirname(__file__), '..', '..', 'cpp', 'audio', 'count_down.mov'), CTX, 4410) 16 | 17 | def get_channel_change_reader(): 18 | return AudioReader(os.path.join(os.path.dirname(__file__), '..', '..', 'cpp', 'audio', 'sample-mov-file.mov'), CTX) 19 | 20 | def test_single_channel_audio_reader(): 21 | ar = get_single_channel_reader() 22 | assert ar.shape() == (1, 482240) 23 | 24 | def test_double_channels_audio_reader(): 25 | ar = get_double_channels_reader() 26 | assert ar.shape() == (2, 5555200) 27 | 28 | def test_no_audio_stream(): 29 | from nose.tools import assert_raises 30 | assert_raises(DECORDError, AudioReader, os.path.join(os.path.dirname(__file__), '..', '..', 'test_data', 'video_0.mov'), CTX) 31 | 32 | def test_bytes_io(): 33 | fn = os.path.join(os.path.dirname(__file__), '..', '..', 'cpp', 'audio', 'count_down.mov') 34 | with open(fn, 'rb') as f: 35 | ar = AudioReader(f) 36 | assert ar.shape() == (1, 482240) 37 | ar2 = get_single_channel_reader() 38 | assert np.allclose(ar[10].asnumpy(), ar2[10].asnumpy()) 39 | 40 | def test_resample(): 41 | ar = get_resampled_reader() 42 | assert ar.shape() == (1, 48224) 43 | 44 | def test_channel_change(): 45 | ar = get_channel_change_reader() 46 | assert ar.shape() == (1, 5555200) 47 | 48 | def test_index(): 49 | ar = get_double_channels_reader() 50 | ar[0] 51 | ar[-1] 52 | 53 | def test_indices(): 54 | ar = get_double_channels_reader() 55 | ar[:] 56 | ar[-20:-10] 57 | 58 | def test_get_batch(): 59 | ar = get_double_channels_reader() 60 | ar.get_batch([-1,0,1,2,3]) 61 | 62 | def test_get_info(): 63 | ar = get_double_channels_reader() 64 | ar.get_info() 65 | 66 | def test_add_padding(): 67 | ar = get_single_channel_reader() 68 | num_channels = ar.shape()[0] 69 | num_padding = ar.add_padding() 70 | assert np.array_equal(ar[:num_padding].asnumpy(), np.zeros((num_channels, num_padding))) 71 | 72 | def test_free(): 73 | ar = get_single_channel_reader() 74 | del ar 75 | 76 | if __name__ == '__main__': 77 | import nose 78 | nose.runmodule() 79 | -------------------------------------------------------------------------------- /tests/python/unittests/test_av_reader.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | from decord import AVReader, cpu, gpu 4 | from decord.base import DECORDError 5 | 6 | CTX = cpu(0) 7 | 8 | def get_normal_av_reader(): 9 | return AVReader('/Users/weisy/Developer/yinweisu/decord/tests/cpp/audio/count_down.mov', CTX) 10 | 11 | def test_normal_av_reader(): 12 | av = get_normal_av_reader() 13 | assert len(av) == 328 14 | 15 | def test_bytes_io(): 16 | fn = os.path.join(os.path.dirname(__file__), '..', '..', 'cpp', 'audio', 'count_down.mov') 17 | with open(fn, 'rb') as f: 18 | av = AVReader(f) 19 | assert len(av) == 328 20 | av2 = get_normal_av_reader() 21 | audio, video = av[10] 22 | audio2, video2 = av2[10] 23 | assert np.allclose(audio.asnumpy(), audio2.asnumpy()) 24 | assert np.allclose(video.asnumpy(), video2.asnumpy()) 25 | 26 | def test_no_audio_stream(): 27 | from nose.tools import assert_raises 28 | assert_raises(DECORDError, AVReader, os.path.join(os.path.dirname(__file__), '..', '..', 'test_data', 'video_0.mov'), CTX) 29 | 30 | def test_index(): 31 | av = get_normal_av_reader() 32 | audio, video = av[0] 33 | 34 | def test_indices(): 35 | av = get_normal_av_reader() 36 | audio, video = av[:] 37 | 38 | def test_get_batch(): 39 | av = get_normal_av_reader() 40 | av.get_batch([-1,0,1,2,3]) 41 | 42 | def test_sync(): 43 | av = get_normal_av_reader() 44 | import simpleaudio 45 | audio = av[25:40][0] 46 | buffer = np.array([], dtype='float32') 47 | for samples in audio: 48 | buffer = np.append(buffer, samples.asnumpy()) 49 | play = simpleaudio.play_buffer(buffer, 1, 4, 44100) 50 | play.wait_done() 51 | 52 | if __name__ == '__main__': 53 | import nose 54 | nose.runmodule() 55 | -------------------------------------------------------------------------------- /tests/python/unittests/test_bridges.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | import decord 4 | from decord import VideoReader 5 | from decord.bridge import * 6 | 7 | def _get_default_test_video(): 8 | return VideoReader(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'examples', 'flipping_a_pancake.mkv'))) 9 | 10 | def test_mxnet_bridge(): 11 | try: 12 | from decord.bridge.mxnet import try_import_mxnet 13 | mxnet = try_import_mxnet() 14 | vr = _get_default_test_video() 15 | with use_mxnet(): 16 | frame = vr[0] 17 | assert isinstance(frame, mxnet.nd.NDArray) 18 | native_frame = bridge_in(frame) 19 | assert isinstance(native_frame, decord.nd.NDArray), type(native_frame) 20 | except ImportError: 21 | print('Skip test mxnet bridge as mxnet is not found') 22 | 23 | def test_torch_bridge(): 24 | try: 25 | from decord.bridge.torchdl import try_import_torch 26 | import torch 27 | vr = _get_default_test_video() 28 | with use_torch(): 29 | frame = vr[0] 30 | assert isinstance(frame, torch.Tensor), type(frame) 31 | native_frame = bridge_in(frame) 32 | assert isinstance(native_frame, decord.nd.NDArray), type(native_frame) 33 | except ImportError: 34 | print('Skip test torchdl bridge as torch is not found') 35 | 36 | def test_tf_bridge(): 37 | try: 38 | from decord.bridge.tf import try_import_tfdl 39 | import tensorflow as tf 40 | vr = _get_default_test_video() 41 | with use_tensorflow(): 42 | frame = vr[0] 43 | assert isinstance(frame, tf.Tensor), type(frame) 44 | native_frame = bridge_in(frame) 45 | assert isinstance(native_frame, decord.nd.NDArray), type(native_frame) 46 | except ImportError: 47 | print('Skip test tensorflow bridge as tf is not found') 48 | 49 | def test_tvm_bridge(): 50 | try: 51 | from decord.bridge.tvm import try_import_tvm 52 | tvm = try_import_tvm() 53 | vr = _get_default_test_video() 54 | with use_tvm(): 55 | frame = vr[0] 56 | assert isinstance(frame, tvm.nd.NDArray) 57 | native_frame = bridge_in(frame) 58 | assert isinstance(native_frame, decord.nd.NDArray), type(native_frame) 59 | except ImportError: 60 | print('Skip test tvm bridge as tvm is not found') 61 | 62 | def test_threaded_bridge(): 63 | # issue #85 64 | from decord import cpu, gpu 65 | from multiprocessing.dummy import Pool as ThreadPool 66 | 67 | video_paths = [ 68 | os.path.expanduser('~/Dev/decord/examples/flipping_a_pancake.mkv'), #list of paths to video 69 | ] 70 | 71 | def process_path(path): 72 | vr = VideoReader(path, ctx=cpu(0)) 73 | 74 | for i in range(len(vr)): 75 | frame = vr[i] 76 | 77 | pool = ThreadPool(1) 78 | pool.map(process_path, video_paths) 79 | 80 | if __name__ == '__main__': 81 | import nose 82 | nose.runmodule() 83 | -------------------------------------------------------------------------------- /tests/python/unittests/test_video_reader.py: -------------------------------------------------------------------------------- 1 | import os 2 | import random 3 | import numpy as np 4 | from decord import VideoReader, cpu, gpu 5 | from decord.base import DECORDError 6 | 7 | CTX = cpu(0) 8 | 9 | def _get_default_test_video(ctx=CTX): 10 | return VideoReader(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'examples', 'flipping_a_pancake.mkv')), ctx=ctx) 11 | 12 | def _get_corrupted_test_video(ctx=CTX): 13 | return VideoReader(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'test_data', 'corrupted.mp4')), ctx=ctx) 14 | 15 | def _get_rotated_test_video(rot, height=-1, width=-1, ctx=CTX): 16 | return VideoReader(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'test_data', f'video_{rot}.mov')), height=height, width=width, ctx=ctx) 17 | 18 | def _get_unordered_test_video(ctx=CTX): 19 | # video with frames not ordered by pts 20 | return VideoReader(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'test_data', 'unordered.mov')), ctx=ctx) 21 | 22 | def test_video_reader_len(): 23 | vr = _get_default_test_video() 24 | assert len(vr) == 310 25 | 26 | def test_video_reader_read_sequential(): 27 | vr = _get_default_test_video() 28 | for i in range(len(vr)): 29 | frame = vr[i] 30 | 31 | def test_video_reader_read_slice(): 32 | vr = _get_default_test_video() 33 | frames = vr[:] 34 | assert frames.shape[0] == len(vr) 35 | 36 | vr = _get_default_test_video() 37 | frames = vr[:10] 38 | assert frames.shape[0] == 10 39 | 40 | def test_video_reader_read_random(): 41 | vr = _get_default_test_video() 42 | lst = list(range(len(vr))) 43 | random.shuffle(lst) 44 | num = min(len(lst), 10) 45 | rand_lst = lst[:num] 46 | for i in rand_lst: 47 | frame = vr[i] 48 | 49 | def test_video_get_batch(): 50 | vr = _get_default_test_video() 51 | lst = list(range(len(vr))) 52 | random.shuffle(lst) 53 | num = min(len(lst), 10) 54 | rand_lst = lst[:num] 55 | frames = vr.get_batch(rand_lst) 56 | 57 | def test_video_corrupted_get_batch(): 58 | from nose.tools import assert_raises 59 | vr = _get_corrupted_test_video(ctx=cpu(0)) 60 | assert_raises(DECORDError, vr.get_batch, range(40)) 61 | 62 | def test_rotated_video(): 63 | # Input videos are all h=320 w=568 in metadata, but 64 | # rotation should be applied to recover correctly 65 | # displayed image (from rotation metadata). 66 | for rot in [0, 180]: 67 | # shot in landscape; correct video orientation has 68 | # same shape as "original" frame 69 | vr = _get_rotated_test_video(rot, ctx=cpu(0)) 70 | assert vr[0].shape == (320, 568, 3) 71 | assert vr[:].shape == (3, 320, 568, 3) 72 | for rot in [90, 270]: 73 | # shot in portrait mode; correct video orientation has 74 | # swapped width and height (height>>width) 75 | vr = _get_rotated_test_video(rot, ctx=cpu(0)) 76 | assert vr[0].shape == (568, 320, 3), vr[0].shape 77 | assert vr[:].shape == (3, 568, 320, 3) 78 | # resize is applied in target shape 79 | vr = _get_rotated_test_video(rot, height=300, width=200, ctx=cpu(0)) 80 | assert vr[0].shape == (300, 200, 3), vr[0].shape 81 | 82 | def test_frame_timestamps(): 83 | vr = _get_default_test_video() 84 | frame_ts = vr.get_frame_timestamp(range(5)) 85 | assert np.allclose(frame_ts[:,0], [0.0, 0.033, 0.067, 0.1, 0.133]) 86 | 87 | vr = _get_unordered_test_video() 88 | '''ffprobe output: 89 | pts_time=0.000000 dts_time=-0.062500 90 | pts_time=0.093750 dts_time=-0.031250 91 | pts_time=0.031250 dts_time=0.000000 92 | pts_time=0.062500 dts_time=0.031250 93 | ''' 94 | frame_ts = vr.get_frame_timestamp(range(4)) 95 | assert np.allclose(frame_ts[:,0], [0.0, 0.03125, 0.0625, 0.09375]), frame_ts[:,0] 96 | 97 | def test_bytes_io(): 98 | fn = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'examples', 'flipping_a_pancake.mkv')) 99 | with open(fn, 'rb') as f: 100 | vr = VideoReader(f) 101 | assert len(vr) == 310 102 | vr2 = _get_default_test_video() 103 | assert np.mean(np.abs(vr[10].asnumpy().astype('float') - vr2[10].asnumpy().astype('float'))) < 2 # average pixel diff < 2 104 | 105 | 106 | if __name__ == '__main__': 107 | import nose 108 | nose.runmodule() 109 | -------------------------------------------------------------------------------- /tests/test_data/corrupted.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmlc/decord/d2e56190286ae394032a8141885f76d5372bd44b/tests/test_data/corrupted.mp4 -------------------------------------------------------------------------------- /tests/test_data/unordered.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmlc/decord/d2e56190286ae394032a8141885f76d5372bd44b/tests/test_data/unordered.mov -------------------------------------------------------------------------------- /tests/test_data/video_0.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmlc/decord/d2e56190286ae394032a8141885f76d5372bd44b/tests/test_data/video_0.mov -------------------------------------------------------------------------------- /tests/test_data/video_180.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmlc/decord/d2e56190286ae394032a8141885f76d5372bd44b/tests/test_data/video_180.mov -------------------------------------------------------------------------------- /tests/test_data/video_270.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmlc/decord/d2e56190286ae394032a8141885f76d5372bd44b/tests/test_data/video_270.mov -------------------------------------------------------------------------------- /tests/test_data/video_90.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmlc/decord/d2e56190286ae394032a8141885f76d5372bd44b/tests/test_data/video_90.mov -------------------------------------------------------------------------------- /tests/utils/generate_test_videos.cmd: -------------------------------------------------------------------------------- 1 | # generate 10 sec testsrc H.264 video, with all default keyframe properties 2 | ffmpeg -n -f lavfi -i testsrc=duration=300:size=1280x720:rate=1 -pix_fmt yuv420p -vcodec libx264 /tmp/testsrc_h264_10s_default.mp4 3 | 4 | # generate 100 sec testsrc H.264 video, with all default keyframe properties 5 | ffmpeg -n -f lavfi -i testsrc=duration=3000:size=1280x720:rate=1 -pix_fmt yuv420p -vcodec libx264 /tmp/testsrc_h264_100s_default.mp4 6 | 7 | # generate 100 sec testsrc H.264 video, with keyframes interval 5 8 | ffmpeg -n -f lavfi -i testsrc=duration=3000:size=1280x720:rate=1 -pix_fmt yuv420p -vcodec libx264 -x264-params keyint=5:scenecut=0 /tmp/testsrc_h264_100s_ki5.mp4 -------------------------------------------------------------------------------- /tests/utils/generate_test_videos.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # generate 10 sec testsrc H.264 video, with all default keyframe properties 4 | ffmpeg -n -f lavfi -i testsrc=duration=300:size=1280x720:rate=1 -pix_fmt yuv420p -vcodec libx264 /tmp/testsrc_h264_10s_default.mp4 5 | 6 | # generate 100 sec testsrc H.264 video, with all default keyframe properties 7 | ffmpeg -n -f lavfi -i testsrc=duration=3000:size=1280x720:rate=1 -pix_fmt yuv420p -vcodec libx264 /tmp/testsrc_h264_100s_default.mp4 8 | 9 | # generate 100 sec testsrc H.264 video, with keyframes interval 5 10 | ffmpeg -n -f lavfi -i testsrc=duration=3000:size=1280x720:rate=1 -pix_fmt yuv420p -vcodec libx264 -x264-params keyint=5:scenecut=0 /tmp/testsrc_h264_100s_ki5.mp4 11 | -------------------------------------------------------------------------------- /tools/build_macos_10_9.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # this file is actually for building decord for macos >=10.9 on github action 4 | 5 | set -e 6 | 7 | # pwd 8 | pwd 9 | 10 | # build tools 11 | brew install cmake nasm yasm 12 | 13 | # cmake > 3.8 14 | cmake --version 15 | 16 | # workspace 17 | pushd ~ 18 | mkdir ~/ffmpeg_sources 19 | 20 | # libx264 21 | cd ~/ffmpeg_sources 22 | git clone --depth 1 https://code.videolan.org/videolan/x264.git 23 | cd x264 24 | ./configure --prefix="$HOME/ffmpeg_build" --bindir="$HOME/bin" --enable-shared --extra-cflags=-mmacosx-version-min=10.9 --extra-ldflags=-mmacosx-version-min=10.9 25 | make -j$(nproc) 26 | make install 27 | 28 | # libvpx 29 | cd ~/ffmpeg_sources 30 | git clone --depth 1 https://chromium.googlesource.com/webm/libvpx.git 31 | cd libvpx 32 | ./configure --prefix="$HOME/ffmpeg_build" --disable-examples --disable-unit-tests --enable-vp9-highbitdepth --as=yasm --enable-shared --extra-cflags=-mmacosx-version-min=10.9 --extra-cxxflags=-mmacosx-version-min=10.9 33 | make -j$(nproc) 34 | make install 35 | 36 | # ffmpeg 37 | cd ~/ffmpeg_sources 38 | curl -O -L https://ffmpeg.org/releases/ffmpeg-4.1.6.tar.bz2 39 | tar xjf ffmpeg-4.1.6.tar.bz2 40 | cd ffmpeg-4.1.6 41 | ./configure \ 42 | --prefix="$HOME/ffmpeg_build" \ 43 | --enable-shared \ 44 | --extra-cflags="-mmacosx-version-min=10.9 -I$HOME/ffmpeg_build/include" \ 45 | --extra-cxxflags="-mmacosx-version-min=10.9 -I$HOME/ffmpeg_build/include" \ 46 | --extra-ldflags="-mmacosx-version-min=10.9 -L$HOME/ffmpeg_build/lib" \ 47 | --bindir="$HOME/bin" \ 48 | --enable-gpl \ 49 | --enable-nonfree \ 50 | --enable-libvpx \ 51 | --enable-libx264 \ 52 | --disable-static 53 | make 54 | make install 55 | 56 | # built libs 57 | ls ~/ffmpeg_build/lib 58 | 59 | # decord 60 | popd 61 | pwd 62 | mkdir -p build && cd build 63 | cmake .. -DUSE_CUDA=0 -DFFMPEG_DIR="$HOME/ffmpeg_build" 64 | make -j$(nproc) 65 | ls -lh 66 | -------------------------------------------------------------------------------- /tools/build_manylinux2010.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # this file is actually for building decord for manylinux2010 on github action 4 | 5 | set -e 6 | 7 | # pwd 8 | pwd 9 | 10 | # build tools 11 | yum install -y autoconf automake bzip2 bzip2-devel freetype-devel gcc gcc-c++ git libtool make mercurial pkgconfig zlib-devel 12 | 13 | # cmake 14 | pushd ~ 15 | curl -O -L https://github.com/Kitware/CMake/releases/download/v3.19.1/cmake-3.19.1-Linux-x86_64.sh 16 | chmod +x ./cmake-3.19.1-Linux-x86_64.sh 17 | ./cmake-3.19.1-Linux-x86_64.sh --skip-license --prefix=/usr/local 18 | /usr/local/bin/cmake -version 19 | 20 | # workspace 21 | mkdir ~/ffmpeg_sources 22 | 23 | # nasm 24 | cd ~/ffmpeg_sources 25 | curl -O -L https://github.com/dmlc/decord/files/5685923/nasm-2.14.02.zip 26 | unzip nasm-2.14.02.zip 27 | cd nasm-2.14.02 28 | ./autogen.sh 29 | ./configure --prefix="$HOME/ffmpeg_build" --bindir="$HOME/bin" 30 | make -j$(nproc) 31 | make install 32 | 33 | # yasm 34 | cd ~/ffmpeg_sources 35 | curl -O -L https://www.tortall.net/projects/yasm/releases/yasm-1.3.0.tar.gz 36 | tar xzf yasm-1.3.0.tar.gz 37 | cd yasm-1.3.0 38 | ./configure --prefix="$HOME/ffmpeg_build" --bindir="$HOME/bin" 39 | make -j$(nproc) 40 | make install 41 | 42 | # libx264 43 | cd ~/ffmpeg_sources 44 | git clone --depth 1 https://code.videolan.org/videolan/x264.git 45 | cd x264 46 | export PATH="$HOME/bin:$PATH" 47 | PKG_CONFIG_PATH="$HOME/ffmpeg_build/lib/pkgconfig" ./configure --prefix="$HOME/ffmpeg_build" --bindir="$HOME/bin" --enable-shared --enable-pic 48 | make -j$(nproc) 49 | make install 50 | 51 | # libvpx 52 | cd ~/ffmpeg_sources 53 | git clone --depth 1 https://chromium.googlesource.com/webm/libvpx.git 54 | cd libvpx 55 | export PATH="$HOME/bin:$PATH" 56 | ./configure --prefix="$HOME/ffmpeg_build" --disable-examples --disable-unit-tests --enable-vp9-highbitdepth --as=yasm --enable-shared --enable-pic 57 | make -j$(nproc) 58 | make install 59 | 60 | # ffmpeg 61 | cd ~/ffmpeg_sources 62 | curl -O -L https://ffmpeg.org/releases/ffmpeg-4.1.6.tar.bz2 63 | tar xjf ffmpeg-4.1.6.tar.bz2 64 | cd ffmpeg-4.1.6 65 | export PATH="$HOME/bin:$PATH" 66 | PKG_CONFIG_PATH="$HOME/ffmpeg_build/lib/pkgconfig" ./configure \ 67 | --prefix="$HOME/ffmpeg_build" \ 68 | --extra-cflags="-I$HOME/ffmpeg_build/include" \ 69 | --extra-ldflags="-L$HOME/ffmpeg_build/lib" \ 70 | --extra-libs=-lpthread \ 71 | --extra-libs=-lm \ 72 | --bindir="$HOME/bin" \ 73 | --enable-gpl \ 74 | --enable-libvpx \ 75 | --enable-libx264 \ 76 | --enable-nonfree \ 77 | --disable-static \ 78 | --enable-shared \ 79 | --enable-pic 80 | make -j$(nproc) 81 | make install 82 | 83 | # build libs 84 | ls ~/ffmpeg_build/lib 85 | 86 | # decord 87 | popd 88 | pwd 89 | ls .. 90 | mkdir -p ../build 91 | pushd ../build 92 | /usr/local/bin/cmake .. -DUSE_CUDA=0 -DFFMPEG_DIR=~/ffmpeg_build 93 | make -j$(nproc) 94 | cp libdecord.so /usr/local/lib/ 95 | popd 96 | -------------------------------------------------------------------------------- /tools/update_version.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is the global script that set the version information of DECORD. 3 | This script runs and update all the locations that related to versions 4 | List of affected files: 5 | - decord-root/python/decord/_ffi/libinfo.py 6 | - decord-root/include/decord/runtime/c_runtime_api.h 7 | - decord-root/src/runtime/file_util.cc 8 | """ 9 | import os 10 | import re 11 | # current version 12 | # We use the version of the incoming release for code 13 | # that is under development 14 | __version__ = "0.6.0" 15 | 16 | # Implementations 17 | def update(file_name, pattern, repl): 18 | update = [] 19 | hit_counter = 0 20 | need_update = False 21 | for l in open(file_name): 22 | result = re.findall(pattern, l) 23 | if result: 24 | assert len(result) == 1 25 | hit_counter += 1 26 | if result[0] != repl: 27 | l = re.sub(pattern, repl, l) 28 | need_update = True 29 | print("%s: %s->%s" % (file_name, result[0], repl)) 30 | else: 31 | print("%s: version is already %s" % (file_name, repl)) 32 | 33 | update.append(l) 34 | if hit_counter != 1: 35 | raise RuntimeError("Cannot find version in %s" % file_name) 36 | 37 | if need_update: 38 | with open(file_name, "w") as output_file: 39 | for l in update: 40 | output_file.write(l) 41 | 42 | 43 | def main(): 44 | curr_dir = os.path.dirname(os.path.abspath(os.path.expanduser(__file__))) 45 | proj_root = os.path.abspath(os.path.join(curr_dir, "..")) 46 | # python path 47 | update(os.path.join(proj_root, "python", "decord", "_ffi", "libinfo.py"), 48 | r"(?<=__version__ = \")[.0-9a-z]+", __version__) 49 | # C++ header 50 | update(os.path.join(proj_root, "include", "decord", "runtime", "c_runtime_api.h"), 51 | "(?<=DECORD_VERSION \")[.0-9a-z]+", __version__) 52 | # file util 53 | update(os.path.join(proj_root, "src", "runtime", "file_util.cc"), 54 | "(?<=std::string version = \")[.0-9a-z]+", __version__) 55 | 56 | if __name__ == "__main__": 57 | main() 58 | --------------------------------------------------------------------------------