|
1 /* This file is part of the KDE project. |
|
2 |
|
3 Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). |
|
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 by |
|
7 the Free Software Foundation, either version 2.1 or 3 of the License. |
|
8 |
|
9 This library is distributed in the hope that it will be useful, |
|
10 but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 GNU Lesser General Public License for more details. |
|
13 |
|
14 You should have received a copy of the GNU Lesser General Public License |
|
15 along with this library. If not, see <http://www.gnu.org/licenses/>. |
|
16 */ |
|
17 |
|
18 #include "videowidget.h" |
|
19 #include <QtCore/QEvent> |
|
20 #include <QtGui/QResizeEvent> |
|
21 #include <QtGui/QPalette> |
|
22 #include <QtGui/QImage> |
|
23 #include <QtGui/QPainter> |
|
24 #include <QtGui/QBoxLayout> |
|
25 #include <QApplication> |
|
26 #include <gst/gst.h> |
|
27 #include <gst/interfaces/propertyprobe.h> |
|
28 #include "mediaobject.h" |
|
29 #include "message.h" |
|
30 #include "common.h" |
|
31 |
|
32 #include "glrenderer.h" |
|
33 #include "widgetrenderer.h" |
|
34 #include "x11renderer.h" |
|
35 |
|
36 QT_BEGIN_NAMESPACE |
|
37 |
|
38 namespace Phonon |
|
39 { |
|
40 namespace Gstreamer |
|
41 { |
|
42 |
|
43 VideoWidget::VideoWidget(Backend *backend, QWidget *parent) : |
|
44 QWidget(parent), |
|
45 MediaNode(backend, VideoSink), |
|
46 m_videoBin(0), |
|
47 m_renderer(0), |
|
48 m_aspectRatio(Phonon::VideoWidget::AspectRatioAuto), |
|
49 m_brightness(0.0), |
|
50 m_hue(0.0), |
|
51 m_contrast(0.0), |
|
52 m_saturation(0.0), |
|
53 m_scaleMode(Phonon::VideoWidget::FitInView), |
|
54 m_videoBalance(0), |
|
55 m_colorspace(0), |
|
56 m_videoplug(0) |
|
57 { |
|
58 setupVideoBin(); |
|
59 } |
|
60 |
|
61 VideoWidget::~VideoWidget() |
|
62 { |
|
63 if (m_videoBin) { |
|
64 gst_element_set_state (m_videoBin, GST_STATE_NULL); |
|
65 gst_object_unref (m_videoBin); |
|
66 } |
|
67 |
|
68 if (m_renderer) |
|
69 delete m_renderer; |
|
70 } |
|
71 |
|
72 |
|
73 void VideoWidget::setupVideoBin() |
|
74 { |
|
75 |
|
76 m_renderer = m_backend->deviceManager()->createVideoRenderer(this); |
|
77 GstElement *videoSink = m_renderer->videoSink(); |
|
78 |
|
79 m_videoBin = gst_bin_new (NULL); |
|
80 Q_ASSERT(m_videoBin); |
|
81 gst_object_ref (GST_OBJECT (m_videoBin)); //Take ownership |
|
82 gst_object_sink (GST_OBJECT (m_videoBin)); |
|
83 |
|
84 //The videoplug element is the final element before the pluggable videosink |
|
85 m_videoplug = gst_element_factory_make ("identity", NULL); |
|
86 |
|
87 //Colorspace ensures that the output of the stream matches the input format accepted by our video sink |
|
88 m_colorspace = gst_element_factory_make ("ffmpegcolorspace", NULL); |
|
89 |
|
90 //Video scale is used to prepare the correct aspect ratio and scale. |
|
91 GstElement *videoScale = gst_element_factory_make ("videoscale", NULL); |
|
92 |
|
93 //We need a queue to support the tee from parent node |
|
94 GstElement *queue = gst_element_factory_make ("queue", NULL); |
|
95 |
|
96 if (queue && m_videoBin && videoScale && m_colorspace && videoSink && m_videoplug) { |
|
97 //Ensure that the bare essentials are prepared |
|
98 gst_bin_add_many (GST_BIN (m_videoBin), queue, m_colorspace, m_videoplug, videoScale, videoSink, (const char*)NULL); |
|
99 bool success = false; |
|
100 //Video balance controls color/sat/hue in the YUV colorspace |
|
101 m_videoBalance = gst_element_factory_make ("videobalance", NULL); |
|
102 if (m_videoBalance) { |
|
103 // For video balance to work we have to first ensure that the video is in YUV colorspace, |
|
104 // then hand it off to the videobalance filter before finally converting it back to RGB. |
|
105 // Hence we nede a videoFilter to convert the colorspace before and after videobalance |
|
106 GstElement *m_colorspace2 = gst_element_factory_make ("ffmpegcolorspace", NULL); |
|
107 gst_bin_add_many(GST_BIN(m_videoBin), m_videoBalance, m_colorspace2, (const char*)NULL); |
|
108 success = gst_element_link_many(queue, m_colorspace, m_videoBalance, m_colorspace2, videoScale, m_videoplug, videoSink, (const char*)NULL); |
|
109 } else { |
|
110 //If video balance is not available, just connect to sink directly |
|
111 success = gst_element_link_many(queue, m_colorspace, videoScale, m_videoplug, videoSink, (const char*)NULL); |
|
112 } |
|
113 |
|
114 if (success) { |
|
115 GstPad *videopad = gst_element_get_pad (queue, "sink"); |
|
116 gst_element_add_pad (m_videoBin, gst_ghost_pad_new ("sink", videopad)); |
|
117 gst_object_unref (videopad); |
|
118 QWidget *parentWidget = qobject_cast<QWidget*>(parent()); |
|
119 if (parentWidget) |
|
120 parentWidget->winId(); // Due to some existing issues with alien in 4.4, |
|
121 // we must currently force the creation of a parent widget. |
|
122 m_isValid = true; //initialization ok, accept input |
|
123 } |
|
124 } |
|
125 } |
|
126 |
|
127 void VideoWidget::paintEvent(QPaintEvent *event) |
|
128 { |
|
129 Q_ASSERT(m_renderer); |
|
130 m_renderer->handlePaint(event); |
|
131 } |
|
132 |
|
133 void VideoWidget::setVisible(bool val) { |
|
134 Q_ASSERT(m_renderer); |
|
135 |
|
136 // Disable overlays for graphics view |
|
137 if (root() && window() && window()->testAttribute(Qt::WA_DontShowOnScreen) && !m_renderer->paintsOnWidget()) { |
|
138 m_backend->logMessage(QString("Widget rendering forced"), Backend::Info, this); |
|
139 GstElement *videoSink = m_renderer->videoSink(); |
|
140 Q_ASSERT(videoSink); |
|
141 |
|
142 gst_element_set_state (videoSink, GST_STATE_NULL); |
|
143 gst_bin_remove(GST_BIN(m_videoBin), videoSink); |
|
144 delete m_renderer; |
|
145 m_renderer = 0; |
|
146 |
|
147 // Use widgetRenderer as a fallback |
|
148 m_renderer = new WidgetRenderer(this); |
|
149 videoSink = m_renderer->videoSink(); |
|
150 gst_bin_add(GST_BIN(m_videoBin), videoSink); |
|
151 gst_element_link(m_videoplug, videoSink); |
|
152 gst_element_set_state (videoSink, GST_STATE_PAUSED); |
|
153 |
|
154 // Request return to current state |
|
155 root()->invalidateGraph(); |
|
156 root()->setState(root()->state()); |
|
157 } |
|
158 QWidget::setVisible(val); |
|
159 } |
|
160 |
|
161 bool VideoWidget::event(QEvent *event) |
|
162 { |
|
163 if (m_renderer && m_renderer->eventFilter(event)) |
|
164 return true; |
|
165 return QWidget::event(event); |
|
166 } |
|
167 |
|
168 Phonon::VideoWidget::AspectRatio VideoWidget::aspectRatio() const |
|
169 { |
|
170 return m_aspectRatio; |
|
171 } |
|
172 |
|
173 QSize VideoWidget::sizeHint() const |
|
174 { |
|
175 if (!m_movieSize.isEmpty()) |
|
176 return m_movieSize; |
|
177 else |
|
178 return QSize(640, 480); |
|
179 } |
|
180 |
|
181 void VideoWidget::setAspectRatio(Phonon::VideoWidget::AspectRatio aspectRatio) |
|
182 { |
|
183 m_aspectRatio = aspectRatio; |
|
184 if (m_renderer) |
|
185 m_renderer->aspectRatioChanged(aspectRatio); |
|
186 } |
|
187 |
|
188 Phonon::VideoWidget::ScaleMode VideoWidget::scaleMode() const |
|
189 { |
|
190 return m_scaleMode; |
|
191 } |
|
192 |
|
193 QRect VideoWidget::scaleToAspect(QRect srcRect, int w, int h) const |
|
194 { |
|
195 float width = srcRect.width(); |
|
196 float height = srcRect.width() * (float(h) / float(w)); |
|
197 if (height > srcRect.height()) { |
|
198 height = srcRect.height(); |
|
199 width = srcRect.height() * (float(w) / float(h)); |
|
200 } |
|
201 return QRect(0, 0, (int)width, (int)height); |
|
202 } |
|
203 |
|
204 /*** |
|
205 * Calculates the actual rectangle the movie will be presented with |
|
206 **/ |
|
207 QRect VideoWidget::calculateDrawFrameRect() const |
|
208 { |
|
209 QRect widgetRect = rect(); |
|
210 QRect drawFrameRect; |
|
211 // Set m_drawFrameRect to be the size of the smallest possible |
|
212 // rect conforming to the aspect and containing the whole frame: |
|
213 switch (aspectRatio()) { |
|
214 |
|
215 case Phonon::VideoWidget::AspectRatioWidget: |
|
216 drawFrameRect = widgetRect; |
|
217 // No more calculations needed. |
|
218 return drawFrameRect; |
|
219 |
|
220 case Phonon::VideoWidget::AspectRatio4_3: |
|
221 drawFrameRect = scaleToAspect(widgetRect, 4, 3); |
|
222 break; |
|
223 |
|
224 case Phonon::VideoWidget::AspectRatio16_9: |
|
225 drawFrameRect = scaleToAspect(widgetRect, 16, 9); |
|
226 break; |
|
227 |
|
228 case Phonon::VideoWidget::AspectRatioAuto: |
|
229 default: |
|
230 drawFrameRect = QRect(0, 0, movieSize().width(), movieSize().height()); |
|
231 break; |
|
232 } |
|
233 |
|
234 // Scale m_drawFrameRect to fill the widget |
|
235 // without breaking aspect: |
|
236 float widgetWidth = widgetRect.width(); |
|
237 float widgetHeight = widgetRect.height(); |
|
238 float frameWidth = widgetWidth; |
|
239 float frameHeight = drawFrameRect.height() * float(widgetWidth) / float(drawFrameRect.width()); |
|
240 |
|
241 switch (scaleMode()) { |
|
242 case Phonon::VideoWidget::ScaleAndCrop: |
|
243 if (frameHeight < widgetHeight) { |
|
244 frameWidth *= float(widgetHeight) / float(frameHeight); |
|
245 frameHeight = widgetHeight; |
|
246 } |
|
247 break; |
|
248 case Phonon::VideoWidget::FitInView: |
|
249 default: |
|
250 if (frameHeight > widgetHeight) { |
|
251 frameWidth *= float(widgetHeight) / float(frameHeight); |
|
252 frameHeight = widgetHeight; |
|
253 } |
|
254 break; |
|
255 } |
|
256 drawFrameRect.setSize(QSize(int(frameWidth), int(frameHeight))); |
|
257 drawFrameRect.moveTo(int((widgetWidth - frameWidth) / 2.0f), |
|
258 int((widgetHeight - frameHeight) / 2.0f)); |
|
259 return drawFrameRect; |
|
260 } |
|
261 |
|
262 void VideoWidget::setScaleMode(Phonon::VideoWidget::ScaleMode scaleMode) |
|
263 { |
|
264 m_scaleMode = scaleMode; |
|
265 if (m_renderer) |
|
266 m_renderer->scaleModeChanged(scaleMode); |
|
267 } |
|
268 |
|
269 qreal VideoWidget::brightness() const |
|
270 { |
|
271 return m_brightness; |
|
272 } |
|
273 |
|
274 qreal clampedValue(qreal val) |
|
275 { |
|
276 if (val > 1.0 ) |
|
277 return 1.0; |
|
278 else if (val < -1.0) |
|
279 return -1.0; |
|
280 else return val; |
|
281 } |
|
282 |
|
283 void VideoWidget::setBrightness(qreal newValue) |
|
284 { |
|
285 newValue = clampedValue(newValue); |
|
286 |
|
287 if (newValue == m_brightness) |
|
288 return; |
|
289 |
|
290 m_brightness = newValue; |
|
291 |
|
292 if (m_videoBalance) |
|
293 g_object_set(G_OBJECT(m_videoBalance), "brightness", newValue, (const char*)NULL); //gstreamer range is [-1, 1] |
|
294 |
|
295 } |
|
296 |
|
297 qreal VideoWidget::contrast() const |
|
298 { |
|
299 return m_contrast; |
|
300 } |
|
301 |
|
302 void VideoWidget::setContrast(qreal newValue) |
|
303 { |
|
304 newValue = clampedValue(newValue); |
|
305 |
|
306 if (newValue == m_contrast) |
|
307 return; |
|
308 |
|
309 m_contrast = newValue; |
|
310 |
|
311 if (m_videoBalance) |
|
312 g_object_set(G_OBJECT(m_videoBalance), "contrast", (newValue + 1.0), (const char*)NULL); //gstreamer range is [0-2] |
|
313 } |
|
314 |
|
315 qreal VideoWidget::hue() const |
|
316 { |
|
317 return m_hue; |
|
318 } |
|
319 |
|
320 void VideoWidget::setHue(qreal newValue) |
|
321 { |
|
322 if (newValue == m_hue) |
|
323 return; |
|
324 |
|
325 newValue = clampedValue(newValue); |
|
326 |
|
327 m_hue = newValue; |
|
328 |
|
329 if (m_videoBalance) |
|
330 g_object_set(G_OBJECT(m_videoBalance), "hue", newValue, (const char*)NULL); //gstreamer range is [-1, 1] |
|
331 } |
|
332 |
|
333 qreal VideoWidget::saturation() const |
|
334 { |
|
335 return m_saturation; |
|
336 } |
|
337 |
|
338 void VideoWidget::setSaturation(qreal newValue) |
|
339 { |
|
340 newValue = clampedValue(newValue); |
|
341 |
|
342 if (newValue == m_saturation) |
|
343 return; |
|
344 |
|
345 m_saturation = newValue; |
|
346 |
|
347 if (m_videoBalance) |
|
348 g_object_set(G_OBJECT(m_videoBalance), "saturation", newValue + 1.0, (const char*)NULL); //gstreamer range is [0, 2] |
|
349 } |
|
350 |
|
351 |
|
352 void VideoWidget::setMovieSize(const QSize &size) |
|
353 { |
|
354 m_backend->logMessage(QString("New video size %0 x %1").arg(size.width()).arg(size.height()), Backend::Info); |
|
355 if (size == m_movieSize) |
|
356 return; |
|
357 m_movieSize = size; |
|
358 widget()->updateGeometry(); |
|
359 widget()->update(); |
|
360 |
|
361 if (m_renderer) |
|
362 m_renderer->movieSizeChanged(m_movieSize); |
|
363 } |
|
364 |
|
365 void VideoWidget::mediaNodeEvent(const MediaNodeEvent *event) |
|
366 { |
|
367 switch (event->type()) { |
|
368 case MediaNodeEvent::VideoSizeChanged: { |
|
369 const QSize *size = static_cast<const QSize*>(event->data()); |
|
370 setMovieSize(*size); |
|
371 } |
|
372 break; |
|
373 default: |
|
374 break; |
|
375 } |
|
376 |
|
377 // Forward events to renderer |
|
378 if (m_renderer) |
|
379 m_renderer->handleMediaNodeEvent(event); |
|
380 } |
|
381 |
|
382 } |
|
383 } //namespace Phonon::Gstreamer |
|
384 |
|
385 QT_END_NAMESPACE |
|
386 |
|
387 #include "moc_videowidget.cpp" |