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 <lakshmi.kumar@amd.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
924d19eb65
Коммит
52eb62fabf
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**************************************************************************************************************/
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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<double, std::milli>(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<int>(digest[i]);
|
||||
|
||||
@@ -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<std::mutex> 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<uint8_t *, int> *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<FFMpeg>:: 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<uint8_t *> (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<uint8_t *> (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<int>(p_video_format->coded_width))
|
||||
max_width_ = p_video_format->coded_width;
|
||||
if (max_height_ < static_cast<int>(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<int>(ceil(target_height_ * GetChromaHeightFactor(video_surface_format_)));
|
||||
chroma_width_ = static_cast<int>(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<int>(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<void *>(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<std::mutex> 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<int>(std::ceil(target_height_ * GetChromaHeightFactor(video_surface_format_)));
|
||||
chroma_width_ = static_cast<int>(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<int>(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<uint8_t *, int> *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<uint8_t *>(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<uint8_t *>(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<std::mutex> 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<uint8_t *>(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<uint8_t *>(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<uint8_t *>(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<std::mutex> 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<std::mutex> 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<int>(ceil(target_width_ * GetChromaWidthFactor(video_surface_format_)));
|
||||
chroma_height_ = static_cast<int>(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<uint8_t *> (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;
|
||||
}
|
||||
}
|
||||
@@ -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 <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavutil/pixdesc.h>
|
||||
#if USE_AVCODEC_GREATER_THAN_58_134
|
||||
#include <libavcodec/bsf.h>
|
||||
#endif
|
||||
}
|
||||
#include "rocvideodecode/roc_video_dec.h" // for derived class
|
||||
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <queue>
|
||||
#include <atomic>
|
||||
|
||||
|
||||
#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<std::mutex> lock(mtx_pkt_q_);
|
||||
av_packet_q_.push(pkt);
|
||||
}
|
||||
cv_pkt_.notify_one();
|
||||
}
|
||||
|
||||
AVPacket *PopPacket() {
|
||||
AVPacket *pkt;
|
||||
std::unique_lock<std::mutex> 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<std::mutex> lock(mtx_frame_q_);
|
||||
av_frame_q_.push(av_frame);
|
||||
}
|
||||
cv_frame_.notify_one();
|
||||
};
|
||||
|
||||
AVFrame *PopFrame() {
|
||||
std::unique_lock<std::mutex> 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<AVPacket *> av_packet_q_; // queue for compressed packets
|
||||
std::queue<AVFrame *> av_frame_q_;
|
||||
std::vector<DecFrameBufferFFMpeg> vp_frames_ffmpeg_; // vector of decoded frames
|
||||
std::vector<AVFrame *> dec_frames_; // vector of AVFrame * for decoded frames
|
||||
std::vector<AVPacket *> av_packets_; // store of AVPackets for decoding
|
||||
std::vector<std::pair<uint8_t *, int>> 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<bool> 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;
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Ссылка в новой задаче
Block a user