• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #include <QQuickWindow>
2 #include <QQmlEngine>
3 #include <QRunnable>
4 
5 #include <gst/gst.h>
6 #include <gst/gl/gl.h>
7 
8 #include "videoitem.h"
9 
registerMetatypes()10 static void registerMetatypes()
11 {
12     qmlRegisterType<VideoItem>("ACME.VideoItem", 1, 0, "VideoItem");
13     qRegisterMetaType<VideoItem::State>("VideoItem::State");
14 }
15 Q_CONSTRUCTOR_FUNCTION(registerMetatypes)
16 
17 static const QStringList patterns = {
18     "smpte",
19     "snow",
20     "black",
21     "white",
22     "red",
23     "green",
24     "blue",
25     "checkers-1",
26     "checkers-2",
27     "checkers-4",
28     "checkers-8",
29     "circular",
30     "blink",
31     "smpte75",
32     "zone-plate",
33     "gamut",
34     "chroma-zone-plate",
35     "solid-color",
36     "ball",
37     "smpte100",
38     "bar",
39     "pinwheel",
40     "spokes",
41     "gradient",
42     "colors"
43 };
44 
45 struct VideoItemPrivate {
VideoItemPrivateVideoItemPrivate46     explicit VideoItemPrivate(VideoItem *owner) : own(owner) { }
47 
48     VideoItem *own { nullptr };
49 
50     GstElement *pipeline { nullptr };
51     GstElement *src { nullptr };
52     GstElement *sink { nullptr };
53 
54     GstPad *renderPad { nullptr };
55     GstBus *bus { nullptr };
56 
57     VideoItem::State state { VideoItem::STATE_VOID_PENDING };
58 
59     QString pattern {};
60     QRect rect { 0, 0, 0, 0 };
61     QSize resolution { 0, 0 };
62 
63     /* TODO: make q-properties? */
64     quint64 timeout { 3000 }; /* 3s timeout */
65 };
66 
67 struct RenderJob : public QRunnable {
68     using Callable = std::function<void()>;
69 
RenderJobRenderJob70     explicit RenderJob(Callable c) : _c(c) { }
71 
runRenderJob72     void run() { _c(); }
73 
74 private:
75     Callable _c;
76 };
77 
78 namespace {
79 
messageHandler(GstBus *,GstMessage * msg,gpointer userData)80 GstBusSyncReply messageHandler(GstBus * /*bus*/, GstMessage *msg, gpointer userData)
81 {
82     auto priv = static_cast<VideoItemPrivate *>(userData);
83 
84     switch (GST_MESSAGE_TYPE(msg)) {
85     case GST_MESSAGE_ERROR: {
86         GError *error { nullptr };
87         QString str { "GStreamer error: " };
88 
89         gst_message_parse_error(msg, &error, nullptr);
90         str.append(error->message);
91         g_error_free(error);
92 
93         emit priv->own->errorOccurred(str);
94         qWarning() << str;
95     } break;
96 
97     case GST_MESSAGE_STATE_CHANGED: {
98         if (GST_MESSAGE_SRC(msg) == GST_OBJECT(priv->pipeline)) {
99             GstState oldState { GST_STATE_NULL }, newState { GST_STATE_NULL };
100 
101             gst_message_parse_state_changed(msg, &oldState, &newState, nullptr);
102             priv->own->setState(static_cast<VideoItem::State>(newState));
103         }
104     } break;
105 
106     case GST_MESSAGE_HAVE_CONTEXT: {
107         GstContext *context { nullptr };
108 
109         gst_message_parse_have_context(msg, &context);
110 
111         if (gst_context_has_context_type(context, GST_GL_DISPLAY_CONTEXT_TYPE))
112             gst_element_set_context(priv->pipeline, context);
113 
114         if (context)
115             gst_context_unref(context);
116 
117         gst_message_unref(msg);
118 
119         return GST_BUS_DROP;
120     } break;
121 
122     default:
123         break;
124     }
125 
126     return GST_BUS_PASS;
127 }
128 
129 } // end namespace
130 
VideoItem(QQuickItem * parent)131 VideoItem::VideoItem(QQuickItem *parent)
132     : QQuickItem(parent), _priv(new VideoItemPrivate(this))
133 {
134     connect(this, &VideoItem::rectChanged, this, &VideoItem::updateRect);
135     connect(this, &VideoItem::stateChanged, this, &VideoItem::updateRect);
136 
137     // gst init pipeline
138     _priv->pipeline = gst_pipeline_new(nullptr);
139     _priv->src = gst_element_factory_make("videotestsrc", nullptr);
140     _priv->sink = gst_element_factory_make("glsinkbin", nullptr);
141 
142     GstElement *fakesink = gst_element_factory_make("fakesink", nullptr);
143 
144     Q_ASSERT(_priv->pipeline && _priv->src && _priv->sink);
145     Q_ASSERT(fakesink);
146 
147     g_object_set(_priv->sink, "sink", fakesink, nullptr);
148 
149     gst_bin_add_many(GST_BIN(_priv->pipeline), _priv->src, _priv->sink, nullptr);
150     gst_element_link_many (_priv->src, _priv->sink, nullptr);
151 
152     // add watch
153     _priv->bus = gst_pipeline_get_bus(GST_PIPELINE(_priv->pipeline));
154     gst_bus_set_sync_handler(_priv->bus, messageHandler, _priv.data(), nullptr);
155 
156     gst_element_set_state(_priv->pipeline, GST_STATE_READY);
157     gst_element_get_state(_priv->pipeline, nullptr, nullptr, _priv->timeout * GST_MSECOND);
158 }
159 
~VideoItem()160 VideoItem::~VideoItem()
161 {
162     gst_bus_set_sync_handler(_priv->bus, nullptr, nullptr, nullptr); // stop handling messages
163 
164     gst_element_set_state(_priv->pipeline, GST_STATE_NULL);
165 
166     gst_object_unref(_priv->pipeline);
167     gst_object_unref(_priv->bus);
168 }
169 
hasVideo() const170 bool VideoItem::hasVideo() const
171 {
172     return _priv->renderPad && (state() & STATE_PLAYING);
173 }
174 
source() const175 QString VideoItem::source() const
176 {
177     return _priv->pattern;
178 }
179 
setSource(const QString & source)180 void VideoItem::setSource(const QString &source)
181 {
182     if (_priv->pattern == source)
183         return;
184 
185     _priv->pattern = source;
186 
187     stop();
188 
189     if (!_priv->pattern.isEmpty()) {
190 
191         auto it = std::find (patterns.begin(), patterns.end(), source);
192 
193         if (it != patterns.end()) {
194             g_object_set(_priv->src, "pattern", std::distance(patterns.begin(), it), nullptr);
195             play();
196         }
197     }
198     emit sourceChanged(_priv->pattern);
199 }
200 
play()201 void VideoItem::play()
202 {
203     if (_priv->state > STATE_NULL) {
204         const auto status = gst_element_set_state(_priv->pipeline, GST_STATE_PLAYING);
205 
206         if (status == GST_STATE_CHANGE_FAILURE)
207             qWarning() << "GStreamer error: unable to start playback";
208     }
209 }
210 
stop()211 void VideoItem::stop()
212 {
213     if (_priv->state > STATE_NULL) {
214         const auto status = gst_element_set_state(_priv->pipeline, GST_STATE_READY);
215 
216         if (status == GST_STATE_CHANGE_FAILURE)
217             qWarning() << "GStreamer error: unable to stop playback";
218     }
219 }
220 
componentComplete()221 void VideoItem::componentComplete()
222 {
223     QQuickItem::componentComplete();
224 
225     QQuickItem *videoItem = findChild<QQuickItem *>("videoItem");
226     Q_ASSERT(videoItem); // should not fail: check VideoItem.qml
227 
228     // needed for proper OpenGL context setup for GStreamer elements (QtQuick renderer)
229     auto setRenderer = [=](QQuickWindow *window) {
230         if (window) {
231             GstElement *glsink = gst_element_factory_make("qmlglsink", nullptr);
232             Q_ASSERT(glsink);
233 
234             GstState current {GST_STATE_NULL}, pending {GST_STATE_NULL}, target {GST_STATE_NULL};
235             auto status = gst_element_get_state(_priv->pipeline, &current, &pending, 0);
236 
237             switch (status) {
238             case GST_STATE_CHANGE_FAILURE: {
239                 qWarning() << "GStreamer error: while setting renderer: pending state change failure";
240                 return;
241             }
242             case GST_STATE_CHANGE_SUCCESS:
243                 Q_FALLTHROUGH();
244             case GST_STATE_CHANGE_NO_PREROLL: {
245                 target = current;
246                 break;
247             }
248             case GST_STATE_CHANGE_ASYNC: {
249                 target = pending;
250                 break;
251             }
252             }
253 
254             gst_element_set_state(_priv->pipeline, GST_STATE_NULL);
255 
256             glsink = GST_ELEMENT(gst_object_ref(glsink));
257 
258             window->scheduleRenderJob(new RenderJob([=] {
259                 g_object_set(glsink, "widget", videoItem, nullptr);
260                 _priv->renderPad = gst_element_get_static_pad(glsink, "sink");
261                 g_object_set(_priv->sink, "sink", glsink, nullptr);
262                 gst_element_set_state(_priv->pipeline, target);
263                 }),
264                 QQuickWindow::BeforeSynchronizingStage);
265         }
266     };
267 
268     setRenderer(window());
269     connect(this, &QQuickItem::windowChanged, this, setRenderer);
270 }
271 
releaseResources()272 void VideoItem::releaseResources()
273 {
274     GstElement *sink { nullptr };
275     QQuickWindow *win { window() };
276 
277     gst_element_set_state(_priv->pipeline, GST_STATE_NULL);
278     g_object_get(_priv->sink, "sink", &sink, nullptr);
279 
280     if (_priv->renderPad) {
281         g_object_set(sink, "widget", nullptr, nullptr);
282         _priv->renderPad = nullptr;
283     }
284 
285     connect(this, &VideoItem::destroyed, this, [sink, win] {
286         auto job = new RenderJob(std::bind(&gst_object_unref, sink));
287         win->scheduleRenderJob(job, QQuickWindow::AfterSwapStage);
288     });
289 }
290 
updateRect()291 void VideoItem::updateRect()
292 {
293     // WARNING: don't touch this
294     if (!_priv->renderPad || _priv->state < STATE_PLAYING) {
295         setRect(QRect(0, 0, 0, 0));
296         setResolution(QSize(0, 0));
297         return;
298     }
299 
300     // update size
301     GstCaps *caps = gst_pad_get_current_caps(_priv->renderPad);
302     GstStructure *caps_struct = gst_caps_get_structure(caps, 0);
303 
304     gint picWidth { 0 }, picHeight { 0 };
305     gst_structure_get_int(caps_struct, "width", &picWidth);
306     gst_structure_get_int(caps_struct, "height", &picHeight);
307 
308     qreal winWidth { this->width() }, winHeight { this->height() };
309 
310     float picScaleRatio = picWidth * 1.0f / picHeight;
311     float wndScaleRatio = winWidth / winHeight;
312 
313     if (picScaleRatio >= wndScaleRatio) {
314         float span = winHeight - winWidth * picHeight / picWidth;
315         setRect(QRect(0, span / 2, winWidth, winHeight - span));
316     } else {
317         float span = winWidth - winHeight * picWidth / picHeight;
318         setRect(QRect(span / 2, 0, winWidth - span, winHeight));
319     }
320     setResolution(QSize(picWidth, picHeight));
321 }
322 
state() const323 VideoItem::State VideoItem::state() const
324 {
325     return _priv->state;
326 }
327 
setState(VideoItem::State state)328 void VideoItem::setState(VideoItem::State state)
329 {
330     if (_priv->state == state)
331         return;
332 
333     _priv->state = state;
334 
335     emit hasVideoChanged(_priv->state & STATE_PLAYING);
336     emit stateChanged(_priv->state);
337 }
338 
rect() const339 QRect VideoItem::rect() const
340 {
341     return _priv->rect;
342 }
343 
setRect(const QRect & rect)344 void VideoItem::setRect(const QRect &rect)
345 {
346     if (_priv->rect == rect)
347         return;
348 
349     _priv->rect = rect;
350     emit rectChanged(_priv->rect);
351 }
352 
resolution() const353 QSize VideoItem::resolution() const
354 {
355     return _priv->resolution;
356 }
357 
setResolution(const QSize & size)358 void VideoItem::setResolution(const QSize &size)
359 {
360     if (_priv->resolution == size)
361         return;
362 
363     _priv->resolution = size;
364     emit resolutionChanged(_priv->resolution);
365 }
366