Added the bit stream reader feature. (#433)

* * rocDecode/ES parser: Added elementary stream file parser for HEVC and AVC.

* * rocDecode/ES parser: Added elementary stream file parser for AV1. Also cleaned up the bitstream ring buffer code.

* * rocDecode/ES parser: Added the IVF container file parser for AV1. Also fixed a bug in fill ring buffer function.

* * rocDecode/ES file parder: Added supported stream type detection.
 - The stream type detection checks the unique syntax patterns of the stream type and calculate the likeliheed score. Based on the score, the most likely type is determined.
 - The current supported stream types are: AVC/HEVC/AV1 elementary streams, IVF AV1 streams.

* * rocDecode/ES file parser: Fixed an AVC decode regression due to a copy and paste error.

* * rocDecode/ES file parser: Added bit depth parsing for codec support check; Added stronger AV1 detection for IVF AV1 stream type.

* * rocDecode/ES file parser: Removed debugging logs.

* * rocDecode/ES file parser: Added exmaple code to use the built-in file parser.

* * rocDecode/Bitstream reader: Renamed the elementary parser feature to bitstream reader and re-organized the code.
 - Moved the bitstream reader code to rocDecode core lib from utility.
 - Added bitstream reader interface in parallel with rocDecode parser and decoder interfaces.

* * rocDecode/Bitstream reader: Added sample to use bitstream reader, instead of FFMPEG demuxer, to get picture data. Also reverted the original sample app back to using FFMPEG demuxer only.

* * rocDecode/Bitstream reader: Renamed the new sample app.

* * rocDecode/Bitstream reader: FFMPEG dependency reduction.
 - Moved MD5 functions out of RocVideoDecoder utility class. This removed RocVideoDecoder's dependency on FFMPEG.
 - Added the new MD5 utility, which depends on FFMPEG lib. MD5 message digest generation is now performed in the MD5 utility.
 - Modified decode sampples that uses MD5 generation function.
 - Removed FFMPEG dependency from video decoder basic sample.

* * rocDecode/Bitstream reader: Added option to use bitstream reader to video decode sample and conformance test script. Added the missing destroy bitstream reader call in video decode basic sample.

* * rocDecode/Bitstream reader: Minor format change. No functional changes.

* * rocDecode/Bitstream reader: Added handling of unsupported stream file type by the bitstream reader to decode sample apps.

* * rocDecode/Bitstream reader: Fixed build errors of several samples.

* * rocDecode/Bitstream reader: Added changes based on review comments.

* * rocDecode/Bitstream reader: File name changes based on review comments.

* * rocDecode/Bitstream reader: Moved MD5 code into single header file. Added changes based on review comments.

* * rocDecode/Bitstream reader: Removed redundant path.

* * rocDecode/Bitstream reader: Changed rocDecode version to 0.10.0. Added minor changes based on review comments.

---------

Co-authored-by: Kiriti Gowda <kiritigowda@gmail.com>

[ROCm/rocdecode commit: e62aa3e09b]
This commit is contained in:
jeffqjiangNew
2024-12-05 09:46:24 -05:00
zatwierdzone przez GitHub
rodzic fb82691ef2
commit c8a16141e4
20 zmienionych plików z 2374 dodań i 187 usunięć
+5 -2
Wyświetl plik
@@ -23,6 +23,7 @@ THE SOFTWARE.
#pragma once
#include "roc_video_dec.h"
#include "md5.h"
typedef enum ReconfigFlushMode_enum {
RECONFIG_FLUSH_MODE_NONE = 0, /**< Just flush to get the frame count */
@@ -34,6 +35,7 @@ typedef enum ReconfigFlushMode_enum {
typedef struct ReconfigDumpFileStruct_t {
bool b_dump_frames_to_file;
std::string output_file_name;
void *md5_generator_handle;
} ReconfigDumpFileStruct;
@@ -53,13 +55,14 @@ int ReconfigureFlushCallback(void *p_viddec_obj, uint32_t flush_mode, void *p_us
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) {
ReconfigDumpFileStruct *p_dump_file_struct = static_cast<ReconfigDumpFileStruct *>(p_user_struct);
if (p_dump_file_struct->b_dump_frames_to_file) {
viddec->SaveFrameToFile(p_dump_file_struct->output_file_name, pframe, surf_info);
}
} else if (flush_mode == ReconfigFlushMode::RECONFIG_FLUSH_MODE_CALCULATE_MD5) {
viddec->UpdateMd5ForFrame(pframe, surf_info);
MD5Generator *md5_generator = static_cast<MD5Generator*>(p_dump_file_struct->md5_generator_handle);
md5_generator->UpdateMd5ForFrame(pframe, surf_info);
}
}
// release and flush frame
@@ -38,4 +38,5 @@ make -j
-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]>
```
@@ -37,6 +37,7 @@ THE SOFTWARE.
#include <experimental/filesystem>
#endif
#include "video_demuxer.h"
#include "roc_bitstream_reader.h"
#include "roc_video_dec.h"
#include "ffmpeg_video_dec.h"
#include "common.h"
@@ -59,7 +60,8 @@ void ShowHelpAndExit(const char *option = NULL) {
<< "-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;
<< "[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);
}
@@ -85,6 +87,7 @@ int main(int argc, char **argv) {
// 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) {
@@ -207,6 +210,13 @@ int main(int argc, char **argv) {
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]);
}
@@ -214,10 +224,38 @@ int main(int argc, char **argv) {
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;
VideoDemuxer demuxer(input_file_path.c_str());
VideoSeekContext video_seek_ctx;
rocDecVideoCodec rocdec_codec_id = AVCodec2RocDecVideoCodec(demuxer.GetCodecID());
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 {
@@ -230,10 +268,10 @@ int main(int argc, char **argv) {
viddec = new FFMpegVideoDecoder(device_id, mem_type, rocdec_codec_id, b_force_zero_latency, p_crop_rect, b_extract_sei_messages, disp_delay, true);
}
if(!viddec->CodecSupported(device_id, rocdec_codec_id, demuxer.GetBitDepth())) {
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;
@@ -253,6 +291,8 @@ int main(int argc, char **argv) {
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;
@@ -267,32 +307,41 @@ int main(int argc, char **argv) {
reconfig_params.p_reconfig_user_struct = &reconfig_user_struct;
if (b_generate_md5) {
viddec->InitMd5();
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 (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;
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 {
demuxer.Demux(&pvideo, &n_video_bytes, &pts);
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) {
@@ -307,7 +356,7 @@ int main(int argc, char **argv) {
for (int i = 0; i < n_frame_returned; i++) {
pframe = viddec->GetFrame(&pts);
if (b_generate_md5) {
viddec->UpdateMd5ForFrame(pframe, surf_info);
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);
@@ -323,7 +372,6 @@ int main(int argc, char **argv) {
if (num_decoded_frames && num_decoded_frames <= n_frame) {
break;
}
} while (n_video_bytes);
n_frame += viddec->GetNumOfFlushedFrames();
@@ -343,7 +391,7 @@ int main(int argc, char **argv) {
}
if (b_generate_md5) {
uint8_t *digest;
viddec->FinalizeMd5(&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]);
@@ -371,9 +419,15 @@ int main(int argc, char **argv) {
} else {
std::cout << "MD5 digest does not match the reference MD5 digest: ";
}
std::cout << ref_md5_string << std::endl;
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;
@@ -37,6 +37,7 @@ THE SOFTWARE.
#endif
#include "video_demuxer.h"
#include "roc_video_dec.h"
#include "md5.h"
class FileStreamProvider : public VideoDemuxer::StreamProvider {
public:
@@ -210,9 +211,11 @@ int main(int argc, char **argv) {
OutputSurfaceInfo *surf_info;
uint32_t width, height;
double total_dec_time = 0;
MD5Generator *md5_generator = nullptr;
if (b_generate_md5) {
viddec.InitMd5();
md5_generator = new MD5Generator();
md5_generator->InitMd5();
}
do {
@@ -233,7 +236,7 @@ int main(int argc, char **argv) {
for (int i = 0; i < n_frame_returned; i++) {
pframe = viddec.GetFrame(&pts);
if (b_generate_md5) {
viddec.UpdateMd5ForFrame(pframe, surf_info);
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);
@@ -258,7 +261,7 @@ int main(int argc, char **argv) {
}
if (b_generate_md5) {
uint8_t *digest;
viddec.FinalizeMd5(&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]);
@@ -289,6 +292,7 @@ int main(int argc, char **argv) {
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;
@@ -42,6 +42,7 @@ THE SOFTWARE.
#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"};
@@ -65,7 +66,7 @@ std::condition_variable cv[frame_buffers_size];
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, bool b_generate_md5) {
std::string &output_file_path, RocVideoDecoder &viddec, VideoPostProcess &post_proc, MD5Generator *md5_gen_handle, bool b_generate_md5) {
size_t rgb_image_size, resize_image_size;
hipError_t hip_status = hipSuccess;
@@ -138,7 +139,7 @@ void ColorSpaceConversionThread(std::atomic<bool>& continue_processing, bool con
viddec.SaveFrameToFile(output_file_path, out_frame, p_surf_info);
}
if(b_generate_md5 && convert_to_rgb){
viddec.UpdateMd5ForDataBuffer(p_rgb_dev_mem, rgb_image_size);
md5_gen_handle->UpdateMd5ForDataBuffer(p_rgb_dev_mem, rgb_image_size);
}
@@ -270,6 +271,7 @@ int main(int argc, char **argv) {
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;
@@ -282,7 +284,8 @@ int main(int argc, char **argv) {
std::cout << "info: decoding started, please wait!" << std::endl;
if (b_generate_md5) {
viddec.InitMd5();
md5_generator = new MD5Generator();
md5_generator->InitMd5();
}
int n_video_bytes = 0, n_frames_returned = 0, n_frame = 0;
@@ -296,7 +299,7 @@ int main(int argc, char **argv) {
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), b_generate_md5);
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);
auto startTime = std::chrono::high_resolution_clock::now();
do {
@@ -379,7 +382,7 @@ int main(int argc, char **argv) {
}
if (b_generate_md5) {
uint8_t *digest;
viddec.FinalizeMd5(&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]);
@@ -410,6 +413,7 @@ int main(int argc, char **argv) {
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;
@@ -0,0 +1,86 @@
################################################################################
# Copyright (c) 2023 - 2024 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
if (NOT DEFINED CMAKE_CXX_COMPILER)
set(CMAKE_CXX_COMPILER ${ROCM_PATH}/bin/amdclang++)
endif()
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/../../cmake)
list(APPEND CMAKE_PREFIX_PATH ${ROCM_PATH}/hip ${ROCM_PATH})
project(videodecoderaw)
set(CMAKE_CXX_STANDARD 17)
# 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
# -Og -- Optimize for debugging experience rather than speed or size
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -gdwarf-4 -Og")
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()
find_package(HIP QUIET)
find_package(rocDecode QUIET)
if(HIP_FOUND AND ROCDECODE_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_LIBRARY})
# 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})
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++17")
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()
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 - 2024 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 "roc_bitstream_reader.h"
#include "roc_video_dec.h"
typedef enum ReconfigFlushMode_enum {
RECONFIG_FLUSH_MODE_NONE = 0, /**< Just flush to get the frame count */
RECONFIG_FLUSH_MODE_DUMP_TO_FILE = 1, /**< The remaining frames will be dumped to file in this mode */
RECONFIG_FLUSH_MODE_CALCULATE_MD5 = 2, /**< Calculate the MD5 of the flushed frames */
} ReconfigFlushMode;
// this struct is used by videodecode and videodecodeMultiFiles to dump last frames to file
typedef struct ReconfigDumpFileStruct_t {
bool b_dump_frames_to_file;
std::string output_file_name;
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;
}