Add 'projects/rocdecode/' from commit 'b0bab079403eda171f9056409fa96b0908f61073'

git-subtree-dir: projects/rocdecode
git-subtree-mainline: 5d609c1e57
git-subtree-split: b0bab07940
This commit is contained in:
Ameya Keshava Mallya
2026-01-30 20:33:26 +00:00
208 changed files with 43533 additions and 0 deletions
+37
View File
@@ -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.
+87
View File
@@ -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, &params));
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, &params));
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, &params));
}
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;
}