File indexing completed on 2025-01-19 04:22:52

0001 /*
0002     SPDX-FileCopyrightText: 2010 Dirk Vanden Boer <dirk.vdb@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "moviedecoder.h"
0008 
0009 #include <QDebug>
0010 #include <QFileInfo>
0011 
0012 extern "C" {
0013 #include <libswscale/swscale.h>
0014 #include <libavutil/imgutils.h>
0015 }
0016 
0017 using namespace std;
0018 
0019 namespace ffmpegthumbnailer
0020 {
0021 
0022 MovieDecoder::MovieDecoder(const QString& filename, AVFormatContext* pavContext)
0023         : m_VideoStream(-1)
0024         , m_pFormatContext(pavContext)
0025         , m_pVideoCodecContext(nullptr)
0026         , m_pVideoCodec(nullptr)
0027 //        , m_pVideoStream(nullptr)
0028         , m_pFrame(nullptr)
0029         , m_pFrameBuffer(nullptr)
0030         , m_pPacket(nullptr)
0031         , m_FormatContextWasGiven(pavContext != nullptr)
0032         , m_AllowSeek(true)
0033         , m_initialized(false)
0034         , m_bufferSinkContext(nullptr)
0035         , m_bufferSourceContext(nullptr)
0036         , m_filterGraph(nullptr)
0037         , m_filterFrame(nullptr)
0038 {
0039     initialize(filename);
0040 }
0041 
0042 MovieDecoder::~MovieDecoder()
0043 {
0044     destroy();
0045 }
0046 
0047 void MovieDecoder::initialize(const QString& filename)
0048 {
0049     m_lastWidth = -1;
0050     m_lastHeight = -1;
0051     m_lastPixfmt = AV_PIX_FMT_NONE;
0052 
0053 #if (LIBAVFORMAT_VERSION_MAJOR < 58)
0054     av_register_all();
0055 #endif
0056 
0057     QFileInfo fileInfo(filename);
0058 
0059     if ((!m_FormatContextWasGiven) && avformat_open_input(&m_pFormatContext, fileInfo.absoluteFilePath().toLocal8Bit().data(), nullptr, nullptr) != 0) {
0060         qDebug() <<  "Could not open input file: " << fileInfo.absoluteFilePath();
0061         return;
0062     }
0063 
0064     if (avformat_find_stream_info(m_pFormatContext, nullptr) < 0) {
0065         qDebug() << "Could not find stream information";
0066         return;
0067     }
0068 
0069     if (!initializeVideo()) {
0070         // It already printed a message
0071         return;
0072     }
0073     m_pFrame = av_frame_alloc();
0074 
0075     if (m_pFrame) {
0076         m_initialized=true;
0077     }
0078 }
0079 
0080 bool MovieDecoder::getInitialized()
0081 {
0082     return m_initialized;
0083 }
0084 
0085 
0086 void MovieDecoder::destroy()
0087 {
0088     deleteFilterGraph();
0089     if (m_pVideoCodecContext) {
0090         avcodec_close(m_pVideoCodecContext);
0091         m_pVideoCodecContext = nullptr;
0092     }
0093 
0094     if ((!m_FormatContextWasGiven) && m_pFormatContext) {
0095         avformat_close_input(&m_pFormatContext);
0096         m_pFormatContext = nullptr;
0097     }
0098 
0099     if (m_pPacket) {
0100         av_packet_unref(m_pPacket);
0101         delete m_pPacket;
0102         m_pPacket = nullptr;
0103     }
0104 
0105     if (m_pFrame) {
0106         av_frame_free(&m_pFrame);
0107         m_pFrame = nullptr;
0108     }
0109 
0110     if (m_pFrameBuffer) {
0111         av_free(m_pFrameBuffer);
0112         m_pFrameBuffer = nullptr;
0113     }
0114 }
0115 
0116 QString MovieDecoder::getCodec()
0117 {
0118     QString codecName;
0119     if (m_pVideoCodec) {
0120         codecName=QString::fromLatin1(m_pVideoCodec->name);
0121     }
0122     return codecName;
0123 }
0124 
0125 bool MovieDecoder::initializeVideo()
0126 {
0127     m_VideoStream = av_find_best_stream(m_pFormatContext, AVMEDIA_TYPE_VIDEO, -1, -1, &m_pVideoCodec, 0);
0128     if (m_VideoStream < 0) {
0129         qDebug() << "Could not find video stream";
0130         return false;
0131     }
0132 
0133     m_pVideoCodecContext = avcodec_alloc_context3(m_pVideoCodec);
0134     avcodec_parameters_to_context(m_pVideoCodecContext, m_pFormatContext->streams[m_VideoStream]->codecpar);
0135 
0136     if (m_pVideoCodec == nullptr) {
0137         // set to nullptr, otherwise avcodec_close(m_pVideoCodecContext) crashes
0138         m_pVideoCodecContext = nullptr;
0139         qDebug() << "Video Codec not found";
0140         return false;
0141     }
0142 
0143     m_pVideoCodecContext->workaround_bugs = 1;
0144 
0145     if (avcodec_open2(m_pVideoCodecContext, m_pVideoCodec, nullptr) < 0) {
0146         qDebug() << "Could not open video codec";
0147         return false;
0148     }
0149 
0150     return true;
0151 }
0152 
0153 int MovieDecoder::getWidth()
0154 {
0155     if (m_pVideoCodecContext) {
0156         return m_pVideoCodecContext->width;
0157     }
0158 
0159     return -1;
0160 }
0161 
0162 int MovieDecoder::getHeight()
0163 {
0164     if (m_pVideoCodecContext) {
0165         return m_pVideoCodecContext->height;
0166     }
0167 
0168     return -1;
0169 }
0170 
0171 int MovieDecoder::getDuration()
0172 {
0173     if (m_pFormatContext) {
0174         return static_cast<int>(m_pFormatContext->duration / AV_TIME_BASE);
0175     }
0176 
0177     return 0;
0178 }
0179 
0180 void MovieDecoder::seek(int timeInSeconds)
0181 {
0182     if (!m_AllowSeek) {
0183         return;
0184     }
0185 
0186     qint64 timestamp = AV_TIME_BASE * static_cast<qint64>(timeInSeconds);
0187 
0188     if (timestamp < 0) {
0189         timestamp = 0;
0190     }
0191 
0192     int ret = av_seek_frame(m_pFormatContext, -1, timestamp, 0);
0193     if (ret >= 0) {
0194         avcodec_flush_buffers(m_pVideoCodecContext);
0195     } else {
0196         qDebug() << "Seeking in video failed";
0197         return;
0198     }
0199 
0200     int keyFrameAttempts = 0;
0201     bool gotFrame = 0;
0202 
0203     do {
0204         int count = 0;
0205         gotFrame = 0;
0206 
0207         while (!gotFrame && count < 20) {
0208             getVideoPacket();
0209             gotFrame = decodeVideoPacket();
0210             ++count;
0211         }
0212 
0213         ++keyFrameAttempts;
0214     } while ((!gotFrame || !m_pFrame->key_frame) && keyFrameAttempts < 200);
0215 
0216     if (gotFrame == 0) {
0217         qDebug() << "Seeking in video failed";
0218     }
0219 }
0220 
0221 
0222 bool MovieDecoder::decodeVideoFrame()
0223 {
0224     bool frameFinished = false;
0225 
0226     while (!frameFinished && getVideoPacket()) {
0227         frameFinished = decodeVideoPacket();
0228     }
0229 
0230     if (!frameFinished) {
0231         qDebug() << "decodeVideoFrame() failed: frame not finished";
0232     }
0233 
0234     return frameFinished;
0235 }
0236 
0237 bool MovieDecoder::decodeVideoPacket()
0238 {
0239     if (m_pPacket->stream_index != m_VideoStream) {
0240         return false;
0241     }
0242 
0243     av_frame_unref(m_pFrame);
0244 
0245     avcodec_send_packet(m_pVideoCodecContext, m_pPacket);
0246     int ret = avcodec_receive_frame(m_pVideoCodecContext, m_pFrame);
0247     if (ret == AVERROR(EAGAIN)) {
0248         return false;
0249     }
0250 
0251     return true;
0252 }
0253 
0254 bool MovieDecoder::getVideoPacket()
0255 {
0256     bool framesAvailable = true;
0257     bool frameDecoded = false;
0258 
0259     int attempts = 0;
0260 
0261     if (m_pPacket) {
0262         av_packet_unref(m_pPacket);
0263         delete m_pPacket;
0264     }
0265 
0266     m_pPacket = new AVPacket();
0267 
0268     while (framesAvailable && !frameDecoded && (attempts++ < 1000)) {
0269         framesAvailable = av_read_frame(m_pFormatContext, m_pPacket) >= 0;
0270         if (framesAvailable) {
0271             frameDecoded = m_pPacket->stream_index == m_VideoStream;
0272             if (!frameDecoded) {
0273                 av_packet_unref(m_pPacket);
0274             }
0275         }
0276     }
0277 
0278     return frameDecoded;
0279 }
0280 
0281 void MovieDecoder::deleteFilterGraph()
0282 {
0283     if (m_filterGraph) {
0284         av_frame_free(&m_filterFrame);
0285         avfilter_graph_free(&m_filterGraph);
0286         m_filterGraph = nullptr;
0287     }
0288 }
0289 
0290 bool MovieDecoder::initFilterGraph(enum AVPixelFormat pixfmt, int width, int height)
0291 {
0292     AVFilterInOut *inputs = nullptr, *outputs = nullptr;
0293 
0294     deleteFilterGraph();
0295     m_filterGraph = avfilter_graph_alloc();
0296 
0297     QByteArray arguments("buffer=");
0298     arguments += "video_size=" + QByteArray::number(width) + 'x' + QByteArray::number(height) + ':';
0299     arguments += "pix_fmt=" + QByteArray::number(pixfmt) + ':';
0300     arguments += "time_base=1/1:pixel_aspect=0/1[in];";
0301     arguments += "[in]yadif[out];";
0302     arguments += "[out]buffersink";
0303 
0304     int ret = avfilter_graph_parse2(m_filterGraph, arguments.constData(), &inputs, &outputs);
0305     if (ret < 0) {
0306         qWarning() << "Unable to parse filter graph";
0307         return false;
0308     }
0309 
0310     if(inputs || outputs)
0311         return -1;
0312 
0313     ret = avfilter_graph_config(m_filterGraph, nullptr);
0314     if (ret < 0) {
0315         qWarning() << "Unable to validate filter graph";
0316         return false;
0317     }
0318 
0319     m_bufferSourceContext = avfilter_graph_get_filter(m_filterGraph, "Parsed_buffer_0");
0320     m_bufferSinkContext = avfilter_graph_get_filter(m_filterGraph, "Parsed_buffersink_2");
0321     if (!m_bufferSourceContext || !m_bufferSinkContext) {
0322         qWarning() << "Unable to get source or sink";
0323         return false;
0324     }
0325     m_filterFrame = av_frame_alloc();
0326     m_lastWidth = width;
0327     m_lastHeight = height;
0328     m_lastPixfmt = pixfmt;
0329 
0330     return true;
0331 }
0332 
0333 bool MovieDecoder::processFilterGraph(AVFrame *dst, const AVFrame *src,
0334                                 enum AVPixelFormat pixfmt, int width, int height)
0335 {
0336     if (!m_filterGraph || width != m_lastWidth ||
0337         height != m_lastHeight || pixfmt != m_lastPixfmt) {
0338 
0339         if (!initFilterGraph(pixfmt, width, height)) {
0340             return false;
0341         }
0342     }
0343 
0344     memcpy(m_filterFrame->data, src->data, sizeof(src->data));
0345     memcpy(m_filterFrame->linesize, src->linesize, sizeof(src->linesize));
0346     m_filterFrame->width = width;
0347     m_filterFrame->height = height;
0348     m_filterFrame->format = pixfmt;
0349 
0350     int ret = av_buffersrc_add_frame(m_bufferSourceContext, m_filterFrame);
0351     if (ret < 0) {
0352         return false;
0353     }
0354 
0355     ret = av_buffersink_get_frame(m_bufferSinkContext, m_filterFrame);
0356     if (ret < 0) {
0357         return false;
0358     }
0359 
0360     av_image_copy(dst->data, dst->linesize, (const uint8_t **)m_filterFrame->data, m_filterFrame->linesize, pixfmt, width, height);
0361     av_frame_unref(m_filterFrame);
0362 
0363     return true;
0364 }
0365 
0366 void MovieDecoder::getScaledVideoFrame(int scaledSize, bool maintainAspectRatio, VideoFrame& videoFrame)
0367 {
0368     if (m_pFrame->interlaced_frame) {
0369         processFilterGraph((AVFrame*) m_pFrame, (AVFrame*) m_pFrame, m_pVideoCodecContext->pix_fmt,
0370                               m_pVideoCodecContext->width, m_pVideoCodecContext->height);
0371     }
0372 
0373     int scaledWidth, scaledHeight;
0374     convertAndScaleFrame(AV_PIX_FMT_RGB24, scaledSize, maintainAspectRatio, scaledWidth, scaledHeight);
0375 
0376     videoFrame.width = scaledWidth;
0377     videoFrame.height = scaledHeight;
0378     videoFrame.lineSize = m_pFrame->linesize[0];
0379 
0380     videoFrame.frameData.clear();
0381     videoFrame.frameData.resize(videoFrame.lineSize * videoFrame.height);
0382     memcpy((&(videoFrame.frameData.front())), m_pFrame->data[0], videoFrame.lineSize * videoFrame.height);
0383 }
0384 
0385 void MovieDecoder::convertAndScaleFrame(AVPixelFormat format, int scaledSize, bool maintainAspectRatio, int& scaledWidth, int& scaledHeight)
0386 {
0387     calculateDimensions(scaledSize, maintainAspectRatio, scaledWidth, scaledHeight);
0388     SwsContext* scaleContext = sws_getContext(m_pVideoCodecContext->width, m_pVideoCodecContext->height,
0389                                m_pVideoCodecContext->pix_fmt, scaledWidth, scaledHeight,
0390                                format, SWS_BICUBIC, nullptr, nullptr, nullptr);
0391 
0392     if (nullptr == scaleContext) {
0393         qDebug() << "Failed to create resize context";
0394         return;
0395     }
0396 
0397     AVFrame* convertedFrame = nullptr;
0398     uint8_t* convertedFrameBuffer = nullptr;
0399 
0400     createAVFrame(&convertedFrame, &convertedFrameBuffer, scaledWidth, scaledHeight, format);
0401 
0402     sws_scale(scaleContext, m_pFrame->data, m_pFrame->linesize, 0, m_pVideoCodecContext->height,
0403               convertedFrame->data, convertedFrame->linesize);
0404     sws_freeContext(scaleContext);
0405 
0406     av_frame_free(&m_pFrame);
0407     av_free(m_pFrameBuffer);
0408 
0409     m_pFrame        = convertedFrame;
0410     m_pFrameBuffer  = convertedFrameBuffer;
0411 }
0412 
0413 void MovieDecoder::calculateDimensions(int squareSize, bool maintainAspectRatio, int& destWidth, int& destHeight)
0414 {
0415     if (!maintainAspectRatio) {
0416         destWidth = squareSize;
0417         destHeight = squareSize;
0418     } else {
0419         int srcWidth            = m_pVideoCodecContext->width;
0420         int srcHeight           = m_pVideoCodecContext->height;
0421         int ascpectNominator    = m_pVideoCodecContext->sample_aspect_ratio.num;
0422         int ascpectDenominator  = m_pVideoCodecContext->sample_aspect_ratio.den;
0423 
0424         if (ascpectNominator != 0 && ascpectDenominator != 0) {
0425             srcWidth = srcWidth * ascpectNominator / ascpectDenominator;
0426         }
0427 
0428         if (srcWidth > srcHeight) {
0429             destWidth  = squareSize;
0430             destHeight = static_cast<int>(static_cast<float>(squareSize) / srcWidth * srcHeight);
0431         } else {
0432             destWidth  = static_cast<int>(static_cast<float>(squareSize) / srcHeight * srcWidth);
0433             destHeight = squareSize;
0434         }
0435     }
0436 }
0437 
0438 void MovieDecoder::createAVFrame(AVFrame** avFrame, quint8** frameBuffer, int width, int height, AVPixelFormat format)
0439 {
0440     *avFrame = av_frame_alloc();
0441 
0442     int numBytes = av_image_get_buffer_size (format, width + 1, height + 1, 16);
0443     *frameBuffer = reinterpret_cast<quint8*>(av_malloc(numBytes));
0444     av_image_fill_arrays ((*avFrame)->data, (*avFrame)->linesize, *frameBuffer, format, width, height, 1);
0445 }
0446 
0447 }