From 226d35244df85a27c332d3a3ded1b25b3c7f4951 Mon Sep 17 00:00:00 2001 From: Roman Moravcik Date: Thu, 23 Jun 2011 14:02:17 +0200 Subject: [PATCH] Added qmafw-gst-subtitles-renderer-0.0.55 for Meego Harmattan 1.2 --- ....context_provider.libqmafw_gst_renderer.context | 5 + ...afw.plugin.libqmafw_gst_renderer_plugin.service | 3 + qmafw-gst-subtitles-renderer/debian/changelog | 1276 ++++++++ qmafw-gst-subtitles-renderer/debian/compat | 1 + qmafw-gst-subtitles-renderer/debian/control | 30 + qmafw-gst-subtitles-renderer/debian/copyright | 3 + qmafw-gst-subtitles-renderer/debian/dirs | 1 + .../debian/libqmafw-gst-subtitles-renderer.install | 4 + .../libqmafw-gst-subtitles-renderer.postinst | 5 + qmafw-gst-subtitles-renderer/debian/rules | 98 + .../inc/MafwBlankingPreventer.h | 57 + qmafw-gst-subtitles-renderer/inc/MafwGstRenderer.h | 508 ++++ .../inc/MafwGstRendererDolby.h | 81 + .../inc/MafwGstRendererHaltState.h | 83 + .../inc/MafwGstRendererNetworkMonitor.h | 45 + .../inc/MafwGstRendererPlaylistFileUtility.h | 94 + .../inc/MafwGstRendererPlugin.h | 58 + .../inc/MafwGstRendererVolume.h | 97 + .../inc/MafwGstScreenshot.h | 55 + qmafw-gst-subtitles-renderer/inc/MafwMmcMonitor.h | 59 + .../inc/mafw-gst-renderer-seeker.h | 43 + .../inc/mafw-gst-renderer-utils.h | 38 + .../inc/mafw-gst-renderer-worker.h | 382 +++ .../mafw-gst-renderer-plugin.conf | 37 + .../qmafw-gst-subtitles-renderer.pro | 99 + .../src/MafwBlankingPreventer.cpp | 51 + .../src/MafwGstRenderer.cpp | 2437 +++++++++++++++ .../src/MafwGstRendererDolby.cpp | 312 ++ .../src/MafwGstRendererHaltState.cpp | 161 + .../src/MafwGstRendererNetworkMonitor.cpp | 60 + .../src/MafwGstRendererPlaylistFileUtility.cpp | 165 ++ .../src/MafwGstRendererPlugin.cpp | 145 + .../src/MafwGstRendererVolume.cpp | 476 +++ .../src/MafwGstScreenshot.cpp | 223 ++ .../src/MafwMmcMonitor.cpp | 139 + .../src/mafw-gst-renderer-seeker.c | 201 ++ .../src/mafw-gst-renderer-utils.c | 324 ++ .../src/mafw-gst-renderer-worker.c | 3105 ++++++++++++++++++++ .../unittests/common/LibCredsStub.cpp | 51 + .../unittests/common/MafwBasicRendererStub.cpp | 42 + .../common/MafwPlaylistFileUtilityStub.cpp | 66 + .../unittests/common/MafwRendererPolicyStub.cpp | 39 + .../unittests/common/MafwRendererPolicyStub.h | 21 + .../common/MafwRoutingInfoHandlerStub.cpp | 67 + .../unittests/common/MafwStubHelper.cpp | 140 + .../unittests/common/MafwStubHelper.h | 108 + .../unittests/common/QNetworkStubs.cpp | 114 + .../unittests/common/QNetworkStubs.h | 53 + .../unittests/common/renderer-worker-stub.c | 380 +++ qmafw-gst-subtitles-renderer/unittests/media | 1 + .../unittests/run_all_tests.sh | 35 + .../unittests/run_valgrind.sh | 6 + .../unittests/test.suppressions | 2744 +++++++++++++++++ .../ut_GstScreenshot/ut_GstScreenshot.cpp | 209 ++ .../unittests/ut_GstScreenshot/ut_GstScreenshot.h | 52 + .../ut_GstScreenshot/ut_GstScreenshot.pro | 29 + .../unittests/ut_MafwGstRenderer/ContextFWStub.cpp | 127 + .../MafwGstRendererDolbyStub.cpp | 122 + .../ut_MafwGstRenderer/MafwMmcMonitorStub.cpp | 45 + .../unittests/ut_MafwGstRenderer/QSettingsStub.cpp | 32 + .../unittests/ut_MafwGstRenderer/QmSystemStub.cpp | 39 + .../ut_MafwGstRenderer/Ut_MafwGstRenderer.cpp | 1662 +++++++++++ .../ut_MafwGstRenderer/Ut_MafwGstRenderer.h | 102 + .../ut_MafwGstRenderer/ut_MafwGstRenderer.pro | 63 + .../ut_MafwGstRendererDolby.cpp | 120 + .../ut_MafwGstRendererDolby.h | 39 + .../ut_MafwGstRendererDolby.pro | 33 + .../ut_MafwGstRendererNetworkMonitor.cpp | 90 + .../ut_MafwGstRendererNetworkMonitor.h | 37 + .../ut_MafwGstRendererNetworkMonitor.pro | 28 + .../Ut_MafwGstRendererPlugin.cpp | 285 ++ .../Ut_MafwGstRendererPlugin.h | 45 + .../ut_MafwGstRendererPlugin.pro | 57 + .../ut_MafwGstRendererSeeker.cpp | 196 ++ .../ut_MafwGstRendererSeeker.h | 45 + .../ut_MafwGstRendererSeeker.pro | 28 + .../ut_MafwGstRendererSeeker_stubs.c | 55 + .../ut_MafwGstRendererVolume/MafwVolumeStubs.cpp | 271 ++ .../ut_MafwGstRendererVolume.cpp | 223 ++ .../ut_MafwGstRendererVolume.h | 46 + .../ut_MafwGstRendererVolume.pro | 29 + .../ut_MafwGstRendererWorker/media/test.avi | Bin 0 -> 86540 bytes .../ut_MafwGstRendererWorker/media/test.wav | Bin 0 -> 441484 bytes .../ut_MafwGstRendererWorker/media/testframe.png | Bin 0 -> 13945 bytes .../ut_MafwGstRendererWorker.cpp | 1432 +++++++++ .../ut_MafwGstRendererWorker.h | 111 + .../ut_MafwGstRendererWorker.pro | 31 + .../ut_MafwMmcMonitor/ut_MafwMmcMonitor.cpp | 97 + .../ut_MafwMmcMonitor/ut_MafwMmcMonitor.h | 44 + .../ut_MafwMmcMonitor/ut_MafwMmcMonitor.pro | 28 + .../ut_MafwMmcMonitor/ut_MafwMmcMonitorStubs.cpp | 111 + .../unittests/ut_Others/ut_Others.cpp | 150 + .../unittests/ut_Others/ut_Others.h | 44 + .../unittests/ut_Others/ut_Others.pro | 26 + .../Ut_PlaylistFileUtility.cpp | 149 + .../Ut_PlaylistFileUtility.h | 46 + .../Ut_PlaylistFileUtility.pro | 31 + .../ut_PlaylistFileUtility/test-station.pls | 60 + .../test-station_invalid.pls | 3 + .../unittests/ut_PlaylistFileUtility/test.asf | 3 + .../unittests/valgrind.xsl | 80 + .../unittests/valgrind_report.sh | 21 + 102 files changed, 21404 insertions(+) create mode 100644 qmafw-gst-subtitles-renderer/com.nokia.mafw.context_provider.libqmafw_gst_renderer.context create mode 100644 qmafw-gst-subtitles-renderer/com.nokia.mafw.plugin.libqmafw_gst_renderer_plugin.service create mode 100644 qmafw-gst-subtitles-renderer/debian/changelog create mode 100644 qmafw-gst-subtitles-renderer/debian/compat create mode 100644 qmafw-gst-subtitles-renderer/debian/control create mode 100644 qmafw-gst-subtitles-renderer/debian/copyright create mode 100644 qmafw-gst-subtitles-renderer/debian/dirs create mode 100644 qmafw-gst-subtitles-renderer/debian/libqmafw-gst-subtitles-renderer.install create mode 100644 qmafw-gst-subtitles-renderer/debian/libqmafw-gst-subtitles-renderer.postinst create mode 100755 qmafw-gst-subtitles-renderer/debian/rules create mode 100644 qmafw-gst-subtitles-renderer/inc/MafwBlankingPreventer.h create mode 100644 qmafw-gst-subtitles-renderer/inc/MafwGstRenderer.h create mode 100644 qmafw-gst-subtitles-renderer/inc/MafwGstRendererDolby.h create mode 100644 qmafw-gst-subtitles-renderer/inc/MafwGstRendererHaltState.h create mode 100644 qmafw-gst-subtitles-renderer/inc/MafwGstRendererNetworkMonitor.h create mode 100644 qmafw-gst-subtitles-renderer/inc/MafwGstRendererPlaylistFileUtility.h create mode 100644 qmafw-gst-subtitles-renderer/inc/MafwGstRendererPlugin.h create mode 100644 qmafw-gst-subtitles-renderer/inc/MafwGstRendererVolume.h create mode 100644 qmafw-gst-subtitles-renderer/inc/MafwGstScreenshot.h create mode 100644 qmafw-gst-subtitles-renderer/inc/MafwMmcMonitor.h create mode 100644 qmafw-gst-subtitles-renderer/inc/mafw-gst-renderer-seeker.h create mode 100644 qmafw-gst-subtitles-renderer/inc/mafw-gst-renderer-utils.h create mode 100644 qmafw-gst-subtitles-renderer/inc/mafw-gst-renderer-worker.h create mode 100644 qmafw-gst-subtitles-renderer/mafw-gst-renderer-plugin.conf create mode 100644 qmafw-gst-subtitles-renderer/qmafw-gst-subtitles-renderer.pro create mode 100644 qmafw-gst-subtitles-renderer/src/MafwBlankingPreventer.cpp create mode 100644 qmafw-gst-subtitles-renderer/src/MafwGstRenderer.cpp create mode 100644 qmafw-gst-subtitles-renderer/src/MafwGstRendererDolby.cpp create mode 100644 qmafw-gst-subtitles-renderer/src/MafwGstRendererHaltState.cpp create mode 100644 qmafw-gst-subtitles-renderer/src/MafwGstRendererNetworkMonitor.cpp create mode 100644 qmafw-gst-subtitles-renderer/src/MafwGstRendererPlaylistFileUtility.cpp create mode 100644 qmafw-gst-subtitles-renderer/src/MafwGstRendererPlugin.cpp create mode 100644 qmafw-gst-subtitles-renderer/src/MafwGstRendererVolume.cpp create mode 100644 qmafw-gst-subtitles-renderer/src/MafwGstScreenshot.cpp create mode 100644 qmafw-gst-subtitles-renderer/src/MafwMmcMonitor.cpp create mode 100644 qmafw-gst-subtitles-renderer/src/mafw-gst-renderer-seeker.c create mode 100644 qmafw-gst-subtitles-renderer/src/mafw-gst-renderer-utils.c create mode 100644 qmafw-gst-subtitles-renderer/src/mafw-gst-renderer-worker.c create mode 100644 qmafw-gst-subtitles-renderer/unittests/common/LibCredsStub.cpp create mode 100644 qmafw-gst-subtitles-renderer/unittests/common/MafwBasicRendererStub.cpp create mode 100644 qmafw-gst-subtitles-renderer/unittests/common/MafwPlaylistFileUtilityStub.cpp create mode 100644 qmafw-gst-subtitles-renderer/unittests/common/MafwRendererPolicyStub.cpp create mode 100644 qmafw-gst-subtitles-renderer/unittests/common/MafwRendererPolicyStub.h create mode 100644 qmafw-gst-subtitles-renderer/unittests/common/MafwRoutingInfoHandlerStub.cpp create mode 100644 qmafw-gst-subtitles-renderer/unittests/common/MafwStubHelper.cpp create mode 100644 qmafw-gst-subtitles-renderer/unittests/common/MafwStubHelper.h create mode 100644 qmafw-gst-subtitles-renderer/unittests/common/QNetworkStubs.cpp create mode 100644 qmafw-gst-subtitles-renderer/unittests/common/QNetworkStubs.h create mode 100644 qmafw-gst-subtitles-renderer/unittests/common/renderer-worker-stub.c create mode 120000 qmafw-gst-subtitles-renderer/unittests/media create mode 100755 qmafw-gst-subtitles-renderer/unittests/run_all_tests.sh create mode 100755 qmafw-gst-subtitles-renderer/unittests/run_valgrind.sh create mode 100644 qmafw-gst-subtitles-renderer/unittests/test.suppressions create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_GstScreenshot/ut_GstScreenshot.cpp create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_GstScreenshot/ut_GstScreenshot.h create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_GstScreenshot/ut_GstScreenshot.pro create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRenderer/ContextFWStub.cpp create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRenderer/MafwGstRendererDolbyStub.cpp create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRenderer/MafwMmcMonitorStub.cpp create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRenderer/QSettingsStub.cpp create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRenderer/QmSystemStub.cpp create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRenderer/Ut_MafwGstRenderer.cpp create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRenderer/Ut_MafwGstRenderer.h create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRenderer/ut_MafwGstRenderer.pro create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererDolby/ut_MafwGstRendererDolby.cpp create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererDolby/ut_MafwGstRendererDolby.h create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererDolby/ut_MafwGstRendererDolby.pro create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererNetworkMonitor/ut_MafwGstRendererNetworkMonitor.cpp create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererNetworkMonitor/ut_MafwGstRendererNetworkMonitor.h create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererNetworkMonitor/ut_MafwGstRendererNetworkMonitor.pro create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererPlugin/Ut_MafwGstRendererPlugin.cpp create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererPlugin/Ut_MafwGstRendererPlugin.h create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererPlugin/ut_MafwGstRendererPlugin.pro create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererSeeker/ut_MafwGstRendererSeeker.cpp create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererSeeker/ut_MafwGstRendererSeeker.h create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererSeeker/ut_MafwGstRendererSeeker.pro create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererSeeker/ut_MafwGstRendererSeeker_stubs.c create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererVolume/MafwVolumeStubs.cpp create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererVolume/ut_MafwGstRendererVolume.cpp create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererVolume/ut_MafwGstRendererVolume.h create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererVolume/ut_MafwGstRendererVolume.pro create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererWorker/media/test.avi create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererWorker/media/test.wav create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererWorker/media/testframe.png create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererWorker/ut_MafwGstRendererWorker.cpp create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererWorker/ut_MafwGstRendererWorker.h create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererWorker/ut_MafwGstRendererWorker.pro create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_MafwMmcMonitor/ut_MafwMmcMonitor.cpp create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_MafwMmcMonitor/ut_MafwMmcMonitor.h create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_MafwMmcMonitor/ut_MafwMmcMonitor.pro create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_MafwMmcMonitor/ut_MafwMmcMonitorStubs.cpp create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_Others/ut_Others.cpp create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_Others/ut_Others.h create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_Others/ut_Others.pro create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_PlaylistFileUtility/Ut_PlaylistFileUtility.cpp create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_PlaylistFileUtility/Ut_PlaylistFileUtility.h create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_PlaylistFileUtility/Ut_PlaylistFileUtility.pro create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_PlaylistFileUtility/test-station.pls create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_PlaylistFileUtility/test-station_invalid.pls create mode 100644 qmafw-gst-subtitles-renderer/unittests/ut_PlaylistFileUtility/test.asf create mode 100644 qmafw-gst-subtitles-renderer/unittests/valgrind.xsl create mode 100755 qmafw-gst-subtitles-renderer/unittests/valgrind_report.sh diff --git a/qmafw-gst-subtitles-renderer/com.nokia.mafw.context_provider.libqmafw_gst_renderer.context b/qmafw-gst-subtitles-renderer/com.nokia.mafw.context_provider.libqmafw_gst_renderer.context new file mode 100644 index 0000000..e222b15 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/com.nokia.mafw.context_provider.libqmafw_gst_renderer.context @@ -0,0 +1,5 @@ + + + + diff --git a/qmafw-gst-subtitles-renderer/com.nokia.mafw.plugin.libqmafw_gst_renderer_plugin.service b/qmafw-gst-subtitles-renderer/com.nokia.mafw.plugin.libqmafw_gst_renderer_plugin.service new file mode 100644 index 0000000..e323c3a --- /dev/null +++ b/qmafw-gst-subtitles-renderer/com.nokia.mafw.plugin.libqmafw_gst_renderer_plugin.service @@ -0,0 +1,3 @@ +[D-BUS Service] +Name=com.nokia.mafw.plugin.libqmafw_gst_renderer_plugin +Exec=/usr/bin/qmafw-dbus-wrapper /usr/lib/qmafw-plugin/libqmafw-gst-renderer-plugin.so diff --git a/qmafw-gst-subtitles-renderer/debian/changelog b/qmafw-gst-subtitles-renderer/debian/changelog new file mode 100644 index 0000000..f1b69f7 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/debian/changelog @@ -0,0 +1,1276 @@ +qmafw-gst-subtitles-renderer (0.0.55-1+0m6-1) unstable; urgency=low + + * Added subtitles support. + * Added Subtitles control panel applet. + + -- Roman Moravcik Wed, 22 Jun 2011 08:48:29 +0200 + +qmafw-gst-renderer (0.0.55-1+0m6) unstable; urgency=low + + * This entry has been added by BIFH queue processor + version has been changed to 0.0.55-1+0m6 + + -- BIFH Bot Mon, 23 May 2011 20:10:36 +0300 + +qmafw-gst-renderer (0.0.55-1) unstable; urgency=low + + * Fixes: NB#258573 - Never played , most played , recently payed queries does not yield any results + + -- Mika Tapojärvi Mon, 23 May 2011 20:10:35 +0300 + +qmafw-gst-renderer (0.0.54-1) unstable; urgency=low + + * Fixes: NB#253849 - “Can’t play the video because it’s the wrong format” error throws while trying to play any video when music suite is in background. + * MafwGstRenderer creates video-sink only if XID is set. + * Dependency to new gstreamer added. + * Fixing coverity findings. + + -- Mika Tapojärvi Wed, 11 May 2011 07:44:56 +0300 + +qmafw-gst-renderer (0.0.53-1) unstable; urgency=low + + * Fixes: NB#240728 - Resources are granted too early + * Fixes: NB#250212 - Possible memory leak [+1332kB] could be seen in qmafw-dbus-wrapper + * VideoResource PID usage plus resources released even with non-seekable + streams. + * GstBuffer now deleted with gst_buffer_unref (GstBuffer does not inherit + GstObject). + + -- Mika Tapojärvi Thu, 05 May 2011 13:29:33 +0300 + +qmafw-gst-renderer (0.0.52-1) unstable; urgency=low + + * Fixes: NB#248815 - Dolby setting changes has no effect after relaunching application + * Check the values of the audio/video route ContextProperty instances after + creating them. + + -- Mika Tapojärvi Mon, 02 May 2011 19:46:02 +0300 + +qmafw-gst-renderer (0.0.51-1) unstable; urgency=low + + * Fixes: NB#245854 - [SSU-Package Manager]:error seen at 'CRITICAL [contextc.cpp:109:int context_provider_init(DBusBusType, const char*)] Service already initialized' after SSU when opening video + * Fixes: NB#246993 - Renderer crashes if network is disabled during stream playback + * MAFW GStreamer renderer's latency-time setting changed back to 300000. + * Added global variable to monitor the initialisation state of process wide + singleton inside context fw. + * Also stopped setting some properties that omapxvsink doesn't have. + * MafwGstRenderer can handle network changes for simple streams. + + -- Mika Tapojärvi Wed, 20 Apr 2011 18:30:41 +0300 + +qmafw-gst-renderer (0.0.50-1) unstable; urgency=low + + * Fixes: NB#244162 - "Unable to play unsupported video format" is displayed while playing 1080p video file. + * Fixes: NB#242441 - wrong error message displayed when stream is not reachable + * Fixes: NB#242345 - Renderer goes to buffering loop when playing online stream + * MafwGstRenderer correctly generates unsupported resolution error. + * MafwGstRenderer sends URINotAvailable error for not found streams. + + -- Mika Tapojärvi Thu, 07 Apr 2011 10:01:57 +0300 + +qmafw-gst-renderer (0.0.49-1) unstable; urgency=low + + * Removed obsolete code for pausing playback when headphones get disconnected. + + -- Mika Tapojärvi Tue, 05 Apr 2011 07:48:12 +0300 + +qmafw-gst-renderer (0.0.48-1) unstable; urgency=low + + * Fixes: NB#236498 - Music suite does not continue internet radio playback after internet connection is renegotiated. + * Fixes: NB#238504 - Handover from WLAN to 3G does not happen when streaming audio + * Fixes: NB#240756 - defects from qmafw-gst-renderer/0.0.46-1_0m6 + * Fixes: NB#239983 - Remove dependencies to DRM Subsystem + * Fixes: NB#239428 - Black thumbnail displayed in minimized view after seek and swiping during online playback + * MafwGstRenderer tries to handle network changes gracefully. + + -- Mika Tapojärvi Wed, 30 Mar 2011 20:55:34 +0300 + +qmafw-gst-renderer (0.0.47-1) unstable; urgency=low + + * Fixes: NB#235660 - The note "Unable to play song, file not found" is shown with some music clips + * Adds proper encoding for file URI in some cases. Affects file paths which + contain non-ASCII characters. + + -- Mika Tapojärvi Thu, 24 Mar 2011 19:04:58 +0200 + +qmafw-gst-renderer (0.0.46-1) unstable; urgency=low + + * Fixes: NB#238161 - Playback fails until reboot after playing unsupported media file + * Fixes: NB#236869 - Names with special characters are not shown correctly in Music Player + * Fixes: NB#237491 - nie:usageCounter isn't updated if one of nie:usageCounter and nie:contentAccessed is missing + * Fixes: NB#237464 - Song play count is not updated properly. + * Dependencies modified for new DRM packaging. + * MafwGstRenderer returns duration as position in eos if query is not + succesful. + * Gst Renderer now uses QString::fromUtf8 to read in the UTF-8 (or ASCII) + tags from GStreamer. + * MafwGstRenderer updates usageCounter for repeating media. + + -- Mika Tapojärvi Tue, 22 Mar 2011 21:39:56 +0200 + +qmafw-gst-renderer (0.0.45-1) unstable; urgency=low + + * Fixes: NB#236498 - Music suite does not continue internet radio playback after internet connection is renegotiated. + * Fixes: NB#233404 - Renderer removed when play->stop actions are repeated many times + * Fixes: NB#229794 - Music Player failed to handle empty playlists (qtn_musi_empty_playlist not shown) + * MafwBasicRenderer better handling for stopped to pause transitions for + updating the mediaChanged() signal. + * MafwGstRenderer tries to restart infinite length streams when EOS + encountered. + * Switch to using the new policy context keys. + * Changed critical message to just warning and added hint why it maybe printed. + * Error signalling when trying to play empty playlist file. + * libqttracker removed from mafw. + + -- Mika Tapojärvi Thu, 17 Mar 2011 14:12:12 +0200 + +qmafw-gst-renderer (0.0.44-1) unstable; urgency=low + + * Fixes: NB#225667 - First frame flashes for fraction of sec just before playback resumes from previously watched position + * Fixes: NB#231298 - libqmafw-gst-renderer pulls gstreamer0.10-plugins-good-extra + * Fixes: NB#225941 - While Adding two songs via BT,One song goes to never played playlist and other goes to recently played playlist. + * Removed some unnecessary dependencies. + * Removed the dependency to plugins-good-extra. + * Unit testing for played stamping fixed. + * Fix for played stamping if piece was not harvested when it is played. + * MafwGstRenderer duration is signaled, among others. + + -- Mika Tapojärvi Thu, 03 Mar 2011 22:23:09 +0200 + +qmafw-gst-renderer (0.0.43-1) unstable; urgency=low + + * Implemented: SWP#MMAFW-2666 + + -- Mika Tapojärvi Fri, 25 Feb 2011 06:20:50 +0200 + +qmafw-gst-renderer (0.0.42-1) unstable; urgency=low + + * Fixes: NB#228964 - Video Rendrer crashes randomly on tapping next. + * Fixes: NB#219808 - Renderer state doesn't change, when video stream is launched. + * Removed unnecessary XError handling from mafw-gst-renderer. + + -- Mika Tapojärvi Tue, 22 Feb 2011 18:22:29 +0200 + +qmafw-gst-renderer (0.0.41-1) unstable; urgency=low + + * Fixes: NB#219808 - Renderer state doesn't change, when video stream is launched. + * MACHINE READABLE LINE: pre-release commit. + * ABI BREAK: Changes MafwRenderer::play(QUrl) to accept starting position to + start playback from as parameter + * MACHINE READABLE LINE: pre-release commit. + + -- Mika Tapojärvi Mon, 21 Feb 2011 10:17:29 +0200 + +qmafw-gst-renderer (0.0.40-1) unstable; urgency=low + + * ABI BREAK: Changes MafwRenderer::play(QUrl) to accept starting position to + start playback from as parameter. + + -- Mika Tapojärvi Thu, 17 Feb 2011 23:26:02 +0200 + +qmafw-gst-renderer (0.0.39-1) unstable; urgency=low + + * Fixes: NB#224017 - Hash marks are double encoded in renderer's metadataChanged signals + * MafwRenderer URL encoding handling improvements. Delimiter characters + handled correctly. + + -- Mika Tapojärvi Sun, 13 Feb 2011 22:06:45 +0200 + +qmafw-gst-renderer (0.0.38-1) unstable; urgency=low + + * Fixes: NB#207278 - Fast forward /rewind can not work during playing special MPEG4 video clip. + * Fixes: NB#222381 - pauseframes don't have unique names + * MafwGstRenderer tries to do better seek if a key frame seeks does not + advance or even goes backwards. It tries to find the next key frame. + + -- Mika Tapojärvi Tue, 08 Feb 2011 06:21:07 +0200 + +qmafw-gst-renderer (0.0.37-1) unstable; urgency=low + + * Fixes: NB#214335 - Unable to play. file not found message appears before the online playback starts + * Fixes: NB#217783 - Seekbar information and timer increment mismatch for particular .AAC clip. + * Fixes: NB#180798 - Music does not stop playing after DUT and PC were connected as Mass storage mode + * Now the playlist file playback doesn't report errors about individual uris. + * The possible error is reported only after the last uri has also been tried. + * MafwGstRenderer does not sent duration in nanosecond precision in some + scenarios. + * MafwGstRenderer listens for pre-unmount signal from usb_moded. + + -- Mika Tapojärvi Thu, 03 Feb 2011 19:54:23 +0200 + +qmafw-gst-renderer (0.0.36-1) unstable; urgency=low + + * Fixes: NB#220938 - Buffering banner not displayed for Iradio applet if applet invoked first time(Before Audio Player) after flashing + * Fixes: NB#210899 - Playback is stopped after TV-Out cable is plugged and unplugged for DRM restricted video + * MafwGstRenderer pauses when unallowed route switch occurs. + * MafwGstRenderer updates duration to tracker, also MafwBasicRenderer + provides duration metadata in MafwMediaInfo with doPlay(). + + -- Mika Tapojärvi Mon, 31 Jan 2011 08:30:21 +0200 + +qmafw-gst-renderer (0.0.35-2) unstable; urgency=low + + * Version increased for reintegration. + * Pause frame timer now takes milliseconds instead of seconds and default is + now 700 ms. + + -- Mika Tapojärvi Thu, 27 Jan 2011 11:18:14 +0200 + +qmafw-gst-renderer (0.0.35-1) unstable; urgency=low + + * Pause frame timer now takes milliseconds instead of seconds and default is + now 700 ms. + + -- Mika Tapojärvi Mon, 24 Jan 2011 06:17:12 +0200 + +qmafw-gst-renderer (0.0.34-1) unstable; urgency=low + + * Fixes: NB#213693 - TVandvideo releases playback resources too early + * Fixes: NB#217389 - Black screen is displayed in video pause mode when an incoming call comes + * Png encoder now uses compression-level 1. This should improve performance. + * MafwGstRenderer copies gstbuffer for further handling of pause. Also + resources are informed as being released only when they are released. + * Pause frame pipeline is now reused. + + -- Mika Tapojärvi Tue, 18 Jan 2011 22:11:19 +0200 + +qmafw-gst-renderer (0.0.33-2) unstable; urgency=low + + * Fixes: NB#215967 - When tapped during first 3 secs of playback seekbar behaves strangely in online player + * Fixes: NB#211139 - Pause button shown instead of stop for non seekable streams + * Fixes: NB#214376 - getPosition starts returning constant value after setPosition for nonseekable stream + * Version increased. + * Added resetting the error flag of the pipeline, if the playback attempt + ends because of DRM destination check, before the pipeline gets actually used. + * Changed the redirect message test case. + * MafwGstRenderer duration query fixes for media startup. + + -- Mika Tapojärvi Mon, 17 Jan 2011 09:42:43 +0200 + +qmafw-gst-renderer (0.0.32-1) unstable; urgency=low + + * Implemented: SWP#MMAFW-2546 + * Fixes: NB#214969 - No audio while playback when auto advance is set 'off' and then 'on' in a particular scenario + * Fixes: NB#213330 - Whitespaces are not encoded in renderer's metadataChanged signals + * Fixes: NB#206030 - Music playback prevents reaching 60 fps panning performance + * Added MafwGstScreenshot.cpp and .h to .pro file. + * Added MafwGstScreenshot.h and .cpp to pro file. + * mmcPreUnmount is back. + * MAFW_METADATA_KEY_URI emitted in encoded form. + * MafwGstRenderer latency-time tuning. + + -- Mika Tapojärvi Tue, 04 Jan 2011 08:17:20 +0200 + +qmafw-gst-renderer (0.0.31-1) unstable; urgency=low + + * Fixes: NB#194458 - CJSE:NFT:AudioPlayer:CJSE_NFT13 test case is failing + * Fixes: NB#211505 - Internet stream URI metadata not emitted + * Fixes: NB#209526 - Noise heard on DRM protected clip playback attempt + * Fixed few TYPOs on ut_MafwMmcMonitor. + * Wrong notification was displayed if text file which was not playlist were + tried to be played without mime type. + * MafwGstRenderer MMC monitoring. + * trackerRelease call added to gst-renderer unit tests cleanup. + * MAFW error code DRM error for clock not set added. + * Playback outputs are checked for DRM clips before starting playback. + + -- Mika Tapojärvi Tue, 21 Dec 2010 12:35:49 +0200 + +qmafw-gst-renderer (0.0.30-1) unstable; urgency=low + + * Fixes: NB#206367 - calling pauseAt(uint) emits playing state signal before pause signal + * MafwGstRenderer does not send playing state first when calling pauseAt. + + -- Mika Tapojärvi Mon, 13 Dec 2010 06:49:56 +0200 + +qmafw-gst-renderer (0.0.29-1) unstable; urgency=low + + * Fixes: NB#207882 - Seek does not work for the attached mp3 clip. + + -- Mika Tapojärvi Thu, 09 Dec 2010 10:55:48 +0200 + +qmafw-gst-renderer (0.0.28-1) unstable; urgency=low + + * Fixes: NB#208336 - Wrong notification is displayed when playing 1080p video clip. + * Fixes: NB#207288 - Video playback starts from beginning when resumed playback in certain scenario + * Fixes: NB#207837 - Dolby effectss not preserved after reconnecting the headset + * There was a logical error in the for loop, which caused infinite loop with + some medias. + * Some files were earlier left out of the commit (pipeline configurability) + because of conflicts. + * Duration handling removed in ready state. + * MafwGstRenderer can be configured via text file. + * Bug changed video settings as the active ones when reconnecting headset. + Now works correctly. + + -- Mika Tapojärvi Tue, 07 Dec 2010 21:50:59 +0200 + +qmafw-gst-renderer (0.0.27-1) unstable; urgency=low + + * Fixes: NB#208106 - Playback starts from the beginning of the clip on Resume after a call + * g_asserts removed from release builds. + * Added dependency to gstreamer0.10-plugins-bad + * There was a small error in the logic of handling the duration + unavailability. + * Now the last known seekability value is actually used, if the duration is + not known. + * Small fixes for MMS stream buffer size handling. + + -- Mika Tapojärvi Fri, 03 Dec 2010 08:27:33 +0200 + +qmafw-gst-renderer (0.0.26-1) unstable; urgency=low + + * Fixes: NB#199374 - Unable to stream ASF videos + * Fixes: NB#199405 - Unable to stream WMV videos through MMS and http + * Fixes: NB#203058 - “Unable to play song, Media format is not supported” pops up after the special playback is finished. + * Fixes: NB#207747 - WMA with Multichannel encoding not playing + * Fixes: NB#208036 - "unable to play song, media format not supported" is displayed for few WMA files. + * CI Test case for bug that timers for calling playNextURIFromPlaylist are + not cancelled. + * Fallback mechanism for playlist file playback error. + * Changed flags from 99 to 67 when using DHM to support "unsupported" audio. + + -- Mika Tapojärvi Tue, 30 Nov 2010 21:14:12 +0200 + +qmafw-gst-renderer (0.0.25-1) unstable; urgency=low + + * Fixes: NB#204996 - COREWEB: /usr/bin/qmafw-dbus-wrapper 'QString::operator< qMapLessThanKey findNode value MafwMediaInfo::firstMetaData' + * Fixes: NB#206947 - Duplicated is-seekable metadata received + * MafwGstRenderer does not send same metadata value again + * Added cancellation of screenshot capture into various cases. + * It is a strong assumption that 204996 is caused by the screenshot never + being cancelled. + * MafwGstRenderer does not send seekability metadata unless it changes. + + -- Mika Tapojärvi Sun, 28 Nov 2010 18:32:23 +0200 + +qmafw-gst-renderer (0.0.24-1) unstable; urgency=low + + * Fixes: NB#206561 - Playback fails after multiple seek of mms stream + * Fixes: NB#205172 - Audio not heard for the next file playable when "codec not found" error pops up + * MafwGstRenderer: Improved handling of seeking-buffering-pause sequences + * MafwGstRenderer reuses audio elements when reconstructing pipeline. + + -- Jukka Heikkilä Thu, 25 Nov 2010 08:26:54 +0200 + +qmafw-gst-renderer (0.0.23-1) unstable; urgency=low + + * Fixes: NB#202071 - Unable to stream WMA audio through MMS. + * Added dependency to gstreamer0.10-plugins-good-extra. + * It is needed because it provides png encoder. + + -- Jukka Heikkilä Fri, 19 Nov 2010 08:16:55 +0200 + +qmafw-gst-renderer (0.0.22-1) unstable; urgency=low + + * Fixes: NB#203250 - Dropping sdpdemux patch from meego gstreamer -bad package + * Fixes: NB#203010 - ‘Playback error: 326’ pops up when seeked till end during playback + * Fixes: NB#197812 - Error message displayed while resuming an rtsp stream playback + * Use "element-added" signal to get queue2 element from the pipeline. + * rm ==> rm -f + + -- Jukka Heikkilä Thu, 11 Nov 2010 12:59:59 +0200 + +qmafw-gst-renderer (0.0.21-1) unstable; urgency=low + + * GstRenderer remove unnecessary context provider check from initialize. + * Default room size of Dolby effect changed from 0 to 2. + + -- Mika Tapojärvi Mon, 08 Nov 2010 16:24:07 +0200 + +qmafw-gst-renderer (0.0.20-1) unstable; urgency=low + + * Fixes: NB#201764 - Playing online stream doesn't always start + * Fixes: NB#201569 - Play count and content accessed data not available when video clip is played with uri. + * Fixes: NB#199784 - No audio output for YouTube video when audio is paused from music application by unplug the headset. + * Removed one buffer size optimisation (setting buffer-duration in playbin2) + as it seemed to make gstreamer a bit unstable. + * Replaced Qt keywords with appropriate macros. + * MAFW to use libqmsystem2. + * Replaced emit and signals keywords with corresponding macros to make the + test compile again. + * Removing code that is nowadays unnecessary and causing valgrind findings. + * Gst renderer updating usageCount and contentAccessed in play URI case + * MafwGstRenderer now releases resources when headphones are disconnected, + which causes playback to pause. + * Added one more suppression related to g_type_register_static. + + -- Mika Tapojärvi Thu, 04 Nov 2010 22:54:27 +0200 + +qmafw-gst-renderer (0.0.19-2) unstable; urgency=low + + * Version increased. + + -- Mika Tapojärvi Tue, 02 Nov 2010 14:56:39 +0200 + +qmafw-gst-renderer (0.0.19-1) unstable; urgency=low + + * Fixes: NB#199965 - mafw-gst-renderer fails to build with GLib 2.26 + * The error code mapping was changed earlier. Now correcting the unit test + accordingly. + * Disabled moc keywords from mafw-gst-renderer. + * CI tests for dbus-wrapper lingering. + * Generalised libc6 library condition in some suppresions. + + -- Mika Tapojärvi Mon, 01 Nov 2010 13:52:03 +0200 + +qmafw-gst-renderer (0.0.18-1) unstable; urgency=low + + * Removed unnecessary LKM stubs from unit tests and enabled -Werror where it + was disabled because of LKM-caused warnings. + * Little formatting change in mafw-gst-renderer and error code fix when + mapping gstreamer errors to worker errors. + + -- Mika Tapojärvi Fri, 29 Oct 2010 10:46:35 +0300 + +qmafw-gst-renderer (0.0.17-1) unstable; urgency=low + + * Removed unnecessary LKM stubs from unit tests and enabled -Werror where it + was disabled because of LKM-caused warnings. + * Little formatting change in mafw-gst-renderer and error code fix when + mapping gstreamer errors to worker errors + + -- Mika Tapojärvi Thu, 28 Oct 2010 13:50:53 +0300 + +qmafw-gst-renderer (0.0.16-1) unstable; urgency=low + + * Fixes: NB#197421 - Seeking back functions as 'Next', and also jumps to every second video in a yt playlist after seek back at an end of a clip once + * Fixes: NB#196798 - Total duration of a seekable stream is not seen if a playlist is present already + * Fixes: NB#197812 - Error message displayed while resuming an rtsp stream playback + * Gst renderer regression fixed when resuming from ready state now works. + * Live source seeking is now delayed in Paused state. CITA test fixes, plus + last version info commits. + * Removed temporary fixes to counter build break on x86 target: DRM libraries + are again required for all architectures and temporary stubs are removed. + * Added initilization for m_pendingCall member on MafwGstRendererVolume. + * Dolby mixer enabled. + * MafwGstRenderer behaves correctly and sends only one playing state change + even when seeking while in playing state. + * A bug prevented DRM playback because drm_create_uri() was given 0 as + maximum length of string. + * Seekability is now send as false, when duration is unknown. + * DRM stub definitions changed to function declarations. + + -- Mika Tapojärvi Tue, 26 Oct 2010 22:07:05 +0300 + +qmafw-gst-renderer (0.0.15-1) unstable; urgency=low + + * Removed volume intialization at gst renderer startup. Volume is currently + set by system component and volume initialization at gst-renderer startup + causes unneeded processing. + * DRM client i486 support was removed. Implemented temporary stubs for those + used in i486 target. + * Updated DRM library version numbers to dependencies and removed dependency + to DRM from non-ARM. + + -- Mika Tapojärvi Mon, 18 Oct 2010 21:10:09 +0300 + +qmafw-gst-renderer (0.0.14-1) unstable; urgency=low + + * Implemented: SWP#MMAFW-2457 + * Fixes: NB#197668 - Unable to play DRM clip second time using the same renderer and not more than twice using any renderer + * Fixes: NB#186008 - MaFW play for audio streaming taking long time + * Fixes: NB#196891 - Sometimes resume is not working when called after audiopolicy is given back to music-suite + * Adaptation to API change in libdrm-playready0. + * Moved clearing worker->destinations from _reset_pipeline_and_worker() to + mafw_gst_renderer_worker_exit(). + * Increased mmsh buffer duration. + * Added usage of connection-speed and buffer-duration properties of playbin2. + Subsequent task is needed to make more elegant final solution, but this + should be enough for the bug. + * MafwGstRenderer handles earpiece audio route as builtin speaker route. + + -- Mika Tapojärvi Thu, 14 Oct 2010 15:42:56 +0300 + +qmafw-gst-renderer (0.0.13-1) unstable; urgency=low + + * Fixes: NB#196975 - defects from MAFW + * Dolby effect disabled for now. + * Part VIII: MafwGstRenderer unittest valgrind suppression file updated. + * Part VII: MafwGstRenderer unittest valgrind suppression file updated. + * Coverity findings fixed. + + -- Mika Tapojärvi Mon, 11 Oct 2010 12:09:24 +0300 + +qmafw-gst-renderer (0.0.12-1) unstable; urgency=low + + * MafwGstRenderer unittest valgrind suppression file updated. + * Unittests for Dolby effect. + + -- Mika Tapojärvi Fri, 08 Oct 2010 09:47:20 +0300 + +qmafw-gst-renderer (0.0.11-1) unstable; urgency=low + + * Fixes: NB#196267 - Renderer is lost after random qmafw-dbus-wrapper crash, only reflash the way out + * Fixes: NB#196026 - Renderer gets stuck on performing playback by 'Play uri' for a particular m3u playlist + * GstRenderer and YoutubeSource plugins dbus .service file naming convention. + * Moved Dolby initialization to worker initialization and added check for + m_worker. + * Added Dolby effect audio route handling. + * MafwGstRenderer send playbackCompleted() even if error case when playing + playlistURI. Fixes faulty cleanup in mafw-gst-renderer-worker. + + -- Mika Tapojärvi Wed, 06 Oct 2010 19:57:13 +0300 + +qmafw-gst-renderer (0.0.10-1) unstable; urgency=low + + * Implemented: SWP#MMAFW-1820 + * Fixes: NB#182065 - BT downloaded music clip does not play while camera is open in still picture mode. + * Added initialization list to get correct behavior. + * Added support for Dolby Headphone feature. + * GstRenderer fix in render-rectangle parsing. + + -- Mika Tapojärvi Tue, 05 Oct 2010 08:23:43 +0300 + +qmafw-gst-renderer (0.0.9-1) unstable; urgency=low + + * Fixes: NB#188929 - The Audio is not heard through wired headset when already connected BT headset is disconnected + * In MafwGstRenderer bluetooth headset disconnecting is handled in same way + as wired headset. + * Correct error code emission for DRM "no license" case. + + -- Mika Tapojärvi Fri, 01 Oct 2010 10:53:27 +0300 + +qmafw-gst-renderer (0.0.8-1) unstable; urgency=low + + * Fixes: NB#194221 - Seekability value is wrong for Internet radio streams + * Partial fix for #194442. MafwGstRenderer does more robust network change + observations + * We don't send out a gstreamer query in case we don't know the duration. + + -- Mika Tapojärvi Wed, 29 Sep 2010 18:29:54 +0300 + +qmafw-gst-renderer (0.0.7-1) unstable; urgency=low + + * Fixes: NB#193425 - Video image it not seen when starting playback in some cases + + -- Mika Tapojärvi Thu, 23 Sep 2010 20:46:07 +0300 + +qmafw-gst-renderer (0.0.6-1) unstable; urgency=low + + * Fixes: NB#193662 - qmafw package may have resource token problem with Tracker, please verify. + * Fixes: NB#193682 - qmafw-gst-renderer package may have resource token problem with Tracker, please verify. + * Fixes: NB#186008 - MaFW play for audio streaming taking long time + * Fixes: NB#190121 - Online video clip fails to play from the position where it was paused before + * Fixes: NB#192157 - qmafw-shared: add pkg-config to build-deps + * Added API documentation about the required credentials. + * Decreased the buffer size to 30 % of the original. It takes another second + off the stream start delay. + * gst-renderer seeks live streams in PLAYING state. + + -- Mika Tapojärvi Wed, 22 Sep 2010 21:20:17 +0300 + +qmafw-gst-renderer (0.0.5-1) unstable; urgency=low + + * Fixes: NB#192243 - Pause->resume->pause->resume sequence causes some videos to start from beginning. + * Fixes: NB#191257 - performance problem after renderer pause playback + * InvalidURI was sometimes incorrectly received as error code. Changed to + correct UNSUPPRTED TYPE code. + * Added a check for seekability request's result validity. If unknown is + * passed as a value, it's ignored. + + -- Mika Tapojärvi Mon, 20 Sep 2010 07:35:57 +0300 + +qmafw-gst-renderer (0.0.4-1) unstable; urgency=low + + * Fixes: NB#190307 - Black frame displayed when tap on screen after swiping to next clip + * Fixes: NB#189621 - Stream Playback starts playing from the beginning after a call or pause/resume after few seconds. + * Fixes: NB#190121 - Online video clip fails to play from the position where it was paused before + * MafwGstRenderer resumes from correct position after resources released in + pause state when streaming content. + + -- Mika Tapojärvi Fri, 17 Sep 2010 05:42:44 +0300 + +qmafw-gst-renderer (0.0.3-1) unstable; urgency=low + + * Implemented: MMAFW-2416 + * Fixes: NB#190752 - Music player does not plays after invalid video is played + * MafwGstRenderer reacts to network changes by emitting and error when + streaming cannot continue. + * Version dependency for playready packages updated. + * Fixed bug that stream playback is resumed automatically after a call. + + -- Mika Tapojärvi Wed, 15 Sep 2010 07:51:45 +0300 + +qmafw-gst-renderer (0.0.2-1) unstable; urgency=low + + * Fixes: NB#190393 - Stream buffers after 1-2 seconds of playback ,everytime after playback is initiated + * Fixes: NB#186008 - MaFW play for audio streaming taking long time + * Streaming optimization for mms streams. + * added CONFIG += plugin in .pro file + * fallback to alsasink in worker code + + -- Mika Tapojärvi Fri, 10 Sep 2010 13:22:17 +0300 + +qmafw-gst-renderer (0.0.1-73) unstable; urgency=low + + * Buffering optimization for mms streams. + + -- Mika Tapojärvi Wed, 08 Sep 2010 06:54:04 +0300 + +qmafw-gst-renderer (0.0.1-72) unstable; urgency=low + + * Fixes: NB#189707 - Playback from a playlist differs with the presence of local playlist + * Added getUriList function to MafwPlaylistFileUtilityStub so that the + untitests can pass. + * Added a waiting time for the playlist parser so that playing URI lists + doesn't stop when empty items are found before parsing the list far enough. + + -- Mika Tapojärvi Mon, 06 Sep 2010 22:24:21 +0300 + +qmafw-gst-renderer (0.0.1-71) unstable; urgency=low + + * Lintian warnings fixed. + + -- Mika Tapojärvi Sun, 05 Sep 2010 21:58:13 +0300 + +qmafw-gst-renderer (0.0.1-70) unstable; urgency=low + + * mafw-gst-renderer rebuild needed because of libplayready packaging changes. + + -- Mika Tapojärvi Thu, 02 Sep 2010 10:47:27 +0300 + +qmafw-gst-renderer (0.0.1-69) unstable; urgency=low + + * Implemented: MMAFW-2006 + * Fixes: NB#187115 - DRM playback stops and throws a error on receiving a call. + * Fixes: NB#188378 - End of playlist reached after playing first song/stream in the Playlist file + * Fixes to playlist URI playing and signalling. + + -- Mika Tapojärvi Tue, 31 Aug 2010 10:08:44 +0300 + +qmafw-gst-renderer (0.0.1-68) unstable; urgency=low + + * Implemented: SWP#MMAFW-2006 + * Implemented: SWP#MMAFW-2243 + * Fixes: NB#183176 - Resuming music after playing video with music in paused state doesn't work + * Fixes: NB#176434 - When pausing a song, music-suite does not release audio resource + * Signalling paused-thumbnail-uri metadata fixed. + * Playlist file playback support in gst-renderer. + * Pause state releases resources, although 2 seconds later. + + -- Mika Tapojärvi Thu, 26 Aug 2010 13:56:02 +0300 + +qmafw-gst-renderer (0.0.1-67) unstable; urgency=low + + * Implemented: MMAFW-2331 + * Fixes: NB#184640 - Fast seek makes playback unreliable + * Related metadata added to pause-frame-uri metadata: URI to video file the + frame is from and the position in seconds. + * RTSP redirect MAFW implementation. + + -- Mika Tapojärvi Mon, 23 Aug 2010 15:46:40 +0300 + +qmafw-gst-renderer (0.0.1-66) unstable; urgency=low + + * Implemented: SWP#MMAFW-2261 + * Implemented: SWP#MMAFW-2359 + * Fixes: NB#184583 - audio continues even after playback is completed when tried to play an online video + * No longer automatic resume when buffering after seek after EOS. + * Bypasses bug of resetting render-rectangle values does not work. + * Werror compile flag back to use. + * GstRenderer media routing info provider's credential check. + * Missing unittests change for render rectangle added. + * gstreamer X Overlay render rectangle setting possible via mafwgstrenderer. + * colorkey reverted back to 0x080810. + + -- Mika Tapojärvi Wed, 18 Aug 2010 07:30:56 +0300 + +qmafw-gst-renderer (0.0.1-65) unstable; urgency=low + + * Fixes to pkg-config usage, now pointing PKG_CONFIG_PATH into -uninstalled + .pc files makes it possible to compile mafw, mafw-shared and mafw-gst-plugin. + * Video sink colorkey is now 0xff00ff. + + -- Mika Tapojärvi Thu, 12 Aug 2010 15:05:34 +0300 + +qmafw-gst-renderer (0.0.1-64) unstable; urgency=low + + * Implemented: SWP#MMAFW-2358 + * Implemented: SWP#MMAFW-380 + * Pause playback when headset disconnected. + + -- Mika Tapojärvi Mon, 09 Aug 2010 09:38:36 +0300 + +qmafw-gst-renderer (0.0.1-63) unstable; urgency=low + + * Implemented: SWP#MMAFW-918 + * Fixes: NB#183564 - Video doesn't play when tap on a video file in VideosandTv application + * Explicit version for libdrm-playready0-dev build dependency. + * Enhancements to gstscreenshot code. + + -- Mika Tapojärvi Fri, 06 Aug 2010 07:46:00 +0300 + +qmafw-gst-renderer (0.0.1-62) unstable; urgency=low + + * Fixes: NB#176165 - MAFW should not wait for gst state changes to complete + * Fixes mafw-gst-renderer unittests. Policy functionality changes in + MafwBasicRenderer changed the API + + -- Tuomas Inkeroinen Thu, 29 Jul 2010 12:43:20 +0300 + +qmafw-gst-renderer (0.0.1-61) unstable; urgency=low + + * Fixes: NB#179733 - + * Fix for bug 179733 - Multiple seek beyond the duration of the video clip + hangs playback + + -- Tuomas Inkeroinen Mon, 26 Jul 2010 10:58:33 +0300 + +qmafw-gst-renderer (0.0.1-60) unstable; urgency=low + + * Fixes: NB#179782 - When a video is continuously skipped forward by 10 seconds in playing state, the pause control changes to play, while video continues to play + + -- Tuomas Inkeroinen Thu, 22 Jul 2010 08:54:27 +0300 + +qmafw-gst-renderer (0.0.1-59) unstable; urgency=low + + * Implemented: SWP#MMAFW-2330 + * We now use pngenc for pause frames instead of jpegenc + + -- Jukka Heikkilä Thu, 15 Jul 2010 09:55:39 +0300 + +qmafw-gst-renderer (0.0.1-58) unstable; urgency=low + + * Fixes: NB#177617 - Playcount does not increase when a clip is played repeatedly in player view + * Coverity issues fixed. + * mafw-gst-renderer usageCounter and contentAccessed update on replay + + -- Jukka Heikkilä Tue, 13 Jul 2010 12:34:26 +0300 + +qmafw-gst-renderer (0.0.1-57) unstable; urgency=low + + * Fixes: NB#176727 - Invalid pause frame given with pauseAt + * GstRenderer policy-context-daemon security token check + + + -- Jukka Heikkilä Thu, 08 Jul 2010 12:55:38 +0300 + +qmafw-gst-renderer (0.0.1-56) unstable; urgency=low + + * Fixes: NB#177006 - MAFW crash when playing regina spectres album. + * Earlier the "Next" URI was read from MafwMediaInfo into QString instead of + QUrl. + * This caused the uri that was passed to be percent encoded. + * Syslog routines then failed with because they assumed the uri to specify + format containing variables to be replaced. + * Append 'release'/'debug' into CONFIG variable rather than overwrite it. + * Earlier version caused extra 'strip' command into the Makefile. This in + turn, cause dbg-package to be empty. + + -- Jukka Heikkilä Tue, 29 Jun 2010 13:01:33 +0300 + +qmafw-gst-renderer (0.0.1-55) unstable; urgency=low + + * Fixes: NB#176077 - Screen blanking doesn't happen on playing videos in from video suite with TV-out connected + * Removed accidentally forgot wrt dependencies from pro file. + + -- Mika Tapojärvi Thu, 24 Jun 2010 13:10:05 +0300 + +qmafw-gst-renderer (0.0.1-54) unstable; urgency=low + + * Remove lisence fething from mafw and only report error from gst-renderer. + + -- Mika Tapojärvi Wed, 23 Jun 2010 10:14:45 +0300 + +qmafw-gst-renderer (0.0.1-53) unstable; urgency=low + + * Volume robustness test failing fixed. + * Build-Depends for libqttracker-dev (>= 1~6.9.5) and Depends for + libqttracker1pre6 (>= 1~6.9.5) added, Standards-Version: 3.8.0 updated where + needed. + + -- Mika Tapojärvi Thu, 17 Jun 2010 07:47:49 +0300 + +qmafw-gst-renderer (0.0.1-52) unstable; urgency=low + + * Fixes: NB#170773 - Renderer doesn't show up in MTG atall, if MTG gets closed before the renderer loads for the first time + + -- Mika Tapojärvi Sat, 12 Jun 2010 12:53:51 +0300 + +qmafw-gst-renderer (0.0.1-51) unstable; urgency=low + + * Fixes: NB#172047 - Resuming a song from paused state updates usageCount and contentAccessed + * Fixes: NB#170238 - Volume level of music is set to the same as alarm level (maximum) + * Fixes: NB#172330 - Video Rendrer crash on playing a video + * Temporarily removing DRM check. + * Getting lkm service interface commented temporarily out. + * Adapted changes in drmclient.h so that gst-renderer compiles: + drm_check_destination stub updated. + * Adapted changes in drmclient.h so that gst-renderer compiles. Actual fix + will come later... + * Fix for bug "Volume level of music is set to the same as alarm level + (maximum)". + * Removing unnecessary debian configuration. + * mafw-gst-renderer now informs of visual content when going to playing state. + * Mafwgstrenderer debian package building fix for servicehandler lib changes. + * Fix to get mafw-gstrenderer to compile againts lkm 1.7 + + -- Mika Tapojärvi Fri, 11 Jun 2010 12:45:20 +0300 + +qmafw-gst-renderer (0.0.1-50) unstable; urgency=low + + * Implemented: SWP#MMAFW-2024 + * Added a missing wrt lib for linking. + * Video resource is released when it is not needed. + * Undoing resource usage optimization when paused. + * mafw-gst-renderer libqttracker/dbus usage optimization. + + -- Mika Tapojärvi Wed, 02 Jun 2010 22:41:04 +0300 + +qmafw-gst-renderer (0.0.1-49) unstable; urgency=low + + * Implemented: SWP#MMAFW-2243 + * Implemented: SWP#MMAFW-2288 + * Fixes: NB#169418 - Playback resumption after 'pause at' set starts the playback from beginning + * Optimize resource usage when paused. + * Gst-renderer config file has a separate uuid for the in-process renderer. + When loaded in-process, the plugin creates the renderer with that uuid. + * New CI-test testInProcessPlayUri. + + -- Mika Tapojärvi Mon, 31 May 2010 19:41:45 +0300 + +qmafw-gst-renderer (0.0.1-48) unstable; urgency=low + + * Implemented: SWP#MMAFW-1925 + * Fixes: NB#170621 - qmafw crash while playing a video which blocks videoandTV testing + * When the reconnect to pulse audio was done, earlier the old connection was + unref'd synchronously. This caused problems in libdbus-qeventloop so now the + unref (and the reconnect) is done asynchronously in a separate event. + * Merge from fremantle: Bug 149945 - mafw-gst-renderer leaks some GStreamer + messages + * Merge from fremantle: Bug 137609 - UPnP: playback pauses often when + seek the attached content. + + -- Mika Tapojärvi Thu, 27 May 2010 22:55:54 +0300 + +qmafw-gst-renderer (0.0.1-47) unstable; urgency=low + + * Fixes: NB#169742 - Seekbar Maximum Value: Actual Length of the video clip is not displayed in Player view + * Merge from fremantle: Bug 141508 - Specific video file (mjpeg) makes + Mediaplayer unusable + * Use wildcard for the target name in the suppression file. + * Merge from fremantle: Bug 143429 - [PR1.2 proposal] memory fragemntation + & unneeded copies + * Merge from fremantle: Bug 134495 - [PR1.1 proposal] State changed signal + does not come sometimes when stream is played + * Corrected Ut_MafwGstRendererWorker::basicPlaybackTestCase() to expect + test.wav duration 3 instead of 2 because of new changed duration rounding. + * Fixed one memory leak in a unittest and added some suppressions for glib + related leaks. + * Merge from fremantle: Bug 143972 - [PR1.2 proposal] bundle g_object_set + calls + + -- Mika Tapojärvi Mon, 24 May 2010 20:42:03 +0300 + +qmafw-gst-renderer (0.0.1-46) unstable; urgency=low + + * Implemented: SWP#MMAFW-2237 + * Added the destructor stub to the QmDisplayState stub (the real destructor + assumed that the constructor created a private class instance). + * More robustness for using pulseaudio dbus volume API. + + -- Mika Tapojärvi Thu, 20 May 2010 13:04:53 +0300 + +qmafw-gst-renderer (0.0.1-45) unstable; urgency=low + + * Fixes: NB#167753 - Playback state does not change and focus does not move to next clip in this scenario + * Pending volume calls cancelled at destructor. + * So added internal stop when the license check fails. + * Fixed memory leak in ut_MafwGstRenderer. + + -- Mika Tapojärvi Mon, 17 May 2010 13:50:15 +0300 + +qmafw-gst-renderer (0.0.1-44) unstable; urgency=low + + * Implemented: SWP#MMAFW-2243 + * Implemented: SWP#MMAFW-2189 + * Fixes: NB#166855 - Tv and Video does not release Xv resources correctly + * Optimize resource usage when paused. + * MafwRenderer now has playbackCompleted signal. + * libtimed-dev added as a build dependency. + + -- Mika Tapojärvi Wed, 12 May 2010 09:49:05 +0300 + +qmafw-gst-renderer (0.0.1-43) unstable; urgency=low + + * Implemented: SWP#MMAFW-2053 + * Implemented: SWP#MMAFW-2138 + * Fixes: NB#167042 - MAFW video seeking is very slow + * debian/changelog files copied from tag to trunk. + * Fixes to renderer(s) when using pauseAt with too large value. Also required + changes to unittests. Plus proxyplaylist test fix. + * Prepared for the upcoming omapxvsink (patch from Rene Stadler) + * Updated handle screen blanking in video playback appropriately, when TV-OUT + cable is connected. + + -- Mika Tapojärvi Sat, 08 May 2010 11:35:09 +0300 + +qmafw-gst-renderer (0.0.1-42) unstable; urgency=low + + * Version increased. + + -- Mika Tapojärvi Fri, 07 May 2010 12:57:26 +0300 + +qmafw-gst-renderer (0.0.1-41) unstable; urgency=low + + * Implemented: SWP#MMAFW-2156 + * Fixes: NB#166473 - Volume resets to zero from the second instance of MTG launch, after resetting it for the first time + * Modified valgrind.xls for new valgring version. + * Volume resets to zero from the second instance of MTG launch, after + resetting it for the first time. + + -- Mika Tapojärvi Mon, 03 May 2010 22:07:19 +0300 + +qmafw-gst-renderer (0.0.1-40) unstable; urgency=low + + * Implemented: SWP#MMAFW-2156 + + -- Mika Tapojärvi Thu, 29 Apr 2010 13:34:15 +0300 + +qmafw-gst-renderer (0.0.1-39) unstable; urgency=low + + * Fixes: NB#165339 - Renderer crash on playing mp3s in music suite + * Added a NULL pointer check to gotoindex implementation of playlist handler. + + -- Mika Tapojärvi Wed, 28 Apr 2010 19:48:12 +0300 + +qmafw-gst-renderer (0.0.1-38) unstable; urgency=low + + * Audio routing to TV-out was not taken into account. + * Tv-out CF property should be "tvout", not "tv-out". + * A potential bug fix included: mafw_gst_renderer_worker_exit() is called in + the destructor before anything else. + * Added a check against NULL ptr for blanking__control_handler callback in + the worker code. + + -- Mika Tapojärvi Tue, 27 Apr 2010 20:09:41 +0300 + +qmafw-gst-renderer (0.0.1-37) unstable; urgency=low + + * Missing library dependencies temporarily added with debian/shlibs.local + file. + + -- Mika Tapojärvi Mon, 26 Apr 2010 18:35:27 +0300 + +qmafw-gst-renderer (0.0.1-36) unstable; urgency=low + + * Implemented: SWP#MMAFW-918 + * DRM content playback restrictions. + * Added missing contextsubscriber-1.0 into pro -file. + * Added libcontextsubscriber-dev into mafw-gst-renderer debian/control. + + -- Mika Tapojärvi Thu, 22 Apr 2010 19:32:42 +0300 + +qmafw-gst-renderer (0.0.1-35) unstable; urgency=low + + * Implemented: SWP#MMAFW-2138 + * Handle screen blanking in video playback appropriately, when TV-OUT cable + is connected. ContextFW provides com.nokia.policy.video_route property + that is used to check TV-OUT cable status. + + -- Mika Tapojärvi Sun, 18 Apr 2010 22:18:37 +0300 + +qmafw-gst-renderer (0.0.1-34) unstable; urgency=low + + * Implemented: SWP#MMAFW-1532 + * Fixes: NB#163774 - First absolute seek fails after playback start + * Fixes: NB#158231 - Last video frame is displayed briefly when playback has stopped and it's started again + * Gst-renderer, show last video frame when video at end. + * Making unittests to be compiled as single process on build-bot. + * Removed deprecated MafwRenderer API parts. + + -- Mika Tapojärvi Thu, 15 Apr 2010 11:42:07 +0300 + +qmafw-gst-renderer (0.0.1-33) unstable; urgency=low + + * Implemented: SWP#MMAFW-2057 + * Support "force aspect ratio" property in qmafw-gst-renderer. + * Removed deprecated MafwRenderer API parts. + * Updating run_valgrind.sh script for new valgring version. + * Two little coverity findings fixed. + + -- Mika Tapojärvi Mon, 12 Apr 2010 11:49:28 +0300 + +qmafw-gst-renderer (0.0.1-32) unstable; urgency=low + + * Fixes: NB#163168 - Qmafw renderer crash on youtube playback + * Fixes: NB#162434 - Removing items from a playing MafwPlaylist disturbs playback + * MafwGstRenderer no longer makes an LKM check for remote URI's. + * MafwProxyPlaylist caches size and current index as much as possible. + * dh_shlibdeps commented in debian/rules. + + -- Mika Tapojärvi Tue, 06 Apr 2010 13:29:47 +0300 + +qmafw-gst-renderer (0.0.1-31) unstable; urgency=low + + * Implemented: SWP#MMAFW-728 + * DRM fulfills the OVI service authentication (LKM) requirements. + * Moved LKM/CP stuff to common dir. + + -- Mika Tapojärvi Thu, 01 Apr 2010 09:18:29 +0300 + +qmafw-gst-renderer (0.0.1-30) unstable; urgency=low + + * Implemented: SW#MMAFW-2129 + * Implemented MafwGstRenderer property playback-speed. + * GstRenderer does not show the first frame when starting from certain point + in video media. + + -- Mika Tapojärvi Wed, 24 Mar 2010 07:47:26 +0200 + +qmafw-gst-renderer (0.0.1-29) unstable; urgency=low + + * Implemented: SWP#MMAFW-2114 + * Implemented: SWP#MMAFW-2137 + * Video pause frame must not be scaled in MAFW. + * MafwRenderer pauseAt addition plus bunch of changes. + + -- Mika Tapojärvi Mon, 22 Mar 2010 07:04:25 +0200 + +qmafw-gst-renderer (0.0.1-28) unstable; urgency=low + + * Implemented: SWP#MMAFW-2137 + * RHL: Video pause frame must not be scaled in MAFW + + -- Mika Tapojärvi Thu, 18 Mar 2010 15:24:05 +0200 + +qmafw-gst-renderer (0.0.1-27) unstable; urgency=low + + * Fixes: NB#160986 - qmafw-gst-renderer Disappears on attempting to play. + * Added the build dependency needed by libplayready0 + + -- Mika Tapojärvi Wed, 17 Mar 2010 13:57:44 +0200 + +qmafw-gst-renderer (0.0.1-26) unstable; urgency=low + + * Fixes: NB#159904 - libqmafw-gst-renderer depends on libosso1 + * Gst renderer use QmSystem instead of libosso in screen blanking prevention. + + -- Mika Tapojärvi Wed, 17 Mar 2010 06:58:27 +0200 + +qmafw-gst-renderer (0.0.1-25) unstable; urgency=low + + * Fixes: NB#158438 - Playback of the next clip does not happen after the DRM licence fetch error dialog is displayed. + * GstRenderer can be issued pause command quickly after play command has been + issued. + * Added clearing of the worker->is_error after drm check failure. As the + worker was exited, the is_error state wasn't handled in anyway later on. + * Removed unnecessary libosso from gst renderer's .pro file. + + -- Mika Tapojärvi Mon, 15 Mar 2010 10:13:50 +0200 + +qmafw-gst-renderer (0.0.1-24) unstable; urgency=low + + * Changed the initialisation test so that the default policy assignment + always fails. + * Even if it fails always, it doesn't affect the rest of the tests in anyway. + Just gives us more complete unittest. + + -- Mika Tapojärvi Mon, 08 Mar 2010 15:42:57 +0200 + +qmafw-gst-renderer (0.0.1-23) unstable; urgency=low + + * Implemented: SWP#MMAFW-2053 + * Implemented: SWP#MMAFW-871 + * Fixes: NB#156704 - Youtube playback_Play/Pause toggle gets reset on a seek operation + * Policy mgmt in qmafw-gst-renderer. + * Removed renderer error handling from state-machine. Now gst-renderer goes + into STOPPED state if there is an error that requires that. Updated + MafwRendererStateMachine unittest. + * Fixed coverity findings. + + -- Mika Tapojärvi Thu, 04 Mar 2010 15:16:11 +0200 + +qmafw-gst-renderer (0.0.1-22) unstable; urgency=low + + * Implemented: SWP#MMAFW-1964 + * Fixes: NB#158196 - Pausing the video right after playback starts will put renderer to invalid state + * If pause comes during seeking, now it is performed after the seek is + completed. + * MafwBasicRenderer has readonly property for policy override. + * New test.suppression files for valgrind. + + -- Mika Tapojärvi Mon, 01 Mar 2010 18:26:17 +0200 + +qmafw-gst-renderer (0.0.1-21) unstable; urgency=low + + * Implemented: SWP#MMAFW-2100 + * GST renderer publishing media duration to context framework. + + -- Mika Tapojärvi Mon, 22 Feb 2010 20:39:08 +0200 + +qmafw-gst-renderer (0.0.1-20) unstable; urgency=low + + * Unittests pass rate to 100% for mafw-gst-renderer. + * CI test and unittest for getCurrentMediaInfo. + + -- Mika Tapojärvi Thu, 18 Feb 2010 21:43:24 +0200 + +qmafw-gst-renderer (0.0.1-19) unstable; urgency=low + + * Implemented: SWP#MMAFW-1827 + * Implemented: SWP#MMAFW-1898 + * Fixes: NB#155284 - Tapping on the Player View - View Menu - Play Album stops the playback of the current playing song. + * Fixes: NB#155820 - Renderer goes into Stopped state after automatic next instead of Transitioning state. + * Only necessary headers are exported. + * MafwInMemoryPlaylist updates the current index based on the changes in the + playlist. + * MafwBasicRenderer also acts better to changes in the playlist contents. + * Removed the "fixes" statements from the changelog, because of current + problems in integration systems. + * Renderer goes into Stopped state after automatic next instead of + Transitioning state. + * Implemented MafwRenderer::getCurrentMediaInfo(). + + -- Mika Tapojärvi Tue, 09 Feb 2010 21:18:53 +0200 + +qmafw-gst-renderer (0.0.1-18) unstable; urgency=low + + * Non-maintainer upload. + * Version increased. + + -- Toni Mäki Mon, 08 Feb 2010 09:39:31 +0200 + +qmafw-gst-renderer (0.0.1-17) unstable; urgency=low + + * Bug 155343 removed - Last entry of a playlist plays twice when it comes to focus by 'Next' function + * context provider DBus name fixed not to conflict with MAFW plugin DBus name. + * Replace/Remove unnecessary includes. + + -- Mika Tapojärvi Wed, 03 Feb 2010 15:16:30 +0200 + +qmafw-gst-renderer (0.0.1-16) unstable; urgency=low + + * Version increased. + + -- Mika Tapojärvi Tue, 02 Feb 2010 10:22:49 +0200 + +qmafw-gst-renderer (0.0.1-15) unstable; urgency=low + + * Removing added renederer from registry at GstRenderer plugin destructor. + * implemented provider for context framework core property Media.NowPlaying + * provider for old context framework properties removed + * Versions increased. + * Maintainer and Standards-Version updated. + + -- Mika Tapojärvi Tue, 02 Feb 2010 10:08:24 +0200 + +qmafw-gst-renderer (0.0.1-14) unstable; urgency=low + + * Version increased. + + -- Mika Tapojärvi Wed, 27 Jan 2010 15:40:40 +0200 + +qmafw-gst-renderer (0.0.1-13) unstable; urgency=low + + * Version increased. + * Rebuild. + + -- Mika Tapojärvi Tue, 26 Jan 2010 12:39:48 +0200 + +qmafw-gst-renderer (0.0.1-12) unstable; urgency=low + + * MafwRenderer can be given a parent. + * MafwRenderer API Harmonization step1. Both scenarios supported with or + without callback slots. + + -- Mika Tapojärvi Mon, 25 Jan 2010 09:22:23 +0200 + +qmafw-gst-renderer (0.0.1-11) unstable; urgency=low + + * Mafw-Gst-renderer returns 0 for getPosition() when gstreamer isn't playing + or paused. + * Renderer property API tests for gst-renderer. + + -- Mika Tapojärvi Thu, 21 Jan 2010 15:18:07 +0200 + +qmafw-gst-renderer (0.0.1-10) unstable; urgency=low + + * Non-maintainer upload. + * Version increased. + + -- Toni Mäki Tue, 19 Jan 2010 08:29:28 +0200 + +qmafw-gst-renderer (0.0.1-9) unstable; urgency=low + + * Version increased. + + -- Mika Tapojärvi Mon, 18 Jan 2010 13:42:48 +0200 + +qmafw-gst-renderer (0.0.1-8) unstable; urgency=low + + * Mostly editorial fixes based on code review. + + -- Mika Tapojärvi Fri, 15 Jan 2010 13:24:47 +0200 + +qmafw-gst-renderer (0.0.1-7) unstable; urgency=low + + * Non-maintainer upload. + * Version increased after release. + + -- Toni Mäki Fri, 15 Jan 2010 08:57:03 +0200 + +qmafw-gst-renderer (0.0.1-6) unstable; urgency=low + + * Fixes: NB#151525 - qmafw fails to build with cs2009q1 toolchain + * Play URI implemented in renderer state-machine. + * Increased the version numbers for unstable repo delivery. + * Next & previous will wrap in first and last item respectively. + * qmafw gst renderer added libqttracker-dev to build-depends. + * qmafw gst renderer include path change did not help undo. + * good GStreamer error mapping. + * qmafw gst renderer's QtTracker includepath. + * gst renderer usageCounter and contentAccessed update. + * Error signal from renderer now goes all the way through to MTG + * Various changes to Renderer CI and unittests because of new + qmafw-gst-renderer and changed playlist API. + * setting global_drm_uri to NULL in init(). + * context framework fixes. + * MafwRenderer can now signal that is doesn't have a playlist to play, via + playlistChanged signal. + * Added seek functionality to MafwBasicRenderer, with additional fixes to URL + handling. Was doubly encoded sometimes. + * Changes after the updated toolchain. + * Also some changes after the playlist sorting API. + * better drm testing. + * added gettersTestCase and improved coverage. + * added resumeDelayedTestCase. + * merged mafw-gst-renderer from branch to trunk. + * merged drm stuff from fmafw trunk gst-renderer. + + -- Mika Tapojärvi Tue, 12 Jan 2010 09:08:17 +0200 + +qmafw-gst-renderer (0.0.1-5) unstable; urgency=low + + * Non-maintainer upload. + * Version increased. + + -- Toni Mäki Fri, 08 Jan 2010 11:14:06 +0200 + +qmafw-gst-renderer (0.0.1-4) unstable; urgency=low + + * Version increased. + + -- Toni Mäki Thu, 07 Jan 2010 11:28:58 +0200 + +qmafw-gst-renderer (0.0.1-3) unstable; urgency=low + + * Version increased. + + -- Toni Mäki Wed, 23 Dec 2009 11:53:04 +0200 + +qmafw-gst-renderer (0.0.1-2) unstable; urgency=low + + * compiled with new toolchain + * Non-maintainer upload. + * Version increased. + + -- Toni Mäki Mon, 21 Dec 2009 15:15:34 +0200 + +qmafw-gst-renderer (0.0.1-1) unstable; urgency=low + + * Initial release + + -- Seppo Yliklaavu Tue, 27 Oct 2009 11:01:57 +0200 + diff --git a/qmafw-gst-subtitles-renderer/debian/compat b/qmafw-gst-subtitles-renderer/debian/compat new file mode 100644 index 0000000..7ed6ff8 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/debian/compat @@ -0,0 +1 @@ +5 diff --git a/qmafw-gst-subtitles-renderer/debian/control b/qmafw-gst-subtitles-renderer/debian/control new file mode 100644 index 0000000..61b45c2 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/debian/control @@ -0,0 +1,30 @@ +Source: qmafw-gst-subtitles-renderer +Section: debug +Priority: extra +Maintainer: Roman Moravcik +XSBC-Original-Maintainer: Mika Tapojarvi +Build-Depends: debhelper (>= 4), libqmafw0-dev, libglib2.0-dev, + libgstreamer0.10-dev (>= 0.10.32.2), libgq-gconf-dev, + libgstreamer-plugins-base0.10-dev (>= 0.10.28.3), + libqmsystem2-dev,libcontextprovider-dev, + libqtsparql-dev, libcontextsubscriber-dev, + libdbus-qeventloop-dev, libtotem-plparser-dev, usb-moded-dev, + pkg-config +Standards-Version: 3.8.0 +Homepage: http://mafwsubrenderer.garage.maemo.org/ +Vcs-Browser: https://garage.maemo.org/plugins/ggit/browse.php/?p=mafwsubrenderer +Vcs-Git: https://vcs.maemo.org/git/mafwsubrenderer + +Package: libqmafw-gst-subtitles-renderer +Architecture: any +Depends: ${shlibs:Depends}, libqtsparql0, libqtsparql-tracker, libdbus-qeventloop1, gstreamer0.10-plugins-bad (>= 0.10.21), gstreamer0.10-plugins-base-subtitles (>= 0.10.34-0maemo1+0m6) +Replaces: qmafw-gst-renderer +Description: QMAFW GStreamer renderer with subtitles support + GStreamer based renderer plugin for QMAFW. + +Package: libqmafw-gst-subtitles-renderer-dbg +Architecture: any +Depends: libqmafw-gst-subtitles-renderer (= ${binary:Version}) +Priority: extra +Description: Debug symbols for QMAFW GStreamer renderer with subtitles support + Debug symbols for QMAFW GStreamer renderer, makes debugging fun. diff --git a/qmafw-gst-subtitles-renderer/debian/copyright b/qmafw-gst-subtitles-renderer/debian/copyright new file mode 100644 index 0000000..269555d --- /dev/null +++ b/qmafw-gst-subtitles-renderer/debian/copyright @@ -0,0 +1,3 @@ +Copyright (C) 2009 Nokia Corporation. All rights reserved. + +Contact: Visa Smolander diff --git a/qmafw-gst-subtitles-renderer/debian/dirs b/qmafw-gst-subtitles-renderer/debian/dirs new file mode 100644 index 0000000..6845771 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/debian/dirs @@ -0,0 +1 @@ +usr/lib diff --git a/qmafw-gst-subtitles-renderer/debian/libqmafw-gst-subtitles-renderer.install b/qmafw-gst-subtitles-renderer/debian/libqmafw-gst-subtitles-renderer.install new file mode 100644 index 0000000..6b70264 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/debian/libqmafw-gst-subtitles-renderer.install @@ -0,0 +1,4 @@ +usr/lib/qmafw-plugin/* +usr/share/dbus-1/services/* +usr/share/contextkit/providers/* +usr/share/qmafw/* diff --git a/qmafw-gst-subtitles-renderer/debian/libqmafw-gst-subtitles-renderer.postinst b/qmafw-gst-subtitles-renderer/debian/libqmafw-gst-subtitles-renderer.postinst new file mode 100644 index 0000000..56b8eb4 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/debian/libqmafw-gst-subtitles-renderer.postinst @@ -0,0 +1,5 @@ +#!/bin/sh + +set -e + +chmod 444 /usr/share/qmafw/mafw-gst-renderer-plugin.conf diff --git a/qmafw-gst-subtitles-renderer/debian/rules b/qmafw-gst-subtitles-renderer/debian/rules new file mode 100755 index 0000000..b681d05 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/debian/rules @@ -0,0 +1,98 @@ +#!/usr/bin/make -f +# -*- makefile -*- +# Sample debian/rules that uses debhelper. +# This file was originally written by Joey Hess and Craig Small. +# As a special exception, when this file is copied by dh-make into a +# dh-make output file, you may use that output file without restriction. +# This special exception was added by Craig Small in version 0.37 of dh-make. + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + + +APPNAME = qmafw-gst-subtitles-renderer + +ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS))) + MODE=debug +else + MODE=release +endif + +configure: configure-stamp +configure-stamp: + dh_testdir + # Add here commands to configure the package. + qmake PREFIX=/usr MODE=$(MODE) $(APPNAME).pro && $(MAKE) + + touch configure-stamp + + +build: build-stamp + +build-stamp: configure-stamp + dh_testdir + + # Add here commands to compile the package. + $(MAKE) + #docbook-to-man debian/qmafw-gst-renderer.sgml > qmafw-gst-renderer.1 + + touch $@ + +clean: + dh_testdir + dh_testroot + rm -f build-stamp configure-stamp + + # Add here commands to clean up after the build process. + -rm -f debian/build-stamp + [ ! -f Makefile ] || $(MAKE) distclean + + dh_clean + +install: build + dh_testdir + dh_testroot + dh_clean -k + dh_installdirs + + # Add here commands to install the package into debian/app_name + $(MAKE) INSTALL_ROOT=$(CURDIR)/debian/$(APPNAME) install + + +# Build architecture-independent files here. +binary-indep: build install +# We have nothing to do by default. + +# Build architecture-dependent files here. +binary-arch: build install + dh_testdir + dh_testroot + dh_installchangelogs + dh_installdocs + dh_installexamples + dh_install -a --sourcedir=debian/$(APPNAME) +# dh_installmenu +# dh_installdebconf +# dh_installlogrotate +# dh_installemacsen +# dh_installpam +# dh_installmime +# dh_python +# dh_installinit +# dh_installcron +# dh_installinfo + dh_installman + dh_link + dh_strip --dbg-package=libqmafw-gst-subtitles-renderer-dbg + dh_compress + dh_fixperms +# dh_perl + dh_makeshlibs + dh_installdeb + dh_shlibdeps + dh_gencontrol + dh_md5sums + dh_builddeb + +binary: binary-indep binary-arch +.PHONY: build clean binary-indep binary-arch binary install configure diff --git a/qmafw-gst-subtitles-renderer/inc/MafwBlankingPreventer.h b/qmafw-gst-subtitles-renderer/inc/MafwBlankingPreventer.h new file mode 100644 index 0000000..14bf085 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/inc/MafwBlankingPreventer.h @@ -0,0 +1,57 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#ifndef MAFW_BLANKING_PREVENTER_H +#define MAFW_BLANKING_PREVENTER_H + +#include +#include + +namespace MeeGo +{ + class QmDisplayState; +}; + +/** + * Helper class for preventing screen blanking. + */ +class MafwBlankingPreventer : public QObject +{ + Q_OBJECT + +public: + MafwBlankingPreventer(QObject* parent); + + /** + * Disable screen blanking. + */ + void blankingProhibit(); + + /** + * Enable screen blanking. + */ + void blankingAllow(); + +private Q_SLOTS: + void refresh(); + +private: + QTimer m_refreshTimer; + MeeGo::QmDisplayState *m_display; +}; + +#endif // MAFW_BLANKING_PREVENTER_H diff --git a/qmafw-gst-subtitles-renderer/inc/MafwGstRenderer.h b/qmafw-gst-subtitles-renderer/inc/MafwGstRenderer.h new file mode 100644 index 0000000..9e3fcb0 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/inc/MafwGstRenderer.h @@ -0,0 +1,508 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#ifndef MAFW_GSTRENDERER_INTERFACE_H +#define MAFW_GSTRENDERER_INTERFACE_H + +#include "mafw-gst-renderer-worker.h" +#include "MafwGstRendererHaltState.h" + +#include +#include +#include + +#include +#include + +class MafwBlankingPreventer; +class MafwGstRendererVolume; +class MafwGstRendererDolby; +class MafwGstRendererNetworkMonitor; +class ContextProperty; +class QDBusMessage; +class MafwGstRendererPlaylistFileUtility; +class MafwMmcMonitor; +class QSettings; +class MafwGstScreenshot; +class MafwGstRendererHaltState; + +class QSparqlConnection; +class QSparqlResult; + +/** + * Implements MAFW GStreamer renderer. + * @credential TrackerReadAccess Tracker read access + * @credential TrackerWriteAccess Tracker write access + * + */ +class MafwGstRenderer : public MafwBasicRenderer +{ + Q_OBJECT + +private: + /** + * Callback function that gets called when the GStreamer worker pipeline has + * gone to playing state. + * @param worker Pointer to GStreamer worker instance + * @param owner Pointer to GStreamer worker owner (instance of this class) + */ + static void playCallback(MafwGstRendererWorker *worker, gpointer owner); + + /** + * Callback function that gets called when the GStreamer worker pipeline has + * gone to paused state. + * @param worker Pointer to GStreamer worker instance + * @param owner Pointer to GStreamer worker owner (instance of this class) + */ + static void pauseCallback(MafwGstRendererWorker *worker, gpointer owner); + + /** + * Callback function that gets called when the GStreamer worker pipeline has + * reached EOS state. + * @param worker Pointer to GStreamer worker instance + * @param owner Pointer to GStreamer worker owner (instance of this class) + */ + static void eosCallback(MafwGstRendererWorker *worker, gpointer owner); + + /** + * Callback for informing when resources are no longer used in PAUSED state. + * @param worker Pointer to GStreamer worker instance + * @param owner Pointer to GStreamer worker owner (instance of this class) + */ + static void readyStateCallback(MafwGstRendererWorker *worker, gpointer owner); + + /** + * Callback function that gets called when the GStreamer worker signals an + * error. + * @param worker Pointer to GStreamer worker instance + * @param owner Pointer to GStreamer worker owner (instance of this class) + * @param error Pointer to signalled error + */ + static void errorCallback(MafwGstRendererWorker *worker, + gpointer owner, + const GError *error); + + /** + * Callback function that gets called when the GStreamer worker signals + * new metadata. + * @param worker Pointer to GStreamer worker instance + * @param owner Pointer to GStreamer worker owner (instance of this class) + * @param key Metadata key indicating what metadata is signalled + * @param type Type of signalled metadata + * @param value Pointer to actual metadata value + */ + static void metadataCallback(MafwGstRendererWorker *worker, + gpointer owner, + gint key, + GType type, + gpointer value); + + /** + * Callback function that gets called when the GStreamer worker signals + * a (new) property value. + * @param worker Pointer to GStreamer worker instance + * @param owner Pointer to GStreamer worker owner (instance of this class) + * @param id ID indicating what property is signalled + * @param value Pointer to actual property value + */ + static void propertyCallback(MafwGstRendererWorker *worker, + gpointer owner, + gint id, + GValue *value); + + /** + * Callback function that gets called when the GStreamer worker signals + * a status when it is buffering a stream. + * @param worker Pointer to GStreamer worker instance + * @param owner Pointer to GStreamer worker owner (instance of this class) + * @param percent Buffering percentage value + */ + static void bufferStatusCallback(MafwGstRendererWorker *worker, + gpointer owner, + gdouble percent); + + static void blankingControlCallback(MafwGstRendererWorker *worker, + gpointer owner, + gboolean prohibit); + + /** + * Callback funtion that gets called when we save new pause frame. + * @param worker Pointer to GStreamer worker instance + * @param owner Pointer to GStreamer worker owner (instence of this class) + * @param buffer Pointer to GstBuffer that contains pause frame data + * @param filename Location where the pause frame is saved + * @param cancel True if pause frame saving is canceled + */ + static void screenshotCallback(MafwGstRendererWorker *worker, + gpointer owner, + GstBuffer *buffer, + const char *filename, + gboolean cancel); + + /** + * Helper function to convert a GValue to a QVariant + * @param v Pointer to GValue to be converted + * @return QVariant holding the same value as the GValue held + */ + static QVariant getValue(const GValue *v); + + /** + * Mapping between GStreamer worker metadata vs mafw metadata + */ + static const QHash& metadataMap(); + + /** + * Mapping of obtained audio route to internal type + */ + static const QHash >& audioRouteMap(); + + /** + * Mapping of obtained video route to internal type + */ + static const QHash >& videoRouteMap(); + + /** + * Mapping between GStreamer worker errors vs mafw errors + */ + static const QHash& errorMap(); + +public: + + MafwGstRenderer(const QString& uuid, + const QString& pluginName, + const QString& name, + QObject* parent = 0); + /** + * Desctructor + */ + ~MafwGstRenderer(); + + /** + * Initializes the renderer. + * @param settings Settings to use for configuration + * @return True on success, false otherwise + */ + bool initialize(QSettings *settings); + +public: //MafwBasicRenderer implementation + + void doPlay(const MafwContent& content); + void doPlay(const MafwMediaInfo& mediaInfo); + + void doStop(); + + void doPause(); + + void doResume(); + + void doSeek(int position, MafwRenderer::SeekMode); + + bool doNextHint(const MafwContent& next); + bool doNextHint(const MafwMediaInfo& nextMediaInfo); + +public: // MafwRenderer implementation + bool getPosition(QObject* resultsReceiver, const char* resultsMember); + + bool setMafwProperty(const QString& name, const QVariant& value); + bool mafwProperty(QString& name, QObject* receiver, const char* member); + bool getCurrentMediaInfo(QObject* receiver, const char* member, const QString& metadataKey=0); + +private: //functionality + void playURI(const QString& uri); + void sendMediaInfo(const MafwMediaInfo& content, QObject* receiver, const char* member); + bool connectNameOwnerChanged(); + void setConfiguration(QSettings *settings); + QVariant readSettingsValue(QSettings *settings, const QString &valueName, + const QVariant &defaultValue) const; + /*Helper function for metadataCallback(). Appends related metadata to the result list for emitting metadataChanged().*/ + void appendRelatedMetadata(const QString key, QList* results); + void stampIt(const QString& node, int usageCount, int mediaDuration); + /** + * Helper function which sets the error code to be RendererError_UnsupportedResolution if + * resolution is above boundaries 720 x 1280, otherwise error code is to + * RendererError_UnsupportedResolution. + * @param error reference to error which code is set. + */ + void handleResolutionError(MafwError& error); + /** Helper function for creating a MafwError from GError */ + MafwError constructMafwError(const GError* error); + +private Q_SLOTS: + + /** + * Slot to call when the current playback position queried. + * @param resultsReceiver Pointer to object where to deliver result + * @param resultsMember Pointer to member where to deliver result + */ + void slotGetPosition(QObject* resultsReceiver, + const char* resultsMember); + + /** + * Slot to call when a property value is queried. + * @param name Property name to be queried + * @param receiver Pointer to object where to deliver result + * @param member Pointer to member where to deliver result + */ + void slotMafwProperty(const QString& name, QObject* receiver, const char* member); + + /** + * Store usageCounter and contentAccessed properties of currently playing media to Tracker. + */ + void slotStamp(); + + /** + * Slot to call when stampit has finished. + */ + void slotStampItDone(); + + /** + * usageCounter query ready in play uri case + */ + void slotStampQueryReady(); + + /** + * Commands the next URI to be played. + */ + void playNext(); + + /** + * Tries to play the next URI from the given playlist URI. If no next + * URI commands playNext() + */ + void playNextURIFromPlaylist(); + + /** + * Slot to call asynchronously to restart playback + * (e.g. when internet radio disconnect due to network issues) + */ + void restartPlay(); + + /** + * Query metadata from currently played clip or stream. + */ + void slotGetCurrentMediaInfo(QObject* receiver, const char* member, const QString& metadataKey); + + /** + * Called when volume has been changed. + */ + void handleVolumeChange(uint level); + + /** + * Slot called when audio/video route context framework property changes. + */ + void slotRouteChanged(); + + /** + * Called when a property has been changed. + */ + void handlePropertyChanged(const QString& name, const QVariant& value); + + /** + * Called when Dolby Headphones Mobile music property has been changed. + */ + void handleDHMMusicPropertyChanged(); + + /** + * Called when Dolby Headphones Mobile video property has been changed. + */ + void handleDHMVideoPropertyChanged(); + + /** + * Handle NameOwnerChanged signal of policy-context-daemon. + */ + void handleContextProviderRemoval( const QDBusMessage& message ); + + /** + * Starts playing playlist file. This is called when parsing has been done. + */ + void startPlayingPlaylistFile(); + + /** + * Handles the errors occured while parsing playlist file. + * @param succeeded true if no errors occured. + */ + void handlePlaylistFileParsingErrors(bool succeeded); + + /** + * Stops streaming if necessary + */ + void stopStreaming(); + + /** + * Halts streaming if necessary, stores the needed info for future reference (@see MafwGstRenderer::continueStreaming()) + */ + void haltStreaming(); + + /** + * Continues streaming + */ + void continueStreaming(); + + /** + * Any timers e.g. playlist file util & stamping timers will be stopped here + */ + void stopTimers(); + + /** + * Called when new pause frame is ready or + * when there was error with pause frame encoding. + */ + void handleScreenshot(char *location, GError *error); + + /** + * Called when pause frame is cancelled. + */ + void cancelScreenshot(); + + /** + * MMC going to be unmounted. + */ + void mmcPreUnmount(); + +Q_SIGNALS: + void signalGetPosition(QObject* resultsReceiver, + const char* resultsMember); + + void signalMafwProperty(const QString& name, QObject* receiver, + const char* member); + + void signalGetCurrentMediaInfo(QObject*, const char*, const QString); + +private: //data + + /** + * Has this instance been initialized? + */ + bool m_initialized; + + /** + * Helps determining whether to request a new item to play + */ + MafwRenderer::State m_currentState; + + /** + * The next Content to play, if any. + */ + MafwMediaInfo m_nextContent; + + /** + * The currently to be played Content + */ + MafwMediaInfo m_currentContent; + + QMap > m_currentMetaData; + + /** + * What item is the renderer currently playing? + */ + MafwRendererPlayingUri m_playingItem; + + /** + * GStreamer renderer worker + */ + MafwGstRendererWorker *m_worker; + + /** + * Timer for stamping media usageCount and contentAccessed + */ + QTimer m_playedStampTimer; + + MafwBlankingPreventer* m_blankingPreventer; + + MafwGstScreenshot* m_screenshot; + + /** + * Help to monitor network access + */ + MafwGstRendererNetworkMonitor *m_networkMonitor; + + /** + * "Halt" state to store state when changing network access point + */ + MafwGstRendererHaltState m_haltState; + + /** + * Object to handle volume + */ + MafwGstRendererVolume* m_volume; + + /** + * Object to handle Dolby Headphones Mobile plugin + */ + MafwGstRendererDolby* m_dolby; + + /** + * Context framework property for video route + */ + ContextProperty *m_videoRoute; + + /** + * Context framework property for audio route + */ + ContextProperty *m_audioRoute; + + /** + * Is contentAccessed and usageCount of current playing item already updated. (Update process has started) + */ + bool m_playedStamped; + int m_playedStampTryCounter; + + /** + * Used to stamp usageCount and updated duration + */ + QSparqlConnection *m_sparqlConnection; + /** + * If playing just an URI this result is used to get the tracker urn for it + */ + QSparqlResult *m_urnQueryResult; + + /** + * Result handle for stampit actions + */ + QSparqlResult *m_stampItResult; + + /** + * Pointer to playlist file parsing utility + */ + MafwGstRendererPlaylistFileUtility* m_playlistFileUtil; + + /** + * Timer to try to play next item from given playlist URI, totem parser can be slow sometimes. + */ + QTimer m_playlistNextTimer; + + /** + * Flag indicating whether we are playing playlist file. + */ + bool m_playingPlaylistFile; + + /** + * The error we got when we have possibly tried gstreamer to play playlist file. + */ + GError* m_unsupportedTypeError; + + /** + * Flag indicating whether we have already played an item from URI playlist. + * I.e. an URI was given which points to a playlist, this flag + * tells if rendererPlaying() signal has already been sent for URI. + */ + bool m_playedPlaylistItem; + + MafwMmcMonitor* m_mmcMonitor; +}; + +#endif // MAFW_GSTRENDERER_INTERFACE_H diff --git a/qmafw-gst-subtitles-renderer/inc/MafwGstRendererDolby.h b/qmafw-gst-subtitles-renderer/inc/MafwGstRendererDolby.h new file mode 100644 index 0000000..6382000 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/inc/MafwGstRendererDolby.h @@ -0,0 +1,81 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#ifndef MAFWGSTRENDERERDOLBY_H +#define MAFWGSTRENDERERDOLBY_H + +#include +#include + +/** + * Provides volume setting, getting and listening functionality using + * PulseAudioMainVolume DBus API. + */ +class MafwGstRendererDolby : public QObject +{ + Q_OBJECT +public: + /** + * Constructor + */ + MafwGstRendererDolby( QObject* parent ); + ~MafwGstRendererDolby(); + void initialize(); + bool setMusicDolbyState (uint value); + bool setMusicDolbyRoom (int value); + bool setMusicDolbyColor (int value); + uint getMusicDolbyState (); + int getMusicDolbyRoom (); + int getMusicDolbyColor (); + bool setVideoDolbyState (uint value); + bool setVideoDolbyRoom (int value); + bool setVideoDolbyColor (int value); + uint getVideoDolbyState (); + int getVideoDolbyRoom (); + int getVideoDolbyColor (); + +Q_SIGNALS: + /** + * Signal telling that music surround is OFF/ON/AUTO. + */ + void mafwDHMMusicPropertyChanged(); + /** + * Signal telling that video surround is OFF/ON/AUTO. + */ + void mafwDHMVideoPropertyChanged(); + +private Q_SLOTS: + void valueMusicChanged(); + void valueVideoChanged(); + +private: + uint m_currentMusicDolbyState; + int m_currentMusicDolbyRoom; + int m_currentMusicDolbyColor; + uint m_currentVideoDolbyState; + int m_currentVideoDolbyRoom; + int m_currentVideoDolbyColor; + + GConfItem *m_dolbyConfMusic; + GConfItem *m_dolbyConfMusicRoom; + GConfItem *m_dolbyConfMusicColor; + GConfItem *m_dolbyConfVideo; + GConfItem *m_dolbyConfVideoRoom; + GConfItem *m_dolbyConfVideoColor; +}; + +#endif // MAFWGSTRENDERERDOLBY_H diff --git a/qmafw-gst-subtitles-renderer/inc/MafwGstRendererHaltState.h b/qmafw-gst-subtitles-renderer/inc/MafwGstRendererHaltState.h new file mode 100644 index 0000000..d7168d1 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/inc/MafwGstRendererHaltState.h @@ -0,0 +1,83 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#ifndef MAFWGSTRENDERERHALTSTATE_H +#define MAFWGSTRENDERERHALTSTATE_H + +#include + +#include + +class MafwGstRendererHaltState : public QObject +{ + Q_OBJECT +public: + static int DECAY_TIME; +public: + MafwGstRendererHaltState(); + MafwGstRendererHaltState(const QString &uri, MafwRenderer::State state, int position); + ~MafwGstRendererHaltState(); + + /** + * Assigns other halt state to this object, also resets the decay timer. + */ + MafwGstRendererHaltState(const MafwGstRendererHaltState &other); + + /** + * Assigns other halt state to this object, also resets the decay timer. + */ + MafwGstRendererHaltState& operator =(const MafwGstRendererHaltState &other); + + + /** + * Set new renderer state for halt state, user pressed pause etc + * @param newState, if newState is MafwRenderer::Paused the decay time is paused also + */ + void setState(MafwRenderer::State newState); + + /** + * Is the halt state valid? Not decayed and contains relevenat information + */ + bool isSet() const; + + /** + * Clears the halt state from any valid content, also stop the decay timer + */ + void clear(); + + QString uri() const; + MafwRenderer::State state() const; + int position() const; + +Q_SIGNALS: + /** + * This signal is emitted when the MafwGstRendererHaltState::DECAY_TIME has passed + * from object creation or when valid parameters were assigned to it. + */ + void decayed(); + +private: + void initializeDecayTimer(); + +private: + QString m_uri; + MafwRenderer::State m_state; + int m_position; + QTimer m_decayTimer; +}; + +#endif // MAFWGSTRENDERERHALTSTATE_H diff --git a/qmafw-gst-subtitles-renderer/inc/MafwGstRendererNetworkMonitor.h b/qmafw-gst-subtitles-renderer/inc/MafwGstRendererNetworkMonitor.h new file mode 100644 index 0000000..48a345f --- /dev/null +++ b/qmafw-gst-subtitles-renderer/inc/MafwGstRendererNetworkMonitor.h @@ -0,0 +1,45 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#ifndef MAFWGSTRENDERERNETWORKMONITOR_H +#define MAFWGSTRENDERERNETWORKMONITOR_H + +#include +#include + +class QNetworkConfigurationManager; +class MafwGstRendererNetworkMonitor : public QObject +{ + Q_OBJECT + +public: + MafwGstRendererNetworkMonitor(); + virtual ~MafwGstRendererNetworkMonitor(); + +Q_SIGNALS: + void prepareNetworkChange(); + void networkChangeFinished(); + +private Q_SLOTS: + void handleConfigurationChange(const QNetworkConfiguration & config); + +private: + QNetworkConfigurationManager *m_networkManager; + QNetworkConfiguration m_currentConfiguration; +}; + +#endif // MAFWGSTRENDERERNETWORKMONITOR_H diff --git a/qmafw-gst-subtitles-renderer/inc/MafwGstRendererPlaylistFileUtility.h b/qmafw-gst-subtitles-renderer/inc/MafwGstRendererPlaylistFileUtility.h new file mode 100644 index 0000000..3e5bb76 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/inc/MafwGstRendererPlaylistFileUtility.h @@ -0,0 +1,94 @@ +#ifndef MafwGstRendererPlaylistFileUtility_H +#define MafwGstRendererPlaylistFileUtility_H + +#include +#include +#include +#include + +class QUrl; + +/** + * Utility class for parsing playlist files. + */ +class MafwGstRendererPlaylistFileUtility : public QObject +{ + Q_OBJECT +public: + /** + * Constructor + */ + MafwGstRendererPlaylistFileUtility(QObject* parent); + + /** + * Destructor + */ + ~MafwGstRendererPlaylistFileUtility(); + + /** + * Returns the parsed URI list + * @return list of parsed URIs + */ + QStringList getUriList(); + + /** + * Starts playlist file parsing, parsingReady is signalled when parsing + * has been done. Parsed uris can be fetched using getUri() function. + * @param uri The absolute uri to the playlist file. + */ + void parsePlaylistFile(const QUrl& uri); + + /** + * Removes the first unused uri parsed from playlist and returns it. + * @return The first unused playlist uri or empty string if each uris are + * used or parsing has been failed. + */ + QString takeFirstUri(); + + /** + * Saves the pending error that will be fired, if no new URI's are found. + * This error saving feature is needed because we don't get the proper + * "parsing ended" signals from Totem playlist parser. + * @param error The error to be saved. + */ + void setPendingError(MafwError& error); + + /** + * Returns the saved error. + * @return The saved error. + */ + MafwError takePendingError(); + +Q_SIGNALS: + /** Signal telling that first uri on playlist file is parsed. */ + void firstItemParsed(); + /** Signal telling that parsing is ready. */ + void parsingReady(bool succeeded); + +private: + static void readyCb(TotemPlParser* parser, + GAsyncResult *async_result, + MafwGstRendererPlaylistFileUtility* self); + static void uriParsed(TotemPlParser *parser, + gchar* uri, + gpointer metadata, + MafwGstRendererPlaylistFileUtility* self); + +private: + /** Modifies the resulting item URI if necessary. + * Unfortunately there are various reasons why this is required. + */ + QString manHandleURI(const QString &itemUri) const; + +private: + /** Unique id (=pointer) of currently used parser. */ + TotemPlParser* m_parserId; + /** The list of parsed uris. */ + QStringList m_uriList; + /** Parsing the first item */ + bool m_firstItem; + /** The pending error */ + MafwError m_pendingError; +}; + +#endif // MafwGstRendererPlaylistFileUtility_H diff --git a/qmafw-gst-subtitles-renderer/inc/MafwGstRendererPlugin.h b/qmafw-gst-subtitles-renderer/inc/MafwGstRendererPlugin.h new file mode 100644 index 0000000..05863aa --- /dev/null +++ b/qmafw-gst-subtitles-renderer/inc/MafwGstRendererPlugin.h @@ -0,0 +1,58 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#ifndef MAFW_GST_RENDERER_PLUGIN_H +#define MAFW_GST_RENDERER_PLUGIN_H + +#include +#include + +class MafwGstRendererPlugin : public QObject, public MafwPlugin +{ + Q_OBJECT + Q_INTERFACES(MafwPlugin) + +public: + void initialize(MafwInternalRegistry* registry); + ~MafwGstRendererPlugin(); + + QString name() const; + +protected: + MafwInternalRegistry* m_registry; + +private: + QList m_rendererIds; + + /** + * Loads gst-renderers from config file. + * Config file contais "renderers" and "in-process-renderers" arrays. + * Renderer Array contains gst-renderer's id and it's "friendly" name. + * Syntax of the "renderers" array: + * [renderers] + * 1\Id=mafw_gst_renderer + * 1\FriendlyName=MafwGstRenderer + * 2\Id=mafw_gst_internal_video_renderer + * 2\FriendlyName=MafwGstVideoRenderer + * size=2 + * If config file is deleted of empty we create default renderer with "mafw_gst_renderer" uuid. + **/ + void loadRenderers(const QString& rendererArrayKey); + +}; + +#endif diff --git a/qmafw-gst-subtitles-renderer/inc/MafwGstRendererVolume.h b/qmafw-gst-subtitles-renderer/inc/MafwGstRendererVolume.h new file mode 100644 index 0000000..47d415d --- /dev/null +++ b/qmafw-gst-subtitles-renderer/inc/MafwGstRendererVolume.h @@ -0,0 +1,97 @@ +#ifndef MAFWGSTRENDERERVOLUME_H +#define MAFWGSTRENDERERVOLUME_H + +#include + +class DBusConnection; +class DBusMessage; +class DBusPendingCall; +class DBusMessageIter; +class DBusPendingCall; + +/** + * Provides volume setting, getting and listening functionality using + * PulseAudioMainVolume DBus API. + */ +class MafwGstRendererVolume : public QObject +{ + Q_OBJECT +public: + /** + * Constructor + */ + MafwGstRendererVolume(); + ~MafwGstRendererVolume(); + + /** + * Get the volume level. Initial volume level is always got via volumeChanged signal. + * Before that signal returns always 0. + * @return The current volume level. Value is between 0 to 99. + */ + uint getVolume(); + + /** + * Set the volume level to pulse audio. + * @note Volume setting happens asynchronously + * and may wait until dbus connection to pulse audio is ready. + * @param The volume level to be set. Valid value range is between 0 to 99. + * @return true on success. + */ + bool setVolume (uint value); + +Q_SIGNALS: + /** + * Signal telling that volume level has been changed. + */ + void volumeChanged(uint newLevel); + +private Q_SLOTS: + /** Makes p2p dbus connection to pulse audio. */ + void connectToPulseAudio(); + +private: + /** + * Starts to listen signal from PulseAudioMainVolume DBus API + */ + void listenVolumeSignals(); + + /** + * Get the step configuration asynchronously from PulseAudioMainVolume + */ + void getRestoreEntryForMediaRole(); + + /** + * Catch signals from PulseAudioMainVolume telling that volume step configuration has been changed + */ + static void handleIncomingMessages( DBusConnection* conn, + DBusMessage* message, + MafwGstRendererVolume* self); + + /** + * Catch reply callback for step configuration request from PulseAudioMainVolume. + */ + static void getEntryReply(DBusPendingCall *pending, MafwGstRendererVolume *self); + + static void volumeReply(DBusPendingCall *pending, MafwGstRendererVolume *self); + + bool readVolumeFromStruct(DBusMessageIter *iterator); + +private: + /** The current volume step, values are between 0, used maximum volume level */ + uint m_currentVolume; + + /** The volume step which is pending to be set. */ + uint m_pendingVolumeValue; + + /** The dbus connection object */ + DBusConnection* m_dbusConnection; + + QString m_objectPath; + + /** The pending call. */ + DBusPendingCall* m_pendingCall; + + +}; + +#endif // MAFWGSTRENDERERVOLUME_H diff --git a/qmafw-gst-subtitles-renderer/inc/MafwGstScreenshot.h b/qmafw-gst-subtitles-renderer/inc/MafwGstScreenshot.h new file mode 100644 index 0000000..a032c04 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/inc/MafwGstScreenshot.h @@ -0,0 +1,55 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#ifndef MAFWGSTSCREENSHOT_H +#define MAFWGSTSCREENSHOT_H + +#include +#include + +class MafwGstScreenshot : public QObject +{ + Q_OBJECT +public: + MafwGstScreenshot(QObject* parent); + ~MafwGstScreenshot(); + + bool savePauseFrame(GstBuffer *buffer, const char *filename); + void cancelPauseFrame(); + bool reportBack(GError *error); + +private: + /* All GStreamer elements are owned by m_pipeline. + Elements are destroyed when m_pipeline is destroyed.*/ + GstElement *m_src; + GstElement *m_sink; + GstElement *m_pipeline; + GstElement *m_filter; + GstElement *m_enc; + GstElement *m_csp; + GstBus *m_bus; + GstCaps *m_caps; + GstStructure *m_structure; + + gulong m_handler_id; + +Q_SIGNALS: + void screenshotCancelled(); + void screenshotTaken(char *location, GError *error); +}; + +#endif // MAFWGSTSCREENSHOT_H diff --git a/qmafw-gst-subtitles-renderer/inc/MafwMmcMonitor.h b/qmafw-gst-subtitles-renderer/inc/MafwMmcMonitor.h new file mode 100644 index 0000000..8c65055 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/inc/MafwMmcMonitor.h @@ -0,0 +1,59 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#ifndef MAFW_MMC_MONITOR_H +#define MAFW_MMC_MONITOR_H + +#include + +#include + +/** + * Helper class for MMC state. + */ +class MafwMmcMonitor : public QObject +{ + Q_OBJECT + +public: + static const QString MMC_URI_PREFIX; + + MafwMmcMonitor(QObject* parent); + ~MafwMmcMonitor(); + bool isMounted(); + +Q_SIGNALS: + void preUnmount(); + +private: + static void unmountEvent(GVolumeMonitor* mon, + GMount* event, + gpointer userData); + static void mountEvent(GVolumeMonitor* mon, + GMount* event, + gpointer userData); + static bool isMyDocs(GMount* mount); + +private Q_SLOTS: + void preUnmountEvent(const QString &state); + +private: + GVolumeMonitor* m_gVolMonitor; + bool m_mounted; +}; + +#endif // MAFW_MMC_MONITOR_H diff --git a/qmafw-gst-subtitles-renderer/inc/mafw-gst-renderer-seeker.h b/qmafw-gst-subtitles-renderer/inc/mafw-gst-renderer-seeker.h new file mode 100644 index 0000000..9010111 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/inc/mafw-gst-renderer-seeker.h @@ -0,0 +1,43 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#ifndef MAFWGSTRENDERERSEEKER_H +#define MAFWGSTRENDERERSEEKER_H + +#include + +G_BEGIN_DECLS + +typedef struct _MafwGstRendererSeeker MafwGstRendererSeeker; + + +MafwGstRendererSeeker* mafw_gst_renderer_seeker_new(); +void mafw_gst_renderer_seeker_set_pipeline(MafwGstRendererSeeker *seeker, GstElement *pipeline); +gboolean mafw_gst_renderer_seeker_seek_to(MafwGstRendererSeeker *seeker, gint64 seek_pos); + +/* + * Processes possible seek results. Check if position has changed correctly or enough, if not + * executes new seek operation on the pipeline element. + * @return The new seek request position or -1 if no new seek request is necessary. + */ +gint64 mafw_gst_renderer_seeker_process(MafwGstRendererSeeker *seeker); +void mafw_gst_renderer_seeker_cancel(MafwGstRendererSeeker *seeker); +void mafw_gst_renderer_seeker_free(MafwGstRendererSeeker *seeker); + +G_END_DECLS + +#endif // MAFWGSTRENDERERSEEKER_H diff --git a/qmafw-gst-subtitles-renderer/inc/mafw-gst-renderer-utils.h b/qmafw-gst-subtitles-renderer/inc/mafw-gst-renderer-utils.h new file mode 100644 index 0000000..41e30eb --- /dev/null +++ b/qmafw-gst-subtitles-renderer/inc/mafw-gst-renderer-utils.h @@ -0,0 +1,38 @@ +/* + * This file is a part of MAFW + * + * Copyright (C) 2007, 2008, 2009 Nokia Corporation, all rights reserved. + * + * Contact: Visa Smolander + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ +#ifndef MAFW_GST_RENDERER_UTILS_H +#define MAFW_GST_RENDERER_UTILS_H + +G_BEGIN_DECLS +#include +#include +#include "mafw-gst-renderer-worker.h" + +gboolean convert_utf8(const gchar *src, gchar **dst); +gboolean uri_is_stream(const gchar *uri); +gint remap_gst_error_code(const GError *error); + +char *uri_get_subtitle_uri(const char *uri); + +G_END_DECLS +#endif diff --git a/qmafw-gst-subtitles-renderer/inc/mafw-gst-renderer-worker.h b/qmafw-gst-subtitles-renderer/inc/mafw-gst-renderer-worker.h new file mode 100644 index 0000000..592b67f --- /dev/null +++ b/qmafw-gst-subtitles-renderer/inc/mafw-gst-renderer-worker.h @@ -0,0 +1,382 @@ +/* + * This file is a part of MAFW + * + * Copyright (C) 2007, 2008, 2009 Nokia Corporation, all rights reserved. + * + * Contact: Visa Smolander + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ +#ifndef MAFW_GST_RENDERER_WORKER_H +#define MAFW_GST_RENDERER_WORKER_H + +#include +G_BEGIN_DECLS +#include +#include +#include + +#include "mafw-gst-renderer-seeker.h" + +#define MAFW_GST_RENDERER_WORKER_READY_TIMEOUT 3 +#define MAFW_GST_RENDERER_MAX_TMP_FILES 5 + +enum { + WORKER_ERROR_PLAYBACK, + WORKER_ERROR_VIDEO_CODEC_NOT_FOUND, + WORKER_ERROR_AUDIO_CODEC_NOT_FOUND, + WORKER_ERROR_CODEC_NOT_FOUND, + WORKER_ERROR_UNSUPPORTED_TYPE, + WORKER_ERROR_UNABLE_TO_PERFORM, + WORKER_ERROR_CANNOT_SET_POSITION, + WORKER_ERROR_PLAYLIST_PARSING, + WORKER_ERROR_DRM_NO_LICENSE, + WORKER_ERROR_DRM_NOT_ALLOWED, + WORKER_ERROR_DRM_CLOCK_NOT_SET, + WORKER_ERROR_DRM_OTHER, + WORKER_ERROR_STREAM_DISCONNECTED, + WORKER_ERROR_INVALID_URI, + WORKER_ERROR_MEDIA_NOT_FOUND, + WORKER_ERROR_CORRUPTED_FILE, + WORKER_ERROR_TYPE_NOT_AVAILABLE, + WORKER_ERROR_UNKOWN, + WORKER_ERROR_POSSIBLY_PLAYLIST_TYPE = 1000, +}; + +enum { + WORKER_METADATA_KEY_TITLE = 1, /* 1st must be non-zero! */ + WORKER_METADATA_KEY_ARTIST, + WORKER_METADATA_KEY_AUDIO_CODEC, + WORKER_METADATA_KEY_VIDEO_CODEC, + WORKER_METADATA_KEY_BITRATE, + WORKER_METADATA_KEY_ENCODING, + WORKER_METADATA_KEY_ALBUM, + WORKER_METADATA_KEY_GENRE, + WORKER_METADATA_KEY_TRACK, + WORKER_METADATA_KEY_ORGANIZATION, + WORKER_METADATA_KEY_RENDERER_ART_URI, + WORKER_METADATA_KEY_RES_X, + WORKER_METADATA_KEY_RES_Y, + WORKER_METADATA_KEY_VIDEO_FRAMERATE, + WORKER_METADATA_KEY_DURATION, + WORKER_METADATA_KEY_IS_SEEKABLE, + WORKER_METADATA_KEY_PAUSED_THUMBNAIL_URI, + WORKER_METADATA_KEY_URI +}; + +enum { + WORKER_PROPERTY_VOLUME, + WORKER_PROPERTY_MUTE, + WORKER_PROPERTY_AUTOPAINT, + WORKER_PROPERTY_COLORKEY, + WORKER_PROPERTY_XID, + WORKER_PROPERTY_RENDER_RECTANGLE, + WORKER_PROPERTY_CURRENT_FRAME_ON_PAUSE, + WORKER_PROPERTY_PLAYBACK_SPEED, + WORKER_PROPERTY_FORCE_ASPECT_RATIO +}; + +enum { + WORKER_OUTPUT_NULL, + WORKER_OUTPUT_BUILTIN_SPEAKERS, + WORKER_OUTPUT_FM_RADIO, + WORKER_OUTPUT_BLUETOOTH_AUDIO, + WORKER_OUTPUT_HEADPHONE_JACK, + WORKER_OUTPUT_BUILTIN_DISPLAY, + WORKER_OUTPUT_TVOUT +}; + +typedef struct _MafwGstRendererWorker MafwGstRendererWorker; + +typedef void (*MafwGstRendererWorkerNotifySeekCb)(MafwGstRendererWorker *worker, gpointer owner); +typedef void (*MafwGstRendererWorkerNotifyPauseCb)(MafwGstRendererWorker *worker, gpointer owner); +typedef void (*MafwGstRendererWorkerNotifyPlayCb)(MafwGstRendererWorker *worker, gpointer owner); +typedef void (*MafwGstRendererWorkerNotifyReadyStateCb)(MafwGstRendererWorker *worker, gpointer owner); +typedef void (*MafwGstRendererWorkerNotifyBufferStatusCb)(MafwGstRendererWorker *worker, gpointer owner, gdouble percent); +typedef void (*MafwGstRendererWorkerNotifyEOSCb)(MafwGstRendererWorker *worker, gpointer owner); +typedef void (*MafwGstRendererWorkerNotifyMetadataCb)(MafwGstRendererWorker *worker, gpointer owner, gint key, GType type, gpointer value); +typedef void (*MafwGstRendererWorkerNotifyErrorCb)(MafwGstRendererWorker *worker, gpointer owner, const GError *error); +typedef void (*MafwGstRendererWorkerNotifyPropertyCb)(MafwGstRendererWorker *worker, gpointer owner, gint id, GValue *value); +typedef void (*MafwGstRendererWorkerBlankingControlCb)(MafwGstRendererWorker *worker, gpointer owner, gboolean prohibit); +typedef void (*MafwGstRendererWorkerScreenshotCb)(MafwGstRendererWorker *worker, gpointer owner, GstBuffer *buffer, const char *filename, gboolean cancel); + +typedef enum { + SEEKABILITY_UNKNOWN = -1, + SEEKABILITY_NO_SEEKABLE, + SEEKABILITY_SEEKABLE, +} SeekabilityType; + +typedef enum { + DURATION_UNQUERIED = -2, + DURATION_INDEFINITE = -1 + /* other values are actual */ +} Duration; + +typedef struct { + gint x; + gint y; + gint width; + gint height; +} render_rectangle; + +/* This struct contains all configurable settings + update this and the conf file synchronously, plz + */ +typedef struct { + /* pipeline */ + gchar *asink; + gchar *vsink; + gint flags; + gint64 buffer_time; + gint64 latency_time; + gboolean autoload_subtitles; + gchar *subtitle_encoding; + gchar *subtitle_font; + + /* timers */ + guint milliseconds_to_pause_frame; + guint seconds_to_pause_to_ready; + + /* dhmmixer */ + gboolean use_dhmmixer; + struct { + guint state; + gint room; + gint color; + } mobile_surround_music; + struct { + guint state; + gint room; + gint color; + } mobile_surround_video; +} configuration; + +/* + * media: Information about currently selected media. + * location: Current media location + * length_nanos: Length of the media, in nanoseconds + * has_visual_content: the clip contains some visual content (video) + * video_width: If media contains video, this tells the video width + * video_height: If media contains video, this tells the video height + * seekable: Tells whether the media can be seeked + * par_n: Video pixel aspect ratio numerator + * par_d: Video pixel aspect ratio denominator + * owner: Owner of the worker; usually a MafwGstRenderer (FIXME USUALLY?) + * pipeline: Playback pipeline + * bus: Message bus + * state: Current playback pipeline state + * is_stream: Is currently playing media a stream + * muted: Is the audio muted + * eos: Has playback reached EOS already + * is_error: Has there been an error situation + * buffering: Indicates the buffering state + * prerolling: Indicates the prerolling state (NULL -> PAUSED) + * report_statechanges: Report state change bus messages + * current_volume: Current audio volume [0.0 .. 1.0], see playbin:volume + * async_bus_id: ID handle for GstBus + * buffer_probe_id: ID of the video renderer buffer probe + * seek_position: Indicates the pos where to seek, in seconds + * vsink: Video sink element of the pipeline + * asink: Audio sink element of the pipeline + * tsink: Text sink element of the pipeline + * xid: XID for video playback + * current_frame_on_pause: whether to emit current frame when pausing + */ +struct _MafwGstRendererWorker { + struct { + gchar *location; + gint64 length_nanos; + gboolean has_visual_content; + gint video_width; + gint video_height; + gdouble fps; + SeekabilityType seekable; + gint par_n; + gint par_d; + } media; + gpointer owner; + GstElement *pipeline; + + // Audio bin for Dolby Headphones Mobile plugin + GstElement *audiobin; + + GstBus *bus; + /* GStreamer state we are considering right now */ + GstState state; + gboolean is_stream; + gboolean muted; + /* we are handing eos or we did */ + gboolean eos; + /* if we are handling (or handled) and error */ + gboolean is_error; + /* pipeline is buffering */ + gboolean buffering; + /* pipeline is prerolling */ + gboolean prerolling; + /* stream is live and doesn't need prerolling */ + gboolean is_live; + /* if we have to stay in paused though a do_play was + * requested. Usually used when pausing in transitioning */ + gboolean stay_paused; + /* this variable should be FALSE while we are hiding state + * changed to the UI. This is that GStreamer can perform + * state_changes without us requiring it, for example, then + * seeking, buffering and so on and we have to hide those + * changes */ + gboolean report_statechanges; + guint async_bus_id; + gint seek_position; + guint ready_timeout; + guint duration_seek_timeout; + guint duration_seek_timeout_loop_count; + /* TRUE when a transition to GST_STATE_READY has been + * requested or we are actually in GST_STATE_READY (requested + * by us) */ + gboolean in_ready; + /* Flag indicating whether taking image from pause frame has succeed after pause. */ + gboolean pause_frame_taken; + guint pause_frame_timeout; + GstBuffer *pause_frame_buffer; + GstElement *vsink; + GstElement *asink; + GstElement *tsink; + + // Dolby Headphones Mobile mixer + GstElement *amixer; + + XID xid; + render_rectangle x_overlay_rectangle; + gboolean autopaint; + gfloat playback_speed; + gboolean force_aspect_ratio; + gint colorkey; + GPtrArray *tag_list; + GHashTable *current_metadata; + gpointer context_nowplaying; + + gboolean current_frame_on_pause; + gboolean taking_screenshot; + gchar *tmp_files_pool[MAFW_GST_RENDERER_MAX_TMP_FILES]; + guint8 tmp_files_pool_index; + + GSList *destinations; + + GstElement *queue; + + configuration *config; + + MafwGstRendererSeeker *seeker; + + /* Handlers for notifications */ + MafwGstRendererWorkerNotifySeekCb notify_seek_handler; + MafwGstRendererWorkerNotifyPauseCb notify_pause_handler; + MafwGstRendererWorkerNotifyPlayCb notify_play_handler; + MafwGstRendererWorkerNotifyBufferStatusCb notify_buffer_status_handler; + MafwGstRendererWorkerNotifyEOSCb notify_eos_handler; + MafwGstRendererWorkerNotifyReadyStateCb notify_ready_state_handler; + MafwGstRendererWorkerNotifyMetadataCb notify_metadata_handler; + MafwGstRendererWorkerNotifyErrorCb notify_error_handler; + MafwGstRendererWorkerNotifyPropertyCb notify_property_handler; + + MafwGstRendererWorkerBlankingControlCb blanking__control_handler; + MafwGstRendererWorkerScreenshotCb screenshot_handler; +}; + + +MafwGstRendererWorker *mafw_gst_renderer_worker_new(gpointer owner); + +void mafw_gst_renderer_worker_exit(MafwGstRendererWorker *worker); + +void mafw_gst_renderer_worker_set_current_frame_on_pause( + MafwGstRendererWorker *worker, + gboolean current_frame_on_pause); + +void mafw_gst_renderer_worker_set_ready_timeout(MafwGstRendererWorker *worker, + guint seconds); + +gboolean mafw_gst_renderer_worker_get_current_frame_on_pause( + MafwGstRendererWorker *worker); + +configuration* mafw_gst_renderer_worker_create_default_configuration(MafwGstRendererWorker *worker); +void mafw_gst_renderer_worker_set_configuration(MafwGstRendererWorker *worker, + configuration *config); + +void mafw_gst_renderer_worker_set_position(MafwGstRendererWorker *worker, + GstSeekType seek_type, + gint position, + GError **error); + +gint mafw_gst_renderer_worker_get_position(MafwGstRendererWorker *worker); + +gint64 mafw_gst_renderer_worker_get_last_known_duration(MafwGstRendererWorker *worker); +gint64 mafw_gst_renderer_worker_get_duration(MafwGstRendererWorker *worker); + +void mafw_gst_renderer_worker_set_xid(MafwGstRendererWorker *worker, XID xid); + +XID mafw_gst_renderer_worker_get_xid(MafwGstRendererWorker *worker); + +void mafw_gst_renderer_worker_set_render_rectangle(MafwGstRendererWorker *worker, render_rectangle *rect); +const render_rectangle* mafw_gst_renderer_worker_get_render_rectangle(MafwGstRendererWorker *worker); + +gboolean mafw_gst_renderer_worker_get_autopaint(MafwGstRendererWorker *worker); + +void mafw_gst_renderer_worker_set_autopaint(MafwGstRendererWorker *worker, + gboolean autopaint); + +gboolean mafw_gst_renderer_worker_set_playback_speed(MafwGstRendererWorker *worker, gfloat speed); + +gfloat mafw_gst_renderer_worker_get_playback_speed(MafwGstRendererWorker *worker); + +void mafw_gst_renderer_worker_set_force_aspect_ratio(MafwGstRendererWorker *worker, gboolean force); + +gboolean mafw_gst_renderer_worker_get_force_aspect_ratio(MafwGstRendererWorker *worker); + +gint mafw_gst_renderer_worker_get_colorkey(MafwGstRendererWorker *worker); + +gboolean mafw_gst_renderer_worker_get_seekable(MafwGstRendererWorker *worker); + +gboolean mafw_gst_renderer_worker_get_streaming(MafwGstRendererWorker *worker); + +const char* mafw_gst_renderer_worker_get_uri(MafwGstRendererWorker *worker); + +GHashTable *mafw_gst_renderer_worker_get_current_metadata( + MafwGstRendererWorker *worker); + +void mafw_gst_renderer_worker_play(MafwGstRendererWorker *worker, + const gchar *uri); + +void mafw_gst_renderer_worker_stop(MafwGstRendererWorker *worker); + +void mafw_gst_renderer_worker_pause(MafwGstRendererWorker *worker); + +void mafw_gst_renderer_worker_resume(MafwGstRendererWorker *worker); + +void mafw_gst_renderer_worker_pause_at(MafwGstRendererWorker *worker, guint position); + +guint check_dolby_audioroute(MafwGstRendererWorker *worker, guint prop); + +void set_dolby_music_property(MafwGstRendererWorker *worker, guint prop); +void set_dolby_music_sound_property(MafwGstRendererWorker *worker, gint prop, gboolean isRoomProperty); + +void set_dolby_video_property(MafwGstRendererWorker *worker, guint prop); +void set_dolby_video_sound_property(MafwGstRendererWorker *worker, gint prop, gboolean isRoomProperty); + +void mafw_gst_renderer_worker_notify_media_destination(MafwGstRendererWorker *worker, + GSList *destinations); + +G_END_DECLS +#endif diff --git a/qmafw-gst-subtitles-renderer/mafw-gst-renderer-plugin.conf b/qmafw-gst-subtitles-renderer/mafw-gst-renderer-plugin.conf new file mode 100644 index 0000000..2767d34 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/mafw-gst-renderer-plugin.conf @@ -0,0 +1,37 @@ +;Renderer instances +[renderers] +1\Id=mafw_gst_renderer +1\FriendlyName=MafwGstRenderer +2\Id=mafw_gst_internal_video_renderer +2\FriendlyName=MafwGstVideoRenderer +size=2 + +[in-process-renderers] +1\Id=mafw_in_process_gst_renderer +1\FriendlyName=MafwInProcessGstRenderer +size=1 + +;Renderer's configuration +[pipeline] +audio-sink=pulsesink +video-sink=omapxvsink +flags=71 +use_dhmmixer=1 +buffer-time=600000 +latency-time=300000 +autoload_subtitles=1 +subtitle_encoding=NULL +subtitle_font=Sans Bold 18 + +[timers] +pause-frame=700 +pause-to-ready=3 + +[dhmmixer] +;default values should be overwritten from gconf +dhm-music-surround=0 +dhm-music-color=2 +dhm-music-room-size=2 +dhm-video-surround=0 +dhm-video-color=2 +dhm-video-room-size=2 diff --git a/qmafw-gst-subtitles-renderer/qmafw-gst-subtitles-renderer.pro b/qmafw-gst-subtitles-renderer/qmafw-gst-subtitles-renderer.pro new file mode 100644 index 0000000..05636b4 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/qmafw-gst-subtitles-renderer.pro @@ -0,0 +1,99 @@ +TEMPLATE = lib +TARGET = qmafw-gst-renderer-plugin +VERSION = 0.0.55-1 + +QT = core network + +isEmpty(PREFIX) { + PREFIX=/usr +} + +isEmpty(MODE) { + CONFIG += release + QMAKE_CXXFLAGS += -g + QMAKE_CFLAGS += -g +} + +contains(MODE, release) { + CONFIG += release + DEFINES += G_DISABLE_ASSERT + QMAKE_CXXFLAGS += -g + QMAKE_CFLAGS += -g +} + +contains(MODE, debug) { + CONFIG += debug +} + +CONFIG += no_keywords qt qdbus link_pkgconfig plugin +PKGCONFIG += qmafw glib-2.0 gobject-2.0 gq-gconf gstreamer-0.10 gstreamer-plugins-base-0.10 QtSparql +PKGCONFIG += contextprovider-1.0 contextsubscriber-1.0 qmsystem2 usb_moded + +LIBS += -lgstinterfaces-0.10 -lgstpbutils-0.10 -ldbus-qeventloop -ltotem-plparser + +DEPENDPATH += . inc src +INCLUDEPATH += . inc $$system(pkg-config --variable=includedir qmafw) + +#DEFINES += QT_NO_DEBUG_OUTPUT + +QMAKE_CXXFLAGS += -Wall -Werror +QMAKE_CFLAGS += -Wall -Werror + +QMAKE_CLEAN += build-stamp \ + configure-stamp + +# Version info +DEFINES += _VERSION_INFO +QMAKE_EXTRA_TARGETS += revtarget +revtarget.target = inc/version.h +revtarget.commands += @echo update version info +revtarget.commands += $$escape_expand( \\n\\t )@echo \"namespace \{\" > $$revtarget.target +revtarget.commands += $$escape_expand( \\n\\t )@echo \"const char *revision = \\\"$(shell svnversion -n . || echo N/A )\\\";\" >> $$revtarget.target +revtarget.commands += $$escape_expand( \\n\\t )@echo \"const char *build_time = \\\"$(shell date )\\\";\" >> $$revtarget.target +revtarget.commands += $$escape_expand( \\n\\t )@echo \"\};\" >> $$revtarget.target +# update revision/buildtime every time when linking is required +QMAKE_POST_LINK += @rm -f $$revtarget.target + +# Input +HEADERS += MafwGstRenderer.h \ + MafwGstRendererPlugin.h \ + MafwBlankingPreventer.h \ + mafw-gst-renderer-utils.h \ + mafw-gst-renderer-worker.h \ + MafwGstRendererVolume.h \ + MafwGstRendererPlaylistFileUtility.h \ + MafwGstRendererNetworkMonitor.h \ + MafwGstRendererDolby.h \ + MafwGstScreenshot.h \ + MafwMmcMonitor.h \ + mafw-gst-renderer-seeker.h \ + MafwGstRendererHaltState.h + +SOURCES += MafwGstRenderer.cpp \ + MafwGstRendererPlugin.cpp \ + MafwBlankingPreventer.cpp \ + mafw-gst-renderer-utils.c \ + mafw-gst-renderer-worker.c \ + MafwGstRendererVolume.cpp \ + MafwGstRendererPlaylistFileUtility.cpp \ + MafwGstRendererNetworkMonitor.cpp \ + MafwGstRendererDolby.cpp \ + MafwGstScreenshot.cpp \ + MafwMmcMonitor.cpp \ + mafw-gst-renderer-seeker.c \ + MafwGstRendererHaltState.cpp + +DISTFILES += qmafw-gst-renderer.service + +# Installation +service.files = com.nokia.mafw.plugin.libqmafw_gst_renderer_plugin.service +service.path = $$PREFIX/share/dbus-1/services +target.path = $$PREFIX/lib/qmafw-plugin + +context.files = com.nokia.mafw.context_provider.libqmafw_gst_renderer.context +context.path = $$PREFIX/share/contextkit/providers + +settings.files = mafw-gst-renderer-plugin.conf +settings.path = /usr/share/qmafw + +INSTALLS += target service context settings diff --git a/qmafw-gst-subtitles-renderer/src/MafwBlankingPreventer.cpp b/qmafw-gst-subtitles-renderer/src/MafwBlankingPreventer.cpp new file mode 100644 index 0000000..e328255 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/src/MafwBlankingPreventer.cpp @@ -0,0 +1,51 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#include "MafwBlankingPreventer.h" +#include +#include + +/* Interval of recalling setBlankingPause in seconds */ +const int BLANKING_TIMER_INTERVAL=45; + +MafwBlankingPreventer::MafwBlankingPreventer(QObject* parent) : QObject(parent) +{ + connect( &m_refreshTimer, SIGNAL(timeout()), this, SLOT(refresh())); + m_refreshTimer.setInterval( BLANKING_TIMER_INTERVAL*1000 ); + m_display = new MeeGo::QmDisplayState(this); +} + +void MafwBlankingPreventer::blankingProhibit() +{ + qDebug() << "MafwBlankingPreventer::blankingProhibit"; + refresh(); + m_refreshTimer.start(); +} + +void MafwBlankingPreventer::blankingAllow() +{ + qDebug() << "MafwBlankingPreventer::blankingAllow"; + m_refreshTimer.stop(); + m_display->cancelBlankingPause(); +} + +void MafwBlankingPreventer::refresh() +{ + bool success = m_display->setBlankingPause(); + qDebug() << "MafwBlankingPreventer::refresh success" << success; +} + diff --git a/qmafw-gst-subtitles-renderer/src/MafwGstRenderer.cpp b/qmafw-gst-subtitles-renderer/src/MafwGstRenderer.cpp new file mode 100644 index 0000000..a595711 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/src/MafwGstRenderer.cpp @@ -0,0 +1,2437 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include "MafwGstRenderer.h" +#include "MafwBlankingPreventer.h" +#include "mafw-gst-renderer-worker.h" +#include "MafwGstRendererVolume.h" +#include "MafwGstRendererDolby.h" +#include "MafwGstRendererNetworkMonitor.h" +#include "MafwGstRendererHaltState.h" +#include "MafwGstRendererPlaylistFileUtility.h" +#include "MafwGstScreenshot.h" +#include "MafwMmcMonitor.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// milliseconds, stamp after playing this much +const int PLAYED_STAMP_INTERVAL = 5000; + +const int MAX_SUPPORTED_HEIGHT = 720; +const int MAX_SUPPORTED_WIDTH = 1280; +// how many time we retried to make played stamps +const int PLAYED_STAMP_TRIES = 3; + +static const int ISO_DATE_BASE_LENGTH = 19; // length of "CCYY-MM-DDThh:mm:ss" + +// property names +const QString PROPERTY_DOLBY_STATE_MUSIC = "mobile-surround-state-music"; +const QString PROPERTY_DOLBY_STATE_MUSIC_ROOM = "mobile-surround-state-music-room"; +const QString PROPERTY_DOLBY_STATE_MUSIC_COLOR = "mobile-surround-state-music-color"; +const QString PROPERTY_DOLBY_STATE_VIDEO = "mobile-surround-state-video"; +const QString PROPERTY_DOLBY_STATE_VIDEO_ROOM = "mobile-surround-state-video-room"; +const QString PROPERTY_DOLBY_STATE_VIDEO_COLOR = "mobile-surround-state-video-color"; +const QString PROPERTY_VOLUME = "volume"; +const QString PROPERTY_AUTOPAINT = "autopaint"; +const QString PROPERTY_COLORKEY = "colorkey"; +const QString PROPERTY_XID = "xid"; +const QString PROPERTY_RENDER_RECT = "render-rectangle"; +const QString PROPERTY_CURRENT_FRAME_ON_PAUSE = "current-frame-on-pause"; +const QString PROPERTY_PLAYBACK_SPEED = "playback-speed"; +const QString PROPERTY_FORCE_ASPECT_RATIO = "force-aspect-ratio"; + +// audio/video destinations +const QString CONTEXT_FW_PROPERTY_AUDIO_ROUTE = "/com/nokia/policy/audio_route"; +const QString CONTEXT_FW_PROPERTY_VIDEO_ROUTE = "/com/nokia/policy/video_route"; +const QString AUDIO_ROUTE_NULL = "null"; +const QString AUDIO_ROUTE_IHF = "ihf"; +const QString AUDIO_ROUTE_FMRADIO = "fmtx"; +const QString AUDIO_ROUTE_IHF_AND_FMRADIO = "ihfandfmtx"; +const QString AUDIO_ROUTE_EARPIECE = "earpiece"; +const QString AUDIO_ROUTE_EARPIECE_AND_TVOUT = "earpieceandtvout"; +const QString AUDIO_ROUTE_TV_OUT = "tvout"; +const QString AUDIO_ROUTE_IHF_AND_TV_OUT = "ihfandtvout"; +const QString AUDIO_ROUTE_HEADPHONE = "headphone"; +const QString AUDIO_ROUTE_HEADSET = "headset"; +const QString AUDIO_ROUTE_BTHSP = "bthsp"; +const QString AUDIO_ROUTE_BTA2DP = "bta2dp"; +const QString AUDIO_ROUTE_IHF_AND_HEADSET = "ihfandheadset"; +const QString AUDIO_ROUTE_IHF_AND_HEADPHONE = "ihfandheadphone"; +const QString AUDIO_ROUTE_IHF_AND_BTHSP = "ihfandbthsp"; +const QString AUDIO_ROUTE_TV_OUT_AND_BTHSP = "tvoutandbthsp"; +const QString AUDIO_ROUTE_TV_OUT_AND_BTA2DP = "tvoutandbta2dp"; +const QString VIDEO_ROUTE_TV_OUT = "tvout"; +const QString VIDEO_ROUTE_BUILT_IN = "builtin"; +const QString VIDEO_ROUTE_BUILT_IN_AND_TV_OUT = "builtinandtvout"; + +const QString DBUS_INTERFACE_DBUS="org.freedesktop.DBus"; +const QString DBUS_SIGNAL_NAME_OWNER_CHANGED="NameOwnerChanged"; +const QString DBUS_NAME_PCFD = "com.nokia.policy.pcfd"; + +/******************************************************************** + * MafwGstRenderer::MafwGstRenderer + ********************************************************************/ +MafwGstRenderer::MafwGstRenderer(const QString& uuid, + const QString& pluginName, + const QString& name, + QObject *parent) + : + MafwBasicRenderer(uuid, pluginName, name, parent), + m_initialized(false), + m_currentState(MafwRenderer::Invalid), + m_nextContent(""), + m_currentContent(""), + m_playingItem(MafwBasicRenderer::UnknownUri), + m_blankingPreventer(0), + m_screenshot(0), + m_networkMonitor(new MafwGstRendererNetworkMonitor()), + m_volume(0), + m_playedStamped(false), + m_playedStampTryCounter(0), + m_sparqlConnection(new QSparqlConnection("QTRACKER", QSparqlConnectionOptions(), this)), + m_urnQueryResult(0), + m_stampItResult(0), + m_playlistFileUtil(0), + m_playingPlaylistFile(false), + m_unsupportedTypeError(0), + m_playedPlaylistItem(false), + m_mmcMonitor(0) +{ + qDebug() << __PRETTY_FUNCTION__; + + m_dolby = new MafwGstRendererDolby(this); + connect(m_dolby, SIGNAL(mafwDHMMusicPropertyChanged()), + this, SLOT(handleDHMMusicPropertyChanged())); + connect(m_dolby, SIGNAL(mafwDHMVideoPropertyChanged()), + this, SLOT(handleDHMVideoPropertyChanged())); + + m_worker = 0; + m_videoRoute = 0; + m_audioRoute = 0; + gst_init(0, 0); + + /* make this connection a queued connection to postpone results delivery, + * so that results callback is not called already in the function that + * initiates this procedure */ + QObject::connect(this, SIGNAL(signalGetPosition(QObject*, + const char*)), + this, SLOT(slotGetPosition(QObject*, + const char*)), + Qt::QueuedConnection); + + /* make this connection a queued connection to postpone results delivery, + * so that results callback is not called already in the function that + * initiates this procedure */ + QObject::connect(this, SIGNAL(signalMafwProperty(QString, + QObject*, + const char*)), + this, SLOT(slotMafwProperty(QString, + QObject*, + const char*)), + Qt::QueuedConnection); + + /* make this connection a queued connection to postpone results delivery, + * so that results callback is not called already in the function that + * initiates this procedure */ + QObject::connect(this, SIGNAL(signalGetCurrentMediaInfo(QObject*, + const char*, + const QString)), + this, SLOT(slotGetCurrentMediaInfo(QObject*, + const char*, + const QString)), + Qt::QueuedConnection); + + m_playedStampTimer.setSingleShot(true); + connect(&m_playedStampTimer,SIGNAL(timeout()),this,SLOT(slotStamp())); + + m_videoRoute = new ContextProperty(CONTEXT_FW_PROPERTY_VIDEO_ROUTE); + m_audioRoute = new ContextProperty(CONTEXT_FW_PROPERTY_AUDIO_ROUTE); + + connectNameOwnerChanged(); + + m_playlistNextTimer.setSingleShot(true); + connect(&m_playlistNextTimer, SIGNAL(timeout()), + this, SLOT(playNextURIFromPlaylist())); + + /* connection is to track when policy is on/off */ + connect(this, SIGNAL(mafwPropertyChanged(const QString, const QVariant)), + this, SLOT(handlePropertyChanged(const QString&, const QVariant&))); + + /* Connection to handle online status message if necessary */ + connect(m_networkMonitor, SIGNAL(prepareNetworkChange()), + this, SLOT(haltStreaming())); + connect(m_networkMonitor, SIGNAL(networkChangeFinished()), + this, SLOT(continueStreaming())); + + connect(&m_haltState, SIGNAL(decayed()), + this, SLOT(stopStreaming())); +} + +/******************************************************************** + * MafwGstRenderer::~MafwGstRenderer + ********************************************************************/ +MafwGstRenderer::~MafwGstRenderer() +{ + + qDebug() << __PRETTY_FUNCTION__; + delete m_volume; + // this releases the resources allocated by the worker, do this before + // releasing anything else so any callbacks from worker won't be called + mafw_gst_renderer_worker_exit(m_worker); + + delete m_videoRoute; + delete m_audioRoute; + delete m_networkMonitor; + delete m_screenshot; + delete m_urnQueryResult; + delete m_stampItResult; + delete m_sparqlConnection; + + g_free(m_worker); + + if( m_unsupportedTypeError ) + { + g_error_free(m_unsupportedTypeError); + } +} + + +/******************************************************************** + * MafwGstRenderer::initialize + ********************************************************************/ +bool MafwGstRenderer::initialize(QSettings *settings) +{ + + qDebug() << __PRETTY_FUNCTION__; + + //if already initialized do nothing + if (m_initialized) + { + return m_initialized; + } + + m_initialized = MafwBasicRenderer::initialize(); + + if (m_initialized) + { + // fail to apply default policy is not considered fatal for now + if (MafwBasicRenderer::setDefaultRendererPolicy(MafwRendererPolicy::MediaPlayer)) + { + MafwRendererPolicy *policy = rendererPolicy(); + Q_ASSERT(policy); + policy->setDefaultResources(MafwRendererPolicy::Audio); + } + else + { + qWarning() << "Setting default policy failed, continuing"; + } + + m_blankingPreventer = new MafwBlankingPreventer(this); + + m_screenshot = new MafwGstScreenshot(this); + + connect(m_screenshot, SIGNAL(screenshotTaken(char*,GError*)), + this, SLOT(handleScreenshot(char*,GError*))); + connect(m_screenshot, SIGNAL(screenshotCancelled()), + this, SLOT(cancelScreenshot())); + + m_worker = mafw_gst_renderer_worker_new(this); + m_worker->notify_play_handler = &playCallback; + m_worker->notify_pause_handler = &pauseCallback; + m_worker->notify_error_handler = &errorCallback; + m_worker->notify_eos_handler = &eosCallback; + m_worker->notify_ready_state_handler = &readyStateCallback; + m_worker->notify_metadata_handler = &metadataCallback; + m_worker->notify_property_handler = &propertyCallback; + m_worker->notify_buffer_status_handler = &bufferStatusCallback; + m_worker->blanking__control_handler = &blankingControlCallback; + m_worker->screenshot_handler = &screenshotCallback; + + setConfiguration(settings); + + // Initialize Dolby support + m_dolby->initialize(); + + m_mmcMonitor = new MafwMmcMonitor(this); + connect( m_mmcMonitor, SIGNAL( preUnmount() ), this, SLOT( mmcPreUnmount() ) ); + + // connect the audio routes AND check the current values of routes. + connect( m_videoRoute, SIGNAL( valueChanged() ), this, SLOT( slotRouteChanged() ) ); + connect( m_audioRoute, SIGNAL( valueChanged() ), this, SLOT( slotRouteChanged() ) ); + slotRouteChanged(); + } + return m_initialized; +} + +/******************************************************************** + * MafwGstRenderer::playCallback + ********************************************************************/ +void MafwGstRenderer::playCallback(MafwGstRendererWorker *worker, + gpointer owner) +{ + Q_UNUSED(worker); + + qDebug() << __PRETTY_FUNCTION__; + MafwGstRenderer* self = static_cast(owner); + + if( self->m_currentState == MafwRenderer::Paused ) + { + Q_EMIT self->rendererResumed(); + } + else + { + if( self->m_playingPlaylistFile ) + { + if( !self->m_playedPlaylistItem ) + { + qDebug() << "Emitting playing item event"; + Q_EMIT self->rendererPlaying(static_cast(self->m_playingItem)); + self->m_playedPlaylistItem = true; + } + } + else + { + Q_EMIT self->rendererPlaying(static_cast(self->m_playingItem)); + } + } + + if( mafw_gst_renderer_worker_get_position(worker)==0 ) + { + self->m_playedStamped = false; + self->m_playedStampTryCounter = 0; + } + + if( !self->m_playedStamped ) + { + const QUrl url = self->m_currentContent.firstMetaData(MAFW_METADATA_KEY_URI).toUrl(); + if (url.scheme() == "file") + { + qDebug() << __PRETTY_FUNCTION__ << "starting play stamp timer."; + self->m_playedStampTimer.start(PLAYED_STAMP_INTERVAL); + } + } + + self->m_currentState = MafwRenderer::Playing; +} + +/******************************************************************** + * MafwGstRenderer::bufferStatusCallback + ********************************************************************/ +void MafwGstRenderer::bufferStatusCallback(MafwGstRendererWorker *worker, + gpointer owner, + gdouble percent) +{ + + Q_UNUSED(worker); + + qDebug() << __PRETTY_FUNCTION__; + MafwGstRenderer* self = static_cast(owner); + + Q_EMIT self->bufferingInfo(static_cast(percent)); + +} +/******************************************************************** + * MafwGstRenderer::constructMafwError + ********************************************************************/ +MafwError MafwGstRenderer::constructMafwError(const GError* error) +{ + MafwError mafwError; + guint32 code = static_cast(error->code); + + //for streams the media not found is other than the default + if( code == WORKER_ERROR_MEDIA_NOT_FOUND && mafw_gst_renderer_worker_get_streaming(m_worker) ) + { + mafwError.setCode(MafwError::RendererError_URINotAvailable); + } + else if(code == WORKER_ERROR_UNSUPPORTED_TYPE) + { + handleResolutionError(mafwError); + } + else if (errorMap().contains(code)) + { + mafwError.setCode(errorMap().value(code)); + } + else + { + mafwError.setCode(MafwError::NothingButErrors); + } + + mafwError.setMessage(error->message); + return mafwError; +} + +/******************************************************************** + * MafwGstRenderer::errorCallback + ********************************************************************/ +void MafwGstRenderer::errorCallback(MafwGstRendererWorker *worker, + gpointer owner, + const GError *error) +{ + Q_UNUSED(worker); + qWarning() << __PRETTY_FUNCTION__ << error->message; + MafwError mafwError; + guint32 code; + MafwGstRenderer* self = static_cast(owner); + + code = static_cast(error->code); + + //The content might be a playlist file which was tried to play without + //mime type. This case can be detected by trying to play it as playlist + //file. If that was not the case same error will be signalled via + //MafwGstRenderer::handlePlaylistFileParsingErrors + if (!self->m_playingPlaylistFile && + !self->m_unsupportedTypeError && + code == WORKER_ERROR_POSSIBLY_PLAYLIST_TYPE) + { + QMap mime; + mime[MAFW_METADATA_KEY_MIME] = "audio/x-scpls"; + self->m_currentContent.setMetaData(mime); + self->doPlay(self->m_currentContent); + self->m_unsupportedTypeError = g_error_copy(error); + qWarning() << __PRETTY_FUNCTION__ << "Probably we were trying to play playlist file without mime type. If that's the case use bool play(url, 'audio/x-scpls')."; + qWarning() << __PRETTY_FUNCTION__ << "Trying to play as playlist file now..."; + return; + } + mafwError = self->constructMafwError(error); + + /* We release resources when we got error that causes stop. + * WORKER_ERROR_CANNOT_SET_POSITION and WORKER_ERROR_DRM_NOT_ALLOWED error don't cause stop. + */ + if((code != WORKER_ERROR_CANNOT_SET_POSITION + && code != WORKER_ERROR_DRM_NOT_ALLOWED) + && !self->m_playingPlaylistFile) + { + Q_EMIT self->rendererError(mafwError); + MafwRendererPolicy *policy = self->rendererPolicy(); + Q_ASSERT(policy); + if( policy ) + { + policy->release(); + qDebug() << __PRETTY_FUNCTION__ << "Resources released because of error" << mafwError.code(); + } + else + { + qWarning() << __PRETTY_FUNCTION__ << "No policy exists!"; + } + + self->doStop(); + } + else if (code != WORKER_ERROR_CANNOT_SET_POSITION && code != WORKER_ERROR_DRM_NOT_ALLOWED) //Try next uri + { + //using singleshot gives worker/gstreamer time to do + //cleanup before calling worker_play + if (self->m_playlistFileUtil->getUriList().isEmpty()) + { + //delayed call to playNextURIFromPlaylist used to give the parser + //enough time to read new items from the playlist + self->m_playlistFileUtil->setPendingError(mafwError); + + self->m_playlistNextTimer.start(1000); + } + else + { + self->m_playlistNextTimer.start(0); + } + } + else + { + Q_EMIT self->rendererError(mafwError); + } +} + +/******************************************************************** + * MafwGstRenderer::propertyCallback + ********************************************************************/ +void MafwGstRenderer::propertyCallback(MafwGstRendererWorker *worker, + gpointer owner, + gint id, + GValue *value) +{ + + QString name; + + Q_UNUSED(worker); + + MafwGstRenderer* self = static_cast(owner); + + switch (id) + { + case WORKER_PROPERTY_AUTOPAINT: + name = PROPERTY_AUTOPAINT; + break; + case WORKER_PROPERTY_COLORKEY: + name = PROPERTY_COLORKEY; + break; + case WORKER_PROPERTY_XID: + name = PROPERTY_XID; + break; + case WORKER_PROPERTY_CURRENT_FRAME_ON_PAUSE: + name = PROPERTY_CURRENT_FRAME_ON_PAUSE; + break; + case WORKER_PROPERTY_PLAYBACK_SPEED: + name = PROPERTY_PLAYBACK_SPEED; + break; + case WORKER_PROPERTY_FORCE_ASPECT_RATIO: + name = PROPERTY_FORCE_ASPECT_RATIO; + break; + case WORKER_PROPERTY_RENDER_RECTANGLE: + name = PROPERTY_RENDER_RECT; + break; + default: + qWarning() << __PRETTY_FUNCTION__ << "unknown property id:" << id; + return; + break; + } + + qDebug() << __PRETTY_FUNCTION__ << name; + + QVariant result = getValue(value); + + if (result.isValid()) + { + Q_EMIT self->mafwPropertyChanged(name, result); + } + +} + +/******************************************************************** + * MafwGstRenderer::blankingControlCallback + ********************************************************************/ +void MafwGstRenderer::blankingControlCallback(MafwGstRendererWorker *worker, + gpointer owner, gboolean prohibit) +{ + + Q_UNUSED(worker); + qDebug() << __PRETTY_FUNCTION__ << prohibit; + MafwGstRenderer* self = static_cast(owner); + if(self->m_videoRoute->value() == VIDEO_ROUTE_TV_OUT || + self->m_videoRoute->value() == VIDEO_ROUTE_BUILT_IN_AND_TV_OUT) + { + prohibit = false; + } + + if( prohibit ) + { + self->m_blankingPreventer->blankingProhibit(); + } + else + { + self->m_blankingPreventer->blankingAllow(); + } +} + +/******************************************************************** + * MafwGstRenderer::screenshotCallback + ********************************************************************/ +void MafwGstRenderer::screenshotCallback(MafwGstRendererWorker *worker, + gpointer owner, GstBuffer *buffer, + const char *filename, gboolean cancel) +{ + qDebug() << __PRETTY_FUNCTION__; + MafwGstRenderer *self = static_cast(owner); + + if(cancel) + { + self->m_screenshot->cancelPauseFrame(); + } + else + { + if(!self->m_screenshot->savePauseFrame(buffer, filename)) + { + worker->taking_screenshot = FALSE; + qCritical() << "Failed to create pause frame pipeline"; + } + } +} + +/******************************************************************** + * MafwGstRenderer::getValue + ********************************************************************/ +QVariant MafwGstRenderer::getValue(const GValue *v) +{ + + QVariant result; + + if (G_IS_VALUE(v)) + { + if (G_VALUE_TYPE(v) == G_TYPE_STRING) + { + // tags from GStreamer are always expected to be UTF-8 + result = QVariant(QString::fromUtf8(g_value_get_string(v))); + } + else if (G_VALUE_TYPE(v) == G_TYPE_UINT) + { + result = QVariant(g_value_get_uint(v)); + } + else if (G_VALUE_TYPE(v) == G_TYPE_INT) + { + result = QVariant(g_value_get_int(v)); + } + else if (G_VALUE_TYPE(v) == G_TYPE_BOOLEAN) + { + result = QVariant::fromValue(g_value_get_boolean(v)); + } + else if (G_VALUE_TYPE(v) == G_TYPE_DOUBLE) + { + result = QVariant(g_value_get_double(v)); + } + else if (G_VALUE_TYPE(v) == G_TYPE_INT64) + { + result = QVariant(g_value_get_int64(v)); + } + else if (G_VALUE_TYPE(v) == G_TYPE_FLOAT) + { + result = QVariant(g_value_get_float(v)); + } + else if (G_VALUE_TYPE(v) == G_TYPE_VALUE_ARRAY) + { + GValueArray *vals = static_cast(g_value_get_boxed(v)); + if( vals->n_values == 4 ) + { + result = QString("%1,%2,%3,%4") + .arg(g_value_get_int(g_value_array_get_nth(vals, 0))) + .arg(g_value_get_int(g_value_array_get_nth(vals, 1))) + .arg(g_value_get_int(g_value_array_get_nth(vals, 2))) + .arg(g_value_get_int(g_value_array_get_nth(vals, 3))); + } + else + { + qWarning() << "Invalid rect values received? Size:" << vals->n_values; + } + + } + else + { + qWarning() << "unsupported value g_type"; + } + } + + return result; + +} + +/******************************************************************** + * MafwGstRenderer::metadataCallback + ********************************************************************/ +void MafwGstRenderer::metadataCallback(MafwGstRendererWorker *worker, + gpointer owner, + gint key, + GType type, + gpointer value) +{ + + QList results; + + Q_UNUSED(worker); + + qDebug() << __PRETTY_FUNCTION__ << key << metadataMap().value(key); + + MafwGstRenderer* self = static_cast(owner); + + if (metadataMap().contains(key)) + { + if (type == G_TYPE_VALUE_ARRAY) + { + uint i; + GValueArray *vals = static_cast(value); + for (i = 0; i < vals->n_values; i++) + { + QVariant v = getValue(g_value_array_get_nth(vals, i)); + if (v.isValid()) + { + results << v; + } + } + + QString mafwMetadataKey = metadataMap().value(key); + + self->appendRelatedMetadata(mafwMetadataKey, &results); + + Q_EMIT self->metadataChanged(mafwMetadataKey, results); + self->m_currentMetaData.insert(mafwMetadataKey, results); + } + else + { + qWarning() << "unsupported g_type"; + } + } + else + { + qWarning() << "unknown metadata key:" << key; + } +} + +/******************************************************************** + * MafwGstRenderer::appendRelatedMetadata + ********************************************************************/ +void MafwGstRenderer::appendRelatedMetadata(const QString key, QList* results) +{ + if(key == MAFW_METADATA_KEY_PAUSED_THUMBNAIL_URI) + { + gint position = mafw_gst_renderer_worker_get_position(m_worker); + if(position < 0) + { + position = 0; + } + + QUrl uri = m_currentContent.firstMetaData(MAFW_METADATA_KEY_URI).toUrl(); + *results << uri.toEncoded().constData(); + *results << QVariant(position); + } +} + +// +// QMafw renderer interface implementation +// + +/******************************************************************** + * MafwGstRenderer::doPlay + ********************************************************************/ +void MafwGstRenderer::doPlay(const MafwContent& content) +{ + Q_ASSERT_X(false, "MafwGstRenderer", "Wrong play function called!"); + Q_UNUSED(content); +} + +/******************************************************************** + * MafwGstRenderer::doPlay + ********************************************************************/ +void MafwGstRenderer::doPlay(const MafwMediaInfo& mediaInfo) +{ + //Preserve m_currentContent for keeping usage count up if the same item is + //played again. + if(mediaInfo.uuid().isEmpty() || + mediaInfo.uuid() != m_currentContent.uuid()) + { + m_currentContent = mediaInfo; + } + m_playingItem = MafwBasicRenderer::CurrentUri; + m_currentMetaData.clear(); + + const QUrl url = mediaInfo.firstMetaData(MAFW_METADATA_KEY_URI).toUrl(); + qDebug() << __PRETTY_FUNCTION__ << url.toEncoded(); + + m_haltState.clear(); + + if( !m_mmcMonitor->isMounted() && url.toString().startsWith( MafwMmcMonitor::MMC_URI_PREFIX ) ) + { + qDebug() << "MafwGstRenderer::doPlay: Can't play MMC not mounted"; + MafwError mafwError(MafwError::RendererError_MmcNotAvailable, url.toEncoded()); + Q_EMIT rendererError(mafwError); + return; + } + + m_playedPlaylistItem = false; + m_playingPlaylistFile = false; + if (m_unsupportedTypeError) + { + g_error_free(m_unsupportedTypeError); + m_unsupportedTypeError = 0; + } + + if( url.isValid() ) + { + stopTimers(); + + // Set correct value for the Dolby Headphones Mobile effect plugin + set_dolby_music_property(m_worker, m_dolby->getMusicDolbyState()); + set_dolby_music_sound_property(m_worker, m_dolby->getMusicDolbyRoom(), TRUE); + set_dolby_music_sound_property(m_worker, m_dolby->getMusicDolbyColor(), FALSE); + set_dolby_video_property(m_worker, m_dolby->getVideoDolbyState()); + set_dolby_video_sound_property(m_worker, m_dolby->getVideoDolbyRoom(), TRUE); + set_dolby_video_sound_property(m_worker, m_dolby->getVideoDolbyColor(), FALSE); + + const QString& mimeType = mediaInfo.firstMetaData(MAFW_METADATA_KEY_MIME).toString(); + if (mimeType == "audio/x-scpls" ) + { + if (!m_playlistFileUtil) + { + m_playlistFileUtil = new MafwGstRendererPlaylistFileUtility(this); + connect(m_playlistFileUtil, SIGNAL(firstItemParsed()), + this, SLOT(startPlayingPlaylistFile()), Qt::QueuedConnection); + connect(m_playlistFileUtil, SIGNAL(parsingReady(bool)), + this, SLOT(handlePlaylistFileParsingErrors(bool)), Qt::QueuedConnection); + } + m_playlistFileUtil->parsePlaylistFile(url); + + } + else + { + playURI(url.toEncoded()); + + QVariant startPosition = mediaInfo.firstMetaData(MAFW_METADATA_KEY_START_POSITION); + if( startPosition.isValid() ) + { + uint pos = startPosition.toUInt(); + qDebug() << "Immediate seek requested to: " << pos; + doSeek(pos, MafwRenderer::SeekAbsolute); + } + else + { + QVariant pausePosition = mediaInfo.firstMetaData(MAFW_METADATA_KEY_PAUSED_POSITION); + if( pausePosition.isValid() ) + { + uint position = pausePosition.toUInt(); + qDebug() << "Immediate pause requested at:" << position; + mafw_gst_renderer_worker_pause_at(m_worker, position); + } + } + } + } + else + { + MafwError mafwError(MafwError::RendererError_InvalidURI, url.toString()); + Q_EMIT rendererError(mafwError); + doStop(); + } +} + +/******************************************************************** + * MafwGstRenderer::doStop + ********************************************************************/ +void MafwGstRenderer::doStop() +{ + qDebug() << __PRETTY_FUNCTION__; + + mafw_gst_renderer_worker_stop(m_worker); + m_currentState = MafwRenderer::Stopped; + m_playingItem = MafwBasicRenderer::UnknownUri; + + m_haltState.clear(); + + stopTimers(); + Q_EMIT rendererStopped(); +} + +/******************************************************************** + * MafwGstRenderer::doPause + ********************************************************************/ +void MafwGstRenderer::doPause() +{ + qDebug() << __PRETTY_FUNCTION__; + + if( m_haltState.isSet() && m_haltState.state() == MafwRenderer::Playing ) + { + m_haltState.setState(MafwRenderer::Paused); + m_currentState = MafwRenderer::Paused; + Q_EMIT rendererPaused(); + } + else + { + mafw_gst_renderer_worker_pause(m_worker); + } +} + +/******************************************************************** + * MafwGstRenderer::doResume + ********************************************************************/ +void MafwGstRenderer::doResume() +{ + qDebug() << __PRETTY_FUNCTION__; + + if( m_currentState == MafwRenderer::Paused && m_haltState.isSet() && m_haltState.state() == MafwRenderer::Paused ) + { + mafw_gst_renderer_worker_play(m_worker, m_haltState.uri().toAscii().constData()); + m_currentState = MafwRenderer::Paused; + if( m_haltState.position() > 0 ) + { + doSeek(m_haltState.position(), MafwRenderer::SeekAbsolute); + } + } + else + { + mafw_gst_renderer_worker_resume(m_worker); + } + + if( m_haltState.isSet() ) + { + m_haltState.clear(); + } +} + +/******************************************************************** + * MafwGstRenderer::doSeek + ********************************************************************/ +void MafwGstRenderer::doSeek(int position, MafwRenderer::SeekMode seekMode) +{ + GError *error = 0; + + qDebug() << __PRETTY_FUNCTION__; + + GstSeekType seekType; + if( MafwRenderer::SeekAbsolute == seekMode ) + { + seekType = GST_SEEK_TYPE_SET; + } + else if( MafwRenderer::SeekRelative == seekMode ) + { + seekType = GST_SEEK_TYPE_CUR; + } + else + { + qCritical("MafwGstRenderer: Invalid seek operation requested!"); + return; + } + + mafw_gst_renderer_worker_set_position(m_worker, + seekType, + position, + &error); + + if (error) + { + MafwError mafwError; + mafwError.setCode(MafwError::RendererError_CannotSetPosition); + mafwError.setMessage(error->message); + Q_EMIT rendererError(mafwError); + g_error_free(error); + } + +} + +/******************************************************************** + * MafwGstRenderer::doNextHint + ********************************************************************/ +bool MafwGstRenderer::doNextHint(const MafwContent& content) +{ + Q_ASSERT_X(false, "MafwGstRenderer", "Wrong play function called!"); + Q_UNUSED(content); + return false; +} + +/******************************************************************** + * MafwGstRenderer::doNextHint + ********************************************************************/ +bool MafwGstRenderer::doNextHint(const MafwMediaInfo& mediaInfo) +{ + qDebug() << __PRETTY_FUNCTION__; + + m_nextContent = mediaInfo; + // If we have already reached EOS trigger a new play attempt because the + // next content was signalled too late. However, if we have gone from playing + // state we can not continue, because we have released resources. + if (m_worker->eos && (m_currentState == MafwRenderer::Playing)) + { + QTimer::singleShot(0, this, SLOT(playNext())); + } + return true; +} + +/******************************************************************** + * MafwGstRenderer::getPosition + ********************************************************************/ +bool MafwGstRenderer::getPosition(QObject* resultsReceiver, + const char* resultsMember) +{ + + Q_EMIT signalGetPosition(resultsReceiver, + resultsMember); + + return true; + +} + +/******************************************************************** + * MafwGstRenderer::setMafwProperty + ********************************************************************/ +bool MafwGstRenderer::setMafwProperty(const QString& name, + const QVariant& value) +{ + qDebug() << __PRETTY_FUNCTION__ << name; + + bool success = true; + if (name == PROPERTY_VOLUME) + { + if (!m_volume) + { + m_volume = new MafwGstRendererVolume(); + connect(m_volume, SIGNAL(volumeChanged(uint)), this, SLOT(handleVolumeChange(uint))); + } + success = m_volume->setVolume(value.toUInt()); + } + else if (name == PROPERTY_DOLBY_STATE_MUSIC) + { + success = m_dolby->setMusicDolbyState(value.toUInt()); + if (success) + { + set_dolby_music_property(m_worker, m_dolby->getMusicDolbyState()); + } + } + else if (name == PROPERTY_DOLBY_STATE_MUSIC_ROOM) + { + success = m_dolby->setMusicDolbyState(value.toInt()); + if (success) + { + set_dolby_music_sound_property(m_worker, m_dolby->getMusicDolbyRoom(), TRUE); + } + } + else if (name == PROPERTY_DOLBY_STATE_MUSIC_COLOR) + { + success = m_dolby->setMusicDolbyState(value.toInt()); + if (success) + { + set_dolby_music_sound_property(m_worker, m_dolby->getMusicDolbyColor(), FALSE); + } + } + else if (name == PROPERTY_DOLBY_STATE_VIDEO) + { + success = m_dolby->setVideoDolbyState(value.toUInt()); + if (success) + { + set_dolby_video_property(m_worker, m_dolby->getVideoDolbyState()); + } + } + else if (name == PROPERTY_DOLBY_STATE_VIDEO_ROOM) + { + success = m_dolby->setVideoDolbyState(value.toInt()); + if (success) + { + set_dolby_video_sound_property(m_worker, m_dolby->getVideoDolbyRoom(), TRUE); + } + } + else if (name == PROPERTY_DOLBY_STATE_VIDEO_COLOR) + { + success = m_dolby->setVideoDolbyState(value.toInt()); + if (success) + { + set_dolby_video_sound_property(m_worker, m_dolby->getVideoDolbyColor(), FALSE); + } + } + else if (name == PROPERTY_AUTOPAINT) + { + mafw_gst_renderer_worker_set_autopaint(m_worker, value.toBool()); + } + else if (name == PROPERTY_XID) + { + if (rendererPolicy()) + { + rendererPolicy()->setDefaultResources(MafwRendererPolicy::Audio | MafwRendererPolicy::Video); + } + else + { + qCritical() << __PRETTY_FUNCTION__ << "unable to append video to default resources"; + } + + mafw_gst_renderer_worker_set_xid(m_worker, value.toUInt()); + } + else if (name == PROPERTY_CURRENT_FRAME_ON_PAUSE) + { + mafw_gst_renderer_worker_set_current_frame_on_pause(m_worker, + value.toBool()); + } + else if (name == PROPERTY_PLAYBACK_SPEED) + { + success = mafw_gst_renderer_worker_set_playback_speed(m_worker, value.toFloat()); + } + else if (name == PROPERTY_FORCE_ASPECT_RATIO) + { + mafw_gst_renderer_worker_set_force_aspect_ratio(m_worker, value.toBool()); + } + else if( name == PROPERTY_RENDER_RECT ) + { + if( value.type() != QVariant::String ) + { + qWarning() << "MafwGstRenderer Invalid ("<(owner); + + self->m_playedStampTimer.stop(); + + Q_EMIT self->rendererPaused(); + + //are we staying in paused after stopped state (pauseAt requested) + //we'll need to inform the MafwBasicRenderer to give the next item to play after current + //if so start fetching next also in this case + if( self->m_currentState == MafwRenderer::Stopped ) + { + Q_EMIT self->rendererReadyForNext(self->m_playingItem); + } + + self->m_currentState = MafwRenderer::Paused; +} + +/******************************************************************** + * MafwGstRenderer::eosCallback + * Renderer does not stop here, because there could be set next item to play. + ********************************************************************/ +void MafwGstRenderer::eosCallback(MafwGstRendererWorker *worker, + gpointer owner) +{ + + Q_UNUSED(worker); + + qDebug() << __PRETTY_FUNCTION__; + + MafwGstRenderer* self = static_cast(owner); + + //this is very special case to restart playing streams of undetermined duration and nonseekable + if( mafw_gst_renderer_worker_get_streaming(worker) + && mafw_gst_renderer_worker_get_last_known_duration(worker) < 0 + && !mafw_gst_renderer_worker_get_seekable(worker) ) + { + QTimer::singleShot(0, self, SLOT(restartPlay())); + return; + } + + if( self->m_playedStampTimer.isActive() ) // eos before stamped, stamp now + { + self->m_playedStampTimer.stop(); + self->slotStamp(); + } + + if (self->m_playingPlaylistFile) //Try next uri if exists + { + self->m_playlistNextTimer.start(0); + } + else + { + QTimer::singleShot(0, self, SLOT(playNext())); + Q_EMIT self->rendererEos(); + } + +} + +/******************************************************************** + * MafwGstRenderer::restartPlay + * Slot to call asynchronously to restart playback (e.g. when internet radio disconnect due to network issues) + ********************************************************************/ +void MafwGstRenderer::restartPlay() +{ + //only restart if we're still playing + if( m_currentState == MafwRenderer::Playing ) + { + doPlay(m_currentContent); + } +} + +/******************************************************************** + * MafwGstRenderer::readyStateCallback + * Worker informs via this when it is no longer using any resources when paused + ********************************************************************/ +void MafwGstRenderer::readyStateCallback(MafwGstRendererWorker *worker, gpointer owner) +{ + Q_UNUSED(worker); + + MafwGstRenderer *self = static_cast(owner); + + if( self->m_currentState != MafwRenderer::Paused ) + { + qCritical("MafwGstRenderer: Ready state informed, but not in PAUSED state! Not releasing resources!"); + return; + } + + MafwRendererPolicy *policy = self->rendererPolicy(); + if( policy ) + { + policy->release(); + } +} + +// +//Private implementation +// + +/******************************************************************** + * MafwGstRenderer::slotGetPosition + ********************************************************************/ +void MafwGstRenderer::slotGetPosition(QObject* resultsReceiver, + const char* resultsMember) +{ + QMetaMethod method; + bool methodFound; + gint pos; + + if(m_currentState == MafwRenderer::Stopped) + { + pos = 0; + } + else if( m_haltState.isSet() ) + { + pos = m_haltState.position(); + } + else + { + /* this returns -1 on failure */ + pos = mafw_gst_renderer_worker_get_position(m_worker); + } + + if (pos < 0) + { + MafwError err; + err.setCode(MafwError::RendererError_CannotGetPosition); + Q_EMIT rendererError(err); + } + else + { + methodFound = MafwCallbackHelper::getCallbackMethod(resultsReceiver, + resultsMember, + method); + + if (!methodFound || + method.invoke(resultsReceiver, Q_ARG(uint, pos)) == false) + { + qCritical() << "Invoking the get position callback method failed!"; + } + } +} + +/******************************************************************** + * MafwGstRenderer::slotMafwProperty + ********************************************************************/ +void MafwGstRenderer::slotMafwProperty(const QString& name, + QObject* receiver, + const char* member) +{ + + QVariant prop; + QMetaMethod method; + bool methodFound; + + if (name == PROPERTY_VOLUME) + { + if (!m_volume) + { + m_volume = new MafwGstRendererVolume(); + connect(m_volume, SIGNAL(volumeChanged(uint)), this, SLOT(handleVolumeChange(uint))); + } + + uint value = m_volume->getVolume(); + prop = QVariant(value); + } + else if (name == PROPERTY_DOLBY_STATE_MUSIC) + { + uint value = m_dolby->getMusicDolbyState(); + prop = QVariant(value); + } + else if (name == PROPERTY_DOLBY_STATE_VIDEO) + { + uint value = m_dolby->getVideoDolbyState(); + prop = QVariant(value); + } + else if (name == PROPERTY_AUTOPAINT) + { + gboolean value; + value = mafw_gst_renderer_worker_get_autopaint(m_worker); + prop = QVariant(value); + } + else if (name == PROPERTY_COLORKEY) + { + gint value; + value = mafw_gst_renderer_worker_get_colorkey(m_worker); + prop = QVariant(value); + } + else if (name == PROPERTY_XID) + { + XID value; + value = mafw_gst_renderer_worker_get_xid(m_worker); + prop = QVariant(static_cast(value)); + } + else if (name == PROPERTY_PLAYBACK_SPEED) + { + gfloat value; + value = mafw_gst_renderer_worker_get_playback_speed(m_worker); + prop = QVariant(value); + } + else if (name == PROPERTY_FORCE_ASPECT_RATIO) + { + gboolean value; + value = mafw_gst_renderer_worker_get_force_aspect_ratio(m_worker); + prop = QVariant(value); + } + else if( name == PROPERTY_RENDER_RECT) + { + const render_rectangle *rect = mafw_gst_renderer_worker_get_render_rectangle(m_worker); + prop = QString("%1,%2,%3,%4") + .arg(rect->x).arg(rect->y).arg(rect->width).arg(rect->height); + } + else + { + qWarning() << "unknown property: " << name; + } + + methodFound = MafwCallbackHelper::getCallbackMethod(receiver, + member, + method); + + if (!methodFound || method.invoke(receiver, + Q_ARG(QString, name), + Q_ARG(QVariant, prop)) == false) + { + qCritical() << "Invoking the callback method failed!"; + } + +} + +/******************************************************************** + * MafwGstRenderer::slotStamp + ********************************************************************/ +void MafwGstRenderer::slotStamp() +{ + qDebug() << __PRETTY_FUNCTION__; + + QString uid=m_currentContent.uuid(); + if( !uid.isEmpty() ) + { + // create live node from MAFW object ID. Tracker case only implemented + // here. There definitely should be helper function for this. + const QString TRACKER_SOURCE_UUID = "MafwTrackerSource"; + const QString MAFW_UUID_SEPARATOR = "::"; + + QString source = uid.section(MAFW_UUID_SEPARATOR, 0 , 0); + + if ( source == TRACKER_SOURCE_UUID ) + { + QString uniqueNodeIdentifier = uid.section(MAFW_UUID_SEPARATOR, 1, 1); + if (uniqueNodeIdentifier.length() > 0) + { + int counter = m_currentContent.firstMetaData(MAFW_METADATA_KEY_PLAY_COUNT).toInt(); + counter++; + qDebug() << "MafwGstRenderer::slotStamp counter" << counter; + m_currentContent.appendMetaData(MAFW_METADATA_KEY_PLAY_COUNT, QList() << QVariant(counter)); + + int storedDuration = m_currentContent.firstMetaData(MAFW_METADATA_KEY_DURATION).toInt(); + int currentDuration = mafw_gst_renderer_worker_get_duration(m_worker); + int stampDuration = -1; + if( currentDuration >= 0 && storedDuration != currentDuration ) + { + qDebug() << "Will store new duration:" << currentDuration; + stampDuration = currentDuration; + Q_EMIT(metadataChanged(MAFW_METADATA_KEY_DURATION, QList() << stampDuration)); + } + + stampIt(uniqueNodeIdentifier, counter, stampDuration); + } + } + } + else // UUID is unknown + { + const QUrl url = m_currentContent.firstMetaData(MAFW_METADATA_KEY_URI).toUrl(); + + if( url.isValid() && url.toString().startsWith("file://") ) + { + qDebug() << "MafwGstRenderer::slotStamp query from tracker" << url; + + QSparqlQuery query(QString("SELECT ?urn ?usageCount ?duration WHERE { " + "?urn nie:url \"%1\". " + "OPTIONAL { " + "?urn nie:usageCounter ?usageCount. " + "?urn nfo:duration ?duration } " + "}") + .arg(url.toEncoded().constData())); + + delete m_urnQueryResult; + m_urnQueryResult = m_sparqlConnection->exec(query); + connect(m_urnQueryResult, SIGNAL(finished()), + this, SLOT(slotStampQueryReady())); + } + } + + m_playedStamped=true; +} + +/******************************************************************** + * MafwGstRenderer::slotStampQueryReady + ********************************************************************/ +void MafwGstRenderer::slotStampQueryReady() +{ + m_playedStampTryCounter++; + if( !m_urnQueryResult || m_urnQueryResult->hasError() || !m_urnQueryResult->first() ) + { + qWarning() << "MafwGstRenderer::slotStampQueryReady: surprising result"; + if (!m_playedStampTimer.isActive() + && m_currentState == MafwRenderer::Playing + && m_playedStampTryCounter < PLAYED_STAMP_TRIES) + { + qDebug() << __PRETTY_FUNCTION__ << "restarting timer."; + m_playedStampTimer.start(PLAYED_STAMP_INTERVAL); + } + else + { + qWarning() << __PRETTY_FUNCTION__ << "played stamping didn't succeeded."; + m_playedStamped = false; + } + } + else + { + QString urn = m_urnQueryResult->stringValue(0); + int usageCount = m_urnQueryResult->stringValue(1).toInt(); + int storedDuration = m_urnQueryResult->stringValue(2).toInt(); + + int currentDuration = mafw_gst_renderer_worker_get_duration(m_worker); + + int mediaDuration = -1; + if( storedDuration != currentDuration) + { + mediaDuration = currentDuration; + Q_EMIT(metadataChanged(MAFW_METADATA_KEY_DURATION, QList() << mediaDuration)); + } + + qDebug() << "MafwGstRenderer::slotStampQueryReady" << urn << usageCount << mediaDuration; + + stampIt(urn, usageCount+1, mediaDuration); + } + + + delete m_urnQueryResult; + m_urnQueryResult = 0; +} + +/******************************************************************** + * MafwGstRenderer::stopTimers + ********************************************************************/ +void MafwGstRenderer::stopTimers() +{ + m_playlistNextTimer.stop(); + if (m_playlistFileUtil) + { + m_playlistFileUtil->takePendingError(); + } + m_playedStampTimer.stop(); +} + +/******************************************************************** + * MafwGstRenderer::stampIt + ********************************************************************/ +void MafwGstRenderer::stampIt(const QString& urn, int usageCount, int mediaDuration) +{ + QString isoDate=QDateTime::currentDateTime().toUTC().toString(Qt::ISODate); + // Add UTC mark "Z" if it is missing (Qt behaviour has changed it seems to add it nowadays) + if( isoDate.length()==ISO_DATE_BASE_LENGTH ) + { + isoDate.append("Z"); + } + + QSparqlQuery update; + if( mediaDuration > -1 ) + { + update.setQuery(QString( + " DELETE { <%1> nie:contentAccessed ?old } " + " WHERE { <%1> nie:contentAccessed ?old } " + " DELETE { <%1> nie:usageCounter ?oldu } " + " WHERE { <%1> nie:usageCounter ?oldu } " + " DELETE { <%1> nfo:duration ?oldd } " + " WHERE { <%1> nfo:duration ?oldd } " + " INSERT { <%1> nie:contentAccessed \"%2\" . " + " <%1> nie:usageCounter \"%3\" . " + " <%1> nfo:duration \"%4\" }") + .arg(urn) + .arg(isoDate) + .arg(usageCount) + .arg(mediaDuration)); + } + else + { + update.setQuery(QString( + "DELETE { <%1> nie:contentAccessed ?old } " + " WHERE { <%1> nie:contentAccessed ?old } " + "DELETE { <%1> nie:usageCounter ?oldu } " + " WHERE { <%1> nie:usageCounter ?oldu } " + "INSERT { <%1> nie:contentAccessed \"%2\" . " + " <%1> nie:usageCounter \"%3\"}") + .arg(urn) + .arg(isoDate) + .arg(usageCount)); + } + + update.setType(QSparqlQuery::InsertStatement); + + + delete m_stampItResult; + m_stampItResult = m_sparqlConnection->exec(update); + connect(m_stampItResult, SIGNAL(finished()), + this, SLOT(slotStampItDone())); +} + +/******************************************************************** + * MafwGstRenderer::slotStampItDone() + ********************************************************************/ +void MafwGstRenderer::slotStampItDone() +{ + if( !m_stampItResult ) + { + qWarning() << "Stampit cannot be done without stmapit result! Invalid slot call?"; + return; + } + + if( m_stampItResult->hasError() ) + { + qWarning() << "Stampit failed:" << m_stampItResult->lastError().message(); + } + delete m_stampItResult; + m_stampItResult = 0; +} + +/******************************************************************** + * MafwGstRenderer::slotRouteChanged() + ********************************************************************/ +void MafwGstRenderer::slotRouteChanged() +{ + QSet set; + QString route; + + // 1. add audio route(s) to the route set + route = m_audioRoute->value().toString(); + qDebug() << "audio route is:" << route; + if (audioRouteMap().contains(route)) + { + Q_FOREACH (int value, audioRouteMap().value(route)) + { + set.insert(value); + } + } + else + { + // TODO: Is it ok to use NULL here? + qWarning() << "adding null route (audio)"; + set.insert(WORKER_OUTPUT_NULL); + } + + // 2. add video route(s) to the route set + route = m_videoRoute->value().toString(); + qDebug() << "video route is:" << route; + if (videoRouteMap().contains(route)) + { + Q_FOREACH (int value, videoRouteMap().value(route)) + { + set.insert(value); + } + } + else + { + // TODO: Is it ok to use NULL here? + qWarning() << "adding null route (video)"; + set.insert(WORKER_OUTPUT_NULL); + } + + // 3. finally notify the worker about the current routes + GSList *destinations = NULL; + Q_FOREACH (int value, set) + { + destinations = g_slist_append(destinations, GINT_TO_POINTER(value)); + } + mafw_gst_renderer_worker_notify_media_destination(this->m_worker, + destinations); + g_slist_free(destinations); + +} + +/******************************************************************** + * MafwGstRenderer::playURI + ********************************************************************/ +void MafwGstRenderer::playURI(const QString& uri) +{ + m_playedStamped = false; + m_playedStampTryCounter = 0; + + //little hack to get pause-to-play transition to be signalled + //correctly, in case different URI is asked to be played. + //So it's not resume transition + m_currentState = MafwRenderer::Stopped; + mafw_gst_renderer_worker_play(m_worker, uri.toAscii().constData()); + m_nextContent = MafwMediaInfo(); +} + +/******************************************************************** + * MafwGstRenderer::startPlayingPlaylistFile + ********************************************************************/ +void MafwGstRenderer::startPlayingPlaylistFile() +{ + m_playlistNextTimer.stop(); + QString uri = QString(); + if (m_playlistFileUtil) + { + uri = m_playlistFileUtil->takeFirstUri(); + m_playlistFileUtil->takePendingError(); + } + else + { + qCritical() << __PRETTY_FUNCTION__ << "playlist file util is NULL!"; + } + + if (!uri.isEmpty()) + { + qDebug() << __PRETTY_FUNCTION__ << uri; + + if( !m_mmcMonitor->isMounted() && uri.startsWith( MafwMmcMonitor::MMC_URI_PREFIX ) ) + { + qDebug() << "MafwGstRenderer::startPlayingPlaylistFile: Can't play MMC not mounted"; + MafwError mafwError(MafwError::RendererError_MmcNotAvailable, uri); + Q_EMIT rendererError(mafwError); + return; + } + + m_playingPlaylistFile = true; + mafw_gst_renderer_worker_play(m_worker, uri.toAscii().constData()); + QList metadataValue; + metadataValue << uri; + Q_EMIT metadataChanged(MAFW_METADATA_KEY_URI, metadataValue); + } + else + { + MafwError err; + err.setCode(MafwError::RendererError_PlaylistParsing); + Q_EMIT rendererError(err); + } +} + + +/******************************************************************** + * MafwGstRenderer::handlePlaylistFileParsingErrors + ********************************************************************/ +void MafwGstRenderer::handlePlaylistFileParsingErrors(bool succeeded) +{ + qDebug() << __PRETTY_FUNCTION__; + + if (!succeeded) + { + if (m_unsupportedTypeError) + { + errorCallback(m_worker, this, m_unsupportedTypeError); + g_error_free(m_unsupportedTypeError); + m_unsupportedTypeError = 0; + } + else + { + MafwError err; + err.setCode(MafwError::RendererError_PlaylistParsing); + Q_EMIT rendererError(err); + } + } + else if (!m_playingPlaylistFile) + { + qDebug() << __PRETTY_FUNCTION__ << "waiting for playlist file items..."; + MafwError err; + err.setCode(MafwError::RendererError_NoPlaylist); + m_playlistFileUtil->setPendingError(err); + m_playlistNextTimer.start(1000); + } +} + +/******************************************************************** + * MafwGstRenderer::playNext + ********************************************************************/ +void MafwGstRenderer::playNext() +{ + qDebug() << __PRETTY_FUNCTION__; + m_playingPlaylistFile = false; + m_playedPlaylistItem = false; + + //Preserve m_currentContent for keeping usage count up if the same item is + //played again. + if( !m_nextContent.uuid().isEmpty() && (m_nextContent.uuid() == m_currentContent.uuid()) ) + { + m_nextContent = m_currentContent; + } + + const QUrl nextURI = m_nextContent.firstMetaData(MAFW_METADATA_KEY_URI).toUrl(); + if( !nextURI.isEmpty() ) + { + m_playingItem = MafwBasicRenderer::NextUri; + m_currentContent = m_nextContent; + m_nextContent = MafwMediaInfo(); + + playURI(nextURI.toEncoded()); + } +} + +/******************************************************************** + * MafwGstRenderer::playNextURIFromPlaylist + ********************************************************************/ +void MafwGstRenderer::playNextURIFromPlaylist() +{ + qDebug() << __PRETTY_FUNCTION__; + QString uri = m_playlistFileUtil->takeFirstUri(); + + bool okToPlay=true; + if(uri.isEmpty()) + { + okToPlay=false; + } + else if( !m_mmcMonitor->isMounted() && uri.startsWith( MafwMmcMonitor::MMC_URI_PREFIX ) ) + { + qDebug() << "MafwGstRenderer::playNextURIFromPlaylist: Can't play MMC not mounted"; + MafwError mafwError(MafwError::RendererError_MmcNotAvailable, uri); + m_playlistFileUtil->setPendingError( mafwError ); + okToPlay=false; + } + + if (okToPlay) + { + m_playlistFileUtil->takePendingError(); // clear it, we have a new candidate + qDebug() << "Trying next uri: " << uri; + mafw_gst_renderer_worker_play(m_worker, uri.toAscii().constData()); + QList metadataValue; + metadataValue << uri; + Q_EMIT metadataChanged(MAFW_METADATA_KEY_URI, metadataValue); + } + else + { + m_playingPlaylistFile = false; + + if (m_playedPlaylistItem) + { + Q_EMIT rendererEos(); + } + m_playedPlaylistItem = false; + + + MafwError mafwError = m_playlistFileUtil->takePendingError(); + if ( mafwError.code() != MafwError::NoError) + { + Q_EMIT rendererError(mafwError); + doStop(); + MafwRendererPolicy *policy = rendererPolicy(); + if( policy ) + { + policy->release(); + } + } + } +} + +/******************************************************************** + * MafwGstRenderer::slotCurrentMediaInfo + ********************************************************************/ +void MafwGstRenderer::slotGetCurrentMediaInfo(QObject* receiver, const char* member, const QString& metadataKey) +{ + MafwMediaInfo info(m_currentContent.uuid()); + + //get all metadata + if(metadataKey.isEmpty()) + { + info.setMetaData(m_currentMetaData); + } + //get one item + else + { + QMap >::const_iterator iter = m_currentMetaData.find(metadataKey); + if (iter != m_currentMetaData.end()) + { + info.appendMetaData(iter.key(), iter.value()); + } + } + + sendMediaInfo(info, receiver, member); +} + +/******************************************************************** + * MafwGstRenderer::handleVolumeChange + ********************************************************************/ +void MafwGstRenderer::handleVolumeChange(uint level) +{ + qDebug() << "MafwGstRenderer::handleVolumeChange: " << level; + Q_EMIT mafwPropertyChanged(PROPERTY_VOLUME, level); +} + +/******************************************************************** + * MafwGstRenderer::stopStreaming + ********************************************************************/ +void MafwGstRenderer::stopStreaming() +{ + qDebug() << __PRETTY_FUNCTION__; + if( mafw_gst_renderer_worker_get_streaming(m_worker) ) + { + mafw_gst_renderer_worker_stop(m_worker); + stopTimers(); + } + + // emit error and stop for real, only if no valid halt state is set + if( !m_haltState.isSet() ) + { + doStop(); + MafwError error; + error.setCode(MafwError::RendererError_StreamDisconnected); + Q_EMIT rendererError(error); + } +} + +/******************************************************************** + * MafwGstRenderer::haltStreaming + ********************************************************************/ +void MafwGstRenderer::haltStreaming() +{ + qDebug() << __PRETTY_FUNCTION__; + if( mafw_gst_renderer_worker_get_streaming(m_worker) ) + { + QString uri; + if( m_playlistNextTimer.isActive() ) + { + uri = m_playlistFileUtil->takeFirstUri(); + } + else + { + uri = mafw_gst_renderer_worker_get_uri(m_worker); + } + + int position = -1; + if( mafw_gst_renderer_worker_get_seekable(m_worker) ) + { + position = mafw_gst_renderer_worker_get_position(m_worker); + if( position < 0 ) + { + qWarning() << "Cannot resume to correct position after networkchange!"; + } + } + + //make sure we've uri to resume, the playlist parser may have been trying to parse something + if( uri.length() > 0 ) + { + m_haltState = MafwGstRendererHaltState(uri, m_currentState, position); + //valid haltstate constructed, clear the possible pending error in playlist handling + if( m_playlistFileUtil ) + { + m_playlistFileUtil->takePendingError(); + } + } + else + { + //just in case + m_haltState.clear(); + } + + //now actually stop, and depending on the haltstate validity it will also emit error + stopStreaming(); + } + else + { + qDebug() << "Not streaming!"; + } +} + +/******************************************************************** + * MafwGstRenderer::continueStreaming + ********************************************************************/ +void MafwGstRenderer::continueStreaming() +{ + if( mafw_gst_renderer_worker_get_streaming(m_worker) || m_haltState.isSet() ) + { + //if not yet halted, do it now + if( !m_haltState.isSet() ) + { + haltStreaming(); + } + + m_playingItem = MafwBasicRenderer::CurrentUri; + + if( m_haltState.state() == MafwRenderer::Playing ) + { + mafw_gst_renderer_worker_play(m_worker, m_haltState.uri().toAscii().constData()); + int pausePos = m_haltState.position() > 0 ? m_haltState.position() : 0; + + if( m_haltState.state() == MafwRenderer::Playing && pausePos > 0 ) + { + qDebug() << "Resuming streaming from position: " << m_haltState.position(); + doSeek(m_haltState.position(), MafwRenderer::SeekAbsolute); + } + m_haltState.clear(); + } + } +} + +/******************************************************************** + * MafwGstRenderer::handlePropertyChanged + ********************************************************************/ +void MafwGstRenderer::handlePropertyChanged(const QString& name, + const QVariant& value) +{ + // This is a way to check if the policy is on. We need to set the + // PAUSED-to-READY timeout to zero, since we need to be sure that the + // resources really get released by the GStreamer. It is unfortunate that + // a doPause() and PAUSED state is not enough, e.g. in case of XVideo. + if (name == MAFW_RENDERER_PROPERTY_POLICY_OVERRIDE) + { + guint timeout; + if (value.toBool() == true) + { + timeout = 0; + } + else + { + timeout = MAFW_GST_RENDERER_WORKER_READY_TIMEOUT; + } + mafw_gst_renderer_worker_set_ready_timeout(m_worker, timeout); + } +} + +/******************************************************************** + * MafwGstRenderer::handleDHMMusicPropertyChanged + ********************************************************************/ +void MafwGstRenderer::handleDHMMusicPropertyChanged() +{ + if (m_worker) + { + qDebug() << "MafwGstRenderer::handleDHMMusicPropertyChanged set_dolby_music_property" << m_dolby->getMusicDolbyState(); + set_dolby_music_property(m_worker, m_dolby->getMusicDolbyState()); + set_dolby_music_sound_property(m_worker, m_dolby->getMusicDolbyRoom(), TRUE); + set_dolby_music_sound_property(m_worker, m_dolby->getMusicDolbyColor(), FALSE); + } +} + +/******************************************************************** + * MafwGstRenderer::handleDHMVideoPropertyChanged + ********************************************************************/ +void MafwGstRenderer::handleDHMVideoPropertyChanged() +{ + if (m_worker) + { + qDebug() << "MafwGstRenderer::handleDHMVideoPropertyChanged set_dolby_video_property" << m_dolby->getVideoDolbyState(); + set_dolby_video_property(m_worker, m_dolby->getVideoDolbyState()); + set_dolby_video_sound_property(m_worker, m_dolby->getVideoDolbyRoom(), TRUE); + set_dolby_video_sound_property(m_worker, m_dolby->getVideoDolbyColor(), FALSE); + } +} + +/******************************************************************** + * MafwGstRenderer::handleScreenshot + ********************************************************************/ +void MafwGstRenderer::handleScreenshot(char *location, GError *error) +{ + if(!error) + { + QList results; + results << location; + + QString mafwMetadataKey = MAFW_METADATA_KEY_PAUSED_THUMBNAIL_URI; + + appendRelatedMetadata(mafwMetadataKey, &results); + Q_EMIT metadataChanged(mafwMetadataKey, results); + m_currentMetaData.insert(mafwMetadataKey, results); + } + else + { + qCritical() << error->message; + } + + m_worker->taking_screenshot = FALSE; +} + +/******************************************************************** + * MafwGstRenderer::cancelScreenshot + ********************************************************************/ +void MafwGstRenderer::cancelScreenshot() +{ + if(m_worker) + { + m_worker->taking_screenshot = FALSE; + } +} + +/******************************************************************** + * MafwGstRenderer::sendMediaInfo + ********************************************************************/ +void MafwGstRenderer::sendMediaInfo(const MafwMediaInfo& info, QObject* receiver, const char* member) +{ + QMetaMethod method; + bool methodFound; + + methodFound = MafwCallbackHelper::getCallbackMethod(receiver, member, method); + + if (!methodFound) + { + MafwError err; + err.setCode(MafwError::CallbackSlotNotFound); + Q_EMIT error(err); + } + //actual result callback call is inside this if() + else if( !method.invoke(receiver, Q_ARG(MafwMediaInfo, info)) ) + { + MafwError err; + err.setCode(MafwError::CallbackCouldNotInvoke); + Q_EMIT error(err); + } +} + +/******************************************************************** + * MafwGstRenderer::mmcPreUnmount + ********************************************************************/ +void MafwGstRenderer::mmcPreUnmount() +{ + qDebug() << "MafwGstRenderer::mmcPreUnmount" << m_currentState; + + if( m_currentState!=MafwRenderer::Stopped ) + { + const QUrl url = m_currentContent.firstMetaData(MAFW_METADATA_KEY_URI).toUrl(); + if( url.toString().startsWith(MafwMmcMonitor::MMC_URI_PREFIX) ) + { + qDebug() << "MafwGstRenderer::mmcPreUnmount: playing from MMC, going to stop"; + doStop(); + MafwError mafwError(MafwError::RendererError_MmcNotAvailable, url.toEncoded()); + Q_EMIT rendererError(mafwError); + } + } +} + +/******************************************************************** + * MafwGstRenderer::connectNameOwnerChanged + ********************************************************************/ +bool MafwGstRenderer::connectNameOwnerChanged() +{ + QStringList argumentMatch; + argumentMatch << DBUS_NAME_PCFD; + + QDBusConnection connection = QDBusConnection::systemBus(); + return connection.connect( QString(), + QString(), + DBUS_INTERFACE_DBUS, + DBUS_SIGNAL_NAME_OWNER_CHANGED, + argumentMatch, + QString(), + this, + SLOT(handleContextProviderRemoval(QDBusMessage) ) ); +} + +/******************************************************************** + * MafwGstRenderer::handleContextProviderRemoval + ********************************************************************/ +void MafwGstRenderer::handleContextProviderRemoval( const QDBusMessage& message ) +{ + QList arguments; + QString name; + QString oldName; + QString newName; + + arguments= message.arguments(); + + if ( message.type() == QDBusMessage::SignalMessage && arguments.size()==3 ) + { + + name = arguments.at(0).toString(); + oldName = arguments.at(1).toString(); + newName = arguments.at(2).toString(); + + if ( oldName.length() && newName.length()==0 ) + { + qDebug() << "MafwGstRenderer::handleContextProviderRemoval context provider died"; + + // add null output + GSList *destinations = NULL; + destinations = g_slist_append(destinations, + GINT_TO_POINTER(WORKER_OUTPUT_NULL)); + mafw_gst_renderer_worker_notify_media_destination(this->m_worker, + destinations); + g_slist_free(destinations); + } + } +} + +/******************************************************************** + * MafwGstRenderer::handleResolutionError + ********************************************************************/ +void MafwGstRenderer::handleResolutionError(MafwError &error) +{ + qDebug() << __PRETTY_FUNCTION__; + const QUrl url = m_currentContent.firstMetaData(MAFW_METADATA_KEY_URI).toUrl(); + MafwError::Code errorCode = MafwError::RendererError_UnsuppertedType; + + if( url.isValid() && url.toString().startsWith("file://") ) + { + qDebug() << __PRETTY_FUNCTION__ << url; + + QSparqlQuery query(QString("SELECT ?height ?width WHERE { " + "?_u nie:url \"%1\" ." + "?_u nfo:height ?height . " + "?_u nfo:width ?width }") + .arg(QString(url.toEncoded()))); + + QSparqlResult *result = m_sparqlConnection->syncExec(query); + + if( result->hasError() ) + { + qWarning() << __PRETTY_FUNCTION__ << " surprising result"; + qWarning() << result->lastError().message(); + } + else if( result->first() ) + { + int height = result->stringValue(0).toInt(); + int width = result->stringValue(1).toInt(); + + if (height > MAX_SUPPORTED_HEIGHT || width > MAX_SUPPORTED_WIDTH) + { + errorCode = MafwError::RendererError_UnsupportedResolution; + } + } + delete result; + } + error.setCode(errorCode); +} + +/******************************************************************** + * MafwGstRenderer::setConfiguration + ********************************************************************/ +void MafwGstRenderer::setConfiguration(QSettings *settings) +{ + //if no settings use "factory" configuration + if( !settings ) + { + return; + } + + configuration *defaultconfig = mafw_gst_renderer_worker_create_default_configuration(m_worker); + + //pipeline settings + settings->beginGroup("pipeline"); + QVariant value = readSettingsValue(settings, "audio-sink", defaultconfig->asink); + qFree(defaultconfig->asink); + defaultconfig->asink = g_strdup(value.toString().toAscii()); + + value = readSettingsValue(settings, "video-sink", defaultconfig->vsink); + qFree(defaultconfig->vsink); + defaultconfig->vsink = g_strdup(value.toString().toAscii()); + + value = readSettingsValue(settings, "flags", defaultconfig->flags); + defaultconfig->flags = value.toInt(); + + value = readSettingsValue(settings, "use_dhmmixer", defaultconfig->use_dhmmixer); + defaultconfig->use_dhmmixer = value.toBool(); + + value = readSettingsValue(settings, "buffer-time", defaultconfig->buffer_time); + defaultconfig->buffer_time = value.toULongLong(); + + value = readSettingsValue(settings, "latency-time", defaultconfig->latency_time); + defaultconfig->latency_time = value.toULongLong(); + + value = readSettingsValue(settings, "autoload_subtitles", defaultconfig->autoload_subtitles); + defaultconfig->autoload_subtitles = value.toBool(); + + value = readSettingsValue(settings, "subtitle_encoding", defaultconfig->subtitle_encoding); + qFree(defaultconfig->subtitle_encoding); + defaultconfig->subtitle_encoding = g_strdup(value.toString().toAscii()); + + value = readSettingsValue(settings, "subtitle_font", defaultconfig->subtitle_font); + qFree(defaultconfig->subtitle_font); + defaultconfig->subtitle_font = g_strdup(value.toString().toAscii()); + settings->endGroup(); + + //timers + settings->beginGroup("timers"); + value = readSettingsValue(settings, "pause-frame", defaultconfig->milliseconds_to_pause_frame); + defaultconfig->milliseconds_to_pause_frame = value.toUInt(); + + value = readSettingsValue(settings, "pause-to-ready", defaultconfig->seconds_to_pause_to_ready); + defaultconfig->seconds_to_pause_to_ready = value.toUInt(); + settings->endGroup(); + + //dhmmixer + settings->beginGroup("dhmmixer"); + value = readSettingsValue(settings, "dhm-music-surround", defaultconfig->mobile_surround_music.state); + defaultconfig->mobile_surround_music.state = value.toUInt(); + + value = readSettingsValue(settings, "dhm-music-color", defaultconfig->mobile_surround_music.color); + defaultconfig->mobile_surround_music.color = value.toInt(); + + value = readSettingsValue(settings, "dhm-music-room-size", defaultconfig->mobile_surround_music.room); + defaultconfig->mobile_surround_music.room = value.toInt(); + + value = readSettingsValue(settings, "dhm-video-surround", defaultconfig->mobile_surround_video.state); + defaultconfig->mobile_surround_video.state = value.toUInt(); + + value = readSettingsValue(settings, "dhm-video-color", defaultconfig->mobile_surround_video.color); + defaultconfig->mobile_surround_video.color = value.toInt(); + + value = readSettingsValue(settings, "dhm-video-room-size", defaultconfig->mobile_surround_video.room); + defaultconfig->mobile_surround_video.room = value.toInt(); + settings->endGroup(); + + mafw_gst_renderer_worker_set_configuration(m_worker, defaultconfig); +} + +/******************************************************************** + * MafwGstRenderer::readSettingsValue + ********************************************************************/ +QVariant MafwGstRenderer::readSettingsValue(QSettings *settings, + const QString &valueName, + const QVariant &defaultValue) const +{ + QVariant value = settings->value(valueName, defaultValue); + if( !settings->contains(valueName) ) + { + qWarning() << "No value for: (" << valueName << ") in configuration file! Using factory default"; + } + return value; +} + +/******************************************************************** + * MafwGstRenderer::errorMap + ********************************************************************/ +const QHash& MafwGstRenderer::errorMap() +{ + + static QHash map; + + if (map.isEmpty()) + { + /* initialize error map */ + map[WORKER_ERROR_PLAYBACK] = + MafwError::RendererError_Playback; + map[WORKER_ERROR_VIDEO_CODEC_NOT_FOUND] = + MafwError::RendererError_VideoCodeNotFound; + map[WORKER_ERROR_AUDIO_CODEC_NOT_FOUND] = + MafwError::RendererError_AudioCodecNotFound; + map[WORKER_ERROR_CODEC_NOT_FOUND] = + MafwError::RendererError_CodecNotFound; + map[WORKER_ERROR_UNSUPPORTED_TYPE] = + MafwError::RendererError_UnsuppertedType; + map[WORKER_ERROR_POSSIBLY_PLAYLIST_TYPE] = + MafwError::RendererError_UnsuppertedType; + map[WORKER_ERROR_UNABLE_TO_PERFORM] = + MafwError::RendererError_UnableToPerform; + map[WORKER_ERROR_CANNOT_SET_POSITION] = + MafwError::RendererError_CannotSetPosition; + map[WORKER_ERROR_PLAYLIST_PARSING] = + MafwError::RendererError_PlaylistParsing; + map[WORKER_ERROR_DRM_NO_LICENSE] = + MafwError::RendererError_DRMNoLicense; + map[WORKER_ERROR_DRM_NOT_ALLOWED] = + MafwError::RendererError_DRMNotAllowed; + map[WORKER_ERROR_DRM_OTHER] = + MafwError::RendererError_DRMOther; + map[WORKER_ERROR_STREAM_DISCONNECTED] = + MafwError::RendererError_StreamDisconnected; + map[WORKER_ERROR_INVALID_URI] = + MafwError::RendererError_InvalidURI; + map[WORKER_ERROR_MEDIA_NOT_FOUND] = + MafwError::RendererError_MediaNotFound; + map[WORKER_ERROR_CORRUPTED_FILE] = + MafwError::RendererError_CorruptedFile; + map[WORKER_ERROR_TYPE_NOT_AVAILABLE] = + MafwError::RendererError_TypeNotAvailable; + } + + return map; + +} + +/******************************************************************** + * MafwGstRenderer::metadataMap + ********************************************************************/ +const QHash& MafwGstRenderer::metadataMap() +{ + + static QHash map; + + if (map.isEmpty()) + { + /* initialize metadata key map */ + map[WORKER_METADATA_KEY_TITLE] = + MAFW_METADATA_KEY_TITLE; + map[WORKER_METADATA_KEY_ARTIST] = + MAFW_METADATA_KEY_ARTIST; + map[WORKER_METADATA_KEY_AUDIO_CODEC] = + MAFW_METADATA_KEY_AUDIO_CODEC; + map[WORKER_METADATA_KEY_VIDEO_CODEC] = + MAFW_METADATA_KEY_VIDEO_CODEC; + map[WORKER_METADATA_KEY_BITRATE] = + MAFW_METADATA_KEY_BITRATE; + map[WORKER_METADATA_KEY_ENCODING] = + MAFW_METADATA_KEY_ENCODING; + map[WORKER_METADATA_KEY_ALBUM] = + MAFW_METADATA_KEY_ALBUM; + map[WORKER_METADATA_KEY_GENRE] = + MAFW_METADATA_KEY_GENRE; + map[WORKER_METADATA_KEY_TRACK] = + MAFW_METADATA_KEY_TRACK; + map[WORKER_METADATA_KEY_ORGANIZATION] = + MAFW_METADATA_KEY_ORGANIZATION; + map[WORKER_METADATA_KEY_RENDERER_ART_URI] = + MAFW_METADATA_KEY_RENDERER_ART_URI; + map[WORKER_METADATA_KEY_RES_X] = + MAFW_METADATA_KEY_RES_X; + map[WORKER_METADATA_KEY_RES_Y] = + MAFW_METADATA_KEY_RES_Y; + map[WORKER_METADATA_KEY_VIDEO_FRAMERATE] = + MAFW_METADATA_KEY_VIDEO_FRAMERATE; + map[WORKER_METADATA_KEY_DURATION] = + MAFW_METADATA_KEY_DURATION; + map[WORKER_METADATA_KEY_IS_SEEKABLE] = + MAFW_METADATA_KEY_IS_SEEKABLE; + map[WORKER_METADATA_KEY_PAUSED_THUMBNAIL_URI] = + MAFW_METADATA_KEY_PAUSED_THUMBNAIL_URI; + map[WORKER_METADATA_KEY_URI] = + MAFW_METADATA_KEY_URI; + } + + return map; + +} + +/******************************************************************** + * MafwGstRenderer::audioRouteMap + ********************************************************************/ +const QHash >& MafwGstRenderer::audioRouteMap() +{ + + static QHash > map; + + if (map.isEmpty()) + { + map[AUDIO_ROUTE_NULL] = QList() << WORKER_OUTPUT_NULL; + map[AUDIO_ROUTE_IHF] = QList() << WORKER_OUTPUT_BUILTIN_SPEAKERS; + map[AUDIO_ROUTE_FMRADIO] = QList() << WORKER_OUTPUT_FM_RADIO; + map[AUDIO_ROUTE_IHF_AND_FMRADIO] = QList() << WORKER_OUTPUT_BUILTIN_SPEAKERS << WORKER_OUTPUT_FM_RADIO; + + // earpiece is intentionally handled as builtdin speaker, well it kinda is + map[AUDIO_ROUTE_EARPIECE] = QList() << WORKER_OUTPUT_BUILTIN_SPEAKERS; + map[AUDIO_ROUTE_EARPIECE_AND_TVOUT] = QList() << WORKER_OUTPUT_BUILTIN_SPEAKERS << WORKER_OUTPUT_TVOUT; + + map[AUDIO_ROUTE_TV_OUT] = QList() << WORKER_OUTPUT_HEADPHONE_JACK; + map[AUDIO_ROUTE_IHF_AND_TV_OUT] = QList() << WORKER_OUTPUT_BUILTIN_SPEAKERS << WORKER_OUTPUT_TVOUT; + map[AUDIO_ROUTE_HEADPHONE] = QList() << WORKER_OUTPUT_HEADPHONE_JACK; + map[AUDIO_ROUTE_HEADSET] = QList() << WORKER_OUTPUT_HEADPHONE_JACK; + map[AUDIO_ROUTE_BTHSP] = QList() << WORKER_OUTPUT_BLUETOOTH_AUDIO; + map[AUDIO_ROUTE_BTA2DP] = QList() << WORKER_OUTPUT_BLUETOOTH_AUDIO; + map[AUDIO_ROUTE_IHF_AND_HEADSET] = QList() << WORKER_OUTPUT_BUILTIN_SPEAKERS << WORKER_OUTPUT_HEADPHONE_JACK; + map[AUDIO_ROUTE_IHF_AND_HEADPHONE] = QList() << WORKER_OUTPUT_BUILTIN_SPEAKERS << WORKER_OUTPUT_HEADPHONE_JACK; + map[AUDIO_ROUTE_IHF_AND_BTHSP] = QList() << WORKER_OUTPUT_BUILTIN_SPEAKERS << WORKER_OUTPUT_BLUETOOTH_AUDIO; + map[AUDIO_ROUTE_TV_OUT_AND_BTHSP] = QList() << WORKER_OUTPUT_HEADPHONE_JACK << WORKER_OUTPUT_BLUETOOTH_AUDIO; + map[AUDIO_ROUTE_TV_OUT_AND_BTA2DP] = QList() << WORKER_OUTPUT_HEADPHONE_JACK << WORKER_OUTPUT_BLUETOOTH_AUDIO; + } + + return map; + +} + +/******************************************************************** + * MafwGstRenderer::videoRouteMap + ********************************************************************/ +const QHash >& MafwGstRenderer::videoRouteMap() +{ + + static QHash > map; + + if (map.isEmpty()) + { + map[VIDEO_ROUTE_TV_OUT] = QList() << WORKER_OUTPUT_TVOUT; + map[VIDEO_ROUTE_BUILT_IN] = QList() << WORKER_OUTPUT_BUILTIN_DISPLAY; + map[VIDEO_ROUTE_BUILT_IN_AND_TV_OUT] = QList() << WORKER_OUTPUT_BUILTIN_DISPLAY << WORKER_OUTPUT_TVOUT; + } + + return map; + +} + diff --git a/qmafw-gst-subtitles-renderer/src/MafwGstRendererDolby.cpp b/qmafw-gst-subtitles-renderer/src/MafwGstRendererDolby.cpp new file mode 100644 index 0000000..91a6fa0 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/src/MafwGstRendererDolby.cpp @@ -0,0 +1,312 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#include "MafwGstRendererDolby.h" + +#include + +const int DEFAULT_COLOR = 2; +const int DEFAULT_ROOM_SIZE = 2; +const int MAX_VALUE = 4; + +enum MafwDolbyStates +{ + MafwDolbyOff, + MafwDolbyOn, + MafwDolbyAuto +}; + +MafwGstRendererDolby::MafwGstRendererDolby( QObject* parent ): + QObject(parent), + m_dolbyConfMusic(0), + m_dolbyConfMusicRoom(0), + m_dolbyConfMusicColor(0), + m_dolbyConfVideo(0), + m_dolbyConfVideoRoom(0), + m_dolbyConfVideoColor(0) +{ + qDebug() << __PRETTY_FUNCTION__; + m_currentMusicDolbyState = MafwDolbyOff; + m_currentMusicDolbyRoom = DEFAULT_ROOM_SIZE; + m_currentMusicDolbyColor = DEFAULT_COLOR; + m_currentVideoDolbyState = MafwDolbyOff; + m_currentVideoDolbyRoom = DEFAULT_ROOM_SIZE; + m_currentVideoDolbyColor = DEFAULT_COLOR; +} + +void MafwGstRendererDolby::initialize() +{ + if (!m_dolbyConfMusic) + { + m_dolbyConfMusic = new GConfItem("/apps/Multimedia/music/dolbyConf", this); + } + if (!m_dolbyConfMusicRoom) + { + m_dolbyConfMusicRoom = new GConfItem("/apps/Multimedia/music/dolbyConfRoom", this); + } + if (!m_dolbyConfMusicColor) + { + m_dolbyConfMusicColor = new GConfItem("/apps/Multimedia/music/dolbyConfColor", this); + } + if (!m_dolbyConfVideo) + { + m_dolbyConfVideo = new GConfItem("/apps/Multimedia/video/dolbyConf", this); + } + if (!m_dolbyConfVideoRoom) + { + m_dolbyConfVideoRoom = new GConfItem("/apps/Multimedia/video/dolbyConfRoom", this); + } + if (!m_dolbyConfVideoColor) + { + m_dolbyConfVideoColor = new GConfItem("/apps/Multimedia/video/dolbyConfColor", this); + } + if (!m_dolbyConfMusic->value().toString().isEmpty()) + { + valueMusicChanged(); + connect(m_dolbyConfMusic, SIGNAL(valueChanged()), this, SLOT(valueMusicChanged())); + connect(m_dolbyConfMusicRoom, SIGNAL(valueChanged()), this, SLOT(valueMusicChanged())); + connect(m_dolbyConfMusicColor, SIGNAL(valueChanged()), this, SLOT(valueMusicChanged())); + } + if (!m_dolbyConfVideo->value().toString().isEmpty()) + { + valueVideoChanged(); + connect(m_dolbyConfVideo, SIGNAL(valueChanged()), this, SLOT(valueVideoChanged())); + connect(m_dolbyConfVideoRoom, SIGNAL(valueChanged()), this, SLOT(valueVideoChanged())); + connect(m_dolbyConfVideoColor, SIGNAL(valueChanged()), this, SLOT(valueVideoChanged())); + } +} + +MafwGstRendererDolby::~MafwGstRendererDolby() +{ + qDebug() << __PRETTY_FUNCTION__; +} + +bool MafwGstRendererDolby::setMusicDolbyState (uint value) +{ + qDebug() << __PRETTY_FUNCTION__ << value; + if ( value <= MafwDolbyAuto) + { + m_currentMusicDolbyState = value; + m_dolbyConfMusic->set(m_currentMusicDolbyState); + return true; + } + else + { + m_currentMusicDolbyState = MafwDolbyOff; + m_dolbyConfMusic->set(m_currentMusicDolbyState); + return false; + } +} + +bool MafwGstRendererDolby::setMusicDolbyRoom (int value) +{ + qDebug() << __PRETTY_FUNCTION__ << value; + if ( value < 0) + { + m_currentMusicDolbyRoom = 0; + m_dolbyConfMusicRoom->set(m_currentMusicDolbyRoom); + } + else if ( value > MAX_VALUE) + { + m_currentMusicDolbyRoom = MAX_VALUE; + m_dolbyConfMusicRoom->set(m_currentMusicDolbyRoom); + } + else + { + m_currentMusicDolbyRoom = value; + m_dolbyConfMusicRoom->set(m_currentMusicDolbyRoom); + } + return true; +} + +bool MafwGstRendererDolby::setMusicDolbyColor (int value) +{ + qDebug() << __PRETTY_FUNCTION__ << value; + if ( value < 0) + { + m_currentMusicDolbyColor = 0; + m_dolbyConfMusicColor->set(m_currentMusicDolbyColor); + } + else if ( value > MAX_VALUE) + { + m_currentMusicDolbyColor = MAX_VALUE; + m_dolbyConfMusicColor->set(m_currentMusicDolbyColor); + } + else + { + m_currentMusicDolbyColor = value; + m_dolbyConfMusicColor->set(m_currentMusicDolbyColor); + } + return true; +} + +bool MafwGstRendererDolby::setVideoDolbyState (uint value) +{ + qDebug() << __PRETTY_FUNCTION__ << value; + if ( value <= MafwDolbyAuto) + { + m_currentVideoDolbyState = value; + m_dolbyConfVideo->set(int(m_currentVideoDolbyState)); + return true; + } + else + { + m_currentVideoDolbyState = MafwDolbyOff; + m_dolbyConfVideo->set(int(m_currentVideoDolbyState)); + return false; + } +} + +bool MafwGstRendererDolby::setVideoDolbyRoom (int value) +{ + qDebug() << __PRETTY_FUNCTION__ << value; + if ( value < 0) + { + m_currentVideoDolbyRoom = 0; + m_dolbyConfVideoRoom->set(m_currentVideoDolbyRoom); + } + else if ( value > MAX_VALUE) + { + m_currentVideoDolbyRoom = MAX_VALUE; + m_dolbyConfVideoRoom->set(m_currentVideoDolbyRoom); + } + else + { + m_currentVideoDolbyRoom = value; + m_dolbyConfVideoRoom->set(m_currentVideoDolbyRoom); + } + return true; +} + +bool MafwGstRendererDolby::setVideoDolbyColor (int value) +{ + qDebug() << __PRETTY_FUNCTION__ << value; + if ( value < 0) + { + m_currentVideoDolbyColor = 0; + m_dolbyConfVideoColor->set(m_currentVideoDolbyColor); + } + else if ( value > MAX_VALUE) + { + m_currentVideoDolbyColor = MAX_VALUE; + m_dolbyConfVideoColor->set(m_currentVideoDolbyColor); + } + else + { + m_currentVideoDolbyColor = value; + m_dolbyConfVideoColor->set(m_currentVideoDolbyColor); + } + return true; +} + +void MafwGstRendererDolby::valueMusicChanged() +{ + m_currentMusicDolbyState = m_dolbyConfMusic->value().toUInt(); + if (!(m_currentMusicDolbyState <= MafwDolbyAuto)) + { + m_currentMusicDolbyState = MafwDolbyOff; + m_currentMusicDolbyRoom = m_dolbyConfMusicRoom->value().toInt(); + if (m_currentMusicDolbyRoom < 0) + { + m_currentMusicDolbyRoom = 0; + } + if (m_currentMusicDolbyRoom > MAX_VALUE) + { + m_currentMusicDolbyRoom = MAX_VALUE; + } + m_currentMusicDolbyColor = m_dolbyConfMusicColor->value().toInt(); + if (m_currentMusicDolbyColor < 0) + { + m_currentMusicDolbyColor = 0; + } + if (m_currentMusicDolbyColor > MAX_VALUE) + { + m_currentMusicDolbyColor = MAX_VALUE; + } + } + qDebug() << __PRETTY_FUNCTION__ << "state" << m_currentMusicDolbyState; + qDebug() << __PRETTY_FUNCTION__ << "room" << m_currentMusicDolbyRoom; + qDebug() << __PRETTY_FUNCTION__ << "color" << m_currentMusicDolbyColor; + Q_EMIT mafwDHMMusicPropertyChanged(); +} + +void MafwGstRendererDolby::valueVideoChanged() +{ + m_currentVideoDolbyState = m_dolbyConfVideo->value().toUInt(); + if (!(m_currentVideoDolbyState <= MafwDolbyAuto)) + { + m_currentVideoDolbyState = MafwDolbyOff; + m_currentVideoDolbyRoom = m_dolbyConfVideoRoom->value().toInt(); + if (m_currentVideoDolbyRoom < 0) + { + m_currentVideoDolbyRoom = 0; + } + if (m_currentVideoDolbyRoom > MAX_VALUE) + { + m_currentVideoDolbyRoom = MAX_VALUE; + } + m_currentVideoDolbyColor = m_dolbyConfVideoColor->value().toInt(); + if (m_currentVideoDolbyColor < 0) + { + m_currentVideoDolbyColor = 0; + } + if (m_currentVideoDolbyColor > MAX_VALUE) + { + m_currentVideoDolbyColor = MAX_VALUE; + } + } + qDebug() << __PRETTY_FUNCTION__ << "state" << m_currentVideoDolbyState; + qDebug() << __PRETTY_FUNCTION__ << "room" << m_currentVideoDolbyRoom; + qDebug() << __PRETTY_FUNCTION__ << "color" << m_currentVideoDolbyColor; + Q_EMIT mafwDHMVideoPropertyChanged(); +} + +uint MafwGstRendererDolby::getMusicDolbyState () +{ + qDebug() << __PRETTY_FUNCTION__ << m_currentMusicDolbyState; + return m_currentMusicDolbyState; +} + +int MafwGstRendererDolby::getMusicDolbyRoom () +{ + qDebug() << __PRETTY_FUNCTION__ << m_currentMusicDolbyRoom; + return m_currentMusicDolbyRoom; +} + +int MafwGstRendererDolby::getMusicDolbyColor () +{ + qDebug() << __PRETTY_FUNCTION__ << m_currentMusicDolbyColor; + return m_currentMusicDolbyColor; +} + +uint MafwGstRendererDolby::getVideoDolbyState () +{ + qDebug() << __PRETTY_FUNCTION__ << m_currentVideoDolbyState; + return m_currentVideoDolbyState; +} + +int MafwGstRendererDolby::getVideoDolbyRoom () +{ + qDebug() << __PRETTY_FUNCTION__ << m_currentVideoDolbyRoom; + return m_currentVideoDolbyRoom; +} + +int MafwGstRendererDolby::getVideoDolbyColor () +{ + qDebug() << __PRETTY_FUNCTION__ << m_currentVideoDolbyColor; + return m_currentVideoDolbyColor; +} diff --git a/qmafw-gst-subtitles-renderer/src/MafwGstRendererHaltState.cpp b/qmafw-gst-subtitles-renderer/src/MafwGstRendererHaltState.cpp new file mode 100644 index 0000000..8fbe2ef --- /dev/null +++ b/qmafw-gst-subtitles-renderer/src/MafwGstRendererHaltState.cpp @@ -0,0 +1,161 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#include "MafwGstRendererHaltState.h" + +int MafwGstRendererHaltState::DECAY_TIME = 20; + +/******************************************************************** + * MafwGstRendererHaltState::MafwGstRendererHaltState + ********************************************************************/ +MafwGstRendererHaltState::MafwGstRendererHaltState() + : + QObject(), + m_state(MafwRenderer::Invalid), + m_position(-1) +{ + connect(&m_decayTimer, SIGNAL(timeout()), + this, SIGNAL(decayed())); +} + +/******************************************************************** + * MafwGstRendererHaltState::MafwGstRendererHaltState + ********************************************************************/ +MafwGstRendererHaltState::MafwGstRendererHaltState(const QString &uri, + MafwRenderer::State state, + int position) + : + QObject(), + m_uri(uri), + m_state(state), + m_position(position) +{ + connect(&m_decayTimer, SIGNAL(timeout()), + this, SIGNAL(decayed())); + initializeDecayTimer(); +} + +/******************************************************************** + * MafwGstRendererHaltState::MafwGstRendererHaltState + ********************************************************************/ +MafwGstRendererHaltState::MafwGstRendererHaltState(const MafwGstRendererHaltState &other) + : + QObject() +{ + *this = other; +} + +/******************************************************************** + * MafwGstRendererHaltState::operator = + ********************************************************************/ +MafwGstRendererHaltState& MafwGstRendererHaltState::operator =(const MafwGstRendererHaltState &other) +{ + if( this == &other ) + { + return *this; + } + + this->m_uri = other.m_uri; + this->m_position = other.m_position; + this->m_state = other.m_state; + + initializeDecayTimer(); + + return *this; +} + +/******************************************************************** + * MafwGstRendererHaltState::~MafwGstRendererHaltState + ********************************************************************/ +MafwGstRendererHaltState::~MafwGstRendererHaltState() +{ + +} + +/******************************************************************** + * MafwGstRendererHaltState::isSet + ********************************************************************/ +bool MafwGstRendererHaltState::isSet() const +{ + return ((m_uri.length() > 0) + && (m_state != MafwRenderer::Invalid) + && (m_decayTimer.isActive() || m_state == MafwRenderer::Paused)); +} + +/******************************************************************** + * MafwGstRendererHaltState::setState + ********************************************************************/ +void MafwGstRendererHaltState::clear() +{ + m_uri.clear(); + m_position = -1; + m_state = MafwRenderer::Invalid; + m_decayTimer.stop(); +} + +/******************************************************************** + * MafwGstRendererHaltState::setState + ********************************************************************/ +void MafwGstRendererHaltState::setState(MafwRenderer::State newState) +{ + m_state = newState; + if( newState == MafwRenderer::Paused ) + { + m_decayTimer.stop(); + } +} + +/******************************************************************** + * MafwGstRendererHaltState::uri + ********************************************************************/ +QString MafwGstRendererHaltState::uri() const +{ + return m_uri; +} + +/******************************************************************** + * MafwGstRendererHaltState::state + ********************************************************************/ +MafwRenderer::State MafwGstRendererHaltState::state() const +{ + return m_state; +} + +/******************************************************************** + * MafwGstRendererHaltState::position + ********************************************************************/ +int MafwGstRendererHaltState::position() const +{ + return m_position; +} + +/******************************************************************** + * MafwGstRendererHaltState::initializeDecayTimer + ********************************************************************/ +void MafwGstRendererHaltState::initializeDecayTimer() +{ + if( m_uri.length() > 0 && m_state != MafwRenderer::Invalid ) + { + m_decayTimer.setSingleShot(true); + m_decayTimer.setInterval(DECAY_TIME * 1000); + m_decayTimer.start(); + } + else + { + m_decayTimer.stop(); + } +} diff --git a/qmafw-gst-subtitles-renderer/src/MafwGstRendererNetworkMonitor.cpp b/qmafw-gst-subtitles-renderer/src/MafwGstRendererNetworkMonitor.cpp new file mode 100644 index 0000000..504676d --- /dev/null +++ b/qmafw-gst-subtitles-renderer/src/MafwGstRendererNetworkMonitor.cpp @@ -0,0 +1,60 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#include "MafwGstRendererNetworkMonitor.h" +#include "MafwGstRendererHaltState.h" + +#include +#include + +/******************************************************************** + * MafwGstRendererNetworkMonitor::MafwGstRendererNetworkMonitor + ********************************************************************/ +MafwGstRendererNetworkMonitor::MafwGstRendererNetworkMonitor() + : + m_networkManager(new QNetworkConfigurationManager(this)) +{ + connect(m_networkManager, SIGNAL(configurationChanged(QNetworkConfiguration)), + this, SLOT(handleConfigurationChange(QNetworkConfiguration))); +} + +/******************************************************************** + * MafwGstRendererNetworkMonitor::MafwGstRendererNetworkMonitor + ********************************************************************/ +MafwGstRendererNetworkMonitor::~MafwGstRendererNetworkMonitor() +{ + +} + +/******************************************************************** + * MafwGstRendererNetworkMonitor::handleConfigurationChange + ********************************************************************/ +void MafwGstRendererNetworkMonitor::handleConfigurationChange(const QNetworkConfiguration &config) +{ + qDebug() << __PRETTY_FUNCTION__ << "Configs status: " << config.name() << config.state(); + + QNetworkConfiguration::StateFlags flags = config.state(); + if( flags.testFlag(QNetworkConfiguration::Active) ) + { + Q_EMIT networkChangeFinished(); + m_currentConfiguration = config; + } + else if( !m_currentConfiguration.isValid() || config == m_currentConfiguration ) + { + Q_EMIT prepareNetworkChange(); + } +} diff --git a/qmafw-gst-subtitles-renderer/src/MafwGstRendererPlaylistFileUtility.cpp b/qmafw-gst-subtitles-renderer/src/MafwGstRendererPlaylistFileUtility.cpp new file mode 100644 index 0000000..eb70f10 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/src/MafwGstRendererPlaylistFileUtility.cpp @@ -0,0 +1,165 @@ +#include "MafwGstRendererPlaylistFileUtility.h" +#include +#include +#include +#include + +/******************************************************************** + * MafwGstRendererPlaylistFileUtility::MafwGstRendererPlaylistFileUtility + ********************************************************************/ +MafwGstRendererPlaylistFileUtility::MafwGstRendererPlaylistFileUtility(QObject* parent): + QObject(parent), m_parserId(0), m_firstItem(false) +{ + g_type_init(); +} + +/******************************************************************** + * MafwGstRendererPlaylistFileUtility::MafwGstRendererPlaylistFileUtility + ********************************************************************/ +MafwGstRendererPlaylistFileUtility::~MafwGstRendererPlaylistFileUtility() +{ + qDebug() << __PRETTY_FUNCTION__; +} + +/******************************************************************** + * MafwGstRendererPlaylistFileUtility::getUriList + ********************************************************************/ +QStringList MafwGstRendererPlaylistFileUtility::getUriList() +{ + return m_uriList; +} + +/******************************************************************** + * MafwGstRendererPlaylistFileUtility::playPlaylistFile + ********************************************************************/ +void MafwGstRendererPlaylistFileUtility::parsePlaylistFile(const QUrl& url) +{ + qDebug() << __PRETTY_FUNCTION__; + + if (url.isValid() && !url.scheme().isEmpty()) + { + m_uriList.clear(); + m_parserId = totem_pl_parser_new (); + g_object_set(m_parserId, "recurse", false, "disable-unsafe", + true, NULL); + g_signal_connect(G_OBJECT(m_parserId), "entry-parsed", G_CALLBACK(uriParsed), this); + totem_pl_parser_parse_async(m_parserId, + url.toString().toAscii().constData(), + false, + 0, + GAsyncReadyCallback(readyCb), + (void *)this); + g_object_unref(m_parserId); + m_firstItem = true; + } + else + { + Q_EMIT parsingReady(false); + } +} + +/******************************************************************** + * MafwGstRendererPlaylistFileUtility::takeFirstUri + ********************************************************************/ +QString MafwGstRendererPlaylistFileUtility::takeFirstUri() +{ + if (m_uriList.isEmpty()) + { + qDebug() << __PRETTY_FUNCTION__ << ": there are no more items parsed"; + return QString(); + } + else + { + return m_uriList.takeFirst(); + } +} + +/******************************************************************** + * MafwGstRendererPlaylistFileUtility::setPendingError + ********************************************************************/ +void MafwGstRendererPlaylistFileUtility::setPendingError(MafwError& error) +{ + m_pendingError = error; +} + +/******************************************************************** + * MafwGstRendererPlaylistFileUtility::takePendingError + ********************************************************************/ +MafwError MafwGstRendererPlaylistFileUtility::takePendingError() +{ + MafwError retMe = m_pendingError; + m_pendingError = MafwError(); + return retMe; +} + + +/******************************************************************** + * MafwGstRendererPlaylistFileUtility::uriParsed + ********************************************************************/ +void MafwGstRendererPlaylistFileUtility::uriParsed(TotemPlParser* parser, + gchar* uri, + gpointer /*metadata*/, + MafwGstRendererPlaylistFileUtility* self) +{ + qDebug() << __PRETTY_FUNCTION__ << parser << uri; + + if (uri && (parser == self->m_parserId)) + { + QString modifiedURI = self->manHandleURI(uri); + self->m_uriList.append(modifiedURI); + if (self->m_firstItem) + { + Q_EMIT self->firstItemParsed(); + self->m_firstItem = false; + } + } +} + +/******************************************************************** + * MafwGstRendererPlaylistFileUtility::readyCb + ********************************************************************/ +void MafwGstRendererPlaylistFileUtility::readyCb(TotemPlParser* parser, + GAsyncResult* async_result, + MafwGstRendererPlaylistFileUtility* self) +{ + qDebug() << __PRETTY_FUNCTION__ << parser; + if (parser == self->m_parserId) + { + GError *error = 0; + bool success = true; + + TotemPlParserResult result = totem_pl_parser_parse_finish(parser, + async_result, + &error); + qDebug() << __PRETTY_FUNCTION__ << result; + if (result != TOTEM_PL_PARSER_RESULT_SUCCESS) + { + success = false; + qWarning() << __PRETTY_FUNCTION__ << ": Playlist file parsing failed"; + if (error) + { + qWarning() << error->message; + g_error_free (error); + } + } + //Actually parsing is not ready yet, we might have no results yet. + Q_EMIT self->parsingReady(success); + } +} + +/******************************************************************** + * MafwGstRendererPlaylistFileUtility::manHandleURI + ********************************************************************/ +QString MafwGstRendererPlaylistFileUtility::manHandleURI(const QString &itemUri) const +{ + qDebug() << __FUNCTION__ << "Orig: " << itemUri; + QString modifiedUri = itemUri; + + if( itemUri.endsWith(".asf", Qt::CaseInsensitive) + && itemUri.startsWith("http://", Qt::CaseInsensitive) ) + { + modifiedUri.replace(0,4, "mmsh"); + } + qDebug() << __FUNCTION__ << "Handled: " << modifiedUri; + return modifiedUri; +} diff --git a/qmafw-gst-subtitles-renderer/src/MafwGstRendererPlugin.cpp b/qmafw-gst-subtitles-renderer/src/MafwGstRendererPlugin.cpp new file mode 100644 index 0000000..734ddd9 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/src/MafwGstRendererPlugin.cpp @@ -0,0 +1,145 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#include +#include +#include +#include +#include + +#include + +#include "MafwGstRendererPlugin.h" +#include "MafwGstRenderer.h" + +#ifdef _VERSION_INFO +#include "version.h" +#endif + +const QString PLUGIN_NAME = "MafwGstRendererPlugin"; +const QString RENDERER_UUID = "mafw_gst_renderer"; +const QString DBUS_WRAPPER_NAME = "qmafw-dbus-wrapper"; +const QString RENDERER_PLUGIN_CONFIG_FILE = "/usr/share/qmafw/mafw-gst-renderer-plugin.conf"; + + +MafwGstRendererPlugin::~MafwGstRendererPlugin() +{ + qDebug() << __PRETTY_FUNCTION__; + for(int i = 0; i < m_rendererIds.count(); i++) + { + m_registry->removeExtension(m_rendererIds.at(i)); + } +} + +void MafwGstRendererPlugin::initialize(MafwInternalRegistry* registry) +{ +#ifdef _VERSION_INFO + qDebug() << "mafw-gst-renderer revision:" << revision; + qDebug() << "mafw-gst-renderer library builtime:" << build_time; +#endif + + Q_ASSERT(registry); + + m_registry = registry; + + QString rendererArrayKey; + QString appname = QCoreApplication::applicationName(); + // appname can contain full path to config file + if(appname.endsWith(DBUS_WRAPPER_NAME)) + { + // We are loading out-process renderers from config file + rendererArrayKey = "renderers"; + loadRenderers(rendererArrayKey); + } + else + { + // We are loading in-process renderers from config file + rendererArrayKey = "in-process-renderers"; + loadRenderers(rendererArrayKey); + } + + // if there are no gst-renderers in config file, we create a "basic" gst-renderer + if(m_rendererIds.isEmpty()) + { + MafwGstRenderer *rnd = new MafwGstRenderer(RENDERER_UUID, + PLUGIN_NAME, + "QMAFW GStreamer Renderer", + registry); + + QSettings settings(RENDERER_PLUGIN_CONFIG_FILE, QSettings::NativeFormat); + + if(rnd->initialize(&settings)) + { + m_registry->addRenderer(rnd); + m_rendererIds.append(RENDERER_UUID); + } + else + { + qCritical() << "Failed to initialize QMAFW GStreamer Renderer"; + delete rnd; + } + } +} + +void MafwGstRendererPlugin::loadRenderers(const QString& rendererArrayKey) +{ + QSettings settings(RENDERER_PLUGIN_CONFIG_FILE, QSettings::NativeFormat); + QString id; + QString friendlyname; + + QList rnds; + + // Configuration file contains the array of renderer names and uuids as string. + // beginReadArray returns size of the array. + int size = settings.beginReadArray(rendererArrayKey); + for(int i = 0; i < size; i++) + { + settings.setArrayIndex(i); + id = settings.value("Id").toString(); + friendlyname = settings.value("FriendlyName").toString(); + MafwGstRenderer *rnd = new MafwGstRenderer(id, + PLUGIN_NAME, + friendlyname, + m_registry); + rnds.append(rnd); + } + settings.endArray(); + + Q_FOREACH( MafwGstRenderer *rnd, rnds ) + { + if(rnd->initialize(&settings)) + { + m_registry->addRenderer(rnd); + m_rendererIds.append(rnd->uuid()); + } + else + { + qCritical() << "Failed to initialize" << rnd->name(); + delete rnd; + } + } +} + +QString MafwGstRendererPlugin::name() const +{ + return PLUGIN_NAME; +} + +/***************************************************************************** + * Plugin export + ****************************************************************************/ +Q_EXPORT_PLUGIN2(qmafw-gst-renderer-plugin, MafwGstRendererPlugin) diff --git a/qmafw-gst-subtitles-renderer/src/MafwGstRendererVolume.cpp b/qmafw-gst-subtitles-renderer/src/MafwGstRendererVolume.cpp new file mode 100644 index 0000000..daf3b95 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/src/MafwGstRendererVolume.cpp @@ -0,0 +1,476 @@ +#include "MafwGstRendererVolume.h" + +#include +#include + +#include +#include + +// The default PulseAudioMainVolume DBus API socket +#define DEFAULT_ADDRESS "unix:path=/var/run/pulse/dbus-socket" +#define ROLE = "x-maemo"; + +#define PULSE_CORE_PATH "/org/pulseaudio/core1" +#define STREAM_RESTORE_PATH "/org/pulseaudio/stream_restore1" +#define STREAM_RESTORE_IF "org.PulseAudio.Ext.StreamRestore1" +#define GET_ENTRY_METHOD "GetEntryByName" +#define STREAM_RESTORE_IF_ENTRY STREAM_RESTORE_IF ".RestoreEntry" +#define STREAM_RESTORE_VOLUME_SIGNAL STREAM_RESTORE_IF_ENTRY ".VolumeUpdated" + +#define ENTRY_NAME "sink-input-by-media-role:x-maemo" +//# 100% volume in Pulseaudio's native volume units. +const uint VOLUME_NORM = 0x10000; +//Place of mono channel at pulse audio's channel position enumeration +const uint VOLUME_CHANNEL_MONO = 0; +//Delay for attempting to reconnect to pulseaudio +const uint PULSE_RESTART_DELAY = 2000; + +/******************************************************************** + * MafwGstRendererVolume::MafwGstRendererVolume + ********************************************************************/ +MafwGstRendererVolume::MafwGstRendererVolume(): m_currentVolume(0), m_pendingVolumeValue(0), + m_dbusConnection(0), m_objectPath(QString()), m_pendingCall(0) +{ + qDebug() << __PRETTY_FUNCTION__; + + connectToPulseAudio(); +} + +/******************************************************************** + * MafwGstRendererVolume::connectToPulseAudio + ********************************************************************/ +void MafwGstRendererVolume::connectToPulseAudio() +{ + qDebug() << __PRETTY_FUNCTION__; + DBusError error; + QByteArray address = qgetenv("PULSE_DBUS_SERVER"); + + if (address.isEmpty()) + { + address = QByteArray(DEFAULT_ADDRESS); + } + + dbus_error_init (&error); + + if (m_dbusConnection) + { + DBUSConnectionEventLoop::removeConnection(m_dbusConnection); + dbus_connection_unref(m_dbusConnection); + m_dbusConnection = 0; + } + + m_dbusConnection = dbus_connection_open (address.constData(), &error); + + if (dbus_error_is_set(&error)) + { + qCritical() << "Unable to open dbus connection to pulse audio:" << error.message; + dbus_error_free (&error); + QTimer::singleShot(PULSE_RESTART_DELAY, this, SLOT(connectToPulseAudio())); + } + else + { + if (DBUSConnectionEventLoop::addConnection(m_dbusConnection)) + { + dbus_connection_add_filter ( + m_dbusConnection, + (DBusHandleMessageFunction) handleIncomingMessages, + (void *) this, NULL); + + getRestoreEntryForMediaRole(); + } + else + { + qCritical() << "DBUSConnectionEventLoop failure"; + dbus_connection_unref(m_dbusConnection); + m_dbusConnection = 0; + } + } +} + +/******************************************************************** + * MafwGstRendererVolume::~MafwGstRendererVolume() + ********************************************************************/ +MafwGstRendererVolume::~MafwGstRendererVolume() +{ + if(m_pendingCall) + { + dbus_pending_call_cancel(m_pendingCall); + } + DBUSConnectionEventLoop::removeConnection(m_dbusConnection); + dbus_connection_unref(m_dbusConnection); +} +/******************************************************************** + * We need the object path for the RestoreEntry of the "x-maemo" role. + * GetEntryByName with parameter "sink-input-by-media-role:x-maemo" is used + * for that. + ********************************************************************/ +void MafwGstRendererVolume::getRestoreEntryForMediaRole() +{ + qDebug() << __PRETTY_FUNCTION__; + DBusMessage *msg; + DBusError error; + + dbus_error_init (&error); + + msg = dbus_message_new_method_call(0, + STREAM_RESTORE_PATH, + STREAM_RESTORE_IF, + GET_ENTRY_METHOD); + const char *name = ENTRY_NAME; + dbus_message_append_args (msg, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID); + + + DBusPendingCall *pending = 0; + //Return value taken for satisfying coverity tool + bool outOfMemory = dbus_connection_send_with_reply( m_dbusConnection, msg, &pending, -1 ); + Q_UNUSED(outOfMemory); + m_pendingCall = pending; + + if (pending) + { + qDebug() << __PRETTY_FUNCTION__ << "pending call sent!"; + dbus_pending_call_set_notify( pending, + (DBusPendingCallNotifyFunction) getEntryReply, + (void *)this, + NULL ); + dbus_message_unref(msg); + } + qDebug() << __PRETTY_FUNCTION__ << "exit"; +} + +/******************************************************************** + * Now we have the RestoreEntry object path for "x-maemo". Next we just + * need to start listening for the VolumeUpdated signals from that object, + * and use the Volume property for controlling the role volume. + ********************************************************************/ +void MafwGstRendererVolume::getEntryReply(DBusPendingCall *pending, MafwGstRendererVolume *self) +{ + qDebug() << __PRETTY_FUNCTION__; + self->m_pendingCall = 0; + DBusMessage *reply; + DBusError error; + + dbus_error_init(&error); + + reply = dbus_pending_call_steal_reply(pending); + + if (dbus_set_error_from_message(&error, reply)) + { + qWarning() << "Unable to get volume from pulse audio:" << error.message; + dbus_error_free (&error); + //Try to reconnect + QTimer::singleShot(0, self, SLOT(connectToPulseAudio())); + } + else if (reply && (dbus_message_get_type (reply) == DBUS_MESSAGE_TYPE_METHOD_RETURN)) + { + const char *object_path; + bool argError = dbus_message_get_args(reply, &error, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID); + if (!argError) + { + qWarning() << "Unable to get volume from pulse audio:" << error.message; + dbus_error_free (&error); + //Try to reconnect + QTimer::singleShot(0, self, SLOT(connectToPulseAudio())); + } + + qDebug() << __PRETTY_FUNCTION__ << "Object path: " << object_path; + self->m_objectPath = object_path; + + if (self->m_pendingVolumeValue > 0) + { + qDebug() << __PRETTY_FUNCTION__ << "setting volume to level " << self->m_pendingVolumeValue; + self->listenVolumeSignals(); + self->setVolume(self->m_pendingVolumeValue); + self->m_pendingVolumeValue = 0; + } + else + { + qDebug() << __PRETTY_FUNCTION__ << "getting initial volume level."; + DBusMessage *msg; + DBusError error2; + dbus_error_init (&error2); + + msg = dbus_message_new_method_call(0, + object_path, + DBUS_INTERFACE_PROPERTIES, + "Get"); + const char* interface = STREAM_RESTORE_IF_ENTRY; + const char* property = "Volume"; + + dbus_message_append_args (msg, + DBUS_TYPE_STRING, &interface, + DBUS_TYPE_STRING, &property, + DBUS_TYPE_INVALID); + + DBusPendingCall *pending = 0; + //Return value taken for satisfying coverity tool + bool outOfMemory = dbus_connection_send_with_reply( self->m_dbusConnection, msg, &pending, -1 ); + Q_UNUSED(outOfMemory); + self->m_pendingCall = pending; + if (pending) + { + qDebug() << __PRETTY_FUNCTION__ << "pending call sent!"; + dbus_pending_call_set_notify( pending, + (DBusPendingCallNotifyFunction) volumeReply, + (void *)self, + NULL ); + dbus_message_unref(msg); + } + } + } + dbus_message_unref (reply); +} + +/******************************************************************** + * MafwGstRendererVolume::volumeReply + ********************************************************************/ +void MafwGstRendererVolume::volumeReply(DBusPendingCall *pending, MafwGstRendererVolume *self) +{ + self->m_pendingCall = 0; + Q_UNUSED (self); + qDebug() << __PRETTY_FUNCTION__; + + DBusMessage *reply; + reply = dbus_pending_call_steal_reply(pending); + + DBusError error; + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, reply)) + { + qWarning() << "MafwGstRendererVolume: Unable to get volume from pulse audio:" << error.message; + dbus_error_free (&error); + } + else + { + DBusMessageIter iter, array_iterator; + dbus_message_iter_init (reply, &iter); + if(dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_VARIANT) + { + dbus_message_iter_recurse (&iter, &array_iterator); + } + + if (dbus_message_iter_get_arg_type (&array_iterator) == DBUS_TYPE_ARRAY) + { + self->readVolumeFromStruct(&array_iterator); + Q_EMIT self->volumeChanged(self->m_currentVolume); + } + else + { + qCritical("Unable to get initial volume from Pulse Audio!!"); + } + } + dbus_message_unref (reply); + self->listenVolumeSignals(); +} + +/******************************************************************** + * MafwGstRendererVolume::listenVolumeSignals + ********************************************************************/ +void MafwGstRendererVolume::listenVolumeSignals() +{ + qDebug() << __PRETTY_FUNCTION__; + + DBusMessage *message = 0; + char *signal = (char *) STREAM_RESTORE_VOLUME_SIGNAL; + char **emptyarray = { 0 }; + + message = dbus_message_new_method_call (0, + PULSE_CORE_PATH, + 0, + "ListenForSignal"); + + dbus_message_append_args (message, + DBUS_TYPE_STRING, &signal, + DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &emptyarray, 0, + DBUS_TYPE_INVALID); + + dbus_connection_send (m_dbusConnection, message, NULL); + dbus_connection_flush (m_dbusConnection); + dbus_message_unref (message); +} + +/******************************************************************** + * MafwGstRendererVolume::handleIncomingMessages + ********************************************************************/ +void MafwGstRendererVolume::handleIncomingMessages (DBusConnection* conn, + DBusMessage* message, + MafwGstRendererVolume* self) +{ + qDebug(__PRETTY_FUNCTION__); + Q_UNUSED (conn); + + if (message && + dbus_message_has_member (message, "VolumeUpdated") && + dbus_message_has_interface(message, STREAM_RESTORE_IF_ENTRY) && + dbus_message_has_path(message, self->m_objectPath.toAscii())) + { + qDebug() << "MafwGstRendererVolume: VolumeUpdated signal received."; + DBusError error; + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, message)) + { + qWarning() << "Got volume error from pulse audio:" << error.message; + dbus_error_free (&error); + } + + DBusMessageIter iter; + dbus_message_iter_init (message, &iter); + + if (self->readVolumeFromStruct(&iter)) + { + Q_EMIT self->volumeChanged(self->m_currentVolume); + } + } + //When a connection is disconnected, you are guaranteed to get a signal "Disconnected" from the interface DBUS_INTERFACE_LOCAL, path DBUS_PATH_LOCAL. + else if (message && + dbus_message_has_member (message, "Disconnected") && + dbus_message_get_interface(message) == QString(DBUS_INTERFACE_LOCAL) && + dbus_message_get_path(message) == QString(DBUS_PATH_LOCAL)) + { + qWarning("MafwGstRendererVolume: Connection with pulse audio is disconnected!"); + QTimer::singleShot(PULSE_RESTART_DELAY, self, SLOT(connectToPulseAudio())); + qDebug("MafwGstRendererVolume: Trying to reconnect."); + } + +} + +/******************************************************************** + * There is a dbus variant containing array of structures. Each structure + * contains channels postion and the volume level of this position + ********************************************************************/ +bool MafwGstRendererVolume::readVolumeFromStruct(DBusMessageIter *iterator) +{ + qDebug(__PRETTY_FUNCTION__); + bool volumeChanged = false; + DBusMessageIter struct_iterator; + dbus_message_iter_recurse (iterator, &struct_iterator); + int volume = -1; + + while (dbus_message_iter_get_arg_type(&struct_iterator) == DBUS_TYPE_STRUCT) + { + DBusMessageIter variant; + dbus_message_iter_recurse (&struct_iterator, &variant); + int channel, value = 0; + + if (dbus_message_iter_get_arg_type (&variant) == DBUS_TYPE_UINT32) + { + dbus_message_iter_get_basic (&variant, &channel); + qDebug("Channel %d", channel); + dbus_message_iter_next (&variant); + dbus_message_iter_get_basic (&variant, &value); + + value = qRound((float)value / (float)VOLUME_NORM * 100); + + //We're interested in all the channels that stream-restore happens to give you. + //We have to somehow map between a single volume value and per-channel volume. + //The usual way to do the mapping is just to use the loudest channel as + //the overall volume + if (value > volume) + { + uint newVolume = value; + if (newVolume != m_currentVolume) + { + m_currentVolume = newVolume; + volumeChanged = true; + qDebug("MafwGstRendererVolume: current volume level has changed: %d", m_currentVolume); + } + } + + if (m_currentVolume > 100) //MAFW volume has range 0-100 + { + qWarning("MafwGstRendererVolume: Pulse audio signals volume level which is out-of range!"); + m_currentVolume = 100; + break; + } + } + else + { + qWarning("MafwGstRendererVolume: Invalid volume value from pulse audio!"); + } + dbus_message_iter_next (&struct_iterator); + } + return volumeChanged; +} + +/******************************************************************** + * MafwGstRendererVolume::getVolume + ********************************************************************/ +uint MafwGstRendererVolume::getVolume() +{ + qDebug(__PRETTY_FUNCTION__); + return m_currentVolume; +} + +/******************************************************************** + * Incoming value has range 0-99 pulseaudio uses values 0-VOLUME_NORM + ********************************************************************/ +bool MafwGstRendererVolume::setVolume (uint value) +{ + bool success = true; + qDebug("MafwGstRendererVolume::setVolume (uint %d)", value); + if (m_objectPath.isEmpty()) + { + qDebug() << "MafwGstRendererVolume: Can not set volume yet. Waiting for RestoreEntry object path"; + m_pendingVolumeValue = value; + return success; + } + + const char* interface = STREAM_RESTORE_IF_ENTRY; + const char* property = "Volume"; + if (value > 100) //MAFW volume has range 0-100 + { + qWarning("MafwGstRendererVolume: Trying to set volume level which is out-of range!"); + value = (uint)100; + } + uint nativeValue = (((float)value / (float)100) * VOLUME_NORM); + DBusMessage* message; + message = dbus_message_new_method_call(0, + m_objectPath.toAscii(), + DBUS_INTERFACE_PROPERTIES, + "Set"); + + if ( dbus_message_append_args(message, + DBUS_TYPE_STRING, &interface, + DBUS_TYPE_STRING, &property, + DBUS_TYPE_INVALID)) + { + //Compose the dbus variant containing an array of struct of pair + //of unsigned integers va(uu) + DBusMessageIter argument_iterator, variant_iterator, array_iterator, struct_iterator; + dbus_message_iter_init_append (message, &argument_iterator); + dbus_message_iter_open_container (&argument_iterator, + DBUS_TYPE_VARIANT, + "a(uu)", + &variant_iterator); + dbus_message_iter_open_container (&variant_iterator, + DBUS_TYPE_ARRAY, + "(uu)", + &array_iterator); + dbus_message_iter_open_container (&array_iterator, + DBUS_TYPE_STRUCT, + NULL, + &struct_iterator); + + dbus_message_iter_append_basic (&struct_iterator, DBUS_TYPE_UINT32, &VOLUME_CHANNEL_MONO); + dbus_message_iter_append_basic (&struct_iterator, DBUS_TYPE_UINT32, &nativeValue); + + dbus_message_iter_close_container (&array_iterator, &struct_iterator); + dbus_message_iter_close_container (&variant_iterator, &array_iterator); + dbus_message_iter_close_container (&argument_iterator, &variant_iterator); + + dbus_connection_send (m_dbusConnection, message, NULL); + dbus_connection_flush (m_dbusConnection); + } + else + { + qWarning("Cannot set volume!"); + success = false; + } + dbus_message_unref (message); + + return success; +} diff --git a/qmafw-gst-subtitles-renderer/src/MafwGstScreenshot.cpp b/qmafw-gst-subtitles-renderer/src/MafwGstScreenshot.cpp new file mode 100644 index 0000000..610ab1d --- /dev/null +++ b/qmafw-gst-subtitles-renderer/src/MafwGstScreenshot.cpp @@ -0,0 +1,223 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#include "MafwGstScreenshot.h" +#include + +MafwGstScreenshot::MafwGstScreenshot(QObject *parent) : QObject(parent) +{ + qDebug() << __PRETTY_FUNCTION__; + + m_src = NULL; + m_sink = NULL; + m_pipeline = NULL; + m_filter = NULL; + m_csp = NULL; + m_enc = NULL; + m_bus = NULL; + m_caps = NULL; + m_structure = NULL; + m_handler_id = 0; +} + +MafwGstScreenshot::~MafwGstScreenshot() +{ + qDebug() << __PRETTY_FUNCTION__; + if(m_pipeline) + { + gst_element_set_state(m_pipeline, GST_STATE_NULL); + gst_object_unref(m_pipeline); + } +} + +static void copyBufferToSource(GstElement *src, GstBuffer *buffer, GstPad *pad, + gpointer data) +{ + + Q_UNUSED(src); + Q_UNUSED(pad); + + GstBuffer *in_buf = GST_BUFFER(data); + + memcpy(GST_BUFFER_DATA(buffer), GST_BUFFER_DATA(in_buf), + GST_BUFFER_SIZE(in_buf)); + GST_BUFFER_SIZE(buffer) = GST_BUFFER_SIZE(in_buf); +} + +static void freeSourceBuffer(gpointer data) +{ + GstBuffer *in_buf = GST_BUFFER(data); + gst_buffer_unref(in_buf); +} + +static gboolean asyncBusHandler(GstBus *bus, GstMessage *msg, gpointer data) +{ + Q_UNUSED(bus); + + gboolean ret = TRUE; + + MafwGstScreenshot *self = static_cast(data); + + switch(GST_MESSAGE_TYPE(msg)) + { + case GST_MESSAGE_EOS: + { + ret = self->reportBack(NULL); + break; + } + case GST_MESSAGE_ERROR: + { + GError *error = NULL; + + gst_message_parse_error(msg, &error, NULL); + + ret = self->reportBack(error); + g_error_free(error); + break; + } + default: + break; + } + return ret; +} + +/******************************************************************** + * MafwGstScreenshot::savePauseFrame + ********************************************************************/ +bool MafwGstScreenshot::savePauseFrame(GstBuffer *buffer, const char *filename) +{ + qDebug() << __PRETTY_FUNCTION__; + + gint width = 0; + gint height = 0; + + if(!m_pipeline) + { + m_pipeline = gst_pipeline_new("screenshot-pipeline"); + if(!m_pipeline) + { + goto err_out; + } + m_src = gst_element_factory_make("fakesrc", NULL); + m_sink = gst_element_factory_make("filesink", NULL); + m_filter = gst_element_factory_make("capsfilter", NULL); + m_csp = gst_element_factory_make("ffmpegcolorspace", NULL); + m_enc = gst_element_factory_make("pngenc", NULL); + + if(!m_src || !m_sink || !m_filter || !m_csp || !m_enc) + { + goto err_out; + } + + gst_bin_add_many(GST_BIN(m_pipeline), m_src, m_filter, m_csp, m_enc, m_sink, NULL); + + if(!gst_element_link_many(m_src, m_filter, m_csp, m_enc, m_sink, NULL)) + { + goto err_out; + } + + m_bus = gst_pipeline_get_bus(GST_PIPELINE(m_pipeline)); + gst_bus_add_watch(m_bus, asyncBusHandler, this); + gst_object_unref(m_bus); + + g_object_set(m_sink, "preroll-queue-len", 1, NULL); + g_object_set(m_src, + "sizetype", 2, + "num-buffers", 1, + "signal-handoffs", TRUE, + NULL); + g_object_set(m_enc, "compression-level", 1, NULL); + } + + m_caps = gst_caps_copy(GST_BUFFER_CAPS(buffer)); + if(!m_caps) + { + goto err_out; + } + m_structure = gst_caps_get_structure(m_caps, 0); + gst_structure_remove_field(m_structure, "pixel-aspect-ratio"); + /* limit image size in case it exceeds the pre-determined size */ + if(gst_structure_get_int(m_structure, "width", &width) && + gst_structure_get_int(m_structure, "height", &height)) + { + gst_caps_set_simple(m_caps, + "width", G_TYPE_INT, width, + "height", G_TYPE_INT, height, + NULL); + } + g_object_set(m_filter, "caps", m_caps, NULL); + gst_caps_unref(m_caps); + + g_object_set(m_sink, "location", filename, NULL); + g_object_set(m_src, "sizemax", GST_BUFFER_SIZE(buffer), NULL); + + m_handler_id = g_signal_connect_data(m_src, "handoff", G_CALLBACK(copyBufferToSource), buffer, (GClosureNotify) freeSourceBuffer, G_CONNECT_AFTER); + + gst_element_set_state(m_pipeline, GST_STATE_PLAYING); + + return true; + +err_out: + if( m_pipeline ) + { + gst_object_unref(m_pipeline); + m_pipeline = NULL; + } + if( buffer ) + { + gst_buffer_unref(buffer); + } + + return false; +} + +/******************************************************************** + * MafwGstScreenshot::cancelPauseFrame + ********************************************************************/ +void MafwGstScreenshot::cancelPauseFrame() +{ + qDebug() << __PRETTY_FUNCTION__; + + gst_element_set_state(m_pipeline, GST_STATE_NULL); + g_signal_handler_disconnect(m_src, m_handler_id); + + Q_EMIT screenshotCancelled(); +} + +/******************************************************************** + * MafwGstScreenshot::reportBack + ********************************************************************/ +bool MafwGstScreenshot::reportBack(GError *error) +{ + qDebug() << __PRETTY_FUNCTION__; + if(!error) + { + char *location; + g_object_get(m_sink, "location", &location, NULL); + Q_EMIT screenshotTaken(location, NULL); + g_free(location); + } + else + { + Q_EMIT screenshotTaken(NULL, error); + } + + gst_element_set_state(m_pipeline, GST_STATE_NULL); + g_signal_handler_disconnect(m_src, m_handler_id); + + return true; +} diff --git a/qmafw-gst-subtitles-renderer/src/MafwMmcMonitor.cpp b/qmafw-gst-subtitles-renderer/src/MafwMmcMonitor.cpp new file mode 100644 index 0000000..94fd5b5 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/src/MafwMmcMonitor.cpp @@ -0,0 +1,139 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#include "MafwMmcMonitor.h" +#include +#include + +#include +#include + +const QString MafwMmcMonitor::MMC_URI_PREFIX="file:///home/user/MyDocs"; + +MafwMmcMonitor::MafwMmcMonitor(QObject* parent) : QObject(parent), m_mounted(false) +{ + m_gVolMonitor=g_volume_monitor_get(); + + g_signal_connect(m_gVolMonitor, "mount-removed", + G_CALLBACK(unmountEvent), this); + g_signal_connect(m_gVolMonitor, "mount-added", + G_CALLBACK(mountEvent), this); + + GList* mounts=g_volume_monitor_get_mounts(m_gVolMonitor); + if( mounts ) + { + for( guint i=0; i(userData); + self->m_mounted = false; + } +} + +void MafwMmcMonitor::mountEvent(GVolumeMonitor * mon, + GMount * mount, + gpointer userData) +{ + Q_UNUSED(mon); + + qDebug() << "mountEvent"; + + if( isMyDocs(mount) ) + { + MafwMmcMonitor* self=static_cast(userData); + self->m_mounted = true; + } +} + +bool MafwMmcMonitor::isMyDocs(GMount* mount) +{ + bool isIt=false; + GFile *root=0; + root = g_mount_get_root(mount); + if( root ) + { + char* uri=g_file_get_uri(root); + if( uri && MMC_URI_PREFIX.compare(uri)==0 ) + { + isIt=true; + } + qDebug() << "MafwMmcMonitor::isMyDocs" << uri << isIt; + g_free(uri); + g_object_unref(root); + } + return isIt; +} diff --git a/qmafw-gst-subtitles-renderer/src/mafw-gst-renderer-seeker.c b/qmafw-gst-subtitles-renderer/src/mafw-gst-renderer-seeker.c new file mode 100644 index 0000000..07d7fc6 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/src/mafw-gst-renderer-seeker.c @@ -0,0 +1,201 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#include "mafw-gst-renderer-seeker.h" + +#include + +#define CLOSE_LIMIT 2 +#define MOVE_FORWARD 10 + +typedef struct { + gint64 required_pos; + gint64 starting_pos; + gint64 current_pos; +} seek_request; + +struct _MafwGstRendererSeeker { + GstElement *pipeline; + seek_request last_request; +}; + +gint64 _get_current_pos(GstElement* pipeline) +{ + GstFormat format = GST_FORMAT_TIME; + gint64 time = 0; + + if(pipeline && + gst_element_query_position(pipeline, &format, &time)) + { + return (time + (GST_SECOND/2)) / GST_SECOND; + } + else + { + return -1; + } +} + +gint64 _get_duration(GstElement *pipeline) +{ + gint64 value = -1; + GstFormat format = GST_FORMAT_TIME; + + gboolean success = gst_element_query_duration(pipeline, &format, &value); + if( success ) + { + return (value + (GST_SECOND/2)) / GST_SECOND; + } + else + { + return -1; + } +} + +gboolean _try_seek_required_pos(MafwGstRendererSeeker *seeker) +{ + gboolean ret; + gint64 spos; + GstSeekFlags flags = GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT; + + + spos = (seeker->last_request.required_pos) * GST_SECOND; + g_debug("seek target: %lld", spos); + + ret = gst_element_seek(seeker->pipeline, + 1.0, + GST_FORMAT_TIME, + flags, + GST_SEEK_TYPE_SET, + spos, + GST_SEEK_TYPE_NONE, + GST_CLOCK_TIME_NONE); + + return ret; +} + +gboolean _is_position_close_enough(gint64 pos, gint64 required_pos) +{ + return ABS((pos - required_pos)) < CLOSE_LIMIT; +} + +gboolean _has_position_changed_enough(gint64 pos, gint64 required_pos) +{ + return !_is_position_close_enough(pos, required_pos); +} + +MafwGstRendererSeeker* mafw_gst_renderer_seeker_new() +{ + return g_new0(MafwGstRendererSeeker, 1); +} + +void mafw_gst_renderer_seeker_free(MafwGstRendererSeeker *seeker) +{ + g_free(seeker); +} + +void mafw_gst_renderer_seeker_set_pipeline(MafwGstRendererSeeker *seeker, GstElement *pipeline) +{ + seeker->pipeline = pipeline; + mafw_gst_renderer_seeker_cancel(seeker); +} + +gboolean mafw_gst_renderer_seeker_seek_to(MafwGstRendererSeeker *seeker, gint64 seek_pos) +{ + if( seeker == NULL ) + { + g_critical("Seeker is NULL!"); + return FALSE; + } + + seek_request *request = &(seeker->last_request); + request->required_pos = seek_pos; + request->starting_pos = _get_current_pos(seeker->pipeline); + request->current_pos = request->starting_pos; + + return _try_seek_required_pos(seeker); + +} + +void mafw_gst_renderer_seeker_cancel(MafwGstRendererSeeker *seeker) +{ + seek_request *request = &(seeker->last_request); + request->current_pos = -1; + request->required_pos = -1; + request->starting_pos = -1; +} + +gint64 mafw_gst_renderer_seeker_process(MafwGstRendererSeeker *seeker) +{ + if( seeker == NULL ) + { + g_critical("Seeker is NULL!"); + return -1; + } + + seek_request *request = &(seeker->last_request); + + if(request->required_pos < 0) + { + g_debug("[Seeker] No valid request set! Doing nothing,"); + return request->required_pos; + } + + request->current_pos = _get_current_pos(seeker->pipeline); + if( request->current_pos < 0 ) + { + mafw_gst_renderer_seeker_cancel(seeker); + g_warning("[Seeker] Could not get position! Cannot refine seek!"); + return seeker->last_request.current_pos; + } + + gboolean forward_seek = ( request->required_pos >= request->starting_pos ); + + if( _is_position_close_enough(request->current_pos, request->required_pos) + || + ((_has_position_changed_enough(request->current_pos, request->starting_pos) + && forward_seek + && request->current_pos > request->starting_pos)) ) + { + g_debug("Got good enough seek result: Current pos: %lld, Required pos: %lld", request->current_pos, request->required_pos); + mafw_gst_renderer_seeker_cancel(seeker); + } + else + { + if( forward_seek ) + { + request->required_pos += MOVE_FORWARD; + gint64 duration = _get_duration(seeker->pipeline); + if( request->required_pos > duration ) + { + g_debug("[Seeker] Cancelling increased seek target beyond media length!"); + mafw_gst_renderer_seeker_cancel(seeker); + } + } + else + { + g_debug("Backward seek done, cannot do better..."); + mafw_gst_renderer_seeker_cancel(seeker); + } + } + + if( request->required_pos >= 0 ) + { + _try_seek_required_pos(seeker); + } + + return request->required_pos; +} diff --git a/qmafw-gst-subtitles-renderer/src/mafw-gst-renderer-utils.c b/qmafw-gst-subtitles-renderer/src/mafw-gst-renderer-utils.c new file mode 100644 index 0000000..49364fd --- /dev/null +++ b/qmafw-gst-subtitles-renderer/src/mafw-gst-renderer-utils.c @@ -0,0 +1,324 @@ +/* + * This file is a part of MAFW + * + * Copyright (C) 2007, 2008, 2009 Nokia Corporation, all rights reserved. + * + * Contact: Visa Smolander + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include + +#include + +#include "mafw-gst-renderer-utils.h" + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "mafw-gst-renderer-utils" + +/** + * convert_utf8: + * @src: string. + * @dst: location for utf8 version of @src. + * + * Tries to convert @src into UTF-8, placing it into @dst. + * + * Returns: TRUE on success. + */ +gboolean convert_utf8(const gchar *src, gchar **dst) +{ + GError *error; + + if (!src || !dst) + return FALSE; + if (g_utf8_validate(src, -1, NULL)) { + *dst = g_strdup(src); + return TRUE; + } + error = NULL; + *dst = g_locale_to_utf8(src, -1, NULL, NULL, &error); + if (error) { + g_warning("utf8 conversion failed '%s' (%d: %s)", + src, error->code, error->message); + g_error_free(error); + return FALSE; + } + return TRUE; +} + +/** + * uri_is_stream: + * @uri: the URI to be checked. + * + * Check if given URI is a stream (not a local resource). To not depend on + * gnomevfs for this, we assume everything that doesn't start with "file://" is + * a stream. + * + * Returns: TRUE if the URI is not local. + */ +gboolean uri_is_stream(const gchar *uri) +{ + if (uri == NULL) { + return FALSE; + } else { + return !g_str_has_prefix(uri, "file://"); + } +} + +/** remap_gst_error_code: + * + * @error: pointer to error + * + * Maps a Gst error to worker errror code, + * mapping follows roughly the one from FMAFW gst-renderer. + * + * Returns: possibly remapped ecode if the domain was certain Gst domain. +*/ +gint remap_gst_error_code(const GError *error) +{ + + gint new_err_code; + + if (error->domain == GST_RESOURCE_ERROR) + { + /* handle RESOURCE errors */ + switch (error->code) + { + case GST_RESOURCE_ERROR_READ: + new_err_code = WORKER_ERROR_STREAM_DISCONNECTED; + break; + case GST_RESOURCE_ERROR_NOT_FOUND: + case GST_RESOURCE_ERROR_OPEN_READ_WRITE: + case GST_RESOURCE_ERROR_OPEN_READ: + new_err_code = WORKER_ERROR_MEDIA_NOT_FOUND; + break; + case GST_RESOURCE_ERROR_NO_SPACE_LEFT: + new_err_code = WORKER_ERROR_UNABLE_TO_PERFORM; + break; + case GST_RESOURCE_ERROR_WRITE: + /* DSP renderers send ERROR_WRITE when they find + corrupted data */ + new_err_code = WORKER_ERROR_CORRUPTED_FILE; + break; + case GST_RESOURCE_ERROR_SEEK: + new_err_code = WORKER_ERROR_CANNOT_SET_POSITION; + break; + default: + /* Unknown RESOURCE error */ + new_err_code = WORKER_ERROR_UNABLE_TO_PERFORM; + break; + } + } + else if (error->domain == GST_STREAM_ERROR) + { + /* handle STREAM errors */ + switch (error->code) { + case GST_STREAM_ERROR_TYPE_NOT_FOUND: + new_err_code = WORKER_ERROR_TYPE_NOT_AVAILABLE; + break; + case GST_STREAM_ERROR_FORMAT: + case GST_STREAM_ERROR_WRONG_TYPE: + case GST_STREAM_ERROR_FAILED: + new_err_code = WORKER_ERROR_UNSUPPORTED_TYPE; + break; + case GST_STREAM_ERROR_DECODE: + case GST_STREAM_ERROR_DEMUX: + new_err_code = WORKER_ERROR_CORRUPTED_FILE; + break; + case GST_STREAM_ERROR_CODEC_NOT_FOUND: + new_err_code = WORKER_ERROR_CODEC_NOT_FOUND; + break; + case GST_STREAM_ERROR_DECRYPT: + case GST_STREAM_ERROR_DECRYPT_NOKEY: + new_err_code = WORKER_ERROR_DRM_NOT_ALLOWED; + break; + case WORKER_ERROR_POSSIBLY_PLAYLIST_TYPE: + new_err_code = WORKER_ERROR_POSSIBLY_PLAYLIST_TYPE; + break; + default: + /* Unknown STREAM error */ + new_err_code = WORKER_ERROR_UNABLE_TO_PERFORM; + break; + } + } + else if( error->domain == GST_CORE_ERROR ) + { + switch( error->code ) { + case GST_CORE_ERROR_MISSING_PLUGIN: + new_err_code = WORKER_ERROR_UNSUPPORTED_TYPE; + break; + default: + new_err_code = error->code; + } + } + else + { + /* by default return the original code */ + new_err_code = error->code; + } + + return new_err_code; + +} + +/* + * Imported from totem-uri.c + * Copyright (C) 2004 Bastien Nocera + */ + +/* List from xine-lib's demux_sputext.c */ +static const char subtitle_ext[][4] = { + "sub", + "srt", + "smi", + "ssa", + "ass", + "asc" +}; + +static inline gboolean +uri_exists (const char *uri) +{ + GFile *file = g_file_new_for_uri (uri); + if (file != NULL) { + if (g_file_query_exists (file, NULL)) { + g_object_unref (file); + return TRUE; + } + g_object_unref (file); + } + return FALSE; +} + +static char * +uri_get_subtitle_for_uri (const char *uri) +{ + char *subtitle; + guint len, i; + gint suffix; + + /* Find the filename suffix delimiter */ + len = strlen (uri); + for (suffix = len - 1; suffix > 0; suffix--) { + if (uri[suffix] == G_DIR_SEPARATOR || + (uri[suffix] == '/')) { + /* This filename has no extension; we'll need to + * add one */ + suffix = len; + break; + } + if (uri[suffix] == '.') { + /* Found our extension marker */ + break; + } + } + if (suffix < 0) + return NULL; + + /* Generate a subtitle string with room at the end to store the + * 3 character extensions for which we want to search */ + subtitle = g_malloc0 (suffix + 4 + 1); + g_return_val_if_fail (subtitle != NULL, NULL); + g_strlcpy (subtitle, uri, suffix + 4 + 1); + g_strlcpy (subtitle + suffix, ".???", 5); + + /* Search for any files with one of our known subtitle extensions */ + for (i = 0; i < G_N_ELEMENTS (subtitle_ext) ; i++) { + char *subtitle_ext_upper; + memcpy (subtitle + suffix + 1, subtitle_ext[i], 3); + + if (uri_exists (subtitle)) + return subtitle; + + /* Check with upper-cased extension */ + subtitle_ext_upper = g_ascii_strup (subtitle_ext[i], -1); + memcpy (subtitle + suffix + 1, subtitle_ext_upper, 3); + g_free (subtitle_ext_upper); + + if (uri_exists (subtitle)) + return subtitle; + } + g_free (subtitle); + return NULL; +} + +static char * +uri_get_subtitle_in_subdir (GFile *file, const char *subdir) +{ + char *filename, *subtitle, *full_path_str; + GFile *parent, *full_path, *directory; + + /* Get the sibling directory @subdir of the file @file */ + parent = g_file_get_parent (file); + directory = g_file_get_child (parent, subdir); + g_object_unref (parent); + + /* Get the file of the same name as @file in the @subdir directory */ + filename = g_file_get_basename (file); + full_path = g_file_get_child (directory, filename); + g_object_unref (directory); + g_free (filename); + + /* Get the subtitles from that URI */ + full_path_str = g_file_get_uri (full_path); + g_object_unref (full_path); + subtitle = uri_get_subtitle_for_uri (full_path_str); + g_free (full_path_str); + + return subtitle; +} + +char * +uri_get_subtitle_uri (const char *uri) +{ + GFile *file; + char *subtitle; + + if (g_str_has_prefix (uri, "http") != FALSE) + return NULL; + + /* Has the user specified a subtitle file manually? */ + if (strstr (uri, "#subtitle:") != NULL) + return NULL; + + /* Does the file exist? */ + file = g_file_new_for_uri (uri); + if (g_file_query_exists (file, NULL) != TRUE) { + g_object_unref (file); + return NULL; + } + + /* Try in the current directory */ + subtitle = uri_get_subtitle_for_uri (uri); + if (subtitle != NULL) { + g_object_unref (file); + return subtitle; + } + + subtitle = uri_get_subtitle_in_subdir (file, "subtitles"); + g_object_unref (file); + + return subtitle; +} diff --git a/qmafw-gst-subtitles-renderer/src/mafw-gst-renderer-worker.c b/qmafw-gst-subtitles-renderer/src/mafw-gst-renderer-worker.c new file mode 100644 index 0000000..eeceb39 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/src/mafw-gst-renderer-worker.c @@ -0,0 +1,3105 @@ +/* + * This file is a part of MAFW + * + * Copyright (C) 2007, 2008, 2009 Nokia Corporation, all rights reserved. + * + * Contact: Visa Smolander + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mafw-gst-renderer-worker.h" +#include "mafw-gst-renderer-utils.h" + +#define UNUSED(x) (void)(x) + +/* context provider DBus name must be the same as the .context file name without + * the .context suffix, service name in the .context file must be the same too */ +#define CONTEXT_PROVIDER_BUS_NAME "com.nokia.mafw.context_provider.libqmafw_gst_renderer" +#define CONTEXT_PROVIDER_KEY_NOWPLAYING "Media.NowPlaying" +#define CONTEXT_PROVIDER_KEY_NOWPLAYING_TITLE "title" +#define CONTEXT_PROVIDER_KEY_NOWPLAYING_ALBUM "album" +#define CONTEXT_PROVIDER_KEY_NOWPLAYING_ARTIST "artist" +#define CONTEXT_PROVIDER_KEY_NOWPLAYING_GENRE "genre" +#define CONTEXT_PROVIDER_KEY_NOWPLAYING_RESOURCE "resource" +#define CONTEXT_PROVIDER_KEY_NOWPLAYING_DURATION "duration" + +#define WORKER_ERROR g_quark_from_static_string("com.nokia.mafw.error.renderer") + +#define MAFW_GST_RENDERER_WORKER_DURATION_AND_SEEKABILITY_LAZY_TIMEOUT 4000 +#define MAFW_GST_RENDERER_WORKER_DURATION_AND_SEEKABILITY_FAST_TIMEOUT 200 +#define MAFW_GST_RENDERER_WORKER_DURATION_AND_SEEKABILITY_LOOP_LIMIT 10 +#define MAFW_GST_MISSING_TYPE_DECODER "decoder" +#define MAFW_GST_MISSING_TYPE_ENCODER "encoder" + +#define MAFW_TMP_URI_LEN 2048 + +#define STREAM_TYPE_MMS "mms://" +#define STREAM_TYPE_MMSH "mmsh://" +#define MAFW_GST_MMSH_CONNECTION_SPEED "2000" /* kbit/s */ +#define MAFW_GST_MMSH_TCP_TIMEOUT "30000000" /* microseconds */ + +/* struct needed when emitting renderer art/frames as image files */ +typedef struct { + MafwGstRendererWorker *worker; + gint metadata_key; + const gchar *filename; +} SaveGraphicData; + +/* Forward declarations. */ +static void _do_play(MafwGstRendererWorker *worker); + +static void _do_seek(MafwGstRendererWorker *worker, + GstSeekType seek_type, + gint position, + gboolean key_frame_seek, + GError **error); + +static gboolean _set_value(GValue *v, GType type, gconstpointer value); + +static void _emit_metadatas(MafwGstRendererWorker *worker); + +static gboolean _current_metadata_add(MafwGstRendererWorker *worker, + const gint key, + GType type, + const gpointer value); + +static gpointer _set_context_map_value(gpointer map, + const gchar *tag, + const gchar *value); + +/* + * Is used to prevent a critical log from context fw in case of multiple initialisations. + * Common to all renderers in the process. + */ +static gboolean _context_fw_initialised = FALSE; + +/* + * Sends @error to MafwGstRenderer. Only call this from the glib main thread, + * or face the consequences. @err is free'd. + */ +static void _send_error(MafwGstRendererWorker *worker, GError *err) +{ + worker->is_error = TRUE; + if (worker->notify_error_handler) + { + /* remap a possible gst ecode to worker ecode */ + err->code = remap_gst_error_code(err); + worker->notify_error_handler(worker, worker->owner, err); + } + g_error_free(err); +} + +configuration* _create_default_configuration() +{ + configuration *config = g_malloc0(sizeof(configuration)); + config->asink = g_strdup("pulsesink"); + config->vsink = g_strdup("omapxvsink"); + config->flags = 71; + config->buffer_time = 600000; /* microseconds */ + config->latency_time = 100000; /* microseconds */ + config->autoload_subtitles = TRUE; + config->subtitle_encoding = NULL; + config->subtitle_font = g_strdup("Sans Bold 18"); + + /* timers */ + config->milliseconds_to_pause_frame = 700; /* milliseconds */ + config->seconds_to_pause_to_ready = 3; /* seconds */ + + /* dhmmixer */ + config->use_dhmmixer = TRUE; + + config->mobile_surround_music.state = 0; + config->mobile_surround_music.room = 2; + config->mobile_surround_music.color = 2; + config->mobile_surround_video.state = 0; + config->mobile_surround_video.room = 2; + config->mobile_surround_video.color = 2; + + return config; +} + +void _free_configuration(configuration* config) +{ + g_free(config->asink); + g_free(config->vsink); + + g_free(config); +} + +/* + * Posts an @error on the gst bus. _async_bus_handler will then pick it up and + * forward to MafwGstRenderer. @err is free'd. + */ +static void _post_error(MafwGstRendererWorker *worker, GError *err) +{ + gst_bus_post(worker->bus, + gst_message_new_error(GST_OBJECT(worker->pipeline), + err, + NULL)); + g_error_free(err); +} + +static gboolean _set_value(GValue *v, GType type, gconstpointer value) +{ + + gboolean ret = TRUE; + + if (v && value) + { + memset(v, 0, sizeof(GValue)); + g_value_init(v, type); + + if (type == G_TYPE_STRING) { + g_value_set_string(v, (const gchar*)value); + } + else if (type == G_TYPE_INT) { + g_value_set_int(v, *(gint*)value); + } + else if (type == G_TYPE_UINT) { + g_value_set_uint(v, *(uint*)value); + } + else if (type == G_TYPE_DOUBLE) { + g_value_set_double(v, *(gdouble*)value); + } + else if (type == G_TYPE_BOOLEAN) { + g_value_set_boolean(v, *(gboolean*)value); + } + else if (type == G_TYPE_INT64) { + g_value_set_int64(v, *(gint64*)value); + } + else if (type == G_TYPE_FLOAT) { + g_value_set_float(v, *(gfloat*)value); + } + else if (type == G_TYPE_VALUE_ARRAY) { + g_value_copy((GValue*)value,v); + } + else { + g_warning("%s: unknown g_type", G_STRFUNC); + ret = FALSE; + } + } + else + { + ret = FALSE; + } + + return ret; + +} + +static void _emit_metadata(MafwGstRendererWorker *worker, + gint metadata_key, + GType type, + gconstpointer value) +{ + + GValue v; + + if (worker && worker->notify_metadata_handler && + _set_value(&v, type, value)) + { + GValueArray *array = g_value_array_new(0); + g_value_array_append(array, &v); + worker->notify_metadata_handler(worker, + worker->owner, + metadata_key, + G_TYPE_VALUE_ARRAY, + array); + g_value_array_free(array); + g_value_unset(&v); + } + +} + +static void _emit_property(MafwGstRendererWorker *worker, + gint property, + GType type, + gconstpointer value) +{ + + GValue v; + + if (worker && worker->notify_property_handler && + _set_value(&v, type, value)) + { + worker->notify_property_handler(worker, worker->owner, property, &v); + g_value_unset(&v); + } + +} + +static gchar *_init_tmp_file(void) +{ + gint fd; + gchar *path = NULL; + + fd = g_file_open_tmp("mafw-gst-renderer-XXXXXX.picture", &path, NULL); + if (fd >= 0 ) + { + close(fd); + } + + return path; +} + +static void _destroy_tmp_file(MafwGstRendererWorker *worker, guint index) +{ + g_unlink(worker->tmp_files_pool[index]); + g_free(worker->tmp_files_pool[index]); + worker->tmp_files_pool[index] = NULL; +} + +static void _init_tmp_files_pool(MafwGstRendererWorker *worker) +{ + guint8 i; + + worker->tmp_files_pool_index = 0; + + for (i = 0; i < MAFW_GST_RENDERER_MAX_TMP_FILES; i++) { + worker->tmp_files_pool[i] = NULL; + } +} + +static void _destroy_tmp_files_pool(MafwGstRendererWorker *worker) +{ + guint8 i; + + for (i = 0; (i < MAFW_GST_RENDERER_MAX_TMP_FILES) && + (worker->tmp_files_pool[i] != NULL); i++) { + g_unlink(worker->tmp_files_pool[i]); + g_free(worker->tmp_files_pool[i]); + } +} + +static const gchar *_get_tmp_file_from_pool(MafwGstRendererWorker *worker) +{ + gchar *path = worker->tmp_files_pool[worker->tmp_files_pool_index]; + + if (path == NULL) { + path = _init_tmp_file(); + worker->tmp_files_pool[worker->tmp_files_pool_index] = path; + } + else + { + _destroy_tmp_file(worker, worker->tmp_files_pool_index); + path = _init_tmp_file(); + worker->tmp_files_pool[worker->tmp_files_pool_index] = path; + } + + if (++(worker->tmp_files_pool_index) >= MAFW_GST_RENDERER_MAX_TMP_FILES) { + worker->tmp_files_pool_index = 0; + } + + return path; +} + +static void _emit_gst_buffer_as_graphic_file_cb(GError *error, + gpointer user_data) +{ + SaveGraphicData *sgd = user_data; + + if (error == NULL) { + /* Add the info to the current metadata. */ + _current_metadata_add(sgd->worker, + sgd->metadata_key, + G_TYPE_STRING, + (const gpointer)sgd->filename); + + /* Emit the metadata. */ + _emit_metadata(sgd->worker, + sgd->metadata_key, + G_TYPE_STRING, + sgd->filename); + } + else + { + g_warning("could not emit graphic file: %s", error->message); + } + + g_free(sgd); +} + +static void _emit_gst_buffer_as_graphic_file(MafwGstRendererWorker *worker, + GstBuffer *buffer, + const gint metadata_key) +{ + GstStructure *structure; + const gchar *mime = NULL; + GError *error = NULL; + SaveGraphicData *sgd; + + g_return_if_fail((buffer != NULL) && GST_IS_BUFFER(buffer)); + + structure = gst_caps_get_structure(GST_BUFFER_CAPS(buffer), 0); + mime = gst_structure_get_name(structure); + + /* video pause frame related branch */ + if (g_str_has_prefix(mime, "video/x-raw")) { + const gchar *filename = _get_tmp_file_from_pool(worker); + + if(worker->taking_screenshot) + { + worker->screenshot_handler(worker, worker->owner, NULL, NULL, TRUE); + } + worker->taking_screenshot = TRUE; + worker->screenshot_handler(worker, worker->owner, buffer, filename, FALSE); + + /* gst image tag related branch */ + } else if (g_str_has_prefix(mime, "image/")) { + + sgd = g_new0(SaveGraphicData, 1); + sgd->worker = worker; + sgd->metadata_key = metadata_key; + sgd->filename = _get_tmp_file_from_pool(worker); + + g_debug("dumping gst image %s directly to a file", mime); + g_file_set_contents(sgd->filename, + (const gchar*)GST_BUFFER_DATA(buffer), + GST_BUFFER_SIZE(buffer), + &error); + _emit_gst_buffer_as_graphic_file_cb(error, sgd); + if (error) { + g_error_free(error); + } + } else { + g_warning("Mime type not supported, will not create a thumbnail"); + gst_buffer_unref(buffer); + } +} + +static gboolean _go_to_gst_ready(gpointer user_data) +{ + g_debug("_go_to_gst_ready"); + MafwGstRendererWorker *worker = user_data; + + g_return_val_if_fail(worker->state == GST_STATE_PAUSED || + worker->prerolling, FALSE); + + worker->seek_position = mafw_gst_renderer_worker_get_position(worker); + + g_debug("going to GST_STATE_READY"); + gst_element_set_state(worker->pipeline, GST_STATE_READY); + worker->in_ready = TRUE; + return FALSE; +} + +static void _add_ready_timeout(MafwGstRendererWorker *worker) +{ + if( worker->ready_timeout == 0 ) + { + g_debug("Adding timeout to go to GST_STATE_READY"); + worker->ready_timeout = + g_timeout_add_seconds( + worker->config->seconds_to_pause_to_ready, + _go_to_gst_ready, + worker); + } +} + +static void _remove_ready_timeout(MafwGstRendererWorker *worker) +{ + if( worker->ready_timeout != 0 ) + { + g_debug("removing timeout for READY"); + g_source_remove(worker->ready_timeout); + worker->ready_timeout = 0; + } +} + +static gboolean _take_pause_frame(gpointer user_data) +{ + MafwGstRendererWorker *worker = user_data; + + if( worker->pause_frame_taken && worker->pause_frame_buffer ) + { + gst_buffer_unref(worker->pause_frame_buffer); + worker->pause_frame_buffer = NULL; + return FALSE; + } + + if (worker->pause_frame_buffer != NULL) { + worker->pause_frame_taken = TRUE; + _emit_gst_buffer_as_graphic_file( + worker, + worker->pause_frame_buffer, + WORKER_METADATA_KEY_PAUSED_THUMBNAIL_URI); + worker->pause_frame_buffer = NULL; + } + return FALSE; +} + +static void _add_pause_frame_timeout(MafwGstRendererWorker *worker) +{ + if (worker->media.has_visual_content && worker->current_frame_on_pause && worker->seek_position == -1) + { + if (!worker->pause_frame_timeout) + { + GstBuffer *buffer = NULL; + g_object_get(worker->pipeline, "frame", &buffer, NULL); + if( buffer ) + { + GstBuffer *copy = gst_buffer_copy(buffer); + gst_buffer_copy_metadata(copy, buffer, GST_BUFFER_COPY_ALL); + worker->pause_frame_buffer = copy; + gst_buffer_unref(buffer); + + g_debug("Adding timeout to go to current frame capture"); + worker->pause_frame_timeout = + g_timeout_add_full( + G_PRIORITY_DEFAULT, + worker->config->milliseconds_to_pause_frame, + _take_pause_frame, + worker, NULL); + } + else + { + g_warning("MafwGstRenderer Worker: Could not get buffer from pipeline! Maybe at EOS?"); + } + } + } else { + g_debug("Not adding timeout to take pause frame."); + worker->pause_frame_timeout = 0; + } +} + +static void _remove_pause_frame_timeout(MafwGstRendererWorker *worker) +{ + if (worker->pause_frame_timeout != 0) { + g_debug("removing timeout for pause frame!"); + g_source_remove(worker->pause_frame_timeout); + worker->pause_frame_timeout = 0; + } + + if(worker->taking_screenshot) + { + worker->screenshot_handler(worker, worker->owner, NULL, NULL, TRUE); + worker->taking_screenshot = FALSE; + } + else + { + /* in this case the buffer has not been given to the + * screenshot component to be processed */ + if(worker->pause_frame_buffer) + { + gst_buffer_unref(worker->pause_frame_buffer); + worker->pause_frame_buffer = NULL; + } + } +} + +static gboolean _emit_video_info(MafwGstRendererWorker *worker) +{ + + _emit_metadata(worker, + WORKER_METADATA_KEY_RES_X, + G_TYPE_INT, + &worker->media.video_width); + + _emit_metadata(worker, + WORKER_METADATA_KEY_RES_Y, + G_TYPE_INT, + &worker->media.video_height); + + _emit_metadata(worker, + WORKER_METADATA_KEY_VIDEO_FRAMERATE, + G_TYPE_DOUBLE, + &worker->media.fps); + + return FALSE; + +} + +/* + * Checks if the video details are supported. It also extracts other useful + * information (such as PAR and framerate) from the caps, if available. NOTE: + * this will be called from a different thread than glib's mainloop (when + * invoked via _stream_info_cb); don't call MafwGstRenderer directly. + * + * Returns: TRUE if video details are acceptable. + */ +static gboolean _handle_video_info(MafwGstRendererWorker *worker, + const GstStructure *structure) +{ + gint width, height; + gdouble fps; + + width = height = 0; + gst_structure_get_int(structure, "width", &width); + gst_structure_get_int(structure, "height", &height); + g_debug("video size: %d x %d", width, height); + if (gst_structure_has_field(structure, "pixel-aspect-ratio")) + { + gst_structure_get_fraction(structure, + "pixel-aspect-ratio", + &worker->media.par_n, + &worker->media.par_d); + g_debug("video PAR: %d:%d", worker->media.par_n, worker->media.par_d); + width = width * worker->media.par_n / worker->media.par_d; + } + + fps = 1.0; + if (gst_structure_has_field(structure, "framerate")) + { + gint fps_n, fps_d; + + gst_structure_get_fraction(structure, "framerate", &fps_n, &fps_d); + if (fps_d > 0) { + fps = (gdouble)fps_n / (gdouble)fps_d; + } + g_debug("video fps: %f", fps); + } + + worker->media.video_width = width; + worker->media.video_height = height; + worker->media.fps = fps; + + _current_metadata_add(worker, WORKER_METADATA_KEY_RES_X, G_TYPE_INT, + (const gpointer)&width); + _current_metadata_add(worker, WORKER_METADATA_KEY_RES_Y, G_TYPE_INT, + (const gpointer)&height); + _current_metadata_add(worker, WORKER_METADATA_KEY_VIDEO_FRAMERATE, + G_TYPE_DOUBLE, (const gpointer)&fps); + + /* Emit the metadata.*/ + g_idle_add((GSourceFunc)_emit_video_info, worker); + return TRUE; +} + +static void _parse_stream_info_item(MafwGstRendererWorker *worker, GObject *obj) +{ + GParamSpec *pspec; + GEnumValue *val; + gint type; + + g_object_get(obj, "type", &type, NULL); + pspec = g_object_class_find_property(G_OBJECT_GET_CLASS(obj), "type"); + if(!pspec) + return; + val = g_enum_get_value(G_PARAM_SPEC_ENUM(pspec)->enum_class, type); + if (!val) + return; + if (!g_ascii_strcasecmp(val->value_nick, "video") || + !g_ascii_strcasecmp(val->value_name, "video")) + { + GstCaps *vcaps; + GstObject *object; + + object = NULL; + g_object_get(obj, "object", &object, NULL); + vcaps = NULL; + if (object) { + vcaps = gst_pad_get_caps(GST_PAD_CAST(object)); + } else { + g_object_get(obj, "caps", &vcaps, NULL); + gst_caps_ref(vcaps); + } + if (vcaps) { + if (gst_caps_is_fixed(vcaps)) + { + _handle_video_info(worker, gst_caps_get_structure(vcaps, 0)); + } + gst_caps_unref(vcaps); + } + } +} + +/* It always returns FALSE, because it is used as an idle callback as well. */ +static gboolean _parse_stream_info(MafwGstRendererWorker *worker) +{ + GList *stream_info, *s; + + stream_info = NULL; + if (g_object_class_find_property(G_OBJECT_GET_CLASS(worker->pipeline), + "stream-info")) + { + g_object_get(worker->pipeline, "stream-info", &stream_info, NULL); + } + for (s = stream_info; s; s = g_list_next(s)) + _parse_stream_info_item(worker, G_OBJECT(s->data)); + return FALSE; +} + +static void mafw_gst_renderer_worker_apply_xid(MafwGstRendererWorker *worker) +{ + /* Set sink to render on the provided XID if we have do have + a XID a valid video sink and we are rendering video content */ + if (worker->xid && + worker->vsink && + worker->media.has_visual_content) + { + g_debug ("Setting overlay, window id: %x", (gint) worker->xid); + gst_x_overlay_set_xwindow_id(GST_X_OVERLAY(worker->vsink), worker->xid); + + + /* Ask the gst to redraw the frame if we are paused */ + /* TODO: in MTG this works only in non-fs -> fs way. */ + if (worker->state == GST_STATE_PAUSED) + { + gst_x_overlay_expose(GST_X_OVERLAY(worker->vsink)); + } + } else { + g_debug("Not setting overlay for window id: %x", (gint) worker->xid); + } +} + +static void mafw_gst_renderer_worker_apply_render_rectangle(MafwGstRendererWorker *worker) +{ + /* Set sink to render on the provided XID if we have do have + a XID a valid video sink and we are rendering video content */ + if (worker->xid && + worker->vsink && + worker->media.has_visual_content + && + (worker->x_overlay_rectangle.x >= 0 && + worker->x_overlay_rectangle.y >= 0 && + worker->x_overlay_rectangle.width >= 0 && + worker->x_overlay_rectangle.height >= 0) ) + { + g_debug("Applying render rectangle: X:%d,Y:%d Width:%d, Height:%d", + worker->x_overlay_rectangle.x, + worker->x_overlay_rectangle.y, + worker->x_overlay_rectangle.width, + worker->x_overlay_rectangle.height); + + gst_x_overlay_set_render_rectangle(GST_X_OVERLAY(worker->vsink), + worker->x_overlay_rectangle.x, + worker->x_overlay_rectangle.y, + worker->x_overlay_rectangle.width, + worker->x_overlay_rectangle.height); + /* Ask the gst to redraw the frame if we are paused */ + /* TODO: in MTG this works only in non-fs -> fs way. */ + if (worker->state == GST_STATE_PAUSED) + { + gst_x_overlay_expose(GST_X_OVERLAY(worker->vsink)); + } + + } else { + g_debug("Not setting render rectangle for window id: %x", (gint) worker->xid); + } +} + +/* + * GstBus synchronous message handler. NOTE that this handler is NOT invoked + * from the glib thread, so be careful what you do here. + */ +static GstBusSyncReply _sync_bus_handler(GstBus *bus, + GstMessage *msg, + MafwGstRendererWorker *worker) +{ + + UNUSED(bus); + + if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_ELEMENT && + gst_structure_has_name(msg->structure, "prepare-xwindow-id")) + { + g_debug("got prepare-xwindow-id"); + worker->media.has_visual_content = TRUE; + set_dolby_video_property(worker, worker->config->mobile_surround_video.state); + set_dolby_video_sound_property(worker, worker->config->mobile_surround_video.room, TRUE); + set_dolby_video_sound_property(worker, worker->config->mobile_surround_video.color, FALSE); + /* The user has to preset the XID, we don't create windows by + * ourselves. */ + if (!worker->xid || !worker->vsink) { + /* We must post an error message to the bus that will be picked up + * by _async_bus_handler. Calling the notification function + * directly from here (different thread) is not healthy. */ + g_warning("No video window or video-sink set!"); + _post_error(worker, + g_error_new_literal(WORKER_ERROR, + WORKER_ERROR_PLAYBACK, + "No video window XID or video-sink set")); + gst_message_unref (msg); + return GST_BUS_DROP; + } else { + g_debug ("Video window to use is: %x", (gint)worker->xid); + } + + /* Instruct vsink to use the client-provided window */ + mafw_gst_renderer_worker_apply_xid(worker); + /* Instruct vsink to use the required render rectangle */ + mafw_gst_renderer_worker_apply_render_rectangle(worker); + + /* Handle colorkey and autopaint */ + mafw_gst_renderer_worker_set_autopaint(worker, worker->autopaint); + g_object_get(worker->vsink, "colorkey", &worker->colorkey, NULL); + /* Defer the signal emission to the thread running the mainloop. */ + if (worker->colorkey != -1) { + gst_bus_post(worker->bus, + gst_message_new_application( + GST_OBJECT(worker->vsink), + gst_structure_empty_new("ckey"))); + } + gst_message_unref (msg); + return GST_BUS_DROP; + } + /* do not unref message when returning PASS */ + return GST_BUS_PASS; +} + +static void _free_taglist_item(GstMessage *msg, gpointer data) +{ + UNUSED(data); + + gst_message_unref(msg); +} + +static void _free_taglist(MafwGstRendererWorker *worker) +{ + if (worker->tag_list != NULL) + { + g_ptr_array_foreach(worker->tag_list, (GFunc)_free_taglist_item, NULL); + g_ptr_array_free(worker->tag_list, TRUE); + worker->tag_list = NULL; + } +} + +static gboolean _seconds_duration_equal(gint64 duration1, gint64 duration2) +{ + gint64 duration1_seconds, duration2_seconds; + + duration1_seconds = duration1 / GST_SECOND; + duration2_seconds = duration2 / GST_SECOND; + + return duration1_seconds == duration2_seconds; +} + +static void _check_duration(MafwGstRendererWorker *worker, gint64 value) +{ + gboolean right_query = TRUE; + + if (value == -1) { + GstFormat format = GST_FORMAT_TIME; + right_query = + gst_element_query_duration(worker->pipeline, &format, &value); + } + + if (right_query && value > 0 + && !_seconds_duration_equal(worker->media.length_nanos, value)) + { + gint64 duration = (value + (GST_SECOND/2)) / GST_SECOND; + + /* Add the duration to the current metadata. */ + if( _current_metadata_add(worker, + WORKER_METADATA_KEY_DURATION, + G_TYPE_INT64, + (const gpointer)&duration) ) + { + _emit_metadata(worker, + WORKER_METADATA_KEY_DURATION, + G_TYPE_INT64, + &duration); + } + + /* Publish to context FW */ + if( worker->context_nowplaying == NULL ) + { + worker->context_nowplaying = context_provider_map_new(); + } + context_provider_map_set_integer(worker->context_nowplaying, + CONTEXT_PROVIDER_KEY_NOWPLAYING_DURATION, + duration); + context_provider_set_map(CONTEXT_PROVIDER_KEY_NOWPLAYING, + worker->context_nowplaying, FALSE); + /* end of publishing to context FW */ + } + + if( right_query ) + { + worker->media.length_nanos = value; + } + + g_debug("media duration: %lld", worker->media.length_nanos); +} + +static void _check_seekability(MafwGstRendererWorker *worker) +{ + SeekabilityType seekable = SEEKABILITY_UNKNOWN; + if (worker->media.length_nanos >= 0 ) + { + g_debug("Quering GStreamer for seekability"); + GstQuery *seek_query; + GstFormat format = GST_FORMAT_TIME; + /* Query the seekability of the stream */ + seek_query = gst_query_new_seeking(format); + if (gst_element_query(worker->pipeline, seek_query)) { + gboolean renderer_seekable = FALSE; + gst_query_parse_seeking(seek_query, + NULL, + &renderer_seekable, + NULL, NULL); + g_debug("GStreamer seekability %d", renderer_seekable); + seekable = renderer_seekable ? SEEKABILITY_SEEKABLE : SEEKABILITY_NO_SEEKABLE; + } + else + { + g_debug("Could not query pipeline for seekability! Using old value!"); + seekable = worker->media.seekable; + } + gst_query_unref(seek_query); + } + else if( worker->media.length_nanos == DURATION_INDEFINITE ) + { + /* duration indefinite, "clearly" not seekable */ + seekable = SEEKABILITY_NO_SEEKABLE; + } + else + { + /* otherwise we'll use last known/guessed value */ + seekable = worker->media.seekable; + } + + g_debug("media seekable: %d", seekable); + + /* If the seekability is unknown it is set as false and sent. After that it is + sent only if it changes to true + */ + if( (seekable == SEEKABILITY_UNKNOWN && worker->media.seekable == SEEKABILITY_UNKNOWN) + || seekable != worker->media.seekable ) + { + if( seekable != SEEKABILITY_NO_SEEKABLE ) + { + worker->media.seekable = SEEKABILITY_SEEKABLE; + } + else + { + worker->media.seekable = SEEKABILITY_NO_SEEKABLE; + } + + gboolean is_seekable = (worker->media.seekable == SEEKABILITY_SEEKABLE); + _current_metadata_add(worker, + WORKER_METADATA_KEY_IS_SEEKABLE, + G_TYPE_BOOLEAN, + (const gpointer)&is_seekable); + _emit_metadata(worker, + WORKER_METADATA_KEY_IS_SEEKABLE, + G_TYPE_BOOLEAN, + &is_seekable); + } +} + +static gboolean _query_duration_and_seekability_timeout(gpointer data) +{ + MafwGstRendererWorker *worker = data; + + if (!worker->in_ready) + { + _check_duration(worker, -1); + worker->duration_seek_timeout_loop_count += 1; + + /* for worker's internal logic let's put the indefinite duration if loop limit has been reached */ + /* this affects the seekability resolution */ + if( worker->duration_seek_timeout_loop_count >= MAFW_GST_RENDERER_WORKER_DURATION_AND_SEEKABILITY_LOOP_LIMIT + && worker->media.length_nanos == DURATION_UNQUERIED ) + { + worker->media.length_nanos = DURATION_INDEFINITE; + } + + _check_seekability(worker); + + if( worker->media.length_nanos >= DURATION_INDEFINITE ) + { + worker->duration_seek_timeout = 0; + /* we've got a valid duration value no need to ask for more */ + return FALSE; + } + else + { + return TRUE; + } + } + else + { + g_warning("_query_duration_and_seekability_timeout: We are in ready state, duration and seekability not checked."); + return FALSE; + } +} + +/* + * Resets the media information. + */ +static void _reset_media_info(MafwGstRendererWorker *worker) +{ + if (worker->media.location) { + g_free(worker->media.location); + worker->media.location = NULL; + } + worker->media.length_nanos = DURATION_UNQUERIED; + worker->media.has_visual_content = FALSE; + worker->media.seekable = SEEKABILITY_UNKNOWN; + worker->media.video_width = 0; + worker->media.video_height = 0; + worker->media.fps = 0.0; +} + +static void _reset_pipeline_and_worker(MafwGstRendererWorker *worker) +{ + + if (worker->pipeline) { + g_debug("destroying pipeline"); + if (worker->async_bus_id) { + g_source_remove(worker->async_bus_id); + worker->async_bus_id = 0; + } + gst_element_set_state(worker->pipeline, GST_STATE_NULL); + if (worker->bus) { + gst_bus_set_sync_handler(worker->bus, NULL, NULL); + gst_object_unref(GST_OBJECT_CAST(worker->bus)); + worker->bus = NULL; + } + gst_object_unref(worker->pipeline); + worker->pipeline = NULL; + } + + worker->report_statechanges = TRUE; + worker->state = GST_STATE_NULL; + worker->prerolling = FALSE; + worker->is_live = FALSE; + worker->buffering = FALSE; + worker->is_stream = FALSE; + worker->is_error = FALSE; + worker->eos = FALSE; + worker->seek_position = -1; + worker->stay_paused = FALSE; + worker->playback_speed = 1; + worker->in_ready = FALSE; + _remove_ready_timeout(worker); + _remove_pause_frame_timeout(worker); + _free_taglist(worker); + if (worker->current_metadata) { + g_hash_table_destroy(worker->current_metadata); + worker->current_metadata = NULL; + } + + if (worker->duration_seek_timeout != 0) { + g_source_remove(worker->duration_seek_timeout); + worker->duration_seek_timeout = 0; + } + worker->duration_seek_timeout_loop_count = 0; + + _reset_media_info(worker); + + /* removes all idle timeouts with this worker as data */ + while(g_idle_remove_by_data(worker)); +} + + +/* + * Called when the pipeline transitions into PAUSED state. It extracts more + * information from Gst. + */ +static void _finalize_startup(MafwGstRendererWorker *worker) +{ + /* Check video caps */ + if (worker->media.has_visual_content && worker->vsink) { + GstPad *pad = GST_BASE_SINK_PAD(worker->vsink); + GstCaps *caps = GST_PAD_CAPS(pad); + if (caps && gst_caps_is_fixed(caps)) { + GstStructure *structure; + structure = gst_caps_get_structure(caps, 0); + if (!_handle_video_info(worker, structure)) + return; + } + } + + /* Something might have gone wrong at this point already. */ + if (worker->is_error) { + g_debug("Error occured during preroll"); + return; + } + + /* Streaminfo might reveal the media to be unsupported. Therefore we + * need to check the error again. */ + _parse_stream_info(worker); + if (worker->is_error) { + g_debug("Error occured. Leaving"); + return; + } + + /* Check duration and seekability */ + if (worker->duration_seek_timeout != 0) { + g_source_remove(worker->duration_seek_timeout); + worker->duration_seek_timeout = 0; + } + + _check_duration(worker, -1); + _check_seekability(worker); +} + +static void _add_duration_seek_query_timeout(MafwGstRendererWorker *worker) +{ + if(worker->duration_seek_timeout == 0) + { + gint timeout = 0; + if( worker->duration_seek_timeout_loop_count >= MAFW_GST_RENDERER_WORKER_DURATION_AND_SEEKABILITY_LOOP_LIMIT + || worker->media.length_nanos >= DURATION_INDEFINITE ) + { + /* this is just for verifying the duration later on if it was received in PAUSED state early on */ + timeout = MAFW_GST_RENDERER_WORKER_DURATION_AND_SEEKABILITY_LAZY_TIMEOUT; + } + else + { + timeout = MAFW_GST_RENDERER_WORKER_DURATION_AND_SEEKABILITY_FAST_TIMEOUT; + } + + worker->duration_seek_timeout = g_timeout_add( + timeout, + _query_duration_and_seekability_timeout, + worker); + } +} + +static void _do_pause_postprocessing(MafwGstRendererWorker *worker) +{ + if (worker->notify_pause_handler) { + worker->notify_pause_handler(worker, worker->owner); + } + + _add_pause_frame_timeout(worker); + _add_ready_timeout(worker); +} + +static void _report_playing_state(MafwGstRendererWorker * worker) +{ + if (worker->report_statechanges && worker->notify_play_handler) + { + worker->notify_play_handler( worker, + worker->owner); + } +} + +static void _handle_state_changed(GstMessage *msg, + MafwGstRendererWorker *worker) +{ + GstState newstate, oldstate; + GstStateChange statetrans; + + gst_message_parse_state_changed(msg, &oldstate, &newstate, NULL); + statetrans = GST_STATE_TRANSITION(oldstate, newstate); + g_debug("State changed: %d: %d -> %d", worker->state, oldstate, newstate); + + /* If the state is the same we do nothing, otherwise, we keep it */ + if (worker->state == newstate) + { + /* This is used for saving correct pause frame after pauseAt. + * If we doing normal seek we dont want to save pause frame. + * We use gst_element_get_state to check if the state change is completed. + * If gst_element_get_state returns GST_STATE_CHANGE_SUCCESS we know that + * it's save to do pause_postprocessing */ + if (newstate == GST_STATE_PAUSED && worker->stay_paused && + gst_element_get_state(worker->pipeline, NULL, NULL, 0) == GST_STATE_CHANGE_SUCCESS) + { + worker->seek_position = mafw_gst_renderer_seeker_process(worker->seeker); + + /* has seeking ended successfully? */ + if( worker->seek_position < 0 ) + { + /* we do pause_postprocessing for pauseAt */ + _do_pause_postprocessing(worker); + } + } + + /* the EOS flag should only be cleared if it has been set and seeking has been done + * paused -> paused transition should only happen when seeking + */ + if( newstate == GST_STATE_PAUSED && worker->eos ) + { + worker->eos = FALSE; + } + return; + } + + worker->state = newstate; + + switch (statetrans) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + if (worker->in_ready) { + /* Woken up from READY, resume stream position and playback */ + + /*live sources can be sought only in PLAYING state*/ + if( !worker->is_live ) { + _do_seek(worker, + GST_SEEK_TYPE_SET, + worker->seek_position, + FALSE, + NULL); + } + + /* While buffering, we have to wait in PAUSED until we reach 100% before + * doing anything */ + if (worker->buffering) { + return; + } else { + _do_play(worker); + } + } else if (worker->prerolling && worker->report_statechanges && !worker->buffering) { + /* PAUSED after pipeline has been constructed. We check caps, + * seek and duration and if staying in pause is needed, we + * perform operations for pausing, such as current frame on + * pause and signalling state change and adding the timeout to + * go to ready */ + g_debug ("Prerolling done, finalizaing startup"); + _finalize_startup(worker); + + if (worker->stay_paused) { + /* then we can tell we're paused */ + _do_pause_postprocessing(worker); + } + + if( worker->seek_position > 0 ) + { + g_debug("Immediate seek from READY state to: %d", worker->seek_position); + _do_seek(worker, GST_SEEK_TYPE_SET, + worker->seek_position, FALSE, NULL); + + if(worker->vsink) + { + g_object_set(worker->vsink, "show-preroll-frame", + TRUE, NULL); + } + + /* do_seek will set this to false, but we'll want to report state changes + when doing immediate seek from start */ + worker->report_statechanges = TRUE; + } + worker->prerolling = FALSE; + _do_play(worker); + } + break; + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + /* When pausing we do the stuff, like signalling state, current + * frame on pause and timeout to go to ready */ + if (worker->report_statechanges) { + _do_pause_postprocessing(worker); + } + break; + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + + /*live sources can be sought only in PLAYING state + This seek should happen only after READY to PAUSED to PLAYING + transitions + */ + if( worker->report_statechanges + && worker->seek_position > -1 + && worker->is_live ) + { + g_debug("Seeking live source in PLAYING state!"); + _do_seek(worker, + GST_SEEK_TYPE_SET, + worker->seek_position, + FALSE, + NULL); + /* this has to be set as do_seek sets statechanges to FALSE + but we still want to inform that we're in PLAYING state */ + worker->report_statechanges = TRUE; + /* seek position needs to be reset here for a live stream */ + worker->seek_position = -1; + } + + /* Because live streams are sought in PLAYING state, we reset + seek_position after all state transitions are completed. Normal + streams resetting seek_position here is OK. */ + if(worker->report_statechanges == FALSE || !worker->is_live) + { + /* if seek was called, at this point it is really ended */ + worker->seek_position = mafw_gst_renderer_seeker_process(worker->seeker); + } + + /* Signal state change if needed */ + _report_playing_state(worker); + + /* Prevent blanking if we are playing video */ + if (worker->media.has_visual_content && + worker->blanking__control_handler) + { + worker->blanking__control_handler(worker, worker->owner, TRUE); + } + + /* Back to playing no longer in_ready (if ever was) */ + worker->in_ready = FALSE; + + /* context framework adaptation starts */ + worker->context_nowplaying = + _set_context_map_value(worker->context_nowplaying, + GST_TAG_LOCATION, + worker->media.location); + context_provider_set_map(CONTEXT_PROVIDER_KEY_NOWPLAYING, + worker->context_nowplaying, FALSE); + /* context framework adaptation ends */ + + /* Emit metadata. We wait until we reach the playing state because + * this speeds up playback start time */ + _emit_metadatas(worker); + + /* in any case the duration is verified, it may change with VBR media */ + _add_duration_seek_query_timeout(worker); + + /* We've reached playing state, state changes are not reported + * unless explicitly requested (e.g. by PAUSE request) seeking + * in PLAYING does not cause state change reports + */ + worker->report_statechanges = FALSE; + + /* Delayed pause e.g. because of seek */ + if (worker->stay_paused) { + mafw_gst_renderer_worker_pause(worker); + } + + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + /* If we went to READY, we free the taglist and * deassign the + * timout it */ + if (worker->in_ready) { + g_debug("changed to GST_STATE_READY"); + worker->ready_timeout = 0; + _free_taglist(worker); + + if( worker->notify_ready_state_handler ) + { + worker->notify_ready_state_handler(worker, worker->owner); + } + } + break; + case GST_STATE_CHANGE_NULL_TO_READY: + if(g_str_has_prefix(worker->media.location, STREAM_TYPE_MMSH) || + g_str_has_prefix(worker->media.location, STREAM_TYPE_MMS)) + { + GstElement *source = NULL; + g_object_get(worker->pipeline, "source", &source, NULL); + if(source) + { + gst_util_set_object_arg(G_OBJECT(source), "tcp-timeout", MAFW_GST_MMSH_TCP_TIMEOUT); + gst_object_unref(source); + } + else + g_warning("Failed to get source element from pipeline"); + } + break; + default: + break; + } +} + +static void _handle_duration(MafwGstRendererWorker *worker) +{ + /* if we've got ongoing query timeout ignore this and in any case do it only in PAUSE/PLAYING */ + if( worker->duration_seek_timeout == 0 + && ( worker->state == GST_STATE_PAUSED || worker->state == GST_STATE_PLAYING) ) + { + /* We want to check this quickly but not immediately */ + g_timeout_add_full(G_PRIORITY_DEFAULT, + MAFW_GST_RENDERER_WORKER_DURATION_AND_SEEKABILITY_FAST_TIMEOUT, + _query_duration_and_seekability_timeout, + worker, + NULL); + } +} + +static void _emit_renderer_art(MafwGstRendererWorker *worker, + const GstTagList *list) +{ + GstBuffer *buffer = NULL; + const GValue *value = NULL; + + g_return_if_fail(gst_tag_list_get_tag_size(list, GST_TAG_IMAGE) > 0); + + value = gst_tag_list_get_value_index(list, GST_TAG_IMAGE, 0); + + g_return_if_fail((value != NULL) && G_VALUE_HOLDS(value, GST_TYPE_BUFFER)); + + buffer = g_value_peek_pointer(value); + + g_return_if_fail((buffer != NULL) && GST_IS_BUFFER(buffer)); + + _emit_gst_buffer_as_graphic_file(worker, + buffer, + WORKER_METADATA_KEY_RENDERER_ART_URI); +} + +static void value_dtor(gpointer value) +{ + + if (G_IS_VALUE(value)) { + g_value_unset(value); + g_free(value); + } else { + g_value_array_free(value); + } + +} + +static gboolean _current_metadata_add(MafwGstRendererWorker *worker, + const gint key, + GType type, + const gpointer value) +{ + GValue *new_gval; + gboolean was_updated = FALSE; + + if( value == NULL ) + { + g_warning("Null value for metadata was tried to be set!"); + return was_updated; + } + + if (!worker->current_metadata) { + worker->current_metadata = g_hash_table_new_full(g_direct_hash, + g_direct_equal, + NULL, + value_dtor); + } + + if (type == G_TYPE_VALUE_ARRAY) { + GValueArray *values = (GValueArray *) value; + + if (values->n_values == 1) { + GValue *gval = g_value_array_get_nth(values, 0); + new_gval = g_new0(GValue, 1); + g_value_init(new_gval, G_VALUE_TYPE(gval)); + g_value_copy(gval, new_gval); + + GValue *existing = (GValue*)g_hash_table_lookup(worker->current_metadata, (gpointer)key); + if(!existing || (GST_VALUE_EQUAL != gst_value_compare(existing, new_gval)) ) + { + was_updated = TRUE; + } + g_hash_table_insert(worker->current_metadata, + (gpointer)key, + new_gval); + } else { + GValueArray *new_gvalues = g_value_array_copy(values); + + GValueArray *existing = (GValueArray*)g_hash_table_lookup(worker->current_metadata, (gpointer)key); + + if( existing + && new_gvalues->n_values == existing->n_values ) + { + guint size = new_gvalues->n_values; + + guint i = 0; + for( ; i < size; ++i ) + { + GValue *newVal = g_value_array_get_nth(new_gvalues, i); + GValue *existingVal = g_value_array_get_nth(existing, i); + if( GST_VALUE_EQUAL != gst_value_compare(newVal, existingVal) ) + { + was_updated = TRUE; + break; + } + } + } + else + { + was_updated = TRUE; + } + + g_hash_table_insert(worker->current_metadata, + (gpointer)key, + new_gvalues); + } + + return was_updated; + } + + new_gval = g_new0(GValue, 1); + + if (_set_value(new_gval, type, value) == FALSE) + { + g_warning("Metadata type: %i is not being handled", type); + return was_updated; + } + + GValue *existing = (GValue*)g_hash_table_lookup(worker->current_metadata, (gpointer)key); + if(!existing || (GST_VALUE_EQUAL != gst_value_compare(existing, new_gval)) ) + { + was_updated = TRUE; + } + g_hash_table_insert(worker->current_metadata, (gpointer)key, new_gval); + + return was_updated; + +} + +static GHashTable* _build_tagmap(void) +{ + GHashTable *hash_table = NULL; + + hash_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + + g_hash_table_insert(hash_table, g_strdup(GST_TAG_TITLE), + (gpointer)WORKER_METADATA_KEY_TITLE); + g_hash_table_insert(hash_table, g_strdup(GST_TAG_ARTIST), + (gpointer)WORKER_METADATA_KEY_ARTIST); + g_hash_table_insert(hash_table, g_strdup(GST_TAG_AUDIO_CODEC), + (gpointer)WORKER_METADATA_KEY_AUDIO_CODEC); + g_hash_table_insert(hash_table, g_strdup(GST_TAG_VIDEO_CODEC), + (gpointer)WORKER_METADATA_KEY_VIDEO_CODEC); + g_hash_table_insert(hash_table, g_strdup(GST_TAG_BITRATE), + (gpointer)WORKER_METADATA_KEY_BITRATE); + g_hash_table_insert(hash_table, g_strdup(GST_TAG_LANGUAGE_CODE), + (gpointer)WORKER_METADATA_KEY_ENCODING); + g_hash_table_insert(hash_table, g_strdup(GST_TAG_ALBUM), + (gpointer)WORKER_METADATA_KEY_ALBUM); + g_hash_table_insert(hash_table, g_strdup(GST_TAG_GENRE), + (gpointer)WORKER_METADATA_KEY_GENRE); + g_hash_table_insert(hash_table, g_strdup(GST_TAG_TRACK_NUMBER), + (gpointer)WORKER_METADATA_KEY_TRACK); + g_hash_table_insert(hash_table, g_strdup(GST_TAG_ORGANIZATION), + (gpointer)WORKER_METADATA_KEY_ORGANIZATION); + g_hash_table_insert(hash_table, g_strdup(GST_TAG_IMAGE), + (gpointer)WORKER_METADATA_KEY_RENDERER_ART_URI); + + return hash_table; +} + +/* + * Sets values to given context framework map, allocates it when map is NULL. + */ +gpointer _set_context_map_value(gpointer map, + const gchar *tag, + const gchar *value) +{ + + if (map == NULL) + { + map = context_provider_map_new(); + } + + if (g_str_equal(tag, GST_TAG_LOCATION)) + { + context_provider_map_set_string(map, + CONTEXT_PROVIDER_KEY_NOWPLAYING_RESOURCE, + value); + } + else if (g_str_equal(tag, GST_TAG_TITLE)) + { + context_provider_map_set_string(map, + CONTEXT_PROVIDER_KEY_NOWPLAYING_TITLE, + value); + } + else if (g_str_equal(tag, GST_TAG_ARTIST)) + { + context_provider_map_set_string(map, + CONTEXT_PROVIDER_KEY_NOWPLAYING_ARTIST, + value); + } + else if (g_str_equal(tag, GST_TAG_ALBUM)) + { + context_provider_map_set_string(map, + CONTEXT_PROVIDER_KEY_NOWPLAYING_ALBUM, + value); + } + else if (g_str_equal(tag, GST_TAG_GENRE)) + { + context_provider_map_set_string(map, + CONTEXT_PROVIDER_KEY_NOWPLAYING_GENRE, + value); + } + + return map; + +} + +/* + * Emits metadata-changed signals for gst tags. + */ +static void _emit_tag(const GstTagList *list, + const gchar *tag, + MafwGstRendererWorker *worker) +{ + /* Mapping between Gst <-> MAFW metadata tags + * NOTE: This assumes that GTypes matches between GST and MAFW. */ + static GHashTable *tagmap = NULL; + gint i, count; + gint mafwtag; + GType type; + GValueArray *values; + + if (tagmap == NULL) { + tagmap = _build_tagmap(); + } + + g_debug("tag: '%s' (type: %s)", tag, g_type_name(gst_tag_get_type(tag))); + /* Is there a mapping for this tag? */ + mafwtag = (gint)g_hash_table_lookup(tagmap, tag); + if (!mafwtag) + return; + + if (mafwtag == WORKER_METADATA_KEY_RENDERER_ART_URI) { + _emit_renderer_art(worker, list); + return; + } + + /* Build a value array of this tag. We need to make sure that strings + * are UTF-8. GstTagList API says that the value is always UTF8, but it + * looks like the ID3 demuxer still might sometimes produce non-UTF-8 + * strings. */ + count = gst_tag_list_get_tag_size(list, tag); + + type = gst_tag_get_type(tag); + values = g_value_array_new(count); + for (i = 0; i < count; ++i) { + GValue *v = (GValue *) + gst_tag_list_get_value_index(list, tag, i); + if (type == G_TYPE_STRING) { + gchar *orig, *utf8; + + gboolean tagExists = gst_tag_list_get_string_index(list, tag, i, &orig); + UNUSED(tagExists); //TODO what if tag does not exists? + if (convert_utf8(orig, &utf8)) { + GValue utf8gval; + memset(&utf8gval, 0, sizeof(utf8gval)); + + g_value_init(&utf8gval, G_TYPE_STRING); + g_value_take_string(&utf8gval, utf8); + g_value_array_append(values, &utf8gval); + g_value_unset(&utf8gval); + } + /* context framework adaptation starts */ + worker->context_nowplaying = + _set_context_map_value(worker->context_nowplaying, + tag, + orig); + /* context framework adaptation ends */ + g_free(orig); + } else if (type == G_TYPE_UINT) { + GValue intgval; + memset(&intgval, 0, sizeof(intgval)); + + g_value_init(&intgval, G_TYPE_INT); + g_value_transform(v, &intgval); + g_value_array_append(values, &intgval); + g_value_unset(&intgval); + } else { + g_value_array_append(values, v); + } + } + + /* context framework adaptation starts */ + context_provider_set_map(CONTEXT_PROVIDER_KEY_NOWPLAYING, + worker->context_nowplaying, FALSE); + /* context framework adaptation ends */ + + /* Add the info to the current metadata. */ + gboolean changed = _current_metadata_add(worker, + mafwtag, + G_TYPE_VALUE_ARRAY, + (const gpointer) values); + + /* Emit the metadata. */ + if (changed && worker->notify_metadata_handler) + { + worker->notify_metadata_handler(worker, + worker->owner, + mafwtag, + G_TYPE_VALUE_ARRAY, + values); + } + + g_value_array_free(values); +} + +/** + * Collect tag-messages, parse it later, when playing is ongoing + */ +static void _handle_tag(MafwGstRendererWorker *worker, GstMessage *msg) +{ + /* Do not emit metadata until we get to PLAYING state to speed up playback + * start */ + if (worker->tag_list == NULL) + worker->tag_list = g_ptr_array_new(); + g_ptr_array_add(worker->tag_list, gst_message_ref(msg)); + + /* Some tags come in playing state, so in this case we have to emit them + * right away (example: radio stations) */ + if (worker->state == GST_STATE_PLAYING) { + _emit_metadatas(worker); + } +} + +/** + * Parses the list of tag-messages + */ +static void _parse_tagmsg(GstMessage *msg, MafwGstRendererWorker *worker) +{ + GstTagList *new_tags; + + gst_message_parse_tag(msg, &new_tags); + gst_tag_list_foreach(new_tags, (gpointer)_emit_tag, worker); + gst_tag_list_free(new_tags); + gst_message_unref(msg); +} + +/** + * Parses the collected tag messages, and emits the metadatas + */ +static void _emit_metadatas(MafwGstRendererWorker *worker) +{ + if (worker->tag_list != NULL) + { + g_ptr_array_foreach(worker->tag_list, (GFunc)_parse_tagmsg, worker); + g_ptr_array_free(worker->tag_list, TRUE); + worker->tag_list = NULL; + } +} + +static void _handle_buffering(MafwGstRendererWorker *worker, GstMessage *msg) +{ + /* We set smaller buffer for mms streams */ + if((g_str_has_prefix(worker->media.location, STREAM_TYPE_MMSH) || g_str_has_prefix(worker->media.location, STREAM_TYPE_MMS)) + && worker->state != GST_STATE_PLAYING && !worker->buffering) + { + if(worker->queue) + { + g_object_set(worker->queue, "high-percent", 30, NULL); + } + else + { + g_warning("Queue2 element doesn't exist!"); + } + } + + /* We can ignore buffering messages when we are in READY state or when going to it */ + if(worker->state == GST_STATE_READY || worker->ready_timeout != 0 ) + { + worker->buffering = TRUE; + return; + } + + gint percent; + gst_message_parse_buffering(msg, &percent); + g_debug("buffering: %d", percent); + + /* No state management needed for live pipelines */ + if (!worker->is_live) { + worker->buffering = TRUE; + if (percent < 100 && worker->state == GST_STATE_PLAYING) { + /* If we need to buffer more, we set larger buffer */ + if(g_str_has_prefix(worker->media.location, STREAM_TYPE_MMSH) || g_str_has_prefix(worker->media.location, STREAM_TYPE_MMS)) + { + if(worker->queue) + { + g_object_set(worker->queue, "high-percent", 100, NULL); + } + else + { + g_warning("Queue2 element doesn't exist!"); + } + } + g_debug("setting pipeline to PAUSED not to wolf the buffer down"); + + //if there's no requested state transitions i.e. resume/pause let's keep this quiet + if( gst_element_get_state(worker->pipeline, NULL, NULL, 0) == GST_STATE_CHANGE_SUCCESS ) + { + worker->report_statechanges = FALSE; + } + + /* We can't call _pause() here, since it sets the + * "report_statechanges" to TRUE. We don't want that, application + * doesn't need to know that internally the state changed to PAUSED. + */ + gst_element_set_state(worker->pipeline, GST_STATE_PAUSED); + } + + if (percent >= 100) { + /* On buffering we go to PAUSED, so here we move back to PLAYING */ + worker->buffering = FALSE; + if (worker->state == GST_STATE_PAUSED) + { + /* If buffering more than once, do this only the first time we + * are done with buffering */ + if (worker->prerolling) + { + g_debug("buffering concluded during prerolling"); + _finalize_startup(worker); + _do_play(worker); + /* Send the paused notification */ + if (worker->stay_paused && + worker->notify_pause_handler) + { + worker->notify_pause_handler(worker, worker->owner); + } + worker->prerolling = FALSE; + } + /* if eos has been reached no automatic playing should be done + only on resume request e.g. eos reached -> seek requested => stays paused after seek&buffering completed */ + else if (!worker->stay_paused && !worker->eos) + { + g_debug("buffering concluded, setting " + "pipeline to PLAYING again"); + worker->report_statechanges = TRUE; + gst_element_set_state(worker->pipeline, GST_STATE_PLAYING); + } + } + /* if there's no pending state changes and we're really in PLAYING state... */ + else if (gst_element_get_state(worker->pipeline, NULL, NULL, 0) == GST_STATE_CHANGE_SUCCESS + && worker->state == GST_STATE_PLAYING) + { + g_debug("buffering concluded, signalling state change"); + /* In this case we got a PLAY command while buffering, likely + * because it was issued before we got the first buffering + * signal. The UI should not do this, but if it does, we have + * to signal that we have executed the state change, since in + * _handle_state_changed we do not do anything if we are + * buffering */ + if (worker->report_statechanges && + worker->notify_play_handler) { + worker->notify_play_handler(worker, worker->owner); + } + _add_duration_seek_query_timeout(worker); + + } + /* has somebody requested pause transition? */ + else if( !worker->stay_paused ) + { + /* we're in PLAYING but pending for paused state change. + Let's request new state change to override the pause */ + gst_element_set_state(worker->pipeline, GST_STATE_PLAYING); + } + } + } + + /* Send buffer percentage */ + if (worker->notify_buffer_status_handler) + worker->notify_buffer_status_handler(worker, worker->owner, percent); +} + +static void _handle_element_msg(MafwGstRendererWorker *worker, GstMessage *msg) +{ + /* Only HelixBin sends "resolution" messages. */ + if (gst_structure_has_name(msg->structure, "resolution") && + _handle_video_info(worker, msg->structure)) + { + worker->media.has_visual_content = TRUE; + set_dolby_video_property(worker, worker->config->mobile_surround_video.state); + set_dolby_video_sound_property(worker, worker->config->mobile_surround_video.room, TRUE); + set_dolby_video_sound_property(worker, worker->config->mobile_surround_video.color, FALSE); + } + /* We do RTSP redirect when we try to play .sdp streams */ + else if(gst_structure_has_name(msg->structure, "redirect")) + { + /* "new-location" contains the rtsp uri what we are going to play */ + mafw_gst_renderer_worker_play(worker, gst_structure_get_string(msg->structure, "new-location")); + } + +} + +static GError * _get_specific_missing_plugin_error(GstMessage *msg) +{ + const GstStructure *gst_struct; + const gchar *type; + + GError *error; + gchar *desc; + + desc = gst_missing_plugin_message_get_description(msg); + + gst_struct = gst_message_get_structure(msg); + type = gst_structure_get_string(gst_struct, "type"); + + if ((type) && ((strcmp(type, MAFW_GST_MISSING_TYPE_DECODER) == 0) || + (strcmp(type, MAFW_GST_MISSING_TYPE_ENCODER) == 0))) { + + /* Missing codec error. */ + const GValue *val; + const GstCaps *caps; + GstStructure *caps_struct; + const gchar *mime; + + val = gst_structure_get_value(gst_struct, "detail"); + caps = gst_value_get_caps(val); + caps_struct = gst_caps_get_structure(caps, 0); + mime = gst_structure_get_name(caps_struct); + + if (g_strrstr(mime, "video")) { + error = g_error_new_literal(WORKER_ERROR, + WORKER_ERROR_VIDEO_CODEC_NOT_FOUND, + desc); + } else if (g_strrstr(mime, "audio")) { + error = g_error_new_literal(WORKER_ERROR, + WORKER_ERROR_AUDIO_CODEC_NOT_FOUND, + desc); + } else { + error = g_error_new_literal(WORKER_ERROR, + WORKER_ERROR_CODEC_NOT_FOUND, + desc); + } + } else { + /* Unsupported type error. */ + error = g_error_new(WORKER_ERROR, + WORKER_ERROR_UNSUPPORTED_TYPE, + "missing plugin: %s", desc); + } + + g_free(desc); + + return error; +} + +/* + * Asynchronous message handler. It gets removed from if it returns FALSE. + */ +static gboolean _async_bus_handler(GstBus *bus, + GstMessage *msg, + MafwGstRendererWorker *worker) +{ + + UNUSED(bus); + + /* No need to handle message if error has already occured. */ + if (worker->is_error) + return TRUE; + + /* Handle missing-plugin (element) messages separately, relaying more + * details. */ + if (gst_is_missing_plugin_message(msg)) { + GError *err = _get_specific_missing_plugin_error(msg); + /* FIXME?: for some reason, calling the error handler directly + * (_send_error) causes problems. On the other hand, turning + * the error into a new GstMessage and letting the next + * iteration handle it seems to work. */ + _post_error(worker, err); + return TRUE; + } + switch (GST_MESSAGE_TYPE(msg)) { + case GST_MESSAGE_ERROR: + if (!worker->is_error) { + gchar *debug; + GError *err; + debug = NULL; + gst_message_parse_error(msg, &err, &debug); + g_debug("gst error: domain = %s, code = %d, " + "message = '%s', debug = '%s'", + g_quark_to_string(err->domain), err->code, err->message, debug); + if (debug) + { + g_free(debug); + } + //decodebin2 uses the error only to report text files + if (err->code == GST_STREAM_ERROR_WRONG_TYPE && g_str_has_prefix(GST_MESSAGE_SRC_NAME(msg), "decodebin2")) + { + err->code = WORKER_ERROR_POSSIBLY_PLAYLIST_TYPE; + } + + _send_error(worker, err); + } + break; + case GST_MESSAGE_EOS: + if (!worker->is_error) { + worker->eos = TRUE; + worker->seek_position = -1; + if (worker->notify_eos_handler) + { + worker->notify_eos_handler(worker, worker->owner); + } + } + break; + case GST_MESSAGE_TAG: + _handle_tag(worker, msg); + break; + case GST_MESSAGE_BUFFERING: + _handle_buffering(worker, msg); + break; + case GST_MESSAGE_DURATION: + /* in ready state we might not get correct seekability info */ + if (!worker->in_ready) + { + _handle_duration(worker); + } + break; + case GST_MESSAGE_ELEMENT: + _handle_element_msg(worker, msg); + break; + case GST_MESSAGE_STATE_CHANGED: + if ((GstElement *)GST_MESSAGE_SRC(msg) == worker->pipeline) + _handle_state_changed(msg, worker); + break; + case GST_MESSAGE_APPLICATION: + if (gst_structure_has_name(gst_message_get_structure(msg), "ckey")) + { + _emit_property(worker, + WORKER_PROPERTY_COLORKEY, + G_TYPE_INT, + &worker->colorkey); + } + default: + break; + } + return TRUE; +} + +/* NOTE this function will possibly be called from a different thread than the + * glib main thread. */ +static void _stream_info_cb(GstObject *pipeline, GParamSpec *unused, + MafwGstRendererWorker *worker) +{ + UNUSED(pipeline); + UNUSED(unused); + + g_debug("stream-info changed"); + _parse_stream_info(worker); +} + +static void _element_added_cb(GstBin *bin, GstElement *element, + MafwGstRendererWorker *worker) +{ + UNUSED(bin); + gchar *element_name; + + element_name = gst_element_get_name(element); + if(g_str_has_prefix(element_name, "uridecodebin") || + g_str_has_prefix(element_name, "decodebin2")) + { + g_signal_connect(element, "element-added", + G_CALLBACK(_element_added_cb), worker); + } + else if(g_str_has_prefix(element_name, "sdpdemux")) + { + g_object_set(element, "redirect", FALSE, NULL); + } + else if(g_str_has_prefix(element_name, "queue2")) + { + worker->queue = element; + } + g_free(element_name); +} + +/* + * Start to play the media + */ +static void _start_play(MafwGstRendererWorker *worker) +{ + GstStateChangeReturn state_change_info; + worker->stay_paused = FALSE; + char *autoload_sub = NULL; + + g_assert(worker->pipeline); + g_object_set(G_OBJECT(worker->pipeline), + "uri", worker->media.location, NULL); + + if (worker->config->autoload_subtitles) { + autoload_sub = uri_get_subtitle_uri(worker->media.location); + if (autoload_sub) { + g_debug("SUBURI: %s", autoload_sub); + g_object_set(G_OBJECT(worker->pipeline), + "suburi", autoload_sub, + "subtitle-font-desc", worker->config->subtitle_font, + "subtitle-encoding", worker->config->subtitle_encoding, + NULL); + + gst_element_set_state(worker->pipeline, GST_STATE_READY); + g_free(autoload_sub); + } + } else { + g_object_set(G_OBJECT(worker->pipeline), "suburi", NULL, NULL); + } + + g_debug("URI: %s", worker->media.location); + g_debug("setting pipeline to PAUSED"); + + worker->report_statechanges = TRUE; + state_change_info = gst_element_set_state(worker->pipeline, + GST_STATE_PAUSED); + if (state_change_info == GST_STATE_CHANGE_NO_PREROLL) { + /* FIXME: for live sources we may have to handle buffering and + * prerolling differently */ + g_debug ("Source is live!"); + worker->is_live = TRUE; + } + worker->prerolling = TRUE; + + worker->is_stream = uri_is_stream(worker->media.location); + +} + +/* + * Constructs gst pipeline + * + * FIXME: Could the same pipeline be used for playing all media instead of + * constantly deleting and reconstructing it again? + */ +static void _construct_pipeline(MafwGstRendererWorker *worker, configuration *config) +{ + g_debug("constructing pipeline"); + g_assert(worker != NULL); + + /* Return if we have already one */ + if (worker->pipeline) + return; + + _free_taglist(worker); + + g_debug("Creating a new instance of playbin2"); + worker->pipeline = gst_element_factory_make("playbin2", "playbin"); + if (worker->pipeline == NULL) + { + /* Let's try with playbin */ + g_warning ("playbin2 failed, falling back to playbin"); + worker->pipeline = gst_element_factory_make("playbin", "playbin"); + + if (worker->pipeline) { + /* Use nwqueue only for non-rtsp and non-mms(h) streams. */ + gboolean use_nw; + use_nw = worker->media.location && + !g_str_has_prefix(worker->media.location, "rtsp://") && + !g_str_has_prefix(worker->media.location, "mms://") && + !g_str_has_prefix(worker->media.location, "mmsh://"); + + g_debug("playbin using network queue: %d", use_nw); + + gst_object_ref_sink(worker->pipeline); + /* These need a modified version of playbin. */ + g_object_set(G_OBJECT(worker->pipeline), + "nw-queue", use_nw, + "no-video-transform", TRUE, + NULL); + + } + } + + if (!worker->pipeline) { + g_critical("failed to create playback pipeline"); + _send_error(worker, + g_error_new(WORKER_ERROR, + WORKER_ERROR_UNABLE_TO_PERFORM, + "Could not create pipeline")); + g_assert_not_reached(); + } + + worker->bus = gst_pipeline_get_bus(GST_PIPELINE(worker->pipeline)); + gst_bus_set_sync_handler(worker->bus, + (GstBusSyncHandler)_sync_bus_handler, + worker); + worker->async_bus_id = gst_bus_add_watch_full( + worker->bus,G_PRIORITY_HIGH, + (GstBusFunc)_async_bus_handler, + worker, NULL); + + /* Listen for changes in stream-info object to find out whether the media + * contains video and throw error if application has not provided video + * window. */ + g_signal_connect(worker->pipeline, "notify::stream-info", + G_CALLBACK(_stream_info_cb), worker); + + g_signal_connect(worker->pipeline, "element-added", + G_CALLBACK(_element_added_cb), worker); + + /* Set audio and video sinks ourselves. We create and configure them only + * once. */ + if (!worker->asink) { + const gchar *sink = g_getenv("AUDIO_SINK"); + worker->asink = gst_element_factory_make(sink?sink: worker->config->asink, NULL); + if (!worker->asink){ + worker->asink = gst_element_factory_make("alsasink", NULL); + } + if (!worker->asink) { + g_critical("Failed to create pipeline audio sink"); + _send_error(worker, + g_error_new(WORKER_ERROR, + WORKER_ERROR_UNABLE_TO_PERFORM, + "Could not create audio sink")); + g_assert_not_reached(); + } + g_debug("MafwGstRendererWorker: Using following buffer-time: %lld and latency-time: %lld", + config->buffer_time, + config->latency_time); + gst_object_ref_sink(worker->asink); + g_object_set(worker->asink, + "buffer-time", config->buffer_time, + "latency-time", config->latency_time, + NULL); + } + + if (worker->config->use_dhmmixer && !worker->amixer) + { + worker->amixer = gst_element_factory_make("nokiadhmmix", NULL); + if( !worker->amixer ) + { + g_warning("Could not create dhmmixer, falling back to basic audiosink!"); + } + } + + if( worker->config->use_dhmmixer && worker->amixer && !worker->audiobin ) + { + worker->audiobin = gst_bin_new("audiobin"); + if( worker->audiobin ) + { + gst_bin_add(GST_BIN (worker->audiobin), worker->amixer); + gst_bin_add(GST_BIN (worker->audiobin), worker->asink); + gst_element_link(worker->amixer, worker->asink); + GstPad *pad; + pad = gst_element_get_static_pad (worker->amixer, "sink"); + gst_element_add_pad (worker->audiobin, gst_ghost_pad_new ("sink", pad)); + gst_object_unref (GST_OBJECT (pad)); + + gst_object_ref(worker->audiobin); + + /* Use Dolby music settings by default */ + set_dolby_music_property(worker, worker->config->mobile_surround_music.state); + set_dolby_music_sound_property(worker, worker->config->mobile_surround_music.room, TRUE); + set_dolby_music_sound_property(worker, worker->config->mobile_surround_music.color, FALSE); + } + else + { + gst_object_ref_sink(worker->asink); + gst_object_sink(worker->amixer); + g_warning("Could not create audiobin! Falling back to basic audio-sink!"); + } + } + + + if( worker->config->use_dhmmixer && worker->amixer && worker->audiobin ) + { + g_object_set(worker->pipeline, + "audio-sink", worker->audiobin, + "flags", worker->config->flags, + NULL); + } + else + { + g_object_set(worker->pipeline, + "audio-sink", worker->asink, + "flags", worker->config->flags, + NULL); + } + + if( worker->pipeline ) + { + mafw_gst_renderer_seeker_set_pipeline(worker->seeker, worker->pipeline); + + if( worker->vsink && worker->xid != 0 ) + { + g_object_set(worker->pipeline, + "video-sink", worker->vsink, + NULL); + } + } + + if (!worker->tsink) { + worker->tsink = gst_element_factory_make("textoverlay", NULL); + if (!worker->tsink) { + g_critical("Failed to create pipeline text sink"); + _send_error(worker, + g_error_new(WORKER_ERROR, + WORKER_ERROR_UNABLE_TO_PERFORM, + "Could not create text sink")); + g_assert_not_reached(); + } + gst_object_ref(worker->tsink); + } + g_object_set(worker->pipeline, "text-sink", worker->tsink, NULL); +} + +guint check_dolby_audioroute(MafwGstRendererWorker *worker, guint prop) { + if (g_slist_find(worker->destinations, + GINT_TO_POINTER(WORKER_OUTPUT_BLUETOOTH_AUDIO)) || + g_slist_find(worker->destinations, + GINT_TO_POINTER(WORKER_OUTPUT_HEADPHONE_JACK))) + { + return prop; + } + else + { + return 0; + } +} + +void set_dolby_music_property(MafwGstRendererWorker *worker, guint prop) { + worker->config->mobile_surround_music.state = prop; + if (worker->amixer && !worker->media.has_visual_content) { + GValue a; + guint tempprop = check_dolby_audioroute(worker, prop); + if (_set_value(&a, G_TYPE_UINT, &tempprop)) + { + g_object_set_property(G_OBJECT(worker->amixer), "mobile-surround", &a); + g_value_unset (&a); + } + } +} + +void set_dolby_music_sound_property(MafwGstRendererWorker *worker, gint prop, gboolean isRoomProperty) { + if (isRoomProperty) { + worker->config->mobile_surround_music.room = prop; + } else { + worker->config->mobile_surround_music.color = prop; + } + if (worker->amixer && !worker->media.has_visual_content) { + GValue a; + + if (_set_value(&a, G_TYPE_UINT, &prop)) + { + if (isRoomProperty) { + g_object_set_property(G_OBJECT(worker->amixer), "room-size", &a); + } else { + g_object_set_property(G_OBJECT(worker->amixer), "brightness", &a); + } + g_value_unset (&a); + } + } +} + +void set_dolby_video_property(MafwGstRendererWorker *worker, guint prop) { + worker->config->mobile_surround_video.state = prop; + if (worker->amixer && worker->media.has_visual_content) { + GValue a; + guint tempprop = check_dolby_audioroute(worker, prop); + if (_set_value(&a, G_TYPE_UINT, &tempprop)) + { + g_object_set_property(G_OBJECT(worker->amixer), "mobile-surround", &a); + g_value_unset (&a); + } + } +} + +void set_dolby_video_sound_property(MafwGstRendererWorker *worker, gint prop, gboolean isRoomProperty) { + if (isRoomProperty) { + worker->config->mobile_surround_video.room = prop; + } else { + worker->config->mobile_surround_video.color = prop; + } + if (worker->amixer && worker->media.has_visual_content) { + GValue a; + + if (_set_value(&a, G_TYPE_UINT, &prop)) + { + if (isRoomProperty) { + g_object_set_property(G_OBJECT(worker->amixer), "room-size", &a); + } else { + g_object_set_property(G_OBJECT(worker->amixer), "brightness", &a); + } + g_value_unset (&a); + } + } +} + +/* + * @seek_type: GstSeekType + * @position: Time in seconds where to seek + * @key_frame_seek: True if this is a key frame based seek + * @error: Possible error that is set and returned + */ +static void _do_seek(MafwGstRendererWorker *worker, + GstSeekType seek_type, + gint position, + gboolean key_frame_seek, + GError **error) +{ + gboolean ret; + gint64 spos; + GstSeekFlags flags = GST_SEEK_FLAG_FLUSH; + + g_assert(worker != NULL); + + if (!worker->media.seekable == SEEKABILITY_SEEKABLE) + { + goto err; + } + + /* According to the docs, relative seeking is not so easy: + GST_SEEK_TYPE_CUR - change relative to currently configured segment. + This can't be used to seek relative to the current playback position - + do a position query, calculate the desired position and then do an + absolute position seek instead if that's what you want to do. */ + if (seek_type == GST_SEEK_TYPE_CUR) + { + gint curpos = mafw_gst_renderer_worker_get_position(worker); + position = curpos + position; + seek_type = GST_SEEK_TYPE_SET; + } + + if (position < 0) { + position = 0; + } + + worker->seek_position = position; + + if (worker->state != GST_STATE_PLAYING && worker->state != GST_STATE_PAUSED ) + { + g_debug("_do_seek: Not in playing or paused state, seeking delayed."); + return; + } + else if( worker->is_live && worker->state == GST_STATE_PAUSED ) + { + g_debug("_do_seek: Live source can be seeked only in playing state, seeking delayed!"); + return; + } + + worker->report_statechanges = FALSE; + + if (key_frame_seek == TRUE) + { + /* tries to do key frame seeks at least with some change */ + ret = mafw_gst_renderer_seeker_seek_to(worker->seeker, worker->seek_position); + } + else + { + spos = (gint64)position * GST_SECOND; + g_debug("seek: type = %d, offset = %lld", seek_type, spos); + + /* exact seek */ + ret = gst_element_seek(worker->pipeline, + 1.0, + GST_FORMAT_TIME, + flags, + seek_type, + spos, + GST_SEEK_TYPE_NONE, + GST_CLOCK_TIME_NONE); + } + + if (ret) + { + /* Seeking is async, so seek_position should not be invalidated here */ + return; + } + +err: + g_set_error(error, + WORKER_ERROR, + WORKER_ERROR_CANNOT_SET_POSITION, + "Seeking to %d failed", position); + worker->report_statechanges = TRUE; + worker->seek_position = -1; + mafw_gst_renderer_seeker_cancel(worker->seeker); +} + +void mafw_gst_renderer_worker_set_current_frame_on_pause( + MafwGstRendererWorker *worker, + gboolean current_frame_on_pause) +{ + + worker->current_frame_on_pause = current_frame_on_pause; + + _emit_property(worker, + WORKER_PROPERTY_CURRENT_FRAME_ON_PAUSE, + G_TYPE_BOOLEAN, + &worker->current_frame_on_pause); +} + +gboolean mafw_gst_renderer_worker_get_current_frame_on_pause( + MafwGstRendererWorker *worker) +{ + return worker->current_frame_on_pause; +} + +configuration *mafw_gst_renderer_worker_create_default_configuration(MafwGstRendererWorker *worker) +{ + UNUSED(worker); + return _create_default_configuration(); +} + +void mafw_gst_renderer_worker_set_configuration(MafwGstRendererWorker *worker, + configuration *config) +{ + if( config == NULL ) + { + g_warning("NULL config was tried to be set!"); + return; + } + + if( worker->config ) + { + _free_configuration(worker->config); + } + worker->config = config; + + if( (worker->pipeline == NULL) + || (worker->state == GST_STATE_NULL && gst_element_get_state(worker->pipeline, NULL, NULL, 0) == GST_STATE_CHANGE_SUCCESS) ) + { + _reset_pipeline_and_worker(worker); + _construct_pipeline(worker, worker->config); + } +} + +/* + * Sets the pipeline PAUSED-to-READY timeout to given value (in seconds). If the + * pipeline is already in PAUSED state and this called with zero value the pipeline + * get immediately set to READY state. + */ +void mafw_gst_renderer_worker_set_ready_timeout(MafwGstRendererWorker *worker, + guint seconds) +{ + g_debug(G_STRFUNC); + + worker->config->seconds_to_pause_to_ready = seconds; + + /* zero is a special case: if we are already in PAUSED state, a pending + * ready timeout has not yet elapsed and we are asked to set the timeout + * value to zero --> remove the pending tmo and go immediately to READY. + * This forces GStreamer to release all pipeline resources and for the + * outsiders it looks like we are still in the PAUSED state. */ + if (seconds == 0 && worker->ready_timeout && worker->state == GST_STATE_PAUSED) + { + _remove_ready_timeout(worker); + _add_ready_timeout(worker); + } + +} + +void mafw_gst_renderer_worker_set_position(MafwGstRendererWorker *worker, + GstSeekType seek_type, + gint position, GError **error) +{ + _do_seek(worker, seek_type, position, TRUE, error); + if (worker->notify_seek_handler) + worker->notify_seek_handler(worker, worker->owner); +} + +static gint64 _get_duration(MafwGstRendererWorker *worker) +{ + gint64 value = DURATION_UNQUERIED; + GstFormat format = GST_FORMAT_TIME; + + gboolean right_query = gst_element_query_duration(worker->pipeline, &format, &value); + if( !right_query ) + { + /* just in case gstreamer messes with the value */ + value = DURATION_UNQUERIED; + } + return value; +} + +/* + * Gets current position, rounded down into precision of one second. If a seek + * is pending, returns the position we are going to seek. Returns -1 on + * failure. + */ +gint mafw_gst_renderer_worker_get_position(MafwGstRendererWorker *worker) +{ + GstFormat format; + gint64 time = 0; + g_assert(worker != NULL); + + /* If seek is ongoing, return the position where we are seeking. */ + if (worker->seek_position != -1) + { + return worker->seek_position; + } + + /* Otherwise query position from pipeline. */ + format = GST_FORMAT_TIME; + if (worker->pipeline && + gst_element_query_position(worker->pipeline, &format, &time)) + { + return (gint)(time / GST_SECOND); + } + /* lets return the duration if we're in eos and the pipeline cannot return position */ + else if( worker->pipeline && worker->eos ) + { + gint64 duration = _get_duration(worker); + if( duration > 0 ) + { + return (gint)(duration / GST_SECOND); + } + } + return -1; +} + +/* + * Returns the duration of the current media in seconds + */ +gint64 mafw_gst_renderer_worker_get_duration(MafwGstRendererWorker *worker) +{ + gint64 duration = _get_duration(worker); + if( duration >= 0 ) + { + gint64 second_precision = (duration + (GST_SECOND/2)) / GST_SECOND; + + if( !_seconds_duration_equal(duration, worker->media.length_nanos) ) + { + worker->media.length_nanos = duration; + + if( _current_metadata_add(worker, + WORKER_METADATA_KEY_DURATION, + G_TYPE_INT64, + (const gpointer)&second_precision) ) + { + _emit_metadata(worker, + WORKER_METADATA_KEY_DURATION, + G_TYPE_INT64, + &second_precision); + } + } + return second_precision; + } + else + { + return -1; + } +} + +gint64 mafw_gst_renderer_worker_get_last_known_duration(MafwGstRendererWorker *worker) +{ + if( worker->media.length_nanos <= 0 ) + { + return worker->media.length_nanos; + } + else + { + return (worker->media.length_nanos + (GST_SECOND/2)) / GST_SECOND; + } +} + +GHashTable *mafw_gst_renderer_worker_get_current_metadata( + MafwGstRendererWorker *worker) +{ + return worker->current_metadata; +} + +void mafw_gst_renderer_worker_set_xid(MafwGstRendererWorker *worker, XID xid) +{ + /* Store the target window id */ + g_debug("Setting xid: %x", (guint)xid); + worker->xid = xid; + + if( !worker->vsink ) + { + g_debug("Creating video-sink as XID has been set, %s", worker->config->vsink); + worker->vsink = gst_element_factory_make(worker->config->vsink, NULL); + if (!worker->vsink) { + worker->vsink = gst_element_factory_make("xvimagesink", NULL); + } + if (!worker->vsink) { + g_critical("Failed to create pipeline video sink"); + _send_error(worker, + g_error_new(WORKER_ERROR, + WORKER_ERROR_UNABLE_TO_PERFORM, + "Could not create video sink")); + g_assert_not_reached(); + } + gst_object_ref_sink(worker->vsink); + + //special case for xvimagesink + { + gchar* element_name = gst_element_get_name(worker->vsink); + g_object_set(G_OBJECT(worker->vsink), + "colorkey", 0x080810, + NULL); + if (g_str_has_prefix(element_name, "xvimagesink")) + { + g_object_set(G_OBJECT(worker->vsink), + "handle-events", TRUE, + "force-aspect-ratio", TRUE, + NULL); + } + g_free(element_name); + } + + //do not dare to set video-sink in any other state + if( worker->pipeline && worker->state == GST_STATE_NULL ) + { + g_object_set(worker->pipeline, + "video-sink", worker->vsink, + NULL); + } + } + + /* We don't want to set XID to video sink here when in READY state, because + it prevents "prepare-xwindow-id" message. Setting it when we are + PAUSED or PLAYING is fine, because we already got "prepare-xwindow-id". */ + if(worker->state == GST_STATE_PLAYING || + worker->state == GST_STATE_PAUSED) + { + /* Check if we should use it right away */ + mafw_gst_renderer_worker_apply_xid(worker); + } + + _emit_property(worker, WORKER_PROPERTY_XID, G_TYPE_UINT, &worker->xid); + +} + +XID mafw_gst_renderer_worker_get_xid(MafwGstRendererWorker *worker) +{ + return worker->xid; +} + +void mafw_gst_renderer_worker_set_render_rectangle(MafwGstRendererWorker *worker, render_rectangle *rect) +{ + /* Store the target window id */ + g_debug("Setting render rectangle: X:%d,Y:%d Width:%d, Height:%d", + rect->x, rect->y, rect->width, rect->height); + + worker->x_overlay_rectangle.x = rect->x; + worker->x_overlay_rectangle.y = rect->y; + worker->x_overlay_rectangle.width = rect->width; + worker->x_overlay_rectangle.height = rect->height; + + /* Check if we should use it right away */ + mafw_gst_renderer_worker_apply_render_rectangle(worker); + + GValueArray *rect_array = g_value_array_new(4); + GValue x; + GValue y; + GValue width; + GValue height; + + _set_value(&x, G_TYPE_INT, &(rect->x)); + _set_value(&y, G_TYPE_INT, &(rect->y)); + _set_value(&width, G_TYPE_INT, &(rect->width)); + _set_value(&height, G_TYPE_INT, &(rect->height)); + + g_value_array_insert(rect_array, 0, &x ); + g_value_array_insert(rect_array, 1, &y ); + g_value_array_insert(rect_array, 2, &width ); + g_value_array_insert(rect_array, 3, &height ); + + GValue value; + memset(&value, 0, sizeof(value)); + g_value_init(&value, G_TYPE_VALUE_ARRAY); + g_value_take_boxed(&value, rect_array); + + _emit_property(worker, WORKER_PROPERTY_RENDER_RECTANGLE, G_TYPE_VALUE_ARRAY, &value); + + g_value_unset(&value); +} + +const render_rectangle* mafw_gst_renderer_worker_get_render_rectangle(MafwGstRendererWorker *worker) +{ + UNUSED(worker); + return &worker->x_overlay_rectangle; +} + +gboolean mafw_gst_renderer_worker_get_autopaint(MafwGstRendererWorker *worker) +{ + return worker->autopaint; +} + +void mafw_gst_renderer_worker_set_autopaint(MafwGstRendererWorker *worker, + gboolean autopaint) +{ + /* TODO Is this a bug or a feature? */ + worker->autopaint = autopaint; + if (worker->vsink) + g_object_set(worker->vsink, "autopaint-colorkey", autopaint, NULL); + + _emit_property(worker, + WORKER_PROPERTY_AUTOPAINT, + G_TYPE_BOOLEAN, + &autopaint); + +} + +gboolean mafw_gst_renderer_worker_set_playback_speed(MafwGstRendererWorker* worker, + gfloat speed) +{ + gboolean retVal = FALSE; + + if (worker->state == GST_STATE_PLAYING) + { + worker->playback_speed = speed; + + gint64 current_position; + GstFormat format = GST_FORMAT_TIME; + + if (worker->pipeline && gst_element_query_position(worker->pipeline, + &format, + ¤t_position)) + { + + retVal = gst_element_seek(worker->pipeline, + speed, + GST_FORMAT_DEFAULT, + GST_SEEK_FLAG_SKIP | GST_SEEK_FLAG_KEY_UNIT, + GST_SEEK_TYPE_NONE, + current_position, + GST_SEEK_TYPE_NONE, + GST_CLOCK_TIME_NONE); + + if(retVal) + { + _emit_property(worker, + WORKER_PROPERTY_PLAYBACK_SPEED, + G_TYPE_FLOAT, + &speed); + } + } + } + + return retVal; +} + +gfloat mafw_gst_renderer_worker_get_playback_speed(MafwGstRendererWorker* worker) +{ + return worker->playback_speed; +} + +void mafw_gst_renderer_worker_set_force_aspect_ratio(MafwGstRendererWorker *worker, gboolean force) +{ + + worker->force_aspect_ratio = force; + if (worker->vsink) + { + g_object_set(worker->vsink, "force-aspect-ratio", force, NULL); + } + _emit_property(worker, WORKER_PROPERTY_FORCE_ASPECT_RATIO, G_TYPE_BOOLEAN, &force); + +} + +gboolean mafw_gst_renderer_worker_get_force_aspect_ratio(MafwGstRendererWorker *worker) +{ + return worker->force_aspect_ratio; +} + +gint mafw_gst_renderer_worker_get_colorkey(MafwGstRendererWorker *worker) +{ + return worker->colorkey; +} + +gboolean mafw_gst_renderer_worker_get_seekable(MafwGstRendererWorker *worker) +{ + return worker->media.seekable == SEEKABILITY_SEEKABLE; +} + +gboolean mafw_gst_renderer_worker_get_streaming(MafwGstRendererWorker *worker) +{ + return uri_is_stream(worker->media.location); +} + +const char* mafw_gst_renderer_worker_get_uri(MafwGstRendererWorker *worker) +{ + return worker->media.location; +} + +static void _do_play(MafwGstRendererWorker *worker) +{ + g_assert(worker != NULL); + + if (worker->pipeline == NULL) { + g_debug("play without a pipeline!"); + return; + } + worker->report_statechanges = TRUE; + + /* If we have to stay paused, we do and add the ready timeout. Otherwise, we + * move the pipeline */ + if (!worker->stay_paused) { + /* If pipeline is READY, we move it to PAUSED, otherwise, to PLAYING */ + if (worker->state == GST_STATE_READY) { + gst_element_set_state(worker->pipeline, GST_STATE_PAUSED); + g_debug("setting pipeline to PAUSED"); + } else { + gst_element_set_state(worker->pipeline, GST_STATE_PLAYING); + g_debug("setting pipeline to PLAYING"); + } + } + else { + g_debug("staying in PAUSED state"); + _add_ready_timeout(worker); + } +} + +void mafw_gst_renderer_worker_play(MafwGstRendererWorker *worker, + const gchar *uri) +{ + g_assert(uri); + + mafw_gst_renderer_worker_stop(worker); + _reset_media_info(worker); + + /* Set the item to be played */ + worker->media.location = g_strdup(uri); + + _start_play(worker); +} + +/* + * Currently, stop destroys the Gst pipeline and resets the worker into + * default startup configuration. + */ +void mafw_gst_renderer_worker_stop(MafwGstRendererWorker *worker) +{ + g_debug("worker stop"); + g_assert(worker != NULL); + + /* If location is NULL, this is a pre-created pipeline */ + if (worker->async_bus_id && worker->pipeline && !worker->media.location) + return; + + _reset_pipeline_and_worker(worker); + + /* context framework adaptation starts */ + if (worker->context_nowplaying) { + context_provider_map_free(worker->context_nowplaying); + worker->context_nowplaying = NULL; + } + context_provider_set_null(CONTEXT_PROVIDER_KEY_NOWPLAYING); + /* context framework adaptation ends */ + + /* We are not playing, so we can let the screen blank */ + if (worker->blanking__control_handler) + { + worker->blanking__control_handler(worker, worker->owner, FALSE); + } + + /* And now get a fresh pipeline ready */ + _construct_pipeline(worker, worker->config); +} + +void mafw_gst_renderer_worker_pause(MafwGstRendererWorker *worker) +{ + g_assert(worker != NULL); + + if (worker->buffering && worker->state == GST_STATE_PAUSED && + !worker->prerolling) + { + /* If we are buffering and get a pause, we have to + * signal state change and stay_paused */ + g_debug("Pausing while buffering, signalling state change"); + + /* We need to make sure that we go into real PAUSE state */ + if (worker->blanking__control_handler) + { + worker->blanking__control_handler(worker, worker->owner, FALSE); + } + _do_pause_postprocessing(worker); + } + else + { + worker->report_statechanges = TRUE; + if (worker->seek_position == -1 && worker->state == GST_STATE_PLAYING ) + { + gst_element_set_state(worker->pipeline, GST_STATE_PAUSED); + if (worker->blanking__control_handler) + { + worker->blanking__control_handler(worker, worker->owner, FALSE); + } + } + } + + worker->stay_paused = TRUE; + worker->pause_frame_taken = FALSE; +} + +/* + * Notifier to call when audio/video routing changes + */ +void mafw_gst_renderer_worker_notify_media_destination( + MafwGstRendererWorker *worker, + GSList *destinations) +{ + g_assert(worker != NULL); + g_assert(destinations != NULL); + + /* 1. update our records of current destinations */ + g_slist_free(worker->destinations); + worker->destinations = g_slist_copy(destinations); + + /* 2. prevent blanking if we are playing video and outputting it on our own + * display, otherwise disable it */ + if (worker->blanking__control_handler && + worker->media.has_visual_content && + worker->state == GST_STATE_PLAYING && + g_slist_find(worker->destinations, + GINT_TO_POINTER(WORKER_OUTPUT_BUILTIN_DISPLAY))) + { + worker->blanking__control_handler(worker, worker->owner, TRUE); + } + else + { + worker->blanking__control_handler(worker, worker->owner, FALSE); + } + + /* 3. disabling Dolby Headphone effect if not outputting to audio jack or + * bluetooth headphones, otherwise using the effect. Actual route check is done + * in set_dolby_*****_property function*/ + set_dolby_music_property(worker, worker->config->mobile_surround_music.state); + set_dolby_video_property(worker, worker->config->mobile_surround_video.state); + +} + +void mafw_gst_renderer_worker_pause_at(MafwGstRendererWorker *worker, guint position) +{ + /* the current implementation works only from ready i.e. stopped state */ + g_assert( worker != NULL); + worker->stay_paused = TRUE; + worker->pause_frame_taken = FALSE; + worker->seek_position = position; + + if( worker->vsink ) + { + g_object_set(worker->vsink, "show-preroll-frame", + FALSE, NULL); + } +} + +void mafw_gst_renderer_worker_resume(MafwGstRendererWorker *worker) +{ + worker->stay_paused = FALSE; + if (worker->buffering && worker->state == GST_STATE_PAUSED && + !worker->prerolling) { + /* If we are buffering we cannot resume, but we know that the pipeline + * will be moved to PLAYING as stay_paused is FALSE, so we just + * activate the state change report, this way as soon as buffering + * is finished the pipeline will be set to PLAYING and the state + * change will be reported */ + worker->report_statechanges = TRUE; + g_debug("Resumed while buffering, activating pipeline state changes"); + /* Notice though that we can receive the Resume before we get any + buffering information. In that case we go with the "else" branch + and set the pipeline to to PLAYING. However, it is possible that + in this case we get the fist buffering signal before the PAUSED -> + PLAYING state change. In that case, since we ignore state changes + while buffering we never signal the state change to PLAYING. We + can only fix this by checking, when we receive a PAUSED -> PLAYING + transition if we are buffering, and in that case signal the state + change (if we get that transition while buffering is on, it can + only mean that the client resumed playback while buffering, and + we must notify the state change) */ + } else { + _do_play(worker); + } + + /* we want to resume no use for these timers anymore */ + _remove_pause_frame_timeout(worker); + _remove_ready_timeout(worker); +} + +MafwGstRendererWorker *mafw_gst_renderer_worker_new(gpointer owner) +{ + MafwGstRendererWorker *worker; + + g_debug("%s", G_STRFUNC); + + worker = g_new0(MafwGstRendererWorker, 1); + worker->owner = owner; + worker->report_statechanges = TRUE; + worker->state = GST_STATE_NULL; + worker->seek_position = -1; + worker->ready_timeout = 0; + worker->pause_frame_timeout = 0; + worker->duration_seek_timeout = 0; + worker->duration_seek_timeout_loop_count = 0; + worker->in_ready = FALSE; + worker->xid = 0; + worker->x_overlay_rectangle.x = -1; + worker->x_overlay_rectangle.y = -1; + worker->x_overlay_rectangle.width = -1; + worker->x_overlay_rectangle.height = -1; + worker->autopaint = TRUE; + worker->playback_speed = 1; + worker->colorkey = -1; + worker->vsink = NULL; + worker->asink = NULL; + worker->tsink = NULL; + worker->amixer = NULL; + worker->audiobin = NULL; + worker->tag_list = NULL; + worker->current_metadata = NULL; + worker->media.seekable = SEEKABILITY_SEEKABLE; + + worker->destinations = NULL; + + worker->current_frame_on_pause = FALSE; + worker->taking_screenshot = FALSE; + worker->force_aspect_ratio = TRUE; + _init_tmp_files_pool(worker); + worker->notify_seek_handler = NULL; + worker->notify_pause_handler = NULL; + worker->notify_play_handler = NULL; + worker->notify_buffer_status_handler = NULL; + worker->notify_eos_handler = NULL; + worker->notify_metadata_handler = NULL; + worker->notify_error_handler = NULL; + worker->blanking__control_handler = NULL; + worker->screenshot_handler = NULL; + + worker->config = _create_default_configuration(); + + worker->seeker = mafw_gst_renderer_seeker_new(); + + if (!_context_fw_initialised) + { + /* context framework adaptation starts */ + if (context_provider_init(DBUS_BUS_SESSION, CONTEXT_PROVIDER_BUS_NAME)) { + _context_fw_initialised = TRUE; + context_provider_install_key(CONTEXT_PROVIDER_KEY_NOWPLAYING, FALSE, + NULL, NULL); + g_debug("Initialized context framework provider"); + } + else { + g_warning("Could not initialize context framework provider"); + } + } + /* context framework adaptation ends */ + + return worker; + +} + +void mafw_gst_renderer_worker_exit(MafwGstRendererWorker *worker) +{ + _destroy_tmp_files_pool(worker); + _reset_pipeline_and_worker(worker); + + /* We are not playing, so we can let the screen blank */ + if (worker->blanking__control_handler) + { + worker->blanking__control_handler(worker, worker->owner, FALSE); + } + + /* now finally sinks/bins are released */ + if( worker->audiobin ) + { + gst_object_unref(worker->audiobin); + worker->audiobin = NULL; + } + else if( worker->asink ) + { + gst_object_unref(worker->asink); + worker->asink = NULL; + } + + if( worker->vsink ) + { + gst_object_unref(worker->vsink); + worker->vsink = NULL; + } + + context_provider_stop(); + _context_fw_initialised = FALSE; + + if( worker->destinations ) + { + g_slist_free(worker->destinations); + worker->destinations = NULL; + } + + if( worker->config ) + { + _free_configuration(worker->config); + worker->config = NULL; + } + + if( worker->seeker ) + { + mafw_gst_renderer_seeker_free(worker->seeker); + worker->seeker = NULL; + } +} diff --git a/qmafw-gst-subtitles-renderer/unittests/common/LibCredsStub.cpp b/qmafw-gst-subtitles-renderer/unittests/common/LibCredsStub.cpp new file mode 100644 index 0000000..f372630 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/common/LibCredsStub.cpp @@ -0,0 +1,51 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#include +#include + +namespace Stub +{ + bool global_route_provider_trusted = true; +} + +creds_t creds_gettask(pid_t /*pid*/) +{ + return (creds_t)1; +} + +int creds_have_p(const creds_t creds, creds_type_t type, creds_value_t value) +{ + Q_UNUSED(creds); + Q_UNUSED(type); + Q_UNUSED(value); + + if( Stub::global_route_provider_trusted ) + { + return 1; + } + + return 0; +} + +long creds_str2creds(const char *credential, creds_value_t *value) +{ + Q_UNUSED(credential); + Q_UNUSED(value); + + return 1234; +} diff --git a/qmafw-gst-subtitles-renderer/unittests/common/MafwBasicRendererStub.cpp b/qmafw-gst-subtitles-renderer/unittests/common/MafwBasicRendererStub.cpp new file mode 100644 index 0000000..c3a1e3b --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/common/MafwBasicRendererStub.cpp @@ -0,0 +1,42 @@ +#include +#include "MafwStubHelper.h" + +MafwStubHelper* m_stubHelper = 0; +MafwRendererPolicy* m_policyStub; + +void setStubHelper(MafwStubHelper* stubHlp) +{ + m_stubHelper = stubHlp; +} + +void setMafwRendererPolicy(MafwRendererPolicy *policy ) +{ + m_policyStub = policy; +} + +bool MafwBasicRenderer::initialize() +{ + return m_stubHelper->getReturn("initialize").toBool(); +} + +bool MafwBasicRenderer::setDefaultRendererPolicy(MafwRendererPolicy::PolicyGroup) +{ + return m_stubHelper->getReturn("setDefaultRendererPolicy").toBool(); +} + +MafwRendererPolicy* MafwBasicRenderer::rendererPolicy() const +{ + return m_policyStub; +} + +bool MafwBasicRenderer::setMafwProperty(const QString &name, const QVariant &value) +{ + return MafwRenderer::setMafwProperty(name, value); +} + +bool MafwBasicRenderer::pause() +{ + doPause(); + return true; +} + diff --git a/qmafw-gst-subtitles-renderer/unittests/common/MafwPlaylistFileUtilityStub.cpp b/qmafw-gst-subtitles-renderer/unittests/common/MafwPlaylistFileUtilityStub.cpp new file mode 100644 index 0000000..129a1e9 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/common/MafwPlaylistFileUtilityStub.cpp @@ -0,0 +1,66 @@ +#include "MafwGstRendererPlaylistFileUtility.h" +#include "MafwGstRenderer.h" +#include +#include +#include +#include "MafwStubHelper.h" + +MafwGstRenderer* m_rnd = 0; +QStringList stubPlaylistFileUtilityUris = QStringList() << "testUri1" << "testUri2"; + +MafwGstRendererPlaylistFileUtility::MafwGstRendererPlaylistFileUtility(QObject* parent): + QObject(parent) +{ + m_rnd = static_cast(parent); +} + +MafwGstRendererPlaylistFileUtility::~MafwGstRendererPlaylistFileUtility(){} + +void MafwGstRendererPlaylistFileUtility::parsePlaylistFile(const QUrl&) +{ + qDebug() << "MafwGstRendererPlaylistFileUtility::parsePlaylistFile"; + m_uriList.append(stubPlaylistFileUtilityUris); + Q_EMIT parsingReady(true); + Q_EMIT firstItemParsed(); +} + +QStringList MafwGstRendererPlaylistFileUtility::getUriList() +{ + return m_uriList; +} + +QString MafwGstRendererPlaylistFileUtility::takeFirstUri() +{ + if (m_uriList.isEmpty()) + { + return QString(); + } + else + { + return m_uriList.takeFirst(); + } +} + +void MafwGstRendererPlaylistFileUtility::uriParsed(TotemPlParser*, + gchar*, + gpointer, + MafwGstRendererPlaylistFileUtility*) +{} + +void MafwGstRendererPlaylistFileUtility::readyCb(TotemPlParser*, GAsyncResult*, MafwGstRendererPlaylistFileUtility*) +{} + +void MafwGstRendererPlaylistFileUtility::setPendingError(MafwError& error) +{ + m_pendingError = error; +} + +/******************************************************************** + * MafwGstRendererPlaylistFileUtility::takePendingError + ********************************************************************/ +MafwError MafwGstRendererPlaylistFileUtility::takePendingError() +{ + MafwError error = m_pendingError; + m_pendingError = MafwError(); + return error; +} diff --git a/qmafw-gst-subtitles-renderer/unittests/common/MafwRendererPolicyStub.cpp b/qmafw-gst-subtitles-renderer/unittests/common/MafwRendererPolicyStub.cpp new file mode 100644 index 0000000..7018be8 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/common/MafwRendererPolicyStub.cpp @@ -0,0 +1,39 @@ +#include "MafwRendererPolicyStub.h" + +MafwRendererPolicyStub::MafwRendererPolicyStub() +{ +} + +MafwRendererPolicyStub::~MafwRendererPolicyStub() +{ + +} + +bool MafwRendererPolicyStub::initialize(MafwRendererPolicy::PolicyGroup group) +{ + return group == MafwRendererPolicy::MediaPlayer; +} + +void MafwRendererPolicyStub::setDefaultResources(MafwRendererPolicy::PolicyResourceFlags) +{ + //nothing to do here +} + +void MafwRendererPolicyStub::request(MafwRendererPolicy::PolicyResourceFlags) +{ + +} + +void MafwRendererPolicyStub::release(MafwRendererPolicy::PolicyResourceFlags flags) +{ + m_latestRelease = flags; +} + +MafwRendererPolicy::PolicyResourceFlags MafwRendererPolicyStub::latestReleaseFlags() +{ + MafwRendererPolicy::PolicyResourceFlags toReturn = m_latestRelease; + m_latestRelease = 0; + return toReturn; +} + + diff --git a/qmafw-gst-subtitles-renderer/unittests/common/MafwRendererPolicyStub.h b/qmafw-gst-subtitles-renderer/unittests/common/MafwRendererPolicyStub.h new file mode 100644 index 0000000..5a476e9 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/common/MafwRendererPolicyStub.h @@ -0,0 +1,21 @@ +#include + +class MafwRendererPolicyStub : public MafwRendererPolicy +{ + Q_OBJECT + +public: + MafwRendererPolicyStub(); + ~MafwRendererPolicyStub(); + + bool initialize(MafwRendererPolicy::PolicyGroup); + void setDefaultResources(QFlags); + void request(QFlags); + void release(QFlags); + + //testing functions + MafwRendererPolicy::PolicyResourceFlags latestReleaseFlags(); + +private: + MafwRendererPolicy::PolicyResourceFlags m_latestRelease; +}; diff --git a/qmafw-gst-subtitles-renderer/unittests/common/MafwRoutingInfoHandlerStub.cpp b/qmafw-gst-subtitles-renderer/unittests/common/MafwRoutingInfoHandlerStub.cpp new file mode 100644 index 0000000..f33d822 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/common/MafwRoutingInfoHandlerStub.cpp @@ -0,0 +1,67 @@ + +/* + * This file is part of QMAFW + * + * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#include "MafwRoutingInfoHandler.h" + +#include +#include +#include + +#include +#include +#include + +bool routeTrusted = false; + +MafwRoutingInfoHandler::MafwRoutingInfoHandler(ContextProperty& property) : + m_property(property) +{ + +} + +MafwRoutingInfoHandler::~MafwRoutingInfoHandler() +{ +} + +void MafwRoutingInfoHandler::startProviderCheck() +{ + qDebug() << "startProviderCheck"; +} + + void MafwRoutingInfoHandler::callFinishedSlot(QDBusPendingCallWatcher*) + { + qDebug() << "callFinishedSlot"; + } + +void MafwRoutingInfoHandler::gotReply(int) +{ + Q_EMIT routeChanged(routeTrusted); +} + +void MafwRoutingInfoHandler::error() +{ + qDebug() << __PRETTY_FUNCTION__; + + Q_EMIT routeChanged(false); +} + +bool MafwRoutingInfoHandler::isPidTrusted (int) +{ + + return true; +} diff --git a/qmafw-gst-subtitles-renderer/unittests/common/MafwStubHelper.cpp b/qmafw-gst-subtitles-renderer/unittests/common/MafwStubHelper.cpp new file mode 100644 index 0000000..9e2d4fa --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/common/MafwStubHelper.cpp @@ -0,0 +1,140 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + + +#include +#include + +#include "MafwStubHelper.h" + +MafwStubHelper::MafwStubHelper() +{ + m_expectedCalls = QQueue(); +} + +MafwStubHelper::~MafwStubHelper() +{ + clear(); +} + +void MafwStubHelper::printExpects() const +{ + QStringList expectedNames; + for(int i=0; iexpectedName; + } + qDebug() << "Expected calls enqueued (" << m_expectedCalls.size() << "): \n\t" << expectedNames.join(",\n\t "); +} + +void MafwStubHelper::expect(const QString& function, QVariant returnVal) +{ + QList emptyParams; + QList returnList; + returnList << returnVal; + expect(function, emptyParams, returnList); +} + + +void MafwStubHelper::expect(const QString function, const QList params, + const QList returns) +{ + qDebug() << "MafwStubHelper::expect, function = " << function << " " << returns.at(0); + + ExpectedItem* item = new ExpectedItem; + item->expectedName = function; + item->expectedParams = params; + item->returnValues = returns; + m_expectedCalls.enqueue(item); +} + +QVariant MafwStubHelper::getReturn(const QString& function) +{ + QList emptyParams; + QList returnList; + getReturn(function, emptyParams, returnList); + if (returnList.isEmpty()) + { + return QVariant(); + } + else + { + return returnList.first(); + } +} + +void MafwStubHelper::getReturn(const QString function, const QList params, + QList& returns) +{ + // Check if the call is expected + if ( m_expectedCalls.isEmpty() ) + { + qDebug() << "MafwStubHelper::getReturn, function = " << function <<", no expected calls"; + return; + } + if (!m_expectedCalls.isEmpty() && function.compare(m_expectedCalls.head()->expectedName)) + { + qDebug() << "MafwStubHelper::getReturn: " << function << ", not expected (2)"; + printExpects(); + return; + } + ExpectedItem* item = m_expectedCalls.dequeue(); + + // Check if the parameters match + if (!item->expectedParams.isEmpty()) + { + for (int i = 0; i < item->expectedParams.count() && item->expectedParams.count() == params.count(); ++i) + { + if (item->expectedParams.at(i) != params.at(i)) + { + qDebug() << "MafwStubHelper::getReturn: " << function <<", not expected (2)"; + return; + } + } + } + // Expected parameters list was empty but given was not + else if (!params.isEmpty()) + { + qDebug() << "MafwStubHelper::getReturn: " << function <<", not expected (3)"; + return; + } + else + { + } + + // Everything ok, let's find the return values + returns = item->returnValues; + + qDebug() << "MafwStubHelper::getReturn, function: " << function << ", returns: " << returns; + + delete item; +} + +bool MafwStubHelper::allCallsConsumed() const +{ + return m_expectedCalls.isEmpty(); +} + +void MafwStubHelper::clear() +{ + qDebug() << "MafwStubHelper::clear()"; + + qDeleteAll(m_expectedCalls); + m_expectedCalls.clear(); +} + +// End of file diff --git a/qmafw-gst-subtitles-renderer/unittests/common/MafwStubHelper.h b/qmafw-gst-subtitles-renderer/unittests/common/MafwStubHelper.h new file mode 100644 index 0000000..87a6871 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/common/MafwStubHelper.h @@ -0,0 +1,108 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#ifndef MAFWSTUBHELPER_H_ +#define MAFWSTUBHELPER_H_ + +#include +#include +#include +#include +#include + +/** + * This class is meant to help implementing stubs needed in unit testing. + * + * Usage: Create instance of MafwStubHelper and provide your stub access + * to it. Initialize MafwStubHelper with function calls the stub is + * expected to get. Implement your stub to ask for return values from + * MafwStubHelper. At the end of a test case, check if all expected + * calls are consumed. + */ + + +class MafwStubHelper +{ +public: + MafwStubHelper(); + ~MafwStubHelper(); + + /** + * expect - Adds call to expected calls + * + * @param function Name of the expected function call, without paranthesis. + * @param params List of expected parameters given to function call. + * @param returns List of return values to be given to the stub. + */ + void expect(const QString function, const QList params, const QList returns); + + /** + * expect - Adds call to expected calls + * + * @param function Name of the expected function call, without paranthesis. + * @param returnVal The return value to be given to the stub. + */ + void expect(const QString& function, QVariant returnVal); + + /** + * getReturn - Consumes call from expected calls + * + * @param function Name of the function call, without paranthesis. + * @param params List of parameters given to function call. + * @param returns Empty list for getting the return values. + * List is empty at return if function wasn't the next expected function call. + */ + void getReturn(const QString function, const QList params, QList& returns); + + /** + * getReturn - Consumes call from expected calls + * + * @param function Name of the function call, without paranthesis. + * @return return value is invalid if function wasn't the next expected function call. + */ + QVariant getReturn(const QString& function); + + /** + * allCallsConsumed - Checks if all expected calls are consumed + * + * @return true, if all expected calls are consumed + * false otherwise. + */ + bool allCallsConsumed() const; + + /** + * clear - Clears all data + */ + void clear(); + +private: + void printExpects() const; + + +private: + + struct ExpectedItem + { + QString expectedName; + QList expectedParams; + QList returnValues; + }; + + QQueue m_expectedCalls; +}; + +#endif /*MAFWSTUBHELPER_H_*/ diff --git a/qmafw-gst-subtitles-renderer/unittests/common/QNetworkStubs.cpp b/qmafw-gst-subtitles-renderer/unittests/common/QNetworkStubs.cpp new file mode 100644 index 0000000..d97aa74 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/common/QNetworkStubs.cpp @@ -0,0 +1,114 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#include "QNetworkStubs.h" + +#include +#include +#include +#include + + +NetworkStubHelper networkStub; +int NetworkStubHelper::networkConfigurationManagerInstances = 0; + +/** + + QNetworkConfiguration + + **/ + +bool NetworkStubHelper::currentCreatedConfigIsValid = false; + +static int staticCreationId = 0; +class QNetworkConfigurationPrivate : public QSharedData +{ +public: + int creationId; + bool isValid; +}; + +QNetworkConfiguration::QNetworkConfiguration() +{ + d = new QNetworkConfigurationPrivate; + d->creationId = ++staticCreationId; + d->isValid = networkStub.currentCreatedConfigIsValid; +} + +QNetworkConfiguration::~QNetworkConfiguration() +{ + +} + +QNetworkConfiguration::QNetworkConfiguration(const QNetworkConfiguration &other) +{ + d = new QNetworkConfigurationPrivate; + d->creationId = other.d->creationId; +} + +QNetworkConfiguration::StateFlags QNetworkConfiguration::state() const +{ + return networkStub.m_currentConfState; +} + +QString QNetworkConfiguration::name() const +{ + return "Test conf"; +} + +bool QNetworkConfiguration::isValid() const +{ + return d->isValid; +} + +QNetworkConfiguration& QNetworkConfiguration::operator=(QNetworkConfiguration const &other) +{ + d->creationId = other.d->creationId; + return *this; +} + +bool QNetworkConfiguration::operator==(const QNetworkConfiguration &other ) const +{ + return d->creationId == other.d->creationId; +} + +/** + + QNetworkConfigurationManager + + **/ + +QNetworkConfigurationManager::QNetworkConfigurationManager(QObject *parent) + : + QObject(parent) +{ + ++networkStub.networkConfigurationManagerInstances; + if( networkStub.networkConfigurationManagerInstances == 1) + { + connect( &networkStub, SIGNAL(configurationChange(QNetworkConfiguration)), + this, SIGNAL(configurationChanged(QNetworkConfiguration))); + } +} + +QNetworkConfigurationManager::~QNetworkConfigurationManager() +{ + --networkStub.networkConfigurationManagerInstances; + if( networkStub.networkConfigurationManagerInstances == 0 ) + { + networkStub.disconnect(this); + } +} diff --git a/qmafw-gst-subtitles-renderer/unittests/common/QNetworkStubs.h b/qmafw-gst-subtitles-renderer/unittests/common/QNetworkStubs.h new file mode 100644 index 0000000..4c31744 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/common/QNetworkStubs.h @@ -0,0 +1,53 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#ifndef QNETWORKSTUBS_H +#define QNETWORKSTUBS_H + +#include +#include + +class QNetworkConfigurationManager; + +class NetworkStubHelper : public QObject +{ + Q_OBJECT + + static int networkConfigurationManagerInstances; + + friend class QNetworkConfiguration; + friend class QNetworkConfigurationManager; +public: + static bool currentCreatedConfigIsValid; + + void emitConfigurationChange(const QNetworkConfiguration &config, + QNetworkConfiguration::StateFlags confState) + { + m_currentConfState = confState; + Q_EMIT configurationChange(config); + } + + void changeCurrentConfiguration(int id); + +Q_SIGNALS: + void configurationChange(QNetworkConfiguration config); + +private: + QNetworkConfiguration::StateFlags m_currentConfState; +}; + +#endif // QNETWORKSTUBS_H diff --git a/qmafw-gst-subtitles-renderer/unittests/common/renderer-worker-stub.c b/qmafw-gst-subtitles-renderer/unittests/common/renderer-worker-stub.c new file mode 100644 index 0000000..b8deb3d --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/common/renderer-worker-stub.c @@ -0,0 +1,380 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#include //memset +#include "mafw-gst-renderer-worker.h" +#include "mafw-gst-renderer-utils.h" + +GError* global_error = 0; +gint global_position = 0; +gint global_worker_audio_destination = -1; +gint global_worker_video_destination = -1; +gchar *global_worker_uri = 0; +gint global_worker_stop_called = 0; +gint global_worker_seek_request = 0; +GSList *global_worker_destinations = NULL; +guint global_ready_timeout = 99999; + +guint m_volume; +gboolean m_mute; +gboolean m_current_frame_on_pause; +gboolean m_force_aspect_ratio; +gfloat m_playback_speed = 1; +XID m_xid; + +render_rectangle m_render_rectangle; +configuration *current_worker_conf; + + +gboolean global_worker_playing = FALSE; + +#define UNUSED(x) (void)(x) + +void mafw_gst_renderer_worker_set_volume(MafwGstRendererWorker *worker, + guint volume) +{ + UNUSED(worker); + m_volume = volume; +} + +guint mafw_gst_renderer_worker_get_volume(MafwGstRendererWorker *worker) +{ + UNUSED(worker); + return m_volume; +} + +void mafw_gst_renderer_worker_set_mute(MafwGstRendererWorker *worker, + gboolean mute) +{ + UNUSED(worker); + m_mute = mute; +} + +gboolean mafw_gst_renderer_worker_get_mute(MafwGstRendererWorker *worker) +{ + UNUSED(worker); + return m_mute; +} + +gint64 mafw_gst_renderer_worker_get_last_known_duration(MafwGstRendererWorker *worker) +{ + UNUSED(worker); + return 10; +} + +const char* mafw_gst_renderer_worker_get_uri(MafwGstRendererWorker *worker) +{ + UNUSED(worker); + return global_worker_uri; +} + +gboolean mafw_gst_renderer_worker_get_seekable(MafwGstRendererWorker *worker) +{ + UNUSED(worker); + return TRUE; +} + +void _free_configuration(configuration *config) +{ + if( !config ) + { + return; + } + + if( config->asink ) + { + g_free(config->asink); + } + if( config->vsink ) + { + g_free(config->vsink); + } + + g_free(config); +} + +void mafw_gst_renderer_worker_set_current_frame_on_pause( + MafwGstRendererWorker *worker, + gboolean current_frame_on_pause) +{ + UNUSED(worker); + m_current_frame_on_pause = current_frame_on_pause; +} + +gboolean mafw_gst_renderer_worker_get_current_frame_on_pause( + MafwGstRendererWorker *worker) +{ + UNUSED(worker); + return m_current_frame_on_pause; +} + +void mafw_gst_renderer_worker_set_position(MafwGstRendererWorker *worker, + GstSeekType seek_type, + gint position, GError **error) +{ + UNUSED(worker); + UNUSED(seek_type); + global_worker_seek_request = position; + *error = global_error; +} + +gint mafw_gst_renderer_worker_get_position(MafwGstRendererWorker *worker) +{ + UNUSED(worker); + return global_position; +} + +void mafw_gst_renderer_worker_set_xid(MafwGstRendererWorker *worker, XID xid) +{ + UNUSED(worker); + m_xid = xid; +} + +gboolean mafw_gst_renderer_worker_get_streaming(MafwGstRendererWorker *worker) +{ + return uri_is_stream(global_worker_uri); +} + +XID mafw_gst_renderer_worker_get_xid(MafwGstRendererWorker *worker) +{ + UNUSED(worker); + return m_xid; +} + + +void mafw_gst_renderer_worker_set_render_rectangle(MafwGstRendererWorker *worker, render_rectangle *rect) +{ + UNUSED(worker); + m_render_rectangle.x = rect->x; + m_render_rectangle.y = rect->y; + m_render_rectangle.width = rect->width; + m_render_rectangle.height = rect->height; +} + +const render_rectangle* mafw_gst_renderer_worker_get_render_rectangle(MafwGstRendererWorker *worker) +{ + UNUSED(worker); + return &m_render_rectangle; +} + + +gboolean mafw_gst_renderer_worker_get_autopaint(MafwGstRendererWorker *worker) +{ + UNUSED(worker); + return TRUE; +} + +void mafw_gst_renderer_worker_set_ready_timeout(MafwGstRendererWorker *worker, + guint seconds) +{ + UNUSED(worker); + global_ready_timeout = seconds; +} + +void mafw_gst_renderer_worker_set_autopaint(MafwGstRendererWorker *worker, + gboolean autopaint) +{ + UNUSED(worker); + UNUSED(autopaint); +} + +gint mafw_gst_renderer_worker_get_colorkey(MafwGstRendererWorker *worker) +{ + UNUSED(worker); + return 1; +} + +void mafw_gst_renderer_worker_play(MafwGstRendererWorker *worker, + const gchar *uri) +{ + UNUSED(worker); + UNUSED(uri); + global_worker_playing = TRUE; + g_free(global_worker_uri); + global_worker_uri = g_strdup(uri); + worker->notify_play_handler(worker, worker->owner); +} + +void mafw_gst_renderer_worker_stop(MafwGstRendererWorker *worker) +{ + UNUSED(worker); + global_worker_playing = FALSE; + ++global_worker_stop_called; + + g_free(global_worker_uri); + global_worker_uri = NULL; +} + +void mafw_gst_renderer_worker_pause(MafwGstRendererWorker *worker) +{ + UNUSED(worker); + global_worker_playing = FALSE; + worker->notify_pause_handler(worker, worker->owner); +} + +void mafw_gst_renderer_worker_pause_at(MafwGstRendererWorker *worker, guint position) +{ + UNUSED(worker); + UNUSED(position); +} + +void mafw_gst_renderer_worker_resume(MafwGstRendererWorker *worker) +{ + UNUSED(worker); +} + +MafwGstRendererWorker *mafw_gst_renderer_worker_new(gpointer owner) +{ + MafwGstRendererWorker *newWorker = g_new0(MafwGstRendererWorker, 1); + newWorker->owner = owner; + newWorker->config = g_new0(configuration, 1); + return newWorker; +} + +void mafw_gst_renderer_worker_exit(MafwGstRendererWorker *worker) +{ + if( worker ) + { + _free_configuration(worker->config); + worker->config = NULL; + } +} + +gboolean mafw_gst_renderer_worker_set_playback_speed(MafwGstRendererWorker *worker, gfloat speed) +{ + UNUSED(worker); + + if(!global_worker_playing) + { + return FALSE; + } + + m_playback_speed = speed; + + GValue v; + memset(&v, 0, sizeof(GValue)); + g_value_init(&v, G_TYPE_FLOAT); + g_value_set_float(&v, m_playback_speed); + + worker->notify_property_handler(worker, worker->owner, WORKER_PROPERTY_PLAYBACK_SPEED, &v); + g_value_unset(&v); + + return TRUE; +} + +gfloat mafw_gst_renderer_worker_get_playback_speed(MafwGstRendererWorker *worker) +{ + UNUSED(worker); + return m_playback_speed; +} + +void mafw_gst_renderer_worker_set_force_aspect_ratio(MafwGstRendererWorker *worker, gboolean force) +{ + UNUSED(worker); + m_force_aspect_ratio = force; +} + +gboolean mafw_gst_renderer_worker_get_force_aspect_ratio(MafwGstRendererWorker *worker) +{ + UNUSED(worker); + return m_force_aspect_ratio; +} + +void mafw_gst_renderer_worker_notify_media_destination(MafwGstRendererWorker *worker, + GSList *destination) +{ + UNUSED(worker); + g_slist_free(global_worker_destinations); + global_worker_destinations = g_slist_copy(destination); +} + +configuration* mafw_gst_renderer_worker_create_default_configuration(MafwGstRendererWorker *worker) +{ + configuration *config = g_malloc0(sizeof(configuration)); + config->asink = g_strdup("pulsesink"); + config->vsink = g_strdup("omapxvsink"); + config->flags = 67; + config->buffer_time = 300000; + config->latency_time = 100000; + + /* timers */ + config->milliseconds_to_pause_frame = 1000; + config->seconds_to_pause_to_ready = 3; + + /* dhmmixer */ + config->use_dhmmixer = TRUE; + + config->mobile_surround_music.state = 0; + config->mobile_surround_music.room = 2; + config->mobile_surround_music.color = 2; + config->mobile_surround_video.state = 0; + config->mobile_surround_video.room = 2; + config->mobile_surround_video.color = 2; + + return config; +} + +void mafw_gst_renderer_worker_set_configuration(MafwGstRendererWorker *worker, configuration *config) +{ + if( worker->config ) + { + _free_configuration(worker->config); + } + worker->config = config; + current_worker_conf = config; +} + +void set_dolby_music_property(MafwGstRendererWorker *worker, guint prop) +{ + worker->config->mobile_surround_music.state = prop; +} + +void set_dolby_music_sound_property(MafwGstRendererWorker *worker, gint prop, gboolean isRoomProperty) +{ + if (isRoomProperty) + { + worker->config->mobile_surround_music.room = prop; + } + else + { + worker->config->mobile_surround_music.color = prop; + } +} + +void set_dolby_video_property(MafwGstRendererWorker *worker, guint prop) +{ + worker->config->mobile_surround_video.state = prop; +} + +void set_dolby_video_sound_property(MafwGstRendererWorker *worker, gint prop, gboolean isRoomProperty) +{ + if (isRoomProperty) + { + worker->config->mobile_surround_video.room = prop; + } + else + { + worker->config->mobile_surround_video.color = prop; + } +} + +gint64 mafw_gst_renderer_worker_get_duration(MafwGstRendererWorker *worker) +{ + UNUSED(worker); + + return 0; +} diff --git a/qmafw-gst-subtitles-renderer/unittests/media b/qmafw-gst-subtitles-renderer/unittests/media new file mode 120000 index 0000000..11718d8 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/media @@ -0,0 +1 @@ +ut_MafwGstRendererWorker/media/ \ No newline at end of file diff --git a/qmafw-gst-subtitles-renderer/unittests/run_all_tests.sh b/qmafw-gst-subtitles-renderer/unittests/run_all_tests.sh new file mode 100755 index 0000000..d953d4d --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/run_all_tests.sh @@ -0,0 +1,35 @@ +#!/bin/sh +STATUS=0 + +qmake -project SUBDIRS+=`find . -type d -name ut_\*` -t subdirs -nopwd +qmake +if [ $SINGLE_PROCESS == "yes" ]; then + make -B +else + make -B -j3 +fi +if [ $? -ne 0 ]; then + exit 1 +fi + +files=`find . -maxdepth 2 -name 'ut_*' -type f -perm -u+x -print` + +echo '' > result.xml +echo '' >> result.xml +for file in $files; do + echo "Running test $file" + $file -lightxml -o tmp_result.xml; + + if [ $? -ne 0 ]; then + STATUS=2 + fi + + cat tmp_result.xml >>result.xml +done +echo '' >> result.xml +rm -f tmp_result.xml +sh run_valgrind.sh + +echo "Done" +exit $STATUS + diff --git a/qmafw-gst-subtitles-renderer/unittests/run_valgrind.sh b/qmafw-gst-subtitles-renderer/unittests/run_valgrind.sh new file mode 100755 index 0000000..41136e9 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/run_valgrind.sh @@ -0,0 +1,6 @@ +#!/bin/sh +export G_SLICE=always-malloc G_DEBUG=gc-friendly +#WAIT_TIMEOUT is for worker test +export WAIT_TIMEOUT=25000 +find . -maxdepth 2 -name 'ut_*' -type f -perm -u+x -print | xargs -t -i{} valgrind --leak-check=full --xml=yes --suppressions=test.suppressions --xml-file={}.vg.xml {} + diff --git a/qmafw-gst-subtitles-renderer/unittests/test.suppressions b/qmafw-gst-subtitles-renderer/unittests/test.suppressions new file mode 100644 index 0000000..aa76012 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/test.suppressions @@ -0,0 +1,2744 @@ +{ + + Memcheck:Leak + ... + fun:_ZN5Maemo14QmDisplayStateC1EP7QObject + ... +} +{ + + Memcheck:Leak + ... + fun:_ZN15ContextPropertyC1ERK7QStringP7QObject +} +{ + + Memcheck:Leak + fun:malloc + fun:dbus_malloc + ... + obj:/targets/*/usr/lib/libdbus-1.so.* + fun:dbus_message_new_signal + obj:/targets/*/usr/lib/libdbus-1.so.* + obj:/targets/*/usr/lib/libdbus-1.so.* + obj:/targets/*/usr/lib/libdbus-1.so.* + obj:/targets/*/usr/lib/libQtDBus.so.* + fun:_ZN15QDBusConnection12connectToBusENS_7BusTypeERK7QString + obj:/targets/*/usr/lib/libQtDBus.so.* + fun:_ZN15QDBusConnection10sessionBusEv +} +{ + + Memcheck:Leak + fun:calloc + fun:dbus_malloc0 + obj:/targets/*/usr/lib/libdbus-1.so.* + obj:/targets/*/usr/lib/libdbus-1.so.* + obj:/targets/*/usr/lib/libdbus-1.so.* + obj:/targets/*/usr/lib/libdbus-1.so.* + fun:dbus_parse_address + obj:/targets/*/usr/lib/libdbus-1.so.* + obj:/targets/*/usr/lib/libdbus-1.so.* + obj:/targets/*/usr/lib/libQtDBus.so.* + fun:_ZN15QDBusConnection12connectToBusENS_7BusTypeERK7QString + obj:/targets/*/usr/lib/libQtDBus.so.* +} +{ + + Memcheck:Leak + fun:realloc + fun:dbus_realloc + obj:/targets/*/usr/lib/libdbus-1.so.* + obj:/targets/*/usr/lib/libdbus-1.so.* + obj:/targets/*/usr/lib/libdbus-1.so.* + obj:/targets/*/usr/lib/libdbus-1.so.* + obj:/targets/*/usr/lib/libdbus-1.so.* + obj:/targets/*/usr/lib/libdbus-1.so.* + fun:dbus_message_new_signal + obj:/targets/*/usr/lib/libdbus-1.so.* + obj:/targets/*/usr/lib/libdbus-1.so.* + obj:/targets/*/usr/lib/libdbus-1.so.* +} +{ + + Memcheck:Leak + fun:memalign + fun:posix_memalign + obj:/targets/*/lib/libglib-2.0.so.* + fun:g_slice_alloc + ... + fun:g_main_context_new + fun:_ZN27QEventDispatcherGlibPrivateC1EP13_GMainContext + fun:_ZN20QEventDispatcherGlibC1EP7QObject + obj:/targets/*/usr/lib/libQtCore.so.* + obj:/targets/*/usr/lib/libQtCore.so.* + fun:start_thread +} +{ + + Memcheck:Leak + ... + obj:/targets/*/usr/lib/libgstreamer-0.1* + fun:g_option_context_parse +} +{ + + Memcheck:Leak + ... + obj:/targets/*/usr/lib/libgobject-2.* + fun:g_object_newv + ... + fun:gst_element_register + obj:/targets/*/usr/lib/libgstreamer-0.1* + obj:/targets/*/usr/lib/libgstreamer-0.1* +} +{ + + Memcheck:Leak + fun:*alloc + fun:g_malloc* + ... + obj:/targets/*/usr/lib/libgobject-2.* + fun:g_object_newv + fun:gst_task_pool_new + obj:/targets/*/usr/lib/libgstreamer-0.1* +} +{ + + Memcheck:Leak + ... + fun:realloc + fun:g_realloc + obj:/targets/*/lib/libglib-2.* + ... + fun:gst_caps_new_simple +} + +{ + + Memcheck:Leak + ... + fun:realloc + fun:g_realloc + obj:/targets/*/lib/libglib-2.* + ... + fun:gst_caps_copy + ... + fun:g_object_newv + fun:g_object_new_valist +} + +{ + + Memcheck:Leak + fun:malloc + ... + fun:gst_pad_set_caps + ... + obj:/targets/*/usr/lib/libgstreamer-0.1* +} + +{ + + Memcheck:Leak + fun:memalign + fun:posix_memalign + ... + fun:gst_pad_start_task + ... + obj:/targets/*/usr/lib/libgstbase-0.1* +} + + +{ + + Memcheck:Leak + ... + obj:/targets/*/usr/lib/libgstreamer-0.1* + fun:gst_registry_binary_read_cache + fun:gst_update_registry +} + +{ + + Memcheck:Leak + fun:memalign + ... + fun:g_object_new + fun:gst_pad_new_from_template + obj:/targets/*/usr/lib/libgstbase-0.1* +} + + +{ + + Memcheck:Leak + fun:memalign + fun:posix_memalign + obj:/targets/*/lib/libglib-2.* + fun:g_slice_alloc + fun:g_slice_alloc0 + fun:g_type_create_instance + obj:/targets/*/usr/lib/libgobject-2.* + fun:g_object_newv + fun:gst_task_pool_new + obj:/targets/*/usr/lib/libgstreamer-0.1* + fun:g_type_class_ref +} +{ + + Memcheck:Leak + fun:memalign + fun:posix_memalign + ... + fun:gst_buffer_new_and_alloc +} +{ + + Memcheck:Leak + fun:memalign + fun:posix_memalign + obj:/targets/*/lib/libglib-2.* + fun:g_slice_alloc + fun:g_slice_alloc0 + fun:g_type_create_instance + obj:/targets/*/usr/lib/libgobject-2.* + fun:g_object_newv + ... + fun:gst_element_factory_create + fun:gst_element_factory_make +} +{ + + Memcheck:Leak + fun:memalign + fun:posix_memalign + ... + fun:gst_caps_subtract + fun:gst_caps_is_subset + obj:/targets/*/usr/lib/libgstreamer-0.10.so.0.25.0 +} +{ + + Memcheck:Leak + fun:memalign + ... + fun:gst_element_change_state + ... +} + + +{ + + Memcheck:Leak + fun:malloc + fun:realloc + fun:g_realloc + obj:/targets/*/lib/libglib-2.0.so.* + fun:g_ptr_array_add + fun:g_main_context_check + obj:/targets/*/lib/libglib-2.0.so.* + fun:g_main_context_pending + fun:_ZN20QEventDispatcherGlib16hasPendingEventsEv + fun:_ZN16QCoreApplication16hasPendingEventsEv +} +{ + + Memcheck:Leak + fun:memalign + fun:posix_memalign + obj:/targets/*/lib/libglib-2.0.so.* + fun:g_slice_alloc + fun:g_array_sized_new + fun:g_array_new + fun:g_static_private_set + fun:g_get_filename_charsets + obj:/targets/*/lib/libglib-2.0.so.* + fun:g_thread_init_glib + fun:g_thread_init + fun:_ZN27QEventDispatcherGlibPrivateC2EP13_GMainContext +} +{ + + Memcheck:Leak + ... + fun:g_get_language_names + fun:g_thread_init + fun:_ZN27QEventDispatcherGlibPrivateC2EP13_GMainContext + obj:/targets/*/usr/lib/libQtGui.so.* + obj:/targets/*/usr/lib/libQtGui.so.* + fun:_ZN19QApplicationPrivate21createEventDispatcherEv + fun:_ZN16QCoreApplication4initEv + fun:_ZN16QCoreApplicationC2ER23QCoreApplicationPrivate +} +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_strdup + ... + fun:g_get_filename_charsets + obj:/targets/*/lib/libglib-2.0.so.* + fun:g_thread_init_glib + fun:g_thread_init + fun:_ZN27QEventDispatcherGlibPrivateC2EP13_GMainContext + obj:/targets/*/usr/lib/libQtGui.so.* + obj:/targets/*/usr/lib/libQtGui.so.* + fun:_ZN19QApplicationPrivate21createEventDispatcherEv +} +{ + + Memcheck:Leak + fun:calloc + fun:g_malloc0 + ... + fun:g_get_filename_charsets + obj:/targets/*/lib/libglib-2.0.so.* + fun:g_thread_init_glib + fun:g_thread_init + fun:_ZN27QEventDispatcherGlibPrivateC2EP13_GMainContext + obj:/targets/*/usr/lib/libQtGui.so.* + obj:/targets/*/usr/lib/libQtGui.so.* + fun:_ZN19QApplicationPrivate21createEventDispatcherEv + fun:_ZN16QCoreApplication4initEv +} +{ + + Memcheck:Leak + fun:realloc + fun:g_realloc + obj:/targets/*/lib/libglib-2.0.so.* + fun:g_array_set_size + fun:g_static_private_set + fun:g_get_language_names + fun:g_thread_init + fun:_ZN27QEventDispatcherGlibPrivateC2EP13_GMainContext + obj:/targets/*/usr/lib/libQtGui.so.* + obj:/targets/*/usr/lib/libQtGui.so.* + fun:_ZN19QApplicationPrivate21createEventDispatcherEv + fun:_ZN16QCoreApplication4initEv +} +{ + + Memcheck:Leak + fun:memalign + fun:posix_memalign + obj:/targets/*/lib/libglib-2.0.so.* + fun:g_slice_alloc + ... + ... + fun:_ZN27QEventDispatcherGlibPrivateC2EP13_GMainContext + obj:/targets/*/usr/lib/libQtGui.so.* + obj:/targets/*/usr/lib/libQtGui.so.* + fun:_ZN19QApplicationPrivate21createEventDispatcherEv +} + +{ + + Memcheck:Leak + fun:_Znwj + obj:/targets/*/usr/lib/libQtCore.so.* + obj:/targets/*/usr/lib/libQtCore.so.* + obj:/targets/*/usr/lib/libQtCore.so.* + fun:_ZN9QSettingsC1ENS_5ScopeERK7QStringS3_P7QObject + fun:_ZN19QApplicationPrivate18x11_apply_settingsEv + obj:/targets/*/usr/lib/libQtGui.so.* + obj:/targets/*/usr/lib/libQtGui.so.* + fun:_ZN19QApplicationPrivate9constructEP9_XDisplaymm + fun:_ZN12QApplicationC1ERiPPci + fun:main +} + +{ + + Memcheck:Leak + fun:malloc + obj:/targets/*/usr/lib/libfontconfig.so.* + obj:/targets/*/usr/lib/libfontconfig.so.* + ... + obj:/targets/*/usr/lib/libexpat.so.* + obj:/targets/*/usr/lib/libexpat.so.* + ... + fun:XML_ParseBuffer + fun:FcConfigParseAndLoad + fun:FcConfigParseAndLoad +} + +{ + + Memcheck:Leak + fun:malloc + fun:realloc + fun:g_realloc + obj:/targets/*/lib/libglib-2.0.so.* + fun:g_ptr_array_add + fun:g_main_context_check + obj:/targets/*/lib/libglib-2.0.so.* + fun:g_main_context_iteration + fun:_ZN20QEventDispatcherGlib13processEventsE6QFlagsIN10QEventLoop17ProcessEventsFlagEE + obj:/targets/*/usr/lib/libQtGui.so.* + fun:_ZN16QCoreApplication13processEventsE6QFlagsIN10QEventLoop17ProcessEventsFlagEEi + fun:_ZN5QTestL5qWaitEi +} + +{ + + Memcheck:Cond + fun:_dl_relocate_object + fun:dl_main + fun:_dl_sysdep_start + fun:_dl_start + obj:*/ld-2.5.so +} +{ + + Memcheck:Cond + fun:_dl_relocate_object + fun:dl_open_worker + fun:_dl_catch_error + fun:_dl_open + fun:do_dlopen + fun:_dl_catch_error + fun:dlerror_run + fun:__libc_dlopen_mode + fun:__gconv_find_shlib + fun:find_module + fun:__gconv_lookup_cache + fun:__gconv_find_transform + + obj:*/ld-2.5.so +} +{ + + Memcheck:Addr4 + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/libc-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/libc-2.5.so + fun:__libc_dlopen_mode + obj:/targets/*/lib/libc-2.5.so + obj:/targets/*/lib/libc-2.5.so + obj:/targets/*/lib/libc-2.5.so + obj:/targets/*/lib/libc-2.5.so + obj:/targets/*/lib/libc-2.5.so +} +{ + + Memcheck:Addr4 + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/libc-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/libc-2.5.so + fun:__libc_dlopen_mode + obj:/targets/*/lib/libc-2.5.so + obj:/targets/*/lib/libc-2.5.so + obj:/targets/*/lib/libc-2.5.so +} +{ + + Memcheck:Addr4 + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/libc-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/libc-2.5.so + fun:__libc_dlopen_mode + obj:/targets/*/lib/libc-2.5.so + obj:/targets/*/lib/libc-2.5.so +} +{ + + Memcheck:Addr4 + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/libdl-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/libdl-2.5.so +} +{ + + Memcheck:Addr4 + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/libdl-2.5.so + obj:/targets/*/lib/ld-2.5.so +} +{ + + Memcheck:Addr4 + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/libdl-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/libdl-2.5.so + fun:dlopen + fun:_ZN15QLibraryPrivate8load_sysEv + fun:_ZN15QLibraryPrivate4loadEv + fun:_ZN8QLibrary4loadEv + fun:_Z7qt_initP19QApplicationPrivateiP9_XDisplaymm + fun:_ZN19QApplicationPrivate9constructEP9_XDisplaymm + fun:_ZN12QApplicationC1ERiPPci + fun:main +} +{ + + Memcheck:Cond + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/libdl-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/libdl-2.5.so + fun:dlopen + fun:_ZN15QLibraryPrivate8load_sysEv + fun:_ZN15QLibraryPrivate4loadEv + fun:_ZN8QLibrary4loadEv + fun:_Z7qt_initP19QApplicationPrivateiP9_XDisplaymm + fun:_ZN19QApplicationPrivate9constructEP9_XDisplaymm + fun:_ZN12QApplicationC1ERiPPci +} +{ + + Memcheck:Cond + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/libdl-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/libdl-2.5.so + fun:dlopen + fun:_ZN15QLibraryPrivate8load_sysEv + fun:_ZN15QLibraryPrivate4loadEv + fun:_ZN8QLibrary4loadEv + fun:_Z7qt_initP19QApplicationPrivateiP9_XDisplaymm + fun:_ZN19QApplicationPrivate9constructEP9_XDisplaymm + fun:_ZN12QApplicationC1ERiPPci +} +{ + + Memcheck:Addr4 + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/libdl-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/libdl-2.5.so + fun:dlopen + fun:_ZN15QLibraryPrivate8load_sysEv + fun:_ZN15QLibraryPrivate4loadEv + fun:_ZN8QLibrary4loadEv + fun:_Z7qt_initP19QApplicationPrivateiP9_XDisplaymm + fun:_ZN19QApplicationPrivate9constructEP9_XDisplaymm + fun:_ZN12QApplicationC1ERiPPci + fun:main +} +{ + + Memcheck:Cond + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/libdl-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/libdl-2.5.so + fun:dlopen + fun:_ZN15QLibraryPrivate8load_sysEv + fun:_ZN15QLibraryPrivate4loadEv + fun:_ZN8QLibrary4loadEv + fun:_Z7qt_initP19QApplicationPrivateiP9_XDisplaymm + fun:_ZN19QApplicationPrivate9constructEP9_XDisplaymm + fun:_ZN12QApplicationC1ERiPPci +} +{ + + Memcheck:Cond + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/libdl-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/libdl-2.5.so + fun:dlopen + fun:_ZN15QLibraryPrivate8load_sysEv + fun:_ZN15QLibraryPrivate4loadEv + fun:_ZN8QLibrary4loadEv + fun:_Z7qt_initP19QApplicationPrivateiP9_XDisplaymm + fun:_ZN19QApplicationPrivate9constructEP9_XDisplaymm + fun:_ZN12QApplicationC1ERiPPci +} +{ + + Memcheck:Addr4 + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/libdl-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/libdl-2.5.so + fun:dlopen + fun:_ZN15QLibraryPrivate8load_sysEv + fun:_ZN15QLibraryPrivate4loadEv + fun:_ZN8QLibrary4loadEv + obj:/targets/*/usr/lib/libQtGui.so.4.6.0 + fun:_Z7qt_initP19QApplicationPrivateiP9_XDisplaymm + fun:_ZN19QApplicationPrivate9constructEP9_XDisplaymm + fun:_ZN12QApplicationC1ERiPPci +} +{ + + Memcheck:Cond + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/libdl-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/libdl-2.5.so + fun:dlopen + fun:_ZN15QLibraryPrivate8load_sysEv + fun:_ZN15QLibraryPrivate4loadEv + fun:_ZN8QLibrary4loadEv + obj:/targets/*/usr/lib/libQtGui.so.* + fun:_Z7qt_initP19QApplicationPrivateiP9_XDisplaymm + fun:_ZN19QApplicationPrivate9constructEP9_XDisplaymm +} +{ + + Memcheck:Addr4 + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/libdl-2.5.so + obj:/targets/*/lib/ld-2.5.so + obj:/targets/*/lib/libdl-2.5.so + fun:dlopen + fun:_ZN15QLibraryPrivate8load_sysEv + fun:_ZN15QLibraryPrivate4loadEv +} +{ + + Memcheck:Cond + fun:_ZN11QMetaObject16checkConnectArgsEPKcS1_ +} +{ + + Memcheck:Addr1 + fun:_ZN11QMetaObject16checkConnectArgsEPKcS1_ +} +{ + + Memcheck:Cond + fun:strlen + fun:_dl_init_paths + fun:dl_main + fun:_dl_sysdep_start + fun:_dl_start + obj:/targets/*/lib/ld-2.*.so +} +{ + + Memcheck:Cond + fun:_dl_relocate_object + fun:dl_main + fun:_dl_sysdep_start + fun:_dl_start + obj:/targets/*/lib/ld-2.*.so +} +{ + + Memcheck:Leak + fun:realloc + obj:/targets/*/usr/lib/libX11.so.* + obj:/targets/*/usr/lib/libX11.so.* + obj:/targets/*/usr/lib/libX11.so.* + fun:_XlcCreateLC + fun:_XlcDefaultLoader + fun:_XOpenLC + fun:_XrmInitParseInfo + obj:/targets/*/usr/lib/libX11.so.* + fun:XrmGetStringDatabase + obj:/targets/*/usr/lib/libX11.so.* + fun:XGetDefault +} +{ + + Memcheck:Leak + ... + fun:g_type_init_with_debug_flags + fun:g_type_init + ... +} + +{ + + Memcheck:Leak + fun:realloc + fun:g_realloc + ... + fun:g_type_register_static + fun:g_boxed_type_register_static + fun:g_value_get_type + ... +} + +{ + + Memcheck:Leak + fun:realloc + fun:g_realloc + ... + fun:g_type_register_static + fun:g_param_type_register_static + fun:gst_param_spec_mini_object_get_type + ... +} + + +{ + + Memcheck:Leak + fun:memalign + fun:posix_memalign + obj:/targets/*/lib/libglib-2.* + fun:g_slice_alloc + fun:g_slice_alloc0 + fun:g_type_create_instance + fun:gst_mini_object_new + fun:gst_buffer_new + obj:/targets/*/usr/lib/gstreamer-0.10/libgstcoreelements.so + obj:/targets/*/usr/lib/libgstbase-0.10* + obj:/targets/*/usr/lib/libgstbase-0.10* + obj:/targets/*/usr/lib/libgstreamer-0* +} + + + + +{ + + Memcheck:Leak + fun:memalign + fun:posix_memalign + obj:/targets/*/lib/libglib-2* + fun:g_slice_alloc + fun:g_slice_alloc0 + fun:gst_poll_new + fun:gst_poll_new_timer + obj:/targets/*/usr/lib/libgstreamer-0.1* + fun:g_type_create_instance + obj:/targets/*/usr/lib/libgobject-2* + fun:g_object_newv + fun:g_object_new_valist +} + +{ + + Memcheck:Leak + fun:calloc + fun:_dl_new_object + fun:_dl_map_object_from_fd + fun:_dl_map_object + fun:openaux + fun:_dl_catch_error + fun:_dl_map_object_deps + fun:dl_open_worker + fun:_dl_catch_error + fun:_dl_open + fun:dlopen_doit + fun:_dl_catch_error +} +{ + + Memcheck:Leak + fun:memalign + fun:posix_memalign + obj:/targets/*/lib/libglib-2* + fun:g_slice_alloc + fun:g_slice_alloc0 + obj:/targets/*/usr/lib/libgstreamer-0.1* + fun:g_value_init + fun:gst_value_init_and_copy + fun:gst_structure_copy + fun:gst_ffmpegcsp_transform_caps + obj:/targets/*/usr/lib/libgstbase-0.1* + obj:/targets/*/usr/lib/libgstbase-0.1* +} + +{ + + Memcheck:Addr4 + obj:/targets/*/lib/ld-2.10.* + obj:/targets/*/lib/ld-2.10.* + ... + obj:/targets/*/lib/libc-2.10.* + obj:/targets/*/lib/libc-2.10.* +} + +{ + + Memcheck:Addr4 + obj:/targets/*/lib/ld-2.10.* + obj:/targets/*/lib/ld-2.10.* + ... + obj:/targets/*/usr/lib/libQtCore.so.4.7.0 + obj:/targets/*/usr/lib/libQtCore.so.4.7.0 +} + +{ + + Memcheck:Addr4 + obj:/targets/*/lib/ld-2.10.* + obj:/targets/*/lib/ld-2.10.* + ... + obj:/targets/*/lib/libc-2.10.* +} + +{ + + Memcheck:Addr4 + obj:/targets/*/lib/ld-2.10.* + obj:/targets/*/lib/ld-2.10.* + ... + obj:/targets/*/lib/libdl-2.10.* +} + +{ + + Memcheck:Cond + obj:/targets/*/lib/ld-2.10.* + obj:/targets/*/lib/ld-2.10.* + obj:/targets/*/lib/libdl-2.10.* + obj:/targets/*/lib/ld-2.10.* + obj:/targets/*/lib/libdl-2.10.* + fun:dlopen + obj:/targets/*/usr/lib/libQtCore.so.4.7.0 + obj:/targets/*/usr/lib/libQtCore.so.4.7.0 + obj:/targets/*/usr/lib/libQtDBus.so.4.7.0 + fun:_ZN15QDBusConnection12connectToBusENS_7BusTypeERK7QString + obj:/targets/*/usr/lib/libQtDBus.so.4.7.0 + fun:_ZN15QDBusConnection9systemBusEv +} + +{ + + Memcheck:Addr4 + obj:/targets/*/lib/ld-2.10.* + ... + fun:gst_plugin_feature_load + fun:gst_element_factory_create +} + +{ + + Memcheck:Addr4 + obj:/targets/*/lib/ld-2.10.* + obj:/targets/*/lib/ld-2.10.* + ... + fun:gst_plugin_load_file +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_strdup + fun:g_strdupv + fun:gst_type_find_register + obj:/targets/*/usr/lib/gstreamer-0.10/libgsttypefindfunctions.so + obj:/targets/*/usr/lib/libgstreamer-0.10.so.0.26.0 + fun:gst_plugin_load_file + fun:gst_plugin_load_by_name + fun:gst_plugin_feature_load + fun:gst_type_find_factory_call_function + fun:gst_type_find_helper_get_range_ext +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_strdup + ... + fun:gst_plugin_load_file + fun:gst_plugin_load_by_name +} + + +{ + + Memcheck:Addr4 + obj:/targets/*/lib/ld-2.10.* + fun:gst_type_find_factory_call_function +} + +{ + + Memcheck:Param + ioctl(arg) + fun:ioctl + fun:snd_pcm_prepare + fun:snd_pcm_hw_params + obj:/targets/*/usr/lib/libasound.so.2.0.0 + fun:snd_pcm_dmix_open + fun:_snd_pcm_dmix_open + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + fun:_snd_pcm_softvol_open + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 +} + +{ + + Memcheck:Param + ioctl(arg) + fun:ioctl + fun:snd_pcm_hwsync + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + fun:snd_pcm_start + obj:/targets/*/usr/lib/libasound.so.2.0.0 + fun:snd_pcm_start + obj:/targets/*/usr/lib/libasound.so.2.0.0 + fun:snd_pcm_mmap_writei + fun:snd_pcm_writei + obj:/targets/*/usr/lib/gstreamer-0.10/libgstalsa.so + obj:/targets/*/usr/lib/libgstaudio-0.10.so.0.21.0 +} + +{ + + Memcheck:Param + ioctl(arg) + fun:ioctl + fun:snd_pcm_hwsync + obj:/targets/*/usr/lib/libasound.so.2.0.0 + fun:snd_pcm_hwsync + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + fun:snd_pcm_delay + obj:/targets/*/usr/lib/gstreamer-0.10/libgstalsa.so + obj:/targets/*/usr/lib/libgstaudio-0.10.so.0.21.0 + fun:gst_ring_buffer_delay + obj:/targets/*/usr/lib/libgstaudio-0.10.so.0.21.0 + obj:/targets/*/usr/lib/libgstaudio-0.10.so.0.21.0 +} + +{ + + Memcheck:Param + ioctl(arg) + fun:ioctl + fun:snd_pcm_start + obj:/targets/*/usr/lib/libasound.so.2.0.0 + fun:snd_pcm_dmix_open + fun:_snd_pcm_dmix_open + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + fun:_snd_pcm_softvol_open + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + fun:_snd_pcm_plug_open +} + +{ + + Memcheck:Param + ioctl(arg) + fun:ioctl + fun:snd_timer_start + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + fun:snd_pcm_start + obj:/targets/*/usr/lib/libasound.so.2.0.0 + fun:snd_pcm_start + obj:/targets/*/usr/lib/libasound.so.2.0.0 + fun:snd_pcm_mmap_writei + fun:snd_pcm_writei + obj:/targets/*/usr/lib/gstreamer-0.10/libgstalsa.so + obj:/targets/*/usr/lib/libgstaudio-0.10.so.0.21.0 +} + +{ + + Memcheck:Param + ioctl(arg) + fun:ioctl + fun:snd_pcm_hwsync + obj:/targets/*/usr/lib/libasound.so.2.0.0 + fun:snd_pcm_hwsync + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + fun:snd_pcm_poll_descriptors_revents + obj:/targets/*/usr/lib/libasound.so.2.0.0 + fun:snd_pcm_wait + obj:/targets/*/usr/lib/gstreamer-0.10/libgstalsa.so + obj:/targets/*/usr/lib/libgstaudio-0.10.so.0.21.0 + obj:/targets/*/lib/libglib-2.0.so.0.2400.1 +} + +{ + + Memcheck:Param + ioctl(arg) + fun:ioctl + fun:snd_timer_stop + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + fun:snd_pcm_drop + obj:/targets/*/usr/lib/libasound.so.2.0.0 + fun:snd_pcm_drop + obj:/targets/*/usr/lib/gstreamer-0.10/libgstalsa.so + obj:/targets/*/usr/lib/libgstaudio-0.10.so.0.21.0 + obj:/targets/*/usr/lib/libgstaudio-0.10.so.0.21.0 + fun:gst_ring_buffer_pause + obj:/targets/*/usr/lib/libgstaudio-0.10.so.0.21.0 +} + +{ + + Memcheck:Param + ioctl(arg) + fun:ioctl + fun:snd_pcm_hwsync + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + fun:snd_pcm_mmap_commit + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + fun:snd_pcm_mmap_commit + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + fun:snd_pcm_mmap_writei +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_slice_alloc + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + fun:g_type_add_interface_static + obj:/targets/*/usr/lib/gstreamer-0.10/libgstalsa.so + obj:/targets/*/usr/lib/gstreamer-0.10/libgstalsa.so + obj:/targets/*/usr/lib/libgstreamer-0.10.so.0.26.0 +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_slice_alloc + obj:/targets/*/usr/lib/libgobject-2.0.so.0.* + obj:/targets/*/usr/lib/libgobject-2.0.so.0.* + obj:/targets/*/usr/lib/libgobject-2.0.so.0.* + obj:/targets/*/usr/lib/libgobject-2.0.so.0.* + obj:/targets/*/usr/lib/libgobject-2.0.so.0.* + fun:g_type_add_interface_static + obj:/targets/*/usr/lib/gstreamer-0.10/libgstalsa.so + obj:/targets/*/usr/lib/gstreamer-0.10/libgstalsa.so + obj:/targets/*/usr/lib/gstreamer-0.10/libgstalsa.so +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_slice_alloc + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + fun:g_type_add_interface_static + obj:/targets/*/usr/lib/gstreamer-0.10/libgstcoreelements.so + obj:/targets/*/usr/lib/gstreamer-0.10/libgstcoreelements.so + obj:/targets/*/usr/lib/libgstreamer-0.10.so.0.26.0 +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_memdup + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + fun:g_type_class_ref + fun:g_object_newv + fun:gst_element_factory_create + fun:gst_element_factory_make + obj:/targets/*/usr/lib/gstreamer-0.10/libgstplaybin.so + obj:/targets/*/usr/lib/gstreamer-0.10/libgstplaybin.so + obj:/targets/*/usr/lib/gstreamer-0.10/libgstplaybin.so + fun:gst_element_change_state +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_strdup + ... + fun:gst_element_factory_create + fun:gst_element_factory_make +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_strdup + ... + fun:gst_element_factory_make + obj:/targets/*/usr/lib/gstreamer-0.10/libgstplaybin.so +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_strdup + ... + obj:/targets/*/usr/lib/libgstreamer-0.10.so.* + fun:gst_element_provide_clock +} + + + + +{ + + Memcheck:Param + ioctl(arg) + fun:ioctl + fun:snd_pcm_hwsync + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + fun:snd_pcm_avail_update + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + fun:snd_pcm_poll_descriptors_revents + obj:/targets/*/usr/lib/libasound.so.2.0.0 + fun:snd_pcm_wait + obj:/targets/*/usr/lib/gstreamer-0.10/libgstalsa.so + obj:/targets/*/usr/lib/libgstaudio-0.10.so.0.21.0 +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + ... + obj:/targets/*/usr/lib/libgstreamer-0.10.so.* +} + +{ + + Memcheck:Leak + fun:malloc + fun:strdup + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + fun:snd_config_hook_load + fun:snd_config_hook_load_for_all_cards + obj:/targets/*/usr/lib/libasound.so.2.0.0 +} + +{ + + Memcheck:Leak + fun:malloc + fun:strdup + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + fun:snd_config_hook_load + fun:snd_config_hook_load_for_all_cards +} + +{ + + Memcheck:Leak + fun:malloc + obj:/targets/*/usr/lib/libasound.so.2.0.0 + ... + obj:/targets/*/usr/lib/libasound.so.2.0.0 +} + +{ + + Memcheck:Leak + fun:malloc + ... + fun:snd_config_hook_load +} + +{ + + Memcheck:Leak + fun:malloc + obj:/targets/*/usr/lib/libasound.so.2.0.0 + ... + obj:/targets/*/usr/lib/libgstaudio-0.10.so.0.21.0 +} + +{ + + Memcheck:Leak + fun:malloc + fun:strdup + obj:/targets/*/usr/lib/libasound.so.2.0.0 + ... + obj:/targets/*/usr/lib/libasound.so.2.0.0 +} + +{ + + Memcheck:Leak + fun:malloc + fun:strdup + obj:/targets/*/usr/lib/libasound.so.2.0.0 + ... + obj:/targets/*/usr/lib/gstreamer-0.10/libgstalsa.so +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_slice_alloc + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + ... + obj:/targets/*/usr/lib/gstreamer-0.10/libgstplaybin.so +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + ... + obj:/targets/*/usr/lib/libgstreamer-0.10.so.0.26.0 + fun:gst_plugin_load_file +} + +{ + + Memcheck:Leak + fun:malloc + obj:/targets/*/usr/lib/libasound.so.2.0.0 + ... + obj:/targets/*/usr/lib/libasound.so.2.0.0 +} + +{ + + Memcheck:Leak + fun:malloc + fun:strdup + obj:/targets/*/usr/lib/libasound.so.2.0.0 + ... + fun:gst_element_set_state +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_slice_alloc + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + ... + fun:g_io_modules_scan_all_in_directory +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_slice_alloc + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + ... + fun:g_vfs_get_file_for_uri +} + +{ + + Memcheck:Leak + fun:malloc + fun:strdup + obj:/targets/*/usr/lib/libasound.so.2.0.0 + ... + obj:/targets/*/usr/lib/libasound.so.2.0.0 + fun:_snd_pcm_empty_open +} + +{ + + Memcheck:Leak + fun:malloc + obj:/targets/*/usr/lib/libasound.so.2.0.0 + ... + obj:/targets/*/usr/lib/libasound.so.2.0.0 + fun:snd_config_search_definition +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_slice_alloc + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + ... + obj:/targets/*/usr/lib/libgstreamer-0.10.so.0.26.0 + fun:gst_plugin_load_file +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_slice_alloc + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + ... + obj:/targets/*/usr/lib/libgstreamer-0.10.so.0.26.0 +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_slice_alloc + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + ... + obj:/targets/*/usr/lib/gstreamer-0.10/libgstplaybin.so +} + +{ + + Memcheck:Leak + fun:malloc + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + fun:_snd_pcm_softvol_open + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + fun:_snd_pcm_plug_open + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + fun:_snd_pcm_asym_open +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_slice_alloc + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + fun:g_type_add_interface_static + fun:gst_tag_reader_add_interface_to_type + obj:/targets/*/usr/lib/gstreamer-0.10/libgstdecodebin2.so + obj:/targets/*/usr/lib/gstreamer-0.10/libgstdecodebin2.so + obj:/targets/*/usr/lib/libgstreamer-0.10.so.0.26.0 +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_slice_alloc + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + fun:g_type_add_interface_static + obj:/targets/*/usr/lib/gstreamer-0.10/libgstgio.so + obj:/targets/*/usr/lib/gstreamer-0.10/libgstgio.so + obj:/targets/*/usr/lib/gstreamer-0.10/libgstgio.so + obj:/targets/*/usr/lib/libgstreamer-0.10.so.0.26.0 +} + +{ + + Memcheck:Leak + fun:malloc + fun:strdup + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + fun:snd_config_hook_load + obj:/targets/*/usr/lib/libasound.so.2.0.0 + fun:snd_config_searcha_hooks + obj:/targets/*/usr/lib/libasound.so.2.0.0 + fun:snd_config_search_definition + obj:/targets/*/usr/lib/libasound.so.2.0.0 +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_memdup + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + fun:g_type_class_ref + fun:g_type_class_ref + fun:g_object_newv + fun:g_object_new + fun:g_io_module_new + fun:g_io_modules_scan_all_in_directory + obj:/targets/*/usr/lib/libgio-2.0.so.0.2400.1 + obj:/targets/*/usr/lib/libgio-2.0.so.0.2400.1 +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_slice_alloc + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + fun:g_type_add_interface_static + obj:/targets/*/usr/lib/gstreamer-0.10/libgstvolume.so + obj:/targets/*/usr/lib/gstreamer-0.10/libgstvolume.so + obj:/targets/*/usr/lib/libgstreamer-0.10.so.0.26.0 +} + +{ + + Memcheck:Leak + fun:malloc + fun:strdup + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + fun:snd_pcm_rate_open + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + fun:snd_pcm_hw_params + obj:/targets/*/usr/lib/gstreamer-0.10/libgstalsa.so + obj:/targets/*/usr/lib/libgstaudio-0.10.so.0.21.0 + fun:gst_ring_buffer_acquire +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_memdup + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + ... + obj:/targets/*/usr/lib/gstreamer-0.10/libgstplaybin.so + obj:/targets/*/usr/lib/gstreamer-0.10/libgstplaybin.so +} + +{ + + Memcheck:Leak + fun:realloc + fun:g_realloc + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + fun:g_type_register_static + fun:g_boxed_type_register_static + fun:gst_tag_list_get_type + obj:/targets/*/usr/lib/gstreamer-0.10/libgstplaybin.so + fun:g_type_class_ref + obj:/targets/*/usr/lib/gstreamer-0.10/libgstplaybin.so + obj:/targets/*/usr/lib/gstreamer-0.10/libgstplaybin.so + obj:/targets/*/usr/lib/libgstreamer-0.10.so.0.26.0 +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_slice_alloc + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + fun:g_type_add_interface_static + obj:/targets/*/usr/lib/gstreamer-0.10/libgstvolume.so + obj:/targets/*/usr/lib/gstreamer-0.10/libgstvolume.so + obj:/targets/*/usr/lib/libgstreamer-0.10.so.0.26.0 + fun:gst_plugin_load_file +} + +{ + + Memcheck:Leak + fun:calloc + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + fun:snd_config_hook_load + obj:/targets/*/usr/lib/libasound.so.2.0.0 + fun:snd_config_searcha_hooks + obj:/targets/*/usr/lib/libasound.so.2.0.0 + fun:snd_config_search_definition + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/gstreamer-0.10/libgstalsa.so +} + +{ + + Memcheck:Leak + fun:calloc + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + ... + obj:/targets/*/usr/lib/libasound.so.2.0.0 +} + +{ + + Memcheck:Leak + fun:calloc + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + ... + fun:snd_config_* +} + +{ + + Memcheck:Leak + fun:malloc + fun:strdup + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + ... + fun:snd_config_searcha_hooks +} + +{ + + Memcheck:Leak + fun:malloc + obj:/targets/*/usr/lib/libasound.so.2.0.0 + obj:/targets/*/usr/lib/libasound.so.2.0.0 + ... + obj:/targets/*/usr/lib/gstreamer-0.10/libgstalsa.so +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_slice_alloc + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + ... + obj:/targets/*/usr/lib/libgstreamer-0.10.so.0.26.0 +} + +{ + + Memcheck:Leak + fun:realloc + fun:g_realloc + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + fun:g_type_register_static + fun:g_type_register_static_simple + fun:g_desktop_app_info_lookup_get_type + obj:/targets/*/usr/lib/libgio-2.0.so.0.2400.1 + obj:/targets/*/usr/lib/libgio-2.0.so.0.2400.1 + obj:/targets/*/usr/lib/libgio-2.0.so.0.2400.1 + fun:g_once_impl + fun:g_vfs_get_default +} + +{ + + Memcheck:Leak + fun:realloc + fun:g_realloc + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + fun:g_type_register_static + fun:g_type_register_static_simple + fun:g_input_stream_get_type + obj:/targets/*/usr/lib/gstreamer-0.10/libgstgio.so + obj:/targets/*/usr/lib/libgstbase-0.10.so.0.26.0 + obj:/targets/*/usr/lib/libgstbase-0.10.so.0.26.0 + fun:gst_pad_activate_pull + fun:gst_pad_activate_pull +} + +{ + + Memcheck:Leak + fun:malloc + fun:strdup + obj:/targets/*/usr/lib/libasound.so.2.0.0 + ... + obj:/targets/*/usr/lib/libgstaudio-0.10.so.0.21.0 + fun:gst_ring_buffer_open_device +} + +{ + + Memcheck:Leak + fun:malloc + obj:/targets/*/lib/ld-2.10.1.so + obj:/targets/*/lib/ld-2.10.1.so + ... + fun:snd_config_update_r +} + +{ + + Memcheck:Param + semctl(IPC_SET, arg.buf) + fun:semctl + obj:/targets/*/usr/lib/libasound.so.2.0.0 + ... + obj:/targets/*/usr/lib/libasound.so.2.0.0 +} + + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_memdup + ... + obj:/targets/*/usr/lib/gstreamer-0.10/libgstplaybin.so + fun:gst_element_change_state +} + + +{ + + Memcheck:Leak + fun:realloc + fun:g_realloc + ... + fun:g_type_class_ref + fun:g_object_new_valist + fun:g_object_new +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_slice_alloc + ... + fun:g_type_class_ref + fun:g_object_new_valist + fun:g_object_new +} + + + + +{ + + Memcheck:Leak + fun:malloc + obj:/targets/*/lib/ld-2.10.1.so + ... + fun:gst_plugin_load_file + fun:gst_plugin_load_by_name + fun:gst_plugin_feature_load +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_memdup + ... + obj:/targets/*/usr/lib/gstreamer-0.10/libgstplaybin.so + obj:/targets/*/usr/lib/gstreamer-0.10/libgstplaybin.so +} + +{ + + Memcheck:Leak + fun:malloc + obj:/targets/*/lib/ld-2.10.1.so + ... + obj:/targets/*/lib/libdl-2.10.1.so +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_memdup + ... + obj:/targets/*/usr/lib/gio/modules/libgio-playready-vfs.so + fun:g_vfs_get_file_for_uri +} + +{ + + Memcheck:Leak + fun:malloc + obj:/targets/*/lib/ld-2.10.1.so + obj:/targets/*/lib/ld-2.10.1.so + ... + fun:gst_plugin_load_file +} + +{ + + Memcheck:Leak + fun:malloc + obj:/targets/*/lib/ld-2.10.1.so + ... + obj:/targets/*/lib/libdl-2.10.1.so + fun:dlopen + fun:g_module_open +} + +{ + + Memcheck:Leak + fun:malloc + obj:/targets/*/lib/ld-2.10.1.so + ... + fun:gst_plugin_load_file + fun:gst_plugin_load_by_name +} + +{ + + Memcheck:Leak + fun:calloc + fun:g_malloc0 + obj:/targets/*/usr/lib/libgstreamer-0.10.so.0.26.0 + ... + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 +} + +{ + + Memcheck:Leak + fun:calloc + obj:/targets/*/lib/ld-2.10.1.so + ... + obj:/targets/*/lib/ld-2.10.1.so +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + ... + obj:/targets/*/usr/lib/libgstbase-0.10.so.0.26.0 + fun:gst_pad_get_range +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_slice_alloc + ... + obj:/targets/*/usr/lib/libgstaudio-0.10.so.0.21.0 + fun:g_type_create_instance +} + +{ + + Memcheck:Leak + fun:realloc + fun:g_realloc + ... + fun:gst_element_factory_create +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + ... + fun:g_object_new + obj:/targets/*/usr/lib/gstreamer-0.10/libgstplaybin.so +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_slice_alloc + fun:g_slice_alloc0 + ... + fun:g_object_new + fun:gst_element_factory_create +} + +{ + + Memcheck:Leak + fun:realloc + fun:g_realloc + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + ... + fun:gst_plugin_load_by_name +} + +{ + + Memcheck:Leak + fun:calloc + fun:_dl_allocate_tls + ... + obj:/targets/*/usr/lib/gstreamer-0.10/libgstwavparse.so +} + +{ + + Memcheck:Leak + fun:calloc + fun:_dl_allocate_tls + ... + obj:/targets/*/usr/lib/gstreamer-0.10/libgstcoreelements.so +} + +{ + + Memcheck:Leak + fun:realloc + fun:g_realloc + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + ... + obj:/targets/*/usr/lib/gstreamer-0.10/libgstplaybin.so +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_slice_alloc + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + ... + obj:/targets/*/usr/lib/gstreamer-0.10/libgstdecodebin2.so +} + +{ + + Memcheck:Leak + fun:realloc + fun:g_realloc + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + obj:/targets/*/usr/lib/libgobject-2.0.so.0.2400.1 + ... + obj:/targets/*/usr/lib/gstreamer-0.10/libgstdecodebin2.so + obj:/targets/*/usr/lib/gstreamer-0.10/libgstdecodebin2.so +} + +{ + + Memcheck:Leak + fun:realloc + fun:g_realloc + obj:/targets/*/usr/lib/libgobject-2.0.so.0.* + ... + fun:_ZN24ut_MafwGstRendererWorker19rendererArtTestCaseEv + fun:_ZN24ut_MafwGstRendererWorker11qt_metacallEN11QMetaObject4CallEiPPv + fun:_ZN11QMetaObject8metacallEP7QObjectNS_4CallEiPPv +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_strdup + fun:g_module_open + fun:gst_plugin_load_file + ... + obj:/targets/*/usr/lib/gstreamer-0.10/libgstplaybin.so +} + + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + ... + fun:g_type_class_ref + fun:g_object_new_valist +} + + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + ... + obj:/targets/*/usr/lib/gstreamer-0.10/libgstcoreelements.so +} + +{ + + Memcheck:Leak + fun:malloc + fun:dbus_malloc + ... + fun:dbus_pending_call_block +} + +{ + + Memcheck:Leak + fun:realloc + ... + fun:dbus_connection_send_with_reply +} + +{ + + Memcheck:Leak + fun:realloc + ... + fun:dbus_connection_send_with_reply +} + +{ + + Memcheck:Leak + fun:malloc + ... + fun:_ZN17ContextSubscriber16DBusNameListener14startListeningEb + fun:_ZN17ContextSubscriber14PropertyHandleC1ERK7QString + fun:_ZN17ContextSubscriber14PropertyHandle8instanceERK7QString +} + +{ + + Memcheck:Leak + fun:malloc + fun:dbus_malloc + ... + fun:_ZN15QDBusConnection12connectToBusENS_7BusTypeERK7QString +} + +{ + + Memcheck:Leak + fun:malloc + fun:dbus_malloc + ... + obj:/targets/*/usr/lib/libdbus-1.so.3.5.1 +} + +{ + + Memcheck:Leak + fun:malloc + fun:realloc + ... + obj:/targets/*/usr/lib/libQtDBus.so.4.7.0 + fun:_ZN15QDBusConnection10sessionBusEv +} + +{ + + Memcheck:Leak + fun:calloc + ... + fun:_ZN17ContextSubscriber16DBusNameListener14startListeningEb + fun:_ZN17ContextSubscriber14PropertyHandleC1ERK7QString +} + +{ + + Memcheck:Leak + fun:calloc + ... + fun:_ZN17ContextSubscriber14PropertyHandleC1ERK7QString + fun:_ZN17ContextSubscriber14PropertyHandle8instanceERK7QString +} + +{ + + Memcheck:Leak + fun:malloc + fun:realloc + fun:g_realloc + obj:/targets/maemo6-i486/usr/lib/libgobject-2.0.so.* + obj:/targets/maemo6-i486/usr/lib/libgobject-2.0.so.* + fun:g_type_register_static + fun:gconf_client_get_type + fun:gconf_client_get_default + obj:/targets/*/usr/lib/libgq-gconf.so.* + ... +} + +{ + + Memcheck:Leak + fun:calloc + fun:dbus_malloc0 + ... + fun:_ZN9GConfItem12update_valueEb + fun:_ZN9GConfItemC1ERK7QStringP7QObject +} + +{ + + Memcheck:Leak + fun:realloc + fun:dbus_realloc + ... + fun:dbus_message_append_args +} + +{ + + Memcheck:Leak + fun:malloc + ... + fun:_ZN15QDBusConnection10sessionBusEv + fun:_ZN17ContextSubscriber16DBusNameListener14startListeningEb +} + +{ + + Memcheck:Leak + fun:malloc + fun:_Z7qMallocj + ... + obj:/targets/maemo6-i486/usr/lib/libdbus-1.so.3.* +} + +{ + + Memcheck:Leak + fun:_Znwj + obj:/targets/*/usr/lib/libQtDBus.so.4.* + ... + fun:_ZN17ContextSubscriber14PropertyHandleC1ERK7QString + fun:_ZN17ContextSubscriber14PropertyHandle8instanceERK7QString +} + +{ + + Memcheck:Leak + fun:_Znwj + fun:_ZN14QObjectPrivate13addConnectionEiPNS_10ConnectionE + ... + fun:_ZN15QDBusConnection10sessionBusEv +} + +{ + + Memcheck:Leak + fun:_Znwj + ... + fun:_ZN15QDBusConnection12connectToBusENS_7BusTypeERK7QString + obj:/targets/*/usr/lib/libQtDBus.so.4.7.0 +} + +{ + + Memcheck:Leak + fun:calloc + ... + fun:_ZN15QDBusConnection10sessionBusEv + fun:_ZN17ContextSubscriber16DBusNameListener14startListeningEb +} + +{ + + Memcheck:Leak + fun:malloc + fun:_Z7qMallocj + ... + fun:_ZN11QMetaObject8metacallEP7QObjectNS_4CallEiPPv +} + +{ + + Memcheck:Leak + fun:realloc + fun:dbus_realloc + ... + obj:/targets/*/usr/lib/libdbus-1.so.3.5.1 +} + +{ + + Memcheck:Leak + fun:malloc + fun:_Z7qMallocj + ... + fun:dbus_connection_set_watch_functions +} + +{ + + Memcheck:Leak + fun:_Znwj + ... + obj:/targets/*/usr/lib/libQtDBus.so.4.7.0 +} + +{ + + Memcheck:Leak + fun:realloc + fun:dbus_realloc + ... + obj:/targets/*/usr/lib/libdbus-1.so.3.5.1 +} + +{ + + Memcheck:Leak + fun:_Znwj + ... + fun:_ZN17ContextSubscriber14PropertyHandleC1ERK7QString + fun:_ZN17ContextSubscriber14PropertyHandle8instanceERK7QString +} + +{ + + Memcheck:Leak + fun:malloc + ... + fun:_ZN15QDBusConnection10sessionBusEv +} + +{ + + Memcheck:Leak + fun:malloc + fun:_Z7qMallocj + fun:_ZN9QListData11detach_growEPii + ... + fun:_ZN16QCoreApplication6notifyEP7QObjectP6QEvent + fun:_ZN16QCoreApplication14notifyInternalEP7QObjectP6QEvent +} + +{ + + Memcheck:Leak + fun:calloc + fun:dbus_malloc0 + ... + fun:_ZN15QDBusConnection12connectToBusENS_7BusTypeERK7QString + obj:/targets/*/usr/lib/libQtDBus.so.4.* +} + +{ + + Memcheck:Leak + fun:realloc + fun:_Z8qReallocPvj + ... + obj:/targets/*/usr/lib/libQtDBus.so.4.* +} + +{ + + Memcheck:Leak + fun:realloc + fun:_Z8qReallocPvj + ... + fun:_ZN22QDBusAbstractInterfaceC2ERK7QStringS2_PKcRK15QDBusConnectionP7QObject +} + +{ + + Memcheck:Leak + fun:malloc + ... + fun:_ZN15QDBusConnection10sessionBusEv +} + +{ + + Memcheck:Leak + fun:calloc + ... + fun:_ZN15QDBusConnection12connectToBusENS_7BusTypeERK7QString +} + +{ + + Memcheck:Leak + fun:malloc + fun:_Z7qMallocj + ... + obj:/targets/*/lib/ld-2.*.so +} + +{ + + Memcheck:Leak + fun:malloc + ... + obj:/targets/*/usr/lib/libQtDBus.so.4.* +} + +{ + + Memcheck:Leak + fun:_Znaj + ... + obj:/targets/*/usr/lib/libQtDBus.so.4.* +} + +{ + + Memcheck:Leak + fun:_Znaj + ... + fun:_ZN17ContextSubscriber14PropertyHandleC1ERK7QString +} + +{ + + Memcheck:Leak + fun:malloc + ... + fun:_ZN15QDBusConnection10sessionBusEv +} + +{ + + Memcheck:Leak + fun:calloc + ... + fun:_ZN15QDBusConnection10sessionBusEv +} + +{ + + Memcheck:Leak + fun:malloc + fun:_Z7qMallocj + ... + fun:_ZN15QDBusConnection12connectToBusENS_7BusTypeERK7QString +} + +{ + + Memcheck:Leak + fun:malloc + ... + obj:/targets/*/usr/lib/libQtDBus.so.4.* +} + +{ + + Memcheck:Leak + fun:malloc + ... + fun:_ZN17ContextSubscriber16DBusNameListener14startListeningEb +} + +{ + + Memcheck:Leak + fun:malloc + fun:realloc + ... + fun:dbus_connection_send_with_reply_and_block + fun:dbus_bus_register +} + +{ + + Memcheck:Leak + fun:calloc + fun:dbus_malloc0 + ... + obj:/targets/*/usr/lib/libQtDBus.so.4.7.0 +} + +{ + + Memcheck:Leak + fun:malloc + obj:/targets/maemo6-i486/usr/lib/libfontconfig.so.1.* + ... + fun:XML_ParseBuffer + fun:FcConfigParseAndLoad +} + +{ + + Memcheck:Leak + fun:malloc + ... + fun:_ZN19QApplicationPrivate9constructEP9_XDisplaymm + fun:_ZN12QApplicationC1ERiPPci + fun:main +} + +{ + + Memcheck:Leak + fun:calloc + ... + fun:_ZN19QApplicationPrivate9constructEP9_XDisplaymm + fun:_ZN12QApplicationC1ERiPPci + fun:main +} + +{ + + Memcheck:Cond + obj:/targets/*/lib/ld-2.*.so + ... + fun:_ZN19QApplicationPrivate9constructEP9_XDisplaymm + fun:_ZN12QApplicationC1ERiPPci + fun:main +} + +{ + + Memcheck:Cond + obj:/targets/*/lib/ld-2.10.1.so + ... + fun:_ZN19QApplicationPrivate9constructEP9_XDisplaymm + fun:_ZN12QApplicationC1ERiPPci +} + +{ + + Memcheck:Leak + fun:malloc + fun:realloc + ... + fun:g_type_register_static + ... + fun:_ZN9GConfItemC1ERK7QStringP7QObject + fun:_ZN20MafwGstRendererDolby10initializeEv +} + +{ + + Memcheck:Leak + fun:malloc + fun:realloc + fun:g_realloc + fun:g_realloc_n + ... + fun:g_type_register_static + ... + fun:g_type_class_ref + fun:g_type_class_ref +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_slice_alloc + obj:/targets/*/usr/lib/libgobject-2*.so.* + obj:/targets/*/usr/lib/libgobject-2*.so.* + obj:/targets/*/usr/lib/libgobject-2*.so.* + obj:/targets/*/usr/lib/libgobject-2*.so.* + obj:/targets/*/usr/lib/libgobject-2*.so.* + fun:g_type_add_interface_static + ... +} + +{ + + Memcheck:Leak + fun:calloc + fun:g_malloc0 + fun:g_type_class_ref + fun:g_type_class_ref + fun:g_param_spec_flags + obj:/targets/*/usr/lib/libgio-2.*.so.* + fun:g_type_class_ref + fun:g_type_class_ref + fun:g_io_extension_ref_class + obj:/targets/*/usr/lib/libgio-2.*.so.* + fun:g_once_impl + obj:/targets/*/usr/lib/libgio-2.*.so.* +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_slice_alloc + obj:/targets/*/usr/lib/libgobject-2.0.so.* + obj:/targets/*/usr/lib/libgobject-2.0.so.* + obj:/targets/*/usr/lib/libgobject-2.0.so.* + ... + fun:g_type_add_interface_static + obj:/targets/*/usr/lib/libgio-2.0.so.* + obj:/targets/*/usr/lib/libgio-2.0.so.* + obj:/targets/*/usr/lib/libgio-2.0.so.* + fun:g_once_impl +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_slice_alloc + obj:/targets/*/usr/lib/libgobject-2.0.so.* + ... + fun:g_type_register_static + fun:g_type_register_static_simple + obj:/targets/*/usr/lib/libgio-2.0.so.* + obj:/targets/*/usr/lib/libgio-2.0.so.* + obj:/targets/*/usr/lib/libgio-2.0.so.* +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_memdup + obj:/targets/*/usr/lib/libgobject-2.0.so.* + fun:g_type_class_ref + fun:g_object_newv + fun:g_object_new + obj:/targets/*/usr/lib/libgio-2.0.so.* + obj:/targets/*/usr/lib/libgio-2.0.so.* + fun:g_vfs_get_file_for_path + fun:g_file_new_for_path + obj:/targets/*/usr/lib/libgio-2.0.so.* +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_slice_alloc + obj:/targets/*/usr/lib/libgobject-2.0.so.* + obj:/targets/*/usr/lib/libgobject-2.0.so.* + obj:/targets/*/usr/lib/libgobject-2.0.so.* + obj:/targets/*/usr/lib/libgobject-2.0.so.* + fun:g_type_add_interface_static + obj:/targets/*/usr/lib/libgio-2.0.so.* + obj:/targets/*/usr/lib/libgio-2.0.so.* + obj:/targets/*/usr/lib/libgio-2.0.so.* + fun:g_vfs_get_file_for_path +} + +{ + + Memcheck:Leak + fun:realloc + fun:g_realloc + fun:g_realloc_n + obj:/targets/*/usr/lib/libgobject-2.0.so.* + obj:/targets/*/usr/lib/libgobject-2.0.so.* + fun:g_type_register_static + fun:g_type_register_static_simple + obj:/targets/*/usr/lib/libgio-2.0.so.* + obj:/targets/*/usr/lib/libgio-2.0.so.* + obj:/targets/*/usr/lib/libgio-2.0.so.* + fun:g_vfs_get_file_for_path + fun:g_file_new_for_path +} + +{ + + Memcheck:Leak + fun:calloc + fun:g_malloc0 + fun:g_type_class_ref + fun:g_type_class_ref + fun:g_type_create_instance + fun:g_param_spec_internal + fun:g_param_spec_int + obj:/targets/*/usr/lib/libgio-2.0.so.* + fun:g_type_class_ref + fun:g_type_class_ref + fun:g_type_class_ref + fun:g_io_extension_ref_class +} + +{ + + Memcheck:Leak + fun:realloc + fun:g_realloc + fun:g_realloc_n + obj:/targets/*/usr/lib/libgobject-2.0.so.* + obj:/targets/*/usr/lib/libgobject-2.0.so.* + fun:g_type_register_static + fun:g_type_register_static_simple + fun:g_file_get_type + obj:/targets/*/usr/lib/libgio-2.0.so.* + obj:/targets/*/usr/lib/libgio-2.0.so.* + obj:/targets/*/usr/lib/libgio-2.0.so.* + fun:g_vfs_get_file_for_path +} + +{ + + Memcheck:Leak + fun:calloc + fun:_dl_allocate_tls + fun:pthread_create@@GLIBC_2.1 + obj:/targets/*/usr/lib/libgthread-2.0.so.* + fun:g_thread_create_full + obj:/targets/*/lib/libglib-2.0.so.* + fun:g_thread_pool_push + obj:/targets/*/usr/lib/libgstreamer-0.10.so.* + fun:gst_task_pool_push + fun:gst_task_set_state + fun:gst_pad_start_task + obj:/targets/*/usr/lib/libgstbase-0.10.so.* +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_slice_alloc + obj:/targets/*/usr/lib/libgobject-2.* + obj:/targets/*/usr/lib/libgobject-2.* + obj:/targets/*/usr/lib/libgobject-2.* + obj:/targets/*/usr/lib/libgobject-2.* + fun:g_type_register_static + obj:/targets/*/usr/lib/gstreamer-0.10/libgstplaybin.so + obj:/targets/*/usr/lib/gstreamer-0.10/libgstplaybin.so + obj:/targets/*/usr/lib/gstreamer-0.10/libgstplaybin.so + obj:/targets/*/usr/lib/gstreamer-0.10/libgstplaybin.so +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_slice_alloc + obj:/targets/*/usr/lib/libgobject-2.* + obj:/targets/*/usr/lib/libgobject-2.* + obj:/targets/*/usr/lib/libgobject-2.* + obj:/targets/*/usr/lib/libgobject-2.* + fun:g_type_register_static + fun:g_type_register_static_simple + obj:/targets/*/usr/lib/gstreamer-0.10/libgstplaybin.so + obj:/targets/*/usr/lib/gstreamer-0.10/libgstplaybin.so + obj:/targets/*/usr/lib/gstreamer-0.10/libgstplaybin.so +} + +{ + + Memcheck:Leak + fun:realloc + fun:g_realloc + fun:g_realloc_n + obj:/targets/*/usr/lib/libgobject-2.* + obj:/targets/*/usr/lib/libgobject-2.* + fun:g_type_register_static + fun:g_type_register_static_simple + fun:gst_data_queue_get_type + fun:gst_data_queue_new_full + obj:/targets/*/usr/lib/gstreamer-0.10/libgstcoreelements.so + obj:/targets/*/usr/lib/libgstreamer-0.10.so.* + fun:gst_element_get_request_pad +} + +{ + + Memcheck:Leak + fun:realloc + fun:g_realloc + fun:g_realloc_n + obj:/targets/*/usr/lib/libgobject-2.* + obj:/targets/*/usr/lib/libgobject-2.* + fun:g_type_register_static + fun:gst_x_overlay_get_type + fun:mafw_gst_renderer_worker_apply_xid + fun:_sync_bus_handler + fun:gst_bus_post + fun:_ZN24ut_MafwGstRendererWorker18pauseFrameTestCaseEv + fun:_ZN24ut_MafwGstRendererWorker11qt_metacallEN11QMetaObject4CallEiPPv +} + +{ + + Memcheck:Leak + fun:realloc + fun:g_realloc + fun:g_realloc_n + obj:/targets/*/usr/lib/libgobject-2.* + obj:/targets/*/usr/lib/libgobject-2.* + fun:g_type_register_static + fun:g_enum_register_static + obj:/targets/*/usr/lib/libgsttag-0.10.so.* + fun:g_once_impl + fun:gst_tag_image_type_get_type + fun:_ZN24ut_MafwGstRendererWorker19rendererArtTestCaseEv + fun:_ZN24ut_MafwGstRendererWorker11qt_metacallEN11QMetaObject4CallEiPPv +} + +{ + + Memcheck:Addr4 + obj:/targets/*/usr/lib/libqttracker.so.1~6.12.8 + ... + obj:/targets/*/usr/lib/libqttracker.so.1~6.12.8 + obj:/targets/*/usr/lib/libqttracker.so.1~6.12.8 +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_slice_alloc + obj:/targets/*/usr/lib/libgobject-2.* + obj:/targets/*/usr/lib/libgobject-2.* + obj:/targets/*/usr/lib/libgobject-2.* + obj:/targets/*/usr/lib/libgobject-2.* + fun:g_type_add_interface_static + fun:g_type_module_get_type + fun:g_io_module_get_type + fun:g_io_module_new + fun:g_io_modules_scan_all_in_directory +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_memdup + obj:/targets/*/usr/lib/libgobject-2.* + fun:g_type_class_ref + fun:g_type_class_ref + fun:g_object_newv + fun:g_object_new + fun:g_io_module_new + fun:g_io_modules_scan_all_in_directory + obj:/targets/*/usr/lib/libgio-2.* + obj:/targets/*/usr/lib/libgio-2.* +} + +{ + + Memcheck:Leak + fun:realloc + fun:g_realloc + fun:g_realloc_n + obj:/targets/*/usr/lib/libgobject-2.* + obj:/targets/*/usr/lib/libgobject-2.* + fun:g_type_register_static + fun:g_type_register_static_simple + obj:/targets/*/usr/lib/libgio-2.* + obj:/targets/*/usr/lib/libgio-2.* + obj:/targets/*/usr/lib/libgio-2.* + fun:g_vfs_get_file_for_path + obj:/targets/*/usr/lib/gio/modules/libprdrm-gio-vfs0.so +} + +{ + + Memcheck:Leak + fun:malloc + fun:g_malloc + fun:g_memdup + obj:/targets/*/usr/lib/libgobject-2.* + fun:g_type_class_ref + fun:g_object_newv + fun:g_object_new + obj:/targets/*/usr/lib/libgio-2.* + obj:/targets/*/usr/lib/libgio-2.* + fun:g_vfs_get_file_for_path + obj:/targets/*/usr/lib/gio/modules/libprdrm-gio-vfs0.so + fun:g_vfs_get_file_for_path +} + + + + diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_GstScreenshot/ut_GstScreenshot.cpp b/qmafw-gst-subtitles-renderer/unittests/ut_GstScreenshot/ut_GstScreenshot.cpp new file mode 100644 index 0000000..9bf94a4 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_GstScreenshot/ut_GstScreenshot.cpp @@ -0,0 +1,209 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#include + +#include +#include +#include + +#include "MafwGstScreenshot.h" +#include "ut_GstScreenshot.h" + +static gchar *global_failing_gst_element = NULL; + +/* START OF STUB DEFINITIONS */ +GstElement *gst_element_factory_make(const gchar *factoryname, const gchar *name) +{ + + GstElementFactory *factory; + + qDebug() << __PRETTY_FUNCTION__ << factoryname; + + if (global_failing_gst_element != NULL && + g_str_equal(factoryname, global_failing_gst_element)) + { + qDebug() << "SIMULATED FAIL OF gst_element_factory_make()"; + return NULL; + } + else + { + factory = gst_element_factory_find(factoryname); + return gst_element_factory_create(factory, name); + } + +} +/* END OF STUB DEFINITIONS */ + +void ut_GstScreenshot::testFailures() +{ + gint width = 1024; + gint height = 1024; + gint size = width * height + width * height / 2; + GstBuffer *buf = gst_buffer_new_and_alloc(size); + QCOMPARE(GST_OBJECT_REFCOUNT_VALUE(buf), 1); + + memset(GST_BUFFER_DATA(buf), 0, size); + + /* no caps set, returns false */ + QVERIFY(m_screenshot->savePauseFrame(buf, + "/dev/null") == FALSE); + + buf = gst_buffer_new_and_alloc(size); + memset(GST_BUFFER_DATA(buf), 0, size); + + GstCaps *caps; + caps = gst_caps_new_simple("video/x-raw-yuv", + "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC ('I','4','2','0'), + "framerate", GST_TYPE_FRACTION, 25, 1, + "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1, + "width", G_TYPE_INT, width, + "height", G_TYPE_INT, height, + NULL); + gst_buffer_set_caps(buf, caps); + + /* fakesrc creation fails, returns false */ + global_failing_gst_element = g_strdup("fakesrc"); + QVERIFY(m_screenshot->savePauseFrame(buf, + "/dev/null") == FALSE); + + gst_caps_unref(caps); + + /* TODO: Gst pipeline errors are difficult to test... */ +} + +void ut_GstScreenshot::testConvert() +{ + gint width = 1024; + gint height = 512; + gint size = width * height + width * height / 2; + GstBuffer *buf = gst_buffer_new_and_alloc(size); + memset(GST_BUFFER_DATA(buf), 0, size); + + /* set caps, returns true */ + GstCaps *caps; + caps = gst_caps_new_simple("video/x-raw-yuv", + "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC ('I','4','2','0'), + "framerate", GST_TYPE_FRACTION, 25, 1, + "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1, + "width", G_TYPE_INT, width, + "height", G_TYPE_INT, height, + NULL); + gst_buffer_set_caps(buf, caps); + QVERIFY(m_screenshot->savePauseFrame(buf, + "/dev/null") == TRUE); + + QTest::qWait(500); + QVERIFY(m_gotScreenshotSignal == TRUE); + + gst_caps_unref(caps); +} + + +void ut_GstScreenshot::testCancel() +{ + gint width = 1024; + gint height = 512; + gint size = width * height + width * height / 2; + GstBuffer *buf = gst_buffer_new_and_alloc(size); + memset(GST_BUFFER_DATA(buf), 0, size); + + QFETCH(uint, timeout); + + /* set caps, returns true */ + GstCaps *caps; + caps = gst_caps_new_simple("video/x-raw-yuv", + "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC ('I','4','2','0'), + "framerate", GST_TYPE_FRACTION, 25, 1, + "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1, + "width", G_TYPE_INT, width, + "height", G_TYPE_INT, height, + NULL); + gst_buffer_set_caps(buf, caps); + + QVERIFY(m_screenshot->savePauseFrame(buf, + "/dev/null") == TRUE); + QTest::qWait(timeout); + if (m_gotScreenshotSignal == false ) + { + m_screenshot->cancelPauseFrame(); + QVERIFY(m_gotCancelSignal == TRUE); + } +} + +void ut_GstScreenshot::testCancel_data() +{ + QTest::addColumn("timeout"); + QTest::newRow("0 ms") << (uint)0; + QTest::newRow("1 ms") << (uint)1; + QTest::newRow("5 ms") << (uint)5; + QTest::newRow("10 ms") << (uint)10; + QTest::newRow("20 ms") << (uint)20; + QTest::newRow("35 ms") << (uint)35; + QTest::newRow("50 ms") << (uint)50; + QTest::newRow("100 ms") << (uint)100; +} + +void ut_GstScreenshot::slotScreenshot(char *location, GError *error) +{ + qDebug() << __PRETTY_FUNCTION__; + Q_UNUSED(location); + Q_UNUSED(error); + m_gotScreenshotSignal = TRUE; +} + +void ut_GstScreenshot::slotCancelScreenshot() +{ + qDebug() << __PRETTY_FUNCTION__; + + m_gotCancelSignal = TRUE; +} + +void ut_GstScreenshot::init() +{ + + qDebug() << __PRETTY_FUNCTION__; + + gst_init(0, 0); + + m_gotScreenshotSignal = FALSE; + m_gotCancelSignal = FALSE; + + m_screenshot = new MafwGstScreenshot(this); + connect(m_screenshot, SIGNAL(screenshotTaken(char*,GError*)), this, SLOT(slotScreenshot(char*,GError*))); + connect(m_screenshot, SIGNAL(screenshotCancelled()), this, SLOT(slotCancelScreenshot())); + + global_failing_gst_element = NULL; + +} + +void ut_GstScreenshot::cleanup() +{ + + qDebug() << __PRETTY_FUNCTION__; + g_free(global_failing_gst_element); + + while(QCoreApplication::hasPendingEvents()) + { + QCoreApplication::sendPostedEvents(); + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); + QCoreApplication::processEvents(); + } + delete m_screenshot; +} + +QTEST_MAIN(ut_GstScreenshot) diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_GstScreenshot/ut_GstScreenshot.h b/qmafw-gst-subtitles-renderer/unittests/ut_GstScreenshot/ut_GstScreenshot.h new file mode 100644 index 0000000..b9fca86 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_GstScreenshot/ut_GstScreenshot.h @@ -0,0 +1,52 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#ifndef UT_GSTSCREENSHOT_H_ +#define UT_GSTSCREENSHOT_H_ + +#include +#include + +#include "MafwGstScreenshot.h" + +class MafwGstScreenshot; + +class ut_GstScreenshot: public QObject +{ + Q_OBJECT + +private slots: // tests, don't touch the test order + + void testFailures(); + void testConvert(); + void testCancel(); + void testCancel_data(); + + void init(); + void cleanup(); + +protected slots: + void slotScreenshot(char *location, GError *error); + void slotCancelScreenshot(); + +private: //data + MafwGstScreenshot *m_screenshot; + bool m_gotCancelSignal; + bool m_gotScreenshotSignal; +}; + +#endif diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_GstScreenshot/ut_GstScreenshot.pro b/qmafw-gst-subtitles-renderer/unittests/ut_GstScreenshot/ut_GstScreenshot.pro new file mode 100644 index 0000000..3afb79c --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_GstScreenshot/ut_GstScreenshot.pro @@ -0,0 +1,29 @@ +TEMPLATE = app + +CONFIG -= gui +CONFIG += console + +isEmpty(PREFIX) { + PREFIX=/usr +} + +CONFIG += qt link_pkgconfig release +CONFIG += qtestlib +PKGCONFIG += glib-2.0 gobject-2.0 gstreamer-0.10 gstreamer-plugins-base-0.10 + +LIBS += -lgcov + +DEPENDPATH += . ../../inc ../../src +INCLUDEPATH += . ../../inc + +QMAKE_CXXFLAGS += -fprofile-arcs -ftest-coverage -Wall -Werror -g +QMAKE_CFLAGS += -fprofile-arcs -ftest-coverage -Wall -Werror -g + +QMAKE_CLEAN += *.gcda *.gcno *.gcov ut_GstScreenshot *.vg.xml vgcore.* + +HEADERS += ut_GstScreenshot.h \ + MafwGstScreenshot.h + +SOURCES += ut_GstScreenshot.cpp \ + MafwGstScreenshot.cpp + diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRenderer/ContextFWStub.cpp b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRenderer/ContextFWStub.cpp new file mode 100644 index 0000000..61e9da0 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRenderer/ContextFWStub.cpp @@ -0,0 +1,127 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#include +#include +#include +#include +#include +#include + +ContextProperty *global_video_route_property = 0; +ContextProperty *global_audio_route_property = 0; +QString global_audio_route(""); +QString global_video_route(""); +ContextPropertyInfo propertyInfo("key"); + +ContextProperty::ContextProperty(const QString& key, QObject*) +{ + if (key == "/com/nokia/policy/video_route" && global_video_route_property == 0) + { + global_video_route_property = this; + } + else if (key == "/com/nokia/policy/audio_route" && global_audio_route_property == 0) + { + global_audio_route_property = this; + } +} + +ContextProperty::~ContextProperty() +{ + qDebug() << __PRETTY_FUNCTION__; + if (this == global_video_route_property) + { + global_video_route_property = 0; + } + else if (this == global_audio_route_property) + { + global_audio_route_property = 0; + } +} + +// this non-const function is used to trigger the signal emissions +void ContextProperty::ignoreCommander() +{ + Q_EMIT global_video_route_property->valueChanged(); + Q_EMIT global_audio_route_property->valueChanged(); +} + +QString ContextProperty::key() const +{ + if (this == global_video_route_property) + { + return "/com/nokia/policy/video_route"; + } + else if (this == global_audio_route_property) + { + return "/com/nokia/policy/audio_route"; + } + else + { + return ""; + } +} + +QVariant ContextProperty::value() const +{ + if (this == global_video_route_property) + { + return QVariant(global_video_route); + } + else if (this == global_audio_route_property) + { + return QVariant(global_audio_route); + } + else + { + return QVariant(); + } +} + +const ContextPropertyInfo* ContextProperty::info() const +{ + return &propertyInfo; +} + +ContextPropertyInfo::ContextPropertyInfo(const QString &key, QObject *parent) +{ + Q_UNUSED(key); + Q_UNUSED(parent); +} + +const QList ContextPropertyInfo::providers() const +{ + QList list; + list << ContextProviderInfo("contextkit-dbus", "system:com.nokia.policy.pcfd"); + return list; +} + +// d-bus stub +bool QDBusError::isValid() const +{ + return false; +} + +QDBusMessage::MessageType QDBusMessage::type() const +{ + if( this->member()=="GetConnectionUnixProcessID" ) + { + return QDBusMessage::ReplyMessage; + } + + return QDBusMessage::SignalMessage; +} diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRenderer/MafwGstRendererDolbyStub.cpp b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRenderer/MafwGstRendererDolbyStub.cpp new file mode 100644 index 0000000..8262b95 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRenderer/MafwGstRendererDolbyStub.cpp @@ -0,0 +1,122 @@ +#include "MafwGstRendererDolby.h" + +#include + +const int DEFAULT_COLOR = 2; +const int DEFAULT_ROOM_SIZE = 2; +const int MAX_VALUE = 4; + +enum MafwDolbyStates +{ + MafwDolbyOff, + MafwDolbyOn, + MafwDolbyAuto +}; + +MafwGstRendererDolby::MafwGstRendererDolby( QObject* parent ): + QObject(parent), + m_dolbyConfMusic(0), + m_dolbyConfMusicRoom(0), + m_dolbyConfMusicColor(0), + m_dolbyConfVideo(0), + m_dolbyConfVideoRoom(0), + m_dolbyConfVideoColor(0) +{ + qDebug() << Q_FUNC_INFO; + m_currentMusicDolbyState = MafwDolbyOff; + m_currentMusicDolbyRoom = DEFAULT_ROOM_SIZE; + m_currentMusicDolbyColor = DEFAULT_COLOR; + m_currentVideoDolbyState = MafwDolbyOff; + m_currentVideoDolbyRoom = DEFAULT_ROOM_SIZE; + m_currentVideoDolbyColor = DEFAULT_COLOR; +} + +void MafwGstRendererDolby::initialize() +{ + qDebug() << Q_FUNC_INFO; +} + +MafwGstRendererDolby::~MafwGstRendererDolby() +{ + qDebug() << Q_FUNC_INFO; +} + +bool MafwGstRendererDolby::setMusicDolbyState (uint value) +{ +} + +bool MafwGstRendererDolby::setMusicDolbyRoom (int value) +{ + qDebug() << Q_FUNC_INFO; + return true; +} + +bool MafwGstRendererDolby::setMusicDolbyColor (int value) +{ + qDebug() << Q_FUNC_INFO; + return true; +} + +bool MafwGstRendererDolby::setVideoDolbyState (uint value) +{ + qDebug() << Q_FUNC_INFO; + return true; +} + +bool MafwGstRendererDolby::setVideoDolbyRoom (int value) +{ + qDebug() << Q_FUNC_INFO; + return true; +} + +bool MafwGstRendererDolby::setVideoDolbyColor (int value) +{ + qDebug() << Q_FUNC_INFO; + return true; +} + +void MafwGstRendererDolby::valueMusicChanged() +{ + qDebug() << Q_FUNC_INFO; +} + +void MafwGstRendererDolby::valueVideoChanged() +{ + qDebug() << Q_FUNC_INFO; +} + +uint MafwGstRendererDolby::getMusicDolbyState () +{ + qDebug() << Q_FUNC_INFO; + return m_currentMusicDolbyState; +} + +int MafwGstRendererDolby::getMusicDolbyRoom () +{ + qDebug() << Q_FUNC_INFO; + return m_currentMusicDolbyRoom; +} + +int MafwGstRendererDolby::getMusicDolbyColor () +{ + qDebug() << Q_FUNC_INFO; + return m_currentMusicDolbyColor; +} + +uint MafwGstRendererDolby::getVideoDolbyState () +{ + qDebug() << Q_FUNC_INFO; + return m_currentVideoDolbyState; +} + +int MafwGstRendererDolby::getVideoDolbyRoom () +{ + qDebug() << Q_FUNC_INFO; + return m_currentVideoDolbyRoom; +} + +int MafwGstRendererDolby::getVideoDolbyColor () +{ + qDebug() << Q_FUNC_INFO; + return m_currentVideoDolbyColor; +} diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRenderer/MafwMmcMonitorStub.cpp b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRenderer/MafwMmcMonitorStub.cpp new file mode 100644 index 0000000..f34050a --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRenderer/MafwMmcMonitorStub.cpp @@ -0,0 +1,45 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#include "MafwMmcMonitor.h" +#include + +bool stubMmcMounted = true; +MafwMmcMonitor* stubMmcMonitor; + +const QString MafwMmcMonitor::MMC_URI_PREFIX="file:///home/user/MyDocs"; + +MafwMmcMonitor::MafwMmcMonitor(QObject* parent) : QObject(parent), m_mounted(false) +{ + stubMmcMonitor = this; +} + +MafwMmcMonitor::~MafwMmcMonitor() +{ + stubMmcMonitor = 0; +} + +bool MafwMmcMonitor::isMounted() +{ + return stubMmcMounted; +} + +void MafwMmcMonitor::preUnmountEvent(const QString &/*state*/) +{ + Q_EMIT stubMmcMonitor->preUnmount(); +} + diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRenderer/QSettingsStub.cpp b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRenderer/QSettingsStub.cpp new file mode 100644 index 0000000..a8e4d06 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRenderer/QSettingsStub.cpp @@ -0,0 +1,32 @@ +#include + +QMap globalSettingsMap; + +//QSettings stub +QSettings::QSettings( const QString&, const QString&, QObject* ){} +//Hope we will never need below ctor stub, because it is used somehow by unit test framework. +//QSettings::QSettings(QSettings::Scope, const QString&, const QString&, QObject*){} +QSettings::QSettings(QSettings::Format, QSettings::Scope, const QString&, const QString&, QObject*){} +QSettings::QSettings(const QString&, QSettings::Format, QObject*){} +QSettings::QSettings(QObject*){} +QSettings::~QSettings(){} + +QVariant QSettings::value(const QString& key, const QVariant& defaultValue) const +{ + return globalSettingsMap.value(key, defaultValue); +} + +bool QSettings::contains(const QString &key) const +{ + return true; +} + +void QSettings::beginGroup(const QString &prefix) +{ + +} + +void QSettings::endGroup() +{ + +} diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRenderer/QmSystemStub.cpp b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRenderer/QmSystemStub.cpp new file mode 100644 index 0000000..3b04019 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRenderer/QmSystemStub.cpp @@ -0,0 +1,39 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#include +#include + +int global_qmsystemstub_setBlankingPauseCalled; + +namespace MeeGo { + QmDisplayState::QmDisplayState(QObject* parent) : QObject(parent) + { + } + + QmDisplayState::~QmDisplayState(){} + + bool QmDisplayState::setBlankingPause(void) + { + global_qmsystemstub_setBlankingPauseCalled++; + return true; + } + bool QmDisplayState::cancelBlankingPause(void) + { + return true; + } +} diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRenderer/Ut_MafwGstRenderer.cpp b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRenderer/Ut_MafwGstRenderer.cpp new file mode 100644 index 0000000..dbbded0 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRenderer/Ut_MafwGstRenderer.cpp @@ -0,0 +1,1662 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#include "Ut_MafwGstRenderer.h" + +#include +#include +#include +#include +#include +#include "MafwGstRendererVolume.h" +#include "MafwMetadata.h" +#include "MafwGstRendererPlaylistFileUtility.h" +#include "MafwStubHelper.h" +#include "MafwRendererPolicyStub.h" +#include "mafw-gst-renderer-worker.h" +#include "MafwMediaInfo.h" + +#include "QNetworkStubs.h" +#include + +#define private public // access private members from unit tests +#include "MafwGstRenderer.h" +#include "MafwMmcMonitor.h" + +extern void setStubHelper(MafwStubHelper* stubHlp); +extern void setMafwRendererPolicy(MafwRendererPolicy *policy ); + +extern GError* global_error; +extern gint global_position; +extern int global_qmsystemstub_setBlankingPauseCalled; +extern ContextProperty *global_video_route_property; +extern ContextProperty *global_audio_route_property; +extern QString global_audio_route; +extern QString global_video_route; +extern gboolean global_worker_playing; +extern const gchar* global_worker_uri; +extern int global_worker_stop_called; +extern int global_worker_seek_request; +extern guint global_ready_timeout; +extern GSList *global_worker_destinations; +extern NetworkStubHelper networkStub; +extern configuration *current_worker_conf; +extern QMap globalSettingsMap; + +extern bool stubMmcMounted; +extern MafwMmcMonitor* stubMmcMonitor; +extern QStringList stubPlaylistFileUtilityUris; + +void Ut_MafwGstRenderer::initTestCase() +{ + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + g_type_init(); + + m_stubHelper = new MafwStubHelper; + setStubHelper(m_stubHelper); + m_rendererPolicy = new MafwRendererPolicyStub(); + setMafwRendererPolicy(m_rendererPolicy); + + m_metadataCount = 0; + + /* Section to test initialization */ + MafwGstRenderer *renderer = new MafwGstRenderer("renderer_uuid", "renderer-plugin", "rendere_name"); + //Basic renderer initialize fails + m_stubHelper->expect("initialize", false); + QVERIFY(renderer->initialize(0) == false); + // Let setDefaultRendererPolicy fail now to get more complete unittesting. + // Failure in loading policy is not considered fatal at this point. + m_stubHelper->expect("initialize", true); + m_stubHelper->expect("setDefaultRendererPolicy", false); + QVERIFY(renderer->initialize(0) == true); + delete renderer; + /* Section End */ + + m_renderer = new MafwGstRenderer("renderer_uuid", "renderer-plugin", "rendere_name"); + + m_stubHelper->expect("initialize", true); + m_stubHelper->expect("setDefaultRendererPolicy", true); + QVERIFY(m_renderer->initialize(0) == true); + QVERIFY(m_renderer->m_worker); + + //redundant initialize call + QVERIFY(m_renderer->initialize(0) == true); + + global_qmsystemstub_setBlankingPauseCalled = 0; +} + +void Ut_MafwGstRenderer::testPlayURI() +{ + //rendererPlaying signal not emitted there + MafwMediaInfo content(""); + QMap > map; + map[MAFW_METADATA_KEY_URI] = QList() << "http://test.play.uri"; + content.setMetaData(map); + + m_renderer->doPlay(content); +} + +/** + * PlaylistFileUtilityStub gives always two uris to play + */ +void Ut_MafwGstRenderer::testPlayPlaylistFile() +{ + QSignalSpy playSpy(m_renderer, SIGNAL(rendererPlaying(int))); + + MafwMediaInfo mediaInfo; + QMap testUri; + QMap testMimeType; + testUri.insert(MAFW_METADATA_KEY_URI, "file:///var/tmp/tunein-station.pls"); + testMimeType.insert(MAFW_METADATA_KEY_MIME, "audio/x-scpls"); + + mediaInfo.setMetaData(testUri); + mediaInfo.setMetaData(testMimeType); + + m_renderer->doPlay(mediaInfo); + QTest::qWait(10); + QCOMPARE(playSpy.count(), 1); + QList arguments = playSpy.takeFirst(); + QCOMPARE(arguments.count(), 1); + QCOMPARE(arguments.at(0).toInt(), 1); //MafwRendererPlayingUri::CurrentUri + +//Test that next uri is started to play if playing first stops because of an error + GError* error = g_error_new_literal(1, 2, "Playlist uri playback error"); + m_renderer->m_worker->notify_error_handler(m_renderer->m_worker, m_renderer, error); + QTest::qWait(10); + QCOMPARE(playSpy.count(), 0); + QVERIFY(global_worker_playing); + QCOMPARE(QString(global_worker_uri), QString("testUri2")); + +//Test that renderer does not go playing state anymore when all uris is used + m_renderer->m_worker->notify_error_handler(m_renderer->m_worker, m_renderer, error); + QTest::qWait(10); + QCOMPARE(playSpy.count(), 0); + + g_error_free(error); +} + +void Ut_MafwGstRenderer::testStop() +{ + //@TODO doStop needs to be tested for all the states the renderer can be in. + QSignalSpy spy(m_renderer, SIGNAL(rendererStopped())); + m_renderer->doStop(); + QCOMPARE(spy.count(), 1); + //Not clear that should signal be emitted when calling doStop at stopped state + m_renderer->doStop(); + m_renderer->doStop(); +} + +void Ut_MafwGstRenderer::testPause() +{ + m_renderer->doPause(); +} + +void Ut_MafwGstRenderer::testResume() +{ + m_renderer->doResume(); +} + +void Ut_MafwGstRenderer::testSeek() +{ + QSignalSpy errorSpy(m_renderer, SIGNAL(rendererError(MafwError))); + global_error = g_error_new_literal(1, 2, "The seek error"); + m_renderer->doSeek(1, MafwRenderer::SeekAbsolute); + QCOMPARE(errorSpy.count(), 1); + + global_error = 0; + m_renderer->doSeek(100, MafwRenderer::SeekRelative); +} + +void Ut_MafwGstRenderer::testDefaultConfiguration() +{ + QVERIFY2(sizeof(configuration) == 64, "You've (or somebody else) most likely changed the configuration struct! Update the unittests also!"); + + MafwGstRenderer *rnd = new MafwGstRenderer("tesssssst", "conf-test-plug", "conf-test-rnd"); + QSettings settings("test-set", QSettings::NativeFormat); + + m_stubHelper->expect("initialize", true); + + rnd->initialize(&settings); + + QCOMPARE(current_worker_conf->asink, "pulsesink"); + QCOMPARE(current_worker_conf->vsink, "omapxvsink"); + QCOMPARE(current_worker_conf->buffer_time, 300000LL); + QCOMPARE(current_worker_conf->latency_time, 100000LL); + QCOMPARE(current_worker_conf->flags, 67); + + QCOMPARE(current_worker_conf->mobile_surround_music.state, 0U); + QCOMPARE(current_worker_conf->mobile_surround_music.color, 2); + QCOMPARE(current_worker_conf->mobile_surround_music.room, 2); + QCOMPARE(current_worker_conf->mobile_surround_video.state, 0U); + QCOMPARE(current_worker_conf->mobile_surround_video.color, 2); + QCOMPARE(current_worker_conf->mobile_surround_video.room, 2); + + QCOMPARE(current_worker_conf->milliseconds_to_pause_frame, 1000U); + QCOMPARE(current_worker_conf->seconds_to_pause_to_ready, 3U); + QCOMPARE(current_worker_conf->use_dhmmixer, 1); + + delete rnd; + +} + +void Ut_MafwGstRenderer::testConfiguration() +{ + globalSettingsMap.clear(); + globalSettingsMap.insert("audio-sink", "no-audio"); + globalSettingsMap.insert("video-sink", "no-video"); + globalSettingsMap.insert("flags", 67); + globalSettingsMap.insert("use_dhmmixer", false); + globalSettingsMap.insert("buffer-time", 313); + globalSettingsMap.insert("latency-time", 007); + + globalSettingsMap.insert("pause-frame", 13000); + globalSettingsMap.insert("pause-to-ready", 7); + + globalSettingsMap.insert("dhm-music-surround", 6); + globalSettingsMap.insert("dhm-music-color", 5); + globalSettingsMap.insert("dhm-music-room-size", 4); + globalSettingsMap.insert("dhm-video-surround", 3); + globalSettingsMap.insert("dhm-video-color", 12); + globalSettingsMap.insert("dhm-video-room-size", 1); + + MafwGstRenderer *rnd = new MafwGstRenderer("tesst", "conf-test-plug", "conf-test-rnd"); + QSettings settings("test-set", QSettings::NativeFormat); + m_stubHelper->expect("initialize", true); + rnd->initialize(&settings); + + QCOMPARE(current_worker_conf->asink, "no-audio"); + QCOMPARE(current_worker_conf->vsink, "no-video"); + QCOMPARE(current_worker_conf->buffer_time, 313LL); + QCOMPARE(current_worker_conf->latency_time, 007LL); + QCOMPARE(current_worker_conf->flags, 67); + + QCOMPARE(current_worker_conf->mobile_surround_music.state, 6U); + QCOMPARE(current_worker_conf->mobile_surround_music.color, 5); + QCOMPARE(current_worker_conf->mobile_surround_music.room, 4); + + // for some reason video values are overwritten via gconf in unittests + QCOMPARE(current_worker_conf->mobile_surround_video.state, 3U); + QCOMPARE(current_worker_conf->mobile_surround_video.color, 12); + QCOMPARE(current_worker_conf->mobile_surround_video.room, 1); + + QCOMPARE(current_worker_conf->milliseconds_to_pause_frame, 13000U); + QCOMPARE(current_worker_conf->seconds_to_pause_to_ready, 7U); + QCOMPARE(current_worker_conf->use_dhmmixer, 0); + + globalSettingsMap.clear(); + + delete rnd; +} + +void Ut_MafwGstRenderer::testNextHint() +{ + MafwMediaInfo content(""); + QMap >map; + map[MAFW_METADATA_KEY_URI] = QList() << "http://test.play.next_uri"; + content.setMetaData(map); + + m_renderer->doNextHint(content); +} + +void Ut_MafwGstRenderer::testPosition() +{ + QVERIFY(connect(m_renderer, SIGNAL(rendererError(MafwError)), this, SLOT(positionError(MafwError)))); + //set renderer to playing state first + MafwMediaInfo content(""); + QMap >map; + map[MAFW_METADATA_KEY_URI] = QList() << "http://test.play.uri"; + content.setMetaData(map); + + m_renderer->doPlay(content); + + // Normal case + m_position = 0; + global_position = 157; + m_renderer->getPosition(this, SLOT(positionChanged(uint))); + QTest::qWait(10); + QVERIFY2(m_position == 157, "Wrong position"); + + //repeated call with same value + m_position = 0; + m_renderer->getPosition(this, SLOT(positionChanged(uint))); + QTest::qWait(10); + QVERIFY2(m_position == 157, "Wrong position"); + + // Normal case, maxint position + m_position = 0; + int maxint = 2147483647; + global_position = maxint; + m_renderer->getPosition(this, SLOT(positionChanged(uint))); + QTest::qWait(10); + QVERIFY2(m_position == (uint)maxint, "Wrong position"); + + // Normal case, position to 0 + global_position = 0; + m_renderer->getPosition(this, SLOT(positionChanged(uint))); + QTest::qWait(10); + QVERIFY2(m_position == 0, "Wrong position"); + + // Errorenuous position case + m_position = 0; + global_position = -1; + m_renderer->getPosition(this, SLOT(positionChanged(uint))); + QTest::qWait(10); + QVERIFY2(m_position == 0, "Wrong position"); + QVERIFY2(m_error.code() == MafwError::RendererError_CannotGetPosition, "Wrong error code"); + + //Errorenuous position case, -maxint + m_position = 0; + global_position = -maxint; + m_renderer->getPosition(this, SLOT(positionChanged(uint))); + QTest::qWait(10); + QVERIFY2(m_position == 0, "Wrong position"); + + //NULL result slot + global_position = 1; + m_renderer->getPosition(0, SLOT(positionChanged(uint))); + QTest::qWait(10); + + //Not existing error slot + global_position = -1; + m_renderer->getPosition(this, SLOT(positionChanged(uint))); + QTest::qWait(10); + + // Stopped case when 0 should be returned + m_renderer->doStop(); + m_position = 0; + global_position = -1; + m_renderer->getPosition(this, SLOT(positionChanged(uint))); + QTest::qWait(10); + QVERIFY2(m_position == 0, "Wrong position"); +} + +void Ut_MafwGstRenderer::testProperty() +{ + QFETCH(QString, name); + QFETCH(QVariant, value); + bool success = m_renderer->setMafwProperty(name, value); + QVERIFY2(success, "Unable to set property"); + + m_renderer->mafwProperty(name, this, SLOT(propertyChanged(const QString&, const QVariant&))); + QTest::qWait(10); + + //@TODO getter for current-frame-on-pause needs to be implemented + if (name == "colorkey") + { + QCOMPARE(m_value, QVariant(1)); //value comes from worker stub + + //Test unexisting slot case + m_renderer->mafwProperty(name, this, SLOT(propertyChanged2(const QString&, const QVariant&))); + QTest::qWait(10); + } + else if (name != "unknown" + && name!= "current-frame-on-pause" + && name != MafwRenderer::MAFW_RENDERER_PROPERTY_POLICY_OVERRIDE ) + { + QCOMPARE(m_name, name); + QCOMPARE(m_value, value); + } +} + +void Ut_MafwGstRenderer::testProperty_data() +{ + QTest::addColumn("name"); + QTest::addColumn("value"); + + QTest::newRow("volume") << "volume" << QVariant(1); + QTest::newRow("force-aspect-ratio") << "force-aspect-ratio" << QVariant(false); + QTest::newRow("autopaint") << "autopaint" << QVariant(true); + QTest::newRow("xid") << "xid" << QVariant(12345); + QTest::newRow("current-frame-on-pause") << "current-frame-on-pause" << QVariant(true); + QTest::newRow("colorkey") << "colorkey" << QVariant(23456); + QTest::newRow("render-rectangle") << "render-rectangle" << QVariant(QString("%1,%2,%3,%4").arg(1).arg(2).arg(3).arg(4)); + QTest::newRow("unknown property") << "unknown" << QVariant(true); +} + +void Ut_MafwGstRenderer::testPlaybackSpeedProperty() +{ + QString propertyName = "playback-speed"; + + QSignalSpy propertyChangedSpy(m_renderer, SIGNAL(mafwPropertyChanged(const QString&, const QVariant&))); + + // + //Stopped - won't accept the property + // + m_renderer->setMafwProperty(propertyName, 0); + m_renderer->setMafwProperty(propertyName, 1); + m_renderer->setMafwProperty(propertyName, -1); + QCOMPARE(propertyChangedSpy.size(), 0); + + // + //Playing + // + //then test in playing state + MafwMediaInfo content(""); + QMap >map; + map[MAFW_METADATA_KEY_URI] = QList() << "http://test.play.uri"; + content.setMetaData(map); + + m_renderer->doPlay(content); + + //no change in playback, just expect the signal + m_renderer->setMafwProperty(propertyName, 1); + QTest::qWait(20); + QCOMPARE(propertyChangedSpy.size(), 1); + QList arguments = propertyChangedSpy.takeFirst(); + QCOMPARE(arguments.count(), 2); + QCOMPARE(arguments.at(0).toString(), propertyName); + //There is a QVariant inside QVariant + QCOMPARE(arguments.at(1).value().toFloat(), float(1)); + + m_renderer->setMafwProperty(propertyName, 2); + QTest::qWait(20); + QCOMPARE(propertyChangedSpy.size(), 1); + arguments = propertyChangedSpy.takeFirst(); + QCOMPARE(arguments.count(), 2); + QCOMPARE(arguments.at(0).toString(), propertyName); + //There is a QVariant inside QVariant + QCOMPARE(arguments.at(1).value().toFloat(), float(2)); + + m_renderer->setMafwProperty(propertyName, 2); + QTest::qWait(20); + QCOMPARE(propertyChangedSpy.size(), 1); + arguments = propertyChangedSpy.takeFirst(); + QCOMPARE(arguments.count(), 2); + QCOMPARE(arguments.at(0).toString(), propertyName); + //There is a QVariant inside QVariant + QCOMPARE(arguments.at(1).value().toFloat(), float(2)); + + m_renderer->setMafwProperty(propertyName, -1); + QTest::qWait(20); + QCOMPARE(propertyChangedSpy.size(), 1); + arguments = propertyChangedSpy.takeFirst(); + QCOMPARE(arguments.count(), 2); + QCOMPARE(arguments.at(0).toString(), propertyName); + //There is a QVariant inside QVariant + QCOMPARE(arguments.at(1).value().toFloat(), float(-1)); + + m_renderer->setMafwProperty(propertyName, 0.3); + QTest::qWait(20); + QCOMPARE(propertyChangedSpy.size(), 1); + arguments = propertyChangedSpy.takeFirst(); + QCOMPARE(arguments.count(), 2); + QCOMPARE(arguments.at(0).toString(), propertyName); + //There is a QVariant inside QVariant + QCOMPARE(arguments.at(1).value().toFloat(), float(0.3)); + + m_renderer->setMafwProperty(propertyName, 1.3); + QTest::qWait(20); + QCOMPARE(propertyChangedSpy.size(), 1); + arguments = propertyChangedSpy.takeFirst(); + QCOMPARE(arguments.count(), 2); + QCOMPARE(arguments.at(0).toString(), propertyName); + //There is a QVariant inside QVariant + QCOMPARE(arguments.at(1).value().toFloat(), float(1.3)); + + m_renderer->setMafwProperty(propertyName, -0.3); + QTest::qWait(20); + QCOMPARE(propertyChangedSpy.size(), 1); + arguments = propertyChangedSpy.takeFirst(); + QCOMPARE(arguments.count(), 2); + QCOMPARE(arguments.at(0).toString(), propertyName); + //There is a QVariant inside QVariant + QCOMPARE(arguments.at(1).value().toFloat(), float(-0.3)); + + m_renderer->setMafwProperty(propertyName, -1.3); + QTest::qWait(20); + QCOMPARE(propertyChangedSpy.size(), 1); + arguments = propertyChangedSpy.takeFirst(); + QCOMPARE(arguments.count(), 2); + QCOMPARE(arguments.at(0).toString(), propertyName); + //There is a QVariant inside QVariant + QCOMPARE(arguments.at(1).value().toFloat(), float(-1.3)); + //check that mafwProperty() returns the correct value + m_value = QVariant(0); + QVERIFY(m_renderer->mafwProperty(propertyName, this, SLOT(propertyChanged(const QString&, const QVariant&)))); + QTest::qWait(20); + QCOMPARE(m_value.toFloat(), float(-1.3)); + + //TODO what does 0 mean? + m_renderer->setMafwProperty(propertyName, 0); + QCOMPARE(propertyChangedSpy.size(), 1); + arguments = propertyChangedSpy.takeFirst(); + QCOMPARE(arguments.count(), 2); + QCOMPARE(arguments.at(0).toString(), propertyName); + //There is a QVariant inside QVariant + QCOMPARE(arguments.at(1).value().toFloat(), float(0)); + + // + //Paused - won't accept the property + // + m_renderer->doPause(); + m_renderer->setMafwProperty(propertyName, 3); + //check that mafwProperty() returns the correct value + m_value = QVariant(-1); //not same as the last value set in playing state + QVERIFY(m_renderer->mafwProperty(propertyName, this, SLOT(propertyChanged(const QString&, const QVariant&)))); + QTest::qWait(20); + QCOMPARE(m_value, QVariant(0)); //the value that was last set in playing state + + //TODO test property value after resume + + // + //Stopped - won't accept the property + // + m_renderer->doStop(); + + m_renderer->setMafwProperty(propertyName, 3); + //check that mafwProperty() returns the correct value + m_value = QVariant(-1); //not same as the last value set in playing state + QVERIFY(m_renderer->mafwProperty(propertyName, this, SLOT(propertyChanged(const QString&, const QVariant&)))); + QTest::qWait(20); + QCOMPARE(m_value, QVariant(0)); //the value that was last set in playing state + +} + +void Ut_MafwGstRenderer::cleanupTestCase() +{ + delete m_renderer; + g_slist_free(global_worker_destinations); + + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); + while(QCoreApplication::hasPendingEvents()) + { + QCoreApplication::sendPostedEvents(); + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); + QCoreApplication::processEvents(); + } +} + +/** + * Pointers to callback functions after are set to renderer worker at + * MafwGstRenderer::initialize + */ +void Ut_MafwGstRenderer::testCallbacks() +{ + GValue intValue; + GValue uintValue; + GValue boolValue; + GValue stringValue; + GValue uninitValue; + GValue objectValue; + GValue doubleValue; + GValue int64Value; + + memset(&intValue, 0, sizeof(GValue)); + memset(&uintValue, 0, sizeof(GValue)); + memset(&boolValue, 0, sizeof(GValue)); + memset(&stringValue, 0, sizeof(GValue)); + memset(&uninitValue, 0, sizeof(GValue)); + memset(&objectValue, 0, sizeof(GValue)); + memset(&doubleValue, 0, sizeof(GValue)); + memset(&int64Value, 0, sizeof(GValue)); + + g_value_init(&intValue, G_TYPE_INT); + g_value_init(&uintValue, G_TYPE_UINT); + g_value_init(&boolValue, G_TYPE_BOOLEAN); + g_value_init(&stringValue, G_TYPE_STRING); + g_value_init(&objectValue, G_TYPE_OBJECT); + g_value_init(&doubleValue, G_TYPE_DOUBLE); + g_value_init(&int64Value, G_TYPE_INT64); + + g_value_set_int(&intValue, -123); + g_value_set_uint(&uintValue, (uint)-1); + g_value_set_boolean(&boolValue, TRUE); + g_value_set_string(&stringValue, "The test property"); + g_value_set_double(&doubleValue, 1.01); + g_value_set_int64(&int64Value, 1234567890); + +//playCallback + QSignalSpy playSpy(m_renderer, SIGNAL(rendererPlaying(int))); + m_renderer->m_worker->notify_play_handler(m_renderer->m_worker, m_renderer); + QCOMPARE(playSpy.count(), 1); + +//pauseCallback + QSignalSpy pauseSpy(m_renderer, SIGNAL(rendererPaused())); + m_renderer->m_worker->notify_pause_handler(m_renderer->m_worker, m_renderer); + QCOMPARE(pauseSpy.count(), 1); + +//errorCallback + QSignalSpy errorSpy(m_renderer, SIGNAL(rendererError(MafwError))); + GError* error = g_error_new_literal(1, 2, "The unit test error"); + GError* error2 = g_error_new_literal(1, -1, "Another unit test error"); + m_renderer->m_worker->notify_error_handler(m_renderer->m_worker, m_renderer, error); + m_renderer->m_worker->notify_error_handler(m_renderer->m_worker, m_renderer, error2); + QCOMPARE(errorSpy.count(), 2); + g_error_free(error); + g_error_free(error2); + +//eosCallback + QSignalSpy eosSpy(m_renderer, SIGNAL(rendererEos())); + m_renderer->m_worker->notify_eos_handler(m_renderer->m_worker, m_renderer); + QTest::qWait(10); //renderer paused will be emitted asynchronously + QCOMPARE(eosSpy.count(), 1); + +//metadataCallback + QSignalSpy metadataSpy(m_renderer, SIGNAL(metadataChanged(const QString&, const QList&))); + GValueArray *array = g_value_array_new(0); + g_value_array_append(array, &stringValue); + g_value_array_append(array, &doubleValue); + g_value_array_append(array, &int64Value); + + //unknown metadata key + m_renderer->m_worker->notify_metadata_handler(m_renderer->m_worker, m_renderer, -1, G_TYPE_VALUE_ARRAY, array); + //invalid type + m_renderer->m_worker->notify_metadata_handler(m_renderer->m_worker, m_renderer, 1, G_TYPE_VALUE, &stringValue); + //valid case + m_renderer->m_worker->notify_metadata_handler(m_renderer->m_worker, m_renderer, 1, G_TYPE_VALUE_ARRAY, array); + + QCOMPARE(metadataSpy.count(), 1); + g_value_array_free(array); + +//propertyCallback + int count = 0; + QSignalSpy propertySpy(m_renderer, SIGNAL(mafwPropertyChanged(const QString&, const QVariant&))); + + //auto paint + m_renderer->m_worker->notify_property_handler(m_renderer->m_worker, m_renderer, WORKER_PROPERTY_AUTOPAINT, &boolValue); + QCOMPARE(propertySpy.count(), ++count); + + //color key + m_renderer->m_worker->notify_property_handler(m_renderer->m_worker, m_renderer, WORKER_PROPERTY_COLORKEY, &intValue); + QCOMPARE(propertySpy.count(), ++count); + + //xid + m_renderer->m_worker->notify_property_handler(m_renderer->m_worker, m_renderer, WORKER_PROPERTY_XID, &uintValue); + QCOMPARE(propertySpy.count(), ++count); + + //pause frame + m_renderer->m_worker->notify_property_handler(m_renderer->m_worker, m_renderer, WORKER_PROPERTY_CURRENT_FRAME_ON_PAUSE, &boolValue); + QCOMPARE(propertySpy.count(), ++count); + + //force aspect ratio + m_renderer->m_worker->notify_property_handler(m_renderer->m_worker, m_renderer, WORKER_PROPERTY_FORCE_ASPECT_RATIO, &boolValue); + QCOMPARE(propertySpy.count(), ++count); + + //playback speed + m_renderer->m_worker->notify_property_handler(m_renderer->m_worker, m_renderer, WORKER_PROPERTY_PLAYBACK_SPEED, &doubleValue); + QCOMPARE(propertySpy.count(), ++count); + + //unknown property id + m_renderer->m_worker->notify_property_handler(m_renderer->m_worker, m_renderer, 6, &uintValue); + QCOMPARE(propertySpy.count(), ++count); + + //!G_IS_VALUE + m_renderer->m_worker->notify_property_handler(m_renderer->m_worker, m_renderer, WORKER_PROPERTY_CURRENT_FRAME_ON_PAUSE, &uninitValue); + QCOMPARE(propertySpy.count(), count); + + //unsupported value g_type + m_renderer->m_worker->notify_property_handler(m_renderer->m_worker, m_renderer, WORKER_PROPERTY_MUTE, &objectValue); + QCOMPARE(propertySpy.count(), count); + + //boolean property with string value, currently there is no check -> emits mafwPropertyChanged( "mute", "The test property" ) + m_renderer->m_worker->notify_property_handler(m_renderer->m_worker, m_renderer, WORKER_PROPERTY_MUTE, &intValue); + + +//bufferStatusCallback + QSignalSpy bufferSpy(m_renderer, SIGNAL(bufferingInfo(float))); + m_renderer->m_worker->notify_buffer_status_handler(m_renderer->m_worker, m_renderer, (gdouble)1.001); + m_renderer->m_worker->notify_buffer_status_handler(m_renderer->m_worker, m_renderer, (gdouble)-1.001); + m_renderer->m_worker->notify_buffer_status_handler(m_renderer->m_worker, m_renderer, (gdouble)1001); + QCOMPARE(bufferSpy.count(), 3); + + g_value_unset(&intValue); + g_value_unset(&uintValue); + g_value_unset(&boolValue); + g_value_unset(&stringValue); + g_value_unset(&int64Value); + g_value_unset(&doubleValue); +} + +void Ut_MafwGstRenderer::testGetCurrentMediaInfo() +{ + QFETCH(QString, name); + QFETCH(QList, values); + + qDebug() << "values.size():" << values.size(); + + MafwMediaInfo content(""); + MafwMediaInfo mediaInfo; + QMap > map; + QMap >old_map; + + bool success = false; + m_name = name; + + GValueArray *array = g_value_array_new(0); + + if(name == "all") + { + QList valueList; + valueList.append("http://test.play.uri"); + map[MAFW_METADATA_KEY_URI] = valueList; + old_map[MAFW_METADATA_KEY_URI] = valueList; + mediaInfo.setMetaData(map); + content.setMetaData(old_map); + + GValue geevalue; + memset(&geevalue, 0, sizeof(GValue)); + g_value_init(&geevalue, G_TYPE_STRING); + g_value_set_string(&geevalue, "http://test.play.uri"); + g_value_array_append(array, &geevalue); + g_value_unset(&geevalue); + + m_renderer->doPlay(content); + m_renderer->m_worker->notify_metadata_handler(m_renderer->m_worker, m_renderer, 18, G_TYPE_VALUE_ARRAY, array); + success = m_renderer->getCurrentMediaInfo(this, SLOT(media_info_result(const MafwMediaInfo& ))); + QTest::qWait(10); + + if(success) + { + QCOMPARE(m_media_values.at(0), mediaInfo.firstMetaData(MAFW_METADATA_KEY_URI)); + } + else if(!success) + { + QVERIFY(success); + } + } + else + { + //URI must be set + QMap onlyURI; + onlyURI[MAFW_METADATA_KEY_URI] = "http://2222.222.22"; + content.setMetaData(onlyURI); + m_renderer->doPlay(content); + + map[name] = values; + old_map[name] = values; + mediaInfo.setMetaData(map); + content.setMetaData(old_map); + m_metadataCount++; + + Q_FOREACH(QVariant value, values) + { + GValue geevalue; + memset(&geevalue, 0, sizeof(GValue)); + QString type = value.typeName(); + + if(type == "QString") + { + g_value_init(&geevalue, G_TYPE_STRING); + g_value_set_string(&geevalue, value.toByteArray()); + } + else if(type == "int") + { + g_value_init(&geevalue, G_TYPE_INT); + g_value_set_int(&geevalue, value.toInt()); + } + else if(type == "float") + { + g_value_init(&geevalue, G_TYPE_DOUBLE); + g_value_set_double(&geevalue, value.toDouble()); + } + else if(type == "bool") + { + g_value_init(&geevalue, G_TYPE_BOOLEAN); + g_value_set_boolean(&geevalue, value.toBool()); + } + + g_value_array_append(array, &geevalue); + g_value_unset(&geevalue); + } + qDebug() << "array.size:" << array->n_values; + m_renderer->m_worker->notify_metadata_handler(m_renderer->m_worker, m_renderer, m_metadataCount, G_TYPE_VALUE_ARRAY, array); + + success = m_renderer->getCurrentMediaInfo(this, SLOT(media_info_result(const MafwMediaInfo& )), name); + QTest::qWait(10); + + if(success) + { + //TODO rethink this and do the test properly if more metadata start to have related info attached + //Related metadata is returned with paused-thumbnail-uri. + if(name == "paused-thumbnail-uri") + { + values.append(QVariant(QString("http://2222.222.22"))); + values.append(QVariant(0)); + } + qDebug() << m_media_values; + qDebug() << values; + QCOMPARE(m_media_values, values); + } + else if(!success) + { + QVERIFY(success); + } + } + + g_value_array_free(array); +} + +void Ut_MafwGstRenderer::testGetCurrentMediaInfo_data() +{ + QTest::addColumn("name"); + QTest::addColumn >("values"); + + QTest::newRow("all") << "all" << QList(); + QTest::newRow("title") << MAFW_METADATA_KEY_TITLE << ( QList() << QVariant("test-title") ); + QTest::newRow("artist") << MAFW_METADATA_KEY_ARTIST << ( QList() << QVariant("test-artist") << QVariant("test-artist 2") ); + QTest::newRow("audio-codec") << MAFW_METADATA_KEY_AUDIO_CODEC << ( QList() << QVariant("test-audio-codec") ); + QTest::newRow("video-codec") << MAFW_METADATA_KEY_VIDEO_CODEC << ( QList() << QVariant("test-video-codec") ); + QTest::newRow("bitrate") << MAFW_METADATA_KEY_BITRATE << ( QList() << QVariant(256) ); + QTest::newRow("encoding") << MAFW_METADATA_KEY_ENCODING << ( QList() << QVariant("test-encoding") ); + QTest::newRow("album") << MAFW_METADATA_KEY_ALBUM << ( QList() << QVariant("test-album") ); + QTest::newRow("genre") << MAFW_METADATA_KEY_GENRE << ( QList() << QVariant("test-genre") ); + QTest::newRow("track") << MAFW_METADATA_KEY_TRACK << ( QList() << QVariant(1) ); + QTest::newRow("organization") << MAFW_METADATA_KEY_ORGANIZATION << ( QList() << QVariant("test-organization") ); + QTest::newRow("renderer-art-uri") << MAFW_METADATA_KEY_RENDERER_ART_URI << ( QList() << QVariant("http://test.renderer.art.uri") ); + QTest::newRow("res-x") << MAFW_METADATA_KEY_RES_X << ( QList() << QVariant(480) ); + QTest::newRow("res-y") << MAFW_METADATA_KEY_RES_Y << ( QList() << QVariant(640) ); + QTest::newRow("video-framerate") << MAFW_METADATA_KEY_VIDEO_FRAMERATE << ( QList() << QVariant(float(30/1)) ); + QTest::newRow("duration") << MAFW_METADATA_KEY_DURATION << ( QList() << QVariant(5) ); + QTest::newRow("is-seekable") << MAFW_METADATA_KEY_IS_SEEKABLE << ( QList() << QVariant(true) ); + QTest::newRow("paused-thumbnail-uri") << MAFW_METADATA_KEY_PAUSED_THUMBNAIL_URI << ( QList() << QVariant("file:///test.thumbnail.paused.uri") );// << QVariant("http://test.play.uri") << QVariant(666) ); + QTest::newRow("uri") << MAFW_METADATA_KEY_URI << ( QList() << QVariant("http://test.play.uri") ); +} + +void Ut_MafwGstRenderer::testErrorCodeScenarios() +{ + m_renderer->m_playingPlaylistFile = false; + + QFETCH(QString, uri); + QFETCH(int, workerError); + QFETCH(MafwError::Code, emittedError); + + global_worker_uri = g_strdup(uri.toAscii().constData()); + + QSignalSpy errorSpy(m_renderer, SIGNAL(rendererError(MafwError))); + + GError* gerror = g_error_new_literal(1, workerError, "Media not found"); + m_renderer->m_worker->notify_error_handler(m_renderer->m_worker, m_renderer, gerror); + + QCOMPARE(errorSpy.size(), 1); + + QVariantList params = errorSpy.takeFirst(); + MafwError mafwError = params.at(0).value(); + + QCOMPARE(mafwError.code(), MafwError::Code(emittedError)); + + g_error_free(gerror); +} + +void Ut_MafwGstRenderer::testErrorCodeScenarios_data() +{ + QTest::addColumn("uri"); + QTest::addColumn("workerError"); + QTest::addColumn("emittedError"); + + QTest::newRow("Local file not found") << "file:///donotexits.mp3" << int(WORKER_ERROR_MEDIA_NOT_FOUND) << MafwError::RendererError_MediaNotFound; + QTest::newRow("Stream could not be connected") << "mms:///donotexits.mp3" << int(WORKER_ERROR_MEDIA_NOT_FOUND) << MafwError::RendererError_URINotAvailable; +} + +void Ut_MafwGstRenderer::testBlankingPreventer() +{ + m_renderer->m_worker->blanking__control_handler(m_renderer->m_worker, m_renderer, TRUE ); + QCOMPARE( global_qmsystemstub_setBlankingPauseCalled, 1 ); + QTest::qWait( 50*1000 ); // >45 secs + QCOMPARE( global_qmsystemstub_setBlankingPauseCalled, 2 ); + global_qmsystemstub_setBlankingPauseCalled = 0; + + m_renderer->m_worker->blanking__control_handler(m_renderer->m_worker, m_renderer, FALSE ); + QTest::qWait( 50*1000 ); // >45 secs + // no more calls arrived + QCOMPARE( global_qmsystemstub_setBlankingPauseCalled, 0 ); +} + +void Ut_MafwGstRenderer::testPolicyPropertyHandler() +{ + m_renderer->setMafwProperty(MafwRenderer::MAFW_RENDERER_PROPERTY_POLICY_OVERRIDE, true); + QVERIFY(global_ready_timeout == 0); + + m_renderer->setMafwProperty(MafwRenderer::MAFW_RENDERER_PROPERTY_POLICY_OVERRIDE, false); + QCOMPARE(global_ready_timeout, (guint)MAFW_GST_RENDERER_WORKER_READY_TIMEOUT); + + m_renderer->setMafwProperty(MafwRenderer::MAFW_RENDERER_PROPERTY_POLICY_OVERRIDE, true); + QVERIFY(global_ready_timeout == 0); + + m_renderer->setMafwProperty(MafwRenderer::MAFW_RENDERER_PROPERTY_POLICY_OVERRIDE, false); + QCOMPARE(global_ready_timeout, (guint)MAFW_GST_RENDERER_WORKER_READY_TIMEOUT); + +} + +void Ut_MafwGstRenderer::testMediaRouting() +{ + + QVERIFY(global_audio_route_property); + QVERIFY(global_video_route_property); + + global_audio_route = "tvout"; + global_video_route = "builtinandtvout"; + m_renderer->m_audioRoute->ignoreCommander(); + m_renderer->m_videoRoute->ignoreCommander(); + QVERIFY(global_worker_destinations); + QVERIFY(g_slist_length(global_worker_destinations) == 3); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_HEADPHONE_JACK))); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_BUILTIN_DISPLAY))); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_TVOUT))); + + global_audio_route = "tvout"; + global_video_route = "tvout"; + m_renderer->m_audioRoute->ignoreCommander(); + m_renderer->m_videoRoute->ignoreCommander(); + QVERIFY(global_worker_destinations); + QVERIFY(g_slist_length(global_worker_destinations) == 2); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_HEADPHONE_JACK))); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_TVOUT))); + + global_audio_route = "null"; + global_video_route = "builtin"; + m_renderer->m_audioRoute->ignoreCommander(); + m_renderer->m_videoRoute->ignoreCommander(); + QVERIFY(global_worker_destinations); + QVERIFY(g_slist_length(global_worker_destinations) == 2); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_NULL))); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_BUILTIN_DISPLAY))); + + global_audio_route = "headset"; + global_video_route = "kjkjkjfg"; + m_renderer->m_audioRoute->ignoreCommander(); + m_renderer->m_videoRoute->ignoreCommander(); + QVERIFY(global_worker_destinations); + QVERIFY(g_slist_length(global_worker_destinations) == 2); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_HEADPHONE_JACK))); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_NULL))); + + MafwMediaInfo content("test_uuid"); + QMap > map; + map[MAFW_METADATA_KEY_URI] = QList() << "http://test.play.uri"; + content.setMetaData(map); + m_renderer->doPlay(content); + QVERIFY(global_worker_playing == TRUE); //should be playing now + global_audio_route = "ihf"; + global_video_route = "builtin"; + m_renderer->m_audioRoute->ignoreCommander(); + QVERIFY(global_worker_destinations); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_BUILTIN_SPEAKERS))); + m_renderer->m_videoRoute->ignoreCommander(); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_BUILTIN_DISPLAY))); + QVERIFY(g_slist_length(global_worker_destinations) == 2); + + global_audio_route = "earpiece"; + global_video_route = "builtinandtvout"; + m_renderer->m_audioRoute->ignoreCommander(); + m_renderer->m_videoRoute->ignoreCommander(); + QVERIFY(global_worker_destinations); + QVERIFY(g_slist_length(global_worker_destinations) == 3); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_BUILTIN_SPEAKERS))); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_BUILTIN_DISPLAY))); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_TVOUT))); + + global_audio_route = "bthsp"; + global_video_route = "kjkjkjfg"; + m_renderer->m_audioRoute->ignoreCommander(); + m_renderer->m_videoRoute->ignoreCommander(); + QVERIFY(global_worker_destinations); + QVERIFY(g_slist_length(global_worker_destinations) == 2); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_BLUETOOTH_AUDIO))); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_NULL))); + + global_audio_route = "bta2dp"; + global_video_route = "builtin"; + m_renderer->m_audioRoute->ignoreCommander(); + m_renderer->m_videoRoute->ignoreCommander(); + QVERIFY(global_worker_destinations); + QVERIFY(g_slist_length(global_worker_destinations) == 2); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_BLUETOOTH_AUDIO))); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_BUILTIN_DISPLAY))); + + global_audio_route = "lkglklkgh"; + global_video_route = "bugvfdd"; + m_renderer->m_audioRoute->ignoreCommander(); + m_renderer->m_videoRoute->ignoreCommander(); + QVERIFY(global_worker_destinations); + QVERIFY(g_slist_length(global_worker_destinations) == 1); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_NULL))); + + global_audio_route = "fmtx"; + global_video_route = "builtinandtvout"; + m_renderer->m_audioRoute->ignoreCommander(); + m_renderer->m_videoRoute->ignoreCommander(); + QVERIFY(global_worker_destinations); + QVERIFY(g_slist_length(global_worker_destinations) == 3); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_FM_RADIO))); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_BUILTIN_DISPLAY))); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_TVOUT))); + + global_audio_route = "ihfandfmtx"; + global_video_route = "builtinandtvout"; + m_renderer->m_audioRoute->ignoreCommander(); + m_renderer->m_videoRoute->ignoreCommander(); + QVERIFY(global_worker_destinations); + QVERIFY(g_slist_length(global_worker_destinations) == 4); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_BUILTIN_SPEAKERS))); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_FM_RADIO))); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_BUILTIN_DISPLAY))); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_TVOUT))); + + global_audio_route = "earpieceandtvout"; + global_video_route = "builtinandtvout"; + m_renderer->m_audioRoute->ignoreCommander(); + m_renderer->m_videoRoute->ignoreCommander(); + QVERIFY(global_worker_destinations); + QVERIFY(g_slist_length(global_worker_destinations) == 3); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_BUILTIN_SPEAKERS))); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_BUILTIN_DISPLAY))); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_TVOUT))); + + global_audio_route = "headphone"; + global_video_route = "builtinandtvout"; + m_renderer->m_audioRoute->ignoreCommander(); + m_renderer->m_videoRoute->ignoreCommander(); + QVERIFY(global_worker_destinations); + QVERIFY(g_slist_length(global_worker_destinations) == 3); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_HEADPHONE_JACK))); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_BUILTIN_DISPLAY))); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_TVOUT))); + + global_audio_route = "ihfandheadset"; + global_video_route = "builtinandtvout"; + m_renderer->m_audioRoute->ignoreCommander(); + m_renderer->m_videoRoute->ignoreCommander(); + QVERIFY(global_worker_destinations); + QVERIFY(g_slist_length(global_worker_destinations) == 4); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_BUILTIN_SPEAKERS))); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_HEADPHONE_JACK))); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_BUILTIN_DISPLAY))); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_TVOUT))); + + global_audio_route = "ihfandheadphone"; + global_video_route = "builtinandtvout"; + m_renderer->m_audioRoute->ignoreCommander(); + m_renderer->m_videoRoute->ignoreCommander(); + QVERIFY(global_worker_destinations); + QVERIFY(g_slist_length(global_worker_destinations) == 4); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_BUILTIN_SPEAKERS))); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_HEADPHONE_JACK))); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_BUILTIN_DISPLAY))); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_TVOUT))); + + global_audio_route = "ihfandbthsp"; + global_video_route = "builtinandtvout"; + m_renderer->m_audioRoute->ignoreCommander(); + m_renderer->m_videoRoute->ignoreCommander(); + QVERIFY(global_worker_destinations); + QVERIFY(g_slist_length(global_worker_destinations) == 4); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_BUILTIN_SPEAKERS))); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_BLUETOOTH_AUDIO))); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_BUILTIN_DISPLAY))); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_TVOUT))); + + global_audio_route = "tvoutandbthsp"; + global_video_route = "builtinandtvout"; + m_renderer->m_audioRoute->ignoreCommander(); + m_renderer->m_videoRoute->ignoreCommander(); + QVERIFY(global_worker_destinations); + QVERIFY(g_slist_length(global_worker_destinations) == 4); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_HEADPHONE_JACK))); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_BLUETOOTH_AUDIO))); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_BUILTIN_DISPLAY))); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_TVOUT))); + + global_audio_route = "tvoutandbta2dp"; + global_video_route = "builtinandtvout"; + m_renderer->m_audioRoute->ignoreCommander(); + m_renderer->m_videoRoute->ignoreCommander(); + QVERIFY(global_worker_destinations); + QVERIFY(g_slist_length(global_worker_destinations) == 4); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_HEADPHONE_JACK))); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_BLUETOOTH_AUDIO))); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_BUILTIN_DISPLAY))); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_TVOUT))); + + // provider dies + global_audio_route = "earpiece"; + global_video_route = "builtin"; + m_renderer->m_audioRoute->ignoreCommander(); + m_renderer->m_videoRoute->ignoreCommander(); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_BUILTIN_SPEAKERS))); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_BUILTIN_DISPLAY))); + QList args; + args << "name" << "old" << ""; + QDBusMessage dbusMsg; + dbusMsg.setArguments(args); + m_renderer->handleContextProviderRemoval( dbusMsg ); + QVERIFY(g_slist_find(global_worker_destinations, GINT_TO_POINTER(WORKER_OUTPUT_NULL))); +} + +void Ut_MafwGstRenderer::testStamping() +{ + // start playing + MafwMediaInfo content("MafwTrackerSource::test_uuid"); + QMap > map; + map[MAFW_METADATA_KEY_URI] = QList() << "file:///home/user/MyDocs/test.play.uri"; + content.setMetaData(map); + + m_renderer->doPlay(content); + + QVERIFY( m_renderer->m_playedStamped==false ); + QTest::qWait(5200); + QVERIFY( m_renderer->m_playedStamped==true ); + m_renderer->doStop(); + + //http uri + MafwMediaInfo content2("MafwTrackerSource::test_uuid2"); + QMap > map2; + map2[MAFW_METADATA_KEY_URI] = QList() << "http://test.play.uri"; + content2.setMetaData(map2); + + m_renderer->doPlay(content2); + + QVERIFY( m_renderer->m_playedStamped==false ); + QTest::qWait(5200); + QVERIFY( m_renderer->m_playedStamped==false ); + m_renderer->doStop(); + + // play uri case (no uuid) + MafwMediaInfo content3; + QMap > map3; + map3[MAFW_METADATA_KEY_URI] = QList() << "file://test.play.uri"; + content3.setMetaData(map3); + + m_renderer->doPlay(content3); + + QVERIFY( m_renderer->m_playedStamped==false ); + QTest::qWait(5200); + QVERIFY( m_renderer->m_playedStamped==true ); + m_renderer->doStop(); +} + +void Ut_MafwGstRenderer::testNetworkChangesWhenPlaying() +{ + MafwMediaInfo content("test_uuid"); + QMap > map; + map[MAFW_METADATA_KEY_URI] = QList() << "http://test.play.uri"; + content.setMetaData(map); + + m_renderer->doPlay(content); + QVERIFY(global_worker_playing); //should be playing now + + NetworkStubHelper::currentCreatedConfigIsValid = true; + QNetworkConfiguration config; + + global_worker_stop_called = 0; + networkStub.emitConfigurationChange(config, + QNetworkConfiguration::Active); + QVERIFY(global_worker_playing); + QCOMPARE(global_worker_uri, "http://test.play.uri"); + QCOMPARE(global_worker_stop_called, 1); + + map[MAFW_METADATA_KEY_URI] = QList() << "file://test.play.uri"; + content.setMetaData(map); + + m_renderer->doPlay(content); + QVERIFY(global_worker_playing); //should be playing now + + //another network config is activated + QNetworkConfiguration config2; + networkStub.emitConfigurationChange(config2, + QNetworkConfiguration::Active); + + QVERIFY(global_worker_playing); //should keep playing as URI is file:// + + m_renderer->doStop(); + QVERIFY(!global_worker_playing); + + //checks that activating new network config stops play when streaming + map[MAFW_METADATA_KEY_URI] = QList() << "http://test.play.uri"; + content.setMetaData(map); + + m_renderer->doPlay(content); + QVERIFY(global_worker_playing); + + QNetworkConfiguration config3; + global_worker_stop_called = 0; + networkStub.emitConfigurationChange(config3, QNetworkConfiguration::Active); + QCOMPARE(global_worker_uri, "http://test.play.uri"); + QCOMPARE(global_worker_stop_called, 1); + QVERIFY(global_worker_playing); + + //checks that deactivating the current configuration stops playback + m_renderer->doPlay(content); + QVERIFY(global_worker_playing); + networkStub.emitConfigurationChange(config3, QNetworkConfiguration::Discovered); + QVERIFY(!global_worker_playing); +} + +void Ut_MafwGstRenderer::testNetworkChangesWithNoPlaylistFileUtil() +{ + delete m_renderer->m_playlistFileUtil; + m_renderer->m_playlistFileUtil = 0; + testNetworkChangesWhenPlaying(); +} + +void Ut_MafwGstRenderer::testNetworkChangesWhenPaused() +{ + MafwMediaInfo content("test_uuid"); + QMap > map; + map[MAFW_METADATA_KEY_URI] = QList() << "http://test.play.uri"; + content.setMetaData(map); + + QSignalSpy pauseSpy(m_renderer, SIGNAL(rendererPaused())); + QSignalSpy stopSpy(m_renderer, SIGNAL(rendererStopped())); + QSignalSpy playSpy(m_renderer, SIGNAL(rendererPlaying(int))); + QSignalSpy resumeSpy(m_renderer, SIGNAL(rendererResumed())); + + m_renderer->doPlay(content); + QVERIFY(global_worker_playing); //should be playing now + + m_renderer->doPause(); + + QCOMPARE(playSpy.size(), 1); + playSpy.clear(); + QCOMPARE(pauseSpy.size(), 1); + pauseSpy.clear(); + QVERIFY(!global_worker_playing); //should be playing now + + global_position = 313; + QNetworkConfiguration config; + networkStub.emitConfigurationChange(config, QNetworkConfiguration::Active); + + QVERIFY(!global_worker_playing); + QCOMPARE(m_renderer->m_playingItem, MafwBasicRenderer::CurrentUri); + + QCOMPARE(m_renderer->m_haltState.m_state, MafwRenderer::Paused); + QCOMPARE(m_renderer->m_haltState.m_position, 313); + QCOMPARE(m_renderer->m_haltState.m_uri, QString("http://test.play.uri")); + + QCOMPARE(stopSpy.size(), 0); + QCOMPARE(pauseSpy.size(), 0); + QCOMPARE(playSpy.size(), 0); + QCOMPARE(resumeSpy.size(), 0); + + global_worker_seek_request = 0; + + m_renderer->doResume(); + + QCOMPARE(stopSpy.size(), 0); + QCOMPARE(pauseSpy.size(), 0); + QCOMPARE(playSpy.size(), 0); + QCOMPARE(resumeSpy.size(), 1); + + QVERIFY(global_worker_playing); + + QCOMPARE(QString(global_worker_uri), QString("http://test.play.uri")); + QCOMPARE(m_renderer->m_playingItem, MafwBasicRenderer::CurrentUri); + QVERIFY(!m_renderer->m_haltState.isSet()); + + QCOMPARE(global_worker_seek_request, 313); +} + +void Ut_MafwGstRenderer::testNetworkChangesWhenPausedAndRendererStopped() +{ + MafwMediaInfo content("test_uuid"); + QMap > map; + map[MAFW_METADATA_KEY_URI] = QList() << "http://test.play.uri"; + content.setMetaData(map); + + QSignalSpy pauseSpy(m_renderer, SIGNAL(rendererPaused())); + QSignalSpy stopSpy(m_renderer, SIGNAL(rendererStopped())); + QSignalSpy playSpy(m_renderer, SIGNAL(rendererPlaying(int))); + + m_renderer->doPlay(content); + QVERIFY(global_worker_playing); //should be playing now + + m_renderer->doPause(); + + QCOMPARE(playSpy.size(), 1); + playSpy.clear(); + QCOMPARE(pauseSpy.size(), 1); + pauseSpy.clear(); + QVERIFY(!global_worker_playing); //should be playing now + + global_position = 313; + QNetworkConfiguration config; + networkStub.emitConfigurationChange(config, QNetworkConfiguration::Active); + + m_renderer->doStop(); + QCOMPARE(stopSpy.size(), 1); + QVERIFY(!m_renderer->m_haltState.isSet()); +} + +void Ut_MafwGstRenderer::testNetworkChangesToInactiveAndPauseResumeRequested() +{ + QFETCH(int, resumeDelay); + MafwGstRendererHaltState::DECAY_TIME = 3; + + MafwMediaInfo content("test_uuid"); + QMap > map; + map[MAFW_METADATA_KEY_URI] = QList() << "http://test.resume-pause.uri"; + content.setMetaData(map); + + QSignalSpy pauseSpy(m_renderer, SIGNAL(rendererPaused())); + QSignalSpy stopSpy(m_renderer, SIGNAL(rendererStopped())); + QSignalSpy playSpy(m_renderer, SIGNAL(rendererPlaying(int))); + + m_renderer->doPlay(content); + QVERIFY(global_worker_playing); //should be playing now + + QCOMPARE(playSpy.size(), 1); + playSpy.clear(); + + global_position = 999; + QNetworkConfiguration config; + networkStub.emitConfigurationChange(config, QNetworkConfiguration::Discovered); + + m_renderer->doPause(); + QCOMPARE(pauseSpy.size(), 1); + pauseSpy.clear(); + + QVERIFY(m_renderer->m_haltState.isSet()); + QCOMPARE(m_renderer->m_haltState.m_state, MafwRenderer::Paused); + QCOMPARE(m_renderer->m_haltState.m_position, 999); + QCOMPARE(m_renderer->m_haltState.m_uri, QString("http://test.resume-pause.uri")); + + global_worker_seek_request = 0; + + QTest::qWait(resumeDelay); + + m_renderer->doResume(); + QCOMPARE(global_worker_seek_request, 999); + QVERIFY(global_worker_playing); //should be playing yeah there's no network but it will try + QVERIFY(!m_renderer->m_haltState.isSet()); +} + +void Ut_MafwGstRenderer::testNetworkChangesToInactiveAndPauseResumeRequested_data() +{ + QTest::addColumn("resumeDelay"); + QTest::newRow("short delay") << 1000; + QTest::newRow("same as decay") << 3000; + QTest::newRow("longer than decay") << 5000; +} + +void Ut_MafwGstRenderer::testNetworkChangesWithPlaylistURI() +{ + QStringList playlistUris = QStringList() << "http://test.uriplaylist.item/1" << "http://test.uriplaylist.item/2"; + stubPlaylistFileUtilityUris.clear(); + stubPlaylistFileUtilityUris = playlistUris; + + MafwMediaInfo content("test_uuid"); + QMap > map; + map[MAFW_METADATA_KEY_URI] = QList() << "http://test.playlist.m3u"; //does not matter + map[MAFW_METADATA_KEY_MIME] = QList() << "audio/x-scpls"; + content.setMetaData(map); + + QSignalSpy pauseSpy(m_renderer, SIGNAL(rendererPaused())); + QSignalSpy stopSpy(m_renderer, SIGNAL(rendererStopped())); + QSignalSpy playSpy(m_renderer, SIGNAL(rendererPlaying(int))); + QSignalSpy resumeSpy(m_renderer, SIGNAL(rendererResumed())); + + m_renderer->doPlay(content); + + //time to parse the playlist + QTest::qWait(75); + + QVERIFY(global_worker_playing); //should be playing now + + QCOMPARE(playSpy.size(), 1); + playSpy.clear(); + + global_position = 007; + global_worker_stop_called = 0; + + NetworkStubHelper::currentCreatedConfigIsValid = false; + QNetworkConfiguration config; + networkStub.emitConfigurationChange(config, QNetworkConfiguration::Discovered); + + QVERIFY(!global_worker_playing); + QCOMPARE(global_worker_stop_called, 1); + QCOMPARE(m_renderer->m_playingItem, MafwBasicRenderer::CurrentUri); + + QCOMPARE(m_renderer->m_haltState.m_state, MafwRenderer::Playing); + QCOMPARE(m_renderer->m_haltState.m_position, 007); + QCOMPARE(m_renderer->m_haltState.m_uri, playlistUris.at(0)); + + QCOMPARE(pauseSpy.size(), 0); + QCOMPARE(stopSpy.size(), 0); + QCOMPARE(playSpy.size(), 0); + QCOMPARE(resumeSpy.size(), 0); + + global_worker_seek_request = 0; + + NetworkStubHelper::currentCreatedConfigIsValid = true; + QNetworkConfiguration configActive; + networkStub.emitConfigurationChange(configActive, QNetworkConfiguration::Active); + + QVERIFY(global_worker_playing); + QCOMPARE(global_worker_seek_request, 007); + + QCOMPARE(stopSpy.size(), 0); + QCOMPARE(pauseSpy.size(), 0); + QCOMPARE(playSpy.size(), 0); + QCOMPARE(resumeSpy.size(), 0); + + QCOMPARE(QString(global_worker_uri), playlistUris.at(0)); + QCOMPARE(m_renderer->m_playingItem, MafwBasicRenderer::CurrentUri); + QVERIFY(!m_renderer->m_haltState.isSet()); + + //Test that next uri is started to play if playing first stops because of an error + GError* error = g_error_new_literal(1, 2, "Playlist uri playback error"); + m_renderer->m_worker->notify_error_handler(m_renderer->m_worker, m_renderer, error); + QTest::qWait(10); + + QVERIFY(global_worker_playing); + QCOMPARE(stopSpy.size(), 0); + QCOMPARE(pauseSpy.size(), 0); + QCOMPARE(playSpy.size(), 0); + QCOMPARE(resumeSpy.size(), 0); + + QCOMPARE(QString(global_worker_uri), playlistUris.at(1)); + + m_renderer->doStop(); + QVERIFY(!global_worker_playing); + QCOMPARE(stopSpy.size(), 1); + + g_error_free(error); +} + +void Ut_MafwGstRenderer::testNetworkChangesHaltStateDecay() +{ + MafwGstRendererHaltState::DECAY_TIME = 4; + + MafwMediaInfo content("test_uuid"); + QMap > map; + map[MAFW_METADATA_KEY_URI] = QList() << "http://test.decay.uri"; + content.setMetaData(map); + + QSignalSpy pauseSpy(m_renderer, SIGNAL(rendererPaused())); + QSignalSpy stopSpy(m_renderer, SIGNAL(rendererStopped())); + QSignalSpy playSpy(m_renderer, SIGNAL(rendererPlaying(int))); + QSignalSpy resumeSpy(m_renderer, SIGNAL(rendererResumed())); + + m_renderer->doPlay(content); + QVERIFY(global_worker_playing); //should be playing now + + QCOMPARE(playSpy.size(), 1); + playSpy.clear(); + + global_position = 313; + NetworkStubHelper::currentCreatedConfigIsValid = false; + QNetworkConfiguration config; + networkStub.emitConfigurationChange(config, QNetworkConfiguration::Discovered); + + QVERIFY(!global_worker_playing); + QCOMPARE(m_renderer->m_playingItem, MafwBasicRenderer::CurrentUri); + + QCOMPARE(m_renderer->m_haltState.m_state, MafwRenderer::Playing); + QCOMPARE(m_renderer->m_haltState.m_position, 313); + QCOMPARE(m_renderer->m_haltState.m_uri, QString("http://test.decay.uri")); + + QCOMPARE(stopSpy.size(), 0); + QCOMPARE(pauseSpy.size(), 0); + QCOMPARE(playSpy.size(), 0); + QCOMPARE(resumeSpy.size(), 0); + + global_worker_stop_called = 0; + QTest::qWait(MafwGstRendererHaltState::DECAY_TIME * 1000 + 1000); + + global_worker_stop_called = 1; + QCOMPARE(stopSpy.size(), 1); + stopSpy.clear(); + QCOMPARE(pauseSpy.size(), 0); + QCOMPARE(playSpy.size(), 0); + QCOMPARE(resumeSpy.size(), 0); + + QVERIFY(!m_renderer->m_haltState.isSet()); + + global_worker_seek_request = 0; + global_worker_stop_called = 0; + NetworkStubHelper::currentCreatedConfigIsValid = true; + QNetworkConfiguration config2; + networkStub.emitConfigurationChange(config2, QNetworkConfiguration::Active); + + QCOMPARE(stopSpy.size(), 0); + QCOMPARE(pauseSpy.size(), 0); + QCOMPARE(playSpy.size(), 0); + QCOMPARE(resumeSpy.size(), 0); + + QVERIFY(!global_worker_playing); + + global_worker_stop_called = 0; + QCOMPARE(m_renderer->m_playingItem, MafwBasicRenderer::UnknownUri); + QVERIFY(!m_renderer->m_haltState.isSet()); + + QCOMPARE(global_worker_seek_request, 0); +} + +void Ut_MafwGstRenderer::testMmc() +{ + QVERIFY(!global_worker_playing); + + MafwGstRenderer *rnd = new MafwGstRenderer("renderer_uuid", "renderer-plugin", "rendere_name"); + m_stubHelper->expect("initialize", true); + QVERIFY(rnd->initialize(0) == true); + + // MMC not mounted at first case + + QSignalSpy errorSpy( rnd, SIGNAL( rendererError(const MafwError&) ) ); + stubMmcMounted = false; + MafwMediaInfo content("test_uuid_xyz"); + QMap > map; + map[MAFW_METADATA_KEY_URI] = QList() << "file:///home/user/MyDocs/rock.mp3"; + content.setMetaData(map); + + rnd->doPlay(content); + QVERIFY(!global_worker_playing); + QCOMPARE( errorSpy.size(), 1 ); + MafwError err=errorSpy[0][0].value(); + QVERIFY( err.code()==MafwError::RendererError_MmcNotAvailable ); + + // MMC unmounted during play + + stubMmcMounted = true; + errorSpy.clear(); + + rnd->doPlay(content); + QVERIFY(global_worker_playing); + + Q_EMIT stubMmcMonitor->preUnmountEvent("pre-unmount"); + QTest::qWait(100); + QVERIFY(!global_worker_playing); + QVERIFY( errorSpy.size()==1 ); + + // Playlist first not available + + stubPlaylistFileUtilityUris.clear(); + stubPlaylistFileUtilityUris << "file:///home/user/MyDocs/rock.mp3"; + stubPlaylistFileUtilityUris << "file:///home/user/MyDocs/rock2.mp3"; + errorSpy.clear(); + stubMmcMounted = false; + + MafwMediaInfo mediaInfo; + QMap testUri; + QMap testMimeType; + testUri.insert(MAFW_METADATA_KEY_URI, "file:///var/tmp/tunein-station.pls"); + testMimeType.insert(MAFW_METADATA_KEY_MIME, "audio/x-scpls"); + mediaInfo.setMetaData(testUri); + mediaInfo.setMetaData(testMimeType); + + rnd->doPlay(mediaInfo); + QTest::qWait(10); + QVERIFY(!global_worker_playing); + QCOMPARE( errorSpy.size(), 1 ); + err=errorSpy[0][0].value(); + QVERIFY( err.code()==MafwError::RendererError_MmcNotAvailable ); + + // Playlist play first, second not available + + errorSpy.clear(); + stubMmcMounted = true; + + rnd->doPlay(mediaInfo); + QTest::qWait(10); + QVERIFY(global_worker_playing); + + stubMmcMounted = false; + // cause next + GError* error = g_error_new_literal(1, 2, "Playlist uri playback error"); + m_renderer->m_worker->notify_error_handler(m_renderer->m_worker, rnd, error); + errorSpy.clear(); + + QTest::qWait(10); + QCOMPARE( errorSpy.size(), 1 ); + err=errorSpy[0][0].value(); + QVERIFY( err.code()==MafwError::RendererError_MmcNotAvailable ); + QVERIFY(!global_worker_playing); + + g_error_free(error); + + delete rnd; +} + +void Ut_MafwGstRenderer::play_result(const MafwError& /*error*/, MafwRenderer& /*rndr*/) +{ + qDebug() << "Ut_MafwGstRenderer::play_result"; + +} + +void Ut_MafwGstRenderer::positionChanged(uint position) +{ + m_position = position; +} + +void Ut_MafwGstRenderer::positionError(const MafwError& error) +{ + m_error = error; +} + +void Ut_MafwGstRenderer::propertyChanged(const QString& name, const QVariant& value) +{ + m_name = name; + m_value = value; +} + +void Ut_MafwGstRenderer::media_info_result(const MafwMediaInfo& mediaInfo) +{ + qDebug() << __PRETTY_FUNCTION__; + qDebug() << "m_name:" << m_name; + + m_media_values.clear(); + + if(m_name == "all") + { + m_media_values = mediaInfo.metaData(MAFW_METADATA_KEY_URI); + } + else + { + m_media_values = mediaInfo.metaData(m_name); + } + + qDebug() << "m_media_values.size():" << m_media_values.size(); + Q_FOREACH(QVariant v, m_media_values) + { + qDebug() << "value:" << v; + } +} + +void gst_init(int *argc, char **argv[]) +{ + Q_UNUSED(argc); + Q_UNUSED(argv); +} + +QTEST_MAIN(Ut_MafwGstRenderer) + +//Volume stub +MafwGstRendererVolume::MafwGstRendererVolume() +{ + qDebug() << "Volume stub ctor"; +} + +void MafwGstRendererVolume::connectToPulseAudio() +{ +} + +MafwGstRendererVolume::~MafwGstRendererVolume() +{ +} + +uint MafwGstRendererVolume::getVolume() +{ + qDebug(__PRETTY_FUNCTION__); + return (uint)1; +} + +bool MafwGstRendererVolume::setVolume (uint value) +{ + Q_UNUSED(value); + return true; +} diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRenderer/Ut_MafwGstRenderer.h b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRenderer/Ut_MafwGstRenderer.h new file mode 100644 index 0000000..f464304 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRenderer/Ut_MafwGstRenderer.h @@ -0,0 +1,102 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#ifndef UT_MAFWGSTRENDERER_H_ +#define UT_MAFWGSTRENDERER_H_ + +#include +#include +#include + +class MafwInternalRegistry; +class MafwGstRenderer; +class MafwRenderer; +class MafwStubHelper; +class MafwRendererPolicyStub; +class MafwMediaInfo; + +Q_DECLARE_METATYPE(QVariant) +Q_DECLARE_METATYPE(MafwError::Code) + +class Ut_MafwGstRenderer: public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + + void testPlayURI(); + void testPlayPlaylistFile(); + void testStop(); + void testPause(); + void testResume(); + void testSeek(); + void testErrorCodeScenarios(); + void testErrorCodeScenarios_data(); + void testNextHint(); + void testPosition(); + void testProperty(); + void testProperty_data(); + void testPlaybackSpeedProperty(); + void testCallbacks(); + void testDefaultConfiguration(); + void testConfiguration(); + void testGetCurrentMediaInfo(); + void testGetCurrentMediaInfo_data(); + void testBlankingPreventer(); + void testPolicyPropertyHandler(); + void testMediaRouting(); + void testStamping(); + void testNetworkChangesWhenPlaying(); + void testNetworkChangesWithNoPlaylistFileUtil(); + void testNetworkChangesWhenPaused(); + void testNetworkChangesWhenPausedAndRendererStopped(); + void testNetworkChangesToInactiveAndPauseResumeRequested(); + void testNetworkChangesToInactiveAndPauseResumeRequested_data(); + void testNetworkChangesHaltStateDecay(); + void testNetworkChangesWithPlaylistURI(); + void testMmc(); + + /** + * @param error The status of the completed request. + * @param rndr Reference to instance of MafwRenderer invoked the callback slot + */ + void play_result(const MafwError& error, MafwRenderer& rndr); + + void positionChanged(uint position); + void positionError(const MafwError& error); + void media_info_result(const MafwMediaInfo& mediaInfo); + +protected Q_SLOTS: + void propertyChanged(const QString& name, const QVariant& value); + +private: + + MafwGstRenderer* m_renderer; + MafwStubHelper* m_stubHelper; + MafwRendererPolicyStub* m_rendererPolicy; + MafwError m_error; + uint m_position; + QVariant m_value; + QString m_name; + QList m_media_values; + int m_metadataCount; + ContextProperty *m_videoRoute; +}; + +#endif /*UT_MAFWGSTRENDERER_H_*/ diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRenderer/ut_MafwGstRenderer.pro b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRenderer/ut_MafwGstRenderer.pro new file mode 100644 index 0000000..e5f2342 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRenderer/ut_MafwGstRenderer.pro @@ -0,0 +1,63 @@ +TEMPLATE = app +TARGET = +INCLUDEPATH += . +QT += network +QT -= gui + +CONFIG = console + +MAFW_GST_INCDIR = ../../inc +UT_COMMONDIR = ../common + +INCLUDEPATH += $$MAFW_GST_INCDIR \ + $$UT_COMMONDIR +DEPENDPATH += . ../../inc ../../src + +CONFIG += qtestlib no_keywords +CONFIG += qt link_pkgconfig debug + +PKGCONFIG += qmafw glib-2.0 gobject-2.0 +PKGCONFIG += contextprovider-1.0 x11 gq-gconf gstreamer-tag-0.10 libpulse-mainloop-glib +PKGCONFIG += qmsystem2 contextsubscriber-1.0 + +LIBS += -lgcov +QMAKE_CXXFLAGS += -fprofile-arcs -ftest-coverage -O0 -Werror $$system(pkg-config --cflags-only-I QtSparql) +QMAKE_CFLAGS += -fprofile-arcs -ftest-coverage -O0 -Werror + +# Input +HEADERS += Ut_MafwGstRenderer.h \ + MafwGstRenderer.h \ + MafwGstRendererVolume.h \ + MafwGstRendererDolby.h \ + MafwBlankingPreventer.h \ + MafwGstRendererNetworkMonitor.h \ + mafw-gst-renderer-worker.h \ + mafw-gst-renderer-utils.h \ + MafwGstRendererPlaylistFileUtility.h \ + $$UT_COMMONDIR/MafwStubHelper.h \ + $$UT_COMMONDIR/MafwRendererPolicyStub.h \ + $$UT_COMMONDIR/QNetworkStubs.h \ + /usr/include/qt4/QtNetwork/qnetworkconfigmanager.h \ + MafwMmcMonitor.h \ + MafwGstScreenshot.h \ + MafwGstRendererHaltState.h + +SOURCES += QmSystemStub.cpp \ + QSettingsStub.cpp \ + Ut_MafwGstRenderer.cpp \ + MafwGstRenderer.cpp \ + MafwGstRendererDolbyStub.cpp \ + MafwBlankingPreventer.cpp \ + MafwGstRendererNetworkMonitor.cpp \ + mafw-gst-renderer-utils.c \ + $$UT_COMMONDIR/MafwStubHelper.cpp \ + $$UT_COMMONDIR/renderer-worker-stub.c \ + $$UT_COMMONDIR/MafwBasicRendererStub.cpp \ + $$UT_COMMONDIR/MafwRendererPolicyStub.cpp \ + $$UT_COMMONDIR/LibCredsStub.cpp \ + $$UT_COMMONDIR/MafwPlaylistFileUtilityStub.cpp \ + ContextFWStub.cpp \ + $$UT_COMMONDIR/QNetworkStubs.cpp \ + MafwMmcMonitorStub.cpp \ + MafwGstScreenshot.cpp \ + MafwGstRendererHaltState.cpp diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererDolby/ut_MafwGstRendererDolby.cpp b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererDolby/ut_MafwGstRendererDolby.cpp new file mode 100644 index 0000000..1038e43 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererDolby/ut_MafwGstRendererDolby.cpp @@ -0,0 +1,120 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ +#include "ut_MafwGstRendererDolby.h" + +#include +#include + +#include "MafwGstRendererDolby.h" + +void Ut_MafwGstRendererDolby::initTestCase() +{ + m_dolby = new MafwGstRendererDolby(this); + m_dolby->initialize(); + delete m_dolby; + m_dolby = new MafwGstRendererDolby(this); + m_dolby->initialize(); +} + +void Ut_MafwGstRendererDolby::cleanupTestCase() +{ + delete m_dolby; +} + +void Ut_MafwGstRendererDolby::testDolby() +{ + m_dolby->setMusicDolbyState(0); + QCOMPARE(m_dolby->getMusicDolbyState(), (uint)0); + m_dolby->setMusicDolbyState(1); + QCOMPARE(m_dolby->getMusicDolbyState(), (uint)1); + m_dolby->setMusicDolbyState(2); + QCOMPARE(m_dolby->getMusicDolbyState(), (uint)2); + m_dolby->setMusicDolbyState(3); + QCOMPARE(m_dolby->getMusicDolbyState(), (uint)0); + + m_dolby->setMusicDolbyRoom(-1); + QCOMPARE(m_dolby->getMusicDolbyRoom(), (int)0); + m_dolby->setMusicDolbyRoom(0); + QCOMPARE(m_dolby->getMusicDolbyRoom(), (int)0); + m_dolby->setMusicDolbyRoom(1); + QCOMPARE(m_dolby->getMusicDolbyRoom(), (int)1); + m_dolby->setMusicDolbyRoom(2); + QCOMPARE(m_dolby->getMusicDolbyRoom(), (int)2); + m_dolby->setMusicDolbyRoom(3); + QCOMPARE(m_dolby->getMusicDolbyRoom(), (int)3); + m_dolby->setMusicDolbyRoom(4); + QCOMPARE(m_dolby->getMusicDolbyRoom(), (int)4); + m_dolby->setMusicDolbyRoom(5); + QCOMPARE(m_dolby->getMusicDolbyRoom(), (int)4); + + m_dolby->setMusicDolbyColor(-1); + QCOMPARE(m_dolby->getMusicDolbyColor(), (int)0); + m_dolby->setMusicDolbyColor(0); + QCOMPARE(m_dolby->getMusicDolbyColor(), (int)0); + m_dolby->setMusicDolbyColor(1); + QCOMPARE(m_dolby->getMusicDolbyColor(), (int)1); + m_dolby->setMusicDolbyColor(2); + QCOMPARE(m_dolby->getMusicDolbyColor(), (int)2); + m_dolby->setMusicDolbyColor(3); + QCOMPARE(m_dolby->getMusicDolbyColor(), (int)3); + m_dolby->setMusicDolbyColor(4); + QCOMPARE(m_dolby->getMusicDolbyColor(), (int)4); + m_dolby->setMusicDolbyColor(5); + QCOMPARE(m_dolby->getMusicDolbyColor(), (int)4); + + m_dolby->setVideoDolbyState(0); + QCOMPARE(m_dolby->getVideoDolbyState(), (uint)0); + m_dolby->setVideoDolbyState(1); + QCOMPARE(m_dolby->getVideoDolbyState(), (uint)1); + m_dolby->setVideoDolbyState(2); + QCOMPARE(m_dolby->getVideoDolbyState(), (uint)2); + m_dolby->setVideoDolbyState(3); + QCOMPARE(m_dolby->getVideoDolbyState(), (uint)0); + + m_dolby->setVideoDolbyRoom(-1); + QCOMPARE(m_dolby->getVideoDolbyRoom(), (int)0); + m_dolby->setVideoDolbyRoom(0); + QCOMPARE(m_dolby->getVideoDolbyRoom(), (int)0); + m_dolby->setVideoDolbyRoom(1); + QCOMPARE(m_dolby->getVideoDolbyRoom(), (int)1); + m_dolby->setVideoDolbyRoom(2); + QCOMPARE(m_dolby->getVideoDolbyRoom(), (int)2); + m_dolby->setVideoDolbyRoom(3); + QCOMPARE(m_dolby->getVideoDolbyRoom(), (int)3); + m_dolby->setVideoDolbyRoom(4); + QCOMPARE(m_dolby->getVideoDolbyRoom(), (int)4); + m_dolby->setVideoDolbyRoom(5); + QCOMPARE(m_dolby->getVideoDolbyRoom(), (int)4); + + m_dolby->setVideoDolbyColor(-1); + QCOMPARE(m_dolby->getVideoDolbyColor(), (int)0); + m_dolby->setVideoDolbyColor(0); + QCOMPARE(m_dolby->getVideoDolbyColor(), (int)0); + m_dolby->setVideoDolbyColor(1); + QCOMPARE(m_dolby->getVideoDolbyColor(), (int)1); + m_dolby->setVideoDolbyColor(2); + QCOMPARE(m_dolby->getVideoDolbyColor(), (int)2); + m_dolby->setVideoDolbyColor(3); + QCOMPARE(m_dolby->getVideoDolbyColor(), (int)3); + m_dolby->setVideoDolbyColor(4); + QCOMPARE(m_dolby->getVideoDolbyColor(), (int)4); + m_dolby->setVideoDolbyColor(5); + QCOMPARE(m_dolby->getVideoDolbyColor(), (int)4); +} + +QTEST_MAIN(Ut_MafwGstRendererDolby) + diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererDolby/ut_MafwGstRendererDolby.h b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererDolby/ut_MafwGstRendererDolby.h new file mode 100644 index 0000000..3071730 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererDolby/ut_MafwGstRendererDolby.h @@ -0,0 +1,39 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#ifndef UT_MAFWGSTRENDERERDOLBY_H_ +#define UT_MAFWGSTRENDERERDOLBY_H_ + +#include + +class MafwGstRendererDolby; + +class Ut_MafwGstRendererDolby: public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void cleanupTestCase(); + void testDolby(); + +private: + + MafwGstRendererDolby* m_dolby; +}; + +#endif /*UT_MAFWGSTRENDERERDOLBY_H_*/ diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererDolby/ut_MafwGstRendererDolby.pro b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererDolby/ut_MafwGstRendererDolby.pro new file mode 100644 index 0000000..b01065c --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererDolby/ut_MafwGstRendererDolby.pro @@ -0,0 +1,33 @@ +TEMPLATE = app +TARGET = +QT -= gui +CONFIG = console +INCLUDEPATH += . +MAFW_GST_INCDIR = ../../inc +UT_COMMONDIR = ../common +INCLUDEPATH += $$MAFW_GST_INCDIR \ + $$UT_COMMONDIR +DEPENDPATH += . \ + ../../inc \ + ../../src +CONFIG += qtestlib \ + qt \ + link_pkgconfig \ + debug +PKGCONFIG += qmafw \ + gq-gconf \ + libpulse-mainloop-glib +LIBS += -lgcov \ + -ldbus-qeventloop +QMAKE_CXXFLAGS += -fprofile-arcs \ + -ftest-coverage \ + -O0 -Werror +QMAKE_CFLAGS += -fprofile-arcs \ + -ftest-coverage \ + -O0 -Werror + +# Input +HEADERS += ut_MafwGstRendererDolby.h \ + MafwGstRendererDolby.h +SOURCES += ut_MafwGstRendererDolby.cpp \ + MafwGstRendererDolby.cpp diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererNetworkMonitor/ut_MafwGstRendererNetworkMonitor.cpp b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererNetworkMonitor/ut_MafwGstRendererNetworkMonitor.cpp new file mode 100644 index 0000000..6686401 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererNetworkMonitor/ut_MafwGstRendererNetworkMonitor.cpp @@ -0,0 +1,90 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ +#include "ut_MafwGstRendererNetworkMonitor.h" +#include "MafwGstRendererNetworkMonitor.h" +#include "QNetworkStubs.h" + +#include +#include + +extern NetworkStubHelper networkStub; + +void Ut_MafwGstRendererNetworkMonitor::initTestCase() +{ + +} + +void Ut_MafwGstRendererNetworkMonitor::cleanupTestCase() +{ + +} + +void Ut_MafwGstRendererNetworkMonitor::testPrepareNetworkChangeSignal() +{ + MafwGstRendererNetworkMonitor monitor; + QNetworkConfiguration config; + + QSignalSpy networkChangeFinished(&monitor, SIGNAL(networkChangeFinished())); + + networkStub.emitConfigurationChange(config, QNetworkConfiguration::Active); + + QCOMPARE(networkChangeFinished.size(), 1); +} + +void Ut_MafwGstRendererNetworkMonitor::testOfflineSignal() +{ + networkStub.currentCreatedConfigIsValid = false; + MafwGstRendererNetworkMonitor monitor; + networkStub.currentCreatedConfigIsValid = true; + + QSignalSpy prepareNetworkChangeSpy(&monitor, SIGNAL(prepareNetworkChange())); + + QNetworkConfiguration config; + networkStub.emitConfigurationChange(config, QNetworkConfiguration::Discovered); + + QCOMPARE(prepareNetworkChangeSpy.size(), 1); + + //first activate + networkStub.emitConfigurationChange(config, QNetworkConfiguration::Active); + //then set it not active + networkStub.emitConfigurationChange(config, QNetworkConfiguration::Discovered); + + QCOMPARE(prepareNetworkChangeSpy.size(), 2); + +} + +void Ut_MafwGstRendererNetworkMonitor::testConfigChanges() +{ + MafwGstRendererNetworkMonitor monitor; + QSignalSpy networkChangeSpy(&monitor, SIGNAL(prepareNetworkChange())); + QSignalSpy networkChangeFinished(&monitor, SIGNAL(networkChangeFinished())); + + QNetworkConfiguration config; + networkStub.emitConfigurationChange(config, QNetworkConfiguration::Active); + QNetworkConfiguration config2; + networkStub.emitConfigurationChange(config2, QNetworkConfiguration::Active); + networkStub.emitConfigurationChange(config, QNetworkConfiguration::Discovered); + + QCOMPARE(networkChangeFinished.size(), 2); + QCOMPARE(networkChangeSpy.size(), 0); + + networkStub.emitConfigurationChange(config2, QNetworkConfiguration::Discovered); + QCOMPARE(networkChangeSpy.size(), 1); +} + +QTEST_MAIN(Ut_MafwGstRendererNetworkMonitor) + diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererNetworkMonitor/ut_MafwGstRendererNetworkMonitor.h b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererNetworkMonitor/ut_MafwGstRendererNetworkMonitor.h new file mode 100644 index 0000000..86dabaa --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererNetworkMonitor/ut_MafwGstRendererNetworkMonitor.h @@ -0,0 +1,37 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#ifndef UT_MAFWGSTRENDERERNETWORKMONITOR_H_ +#define UT_MAFWGSTRENDERERNETWORKMONITOR_H_ + +#include + + +class Ut_MafwGstRendererNetworkMonitor: public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void cleanupTestCase(); + + void testPrepareNetworkChangeSignal(); + void testOfflineSignal(); + void testConfigChanges(); +}; + +#endif /*UT_MAFWGSTRENDERERNETWORKMONITOR_H_*/ diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererNetworkMonitor/ut_MafwGstRendererNetworkMonitor.pro b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererNetworkMonitor/ut_MafwGstRendererNetworkMonitor.pro new file mode 100644 index 0000000..4204b01 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererNetworkMonitor/ut_MafwGstRendererNetworkMonitor.pro @@ -0,0 +1,28 @@ +TEMPLATE = app +TARGET = +CONFIG = console +QT -= gui + +INCLUDEPATH += . +MAFW_GST_INCDIR = ../../inc +UT_COMMONDIR = ../common + +INCLUDEPATH += $$MAFW_GST_INCDIR $$UT_COMMONDIR +DEPENDPATH += . ../../inc ../../src + +CONFIG += qtestlib qt link_pkgconfig debug +PKGCONFIG += qmafw +LIBS += -lgcov + +QMAKE_CXXFLAGS +=-fprofile-arcs -ftest-coverage -O0 -Wall -Werror +QMAKE_CFLAGS +=-fprofile-arcs -ftest-coverage -O0 -Wall -Werror + +# Input +HEADERS += ut_MafwGstRendererNetworkMonitor.h \ + MafwGstRendererNetworkMonitor.h \ + /usr/include/qt4/QtNetwork/qnetworkconfigmanager.h \ + $$UT_COMMONDIR/QNetworkStubs.h + +SOURCES += ut_MafwGstRendererNetworkMonitor.cpp \ + MafwGstRendererNetworkMonitor.cpp \ + $$UT_COMMONDIR/QNetworkStubs.cpp diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererPlugin/Ut_MafwGstRendererPlugin.cpp b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererPlugin/Ut_MafwGstRendererPlugin.cpp new file mode 100644 index 0000000..f6d7174 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererPlugin/Ut_MafwGstRendererPlugin.cpp @@ -0,0 +1,285 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#include +#include +#include +#include +#include +#include + +#include "Ut_MafwGstRendererPlugin.h" +#include "MafwGstRendererPlugin.h" +#include "MafwGstRenderer.h" +#include "MafwGstRendererVolume.h" +#include "MafwGstRendererDolby.h" +#include "../common/MafwStubHelper.h" + +Q_DECLARE_METATYPE(MafwRenderer*) + +extern void setStubHelper(MafwStubHelper* stubHlp); +QMap renderers; +int renderer_index = 0; + +void Ut_MafwGstRendererPlugin::initTestCase() +{ + qRegisterMetaType(); + m_stubHelper = new MafwStubHelper; + setStubHelper(m_stubHelper); + + m_plugin = new MafwGstRendererPlugin(); +} + +void Ut_MafwGstRendererPlugin::testInProcess() +{ + QSignalSpy addedSpy( MafwRegistry::instance(), SIGNAL(rendererAdded(const QString&, const QString&)) ); + QSignalSpy removedSpy( MafwRegistry::instance(), SIGNAL(rendererRemoved(const QString&, const QString&)) ); + //MafwBasicRenderer::initialize is called twice: + //from its own constructor and from MafwGstRenderer::initialize + + //Basic renderer initialize fails + m_stubHelper->expect("initialize", false); + m_stubHelper->expect("initialize", false); + m_plugin->initialize( MafwInternalRegistry::internalInstance() ); + QCOMPARE( addedSpy.size(), 0 ); + addedSpy.clear(); + removedSpy.clear(); + + m_stubHelper->expect("initialize", true); + m_stubHelper->expect("initialize", true); + m_plugin->initialize( MafwInternalRegistry::internalInstance() ); + QCOMPARE( addedSpy.size(), 1 ); + QCOMPARE( removedSpy.size(), 0 ); + QVariantList arguments = addedSpy.takeFirst(); + + QString uuid = arguments.at(0).toString(); + QCOMPARE(uuid, QString("mafw_gst_renderer")); + QCOMPARE(arguments.at(1).toString(), QString("QMAFW GStreamer Renderer")); + + MafwRenderer* rnd = MafwRegistry::instance()->renderer(uuid); + QVERIFY(rnd != 0); + QCOMPARE(rnd->pluginName(), m_plugin->name()); + + MafwInternalRegistry::internalInstance()->removeExtension(uuid); + QCOMPARE( removedSpy.size(), 1 ); +} + +void Ut_MafwGstRendererPlugin::testOutProcess() +{ + QCoreApplication::setApplicationName("qmafw-dbus-wrapper"); + //TODO empty settings case + + renderers.insert("mafw-gst-renderer", "Mafw-GStreamer-Renderer"); + renderers.insert("mafw-gst-video-suite-renderer", "Mafw-GStreamer-Renderer-For-Video"); + + QSignalSpy addedSpy( MafwRegistry::instance(), SIGNAL(rendererAdded(const QString&, const QString&)) ); + QSignalSpy removedSpy( MafwRegistry::instance(), SIGNAL(rendererRemoved(const QString&, const QString&)) ); + //MafwBasicRenderer::initialize is called twice: + //from its own constructor and from MafwGstRenderer::initialize + + //Basic renderer initialize fails + m_stubHelper->expect("initialize", false); + m_stubHelper->expect("initialize", false); + m_stubHelper->expect("initialize", false); + m_stubHelper->expect("initialize", false); + m_plugin->initialize( MafwInternalRegistry::internalInstance() ); + QCOMPARE( addedSpy.size(), 0 ); + addedSpy.clear(); + removedSpy.clear(); + + m_stubHelper->expect("initialize", true); + m_stubHelper->expect("initialize", true); + m_stubHelper->expect("initialize", true); + m_stubHelper->expect("initialize", true); + m_plugin->initialize( MafwInternalRegistry::internalInstance() ); + QCOMPARE( addedSpy.size(), 2 ); + QCOMPARE( removedSpy.size(), 0 ); + + QVariantList arguments = addedSpy.takeFirst(); + QString uuid1 = arguments.at(0).toString(); + QString name1 = arguments.at(1).toString(); + arguments = addedSpy.takeFirst(); + QString uuid2 = arguments.at(0).toString(); + QString name2 = arguments.at(1).toString(); + + MafwRenderer* rnd1 = MafwRegistry::instance()->renderer(uuid1); + QVERIFY(rnd1 != 0); + QCOMPARE(rnd1->pluginName(), m_plugin->name()); + MafwRenderer* rnd2 = MafwRegistry::instance()->renderer(uuid2); + QVERIFY(rnd2 != 0); + QCOMPARE(rnd2->pluginName(), m_plugin->name()); + + QCOMPARE(uuid1, rnd1->uuid()); + QCOMPARE(name1, rnd1->name()); + QCOMPARE(uuid2, rnd2->uuid()); + QCOMPARE(name2, rnd2->name()); + + MafwInternalRegistry::internalInstance()->removeExtension(uuid1); + MafwInternalRegistry::internalInstance()->removeExtension(uuid2); + + // Test huge amount of renderers + renderers.clear(); + addedSpy.clear(); + removedSpy.clear(); + for(int i = 0; i < 100; i++) + { + QString rndId = QString("rndId %1").arg(i); + QString rndName = QString("rndName %1").arg(i); + renderers.insert(rndId, rndName); + m_stubHelper->expect("initialize", true); + m_stubHelper->expect("initialize", true); + } + m_plugin->initialize( MafwInternalRegistry::internalInstance()); + QCOMPARE(removedSpy.size(), 0); + QCOMPARE(addedSpy.size(), 100); + + // Let's only remove 99 renderers + for(int i = 0; i < 99; i++) + { + QString rndId = QString("rndId %1").arg(i); + MafwInternalRegistry::internalInstance()->removeExtension(rndId); + } + QCOMPARE(removedSpy.size(), 99); + // Check that there is still one renderer left + MafwRenderer* rnd99 = MafwRegistry::instance()->renderer("rndId 99"); + QVERIFY(rnd99 != 0); + MafwInternalRegistry::internalInstance()->removeExtension("rndId 99"); + + addedSpy.clear(); + removedSpy.clear(); + + //Test no renderers + renderers.clear(); + m_plugin->initialize(MafwInternalRegistry::internalInstance()); + QCOMPARE(addedSpy.size(), 0); + + //Test 2 renderers with same uuid and name + renderers.insert("rndId1", "rndName1"); + renderers.insert("rndId1", "rndName1"); + m_stubHelper->expect("initialize", true); + m_stubHelper->expect("initialize", true); + m_stubHelper->expect("initialize", false); + m_stubHelper->expect("initialize", false); + m_plugin->initialize( MafwInternalRegistry::internalInstance()); + QCOMPARE(addedSpy.size(), 1); + +} + +void Ut_MafwGstRendererPlugin::cleanupTestCase() +{ + delete m_plugin; + while(QCoreApplication::hasPendingEvents()) + { + QCoreApplication::sendPostedEvents(); + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); + QCoreApplication::processEvents(); + } +} + +void gst_init(int *argc, char **argv[]) +{ + Q_UNUSED(argc); + Q_UNUSED(argv); +} + +QTEST_MAIN(Ut_MafwGstRendererPlugin) + +//Volume stub +MafwGstRendererVolume::MafwGstRendererVolume() +{ + qDebug() << "Volume stub ctor"; +} + +void MafwGstRendererVolume::connectToPulseAudio() +{ +} + +MafwGstRendererVolume::~MafwGstRendererVolume() +{ +} + +uint MafwGstRendererVolume::getVolume() +{ + qDebug(__PRETTY_FUNCTION__); + return (uint)1; +} + +bool MafwGstRendererVolume::setVolume (uint value) +{ + Q_UNUSED(value); + return true; +} + +//QSettings stub +QSettings::QSettings( const QString&, const QString&, QObject* ){} +//Hope we will never need below ctor stub, because it is used somehow by unit test framework. +//QSettings::QSettings(QSettings::Scope, const QString&, const QString&, QObject*){} +QSettings::QSettings(QSettings::Format, QSettings::Scope, const QString&, const QString&, QObject*){} +QSettings::QSettings(const QString&, QSettings::Format, QObject*){} +QSettings::QSettings(QObject*){} +QSettings::~QSettings(){} + +QVariant QSettings::value(const QString& key, const QVariant& ) const +{ + if(renderers.size() == 0) + { + return QString(); + } + QMap::const_iterator renderers_iterator = renderers.constBegin(); + for(int i = 0; i < renderer_index; i++) + { + renderers_iterator++; + } + + if(key == "Id") + { + return renderers_iterator.key(); + } + else + { + return renderers_iterator.value(); + } +} + +int QSettings::beginReadArray(const QString&) +{ + return renderers.count(); +} + +void QSettings::setArrayIndex(int i) +{ + renderer_index = i; +} + +void QSettings::endArray() +{ +} + +void QSettings::beginGroup(const QString &prefix) +{ + Q_UNUSED(prefix); +} + +void QSettings::endGroup() +{ + +} + +bool QSettings::contains(const QString &key) const +{ + return true; +} diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererPlugin/Ut_MafwGstRendererPlugin.h b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererPlugin/Ut_MafwGstRendererPlugin.h new file mode 100644 index 0000000..3a04214 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererPlugin/Ut_MafwGstRendererPlugin.h @@ -0,0 +1,45 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#ifndef UT_MAFWGSTRENDERERPLUGIN_H +#define UT_MAFWGSTRENDERERPLUGIN_H + +#include + +class MafwBasicRenderer; +class MafwGstRendererPlugin; +class MafwRenderer; +class MafwStubHelper; + +class Ut_MafwGstRendererPlugin: public QObject +{ + Q_OBJECT + +private Q_SLOTS: // tests + + void initTestCase(); + void testInProcess(); + void testOutProcess(); + void cleanupTestCase(); + +private: + MafwGstRendererPlugin* m_plugin; + MafwStubHelper* m_stubHelper; + +}; + +#endif // UT_MAFWGSTRENDERERPLUGIN_H diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererPlugin/ut_MafwGstRendererPlugin.pro b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererPlugin/ut_MafwGstRendererPlugin.pro new file mode 100644 index 0000000..b736c3e --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererPlugin/ut_MafwGstRendererPlugin.pro @@ -0,0 +1,57 @@ +###################################################################### +# Automatically generated by qmake (2.01a) Thu Nov 19 11:32:17 2009 +###################################################################### + +TEMPLATE = app +QT += network +QT -=gui + +CONFIG = console + +TARGET = +MAFW_GST_INCDIR = ../../inc +DEPENDPATH += . $$MAFW_GST_INCDIR ../../src +INCLUDEPATH += . $$MAFW_GST_INCDIR /usr/include/qmafw + +CONFIG += qtestlib no_keywords +CONFIG += qt link_pkgconfig debug +PKGCONFIG += qmafw glib-2.0 gobject-2.0 gio-2.0 gio-unix-2.0 usb_moded QtSparql +PKGCONFIG += contextprovider-1.0 x11 gstreamer-tag-0.10 gq-gconf libpulse-mainloop-glib qmsystem2 contextsubscriber-1.0 +LIBS += -lgcov + +QMAKE_CXXFLAGS +=-fprofile-arcs -ftest-coverage -O0 -Werror +QMAKE_CFLAGS +=-fprofile-arcs -ftest-coverage -O0 -Werror + +# Input +HEADERS += Ut_MafwGstRendererPlugin.h \ + MafwGstRendererPlugin.h \ + MafwGstRenderer.h \ + MafwGstRendererVolume.h \ + MafwGstRendererDolby.h \ + MafwBlankingPreventer.h \ + MafwGstRendererNetworkMonitor.h \ + MafwGstRendererPlaylistFileUtility.h \ + MafwMmcMonitor.h \ + MafwGstScreenshot.h \ + MafwGstRendererHaltState.h \ + mafw-gst-renderer-worker.h \ + mafw-gst-renderer-utils.h \ + ../common/MafwStubHelper.h \ + ../common/MafwRendererPolicyStub.h + +SOURCES += Ut_MafwGstRendererPlugin.cpp \ + MafwGstRendererPlugin.cpp \ + MafwGstRenderer.cpp \ + MafwGstRendererDolby.cpp \ + MafwGstRendererNetworkMonitor.cpp \ + MafwBlankingPreventer.cpp \ + MafwMmcMonitor.cpp \ + MafwGstScreenshot.cpp \ + MafwGstRendererHaltState.cpp \ + mafw-gst-renderer-utils.c \ + ../common/renderer-worker-stub.c \ + ../common/MafwStubHelper.cpp \ + ../common/MafwBasicRendererStub.cpp \ + ../common/MafwRendererPolicyStub.cpp \ + ../common/MafwPlaylistFileUtilityStub.cpp + diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererSeeker/ut_MafwGstRendererSeeker.cpp b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererSeeker/ut_MafwGstRendererSeeker.cpp new file mode 100644 index 0000000..8d34d64 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererSeeker/ut_MafwGstRendererSeeker.cpp @@ -0,0 +1,196 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#include "ut_MafwGstRendererSeeker.h" + +#include + +Q_DECLARE_METATYPE(QList); + +extern gint64 g_currentPosition; +extern gint64 g_duration; +extern gint64 g_seekRequested; +extern gint g_seeksCalled; + +void Ut_MafwGstRendererSeeker::initTestCase() +{ + g_type_init(); +} + +void Ut_MafwGstRendererSeeker::cleanupTestCase() +{ +} + +void Ut_MafwGstRendererSeeker::init() +{ + m_seeker = mafw_gst_renderer_seeker_new(); + mafw_gst_renderer_seeker_set_pipeline(m_seeker, (GstElement*)(313)); +} + +void Ut_MafwGstRendererSeeker::cleanup() +{ + mafw_gst_renderer_seeker_free(m_seeker); + + g_currentPosition = -1; + g_duration = -1; + g_seekRequested = -1; + g_seeksCalled = 0; +} + + + +void Ut_MafwGstRendererSeeker::testSeekSuccess() +{ + QFETCH(qint64, startPosition); + QFETCH(qint64, seekTo); + QFETCH(qint64, seekGoesTo); + QFETCH(qint64, mediaDuration); + + g_duration = mediaDuration; + g_currentPosition = startPosition; + + mafw_gst_renderer_seeker_seek_to(m_seeker, seekTo); + QCOMPARE(g_seekRequested, seekTo); + + g_currentPosition = seekGoesTo; + + gint64 newSeekPos = mafw_gst_renderer_seeker_process(m_seeker); + + // only one seek operation should be called when successful + QCOMPARE(g_seeksCalled, 1); + QCOMPARE(newSeekPos, -1ll); +} + +void Ut_MafwGstRendererSeeker::testSeekSuccess_data() +{ + QTest::addColumn("startPosition"); + QTest::addColumn("seekTo"); + QTest::addColumn("seekGoesTo"); + QTest::addColumn("mediaDuration"); + + QTest::newRow("1") << 0ll << 20ll << 21ll << 30ll; + QTest::newRow("1a") << 0ll << 19ll << 21ll << 30ll; + QTest::newRow("2") << 5ll << 1ll << 2ll << 10ll; + QTest::newRow("3") << 100ll << 150ll << 119ll << 120ll; + + //intentionally messed values + QTest::newRow("weird1") << 4ll << 1ll << 1ll << 2ll; + QTest::newRow("weird2") << -13ll << 13ll << 12ll << 0ll; + QTest::newRow("weird2") << 10ll << 5ll << 4ll << 1ll; +} + + +void Ut_MafwGstRendererSeeker::testSeekFails() +{ + QFETCH(qint64, startPosition); + QFETCH(qint64, seekTo); + QFETCH(QList, processedSeekRequests); + QFETCH(QList, seekGoesTo); + QFETCH(qint64, mediaDuration); + + g_duration = mediaDuration; + g_currentPosition = startPosition; + + mafw_gst_renderer_seeker_seek_to(m_seeker, seekTo); + QCOMPARE(g_seekRequested, seekTo); + + qint64 newSeekPos = 0; + int i = 0; + + while( true ) + { + g_currentPosition = seekGoesTo.at(i); + newSeekPos = mafw_gst_renderer_seeker_process(m_seeker); + + if( newSeekPos == -1 ) + { + break; + } + + QCOMPARE(newSeekPos, processedSeekRequests.at(i)); + QCOMPARE(g_seekRequested, processedSeekRequests.at(i)); + ++i; + } + + qint64 lastSeekPos = mafw_gst_renderer_seeker_process(m_seeker); + QCOMPARE( lastSeekPos, -1ll); + QCOMPARE( g_seeksCalled, seekGoesTo.size()); +} + +void Ut_MafwGstRendererSeeker::testSeekFails_data() +{ + QTest::addColumn("startPosition"); + QTest::addColumn("seekTo"); + QTest::addColumn >("processedSeekRequests"); + QTest::addColumn >("seekGoesTo"); + QTest::addColumn("mediaDuration"); + + QTest::newRow("Second seek is close enough") + << 0ll // position where seeking start + << 20ll // the seek request + << (QList() << 30 ) // list of processed seek request affected by the seekGoesTo list + << (QList() << 1 << 31 ) // list of resulting seeks to gst_element_seek() + << 35ll; //affects when seek processing should stop + + QTest::newRow("Fourth seek forward required") + << 5ll // position where seeking start + << 20ll // the seek request + << (QList() << 30 << 40 << 50 ) // list of processed seek request affected by the seekGoesTo list + << (QList() << 1 << 2 << 6 << 15 ) // list of resulting positions after calling gst_element_seek() + << 70ll; //affects when seek processing should stop + + // backward seeks assume that key_frame seeks will always seek at least to the requested position most likely earlier + // such backward seeks that would not reach at least the required position succeed anyway + QTest::newRow("backward seek fails") + << 20ll // position where seeking start + << 10ll // the seek request + << (QList() ) // list of processed seek request affected by the seekGoesTo list + << (QList() << 0 ) // list of resulting positions after calling gst_element_seek() + << 70ll; //affects when seek processing should stop + + QTest::newRow("backward seek fails 2") + << 20ll // position where seeking start + << 10ll // the seek request + << (QList() ) // list of processed seek request affected by the seekGoesTo list + << (QList() << 15 ) // list of resulting positions after calling gst_element_seek() + << 70ll; //affects when seek processing should stop + + QTest::newRow("Seek over media duration cancelled when processing") + << 5ll // position where seeking start + << 20ll // the seek request + << (QList()) // list of processed seek request affected by the seekGoesTo list + << (QList() << 5 ) // list of resulting positions after calling gst_element_seek() + << 25ll; //affects when seek processing should stop + + QTest::newRow("Test accuracy, first fails just by an inch") + << 5ll // position where seeking start + << 20ll // the seek request + << (QList() << 30) // list of processed seek request affected by the seekGoesTo list + << (QList() << 6 << 7) // list of resulting positions after calling gst_element_seek() + << 35ll; //affects when seek processing should stop + + QTest::newRow("Test accuracy 1, first fails just by an inch") + << 5ll // position where seeking start + << 20ll // the seek request + << (QList() << 30) // list of processed seek request affected by the seekGoesTo list + << (QList() << 6 << 7) // list of resulting seeks to gst_element_seek() + << 35ll; //affects when seek processing should stop + +} + + +QTEST_MAIN(Ut_MafwGstRendererSeeker) diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererSeeker/ut_MafwGstRendererSeeker.h b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererSeeker/ut_MafwGstRendererSeeker.h new file mode 100644 index 0000000..93eb2c8 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererSeeker/ut_MafwGstRendererSeeker.h @@ -0,0 +1,45 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#ifndef UT_MAFWGSTRENDERERSEEKER_H_ +#define UT_MAFWGSTRENDERERSEEKER_H_ + +#include + +#include "mafw-gst-renderer-seeker.h" + +class Ut_MafwGstRendererSeeker : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + void testSeekSuccess(); + void testSeekSuccess_data(); + void testSeekFails(); + void testSeekFails_data(); + + +private: + MafwGstRendererSeeker *m_seeker; +}; + +#endif /*UT_MAFWGSTRENDERERSEEKER_H_*/ diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererSeeker/ut_MafwGstRendererSeeker.pro b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererSeeker/ut_MafwGstRendererSeeker.pro new file mode 100644 index 0000000..f56967a --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererSeeker/ut_MafwGstRendererSeeker.pro @@ -0,0 +1,28 @@ +TEMPLATE = app +QT -= gui +CONFIG = console + +isEmpty(PREFIX) { + PREFIX=/usr +} + +CONFIG += qt link_pkgconfig debug +CONFIG += qtestlib +PKGCONFIG += glib-2.0 gobject-2.0 + +LIBS += -lgcov + +DEPENDPATH += . ../../src ../../inc +INCLUDEPATH += . ../../inc $$system(pkg-config --variable=includedir gstreamer-0.10) + +QMAKE_CXXFLAGS += -fprofile-arcs -ftest-coverage -Wall -Werror -g $$system(pkg-config --cflags gstreamer-0.10) +QMAKE_CFLAGS += -fprofile-arcs -ftest-coverage -Wall -Werror -g $$system(pkg-config --cflags gstreamer-0.10) + +QMAKE_CLEAN += *.gcda *.gcno *.gcov ut_MafwGstRendererSeeker *.vg.xml vgcore.* + +HEADERS += mafw-gst-renderer-seeker.h \ + ut_MafwGstRendererSeeker.h + +SOURCES += mafw-gst-renderer-seeker.c \ + ut_MafwGstRendererSeeker_stubs.c \ + ut_MafwGstRendererSeeker.cpp diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererSeeker/ut_MafwGstRendererSeeker_stubs.c b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererSeeker/ut_MafwGstRendererSeeker_stubs.c new file mode 100644 index 0000000..04d3cde --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererSeeker/ut_MafwGstRendererSeeker_stubs.c @@ -0,0 +1,55 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#include + +#include + +gint64 g_currentPosition; +gint64 g_duration; +gint64 g_seekRequested; +gint g_seeksCalled; + +gboolean gst_element_query_position(GstElement *element, + GstFormat *format, + gint64 *value) +{ + *value = g_currentPosition * GST_SECOND; + return TRUE; +} + +gboolean gst_element_query_duration(GstElement *element, + GstFormat *format, + gint64 *value) +{ + *value = g_duration * GST_SECOND; + return TRUE; +} + +gboolean gst_element_seek(GstElement *element, + gdouble rate, + GstFormat format, + GstSeekFlags flags, + GstSeekType cur_type, + gint64 cur, + GstSeekType stop_type, + gint64 stop) +{ + g_seekRequested = cur / GST_SECOND; + ++g_seeksCalled; + return TRUE; +} diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererVolume/MafwVolumeStubs.cpp b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererVolume/MafwVolumeStubs.cpp new file mode 100644 index 0000000..6715ea9 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererVolume/MafwVolumeStubs.cpp @@ -0,0 +1,271 @@ +#include +#include +#include +#include +#include "MafwStubHelper.h" + +char obj_path[] = "/org/pulseaudio/stream_restore1/entry7"; + +MafwStubHelper* m_stubHelper = 0; +void* m_user_data; +DBusHandleMessageFunction m_signalCb; +DBusPendingCallNotifyFunction m_restoreObjectCb = 0; +DBusPendingCallNotifyFunction m_volumeCb = 0; +DBusMessage* m_getVolumeMethodCall = 0; +DBusMessage* m_setVolumeMethod = 0; + +QMap m_volumes; + +void setStubHelper(MafwStubHelper* stubHlp) +{ + m_stubHelper = stubHlp; +} + +dbus_bool_t dbus_error_is_set(const DBusError* error) +{ + Q_UNUSED(error); + return m_stubHelper->getReturn("dbus_error_is_set").toBool(); +} + +struct DBusConnection +{ + int aField; +}; + +DBusConnection* dbus_connection_open(const char *address, DBusError *error) +{ + DBusConnection* conn = 0; + + if (m_stubHelper->getReturn("dbus_connection_open").toBool()) + { + conn = static_cast( malloc(sizeof(DBusConnection)) ); + } + Q_UNUSED(address); + Q_UNUSED(error); + return conn; +} + +void dbus_connection_unref(DBusConnection* conn) +{ + if (conn) + { + free(conn); + } +} + +bool DBUSConnectionEventLoop::addConnection(DBusConnection* conn) +{ + Q_UNUSED(conn); + + return m_stubHelper->getReturn("addConnection").toBool(); +} + +void DBUSConnectionEventLoop::removeConnection(DBusConnection* conn) +{ + Q_UNUSED(conn); + + m_stubHelper->getReturn("removeConnection"); +} + +dbus_bool_t dbus_connection_add_filter(DBusConnection* connection, + DBusHandleMessageFunction function, + void* user_data, + DBusFreeFunction free_data_function) +{ + Q_UNUSED(connection); + Q_UNUSED(free_data_function); + m_signalCb = function; + m_user_data = user_data; + return true; +} + +void dbus_pending_call_cancel(DBusPendingCall* pending) +{ + Q_UNUSED(pending); +} + +dbus_bool_t dbus_set_error_from_message(DBusError* error, DBusMessage* message) +{ + Q_UNUSED(error); + Q_UNUSED(message); + return m_stubHelper->getReturn("dbus_set_error_from_message").toBool(); +} + +void signalVolumeUpdated(uint channel, uint value) +{ + DBusMessage* message = dbus_message_new_signal(obj_path, "org.PulseAudio.Ext.StreamRestore1.RestoreEntry", "VolumeUpdated"); + + DBusMessageIter argument_iterator, variant_iterator, struct_iterator; + dbus_message_iter_init_append (message, &argument_iterator); + dbus_message_iter_open_container (&argument_iterator, + DBUS_TYPE_VARIANT, + "(uu)", + &variant_iterator); + dbus_message_iter_open_container (&variant_iterator, + DBUS_TYPE_STRUCT, + NULL, + &struct_iterator); + + dbus_message_iter_append_basic (&struct_iterator, DBUS_TYPE_UINT32, &channel); + dbus_message_iter_append_basic (&struct_iterator, DBUS_TYPE_UINT32, &value); + + dbus_message_iter_close_container (&variant_iterator, &struct_iterator); + dbus_message_iter_close_container (&argument_iterator, &variant_iterator); + + DBusConnection* conn = 0; + (*m_signalCb)(conn, message, m_user_data); + dbus_message_unref(message); +} + +struct DBusPendingCall +{ + uint a; +}; + +dbus_bool_t dbus_connection_send_with_reply(DBusConnection* connection, + DBusMessage* message, + DBusPendingCall ** pending_return, + int timeout_milliseconds) +{ + DBusPendingCall pending; + *pending_return = &pending; + Q_UNUSED(connection); + m_getVolumeMethodCall = message; + Q_UNUSED(pending_return); + Q_UNUSED(timeout_milliseconds); + return true; +} + +// Saves callback function pointer. Assumes that first call gives callback for +// GetEntryByName and the second one gives callback for Get Volume +dbus_bool_t dbus_pending_call_set_notify( DBusPendingCall* pending, + DBusPendingCallNotifyFunction function, + void* user_data, + DBusFreeFunction free_user_data) +{ + if (!m_restoreObjectCb) + { + m_restoreObjectCb = function; + } + else + { + m_volumeCb = function; + } + + Q_UNUSED(pending); + Q_UNUSED(user_data); + Q_UNUSED(free_user_data); + return true; +} + +void giveRestoreEntryReply() +{ + DBusPendingCall pending; + (*m_restoreObjectCb) (&pending, m_user_data); +} + +void giveVolumeReply(QMap volumes) +{ + DBusPendingCall pending; + m_volumes = volumes; + (*m_volumeCb) (&pending, m_user_data); +} + +DBusMessage* dbus_pending_call_steal_reply(DBusPendingCall* pending) +{ + Q_UNUSED(pending); + bool replyForGetVolume = dbus_message_has_member (m_getVolumeMethodCall, "Get"); + DBusMessage* message = dbus_message_new_method_return(m_getVolumeMethodCall); + + qDebug() << "replyForGetVolume= " << replyForGetVolume; + qDebug() << "m_volumes.count()= " << m_volumes.count(); + if (replyForGetVolume) + { + qDebug() << "Jee"; + DBusMessageIter argument_iterator, variant_iterator, array_iterator; + dbus_message_iter_init_append (message, &argument_iterator); + dbus_message_iter_open_container (&argument_iterator, + DBUS_TYPE_VARIANT, + "a(uu)", + &variant_iterator); + dbus_message_iter_open_container (&variant_iterator, + DBUS_TYPE_ARRAY, + "(uu)", + &array_iterator); + if (m_volumes.isEmpty()) + { + qDebug() << "Stub is giving an invalid reply"; + } + else + { + QMapIterator i(m_volumes); + i.toBack(); + while (i.hasPrevious()) + { + DBusMessageIter iterator; + dbus_message_iter_open_container (&array_iterator, + DBUS_TYPE_STRUCT, + NULL, + &iterator); + i.previous(); + dbus_message_iter_append_basic (&iterator, DBUS_TYPE_UINT32, &i.key()); + dbus_message_iter_append_basic (&iterator, DBUS_TYPE_UINT32, &i.value()); + dbus_message_iter_close_container (&array_iterator, &iterator); + } + } + dbus_message_iter_close_container (&variant_iterator, &array_iterator); + dbus_message_iter_close_container (&argument_iterator, &variant_iterator); + } + return message; +} + +dbus_uint32_t dbus_message_get_serial(DBusMessage* message) +{ + Q_UNUSED(message); + return 1; +} + +dbus_bool_t dbus_message_get_args (DBusMessage* message, + DBusError* error, + int first_arg_type, + ...) +{ + qDebug() << "dbus_message_get_args"; + va_list var_args; + va_start (var_args, first_arg_type); + + while (first_arg_type != DBUS_TYPE_INVALID) + { + if (first_arg_type == DBUS_TYPE_OBJECT_PATH) + { + char** str_p; + str_p = va_arg (var_args, char**); + *str_p = obj_path; + } + first_arg_type = va_arg(var_args, int); + } + va_end(var_args); + Q_UNUSED(error); + Q_UNUSED(message); + return true; +} + +dbus_bool_t dbus_connection_send( DBusConnection* connection, + DBusMessage* message, + dbus_uint32_t* serial) +{ + Q_UNUSED(connection); + Q_UNUSED(serial); + + if (dbus_message_has_member (m_getVolumeMethodCall, "Set")) + { + m_setVolumeMethod = message; + } + + return true; +} + +void dbus_connection_flush( DBusConnection* connection) +{ + Q_UNUSED(connection); +} diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererVolume/ut_MafwGstRendererVolume.cpp b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererVolume/ut_MafwGstRendererVolume.cpp new file mode 100644 index 0000000..1558361 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererVolume/ut_MafwGstRendererVolume.cpp @@ -0,0 +1,223 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ +#include "ut_MafwGstRendererVolume.h" + +#include +#include +#include + +#include "MafwGstRendererVolume.h" +#include "MafwStubHelper.h" + +extern void setStubHelper(MafwStubHelper* stubHlp); +extern void signalVolumeUpdated(uint channel, uint value); +extern void giveRestoreEntryReply(); +extern void giveVolumeReply(QMap volumes); +extern DBusMessage* m_setVolumeMethod; + +void Ut_MafwGstRendererVolume::initTestCase() +{ + m_stubHelper = new MafwStubHelper; + setStubHelper(m_stubHelper); + + //Tests the case where first pulseaudio connect fails and + //DBUSConnectionEventLoop::addConnection fails. + m_stubHelper->expect("dbus_connection_open", false); + m_stubHelper->expect("dbus_error_is_set", true); + m_volume = new MafwGstRendererVolume(); + QVERIFY(m_stubHelper->allCallsConsumed()); + //Wait that connecting to pulseaudio is retried + + m_stubHelper->expect("dbus_connection_open", true); + m_stubHelper->expect("dbus_error_is_set", false); + m_stubHelper->expect("addConnection", false); + QTest::qWait(3000); + QVERIFY(m_stubHelper->allCallsConsumed()); + + delete m_volume; + //Tests the successfull case + m_stubHelper->expect("dbus_connection_open", true); + m_stubHelper->expect("dbus_error_is_set", false); + m_stubHelper->expect("addConnection", true); + m_volume = new MafwGstRendererVolume(); + QVERIFY(m_stubHelper->allCallsConsumed()); +} + +void Ut_MafwGstRendererVolume::cleanupTestCase() +{ + m_stubHelper->expect("removeConnection", false); + delete m_volume; + QVERIFY(m_stubHelper->allCallsConsumed()); + delete m_stubHelper; +} + +void Ut_MafwGstRendererVolume::testVolumeReply() +{ + QMap map; +//Error reply for "GetEntryByName" + m_stubHelper->expect("dbus_set_error_from_message", true); + + //Pulseaudio reconnect is done + m_stubHelper->expect("removeConnection", false); + m_stubHelper->expect("dbus_connection_open", true); + m_stubHelper->expect("dbus_error_is_set", false); + m_stubHelper->expect("addConnection", true); + giveRestoreEntryReply(); + QTest::qWait(500); + QVERIFY(m_stubHelper->allCallsConsumed()); + QCOMPARE(m_volume->getVolume(), (uint)0); +//Error reply for "Get Volume" + m_stubHelper->expect("dbus_set_error_from_message", false); + m_stubHelper->expect("dbus_set_error_from_message", true); + giveRestoreEntryReply(); + giveVolumeReply(map); + QVERIFY(m_stubHelper->allCallsConsumed()); + QCOMPARE(m_volume->getVolume(), (uint)0); +//Invalid reply for "Get Volume" + m_stubHelper->expect("dbus_set_error_from_message", false); + m_stubHelper->expect("dbus_set_error_from_message", false); + giveRestoreEntryReply(); + giveVolumeReply(map); + QVERIFY(m_stubHelper->allCallsConsumed()); + QCOMPARE(m_volume->getVolume(), (uint)0); + +//Maximum value + giveRestoreEntryReply(); + map[0] = 0x10000; + giveVolumeReply(map); + QCOMPARE(m_volume->getVolume(), (uint)100); +//Maximum value-1 + giveRestoreEntryReply(); + map[0] = 0x10000; + giveVolumeReply(map); + QCOMPARE(m_volume->getVolume(), (uint)100); +//Value above range + giveRestoreEntryReply(); + map[0] = 0x10001; + giveVolumeReply(map); + QCOMPARE(m_volume->getVolume(), (uint)100); +//Huge value + map[0] = 0xFF000; + giveRestoreEntryReply(); + giveVolumeReply(map); + QCOMPARE(m_volume->getVolume(), (uint)100); +//Zero + map[0] = 0; + giveRestoreEntryReply(); + giveVolumeReply(map); + QCOMPARE(m_volume->getVolume(), (uint)0); +//Many channels, containing mono channel + map[0] = 0x10000/2; + map[1] = 20; + map[2] = 30; + map[3] = 40; + giveRestoreEntryReply(); + giveVolumeReply(map); + QCOMPARE(m_volume->getVolume(), (uint)50); +//Many channels, without mono channel + map.remove(0); + map[1] = 0x10000/3; + map[2] = 0x10000/4; + map[3] = 0x10000/5; + giveRestoreEntryReply(); + giveVolumeReply(map); + QCOMPARE(m_volume->getVolume(), (uint)(100/3)); +} + +void Ut_MafwGstRendererVolume::testSignal() +{ + QSignalSpy spy(m_volume, SIGNAL(volumeChanged(uint))); + QFETCH(uint, volume); + QFETCH(uint, channel); + // Volume updated signal gives volume level as pulse audio's native value + // Which has range 0 - 0x10000 + signalVolumeUpdated(channel, 0x10000*volume/100); + + QCOMPARE(spy.count(), 1); + QList arguments = spy.takeFirst(); + + if (volume > 100) //Volume must not be set out of range + { + volume = 100; + } + QCOMPARE(arguments.at(0).toUInt(), volume); + QCOMPARE(m_volume->getVolume(), volume); +} + +void Ut_MafwGstRendererVolume::testSignal_data() +{ + QTest::addColumn("volume"); + QTest::addColumn("channel"); + + QTest::newRow("Maximum value") << (uint)100 << (uint)0; + QTest::newRow("Maximum value -1") << (uint)99 << (uint)0; + QTest::newRow("Value above range") << (uint)101 << (uint)0; + QTest::newRow("Huge value") << (uint)-1 << (uint)0; + QTest::newRow("Zero") << (uint)0 << (uint)0; + QTest::newRow("One") << (uint)1 << (uint)0; + QTest::newRow("Another channel") << (uint)2 << (uint)1; +} + +//one MAFW volume step is 0x10000/100 = 655,36 native volume steps +void Ut_MafwGstRendererVolume::testSetVolume() +{ + QFETCH(uint, volume); + quint32 value = 1; + + m_volume->setVolume(volume); + QVERIFY(m_setVolumeMethod != 0); + DBusMessageIter iter, array_iterator, struct_iterator, value_iterator; + dbus_message_iter_init (m_setVolumeMethod, &iter); + QCOMPARE(dbus_message_iter_get_arg_type(&iter), DBUS_TYPE_STRING);//interface + dbus_message_iter_next(&iter); + QCOMPARE(dbus_message_iter_get_arg_type(&iter), DBUS_TYPE_STRING);//property + dbus_message_iter_next(&iter); + dbus_message_iter_recurse (&iter, &array_iterator); + QCOMPARE(dbus_message_iter_get_arg_type(&array_iterator), DBUS_TYPE_ARRAY); + dbus_message_iter_recurse (&array_iterator, &struct_iterator); + QCOMPARE(dbus_message_iter_get_arg_type(&struct_iterator), DBUS_TYPE_STRUCT); + + dbus_message_iter_recurse (&struct_iterator, &value_iterator); + QCOMPARE(dbus_message_iter_get_arg_type (&value_iterator), DBUS_TYPE_UINT32); + dbus_message_iter_get_basic (&value_iterator, &value); + QCOMPARE(value, (quint32)0); //mono channel + dbus_message_iter_next (&value_iterator); + QCOMPARE(dbus_message_iter_get_arg_type (&value_iterator), DBUS_TYPE_UINT32); + dbus_message_iter_get_basic (&value_iterator, &value); + + if (volume > 100) //Volume must not be set out of range + { + volume = 100; + } + + QCOMPARE(value, (quint32)(volume*0x10000/100)); +} + + +void Ut_MafwGstRendererVolume::testSetVolume_data() +{ + QTest::addColumn("volume"); + + QTest::newRow("Set volume to 0") << (uint)0; + QTest::newRow("Set volume to 1") << (uint)1; + QTest::newRow("Set volume to 99") << (uint)99; + QTest::newRow("Set volume to 100") << (uint)100; + QTest::newRow("Set volume to huge value") << (uint)-1; +} + +QTEST_MAIN(Ut_MafwGstRendererVolume) + diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererVolume/ut_MafwGstRendererVolume.h b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererVolume/ut_MafwGstRendererVolume.h new file mode 100644 index 0000000..7ad1de6 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererVolume/ut_MafwGstRendererVolume.h @@ -0,0 +1,46 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#ifndef UT_MAFWGSTRENDERERVOLUME_H_ +#define UT_MAFWGSTRENDERERVOLUME_H_ + +#include + +class MafwGstRendererVolume; +class MafwStubHelper; + +class Ut_MafwGstRendererVolume: public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void cleanupTestCase(); + + void testVolumeReply(); + void testSignal(); + void testSignal_data(); + void testSetVolume(); + void testSetVolume_data(); + +private: + + MafwGstRendererVolume* m_volume; + MafwStubHelper* m_stubHelper; +}; + +#endif /*UT_MAFWGSTRENDERERVOLUME_H_*/ diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererVolume/ut_MafwGstRendererVolume.pro b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererVolume/ut_MafwGstRendererVolume.pro new file mode 100644 index 0000000..f6f5a81 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererVolume/ut_MafwGstRendererVolume.pro @@ -0,0 +1,29 @@ +TEMPLATE = app +TARGET = +QT -= gui +CONFIG = console + +INCLUDEPATH += . +MAFW_GST_INCDIR = ../../inc +UT_COMMONDIR = ../common + +INCLUDEPATH += $$MAFW_GST_INCDIR $$UT_COMMONDIR +DEPENDPATH += . ../../inc ../../src + +CONFIG += qtestlib qt link_pkgconfig debug qdbus +PKGCONFIG += qmafw dbus-1 libpulse-mainloop-glib +LIBS += -lgcov -ldbus-qeventloop + +QMAKE_CXXFLAGS +=-fprofile-arcs -ftest-coverage -O0 -Werror +QMAKE_CFLAGS +=-fprofile-arcs -ftest-coverage -O0 -Werror + +# Input +HEADERS += ut_MafwGstRendererVolume.h \ + MafwGstRendererVolume.h \ + $$UT_COMMONDIR/MafwStubHelper.h + +SOURCES += ut_MafwGstRendererVolume.cpp \ + MafwVolumeStubs.cpp \ + MafwGstRendererVolume.cpp \ + $$UT_COMMONDIR/MafwStubHelper.cpp \ + diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererWorker/media/test.avi b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererWorker/media/test.avi new file mode 100644 index 0000000000000000000000000000000000000000..f868c625c4bd780ab3e33dc5cfb83ee6a2b28049 GIT binary patch literal 86540 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%0Aun=rc|E*dFqHw3+5l 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 U+!+ndl6(7R+YIF5J-HN$3U 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 ZImh{7HFeJ+Z3dZ_$Zv+(pL7z;{{gZ!UmE}b literal 0 HcmV?d00001 diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererWorker/media/testframe.png b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererWorker/media/testframe.png new file mode 100644 index 0000000000000000000000000000000000000000..a0db335ffcc6a396393e883b44ab5aacb41c2f36 GIT binary patch literal 13945 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 OnWC(!OoNnJ`2Pcz^d~C- literal 0 HcmV?d00001 diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererWorker/ut_MafwGstRendererWorker.cpp b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererWorker/ut_MafwGstRendererWorker.cpp new file mode 100644 index 0000000..b881ef7 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererWorker/ut_MafwGstRendererWorker.cpp @@ -0,0 +1,1432 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include "ut_MafwGstRendererWorker.h" +#include "mafw-gst-renderer-worker.h" +#include "mafw-gst-renderer-utils.h" + +static QUrl AUDIO_URI(QDir::currentPath() + QDir::separator() + + QString("media") + QDir::separator() + + QString("test.wav")); + +static QUrl VIDEO_URI(QDir::currentPath() + QDir::separator() + + QString("media") + QDir::separator() + + QString("test.avi")); + +static QUrl IMAGE_URI(QDir::currentPath() + QDir::separator() + + QString("media") + QDir::separator() + + QString("testframe.png")); + +static int WAIT_TIMEOUT = 3500; +static int EOS_TIMEOUT = 7000; +static int READY_DELAY = 65000; + +static bool NEVER_HAPPENING_EVENT = false; + +static int global_context_duration = 0; + +static QStringList globalGstFactoryRequests; +static GstElement *globalGstPipeline; + + +const gchar* g_getenv(const gchar *variable) +{ + Q_UNUSED(variable); + return NULL; +} + +gboolean convert_utf8(const gchar *src, gchar **dst) +{ + *dst = g_strdup(src); + return TRUE; +} + +gboolean uri_is_playlist(const gchar *uri) +{ + Q_UNUSED(uri); + return FALSE; +} + +gboolean uri_is_stream(const gchar *uri) +{ + Q_UNUSED(uri); + return FALSE; +} + +gint remap_gst_error_code(const GError *error) +{ + return error->code; +} + +void context_provider_set_map(const char* key, void* map, int free_map) +{ + Q_UNUSED(key); + Q_UNUSED(map); + Q_UNUSED(free_map); +} + +void *context_provider_map_new(void) +{ + + return NULL; +} + +void context_provider_map_free(void* map) +{ + Q_UNUSED(map); +} + +void context_provider_map_set_integer(void* map, const char* key, int value) +{ + Q_UNUSED(map); + + if( strcmp( key, "duration" )==0 ) + { + global_context_duration = value; + } +} + +void context_provider_map_set_double(void* map, const char* key, double value) +{ + Q_UNUSED(map); + Q_UNUSED(key); + Q_UNUSED(value); +} + +void context_provider_map_set_boolean(void* map, const char* key, int value) +{ + Q_UNUSED(map); + Q_UNUSED(key); + Q_UNUSED(value); +} + +void context_provider_map_set_string(void* map, const char* key, const char* value) +{ + Q_UNUSED(map); + Q_UNUSED(key); + Q_UNUSED(value); +} + +void context_provider_map_set_map(void* map, const char* key, void* value) +{ + Q_UNUSED(map); + Q_UNUSED(key); + Q_UNUSED(value); +} + +void context_provider_set_null(const char* key) +{ + Q_UNUSED(key); +} + +gboolean context_provider_init(DBusBusType bus_type, const char* bus_name) +{ + + Q_UNUSED(bus_type); + Q_UNUSED(bus_name); + + return TRUE; + +} + +void context_provider_stop(void) +{ +} + +void context_provider_install_key( + const char* key, + gboolean clear_values_on_subscribe, + ContextProviderSubscriptionChangedCallback subscription_changed_cb, + void* subscription_changed_cb_target) +{ + Q_UNUSED(key); + Q_UNUSED(clear_values_on_subscribe); + Q_UNUSED(subscription_changed_cb); + Q_UNUSED(subscription_changed_cb_target); +} + +GstElement *gst_element_factory_make(const gchar *factoryname, const gchar *name) +{ + GstElementFactory *factory; + GstElement *element; + const gchar *use_factoryname; + + g_return_val_if_fail(factoryname != NULL, NULL); + + globalGstFactoryRequests.append(factoryname); + + /* For testing, use playbin instead of playbin2 */ + if (g_str_equal(factoryname, "playbin2")) + { + use_factoryname = "playbin"; + } + else + { + use_factoryname = factoryname; + } + + GST_LOG("gstelementfactory: make \"%s\" \"%s\"", + use_factoryname, GST_STR_NULL (name)); + + factory = gst_element_factory_find(use_factoryname); + if (factory == NULL) + { + /* No factory */ + GST_INFO("no such element factory \"%s\"!", use_factoryname); + return NULL; + } + + GST_LOG_OBJECT(factory, "found factory %p", factory); + if (g_str_equal(use_factoryname, "pulsesink")) + { + element = gst_element_factory_make("fakesink", "pulsesink"); + g_object_set(G_OBJECT(element), "sync", TRUE, NULL); + } + else if (g_str_equal(use_factoryname, "alsasink")) + { + element = gst_element_factory_make("fakesink", "alsasink"); + g_object_set(G_OBJECT(element), "sync", TRUE, NULL); + } + else if (g_str_equal(use_factoryname, "xvimagesink") + || g_str_equal(use_factoryname, "omapxvsink")) + { + element = gst_element_factory_make("fakesink", "xvimagesink"); + g_object_set(G_OBJECT(element), "sync", TRUE, NULL); + } + else + { + element = gst_element_factory_create(factory, name); + if( !g_strcmp0(use_factoryname, "playbin") ) + { + globalGstPipeline = element; + } + } + gst_object_unref(factory); + + if (element == NULL) + { + /* Create failed */ + GST_INFO_OBJECT(factory, "couldn't create instance!"); + return NULL; + } + + GST_LOG("gstelementfactory: make \"%s\" \"%s\"",use_factoryname, + GST_STR_NULL(name)); + + /* Playbin will use fake renderer */ + if (g_str_equal(use_factoryname, "playbin")) + { + GstElement *audiorenderer = gst_element_factory_make("fakesink", + "audiorenderer"); + g_object_set(G_OBJECT(audiorenderer), "sync", TRUE, NULL); + g_object_set(G_OBJECT(element), "audio-sink", audiorenderer, NULL); + g_object_set(G_OBJECT(element), "video-sink", audiorenderer, NULL); + } + + return element; + +} +/* END OF STUB DEFINITIONS */ + +void ut_MafwGstRendererWorker::playCallback(MafwGstRendererWorker *worker, + gpointer owner) +{ + + Q_UNUSED(worker); + Q_UNUSED(owner); + qDebug() << __PRETTY_FUNCTION__; + + ut_MafwGstRendererWorker* self = + static_cast(owner); + self->m_gotPlayCallback = true; + +} + +void ut_MafwGstRendererWorker::pauseCallback(MafwGstRendererWorker *worker, + gpointer owner) +{ + Q_UNUSED(worker); + + qDebug() << __PRETTY_FUNCTION__; + + ut_MafwGstRendererWorker* self = + static_cast(owner); + self->m_gotPauseCallback = true; +} + +void ut_MafwGstRendererWorker::bufferingCallback(MafwGstRendererWorker *worker, + gpointer owner, + gdouble percent) +{ + Q_UNUSED(worker); + + qDebug() << __PRETTY_FUNCTION__; + + ut_MafwGstRendererWorker* self = + static_cast(owner); + self->m_receivedBufferStatus = percent; + self->m_gotBufferStatusCallback = true; +} + +void ut_MafwGstRendererWorker::eosCallback(MafwGstRendererWorker *worker, + gpointer owner) +{ + + Q_UNUSED(worker); + + qDebug() << __PRETTY_FUNCTION__; + + ut_MafwGstRendererWorker* self = + static_cast(owner); + self->m_gotEosCallback = true; + +} + +void ut_MafwGstRendererWorker::seekCallback(MafwGstRendererWorker *worker, + gpointer owner) +{ + + Q_UNUSED(worker); + + qDebug() << __PRETTY_FUNCTION__; + + ut_MafwGstRendererWorker* self = + static_cast(owner); + self->m_gotSeekCallback = true; + +} + +void ut_MafwGstRendererWorker::errorCallback(MafwGstRendererWorker *worker, + gpointer owner, + const GError *error) +{ + + Q_UNUSED(worker); + + ut_MafwGstRendererWorker* self = + static_cast(owner); + self->m_receivedErrorCode = error->code; + + if (error) + { + qCritical() << "error code: " << error->code << " message: " << + error->message; + } + self->m_gotErrorCallback = true; + +} + +void ut_MafwGstRendererWorker::propertyCallback(MafwGstRendererWorker *worker, + gpointer owner, + gint id, + GValue *value) +{ + + Q_UNUSED(worker); + Q_UNUSED(value); + + ut_MafwGstRendererWorker* self = + static_cast(owner); + self->m_receivedPropertyId = id; + self->m_gotPropertyCallback = true; + +} + +void ut_MafwGstRendererWorker::metadataCallback(MafwGstRendererWorker *worker, + gpointer owner, + gint key, + GType type, + gpointer value) +{ + + Q_UNUSED(worker); + Q_UNUSED(type); + Q_UNUSED(value); + + ut_MafwGstRendererWorker* self = + static_cast(owner); + self->m_receivedMetadata.append(key); + self->m_gotMetadataCallback = true; + +} + +void ut_MafwGstRendererWorker::blankingControlCallback(MafwGstRendererWorker *worker, + gpointer owner, gboolean prohibit) +{ + Q_UNUSED(worker); + + ut_MafwGstRendererWorker* self = + static_cast(owner); + self->m_blankingProhibited = prohibit; +} + +void ut_MafwGstRendererWorker::screenshotCallback(MafwGstRendererWorker *worker, + gpointer owner, GstBuffer *buffer, + const char *filename, gboolean cancel) +{ + Q_UNUSED(worker); + Q_UNUSED(buffer); + Q_UNUSED(filename); + if(!cancel) + { + ut_MafwGstRendererWorker* self = + static_cast(owner); + self->m_receivedMetadata.append(WORKER_METADATA_KEY_PAUSED_THUMBNAIL_URI); + self->m_gotMetadataCallback = true; + } +} + +void ut_MafwGstRendererWorker::basicPlaybackTestCase() +{ + + m_worker->notify_play_handler = &playCallback; + m_worker->notify_error_handler = &errorCallback; + m_worker->blanking__control_handler = &blankingControlCallback; + mafw_gst_renderer_worker_play(m_worker, AUDIO_URI.toString().toAscii()); + + waitForEvent(WAIT_TIMEOUT, m_gotPlayCallback); + + QVERIFY(m_gotPlayCallback == true); + QVERIFY(m_gotErrorCallback == false); + QCOMPARE( global_context_duration, 3 ); // 2.5 seconds is duration of test.wav + QVERIFY(m_blankingProhibited == false); + + //video-sink should not be created unless XID has been set + QVERIFY(!m_worker->vsink); +} + +void ut_MafwGstRendererWorker::basicVideoPlaybackTestCase() +{ + + m_worker->notify_play_handler = &playCallback; + m_worker->notify_error_handler = &errorCallback; + m_worker->blanking__control_handler = &blankingControlCallback; + + QVERIFY(!m_worker->vsink); + mafw_gst_renderer_worker_set_xid(m_worker, 0xffff); + QVERIFY(m_worker->vsink); + + mafw_gst_renderer_worker_play(m_worker, VIDEO_URI.toString().toAscii()); + m_worker->media.has_visual_content = true; // normally gst sets this but not in unittest for some reason + + waitForEvent(WAIT_TIMEOUT, m_gotPlayCallback); + + QVERIFY(m_gotPlayCallback == true); + QVERIFY(m_gotErrorCallback == false); + QVERIFY(m_blankingProhibited == true); +} + +void ut_MafwGstRendererWorker::pauseFrameTestCase() +{ + + m_worker->notify_play_handler = &playCallback; + m_worker->notify_pause_handler = &pauseCallback; + m_worker->notify_metadata_handler = &metadataCallback; + m_worker->notify_property_handler = &propertyCallback; + m_worker->notify_error_handler = &errorCallback; + m_worker->blanking__control_handler = &blankingControlCallback; + m_worker->screenshot_handler = screenshotCallback; + + mafw_gst_renderer_worker_set_current_frame_on_pause(m_worker, TRUE); + QVERIFY(mafw_gst_renderer_worker_get_current_frame_on_pause(m_worker) == + TRUE); + QVERIFY(m_gotPropertyCallback == true); + QVERIFY(m_receivedPropertyId == WORKER_PROPERTY_CURRENT_FRAME_ON_PAUSE); + + mafw_gst_renderer_worker_set_xid(m_worker, 0xffff); + QVERIFY(mafw_gst_renderer_worker_get_xid(m_worker) == 0xffff); + QVERIFY(m_gotPropertyCallback == true); + QVERIFY(m_receivedPropertyId == WORKER_PROPERTY_XID); + m_gotPropertyCallback = false; + m_receivedPropertyId = -1; + + mafw_gst_renderer_worker_play(m_worker, VIDEO_URI.toString().toAscii()); + + /* post xwindow-id on bus since we are using fakesink instead of + * xvimagesink */ + GstBus *bus; + GstStructure *structure; + GstMessage *message; + structure = gst_structure_new("prepare-xwindow-id", "width", + G_TYPE_INT, 64, "height", G_TYPE_INT, 32, + NULL); + message = gst_message_new_element(NULL, structure); + bus = m_worker->bus; + gst_bus_post(bus, message); + waitForEvent(WAIT_TIMEOUT, m_gotPlayCallback); + + mafw_gst_renderer_worker_pause(m_worker); + waitForEvent(WAIT_TIMEOUT, NEVER_HAPPENING_EVENT); + + QVERIFY(m_gotPlayCallback == true); + QVERIFY(m_gotPauseCallback == true); + QVERIFY(m_gotMetadataCallback == true); + QVERIFY(m_gotErrorCallback == false); + QVERIFY(m_receivedMetadata.contains(WORKER_METADATA_KEY_PAUSED_THUMBNAIL_URI)); + +} + +void ut_MafwGstRendererWorker::pauseFrameCancelTestCase() +{ + m_worker->notify_play_handler = &playCallback; + m_worker->notify_pause_handler = &pauseCallback; + m_worker->notify_metadata_handler = &metadataCallback; + m_worker->notify_property_handler = &propertyCallback; + m_worker->notify_error_handler = &errorCallback; + m_worker->blanking__control_handler = &blankingControlCallback; + m_worker->screenshot_handler = screenshotCallback; + + mafw_gst_renderer_worker_set_current_frame_on_pause(m_worker, TRUE); + QVERIFY(mafw_gst_renderer_worker_get_current_frame_on_pause(m_worker) == + TRUE); + QVERIFY(m_gotPropertyCallback == true); + QVERIFY(m_receivedPropertyId == WORKER_PROPERTY_CURRENT_FRAME_ON_PAUSE); + + mafw_gst_renderer_worker_set_xid(m_worker, 0xffff); + QVERIFY(mafw_gst_renderer_worker_get_xid(m_worker) == 0xffff); + QVERIFY(m_gotPropertyCallback == true); + QVERIFY(m_receivedPropertyId == WORKER_PROPERTY_XID); + m_gotPropertyCallback = false; + m_receivedPropertyId = -1; + + mafw_gst_renderer_worker_play(m_worker, VIDEO_URI.toString().toAscii()); + + /* post xwindow-id on bus since we are using fakesink instead of + * xvimagesink */ + GstBus *bus; + GstStructure *structure; + GstMessage *message; + structure = gst_structure_new("prepare-xwindow-id", "width", + G_TYPE_INT, 64, "height", G_TYPE_INT, 32, + NULL); + message = gst_message_new_element(NULL, structure); + bus = m_worker->bus; + gst_bus_post(bus, message); + waitForEvent(WAIT_TIMEOUT, m_gotPlayCallback); + + mafw_gst_renderer_worker_pause(m_worker); + waitForEvent(WAIT_TIMEOUT, m_gotPauseCallback); + mafw_gst_renderer_worker_resume(m_worker); + QTest::qWait(10); + + + QVERIFY(m_gotPlayCallback == true); + QVERIFY(m_gotPauseCallback == true); + QVERIFY(m_gotMetadataCallback == true); + QVERIFY(m_gotErrorCallback == false); + QVERIFY(!m_receivedMetadata.contains(WORKER_METADATA_KEY_PAUSED_THUMBNAIL_URI)); + + +} + +void ut_MafwGstRendererWorker::redirectMessageTestCase() +{ + m_worker->notify_play_handler = &playCallback; + m_worker->notify_error_handler = &errorCallback; + m_worker->blanking__control_handler = &blankingControlCallback; + + /* We just give random uri to renderer, because we ignore the error */ + /* We don't have to test with real RTSP stream, we just use local content */ + char *control = qstrdup(VIDEO_URI.toString().toAscii()); + + GstStructure *structure; + GstMessage *message; + structure = gst_structure_new("redirect", "new-location", + G_TYPE_STRING, control, NULL); + + // reset and construct the pipeline + mafw_gst_renderer_worker_stop(m_worker); + + // when worker receives "redirect" message, it should start playing the uri contained in the message. + message = gst_message_new_element(GST_OBJECT_CAST(m_worker->pipeline), structure); + gst_element_post_message(m_worker->pipeline, message); + + + waitForEvent(WAIT_TIMEOUT, m_gotPlayCallback); + + QVERIFY(m_gotPlayCallback == true); + QVERIFY(strcmp(control, m_worker->media.location) == 0); + QVERIFY(m_gotErrorCallback == false); + + delete[] control; +} + +void ut_MafwGstRendererWorker::slotTimeOut() +{ + qDebug() << "TIMEOUT!"; +} + +void ut_MafwGstRendererWorker::waitForEvent(gint ms, bool &hasEventOccurred) + +{ + qDebug() << __PRETTY_FUNCTION__; + + if (hasEventOccurred) + { + return; + } + + QTimer timer(this); + QObject::connect(&timer, SIGNAL(timeout()), this, SLOT(slotTimeOut())); + timer.setSingleShot(true); + timer.start(ms); + + while (!hasEventOccurred && timer.isActive()) + { + g_main_context_iteration(NULL, TRUE); + } +} + +void ut_MafwGstRendererWorker::bufferingTestCase() +{ + + m_worker->notify_play_handler = &playCallback; + m_worker->notify_buffer_status_handler = &bufferingCallback; + m_worker->notify_error_handler = &errorCallback; + m_worker->blanking__control_handler = &blankingControlCallback; + + mafw_gst_renderer_worker_play(m_worker, AUDIO_URI.toString().toAscii()); + waitForEvent(WAIT_TIMEOUT, m_gotPlayCallback); + + /* post the buffering msg on bus */ + GstBus *bus = 0; + GstMessage *message = 0; + + message = gst_message_new_buffering(NULL, 50); + bus = m_worker->bus; + gst_bus_post(bus, message); + + waitForEvent(WAIT_TIMEOUT, m_gotBufferStatusCallback); + + QVERIFY(m_gotPlayCallback == true); + QVERIFY(m_receivedBufferStatus == 50); + QVERIFY(m_gotErrorCallback == false); + +} + +void ut_MafwGstRendererWorker::rendererArtTestCase() +{ + + m_worker->notify_play_handler = &playCallback; + m_worker->notify_pause_handler = &pauseCallback; + m_worker->notify_metadata_handler = &metadataCallback; + m_worker->notify_error_handler = &errorCallback; + m_worker->blanking__control_handler = &blankingControlCallback; + + mafw_gst_renderer_worker_play(m_worker, AUDIO_URI.toString().toAscii()); + waitForEvent(WAIT_TIMEOUT, m_gotPlayCallback); + mafw_gst_renderer_worker_pause(m_worker); + waitForEvent(WAIT_TIMEOUT, m_gotPauseCallback); + + /* post the renderer art tag on bus */ + gsize image_length = 0; + gchar *image = 0; + GstCaps *caps = 0; + GstBuffer *buffer = 0; + GstMessage *message = 0; + GstTagList *list = 0; + + QVERIFY(g_file_get_contents(IMAGE_URI.toLocalFile().toAscii(), + &image, + &image_length, + NULL)); + buffer = gst_buffer_new(); + gst_buffer_set_data(buffer, (guint8*)image, image_length); + caps = gst_caps_new_simple("image/png", "image-type", + GST_TYPE_TAG_IMAGE_TYPE, + GST_TAG_IMAGE_TYPE_FRONT_COVER, NULL); + gst_buffer_set_caps(buffer, caps); + + list = gst_tag_list_new(); + gst_tag_list_add(list, GST_TAG_MERGE_APPEND, GST_TAG_IMAGE, buffer, NULL); + + message = gst_message_new_tag(NULL, list); + gst_bus_post(m_worker->bus, message); + mafw_gst_renderer_worker_resume(m_worker); + + waitForEvent(WAIT_TIMEOUT, NEVER_HAPPENING_EVENT); + + QVERIFY(m_gotPlayCallback == true); + QVERIFY(m_gotPauseCallback == true); + QVERIFY(m_gotMetadataCallback == true); + QVERIFY(m_gotErrorCallback == false); + QVERIFY(m_receivedMetadata.contains(WORKER_METADATA_KEY_RENDERER_ART_URI)); + + gst_buffer_unref(buffer); + gst_caps_unref(caps); + g_free(image); + +} + +void ut_MafwGstRendererWorker::eosTestCase() +{ + + m_worker->notify_play_handler = &playCallback; + m_worker->notify_error_handler = &errorCallback; + m_worker->notify_eos_handler = &eosCallback; + m_worker->blanking__control_handler = &blankingControlCallback; + mafw_gst_renderer_worker_play(m_worker, AUDIO_URI.toString().toAscii()); + + waitForEvent(EOS_TIMEOUT, m_gotEosCallback); + + QVERIFY(m_gotPlayCallback == true); + QVERIFY(m_gotEosCallback == true); + QVERIFY(m_gotErrorCallback == false); + +} + +void ut_MafwGstRendererWorker::seekTestCase() +{ + + GError *error = 0; + + m_worker->notify_play_handler = &playCallback; + m_worker->notify_error_handler = &errorCallback; + m_worker->notify_seek_handler = &seekCallback; + m_worker->blanking__control_handler = &blankingControlCallback; + mafw_gst_renderer_worker_play(m_worker, AUDIO_URI.toString().toAscii()); + + waitForEvent(WAIT_TIMEOUT, m_gotPlayCallback); + + QVERIFY(m_gotPlayCallback == true); + QVERIFY(m_gotErrorCallback == false); + + mafw_gst_renderer_worker_set_position(m_worker, + GST_SEEK_TYPE_SET, + 1, + &error); + + QVERIFY(m_gotPlayCallback == true); + QVERIFY(m_gotSeekCallback == true); + QVERIFY(m_gotErrorCallback == false); + QVERIFY(error == 0); + QVERIFY(mafw_gst_renderer_worker_get_position(m_worker) == 1); + +} + +void ut_MafwGstRendererWorker::invalidUriTestCase() +{ + + m_worker->notify_play_handler = &playCallback; + m_worker->notify_error_handler = &errorCallback; + m_worker->blanking__control_handler = &blankingControlCallback; + mafw_gst_renderer_worker_play(m_worker, "vjmksbhdfghrejggv"); + + waitForEvent(WAIT_TIMEOUT, m_gotErrorCallback); + + QVERIFY(m_gotPlayCallback == false); + QVERIFY(m_gotErrorCallback == true); + /* FIXME: new code for Gst errors? */ + /*QVERIFY(m_receivedErrorCode == WORKER_ERROR_UNABLE_TO_PERFORM);*/ + +} + +void ut_MafwGstRendererWorker::playAndStopTestCase() +{ + + m_worker->notify_play_handler = &playCallback; + m_worker->notify_error_handler = &errorCallback; + m_worker->blanking__control_handler = &blankingControlCallback; + mafw_gst_renderer_worker_play(m_worker, AUDIO_URI.toString().toAscii()); + + waitForEvent(WAIT_TIMEOUT, m_gotPlayCallback); + QVERIFY(m_gotPlayCallback == true); + + mafw_gst_renderer_worker_stop(m_worker); + + QVERIFY(m_gotPlayCallback == true); + QVERIFY(m_gotErrorCallback == false); + +} + +void ut_MafwGstRendererWorker::playAndPauseTestCase() +{ + + m_worker->notify_play_handler = &playCallback; + m_worker->notify_error_handler = &errorCallback; + m_worker->notify_pause_handler = &pauseCallback; + m_worker->blanking__control_handler = &blankingControlCallback; + mafw_gst_renderer_worker_set_xid(m_worker, 0xffff); + + mafw_gst_renderer_worker_play(m_worker, VIDEO_URI.toString().toAscii()); + m_worker->media.has_visual_content = true; // normally gst sets this but not in unittest for some reason + + waitForEvent(WAIT_TIMEOUT, m_gotPlayCallback); + QVERIFY(m_gotPlayCallback == true); + QVERIFY(m_gotPauseCallback == false); + QVERIFY(m_blankingProhibited == true); + + mafw_gst_renderer_worker_pause(m_worker); + waitForEvent(WAIT_TIMEOUT, m_gotPauseCallback); + + QVERIFY(m_gotPlayCallback == true); + QVERIFY(m_gotPauseCallback == true); + QVERIFY(m_gotErrorCallback == false); + QVERIFY(m_blankingProhibited == false); +} + +void ut_MafwGstRendererWorker::pauseQuicklyAfterPlayTestCase() +{ + m_worker->notify_play_handler = &playCallback; + m_worker->notify_error_handler = &errorCallback; + m_worker->notify_pause_handler = &pauseCallback; + m_worker->blanking__control_handler = &blankingControlCallback; + mafw_gst_renderer_worker_set_xid(m_worker, 0xffff); + + mafw_gst_renderer_worker_play(m_worker, VIDEO_URI.toString().toAscii()); + m_worker->media.has_visual_content = true; // normally gst sets this but not in unittest for some reason + mafw_gst_renderer_worker_pause(m_worker); + + waitForEvent(WAIT_TIMEOUT, m_gotPauseCallback); + QVERIFY(m_gotPlayCallback == false); + QVERIFY(m_gotPauseCallback == true); + QVERIFY(m_blankingProhibited == false); + + m_gotPlayCallback = false; + m_gotPauseCallback = false; + + mafw_gst_renderer_worker_resume(m_worker); + waitForEvent(WAIT_TIMEOUT, m_gotPlayCallback); + waitForEvent(WAIT_TIMEOUT, m_blankingProhibited); + QCOMPARE(m_gotPlayCallback, true); + QCOMPARE(m_blankingProhibited, true); +} + +void ut_MafwGstRendererWorker::pauseAtTestCase() +{ + m_worker->notify_play_handler = &playCallback; + m_worker->notify_error_handler = &errorCallback; + m_worker->notify_pause_handler = &pauseCallback; + m_worker->blanking__control_handler = &blankingControlCallback; + mafw_gst_renderer_worker_set_xid(m_worker, 0xffff); + + mafw_gst_renderer_worker_play(m_worker, VIDEO_URI.toString().toAscii()); + m_worker->media.has_visual_content = true; // normally gst sets this but not in unittest for some reason + //the video is only two seconds long + mafw_gst_renderer_worker_pause_at(m_worker, 1); + + waitForEvent(WAIT_TIMEOUT, m_gotPauseCallback); + QVERIFY(m_gotPlayCallback == false); + QVERIFY(m_gotPauseCallback == true); + QVERIFY(m_blankingProhibited == false); + + QVERIFY(mafw_gst_renderer_worker_get_position(m_worker) == 1); + + m_gotPlayCallback = false; + m_gotPauseCallback = false; + + mafw_gst_renderer_worker_resume(m_worker); + waitForEvent(WAIT_TIMEOUT, m_gotPlayCallback); + waitForEvent(WAIT_TIMEOUT, m_blankingProhibited); + QCOMPARE(m_gotPlayCallback, true); + QCOMPARE(m_blankingProhibited, true); +} + +void ut_MafwGstRendererWorker::playAndPauseAndResumeTestCase() +{ + + m_worker->notify_play_handler = &playCallback; + m_worker->notify_error_handler = &errorCallback; + m_worker->notify_pause_handler = &pauseCallback; + m_worker->blanking__control_handler = &blankingControlCallback; + mafw_gst_renderer_worker_play(m_worker, AUDIO_URI.toString().toAscii()); + + waitForEvent(WAIT_TIMEOUT, m_gotPlayCallback); + QVERIFY(m_gotPlayCallback == true); + QVERIFY(m_gotPauseCallback == false); + + mafw_gst_renderer_worker_pause(m_worker); + waitForEvent(WAIT_TIMEOUT, m_gotPauseCallback); + + m_gotPlayCallback = false; + mafw_gst_renderer_worker_resume(m_worker); + waitForEvent(WAIT_TIMEOUT, m_gotPlayCallback); + + QVERIFY(m_gotPlayCallback == true); + QVERIFY(m_gotPauseCallback == true); + QVERIFY(m_gotErrorCallback == false); + +} + + +void ut_MafwGstRendererWorker::resumeDelayedTestCase() +{ + + m_worker->notify_play_handler = &playCallback; + m_worker->notify_error_handler = &errorCallback; + m_worker->notify_pause_handler = &pauseCallback; + m_worker->blanking__control_handler = &blankingControlCallback; + mafw_gst_renderer_worker_play(m_worker, AUDIO_URI.toString().toAscii()); + + waitForEvent(WAIT_TIMEOUT, m_gotPlayCallback); + QVERIFY(m_gotPlayCallback == true); + QVERIFY(m_gotPauseCallback == false); + + mafw_gst_renderer_worker_pause(m_worker); + waitForEvent(WAIT_TIMEOUT, m_gotPauseCallback); + /* pipeline goes to ready after 60 seconds */ + waitForEvent(READY_DELAY, NEVER_HAPPENING_EVENT); + + m_gotPlayCallback = false; + mafw_gst_renderer_worker_resume(m_worker); + waitForEvent(WAIT_TIMEOUT, m_gotPlayCallback); + + QVERIFY(m_gotPlayCallback == true); + QVERIFY(m_gotPauseCallback == true); + QVERIFY(m_gotErrorCallback == false); + +} + +void ut_MafwGstRendererWorker::getCurrentMetadataTestCase() +{ + + GHashTable *metadata = 0; + + m_worker->notify_play_handler = &playCallback; + m_worker->notify_error_handler = &errorCallback; + m_worker->notify_pause_handler = &pauseCallback; + m_worker->blanking__control_handler = &blankingControlCallback; + mafw_gst_renderer_worker_play(m_worker, AUDIO_URI.toString().toAscii()); + + waitForEvent(WAIT_TIMEOUT, m_gotPlayCallback); + QVERIFY(m_gotPlayCallback == true); + QVERIFY(m_gotPauseCallback == false); + + mafw_gst_renderer_worker_pause(m_worker); + waitForEvent(WAIT_TIMEOUT, m_gotPauseCallback); + + metadata = mafw_gst_renderer_worker_get_current_metadata(m_worker); + QVERIFY(metadata); + QVERIFY(g_hash_table_size(metadata)); + + mafw_gst_renderer_worker_stop(m_worker); + metadata = mafw_gst_renderer_worker_get_current_metadata(m_worker); + QVERIFY(!metadata); + + QVERIFY(m_gotPlayCallback == true); + QVERIFY(m_gotPauseCallback == true); + QVERIFY(m_gotErrorCallback == false); + +} + +void ut_MafwGstRendererWorker::defaultConfigurationTestCase() +{ + QVERIFY2(sizeof(configuration) == 64, "You've (or somebody else) most likely changed the configuration struct! Update the unittests also!"); + + configuration * current_worker_conf = mafw_gst_renderer_worker_create_default_configuration(m_worker); + + QCOMPARE(current_worker_conf->asink, "pulsesink"); + QCOMPARE(current_worker_conf->vsink, "omapxvsink"); + QCOMPARE(current_worker_conf->buffer_time, 600000LL); + QCOMPARE(current_worker_conf->latency_time, 100000LL); + QCOMPARE(current_worker_conf->flags, 67); + + QCOMPARE(current_worker_conf->mobile_surround_music.state, 0U); + QCOMPARE(current_worker_conf->mobile_surround_music.color, 2); + QCOMPARE(current_worker_conf->mobile_surround_music.room, 2); + QCOMPARE(current_worker_conf->mobile_surround_video.state, 0U); + QCOMPARE(current_worker_conf->mobile_surround_video.color, 2); + QCOMPARE(current_worker_conf->mobile_surround_video.room, 2); + + QCOMPARE(current_worker_conf->milliseconds_to_pause_frame, 700U); + QCOMPARE(current_worker_conf->seconds_to_pause_to_ready, 3U); + QCOMPARE(current_worker_conf->use_dhmmixer, 1); + + //this well make valgrind happy. i.e. worker will handle memory cleanupping + mafw_gst_renderer_worker_set_configuration(m_worker, current_worker_conf); +} + +void ut_MafwGstRendererWorker::configurabilityTestCase() +{ + MafwGstRendererWorker *configWorker = mafw_gst_renderer_worker_new(this); + + configuration *config = mafw_gst_renderer_worker_create_default_configuration(configWorker); + + g_free(config->asink); + config->asink = g_strdup("test-sink"); + + //audio sink + QVERIFY(!globalGstFactoryRequests.contains("test-sink")); + mafw_gst_renderer_worker_set_configuration(configWorker, config); + mafw_gst_renderer_worker_stop(configWorker); + QVERIFY(globalGstFactoryRequests.contains("test-sink")); + globalGstFactoryRequests.clear(); + + mafw_gst_renderer_worker_exit(configWorker); + g_free(configWorker); + + + //video sink + configWorker = mafw_gst_renderer_worker_new(this); + config = mafw_gst_renderer_worker_create_default_configuration(configWorker); + g_free(config->vsink); + config->vsink = g_strdup("test-video-sink"); + + QVERIFY(!globalGstFactoryRequests.contains("test-video-sink")); + + mafw_gst_renderer_worker_set_configuration(configWorker, config); + //video-sink is not created unless xid has been set + mafw_gst_renderer_worker_set_xid(configWorker, 0xffff); + + mafw_gst_renderer_worker_stop(configWorker); + QVERIFY(globalGstFactoryRequests.contains("test-video-sink")); + + globalGstFactoryRequests.clear(); + mafw_gst_renderer_worker_exit(configWorker); + g_free(configWorker); + + + //NO dhmmixer + configWorker = mafw_gst_renderer_worker_new(this); + config = mafw_gst_renderer_worker_create_default_configuration(configWorker); + config->use_dhmmixer = 0; + + QVERIFY(!globalGstFactoryRequests.contains("nokiadhmmix")); + mafw_gst_renderer_worker_set_configuration(configWorker, config); + mafw_gst_renderer_worker_stop(configWorker); + QVERIFY(!globalGstFactoryRequests.contains("nokiadhmmix")); + + globalGstFactoryRequests.clear(); + mafw_gst_renderer_worker_exit(configWorker); + g_free(configWorker); + + //YES dhmmixer + configWorker = mafw_gst_renderer_worker_new(this); + config = mafw_gst_renderer_worker_create_default_configuration(configWorker); + config->use_dhmmixer = 1; + + QVERIFY(!globalGstFactoryRequests.contains("nokiadhmmix")); + mafw_gst_renderer_worker_set_configuration(configWorker, config); + mafw_gst_renderer_worker_stop(configWorker); + QVERIFY(globalGstFactoryRequests.contains("nokiadhmmix")); + + globalGstFactoryRequests.clear(); + mafw_gst_renderer_worker_exit(configWorker); + g_free(configWorker); +} + +void ut_MafwGstRendererWorker::pauseFrameConfigurabilityTestCase() +{ + + m_worker->notify_play_handler = &playCallback; + m_worker->notify_pause_handler = &pauseCallback; + m_worker->notify_metadata_handler = &metadataCallback; + m_worker->notify_property_handler = &propertyCallback; + m_worker->notify_error_handler = &errorCallback; + m_worker->blanking__control_handler = &blankingControlCallback; + m_worker->screenshot_handler = screenshotCallback; + + //let's change timeout values + configuration *config = mafw_gst_renderer_worker_create_default_configuration(m_worker); + config->milliseconds_to_pause_frame = 3000; + config->seconds_to_pause_to_ready = 6; + mafw_gst_renderer_worker_set_configuration(m_worker, config); + + + mafw_gst_renderer_worker_set_current_frame_on_pause(m_worker, TRUE); + QVERIFY(mafw_gst_renderer_worker_get_current_frame_on_pause(m_worker) == + TRUE); + QVERIFY(m_gotPropertyCallback == true); + QVERIFY(m_receivedPropertyId == WORKER_PROPERTY_CURRENT_FRAME_ON_PAUSE); + + mafw_gst_renderer_worker_set_xid(m_worker, 0xffff); + QVERIFY(mafw_gst_renderer_worker_get_xid(m_worker) == 0xffff); + QVERIFY(m_gotPropertyCallback == true); + QVERIFY(m_receivedPropertyId == WORKER_PROPERTY_XID); + m_gotPropertyCallback = false; + m_receivedPropertyId = -1; + + mafw_gst_renderer_worker_play(m_worker, VIDEO_URI.toString().toAscii()); + + /* post xwindow-id on bus since we are using fakesink instead of + * xvimagesink */ + GstBus *bus; + GstStructure *structure; + GstMessage *message; + structure = gst_structure_new("prepare-xwindow-id", "width", + G_TYPE_INT, 64, "height", G_TYPE_INT, 32, + NULL); + message = gst_message_new_element(NULL, structure); + bus = m_worker->bus; + gst_bus_post(bus, message); + waitForEvent(WAIT_TIMEOUT, m_gotPlayCallback); + + mafw_gst_renderer_worker_pause(m_worker); + QTest::qWait(2000); + + //no pause frame yet, let's make sure + QVERIFY(m_gotPlayCallback == true); + QVERIFY(m_gotPauseCallback == true); + QVERIFY(m_gotMetadataCallback == true); + QVERIFY(m_gotErrorCallback == false); + QVERIFY(!m_receivedMetadata.contains(WORKER_METADATA_KEY_PAUSED_THUMBNAIL_URI)); + + waitForEvent(WAIT_TIMEOUT, NEVER_HAPPENING_EVENT); + QVERIFY(m_receivedMetadata.contains(WORKER_METADATA_KEY_PAUSED_THUMBNAIL_URI)); + + mafw_gst_renderer_worker_stop(m_worker); + QTest::qWait(100); + config = mafw_gst_renderer_worker_create_default_configuration(m_worker); + mafw_gst_renderer_worker_set_configuration(m_worker, config); + +} + +void ut_MafwGstRendererWorker::setAndGetPropertiesTestCase() +{ + + m_worker->notify_error_handler = &errorCallback; + m_worker->notify_property_handler = &propertyCallback; + m_worker->notify_play_handler = &playCallback; + m_worker->notify_pause_handler = &pauseCallback; + m_worker->blanking__control_handler = &blankingControlCallback; + + m_gotPropertyCallback = false; + m_receivedPropertyId = -1; + mafw_gst_renderer_worker_set_xid(m_worker, 0xffff); + QVERIFY(mafw_gst_renderer_worker_get_xid(m_worker) == 0xffff); + QVERIFY(m_gotPropertyCallback == true); + QVERIFY(m_receivedPropertyId == WORKER_PROPERTY_XID); + m_gotPropertyCallback = false; + m_receivedPropertyId = -1; + + mafw_gst_renderer_worker_set_force_aspect_ratio(m_worker, FALSE); + QVERIFY(mafw_gst_renderer_worker_get_force_aspect_ratio(m_worker) == FALSE); + QVERIFY(m_gotPropertyCallback == true); + QVERIFY(m_receivedPropertyId == WORKER_PROPERTY_FORCE_ASPECT_RATIO); + m_gotPropertyCallback = false; + m_receivedPropertyId = -1; + mafw_gst_renderer_worker_set_force_aspect_ratio(m_worker, TRUE); + QVERIFY(mafw_gst_renderer_worker_get_force_aspect_ratio(m_worker) == TRUE); + QVERIFY(m_gotPropertyCallback == true); + QVERIFY(m_receivedPropertyId == WORKER_PROPERTY_FORCE_ASPECT_RATIO); + m_gotPropertyCallback = false; + m_receivedPropertyId = -1; + + mafw_gst_renderer_worker_set_current_frame_on_pause(m_worker, TRUE); + QVERIFY(mafw_gst_renderer_worker_get_current_frame_on_pause(m_worker) == + TRUE); + QVERIFY(m_gotPropertyCallback == true); + QVERIFY(m_receivedPropertyId == WORKER_PROPERTY_CURRENT_FRAME_ON_PAUSE); + m_gotPropertyCallback = false; + m_receivedPropertyId = -1; + mafw_gst_renderer_worker_set_current_frame_on_pause(m_worker, FALSE); + QVERIFY(mafw_gst_renderer_worker_get_current_frame_on_pause(m_worker) == + FALSE); + QVERIFY(m_gotPropertyCallback == true); + QVERIFY(m_receivedPropertyId == WORKER_PROPERTY_CURRENT_FRAME_ON_PAUSE); + m_gotPropertyCallback = false; + m_receivedPropertyId = -1; + + mafw_gst_renderer_worker_set_autopaint(m_worker, TRUE); + QVERIFY(mafw_gst_renderer_worker_get_autopaint(m_worker) == TRUE); + QVERIFY(m_gotPropertyCallback == true); + QVERIFY(m_receivedPropertyId == WORKER_PROPERTY_AUTOPAINT); + m_gotPropertyCallback = false; + m_receivedPropertyId = -1; + mafw_gst_renderer_worker_set_autopaint(m_worker, FALSE); + QVERIFY(mafw_gst_renderer_worker_get_autopaint(m_worker) == FALSE); + QVERIFY(m_gotPropertyCallback == true); + QVERIFY(m_receivedPropertyId == WORKER_PROPERTY_AUTOPAINT); + + mafw_gst_renderer_worker_play(m_worker, AUDIO_URI.toString().toAscii()); + waitForEvent(WAIT_TIMEOUT, m_gotPlayCallback); + QVERIFY(m_gotPlayCallback == true); + QVERIFY(m_gotPauseCallback == false); + + mafw_gst_renderer_worker_set_playback_speed(m_worker, 2); + QVERIFY(mafw_gst_renderer_worker_get_playback_speed(m_worker) == 2); + QVERIFY(m_gotPropertyCallback == true); + QVERIFY(m_receivedPropertyId == WORKER_PROPERTY_PLAYBACK_SPEED); + m_gotPropertyCallback = false; + m_receivedPropertyId = -1; + mafw_gst_renderer_worker_set_playback_speed(m_worker, float(-1)); + QCOMPARE(mafw_gst_renderer_worker_get_playback_speed(m_worker), float(-1)); + m_gotPropertyCallback = false; + m_receivedPropertyId = -1; + mafw_gst_renderer_worker_set_playback_speed(m_worker, float(2.5)); + QCOMPARE(mafw_gst_renderer_worker_get_playback_speed(m_worker), float(2.5)); + + render_rectangle rect = {1,2,3,4}; + m_gotPropertyCallback = false; + m_receivedPropertyId = -1; + + mafw_gst_renderer_worker_set_render_rectangle(m_worker, &rect); + QVERIFY(m_gotPropertyCallback); + QVERIFY(m_receivedPropertyId != -1); + const render_rectangle *storedRect = mafw_gst_renderer_worker_get_render_rectangle(m_worker); + //the pointer value should not be used, copy must be made + QVERIFY(&rect != storedRect ); + QCOMPARE(rect.x, storedRect->x); + QCOMPARE(rect.y, storedRect->y); + QCOMPARE(rect.width, storedRect->width); + QCOMPARE(rect.height, storedRect->height); + + m_gotPropertyCallback = false; + m_receivedPropertyId = -1; + + QVERIFY(m_gotErrorCallback == false); +} + +void ut_MafwGstRendererWorker::gettersTestCase() +{ + + m_worker->notify_error_handler = &errorCallback; + + QVERIFY(mafw_gst_renderer_worker_get_colorkey(m_worker) == -1); + QVERIFY(mafw_gst_renderer_worker_get_seekable(m_worker) == TRUE); + QVERIFY(m_gotErrorCallback == false); + +} + +void ut_MafwGstRendererWorker::mediaRouteTestCase() +{ + + GSList *list = NULL; + + m_worker->notify_play_handler = &playCallback; + m_worker->notify_error_handler = &errorCallback; + m_worker->notify_eos_handler = &eosCallback; + m_worker->notify_error_handler = &errorCallback; + m_worker->blanking__control_handler = &blankingControlCallback; + mafw_gst_renderer_worker_set_xid(m_worker, 0xffff); + + /* normal audio playback */ + mafw_gst_renderer_worker_play(m_worker, AUDIO_URI.toString().toAscii()); + waitForEvent(WAIT_TIMEOUT, m_gotPlayCallback); + QVERIFY(m_gotPlayCallback == true); + QVERIFY(m_gotErrorCallback == false); + + list = g_slist_append(list, GINT_TO_POINTER(WORKER_OUTPUT_BUILTIN_SPEAKERS)); + list = g_slist_append(list, GINT_TO_POINTER(WORKER_OUTPUT_BUILTIN_DISPLAY)); + mafw_gst_renderer_worker_notify_media_destination(m_worker, list); + g_slist_free(list); + list = NULL; + QVERIFY(g_slist_length(m_worker->destinations) == 2); + QVERIFY(g_slist_find(m_worker->destinations, GINT_TO_POINTER(WORKER_OUTPUT_BUILTIN_SPEAKERS))); + QVERIFY(g_slist_find(m_worker->destinations, GINT_TO_POINTER(WORKER_OUTPUT_BUILTIN_DISPLAY))); + + waitForEvent(EOS_TIMEOUT, m_gotEosCallback); + + QVERIFY(m_gotEosCallback == true); + QVERIFY(m_gotErrorCallback == false); + + /* normal video playback */ + m_gotPlayCallback = false; + m_gotEosCallback = false; + m_gotErrorCallback = false; + QVERIFY(m_blankingProhibited == false); + mafw_gst_renderer_worker_play(m_worker, VIDEO_URI.toString().toAscii()); + m_worker->media.has_visual_content = true; // normally gst sets this but not in unittest for some reason + waitForEvent(WAIT_TIMEOUT, m_gotPlayCallback); + QVERIFY(m_gotPlayCallback == true); + QVERIFY(m_gotErrorCallback == false); + QVERIFY(m_blankingProhibited == true); + + list = g_slist_append(list, GINT_TO_POINTER(WORKER_OUTPUT_HEADPHONE_JACK)); + list = g_slist_append(list, GINT_TO_POINTER(WORKER_OUTPUT_BUILTIN_DISPLAY)); + mafw_gst_renderer_worker_notify_media_destination(m_worker, list); + g_slist_free(list); + list = NULL; + QVERIFY(g_slist_length(m_worker->destinations) == 2); + QVERIFY(g_slist_find(m_worker->destinations, GINT_TO_POINTER(WORKER_OUTPUT_HEADPHONE_JACK))); + QVERIFY(g_slist_find(m_worker->destinations, GINT_TO_POINTER(WORKER_OUTPUT_BUILTIN_DISPLAY))); + + waitForEvent(WAIT_TIMEOUT, m_gotEosCallback); + QVERIFY(m_gotEosCallback == true); + QVERIFY(m_gotErrorCallback == false); + +} + +void ut_MafwGstRendererWorker::setReadyTimeoutTestCase() +{ + + m_worker->notify_play_handler = &playCallback; + m_worker->notify_error_handler = &errorCallback; + m_worker->notify_pause_handler = &pauseCallback; + m_worker->notify_eos_handler = &eosCallback; + m_worker->blanking__control_handler = &blankingControlCallback; + + /* do some basic set calls first */ + mafw_gst_renderer_worker_set_ready_timeout(m_worker, 0); + QVERIFY(m_worker->config->seconds_to_pause_to_ready == 0); + mafw_gst_renderer_worker_set_ready_timeout(m_worker, 60); + QVERIFY(m_worker->config->seconds_to_pause_to_ready == 60); + + /* set timeout to 0, pause in the middle of the playback */ + mafw_gst_renderer_worker_play(m_worker, AUDIO_URI.toString().toAscii()); + waitForEvent(WAIT_TIMEOUT, m_gotPlayCallback); + QVERIFY(m_gotPlayCallback == true); + QVERIFY(m_gotErrorCallback == false); + + m_gotPlayCallback = false; + m_gotPauseCallback = false; + mafw_gst_renderer_worker_set_ready_timeout(m_worker, 0); + QVERIFY(m_worker->config->seconds_to_pause_to_ready == 0); + QTest::qWait(1500); + QVERIFY(m_worker->state == GST_STATE_PLAYING); + mafw_gst_renderer_worker_pause(m_worker); + waitForEvent(WAIT_TIMEOUT, m_gotPauseCallback); + QVERIFY(m_worker->state == GST_STATE_PAUSED); + QTest::qWait(500); + QVERIFY(m_worker->state == GST_STATE_READY); + + m_gotPlayCallback = false; + m_gotPauseCallback = false; + + mafw_gst_renderer_worker_resume(m_worker); + waitForEvent(WAIT_TIMEOUT, m_gotPlayCallback); + QVERIFY(m_gotPlayCallback == true); + QVERIFY(m_worker->state == GST_STATE_PLAYING); + waitForEvent(WAIT_TIMEOUT, m_gotEosCallback); + QVERIFY(m_gotEosCallback == true); + + /* set timeout to 3, pause in the middle of the playback */ + mafw_gst_renderer_worker_play(m_worker, AUDIO_URI.toString().toAscii()); + waitForEvent(WAIT_TIMEOUT, m_gotPlayCallback); + QVERIFY(m_gotPlayCallback == true); + QVERIFY(m_gotErrorCallback == false); + + m_gotPlayCallback = false; + m_gotPauseCallback = false; + mafw_gst_renderer_worker_set_ready_timeout(m_worker, 3); + QVERIFY(m_worker->config->seconds_to_pause_to_ready == 3); + QTest::qWait(1500); + QVERIFY(m_worker->state == GST_STATE_PLAYING); + mafw_gst_renderer_worker_pause(m_worker); + waitForEvent(WAIT_TIMEOUT, m_gotPauseCallback); + QVERIFY(m_worker->state == GST_STATE_PAUSED); + + m_gotPlayCallback = false; + m_gotPauseCallback = false; + QTest::qWait(4000); + QVERIFY(m_worker->state == GST_STATE_READY); + + mafw_gst_renderer_worker_resume(m_worker); + waitForEvent(WAIT_TIMEOUT, m_gotPlayCallback); + QVERIFY(m_gotPlayCallback == true); + QVERIFY(m_worker->state == GST_STATE_PLAYING); + waitForEvent(WAIT_TIMEOUT, m_gotEosCallback); + QVERIFY(m_gotEosCallback == true); + + /* set timeout to 0, pause in the middle of the playback after setting the + * timeout value --> we should go to ready immediately */ + mafw_gst_renderer_worker_play(m_worker, AUDIO_URI.toString().toAscii()); + waitForEvent(WAIT_TIMEOUT, m_gotPlayCallback); + QVERIFY(m_gotPlayCallback == true); + QVERIFY(m_gotErrorCallback == false); + mafw_gst_renderer_worker_set_ready_timeout(m_worker, 60); + QVERIFY(m_worker->config->seconds_to_pause_to_ready == 60); + + m_gotPlayCallback = false; + m_gotPauseCallback = false; + QTest::qWait(1500); + QVERIFY(m_worker->state == GST_STATE_PLAYING); + mafw_gst_renderer_worker_pause(m_worker); + waitForEvent(WAIT_TIMEOUT, m_gotPauseCallback); + QVERIFY(m_worker->state == GST_STATE_PAUSED); + mafw_gst_renderer_worker_set_ready_timeout(m_worker, 0); + QVERIFY(m_worker->config->seconds_to_pause_to_ready == 0); + QTest::qWait(500); + QVERIFY(m_worker->state == GST_STATE_READY); + + m_gotPlayCallback = false; + m_gotPauseCallback = false; + + mafw_gst_renderer_worker_resume(m_worker); + waitForEvent(WAIT_TIMEOUT, m_gotPlayCallback); + QVERIFY(m_gotPlayCallback == true); + QVERIFY(m_worker->state == GST_STATE_PLAYING); + waitForEvent(WAIT_TIMEOUT, m_gotEosCallback); + QVERIFY(m_gotEosCallback == true); + +} + +void ut_MafwGstRendererWorker::initTestCase() +{ + gst_init(0,0); +} + +void ut_MafwGstRendererWorker::init() +{ + + qDebug() << __PRETTY_FUNCTION__; + + m_worker = 0; + m_receivedErrorCode = -1; + m_receivedPropertyId = -1; + m_receivedBufferStatus = -1.0; + m_receivedMetadata.clear(); + m_gotPlayCallback = false; + m_gotEosCallback = false; + m_gotErrorCallback = false; + m_gotPauseCallback = false; + m_gotSeekCallback = false; + m_gotMetadataCallback = false; + m_gotPropertyCallback = false; + m_gotBufferStatusCallback = false; + m_blankingProhibited = false; + + global_context_duration = 0; + + bool ok; + QByteArray tmo = qgetenv("WAIT_TIMEOUT"); + int val = tmo.toInt(&ok, 10); + if (ok) + { + WAIT_TIMEOUT = val; + qDebug() << "WAIT_TIMEOUT is set to" << WAIT_TIMEOUT; + } + else + { + qDebug() << "Using default WAIT_TIMEOUT" << WAIT_TIMEOUT; + } + + const QString scheme("file"); + AUDIO_URI.setScheme(scheme); + VIDEO_URI.setScheme(scheme); + IMAGE_URI.setScheme(scheme); + + m_worker = mafw_gst_renderer_worker_new(this); + + QVERIFY(m_worker != 0); + + globalGstFactoryRequests.clear(); +} + +void ut_MafwGstRendererWorker::cleanup() +{ + QVERIFY(m_worker != 0); + + mafw_gst_renderer_worker_exit(m_worker); + g_free(m_worker); + + QVERIFY(m_blankingProhibited == false); + + m_gotPlayCallback = false; + m_gotErrorCallback = false; +} + +QTEST_MAIN(ut_MafwGstRendererWorker) diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererWorker/ut_MafwGstRendererWorker.h b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererWorker/ut_MafwGstRendererWorker.h new file mode 100644 index 0000000..df7e87d --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererWorker/ut_MafwGstRendererWorker.h @@ -0,0 +1,111 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#ifndef UT_MAFW_GST_RENDERER_WORKER_H_ +#define UT_MAFW_GST_RENDERER_WORKER_H_ + +#include +#include + +#include "mafw-gst-renderer-worker.h" + +class ut_MafwGstRendererWorker: public QObject +{ + Q_OBJECT + +private slots: // tests + + void basicPlaybackTestCase(); + void basicVideoPlaybackTestCase(); + void eosTestCase(); + void playAndPauseTestCase(); + void playAndStopTestCase(); + void setAndGetPropertiesTestCase(); + void gettersTestCase(); + void playAndPauseAndResumeTestCase(); + void pauseQuicklyAfterPlayTestCase(); + void pauseAtTestCase(); + void resumeDelayedTestCase(); + void getCurrentMetadataTestCase(); + void invalidUriTestCase(); + void seekTestCase(); + void defaultConfigurationTestCase(); + void configurabilityTestCase(); + void pauseFrameTestCase(); + void pauseFrameConfigurabilityTestCase(); + void pauseFrameCancelTestCase(); + void rendererArtTestCase(); + void bufferingTestCase(); + void mediaRouteTestCase(); + void setReadyTimeoutTestCase(); + void redirectMessageTestCase(); + + void initTestCase(); + void init(); + void cleanup(); + +protected slots: + void slotTimeOut(); + +protected: + static void playCallback(MafwGstRendererWorker *worker, gpointer owner); + static void eosCallback(MafwGstRendererWorker *worker, gpointer owner); + static void errorCallback(MafwGstRendererWorker *worker, + gpointer owner, + const GError *error); + static void pauseCallback(MafwGstRendererWorker *worker, gpointer owner); + static void propertyCallback(MafwGstRendererWorker *worker, + gpointer owner, + gint id, + GValue *value); + static void seekCallback(MafwGstRendererWorker *worker, gpointer owner); + static void metadataCallback(MafwGstRendererWorker *worker, + gpointer owner, + gint key, + GType type, + gpointer value); + static void bufferingCallback(MafwGstRendererWorker *worker, + gpointer owner, + gdouble percent); + static void blankingControlCallback(MafwGstRendererWorker *worker, + gpointer owner, gboolean prohibit); + static void screenshotCallback(MafwGstRendererWorker *worker, + gpointer owner, + GstBuffer *buffer, + const char *filename, + gboolean cancel); + + void waitForEvent(gint ms, bool &hasEventOccurred); + +private: //data + MafwGstRendererWorker *m_worker; + bool m_gotPlayCallback; + bool m_gotEosCallback; + bool m_gotErrorCallback; + bool m_gotPauseCallback; + bool m_gotSeekCallback; + bool m_gotMetadataCallback; + bool m_gotPropertyCallback; + bool m_gotBufferStatusCallback; + bool m_blankingProhibited; + int m_receivedErrorCode; + int m_receivedPropertyId; + gdouble m_receivedBufferStatus; + QList m_receivedMetadata; +}; + +#endif diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererWorker/ut_MafwGstRendererWorker.pro b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererWorker/ut_MafwGstRendererWorker.pro new file mode 100644 index 0000000..8e4a2ac --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_MafwGstRendererWorker/ut_MafwGstRendererWorker.pro @@ -0,0 +1,31 @@ +TEMPLATE = app +QT -= gui +CONFIG = console + +isEmpty(PREFIX) { + PREFIX=/usr +} + +CONFIG += qt link_pkgconfig debug +CONFIG += qtestlib +PKGCONFIG += glib-2.0 gobject-2.0 gstreamer-0.10 gstreamer-plugins-base-0.10 +PKGCONFIG += contextprovider-1.0 x11 gstreamer-tag-0.10 + +LIBS += -lgstinterfaces-0.10 -lgstpbutils-0.10 -lgcov + +DEPENDPATH += . ../../inc ../../src +INCLUDEPATH += . ../../inc + +QMAKE_CXXFLAGS += -fprofile-arcs -ftest-coverage -Wall -Werror -g +QMAKE_CFLAGS += -fprofile-arcs -ftest-coverage -Wall -Werror -g + +QMAKE_CLEAN += *.gcda *.gcno *.gcov ut_MafwGstRendererWorker *.vg.xml vgcore.* + +HEADERS += ut_MafwGstRendererWorker.h \ + mafw-gst-renderer-worker.h \ + mafw-gst-renderer-seeker.c + +SOURCES += ut_MafwGstRendererWorker.cpp \ + mafw-gst-renderer-worker.c \ + mafw-gst-renderer-seeker.c + diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_MafwMmcMonitor/ut_MafwMmcMonitor.cpp b/qmafw-gst-subtitles-renderer/unittests/ut_MafwMmcMonitor/ut_MafwMmcMonitor.cpp new file mode 100644 index 0000000..80fb7f3 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_MafwMmcMonitor/ut_MafwMmcMonitor.cpp @@ -0,0 +1,97 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#include "ut_MafwMmcMonitor.h" + +#include + +#include +#include + +#define private public // access private slots +#include "MafwMmcMonitor.h" + +extern QStringList stubMounts; +extern QStringList g_dbusConnectionParams; + +void Ut_MafwMmcMonitor::initTestCase() +{ +} + +void Ut_MafwMmcMonitor::cleanupTestCase() +{ +} + +void Ut_MafwMmcMonitor::testConstruction() +{ + QFETCH(QStringList, mounts); + QFETCH(bool, mmcMounted); + + stubMounts = mounts; + + MafwMmcMonitor monitor(this); + + QVERIFY( monitor.isMounted()==mmcMounted ); + + QCOMPARE(g_dbusConnectionParams.at(0), QString(USB_MODE_SERVICE)); + QCOMPARE(g_dbusConnectionParams.at(1), QString(USB_MODE_OBJECT)); + QCOMPARE(g_dbusConnectionParams.at(2), QString(USB_MODE_INTERFACE)); + QCOMPARE(g_dbusConnectionParams.at(3), QString(USB_MODE_SIGNAL_NAME)); + QVERIFY(g_dbusConnectionParams.at(4).toUInt() > static_cast(0)); + QVERIFY(g_dbusConnectionParams.at(5).length() > 0); + +} + +void Ut_MafwMmcMonitor::testConstruction_data() +{ + QTest::addColumn("mounts"); + QTest::addColumn("mmcMounted"); + + QTest::newRow("none") << (QStringList()) << false; + QTest::newRow("nommc") << (QStringList() << "file:///home/user/.mms/private" << "file:///home/user/.nokiamessaging/IM") << false; + QTest::newRow("mmc") << (QStringList() << "file:///home/user/MyDocs" << "file:///home/user/.nokiamessaging/IM") << true; +} + +void Ut_MafwMmcMonitor::testMounts() +{ + stubMounts.clear(); + MafwMmcMonitor monitor(this); + + QVERIFY( !monitor.isMounted() ); + + stubMounts << "file:///home/user/.mms/private" << "file:///home/user/MyDocs"; + monitor.mountEvent( 0, (GMount*)1, &monitor ); + QVERIFY( !monitor.isMounted() ); + monitor.mountEvent( 0, (GMount*)2, &monitor ); + QVERIFY( monitor.isMounted() ); + + monitor.unmountEvent( 0, (GMount*)1, &monitor ); + QVERIFY( monitor.isMounted() ); + monitor.unmountEvent( 0, (GMount*)2, &monitor ); + QVERIFY( !monitor.isMounted() ); +} + +void Ut_MafwMmcMonitor::testPreUnmount() +{ + MafwMmcMonitor monitor(this); + QSignalSpy spy( &monitor, SIGNAL( preUnmount() ) ); + + monitor.preUnmountEvent("pre-unmount"); + QVERIFY( spy.size()==1 ); +} + +QTEST_MAIN(Ut_MafwMmcMonitor) diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_MafwMmcMonitor/ut_MafwMmcMonitor.h b/qmafw-gst-subtitles-renderer/unittests/ut_MafwMmcMonitor/ut_MafwMmcMonitor.h new file mode 100644 index 0000000..9b840db --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_MafwMmcMonitor/ut_MafwMmcMonitor.h @@ -0,0 +1,44 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#ifndef UT_MAFWMMCMONITOR_H_ +#define UT_MAFWMMCMONITOR_H_ + +#include +#include +#include + +#include + + +class Ut_MafwMmcMonitor : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void cleanupTestCase(); + + void testConstruction(); + void testConstruction_data(); + + void testMounts(); + void testPreUnmount(); + +}; + +#endif /*UT_MAFWMMCMONITOR_H_*/ diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_MafwMmcMonitor/ut_MafwMmcMonitor.pro b/qmafw-gst-subtitles-renderer/unittests/ut_MafwMmcMonitor/ut_MafwMmcMonitor.pro new file mode 100644 index 0000000..ccc537f --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_MafwMmcMonitor/ut_MafwMmcMonitor.pro @@ -0,0 +1,28 @@ +TEMPLATE = app +TARGET = +QT -= gui +CONFIG = console + +INCLUDEPATH += . +MAFW_GST_INCDIR = ../../inc +#UT_COMMONDIR = ../common + +INCLUDEPATH += $$MAFW_GST_INCDIR +DEPENDPATH += . ../../inc ../../src + +CONFIG += qtestlib +CONFIG += qt qdbus link_pkgconfig debug + +PKGCONFIG += glib-2.0 gio-2.0 gio-unix-2.0 usb_moded dbus-1 + +LIBS += -lgcov +QMAKE_CXXFLAGS += -fprofile-arcs -ftest-coverage -O0 -Werror +QMAKE_CFLAGS += -fprofile-arcs -ftest-coverage -O0 -Werror + +# Input +HEADERS += MafwMmcMonitor.h \ + ut_MafwMmcMonitor.h + +SOURCES += MafwMmcMonitor.cpp \ + ut_MafwMmcMonitor.cpp \ + ut_MafwMmcMonitorStubs.cpp diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_MafwMmcMonitor/ut_MafwMmcMonitorStubs.cpp b/qmafw-gst-subtitles-renderer/unittests/ut_MafwMmcMonitor/ut_MafwMmcMonitorStubs.cpp new file mode 100644 index 0000000..507b144 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_MafwMmcMonitor/ut_MafwMmcMonitorStubs.cpp @@ -0,0 +1,111 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#include +#include +#include + +QStringList stubMounts; + +QStringList g_dbusConnectionParams; + +// GVolumeMonitor + +GVolumeMonitor *g_volume_monitor_get(void) +{ + return (GVolumeMonitor*)1; +} + +GList * g_volume_monitor_get_mounts(GVolumeMonitor *volume_monitor) +{ + return (GList*)1; +} + +// GObject + +gulong g_signal_connect_data (gpointer instance, + const gchar *detailed_signal, + GCallback c_handler, + gpointer data, + GClosureNotify destroy_data, + GConnectFlags connect_flags) +{ + return 0; +} + +void g_object_unref(gpointer object) +{ +} + +// GList + +guint g_list_length(GList *list) +{ + return stubMounts.size(); +} + +gpointer g_list_nth_data(GList *list, guint n) +{ + return (void*)(n+1); +} + +void g_list_free(GList *list) +{ +} + +// GMount + +GFile * g_mount_get_root(GMount *mount) +{ + return (GFile*)mount; +} + +// GFile + +char* g_file_get_uri(GFile *file) +{ + return g_strdup(stubMounts[(int)file-1].toAscii().constData()); +} + + +//QDBusConnection + +QDBusConnection::QDBusConnection(const QString& /*name*/) +{ +} + +QDBusConnection QDBusConnection::sessionBus() +{ + return QDBusConnection("..."); +} + +bool QDBusConnection::connect(const QString &service, + const QString &path, + const QString &interface, + const QString &name, + QObject *receiver, + const char *slot) +{ + g_dbusConnectionParams << service; + g_dbusConnectionParams << path; + g_dbusConnectionParams << interface; + g_dbusConnectionParams << name; + g_dbusConnectionParams << QString::number(reinterpret_cast(receiver)); + g_dbusConnectionParams << slot; + + return true; +} diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_Others/ut_Others.cpp b/qmafw-gst-subtitles-renderer/unittests/ut_Others/ut_Others.cpp new file mode 100644 index 0000000..a820cc1 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_Others/ut_Others.cpp @@ -0,0 +1,150 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#include + +#include +#include +#include + +#include "ut_Others.h" +#include "mafw-gst-renderer-utils.h" +#include "mafw-gst-renderer-worker.h" + +#define UNUSED(x) (void)(x) + +/* START OF STUB DEFINITIONS */ +gboolean g_utf8_validate(const gchar *str, + gssize max_len, + const gchar **end) +{ + + UNUSED(max_len); + UNUSED(end); + + if (g_str_equal("valid", str)) + { + return TRUE; + } + else + { + return FALSE; + } + +} + +gchar* g_locale_to_utf8(const gchar *opsysstring, + gssize len, + gsize *bytes_read, + gsize *bytes_written, + GError **error) +{ + + UNUSED(len); + UNUSED(bytes_read); + UNUSED(bytes_written); + + if (g_str_equal("non-valid", opsysstring)) + { + return g_strdup(opsysstring); + } + else + { + g_set_error(error, + g_quark_from_string("test"), + 100, + "%s", "SIMULATED ERROR"); + return NULL; + } +} + + +/* END OF STUB DEFINITIONS */ + +void ut_Others::testUtils() +{ + QVERIFY(uri_is_stream("file:///file.m3u") == FALSE); + QVERIFY(uri_is_stream("http:///joojoo.com") == TRUE); + QVERIFY(uri_is_stream(NULL) == FALSE); + + gchar *dst = NULL; + QVERIFY(convert_utf8(NULL, NULL) == FALSE); + QVERIFY(convert_utf8("valid", NULL) == FALSE); + QVERIFY(convert_utf8(NULL, &dst) == FALSE); + QVERIFY(convert_utf8("valid", &dst) == TRUE); + g_free(dst); + QVERIFY(convert_utf8("non-valid", &dst) == TRUE); + QVERIFY(dst); + QVERIFY(g_str_equal(dst, "non-valid") == TRUE); + g_free(dst); + QVERIFY(convert_utf8("error", &dst) == FALSE); + +} + +void ut_Others::testGstErrorRemapping() +{ + + GQuark domain = g_quark_from_string("test_error_domain"); + + GError* error = g_error_new_literal(domain, 999, "error_message"); + QVERIFY(remap_gst_error_code(error) == 999); + + error->domain = GST_RESOURCE_ERROR; + error->code = GST_RESOURCE_ERROR_READ; + QVERIFY(remap_gst_error_code(error) == WORKER_ERROR_STREAM_DISCONNECTED); + error->code = GST_RESOURCE_ERROR_NOT_FOUND; + QVERIFY(remap_gst_error_code(error) == WORKER_ERROR_MEDIA_NOT_FOUND); + error->code = GST_RESOURCE_ERROR_OPEN_READ_WRITE; + QVERIFY(remap_gst_error_code(error) == WORKER_ERROR_MEDIA_NOT_FOUND); + error->code = GST_RESOURCE_ERROR_NO_SPACE_LEFT; + QVERIFY(remap_gst_error_code(error) == WORKER_ERROR_UNABLE_TO_PERFORM); + error->code = GST_RESOURCE_ERROR_WRITE; + QVERIFY(remap_gst_error_code(error) == WORKER_ERROR_CORRUPTED_FILE); + error->code = GST_RESOURCE_ERROR_SEEK; + QVERIFY(remap_gst_error_code(error) == WORKER_ERROR_CANNOT_SET_POSITION); + error->code = 999999; + QVERIFY(remap_gst_error_code(error) == WORKER_ERROR_UNABLE_TO_PERFORM); + + error->domain = GST_STREAM_ERROR; + error->code = GST_STREAM_ERROR_TYPE_NOT_FOUND; + QVERIFY(remap_gst_error_code(error) == WORKER_ERROR_TYPE_NOT_AVAILABLE); + error->code = GST_STREAM_ERROR_FORMAT; + QVERIFY(remap_gst_error_code(error) == WORKER_ERROR_UNSUPPORTED_TYPE); + error->code = GST_STREAM_ERROR_DECODE; + QVERIFY(remap_gst_error_code(error) == WORKER_ERROR_CORRUPTED_FILE); + error->code = GST_STREAM_ERROR_CODEC_NOT_FOUND; + QVERIFY(remap_gst_error_code(error) == WORKER_ERROR_CODEC_NOT_FOUND); + error->code = GST_STREAM_ERROR_DECRYPT; + QVERIFY(remap_gst_error_code(error) == WORKER_ERROR_DRM_NOT_ALLOWED); + error->code = 999999; + QVERIFY(remap_gst_error_code(error) == WORKER_ERROR_UNABLE_TO_PERFORM); + + g_error_free(error); + +} + +void ut_Others::init() +{ + +} + +void ut_Others::cleanup() +{ + +} + +QTEST_APPLESS_MAIN(ut_Others) diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_Others/ut_Others.h b/qmafw-gst-subtitles-renderer/unittests/ut_Others/ut_Others.h new file mode 100644 index 0000000..1b42c02 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_Others/ut_Others.h @@ -0,0 +1,44 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#ifndef UT_OTHERS_H_ +#define UT_OTHERS_H_ + +#include +#include + +class ut_Others: public QObject +{ + Q_OBJECT + +private slots: // tests + + void testUtils(); + void testGstErrorRemapping(); + + void init(); + void cleanup(); + +protected slots: + +protected: + +private: //data + +}; + +#endif diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_Others/ut_Others.pro b/qmafw-gst-subtitles-renderer/unittests/ut_Others/ut_Others.pro new file mode 100644 index 0000000..e204160 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_Others/ut_Others.pro @@ -0,0 +1,26 @@ +TEMPLATE = app + +isEmpty(PREFIX) { + PREFIX=/usr +} + +CONFIG += qt link_pkgconfig release +CONFIG += qtestlib +PKGCONFIG += glib-2.0 gobject-2.0 gstreamer-0.10 + +LIBS += -lgcov + +DEPENDPATH += . ../../inc ../../src +INCLUDEPATH += . ../../inc + +QMAKE_CXXFLAGS += -fprofile-arcs -ftest-coverage -Wall -Werror -g +QMAKE_CFLAGS += -fprofile-arcs -ftest-coverage -Wall -Werror -g + +QMAKE_CLEAN += *.gcda *.gcno *.gcov ut_Others *.vg.xml vgcore.* + +HEADERS += ut_Others.h \ + mafw-gst-renderer-utils.h + +SOURCES += ut_Others.cpp \ + mafw-gst-renderer-utils.c + diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_PlaylistFileUtility/Ut_PlaylistFileUtility.cpp b/qmafw-gst-subtitles-renderer/unittests/ut_PlaylistFileUtility/Ut_PlaylistFileUtility.cpp new file mode 100644 index 0000000..46029c2 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_PlaylistFileUtility/Ut_PlaylistFileUtility.cpp @@ -0,0 +1,149 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#include +#include +#include + +#include "Ut_PlaylistFileUtility.h" +#include "../common/MafwStubHelper.h" +#include "MafwGstRendererPlaylistFileUtility.h" + +void Ut_PlaylistFileUtility::initTestCase() +{ + m_stubHelper = new MafwStubHelper; + m_utility = new MafwGstRendererPlaylistFileUtility(this); +} + +void Ut_PlaylistFileUtility::testParsing() +{ + QString file("/test-station.pls"); + file.prepend(QDir::currentPath()); + file.prepend("file://"); + + QSignalSpy readySpy(m_utility, SIGNAL(parsingReady(bool))); + QSignalSpy parsingSpy(m_utility, SIGNAL(firstItemParsed())); + m_utility->parsePlaylistFile(QUrl(file)); + QTest::qWait(1000); + + QCOMPARE(readySpy.count(), 1); + QList arguments = readySpy.takeFirst(); + QVERIFY(arguments.at(0).toBool() == true); + QCOMPARE(parsingSpy.count(), 1); + + //There is 19 entries in test-station.pls file + for (int i=0; i<19; i++) + { + QVERIFY(m_utility->takeFirstUri().length() > 0); + } + + QVERIFY(m_utility->takeFirstUri().length() == 0); +} + +void Ut_PlaylistFileUtility::testParsingForNotExistingFile() +{ + QString file("/not-existing.pls"); + file.prepend(QDir::currentPath()); + file.prepend("file://"); + QSignalSpy readySpy(m_utility, SIGNAL(parsingReady(bool))); + QSignalSpy parsingSpy(m_utility, SIGNAL(firstItemParsed())); + + m_utility->parsePlaylistFile(QUrl(file)); + QTest::qWait(1000); + QCOMPARE(readySpy.count(), 1); + QCOMPARE(parsingSpy.count(), 0); + QList arguments = readySpy.takeFirst(); + QVERIFY(arguments.at(0).toBool() == false); +} + +void Ut_PlaylistFileUtility::testParsingInvalidFileUri() +{ +//No scheme + QString file("/test-station.pls"); + file.prepend(QDir::currentPath()); + QSignalSpy parsingSpy(m_utility, SIGNAL(firstItemParsed())); + QSignalSpy readySpy(m_utility, SIGNAL(parsingReady(bool))); + m_utility->parsePlaylistFile(QUrl(file)); + QTest::qWait(1000); + QCOMPARE(parsingSpy.count(), 0); + QCOMPARE(readySpy.count(), 1); + QList arguments = readySpy.takeFirst(); + QVERIFY(arguments.at(0).toBool() == false); + +//Contains only path + QString file2(QDir::currentPath()); + file2.prepend("file://"); + m_utility->parsePlaylistFile(file2); + QTest::qWait(1000); + QCOMPARE(parsingSpy.count(), 0); + QCOMPARE(readySpy.count(), 1); + arguments = readySpy.takeFirst(); + QVERIFY(arguments.at(0).toBool() == false); +} + +void Ut_PlaylistFileUtility::testParsingInvalidFile() +{ + QString file("/test-station_invalid.pls"); + file.prepend(QDir::currentPath()); + file.prepend("file://"); + + QSignalSpy readySpy(m_utility, SIGNAL(parsingReady(bool))); + QSignalSpy parsingSpy(m_utility, SIGNAL(firstItemParsed())); + m_utility->parsePlaylistFile(QUrl(file)); + QTest::qWait(1000); + + QCOMPARE(readySpy.count(), 1); + QList arguments = readySpy.takeFirst(); + QVERIFY(arguments.at(0).toBool() == true); + QCOMPARE(parsingSpy.count(), 0); + + QVERIFY(m_utility->takeFirstUri().length() == 0); +} + +void Ut_PlaylistFileUtility::testParsingASFFile() +{ + QString file("/test.asf"); + file.prepend(QDir::currentPath()); + file.prepend("file://"); + + QSignalSpy readySpy(m_utility, SIGNAL(parsingReady(bool))); + QSignalSpy parsingSpy(m_utility, SIGNAL(firstItemParsed())); + m_utility->parsePlaylistFile(QUrl(file)); + QTest::qWait(1000); + + QCOMPARE(readySpy.count(), 1); + QList arguments = readySpy.takeFirst(); + QVERIFY(arguments.at(0).toBool() == true); + QCOMPARE(parsingSpy.count(), 1); + + for (int i=0; i < 1; i++) + { + QString test = m_utility->takeFirstUri(); + QVERIFY(test.length() > 0); + QVERIFY(test.startsWith("mmsh://195")); + QVERIFY(test.endsWith(".asf")); + } + + QVERIFY(m_utility->takeFirstUri().length() == 0); +} + +void Ut_PlaylistFileUtility::cleanupTestCase() +{ + delete m_stubHelper; +} + +QTEST_MAIN(Ut_PlaylistFileUtility) diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_PlaylistFileUtility/Ut_PlaylistFileUtility.h b/qmafw-gst-subtitles-renderer/unittests/ut_PlaylistFileUtility/Ut_PlaylistFileUtility.h new file mode 100644 index 0000000..eeaea93 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_PlaylistFileUtility/Ut_PlaylistFileUtility.h @@ -0,0 +1,46 @@ +/* + * This file is part of QMAFW + * + * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). All rights + * reserved. + * + * Contact: Visa Smolander + * + * This software, including documentation, is protected by copyright controlled + * by Nokia Corporation. All rights are reserved. Copying, including + * reproducing, storing, adapting or translating, any or all of this material + * requires the prior written consent of Nokia Corporation. This material also + * contains confidential information which may not be disclosed to others + * without the prior written consent of Nokia. + * + */ + +#ifndef UT_PLAYLISTFILEUTILITY_H +#define UT_PLAYLISTFILEUTILITY_H + +#include + +class MafwStubHelper; +class MafwGstRendererPlaylistFileUtility; + +class Ut_PlaylistFileUtility: public QObject +{ + Q_OBJECT + +private Q_SLOTS: // tests + + void initTestCase(); + void testParsing(); + void testParsingForNotExistingFile(); + void testParsingInvalidFileUri(); + void testParsingInvalidFile(); + void testParsingASFFile(); + void cleanupTestCase(); + +private: + MafwStubHelper* m_stubHelper; + MafwGstRendererPlaylistFileUtility* m_utility; + +}; + +#endif // UT_PLAYLISTFILEUTILITY_H diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_PlaylistFileUtility/Ut_PlaylistFileUtility.pro b/qmafw-gst-subtitles-renderer/unittests/ut_PlaylistFileUtility/Ut_PlaylistFileUtility.pro new file mode 100644 index 0000000..c75d957 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_PlaylistFileUtility/Ut_PlaylistFileUtility.pro @@ -0,0 +1,31 @@ +###################################################################### +# Automatically generated by qmake (2.01a) Mon Aug 23 09:32:17 2010 +###################################################################### +TEMPLATE = app +TARGET = +QT -= gui +CONFIG = console + +MAFW_GST_INCDIR = ../../inc +DEPENDPATH += . $$MAFW_GST_INCDIR ../../src +INCLUDEPATH += . $$MAFW_GST_INCDIR /usr/include/qmafw + +CONFIG += qtestlib no_keywords +CONFIG += qt link_pkgconfig debug +PKGCONFIG += qmafw glib-2.0 gobject-2.0 +PKGCONFIG += contextprovider-1.0 x11 libpulse-mainloop-glib contextsubscriber-1.0 +LIBS += -lgcov -ltotem-plparser + +QMAKE_CXXFLAGS +=-fprofile-arcs -ftest-coverage -O0 -Wall -Werror +QMAKE_CFLAGS +=-fprofile-arcs -ftest-coverage -O0 -Wall -Werror + +# Input +HEADERS += Ut_PlaylistFileUtility.h \ + ../common/MafwStubHelper.h \ + MafwGstRendererPlaylistFileUtility.h + +SOURCES += Ut_PlaylistFileUtility.cpp \ + ../common/MafwStubHelper.cpp \ + MafwGstRendererPlaylistFileUtility.cpp \ + #totemParserStub.c + diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_PlaylistFileUtility/test-station.pls b/qmafw-gst-subtitles-renderer/unittests/ut_PlaylistFileUtility/test-station.pls new file mode 100644 index 0000000..572e215 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_PlaylistFileUtility/test-station.pls @@ -0,0 +1,60 @@ +[playlist] +numberofentries=19 +File1=http://188.165.211.147:8002 +Title1=(#1 - 7/19) RMF FM +Length1=-1 +File2=http://188.165.12.66:8006 +Title2=(#2 - 524/950) RMF FM +Length1=-1 +File3=http://91.121.124.91:8002 +Title3=(#3 - 543/950) RMF FM +Length1=-1 +File4=http://188.165.12.67:8004 +Title4=(#4 - 547/950) RMF FM +Length1=-1 +File5=http://188.165.12.67:8000 +Title5=(#5 - 554/950) RMF FM +Length1=-1 +File6=http://188.165.12.66:8002 +Title6=(#6 - 562/950) RMF FM +Length1=-1 +File7=http://188.165.12.66:8000 +Title7=(#7 - 573/950) RMF FM +Length1=-1 +File8=http://188.165.12.66:8004 +Title8=(#8 - 596/950) RMF FM +Length1=-1 +File9=http://91.121.125.91:8000 +Title9=(#9 - 614/950) RMF FM +Length1=-1 +File10=http://188.165.12.67:8002 +Title10=(#10 - 647/950) RMF FM +Length1=-1 +File11=http://188.165.12.66:8008 +Title11=(#11 - 655/950) RMF FM +Length1=-1 +File12=http://188.165.12.67:8006 +Title12=(#12 - 658/950) RMF FM +Length1=-1 +File13=http://91.121.125.91:8002 +Title13=(#13 - 670/950) RMF FM +Length1=-1 +File14=http://91.121.124.91:8000 +Title14=(#14 - 714/950) RMF FM +Length1=-1 +File15=http://87.98.222.167:8000 +Title15=(#15 - 779/950) RMF FM +Length1=-1 +File16=http://87.98.223.167:8000 +Title16=(#16 - 869/950) RMF FM +Length1=-1 +File17=http://188.165.12.67:8008 +Title17=(#17 - 887/950) RMF FM +Length1=-1 +File18=http://87.98.222.167:8002 +Title18=(#18 - 896/950) RMF FM +Length1=-1 +File19=http://87.98.223.167:8002 +Title19=(#19 - 901/950) RMF FM +Length1=-1 +Version=2 diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_PlaylistFileUtility/test-station_invalid.pls b/qmafw-gst-subtitles-renderer/unittests/ut_PlaylistFileUtility/test-station_invalid.pls new file mode 100644 index 0000000..ae759ad --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_PlaylistFileUtility/test-station_invalid.pls @@ -0,0 +1,3 @@ +[playlist] +numberofentries=19 +Version=2 diff --git a/qmafw-gst-subtitles-renderer/unittests/ut_PlaylistFileUtility/test.asf b/qmafw-gst-subtitles-renderer/unittests/ut_PlaylistFileUtility/test.asf new file mode 100644 index 0000000..3d20a2c --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/ut_PlaylistFileUtility/test.asf @@ -0,0 +1,3 @@ +[Reference] +Ref1=http://195.134.228.177:443/data/ByContainer/Video/ASF/VC1/CIF/WMA/ASF_VC-1_CIF_29.7FPS_356Kbps_WMA8_44.1KHz_64Kbps_60sec(3Mb)_BBB.asf +Ref2=http://195.134.228.177:443/data/ByContainer/Video/ASF/VC1/CIF/WMA/ASF_VC-1_CIF_29.7FPS_356Kbps_WMA8_44.1KHz_64Kbps_60sec(3Mb)_BBB.asf diff --git a/qmafw-gst-subtitles-renderer/unittests/valgrind.xsl b/qmafw-gst-subtitles-renderer/unittests/valgrind.xsl new file mode 100644 index 0000000..543816f --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/valgrind.xsl @@ -0,0 +1,80 @@ + + + + + +

Executed Test Programs

+ + +

+ + +

Errors

+ + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+

+ +

Stack frames

+ +
+ call stack (): + + + + + + + + + + + + + + + + + + + + + + + + +
functionfilelinedirectory
+

+ + + + (call stack): + + diff --git a/qmafw-gst-subtitles-renderer/unittests/valgrind_report.sh b/qmafw-gst-subtitles-renderer/unittests/valgrind_report.sh new file mode 100755 index 0000000..9f71d00 --- /dev/null +++ b/qmafw-gst-subtitles-renderer/unittests/valgrind_report.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +valgrind_results_file=valgrind.results + +files=`find . -name "*.vg.xml"` + +rm -f $valgrind_results_file + +if [ -z "$files" ] +then + echo "Valgrind results not generated." + exit 0 +fi + +echo '' > $valgrind_results_file +echo '' >> $valgrind_results_file +for file in $files; do + grep -v \<\/\valgrindoutput\> $file | tail --quiet --lines=+2 >> $valgrind_results_file; + echo '' >> $valgrind_results_file; +done +echo '' >> $valgrind_results_file -- 1.7.9.5