4#include "playbackengine/qffmpegmediadataholder_p.h"
17#include "libavutil/display.h"
28 if (stream.duration > 0)
29 return toTrackDuration(AVStreamDuration(stream.duration), &stream);
34 if (stream.duration < 0 && stream.duration != AV_NOPTS_VALUE) {
35 qCWarning(qLcMediaDataHolder) <<
"AVStream duration" << stream.duration
36 <<
"is invalid. Taking it from the metadata";
39 if (
const auto duration = av_dict_get(stream.metadata,
"DURATION",
nullptr, 0)) {
40 const auto time = QTime::fromString(QString::fromUtf8(duration->value));
41 return TrackDuration(qint64(1000) * time.msecsSinceStartOfDay());
66 auto toRotateMirrorValue = [displayMatrix](
int index) {
69 return displayMatrix[index];
72 return QTransform(toRotateMirrorValue(0), toRotateMirrorValue(1),
73 toRotateMirrorValue(3), toRotateMirrorValue(4),
81 using SideDataSize =
decltype(AVPacketSideData::size);
82 constexpr SideDataSize displayMatrixSize =
sizeof(int32_t) * 9;
83 const AVPacketSideData *sideData = streamSideData(stream, AV_PKT_DATA_DISPLAYMATRIX);
84 if (!sideData || sideData->size < displayMatrixSize)
87 const auto displayMatrix =
reinterpret_cast<
const int32_t *>(sideData->data);
88 const QTransform transform = displayMatrixToTransform(displayMatrix);
89 const VideoTransformationOpt result = qVideoTransformationFromMatrix(transform);
92 <<
"Video stream contains malformed display matrix" << transform;
103 const AVCodecParameters *codecPar = stream->codecpar;
107 const QVideoFrameFormat::ColorTransfer colorTransfer = fromAvColorTransfer(codecPar->color_trc);
112 return colorTransfer == QVideoFrameFormat::ColorTransfer_ST2084
113 || colorTransfer == QVideoFrameFormat::ColorTransfer_STD_B67;
122 const int streamIndex = m_currentAVStreamIndex[QPlatformMediaPlayer::VideoStream];
126 return streamTransformation(m_context->streams[streamIndex]);
131 return m_context.get();
136 return m_currentAVStreamIndex[trackType];
172 case AVMEDIA_TYPE_AUDIO:
173 return QPlatformMediaPlayer::AudioStream;
174 case AVMEDIA_TYPE_VIDEO:
175 return QPlatformMediaPlayer::VideoStream;
176 case AVMEDIA_TYPE_SUBTITLE:
177 return QPlatformMediaPlayer::SubtitleStream;
179 return QPlatformMediaPlayer::NTrackTypes;
184QMaybe<AVFormatContextUPtr, MediaDataHolder::ContextError>
185loadMedia(
const QUrl &mediaUrl, QIODevice *stream,
const std::shared_ptr<
ICancelToken> &cancelToken)
187 const QByteArray url = mediaUrl.toString(QUrl::PreferLocalFile).toUtf8();
189 AVFormatContextUPtr context{ avformat_alloc_context() };
192 if (!stream->isOpen()) {
193 if (!stream->open(QIODevice::ReadOnly))
195 MediaDataHolder::ContextError{
196 QMediaPlayer::ResourceError,
197 QLatin1String(
"Could not open source device.") } };
200 auto seek = &seekQIODevice;
202 if (!stream->isSequential()) {
205 context->ctx_flags |= AVFMTCTX_UNSEEKABLE;
209 constexpr int bufferSize = 32768;
210 unsigned char *buffer = (
unsigned char *)av_malloc(bufferSize);
211 context->pb = avio_alloc_context(buffer, bufferSize,
false, stream, &readQIODevice,
nullptr,
215 AVDictionaryHolder dict;
216 constexpr auto NetworkTimeoutUs =
"5000000";
217 av_dict_set(dict,
"timeout", NetworkTimeoutUs, 0);
219 const QByteArray protocolWhitelist = qgetenv(
"QT_FFMPEG_PROTOCOL_WHITELIST");
220 if (!protocolWhitelist.isNull())
221 av_dict_set(dict,
"protocol_whitelist", protocolWhitelist.data(), 0);
223 context->interrupt_callback.opaque = cancelToken.get();
224 context->interrupt_callback.callback = [](
void *opaque) {
225 const auto *cancelToken =
static_cast<
const ICancelToken *>(opaque);
233 AVFormatContext *contextRaw = context.release();
234 ret = avformat_open_input(&contextRaw, url.constData(),
nullptr, dict);
235 context.reset(contextRaw);
239 auto code = QMediaPlayer::ResourceError;
240 if (ret == AVERROR(EACCES))
241 code = QMediaPlayer::AccessDeniedError;
242 else if (ret == AVERROR(EINVAL) || ret == AVERROR_INVALIDDATA)
243 code = QMediaPlayer::FormatError;
246 <<
"Could not open media. FFmpeg error description:" << err2str(ret);
249 MediaDataHolder::ContextError{ code, QMediaPlayer::tr(
"Could not open file") } };
252 ret = avformat_find_stream_info(context.get(),
nullptr);
255 MediaDataHolder::ContextError{
256 QMediaPlayer::FormatError,
257 QMediaPlayer::tr(
"Could not find stream information for media file") } };
260 if (qLcMediaDataHolder().isInfoEnabled())
261 av_dump_format(context.get(), 0, url.constData(), 0);
272 QMaybe context = loadMedia(url, stream, cancelToken);
275 return QSharedPointer<MediaDataHolder>{
new MediaDataHolder{ std::move(context.value()), cancelToken } };
277 return { unexpect, context.error() };
282 : m_cancelToken{ cancelToken }
286 m_context = std::move(context);
287 m_isSeekable = !(m_context->ctx_flags & AVFMTCTX_UNSEEKABLE);
289 for (
unsigned int i = 0; i < m_context->nb_streams; ++i) {
291 const auto *stream = m_context->streams[i];
292 const auto trackType = trackTypeFromMediaType(stream->codecpar->codec_type);
294 if (trackType == QPlatformMediaPlayer::NTrackTypes)
297 if (stream->disposition & AV_DISPOSITION_ATTACHED_PIC)
300 if (stream->time_base.num <= 0 || stream->time_base.den <= 0) {
302 qCWarning(qLcMediaDataHolder) <<
"A stream for the track type" << trackType
303 <<
"has an invalid timebase:" << stream->time_base;
307 auto metaData = QFFmpegMetaData::fromAVMetaData(stream->metadata);
308 const bool isDefault = stream->disposition & AV_DISPOSITION_DEFAULT;
310 if (trackType != QPlatformMediaPlayer::SubtitleStream) {
313 if (isDefault && m_requestedStreams[trackType] < 0)
314 m_requestedStreams[trackType] = m_streamMap[trackType].size();
317 if (
auto duration = streamDuration(*stream)) {
318 m_duration = qMax(m_duration, *duration);
319 metaData.insert(QMediaMetaData::Duration, toUserDuration(*duration).get());
322 m_streamMap[trackType].append({ (
int)i, isDefault, metaData });
327 if (m_duration == TrackDuration(0) && m_context->duration > 0ll) {
328 m_duration = toTrackDuration(AVContextDuration(m_context->duration));
331 for (
auto trackType :
332 { QPlatformMediaPlayer::VideoStream, QPlatformMediaPlayer::AudioStream }) {
333 auto &requestedStream = m_requestedStreams[trackType];
334 auto &streamMap = m_streamMap[trackType];
336 if (requestedStream < 0 && !streamMap.empty())
339 if (requestedStream >= 0)
340 m_currentAVStreamIndex[trackType] = streamMap[requestedStream].avStreamIndex;
349
350
351
352
353
354
355QImage getAttachedPicture(
const AVFormatContext *context)
360 for (
unsigned int i = 0; i < context->nb_streams; ++i) {
361 const AVStream* stream = context->streams[i];
362 if (!stream || !(stream->disposition & AV_DISPOSITION_ATTACHED_PIC))
365 const AVPacket *compressedImage = &stream->attached_pic;
366 if (!compressedImage || !compressedImage->data || compressedImage->size <= 0)
371 QImage image = QImage::fromData({ compressedImage->data, compressedImage->size });
388 m_metaData = QFFmpegMetaData::fromAVMetaData(m_context->metadata);
389 m_metaData.insert(QMediaMetaData::FileFormat,
390 QVariant::fromValue(QFFmpegMediaFormatInfo::fileFormatForAVInputFormat(
391 *m_context->iformat)));
392 m_metaData.insert(QMediaMetaData::Duration, toUserDuration(m_duration).get());
394 if (!m_cachedThumbnail.has_value())
395 m_cachedThumbnail = getAttachedPicture(m_context.get());
397 if (!m_cachedThumbnail->isNull())
398 m_metaData.insert(QMediaMetaData::ThumbnailImage, m_cachedThumbnail.value());
400 for (
auto trackType :
401 { QPlatformMediaPlayer::AudioStream, QPlatformMediaPlayer::VideoStream }) {
402 const auto streamIndex = m_currentAVStreamIndex[trackType];
403 if (streamIndex >= 0)
404 insertMediaData(m_metaData, trackType, m_context->streams[streamIndex]);
438 QPlatformMediaPlayer::TrackType trackType)
const
440 Q_ASSERT(trackType < QPlatformMediaPlayer::NTrackTypes);
442 return m_streamMap[trackType];
static VideoTransformation streamTransformation(const AVStream *stream)
static bool colorTransferSupportsHdr(const AVStream *stream)
static void insertMediaData(QMediaMetaData &metaData, QPlatformMediaPlayer::TrackType trackType, const AVStream *stream)
static std::optional< TrackDuration > streamDuration(const AVStream &stream)
static QTransform displayMatrixToTransform(const int32_t *displayMatrix)
#define qCWarning(category,...)
#define qCDebug(category,...)
#define Q_STATIC_LOGGING_CATEGORY(name,...)
virtual bool isCancelled() const =0