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 }