#include "cutscene/ffmpeg/VideoDecoder.h" #include "tracing/tracing.h" namespace { SwsContext* getSWSContext(int width, int height, AVPixelFormat fmt, AVPixelFormat destination_fmt) { return sws_getContext(width, height, fmt, width, height, destination_fmt, SWS_BILINEAR, nullptr, nullptr, nullptr); } double getFrameTime(int64_t pts, AVRational time_base) { return pts * av_q2d(time_base); } } namespace cutscene { namespace ffmpeg { class FFMPEGVideoFrame: public VideoFrame { size_t _width; size_t _height; AVFrame* _frame; public: FFMPEGVideoFrame(size_t width, size_t height, AVFrame* frame) : _width(width), _height(height), _frame(frame) {} ~FFMPEGVideoFrame() override { if (_frame != nullptr) { av_freep(&_frame->data[0]); av_frame_free(&_frame); } } size_t getPlaneNumber() override { switch (_frame->format) { case AV_PIX_FMT_YUV420P: // YUV data is planar return 3; default: // Everything else is packed return 1; } } FrameSize getPlaneSize(size_t plane) override { switch (_frame->format) { case AV_PIX_FMT_YUV420P: { // YUV data is planar auto width = plane > 0 ? _width / 2 : _width; auto height = plane > 0 ? _height / 2 : _height; return {width, height, (size_t)_frame->linesize[plane]}; } default: // Everything else is packed return {_width, _height, (size_t)_frame->linesize[plane]}; } } void* getPlaneData(size_t plane) override { return _frame->data[plane]; } }; VideoDecoder::VideoDecoder(DecoderStatus* status, AVPixelFormat destination_fmt) : FFMPEGStreamDecoder(status), m_frameId(0), m_destinationFormat(destination_fmt) { m_swsCtx = getSWSContext(m_status->videoCodecPars.width, m_status->videoCodecPars.height, m_status->videoCodecPars.pixel_format, destination_fmt); } VideoDecoder::~VideoDecoder() { sws_freeContext(m_swsCtx); } void VideoDecoder::convertAndPushPicture(const AVFrame* frame) { // Allocate a picture to hold the destination data AVFrame* yuvFrame = av_frame_alloc(); av_frame_copy(yuvFrame, frame); yuvFrame->format = m_destinationFormat; av_image_alloc(yuvFrame->data, yuvFrame->linesize, m_status->videoCodecPars.width, m_status->videoCodecPars.height, m_destinationFormat, 1); if (m_status->videoCodecPars.pixel_format == m_destinationFormat) { av_image_copy(yuvFrame->data, yuvFrame->linesize, (const uint8_t**)(frame->data), frame->linesize, m_destinationFormat, m_status->videoCodecPars.width, m_status->videoCodecPars.height); } else { // Convert frame to destination format sws_scale(m_swsCtx, (uint8_t const* const*)frame->data, frame->linesize, 0, m_status->videoCodecPars.height, yuvFrame->data, yuvFrame->linesize); } std::unique_ptr videoFramePtr( new FFMPEGVideoFrame(static_cast(m_status->videoCodecPars.width), static_cast(m_status->videoCodecPars.height), yuvFrame)); videoFramePtr->id = ++m_frameId; #if LIBAVCODEC_VERSION_INT > AV_VERSION_INT(58, 3, 102) videoFramePtr->frameTime = getFrameTime(frame->best_effort_timestamp, m_status->videoStream->time_base); #else videoFramePtr->frameTime = getFrameTime(av_frame_get_best_effort_timestamp(frame), m_status->videoStream->time_base); #endif pushFrame(VideoFramePtr(videoFramePtr.release())); } void VideoDecoder::decodePacket(AVPacket* packet) { TRACE_SCOPE(tracing::CutsceneFFmpegVideoDecoder); #if LIBAVCODEC_VERSION_INT > AV_VERSION_INT(57, 24, 255) int send_result; do { send_result = avcodec_send_packet(m_status->videoCodecCtx, packet); while(avcodec_receive_frame(m_status->videoCodecCtx, m_decodeFrame) == 0) { convertAndPushPicture(m_decodeFrame); } } while (send_result == AVERROR(EAGAIN)); #else int finishedFrame = 0; auto result = avcodec_decode_video2(m_status->videoCodecCtx, m_decodeFrame, &finishedFrame, packet); if (result >= 0 && finishedFrame) { convertAndPushPicture(m_decodeFrame); } #endif } void VideoDecoder::finishDecoding() { TRACE_SCOPE(tracing::CutsceneFFmpegVideoDecoder); #if LIBAVCODEC_VERSION_INT > AV_VERSION_INT(57, 24, 255) // Send flush packet avcodec_send_packet(m_status->videoCodecCtx, nullptr); // Handle those decoders that have a delay while (true) { auto ret = avcodec_receive_frame(m_status->videoCodecCtx, m_decodeFrame); if (ret == 0) { convertAndPushPicture(m_decodeFrame); } else { // Everything consumed or error break; } } #else // Handle those decoders that have a delay AVPacket nullPacket; memset(&nullPacket, 0, sizeof(nullPacket)); nullPacket.data = nullptr; nullPacket.size = 0; while (true) { int finishedFrame = 1; auto err = avcodec_decode_video2(m_status->videoCodecCtx, m_decodeFrame, &finishedFrame, &nullPacket); if (err < 0 || !finishedFrame) { break; } convertAndPushPicture(m_decodeFrame); } #endif } void VideoDecoder::flushBuffers() { avcodec_flush_buffers(m_status->videoCodecCtx); } } }