Add tests to check VCN activity tracing in perfetto output (#75)

The test will run sampling on the example from the rocDecode repo:
https://github.com/ROCm/rocDecode/tree/develop/samples/videoDecodeBatch

Then, ensure that the Perfetto output captures VCN activity in the trace.

[ROCm/rocprofiler-systems commit: 6dd4938b3e]
This commit is contained in:
Sajina PK
2025-01-10 10:38:05 -05:00
committed by Sajina Kandy
orang tua 50d63cbb03
melakukan b502b03be2
16 mengubah file dengan 4034 tambahan dan 15 penghapusan
@@ -102,7 +102,7 @@ jobs:
-DROCPROFSYS_PYTHON_ENVS="py3.6;py3.7;py3.8;py3.9;py3.10;py3.11"
-DROCPROFSYS_CI_MPI_RUN_AS_ROOT=ON
-DROCPROFSYS_MAX_THREADS=64
-DROCPROFSYS_DISABLE_EXAMPLES="transpose;rccl"
-DROCPROFSYS_DISABLE_EXAMPLES="transpose;rccl;videodecode"
-DROCPROFSYS_BUILD_NUMBER=${{ github.run_attempt }}
- name: Install
@@ -83,10 +83,11 @@ jobs:
ROCM_MAJOR=$(echo ${ROCM_VERSION} | sed 's/\./ /g' | awk '{print $1}')
ROCM_MINOR=$(echo ${ROCM_VERSION} | sed 's/\./ /g' | awk '{print $2}')
ROCM_VERSN=$(( (${ROCM_MAJOR}*10000)+(${ROCM_MINOR}*100) ))
if [ "${OS_VERSION_MAJOR}" -eq 8 ]; then PERL_REPO=powertools; else PERL_REPO=crb; fi && \
if [ "${OS_VERSION_MAJOR}" -eq 8 ]; then PERL_REPO=powertools; else PERL_REPO=crb; fi
dnf -y --enablerepo=${PERL_REPO} install perl-File-BaseDir
yum install -y https://repo.radeon.com/amdgpu-install/${{ matrix.rocm-version }}/rhel/${{ matrix.os-release }}/amdgpu-install-${ROCM_MAJOR}.${ROCM_MINOR}.${ROCM_VERSN}-1${RPM_TAG}.noarch.rpm
yum install -y rocm-dev rocm-smi-lib roctracer-dev rocprofiler-dev libpciaccess
yum install -y rocm-dev rocdecode-devel
if [ "${OS_VERSION_MAJOR}" -gt 8 ]; then dnf install -y libavcodec-free-devel libavformat-free-devel; fi
- name: Configure, Build, and Test
timeout-minutes: 115
@@ -120,7 +121,7 @@ jobs:
-DROCPROFSYS_DISABLE_EXAMPLES="transpose;rccl"
-DROCPROFSYS_BUILD_NUMBER=${{ github.run_attempt }}
--
-LE "transpose|rccl"
-LE "transpose|rccl|videodecode"
- name: Install
timeout-minutes: 10
@@ -157,7 +157,7 @@ jobs:
-DROCPROFSYS_PYTHON_PREFIX=/opt/conda/envs
-DROCPROFSYS_PYTHON_ENVS="py3.6;py3.7;py3.8;py3.9;py3.10;py3.11"
-DROCPROFSYS_MAX_THREADS=64
-DROCPROFSYS_DISABLE_EXAMPLES="transpose;rccl"
-DROCPROFSYS_DISABLE_EXAMPLES="transpose;rccl;videodecode"
-DROCPROFSYS_BUILD_NUMBER=${{ github.run_attempt }}
-DMPI_HEADERS_ALLOW_MPICH=OFF
@@ -248,7 +248,7 @@ jobs:
rocm-version: ['6.3']
mpi-headers: ['OFF']
build-jobs: ['3']
ctest-exclude: ['-LE "transpose"']
ctest-exclude: ['-LE "transpose|videodecode"']
env:
BUILD_TYPE: MinSizeRel
@@ -268,10 +268,16 @@ jobs:
command: |
apt-get update &&
apt-get install -y software-properties-common wget gnupg2 &&
wget -q -O - https://repo.radeon.com/rocm/rocm.gpg.key | apt-key add - &&
echo "deb [arch=amd64] https://repo.radeon.com/rocm/apt/${{ matrix.rocm-version }}/ ubuntu main" | tee /etc/apt/sources.list.d/rocm.list &&
ROCM_VERSION=${{ matrix.rocm-version }} &&
ROCM_MAJOR=$(echo ${ROCM_VERSION} | sed 's/\./ /g' | awk '{print $1}') &&
ROCM_MINOR=$(echo ${ROCM_VERSION} | sed 's/\./ /g' | awk '{print $2}') &&
ROCM_VERSN=$(( (${ROCM_MAJOR}*10000)+(${ROCM_MINOR}*100) )) &&
echo "ROCM_MAJOR=${ROCM_MAJOR} ROCM_MINOR=${ROCM_MINOR} ROCM_VERSN=${ROCM_VERSN}" &&
wget -q https://repo.radeon.com/amdgpu-install/${{ matrix.rocm-version }}/ubuntu/focal/amdgpu-install_${ROCM_MAJOR}.${ROCM_MINOR}.${ROCM_VERSN}-1_all.deb &&
apt-get install -y ./amdgpu-install_${ROCM_MAJOR}.${ROCM_MINOR}.${ROCM_VERSN}-1_all.deb &&
apt-get update &&
apt-get install -y autoconf bison build-essential clang curl gettext libfabric-dev libnuma1 libomp-dev libopenmpi-dev libpapi-dev libtool libudev1 m4 openmpi-bin python3-pip rocm-dev texinfo &&
apt-get install -y rocdecode-dev libavformat-dev libavcodec-dev &&
wget https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v47.0/linux-amd64/trace_processor_shell -P /opt/trace_processor/bin &&
chmod +x /opt/trace_processor/bin/trace_processor_shell &&
python3 -m pip install --upgrade pip &&
@@ -483,7 +489,7 @@ jobs:
-DDYNINST_BUILD_STATIC_LIBS=OFF
-DDYNINST_ELFUTILS_DOWNLOAD_VERSION=${{ env.ELFUTILS_DOWNLOAD_VERSION }}
-DROCPROFSYS_MAX_THREADS=64
-DROCPROFSYS_DISABLE_EXAMPLES="transpose;rccl"
-DROCPROFSYS_DISABLE_EXAMPLES="transpose;rccl;videodecode"
-DROCPROFSYS_BUILD_NUMBER=${{ github.run_attempt }}
-DMPI_HEADERS_ALLOW_MPICH=ON
@@ -622,5 +628,5 @@ jobs:
-DROCPROFSYS_USE_ROCM=OFF
-DROCPROFSYS_USE_RCCL=OFF
-DROCPROFSYS_MAX_THREADS=64
-DROCPROFSYS_DISABLE_EXAMPLES="transpose;rccl"
-DROCPROFSYS_DISABLE_EXAMPLES="transpose;rccl;videodecode"
-DROCPROFSYS_BUILD_NUMBER=${{ github.run_attempt }}
@@ -114,11 +114,15 @@ jobs:
max_attempts: 5
shell: bash
command: |
wget -q -O - https://repo.radeon.com/rocm/rocm.gpg.key | apt-key add -
echo "deb [arch=amd64] https://repo.radeon.com/rocm/apt/${{ matrix.rocm-version }}/ jammy main" | tee /etc/apt/sources.list.d/rocm.list
ROCM_VERSION=${{ matrix.rocm-version }}
ROCM_MAJOR=$(echo ${ROCM_VERSION} | sed 's/\./ /g' | awk '{print $1}')
ROCM_MINOR=$(echo ${ROCM_VERSION} | sed 's/\./ /g' | awk '{print $2}')
ROCM_VERSN=$(( (${ROCM_MAJOR}*10000)+(${ROCM_MINOR}*100) ))
echo "ROCM_MAJOR=${ROCM_MAJOR} ROCM_MINOR=${ROCM_MINOR} ROCM_VERSN=${ROCM_VERSN}"
wget -q https://repo.radeon.com/amdgpu-install/${{ matrix.rocm-version }}/ubuntu/jammy/amdgpu-install_${ROCM_MAJOR}.${ROCM_MINOR}.${ROCM_VERSN}-1_all.deb
apt-get install -y ./amdgpu-install_${ROCM_MAJOR}.${ROCM_MINOR}.${ROCM_VERSN}-1_all.deb
apt-get update
ROCM_VERSION=$(apt-cache search rocm-dev[0-9] | awk '{print $1}' | sed 's/rocm-dev//g')
apt-get install -y {rocm-dev,hip-dev,roctracer-dev,rocprofiler-dev,rocm-smi-lib,rocminfo}${ROCM_VERSION}
apt-get install -y rocm-dev rocdecode-dev libavformat-dev libavcodec-dev
echo "/opt/rocm/bin" >> $GITHUB_PATH
echo "ROCM_PATH=/opt/rocm" >> $GITHUB_ENV
echo "LD_LIBRARY_PATH=/opt/rocm/lib:${LD_LIBRARY_PATH}" >> $GITHUB_ENV
@@ -185,6 +189,8 @@ jobs:
-DROCPROFSYS_DISABLE_EXAMPLES="transpose;rccl"
-DROCPROFSYS_BUILD_NUMBER=${{ github.run_attempt }}
-DUSE_CLANG_OMP=OFF
--
-LE "transpose|rccl|videodecode"
- name: Install
timeout-minutes: 10
@@ -84,7 +84,7 @@ jobs:
wget -q https://repo.radeon.com/amdgpu-install/${{ matrix.rocm-version }}/ubuntu/noble/amdgpu-install_${ROCM_MAJOR}.${ROCM_MINOR}.${ROCM_VERSN}-1_all.deb
apt-get install -y ./amdgpu-install_${ROCM_MAJOR}.${ROCM_MINOR}.${ROCM_VERSN}-1_all.deb
apt-get update
apt-get install -y rocm-dev
apt-get install -y rocm-dev rocdecode-dev libavformat-dev libavcodec-dev
- name: Configure
timeout-minutes: 30
@@ -171,6 +171,9 @@ if(ROCPROFSYS_USE_MPI)
list(APPEND _DEBIAN_PACKAGE_DEPENDS "libmpich-dev")
endif()
endif()
if(ROCPROFSYS_BUILD_TESTING)
list(APPEND _DEBIAN_PACKAGE_DEPENDS "rocdecode-test")
endif()
string(REPLACE ";" ", " _DEBIAN_PACKAGE_DEPENDS "${_DEBIAN_PACKAGE_DEPENDS}")
set(CPACK_DEBIAN_PACKAGE_DEPENDS
"${_DEBIAN_PACKAGE_DEPENDS}"
@@ -56,3 +56,4 @@ add_subdirectory(rewrite-caller)
add_subdirectory(causal)
add_subdirectory(trace-time-window)
add_subdirectory(fork)
add_subdirectory(videodecode)
@@ -0,0 +1,160 @@
cmake_minimum_required(VERSION 3.18.4 FATAL_ERROR)
project(rocprofiler-systems-videodecode-example LANGUAGES CXX)
# This example requires hip and rocdecode.
find_package(HIP QUIET)
if(NOT HIP_FOUND)
message(WARNING "hip is not found. Skip videodecode example.")
return()
endif()
message(STATUS "hip found: ${hip_DIR}")
# Set AMD Clang as default compiler
if(NOT DEFINED CMAKE_CXX_COMPILER)
set(CMAKE_CXX_COMPILER ${ROCmVersion_DIR}/bin/amdclang++)
endif()
set(CMAKE_CXX_STANDARD 17)
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/../../cmake)
list(APPEND CMAKE_PREFIX_PATH ${ROCmVersion_DIR}/hip ${ROCmVersion_DIR})
list(APPEND CMAKE_MODULE_PATH ${ROCmVersion_DIR}/share/rocdecode/cmake)
set(CMAKE_BUILD_TYPE "RelWithDebInfo")
string(REPLACE " " ";" _FLAGS "${CMAKE_CXX_FLAGS_DEBUG}")
if(ROCPROFSYS_DISABLE_EXAMPLES)
get_filename_component(_DIR ${CMAKE_CURRENT_LIST_DIR} NAME)
if(${PROJECT_NAME} IN_LIST ROCPROFSYS_DISABLE_EXAMPLES OR ${_DIR} IN_LIST
ROCPROFSYS_DISABLE_EXAMPLES)
return()
endif()
endif()
function(videodecode_message _MSG_TYPE)
if("${CMAKE_PROJECT_NAME}" STREQUAL "rocprofiler-systems"
AND "$ENV{ROCPROFSYS_CI}"
AND "${_MSG_TYPE}" MATCHES "WARNING")
set(_MSG_TYPE STATUS) # don't generate warnings during CI
endif()
if("${CMAKE_PROJECT_NAME}" STREQUAL "rocprofiler-systems")
rocprofiler_systems_message(${_MSG_TYPE} ${ARGN})
else()
message(${_MSG_TYPE} ${ARGN})
endif()
endfunction()
# Find RocDecode
find_package(rocDecode REQUIRED)
if(NOT ROCDECODE_FOUND)
videodecode_message(AUTHOR_WARNING "${PROJECT_NAME} skipped. Missing RocDecode...")
return()
endif()
find_path(
ROCDECODE_INCLUDE_DIR
NAMES rocdecode.h
PATHS ${ROCmVersion_DIR}/include/rocdecode)
find_library(
ROCDECODE_LIBRARY
NAMES rocdecode
HINTS ${ROCmVersion_DIR}/lib)
mark_as_advanced(ROCDECODE_INCLUDE_DIR ROCDECODE_LIBRARY)
if(ROCDECODE_INCLUDE_DIR AND ROCDECODE_LIBRARY)
set(ROCDECODE_FOUND TRUE)
else()
videodecode_message(AUTHOR_WARNING "${PROJECT_NAME} skipped. Missing RocDecode ...")
endif()
# Find FFMPEG
find_package(FFmpeg)
if(NOT FFMPEG_FOUND)
videodecode_message(AUTHOR_WARNING "${PROJECT_NAME} skipped. Missing FFMPEG...")
return()
endif()
message(STATUS "FFMPEG libraries: ${FFMPEG_LIBRARIES}")
message(STATUS "FFMPEG AVFORMAT version: ${_FFMPEG_AVFORMAT_VERSION}")
message(STATUS "FFMPEG AVCODEC version: ${_FFMPEG_AVCODEC_VERSION}")
message(STATUS "FFMPEG AVUTIL version: ${_FFMPEG_AVUTIL_VERSION}")
find_path(
FFMPEG_INCLUDE_DIR
NAMES libavcodec/avcodec.h libavformat/avformat.h libavutil/avutil.h
PATHS ${FFMPEG_INCLUDE_DIRS}
PATH_SUFFIXES ffmpeg libav)
find_library(
AVCODEC_LIBRARY
NAMES avcodec
PATHS ${FFMPEG_LIBRARY_DIRS})
find_library(
AVFORMAT_LIBRARY
NAMES avformat
PATHS ${FFMPEG_LIBRARY_DIRS})
find_library(
AVUTIL_LIBRARY
NAMES avutil
PATHS ${FFMPEG_LIBRARY_DIRS})
set(FFMPEG_LIBRARIES ${AVCODEC_LIBRARY} ${AVFORMAT_LIBRARY} ${AVUTIL_LIBRARY})
set(FFMPEG_INCLUDE_DIRS ${FFMPEG_INCLUDE_DIR})
mark_as_advanced(FFMPEG_INCLUDE_DIR AVCODEC_LIBRARY AVFORMAT_LIBRARY AVUTIL_LIBRARY)
if(FFMPEG_FOUND AND ROCDECODE_FOUND)
# HIP
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} hip::host)
# FFMPEG
include_directories(${FFMPEG_INCLUDE_DIR})
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} ${FFMPEG_LIBRARIES})
# filesystem: c++ compilers less than equal to 8.5 need explicit link with stdc++fs
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS_EQUAL "8.5")
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} stdc++fs)
endif()
# rocDecode
include_directories(${ROCDECODE_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/..)
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} ${ROCDECODE_LIBRARY})
# Threads
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} Threads::Threads)
add_executable(videodecode videodecodebatch.cpp roc_video_dec.cpp)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++17")
target_link_libraries(videodecode ${LINK_LIBRARY_LIST})
target_compile_options(videodecode PRIVATE ${_FLAGS})
# FFMPEG multi-version support
if(_FFMPEG_AVCODEC_VERSION VERSION_LESS_EQUAL 58.134.100)
target_compile_definitions(videodecode PUBLIC USE_AVCODEC_GREATER_THAN_58_134=0)
else()
target_compile_definitions(videodecode PUBLIC USE_AVCODEC_GREATER_THAN_58_134=1)
endif()
if(ROCPROFSYS_INSTALL_EXAMPLES)
install(
TARGETS videodecode
DESTINATION bin
COMPONENT rocprofiler-systems-examples)
endif()
else()
message(
"-- ERROR!: videodecode excluded! please install all the dependencies and try again!"
)
if(NOT FFMPEG_FOUND)
message(FATAL_ERROR "-- ERROR!: FFMPEG Not Found! - please install FFMPEG!")
endif()
if(NOT ROCDECODE_FOUND)
message(FATAL_ERROR "-- ERROR!: rocDecode Not Found! - please install rocDecode!")
endif()
endif()
@@ -0,0 +1,102 @@
/*
Copyright (c) 2023 - 2025 Advanced Micro Devices, Inc. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#pragma once
#include "roc_video_dec.h"
typedef enum ReconfigFlushMode_enum
{
RECONFIG_FLUSH_MODE_NONE = 0, /**< Just flush to get the frame count */
RECONFIG_FLUSH_MODE_DUMP_TO_FILE =
1, /**< The remaining frames will be dumped to file in this mode */
RECONFIG_FLUSH_MODE_CALCULATE_MD5 =
2, /**< Calculate the MD5 of the flushed frames */
} ReconfigFlushMode;
// this struct is used by videodecode and videodecodeMultiFiles to dump last frames to
// file
typedef struct ReconfigDumpFileStruct_t
{
bool b_dump_frames_to_file;
std::string output_file_name;
} ReconfigDumpFileStruct;
// callback function to flush last frames and save it to file when reconfigure happens
int
ReconfigureFlushCallback(void* p_viddec_obj, uint32_t flush_mode, void* p_user_struct)
{
int n_frames_flushed = 0;
if((p_viddec_obj == nullptr) || (p_user_struct == nullptr)) return n_frames_flushed;
RocVideoDecoder* viddec = static_cast<RocVideoDecoder*>(p_viddec_obj);
OutputSurfaceInfo* surf_info;
if(!viddec->GetOutputSurfaceInfo(&surf_info))
{
std::cerr << "Error: Failed to get Output Surface Info!" << std::endl;
return n_frames_flushed;
}
uint8_t* pframe = nullptr;
int64_t pts;
while((pframe = viddec->GetFrame(&pts)))
{
if(flush_mode != RECONFIG_FLUSH_MODE_NONE)
{
if(flush_mode == ReconfigFlushMode::RECONFIG_FLUSH_MODE_DUMP_TO_FILE)
{
ReconfigDumpFileStruct* p_dump_file_struct =
static_cast<ReconfigDumpFileStruct*>(p_user_struct);
if(p_dump_file_struct->b_dump_frames_to_file)
{
viddec->SaveFrameToFile(p_dump_file_struct->output_file_name, pframe,
surf_info);
}
}
else if(flush_mode == ReconfigFlushMode::RECONFIG_FLUSH_MODE_CALCULATE_MD5)
{
viddec->UpdateMd5ForFrame(pframe, surf_info);
}
}
// release and flush frame
viddec->ReleaseFrame(pts, true);
n_frames_flushed++;
}
return n_frames_flushed;
}
int
GetEnvVar(const char* name, int& dev_count)
{
char* v = std::getenv(name);
if(v)
{
char* p_tkn = std::strtok(v, ",");
while(p_tkn != nullptr)
{
dev_count++;
p_tkn = strtok(nullptr, ",");
}
}
return dev_count;
}
File diff ditekan karena terlalu besar Load Diff
@@ -0,0 +1,650 @@
/*
Copyright (c) 2023 - 2025 Advanced Micro Devices, Inc. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#pragma once
#include <assert.h>
#include <chrono>
#include <cstring>
#include <exception>
#include <hip/hip_runtime.h>
#include <iostream>
#include <mutex>
#include <queue>
#include <sstream>
#include <stdexcept>
#include <stdint.h>
#include <string.h>
#include <string>
#include <unordered_map>
#include <vector>
extern "C"
{
#include "libavutil/md5.h"
#include "libavutil/mem.h"
}
#include "rocdecode.h"
#include "rocparser.h"
/*!
* \file
* \brief The AMD Video Decode Library.
*
* \defgroup group_amd_roc_video_dec rocDecode Video Decode: AMD Video Decode API
* \brief AMD The rocDecode video decoder for AMDs GPUs.
*/
#define MAX_FRAME_NUM 16
typedef int(ROCDECAPI* PFNRECONFIGUEFLUSHCALLBACK)(void*, uint32_t, void*);
typedef enum SeiAvcHevcPayloadType_enum
{
SEI_TYPE_TIME_CODE = 136,
SEI_TYPE_USER_DATA_UNREGISTERED = 5
} SeiAvcHevcPayloadType;
typedef enum OutputSurfaceMemoryType_enum
{
OUT_SURFACE_MEM_DEV_INTERNAL = 0, /**< Internal interopped decoded surface
memory(original mapped decoded surface) */
OUT_SURFACE_MEM_DEV_COPIED =
1, /**< decoded output will be copied to a separate device memory (the user
doesn't need to call release) **/
OUT_SURFACE_MEM_HOST_COPIED =
2, /**< decoded output will be copied to a separate host memory (the user doesn't
need to call release) **/
OUT_SURFACE_MEM_NOT_MAPPED = 3 /**< < decoded output is not available (interop won't
be used): useful for decode only performance app*/
} OutputSurfaceMemoryType;
#define TOSTR(X) std::to_string(static_cast<int>(X))
#define STR(X) std::string(X)
#if DBGINFO
# define INFO(X) \
std::clog << "[INF] " \
<< " {" << __func__ << "} " \
<< " " << X << std::endl;
#else
# define INFO(X) ;
#endif
#define ERR(X) \
std::cerr << "[ERR] " \
<< " {" << __func__ << "} " \
<< " " << X << std::endl;
class RocVideoDecodeException : public std::exception
{
public:
explicit RocVideoDecodeException(const std::string& message, const int err_code)
: _message(message)
, _err_code(err_code)
{}
explicit RocVideoDecodeException(const std::string& message)
: _message(message)
, _err_code(-1)
{}
virtual const char* what() const throw() override { return _message.c_str(); }
int Geterror_code() const { return _err_code; }
private:
std::string _message;
int _err_code;
};
#define ROCDEC_THROW(X, CODE) \
throw RocVideoDecodeException(" { " + std::string(__func__) + " } " + X, CODE);
#define THROW(X) throw RocVideoDecodeException(" { " + std::string(__func__) + " } " + X);
#define ROCDEC_API_CALL(rocDecAPI) \
do \
{ \
rocDecStatus error_code = rocDecAPI; \
if(error_code != ROCDEC_SUCCESS) \
{ \
std::ostringstream error_log; \
error_log << #rocDecAPI << " returned " << rocDecGetErrorName(error_code) \
<< " at " << __FILE__ << ":" << __LINE__; \
ROCDEC_THROW(error_log.str(), error_code); \
} \
} while(0)
#define HIP_API_CALL(call) \
do \
{ \
hipError_t hip_status = call; \
if(hip_status != hipSuccess) \
{ \
const char* sz_err_name = NULL; \
sz_err_name = hipGetErrorName(hip_status); \
std::ostringstream error_log; \
error_log << "hip API error " << sz_err_name; \
ROCDEC_THROW(error_log.str(), hip_status); \
} \
} while(0)
struct Rect
{
int left;
int top;
int right;
int bottom;
};
struct Dim
{
int w, h;
};
static inline int
align(int value, int alignment)
{
return (value + alignment - 1) & ~(alignment - 1);
}
typedef struct DecFrameBuffer_
{
uint8_t* frame_ptr; /**< device memory pointer for the decoded frame */
int64_t pts; /**< timestamp for the decoded frame */
int picture_index; /**< surface index for the decoded frame */
} DecFrameBuffer;
typedef struct OutputSurfaceInfoType
{
uint32_t output_width; /**< Output width of decoded surface*/
uint32_t output_height; /**< Output height of decoded surface*/
uint32_t output_pitch; /**< Output pitch in bytes of luma plane, chroma pitch can be
inferred based on chromaFormat*/
uint32_t output_vstride; /**< Output vertical stride in case of using internal mem
pointer **/
uint32_t bytes_per_pixel; /**< Output BytesPerPixel of decoded image*/
uint32_t bit_depth; /**< Output BitDepth of the image*/
uint32_t num_chroma_planes; /**< Output Chroma number of planes*/
uint64_t output_surface_size_in_bytes; /**< Output Image Size in Bytes; including both
luma and chroma planes*/
rocDecVideoSurfaceFormat surface_format; /**< Chroma format of the decoded image*/
OutputSurfaceMemoryType mem_type; /**< Output mem_type of the surface*/
} OutputSurfaceInfo;
typedef struct ReconfigParams_t
{
PFNRECONFIGUEFLUSHCALLBACK p_fn_reconfigure_flush;
void* p_reconfig_user_struct;
uint32_t reconfig_flush_mode;
} ReconfigParams;
class RocVideoDecoder
{
public:
/**
* @brief Construct a new Roc Video Decoder object
*
* @param hip_ctx
* @param b_use_device_mem
* @param codec
* @param device_id
* @param b_low_latency
* @param device_frame_pitched
* @param p_crop_rect
* @param extract_user_SEI_Message
* @param max_width
* @param max_height
* @param clk_rate
* @param force_zero_latency
*/
RocVideoDecoder(int device_id, OutputSurfaceMemoryType out_mem_type,
rocDecVideoCodec codec, bool force_zero_latency = false,
const Rect* p_crop_rect = nullptr,
bool extract_user_SEI_Message = false, uint32_t disp_delay = 0,
int max_width = 0, int max_height = 0, uint32_t clk_rate = 1000);
~RocVideoDecoder();
rocDecVideoCodec GetCodecId() { return codec_id_; }
hipStream_t GetStream() { return hip_stream_; }
/**
* @brief Get the output frame width
*/
uint32_t GetWidth()
{
assert(disp_width_);
return disp_width_;
}
/**
* @brief This function is used to get the actual decode width
*/
int GetDecodeWidth()
{
assert(coded_width_);
return coded_width_;
}
/**
* @brief Get the output frame height
*/
uint32_t GetHeight()
{
assert(disp_height_);
return disp_height_;
}
/**
* @brief This function is used to get the current chroma height.
*/
int GetChromaHeight()
{
assert(chroma_height_);
return chroma_height_;
}
/**
* @brief This function is used to get the number of chroma planes.
*/
int GetNumChromaPlanes()
{
assert(num_chroma_planes_);
return num_chroma_planes_;
}
/**
* @brief This function is used to get the current frame size based on pixel
* format.
*/
int GetFrameSize()
{
assert(disp_width_);
return disp_width_ * (disp_height_ + (chroma_height_ * num_chroma_planes_)) *
byte_per_pixel_;
}
/**
* @brief This function is used to get the current frame size based on pitch
*/
int GetFrameSizePitched()
{
assert(surface_stride_);
return surface_stride_ * (disp_height_ + (chroma_height_ * num_chroma_planes_));
}
/**
* @brief Get the Bit Depth and BytesPerPixel associated with the pixel format
*
* @return uint32_t
*/
uint32_t GetBitDepth()
{
assert(bitdepth_minus_8_);
return (bitdepth_minus_8_ + 8);
}
uint32_t GetBytePerPixel()
{
assert(byte_per_pixel_);
return byte_per_pixel_;
}
/**
* @brief Functions to get the output surface attributes
*/
size_t GetSurfaceSize()
{
assert(surface_size_);
return surface_size_;
}
uint32_t GetSurfaceStride()
{
assert(surface_stride_);
return surface_stride_;
}
// RocDecImageFormat GetSubsampling() { return subsampling_; }
/**
* @brief Get the name of the output format
*
* @param codec_id
* @return std::string
*/
const char* GetCodecFmtName(rocDecVideoCodec codec_id);
/**
* @brief function to return the name from surface_format_id
*
* @param surface_format_id - enum for surface format
* @return const char*
*/
const char* GetSurfaceFmtName(rocDecVideoSurfaceFormat surface_format_id);
/**
* @brief Get the pointer to the Output Image Info
*
* @param surface_info ptr to output surface info
* @return true
* @return false
*/
bool GetOutputSurfaceInfo(OutputSurfaceInfo** surface_info);
/**
* @brief Function to set the Reconfig Params object
*
* @param p_reconfig_params: pointer to reconfig params struct
* @return true : success
* @return false : fail
*/
bool SetReconfigParams(ReconfigParams* p_reconfig_params,
bool b_force_reconfig_flush = false);
/**
* @brief Function to force Reconfigure Flush: needed for random seeking to key frames
*
* @return int 1: Success 0: Fail
*/
int FlushAndReconfigure();
/**
* @brief this function decodes a frame and returns the number of frames avalable for
* display
*
* @param data - pointer to the data buffer that is to be decode
* @param size - size of the data buffer in bytes
* @param pts - presentation timestamp
* @param flags - video packet flags
* @param num_decoded_pics - nummber of pictures decoded in this call
* @return int - num of frames to display
*/
int DecodeFrame(const uint8_t* data, size_t size, int pkt_flags, int64_t pts = 0,
int* num_decoded_pics = nullptr);
/**
* @brief This function returns a decoded frame and timestamp. This should be called
* in a loop fetching all the available frames
*
*/
uint8_t* GetFrame(int64_t* pts);
/**
* @brief function to release frame after use by the application: Only used with
* "OUT_SURFACE_MEM_DEV_INTERNAL"
*
* @param pTimestamp - timestamp of the frame to be released (unmapped)
* @param b_flushing - true when flushing
* @return true - success
* @return false - falied
*/
bool ReleaseFrame(int64_t pTimestamp, bool b_flushing = false);
/**
* @brief utility function to save image to a file
*
* @param output_file_name - file to write
* @param dev_mem - dev_memory pointer of the frame
* @param image_info - output image info
* @param is_output_RGB - to write in RGB
*/
// void SaveImage(std::string output_file_name, void* dev_mem, OutputImageInfo*
// image_info, bool is_output_RGB = 0);
/**
* @brief Get the Device info for the current device
*
* @param device_name
* @param gcn_arch_name
* @param pci_bus_id
* @param pci_domain_id
* @param pci_device_id
*/
void GetDeviceinfo(std::string& device_name, std::string& gcn_arch_name,
int& pci_bus_id, int& pci_domain_id, int& pci_device_id);
/**
* @brief Helper function to dump decoded output surface to file
*
* @param output_file_name - Output file name
* @param dev_mem - pointer to surface memory
* @param surf_info - surface info
* @param rgb_image_size - image size for rgb (optional). A non_zero value
* indicates the surf_mem holds an rgb interleaved image and the entire size will be
* dumped to file
*/
void SaveFrameToFile(std::string output_file_name, void* surf_mem,
OutputSurfaceInfo* surf_info, size_t rgb_image_size = 0);
/**
* @brief Helper funtion to close a existing file and dump to new file in case of
* multiple files using same decoder
*/
void ResetSaveFrameToFile();
/**
* @brief Helper function to start MD5 calculation
*/
void InitMd5();
void UpdateMd5ForDataBuffer(void* pDevMem, int rgb_image_size);
/**
* @brief Helper function to dump decoded output surface to file
*
* @param dev_mem - pointer to surface memory
* @param surf_info - surface info
*/
void UpdateMd5ForFrame(void* surf_mem, OutputSurfaceInfo* surf_info);
/**
* @brief Helper function to complete MD5 calculation
*
* @param [out] digest Pointer to the 16 byte message digest
*/
void FinalizeMd5(uint8_t** digest);
/**
* @brief Get the Num Of Flushed Frames from video decoder object
*
* @return int32_t
*/
int32_t GetNumOfFlushedFrames() { return num_frames_flushed_during_reconfig_; }
/*! \brief Function to wait for the decode completion of the last submitted picture
*/
void WaitForDecodeCompletion();
// Session overhead refers to decoder initialization and deinitialization time
void AddDecoderSessionOverHead(std::thread::id session_id, double duration)
{
session_overhead_[session_id] += duration;
}
double GetDecoderSessionOverHead(std::thread::id session_id)
{
if(session_overhead_.find(session_id) != session_overhead_.end())
{
return session_overhead_[session_id];
}
else
{
return 0;
}
}
/**
* @brief Check if the given Video Codec is supported on the given GPU
*
* @return rocDecStatus
*/
bool CodecSupported(int device_id, rocDecVideoCodec codec_id, uint32_t bit_depth);
private:
/**
* @brief Callback function to be registered for getting a callback when decoding
* of sequence starts
*/
static int ROCDECAPI HandleVideoSequenceProc(void* p_user_data,
RocdecVideoFormat* p_video_format)
{
return ((RocVideoDecoder*) p_user_data)->HandleVideoSequence(p_video_format);
}
/**
* @brief Callback function to be registered for getting a callback when a decoded
* frame is ready to be decoded
*/
static int ROCDECAPI HandlePictureDecodeProc(void* p_user_data,
RocdecPicParams* p_pic_params)
{
return ((RocVideoDecoder*) p_user_data)->HandlePictureDecode(p_pic_params);
}
/**
* @brief Callback function to be registered for getting a callback when a decoded
* frame is available for display
*/
static int ROCDECAPI HandlePictureDisplayProc(void* p_user_data,
RocdecParserDispInfo* p_disp_info)
{
return ((RocVideoDecoder*) p_user_data)->HandlePictureDisplay(p_disp_info);
}
/**
* @brief Callback function to be registered for getting a callback when all the
* unregistered user SEI Messages are parsed for a frame.
*/
static int ROCDECAPI HandleSEIMessagesProc(void* p_user_data,
RocdecSeiMessageInfo* p_sei_message_info)
{
return ((RocVideoDecoder*) p_user_data)->GetSEIMessage(p_sei_message_info);
}
/**
* @brief This function gets called when a sequence is ready to be decoded. The
function also gets called when there is format change
*/
int HandleVideoSequence(RocdecVideoFormat* p_video_format);
/**
* @brief This function gets called when a picture is ready to be decoded.
* cuvidDecodePicture is called from this function to decode the picture
*/
int HandlePictureDecode(RocdecPicParams* p_pic_params);
/**
* @brief This function gets called after a picture is decoded and available for
display. Frames are fetched and stored in internal buffer
*/
int HandlePictureDisplay(RocdecParserDispInfo* p_disp_info);
/**
* @brief This function gets called when all unregistered user SEI messages are
* parsed for a frame
*/
int GetSEIMessage(RocdecSeiMessageInfo* p_sei_message_info);
/**
* @brief This function reconfigure decoder if there is a change in sequence
* params.
*/
int ReconfigureDecoder(RocdecVideoFormat* p_video_format);
/**
* @brief function to release all internal frames and clear the vp_frames_q_ (used
* with reconfigure): Only used with "OUT_SURFACE_MEM_DEV_INTERNAL"
*
* @return true - success
* @return false - falied
*/
bool ReleaseInternalFrames();
/**
* @brief Function to Initialize GPU-HIP
*
*/
bool InitHIP(int device_id);
/**
* @brief Function to get start time
*
*/
std::chrono::_V2::system_clock::time_point StartTimer();
/**
* @brief Function to get elapsed time
*
*/
double StopTimer(const std::chrono::_V2::system_clock::time_point& start_time);
int num_devices_;
int device_id_;
RocdecVideoParser rocdec_parser_ = nullptr;
rocDecDecoderHandle roc_decoder_ = nullptr;
OutputSurfaceMemoryType out_mem_type_ = OUT_SURFACE_MEM_DEV_INTERNAL;
bool b_extract_sei_message_ = false;
bool b_force_zero_latency_ = false;
uint32_t disp_delay_;
ReconfigParams* p_reconfig_params_ = nullptr;
bool b_force_recofig_flush_ = false;
int32_t num_frames_flushed_during_reconfig_ = 0;
hipDeviceProp_t hip_dev_prop_;
hipStream_t hip_stream_;
rocDecVideoCodec codec_id_ = rocDecVideoCodec_NumCodecs;
rocDecVideoChromaFormat video_chroma_format_ = rocDecVideoChromaFormat_420;
rocDecVideoSurfaceFormat video_surface_format_ = rocDecVideoSurfaceFormat_NV12;
RocdecSeiMessageInfo* curr_sei_message_ptr_ = nullptr;
RocdecSeiMessageInfo sei_message_display_q_[MAX_FRAME_NUM];
RocdecVideoFormat* curr_video_format_ptr_ = nullptr;
int output_frame_cnt_ = 0, output_frame_cnt_ret_ = 0;
int decoded_pic_cnt_ = 0;
int decode_poc_ = 0, pic_num_in_dec_order_[MAX_FRAME_NUM];
int num_alloced_frames_ = 0;
int last_decode_surf_idx_ = 0;
std::ostringstream input_video_info_str_;
int bitdepth_minus_8_ = 0;
uint32_t byte_per_pixel_ = 1;
uint32_t coded_width_ = 0;
uint32_t disp_width_ = 0;
uint32_t coded_height_ = 0;
uint32_t disp_height_ = 0;
uint32_t target_width_ = 0;
uint32_t target_height_ = 0;
int max_width_ = 0, max_height_ = 0;
uint32_t chroma_height_ = 0;
uint32_t num_chroma_planes_ = 0;
uint32_t num_components_ = 0;
uint32_t surface_stride_ = 0;
uint32_t
surface_vstride_ = 0,
chroma_vstride_ =
0; // vertical stride between planes: used when using internal dev memory
size_t surface_size_ = 0;
OutputSurfaceInfo output_surface_info_ = {};
std::mutex mtx_vp_frame_;
std::vector<DecFrameBuffer> vp_frames_; // vector of decoded frames
std::queue<DecFrameBuffer> vp_frames_q_;
Rect disp_rect_ = {}; // displayable area specified in the bitstream
Rect crop_rect_ = {}; // user specified region of interest within diplayable area
// disp_rect_
FILE* fp_sei_ = NULL;
FILE* fp_out_ = NULL;
struct AVMD5* md5_ctx_;
uint8_t md5_digest_[16];
bool is_decoder_reconfigured_ = false;
std::string current_output_filename = "";
uint32_t extra_output_file_count_ = 0;
std::thread::id decoder_session_id_; // Decoder session identifier. Used to gather
// session level stats.
std::unordered_map<std::thread::id, double>
session_overhead_; // Records session overhead of initialization+deinitialization
// time. Format is (thread id, duration)
};
@@ -0,0 +1,716 @@
/*
Copyright (c) 2023 - 2025 Advanced Micro Devices, Inc. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#pragma once
#include <iostream>
extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#if USE_AVCODEC_GREATER_THAN_58_134
# include <libavcodec/bsf.h>
#endif
}
#include "rocdecode.h"
/*!
* \file
* \brief The AMD Video Demuxer for rocDecode Library.
*
* \defgroup group_amd_rocdecode_videodemuxer videoDemuxer: AMD rocDecode Video Demuxer
* API \brief AMD The rocDecode video demuxer API.
*/
/**
* @brief Enum for Seek mode
*
*/
typedef enum SeekModeEnum
{
SEEK_MODE_EXACT_FRAME = 0,
SEEK_MODE_PREV_KEY_FRAME = 1,
SEEK_MODE_NUM,
} SeekMode;
/**
* @brief Enum for Seek Criteria
*
*/
typedef enum SeekCriteriaEnum
{
SEEK_CRITERIA_FRAME_NUM = 0,
SEEK_CRITERIA_TIME_STAMP = 1,
SEEK_CRITERIA_NUM,
} SeekCriteria;
struct PacketData
{
int32_t key;
int64_t pts;
int64_t dts;
uint64_t pos;
uintptr_t bsl_data;
uint64_t bsl;
uint64_t duration;
};
class VideoSeekContext
{
public:
VideoSeekContext()
: use_seek_(false)
, seek_frame_(0)
, seek_mode_(SEEK_MODE_PREV_KEY_FRAME)
, seek_crit_(SEEK_CRITERIA_FRAME_NUM)
, out_frame_pts_(0)
, out_frame_duration_(0)
, num_frames_decoded_(0U)
{}
VideoSeekContext(uint64_t frame_id)
: use_seek_(true)
, seek_frame_(frame_id)
, seek_mode_(SEEK_MODE_PREV_KEY_FRAME)
, seek_crit_(SEEK_CRITERIA_FRAME_NUM)
, out_frame_pts_(0)
, out_frame_duration_(0)
, num_frames_decoded_(0U)
{}
VideoSeekContext& operator=(const VideoSeekContext& other)
{
use_seek_ = other.use_seek_;
seek_frame_ = other.seek_frame_;
seek_mode_ = other.seek_mode_;
seek_crit_ = other.seek_crit_;
out_frame_pts_ = other.out_frame_pts_;
out_frame_duration_ = other.out_frame_duration_;
num_frames_decoded_ = other.num_frames_decoded_;
return *this;
}
/* Will be set to false when not seeking, true otherwise;
*/
bool use_seek_;
/* Frame we want to get. Set by user.
* Shall be set to frame timestamp in case seek is done by time.
*/
uint64_t seek_frame_;
/* Mode in which we seek. */
SeekMode seek_mode_;
/* Criteria by which we seek. */
SeekCriteria seek_crit_;
/* PTS of frame found after seek. */
int64_t out_frame_pts_;
/* Duration of frame found after seek. */
int64_t out_frame_duration_;
/* Number of frames that were decoded during seek. */
uint64_t num_frames_decoded_;
};
// Video Demuxer Interface class
class VideoDemuxer
{
public:
class StreamProvider
{
public:
virtual ~StreamProvider() {}
virtual int GetData(uint8_t* buf, int buf_size) = 0;
virtual size_t GetBufferSize() = 0;
};
AVCodecID GetCodecID() { return av_video_codec_id_; };
VideoDemuxer(const char* input_file_path)
: VideoDemuxer(CreateFmtContextUtil(input_file_path))
{}
VideoDemuxer(StreamProvider* stream_provider)
: VideoDemuxer(CreateFmtContextUtil(stream_provider))
{
av_io_ctx_ = av_fmt_input_ctx_->pb;
}
~VideoDemuxer()
{
if(!av_fmt_input_ctx_)
{
return;
}
if(packet_)
{
av_packet_free(&packet_);
}
if(packet_filtered_)
{
av_packet_free(&packet_filtered_);
}
if(av_bsf_ctx_)
{
av_bsf_free(&av_bsf_ctx_);
}
avformat_close_input(&av_fmt_input_ctx_);
if(av_io_ctx_)
{
av_freep(&av_io_ctx_->buffer);
av_freep(&av_io_ctx_);
}
if(data_with_header_)
{
av_free(data_with_header_);
}
}
bool Demux(uint8_t** video, int* video_size, int64_t* pts = nullptr)
{
if(!av_fmt_input_ctx_)
{
return false;
}
*video_size = 0;
if(packet_->data)
{
av_packet_unref(packet_);
}
int ret = 0;
while((ret = av_read_frame(av_fmt_input_ctx_, packet_)) >= 0 &&
packet_->stream_index != av_stream_)
{
av_packet_unref(packet_);
}
if(ret < 0)
{
return false;
}
if(is_h264_ || is_hevc_)
{
if(packet_filtered_->data)
{
av_packet_unref(packet_filtered_);
}
if(av_bsf_send_packet(av_bsf_ctx_, packet_) != 0)
{
std::cerr << "ERROR: av_bsf_send_packet failed!" << std::endl;
return false;
}
if(av_bsf_receive_packet(av_bsf_ctx_, packet_filtered_) != 0)
{
std::cerr << "ERROR: av_bsf_receive_packet failed!" << std::endl;
return false;
}
*video = packet_filtered_->data;
*video_size = packet_filtered_->size;
if(packet_filtered_->dts != AV_NOPTS_VALUE)
{
pkt_dts_ = packet_filtered_->dts;
}
else
{
pkt_dts_ = packet_filtered_->pts;
}
if(pts)
{
*pts =
(int64_t)(packet_filtered_->pts * default_time_scale_ * time_base_);
pkt_duration_ = packet_filtered_->duration;
}
}
else
{
if(is_mpeg4_ && (frame_count_ == 0))
{
int ext_data_size =
av_fmt_input_ctx_->streams[av_stream_]->codecpar->extradata_size;
if(ext_data_size > 0)
{
data_with_header_ = (uint8_t*) av_malloc(
ext_data_size + packet_->size - 3 * sizeof(uint8_t));
if(!data_with_header_)
{
std::cerr << "ERROR: av_malloc failed!" << std::endl;
return false;
}
memcpy(data_with_header_,
av_fmt_input_ctx_->streams[av_stream_]->codecpar->extradata,
ext_data_size);
memcpy(data_with_header_ + ext_data_size, packet_->data + 3,
packet_->size - 3 * sizeof(uint8_t));
*video = data_with_header_;
*video_size = ext_data_size + packet_->size - 3 * sizeof(uint8_t);
}
}
else
{
*video = packet_->data;
*video_size = packet_->size;
}
if(packet_->dts != AV_NOPTS_VALUE)
{
pkt_dts_ = packet_->dts;
}
else
{
pkt_dts_ = packet_->pts;
}
if(pts)
{
*pts = (int64_t)(packet_->pts * default_time_scale_ * time_base_);
pkt_duration_ = packet_->duration;
}
}
frame_count_++;
return true;
}
bool Seek(VideoSeekContext& seek_ctx, uint8_t** pp_video, int* video_size)
{
/* !!! IMPORTANT !!!
* Across this function, packet decode timestamp (DTS) values are used to
* compare given timestamp against. This is done because DTS values shall
* monotonically increase during the course of decoding unlike PTS values
* which may be affected by frame reordering due to B frames.
*/
if(!is_seekable_)
{
std::cerr << "ERROR: Seek isn't supported for this input." << std::endl;
return false;
}
if(IsVFR() && (SEEK_CRITERIA_FRAME_NUM == seek_ctx.seek_crit_))
{
std::cerr << "ERROR: Can't seek by frame number in VFR sequences. Seek by "
"timestamp instead."
<< std::endl;
return false;
}
// Seek for single frame;
auto seek_frame = [&](VideoSeekContext const& seek_ctx, int flags) {
bool seek_backward = true;
int64_t timestamp = 0;
int ret = 0;
switch(seek_ctx.seek_crit_)
{
case SEEK_CRITERIA_FRAME_NUM:
timestamp = TsFromFrameNumber(seek_ctx.seek_frame_);
ret = av_seek_frame(av_fmt_input_ctx_, av_stream_, timestamp,
seek_backward ? AVSEEK_FLAG_BACKWARD | flags
: flags);
break;
case SEEK_CRITERIA_TIME_STAMP:
timestamp = TsFromTime(seek_ctx.seek_frame_);
ret = av_seek_frame(av_fmt_input_ctx_, av_stream_, timestamp,
seek_backward ? AVSEEK_FLAG_BACKWARD | flags
: flags);
break;
default: std::cerr << "ERROR: Invalid seek mode" << std::endl; ret = -1;
}
if(ret < 0)
{
throw std::runtime_error("ERROR: seeking for frame");
}
};
// Check if frame satisfies seek conditions;
auto is_seek_done = [&](PacketData& pkt_data, VideoSeekContext const& seek_ctx) {
int64_t target_ts = 0;
switch(seek_ctx.seek_crit_)
{
case SEEK_CRITERIA_FRAME_NUM:
target_ts = TsFromFrameNumber(seek_ctx.seek_frame_);
break;
case SEEK_CRITERIA_TIME_STAMP:
target_ts = TsFromTime(seek_ctx.seek_frame_);
break;
default:
std::cerr << "ERROR::Invalid seek criteria" << std::endl;
return -1;
}
if(pkt_dts_ == target_ts)
{
return 0;
}
else if(pkt_dts_ > target_ts)
{
return 1;
}
else
{
return -1;
};
};
/* This will seek for exact frame number;
* Note that decoder may not be able to decode such frame; */
auto seek_for_exact_frame = [&](PacketData& pkt_data,
VideoSeekContext& seek_ctx) {
// Repetititive seek until seek condition is satisfied;
VideoSeekContext tmp_ctx(seek_ctx.seek_frame_);
seek_frame(tmp_ctx, AVSEEK_FLAG_ANY);
int seek_done = 0;
do
{
if(!Demux(pp_video, video_size, &pkt_data.pts))
{
throw std::runtime_error("ERROR: Demux failed trying to seek for "
"specified frame number/timestamp");
}
seek_done = is_seek_done(pkt_data, seek_ctx);
// TODO: one last condition, check for a target too high than available
// for timestamp
if(seek_done > 0)
{ // We've gone too far and need to seek backwards;
if((tmp_ctx.seek_frame_--) >= 0)
{
seek_frame(tmp_ctx, AVSEEK_FLAG_ANY);
}
}
else if(seek_done < 0)
{ // Need to read more frames until we reach requested number;
tmp_ctx.seek_frame_++;
seek_frame(tmp_ctx, AVSEEK_FLAG_ANY);
}
if(tmp_ctx.seek_frame_ ==
seek_ctx.seek_frame_) // if frame 'N' is too far and frame 'N-1' is
// too less from target. Avoids infinite loop
// between N & N-1
break;
} while(seek_done != 0);
seek_ctx.out_frame_pts_ = pkt_data.pts;
seek_ctx.out_frame_duration_ = pkt_data.duration = pkt_duration_;
};
// Seek for closest key frame in the past;
auto seek_for_prev_key_frame = [&](PacketData& pkt_data,
VideoSeekContext& seek_ctx) {
seek_frame(seek_ctx, AVSEEK_FLAG_BACKWARD);
Demux(pp_video, video_size, &pkt_data.pts);
seek_ctx.num_frames_decoded_ =
static_cast<uint64_t>(pkt_data.pts / 1000 * frame_rate_);
seek_ctx.out_frame_pts_ = pkt_data.pts;
seek_ctx.out_frame_duration_ = pkt_data.duration = pkt_duration_;
};
PacketData pktData;
pktData.bsl_data = size_t(*pp_video);
pktData.bsl = *video_size;
switch(seek_ctx.seek_mode_)
{
case SEEK_MODE_EXACT_FRAME: seek_for_exact_frame(pktData, seek_ctx); break;
case SEEK_MODE_PREV_KEY_FRAME:
seek_for_prev_key_frame(pktData, seek_ctx);
break;
default: throw std::runtime_error("ERROR::Unsupported seek mode"); break;
}
return true;
}
const uint32_t GetWidth() const { return width_; }
const uint32_t GetHeight() const { return height_; }
const uint32_t GetChromaHeight() const { return chroma_height_; }
const uint32_t GetBitDepth() const { return bit_depth_; }
const uint32_t GetBytePerPixel() const { return byte_per_pixel_; }
const uint32_t GetBitRate() const { return bit_rate_; }
const double GetFrameRate() const { return frame_rate_; };
bool IsVFR() const { return frame_rate_ != avg_frame_rate_; };
int64_t TsFromTime(double ts_sec)
{
// Convert integer timestamp representation to AV_TIME_BASE and switch to
// fixed_point
auto const ts_tbu = llround(ts_sec * AV_TIME_BASE);
// Rescale the timestamp to value represented in stream base units;
AVRational time_factor = { 1, AV_TIME_BASE };
return av_rescale_q(ts_tbu, time_factor,
av_fmt_input_ctx_->streams[av_stream_]->time_base);
}
int64_t TsFromFrameNumber(int64_t frame_num)
{
auto const ts_sec = static_cast<double>(frame_num) / frame_rate_;
return TsFromTime(ts_sec);
}
private:
VideoDemuxer(AVFormatContext* av_fmt_input_ctx)
: av_fmt_input_ctx_(av_fmt_input_ctx)
{
av_log_set_level(AV_LOG_QUIET);
if(!av_fmt_input_ctx_)
{
std::cerr << "ERROR: av_fmt_input_ctx_ is not vaild!" << std::endl;
return;
}
packet_ = av_packet_alloc();
packet_filtered_ = av_packet_alloc();
if(!packet_ || !packet_filtered_)
{
std::cerr << "ERROR: av_packet_alloc failed!" << std::endl;
return;
}
if(avformat_find_stream_info(av_fmt_input_ctx_, nullptr) < 0)
{
std::cerr << "ERROR: avformat_find_stream_info failed!" << std::endl;
return;
}
av_stream_ = av_find_best_stream(av_fmt_input_ctx_, AVMEDIA_TYPE_VIDEO, -1, -1,
nullptr, 0);
if(av_stream_ < 0)
{
std::cerr << "ERROR: av_find_best_stream failed!" << std::endl;
av_packet_free(&packet_);
av_packet_free(&packet_filtered_);
return;
}
av_video_codec_id_ = av_fmt_input_ctx_->streams[av_stream_]->codecpar->codec_id;
width_ = av_fmt_input_ctx_->streams[av_stream_]->codecpar->width;
height_ = av_fmt_input_ctx_->streams[av_stream_]->codecpar->height;
chroma_format_ =
(AVPixelFormat) av_fmt_input_ctx_->streams[av_stream_]->codecpar->format;
bit_rate_ = av_fmt_input_ctx_->streams[av_stream_]->codecpar->bit_rate;
if(av_fmt_input_ctx_->streams[av_stream_]->r_frame_rate.den != 0)
frame_rate_ = static_cast<double>(
av_fmt_input_ctx_->streams[av_stream_]->r_frame_rate.num) /
static_cast<double>(
av_fmt_input_ctx_->streams[av_stream_]->r_frame_rate.den);
if(av_fmt_input_ctx_->streams[av_stream_]->avg_frame_rate.den != 0)
avg_frame_rate_ =
static_cast<double>(
av_fmt_input_ctx_->streams[av_stream_]->avg_frame_rate.num) /
static_cast<double>(
av_fmt_input_ctx_->streams[av_stream_]->avg_frame_rate.den);
switch(chroma_format_)
{
case AV_PIX_FMT_YUV420P10LE:
case AV_PIX_FMT_GRAY10LE:
bit_depth_ = 10;
chroma_height_ = (height_ + 1) >> 1;
byte_per_pixel_ = 2;
break;
case AV_PIX_FMT_YUV420P12LE:
bit_depth_ = 12;
chroma_height_ = (height_ + 1) >> 1;
byte_per_pixel_ = 2;
break;
case AV_PIX_FMT_YUV444P10LE:
bit_depth_ = 10;
chroma_height_ = height_ << 1;
byte_per_pixel_ = 2;
break;
case AV_PIX_FMT_YUV444P12LE:
bit_depth_ = 12;
chroma_height_ = height_ << 1;
byte_per_pixel_ = 2;
break;
case AV_PIX_FMT_YUV444P:
bit_depth_ = 8;
chroma_height_ = height_ << 1;
byte_per_pixel_ = 1;
break;
case AV_PIX_FMT_YUV420P:
case AV_PIX_FMT_YUVJ420P:
case AV_PIX_FMT_YUVJ422P:
case AV_PIX_FMT_YUVJ444P:
case AV_PIX_FMT_GRAY8:
bit_depth_ = 8;
chroma_height_ = (height_ + 1) >> 1;
byte_per_pixel_ = 1;
break;
default:
chroma_format_ = AV_PIX_FMT_YUV420P;
bit_depth_ = 8;
chroma_height_ = (height_ + 1) >> 1;
byte_per_pixel_ = 1;
}
AVRational time_base = av_fmt_input_ctx_->streams[av_stream_]->time_base;
time_base_ = av_q2d(time_base);
is_h264_ = av_video_codec_id_ == AV_CODEC_ID_H264 &&
(!strcmp(av_fmt_input_ctx_->iformat->long_name, "QuickTime / MOV") ||
!strcmp(av_fmt_input_ctx_->iformat->long_name, "FLV (Flash Video)") ||
!strcmp(av_fmt_input_ctx_->iformat->long_name, "Matroska / WebM"));
is_hevc_ = av_video_codec_id_ == AV_CODEC_ID_HEVC &&
(!strcmp(av_fmt_input_ctx_->iformat->long_name, "QuickTime / MOV") ||
!strcmp(av_fmt_input_ctx_->iformat->long_name, "FLV (Flash Video)") ||
!strcmp(av_fmt_input_ctx_->iformat->long_name, "Matroska / WebM"));
is_mpeg4_ =
av_video_codec_id_ == AV_CODEC_ID_MPEG4 &&
(!strcmp(av_fmt_input_ctx_->iformat->long_name, "QuickTime / MOV") ||
!strcmp(av_fmt_input_ctx_->iformat->long_name, "FLV (Flash Video)") ||
!strcmp(av_fmt_input_ctx_->iformat->long_name, "Matroska / WebM"));
// Check if the input file allow seek functionality.
is_seekable_ = av_fmt_input_ctx_->iformat->read_seek ||
av_fmt_input_ctx_->iformat->read_seek2;
if(is_h264_)
{
const AVBitStreamFilter* bsf = av_bsf_get_by_name("h264_mp4toannexb");
if(!bsf)
{
std::cerr << "ERROR: av_bsf_get_by_name() failed" << std::endl;
av_packet_free(&packet_);
av_packet_free(&packet_filtered_);
return;
}
if(av_bsf_alloc(bsf, &av_bsf_ctx_) != 0)
{
std::cerr << "ERROR: av_bsf_alloc failed!" << std::endl;
return;
}
avcodec_parameters_copy(av_bsf_ctx_->par_in,
av_fmt_input_ctx_->streams[av_stream_]->codecpar);
if(av_bsf_init(av_bsf_ctx_) < 0)
{
std::cerr << "ERROR: av_bsf_init failed!" << std::endl;
return;
}
}
if(is_hevc_)
{
const AVBitStreamFilter* bsf = av_bsf_get_by_name("hevc_mp4toannexb");
if(!bsf)
{
std::cerr << "ERROR: av_bsf_get_by_name() failed" << std::endl;
av_packet_free(&packet_);
av_packet_free(&packet_filtered_);
return;
}
if(av_bsf_alloc(bsf, &av_bsf_ctx_) != 0)
{
std::cerr << "ERROR: av_bsf_alloc failed!" << std::endl;
return;
}
avcodec_parameters_copy(av_bsf_ctx_->par_in,
av_fmt_input_ctx_->streams[av_stream_]->codecpar);
if(av_bsf_init(av_bsf_ctx_) < 0)
{
std::cerr << "ERROR: av_bsf_init failed!" << std::endl;
return;
}
}
}
AVFormatContext* CreateFmtContextUtil(StreamProvider* stream_provider)
{
AVFormatContext* ctx = nullptr;
if(!(ctx = avformat_alloc_context()))
{
std::cerr << "ERROR: avformat_alloc_context failed" << std::endl;
return nullptr;
}
uint8_t* avioc_buffer = nullptr;
int avioc_buffer_size = stream_provider->GetBufferSize();
avioc_buffer = (uint8_t*) av_malloc(avioc_buffer_size);
if(!avioc_buffer)
{
std::cerr << "ERROR: av_malloc failed!" << std::endl;
return nullptr;
}
av_io_ctx_ = avio_alloc_context(avioc_buffer, avioc_buffer_size, 0,
stream_provider, &ReadPacket, nullptr, nullptr);
if(!av_io_ctx_)
{
std::cerr << "ERROR: avio_alloc_context failed!" << std::endl;
return nullptr;
}
ctx->pb = av_io_ctx_;
if(avformat_open_input(&ctx, nullptr, nullptr, nullptr) != 0)
{
std::cerr << "ERROR: avformat_open_input failed!" << std::endl;
return nullptr;
}
return ctx;
}
AVFormatContext* CreateFmtContextUtil(const char* input_file_path)
{
avformat_network_init();
AVFormatContext* ctx = nullptr;
if(avformat_open_input(&ctx, input_file_path, nullptr, nullptr) != 0)
{
std::cerr << "ERROR: avformat_open_input failed!" << std::endl;
return nullptr;
}
return ctx;
}
static int ReadPacket(void* data, uint8_t* buf, int buf_size)
{
return ((StreamProvider*) data)->GetData(buf, buf_size);
}
AVFormatContext* av_fmt_input_ctx_ = nullptr;
AVIOContext* av_io_ctx_ = nullptr;
AVPacket* packet_ = nullptr;
AVPacket* packet_filtered_ = nullptr;
AVBSFContext* av_bsf_ctx_ = nullptr;
AVCodecID av_video_codec_id_;
AVPixelFormat chroma_format_;
double frame_rate_ = 0.0;
double avg_frame_rate_ = 0.0;
uint8_t* data_with_header_ = nullptr;
int av_stream_ = 0;
bool is_h264_ = false;
bool is_hevc_ = false;
bool is_mpeg4_ = false;
bool is_seekable_ = false;
int64_t default_time_scale_ = 1000;
double time_base_ = 0.0;
uint32_t frame_count_ = 0;
uint32_t width_ = 0;
uint32_t height_ = 0;
uint32_t chroma_height_ = 0;
uint32_t bit_depth_ = 0;
uint32_t byte_per_pixel_ = 0;
uint32_t bit_rate_ = 0;
// used for Seek Exact frame
int64_t pkt_dts_ = 0;
int64_t pkt_duration_ = 0;
};
static inline rocDecVideoCodec
AVCodec2RocDecVideoCodec(AVCodecID av_codec)
{
switch(av_codec)
{
case AV_CODEC_ID_MPEG1VIDEO: return rocDecVideoCodec_MPEG1;
case AV_CODEC_ID_MPEG2VIDEO: return rocDecVideoCodec_MPEG2;
case AV_CODEC_ID_MPEG4: return rocDecVideoCodec_MPEG4;
case AV_CODEC_ID_H264: return rocDecVideoCodec_AVC;
case AV_CODEC_ID_HEVC: return rocDecVideoCodec_HEVC;
case AV_CODEC_ID_VP8: return rocDecVideoCodec_VP8;
case AV_CODEC_ID_VP9: return rocDecVideoCodec_VP9;
case AV_CODEC_ID_MJPEG: return rocDecVideoCodec_JPEG;
case AV_CODEC_ID_AV1: return rocDecVideoCodec_AV1;
default: return rocDecVideoCodec_NumCodecs;
}
}
@@ -0,0 +1,729 @@
/*
Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <functional>
#include <iomanip>
#include <iostream>
#include <libgen.h>
#include <mutex>
#include <queue>
#include <string>
#include <sys/stat.h>
#include <thread>
#include <unistd.h>
#include <vector>
#if __cplusplus >= 201703L && __has_include(<filesystem>)
# include <filesystem>
#else
# include <experimental/filesystem>
#endif
#include "common.h"
#include "roc_video_dec.h"
#include "video_demuxer.h"
class ThreadPool
{
public:
ThreadPool(int nthreads)
: shutdown_(false)
{
// Create the specified number of threads
threads_.reserve(nthreads);
for(int i = 0; i < nthreads; ++i)
threads_.emplace_back(std::bind(&ThreadPool::ThreadEntry, this, i));
}
~ThreadPool() {}
void JoinThreads()
{
{
// Unblock any threads and tell them to stop
std::unique_lock<std::mutex> lock(mutex_);
shutdown_ = true;
cond_var_.notify_all();
}
// Wait for all threads to stop
for(auto& thread : threads_)
thread.join();
}
void ExecuteJob(std::function<void()> func)
{
// Place a job on the queue and unblock a thread
std::unique_lock<std::mutex> lock(mutex_);
decode_jobs_queue_.emplace(std::move(func));
cond_var_.notify_one();
}
protected:
void ThreadEntry(int i)
{
std::function<void()> execute_decode_job;
while(true)
{
{
std::unique_lock<std::mutex> lock(mutex_);
cond_var_.wait(lock,
[&] { return shutdown_ || !decode_jobs_queue_.empty(); });
if(decode_jobs_queue_.empty())
{
// No jobs to do; shutting down
return;
}
execute_decode_job = std::move(decode_jobs_queue_.front());
decode_jobs_queue_.pop();
}
// Execute the decode job without holding any locks
execute_decode_job();
}
}
std::mutex mutex_;
std::condition_variable cond_var_;
bool shutdown_;
std::queue<std::function<void()>> decode_jobs_queue_;
std::vector<std::thread> threads_;
};
struct DecoderInfo
{
int dec_device_id;
std::unique_ptr<RocVideoDecoder> viddec;
std::uint32_t bit_depth;
rocDecVideoCodec rocdec_codec_id;
std::atomic_bool decoding_complete;
DecoderInfo()
: dec_device_id(0)
, viddec(nullptr)
, bit_depth(8)
, decoding_complete(false)
{}
};
void
DecProc(RocVideoDecoder* p_dec, VideoDemuxer* demuxer, int* pn_frame, double* pn_fps,
std::atomic_bool& decoding_complete, bool& b_dump_output_frames,
std::string& output_file_name, OutputSurfaceMemoryType mem_type)
{
int n_video_bytes = 0, n_frame_returned = 0, n_frame = 0;
uint8_t * p_video = nullptr, *p_frame = nullptr;
int64_t pts = 0;
double total_dec_time = 0.0;
OutputSurfaceInfo* surf_info;
auto start_time = std::chrono::high_resolution_clock::now();
do
{
demuxer->Demux(&p_video, &n_video_bytes, &pts);
n_frame_returned = p_dec->DecodeFrame(p_video, n_video_bytes, 0, pts);
n_frame += n_frame_returned;
if(b_dump_output_frames && mem_type != OUT_SURFACE_MEM_NOT_MAPPED)
{
if(n_frame_returned)
{
if(!p_dec->GetOutputSurfaceInfo(&surf_info))
{
std::cerr << "Error: Failed to get Output Surface Info!" << std::endl;
break;
}
}
for(int i = 0; i < n_frame_returned; i++)
{
p_frame = p_dec->GetFrame(&pts);
p_dec->SaveFrameToFile(output_file_name, p_frame, surf_info);
// release frame
p_dec->ReleaseFrame(pts);
}
}
} while(n_video_bytes);
n_frame += p_dec->GetNumOfFlushedFrames();
auto end_time = std::chrono::high_resolution_clock::now();
auto time_per_decode =
std::chrono::duration<double, std::milli>(end_time - start_time).count();
// Calculate average decoding time
total_dec_time = time_per_decode;
double average_decoding_time = total_dec_time / n_frame;
double n_fps = 1000 / average_decoding_time;
*pn_fps = n_fps;
*pn_frame = n_frame;
p_dec->ResetSaveFrameToFile();
decoding_complete = true;
}
void
ShowHelpAndExit(const char* option = NULL)
{
std::cout
<< "Options:" << std::endl
<< "-i <directory containing input video files [required]> " << std::endl
<< "-t Number of threads ( 1 >= n_thread <= 64) - optional; default: 4"
<< std::endl
<< "-d Device ID (>= 0) - optional; default: 0" << std::endl
<< "-o Directory for output YUV files - optional" << std::endl
<< "-m output_surface_memory_type - decoded surface memory; optional; default - 3"
<< " [0 : OUT_SURFACE_MEM_DEV_INTERNAL/ 1 : OUT_SURFACE_MEM_DEV_COPIED/ 2 : "
"OUT_SURFACE_MEM_HOST_COPIED/ 3 : OUT_SURFACE_MEM_NOT_MAPPED]"
<< std::endl
<< "-disp_delay -specify the number of frames to be delayed for display; "
"optional; default: 1"
<< std::endl;
exit(0);
}
void
ParseCommandLine(std::string& input_folder_path, std::string& output_folder_path,
int& device_id, int& n_thread, bool& b_dump_output_frames,
OutputSurfaceMemoryType& mem_type, int& disp_delay, int argc,
char* argv[])
{
// Parse command-line arguments
if(argc <= 1)
{
ShowHelpAndExit();
}
for(int i = 1; i < argc; i++)
{
if(!strcmp(argv[i], "-h"))
{
ShowHelpAndExit();
}
if(!strcmp(argv[i], "-i"))
{
if(++i == argc)
{
ShowHelpAndExit("-i");
}
input_folder_path = argv[i];
continue;
}
if(!strcmp(argv[i], "-t"))
{
if(++i == argc)
{
ShowHelpAndExit("-t");
}
n_thread = atoi(argv[i]);
if(n_thread <= 0 || n_thread > 64)
{
ShowHelpAndExit(argv[i]);
}
continue;
}
if(!strcmp(argv[i], "-d"))
{
if(++i == argc)
{
ShowHelpAndExit("-d");
}
device_id = atoi(argv[i]);
if(device_id < 0)
{
ShowHelpAndExit(argv[i]);
}
continue;
}
if(!strcmp(argv[i], "-o"))
{
if(++i == argc)
{
ShowHelpAndExit("-o");
}
output_folder_path = argv[i];
#if __cplusplus >= 201703L && __has_include(<filesystem>)
if(std::filesystem::is_directory(output_folder_path))
{
std::filesystem::remove_all(output_folder_path);
}
std::filesystem::create_directory(output_folder_path);
#else
if(std::experimental::filesystem::is_directory(output_folder_path))
{
std::experimental::filesystem::remove_all(output_folder_path);
}
std::experimental::filesystem::create_directory(output_folder_path);
#endif
b_dump_output_frames = true;
continue;
}
if(!strcmp(argv[i], "-m"))
{
if(++i == argc)
{
ShowHelpAndExit("-m");
}
mem_type = static_cast<OutputSurfaceMemoryType>(atoi(argv[i]));
continue;
}
if(!strcmp(argv[i], "-disp_delay"))
{
if(++i == argc)
{
ShowHelpAndExit("-disp_delay");
}
disp_delay = atoi(argv[i]);
continue;
}
ShowHelpAndExit(argv[i]);
}
}
int
main(int argc, char** argv)
{
std::string input_folder_path, output_folder_path;
int device_id = 0, num_files = 0;
int n_thread = 4;
int disp_delay = 1;
Rect* p_crop_rect = nullptr;
bool b_extract_sei_messages = false;
OutputSurfaceMemoryType mem_type =
OUT_SURFACE_MEM_DEV_INTERNAL; // set to decode only for performance
bool b_force_zero_latency = false, b_dump_output_frames = false;
std::vector<std::string> input_file_names;
ParseCommandLine(input_folder_path, output_folder_path, device_id, n_thread,
b_dump_output_frames, mem_type, disp_delay, argc, argv);
try
{
#if __cplusplus >= 201703L && __has_include(<filesystem>)
for(const auto& entry : std::filesystem::directory_iterator(input_folder_path))
{
#else
for(const auto& entry :
std::experimental::filesystem::directory_iterator(input_folder_path))
{
#endif
input_file_names.push_back(entry.path());
num_files++;
}
std::vector<std::string> output_file_names(num_files);
n_thread = ((n_thread > num_files) ? num_files : n_thread);
int num_devices = 0, sd = 0;
hipError_t hip_status = hipSuccess;
hipDeviceProp_t hip_dev_prop;
std::string gcn_arch_name;
hip_status = hipGetDeviceCount(&num_devices);
if(hip_status != hipSuccess)
{
std::cout << "ERROR: hipGetDeviceCount failed! (" << hip_status << ")"
<< std::endl;
return -1;
}
if(num_devices < 1)
{
ERR("ERROR: didn't find any GPU!");
return -1;
}
hip_status = hipGetDeviceProperties(&hip_dev_prop, device_id);
if(hip_status != hipSuccess)
{
ERR("ERROR: hipGetDeviceProperties for device (" + TOSTR(device_id) +
" ) failed! (" + hipGetErrorName(hip_status) + ")");
return -1;
}
gcn_arch_name = hip_dev_prop.gcnArchName;
std::size_t pos = gcn_arch_name.find_first_of(":");
std::string gcn_arch_name_base =
(pos != std::string::npos) ? gcn_arch_name.substr(0, pos) : gcn_arch_name;
// gfx90a has two GCDs as two separate devices
if(!gcn_arch_name_base.compare("gfx90a") && num_devices > 1)
{
sd = 1;
}
std::string device_name;
int pci_bus_id, pci_domain_id, pci_device_id;
double total_fps = 0;
int n_total = 0;
std::vector<double> v_fps;
std::vector<int> v_frame;
v_fps.resize(num_files, 0);
v_frame.resize(num_files, 0);
int hip_vis_dev_count = 0;
GetEnvVar("HIP_VISIBLE_DEVICES", hip_vis_dev_count);
std::cout << "info: Number of threads: " << n_thread << std::endl;
std::vector<std::unique_ptr<VideoDemuxer>> v_demuxer(num_files);
std::unique_ptr<RocVideoDecoder> dec_8bit_avc(nullptr), dec_8bit_hevc(nullptr),
dec_10bit_hevc(nullptr), dec_8bit_av1(nullptr), dec_10bit_av1(nullptr),
dec_8bit_vp9(nullptr), dec_10bit_vp9(nullptr);
std::vector<std::unique_ptr<DecoderInfo>> v_dec_info;
ThreadPool thread_pool(n_thread);
// reconfig parameters
ReconfigParams reconfig_params = { 0 };
ReconfigDumpFileStruct reconfig_user_struct = { 0 };
reconfig_params.p_fn_reconfigure_flush = ReconfigureFlushCallback;
if(!b_dump_output_frames)
{
reconfig_user_struct.b_dump_frames_to_file = false;
reconfig_params.reconfig_flush_mode = RECONFIG_FLUSH_MODE_DUMP_TO_FILE;
}
else
{
reconfig_user_struct.b_dump_frames_to_file = true;
reconfig_params.reconfig_flush_mode = RECONFIG_FLUSH_MODE_NONE;
}
reconfig_params.p_reconfig_user_struct = &reconfig_user_struct;
for(int i = 0; i < num_files; i++)
{
std::unique_ptr<VideoDemuxer> demuxer(
new VideoDemuxer(input_file_names[i].c_str()));
v_demuxer[i] = std::move(demuxer);
std::size_t found_file = input_file_names[i].find_last_of('/');
input_file_names[i] = input_file_names[i].substr(found_file + 1);
if(b_dump_output_frames)
{
std::size_t found_ext = input_file_names[i].find_last_of('.');
std::string path = output_folder_path + "/output_" +
input_file_names[i].substr(0, found_ext) + ".yuv";
output_file_names[i] = path;
}
}
for(int i = 0; i < n_thread; i++)
{
v_dec_info.emplace_back(std::make_unique<DecoderInfo>());
if(!hip_vis_dev_count)
{
if(device_id % 2 == 0)
{
v_dec_info[i]->dec_device_id =
(i % 2 == 0) ? device_id : device_id + sd;
}
else
v_dec_info[i]->dec_device_id =
(i % 2 == 0) ? device_id - sd : device_id;
}
else
{
v_dec_info[i]->dec_device_id = i % hip_vis_dev_count;
}
v_dec_info[i]->rocdec_codec_id =
AVCodec2RocDecVideoCodec(v_demuxer[i]->GetCodecID());
v_dec_info[i]->bit_depth = v_demuxer[i]->GetBitDepth();
if(v_dec_info[i]->bit_depth == 8)
{
if(v_dec_info[i]->rocdec_codec_id == rocDecVideoCodec_AVC)
{
std::unique_ptr<RocVideoDecoder> dec_8bit_avc(new RocVideoDecoder(
v_dec_info[i]->dec_device_id, mem_type,
v_dec_info[i]->rocdec_codec_id, b_force_zero_latency, p_crop_rect,
b_extract_sei_messages, disp_delay));
v_dec_info[i]->viddec = std::move(dec_8bit_avc);
}
else if(v_dec_info[i]->rocdec_codec_id == rocDecVideoCodec_HEVC)
{
std::unique_ptr<RocVideoDecoder> dec_8bit_hevc(new RocVideoDecoder(
v_dec_info[i]->dec_device_id, mem_type,
v_dec_info[i]->rocdec_codec_id, b_force_zero_latency, p_crop_rect,
b_extract_sei_messages, disp_delay));
v_dec_info[i]->viddec = std::move(dec_8bit_hevc);
}
else if(v_dec_info[i]->rocdec_codec_id == rocDecVideoCodec_AV1)
{
std::unique_ptr<RocVideoDecoder> dec_8bit_av1(new RocVideoDecoder(
v_dec_info[i]->dec_device_id, mem_type,
v_dec_info[i]->rocdec_codec_id, b_force_zero_latency, p_crop_rect,
b_extract_sei_messages, disp_delay));
v_dec_info[i]->viddec = std::move(dec_8bit_av1);
}
else if(v_dec_info[i]->rocdec_codec_id == rocDecVideoCodec_VP9)
{
std::unique_ptr<RocVideoDecoder> dec_8bit_vp9(new RocVideoDecoder(
v_dec_info[i]->dec_device_id, mem_type,
v_dec_info[i]->rocdec_codec_id, b_force_zero_latency, p_crop_rect,
b_extract_sei_messages, disp_delay));
v_dec_info[i]->viddec = std::move(dec_8bit_vp9);
}
else
{
ERR("ERROR: codec type is not supported!");
return -1;
}
}
else
{ // bit depth = 10bit
if(v_dec_info[i]->rocdec_codec_id == rocDecVideoCodec_HEVC)
{
std::unique_ptr<RocVideoDecoder> dec_10bit_hevc(new RocVideoDecoder(
v_dec_info[i]->dec_device_id, mem_type,
v_dec_info[i]->rocdec_codec_id, b_force_zero_latency, p_crop_rect,
b_extract_sei_messages, disp_delay));
v_dec_info[i]->viddec = std::move(dec_10bit_hevc);
}
else if(v_dec_info[i]->rocdec_codec_id == rocDecVideoCodec_AV1)
{
std::unique_ptr<RocVideoDecoder> dec_10bit_av1(new RocVideoDecoder(
v_dec_info[i]->dec_device_id, mem_type,
v_dec_info[i]->rocdec_codec_id, b_force_zero_latency, p_crop_rect,
b_extract_sei_messages, disp_delay));
v_dec_info[i]->viddec = std::move(dec_10bit_av1);
}
else if(v_dec_info[i]->rocdec_codec_id == rocDecVideoCodec_VP9)
{
std::unique_ptr<RocVideoDecoder> dec_10bit_vp9(new RocVideoDecoder(
v_dec_info[i]->dec_device_id, mem_type,
v_dec_info[i]->rocdec_codec_id, b_force_zero_latency, p_crop_rect,
b_extract_sei_messages, disp_delay));
v_dec_info[i]->viddec = std::move(dec_10bit_vp9);
}
else
{
ERR("ERROR: codec type is not supported!");
return -1;
}
}
v_dec_info[i]->viddec->GetDeviceinfo(device_name, gcn_arch_name, pci_bus_id,
pci_domain_id, pci_device_id);
std::cout << "info: decoding " << input_file_names[i] << " using GPU device "
<< v_dec_info[i]->dec_device_id << " - " << device_name << "["
<< gcn_arch_name << "] on PCI bus " << std::setfill('0')
<< std::setw(2) << std::right << std::hex << pci_bus_id << ":"
<< std::setfill('0') << std::setw(2) << std::right << std::hex
<< pci_domain_id << "." << pci_device_id << std::dec << std::endl;
}
std::mutex mutex;
for(int j = 0; j < num_files; j++)
{
int thread_idx = j % n_thread;
if(j >= n_thread)
{
{
std::unique_lock<std::mutex> lock(mutex);
while(!v_dec_info[thread_idx]->decoding_complete)
;
v_dec_info[thread_idx]->decoding_complete = false;
}
uint32_t bit_depth = v_demuxer[j]->GetBitDepth();
rocDecVideoCodec codec_id =
AVCodec2RocDecVideoCodec(v_demuxer[j]->GetCodecID());
if(v_dec_info[thread_idx]->bit_depth != bit_depth ||
v_dec_info[thread_idx]->rocdec_codec_id != codec_id)
{
if(bit_depth == 8)
{ // can be HEVC or H.264 or AV1
if(dec_8bit_avc == nullptr && codec_id == rocDecVideoCodec_AVC)
{
std::unique_ptr<RocVideoDecoder> dec_8bit_avc(
new RocVideoDecoder(v_dec_info[thread_idx]->dec_device_id,
mem_type, codec_id,
b_force_zero_latency, p_crop_rect,
b_extract_sei_messages, disp_delay));
v_dec_info[thread_idx]->viddec = std::move(dec_8bit_avc);
}
else if(dec_8bit_hevc == nullptr &&
codec_id == rocDecVideoCodec_HEVC)
{
std::unique_ptr<RocVideoDecoder> dec_8bit_hevc(
new RocVideoDecoder(v_dec_info[thread_idx]->dec_device_id,
mem_type, codec_id,
b_force_zero_latency, p_crop_rect,
b_extract_sei_messages, disp_delay));
v_dec_info[thread_idx]->viddec = std::move(dec_8bit_hevc);
}
else if(dec_8bit_av1 == nullptr &&
codec_id == rocDecVideoCodec_AV1)
{
std::unique_ptr<RocVideoDecoder> dec_8bit_av1(
new RocVideoDecoder(v_dec_info[thread_idx]->dec_device_id,
mem_type, codec_id,
b_force_zero_latency, p_crop_rect,
b_extract_sei_messages, disp_delay));
v_dec_info[thread_idx]->viddec = std::move(dec_8bit_av1);
}
else if(dec_8bit_av1 == nullptr &&
codec_id == rocDecVideoCodec_VP9)
{
std::unique_ptr<RocVideoDecoder> dec_8bit_vp9(
new RocVideoDecoder(v_dec_info[thread_idx]->dec_device_id,
mem_type, codec_id,
b_force_zero_latency, p_crop_rect,
b_extract_sei_messages, disp_delay));
v_dec_info[thread_idx]->viddec = std::move(dec_8bit_vp9);
}
else
{
if(codec_id == rocDecVideoCodec_AVC)
{
v_dec_info[thread_idx]->viddec.swap(dec_8bit_avc);
}
else if(codec_id == rocDecVideoCodec_HEVC)
{
v_dec_info[thread_idx]->viddec.swap(dec_8bit_hevc);
}
else if(codec_id == rocDecVideoCodec_AV1)
{
v_dec_info[thread_idx]->viddec.swap(dec_8bit_av1);
}
else if(codec_id == rocDecVideoCodec_VP9)
{
v_dec_info[thread_idx]->viddec.swap(dec_8bit_vp9);
}
else
{
ERR("ERROR: codec type is not supported!");
return -1;
}
}
v_dec_info[thread_idx]->bit_depth = bit_depth;
v_dec_info[thread_idx]->rocdec_codec_id = codec_id;
}
else
{ // bit_depth = 10bit; HEVC or AV1
if(dec_10bit_hevc == nullptr && codec_id == rocDecVideoCodec_HEVC)
{
std::unique_ptr<RocVideoDecoder> dec_10bit_hevc(
new RocVideoDecoder(v_dec_info[thread_idx]->dec_device_id,
mem_type, codec_id,
b_force_zero_latency, p_crop_rect,
b_extract_sei_messages, disp_delay));
v_dec_info[thread_idx]->viddec = std::move(dec_10bit_hevc);
}
else if(dec_10bit_av1 == nullptr &&
codec_id == rocDecVideoCodec_AV1)
{
std::unique_ptr<RocVideoDecoder> dec_10bit_av1(
new RocVideoDecoder(v_dec_info[thread_idx]->dec_device_id,
mem_type, codec_id,
b_force_zero_latency, p_crop_rect,
b_extract_sei_messages, disp_delay));
v_dec_info[thread_idx]->viddec = std::move(dec_10bit_av1);
}
else if(dec_10bit_vp9 == nullptr &&
codec_id == rocDecVideoCodec_VP9)
{
std::unique_ptr<RocVideoDecoder> dec_10bit_vp9(
new RocVideoDecoder(v_dec_info[thread_idx]->dec_device_id,
mem_type, codec_id,
b_force_zero_latency, p_crop_rect,
b_extract_sei_messages, disp_delay));
v_dec_info[thread_idx]->viddec = std::move(dec_10bit_vp9);
}
else
{
if(codec_id == rocDecVideoCodec_HEVC)
{
v_dec_info[thread_idx]->viddec.swap(dec_10bit_hevc);
}
else if(codec_id == rocDecVideoCodec_AV1)
{
v_dec_info[thread_idx]->viddec.swap(dec_10bit_av1);
}
else if(codec_id == rocDecVideoCodec_VP9)
{
v_dec_info[thread_idx]->viddec.swap(dec_10bit_vp9);
}
else
{
ERR("ERROR: codec type is not supported!");
return -1;
}
}
v_dec_info[thread_idx]->bit_depth = bit_depth;
v_dec_info[thread_idx]->rocdec_codec_id = codec_id;
}
}
v_dec_info[thread_idx]->viddec->GetDeviceinfo(
device_name, gcn_arch_name, pci_bus_id, pci_domain_id, pci_device_id);
std::cout << "info: decoding " << input_file_names[j]
<< " using GPU device " << v_dec_info[thread_idx]->dec_device_id
<< " - " << device_name << "[" << gcn_arch_name
<< "] on PCI bus " << std::setfill('0') << std::setw(2)
<< std::right << std::hex << pci_bus_id << ":"
<< std::setfill('0') << std::setw(2) << std::right << std::hex
<< pci_domain_id << "." << pci_device_id << std::dec
<< std::endl;
}
if(!v_dec_info[thread_idx]->viddec->CodecSupported(
v_dec_info[thread_idx]->dec_device_id,
v_dec_info[thread_idx]->rocdec_codec_id,
v_dec_info[thread_idx]->bit_depth))
{
std::cerr << "Codec not supported on GPU, skipping this file!"
<< std::endl;
v_dec_info[thread_idx]->decoding_complete = true;
continue;
}
thread_pool.ExecuteJob(
std::bind(DecProc, v_dec_info[thread_idx]->viddec.get(),
v_demuxer[j].get(), &v_frame[j], &v_fps[j],
std::ref(v_dec_info[thread_idx]->decoding_complete),
b_dump_output_frames, output_file_names[j], mem_type));
}
thread_pool.JoinThreads();
for(int i = 0; i < num_files; i++)
{
total_fps +=
v_fps[i] * static_cast<double>(n_thread) / static_cast<double>(num_files);
n_total += v_frame[i];
}
if(!b_dump_output_frames)
{
std::cout << "info: Total frame decoded: " << n_total << std::endl;
std::cout << "info: avg decoding time per frame: " << 1000 / total_fps
<< " ms" << std::endl;
std::cout << "info: avg FPS: " << total_fps << std::endl;
}
else
{
if(mem_type == OUT_SURFACE_MEM_NOT_MAPPED)
{
std::cout << "info: saving frames with -m 3 option is not supported!"
<< std::endl;
}
else
{
for(int i = 0; i < num_files; i++)
std::cout << "info: saved frames into " << output_file_names[i]
<< std::endl;
}
}
} catch(const std::exception& ex)
{
std::cout << ex.what() << std::endl;
}
return 0;
}
@@ -23,5 +23,6 @@ include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-overflow-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-annotate-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-causal-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-python-tests.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/rocprof-sys-videodecode-tests.cmake)
add_subdirectory(source)
@@ -0,0 +1,20 @@
# -------------------------------------------------------------------------------------- #
#
# video decode tests
#
# -------------------------------------------------------------------------------------- #
rocprofiler_systems_add_test(
SKIP_BASELINE SKIP_RUNTIME SKIP_REWRITE
NAME videodecode
TARGET videodecode
GPU ON
RUN_ARGS -i ${ROCmVersion_DIR}/share/rocdecode/video/ -t 2
LABELS "videodecode")
rocprofiler_systems_add_validation_test(
NAME videodecode-sampling
PERFETTO_METRIC "host"
PERFETTO_FILE "perfetto-trace.proto"
LABELS "videodecode"
ARGS -l videodecode -c 1 -d 0 --counter-names "GPU VCN Activity")
@@ -101,6 +101,13 @@ if __name__ == "__main__":
default=[],
nargs="*",
)
parser.add_argument(
"--counter-names",
type=str,
help="Require counter name in the traces",
default=[],
nargs="*",
)
args = parser.parse_args()
@@ -175,6 +182,18 @@ if __name__ == "__main__":
if key_count != count:
ret = 1
for counter_name in args.counter_names:
sum_counter_values = tp.query(
f"""SELECT SUM(counter.value) AS total_value FROM counter_track JOIN counter ON
counter.track_id = counter_track.id WHERE counter_track.name LIKE
'{counter_name}%'"""
)
total_value = 0
for row in sum_counter_values:
total_value = row.total_value if row.total_value is not None else -1
if total_value < 0:
ret = 1
if ret == 0:
print(f"{args.input} validated")
else: