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 ¶ms) //*************************************************************************** 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 : [email protected] + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 : [email protected] + ***************************************************************************/ + +/*************************************************************************** + * * + * 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( _______________________________________________ kde-doc-english mailing list [email protected] https://mail.kde.org/mailman/listinfo/kde-doc-english
