Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qffmpegmediadataholder.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5
8#include "qiodevice.h"
9#include "qdatetime.h"
10#include "qloggingcategory.h"
11
12#include <optional>
13
15
16static Q_LOGGING_CATEGORY(qLcMediaDataHolder, "qt.multimedia.ffmpeg.mediadataholder")
17
18namespace QFFmpeg {
19
20static std::optional<qint64> streamDuration(const AVStream &stream)
21{
22 const auto &factor = stream.time_base;
23
24 if (stream.duration > 0 && factor.num > 0 && factor.den > 0) {
25 return qint64(1000000) * stream.duration * factor.num / factor.den;
26 }
27
28 // In some cases ffmpeg reports negative duration that is definitely invalid.
29 // However, the correct duration may be read from the metadata.
30
31 if (stream.duration < 0) {
32 qCWarning(qLcMediaDataHolder) << "AVStream duration" << stream.duration
33 << "is invalid. Taking it from the metadata";
34 }
35
36 if (const auto duration = av_dict_get(stream.metadata, "DURATION", nullptr, 0)) {
37 const auto time = QTime::fromString(QString::fromUtf8(duration->value));
38 return qint64(1000) * time.msecsSinceStartOfDay();
39 }
40
41 return {};
42}
43
44static void insertMediaData(QMediaMetaData &metaData, QPlatformMediaPlayer::TrackType trackType,
45 const AVStream *stream)
46{
48 const auto *codecPar = stream->codecpar;
49
50 switch (trackType) {
52 metaData.insert(QMediaMetaData::VideoBitRate, (int)codecPar->bit_rate);
55 codecPar->codec_id)));
56 metaData.insert(QMediaMetaData::Resolution, QSize(codecPar->width, codecPar->height));
58 qreal(stream->avg_frame_rate.num) / qreal(stream->avg_frame_rate.den));
59 break;
61 metaData.insert(QMediaMetaData::AudioBitRate, (int)codecPar->bit_rate);
64 codecPar->codec_id)));
65 break;
66 default:
67 break;
68 }
69};
70
71static int readQIODevice(void *opaque, uint8_t *buf, int buf_size)
72{
73 auto *dev = static_cast<QIODevice *>(opaque);
74 if (dev->atEnd())
75 return AVERROR_EOF;
76 return dev->read(reinterpret_cast<char *>(buf), buf_size);
77}
78
79static int64_t seekQIODevice(void *opaque, int64_t offset, int whence)
80{
81 QIODevice *dev = static_cast<QIODevice *>(opaque);
82
83 if (dev->isSequential())
84 return AVERROR(EINVAL);
85
86 if (whence & AVSEEK_SIZE)
87 return dev->size();
88
89 whence &= ~AVSEEK_FORCE;
90
91 if (whence == SEEK_CUR)
92 offset += dev->pos();
93 else if (whence == SEEK_END)
94 offset += dev->size();
95
96 if (!dev->seek(offset))
97 return AVERROR(EINVAL);
98 return offset;
99}
100
101QPlatformMediaPlayer::TrackType MediaDataHolder::trackTypeFromMediaType(int mediaType)
102{
103 switch (mediaType) {
104 case AVMEDIA_TYPE_AUDIO:
106 case AVMEDIA_TYPE_VIDEO:
108 case AVMEDIA_TYPE_SUBTITLE:
110 default:
112 }
113}
114
115std::optional<MediaDataHolder::ContextError>
116MediaDataHolder::recreateAVFormatContext(const QUrl &media, QIODevice *stream)
117{
118 *this = MediaDataHolder{};
119
121
122 AVFormatContext *context = nullptr;
123
124 if (stream) {
125 if (!stream->isOpen()) {
126 if (!stream->open(QIODevice::ReadOnly))
127 return ContextError{ QMediaPlayer::ResourceError,
128 QLatin1String("Could not open source device.") };
129 }
130 if (!stream->isSequential())
131 stream->seek(0);
132 context = avformat_alloc_context();
133
134 constexpr int bufferSize = 32768;
135 unsigned char *buffer = (unsigned char *)av_malloc(bufferSize);
136 context->pb = avio_alloc_context(buffer, bufferSize, false, stream, &readQIODevice, nullptr, &seekQIODevice);
137 }
138
139 int ret = avformat_open_input(&context, url.constData(), nullptr, nullptr);
140 if (ret < 0) {
141 auto code = QMediaPlayer::ResourceError;
142 if (ret == AVERROR(EACCES))
144 else if (ret == AVERROR(EINVAL))
146
147 return ContextError{ code, QMediaPlayer::tr("Could not open file") };
148 }
149
150 ret = avformat_find_stream_info(context, nullptr);
151 if (ret < 0) {
152 avformat_close_input(&context);
153 return ContextError{ QMediaPlayer::FormatError,
154 QMediaPlayer::tr("Could not find stream information for media file") };
155 }
156
157#ifndef QT_NO_DEBUG
158 av_dump_format(context, 0, url.constData(), 0);
159#endif
160
161 m_isSeekable = !(context->ctx_flags & AVFMTCTX_UNSEEKABLE);
162 m_context.reset(context);
163
164 updateStreams();
165 updateMetaData();
166
167 return {};
168}
169
170void MediaDataHolder::updateStreams()
171{
172 m_duration = 0;
173 m_requestedStreams = { -1, -1, -1 };
174 m_currentAVStreamIndex = { -1, -1, -1 };
175 m_streamMap = {};
176
177 if (!m_context) {
178 return;
179 }
180
181 for (unsigned int i = 0; i < m_context->nb_streams; ++i) {
182
183 const auto *stream = m_context->streams[i];
184 const auto trackType = trackTypeFromMediaType(stream->codecpar->codec_type);
185
186 if (trackType == QPlatformMediaPlayer::NTrackTypes)
187 continue;
188
189 auto metaData = QFFmpegMetaData::fromAVMetaData(stream->metadata);
190 const bool isDefault = stream->disposition & AV_DISPOSITION_DEFAULT;
191
192 if (trackType != QPlatformMediaPlayer::SubtitleStream) {
193 insertMediaData(metaData, trackType, stream);
194
195 if (isDefault && m_requestedStreams[trackType] < 0)
196 m_requestedStreams[trackType] = m_streamMap[trackType].size();
197 }
198
199 if (auto duration = streamDuration(*stream)) {
200 m_duration = qMax(m_duration, *duration);
201 metaData.insert(QMediaMetaData::Duration, *duration / qint64(1000));
202 }
203
204 m_streamMap[trackType].append({ (int)i, isDefault, metaData });
205 }
206
207 for (auto trackType :
209 auto &requestedStream = m_requestedStreams[trackType];
210 auto &streamMap = m_streamMap[trackType];
211
212 if (requestedStream < 0 && !streamMap.empty())
213 requestedStream = 0;
214
215 if (requestedStream >= 0)
216 m_currentAVStreamIndex[trackType] = streamMap[requestedStream].avStreamIndex;
217 }
218}
219
220void MediaDataHolder::updateMetaData()
221{
222 m_metaData = {};
223
224 if (!m_context)
225 return;
226
227 m_metaData = QFFmpegMetaData::fromAVMetaData(m_context->metadata);
228 m_metaData.insert(QMediaMetaData::FileFormat,
230 m_context->iformat)));
231 m_metaData.insert(QMediaMetaData::Duration, m_duration / qint64(1000));
232
233 for (auto trackType :
235 const auto streamIndex = m_currentAVStreamIndex[trackType];
236 if (streamIndex >= 0)
237 insertMediaData(m_metaData, trackType, m_context->streams[streamIndex]);
238 }
239}
240
241bool MediaDataHolder::setActiveTrack(QPlatformMediaPlayer::TrackType type, int streamNumber)
242{
243 if (!m_context)
244 return false;
245
246 if (streamNumber < 0 || streamNumber >= m_streamMap[type].size())
247 streamNumber = -1;
248 if (m_requestedStreams[type] == streamNumber)
249 return false;
250 m_requestedStreams[type] = streamNumber;
251 const int avStreamIndex = m_streamMap[type].value(streamNumber).avStreamIndex;
252
253 const int oldIndex = m_currentAVStreamIndex[type];
254 qCDebug(qLcMediaDataHolder) << ">>>>> change track" << type << "from" << oldIndex << "to"
255 << avStreamIndex;
256
257 // TODO: maybe add additional verifications
258 m_currentAVStreamIndex[type] = avStreamIndex;
259
260 updateMetaData();
261
262 return true;
263}
264
265int MediaDataHolder::activeTrack(QPlatformMediaPlayer::TrackType type) const
266{
267 return type < QPlatformMediaPlayer::NTrackTypes ? m_requestedStreams[type] : -1;
268}
269
270const QList<MediaDataHolder::StreamInfo> &MediaDataHolder::streamInfo(
271 QPlatformMediaPlayer::TrackType trackType) const
272{
274
275 return m_streamMap[trackType];
276}
277
278} // namespace QFFmpeg
279
\inmodule QtCore
Definition qbytearray.h:57
static QMediaFormat::VideoCodec videoCodecForAVCodecId(AVCodecID id)
static QMediaFormat::FileFormat fileFormatForAVInputFormat(const AVInputFormat *format)
static QMediaFormat::AudioCodec audioCodecForAVCodecId(AVCodecID id)
static QMediaMetaData fromAVMetaData(const AVDictionary *tags)
\inmodule QtCore \reentrant
Definition qiodevice.h:34
virtual qint64 size() const
For open random-access devices, this function returns the size of the device.
virtual qint64 pos() const
For random-access devices, this function returns the position that data is written to or read from.
virtual bool isSequential() const
Returns true if this device is sequential; otherwise returns false.
virtual bool seek(qint64 pos)
For random-access devices, this function sets the current position to pos, returning true on success,...
qint64 read(char *data, qint64 maxlen)
Reads at most maxSize bytes from the device into data, and returns the number of bytes read.
Definition qlist.h:74
\inmodule QtMultimedia
Q_INVOKABLE void insert(Key k, const QVariant &value)
\qmlmethod void QtMultimedia::mediaMetaData::insert(Key k, variant value) Inserts a value into a Key:...
\inmodule QtCore
Definition qsize.h:25
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5857
QByteArray toUtf8() const &
Definition qstring.h:563
constexpr int msecsSinceStartOfDay() const
Returns the number of msecs since the start of the day, i.e.
Definition qdatetime.h:218
\inmodule QtCore
Definition qurl.h:94
@ PreferLocalFile
Definition qurl.h:114
QString toString(FormattingOptions options=FormattingOptions(PrettyDecoded)) const
Returns a string representation of the URL.
Definition qurl.cpp:2828
static auto fromValue(T &&value) noexcept(std::is_nothrow_copy_constructible_v< T > &&Private::CanUseInternalSpace< T >) -> std::enable_if_t< std::conjunction_v< std::is_copy_constructible< T >, std::is_destructible< T > >, QVariant >
Definition qvariant.h:531
Combined button and popup list for selecting options.
static void * context
EGLStreamKHR stream
#define Q_LOGGING_CATEGORY(name,...)
#define qCWarning(category,...)
#define qCDebug(category,...)
return ret
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLenum GLuint buffer
GLenum type
GLenum GLuint GLenum GLsizei const GLchar * buf
GLenum GLuint GLintptr offset
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
QLatin1StringView QLatin1String
Definition qstringfwd.h:31
long long qint64
Definition qtypes.h:55
double qreal
Definition qtypes.h:92
QUrl url("example.com")
[constructor-url-reference]