1 | // Copyright (C) 2024 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 | |
4 | #include "common/qgst_discoverer_p.h" |
5 | |
6 | #include <QtMultimedia/qmediaformat.h> |
7 | |
8 | #include <common/qglist_helper_p.h> |
9 | #include <common/qgst_debug_p.h> |
10 | #include <common/qgstreamermetadata_p.h> |
11 | #include <common/qgstutils_p.h> |
12 | #include <uri_handler/qgstreamer_qiodevice_handler_p.h> |
13 | |
14 | QT_BEGIN_NAMESPACE |
15 | |
16 | namespace QGst { |
17 | |
18 | namespace { |
19 | |
20 | template <typename StreamInfo> |
21 | struct GstDiscovererStreamInfoList : QGstUtils::GListRangeAdaptor<StreamInfo *> |
22 | { |
23 | using BaseType = QGstUtils::GListRangeAdaptor<StreamInfo *>; |
24 | using BaseType::BaseType; |
25 | |
26 | ~GstDiscovererStreamInfoList() { gst_discoverer_stream_info_list_free(BaseType::head); } |
27 | }; |
28 | |
29 | QGstTagListHandle duplicateTagList(const GstTagList *tagList) |
30 | { |
31 | if (!tagList) |
32 | return {}; |
33 | return QGstTagListHandle{ |
34 | gst_tag_list_copy(tagList), |
35 | QGstTagListHandle::HasRef, |
36 | }; |
37 | } |
38 | |
39 | QGstDiscovererStreamInfo parseGstDiscovererStreamInfo(GstDiscovererStreamInfo *info) |
40 | { |
41 | QGstDiscovererStreamInfo result; |
42 | |
43 | result.streamID = QString::fromUtf8(utf8: gst_discoverer_stream_info_get_stream_id(info)); |
44 | result.tags = duplicateTagList(tagList: gst_discoverer_stream_info_get_tags(info)); |
45 | |
46 | result.streamNumber = gst_discoverer_stream_info_get_stream_number(info); |
47 | |
48 | result.caps = QGstCaps{ |
49 | gst_discoverer_stream_info_get_caps(info), |
50 | QGstCaps::HasRef, |
51 | }; |
52 | |
53 | return result; |
54 | } |
55 | |
56 | template <typename T> |
57 | constexpr bool isStreamInfo = std::is_same_v<std::decay_t<T>, GstDiscovererVideoInfo> |
58 | || std::is_same_v<std::decay_t<T>, GstDiscovererAudioInfo> |
59 | || std::is_same_v<std::decay_t<T>, GstDiscovererSubtitleInfo> |
60 | || std::is_same_v<std::decay_t<T>, GstDiscovererContainerInfo>; |
61 | |
62 | template <typename T> |
63 | QGstDiscovererStreamInfo parseGstDiscovererStreamInfo(T *info) |
64 | { |
65 | static_assert(isStreamInfo<T>); |
66 | return parseGstDiscovererStreamInfo(GST_DISCOVERER_STREAM_INFO(info)); |
67 | } |
68 | |
69 | QGstDiscovererVideoInfo parseGstDiscovererVideoInfo(GstDiscovererVideoInfo *info) |
70 | { |
71 | QGstDiscovererVideoInfo result; |
72 | static_cast<QGstDiscovererStreamInfo &>(result) = parseGstDiscovererStreamInfo(info); |
73 | |
74 | result.size = QSize{ |
75 | int(gst_discoverer_video_info_get_width(info)), |
76 | int(gst_discoverer_video_info_get_height(info)), |
77 | }; |
78 | |
79 | result.bitDepth = gst_discoverer_video_info_get_depth(info); |
80 | result.framerate = Fraction{ |
81 | .numerator: int(gst_discoverer_video_info_get_framerate_num(info)), |
82 | .denominator: int(gst_discoverer_video_info_get_framerate_denom(info)), |
83 | }; |
84 | |
85 | result.pixelAspectRatio = Fraction{ |
86 | .numerator: int(gst_discoverer_video_info_get_par_num(info)), |
87 | .denominator: int(gst_discoverer_video_info_get_par_denom(info)), |
88 | }; |
89 | |
90 | result.isInterlaced = gst_discoverer_video_info_is_interlaced(info); |
91 | result.bitrate = int(gst_discoverer_video_info_get_bitrate(info)); |
92 | result.maxBitrate = int(gst_discoverer_video_info_get_max_bitrate(info)); |
93 | result.isImage = int(gst_discoverer_video_info_is_image(info)); |
94 | |
95 | return result; |
96 | } |
97 | |
98 | QGstDiscovererAudioInfo parseGstDiscovererAudioInfo(GstDiscovererAudioInfo *info) |
99 | { |
100 | QGstDiscovererAudioInfo result; |
101 | static_cast<QGstDiscovererStreamInfo &>(result) = parseGstDiscovererStreamInfo(info); |
102 | |
103 | result.channels = int(gst_discoverer_audio_info_get_channels(info)); |
104 | result.channelMask = gst_discoverer_audio_info_get_channel_mask(info); |
105 | result.sampleRate = gst_discoverer_audio_info_get_sample_rate(info); |
106 | result.bitsPerSample = gst_discoverer_audio_info_get_depth(info); |
107 | |
108 | result.bitrate = int(gst_discoverer_audio_info_get_bitrate(info)); |
109 | result.maxBitrate = int(gst_discoverer_audio_info_get_max_bitrate(info)); |
110 | result.language = QGstUtils::codeToLanguage(gst_discoverer_audio_info_get_language(info)); |
111 | |
112 | return result; |
113 | } |
114 | |
115 | QGstDiscovererSubtitleInfo parseGstDiscovererSubtitleInfo(GstDiscovererSubtitleInfo *info) |
116 | { |
117 | QGstDiscovererSubtitleInfo result; |
118 | static_cast<QGstDiscovererStreamInfo &>(result) = parseGstDiscovererStreamInfo(info); |
119 | result.language = QGstUtils::codeToLanguage(gst_discoverer_subtitle_info_get_language(info)); |
120 | return result; |
121 | } |
122 | |
123 | QGstDiscovererContainerInfo parseGstDiscovererContainerInfo(GstDiscovererContainerInfo *info) |
124 | { |
125 | QGstDiscovererContainerInfo result; |
126 | static_cast<QGstDiscovererStreamInfo &>(result) = parseGstDiscovererStreamInfo(info); |
127 | |
128 | result.tags = duplicateTagList(tagList: gst_discoverer_container_info_get_tags(info)); |
129 | |
130 | return result; |
131 | } |
132 | |
133 | QGstDiscovererInfo parseGstDiscovererInfo(GstDiscovererInfo *info) |
134 | { |
135 | using namespace QGstUtils; |
136 | |
137 | QGstDiscovererInfo result; |
138 | result.isLive = gst_discoverer_info_get_live(info); |
139 | result.isSeekable = gst_discoverer_info_get_seekable(info); |
140 | |
141 | GstClockTime duration = gst_discoverer_info_get_duration(info); |
142 | if (duration != GST_CLOCK_TIME_NONE) |
143 | result.duration = std::chrono::nanoseconds{ duration }; |
144 | |
145 | GstDiscovererStreamInfo *streamInfo = gst_discoverer_info_get_stream_info(info); |
146 | if (streamInfo && GST_IS_DISCOVERER_CONTAINER_INFO(streamInfo)) |
147 | result.containerInfo = |
148 | parseGstDiscovererContainerInfo(GST_DISCOVERER_CONTAINER_INFO(streamInfo)); |
149 | result.tags = duplicateTagList(tagList: gst_discoverer_info_get_tags(info)); |
150 | |
151 | GstDiscovererStreamInfoList<GstDiscovererVideoInfo> videoStreams{ |
152 | gst_discoverer_info_get_video_streams(info), |
153 | }; |
154 | for (GstDiscovererVideoInfo *videoInfo : videoStreams) |
155 | result.videoStreams.emplace_back(args: parseGstDiscovererVideoInfo(info: videoInfo)); |
156 | |
157 | GstDiscovererStreamInfoList<GstDiscovererAudioInfo> audioStreams{ |
158 | gst_discoverer_info_get_audio_streams(info), |
159 | }; |
160 | for (GstDiscovererAudioInfo *audioInfo : audioStreams) |
161 | result.audioStreams.emplace_back(args: parseGstDiscovererAudioInfo(info: audioInfo)); |
162 | |
163 | GstDiscovererStreamInfoList<GstDiscovererSubtitleInfo> subtitleStreams{ |
164 | gst_discoverer_info_get_subtitle_streams(info), |
165 | }; |
166 | for (GstDiscovererSubtitleInfo *subtitleInfo : subtitleStreams) |
167 | result.subtitleStreams.emplace_back(args: parseGstDiscovererSubtitleInfo(info: subtitleInfo)); |
168 | |
169 | GstDiscovererStreamInfoList<GstDiscovererContainerInfo> containerStreams{ |
170 | gst_discoverer_info_get_container_streams(info), |
171 | }; |
172 | for (GstDiscovererContainerInfo *containerInfo : containerStreams) |
173 | result.containerStreams.emplace_back(args: parseGstDiscovererContainerInfo(info: containerInfo)); |
174 | |
175 | return result; |
176 | } |
177 | |
178 | } // namespace |
179 | |
180 | //---------------------------------------------------------------------------------------------------------------------- |
181 | |
182 | static constexpr std::chrono::nanoseconds discovererTimeout = std::chrono::seconds(10); |
183 | |
184 | QGstDiscoverer::QGstDiscoverer() |
185 | : m_instance{ |
186 | gst_discoverer_new(timeout: discovererTimeout.count(), err: nullptr), |
187 | } |
188 | { |
189 | } |
190 | |
191 | QMaybe<QGstDiscovererInfo, QUniqueGErrorHandle> QGstDiscoverer::discover(const QString &uri) |
192 | { |
193 | return discover(uri.toUtf8().constData()); |
194 | } |
195 | |
196 | QMaybe<QGstDiscovererInfo, QUniqueGErrorHandle> QGstDiscoverer::discover(const QUrl &url) |
197 | { |
198 | return discover(url.toEncoded().constData()); |
199 | } |
200 | |
201 | QMaybe<QGstDiscovererInfo, QUniqueGErrorHandle> QGstDiscoverer::discover(QIODevice *device) |
202 | { |
203 | return discover(url: qGstRegisterQIODevice(device)); |
204 | } |
205 | |
206 | QMaybe<QGstDiscovererInfo, QUniqueGErrorHandle> QGstDiscoverer::discover(const char *uri) |
207 | { |
208 | QUniqueGErrorHandle error; |
209 | QGstDiscovererInfoHandle info{ |
210 | gst_discoverer_discover_uri(discoverer: m_instance.get(), uri, err: &error), |
211 | }; |
212 | |
213 | if (error) |
214 | return { |
215 | unexpect, |
216 | std::move(error), |
217 | }; |
218 | |
219 | QGstDiscovererInfo result = parseGstDiscovererInfo(info: info.get()); |
220 | return result; |
221 | } |
222 | |
223 | //---------------------------------------------------------------------------------------------------------------------- |
224 | |
225 | QMediaMetaData toContainerMetadata(const QGstDiscovererInfo &info) |
226 | { |
227 | QMediaMetaData metadata; |
228 | using Key = QMediaMetaData::Key; |
229 | using namespace std::chrono; |
230 | |
231 | if (info.containerInfo) |
232 | extendMetaDataFromTagList(metadata, info.containerInfo->tags); |
233 | else |
234 | extendMetaDataFromTagList(metadata, info.tags); |
235 | |
236 | auto updateMetadata = [&](Key key, auto value) { |
237 | QVariant currentValue = metadata.value(k: key); |
238 | if (!currentValue.isValid() || currentValue != value) |
239 | metadata.insert(k: key, value); |
240 | }; |
241 | |
242 | if (info.duration) |
243 | updateMetadata(Key::Duration, |
244 | QVariant::fromValue(value: round<milliseconds>(d: *info.duration).count())); |
245 | |
246 | return metadata; |
247 | } |
248 | |
249 | void addMissingKeysFromTaglist(QMediaMetaData &metadata, const QGstTagListHandle &tagList) |
250 | { |
251 | QMediaMetaData tagMetaData = taglistToMetaData(tagList); |
252 | for (auto element : tagMetaData.asKeyValueRange()) { |
253 | if (metadata.keys().contains(t: element.first)) |
254 | continue; |
255 | |
256 | metadata.insert(k: element.first, value: element.second); |
257 | } |
258 | } |
259 | |
260 | template <typename ValueType> |
261 | static void updateMetadata(QMediaMetaData &metadata, QMediaMetaData::Key key, ValueType value) |
262 | { |
263 | QVariant currentValue = metadata.value(k: key); |
264 | if (!currentValue.isValid() || currentValue != value) |
265 | metadata.insert(k: key, value); |
266 | } |
267 | |
268 | QMediaMetaData toStreamMetadata(const QGstDiscovererVideoInfo &info) |
269 | { |
270 | QMediaMetaData metadata; |
271 | using Key = QMediaMetaData::Key; |
272 | |
273 | updateMetadata(metadata, key: Key::VideoBitRate, value: info.bitrate); |
274 | |
275 | extendMetaDataFromCaps(metadata, info.caps); |
276 | addMissingKeysFromTaglist(metadata, tagList: info.tags); |
277 | |
278 | return metadata; |
279 | } |
280 | |
281 | QMediaMetaData toStreamMetadata(const QGstDiscovererAudioInfo &info) |
282 | { |
283 | QMediaMetaData metadata; |
284 | using Key = QMediaMetaData::Key; |
285 | |
286 | updateMetadata(metadata, key: Key::AudioBitRate, value: info.bitrate); |
287 | updateMetadata(metadata, key: Key::Language, value: info.language); |
288 | |
289 | extendMetaDataFromCaps(metadata, info.caps); |
290 | addMissingKeysFromTaglist(metadata, tagList: info.tags); |
291 | |
292 | return metadata; |
293 | } |
294 | |
295 | QMediaMetaData toStreamMetadata(const QGstDiscovererSubtitleInfo &info) |
296 | { |
297 | QMediaMetaData metadata; |
298 | using Key = QMediaMetaData::Key; |
299 | |
300 | updateMetadata(metadata, key: Key::Language, value: info.language); |
301 | |
302 | extendMetaDataFromCaps(metadata, info.caps); |
303 | addMissingKeysFromTaglist(metadata, tagList: info.tags); |
304 | |
305 | return metadata; |
306 | } |
307 | |
308 | } // namespace QGst |
309 | |
310 | QT_END_NAMESPACE |
311 | |