Revision control
Copy as Markdown
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "common/qgst_discoverer_p.h"
#include <QtMultimedia/qmediaformat.h>
#include <common/qglist_helper_p.h>
#include <common/qgst_debug_p.h>
#include <common/qgstreamermetadata_p.h>
#include <common/qgstutils_p.h>
#include <uri_handler/qgstreamer_qiodevice_handler_p.h>
QT_BEGIN_NAMESPACE
namespace QGst {
namespace {
template <typename StreamInfo>
struct GstDiscovererStreamInfoList : QGstUtils::GListRangeAdaptor<StreamInfo *>
{
using BaseType = QGstUtils::GListRangeAdaptor<StreamInfo *>;
using BaseType::BaseType;
~GstDiscovererStreamInfoList() { gst_discoverer_stream_info_list_free(BaseType::head); }
};
QGstTagListHandle duplicateTagList(const GstTagList *tagList)
{
if (!tagList)
return {};
return QGstTagListHandle{
gst_tag_list_copy(tagList),
QGstTagListHandle::HasRef,
};
}
QGstDiscovererStreamInfo parseGstDiscovererStreamInfo(GstDiscovererStreamInfo *info)
{
QGstDiscovererStreamInfo result;
result.streamID = QString::fromUtf8(gst_discoverer_stream_info_get_stream_id(info));
result.tags = duplicateTagList(gst_discoverer_stream_info_get_tags(info));
result.streamNumber = gst_discoverer_stream_info_get_stream_number(info);
result.caps = QGstCaps{
gst_discoverer_stream_info_get_caps(info),
QGstCaps::HasRef,
};
return result;
}
template <typename T>
constexpr bool isStreamInfo = std::is_same_v<std::decay_t<T>, GstDiscovererVideoInfo>
|| std::is_same_v<std::decay_t<T>, GstDiscovererAudioInfo>
|| std::is_same_v<std::decay_t<T>, GstDiscovererSubtitleInfo>
|| std::is_same_v<std::decay_t<T>, GstDiscovererContainerInfo>;
template <typename T>
QGstDiscovererStreamInfo parseGstDiscovererStreamInfo(T *info)
{
static_assert(isStreamInfo<T>);
return parseGstDiscovererStreamInfo(GST_DISCOVERER_STREAM_INFO(info));
}
QGstDiscovererVideoInfo parseGstDiscovererVideoInfo(GstDiscovererVideoInfo *info)
{
QGstDiscovererVideoInfo result;
static_cast<QGstDiscovererStreamInfo &>(result) = parseGstDiscovererStreamInfo(info);
result.size = QSize{
int(gst_discoverer_video_info_get_width(info)),
int(gst_discoverer_video_info_get_height(info)),
};
result.bitDepth = gst_discoverer_video_info_get_depth(info);
result.framerate = Fraction{
int(gst_discoverer_video_info_get_framerate_num(info)),
int(gst_discoverer_video_info_get_framerate_denom(info)),
};
result.pixelAspectRatio = Fraction{
int(gst_discoverer_video_info_get_par_num(info)),
int(gst_discoverer_video_info_get_par_denom(info)),
};
result.isInterlaced = gst_discoverer_video_info_is_interlaced(info);
result.bitrate = int(gst_discoverer_video_info_get_bitrate(info));
result.maxBitrate = int(gst_discoverer_video_info_get_max_bitrate(info));
result.isImage = int(gst_discoverer_video_info_is_image(info));
return result;
}
QGstDiscovererAudioInfo parseGstDiscovererAudioInfo(GstDiscovererAudioInfo *info)
{
QGstDiscovererAudioInfo result;
static_cast<QGstDiscovererStreamInfo &>(result) = parseGstDiscovererStreamInfo(info);
result.channels = int(gst_discoverer_audio_info_get_channels(info));
result.channelMask = gst_discoverer_audio_info_get_channel_mask(info);
result.sampleRate = gst_discoverer_audio_info_get_sample_rate(info);
result.bitsPerSample = gst_discoverer_audio_info_get_depth(info);
result.bitrate = int(gst_discoverer_audio_info_get_bitrate(info));
result.maxBitrate = int(gst_discoverer_audio_info_get_max_bitrate(info));
result.language = QGstUtils::codeToLanguage(gst_discoverer_audio_info_get_language(info));
return result;
}
QGstDiscovererSubtitleInfo parseGstDiscovererSubtitleInfo(GstDiscovererSubtitleInfo *info)
{
QGstDiscovererSubtitleInfo result;
static_cast<QGstDiscovererStreamInfo &>(result) = parseGstDiscovererStreamInfo(info);
result.language = QGstUtils::codeToLanguage(gst_discoverer_subtitle_info_get_language(info));
return result;
}
QGstDiscovererContainerInfo parseGstDiscovererContainerInfo(GstDiscovererContainerInfo *info)
{
QGstDiscovererContainerInfo result;
static_cast<QGstDiscovererStreamInfo &>(result) = parseGstDiscovererStreamInfo(info);
result.tags = duplicateTagList(gst_discoverer_container_info_get_tags(info));
return result;
}
QGstDiscovererInfo parseGstDiscovererInfo(GstDiscovererInfo *info)
{
using namespace QGstUtils;
QGstDiscovererInfo result;
result.isLive = gst_discoverer_info_get_live(info);
result.isSeekable = gst_discoverer_info_get_seekable(info);
GstClockTime duration = gst_discoverer_info_get_duration(info);
if (duration != GST_CLOCK_TIME_NONE)
result.duration = std::chrono::nanoseconds{ duration };
QGstDiscovererStreamInfoHandle streamInfo{
gst_discoverer_info_get_stream_info(info),
QGstDiscovererStreamInfoHandle::HasRef,
};
if (streamInfo && GST_IS_DISCOVERER_CONTAINER_INFO(streamInfo.get()))
result.containerInfo =
parseGstDiscovererContainerInfo(GST_DISCOVERER_CONTAINER_INFO(streamInfo.get()));
result.tags = duplicateTagList(gst_discoverer_info_get_tags(info));
GstDiscovererStreamInfoList<GstDiscovererVideoInfo> videoStreams{
gst_discoverer_info_get_video_streams(info),
};
for (GstDiscovererVideoInfo *videoInfo : videoStreams) {
QGstDiscovererVideoInfo item = parseGstDiscovererVideoInfo(videoInfo);
if (!item.streamID.isNull()) // Ignore stream info without stream ID
result.videoStreams.emplace_back(std::move(item));
}
GstDiscovererStreamInfoList<GstDiscovererAudioInfo> audioStreams{
gst_discoverer_info_get_audio_streams(info),
};
for (GstDiscovererAudioInfo *audioInfo : audioStreams) {
QGstDiscovererAudioInfo item = parseGstDiscovererAudioInfo(audioInfo);
if (!item.streamID.isNull()) // Ignore stream info without stream ID
result.audioStreams.emplace_back(std::move(item));
}
GstDiscovererStreamInfoList<GstDiscovererSubtitleInfo> subtitleStreams{
gst_discoverer_info_get_subtitle_streams(info),
};
for (GstDiscovererSubtitleInfo *subtitleInfo : subtitleStreams) {
QGstDiscovererSubtitleInfo item = parseGstDiscovererSubtitleInfo(subtitleInfo);
if (!item.streamID.isNull()) // Ignore stream info without stream ID
result.subtitleStreams.emplace_back(std::move(item));
}
GstDiscovererStreamInfoList<GstDiscovererContainerInfo> containerStreams{
gst_discoverer_info_get_container_streams(info),
};
for (GstDiscovererContainerInfo *containerInfo : containerStreams)
result.containerStreams.emplace_back(parseGstDiscovererContainerInfo(containerInfo));
return result;
}
} // namespace
//----------------------------------------------------------------------------------------------------------------------
static constexpr std::chrono::nanoseconds discovererTimeout = std::chrono::seconds(10);
QGstDiscoverer::QGstDiscoverer()
: m_instance{
gst_discoverer_new(discovererTimeout.count(), nullptr),
QGstDiscovererHandle::HasRef,
}
{
}
q23::expected<QGstDiscovererInfo, QUniqueGErrorHandle> QGstDiscoverer::discover(const QString &uri)
{
return discover(uri.toUtf8().constData());
}
q23::expected<QGstDiscovererInfo, QUniqueGErrorHandle> QGstDiscoverer::discover(const QUrl &url)
{
return discover(url.toEncoded().constData());
}
q23::expected<QGstDiscovererInfo, QUniqueGErrorHandle> QGstDiscoverer::discover(QIODevice *device)
{
return discover(qGstRegisterQIODevice(device));
}
q23::expected<QGstDiscovererInfo, QUniqueGErrorHandle> QGstDiscoverer::discover(const char *uri)
{
QUniqueGErrorHandle error;
QGstDiscovererInfoHandle info{
gst_discoverer_discover_uri(m_instance.get(), uri, &error),
QGstDiscovererInfoHandle::HasRef,
};
if (error)
return q23::unexpected{
std::move(error),
};
QGstDiscovererInfo result = parseGstDiscovererInfo(info.get());
return result;
}
//----------------------------------------------------------------------------------------------------------------------
QMediaMetaData toContainerMetadata(const QGstDiscovererInfo &info)
{
QMediaMetaData metadata;
using Key = QMediaMetaData::Key;
using namespace std::chrono;
if (info.containerInfo)
extendMetaDataFromTagList(metadata, info.containerInfo->tags);
else
extendMetaDataFromTagList(metadata, info.tags);
auto updateMetadata = [&](Key key, const auto &value) {
QVariant currentValue = metadata.value(key);
if (!currentValue.isValid() || currentValue != value)
metadata.insert(key, value);
};
if (info.duration)
updateMetadata(Key::Duration,
QVariant::fromValue(round<milliseconds>(*info.duration).count()));
return metadata;
}
void addMissingKeysFromTaglist(QMediaMetaData &metadata, const QGstTagListHandle &tagList)
{
QMediaMetaData tagMetaData = taglistToMetaData(tagList);
for (auto element : tagMetaData.asKeyValueRange()) {
if (metadata.keys().contains(element.first))
continue;
metadata.insert(element.first, element.second);
}
}
template <typename ValueType>
static void updateMetadata(QMediaMetaData &metadata, QMediaMetaData::Key key, ValueType value)
{
QVariant currentValue = metadata.value(key);
if (!currentValue.isValid() || currentValue != value)
metadata.insert(key, value);
}
QMediaMetaData toStreamMetadata(const QGstDiscovererVideoInfo &info)
{
QMediaMetaData metadata;
using Key = QMediaMetaData::Key;
updateMetadata(metadata, Key::VideoBitRate, info.bitrate);
extendMetaDataFromCaps(metadata, info.caps);
addMissingKeysFromTaglist(metadata, info.tags);
// Get resolution and framerate from info if missing from caps, as seen for mpeg2 format
updateMetadata(metadata, Key::Resolution, info.size);
if (info.framerate.numerator > 0 && info.framerate.denominator > 0)
updateMetadata(metadata, QMediaMetaData::VideoFrameRate,
qreal(info.framerate.numerator) / qreal(info.framerate.denominator));
return metadata;
}
QMediaMetaData toStreamMetadata(const QGstDiscovererAudioInfo &info)
{
QMediaMetaData metadata;
using Key = QMediaMetaData::Key;
updateMetadata(metadata, Key::AudioBitRate, info.bitrate);
updateMetadata(metadata, Key::Language, info.language);
extendMetaDataFromCaps(metadata, info.caps);
addMissingKeysFromTaglist(metadata, info.tags);
return metadata;
}
QMediaMetaData toStreamMetadata(const QGstDiscovererSubtitleInfo &info)
{
QMediaMetaData metadata;
using Key = QMediaMetaData::Key;
updateMetadata(metadata, Key::Language, info.language);
extendMetaDataFromCaps(metadata, info.caps);
addMissingKeysFromTaglist(metadata, info.tags);
return metadata;
}
} // namespace QGst
QT_END_NAMESPACE