Add 'projects/rocdecode/' from commit 'b0bab079403eda171f9056409fa96b0908f61073'
git-subtree-dir: projects/rocdecode git-subtree-mainline:5d609c1e57git-subtree-split:b0bab07940
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
# Samples overview
|
||||
|
||||
rocDecode samples
|
||||
|
||||
## [Video decode](videoDecode)
|
||||
|
||||
The video decode sample illustrates decoding a single packetized video stream using FFMPEG demuxer, video parser, and rocDecoder to get the individual decoded frames in YUV format. This sample can be configured with a device ID and optionally able to dump the output to a file. This sample uses the high-level RocVideoDecoder class which connects both the video parser and Rocdecoder. This process repeats in a loop until all frames have been decoded.
|
||||
|
||||
## [Video decode batch sample](videoDecodeBatch)
|
||||
|
||||
This sample decodes multiple files using multiple threads, using the rocDecode library. The input is a directory of files and an input number of threads. The maximum number of threads is capped to 64.
|
||||
If the number of files is higher than the number of threads requested by the user, the files are distributed to the threads in a round robin fashion.
|
||||
If the number of files is lesser than the number of threads requested by the user, the number of threads created will be equal to the number of files.
|
||||
|
||||
## [Video decode memory](videoDecodeMem)
|
||||
|
||||
The video decode memory sample illustrates a way to pass the data chunk-by-chunk sequentially to the FFMPEG demuxer which is then decoded on AMD hardware using rocDecode library.
|
||||
|
||||
The sample provides a user class `FileStreamProvider` derived from the existing `VideoDemuxer::StreamProvider` to read a video file and fill the buffer owned by the demuxer. It then takes frames from this buffer for further parsing and decoding.
|
||||
|
||||
## [Video decode multi files](videoDecodeMultiFiles)
|
||||
|
||||
The video decodes multiple files sample illustrates the use of providing a list of files as input to showcase the reconfigure option in the rocDecode library. The input video files have to be of the same codec type to use the reconfigure option but can have different resolutions or resize parameters.
|
||||
|
||||
The reconfigure option can be disabled by the user if needed. The input file is parsed line by line and data is stored in a queue. The individual video files are demuxed and decoded one after the other in a loop. Output for each input file can also be stored if needed.
|
||||
|
||||
## [Video decode performance](videoDecodePerf)
|
||||
|
||||
This sample illustrates the FFMPEG demuxer to get the individual frames which are then decoded on AMD hardware using rocDecode library.
|
||||
|
||||
This sample uses multiple threads to decode the same input video parallelly.
|
||||
|
||||
## [Video decode RGB](videoDecodeRGB)
|
||||
|
||||
This sample illustrates the FFMPEG demuxer to get the individual frames which are then decoded using rocDecode API and optionally color-converted using custom HIP kernels on AMD hardware. This sample converts decoded YUV output to one of the RGB or BGR formats(24bit, 32bit, 464bit) in a separate thread allowing it to run both VCN hardware and compute engine in parallel.
|
||||
|
||||
This sample uses HIP kernels to showcase the color conversion. Whenever a frame is ready after decoding, the `ColorSpaceConversionThread` is notified and can be used for post-processing.
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
Copyright (c) 2023 - 2026 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"
|
||||
#include "md5.h"
|
||||
|
||||
typedef enum ReconfigFlushMode_enum {
|
||||
RECONFIG_FLUSH_MODE_NONE = 0x0, /**< Just flush to get the frame count */
|
||||
RECONFIG_FLUSH_MODE_DUMP_TO_FILE = 0x1, /**< The remaining frames will be dumped to file in this mode */
|
||||
RECONFIG_FLUSH_MODE_CALCULATE_MD5 = (0x1 << 1), /**< Calculate the MD5 of the flushed frames */
|
||||
} ReconfigFlushMode;
|
||||
|
||||
// This struct is used by sample apps to dump last frames to file
|
||||
typedef struct ReconfigDumpFileStruct_t {
|
||||
bool b_dump_frames_to_file;
|
||||
std::string output_file_name;
|
||||
void *md5_generator_handle;
|
||||
} 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) {
|
||||
ReconfigDumpFileStruct *p_dump_file_struct = static_cast<ReconfigDumpFileStruct *>(p_user_struct);
|
||||
if (flush_mode & ReconfigFlushMode::RECONFIG_FLUSH_MODE_DUMP_TO_FILE) {
|
||||
if (p_dump_file_struct->b_dump_frames_to_file) {
|
||||
viddec->SaveFrameToFile(p_dump_file_struct->output_file_name, pframe, surf_info);
|
||||
}
|
||||
}
|
||||
if (flush_mode & ReconfigFlushMode::RECONFIG_FLUSH_MODE_CALCULATE_MD5) {
|
||||
MD5Generator *md5_generator = static_cast<MD5Generator*>(p_dump_file_struct->md5_generator_handle);
|
||||
md5_generator->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;
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
################################################################################
|
||||
# Copyright (c) 2023 - 2026 Advanced Micro Devices, Inc.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
cmake_minimum_required (VERSION 3.10)
|
||||
# ROCM Path
|
||||
if(DEFINED ENV{ROCM_PATH})
|
||||
set(ROCM_PATH $ENV{ROCM_PATH} CACHE PATH "${White}${PROJECT_NAME}: Default ROCm installation path${ColourReset}")
|
||||
elseif(ROCM_PATH)
|
||||
message("-- ${White}${PROJECT_NAME} :ROCM_PATH Set -- ${ROCM_PATH}${ColourReset}")
|
||||
else()
|
||||
set(ROCM_PATH /opt/rocm CACHE PATH "${White}${PROJECT_NAME}: Default ROCm installation path${ColourReset}")
|
||||
endif()
|
||||
# Set AMD Clang as default compiler
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED On)
|
||||
set(CMAKE_CXX_EXTENSIONS ON)
|
||||
if (NOT DEFINED CMAKE_CXX_COMPILER)
|
||||
set(CMAKE_C_COMPILER ${ROCM_PATH}/lib/llvm/bin/amdclang)
|
||||
set(CMAKE_CXX_COMPILER ${ROCM_PATH}/lib/llvm/bin/amdclang++)
|
||||
endif()
|
||||
|
||||
project(rocdecdecode)
|
||||
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/../../cmake)
|
||||
list(APPEND CMAKE_PREFIX_PATH ${ROCM_PATH}/lib/cmake)
|
||||
|
||||
# rocdecode sample build type
|
||||
set(DEFAULT_BUILD_TYPE "Release")
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE "${DEFAULT_BUILD_TYPE}" CACHE STRING "rocdecode Default Build Type" FORCE)
|
||||
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release")
|
||||
endif()
|
||||
if(CMAKE_BUILD_TYPE MATCHES Debug)
|
||||
# -O0 -- Don't Optimize output file
|
||||
# -gdwarf-4 -- generate debugging information, dwarf-4 for making valgrind work
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -gdwarf-4 -Wall")
|
||||
else()
|
||||
# -O3 -- Optimize output file
|
||||
# -DNDEBUG -- turn off asserts
|
||||
# -fPIC -- Generate position-independent code if possible
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -DNDEBUG -fPIC -Wall")
|
||||
endif()
|
||||
|
||||
set (HIP_PLATFORM amd CACHE STRING "HIP platform")
|
||||
find_package(HIP QUIET)
|
||||
find_package(FFmpeg QUIET)
|
||||
find_package(rocdecode QUIET)
|
||||
find_package(rocdecode-host 1.0.0 QUIET)
|
||||
|
||||
if(HIP_FOUND AND rocdecode_FOUND)
|
||||
# HIP
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} hip::host)
|
||||
|
||||
if(FFMPEG_FOUND)
|
||||
# FFMPEG
|
||||
include_directories(${AVUTIL_INCLUDE_DIR} ${AVCODEC_INCLUDE_DIR}
|
||||
${AVFORMAT_INCLUDE_DIR})
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} ${FFMPEG_LIBRARIES})
|
||||
endif()
|
||||
if(rocdecode-host_FOUND)
|
||||
# rocdecode-host
|
||||
include_directories(${rocdecode-host_INCLUDE_DIR})
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} rocdecode::rocdecode-host)
|
||||
endif()
|
||||
|
||||
# rocdecode and utils
|
||||
include_directories(${rocdecode_INCLUDE_DIR} ${ROCM_PATH}/lib)
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} rocdecode::rocdecode)
|
||||
|
||||
# rocdecode
|
||||
# sample app exe
|
||||
list(APPEND SOURCES ${PROJECT_SOURCE_DIR} rocdecdecode.cpp)
|
||||
add_executable(${PROJECT_NAME} ${SOURCES})
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++17")
|
||||
if(FFMPEG_FOUND AND rocdecode-host_FOUND)
|
||||
target_compile_definitions(${PROJECT_NAME} PUBLIC ENABLE_HOST_DECODE=1)
|
||||
else()
|
||||
target_compile_definitions(${PROJECT_NAME} PUBLIC ENABLE_HOST_DECODE=0)
|
||||
endif()
|
||||
target_link_libraries(${PROJECT_NAME} ${LINK_LIBRARY_LIST})
|
||||
else()
|
||||
message("-- ERROR!: ${PROJECT_NAME} excluded! please install all the dependencies and try again!")
|
||||
if (NOT HIP_FOUND)
|
||||
message(FATAL_ERROR "-- ERROR!: HIP Not Found! - please install ROCm and HIP!")
|
||||
endif()
|
||||
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()
|
||||
if (NOT rocdecode-host_FOUND)
|
||||
message(FATAL_ERROR "-- ERROR!: rocdecode-host Not Found! - please install rocdecode-host!")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
|
||||
# rocdecDecode sample
|
||||
|
||||
The rocdec decode sample illustrates decoding of individual frames of video elementary stream data using the rocDecoder and rocDecodeHost low level api to get the individual decoded frames in YUV format. This sample can be configured with a device ID and optionally able to dump the output to a file. This sample directly uses low-level Rocdecoder/RocDecoderHost api. This sample only works with raw elementary video frame files, and not with packetized data. Typical input to this sample is a folder containing extracted individual video frames of one or more video files. The files containing individual frames has to be numbered in ascending order of frames, otherwise the output will be corrupted since the parser assumes random order for those files which can result in corrupted reference frames.
|
||||
|
||||
|
||||
### Note: If the input is a packetized file like ".mp4", the sample will treat it as a single video frame and output will not be correct.
|
||||
|
||||
## Prerequisites:
|
||||
|
||||
* Install [rocDecode](../../README.md#build-and-install-instructions)
|
||||
|
||||
* [FFMPEG](https://ffmpeg.org/about.html) for rocDecodeHost
|
||||
|
||||
* On `Ubuntu`
|
||||
|
||||
```shell
|
||||
sudo apt install libavcodec-dev libavformat-dev libavutil-dev
|
||||
```
|
||||
|
||||
* On `RHEL`/`SLES` - install ffmpeg development packages manually or use [rocDecode-setup.py](../../rocDecode-setup.py) script
|
||||
|
||||
## Build
|
||||
|
||||
```shell
|
||||
mkdir rocdec_decode_sample && cd rocdec_decode_sample
|
||||
cmake ../
|
||||
make -j
|
||||
```
|
||||
|
||||
## Run
|
||||
|
||||
```shell
|
||||
./rocdecdecode -i <input video frame file or folder containing multiple frames [required]> -b <backend> -o <outfile>
|
||||
-o <output path to save decoded YUV frames [optional]>
|
||||
-d <GPU device ID - 0:device 0 / 1:device 1/ ... [optional - default:0]>
|
||||
-b <backend for the decoder - 0:device 1:host [optional - default:0]>
|
||||
-c <codec - 0 : HEVC, 1 : H264, 2: AV1, 4: VP9, 5: VP8, 6: MJPEG [optional; default: 0]>
|
||||
-n <Number of iteration - specify the number of iterations for performance evaluation [optional; default: 1]>
|
||||
-m <output_surface_memory_type - decoded surface memory [optional - default: 0][0 : OUT_SURFACE_MEM_DEV_INTERNAL/ 1 : OUT_SURFACE_MEM_DEV_COPIED/ 2 : OUT_SURFACE_MEM_HOST_COPIED/3 : OUT_SURFACE_MEM_NOT_MAPPED]>
|
||||
```
|
||||
```shell
|
||||
"./rocdecdecode -i ROCDECODE_DATA_FOLDER/frames -o <output.yuv> -b 0".
|
||||
```
|
||||
@@ -0,0 +1,757 @@
|
||||
/*
|
||||
Copyright (c) 2023 - 2026 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 <chrono>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <hip/hip_runtime.h>
|
||||
#include <rocdecode/rocdecode.h>
|
||||
#include <rocdecode/rocparser.h>
|
||||
#if ENABLE_HOST_DECODE
|
||||
#include <rocdecode/rocdecode_host.h>
|
||||
#endif
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
__attribute__((visibility("hidden"))) inline bool is_error(rocDecStatus status)
|
||||
{
|
||||
return status != ROCDEC_SUCCESS;
|
||||
}
|
||||
|
||||
__attribute__((visibility("hidden"))) inline const char* error_string(rocDecStatus status)
|
||||
{
|
||||
return rocDecGetErrorName(status);
|
||||
}
|
||||
|
||||
struct Rect {
|
||||
int left;
|
||||
int top;
|
||||
int right;
|
||||
int bottom;
|
||||
};
|
||||
|
||||
template <typename Status, typename... Args>
|
||||
__attribute__((visibility("hidden"))) inline void report_error(
|
||||
Status status, const char* function_name, const char* file_name, int line, Args&&... args)
|
||||
{
|
||||
((std::cerr << "ERROR: " << error_string(status) << "; " << function_name << "; "
|
||||
<< file_name << ":" << line)
|
||||
<< ... << std::forward<Args>(args))
|
||||
<< std::endl;
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
//hardcoding for this sample
|
||||
#define DEFAULT_WIDTH 2912
|
||||
#define DEFAULT_HEIGHT 1888
|
||||
|
||||
// helper functions for saving output to file
|
||||
|
||||
static inline float GetChromaHeightFactor(rocDecVideoSurfaceFormat surface_format) {
|
||||
float factor = 0.5;
|
||||
switch (surface_format) {
|
||||
case rocDecVideoSurfaceFormat_NV12:
|
||||
case rocDecVideoSurfaceFormat_P016:
|
||||
case rocDecVideoSurfaceFormat_YUV420:
|
||||
case rocDecVideoSurfaceFormat_YUV420_16Bit:
|
||||
factor = 0.5;
|
||||
break;
|
||||
case rocDecVideoSurfaceFormat_YUV422:
|
||||
case rocDecVideoSurfaceFormat_YUV422_16Bit:
|
||||
case rocDecVideoSurfaceFormat_YUV444:
|
||||
case rocDecVideoSurfaceFormat_YUV444_16Bit:
|
||||
factor = 1.0;
|
||||
break;
|
||||
}
|
||||
|
||||
return factor;
|
||||
};
|
||||
|
||||
static inline rocDecVideoCodec CodecTypeToRocDecVideoCodec(int codec_type) {
|
||||
switch (codec_type) {
|
||||
case 0: return rocDecVideoCodec_HEVC;
|
||||
case 1: return rocDecVideoCodec_AVC;
|
||||
case 2: return rocDecVideoCodec_AV1;
|
||||
case 3: return rocDecVideoCodec_VP9;
|
||||
case 4: return rocDecVideoCodec_VP8;
|
||||
case 5: return rocDecVideoCodec_JPEG;
|
||||
default: return rocDecVideoCodec_NumCodecs;
|
||||
}
|
||||
}
|
||||
static inline float GetChromaWidthFactor(rocDecVideoSurfaceFormat surface_format) {
|
||||
float factor = 0.5;
|
||||
switch (surface_format) {
|
||||
case rocDecVideoSurfaceFormat_NV12:
|
||||
case rocDecVideoSurfaceFormat_P016:
|
||||
case rocDecVideoSurfaceFormat_YUV444:
|
||||
case rocDecVideoSurfaceFormat_YUV444_16Bit:
|
||||
factor = 1.0;
|
||||
break;
|
||||
case rocDecVideoSurfaceFormat_YUV420:
|
||||
case rocDecVideoSurfaceFormat_YUV420_16Bit:
|
||||
case rocDecVideoSurfaceFormat_YUV422:
|
||||
case rocDecVideoSurfaceFormat_YUV422_16Bit:
|
||||
factor = 0.5;
|
||||
break;
|
||||
}
|
||||
return factor;
|
||||
};
|
||||
|
||||
// only 2 types of memory mode is supported in this sample for simplicity.
|
||||
typedef enum OutputSurfaceMemoryType_enum {
|
||||
OUT_SURFACE_MEM_DEV_INTERNAL = 0, /**< Internal interopped decoded surface memory(original mapped decoded surface) */
|
||||
OUT_SURFACE_MEM_HOST = 2, /**< decoded output will be in host memory (true for host based decoding) **/
|
||||
} OutputSurfaceMemoryType;
|
||||
|
||||
// Enum for decoder backend
|
||||
typedef enum DecoderBackend_enum {
|
||||
DECODER_BACKEND_DEVICE = 0, /**< Decoding using VCN hardware in the device specified by user */
|
||||
DECODER_BACKEND_HOST = 1, /**< decoded using host and ffmpeg avcodec **/
|
||||
} DecoderBackend;
|
||||
|
||||
|
||||
#define CHECK(callable, ...) \
|
||||
do \
|
||||
{ \
|
||||
auto status__ = callable; /* invoke the callable and assign the return status */ \
|
||||
if (is_error(status__)) \
|
||||
{ \
|
||||
report_error(status__, __FUNCTION__, __FILE__, __LINE__, ##__VA_ARGS__); \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
/**
|
||||
* @brief Struct containing all the information for decoding and displaying output
|
||||
*
|
||||
*/
|
||||
struct DecoderInfo {
|
||||
int dec_device_id;
|
||||
DecoderBackend backend; //0: device, 1: host
|
||||
rocDecDecoderHandle decoder;
|
||||
RocdecVideoParser parser;
|
||||
std::uint32_t bit_depth;
|
||||
rocDecVideoCodec rocdec_codec_id;
|
||||
int dump_decoded_frames;
|
||||
std::string output_file_path;
|
||||
OutputSurfaceMemoryType mem_type;
|
||||
rocDecVideoSurfaceFormat surf_format;
|
||||
rocDecVideoSurfaceFormat video_chroma_format;
|
||||
uint32_t coded_width, coded_height;
|
||||
uint32_t bytes_per_pixel;
|
||||
bool is_decoder_reconfigured;
|
||||
Rect disp_rect;
|
||||
FILE *fp_out;
|
||||
DecoderInfo() : dec_device_id(0), backend(DECODER_BACKEND_DEVICE), decoder(nullptr), bit_depth(8), dump_decoded_frames(0), mem_type{OUT_SURFACE_MEM_DEV_INTERNAL},
|
||||
surf_format{rocDecVideoSurfaceFormat_NV12}, video_chroma_format{rocDecVideoSurfaceFormat_NV12},
|
||||
is_decoder_reconfigured{false}, fp_out{nullptr} {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Funtion to save internal frame buffer to file for device buffer : chroma format is assumed to be NV12 for internal device memory
|
||||
*
|
||||
* @param p_dec_info
|
||||
* @param surf_mem device mem pointers of luma and chroma planes
|
||||
* @param pitch stride in bytes of luma and chroma planes
|
||||
*/
|
||||
void save_frame_to_file(DecoderInfo *p_dec_info, void *surf_mem[], uint32_t *pitch) {
|
||||
|
||||
uint8_t *hst_ptr = nullptr;
|
||||
uint64_t output_image_size_luma = pitch[0] * p_dec_info->coded_height;
|
||||
uint64_t output_image_size_chroma = pitch[1] * ((p_dec_info->coded_height * GetChromaHeightFactor(p_dec_info->surf_format)));
|
||||
if (p_dec_info->mem_type == OUT_SURFACE_MEM_DEV_INTERNAL) {
|
||||
if (hst_ptr == nullptr) {
|
||||
hst_ptr = new uint8_t [output_image_size_luma + output_image_size_chroma];
|
||||
}
|
||||
hipError_t hip_status = hipSuccess;
|
||||
// copy luma
|
||||
hip_status = hipMemcpyDtoH((void *)hst_ptr, surf_mem[0], output_image_size_luma);
|
||||
if (hip_status != hipSuccess) {
|
||||
std::cerr << "ERROR: hipMemcpyDtoH failed for luma! (" << hipGetErrorName(hip_status) << ")" << std::endl;
|
||||
delete [] hst_ptr;
|
||||
return;
|
||||
}
|
||||
hip_status = hipMemcpyDtoH((void *)(hst_ptr + output_image_size_luma), surf_mem[1], output_image_size_chroma);
|
||||
if (hip_status != hipSuccess) {
|
||||
std::cerr << "ERROR: hipMemcpyDtoH failed for chroma! (" << hipGetErrorName(hip_status) << ")" << std::endl;
|
||||
delete [] hst_ptr;
|
||||
return;
|
||||
}
|
||||
} else
|
||||
hst_ptr = static_cast<uint8_t *> (surf_mem[0]);
|
||||
|
||||
if (p_dec_info->is_decoder_reconfigured) {
|
||||
if (p_dec_info->fp_out) {
|
||||
fclose(p_dec_info->fp_out);
|
||||
p_dec_info->fp_out = nullptr;
|
||||
}
|
||||
p_dec_info->is_decoder_reconfigured = false;
|
||||
}
|
||||
|
||||
if (p_dec_info->fp_out == nullptr && !p_dec_info->output_file_path.empty()) {
|
||||
p_dec_info->fp_out = fopen(p_dec_info->output_file_path.c_str(), "wb");
|
||||
}
|
||||
|
||||
if (p_dec_info->fp_out) {
|
||||
uint8_t *tmp_hst_ptr = hst_ptr;
|
||||
if (p_dec_info->mem_type == OUT_SURFACE_MEM_DEV_INTERNAL) {
|
||||
tmp_hst_ptr += (p_dec_info->disp_rect.top * pitch[0]) + (p_dec_info->disp_rect.left * p_dec_info->bytes_per_pixel);
|
||||
}
|
||||
int img_width = p_dec_info->disp_rect.right - p_dec_info->disp_rect.left;
|
||||
int img_height = p_dec_info->disp_rect.bottom - p_dec_info->disp_rect.top;
|
||||
uint32_t output_stride = pitch[0];
|
||||
if ((img_width * p_dec_info->bytes_per_pixel) == output_stride) {
|
||||
fwrite(tmp_hst_ptr, 1, output_image_size_luma, p_dec_info->fp_out);
|
||||
tmp_hst_ptr += output_image_size_luma;
|
||||
fwrite(tmp_hst_ptr, 1, output_image_size_chroma, p_dec_info->fp_out);
|
||||
} else {
|
||||
uint32_t width = img_width * p_dec_info->bytes_per_pixel;
|
||||
if (p_dec_info->bit_depth <= 16) {
|
||||
for (int i = 0; i < img_height; i++) {
|
||||
fwrite(tmp_hst_ptr, 1, width, p_dec_info->fp_out);
|
||||
tmp_hst_ptr += output_stride;
|
||||
}
|
||||
// dump chroma
|
||||
uint8_t *uv_hst_ptr = hst_ptr + output_image_size_luma;
|
||||
uint32_t chroma_height = static_cast<int>(GetChromaHeightFactor(p_dec_info->surf_format) * img_height);
|
||||
if (p_dec_info->mem_type == OUT_SURFACE_MEM_DEV_INTERNAL) {
|
||||
uv_hst_ptr += ((p_dec_info->disp_rect.top >> 1) * output_stride) + (p_dec_info->disp_rect.left * p_dec_info->bytes_per_pixel);
|
||||
}
|
||||
for (uint32_t i = 0; i < chroma_height; i++) {
|
||||
fwrite(uv_hst_ptr, 1, width, p_dec_info->fp_out);
|
||||
uv_hst_ptr += pitch[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hst_ptr != nullptr) {
|
||||
delete [] hst_ptr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Funtion to save internal frame buffer to file for host buffer
|
||||
*
|
||||
* @param p_dec_info
|
||||
* @param frame_mem
|
||||
* @param pitch
|
||||
*/
|
||||
void save_frame_to_file_host(DecoderInfo *p_dec_info, void *frame_mem[], uint32_t *pitch) {
|
||||
|
||||
if (p_dec_info->is_decoder_reconfigured) {
|
||||
if (p_dec_info->fp_out) {
|
||||
fclose(p_dec_info->fp_out);
|
||||
p_dec_info->fp_out = nullptr;
|
||||
}
|
||||
p_dec_info->is_decoder_reconfigured = false;
|
||||
}
|
||||
|
||||
if (p_dec_info->fp_out == nullptr && !p_dec_info->output_file_path.empty()) {
|
||||
p_dec_info->fp_out = fopen(p_dec_info->output_file_path.c_str(), "wb");
|
||||
}
|
||||
|
||||
if (p_dec_info->fp_out) {
|
||||
uint8_t *p_src_ptr_y = static_cast<uint8_t *>(frame_mem[0]) + (p_dec_info->disp_rect.top * pitch[0] + p_dec_info->disp_rect.left * p_dec_info->bytes_per_pixel);
|
||||
if (!p_src_ptr_y) {
|
||||
std::cerr << "save_frame_to_file_host: Invalid Memory address for src/dst" << std::endl;
|
||||
return;
|
||||
}
|
||||
int img_width = p_dec_info->disp_rect.right - p_dec_info->disp_rect.left;
|
||||
int img_height = p_dec_info->disp_rect.bottom - p_dec_info->disp_rect.top;
|
||||
int output_stride = pitch[0];
|
||||
|
||||
uint32_t width = img_width * p_dec_info->bytes_per_pixel;
|
||||
if (p_dec_info->bit_depth <= 16) {
|
||||
for (int i = 0; i < img_height; i++) {
|
||||
fwrite(p_src_ptr_y, 1, width, p_dec_info->fp_out);
|
||||
p_src_ptr_y += output_stride;
|
||||
}
|
||||
// dump chroma
|
||||
uint8_t *p_src_ptr_uv = static_cast<uint8_t *>(frame_mem[1]) + ((p_dec_info->disp_rect.top >> 1) * pitch[1] + (p_dec_info->disp_rect.left >> 1) * p_dec_info->bytes_per_pixel);
|
||||
int32_t chroma_height = static_cast<int>(GetChromaHeightFactor(p_dec_info->surf_format) * img_height);
|
||||
int32_t chroma_width = static_cast<int>(GetChromaWidthFactor(p_dec_info->surf_format) * img_width) * p_dec_info->bytes_per_pixel;
|
||||
for (int32_t i = 0; i < chroma_height; i++) {
|
||||
fwrite(p_src_ptr_uv, 1, chroma_width, p_dec_info->fp_out);
|
||||
p_src_ptr_uv += pitch[1];
|
||||
}
|
||||
if (frame_mem[2] != nullptr) {
|
||||
uint8_t *p_src_ptr_v = static_cast<uint8_t *>(frame_mem[2]) + p_dec_info->disp_rect.top * pitch[2] + (p_dec_info->disp_rect.left >> 1) * p_dec_info->bytes_per_pixel;
|
||||
for (int32_t i = 0; i < chroma_height; i++) {
|
||||
fwrite(p_src_ptr_v, 1, chroma_width, p_dec_info->fp_out);
|
||||
p_src_ptr_v += pitch[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::vector<uint8_t>> read_frames(std::vector<std::string>& names) {
|
||||
std::vector<std::vector<uint8_t>> frames;
|
||||
// sort the frames file so it is consecutive
|
||||
for (std::string name : names) {
|
||||
std::ifstream inputFile(name.c_str(), std::ios::binary);
|
||||
if (!inputFile) {
|
||||
std::cerr << "Error opening " << name << " for reading." << std::endl;
|
||||
std::abort();
|
||||
}
|
||||
std::cout << "Reading " << name << " for reading." << std::endl;
|
||||
// Determine the file size
|
||||
inputFile.seekg(0, std::ios::end);
|
||||
std::streamsize fileSize = inputFile.tellg();
|
||||
inputFile.seekg(0, std::ios::beg);
|
||||
|
||||
// Read the file contents into a byte array
|
||||
std::vector<uint8_t> frame(fileSize);
|
||||
if (!inputFile.read(reinterpret_cast<char*>(frame.data()), fileSize)) {
|
||||
std::cerr << "Error reading " << name << "." << std::endl;
|
||||
std::abort();
|
||||
}
|
||||
// Close the file
|
||||
inputFile.close();
|
||||
frames.push_back(std::move(frame));
|
||||
}
|
||||
|
||||
return frames;
|
||||
}
|
||||
|
||||
void init() {}
|
||||
|
||||
void create_decoder(DecoderInfo& dec_info) {
|
||||
RocDecoderCreateInfo create_info = {};
|
||||
create_info.codec_type = dec_info.rocdec_codec_id; // user specified codec_type for raw files
|
||||
create_info.max_width = DEFAULT_WIDTH;
|
||||
create_info.max_height = DEFAULT_HEIGHT;
|
||||
create_info.width = DEFAULT_WIDTH;
|
||||
create_info.height = DEFAULT_HEIGHT;
|
||||
create_info.num_decode_surfaces = 6;
|
||||
create_info.target_width = DEFAULT_WIDTH;
|
||||
create_info.target_height = DEFAULT_HEIGHT;
|
||||
create_info.display_rect.left = 0;
|
||||
create_info.display_rect.right = static_cast<short>(DEFAULT_WIDTH);
|
||||
create_info.display_rect.top = 0;
|
||||
create_info.display_rect.bottom = static_cast<short>(DEFAULT_HEIGHT);
|
||||
// for decode creation: assuming chroma_format is 4:2:0 and output_format is NV12.
|
||||
// video dimensions ( width, height, max_width, max_height), num_decode_surfaces, and bit_depth_minus_8 are hardcoded here
|
||||
// this will get changed in reconfigure when the sequence header is parsed from the stream to detect the actual video parameters
|
||||
create_info.chroma_format = rocDecVideoChromaFormat_420;
|
||||
create_info.output_format = rocDecVideoSurfaceFormat_NV12;
|
||||
create_info.bit_depth_minus_8 = 0;
|
||||
create_info.num_output_surfaces = 1;
|
||||
CHECK(rocDecCreateDecoder(&dec_info.decoder, &create_info));
|
||||
}
|
||||
|
||||
#if ENABLE_HOST_DECODE
|
||||
int ROCDECAPI handle_video_sequence_host(void* user_data, RocdecVideoFormatHost* format_host) {
|
||||
DecoderInfo *p_dec_info = static_cast<DecoderInfo *>(user_data);
|
||||
RocdecVideoFormat *format = &format_host->video_format;
|
||||
RocdecReconfigureDecoderInfo reconfig_params = {};
|
||||
reconfig_params.width = format->coded_width;
|
||||
reconfig_params.height = format->coded_height;
|
||||
reconfig_params.num_decode_surfaces = 6;
|
||||
reconfig_params.target_width = format->coded_width;
|
||||
reconfig_params.target_height = format->coded_height;
|
||||
reconfig_params.display_rect.left = 0;
|
||||
reconfig_params.display_rect.right = static_cast<short>(format->coded_width);
|
||||
reconfig_params.display_rect.top = 0;
|
||||
reconfig_params.display_rect.bottom = static_cast<short>(format->coded_height);
|
||||
p_dec_info->surf_format = format_host->video_surface_format;
|
||||
p_dec_info->disp_rect.top = format->display_area.top;
|
||||
p_dec_info->disp_rect.bottom = format->display_area.bottom;
|
||||
p_dec_info->disp_rect.left = format->display_area.left;
|
||||
p_dec_info->disp_rect.right = format->display_area.right;
|
||||
CHECK(rocDecReconfigureDecoderHost(p_dec_info->decoder, &reconfig_params));
|
||||
p_dec_info->is_decoder_reconfigured = true;
|
||||
int bitdepth_minus_8 = format->bit_depth_luma_minus8;
|
||||
p_dec_info->coded_width = format->coded_width;
|
||||
p_dec_info->coded_height = format->coded_height;
|
||||
p_dec_info->bytes_per_pixel = bitdepth_minus_8 > 0 ? 2 : 1;
|
||||
std::ostringstream input_video_info_str;
|
||||
input_video_info_str.str("");
|
||||
input_video_info_str.clear();
|
||||
input_video_info_str << "Input Video Information" << std::endl
|
||||
<< "\tCodec : " << format->codec << std::endl;
|
||||
if (format->frame_rate.numerator && format->frame_rate.denominator) {
|
||||
input_video_info_str << "\tFrame rate : " << format->frame_rate.numerator << "/" << format->frame_rate.denominator << " = " << 1.0 * format->frame_rate.numerator / format->frame_rate.denominator << " fps" << std::endl;
|
||||
}
|
||||
input_video_info_str << "\tSequence : " << (format->progressive_sequence ? "Progressive" : "Interlaced") << std::endl
|
||||
<< "\tCoded size : [" << format->coded_width << ", " << format->coded_height << "]" << std::endl
|
||||
<< "\tDisplay area : [" << format->display_area.left << ", " << format->display_area.top << ", "
|
||||
<< format->display_area.right << ", " << format->display_area.bottom << "]" << std::endl
|
||||
<< "\tBit depth : " << format->bit_depth_luma_minus8 + 8
|
||||
;
|
||||
input_video_info_str << std::endl;
|
||||
std::cout << input_video_info_str.str();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int ROCDECAPI handle_picture_display_host(void* user_data, RocdecParserDispInfo* disp_info) {
|
||||
DecoderInfo *p_dec_info = static_cast<DecoderInfo *>(user_data);
|
||||
RocdecParserDispInfo *p_disp_info = static_cast<RocdecParserDispInfo *>(disp_info);
|
||||
RocdecProcParams params = {};
|
||||
params.progressive_frame = p_disp_info->progressive_frame;
|
||||
params.top_field_first = p_disp_info->top_field_first;
|
||||
void* frame_mem_ptr[3] = {nullptr};
|
||||
uint32_t pitch[3] = {0};
|
||||
CHECK(rocDecGetVideoFrameHost(p_dec_info->decoder, p_disp_info->picture_index, frame_mem_ptr, pitch, ¶ms));
|
||||
p_dec_info->mem_type = OUT_SURFACE_MEM_HOST;
|
||||
if (p_dec_info->dump_decoded_frames) {
|
||||
save_frame_to_file_host(p_dec_info, frame_mem_ptr, pitch);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void create_decoder_host(DecoderInfo& dec_info) {
|
||||
// many of the decoder parameters are hardcoded below for just creating the decoder.
|
||||
// In the handlevideosequence callback, the decoder will get reconfigured to the actual parameters in the sequence header
|
||||
RocDecoderHostCreateInfo create_info = {};
|
||||
create_info.codec_type = dec_info.rocdec_codec_id;
|
||||
create_info.num_decode_threads = 0; // default
|
||||
create_info.max_width = DEFAULT_WIDTH;
|
||||
create_info.max_height = DEFAULT_HEIGHT;
|
||||
create_info.width = DEFAULT_WIDTH;
|
||||
create_info.height = DEFAULT_HEIGHT;
|
||||
create_info.target_width = DEFAULT_WIDTH;
|
||||
create_info.target_height = DEFAULT_HEIGHT;
|
||||
create_info.display_rect.left = 0;
|
||||
create_info.display_rect.right = static_cast<short>(DEFAULT_WIDTH);
|
||||
create_info.display_rect.top = 0;
|
||||
create_info.display_rect.bottom = static_cast<short>(DEFAULT_HEIGHT);
|
||||
create_info.chroma_format = rocDecVideoChromaFormat_420;
|
||||
create_info.output_format = rocDecVideoSurfaceFormat_P016;
|
||||
create_info.bit_depth_minus_8 = 2;
|
||||
create_info.num_output_surfaces = 1;
|
||||
create_info.user_data = &dec_info;
|
||||
create_info.pfn_sequence_callback = handle_video_sequence_host;
|
||||
create_info.pfn_display_picture = handle_picture_display_host;
|
||||
CHECK(rocDecCreateDecoderHost(&dec_info.decoder, &create_info));
|
||||
dec_info.backend = DECODER_BACKEND_HOST;
|
||||
}
|
||||
#endif
|
||||
|
||||
int ROCDECAPI handle_video_sequence(void* user_data, RocdecVideoFormat* format) {
|
||||
DecoderInfo *p_dec_info = static_cast<DecoderInfo *>(user_data);
|
||||
RocdecReconfigureDecoderInfo reconfig_params = {};
|
||||
int bitdepth_minus_8 = format->bit_depth_luma_minus8;
|
||||
uint32_t target_width = (format->display_area.right - format->display_area.left + 1) & ~1;
|
||||
uint32_t target_height = (format->display_area.bottom - format->display_area.top + 1) & ~1;
|
||||
reconfig_params.width = format->coded_width;
|
||||
reconfig_params.height = format->coded_height;
|
||||
reconfig_params.bit_depth_minus_8 = bitdepth_minus_8;
|
||||
reconfig_params.num_decode_surfaces = format->min_num_decode_surfaces;
|
||||
reconfig_params.target_width = target_width;
|
||||
reconfig_params.target_height = target_height;
|
||||
reconfig_params.display_rect.left = format->display_area.left;
|
||||
reconfig_params.display_rect.right = format->display_area.right;
|
||||
reconfig_params.display_rect.top = format->display_area.top;
|
||||
reconfig_params.display_rect.bottom = format->display_area.bottom;
|
||||
CHECK(rocDecReconfigureDecoder(p_dec_info->decoder, &reconfig_params));
|
||||
p_dec_info->is_decoder_reconfigured = true;
|
||||
p_dec_info->disp_rect.top = format->display_area.top;
|
||||
p_dec_info->disp_rect.bottom = format->display_area.bottom;
|
||||
p_dec_info->disp_rect.left = format->display_area.left;
|
||||
p_dec_info->disp_rect.right = format->display_area.right;
|
||||
rocDecVideoChromaFormat video_chroma_format = format->chroma_format;
|
||||
if (video_chroma_format == rocDecVideoChromaFormat_420 || rocDecVideoChromaFormat_Monochrome)
|
||||
p_dec_info->surf_format = bitdepth_minus_8 ? rocDecVideoSurfaceFormat_P016 : rocDecVideoSurfaceFormat_NV12;
|
||||
else if (video_chroma_format == rocDecVideoChromaFormat_444)
|
||||
p_dec_info->surf_format = bitdepth_minus_8 ? rocDecVideoSurfaceFormat_YUV444_16Bit : rocDecVideoSurfaceFormat_YUV444;
|
||||
else if (video_chroma_format == rocDecVideoChromaFormat_422)
|
||||
p_dec_info->surf_format = bitdepth_minus_8 ? rocDecVideoSurfaceFormat_YUV422_16Bit : rocDecVideoSurfaceFormat_YUV422;
|
||||
p_dec_info->coded_width = format->coded_width;
|
||||
p_dec_info->coded_height = format->coded_height;
|
||||
p_dec_info->bytes_per_pixel = bitdepth_minus_8 > 0 ? 2 : 1;
|
||||
std::ostringstream input_video_info_str;
|
||||
input_video_info_str.str("");
|
||||
input_video_info_str.clear();
|
||||
input_video_info_str << "Input Video Information" << std::endl
|
||||
<< "\tCodec : " << format->codec << std::endl;
|
||||
if (format->frame_rate.numerator && format->frame_rate.denominator) {
|
||||
input_video_info_str << "\tFrame rate : " << format->frame_rate.numerator << "/" << format->frame_rate.denominator << " = " << 1.0 * format->frame_rate.numerator / format->frame_rate.denominator << " fps" << std::endl;
|
||||
}
|
||||
input_video_info_str << "\tSequence : " << (format->progressive_sequence ? "Progressive" : "Interlaced") << std::endl
|
||||
<< "\tCoded size : [" << format->coded_width << ", " << format->coded_height << "]" << std::endl
|
||||
<< "\tDisplay area : [" << format->display_area.left << ", " << format->display_area.top << ", "
|
||||
<< format->display_area.right << ", " << format->display_area.bottom << "]" << std::endl
|
||||
<< "\tBit depth : " << format->bit_depth_luma_minus8 + 8
|
||||
;
|
||||
input_video_info_str << std::endl;
|
||||
std::cout << input_video_info_str.str();
|
||||
return 1;
|
||||
}
|
||||
|
||||
int ROCDECAPI handle_picture_decode(void* user_data, RocdecPicParams* params) {
|
||||
DecoderInfo *p_dec_info = static_cast<DecoderInfo *>(user_data);
|
||||
CHECK(rocDecDecodeFrame(p_dec_info->decoder, params));
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
int ROCDECAPI handle_picture_display(void* user_data, RocdecParserDispInfo* disp_info) {
|
||||
DecoderInfo *p_dec_info = static_cast<DecoderInfo *>(user_data);
|
||||
RocdecProcParams params = {};
|
||||
params.progressive_frame = disp_info->progressive_frame;
|
||||
params.top_field_first = disp_info->top_field_first;
|
||||
// get device memory pointer for decoded output surface
|
||||
void* dev_mem_ptr[3] = { 0 };
|
||||
uint32_t pitch[3] = { 0 };
|
||||
CHECK(rocDecGetVideoFrame(p_dec_info->decoder, disp_info->picture_index, dev_mem_ptr, pitch, ¶ms));
|
||||
|
||||
if (p_dec_info->dump_decoded_frames) {
|
||||
save_frame_to_file(p_dec_info, dev_mem_ptr, pitch);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
void create_parser(DecoderInfo& dec_info) {
|
||||
RocdecParserParams params = {};
|
||||
params.codec_type = dec_info.rocdec_codec_id;
|
||||
params.max_num_decode_surfaces = 6;
|
||||
params.max_display_delay = 1; // min display delay of 1 is recommented to get optimal performance from hardware decoder
|
||||
params.user_data = &dec_info;
|
||||
params.pfn_sequence_callback = handle_video_sequence;
|
||||
params.pfn_decode_picture = handle_picture_decode;
|
||||
params.pfn_display_picture = handle_picture_display;
|
||||
CHECK(rocDecCreateVideoParser(&dec_info.parser, ¶ms));
|
||||
}
|
||||
|
||||
void decode_frames(DecoderInfo& dec_info, const std::vector<std::vector<uint8_t>>& frames) {
|
||||
// gpu backend using VCN
|
||||
if (dec_info.backend == DECODER_BACKEND_DEVICE) {
|
||||
for (int i=0; i < static_cast<int>(frames.size()); ++i) {
|
||||
RocdecSourceDataPacket packet = {};
|
||||
packet.payload_size = frames[i].size();
|
||||
packet.payload = frames[i].data();
|
||||
if (i == static_cast<int>(frames.size() - 1)) {
|
||||
packet.flags = ROCDEC_PKT_ENDOFPICTURE; // mark end_of_picture flag for last frame
|
||||
}
|
||||
CHECK(rocDecParseVideoData(dec_info.parser, &packet));
|
||||
}
|
||||
}
|
||||
#if ENABLE_HOST_DECODE
|
||||
else if (dec_info.backend == DECODER_BACKEND_HOST) {
|
||||
for (int i=0; i < static_cast<int>(frames.size()); ++i) {
|
||||
RocdecPicParamsHost pic_params = {};
|
||||
pic_params.bitstream_data_len = frames[i].size();
|
||||
pic_params.bitstream_data = frames[i].data();
|
||||
if (i == static_cast<int>(frames.size() - 1)) {
|
||||
pic_params.flags = ROCDEC_PKT_ENDOFPICTURE; // mark end_of_picture flag for last frame
|
||||
}
|
||||
CHECK(rocDecDecodeFrameHost(dec_info.decoder, &pic_params));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void destroy_decoder(DecoderInfo& dec_info) {
|
||||
if (dec_info.backend == DECODER_BACKEND_DEVICE) {
|
||||
CHECK(rocDecDestroyDecoder(dec_info.decoder));
|
||||
}
|
||||
#if ENABLE_HOST_DECODE
|
||||
else if (dec_info.backend == DECODER_BACKEND_HOST) {
|
||||
CHECK(rocDecDestroyDecoderHost(dec_info.decoder));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void destroy_parser(DecoderInfo& dec_info) {
|
||||
if (dec_info.backend == DECODER_BACKEND_DEVICE)
|
||||
CHECK(rocDecDestroyVideoParser(dec_info.parser));
|
||||
}
|
||||
|
||||
void ShowHelpAndExit(const char *option = NULL) {
|
||||
std::cout << "Options:" << std::endl
|
||||
<< "-i Input File Path - required" << std::endl
|
||||
<< "-o Output File Path - dumps output if requested; optional" << std::endl
|
||||
<< "-d GPU device ID (0 for the first device, 1 for the second, etc.); optional; default: 0" << std::endl
|
||||
#if ENABLE_HOST_DECODE
|
||||
<< "-b backend (0 for GPU, 1 CPU-FFMpeg); optional; default: 0" << std::endl
|
||||
#else
|
||||
<< "-b backend (0 for GPU); optional; default: 0" << std::endl
|
||||
#endif
|
||||
<< "-c codec (0 : HEVC, 1 : H264, 2: AV1, 4: VP9, 5: VP8 ); optional; default: 0" << std::endl
|
||||
<< "-n Number of iteration - specify the number of iterations for performance evaluation; optional; default: 1" << std::endl
|
||||
<< "-m output_surface_memory_type - decoded surface memory; optional; default - 0"
|
||||
<< " [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;
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// helper function for sort
|
||||
std::string getLastPart(const std::string& str, char delimiter) {
|
||||
size_t pos = str.find_last_of(delimiter);
|
||||
if (pos == std::string::npos) {
|
||||
return str; // Delimiter not found, return the whole string
|
||||
}
|
||||
return str.substr(pos + 1);
|
||||
}
|
||||
|
||||
// helper function for sort
|
||||
int extractNumber(const std::string& filename) {
|
||||
std::string numStr;
|
||||
for (char c : filename) {
|
||||
if (std::isdigit(c)) {
|
||||
numStr += c;
|
||||
} else if (!numStr.empty()) {
|
||||
break; // Stop at first non-digit after a digit sequence
|
||||
}
|
||||
}
|
||||
return numStr.empty() ? 0 : std::stoi(numStr);
|
||||
}
|
||||
|
||||
// helper function for sort
|
||||
// Sort entries based on the numerical part of their filenames
|
||||
bool compareFilenames(const std::string& a, const std::string& b) {
|
||||
int num_a = extractNumber(a);
|
||||
int num_b = extractNumber(b);
|
||||
if (num_a != num_b) {
|
||||
return num_a < num_b;
|
||||
}
|
||||
return a < b; // Fallback to lexicographical comparison
|
||||
};
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
std::string input_file_path, output_file_path;
|
||||
int dump_output_frames = 0;
|
||||
int device_id = 0;
|
||||
DecoderBackend backend = DECODER_BACKEND_DEVICE;
|
||||
int num_iterations = 1;
|
||||
std::vector<std::string> input_file_names;
|
||||
int codec_type = 0; // default for HEVC
|
||||
DecoderInfo dec_info;
|
||||
|
||||
// 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_file_path = argv[i];
|
||||
bool b_sort_filenames = false;
|
||||
if (std::filesystem::is_directory(input_file_path)) {
|
||||
for (const auto& entry : std::filesystem::directory_iterator(input_file_path)) {
|
||||
if (entry.is_directory()) {
|
||||
std::vector<std::string> file_names_sub_folder;
|
||||
for (const auto& sub_entry : std::filesystem::directory_iterator(entry)) {
|
||||
file_names_sub_folder.push_back(sub_entry.path());
|
||||
}
|
||||
std::sort(file_names_sub_folder.begin(), file_names_sub_folder.end(), compareFilenames);
|
||||
input_file_names.insert(input_file_names.end(), file_names_sub_folder.begin(), file_names_sub_folder.end());
|
||||
file_names_sub_folder.clear();
|
||||
} else if(entry.is_regular_file()) {
|
||||
b_sort_filenames = true;
|
||||
input_file_names.push_back(entry.path());
|
||||
}
|
||||
else {
|
||||
std::cout << "unknown file type in input folder: " << entry.path().string() << '\n';
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (b_sort_filenames) {
|
||||
std::sort(input_file_names.begin(), input_file_names.end(), compareFilenames);
|
||||
}
|
||||
} else {
|
||||
input_file_names.push_back(input_file_path);
|
||||
}
|
||||
|
||||
std::cout << "Read " << input_file_names.size() << " frames from disk." << std::endl;
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-o")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-o");
|
||||
}
|
||||
output_file_path = argv[i];
|
||||
dec_info.output_file_path = output_file_path;
|
||||
dump_output_frames = true;
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-b")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-b");
|
||||
}
|
||||
backend = static_cast<DecoderBackend>(atoi(argv[i]));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!strcmp(argv[i], "-d")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-d");
|
||||
}
|
||||
device_id = atoi(argv[i]);
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-c")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-c");
|
||||
}
|
||||
codec_type = atoi(argv[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!strcmp(argv[i], "-n")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-n");
|
||||
}
|
||||
num_iterations = atoi(argv[i]);
|
||||
continue;
|
||||
}
|
||||
ShowHelpAndExit(argv[i]);
|
||||
}
|
||||
dec_info.rocdec_codec_id = CodecTypeToRocDecVideoCodec(codec_type);
|
||||
dec_info.dec_device_id = device_id;
|
||||
dec_info.mem_type = (!backend) ? OUT_SURFACE_MEM_DEV_INTERNAL : OUT_SURFACE_MEM_HOST;
|
||||
init();
|
||||
if (backend == DECODER_BACKEND_DEVICE) {
|
||||
create_parser(dec_info);
|
||||
create_decoder(dec_info);
|
||||
}
|
||||
#if ENABLE_HOST_DECODE
|
||||
else {
|
||||
create_decoder_host(dec_info);
|
||||
}
|
||||
#endif
|
||||
dec_info.dump_decoded_frames = dump_output_frames;
|
||||
auto input_frames = read_frames(input_file_names);
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
for (int i = 0; i < num_iterations; i++) {
|
||||
decode_frames(dec_info, input_frames);
|
||||
}
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
|
||||
std::cout << "Decoding time: " << elapsed << " microseconds" << std::endl;
|
||||
destroy_decoder(dec_info);
|
||||
destroy_parser(dec_info);
|
||||
std::cout << "Success." << std::endl << std::endl << std::endl;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
################################################################################
|
||||
# Copyright (c) 2023 - 2026 Advanced Micro Devices, Inc.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
# ROCM Path
|
||||
if(DEFINED ENV{ROCM_PATH})
|
||||
set(ROCM_PATH $ENV{ROCM_PATH} CACHE PATH "Default ROCm installation path")
|
||||
elseif(ROCM_PATH)
|
||||
message("-- INFO:ROCM_PATH Set -- ${ROCM_PATH}")
|
||||
else()
|
||||
set(ROCM_PATH /opt/rocm CACHE PATH "Default ROCm installation path")
|
||||
endif()
|
||||
# Set AMD Clang as default compiler
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED On)
|
||||
set(CMAKE_CXX_EXTENSIONS ON)
|
||||
if (NOT DEFINED CMAKE_CXX_COMPILER)
|
||||
set(CMAKE_C_COMPILER ${ROCM_PATH}/lib/llvm/bin/amdclang)
|
||||
set(CMAKE_CXX_COMPILER ${ROCM_PATH}/lib/llvm/bin/amdclang++)
|
||||
endif()
|
||||
|
||||
project(videodecode)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/../../cmake)
|
||||
list(APPEND CMAKE_PREFIX_PATH ${ROCM_PATH}/lib/cmake)
|
||||
|
||||
# rocdecode sample build type
|
||||
set(DEFAULT_BUILD_TYPE "Release")
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE "${DEFAULT_BUILD_TYPE}" CACHE STRING "rocdecode Default Build Type" FORCE)
|
||||
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release")
|
||||
endif()
|
||||
if(CMAKE_BUILD_TYPE MATCHES Debug)
|
||||
# -O0 -- Don't Optimize output file
|
||||
# -gdwarf-4 -- generate debugging information, dwarf-4 for making valgrind work
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -gdwarf-4")
|
||||
else()
|
||||
# -O3 -- Optimize output file
|
||||
# -DNDEBUG -- turn off asserts
|
||||
# -fPIC -- Generate position-independent code if possible
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -DNDEBUG -fPIC")
|
||||
endif()
|
||||
|
||||
set (HIP_PLATFORM amd CACHE STRING "HIP platform")
|
||||
find_package(HIP QUIET)
|
||||
find_package(rocdecode QUIET)
|
||||
find_package(rocdecode-host 1.0.0 QUIET)
|
||||
find_package(rocprofiler-register QUIET)
|
||||
find_package(FFmpeg QUIET)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
if(HIP_FOUND AND FFMPEG_FOUND AND rocdecode_FOUND AND Threads_FOUND AND rocprofiler-register_FOUND)
|
||||
# HIP
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} hip::host)
|
||||
# FFMPEG
|
||||
include_directories(${AVUTIL_INCLUDE_DIR} ${AVCODEC_INCLUDE_DIR}
|
||||
${AVFORMAT_INCLUDE_DIR})
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} ${FFMPEG_LIBRARIES})
|
||||
# rocdecode and utils
|
||||
include_directories (${rocdecode_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/.. ${CMAKE_CURRENT_SOURCE_DIR}/../../utils ${CMAKE_CURRENT_SOURCE_DIR}/../../utils/rocvideodecode)
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} rocdecode::rocdecode)
|
||||
# threads
|
||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} Threads::Threads)
|
||||
# rocprofiler-register
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} rocprofiler-register::rocprofiler-register)
|
||||
# rocdecode
|
||||
list(APPEND SOURCES ${PROJECT_SOURCE_DIR} videodecode.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../utils/rocvideodecode/roc_video_dec.cpp)
|
||||
# Add rocdecode host utils if host library found
|
||||
if(rocdecode-host_FOUND)
|
||||
include_directories (${CMAKE_CURRENT_SOURCE_DIR}/../../utils/ffmpegvideodecode)
|
||||
list(APPEND SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/../../utils/ffmpegvideodecode/ffmpeg_video_dec.cpp)
|
||||
endif()
|
||||
# sample app exe
|
||||
add_executable(${PROJECT_NAME} ${SOURCES})
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} rocdecode::rocdecode)
|
||||
if(rocdecode-host_FOUND)
|
||||
include_directories(${rocdecode-host_INCLUDE_DIR})
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} rocdecode::rocdecode-host)
|
||||
target_compile_definitions(${PROJECT_NAME} PUBLIC ENABLE_HOST_DECODE=1)
|
||||
else()
|
||||
target_compile_definitions(${PROJECT_NAME} PUBLIC ENABLE_HOST_DECODE=0)
|
||||
endif()
|
||||
# FFMPEG multi-version support
|
||||
if(_FFMPEG_AVCODEC_VERSION VERSION_LESS_EQUAL 58.134.100)
|
||||
target_compile_definitions(${PROJECT_NAME} PUBLIC USE_AVCODEC_GREATER_THAN_58_134=0)
|
||||
else()
|
||||
target_compile_definitions(${PROJECT_NAME} PUBLIC USE_AVCODEC_GREATER_THAN_58_134=1)
|
||||
endif()
|
||||
target_link_libraries(${PROJECT_NAME} ${LINK_LIBRARY_LIST})
|
||||
else()
|
||||
message("-- ERROR!: ${PROJECT_NAME} excluded! please install all the dependencies and try again!")
|
||||
if (NOT HIP_FOUND)
|
||||
message(FATAL_ERROR "-- ERROR!: HIP Not Found! - please install ROCm and HIP!")
|
||||
endif()
|
||||
if (NOT rocdecode_FOUND)
|
||||
message(FATAL_ERROR "-- ERROR!: rocdecode Not Found! - please install rocdecode!")
|
||||
endif()
|
||||
if (NOT Threads_FOUND)
|
||||
message(FATAL_ERROR "-- ERROR!: Threads Not Found! - please install Threads!")
|
||||
endif()
|
||||
if (NOT rocprofiler-register_FOUND)
|
||||
message(FATAL_ERROR "-- ERROR!: rocprofiler-register Not Found! - please install rocprofiler-register!")
|
||||
endif()
|
||||
endif()
|
||||
@@ -0,0 +1,42 @@
|
||||
# Video decode sample
|
||||
|
||||
The video decode sample illustrates decoding a single packetized video stream using FFMPEG demuxer, video parser, and rocDecoder to get the individual decoded frames in YUV format. This sample can be configured with a device ID and optionally able to dump the output to a file. This sample uses the high-level RocVideoDecoder class which connects both the video parser and Rocdecoder. This process repeats in a loop until all frames have been decoded.
|
||||
|
||||
## Prerequisites:
|
||||
|
||||
* Install [rocDecode](../../README.md#build-and-install-instructions)
|
||||
|
||||
* [FFMPEG](https://ffmpeg.org/about.html)
|
||||
|
||||
* On `Ubuntu`
|
||||
|
||||
```shell
|
||||
sudo apt install libavcodec-dev libavformat-dev libavutil-dev
|
||||
```
|
||||
|
||||
* On `RHEL`/`SLES` - install ffmpeg development packages manually or use [rocDecode-setup.py](../../rocDecode-setup.py) script
|
||||
|
||||
## Build
|
||||
|
||||
```shell
|
||||
mkdir video_decode_sample && cd video_decode_sample
|
||||
cmake ../
|
||||
make -j
|
||||
```
|
||||
|
||||
## Run
|
||||
|
||||
```shell
|
||||
./videodecode -i <input video file [required]>
|
||||
-o <output path to save decoded YUV frames [optional]>
|
||||
-d <GPU device ID - 0:device 0 / 1:device 1/ ... [optional - default:0]>
|
||||
-f <Number of decoded frames - specify the number of pictures to be decoded [optional]>
|
||||
-z <force_zero_latency - Decoded frames will be flushed out for display immediately [optional]>
|
||||
-disp_delay <display delay - specify the number of frames to be delayed for display [optional - default: 1]>
|
||||
-sei <extract SEI messages [optional]>
|
||||
-md5 <generate MD5 message digest on the decoded YUV image sequence [optional]>
|
||||
-md5_check MD5_File_Path <generate MD5 message digest on the decoded YUV image sequence and compare to the reference MD5 string in a file [optional]>
|
||||
-crop <crop rectangle for output (not used when using interopped decoded frame) [optional - default: 0,0,0,0]>
|
||||
-m <output_surface_memory_type - decoded surface memory [optional - default: 0][0 : OUT_SURFACE_MEM_DEV_INTERNAL/ 1 : OUT_SURFACE_MEM_DEV_COPIED/ 2 : OUT_SURFACE_MEM_HOST_COPIED/3 : OUT_SURFACE_MEM_NOT_MAPPED]>
|
||||
-no_ffmpeg_demux <use the built-in bitstream reader instead of FFMPEG demuxer to obtain picture data [optional]>
|
||||
```
|
||||
@@ -0,0 +1,449 @@
|
||||
/*
|
||||
Copyright (c) 2023 - 2026 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 <iostream>
|
||||
#include <fstream>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <iomanip>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
#include <sys/stat.h>
|
||||
#include <libgen.h>
|
||||
#if __cplusplus >= 201703L && __has_include(<filesystem>)
|
||||
#include <filesystem>
|
||||
#else
|
||||
#include <experimental/filesystem>
|
||||
#endif
|
||||
#include "video_demuxer.h"
|
||||
#include "rocdecode/roc_bitstream_reader.h"
|
||||
#include "roc_video_dec.h"
|
||||
#include "common.h"
|
||||
#if ENABLE_HOST_DECODE
|
||||
#include "ffmpeg_video_dec.h"
|
||||
#endif
|
||||
|
||||
//hardcoding for host based decoder creation if demux is not available
|
||||
#define DEFAULT_WIDTH 2912
|
||||
#define DEFAULT_HEIGHT 1888
|
||||
|
||||
|
||||
void ShowHelpAndExit(const char *option = NULL) {
|
||||
std::cout << "Options:" << std::endl
|
||||
<< "-i Input File Path - required" << std::endl
|
||||
<< "-o Output File Path - dumps output if requested; optional" << std::endl
|
||||
<< "-d GPU device ID (0 for the first device, 1 for the second, etc.); optional; default: 0" << std::endl
|
||||
<< "-backend backend (0 for GPU, 1 CPU-FFMpeg); optional; default: 0" << std::endl
|
||||
<< "-f Number of decoded frames - specify the number of pictures to be decoded; optional" << std::endl
|
||||
<< "-z force_zero_latency (force_zero_latency, Decoded frames will be flushed out for display immediately); optional;" << std::endl
|
||||
<< "-disp_delay -specify the number of frames to be delayed for display; optional; default: 1" << std::endl
|
||||
<< "-sei extract SEI messages; optional;" << std::endl
|
||||
<< "-md5 generate MD5 message digest on the decoded YUV image sequence; optional;" << std::endl
|
||||
<< "-md5_check MD5 File Path - generate MD5 message digest on the decoded YUV image sequence and compare to the reference MD5 string in a file; optional;" << std::endl
|
||||
<< "-crop crop rectangle for output (not used when using interopped decoded frame); optional; default: 0" << std::endl
|
||||
<< "-m output_surface_memory_type - decoded surface memory; optional; default - 0"
|
||||
<< " [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
|
||||
<< "-seek_criteria - Demux seek criteria & value - optional; default - 0,0; "
|
||||
<< "[0: no seek; 1: SEEK_CRITERIA_FRAME_NUM, frame number; 2: SEEK_CRITERIA_TIME_STAMP, frame number (time calculated internally)]" << std::endl
|
||||
<< "-seek_mode - Seek to previous key frame or exact - optional; default - 0"
|
||||
<< "[0: SEEK_MODE_PREV_KEY_FRAME; 1: SEEK_MODE_EXACT_FRAME]" << std::endl
|
||||
<< "-no_ffmpeg_demux - use the built-in bitstream reader instead of FFMPEG demuxer to obtain picture data; optional." << std::endl;
|
||||
exit(0);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
|
||||
std::string input_file_path, output_file_path, md5_file_path;
|
||||
std::fstream ref_md5_file;
|
||||
int dump_output_frames = 0;
|
||||
int device_id = 0;
|
||||
int disp_delay = 1;
|
||||
int backend = 0;
|
||||
bool b_force_zero_latency = false; // false by default: enabling this option might affect decoding performance
|
||||
bool b_extract_sei_messages = false;
|
||||
bool b_generate_md5 = false;
|
||||
bool b_md5_check = false;
|
||||
bool b_flush_frames_during_reconfig = true;
|
||||
Rect crop_rect = {};
|
||||
Rect *p_crop_rect = nullptr;
|
||||
OutputSurfaceMemoryType mem_type = OUT_SURFACE_MEM_DEV_INTERNAL; // set to internal
|
||||
ReconfigParams reconfig_params = { 0 };
|
||||
ReconfigDumpFileStruct reconfig_user_struct = { 0 };
|
||||
uint32_t num_decoded_frames = 0; // default value is 0, meaning decode the entire stream
|
||||
// seek options
|
||||
uint64_t seek_to_frame = 0;
|
||||
int seek_criteria = 0, seek_mode = 0;
|
||||
bool b_use_ffmpeg_demuxer = true; // true by default to use FFMPEG demuxer. set to false to use the built-in bitstream reader.
|
||||
|
||||
// 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_file_path = argv[i];
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-o")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-o");
|
||||
}
|
||||
output_file_path = argv[i];
|
||||
dump_output_frames = 1;
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-backend")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-backend");
|
||||
}
|
||||
backend = atoi(argv[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!strcmp(argv[i], "-d")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-d");
|
||||
}
|
||||
device_id = atoi(argv[i]);
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-disp_delay")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-disp_delay");
|
||||
}
|
||||
disp_delay = atoi(argv[i]);
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-f")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-d");
|
||||
}
|
||||
num_decoded_frames = atoi(argv[i]);
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-z")) {
|
||||
if (i == argc) {
|
||||
ShowHelpAndExit("-z");
|
||||
}
|
||||
b_force_zero_latency = true;
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-sei")) {
|
||||
if (i == argc) {
|
||||
ShowHelpAndExit("-sei");
|
||||
}
|
||||
b_extract_sei_messages = true;
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-md5")) {
|
||||
if (i == argc) {
|
||||
ShowHelpAndExit("-md5");
|
||||
}
|
||||
b_generate_md5 = true;
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-md5_check")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-md5_check");
|
||||
}
|
||||
b_generate_md5 = true;
|
||||
b_md5_check = true;
|
||||
md5_file_path = argv[i];
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-crop")) {
|
||||
if (++i == argc || 4 != sscanf(argv[i], "%d,%d,%d,%d", &crop_rect.left, &crop_rect.top, &crop_rect.right, &crop_rect.bottom)) {
|
||||
ShowHelpAndExit("-crop");
|
||||
}
|
||||
if ((crop_rect.right - crop_rect.left) % 2 == 1 || (crop_rect.bottom - crop_rect.top) % 2 == 1) {
|
||||
std::cout << "output crop rectangle must have width and height of even numbers" << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
p_crop_rect = &crop_rect;
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-m")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-m");
|
||||
}
|
||||
mem_type = static_cast<OutputSurfaceMemoryType>(atoi(argv[i]));
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "flush")) {
|
||||
b_flush_frames_during_reconfig = atoi(argv[i]) ? true : false;
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-seek_criteria")) {
|
||||
if (++i == argc || 2 != sscanf(argv[i], "%d,%lu", &seek_criteria, &seek_to_frame)) {
|
||||
ShowHelpAndExit("-seek_criteria");
|
||||
}
|
||||
if (0 > seek_criteria || seek_criteria >= 3)
|
||||
ShowHelpAndExit("-seek_criteria");
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-seek_mode")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-seek_mode");
|
||||
}
|
||||
seek_mode = atoi(argv[i]);
|
||||
if (seek_mode != 0 && seek_mode != 1)
|
||||
ShowHelpAndExit("-seek_mode");
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-no_ffmpeg_demux")) {
|
||||
if (i == argc) {
|
||||
ShowHelpAndExit("-no_ffmpeg_demux");
|
||||
}
|
||||
b_use_ffmpeg_demuxer = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
ShowHelpAndExit(argv[i]);
|
||||
}
|
||||
|
||||
try {
|
||||
std::size_t found_file = input_file_path.find_last_of('/');
|
||||
std::cout << "info: Input file: " << input_file_path.substr(found_file + 1) << std::endl;
|
||||
VideoDemuxer *demuxer;
|
||||
RocdecBitstreamReader bs_reader = nullptr;
|
||||
rocDecVideoCodec rocdec_codec_id;
|
||||
int bit_depth;
|
||||
|
||||
if (b_use_ffmpeg_demuxer) {
|
||||
std::cout << "info: Using FFMPEG demuxer" << std::endl;
|
||||
demuxer = new VideoDemuxer(input_file_path.c_str());
|
||||
rocdec_codec_id = AVCodec2RocDecVideoCodec(demuxer->GetCodecID());
|
||||
bit_depth = demuxer->GetBitDepth();
|
||||
} else {
|
||||
std::cout << "info: Using built-in bitstream reader" << std::endl;
|
||||
if (rocDecCreateBitstreamReader(&bs_reader, input_file_path.c_str()) != ROCDEC_SUCCESS) {
|
||||
std::cerr << "Failed to create the bitstream reader." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
if (rocDecGetBitstreamCodecType(bs_reader, &rocdec_codec_id) != ROCDEC_SUCCESS) {
|
||||
std::cerr << "Failed to get stream codec type." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
if (rocdec_codec_id >= rocDecVideoCodec_NumCodecs) {
|
||||
std::cerr << "Unsupported stream file type or codec type by the bitstream reader. Exiting." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
if (rocDecGetBitstreamBitDepth(bs_reader, &bit_depth) != ROCDEC_SUCCESS) {
|
||||
std::cerr << "Failed to get stream bit depth." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
RocVideoDecoder *viddec;
|
||||
VideoSeekContext video_seek_ctx;
|
||||
if (!backend) // gpu backend
|
||||
viddec = new RocVideoDecoder(device_id, mem_type, rocdec_codec_id, b_force_zero_latency, p_crop_rect, b_extract_sei_messages, disp_delay);
|
||||
else {
|
||||
#if ENABLE_HOST_DECODE
|
||||
std::cout << "info: RocDecode is using CPU backend!" << std::endl;
|
||||
uint32_t max_width = b_use_ffmpeg_demuxer ? demuxer->GetWidth() : DEFAULT_WIDTH;
|
||||
uint32_t max_height = b_use_ffmpeg_demuxer ? demuxer->GetHeight() : DEFAULT_HEIGHT;
|
||||
if (mem_type == OUT_SURFACE_MEM_DEV_INTERNAL) mem_type = OUT_SURFACE_MEM_DEV_COPIED; // mem_type internal is not supported in this mode
|
||||
viddec = new FFMpegVideoDecoder(device_id, mem_type, rocdec_codec_id, b_force_zero_latency, p_crop_rect, b_extract_sei_messages, disp_delay, max_width, max_height);
|
||||
#else
|
||||
std::cout << "Error: RocDecode HOST library is not found and backend is not supported!" << std::endl;
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
if(!viddec->CodecSupported(device_id, rocdec_codec_id, bit_depth)) {
|
||||
std::cerr << "rocDecode doesn't support codec!" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
std::string device_name, gcn_arch_name;
|
||||
int pci_bus_id, pci_domain_id, pci_device_id;
|
||||
|
||||
viddec->GetDeviceinfo(device_name, gcn_arch_name, pci_bus_id, pci_domain_id, pci_device_id);
|
||||
std::cout << "info: Using GPU device " << 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::cout << "info: decoding started, please wait!" << std::endl;
|
||||
|
||||
int n_video_bytes = 0, n_frame_returned = 0, n_frame = 0;
|
||||
int n_pic_decoded = 0, decoded_pics = 0;
|
||||
uint8_t *pvideo = nullptr;
|
||||
int pkg_flags = 0;
|
||||
uint8_t *pframe = nullptr;
|
||||
int64_t pts = 0;
|
||||
OutputSurfaceInfo *surf_info;
|
||||
uint32_t width, height;
|
||||
double total_dec_time = 0;
|
||||
bool first_frame = true;
|
||||
MD5Generator *md5_generator = nullptr;
|
||||
|
||||
// initialize reconfigure params: the following is configured to dump to output which is relevant for this sample
|
||||
reconfig_params.p_fn_reconfigure_flush = ReconfigureFlushCallback;
|
||||
reconfig_user_struct.b_dump_frames_to_file = dump_output_frames;
|
||||
reconfig_user_struct.output_file_name = output_file_path;
|
||||
reconfig_params.reconfig_flush_mode = RECONFIG_FLUSH_MODE_NONE;
|
||||
if (dump_output_frames) {
|
||||
reconfig_params.reconfig_flush_mode |= RECONFIG_FLUSH_MODE_DUMP_TO_FILE;
|
||||
}
|
||||
if (b_generate_md5) {
|
||||
reconfig_params.reconfig_flush_mode |= RECONFIG_FLUSH_MODE_CALCULATE_MD5;
|
||||
}
|
||||
reconfig_params.p_reconfig_user_struct = &reconfig_user_struct;
|
||||
|
||||
if (b_generate_md5) {
|
||||
md5_generator = new MD5Generator();
|
||||
md5_generator->InitMd5();
|
||||
reconfig_user_struct.md5_generator_handle = static_cast<void*>(md5_generator);
|
||||
}
|
||||
viddec->SetReconfigParams(&reconfig_params);
|
||||
|
||||
do {
|
||||
auto start_time = std::chrono::high_resolution_clock::now();
|
||||
if (b_use_ffmpeg_demuxer) {
|
||||
if (seek_criteria == 1 && first_frame) {
|
||||
// use VideoSeekContext class to seek to given frame number
|
||||
video_seek_ctx.seek_frame_ = seek_to_frame;
|
||||
video_seek_ctx.seek_crit_ = SEEK_CRITERIA_FRAME_NUM;
|
||||
video_seek_ctx.seek_mode_ = (seek_mode ? SEEK_MODE_EXACT_FRAME : SEEK_MODE_PREV_KEY_FRAME);
|
||||
demuxer->Seek(video_seek_ctx, &pvideo, &n_video_bytes);
|
||||
pts = video_seek_ctx.out_frame_pts_;
|
||||
std::cout << "info: Number of frames that were decoded during seek - " << video_seek_ctx.num_frames_decoded_ << std::endl;
|
||||
first_frame = false;
|
||||
} else if (seek_criteria == 2 && first_frame) {
|
||||
// use VideoSeekContext class to seek to given timestamp
|
||||
video_seek_ctx.seek_frame_ = seek_to_frame;
|
||||
video_seek_ctx.seek_crit_ = SEEK_CRITERIA_TIME_STAMP;
|
||||
video_seek_ctx.seek_mode_ = (seek_mode ? SEEK_MODE_EXACT_FRAME : SEEK_MODE_PREV_KEY_FRAME);
|
||||
demuxer->Seek(video_seek_ctx, &pvideo, &n_video_bytes);
|
||||
pts = video_seek_ctx.out_frame_pts_;
|
||||
std::cout << "info: Duration of frame found after seek - " << video_seek_ctx.out_frame_duration_ << " ms" << std::endl;
|
||||
first_frame = false;
|
||||
} else {
|
||||
demuxer->Demux(&pvideo, &n_video_bytes, &pts);
|
||||
}
|
||||
} else {
|
||||
if (rocDecGetBitstreamPicData(bs_reader, &pvideo, &n_video_bytes, &pts) != ROCDEC_SUCCESS) {
|
||||
std::cerr << "Failed to get picture data." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
// Treat 0 bitstream size as end of stream indicator
|
||||
if (n_video_bytes == 0) {
|
||||
pkg_flags |= ROCDEC_PKT_ENDOFSTREAM;
|
||||
}
|
||||
n_frame_returned = viddec->DecodeFrame(pvideo, n_video_bytes, pkg_flags, pts, &decoded_pics);
|
||||
|
||||
// get output surface info after the first decoded frame
|
||||
if (!n_frame && !viddec->GetOutputSurfaceInfo(&surf_info)) {
|
||||
std::cerr << "Error: Failed to get Output Surface Info!" << std::endl;
|
||||
break;
|
||||
}
|
||||
for (int i = 0; i < n_frame_returned; i++) {
|
||||
pframe = viddec->GetFrame(&pts);
|
||||
if (b_generate_md5 && pframe) {
|
||||
md5_generator->UpdateMd5ForFrame(pframe, surf_info);
|
||||
}
|
||||
if (dump_output_frames && mem_type != OUT_SURFACE_MEM_NOT_MAPPED) {
|
||||
viddec->SaveFrameToFile(output_file_path, pframe, surf_info);
|
||||
}
|
||||
// release frame
|
||||
viddec->ReleaseFrame(pts);
|
||||
}
|
||||
auto end_time = std::chrono::high_resolution_clock::now();
|
||||
auto time_per_decode = std::chrono::duration<double, std::milli>(end_time - start_time).count();
|
||||
total_dec_time += time_per_decode;
|
||||
n_frame += n_frame_returned;
|
||||
n_pic_decoded += decoded_pics;
|
||||
if (num_decoded_frames && num_decoded_frames <= n_frame) {
|
||||
break;
|
||||
}
|
||||
} while (n_video_bytes);
|
||||
|
||||
n_frame += viddec->GetNumOfFlushedFrames();
|
||||
std::cout << "info: Total pictures decoded: " << n_pic_decoded << std::endl;
|
||||
std::cout << "info: Total frames output/displayed: " << n_frame << std::endl;
|
||||
if (!dump_output_frames) {
|
||||
std::cout << "info: avg decoding time per picture: " << total_dec_time / n_pic_decoded << " ms" <<std::endl;
|
||||
std::cout << "info: avg decode FPS: " << (n_pic_decoded / total_dec_time) * 1000 << std::endl;
|
||||
std::cout << "info: avg output/display time per frame: " << total_dec_time / n_frame << " ms" <<std::endl;
|
||||
std::cout << "info: avg output/display FPS: " << (n_frame / total_dec_time) * 1000 << 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 {
|
||||
std::cout << "info: saved frames into " << output_file_path << std::endl;
|
||||
}
|
||||
}
|
||||
if (b_generate_md5) {
|
||||
uint8_t *digest;
|
||||
md5_generator->FinalizeMd5(&digest);
|
||||
std::cout << "MD5 message digest: ";
|
||||
for (int i = 0; i < 16; i++) {
|
||||
std::cout << std::setfill('0') << std::setw(2) << std::hex << static_cast<int>(digest[i]);
|
||||
}
|
||||
std::cout << std::endl;
|
||||
if (b_md5_check) {
|
||||
std::string ref_md5_string(33, 0);
|
||||
uint8_t ref_md5[16];
|
||||
ref_md5_file.open(md5_file_path.c_str(), std::ios::in);
|
||||
if ((ref_md5_file.rdstate() & std::ifstream::failbit) != 0) {
|
||||
std::cerr << "Failed to open MD5 file." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
ref_md5_file.getline(ref_md5_string.data(), ref_md5_string.length());
|
||||
if ((ref_md5_file.rdstate() & std::ifstream::badbit) != 0) {
|
||||
std::cerr << "Failed to read MD5 digest string." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
for (int i = 0; i < 16; i++) {
|
||||
std::string part = ref_md5_string.substr(i * 2, 2);
|
||||
ref_md5[i] = std::stoi(part, nullptr, 16);
|
||||
}
|
||||
if (memcmp(digest, ref_md5, 16) == 0) {
|
||||
std::cout << "MD5 digest matches the reference MD5 digest: ";
|
||||
} else {
|
||||
std::cout << "MD5 digest does not match the reference MD5 digest: ";
|
||||
}
|
||||
std::cout << ref_md5_string.c_str() << std::endl;
|
||||
ref_md5_file.close();
|
||||
}
|
||||
delete md5_generator;
|
||||
}
|
||||
if (b_use_ffmpeg_demuxer && demuxer) {
|
||||
delete demuxer;
|
||||
} else if (bs_reader) {
|
||||
rocDecDestroyBitstreamReader(bs_reader);
|
||||
}
|
||||
} catch (const std::exception &ex) {
|
||||
std::cout << ex.what() << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
################################################################################
|
||||
# Copyright (c) 2023 - 2026 Advanced Micro Devices, Inc.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
# ROCM Path
|
||||
if(DEFINED ENV{ROCM_PATH})
|
||||
set(ROCM_PATH $ENV{ROCM_PATH} CACHE PATH "Default ROCm installation path")
|
||||
elseif(ROCM_PATH)
|
||||
message("-- INFO:ROCM_PATH Set -- ${ROCM_PATH}")
|
||||
else()
|
||||
set(ROCM_PATH /opt/rocm CACHE PATH "Default ROCm installation path")
|
||||
endif()
|
||||
# Set AMD Clang as default compiler
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED On)
|
||||
set(CMAKE_CXX_EXTENSIONS ON)
|
||||
if (NOT DEFINED CMAKE_CXX_COMPILER)
|
||||
set(CMAKE_C_COMPILER ${ROCM_PATH}/lib/llvm/bin/amdclang)
|
||||
set(CMAKE_CXX_COMPILER ${ROCM_PATH}/lib/llvm/bin/amdclang++)
|
||||
endif()
|
||||
|
||||
project(videodecodebatch)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/../../cmake)
|
||||
list(APPEND CMAKE_PREFIX_PATH ${ROCM_PATH}/lib/cmake)
|
||||
|
||||
# rocdecode sample build type
|
||||
set(DEFAULT_BUILD_TYPE "Release")
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE "${DEFAULT_BUILD_TYPE}" CACHE STRING "rocdecode Default Build Type" FORCE)
|
||||
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release")
|
||||
endif()
|
||||
if(CMAKE_BUILD_TYPE MATCHES Debug)
|
||||
# -O0 -- Don't Optimize output file
|
||||
# -gdwarf-4 -- generate debugging information, dwarf-4 for making valgrind work
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -gdwarf-4")
|
||||
else()
|
||||
# -O3 -- Optimize output file
|
||||
# -DNDEBUG -- turn off asserts
|
||||
# -fPIC -- Generate position-independent code if possible
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -DNDEBUG -fPIC")
|
||||
endif()
|
||||
|
||||
set (HIP_PLATFORM amd CACHE STRING "HIP platform")
|
||||
find_package(HIP QUIET)
|
||||
find_package(rocdecode QUIET)
|
||||
find_package(rocprofiler-register QUIET)
|
||||
find_package(FFmpeg QUIET)
|
||||
|
||||
if(HIP_FOUND AND FFMPEG_FOUND AND rocdecode_FOUND AND rocprofiler-register_FOUND)
|
||||
# HIP
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} hip::host)
|
||||
# FFMPEG
|
||||
include_directories(${AVUTIL_INCLUDE_DIR} ${AVCODEC_INCLUDE_DIR}
|
||||
${SWSCALE_INCLUDE_DIR} ${AVFORMAT_INCLUDE_DIR})
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} ${FFMPEG_LIBRARIES})
|
||||
# STD Filesystem
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} stdc++fs)
|
||||
# rocdecode and utils
|
||||
include_directories (${rocdecode_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/.. ${CMAKE_CURRENT_SOURCE_DIR}/../../utils ${CMAKE_CURRENT_SOURCE_DIR}/../../utils/rocvideodecode)
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} rocdecode::rocdecode)
|
||||
#threads
|
||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
find_package(Threads REQUIRED)
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} Threads::Threads)
|
||||
# rocprofiler-register
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} rocprofiler-register::rocprofiler-register)
|
||||
|
||||
list(APPEND SOURCES ${PROJECT_SOURCE_DIR} videodecodebatch.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../utils/rocvideodecode/roc_video_dec.cpp)
|
||||
add_executable(${PROJECT_NAME} ${SOURCES})
|
||||
target_link_libraries(${PROJECT_NAME} ${LINK_LIBRARY_LIST})
|
||||
# FFMPEG multi-version support
|
||||
if(_FFMPEG_AVCODEC_VERSION VERSION_LESS_EQUAL 58.134.100)
|
||||
target_compile_definitions(${PROJECT_NAME} PUBLIC USE_AVCODEC_GREATER_THAN_58_134=0)
|
||||
else()
|
||||
target_compile_definitions(${PROJECT_NAME} PUBLIC USE_AVCODEC_GREATER_THAN_58_134=1)
|
||||
endif()
|
||||
else()
|
||||
message("-- ERROR!: ${PROJECT_NAME} excluded! please install all the dependencies and try again!")
|
||||
if (NOT HIP_FOUND)
|
||||
message(FATAL_ERROR "-- ERROR!: HIP Not Found! - please install ROCm and HIP!")
|
||||
endif()
|
||||
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()
|
||||
if (NOT Threads_FOUND)
|
||||
message(FATAL_ERROR "-- ERROR!: Threads Not Found! - please install Threads!")
|
||||
endif()
|
||||
if (NOT rocprofiler-register_FOUND)
|
||||
message(FATAL_ERROR "-- ERROR!: rocprofiler-register Not Found! - please install rocprofiler-register!")
|
||||
endif()
|
||||
endif()
|
||||
@@ -0,0 +1,38 @@
|
||||
# Video decode batch sample
|
||||
|
||||
This sample decodes multiple files using multiple threads, using the rocDecode library. The input is a directory of files and an input number of threads. The maximum number of threads is capped to 64.
|
||||
If the number of files is higher than the number of threads requested by the user, the files are distributed to the threads in a round robin fashion.
|
||||
If the number of files is lesser than the number of threads requested by the user, the number of threads created will be equal to the number of files.
|
||||
|
||||
## Prerequisites:
|
||||
|
||||
* Install [rocDecode](../../README.md#build-and-install-instructions)
|
||||
|
||||
* [FFMPEG](https://ffmpeg.org/about.html)
|
||||
|
||||
* On `Ubuntu`
|
||||
|
||||
```shell
|
||||
sudo apt install libavcodec-dev libavformat-dev libavutil-dev
|
||||
```
|
||||
|
||||
* On `RHEL`/`SLES` - install ffmpeg development packages manually or use [rocDecode-setup.py](../../rocDecode-setup.py) script
|
||||
|
||||
## Build
|
||||
|
||||
```shell
|
||||
mkdir video_decode_batch && cd video_decode_batch
|
||||
cmake ../
|
||||
make -j
|
||||
```
|
||||
|
||||
## Run
|
||||
|
||||
```shell
|
||||
./videodecodebatch -i <directory containing input video files [required]>
|
||||
-t <number of threads [optional - default:4]>
|
||||
-d <Device ID (>= 0) [optional - default:0]>
|
||||
-o Directory for output YUV files - optional
|
||||
-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]
|
||||
-disp_delay -specify the number of frames to be delayed for display; optional; default: 1
|
||||
```
|
||||
@@ -0,0 +1,497 @@
|
||||
/*
|
||||
Copyright (c) 2023 - 2026 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 <iostream>
|
||||
#include <iomanip>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <queue>
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include <functional>
|
||||
#include <sys/stat.h>
|
||||
#include <libgen.h>
|
||||
#if __cplusplus >= 201703L && __has_include(<filesystem>)
|
||||
#include <filesystem>
|
||||
#else
|
||||
#include <experimental/filesystem>
|
||||
#endif
|
||||
#include "video_demuxer.h"
|
||||
#include "roc_video_dec.h"
|
||||
#include "common.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) {
|
||||
ROCDEC_ERR("ERROR: didn't find any GPU!");
|
||||
return -1;
|
||||
}
|
||||
|
||||
hip_status = hipGetDeviceProperties(&hip_dev_prop, device_id);
|
||||
if (hip_status != hipSuccess) {
|
||||
ROCDEC_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_NONE;
|
||||
} else {
|
||||
reconfig_user_struct.b_dump_frames_to_file = true;
|
||||
reconfig_params.reconfig_flush_mode = RECONFIG_FLUSH_MODE_DUMP_TO_FILE;
|
||||
}
|
||||
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 {
|
||||
ROCDEC_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 {
|
||||
ROCDEC_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 {
|
||||
ROCDEC_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 {
|
||||
ROCDEC_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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
################################################################################
|
||||
# Copyright (c) 2023 - 2026 Advanced Micro Devices, Inc.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
# ROCM Path
|
||||
if(DEFINED ENV{ROCM_PATH})
|
||||
set(ROCM_PATH $ENV{ROCM_PATH} CACHE PATH "Default ROCm installation path")
|
||||
elseif(ROCM_PATH)
|
||||
message("-- INFO:ROCM_PATH Set -- ${ROCM_PATH}")
|
||||
else()
|
||||
set(ROCM_PATH /opt/rocm CACHE PATH "Default ROCm installation path")
|
||||
endif()
|
||||
# Set AMD Clang as default compiler
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED On)
|
||||
set(CMAKE_CXX_EXTENSIONS ON)
|
||||
if (NOT DEFINED CMAKE_CXX_COMPILER)
|
||||
set(CMAKE_C_COMPILER ${ROCM_PATH}/lib/llvm/bin/amdclang)
|
||||
set(CMAKE_CXX_COMPILER ${ROCM_PATH}/lib/llvm/bin/amdclang++)
|
||||
endif()
|
||||
|
||||
project(videodecodemem)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/../../cmake)
|
||||
list(APPEND CMAKE_PREFIX_PATH ${ROCM_PATH}/lib/cmake)
|
||||
|
||||
# rocdecode sample build type
|
||||
set(DEFAULT_BUILD_TYPE "Release")
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE "${DEFAULT_BUILD_TYPE}" CACHE STRING "rocdecode Default Build Type" FORCE)
|
||||
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release")
|
||||
endif()
|
||||
if(CMAKE_BUILD_TYPE MATCHES Debug)
|
||||
# -O0 -- Don't Optimize output file
|
||||
# -gdwarf-4 -- generate debugging information, dwarf-4 for making valgrind work
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -gdwarf-4")
|
||||
else()
|
||||
# -O3 -- Optimize output file
|
||||
# -DNDEBUG -- turn off asserts
|
||||
# -fPIC -- Generate position-independent code if possible
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -DNDEBUG -fPIC")
|
||||
endif()
|
||||
|
||||
set (HIP_PLATFORM amd CACHE STRING "HIP platform")
|
||||
find_package(HIP QUIET)
|
||||
find_package(rocdecode QUIET)
|
||||
find_package(rocprofiler-register QUIET)
|
||||
find_package(FFmpeg QUIET)
|
||||
|
||||
if(HIP_FOUND AND FFMPEG_FOUND AND rocdecode_FOUND AND rocprofiler-register_FOUND)
|
||||
# HIP
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} hip::host)
|
||||
# FFMPEG
|
||||
include_directories(${AVUTIL_INCLUDE_DIR} ${AVCODEC_INCLUDE_DIR}
|
||||
${AVFORMAT_INCLUDE_DIR})
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} ${FFMPEG_LIBRARIES})
|
||||
# rocdecode and utils
|
||||
include_directories (${rocdecode_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/../../utils ${CMAKE_CURRENT_SOURCE_DIR}/../../utils/rocvideodecode)
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} rocdecode::rocdecode)
|
||||
# rocprofiler-register
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} rocprofiler-register::rocprofiler-register)
|
||||
# sample app exe
|
||||
list(APPEND SOURCES ${PROJECT_SOURCE_DIR} videodecodemem.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../utils/rocvideodecode/roc_video_dec.cpp)
|
||||
add_executable(${PROJECT_NAME} ${SOURCES})
|
||||
target_link_libraries(${PROJECT_NAME} ${LINK_LIBRARY_LIST})
|
||||
# FFMPEG multi-version support
|
||||
if(_FFMPEG_AVCODEC_VERSION VERSION_LESS_EQUAL 58.134.100)
|
||||
target_compile_definitions(${PROJECT_NAME} PUBLIC USE_AVCODEC_GREATER_THAN_58_134=0)
|
||||
else()
|
||||
target_compile_definitions(${PROJECT_NAME} PUBLIC USE_AVCODEC_GREATER_THAN_58_134=1)
|
||||
endif()
|
||||
else()
|
||||
message("-- ERROR!: ${PROJECT_NAME} excluded! please install all the dependencies and try again!")
|
||||
if (NOT HIP_FOUND)
|
||||
message(FATAL_ERROR "-- ERROR!: HIP Not Found! - please install ROCm and HIP!")
|
||||
endif()
|
||||
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()
|
||||
if (NOT rocprofiler-register_FOUND)
|
||||
message(FATAL_ERROR "-- ERROR!: rocprofiler-register Not Found! - please install rocprofiler-register!")
|
||||
endif()
|
||||
endif()
|
||||
@@ -0,0 +1,39 @@
|
||||
# Video decode memory sample
|
||||
|
||||
The video decode memory sample illustrates a way to pass the data chunk-by-chunk sequentially to the FFMPEG demuxer which is then decoded on AMD hardware using rocDecode library.
|
||||
|
||||
The sample provides a user class `FileStreamProvider` derived from the existing `VideoDemuxer::StreamProvider` to read a video file and fill the buffer owned by the demuxer. It then takes frames from this buffer for further parsing and decoding.
|
||||
|
||||
## Prerequisites:
|
||||
|
||||
* Install [rocDecode](../../README.md#build-and-install-instructions)
|
||||
|
||||
* [FFMPEG](https://ffmpeg.org/about.html)
|
||||
|
||||
* On `Ubuntu`
|
||||
|
||||
```shell
|
||||
sudo apt install libavcodec-dev libavformat-dev libavutil-dev
|
||||
```
|
||||
|
||||
* On `RHEL`/`SLES` - install ffmpeg development packages manually or use [rocDecode-setup.py](../../rocDecode-setup.py) script
|
||||
|
||||
## Build
|
||||
|
||||
```shell
|
||||
mkdir video_decode_mem_sample && cd video_decode_mem_sample
|
||||
cmake ../
|
||||
make -j
|
||||
```
|
||||
|
||||
## Run
|
||||
|
||||
```shell
|
||||
./videodecodemem -i <input video file [required]>
|
||||
-o <output path to save decoded YUV frames [optional]>
|
||||
-d <GPU device ID - 0:device 0 / 1:device 1/ ... [optional - default:0]>
|
||||
-z <force_zero_latency - Decoded frames will be flushed out for display immediately [optional]>
|
||||
-sei <extract SEI messages [optional]>
|
||||
-crop <crop rectangle for output (not used when using interopped decoded frame) [optional - default: 0,0,0,0]>
|
||||
-m <output_surface_memory_type - decoded surface memory [optional - default: 0][0 : OUT_SURFACE_MEM_DEV_INTERNAL/ 1 : OUT_SURFACE_MEM_DEV_COPIED/ 2 : OUT_SURFACE_MEM_HOST_COPIED/ 3 : OUT_SURFACE_MEM_NOT_MAPPED]>
|
||||
```
|
||||
@@ -0,0 +1,303 @@
|
||||
/*
|
||||
Copyright (c) 2023 - 2026 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 <iostream>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <chrono>
|
||||
#include <sys/stat.h>
|
||||
#include <libgen.h>
|
||||
#if __cplusplus >= 201703L && __has_include(<filesystem>)
|
||||
#include <filesystem>
|
||||
#else
|
||||
#include <experimental/filesystem>
|
||||
#endif
|
||||
#include "video_demuxer.h"
|
||||
#include "roc_video_dec.h"
|
||||
#include "md5.h"
|
||||
|
||||
class FileStreamProvider : public VideoDemuxer::StreamProvider {
|
||||
public:
|
||||
FileStreamProvider(const char *input_file_path) {
|
||||
fp_in_.open(input_file_path, std::ifstream::in | std::ifstream::binary);
|
||||
if (!fp_in_) {
|
||||
std::cerr << "Unable to open input file: " << input_file_path << std::endl;
|
||||
exit(-1);
|
||||
}
|
||||
fp_in_.seekg (0, fp_in_.end);
|
||||
int length = fp_in_.tellg();
|
||||
fp_in_.seekg (0, fp_in_.beg);
|
||||
io_buffer_size_ = length;
|
||||
}
|
||||
~FileStreamProvider() {
|
||||
fp_in_.close();
|
||||
}
|
||||
// Fill in the buffer owned by the demuxer
|
||||
int GetData(uint8_t *p_buf, int n_buf) {
|
||||
// We read a file for this example. You may get your data from network or somewhere else
|
||||
return static_cast<int>(fp_in_.read(reinterpret_cast<char*>(p_buf), n_buf).gcount());
|
||||
}
|
||||
size_t GetBufferSize() { return io_buffer_size_; };
|
||||
|
||||
private:
|
||||
std::ifstream fp_in_;
|
||||
size_t io_buffer_size_;
|
||||
};
|
||||
|
||||
void ShowHelpAndExit(const char *option = NULL) {
|
||||
std::cout << "Options:" << std::endl
|
||||
<< "-i Input File Path - required" << std::endl
|
||||
<< "-o Output File Path - dumps output if requested; optional" << std::endl
|
||||
<< "-d GPU device ID (0 for the first device, 1 for the second, etc.); optional; default: 0" << std::endl
|
||||
<< "-z force_zero_latency (force_zero_latency, Decoded frames will be flushed out for display immediately); optional;" << std::endl
|
||||
<< "-sei extract SEI messages; optional;" << std::endl
|
||||
<< "-md5 generate MD5 message digest on the decoded YUV image sequence; optional;" << std::endl
|
||||
<< "-md5_check MD5 File Path - generate MD5 message digest on the decoded YUV image sequence and compare to the reference MD5 string in a file; optional;" << std::endl
|
||||
<< "-crop crop rectangle for output (not used when using interopped decoded frame); optional; default: 0" << std::endl
|
||||
<< "-m output_surface_memory_type - decoded surface memory; optional; default - 0"
|
||||
<< " [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);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
|
||||
std::string input_file_path, output_file_path, md5_file_path;
|
||||
std::fstream ref_md5_file;
|
||||
int dump_output_frames = 0;
|
||||
int device_id = 0;
|
||||
bool b_force_zero_latency = false; // false by default: enabling this option might affect decoding performance
|
||||
bool b_extract_sei_messages = false;
|
||||
bool b_generate_md5 = false;
|
||||
bool b_md5_check = false;
|
||||
int disp_delay = 1;
|
||||
Rect crop_rect = {};
|
||||
Rect *p_crop_rect = nullptr;
|
||||
OutputSurfaceMemoryType mem_type = OUT_SURFACE_MEM_DEV_INTERNAL; // set to internal
|
||||
// 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_file_path = argv[i];
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-o")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-o");
|
||||
}
|
||||
output_file_path = argv[i];
|
||||
dump_output_frames = 1;
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-d")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-d");
|
||||
}
|
||||
device_id = atoi(argv[i]);
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-z")) {
|
||||
if (i == argc) {
|
||||
ShowHelpAndExit("-z");
|
||||
}
|
||||
b_force_zero_latency = true;
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-sei")) {
|
||||
if (i == argc) {
|
||||
ShowHelpAndExit("-sei");
|
||||
}
|
||||
b_extract_sei_messages = true;
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-md5")) {
|
||||
if (i == argc) {
|
||||
ShowHelpAndExit("-md5");
|
||||
}
|
||||
b_generate_md5 = true;
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-md5_check")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-md5_check");
|
||||
}
|
||||
b_generate_md5 = true;
|
||||
b_md5_check = true;
|
||||
md5_file_path = argv[i];
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-crop")) {
|
||||
if (++i == argc || 4 != sscanf(argv[i], "%d,%d,%d,%d", &crop_rect.left, &crop_rect.top, &crop_rect.right, &crop_rect.bottom)) {
|
||||
ShowHelpAndExit("-crop");
|
||||
}
|
||||
if ((crop_rect.right - crop_rect.left) % 2 == 1 || (crop_rect.bottom - crop_rect.top) % 2 == 1) {
|
||||
std::cout << "output crop rectangle must have width and height of even numbers" << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
p_crop_rect = &crop_rect;
|
||||
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]);
|
||||
}
|
||||
try {
|
||||
FileStreamProvider stream_provider(input_file_path.c_str());
|
||||
VideoDemuxer demuxer(&stream_provider);
|
||||
rocDecVideoCodec rocdec_codec_id = AVCodec2RocDecVideoCodec(demuxer.GetCodecID());
|
||||
RocVideoDecoder viddec(device_id, mem_type, rocdec_codec_id, b_force_zero_latency, p_crop_rect, b_extract_sei_messages, disp_delay);
|
||||
if(!viddec.CodecSupported(device_id, rocdec_codec_id, demuxer.GetBitDepth())) {
|
||||
std::cerr << "GPU doesn't support codec!" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string device_name, gcn_arch_name;
|
||||
int pci_bus_id, pci_domain_id, pci_device_id;
|
||||
|
||||
viddec.GetDeviceinfo(device_name, gcn_arch_name, pci_bus_id, pci_domain_id, pci_device_id);
|
||||
std::cout << "info: Using GPU device " << 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::cout << "info: decoding started, please wait!" << std::endl;
|
||||
|
||||
int n_video_bytes = 0, n_frame_returned = 0, n_frame = 0;
|
||||
uint8_t *pvideo = nullptr;
|
||||
int pkg_flags = 0;
|
||||
uint8_t *pframe = nullptr;
|
||||
int64_t pts = 0;
|
||||
OutputSurfaceInfo *surf_info;
|
||||
uint32_t width, height;
|
||||
double total_dec_time = 0;
|
||||
MD5Generator *md5_generator = nullptr;
|
||||
|
||||
if (b_generate_md5) {
|
||||
md5_generator = new MD5Generator();
|
||||
md5_generator->InitMd5();
|
||||
}
|
||||
|
||||
do {
|
||||
auto start_time = std::chrono::high_resolution_clock::now();
|
||||
demuxer.Demux(&pvideo, &n_video_bytes, &pts);
|
||||
// Treat 0 bitstream size as end of stream indicator
|
||||
if (n_video_bytes == 0) {
|
||||
pkg_flags |= ROCDEC_PKT_ENDOFSTREAM;
|
||||
}
|
||||
n_frame_returned = viddec.DecodeFrame(pvideo, n_video_bytes, pkg_flags, pts);
|
||||
auto end_time = std::chrono::high_resolution_clock::now();
|
||||
auto time_per_frame = std::chrono::duration<double, std::milli>(end_time - start_time).count();
|
||||
total_dec_time += time_per_frame;
|
||||
if (!n_frame && !viddec.GetOutputSurfaceInfo(&surf_info)) {
|
||||
std::cerr << "Error: Failed to get Output Surface Info!" << std::endl;
|
||||
break;
|
||||
}
|
||||
for (int i = 0; i < n_frame_returned; i++) {
|
||||
pframe = viddec.GetFrame(&pts);
|
||||
if (b_generate_md5 && pframe) {
|
||||
md5_generator->UpdateMd5ForFrame(pframe, surf_info);
|
||||
}
|
||||
if (dump_output_frames && mem_type != OUT_SURFACE_MEM_NOT_MAPPED) {
|
||||
viddec.SaveFrameToFile(output_file_path, pframe, surf_info);
|
||||
}
|
||||
// release frame
|
||||
viddec.ReleaseFrame(pts);
|
||||
}
|
||||
n_frame += n_frame_returned;
|
||||
} while (n_video_bytes);
|
||||
|
||||
std::cout << "info: Total frame decoded: " << n_frame << std::endl;
|
||||
if (!dump_output_frames) {
|
||||
std::cout << "info: avg decoding time per frame (ms): " << total_dec_time / n_frame << std::endl;
|
||||
std::cout << "info: avg FPS: " << (n_frame / total_dec_time) * 1000 << 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 {
|
||||
std::cout << "info: saved frames into " << output_file_path << std::endl;
|
||||
}
|
||||
|
||||
}
|
||||
if (b_generate_md5) {
|
||||
uint8_t *digest;
|
||||
md5_generator->FinalizeMd5(&digest);
|
||||
std::cout << "MD5 message digest: ";
|
||||
for (int i = 0; i < 16; i++) {
|
||||
std::cout << std::setfill('0') << std::setw(2) << std::hex << static_cast<int>(digest[i]);
|
||||
}
|
||||
std::cout << std::endl;
|
||||
if (b_md5_check) {
|
||||
std::string ref_md5_string(33, 0);
|
||||
uint8_t ref_md5[16];
|
||||
ref_md5_file.open(md5_file_path.c_str(), std::ios::in);
|
||||
if ((ref_md5_file.rdstate() & std::ifstream::failbit) != 0) {
|
||||
std::cerr << "Failed to open MD5 file." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
ref_md5_file.getline(ref_md5_string.data(), ref_md5_string.length());
|
||||
if ((ref_md5_file.rdstate() & std::ifstream::badbit) != 0) {
|
||||
std::cerr << "Failed to read MD5 digest string." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
for (int i = 0; i < 16; i++) {
|
||||
std::string part = ref_md5_string.substr(i * 2, 2);
|
||||
ref_md5[i] = std::stoi(part, nullptr, 16);
|
||||
}
|
||||
if (memcmp(digest, ref_md5, 16) == 0) {
|
||||
std::cout << "MD5 digest matches the reference MD5 digest: ";
|
||||
} else {
|
||||
std::cout << "MD5 digest does not match the reference MD5 digest: ";
|
||||
}
|
||||
std::cout << ref_md5_string << std::endl;
|
||||
ref_md5_file.close();
|
||||
}
|
||||
delete md5_generator;
|
||||
}
|
||||
} catch (const std::exception &ex) {
|
||||
std::cout << ex.what() << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
################################################################################
|
||||
# Copyright (c) 2023 - 2026 Advanced Micro Devices, Inc.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
# ROCM Path
|
||||
if(DEFINED ENV{ROCM_PATH})
|
||||
set(ROCM_PATH $ENV{ROCM_PATH} CACHE PATH "Default ROCm installation path")
|
||||
elseif(ROCM_PATH)
|
||||
message("-- INFO:ROCM_PATH Set -- ${ROCM_PATH}")
|
||||
else()
|
||||
set(ROCM_PATH /opt/rocm CACHE PATH "Default ROCm installation path")
|
||||
endif()
|
||||
# Set AMD Clang as default compiler
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED On)
|
||||
set(CMAKE_CXX_EXTENSIONS ON)
|
||||
if (NOT DEFINED CMAKE_CXX_COMPILER)
|
||||
set(CMAKE_C_COMPILER ${ROCM_PATH}/lib/llvm/bin/amdclang)
|
||||
set(CMAKE_CXX_COMPILER ${ROCM_PATH}/lib/llvm/bin/amdclang++)
|
||||
endif()
|
||||
|
||||
project(videodecodemultifiles)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/../../cmake)
|
||||
list(APPEND CMAKE_PREFIX_PATH ${ROCM_PATH}/lib/cmake)
|
||||
|
||||
# rocdecode sample build type
|
||||
set(DEFAULT_BUILD_TYPE "Release")
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE "${DEFAULT_BUILD_TYPE}" CACHE STRING "rocdecode Default Build Type" FORCE)
|
||||
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release")
|
||||
endif()
|
||||
if(CMAKE_BUILD_TYPE MATCHES Debug)
|
||||
# -O0 -- Don't Optimize output file
|
||||
# -gdwarf-4 -- generate debugging information, dwarf-4 for making valgrind work
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -gdwarf-4")
|
||||
else()
|
||||
# -O3 -- Optimize output file
|
||||
# -DNDEBUG -- turn off asserts
|
||||
# -fPIC -- Generate position-independent code if possible
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -DNDEBUG -fPIC")
|
||||
endif()
|
||||
|
||||
set (HIP_PLATFORM amd CACHE STRING "HIP platform")
|
||||
find_package(HIP QUIET)
|
||||
find_package(rocdecode QUIET)
|
||||
find_package(rocprofiler-register QUIET)
|
||||
find_package(FFmpeg QUIET)
|
||||
|
||||
if(HIP_FOUND AND FFMPEG_FOUND AND rocdecode_FOUND AND rocprofiler-register_FOUND)
|
||||
# HIP
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} hip::host)
|
||||
# FFMPEG
|
||||
include_directories(${AVUTIL_INCLUDE_DIR} ${AVCODEC_INCLUDE_DIR}
|
||||
${AVFORMAT_INCLUDE_DIR})
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} ${FFMPEG_LIBRARIES})
|
||||
# rocdecode and utils
|
||||
include_directories (${rocdecode_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/.. ${CMAKE_CURRENT_SOURCE_DIR}/../../utils ${CMAKE_CURRENT_SOURCE_DIR}/../../utils/rocvideodecode)
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} rocdecode::rocdecode)
|
||||
# rocprofiler-register
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} rocprofiler-register::rocprofiler-register)
|
||||
# sample app exe
|
||||
list(APPEND SOURCES ${PROJECT_SOURCE_DIR} videodecodemultifiles.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../utils/rocvideodecode/roc_video_dec.cpp)
|
||||
add_executable(${PROJECT_NAME} ${SOURCES})
|
||||
target_link_libraries(${PROJECT_NAME} ${LINK_LIBRARY_LIST})
|
||||
# FFMPEG multi-version support
|
||||
if(_FFMPEG_AVCODEC_VERSION VERSION_LESS_EQUAL 58.134.100)
|
||||
target_compile_definitions(${PROJECT_NAME} PUBLIC USE_AVCODEC_GREATER_THAN_58_134=0)
|
||||
else()
|
||||
target_compile_definitions(${PROJECT_NAME} PUBLIC USE_AVCODEC_GREATER_THAN_58_134=1)
|
||||
endif()
|
||||
else()
|
||||
message("-- ERROR!: ${PROJECT_NAME} excluded! please install all the dependencies and try again!")
|
||||
if (NOT HIP_FOUND)
|
||||
message(FATAL_ERROR "-- ERROR!: HIP Not Found! - please install ROCm and HIP!")
|
||||
endif()
|
||||
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()
|
||||
if (NOT rocprofiler-register_FOUND)
|
||||
message(FATAL_ERROR "-- ERROR!: rocprofiler-register Not Found! - please install rocprofiler-register!")
|
||||
endif()
|
||||
endif()
|
||||
@@ -0,0 +1,49 @@
|
||||
# Video decode multi files sample
|
||||
|
||||
The video decodes multiple files sample illustrates the use of providing a list of files as input to showcase the reconfigure option in the rocDecode library. The input video files have to be of the same codec type to use the reconfigure option but can have different resolutions or resize parameters.
|
||||
|
||||
The reconfigure option can be disabled by the user if needed. The input file is parsed line by line and data is stored in a queue. The individual video files are demuxed and decoded one after the other in a loop. Output for each input file can also be stored if needed.
|
||||
|
||||
## Prerequisites:
|
||||
|
||||
* Install [rocDecode](../../README.md#build-and-install-instructions)
|
||||
|
||||
* [FFMPEG](https://ffmpeg.org/about.html)
|
||||
|
||||
* On `Ubuntu`
|
||||
|
||||
```shell
|
||||
sudo apt install libavcodec-dev libavformat-dev libavutil-dev
|
||||
```
|
||||
|
||||
* On `RHEL`/`SLES` - install ffmpeg development packages manually or use [rocDecode-setup.py](../../rocDecode-setup.py) script
|
||||
|
||||
## Build
|
||||
|
||||
```shell
|
||||
mkdir video_decode_multi_files_sample && cd video_decode_multi_files_sample
|
||||
cmake ../
|
||||
make -j
|
||||
```
|
||||
|
||||
## Run
|
||||
|
||||
```shell
|
||||
./videodecodemultifiles -i <input file list[required - example.txt]>
|
||||
-d <GPU device ID - 0:device 0 / 1:device 1/ ... [optional - default:0]>
|
||||
-use_reconfigure <flag (bool - 0/1) [optional - default: 1] set 0 to disable reconfigure api for decoding multiple files. Only resolution changes between files are supported when reconfigure is enabled. The codec, bit_depth, and the chroma_format must be the same between files>
|
||||
```
|
||||
### Note: Example input file list - example.txt
|
||||
|
||||
```shell
|
||||
infile input1.[mp4/mov...] [required]
|
||||
outfile output1.yuv [optional]
|
||||
z 0 [optional]
|
||||
sei 0 [optional]
|
||||
crop l,t,r,b [optional]
|
||||
m 0 [optional] [0 : OUT_SURFACE_MEM_DEV_INTERNAL/ 1 : OUT_SURFACE_MEM_DEV_COPIED/ 2 : OUT_SURFACE_MEM_HOST_COPIED/ 3 : OUT_SURFACE_MEM_NOT_MAPPED]
|
||||
infile input2.[mp4/mov...] [optional]
|
||||
outfile output2.yuv [optional]
|
||||
...
|
||||
...
|
||||
```
|
||||
@@ -0,0 +1,279 @@
|
||||
/*
|
||||
Copyright (c) 2023 - 2026 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 <iostream>
|
||||
#include <iomanip>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <chrono>
|
||||
#include <deque>
|
||||
#include <sys/stat.h>
|
||||
#include <libgen.h>
|
||||
#if __cplusplus >= 201703L && __has_include(<filesystem>)
|
||||
#include <filesystem>
|
||||
#else
|
||||
#include <experimental/filesystem>
|
||||
#endif
|
||||
#include "video_demuxer.h"
|
||||
#include "roc_video_dec.h"
|
||||
#include "common.h"
|
||||
|
||||
typedef struct {
|
||||
std::string in_file;
|
||||
std::string out_file;
|
||||
bool b_force_zero_latency;
|
||||
bool b_extract_sei_messages;
|
||||
bool b_flush_last_frames;
|
||||
Rect crop_rect;
|
||||
Rect *p_crop_rect;
|
||||
int dump_output_frames;
|
||||
OutputSurfaceMemoryType mem_type; // set to internal
|
||||
int disp_delay;
|
||||
} FileInfo;
|
||||
|
||||
void ShowHelpAndExit(const char *option = NULL) {
|
||||
std::cout << "Options:" << std::endl
|
||||
<< "-i Input File List - required (text file containing all files to decode in below format)" << std::endl
|
||||
<< "example.txt:" << std::endl
|
||||
<< "infile input1.[mp4/mov...] (Input file path)" << std::endl
|
||||
<< "outfile output1.yuv (Output file path)" << std::endl
|
||||
<< "z 0 (force_zero_latency - Decoded frames will be flushed out for display immediately; default: 0)" << std::endl
|
||||
<< "sei 0 (extract SEI messages; default: 0)" << std::endl
|
||||
<< "crop l,t,r,b (crop rectangle for output (not used when using interopped decoded frame); default: 0)" << std::endl
|
||||
<< "m 0 decoded surface memory; optional; default - 0 [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
|
||||
<< "flush 1 flush last frames during reconfig; optional; default - 1 [1 : Flush last frames during reconfig 0 : Discard last frames during reconfigure ]" << std::endl
|
||||
<< "-disp_delay -specify the number of frames to be delayed for display; optional; default: 1" << std::endl
|
||||
<< "infile input2.[mp4/mov...]" << std::endl
|
||||
<< "outfile output2.yuv" << std::endl
|
||||
<< "...." << std::endl
|
||||
<< "...." << std::endl
|
||||
<< "-d GPU device ID (0 for the first device, 1 for the second, etc.); optional; default: 0" << std::endl
|
||||
<< "-use_reconfigure flag (bool - 0/1); optional; default: 1; set 0 to disable reconfigure api for decoding multiple files; "
|
||||
<< "only resolution changes between files are supported when reconfigure is enabled. The codec, bit_depth, and the chroma_format must be the same between files." << std::endl;
|
||||
exit(0);
|
||||
}
|
||||
|
||||
void ParseCommandLine(std::deque<FileInfo> *multi_file_data, int &device_id, bool &use_reconfigure, int argc, char *argv[]) {
|
||||
|
||||
FileInfo file_data;
|
||||
std::string file_list_path;
|
||||
|
||||
// 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");
|
||||
}
|
||||
file_list_path = argv[i];
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-d")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-d");
|
||||
}
|
||||
device_id = atoi(argv[i]);
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-use_reconfigure")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-use_reconfigure");
|
||||
}
|
||||
use_reconfigure = atoi(argv[i]) ? true : false;
|
||||
continue;
|
||||
}
|
||||
ShowHelpAndExit(argv[i]);
|
||||
}
|
||||
|
||||
// Parse the input filelist
|
||||
std::ifstream filestream(file_list_path);
|
||||
std::string line;
|
||||
char* str;
|
||||
char param[256];
|
||||
char value[256];
|
||||
int file_idx = 0;
|
||||
|
||||
while (std::getline(filestream, line)) {
|
||||
str = (char *)line.c_str();
|
||||
sscanf(str,"%s %s", param, value);
|
||||
if (!strcmp(param, "infile")) {
|
||||
if (file_idx > 0) {
|
||||
multi_file_data->push_back(file_data);
|
||||
}
|
||||
file_data.in_file = value;
|
||||
file_idx++;
|
||||
file_data.b_force_zero_latency = false;
|
||||
file_data.b_extract_sei_messages = false;
|
||||
file_data.b_flush_last_frames = true;
|
||||
file_data.dump_output_frames = 0;
|
||||
file_data.crop_rect = {0, 0, 0, 0};
|
||||
file_data.p_crop_rect = nullptr;
|
||||
file_data.mem_type = OUT_SURFACE_MEM_DEV_INTERNAL;
|
||||
file_data.disp_delay = 1;
|
||||
} else if (!strcmp(param, "outfile")) {
|
||||
file_data.out_file = value;
|
||||
file_data.dump_output_frames = 1;
|
||||
} else if (!strcmp(param, "z")) {
|
||||
file_data.b_force_zero_latency = atoi(value) ? true : false;
|
||||
} else if (!strcmp(param, "sei")) {
|
||||
file_data.b_extract_sei_messages = atoi(value) ? true : false;
|
||||
} else if (!strcmp(param, "flush")) {
|
||||
file_data.b_flush_last_frames = atoi(value) ? true : false;
|
||||
} else if (!strcmp(param, "crop")) {
|
||||
sscanf(value, "%d,%d,%d,%d", &file_data.crop_rect.left, &file_data.crop_rect.top, &file_data.crop_rect.right, &file_data.crop_rect.bottom);
|
||||
if ((file_data.crop_rect.right - file_data.crop_rect.left) % 2 == 1 || (file_data.crop_rect.bottom - file_data.crop_rect.top) % 2 == 1) {
|
||||
std::cout << "Cropping rect must have width and height of even numbers" << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
file_data.p_crop_rect = &file_data.crop_rect;
|
||||
} else if (!strcmp(param, "m")) {
|
||||
file_data.mem_type = static_cast<OutputSurfaceMemoryType>(atoi(value));
|
||||
} else if (!strcmp(param, "disp_delay")) {
|
||||
file_data.disp_delay = atoi(value);
|
||||
}
|
||||
}
|
||||
if (file_idx > 0) {
|
||||
multi_file_data->push_back(file_data);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
|
||||
std::deque<FileInfo> multi_file_data;
|
||||
FileInfo file_data;
|
||||
int device_id = 0;
|
||||
bool use_reconfigure = true;
|
||||
|
||||
ParseCommandLine (&multi_file_data, device_id, use_reconfigure, argc, argv);
|
||||
RocVideoDecoder *viddec = NULL;
|
||||
ReconfigParams reconfig_params = { 0 };
|
||||
ReconfigDumpFileStruct reconfig_user_struct = { 0 };
|
||||
|
||||
try {
|
||||
while (!multi_file_data.empty()) {
|
||||
file_data = multi_file_data.front();
|
||||
multi_file_data.pop_front();
|
||||
VideoDemuxer demuxer(file_data.in_file.c_str());
|
||||
rocDecVideoCodec rocdec_codec_id = AVCodec2RocDecVideoCodec(demuxer.GetCodecID());
|
||||
if (file_data.b_flush_last_frames && file_data.dump_output_frames) {
|
||||
reconfig_params.p_fn_reconfigure_flush = ReconfigureFlushCallback;
|
||||
reconfig_user_struct.b_dump_frames_to_file = file_data.dump_output_frames;
|
||||
reconfig_user_struct.output_file_name = file_data.out_file;
|
||||
reconfig_params.reconfig_flush_mode = RECONFIG_FLUSH_MODE_DUMP_TO_FILE;
|
||||
reconfig_params.p_reconfig_user_struct = &reconfig_user_struct;
|
||||
}
|
||||
if (use_reconfigure) {
|
||||
if (!viddec) {
|
||||
viddec = new RocVideoDecoder(device_id, file_data.mem_type, rocdec_codec_id, file_data.b_force_zero_latency, file_data.p_crop_rect, file_data.b_extract_sei_messages, file_data.disp_delay);
|
||||
}
|
||||
} else {
|
||||
viddec = new RocVideoDecoder(device_id, file_data.mem_type, rocdec_codec_id, file_data.b_force_zero_latency, file_data.p_crop_rect, file_data.b_extract_sei_messages, file_data.disp_delay);
|
||||
}
|
||||
if(!viddec->CodecSupported(device_id, rocdec_codec_id, demuxer.GetBitDepth())) {
|
||||
std::cerr << "Codec not supported on GPU, skipping this file!" << std::endl;
|
||||
continue;
|
||||
}
|
||||
if (viddec && file_data.b_flush_last_frames) viddec->SetReconfigParams(&reconfig_params);
|
||||
std::string device_name, gcn_arch_name;
|
||||
int pci_bus_id, pci_domain_id, pci_device_id;
|
||||
|
||||
std::size_t found_file = file_data.in_file.find_last_of('/');
|
||||
std::cout << "info: Input file: " << file_data.in_file.substr(found_file + 1) << std::endl;
|
||||
viddec->GetDeviceinfo(device_name, gcn_arch_name, pci_bus_id, pci_domain_id, pci_device_id);
|
||||
std::cout << "info: Using GPU device " << 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::cout << "info: decoding started, please wait!" << std::endl;
|
||||
|
||||
int n_video_bytes = 0, n_frame_returned = 0, n_frame = 0;
|
||||
uint8_t *pvideo = nullptr;
|
||||
int pkg_flags = 0;
|
||||
uint8_t *pframe = nullptr;
|
||||
int64_t pts = 0;
|
||||
OutputSurfaceInfo *surf_info;
|
||||
uint32_t width, height;
|
||||
double total_dec_time = 0;
|
||||
|
||||
do {
|
||||
auto start_time = std::chrono::high_resolution_clock::now();
|
||||
demuxer.Demux(&pvideo, &n_video_bytes, &pts);
|
||||
// Treat 0 bitstream size as end of stream indicator
|
||||
if (n_video_bytes == 0) {
|
||||
pkg_flags |= ROCDEC_PKT_ENDOFSTREAM;
|
||||
}
|
||||
n_frame_returned = viddec->DecodeFrame(pvideo, n_video_bytes, pkg_flags, pts);
|
||||
auto end_time = std::chrono::high_resolution_clock::now();
|
||||
auto time_per_frame = std::chrono::duration<double, std::milli>(end_time - start_time).count();
|
||||
total_dec_time += time_per_frame;
|
||||
if (!n_frame && !viddec->GetOutputSurfaceInfo(&surf_info)) {
|
||||
std::cerr << "Error: Failed to get Output Surface Info!" << std::endl;
|
||||
break;
|
||||
}
|
||||
for (int i = 0; i < n_frame_returned; i++) {
|
||||
pframe = viddec->GetFrame(&pts);
|
||||
if (file_data.dump_output_frames && file_data.mem_type != OUT_SURFACE_MEM_NOT_MAPPED) {
|
||||
viddec->SaveFrameToFile(file_data.out_file, pframe, surf_info);
|
||||
}
|
||||
// release frame
|
||||
viddec->ReleaseFrame(pts);
|
||||
}
|
||||
n_frame += n_frame_returned;
|
||||
} while (n_video_bytes);
|
||||
|
||||
n_frame += viddec->GetNumOfFlushedFrames();
|
||||
std::cout << "info: Total frame decoded: " << n_frame << std::endl;
|
||||
if (!file_data.dump_output_frames) {
|
||||
std::cout << "info: avg decoding time per frame (ms): " << total_dec_time / n_frame << std::endl;
|
||||
std::cout << "info: avg FPS: " << (n_frame / total_dec_time) * 1000 << std::endl;
|
||||
} else {
|
||||
if (file_data.mem_type == OUT_SURFACE_MEM_NOT_MAPPED) {
|
||||
std::cout << "info: saving frames with -m 3 option is not supported!" << std::endl;
|
||||
} else {
|
||||
std::cout << "info: saved frames into " << file_data.out_file << std::endl;
|
||||
}
|
||||
|
||||
}
|
||||
if (!use_reconfigure) {
|
||||
delete viddec;
|
||||
viddec = NULL;
|
||||
}
|
||||
std::cout << "\n";
|
||||
}
|
||||
if(viddec) {
|
||||
delete viddec;
|
||||
viddec = NULL;
|
||||
}
|
||||
} catch (const std::exception &ex) {
|
||||
std::cout << ex.what() << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
################################################################################
|
||||
# Copyright (c) 2023 - 2026 Advanced Micro Devices, Inc.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
# ROCM Path
|
||||
if(DEFINED ENV{ROCM_PATH})
|
||||
set(ROCM_PATH $ENV{ROCM_PATH} CACHE PATH "Default ROCm installation path")
|
||||
elseif(ROCM_PATH)
|
||||
message("-- INFO:ROCM_PATH Set -- ${ROCM_PATH}")
|
||||
else()
|
||||
set(ROCM_PATH /opt/rocm CACHE PATH "Default ROCm installation path")
|
||||
endif()
|
||||
# Set AMD Clang as default compiler
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED On)
|
||||
set(CMAKE_CXX_EXTENSIONS ON)
|
||||
if (NOT DEFINED CMAKE_CXX_COMPILER)
|
||||
set(CMAKE_C_COMPILER ${ROCM_PATH}/lib/llvm/bin/amdclang)
|
||||
set(CMAKE_CXX_COMPILER ${ROCM_PATH}/lib/llvm/bin/amdclang++)
|
||||
endif()
|
||||
|
||||
project(videodecodeperf)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/../../cmake)
|
||||
list(APPEND CMAKE_PREFIX_PATH ${ROCM_PATH}/lib/cmake)
|
||||
|
||||
# rocdecode sample build type
|
||||
set(DEFAULT_BUILD_TYPE "Release")
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE "${DEFAULT_BUILD_TYPE}" CACHE STRING "rocdecode Default Build Type" FORCE)
|
||||
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release")
|
||||
endif()
|
||||
if(CMAKE_BUILD_TYPE MATCHES Debug)
|
||||
# -O0 -- Don't Optimize output file
|
||||
# -gdwarf-4 -- generate debugging information, dwarf-4 for making valgrind work
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -gdwarf-4")
|
||||
else()
|
||||
# -O3 -- Optimize output file
|
||||
# -DNDEBUG -- turn off asserts
|
||||
# -fPIC -- Generate position-independent code if possible
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -DNDEBUG -fPIC")
|
||||
endif()
|
||||
|
||||
set (HIP_PLATFORM amd CACHE STRING "HIP platform")
|
||||
find_package(HIP QUIET)
|
||||
find_package(rocdecode QUIET)
|
||||
find_package(rocdecode-host 1.0.0 QUIET)
|
||||
find_package(rocprofiler-register QUIET)
|
||||
find_package(FFmpeg QUIET)
|
||||
find_package(Threads QUIET)
|
||||
|
||||
if(HIP_FOUND AND FFMPEG_FOUND AND rocdecode_FOUND AND Threads_FOUND AND rocprofiler-register_FOUND)
|
||||
# HIP
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} hip::host)
|
||||
# FFMPEG
|
||||
include_directories(${AVUTIL_INCLUDE_DIR} ${AVCODEC_INCLUDE_DIR}
|
||||
${SWSCALE_INCLUDE_DIR} ${AVFORMAT_INCLUDE_DIR})
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} ${FFMPEG_LIBRARIES})
|
||||
# rocdecode and utils
|
||||
include_directories (${rocdecode_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/../../utils ${CMAKE_CURRENT_SOURCE_DIR}/../../utils/rocvideodecode ${CMAKE_CURRENT_SOURCE_DIR}/..)
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} rocdecode::rocdecode)
|
||||
# threads
|
||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
find_package(Threads REQUIRED)
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} Threads::Threads)
|
||||
# rocprofiler-register
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} rocprofiler-register::rocprofiler-register)
|
||||
# sample app exe
|
||||
list(APPEND SOURCES ${PROJECT_SOURCE_DIR} videodecodeperf.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../utils/rocvideodecode/roc_video_dec.cpp)
|
||||
if(rocdecode-host_FOUND)
|
||||
include_directories (${CMAKE_CURRENT_SOURCE_DIR}/../../utils/ffmpegvideodecode)
|
||||
list(APPEND SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/../../utils/ffmpegvideodecode/ffmpeg_video_dec.cpp)
|
||||
endif()
|
||||
add_executable(${PROJECT_NAME} ${SOURCES})
|
||||
if(rocdecode-host_FOUND)
|
||||
# rocdecode-host
|
||||
include_directories(${rocdecode-host_INCLUDE_DIR})
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} rocdecode::rocdecode-host)
|
||||
target_compile_definitions(${PROJECT_NAME} PUBLIC ENABLE_HOST_DECODE=1)
|
||||
else()
|
||||
target_compile_definitions(${PROJECT_NAME} PUBLIC ENABLE_HOST_DECODE=0)
|
||||
endif()
|
||||
target_link_libraries(${PROJECT_NAME} ${LINK_LIBRARY_LIST})
|
||||
|
||||
# FFMPEG multi-version support
|
||||
if(_FFMPEG_AVCODEC_VERSION VERSION_LESS_EQUAL 58.134.100)
|
||||
target_compile_definitions(${PROJECT_NAME} PUBLIC USE_AVCODEC_GREATER_THAN_58_134=0)
|
||||
else()
|
||||
target_compile_definitions(${PROJECT_NAME} PUBLIC USE_AVCODEC_GREATER_THAN_58_134=1)
|
||||
endif()
|
||||
else()
|
||||
message("-- ERROR!: ${PROJECT_NAME} excluded! please install all the dependencies and try again!")
|
||||
if (NOT HIP_FOUND)
|
||||
message(FATAL_ERROR "-- ERROR!: HIP Not Found! - please install ROCm and HIP!")
|
||||
endif()
|
||||
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()
|
||||
if (NOT Threads_FOUND)
|
||||
message(FATAL_ERROR "-- ERROR!: Threads Not Found! - please install Threads!")
|
||||
endif()
|
||||
if (NOT rocprofiler-register_FOUND)
|
||||
message(FATAL_ERROR "-- ERROR!: rocprofiler-register Not Found! - please install rocprofiler-register!")
|
||||
endif()
|
||||
endif()
|
||||
@@ -0,0 +1,43 @@
|
||||
# Video decode performance sample
|
||||
|
||||
This sample illustrates the FFMPEG demuxer to get the individual frames which are then decoded on AMD hardware using rocDecode library.
|
||||
|
||||
This sample uses multiple threads to decode the same input video parallelly.
|
||||
|
||||
## Prerequisites:
|
||||
|
||||
* Install [rocDecode](../../README.md#build-and-install-instructions)
|
||||
|
||||
* [FFMPEG](https://ffmpeg.org/about.html)
|
||||
|
||||
* On `Ubuntu`
|
||||
|
||||
```shell
|
||||
sudo apt install libavcodec-dev libavformat-dev libavutil-dev
|
||||
```
|
||||
|
||||
* On `RHEL`/`SLES` - install ffmpeg development packages manually or use [rocDecode-setup.py](../../rocDecode-setup.py) script
|
||||
|
||||
## Build
|
||||
|
||||
```shell
|
||||
mkdir video_decode_perf_sample && cd video_decode_perf_sample
|
||||
cmake ../
|
||||
make -j
|
||||
```
|
||||
|
||||
## Run
|
||||
|
||||
```shell
|
||||
./videodecodeperf -i <input video file [required]>
|
||||
-t <number of threads [optional - default:1]>
|
||||
-f <Number of decoded frames - specify the number of pictures to be decoded [optional]>
|
||||
-disp_delay <display delay - specify the number of frames to be delayed for display [optional]>
|
||||
-d <Device ID (>= 0) [optional - default:0]>
|
||||
-z <force_zero_latency - Decoded frames will be flushed out for display immediately [optional]>
|
||||
-m <Memory type (integer values between 0 to 3: specifies where to store the decoded output:
|
||||
0 = decoded output will be in internal interopped memory,
|
||||
1 = decoded output will be copied to a separate device memory
|
||||
2 = decoded output will be copied to a separate host memory
|
||||
3 = decoded output will not be available (decode only)) [optional; default: 3]>
|
||||
```
|
||||
@@ -0,0 +1,305 @@
|
||||
/*
|
||||
Copyright (c) 2023 - 2026 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 <iostream>
|
||||
#include <iomanip>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
#include <sys/stat.h>
|
||||
#include <libgen.h>
|
||||
#if __cplusplus >= 201703L && __has_include(<filesystem>)
|
||||
#include <filesystem>
|
||||
#else
|
||||
#include <experimental/filesystem>
|
||||
#endif
|
||||
#include "video_demuxer.h"
|
||||
#include "roc_video_dec.h"
|
||||
#include "common.h"
|
||||
#if ENABLE_HOST_DECODE
|
||||
#include "ffmpeg_video_dec.h"
|
||||
#endif
|
||||
|
||||
void DecProc(RocVideoDecoder *p_dec, VideoDemuxer *demuxer, int *pn_frame, int *pn_pic_dec, double *pn_fps, double *pn_fps_dec, int max_num_frames, OutputSurfaceMemoryType mem_type) {
|
||||
int n_video_bytes = 0, n_frame_returned = 0, n_frame = 0;
|
||||
int n_pic_decoded = 0, decoded_pics = 0;
|
||||
uint8_t *p_video = nullptr;
|
||||
int64_t pts = 0;
|
||||
double total_dec_time = 0.0;
|
||||
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, &decoded_pics);
|
||||
n_frame += n_frame_returned;
|
||||
n_pic_decoded += decoded_pics;
|
||||
if (max_num_frames && max_num_frames <= n_frame) {
|
||||
break;
|
||||
}
|
||||
} while (n_video_bytes);
|
||||
if (mem_type == OUT_SURFACE_MEM_NOT_MAPPED) {
|
||||
p_dec->WaitForDecodeCompletion();
|
||||
}
|
||||
|
||||
auto end_time = std::chrono::high_resolution_clock::now();
|
||||
auto time_per_decode = std::chrono::duration<double, std::milli>(end_time - start_time).count();
|
||||
auto session_overhead = p_dec->GetDecoderSessionOverHead(std::this_thread::get_id());
|
||||
// Calculate average decoding time
|
||||
total_dec_time = time_per_decode - session_overhead;
|
||||
double average_output_time = total_dec_time / n_frame;
|
||||
double average_decoding_time = total_dec_time / n_pic_decoded;
|
||||
double n_fps = 1000 / average_output_time;
|
||||
double n_fps_dec = 1000 / average_decoding_time;
|
||||
*pn_fps = n_fps;
|
||||
*pn_fps_dec = n_fps_dec;
|
||||
*pn_frame = n_frame;
|
||||
*pn_pic_dec = n_pic_decoded;
|
||||
}
|
||||
|
||||
void ShowHelpAndExit(const char *option = NULL) {
|
||||
std::cout << "Options:" << std::endl
|
||||
<< "-i Input File Path - required" << std::endl
|
||||
<< "-t Number of threads (>= 1) - optional; default: 1" << std::endl
|
||||
<< "-d Device ID (>= 0) - optional; default: 0" << std::endl
|
||||
<< "-z Force zero latency (decoded frames will be flushed out for display immediately) - optional" << std::endl
|
||||
<< "-disp_delay -specify the number of frames to be delayed for display; optional; default: 1" << std::endl
|
||||
<< "-m Memory type (integer values between 0 to 3: specifies where to store the decoded output:" << std::endl
|
||||
<< " 0 = decoded output will be in internal interopped memory," << std::endl
|
||||
<< " 1 = decoded output will be copied to a separate device memory," << std::endl
|
||||
<< " 2 = decoded output will be copied to a separate host memory," << std::endl
|
||||
<< " 3 = decoded output will not be available (decode only)) - optional; default: 3" << std::endl;
|
||||
exit(0);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
|
||||
std::string input_file_path;
|
||||
int device_id = 0;
|
||||
int n_thread = 1;
|
||||
Rect *p_crop_rect = nullptr;
|
||||
OutputSurfaceMemoryType mem_type = OUT_SURFACE_MEM_NOT_MAPPED; // set to decode only for performance
|
||||
bool b_force_zero_latency = false;
|
||||
uint32_t max_num_frames = 0; // max number of frames to be decoded. default value is 0, meaning decode the entire stream
|
||||
int disp_delay = 1;
|
||||
int backend = 0;
|
||||
|
||||
// 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_file_path = argv[i];
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-t")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-t");
|
||||
}
|
||||
n_thread = atoi(argv[i]);
|
||||
if (n_thread <= 0) {
|
||||
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], "-disp_delay")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-disp_delay");
|
||||
}
|
||||
disp_delay = atoi(argv[i]);
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-f")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-d");
|
||||
}
|
||||
max_num_frames = atoi(argv[i]);
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-z")) {
|
||||
if (i == argc) {
|
||||
ShowHelpAndExit("-z");
|
||||
}
|
||||
b_force_zero_latency = 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], "-backend")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-backend");
|
||||
}
|
||||
backend = atoi(argv[i]);
|
||||
continue;
|
||||
}
|
||||
ShowHelpAndExit(argv[i]);
|
||||
}
|
||||
|
||||
try {
|
||||
// TODO: Change this block to use VCN query API
|
||||
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) {
|
||||
ROCDEC_ERR("ERROR: didn't find any GPU!");
|
||||
return -1;
|
||||
}
|
||||
|
||||
hip_status = hipGetDeviceProperties(&hip_dev_prop, device_id);
|
||||
if (hip_status != hipSuccess) {
|
||||
ROCDEC_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::vector<std::unique_ptr<VideoDemuxer>> v_demuxer;
|
||||
std::vector<std::unique_ptr<RocVideoDecoder>> v_viddec;
|
||||
std::vector<int> v_device_id(n_thread);
|
||||
|
||||
int hip_vis_dev_count = 0;
|
||||
GetEnvVar("HIP_VISIBLE_DEVICES", hip_vis_dev_count);
|
||||
|
||||
std::size_t found_file = input_file_path.find_last_of('/');
|
||||
std::cout << "info: Input file: " << input_file_path.substr(found_file + 1) << std::endl;
|
||||
std::cout << "info: Number of threads: " << n_thread << std::endl;
|
||||
|
||||
for (int i = 0; i < n_thread; i++) {
|
||||
std::unique_ptr<VideoDemuxer> demuxer(new VideoDemuxer(input_file_path.c_str()));
|
||||
rocDecVideoCodec rocdec_codec_id = AVCodec2RocDecVideoCodec(demuxer->GetCodecID());
|
||||
if (!hip_vis_dev_count) {
|
||||
if (device_id % 2 == 0)
|
||||
v_device_id[i] = (i % 2 == 0) ? device_id : device_id + sd;
|
||||
else
|
||||
v_device_id[i] = (i % 2 == 0) ? device_id - sd : device_id;
|
||||
} else {
|
||||
v_device_id[i] = i % hip_vis_dev_count;
|
||||
}
|
||||
std::unique_ptr<RocVideoDecoder> dec;
|
||||
if (!backend) { // gpu backend
|
||||
dec = std::make_unique<RocVideoDecoder>(v_device_id[i], mem_type, rocdec_codec_id, b_force_zero_latency, p_crop_rect, false, disp_delay);
|
||||
} else {
|
||||
#if ENABLE_HOST_DECODE
|
||||
std::cout << "info: RocDecode is using CPU backend!" << std::endl;
|
||||
uint32_t max_width = demuxer->GetWidth();
|
||||
uint32_t max_height =demuxer->GetHeight();
|
||||
mem_type = OUT_SURFACE_MEM_HOST_COPIED;
|
||||
dec = std::make_unique<FFMpegVideoDecoder>(v_device_id[i], mem_type, rocdec_codec_id, b_force_zero_latency, p_crop_rect, false, disp_delay, max_width, max_height);
|
||||
#else
|
||||
std::cout << "Error: RocDecode HOST library is not found and backend is not supported!" << std::endl;
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (!dec->CodecSupported(v_device_id[i], rocdec_codec_id, demuxer->GetBitDepth())) {
|
||||
std::cerr << "Codec not supported on GPU, skipping this file!" << std::endl;
|
||||
continue;
|
||||
}
|
||||
v_demuxer.push_back(std::move(demuxer));
|
||||
v_viddec.push_back(std::move(dec));
|
||||
}
|
||||
|
||||
float total_fps = 0;
|
||||
float total_fps_dec = 0;
|
||||
std::vector<std::thread> v_thread;
|
||||
std::vector<double> v_fps, v_fps_dec;
|
||||
std::vector<int> v_frame, v_frame_dec;
|
||||
v_fps.resize(n_thread, 0);
|
||||
v_fps_dec.resize(n_thread, 0);
|
||||
v_frame.resize(n_thread, 0);
|
||||
v_frame_dec.resize(n_thread, 0);
|
||||
int n_total = 0;
|
||||
int n_total_dec = 0;
|
||||
OutputSurfaceInfo *p_surf_info;
|
||||
|
||||
std::string device_name;
|
||||
int pci_bus_id, pci_domain_id, pci_device_id;
|
||||
|
||||
for (int i = 0; i < n_thread; i++) {
|
||||
v_viddec[i]->GetDeviceinfo(device_name, gcn_arch_name, pci_bus_id, pci_domain_id, pci_device_id);
|
||||
if (!backend) std::cout << "info: stream " << i << " using GPU device " << v_device_id[i] << " - " << 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::cout << "info: decoding started for thread " << i << " ,please wait!" << std::endl;
|
||||
}
|
||||
|
||||
for (int i = 0; i < n_thread; i++) {
|
||||
v_thread.push_back(std::thread(DecProc, v_viddec[i].get(), v_demuxer[i].get(), &v_frame[i], &v_frame_dec[i], &v_fps[i], &v_fps_dec[i], max_num_frames, mem_type));
|
||||
}
|
||||
|
||||
for (int i = 0; i < n_thread; i++) {
|
||||
v_thread[i].join();
|
||||
total_fps += v_fps[i];
|
||||
total_fps_dec += v_fps_dec[i];
|
||||
n_total += v_frame[i];
|
||||
n_total_dec += v_frame_dec[i];
|
||||
}
|
||||
|
||||
std::cout << "info: Total pictures decoded: " << n_total_dec << std::endl;
|
||||
std::cout << "info: Total frames output/displayed: " << n_total << std::endl;
|
||||
std::cout << "info: avg decoding time per picture: " << 1000 / total_fps_dec << " ms" << std::endl;
|
||||
std::cout << "info: avg decode FPS: " << total_fps_dec << std::endl;
|
||||
std::cout << "info: avg output/display time per frame: " << 1000 / total_fps << " ms" << std::endl;
|
||||
std::cout << "info: avg output/display FPS: " << total_fps << std::endl;
|
||||
} catch (const std::exception &ex) {
|
||||
std::cout << ex.what() << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
################################################################################
|
||||
# Copyright (c) 2023 - 2026 Advanced Micro Devices, Inc.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
# ROCM Path
|
||||
if(DEFINED ENV{ROCM_PATH})
|
||||
set(ROCM_PATH $ENV{ROCM_PATH} CACHE PATH "Default ROCm installation path")
|
||||
elseif(ROCM_PATH)
|
||||
message("-- INFO:ROCM_PATH Set -- ${ROCM_PATH}")
|
||||
else()
|
||||
set(ROCM_PATH /opt/rocm CACHE PATH "Default ROCm installation path")
|
||||
endif()
|
||||
# Set AMD Clang as default compiler
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED On)
|
||||
set(CMAKE_CXX_EXTENSIONS ON)
|
||||
if (NOT DEFINED CMAKE_CXX_COMPILER)
|
||||
set(CMAKE_C_COMPILER ${ROCM_PATH}/lib/llvm/bin/amdclang)
|
||||
set(CMAKE_CXX_COMPILER ${ROCM_PATH}/lib/llvm/bin/amdclang++)
|
||||
endif()
|
||||
|
||||
project(videodecodepicfiles)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/../../cmake)
|
||||
list(APPEND CMAKE_PREFIX_PATH ${ROCM_PATH}/lib/cmake)
|
||||
|
||||
# rocdecode sample build type
|
||||
set(DEFAULT_BUILD_TYPE "Release")
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE "${DEFAULT_BUILD_TYPE}" CACHE STRING "rocdecode Default Build Type" FORCE)
|
||||
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release")
|
||||
endif()
|
||||
if(CMAKE_BUILD_TYPE MATCHES Debug)
|
||||
# -O0 -- Don't Optimize output file
|
||||
# -gdwarf-4 -- generate debugging information, dwarf-4 for making valgrind work
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -gdwarf-4")
|
||||
else()
|
||||
# -O3 -- Optimize output file
|
||||
# -DNDEBUG -- turn off asserts
|
||||
# -fPIC -- Generate position-independent code if possible
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -DNDEBUG -fPIC")
|
||||
endif()
|
||||
|
||||
set (HIP_PLATFORM amd CACHE STRING "HIP platform")
|
||||
find_package(HIP QUIET)
|
||||
find_package(rocdecode QUIET)
|
||||
find_package(rocdecode-host 1.0.0 QUIET)
|
||||
find_package(rocprofiler-register QUIET)
|
||||
find_package(FFmpeg QUIET)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
if(HIP_FOUND AND FFMPEG_FOUND AND rocdecode_FOUND AND Threads_FOUND AND rocprofiler-register_FOUND)
|
||||
# HIP
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} hip::host)
|
||||
# FFMPEG
|
||||
include_directories(${AVUTIL_INCLUDE_DIR} ${AVCODEC_INCLUDE_DIR}
|
||||
${AVFORMAT_INCLUDE_DIR})
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} ${FFMPEG_LIBRARIES})
|
||||
# rocdecode and utils
|
||||
include_directories (${rocdecode_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/.. ${CMAKE_CURRENT_SOURCE_DIR}/../../utils ${CMAKE_CURRENT_SOURCE_DIR}/../../utils/rocvideodecode)
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} rocdecode::rocdecode)
|
||||
# threads
|
||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} Threads::Threads)
|
||||
# rocprofiler-register
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} rocprofiler-register::rocprofiler-register)
|
||||
# sample app exe
|
||||
list(APPEND SOURCES ${PROJECT_SOURCE_DIR} videodecodepicfiles.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../utils/rocvideodecode/roc_video_dec.cpp)
|
||||
if(rocdecode-host_FOUND)
|
||||
include_directories (${CMAKE_CURRENT_SOURCE_DIR}/../../utils/ffmpegvideodecode)
|
||||
list(APPEND SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/../../utils/ffmpegvideodecode/ffmpeg_video_dec.cpp)
|
||||
endif()
|
||||
add_executable(${PROJECT_NAME} ${SOURCES})
|
||||
if(rocdecode-host_FOUND)
|
||||
# rocdecode-host
|
||||
include_directories(${rocdecode-host_INCLUDE_DIR})
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} rocdecode::rocdecode-host)
|
||||
target_compile_definitions(${PROJECT_NAME} PUBLIC ENABLE_HOST_DECODE=1)
|
||||
else()
|
||||
target_compile_definitions(${PROJECT_NAME} PUBLIC ENABLE_HOST_DECODE=0)
|
||||
endif()
|
||||
# FFMPEG multi-version support
|
||||
if(_FFMPEG_AVCODEC_VERSION VERSION_LESS_EQUAL 58.134.100)
|
||||
target_compile_definitions(${PROJECT_NAME} PUBLIC USE_AVCODEC_GREATER_THAN_58_134=0)
|
||||
else()
|
||||
target_compile_definitions(${PROJECT_NAME} PUBLIC USE_AVCODEC_GREATER_THAN_58_134=1)
|
||||
endif()
|
||||
target_link_libraries(${PROJECT_NAME} ${LINK_LIBRARY_LIST})
|
||||
else()
|
||||
message("-- ERROR!: ${PROJECT_NAME} excluded! please install all the dependencies and try again!")
|
||||
if (NOT HIP_FOUND)
|
||||
message(FATAL_ERROR "-- ERROR!: HIP Not Found! - please install ROCm and HIP!")
|
||||
endif()
|
||||
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()
|
||||
if (NOT Threads_FOUND)
|
||||
message(FATAL_ERROR "-- ERROR!: Threads Not Found! - please install Threads!")
|
||||
endif()
|
||||
if (NOT rocprofiler-register_FOUND)
|
||||
message(FATAL_ERROR "-- ERROR!: rocprofiler-register Not Found! - please install rocprofiler-register!")
|
||||
endif()
|
||||
endif()
|
||||
@@ -0,0 +1,33 @@
|
||||
# Video decode picture files sample
|
||||
|
||||
The video decode picture files sample illustrates decoding an elementary video stream which is stored in multiple files with each file containing bitstream data of a coded picutre. This sample can be configured with a device ID and optionally able to dump the output to a file. This sample uses the high-level RocVideoDecoder class which connects both the video parser and Rocdecoder. This process repeats in a loop until all frames have been decoded.
|
||||
|
||||
## Prerequisites:
|
||||
|
||||
* Install [rocDecode](../../README.md#build-and-install-instructions)
|
||||
|
||||
## Build
|
||||
|
||||
```shell
|
||||
mkdir video_decode_pic_files && cd video_decode_pic_files
|
||||
cmake ../
|
||||
make -j
|
||||
```
|
||||
|
||||
## Run
|
||||
|
||||
```shell
|
||||
./videodecodepicfiles -i <Input picture files [required]>
|
||||
-codec <Codec type (0: HEVC, 1: AVC; 2: AV1; 3: VP9) - [required]>
|
||||
-l <Number of iterations [optional - default: 1]>
|
||||
-o <output path to save decoded YUV frames [optional]>
|
||||
-d <GPU device ID - 0:device 0 / 1:device 1/ ... [optional - default:0]>
|
||||
-f <Number of decoded frames - specify the number of pictures to be decoded [optional]>
|
||||
-z <force_zero_latency - Decoded frames will be flushed out for display immediately [optional]>
|
||||
-disp_delay <display delay - specify the number of frames to be delayed for display [optional - default: 1]>
|
||||
-sei <extract SEI messages [optional]>
|
||||
-md5 <generate MD5 message digest on the decoded YUV image sequence [optional]>
|
||||
-md5_check MD5_File_Path <generate MD5 message digest on the decoded YUV image sequence and compare to the reference MD5 string in a file [optional]>
|
||||
-crop <crop rectangle for output (not used when using interopped decoded frame) [optional - default: 0,0,0,0]>
|
||||
-m <output_surface_memory_type - decoded surface memory [optional - default: 0][0 : OUT_SURFACE_MEM_DEV_INTERNAL/ 1 : OUT_SURFACE_MEM_DEV_COPIED/ 2 : OUT_SURFACE_MEM_HOST_COPIED/3 : OUT_SURFACE_MEM_NOT_MAPPED]>
|
||||
```
|
||||
@@ -0,0 +1,397 @@
|
||||
/*
|
||||
Copyright (c) 2023 - 2026 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 <iostream>
|
||||
#include <fstream>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <iomanip>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
#include <sys/stat.h>
|
||||
#include <libgen.h>
|
||||
#include "video_demuxer.h"
|
||||
#include "rocdecode/roc_bitstream_reader.h"
|
||||
#include "roc_video_dec.h"
|
||||
#include "common.h"
|
||||
#if ENABLE_HOST_DECODE
|
||||
#include "ffmpeg_video_dec.h"
|
||||
#endif
|
||||
|
||||
void ShowHelpAndExit(const char *option = NULL) {
|
||||
std::cout << "Options:" << std::endl
|
||||
<< "-i Input picture files - required" << std::endl
|
||||
<< "-codec Codec type (0: HEVC, 1: AVC; 2: AV1; 3: VP9) - required" << std::endl
|
||||
<< "-l Number of iterations - optional; default: 1" << std::endl
|
||||
<< "-o Output File Path - dumps output if requested; optional" << std::endl
|
||||
<< "-d GPU device ID (0 for the first device, 1 for the second, etc.); optional; default: 0" << std::endl
|
||||
<< "-backend backend (0 for GPU, 1 CPU-FFMpeg, 2 CPU-FFMpeg No threading); optional; default: 0" << std::endl
|
||||
<< "-f Number of decoded frames - specify the number of pictures to be decoded; optional" << std::endl
|
||||
<< "-z force_zero_latency (force_zero_latency, Decoded frames will be flushed out for display immediately); optional;" << std::endl
|
||||
<< "-disp_delay -specify the number of frames to be delayed for display; optional; default: 1" << std::endl
|
||||
<< "-md5 generate MD5 message digest on the decoded YUV image sequence; optional;" << std::endl
|
||||
<< "-md5_check MD5 File Path - generate MD5 message digest on the decoded YUV image sequence and compare to the reference MD5 string in a file; optional;" << std::endl
|
||||
<< "-crop crop rectangle for output (not used when using interopped decoded frame); optional; default: 0" << std::endl
|
||||
<< "-m output_surface_memory_type - decoded surface memory; optional; default - 0"
|
||||
<< " [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;
|
||||
exit(0);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
|
||||
std::vector<const char*> file_names;
|
||||
std::string output_file_path, md5_file_path;
|
||||
std::fstream ref_md5_file;
|
||||
int codec_type = 0;
|
||||
int num_iterations = 1;
|
||||
int dump_output_frames = 0;
|
||||
int device_id = 0;
|
||||
int disp_delay = 1;
|
||||
int backend = 0;
|
||||
bool b_force_zero_latency = false; // false by default: enabling this option might affect decoding performance
|
||||
bool b_extract_sei_messages = false;
|
||||
bool b_generate_md5 = false;
|
||||
bool b_md5_check = false;
|
||||
bool b_flush_frames_during_reconfig = true;
|
||||
Rect crop_rect = {};
|
||||
Rect *p_crop_rect = nullptr;
|
||||
OutputSurfaceMemoryType mem_type = OUT_SURFACE_MEM_DEV_INTERNAL; // set to internal
|
||||
ReconfigParams reconfig_params = { 0 };
|
||||
ReconfigDumpFileStruct reconfig_user_struct = { 0 };
|
||||
uint32_t num_decoded_frames = 0; // default value is 0, meaning decode the entire stream
|
||||
|
||||
// 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");
|
||||
}
|
||||
for (; i < argc; i++) {
|
||||
file_names.push_back(argv[i]);
|
||||
if (i + 1 < argc) {
|
||||
if (argv[i + 1][0] == '-') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-codec")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-codec");
|
||||
}
|
||||
codec_type = atoi(argv[i]);
|
||||
if (codec_type < 0 || codec_type > 3) {
|
||||
ShowHelpAndExit("-codec");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-l")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-l");
|
||||
}
|
||||
num_iterations = atoi(argv[i]);
|
||||
if (num_iterations < 1) {
|
||||
ShowHelpAndExit("-l");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-o")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-o");
|
||||
}
|
||||
output_file_path = argv[i];
|
||||
dump_output_frames = 1;
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-backend")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-backend");
|
||||
}
|
||||
backend = atoi(argv[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!strcmp(argv[i], "-d")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-d");
|
||||
}
|
||||
device_id = atoi(argv[i]);
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-disp_delay")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-disp_delay");
|
||||
}
|
||||
disp_delay = atoi(argv[i]);
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-f")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-f");
|
||||
}
|
||||
num_decoded_frames = atoi(argv[i]);
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-z")) {
|
||||
if (i == argc) {
|
||||
ShowHelpAndExit("-z");
|
||||
}
|
||||
b_force_zero_latency = true;
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-md5")) {
|
||||
if (i == argc) {
|
||||
ShowHelpAndExit("-md5");
|
||||
}
|
||||
b_generate_md5 = true;
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-md5_check")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-md5_check");
|
||||
}
|
||||
b_generate_md5 = true;
|
||||
b_md5_check = true;
|
||||
md5_file_path = argv[i];
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-crop")) {
|
||||
if (++i == argc || 4 != sscanf(argv[i], "%d,%d,%d,%d", &crop_rect.left, &crop_rect.top, &crop_rect.right, &crop_rect.bottom)) {
|
||||
ShowHelpAndExit("-crop");
|
||||
}
|
||||
if ((crop_rect.right - crop_rect.left) % 2 == 1 || (crop_rect.bottom - crop_rect.top) % 2 == 1) {
|
||||
std::cout << "output crop rectangle must have width and height of even numbers" << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
p_crop_rect = &crop_rect;
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-m")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-m");
|
||||
}
|
||||
mem_type = static_cast<OutputSurfaceMemoryType>(atoi(argv[i]));
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "flush")) {
|
||||
b_flush_frames_during_reconfig = atoi(argv[i]) ? true : false;
|
||||
continue;
|
||||
}
|
||||
ShowHelpAndExit(argv[i]);
|
||||
}
|
||||
|
||||
try {
|
||||
std::cout << "Total frame number = " << file_names.size() << std::endl;
|
||||
rocDecVideoCodec rocdec_codec_id;
|
||||
switch (codec_type) {
|
||||
case 0:
|
||||
rocdec_codec_id = rocDecVideoCodec_HEVC;
|
||||
break;
|
||||
case 1:
|
||||
rocdec_codec_id = rocDecVideoCodec_AVC;
|
||||
break;
|
||||
case 2:
|
||||
rocdec_codec_id = rocDecVideoCodec_AV1;
|
||||
break;
|
||||
case 3:
|
||||
rocdec_codec_id = rocDecVideoCodec_VP9;
|
||||
break;
|
||||
default:
|
||||
std::cerr << "Unsupported stream codec type." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
RocVideoDecoder *viddec;
|
||||
if (!backend) // gpu backend
|
||||
viddec = new RocVideoDecoder(device_id, mem_type, rocdec_codec_id, b_force_zero_latency, p_crop_rect, b_extract_sei_messages, disp_delay);
|
||||
else {
|
||||
std::cout << "info: RocDecode is using CPU backend!" << std::endl;
|
||||
bool use_threading = false;
|
||||
if (mem_type == OUT_SURFACE_MEM_DEV_INTERNAL) mem_type = OUT_SURFACE_MEM_DEV_COPIED; // mem_type internal is not supported in this mode
|
||||
if (backend == 1) {
|
||||
viddec = new FFMpegVideoDecoder(device_id, mem_type, rocdec_codec_id, b_force_zero_latency, p_crop_rect, b_extract_sei_messages, disp_delay);
|
||||
} else
|
||||
viddec = new FFMpegVideoDecoder(device_id, mem_type, rocdec_codec_id, b_force_zero_latency, p_crop_rect, b_extract_sei_messages, disp_delay, true);
|
||||
}
|
||||
|
||||
std::string device_name, gcn_arch_name;
|
||||
int pci_bus_id, pci_domain_id, pci_device_id;
|
||||
|
||||
viddec->GetDeviceinfo(device_name, gcn_arch_name, pci_bus_id, pci_domain_id, pci_device_id);
|
||||
std::cout << "info: Using GPU device " << 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::cout << "info: decoding started, please wait!" << std::endl;
|
||||
|
||||
int n_video_bytes = 0, n_frame_returned = 0, n_frame = 0;
|
||||
int n_pic_decoded = 0, decoded_pics = 0;
|
||||
std::vector<uint8_t> bitstream(5 * 1024 * 1024);
|
||||
int pkg_flags = 0;
|
||||
uint8_t *pframe = nullptr;
|
||||
int64_t pts = 0;
|
||||
OutputSurfaceInfo *surf_info;
|
||||
uint32_t width, height;
|
||||
double total_dec_time = 0;
|
||||
bool first_frame = true;
|
||||
MD5Generator *md5_generator = nullptr;
|
||||
|
||||
// initialize reconfigure params: the following is configured to dump to output which is relevant for this sample
|
||||
reconfig_params.p_fn_reconfigure_flush = ReconfigureFlushCallback;
|
||||
reconfig_user_struct.b_dump_frames_to_file = dump_output_frames;
|
||||
reconfig_user_struct.output_file_name = output_file_path;
|
||||
reconfig_params.reconfig_flush_mode = RECONFIG_FLUSH_MODE_NONE;
|
||||
if (dump_output_frames) {
|
||||
reconfig_params.reconfig_flush_mode |= RECONFIG_FLUSH_MODE_DUMP_TO_FILE;
|
||||
}
|
||||
if (b_generate_md5) {
|
||||
reconfig_params.reconfig_flush_mode |= RECONFIG_FLUSH_MODE_CALCULATE_MD5;
|
||||
}
|
||||
reconfig_params.p_reconfig_user_struct = &reconfig_user_struct;
|
||||
|
||||
if (b_generate_md5) {
|
||||
md5_generator = new MD5Generator();
|
||||
md5_generator->InitMd5();
|
||||
reconfig_user_struct.md5_generator_handle = static_cast<void*>(md5_generator);
|
||||
}
|
||||
viddec->SetReconfigParams(&reconfig_params);
|
||||
|
||||
for (int i = 0; i < num_iterations; i++) {
|
||||
int num_frames_decoded_in_loop = 0;
|
||||
pkg_flags = 0;
|
||||
for ( const char* file_name : file_names) {
|
||||
std::ifstream in_file(file_name, std::ios::binary);
|
||||
if (!in_file) {
|
||||
std::cerr << "Error: Failed to open " << file_name << " for reading." << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
in_file.seekg(0, std::ios::end);
|
||||
n_video_bytes = in_file.tellg();
|
||||
if (n_video_bytes > bitstream.size()) {
|
||||
bitstream.resize(n_video_bytes);
|
||||
}
|
||||
in_file.seekg(0, std::ios::beg);
|
||||
if (!in_file.read(reinterpret_cast<char*>(bitstream.data()), n_video_bytes)) {
|
||||
std::cerr << "Error: Failed to read " << file_name << "." << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
// Close the file
|
||||
in_file.close();
|
||||
|
||||
auto start_time = std::chrono::high_resolution_clock::now();
|
||||
if (num_frames_decoded_in_loop + 1 == file_names.size()) {
|
||||
pkg_flags |= ROCDEC_PKT_ENDOFSTREAM;
|
||||
}
|
||||
n_frame_returned = viddec->DecodeFrame(bitstream.data(), n_video_bytes, pkg_flags, pts, &decoded_pics);
|
||||
num_frames_decoded_in_loop++;
|
||||
|
||||
if (!n_frame && !viddec->GetOutputSurfaceInfo(&surf_info)) {
|
||||
std::cerr << "Error: Failed to get Output Surface Info!" << std::endl;
|
||||
break;
|
||||
}
|
||||
for (int i = 0; i < n_frame_returned; i++) {
|
||||
pframe = viddec->GetFrame(&pts);
|
||||
if (b_generate_md5 && pframe) {
|
||||
md5_generator->UpdateMd5ForFrame(pframe, surf_info);
|
||||
}
|
||||
if (dump_output_frames && mem_type != OUT_SURFACE_MEM_NOT_MAPPED) {
|
||||
viddec->SaveFrameToFile(output_file_path, pframe, surf_info);
|
||||
}
|
||||
// release frame
|
||||
viddec->ReleaseFrame(pts);
|
||||
}
|
||||
auto end_time = std::chrono::high_resolution_clock::now();
|
||||
auto time_per_decode = std::chrono::duration<double, std::milli>(end_time - start_time).count();
|
||||
total_dec_time += time_per_decode;
|
||||
n_frame += n_frame_returned;
|
||||
n_pic_decoded += decoded_pics;
|
||||
if (num_decoded_frames && num_decoded_frames <= n_frame) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
n_frame += viddec->GetNumOfFlushedFrames();
|
||||
|
||||
std::cout << "info: Total pictures decoded: " << n_pic_decoded << std::endl;
|
||||
std::cout << "info: Total frames output/displayed: " << n_frame << std::endl;
|
||||
if (!dump_output_frames) {
|
||||
std::cout << "info: avg decoding time per picture: " << total_dec_time / n_pic_decoded << " ms" <<std::endl;
|
||||
std::cout << "info: avg decode FPS: " << (n_pic_decoded / total_dec_time) * 1000 << std::endl;
|
||||
std::cout << "info: avg output/display time per frame: " << total_dec_time / n_frame << " ms" <<std::endl;
|
||||
std::cout << "info: avg output/display FPS: " << (n_frame / total_dec_time) * 1000 << 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 {
|
||||
std::cout << "info: saved frames into " << output_file_path << std::endl;
|
||||
}
|
||||
}
|
||||
if (b_generate_md5) {
|
||||
uint8_t *digest;
|
||||
md5_generator->FinalizeMd5(&digest);
|
||||
std::cout << "MD5 message digest: ";
|
||||
for (int i = 0; i < 16; i++) {
|
||||
std::cout << std::setfill('0') << std::setw(2) << std::hex << static_cast<int>(digest[i]);
|
||||
}
|
||||
std::cout << std::endl;
|
||||
if (b_md5_check) {
|
||||
std::string ref_md5_string(33, 0);
|
||||
uint8_t ref_md5[16];
|
||||
ref_md5_file.open(md5_file_path.c_str(), std::ios::in);
|
||||
if ((ref_md5_file.rdstate() & std::ifstream::failbit) != 0) {
|
||||
std::cerr << "Failed to open MD5 file." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
ref_md5_file.getline(ref_md5_string.data(), ref_md5_string.length());
|
||||
if ((ref_md5_file.rdstate() & std::ifstream::badbit) != 0) {
|
||||
std::cerr << "Failed to read MD5 digest string." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
for (int i = 0; i < 16; i++) {
|
||||
std::string part = ref_md5_string.substr(i * 2, 2);
|
||||
ref_md5[i] = std::stoi(part, nullptr, 16);
|
||||
}
|
||||
if (memcmp(digest, ref_md5, 16) == 0) {
|
||||
std::cout << "MD5 digest matches the reference MD5 digest: ";
|
||||
} else {
|
||||
std::cout << "MD5 digest does not match the reference MD5 digest: ";
|
||||
}
|
||||
std::cout << ref_md5_string.c_str() << std::endl;
|
||||
ref_md5_file.close();
|
||||
}
|
||||
delete md5_generator;
|
||||
}
|
||||
} catch (const std::exception &ex) {
|
||||
std::cout << ex.what() << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
################################################################################
|
||||
# Copyright (c) 2023 - 2026 Advanced Micro Devices, Inc.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
# ROCM Path
|
||||
if(DEFINED ENV{ROCM_PATH})
|
||||
set(ROCM_PATH $ENV{ROCM_PATH} CACHE PATH "Default ROCm installation path")
|
||||
elseif(ROCM_PATH)
|
||||
message("-- INFO:ROCM_PATH Set -- ${ROCM_PATH}")
|
||||
else()
|
||||
set(ROCM_PATH /opt/rocm CACHE PATH "Default ROCm installation path")
|
||||
endif()
|
||||
|
||||
# Check if lib/rocm_sysdeps/lib exists in the ROCm path which indicates ROCm installation via TheRock
|
||||
set(USING_THE_ROCK OFF)
|
||||
if(EXISTS "${ROCM_PATH}/lib/rocm_sysdeps/lib")
|
||||
set(USING_THE_ROCK ON)
|
||||
endif()
|
||||
|
||||
if(USING_THE_ROCK)
|
||||
if(NOT DEFINED ENV{HIP_DEVICE_LIB_PATH})
|
||||
set(ENV{HIP_DEVICE_LIB_PATH} ${ROCM_PATH}/lib/llvm/amdgcn/bitcode)
|
||||
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "HIP_DEVICE_LIB_PATH=${ROCM_PATH}/lib/llvm/amdgcn/bitcode")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Set AMD Clang as default compiler
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED On)
|
||||
set(CMAKE_CXX_EXTENSIONS ON)
|
||||
if (NOT DEFINED CMAKE_CXX_COMPILER)
|
||||
set(CMAKE_C_COMPILER ${ROCM_PATH}/lib/llvm/bin/amdclang)
|
||||
set(CMAKE_CXX_COMPILER ${ROCM_PATH}/lib/llvm/bin/amdclang++)
|
||||
endif()
|
||||
|
||||
project(videodecodergb)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/../../cmake)
|
||||
list(APPEND CMAKE_PREFIX_PATH ${ROCM_PATH}/lib/cmake ${ROCM_PATH}/share/rocmcmakebuildtools/cmake)
|
||||
|
||||
# rocdecode sample build type
|
||||
set(DEFAULT_BUILD_TYPE "Release")
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE "${DEFAULT_BUILD_TYPE}" CACHE STRING "rocdecode Default Build Type" FORCE)
|
||||
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release")
|
||||
endif()
|
||||
if(CMAKE_BUILD_TYPE MATCHES Debug)
|
||||
# -O0 -- Don't Optimize output file
|
||||
# -gdwarf-4 -- generate debugging information, dwarf-4 for making valgrind work
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -gdwarf-4")
|
||||
else()
|
||||
# -O3 -- Optimize output file
|
||||
# -DNDEBUG -- turn off asserts
|
||||
# -fPIC -- Generate position-independent code if possible
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -DNDEBUG -fPIC")
|
||||
endif()
|
||||
|
||||
# Set supported GPU Targets
|
||||
if(NOT GPU_TARGETS AND NOT AMDGPU_TARGETS)
|
||||
find_package(ROCmCMakeBuildTools QUIET)
|
||||
if(NOT ROCmCMakeBuildTools_FOUND)
|
||||
find_package(ROCM QUIET)
|
||||
endif()
|
||||
include(ROCMCheckTargetIds OPTIONAL RESULT_VARIABLE HAS_ROCM_CHECK_TARGET_IDS)
|
||||
|
||||
set(OPTIONAL_GPU_TARGETS "gfx950;gfx1151;gfx1200;gfx1201")
|
||||
if(HAS_ROCM_CHECK_TARGET_IDS)
|
||||
rocm_check_target_ids(OPTIONAL_GPU_TARGETS_AVAILABLE TARGETS ${OPTIONAL_GPU_TARGETS})
|
||||
else() # if we don't have rocm_check_target_ids, just assume the targets are available
|
||||
set(OPTIONAL_GPU_TARGETS_AVAILABLE "${OPTIONAL_GPU_TARGETS}")
|
||||
endif()
|
||||
set(DEFAULT_GPU_TARGETS "gfx908;gfx90a;gfx942;gfx1030;gfx1031;gfx1032;gfx1100;gfx1101;gfx1102;${OPTIONAL_GPU_TARGETS_AVAILABLE}")
|
||||
endif()
|
||||
|
||||
# Set AMD GPU_TARGETS
|
||||
if((AMDGPU_TARGETS OR DEFINED ENV{AMDGPU_TARGETS}) AND (NOT GPU_TARGETS))
|
||||
message("-- ${Red}${PROJECT_NAME} DEPRECATION -- AMDGPU_TARGETS use is deprecated. Use GPU_TARGETS${ColourReset}")
|
||||
if(DEFINED ENV{AMDGPU_TARGETS})
|
||||
set(GPU_TARGETS $ENV{AMDGPU_TARGETS} CACHE STRING "List of specific machine types for library to target")
|
||||
else()
|
||||
set(GPU_TARGETS ${AMDGPU_TARGETS})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(DEFINED ENV{GPU_ARCHS})
|
||||
set(GPU_TARGETS $ENV{GPU_ARCHS} CACHE STRING "List of specific machine types for library to target")
|
||||
elseif(GPU_TARGETS)
|
||||
message("-- ${White}${PROJECT_NAME} -- GPU_TARGETS set with -D option${ColourReset}")
|
||||
else()
|
||||
set(GPU_TARGETS "${DEFAULT_GPU_TARGETS}" CACHE STRING "List of specific machine types for library to target")
|
||||
endif()
|
||||
message("-- ${White}${PROJECT_NAME} -- AMD GPU_TARGETS: ${GPU_TARGETS}${ColourReset}")
|
||||
|
||||
set(HIP_PLATFORM amd CACHE STRING "HIP platform")
|
||||
find_package(HIP QUIET)
|
||||
find_package(rocdecode QUIET)
|
||||
find_package(rocprofiler-register QUIET)
|
||||
find_package(FFmpeg QUIET)
|
||||
|
||||
if(HIP_FOUND AND FFMPEG_FOUND AND rocdecode_FOUND AND rocprofiler-register_FOUND)
|
||||
# HIP
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} hip::device)
|
||||
# FFMPEG
|
||||
include_directories(${AVUTIL_INCLUDE_DIR} ${AVCODEC_INCLUDE_DIR})
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} ${FFMPEG_LIBRARIES})
|
||||
# rocdecode and utils
|
||||
include_directories (${CMAKE_CURRENT_SOURCE_DIR}/../../utils ${rocdecode_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/../../utils/rocvideodecode)
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} rocdecode::rocdecode)
|
||||
# threads
|
||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
find_package(Threads REQUIRED)
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} Threads::Threads)
|
||||
# rocprofiler-register
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} rocprofiler-register::rocprofiler-register)
|
||||
|
||||
# sample app exe
|
||||
list(APPEND SOURCES ${PROJECT_SOURCE_DIR}
|
||||
videodecrgb.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../utils/rocvideodecode/roc_video_dec.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../utils/colorspace_kernels.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../utils/resize_kernels.cpp)
|
||||
|
||||
add_executable(${PROJECT_NAME} ${SOURCES})
|
||||
target_link_libraries(${PROJECT_NAME} ${LINK_LIBRARY_LIST})
|
||||
# FFMPEG multi-version support
|
||||
if(_FFMPEG_AVCODEC_VERSION VERSION_LESS_EQUAL 58.134.100)
|
||||
target_compile_definitions(${PROJECT_NAME} PUBLIC USE_AVCODEC_GREATER_THAN_58_134=0)
|
||||
else()
|
||||
target_compile_definitions(${PROJECT_NAME} PUBLIC USE_AVCODEC_GREATER_THAN_58_134=1)
|
||||
endif()
|
||||
else()
|
||||
message("-- ERROR!: ${PROJECT_NAME} excluded! please install all the dependencies and try again!")
|
||||
if (NOT HIP_FOUND)
|
||||
message(FATAL_ERROR "-- ERROR!: HIP Not Found! - please install ROCm and HIP!")
|
||||
endif()
|
||||
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()
|
||||
if (NOT rocprofiler-register_FOUND)
|
||||
message(FATAL_ERROR "-- ERROR!: rocprofiler-register Not Found! - please install rocprofiler-register!")
|
||||
endif()
|
||||
endif()
|
||||
@@ -0,0 +1,36 @@
|
||||
# Video decode RGB sample
|
||||
|
||||
This sample illustrates the FFMPEG demuxer to get the individual frames which are then decoded using rocDecode API and optionally color-converted using custom HIP kernels on AMD hardware. This sample converts decoded YUV output to one of the RGB or BGR formats(24bit, 32bit, 464bit) in a separate thread allowing it to run both VCN hardware and compute engine in parallel.
|
||||
|
||||
This sample uses HIP kernels to showcase the color conversion. Whenever a frame is ready after decoding, the `ColorSpaceConversionThread` is notified and can be used for post-processing.
|
||||
|
||||
## Prerequisites:
|
||||
|
||||
* Install [rocDecode](../../README.md#build-and-install-instructions)
|
||||
|
||||
* [FFMPEG](https://ffmpeg.org/about.html)
|
||||
|
||||
* On `Ubuntu`
|
||||
|
||||
```shell
|
||||
sudo apt install libavcodec-dev libavformat-dev libavutil-dev
|
||||
```
|
||||
|
||||
* On `RHEL`/`SLES` - install ffmpeg development packages manually or use [rocDecode-setup.py](../../rocDecode-setup.py) script
|
||||
|
||||
## Build
|
||||
|
||||
```shell
|
||||
mkdir video_decode_rgb_sample && cd video_decode_rgb_sample
|
||||
cmake ../
|
||||
make -j
|
||||
```
|
||||
|
||||
## Run
|
||||
|
||||
```shell
|
||||
./videodecodergb -i <input video file - required>
|
||||
-o <optional; output path to save decoded YUV frames>
|
||||
-d <GPU device ID, 0 for the first device, 1 for the second device, etc>
|
||||
-of <optional: output format bgr, bgra, bgr48, bgr64 etc>
|
||||
```
|
||||
@@ -0,0 +1,441 @@
|
||||
/*
|
||||
Copyright (c) 2023 - 2026 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 <iostream>
|
||||
#include <iomanip>
|
||||
#include <fstream>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
#include <sys/stat.h>
|
||||
#include <libgen.h>
|
||||
#if __cplusplus >= 201703L && __has_include(<filesystem>)
|
||||
#include <filesystem>
|
||||
#else
|
||||
#include <experimental/filesystem>
|
||||
#endif
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <queue>
|
||||
#include <atomic>
|
||||
#include "video_demuxer.h"
|
||||
#include "roc_video_dec.h"
|
||||
#include "video_post_process.h"
|
||||
#include "md5.h"
|
||||
|
||||
std::vector<std::string> st_output_format_name = {"native", "bgr", "bgr48", "rgb", "rgb48", "bgra", "bgra64", "rgba", "rgba64"};
|
||||
|
||||
void ShowHelpAndExit(const char *option = NULL) {
|
||||
std::cout << "Options:" << std::endl
|
||||
<< "-i Input File Path - required" << std::endl
|
||||
<< "-o Output File Path - dumps output if requested; optional" << std::endl
|
||||
<< "-d GPU device ID (0 for the first device, 1 for the second, etc.); optional; default: 0" << std::endl
|
||||
<< "-of Output Format name - (native, bgr, bgr48, rgb, rgb48, bgra, bgra64, rgba, rgba64; converts native YUV frame to RGB image format; optional; default: 0" << std::endl
|
||||
<< "-resize WxH - (where W is resize width and H is resize height) optional; default: no resize " << std::endl
|
||||
<< "-crop crop rectangle for output (not used when using interopped decoded frame); optional; default: 0" << std::endl
|
||||
<< "-disp_delay -specify the number of frames to be delayed for display; optional; default: 1" << std::endl;
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
constexpr int frame_buffers_size = 2;
|
||||
std::mutex mutex;
|
||||
std::condition_variable cv;
|
||||
std::queue<int> frame_indices_q;
|
||||
uint8_t* frame_buffers[frame_buffers_size] = {0};
|
||||
|
||||
void ColorSpaceConversionThread(std::atomic<bool>& continue_processing, bool convert_to_rgb, Dim *p_resize_dim, OutputSurfaceInfo **surf_info, OutputSurfaceInfo **res_surf_info,
|
||||
OutputFormatEnum e_output_format, uint8_t *p_rgb_dev_mem, uint8_t *p_resize_dev_mem, bool dump_output_frames,
|
||||
std::string &output_file_path, RocVideoDecoder &viddec, VideoPostProcess &post_proc, MD5Generator *md5_gen_handle, bool b_generate_md5, int device_id, hipStream_t hip_stream) {
|
||||
|
||||
size_t rgb_image_size, resize_image_size;
|
||||
hipError_t hip_status = hipSuccess;
|
||||
int current_frame_index;
|
||||
uint8_t *frame;
|
||||
|
||||
HIP_API_CALL(hipSetDevice(device_id));
|
||||
while (continue_processing || !frame_indices_q.empty()) {
|
||||
OutputSurfaceInfo *p_surf_info;
|
||||
uint8_t *out_frame;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
// Wait until there is a frame available in the queue or processing is complete
|
||||
cv.wait(lock, [&] {return !frame_indices_q.empty() || !continue_processing;});
|
||||
if (!continue_processing && frame_indices_q.empty()) {
|
||||
break;
|
||||
}
|
||||
p_surf_info = *surf_info;
|
||||
current_frame_index = frame_indices_q.front();
|
||||
// Get the current frame at the curren_buffer index for processing
|
||||
frame = frame_buffers[current_frame_index];
|
||||
out_frame = frame;
|
||||
}
|
||||
if (p_resize_dim->w && p_resize_dim->h && *res_surf_info) {
|
||||
// check if the resize dims are different from output dims
|
||||
// resize is needed since output dims are different from resize dims
|
||||
// TODO:: the below code assumes NV12/P016 for decoded output surface. Modify to take other surface formats in future
|
||||
if (((*surf_info)->output_width != p_resize_dim->w) || ((*surf_info)->output_height != p_resize_dim->h)) {
|
||||
resize_image_size = p_resize_dim->w * (p_resize_dim->h + (p_resize_dim->h >> 1)) * (*surf_info)->bytes_per_pixel;
|
||||
if (p_resize_dev_mem == nullptr && resize_image_size > 0) {
|
||||
hip_status = hipMalloc(&p_resize_dev_mem, resize_image_size);
|
||||
if (hip_status != hipSuccess) {
|
||||
std::cerr << "ERROR: hipMalloc failed to allocate the device memory for the output!" << hip_status << std::endl;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// call resize kernel
|
||||
if ((*surf_info)->bytes_per_pixel == 2) {
|
||||
ResizeP016(p_resize_dev_mem, p_resize_dim->w * 2, p_resize_dim->w, p_resize_dim->h, frame, (*surf_info)->output_pitch, (*surf_info)->output_width,
|
||||
(*surf_info)->output_height, (frame + (*surf_info)->output_vstride * (*surf_info)->output_pitch), nullptr, hip_stream);
|
||||
} else {
|
||||
ResizeNv12(p_resize_dev_mem, p_resize_dim->w, p_resize_dim->w, p_resize_dim->h, frame, (*surf_info)->output_pitch, (*surf_info)->output_width,
|
||||
(*surf_info)->output_height, (frame + (*surf_info)->output_vstride * (*surf_info)->output_pitch), nullptr, hip_stream);
|
||||
}
|
||||
(*res_surf_info)->output_width = p_resize_dim->w;
|
||||
(*res_surf_info)->output_height = p_resize_dim->h;
|
||||
(*res_surf_info)->output_pitch = p_resize_dim->w * (*surf_info)->bytes_per_pixel;
|
||||
(*res_surf_info)->output_vstride = p_resize_dim->h;
|
||||
(*res_surf_info)->output_surface_size_in_bytes = (*res_surf_info)->output_pitch * (p_resize_dim->h + (p_resize_dim->h >> 1));
|
||||
(*res_surf_info)->mem_type = OUT_SURFACE_MEM_DEV_COPIED;
|
||||
p_surf_info = *res_surf_info;
|
||||
out_frame = p_resize_dev_mem;
|
||||
}
|
||||
}
|
||||
|
||||
if (convert_to_rgb) {
|
||||
uint32_t rgb_stride = post_proc.GetRgbStride(e_output_format, p_surf_info);
|
||||
rgb_image_size = p_surf_info->output_height * rgb_stride;
|
||||
if (p_rgb_dev_mem == nullptr) {
|
||||
hip_status = hipMalloc(&p_rgb_dev_mem, rgb_image_size);
|
||||
if (hip_status != hipSuccess) {
|
||||
std::cerr << "ERROR: hipMalloc failed to allocate the device memory for the output!" << hip_status << std::endl;
|
||||
return;
|
||||
}
|
||||
}
|
||||
post_proc.ColorConvertYUV2RGB(out_frame, p_surf_info, p_rgb_dev_mem, e_output_format, hip_stream);
|
||||
}
|
||||
if (dump_output_frames) {
|
||||
if (convert_to_rgb)
|
||||
viddec.SaveFrameToFile(output_file_path, p_rgb_dev_mem, p_surf_info, rgb_image_size);
|
||||
else
|
||||
viddec.SaveFrameToFile(output_file_path, out_frame, p_surf_info);
|
||||
}
|
||||
if (b_generate_md5) {
|
||||
if (convert_to_rgb) {
|
||||
md5_gen_handle->UpdateMd5ForDataBuffer(p_rgb_dev_mem, rgb_image_size);
|
||||
} else {
|
||||
md5_gen_handle->UpdateMd5ForFrame(frame, p_surf_info);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
frame_indices_q.pop();
|
||||
}
|
||||
|
||||
cv.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
std::string input_file_path, output_file_path, md5_file_path;
|
||||
std::fstream ref_md5_file;
|
||||
bool b_generate_md5 = false;
|
||||
bool b_md5_check = false;
|
||||
bool dump_output_frames = false;
|
||||
bool convert_to_rgb = false;
|
||||
int device_id = 0;
|
||||
int disp_delay = 1;
|
||||
bool b_extract_sei_messages = false;
|
||||
Rect crop_rect = {};
|
||||
Dim resize_dim = {};
|
||||
Rect *p_crop_rect = nullptr;
|
||||
size_t rgb_image_size;
|
||||
uint32_t rgb_image_stride;
|
||||
hipError_t hip_status = hipSuccess;
|
||||
uint8_t *p_rgb_dev_mem = nullptr;
|
||||
uint8_t *p_resize_dev_mem = nullptr;
|
||||
OutputSurfaceMemoryType mem_type = OUT_SURFACE_MEM_DEV_INTERNAL;
|
||||
OutputFormatEnum e_output_format = native;
|
||||
int rgb_width;
|
||||
int current_frame_index = 0;
|
||||
hipStream_t hip_stream_dec = 0;
|
||||
hipStream_t hip_stream_csc = 0;
|
||||
|
||||
// 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_file_path = argv[i];
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-o")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-o");
|
||||
}
|
||||
output_file_path = argv[i];
|
||||
dump_output_frames = true;
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-d")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-d");
|
||||
}
|
||||
device_id = atoi(argv[i]);
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-crop")) {
|
||||
if (++i == argc || 4 != sscanf(argv[i], "%d,%d,%d,%d", &crop_rect.left, &crop_rect.top, &crop_rect.right, &crop_rect.bottom)) {
|
||||
ShowHelpAndExit("-crop");
|
||||
}
|
||||
if ((crop_rect.right - crop_rect.left) % 2 == 1 || (crop_rect.bottom - crop_rect.top) % 2 == 1) {
|
||||
std::cout << "output crop rectangle must have width and height of even numbers" << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
p_crop_rect = &crop_rect;
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-resize")) {
|
||||
if (++i == argc || 2 != sscanf(argv[i], "%dx%d", &resize_dim.w, &resize_dim.h)) {
|
||||
ShowHelpAndExit("-resize");
|
||||
}
|
||||
if (resize_dim.w % 2 == 1 || resize_dim.h % 2 == 1) {
|
||||
std::cout << "Resizing dimensions must have width and height of even numbers" << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-of")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-of");
|
||||
}
|
||||
auto it = std::find(st_output_format_name.begin(), st_output_format_name.end(), argv[i]);
|
||||
if (it == st_output_format_name.end()) {
|
||||
ShowHelpAndExit("-of");
|
||||
}
|
||||
e_output_format = (OutputFormatEnum)(it - st_output_format_name.begin());
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-md5")) {
|
||||
if (i == argc) {
|
||||
ShowHelpAndExit("-md5");
|
||||
}
|
||||
b_generate_md5 = true;
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-md5_check")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-md5_check");
|
||||
}
|
||||
b_generate_md5 = true;
|
||||
b_md5_check = true;
|
||||
md5_file_path = argv[i];
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-disp_delay")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-disp_delay");
|
||||
}
|
||||
disp_delay = atoi(argv[i]);
|
||||
continue;
|
||||
}
|
||||
ShowHelpAndExit(argv[i]);
|
||||
}
|
||||
|
||||
try {
|
||||
VideoDemuxer demuxer(input_file_path.c_str());
|
||||
rocDecVideoCodec rocdec_codec_id = AVCodec2RocDecVideoCodec(demuxer.GetCodecID());
|
||||
RocVideoDecoder viddec(device_id, mem_type, rocdec_codec_id, false, p_crop_rect, b_extract_sei_messages, disp_delay);
|
||||
if(!viddec.CodecSupported(device_id, rocdec_codec_id, demuxer.GetBitDepth())) {
|
||||
std::cerr << "GPU doesn't support codec!" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
VideoPostProcess post_process;
|
||||
MD5Generator *md5_generator = nullptr;
|
||||
|
||||
std::string device_name, gcn_arch_name;
|
||||
int pci_bus_id, pci_domain_id, pci_device_id;
|
||||
|
||||
viddec.GetDeviceinfo(device_name, gcn_arch_name, pci_bus_id, pci_domain_id, pci_device_id);
|
||||
std::cout << "info: Using GPU device " << 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::cout << "info: decoding started, please wait!" << std::endl;
|
||||
HIP_API_CALL(hipStreamCreate(&hip_stream_dec));
|
||||
HIP_API_CALL(hipStreamCreate(&hip_stream_csc));
|
||||
|
||||
if (b_generate_md5) {
|
||||
md5_generator = new MD5Generator();
|
||||
md5_generator->InitMd5();
|
||||
}
|
||||
|
||||
int n_video_bytes = 0, n_frames_returned = 0, n_frame = 0;
|
||||
uint8_t *p_video = nullptr;
|
||||
uint8_t *p_frame = nullptr;
|
||||
int64_t pts = 0;
|
||||
OutputSurfaceInfo *surf_info;
|
||||
OutputSurfaceInfo *resize_surf_info = nullptr;
|
||||
uint32_t width, height;
|
||||
double total_dec_time = 0;
|
||||
convert_to_rgb = e_output_format != native;
|
||||
std::atomic<bool> continue_processing(true);
|
||||
std::thread color_space_conversion_thread(ColorSpaceConversionThread, std::ref(continue_processing), std::ref(convert_to_rgb), &resize_dim, &surf_info, &resize_surf_info, std::ref(e_output_format),
|
||||
std::ref(p_rgb_dev_mem), std::ref(p_resize_dev_mem), std::ref(dump_output_frames), std::ref(output_file_path), std::ref(viddec), std::ref(post_process), md5_generator, b_generate_md5, device_id, hip_stream_csc);
|
||||
|
||||
auto startTime = std::chrono::high_resolution_clock::now();
|
||||
do {
|
||||
demuxer.Demux(&p_video, &n_video_bytes, &pts);
|
||||
n_frames_returned = viddec.DecodeFrame(p_video, n_video_bytes, 0, pts);
|
||||
if (!n_frame && !viddec.GetOutputSurfaceInfo(&surf_info)) {
|
||||
std::cerr << "Error: Failed to get Output Image Info!" << std::endl;
|
||||
break;
|
||||
}
|
||||
if (resize_dim.w && resize_dim.h && !resize_surf_info) {
|
||||
resize_surf_info = new OutputSurfaceInfo;
|
||||
memcpy(resize_surf_info, surf_info, sizeof(OutputSurfaceInfo));
|
||||
}
|
||||
|
||||
int last_index = 0;
|
||||
for (int i = 0; i < n_frames_returned; i++) {
|
||||
p_frame = viddec.GetFrame(&pts);
|
||||
// allocate extra device memories to use double-buffering for keeping two decoded frames
|
||||
if (frame_buffers[0] == nullptr) {
|
||||
for (int i = 0; i < frame_buffers_size; i++) {
|
||||
HIP_API_CALL(hipMalloc(&frame_buffers[i], surf_info->output_surface_size_in_bytes));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
cv.wait(lock, [&] {return frame_indices_q.size() < frame_buffers_size;});
|
||||
// copy the decoded frame into the frame_buffers at current_frame_index
|
||||
HIP_API_CALL(hipMemcpyDtoDAsync(frame_buffers[current_frame_index], p_frame, surf_info->output_surface_size_in_bytes, hip_stream_dec));
|
||||
HIP_API_CALL(hipStreamSynchronize(hip_stream_dec));
|
||||
frame_indices_q.push(current_frame_index);
|
||||
}
|
||||
|
||||
viddec.ReleaseFrame(pts);
|
||||
current_frame_index = (current_frame_index + 1) % frame_buffers_size; // update the current_frame_index to the next index in the frame_buffers
|
||||
cv.notify_one(); // Notify the ColorSpaceConversionThread that a frame is available for post-processing
|
||||
}
|
||||
|
||||
n_frame += n_frames_returned;
|
||||
} while (n_video_bytes);
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
//Signal ColorSpaceConversionThread to stop
|
||||
continue_processing = false;
|
||||
}
|
||||
|
||||
cv.notify_one();
|
||||
|
||||
auto end_time = std::chrono::high_resolution_clock::now();
|
||||
auto time_per_frame = std::chrono::duration<double, std::milli>(end_time - startTime).count();
|
||||
total_dec_time += time_per_frame;
|
||||
|
||||
color_space_conversion_thread.join();
|
||||
|
||||
if (p_rgb_dev_mem != nullptr) {
|
||||
hip_status = hipFree(p_rgb_dev_mem);
|
||||
if (hip_status != hipSuccess) {
|
||||
std::cout << "ERROR: hipFree failed! (" << hip_status << ")" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < frame_buffers_size; i++) {
|
||||
hip_status = hipFree(frame_buffers[i]);
|
||||
if (hip_status != hipSuccess) {
|
||||
std::cout << "ERROR: hipFree failed! (" << hip_status << ")" << std::endl;
|
||||
}
|
||||
}
|
||||
if (hip_stream_dec) {
|
||||
HIP_API_CALL(hipStreamDestroy(hip_stream_dec));
|
||||
}
|
||||
if (hip_stream_csc) {
|
||||
HIP_API_CALL(hipStreamDestroy(hip_stream_csc));
|
||||
}
|
||||
|
||||
std::cout << "info: Total frame decoded: " << n_frame << std::endl;
|
||||
if (!dump_output_frames) {
|
||||
std::string info_message = "info: avg decoding time per frame (ms): ";
|
||||
if (convert_to_rgb) {
|
||||
info_message = "info: avg decoding and post processing time per frame (ms): ";
|
||||
}
|
||||
std::cout << info_message << total_dec_time / n_frame << std::endl;
|
||||
std::cout << "info: avg FPS: " << (n_frame / total_dec_time) * 1000 << std::endl;
|
||||
}
|
||||
if (resize_surf_info != nullptr) {
|
||||
delete resize_surf_info;
|
||||
}
|
||||
if (b_generate_md5) {
|
||||
uint8_t *digest;
|
||||
md5_generator->FinalizeMd5(&digest);
|
||||
std::cout << "MD5 message digest: ";
|
||||
for (int i = 0; i < 16; i++) {
|
||||
std::cout << std::setfill('0') << std::setw(2) << std::hex << static_cast<int>(digest[i]);
|
||||
}
|
||||
std::cout << std::endl;
|
||||
if (b_md5_check) {
|
||||
std::string ref_md5_string(33, 0);
|
||||
uint8_t ref_md5[16];
|
||||
ref_md5_file.open(md5_file_path.c_str(), std::ios::in);
|
||||
if ((ref_md5_file.rdstate() & std::ifstream::failbit) != 0) {
|
||||
std::cerr << "Failed to open MD5 file." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
ref_md5_file.getline(ref_md5_string.data(), ref_md5_string.length());
|
||||
if ((ref_md5_file.rdstate() & std::ifstream::badbit) != 0) {
|
||||
std::cerr << "Failed to read MD5 digest string." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
for (int i = 0; i < 16; i++) {
|
||||
std::string part = ref_md5_string.substr(i * 2, 2);
|
||||
ref_md5[i] = std::stoi(part, nullptr, 16);
|
||||
}
|
||||
if (memcmp(digest, ref_md5, 16) == 0) {
|
||||
std::cout << "MD5 digest matches the reference MD5 digest: ";
|
||||
} else {
|
||||
std::cout << "MD5 digest does not match the reference MD5 digest: ";
|
||||
}
|
||||
std::cout << ref_md5_string << std::endl;
|
||||
ref_md5_file.close();
|
||||
}
|
||||
delete md5_generator;
|
||||
}
|
||||
} catch (const std::exception &ex) {
|
||||
std::cout << ex.what() << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
################################################################################
|
||||
# Copyright (c) 2023 - 2026 Advanced Micro Devices, Inc.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
# ROCM Path
|
||||
if(DEFINED ENV{ROCM_PATH})
|
||||
set(ROCM_PATH $ENV{ROCM_PATH} CACHE PATH "Default ROCm installation path")
|
||||
elseif(ROCM_PATH)
|
||||
message("-- INFO:ROCM_PATH Set -- ${ROCM_PATH}")
|
||||
else()
|
||||
set(ROCM_PATH /opt/rocm CACHE PATH "Default ROCm installation path")
|
||||
endif()
|
||||
# Set AMD Clang as default compiler
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED On)
|
||||
set(CMAKE_CXX_EXTENSIONS ON)
|
||||
if (NOT DEFINED CMAKE_CXX_COMPILER)
|
||||
set(CMAKE_C_COMPILER ${ROCM_PATH}/lib/llvm/bin/amdclang)
|
||||
set(CMAKE_CXX_COMPILER ${ROCM_PATH}/lib/llvm/bin/amdclang++)
|
||||
endif()
|
||||
|
||||
project(videodecoderaw)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/../../cmake)
|
||||
list(APPEND CMAKE_PREFIX_PATH ${ROCM_PATH}/lib/cmake)
|
||||
|
||||
# rocdecode sample build type
|
||||
set(DEFAULT_BUILD_TYPE "Release")
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE "${DEFAULT_BUILD_TYPE}" CACHE STRING "rocdecode Default Build Type" FORCE)
|
||||
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release")
|
||||
endif()
|
||||
if(CMAKE_BUILD_TYPE MATCHES Debug)
|
||||
# -O0 -- Don't Optimize output file
|
||||
# -gdwarf-4 -- generate debugging information, dwarf-4 for making valgrind work
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -gdwarf-4")
|
||||
else()
|
||||
# -O3 -- Optimize output file
|
||||
# -DNDEBUG -- turn off asserts
|
||||
# -fPIC -- Generate position-independent code if possible
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -DNDEBUG -fPIC")
|
||||
endif()
|
||||
|
||||
set (HIP_PLATFORM amd CACHE STRING "HIP platform")
|
||||
find_package(HIP QUIET)
|
||||
find_package(rocdecode QUIET)
|
||||
find_package(rocprofiler-register QUIET)
|
||||
|
||||
if(HIP_FOUND AND rocdecode_FOUND AND rocprofiler-register_FOUND)
|
||||
# HIP
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} hip::host)
|
||||
# rocdecode and utils
|
||||
include_directories (${rocdecode_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/.. ${CMAKE_CURRENT_SOURCE_DIR}/../../utils ${CMAKE_CURRENT_SOURCE_DIR}/../../utils/rocvideodecode)
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} rocdecode::rocdecode)
|
||||
# rocprofiler-register
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} rocprofiler-register::rocprofiler-register)
|
||||
# sample app exe
|
||||
list(APPEND SOURCES ${PROJECT_SOURCE_DIR} videodecoderaw.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../utils/rocvideodecode/roc_video_dec.cpp)
|
||||
add_executable(${PROJECT_NAME} ${SOURCES})
|
||||
target_link_libraries(${PROJECT_NAME} ${LINK_LIBRARY_LIST})
|
||||
else()
|
||||
message("-- ERROR!: ${PROJECT_NAME} excluded! please install all the dependencies and try again!")
|
||||
if (NOT HIP_FOUND)
|
||||
message(FATAL_ERROR "-- ERROR!: HIP Not Found! - please install ROCm and HIP!")
|
||||
endif()
|
||||
if (NOT rocdecode_FOUND)
|
||||
message(FATAL_ERROR "-- ERROR!: rocdecode Not Found! - please install rocdecode!")
|
||||
endif()
|
||||
if (NOT rocprofiler-register_FOUND)
|
||||
message(FATAL_ERROR "-- ERROR!: rocprofiler-register Not Found! - please install rocprofiler-register!")
|
||||
endif()
|
||||
endif()
|
||||
@@ -0,0 +1,29 @@
|
||||
# Video decode sample
|
||||
|
||||
The video decode raw sample illustrates decoding a single packetized video stream using the built-in bitstream reader, video parser, and rocDecoder to get the individual decoded frames in YUV format. This sample can be configured with a device ID and optionally able to dump the output to a file. This sample uses the high-level RocVideoDecoder class which connects both the video parser and Rocdecoder. This process repeats in a loop until all frames have been decoded.
|
||||
|
||||
## Prerequisites:
|
||||
|
||||
* Install [rocDecode](../../README.md#build-and-install-instructions)
|
||||
|
||||
## Build
|
||||
|
||||
```shell
|
||||
mkdir video_decode_raw_sample && cd video_decode_raw_sample
|
||||
cmake ../
|
||||
make -j
|
||||
```
|
||||
|
||||
## Run
|
||||
|
||||
```shell
|
||||
./videodecoderaw -i <input video file [required]>
|
||||
-o <output path to save decoded YUV frames [optional]>
|
||||
-d <GPU device ID - 0:device 0 / 1:device 1/ ... [optional - default:0]>
|
||||
-f <Number of decoded frames - specify the number of pictures to be decoded [optional]>
|
||||
-z <force_zero_latency - Decoded frames will be flushed out for display immediately [optional]>
|
||||
-disp_delay <display delay - specify the number of frames to be delayed for display [optional - default: 1]>
|
||||
-sei <extract SEI messages [optional]>
|
||||
-crop <crop rectangle for output (not used when using interopped decoded frame) [optional - default: 0,0,0,0]>
|
||||
-m <output_surface_memory_type - decoded surface memory [optional - default: 0][0 : OUT_SURFACE_MEM_DEV_INTERNAL/ 1 : OUT_SURFACE_MEM_DEV_COPIED/ 2 : OUT_SURFACE_MEM_HOST_COPIED/3 : OUT_SURFACE_MEM_NOT_MAPPED]>
|
||||
```
|
||||
@@ -0,0 +1,322 @@
|
||||
/*
|
||||
Copyright (c) 2023 - 2026 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 <iostream>
|
||||
#include <fstream>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <iomanip>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
#include <sys/stat.h>
|
||||
#include <libgen.h>
|
||||
#if __cplusplus >= 201703L && __has_include(<filesystem>)
|
||||
#include <filesystem>
|
||||
#else
|
||||
#include <experimental/filesystem>
|
||||
#endif
|
||||
|
||||
#include "rocdecode/roc_bitstream_reader.h"
|
||||
#include "roc_video_dec.h"
|
||||
|
||||
typedef enum ReconfigFlushMode_enum {
|
||||
RECONFIG_FLUSH_MODE_NONE = 0x0, /**< Just flush to get the frame count */
|
||||
RECONFIG_FLUSH_MODE_DUMP_TO_FILE = 0x1, /**< The remaining frames will be dumped to file in this mode */
|
||||
RECONFIG_FLUSH_MODE_CALCULATE_MD5 = (0x1 << 1), /**< 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;
|
||||
void *md5_generator_handle;
|
||||
} 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) {
|
||||
ReconfigDumpFileStruct *p_dump_file_struct = static_cast<ReconfigDumpFileStruct *>(p_user_struct);
|
||||
if (flush_mode & ReconfigFlushMode::RECONFIG_FLUSH_MODE_DUMP_TO_FILE) {
|
||||
if (p_dump_file_struct->b_dump_frames_to_file) {
|
||||
viddec->SaveFrameToFile(p_dump_file_struct->output_file_name, pframe, surf_info);
|
||||
}
|
||||
}
|
||||
}
|
||||
// release and flush frame
|
||||
viddec->ReleaseFrame(pts, true);
|
||||
n_frames_flushed ++;
|
||||
}
|
||||
|
||||
return n_frames_flushed;
|
||||
}
|
||||
|
||||
void ShowHelpAndExit(const char *option = NULL) {
|
||||
std::cout << "Options:" << std::endl
|
||||
<< "-i Input File Path - required" << std::endl
|
||||
<< "-o Output File Path - dumps output if requested; optional" << std::endl
|
||||
<< "-d GPU device ID (0 for the first device, 1 for the second, etc.); optional; default: 0" << std::endl
|
||||
<< "-f Number of decoded frames - specify the number of pictures to be decoded; optional" << std::endl
|
||||
<< "-z force_zero_latency (force_zero_latency, Decoded frames will be flushed out for display immediately); optional;" << std::endl
|
||||
<< "-disp_delay -specify the number of frames to be delayed for display; optional; default: 1" << std::endl
|
||||
<< "-sei extract SEI messages; optional;" << std::endl
|
||||
<< "-crop crop rectangle for output (not used when using interopped decoded frame); optional; default: 0" << std::endl
|
||||
<< "-m output_surface_memory_type - decoded surface memory; optional; default - 0"
|
||||
<< " [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;
|
||||
exit(0);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
std::string input_file_path, output_file_path;
|
||||
int dump_output_frames = 0;
|
||||
int device_id = 0;
|
||||
int disp_delay = 1;
|
||||
bool b_force_zero_latency = false; // false by default: enabling this option might affect decoding performance
|
||||
bool b_extract_sei_messages = false;
|
||||
bool b_flush_frames_during_reconfig = true;
|
||||
Rect crop_rect = {};
|
||||
Rect *p_crop_rect = nullptr;
|
||||
OutputSurfaceMemoryType mem_type = OUT_SURFACE_MEM_DEV_INTERNAL; // set to internal
|
||||
ReconfigParams reconfig_params = { 0 };
|
||||
ReconfigDumpFileStruct reconfig_user_struct = { 0 };
|
||||
uint32_t num_decoded_frames = 0; // default value is 0, meaning decode the entire stream
|
||||
|
||||
// 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_file_path = argv[i];
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-o")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-o");
|
||||
}
|
||||
output_file_path = argv[i];
|
||||
dump_output_frames = 1;
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-d")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-d");
|
||||
}
|
||||
device_id = atoi(argv[i]);
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-disp_delay")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-disp_delay");
|
||||
}
|
||||
disp_delay = atoi(argv[i]);
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-f")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-d");
|
||||
}
|
||||
num_decoded_frames = atoi(argv[i]);
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-z")) {
|
||||
if (i == argc) {
|
||||
ShowHelpAndExit("-z");
|
||||
}
|
||||
b_force_zero_latency = true;
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-sei")) {
|
||||
if (i == argc) {
|
||||
ShowHelpAndExit("-sei");
|
||||
}
|
||||
b_extract_sei_messages = true;
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-crop")) {
|
||||
if (++i == argc || 4 != sscanf(argv[i], "%d,%d,%d,%d", &crop_rect.left, &crop_rect.top, &crop_rect.right, &crop_rect.bottom)) {
|
||||
ShowHelpAndExit("-crop");
|
||||
}
|
||||
if ((crop_rect.right - crop_rect.left) % 2 == 1 || (crop_rect.bottom - crop_rect.top) % 2 == 1) {
|
||||
std::cout << "output crop rectangle must have width and height of even numbers" << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
p_crop_rect = &crop_rect;
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-m")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-m");
|
||||
}
|
||||
mem_type = static_cast<OutputSurfaceMemoryType>(atoi(argv[i]));
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "flush")) {
|
||||
b_flush_frames_during_reconfig = atoi(argv[i]) ? true : false;
|
||||
continue;
|
||||
}
|
||||
|
||||
ShowHelpAndExit(argv[i]);
|
||||
}
|
||||
|
||||
try {
|
||||
std::size_t found_file = input_file_path.find_last_of('/');
|
||||
std::cout << "info: Input file: " << input_file_path.substr(found_file + 1) << std::endl;
|
||||
std::cout << "info: Using built-in bitstream reader" << std::endl;
|
||||
RocdecBitstreamReader bs_reader = nullptr;
|
||||
rocDecVideoCodec rocdec_codec_id;
|
||||
int bit_depth;
|
||||
if (rocDecCreateBitstreamReader(&bs_reader, input_file_path.c_str()) != ROCDEC_SUCCESS) {
|
||||
std::cerr << "Failed to create the bitstream reader." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
if (rocDecGetBitstreamCodecType(bs_reader, &rocdec_codec_id) != ROCDEC_SUCCESS) {
|
||||
std::cerr << "Failed to get stream codec type." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
if (rocdec_codec_id >= rocDecVideoCodec_NumCodecs) {
|
||||
std::cerr << "Unsupported stream file type or codec type by the bitstream reader. Exiting." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
if (rocDecGetBitstreamBitDepth(bs_reader, &bit_depth) != ROCDEC_SUCCESS) {
|
||||
std::cerr << "Failed to get stream bit depth." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
RocVideoDecoder viddec(device_id, mem_type, rocdec_codec_id, b_force_zero_latency, p_crop_rect, b_extract_sei_messages, disp_delay);
|
||||
if(!viddec.CodecSupported(device_id, rocdec_codec_id, bit_depth)) {
|
||||
std::cerr << "GPU doesn't support codec!" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
std::string device_name, gcn_arch_name;
|
||||
int pci_bus_id, pci_domain_id, pci_device_id;
|
||||
|
||||
viddec.GetDeviceinfo(device_name, gcn_arch_name, pci_bus_id, pci_domain_id, pci_device_id);
|
||||
std::cout << "info: Using GPU device " << 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::cout << "info: decoding started, please wait!" << std::endl;
|
||||
|
||||
int n_video_bytes = 0, n_frame_returned = 0, n_frame = 0;
|
||||
int n_pic_decoded = 0, decoded_pics = 0;
|
||||
uint8_t *pvideo = nullptr;
|
||||
int pkg_flags = 0;
|
||||
uint8_t *pframe = nullptr;
|
||||
int64_t pts = 0;
|
||||
OutputSurfaceInfo *surf_info;
|
||||
uint32_t width, height;
|
||||
double total_dec_time = 0;
|
||||
bool first_frame = true;
|
||||
// initialize reconfigure params: the following is configured to dump to output which is relevant for this sample
|
||||
reconfig_params.p_fn_reconfigure_flush = ReconfigureFlushCallback;
|
||||
reconfig_user_struct.b_dump_frames_to_file = dump_output_frames;
|
||||
reconfig_user_struct.output_file_name = output_file_path;
|
||||
if (dump_output_frames) {
|
||||
reconfig_params.reconfig_flush_mode |= RECONFIG_FLUSH_MODE_DUMP_TO_FILE;
|
||||
} else {
|
||||
reconfig_params.reconfig_flush_mode = RECONFIG_FLUSH_MODE_NONE;
|
||||
}
|
||||
reconfig_params.p_reconfig_user_struct = &reconfig_user_struct;
|
||||
|
||||
viddec.SetReconfigParams(&reconfig_params);
|
||||
|
||||
do {
|
||||
auto start_time = std::chrono::high_resolution_clock::now();
|
||||
if (rocDecGetBitstreamPicData(bs_reader, &pvideo, &n_video_bytes, &pts) != ROCDEC_SUCCESS) {
|
||||
std::cerr << "Failed to get picture data." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
// Treat 0 bitstream size as end of stream indicator
|
||||
if (n_video_bytes == 0) {
|
||||
pkg_flags |= ROCDEC_PKT_ENDOFSTREAM;
|
||||
}
|
||||
n_frame_returned = viddec.DecodeFrame(pvideo, n_video_bytes, pkg_flags, pts, &decoded_pics);
|
||||
|
||||
if (!n_frame && !viddec.GetOutputSurfaceInfo(&surf_info)) {
|
||||
std::cerr << "Error: Failed to get Output Surface Info!" << std::endl;
|
||||
break;
|
||||
}
|
||||
for (int i = 0; i < n_frame_returned; i++) {
|
||||
pframe = viddec.GetFrame(&pts);
|
||||
if (dump_output_frames && mem_type != OUT_SURFACE_MEM_NOT_MAPPED) {
|
||||
viddec.SaveFrameToFile(output_file_path, pframe, surf_info);
|
||||
}
|
||||
// release frame
|
||||
viddec.ReleaseFrame(pts);
|
||||
}
|
||||
auto end_time = std::chrono::high_resolution_clock::now();
|
||||
auto time_per_decode = std::chrono::duration<double, std::milli>(end_time - start_time).count();
|
||||
total_dec_time += time_per_decode;
|
||||
n_frame += n_frame_returned;
|
||||
n_pic_decoded += decoded_pics;
|
||||
if (num_decoded_frames && num_decoded_frames <= n_frame) {
|
||||
break;
|
||||
}
|
||||
|
||||
} while (n_video_bytes);
|
||||
|
||||
n_frame += viddec.GetNumOfFlushedFrames();
|
||||
std::cout << "info: Total pictures decoded: " << n_pic_decoded << std::endl;
|
||||
std::cout << "info: Total frames output/displayed: " << n_frame << std::endl;
|
||||
if (!dump_output_frames) {
|
||||
std::cout << "info: avg decoding time per picture: " << total_dec_time / n_pic_decoded << " ms" <<std::endl;
|
||||
std::cout << "info: avg decode FPS: " << (n_pic_decoded / total_dec_time) * 1000 << std::endl;
|
||||
std::cout << "info: avg output/display time per frame: " << total_dec_time / n_frame << " ms" <<std::endl;
|
||||
std::cout << "info: avg output/display FPS: " << (n_frame / total_dec_time) * 1000 << 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 {
|
||||
std::cout << "info: saved frames into " << output_file_path << std::endl;
|
||||
}
|
||||
}
|
||||
if (bs_reader) {
|
||||
rocDecDestroyBitstreamReader(bs_reader);
|
||||
}
|
||||
} catch (const std::exception &ex) {
|
||||
std::cout << ex.what() << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
################################################################################
|
||||
# Copyright (c) 2024 - 2026 Advanced Micro Devices, Inc.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
# ROCM Path
|
||||
if(DEFINED ENV{ROCM_PATH})
|
||||
set(ROCM_PATH $ENV{ROCM_PATH} CACHE PATH "Default ROCm installation path")
|
||||
elseif(ROCM_PATH)
|
||||
message("-- INFO:ROCM_PATH Set -- ${ROCM_PATH}")
|
||||
else()
|
||||
set(ROCM_PATH /opt/rocm CACHE PATH "Default ROCm installation path")
|
||||
endif()
|
||||
# Set AMD Clang as default compiler
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED On)
|
||||
set(CMAKE_CXX_EXTENSIONS ON)
|
||||
if (NOT DEFINED CMAKE_CXX_COMPILER)
|
||||
set(CMAKE_C_COMPILER ${ROCM_PATH}/lib/llvm/bin/amdclang)
|
||||
set(CMAKE_CXX_COMPILER ${ROCM_PATH}/lib/llvm/bin/amdclang++)
|
||||
endif()
|
||||
|
||||
project(videotosequence)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/../../cmake)
|
||||
list(APPEND CMAKE_PREFIX_PATH ${ROCM_PATH}/lib/cmake)
|
||||
|
||||
# rocdecode sample build type
|
||||
set(DEFAULT_BUILD_TYPE "Release")
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE "${DEFAULT_BUILD_TYPE}" CACHE STRING "rocdecode Default Build Type" FORCE)
|
||||
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release")
|
||||
endif()
|
||||
if(CMAKE_BUILD_TYPE MATCHES Debug)
|
||||
# -O0 -- Don't Optimize output file
|
||||
# -gdwarf-4 -- generate debugging information, dwarf-4 for making valgrind work
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -gdwarf-4")
|
||||
else()
|
||||
# -O3 -- Optimize output file
|
||||
# -DNDEBUG -- turn off asserts
|
||||
# -fPIC -- Generate position-independent code if possible
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -DNDEBUG -fPIC")
|
||||
endif()
|
||||
|
||||
set (HIP_PLATFORM amd CACHE STRING "HIP platform")
|
||||
find_package(HIP QUIET)
|
||||
find_package(rocdecode QUIET)
|
||||
find_package(rocprofiler-register QUIET)
|
||||
find_package(FFmpeg QUIET)
|
||||
|
||||
if(HIP_FOUND AND FFMPEG_FOUND AND rocdecode_FOUND AND rocprofiler-register_FOUND)
|
||||
# HIP
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} hip::host)
|
||||
# FFMPEG
|
||||
include_directories(${AVUTIL_INCLUDE_DIR} ${AVCODEC_INCLUDE_DIR}
|
||||
${AVFORMAT_INCLUDE_DIR})
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} ${FFMPEG_LIBRARIES})
|
||||
# rocdecode and utils
|
||||
include_directories (${rocdecode_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/.. ${CMAKE_CURRENT_SOURCE_DIR}/../../utils ${CMAKE_CURRENT_SOURCE_DIR}/../../utils/rocvideodecode)
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} rocdecode::rocdecode)
|
||||
# rocprofiler-register
|
||||
set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} rocprofiler-register::rocprofiler-register)
|
||||
# sample app exe
|
||||
list(APPEND SOURCES ${PROJECT_SOURCE_DIR} videotosequence.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../utils/rocvideodecode/roc_video_dec.cpp)
|
||||
add_executable(${PROJECT_NAME} ${SOURCES})
|
||||
target_link_libraries(${PROJECT_NAME} ${LINK_LIBRARY_LIST})
|
||||
# FFMPEG multi-version support
|
||||
if(_FFMPEG_AVCODEC_VERSION VERSION_LESS_EQUAL 58.134.100)
|
||||
target_compile_definitions(${PROJECT_NAME} PUBLIC USE_AVCODEC_GREATER_THAN_58_134=0)
|
||||
else()
|
||||
target_compile_definitions(${PROJECT_NAME} PUBLIC USE_AVCODEC_GREATER_THAN_58_134=1)
|
||||
endif()
|
||||
else()
|
||||
message("-- ERROR!: ${PROJECT_NAME} excluded! please install all the dependencies and try again!")
|
||||
if (NOT HIP_FOUND)
|
||||
message(FATAL_ERROR "-- ERROR!: HIP Not Found! - please install ROCm and HIP!")
|
||||
endif()
|
||||
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()
|
||||
if (NOT rocprofiler-register_FOUND)
|
||||
message(FATAL_ERROR "-- ERROR!: rocprofiler-register Not Found! - please install rocprofiler-register!")
|
||||
endif()
|
||||
endif()
|
||||
@@ -0,0 +1,41 @@
|
||||
# Video decode sample
|
||||
|
||||
The VideoToSequence sample illustrates decoding a single packetized video stream using FFMPEG demuxer and splitting it into multiple video sequences. This uses seek functionality to seek to a random position and extract a batch of video sequences in YUV format with step and stride. This sample can be configured with a device ID and optionally able to dump the output to a file. This sample uses the high-level RocVideoDecoder class which connects both the video parser and Rocdecoder. This process repeats until a batch of sequences are extracted or EOS is reached.
|
||||
|
||||
## Prerequisites:
|
||||
|
||||
* Install [rocDecode](../../README.md#build-and-install-instructions)
|
||||
|
||||
* [FFMPEG](https://ffmpeg.org/about.html)
|
||||
|
||||
* On `Ubuntu`
|
||||
|
||||
```shell
|
||||
sudo apt install libavcodec-dev libavformat-dev libavutil-dev
|
||||
```
|
||||
|
||||
* On `RHEL`/`SLES` - install ffmpeg development packages manually or use [rocDecode-setup.py](../../rocDecode-setup.py) script
|
||||
|
||||
## Build
|
||||
|
||||
```shell
|
||||
mkdir video_decode_sample && cd video_decode_sample
|
||||
cmake ../
|
||||
make -j
|
||||
```
|
||||
|
||||
## Run
|
||||
|
||||
```shell
|
||||
./videotosequence -i <Input file/folder Path [required]>
|
||||
-o <Output folder to dump sequences - dumps output if requested [optional]>
|
||||
-d <GPU device ID - 0:device 0 / 1:device 1/ ... [optional - default:0]>
|
||||
-b <batch_size - specify the number of sequences to be decoded [optional - default:1]>
|
||||
-step <frame interval between each sequence [optional - default:1]>
|
||||
-stride <distance between consective frames in a sequence [optional - default:1]>
|
||||
-l <Number of frames in each sequence [optional - default:1]>
|
||||
-crop <crop rectangle for output (not used when using interopped decoded frame) [optional - default:1]>
|
||||
-seek_mode <option for seeking (0: no seek 1: seek to prev key frame) [optional - default: 0]>
|
||||
-crop <crop rectangle for output (not used when using interopped decoded frame) [optional - default: 0,0,0,0]>
|
||||
-m <output_surface_memory_type - decoded surface memory [optional - default: 0][0 : OUT_SURFACE_MEM_DEV_INTERNAL/ 1 : OUT_SURFACE_MEM_DEV_COPIED/ 2 : OUT_SURFACE_MEM_HOST_COPIED/3 : OUT_SURFACE_MEM_NOT_MAPPED]>
|
||||
```
|
||||
@@ -0,0 +1,519 @@
|
||||
/*
|
||||
Copyright (c) 2024 - 2026 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 <iostream>
|
||||
#include <fstream>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <iomanip>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <queue>
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include <functional>
|
||||
#include <sys/stat.h>
|
||||
#include <libgen.h>
|
||||
|
||||
#if __cplusplus >= 201703L && __has_include(<filesystem>)
|
||||
#include <filesystem>
|
||||
#else
|
||||
#include <experimental/filesystem>
|
||||
#endif
|
||||
#include "video_demuxer.h"
|
||||
#include "roc_video_dec.h"
|
||||
#include "common.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) {}
|
||||
};
|
||||
|
||||
struct SeqInfo {
|
||||
int batch_size; // seq_info.batch_size: #of sequences in output
|
||||
int seq_length; // length of each sequence: #frames per seq
|
||||
int step; // step in number of frames to skip from one sequence to next
|
||||
int stride; // stride in muber of frames to skip between consecutive frames in a seq
|
||||
};
|
||||
|
||||
void DecProc(RocVideoDecoder *p_dec, VideoDemuxer *demuxer, int *pn_frame, double *pn_fps, std::atomic_bool &decoding_complete, int &seek_mode, bool &b_dump_output_frames, SeqInfo &seq_info, std::string *p_output_file_name, OutputSurfaceMemoryType mem_type) {
|
||||
|
||||
int n_video_bytes = 0, n_frame_returned = 0;
|
||||
int64_t n_frame = 0;
|
||||
uint8_t *p_video = nullptr, *p_frame = nullptr;
|
||||
int64_t pts = 0;
|
||||
double total_dec_time = 0.0;
|
||||
int seq_id = 0;
|
||||
OutputSurfaceInfo *surf_info;
|
||||
VideoSeekContext video_seek_ctx;
|
||||
int seq_frame_start[seq_info.batch_size];
|
||||
seq_frame_start[0] = 0;
|
||||
for (int i = 1; i < seq_info.batch_size; i++) {
|
||||
seq_frame_start[i] = seq_frame_start[i-1] + (seq_info.seq_length - 1) * seq_info.stride + seq_info.step;
|
||||
//std::cout << "seq: " << i << " seq_start: " << seq_frame_start[i] << std::endl;
|
||||
}
|
||||
auto start_time = std::chrono::high_resolution_clock::now();
|
||||
int n_frames_skipped = 0, n_frame_seq = 0, num_seq = 0;
|
||||
int next_frame_num = 0;
|
||||
bool seq_start = true;
|
||||
std::string seq_output_file_name = p_output_file_name[num_seq];
|
||||
//set reconfig before decode start
|
||||
ReconfigParams reconfig_params = { 0 };
|
||||
ReconfigDumpFileStruct reconfig_user_struct = { 0 };
|
||||
reconfig_params.p_fn_reconfigure_flush = ReconfigureFlushCallback;
|
||||
reconfig_user_struct.b_dump_frames_to_file = false;
|
||||
reconfig_params.reconfig_flush_mode = RECONFIG_FLUSH_MODE_NONE;
|
||||
reconfig_params.p_reconfig_user_struct = &reconfig_user_struct;
|
||||
p_dec->SetReconfigParams(&reconfig_params, true); // force reconfig flush mode
|
||||
|
||||
do {
|
||||
if (seek_mode && seq_start) {
|
||||
// todo:: reconfigure before seeking
|
||||
video_seek_ctx.seek_frame_ = seq_frame_start[num_seq];
|
||||
video_seek_ctx.seek_crit_ = SEEK_CRITERIA_FRAME_NUM;
|
||||
video_seek_ctx.seek_mode_ = SEEK_MODE_PREV_KEY_FRAME;
|
||||
demuxer->Seek(video_seek_ctx, &p_video, &n_video_bytes);
|
||||
pts = video_seek_ctx.out_frame_pts_;
|
||||
n_frame = static_cast<int64_t> (pts * demuxer->GetFrameRate()); // start frame number
|
||||
seq_start = false;
|
||||
p_dec->FlushAndReconfigure();
|
||||
} else {
|
||||
demuxer->Demux(&p_video, &n_video_bytes, &pts);
|
||||
}
|
||||
n_frame_returned = p_dec->DecodeFrame(p_video, n_video_bytes, 0, pts);
|
||||
if (b_dump_output_frames && mem_type != OUT_SURFACE_MEM_NOT_MAPPED) {
|
||||
if (!n_frame && !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++) {
|
||||
if ((n_frame + i) == next_frame_num) {
|
||||
p_frame = p_dec->GetFrame(&pts);
|
||||
if (n_frame_seq < seq_info.seq_length) {
|
||||
p_dec->SaveFrameToFile(seq_output_file_name, p_frame, surf_info);
|
||||
//std::cout << "saving " << next_frame_num << " to " << seq_output_file_name << std::endl;
|
||||
n_frame_seq ++;
|
||||
}
|
||||
p_dec->ReleaseFrame(pts);
|
||||
next_frame_num += seq_info.stride;
|
||||
}else {
|
||||
p_frame = p_dec->GetFrame(&pts);
|
||||
p_dec->ReleaseFrame(pts);
|
||||
}
|
||||
}
|
||||
}
|
||||
n_frame += n_frame_returned;
|
||||
if (n_frame_seq >= seq_info.seq_length) {
|
||||
n_frame_seq = 0; //reset for next sequence
|
||||
seq_start = true;
|
||||
num_seq ++;
|
||||
if (num_seq < seq_info.batch_size) {
|
||||
next_frame_num = seq_frame_start[num_seq];
|
||||
seq_output_file_name = p_output_file_name[num_seq];
|
||||
}
|
||||
p_dec->ResetSaveFrameToFile();
|
||||
// needed to flush last sequence frames before decoding the next sequence by passing EOS to parser
|
||||
n_frame_returned = p_dec->DecodeFrame(nullptr, 0, ROCDEC_PKT_ENDOFSTREAM, -1);
|
||||
}
|
||||
} while (n_video_bytes && num_seq < seq_info.batch_size);
|
||||
|
||||
//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 Input File / Folder Path - required" << std::endl
|
||||
<< "-o Output folder to dump sequences - dumps output if requested; optional" << std::endl
|
||||
<< "-d GPU device ID (0 for the first device, 1 for the second, etc.); optional; default: 0" << std::endl
|
||||
<< "-b seq_info.batch_size - specify the number of sequences to be decoded; (default: all sequences till eof)" << std::endl
|
||||
<< "-step - frame interval between each sequence; (default: sequence length)" << std::endl
|
||||
<< "-stride - distance between consective frames in a sequence; (default: 1)" << std::endl
|
||||
<< "-l - Number of frames in each sequence; (default: 3)" << std::endl
|
||||
<< "-crop crop rectangle for output (not used when using interopped decoded frame); optional; default: 0" << std::endl
|
||||
<< "-seek_mode option for seeking (0: no seek 1: seek to prev key frame); optional; default: 0" << std::endl
|
||||
<< "-m output_surface_memory_type - decoded surface memory; optional; default - 0"
|
||||
<< " [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);
|
||||
}
|
||||
// input_folder_path, output_folder_path, device_id, n_threads, seq_info, seek_mode, mem_type, argc, argv
|
||||
void ParseCommandLine(std::string &input_folder_path, std::string &output_folder_path, int &device_id, int &n_thread, SeqInfo &seq_info, int &seek_mode,
|
||||
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 (!output_folder_path.empty()) {
|
||||
#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], "-b")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-b");
|
||||
}
|
||||
seq_info.batch_size = atoi(argv[i]);
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-l")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-l");
|
||||
}
|
||||
seq_info.seq_length = atoi(argv[i]);
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-step")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-step");
|
||||
}
|
||||
seq_info.step = atoi(argv[i]);
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-stride")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-stride");
|
||||
}
|
||||
seq_info.stride = atoi(argv[i]);
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(argv[i], "-seek_mode")) {
|
||||
if (++i == argc) {
|
||||
ShowHelpAndExit("-seek_mode");
|
||||
}
|
||||
seek_mode = atoi(argv[i]);
|
||||
if (seek_mode != 0 && seek_mode != 1)
|
||||
ShowHelpAndExit("-seek_mode");
|
||||
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 dump_output_frames = 0;
|
||||
int device_id = 0, num_files = 0, seek_mode = 0;
|
||||
SeqInfo seq_info = {4, 1, 1, 4}; //default values
|
||||
int n_threads = 1;
|
||||
int disp_delay = 1;
|
||||
bool b_extract_sei_messages = false;
|
||||
bool b_flush_frames_during_reconfig = true, b_dump_output_frames = false;
|
||||
Rect *p_crop_rect = nullptr; // specify crop_rect if output cropping is needed
|
||||
OutputSurfaceMemoryType mem_type = OUT_SURFACE_MEM_DEV_INTERNAL; // set to internal
|
||||
|
||||
uint32_t num_decoded_frames = 0; // default value is 0, meaning decode the entire stream
|
||||
std::vector<std::string> input_file_names;
|
||||
|
||||
ParseCommandLine(input_folder_path, output_folder_path, device_id, n_threads, seq_info, seek_mode, 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++;
|
||||
}
|
||||
n_threads = ((n_threads > num_files) ? num_files : n_threads);
|
||||
std::vector<std::string> output_seq_file_names;
|
||||
output_seq_file_names.resize(seq_info.batch_size * num_files);
|
||||
int num_devices = 0, sd = 0;
|
||||
hipError_t hip_status = hipSuccess;
|
||||
hipDeviceProp_t hip_dev_prop;
|
||||
std::string gcn_arch_name;
|
||||
if (hipGetDeviceCount(&num_devices) != hipSuccess) {
|
||||
std::cout << "ERROR: hipGetDeviceCount failed! (" << hip_status << ")" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
if (num_devices < 1) {
|
||||
ROCDEC_ERR("ERROR: didn't find any GPU!");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (hipSuccess != hipGetDeviceProperties(&hip_dev_prop, device_id)) {
|
||||
ROCDEC_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::vector<std::unique_ptr<VideoDemuxer>> v_demuxer;
|
||||
std::vector<std::unique_ptr<DecoderInfo>> v_dec_info;
|
||||
ThreadPool thread_pool(n_threads);
|
||||
std::mutex mutex;
|
||||
|
||||
for (int i = 0; i < num_files; i++) {
|
||||
v_demuxer.push_back(std::make_unique<VideoDemuxer>(input_file_names[i].c_str()));
|
||||
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);
|
||||
for (int n = 0; n < seq_info.batch_size; n++) {
|
||||
output_seq_file_names[i * seq_info.batch_size + n] = path + "_seq_" + std::to_string(n) + ".yuv";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < n_threads; 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();
|
||||
v_dec_info[i]->viddec = std::make_unique<RocVideoDecoder>(v_dec_info[i]->dec_device_id, mem_type, v_dec_info[i]->rocdec_codec_id, false, p_crop_rect, b_extract_sei_messages, disp_delay);
|
||||
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;
|
||||
}
|
||||
|
||||
for (int j = 0; j < num_files; j++) {
|
||||
int thread_idx = j % n_threads;
|
||||
if (j >= n_threads) {
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
while (!v_dec_info[thread_idx]->decoding_complete)
|
||||
sleep(1);
|
||||
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 the codec_type or bit_depth has changed, recreate the decoder
|
||||
//if (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).release();
|
||||
v_dec_info[thread_idx]->viddec = std::make_unique<RocVideoDecoder>(v_dec_info[thread_idx]->dec_device_id, mem_type, codec_id, false, p_crop_rect, b_extract_sei_messages, disp_delay);
|
||||
//}
|
||||
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;
|
||||
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),
|
||||
seek_mode, b_dump_output_frames, seq_info, &output_seq_file_names[j*seq_info.batch_size], mem_type));
|
||||
}
|
||||
|
||||
thread_pool.JoinThreads();
|
||||
for (int i = 0; i < num_files; i++) {
|
||||
total_fps += v_fps[i] * static_cast<double>(n_threads) / 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++){
|
||||
for (int n = 0; n < seq_info.batch_size; n++) {
|
||||
std::cout << "info: saved frames into " << output_seq_file_names[i * seq_info.batch_size + n] << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (const std::exception &ex) {
|
||||
std::cout << ex.what() << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user