Revision control
++ // 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