From fb82691ef2800711afd0b5a76049fc4bbf98e0fe Mon Sep 17 00:00:00 2001 From: Rajy Rawther Date: Wed, 4 Dec 2024 11:11:02 -0800 Subject: [PATCH] ffmpeg software decoder implementation (#461) * WIP: ffmpeg based software decoder * minor update * WIP: FFMpeg SW decoder support * FFMpeg decoder working version * working version without threading * multithreading working with FFMpeg decoder * moved ffmpeg decoder to separate folder * updated changelog * minor change * resolved review comments * resolved review comments --------- Co-authored-by: Lakshmi Kumar [ROCm/rocdecode commit: 52eb62fabf0fa0ea85db1c2a663a8478a595fa88] --- projects/rocdecode/CHANGELOG.md | 2 + projects/rocdecode/CMakeLists.txt | 1 + projects/rocdecode/api/rocdecode.h | 3 + .../samples/videoDecode/CMakeLists.txt | 4 +- .../samples/videoDecode/videodecode.cpp | 51 +- .../ffmpegvideodecode/ffmpeg_video_dec.cpp | 857 ++++++++++++++++++ .../ffmpegvideodecode/ffmpeg_video_dec.h | 236 +++++ .../utils/rocvideodecode/roc_video_dec.cpp | 41 +- .../utils/rocvideodecode/roc_video_dec.h | 99 +- 9 files changed, 1213 insertions(+), 81 deletions(-) create mode 100644 projects/rocdecode/utils/ffmpegvideodecode/ffmpeg_video_dec.cpp create mode 100644 projects/rocdecode/utils/ffmpegvideodecode/ffmpeg_video_dec.h diff --git a/projects/rocdecode/CHANGELOG.md b/projects/rocdecode/CHANGELOG.md index 4bf5897696..64ef5a8185 100644 --- a/projects/rocdecode/CHANGELOG.md +++ b/projects/rocdecode/CHANGELOG.md @@ -36,6 +36,8 @@ Full documentation for rocDecode is available at [https://rocm.docs.amd.com/proj * Clang is now the default CXX compiler. * The new minimum supported version of va-api is 1.16. * New build and runtime options have been added to the `rocDecode-setup.py` setup script. +* Added FFMpeg based software decoding into utils. +* Modified videodecode sample to allow FFMpeg based decoding ### Removed diff --git a/projects/rocdecode/CMakeLists.txt b/projects/rocdecode/CMakeLists.txt index 1819e8b093..80eb085ed7 100644 --- a/projects/rocdecode/CMakeLists.txt +++ b/projects/rocdecode/CMakeLists.txt @@ -171,6 +171,7 @@ if(HIP_FOUND AND Libva_FOUND) # install rocDecode samples -- {ROCM_PATH}/share/rocdecode install(DIRECTORY cmake DESTINATION ${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME} COMPONENT dev) install(DIRECTORY utils/rocvideodecode DESTINATION ${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME}/utils COMPONENT dev) + install(DIRECTORY utils/ffmpegvideodecode DESTINATION ${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME}/utils COMPONENT dev) install(FILES samples/videoDecode/CMakeLists.txt samples/videoDecode/README.md samples/videoDecode/videodecode.cpp DESTINATION ${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME}/samples/videoDecode COMPONENT dev) install(FILES samples/videoDecodeMem/CMakeLists.txt samples/videoDecodeMem/README.md samples/videoDecodeMem/videodecodemem.cpp DESTINATION ${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME}/samples/videoDecodeMem COMPONENT dev) install(FILES samples/videoDecodePerf/CMakeLists.txt samples/videoDecodePerf/README.md samples/videoDecodePerf/videodecodeperf.cpp DESTINATION ${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME}/samples/videoDecodePerf COMPONENT dev) diff --git a/projects/rocdecode/api/rocdecode.h b/projects/rocdecode/api/rocdecode.h index d3506e1ec5..72d8ba9217 100644 --- a/projects/rocdecode/api/rocdecode.h +++ b/projects/rocdecode/api/rocdecode.h @@ -107,6 +107,9 @@ typedef enum rocDecVideoSurfaceFormat_enum { rocDecVideoSurfaceFormat_YUV444 = 2, /**< Planar YUV [Y plane followed by U and V planes] */ rocDecVideoSurfaceFormat_YUV444_16Bit = 3, /**< 16 bit Planar YUV [Y plane followed by U and V planes]. Can be used for 10 bit(6LSB bits 0), 12 bit (4LSB bits 0) */ + rocDecVideoSurfaceFormat_YUV420 = 4, /**< Planar YUV [Y plane followed by U and V planes in 4:2:0 format] */ + rocDecVideoSurfaceFormat_YUV420_16Bit = 5, /**< 16 bit Planar YUV [Y plane followed by U and V planes in ]. + Can be used for 10 bit(LSB), 12 bit (LSB) */ } rocDecVideoSurfaceFormat; /**************************************************************************************************************/ diff --git a/projects/rocdecode/samples/videoDecode/CMakeLists.txt b/projects/rocdecode/samples/videoDecode/CMakeLists.txt index 431d689187..b3c07335ad 100644 --- a/projects/rocdecode/samples/videoDecode/CMakeLists.txt +++ b/projects/rocdecode/samples/videoDecode/CMakeLists.txt @@ -72,10 +72,10 @@ if(HIP_FOUND AND FFMPEG_FOUND AND ROCDECODE_FOUND) ${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) + include_directories (${ROCDECODE_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/.. ${CMAKE_CURRENT_SOURCE_DIR}/../../utils ${CMAKE_CURRENT_SOURCE_DIR}/../../utils/rocvideodecode ${CMAKE_CURRENT_SOURCE_DIR}/../../utils/ffmpegvideodecode) set(LINK_LIBRARY_LIST ${LINK_LIBRARY_LIST} ${ROCDECODE_LIBRARY}) # sample app exe - list(APPEND SOURCES ${PROJECT_SOURCE_DIR} videodecode.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../utils/rocvideodecode/roc_video_dec.cpp) + list(APPEND SOURCES ${PROJECT_SOURCE_DIR} videodecode.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../utils/rocvideodecode/roc_video_dec.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../utils/ffmpegvideodecode/ffmpeg_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}) diff --git a/projects/rocdecode/samples/videoDecode/videodecode.cpp b/projects/rocdecode/samples/videoDecode/videodecode.cpp index 1f6d65b12f..eb7d67cfc2 100644 --- a/projects/rocdecode/samples/videoDecode/videodecode.cpp +++ b/projects/rocdecode/samples/videoDecode/videodecode.cpp @@ -38,6 +38,7 @@ THE SOFTWARE. #endif #include "video_demuxer.h" #include "roc_video_dec.h" +#include "ffmpeg_video_dec.h" #include "common.h" void ShowHelpAndExit(const char *option = NULL) { @@ -45,6 +46,7 @@ void ShowHelpAndExit(const char *option = NULL) { << "-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, 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 @@ -68,6 +70,7 @@ int main(int argc, char **argv) { 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; @@ -106,6 +109,14 @@ int main(int argc, char **argv) { 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"); @@ -203,18 +214,30 @@ 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; + RocVideoDecoder *viddec; VideoDemuxer demuxer(input_file_path.c_str()); VideoSeekContext video_seek_ctx; 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; + 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); + } + + if(!viddec->CodecSupported(device_id, rocdec_codec_id, demuxer.GetBitDepth())) { + 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); + 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; @@ -244,9 +267,9 @@ int main(int argc, char **argv) { reconfig_params.p_reconfig_user_struct = &reconfig_user_struct; if (b_generate_md5) { - viddec.InitMd5(); + viddec->InitMd5(); } - viddec.SetReconfigParams(&reconfig_params); + viddec->SetReconfigParams(&reconfig_params); do { auto start_time = std::chrono::high_resolution_clock::now(); @@ -275,22 +298,22 @@ int main(int argc, char **argv) { if (n_video_bytes == 0) { pkg_flags |= ROCDEC_PKT_ENDOFSTREAM; } - n_frame_returned = viddec.DecodeFrame(pvideo, n_video_bytes, pkg_flags, pts, &decoded_pics); + n_frame_returned = viddec->DecodeFrame(pvideo, n_video_bytes, pkg_flags, pts, &decoded_pics); - if (!n_frame && !viddec.GetOutputSurfaceInfo(&surf_info)) { + 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); + pframe = viddec->GetFrame(&pts); if (b_generate_md5) { - viddec.UpdateMd5ForFrame(pframe, surf_info); + viddec->UpdateMd5ForFrame(pframe, surf_info); } if (dump_output_frames && mem_type != OUT_SURFACE_MEM_NOT_MAPPED) { - viddec.SaveFrameToFile(output_file_path, pframe, surf_info); + viddec->SaveFrameToFile(output_file_path, pframe, surf_info); } // release frame - viddec.ReleaseFrame(pts); + viddec->ReleaseFrame(pts); } auto end_time = std::chrono::high_resolution_clock::now(); auto time_per_decode = std::chrono::duration(end_time - start_time).count(); @@ -303,7 +326,7 @@ int main(int argc, char **argv) { } while (n_video_bytes); - n_frame += viddec.GetNumOfFlushedFrames(); + 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) { @@ -320,7 +343,7 @@ int main(int argc, char **argv) { } if (b_generate_md5) { uint8_t *digest; - viddec.FinalizeMd5(&digest); + viddec->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(digest[i]); diff --git a/projects/rocdecode/utils/ffmpegvideodecode/ffmpeg_video_dec.cpp b/projects/rocdecode/utils/ffmpegvideodecode/ffmpeg_video_dec.cpp new file mode 100644 index 0000000000..b60521b357 --- /dev/null +++ b/projects/rocdecode/utils/ffmpegvideodecode/ffmpeg_video_dec.cpp @@ -0,0 +1,857 @@ +/* +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 "ffmpeg_video_dec.h" + +/** + * @brief helper function for inferring AVCodecID from rocDecVideoCodec + * + * @param rocdec_codec + * @return AVCodecID + */ +static inline AVCodecID RocDecVideoCodec2AVCodec(rocDecVideoCodec rocdec_codec) { + switch (rocdec_codec) { + case rocDecVideoCodec_MPEG1 : return AV_CODEC_ID_MPEG1VIDEO; + case rocDecVideoCodec_MPEG2 : return AV_CODEC_ID_MPEG2VIDEO; + case rocDecVideoCodec_MPEG4 : return AV_CODEC_ID_MPEG4; + case rocDecVideoCodec_AVC : return AV_CODEC_ID_H264; + case rocDecVideoCodec_HEVC : return AV_CODEC_ID_HEVC; + case rocDecVideoCodec_VP8 : return AV_CODEC_ID_VP8; + case rocDecVideoCodec_VP9 : return AV_CODEC_ID_VP9; + case rocDecVideoCodec_JPEG : return AV_CODEC_ID_MJPEG; + case rocDecVideoCodec_AV1 : return AV_CODEC_ID_AV1; + default : return AV_CODEC_ID_NONE; + } +} + +static inline float GetChromaWidthFactor(rocDecVideoSurfaceFormat surface_format) { + float factor = 0.5; + switch (surface_format) { + case rocDecVideoSurfaceFormat_NV12: + case rocDecVideoSurfaceFormat_P016: + factor = 1.0; + break; + case rocDecVideoSurfaceFormat_YUV444: + case rocDecVideoSurfaceFormat_YUV444_16Bit: + factor = 1.0; + break; + case rocDecVideoSurfaceFormat_YUV420: + case rocDecVideoSurfaceFormat_YUV420_16Bit: + factor = 0.5; + break; + } + return factor; +}; + + +/** + * @brief helper function for inferring AVCodecID from rocDecVideoSurfaceFormat + * + * @param rocdec_codec + * @return AVCodecID + */ +static inline rocDecVideoSurfaceFormat AVPixelFormat2rocDecVideoSurfaceFormat(AVPixelFormat av_pixel_format) { + switch (av_pixel_format) { + case AV_PIX_FMT_YUV420P : + case AV_PIX_FMT_YUVJ420P : + return rocDecVideoSurfaceFormat_YUV420; + case AV_PIX_FMT_YUV444P : + case AV_PIX_FMT_YUVJ444P : + return rocDecVideoSurfaceFormat_YUV444; + case AV_PIX_FMT_YUV420P10LE : + case AV_PIX_FMT_YUV420P12LE : + return rocDecVideoSurfaceFormat_YUV420_16Bit; + default : + std::cerr << "ERROR: " << av_get_pix_fmt_name(av_pixel_format) << " pixel_format is not supported!" << std::endl; + return rocDecVideoSurfaceFormat_NV12; // for sanity + } +} + +FFMpegVideoDecoder::FFMpegVideoDecoder(int device_id, OutputSurfaceMemoryType out_mem_type, rocDecVideoCodec codec, bool force_zero_latency, + const Rect *p_crop_rect, bool extract_user_sei_Message, uint32_t disp_delay, bool no_multithreading, int max_width, int max_height, uint32_t clk_rate) : + RocVideoDecoder(device_id, out_mem_type, codec, force_zero_latency, p_crop_rect, extract_user_sei_Message, disp_delay, max_width, max_height, clk_rate), no_multithreading_(no_multithreading) { + + if ((out_mem_type_ == OUT_SURFACE_MEM_DEV_INTERNAL) || (out_mem_type_ == OUT_SURFACE_MEM_NOT_MAPPED)) { + THROW("Output Memory Type is not supported"); + } + if (rocdec_parser_) { + rocDecDestroyVideoParser(rocdec_parser_); // need to do this here since it was already created in the base class + // create rocdec videoparser + RocdecParserParams parser_params = {}; + parser_params.codec_type = codec_id_; + parser_params.max_num_decode_surfaces = 1; // let the parser to determine the decode buffer pool size + parser_params.clock_rate = clk_rate; + parser_params.max_display_delay = disp_delay_; + parser_params.user_data = this; + parser_params.pfn_sequence_callback = FFMpegHandleVideoSequenceProc; + parser_params.pfn_decode_picture = FFMpegHandlePictureDecodeProc; + parser_params.pfn_display_picture = FFMpegHandlePictureDisplayProc; + parser_params.pfn_get_sei_msg = b_extract_sei_message_ ? RocVideoDecoder::HandleSEIMessagesProc : NULL; + ROCDEC_API_CALL(rocDecCreateVideoParser(&rocdec_parser_, &parser_params)); + } + if (!no_multithreading_) { + // start the FFMpeg decoding thread + ffmpeg_decoder_thread_ = new std::thread(&FFMpegVideoDecoder::DecodeThread, this); + if (!ffmpeg_decoder_thread_) { + THROW("FFMpegVideoDecoder create thread failed"); + } + } +} + + +FFMpegVideoDecoder::~FFMpegVideoDecoder() { + + std::lock_guard lock(mtx_vp_frame_); + for (auto &p_frame : vp_frames_ffmpeg_) { + if (p_frame.frame_ptr) { + if (out_mem_type_ == OUT_SURFACE_MEM_DEV_COPIED) { + hipError_t hip_status = hipFree(p_frame.frame_ptr); + if (hip_status != hipSuccess) { + std::cerr << "ERROR: hipFree failed! (" << hip_status << ")" << std::endl; + } + } + } + } + + // release frame_q + while (!dec_frames_.empty()) { + av_frame_free(&dec_frames_.back()); + dec_frames_.pop_back(); + } + // free av_packet_data_ + while (!av_packet_data_.empty()) { + std::pair *packet_data = &av_packet_data_.back(); + av_freep(&packet_data->first); + av_packet_data_.pop_back(); + } + + //release av_packets + while (!av_packets_.empty()) { + av_packet_free(&av_packets_.back()); + av_packets_.pop_back(); + } + + if (dec_context_) { + avcodec_free_context(&dec_context_); + } +} + +/* Return value from HandleVideoSequence() are interpreted as : +* 0: fail, 1: succeeded, > 1: override dpb size of parser (set by CUVIDPARSERPARAMS::max_num_decode_surfaces while creating parser) +*/ +int FFMpegVideoDecoder::HandleVideoSequence(RocdecVideoFormat *p_video_format) { + if (p_video_format == nullptr) { + ROCDEC_THROW("Rocdec:: Invalid video format in HandleVideoSequence: ", ROCDEC_INVALID_PARAMETER); + return 0; + } + auto start_time = StartTimer(); + input_video_info_str_.str(""); + input_video_info_str_.clear(); + input_video_info_str_ << "Input Video Information" << std::endl + << "\tCodec : " << GetCodecFmtName(p_video_format->codec) << std::endl; + if (p_video_format->frame_rate.numerator && p_video_format->frame_rate.denominator) { + input_video_info_str_ << "\tFrame rate : " << p_video_format->frame_rate.numerator << "/" << p_video_format->frame_rate.denominator << " = " << 1.0 * p_video_format->frame_rate.numerator / p_video_format->frame_rate.denominator << " fps" << std::endl; + } + input_video_info_str_ << "\tSequence : " << (p_video_format->progressive_sequence ? "Progressive" : "Interlaced") << std::endl + << "\tCoded size : [" << p_video_format->coded_width << ", " << p_video_format->coded_height << "]" << std::endl + << "\tDisplay area : [" << p_video_format->display_area.left << ", " << p_video_format->display_area.top << ", " + << p_video_format->display_area.right << ", " << p_video_format->display_area.bottom << "]" << std::endl + << "\tBit depth : " << p_video_format->bit_depth_luma_minus8 + 8 + ; + input_video_info_str_ << std::endl; + + int num_decode_surfaces = p_video_format->min_num_decode_surfaces; + + // check if the codec is supported in FFMpeg + // Initialize FFMpeg and find the decoder for codec + if (!decoder_) decoder_ = avcodec_find_decoder(RocDecVideoCodec2AVCodec(p_video_format->codec)); + if(!decoder_) { + ROCDEC_THROW("rocDecode:: Codec not supported by FFMpeg ", ROCDEC_NOT_SUPPORTED); + return 0; + } + if (!dec_context_) { + dec_context_ = avcodec_alloc_context3(decoder_); //alloc dec_context_ + if (!dec_context_) { + THROW("Could not allocate video codec context"); + } + // open the codec + if (avcodec_open2(dec_context_, decoder_, NULL) < 0) { + THROW("Could not open codec"); + } + // get the output pixel format from dec_context_ + decoder_pixel_format_ = (dec_context_->pix_fmt == AV_PIX_FMT_NONE) ? AV_PIX_FMT_YUV420P : dec_context_->pix_fmt; + dec_context_->thread_count = 4; + dec_context_->thread_type = FF_THREAD_FRAME; + + } + // allocate av_frame buffer pool for number of surfaces + if (dec_frames_.empty()) { + for (int i = 0; i < num_decode_surfaces; i++) { + AVFrame *p_frame = av_frame_alloc(); + dec_frames_.push_back(p_frame); + } + av_frame_cnt_ = 0; + } + if (av_packet_data_.empty()) { + for (int i = 0; i < num_decode_surfaces; i++) { + uint8_t *pkt_data = static_cast (av_malloc(MAX_AV_PACKET_DATA_SIZE)); + av_packet_data_.push_back(std::make_pair(pkt_data, MAX_AV_PACKET_DATA_SIZE)); + } + } + + // allocate av_packets_ for decoding + if (av_packets_.empty()) { + for (int i = 0; i < num_decode_surfaces; i++) { + AVPacket *pkt = av_packet_alloc(); + pkt->data = static_cast (av_packet_data_[i].first); + pkt->size = av_packet_data_[i].second; + av_packets_.push_back(pkt); + } + } + + if (curr_video_format_ptr_ == nullptr) { + curr_video_format_ptr_ = new RocdecVideoFormat(); + } + // store current video format: this is required to call reconfigure from application in case of random seek + if (curr_video_format_ptr_) memcpy(curr_video_format_ptr_, p_video_format, sizeof(RocdecVideoFormat)); + + if (coded_width_ && coded_height_) { + end_of_stream_ = false; + // rocdecCreateDecoder() has been called before, and now there's possible config change + return ReconfigureDecoder(p_video_format); + } + // e_codec has been set in the constructor (for parser). Here it's set again for potential correction + codec_id_ = p_video_format->codec; + video_chroma_format_ = p_video_format->chroma_format; + bitdepth_minus_8_ = p_video_format->bit_depth_luma_minus8; + byte_per_pixel_ = bitdepth_minus_8_ > 0 ? 2 : 1; + + // convert AVPixelFormat to rocDecVideoChromaFormat + video_surface_format_ = AVPixelFormat2rocDecVideoSurfaceFormat(decoder_pixel_format_); + coded_width_ = p_video_format->coded_width; + coded_height_ = p_video_format->coded_height; + disp_rect_.top = p_video_format->display_area.top; + disp_rect_.bottom = p_video_format->display_area.bottom; + disp_rect_.left = p_video_format->display_area.left; + disp_rect_.right = p_video_format->display_area.right; + disp_width_ = p_video_format->display_area.right - p_video_format->display_area.left; + disp_height_ = p_video_format->display_area.bottom - p_video_format->display_area.top; + + // AV1 has max width/height of sequence in sequence header + if (codec_id_ == rocDecVideoCodec_AV1 && p_video_format->seqhdr_data_length > 0) { + // dont overwrite if it is already set from cmdline or reconfig.txt + if (!(max_width_ > p_video_format->coded_width || max_height_ > p_video_format->coded_height)) { + RocdecVideoFormatEx *vidFormatEx = (RocdecVideoFormatEx *)p_video_format; + max_width_ = vidFormatEx->max_width; + max_height_ = vidFormatEx->max_height; + } + } + if (max_width_ < static_cast(p_video_format->coded_width)) + max_width_ = p_video_format->coded_width; + if (max_height_ < static_cast(p_video_format->coded_height)) + max_height_ = p_video_format->coded_height; + + if (!(crop_rect_.right && crop_rect_.bottom)) { + target_width_ = (disp_width_ + 1) & ~1; + target_height_ = (disp_height_ + 1) & ~1; + } else { + target_width_ = (crop_rect_.right - crop_rect_.left + 1) & ~1; + target_height_ = (crop_rect_.bottom - crop_rect_.top + 1) & ~1; + } + + chroma_height_ = static_cast(ceil(target_height_ * GetChromaHeightFactor(video_surface_format_))); + chroma_width_ = static_cast(ceil(target_width_ * GetChromaWidthFactor(video_surface_format_))); + num_chroma_planes_ = GetChromaPlaneCount(video_surface_format_); + if (video_chroma_format_ == rocDecVideoChromaFormat_Monochrome) num_chroma_planes_ = 0; + surface_stride_ = target_width_ * byte_per_pixel_; + chroma_vstride_ = static_cast(ceil(surface_vstride_ * GetChromaHeightFactor(video_surface_format_))); + // fill output_surface_info_ + output_surface_info_.output_width = target_width_; + output_surface_info_.output_height = target_height_; + output_surface_info_.output_pitch = surface_stride_; + output_surface_info_.output_vstride = target_height_; + output_surface_info_.bit_depth = bitdepth_minus_8_ + 8; + output_surface_info_.bytes_per_pixel = byte_per_pixel_; + output_surface_info_.surface_format = video_surface_format_; + output_surface_info_.num_chroma_planes = num_chroma_planes_; + if (out_mem_type_ == OUT_SURFACE_MEM_DEV_COPIED) { + output_surface_info_.output_surface_size_in_bytes = GetFrameSize(); + output_surface_info_.mem_type = OUT_SURFACE_MEM_DEV_COPIED; + } else if (out_mem_type_ == OUT_SURFACE_MEM_HOST_COPIED){ + output_surface_info_.output_surface_size_in_bytes = GetFrameSize(); + output_surface_info_.mem_type = OUT_SURFACE_MEM_HOST_COPIED; + } + input_video_info_str_ << "Video Decoding Params:" << std::endl + << "\tNum Surfaces : " << num_decode_surfaces << std::endl + << "\tCrop : [" << disp_rect_.left << ", " << disp_rect_.top << ", " + << disp_rect_.right << ", " << disp_rect_.bottom << "]" << std::endl + << "\tResize : " << target_width_ << "x" << target_height_ << std::endl + ; + input_video_info_str_ << std::endl; + std::cout << input_video_info_str_.str(); + + double elapsed_time = StopTimer(start_time); + AddDecoderSessionOverHead(std::this_thread::get_id(), elapsed_time); + return num_decode_surfaces; +} + +/** + * @brief function to reconfigure decoder if there is a change in sequence params. + * + * @param p_video_format + * @return int 1: success 0: fail + */ +int FFMpegVideoDecoder::ReconfigureDecoder(RocdecVideoFormat *p_video_format) { + if (p_video_format->codec != codec_id_) { + ROCDEC_THROW("Reconfigure Not supported for codec change", ROCDEC_NOT_SUPPORTED); + return 0; + } + if (p_video_format->chroma_format != video_chroma_format_) { + ROCDEC_THROW("Reconfigure Not supported for chroma format change", ROCDEC_NOT_SUPPORTED); + return 0; + } + if (p_video_format->bit_depth_luma_minus8 != bitdepth_minus_8_){ + ROCDEC_THROW("Reconfigure Not supported for bit depth change", ROCDEC_NOT_SUPPORTED); + return 0; + } + bool is_decode_res_changed = !(p_video_format->coded_width == coded_width_ && p_video_format->coded_height == coded_height_); + bool is_display_rect_changed = !(p_video_format->display_area.bottom == disp_rect_.bottom && + p_video_format->display_area.top == disp_rect_.top && + p_video_format->display_area.left == disp_rect_.left && + p_video_format->display_area.right == disp_rect_.right); + + if (!is_decode_res_changed && !is_display_rect_changed && !b_force_recofig_flush_) { + return 1; + } + + // Flush and clear internal frame store to reconfigure when either coded size or display size has changed. + if (p_reconfig_params_ && p_reconfig_params_->p_fn_reconfigure_flush) + num_frames_flushed_during_reconfig_ += p_reconfig_params_->p_fn_reconfigure_flush(this, p_reconfig_params_->reconfig_flush_mode, static_cast(p_reconfig_params_->p_reconfig_user_struct)); + // clear the existing output buffers of different size + // note that app lose the remaining frames in the vp_frames in case application didn't set p_fn_reconfigure_flush_ callback + std::lock_guard lock(mtx_vp_frame_); + while(!vp_frames_ffmpeg_.empty()) { + DecFrameBufferFFMpeg *p_frame = &vp_frames_ffmpeg_.back(); + // pop decoded frame + vp_frames_ffmpeg_.pop_back(); + if (p_frame->frame_ptr) { + if (out_mem_type_ == OUT_SURFACE_MEM_DEV_COPIED) { + hipError_t hip_status = hipFree(p_frame->frame_ptr); + if (hip_status != hipSuccess) std::cerr << "ERROR: hipFree failed! (" << hip_status << ")" << std::endl; + } + } + // release associated av_frame + if (p_frame->av_frame_ptr) { + av_frame_free(&p_frame->av_frame_ptr); + } + } + output_frame_cnt_ = 0; // reset frame_count + if (is_decode_res_changed) { + coded_width_ = p_video_format->coded_width; + coded_height_ = p_video_format->coded_height; + } + if (is_display_rect_changed) { + disp_rect_.left = p_video_format->display_area.left; + disp_rect_.right = p_video_format->display_area.right; + disp_rect_.top = p_video_format->display_area.top; + disp_rect_.bottom = p_video_format->display_area.bottom; + disp_width_ = p_video_format->display_area.right - p_video_format->display_area.left; + disp_height_ = p_video_format->display_area.bottom - p_video_format->display_area.top; + + if (!(crop_rect_.right && crop_rect_.bottom)) { + target_width_ = (disp_width_ + 1) & ~1; + target_height_ = (disp_height_ + 1) & ~1; + } else { + target_width_ = (crop_rect_.right - crop_rect_.left + 1) & ~1; + target_height_ = (crop_rect_.bottom - crop_rect_.top + 1) & ~1; + } + } + + surface_stride_ = target_width_ * byte_per_pixel_; + chroma_height_ = static_cast(std::ceil(target_height_ * GetChromaHeightFactor(video_surface_format_))); + chroma_width_ = static_cast(ceil(target_width_ * GetChromaWidthFactor(video_surface_format_))); + num_chroma_planes_ = GetChromaPlaneCount(video_surface_format_); + if (p_video_format->chroma_format == rocDecVideoChromaFormat_Monochrome) num_chroma_planes_ = 0; + chroma_vstride_ = static_cast(std::ceil(surface_vstride_ * GetChromaHeightFactor(video_surface_format_))); + // Fill output_surface_info_ + output_surface_info_.output_width = target_width_; + output_surface_info_.output_height = target_height_; + output_surface_info_.output_pitch = surface_stride_; + output_surface_info_.output_vstride = (out_mem_type_ == OUT_SURFACE_MEM_DEV_INTERNAL) ? surface_vstride_ : target_height_; + output_surface_info_.bit_depth = bitdepth_minus_8_ + 8; + output_surface_info_.bytes_per_pixel = byte_per_pixel_; + output_surface_info_.surface_format = video_surface_format_; + output_surface_info_.num_chroma_planes = num_chroma_planes_; + if (out_mem_type_ == OUT_SURFACE_MEM_DEV_COPIED) { + output_surface_info_.output_surface_size_in_bytes = GetFrameSize(); + output_surface_info_.mem_type = OUT_SURFACE_MEM_DEV_COPIED; + } else if (out_mem_type_ == OUT_SURFACE_MEM_HOST_COPIED) { + output_surface_info_.output_surface_size_in_bytes = GetFrameSize(); + output_surface_info_.mem_type = OUT_SURFACE_MEM_HOST_COPIED; + } + + // If the coded_width or coded_height hasn't changed but display resolution has changed, then need to update width and height for + // correct output with cropping. There is no need to reconfigure the decoder. + if (!is_decode_res_changed && is_display_rect_changed) { + return 1; + } + + input_video_info_str_.str(""); + input_video_info_str_.clear(); + input_video_info_str_ << "Input Video Resolution Changed:" << std::endl + << "\tCoded size : [" << p_video_format->coded_width << ", " << p_video_format->coded_height << "]" << std::endl + << "\tDisplay area : [" << p_video_format->display_area.left << ", " << p_video_format->display_area.top << ", " + << p_video_format->display_area.right << ", " << p_video_format->display_area.bottom << "]" << std::endl; + input_video_info_str_ << std::endl; + is_decoder_reconfigured_ = true; + return 1; +} + +/** + * @brief + * + * @param pPicParams + * @return int 1: success 0: fail + */ +int FFMpegVideoDecoder::HandlePictureDecode(RocdecPicParams *pPicParams) { + AVPacket *av_pkt = av_packets_[av_pkt_cnt_]; + std::pair *packet_data = &av_packet_data_[av_pkt_cnt_]; + if (last_packet_.payload_size > packet_data->second) { + void *new_pkt_data = av_realloc(av_pkt->data, (last_packet_.payload_size + MAX_AV_PACKET_DATA_SIZE)); // add more to avoid frequence reallocation + if (!new_pkt_data) { + std::cerr << "ERROR: couldn't allocate packet data" << std::endl; + } + packet_data->first = static_cast(new_pkt_data); + packet_data->second = (last_packet_.payload_size + MAX_AV_PACKET_DATA_SIZE); + av_pkt->data = packet_data->first; + } + memcpy(av_pkt->data, last_packet_.payload, last_packet_.payload_size); + av_pkt->size = last_packet_.payload_size; + av_pkt->flags = 0; + av_pkt->pts = last_packet_.pts; + + if (no_multithreading_) { + DecodeAvFrame(av_pkt, dec_frames_[av_frame_cnt_]); + } else { + //push packet into packet q for decoding + PushPacket(av_pkt); + } + av_pkt_cnt_ = (av_pkt_cnt_ + 1) % av_packets_.size(); + if (!av_pkt->data || !av_pkt->size) { + end_of_stream_ = true; + } + + return 1; +} + +/** + * @brief function to handle display picture + * + * @param pDispInfo + * @return int 0:fail 1: success + */ +int FFMpegVideoDecoder::HandlePictureDisplay(RocdecParserDispInfo *pDispInfo) { + // check if we reached eos here. This is a hack since HandlePictureDecode won't be called at the end_of_sequece. + // so we need to flush FFMpeg decoder when we have received the lastpacket with 0 bytes + if (!last_packet_.payload_size && !end_of_stream_) { + AVPacket pkt = { 0 }; + if (no_multithreading_) { + DecodeAvFrame(&pkt, dec_frames_[av_frame_cnt_]); + } else { + //push packet into packet q for decoding + PushPacket(&pkt); + } + } + + if (b_extract_sei_message_) { + if (sei_message_display_q_[pDispInfo->picture_index].sei_data) { + // Write SEI Message + uint8_t *sei_buffer = static_cast(sei_message_display_q_[pDispInfo->picture_index].sei_data); + uint32_t sei_num_messages = sei_message_display_q_[pDispInfo->picture_index].sei_message_count; + RocdecSeiMessage *sei_message = sei_message_display_q_[pDispInfo->picture_index].sei_message; + if (fp_sei_) { + for (uint32_t i = 0; i < sei_num_messages; i++) { + if (codec_id_ == rocDecVideoCodec_AVC || rocDecVideoCodec_HEVC) { + switch (sei_message[i].sei_message_type) { + case SEI_TYPE_TIME_CODE: { + //todo:: check if we need to write timecode + } + break; + case SEI_TYPE_USER_DATA_UNREGISTERED: { + fwrite(sei_buffer, sei_message[i].sei_message_size, 1, fp_sei_); + } + break; + } + } + if (codec_id_ == rocDecVideoCodec_AV1) { + fwrite(sei_buffer, sei_message[i].sei_message_size, 1, fp_sei_); + } + sei_buffer += sei_message[i].sei_message_size; + } + } + free(sei_message_display_q_[pDispInfo->picture_index].sei_data); + sei_message_display_q_[pDispInfo->picture_index].sei_data = NULL; // to avoid double free + free(sei_message_display_q_[pDispInfo->picture_index].sei_message); + sei_message_display_q_[pDispInfo->picture_index].sei_message = NULL; // to avoid double free + } + } + // vp_frames_ffmpeg_.size() is empty, wait for decoding to finish + // this will happen during PopFrame() + AVFrame *p_av_frame; + if (no_multithreading_) { + p_av_frame = av_frame_q_.front(); + av_frame_q_.pop(); + } else { + p_av_frame = PopFrame(); + } + + if (p_av_frame == nullptr) { + std::cerr << "Invalid avframe decode output" << std::endl; + return 0; + } + void* src_ptr[3] = { 0 }; + int32_t src_pitch[3] = { 0 }; + src_ptr[0] = p_av_frame->data[0]; + src_ptr[1] = p_av_frame->data[1]; + src_ptr[2] = p_av_frame->data[2]; + src_pitch[0] = p_av_frame->linesize[0]; + src_pitch[1] = p_av_frame->linesize[1]; + src_pitch[2] = p_av_frame->linesize[2]; + + // copy the decoded surface info device or host + uint8_t *p_dec_frame = nullptr; + { + std::lock_guard lock(mtx_vp_frame_); + // if not enough frames in stock, allocate + if (++output_frame_cnt_ > vp_frames_ffmpeg_.size()) { + num_alloced_frames_++; + DecFrameBufferFFMpeg dec_frame = { 0 }; + if (out_mem_type_ == OUT_SURFACE_MEM_DEV_COPIED) { + // allocate device memory + HIP_API_CALL(hipMalloc((void **)&dec_frame.frame_ptr, GetFrameSize())); + } else { + dec_frame.frame_ptr = new uint8_t[GetFrameSize()]; + } + + dec_frame.av_frame_ptr = p_av_frame; + dec_frame.pts = pDispInfo->pts; + dec_frame.picture_index = pDispInfo->picture_index; + vp_frames_ffmpeg_.push_back(dec_frame); + } + p_dec_frame = vp_frames_ffmpeg_[output_frame_cnt_ - 1].frame_ptr; + } + + // Copy luma data + int dst_pitch = disp_width_ * byte_per_pixel_; + uint8_t *p_src_ptr_y = static_cast(src_ptr[0]) + (disp_rect_.top + crop_rect_.top) * src_pitch[0] + (disp_rect_.left + crop_rect_.left) * byte_per_pixel_; + uint8_t *p_frame_y = p_dec_frame; + if (!p_frame_y && !p_src_ptr_y) { + std::cerr << "HandlePictureDisplay: Invalid Memory address for src/dst" << std::endl; + return 0; + } + if (out_mem_type_ == OUT_SURFACE_MEM_DEV_COPIED) { + if (src_pitch[0] == dst_pitch) { + int luma_size = src_pitch[0] * disp_height_; + HIP_API_CALL(hipMemcpyHtoDAsync(p_frame_y, p_src_ptr_y, luma_size, hip_stream_)); + } else { + // use 2d copy to copy an ROI + HIP_API_CALL(hipMemcpy2DAsync(p_frame_y, dst_pitch, p_src_ptr_y, src_pitch[0], dst_pitch, disp_height_, hipMemcpyHostToDevice, hip_stream_)); + } + } else { + if (src_pitch[0] == dst_pitch) { + int luma_size = src_pitch[0] * disp_height_; + memcpy(p_frame_y, p_src_ptr_y, luma_size); + } else { + for (int i = 0; i < disp_height_; i++) { + memcpy(p_dec_frame, p_src_ptr_y, dst_pitch); + p_frame_y += dst_pitch; + p_src_ptr_y += src_pitch[0]; + } + } + } + // Copy chroma plane ( ) + // rocDec output gives pointer to luma and chroma pointers seperated for the decoded frame + uint8_t *p_frame_uv = p_dec_frame + dst_pitch * disp_height_; + uint8_t *p_src_ptr_uv = static_cast(src_ptr[1]) + ((disp_rect_.top + crop_rect_.top) >> 1) * src_pitch[1] + ((disp_rect_.left + crop_rect_.left)>>1) * byte_per_pixel_ ; + dst_pitch = chroma_width_ * byte_per_pixel_; + if (out_mem_type_ == OUT_SURFACE_MEM_DEV_COPIED) { + if (src_pitch[1] == dst_pitch) { + int chroma_size = chroma_height_ * dst_pitch; + HIP_API_CALL(hipMemcpyHtoDAsync(p_frame_uv, p_src_ptr_uv, chroma_size, hip_stream_)); + } else { + // use 2d copy to copy an ROI + HIP_API_CALL(hipMemcpy2DAsync(p_frame_uv, dst_pitch, p_src_ptr_uv, src_pitch[1], dst_pitch, chroma_height_, hipMemcpyHostToDevice, hip_stream_)); + } + } else { + if (src_pitch[1] == dst_pitch) { + int chroma_size = chroma_height_ * dst_pitch; + memcpy(p_frame_uv, p_src_ptr_uv, chroma_size); + } + else { + for (int i = 0; i < chroma_height_; i++) { + memcpy(p_frame_uv, p_src_ptr_uv, dst_pitch); + p_frame_uv += dst_pitch; + p_src_ptr_uv += src_pitch[1]; + } + } + } + + if (num_chroma_planes_ == 2) { + uint8_t *p_frame_v = p_frame_uv + dst_pitch * chroma_height_; + uint8_t *p_src_ptr_v = static_cast(src_ptr[2]) + (disp_rect_.top + crop_rect_.top) * src_pitch[2] + ((disp_rect_.left + crop_rect_.left) >> 1) * byte_per_pixel_; + if (out_mem_type_ == OUT_SURFACE_MEM_DEV_COPIED) { + if (src_pitch[2] == dst_pitch) { + int chroma_size = chroma_height_ * dst_pitch; + HIP_API_CALL(hipMemcpyDtoDAsync(p_frame_v, p_src_ptr_v, chroma_size, hip_stream_)); + } else { + // use 2d copy to copy an ROI + HIP_API_CALL(hipMemcpy2DAsync(p_frame_v, dst_pitch, p_src_ptr_v, src_pitch[2], dst_pitch, chroma_height_, hipMemcpyDeviceToDevice, hip_stream_)); + } + } + else { + if (src_pitch[2] == dst_pitch) { + int chroma_size = chroma_height_ * dst_pitch; + memcpy(p_frame_v, p_src_ptr_v, chroma_size); + } + else { + for (int i = 0; i < chroma_height_; i++) { + memcpy(p_frame_v, p_src_ptr_v, dst_pitch); + p_frame_v += dst_pitch; + p_src_ptr_v += src_pitch[1]; + } + } + } + } + if (out_mem_type_ == OUT_SURFACE_MEM_DEV_COPIED) HIP_API_CALL(hipStreamSynchronize(hip_stream_)); + + return 1; +} + + +int FFMpegVideoDecoder::DecodeFrame(const uint8_t *data, size_t size, int pkt_flags, int64_t pts, int *num_decoded_pics) { + output_frame_cnt_ = 0, output_frame_cnt_ret_ = 0; + decoded_pic_cnt_ = 0; + last_packet_ = { 0 }; + last_packet_.payload = data; + last_packet_.payload_size = size; + last_packet_.flags = pkt_flags | ROCDEC_PKT_TIMESTAMP; + last_packet_.pts = pts; + if (!data || size == 0) { + last_packet_.flags |= ROCDEC_PKT_ENDOFSTREAM; + } + ROCDEC_API_CALL(rocDecParseVideoData(rocdec_parser_, &last_packet_)); + if (num_decoded_pics) { + *num_decoded_pics = decoded_pic_cnt_; + } + return output_frame_cnt_; +} + + +uint8_t* FFMpegVideoDecoder::GetFrame(int64_t *pts) { + if (output_frame_cnt_ > 0) { + std::lock_guard lock(mtx_vp_frame_); + if (vp_frames_ffmpeg_.size() > 0){ + output_frame_cnt_--; + if (pts) *pts = vp_frames_ffmpeg_[output_frame_cnt_ret_].pts; + return vp_frames_ffmpeg_[output_frame_cnt_ret_++].frame_ptr; + } + } + return nullptr; +} + +bool FFMpegVideoDecoder::ReleaseFrame(int64_t pTimestamp, bool b_flushing) { + // if not flushing the buffers are re-used, so keep them + if (out_mem_type_ == OUT_SURFACE_MEM_NOT_MAPPED || !b_flushing) + return true; // nothing to do + if (!b_flushing) + return true; + else { + // remove frames in flushing mode + std::lock_guard lock(mtx_vp_frame_); + DecFrameBufferFFMpeg *fb = &vp_frames_ffmpeg_[0]; + if (pTimestamp != fb->pts) { + std::cerr << "Decoded Frame is released out of order" << std::endl; + return false; + } + av_frame_free(&fb->av_frame_ptr); + vp_frames_ffmpeg_.erase(vp_frames_ffmpeg_.begin()); // get rid of the frames from the framestore + } + return true; +} + +void FFMpegVideoDecoder::InitOutputFrameInfo(AVFrame *p_frame) { + + video_surface_format_ = AVPixelFormat2rocDecVideoSurfaceFormat((AVPixelFormat)p_frame->format); + surface_stride_ = target_width_ * byte_per_pixel_; + chroma_width_ = static_cast(ceil(target_width_ * GetChromaWidthFactor(video_surface_format_))); + chroma_height_ = static_cast(ceil(target_height_ * GetChromaHeightFactor(video_surface_format_))); + num_chroma_planes_ = GetChromaPlaneCount(video_surface_format_); + // Fill output_surface_info_ + output_surface_info_.output_width = target_width_; + output_surface_info_.output_height = target_height_; + output_surface_info_.output_pitch = surface_stride_; + output_surface_info_.output_vstride = target_height_; + output_surface_info_.bit_depth = bitdepth_minus_8_ + 8; + output_surface_info_.bytes_per_pixel = byte_per_pixel_; + output_surface_info_.surface_format = video_surface_format_; + output_surface_info_.num_chroma_planes = num_chroma_planes_; + if (out_mem_type_ == OUT_SURFACE_MEM_DEV_COPIED) { + output_surface_info_.output_surface_size_in_bytes = GetFrameSize(); + output_surface_info_.mem_type = OUT_SURFACE_MEM_DEV_COPIED; + } else if (out_mem_type_ == OUT_SURFACE_MEM_HOST_COPIED) { + output_surface_info_.output_surface_size_in_bytes = GetFrameSize(); + output_surface_info_.mem_type = OUT_SURFACE_MEM_HOST_COPIED; + } +} + +void FFMpegVideoDecoder::DecodeThread() +{ + AVPacket *pkt; + do { + pkt = PopPacket(); + DecodeAvFrame(pkt, dec_frames_[av_frame_cnt_]); + } while (!end_of_stream_); +} + +int FFMpegVideoDecoder::DecodeAvFrame(AVPacket *av_pkt, AVFrame *p_frame) { + int status; + //send packet to av_codec + status = avcodec_send_packet(dec_context_, av_pkt); + if (status < 0) { + std::cout << "Error sending av packet for decoding: status: " << status << std::endl; + } + while (status >= 0) { + status = avcodec_receive_frame(dec_context_, p_frame); + if (status == AVERROR(EAGAIN) || status == AVERROR_EOF) { + end_of_stream_ = (status == AVERROR_EOF); + return 0; + } + else if (status < 0) { + std::cout << "Error during decoding" << std::endl; + return 0; + } + // for the first frame, initialize OutputsurfaceInfo + if (dec_context_->frame_number == 1) { + InitOutputFrameInfo(p_frame); + } + decoded_pic_cnt_++; + + if (no_multithreading_) + av_frame_q_.push(p_frame); + else + PushFrame(p_frame); // add frame to the frame_q + av_frame_cnt_ = (av_frame_cnt_ + 1) % dec_frames_.size(); + p_frame = dec_frames_[av_frame_cnt_]; //advance for next frame decode + } + return 0; +} + + +void FFMpegVideoDecoder::SaveFrameToFile(std::string output_file_name, void *surf_mem, OutputSurfaceInfo *surf_info, size_t rgb_image_size) { + uint8_t *hst_ptr = nullptr; + bool is_rgb = (rgb_image_size != 0); + uint64_t output_image_size = is_rgb ? rgb_image_size : surf_info->output_surface_size_in_bytes; + if (surf_info->mem_type == OUT_SURFACE_MEM_DEV_COPIED) { + if (hst_ptr == nullptr) { + hst_ptr = new uint8_t [output_image_size]; + } + hipError_t hip_status = hipSuccess; + hip_status = hipMemcpyDtoH((void *)hst_ptr, surf_mem, output_image_size); + if (hip_status != hipSuccess) { + std::cerr << "ERROR: hipMemcpyDtoH failed! (" << hipGetErrorName(hip_status) << ")" << std::endl; + delete [] hst_ptr; + return; + } + } else + hst_ptr = static_cast (surf_mem); + + + if (current_output_filename.empty()) { + current_output_filename = output_file_name; + } + + // don't overwrite to the same file if reconfigure is detected for a resolution changes. + if (is_decoder_reconfigured_) { + if (fp_out_) { + fclose(fp_out_); + fp_out_ = nullptr; + } + // Append the width and height of the new stream to the old file name to create a file name to save the new frames + // do this only if resolution changes within a stream (e.g., decoding a multi-resolution stream using the videoDecode app) + // don't append to the output_file_name if multiple output file name is provided (e.g., decoding multi-files using the videDecodeMultiFiles) + if (!current_output_filename.compare(output_file_name)) { + std::string::size_type const pos(output_file_name.find_last_of('.')); + extra_output_file_count_++; + std::string to_append = "_" + std::to_string(surf_info->output_width) + "_" + std::to_string(surf_info->output_height) + "_" + std::to_string(extra_output_file_count_); + if (pos != std::string::npos) { + output_file_name.insert(pos, to_append); + } else { + output_file_name += to_append; + } + } + is_decoder_reconfigured_ = false; + } + + if (fp_out_ == nullptr) { + fp_out_ = fopen(output_file_name.c_str(), "wb"); + } + if (fp_out_) { + if (!is_rgb) { + uint8_t *tmp_hst_ptr = hst_ptr; + int img_width = surf_info->output_width; + int img_height = surf_info->output_height; + int output_stride = surf_info->output_pitch; + if (img_width * surf_info->bytes_per_pixel == output_stride && img_height == surf_info->output_vstride) { + fwrite(hst_ptr, 1, output_image_size, fp_out_); + } else { + uint32_t width = surf_info->output_width * surf_info->bytes_per_pixel; + if (surf_info->bit_depth <= 16) { + for (int i = 0; i < surf_info->output_height; i++) { + fwrite(tmp_hst_ptr, 1, width, fp_out_); + tmp_hst_ptr += output_stride; + } + // dump chroma + uint32_t chroma_stride = (output_stride >> 1); + uint8_t *u_hst_ptr = hst_ptr + output_stride * surf_info->output_height; + uint8_t *v_hst_ptr = u_hst_ptr + chroma_stride * chroma_height_; + for (int i = 0; i < chroma_height_; i++) { + fwrite(u_hst_ptr, 1, chroma_width_, fp_out_); + u_hst_ptr += chroma_stride; + } + if (num_chroma_planes_ == 2) { + for (int i = 0; i < chroma_height_; i++) { + fwrite(v_hst_ptr, 1, chroma_width_, fp_out_); + v_hst_ptr += chroma_stride; + } + } + } + } + } else { + fwrite(hst_ptr, 1, rgb_image_size, fp_out_); + } + } + + if (hst_ptr && (surf_info->mem_type != OUT_SURFACE_MEM_HOST_COPIED)) { + delete [] hst_ptr; + } +} diff --git a/projects/rocdecode/utils/ffmpegvideodecode/ffmpeg_video_dec.h b/projects/rocdecode/utils/ffmpegvideodecode/ffmpeg_video_dec.h new file mode 100644 index 0000000000..5dc8d02fb2 --- /dev/null +++ b/projects/rocdecode/utils/ffmpegvideodecode/ffmpeg_video_dec.h @@ -0,0 +1,236 @@ +/* +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. +*/ + +#pragma once + +extern "C" { + #include + #include + #include + #if USE_AVCODEC_GREATER_THAN_58_134 + #include + #endif +} +#include "rocvideodecode/roc_video_dec.h" // for derived class + +#include +#include +#include +#include + + +#define MAX_AV_PACKET_DATA_SIZE 4096 + +typedef struct DecFrameBufferFFMpeg_ { + AVFrame *av_frame_ptr; /**< av_frame pointer for the decoded frame */ + uint8_t *frame_ptr; /**< host/device memory pointer for the decoded frame depending on mem_type*/ + int64_t pts; /**< timestamp for the decoded frame */ + int picture_index; /**< surface index for the decoded frame */ +} DecFrameBufferFFMpeg; + +typedef struct DecPacketBuffer_ +{ + AVPacket *av_pckt; + int av_frame_index; +} DecPacketBuffer; + +class FFMpegVideoDecoder: public RocVideoDecoder { + public: + /** + * @brief Construct a new FFMpegVideoDecoder object + * + * @param num_threads : number of cpu threads for the decoder + * @param out_mem_type : out_mem_type for the decoded surface + * @param codec : codec type + * @param force_zero_latency : no support in FFMpeg decoding (false) + * @param p_crop_rect : to crop output + * @param extract_user_SEI_Message : enable to extract SEI + * @param disp_delay : output delayed by #disp_delay surfaces + * @param max_width : Max. width for the output surface + * @param max_height : Max. height for the output surface + * @param clk_rate : FPS clock-rate + * @param no_multithreading : run FFMpeg decoder in the main thread (no multithreading) + */ + FFMpegVideoDecoder(int num_threads, OutputSurfaceMemoryType out_mem_type, rocDecVideoCodec codec, bool force_zero_latency = false, + const Rect *p_crop_rect = nullptr, bool extract_user_SEI_Message = false, uint32_t disp_delay = 0, bool no_multithreading = false, int max_width = 0, int max_height = 0, + uint32_t clk_rate = 1000); + /** + * @brief destructor + * + */ + ~FFMpegVideoDecoder(); + + /** + * @brief this function decodes a frame and returns the number of frames avalable for display + * + * @param data - pointer to the compressed data buffer that is to be decoded + * @param size - size of the data buffer in bytes + * @param pts - presentation timestamp + * @param flags - video packet flags + * @param num_decoded_pics - nummber of pictures decoded in this call + * @return int - num of frames to display + */ + int DecodeFrame(const uint8_t *data, size_t size, int pkt_flags, int64_t pts = 0, int *num_decoded_pics = nullptr); + + /** + * @brief This function returns a decoded frame and timestamp. This should be called in a loop fetching all the available frames + * + */ + uint8_t* GetFrame(int64_t *pts); + + /** + * @brief function to release frame after use by the application: Only used with "OUT_SURFACE_MEM_DEV_INTERNAL" + * + * @param pTimestamp - timestamp of the frame to be released (unmapped) + * @param b_flushing - true when flushing + * @return true - success + * @return false - falied + */ + bool ReleaseFrame(int64_t pTimestamp, bool b_flushing = false); + + /** + * @brief Helper function to dump decoded output surface to file + * + * @param output_file_name - Output file name + * @param dev_mem - pointer to surface memory + * @param surf_info - surface info + * @param rgb_image_size - image size for rgb (optional). A non_zero value indicates the surf_mem holds an rgb interleaved image and the entire size will be dumped to file + */ + void SaveFrameToFile(std::string output_file_name, void *surf_mem, OutputSurfaceInfo *surf_info, size_t rgb_image_size = 0); + + /** + * @brief This function is used to get the current frame size based on pixel format. + */ + virtual int GetFrameSize() { assert(disp_width_); return ((disp_width_ * disp_height_) + ((chroma_height_ * chroma_width_) * num_chroma_planes_)) * byte_per_pixel_; } + + private: + /** + * @brief Callback function to be registered for getting a callback when decoding of sequence starts + */ + static int ROCDECAPI FFMpegHandleVideoSequenceProc(void *p_user_data, RocdecVideoFormat *p_video_format) { return ((FFMpegVideoDecoder *)p_user_data)->HandleVideoSequence(p_video_format); } + + /** + * @brief Callback function to be registered for getting a callback when a decoded frame is ready to be decoded + */ + static int ROCDECAPI FFMpegHandlePictureDecodeProc(void *p_user_data, RocdecPicParams *p_pic_params) { return ((FFMpegVideoDecoder *)p_user_data)->HandlePictureDecode(p_pic_params); } + + /** + * @brief Callback function to be registered for getting a callback when a decoded frame is available for display + */ + static int ROCDECAPI FFMpegHandlePictureDisplayProc(void *p_user_data, RocdecParserDispInfo *p_disp_info) { return ((FFMpegVideoDecoder *)p_user_data)->HandlePictureDisplay(p_disp_info); } + + /** + * @brief Callback function to be registered for getting a callback when all the unregistered user SEI Messages are parsed for a frame. + */ + static int ROCDECAPI FFMpegHandleSEIMessagesProc(void *p_user_data, RocdecSeiMessageInfo *p_sei_message_info) { return ((FFMpegVideoDecoder *)p_user_data)->GetSEIMessage(p_sei_message_info); } + + /** + * @brief This function gets called when a sequence is ready to be decoded. The function also gets called + when there is format change + */ + int HandleVideoSequence(RocdecVideoFormat *p_video_format); + + /** + * @brief This function gets called when a picture is ready to be decoded. cuvidDecodePicture is called from this function + * to decode the picture + */ + int HandlePictureDecode(RocdecPicParams *p_pic_params); + + /** + * @brief This function gets called after a picture is decoded and available for display. Frames are fetched and stored in + internal buffer + */ + int HandlePictureDisplay(RocdecParserDispInfo *p_disp_info); + + /** + * @brief This function gets called when all unregistered user SEI messages are parsed for a frame + */ + int GetSEIMessage(RocdecSeiMessageInfo *p_sei_message_info) { return RocVideoDecoder::GetSEIMessage(p_sei_message_info);}; + + /** + * @brief This function reconfigure decoder if there is a change in sequence params. + */ + int ReconfigureDecoder(RocdecVideoFormat *p_video_format); + + void DecodeThread(); + int DecodeAvFrame(AVPacket *av_pkt, AVFrame *p_frame); + void InitOutputFrameInfo(AVFrame *p_frame); + void PushPacket(AVPacket *pkt) { + { + std::lock_guard lock(mtx_pkt_q_); + av_packet_q_.push(pkt); + } + cv_pkt_.notify_one(); + } + + AVPacket *PopPacket() { + AVPacket *pkt; + std::unique_lock lock(mtx_pkt_q_); + cv_pkt_.wait(lock, [&] { return !av_packet_q_.empty(); }); + pkt = av_packet_q_.front(); + av_packet_q_.pop(); + return pkt; + } + + void PushFrame(AVFrame *av_frame) { + { + std::lock_guard lock(mtx_frame_q_); + av_frame_q_.push(av_frame); + } + cv_frame_.notify_one(); + }; + + AVFrame *PopFrame() { + std::unique_lock lock(mtx_frame_q_); + cv_frame_.wait(lock, [&] { return !av_frame_q_.empty() || end_of_stream_; }); + if (end_of_stream_ && av_frame_q_.empty()) + return nullptr; + AVFrame *p_frame = av_frame_q_.front(); + av_frame_q_.pop(); + return p_frame; + } + + typedef enum { CMD_ABORT, CMD_DECODE } CommandType; + typedef enum { STATUS_SUCCESS = 0, STATUS_FAILURE = -1 } StatusType; + + bool no_multithreading_ = false; + uint32_t av_frame_cnt_ = 0; + uint32_t av_pkt_cnt_ = 0; + RocdecSourceDataPacket last_packet_; + std::thread *ffmpeg_decoder_thread_ = nullptr; + std::queue av_packet_q_; // queue for compressed packets + std::queue av_frame_q_; + std::vector vp_frames_ffmpeg_; // vector of decoded frames + std::vector dec_frames_; // vector of AVFrame * for decoded frames + std::vector av_packets_; // store of AVPackets for decoding + std::vector> av_packet_data_; + std::mutex mtx_pkt_q_, mtx_frame_q_; //for command and status + std::condition_variable cv_pkt_, cv_frame_; //for command and status + std::atomic end_of_stream_ = false; + // Variables for FFMpeg decoding + AVCodecContext * dec_context_ = nullptr; + AVPixelFormat decoder_pixel_format_; + AVCodec *decoder_ = nullptr; + AVFormatContext * formatContext = nullptr; + AVInputFormat * inputFormat = nullptr; + AVStream *video = nullptr; +}; diff --git a/projects/rocdecode/utils/rocvideodecode/roc_video_dec.cpp b/projects/rocdecode/utils/rocvideodecode/roc_video_dec.cpp index a7648f1430..066558b52e 100644 --- a/projects/rocdecode/utils/rocvideodecode/roc_video_dec.cpp +++ b/projects/rocdecode/utils/rocvideodecode/roc_video_dec.cpp @@ -196,38 +196,6 @@ static const char * GetVideoChromaFormatName(rocDecVideoChromaFormat e_chroma_fo return "Unknown"; } -static float GetChromaHeightFactor(rocDecVideoSurfaceFormat surface_format) { - float factor = 0.5; - switch (surface_format) { - case rocDecVideoSurfaceFormat_NV12: - case rocDecVideoSurfaceFormat_P016: - factor = 0.5; - break; - case rocDecVideoSurfaceFormat_YUV444: - case rocDecVideoSurfaceFormat_YUV444_16Bit: - factor = 1.0; - break; - } - - return factor; -} - -static int GetChromaPlaneCount(rocDecVideoSurfaceFormat surface_format) { - int num_planes = 1; - switch (surface_format) { - case rocDecVideoSurfaceFormat_NV12: - case rocDecVideoSurfaceFormat_P016: - num_planes = 1; - break; - case rocDecVideoSurfaceFormat_YUV444: - case rocDecVideoSurfaceFormat_YUV444_16Bit: - num_planes = 2; - break; - } - - return num_planes; -} - static void GetSurfaceStrideInternal(rocDecVideoSurfaceFormat surface_format, uint32_t width, uint32_t height, uint32_t *pitch, uint32_t *vstride) { switch (surface_format) { @@ -247,6 +215,15 @@ static void GetSurfaceStrideInternal(rocDecVideoSurfaceFormat surface_format, ui *pitch = align(width, 128) * 2; *vstride = align(height, 16); break; + case rocDecVideoSurfaceFormat_YUV420: + *pitch = align(width, 256); + *vstride = align(height, 16); + break; + case rocDecVideoSurfaceFormat_YUV420_16Bit: + *pitch = align(width, 128) * 2; + *vstride = align(height, 16); + break; + } return; } diff --git a/projects/rocdecode/utils/rocvideodecode/roc_video_dec.h b/projects/rocdecode/utils/rocvideodecode/roc_video_dec.h index aec4125820..21546d3ce2 100644 --- a/projects/rocdecode/utils/rocvideodecode/roc_video_dec.h +++ b/projects/rocdecode/utils/rocvideodecode/roc_video_dec.h @@ -78,6 +78,45 @@ typedef enum OutputSurfaceMemoryType_enum { #endif #define ERR(X) std::cerr << "[ERR] " << " {" << __func__ <<"} " << " " << X << std::endl; +inline int GetChromaPlaneCount(rocDecVideoSurfaceFormat surface_format) { + int num_planes = 1; + switch (surface_format) { + case rocDecVideoSurfaceFormat_NV12: + case rocDecVideoSurfaceFormat_P016: + num_planes = 1; + break; + case rocDecVideoSurfaceFormat_YUV444: + case rocDecVideoSurfaceFormat_YUV444_16Bit: + num_planes = 2; + break; + case rocDecVideoSurfaceFormat_YUV420: + case rocDecVideoSurfaceFormat_YUV420_16Bit: + num_planes = 2; + break; + } + + return num_planes; +}; + +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_YUV444: + case rocDecVideoSurfaceFormat_YUV444_16Bit: + factor = 1.0; + break; + } + + return factor; +}; + + class RocVideoDecodeException : public std::exception { public: @@ -162,22 +201,20 @@ typedef struct ReconfigParams_t { class RocVideoDecoder { public: - /** - * @brief Construct a new Roc Video Decoder object - * - * @param hip_ctx - * @param b_use_device_mem - * @param codec - * @param device_id - * @param b_low_latency - * @param device_frame_pitched - * @param p_crop_rect - * @param extract_user_SEI_Message - * @param max_width - * @param max_height - * @param clk_rate - * @param force_zero_latency - */ + /** + * @brief Construct a new Roc Video Decoder object + * + * @param device_id : device_id to initialize HIP and VCN + * @param out_mem_type : out_mem_type for the decoded surface + * @param codec : codec type + * @param force_zero_latency : to force zero latency (output in decoding orde) + * @param p_crop_rect : to crop output + * @param extract_user_SEI_Message : enable to extract SEI + * @param disp_delay : output delayed by #disp_delay surfaces + * @param max_width : Max. width for the output surface + * @param max_height : Max. height for the output surface + * @param clk_rate : FPS clock-rate + */ RocVideoDecoder(int device_id, OutputSurfaceMemoryType out_mem_type, rocDecVideoCodec codec, bool force_zero_latency = false, const Rect *p_crop_rect = nullptr, bool extract_user_SEI_Message = false, uint32_t disp_delay = 0, int max_width = 0, int max_height = 0, uint32_t clk_rate = 1000); @@ -215,12 +252,8 @@ class RocVideoDecoder { /** * @brief This function is used to get the current frame size based on pixel format. */ - int GetFrameSize() { assert(disp_width_); return disp_width_ * (disp_height_ + (chroma_height_ * num_chroma_planes_)) * byte_per_pixel_; } + virtual int GetFrameSize() { assert(disp_width_); return disp_width_ * (disp_height_ + (chroma_height_ * num_chroma_planes_)) * byte_per_pixel_; } - /** - * @brief This function is used to get the current frame size based on pitch - */ - int GetFrameSizePitched() { assert(surface_stride_); return surface_stride_ * (disp_height_ + (chroma_height_ * num_chroma_planes_)); } /** * @brief Get the Bit Depth and BytesPerPixel associated with the pixel format @@ -285,12 +318,12 @@ class RocVideoDecoder { * @param num_decoded_pics - nummber of pictures decoded in this call * @return int - num of frames to display */ - int DecodeFrame(const uint8_t *data, size_t size, int pkt_flags, int64_t pts = 0, int *num_decoded_pics = nullptr); + virtual int DecodeFrame(const uint8_t *data, size_t size, int pkt_flags, int64_t pts = 0, int *num_decoded_pics = nullptr); /** * @brief This function returns a decoded frame and timestamp. This should be called in a loop fetching all the available frames * */ - uint8_t* GetFrame(int64_t *pts); + virtual uint8_t* GetFrame(int64_t *pts); /** * @brief function to release frame after use by the application: Only used with "OUT_SURFACE_MEM_DEV_INTERNAL" @@ -300,7 +333,7 @@ class RocVideoDecoder { * @return true - success * @return false - falied */ - bool ReleaseFrame(int64_t pTimestamp, bool b_flushing = false); + virtual bool ReleaseFrame(int64_t pTimestamp, bool b_flushing = false); /** * @brief utility function to save image to a file @@ -331,12 +364,12 @@ class RocVideoDecoder { * @param surf_info - surface info * @param rgb_image_size - image size for rgb (optional). A non_zero value indicates the surf_mem holds an rgb interleaved image and the entire size will be dumped to file */ - void SaveFrameToFile(std::string output_file_name, void *surf_mem, OutputSurfaceInfo *surf_info, size_t rgb_image_size = 0); + virtual void SaveFrameToFile(std::string output_file_name, void *surf_mem, OutputSurfaceInfo *surf_info, size_t rgb_image_size = 0); /** * @brief Helper funtion to close a existing file and dump to new file in case of multiple files using same decoder */ - void ResetSaveFrameToFile(); + virtual void ResetSaveFrameToFile(); /** * @brief Helper function to start MD5 calculation @@ -387,7 +420,12 @@ class RocVideoDecoder { */ bool CodecSupported(int device_id, rocDecVideoCodec codec_id, uint32_t bit_depth); - private: + /** + * @brief This function reconfigure decoder if there is a change in sequence params. + */ + virtual int ReconfigureDecoder(RocdecVideoFormat *p_video_format); + + protected: /** * @brief Callback function to be registered for getting a callback when decoding of sequence starts */ @@ -429,11 +467,6 @@ class RocVideoDecoder { * @brief This function gets called when all unregistered user SEI messages are parsed for a frame */ int GetSEIMessage(RocdecSeiMessageInfo *p_sei_message_info); - - /** - * @brief This function reconfigure decoder if there is a change in sequence params. - */ - int ReconfigureDecoder(RocdecVideoFormat *p_video_format); /** * @brief function to release all internal frames and clear the vp_frames_q_ (used with reconfigure): Only used with "OUT_SURFACE_MEM_DEV_INTERNAL" @@ -495,7 +528,7 @@ class RocVideoDecoder { uint32_t target_width_ = 0; uint32_t target_height_ = 0; int max_width_ = 0, max_height_ = 0; - uint32_t chroma_height_ = 0; + uint32_t chroma_height_ = 0, chroma_width_ = 0; uint32_t num_chroma_planes_ = 0; uint32_t num_components_ = 0; uint32_t surface_stride_ = 0;