[kde-doc-english] [kwave] /: implemented playback via Qt Multimedia

Thomas Eschenbacher Thomas.Eschenbacher at gmx.de
Fri Jan 1 17:54:17 UTC 2016


Git commit f83d0a8413ba20a66b54c0154a69ae0f894b8d98 by Thomas Eschenbacher.
Committed on 01/01/2016 at 17:45.
Pushed by eschenbacher into branch 'master'.

implemented playback via Qt Multimedia

M  +1    -1    .kdev4/kwave.kdev4
M  +1    -0    CHANGES
M  +9    -0    CMakeLists.txt
M  +2    -0    LICENSES
M  +3    -0    config.h.cmake
M  +6    -0    doc/en/index.docbook
M  +1    -0    doxy.cfg.in
M  +1    -0    kwave.ebuild.in
M  +1    -1    kwave/FileContext.cpp
M  +5    -0    kwave/FileContext.h
M  +1    -0    libkwave/PlayBackParam.h
M  +5    -0    libkwave/PlayBackTypesMap.cpp
M  +9    -5    libkwave/PlaybackController.cpp
M  +5    -0    plugins/playback/CMakeLists.txt
M  +1    -1    plugins/playback/PlayBack-ALSA.cpp
M  +0    -1    plugins/playback/PlayBack-PulseAudio.cpp
M  +0    -1    plugins/playback/PlayBack-PulseAudio.h
A  +589  -0    plugins/playback/PlayBack-Qt.cpp     [License: GPL (v2+)]
A  +248  -0    plugins/playback/PlayBack-Qt.h     [License: GPL (v2+)]
M  +10   -0    plugins/playback/PlayBackPlugin.cpp

http://commits.kde.org/kwave/f83d0a8413ba20a66b54c0154a69ae0f894b8d98

diff --git a/.kdev4/kwave.kdev4 b/.kdev4/kwave.kdev4
index ae69129..ff13b22 100644
--- a/.kdev4/kwave.kdev4
+++ b/.kdev4/kwave.kdev4
@@ -21,7 +21,7 @@ Install Directory=file:///usr
 
 [CustomDefinesAndIncludes][ProjectPath0]
 Defines=\x00\x00\x00\x02\x00\x00\x00\x18\x00_\x00_\x00F\x00U\x00N\x00C\x00T\x00I\x00O\x00N\x00_\x00_\x00\x00\x00\n\x00\x00\x00\x00\x18\x00_\x00_\x00F\x00U\x00N\x00C\x00T\x00I\x00O\x00N\x00_\x00_\x00\x00\x00\x10\x00_\x00_\x00L\x00I\x00N\x00E\x00_\x00_\x00\x00\x00\n\x00\x00\x00\x00\x10\x00_\x00_\x00L\x00I\x00N\x00E\x00_\x00_
-Includes=\x00\x00\x00!\x00\x00\x00"\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00\x00\x00.\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00G\x00e\x00n\x00t\x00o\x00o\x00\x00\x00:\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00C\x00o\x00n\x00c\x00u\x00r\x00r\x00e\x00n\x00t\x00\x00\x00.\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00C\x00o\x00r\x00e\x00\x00\x00.\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00D\x00B\x00u\x00s\x00\x00\x00<\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00D\x00e\x00c\x00l\x00a\x00r\x00a\x00t\x00i\x00v\x00e\x00\x00\x006\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00D\x00e\x00s\x00i\x00g\x00n\x00e\x00r\x00\x00\x00J\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00D\x00e\x00s\x00i\x00g\x00n\x00e\x00r\x00C\x00o\x00m\x00p\x00o\x00n\x00e\x00n\x00t\x00s\x00\x00\x00,\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00G\x00u\x00i\x00\x00\x004\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00N\x00e\x00t\x00w\x00o\x00r\x00k\x00\x00\x002\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00O\x00p\x00e\x00n\x00G\x00L\x00\x00\x00F\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00O\x00p\x00e\x00n\x00G\x00L\x00E\x00x\x00t\x00e\x00n\x00s\x00i\x00o\x00n\x00s\x00\x00\x00D\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00P\x00l\x00a\x00t\x00f\x00o\x00r\x00m\x00H\x00e\x00a\x00d\x00e\x00r\x00s\x00\x00\x00D\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00P\x00l\x00a\x00t\x00f\x00o\x00r\x00m\x00S\x00u\x00p\x00p\x00o\x00r\x00t\x00\x00\x00>\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00P\x00r\x00i\x00n\x00t\x00S\x00u\x00p\x00p\x00o\x00r\x00t\x00\x00\x00,\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00Q\x00m\x00l\x00\x00\x000\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00Q\x00u\x00i\x00c\x00k\x00\x00\x00B\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00Q\x00u\x00i\x00c\x00k\x00P\x00a\x00r\x00t\x00i\x00c\x00l\x00e\x00s\x00\x00\x008\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00Q\x00u\x00i\x00c\x00k\x00T\x00e\x00s\x00t\x00\x00\x00>\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00Q\x00u\x00i\x00c\x00k\x00W\x00i\x00d\x00g\x00e\x00t\x00s\x00\x00\x002\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00S\x00c\x00r\x00i\x00p\x00t\x00\x00\x00,\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00S\x00q\x00l\x00\x00\x00,\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00S\x00v\x00g\x00\x00\x00.\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00T\x00e\x00s\x00t\x00\x00\x004\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00U\x00i\x00T\x00o\x00o\x00l\x00s\x00\x00\x002\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00W\x00e\x00b\x00K\x00i\x00t\x00\x00\x00@\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00W\x00e\x00b\x00K\x00i\x00t\x00W\x00i\x00d\x00g\x00e\x00t\x00s\x00\x00\x004\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00W\x00i\x00d\x00g\x00e\x00t\x00s\x00\x00\x008\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00X\x001\x001\x00E\x00x\x00t\x00r\x00a\x00s\x00\x00\x00,\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00X\x00m\x00l\x00\x00\x00<\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00X\x00m\x00l\x00P\x00a\x00t\x00t\x00e\x00r\x00n\x00s\x00\x00\x00.\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00K\x00F\x005\x00/\x00K\x00I\x001\x008\x00n\x00/\x00\x00\x00@\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00K\x00F\x005\x00/\x00K\x00W\x00i\x00d\x00g\x00e\x00t\x00s\x00A\x00d\x00d\x00o\x00n\x00s\x00/
+Includes=\x00\x00\x00#\x00\x00\x00"\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00\x00\x00.\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00G\x00e\x00n\x00t\x00o\x00o\x00\x00\x00:\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00C\x00o\x00n\x00c\x00u\x00r\x00r\x00e\x00n\x00t\x00\x00\x00.\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00C\x00o\x00r\x00e\x00\x00\x00.\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00D\x00B\x00u\x00s\x00\x00\x00<\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00D\x00e\x00c\x00l\x00a\x00r\x00a\x00t\x00i\x00v\x00e\x00\x00\x006\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00D\x00e\x00s\x00i\x00g\x00n\x00e\x00r\x00\x00\x00J\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00D\x00e\x00s\x00i\x00g\x00n\x00e\x00r\x00C\x00o\x00m\x00p\x00o\x00n\x00e\x00n\x00t\x00s\x00\x00\x00,\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00G\x00u\x00i\x00\x00\x004\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00N\x00e\x00t\x00w\x00o\x00r\x00k\x00\x00\x002\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00O\x00p\x00e\x00n\x00G\x00L\x00\x00\x00F\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00O\x00p\x00e\x00n\x00G\x00L\x00E\x00x\x00t\x00e\x00n\x00s\x00i\x00o\x00n\x00s\x00\x00\x00D\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00P\x00l\x00a\x00t\x00f\x00o\x00r\x00m\x00H\x00e\x00a\x00d\x00e\x00r\x00s\x00\x00\x00D\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00P\x00l\x00a\x00t\x00f\x00o\x00r\x00m\x00S\x00u\x00p\x00p\x00o\x00r\x00t\x00\x00\x00>\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00P\x00r\x00i\x00n\x00t\x00S\x00u\x00p\x00p\x00o\x00r\x00t\x00\x00\x00,\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00Q\x00m\x00l\x00\x00\x000\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00Q\x00u\x00i\x00c\x00k\x00\x00\x00B\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00Q\x00u\x00i\x00c\x00k\x00P\x00a\x00r\x00t\x00i\x00c\x00l\x00e\x00s\x00\x00\x008\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00Q\x00u\x00i\x00c\x00k\x00T\x00e\x00s\x00t\x00\x00\x00>\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00Q\x00u\x00i\x00c\x00k\x00W\x00i\x00d\x00g\x00e\x00t\x00s\x00\x00\x002\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00S\x00c\x00r\x00i\x00p\x00t\x00\x00\x00,\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00S\x00q\x00l\x00\x00\x00,\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00S\x00v\x00g\x00\x00\x00.\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00T\x00e\x00s\x00t\x00\x00\x004\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00U\x00i\x00T\x00o\x00o\x00l\x00s\x00\x00\x002\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00W\x00e\x00b\x00K\x00i\x00t\x00\x00\x00@\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00W\x00e\x00b\x00K\x00i\x00t\x00W\x00i\x00d\x00g\x00e\x00t\x00s\x00\x00\x004\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00W\x00i\x00d\x00g\x00e\x00t\x00s\x00\x00\x008\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00X\x001\x001\x00E\x00x\x00t\x00r\x00a\x00s\x00\x00\x00,\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00X\x00m\x00l\x00\x00\x00<\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00X\x00m\x00l\x00P\x00a\x00t\x00t\x00e\x00r\x00n\x00s\x00\x00\x00.\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00K\x00F\x005\x00/\x00K\x00I\x001\x008\x00n\x00/\x00\x00\x00@\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00K\x00F\x005\x00/\x00K\x00W\x00i\x00d\x00g\x00e\x00t\x00s\x00A\x00d\x00d\x00o\x00n\x00s\x00/\x00\x00\x00<\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00M\x00u\x00l\x00t\x00i\x00m\x00e\x00d\x00i\x00a\x00/\x00\x00\x00J\x00/\x00u\x00s\x00r\x00/\x00i\x00n\x00c\x00l\x00u\x00d\x00e\x00/\x00q\x00t\x005\x00/\x00Q\x00t\x00M\x00u\x00l\x00t\x00i\x00m\x00e\x00d\x00i\x00a\x00W\x00i\x00d\x00g\x00e\x00t\x00s\x00/
 Path=.
 
 [Defines And Includes][Compiler]
diff --git a/CHANGES b/CHANGES
index 3854793..dc7f81e 100644
--- a/CHANGES
+++ b/CHANGES
@@ -2,6 +2,7 @@
 0.9.x [2015-xx-xx]
 
  * ported to KDE Frameworks 5 (KF5) / Qt5
+ * playback via Qt Multimedia Audio
  * bugfix: saved plugin parameter lists with escaped characters were not
    unescaped when loading again
  * compile fix for armv7l
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d870a00..b176ce6 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -34,6 +34,7 @@ PROJECT(kwave)
 # OPTION(WITH_OSS        "enable playback/recording via OSS [default=on]" ON)
 # OPTION(WITH_OPTIMIZED_MEMCPY "enable optimized memcpy [default=on]" ON)
 # OPTION(WITH_PULSEAUDIO "enable playback/recording via PulseAudio [default=on]" ON)
+# OPTION(WITH_QT_AUDIO   "enable playback via Qt Multimedia [default=on]" ON)
 
 #############################################################################
 ### toplevel build targets:                                               ###
@@ -180,9 +181,17 @@ SET(CMAKE_AUTOMOC_RELAXED_MODE FALSE)
 FIND_PACKAGE(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS
     Concurrent
     Core
+    Multimedia
     Widgets
 )
 
+# Qt Multimedia support
+OPTION(WITH_QT_AUDIO "enable playback via Qt Multimedia [default=on]" ON)
+IF (WITH_QT_AUDIO)
+    SET(HAVE_QT_AUDIO_SUPPORT on)
+ENDIF (WITH_QT_AUDIO)
+
+
 #############################################################################
 ### KF5 support                                                           ###
 
diff --git a/LICENSES b/LICENSES
index 32bd664..ad239e8 100644
--- a/LICENSES
+++ b/LICENSES
@@ -724,6 +724,8 @@ COMPLETE LIST OF FILES AND THEIR LICENSE
 	plugins/playback/PlayBack-OSS.h			GPL2+
 	plugins/playback/PlayBack-PulseAudio.cpp	GPL2+
 	plugins/playback/PlayBack-PulseAudio.h		GPL2+
+	plugins/playback/PlayBack-Qt.cpp		GPL2+
+	plugins/playback/PlayBack-Qt.h			GPL2+
 	plugins/playback/PlayBackPlugin.cpp		GPL2+
 	plugins/playback/PlayBackPlugin.h		GPL2+
 
diff --git a/config.h.cmake b/config.h.cmake
index c1796cb..95505bc 100644
--- a/config.h.cmake
+++ b/config.h.cmake
@@ -39,6 +39,9 @@
 /* support playback/recording via PulseAudio */
 #cmakedefine HAVE_PULSEAUDIO_SUPPORT
 
+/* support playback via Qt */
+#cmakedefine HAVE_QT_AUDIO_SUPPORT
+
 /* support libsamplerate */
 #cmakedefine HAVE_LIBSAMPLERATE
 
diff --git a/doc/en/index.docbook b/doc/en/index.docbook
index f85344c..a9d0348 100644
--- a/doc/en/index.docbook
+++ b/doc/en/index.docbook
@@ -770,6 +770,12 @@
 		    [<literal>on</literal>/<literal>off</literal>,
 		    default=<literal>on</literal>]
 		</para></listitem>
+		<listitem><para>
+		    <literal>WITH_QT_AUDIO</literal>
+		    enable playback via Qt Multimedia
+		    [<literal>on</literal>/<literal>off</literal>,
+		    default=<literal>on</literal>]
+		</para></listitem>
 	    </itemizedlist>
 	</para>
 
diff --git a/doxy.cfg.in b/doxy.cfg.in
index caf41b8..3a7f496 100644
--- a/doxy.cfg.in
+++ b/doxy.cfg.in
@@ -1955,6 +1955,7 @@ PREDEFINED             = DEBUG \
                          HAVE_OSS_SUPPORT \
                          HAVE_OGG_OPUS \
                          HAVE_OGG_VORBIS \
+                         HAVE_QT_AUDIO_SUPPORT \
                          HAVE_SYSCONF \
                          HAVE_SYSINFO \
                          HAVE_SYSINFO_MEMUNIT
diff --git a/kwave.ebuild.in b/kwave.ebuild.in
index 433c7ca..f747725 100644
--- a/kwave.ebuild.in
+++ b/kwave.ebuild.in
@@ -61,6 +61,7 @@ DEPEND="${RDEPEND}
         dev-qt/qtconcurrent:5
         dev-qt/qtcore:5
         dev-qt/qtgui:5
+        dev-qt/qtmultimedia:5
         dev-qt/qtwidgets:5
 "
 
diff --git a/kwave/FileContext.cpp b/kwave/FileContext.cpp
index 0ff33ef..ac7a33b 100644
--- a/kwave/FileContext.cpp
+++ b/kwave/FileContext.cpp
@@ -20,11 +20,11 @@
 #include <errno.h>
 #include <new>
 
+#include <QApplication>
 #include <QFile>
 #include <QLocale>
 #include <QPointer>
 #include <QTextStream>
-#include <QApplication>
 #include <QMdiSubWindow>
 #include <QStandardPaths>
 
diff --git a/kwave/FileContext.h b/kwave/FileContext.h
index d84c15e..fc51f95 100644
--- a/kwave/FileContext.h
+++ b/kwave/FileContext.h
@@ -173,6 +173,11 @@ namespace Kwave
 	 */
 	bool closeFile();
 
+    protected:
+	friend class App;
+	friend class TopWidget;
+	friend class UsageGuard;
+
 	/**
 	 * increments the usage count of this context, prevents it from
 	 * being deleted
diff --git a/libkwave/PlayBackParam.h b/libkwave/PlayBackParam.h
index afa59fb..dc3c3ef 100644
--- a/libkwave/PlayBackParam.h
+++ b/libkwave/PlayBackParam.h
@@ -31,6 +31,7 @@ namespace Kwave
     typedef enum {
 	PLAYBACK_NONE = 0,   /**< none selected */
 	PLAYBACK_JACK,       /**< Jack sound daemon */
+	PLAYBACK_QT_AUDIO,   /**< Qt Multimedia */
 	PLAYBACK_PULSEAUDIO, /**< PulseAudio Sound Server */
 	PLAYBACK_ALSA,       /**< ALSA native */
 	PLAYBACK_OSS,        /**< OSS native or ALSA OSS emulation */
diff --git a/libkwave/PlayBackTypesMap.cpp b/libkwave/PlayBackTypesMap.cpp
index ae14cec..99ae2be 100644
--- a/libkwave/PlayBackTypesMap.cpp
+++ b/libkwave/PlayBackTypesMap.cpp
@@ -44,6 +44,11 @@ void Kwave::PlayBackTypesMap::fill()
 	   _(I18N_NOOP("Pulse Audio")) );
 #endif /* HAVE_PULSEAUDIO_SUPPORT */
 
+#ifdef HAVE_QT_AUDIO_SUPPORT
+    append(index++, Kwave::PLAYBACK_QT_AUDIO, _("qt_audio"),
+	   _(I18N_NOOP("Qt Multimedia Audio")) );
+#endif /* HAVE_QT_AUDIO_SUPPORT */
+
     if (!index) qWarning("no playback method defined!");
 }
 
diff --git a/libkwave/PlaybackController.cpp b/libkwave/PlaybackController.cpp
index 7c63e08..1d61d7d 100644
--- a/libkwave/PlaybackController.cpp
+++ b/libkwave/PlaybackController.cpp
@@ -541,11 +541,15 @@ void Kwave::PlaybackController::run_wrapper(const QVariant &params)
 //***************************************************************************
 void Kwave::PlaybackController::closeDevice()
 {
-    QMutexLocker lock_for_delete(&m_lock_device);
-
-    if (!m_device) return; // already closed
-    delete m_device;
-    m_device = 0;
+    Kwave::PlayBackDevice *dev = 0;
+    if (m_device) {
+	// NOTE: we could get a recursion here if we delete with the lock
+	//       held, if the device calls processEvents during shutdown
+	QMutexLocker lock_for_delete(&m_lock_device);
+	dev = m_device;
+	m_device = 0;
+    }
+    delete dev;
 }
 
 //***************************************************************************
diff --git a/plugins/playback/CMakeLists.txt b/plugins/playback/CMakeLists.txt
index b23de45..d7b5787 100644
--- a/plugins/playback/CMakeLists.txt
+++ b/plugins/playback/CMakeLists.txt
@@ -34,6 +34,11 @@ IF (HAVE_PULSEAUDIO_SUPPORT)
     ENDIF (NOT HAVE_POLL_H)
 ENDIF (HAVE_PULSEAUDIO_SUPPORT)
 
+IF (HAVE_QT_AUDIO_SUPPORT)
+    SET(PLAYBACK_SOURCES ${PLAYBACK_SOURCES} PlayBack-Qt.cpp)
+    SET(PLAYBACK_REQUIRED_LIBS ${PLAYBACK_REQUIRED_LIBS} Qt5::Multimedia)
+ENDIF (HAVE_QT_AUDIO_SUPPORT)
+
 SET(plugin_playback_LIB_SRCS
     PlayBackDialog.cpp
     PlayBackPlugin.cpp
diff --git a/plugins/playback/PlayBack-ALSA.cpp b/plugins/playback/PlayBack-ALSA.cpp
index 5b9ed64..de8d575 100644
--- a/plugins/playback/PlayBack-ALSA.cpp
+++ b/plugins/playback/PlayBack-ALSA.cpp
@@ -554,7 +554,7 @@ QString Kwave::PlayBackALSA::open(const QString &device, double rate,
                                   unsigned int channels, unsigned int bits,
                                   unsigned int bufbase)
 {
-    qDebug("PlayBackALSA::open(device=%s,rate=%0.1f,channels=%u, bits=%u, "
+    qDebug("PlayBackALSA::open(device=%s, rate=%0.1f, channels=%u, bits=%u, "
            "bufbase=%u)", DBG(device), rate, channels, bits, bufbase);
 
     m_device_name = device;
diff --git a/plugins/playback/PlayBack-PulseAudio.cpp b/plugins/playback/PlayBack-PulseAudio.cpp
index fd2f536..850656a 100644
--- a/plugins/playback/PlayBack-PulseAudio.cpp
+++ b/plugins/playback/PlayBack-PulseAudio.cpp
@@ -39,7 +39,6 @@
 #include <KUser>
 
 #include "libkwave/FileInfo.h"
-#include "libkwave/SampleEncoderLinear.h"
 #include "libkwave/String.h"
 #include "libkwave/Utils.h"
 #include "libkwave/memcpy.h"
diff --git a/plugins/playback/PlayBack-PulseAudio.h b/plugins/playback/PlayBack-PulseAudio.h
index b7b8901..5939764 100644
--- a/plugins/playback/PlayBack-PulseAudio.h
+++ b/plugins/playback/PlayBack-PulseAudio.h
@@ -34,7 +34,6 @@
 #include <QList>
 #include <QMap>
 #include <QMutex>
-#include <QSemaphore>
 #include <QString>
 #include <QWaitCondition>
 
diff --git a/plugins/playback/PlayBack-Qt.cpp b/plugins/playback/PlayBack-Qt.cpp
new file mode 100644
index 0000000..b5e639a
--- /dev/null
+++ b/plugins/playback/PlayBack-Qt.cpp
@@ -0,0 +1,589 @@
+/***************************************************************************
+        PlayBack-Qt.cpp  -  playback device for Qt Multimedia
+                             -------------------
+    begin                : Thu Nov 12 2015
+    copyright            : (C) 2015 by Thomas Eschenbacher
+    email                : Thomas.Eschenbacher at gmx.de
+ ***************************************************************************/
+
+/***************************************************************************
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *                                                                         *
+ ***************************************************************************/
+
+#include "config.h"
+#ifdef HAVE_QT_AUDIO_SUPPORT
+
+#include <errno.h>
+#include <algorithm>
+#include <limits>
+
+#include <QApplication>
+#include <QAudioDeviceInfo>
+#include <QAudioFormat>
+#include <QAudioOutput>
+#include <QObject>
+#include <QSysInfo>
+
+#include <KLocalizedString>
+
+#include "libkwave/SampleEncoderLinear.h"
+#include "libkwave/String.h"
+#include "libkwave/Utils.h"
+
+#include "PlayBack-Qt.h"
+
+/** gui name of the default device */
+#define DEFAULT_DEVICE (i18n("Default device") + _("|sound_note"))
+
+//***************************************************************************
+Kwave::PlayBackQt::PlayBackQt()
+    :QObject(), Kwave::PlayBackDevice(),
+     m_lock(QMutex::Recursive),
+     m_device_name_map(),
+     m_available_devices(),
+     m_output(0),
+     m_buffer_size(0),
+     m_encoder(0)
+{
+}
+
+//***************************************************************************
+Kwave::PlayBackQt::~PlayBackQt()
+{
+    close();
+}
+
+//***************************************************************************
+void Kwave::PlayBackQt::createEncoder(const QAudioFormat &format)
+{
+    // discard the old encoder
+    delete m_encoder;
+    m_encoder = 0;
+
+    // get the sample format
+    Kwave::SampleFormat::Format sample_format = Kwave::SampleFormat::Unknown;
+    switch (format.sampleType()) {
+	case QAudioFormat::SignedInt:
+	    sample_format = Kwave::SampleFormat::Signed;
+	    break;
+	case QAudioFormat::UnSignedInt:
+	    sample_format = Kwave::SampleFormat::Unsigned;
+	    break;
+	default:
+	    sample_format = Kwave::SampleFormat::Unknown;
+	    break;
+    }
+    if (sample_format == Kwave::SampleFormat::Unknown) {
+	qWarning("PlayBackQt: unsupported sample format %d",
+	         static_cast<int>(format.sampleType()));
+	return;
+    }
+
+    unsigned int bits = 0;
+    switch (format.sampleSize()) {
+	case  8: bits =  8; break;
+	case 16: bits = 16; break;
+	case 24: bits = 24; break;
+	case 32: bits = 32; break;
+	default: bits =  0; break;
+    }
+    if (bits == 0) {
+	qWarning("PlayBackQt: unsupported bits per sample: %d",
+		 static_cast<int>(format.sampleSize()));
+	return;
+    }
+
+    Kwave::byte_order_t endian = Kwave::UnknownEndian;
+    switch (format.byteOrder()) {
+	case QAudioFormat::BigEndian:    endian = Kwave::BigEndian;     break;
+	case QAudioFormat::LittleEndian: endian = Kwave::LittleEndian;  break;
+	default:                         endian = Kwave::UnknownEndian; break;
+    }
+    if (endian == Kwave::UnknownEndian) {
+	qWarning("PlayBackQt: unsupported byte order in audio format: %d",
+	         static_cast<int>(format.byteOrder()));
+	return;
+    }
+
+    // create the sample encoder
+    m_encoder = new Kwave::SampleEncoderLinear(sample_format, bits, endian);
+}
+
+//***************************************************************************
+QString Kwave::PlayBackQt::open(const QString &device, double rate,
+                                unsigned int channels, unsigned int bits,
+                                unsigned int bufbase)
+{
+    qDebug("PlayBackQt::open(device='%s', rate=%0.1f,channels=%u, bits=%u, "
+           "bufbase=%u)", DBG(device), rate, channels, bits, bufbase);
+
+    if ((rate < 1.0f)  || !channels || !bits || !bufbase)
+	return i18n("One or more invalid/out of range arguments.");
+
+    // close the previous device
+    close();
+
+    QMutexLocker _lock(&m_lock); // context: main thread
+
+    // make sure we have a valid list of devices
+    scanDevices();
+
+    const QAudioDeviceInfo info(deviceInfo(device));
+    if (info.isNull()) {
+	return i18n("The audio device '%1' is unknown or no longer connected",
+	            device.section(QLatin1Char('|'), 0, 0));
+    }
+
+    // find a supported sample format
+    const QAudioFormat preferred_format(info.preferredFormat());
+    QAudioFormat format(preferred_format);
+    format.setSampleSize(Kwave::toInt(bits));
+    format.setChannelCount(Kwave::toInt(channels));
+    format.setSampleRate(Kwave::toInt(rate));
+
+    // find a replacement format with matching codec, channels, bits and rate
+    if (!format.isValid() || !info.isFormatSupported(format))
+	format = info.nearestFormat(format);
+
+    if (format.codec() != _("audio/pcm"))
+	return i18n("PCM playback is not supported");
+
+    if (format.sampleSize() != Kwave::toInt(bits))
+	return i18n("%1 bits per sample are not supported", bits);
+
+    if (format.channelCount() != Kwave::toInt(channels))
+	return i18n("%1 channels playback is not supported", channels);
+
+    if (format.sampleRate() != Kwave::toInt(rate))
+	return i18n("Playback rate %1 Hz is not supported", Kwave::toInt(rate));
+
+    if ( (format.sampleType() != QAudioFormat::SignedInt) &&
+	 (format.sampleType() != QAudioFormat::UnSignedInt) )
+	return i18n("Integer sample format is not supported");
+
+    // create a sample encoder
+    createEncoder(format);
+    Q_ASSERT(m_encoder);
+    if (!m_encoder) return i18n("Out of memory");
+
+    // create a new Qt output device
+    m_output = new QAudioOutput(format, this);
+    Q_ASSERT(m_output);
+    if (!m_output) return i18n("Out of memory");
+
+    // connect the state machine and the notification engine
+    connect(m_output, SIGNAL(stateChanged(QAudio::State)),
+            this,     SLOT(stateChanged(QAudio::State)));
+
+    // calculate the buffer size in bytes
+    if (bufbase < 8)
+	bufbase = 8;
+    m_buffer_size = (1U << bufbase);
+    qDebug("    buffer size = %u", m_buffer_size);
+
+    // in the rare case that out backend already gives us a period size,
+    // check out buffer size against it
+    m_buffer_size = qMax(m_buffer_size, Kwave::toUint(m_output->periodSize()));
+
+    m_buffer.start(m_buffer_size, 0);
+
+    // open the output device for writing
+    m_output->start(&m_buffer);
+
+    // calculate an appropriate timeout, based on the period and buffer sizes
+    const int period_size = m_output->periodSize();
+    qDebug("    period_size = %d", period_size);
+    unsigned int bytes_per_frame = m_encoder->rawBytesPerSample() * channels;
+    unsigned int buffer_size = qMax(qMax<int>(period_size, m_buffer_size),
+                                    m_output->bufferSize());
+    unsigned int buffer_frames =
+	((buffer_size * 2) + (bytes_per_frame - 1)) / bytes_per_frame;
+    int timeout = qMax(Kwave::toInt((1000 * buffer_frames) / rate), 100);
+    qDebug("    timeout = %d ms", timeout);
+    m_buffer.setTimeout(timeout);
+
+    if (m_output->error() != QAudio::NoError) {
+	qDebug("error no: %d", int(m_output->error()));
+	return i18n("Opening the Qt Multimedia device '%1' failed", device);
+    }
+
+    return QString();
+}
+
+//***************************************************************************
+int Kwave::PlayBackQt::write(const Kwave::SampleArray &samples)
+{
+    QByteArray frame;
+
+    {
+	QMutexLocker _lock(&m_lock); // context: worker thread
+
+	if (!m_encoder || !m_output) return -EIO;
+
+	int bytes_per_sample = m_encoder->rawBytesPerSample();
+	int bytes_raw        = samples.size() * bytes_per_sample;
+
+	frame.resize(bytes_raw);
+	frame.fill(char(0));
+	m_encoder->encode(samples, samples.size(), frame);
+    }
+
+    qint64 written = m_buffer.writeData(frame.constData(), frame.size());
+    if (written != frame.size()) {
+	qDebug("WARNING: Kwave::PlayBackQt::write: written=%lld/%d",
+	       written, frame.size());
+	return -EIO;
+    }
+
+    return 0;
+}
+
+//***************************************************************************
+void Kwave::PlayBackQt::stateChanged(QAudio::State state)
+{
+    Q_ASSERT(m_output);
+    if (!m_output) return;
+
+    if (m_output->error() != QAudio::NoError) {
+	qDebug("PlaybBackQt::stateChanged(%d), ERROR=%d, "
+	       "buffer free=%d",
+	       static_cast<int>(state),
+	       static_cast<int>(m_output->error()),
+	       m_output->bytesFree()
+	);
+    }
+    switch (state) {
+	case QAudio::ActiveState:
+	    qDebug("PlaybBackQt::stateChanged(ActiveState)");
+	    break;
+	case QAudio::SuspendedState:
+	    qDebug("PlaybBackQt::stateChanged(SuspendedState)");
+	    break;
+	case QAudio::StoppedState: {
+	    qDebug("PlaybBackQt::stateChanged(StoppedState)");
+	    break;
+	}
+	case QAudio::IdleState:
+	    qDebug("PlaybBackQt::stateChanged(IdleState)");
+	    break;
+	default:
+	    qWarning("PlaybBackQt::stateChanged(%d)",
+	             static_cast<int>(state));
+	    break;
+    }
+}
+
+//***************************************************************************
+QAudioDeviceInfo Kwave::PlayBackQt::deviceInfo(const QString &device) const
+{
+    // check for default device
+    if (!device.length() || (device == DEFAULT_DEVICE))
+	return QAudioDeviceInfo::defaultOutputDevice();
+
+    // check if the device name is known
+    if (m_device_name_map.isEmpty() || !m_device_name_map.contains(device))
+	return QAudioDeviceInfo();
+
+    // translate the path into a Qt audio output device name
+    // iterate over all available devices
+    const QString dev_name = m_device_name_map[device];
+    foreach (const QAudioDeviceInfo &dev, m_available_devices) {
+	if (dev.deviceName() == dev_name)
+	    return QAudioDeviceInfo(dev);
+    }
+
+    // fallen through: return empty info
+    return QAudioDeviceInfo();
+}
+
+//***************************************************************************
+int Kwave::PlayBackQt::close()
+{
+    qDebug("Kwave::PlayBackQt::close()");
+
+    QMutexLocker _lock(&m_lock); // context: main thread
+
+    if (m_output && m_encoder) {
+	unsigned int pad_bytes_cnt   = m_output->periodSize();
+	unsigned int bytes_per_frame = m_output->format().bytesPerFrame();
+	unsigned int pad_samples_cnt = pad_bytes_cnt / bytes_per_frame;
+	Kwave::SampleArray pad_samples(pad_samples_cnt);
+	QByteArray pad_bytes(pad_bytes_cnt, char(0));
+	m_encoder->encode(pad_samples, pad_samples_cnt, pad_bytes);
+
+	m_buffer.drain(pad_bytes);
+
+	// stopping the engine might block, so we need to do this unlocked
+	qDebug("Kwave::PlayBackQt::close() - flushing..., state=%d",
+	       m_output->state());
+	while (
+	    m_output &&
+	    (m_output->state() == QAudio::ActiveState) &&
+	    m_buffer.bytesAvailable()
+	) {
+	    m_lock.unlock();
+	    qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
+	    m_lock.lock();
+	}
+	qDebug("Kwave::PlayBackQt::close() - flushing done.");
+	m_lock.unlock();
+	m_output->stop();
+	m_buffer.stop();
+	m_lock.lock();
+    }
+
+    delete m_output;
+    m_output = 0;
+
+    delete m_encoder;
+    m_encoder = 0;
+
+    m_device_name_map.clear();
+    m_available_devices.clear();
+
+    qDebug("Kwave::PlayBackQt::close() - DONE");
+    return 0;
+}
+
+//***************************************************************************
+QStringList Kwave::PlayBackQt::supportedDevices()
+{
+    QMutexLocker _lock(&m_lock); // context: main thread
+
+    // re-validate the list if necessary
+    if (m_device_name_map.isEmpty() || m_available_devices.isEmpty())
+	scanDevices();
+
+    QStringList list = m_device_name_map.keys();
+
+    // move the "default" device to the start of the list
+    if (list.contains(DEFAULT_DEVICE))
+	list.move(list.indexOf(DEFAULT_DEVICE), 0);
+
+    if (!list.isEmpty()) list.append(_("#TREE#"));
+
+    return list;
+}
+
+//***************************************************************************
+void Kwave::PlayBackQt::scanDevices()
+{
+    m_available_devices.clear();
+    m_device_name_map.clear();
+
+    // get the list of available audio output devices from Qt
+    foreach (const QAudioDeviceInfo &device,
+	     QAudioDeviceInfo::availableDevices(QAudio::AudioOutput))
+    {
+	QString qt_name = device.deviceName();
+
+	// for debugging: list all devices
+// 	qDebug("name='%s'", DBG(qt_name));
+
+	// device name not available ?
+	if (!qt_name.length()) {
+	    qWarning("PlayBackQt::supportedDevices() "
+		      "=> BUG: device with no name?");
+	    continue;
+	}
+
+	QString gui_name = qt_name + _("|sound_note");
+	if (m_device_name_map.contains(gui_name)) {
+	    qWarning("PlayBackQt::supportedDevices() "
+	    "=> BUG: duplicate device name: '%s'", DBG(gui_name));
+	    continue;
+	}
+
+	m_available_devices.append(device);
+	m_device_name_map[gui_name] = qt_name;
+    }
+}
+
+//***************************************************************************
+QString Kwave::PlayBackQt::fileFilter()
+{
+    return _("");
+}
+
+//***************************************************************************
+QList<unsigned int> Kwave::PlayBackQt::supportedBits(const QString &device)
+{
+    QMutexLocker _lock(&m_lock); // context: main thread
+
+    QList<unsigned int> list;
+    const QAudioDeviceInfo info(deviceInfo(device));
+
+    // no devices at all -> empty list
+    if (info.isNull()) return list;
+
+    // iterate over all supported sample sizes
+    foreach (int bits, info.supportedSampleSizes()) {
+	if (!list.contains(bits) && (bits > 0))
+	    list <<  Kwave::toUint(bits);
+    }
+
+    std::sort(list.begin(), list.end(), std::greater<unsigned int>());
+    return list;
+}
+
+//***************************************************************************
+int Kwave::PlayBackQt::detectChannels(const QString &device,
+                                      unsigned int &min, unsigned int &max)
+{
+    QMutexLocker _lock(&m_lock); // context: main thread
+
+    const QAudioDeviceInfo info(deviceInfo(device));
+
+    max = std::numeric_limits<unsigned int>::min();
+    min = std::numeric_limits<unsigned int>::max();
+
+    // no devices at all -> empty
+    if (info.isNull()) return -1;
+
+    // iterate over all supported sample sizes
+    foreach (int channels, info.supportedChannelCounts()) {
+	if (channels <= 0) continue;
+	unsigned int c = Kwave::toUint(channels);
+	if (c < 1) continue;
+	if (c < min) min = c;
+	if (c > max) max = c;
+    }
+
+    return (max > 0) ? max : -1;
+}
+
+//***************************************************************************
+//***************************************************************************
+
+//***************************************************************************
+Kwave::PlayBackQt::Buffer::Buffer()
+    :QIODevice(),
+     m_lock(QMutex::Recursive),
+     m_sem_free(0),
+     m_sem_filled(0),
+     m_raw_buffer(),
+     m_timeout(1000),
+     m_pad_data(),
+     m_pad_ofs(0)
+{
+}
+
+//***************************************************************************
+Kwave::PlayBackQt::Buffer::~Buffer()
+{
+}
+
+//***************************************************************************
+void Kwave::PlayBackQt::Buffer::start(unsigned int buf_size, int timeout)
+{
+    m_raw_buffer.clear();
+    m_sem_filled.acquire(m_sem_filled.available());
+    m_sem_free.acquire(m_sem_free.available());
+    m_sem_free.release(buf_size);
+    m_timeout = timeout;
+    m_pad_data.clear();
+    m_pad_ofs = 0;
+
+    open(QIODevice::ReadOnly);
+}
+
+//***************************************************************************
+void Kwave::PlayBackQt::Buffer::setTimeout(int timeout)
+{
+    QMutexLocker _lock(&m_lock); // context: main thread
+    m_timeout = timeout;
+    qDebug("Kwave::PlayBackQt::Buffer::setTimeout(%d)", timeout);
+}
+
+//***************************************************************************
+void Kwave::PlayBackQt::Buffer::drain(QByteArray &padding)
+{
+    m_pad_data = padding;
+    m_pad_ofs  = 0;
+}
+
+//***************************************************************************
+void Kwave::PlayBackQt::Buffer::stop()
+{
+    close();
+}
+
+//***************************************************************************
+qint64 Kwave::PlayBackQt::Buffer::readData(char *data, qint64 len)
+{
+    qint64 read_bytes = -1;
+    qint64 requested  = len;
+
+//     qDebug("Kwave::PlayBackQt::Buffer::readData(..., len=%lld", requested);
+    if (len == 0) return  0;
+    if (len  < 0) return -1;
+
+    while (len > 0) {
+	int count = qMin(qMax<qint64>(m_sem_filled.available(), 1), len);
+	if (Q_LIKELY(m_sem_filled.tryAcquire(count, m_timeout))) {
+// 	    qDebug("    read: locking...");
+	    QMutexLocker _lock(&m_lock); // context: qt streaming engine
+// 	    qDebug("    read: locked, count=%lld", len);
+	    m_sem_free.release(count);
+	    if (read_bytes < 0) read_bytes = 0;
+	    read_bytes += count;
+	    len        -= count;
+	    while (count--)
+		*(data++) = m_raw_buffer.dequeue();
+	} else break;
+    }
+
+    // if we are at the end of the stream: do some padding to satisfy Qt
+    while ( (read_bytes < requested) &&
+	    !m_pad_data.isEmpty() && (m_pad_ofs < m_pad_data.size()) )
+    {
+	*(data++) = 0;
+	read_bytes++;
+	m_pad_ofs++;
+    }
+
+    if (read_bytes != requested)
+	qDebug("Kwave::PlayBackQt::Buffer::readData(...) -> read=%lld/%lld",
+	       read_bytes, requested);
+
+    return read_bytes;
+}
+
+//***************************************************************************
+qint64 Kwave::PlayBackQt::Buffer::writeData(const char *data, qint64 len)
+{
+
+    qint64 written_bytes = 0;
+    while (len) {
+	int count = qMin(qMax<qint64>(m_sem_free.available(), 1), len);
+	if (Q_LIKELY(m_sem_free.tryAcquire(count, m_timeout * 10))) {
+	    QMutexLocker _lock(&m_lock); // context: kwave worker thread
+	    m_sem_filled.release(count);
+	    written_bytes += count;
+	    len           -= count;
+	    while (count--)
+		m_raw_buffer.enqueue(*(data++));
+	} else break;
+    }
+
+    return written_bytes;
+}
+
+//***************************************************************************
+qint64 Kwave::PlayBackQt::Buffer::bytesAvailable() const
+{
+    return QIODevice::bytesAvailable() +
+           m_sem_filled.available() +
+           m_pad_data.size() -
+           m_pad_ofs;
+}
+
+#endif /* HAVE_QT_AUDIO_SUPPORT */
+
+//***************************************************************************
+//***************************************************************************
diff --git a/plugins/playback/PlayBack-Qt.h b/plugins/playback/PlayBack-Qt.h
new file mode 100644
index 0000000..d578bad
--- /dev/null
+++ b/plugins/playback/PlayBack-Qt.h
@@ -0,0 +1,248 @@
+/***************************************************************************
+          PlayBack-Qt.h  -  playback device for Qt Multimedia
+                             -------------------
+    begin                : Thu Nov 12 2015
+    copyright            : (C) 2015 by Thomas Eschenbacher
+    email                : Thomas.Eschenbacher at gmx.de
+ ***************************************************************************/
+
+/***************************************************************************
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *                                                                         *
+ ***************************************************************************/
+
+#ifndef PLAY_BACK_QT_H
+#define PLAY_BACK_QT_H
+
+#include "config.h"
+#ifdef HAVE_QT_AUDIO_SUPPORT
+
+#include <QAudio>
+#include <QAudioDeviceInfo>
+#include <QByteArray>
+#include <QIODevice>
+#include <QList>
+#include <QMap>
+#include <QMutex>
+#include <QObject>
+#include <QQueue>
+#include <QSemaphore>
+#include <QString>
+
+#include "libkwave/PlayBackDevice.h"
+#include "libkwave/SampleArray.h"
+
+class QAudioOutput;
+class QIODevice;
+
+namespace Kwave
+{
+
+    class SampleEncoder;
+
+    class PlayBackQt: public QObject,
+                      public Kwave::PlayBackDevice
+    {
+	Q_OBJECT
+    public:
+
+	/** Default constructor */
+	PlayBackQt();
+
+	/** Destructor */
+	virtual ~PlayBackQt();
+
+	/**
+	 * Opens the device for playback.
+	 * @see PlayBackDevice::open
+	 */
+	virtual QString open(const QString &device, double rate,
+	                     unsigned int channels, unsigned int bits,
+	                     unsigned int bufbase);
+
+	/**
+	 * Writes an array of samples to the output device.
+	 * @see PlayBackDevice::write
+	 */
+	virtual int write(const Kwave::SampleArray &samples);
+
+	/**
+	 * Closes the output device.
+	 * @see PlayBackDevice::close
+	 */
+	virtual int close();
+
+	/** return a string list with supported device names */
+	virtual QStringList supportedDevices();
+
+	/** return a string suitable for a "File Open..." dialog */
+	virtual QString fileFilter();
+
+	/**
+	 * returns a list of supported bits per sample resolutions
+	 * of a given device.
+	 *
+	 * @param device filename of the device
+	 * @return list of supported bits per sample, or empty on errors
+	 */
+	virtual QList<unsigned int> supportedBits(const QString &device);
+
+	/**
+	 * Detect the minimum and maximum number of channels.
+	 * If the detection fails, minimum and maximum are set to zero.
+	 *
+	 * @param device filename of the device
+	 * @param min receives the lowest supported number of channels
+	 * @param max receives the highest supported number of channels
+	 * @return zero or positive number if ok, negative error number if failed
+	 */
+	virtual int detectChannels(const QString &device,
+	                           unsigned int &min, unsigned int &max);
+
+    private slots:
+
+	/**
+	 * connected to the audio output device and gets notified in case
+	 * of state changes like start/stop of the stream or errors.
+	 * @param state the new device state, like active, stopped, idle etc.
+	 */
+	void stateChanged(QAudio::State state);
+
+    private:
+
+	/**
+	 * creates a sample encoder for playback, for linear
+	 * formats
+	 * @param format the preferred format of the audio output device
+	 */
+	void createEncoder(const QAudioFormat &format);
+
+	/** scan all Qt audio output devices, re-creates m_device_list */
+	void scanDevices();
+
+	/**
+	 * Gets the full device info of a playback device, identified by
+	 * the device name.
+	 *
+	 * @param device name of the device or empty string for default
+	 * @return a QAudioDeviceInfo
+	 */
+	QAudioDeviceInfo deviceInfo(const QString &device) const;
+
+    private:
+
+	class Buffer : public QIODevice
+	{
+	public:
+	    /** constructor */
+	    Buffer();
+
+	    /** destructor */
+	    virtual ~Buffer();
+
+	    /**
+	     * start filling the buffer
+	     * @param buf_size size of the buffer in bytes
+	     * @param timeout read/write timeout [ms]
+	     */
+	    void start(unsigned int buf_size, int timeout);
+
+	    /**
+	     * set a new read/write timeout
+	     * @note does not influence currently waiting reads/writes
+	     * @param timeout a new read/write timeout [ms]
+	     */
+	    void setTimeout(int timeout);
+
+	    /**
+	     * drain the sink, at the end of playback:
+	     * provide padding to provide data for a full period
+	     * @param padding array of bytes used for padding
+	     */
+	    void drain(QByteArray &padding);
+
+	    /** stop filling the buffer */
+	    void stop();
+
+	    /**
+	     * read data out from the buffer, called from the Qt audio device
+	     * side
+	     * @param data pointer to a buffer (of bytes) to receive the data
+	     * @param len number of bytes to read
+	     * @return number of bytes that have been read
+	     */
+	    virtual qint64 readData(char *data, qint64 len);
+
+	    /**
+	     * write data into the buffer, called from our own worker thread
+	     * @param data pointer to a buffer (of bytes) to write
+	     * @param len number of bytes to write
+	     * @return number of bytes written
+	     */
+	    virtual qint64 writeData(const char *data, qint64 len);
+
+	    /** returns the number of bytes available for reading */
+	    virtual qint64 bytesAvailable() const;
+
+	private:
+
+	    /** mutex for locking the queue */
+	    QMutex m_lock;
+
+	    /** semaphore with free buffer space */
+	    QSemaphore m_sem_free;
+
+	    /** semaphore with filled buffer space */
+	    QSemaphore m_sem_filled;
+
+	    /** raw buffer with audio data */
+	    QQueue<char> m_raw_buffer;
+
+	    /** read timeout [ms] */
+	    int m_timeout;
+
+	    /** buffer with padding data */
+	    QByteArray m_pad_data;
+
+	    /** read pointer within m_pad_data */
+	    int m_pad_ofs;
+	};
+
+    private:
+
+	/** mutex for locking the streaming thread against main thread */
+	QMutex m_lock;
+
+	/**
+	 * dictionary for translating verbose device names
+	 * into Qt audio output device names
+	 * (key = verbose name, data = Qt output device name)
+	 */
+	QMap<QString, QString> m_device_name_map;
+
+	/** list of available Qt output devices */
+	QList<QAudioDeviceInfo> m_available_devices;
+
+	/** Qt audio output instance */
+	QAudioOutput *m_output;
+
+	/** buffer size in bytes */
+	unsigned int m_buffer_size;
+
+	/** encoder for converting from samples to raw format */
+	Kwave::SampleEncoder *m_encoder;
+
+	Kwave::PlayBackQt::Buffer m_buffer;
+    };
+}
+
+#endif /* HAVE_QT_AUDIO_SUPPORT */
+
+#endif /* PLAY_BACK_QT_H */
+
+//***************************************************************************
+//***************************************************************************
diff --git a/plugins/playback/PlayBackPlugin.cpp b/plugins/playback/PlayBackPlugin.cpp
index bd38179..b420ce9 100644
--- a/plugins/playback/PlayBackPlugin.cpp
+++ b/plugins/playback/PlayBackPlugin.cpp
@@ -60,6 +60,7 @@
 #include "PlayBack-ALSA.h"
 #include "PlayBack-OSS.h"
 #include "PlayBack-PulseAudio.h"
+#include "PlayBack-Qt.h"
 
 #include "PlayBackDialog.h"
 #include "PlayBackPlugin.h"
@@ -230,6 +231,10 @@ QList<Kwave::playback_method_t> Kwave::PlayBackPlugin::supportedMethods()
 {
     QList<Kwave::playback_method_t> methods;
 
+#ifdef HAVE_QT_AUDIO_SUPPORT
+    methods.append(Kwave::PLAYBACK_QT_AUDIO);
+#endif /* HAVE_QT_AUDIO_SUPPORT */
+
 #ifdef HAVE_PULSEAUDIO_SUPPORT
     methods.append(Kwave::PLAYBACK_PULSEAUDIO);
 #endif /* HAVE_PULSEAUDIO_SUPPORT */
@@ -255,6 +260,11 @@ Kwave::PlayBackDevice *Kwave::PlayBackPlugin::createDevice(
 	   static_cast<int>(method) );
 
     switch (method) {
+#ifdef HAVE_QT_AUDIO_SUPPORT
+	case Kwave::PLAYBACK_QT_AUDIO:
+	    return new Kwave::PlayBackQt();
+#endif /* HAVE_QT_AUDIO_SUPPORT */
+
 #ifdef HAVE_PULSEAUDIO_SUPPORT
 	case Kwave::PLAYBACK_PULSEAUDIO:
 	    return new Kwave::PlayBackPulseAudio(


More information about the kde-doc-english mailing list