File indexing completed on 2025-04-27 03:58:10
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2016-04-21 0007 * Description : video thumbnails extraction based on ffmpeg 0008 * 0009 * SPDX-FileCopyrightText: 2010 by Dirk Vanden Boer <dirk dot vdb at gmail dot com> 0010 * SPDX-FileCopyrightText: 2016-2018 by Maik Qualmann <metzpinguin at gmail dot com> 0011 * SPDX-FileCopyrightText: 2016-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0012 * 0013 * SPDX-License-Identifier: GPL-2.0-or-later 0014 * 0015 * ============================================================ */ 0016 0017 #include "videothumbdecoder_p.h" 0018 0019 // Local includes 0020 0021 #include "digikam_debug.h" 0022 0023 namespace Digikam 0024 { 0025 0026 VideoThumbDecoder::VideoThumbDecoder(const QString& filename) 0027 : d(new Private) 0028 { 0029 initialize(filename); 0030 } 0031 0032 VideoThumbDecoder::~VideoThumbDecoder() 0033 { 0034 destroy(); 0035 delete d; 0036 } 0037 0038 void VideoThumbDecoder::initialize(const QString& filename) 0039 { 0040 d->lastWidth = -1; 0041 d->lastHeight = -1; 0042 d->lastPixfmt = AV_PIX_FMT_NONE; 0043 0044 #if LIBAVFORMAT_VERSION_MAJOR < 58 0045 0046 av_register_all(); 0047 0048 #endif 0049 0050 #if LIBAVCODEC_VERSION_MAJOR < 58 0051 0052 avcodec_register_all(); 0053 0054 #endif 0055 0056 if (avformat_open_input(&d->pFormatContext, 0057 filename.toUtf8().data(), nullptr, nullptr) != 0) 0058 { 0059 qDebug(DIGIKAM_GENERAL_LOG) << "Could not open input file: " 0060 << filename; 0061 return; 0062 } 0063 0064 if (avformat_find_stream_info(d->pFormatContext, nullptr) < 0) 0065 { 0066 qDebug(DIGIKAM_GENERAL_LOG) << "Could not find stream information"; 0067 return; 0068 } 0069 0070 if (!d->initializeVideo()) 0071 { 0072 return; 0073 } 0074 0075 d->pFrame = av_frame_alloc(); 0076 0077 if (d->pFrame) 0078 { 0079 d->initialized = true; 0080 } 0081 } 0082 0083 bool VideoThumbDecoder::getInitialized() const 0084 { 0085 return d->initialized; 0086 } 0087 0088 void VideoThumbDecoder::destroy() 0089 { 0090 d->deleteFilterGraph(); 0091 0092 if (d->pVideoCodecContext) 0093 { 0094 avcodec_close(d->pVideoCodecContext); 0095 d->pVideoCodecContext = nullptr; 0096 } 0097 0098 if (d->pFormatContext) 0099 { 0100 avformat_close_input(&d->pFormatContext); 0101 d->pFormatContext = nullptr; 0102 } 0103 0104 if (d->pPacket) 0105 { 0106 av_packet_unref(d->pPacket); 0107 delete d->pPacket; 0108 d->pPacket = nullptr; 0109 } 0110 0111 if (d->pFrame) 0112 { 0113 av_frame_free(&d->pFrame); 0114 d->pFrame = nullptr; 0115 } 0116 0117 if (d->pFrameBuffer) 0118 { 0119 av_free(d->pFrameBuffer); 0120 d->pFrameBuffer = nullptr; 0121 } 0122 } 0123 0124 QString VideoThumbDecoder::getCodec() const 0125 { 0126 QString codecName; 0127 0128 if (d->pVideoCodec) 0129 { 0130 codecName = QLatin1String(d->pVideoCodec->name); 0131 } 0132 0133 return codecName; 0134 } 0135 0136 int VideoThumbDecoder::getWidth() const 0137 { 0138 if (d->pVideoCodecContext) 0139 { 0140 return d->pVideoCodecContext->width; 0141 } 0142 0143 return -1; 0144 } 0145 0146 int VideoThumbDecoder::getHeight() const 0147 { 0148 if (d->pVideoCodecContext) 0149 { 0150 return d->pVideoCodecContext->height; 0151 } 0152 0153 return -1; 0154 } 0155 0156 int VideoThumbDecoder::getDuration() const 0157 { 0158 if (d->pFormatContext) 0159 { 0160 return static_cast<int>(d->pFormatContext->duration / AV_TIME_BASE); 0161 } 0162 0163 return 0; 0164 } 0165 0166 void VideoThumbDecoder::seek(int timeInSeconds) 0167 { 0168 if (!d->allowSeek) 0169 { 0170 return; 0171 } 0172 0173 qint64 timestamp = AV_TIME_BASE * static_cast<qint64>(timeInSeconds); 0174 0175 if (timestamp < 0) 0176 { 0177 timestamp = 0; 0178 } 0179 0180 int ret = av_seek_frame(d->pFormatContext, -1, timestamp, 0181 AVSEEK_FLAG_FRAME | AVSEEK_FLAG_BACKWARD); 0182 0183 if (ret >= 0) 0184 { 0185 avcodec_flush_buffers(d->pVideoCodecContext); 0186 } 0187 else 0188 { 0189 qDebug(DIGIKAM_GENERAL_LOG) << "Seeking in video failed"; 0190 return; 0191 } 0192 0193 int keyFrameAttempts = 0; 0194 bool gotFrame = false; 0195 0196 do 0197 { 0198 int count = 0; 0199 gotFrame = false; 0200 0201 while (!gotFrame && (count < 20)) 0202 { 0203 d->getVideoPacket(); 0204 gotFrame = d->decodeVideoPacket(); 0205 count++; 0206 } 0207 0208 keyFrameAttempts++; 0209 } 0210 while ((!gotFrame || !d->pFrame->key_frame) && 0211 (keyFrameAttempts < 200)); 0212 0213 if (!gotFrame) 0214 { 0215 qDebug(DIGIKAM_GENERAL_LOG) << "Seeking in video failed"; 0216 } 0217 } 0218 0219 bool VideoThumbDecoder::decodeVideoFrame() const 0220 { 0221 bool frameFinished = false; 0222 0223 while (!frameFinished && d->getVideoPacket()) 0224 { 0225 frameFinished = d->decodeVideoPacket(); 0226 } 0227 0228 if (!frameFinished) 0229 { 0230 qDebug(DIGIKAM_GENERAL_LOG) << "decodeVideoFrame() failed: frame not finished"; 0231 } 0232 0233 return frameFinished; 0234 } 0235 0236 void VideoThumbDecoder::getScaledVideoFrame(int scaledSize, 0237 bool maintainAspectRatio, 0238 VideoFrame& videoFrame) 0239 { 0240 if (d->pFrame->interlaced_frame) 0241 { 0242 d->processFilterGraph(d->pFrame, 0243 d->pFrame, 0244 d->pVideoCodecContext->pix_fmt, 0245 d->pVideoCodecContext->width, 0246 d->pVideoCodecContext->height); 0247 } 0248 0249 int scaledWidth, scaledHeight; 0250 d->convertAndScaleFrame(AV_PIX_FMT_RGB24, 0251 scaledSize, 0252 maintainAspectRatio, 0253 scaledWidth, 0254 scaledHeight); 0255 0256 videoFrame.width = scaledWidth; 0257 videoFrame.height = scaledHeight; 0258 videoFrame.lineSize = d->pFrame->linesize[0]; 0259 0260 videoFrame.frameData.clear(); 0261 videoFrame.frameData.resize(videoFrame.lineSize * videoFrame.height); 0262 memcpy((&(videoFrame.frameData.front())), 0263 d->pFrame->data[0], 0264 videoFrame.lineSize * videoFrame.height); 0265 } 0266 0267 } // namespace Digikam