1 /*
2     Copyright (C) 2012 Openismus GmbH
3       @author Mathias Hasselmann <mathias@openismus.com>
4 
5     This library is free software; you can redistribute it and/or modify
6     it under the terms of the GNU Lesser General Public License as published
7     by the Free Software Foundation; either version 2.1 of the License, or
8     (at your option) any later version.
9 
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU Lesser General Public License for more details.
14 
15     You should have received a copy of the GNU Lesser General Public License
16     along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 */
18 #include "qgsttest.h"
19 
20 #include <QGlib/Connect>
21 #include <QGlib/Error>
22 
23 #include <QGst/Caps>
24 #include <QGst/ClockTime>
25 #include <QGst/Discoverer>
26 
27 #include <gst/pbutils/pbutils.h>
28 
29 // Declare a simple tag list that actually supports iteration. Needed for our test data.
30 typedef QPair<const char *, QGlib::Value> Tag;
31 typedef QList<Tag> TagList;
32 
makeTag(const char * name,const QGlib::Value & value)33 static Tag makeTag(const char *name, const QGlib::Value &value)
34 {
35     return qMakePair(name, value);
36 }
37 
38 namespace QGlib {
39 
40 // Declare a simple compare operator for QGlib::Value.
41 // Comparing by string value isn't accurate at all, but good enough for our testing purposes.
operator ==(const Value & a,const Value & b)42 static bool operator ==(const Value &a, const Value &b)
43 {
44     bool okA = false, okB = false;
45     return a.type() == b.type() && a.toString(&okA) == b.toString(&okB) && okA && okB;
46 }
47 
operator !=(const Value & a,const Value & b)48 static bool operator !=(const Value &a, const Value &b)
49 {
50     return !(a == b);
51 }
52 
53 } //namespace QGlib
54 
55 // This classes describes what kind of streams we expect.
56 class StreamInfo : public QSharedData
57 {
58     Q_DISABLE_COPY(StreamInfo)
59 
60 public:
61     enum DebugMode
62     {
63         NoDebug,
64         Debug
65     };
66 
67     enum CapsMode
68     {
69         AutoCaps,
70         ManualCaps
71     };
72 
~StreamInfo()73     virtual ~StreamInfo() {}
74 
75 protected:
StreamInfo(const QString & caps,CapsMode capsMode)76     StreamInfo(const QString &caps, CapsMode capsMode)
77         : m_isNative(true)
78         , m_caps(QGst::Caps::fromString(caps))
79         , m_capsMode(capsMode)
80     {
81     }
82 
83     template<typename T>
84     class Field
85     {
86     public:
Field()87         Field()
88             : m_value(T())
89             , m_assigned(false)
90         {
91         }
92 
operator =(const T & value)93         Field & operator =(const T &value)
94         {
95             m_value = value;
96             m_assigned = true;
97             return *this;
98         }
99 
operator ==(const T & other) const100         bool operator ==(const T &other) const
101         {
102             return !m_assigned || m_value == other;
103         }
104 
operator !=(const T & other) const105         bool operator !=(const T &other) const
106         {
107             return !operator =(other);
108         }
109 
110     private:
111         T m_value;
112         bool m_assigned;
113     };
114 
115 public:
acceptStreamType(QGlib::Type streamType) const116     virtual bool acceptStreamType(QGlib::Type streamType) const
117     {
118         return streamType.isA(GST_TYPE_DISCOVERER_STREAM_INFO);
119     }
120 
acceptStream(QGst::DiscovererStreamInfoPtr info) const121     virtual bool acceptStream(QGst::DiscovererStreamInfoPtr info) const
122     {
123         return acceptStream(info, NoDebug);
124     }
125 
acceptStream(QGst::DiscovererStreamInfoPtr info,DebugMode debugMode) const126     virtual bool acceptStream(QGst::DiscovererStreamInfoPtr info, DebugMode debugMode) const
127     {
128         if (info.isNull()) {
129             if (debugMode) {
130                 qDebug() << "stream info is null";
131             }
132             return false;
133         }
134 
135         if (info->tags().isEmpty() != m_tags.isEmpty()) {
136             if (debugMode) {
137                 qDebug() << (m_tags.isEmpty() ? "tags should be empty, but aren't"
138                                               : "none of the expected tags found");
139                 qDebug() << m_tags;
140             }
141 
142             return false;
143         }
144 
145         Q_FOREACH(const Tag &tag, m_tags) {
146             if (info->tags().tagValue(tag.first) != tag.second) {
147                 if (debugMode) {
148                     qDebug() << tag.first << "tag has bad value\n"
149                              << "  expected value:" << tag.second << "\n"
150                              << "  actual value: " << info->tags().tagValue(tag.first);
151                 }
152                 return false;
153             }
154         }
155 
156         if (!info->caps()->isAlwaysCompatibleWith(m_caps)) {
157             if (debugMode) {
158                 qDebug() << "capabilities are incompatible\n"
159                          << "  expected caps:" << m_caps->toString() << "\n"
160                          << "  actual caps: " << info->caps()->toString();
161             }
162 
163             return false;
164         }
165 
166         return true;
167     }
168 
setNative(bool value)169     StreamInfo * setNative(bool value) { m_isNative = value; return this; }
isNative() const170     bool isNative() const { return m_isNative; }
171 
addTag(const char * name,const QGlib::Value & value)172     StreamInfo * addTag(const char *name, const QGlib::Value &value)
173     {
174         m_tags += makeTag(name, value);
175         return this;
176     }
177 
addCap(const char * name,const QGlib::Value & value)178     StreamInfo * addCap(const char *name, const QGlib::Value &value)
179     {
180         if (m_capsMode == AutoCaps) {
181             m_caps->setValue(name, value);
182         }
183 
184         return this;
185     }
186 
isImage() const187     bool isImage() const
188     {
189         return m_caps->toString().startsWith("image/");
190     }
191 
192 private:
193     bool m_isNative;
194     TagList m_tags;
195     QGst::CapsPtr m_caps;
196     CapsMode m_capsMode;
197 };
198 
199 class AudioStreamInfo : public StreamInfo
200 {
201 public:
AudioStreamInfo(const QString & caps,CapsMode capsMode=AutoCaps)202     AudioStreamInfo(const QString &caps, CapsMode capsMode = AutoCaps)
203         : StreamInfo(caps, capsMode)
204     {
205     }
206 
acceptStreamType(QGlib::Type streamType) const207     bool acceptStreamType(QGlib::Type streamType) const
208     {
209         return streamType.isA(GST_TYPE_DISCOVERER_AUDIO_INFO);
210     }
211 
acceptStream(QGst::DiscovererStreamInfoPtr info) const212     bool acceptStream(QGst::DiscovererStreamInfoPtr info) const
213     {
214         QGst::DiscovererAudioInfoPtr audioInfo = info.dynamicCast<QGst::DiscovererAudioInfo>();
215 
216         return !audioInfo.isNull()
217                 && StreamInfo::acceptStream(info)
218                 && m_channels == audioInfo->channels()
219                 && m_sampleRate == audioInfo->sampleRate()
220                 && m_bitrate == audioInfo->bitrate()
221                 && m_depth == audioInfo->depth();
222     }
223 
setChannels(uint value)224     AudioStreamInfo * setChannels(uint value) { m_channels = value; addCap("channels", int(value)); return this; }
setSampleRate(uint value)225     AudioStreamInfo * setSampleRate(uint value) { m_sampleRate = value; addCap("rate", int(value)); return this; }
setBitrate(uint value)226     AudioStreamInfo * setBitrate(uint value) { m_bitrate = value; addTag("bitrate", value); return this; }
setDepth(uint value)227     AudioStreamInfo * setDepth(uint value) { m_depth = value; return this; }
228 
229 private:
230     Field<uint> m_channels;
231     Field<uint> m_sampleRate;
232     Field<uint> m_bitrate;
233     Field<uint> m_depth;
234 };
235 
236 class VideoStreamInfo : public StreamInfo
237 {
238 public:
VideoStreamInfo(const QString & caps,CapsMode capsMode=AutoCaps)239     VideoStreamInfo(const QString &caps, CapsMode capsMode = AutoCaps)
240         : StreamInfo(caps, capsMode)
241     {
242         if (capsMode != AutoCaps) {
243             qWarning("working around PNG decoder bug");
244         }
245     }
246 
acceptStreamType(QGlib::Type streamType) const247     bool acceptStreamType(QGlib::Type streamType) const
248     {
249         return streamType.isA(GST_TYPE_DISCOVERER_VIDEO_INFO);
250     }
251 
acceptStream(QGst::DiscovererStreamInfoPtr info) const252     bool acceptStream(QGst::DiscovererStreamInfoPtr info) const
253     {
254         QGst::DiscovererVideoInfoPtr videoInfo = info.dynamicCast<QGst::DiscovererVideoInfo>();
255 
256         return !videoInfo.isNull()
257                 && StreamInfo::acceptStream(info)
258                 && isImage() == videoInfo->isImage()
259                 && m_width == videoInfo->width()
260                 && m_height == videoInfo->height()
261                 && m_interlaced == videoInfo->isInterlaced()
262                 && m_bitrate == videoInfo->bitrate()
263                 && m_framerate == videoInfo->framerate()
264                 && m_pixelAspectRatio == videoInfo->pixelAspectRatio();
265     }
266 
setWidth(uint value)267     VideoStreamInfo * setWidth(uint value) { m_width = value; addCap("width", int(value)); return this; }
setHeight(uint value)268     VideoStreamInfo * setHeight(uint value) { m_height = value; addCap("height", int(value)); return this; }
setInterlaced(bool value)269     VideoStreamInfo * setInterlaced(bool value) { m_interlaced = value; return this; }
setBitrate(uint value)270     VideoStreamInfo * setBitrate(uint value) { m_bitrate = value; addTag("bitrate", value); return this; }
setFramerate(const QGst::Fraction & value)271     VideoStreamInfo * setFramerate(const QGst::Fraction &value) { m_framerate = value; addCap("framerate", QGlib::Value::create(value)); return this; }
setPixelAspectRatio(const QGst::Fraction & value)272     VideoStreamInfo * setPixelAspectRatio(const QGst::Fraction &value) { m_pixelAspectRatio = value; addCap("pixel-aspect-ratio", QGlib::Value::create(value)); return this; }
273 
274 private:
275     Field<uint> m_width;
276     Field<uint> m_height;
277     Field<bool> m_interlaced;
278     Field<uint> m_bitrate;
279     Field<QGst::Fraction> m_framerate;
280     Field<QGst::Fraction> m_pixelAspectRatio;
281 };
282 
283 typedef QSharedDataPointer<StreamInfo> StreamInfoPtr;
284 typedef QList<StreamInfoPtr> StreamInfoList;
285 
286 // A few filtering algorithms
287 template<class Container, class UnaryPredicate>
filter(const Container & input,UnaryPredicate predicate)288 static Container filter(const Container &input, UnaryPredicate predicate)
289 {
290     Container result;
291 
292     const typename Container::ConstIterator end = input.constEnd();
293     for(typename Container::ConstIterator it = input.constBegin(); it != end; ++it) {
294         if (predicate(it->data())) {
295             result += *it;
296         }
297     }
298 
299     return result;
300 }
301 
filterByStreamType(const StreamInfoList & streams,QGlib::Type streamType)302 static StreamInfoList filterByStreamType(const StreamInfoList &streams, QGlib::Type streamType)
303 {
304     return filter(streams, std::bind2nd(std::mem_fun(&StreamInfo::acceptStreamType), streamType));
305 }
306 
307 // Declare a few metatypes, so that we can use this types for test data.
308 Q_DECLARE_METATYPE(QGst::ClockTime)
309 Q_DECLARE_METATYPE(StreamInfoList)
310 Q_DECLARE_METATYPE(TagList)
311 
312 // The actual test
313 class DiscovererTest : public QGstTest
314 {
315     Q_OBJECT
316 
317 public:
318     enum DiscoveryState {
319         DiscoveryPending,
320         DiscoveryStarted,
321         UriDiscovered,
322         DiscoveryFinished
323     };
324 
325 private:
326     void setupDiscoveryData();
327     void verifyStreamInfo(QGst::DiscovererInfoPtr info);
328 
329 private Q_SLOTS:
330     void cleanup();
331 
332     void testGLibTypes_data();
333     void testGLibTypes();
334 
335     void testSyncDiscovery_data();
336     void testSyncDiscovery();
337 
338     void testAsyncDiscovery_data();
339     void testAsyncDiscovery();
340 
341 protected: // mark as protected to avoid that QTestLib invoking those methods as test
342     void onStartingDiscovery();
343     void onUriDiscovered(QGst::DiscovererInfoPtr info, const QGlib::Error &error);
344     void onDiscoveryFinished();
345 
346 private:
347     QScopedPointer<QEventLoop> m_eventLoop;
348     DiscoveryState m_discoveryState;
349 };
350 
351 namespace QTest {
toString(const DiscovererTest::DiscoveryState & state)352 template<> char *toString(const DiscovererTest::DiscoveryState &state)
353 {
354     switch(state) {
355     case DiscovererTest::DiscoveryPending: return toString("discovery-pending");
356     case DiscovererTest::DiscoveryStarted: return toString("discovery-started");
357     case DiscovererTest::UriDiscovered: return toString("uri-discovered");
358     case DiscovererTest::DiscoveryFinished: return toString("discovery-finished");
359     }
360 
361     return toString(uint(state));
362 }
363 }
364 
setupDiscoveryData()365 void DiscovererTest::setupDiscoveryData()
366 {
367     QTest::addColumn<QUrl>("uri");
368     QTest::addColumn<int>("errorCode");
369     QTest::addColumn<QString>("errorDomain");
370     QTest::addColumn<QGst::ClockTime>("duration");
371     QTest::addColumn<bool>("seekable");
372     QTest::addColumn<StreamInfoList>("streams");
373     QTest::addColumn<TagList>("expectedTags");
374 
375     const bool Seekable = true;
376     const bool NonSeekable = false;
377     const QUrl baseUrl = QUrl::fromLocalFile(QString::fromLocal8Bit(SRCDIR) + "/");
378 
379     QTest::newRow("null")
380             << QUrl("about:null")
381             << int(GST_CORE_ERROR_MISSING_PLUGIN)
382             << QString::fromUtf8(g_quark_to_string(GST_CORE_ERROR))
383             << QGst::ClockTime(0) << Seekable
384             << StreamInfoList()
385             << TagList();
386     QTest::newRow("numbers.ogv")
387             << baseUrl.resolved(QUrl::fromEncoded("data/numbers.ogv")) << 0 << QString()
388             << QGst::ClockTime(2017333333) << Seekable
389             << (StreamInfoList()
390                 << StreamInfoPtr((new AudioStreamInfo("audio/x-flac, framed=true"))
391                                  ->setSampleRate(48000)->setChannels(1)->setDepth(16)
392                                  ->addTag("audio-codec", "FLAC")
393                                  ->addTag("container-format", "Ogg"))
394                 << StreamInfoPtr((new AudioStreamInfo("audio/x-flac"))
395                                  ->setSampleRate(48000)->setChannels(1)
396                                  ->setNative(false))
397                 << StreamInfoPtr((new VideoStreamInfo("video/x-theora"))
398                                  ->setWidth(160)->setHeight(120)->setInterlaced(false)
399                                  ->setBitrate(200000)->setFramerate(QGst::Fraction(5, 1))
400                                  ->setPixelAspectRatio(QGst::Fraction(1, 1))
401                                  ->addTag("container-format", "Ogg")
402                                  ->addTag("video-codec", "Theora")))
403             << (TagList()
404                 << makeTag("container-format", "Ogg")
405                 << makeTag("audio-codec", "FLAC")
406                 << makeTag("video-codec", "Theora")
407                 << makeTag("bitrate", 200000U));
408     QTest::newRow("numbers07.png")
409             << baseUrl.resolved(QUrl::fromEncoded("data/numbers07.png")) << 0 << QString()
410             << QGst::ClockTime(0) << Seekable
411             << (StreamInfoList()
412                 << StreamInfoPtr((new VideoStreamInfo("image/png", VideoStreamInfo::ManualCaps))
413                                  ->setWidth(160)->setHeight(120)->setInterlaced(false)))
414             << (TagList());
415     QTest::newRow("numbers07.jpg")
416             << baseUrl.resolved(QUrl::fromEncoded("data/numbers07.jpg")) << 0 << QString()
417             << QGst::ClockTime(0) << NonSeekable
418             << (StreamInfoList()
419                 << StreamInfoPtr((new VideoStreamInfo("image/jpeg"))
420                                  ->setWidth(120)->setHeight(160)->setInterlaced(false)))
421             << (TagList());
422     QTest::newRow("sine.ogg")
423             << baseUrl.resolved(QUrl::fromEncoded("data/sine.ogg")) << 0 << QString()
424             << QGst::ClockTime::fromSeconds(2) << Seekable
425             << (StreamInfoList()
426                 << StreamInfoPtr((new AudioStreamInfo("audio/x-vorbis"))
427                                  ->setSampleRate(48000)->setChannels(1)
428                                  ->setBitrate(80000)
429                                  ->addTag("container-format", "Ogg")
430                                  ->addTag("audio-codec", "Vorbis")))
431             << (TagList()
432                 << makeTag("container-format", "Ogg")
433                 << makeTag("audio-codec", "Vorbis")
434                 << makeTag("bitrate", 80000U));
435 }
436 
verifyStreamInfo(QGst::DiscovererInfoPtr info)437 void DiscovererTest::verifyStreamInfo(QGst::DiscovererInfoPtr info)
438 {
439     // verify discovery result
440     QVERIFY(!info.isNull());
441     QTEST(info->uri(), "uri");
442     QTEST(QString(), "errorDomain");
443     QCOMPARE(info->result(), QGst::DiscovererOk);
444     QTEST(info->duration(), "duration");
445     QTEST(info->seekable(), "seekable");
446     QVERIFY(!info->misc().isValid());
447 
448     QFETCH(TagList, expectedTags);
449     QCOMPARE(info->tags().isEmpty(), expectedTags.isEmpty());
450 
451     Q_FOREACH(const Tag &tag, expectedTags) {
452         QCOMPARE(info->tags().tagValue(tag.first), tag.second);
453     }
454 
455     QFETCH(StreamInfoList, streams);
456     QVERIFY(!info->streamInfo().isNull());
457 
458     if (streams.count() != 1 || !(*streams.constBegin())->isImage()) {
459         const QGst::DiscovererContainerInfoPtr root = info->streamInfo().dynamicCast<QGst::DiscovererContainerInfo>();
460         QVERIFY(!root.isNull());
461 
462         const StreamInfoList nativeStreams = filter(streams, std::mem_fun(&StreamInfo::isNative));
463         QCOMPARE(root->streams().count(), nativeStreams.count());
464     } else {
465         const QGst::DiscovererVideoInfoPtr root = info->streamInfo().dynamicCast<QGst::DiscovererVideoInfo>();
466         QVERIFY(!root.isNull());
467         QVERIFY(root->isImage());
468     }
469 
470     const StreamInfoList audioStreams = filterByStreamType(streams, GST_TYPE_DISCOVERER_AUDIO_INFO);
471     QCOMPARE(info->streams(GST_TYPE_DISCOVERER_AUDIO_INFO).count(), audioStreams.count());
472     QCOMPARE(info->audioStreams().count(), audioStreams.count());
473 
474     const StreamInfoList videoStreams = filterByStreamType(streams, GST_TYPE_DISCOVERER_VIDEO_INFO);
475     QCOMPARE(info->streams(GST_TYPE_DISCOVERER_VIDEO_INFO).count(), videoStreams.count());
476     QCOMPARE(info->videoStreams().count(), videoStreams.count());
477 
478     const StreamInfoList subtitleStreams = filterByStreamType(streams, GST_TYPE_DISCOVERER_SUBTITLE_INFO);
479     QCOMPARE(info->streams(GST_TYPE_DISCOVERER_SUBTITLE_INFO).count(), subtitleStreams.count());
480     QCOMPARE(info->subtitleStreams().count(), subtitleStreams.count());
481 
482     const StreamInfoList containerStreams = filterByStreamType(streams, GST_TYPE_DISCOVERER_CONTAINER_INFO);
483     QCOMPARE(info->streams(GST_TYPE_DISCOVERER_CONTAINER_INFO).count(), containerStreams.count());
484     QCOMPARE(info->containerStreams().count(), containerStreams.count());
485 
486     QCOMPARE(info->streams(GST_TYPE_DISCOVERER_STREAM_INFO).count(), streams.count());
487     QCOMPARE(info->streams().count(), streams.count());
488 
489     Q_FOREACH(const QGst::DiscovererStreamInfoPtr &streamInfo, info->streams()) {
490         const StreamInfoList::ConstIterator it =
491                 std::find_if(streams.constBegin(), streams.constEnd(),
492                              std::bind2nd(std::mem_fun(&StreamInfo::acceptStream), streamInfo));
493 
494         if (it == streams.constEnd()) {
495             Q_FOREACH(const StreamInfoPtr expectedInfo, streams) {
496                 expectedInfo->acceptStream(streamInfo, StreamInfo::Debug);
497             }
498 
499             QFAIL(qPrintable("Unexpected stream: " + streamInfo->caps()->toString()));
500         }
501 
502         streams.removeAt(it - streams.constBegin());
503         QVERIFY(!streamInfo->misc().isValid());
504     }
505 
506     QVERIFY(streams.isEmpty());
507 }
508 
cleanup()509 void DiscovererTest::cleanup()
510 {
511     m_eventLoop.reset();
512 }
513 
testGLibTypes_data()514 void DiscovererTest::testGLibTypes_data()
515 {
516     QTest::addColumn<QString>("wrappedTypeName");
517     QTest::addColumn<QString>("expectedTypeName");
518 
519     QTest::newRow("discoverer")
520             << QGlib::GetType<QGst::Discoverer>().name()
521             << QString::fromLatin1(g_type_name(GST_TYPE_DISCOVERER));
522     QTest::newRow("discoverer-info")
523             << QGlib::GetType<QGst::DiscovererInfo>().name()
524             << QString::fromLatin1(g_type_name(GST_TYPE_DISCOVERER_INFO));
525     QTest::newRow("discoverer-stream-info")
526             << QGlib::GetType<QGst::DiscovererStreamInfo>().name()
527             << QString::fromLatin1(g_type_name(GST_TYPE_DISCOVERER_STREAM_INFO));
528     QTest::newRow("discoverer-container-info")
529             << QGlib::GetType<QGst::DiscovererContainerInfo>().name()
530             << QString::fromLatin1(g_type_name(GST_TYPE_DISCOVERER_CONTAINER_INFO));
531     QTest::newRow("discoverer-audio-info")
532             << QGlib::GetType<QGst::DiscovererAudioInfo>().name()
533             << QString::fromLatin1(g_type_name(GST_TYPE_DISCOVERER_AUDIO_INFO));
534     QTest::newRow("discoverer-video-info")
535             << QGlib::GetType<QGst::DiscovererVideoInfo>().name()
536             << QString::fromLatin1(g_type_name(GST_TYPE_DISCOVERER_VIDEO_INFO));
537     QTest::newRow("discoverer-subtitle-info")
538             << QGlib::GetType<QGst::DiscovererSubtitleInfo>().name()
539             << QString::fromLatin1(g_type_name(GST_TYPE_DISCOVERER_SUBTITLE_INFO));
540 }
541 
testGLibTypes()542 void DiscovererTest::testGLibTypes()
543 {
544     QFETCH(QString, wrappedTypeName);
545     QFETCH(QString, expectedTypeName);
546 
547     QCOMPARE(wrappedTypeName, expectedTypeName);
548 }
549 
testSyncDiscovery_data()550 void DiscovererTest::testSyncDiscovery_data()
551 {
552     setupDiscoveryData();
553 }
554 
testSyncDiscovery()555 void DiscovererTest::testSyncDiscovery()
556 {
557     // create a discoverer
558     QGst::DiscovererPtr discoverer = QGst::Discoverer::create(QGst::ClockTime::fromSeconds(1));
559     QVERIFY(!discoverer.isNull());
560 
561     // test discovery request
562     QGst::DiscovererInfoPtr info;
563     QFETCH(QUrl, uri);
564 
565     try {
566         info = discoverer->discoverUri(uri);
567     } catch(const QGlib::Error &error) {
568         QTEST(error.domain().toString(), "errorDomain");
569         QTEST(error.code(), "errorCode");
570         return;
571     }
572 
573     verifyStreamInfo(info);
574 }
575 
testAsyncDiscovery_data()576 void DiscovererTest::testAsyncDiscovery_data()
577 {
578     setupDiscoveryData();
579 }
580 
testAsyncDiscovery()581 void DiscovererTest::testAsyncDiscovery()
582 {
583 // glib event loop required - see QCoreApplicationPrivate::createEventDispatcher() for the defines check
584 #if defined(Q_OS_WIN) || defined(Q_OS_BLACKBERRY) || defined(QT_NO_GLIB)
585     QSKIP_PORT("Platform does not have a GLib event loop", SkipAll);
586 #endif
587 
588     // setup discovery timeout
589     m_eventLoop.reset(new QEventLoop);
590     QTimer::singleShot(3000, m_eventLoop.data(), SLOT(quit()));
591 
592     // create a discoverer
593     m_discoveryState = DiscoveryPending;
594     QGst::DiscovererPtr discoverer = QGst::Discoverer::create(QGst::ClockTime::fromSeconds(1));
595     QVERIFY(!discoverer.isNull());
596 
597     QVERIFY(QGlib::connect(discoverer, "starting", this, &DiscovererTest::onStartingDiscovery, 0/*QGlib::PassSender*/));
598     QVERIFY(QGlib::connect(discoverer, "discovered", this, &DiscovererTest::onUriDiscovered));
599     QVERIFY(QGlib::connect(discoverer, "finished", this, &DiscovererTest::onDiscoveryFinished, QGlib::PassSender));
600 
601     // place URI to discover
602     QFETCH(QUrl, uri);
603     discoverer->start();
604     QVERIFY(discoverer->discoverUriAsync(uri));
605 
606     // verify discovery succeeded
607     QVERIFY2(m_eventLoop->exec(), "Discovery timeout out");
608     QCOMPARE(m_discoveryState, DiscoveryFinished);
609 }
610 
onStartingDiscovery()611 void DiscovererTest::onStartingDiscovery()
612 {
613     QCOMPARE(m_discoveryState, DiscoveryPending);
614     m_discoveryState = DiscoveryStarted;
615 }
616 
onUriDiscovered(QGst::DiscovererInfoPtr info,const QGlib::Error & error)617 void DiscovererTest::onUriDiscovered(QGst::DiscovererInfoPtr info, const QGlib::Error &error)
618 {
619     QCOMPARE(m_discoveryState, DiscoveryStarted);
620     m_discoveryState = UriDiscovered;
621 
622     if (error) {
623         QTEST(error.domain().toString(), "errorDomain");
624         QTEST(error.code(), "errorCode");
625         return;
626     }
627 
628     verifyStreamInfo(info);
629 }
630 
onDiscoveryFinished()631 void DiscovererTest::onDiscoveryFinished()
632 {
633     m_eventLoop->exit(1);
634     QCOMPARE(m_discoveryState, UriDiscovered);
635     m_discoveryState = DiscoveryFinished;
636 }
637 
638 QTEST_MAIN(DiscovererTest)
639 
640 #include "moc_qgsttest.cpp"
641 #include "discoverertest.moc"
642