FFMpeg decoder rocdecode integration (#583)
* initial commit
* initial implementation for host based decoder
* minor change
* cmake changes and added new sample
* rocdecDecode sample implementation
* rocdecode sample changes working
* working version of avcodec decoder and sample
* Add end of stream handling for repeated decoding with reconfigure
* reorg files and added changelog
* update readme
* revert file
* remove unused class members
* addressed reviw comment for changelog
* fix sample to work on more video files
* resolved review comments
* bumped version to 0.14.0
* fixed build warnings
* addressed review comments
* addressed review comments
* addressed review comments
* fixed readme to match .cpp file options for parameters
* updated review comments, readme, and added test data for the sample
* fixed bug for saving frame for 10 bit videos
* addressed review comments
* addressed all the new review comments
[ROCm/rocdecode commit: 3ddb12f075]
Este cometimento está contido em:
cometido por
GitHub
ascendente
2145992a4d
cometimento
40e072f394
@@ -0,0 +1,435 @@
|
||||
/*
|
||||
Copyright (c) 2023 - 2025 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 "avcodec_videodecoder.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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief helper function for inferring AVCodecID from rocDecVideoSurfaceFormat
|
||||
*
|
||||
* @param AVPixelFormat
|
||||
* @return rocDecVideoChromaFormat
|
||||
*/
|
||||
static inline rocDecVideoChromaFormat AVPixelFormat2rocDecVideoChromaFormat(AVPixelFormat av_pixel_format) {
|
||||
switch (av_pixel_format) {
|
||||
case AV_PIX_FMT_YUV420P :
|
||||
case AV_PIX_FMT_YUVJ420P :
|
||||
case AV_PIX_FMT_YUV420P10LE :
|
||||
case AV_PIX_FMT_YUV420P12LE :
|
||||
return rocDecVideoChromaFormat_420;
|
||||
case AV_PIX_FMT_YUV422P :
|
||||
case AV_PIX_FMT_YUVJ422P :
|
||||
return rocDecVideoChromaFormat_422;
|
||||
case AV_PIX_FMT_YUV444P :
|
||||
case AV_PIX_FMT_YUVJ444P :
|
||||
return rocDecVideoChromaFormat_444;
|
||||
default :
|
||||
std::cerr << "ERROR: " << av_get_pix_fmt_name(av_pixel_format) << " pixel_format is not supported!" << std::endl;
|
||||
return rocDecVideoChromaFormat_420; // for sanity
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief helper function for inferring AVCodecID from rocDecVideoSurfaceFormat
|
||||
*
|
||||
* @param AVPixelFormat
|
||||
* @return AVCodecID
|
||||
*/
|
||||
static inline int BitDepthFromPixelFormat(AVPixelFormat av_pixel_format) {
|
||||
switch (av_pixel_format) {
|
||||
case AV_PIX_FMT_YUV420P :
|
||||
case AV_PIX_FMT_YUVJ420P :
|
||||
return 8;
|
||||
case AV_PIX_FMT_YUV420P10LE :
|
||||
case AV_PIX_FMT_YUV420P12LE :
|
||||
return 16;
|
||||
case AV_PIX_FMT_YUV422P :
|
||||
case AV_PIX_FMT_YUVJ422P :
|
||||
case AV_PIX_FMT_YUV444P :
|
||||
case AV_PIX_FMT_YUVJ444P :
|
||||
return 16;
|
||||
default :
|
||||
std::cerr << "ERROR: " << av_get_pix_fmt_name(av_pixel_format) << " pixel_format is not supported!" << std::endl;
|
||||
return AV_PIX_FMT_YUV420P; // for sanity
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief helper function for inferring AVCodecID from rocDecVideoSurfaceFormat
|
||||
*
|
||||
* @param AVPixelFormat
|
||||
* @return rocDecVideoSurfaceFormat
|
||||
*/
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Constructor
|
||||
*/
|
||||
|
||||
AvcodecVideoDecoder::AvcodecVideoDecoder(RocDecoderHostCreateInfo &decoder_create_info) : decoder_create_info_{decoder_create_info} {
|
||||
|
||||
b_multithreading_ = false; // todo:: remove
|
||||
pfn_sequece_cb_ = decoder_create_info_.pfn_sequence_callback;
|
||||
pfn_display_picture_cb_ = decoder_create_info_.pfn_display_picture;
|
||||
pfn_get_sei_message_cb_ = decoder_create_info_.pfn_get_sei_msg;
|
||||
|
||||
// start the avcodec decoding thread for multi-threading
|
||||
if (b_multithreading_) {
|
||||
ffmpeg_decoder_thread_ = new std::thread(&AvcodecVideoDecoder::DecodeThread, this);
|
||||
if (!ffmpeg_decoder_thread_) {
|
||||
THROW("FFMpegVideoDecoder create thread failed");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AvcodecVideoDecoder::~AvcodecVideoDecoder() {
|
||||
// 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();
|
||||
}
|
||||
// free decoder context
|
||||
if (dec_context_) {
|
||||
avcodec_free_context(&dec_context_);
|
||||
dec_context_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief InitializeDecoder()
|
||||
*
|
||||
* @return rocDecStatus
|
||||
*/
|
||||
rocDecStatus AvcodecVideoDecoder::InitializeDecoder() {
|
||||
if (!decoder_) decoder_ = avcodec_find_decoder(RocDecVideoCodec2AVCodec(decoder_create_info_.codec_type));
|
||||
if(!decoder_) {
|
||||
ERR("rocDecode<FFMpeg>:: Codec not supported by FFMpeg ");
|
||||
return ROCDEC_NOT_SUPPORTED;
|
||||
}
|
||||
if (!dec_context_) {
|
||||
dec_context_ = avcodec_alloc_context3(decoder_); //alloc dec_context_
|
||||
if (!dec_context_) {
|
||||
ERR("Could not allocate video codec context");
|
||||
return ROCDEC_RUNTIME_ERROR;
|
||||
}
|
||||
// set codec to automatically determine how many threads suits best for the decoding job
|
||||
dec_context_->thread_count = decoder_create_info_.num_decode_threads;
|
||||
|
||||
if (decoder_->capabilities & AV_CODEC_CAP_FRAME_THREADS)
|
||||
dec_context_->thread_type = FF_THREAD_FRAME;
|
||||
else if (decoder_->capabilities & AV_CODEC_CAP_SLICE_THREADS)
|
||||
dec_context_->thread_type = FF_THREAD_SLICE;
|
||||
else
|
||||
dec_context_->thread_count = 1;
|
||||
|
||||
// open the codec
|
||||
if (avcodec_open2(dec_context_, decoder_, NULL) < 0) {
|
||||
ERR("Could not open codec");
|
||||
return ROCDEC_RUNTIME_ERROR;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
// set log level to
|
||||
av_log_set_level(AV_LOG_QUIET);
|
||||
// allocate av_frame buffer pool for number of surfaces to be in the decoder pool
|
||||
// Note: with multi-threading, av_codec needs (dec_context_->delay + max_num_B_frames) number of av_frames.
|
||||
// max_num_B_frames is assumed to be 4 here
|
||||
if (dec_frames_.empty()) {
|
||||
for (int i = 0; i < (dec_context_->delay + 4); i++) {
|
||||
AVFrame *p_frame = av_frame_alloc();
|
||||
dec_frames_.push_back(p_frame);
|
||||
}
|
||||
av_frame_cnt_ = 0;
|
||||
}
|
||||
// allocate max. packet_q of 4
|
||||
if (av_packet_data_.empty()) {
|
||||
for (int i = 0; i < 4; 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 < 4; 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);
|
||||
}
|
||||
}
|
||||
disp_rect_.left = decoder_create_info_.display_rect.left;
|
||||
disp_rect_.top = decoder_create_info_.display_rect.top;
|
||||
disp_rect_.right = decoder_create_info_.display_rect.right;
|
||||
disp_rect_.bottom = decoder_create_info_.display_rect.bottom;
|
||||
return ROCDEC_SUCCESS;
|
||||
}
|
||||
|
||||
rocDecStatus AvcodecVideoDecoder::SubmitDecode(RocdecPicParamsHost *pPicParams) {
|
||||
decoded_pic_cnt_ = 0;
|
||||
if (end_of_stream_) {
|
||||
avcodec_flush_buffers(dec_context_);
|
||||
av_pkt_cnt_ = 0;
|
||||
end_of_stream_ = false;
|
||||
}
|
||||
AVPacket *av_pkt = av_packets_[av_pkt_cnt_];
|
||||
std::pair<uint8_t *, int> *packet_data = &av_packet_data_[av_pkt_cnt_];
|
||||
if (pPicParams->bitstream_data_len > packet_data->second) {
|
||||
void *new_pkt_data = av_realloc(av_pkt->data, (pPicParams->bitstream_data_len + MAX_AV_PACKET_DATA_SIZE)); // add more to avoid frequence reallocation
|
||||
if (!new_pkt_data) {
|
||||
ERR("ERROR: couldn't allocate packet data");
|
||||
return ROCDEC_OUTOF_MEMORY;
|
||||
}
|
||||
packet_data->first = static_cast<uint8_t *>(new_pkt_data);
|
||||
packet_data->second = (pPicParams->bitstream_data_len + MAX_AV_PACKET_DATA_SIZE);
|
||||
av_pkt->data = packet_data->first;
|
||||
}
|
||||
memcpy(av_pkt->data, pPicParams->bitstream_data, pPicParams->bitstream_data_len);
|
||||
av_pkt->size = pPicParams->bitstream_data_len;
|
||||
av_pkt->flags = 0;
|
||||
av_pkt->pts = pPicParams->pts;
|
||||
|
||||
if (!b_multithreading_) {
|
||||
// flush and reconfigure the decoder when we reached eos
|
||||
DecodeAvFrame(av_pkt, dec_frames_[av_frame_cnt_]);
|
||||
NotifyPictureDisplay();
|
||||
if ((!pPicParams->bitstream_data_len || pPicParams->flags == ROCDEC_PKT_ENDOFPICTURE) && !end_of_stream_) {
|
||||
AVPacket pkt = {0};
|
||||
DecodeAvFrame(&pkt, dec_frames_[av_frame_cnt_]);
|
||||
NotifyPictureDisplay();
|
||||
}
|
||||
} else {
|
||||
//push packet into packet q for decoding
|
||||
PushPacket(av_pkt);
|
||||
if ((!pPicParams->bitstream_data_len || pPicParams->flags == ROCDEC_PKT_ENDOFPICTURE) && !end_of_stream_) {
|
||||
AVPacket pkt = {0};
|
||||
PushPacket(&pkt);
|
||||
}
|
||||
// display frames
|
||||
if (!disp_frames_q_.empty()) {
|
||||
NotifyPictureDisplay();
|
||||
}
|
||||
}
|
||||
av_pkt_cnt_ = (av_pkt_cnt_ + 1) % av_packets_.size();
|
||||
if (!av_pkt->data || !av_pkt->size) {
|
||||
end_of_stream_ = true;
|
||||
}
|
||||
if (pPicParams->flags & ROCDEC_PKT_ENDOFSTREAM) {
|
||||
// flush last packet and let FFMpeg decode last frames
|
||||
NotifyPictureDisplay();
|
||||
}
|
||||
|
||||
return ROCDEC_SUCCESS;
|
||||
}
|
||||
|
||||
rocDecStatus AvcodecVideoDecoder::GetDecodeStatus(int pic_idx, RocdecDecodeStatus *decode_status) {
|
||||
if (p_disp_frame_ && p_disp_frame_->picture_index == pic_idx)
|
||||
return ROCDEC_SUCCESS;
|
||||
else
|
||||
return ROCDEC_RUNTIME_ERROR;
|
||||
}
|
||||
|
||||
rocDecStatus AvcodecVideoDecoder::GetVideoFrame(int pic_idx, void **frame_ptr, uint32_t *line_size, RocdecProcParams *vid_postproc_params){
|
||||
|
||||
if (p_disp_frame_ == nullptr) {
|
||||
ERR("GetVideoFrame: No frame available to display");
|
||||
return ROCDEC_RUNTIME_ERROR;
|
||||
}
|
||||
if (p_disp_frame_->picture_index != pic_idx) {
|
||||
ERR("GetVideoFrame: pic_index is invalid" );
|
||||
return ROCDEC_INVALID_PARAMETER;
|
||||
}
|
||||
auto p_av_frame = p_disp_frame_->av_frame_ptr;
|
||||
frame_ptr[0] = p_av_frame->data[0];
|
||||
frame_ptr[1] = p_av_frame->data[1];
|
||||
frame_ptr[2] = p_av_frame->data[2];
|
||||
line_size[0] = p_av_frame->linesize[0];
|
||||
line_size[1] = p_av_frame->linesize[1];
|
||||
line_size[2] = p_av_frame->linesize[2];
|
||||
|
||||
return ROCDEC_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
rocDecStatus AvcodecVideoDecoder::ReconfigureDecoder(RocdecReconfigureDecoderInfo *preconfig_params) {
|
||||
rocDecStatus rocdec_status = ROCDEC_SUCCESS;
|
||||
if (preconfig_params == nullptr) {
|
||||
return ROCDEC_INVALID_PARAMETER;
|
||||
}
|
||||
//avcoded can handle reolution changes. So we just need to flush all remaining frames here.
|
||||
bool is_decode_res_changed = !(preconfig_params->width == coded_width_ && preconfig_params->height == coded_height_);
|
||||
if (is_decode_res_changed) {
|
||||
AVPacket pkt = {0};
|
||||
PushPacket(&pkt);
|
||||
NotifyPictureDisplay();
|
||||
}
|
||||
return rocdec_status;
|
||||
}
|
||||
|
||||
void AvcodecVideoDecoder::DecodeThread()
|
||||
{
|
||||
AVPacket *pkt;
|
||||
do {
|
||||
pkt = PopPacket();
|
||||
DecodeAvFrame(pkt, dec_frames_[av_frame_cnt_]);
|
||||
} while (!end_of_stream_);
|
||||
}
|
||||
|
||||
int AvcodecVideoDecoder::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) {
|
||||
ERR("Error sending av packet for decoding: status: ");
|
||||
return status;
|
||||
}
|
||||
while (status >= 0) {
|
||||
status = avcodec_receive_frame(dec_context_, p_frame);
|
||||
if (status == AVERROR(EAGAIN) || status == AVERROR_EOF) {
|
||||
//if (status == AVERROR_EOF) std::cout << "got end of stream from avcodec_receive_frame" << std::endl;
|
||||
end_of_stream_ = (status == AVERROR_EOF);
|
||||
return 0;
|
||||
}
|
||||
else if (status < 0) {
|
||||
ERR("Error during decoding");
|
||||
return 0;
|
||||
}
|
||||
// for the first frame, initialize OutputsurfaceInfo
|
||||
if (p_frame->width != coded_width_ || p_frame->height != coded_height_ || p_frame->format != av_sample_format) {
|
||||
coded_width_ = p_frame->width;
|
||||
coded_height_ = p_frame->height;
|
||||
av_sample_format = p_frame->format;
|
||||
NotifyNewSequence(p_frame);
|
||||
}
|
||||
// push frame into q
|
||||
DecFrameBufferFFMpeg dec_frame = { 0 };
|
||||
dec_frame.av_frame_ptr = p_frame;
|
||||
dec_frame.pts = p_frame->pts;
|
||||
dec_frame.picture_index = av_frame_cnt_; //picture_index is not used here since it is handled within FFMpeg decoder
|
||||
PushDisplayFrame(dec_frame);
|
||||
decoded_pic_cnt_++;
|
||||
|
||||
av_frame_cnt_ = (av_frame_cnt_ + 1) % dec_frames_.size();
|
||||
p_frame = dec_frames_[av_frame_cnt_]; //advance for next frame decode
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
rocDecStatus AvcodecVideoDecoder::NotifyNewSequence(AVFrame *p_frame) {
|
||||
if (!p_frame)
|
||||
return ROCDEC_INVALID_PARAMETER;
|
||||
video_format_host_.video_surface_format = AVPixelFormat2rocDecVideoSurfaceFormat((AVPixelFormat)p_frame->format);
|
||||
RocdecVideoFormat *p_video_format = &video_format_host_.video_format;
|
||||
p_video_format->codec = decoder_create_info_.codec_type;
|
||||
p_video_format->frame_rate.numerator = dec_context_->framerate.num;
|
||||
p_video_format->frame_rate.denominator = dec_context_->framerate.den;
|
||||
p_video_format->bit_depth_luma_minus8 = BitDepthFromPixelFormat(dec_context_->pix_fmt) - 8;
|
||||
p_video_format->bit_depth_chroma_minus8 = p_video_format->bit_depth_luma_minus8;
|
||||
p_video_format->progressive_sequence = !p_frame->interlaced_frame;
|
||||
p_video_format->min_num_decode_surfaces = dec_context_->delay + dec_context_->max_b_frames;
|
||||
p_video_format->coded_width = p_frame->linesize[0];
|
||||
p_video_format->coded_height = p_frame->height;
|
||||
p_video_format->chroma_format = AVPixelFormat2rocDecVideoChromaFormat(dec_context_->pix_fmt);
|
||||
p_video_format->display_area = { 0, 0, p_frame->width, p_frame->height };
|
||||
p_video_format->bitrate = 0;
|
||||
p_video_format->display_aspect_ratio.x = p_frame->sample_aspect_ratio.num;
|
||||
p_video_format->display_aspect_ratio.y = p_frame->sample_aspect_ratio.den;
|
||||
if (pfn_sequece_cb_ && decoder_create_info_.user_data &&
|
||||
pfn_sequece_cb_(decoder_create_info_.user_data, &video_format_host_) == 0) {
|
||||
ERR("Sequence callback function failed.");
|
||||
return ROCDEC_RUNTIME_ERROR;
|
||||
} else {
|
||||
return ROCDEC_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
rocDecStatus AvcodecVideoDecoder::SendSeiMsgPayload(AVFrame *p_frame) {
|
||||
#if 0 //todo
|
||||
sei_message_info_params_.sei_message_count = sei_message_count_;
|
||||
sei_message_info_params_.sei_message = sei_message_list_.data();
|
||||
sei_message_info_params_.sei_data = (void*)sei_payload_buf_;
|
||||
sei_message_info_params_.picIdx = curr_pic_info_.dec_buf_idx;
|
||||
|
||||
// callback function with RocdecSeiMessageInfo params filled out
|
||||
if (pfn_get_sei_message_cb_) pfn_get_sei_message_cb_(parser_params_.user_data, &sei_message_info_params_);
|
||||
#endif
|
||||
return ROCDEC_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
rocDecStatus AvcodecVideoDecoder::NotifyPictureDisplay() {
|
||||
int num_frames_to_display = decoded_pic_cnt_;
|
||||
while (num_frames_to_display) {
|
||||
p_disp_frame_ = GetDisplayFrame();
|
||||
if (p_disp_frame_) {
|
||||
RocdecParserDispInfo dispInfo = {0}; // dispinfo is not used in ffmpeg decoder, so setting it to zero
|
||||
dispInfo.picture_index = p_disp_frame_->picture_index;
|
||||
if (pfn_display_picture_cb_ && decoder_create_info_.user_data) {
|
||||
pfn_display_picture_cb_(decoder_create_info_.user_data, &dispInfo);
|
||||
}
|
||||
}
|
||||
num_frames_to_display--;
|
||||
};
|
||||
|
||||
return ROCDEC_SUCCESS;
|
||||
}
|
||||
Criar uma nova questão referindo esta
Bloquear um utilizador